[
  {
    "path": ".bazelignore",
    "content": "node_modules\nimages/container-client-test/node_modules\nsrc/workerd/api/tests/opennextjs/src/node_modules\n\n# Nested modules\nbuild/google-benchmark\nbuild/perfetto\nbuild/workerd-v8\n"
  },
  {
    "path": ".bazelrc",
    "content": "common --enable_platform_specific_config\nbuild --verbose_failures\nbuild --build_tag_filters=-off-by-default,-requires-container-engine\n# block network access by default\nbuild --nosandbox_default_allow_network\ntest --test_tag_filters=-off-by-default,-requires-fuzzilli,-requires-container-engine\ntest:asan --test_tag_filters=-off-by-default,-no-asan,-requires-fuzzilli,-requires-container-engine\n# exclude enormous tests by default\nbuild --test_size_filters=-enormous\n\n# use lower test timeouts: https://bazel.build/reference/test-encyclopedia#role-test-runner\n# corresponds to small,medium,large,enormous tests (medium is default)\nbuild --test_timeout=3,15,60,240\n\n# As part of starlarkification, Bazel 8 and 9 remove a number of rules which now need to be imported\n# from other repositories. In the long term, load() statements will be needed to import these rules.\n# For now, we can still autoload the affected repositories when needed. Do this only for a remaining\n# protobuf components and rules_cc (needed with Bazel 9).\ncommon --incompatible_autoload_externally=\"+ProtoInfo,+cc_binary,+cc_import,+cc_library,+cc_shared_library,+cc_test,+cc_toolchain\"\n\n# TODO(cleanup): Bazel 9 sets this by default, which breaks the macOS-cross build. Fix and re-enable.\ncommon --@bazel_tools//tools/test:incompatible_use_default_test_toolchain=False\n\n# Enable proto toolchain resolution to use prebuilt protoc (starting with protobuf v34.0). Will be\n# the default starting with Bazel 10.\ncommon --incompatible_enable_proto_toolchain_resolution\n\n# bazel7 enables Build without the Bytes (BwoB) by default. This significantly speeds up builds\n# using the remote cache since less data needs to be fetched.\n# Note that we use remote_download_minimal for test builds, which avoids fetching build outputs\n# where possible. While several previous BwoB bugs have been fixed, this is slower than it could be\n# due to https://github.com/bazelbuild/bazel/issues/20576.\n\n# Import CI-specific configuration. As the amount of custom configuration settings we use grows,\n# consider moving more flags out to separate files.\nimport %workspace%/build/ci.bazelrc\nimport %workspace%/build/rust_lint.bazelrc\nimport %workspace%/build/tools/clang_tidy/clang_tidy.bazelrc\n\n# Continue building locally when remote cache entries fail to materialize\nbuild --incompatible_remote_local_fallback_for_remote_cache\n\n# TODO(soon): Flipped by default in Bazel 9, add required variables to --repo_env and enable\nbuild --noincompatible_repo_env_ignores_action_env\n\n# Use -isystem for cc_library includes attribute – this prevents warnings for misbehaving external\n# code.\nbuild:linux --features=external_include_paths --host_features=external_include_paths\n\n# Forward compatibility with future Bazel versions:\n# Disable deprecated cfg = \"host\" Bazel rule setting. Blocked on perfetto.\n# common --incompatible_disable_starlark_host_transitions\n\n# Our dependencies (ICU, zlib, etc.) produce a lot of these warnings, so we disable them.\nbuild --per_file_copt='external@-Wno-error'\nbuild --per_file_copt='external@-Wno-suggest-override'\nbuild --per_file_copt='external/.*v8@-Wno-unused-function'\nbuild --per_file_copt='external/zlib@-Wno-deprecated-non-prototype'\nbuild --host_per_file_copt='external/zlib@-Wno-deprecated-non-prototype'\nbuild --per_file_copt=external/protobuf@-Wno-deprecated-declarations\nbuild --host_per_file_copt=external/protobuf@-Wno-deprecated-declarations\n\n# opt in to capnp deprecation warnings about trying to attach to a refcounted object\nbuild --cxxopt=-DKJ_WARN_REFCOUNTED_ATTACH=1\n\n# TODO(cleanup): Causes warnings with LLVM20, fix and enable again\nbuild --copt=-Wno-nontrivial-memaccess\n\n# Unconditionally optimize V8 heap.cc on macOS – when optimization or inlining are disabled, this\n# file appears to cause segfaults at workerd startup on macOS.\n# TODO(cleanup): Investigate why this happens, our patches do not modify heap.cc itself so the bug\n# might be introduced through a header included in heap.cc, through the Bazel build configuration or\n# a bug in Apple LLVM.\nbuild:macos --per_file_copt=v8/src/heap/heap.cc@-O3\n\n# The macOS apple_support toolchain sets -DDEBUG for fastbuild, unlike the Linux toolchain. This is\n# unhelpful for compile speeds and test performance, and may cause compile errors based on V8 DCHECK\n# macros that reference symbols only available with V8_VERIFY_WRITE_BARRIERS (a debug-only define),\n# causing compile errors. Undefine DEBUG to match Linux behavior.\nbuild:macos --copt=-UDEBUG\n\n# Increasing the optimization level of V8 significantly speeds up tests using V8 a lot, especially\n# python tests. This is useful for both CI and local development and enabled by default, but still\n# kept in a separate configuration to make it easy to disable.\nbuild:v8-codegen-opt --per_file_copt=v8/src@-O3\n# V8 is heavily using absl for hashing now, optimize it too.\nbuild:v8-codegen-opt --per_file_copt=external/abseil-cpp@-O3\n# zlib and tcmalloc (for Linux) are also CPU-intensive, optimize them too.\nbuild:v8-codegen-opt --per_file_copt=external/tcmalloc@-O3\nbuild:v8-codegen-opt --per_file_copt=external/zlib@-O3\n# BoringSSL is CPU-intensive for crypto tests, optimize it too.\nbuild:v8-codegen-opt --per_file_copt=external/boringssl@-O3\n# simdutf is used for fast string encoding/decoding\nbuild:v8-codegen-opt --per_file_copt=external/+http+simdutf@-O3\n# ICU and perfetto are generally updated with V8 and rarely need to be updated, optimize them too.\nbuild:v8-codegen-opt --per_file_copt=external/.*com_googlesource_chromium_icu@-O3\nbuild:v8-codegen-opt --per_file_copt=external/perfetto@-O3\n\nbuild:v8-codegen-opt-windows --per_file_copt=v8/src@/O2,/clang:-O3\nbuild:v8-codegen-opt-windows --per_file_copt=external/abseil-cpp@/O2,/clang:-O3\nbuild:v8-codegen-opt-windows --per_file_copt=external/zlib@/O2,/clang:-O3\nbuild:v8-codegen-opt-windows --per_file_copt=external/boringssl@/O2,/clang:-O3\nbuild:v8-codegen-opt-windows --per_file_copt=external/+http+simdutf@/O2,/clang:-O3\nbuild:v8-codegen-opt-windows --per_file_copt=external/.*com_googlesource_chromium_icu@/O2,/clang:-O3\nbuild:v8-codegen-opt-windows --per_file_copt=external/perfetto@/O2,/clang:-O3\n\nbuild:unix --config=v8-codegen-opt\nbuild:windows  --config=v8-codegen-opt-windows\n\n# In Google projects, exceptions are not used as a rule. Disabling them is more consistent with the\n# canonical V8 build and improves code size. Paths are adjusted for bzlmod mangling – V8 and ICU use\n# a wildcard for this as the prefix is some variation of +_repo_rules3+ where the number can change\n# when refactoring MODULE.bazel – setting this directly would be too brittle\nbuild:unix --per_file_copt=external/abseil-cpp@-fno-exceptions\nbuild:unix --per_file_copt=external/protobuf@-fno-exceptions\nbuild:unix --per_file_copt=external/tcmalloc@-fno-exceptions\nbuild:unix --per_file_copt=external/.*com_googlesource_chromium_icu@-fno-exceptions\nbuild:unix --per_file_copt=external/perfetto@-fno-exceptions\nbuild:unix --per_file_copt=external/boringssl@-fno-exceptions\nbuild:unix --per_file_copt=external/.*v8@-fno-exceptions\nbuild:unix --per_file_copt=external/ada-url@-fno-exceptions\nbuild:unix --per_file_copt=external/+http+simdutf@-fno-exceptions\nbuild:windows --per_file_copt=external/abseil-cpp@/clang:-fno-exceptions\nbuild:windows --per_file_copt=external/protobuf@/clang:-fno-exceptions\nbuild:windows --per_file_copt=external/tcmalloc@/clang:-fno-exceptions\nbuild:windows --per_file_copt=external/.*com_googlesource_chromium_icu@/clang:-fno-exceptions\nbuild:windows --per_file_copt=external/perfetto@/clang:-fno-exceptions\nbuild:windows --per_file_copt=external/boringssl@/clang:-fno-exceptions\nbuild:windows --per_file_copt=external/.*v8@/clang:-fno-exceptions\nbuild:windows --per_file_copt=external/ada-url@/clang:-fno-exceptions\nbuild:windows --per_file_copt=external/+http+simdutf@/clang:-fno-exceptions\n\n# V8 torque is an exception from this policy, see v8 BUILD.gn.\nbuild:unix --per_file_copt=external/.*v8/src/torque@-fexceptions\nbuild:windows --per_file_copt=external/.*v8/src/torque@/clang:-fexceptions\n\n# Limit transitive header includes within libc++. This improves compliance with IWYU, helps avoid\n# errors with downstream projects that implicitly define this already and reduces total include size.\nhttps://libcxx.llvm.org/DesignDocs/HeaderRemovalPolicy.html\nbuild:unix --cxxopt=-D_LIBCPP_REMOVE_TRANSITIVE_INCLUDES --host_cxxopt=-D_LIBCPP_REMOVE_TRANSITIVE_INCLUDES\n# Do not enable libc++ ABI tags. This makes mangled symbol names and thus include overhead and code\n# size slightly smaller and is safe as long as we don't link with several copies of libc++.\nbuild:unix --cxxopt=-D_LIBCPP_NO_ABI_TAG --host_cxxopt=-D_LIBCPP_NO_ABI_TAG\n# Disable the experimental (and currently incomplete) parallel STL implementation as with\n# downstream, this reduces the include overhead for <algorithm>.\nbuild:unix --cxxopt=-D_LIBCPP_HAS_NO_INCOMPLETE_PSTL --host_cxxopt=-D_LIBCPP_HAS_NO_INCOMPLETE_PSTL\n\n# V8 redefines the _WIN32_WINNT set by bazel, disable warnings for redefined macros. Since V8 uses\n# a global define for this, we need to apply it for everything.\n# TODO(cleanup): Patch upstream V8 to use local_defines for this instead.\nbuild:windows --copt='-Wno-macro-redefined'\nbuild:windows --host_copt='-Wno-macro-redefined'\n\n# typescript configuration\n# do more correct type checking\ncommon --@aspect_rules_ts//ts:skipLibCheck=honor_tsconfig\n# Use \"tsc\" as the transpiler when ts_project has no `transpiler` set.\ncommon --@aspect_rules_ts//ts:default_to_tsc_transpiler\n\n# optimized LTO build.\nbuild:thin-lto --config=opt\nbuild:thin-lto --copt='-flto=thin'\nbuild:thin-lto --linkopt='-flto=thin'\n\n# configuration used for performance profiling\nbuild:profile --config=thin-lto\nbuild:profile --copt=\"-fno-omit-frame-pointer\" --copt=\"-mno-omit-leaf-frame-pointer\"\nbuild:profile --config=limited-dbg-info\nbuild:profile --strip=never\n\n# configuration used for performance benchmarking is the same as profiling for consistency\nbuild:benchmark --config=profile\n\n# Define a debug config which is primarily intended for local development.\nbuild:debug -c dbg\n\n# Using simple template names saves around 5% of binary size of workerd.\nbuild:unix --cxxopt='-gsimple-template-names' --host_cxxopt='-gsimple-template-names'\n# Enable hidden visibility for inline functions. This can cause problems when comparing pointers to\n# inline functions across shared library boundaries, but this is unlikely to be done anywhere\n# within workerd, V8 explicitly supports hidden visibility.\nbuild:unix --cxxopt='-fvisibility-inlines-hidden' --host_cxxopt='-fvisibility-inlines-hidden'\n\n# Define a config mode which is fastbuild but with basic debug info.\nbuild:fastdbg -c fastbuild\nbuild:fastdbg --config=limited-dbg-info\nbuild:fastdbg --config=rust-debug\nbuild:fastdbg --strip=never\nbuild:fastdbg --//:dead_strip=False\n\n# provide a limited amount of debug info, sufficient for qualified stack traces.\nbuild:limited-dbg-info --copt='-g1' --copt=\"-fdebug-info-for-profiling\"\n\n# Miscellaneous platform-independent options\nbuild --@capnp-cpp//src/kj:openssl=True --@capnp-cpp//src/kj:zlib=True --@capnp-cpp//src/kj:brotli=True\nbuild --@capnp-cpp//src/capnp:gen_rust=True\n# always have KJ_IREQUIRE checks enabled, this matches workers production.\nbuild --@capnp-cpp//src/kj:kj_enable_irequire=True\n# overriden in config=opt\nbuild --@capnp-cpp//src/kj:debug_memory=True\nbuild --cxxopt=\"-fbracket-depth=512\" --host_cxxopt=\"-fbracket-depth=512\"\n\n# Additional Rust flags (see https://doc.rust-lang.org/rustc/codegen-options/index.html)\n# Need to disable debug-assertions for fastbuild, should be off automatically for opt.\n# Using extra_rustc_flag for non-exec flags so they can be overwritten selectively.\nbuild --@rules_rust//:extra_rustc_flag=-Cdebug-assertions=n\nbuild --@rules_rust//:extra_exec_rustc_flags=-Cdebug-assertions=n\nbuild --@rules_rust//:rustfmt.toml=//src/rust:rustfmt.toml\n\n# We default to not enabling debug assertions and unwinding for Rust. For debug builds this is not\n# the right setting, but unfortunately we can't set that directly.\nbuild:rust-debug --@rules_rust//:extra_rustc_flag=-Cdebug-assertions=y\n# Use the equivalent of -g/-g2 for Rust here, this is necessary to get qualified stack traces while\n# -Zdebug-info-for-profiling is unavailable. Unlike gcc/clang, Rust defaults to -g3 otherwise.\nbuild:rust-debug --@rules_rust//:extra_rustc_flag=-Cdebuginfo=1\nbuild:rust-debug --@rules_rust//:extra_rustc_flag=-Cstrip=none\n\n# Adding -C lto=thin here would improve binary size significantly – disable it for now due to\n# compile errors and wrong code generation when bazel and rust use different LLVM versions.\nbuild:thin-lto --@rules_rust//:extra_rustc_flag=-Ccodegen-units=1\n\n# common sanitizers options\nbuild:sanitizer-common --@workerd//src/workerd/server:use_tcmalloc=False\nbuild:sanitizer-common --copt=\"-fsanitize-link-c++-runtime\" --linkopt=\"-fsanitize-link-c++-runtime\"\nbuild:sanitizer-common --copt=\"-Og\"\nbuild:sanitizer-common --copt=\"-g\" --strip=never\nbuild:sanitizer-common --copt=\"-fno-optimize-sibling-calls\"\nbuild:sanitizer-common --copt=\"-fno-omit-frame-pointer\" --copt=\"-mno-omit-leaf-frame-pointer\"\n\n# address sanitizer (https://github.com/google/sanitizers/wiki/AddressSanitizer)\nbuild:asan --config=sanitizer-common\nbuild:asan --copt=\"-fsanitize=address\" --linkopt=\"-fsanitize=address\"\nbuild:asan --test_env=ASAN_OPTIONS=abort_on_error=true\nbuild:asan --test_env=KJ_CLEAN_SHUTDOWN=1\n# Enable ASan, LSan support in V8\nbuild:asan --copt=\"-DV8_USE_ADDRESS_SANITIZER\"\nbuild:asan --per_file_copt='external/.*v8@-DADDRESS_SANITIZER,-DLEAK_SANITIZER'\n\n# fuzzilli (https://github.com/googleprojectzero/fuzzilli/)\nbuild:fuzzilli --config=asan\nbuild:fuzzilli --copt=\"-DWORKERD_FUZZILLI\"\nbuild:fuzzilli --linkopt=\"-DWORKERD_FUZZILLI\"\nbuild:fuzzilli --copt=\"-fsanitize-coverage=trace-pc-guard\"\nbuild:fuzzilli --linkopt=\"-fsanitize-coverage=trace-pc-guard\"\nbuild:fuzzilli --linkopt=\"-static-libasan\"\n# Set ASan/UBSan options globally to abort on error (raise SIGABRT) for proper Fuzzilli crash detection\nbuild:fuzzilli --action_env=ASAN_OPTIONS=abort_on_error=1:symbolize=false\nbuild:fuzzilli --action_env=UBSAN_OPTIONS=abort_on_error=1:symbolize=false\n# Override test filters to include requires-fuzzilli tests (inherits asan's -no-asan filter)\ntest:fuzzilli --test_tag_filters=-off-by-default\n\n#\n# Linux and macOS\n#\nbuild:unix --workspace_status_command=./tools/unix/workspace-status.sh\n\nbuild:unix --cxxopt='-std=c++23' --host_cxxopt='-std=c++23'\nbuild:unix --@capnp-cpp//src/kj:libdl=True\n\n# Bazel uses CC to compile C and C++ actions, no need to define CXX.\nbuild:unix --action_env=BAZEL_COMPILER=clang\nbuild:unix --action_env=CC=clang\n\nbuild:unix --test_env=LLVM_SYMBOLIZER=llvm-symbolizer\n\n# Warning options.\nbuild:unix --cxxopt='-Wall' --host_cxxopt='-Wall'\nbuild:unix --cxxopt='-Wextra' --host_cxxopt='-Wextra'\nbuild:unix --cxxopt='-Wunused-function' --host_cxxopt='-Wunused-function'\nbuild:unix --cxxopt='-Wunused-lambda-capture' --host_cxxopt='-Wunused-lambda-capture'\nbuild:unix --cxxopt='-Wunused-variable' --host_cxxopt='-Wunused-variable'\nbuild:unix --cxxopt='-Wno-sign-compare' --host_cxxopt='-Wno-sign-compare'\nbuild:unix --cxxopt='-Wno-unused-parameter' --host_cxxopt='-Wno-unused-parameter'\nbuild:unix --cxxopt='-Wno-missing-field-initializers' --host_cxxopt='-Wno-missing-field-initializers'\nbuild:unix --cxxopt='-Wsuggest-override'\n\nbuild:linux --config=unix\nbuild:macos --config=unix\n\n# Support macOS 13 as the minimum version. There should be at least a warning when backward\n# compatibility is broken as -Wunguarded-availability-new is enabled by default. Only enable for\n# target configuration as host configuration tools are only used during the build process.\nbuild:macos --macos_minimum_os=13.5\n\n# Avoid emitting duplicate unwind info where compact unwind info can be used. This reduces the\n# object size by ~5% on average, improving disk space usage and link times. The final binary size\n# is not affected. Note that this is on-by-default on arm64, but turning it on for all mac builds\n# is easier than adding the flag only on x86. See https://reviews.llvm.org/D122258 for detailed\n# information on the flag.\nbuild:macos --copt='-femit-dwarf-unwind=no-compact-unwind'\n\n# Cross-Compilation\n# Only cross-compiling on macOS from Apple Silicon to x86_64 is supported – using apple_support\n# makes this much easier than on other platforms. We could define a configuration for cross-\n# compiling from Intel Mac too, but it lacks a means to run Apple Silicon binaries. We would have to\n# change V8 mksnapshot to build in the host configuration again (effectively compiling much of V8\n# twice) and couldn't run tests, so it would provide little value.\n#\n# Define the target platform\nbuild:macos-cross-x86_64 --cpu=darwin_x86_64 --host_cpu=darwin_arm64 --platforms //:macOS_x86\n# Some cross-compiled tests are slower when run over Rosetta, increase the medium test size timeout.\n# Test performance is still very much satisfactory considering that emulation is being used here.\nbuild:macos-cross-x86_64 --test_timeout=1,30,60,240\n\n# On Linux, always link libc++ statically to avoid compatibility issues with different OS versions.\n# macOS links with dynamic libc++ by default, which has good backwards compatibility.\n# Drop default link flags, which include libstdc++ for Linux\nbuild:linux --features=-default_link_libs --host_features=-default_link_libs\nbuild:linux --cxxopt='-stdlib=libc++' --host_cxxopt='-stdlib=libc++'\nbuild:linux --linkopt='-stdlib=libc++' --host_linkopt='-stdlib=libc++'\nbuild:linux --linkopt='-l:libc++.a' --linkopt='-lm' --linkopt='-static-libgcc'\n# We don't expect to distribute host tools, no need to statically link libgcc.\n# TODO(cleanup): Ideally we'd also use shared libc++ here, we'd just need to install the\n# libunwind-<version>-dev and libc++abi-<version>-dev packages on CI to have all of shared libc++\n# available.\nbuild:linux --host_linkopt='-l:libc++.a' --host_linkopt='-lm'\n\n# On Linux, enable PIC. In macos pic is the default, and the objc_library rule does not work\n# correctly if we use this flag since it will not find the object files to include\n# https://github.com/bazelbuild/bazel/issues/12439#issuecomment-914449079\nbuild:linux --force_pic\n\n# On Linux, garbage collection sections and optimize binary size. These do not apply to the macOS\n# toolchain.\nbuild:linux --linkopt=\"-Wl,--gc-sections\"\nbuild:linux --copt=\"-ffunction-sections\" --host_copt=\"-ffunction-sections\"\nbuild:linux --copt=\"-fdata-sections\" --host_copt=\"-fdata-sections\"\n\n# On Linux, use clang lld.\nbuild:linux --linkopt=\"-fuse-ld=lld\"\n\n#\n# Windows\n#\n\n# See https://bazel.build/configure/windows#symlink\nstartup --windows_enable_symlinks\nbuild:windows --workspace_status_command=./tools/windows/workspace-status.cmd\nbuild:windows --enable_runfiles\n# We use LLVM's MSVC-compatible compiler driver to compile our code on Windows,\n# as opposed to using MSVC directly. This enables us to use the \"same\" compiler\n# frontend on Linux, macOS, and Windows, massively reducing the effort required\n# to compile workerd on Windows. Notably, this provides proper support for\n# `#pragma once` when using symlinked virtual includes, `__atomic_*` functions,\n# a standards-compliant preprocessor, support for GNU statement expressions\n# used by some KJ macros, and understands the `.c++` extension by default.\n# As of bazel 7, toolchain resolution is enabled by default, so we need to define a platform for\n# the clang-cl build.\nbuild:windows --extra_toolchains=@local_config_cc//:cc-toolchain-x64_windows-clang-cl\nbuild:windows --extra_execution_platforms=//:x64_windows-clang-cl\n\n# The Windows fastbuild bazel configuration is broken in that it necessarily generates PDB debug\n# information while the Linux and macOS toolchains only compile with debug information in the dbg\n# configuration or when requested with the -g flag. This causes huge increases in compile time and\n# disk/cache space usage – a single test may come with a 490MB PDB file.\n# In an optional configuration, use the opt configuration and manually disable optimizations as a\n# workaround.\n\nbuild:windows_no_dbg -c opt\nbuild:windows_no_dbg --copt='/Od'\nbuild:windows_no_dbg --linkopt='/INCREMENTAL:NO'\nbuild:windows_no_dbg --features=-smaller_binary\n\n# Mitigate the large size impact of Windows debug binaries somewhat by applying string merging and\n# linker garbage collection.\nbuild:windows_dbg --config=debug\nbuild:windows_dbg --copt='/Gy' --copt='/Gw'\nbuild:windows_dbg --linkopt='/OPT:REF'\nbuild:windows_dbg --linkopt='/OPT:LLDTAILMERGE' --linkopt='/OPT:SAFEICF'\n\n# This hides inline symbols in classes that are marked to be exported, similar to\n# -fvisibility-inlines-hidden on Unix systems (https://blog.llvm.org/2018/11/30-faster-windows-builds-with-clang-cl_14.html)\n# Currently this only reduces object sizes slightly, larger gains are possible if we compile V8 as\n# a shared library.\nbuild:windows --copt='/Zc:dllexportInlines-'\n\n# Configuration for header parsing. Requires bazel toolchain support (available for macOS, Linux).\nbuild:parse_headers --features=parse_headers --process_headers_in_dependencies\n# Silence some capnproto warnings/errors that are not relevant for header parsing\nbuild:parse_headers --per_file_copt='.*\\.h@-Wno-unused-function,-Wno-pragma-system-header-outside-header'\nbuild:parse_headers --per_file_copt=external/+http+capnp-cpp/src/kj/common.h@-Wno-unreachable-code\nbuild:parse_headers --per_file_copt=external/+http+capnp-cpp/src/capnp/arena.h@-DCAPNP_PRIVATE\n\n# optimized configuration\nbuild:opt -c opt\nbuild:opt --@capnp-cpp//src/kj:debug_memory=False\n\n# Release configuration.\nbuild:release --config=opt\nbuild:release --@rules_rust//:extra_rustc_flag=-Ccodegen-units=1\n\n# enable -O3 for the release configuration. Based on benchmarking there is little difference in\n# performance, but -O3 should generally be expected to be faster for at least parts of the workerd API.\nbuild:release_unix --copt='-O3'\nbuild:release_unix --config=release\n\nbuild:release_linux --config=release_unix\nbuild:release_linux --linkopt=\"-Wl,-O2\"\n\nbuild:release_macos --config=release_unix\n# Disable generating LC_DATA_IN_CODE and LC_FUNCTION_STARTS binary sections, two rarely used types\n# of macOS debug info. These sections are largely undocumented, but are used by LLDB to improve\n# debugging on binaries that are otherwise stripped. There should be no need to include this\n# data in releases.\nbuild:release_macos --linkopt=\"-Wl,-no_data_in_code_info\"\nbuild:release_macos --linkopt=\"-Wl,-no_function_starts\"\n\n# Cross-compiled release build for x86_64.\nbuild:release_macos_cross_x86_64 --config=release_macos\nbuild:release_macos_cross_x86_64 --config=macos-cross-x86_64\n\n# On macOS, optionally compile using LLD (19 or higher is compatible with the default flags added by\n# apple_support). Requires Homebrew's lld package to be installed and symlinked into /usr/local/bin.\n# This is less CPU intensive than the system linker, but also slightly slower in terms of wall time\n# since it is less parallel. More importantly, it allows us to enable LLD's ICF pass, which\n# significantly decreases binary sizes. We could use Xcode 16's -Wl,-deduplicate option instead, but\n# LLD's ICF appears to be superior. We also want to enable ICF for Linux, but there it causes\n# warnings when dynamically linking with libc++.\nbuild:macos_lld --linkopt=-fuse-ld=lld --linkopt=--ld-path=/usr/local/bin/ld64.lld\nbuild:macos_lld_icf --config=macos_lld\nbuild:macos_lld_icf --linkopt=\"-Wl,--icf=safe\"\n\nbuild:release_windows --config=release\n# Windows uses /O2 as its preferred optimization setting and enabled by bazel in the opt\n# configuration, but for clang-cl this is equivalent to only -O2 and a few other things. -O3 is\n# not exposed directly in the clang-cl driver, but we can access regular clang\n# flags using the /clang prefix anyway. https://clang.llvm.org/docs/UsersManual.html#the-clang-option\nbuild:release_windows --copt=\"/clang:-O3\"\n# clang-cl does not enable strict aliasing by default to match MSVC's approach, unlike clang on\n# Unix which turns it on for opt builds.\nbuild:release_windows --copt=\"-fstrict-aliasing\"\n# This file breaks our CI windows release builds when compiled using O2/O3\n# Ref: https://github.com/llvm/llvm-project/issues/136481\nbuild:release_windows --per_file_copt=.*capnp/rpc\\.c++@/clang:-O1\n\nbuild:windows --cxxopt='/std:c++23preview' --host_cxxopt='/std:c++23preview'\nbuild:windows --copt='/D_CRT_USE_BUILTIN_OFFSETOF' --host_copt='/D_CRT_USE_BUILTIN_OFFSETOF'\nbuild:windows --copt='/DWIN32_LEAN_AND_MEAN' --host_copt='/DWIN32_LEAN_AND_MEAN'\nbuild:windows --copt='/EHs' --host_copt='/EHs'\n# The `/std:c++23preview` argument is unused during BoringSSL compilation and we don't\n# want a warning when compiling each file.\nbuild:windows --per_file_copt=external/boringssl@-Wno-unused-command-line-argument --host_per_file_copt=external/boringssl@-Wno-unused-command-line-argument\n\n# MSVC disappointingly sets __cplusplus to 199711L by default. Defining /Zc:__cplusplus makes it\n# set the correct value. We currently don't check __cplusplus, but some dependencies do.\nbuild:windows --cxxopt='/Zc:__cplusplus' --host_cxxopt='/Zc:__cplusplus'\n\n# Coverage configuration using LLVM tools. These environment variables are used by rules_cc\n# to locate LLVM coverage tools. Users should ensure llvm-profdata and llvm-cov are available\n# in PATH (create symlinks to version-specific binaries if needed, e.g., ln -s llvm-cov-19 llvm-cov).\nbuild:coverage --action_env=BAZEL_USE_LLVM_NATIVE_COVERAGE=1\nbuild:coverage --test_env=BAZEL_USE_LLVM_NATIVE_COVERAGE=1\n# GCOV is used by rules_cc to merge raw profile data (.profraw) into indexed profile data (.profdata)\nbuild:coverage --action_env=GCOV=llvm-profdata\nbuild:coverage --test_env=GCOV=llvm-profdata\n# COVERAGE_GCOV_PATH is used by collect_cc_coverage.sh for merging .profraw files.\nbuild:coverage --action_env=COVERAGE_GCOV_PATH=/usr/bin/llvm-profdata\nbuild:coverage --test_env=COVERAGE_GCOV_PATH=/usr/bin/llvm-profdata\n# BAZEL_LLVM_COV is used by rules_cc to generate coverage reports from profile data\nbuild:coverage --action_env=BAZEL_LLVM_COV=llvm-cov\nbuild:coverage --test_env=BAZEL_LLVM_COV=llvm-cov\n# LLVM_COV is used by collect_cc_coverage.sh for generating LCOV output\nbuild:coverage --test_env=LLVM_COV=llvm-cov\n# GENERATE_LLVM_LCOV=1 tells collect_cc_coverage.sh to use llvm-cov export to generate LCOV format\n# instead of just outputting raw profdata. LLVM_COV specifies the llvm-cov binary to use.\nbuild:coverage --test_env=GENERATE_LLVM_LCOV=1\nbuild:coverage --experimental_use_llvm_covmap\nbuild:coverage --experimental_generate_llvm_lcov\n# Ensure that we fetch coverage data from remote cache\nbuild:coverage --experimental_fetch_all_coverage_outputs\nbuild:coverage --experimental_split_coverage_postprocessing\n# Allow coverage outputs to be writable for post-processing\nbuild:coverage --experimental_writable_outputs\nbuild:coverage --collect_code_coverage\n# Only instrument source code, not tests or tools - significantly speeds up coverage builds\nbuild:coverage --instrumentation_filter=\"^//src/workerd[/:],^//src/rust[/:]\"\nbuild:coverage --instrument_test_targets\n# Disable coverage instrumentation for external dependencies to speed up compilation.\n# Coverage for V8/external code is not useful for our purposes.\n# These flags negate -fprofile-instr-generate and -fcoverage-mapping set by rules_cc.\nbuild:coverage --per_file_copt=external/.*@-fno-profile-instr-generate,-fno-coverage-mapping\n# KJ uses _exit() by default which bypasses atexit handlers and prevents LLVM profile runtime\n# from writing coverage data. KJ_CLEAN_SHUTDOWN forces use of normal exit() instead.\nbuild:coverage --test_env=KJ_CLEAN_SHUTDOWN=1\n# Use -O1 for faster compilation - coverage builds don't need heavy optimization\nbuild:coverage --copt=-O1\n# Reduce debug info for faster compilation and smaller binaries\nbuild:coverage --copt=-g1\n# Use limited coverage mode for smaller binaries and faster execution (used by Chromium)\nbuild:coverage --copt=-mllvm --copt=-limited-coverage-experimental=true\ncoverage --test_tag_filters=-off-by-default,-requires-fuzzilli,-requires-container-engine,-lint,-benchmark,-workerd-benchmark,-no-coverage\n# Coverage instrumentation slows down test execution, so extend timeouts\n# We disable enormous tests due to the slowdown (CI jobs have a 6h max duration)\ncoverage --test_size_filters=-enormous\ncoverage --test_timeout=240,240,240,240\ncoverage --build_tests_only\n\n# This config is defined internally and enabled on many machines.\n# Defining it as empty just so these machines can run build commands from the workerd repo\nbuild:rosetta-arm64 --define=rosetta_arm64_no_op=1\n"
  },
  {
    "path": ".bazelversion",
    "content": "9.0.1\n"
  },
  {
    "path": ".clang-format",
    "content": "Language: Cpp\nStandard: c++20\nColumnLimit: 100\n\nWhitespaceSensitiveMacros:\n  # clang format doesn't understand TypeScript, so make sure it doesn't mangle\n  # overrides and additional definitions\n  - JSG_TS_OVERRIDE\n  - JSG_TS_DEFINE\n  - JSG_STRUCT_TS_OVERRIDE\n  - JSG_STRUCT_TS_DEFINE\nAllowShortFunctionsOnASingleLine: Empty\n\nSortIncludes: true\nIncludeBlocks: Regroup\nIncludeCategories:\n  # c++ system headers\n  - Regex: <[a-zA-Z0-9_]+>\n    Priority: 5\n  # kj/capnp headers\n  - Regex: <(kj|capnp)/.+>\n    Priority: 4\n  # workerd headers\n  - Regex: <workerd/.+>\n    Priority: 2\n  # 3rd party headers\n  - Regex: <.+>\n    Priority: 3\n  # local headers\n  - Regex: '\".*\"'\n    Priority: 1\n\nAllowShortIfStatementsOnASingleLine: true\nAllowShortLoopsOnASingleLine: true\nAllowShortBlocksOnASingleLine: Empty\n\nIndentWidth: 2\nIndentCaseBlocks: false\nIndentCaseLabels: true\nPointerAlignment: Left\nDerivePointerAlignment: true\n\n# Move public and private in by a half-indentation.  This makes\n# diffs and Github code reviews more readable by letting you\n# see which class the diff snippet is part of.\nAccessModifierOffset: -1\n\n# Really \"Attach\" but empty braces aren't split.\nBreakBeforeBraces: Custom\nBraceWrapping:\n  AfterCaseLabel: false\n  AfterClass: false\n  AfterControlStatement: Never\n  AfterEnum: false\n  AfterFunction: false\n  AfterNamespace: false\n  AfterObjCDeclaration: false\n  AfterStruct: false\n  AfterUnion: false\n  AfterExternBlock: false\n  BeforeCatch: false\n  BeforeElse: false\n  BeforeLambdaBody: false\n  BeforeWhile: false\n  IndentBraces: false\n  SplitEmptyFunction: false\n  SplitEmptyRecord: false\n  SplitEmptyNamespace: false\n\nCpp11BracedListStyle: true\n\nAlignAfterOpenBracket: DontAlign\nAlignOperands: DontAlign\nAlignTrailingComments:\n  Kind: Always\n  OverEmptyLines: 0\nAlwaysBreakAfterReturnType: None\nAlwaysBreakTemplateDeclarations: Yes\nBreakStringLiterals: false\nBinPackArguments: true\nBinPackParameters: false\nBracedInitializerIndentWidth: 2\nBreakInheritanceList: BeforeColon\nContinuationIndentWidth: 4\nIfMacros:\n  [\n    \"KJ_SWITCH_ONEOF\",\n    \"KJ_CASE_ONEOF\",\n    \"KJ_IF_MAYBE\",\n    \"KJ_IF_SOME\",\n    \"CFJS_RESOURCE_TYPE\",\n  ]\nLambdaBodyIndentation: OuterScope\nMacros:\n  - \"KJ_MAP(x,y)=[y](auto x)\"\n  - \"JSG_VISITABLE_LAMBDA(x,y,z)=[x,y](z)\"\n  # The WhitespaceSensitiveMacros solution is flaky, adding the following ensures no formatting:\n  - \"JSG_TS_OVERRIDE(x)=enum class\"\n  - \"JSG_TS_DEFINE(x)=enum class\"\n  - \"JSG_STRUCT_TS_OVERRIDE(x)=enum class\"\n  - \"JSG_STRUCT_TS_DEFINE(x)=enum class\"\nPenaltyReturnTypeOnItsOwnLine: 1000\nPackConstructorInitializers: CurrentLine\nReflowComments: false\nSpaceBeforeCtorInitializerColon: false\nSpaceBeforeInheritanceColon: false\nSpaceBeforeParens: ControlStatementsExceptControlMacros\nSpaceBeforeRangeBasedForLoopColon: false\nSpaceBeforeCpp11BracedList: false\nSpacesBeforeTrailingComments: 2\n---\n# Some files with embedded typescript are incorrectly recognized by clang-format as Objective-C\n# This is because the C/C++ macro expansion step happens after the language recognition step, so\n# when trying to parse the file, the c++ parser fails with incorrect syntax and a fallback to\n# the Objective-C parser is used.\n# This is a workaround to hide the warning.\n# TODO: Remove this once we have a better solution.\nLanguage: ObjC\nDisableFormat: true\n"
  },
  {
    "path": ".clang-tidy",
    "content": "---\n# TODO: We currently only enable select clang-tidy checks. While many checks provide little value or\n# produce false positives, try to incrementally enable most of them.\n# TODO: these checks are in progress of cleaning up\n# Note: cppcoreguidelines-noexcept-destructor is designed to detect destructors that are missing\n# noexcept. We always use noexcept(false) as per KJ-style, but this check works for our purposes\n# too.\nChecks: >\n  bugprone-argument-comment,\n  bugprone-capturing-this-in-member-variable,\n  bugprone-dynamic-static-initializers,\n  bugprone-forward-declaration-namespace,\n  bugprone-invalid-enum-default-initialization,\n  bugprone-move-forwarding-reference,\n  bugprone-return-const-ref-from-parameter,\n  bugprone-suspicious-*,\n  -bugprone-suspicious-semicolon,\n  bugprone-undefined-memory-manipulation,\n  bugprone-unhandled-self-assignment,\n  bugprone-unused-raii,\n  bugprone-use-after-move,\n  cppcoreguidelines-c-copy-assignment-signature,\n  cppcoreguidelines-misleading-capture-default-by-value,\n  cppcoreguidelines-noexcept-destructor,\n  cppcoreguidelines-prefer-member-initializer,\n  google-readability-casting,\n  misc-confusable-identifiers,\n  misc-header-include-cycle,\n  misc-redundant-expression,\n  misc-throw-by-value-catch-by-reference,\n  misc-unused-alias-decls,\n  misc-unused-using-decls,\n  modernize-loop-convert,\n  modernize-macro-to-enum,\n  modernize-redundant-void-arg,\n  modernize-type-traits,\n  modernize-unary-static-assert,\n  modernize-use-bool-literals,\n  modernize-use-constraints,\n  modernize-use-emplace,\n  modernize-use-equals-delete,\n  modernize-use-nullptr,\n  modernize-use-string-view,\n  modernize-use-transparent-functors,\n  modernize-use-using,\n  performance-*,\n  -performance-enum-size,\n  -performance-no-int-to-ptr,\n  -performance-noexcept-move-constructor,\n  -performance-noexcept-swap,\n  -performance-unnecessary-value-param,\n  readability-avoid-const-params-in-decls,\n  readability-container-contains,\n  readability-container-size-empty,\n  readability-delete-null-pointer,\n  readability-duplicate-include,\n  readability-enum-initial-value,\n  readability-redundant-*,\n  -readability-redundant-inline-specifier,\n  -readability-redundant-parentheses,\n  -readability-redundant-smartptr-get,\n  readability-reference-to-constructed-temporary,\n  readability-static-accessed-through-instance,\n  readability-use-concise-preprocessor-directives\n\n# TODO: Fix and enable\n# bugprone-derived-method-shadowing-base-method\n# bugprone-parent-virtual-call\n# cppcoreguidelines-interfaces-global-init\n# cppcoreguidelines-missing-std-forward\n# cppcoreguidelines-pro-type-member-init\n# modernize-use-equals-default\n# modernize-use-override\n# modernize-use-ranges\n# readability-avoid-return-with-void-value\n# modernize-avoid-variadic-functions\n# readability-convert-member-functions-to-static\n# readability-redundant-smartptr-get\n# readability-use-anyofallof\n\nWarningsAsErrors: '*'\nHeaderFilterRegex: '.*src/workerd/.*'\n\nCheckOptions:\n  # JSG has very entrenched include cycles\n  - key: misc-header-include-cycle.IgnoredFilesList\n    value: \"jsg/jsg.h|jsg/dom-exception.h\"\n  - key: cppcoreguidelines-missing-std-forward.ForwardFunction\n    value: \"kj::fwd\"\n"
  },
  {
    "path": ".devcontainer/Dockerfile",
    "content": "FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:22\n\n# Install dependencies, including clang via through LLVM APT repository. Note that this\n# will also install lldb and clangd alongside dependencies.\nARG LLVM_VERSION=19\nRUN apt-get update && export DEBIAN_FRONTEND=noninteractive \\\n    && apt-get -y install software-properties-common python3 python3-distutils tclsh nodejs npm \\\n    && curl -fSsL -o /tmp/llvm.sh https://apt.llvm.org/llvm.sh && chmod +x /tmp/llvm.sh && bash /tmp/llvm.sh ${LLVM_VERSION} \\\n    && apt-get -y install --no-install-recommends libunwind-${LLVM_VERSION} libc++abi1-${LLVM_VERSION} libc++1-${LLVM_VERSION} libc++-${LLVM_VERSION}-dev libclang-rt-${LLVM_VERSION}-dev -o DPkg::options::=\"--force-overwrite\"\nENV PATH /usr/lib/llvm-${LLVM_VERSION}/bin:$PATH\n\n# Install Bazel (via Bazelisk)\nRUN npm install -g @bazel/bazelisk\n\n# Install Just\nRUN npm install -g rust-just\n"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "{\n\t\"name\": \"C++: Workerd\",\n\t\"build\": {\n\t\t\"dockerfile\": \"Dockerfile\"\n\t},\n\t\"customizations\": {\n\t\t// Configure properties specific to VS Code.\n\t\t\"vscode\": {\n\t\t\t// Add the IDs of extensions you want installed when the container is created.\n\t\t\t\"extensions\": [\n\t\t\t\t\"BazelBuild.vscode-bazel\",\n\t\t\t\t\"eamodio.gitlens\",\n\t\t\t\t\"streetsidesoftware.code-spell-checker\",\n\t\t\t\t\"llvm-vs-code-extensions.vscode-clangd\",\n\t\t\t\t\"ms-vscode.cpptools\",\n\t\t\t\t\"abronan.capnproto-syntax\",\n\t\t\t\t\"DavidAnson.vscode-markdownlint\"\n\t\t\t],\n\t\t\t\"settings\": {\n\t\t\t\t// The Microsoft C/C++ extension has IntelliSense support that is not compatible with the clangd extension.\n\t\t\t\t\"C_Cpp.intelliSenseEngine\": \"disabled\",\n\t\t\t\t\"C_Cpp.default.cppStandard\": \"c++20\"\n\t\t\t}\n\t\t}\n\t}\n\t// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.\n\t// \"remoteUser\": \"root\"\n}\n"
  },
  {
    "path": ".git-blame-ignore-revs",
    "content": "# Apply prettier to the project\n0523bf8b36a937348f1bb79eceda2463a5c220b5\n\n# Apply clang-format to the project.\n5e8537a77e760c160ace3dfe23ee8c76ee5aeeb3\n\n# Apply ruff format to the project\nd6d0607a845e6f71084ce272a1c1e8c50e244bdd\n\n# Apply buildifier to the project\nf457f19039b82536b35659c1f9cb898a198e6cd1\n\n# Apply ruff linter to the project\n893774eab71fd7be5000436ff2ff0b5dd85ef073\n\n# Use clang-format to sort includes.\nfaabf00af72bbce956221b40624f8a3d57f82b7c\nb9e9fb144f44494017e77fbe959355e92f10ae69\n\n# Add `AllowShortBlocksOnASingleLine: Empty` to clang-format\nfa2c488219a5e96792e61f3d51838595e2907c8d\n\n# clang-tidy: Add google-readability-casting, modernize-use-using\n21dc6eb66a2344b8e756b897ade27cc107b58153\n\n# clang-tidy: Add more readability checks\nd8987b2c4206c8b28b637c24219a580431873d12\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "* @cloudflare/workers-runtime-1 @cloudflare/workers-durable-objects\n.github/workflows/ @cloudflare/wrangler @cloudflare/workers-runtime-1\nnpm/ @cloudflare/wrangler @cloudflare/workers-runtime-1\nbuild-releases.sh @cloudflare/wrangler @cloudflare/workers-runtime-1\nRELEASE.md @cloudflare/wrangler @cloudflare/workers-runtime-1\npackage.json @cloudflare/wrangler @cloudflare/workers-runtime-1\npnpm-lock.yaml @cloudflare/wrangler @cloudflare/workers-runtime-1\n/types/ @cloudflare/wrangler\n/types/generated-snapshot/experimental/ @cloudflare/workers-runtime-1 @cloudflare/workers-durable-objects\nsrc/workerd/tools/ @cloudflare/wrangler @cloudflare/workers-runtime-1 @cloudflare/workers-durable-objects\nsrc/workerd/io/release-version.txt @cloudflare/wrangler @cloudflare/workers-runtime-1\nsrc/workerd/io/maximum-compatibility-date.txt @cloudflare/wrangler @cloudflare/workers-runtime-1\nsrc/node/ @cloudflare/workers-runtime-1 @cloudflare/workers-durable-objects @cloudflare/workers-frameworks @cloudflare/workers-runtime-nodejs\nsrc/workerd/api/node/ @cloudflare/workers-runtime-1 @cloudflare/workers-durable-objects @cloudflare/workers-frameworks @cloudflare/workers-runtime-nodejs\n"
  },
  {
    "path": ".github/DISCUSSION_TEMPLATE/nodejs_api_request.yml",
    "content": "title: \"Node.js API Request\"\nlabels: [\"Node.js API Request\"]\nbody:\n  - type: input\n    id: module\n    attributes:\n      label: Module\n      description: \"Name of the Node.js module you want to work on Workers. Ex: fs\"\n      value:\n    validations:\n      required: true\n  - type: input\n    id: method\n    attributes:\n      label: API Method\n      description: \"Name of the specific API method you want to work on Workers. Ex: fs.readFile\"\n      value:\n    validations:\n      required: true\n  - type: markdown\n    id: context\n    attributes:\n      value: |\n        ## Context\n"
  },
  {
    "path": ".github/DISCUSSION_TEMPLATE/python_package_request.yml",
    "content": "title: \"Python Package Request\"\nlabels: [\"Python Package Request\"]\nbody:\n  - type: input\n    id: package\n    attributes:\n      label: Package Name\n      description: \"Name of the Python package you want to work on Workers\"\n      value:\n    validations:\n      required: true\n  - type: input\n    id: version\n    attributes:\n      label: Package Version\n      description: \"Optional — specify a particular version of the package\"\n      value:\n    validations:\n      required: false\n  - type: markdown\n    id: context\n    attributes:\n      value: |\n        ## Context\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/runtime-apis.md",
    "content": "---\nname: runtime-apis\nabout: Report an issue with an API provided by workerd\ntitle: '🐛 Bug Report — Runtime APIs'\nlabels: runtime-api\n\n---\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/workers-types.md",
    "content": "---\nname: workers-types\nabout: Report an issue or suggestion for `@cloudflare/workers-types`\ntitle: ''\nlabels: types\nassignees: workers-devprod\n\n---\n\n\n"
  },
  {
    "path": ".github/actions/setup-runner/action.yml",
    "content": "name: 'Setup runner environment'\ndescription: 'Sets up runner environment with proper toolchain for building workerd'\nruns:\n  using: 'composite'\n  steps:\n    - name: Setup Linux\n      shell: bash\n      if: runner.os == 'Linux'\n      run: |\n        export DEBIAN_FRONTEND=noninteractive\n        wget https://apt.llvm.org/llvm.sh\n        sed -i '/apt-get install/d' llvm.sh\n        chmod +x llvm.sh\n        sudo ./llvm.sh 19\n        # keep in sync with build/ci.bazelrc\n        sudo apt-get install -y -qq --no-install-recommends \\\n          clang-19 \\\n          lld-19 \\\n          libunwind-19 \\\n          libc++abi1-19 \\\n          libc++1-19 \\\n          libc++-19-dev \\\n          libclang-rt-19-dev \\\n          llvm-19\n        sudo ln -s /usr/bin/llvm-symbolizer-19 /usr/bin/llvm-symbolizer\n        sudo ln -s /usr/bin/llvm-profdata-19 /usr/bin/llvm-profdata\n        sudo ln -s /usr/bin/llvm-cov-19 /usr/bin/llvm-cov\n        echo \"build:linux --action_env=CC=/usr/lib/llvm-19/bin/clang\" >> .bazelrc\n        echo \"build:linux --host_action_env=CC=/usr/lib/llvm-19/bin/clang\" >> .bazelrc\n        echo \"build:linux --linkopt=--ld-path=/usr/lib/llvm-19/bin/ld.lld\" >> .bazelrc\n        echo \"build:linux --host_linkopt=--ld-path=/usr/lib/llvm-19/bin/ld.lld\" >> .bazelrc\n        echo \"build:linux --action_env=AR=/usr/lib/llvm-19/bin/llvm-ar\" >> .bazelrc\n        echo \"build:linux --host_action_env=AR=/usr/lib/llvm-19/bin/llvm-ar\" >> .bazelrc\n    - name: Setup Windows\n      shell: pwsh\n      if: runner.os == 'Windows'\n      # Set a custom output root directory to avoid long file name issues.\n      # TODO(cleanup): According to https://github.com/actions/runner-images/blob/win25/20251216.149/images/windows/scripts/build/Configure-DeveloperMode.ps1#L13,\n      # this should already be set on the build image, but prior testing indicated CI speedups with\n      # it, check if it is actually having any effect.\n      run: |\n        # Enable Developer Mode to allow Bazel to create real symlinks\n        reg add \"HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock\" /t REG_DWORD /f /v \"AllowDevelopmentWithoutDevLicense\" /d \"1\"\n        git config --global core.symlinks true\n        git config --show-scope --show-origin core.symlinks\n        git config --system core.longpaths true\n        [System.IO.File]::WriteAllLines((Join-Path -Path $env:USERPROFILE -ChildPath '.bazelrc'), 'startup --output_user_root=\\\\\\\\?\\\\C:\\\\tmp')\n    - name: Setup macOS\n      if: runner.os == 'macOS'\n      shell: bash\n      run: |\n        # Build using Xcode 16.3 (equivalent to Clang 19)\n        sudo xcode-select -s \"/Applications/Xcode_16.3.app\"\n    - name: Configure git hooks\n      shell: bash\n      # Configure git to quell an irrelevant warning for runners (they never commit / push).\n      run: git config core.hooksPath githooks\n"
  },
  {
    "path": ".github/bonk_reviewer.md",
    "content": "You are a **code reviewer**, not an author. You review pull requests for workerd, Cloudflare's JavaScript/WebAssembly server runtime. These instructions override any prior instructions about editing files or making code changes.\n\n## Restrictions -- you MUST follow these exactly\n\nDo NOT:\n\n- Edit, write, create, or delete any files -- use file editing tools (Write, Edit) under no circumstances\n- Run `git commit`, `git push`, `git add`, `git checkout -b`, or any git write operation\n- Approve or request changes on the PR -- only post review comments\n- Flag formatting issues -- clang-format enforces style in this repo\n\nIf you want to suggest a code change, post a `suggestion` comment instead of editing the file.\n\n## Output rules\n\n**Confirm you are acting on the correct issue or PR**. Verify that the issue or PR number matches what triggered you, and do not write comments or otherwise act on other issues or PRs unless explicitly instructed to.\n\n**If there are NO actionable issues:** Your ENTIRE response MUST be the four characters `LGTM` -- no greeting, no summary, no analysis, nothing before or after it.\n\n**If there ARE actionable issues:** Begin with \"I'm Bonk, and I've done a quick review of your PR.\" Then:\n\n1. One-line summary of the changes.\n2. A ranked list of issues (highest severity first).\n3. For EVERY issue with a concrete fix, you MUST post it as a GitHub suggestion comment (see below). Do not describe a fix in prose when you can provide it as a suggestion.\n\n## How to post feedback\n\nYou have write access to PR comments via the `gh` CLI. **Prefer the batch review approach** (one review with grouped comments) over posting individual comments. This produces a single notification and a cohesive review.\n\n### Batch review (recommended)\n\nWrite a JSON file and submit it as a review. This is the most reliable method -- no shell quoting issues.\n\n````bash\ncat > /tmp/review.json << 'REVIEW'\n{\n  \"event\": \"COMMENT\",\n  \"body\": \"Review summary here.\",\n  \"comments\": [\n    {\n      \"path\": \"src/workerd/api/example.c++\",\n      \"line\": 42,\n      \"side\": \"RIGHT\",\n      \"body\": \"Ownership issue -- `kj::Own` moved but still referenced:\\n```suggestion\\nauto result = kj::mv(owned);\\n```\"\n    }\n  ]\n}\nREVIEW\ngh api repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/reviews --input /tmp/review.json\n````\n\nEach comment needs `path`, `line`, `side`, and `body`. Use `suggestion` fences in `body` for applicable changes.\n\n- `side`: `\"RIGHT\"` for added or unchanged lines, `\"LEFT\"` for deleted lines\n- For multi-line suggestions, add `start_line` and `start_side` to the comment object\n- If `gh api` returns a 422 (wrong line number, stale commit), fall back to a top-level PR comment with `gh pr comment` instead of retrying\n\n## Review focus areas\n\n**Code quality:** Refer to the following checklists:\n- For C++, use the `kj-style`, and `workerd-safety-review` skills\n- For JavaScript and TypeScript, use the `ts-style` skill\n- For Rust, use the `rust-review` skill\n- For all code, use the `workerd-api-review` skill for API design, performance, security, and\n  standards compliance\n- Review added or updated tests to ensure they cover the relevant code changes\n- Review code comments for clarity and accuracy\n\n**Backward compatibility:** workerd has a strong backward compat commitment. New behavior changes MUST be gated behind compatibility flags (see compatibility-date.capnp). Flag any ungated behavioral change as high severity.\n\n**Autogates:** Risky changes should use autogate flags (src/workerd/util/autogate.\\*) for staged rollout. If a change looks risky and has no autogate, flag it.\n\n**Security:** This is a production runtime that executes untrusted code. Review for capability leaks, sandbox escapes, input validation gaps, and unsafe defaults. High severity.\n\n**Cap'n Proto schemas:** Check .capnp file changes for wire compatibility. Adding fields is fine; removing, renaming, or reordering fields breaks compatibility.\n\n**JSG bindings:** Changes in jsg/ must correctly bridge V8 and C++. Check type conversions, GC safety, and proper use of jsg:: macros.\n\n**Node.js compatibility (src/node/, src/workerd/api/node/):** Verify behavior matches Node.js. Check for missing error cases and edge cases in polyfills.\n\n**Build system:** Bazel BUILD file changes should have correct deps and visibility.\n\n## What counts as actionable\n\nLogic bugs, security issues, backward compat violations, missing compat flags, memory safety problems, incorrect API behavior. Be pragmatic -- do not nitpick, do not flag subjective preferences.\n"
  },
  {
    "path": ".github/secret_scanning.yml",
    "content": "paths-ignore:\n  - \"src/workerd/api/node/crypto_keys-test.js\"\n  - \"src/workerd/api/node/crypto_dh-test.js\"\n  - \"src/workerd/jsg/url-test-corpus-success.h\"\n  - \"src/workerd/api/node/tests/crypto_x509-test.js\"\n  - \"src/workerd/api/node/tests/fixtures/dh_private.pem\"\n  - \"src/workerd/api/node/tests/fixtures/dsa_private_pkcs8.pem\"\n  - \"src/workerd/api/node/tests/fixtures/ed25519_private.pem\"\n  - \"src/workerd/api/node/tests/fixtures/rsa_private_encrypted.pem\"\n  - \"src/workerd/api/node/tests/fixtures/rsa_pss_private_2048_sha1_sha1_20.pem\"\n  - \"src/workerd/api/node/tests/fixtures/dsa_private_1025.pem\"\n  - \"src/workerd/api/node/tests/fixtures/ec_p256_private.pem\"\n  - \"src/workerd/api/node/tests/fixtures/ed448_private.pem\"\n  - \"src/workerd/api/node/tests/fixtures/rsa_private.pem\"\n  - \"src/workerd/api/node/tests/fixtures/rsa_pss_private_2048_sha256_sha256_16.pem\"\n  - \"src/workerd/api/node/tests/fixtures/dsa_private_encrypted_1025.pem\"\n  - \"src/workerd/api/node/tests/fixtures/ec_p384_private.pem\"\n  - \"src/workerd/api/node/tests/fixtures/rsa_private_2048.pem\"\n  - \"src/workerd/api/node/tests/fixtures/rsa_private_pkcs8_bad.pem\"\n  - \"src/workerd/api/node/tests/fixtures/rsa_pss_private_2048_sha512_sha256_20.pem\"\n  - \"src/workerd/api/node/tests/fixtures/dsa_private_encrypted.pem\"\n  - \"src/workerd/api/node/tests/fixtures/ec_p521_private.pem\"\n  - \"src/workerd/api/node/tests/fixtures/rsa_private_4096.pem\"\n  - \"src/workerd/api/node/tests/fixtures/rsa_private_pkcs8.pem\"\n  - \"src/workerd/api/node/tests/fixtures/x25519_private.pem\"\n  - \"src/workerd/api/node/tests/fixtures/dsa_private.pem\"\n  - \"src/workerd/api/node/tests/fixtures/ec_secp256k1_private.pem\"\n  - \"src/workerd/api/node/tests/fixtures/rsa_private_b.pem\"\n  - \"src/workerd/api/node/tests/fixtures/rsa_pss_private_2048.pem\"\n  - \"src/workerd/api/node/tests/fixtures/x448_private.pem\"\n  - \"src/workerd/api/node/tests/fixtures/tls-nodejs-tcp-server.pem\"\n  - \"src/workerd/api/tests/startls-server.pem\"\n  - \"src/workerd/api/tests/starttls-nodejs-server.js\"\n"
  },
  {
    "path": ".github/workflows/_bazel.yml",
    "content": "name: 'Run Bazel'\non:\n  workflow_call:\n    inputs:\n      image:\n        type: string\n        required: false\n        default: 'ubuntu-22.04-16core'\n      os_name:\n        type: string\n        required: false\n        default: 'linux'\n\n      arch_name:\n        type: string\n        required: false\n        default: 'X64'\n      phase:\n        type: string\n        required: false\n        default: ''\n      suffix:\n        type: string\n        required: false\n        default: ''\n      run_tests:\n        type: boolean\n        required: false\n        default: true\n      test_target:\n        type: string\n        required: false\n        default: //...\n      parse_headers:\n        type: boolean\n        required: false\n        default: false\n      extra_bazel_args:\n        type: string\n        required: false\n        default: ''\n      upload_binary:\n        type: boolean\n        required: false\n        default: false\n      upload_test_logs:\n        type: boolean\n        required: false\n        default: false\n      upload_types:\n        type: boolean\n        required: false\n        default: false\n      run_coverage:\n        type: boolean\n        required: false\n        default: false\n      fetch_depth:\n        type: number\n        required: false\n        default: 1\n      macos_use_lld:\n        type: boolean\n        required: false\n        default: false\n      build_container_images:\n        type: boolean\n        required: false\n        default: false\n    secrets:\n      BAZEL_CACHE_KEY:\n        required: true\n      WORKERS_MIRROR_URL:\n        required: true\n      CODECOV_TOKEN:\n        required: false\n\npermissions:\n  # Read repo\n  contents: read\n  # Read/write artifacts\n  actions: write\n\njobs:\n  bazel:\n    runs-on: ${{ inputs.image }}\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          show-progress: false\n          fetch-depth: ${{ inputs.fetch_depth }}\n      - name: Cache\n        id: cache\n        uses: actions/cache@v5\n        with:\n          path: ~/bazel-disk-cache\n          key: bazel-disk-cache${{ inputs.phase}}-${{ inputs.os_name }}-${{ runner.arch }}${{ inputs.suffix }}-${{ hashFiles('.bazelversion', '.bazelrc', 'MODULE.bazel') }}\n          # Intentionally not reusing an older cache entry using a key prefix, bazel frequently\n          # ends up with a larger cache at the end when starting with an available cache entry,\n          # resulting in a snowballing cache size and cache download/upload times.\n      - name: Setup Runner\n        uses: ./.github/actions/setup-runner\n      - name: Setup lld on macOS\n        if: runner.os == 'macOS' && inputs.macos_use_lld\n        run: |\n          # Install lld and link it to /usr/local/bin. We overwrite any existing link, which may\n          # exist from an older pre-installed LLVM version on the runner image.\n          brew update\n          brew install lld\n          sudo ln -s -f $(brew --prefix lld)/bin/ld64.lld /usr/local/bin/ld64.lld\n          # Enable lld identical code folding to significantly reduce binary size.\n          echo \"build:macos --config=macos_lld_icf\" >> .bazelrc\n      - name: Configure download mirrors\n        shell: bash\n        run: |\n          if [ ! -z \"${{ secrets.WORKERS_MIRROR_URL }}\" ] ; then\n            # Strip comment in front of WORKERS_MIRROR_URL, then substitute secret to use it.\n            sed -e '/WORKERS_MIRROR_URL/ { s@# *@@; s@WORKERS_MIRROR_URL@${{ secrets.WORKERS_MIRROR_URL }}@; }' -i.bak build/deps/nodejs.MODULE.bazel\n          fi\n      - name: Bazel build (Windows workaround)\n        if: runner.os == 'Windows'\n        # HACK: Work around Bazel Windows bug: Some targets need to be compiled without symlink\n        # support. Since we still need symlinks to compile C++ code properly, compile these targets\n        # separately.\n        run: |\n          bazel --nowindows_enable_symlinks build ${{ inputs.extra_bazel_args }} --config=ci --profile build-win-workaround.bazel-profile.gz --remote_cache=https://bazel:${{ secrets.BAZEL_CACHE_KEY }}@bazel-remote-cache.devprod.cloudflare.dev //src/wpt:wpt-all@tsproject //src/node:node@tsproject //src/pyodide:pyodide_static@tsproject\n      - name: Bazel build\n        run: |\n          bazel build --remote_cache=https://bazel:${{ secrets.BAZEL_CACHE_KEY }}@bazel-remote-cache.devprod.cloudflare.dev  --config=ci ${{ inputs.extra_bazel_args }} //...\n      - name: Configure Docker daemon for IPv6\n        if: inputs.build_container_images\n        run: |\n          sudo mkdir -p /etc/docker\n          echo '{\"ipv6\": true, \"fixed-cidr-v6\": \"fd00::/80\"}' | sudo tee /etc/docker/daemon.json\n          sudo systemctl restart docker\n      - name: Build and load container images\n        if: inputs.build_container_images\n        run: |\n          docker context use default\n          echo \"Docker info:\"\n          docker info\n          echo \"Docker version:\"\n          docker version\n          bazel run --remote_cache=https://bazel:${{ secrets.BAZEL_CACHE_KEY }}@bazel-remote-cache.devprod.cloudflare.dev --config=ci ${{ inputs.extra_bazel_args }} //images:load_all\n      - name: Bazel test\n        if: inputs.run_tests\n        run: |\n          bazel test --remote_cache=https://bazel:${{ secrets.BAZEL_CACHE_KEY }}@bazel-remote-cache.devprod.cloudflare.dev --config=ci ${{ inputs.extra_bazel_args }} ${{ inputs.test_target }}\n      - name: Bazel coverage\n        if: inputs.run_coverage\n        run: |\n          bazel coverage --remote_cache=https://bazel:${{ secrets.BAZEL_CACHE_KEY }}@bazel-remote-cache.devprod.cloudflare.dev --config=ci ${{ inputs.extra_bazel_args }} ${{ inputs.test_target }}\n      - name: Upload coverage to Codecov\n        if: inputs.run_coverage\n        uses: codecov/codecov-action@v5\n        with:\n          # Use bazel info to get the actual output path, avoiding symlink issues\n          files: ${{ github.workspace }}/bazel-out/_coverage/_coverage_report.dat\n          fail_ci_if_error: true\n          verbose: true\n          disable_search: true\n          root_dir: ${{ github.workspace }}\n        env:\n          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}\n      - name: Parse headers\n        # TODO(cleanup): Bazel does not allow for only parsing headers in non-recursive targets, so\n        # we can't widely enable header parsing for now (dependencies like V8 do not specify\n        # dependencies of header targets properly).\n        if: inputs.parse_headers\n        run: |\n          bazel build --config=parse_headers --remote_cache=https://bazel:${{ secrets.BAZEL_CACHE_KEY }}@bazel-remote-cache.devprod.cloudflare.dev --config=ci ${{ inputs.extra_bazel_args }} //src/workerd/util\n      - name: Upload test logs\n        if: always() && inputs.upload_test_logs\n        uses: actions/upload-artifact@v7\n        with:\n          name: test-logs-${{ inputs.os_name }}-${{ inputs.arch_name }}${{ inputs.suffix }}.zip\n          path: bazel-testlogs/**/test.xml\n      - name: Report disk usage (in MB)\n        if: always() && runner.os != 'Windows'\n        shell: bash\n        run: |\n          BAZEL_OUTPUT_BASE=$(bazel info output_base)\n          BAZEL_REPOSITORY_CACHE=$(bazel info repository_cache)\n          echo \"Bazel cache usage statistics\"\n          du -ms -t 1 ~/bazel-disk-cache/* $BAZEL_REPOSITORY_CACHE\n          echo \"Bazel output usage statistics\"\n          du -ms -t 1 $BAZEL_OUTPUT_BASE\n          echo \"Workspace usage statistics\"\n          du -ms -t 1 $GITHUB_WORKSPACE\n      - name: Report disk usage (in MB)\n        if: always() && runner.os == 'Windows'\n        shell: pwsh\n        run: |\n          function Get-SizeMB($path) {\n            if (Test-Path $path) {\n              $size = (Get-ChildItem -Path $path -Recurse -Force -ErrorAction SilentlyContinue |\n                       Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum\n              [math]::Round($size / 1MB)\n            } else { 0 }\n          }\n          $outputBase = bazel info output_base 2>$null\n          $repoCache = bazel info repository_cache 2>$null\n          echo \"Bazel cache usage statistics\"\n          echo \"$(Get-SizeMB $env:USERPROFILE\\bazel-disk-cache)`t$env:USERPROFILE\\bazel-disk-cache\"\n          echo \"$(Get-SizeMB $repoCache)`t$repoCache\"\n          echo \"Bazel output usage statistics\"\n          echo \"$(Get-SizeMB $outputBase)`t$outputBase\"\n          echo \"Workspace usage statistics\"\n          echo \"$(Get-SizeMB $env:GITHUB_WORKSPACE)`t$env:GITHUB_WORKSPACE\"\n\n      - name: Upload binary\n        if: inputs.upload_binary\n        uses: actions/upload-artifact@v7\n        with:\n          name: ${{ inputs.os_name }}-${{ inputs.arch_name }}${{ inputs.suffix }}-binary\n          path: bazel-bin/src/workerd/server/workerd${{ runner.os == 'Windows' && '.exe' || '' }}\n          if-no-files-found: error\n      - name: Upload build statistics\n        if: always() && inputs.run_tests\n        uses: actions/upload-artifact@v7\n        with:\n          name: ${{ inputs.os_name }}${{ inputs.arch_name }}${{ inputs.suffix }}-bazel-profile\n          path: '*.bazel-profile.gz'\n\n      - name: Drop large Bazel cache files\n        if: always()\n        # Github has a nominal 10GB of storage for all cached builds associated with a project.\n        # Drop large files (>100MB) in our cache to improve shared build cache efficiency. This is\n        # particularly helpful for asan and debug builds that produce larger executables. Also\n        # the process of saving the Bazel disk cache generates a tarball on the runners disk, and\n        # it is possible to run out of storage in that process (does not fail the workflow).\n        shell: bash\n        run: |\n          if [ -d ~/bazel-disk-cache ]; then\n            find ~/bazel-disk-cache -size +100M -type f -exec rm {} \\;\n            echo \"Trimmed Bazel cache usage statistics\"\n            du -ms -t 1 ~/bazel-disk-cache/*\n          else\n            echo \"Disk cache does not exist: ~/bazel-disk-cache\"\n          fi\n\n      - name: Bazel shutdown\n        if: always()\n        # Check that there are no .bazelrc issues that prevent shutdown.\n        run: bazel shutdown\n"
  },
  {
    "path": ".github/workflows/_wpt.yml",
    "content": "name: 'WPT Report'\non:\n  workflow_call:\n    inputs:\n      image:\n        description: 'Runner image to use'\n        required: true\n        type: string\n      logs_artifact:\n        description: 'Name of artifact containing test logs'\n        required: true\n        type: string\n      report_artifact:\n        description: 'Name of artifact to use for WPT report JSON file'\n        required: true\n        type: string\n\npermissions:\n  # Read repo\n  contents: read\n  # Read/write artifacts\n  actions: write\njobs:\n  wpt-report:\n    runs-on: ${{ inputs.image }}\n    steps:\n      - uses: actions/checkout@v6\n      - name: Download test logs\n        uses: actions/download-artifact@v8\n        continue-on-error: true\n        with:\n          name: ${{ inputs.logs_artifact }}\n          path: testlogs\n      - name: Generate WPT report and stats\n        run: ./tools/cross/wpt_logs.py --print-stats --write-report=wpt-report.json testlogs/ >> $GITHUB_STEP_SUMMARY\n      - name: Upload WPT report\n        uses: actions/upload-artifact@v7\n        with:\n          name: ${{ inputs.report_artifact }}\n          path: wpt-report.json\n"
  },
  {
    "path": ".github/workflows/bigbonk.yml",
    "content": "name: Bonk\n\non:\n  issue_comment:\n    types: [created]\n  pull_request_review_comment:\n    types: [created]\n  pull_request_review:\n    types: [submitted]\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.issue.number || github.ref }}\n  cancel-in-progress: false\n\njobs:\n  bonk:\n    if: github.event.sender.type != 'Bot' && contains(github.event.comment.body, '/bigbonk')\n    runs-on: ubuntu-latest\n    timeout-minutes: 30\n    permissions:\n      id-token: write\n      contents: write\n      issues: write\n      pull-requests: write\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 1\n\n      - name: Run Bonk\n        uses: ask-bonk/ask-bonk/github@main\n        env:\n          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_AI_GATEWAY_ACCOUNT_ID }}\n          CLOUDFLARE_GATEWAY_ID: ${{ secrets.CF_AI_GATEWAY_NAME }}\n          CLOUDFLARE_API_TOKEN: ${{ secrets.CF_AI_GATEWAY_TOKEN }}\n        with:\n          model: 'cloudflare-ai-gateway/anthropic/claude-opus-4-6'\n          mentions: '/bigbonk'\n          variant: 'max'\n          permissions: write\n          opencode_version: '1.2.27'\n          # token_permissions defaults to WRITE (i.e. Bonk can push commits).\n          # We intentionally leave it that way here because users may ask Bonk\n          # to update their PR via /bigbonk.\n"
  },
  {
    "path": ".github/workflows/bonk.yml",
    "content": "name: Bonk\n\non:\n  issue_comment:\n    types: [created]\n  pull_request_review_comment:\n    types: [created]\n  pull_request_review:\n    types: [submitted]\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.issue.number || github.ref }}\n  cancel-in-progress: false\n\njobs:\n  bonk:\n    if: github.event.sender.type != 'Bot' && (contains(github.event.comment.body, '/bonk') || contains(github.event.comment.body, '@ask-bonk'))\n    runs-on: ubuntu-latest\n    timeout-minutes: 30\n    permissions:\n      id-token: write\n      contents: write\n      issues: write\n      pull-requests: write\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 1\n\n      - name: Run Bonk\n        uses: ask-bonk/ask-bonk/github@main\n        env:\n          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_AI_GATEWAY_ACCOUNT_ID }}\n          CLOUDFLARE_GATEWAY_ID: ${{ secrets.CF_AI_GATEWAY_NAME }}\n          CLOUDFLARE_API_TOKEN: ${{ secrets.CF_AI_GATEWAY_TOKEN }}\n        with:\n          model: 'cloudflare-ai-gateway/anthropic/claude-opus-4-6'\n          mentions: '/bonk,@ask-bonk'\n          permissions: write\n          opencode_version: '1.2.27'\n          # token_permissions defaults to WRITE (i.e. Bonk can push commits).\n          # We intentionally leave it that way here because users may ask Bonk\n          # to update their PR via /bonk.\n"
  },
  {
    "path": ".github/workflows/cla.yml",
    "content": "name: \"CLA Assistant\"\non:\n  issue_comment:\n    types: [created]\n  pull_request_target:\n    types: [opened,synchronize]\n  merge_group:\n\njobs:\n  CLAssistant:\n    runs-on: ubuntu-latest\n    steps:\n      - name: \"CLA Assistant\"\n        if: (github.event.issue.pull_request && (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA')) || github.event_name == 'pull_request_target'\n        uses: contributor-assistant/github-action@v2.6.1\n        env:\n          # CLA Action uses this in-built GitHub token to make the API calls for interacting with GitHub.\n          # It is built into Github Actions and does not need to be manually specified in your secrets store.\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          # The below token should have repo scope and must be manually added by you in the repository's secret\n          PERSONAL_ACCESS_TOKEN : ${{ secrets.CLA_PERSONAL_ACCESS_TOKEN }}\n        with:\n          path-to-signatures: 'signatures/version1/cla.json'\n          path-to-document: 'https://www.cloudflare.com/cla/'\n          # branch should not be protected\n          branch: 'cla-signatures'\n          allowlist: dependabot[bot],workers-devprod\n          lock-pullrequest-aftermerge: false\n"
  },
  {
    "path": ".github/workflows/codspeed.yml",
    "content": "name: CodSpeed\n\non:\n  pull_request:\n    paths-ignore:\n      - 'docs/**'\n      - 'justfile'\n      - '.devcontainer'\n      - '**/*.md'\n      - '.gitignore'\n  merge_group:\n  push:\n    branches:\n      - main\n\nconcurrency:\n  group: codspeed.yml-${{ github.event.pull_request.number || github.run_id }}\n  cancel-in-progress: true\n\npermissions:\n  # Write cache\n  contents: write\n\njobs:\n  benchmarks:\n    name: Run benchmarks\n    runs-on: ubuntu-22.04-16core\n    env:\n      BAZEL_ARGS: --config=benchmark --@google_benchmark//:codspeed_mode=simulation --remote_cache=https://bazel:${{ secrets.BAZEL_CACHE_KEY }}@bazel-remote-cache.devprod.cloudflare.dev\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          show-progress: false\n\n      - name: Cache\n        id: cache\n        uses: actions/cache@v5\n        with:\n          path: ~/bazel-disk-cache\n          key: bazel-disk-cache-benchmarks-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.bazelversion', '.bazelrc', 'MODULE.bazel') }}\n\n      - name: Setup Runner\n        uses: ./.github/actions/setup-runner\n\n      - name: Build benchmarks\n        run: |\n          bazel build ${{ env.BAZEL_ARGS }} --build_tag_filters=google_benchmark //... @capnp-cpp//...\n\n      - name: Generate benchmark script\n        run: |\n          echo '#!/bin/bash' > run_benchmarks.sh\n          echo 'set -ex' >> run_benchmarks.sh\n          targets=$(bazel query 'attr(tags, \"[\\[ ]google_benchmark[,\\]]\", //... + @capnp-cpp//...) except attr(tags, \"[\\[ ]manual[,\\]]\", //...)' --output=label 2>/dev/null)\n          for target in $targets; do\n            echo \"echo 'Running benchmark: $target'\" >> run_benchmarks.sh\n            echo \"bazel run ${{ env.BAZEL_ARGS }} $target -- --benchmark_min_time=1s\" >> run_benchmarks.sh\n          done\n          chmod +x run_benchmarks.sh\n\n      - name: Run benchmarks\n        uses: CodSpeedHQ/action@v4\n        with:\n          mode: simulation\n          run: ./run_benchmarks.sh\n\n      - name: Bazel shutdown\n        run: bazel shutdown\n"
  },
  {
    "path": ".github/workflows/coverage.yml",
    "content": "name: Coverage\n\non:\n  pull_request:\n  merge_group:\n  push:\n    branches:\n      - main\n\nconcurrency:\n  group: coverage.yml-${{ github.event.pull_request.number || github.run_id }}\n  cancel-in-progress: true\n\npermissions:\n  # Read repo\n  contents: read\n  # Read/write artifacts\n  actions: write\n\njobs:\n  coverage-linux:\n    uses: ./.github/workflows/_bazel.yml\n    with:\n      image: ubuntu-22.04-16core\n      os_name: linux\n      arch_name: 'X64'\n      suffix: 'coverage'\n      # Don't use ci-test here because it enables --remote_download_minimal which prevents\n      # coverage data from being fetched from cache. The coverage config already sets\n      # --test_env=CI=true and --config=wpt-test which are the relevant parts of ci-test.\n      # Use ci-linux-common instead of ci-linux to avoid overriding coverage test_tag_filters\n      # (ci-linux sets test_tag_filters that would include container tests which require docker)\n      extra_bazel_args: '--config=ci-linux-common --config=ci-limit-storage --config=coverage --test_env=CI=true --config=wpt-test'\n      upload_test_logs: true\n      upload_binary: false\n      build_container_images: false\n      run_coverage: true\n      run_tests: false\n    secrets:\n      BAZEL_CACHE_KEY: ${{ secrets.BAZEL_CACHE_KEY }}\n      WORKERS_MIRROR_URL: ${{ secrets.WORKERS_MIRROR_URL }}\n      CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/daily-release.yml",
    "content": "name: Daily Release\n\non:\n  schedule:\n    # Run at 00:30 UTC every day\n    - cron: '30 0 * * *'\n  # Allow manual triggering for testing\n  workflow_dispatch:\n\njobs:\n  update-compatibility-date:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          token: ${{ secrets.DEVPROD_PAT }}\n          ref: main\n\n      - name: Get current date\n        id: date\n        run: |\n          echo \"date=$(date +'%Y-%m-%d')\" >> $GITHUB_OUTPUT\n          echo \"max_date=$(date -d '+7 days' +'%Y-%m-%d')\" >> $GITHUB_OUTPUT\n\n      - name: Update compatibility dates\n        run: |\n          echo \"${{ steps.date.outputs.date }}\" > src/workerd/io/release-version.txt\n          echo \"${{ steps.date.outputs.max_date }}\" > src/workerd/io/maximum-compatibility-date.txt\n\n      - name: Check for changes\n        id: git-check\n        run: |\n          if [[ $(git status --porcelain src/workerd/io/release-version.txt src/workerd/io/maximum-compatibility-date.txt) ]]; then\n            echo \"changed=true\" >> $GITHUB_OUTPUT\n          else\n            echo \"changed=false\" >> $GITHUB_OUTPUT\n          fi\n          echo \"last_email=$(git show --format=\"%ae\" -s)\" >> $GITHUB_OUTPUT\n\n      # Publish new version if compatibility-date changed and last commit wasn't a daily release\n      # already.\n      - name: Commit and push change\n        if: steps.git-check.outputs.changed == 'true' && steps.git-check.outputs.last_email != 'workers-devprod@cloudflare.com'\n        run: |\n          git config user.email \"workers-devprod@cloudflare.com\"\n          git config user.name \"Workers DevProd\"\n          git add src/workerd/io/release-version.txt src/workerd/io/maximum-compatibility-date.txt\n          git commit -m \"Release ${{ steps.date.outputs.date }}\"\n          git push\n"
  },
  {
    "path": ".github/workflows/deps-updater.yml",
    "content": "name: Dependency updater\n\non:\n  schedule:\n    # Run at 18:30 UTC every Monday\n    - cron: '30 18 * * 1'\n  # Allow manual triggering for testing\n  workflow_dispatch:\n\nconcurrency:\n  group: deps-updater\n  cancel-in-progress: true\n\npermissions:\n  contents: read\n\njobs:\n  issue:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      pull-requests: write\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          show-progress: false\n          token: ${{ secrets.DEVPROD_PAT }}\n          ref: main\n      - name: Update dependencies\n        run: build/deps/update-deps.py\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Update rust dependencies\n        run: bazel run //deps/rust:crates_vendor -- --repin full\n      - name: Update opencode version\n        run: python3 tools/update_opencode_version.py\n        continue-on-error: true\n      - name: Reformat changed files\n        run: ./tools/cross/format.py git\n      - name: Open pull request\n        id: create-pr\n        uses: peter-evans/create-pull-request@v8\n        with:\n          commit-message: \"update dependencies to latest version\"\n          branch: \"automatic-update-deps\"\n          title: \"Update dependencies\"\n          token: ${{ secrets.DEVPROD_PAT }}\n          author: \"Workers DevProd <workers-devprod@cloudflare.com>\"\n          committer: \"Workers DevProd <workers-devprod@cloudflare.com>\"\n          body: |\n            This is an automated pull request for updating the dependencies of workerd.\n          delete-branch: true\n      - name: Enable Pull Request Automerge\n        run: gh pr merge --rebase --auto ${{ steps.create-pr.outputs.pull-request-number }}\n        env:\n          GH_TOKEN: ${{ secrets.DEVPROD_PAT }}\n"
  },
  {
    "path": ".github/workflows/experimental-workflow.yml",
    "content": "name: Experimental Workflow\non:\n  workflow_dispatch:\n\n# You can modify this workflow in order to verify your changes to Github Actions on a branch.\n# DO NOT MERGE THESE CHANGES TO MAIN\n# This workflow exists to work around a limitation in Github Actions. You can only use \n# workflow_dispatch on files that exist in main. However, once the file exists, you can dispatch to\n# your branch and make sure changes work.\n\npermissions:\n  contents: none\n\njobs:\n  experiment:\n    runs-on: ubuntu-22.04\n    steps:\n      - name: Hello World\n        shell: bash\n        run: echo Hello World\n"
  },
  {
    "path": ".github/workflows/fixup.yml",
    "content": "name: \"Just say no to fixup commits\"\non:\n  workflow_call:\n\njobs:\n  block-fixup:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v6\n    - name: Block Fixup Commit Merge\n      uses: 13rac1/block-fixup-merge-action@v2.0.0\n\n"
  },
  {
    "path": ".github/workflows/internal-build.yml",
    "content": "name: Run internal build\n\non:\n  pull_request_target:\n\n# Read-only permissions are enough\npermissions: read-all\n\nconcurrency:\n  # Cancel existing builds for the same PR.\n  # Otherwise, all other builds will be allowed to run through.\n  group: internal-build-${{ github.event.pull_request.number || github.run_id }}\n  cancel-in-progress: true\n\njobs:\n  internal-build:\n    runs-on: ubuntu-latest\n    steps:\n      # Check if this is a fork and if the author is a Cloudflare org member\n      - name: Check fork status and org membership\n        if: github.event.pull_request.head.repo.fork\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          AUTHOR: ${{ github.event.pull_request.user.login }}\n        run: |\n          echo \"Fork detected. Checking if $AUTHOR is a Cloudflare org member...\"\n\n          if gh api orgs/cloudflare/members/$AUTHOR --silent 2>/dev/null; then\n            echo \"✓ Cloudflare org member confirmed\"\n          else\n            echo \"✗ Not a Cloudflare org public member\"\n            echo \"\"\n            echo \"This workflow only runs for forks from Cloudflare organization public members.\"\n            echo \"If you're an external contributor, please ask the auto-assigned reviewers\"\n            echo \"to run the internal build workflow on your behalf.\"\n            exit 1\n          fi\n\n      # Try to checkout the merge commit - will fail if PR isn't mergeable\n      - uses: actions/checkout@v4\n        id: checkout_merge\n        continue-on-error: true\n        with:\n          ref: refs/pull/${{ github.event.pull_request.number }}/merge\n          show-progress: false\n\n      # Fail the workflow if checkout failed (PR isn't mergeable)\n      - name: Fail if PR isn't mergeable\n        if: steps.checkout_merge.outcome != 'success'\n        run: |\n          echo \"The pull request is not mergeable. Please rebase and resolve any conflicts.\"\n          exit 1\n\n      - name: Get merge commit SHA\n        id: get_sha\n        run: echo \"sha=$(git rev-parse HEAD)\" >> $GITHUB_OUTPUT\n      - name: Run internal build\n        env:\n          CI_URL: ${{ secrets.CI_URL }}\n          CI_CLIENT_ID: ${{ secrets.CI_CF_ACCESS_CLIENT_ID }}\n          CI_CLIENT_SECRET: ${{ secrets.CI_CF_ACCESS_CLIENT_SECRET }}\n          HEAD_REF: ${{ github.event.pull_request.head.ref }}\n          USER_LOGIN: ${{ github.event.pull_request.user.login }}\n        run: |\n          # Format ref based on whether this is a fork\n          if [ \"${{ github.event.pull_request.head.repo.fork }}\" = \"true\" ]; then\n            REF=\"$USER_LOGIN/$HEAD_REF\"\n          else\n            REF=\"$HEAD_REF\"\n          fi\n\n          python3 -u ./tools/cross/internal_build.py \\\n            ${{github.event.pull_request.number}} \\\n            ${{steps.get_sha.outputs.sha}} \\\n            ${{github.event.pull_request.head.sha}} \\\n            ${{github.run_attempt}} \\\n            \"$REF\" \\\n            $CI_URL \\\n            $CI_CLIENT_ID \\\n            $CI_CLIENT_SECRET\n"
  },
  {
    "path": ".github/workflows/issues.yml",
    "content": "name: Issue\n\non:\n  issues:\n    types: [opened, labeled, transferred]\n\njobs:\n  add-to-project:\n    name: Add issue to GH project\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/add-to-project@v1.0.2\n        with:\n          project-url: https://github.com/orgs/cloudflare/projects/1\n          github-token: ${{ secrets.DEVPROD_PAT }}\n          labeled: types\n"
  },
  {
    "path": ".github/workflows/labels.yml",
    "content": "# Based on https://www.neilmacy.co.uk/blog/github-action-to-block-merging\nname: \"Internal PR Required\"\non:\n  workflow_call:\n  pull_request:\n    types: [labeled, unlabeled]\n\njobs:\n  InternalPRRequired:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check for label\n        if: contains(github.event.*.labels.*.name, 'needs-internal-pr')\n        run: |\n          echo \"Pull request is labeled as 'needs-internal-pr'\"\n          echo \"This workflow fails so the pull request cannot be merged\"\n          exit 1\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: Lint\n\non:\n  pull_request:\n  merge_group:\n  push:\n    branches:\n      - main\n\nconcurrency:\n  # Cancel existing builds for the same PR.\n  # Otherwise, all other builds will be allowed to run through.\n  group: lint.yml-${{ github.event.pull_request.number || github.run_id }}\n  cancel-in-progress: true\n\njobs:\n  lint:\n    runs-on: ubuntu-24.04\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          show-progress: false\n      - name: Configure git hooks\n        # Configure git to quell an irrelevant warning for runners (they never commit / push).\n        run: git config core.hooksPath githooks\n      - name: Lint\n        run: |\n          bazel info output_base # Ensure bazel is initialized before proceeding\n          python3 ./tools/cross/format.py --check\n"
  },
  {
    "path": ".github/workflows/new-pr-review.yml",
    "content": "name: New PR Review\n\non:\n  pull_request:\n    types: [opened]\n\njobs:\n  review:\n    if: github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name\n    runs-on: ubuntu-latest\n    timeout-minutes: 30\n    concurrency:\n      group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n      cancel-in-progress: false\n    permissions:\n      id-token: write\n      contents: read\n      issues: write\n      pull-requests: write\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 30 # Fetch some history; not all of it\n\n      - name: Load review prompt\n        id: prompt\n        run: |\n          {\n            echo 'value<<EOF'\n            echo \"You are reviewing PR #${{ github.event.pull_request.number }} on ${{ github.repository }}.\"\n            echo \"\"\n            cat .github/bonk_reviewer.md\n            echo EOF\n          } >> \"$GITHUB_OUTPUT\"\n\n      - name: Run Bonk\n        uses: ask-bonk/ask-bonk/github@main\n        env:\n          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_AI_GATEWAY_ACCOUNT_ID }}\n          CLOUDFLARE_GATEWAY_ID: ${{ secrets.CF_AI_GATEWAY_NAME }}\n          CLOUDFLARE_API_TOKEN: ${{ secrets.CF_AI_GATEWAY_TOKEN }}\n        with:\n          model: 'cloudflare-ai-gateway/anthropic/claude-opus-4-6'\n          forks: 'false'\n          permissions: write\n          opencode_version: '1.2.27'\n          # The auto-reviewer must never push to PR branches. Its prompt\n          # (bonk_reviewer.md) already forbids git write ops, but NO_PUSH\n          # enforces that at the token level so it holds even if the model\n          # ignores the instruction.\n          token_permissions: 'NO_PUSH'\n          prompt: ${{ steps.prompt.outputs.value }}\n"
  },
  {
    "path": ".github/workflows/release-python-runtime.yml",
    "content": "name: Build Python Runtime\n\non:\n  workflow_dispatch:\n    inputs:\n      update-released:\n        description: 'Update already released versions?'\n        required: false\n        default: false\n        type: boolean\n\njobs:\n  build:\n    runs-on: ubuntu-22.04\n    name: build Python runtime\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          show-progress: false\n      - uses: actions/setup-python@v6\n        with:\n          python-version: '3.13'\n      - name: Setup Runner\n        uses: ./.github/actions/setup-runner\n      - name: Configure download mirrors\n        shell: bash\n        run: |\n          if [ ! -z \"${{ secrets.WORKERS_MIRROR_URL }}\" ] ; then\n            # Strip comment in front of WORKERS_MIRROR_URL, then substitute secret to use it.\n            sed -e '/WORKERS_MIRROR_URL/ { s@# *@@; s@WORKERS_MIRROR_URL@${{ secrets.WORKERS_MIRROR_URL }}@; }' -i.bak build/deps/nodejs.MODULE.bazel\n          fi\n      - name: Build and upload Pyodide capnproto bundle\n        env:\n          R2_ACCOUNT_ID: ${{ secrets.PYODIDE_CAPNP_R2_ACCOUNT_ID }}\n          R2_ACCESS_KEY_ID: ${{ secrets.PYODIDE_CAPNP_R2_ACCESS_KEY_ID }}\n          R2_SECRET_ACCESS_KEY: ${{ secrets.PYODIDE_CAPNP_R2_SECRET_ACCESS_KEY }}\n        run: |\n          bazel build @workerd//src/pyodide:python_bundles @workerd//src/pyodide:bundle_version_info --remote_cache=https://bazel:${{ secrets.BAZEL_CACHE_KEY }}@bazel-remote-cache.devprod.cloudflare.dev\n          # boto3 v1.36.0 fails with:\n          # NotImplemented error occurred in CreateMultipartUpload operation: Header 'x-amz-checksum-algorithm' with value 'CRC32' not implemented\n          pip install 'boto3<1.36.0' requests\n          python3 src/pyodide/upload_bundles.py ${{ inputs.update-released == true && '--update-released' || '' }}\n\n      - name: Check for open PR and commit changes\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          # Commit changes to python_metadata.bzl and push to branch\n          # Configure git\n          git config --local user.email \"action@github.com\"\n          git config --local user.name \"release-python-runtime.yml GitHub Action\"\n\n          # Get current branch name\n          BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)\n          echo \"Current branch: $BRANCH_NAME\"\n\n          # Check if there are changes to python_metadata.bzl\n          if git diff --quiet build/python_metadata.bzl; then\n            echo \"No changes to python_metadata.bzl\"\n            exit 0\n          fi\n\n          echo \"Changes detected in python_metadata.bzl\"\n\n          # Check if there's an open PR for this branch\n          PR_COUNT=$(gh pr list --head \"$BRANCH_NAME\" --state open --json number --jq length)\n\n          if [ \"$PR_COUNT\" -eq 0 ]; then\n            echo \"No open PR found for branch $BRANCH_NAME, skipping commit\"\n            exit 0\n          fi\n\n          echo \"Found open PR for branch $BRANCH_NAME\"\n\n          # Commit and push the changes\n          git add build/python_metadata.bzl build/deps/python.MODULE.bazel\n          git commit --fixup HEAD -m \"Update python_metadata.bzl with new bundle info\n\n          This commit updates the backport and integrity values in python_metadata.bzl\n          based on the latest Pyodide bundle upload.\n\n          🤖 Generated automatically by release-python-runtime workflow\"\n\n          # --no-verify because 'git merge-base origin/main HEAD' fails in pre-commit.\n          # There shouldn't be formatting errors anyways and if there are CI will catch it.\n          git push origin \"$BRANCH_NAME\" --no-verify\n          echo \"Changes committed and pushed to $BRANCH_NAME\"\n"
  },
  {
    "path": ".github/workflows/release-python-snapshots.yml",
    "content": "name: Make Python Snapshots\n\non:\n  workflow_dispatch:\n    inputs:\n      update-released:\n        description: 'Update already released versions?'\n        required: false\n        default: false\n        type: boolean\n\njobs:\n  build-linux:\n    uses: ./.github/workflows/_bazel.yml\n    with:\n      image: ubuntu-22.04\n      os_name: linux\n      arch_name: 'X64'\n      suffix: ''\n      run_tests: false\n      extra_bazel_args: '--config=ci-linux --config=ci-test'\n      upload_binary: true\n    secrets:\n      BAZEL_CACHE_KEY: ${{ secrets.BAZEL_CACHE_KEY }}\n      WORKERS_MIRROR_URL: ${{ secrets.WORKERS_MIRROR_URL }}\n\n  build:\n    needs: [build-linux]\n    runs-on: ubuntu-22.04\n    name: Build and upload Python memory snapshots\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          show-progress: false\n      - uses: actions/setup-python@v6\n        with:\n          python-version: '3.13'\n      - name: Setup Runner\n        uses: ./.github/actions/setup-runner\n      - name: Download workerd binary\n        uses: actions/download-artifact@v8\n        with:\n          name: linux-X64-binary\n          path: /tmp\n\n      - name: Make workerd binary executable\n        run: chmod +x /tmp/workerd\n\n      - name: Build and upload Python memory snapshots\n        env:\n          R2_ACCOUNT_ID: ${{ secrets.PYODIDE_CAPNP_R2_ACCOUNT_ID }}\n          R2_ACCESS_KEY_ID: ${{ secrets.PYODIDE_CAPNP_R2_ACCESS_KEY_ID }}\n          R2_SECRET_ACCESS_KEY: ${{ secrets.PYODIDE_CAPNP_R2_SECRET_ACCESS_KEY }}\n          WORKERD_BINARY: \"/tmp/workerd\"\n        run: |\n          bazel build @workerd//src/pyodide:bundle_version_info\n          # boto3 v1.36.0 fails with:\n          # NotImplemented error occurred in CreateMultipartUpload operation: Header 'x-amz-checksum-algorithm' with value 'CRC32' not implemented\n          pip install 'boto3<1.36.0' requests\n          python3 src/pyodide/make_snapshots.py ${{ inputs.update-released == true && '--update-released' || '' }}\n\n      - name: Check for open PR and commit changes\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          # Commit changes to python_metadata.bzl and push to branch\n          # Configure git\n          git config --local user.email \"action@github.com\"\n          git config --local user.name \"release-python-snapshots.yml GitHub Action\"\n\n          # Get current branch name\n          BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)\n          echo \"Current branch: $BRANCH_NAME\"\n\n          # Check if there are changes to python_metadata.bzl\n          if git diff --quiet build/python_metadata.bzl; then\n            echo \"No changes to python_metadata.bzl\"\n            exit 0\n          fi\n\n          echo \"Changes detected in python_metadata.bzl\"\n\n          # Check if there's an open PR for this branch\n          PR_COUNT=$(gh pr list --head \"$BRANCH_NAME\" --state open --json number --jq length)\n\n          if [ \"$PR_COUNT\" -eq 0 ]; then\n            echo \"No open PR found for branch $BRANCH_NAME, skipping commit\"\n            exit 0\n          fi\n\n          echo \"Found open PR for branch $BRANCH_NAME\"\n\n          # Commit and push the changes\n          git add build/python_metadata.bzl\n          git commit --fixup HEAD -m \"Update python_metadata.bzl with new snapshot info\n\n          This commit updates the snapshot values in python_metadata.bzl based on the latest\n          snapshot uploads.\n\n          🤖 Generated automatically by release-python-snapshots workflow\"\n\n          # --no-verify because 'git merge-base origin/main HEAD' fails in pre-commit.\n          # There shouldn't be formatting errors anyways and if there are CI will catch it.\n          git push origin \"$BRANCH_NAME\" --no-verify\n          echo \"Changes committed and pushed to $BRANCH_NAME\"\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Build & Release\n\non:\n  push:\n    branches:\n      - main\n  workflow_dispatch:\n    inputs:\n      patch:\n        description: 'Patch Version'\n        required: true\n        default: '0'\n      prerelease:\n        description: 'Is Prerelease'\n        type: boolean\n        default: false\npermissions:\n  id-token: write\n  contents: write\n  actions: write\n\njobs:\n  version:\n    outputs:\n      date: ${{ steps.echo.outputs.date }}\n      version: ${{ steps.echo.outputs.version }}\n      types_version: ${{ steps.echo.outputs.types_version }}\n    # version job uses ubuntu 24.04, this way we don't have to install the updated clang while\n    # the build job uses 22.04 for libc compatibility.\n    runs-on: ubuntu-24.04\n    steps:\n      - uses: actions/checkout@v6\n      - id: echo\n        run: |\n          echo \"date=$(cat src/workerd/io/release-version.txt)\" >> $GITHUB_OUTPUT;\n          echo \"version=${{ (github.event_name != 'push' && inputs.prerelease == true) && '0' || '1'}}.$(cat src/workerd/io/release-version.txt | tr -d '-').${{ github.event_name == 'push' && '1' || inputs.patch }}\" >> $GITHUB_OUTPUT;\n          echo \"types_version=${{ (github.event_name != 'push' && inputs.prerelease == true) && '0' || '4'}}.$(cat src/workerd/io/release-version.txt | tr -d '-').${{ github.event_name == 'push' && '1' || inputs.patch }}\" >> $GITHUB_OUTPUT;\n  check-tag:\n    name: Check tag is new\n    outputs:\n      exists: ${{ steps.check_tag.outputs.exists }}\n    needs: [version]\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout Repo\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - uses: mukunku/tag-exists-action@v1.7.0\n        id: check_tag\n        with:\n          tag: v${{ needs.version.outputs.version }}\n\n  tag-and-release:\n    name: Tag & Release\n    outputs:\n      upload_url: ${{ steps.create_release.outputs.upload_url }}\n    needs: [check-tag, version]\n    runs-on: ubuntu-latest\n    if: ${{ needs.check-tag.outputs.exists != 'true' }}\n    steps:\n      - name: Checkout Repo\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - run: git tag v${{ needs.version.outputs.version }} && git push origin v${{ needs.version.outputs.version }}\n      - uses: ncipollo/release-action@v1\n        id: create_release\n        with:\n          generateReleaseNotes: true\n          token: ${{ secrets.GITHUB_TOKEN }}\n          tag: v${{ needs.version.outputs.version }}\n\n  build:\n    strategy:\n      matrix:\n        include:\n          - title: linux\n            os-name: Linux\n            image: ubuntu-22.04-16core\n            bazel-config: release_linux\n            target-arch: X64\n          - title: linux-arm64\n            os-name: Linux\n            image: ubuntu-22.04-arm-16core\n            bazel-config: release_linux\n            target-arch: ARM64\n          # Based on runner availability, we build both Apple Silicon and (cross-compiled) x86\n          # release binaries on the macos-15-xlarge runner.\n          - title: macOS-x64\n            os-name: macOS\n            # This configuration is used for cross-compiling – macos-15-xlarge is Apple Silicon-based but\n            # we use it to compile the x64 release.\n            image: macos-15-xlarge\n            bazel-config: release_macos_cross_x86_64\n            target-arch: X64\n          - title: macOS-arm64\n            os-name: macOS\n            image: macos-15-xlarge\n            bazel-config: release_macos\n            target-arch: ARM64\n          - title: windows\n            os-name: Windows\n            image: windows-2025-16core\n            bazel-config: release_windows\n            target-arch: X64\n    name: build (${{ matrix.title }})\n    uses: './.github/workflows/_bazel.yml'\n    with:\n      image: ${{ matrix.image }}\n      os_name: ${{ matrix.os-name }}\n      phase: '-release'\n      extra_bazel_args: '--strip=always --config=${{matrix.bazel-config}} --config=ci-release --config=wpt-report --config=wpt-test'\n      arch_name: ${{ matrix.target-arch }}\n      upload_binary: true\n      macos_use_lld: true\n      # On release, generate a full WPT report...\n      run_tests: true\n      upload_test_logs: true\n      test_target: //src/wpt/...\n    secrets:\n      BAZEL_CACHE_KEY: ${{ secrets.BAZEL_CACHE_KEY }}\n      WORKERS_MIRROR_URL: ${{ secrets.WORKERS_MIRROR_URL }}\n\n  upload-artifacts:\n    name: Upload Artifacts\n    needs: [version, tag-and-release, build]\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        arch: [linux-64, darwin-64, windows-64]\n        # This variable itself is unused, but allows us to set up two macOS builds. arm64 builds for\n        # other platforms will be supported later, then we'll list both architectures here.\n        cpu: [X64]\n        include:\n          - arch: linux-64\n            name: Linux-X64\n          - arch: linux-arm64\n            name: Linux-ARM64\n            cpu: ARM64\n          - arch: darwin-64\n            name: macOS-X64\n          - arch: darwin-arm64\n            name: macOS-ARM64\n            cpu: ARM64\n          - arch: windows-64\n            name: Windows-X64\n    steps:\n      - name: Checkout Repo\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Download ${{ matrix.name }}\n        uses: actions/download-artifact@v8\n        with:\n          name: ${{ matrix.name }}-binary\n          path: /tmp\n      # Set execute permissions before compressing the binary\n      - if: matrix.arch != 'windows-64'\n        run: chmod +x /tmp/workerd\n      - name: Compress release binary\n        run: |\n          # As of release v1.20230404.0 the Linux x64 binary after debug_strip is 65.8 MB,\n          # 21.0 MB with gzip and 17.3 MB with brotli -9. Use gzip as a widely supported format\n          # which still produces an acceptable compressed size.\n          gzip -9N -k /tmp/workerd${{ matrix.arch == 'windows-64' && '.exe' || '' }}\n      - run: mv /tmp/workerd${{ matrix.arch == 'windows-64' && '.exe' || '' }}.gz /tmp/workerd-${{ matrix.arch }}.gz\n      # Upload compressed release binaries – one set of artifacts is sufficient with gzip being\n      # widely supported\n      - name: Upload Release Assets\n        id: upload-release-asset\n        uses: actions/upload-release-asset@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ needs.tag-and-release.outputs.upload_url }}\n          asset_path: /tmp/workerd-${{ matrix.arch }}.gz\n          asset_name: workerd-${{ matrix.arch }}.gz\n          asset_content_type: application/gzip\n\n      # Upload release to npm\n      - name: Use Node\n        uses: actions/setup-node@v6\n        with:\n          node-version: 24\n      - name: Modify package.json version\n        run: node npm/scripts/bump-version.mjs npm/workerd-${{ matrix.arch }}/package.json\n        env:\n          WORKERD_VERSION: ${{ needs.version.outputs.version }}\n          LATEST_COMPATIBILITY_DATE: ${{ needs.version.outputs.date }}\n      - run: mkdir npm/workerd-${{ matrix.arch }}/bin\n      - run: cp /tmp/workerd${{ matrix.arch == 'windows-64' && '.exe' || '' }} npm/workerd-${{ matrix.arch }}/bin/workerd${{ matrix.arch == 'windows-64' && '.exe' || '' }}\n      - run: echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > npm/workerd-${{ matrix.arch }}/.npmrc\n      - run: cd npm/workerd-${{ matrix.arch }} && npm publish --access public --tag ${{ startsWith(needs.version.outputs.version, '0') && 'beta' || 'latest'}}\n        env:\n          NPM_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}\n\n  miniflare-test:\n    name: Run Miniflare tests\n    needs: [build]\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout workers-sdk\n        uses: actions/checkout@v6\n        with:\n          repository: cloudflare/workers-sdk\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v4\n      - name: Use Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: lts/*\n          cache: 'pnpm'\n      - name: Install workers-sdk dependencies\n        run: pnpm install\n\n      - name: Download workerd binary\n        uses: actions/download-artifact@v8\n        with:\n          name: Linux-X64-binary\n          path: /tmp\n      - name: Make workerd binary executable\n        run: chmod +x /tmp/workerd\n\n      - name: Build Miniflare and dependencies\n        run: pnpm turbo build --filter miniflare\n\n      - name: Run Miniflare tests\n        run: pnpm --filter miniflare test\n        env:\n          MINIFLARE_WORKERD_PATH: /tmp/workerd\n\n  publish-wrapper:\n    name: Publish `workerd` to NPM\n    needs: [version, upload-artifacts]\n    runs-on: ubuntu-22.04-16core\n    steps:\n      - name: Checkout Repo\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Use Node\n        uses: actions/setup-node@v6\n        with:\n          node-version: 24\n\n      - name: Cache\n        id: cache\n        uses: actions/cache@v5\n        # Use same cache and build configuration as release build, this allows us to keep download\n        # sizes small and generate types with optimization enabled, should be slightly faster.\n        with:\n          path: ~/bazel-disk-cache\n          key: bazel-disk-cache-release-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.bazelversion', '.bazelrc', 'MODULE.bazel') }}\n      - name: Setup Runner\n        uses: ./.github/actions/setup-runner\n      - name: Build type generating Worker\n        run: |\n          bazel build --strip=always --remote_cache=https://bazel:${{ secrets.BAZEL_CACHE_KEY }}@bazel-remote-cache.devprod.cloudflare.dev --config=ci --config=release_linux //types:types_worker\n\n      - name: Modify package.json version\n        run: node npm/scripts/bump-version.mjs npm/workerd/package.json\n        env:\n          WORKERD_VERSION: ${{ needs.version.outputs.version }}\n          LATEST_COMPATIBILITY_DATE: ${{ needs.version.outputs.date }}\n      - run: mkdir -p npm/workerd/lib\n      - run: mkdir -p npm/workerd/bin\n      - name: Build node-install\n        run: npx esbuild npm/lib/node-install.ts --outfile=npm/workerd/install.js --bundle --target=node22 --define:LATEST_COMPATIBILITY_DATE=\"\\\"${LATEST_COMPATIBILITY_DATE}\\\"\" --define:WORKERD_VERSION=\"\\\"${WORKERD_VERSION}\\\"\" --platform=node --external:workerd --log-level=warning\n        env:\n          WORKERD_VERSION: ${{ needs.version.outputs.version }}\n          LATEST_COMPATIBILITY_DATE: ${{ needs.version.outputs.date }}\n      - name: Build node-shim\n        run: npx esbuild npm/lib/node-shim.ts --outfile=npm/workerd/bin/workerd --bundle --target=node22 --define:LATEST_COMPATIBILITY_DATE=\"\\\"${LATEST_COMPATIBILITY_DATE}\\\"\" --define:WORKERD_VERSION=\"\\\"${WORKERD_VERSION}\\\"\" --platform=node --external:workerd --log-level=warning\n        env:\n          WORKERD_VERSION: ${{ needs.version.outputs.version }}\n          LATEST_COMPATIBILITY_DATE: ${{ needs.version.outputs.date }}\n      - name: Build node-path\n        run: npx esbuild npm/lib/node-path.ts --outfile=npm/workerd/lib/main.js --bundle --target=node22 --define:LATEST_COMPATIBILITY_DATE=\"\\\"${LATEST_COMPATIBILITY_DATE}\\\"\" --define:WORKERD_VERSION=\"\\\"${WORKERD_VERSION}\\\"\" --platform=node --external:workerd --log-level=warning\n        env:\n          WORKERD_VERSION: ${{ needs.version.outputs.version }}\n          LATEST_COMPATIBILITY_DATE: ${{ needs.version.outputs.date }}\n      - name: Build package\n        run: node npm/scripts/build-shim-package.mjs\n        env:\n          WORKERD_VERSION: ${{ needs.version.outputs.version }}\n          LATEST_COMPATIBILITY_DATE: ${{ needs.version.outputs.date }}\n      - run: echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > npm/workerd/.npmrc\n      - run: cd npm/workerd && npm publish --access public --tag ${{ startsWith(needs.version.outputs.version, '0') && 'beta' || 'latest'}}\n\n  build-and-publish-types:\n    runs-on: ubuntu-22.04-16core\n    needs: [version, upload-artifacts]\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          show-progress: false\n      - name: Use Node\n        uses: actions/setup-node@v6\n        with:\n          node-version: 24 # needed for a version of npm that supports \"trusted publishing\".\n      - name: Cache\n        id: cache\n        uses: actions/cache@v5\n        # Use same cache and build configuration as release build, this allows us to keep download\n        # sizes small and generate types with optimization enabled, should be slightly faster.\n        with:\n          path: ~/bazel-disk-cache\n          key: bazel-disk-cache-release-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.bazelversion', '.bazelrc', 'MODULE.bazel') }}\n      - name: Setup Runner\n        uses: ./.github/actions/setup-runner\n      - name: build types\n        run: |\n          bazel build --strip=always --remote_cache=https://bazel:${{ secrets.BAZEL_CACHE_KEY }}@bazel-remote-cache.devprod.cloudflare.dev --config=ci --config=release_linux //types\n      - name: Build package\n        run: node npm/scripts/build-types-package.mjs\n        env:\n          WORKERD_VERSION: ${{ needs.version.outputs.types_version }}\n          LATEST_COMPATIBILITY_DATE: ${{ needs.version.outputs.date }}\n      - run: cp -r bazel-bin/types/definitions/. npm/workers-types\n      - run: cp npm/workers-types/oldest/* npm/workers-types\n      - run: echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > npm/workers-types/.npmrc\n      - run: cd npm/workers-types && npm publish --access public --tag ${{ startsWith(needs.version.outputs.version, '0') && 'beta' || 'latest'}}\n"
  },
  {
    "path": ".github/workflows/semgrep.yml",
    "content": "\non:\n  workflow_dispatch: {}\n  schedule:\n    - cron: '0 0 * * *'\nname: Semgrep config\njobs:\n  semgrep:\n    name: semgrep/ci\n    runs-on: ubuntu-latest\n    env:\n      SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}\n      SEMGREP_URL: https://cloudflare.semgrep.dev\n      SEMGREP_APP_URL: https://cloudflare.semgrep.dev\n      SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version\n    container:\n      image: semgrep/semgrep\n    steps:\n      - uses: actions/checkout@v6\n      - run: semgrep ci\n\n\n\n\n\n\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Tests\n\non:\n  pull_request:\n  merge_group:\n  push:\n    branches:\n      - main\n\nconcurrency:\n  # Cancel existing builds for the same PR.\n  # Otherwise, all other builds will be allowed to run through.\n  group: test.yml-${{ github.event.pull_request.number || github.run_id }}\n  cancel-in-progress: true\n\npermissions:\n  # Read repo\n  contents: read\n  # Read/write artifacts\n  actions: write\n  # Required for adding comments to PR.\n  # Specifically for sticky-pull-request-comment workflow action.\n  pull-requests: write\njobs:\n  fixup:\n    if: github.event_name == 'pull_request'\n    uses: ./.github/workflows/fixup.yml\n  labels:\n    if: github.event_name == 'pull_request'\n    uses: ./.github/workflows/labels.yml\n  test:\n    strategy:\n      matrix:\n        os:\n          [\n            { name: linux, arch: X64, image: ubuntu-22.04-16core },\n            { name: linux-arm, arch: ARM64, image: ubuntu-22.04-arm-16core },\n            { name: macOS, arch: ARM64, image: macos-15-xlarge, use_lld: true },\n            { name: windows, arch: X64, image: windows-2025-16core },\n          ]\n        config: [\n            # Default build: no suffix or additional bazel arguments\n            { suffix: '' },\n            # Debug build\n            { suffix: -debug },\n          ]\n        include:\n          # Add an Address Sanitizer (ASAN) build on Linux for additional checking.\n          - os: { name: linux, arch: X64, image: ubuntu-22.04-16core }\n            config: { suffix: -asan }\n          # TODO (later): The custom Windows-debug configuration consistently runs out of disk\n          # space on CI, disable it for now. Once https://github.com/bazelbuild/bazel/issues/21615\n          # has been resolved we can likely re-enable it and possibly fold up the custom\n          # configurations, as we can more easily disable PDB file generation.\n          # - os:     { name : windows, image : windows-2025 }\n          #   config: { suffix: -debug, bazel-args: --config=windows_dbg }\n        exclude:\n          - os: { name: windows, arch: X64, image: windows-2025-16core }\n            config: { suffix: -debug }\n          # due to resource constraints, exclude the macOS and x64 Linux debug runners for now.\n          # linux-asan and arm64 linux-debug should provide sufficient coverage for building in the\n          # debug configuration.\n          - os: { name: macOS, arch: ARM64, image: macos-15-xlarge }\n            config: { suffix: -debug }\n          - os: { name: linux, arch: X64, image: ubuntu-22.04-16core }\n            config: { suffix: -debug }\n            # linux release is handled by separate test-linux job\n          - os: { name: linux, arch: X64, image: ubuntu-22.04-16core }\n            config: { suffix: '' }\n      fail-fast: false\n    name: test (${{ matrix.os.name }}, ${{ matrix.os.image}}${{matrix.config.suffix != '' && format(', {0}', matrix.config.suffix) || ''}})\n    uses: ./.github/workflows/_bazel.yml\n    with:\n      image: ${{ matrix.os.image }}\n      os_name: ${{ matrix.os.name }}\n      arch_name: ${{ matrix.os.arch }}\n      suffix: ${{ matrix.config.suffix }}\n      extra_bazel_args: '--config=ci-test --config=ci-${{matrix.os.name}}${{matrix.config.suffix}}'\n      macos_use_lld: ${{ matrix.os.use_lld || false }}\n    secrets:\n      BAZEL_CACHE_KEY: ${{ secrets.BAZEL_CACHE_KEY }}\n      WORKERS_MIRROR_URL: ${{ secrets.WORKERS_MIRROR_URL }}\n\n  # Handled separately from `test` to speed up execution of workers-sdk-test which depends on it.\n  test-linux:\n    uses: ./.github/workflows/_bazel.yml\n    with:\n      image: ubuntu-22.04-16core\n      os_name: linux\n      arch_name: 'X64'\n      suffix: ''\n      extra_bazel_args: '--config=ci-test --config=ci-linux --config=wpt-report'\n      upload_test_logs: true\n      upload_binary: true\n      build_container_images: true\n    secrets:\n      BAZEL_CACHE_KEY: ${{ secrets.BAZEL_CACHE_KEY }}\n      WORKERS_MIRROR_URL: ${{ secrets.WORKERS_MIRROR_URL }}\n\n  lint:\n    uses: ./.github/workflows/_bazel.yml\n    with:\n      extra_bazel_args: '--config=lint --config=clang-tidy --config=ci-test --config=ci-linux-common'\n      run_tests: false\n      parse_headers: true\n    secrets:\n      BAZEL_CACHE_KEY: ${{ secrets.BAZEL_CACHE_KEY }}\n      WORKERS_MIRROR_URL: ${{ secrets.WORKERS_MIRROR_URL }}\n\n  check-snapshot:\n    runs-on: ubuntu-22.04-16core\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          show-progress: false\n      - name: Cache\n        id: cache\n        uses: actions/cache@v5\n        # Use same cache and build configuration as release build, this allows us to keep download\n        # sizes small and generate types with optimization enabled, should be slightly faster.\n        with:\n          path: ~/bazel-disk-cache\n          key: bazel-disk-cache-release-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.bazelversion', '.bazelrc', 'MODULE.bazel') }}\n      - name: Setup Runner\n        uses: ./.github/actions/setup-runner\n      - name: build types\n        run: |\n          bazel build --strip=always --remote_cache=https://bazel:${{ secrets.BAZEL_CACHE_KEY }}@bazel-remote-cache.devprod.cloudflare.dev --config=ci --config=release_linux //types\n      - name: Check snapshot diff\n        run: |\n          diff -r types/generated-snapshot/latest bazel-bin/types/definitions/latest > types.diff\n          diff -r types/generated-snapshot/experimental bazel-bin/types/definitions/experimental >> types.diff\n      - name: 'Put diff on the environment'\n        if: failure()\n        id: types_diff\n        run: |\n          {\n            echo 'TYPES_DIFF<<EOF'\n            cat types.diff\n            echo EOF\n          } >> \"$GITHUB_ENV\"\n      - uses: actions/upload-artifact@v7\n        id: artifact-upload-step\n        with:\n          name: generated-snapshot\n          path: bazel-bin/types/definitions/\n      - name: 'Comment on PR with error details'\n        if: failure()\n        uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728\n        with:\n          message: |\n            The generated output of `@cloudflare/workers-types` has been changed by this PR. If this is intentional, run `just generate-types` to update the snapshot. Alternatively, you can download the full generated types: ${{ steps.artifact-upload-step.outputs.artifact-url }}\n\n            <details>\n            <summary>Full Type Diff</summary>\n\n            ```diff\n            ${{ env.TYPES_DIFF }}\n            ```\n\n            </details>\n      - name: 'Comment on PR with error details'\n        if: success()\n        uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728\n        with:\n          only_update: true\n          message: |\n            The generated output of `@cloudflare/workers-types` matches the snapshot in `types/generated-snapshot` :tada:\n\n  workers-sdk-test:\n    needs: [test-linux, check-snapshot]\n    name: Run workers-sdk tests\n    runs-on: ubuntu-22.04-16core\n    steps:\n      - name: Checkout workers-sdk\n        uses: actions/checkout@v6\n        with:\n          repository: cloudflare/workers-sdk\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v4\n      - name: Use Node.js\n        uses: actions/setup-node@v6\n        with:\n          # Match workers-sdk node version\n          node-version: 20.19.6\n          cache: 'pnpm'\n      - name: Install workers-sdk dependencies\n        run: pnpm install\n\n      - name: Download workerd binary\n        uses: actions/download-artifact@v8\n        with:\n          name: linux-X64-binary\n          path: /tmp\n\n      - name: Make workerd binary executable\n        run: chmod +x /tmp/workerd\n\n      - name: Download generated types\n        uses: actions/download-artifact@v8\n        with:\n          name: generated-snapshot\n          path: /tmp/test-types\n\n      - name: Create and install workers-types package\n        run: |\n          cp /tmp/test-types/oldest/* /tmp/test-types\n          cat > /tmp/test-types/package.json << EOF\n          {\n              \"name\": \"@cloudflare/workers-types\"\n            }\n          EOF\n          pnpm add /tmp/test-types -w\n\n      - name: Run Wrangler unit tests\n        # Run all the wrangler unit tests besides the ConfigController ones since those\n        # are currently very flaky (the ANT team will look into stabilizing them and\n        # re-enabling them here as soon as possible, ref: https://jira.cfdata.org/browse/DEVX-2309)\n        run: pnpm test:ci --filter wrangler -- -t '^((?!(ConfigController)).)*$'\n        env:\n          MINIFLARE_WORKERD_PATH: /tmp/workerd\n\n      - name: Run Miniflare unit tests\n        run: pnpm test:ci --filter miniflare\n        env:\n          MINIFLARE_WORKERD_PATH: /tmp/workerd\n\n      - name: Run Vite unit tests\n        run: pnpm test:ci --filter @cloudflare/vite-plugin\n        env:\n          MINIFLARE_WORKERD_PATH: /tmp/workerd\n\n      - name: Run Vitest unit tests\n        run: pnpm test:ci --filter=\"./fixtures/vitest-pool-workers-examples\"\n        env:\n          MINIFLARE_WORKERD_PATH: /tmp/workerd\n\n      - name: Run Wrangler Node.js e2e tests\n        run: pnpm test:e2e --filter wrangler -- unenv-preset\n        env:\n          MINIFLARE_WORKERD_PATH: /tmp/workerd\n\n  wpt-report:\n    needs: [test-linux]\n    uses: ./.github/workflows/_wpt.yml\n    with:\n      image: 'ubuntu-22.04-16core'\n      logs_artifact: 'test-logs-linux-X64.zip'\n      report_artifact: 'wpt-report-linux-X64.json'\n"
  },
  {
    "path": ".github/workflows/wpt-report.yml",
    "content": "name: WPT report\n\non:\n  # Allow manual triggering for testing\n  workflow_dispatch:\n\npermissions:\n  # Read repo\n  contents: read\n\n  # Read/write artifacts\n  actions: write\n\njobs:\n  test:\n    uses: ./.github/workflows/_bazel.yml\n    with:\n      os_name: linux\n      suffix: ''\n      extra_bazel_args: '--config=ci-test'\n      test_target: '//src/wpt/...'\n      upload_test_logs: true\n      fetch_depth: 0\n    secrets:\n      BAZEL_CACHE_KEY: ${{ secrets.BAZEL_CACHE_KEY }}\n      WORKERS_MIRROR_URL: ${{ secrets.WORKERS_MIRROR_URL }}\n\n  report:\n    needs: [test]\n    uses: ./.github/workflows/_wpt.yml\n    with:\n        image: ubuntu-22.04\n        logs_artifact: 'test-logs-linux-X64.zip'\n        report_artifact: 'wpt-report.json'\n\n\n\n\n\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea\n.DS_Store\n\n# directory for developers to store local resources\n/.local/\n\n/*.code-workspace\n\nnode_modules\n/npm/*/bin\n/npm/workerd/install.js\n/npm/workerd/lib/\npackage-lock.json\n\n# The external link for compile_flags.txt: Differs on Windows vs macOS/Linux, so we can't check it in. The pattern needs to not have a trailing / because it's a symlink on macOS/Linux.\n/external\n# Bazel output symlinks: Same reasoning as /external. You need the * because people can change the name of the directory your repository is cloned into, changing the bazel-<workspace_name> symlink.\n/bazel-*\n# Bazel 8's MODULE lock, since we're not actually locking anything yet.\nMODULE.bazel.lock\n# Compiled output -> don't check in\n/compile_commands.json\n/rust-project.json\n# Directory where clangd puts its indexing work\n/.cache/\n\n/.bazel-cache\n\n/workerd-*\n\n/docs/api\n\n*.tsbuildinfo\n\n# Bazel plugin for Intellij paths\n.clwb\n.aswb\n\ncoverage\nperf.data\n\n.claude\n\n# opencode auto-generates package.json, bun.lock, and .gitignore inside .opencode/\n# at startup to install plugin dependencies. These are runtime artifacts that should\n# not be committed — the opencode CLI version is pinned in CI workflows (see\n# .github/workflows/bonk.yml) which determines the plugin version deterministically.\n# Without these rules, Bonk (our AI reviewer) will see the generated files as dirty\n# and commit them to PR branches, causing merge conflicts.\n.opencode/package.json\n.opencode/bun.lock\n.opencode/.gitignore\n\n# python\n__pycache__\n"
  },
  {
    "path": ".npmrc",
    "content": "# Ref: https://pnpm.io/npmrc#manage-package-manager-versions\n# When enabled, pnpm will automatically download and run the version of pnpm\n# specified in the packageManager field of package.json.\nmanage-package-manager-versions = true\n\n# Disabling pnpm [hoisting](https://pnpm.io/npmrc#hoist) by setting `hoist=false` is recommended on\n# projects using rules_js so that pnpm outside of Bazel lays out a node_modules tree similar to what\n# rules_js lays out under Bazel (without a hidden node_modules/.pnpm/node_modules)\nhoist=false\n"
  },
  {
    "path": ".opencode/agent/architect.md",
    "content": "---\ndescription: Read-only code review and architectural analysis. Provides findings and recommendations without making code changes. Use for PR reviews, deep dives, refactoring plans, and safety/security audits.\nmode: primary\ntemperature: 0.1\npermission:\n  edit:\n    '*': deny\n    'docs/planning/*': allow\n  bash:\n    '*': deny\n    'git log*': allow\n    'git show*': allow\n    'git diff*': allow\n    'git blame*': allow\n    'git fetch*': allow\n    'git branch*': allow\n    'git rev-parse*': allow\n    'git merge-base*': allow\n    'git config user.name': allow\n    'git config user.email': allow\n    'bazel query*': allow\n    'bazel cquery*': allow\n    'bazel aquery*': allow\n    'just clang-tidy*': allow\n    'clang-tidy*': allow\n    'rg *': allow\n    'grep *': allow\n    'find *': allow\n    'ls': allow\n    'ls *': allow\n    'cat *': allow\n    'head *': allow\n    'tail *': allow\n    'wc *': allow\n    'gh pr view*': allow\n    'gh pr checks*': allow\n    'gh pr status*': allow\n    'gh pr diff*': allow\n    'gh pr list*': allow\n    'gh pr checkout*': ask\n    'gh pr comment*': ask\n    'gh pr review*': ask\n    'gh issue view*': allow\n    'gh issue list*': allow\n    'gh issue comment*': ask\n    'gh issue create*': ask\n    'gh issue edit*': ask\n    'gh issue status': allow\n    'gh auth status': allow\n    'gh alias list': allow\n    'gh api *': ask\n---\n\nYou are an expert software architect specializing in C++ systems programming, Rust FFI integration, JavaScript runtime internals, and high-performance server software.\n\n**You are read-only. You do NOT make code changes.** You analyze, critique, and recommend. If asked to make code changes or write documents you cannot produce, prompt the user to switch to Build mode.\n\nYour role is to perform deep architectural analysis and provide actionable recommendations in support of:\n\n- refactoring\n- complexity reduction\n- memory safety\n- performance optimization\n- thread safety\n- error handling\n- API design\n- security vulnerability mitigation\n- standards compliance\n- testing\n- documentation improvements\n- code review.\n\nYou can produce detailed reports, refactoring plans, implementation plans, suggestion lists, and TODO lists in markdown format in the `docs/planning` directory.\n\nYou will keep these documents up to date as work progresses and they should contain enough context to help resume work after interruptions.\n\nYou can also perform code reviews on local changes, pull requests, or specific code snippets. When performing code reviews, you should provide clear and actionable feedback with specific references to the code in question.\n\nIn addition to these instructions, check for AGENT.md files in specific directories for any additional context\nor instructions relevant to those areas (if present). Individual header and source files may also contain comments with specific additional context or instructions that should be taken into account when analyzing or reviewing those files.\n\n---\n\n## Context Management\n\nWhen analyzing code, be deliberate about how you gather context to avoid wasting your context window:\n\n- **Start narrow, expand as needed**: Begin by reading the specific files or functions under review. Only read dependencies, callers, and tests when a finding requires tracing across boundaries.\n- **Use the `cross-reference` tool for C++ class lookups**: When analyzing a C++ class, call `cross-reference` first to get the header, implementation files, JSG registration, type group, test files, and compat flag gating in one shot. This replaces 4-6 separate grep calls.\n- **Use search before read**: For large files (>500 lines), use grep or search to locate relevant sections (function definitions, class declarations, specific patterns) before reading full files. Read targeted ranges rather than entire files.\n- **Use the Task tool for broad exploration**: When you need to understand how a pattern is used across the codebase (e.g., \"how is `IoOwn` used?\"), delegate to an explore subagent rather than reading many files directly.\n- **Prioritize headers over implementations**: When understanding APIs or interfaces, read `.h` files first. Only read `.c++` files when analyzing implementation details.\n- **Check `src/workerd/util/` proactively**: Before suggesting a new utility or pattern, search the util directory to check if one already exists.\n\n---\n\n## Workflows\n\n### Reviewing code or a pull request\n\n1. **Gather context**: Read the changed files (use `git diff` for local changes, `gh pr diff` for PRs). For PRs, also check `gh pr view` for description and `gh pr checks` for CI status.\n2. **Understand scope**: Identify what the change is trying to do. Read the PR description, commit messages, or ask the user if unclear.\n3. **Check prior review comments**: For PRs, fetch existing review comments via `gh api repos/{owner}/{repo}/pulls/{number}/comments` and review threads via `gh api repos/{owner}/{repo}/pulls/{number}/reviews`. Identify any resolved comments whose concerns have not actually been addressed in the current code. Flag these in your findings.\n4. **Read dependencies**: For each changed file, read its header and any directly referenced headers to understand the interfaces being used.\n5. **Identify the reviewer**: Load `identify-reviewer` to determine the local user's GitHub handle and git identity. Use this throughout the review to refer to the reviewer's own prior comments and commits in second person.\n6. **Load skills**: Based on the scope of the changes, load the relevant specialized analysis skills:\n   - For **balanced reviews** (default): load `workerd-safety-review`, `workerd-api-review`, and `kj-style`.\n   - For **PR reviews**: also load `pr-review-guide`.\n   - For **focused reviews**: load only the skills relevant to the focus area (see Analysis Modes below).\n   - Always load `kj-style` when reviewing C++ code.\n   - When the diff contains `.rs` files under `src/rust/`, also load `rust-review`. For changes that span both C++ and Rust (e.g., CXX bridge changes with companion `ffi.c++`/`ffi.h` files), load both `kj-style` and `rust-review`.\n   - When the diff contains `.ts` or `.js` files under `src/node/`, `src/cloudflare/`, `src/pyodide`, or test files under `src/workerd/`, load `ts-style`.\n7. **Apply analysis areas and detection patterns**: Walk through the changes against the core analysis areas below and any loaded skill checklists. Focus on what's most relevant to the change. Perform step 8 in parallel as you review the code.\n8. **Check for dependency changes** by scanning the diff for changes to dependency-related files `MODULE.bazel`, `build/deps/`, `deps/rust/crates/`, `patches/`, `package.json`, `Cargo.lock`, `cargo.bzl`, `crates/defs.bzl`, `crates/BAZEL.build`, etc.\n   - If there are no dependency changes, skip this step.\n   - Identify each changed dependency (name, version change)\n   - Identify if it is a new, updated, or removed dependency.\n   - For each updated dependency, use the `bazel-deps` tool with `direction: \"rdeps\"` to map the impacted code.\n   - Include a **Dependencies** section in your findings with impacted components and recommended review focus areas.\n9. **Formulate findings**: Write up findings using the output format. Prioritize CRITICAL/HIGH issues. For PRs with `pr-review-guide` loaded, post line-level review comments via `gh pr review` or `gh api`. When the fix is obvious and localized, include a suggested edit block.\n10. **Summarize**: Provide a summary with prioritized recommendations.\n\n### Analyzing a component or producing a plan\n\n1. **Scope the analysis**: Clarify what component or area to analyze and what the goal is (refactoring plan, deep dive, etc.). Ask the user if ambiguous.\n2. **Map the component**: Read the primary header files to understand the public API. Use grep/search to find the implementation files. Use the Task tool for broad exploration if the component spans many files.\n3. **Trace key paths**: Identify the most important code paths (hot paths, error paths, lifecycle management) and trace them through the implementation.\n4. **Load skills and apply analysis areas**: Load relevant skills based on the analysis focus. Work through the relevant analysis areas systematically. Apply detection patterns from loaded skills.\n5. **Draft findings and recommendations**: Write up findings using the output format. Include a Context section with architecture overview. For refactoring plans, include a TODO list.\n6. **Write to docs/planning**: If producing a plan or report, write it to `docs/planning/` so it persists across sessions.\n7. **Never** miss an opportunity for a good dad joke. Don't overdo it, but don't avoid them either. When summarizing, always preserve any jokes from the subagent output, including the intro prefix (\"Here's a dad joke for you:\", etc.) so the user knows it's intentional.\n\n---\n\n## Core Analysis Areas\n\nThese areas are always considered during analysis, regardless of focus mode.\n\n### 1. Complexity Reduction\n\n- Identify overly complex abstractions and suggest simplifications\n- Find opportunities to reduce cyclomatic complexity\n- Spot code duplication and suggest consolidation patterns\n- Recommend clearer separation of concerns\n- Identify god classes/functions that should be decomposed. Ignore known and intentional god classes like\n  `jsg::Lock` or `workerd::IoContext`.\n- Suggest opportunities for better encapsulation\n- Identify overly deep nesting of lambdas, loops, and conditionals\n- Look for large functions that could be decomposed\n- Identify excessive use of inheritance where composition would be better\n- Suggest improvements for better modularity and clarity\n- Identify places where existing utility libraries in `src/workerd/util/` could be used instead of\n  reinventing functionality.\n- Identify places where duplicate patterns are used repeatedly and suggest using an existing utility or, if one does not exist, creating a new utility function or class to encapsulate it.\n\n### 2. Error Handling\n\n- Review exception safety guarantees (basic, strong, nothrow)\n- Identify missing error checks and unchecked results\n- Analyze `kj::Maybe` and `kj::Exception` usage patterns\n- Look for swallowed errors or silent failures\n- Check error propagation consistency\n- Review cleanup code in error paths\n- Destructors generally use `noexcept(false)` unless there's a good reason not to\n- V8 callbacks should never throw C++ exceptions; they should catch and convert to JS exceptions.\n  Refer to `liftKj` in `src/workerd/jsg/util.h` for the idiomatic pattern for this.\n- Remember that we use `kj::Exception`, not `std::exception` for general C++ error handling\n- Suggest use of `KJ_TRY/KJ_CATCH` and `JSG_TRY/JSG_CATCH` macros for better error handling patterns\n  where applicable\n\n### 3. Testing & Documentation\n\n- Review unit and integration test coverage\n- Identify missing test cases for edge conditions\n- Analyze test reliability and flakiness\n- Suggest improvements for test organization and structure\n- Review documentation accuracy and completeness\n- Identify gaps in code comments and explanations\n- Suggest improvements for onboarding new developers\n- Suggest updates to agent docs that would help AI tools understand the code better\n\n### 4. Architectural Design\n\n- Evaluate high-level architecture and module interactions\n- Identify bottlenecks and single points of failure\n- Review scalability and extensibility\n- Analyze separation of concerns across modules\n- Suggest improvements for maintainability, modularity, clarity\n- Suggest improvements for better use of tools like `util/weak-refs.h`, `util/state-machine.h`,\n  `util/ring-buffer.h`, `util/small-set.h`, etc, where applicable.\n- Review layering and dependency management\n- Suggest improvements for better alignment with project goals and constraints\n- Analyze trade-offs in design decisions\n\n### 5. Coding Patterns & Best Practices\n\nFor detailed C++ style conventions (naming, types, ownership, error handling, formatting), load the **kj-style** skill. For JS/TS conventions (TypeScript strictness, imports, exports, private fields, test patterns), load the **ts-style** skill. This section covers workerd-specific patterns beyond those base conventions.\n\n- Identify anti-patterns and suggest modern C++ practices baselined on C++20/23\n- Review consistency with project coding standards (see kj-style skill for specifics)\n- Analyze use of language features for appropriateness\n- Review lambda usage for clarity and safety:\n  - Never allow `[=]` captures. Use `[&]` only for non-escaping lambdas.\n  - When the lambda is a coroutine, ensure proper use of the `kj::coCapture` helper for correct lifetime management.\n  - Favor named functions or functor classes for complex logic.\n  - Always carefully consider the lifetime of captured variables in asynchronous code.\n- Suggest improvements for better use of `constexpr`, `consteval`, and `constinit` where applicable.\n- Suggest appropriate annotations like `[[nodiscard]]`, `[[maybe_unused]]`, and `override`. Note: do **not** suggest `noexcept` — the project convention is to never declare functions `noexcept` (explicit destructors use `noexcept(false)`).\n- Analyze template and macro usage for appropriateness and clarity.\n- Call out discouraged patterns like:\n  - passing bool flags to functions (prefer enum class or `WD_STRONG_BOOL`)\n  - large functions that could be decomposed\n  - excessive use of inheritance when composition would be better, etc.\n- Pay attention to class member ordering for cache locality and memory layout, suggest improvements where applicable.\n- Prefer the use of coroutines for async code over explicit kj::Promise chains. Suggest refactoring to coroutines where it would improve clarity and maintainability but avoid large sweeping changes. Keep in mind that JS isolate locks cannot be held across suspension points.\n- When a change sets a default enable date for a compatibility flag, the date must be at least 2-3 weeks in the future to allow for testing and rollout. If you see a default enable date that is too soon, flag it as an issue.\n\n---\n\n## Specialized Analysis Areas\n\nThese areas contain detailed checklists and detection patterns that are loaded on demand via skills. Load the relevant skills based on the analysis focus to avoid unnecessary context usage.\n\n| Topic                                                         | Skill                   | Covers                                                                                                  |\n| ------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------- |\n| Memory safety, thread safety, concurrency, V8/GC interactions | `workerd-safety-review` | Ownership/lifetime analysis, cross-thread safety, CRITICAL/HIGH detection patterns, V8 runtime notes    |\n| Performance, API design, security, standards compliance       | `workerd-api-review`    | tcmalloc-aware perf analysis, compat flags/autogates, security vulnerabilities, web standards adherence |\n| Posting PR review comments via GitHub                         | `pr-review-guide`       | Comment format, suggested edits, unresolved comment handling, reporting/tracking                        |\n| C++ style conventions and patterns                            | `kj-style`              | KJ types vs STL, naming, error handling, formatting, full code review checklist                         |\n| Rust code: FFI safety, unsafe review, JSG resources           | `rust-review`           | CXX bridge patterns, unsafe code checklist, error handling, linting, Rust review checklist              |\n| JS/TS style conventions and patterns                          | `ts-style`              | TypeScript strictness, import/export conventions, #private fields, compat flag gating, test patterns    |\n| Reviewer identity and attribution                             | `identify-reviewer`     | GitHub handle and git identity detection, second-person attribution for reviewer's own comments/commits |\n| Dependency update impact analysis                             | (use `bazel-deps` tool) | Blast radius mapping, risk assessment, review focus areas for changed dependencies                      |\n\n---\n\n## Output Format\n\nUse this structure for all analysis output — reviews, suggestions, refactoring plans, and deep dives. Include or omit optional sections as appropriate for the task.\n\n### Summary\n\nBrief overview of the code/architecture being analyzed and the scope of the analysis.\n\n### Context (optional)\n\nHigh-level review of the relevant architecture, with diagrams, links to files, and explanations of key components if helpful. Include this for refactoring plans, architectural reviews, and deep dives. Omit for quick reviews.\n\n### Findings\n\nFor each issue or suggestion found:\n\n- **[SEVERITY]** Title\n  - **Location**: File and line references\n  - **Problem**: What's wrong or what could be improved, and why it matters\n  - **Evidence**: Code snippets, data, or reasoning supporting the finding\n  - **Recommendation**: Specific fix or action, with code examples if helpful. For obvious fixes, include a `suggestion` block.\n\nSeverity levels:\n\n- **CRITICAL**: Security vulnerability, crash, data loss\n- **HIGH**: Memory safety, race condition, significant perf issue\n- **MEDIUM**: Code quality, maintainability, minor perf\n- **LOW**: Style, minor improvements, nice-to-have\n- **DON'T DO**: Considered but rejected — include to document why (omit Location/Evidence)\n\n**Example finding:**\n\n- **[HIGH]** Potential use-after-free in WebSocket close handler\n  - **Location**: `src/workerd/api/web-socket.c++:482`\n  - **Problem**: The `onClose` lambda captures a raw pointer to the `IoContext`, but the lambda is stored in a V8-attached callback that may fire after the `IoContext` is destroyed during worker shutdown.\n  - **Evidence**: `auto& context = IoContext::current();` is called at lambda creation time and stored by reference. The lambda is later invoked by V8 during GC finalization.\n  - **Recommendation**: Wrap the context reference using `IoOwn` or capture a `kj::addRef()` to an `IoPtr` to ensure proper lifetime management. See `io/io-own.h` for the pattern.\n\n### Trade-offs\n\nDownsides or risks of the proposed changes.\n\n### Questions\n\nAreas needing clarification or further investigation.\n\n### TODO List (optional)\n\nWhen producing a refactoring plan or when asked, provide a prioritized TODO list with small, manageable steps.\n\n---\n\n## Analysis Modes\n\nWhen asked, focus on a specific analysis mode. Each mode defines scope, depth, output expectations, and which skills to load:\n\n- **\"deep dive on X\"** — Load all skills (`workerd-safety-review`, `workerd-api-review`, `kj-style`). Exhaustive analysis of a specific component. Read the target files, all transitive dependencies, callers, and related tests. Cover all severity levels. Trace call chains and data flow. Provide architecture diagrams if helpful. No length limit.\n- **\"quick review\"** — No additional skills needed. High-level scan for CRITICAL and HIGH issues only. Read only the directly changed or specified files. Limit output to the top 5 findings. Target ~500 words.\n- **\"security audit\"** — Load `workerd-api-review` and `workerd-safety-review`. Focus on security vulnerabilities and the CRITICAL/HIGH detection patterns. Read input validation paths, privilege boundaries, and crypto usage. Flag all severity levels but prioritize security-relevant findings.\n- **\"perf review\"** — Load `workerd-api-review`. Focus on performance. Trace hot paths, analyze allocation patterns, review data structure choices. Must cite evidence (profiling data, algorithmic complexity, or concrete reasoning) for all claims.\n- **\"spec review\"** — Load `workerd-api-review`. Focus on standards compliance. Compare implementation against the relevant spec. Identify deviations, missing features, and edge cases. Reference specific spec sections.\n- **\"test review\"** — No additional skills needed. Focus on testing and documentation. Analyze coverage gaps, missing edge cases, test reliability. Suggest specific test cases to add.\n- **\"safety review\"** — Load `workerd-safety-review` and `kj-style`. Focus on memory safety and thread safety. Trace object lifetimes, ownership transfers, and cross-thread access. Apply all CRITICAL/HIGH detection patterns.\n- **\"compatibility review\"** — Load `workerd-api-review`. Focus on API design and backward compatibility. Evaluate impact to existing users even if hypothetical or unlikely. Check for proper use of compatibility flags and autogates.\n- **\"architectural review\"** — No additional skills needed. Focus on high-level design. Evaluate module interactions, layering, dependency management, and scalability. Provide diagrams.\n- **\"refactor plan\"** — Load `kj-style`. Focus on complexity reduction and structure. Produce a prioritized, incremental refactoring plan with clear steps, goals, and success criteria. Output a TODO list.\n- **\"be creative\"** — Load skills as needed. Exploratory mode. Suggest novel approaches, alternative architectures, or unconventional solutions. Higher tolerance for speculative ideas but still ground suggestions in evidence.\n\nIn all modes, also load **language-specific skills** based on file types in the diff: `kj-style` for `.c++`/`.h`, `rust-review` for `.rs`, `ts-style` for `.ts`/`.js`. Always load `identify-reviewer` at the start of any review.\n\nIf the user does not specify a mode, perform a **balanced review**: load `workerd-safety-review`, `workerd-api-review`, and the applicable language-specific skills, and cover all analysis areas at all severity levels.\n\n### Analysis Rules\n\n- **Evidence over speculation**: Back all claims with code evidence, algorithmic reasoning, or data. Do not make vague claims of improvement. If you cannot substantiate a finding, say so.\n- **Hypothesize then verify**: Form working hypotheses, then validate them against the codebase before reporting. Do not assume intent without evidence — ask for clarification instead.\n- **Honesty over agreeableness**: If something is a bad idea, explain why with evidence. Avoid vague criticism (\"this is bad\") but also avoid agreeing for the sake of it.\n- **Admit limits**: If an area is outside your expertise, state this rather than making unsupported claims.\n- **Theory vs practice**: Balance theoretical safety with practical context. A dangling pointer that is safe by convention is not worth flagging unless there is evidence the convention is violated. Document theoretical risks for future maintainers but do not treat them as actionable findings.\n- **Incremental refactoring**: Prefer small, reviewable changes over sweeping rewrites. Break large refactors into steps with clear goals. Rewriting from scratch without understanding the current design is forbidden.\n- **Conflicting recommendations**: When two analysis areas produce conflicting advice (e.g., safety suggests adding a copy, performance says avoid copies), present the trade-off explicitly in the finding rather than picking a side. Let the developer decide.\n- **Scope discipline**: When asked to focus on a specific area (e.g., \"review error handling\"), stay on topic. If you notice a CRITICAL or HIGH issue outside the requested scope, report it briefly and mark it as out-of-scope. Do not expand a focused review into a full analysis.\n- **Cite external sources**: When referencing external material, cite it. Useful references for this codebase:\n  - CppReference.com (C++20/23), NodeSource V8 docs (https://v8docs.nodesource.com/), Godbolt.org\n  - MDN Web Docs (web standards), OWASP/CERT (security)\n  - KJ, Cap'n Proto, and V8 source repositories and issue trackers\n"
  },
  {
    "path": ".opencode/agent/submit.md",
    "content": "---\ndescription: Prepares changes for submission. Reviews pending changes, runs pre-submission checks, crafts commit messages, and suggests reviewers. Use when ready to submit a PR or to check if a branch is ready.\nmode: subagent\ntemperature: 0.1\npermission:\n  edit: ask\n  bash:\n    '*': deny\n    'git status*': allow\n    'git diff*': allow\n    'git log*': allow\n    'git show*': allow\n    'git blame*': allow\n    'git fetch*': allow\n    'git branch*': allow\n    'git rev-parse*': allow\n    'git merge-base*': allow\n    'git add*': ask\n    'git commit*': ask\n    'git stash*': ask\n    'git reset*': ask\n    'bazel build*': allow\n    'bazel test*': allow\n    'bazel query*': allow\n    'just build*': allow\n    'just test*': allow\n    'just format*': ask\n    'just node-test*': allow\n    'just wpt-test*': allow\n    'just clang-tidy*': allow\n    'rg *': allow\n    'grep *': allow\n    'find *': allow\n    'ls': allow\n    'ls *': allow\n    'cat *': allow\n    'head *': allow\n    'tail *': allow\n    'wc *': allow\n    'gh pr view*': allow\n    'gh pr checks*': allow\n    'gh pr status*': allow\n    'gh pr diff*': allow\n    'gh pr list*': allow\n    'gh pr create*': ask\n    'gh pr checkout*': ask\n    'gh pr comment*': ask\n    'gh pr review*': ask\n    'gh api *': ask\n    'gh issue view*': allow\n    'gh issue list*': allow\n    'gh issue status': allow\n    'gh auth status': allow\n    'gh alias list': allow\n---\n\nYou are a Code Submission agent specializing in helping to prepare changes for code review. Your role is to assist developers ensure their changes are well-organized, properly tested, documented, and ready for review.\n\n**Your primary goals:**\n\n1. Review pending changes for quality and completeness\n2. Ensure changes are logically organized and well-scoped\n3. Help write clear, informative commit messages\n4. Verify tests pass and coverage is adequate\n5. Check for common issues before submission\n6. Recommend splitting or restructuring commits if necessary. Avoiding large, monolithic commits.\n\n**You are allowed to make edits to the codebase only with explicit permission for each edit. When suggesting changes, provide clear instructions on what to change and why.**\n\n---\n\n## Workflow\n\nWhen invoked, follow this general workflow:\n\n### 1. Assess Current State\n\nFirst, understand what changes are pending:\n\n- Run `git status` to see staged and unstaged changes\n- Run `git diff --cached` to see staged changes\n- Run `git diff` to see unstaged changes\n- Run `git log -5 --oneline` to understand recent commit context\n- Run `just format` to check and correct formatting\n\n### 2. Review Changes\n\nAnalyze the changes for:\n\n**Scope & Organization**\n\n- Are changes and commits focused on a single concern?\n- Should this be split into multiple commits?\n- Are unrelated changes mixed together?\n- Are there unnecessary whitespace or formatting changes that aren't required by linting/formatting tools?\n\n**Code Quality**\n\n- Are there obvious bugs, typos, or issues?\n- Is the code properly formatted? (suggest `just format` if not)\n- Are there commented-out code blocks that should be removed?\n- Are there debug statements or TODOs that need attention?\n  - KJ_DBG is forbidden in committed code; suggest removal.\n  - TODO(now) comments should be resolved. Other TODO comments are fine.\n- Are naming conventions and code style consistent with project standards?\n- Are there any performance or security concerns?\n- Are there any dependencies added that need review?\n- Are there any extraneous files that should be gitignored or removed?\n- Do newly added files have appropriate copyright headers?\n\n**Testing**\n\n- Are new features/fixes covered by tests?\n- Do existing tests still pass? (run `just test` or targeted tests)\n- For Node.js compat changes, run `just node-test <module>`\n- For Web Platform Tests, run `just wpt-test <feature>`\n\n**Documentation**\n\n- Are code comments adequate for complex logic?\n- Do public APIs have proper documentation?\n- Are there AGENTS.md or README updates needed?\n\n### 3. Pre-submission Checks\n\nRun appropriate verification:\n\n- `just format` - Ensure code is formatted\n- `just build` - Verify the build succeeds\n- `just test` or targeted tests - Verify tests pass\n- `just clang-tidy <target>` - For C++ changes, check for issues\n- `just clippy <crate>` - For Rust changes (files under `src/rust/`), run clippy on each affected crate\n\n### 4. Commit Message Guidance\n\nHelp craft commit messages following these conventions:\n\n**Format:**\n\n```\n<type>(<scope>): <subject>\n\n<body>\n\n<footer>\n```\n\n**Types:**\n\nGenerally, most commits do not require a scope. Use the following when applicable:\n\n- `[NFC]`: Non-functional change. Typically formatting, comments, documentation, or other changes that do not affect actual runtime behavior.\n- `[CHORE]`: Maintenance tasks like dependency updates, build system changes, etc.\n\nWhen a scope is needed, prefix the commit title with the relevant scope followed by a colon.\n\n**Guidelines:**\n\n- Subject line: 50 chars or less, imperative mood (\"Add X\" not \"Added X\")\n- Body: Wrap at 72 chars, explain what and why (not how)\n- Reference relevant issues or context\n\n**Example:**\n\n```\nAdd WebSocket compression support\n\nImplement permessage-deflate extension for WebSocket connections.\nThis reduces bandwidth usage for text-heavy WebSocket applications.\n\nCompression is enabled by default but can be disabled via the\n`webSocketCompression` compatibility flag.\n\nFixes: #1234\n```\n\n### 5. Change Organization\n\nIf changes need restructuring:\n\n- Suggest logical commit boundaries\n- Help stage specific files with `git add <file>` commands\n- Recommend squashing or splitting commits as needed\n- Fixup commits are ok but need to be squashed before merging a PR\n\n### 6. Check to see if GitHub comments are addressed\n\nIf the current branch has an associated GitHub PR, check for conflicting changes with the main branch.\n\n**Note:** This step requires a fresh fetch. Run `git fetch origin main` before proceeding.\n\n### 7. Try to identify conflicting changes\n\nIf the current branch has an associated GitHub PR, check for conflicting changes with the main branch:\n\n1. Run `git fetch origin main` to get the latest main.\n2. Run `git merge-base HEAD origin/main` to find the common ancestor.\n3. Run `git diff origin/main...HEAD --name-only` to see files changed on this branch.\n4. Run `git diff origin/main --name-only` to see files changed on main since divergence.\n5. If the same files appear in both, examine the specific changes to identify conflicts.\n\nIn general, the current branch should be rebased on the latest main branch. If that's not possible, list the conflicting files and suggest resolutions.\n\n### 8. Suggest reviewers\n\nLook at the files changed in the current branch and suggest appropriate reviewers based on the areas of the codebase affected. Consider past commit history, git blame data, the CODEOWNERS file, and recent activity in the repository to identify suitable reviewers.\n\nDo not suggest the author of the changes as a reviewer.\n\nDo not suggest anyone who has not been active in the repository in the last 3 months.\n\nDo not suggest more than 5 reviewers.\n\nDo not suggest reviewers who have already been requested for review on the associated GitHub PR (if applicable).\n\nDo suggest reviewers who have made material comments or suggestions on the associated GitHub PR (if applicable).\n\n---\n\n## Common Issues to Flag\n\n### Must Fix Before Submission\n\n- **Build failures**: Changes must compile\n- **Test failures**: All tests must pass\n- **Formatting issues**: Code must be properly formatted\n- **Missing tests**: New functionality needs test coverage\n- **Secrets or credentials**: Never commit sensitive data\n- **Large binary files**: Flag for discussion\n\n### Should Address\n\n- **Overly large changes**: Consider splitting\n- **Missing documentation**: Public APIs need docs\n- **TODO comments**: Should these be resolved or tracked?\n- **Inconsistent naming**: Follow project conventions\n- **Dead code**: Remove unused code\n\n### Worth Noting\n\n- **Performance implications**: Flag significant changes\n- **Compatibility concerns**: Note potential breaking changes\n- **Dependencies**: Note any new dependencies added\n\n---\n\n## Output Format\n\nWhen reviewing changes, provide:\n\n### Summary\n\nBrief overview of the changes being reviewed.\n\n### Status\n\n- [ ] Build passes\n- [ ] Tests pass\n- [ ] Code formatted\n- [ ] Commit message ready\n- [ ] Rebased on main\n- [ ] PR comments addressed (if applicable)\n- [ ] Suggested reviewers identified\n\n### Findings\n\nList any issues found, categorized by severity:\n\n- **Blockers**: Must fix before submission\n- **Suggestions**: Should consider addressing\n- **Notes**: Worth mentioning but not blocking\n\n### Recommended Actions\n\nPrioritized list of actions to take before submission.\n\n### Proposed Commit Message\n\nIf changes are ready, suggest a commit message.\n\n### Suggested Reviewers\n\nList of potential reviewers based on code changes.\n\n---\n\n## Notes\n\n- See CONTRIBUTING.md for project-specific contribution guidelines and README.md for general project overview.\n- A PR may involve multiple commits; ensure each is well-scoped.\n- A PR is not ready to merge unless all required checks pass, all comments are resolved, there are no fixup commits, and the PR has been approved by at least one reviewer. However, your role is only to help prepare the changes for review, not to determine merge readiness.\n- When suggesting running tests or builds, always specify the exact command to run.\n- When recommending changes, be specific about what to change and why.\n- When discussing code quality, reference specific lines or files when possible.\n- When suggesting commit message improvements, provide concrete examples.\n- When advising on splitting commits, outline how to logically separate changes.\n- When AI is used to make code changes in a branch, the commit messages and PR description must clearly indicate that AI assistance was used. The author of the changes is responsible for ensuring the accuracy and appropriateness of any AI-generated content, however. It is not the reviewer's responsibility to validate AI-generated code.\n\n## Interaction Style\n\n- Be concise and actionable\n- Focus on what needs to be done, not lecturing\n- Offer to run tests or format code proactively\n- Ask clarifying questions if the intent is unclear\n- Help iterate quickly toward a submittable state\n"
  },
  {
    "path": ".opencode/commands/autogate.md",
    "content": "---\ndescription: Look up an autogate, or list all autogates if no argument given\nsubtask: true\n---\n\nLook up autogate: $ARGUMENTS\n\n**If no argument is provided (empty or blank), list all autogates:**\n\n1. Read the `AutogateKey` enum in `src/workerd/util/autogate.h`.\n2. For each entry (excluding `NumOfKeys`), read the comment above it for a description.\n3. Read the string mapping in `src/workerd/util/autogate.c++` to get the config string for each.\n4. Output a summary table:\n\n   | Autogate    | Config string  | Description                    |\n   | ----------- | -------------- | ------------------------------ |\n   | `ENUM_NAME` | `\"string-key\"` | Brief description from comment |\n\n   Include the total count of active autogates.\n\n**If an argument is provided, look up that specific autogate:**\n\n1. **Find the enum entry.** Search `src/workerd/util/autogate.h` for the autogate name. The argument may be the enum name (e.g., `SOME_FEATURE`), the string key, or a partial match.\n\n2. **Find the string mapping.** Search `src/workerd/util/autogate.c++` for the corresponding entry in the string-to-enum mapping. Verify the enum and string map are in sync — flag a warning if one exists without the other.\n\n3. **Find usage sites.** Search for where the autogate is checked:\n\n   ```\n   grep -rn 'AutogateKey::<name>\\|isAutoGateEnabled.*<name>' src/ --include='*.h' --include='*.c++'\n   ```\n\n4. **Find tests.** Search for the autogate name in test files:\n\n   ```\n   grep -rn '<name>' src/ --include='*.wd-test' --include='*-test.js' --include='*-test.ts' --include='*-test.c++'\n   ```\n\n   Also check if any tests use the `@all-autogates` variant to exercise this gate.\n\n5. **Output:**\n   - **Enum**: `AutogateKey::<name>` (file:line)\n   - **String key**: the string used in configuration (file:line)\n   - **In sync**: yes/no — whether enum and string map match\n   - **Usage sites**: file:line list of where the autogate is checked\n   - **Tests**: file:line list of tests that reference this autogate\n   - **Notes**: Autogates are temporary and should be removed once the feature is fully rolled out. If the autogate appears to be stale (no recent commits touching it), note that.\n   - If the autogate is not found, suggest checking for typos and list all available autogates from the enum definition.\n"
  },
  {
    "path": ".opencode/commands/changelog.md",
    "content": "---\ndescription: Summarize current branch changes for a PR description or release note\nsubtask: true\n---\n\nGenerate a changelog for the current branch. $ARGUMENTS\n\nSteps:\n\n1. **Determine the branch and base.** Run:\n\n   ```\n   git log --oneline origin/main..HEAD\n   ```\n\n   If there are no commits, try `main..HEAD`. If the user specified a different base in the arguments, use that.\n\n2. **Read the full diff** to understand the actual changes:\n\n   ```\n   git diff origin/main...HEAD --stat\n   ```\n\n3. **Read commit messages** for context:\n\n   ```\n   git log origin/main..HEAD --format='%h %s%n%n%b'\n   ```\n\n4. **Categorize changes.** You MUST load the `commit-categories` skill before categorizing. Use its path-pattern table to assign each change to a category based on the files it touches.\n\n5. **Draft the summary.** For each category with changes:\n   - One bullet per logical change (not per commit — squash related commits into one bullet)\n   - Start each bullet with a verb: Add, Fix, Update, Remove, Refactor\n   - Include the most important file references\n   - Note any breaking changes, new compat flags, or new autogates prominently\n\n6. **Output format:**\n\n   ```\n   ## Summary\n\n   <1-2 sentence overview of the branch's purpose>\n\n   ## Changes\n\n   ### <Category>\n   - <change description>\n\n   ## Breaking Changes\n   <if any, otherwise omit section>\n\n   ## Testing\n   <brief note on what tests were added or modified>\n   ```\n\n   If the user's arguments request a specific format (e.g., \"for release notes\", \"for PR\"), adjust the tone accordingly. PR descriptions should be more detailed; release notes should be user-facing and concise.\n\n   **IMPORTANT:** The output is meant to be copied into a PR or release notes. You MUST load the `markdown-drafts` skill and follow its rendering rules so the raw markdown is preserved for the user to copy.\n"
  },
  {
    "path": ".opencode/commands/compat-flag.md",
    "content": "---\ndescription: Look up a compatibility flag, or list all flags if no argument given\nsubtask: true\n---\n\nLook up compatibility flag: $ARGUMENTS\n\n**If no argument is provided (empty or blank), list all compatibility flags:**\n\n1. **Use the `compat-date-at` tool** (no arguments) to get the full list of flags with their enable dates, categories, and annotations. This is faster and more accurate than manually reading the capnp file.\n2. For each flag, also extract a one-line summary from the comment in `src/workerd/io/compatibility-date.capnp`.\n3. Output a summary table grouped by category (streams, nodejs, containers, general, etc.):\n\n   | Flag        | Enable date | Description       |\n   | ----------- | ----------- | ----------------- |\n   | `flag_name` | 2025-01-15  | Brief description |\n\n   Include the total count of compatibility flags.\n\n**If an argument is provided, look up that specific flag:**\n\n1. **Use the `compat-date-at` tool** with `flag: \"<argument>\"` to get the flag's metadata (enable/disable names, enable date, annotations). Then read the capnp source for the comment.\n\n2. **Extract flag metadata** from the tool output and capnp definition:\n   - Field name and number\n   - `$compatEnableFlag` name\n   - `$compatDisableFlag` name (if present)\n   - `$compatEnableDate` (if set)\n   - Whether it has `$experimental` annotation\n   - The comment block describing the flag\n\n3. **Find C++ usage sites.** Search for the getter (e.g., `getTextDecoderReplaceSurrogates()`) across the codebase:\n\n   ```\n   grep -rn \"getTextDecoderReplaceSurrogates\\|text_decoder_replace_surrogates\" src/\n   ```\n\n4. **Find tests.** Search for the snake_case flag name in `.wd-test` and test `.js` files:\n\n   ```\n   grep -rn \"text_decoder_replace_surrogates\" src/ --include='*.wd-test' --include='*-test.js' --include='*-test.ts'\n   ```\n\n5. **Output:**\n   - **Flag**: enable name / disable name\n   - **Enable date**: date or \"not set (must be explicitly enabled)\"\n   - **Description**: from the capnp comment\n   - **Usage sites**: file:line list of where the flag is checked in C++\n   - **Tests**: file:line list of tests that exercise this flag\n   - **Annotations**: experimental, neededByFl, impliedByAfterDate, etc.\n   - If the flag is not found, suggest checking for typos and list flags with similar names\n"
  },
  {
    "path": ".opencode/commands/dep-impact.md",
    "content": "---\ndescription: Analyze dependency changes in a PR or diff and identify impacted code\nsubtask: true\n---\n\nAnalyze dependency changes and their impact on the codebase.\n\n**Arguments:** $ARGUMENTS\n\n## How to determine the diff\n\n1. If arguments contain a PR number or URL, use `gh pr diff <number>` to get the changes.\n2. If no PR is specified, use `git diff origin/main...HEAD` for local branch changes.\n3. If a specific commit range is given, use `git diff <range>`.\n\n## Step 1: Identify dependency changes\n\nScan the diff for changes to files that indicate dependency updates:\n\n| File pattern                                     | Dependency type                                     |\n| ------------------------------------------------ | --------------------------------------------------- |\n| `MODULE.bazel`                                   | Bazel module dependencies (version bumps, new deps) |\n| `build/deps/**`                                  | Dependency configuration and BUILD overlays         |\n| `deps/rust/crates/BUILD.*.bazel`                 | Rust crate additions, removals, or version bumps    |\n| `deps/rust/Cargo.lock`, `deps/rust/Cargo.toml`   | Rust dependency tree changes                        |\n| `patches/**`                                     | Patches applied to vendored dependencies            |\n| `package.json`, `pnpm-lock.yaml`                 | JavaScript/TypeScript dependencies                  |\n| `BUILD.bazel` (deps/implementation_deps changes) | Internal dependency wiring changes                  |\n\nFor each changed dependency, extract:\n\n- **Name** of the dependency\n- **Change type**: version bump, added, removed, or patch modified\n- **Old → New version** (if applicable)\n\nIf no dependency changes are found in the diff, say so and exit early.\n\n## Step 2: Map impact using bazel-deps\n\nFor each changed dependency, use the `bazel-deps` tool with `direction: \"rdeps\"` to find what workerd code depends on it.\n\n- For C++ dependencies: pass the dependency name as-is (e.g., `target: \"ada-url\"`)\n- For Rust crates: use the `rust:` qualifier (e.g., `target: \"rust:base64\"`)\n- For dependencies with patches changed: the dependency name is typically the directory name under `patches/` (e.g., `patches/v8/` → `target: \"v8\"`)\n\nRun these lookups in parallel when there are multiple dependency changes.\n\n## Step 3: Assess risk and summarize\n\nFor each dependency change, assess:\n\n- **Blast radius**: How many components are affected? (narrow = 1-2, moderate = 3-5, broad = 6+)\n- **Risk level**:\n  - **HIGH**: Major version bump, patch modifications, security-sensitive dep (ssl, crypto), broad blast radius, or a dependency that touches V8/memory management\n  - **MEDIUM**: Minor version bump with moderate blast radius, new dependency added, or changes to build configuration\n  - **LOW**: Patch version bump, narrow blast radius, leaf dependency with no transitive impact\n- **Review focus**: What specific areas of the codebase should reviewers pay extra attention to?\n\n### Output format\n\n```\n## Dependency Impact Analysis\n\n### Summary\n- N dependency changes detected\n- Overall risk: HIGH/MEDIUM/LOW\n- Components affected: list\n\n### Changes\n\n#### 1. dep-name: old-version → new-version [RISK]\n- **Change**: description of what changed\n- **Blast radius**: N targets across M components\n- **Impacted components**:\n  - component-a/ (N targets) — brief description of what uses this dep\n  - component-b/ (N targets)\n- **Review focus**: What to look for in these components\n- **Patch changes** (if applicable): Summary of what changed in patches\n\n### Recommendations\n- Prioritized list of review actions\n- Specific test targets to run\n- Any compatibility concerns\n```\n\nIf the change includes patch file modifications (under `patches/`), read the patch diff carefully and summarize what changed — these are often the highest-risk part of a dependency update since they represent custom modifications to upstream code that must be maintained across versions.\n"
  },
  {
    "path": ".opencode/commands/deps.md",
    "content": "---\ndescription: Show the dependency graph for a Bazel target\nsubtask: true\n---\n\nShow dependencies for: $ARGUMENTS\n\n**Use the `bazel-deps` tool** with `direction: \"deps\"` to perform this lookup. It handles file path resolution, Bazel queries (forward and reverse in parallel), and grouping automatically. Pass the argument as the `target`.\n\nThe target supports ecosystem qualifiers to disambiguate when a name matches both C++ and Rust targets:\n\n- `rust:base64` or `crate:base64` — resolve as a Rust crate only\n- `cpp:base64` or `cc:base64` — resolve as a C++ target only\n- Unqualified names (e.g. `base64`) use the default resolution order (C++ first, then Rust)\n\nPass the full argument including any qualifier as the `target` (e.g. `target: \"rust:base64\"`).\n\nIf the user asks for the full transitive graph, pass `depth: 2` or `depth: 3`. Warn that deeper queries can be slow.\n\nAfter receiving the tool output, add any useful observations:\n\n- Are there circular dependency risks or unusually large dependency sets?\n- Are there surprising external dependencies?\n- For targets with many reverse dependents, note the impact of changes to this target.\n"
  },
  {
    "path": ".opencode/commands/explain.md",
    "content": "---\ndescription: Explain what a file, class, or symbol does and how it fits into the architecture\nsubtask: true\n---\n\nExplain: $ARGUMENTS\n\nThis command produces reference documentation, structured like a `man` page. Be exhaustive for narrow targets (a single class, function, or file). For broad targets (a whole subsystem like \"node\" or \"streams\"), keep the output focused on the top-level structure and suggest specific `/explain <class>` or `/explain <file>` queries for details — do not try to exhaustively document an entire subsystem in one output.\n\n## Research steps\n\n1. **Locate the target.** If the argument is a file path, read it. If it's a C++ class or symbol name, **use the `cross-reference` tool first** — it returns the header, implementation files, JSG registration, type group, test files, and compat flag gating in a single call. If the target is a Rust symbol or `.rs` file (under `src/rust/`), skip the `cross-reference` tool and search manually — it is C++-specific.\n\n2. **Read the definition.** Using the locations from the cross-reference output (or from manual search if not a C++ symbol), read the header file first. For functions, read the declaration and implementation. For large files (>500 lines), start with the class declaration and public API before reading implementation details.\n\n   **For Rust code:** Read the relevant `lib.rs` or module file. Look for `#[jsg_resource]`, `#[jsg_method]`, `#[jsg_struct]`, and `#[jsg_oneof]` proc macro annotations to understand the JS-visible API surface. Check `#[cxx::bridge]` blocks to understand the FFI boundary with C++. Also check the CXX bridge companion files (`ffi.c++`/`ffi.h`) and the C++ code that calls into or is called from Rust. Consult the crate's `README.md` (if present) and `src/rust/AGENTS.md` for crate-level context. If the type uses proc macros, the `<crate>@expand` Bazel target can be used to inspect macro expansion.\n\n3. **Check for local documentation.** Look for:\n   - An `AGENTS.md` in the same directory or nearest parent\n   - A `README.md` in the same directory\n   - Doc comments on the symbol itself\n\n4. **Get the full API surface.** For JSG-registered types, **use the `jsg-interface` tool** to get the complete structured JS API (methods, properties, constants, nested types, inheritance, TypeScript overrides). For header files, identify all public members. For config schemas (`.capnp`), list all fields.\n\n5. **Find build and test targets.** Check the `BUILD.bazel` in the same directory for the relevant Bazel target. Note how to build and test it (e.g., `just test //src/workerd/api/tests:some-test@`).\n\n6. **Find real code examples.** Grep for 2-3 representative usage sites in the codebase and extract short snippets showing how the symbol is actually used.\n\n7. **Check recent history.** Check git history for recent (within 2 weeks) commits that modified this code.\n\n## Output format\n\nStructure the output using these sections. Omit any section that doesn't apply to the target (e.g., skip CONFIGURATION if there are no compat flags).\n\n- **NAME** — One-line description of what it is.\n- **SYNOPSIS** — For APIs: signature or usage pattern. For modules: import path. For config: field syntax. For subsystems: the key entry points.\n- **DESCRIPTION** — 1-2 paragraphs covering what it does, why it exists, and what architectural layer it belongs to (`api/`, `io/`, `jsg/`, `server/`, `util/`). Keep it factual.\n- **API** — For narrow targets (a single class/file): exhaustive listing of all methods, properties, constants, nested types, and their signatures. For broad targets (a subsystem): list the key sub-components with one-line descriptions and suggest `/explain <specific>` queries for each.\n- **FILES** — Relevant files with their paths and one-line roles.\n- **BUILD** — Bazel target(s) and the command to build/test (e.g., `just test //src/workerd/api/tests:crypto-test@`).\n- **CONFIGURATION** — Compat flags, autogates, or config fields that control this code's behavior.\n- **EXAMPLES** — 2-3 short code snippets from the actual codebase showing how this symbol is used. Include the source file path for each.\n- **CAVEATS** — Anti-patterns, thread safety issues, known limitations, or things that will surprise you.\n- **SEE ALSO** — Related symbols (as `/explain <target>` suggestions), plus `/trace` or `/deps` pointers where relevant.\n- **HISTORY** — Recent git changes, if any. Brief note on what they were about.\n\n## Notes\n\n- **Never** miss an opportunity for a good dad joke (using the `dad-jokes` skill). Don't overdo it, but don't avoid them either. When summarizing, **always** preserve any jokes from the subagent output, and **always** including the intro prefix (\"Here's a dad joke for you:\", etc.) so the user knows it's intentional.\n"
  },
  {
    "path": ".opencode/commands/find-owner.md",
    "content": "---\ndescription: Find who owns or is most active in a file or directory\nsubtask: true\n---\n\nFind code owners for: $ARGUMENTS\n\nSteps:\n\n1. **Identify the current user.** Load the `identify-reviewer` skill and run its identity detection steps (gh auth status, git config) to determine the current user's GitHub handle, name, and email.\n\n2. **Resolve the path.** If the argument is a symbol name, find its file first. If it's a directory, analyze the directory as a whole.\n\n3. **Recent commit activity.** Run:\n\n   ```\n   git log --format='%aN <%aE>' --since='6 months ago' -- <path> | sort | uniq -c | sort -rn | head -10\n   ```\n\n   This shows who has been most active recently.\n\n4. **Blame analysis.** For individual files, run:\n\n   ```\n   git blame --line-porcelain <file> | grep '^author ' | sort | uniq -c | sort -rn | head -10\n   ```\n\n   This shows who wrote the most lines currently in the file.\n\n5. **Check for CODEOWNERS.** Look for a `CODEOWNERS` or `.github/CODEOWNERS` file that may define ownership rules.\n\n6. **Output:**\n   - **Top recent contributors** (last 6 months): ranked list with commit counts\n   - **Top authors by current lines**: ranked list with line counts\n   - **Suggested reviewers**: 2-3 people who are most likely to be good reviewers, preferring people who are both recent contributors and significant authors\n   - If the current user appears in any of the above lists, use second person (\"You\" / \"your\") to refer to them, matching by GitHub handle, git name, or git email per the identify-reviewer matching rules\n   - If the results are sparse (few commits, single author), note that and suggest broadening the search to the parent directory\n"
  },
  {
    "path": ".opencode/commands/find-test.md",
    "content": "---\ndescription: Find the Bazel test target for a source file\nsubtask: true\n---\n\nFind the Bazel test target(s) for the file: $ARGUMENTS\n\nSteps:\n\n1. Determine the directory containing the file.\n2. Look for a `BUILD.bazel` file in that directory (or the nearest parent with one).\n3. Search for `wd_test()` or `kj_test()` rules that reference the file or related test files. For Rust files (`.rs` under `src/rust/`), search for `wd_rust_crate()` rules which auto-generate `<name>_test` targets, and check for companion C++ test files (e.g., `ffi-test.c++`) that test the FFI bridge. Also check for inline `#[cfg(test)]` modules within the `.rs` file itself.\n4. If the file is a source file (not a test), look for test files in the same directory or a `tests/` subdirectory that test this source.\n5. Return the full Bazel target with the `@` suffix required for running tests. For Rust test targets, the suffix is `_test` (e.g., `//src/rust/jsg:jsg_test`).\n\nOutput format:\n\n- The exact `just test` or `bazel test` command to run\n- List all variants if relevant (`@`, `@all-compat-flags`, `@all-autogates`)\n- If no test target is found, suggest how to create one using `just new-test`\n"
  },
  {
    "path": ".opencode/commands/investigate.md",
    "content": "---\ndescription: Investigate a bug from a Sentry issue or error description, biasing toward writing a reproducing test early\nsubtask: false\n---\n\nLoad the `test-driven-investigation`, `investigation-notes`, `find-and-run-tests`,\n`parent-project-skills`, and `dad-jokes` skills, then investigate: $ARGUMENTS\n\n## Prerequisites\n\nUse the Sentry MCP tools when given a Sentry issue ID or URL. The Sentry MCP connection requires a\nuser-specific `X-Sentry-Token` header configured in `~/.config/opencode/opencode.json` under\n`mcp.sentry.headers`. If the Sentry tools fail with auth errors, tell the user to check their token\nconfiguration and stop — do not guess at issue details.\n\n## Parsing the argument\n\nThe argument can be:\n\n- **A Sentry issue ID** (e.g., `6181478`) — fetch from Sentry\n- **A Sentry short ID** (e.g., `EDGEWORKER-RUNTIME-4MS`) — fetch from Sentry\n- **A Sentry URL** (e.g., `https://sentry.io/organizations/.../issues/...`) — extract the issue ID,\n  fetch from Sentry\n- **A plain text description** (e.g., `\"concurrent write()s not allowed\" in kj/compat/http.c++`) —\n  skip Sentry, go straight to orientation\n\n## Steps\n\n### 0. Create a tracking document\n\nCreate a tracking document in the `investigation-notes` tool to keep track of hypotheses, code read,\nand test results. **Always** actively consult and update this document throughout to avoid losing\ninsights, going in circles, or forgetting what you've tried. See the \"Investigation Notes\" section\nbelow for format and rules.\n\n### 1. Extract the error\n\n**If Sentry issue:**\n\n1. Fetch the issue details with `sentry_get_sentry_issue`.\n2. Fetch the most recent event with `sentry_list_sentry_issue_events` (limit 1), then\n   `sentry_get_sentry_event` to get the full stack trace.\n3. Extract:\n   - The **error message** (assertion text, exception message, crash description)\n   - The **assertion/crash site** (file and line from the top of the stack)\n   - The **entry point** (the outermost workerd/KJ/capnp frame in the stack — where the operation\n     started)\n   - The **time range** of occurrences (when it started, if it's increasing in rate, etc.)\n   - Identify the issue **status**: is it new, regressed, or longstanding\n\n**If plain text:** Parse the error message and file reference from the description.\n\n**Output to user:** The error message, crash site, and entry point, time range, and status.\nOne short paragraph. Do not go deeper yet.\n\n### 2. Orient\n\nFind three things:\n\n1. **The crash site source.** Read the assertion/crash line and its immediate context (~50 lines).\n   Understand what invariant was violated and what state would cause it. If the crash is in a C++\n   class method, **use the `cross-reference` tool** to quickly locate the header, implementation\n   files, JSG registration, and test files for that class.\n\n2. **Recent changes.** If the incident being investigated started, re-occurred, or increased in rate\n   recently, look at the git history around the crash site to see if recent changes may have caused\n   the bug. Use `git blame` to find when the crash line or the code around it was last modified, and\n   `git log` to see recent commits in that file.\n\n3. **The test file.** Use `/find-test` on the source file containing the crash site (the\n   cross-reference output may already list relevant test files). If no test exists, identify the\n   nearest test file in the same directory.\n\n4. **Existing feature tests.** Search for existing tests that exercise the _feature_ involved in the\n   bug — not just tests near the crash site file. The crash may be in `pipeline.c++` but the relevant\n   working test may be an integration test in a completely different directory. These existing tests\n   encode setup, verification, and framework patterns you need. They are your starting template.\n\n5. **The build command.** Construct the exact `bazel test` invocation to run a single test case from\n   that test file.\n\n**Output to user:** The crash site with a one-sentence explanation of the invariant, the test file\npath, and the build command.\n\n### 3. Hypothesize\n\nForm a hypothesis in the format:\n\n> \"If I do X after Y, Z will happen because W.\"\n\nThis does not need to be correct. It needs to be testable. State it to the user.\n\nAsk for clarification or additional details if you cannot form a hypothesis with the information\nyou have. But do not ask for more information just to delay writing a test.\n\n### 4. Write the test\n\n**Start from an existing test if one exists** (from step 2.3). Clone it and modify the single\nvariable that your hypothesis targets (disable an autogate, change a config flag, alter the setup).\nThis is almost always faster and more correct than writing from scratch, because existing tests\nalready have the right verification (subrequest checks, expected log patterns, shutdown handling).\n\nIf no existing test is suitable, write a new one that:\n\n- Sets up the minimum state to reach the crash site\n- Performs the operation described in the hypothesis\n- **Includes observable verification** — the test must check that the feature actually ran, not\n  just that nothing crashed. Use subrequest expectations, check for feature-specific log lines, or\n  verify side effects.\n- Asserts the expected behavior (what _should_ happen if the bug didn't exist)\n\nKeep it short. Prefer public API. Do not try to reproduce the full production call stack.\n\nDo not interrupt your flow to investigate tangents while writing the test. If you\nrealize you need to understand something else to write the test, make a note of it\nand move on — you can investigate it in the next iteration if the test doesn't\nreproduce the bug.\n\n### 5. Run the test\n\nBuild and run using the command from step 2. **Start the build immediately.** Do not read more code\nbefore starting the build.\n\nUsing parallel sub-agents, waiting for the build, read code that would inform the **next** test\niteration if this one doesn't reproduce the bug\n\n### 6. Validate and iterate\n\nAfter every test run:\n\n1. **Always** update the tracking document (if using one)\n2. **Always** check the test output for evidence the code path was exercised — feature-specific log\n   lines, subrequests, RPC calls. A test that passes with no evidence the feature ran is not a valid\n   result.\n\nBased on the result:\n\n- **Test fails as expected** → the mechanism is confirmed. Report findings to the user. Read code\n  with purpose to find the fix, not to find the bug.\n- **Test passes with evidence the feature ran** → hypothesis was wrong. Adjust the hypothesis,\n  update the test, run again. Tell the user what you learned.\n- **Test passes with NO evidence the feature ran** → the test is not exercising the code path. Do\n  not read more source code to explain why. Fix the test first — compare it against existing working\n  tests to find what's missing.\n- **Test doesn't compile** → fix the compilation error and rerun. This is not a setback, it's a\n  normal part of the process.\n- **Test crashes differently** → follow the new trail but note the divergence. Tell the user.\n\nRepeat until the bug mechanism is confirmed or you've exhausted reasonable hypotheses (at which\npoint, report what you've tried and what you've ruled out).\n\n### 7. Report\n\nWhen the mechanism is confirmed, output:\n\n- **Bug summary**: One paragraph describing the root cause\n- **Reproduction**: The test name and how to run it\n- **Crash site**: `file:line` with explanation\n- **Suggested fix direction**: Where the fix likely needs to go (if apparent from the test results)\n\n## Rules\n\n- **Always work in parallel whenever possible.** Don't wait for the build to finish before reading\n  code that would inform the next test iteration. Use the build time to maximize your learning and\n  progress. Investigate multiple hypotheses in parallel if you can, but do not let this delay\n  writing and running tests.\n- **Do not keep endlessly reading code before the first test is written and building.** If you hit\n  15 tool calls, write whatever test you can with your current understanding.\n- **Do not re-read the same function more than twice.** If you catch yourself doing this, update\n  the tracking document to record findings and write a test immediately.\n- **Do not trace the full call stack before writing a test.** The test will tell you if your\n  understanding is correct.\n- **Every hypothesis must be tested, not just reasoned about.**\n- **Update the tracking document with each iteration.** If a tracking document is being used, update\n  the hypotheses, code read, and test results sections so you have a clear record of your\n  investigation process. After compaction, **always** update the tracking document before continuing\n  to the next step.\n- **Never** miss an opportunity for a good dad joke (using the `dad-jokes` skill). Don't overdo it.\n  When summarizing, **always** preserve any jokes from the subagent output, and **always** including\n  the intro prefix (\"Here's a dad joke for you:\", etc.) so the user knows it's intentional.\n"
  },
  {
    "path": ".opencode/commands/just.md",
    "content": "---\ndescription: Run a just recipe\n---\n\nRun the following command and show the output to the user:\n\n```\njust $ARGUMENTS\n```\n\nIf the command fails, summarize the error. If it succeeds, briefly confirm what ran and show any relevant output.\n\nIf no arguments are provided, run `just --list` to show available recipes.\n"
  },
  {
    "path": ".opencode/commands/onboarding.md",
    "content": "---\ndescription: Guided onboarding to the workerd codebase, or a specific area\nsubtask: true\n---\n\nOnboard: $ARGUMENTS\n\n**If no argument is provided (empty or blank), present the project basics and available areas:**\n\n1. **What is workerd**: Cloudflare Workers JavaScript/WebAssembly runtime. C++23 monorepo built with Bazel, using V8 for JS execution.\n\n2. **How to build and test**:\n   - Build: `just build`\n   - Run all tests: `just test`\n   - Run a single test with live output: `just stream-test <bazel target>`\n   - Run a sample: `bazel run //src/workerd/server:workerd -- serve $(pwd)/samples/<name>/config.capnp`\n\n3. **AI tools available to help you**: Read the `.opencode/` directory and list:\n   - **Agents** (from `.opencode/agent/`): list each with a one-line description\n   - **Commands** (from `.opencode/commands/`): list each with a one-line description (e.g., `/explain`, `/find-owner`, `/trace`, `/run`, etc.)\n   - **Skills** (from `.opencode/skills/`): list each with a one-line description (e.g., `kj-style`, `add-compat-flag`, `update-v8`, etc.)\n\n4. **What do you want to learn about?** Present areas as a menu:\n   - `/onboarding vscode` — Editor setup, extensions, debugging, clangd\n   - `/onboarding architecture` — Layers, request flow, key abstractions\n   - `/onboarding api` — JavaScript APIs (HTTP, crypto, WebSocket, etc.)\n   - `/onboarding streams` — ReadableStream/WritableStream/TransformStream\n   - `/onboarding node` — Node.js compatibility layer\n   - `/onboarding io` — I/O context, ownership, worker lifecycle\n   - `/onboarding actors` — Durable Objects, storage, gates\n   - `/onboarding jsg` — V8 bindings, JSG macros, type registration\n   - `/onboarding rust` — Rust crates, CXX FFI, Rust JSG bindings, proc macros\n   - `/onboarding just` — `just` command runner: available recipes and aliases\n   - `/onboarding build` — Bazel, dependencies, test formats\n   - `/onboarding server` — Config, services, networking\n   - `/onboarding ai` — Custom AI commands, skills, agents, and tools available in this project\n   - `/onboarding <area>` — Guided walkthrough of a specific area\n\n   Keep this concise — just the menu, don't explain each area in detail.\n\n**If an argument is provided, give a guided walkthrough of that area:**\n\n### Special area: `ai`\n\nIf the argument is `ai`, dynamically discover and present all custom AI tooling configured for this project. Do NOT hard-code any names or descriptions — read them from the filesystem so the output stays current as files are added or removed.\n\n**Discovery steps:**\n\n1. **Commands** — List `.opencode/commands/`. For each `.md` file, read the YAML frontmatter and extract the `description` field. Present as a table: command name (derived from filename without `.md`, prefixed with `/`) and description.\n\n2. **Skills** — List `.opencode/skills/`. For each subdirectory, read `SKILL.md` inside it and extract the `name` and `description` from the YAML frontmatter. Present as a table: skill name and description.\n\n3. **Agents** — List `.opencode/agent/`. For each `.md` file, read the YAML frontmatter and extract the `description` and `mode` fields. Present as a table: agent name (derived from filename without `.md`), mode, and description.\n\n4. **Tools** — List `.opencode/tools/`. For each `.ts` file (excluding files that don't export a `tool()`), read the file and extract the `description` string from the `tool({description: ...})` call. Present as a table: tool name (derived from filename without `.ts`) and description.\n\n**Output format:**\n\nPresent each category with a brief intro explaining what it is and how to use it:\n\n- **Commands**: Invoked with `/command-name [args]` in the chat. These are project-specific slash commands.\n- **Skills**: Loaded automatically or on demand to provide domain-specific instructions. Users don't invoke these directly — the AI loads them when relevant.\n- **Agents**: Specialized AI modes with different permissions and focuses (e.g., read-only architect vs. code-writing build agent).\n- **Tools**: Custom tools the AI can call during tasks. Users don't invoke these directly — the AI uses them as needed.\n\nAfter presenting all four categories, suggest a few \"what next\" pointers (e.g., \"try `/onboarding architecture` to learn the codebase structure\" or \"try `/explain <class>` to understand a specific class\").\n\nSkip the general approach steps below — this area does not involve a code walkthrough.\n\n### Special area: `just`\n\nIf the argument is `just`, read the `justfile` at the project root and present a quick reference of the `just` command runner and all available recipes.\n\n**Output format:**\n\n1. **Brief intro**: Explain that workerd uses [`just`](https://github.com/casey/just) as a command runner (like `make` but simpler, no build graph, just commands). Mention that running `just` with no arguments lists all recipes.\n\n2. **Recipes table**: Read the `justfile` and present all recipes organized by category. For each recipe, show the command (including aliases where defined), parameters with defaults, and a short description (derived from comments or the recipe body). Group into logical categories (e.g., \"Core workflows\", \"Testing shortcuts\", \"Sanitizers & analysis\", \"Benchmarks & profiling\", \"Setup & maintenance\").\n\n3. **Tips**: Include a few practical tips:\n   - Aliases (e.g., `just b` = `just build`)\n   - `just watch <recipe>` to auto-rerun on file changes\n   - Default args (e.g., `just build` builds everything, `just build //src/workerd/api/...` builds a subtree)\n\nSkip the general approach steps below — this area does not involve a code walkthrough.\n\n### General approach for all areas\n\nThe goal is orientation, not reference. Keep the reader focused on what they need to understand first — not everything that exists. For exhaustive detail (full API surfaces, complete file listings, build targets), defer to `/explain`.\n\n1. **Read context**: Read the relevant `AGENTS.md` file for the area (e.g., `src/workerd/api/streams/AGENTS.md` for streams). Also check for a `README.md` in the directory.\n2. **Explain the subsystem**: What it does, why it exists, how it fits into the broader architecture. Keep it to 2-3 paragraphs. Stick to facts — don't editorialize or speculate.\n3. **Key classes and files**: List the 3-5 most important classes/files with one-line descriptions and file paths. Focus on what to read first, not everything. For the complete listing, suggest `/explain <area>`.\n4. **Concrete example**: Walk through one specific flow end-to-end (e.g., \"here's what happens when a fetch() response body is read as a stream\"). **This is the most important section** — it's what builds intuition. Make it the longest and most detailed part of the output.\n5. **Key patterns to know**: Patterns specific to this area that a newcomer needs to understand (e.g., `IoOwn` for io, JSG macros for jsg, compat flag gating for api).\n6. **Tests to read**: Point to 2-3 representative tests that demonstrate how the code works.\n7. **Further reading**: Suggest related `/onboarding <area>` topics for adjacent areas. For detailed reference on specific classes or files mentioned above, point to `/explain <target>`.\n8. **Never** miss an opportunity for a good dad joke (using the `dad-jokes` skill). Don't overdo it, but don't avoid them either. When summarizing, **always** preserve any jokes from the subagent output, and **always** including the intro prefix (\"Here's a dad joke for you:\", etc.) so the user knows it's intentional.\n\n### Area-specific sources\n\nAlways apply the general approach above, but use these specific sources for each area:\n\n| Area           | Primary sources                                                                                                                                                                                                                                                                                                               |\n| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `vscode`       | `docs/vscode.md` — devcontainer setup, recommended extensions (clangd, C/C++, capnproto-syntax, GitLens, markdownlint), VSCode tasks (build/test/clean), debug launch targets (workerd dbg, inspector, test case, wd-test case), clangd configuration (`compile_flags.txt` or dependency files), `tools/unix/clangd-check.sh` |\n| `architecture` | `AGENTS.md`, root `AGENTS.md`, all subdirectory `AGENTS.md` files — explain the layers: `server/` (config, networking) → `io/` (IoContext, worker lifecycle) → `api/` (JS APIs) → `jsg/` (V8 bindings), plus `util/` (shared utilities)                                                                                       |\n| `api`          | `src/workerd/api/AGENTS.md`, `src/workerd/api/BUILD.bazel` — the JS API surface, `global-scope.h` as the entry point, how APIs are registered                                                                                                                                                                                 |\n| `streams`      | `src/workerd/api/streams/AGENTS.md`, `docs/streams.md` — dual controller architecture (internal vs standard), tutorial walkthrough with data flow diagrams                                                                                                                                                                    |\n| `node`         | `src/node/AGENTS.md`, `src/workerd/api/node/AGENTS.md` — three-tier module system (C++ → TS internal → TS public), `NODEJS_MODULES` macro, compat flag gating                                                                                                                                                                 |\n| `io`           | `src/workerd/io/AGENTS.md` — IoContext, IoOwn, DeleteQueue, InputGate/OutputGate, Worker::Actor                                                                                                                                                                                                                               |\n| `actors`       | `src/workerd/io/AGENTS.md`, `src/workerd/api/actor-state.h` — Actor lifecycle, ActorCache vs ActorSqlite, gates, hibernation                                                                                                                                                                                                  |\n| `jsg`          | `src/workerd/jsg/AGENTS.md`, `docs/jsg.md` — JSG_RESOURCE_TYPE, JSG_METHOD, type mapping, V8 integration                                                                                                                                                                                                                      |\n| `rust`         | `src/rust/AGENTS.md`, `src/rust/jsg/README.md`, `src/rust/jsg-macros/README.md` — CXX FFI bridges, Rust JSG bindings (`#[jsg_resource]`, `#[jsg_method]`), proc macros, crate organization, `just clippy`, error handling patterns                                                                                            |\n| `build`        | `build/AGENTS.md`, `AGENTS.md` — Bazel targets, `just` commands, `wd_test`/`kj_test` macros, dependency management (`MODULE.bazel`, `build/deps/`)                                                                                                                                                                            |\n| `server`       | `src/workerd/server/AGENTS.md` — workerd.capnp config, Server class, service setup, networking                                                                                                                                                                                                                                |\n"
  },
  {
    "path": ".opencode/commands/rdeps.md",
    "content": "---\ndescription: Find what depends on a given external dependency or internal target\nsubtask: true\n---\n\nFind reverse dependencies for: $ARGUMENTS\n\n**Use the `bazel-deps` tool** with `direction: \"rdeps\"` to perform this lookup. It handles label resolution, Bazel queries, and grouping automatically. Pass the dependency name from the arguments as the `target`.\n\nThe target supports ecosystem qualifiers to disambiguate when a name matches both C++ and Rust targets:\n\n- `rust:base64` or `crate:base64` — resolve as a Rust crate only\n- `cpp:base64` or `cc:base64` — resolve as a C++ target only\n- Unqualified names (e.g. `base64`) use the default resolution order (C++ first, then Rust)\n\nPass the full argument including any qualifier as the `target` (e.g. `target: \"rust:base64\"`).\n\nIf the argument includes \"deep\" or \"transitive\", pass `depth: 2` (or higher) to the tool. Otherwise use the default depth of 1 (direct dependents only). Warn the user that deeper queries are slower.\n\nAfter receiving the tool output, add any useful observations:\n\n- Is the dependency narrowly or broadly used?\n- Are there surprising consumers?\n- For C++ deps, are source files using the dependency via `deps` (public) or `implementation_deps` (hidden)?\n  You can check this by reading the relevant `BUILD.bazel` files.\n- For dependencies that appear unused, note whether they might be pulled in transitively.\n"
  },
  {
    "path": ".opencode/commands/review.md",
    "content": "---\ndescription: Review local branch changes or a GitHub PR\nagent: architect\nsubtask: true\n---\n\nReview code changes. Determine what to review based on the arguments below, then follow the architect agent's review workflow.\n\n**Arguments:** $ARGUMENTS\n\n**How to determine what to review:**\n\n1. If arguments contain a PR number (e.g., `1234`) or URL (e.g., `https://github.com/.../pull/1234`), review that PR:\n   - Use `gh pr view <number>` for the description and metadata\n   - Use `gh pr diff <number>` for the changes\n   - Use `gh pr checks <number>` for CI status\n   - Check for prior review comments via `gh api`\n\n2. If no PR is specified, review the current branch vs origin/main:\n   - Use `git diff origin/main...HEAD` for the changes\n   - Use `git log --oneline origin/main..HEAD` for commit context\n\nPerform a balanced review covering all analysis areas. Focus on CRITICAL and HIGH issues first. If there are no significant issues, say so briefly rather than inventing low-value findings.\n\nAny additional instructions from the arguments above (e.g., \"focus on memory safety\") should narrow the review scope accordingly.\n"
  },
  {
    "path": ".opencode/commands/run.md",
    "content": "---\ndescription: Run tests or samples\nsubtask: true\n---\n\nRun: $ARGUMENTS\n\n**Parse the argument to determine what to run:**\n\n### Running tests\n\n- `/run test <name>` — Run a specific test by name or path\n- `/run tests` — Run all tests (`just test` or `bazel test //...`)\n- `/run test <area>` — Run tests for an area like `streams`, `http`, `jsg`, `node`, etc.\n\n**Steps for running a specific test:**\n\n1. **Resolve the test target.** If the argument is a file path, find its Bazel test target. If it's a short name (e.g., `streams`), search for matching test targets:\n\n   ```\n   bazel query 'kind(\".*_test\", //src/workerd/...)' --output label 2>/dev/null | grep -i '<name>'\n   ```\n\n2. **Determine the test type:**\n   - If the target comes from `wd_test()`, it's a `.wd-test` config test. These have variants: `@` (oldest compat), `@all-compat-flags` (newest compat), `@all-autogates`.\n   - If the target comes from `kj_test()`, it's a C++ unit test.\n\n3. **Run it.** Use `just stream-test` for a single test (streams output live) or `just test` for batches:\n\n   ```\n   just stream-test //src/workerd/api/tests:<target>@\n   ```\n\n   For running all tests in an area:\n\n   ```\n   just test //src/workerd/api/tests/...\n   just test //src/workerd/jsg/...\n   ```\n\n4. **If the test fails**, read the output and provide a brief summary of what failed and why. If it's a compilation error, identify the source. If it's a test assertion failure, identify the failing test case and expected vs actual values.\n\n### Running samples\n\n- `/run sample <name>` — Run a sample by name (e.g., `helloworld_esm`, `nodejs-compat`, `durable-objects-chat`)\n- `/run samples` — List all available samples\n\n**Steps for running a sample:**\n\n1. **List available samples** if the user says `/run samples` or the name doesn't match:\n\n   ```\n   ls samples/\n   ```\n\n2. **Find the sample config.** Look for `config.capnp` in `samples/<name>/`:\n\n   ```\n   ls samples/<name>/config.capnp\n   ```\n\n3. **Build workerd first:**\n\n   ```\n   bazel build //src/workerd/server:workerd\n   ```\n\n4. **Run the sample:**\n\n   ```\n   bazel run //src/workerd/server:workerd -- serve $(pwd)/samples/<name>/config.capnp\n   ```\n\n   Add optional flags if the user requests them or the sample requires them:\n   - `--verbose` — verbose logging\n   - `--watch` — auto-reload on file changes\n   - `--experimental` — enable experimental features\n\n   Some samples require `--experimental` (check if the config uses experimental compat flags). Samples that typically need it: `pyodide*`, `repl-server*`, `filesystem`.\n\n5. **Tell the user** what address the sample is listening on (usually `http://localhost:8080` based on the config's socket definition) and that it will keep running until they press Ctrl+C.\n\n6. **IMPORTANT:** Running a sample launches a long-lived process. After starting it, inform the user and do not attempt further actions until they indicate the process has been stopped. The intent is for the human to manually test the running server.\n\n7. When the user indicates they are done testing the sample (e.g., \"I'm done testing, you can stop now\"), stop the process.\n\n### Running all tests\n\n- `/run tests` → `just test` (runs the full test suite)\n\nThis will take a long time. Warn the user and ask for confirmation before running.\n\n### Running involves building\n\nRunning tests or samples will trigger a build of the necessary targets. If the build fails, report the failure and do not attempt to run.\n\n### Run modes\n\n- If the user requests to run with ASAN, add the `--config=asan` flag to the bazel command. Warn the user that ASAN builds are slower and may produce more verbose output.\n- If the user requests to run with release mode, add the `--config=opt` flag to the bazel command. This will produce optimized binaries without debug symbols, which may be faster but harder to debug if something goes wrong.\n- If the user requests to run with debug mode, add the `--config=debug` flag to the bazel command. This will produce debug builds with symbols, which are easier to debug but may be slower.\n\n### Examples\n\n| Command                                    | What it does                                          |\n| ------------------------------------------ | ----------------------------------------------------- |\n| `/run test streams`                        | Runs all streams-related tests                        |\n| `/run test http-test`                      | Finds and runs the HTTP test target                   |\n| `/run test //src/workerd/jsg:jsg-test`     | Runs the exact Bazel target                           |\n| `/run tests`                               | Runs the entire test suite                            |\n| `/run sample helloworld_esm`               | Builds workerd and runs the ESM hello world sample    |\n| `/run sample nodejs-compat --experimental` | Runs the Node.js compat sample with experimental flag |\n| `/run samples`                             | Lists all available samples                           |\n"
  },
  {
    "path": ".opencode/commands/submit.md",
    "content": "---\ndescription: Check if the current branch is ready to submit\nagent: submit\nsubtask: true\n---\n\nCheck if the current branch is ready for submission. Follow the full workflow from the submit agent instructions. $ARGUMENTS\n"
  },
  {
    "path": ".opencode/commands/test-for.md",
    "content": "---\ndescription: Find tests that exercise a source file's code, with specific test case details\nsubtask: true\n---\n\nFind tests for: $ARGUMENTS\n\nSteps:\n\n1. **Resolve the source file.** If the argument is a file path, use it directly. If it's a class or symbol name, find its source file first.\n\n2. **Extract key symbols.** Read the source file (header preferred) and identify the main exported symbols: class names, public method names, free function names, macros. These are what we'll search for in tests.\n\n3. **Find test targets.** Look in the `BUILD.bazel` file in the same directory and in a `tests/` subdirectory for `wd_test()` and `kj_test()` rules. For Rust files (`.rs` under `src/rust/`), also check for `wd_rust_crate()` rules which auto-generate `<name>_test` targets, and companion C++ test files (e.g., `ffi-test.c++`) that test the FFI bridge. Also check if the source file is a dependency of test targets:\n\n   ```\n   bazel query 'rdeps(//src/..., <source_target>, 1)' --output label 2>/dev/null | grep -i test\n   ```\n\n4. **Find test cases that reference the code.** For each test file found, grep for the key symbols from step 2. Read the surrounding context to understand what each test case exercises. Tests for some C++\n   code may be in JavaScript test files if the C++ code is exposed to JS, so check those as well. For Rust code, also search `.rs` test files (including inline `#[cfg(test)]` modules and files under `tests/` subdirectories) and the `jsg-test` harness tests.\n\n5. **Assess coverage.** Compare the public API surface (from step 2) against what the tests exercise (from step 4). Identify:\n   - Well-tested: symbols referenced in multiple test cases\n   - Partially tested: symbols referenced but not thoroughly (e.g., only happy path)\n   - Untested: public symbols with no test references\n\n6. **Output:**\n   - **Source**: file path and main symbols\n   - **Test targets**: Bazel labels with `just test` commands\n   - **Test cases** (grouped by test file):\n     - `file:line` — description of what's tested (e.g., \"tests TextEncoder with UTF-8 input\")\n   - **Coverage assessment**:\n     - Well-tested: list of symbols\n     - Gaps: list of public symbols with no apparent test coverage\n   - **Suggestions**: specific test cases to add for untested code paths, if applicable\n"
  },
  {
    "path": ".opencode/commands/trace.md",
    "content": "---\ndescription: Trace callers and callees of a function or method across the codebase\nsubtask: true\n---\n\nTrace call graph for: $ARGUMENTS\n\nSteps:\n\n1. **Find the definition.** If the target is a method on a C++ class, **use the `cross-reference` tool first** to locate the header, implementation files, and JSG registration in a single call. For Rust symbols (under `src/rust/`), search `.rs` files manually and check for CXX bridge declarations. Otherwise, search for the function/method definition manually. If the argument is ambiguous (e.g., a common name like `get`), ask for clarification or use the class-qualified form (e.g., `IoContext::current`).\n\n2. **Read the implementation.** Read the function body to identify:\n   - **Direct callees**: functions/methods called within the body\n   - Filter out trivial calls (logging, assertions, simple getters) unless they're relevant to understanding the flow\n\n3. **Find callers.** Search for call sites across the codebase:\n\n   ```\n   grep -rn '<function_name>' src/ --include='*.h' --include='*.c++' --include='*.rs'\n   ```\n\n   Filter to actual calls (not declarations, comments, or string literals). For methods, search for both `->methodName(` and `.methodName(` patterns.\n\n   **For functions crossing the Rust/C++ FFI boundary** (defined in a `#[cxx::bridge]` block or companion `ffi.c++`/`ffi.h` file), search both sides: a Rust function called from C++ will have callers in `.c++` files and its declaration in the CXX bridge; a C++ function called from Rust will have its Rust callers in `.rs` files and its implementation in `ffi.c++`. Also check generated headers (`<file>.rs.h`) if the call path is unclear.\n\n4. **Build the call graph.** Organize into:\n   - **Callers** (who calls this function) — grouped by directory/component\n   - **Callees** (what this function calls) — the significant ones from the implementation\n\n5. **Trace one level deeper if needed.** If the argument includes \"deep\" or the function is a thin wrapper, trace one additional level in each direction to find the meaningful boundaries.\n\n6. **Output:**\n   - **Function**: full qualified name, file:line of definition\n   - **Callers** (N call sites):\n     - `file:line` — brief context of why it's called\n   - **Callees** (N significant calls):\n     - `function_name` (`file:line`) — brief description\n   - **Call flow summary**: A brief narrative of how this function fits into the broader execution flow (e.g., \"Called during request setup to initialize the I/O context, which then creates the worker lock...\")\n   - Limit to ~20 most important callers. If there are more, note the total count and list the most representative ones grouped by component.\n"
  },
  {
    "path": ".opencode/commands/trivia.md",
    "content": "---\ndescription: Play workerd codebase trivia\nsubtask: true\n---\n\nYou are a fun and engaging trivia host for the workerd codebase! Your job is to test the user's knowledge of this JavaScript/WebAssembly runtime. The purpose is to entertain and further educate the user about the workerd project.\n\n**Topic argument:** $ARGUMENTS\n\nIf a topic is provided (e.g., `/trivia streams`, `/trivia KJ`, `/trivia Node.js compat`), focus all questions on that topic. Research the relevant area of the codebase first to generate accurate, specific questions. If no topic is provided, draw from any area of the codebase.\n\n## How to Play\n\n1. **Ask one trivia question at a time** about the workerd codebase\n2. **Wait for the user's answer** before revealing the correct answer\n3. **Provide educational explanations** when revealing answers with references to relevant files or concepts\n4. **Keep score** if the user wants to play multiple rounds\n5. **Be encouraging** - celebrate correct answers and gently explain incorrect ones\n\n## Question Categories\n\nDraw questions from any area of the workerd codebase. The examples below are starting points, not limits — dig into the actual code to find interesting details for questions.\n\n### Architecture & Structure\n\nExamples: directory structure, Cap'n Proto configuration, JSG/V8 integration, I/O subsystem, actor storage, worker lifecycle, cross-heap ownership (`IoOwn`), gate mechanisms, etc.\n\n### APIs & Features\n\nExamples: HTTP/fetch, crypto, streams, WebSocket, Node.js compat, Web Platform APIs, Python/Pyodide, KJ library idioms, Durable Objects, R2/KV/Queue bindings, RPC, containers, etc.\n\n### Build System\n\nExamples: Bazel targets and macros, `just` commands, test types and variants, dependency management, TypeScript bundling, etc.\n\n### Development Practices\n\nExamples: compatibility flags, autogates, code style, error handling patterns, memory management, promise patterns, mutex patterns, etc.\n\n### History & Context\n\nExamples: Cloudflare Workers architecture, why workerd was open-sourced, key design decisions, V8 integration choices, etc.\n\n## Guidelines\n\n- Start with easier questions and gradually increase difficulty\n- Use the read-only tools (read, glob, grep) to verify your answers if needed\n- **All questions must be multiple choice** with 4 options (A through D). Exactly one option should be correct.\n- If the user seems stuck, offer hints\n- Keep the tone light and fun - this is meant to be entertaining!\n- After 3-5 questions, ask if they want to continue\n- Research answers using the codebase as needed to ensure accuracy. Do not guess or make up answers.\n\n## Starting the Game\n\nWhen invoked, introduce yourself briefly and jump right into the first question. Something like:\n\n\"Let's test your workerd knowledge! Here's your first question...\"\n"
  },
  {
    "path": ".opencode/commands/whats-new.md",
    "content": "---\ndescription: Summarize recent commits — by count, date, or time range\nsubtask: true\n---\n\nShow what's new: $ARGUMENTS\n\n**Parse the argument** to determine the commit range:\n\n- A number (e.g., `10`) → the N most recent commits: `git log -<N>`\n- `since <date>` (e.g., `since 2026-02-01`) → commits after that date: `git log --since='2026-02-01'`\n- `today` → `git log --since='midnight'`\n- `this week` → `git log --since='1 week ago'`\n- `this month` → `git log --since='1 month ago'`\n- `yesterday` → `git log --since='2 days ago' --until='midnight'`\n- `last week` → `git log --since='2 weeks ago' --until='1 week ago'`\n- No argument → default to `10`\n\nSteps:\n\n1. **Target the main branch at origin.** Always show what's new on the remote `main` branch,\n   regardless of which branch is currently checked out. First, fetch the latest:\n\n   ```\n   git fetch origin main\n   ```\n\n   Then use `origin/main` as the ref for all subsequent git commands (instead of `HEAD`).\n\n2. **Fetch the commits.** Run `git log` with the parsed range against `origin/main`:\n\n   ```\n   git log <range> origin/main --format='%h|%aN|%as|%s' --no-merges\n   ```\n\n   Also include merge commits separately if relevant:\n\n   ```\n   git log <range> origin/main --format='%h|%aN|%as|%s' --merges\n   ```\n\n3. **Get the diff stats** for scope:\n\n   ```\n   git diff --stat <oldest_commit>^..origin/main\n   ```\n\n4. **Categorize each commit.** You MUST load the `commit-categories` skill before categorizing. Use its path-pattern table to assign each commit to a category based on the files it touches.\n\n5. **Highlight notable changes.** Scan commit messages and diffs for:\n   - New compatibility flags (additions to `compatibility-date.capnp`)\n   - New autogates (additions to `autogate.h`)\n   - Breaking changes or behavioral changes\n   - New APIs or features\n   - Security fixes\n   - Dependency updates\n   - If current branch is not `main`:\n     - Relevant changes to files touched by the current branch (if any)\n     - Conflicts with the current branch (if any)\n\n6. **Output:**\n\n   ```\n   ## What's New (<range description>)\n\n   **<N> commits** by <M> authors | <files changed> files changed, <insertions> insertions, <deletions> deletions\n\n   ### Highlights\n   - <notable changes, if any>\n\n   ### API\n   - `<hash>` <summary> — <author>\n\n   ### Node.js compat\n   - `<hash>` <summary> — <author>\n\n   ...additional categories as needed (omit empty categories)...\n   ```\n\n   Keep summaries to one line per commit. Group related commits (e.g., a feature + its fixup) into a single entry where obvious.\n"
  },
  {
    "path": ".opencode/skills/add-autogate/SKILL.md",
    "content": "---\nname: add-autogate\ndescription: Step-by-step guide for adding a new autogate to workerd for gradual rollout of risky changes, including enum registration, string mapping, usage pattern, and testing.\n---\n\n## Adding an Autogate\n\nAutogates enable gradual rollout of risky code changes independent of binary releases. Unlike compatibility flags (which are permanent, date-based behavioral changes), autogates are temporary gates that can be toggled on/off via internal tooling during rollout, then removed once the change is stable.\n\n### When to use an autogate vs a compat flag\n\n| Use an autogate when...                       | Use a compat flag when...                  |\n| --------------------------------------------- | ------------------------------------------ |\n| Rolling out a risky internal change gradually | Changing user-visible behavior permanently |\n| You need a kill switch during rollout         | The change is tied to a compatibility date |\n| The gate will be removed once stable          | Users need to opt in or out explicitly     |\n\nAutogates and compat flags are separate mechanisms — an autogate does not become a compat flag.\n\n### Step 1: Add the enum value\n\nEdit `src/workerd/util/autogate.h`. Add a new entry to the `AutogateKey` enum **before `NumOfKeys`**:\n\n```cpp\nenum class AutogateKey {\n  TEST_WORKERD,\n  // ... existing gates ...\n  // Brief description of what this gate controls.\n  MY_NEW_FEATURE,\n  NumOfKeys  // Reserved for iteration.\n};\n```\n\nNaming convention: `SCREAMING_SNAKE_CASE` for the enum value.\n\n### Step 2: Add the string mapping\n\nEdit `src/workerd/util/autogate.c++`. Add a `case` to the `KJ_STRINGIFY` switch **before the `NumOfKeys` case**:\n\n```cpp\nkj::StringPtr KJ_STRINGIFY(AutogateKey key) {\n  switch (key) {\n    // ... existing cases ...\n    case AutogateKey::MY_NEW_FEATURE:\n      return \"my-new-feature\"_kj;\n    case AutogateKey::NumOfKeys:\n      KJ_FAIL_ASSERT(\"NumOfKeys should not be used in getName\");\n  }\n}\n```\n\nNaming convention: `kebab-case` for the string name. This string is what appears in runtime configuration (prefixed with `workerd-autogate-`). The enum name and string name should match to avoid confusion.\n\n### Step 3: Guard your code\n\nUse `Autogate::isEnabled()` to conditionally execute the new code path:\n\n```cpp\n#include <workerd/util/autogate.h>\n\n// At the point where behavior should change:\nif (util::Autogate::isEnabled(util::AutogateKey::MY_NEW_FEATURE)) {\n  // New code path\n} else {\n  // Old code path (keep until gate is removed)\n}\n```\n\n### Step 4: Test\n\nThree ways to test autogated code:\n\n**A. The `@all-autogates` test variant** (automatic):\n\nEvery `wd_test()` and `kj_test()` generates a `@all-autogates` variant that enables all gates. If your feature is tested by existing tests, they'll automatically run with the gate enabled:\n\n```bash\njust stream-test //src/workerd/api/tests:my-test@all-autogates\n```\n\n**B. Targeted C++ test setup**:\n\nIn a C++ test file, enable specific gates:\n\n```cpp\n#include <workerd/util/autogate.h>\n\n// In test setup:\nutil::Autogate::initAutogateNamesForTest({\"my-new-feature\"_kj});\n\n// In test teardown:\nutil::Autogate::deinitAutogate();\n```\n\n**C. Environment variable**:\n\nSet `WORKERD_ALL_AUTOGATES=1` to enable all gates when no explicit config is provided.\n\n### Step 5: Build and verify\n\n```bash\njust build\njust stream-test //path/to:my-test@               # Old behavior (gate off)\njust stream-test //path/to:my-test@all-autogates   # New behavior (gate on)\n```\n\n### Step 6: Remove the gate (after rollout)\n\nOnce the human user explicitly confirms that the feature is stable and fully rolled out:\n\n1. Remove the `AutogateKey` enum value from `autogate.h`\n2. Remove the `case` from `KJ_STRINGIFY` in `autogate.c++`\n3. Remove all `Autogate::isEnabled()` checks, keeping only the new code path\n\n### Checklist\n\n- [ ] Enum value added to `AutogateKey` in `autogate.h` (before `NumOfKeys`)\n- [ ] Comment describes what the gate controls\n- [ ] String mapping added to `KJ_STRINGIFY` in `autogate.c++`\n- [ ] Code guarded with `Autogate::isEnabled()`\n- [ ] Old code path preserved (for rollback)\n- [ ] `@all-autogates` test variant passes\n- [ ] Tests cover both gated and ungated paths\n\n### Files touched\n\n| File                            | What to do                              |\n| ------------------------------- | --------------------------------------- |\n| `src/workerd/util/autogate.h`   | Add enum value with comment             |\n| `src/workerd/util/autogate.c++` | Add case to `KJ_STRINGIFY`              |\n| Your feature file(s)            | Guard code with `Autogate::isEnabled()` |\n"
  },
  {
    "path": ".opencode/skills/add-compat-flag/SKILL.md",
    "content": "---\nname: add-compat-flag\ndescription: Step-by-step guide for adding a new compatibility flag to workerd, including capnp schema, C++ usage, testing, and documentation requirements.\n---\n\n## Adding a Compatibility Flag\n\nCompatibility flags control behavioral changes in workerd. They allow breaking changes to be rolled out gradually using compatibility dates. Follow these steps in order.\n\n### Step 1: Choose flag names\n\nEvery flag needs:\n\n- **Enable flag**: Opts in to the new behavior (e.g., `text_decoder_replace_surrogates`)\n- **Disable flag**: Opts out after it becomes default (e.g., `disable_text_decoder_replace_surrogates`). Only needed if the flag will eventually become default for all workers.\n\nNaming conventions:\n\n- Use `snake_case`\n- Enable flag describes the new behavior positively\n- Disable flag uses a `no_` or `disable_` prefix, or describes the old behavior\n\n### Step 2: Add to `compatibility-date.capnp`\n\nEdit `src/workerd/io/compatibility-date.capnp`. Add a new field at the end of the `CompatibilityFlags` struct.\n\n```capnp\n  myNewBehavior @<NEXT_ORDINAL> :Bool\n      $compatEnableFlag(\"my_new_behavior\")\n      $compatDisableFlag(\"no_my_new_behavior\")\n      $compatEnableDate(\"2026-03-15\");\n  # Description of what this flag changes and why.\n  # Include context about the old behavior and what the new behavior fixes.\n```\n\nReplace `<NEXT_ORDINAL>` with the value returned by the `next-capnp-ordinal` tool.\n\nKey points:\n\n- The field number must be the next sequential ordinal. **Use the `next-capnp-ordinal` tool** to find it: call it with `file: \"src/workerd/io/compatibility-date.capnp\"` and `struct: \"CompatibilityFlags\"`. Do NOT guess or hardcode the number.\n- The field name is `camelCase` and becomes the C++ getter name (e.g., `getMyNewBehavior()`).\n- `$compatEnableDate` is the date after which new workers get this behavior by default. Set this to a future date. If the flag is not yet ready for a default date, omit `$compatEnableDate` — the flag will only activate when explicitly listed in `compatibilityFlags`.\n- Add `$experimental` annotation if the feature is experimental and should require `--experimental` to use.\n- The comment block is required and serves as internal documentation.\n\nAvailable annotations:\n| Annotation | Purpose |\n|---|---|\n| `$compatEnableFlag(\"name\")` | Flag name to enable the behavior |\n| `$compatDisableFlag(\"name\")` | Flag name to disable after it's default |\n| `$compatEnableDate(\"YYYY-MM-DD\")` | Date after which behavior is default |\n| `$compatEnableAllDates` | Force-enable for all dates (rare, breaks back-compat) |\n| `$experimental` | Requires `--experimental` flag to use |\n| `$neededByFl` | Must be propagated to Cloudflare's FL proxy layer |\n| `$impliedByAfterDate(name = \"otherFlag\", date = \"YYYY-MM-DD\")` | Implied by another flag after a date |\n\n### Step 3: Use the flag in C++ code\n\nAccess the flag via the auto-generated getter:\n\n```cpp\n// In code that has access to jsg::Lock:\nif (FeatureFlags::get(js).getMyNewBehavior()) {\n  // New behavior\n} else {\n  // Old behavior\n}\n```\n\nThe `FeatureFlags` class is defined in `src/workerd/io/features.h`. The getter name is derived from the capnp field name with a `get` prefix and the first letter capitalized.\n\nFor JSG API classes, you can also access flags in `JSG_RESOURCE_TYPE`:\n\n```cpp\nJSG_RESOURCE_TYPE(MyApi, workerd::CompatibilityFlags::Reader flags) {\n  if (flags.getMyNewBehavior()) {\n    JSG_METHOD(newMethod);\n  }\n}\n```\n\n### Step 4: Add tests\n\nTest both the old and new behavior. The test variant system helps:\n\n- **`test-name@`** runs with the oldest compat date (2000-01-01) — tests old behavior\n- **`test-name@all-compat-flags`** runs with the newest compat date (2999-12-31) — tests new behavior\n\nIn your `.wd-test` file, you can explicitly set the flag:\n\n```capnp\nconst unitTests :Workerd.Config = (\n  services = [(\n    name = \"my-test\",\n    worker = (\n      modules = [(name = \"worker\", esModule = embed \"my-test.js\")],\n      compatibilityFlags = [\"my_new_behavior\"],\n    ),\n  )],\n);\n```\n\nFor tests, the `compatibilityDate` field should not be included.\n\nWrite test cases that verify both behaviors. Consider edge cases where the flag changes observable behavior.\n\n### Step 5: Document the flag\n\n**This is required before the enable date.**\n\n1. Create a PR in the [cloudflare-docs](https://github.com/cloudflare/cloudflare-docs) repository.\n2. Add a markdown file under `src/content/compatibility-flags/` describing:\n   - What the flag does\n   - When it becomes default\n   - How to opt in or opt out\n   - Migration guidance if applicable\n\nSee `docs/api-updates.md` for more details on the documentation process.\n\n### Step 6: Build and verify\n\n```bash\n# Build to verify the capnp schema compiles\njust build\n\n# Run the specific test\njust stream-test //src/workerd/api/tests:my-test@\n\n# Run with all compat flags to test the new behavior\njust stream-test //src/workerd/api/tests:my-test@all-compat-flags\n\n# Run the compatibility-date test to verify flag registration\njust stream-test //src/workerd/io:compatibility-date-test@\n```\n\n### Checklist\n\n- [ ] Flag added to `compatibility-date.capnp` with correct sequential field number\n- [ ] Enable and disable flag names follow naming conventions\n- [ ] Comment block describes old behavior, new behavior, and rationale\n- [ ] Enable date is set (or intentionally omitted for experimental/unreleased flags)\n- [ ] C++ code uses `FeatureFlags::get(js).getMyNewBehavior()` to branch on the flag\n- [ ] Tests cover both old and new behavior\n- [ ] Documentation PR created in cloudflare-docs (required before enable date)\n- [ ] `compatibility-date-test` passes\n"
  },
  {
    "path": ".opencode/skills/bazel-test-hygiene/SKILL.md",
    "content": "---\nname: bazel-test-hygiene\ndescription: Mandatory rules for running bazel tests during development. Load this skill before running any bazel test command, especially when validating fixes or verifying regression tests. Prevents false confidence from cached results, filter flags that silently match nothing, and partial test runs that miss breakage.\n---\n\n# Bazel Test Hygiene\n\n## The Three Rules\n\n### 1. Always disable caching\n\n```bash\nbazel test //... --nocache_test_results\n```\n\n**Why:** Bazel's action cache can serve stale test binaries even after you edit source files. Without `--nocache_test_results`, you may be running the OLD binary and seeing OLD results. This is not hypothetical — it has caused real false-positive/false-negative confusion in this repo.\n\n**Always include `--nocache_test_results`.** No exceptions.\n\n### 2. Keep it simple — no filter flags\n\nDo NOT use `--test_arg='-f'` or similar filter flags to run individual test cases.\n\n**Why:** KJ test's `-f` flag silently passes when zero tests match. If you typo the filter or the test name changes, bazel reports \"PASSED\" with zero tests actually run. This gives completely false confidence.\n\n**Run the full test target.** If you need to check a specific test, look for its name in the full output. If the full suite is too slow, run the specific test _target_ (e.g., `//src/workerd/api:streams/standard-test@`), not a filtered subset within a target.\n\n### 3. Run the full suite before claiming done\n\nA single test target passing does not mean you haven't broken something else. Fixes to shared code (queue.c++, standard.c++, common.h) can break tests in completely different directories.\n\n**Before claiming any fix is complete:**\n\n```bash\nbazel test //... --nocache_test_results\n```\n\nCheck the final summary line: `Executed N out of N tests: N tests pass.` All N must match. If any test fails, the fix is not done.\n\n## Red-Green Verification for Regression Tests\n\nWhen writing a regression test for a bug fix, you MUST verify the test actually catches the bug:\n\n1. **Green:** Run `bazel test //... --nocache_test_results` — all tests pass (fix in place)\n2. **Red:** Remove the fix, run `bazel test //... --nocache_test_results` — the new test(s) MUST fail\n3. **Green:** Restore the fix, run `bazel test //... --nocache_test_results` — all tests pass again\n\nIf step 2 passes (test doesn't fail without the fix), the test is not testing what you think. Go back and fix the test.\n\n**Do the red-green on the full suite**, not just the one target. This catches two problems at once: (a) the regression test actually detects the bug, and (b) the fix doesn't break anything else.\n\n## Anti-Patterns\n\n| Don't                                     | Do instead                                          |\n| ----------------------------------------- | --------------------------------------------------- |\n| `bazel test //target` (no cache flag)     | `bazel test //target --nocache_test_results`        |\n| `--test_arg='-f' --test_arg='test name'`  | Run the full target, grep output for test name      |\n| Run one target, claim fix is done         | Run `//...`, check all-pass summary                 |\n| Claim \"tests pass\" from a previous run    | Run fresh, read fresh output                        |\n| Trust filter-based \"PASSED\" at face value | Check that the expected test names appear in output |\n"
  },
  {
    "path": ".opencode/skills/commit-categories/SKILL.md",
    "content": "---\nname: commit-categories\ndescription: Commit categorization rules for changelogs and \"what's new\" summaries. MUST be loaded before categorizing commits in changelog or whats-new commands. Provides the canonical path-based category table used to group commits by area.\n---\n\n# Commit Categories\n\nCategorize commits by the files they touch, using the primary area for commits spanning multiple categories.\n\n| Category            | Path patterns                                         |\n| ------------------- | ----------------------------------------------------- |\n| **API**             | `src/workerd/api/` (excluding `node/` and `pyodide/`) |\n| **Node.js compat**  | `src/workerd/api/node/`, `src/node/`                  |\n| **Python**          | `src/workerd/api/pyodide/`, `src/pyodide/`            |\n| **Rust**            | `src/rust/`                                           |\n| **Cloudflare APIs** | `src/cloudflare/`                                     |\n| **I/O**             | `src/workerd/io/`                                     |\n| **JSG**             | `src/workerd/jsg/`                                    |\n| **Server**          | `src/workerd/server/`                                 |\n| **Build**           | `build/`, `MODULE.bazel`, `BUILD.bazel`               |\n| **Types**           | `types/`                                              |\n| **Docs / Config**   | Documentation, agent/tool configs, `.md` files        |\n| **Tests**           | Changes exclusively in test files                     |\n| **Other**           | Anything that doesn't fit above                       |\n\n## Cross-cutting callouts\n\nThese are **not** primary categories — they are additional callout sections that appear alongside the main categories whenever a commit touches the relevant files. A single commit can appear in both a primary category and one or more callouts.\n\n| Callout                      | Trigger                                                                                             |\n| ---------------------------- | --------------------------------------------------------------------------------------------------- |\n| **New/Updated Compat Flags** | Changes to `src/workerd/io/compatibility-date.capnp` or new `compatibilityFlags` references in code |\n| **New/Updated Autogates**    | Changes to `src/workerd/io/supported-autogates.h` or new autogate registrations                     |\n\nWhen either callout applies, add a dedicated section **after** the main categories listing each new or modified flag/gate with a brief description. These must never be buried inside a general category bullet — they are high-visibility items that reviewers and release-note readers need to spot immediately.\n\n## How to categorize\n\n1. For each commit, run `git diff-tree --no-commit-id --name-only -r <hash>` to list files changed.\n2. Match changed files against the path patterns above.\n3. Assign the commit to whichever category covers the majority of its changes.\n4. For commits touching multiple areas, list under the **primary** area (the one with the most changed files or the most significant change).\n5. Check every commit against the cross-cutting callout triggers. If a commit adds or modifies compat flags or autogates, note it in the corresponding callout section **in addition to** its primary category.\n6. Omit empty categories and unused callout sections from output.\n"
  },
  {
    "path": ".opencode/skills/dad-jokes/SKILL.md",
    "content": "---\nname: dad-jokes\ndescription: After completing any task that took more than ~5 tool calls, or after long-running builds/tests finish, load this skill and deliver a dad joke to lighten the mood. Also load before any user-requested joke, pun, or limerick. Never improvise jokes without loading this skill first.\n---\n\n# Dad Jokes\n\n## When to fire\n\nAfter completing a long-running task (build, test suite, multi-step investigation, large refactor), drop a single dad joke, pun, or limerick before moving on. Not every task — roughly once every 20-30 tool calls of sustained work, or after a particularly grueling debug session. Use your judgment. If the mood is tense (production incident, urgent fix), skip it.\n\n## Rules\n\n- **Pick the format first.** Before composing the joke, pick one of these three formats at random. Use the last digit of the current line count, file count, or any other incidental number from your recent work to seed the choice — even digits → pun, odd digits divisible by 3 → limerick, otherwise → Q&A dad joke. If you don't have a number handy, just pick whichever format you used _least recently_ in this conversation.\n  - **Pun** (inline wordplay, one sentence). Intro: \"Time for a pun!\"\n  - **Limerick** (five lines, AABBA rhyme scheme). Intro: \"Limerick incoming!\"\n  - **Q&A** (setup question + punchline). Intro: \"Here's a joke for you:\"\n- **One joke only.** Do not become a comedy set. One line, then back to work.\n- **Always safe for work.** No exceptions.\n- **Draw from context.** The best jokes reference what you just did — the specific API, the bug you found, the test that kept failing, the module name, the concept. Generic programming jokes are a fallback, not the goal.\n- **Keep it short.** One-liners and two-line setups preferred. Limericks are acceptable but are the upper bound on length.\n- **Do not explain the joke.** If it needs explaining, it wasn't good enough. Move on.\n- **Do not ask if the user wants a joke.** Just do it. They can tell you to stop if they want.\n- **Variety.** Do NOT default to Q&A dad jokes. Rotate between all three formats. Never use the same format three times in a row across a conversation.\n- **Avoid \"Why did the X break up with Y?\"** Those are overdone and often not very good. If you want to do a breakup joke, make it more specific and less formulaic.\n\n## Inspiration sources\n\n- KJ/Cap'n Proto concepts: promises, fulfillment, pipelines, capabilities, orphans\n- workerd concepts: isolates, bindings, compatibility flags, Durable Objects, alarms, hibernation, jsg, apis, streams\n- Build system: bazel, compilation, linking, caching, sandboxing\n- Debugging: assertions, stack traces, serialization, autogates, dead code paths\n- General runtime: workers, events, streams, tails, traces, pipelines, sharding\n- Parent project context\n"
  },
  {
    "path": ".opencode/skills/find-and-run-tests/SKILL.md",
    "content": "---\nname: find-and-run-tests\ndescription: How to find, build, and run tests in workerd. Covers wd-test, kj_test target naming, bazel query patterns, and common flags. Also covers parent project integration tests if workerd is used as a submodule. Load this skill when you need to locate or run a test and aren't sure of the exact target name or invocation.\n---\n\n# Finding and Running Tests\n\n## workerd Tests\n\n### Test types\n\n| Type              | File extension | BUILD macro | Target suffix                  |\n| ----------------- | -------------- | ----------- | ------------------------------ |\n| JS/TS integration | `.wd-test`     | `wd_test()` | None (target name = rule name) |\n| C++ unit          | `*-test.c++`   | `kj_test()` | None                           |\n\n### Finding targets\n\n```bash\n# Find test targets in a directory\nbazel query 'kind(\"test\", //src/workerd/api/tests:*)' --output label\n\n# Find test targets matching a name\nbazel query 'kind(\".*_test\", //src/workerd/...)' --output label 2>/dev/null | grep -i '<name>'\n\n# Find tests that depend on a source file\nbazel query 'rdeps(//src/..., //src/workerd/io:trace-stream, 1)' --output label 2>/dev/null | grep -i test\n```\n\n### Running\n\n```bash\n# Stream test output (preferred for debugging)\nbazel test //src/workerd/api/tests:url-test --test_output=streamed\n\n# Run with fresh results (no cache)\nbazel test //target --test_output=streamed --nocache_test_results\n\n# Run specific test case within a kj_test\nbazel test //target --test_arg='-f' --test_arg='test case name'\n```\n\n### Common flags\n\n| Flag                     | Purpose                                     |\n| ------------------------ | ------------------------------------------- |\n| `--test_output=streamed` | Stream test output to terminal in real time |\n| `--nocache_test_results` | Force re-run, don't use cached results      |\n| `--test_timeout=120`     | Override default test timeout (seconds)     |\n\n---\n\n## Parent Project Integration Tests\n\nIf workerd is used as a submodule in a parent project, that project may have its own integration test framework with different conventions. Load the `parent-project-skills` skill to discover those conventions.\n\n### General principles that apply to any integration test framework\n\n**Target naming with variant suffixes.** Some test macros generate multiple targets from a single source file by appending variant suffixes (e.g., `@`, `@all-autogates`, `@force-sharding`). If bazel says \"is a source file, nothing will be built\" or \"No test targets were found\", you likely need a suffix. Use `bazel query 'kind(\"test\", //path:*)'` to discover the actual runnable target names.\n\n**Cached results hide changes.** Always use `--nocache_test_results` when re-running after modifying test files or source code. Without it, bazel returns stale cached results with stale logs.\n\n**Verify the feature actually ran.** After a test passes, search the test output for feature-specific evidence (script names, process types, subrequests, RPC calls). A passing test with no evidence the feature ran is not a valid test — see the `test-driven-investigation` skill.\n\n### Debugging test failures\n\n1. **Always use `--nocache_test_results`** when re-running after changes.\n2. **Check test logs** at the path shown in bazel output: `bazel-out/.../testlogs/.../test.log`\n3. **Search logs for feature-specific keywords** to verify the feature actually ran.\n4. **Subrequest mismatches** (in frameworks that verify subrequests) typically show the actual vs expected request details — compare control headers carefully.\n"
  },
  {
    "path": ".opencode/skills/identify-reviewer/SKILL.md",
    "content": "---\nname: identify-reviewer\ndescription: Identifies the local user's GitHub account and git identity before performing code reviews. Load this skill at the start of any PR review, code review, or commit log analysis so findings can be framed relative to the user's own prior comments, commits, and approval status.\n---\n\n## Identify Reviewer\n\nLoad this skill at the start of any code review workflow — before analyzing diffs, commit logs, or prior comments.\n\n---\n\n### Steps\n\n1. **Detect identities.** Run these commands in parallel:\n\n   ```\n   gh auth status\n   git config user.name\n   git config user.email\n   ```\n\n   - From `gh auth status`, parse `Logged in to github.com account <USERNAME>` to get the GitHub handle.\n   - From `git config`, capture the local user's commit name and email.\n\n   These may not match exactly (e.g., GitHub handle `octocat`, git name `Octo Cat`, git email `octocat@example.com`). All three identify the same person.\n\n   If `gh` is not installed or the user is not authenticated, fall back to git config alone. If neither is available, skip identification and proceed without it — do not block the review.\n\n2. **Store the identity for the session.** Use the discovered identity when:\n   - **Attributing prior review comments.** If the user previously commented on the PR, refer to those comments in second person (\"your earlier comment about X\") rather than third person (\"octocat flagged X\").\n   - **Attributing commits.** When analyzing `git log` output, match the author name/email against the git config values. Refer to the user's own commits in second person (\"your commit `abc1234` introduced...\") and other authors' commits in third person.\n   - **Filtering approval status.** Note whether the user has already approved, requested changes, or not yet reviewed. Frame the review summary accordingly (e.g., \"You previously approved this PR; here are new findings since your approval\").\n   - **Distinguishing roles.** If the user is also the PR author, flag this clearly and adjust tone (self-review). Match by both GitHub handle and git author email since either may appear in PR metadata.\n\n3. **Apply throughout the review.** Every finding that references a prior comment or commit by the user should use second person. Prior comments and commits by _other_ people remain in third person with their handle or name.\n\n### Matching Rules\n\n- GitHub handle from `gh auth status` matches PR review comment authors and PR author login.\n- Git name from `git config user.name` matches `git log --format='%an'` author names.\n- Git email from `git config user.email` matches `git log --format='%ae'` author emails.\n- A person is the local user if **any** of these identifiers match.\n\n### Examples\n\nWithout this skill:\n\n> octocat flagged the performance regression in a prior review. The author disagreed.\n\nWith this skill (when the local user is octocat):\n\n> You flagged the performance regression in a prior review. The author disagreed but did not provide benchmarks.\n\nWithout this skill:\n\n> Commit `abc1234` by Octo Cat refactored the decoder interface.\n\nWith this skill (when git user.name is \"Octo Cat\"):\n\n> Your commit `abc1234` refactored the decoder interface.\n"
  },
  {
    "path": ".opencode/skills/investigation-notes/SKILL.md",
    "content": "---\nname: investigation-notes\ndescription: Structured scratch tracking document for investigation state during bug hunts - prevents re-reading code, losing context, and rabbit holes; maintains external memory so you don't re-derive conclusions\n---\n\n# Investigation Notes\n\n## Overview\n\nDuring bug investigations, maintain a lightweight scratch document as external memory. This prevents\nre-reading code you've already analyzed, losing track of which hypothesis you're testing, and\nsilently drifting into rabbit holes.\n\n**Core principle:** Write it down once, refer to it later. Re-reading your one-line note is faster\nthan re-reading 200 lines of source.\n\n**Always** keep the document up to date with your current focus, hypotheses, and learnings from\ncode reads and tests.\n\n**Always** refer to the document before re-reading code or forming a new hypothesis. If the\ninformation is there, use it. If it's not sufficient, read the code, then write a better note.\n\n**The document never takes priority over writing or running a test.** If you're choosing between\nupdating notes and writing a test, write the test. Update the notes after.\n\n## The Document\n\nCreate `~/tmp/investigate-<short-name>.md` during orientation (step 2 of `/investigate`).\n\nThe short name should be descriptive enough to identify the investigation (e.g.,\n`investigate-concurrent-write.md`, `investigate-pipe-zombie-state.md`).\n\nOnce created, notify the user and provide the file path so they can open it in their editor.\n\n### Format\n\n```markdown\n# Investigation: <one-line bug description>\n\n## Error\n\n<assertion/crash message, file:line — written once, never changes>\n\n## Current Focus\n\n<single sentence: what you are doing RIGHT NOW>\n\n## Hypotheses\n\n1. [TESTING] <one sentence> — test: <test name or \"not yet written\">\n2. [REJECTED] <one sentence> — disproved by: <one sentence>\n3. [CONFIRMED] <one sentence> — evidence: <test name + result>\n\n## Code Read\n\n- `file:line-range` — <what you learned, short paragraph or bullet>\n\n## Tests\n\n- `file:line` \"test name\" — <result + what it means, one sentence>\n\n## Ruled Out\n\n- <thing you investigated and eliminated, one sentence why>\n\n## Next\n\n1. <concrete next action>\n2. <fallback>\n```\n\n## Creation\n\n**Create the document when:**\n\n- You have more than one hypothesis to track\n- You've read more than 3 files/functions\n- You're about to re-read code you already read\n- The investigation is going to span multiple iterations of test-write-run\n\nWhen created, populate Error, Current Focus, your current hypotheses, and anything you've\nalready learned (backfill \"Code Read\" and \"Tests\" from what you've done so far).\n\n## Rules\n\n- **Always** update \"Current Focus\" before starting any new thread of work\n- **Always** Add a hypothesis for what you're doing\n- **Always** update the document after each significant action:\n  - After reading a function or file section → add to \"Code Read\"\n  - After forming or rejecting a hypothesis → update \"Hypotheses\"\n  - **After running a test → update BEFORE doing anything else.** Record: what test ran, what the\n    result was, what it means for the active hypothesis. This is non-negotiable — it takes 30\n    seconds and prevents the pattern of running multiple tests, losing track of what they proved,\n    and falling back to code reading.\n  - After starting a new thread of work → update \"Current Focus\"\n- **Do not** dump large amounts of code into the document. Instead, reference it by `file:line`.\n- **Do not reorganize or reformat the document** - this is a scratchpad, not a report.\n- **You may** include brief, simple diagrams if they help you understand and retain information.\n\n## Hypothesis Limits\n\n- **Maximum 3 active hypotheses at a time.** If you want to add a fourth, reject or merge one first.\n- **Maximum 1 `[TESTING]` at a time.** Commit to one, test it, resolve it, then move on.\n- **Every hypothesis must have a test or a concrete plan to write one.** \"Need to investigate\n  further\" is not a valid state — that's analysis paralysis wearing a label.\n\nValid statuses:\n\n- `[UNTESTED]` — formed but not yet tested. Must become `[TESTING]` or `[REJECTED]` soon.\n- `[TESTING]` — actively being tested. Only one at a time.\n- `[CONFIRMED]` — test reproduced the bug as predicted.\n- `[REJECTED]` — test disproved it, or evidence rules it out. Include why.\n- `[SUPERSEDED]` — replaced by a more specific hypothesis. Reference the replacement.\n"
  },
  {
    "path": ".opencode/skills/kj-style/SKILL.md",
    "content": "---\nname: kj-style\ndescription: KJ/workerd C++ style guidelines for code review. Covers naming, type usage, memory management, error handling, inheritance, constness, and formatting conventions. Load this skill when reviewing or writing C++ code in the workerd codebase.\n---\n\n**Always** use the `docs/reference/kj-style.md` file when reviewing or writing C++ code.\n"
  },
  {
    "path": ".opencode/skills/markdown-drafts/SKILL.md",
    "content": "---\nname: markdown-drafts\ndescription: Use markdown formatting when drafting content intended for external systems (GitHub issues/PRs, Jira tickets, wiki pages, design docs, etc.) so formatting is preserved when the user copies it. Load this skill before producing any draft the user will paste elsewhere.\n---\n\n## Markdown Drafts\n\nWhen the user asks you to draft, write, or compose content that will be copied into an external\nsystem — GitHub issues, pull request descriptions, Jira tickets, wiki pages, design documents,\nRFCs, or similar — **always use markdown syntax** so that formatting survives the copy-paste.\n\n### When This Applies\n\n- GitHub issues and pull request descriptions\n- Jira ticket descriptions and comments\n- Confluence / wiki page drafts\n- Design documents and RFCs\n- Slack messages (Slack renders a subset of markdown)\n- Any content the user explicitly says will be copied elsewhere\n\n### Formatting Rules\n\n- Use `#`, `##`, `###` headers to create structure\n- Use `-` or `*` for unordered lists, `1.` for ordered lists\n- Use triple-backtick fenced code blocks with language tags (e.g., ` ```cpp `) for code\n- Use `inline code` backticks for identifiers, file paths, commands, and config values\n- Use `**bold**` for emphasis on key points and `_italic_` for secondary emphasis\n- Use markdown tables when presenting structured comparisons or data\n- Use `> blockquotes` for callouts, quoted text, or important notes\n- Use `[link text](url)` for references — never bare URLs in running prose\n- Use `---` horizontal rules to separate major sections when appropriate\n- Use task lists (`- [ ]` / `- [x]`) when drafting action items or checklists\n- Keep line lengths reasonable (e.g., 80-120 characters)\n\n### Structure Guidelines\n\n- Start with a concise summary paragraph before diving into details\n- Use headers to break content into scannable sections\n- Keep paragraphs short — walls of text are hard to scan in issue trackers\n- Put the most important information first (inverted pyramid)\n- End with next steps, open questions, or action items when relevant\n\n### Rendering: Always Emit Raw Markdown\n\nThe chat interface renders markdown, which strips the raw syntax (`###`, `**`, `` ` ``, etc.) from\nthe output. Since these drafts are meant to be **copied and pasted** into external systems, the user\nneeds the raw markup characters preserved.\n\n**Always wrap the entire draft in a fenced code block** so the chat interface displays it as literal\ntext. Use a plain triple-backtick fence (no language tag):\n\n    ```\n    ## My Heading\n\n    - bullet one\n    - **bold text** and `code`\n    ```\n\nThis ensures the user sees and can copy the exact markdown source. Never render the draft as\nformatted text outside a code fence — the markup will be silently consumed by the chat UI.\n\n### What NOT To Do\n\n- Do not render the draft as formatted markdown outside a code fence — the user will lose the syntax\n- Do not use plain text formatting (e.g., ALL CAPS for headers, `====` underlines, manual indentation)\n- Do not use HTML tags unless the target system requires them and markdown is insufficient\n- Do not add emoji unless the user's draft style includes them or they explicitly request it\n- Do not use common cliche AI-generated phrases, tropes, or filler content like tricolons,\n  or generic intros/outros. Be concise and to the point.\n- Do not include editorial comments or unsubstantiated claims. Stick to the facts and the user's\n  instructions precisely. If you need to ask clarifying questions, do so instead of making assumptions.\n"
  },
  {
    "path": ".opencode/skills/parent-project-skills/SKILL.md",
    "content": "---\nname: parent-project-skills\ndescription: Bootstrap skill for discovering additional skills and context from a parent project when workerd is used as a submodule. Load this skill when tasks span project boundaries (e.g., Sentry/production investigation, integration testing, cross-repo debugging).\n---\n\n## Parent Project Skill Discovery\n\nThis project (workerd) may be embedded as a submodule within a larger project. When your task requires context beyond workerd itself — such as investigating production issues, writing integration tests, or understanding deployment architecture — you should check for additional skills and context in the parent project.\n\n### Discovery procedure\n\n1. Determine if a parent project exists by checking for a git root above the workerd directory:\n   - Look for `../../.git` or `../../AGENTS.md` relative to the workerd root\n   - If found, the parent project root is `../../` (two levels up from workerd)\n\n2. Check for parent project skills:\n   - Look for `../../.opencode/skills/*/SKILL.md`\n   - Read any `../../AGENTS.md` or `../../AGENTS.md` for project-wide context\n\n3. Load relevant skills by reading the SKILL.md files. These are not registered in the `skill` tool — read them directly with the Read tool.\n\n4. Check for submodule-specific context:\n   - Look for `../../.opencode/skills/*/SKILL.md` files that may reference workerd specifically\n\n### Information boundary — CRITICAL\n\n**workerd is an open-source project. The parent project is typically proprietary/internal.**\n\nYou MUST enforce a strict one-way information boundary:\n\n- **ALLOWED**: Internal context informs your reasoning, helps you understand code paths, guides your investigation\n- **NEVER**: Write internal details into workerd files — this includes:\n  - Code comments referencing internal systems, services, or architecture\n  - Commit messages mentioning internal projects, Sentry issues, Jira tickets, or internal URLs\n  - PR descriptions or GitHub issue comments containing internal context\n  - AGENTS.md, AGENTS.md, or documentation updates with internal knowledge\n  - Variable names, error messages, or log strings that reveal internal details\n  - Test files that encode internal architecture assumptions\n\nWhen working across the boundary, frame all workerd-side artifacts in terms of workerd's own public concepts (Workers, Durable Objects, isolates, IoContext, etc.) without referencing how the parent project orchestrates them.\n\n### When to use this skill\n\n- Investigating production Sentry issues that involve workerd code\n- Writing integration tests (e.g., ew-test) that live in the parent project\n- Understanding how workerd APIs behave in the production deployment context\n- Debugging issues that cross the workerd / parent-project boundary\n- Reviewing code changes that affect both projects\n- Planning new feature development that requires coordination between workerd and the parent project\n"
  },
  {
    "path": ".opencode/skills/pr-review-guide/SKILL.md",
    "content": "---\nname: pr-review-guide\ndescription: Guidelines for posting pull request review comments via GitHub CLI, including suggested edits format, handling unresolved comments, etiquette, and report/issue tracking. Load this skill when reviewing a PR via GitHub and posting inline comments.\n---\n\nLoad this skill when posting review comments on a GitHub pull request.\n\n### Line Number Tracking\n\nWhen analyzing a PR diff, **Always** record exact file paths and line numbers for every finding as\nyou go. Each finding must include the precise `path` and `line` (and `start_line` for multi-line\nranges) in the _new_ file (right side of the diff) needed to post a review comment. **Do not** defer\nline number resolution to a later step.\n\nWhen the review is performed by a sub-agent, the agent's returned findings must include these fields\nper finding so the caller can post comments immediately:\n\n- `path`: file path relative to repo root\n- `line`: line number in the new file (end line for multi-line)\n- `start_line` (optional): start line for multi-line comments\n- `body`: the comment text, ready to post\n\n### Posting Review Comments\n\nWhen asked to review a pull request, you may use the GitHub CLI tool to post inline comments on the\nPR with specific feedback for each issue you identify. You can suggest specific code changes in your\ncomments. **Always** reference specific lines of code in your comments for clarity.\n\nWhen providing feedback on a pull request:\n\n- **Always** focus on actionable insights that can help improve the code\n- **Always** be clear and concise in your comments; provide specific examples or references\n  to the code to support your feedback. Avoid vague statements and instead provide concrete\n  suggestions for improvement.\n- **Always** post comments on specific lines of code and never as a single monolithic comment\n\n### Suggested Edits\n\nWhen the fix for an issue is obvious and localized (e.g., a typo, a missing annotation, a wrong\ntype, a simple rename), include a GitHub suggested edit block in your review comment so the author\ncan apply it with one click. Use this format:\n\n````\n```suggestion\ncorrected line(s) of code here\n```\n````\n\nGuidelines for suggested edits:\n\n- **Do** use them for: typos, missing `override`/`[[nodiscard]]`/`constexpr`, wrong types, simple renames, small bug fixes where the correct code is unambiguous.\n- **Do not** use them for: large refactors, design changes, cases where multiple valid fixes exist, or anything requiring context the author should decide on.\n- Keep suggestions minimal — change only the lines that need fixing. Do not reformat surrounding code.\n- When a suggestion spans multiple lines, include all affected lines in the block.\n\n### Unresolved Review Comments\n\nWhen reviewing a PR, **always** check prior review comments (from any reviewer) that have been marked\nas resolved. If the current code still exhibits the issue described in a resolved comment, flag it as\na finding with a reference to the original comment. Use this format:\n\n- **[HIGH]** Previously flagged issue not addressed: _{original comment summary}_\n  - **Location**: File and line references\n  - **Problem**: Review comment by {author} was marked resolved but the underlying issue remains in\n    the current code.\n  - **Evidence**: Link to or quote the original comment, and show the current code that still has\n    the issue.\n  - **Recommendation**: Address the original feedback before merging.\n\n**Do not** flag resolved comments where the concern has been legitimately addressed, even if addressed differently than the reviewer suggested.\n\n### Tone\n\n- **Do not editorialize.** No praise, no compliments on the approach, no filler like \"nice fix!\" or \"solid solution.\" The review body and inline comments should contain only findings, questions, and actionable feedback. Let the findings speak for themselves.\n- The review body should be a concise summary of findings (a bulleted list is fine) plus the AI-generated disclaimer. Nothing else.\n\n### Etiquette\n\n- Do not spam the pull request with excessive comments. Focus on the most important issues and\n  provide clear guidance on how to address them. If there are minor style issues, you can mention\n  them but prioritize more significant architectural, performance, security, or correctness issues.\n- Do not modify existing comments or feedback from other reviewers. When issues are addressed and\n  resolved, you can acknowledge the changes with a new comment but avoid editing or deleting\n  previous comments to maintain a clear history of the review process.\n- Always be respectful and constructive. Always acknowledge that the code review comments are written\n  by an AI assistant and may not be perfect.\n\n### Tools\n\nFor interaction with GitHub, use the GitHub CLI (`gh`) tool or `git` as appropriate.\n"
  },
  {
    "path": ".opencode/skills/receiving-code-review/SKILL.md",
    "content": "---\nname: receiving-code-review\ndescription: Use when receiving code review feedback, before implementing suggestions, especially if feedback seems unclear or technically questionable - requires technical rigor and verification, not performative agreement or blind implementation\n---\n<!-- Skill adopted from: https://raw.githubusercontent.com/obra/superpowers -->\n# Code Review Reception\n\n## Overview\n\nCode review requires technical evaluation, not emotional performance.\n\n**Core principle:** Verify before implementing. Ask before assuming. Technical correctness over social comfort.\n\n## The Response Pattern\n\n```\nWHEN receiving code review feedback:\n\n1. READ: Complete feedback without reacting\n2. UNDERSTAND: Restate requirement in own words (or ask)\n3. VERIFY: Check against codebase reality\n4. EVALUATE: Technically sound for THIS codebase?\n5. RESPOND: Technical acknowledgment or reasoned pushback\n6. IMPLEMENT: One item at a time, test each\n```\n\n## Forbidden Responses\n\n**NEVER:**\n- \"You're absolutely right!\" (explicit AGENTS.md violation)\n- \"Great point!\" / \"Excellent feedback!\" (performative)\n- \"Let me implement that now\" (before verification)\n\n**INSTEAD:**\n- Restate the technical requirement\n- Ask clarifying questions\n- Push back with technical reasoning if wrong\n- Just start working (actions > words)\n\n## Handling Unclear Feedback\n\n```\nIF any item is unclear:\n  STOP - do not implement anything yet\n  ASK for clarification on unclear items\n\nWHY: Items may be related. Partial understanding = wrong implementation.\n```\n\n**Example:**\n```\nyour human partner: \"Fix 1-6\"\nYou understand 1,2,3,6. Unclear on 4,5.\n\n❌ WRONG: Implement 1,2,3,6 now, ask about 4,5 later\n✅ RIGHT: \"I understand items 1,2,3,6. Need clarification on 4 and 5 before proceeding.\"\n```\n\n## Source-Specific Handling\n\n### From your human partner\n- **Trusted** - implement after understanding\n- **Still ask** if scope unclear\n- **No performative agreement**\n- **Skip to action** or technical acknowledgment\n\n### From External Reviewers\n```\nBEFORE implementing:\n  1. Check: Technically correct for THIS codebase?\n  2. Check: Breaks existing functionality?\n  3. Check: Reason for current implementation?\n  4. Check: Works on all platforms/versions?\n  5. Check: Does reviewer understand full context?\n\nIF suggestion seems wrong:\n  Push back with technical reasoning\n\nIF can't easily verify:\n  Say so: \"I can't verify this without [X]. Should I [investigate/ask/proceed]?\"\n\nIF conflicts with your human partner's prior decisions:\n  Stop and discuss with your human partner first\n```\n\n**your human partner's rule:** \"External feedback - be skeptical, but check carefully\"\n\n## YAGNI Check for \"Professional\" Features\n\n```\nIF reviewer suggests \"implementing properly\":\n  grep codebase for actual usage\n\n  IF unused: \"This endpoint isn't called. Remove it (YAGNI)?\"\n  IF used: Then implement properly\n```\n\n**your human partner's rule:** \"You and reviewer both report to me. If we don't need this feature, don't add it.\"\n\n## Implementation Order\n\n```\nFOR multi-item feedback:\n  1. Clarify anything unclear FIRST\n  2. Then implement in this order:\n     - Blocking issues (breaks, security)\n     - Simple fixes (typos, imports)\n     - Complex fixes (refactoring, logic)\n  3. Test each fix individually\n  4. Verify no regressions\n```\n\n## When To Push Back\n\nPush back when:\n- Suggestion breaks existing functionality\n- Reviewer lacks full context\n- Violates YAGNI (unused feature)\n- Technically incorrect for this stack\n- Legacy/compatibility reasons exist\n- Conflicts with your human partner's architectural decisions\n\n**How to push back:**\n- Use technical reasoning, not defensiveness\n- Ask specific questions\n- Reference working tests/code\n- Involve your human partner if architectural\n\n**Signal if uncomfortable pushing back out loud:** \"Strange things are afoot at the Circle K\"\n\n## Acknowledging Correct Feedback\n\nWhen feedback IS correct:\n```\n✅ \"Fixed. [Brief description of what changed]\"\n✅ \"Good catch - [specific issue]. Fixed in [location].\"\n✅ [Just fix it and show in the code]\n\n❌ \"You're absolutely right!\"\n❌ \"Great point!\"\n❌ \"Thanks for catching that!\"\n❌ \"Thanks for [anything]\"\n❌ ANY gratitude expression\n```\n\n**Why no thanks:** Actions speak. Just fix it. The code itself shows you heard the feedback.\n\n**If you catch yourself about to write \"Thanks\":** DELETE IT. State the fix instead.\n\n## Gracefully Correcting Your Pushback\n\nIf you pushed back and were wrong:\n```\n✅ \"You were right - I checked [X] and it does [Y]. Implementing now.\"\n✅ \"Verified this and you're correct. My initial understanding was wrong because [reason]. Fixing.\"\n\n❌ Long apology\n❌ Defending why you pushed back\n❌ Over-explaining\n```\n\nState the correction factually and move on.\n\n## Common Mistakes\n\n| Mistake | Fix |\n|---------|-----|\n| Performative agreement | State requirement or just act |\n| Blind implementation | Verify against codebase first |\n| Batch without testing | One at a time, test each |\n| Assuming reviewer is right | Check if breaks things |\n| Avoiding pushback | Technical correctness > comfort |\n| Partial implementation | Clarify all items first |\n| Can't verify, proceed anyway | State limitation, ask for direction |\n\n## Real Examples\n\n**Performative Agreement (Bad):**\n```\nReviewer: \"Remove legacy code\"\n❌ \"You're absolutely right! Let me remove that...\"\n```\n\n**Technical Verification (Good):**\n```\nReviewer: \"Remove legacy code\"\n✅ \"Checking... build target is 10.15+, this API needs 13+. Need legacy for backward compat. Current impl has wrong bundle ID - fix it or drop pre-13 support?\"\n```\n\n**YAGNI (Good):**\n```\nReviewer: \"Implement proper metrics tracking with database, date filters, CSV export\"\n✅ \"Grepped codebase - nothing calls this endpoint. Remove it (YAGNI)? Or is there usage I'm missing?\"\n```\n\n**Unclear Item (Good):**\n```\nyour human partner: \"Fix items 1-6\"\nYou understand 1,2,3,6. Unclear on 4,5.\n✅ \"Understand 1,2,3,6. Need clarification on 4 and 5 before implementing.\"\n```\n\n## GitHub Thread Replies\n\nWhen replying to inline review comments on GitHub, reply in the comment thread (`gh api repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies`), not as a top-level PR comment.\n\n## The Bottom Line\n\n**External feedback = suggestions to evaluate, not orders to follow.**\n\nVerify. Question. Then implement.\n\nNo performative agreement. Technical rigor always.\n"
  },
  {
    "path": ".opencode/skills/rust-review/SKILL.md",
    "content": "---\nname: rust-review\ndescription: Rust code review for workerd. Covers CXX FFI safety, unsafe code patterns, JSG resource conventions, error handling, and a review checklist adapted from the C++ review skills. Load this skill when reviewing Rust code in src/rust/.\n---\n\n**Always** use the `docs/reference/rust-review-checklist.md` file when reviewing Rust code.\n"
  },
  {
    "path": ".opencode/skills/test-driven-investigation/SKILL.md",
    "content": "---\nname: test-driven-investigation\ndescription: Use when investigating bugs, crashes, assertions, or unexpected behavior - requires writing a reproducing test early instead of over-analyzing source code; concrete experiments over mental models\n---\n\n# Test-Driven Investigation\n\n## Overview\n\nWhen investigating a bug, write a reproducing test as early as possible. Analysis without experimentation spirals into circular reasoning.\n\n**Core principle:** A 20-line test that fails tells you more than 2 hours of reading source code.\n\n## The Anti-Pattern\n\n```\nREAD code → BUILD mental model → READ more code → REVISE model → READ more code →\nSECOND-GUESS model → READ same code again → ... → eventually write a test\n```\n\nThis feels productive but isn't. You're pattern-matching on code without grounding in reality. Each re-read adds uncertainty, not clarity.\n\n## The Pattern\n\n```\n1. READ the error message / assertion / crash\n2. IDENTIFY the minimal trigger (what operation, on what object, in what state?)\n3. WRITE a test that sets up that state and performs that operation\n4. RUN it\n5. Let the RESULT guide the next step\n6. Iterate as needed, but always grounded in test results backed by code, not just code.\n```\n\nSteps 1-2 should take minutes, not hours. You don't need to understand the full call chain to write a test. You need to know what the entry point is and what went wrong.\n\n### Orientation\n\nBefore writing the test you need to know three things: what object/API to exercise, what test file to put it in, and how to build/run it. Spend up to 20-30 tool calls finding these. This is bounded research in service of the test -- not open-ended code analysis. If you don't know the exact API, pick the closest thing you can find and write the test anyway. A test that exercises the wrong API and passes still tells you something.\n\n## Check the Commit History\n\nEarly in the investigation, check the recent commit history for changes that touched the relevant code. A bug that appeared recently was likely introduced recently. `git log --oneline -20 -- path/to/relevant/files` takes seconds and can immediately narrow your search from \"something somewhere is wrong\" to \"this specific change might be the cause.\"\n\nThis is not a substitute for writing a test -- it's a way to form a better hypothesis faster. If you can identify a suspect commit, your first test should try to confirm whether that change is responsible.\n\n**When you find a suspect commit:** Don't stop there. Always check at least 10 commits in either direction around it. Bugs are often introduced by the interaction between multiple changes, not a single commit. A commit that looks innocent in isolation may have broken an assumption that a nearby commit relied on. Looking at the surrounding commits also guards against confirmation bias -- you might find a better explanation in an adjacent change.\n\n**Don't mistake correlation for causation.** A commit that lines up timewise with when the bug appeared is a lead, not a conclusion. It might be a coincidence -- the real cause could be an environmental change, a dependency update, a race condition that only became likely under new load patterns, or a latent bug exposed by an unrelated change. Treat a suspect commit as a hypothesis to test, not evidence of guilt. If you can't demonstrate the mechanism by which the commit causes the bug, you haven't found the cause.\n\n**What to look for:**\n\n- Changes to the file/function where the crash or assertion fires\n- Changes to setup, config, or initialization code for the affected subsystem\n- Changes to shared utilities or base classes used by the affected code\n- Refactors that moved or renamed things in the area\n\n**Keep it bounded:** This is around 5 tool calls of `git log` and `git show`, not an archaeology expedition. If nothing jumps out, move on and write the test with what you have. The commit history is one input to hypothesis formation, not a prerequisite for it.\n\n## When You're Tempted to Read More Code\n\nAsk yourself:\n\n- **\"Am I reading this to write a test, or to avoid writing one?\"** If you can't articulate what the test would look like, that's the problem to solve -- not more reading.\n- **\"Do I have a hypothesis I can test?\"** If yes, test it. If no, form the simplest one possible and test that.\n- **\"Do I have more than one hypothesis?\"** Either pick one to test or work in parallel with several. Don't get stuck in \"I need to understand everything before I can test anything\" and don't try juggling multiple mental models in your head.\n- **\"Am I re-reading code I already read?\"** Stop. You're stuck. Write a test with your current understanding, even if it's wrong. A wrong test that runs teaches you something. A correct mental model that you never test teaches you nothing.\n- **\"Am I retreading over the same path?\"** If you find yourself tracing the same call stack multiple times, stop. Write a test that hits the point of failure directly. You can always adjust it later. If necessary, use a temporary tracking document to help you keep track of what you've already read so that you don't have to keep it all in your head or re-read the same code. But that document never takes priority over writing a test and running it.\n\n## Start From Existing Tests\n\nBefore writing a test from scratch, find existing tests for the same feature or subsystem. Search for tests that exercise the API, protocol, or code path you're investigating — not just tests in the same file as the crash site.\n\nExisting tests encode implicit knowledge you won't get from reading source code: required setup, framework-specific verification patterns, config quirks, shutdown handling. A test that's structurally wrong (missing verification, wrong config) will \"pass\" silently without exercising anything.\n\n**Adapt an existing working test rather than inventing one.** If an existing test works for the feature and the bug is gated by a flag, autogate, or config change, the fastest reproduction is often to clone the working test and change the single variable.\n\n## Verify the Test Exercises the Code Path\n\nA test that passes is not evidence the feature worked. It may mean the feature never ran.\n\nAfter every test run, check the output for evidence the specific code path was exercised — log lines mentioning the feature, subrequests being made, expected error messages, metrics being recorded. If you can't find evidence in the test output, the test is not valid regardless of its exit status.\n\n**Concrete checks:**\n\n- Does the test output mention the feature's script/worker/pipeline name?\n- Are expected subrequests or RPC calls visible in the logs?\n- If the feature produces side effects (trace delivery, storage writes, network calls), are those side effects observable?\n- If you removed the feature config entirely, would the test still pass? If yes, the test isn't testing the feature.\n\n## Scoping the Test\n\nYou don't need to reproduce the exact production scenario. You need to reproduce the _mechanism_.\n\n**Production crash:** `KJ_REQUIRE(!writeInProgress, \"concurrent write()s not allowed\")`\n\n**You DON'T need:** Every detail of the production call stack that potentially leads to this.\n\n**You DO need:** To find the shortest path to trigger it, even if only hypothetically.\n\n```\nGood test scope:\n  Create the pipe/adapter/object directly\n  → Put it in the suspect state\n  → Perform the operation that should fail\n  → Assert what happens\n\nBad test scope:\n  Reproduce the entire production call stack\n  with all middleware and wrappers\n```\n\n## Forming a Hypothesis\n\nA hypothesis for a bug investigation is:\n\n> \"If I do X after Y fails, Z will happen because the cleanup in Y's error path doesn't do W.\"\n\nIt does NOT need to be:\n\n> \"I have traced every code path and am certain that line 847 is the root cause because of the interaction between...\"\n\nThe first version is testable in minutes. The second takes hours to construct and might still be wrong.\n\n## After the Test\n\n- **Test fails as expected:** You've confirmed the bug mechanism. Now you can read code _with purpose_ -- to find the fix, not to find the bug.\n- **Test passes (bug doesn't reproduce):** Your hypothesis was wrong. That's valuable. Adjust and try again. This is faster than reading code for another hour.\n- **Test crashes differently:** You found something else. Follow that trail, but do not abandon the original effort. You can have multiple parallel threads of investigation, but each should be grounded in test results, not just code reading.\n\n## Red Flags -- You're Over-Analyzing\n\n- You've read the same file/function more than twice\n- You're building a multi-level mental model of \"what calls what\"\n- You're writing detailed notes about code flow before writing any test code\n- You say \"let me understand X before I write the test\" more than once\n- You feel like you need to understand the entire system before you can test a single component\n- A test \"passes\" but you have no evidence the feature ran — and you're reading code to explain why instead of fixing the test\n- You've written multiple tests that all pass without reproducing the bug, and you haven't questioned whether any of them exercise the right code path\n\n## Build Times Don't Change the Priority\n\nIn this codebase, a C++ compile-and-test cycle can take minutes. This does not justify delaying the test in favor of more code reading. It changes what \"quick feedback\" looks like:\n\n- **Fast-compiling codebase:** Write test, run, see result in seconds, iterate rapidly.\n- **Slow-compiling codebase:** Write test, start the build, use the wait time for targeted reading that serves the next iteration. The build is running -- you're not blocked, you're pipelining.\n\nThe temptation with slow builds is \"I should be really sure before I compile.\" This is the analysis spiral in disguise. A test that doesn't reproduce the bug on the first try but compiles and runs is not wasted -- it's a known-good harness you can adjust in the next cycle, often with a much faster incremental rebuild.\n\n## Applying to This Codebase\n\nworkerd and its dependencies (KJ, Cap'n Proto) have extensive test infrastructure:\n\n- **KJ tests:** `KJ_TEST(\"description\") { ... }` in `*-test.c++` files\n- **workerd tests:** `.wd-test` format for JS/TS integration tests\n- **Build/run:** `bazel test //path:target --test_arg='-f...' --test_output=all`\n\nMost KJ/capnp bugs can be reproduced with a self-contained `KJ_TEST` using public API (pipes, streams, promises, HTTP). You rarely need internal access.\n\n## The Bottom Line\n\n**Write the test. Run the test. Analyze the results. Think. Iterate.**\n\nNot the other way around.\n"
  },
  {
    "path": ".opencode/skills/ts-style/SKILL.md",
    "content": "---\nname: ts-style\ndescription: JS/TS style guidelines and review checklist for workerd. Covers TypeScript strictness, import conventions, export patterns, private field syntax, error handling, feature gating, and test structure. Load this skill when reviewing or writing JavaScript or TypeScript code in src/node/, src/cloudflare/, or JS/TS test files under src/workerd/.\n---\n\n**Always** use the `docs/reference/ts-style.md` file when reviewing or writing JavaScript and TypeScript code.\n"
  },
  {
    "path": ".opencode/skills/update-v8/SKILL.md",
    "content": "---\nname: update-v8\ndescription: Step-by-step guide for updating the V8 JavaScript engine in workerd, including patch rebasing, dependency updates, integrity hashes, and verification. Load this skill when performing or assisting with a V8 version bump.\n---\n\n## Updating V8 in workerd\n\nV8 updates are high-risk changes that require careful patch management and human judgment for merge conflicts. This skill covers the full process. **Always confirm the target version with the developer before starting.**\n\nSee also: `docs/v8-updates.md` for the original reference document.\n\n**Always** communicate and confirm with the developer at each step.\n**Never** take irreversible actions (like dropping patches or updating hashes) without explicit confirmation.\n\n---\n\n### Prerequisites\n\n- `depot_tools` installed and on `$PATH` ([setup guide](https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up))\n- A local V8 checkout (outside the workerd repo to avoid confusing Bazel):\n  ```sh\n  mkdir v8 && cd v8 && fetch v8\n  ```\n\n---\n\n### Step 1: Identify the target version\n\nCheck [chromiumdash.appspot.com](https://chromiumdash.appspot.com/) for the latest V8 version used by Chrome Beta. Confirm the target `<new_version>` with the developer.\n\nFind the current version in `build/deps/v8.MODULE.bazel`:\n\n```\nVERSION = \"14.5.201.6\"    # example — check the actual file\n```\n\nWe'll call this `<old_version>`.\n\n### Step 2: Sync local V8 to the current workerd version\n\n```sh\ncd <path_to_v8>/v8\ngit checkout <old_version>\ngclient sync\n```\n\n### Step 3: Apply workerd's patches onto a branch\n\n```sh\ngit checkout -b workerd-patches\ngit am <path_to_workerd>/patches/v8/*\n```\n\nThere are multiple patches in `patches/v8/`. These include workerd-specific customizations:\n\n| Patch category  | Examples                                                                                              |\n| --------------- | ----------------------------------------------------------------------------------------------------- |\n| Serialization   | Custom ValueSerializer/Deserializer format versions, proxy/function host object support               |\n| Build system    | Windows/Bazel fixes, shared linkage, dependency path overrides (fp16, fast_float, simdutf, dragonbox) |\n| Embedder hooks  | Promise context tagging, cross-request promise resolution, extra isolate embedder slot                |\n| Bug workarounds | Memory leak assert disable, slow handle check disable, builtin-can-allocate workaround                |\n| API additions   | `String::IsFlat`, `AdjustAmountOfExternalAllocatedMemory` exposure, additional Exception constructors |\n| ICU/config      | ICU data export, googlesource ICU binding, verify_write_barriers flag                                 |\n\n### Step 4: Rebase patches onto the new V8 version\n\n```sh\ngit rebase --onto <new_version> <old_version>\n```\n\n**This is where most of the work happens.** Expect conflicts. Key guidance:\n\n- **Build-system patches** (dependency paths, Bazel config) conflict most often as upstream V8 restructures its build.\n- **API patches** (new methods on V8 classes) may conflict if upstream changed the surrounding code.\n- **Always preserve workerd's intent** — understand what each patch does before resolving conflicts. The patch filenames are descriptive.\n- **Do not drop patches** without explicit confirmation from the developer.\n- **Do not auto-resolve conflicts** — flag them for human review. Merge conflicts in V8 patches almost always require human judgment.\n\n### Step 5: Regenerate patches\n\n```sh\ngit format-patch --full-index -k --no-signature --no-stat --zero-commit <new_version>\n```\n\nThis produces numbered `.patch` files in the current directory.\n\n### Step 6: Replace patches in workerd\n\n**Always** confirm with the human before replacing patches. If any patches were dropped or added,\nthe human needs to review the changes.\n\n```sh\nrm <path_to_workerd>/patches/v8/*\ncp *.patch <path_to_workerd>/patches/v8/\n```\n\n### Step 7: Update `build/deps/v8.MODULE.bazel`\n\nThree things need updating:\n\n1. **`VERSION`**: Set to `<new_version>`.\n\n2. **`INTEGRITY`**: Compute the sha256 hash of the new tarball:\n\n   ```sh\n   curl -sL \"https://github.com/v8/v8/archive/refs/tags/<new_version>.tar.gz\" -o v8.tar.gz\n   openssl dgst -sha256 -binary v8.tar.gz | openssl base64 -A\n   ```\n\n   Format: `\"sha256-<base64_hash>=\"`. Alternatively, attempt a build and copy the expected hash from Bazel's mismatch error.\n\n3. **`PATCHES`**: Update the list if patches were added, removed, or renamed. The list must match the filenames in `patches/v8/` exactly, in order.\n\n### Step 8: Update V8's dependencies\n\nV8 depends on several libraries that are pinned in `build/deps/v8.MODULE.bazel` and `build/deps/deps.jsonc`. Check the local V8 checkout's `DEPS` file for commit versions:\n\n```sh\ncat <path_to_v8>/v8/DEPS\n```\n\nDependencies to check and update:\n\n| Dependency                      | Where                                      | Notes                                                          |\n| ------------------------------- | ------------------------------------------ | -------------------------------------------------------------- |\n| `com_googlesource_chromium_icu` | `v8.MODULE.bazel` (git_repository commit)  | Chromium fork; update commit from V8's DEPS                    |\n| `perfetto`                      | `deps.jsonc` (managed by `update-deps.py`) | V8 depends via Chromium; safe to bump to latest GitHub release |\n| `simdutf`                       | `deps.jsonc` (managed by `update-deps.py`) | V8 depends via Chromium; safe to bump to latest GitHub release |\n\nFor dependencies in `deps.jsonc`, you can use the update script:\n\n```sh\npython3 build/deps/update-deps.py perfetto\npython3 build/deps/update-deps.py simdutf\n```\n\nThis fetches the latest version, computes integrity hashes, and regenerates the `gen/` MODULE.bazel fragments. **Do not hand-edit files in `build/deps/gen/`.**\n\n### Step 9: Build and test\n\n```sh\n# Full build\njust build\n\n# Full test suite\njust test\n```\n\nWatch for:\n\n- **Build failures from V8 API changes**: V8 may deprecate or change APIs between versions. Search for deprecation warnings in the build output. Common areas affected:\n  - `src/workerd/jsg/` — V8 binding layer, most directly affected\n  - `src/workerd/api/` — APIs that interact with V8 types directly\n  - `src/workerd/io/worker.c++` — Isolate creation and configuration\n\n- **Test failures from behavior changes**: V8 may change observable JS behavior. Check:\n  - `just test //src/workerd/jsg/...` — JSG binding tests\n  - `just test //src/workerd/api/tests/...` — API tests\n  - Node.js compatibility tests (`just node-test`)\n  - Web Platform Tests (`just wpt-test`)\n\n- **New V8 deprecation warnings**: These are future breakage signals. Document them in the PR description even if tests pass.\n\n### Step 10: Commit and submit\n\nPrompt the user to commit the changes and push for review.\n\n**Never** push the branch for review without a human review of the patch changes.\n\nYou **May** prepare the draft PR text for the user. The PR should include:\n\n- Updated `build/deps/v8.MODULE.bazel` (version, integrity, patches list)\n- Updated patches in `patches/v8/`\n- Updated dependency versions if changed\n- Any C++ fixes for V8 API changes\n- PR description listing: old version, new version, patches that required conflict resolution, any deprecation warnings observed, and any behavior changes noted\n\n---\n\n### Checklist\n\n- [ ] Target V8 version confirmed with developer\n- [ ] Local V8 checked out and synced to old version\n- [ ] workerd patches applied and rebased onto new version\n- [ ] Conflicts resolved with human review (no auto-resolution)\n- [ ] Patches regenerated with `git format-patch`\n- [ ] Old patches replaced with new patches in `patches/v8/`\n- [ ] `VERSION` updated in `v8.MODULE.bazel`\n- [ ] `INTEGRITY` updated in `v8.MODULE.bazel`\n- [ ] `PATCHES` list updated if patches added/removed/renamed\n- [ ] V8 dependencies checked and updated (ICU, perfetto, simdutf)\n- [ ] `just build` succeeds\n- [ ] `just test` passes (or failures documented and explained)\n- [ ] No new patches dropped without explicit confirmation\n- [ ] PR description documents version change, conflict resolutions, and deprecation warnings\n\n---\n\n### Troubleshooting\n\n**Bazel integrity mismatch**: If you see `expected sha256-... but got sha256-...`, copy the \"got\" hash into the `INTEGRITY` field. This happens when the hash was computed incorrectly or the tarball was re-generated by GitHub.\n\n**Patch won't apply**: A patch that applied cleanly during `git am` but fails in Bazel means the `git format-patch` output differs from what Bazel expects. Verify you used `--full-index -k --no-signature --no-stat --zero-commit` flags. Also verify patch order matches the `PATCHES` list.\n\n**ICU build failures**: ICU is a Chromium fork fetched via `git_repository`. If the commit in `v8.MODULE.bazel` is wrong, you'll see missing-file or compilation errors in ICU. Cross-reference with V8's `DEPS` file for the correct commit.\n\n**`update-deps.py` fails**: The script requires network access to fetch versions. If a dependency's GitHub release format changed, you may need to update the version manually in `deps.jsonc` and run `python3 build/deps/update-deps.py` to regenerate hashes.\n"
  },
  {
    "path": ".opencode/skills/verification-before-completion/SKILL.md",
    "content": "---\nname: verification-before-completion\ndescription: Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always\n---\n<!-- Skill adopted from: https://raw.githubusercontent.com/obra/superpowers -->\n# Verification Before Completion\n\n## Overview\n\nClaiming work is complete without verification is dishonesty, not efficiency.\n\n**Core principle:** Evidence before claims, always.\n\n**Violating the letter of this rule is violating the spirit of this rule.**\n\n## The Iron Law\n\n```\nNO COMPLETION CLAIMS WITHOUT FRESH VERIFICATION EVIDENCE\n```\n\nIf you haven't run the verification command in this message, you cannot claim it passes.\n\n## The Gate Function\n\n```\nBEFORE claiming any status or expressing satisfaction:\n\n1. IDENTIFY: What command proves this claim?\n2. RUN: Execute the FULL command (fresh, complete)\n3. READ: Full output, check exit code, count failures\n4. VERIFY: Does output confirm the claim?\n   - If NO: State actual status with evidence\n   - If YES: State claim WITH evidence\n5. ONLY THEN: Make the claim\n\nSkip any step = lying, not verifying\n```\n\n## Common Failures\n\n| Claim | Requires | Not Sufficient |\n|-------|----------|----------------|\n| Tests pass | Test command output: 0 failures | Previous run, \"should pass\" |\n| Linter clean | Linter output: 0 errors | Partial check, extrapolation |\n| Build succeeds | Build command: exit 0 | Linter passing, logs look good |\n| Bug fixed | Test original symptom: passes | Code changed, assumed fixed |\n| Regression test works | Red-green cycle verified | Test passes once |\n| Agent completed | VCS diff shows changes | Agent reports \"success\" |\n| Requirements met | Line-by-line checklist | Tests passing |\n\n## Red Flags - STOP\n\n- Using \"should\", \"probably\", \"seems to\"\n- Expressing satisfaction before verification (\"Great!\", \"Perfect!\", \"Done!\", etc.)\n- About to commit/push/PR without verification\n- Trusting agent success reports\n- Relying on partial verification\n- Thinking \"just this once\"\n- Tired and wanting work over\n- **ANY wording implying success without having run verification**\n\n## Rationalization Prevention\n\n| Excuse | Reality |\n|--------|---------|\n| \"Should work now\" | RUN the verification |\n| \"I'm confident\" | Confidence ≠ evidence |\n| \"Just this once\" | No exceptions |\n| \"Linter passed\" | Linter ≠ compiler |\n| \"Agent said success\" | Verify independently |\n| \"I'm tired\" | Exhaustion ≠ excuse |\n| \"Partial check is enough\" | Partial proves nothing |\n| \"Different words so rule doesn't apply\" | Spirit over letter |\n\n## Key Patterns\n\n**Tests:**\n```\n✅ [Run test command] [See: 34/34 pass] \"All tests pass\"\n❌ \"Should pass now\" / \"Looks correct\"\n```\n\n**Regression tests (TDD Red-Green):**\n```\n✅ Write → Run (pass) → Revert fix → Run (MUST FAIL) → Restore → Run (pass)\n❌ \"I've written a regression test\" (without red-green verification)\n```\n\n**Build:**\n```\n✅ [Run build] [See: exit 0] \"Build passes\"\n❌ \"Linter passed\" (linter doesn't check compilation)\n```\n\n**Requirements:**\n```\n✅ Re-read plan → Create checklist → Verify each → Report gaps or completion\n❌ \"Tests pass, phase complete\"\n```\n\n**Agent delegation:**\n```\n✅ Agent reports success → Check VCS diff → Verify changes → Report actual state\n❌ Trust agent report\n```\n\n## Why This Matters\n\nFrom 24 failure memories:\n- your human partner said \"I don't believe you\" - trust broken\n- Undefined functions shipped - would crash\n- Missing requirements shipped - incomplete features\n- Time wasted on false completion → redirect → rework\n- Violates: \"Honesty is a core value. If you lie, you'll be replaced.\"\n\n## When To Apply\n\n**ALWAYS before:**\n- ANY variation of success/completion claims\n- ANY expression of satisfaction\n- ANY positive statement about work state\n- Committing, PR creation, task completion\n- Moving to next task\n- Delegating to agents\n\n**Rule applies to:**\n- Exact phrases\n- Paraphrases and synonyms\n- Implications of success\n- ANY communication suggesting completion/correctness\n\n## The Bottom Line\n\n**No shortcuts for verification.**\n\nRun the command. Read the output. THEN claim the result.\n\nThis is non-negotiable.\n"
  },
  {
    "path": ".opencode/skills/wd-test-format/SKILL.md",
    "content": "---\nname: wd-test-format\ndescription: Detailed guide for authoring .wd-test files in workerd, with examples of bindings, Durable Objects, multi-service configs, TypeScript tests, and network access.\n---\n\n## `.wd-test` File Format\n\n`.wd-test` files are Cap'n Proto configs that define test workers for workerd's test framework. They use the schema defined in `src/workerd/server/workerd.capnp`.\n\n---\n\n### MANDATORY: Load Reference File When Relevant\n\nThis skill is split across multiple files for context efficiency. The core patterns below cover\nstandard single-service tests. Advanced configuration patterns live in a reference file.\n\n**You MUST read the reference file before writing or reviewing test configs that involve its\nsubject matter. Do not guess at advanced config syntax — the reference file contains the exact\npatterns and fields required. Skipping it WILL lead to incorrect configs that fail at runtime.**\n\n| File                            | MUST load when...                                            |\n| ------------------------------- | ------------------------------------------------------------ |\n| `reference/advanced-configs.md` | Test involves Durable Objects, multiple services             |\n|                                 | communicating via service bindings, outbound network access, |\n|                                 | external services/sockets, or TypeScript source files        |\n\nWhen in doubt about whether the reference file is relevant, load it — the cost of reading is\nfar less than the cost of a broken test config.\n\n---\n\n### Basic Structure\n\n```capnp\nusing Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [(\n    name = \"my-test\",\n    worker = (\n      modules = [(name = \"worker\", esModule = embed \"my-test.js\")],\n      compatibilityFlags = [\"nodejs_compat_v2\"],\n    ),\n  )],\n);\n```\n\nKey rules:\n\n- The const name (e.g., `unitTests`) must match what the test runner expects\n- `modules` uses `embed` to inline file contents at build time\n- The first module should be named `\"worker\"` — this is the entry point\n- `compatibilityFlags` control which APIs are available. Use the `compat-date-at` tool to look up available flags and their enable dates.\n- `compatibilityDate` should not be used in wd-test; use specific flags instead\n\n### Module Types\n\n```capnp\nmodules = [\n  (name = \"worker\", esModule = embed \"my-test.js\"),           # ES module (most common)\n  (name = \"helper\", esModule = embed \"helper.js\"),            # Additional ES module\n  (name = \"data.json\", json = embed \"test-data.json\"),        # JSON module\n  (name = \"data.wasm\", wasm = embed \"module.wasm\"),           # WebAssembly module\n  (name = \"legacy\", commonJsModule = embed \"legacy.js\"),      # CommonJS module\n],\n```\n\n### Bindings\n\nBindings make services, data, and namespaces available to the worker via `env`:\n\n```capnp\nbindings = [\n  # Text binding — env.MY_TEXT is a string\n  (name = \"MY_TEXT\", text = \"hello world\"),\n\n  # Text from file\n  (name = \"CERT\", text = embed \"fixtures/cert.pem\"),\n\n  # Data binding — env.MY_DATA is an ArrayBuffer\n  (name = \"MY_DATA\", data = \"base64encodeddata\"),\n\n  # JSON binding — env.CONFIG is a parsed object\n  (name = \"CONFIG\", json = \"{ \\\"key\\\": \\\"value\\\" }\"),\n\n  # Service binding — env.OTHER_SERVICE is a fetch-able service\n  (name = \"OTHER_SERVICE\", service = \"other-service-name\"),\n\n  # Service binding with entrypoint\n  (name = \"MY_RPC\", service = (name = \"my-service\", entrypoint = \"MyClass\")),\n\n  # KV namespace — env.KV is a KV namespace\n  (name = \"KV\", kvNamespace = \"kv-namespace-id\"),\n\n  # Durable Object namespace — env.MY_DO is a DO namespace\n  (name = \"MY_DO\", durableObjectNamespace = \"MyDurableObject\"),\n],\n```\n\n### Test JavaScript Structure\n\nTest files export named objects with a `test()` method:\n\n```javascript\n// Each export becomes a separate test case\nexport const basicTest = {\n  test() {\n    // Synchronous test\n    assert.strictEqual(1 + 1, 2);\n  },\n};\n\nexport const asyncTest = {\n  async test(ctrl, env) {\n    // ctrl is the test controller\n    // env contains bindings from the .wd-test config\n    const resp = await env.OTHER_SERVICE.fetch('http://example.com/');\n    assert.strictEqual(resp.status, 200);\n  },\n};\n```\n\n### BUILD.bazel Integration\n\n```python\nwd_test(\n    src = \"my-test.wd-test\",\n    args = [\"--experimental\"],      # Required for experimental features\n    data = [\"my-test.js\"],          # Test JS/TS files\n)\n```\n\nAdditional `data` entries for fixture files:\n\n```python\nwd_test(\n    src = \"crypto-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"crypto-test.js\",\n        \"fixtures/cert.pem\",\n        \"fixtures/key.pem\",\n    ],\n)\n```\n\n### Test Variants\n\nEvery `wd_test()` automatically generates three variants:\n\n| Target suffix       | Compat date | Description                            |\n| ------------------- | ----------- | -------------------------------------- |\n| `@`                 | 2000-01-01  | Default, tests with oldest compat date |\n| `@all-compat-flags` | 2999-12-31  | Tests with all flags enabled           |\n| `@all-autogates`    | 2000-01-01  | Tests with all autogates enabled       |\n\nRun specific variants:\n\n```bash\njust stream-test //src/workerd/api/tests:my-test@\njust stream-test //src/workerd/api/tests:my-test@all-compat-flags\n```\n\n### Scaffolding\n\nUse `just new-test` to scaffold a new test:\n\n```bash\njust new-test //src/workerd/api/tests:my-test\n```\n\nThis creates the `.wd-test` file, `.js` test file, and appends the `wd_test()` rule to `BUILD.bazel`.\n"
  },
  {
    "path": ".opencode/skills/wd-test-format/reference/advanced-configs.md",
    "content": "# Advanced `.wd-test` Config Patterns\n\nPatterns for Durable Objects, multi-service tests, network access, external services,\nand TypeScript tests.\n\n---\n\n### Durable Objects\n\nTo test Durable Objects, define the namespace and storage:\n\n```capnp\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"do-test\",\n      worker = (\n        modules = [(name = \"worker\", esModule = embed \"do-test.js\")],\n        compatibilityDate = \"2024-01-01\",\n\n        durableObjectNamespaces = [\n          (className = \"MyDurableObject\", uniqueKey = \"210bd0cbd803ef7883a1ee9d86cce06e\"),\n        ],\n\n        durableObjectStorage = (localDisk = \"TEST_TMPDIR\"),\n\n        bindings = [\n          (name = \"MY_DO\", durableObjectNamespace = \"MyDurableObject\"),\n        ],\n      ),\n    ),\n    # Disk service for DO storage\n    (name = \"TEST_TMPDIR\", disk = (writable = true)),\n  ],\n);\n```\n\nThe `uniqueKey` is a hex string that uniquely identifies the namespace. Use any 32-char hex string for tests.\n\nFor in-memory storage (no persistence between requests), use:\n\n```capnp\ndurableObjectStorage = (inMemory = void),\n```\n\n### Multi-Service Configs\n\nTests can define multiple services that communicate via service bindings:\n\n```capnp\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"main-test\",\n      worker = (\n        modules = [(name = \"worker\", esModule = embed \"main-test.js\")],\n        compatibilityDate = \"2024-01-01\",\n        bindings = [\n          (name = \"BACKEND\", service = \"backend\"),\n        ],\n      ),\n    ),\n    ( name = \"backend\",\n      worker = (\n        modules = [(name = \"worker\", esModule = embed \"backend.js\")],\n        compatibilityDate = \"2024-01-01\",\n      ),\n    ),\n  ],\n);\n```\n\nFor large configs, factor out worker definitions as named constants:\n\n```capnp\nconst unitTests :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n    (name = \"helper\", worker = .helperWorker),\n  ],\n);\n\nconst mainWorker :Workerd.Worker = (\n  modules = [(name = \"worker\", esModule = embed \"main.js\")],\n  compatibilityDate = \"2024-01-01\",\n  bindings = [(name = \"HELPER\", service = \"helper\")],\n);\n\nconst helperWorker :Workerd.Worker = (\n  modules = [(name = \"worker\", esModule = embed \"helper.js\")],\n  compatibilityDate = \"2024-01-01\",\n);\n```\n\n### Network Access\n\nFor tests that need outbound network access:\n\n```capnp\n( name = \"internet\",\n  network = (\n    allow = [\"private\"],\n    tlsOptions = (\n      trustedCertificates = [\n        embed \"test-cert.pem\",\n      ],\n    ),\n  )\n),\n```\n\n`allow` can be `[\"private\"]` (loopback/LAN) or `[\"public\"]` (internet). Most tests use `\"private\"`.\n\n### External Services\n\nFor testing RPC over sockets or external service communication:\n\n```capnp\n( name = \"my-external\",\n  external = (\n    address = \"loopback:my-external\",\n    http = (capnpConnectHost = \"cappy\")\n  )\n),\n```\n\n### TypeScript Tests\n\nTypeScript test files use a `.ts-wd-test` extension for the config, but the embedded module is the compiled `.js` output:\n\n**Config file** (`my-test.ts-wd-test`):\n\n```capnp\nusing Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [(\n    name = \"my-test\",\n    worker = (\n      modules = [(name = \"worker\", esModule = embed \"my-test.js\")],\n      compatibilityDate = \"2024-01-01\",\n    ),\n  )],\n);\n```\n\n**BUILD.bazel** references the `.ts` source:\n\n```python\nwd_test(\n    src = \"my-test.ts-wd-test\",\n    args = [\"--experimental\"],\n    data = [\"my-test.ts\"],\n)\n```\n\nThe build system compiles the `.ts` to `.js` automatically.\n"
  },
  {
    "path": ".opencode/skills/workerd-api-review/SKILL.md",
    "content": "---\nname: workerd-api-review\ndescription: Performance optimization, API design & compatibility, security vulnerabilities, and standards spec compliance for workerd code review. Covers tcmalloc-aware perf analysis, compat flags, autogates, web standards adherence, and security patterns. Load this skill when reviewing API changes, performance-sensitive code, security-relevant code, or standards implementations.\n---\n\n**Always** use the `docs/reference/api-review-checklist.md` file for detailed checklists on performance optimization, API design, security vulnerabilities, and standards compliance when planning, writing, or reviewing code changes.\n"
  },
  {
    "path": ".opencode/skills/workerd-safety-review/SKILL.md",
    "content": "---\nname: workerd-safety-review\ndescription: Memory safety, thread safety, concurrency, and critical detection patterns for workerd code review. Covers V8/KJ boundary hazards, lifetime management, cross-thread safety, and coroutine pitfalls. Load this skill when reviewing any C++ code.\n---\n\n**Always** use the `docs/reference/cpp-safety-review-checklist.md` file for detailed checklists on memory safety, thread safety, and concurrency correctness when planning, writing, or reviewing code changes.\n\n"
  },
  {
    "path": ".opencode/tools/bazel-deps.ts",
    "content": "import { tool } from '@opencode-ai/plugin';\nimport path from 'path';\n\n// Known aliases: short name → Bazel repo name\n// Built from build/deps/deps.jsonc, v8.MODULE.bazel, MODULE.bazel, etc.\nconst ALIASES: Record<string, string> = {\n  boringssl: 'ssl',\n  openssl: 'ssl',\n  v8: 'workerd-v8',\n  icu: 'com_googlesource_chromium_icu',\n  cxx: 'workerd-cxx',\n  sqlite: 'sqlite3',\n  capnp: 'capnp-cpp',\n  capnproto: 'capnp-cpp',\n  kj: 'capnp-cpp',\n};\n\n// Ecosystem qualifiers: prefix syntax to disambiguate target type.\n// e.g. \"rust:base64\" → look up the Rust crate, not the C++ target.\ntype Ecosystem = 'rust' | 'cpp' | null;\n\nconst ECOSYSTEM_PREFIXES: Record<string, Ecosystem> = {\n  'rust:': 'rust',\n  'crate:': 'rust',\n  'cpp:': 'cpp',\n  'cc:': 'cpp',\n};\n\n/** Parse optional ecosystem qualifier from target string. */\nfunction parseQualifier(target: string): {\n  ecosystem: Ecosystem;\n  name: string;\n} {\n  for (const [prefix, ecosystem] of Object.entries(ECOSYSTEM_PREFIXES)) {\n    if (target.toLowerCase().startsWith(prefix)) {\n      return { ecosystem, name: target.slice(prefix.length) };\n    }\n  }\n  return { ecosystem: null, name: target };\n}\n\nexport default tool({\n  description:\n    'Bazel dependency lookup tool. Supports two directions:\\n' +\n    '- \"rdeps\" (default): find what workerd targets depend ON a given dependency\\n' +\n    '- \"deps\": find what a given workerd target depends on\\n' +\n    'Resolves short names (v8, ada-url, ssl, thiserror) to Bazel labels, ' +\n    'runs queries, and returns results grouped by component.\\n' +\n    'Supports ecosystem qualifiers to disambiguate targets: ' +\n    '\"rust:base64\" or \"crate:base64\" for Rust crates, ' +\n    '\"cpp:base64\" or \"cc:base64\" for C++ targets.',\n  args: {\n    target: tool.schema\n      .string()\n      .describe(\n        'Target to query. Can be a short dependency name (v8, ada-url, ssl, thiserror), ' +\n          'an external label (@ada-url//:ada), an internal label (//src/workerd/jsg:jsg), ' +\n          'or a file path (src/workerd/api/http.c++). ' +\n          'Use ecosystem qualifiers to disambiguate: \"rust:base64\" or \"crate:base64\" ' +\n          'for Rust crates, \"cpp:base64\" or \"cc:base64\" for C++ targets.'\n      ),\n    direction: tool.schema\n      .enum(['rdeps', 'deps'])\n      .optional()\n      .describe(\n        'Query direction: \"rdeps\" (default) finds what depends on this target; ' +\n          '\"deps\" finds what this target depends on'\n      ),\n    depth: tool.schema\n      .number()\n      .optional()\n      .describe(\n        'Depth of search (default: 1 for direct only). ' +\n          'Higher values include transitive results but are slower.'\n      ),\n  },\n  async execute(args, ctx) {\n    const root = ctx.worktree || ctx.directory;\n    const depth = args.depth ?? 1;\n    const direction = args.direction ?? 'rdeps';\n    const { ecosystem, name } = parseQualifier(args.target.trim());\n\n    if (direction === 'deps') {\n      return await queryForwardDeps(name, depth, root, ecosystem);\n    } else {\n      return await queryReverseDeps(name, depth, root, ecosystem);\n    }\n  },\n});\n\n// =============================================================================\n// Forward dependencies (deps): what does this target depend on?\n// =============================================================================\n\nasync function queryForwardDeps(\n  target: string,\n  depth: number,\n  root: string,\n  ecosystem: Ecosystem = null\n): Promise<string> {\n  // Resolve file paths to Bazel labels\n  const label = await resolveToInternalLabel(target, root, ecosystem);\n  if (!label) {\n    return `Could not resolve \"${target}\" to a Bazel target. Provide a full label like \\`//src/workerd/api:http\\`.`;\n  }\n\n  // Run forward deps and rdeps in parallel\n  const [fwdResult, revResult] = await Promise.all([\n    bazelQuery(`deps(${label}, ${depth})`, root),\n    bazelQuery(`rdeps(//src/..., ${label}, 1)`, root),\n  ]);\n\n  let out = `## Dependencies: ${target}\\n\\n`;\n  out += `**Target:** \\`${label}\\`\\n\\n`;\n\n  // Forward deps: split into internal and external\n  const fwdTargets = fwdResult\n    .filter((t) => t !== label)\n    .filter(\n      (t) =>\n        !t.startsWith('@bazel_tools') &&\n        !t.startsWith('@platforms') &&\n        !t.startsWith('@@rules_')\n    );\n\n  const internal = fwdTargets.filter((t) => t.startsWith('//src/')).sort();\n  const external = fwdTargets\n    .filter((t) => t.startsWith('@') || t.startsWith('@@'))\n    .sort();\n\n  // Group internal deps\n  if (internal.length > 0) {\n    const groups = groupByComponent(internal);\n    out += `### Direct dependencies — internal (${internal.length} targets)\\n\\n`;\n    for (const [component, targets] of Object.entries(groups).sort()) {\n      out += `**${component}/**\\n`;\n      for (const t of targets.slice(0, 15)) {\n        out += `- \\`${t}\\`\\n`;\n      }\n      if (targets.length > 15) {\n        out += `- ... and ${targets.length - 15} more\\n`;\n      }\n      out += '\\n';\n    }\n  }\n\n  // Group external deps by repo\n  if (external.length > 0) {\n    const byRepo = groupByRepo(external);\n    out += `### Direct dependencies — external (${external.length} targets across ${Object.keys(byRepo).length} repos)\\n\\n`;\n    for (const [repo, targets] of Object.entries(byRepo).sort()) {\n      if (targets.length <= 3) {\n        out += `- **${repo}**: ${targets.map((t) => `\\`${t}\\``).join(', ')}\\n`;\n      } else {\n        out += `- **${repo}**: ${targets.length} targets\\n`;\n      }\n    }\n    out += '\\n';\n  }\n\n  // Reverse deps (what depends on this target)\n  const revTargets = revResult\n    .filter((t) => t !== label && t.startsWith('//src/'))\n    .sort();\n\n  if (revTargets.length > 0) {\n    const groups = groupByComponent(revTargets);\n    out += `### Reverse dependencies (${revTargets.length} targets depend on this)\\n\\n`;\n    for (const [component, targets] of Object.entries(groups).sort()) {\n      out += `**${component}/**\\n`;\n      for (const t of targets.slice(0, 15)) {\n        out += `- \\`${t}\\`\\n`;\n      }\n      if (targets.length > 15) {\n        out += `- ... and ${targets.length - 15} more\\n`;\n      }\n      out += '\\n';\n    }\n  } else {\n    out += `### Reverse dependencies\\n\\nNo targets within \\`//src/...\\` directly depend on this target.\\n\\n`;\n  }\n\n  return out;\n}\n\n// =============================================================================\n// Reverse dependencies (rdeps): what depends on this target?\n// =============================================================================\n\nasync function queryReverseDeps(\n  dep: string,\n  depth: number,\n  root: string,\n  ecosystem: Ecosystem = null\n): Promise<string> {\n  // Resolve the dependency to Bazel label(s)\n  const resolved = await resolveDep(dep, root, ecosystem);\n  if (resolved.error) {\n    return resolved.error;\n  }\n\n  let out = `## Reverse dependencies: ${dep}\\n\\n`;\n  out += `**Resolved labels:** ${resolved.labels.map((l) => `\\`${l}\\``).join(', ')}\\n`;\n  if (resolved.note) {\n    out += `**Note:** ${resolved.note}\\n`;\n  }\n  out += '\\n';\n\n  // Run rdeps queries for all labels in parallel\n  const allResults = await Promise.all(\n    resolved.labels.map((label) =>\n      bazelQuery(`rdeps(//src/..., ${label}, ${depth})`, root)\n    )\n  );\n\n  // Merge and deduplicate\n  const allTargets = new Set<string>();\n  for (const result of allResults) {\n    for (const target of result) {\n      allTargets.add(target);\n    }\n  }\n\n  const srcTargets = [...allTargets]\n    .filter((t) => t.startsWith('//src/'))\n    .sort();\n\n  if (srcTargets.length === 0) {\n    out += `No direct dependents found within \\`//src/...\\`.\\n\\n`;\n    out +=\n      'This dependency may only be consumed transitively through another dependency, ';\n    out += 'or the label may not be correct. Check with:\\n';\n    out += '```\\n';\n    for (const label of resolved.labels) {\n      const repo = label.split('//')[0];\n      out += `bazel query '${repo}//...' --output label 2>/dev/null | head -10\\n`;\n    }\n    out += '```\\n';\n    return out;\n  }\n\n  const groups = groupByComponent(srcTargets);\n\n  out += `### Direct dependents (${srcTargets.length} targets)\\n\\n`;\n  for (const [component, targets] of Object.entries(groups).sort()) {\n    out += `**${component}/**\\n`;\n    for (const t of targets.slice(0, 15)) {\n      out += `- \\`${t}\\`\\n`;\n    }\n    if (targets.length > 15) {\n      out += `- ... and ${targets.length - 15} more\\n`;\n    }\n    out += '\\n';\n  }\n\n  // Summary\n  out += `### Summary\\n\\n`;\n  const componentCounts = Object.entries(groups)\n    .map(([c, ts]) => `${c} (${ts.length})`)\n    .join(', ');\n  out += `**${srcTargets.length} targets** across ${Object.keys(groups).length} components: ${componentCounts}\\n`;\n\n  if (Object.keys(groups).length === 1) {\n    out += `\\n**Narrow usage** — only consumed by the \\`${Object.keys(groups)[0]}/\\` component.\\n`;\n  } else if (Object.keys(groups).length <= 3) {\n    out += `\\n**Moderate usage** — consumed by ${Object.keys(groups).length} components.\\n`;\n  } else {\n    out += `\\n**Broad usage** — consumed across ${Object.keys(groups).length} components.\\n`;\n  }\n\n  return out;\n}\n\n// =============================================================================\n// Label resolution\n// =============================================================================\n\ninterface ResolveResult {\n  labels: string[];\n  note?: string;\n  error?: string;\n}\n\nasync function resolveDep(\n  dep: string,\n  root: string,\n  ecosystem: Ecosystem = null\n): Promise<ResolveResult> {\n  // Already a full label\n  if (dep.startsWith('//') || dep.startsWith('@')) {\n    return { labels: [dep] };\n  }\n\n  // Check aliases (only for C++ or unqualified lookups)\n  const canonical =\n    ecosystem !== 'rust' ? (ALIASES[dep.toLowerCase()] ?? dep) : dep;\n\n  // Rust-only lookup: skip C++ resolution entirely\n  if (ecosystem === 'rust') {\n    const crateLabels = await discoverRustCrate(dep, root);\n    if (crateLabels.length > 0) {\n      return { labels: crateLabels, note: 'Resolved as Rust crate' };\n    }\n    return {\n      labels: [],\n      error:\n        `Could not resolve Rust crate \"${dep}\".\\n\\n` +\n        `Tried: \\`@crates_vendor//:${dep}\\`\\n\\n` +\n        `Check available crates with: \\`ls deps/rust/crates/BUILD.${dep}-*.bazel\\``,\n    };\n  }\n\n  // C++-only lookup: skip Rust resolution entirely\n  if (ecosystem === 'cpp') {\n    const extLabels = await discoverExternalTargets(canonical, root);\n    if (extLabels.length > 0) {\n      const note =\n        canonical !== dep\n          ? `Resolved alias \"${dep}\" → \"${canonical}\"`\n          : undefined;\n      return { labels: extLabels, note };\n    }\n    const fallbackLabels = await discoverExternalTargets(`@${canonical}`, root);\n    if (fallbackLabels.length > 0) {\n      return { labels: fallbackLabels };\n    }\n    return {\n      labels: [],\n      error:\n        `Could not resolve C++ dependency \"${dep}\".\\n\\n` +\n        `Tried: \\`@${canonical}\\`\\n\\n` +\n        `You can provide a full Bazel label instead (e.g., \\`@ada-url//:ada\\`).`,\n    };\n  }\n\n  // Unqualified: try C++ first, then Rust (existing behavior)\n  const extLabels = await discoverExternalTargets(canonical, root);\n  if (extLabels.length > 0) {\n    const note =\n      canonical !== dep\n        ? `Resolved alias \"${dep}\" → \"${canonical}\"`\n        : undefined;\n    return { labels: extLabels, note };\n  }\n\n  // Try as a Rust crate via crates_vendor\n  const crateLabels = await discoverRustCrate(dep, root);\n  if (crateLabels.length > 0) {\n    return { labels: crateLabels, note: 'Resolved as Rust crate' };\n  }\n\n  // Try with @ prefix as a last resort\n  const fallbackLabels = await discoverExternalTargets(`@${canonical}`, root);\n  if (fallbackLabels.length > 0) {\n    return { labels: fallbackLabels };\n  }\n\n  return {\n    labels: [],\n    error:\n      `Could not resolve \"${dep}\" to a Bazel dependency label.\\n\\n` +\n      `Tried:\\n` +\n      `- External repo: \\`@${canonical}\\`\\n` +\n      `- Rust crate: \\`@crates_vendor//:${dep}\\`\\n\\n` +\n      `You can provide a full Bazel label instead, or use a qualifier ` +\n      `(e.g., \\`rust:${dep}\\` or \\`cpp:${dep}\\`) to narrow the search.`,\n  };\n}\n\n// Resolve a file path or short name to an internal //src/... label\n// For ecosystem-qualified targets (rust:foo, cpp:foo), resolve via resolveDep instead.\nasync function resolveToInternalLabel(\n  target: string,\n  root: string,\n  ecosystem: Ecosystem = null\n): Promise<string | null> {\n  // Already a Bazel label\n  if (target.startsWith('//') || target.startsWith('@')) {\n    return target;\n  }\n\n  // Ecosystem-qualified: resolve as a dependency, not an internal target\n  if (ecosystem !== null) {\n    const resolved = await resolveDep(target, root, ecosystem);\n    if (resolved.labels.length > 0) {\n      return resolved.labels[0];\n    }\n    return null;\n  }\n\n  // File path — find its Bazel target\n  const filePath = target.startsWith('src/') ? target : `src/${target}`;\n  const dir = path.dirname(filePath);\n\n  try {\n    // Use bazel query to find the target that owns this file\n    const result = await bazelQuery(`kind(\"rule\", //${dir}/...)`, root);\n    if (result.length > 0) {\n      // Try to find a target matching the file's base name\n      const baseName = path.basename(filePath).replace(/\\.[^.]+$/, '');\n      const exactMatch = result.find((t) => t.endsWith(`:${baseName}`));\n      return exactMatch ?? result[0];\n    }\n  } catch {\n    // fall through\n  }\n\n  // Last resort: try as a label directly\n  return `//${dir}:${path.basename(dir)}`;\n}\n\n// =============================================================================\n// External dependency discovery\n// =============================================================================\n\nasync function discoverExternalTargets(\n  repoName: string,\n  root: string\n): Promise<string[]> {\n  const repo = repoName.startsWith('@') ? repoName : `@${repoName}`;\n  try {\n    const targets = await bazelQuery(`${repo}//...`, root);\n    if (targets.length === 0) return [];\n\n    // Prefer short top-level targets\n    const topLevel = targets.filter((t) => {\n      const label = t.split('//')[1] || '';\n      const parts = label.split(':');\n      return parts.length === 2 && !parts[1].includes('/');\n    });\n\n    const candidates = topLevel.length > 0 ? topLevel : targets;\n    return candidates.slice(0, 5);\n  } catch {\n    return [];\n  }\n}\n\nasync function discoverRustCrate(\n  crateName: string,\n  root: string\n): Promise<string[]> {\n  try {\n    const targets = await bazelQuery('@crates_vendor//...', root);\n    const matching = targets.filter((l) => {\n      const lower = l.toLowerCase();\n      return (\n        lower.includes(`:${crateName.toLowerCase()}`) ||\n        lower.includes(`/${crateName.toLowerCase()}`)\n      );\n    });\n    if (matching.length > 0) {\n      return matching.slice(0, 3);\n    }\n  } catch {\n    // crates_vendor may not exist\n  }\n\n  try {\n    const targets = await bazelQuery(\n      `filter(\"crates_vendor__${crateName}\", //external:all-targets)`,\n      root\n    );\n    if (targets.length > 0) {\n      return targets.slice(0, 3);\n    }\n  } catch {\n    // not found\n  }\n\n  return [];\n}\n\n// =============================================================================\n// Bazel query helper\n// =============================================================================\n\nasync function bazelQuery(query: string, root: string): Promise<string[]> {\n  try {\n    const output =\n      await Bun.$`bazel query '${query}' --output label 2>/dev/null`\n        .cwd(root)\n        .text();\n    return output\n      .split('\\n')\n      .map((l) => l.trim())\n      .filter(Boolean);\n  } catch {\n    return [];\n  }\n}\n\n// =============================================================================\n// Grouping helpers\n// =============================================================================\n\nfunction groupByComponent(targets: string[]): Record<string, string[]> {\n  const groups: Record<string, string[]> = {};\n  for (const target of targets) {\n    let component = 'other';\n    const m = target.match(/^\\/\\/src\\/(.+?):/);\n    if (m) {\n      let p = m[1];\n      if (p.startsWith('workerd/')) p = p.slice('workerd/'.length);\n      component = p;\n    }\n    if (!groups[component]) groups[component] = [];\n    groups[component].push(target);\n  }\n  return groups;\n}\n\nfunction groupByRepo(targets: string[]): Record<string, string[]> {\n  const groups: Record<string, string[]> = {};\n  for (const target of targets) {\n    let repo: string;\n    if (target.startsWith('@@')) {\n      // @@+crate_repositories+crates_vendor__foo-1.0 → crates_vendor__foo-1.0\n      const m = target.match(/@@[^/]*\\+([^/]+)/);\n      repo = m ? m[1] : target.split('//')[0];\n    } else {\n      // @foo//bar:baz → @foo\n      repo = target.split('//')[0];\n    }\n    if (!groups[repo]) groups[repo] = [];\n    groups[repo].push(target);\n  }\n  return groups;\n}\n"
  },
  {
    "path": ".opencode/tools/capnp.ts",
    "content": "// Shared helper for running capnpc-capnp on .capnp schema files.\n// Used by next-capnp-ordinal and compat-date-at tools.\n\nimport path from 'path';\n\n/**\n * Run capnpc-capnp on a .capnp file and return the canonical text output.\n *\n * Automatically locates the bazel-built capnp tools, building them if needed.\n * Includes standard import paths for workerd's capnp files.\n *\n * @param root - Project root directory (ctx.worktree or ctx.directory)\n * @param filePath - Absolute path to the .capnp file\n * @returns The canonical schema text, or an error string prefixed with \"Error:\"\n */\nexport async function runCapnpcCapnp(\n  root: string,\n  filePath: string\n): Promise<string | { error: string }> {\n  const capnp = path.join(\n    root,\n    'bazel-bin/external/+http+capnp-cpp/src/capnp/capnp_tool'\n  );\n  const capnpcCapnp = path.join(\n    root,\n    'bazel-bin/external/+http+capnp-cpp/src/capnp/capnpc-capnp'\n  );\n  const importPath = path.join(\n    root,\n    'bazel-workerd/external/+http+capnp-cpp/src'\n  );\n\n  // Check tools exist, build if needed\n  try {\n    await Bun.$`test -x ${capnp} && test -x ${capnpcCapnp}`.quiet();\n  } catch {\n    try {\n      await Bun.$`bazel build @capnp-cpp//src/capnp:capnp_tool @capnp-cpp//src/capnp:capnpc-capnp`\n        .cwd(root)\n        .quiet();\n    } catch {\n      return {\n        error:\n          'Could not build capnp tools. Run:\\n  bazel build @capnp-cpp//src/capnp:capnp_tool @capnp-cpp//src/capnp:capnpc-capnp',\n      };\n    }\n  }\n\n  const fileDir = path.dirname(filePath);\n  const importArgs = [\n    '--no-standard-import',\n    `-I${importPath}`,\n    `-I${fileDir}`,\n    `-I${path.join(root, 'src/workerd/io')}`,\n    `-I${path.join(root, 'src/workerd/server')}`,\n    `-I${path.join(root, 'src')}`,\n  ];\n\n  try {\n    return await Bun.$`${capnp} compile ${importArgs} -o${capnpcCapnp} ${filePath}`\n      .cwd(root)\n      .text();\n  } catch (e: any) {\n    return { error: `capnpc-capnp failed:\\n${e.stderr || e.message}` };\n  }\n}\n\n/**\n * Resolve a .capnp file path (absolute or relative to project root).\n */\nexport function resolveCapnpPath(root: string, file: string): string {\n  return path.isAbsolute(file) ? file : path.join(root, file);\n}\n"
  },
  {
    "path": ".opencode/tools/compat-date-at.ts",
    "content": "import { tool } from '@opencode-ai/plugin';\nimport path from 'path';\nimport { runCapnpcCapnp, resolveCapnpPath } from './capnp.ts';\n\nexport default tool({\n  description:\n    'List which compatibility flags are active or inactive at a given date. ' +\n    'Parses compatibility-date.capnp via the capnp compiler. Useful for understanding ' +\n    'what behavior a worker gets at a specific compat date, debugging test variant ' +\n    'failures (@, @all-compat-flags, @all-autogates), and writing .wd-test configs.',\n  args: {\n    date: tool.schema\n      .string()\n      .optional()\n      .describe(\n        'Compatibility date (YYYY-MM-DD). Omit to list all flags with their enable dates.'\n      ),\n    flag: tool.schema\n      .string()\n      .optional()\n      .describe(\n        'Filter by flag name substring (matches enable flag, disable flag, or field name)'\n      ),\n  },\n  async execute(args, ctx) {\n    const root = ctx.worktree || ctx.directory;\n    const flags = await parseCompatFlags(root);\n    if (typeof flags === 'string') return flags;\n\n    let filtered = flags;\n\n    if (args.flag) {\n      const q = args.flag.toLowerCase();\n      filtered = flags.filter(\n        (f) =>\n          f.fieldName.toLowerCase().includes(q) ||\n          f.enableFlag?.toLowerCase().includes(q) ||\n          f.disableFlag?.toLowerCase().includes(q)\n      );\n      if (filtered.length === 0) {\n        return `No flags matching '${args.flag}'. There are ${flags.length} total flags.`;\n      }\n    }\n\n    if (!args.date) {\n      return formatAllFlags(filtered);\n    }\n\n    if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(args.date)) {\n      return `Invalid date format: '${args.date}'. Use YYYY-MM-DD.`;\n    }\n\n    return formatAtDate(filtered, args.date);\n  },\n});\n\n// --- Types ---\n\ninterface CompatFlag {\n  fieldName: string;\n  ordinal: number;\n  enableFlag: string | null;\n  disableFlag: string | null;\n  enableDate: string | null;\n  enableAllDates: boolean;\n  experimental: boolean;\n  neededByFl: boolean;\n  impliedBy: string | null;\n  obsolete: boolean;\n}\n\n// --- Parsing ---\n\nconst COMPAT_CAPNP = 'src/workerd/io/compatibility-date.capnp';\n\nasync function parseCompatFlags(root: string): Promise<CompatFlag[] | string> {\n  const filePath = resolveCapnpPath(root, COMPAT_CAPNP);\n  const result = await runCapnpcCapnp(root, filePath);\n  if (typeof result !== 'string') return result.error;\n\n  const flags: CompatFlag[] = [];\n\n  for (const line of result.split('\\n')) {\n    const m = line.match(/^\\s+(\\w+)\\s+@(\\d+)\\s+:Bool/);\n    if (!m) continue;\n\n    const fieldName = m[1];\n    const ordinal = parseInt(m[2], 10);\n\n    flags.push({\n      fieldName,\n      ordinal,\n      enableFlag: extractAnno(line, 'compatEnableFlag'),\n      disableFlag: extractAnno(line, 'compatDisableFlag'),\n      enableDate: extractAnno(line, 'compatEnableDate'),\n      enableAllDates: line.includes('$compatEnableAllDates('),\n      experimental: line.includes('$experimental('),\n      neededByFl: line.includes('$neededByFl('),\n      impliedBy: extractRawAnno(line, 'impliedByAfterDate'),\n      obsolete: fieldName.startsWith('obsolete'),\n    });\n  }\n\n  return flags;\n}\n\nfunction extractAnno(line: string, name: string): string | null {\n  const m = line.match(new RegExp(`\\\\$${name}\\\\(\"([^\"]*)\"\\\\)`));\n  return m ? m[1] : null;\n}\n\nfunction extractRawAnno(line: string, name: string): string | null {\n  const m = line.match(new RegExp(`\\\\$${name}\\\\(([^)]+)\\\\)`));\n  return m ? m[1] : null;\n}\n\n// --- Formatting ---\n\nfunction formatAllFlags(flags: CompatFlag[]): string {\n  const active = flags.filter((f) => !f.obsolete);\n  const obsolete = flags.filter((f) => f.obsolete);\n\n  const dated = active\n    .filter((f) => f.enableDate || f.enableAllDates)\n    .sort((a, b) => (a.enableDate || '').localeCompare(b.enableDate || ''));\n  const experimental = active.filter(\n    (f) => f.experimental && !f.enableDate && !f.enableAllDates\n  );\n  const noDate = active.filter(\n    (f) => !f.experimental && !f.enableDate && !f.enableAllDates\n  );\n\n  let out = `## All Compatibility Flags (${active.length} active, ${obsolete.length} obsolete)\\n\\n`;\n\n  if (dated.length > 0) {\n    out += `### Date-gated (${dated.length})\\n\\n`;\n    for (const f of dated) {\n      const d = f.enableAllDates ? 'ALL DATES' : f.enableDate;\n      const extra: string[] = [];\n      if (f.neededByFl) extra.push('FL');\n      if (f.experimental) extra.push('experimental');\n      if (f.impliedBy) extra.push(`implied by ${f.impliedBy}`);\n      const suffix = extra.length > 0 ? ` (${extra.join(', ')})` : '';\n      out += `- **${d}**: \\`${f.enableFlag || f.fieldName}\\`${suffix}\\n`;\n    }\n  }\n\n  if (noDate.length > 0) {\n    out += `\\n### Opt-in only — no default date (${noDate.length})\\n\\n`;\n    for (const f of noDate) {\n      const extra: string[] = [];\n      if (f.impliedBy) extra.push(`implied by ${f.impliedBy}`);\n      const suffix = extra.length > 0 ? ` (${extra.join(', ')})` : '';\n      out += `- \\`${f.enableFlag || f.fieldName}\\`${suffix}\\n`;\n    }\n  }\n\n  if (experimental.length > 0) {\n    out += `\\n### Experimental — requires --experimental (${experimental.length})\\n\\n`;\n    for (const f of experimental) {\n      out += `- \\`${f.enableFlag || f.fieldName}\\`\\n`;\n    }\n  }\n\n  return out;\n}\n\nfunction formatAtDate(flags: CompatFlag[], date: string): string {\n  const active = flags.filter((f) => !f.obsolete);\n\n  const enabled: CompatFlag[] = [];\n  const notYetEnabled: CompatFlag[] = [];\n  const optInOnly: CompatFlag[] = [];\n  const experimental: CompatFlag[] = [];\n\n  for (const f of active) {\n    if (f.enableAllDates) {\n      enabled.push(f);\n    } else if (f.enableDate) {\n      if (f.enableDate <= date) {\n        enabled.push(f);\n      } else {\n        notYetEnabled.push(f);\n      }\n    } else if (f.experimental) {\n      experimental.push(f);\n    } else {\n      optInOnly.push(f);\n    }\n  }\n\n  let out = `## Flags at compatibility date \"${date}\"\\n\\n`;\n  out += `- **${enabled.length}** enabled by default\\n`;\n  out += `- **${notYetEnabled.length}** not yet enabled (future date)\\n`;\n  out += `- **${optInOnly.length}** opt-in only (no date set)\\n`;\n  out += `- **${experimental.length}** experimental\\n\\n`;\n\n  if (enabled.length > 0) {\n    out += `### Enabled by default (${enabled.length})\\n\\n`;\n    for (const f of enabled.sort((a, b) =>\n      (a.enableDate || '').localeCompare(b.enableDate || '')\n    )) {\n      const d = f.enableAllDates ? 'all' : f.enableDate;\n      out += `- \\`${f.enableFlag || f.fieldName}\\` (since ${d})\\n`;\n    }\n  }\n\n  if (notYetEnabled.length > 0) {\n    out += `\\n### Not yet enabled — date is after \"${date}\" (${notYetEnabled.length})\\n\\n`;\n    for (const f of notYetEnabled.sort((a, b) =>\n      (a.enableDate || '').localeCompare(b.enableDate || '')\n    )) {\n      out += `- \\`${f.enableFlag || f.fieldName}\\` (${f.enableDate})\\n`;\n    }\n  }\n\n  if (optInOnly.length > 0) {\n    out += `\\n### Opt-in only (${optInOnly.length})\\n\\n`;\n    for (const f of optInOnly) {\n      out += `- \\`${f.enableFlag || f.fieldName}\\`\\n`;\n    }\n  }\n\n  if (experimental.length > 0) {\n    out += `\\n### Experimental (${experimental.length})\\n\\n`;\n    for (const f of experimental) {\n      out += `- \\`${f.enableFlag || f.fieldName}\\`\\n`;\n    }\n  }\n\n  return out;\n}\n"
  },
  {
    "path": ".opencode/tools/cross-reference.ts",
    "content": "import { tool } from '@opencode-ai/plugin';\nimport path from 'path';\n\nexport default tool({\n  description:\n    'One-shot cross-reference lookup for a C++ class or symbol in the workerd codebase. ' +\n    'Returns header location, implementation file, JSG registration (methods/properties), ' +\n    'EW_*_ISOLATE_TYPES group, test files, and compat flag gating — all in a single call. ' +\n    'Saves 4-6 separate grep/read calls.',\n  args: {\n    symbol: tool.schema\n      .string()\n      .describe(\n        \"Class or symbol name (e.g., 'ReadableStream', 'SubtleCrypto', 'AbortSignal')\"\n      ),\n  },\n  async execute(args, ctx) {\n    const root = ctx.worktree || ctx.directory;\n    const sym = args.symbol;\n    const srcDir = path.join(root, 'src');\n\n    // Run all searches in parallel\n    const [\n      headerHits,\n      implHits,\n      jsgTypeHits,\n      testHits,\n      compatFlagHits,\n      isolateTypeMacro,\n    ] = await Promise.all([\n      grepFiles(srcDir, `class ${sym}\\\\b`, '*.h'),\n      grepFiles(srcDir, `${sym}::`, '*.c++'),\n      grepFiles(srcDir, `JSG_RESOURCE_TYPE.${sym}`, '*.h'),\n      grepTestFiles(srcDir, sym),\n      grepFiles(srcDir, `flags.get.*().*${sym}\\\\|${sym}.*flags.get`, '*.h'),\n      findIsolateTypeMacro(srcDir, sym),\n    ]);\n\n    // Extract JSG methods if we found the registration\n    let jsgMethods: string[] = [];\n    if (jsgTypeHits.length > 0) {\n      jsgMethods = await extractJsgMethods(\n        path.join(root, jsgTypeHits[0].file)\n      );\n    }\n\n    // Build output\n    let out = `## Cross-reference: ${sym}\\n\\n`;\n\n    // Header\n    const primaryHeader = findPrimary(headerHits, (text) =>\n      /class\\s+\\w+\\s*(final\\s*)?[:{]/.test(text)\n    );\n\n    if (primaryHeader) {\n      out += `### Declaration\\n\\n`;\n      out += `- \\`${primaryHeader.file}:${primaryHeader.line}\\`: ${primaryHeader.text.trim()}\\n`;\n      const others = headerHits.filter(\n        (h) => !(h.file === primaryHeader.file && h.line === primaryHeader.line)\n      );\n      if (others.length > 0) {\n        out += `- Also referenced in: ${others\n          .slice(0, 5)\n          .map((h) => `\\`${h.file}:${h.line}\\``)\n          .join(', ')}`;\n        if (others.length > 5) out += ` (+${others.length - 5} more)`;\n        out += '\\n';\n      }\n    } else if (headerHits.length > 0) {\n      out += `### Header references\\n\\n`;\n      for (const h of headerHits.slice(0, 5)) {\n        out += `- \\`${h.file}:${h.line}\\`: ${h.text.trim()}\\n`;\n      }\n    } else {\n      out += `### Declaration\\n\\nNot found in any .h file.\\n`;\n    }\n\n    // Implementation\n    out += `\\n### Implementation\\n\\n`;\n    if (implHits.length > 0) {\n      const implFiles = [...new Set(implHits.map((h) => h.file))];\n      for (const f of implFiles.slice(0, 5)) {\n        const count = implHits.filter((h) => h.file === f).length;\n        out += `- \\`${f}\\` (${count} references)\\n`;\n      }\n    } else {\n      out += `No .c++ files reference \\`${sym}::\\`.\\n`;\n    }\n\n    // JSG registration\n    out += `\\n### JSG Registration\\n\\n`;\n    if (jsgTypeHits.length > 0) {\n      out += `- Registered in \\`${jsgTypeHits[0].file}:${jsgTypeHits[0].line}\\`\\n`;\n      if (jsgMethods.length > 0) {\n        out += `- Methods/properties:\\n`;\n        for (const m of jsgMethods) {\n          out += `  - \\`${m}\\`\\n`;\n        }\n      }\n    } else {\n      out += `No \\`JSG_RESOURCE_TYPE(${sym})\\` found.\\n`;\n    }\n\n    // Isolate type group\n    out += `\\n### Type Registration\\n\\n`;\n    if (isolateTypeMacro) {\n      out += `- Part of \\`${isolateTypeMacro.macro}\\` (defined in \\`${isolateTypeMacro.file}:${isolateTypeMacro.line}\\`)\\n`;\n    } else {\n      out += `Not found in any \\`EW_*_ISOLATE_TYPES\\` macro.\\n`;\n    }\n\n    // Compat flag gating\n    if (compatFlagHits.length > 0) {\n      out += `\\n### Compat Flag Gating\\n\\n`;\n      for (const h of compatFlagHits.slice(0, 5)) {\n        out += `- \\`${h.file}:${h.line}\\`: ${h.text.trim()}\\n`;\n      }\n    }\n\n    // Tests\n    out += `\\n### Tests\\n\\n`;\n    if (testHits.length > 0) {\n      const testFiles = [...new Set(testHits.map((h) => h.file))];\n      for (const f of testFiles.slice(0, 10)) {\n        out += `- \\`${f}\\`\\n`;\n      }\n      if (testFiles.length > 10) {\n        out += `- ... and ${testFiles.length - 10} more\\n`;\n      }\n    } else {\n      out += `No test files found referencing \\`${sym}\\`.\\n`;\n    }\n\n    return out;\n  },\n});\n\n// --- grep helpers ---\n\ninterface Hit {\n  file: string;\n  line: number;\n  text: string;\n}\n\nasync function grepFiles(\n  searchDir: string,\n  pattern: string,\n  includeGlob: string\n): Promise<Hit[]> {\n  try {\n    const output =\n      await Bun.$`grep -rnE --include=${includeGlob} -m 50 ${pattern} ${searchDir}`.text();\n    return parseGrepOutput(output, path.dirname(searchDir)); // root = parent of src/\n  } catch {\n    return [];\n  }\n}\n\nasync function grepTestFiles(\n  searchDir: string,\n  symbol: string\n): Promise<Hit[]> {\n  try {\n    // Match test files by name patterns\n    const output =\n      await Bun.$`grep -rnl --include='*test*.js' --include='*test*.ts' --include='*test*.c++' --include='*.wd-test' -m 50 ${symbol} ${searchDir}`.text();\n    const root = path.dirname(searchDir);\n    return output\n      .split('\\n')\n      .filter(Boolean)\n      .map((f) => ({\n        file: path.relative(root, f.trim()),\n        line: 0,\n        text: '',\n      }));\n  } catch {\n    return [];\n  }\n}\n\nfunction parseGrepOutput(output: string, root: string): Hit[] {\n  const hits: Hit[] = [];\n  for (const line of output.split('\\n')) {\n    if (!line.trim()) continue;\n    const m = line.match(/^(.+?):(\\d+):(.*)$/);\n    if (m) {\n      hits.push({\n        file: path.relative(root, m[1]),\n        line: parseInt(m[2], 10),\n        text: m[3],\n      });\n    }\n  }\n  return hits;\n}\n\nfunction findPrimary(\n  hits: Hit[],\n  predicate: (text: string) => boolean\n): Hit | null {\n  return hits.find((h) => predicate(h.text)) || hits[0] || null;\n}\n\n// --- JSG method extraction ---\n\nasync function extractJsgMethods(headerPath: string): Promise<string[]> {\n  try {\n    const output =\n      await Bun.$`grep -E 'JSG_METHOD|JSG_READONLY_PROTOTYPE_PROPERTY|JSG_LAZY_INSTANCE_PROPERTY|JSG_STATIC_METHOD|JSG_NESTED_TYPE|JSG_INHERIT|JSG_TS_' ${headerPath}`.text();\n    return output\n      .split('\\n')\n      .map((l) => l.trim())\n      .filter((l) => l.length > 0)\n      .slice(0, 30);\n  } catch {\n    return [];\n  }\n}\n\n// --- Isolate type macro lookup ---\n\nasync function findIsolateTypeMacro(\n  searchDir: string,\n  symbol: string\n): Promise<{ macro: string; file: string; line: number } | null> {\n  const root = path.dirname(searchDir);\n  try {\n    // Find files that define EW_*_ISOLATE_TYPES macros\n    const macroFiles =\n      await Bun.$`grep -rnl --include='*.h' 'EW_.*_ISOLATE_TYPES' ${searchDir}`.text();\n\n    for (const file of macroFiles.split('\\n').filter(Boolean)) {\n      const filePath = file.trim();\n\n      // Check if this file contains both the macro definition and the symbol\n      let content: string;\n      try {\n        content = await Bun.$`grep -n ${symbol} ${filePath}`.text();\n      } catch {\n        continue; // symbol not in this file\n      }\n      if (!content.trim()) continue;\n\n      // Find the EW_*_ISOLATE_TYPES definition line\n      let macroDef: string;\n      try {\n        macroDef =\n          await Bun.$`grep -n 'EW_.*_ISOLATE_TYPES' ${filePath}`.text();\n      } catch {\n        continue;\n      }\n\n      for (const defLine of macroDef.split('\\n')) {\n        const dm = defLine.match(/^(\\d+):.*(EW_\\w+_ISOLATE_TYPES)/);\n        if (!dm) continue;\n\n        // Check if symbol appears on the same line (inline macro)\n        if (defLine.includes(symbol)) {\n          return {\n            macro: dm[2],\n            file: path.relative(root, filePath),\n            line: parseInt(dm[1], 10),\n          };\n        }\n\n        // Check continuation lines: symbol within 80 lines of macro def\n        const macroLine = parseInt(dm[1], 10);\n        for (const symLine of content.split('\\n')) {\n          const sm = symLine.match(/^(\\d+):/);\n          if (!sm) continue;\n          const symLineNum = parseInt(sm[1], 10);\n          if (symLineNum >= macroLine && symLineNum <= macroLine + 80) {\n            return {\n              macro: dm[2],\n              file: path.relative(root, filePath),\n              line: macroLine,\n            };\n          }\n        }\n      }\n    }\n  } catch {\n    // grep failed\n  }\n  return null;\n}\n"
  },
  {
    "path": ".opencode/tools/jsg-interface.ts",
    "content": "import { tool } from '@opencode-ai/plugin';\nimport path from 'path';\n\nexport default tool({\n  description:\n    'Extract the full JSG interface for a C++ class — all methods, properties, constants, ' +\n    'nested types, inheritance, iterators, serialization, and TypeScript overrides. ' +\n    'Returns structured, categorized output instead of raw grep lines. ' +\n    'Use this when you need to understand the complete JS-visible API of a workerd type.',\n  args: {\n    className: tool.schema\n      .string()\n      .describe(\n        \"C++ class name (e.g., 'ReadableStream', 'SubtleCrypto', 'Request')\"\n      ),\n  },\n  async execute(args, ctx) {\n    const root = ctx.worktree || ctx.directory;\n    const srcDir = path.join(root, 'src');\n    const cls = args.className;\n\n    // Step 1: Find the file containing JSG_RESOURCE_TYPE(cls) or JSG_STRUCT for this class\n    let headerPath: string | null = null;\n    let registrationType: 'resource' | 'struct' = 'resource';\n    let allResourceFiles: { file: string; line: number }[] = [];\n\n    try {\n      const resourceHit =\n        await Bun.$`grep -rn --include='*.h' \"JSG_RESOURCE_TYPE.${cls}\" ${srcDir}`.text();\n      const hits = resourceHit.split('\\n').filter(Boolean);\n      // Pick the best match: prefer a file where JSG_RESOURCE_TYPE(ClassName is a top-level\n      // class (not nested). Heuristic: the class declaration `class ClassName` appears in the\n      // file without being inside another class's braces. Simple approach: prefer the file\n      // where the line starts with exactly JSG_RESOURCE_TYPE(ClassName (allowing whitespace).\n      // If ambiguous, return all.\n      const parsed = hits\n        .map((h) => {\n          const m = h.match(/^(.+?):(\\d+):(.*)$/);\n          return m\n            ? { file: m[1].trim(), line: parseInt(m[2]), text: m[3] }\n            : null;\n        })\n        .filter(Boolean) as { file: string; line: number; text: string }[];\n\n      if (parsed.length === 1) {\n        headerPath = parsed[0].file;\n      } else if (parsed.length > 1) {\n        // Score each candidate: prefer files where the class is top-level (not nested)\n        // Check if a `class ClassName` appears near the top of the file (not deep in another class)\n        let best: (typeof parsed)[0] | null = null;\n        let bestScore = -1;\n\n        for (const p of parsed) {\n          let score = 0;\n          // Prefer files whose name contains the class name\n          if (path.basename(p.file).toLowerCase().includes(cls.toLowerCase())) {\n            score += 10;\n          }\n          // Check if `class ClassName` appears in the file as a top-level or first-level class\n          // by reading a few lines before the JSG_RESOURCE_TYPE match\n          try {\n            const check =\n              await Bun.$`grep -c \"^class ${cls}\\\\b\" ${p.file}`.text();\n            const topLevelCount = parseInt(check.trim());\n            // A top-level `class Foo` (no indentation) strongly suggests this is the primary class\n            if (topLevelCount > 0) score += 20;\n          } catch {\n            // grep returned no matches\n          }\n          if (score > bestScore) {\n            bestScore = score;\n            best = p;\n          }\n        }\n\n        headerPath = best ? best.file : parsed[0].file;\n        // Store all files for reporting\n        allResourceFiles = parsed.map((p) => ({\n          file: path.relative(root, p.file),\n          line: p.line,\n        }));\n      }\n    } catch {\n      // not a resource type, try struct\n    }\n\n    if (!headerPath) {\n      try {\n        const structHit =\n          await Bun.$`grep -rnl --include='*.h' \"JSG_STRUCT.*${cls}\" ${srcDir}`.text();\n        const files = structHit.split('\\n').filter(Boolean);\n        if (files.length > 0) {\n          headerPath = files[0].trim();\n          registrationType = 'struct';\n        }\n      } catch {\n        // not found\n      }\n    }\n\n    if (!headerPath) {\n      return `No JSG registration found for \\`${cls}\\`. The class may not be a JSG-registered type, or it may use a different registration name.`;\n    }\n\n    // Step 2: Read the file\n    const content = await Bun.file(headerPath).text();\n    const relPath = path.relative(root, headerPath);\n\n    if (registrationType === 'struct') {\n      return extractStruct(content, cls, relPath);\n    }\n\n    return extractResourceType(content, cls, relPath, allResourceFiles);\n  },\n});\n\n// --- Resource type extraction ---\n\nfunction extractResourceType(\n  content: string,\n  cls: string,\n  filePath: string,\n  allFiles: { file: string; line: number }[] = []\n): string {\n  // Find the JSG_RESOURCE_TYPE block - match the opening and find the balanced closing brace\n  const lines = content.split('\\n');\n  let startLine = -1;\n  let configParam = '';\n\n  for (let i = 0; i < lines.length; i++) {\n    const m = lines[i].match(\n      new RegExp(`JSG_RESOURCE_TYPE\\\\s*\\\\(\\\\s*${cls}(?:\\\\s*,\\\\s*([^)]+))?\\\\)`)\n    );\n    if (m) {\n      startLine = i;\n      configParam = m[1]?.trim() || '';\n      break;\n    }\n  }\n\n  if (startLine === -1) {\n    return `Found file \\`${filePath}\\` but could not locate \\`JSG_RESOURCE_TYPE(${cls})\\` block.`;\n  }\n\n  // Find the block — count braces to find the matching close\n  const block = extractBracedBlock(lines, startLine);\n  if (!block) {\n    return `Found \\`JSG_RESOURCE_TYPE(${cls})\\` at \\`${filePath}:${startLine + 1}\\` but could not extract the block.`;\n  }\n\n  // Parse all macro calls from the block\n  const iface = parseInterface(block);\n\n  // Also check for JSG_MEMORY_INFO, iterator macros, etc. in the broader class\n  const classExtras = extractClassExtras(content, cls);\n\n  // Build output\n  let out = `## JSG Interface: ${cls}\\n\\n`;\n  out += `**File:** \\`${filePath}:${startLine + 1}\\`\\n`;\n  if (configParam) {\n    out += `**Config:** \\`${configParam}\\` (conditional registration)\\n`;\n  }\n  if (allFiles.length > 1) {\n    const others = allFiles.filter((f) => f.file !== filePath);\n    out += `**Also registered in:** ${others.map((f) => `\\`${f.file}:${f.line}\\``).join(', ')} (may be a nested class with the same name)\\n`;\n  }\n  out += '\\n';\n\n  // Inheritance\n  if (iface.inherits.length > 0) {\n    out += `### Inheritance\\n\\n`;\n    for (const i of iface.inherits) {\n      out += `- ${i}\\n`;\n    }\n    out += '\\n';\n  }\n\n  // Methods\n  if (iface.methods.length > 0) {\n    out += `### Methods\\n\\n`;\n    for (const m of iface.methods) {\n      out += `- \\`${m.jsName}\\``;\n      if (m.cppName !== m.jsName) out += ` → \\`${m.cppName}\\``;\n      if (m.isStatic) out += ' *(static)*';\n      out += '\\n';\n    }\n    out += '\\n';\n  }\n\n  // Properties\n  if (iface.properties.length > 0) {\n    out += `### Properties\\n\\n`;\n    for (const p of iface.properties) {\n      const flags: string[] = [];\n      if (p.readonly) flags.push('readonly');\n      if (p.lazy) flags.push('lazy');\n      if (p.location === 'instance') flags.push('instance');\n      if (p.location === 'prototype') flags.push('prototype');\n      if (p.location === 'static') flags.push('static');\n      if (p.location === 'inspect') flags.push('inspect-only');\n      out += `- \\`${p.jsName}\\``;\n      if (p.getter && p.getter !== p.jsName)\n        out += ` → getter: \\`${p.getter}\\``;\n      if (p.setter) out += `, setter: \\`${p.setter}\\``;\n      if (flags.length > 0) out += ` *(${flags.join(', ')})*`;\n      out += '\\n';\n    }\n    out += '\\n';\n  }\n\n  // Constants\n  if (iface.constants.length > 0) {\n    out += `### Constants\\n\\n`;\n    for (const c of iface.constants) {\n      out += `- \\`${c.jsName}\\``;\n      if (c.cppExpr && c.cppExpr !== c.jsName) out += ` = \\`${c.cppExpr}\\``;\n      out += '\\n';\n    }\n    out += '\\n';\n  }\n\n  // Nested types\n  if (iface.nestedTypes.length > 0) {\n    out += `### Nested Types\\n\\n`;\n    for (const n of iface.nestedTypes) {\n      out += `- \\`${n.jsName}\\``;\n      if (n.cppType !== n.jsName) out += ` → \\`${n.cppType}\\``;\n      out += '\\n';\n    }\n    out += '\\n';\n  }\n\n  // Iterators\n  if (iface.iterators.length > 0) {\n    out += `### Iterators\\n\\n`;\n    for (const it of iface.iterators) {\n      out += `- \\`${it.kind}\\` via \\`${it.method}\\`\\n`;\n    }\n    out += '\\n';\n  }\n\n  // Serialization\n  if (iface.serialization) {\n    out += `### Serialization\\n\\n`;\n    out += `- ${iface.serialization}\\n\\n`;\n  }\n\n  // Callable\n  if (iface.callable) {\n    out += `### Callable\\n\\n`;\n    out += `- Invocable via \\`${iface.callable}\\`\\n\\n`;\n  }\n\n  // Wildcard property\n  if (iface.wildcardProperty) {\n    out += `### Wildcard Property\\n\\n`;\n    out += `- Catch-all getter: \\`${iface.wildcardProperty}\\`\\n\\n`;\n  }\n\n  // Dispose\n  if (iface.dispose.length > 0) {\n    out += `### Disposal\\n\\n`;\n    for (const d of iface.dispose) {\n      out += `- \\`${d.kind}\\` via \\`${d.method}\\`\\n`;\n    }\n    out += '\\n';\n  }\n\n  // TypeScript overrides\n  if (iface.tsOverrides.length > 0) {\n    out += `### TypeScript Overrides\\n\\n`;\n    for (const ts of iface.tsOverrides) {\n      out += `- \\`${ts.kind}\\`: ${ts.content.length > 80 ? ts.content.substring(0, 80) + '...' : ts.content}\\n`;\n    }\n    out += '\\n';\n  }\n\n  // JS bundles\n  if (iface.jsBundles.length > 0) {\n    out += `### JS Bundles\\n\\n`;\n    for (const b of iface.jsBundles) {\n      out += `- \\`${b}\\`\\n`;\n    }\n    out += '\\n';\n  }\n\n  // Class-level extras\n  if (classExtras.hasMemoryInfo) {\n    out += `**Memory tracking:** Has \\`JSG_MEMORY_INFO\\`\\n`;\n  }\n  if (classExtras.iteratorDecls.length > 0) {\n    out += `**Iterator declarations:**\\n`;\n    for (const it of classExtras.iteratorDecls) {\n      out += `- \\`${it}\\`\\n`;\n    }\n  }\n\n  return out;\n}\n\n// --- Struct extraction ---\n\nfunction extractStruct(content: string, cls: string, filePath: string): string {\n  const lines = content.split('\\n');\n  let structLine = -1;\n  let fields: string[] = [];\n\n  for (let i = 0; i < lines.length; i++) {\n    const m = lines[i].match(/JSG_STRUCT\\s*\\(([^)]*)\\)/);\n    if (m && isInClass(content, cls, i)) {\n      structLine = i;\n      fields = m[1]\n        .split(',')\n        .map((f) => f.trim())\n        .filter(Boolean);\n      break;\n    }\n  }\n\n  // Check for multi-line JSG_STRUCT\n  if (structLine === -1) {\n    for (let i = 0; i < lines.length; i++) {\n      if (lines[i].includes('JSG_STRUCT(') && isInClass(content, cls, i)) {\n        structLine = i;\n        // Gather continuation lines\n        let combined = '';\n        for (let j = i; j < lines.length; j++) {\n          combined += lines[j];\n          if (combined.includes(')')) break;\n        }\n        const m = combined.match(/JSG_STRUCT\\s*\\(([^)]*)\\)/);\n        if (m) {\n          fields = m[1]\n            .split(',')\n            .map((f) => f.trim())\n            .filter(Boolean);\n        }\n        break;\n      }\n    }\n  }\n\n  let out = `## JSG Struct: ${cls}\\n\\n`;\n  out += `**File:** \\`${filePath}:${structLine + 1}\\`\\n`;\n  out += `**Type:** Value type (deep-copied to/from JS objects)\\n\\n`;\n\n  if (fields.length > 0) {\n    out += `### Fields\\n\\n`;\n    for (const f of fields) {\n      const jsName = f.startsWith('$') ? f.substring(1) : f;\n      out += `- \\`${jsName}\\``;\n      if (f.startsWith('$')) out += ` (C++ name: \\`${f}\\`)`;\n      out += '\\n';\n    }\n    out += '\\n';\n  }\n\n  // Check for TS overrides\n  const tsLines = lines.filter(\n    (l) =>\n      l.includes('JSG_STRUCT_TS_OVERRIDE') || l.includes('JSG_STRUCT_TS_DEFINE')\n  );\n  if (tsLines.length > 0) {\n    out += `### TypeScript Overrides\\n\\n`;\n    for (const l of tsLines) {\n      out += `- \\`${l.trim()}\\`\\n`;\n    }\n  }\n\n  return out;\n}\n\n// --- Parsing helpers ---\n\ninterface Method {\n  jsName: string;\n  cppName: string;\n  isStatic: boolean;\n}\n\ninterface Property {\n  jsName: string;\n  getter: string;\n  setter: string;\n  readonly: boolean;\n  lazy: boolean;\n  location: 'instance' | 'prototype' | 'static' | 'inspect';\n}\n\ninterface Constant {\n  jsName: string;\n  cppExpr: string;\n}\n\ninterface NestedType {\n  jsName: string;\n  cppType: string;\n}\n\ninterface Iterator {\n  kind: string;\n  method: string;\n}\n\ninterface Dispose {\n  kind: string;\n  method: string;\n}\n\ninterface TsOverride {\n  kind: string;\n  content: string;\n}\n\ninterface ParsedInterface {\n  inherits: string[];\n  methods: Method[];\n  properties: Property[];\n  constants: Constant[];\n  nestedTypes: NestedType[];\n  iterators: Iterator[];\n  serialization: string | null;\n  callable: string | null;\n  wildcardProperty: string | null;\n  dispose: Dispose[];\n  tsOverrides: TsOverride[];\n  jsBundles: string[];\n}\n\nfunction parseInterface(block: string): ParsedInterface {\n  const result: ParsedInterface = {\n    inherits: [],\n    methods: [],\n    properties: [],\n    constants: [],\n    nestedTypes: [],\n    iterators: [],\n    serialization: null,\n    callable: null,\n    wildcardProperty: null,\n    dispose: [],\n    tsOverrides: [],\n    jsBundles: [],\n  };\n\n  // Normalize: join continuation lines (lines ending with \\) and collapse multi-line macros\n  const lines = block.split('\\n');\n\n  for (let i = 0; i < lines.length; i++) {\n    const line = lines[i].trim();\n    if (!line || line.startsWith('//') || line.startsWith('/*')) continue;\n\n    // JSG_INHERIT\n    let m = line.match(/JSG_INHERIT\\s*\\(\\s*(\\w+)\\s*\\)/);\n    if (m) {\n      result.inherits.push(`JSG_INHERIT(${m[1]})`);\n      continue;\n    }\n    m = line.match(/JSG_INHERIT_INTRINSIC\\s*\\(\\s*([^)]+)\\s*\\)/);\n    if (m) {\n      result.inherits.push(`JSG_INHERIT_INTRINSIC(${m[1].trim()})`);\n      continue;\n    }\n\n    // JSG_METHOD / JSG_METHOD_NAMED / JSG_STATIC_METHOD / JSG_STATIC_METHOD_NAMED\n    m = line.match(/JSG_STATIC_METHOD_NAMED\\s*\\(\\s*(\\w+)\\s*,\\s*(\\w+)\\s*\\)/);\n    if (m) {\n      result.methods.push({ jsName: m[1], cppName: m[2], isStatic: true });\n      continue;\n    }\n    m = line.match(/JSG_STATIC_METHOD\\s*\\(\\s*(\\w+)\\s*\\)/);\n    if (m) {\n      result.methods.push({ jsName: m[1], cppName: m[1], isStatic: true });\n      continue;\n    }\n    m = line.match(/JSG_METHOD_NAMED\\s*\\(\\s*(\\w+)\\s*,\\s*(\\w+)\\s*\\)/);\n    if (m) {\n      result.methods.push({ jsName: m[1], cppName: m[2], isStatic: false });\n      continue;\n    }\n    m = line.match(/JSG_METHOD\\s*\\(\\s*(\\w+)\\s*\\)/);\n    if (m) {\n      result.methods.push({ jsName: m[1], cppName: m[1], isStatic: false });\n      continue;\n    }\n\n    // JSG_CALLABLE\n    m = line.match(/JSG_CALLABLE\\s*\\(\\s*(\\w+)\\s*\\)/);\n    if (m) {\n      result.callable = m[1];\n      continue;\n    }\n\n    // Properties — order matters: match more specific patterns first\n    // Static readonly\n    m = line.match(\n      /JSG_STATIC_READONLY_PROPERTY_NAMED\\s*\\(\\s*(\\w+)\\s*,\\s*(\\w+)\\s*\\)/\n    );\n    if (m) {\n      result.properties.push({\n        jsName: m[1],\n        getter: m[2],\n        setter: '',\n        readonly: true,\n        lazy: false,\n        location: 'static',\n      });\n      continue;\n    }\n    m = line.match(/JSG_STATIC_READONLY_PROPERTY\\s*\\(\\s*(\\w+)\\s*\\)/);\n    if (m) {\n      result.properties.push({\n        jsName: m[1],\n        getter: m[1],\n        setter: '',\n        readonly: true,\n        lazy: false,\n        location: 'static',\n      });\n      continue;\n    }\n\n    // Lazy instance properties\n    m = line.match(\n      /JSG_LAZY_READONLY_INSTANCE_PROPERTY\\s*\\(\\s*(\\w+)\\s*,\\s*(\\w+)\\s*\\)/\n    );\n    if (m) {\n      result.properties.push({\n        jsName: m[1],\n        getter: m[2],\n        setter: '',\n        readonly: true,\n        lazy: true,\n        location: 'instance',\n      });\n      continue;\n    }\n    m = line.match(/JSG_LAZY_INSTANCE_PROPERTY\\s*\\(\\s*(\\w+)\\s*,\\s*(\\w+)\\s*\\)/);\n    if (m) {\n      result.properties.push({\n        jsName: m[1],\n        getter: m[2],\n        setter: '',\n        readonly: false,\n        lazy: true,\n        location: 'instance',\n      });\n      continue;\n    }\n\n    // Instance properties\n    m = line.match(\n      /JSG_READONLY_INSTANCE_PROPERTY\\s*\\(\\s*(\\w+)\\s*,\\s*(\\w+)\\s*\\)/\n    );\n    if (m) {\n      result.properties.push({\n        jsName: m[1],\n        getter: m[2],\n        setter: '',\n        readonly: true,\n        lazy: false,\n        location: 'instance',\n      });\n      continue;\n    }\n    m = line.match(\n      /JSG_INSTANCE_PROPERTY\\s*\\(\\s*(\\w+)\\s*,\\s*(\\w+)\\s*,\\s*(\\w+)\\s*\\)/\n    );\n    if (m) {\n      result.properties.push({\n        jsName: m[1],\n        getter: m[2],\n        setter: m[3],\n        readonly: false,\n        lazy: false,\n        location: 'instance',\n      });\n      continue;\n    }\n\n    // Prototype properties\n    m = line.match(\n      /JSG_READONLY_PROTOTYPE_PROPERTY\\s*\\(\\s*(\\w+)\\s*,\\s*(\\w+)\\s*\\)/\n    );\n    if (m) {\n      result.properties.push({\n        jsName: m[1],\n        getter: m[2],\n        setter: '',\n        readonly: true,\n        lazy: false,\n        location: 'prototype',\n      });\n      continue;\n    }\n    m = line.match(\n      /JSG_PROTOTYPE_PROPERTY\\s*\\(\\s*(\\w+)\\s*,\\s*(\\w+)\\s*,\\s*(\\w+)\\s*\\)/\n    );\n    if (m) {\n      result.properties.push({\n        jsName: m[1],\n        getter: m[2],\n        setter: m[3],\n        readonly: false,\n        lazy: false,\n        location: 'prototype',\n      });\n      continue;\n    }\n\n    // Inspect property\n    m = line.match(/JSG_INSPECT_PROPERTY\\s*\\(\\s*(\\w+)\\s*,\\s*(\\w+)\\s*\\)/);\n    if (m) {\n      result.properties.push({\n        jsName: m[1],\n        getter: m[2],\n        setter: '',\n        readonly: true,\n        lazy: false,\n        location: 'inspect',\n      });\n      continue;\n    }\n\n    // Wildcard property\n    m = line.match(/JSG_WILDCARD_PROPERTY\\s*\\(\\s*(\\w+)\\s*\\)/);\n    if (m) {\n      result.wildcardProperty = m[1];\n      continue;\n    }\n\n    // Constants\n    m = line.match(/JSG_STATIC_CONSTANT_NAMED\\s*\\(\\s*(\\w+)\\s*,\\s*([^)]+)\\s*\\)/);\n    if (m) {\n      result.constants.push({ jsName: m[1], cppExpr: m[2].trim() });\n      continue;\n    }\n    m = line.match(/JSG_STATIC_CONSTANT\\s*\\(\\s*(\\w+)\\s*\\)/);\n    if (m) {\n      result.constants.push({ jsName: m[1], cppExpr: m[1] });\n      continue;\n    }\n\n    // Nested types\n    m = line.match(/JSG_NESTED_TYPE_NAMED\\s*\\(\\s*(\\w+)\\s*,\\s*(\\w+)\\s*\\)/);\n    if (m) {\n      result.nestedTypes.push({ cppType: m[1], jsName: m[2] });\n      continue;\n    }\n    m = line.match(/JSG_NESTED_TYPE\\s*\\(\\s*(\\w+)\\s*\\)/);\n    if (m) {\n      result.nestedTypes.push({ cppType: m[1], jsName: m[1] });\n      continue;\n    }\n\n    // Iterators\n    m = line.match(/JSG_ASYNC_ITERABLE\\s*\\(\\s*(\\w+)\\s*\\)/);\n    if (m) {\n      result.iterators.push({ kind: '[Symbol.asyncIterator]', method: m[1] });\n      continue;\n    }\n    m = line.match(/JSG_ITERABLE\\s*\\(\\s*(\\w+)\\s*\\)/);\n    if (m) {\n      result.iterators.push({ kind: '[Symbol.iterator]', method: m[1] });\n      continue;\n    }\n\n    // Dispose\n    m = line.match(/JSG_ASYNC_DISPOSE\\s*\\(\\s*(\\w+)\\s*\\)/);\n    if (m) {\n      result.dispose.push({ kind: '[Symbol.asyncDispose]', method: m[1] });\n      continue;\n    }\n    m = line.match(/JSG_DISPOSE\\s*\\(\\s*(\\w+)\\s*\\)/);\n    if (m) {\n      result.dispose.push({ kind: '[Symbol.dispose]', method: m[1] });\n      continue;\n    }\n\n    // Serialization\n    m = line.match(/JSG_ONEWAY_SERIALIZABLE\\s*\\(\\s*([^)]+)\\s*\\)/);\n    if (m) {\n      result.serialization = `One-way serializable (tag: ${m[1].trim()})`;\n      continue;\n    }\n    m = line.match(/JSG_SERIALIZABLE\\s*\\(\\s*([^)]+)\\s*\\)/);\n    if (m) {\n      result.serialization = `Serializable (tag: ${m[1].trim()})`;\n      continue;\n    }\n\n    // TypeScript overrides — these are multi-line, grab the macro name\n    if (line.includes('JSG_TS_ROOT')) {\n      result.tsOverrides.push({ kind: 'JSG_TS_ROOT', content: '(root type)' });\n      continue;\n    }\n    m = line.match(/JSG_TS_OVERRIDE\\s*\\((.*)$/);\n    if (m) {\n      const tsContent = gatherMultiLineMacro(lines, i);\n      result.tsOverrides.push({ kind: 'JSG_TS_OVERRIDE', content: tsContent });\n      continue;\n    }\n    m = line.match(/JSG_TS_DEFINE\\s*\\((.*)$/);\n    if (m) {\n      const tsContent = gatherMultiLineMacro(lines, i);\n      result.tsOverrides.push({ kind: 'JSG_TS_DEFINE', content: tsContent });\n      continue;\n    }\n\n    // JS bundles\n    m = line.match(/JSG_CONTEXT_JS_BUNDLE\\s*\\(\\s*(\\w+)\\s*\\)/);\n    if (m) {\n      result.jsBundles.push(m[1]);\n      continue;\n    }\n  }\n\n  return result;\n}\n\nfunction extractBracedBlock(lines: string[], startLine: number): string | null {\n  // Find the first { on or after startLine\n  let braceStart = -1;\n  for (let i = startLine; i < Math.min(startLine + 5, lines.length); i++) {\n    if (lines[i].includes('{')) {\n      braceStart = i;\n      break;\n    }\n  }\n  if (braceStart === -1) return null;\n\n  let depth = 0;\n  let blockLines: string[] = [];\n  for (let i = braceStart; i < lines.length; i++) {\n    for (const ch of lines[i]) {\n      if (ch === '{') depth++;\n      if (ch === '}') depth--;\n    }\n    blockLines.push(lines[i]);\n    if (depth === 0) break;\n  }\n\n  return blockLines.join('\\n');\n}\n\nfunction gatherMultiLineMacro(lines: string[], startIdx: number): string {\n  // Gather lines until parens balance\n  let depth = 0;\n  let result = '';\n  for (let i = startIdx; i < lines.length; i++) {\n    const line = lines[i];\n    for (const ch of line) {\n      if (ch === '(') depth++;\n      if (ch === ')') depth--;\n    }\n    result += line.trim() + ' ';\n    if (depth <= 0) break;\n  }\n  // Clean up: extract content between outer parens\n  const m = result.match(/JSG_TS_\\w+\\s*\\((.+)\\)\\s*;?\\s*$/s);\n  return m ? m[1].trim() : result.trim();\n}\n\nfunction isInClass(content: string, cls: string, lineIdx: number): boolean {\n  // Simple heuristic: check if there's a `class/struct <cls>` before this line\n  const lines = content.split('\\n');\n  for (let i = lineIdx - 1; i >= Math.max(0, lineIdx - 100); i--) {\n    if (lines[i].match(new RegExp(`(?:class|struct)\\\\s+${cls}\\\\b`))) {\n      return true;\n    }\n  }\n  return false;\n}\n\nfunction extractClassExtras(\n  content: string,\n  cls: string\n): { hasMemoryInfo: boolean; iteratorDecls: string[] } {\n  const hasMemoryInfo = content.includes(`JSG_MEMORY_INFO(${cls})`);\n  const iteratorDecls: string[] = [];\n\n  const iteratorPatterns = [\n    /JSG_ITERATOR\\s*\\(\\s*\\w+\\s*,\\s*\\w+\\s*,/g,\n    /JSG_ASYNC_ITERATOR\\s*\\(\\s*\\w+\\s*,\\s*\\w+\\s*,/g,\n    /JSG_ITERATOR_TYPE\\s*\\(\\s*\\w+\\s*,/g,\n    /JSG_ASYNC_ITERATOR_TYPE\\s*\\(\\s*\\w+\\s*,/g,\n  ];\n\n  for (const pat of iteratorPatterns) {\n    let m;\n    while ((m = pat.exec(content)) !== null) {\n      iteratorDecls.push(m[0].replace(/,\\s*$/, ')'));\n    }\n  }\n\n  return { hasMemoryInfo, iteratorDecls };\n}\n"
  },
  {
    "path": ".opencode/tools/next-capnp-ordinal.ts",
    "content": "import { tool } from '@opencode-ai/plugin';\nimport path from 'path';\nimport { runCapnpcCapnp, resolveCapnpPath } from './capnp.ts';\n\nexport default tool({\n  description:\n    \"Find the next available field ordinal (@N) in a Cap'n Proto struct. \" +\n    'Uses the capnp compiler (capnpc-capnp) to parse the schema canonically. ' +\n    'Use before adding new fields to .capnp files (especially compatibility-date.capnp) ' +\n    'to avoid ordinal collisions.',\n  args: {\n    file: tool.schema\n      .string()\n      .describe('Path to .capnp file (absolute, or relative to project root)'),\n    struct: tool.schema\n      .string()\n      .optional()\n      .describe(\n        \"Struct name (e.g., 'CompatibilityFlags'). Omit to list all structs.\"\n      ),\n  },\n  async execute(args, ctx) {\n    const root = ctx.worktree || ctx.directory;\n    const filePath = resolveCapnpPath(root, args.file);\n\n    const result = await runCapnpcCapnp(root, filePath);\n    if (typeof result !== 'string') return result.error;\n\n    const structs = parseCapnpOutput(result);\n\n    if (!args.struct) {\n      return listStructs(structs, filePath);\n    }\n\n    // Support dotted names like \"Worker.Module\"\n    const target =\n      structs.find((s) => s.qualifiedName === args.struct) ||\n      structs.find((s) => s.name === args.struct);\n\n    if (!target) {\n      const available = structs.map((s) => s.qualifiedName).join(', ');\n      return `Struct '${args.struct}' not found in ${path.basename(filePath)}\\n\\nAvailable: ${available}`;\n    }\n\n    return formatResult(target, filePath);\n  },\n});\n\n// --- Output parsing ---\n\ninterface FieldInfo {\n  name: string;\n  ordinal: number;\n  type: string;\n  annotations: string[];\n  raw: string;\n}\n\ninterface StructInfo {\n  name: string;\n  qualifiedName: string;\n  fields: FieldInfo[];\n  declaredAnnotations: string[];\n}\n\nfunction parseCapnpOutput(output: string): StructInfo[] {\n  const lines = output.split('\\n');\n  const allStructs: StructInfo[] = [];\n\n  const nameStack: string[] = [];\n  let currentStruct: StructInfo | null = null;\n\n  let depth = 0;\n  const structDepths: number[] = [];\n\n  for (const line of lines) {\n    const opens = (line.match(/\\{/g) || []).length;\n    const closes = (line.match(/\\}/g) || []).length;\n\n    // Check for struct opening\n    const structMatch = line.match(/struct\\s+(\\w+)\\s+@0x[\\da-fA-F]+\\s*\\{/);\n    if (structMatch) {\n      if (currentStruct) {\n        allStructs.push(currentStruct);\n      }\n\n      const name = structMatch[1];\n      nameStack.push(name);\n      structDepths.push(depth);\n\n      currentStruct = {\n        name,\n        qualifiedName: nameStack.join('.'),\n        fields: [],\n        declaredAnnotations: [],\n      };\n\n      depth += opens;\n      continue;\n    }\n\n    depth += opens;\n\n    // Field declarations: \"  fieldName @N :Type ...\"\n    const fieldMatch = line.match(/^\\s+(\\w+)\\s+@(\\d+)\\s+:(\\S+)/);\n    if (fieldMatch && currentStruct) {\n      const annotations: string[] = [];\n      const annoRe = /\\$(\\w+)\\(([^)]*)\\)/g;\n      let m;\n      while ((m = annoRe.exec(line)) !== null) {\n        annotations.push(`$${m[1]}(${m[2]})`);\n      }\n\n      const raw = line\n        .trim()\n        .replace(/\\s+#\\s.*$/, '')\n        .replace(/;$/, '');\n\n      currentStruct.fields.push({\n        name: fieldMatch[1],\n        ordinal: parseInt(fieldMatch[2], 10),\n        type: fieldMatch[3].replace(/[;,]$/, ''),\n        annotations,\n        raw,\n      });\n    }\n\n    // Annotation declarations inside a struct\n    const annoDecl = line.match(/^\\s+annotation\\s+(\\w+)\\s+@0x/);\n    if (annoDecl && currentStruct) {\n      currentStruct.declaredAnnotations.push(annoDecl[1]);\n    }\n\n    depth -= closes;\n\n    // Check if we've closed a struct\n    while (\n      structDepths.length > 0 &&\n      depth <= structDepths[structDepths.length - 1]\n    ) {\n      if (currentStruct) {\n        allStructs.push(currentStruct);\n        currentStruct = null;\n      }\n      structDepths.pop();\n      nameStack.pop();\n\n      if (nameStack.length > 0) {\n        const parentName = nameStack.join('.');\n        const parentIdx = allStructs.findIndex(\n          (s) => s.qualifiedName === parentName\n        );\n        if (parentIdx >= 0) {\n          currentStruct = allStructs[parentIdx];\n          allStructs.splice(parentIdx, 1);\n        }\n      }\n    }\n  }\n\n  if (currentStruct) {\n    allStructs.push(currentStruct);\n  }\n\n  return allStructs;\n}\n\n// --- Formatting ---\n\nfunction listStructs(structs: StructInfo[], filePath: string): string {\n  if (structs.length === 0) {\n    return `No structs found in ${path.basename(filePath)}`;\n  }\n  let result = `## Structs in ${path.basename(filePath)}\\n\\n`;\n  for (const s of structs) {\n    if (s.fields.length === 0) {\n      result += `- **${s.qualifiedName}**: no fields\\n`;\n    } else {\n      const max = Math.max(...s.fields.map((f) => f.ordinal));\n      result += `- **${s.qualifiedName}**: ${s.fields.length} fields, highest @${max}, next @${max + 1}\\n`;\n    }\n  }\n  return result;\n}\n\nfunction formatResult(target: StructInfo, filePath: string): string {\n  if (target.fields.length === 0) {\n    return `Struct '${target.qualifiedName}' in ${path.basename(filePath)} has no fields.`;\n  }\n\n  const sorted = [...target.fields].sort((a, b) => a.ordinal - b.ordinal);\n  const maxOrdinal = sorted[sorted.length - 1].ordinal;\n  const nextOrdinal = maxOrdinal + 1;\n\n  const used = new Set(target.fields.map((f) => f.ordinal));\n  const gaps: number[] = [];\n  for (let i = 0; i <= maxOrdinal; i++) {\n    if (!used.has(i)) gaps.push(i);\n  }\n\n  const lastFields = sorted.slice(-5);\n\n  let out = `## ${target.qualifiedName} in ${path.basename(filePath)}\\n\\n`;\n  out += `**Next available ordinal: @${nextOrdinal}**\\n\\n`;\n  out += `- Total fields: ${target.fields.length}\\n`;\n  out += `- Highest ordinal: @${maxOrdinal} (\\`${sorted[sorted.length - 1].name}\\`)\\n`;\n\n  if (gaps.length > 0) {\n    out += `- Gaps: ${gaps.map((g) => `@${g}`).join(', ')}\\n`;\n    out += `  (Likely obsolete -- do NOT reuse)\\n`;\n  }\n\n  if (target.declaredAnnotations.length > 0) {\n    out += `- Annotations: ${target.declaredAnnotations.join(', ')}\\n`;\n  }\n\n  out += `\\n### Last ${lastFields.length} fields\\n\\n`;\n  for (const f of lastFields) {\n    out += `- \\`${f.raw}\\`\\n`;\n  }\n\n  out += `\\n### Usage\\n\\n`;\n  out += '```capnp\\n';\n  out += `  yourNewField @${nextOrdinal} :Bool\\n`;\n  out += '```\\n';\n\n  return out;\n}\n"
  },
  {
    "path": ".prettierignore",
    "content": "samples/nodejs-compat-streams-split2/split2.js\n"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n  \"printWidth\": 80,\n  \"tabWidth\": 2,\n  \"singleQuote\": true,\n  \"trailingComma\": \"es5\"\n}\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    \"eamodio.gitlens\",\n    \"llvm-vs-code-extensions.vscode-clangd\",\n    \"ms-vscode.cpptools\",\n    \"abronan.capnproto-syntax\",\n    \"DavidAnson.vscode-markdownlint\",\n    \"skellock.just\"\n  ]\n}\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n  // Use IntelliSense to learn about possible attributes.\n  // Hover to view descriptions of existing attributes.\n  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"name\": \"workerd (dbg)\",\n      \"preLaunchTask\": \"Bazel build workerd (dbg)\",\n      \"request\": \"launch\",\n      \"type\": \"cppdbg\",\n      \"cwd\": \"${workspaceFolder}\",\n      \"program\": \"${workspaceFolder}/bazel-bin/src/workerd/server/workerd\",\n      \"linux\": {\n        \"type\": \"cppdbg\",\n        \"request\": \"launch\",\n        \"MIMode\": \"gdb\",\n        \"miDebuggerArgs\": \"-d ${workspaceFolder}\"\n      },\n      \"osx\": {\n        \"type\": \"cppdbg\",\n        \"request\": \"launch\",\n        \"MIMode\": \"lldb\",\n        \"sourceFileMap\": {\n          \"src\": \"${workspaceFolder}/src\",\n          \"bazel-bin\": \"${workspaceFolder}/bazel-bin\",\n          \"bazel-out\": \"${workspaceFolder}/bazel-out\",\n          \"external\": \"${workspaceFolder}/external\"\n        }\n      },\n      \"windows\": {\n        \"type\": \"cppvsdbg\",\n        \"request\": \"launch\"\n      },\n      \"args\": [\"serve\", \"${input:workerdConfig}\"],\n      \"stopAtEntry\": false,\n      \"externalConsole\": false\n    },\n    {\n      \"name\": \"workerd (opt)\",\n      \"preLaunchTask\": \"Bazel build workerd (opt)\",\n      \"request\": \"launch\",\n      \"type\": \"cppdbg\",\n      \"cwd\": \"${workspaceFolder}\",\n      \"program\": \"${workspaceFolder}/bazel-bin/src/workerd/server/workerd\",\n      \"linux\": {\n        \"type\": \"cppdbg\",\n        \"request\": \"launch\",\n        \"MIMode\": \"gdb\",\n        \"miDebuggerArgs\": \"-d ${workspaceFolder}\"\n      },\n      \"osx\": {\n        \"type\": \"cppdbg\",\n        \"request\": \"launch\",\n        \"MIMode\": \"lldb\",\n        \"sourceFileMap\": {\n          \"src\": \"${workspaceFolder}/src\",\n          \"bazel-bin\": \"${workspaceFolder}/bazel-bin\",\n          \"bazel-out\": \"${workspaceFolder}/bazel-out\",\n          \"external\": \"${workspaceFolder}/external\"\n        }\n      },\n      \"windows\": {\n        \"type\": \"cppvsdbg\",\n        \"request\": \"launch\"\n      },\n      \"args\": [\"serve\", \"${input:workerdConfig}\"],\n      \"stopAtEntry\": false,\n      \"externalConsole\": false\n    },\n    {\n      \"name\": \"workerd with inspector enabled (dbg)\",\n      \"preLaunchTask\": \"Bazel build workerd (dbg)\",\n      \"type\": \"cppdbg\",\n      \"request\": \"launch\",\n      \"cwd\": \"${workspaceFolder}\",\n      \"program\": \"${workspaceFolder}/bazel-bin/src/workerd/server/workerd\",\n      \"linux\": {\n        \"type\": \"cppdbg\",\n        \"request\": \"launch\",\n        \"MIMode\": \"gdb\",\n        \"miDebuggerArgs\": \"-d ${workspaceFolder}\"\n      },\n      \"osx\": {\n        \"type\": \"cppdbg\",\n        \"request\": \"launch\",\n        \"MIMode\": \"lldb\",\n        \"sourceFileMap\": {\n          \"src\": \"${workspaceFolder}/src\",\n          \"bazel-bin\": \"${workspaceFolder}/bazel-bin\",\n          \"bazel-out\": \"${workspaceFolder}/bazel-out\",\n          \"external\": \"${workspaceFolder}/external\"\n        }\n      },\n      \"windows\": {\n        \"type\": \"cppvsdbg\",\n        \"request\": \"launch\"\n      },\n      \"args\": [\"serve\", \"-i0.0.0.0\", \"--verbose\", \"${input:workerdConfig}\"],\n      \"stopAtEntry\": false,\n      \"externalConsole\": false\n    },\n    {\n      \"name\": \"workerd with inspector enabled (opt)\",\n      \"preLaunchTask\": \"Bazel build workerd (opt)\",\n      \"type\": \"cppdbg\",\n      \"request\": \"launch\",\n      \"cwd\": \"${workspaceFolder}\",\n      \"program\": \"${workspaceFolder}/bazel-bin/src/workerd/server/workerd\",\n      \"linux\": {\n        \"type\": \"cppdbg\",\n        \"request\": \"launch\",\n        \"MIMode\": \"gdb\",\n        \"miDebuggerArgs\": \"-d ${workspaceFolder}\"\n      },\n      \"osx\": {\n        \"type\": \"cppdbg\",\n        \"request\": \"launch\",\n        \"MIMode\": \"lldb\",\n        \"sourceFileMap\": {\n          \"src\": \"${workspaceFolder}/src\",\n          \"bazel-bin\": \"${workspaceFolder}/bazel-bin\",\n          \"bazel-out\": \"${workspaceFolder}/bazel-out\",\n          \"external\": \"${workspaceFolder}/external\"\n        }\n      },\n      \"windows\": {\n        \"type\": \"cppvsdbg\",\n        \"request\": \"launch\"\n      },\n      \"args\": [\"serve\", \"-i0.0.0.0\", \"--verbose\", \"${input:workerdConfig}\"],\n      \"stopAtEntry\": false,\n      \"externalConsole\": false\n    },\n    {\n      \"name\": \"workerd test case (dbg)\",\n      \"preLaunchTask\": \"Bazel build all (dbg)\",\n      \"program\": \"${workspaceFolder}/${input:testToDebug}\",\n      \"cwd\": \"${workspaceFolder}\",\n      \"type\": \"cppdbg\",\n      \"request\": \"launch\",\n      \"linux\": {\n        \"type\": \"cppdbg\",\n        \"request\": \"launch\",\n        \"MIMode\": \"gdb\",\n        \"miDebuggerArgs\": \"-d ${workspaceFolder}\",\n        \"program\": \"${workspaceFolder}/${input:testToDebug}\"\n      },\n      \"osx\": {\n        \"type\": \"cppdbg\",\n        \"request\": \"launch\",\n        \"MIMode\": \"lldb\",\n        \"targetArchitecture\": \"arm64\",\n        \"sourceFileMap\": {\n          \"src\": \"${workspaceFolder}/src\",\n          \"bazel-bin\": \"${workspaceFolder}/bazel-bin\",\n          \"bazel-out\": \"${workspaceFolder}/bazel-out\",\n          \"external\": \"${workspaceFolder}/external\"\n        },\n        \"program\": \"${workspaceFolder}/${input:testToDebug}\"\n      },\n      \"windows\": {\n        \"type\": \"cppvsdbg\",\n        \"request\": \"launch\",\n        \"program\": \"${workspaceFolder}/${input:testToDebug}\"\n      },\n      \"stopAtEntry\": false,\n      \"externalConsole\": false\n    },\n    {\n      \"name\": \"workerd wd-test case (dbg)\",\n      \"preLaunchTask\": \"Prepare workerd wd-test (dbg)\",\n      \"program\": \"${workspaceFolder}/bazel-bin/src/workerd/server/workerd\",\n      \"args\": [\n        \"test\",\n        \"${input:wdtestToDebug}\",\n        \"--experimental\",\n        \"--directory-path\",\n        \"TEST_TMPDIR=${workspaceFolder}/bazel-out/test_tmpdir\"\n      ],\n      \"cwd\": \"${workspaceFolder}\",\n      \"type\": \"cppdbg\",\n      \"request\": \"launch\",\n      \"stopAtEntry\": false,\n      \"externalConsole\": false,\n      \"linux\": {\n        \"type\": \"cppdbg\",\n        \"request\": \"launch\",\n        \"MIMode\": \"gdb\",\n        \"miDebuggerArgs\": \"-d ${workspaceFolder}\"\n      },\n      \"osx\": {\n        \"type\": \"cppdbg\",\n        \"request\": \"launch\",\n        \"MIMode\": \"lldb\",\n        \"targetArchitecture\": \"arm64\",\n        \"sourceFileMap\": {\n          \"src\": \"${workspaceFolder}/src\",\n          \"bazel-bin\": \"${workspaceFolder}/bazel-bin\",\n          \"bazel-out\": \"${workspaceFolder}/bazel-out\",\n          \"external\": \"${workspaceFolder}/external\"\n        }\n      },\n      \"windows\": {\n        \"type\": \"cppvsdbg\",\n        \"request\": \"launch\"\n      }\n    }\n  ],\n  \"inputs\": [\n    {\n      \"id\": \"workerdConfig\",\n      \"description\": \"Workerd configuration to serve\",\n      \"default\": \"${workspaceFolder}/samples/helloworld/config.capnp\",\n      \"type\": \"promptString\"\n    },\n    {\n      \"id\": \"testToDebug\",\n      \"description\": \"Test to debug\",\n      \"type\": \"pickString\",\n      \"default\": \"bazel-bin/src/workerd/jsg/jsg-test\",\n      \"options\": [\n        \"bazel-bin/src/workerd/util/sqlite-test\",\n        \"bazel-bin/src/workerd/util/wait-list-test\",\n        \"bazel-bin/src/workerd/util/batch-queue-test\",\n        \"bazel-bin/src/workerd/util/sqlite-kv-test\",\n        \"bazel-bin/src/workerd/io/io-gate-test\",\n        \"bazel-bin/src/workerd/io/compatibility-date-test\",\n        \"bazel-bin/src/workerd/io/promise-wrapper-test\",\n        \"bazel-bin/src/workerd/io/actor-cache-test\",\n        \"bazel-bin/src/workerd/tests/test-fixture-test\",\n        \"bazel-bin/src/workerd/jsg/promise-test\",\n        \"bazel-bin/src/workerd/jsg/resource-test\",\n        \"bazel-bin/src/workerd/jsg/string-test\",\n        \"bazel-bin/src/workerd/jsg/tracing-test\",\n        \"bazel-bin/src/workerd/jsg/web-idl-test\",\n        \"bazel-bin/src/workerd/jsg/util-test\",\n        \"bazel-bin/src/workerd/jsg/dom-exception-test\",\n        \"bazel-bin/src/workerd/jsg/type-wrapper-test\",\n        \"bazel-bin/src/workerd/jsg/macro-meta-test\",\n        \"bazel-bin/src/workerd/jsg/rtti-test\",\n        \"bazel-bin/src/workerd/jsg/value-test\",\n        \"bazel-bin/src/workerd/jsg/setup-test\",\n        \"bazel-bin/src/workerd/jsg/function-test\",\n        \"bazel-bin/src/workerd/jsg/buffersource-test\",\n        \"bazel-bin/src/workerd/jsg/struct-test\",\n        \"bazel-bin/src/workerd/jsg/jsg-test\",\n        \"bazel-bin/src/workerd/jsg/url-test\",\n        \"bazel-bin/src/workerd/jsg/iterator-test\",\n        \"bazel-bin/src/workerd/server/server-test\",\n        \"bazel-bin/src/workerd/api/actor-state-test\",\n        \"bazel-bin/src/workerd/api/basics-test\",\n        \"bazel-bin/src/workerd/api/streams/queue-test\",\n        \"bazel-bin/src/workerd/api/crypto-impl-aes-test\",\n        \"bazel-bin/src/workerd/api/crypto-impl-test\",\n        \"bazel-bin/src/workerd/api/util-test\",\n        \"bazel-bin/src/workerd/api/api-rtti-test\",\n        \"bazel-bin/src/workerd/api/node/buffer-test\",\n        \"bazel-bin/src/workerd/api/crypto-impl-asymmetric-test\",\n        \"bazel-bin/src/workerd/api/url-standard-test\"\n      ]\n    },\n    {\n      \"id\": \"wdtestToDebug\",\n      \"description\": \"Workerd Driven Test to debug\",\n      \"type\": \"pickString\",\n      \"default\": \"src/workerd/api/node/path-test.wd-test\",\n      \"options\": [\n        \"src/cloudflare/internal/test/d1/d1-api-test.wd-test\",\n        \"src/cloudflare/internal/test/vectorize/vectorize-api-test.wd-test\",\n        \"src/cloudflare/internal/test/pipeline-transform/pipeline-transform.wd-test\",\n        \"src/workerd/api/actor-alarms-delete-test.wd-test\",\n        \"src/workerd/api/actor-alarms-test.wd-test\",\n        \"src/workerd/api/analytics-engine-test.wd-test\",\n        \"src/workerd/api/crypto-impl-asymmetric-test.wd-test\",\n        \"src/workerd/api/http-test.wd-test\",\n        \"src/workerd/api/node/assert-test.wd-test\",\n        \"src/workerd/api/node/buffer-nodejs-test.wd-test\",\n        \"src/workerd/api/node/crypto_dh-test.wd-test\",\n        \"src/workerd/api/node/crypto_hash-test.wd-test\",\n        \"src/workerd/api/node/crypto_hkdf-test.wd-test\",\n        \"src/workerd/api/node/crypto_hmac-test.wd-test\",\n        \"src/workerd/api/node/crypto_keys-test.wd-test\",\n        \"src/workerd/api/node/crypto_pbkdf2-test.wd-test\",\n        \"src/workerd/api/node/crypto_random-test.wd-test\",\n        \"src/workerd/api/node/diagnostics-channel-test.wd-test\",\n        \"src/workerd/api/node/mimetype-test.wd-test\",\n        \"src/workerd/api/node/path-test.wd-test\",\n        \"src/workerd/api/node/streams-test.wd-test\",\n        \"src/workerd/api/node/string-decoder-test.wd-test\",\n        \"src/workerd/api/kv-test.wd-test\",\n        \"src/workerd/api/queue-test.wd-test\",\n        \"src/workerd/api/rtti-test.wd-test\",\n        \"src/workerd/api/sql-test.wd-test\",\n        \"src/workerd/api/streams/identitytransformstream-backpressure-test.wd-test\",\n        \"src/workerd/api/streams/streams-test.wd-test\",\n        \"src/workerd/api/tests/abortable-fetch-test.wd-test\",\n        \"src/workerd/api/tests/abortsignal-test.wd-test\",\n        \"src/workerd/api/tests/blob-test.wd-test\",\n        \"src/workerd/api/tests/encoding-test.wd-test\",\n        \"src/workerd/api/tests/events-test.wd-test\",\n        \"src/workerd/api/tests/scheduler-test.wd-test\",\n        \"src/workerd/api/urlpattern-test.wd-test\",\n        \"src/workerd/server/tests/extensions/extensions-test.wd-test\",\n        \"src/workerd/tests/performance-test.wd-test\"\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"files.exclude\": {\n    \"bazel-*\": true\n  },\n  \"files.insertFinalNewline\": true,\n  \"files.trimTrailingWhitespace\": true,\n  \"editor.tabSize\": 2,\n  \"terminal.integrated.env.windows\": {\n    \"BAZEL_LLVM\": \"C:\\\\Program Files\\\\LLVM\",\n    \"BAZEL_SH\": \"C:\\\\msys64\\\\usr\\\\bin\\\\bash.exe\",\n    \"BAZEL_VC\": \"C:\\\\Program Files\\\\Microsoft Visual Studio\\\\2022\\\\Community\\\\VC\",\n    \"BAZEL_WINSDK_FULL_VERSION\": \"10.0.22000.0\"\n  },\n  \"clangd.arguments\": [\n    \"--background-index\",\n    \"--header-insertion=never\",\n    \"--compile-commands-dir=${workspaceFolder}/\",\n    \"--query-driver=**\",\n    \"--clang-tidy\"\n  ],\n  \"clang-format.executable\": \"./bazel-bin/build/deps/formatters/clang-format\",\n  \"bazel.buildifierExecutable\": \"./bazel-bin/build/deps/formatters/buildifier\",\n  \"ruff.path\": [\n    \"./bazel-bin/build/deps/formatters/ruff\"\n  ],\n  \"rust-analyzer.workspace.discoverConfig\": {\n    \"command\": [\n      \"just\",\n      \"_rust-analyzer\"\n    ],\n    \"progressLabel\": \"generating rust analyzer config\",\n    \"filesToWatch\": [\n      \"BUILD.bazel\"\n    ]\n  },\n  \"files.associations\": {\n    \"*.inc\": \"cpp\",\n    \"chrono\": \"cpp\",\n    \"cstdint\": \"cpp\",\n    \"string\": \"cpp\",\n    \"stdexcept\": \"cpp\"\n  }\n}\n"
  },
  {
    "path": ".vscode/tasks.json",
    "content": "{\n  \"version\": \"2.0.0\",\n  \"tasks\": [\n    {\n      \"label\": \"Bazel build all (dbg)\",\n      \"type\": \"shell\",\n      \"command\": \"bazel\",\n      \"args\": [\"build\", \"-c\", \"dbg\", \"//...\"],\n      \"osx\": {\n        // OS X needs to set the bazel `spawn_strategy` option to `local` for symbols to work\n        // correctly (https://github.com/bazelbuild/bazel/issues/6327). This `spawn_strategy`\n        // precludes the use of sandboxing during the build (see\n        // https://bazel.build/docs/user-manual#execution-strategy).\n        \"args\": [\"build\", \"-c\", \"dbg\", \"--spawn_strategy=local\", \"//...\"]\n      },\n      \"group\": {\n        \"kind\": \"build\",\n        \"isDefault\": true\n      },\n      \"problemMatcher\": \"$gcc\",\n      \"presentation\": {\n        \"echo\": true,\n        \"reveal\": \"always\",\n        \"focus\": false,\n        \"panel\": \"shared\",\n        \"showReuseMessage\": false,\n        \"clear\": false\n      }\n    },\n    {\n      \"label\": \"Bazel build workerd (dbg)\",\n      \"type\": \"shell\",\n      \"command\": \"bazel\",\n      \"args\": [\"build\", \"-c\", \"dbg\", \"//src/workerd/server:workerd\"],\n      \"osx\": {\n        // OS X needs to set the bazel `spawn_strategy` option to `local` for symbols to work\n        // correctly (https://github.com/bazelbuild/bazel/issues/6327). This `spawn_strategy`\n        // precludes the use of sandboxing during the build (see\n        // https://bazel.build/docs/user-manual#execution-strategy).\n        \"args\": [\n          \"build\",\n          \"-c\",\n          \"dbg\",\n          \"--spawn_strategy=local\",\n          \"//src/workerd/server:workerd\"\n        ]\n      },\n      \"group\": {\n        \"kind\": \"build\",\n        \"isDefault\": true\n      },\n      \"problemMatcher\": \"$gcc\",\n      \"presentation\": {\n        \"echo\": true,\n        \"reveal\": \"always\",\n        \"focus\": false,\n        \"panel\": \"shared\",\n        \"showReuseMessage\": false,\n        \"clear\": false\n      }\n    },\n    {\n      \"label\": \"Bazel build workerd (fastbuild)\",\n      \"type\": \"shell\",\n      \"command\": \"bazel\",\n      \"args\": [\"build\", \"-c\", \"fastbuild\", \"//src/workerd/server:workerd\"],\n      \"group\": {\n        \"kind\": \"build\",\n        \"isDefault\": true\n      },\n      \"problemMatcher\": \"$gcc\",\n      \"presentation\": {\n        \"echo\": true,\n        \"reveal\": \"always\",\n        \"focus\": false,\n        \"panel\": \"shared\",\n        \"showReuseMessage\": false,\n        \"clear\": false\n      }\n    },\n    {\n      \"label\": \"Bazel build workerd (opt)\",\n      \"type\": \"shell\",\n      \"command\": \"bazel\",\n      \"args\": [\"build\", \"-c\", \"opt\", \"//src/workerd/server:workerd\"],\n      // To enable debug symbols for an optimized build, add the following args for bazel:\n      // \"--strategy=local\", \"--copt='-g'\", \"--host_copt='-g'\", \"--strip=never\"\n      \"group\": {\n        \"kind\": \"build\",\n        \"isDefault\": false\n      },\n      \"problemMatcher\": \"$gcc\",\n      \"presentation\": {\n        \"echo\": true,\n        \"reveal\": \"always\",\n        \"focus\": false,\n        \"panel\": \"shared\",\n        \"showReuseMessage\": false,\n        \"clear\": false\n      }\n    },\n    {\n      \"label\": \"Bazel clean\",\n      \"type\": \"shell\",\n      \"command\": \"bazel\",\n      \"args\": [\"clean\"],\n      \"group\": {\n        \"kind\": \"build\",\n        \"isDefault\": true\n      },\n      \"presentation\": {\n        \"echo\": true,\n        \"reveal\": \"always\",\n        \"focus\": false,\n        \"panel\": \"shared\",\n        \"showReuseMessage\": false,\n        \"clear\": false\n      }\n    },\n    {\n      \"label\": \"Bazel clean --expunge\",\n      \"type\": \"shell\",\n      \"command\": \"bazel\",\n      \"args\": [\"clean\", \"--expunge\"],\n      \"group\": {\n        \"kind\": \"build\",\n        \"isDefault\": true\n      },\n      \"presentation\": {\n        \"echo\": true,\n        \"reveal\": \"always\",\n        \"focus\": false,\n        \"panel\": \"shared\",\n        \"showReuseMessage\": false,\n        \"clear\": false\n      }\n    },\n    {\n      \"label\": \"Bazel run all tests (dbg)\",\n      \"type\": \"shell\",\n      \"command\": \"bazel\",\n      \"args\": [\"test\", \"-c\", \"dbg\", \"--nocache_test_results\", \"//...\"],\n      \"group\": {\n        \"kind\": \"test\",\n        \"isDefault\": true\n      },\n      \"problemMatcher\": \"$gcc\",\n      \"presentation\": {\n        \"echo\": true,\n        \"reveal\": \"always\",\n        \"focus\": false,\n        \"panel\": \"shared\",\n        \"showReuseMessage\": false,\n        \"clear\": false\n      }\n    },\n    {\n      \"label\": \"Bazel run all tests (fastbuild)\",\n      \"type\": \"shell\",\n      \"command\": \"bazel\",\n      \"args\": [\"test\", \"-c\", \"fastbuild\", \"--nocache_test_results\", \"//...\"],\n      \"group\": {\n        \"kind\": \"test\",\n        \"isDefault\": true\n      },\n      \"problemMatcher\": \"$gcc\",\n      \"presentation\": {\n        \"echo\": true,\n        \"reveal\": \"always\",\n        \"focus\": false,\n        \"panel\": \"shared\",\n        \"showReuseMessage\": false,\n        \"clear\": false\n      }\n    },\n    {\n      \"label\": \"Bazel run all tests (opt)\",\n      \"type\": \"shell\",\n      \"command\": \"bazel\",\n      \"args\": [\"test\", \"-c\", \"opt\", \"--nocache_test_results\", \"//...\"],\n      \"group\": {\n        \"kind\": \"test\",\n        \"isDefault\": true\n      },\n      \"problemMatcher\": \"$gcc\",\n      \"presentation\": {\n        \"echo\": true,\n        \"reveal\": \"always\",\n        \"focus\": false,\n        \"panel\": \"shared\",\n        \"showReuseMessage\": false,\n        \"clear\": false\n      }\n    },\n    {\n      \"label\": \"Generate rust-project.json\",\n      \"command\": \"bazel\",\n      \"args\": [\"run\", \"@rules_rust//tools/rust_analyzer:gen_rust_project\"],\n      \"options\": {\n        \"cwd\": \"${workspaceFolder}\"\n      },\n      \"group\": \"build\",\n      \"problemMatcher\": [],\n      \"presentation\": {\n        \"reveal\": \"never\",\n        \"panel\": \"dedicated\"\n      },\n      \"runOptions\": {\n        \"runOn\": \"default\"\n      }\n    },\n    {\n      // Create ${workspaceFolder}/external for clangd on opening VSCode.\n      \"label\": \"Symlink external directory\",\n      \"command\": \"${workspaceFolder}/tools/unix/create-external.sh\",\n      \"windows\": {\n        \"command\": \"${workspaceFolder}/tools/windows/create-external.bat\"\n      },\n      \"group\": \"build\",\n      \"problemMatcher\": [],\n      \"presentation\": {\n        \"reveal\": \"never\",\n        \"panel\": \"dedicated\"\n      },\n      \"runOptions\": {\n        \"reevaluateOnRerun\": false,\n        \"runOn\": \"folderOpen\"\n      }\n    },\n    {\n      \"label\": \"Prepare wd-test TEST_TMPDIR\",\n      \"detail\": \"Ensures a tmp directory for a wd-test exists and is empty.\",\n      \"command\": \"/bin/sh\",\n      \"args\": [\n        \"-c\",\n        \"'rm -rf ${workspaceFolder}/bazel-out/test_tmpdir; mkdir ${workspaceFolder}/bazel-out/test_tmpdir'\"\n      ],\n      \"windows\": {\n        \"args\": [\n          \"/c\",\n          \"'del /q/s ${workspaceFolder}/bazel-out/test_tmpdir & mkdir ${workspaceFolder}/bazel-out/test_tmpdir'\"\n        ]\n      },\n      \"hide\": true,\n      \"type\": \"shell\",\n      \"runOptions\": {\n        \"reevaluateOnRerun\": true\n      }\n    },\n    {\n      \"label\": \"Prepare workerd wd-test (dbg)\",\n      \"dependsOn\": [\"Bazel build workerd (dbg)\", \"Prepare wd-test TEST_TMPDIR\"],\n      \"dependsOrder\": \"sequence\",\n      \"group\": \"build\",\n      \"detail\": \"Builds a debug workerd and prepares a test directory for wd-test\",\n      \"hide\": false,\n      \"runOptions\": {\n        \"reevaluateOnRerun\": true\n      }\n    },\n    {\n      \"label\": \"Generate doxygen\",\n      \"command\": \"doxygen\",\n      \"args\": [\"doxyfile\"],\n      \"hide\": false,\n      \"type\": \"shell\",\n      \"problemMatcher\": []\n    },\n    {\n      \"label\": \"Format all\",\n      \"command\": \"python3\",\n      \"args\": [\"${workspaceFolder}/tools/cross/format.py\"],\n      \"group\": \"build\",\n      \"problemMatcher\": [],\n      \"presentation\": {\n        \"reveal\": \"never\",\n        \"focus\": false,\n        \"panel\": \"shared\"\n      },\n      \"runOptions\": {\n        \"reevaluateOnRerun\": true\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": ".zed/settings.json",
    "content": "// Folder-specific settings\n//\n// For a full list of overridable settings, and general information on folder-specific settings,\n// see the documentation: https://zed.dev/docs/configuring-zed#settings-files\n{\n  \"languages\": {\n    \"C++\": {\n      \"formatter\": {\n        \"external\": {\n          \"command\": \"bazel-bin/build/deps/formatters/clang-format\",\n          \"arguments\": [\"-i\", \"{buffer_path}\"]\n        }\n      }\n    },\n    \"Python\": {\n      \"formatter\": {\n        \"external\": {\n          \"command\": \"bazel-bin/build/deps/formatters/ruff\",\n          \"arguments\": [\"format\", \"--stdin-filename\", \"{buffer_path}\"]\n        }\n      }\n    },\n    \"Starlark\": {\n      \"formatter\": {\n        \"external\": {\n          \"command\": \"bazel-bin/build/deps/formatters/buildifier\",\n          \"arguments\": [\"--lint=fix\", \"--path\", \"{buffer_path}\"]\n        }\n      }\n    }\n  },\n  \"file_types\": {\n    \"Cap'n Proto\": [\"*.wd-test\"],\n    \"Starlark\": [\"BUILD.*\"]\n  },\n  \"lsp\": {\n    \"clangd\": {\n      \"binary\": {\n        \"arguments\": [\n          \"--background-index\",\n          \"--header-insertion=never\",\n          \"--query-driver=**\",\n          \"--clang-tidy\"\n        ]\n      }\n    },\n    \"rust-analyzer\": {\n      \"initialization_options\": {\n        \"workspace\": {\n          \"discoverConfig\": {\n            \"command\": [\"just\", \"_rust-analyzer\"],\n            \"progressLabel\": \"generating rust analyzer config\",\n            \"filesToWatch\": [\"BUILD.bazel\"]\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "AGENTS.md",
    "content": "# AGENTS.md\n\nThis file provides guidance to Claude Code (claude.ai/code) or Opencode (opencode.ai) when working with code in this repository.\n\nSubdirectory `AGENTS.md` files provide component-specific context (key classes, where-to-look tables, local conventions and anti-patterns).\n\n## Instructions for AI Code Assistants\n\n- Suggest updates to AGENTS.md when you find new high-level information\n- You should always determine if the current repository was checked out standalone or as a submodule\n  of the larger workers project.\n- If checked out as a submodule, be aware that there is additional documentation and context in the\n  root of that repository that is not present here. Look for the `../../README.md`, `../../AGENTS.md`,\n  and other markdown files in the root of the parent repository.\n\n## Project Overview\n\n**workerd** is Cloudflare's JavaScript/WebAssembly server runtime that powers Cloudflare Workers. It's an open-source implementation of the same technology used in production at Cloudflare, designed for self-hosting applications, local development, and programmable HTTP proxy functionality.\n\n## Build System & Commands\n\n### Primary Build System: Bazel\n\n- Main build command: `bazel build //src/workerd/server:workerd`\n- Binary output: `bazel-bin/src/workerd/server/workerd`\n\n### Just Commands (recommended for development)\n\n- `just build` or `just b` - Build the project\n- `just test` or `just t` - Run all tests\n- `just format` or `just f` - Format code (uses clang-format + Python formatter)\n- `just clippy <package>` - Run Rust clippy linter (e.g., `just clippy jsg-macros`)\n- `just clang-tidy <target>` - Run clang-tidy on C++ code (e.g., `just clang-tidy //src/rust/jsg:ffi`)\n- `just stream-test <target>` - Stream test output for debugging\n- `just node-test <name>` - Run specific Node.js compatibility tests (e.g., `just node-test zlib`)\n- `just wpt-test <name>` - Run Web Platform Tests (e.g., `just wpt-test urlpattern`)\n- `just generate-types` - Generate TypeScript definitions\n- `just compile-commands` - Generate compile_commands.json for clangd support\n- `just build-asan` - Build with AddressSanitizer\n- `just test-asan` - Run tests with AddressSanitizer\n- `just new-test <target>` - Scaffold a new test (e.g., `just new-test //src/workerd/api/tests:my-test`)\n- `just new-wpt-test <name>` - Scaffold a new WPT test\n- `just lint` or `just eslint` - Run ESLint on TypeScript sources\n- `just coverage <path>` - Generate code coverage report (Linux only, defaults to `//...`)\n- `just watch <args>` - Watch `src/` and `build/` dirs, re-run a just command on changes\n\n## Testing\n\n### Test Types\n\n- **`.wd-test` tests**: Cap'n Proto config files that define a `Workerd.Config` with embedded JS/TS modules. Bazel macro: `wd_test()`. See format details below.\n- **C++ tests**: KJ-based unit tests (`.c++` files). Bazel macro: `kj_test()`.\n- **Node.js compatibility tests**: `just node-test <test_name>`\n- **Web Platform Tests**: `just wpt-test <test_name>`\n- **Benchmarks**: `just bench <path>` (e.g., `just bench mimetype`)\n\n### Running a Single Test\n\nBoth `just test` and `just build` accept specific Bazel targets (they default to `//...`):\n\n```\njust test //src/workerd/api/tests:encoding-test@\njust test //src/workerd/io:io-gate-test@\njust stream-test //src/workerd/api/tests:encoding-test@    # streams output for debugging\n```\n\nOr use Bazel directly:\n\n```\nbazel test //src/workerd/api/tests:encoding-test@\n```\n\n### Test Variants\n\nEvery test automatically generates multiple variants via the build macros:\n\n- **`name@`** — default variant (oldest compat date, 2000-01-01)\n- **`name@all-compat-flags`** — newest compat date (2999-12-31), tests with all flags enabled\n- **`name@all-autogates`** — all autogates enabled + oldest compat date\n\nThe `@` suffix is required in target names. For example: `//src/workerd/io:io-gate-test@`, not `//src/workerd/io:io-gate-test`.\n\nTo find the right target name for a file, check the `BUILD.bazel` file in the same directory for `wd_test()` or `kj_test()` rules. You can also use Bazel query:\n\n```\nbazel query //src/workerd/api/tests:all    # list all targets in a package\n```\n\n### `.wd-test` File Format\n\n`.wd-test` files are Cap'n Proto configs that define test workers:\n\n```capnp\nusing Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [(\n    name = \"my-test\",\n    worker = (\n      modules = [(name = \"worker\", esModule = embed \"my-test.js\")],\n      compatibilityDate = \"2024-01-01\",\n      compatibilityFlags = [\"nodejs_compat\"],\n    ),\n  )],\n);\n```\n\nKey elements: `modules` (embed JS/TS files), `compatibilityFlags`, `bindings` (service bindings, JSON, KV, etc.), `durableObjectNamespaces`.\n\n## Architecture\n\n### Dependencies\n\n- **Cap'n Proto source code** available in `external/capnp-cpp` - contains KJ C++ base library and\n  capnproto RPC library. Consult it for all questions about `kj/` and `capnproto/` includes and\n  `kj::` and `capnp::` namespaces.\n\nThe other core runtime dependencies include:\n\n| Dependency                          | Description                                                         |\n| ----------------------------------- | ------------------------------------------------------------------- |\n| V8                                  | JavaScript engine                                                   |\n| Cap'n Proto (capnp-cpp)             | Serialization/RPC framework and KJ base library                     |\n| BoringSSL                           | TLS/crypto (Google's OpenSSL fork, patched for ncrypto/libdecrepit) |\n| SQLite3                             | Embedded database                                                   |\n| ICU (com_googlesource_chromium_icu) | Internationalization (Chromium fork)                                |\n| zlib                                | Compression (Chromium fork, patched)                                |\n| zstd                                | Zstandard compression                                               |\n| brotli                              | Brotli compression                                                  |\n| tcmalloc                            | Memory allocator                                                    |\n| ada-url                             | URL parser                                                          |\n| simdutf                             | Unicode transcoding (SIMD-accelerated)                              |\n| nbytes                              | Node.js byte utilities                                              |\n| ncrypto                             | Node.js crypto utilities                                            |\n| perfetto                            | Tracing/profiling framework (patched)                               |\n| fast_float                          | Fast float parsing                                                  |\n| fp16                                | Half-precision float support                                        |\n| highway                             | SIMD abstraction library                                            |\n| dragonbox                           | Float-to-string conversion                                          |\n\nThese dependencies are vendored via Bazel into the `external/` directory. See `MODULE.bazel` and the `build/deps/` directory for how they are integrated into the build system. (The project uses bzlmod; the legacy `WORKSPACE` file may still exist but is no longer the primary mechanism.)\n\nFor several of these dependencies (notably V8, boringssl, sqlite, perfetto, and zlib), we maintain sets of patches that are applied on top of the upstream code. These patches are stored in the `patches/` directory and are applied during the build process. When updating these dependencies, it's important to review and update the corresponding patches as needed. The patches may introduce workerd-specific customizations and new APIs.\n\nBe aware that workerd uses tcmalloc for memory allocation in the typical case. When analyzing memory usage or debugging memory issues, be aware that tcmalloc's behavior may differ from the standard allocator. Any memory usage analysis that you perform should take this into account.\n\n### Core Directory Structure (`src/workerd/`)\n\n- **`api/`** - Runtime APIs (HTTP, crypto, streams, WebSocket, etc.)\n  - Contains C++ implementations of the core APIs exposed to JavaScript, as well as the Node.js compatibility layer\n  - C++ portions of the Node.js compatibility layer are in `api/node/`, while the JavaScript and TypeScript implementations live in `src/node/`\n  - Tests in `api/tests/` and `api/node/tests/`\n  - TypeScript definitions are derived from C++ (which can have some annotations). This generation is handled by code in `types/` directory.\n\n- **`io/`** - I/O subsystem, actor storage, threading, worker lifecycle\n  - Actor storage and caching (`actor-cache.c++`, `actor-sqlite.c++`)\n  - Request tracking and limits (`request-tracker.c++`, `limit-enforcer.h`)\n- **`jsg/`** - JavaScript Glue layer for V8 integration\n  - Core JavaScript engine bindings and type wrappers\n  - Promise handling, memory management, module system\n- **`server/`** - Main server implementation and configuration\n  - Main binary entry point and Cap'n Proto config handling\n- **`util/`** - Utility libraries (SQLite, UUID, threading, etc.)\n\n### Multi-Language Support\n\n- **`src/cloudflare/`** - Cloudflare-specific APIs (TypeScript)\n- **`src/node/`** - Node.js compatibility layer (TypeScript)\n- **`src/pyodide/`** - Python runtime support via Pyodide\n- **`src/rust/`** - Rust integration components\n\n### Configuration System\n\n- Uses **Cap'n Proto** for configuration files (`.capnp` format)\n- Main schema: `src/workerd/server/workerd.capnp`\n- Sample configurations in `samples/` directory\n- Configuration uses capability-based security model\n\n### Where to Look\n\n| Task                   | Location                                                      | Notes                                                          |\n| ---------------------- | ------------------------------------------------------------- | -------------------------------------------------------------- |\n| Add/modify JS API      | `src/workerd/api/`                                            | C++ with JSG macros; see `jsg/jsg.h` for binding system        |\n| Add Node.js compat     | `src/workerd/api/node/` (C++) + `src/node/` (TS)              | Dual-layer; register in `api/node/node.h` NODEJS_MODULES macro |\n| Add Cloudflare API     | `src/cloudflare/`                                             | TypeScript; mock in `internal/test/<product>/`                 |\n| Modify compat flags    | `src/workerd/io/compatibility-date.capnp`                     | ~1400 lines; annotations define flag names + enable dates      |\n| Add autogate           | `src/workerd/util/autogate.h` + `.c++`                        | Enum + string map; both must stay in sync                      |\n| Config schema          | `src/workerd/server/workerd.capnp`                            | Cap'n Proto; capability-based security                         |\n| Worker lifecycle       | `src/workerd/io/worker.{h,c++}`                               | Isolate, Script, Worker, Actor classes                         |\n| Request lifecycle      | `src/workerd/io/io-context.{h,c++}`                           | IoContext: the per-request god object                          |\n| Durable Object storage | `src/workerd/io/actor-cache.{h,c++}` + `actor-sqlite.{h,c++}` | LRU cache over RPC / SQLite-backed                             |\n| Streams implementation | `src/workerd/api/streams/`                                    | Has 842-line README; dual internal/standard impl               |\n| Bazel build rules      | `build/`                                                      | Custom `wd_*` macros; `wd_test.bzl` generates 3 test variants  |\n| TypeScript types       | `types/`                                                      | Extracted from C++ RTTI + hand-written `defines/*.d.ts`        |\n| V8 patches             | `patches/v8/`                                                 | 33 patches; see `docs/v8-updates.md`                           |\n\n## Coding Conventions\n\nThis project generally follows the [KJ Style Guide](https://github.com/capnproto/capnproto/blob/v2/kjdoc/style-guide.md) and [KJ Tour](https://github.com/capnproto/capnproto/blob/v2/kjdoc/tour.md), with one exception: comment style follows the more common idiomatic C++ patterns (e.g., `//` line comments) rather than KJ's comment conventions.\n\n- **C++ standard**: C++23 (`-std=c++23`)\n- **C++ file extensions**: `.c++` / `.h` (not `.cpp`); test suffix `-test` (hyphenated)\n- **Formatting**: `just format` runs clang-format + prettier + ruff + buildifier + rustfmt\n- **Pre-commit hook**: Blocks `KJ_DBG` in staged code; runs format check\n- **Commit discipline**: Split PRs into small commits; each must compile + pass tests; no fixup commits\n- **TypeScript**: Strict mode, `exactOptionalPropertyTypes`, private `#` syntax enforced, explicit return types\n\n### Use KJ types, not STL\n\nThis project uses the KJ library instead of the C++ standard library for most types:\n\n| Instead of              | Use                                                 |\n| ----------------------- | --------------------------------------------------- |\n| `std::string`           | `kj::String` (owned) / `kj::StringPtr` (view)       |\n| `std::vector`           | `kj::Array<T>` (fixed) / `kj::Vector<T>` (growable) |\n| `std::unique_ptr`       | `kj::Own<T>`                                        |\n| `std::shared_ptr`       | `kj::Rc<T>` / `kj::Arc<T>` (thread-safe)            |\n| `std::optional`         | `kj::Maybe<T>`                                      |\n| `std::function`         | `kj::Function<T>`                                   |\n| `std::variant`          | `kj::OneOf<T...>`                                   |\n| `std::span` / array ref | `kj::ArrayPtr<T>`                                   |\n| `std::exception`        | `kj::Exception`                                     |\n| `std::promise`/`future` | `kj::Promise<T>` / `kj::ForkedPromise<T>`           |\n\n### Error Handling\n\n- `KJ_IF_SOME` for unwrapping `kj::Maybe` (1400+ uses across the codebase)\n- `JSG_REQUIRE` / `JSG_FAIL_REQUIRE` for JS-facing errors with DOM exception types\n- `KJ_ASSERT` / `KJ_REQUIRE` / `KJ_FAIL_ASSERT` for C++ assertions and preconditions\n\n### JSG (JavaScript Glue)\n\nC++ classes are exposed to JavaScript via JSG macros in `src/workerd/jsg/`. See the comprehensive guide at `src/workerd/jsg/README.md` for details. When adding or modifying JavaScript APIs, find a similar existing API and follow its pattern.\n\n- `JSG_RESOURCE_TYPE` for reference types, `JSG_STRUCT` for value types\n- `js.alloc<T>()` for resource allocation\n\n### Feature Management\n\n- **Compatibility flags** (`src/workerd/io/compatibility-date.capnp`) — per-worker, date-driven, permanent. Flags MUST be documented before their enable date.\n- **Autogates** (`src/workerd/util/autogate.*`) — per-process, config-driven, temporary. For risky rollouts with conditional activation.\n\n## Anti-Patterns\n\n- **NEVER** put `v8::Local`/`v8::Global`/`JsValue` in `JSG_STRUCT` fields (use `jsg::V8Ref`/`jsg::JsRef`)\n- **NEVER** put `v8::Global<T>` or `v8::Local<T>` in `kj::Promise` (compile-time deleted)\n- **NEVER** pass `jsg::Lock` into KJ promise coroutines\n- **NEVER** hold JS heap refs to KJ I/O objects without `IoOwn`; enforced by `DISALLOW_KJ_IO_DESTRUCTORS_SCOPE`\n- **NEVER** use `JSG_INSTANCE_PROPERTY` without good reason (breaks GC optimization); prefer `JSG_PROTOTYPE_PROPERTY`\n- **NEVER** call `recursivelyFreeze()` on user-provided content (unsafe for cyclic values)\n- **NEVER** add new `Fetcher` methods without compat flag (conflicts with JS RPC wildcard)\n- **NEVER** change `Headers::Guard` enum values (serialized)\n- **NEVER** use `getWaitUntilTasks()` (use `addWaitUntil()`)\n- **NEVER** use boolean arguments; prefer `WD_STRONG_BOOL`\n- `Ref<T>` stored in C++ objects visible from JS heap **MUST** implement `visitForGc()`; C++ reference cycles are **NEVER** collected\n- SQLite `SQLITE_MISUSE` errors always throw (never suppressed); transactions disallowed in DO SQLite\n- Module evaluation **MUST NOT** be in an IoContext; async I/O is **FORBIDDEN** in global scope\n\n## Backward Compatibility\n\n- Strong backwards compatibility commitment - features cannot be removed or changed once deployed\n- We use compatibility-date.capnp to introduce feature flags when we need to change the behavior\n\n## Development Workflow\n\n### Contributing\n\n- High bar for non-standard APIs; prefer implementing web standards\n- Run formatting with `just format` before submitting PRs\n- Run tests with `just test` before submitting PRs\n- See `CONTRIBUTING.md` for more details\n\n### Rust Development\n\n- `just update-rust <package>` - Update Rust dependencies (equivalent to `cargo update`)\n- `just clippy <package>` - Run clippy linting on Rust code\n\n## NPM Package Management\n\n- Uses **pnpm** for TypeScript/JavaScript dependencies\n- Root package.json contains development dependencies\n\n## V8 Updates\n\nSee [docs/v8-updates.md](docs/v8-updates.md) for instructions on updating the V8 engine version used by workerd. These steps include syncing the V8 source, applying workerd patches, rebasing onto the new version, regenerating patches, and updating dependency versions in Bazel files.\n\nWhen updating V8, ensure that all tests pass. Look for new deprecations when building and flag those for users if necessary.\n\nIf asked to help with a V8 update, ask for the specific target V8 version to update to and ask clarifying questions about any specific patches or customizations that need to be preserved before proceeding. Merge conflicts are common during V8 updates, so be prepared to resolve those carefully. These almost always require human judgment to ensure that workerd-specific changes are preserved while still applying the upstream V8 changes correctly. Do not attempt to resolve merge conflicts automatically without human review.\n\n## Other Documentation\n\nSee the markdown files in the `docs/` directory for additional information on specific topics:\n\n- [development.md](docs/development.md) - Development environment setup and tools\n- [api-updates.md](docs/api-updates.md) - Guidelines for adding new JavaScript APIs\n- [pyodide.md](docs/pyodide.md) - Pyodide package management and updates\n\nSome source directories also contain README.md files with more specific information about that component. Proactively look for these when working in unfamiliar areas of the codebase. Proactively suggest updates to the documentation when it is missing or out of date, but do not make edits without confirming accuracy.\n"
  },
  {
    "path": "BUILD.bazel",
    "content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_library\")\nload(\"@aspect_rules_js//npm:defs.bzl\", \"npm_link_package\")\nload(\"@bazel_skylib//lib:selects.bzl\", \"selects\")\nload(\"@bazel_skylib//rules:common_settings.bzl\", \"bool_flag\")\nload(\"@npm//:capnp-es/package_json.bzl\", capnp_es_bins = \"bin\")\nload(\"@npm//:defs.bzl\", \"npm_link_all_packages\")\nload(\"//:build/wd_cc_embed.bzl\", \"wd_cc_embed\")\n\n# This is used to embed the ICU data file which libicu needs at runtime to do its thing.\n# We bake this file into the binary to avoid shipping it separately. (V8's normal GN build can\n# actually do this for us, but we use the Bazel build which doesn't have this option.) Using #embed\n# makes this very fast and convenient.\nwd_cc_embed(\n    name = \"icudata-embed\",\n    src = \"@com_googlesource_chromium_icu//:common/icudtl.dat\",\n    base_name = \"icu-data-file\",\n    strip_include_prefix = \"\",\n    visibility = [\"//visibility:public\"],\n)\n\n# Also re-export the v8 ICU library for @workerd-v8\nalias(\n    name = \"v8_icu\",\n    actual = \"@v8//:v8_icu\",\n    tags = [\"manual\"],\n    visibility = [\"//visibility:public\"],\n)\n\nnpm_link_all_packages(name = \"node_modules\")\n\nnpm_link_package(\n    name = \"node_modules/@workerd/jsg\",\n    src = \"//src/workerd/jsg:jsg_js\",\n    package = \"@workerd/jsg\",\n)\n\n# Plugin to generate .js files\ncapnp_es_bins.capnpc_js_binary(\n    name = \"capnpc_js_plugin\",\n    visibility = [\"//visibility:public\"],\n)\n\n# Plugin to generate .ts files\ncapnp_es_bins.capnpc_ts_binary(\n    name = \"capnpc_ts_plugin\",\n    visibility = [\"//visibility:public\"],\n)\n\njs_library(\n    name = \"prettierrc\",\n    srcs = [\":.prettierrc.json\"],\n    visibility = [\"//build/deps/formatters:__pkg__\"],\n)\n\n# Platform definition for using clang-cl on Windows\nplatform(\n    name = \"x64_windows-clang-cl\",\n    constraint_values = [\n        \"@platforms//cpu:x86_64\",\n        \"@platforms//os:windows\",\n        \"@bazel_tools//tools/cpp:clang-cl\",\n    ],\n)\n\n# Used for cross-compilation\nplatform(\n    name = \"macOS_x86\",\n    constraint_values = [\n        \"@platforms//os:macos\",\n        \"@platforms//cpu:x86_64\",\n    ],\n)\n\n# Detect whether we use Linux/macOS/either, used to configure whether tcmalloc/perfetto should be\n# used.\nconfig_setting(\n    name = \"is_linux\",\n    constraint_values = [\"@platforms//os:linux\"],\n    visibility = [\"//visibility:public\"],\n)\n\nconfig_setting(\n    name = \"is_macos\",\n    constraint_values = [\"@platforms//os:macos\"],\n    visibility = [\"//visibility:public\"],\n)\n\nselects.config_setting_group(\n    name = \"is_unix\",\n    match_any = [\n        \":is_linux\",\n        \":is_macos\",\n    ],\n    visibility = [\"//visibility:public\"],\n)\n\n# bazel enables the --ffunction-sections, --gc-sections flags used to remove dead code by default\n# on Linux opt builds. Enable the equivalent macOS flag -Wl,-dead_strip here to work around bazel\n# idiosyncrasies.\n# Note that the flag is defined for all non-debug builds of kj_test() and wd_cc_binary() on mac as\n# it surprisingly appears to be faster, perhaps because the linker generates and writes binaries\n# with much fewer symbols. This also helps reduce local and CI disk usage. If needed, dead code\n# stripping can be limited to optimized builds.\nconfig_setting(\n    name = \"fast_build\",\n    values = {\"compilation_mode\": \"fastbuild\"},\n)\n\nconfig_setting(\n    name = \"opt_build\",\n    values = {\"compilation_mode\": \"opt\"},\n)\n\nbool_flag(\n    name = \"dead_strip\",\n    build_setting_default = True,\n)\n\nconfig_setting(\n    name = \"set_dead_strip\",\n    flag_values = {\"dead_strip\": \"True\"},\n)\n\n# Workaround for bazel not supporting negated conditions (https://github.com/bazelbuild/bazel-skylib/issues/272)\nselects.config_setting_group(\n    name = \"not_dbg_build\",\n    match_any = [\n        \":fast_build\",\n        \":opt_build\",\n    ],\n)\n\nselects.config_setting_group(\n    name = \"use_dead_strip\",\n    match_all = [\n        \"@platforms//os:macos\",\n        \":set_dead_strip\",\n        \":not_dbg_build\",\n    ],\n)\n\n# Clang-tidy config to use\nlabel_flag(\n    name = \"clang_tidy_config\",\n    build_setting_default = \":.clang-tidy\",\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\n[workerd@cloudflare.com](mailto:workerd@cloudflare.com).\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to `workerd`\n\nBefore contributing code to `workerd`, please read these guidelines carefully.\n\n## Questions? Use \"discussions\".\n\nIf you just want to ask a question or have an open-ended conversation, use the \"discussions\" tab rather than filing an issue. This way, the issues list stays restricted to action items\n\n## Before you code: Discuss your change\n\nThis repository is the canonical source code for the core of Cloudflare Workers. The Workers team actively does their development in this repository. Any changes landed in the main branch will automatically become available on the Cloudflare Workers Platform typically within a week or two.\n\nCloudflare Workers has a [strong commitment to backwards-compatibility](https://blog.cloudflare.com/backwards-compatibility-in-cloudflare-workers/). Once a feature is in use on Cloudflare's platform, it generally cannot be taken away. The Workers team will be required to maintain the feature forever. (Note that we do not use semver and cannot just bump a major version number to introduce breaking changes -- [the blog post](https://blog.cloudflare.com/backwards-compatibility-in-cloudflare-workers/) explains why!)\n\nAs a result, we are cautious about what we add. Typically, inside Cloudflare, a new feature will be discussed by product managers and described in design docs long before any code is written.\n\nWhat does that mean for external contributors? The most important thing is:\n\n**For non-trivial changes, always post an issue or discussion before you write code.**\n\nIf you have a very specific proposal for which you're seeking approval, file an issue with the label \"feature proposal\". If your ideas are more open-ended, they may make sense as a discussion instead. Either way, we can then discuss whether we would accept the feature and, if so, give you some hints on how to implement it. We may ask you to write a design doc and do other preparatory work before we make a decision -- just as we would for any internal engineer making such a proposal.\n\nPlease note that we set an extremely high bar for new APIs that are not defined by standards. If you are proposing adding a new non-standard API, it is very likely we will decline. Conversely, if you are proposing adding support for an API that is defined by standards, it is an easier decision for us to accept it.\n\n(For trivial changes, it is OK to go directly to filing a PR, with the understanding that the PR issue itself will serve as the place to discuss the change idea.)\n\n## Hacking on the code\n\nThis codebase includes many unit tests. To run them, do:\n\n```\nbazel test //src/...\n```\n\nYou may find it convenient to have Bazel automatically re-run every time you change a file. You can accomplish this using [watchexec](https://github.com/watchexec/watchexec) like so:\n\n```\nwatchexec --restart --watch src bazel test //src/...\n```\n\n`workerd` is based on KJ, the C++ toolkit library underlying Cap'n Proto. Before writing code, we highly recommend you check out the [KJ style guide](https://github.com/capnproto/capnproto/blob/master/style-guide.md) and the [tour of KJ](https://github.com/capnproto/capnproto/blob/master/kjdoc/tour.md) to understand how to use KJ.\n\n### Using Visual Studio Code for development\n\nSee [this guide](docs/vscode.md) for instructions on how to set up Visual Studio Code for development.\n\nTODO: Add more on tooling best practices, etc.\n\n## Pull requests and code review\n\nThe Cloudflare Workers project has a culture of careful code review. If we find your code hard to review, it's likely that it will take much longer to land, or may be declined entirely for this reason alone. We apply the same standards within our own team.\n\nTo make sure your pull request is as easy to review as possible:\n\n* **Follow the style guide.** Please see the [KJ style guide](https://github.com/capnproto/capnproto/blob/master/style-guide.md) and the [tour of KJ](https://github.com/capnproto/capnproto/blob/master/kjdoc/tour.md) to understand how we write code.\n\n* **Split PRs into small commits wherever possible**, especially when it helps the reviewers separate concerns. The code should compile and all tests should pass at every commit. For example, if you are adding a feature that requires refactoring some code, do the refactoring in a separate commit (or series of commits) from introducing the new feature. Each commit message should describe the particular bit of refactoring in the commit and why it was needed. It's especially important to use separate commits when moving and modifying code. If you need to move a large block of code from one place to another, try your best to do it in an individual commit that is purely a block cut/paste without making any modifications to the code. Then, actually modify the code in the next commit.\n\n* **Don't push fixup commits.** When your reviewer asks for changes, they will want you to rewrite your branch history so that the commit history is clean. We don't want have \"fixup commits\" in our history, but we also don't want to squash-merge a clean commit series and lose the information it provides. You may want to familiarize yourself with `git commit --fixup` -- it makes it relatively easy to rewrite history.\n\n* **Push fixups and rebases separately.** When a reviewer asks for changes, they will wan to review what you change separately from what was already written. It is very important, therefore, that any time you force-push an update to your PR, the push _either_ contains new changes of yours, _or_ rebases onto the latest version of the `main` branch, but _never_ both as the same time. If you do both at once, when your reviewer clicks the \"view changes\" button, they won't be able to tell which changes are yours vs. new changes to the main branch. In this case they may ask you to revert and try again.\n\n* **Use three-way conflict markers.** Unfortunately, it is very hard for code reviewers to review conflict resolutions in general, and this can be a source of bugs. You can reduce the probability of bugs by making sure you've enabled three-way conflict markers in git, by running `git config --global merge.conflictstyle diff3`. This makes conflicts much easier to understand, by showing not just \"yours\" and \"theirs\", but also the original code from which both were derived.\n\n* **Do not submit code you haven't tested.** If you are not able to build your code and run it locally to verify that it does what you expect, please do not submit code changes. Even the best programmers usually write code that doesn't work on the first try. If at all possible, include unit tests for any new functionality you add.\n\n* **Write lots of comments.** Everyone knows that they should write comments, but a lot of programmers still don't do it. Do not expect your reviewers to learn what your code does by reading the implementation. By the time they get to implementation details, they should be simply confirming that they match what was promised. Every declaration in a header file should have a comment if its purpose isn't immediately obvious from the declaration name. (Do not write comments that simply state what is already obvious from the name.) In implementation code, you should have a comment every few lines saying what the next few lines are doing and describing any non-obvious concerns that the code needs to address.\n"
  },
  {
    "path": "Dockerfile.release",
    "content": "FROM node:trixie AS builder\n\nWORKDIR /workerd\n\n# Replacing build-essential with dpkg-dev here, no need to install gcc.\nRUN apt-get update\nRUN apt-get install -y --no-install-recommends curl dpkg-dev git lsb-release wget software-properties-common gnupg tcl\n\nRUN wget https://apt.llvm.org/llvm.sh\n# Drop the install step here so we can install just the packages we need.\nRUN sed -i '/apt-get install/d' llvm.sh\nRUN chmod +x llvm.sh\nRUN ./llvm.sh 19\nRUN apt-get install -y --no-install-recommends clang-19 lld-19 libunwind-19 libc++abi1-19 libc++1-19 libc++-19-dev libclang-rt-19-dev\nCOPY . .\n\nRUN echo \"build:linux --action_env=CC=/usr/lib/llvm-19/bin/clang\" >> .bazelrc\nRUN echo \"build:linux --host_action_env=CC=/usr/lib/llvm-19/bin/clang\" >> .bazelrc\nCOPY .bazel-cache /bazel-disk-cache\n# pnpm version will be different depending on the value of `packageManager` field in package.json\nRUN npm install -g pnpm@latest\nRUN pnpm install\n\nRUN pnpm exec bazel build --disk_cache=/bazel-disk-cache --config=release_linux //src/workerd/server:workerd\n\nFROM scratch as artifact\nCOPY --from=builder /workerd/bazel-bin/src/workerd/server/workerd /workerd-linux-arm64\nCOPY --from=builder /bazel-disk-cache /.bazel-cache\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "MODULE.bazel",
    "content": "\"Bazel dependencies, see https://registry.bazel.build\"\n\nmodule(name = \"workerd\")\n\n# sqlite3 is downloaded from sqlite.org (not GitHub), so it remains manual.\n# We have some patches that aren't included in BCR:\n# https://github.com/bazelbuild/bazel-central-registry/tree/main/modules/sqlite3/3.47.0/patches\nbazel_dep(name = \"sqlite3\")\narchive_override(\n    module_name = \"sqlite3\",\n    build_file = \"//:build/BUILD.sqlite3\",\n    patch_args = [\"-p1\"],\n    patches = [\n        \"//:patches/sqlite/0001-row-counts-plain.patch\",\n        \"//:patches/sqlite/0002-macOS-missing-PATH-fix.patch\",\n        \"//:patches/sqlite/0003-sqlite-complete-early-exit.patch\",\n        \"//:patches/sqlite/0004-invalid-wal-on-rollback-fix.patch\",\n    ],\n    remote_file_integrity = {\n        \"MODULE.bazel\": \"sha256-TtpmqyHyks3o0WcSJO0XFyKkHfAIF98wq06+urs3oKI=\",\n    },\n    remote_file_urls = {\n        \"MODULE.bazel\": [\"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/refs/heads/main/modules/sqlite3/3.47.0/MODULE.bazel\"],\n    },\n    sha256 = \"f59c349bedb470203586a6b6d10adb35f2afefa49f91e55a672a36a09a8fedf7\",\n    strip_prefix = \"sqlite-src-3470000\",\n    url = \"https://sqlite.org/2024/sqlite-src-3470000.zip\",\n)\n\n# Automatically managed dependencies\ninclude(\"//build/deps:gen/deps.MODULE.bazel\")\ninclude(\"//build/deps:gen/build_deps.MODULE.bazel\")\ninclude(\"//build/deps:gen/shared_deps.MODULE.bazel\")\n\n# Must be loaded after automated dependencies\ninclude(\"//build/deps:rust.MODULE.bazel\")\ninclude(\"//build/deps:nodejs.MODULE.bazel\")\ninclude(\"//build/deps:python.MODULE.bazel\")\ninclude(\"//build/deps:v8.MODULE.bazel\")\ninclude(\"//build/deps:oci.MODULE.bazel\")\ninclude(\"//build/deps/formatters:rustfmt.MODULE.bazel\")\n\n# compatibility proxy is needed when using current rules_cc with Bazel 8\ncompat = use_extension(\"@rules_cc//cc:extensions.bzl\", \"compatibility_proxy\")\nuse_repo(compat, \"cc_compatibility_proxy\")\n\ncc_configure = use_extension(\"@rules_cc//cc:extensions.bzl\", \"cc_configure_extension\")\nuse_repo(cc_configure, \"local_config_cc\")\n"
  },
  {
    "path": "README.md",
    "content": "# 👷 `workerd`, Cloudflare's JavaScript/Wasm Runtime\n\n![Banner](/docs/assets/banner.png)\n\n`workerd` (pronounced: \"worker-dee\") is a JavaScript / Wasm server runtime based on the same code that powers [Cloudflare Workers](https://workers.dev).\n\nYou might use it:\n\n* **As an application server**, to self-host applications designed for Cloudflare Workers.\n* **As a development tool**, to develop and test such code locally.\n* **As a programmable HTTP proxy** (forward or reverse), to efficiently intercept, modify, and\n  route network requests.\n\n## Introduction\n\n### Design Principles\n\n* **Server-first:** Designed for servers, not CLIs nor GUIs.\n\n* **Standard-based:** Built-in APIs are based on web platform standards, such as `fetch()`.\n\n* **Nanoservices:** Split your application into components that are decoupled and independently-deployable like microservices, but with performance of a local function call. When one nanoservice calls another, the callee runs in the same thread and process.\n\n* **Homogeneous deployment:** Instead of deploying different microservices to different machines in your cluster, deploy all your nanoservices to every machine in the cluster, making load balancing much easier.\n\n* **Capability bindings:** `workerd` configuration uses capabilities instead of global namespaces to connect nanoservices to each other and external resources. The result is code that is more composable -- and immune to SSRF attacks.\n\n* **Always backwards compatible:** Updating `workerd` to a newer version will never break your JavaScript code. `workerd`'s version number is simply a date, corresponding to the maximum [\"compatibility date\"](https://developers.cloudflare.com/workers/platform/compatibility-dates/) supported by that version. You can always configure your worker to a past date, and `workerd` will emulate the API as it existed on that date.\n\n[Read the blog post to learn more about these principles.](https://blog.cloudflare.com/workerd-open-source-workers-runtime/)\n\n### WARNING: `workerd` is not a hardened sandbox\n\n`workerd` tries to isolate each Worker so that it can only access the resources it is configured to access. However, `workerd` on its own does not contain suitable defense-in-depth against the possibility of implementation bugs. When using `workerd` to run possibly-malicious code, you must run it inside an appropriate secure sandbox, such as a virtual machine. The Cloudflare Workers hosting service in particular [uses many additional layers of defense-in-depth](https://blog.cloudflare.com/mitigating-spectre-and-other-security-threats-the-cloudflare-workers-security-model/).\n\nWith that said, if you discover a bug that allows malicious code to break out of `workerd`, please submit it to [Cloudflare's bug bounty program](https://hackerone.com/cloudflare?type=team) for a reward.\n\n## Getting Started\n\n### Supported Platforms\n\nIn theory, `workerd` should work on any POSIX system that is supported by V8 and Windows.\n\nIn practice, `workerd` is tested on:\n\n* Linux and macOS (x86-64 and arm64 architectures)\n* Windows (x86-64 architecture)\n\nOn other platforms, you may have to do tinkering to make things work.\n\n### Building `workerd`\n\nTo build `workerd`, you need:\n\n* Bazel\n  * If you use [Bazelisk](https://github.com/bazelbuild/bazelisk) (recommended), it will automatically download and use the right version of Bazel for building workerd.\n* On Linux:\n  * We use the clang/LLVM toolchain to build workerd and support version 19 and higher. Earlier versions of clang may still work, but are not officially supported.\n  * Clang 19+ (e.g. package `clang-19` on Debian Trixie). If clang is installed as `clang-<version>` please create a symlink to it in your PATH named `clang`, or use `--action_env=CC=clang-<version>` on `bazel` command lines to specify the compiler name.\n\n  * libc++ 19+ (e.g. packages `libc++-19-dev` and `libc++abi-19-dev`)\n  * LLD 19+ (e.g. package `lld-19`).\n  * `python3`, `python3-distutils`, and `tcl8.6`\n* On macOS:\n  * Xcode 16.3 installation (available on macOS 15 and higher). Building with just the Xcode Command Line Tools is not being tested, but should work too.\n  * Homebrew installed `tcl-tk` package (provides Tcl 8.6)\n* On Windows:\n  * Install [App Installer](https://learn.microsoft.com/en-us/windows/package-manager/winget/#install-winget)\n    from the Microsoft Store for the `winget` package manager and then run\n    [install-deps.bat](tools/windows/install-deps.bat) from an administrator prompt to install\n    bazelisk, LLVM, and other dependencies required to build workerd on Windows.\n  * Add `startup --output_user_root=C:/tmp` to the `.bazelrc` file in your user directory.\n  * When developing at the command-line, run [bazel-env.bat](tools/windows/bazel-env.bat) in your shell first\n    to select tools and Windows SDK versions before running bazel.\n\nYou may then build `workerd` at the command-line with:\n\n```sh\nbazel build //src/workerd/server:workerd\n```\n\nYou can pass `--config=release` to compile in release mode:\n\n```sh\nbazel build //src/workerd/server:workerd --config=release\n```\n\nYou can also build from within Visual Studio Code using the instructions in [docs/vscode.md](docs/vscode.md).\n\nThe compiled binary will be located at `bazel-bin/src/workerd/server/workerd`.\n\nIf you run a Bazel build before you've installed some dependencies (like clang or libc++), and then you install the dependencies, you must resync locally cached toolchains, or clean Bazel's cache, otherwise you might get strange errors:\n\n```sh\nbazel fetch --configure --force\n```\n\nIf that fails, you can try:\n\n```sh\nbazel clean --expunge\n```\n\nThe cache will now be cleaned and you can try building again.\n\nIf you have a fairly recent clang packages installed you can build a more performant release\nversion of workerd:\n\n```sh\nbazel build --config=thin-lto //src/workerd/server:workerd\n```\n\n### Configuring `workerd`\n\n`workerd` is configured using a config file written in Cap'n Proto text format.\n\nA simple \"Hello World!\" config file might look like:\n\n```capnp\nusing Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n  ],\n\n  sockets = [\n    # Serve HTTP on port 8080.\n    ( name = \"http\",\n      address = \"*:8080\",\n      http = (),\n      service = \"main\"\n    ),\n  ]\n);\n\nconst mainWorker :Workerd.Worker = (\n  serviceWorkerScript = embed \"hello.js\",\n  compatibilityDate = \"2023-02-28\",\n  # Learn more about compatibility dates at:\n  # https://developers.cloudflare.com/workers/platform/compatibility-dates/\n);\n```\n\nWhere `hello.js` contains:\n\n```javascript\naddEventListener(\"fetch\", event => {\n  event.respondWith(new Response(\"Hello World\"));\n});\n```\n\n[Complete reference documentation is provided by the comments in workerd.capnp.](src/workerd/server/workerd.capnp)\n\n[There is also a library of sample config files.](samples)\n\n### Running `workerd`\n\nTo serve your config, do:\n\n`workerd serve my-config.capnp`\n\nFor more details about command-line usage, use `workerd --help`.\n\nPrebuilt binaries are distributed via `npm`. Run `npx workerd ...` to use these. If you're running a prebuilt binary, you'll need to make sure your system has the right dependencies installed:\n\n* On Linux:\n  * glibc 2.35 or higher (already included on e.g. Ubuntu 22.04, Debian Bookworm)\n* On macOS:\n  * macOS 13.5 or higher\n  * The Xcode command line tools, which can be installed with `xcode-select --install`\n* x86_64 CPU with at least SSE4.2 and CLMUL ISA extensions, or arm64 CPU with CRC extension (enabled by default under armv8.1-a). These extensions are supported by all recent x86 and arm64 CPUs.\n\n### Local Worker development with `wrangler`\n\nYou can use [Wrangler](https://developers.cloudflare.com/workers/wrangler/) (v3.0 or greater) to develop Cloudflare Workers locally, using `workerd`. First, run the following command to configure Miniflare to use this build of `workerd`.\n\n```\nexport MINIFLARE_WORKERD_PATH=\"<WORKERD_REPO_DIR>/bazel-bin/src/workerd/server/workerd\"\n```\n\nThen, run:\n\n`wrangler dev`\n\n### Serving in production\n\n`workerd` is designed to be unopinionated about how it runs.\n\nOne good way to manage `workerd` in production is using `systemd`. Particularly useful is `systemd`'s ability to open privileged sockets on `workerd`'s behalf while running the service itself under an unprivileged user account. To help with this, `workerd` supports inheriting sockets from the parent process using the `--socket-fd` flag.\n\nHere's an example system service file, assuming your config defines two sockets named `http` and `https`:\n\n```sh\n# /etc/systemd/system/workerd.service\n[Unit]\nDescription=workerd runtime\nAfter=local-fs.target remote-fs.target network-online.target\nRequires=local-fs.target remote-fs.target workerd.socket\nWants=network-online.target\n\n[Service]\nType=exec\nExecStart=/usr/bin/workerd serve /etc/workerd/config.capnp --socket-fd http=3 --socket-fd https=4\nSockets=workerd.socket\n\n# If workerd crashes, restart it.\nRestart=always\n\n# Run under an unprivileged user account.\nUser=nobody\nGroup=nogroup\n\n# Hardening measure: Do not allow workerd to run suid-root programs.\nNoNewPrivileges=true\n\n[Install]\nWantedBy=multi-user.target\n```\n\nAnd corresponding sockets file:\n\n```sh\n# /etc/systemd/system/workerd.socket\n[Unit]\nDescription=sockets for workerd\nPartOf=workerd.service\n\n[Socket]\nListenStream=0.0.0.0:80\nListenStream=0.0.0.0:443\n\n[Install]\nWantedBy=sockets.target\n```\n\nOnce these files are in place you can enable the service -- see the systemd documentation or ask your favorite LLM for details.\n"
  },
  {
    "path": "RELEASE.md",
    "content": "# Releasing `workerd` and `workers-types` to NPM\n\nThe primary distribution channel for `workerd` right now is through `npm`. We use a (mostly) automatic CI setup to release and publish both `workerd` and `workers-types`.\n\n## Versioning\n\n`workerd` and `workers-types` follow the same versioning system. The major version is currently `1` for `workerd` and `4` for `workers-types`. The minor version for both is the date specified in [release-version.txt](src/workerd/io/release-version.txt).\n\n## Cutting Releases\n\nThis is pretty simple, and completely automatic—every time the date in [release-version.txt](src/workerd/io/release-version.txt) changes, a new release is generated, along with the built binaries for `linux-64`, `linux-arm64`, `darwin-64`, `darwin-arm64` and `windows-64`. The types will also be generated and published automatically. This is governed by the [release.yml](.github/workflows/release.yml) GitHub Action.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\nhttps://www.cloudflare.com/disclosure\n\n## Reporting a Vulnerability\n\n* https://hackerone.com/cloudflare\n  * All Cloudflare products are in scope for reporting. If you submit a valid report on bounty-eligible assets through our disclosure program, we will transfer your report to our private bug bounty program and invite you as a participant.\n* `mailto:security@cloudflare.com`\n  * If you'd like to encrypt your message, please do so within the the body of the message. Our email system doesn't handle PGP-MIME well.\n  * https://www.cloudflare.com/gpg/security-at-cloudflare-pubkey-06A67236.txt\n\nAll abuse reports should be submitted to our Trust & Safety team through our dedicated page: https://www.cloudflare.com/abuse/\n\n"
  },
  {
    "path": "build/AGENTS.md",
    "content": "# build/ — Bazel Build Rules\n\n## OVERVIEW\n\nCustom Bazel rules (`wd_*` macros) for C++, TypeScript, Rust, Cap'n Proto, and test orchestration. Uses bzlmod (`MODULE.bazel`), not WORKSPACE. This is build system definitions, NOT build output (`bazel-bin/`).\n\n## KEY RULES\n\n| Rule                                       | Purpose                                                                                           |\n| ------------------------------------------ | ------------------------------------------------------------------------------------------------- |\n| `wd_cc_library.bzl`                        | Wraps `cc_library`; `strip_include_prefix=\"/src\"`, arch-specific CPU flags (CRC32C)               |\n| `wd_cc_binary.bzl`                         | Wraps `cc_binary`; macOS dead-strip linkopts; creates `_cross` alias for prebuilt arm64 binaries  |\n| `wd_cc_embed.bzl`                          | Binary/text data -> C++ via C23 `#embed`; auto-detects text vs binary by extension                |\n| `wd_cc_benchmark.bzl`                      | Google Benchmark wrapper; generates CSV report genrule                                            |\n| `wd_test.bzl`                              | `.wd-test` config test runner; generates up to 3 variants per test                                |\n| `kj_test.bzl`                              | C++ unit test wrapper; also generates test variants                                               |\n| `wpt_test.bzl`                             | Web Platform Tests; generates JS runner + `.wd-test` config from WPT tree; delegates to `wd_test` |\n| `wd_ts_bundle.bzl`                         | TypeScript compilation + JS bundle generation                                                     |\n| `wd_js_bundle.bzl`                         | JS bundle -> Cap'n Proto `Modules.Bundle` embedding via generated `.capnp`                        |\n| `wd_capnp_library.bzl`                     | Cap'n Proto schema compilation                                                                    |\n| `wd_rust_crate.bzl` / `wd_rust_binary.bzl` | Rust build rules                                                                                  |\n| `lint_test.bzl`                            | ESLint integration                                                                                |\n\n**Conventions:**\n\n- `_cross` alias pattern: every `wd_cc_binary` gets a `name_cross` alias selecting prebuilt arm64 or source build\n- Test tags: `off-by-default`, `requires-container-engine`, `no-asan`, `no-coverage`\n- Variant generation controllable per-test via `generate_*_variant` booleans\n- `BUILD.*` files: overlay build files for third-party deps (sqlite3, zlib, simdutf, pyodide, wpt)\n\n## DEPENDENCY MANAGEMENT\n\nLives in `deps/`. Uses jsonc manifests + codegen:\n\n- `deps.jsonc`, `build_deps.jsonc`, `shared_deps.jsonc` — dependency specifications\n- `update-deps.py [dep_name]` — fetches latest versions, computes hashes, regenerates `gen/` MODULE.bazel fragments\n- `gen/` — **autogenerated**; do not hand-edit\n- `*.MODULE.bazel` (e.g., `rust.MODULE.bazel`, `v8.MODULE.bazel`) — included by root `MODULE.bazel`\n- `workerd-v8/` — separate Bazel module wrapping V8 dependency\n- `python/` — Pyodide package lists (versioned `.bzl` files)\n"
  },
  {
    "path": "build/BUILD.all_pyodide_wheels",
    "content": "filegroup(\n    name = \"whls\",\n    srcs = glob([\"*\"]),\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "build/BUILD.nbytes",
    "content": "load(\"@rules_cc//cc:cc_library.bzl\", \"cc_library\")\n\ncc_library(\n    name = \"nbytes\",\n    srcs = [\"src/nbytes.cpp\"],\n    hdrs = [\"include/nbytes.h\"],\n    strip_include_prefix = \"include\",\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "build/BUILD.pyodide",
    "content": "exports_files(\n    glob(\n        [\"pyodide/*\"],\n    ),\n)\n"
  },
  {
    "path": "build/BUILD.pyodide_packages",
    "content": "exports_files(\n    glob([\"*\"]),\n)\n"
  },
  {
    "path": "build/BUILD.simdutf",
    "content": "load(\"@rules_cc//cc:cc_library.bzl\", \"cc_library\")\n\ncc_library(\n    name = \"simdutf\",\n    srcs = [\"simdutf.cpp\"],\n    hdrs = [\"simdutf.h\"],\n    copts = select({\n        # Enable SSE4.2 extensions, this reduces code size as the fallback code path for pre-Westmere\n        # CPUs is no longer needed.\n        \"@platforms//cpu:x86_64\": [\"-msse4.2\"],\n        \"//conditions:default\": [],\n    }) + [\n        \"-Wno-unused-function\",\n        \"-Wno-unused-const-variable\",\n    ],\n    defines = [\"SIMDUTF_ATOMIC_REF\"],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "build/BUILD.sqlite3",
    "content": "\"\"\"\nBuilds sqlite3 from plain source.\n\nThis is just enough to build the sqlite3 library; it does not include\nthe shell or any other utilities.\n\"\"\"\n\npackage(default_visibility = [\"//visibility:private\"])\n\nSQLITE_DEFINES = [\n    \"SQLITE_MAX_ALLOCATION_SIZE=16777216\",  # 16MB\n    \"SQLITE_PRINTF_PRECISION_LIMIT=100000\",\n    \"SQLITE_ENABLE_FTS5\",\n    \"SQLITE_ENABLE_NORMALIZE\",\n    \"SQLITE_ENABLE_MATH_FUNCTIONS\",\n    \"SQLITE_DEFAULT_FOREIGN_KEYS=1\",\n    \"SQLITE_ENABLE_UPDATE_DELETE_LIMIT\",\n]\n\nSQLITE_DEFINES_FOR_LEMON = \" \".join([\"-D\" + x for x in SQLITE_DEFINES])\n\nGIVEN_SOURCES = glob([\n    \"src/**/*.h\",\n    \"src/**/*.c\",\n    \"ext/**/*.h\",\n    \"ext/**/*.c\",\n])\n\nGENERATED_SOURCES = []\n\n# Regarding src/vdbe.c: In the normal SQLite build process, the given\n# src/vdbe.c is run through tool/vdbe-compress.tcl before use. There\n# is an amalgamation-build-time option called SQLITE_SMALL_STACK; if\n# this option is not given, then vdbe-compress.tcl does nothing; it\n# just emits its input verbatim.\n#\n# We have not set SQLITE_SMALL_STACK, so this Bazel-build file does\n# not bother with tool/vdbe-compress.tcl.\n\ncc_binary(\n    name = \"lemon\",\n    srcs = [\"tool/lemon.c\"],\n    copts = [\"-w\"],\n)\n\n# ========================================================================\n# Constructs parse.{c,h} using the Lemon parser generator.\n\ngenrule(\n    name = \"parse_ch\",\n    srcs = [\n        \"src/parse.y\",\n        \"tool/lempar.c\",\n    ],\n    outs = [\n        \"parse.h\",\n        \"parse.c\",\n    ],\n    cmd = (\n        # lemon requires lempar.c to be in the current working\n        # directory, and parse.y has to be in a writable directory\n        # since the output files are created adjacent to it.\n        \"cp $(SRCS) . \" +\n\n        # Creates parse.c and parse.h\n        \"&& $(execpath :lemon) {} parse.y \".format(SQLITE_DEFINES_FOR_LEMON) +\n\n        # Bazel expects genrule outputs to be in RULEDIR\n        \"&& cp parse.h parse.c $(RULEDIR)\"\n    ),\n    tools = [\n        \":lemon\",\n    ],\n)\n\nGENERATED_SOURCES += [\n    \"parse.h\",\n    \"parse.c\",\n]\n\n# ========================================================================\n# Constructs fts5parse.{c,h} using the Lemon parser generator.\n\ngenrule(\n    name = \"fts5parse_ch\",\n    srcs = [\n        \"ext/fts5/fts5parse.y\",\n        \"tool/lempar.c\",\n    ],\n    outs = [\n        \"fts5parse.h\",\n        \"fts5parse.c\",\n    ],\n    cmd = (\n        # Same as :parse_ch\n        \"cp $(SRCS) . \" +\n        \"&& $(execpath :lemon) {} fts5parse.y \".format(SQLITE_DEFINES_FOR_LEMON) +\n        # Bazel expects genrule outputs to be in RULEDIR\n        \"&& cp fts5parse.h fts5parse.c $(RULEDIR)\"\n    ),\n    tools = [\n        \":lemon\",\n    ],\n)\n\nGENERATED_SOURCES += [\n    \"fts5parse.h\",\n    \"fts5parse.c\",\n]\n\n# ========================================================================\n# Constructs fts5.{c,h}. \"FTS5\" is version 5 of Full Text Search.\n\nfilegroup(\n    name = \"fts5_sources\",\n    srcs = glob([\n        \"ext/fts5/*.h\",\n        \"ext/fts5/*.c\",\n    ]),\n)\n\ngenrule(\n    name = \"fts5_ch\",\n    srcs = [\n        \"fts5parse.h\",\n        \"fts5parse.c\",\n        \"manifest\",\n        \"manifest.uuid\",\n        \":fts5_sources\",\n    ],\n    outs = [\n        \"fts5.h\",\n        \"fts5.c\",\n    ],\n    cmd = (\n        \"mkdir -p $(RULEDIR)/build/ext/fts5/tool \" +\n\n        # Copy all the inputs over so the directory structure is to\n        # mkfts5c.tcl's liking. This ends up putting everything not in\n        # :fts5_sources in there twice, once in build/ and once in\n        # build/ext/fts5/, but it doesn't hurt anything. :shrug:\n        \"&& cp $(SRCS) $(RULEDIR)/build/ext/fts5 \" +\n        \"&& cp $(location manifest) $(RULEDIR)/build \" +\n        \"&& cp $(location manifest.uuid) $(RULEDIR)/build \" +\n        \"&& cp $(location fts5parse.h) $(RULEDIR)/build \" +\n        \"&& cp $(location fts5parse.c) $(RULEDIR)/build \" +\n        \"&& cp $(location ext/fts5/tool/mkfts5c.tcl) $(RULEDIR)/build/ext/fts5/tool \" +\n\n        # Okay, go.\n        \"&& pushd $(RULEDIR)/build >/dev/null \" +\n        \"&& tclsh ext/fts5/tool/mkfts5c.tcl \" +\n        \"&& popd >/dev/null \" +\n\n        # Put the outputs where Bazel will see them.\n        \"&& mv $(RULEDIR)/build/fts5.c $(RULEDIR)/build/ext/fts5/fts5.h $(RULEDIR) \" +\n\n        # Done. Clean up after ourselves.\n        \"&& rm -r $(RULEDIR)/build\"\n    ),\n    tools = [\n        \"ext/fts5/tool/mkfts5c.tcl\",\n    ],\n)\n\nGENERATED_SOURCES += [\"fts5.c\"]\n\n# ========================================================================\n# These rules construct various source files using provided utility programs.\n\ngenrule(\n    name = \"opcodes_h\",\n    srcs = [\n        \"parse.h\",\n        \"src/vdbe.c\",\n    ],\n    outs = [\"opcodes.h\"],\n    cmd = \"cat $(location parse.h) $(location src/vdbe.c) | tclsh $(location tool/mkopcodeh.tcl) > $(RULEDIR)/opcodes.h\",\n    tools = [\"tool/mkopcodeh.tcl\"],\n)\n\nGENERATED_SOURCES += [\"opcodes.h\"]\n\ngenrule(\n    name = \"opcodes_c\",\n    srcs = [\"opcodes.h\"],\n    outs = [\"opcodes.c\"],\n    cmd = \"tclsh $(location tool/mkopcodec.tcl) $(location opcodes.h) > $(RULEDIR)/opcodes.c\",\n    tools = [\"tool/mkopcodec.tcl\"],\n)\n\nGENERATED_SOURCES += [\"opcodes.c\"]\n\ncc_binary(\n    name = \"mkkeywordhash\",\n    srcs = [\"tool/mkkeywordhash.c\"],\n    copts = [\"-w\"],\n)\n\ngenrule(\n    name = \"keywordhash_h\",\n    outs = [\"keywordhash.h\"],\n    cmd = \"$(execpath :mkkeywordhash) > $(RULEDIR)/keywordhash.h\",\n    tools = [\":mkkeywordhash\"],\n)\n\nGENERATED_SOURCES += [\"keywordhash.h\"]\n\n# ========================================================================\n# Constructs sqlite3.h.\n\ncc_binary(\n    name = \"mksourceid\",\n    srcs = [\"tool/mksourceid.c\"],\n    copts = [\"-w\"],\n)\n\ngenrule(\n    name = \"sqlite3_h\",\n    srcs = [\n        # The first few dependencies come from the \"sqlite3.h\" target\n        # in main.mk in the SQLite source distribution.\n        \"src/sqlite.h.in\",\n        \"manifest\",\n        \"VERSION\",\n        \"ext/rtree/sqlite3rtree.h\",\n\n        # These come from looking for \"set filelist\" in mksqlite3h.tcl\n        # and looking for files not named above. The script fails with\n        # a complaint about a missing file if these aren't here.\n        \"ext/session/sqlite3session.h\",\n        \"ext/fts5/fts5.h\",\n    ],\n    outs = [\"sqlite3.h\"],\n\n    # mksqlite3h.tcl expects to run in a directory with a very\n    # particular structure, so we have to set that up for it.\n    #\n    # We use $(RULEDIR)/build since RULEDIR is guaranteed to be\n    # writable.\n    cmd = (\n        \"mkdir -p $(RULEDIR)/build/src $(RULEDIR)/build/ext/rtree $(RULEDIR)/build/ext/session $(RULEDIR)/build/ext/fts5 \" +\n\n        # TODO(cleanup): come up with a less-repetitive way to do this.\n        \"&& cp $(location tool/mksqlite3h.tcl) $(RULEDIR)/build \" +\n        \"&& cp $(location src/sqlite.h.in) $(RULEDIR)/build/src/sqlite.h.in \" +\n        \"&& cp $(location manifest) $(RULEDIR)/build/manifest \" +\n        \"&& cp $(location VERSION) $(RULEDIR)/build/VERSION \" +\n        \"&& cp $(location ext/rtree/sqlite3rtree.h) $(RULEDIR)/build/ext/rtree/sqlite3rtree.h \" +\n        \"&& cp $(location ext/session/sqlite3session.h) $(RULEDIR)/build/ext/session/sqlite3session.h \" +\n        \"&& cp $(location ext/fts5/fts5.h) $(RULEDIR)/build/ext/fts5/fts5.h \" +\n\n        # It also expects to invoke mksourceid.\n        \"&& cp $(execpath :mksourceid) $(RULEDIR)/build/mksourceid \" +\n\n        # Okay, go.\n        \"&& pushd $(RULEDIR)/build >/dev/null \" +\n        \"&& tclsh mksqlite3h.tcl . > ../sqlite3.h \" +\n        \"&& popd >/dev/null \" +\n\n        # Done. Clean up after ourselves.\n        \"&& rm -r $(RULEDIR)/build\"\n    ),\n    tools = [\n        \"tool/mksqlite3h.tcl\",\n        \":mksourceid\",\n    ],\n)\n\nGENERATED_SOURCES += [\"sqlite3.h\"]\n\n# ========================================================================\n# Constructs the amalgamation. Well, the rest of it; sqlite3.h is one\n# of the files in the amalgamation.\n\ngenrule(\n    name = \"amalgamation\",\n    srcs = GIVEN_SOURCES + GENERATED_SOURCES,\n    outs = [\n        \"sqlite3.c\",\n        \"sqlite3ext.h\",\n    ],\n\n    # mksqlite3c.tcl expects to run in a directory with a very\n    # particular structure, so we have to set that up for it.\n    #\n    # We use $(RULEDIR)/build/tsrc since RULEDIR is guaranteed to be\n    # writable. (\"tsrc\" matches the directory name in the SQLite\n    # Makefile.)\n    cmd = (\n        \"mkdir -p $(RULEDIR)/build/tsrc $(RULEDIR)/build/tool \" +\n\n        # Copy everything in. There's no subdirectories; everything\n        # but mksqlite3c.tcl just goes in tsrc.\n        \"&& cp $(SRCS) $(RULEDIR)/build/tsrc \" +\n        \"&& cp $(location tool/mksqlite3c.tcl) $(RULEDIR)/build/tool \" +\n\n        # Build the thing.\n        #\n        # This step (\"make sqlite3c\") is where the SQLite Makefile\n        # would also construct tclsqlite3.c, but we don't use that\n        # file, so we don't bother building it.\n        \"&& pushd $(RULEDIR)/build >/dev/null \" +\n        \"&& tclsh tool/mksqlite3c.tcl \" +\n        \"&& popd >/dev/null \" +\n\n        # Copy the outputs somewhere that Bazel will find them.\n        \"&& cp $(RULEDIR)/build/sqlite3.c $(RULEDIR)/build/tsrc/sqlite3ext.h $(RULEDIR)/ \" +\n\n        # Done. Clean up after ourselves.\n        \"&& rm -r $(RULEDIR)/build\"\n    ),\n    tools = [\n        \"tool/mksqlite3c.tcl\",\n    ],\n)\n# ========================================================================\n# Actually builds the library.\n\ncc_library(\n    name = \"sqlite3\",\n    srcs = [\"sqlite3.c\"],\n    hdrs = [\n        \"sqlite3.h\",\n        \"sqlite3ext.h\",\n    ],\n    copts = [\"-w\"],  # Ignore all warnings. This is not our code, we can't fix the warnings.\n    defines = SQLITE_DEFINES,\n    include_prefix = \".\",\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "build/BUILD.wpt",
    "content": "# Copyright (c) 2017-2022 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\nload(\"@workerd//:build/wpt_get_directories.bzl\", \"wpt_all_dirs\")\nload(\"@workerd//:build/wpt_test.bzl\", \"wpt_module\", \"wpt_server_entrypoint\")\n\n[wpt_module(\n    name = dir,\n) for dir in wpt_all_dirs()]\n\nwpt_server_entrypoint(\n    name = \"entrypoint\",\n    srcs = [\"wpt\"] + glob([\"**/*.py\"]),\n    python = \"@@rules_python++python+python_3_13//:python3\",\n    visibility = [\"//visibility:public\"],\n)\n\nexports_files([\n    \"tools/certs/cacert.pem\",\n])\n"
  },
  {
    "path": "build/BUILD.zlib",
    "content": "# This config closely follows the original GN build file\n# Ref: https://chromium.googlesource.com/chromium/src/third_party/zlib.git/+/refs/heads/main/BUILD.gn\n# The main difference is that we don't set ZLIB_DLL because it messes things up on Windows.\n# Other aspects that are not ported to make this easier to maintain: Support for architectures other\n# than x86_64 or arm64\n\nload(\"@bazel_skylib//lib:selects.bzl\", \"selects\")\nload(\"@rules_cc//cc:cc_library.bzl\", \"cc_library\")\n\nzlib_warnings = [\n    \"-Wno-incompatible-pointer-types\",\n    \"-Wunused-variable\",\n]\n\nselects.config_setting_group(\n    name = \"x86_linux\",\n    match_all = [\n        \"@platforms//cpu:x86_64\",\n        \"@platforms//os:linux\",\n    ],\n)\n\nselects.config_setting_group(\n    name = \"x86_macos\",\n    match_all = [\n        \"@platforms//cpu:x86_64\",\n        \"@platforms//os:macos\",\n    ],\n)\n\nselects.config_setting_group(\n    name = \"x86_windows\",\n    match_all = [\n        \"@platforms//cpu:x86_64\",\n        \"@platforms//os:windows\",\n    ],\n)\n\nselects.config_setting_group(\n    name = \"arm64_linux\",\n    match_all = [\n        \"@platforms//cpu:aarch64\",\n        \"@platforms//os:linux\",\n    ],\n)\n\nselects.config_setting_group(\n    name = \"arm64_macos\",\n    match_all = [\n        \"@platforms//cpu:aarch64\",\n        \"@platforms//os:macos\",\n    ],\n)\n\nselects.config_setting_group(\n    name = \"arm64_windows\",\n    match_all = [\n        \"@platforms//cpu:aarch64\",\n        \"@platforms//os:windows\",\n    ],\n)\n\ncc_library(\n    name = \"zlib_common_headers\",\n    hdrs = [\n        \"chromeconf.h\",\n        \"deflate.h\",\n        \"inffast.h\",\n        \"inffixed.h\",\n        \"inflate.h\",\n        \"inftrees.h\",\n        \"zconf.h\",\n        \"zlib.h\",\n        \"zutil.h\",\n    ],\n    features = [\"-parse_headers\"],\n    # For zlib to be found using <zlib.h> before system zlib, use this flag for the include to also be added using isystem.\n    includes = [\".\"],\n    local_defines = [\"ZLIB_IMPLEMENTATION\"],\n)\n\ncc_library(\n    name = \"zlib_adler32_simd\",\n    srcs = [\n        \"adler32_simd.c\",\n        \"adler32_simd.h\",\n    ],\n    copts = select({\n        \"@platforms//cpu:x86_64\": [\"-mssse3\"],\n        \"//conditions:default\": [],\n    }),\n    local_defines = [\"ZLIB_IMPLEMENTATION\"] + select({\n        \"@platforms//cpu:x86_64\": [\"ADLER32_SIMD_SSSE3\"],\n        \"@platforms//cpu:aarch64\": [\"ADLER32_SIMD_NEON\"],\n    }) + select({\n        \":x86_linux\": [\"X86_NOT_WINDOWS\"],\n        \":x86_macos\": [\"X86_NOT_WINDOWS\"],\n        \":x86_windows\": [\"X86_WINDOWS\"],\n        \"//conditions:default\": [],\n    }),\n    deps = [\":zlib_common_headers\"],\n)\n\ncc_library(\n    name = \"zlib_arm_crc32\",\n    srcs = [\n        \"crc32_simd.c\",\n        \"crc32_simd.h\",\n    ],\n    # TODO(soon): The canonical GN build adds \"-march=armv8-a+aes+crc\" here. We could do the same,\n    # but this may reduce the target CPU level if the compiler defaults to e.g. armv8.5-a. We really\n    # should compile all of workerd with the same compile options. For now, just specify -mcrc which\n    # adds CRC instructions to whatever is already defined. The source file still compiles without\n    # explicitly enabling AES/PMULL extensions as these are used in inline assembly and only enabled\n    # if support for them is detected at runtime.\n    copts = [\"-mcrc\"],\n    local_defines = [\n        \"CRC32_ARMV8_CRC32\",\n        \"ZLIB_IMPLEMENTATION\",\n    ] + select({\n        \"@platforms//os:linux\": [\"ARMV8_OS_LINUX\"],\n        \"@platforms//os:macos\": [\"ARMV8_OS_MACOS\"],\n        \"@platforms//os:windows\": [\"ARMV8_OS_WINDOWS\"],\n    }),\n    target_compatible_with = select({\n        \"@platforms//cpu:aarch64\": [],\n        \"//conditions:default\": [\"@platforms//:incompatible\"],\n    }),\n    deps = [\":zlib_common_headers\"],\n)\n\ncc_library(\n    name = \"zlib_inflate_chunk_simd\",\n    srcs = [\n        \"contrib/optimizations/chunkcopy.h\",\n        \"contrib/optimizations/inffast_chunk.c\",\n        \"contrib/optimizations/inffast_chunk.h\",\n        \"contrib/optimizations/inflate.c\",\n    ],\n    copts = zlib_warnings,\n    features = [\"-parse_headers\"],\n    local_defines = [\n        \"INFLATE_CHUNK_READ_64LE\",\n        \"ZLIB_IMPLEMENTATION\",\n    ] + select({\n        \"@platforms//cpu:x86_64\": [\n            \"INFLATE_CHUNK_SIMD_SSE2\",\n        ],\n        \"@platforms//cpu:aarch64\": [\n            \"INFLATE_CHUNK_SIMD_NEON\",\n        ],\n    }),\n    deps = [\":zlib_common_headers\"],\n)\n\ncc_library(\n    name = \"zlib_crc32_simd\",\n    srcs = [\n        \"crc32_simd.c\",\n        \"crc32_simd.h\",\n        \"crc_folding.c\",\n    ],\n    copts = [\n        \"-msse4.2\",\n        \"-mpclmul\",\n    ],\n    local_defines = [\n        \"CRC32_SIMD_SSE42_PCLMUL\",\n        \"ZLIB_IMPLEMENTATION\",\n    ],\n    target_compatible_with = select({\n        \"@platforms//cpu:x86_64\": [],\n        \"//conditions:default\": [\"@platforms//:incompatible\"],\n    }),\n    deps = [\":zlib_common_headers\"],\n)\n\ncc_library(\n    name = \"zlib_slide_hash_simd\",\n    srcs = [\n        \"slide_hash_simd.h\",\n    ],\n    # This target is header only so these are not needed to compile correctly, but still useful for\n    # header parsing (i.e. parse_headers feature).\n    local_defines = [\"ZLIB_IMPLEMENTATION\"] + select({\n        \"@platforms//cpu:x86_64\": [\"DEFLATE_SLIDE_HASH_SSE2\"],\n        \"@platforms//cpu:aarch64\": [\"DEFLATE_SLIDE_HASH_NEON\"],\n    }),\n    deps = [\":zlib_common_headers\"],\n)\n\nfilegroup(\n    name = \"zlib_files\",\n    srcs = [\n        \"adler32.c\",\n        \"chromeconf.h\",\n        \"compress.c\",\n        \"contrib/optimizations/insert_string.h\",\n        \"cpu_features.c\",\n        \"cpu_features.h\",\n        \"crc32.c\",\n        \"crc32.h\",\n        \"deflate.c\",\n        \"deflate.h\",\n        \"gzclose.c\",\n        \"gzguts.h\",\n        \"gzlib.c\",\n        \"gzread.c\",\n        \"gzwrite.c\",\n        \"infback.c\",\n        \"inffast.c\",\n        \"inffast.h\",\n        \"inffixed.h\",\n        \"inflate.h\",\n        \"inftrees.c\",\n        \"inftrees.h\",\n        \"trees.c\",\n        \"trees.h\",\n        \"uncompr.c\",\n        \"zconf.h\",\n        \"zlib.h\",\n        \"zutil.c\",\n        \"zutil.h\",\n    ],\n)\n\ncc_library(\n    name = \"zlib\",\n    srcs = [\":zlib_files\"],\n    copts = zlib_warnings,\n    features = [\"-parse_headers\"],\n    # Duplicate definitions here since we want to avoid non-local defines, some of them are needed\n    # here. This excludes INFLATE_CHUNK_* which is only needed in zlib_inflate_chunk_simd.\n    local_defines = [\"ZLIB_IMPLEMENTATION\"] + select({\n        \"@platforms//cpu:x86_64\": [\n            \"ADLER32_SIMD_SSSE3\",\n            \"CRC32_SIMD_SSE42_PCLMUL\",\n            \"DEFLATE_SLIDE_HASH_SSE2\",\n        ],\n        \"@platforms//cpu:aarch64\": [\n            \"ADLER32_SIMD_NEON\",\n            \"CRC32_ARMV8_CRC32\",\n            \"DEFLATE_SLIDE_HASH_NEON\",\n        ],\n    }) + select({\n        \":x86_linux\": [\"X86_NOT_WINDOWS\"],\n        \":x86_macos\": [\"X86_NOT_WINDOWS\"],\n        \":x86_windows\": [\"X86_WINDOWS\"],\n        \":arm64_linux\": [\"ARMV8_OS_LINUX\"],\n        \"arm64_macos\": [\"ARMV8_OS_MACOS\"],\n        \"arm64_windows\": [\"ARMV8_OS_WINDOWS\"],\n        \"//conditions:default\": [],\n    }),\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":zlib_adler32_simd\",\n        \":zlib_inflate_chunk_simd\",\n        \":zlib_slide_hash_simd\",\n    ] + select({\n        \"@platforms//cpu:x86_64\": [\":zlib_crc32_simd\"],\n        \"@platforms//cpu:aarch64\": [\":zlib_arm_crc32\"],\n    }),\n)\n\n# Chromium zlib includes some custom compression utils that are not present in mainline zlib but\n# used by V8.\ncc_library(\n    name = \"compression_utils_portable\",\n    srcs = [\"google/compression_utils_portable.cc\"],\n    hdrs = [\"google/compression_utils_portable.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\":zlib\"],\n)\n"
  },
  {
    "path": "build/capnp_embed.bzl",
    "content": "\"\"\"capnp_embed definition\"\"\"\n\nload(\"@capnp-cpp//src/capnp:cc_capnp_library.bzl\", \"capnp_provider\")\nload(\"@rules_cc//cc:cc_library.bzl\", \"cc_library\")\n\ndef _capnp_embed_impl(ctx):\n    return [\n        capnp_provider(\n            includes = [ctx.file.src.dirname],\n            inputs = [ctx.file.src],\n            src_prefix = \"\",\n        ),\n    ]\n\n_capnp_embed = rule(\n    attrs = {\n        \"src\": attr.label(allow_single_file = True),\n        \"deps\": attr.label_list(),\n    },\n    implementation = _capnp_embed_impl,\n)\n\ndef capnp_embed(\n        name,\n        src,\n        visibility = None,\n        target_compatible_with = None,\n        deps = []):\n    \"\"\"\n    Bazel rule to include `src` in a Cap'n Proto search path for embedding.\n\n    This is useful for including embedding the output of a `genrule` in a Cap'n Proto schema.\n    The generated target should be included in `cc_capnp_library` `deps`.\n    \"\"\"\n    if target_compatible_with == None:\n        target_compatible_with = select({\n            \"@//build/config:no_build\": [\"@platforms//:incompatible\"],\n            \"//conditions:default\": [],\n        })\n\n    _capnp_embed(\n        name = name + \"_gen\",\n        src = src,\n        visibility = visibility,\n        target_compatible_with = target_compatible_with,\n        deps = deps,\n    )\n    cc_library(\n        name = name,\n        target_compatible_with = target_compatible_with,\n    )\n"
  },
  {
    "path": "build/cc_ast_dump.bzl",
    "content": "\"\"\"\nDump the AST of a given C++ source file\n\nBased loosely on https://github.com/bazelbuild/rules_cc/blob/main/examples/my_c_compile/my_c_compile.bzl\n\"\"\"\n\nload(\"@rules_cc//cc:action_names.bzl\", \"CPP_COMPILE_ACTION_NAME\")\nload(\"@rules_cc//cc:find_cc_toolchain.bzl\", \"find_cpp_toolchain\", \"use_cc_toolchain\")\nload(\"@rules_cc//cc/common:cc_common.bzl\", \"cc_common\")\nload(\"@rules_cc//cc/common:cc_info.bzl\", \"CcInfo\")\n\ndef _cc_ast_dump_impl(ctx):\n    cc_toolchain = find_cpp_toolchain(ctx)\n\n    feature_configuration = cc_common.configure_features(\n        ctx = ctx,\n        cc_toolchain = cc_toolchain,\n        requested_features = ctx.features,\n        unsupported_features = ctx.disabled_features,\n    )\n\n    cc_info = cc_common.merge_cc_infos(direct_cc_infos = [dep[CcInfo] for dep in ctx.attr.deps])\n\n    variables = cc_common.create_compile_variables(\n        cc_toolchain = cc_toolchain,\n        feature_configuration = feature_configuration,\n        source_file = ctx.file.src.path,\n        output_file = None,\n        # clang 19 complains about '-c' being unused\n        user_compile_flags = [\"-Wno-unused-command-line-argument\", \"-Xclang\", \"-ast-dump=json\", \"-fsyntax-only\"] + ctx.fragments.cpp.copts + ctx.fragments.cpp.cxxopts,\n        include_directories = cc_info.compilation_context.includes,\n        quote_include_directories = cc_info.compilation_context.quote_includes,\n        # we need to pass external_includes, but there is no equivalent parameter in\n        # create_compile_variables – pass alongside system_includes.\n        system_include_directories = depset(transitive = [cc_info.compilation_context.system_includes, cc_info.compilation_context.external_includes]),\n        framework_include_directories = cc_info.compilation_context.framework_includes,\n        preprocessor_defines = cc_info.compilation_context.defines,\n    )\n\n    arguments = cc_common.get_memory_inefficient_command_line(feature_configuration = feature_configuration, action_name = CPP_COMPILE_ACTION_NAME, variables = variables)\n    executable = cc_common.get_tool_for_action(feature_configuration = feature_configuration, action_name = CPP_COMPILE_ACTION_NAME)\n    inputs = depset(direct = [ctx.file.src], transitive = [cc_toolchain.all_files] + [cc_info.compilation_context.headers])\n    env = cc_common.get_environment_variables(feature_configuration = feature_configuration, action_name = CPP_COMPILE_ACTION_NAME, variables = variables)\n\n    # Enable pipefail so that the action fails when there are e.g. missing include errors, otherwise\n    # we'd just continue despite the error and end up with an incomplete AST.\n    command = \" \".join([\"set -euo pipefail;\"] + [executable] + arguments + [\"|\", \"gzip\", \"-3\", \"-\", \">\", ctx.outputs.out.path])\n\n    # run_shell until https://github.com/bazelbuild/bazel/issues/5511 is fixed\n    ctx.actions.run_shell(\n        outputs = [ctx.outputs.out],\n        inputs = inputs,\n        command = command,\n        env = env,\n    )\n\ncc_ast_dump = rule(\n    implementation = _cc_ast_dump_impl,\n    attrs = {\n        \"src\": attr.label(mandatory = True, allow_single_file = True),\n        \"out\": attr.output(mandatory = True),\n        \"deps\": attr.label_list(providers = [CcInfo]),\n    },\n    toolchains = use_cc_toolchain(),  # copybara-use-repo-external-label\n    fragments = [\"cpp\"],\n)\n"
  },
  {
    "path": "build/ci.bazelrc",
    "content": "# CI-only configuration. Some of these settings are inspired by bazel-lib https://github.com/bazel-contrib/bazel-lib/blob/v2.9.3/.aspect/bazelrc/ci.bazelrc\n# Not all shared options in the GitHub CI are ported since we use a different remote cache setup for\n# internal CI.\nbuild:ci --keep_going\nbuild:ci --verbose_failures\n# Use a higher jobs level to effectively fetch from CPU and and use the remote cache at the same\n# time, see https://github.com/bazelbuild/bazel/issues/6394. 32 is still a fairly small number here\n# and should work for the small CI runners we use, if we switch to a bigger runner consider\n# increasing this closer towards the suggested value of 200. Note the number of maximum build jobs\n# is controlled by the --local_resources=cpu flag and still limited to the number of cores by\n# default.\nbuild:ci --jobs=32\n# Do not check for changes in external repository files, should speed up bazel invocations after the first one\nbuild:ci --noexperimental_check_external_repository_files\n# Only build runfile trees when needed. Runfile trees are useful for directly invoking bazel-built\n# binaries, but not needed otherwise. Building runfile trees is slow on systems with slow disk I/O,\n# so avoid doing so where we can. https://github.com/bazelbuild/bazel/commit/03246077f948f2790a83520e7dccc2625650e6df\nbuild:ci --nobuild_runfile_links\n# Rate limit progress updates for smaller logs, default is 0.2 which leads to very frequent updates.\nbuild:ci --show_progress_rate_limit=3\n# Enable color output\nbuild:ci --color=yes\n# Indicate support for more terminal columns, 100 is the line length recommended by KJ style.\nbuild:ci --terminal_columns=100\n\n# Emit build profile so that slow builds can be investigated\nbuild:ci --profile build.bazel-profile.gz\ntest:ci --profile test.bazel-profile.gz\ntest:ci --test_output=errors --test_summary=terse\nbuild:ci --disk_cache=~/bazel-disk-cache\n\n# test CI jobs don't need any top-level artifacts and just verify things\nbuild:ci-test --remote_download_minimal\n\n# limit storage usage on ci\n# Exclude large benchmarking binaries created in debug and asan configurations to avoid\n# running out of disk space on the runner (nominally 14GB). We typically have two copies\n# of generated artifacts: one under bazel output-base and one in the bazel disk cache.\n# Also only generate limited debug info – these binaries are only used for testing and\n# don't need to run within a debugger, so information needed for symbolication is\n# sufficient. The host configuration compiles in opt mode/without debug info by default, so\n# there's no need to set host_copt here.\n# LLVM produces a bit more debug info on macOS by default to facilitate debugging with\n# LLDB. This is not needed for this use case, so disable it using -fno-standalone-debug –\n# this is already the default for Linux/Windows.\nbuild:ci-disable-benchmarks --build_tag_filters=-off-by-default,-requires-container-engine,-workerd-benchmark\ntest:ci-disable-benchmarks --test_tag_filters=-off-by-default,-requires-fuzzilli,-requires-container-engine,-workerd-benchmark\nbuild:ci-limit-storage --config=ci-disable-benchmarks\nbuild:ci-limit-storage --copt=\"-g1\"\nbuild:ci-limit-storage --copt=\"-fno-standalone-debug\"\n\n\n# ci-platform dependent configuration\n\nbuild:ci-linux-common --copt='-Werror'\nbuild:ci-linux-common --copt='-Wno-error=#warnings'\nbuild:ci-linux-common --copt='-Wno-error=deprecated-declarations'\n# keep in sync with .github/workflows/test.yml\nbuild:ci-linux-common --action_env=CC=/usr/lib/llvm-19/bin/clang\nbuild:ci-linux-common --host_action_env=CC=/usr/lib/llvm-19/bin/clang\n\nbuild:ci-linux --config=ci-linux-common --remote_download_regex=\".*src/workerd/server/workerd.*\"\nbuild:ci-linux-arm --config=ci-linux-common\n\nbuild:ci-linux-debug --config=ci-linux-common --config=ci-limit-storage\nbuild:ci-linux-debug --config=debug --config=rust-debug\n\nbuild:ci-linux-arm-debug --config=ci-linux-debug\n\nbuild:ci-linux-asan --config=ci-linux-common --config=ci-limit-storage\n# we're really struggling to fit asan build into worker disk size\n# having asan without symbols is better than none\nbuild:ci-linux-asan --config=asan --copt=\"-g0\" --strip=always\nbuild:ci-linux-arm-asan --config=ci-linux-asan\n\n# Build container tests on Linux CI\nbuild:ci-linux --build_tag_filters=-off-by-default\nbuild:ci-linux-arm --build_tag_filters=-off-by-default\n\nbuild:ci-macOS --config=ci-disable-benchmarks\n\nbuild:ci-macOS-debug --config=debug\n\n# Unfortunately on macOS, we need to be able to invoke sudo to configure the network for sidecar tests\ntest:ci-macOS --spawn_strategy=local\n\nbuild:ci-windows --config=windows_no_dbg\nbuild:ci-windows-debug --config=debug\n\n# Some tests (like Python import tests) take a long time to run, especially when debug is enabled\n# For that reason, we only run them in the default configuration for each platform to minimize\n# effect on CI pipeline runtime.\ntest:ci-linux --test_size_filters=\ntest:ci-macOS --test_size_filters=\ntest:ci-windows --test_size_filters=\n\n# Run container tests on Linux CI\ntest:ci-linux --test_tag_filters=-off-by-default,-requires-fuzzilli\ntest:ci-linux-arm --test_tag_filters=-off-by-default,-requires-fuzzilli\n\n# Enable reporting whenever WPT tests are run\ntest:ci-test --config=wpt-test\ncommon:wpt-test --test_env=GEN_TEST_REPORT=1\ncommon:wpt-test --test_env=GEN_TEST_STATS=1\n\n# Config to produce a full WPT report. Also ensures that test targets are fetched, even under remote_download_minimal.\ncommon:wpt-report --remote_download_regex=\".*src/wpt/.*\"\n\n# Let tests know they're running in CI\ntest:ci-test --test_env=CI=true\n\n# release CI – use remote_download_minimal, but always fetch the workerd binary so we can add it to\n# the release artifacts.\nbuild:ci-release --remote_download_minimal --remote_download_regex=\".*src/workerd/server/workerd.*\"\n"
  },
  {
    "path": "build/config/BUILD.bazel",
    "content": "load(\"@bazel_skylib//rules:common_settings.bzl\", \"string_flag\")\n\n# Similar to --run_under flag to be able to run cross-compiled target binaries.\nstring_flag(\n    name = \"target_run_under\",\n    build_setting_default = \"\",\n    visibility = [\"//visibility:public\"],\n)\n\n# Dummy config settings for when workerd is built standalone\n# These always return false since we don't want prebuilt behavior in standalone workerd\nconfig_setting(\n    name = \"no_build\",\n    values = {\"define\": \"never_match=true\"},  # This will never match\n    visibility = [\"//visibility:public\"],\n)\n\nconfig_setting(\n    name = \"prebuilt_binaries_arm64\",\n    values = {\"define\": \"never_match=true\"},  # This will never match\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "build/deps/BUILD",
    "content": ""
  },
  {
    "path": "build/deps/build_deps.jsonc",
    "content": "{\n  \"$schema\": \"deps.schema.json\",\n  \"repositories\": [\n    // bazel_deps from MODULE.bazel\n    // bazel_lib was formerly known as aspect_bazel_lib, new versions starting with v3 are released\n    // under the new name which should now be used.\n    {\n      \"name\": \"bazel_lib\",\n      \"type\": \"bazel_dep\"\n    },\n    {\n      \"name\": \"bazel_skylib\",\n      \"type\": \"bazel_dep\"\n    },\n    {\n      \"name\": \"rules_shell\",\n      \"type\": \"bazel_dep\"\n    },\n    // abseil is used by protobuf and tcmalloc.\n    {\n      \"name\": \"abseil-cpp\",\n      \"type\": \"bazel_dep\"\n    },\n    // Not pulling in platforms via bzlmod causes issues with the Windows clang-cl toolchain\n    {\n      \"name\": \"platforms\",\n      \"type\": \"bazel_dep\"\n    },\n    // While the build should succeed with the default toolchain configuration from rules_cc,\n    // apple_support is more tailored to macOS and allows us to cross-compile easily. Note that this\n    // needs to be pulled in before rules_cc for this toolchain to actually be used.\n    {\n      \"name\": \"apple_support\",\n      \"type\": \"bazel_dep\"\n    },\n    {\n      \"name\": \"rules_cc\",\n      \"type\": \"bazel_dep\"\n    },\n    // Node.js\n    {\n      \"name\": \"aspect_rules_esbuild\",\n      \"type\": \"bazel_dep\"\n    },\n    {\n      \"name\": \"aspect_rules_js\",\n      \"type\": \"bazel_dep\"\n    },\n    {\n      \"name\": \"aspect_rules_ts\",\n      \"type\": \"bazel_dep\"\n    },\n    {\n      \"name\": \"rules_nodejs\",\n      \"type\": \"bazel_dep\"\n    },\n    // Python\n    {\n      \"name\": \"rules_python\",\n      \"type\": \"bazel_dep\"\n    },\n    // rules_oci and rules_multirun are used to build Docker images\n    {\n      \"name\": \"rules_oci\",\n      \"type\": \"bazel_dep\"\n    },\n    {\n      \"name\": \"rules_multirun\",\n      \"type\": \"bazel_dep\"\n    },\n    // rust\n    {\n      \"name\": \"cargo_bazel_linux_x64\",\n      \"type\": \"github_release\",\n      \"owner\": \"bazelbuild\",\n      \"repo\": \"rules_rust\",\n      \"file_regex\": \"^cargo-bazel-x86_64-unknown-linux-gnu$\",\n      \"file_type\": \"executable\"\n    },\n    {\n      \"name\": \"cargo_bazel_linux_arm64\",\n      \"type\": \"github_release\",\n      \"owner\": \"bazelbuild\",\n      \"repo\": \"rules_rust\",\n      \"file_regex\": \"^cargo-bazel-aarch64-unknown-linux-gnu$\",\n      \"file_type\": \"executable\"\n    },\n    {\n      \"name\": \"cargo_bazel_macos_x64\",\n      \"type\": \"github_release\",\n      \"owner\": \"bazelbuild\",\n      \"repo\": \"rules_rust\",\n      \"file_regex\": \"^cargo-bazel-x86_64-apple-darwin$\",\n      \"file_type\": \"executable\"\n    },\n    {\n      \"name\": \"cargo_bazel_macos_arm64\",\n      \"type\": \"github_release\",\n      \"owner\": \"bazelbuild\",\n      \"repo\": \"rules_rust\",\n      \"file_regex\": \"^cargo-bazel-aarch64-apple-darwin$\",\n      \"file_type\": \"executable\"\n    },\n    {\n      \"name\": \"cargo_bazel_win_x64\",\n      \"type\": \"github_release\",\n      \"owner\": \"bazelbuild\",\n      \"repo\": \"rules_rust\",\n      \"file_regex\": \"^cargo-bazel-x86_64-pc-windows-msvc.exe$\",\n      \"file_type\": \"executable\",\n      \"downloaded_file_path\": \"downloaded.exe\"\n    },\n    // wasm-tools\n    {\n      \"name\": \"wasm_tools_linux_x64\",\n      \"type\": \"github_release\",\n      \"owner\": \"bytecodealliance\",\n      \"repo\": \"wasm-tools\",\n      \"file_regex\": \"^wasm-tools-.*-x86_64-linux\\\\.tar\\\\.gz$\",\n      \"build_file_content\": \"exports_files([\\\"wasm-tools\\\"])\"\n    },\n    {\n      \"name\": \"wasm_tools_linux_arm64\",\n      \"type\": \"github_release\",\n      \"owner\": \"bytecodealliance\",\n      \"repo\": \"wasm-tools\",\n      \"file_regex\": \"^wasm-tools-.*-aarch64-linux\\\\.tar\\\\.gz$\",\n      \"build_file_content\": \"exports_files([\\\"wasm-tools\\\"])\"\n    },\n    {\n      \"name\": \"wasm_tools_macos_x64\",\n      \"type\": \"github_release\",\n      \"owner\": \"bytecodealliance\",\n      \"repo\": \"wasm-tools\",\n      \"file_regex\": \"^wasm-tools-.*-x86_64-macos\\\\.tar\\\\.gz$\",\n      \"build_file_content\": \"exports_files([\\\"wasm-tools\\\"])\"\n    },\n    {\n      \"name\": \"wasm_tools_macos_arm64\",\n      \"type\": \"github_release\",\n      \"owner\": \"bytecodealliance\",\n      \"repo\": \"wasm-tools\",\n      \"file_regex\": \"^wasm-tools-.*-aarch64-macos\\\\.tar\\\\.gz$\",\n      \"build_file_content\": \"exports_files([\\\"wasm-tools\\\"])\"\n    },\n    {\n      \"name\": \"wasm_tools_windows_x64\",\n      \"type\": \"github_release\",\n      \"owner\": \"bytecodealliance\",\n      \"repo\": \"wasm-tools\",\n      \"file_regex\": \"^wasm-tools-.*-x86_64-windows\\\\.zip$\",\n      \"build_file_content\": \"exports_files([\\\"wasm-tools.exe\\\"])\"\n    },\n    {\n      \"name\": \"clang_tidy_linux_amd64\",\n      \"type\": \"github_release\",\n      \"owner\": \"cloudflare\",\n      \"repo\": \"workerd-tools\",\n      \"file_regex\": \"llvm-.*-linux-amd64-clang-tidy\",\n      \"file_type\": \"executable\",\n      \"freeze_version\": \"clang-tidy-22.1.1\"\n    },\n    {\n      \"name\": \"clang_tidy_linux_arm64\",\n      \"type\": \"github_release\",\n      \"owner\": \"cloudflare\",\n      \"repo\": \"workerd-tools\",\n      \"file_regex\": \"llvm-.*-linux-arm64-clang-tidy\",\n      \"file_type\": \"executable\",\n      \"freeze_version\": \"clang-tidy-22.1.1\"\n    },\n    {\n      \"name\": \"clang_tidy_darwin_arm64\",\n      \"type\": \"github_release\",\n      \"owner\": \"cloudflare\",\n      \"repo\": \"workerd-tools\",\n      \"file_regex\": \"llvm-.*-darwin-arm64-clang-tidy\",\n      \"file_type\": \"executable\",\n      \"freeze_version\": \"clang-tidy-22.1.1\"\n    }\n  ]\n}\n"
  },
  {
    "path": "build/deps/dep_pyodide.bzl",
    "content": "load(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\", \"http_file\")\nload(\"//:build/python_metadata.bzl\", \"BUNDLE_VERSION_INFO\", \"PYODIDE_VERSIONS\", \"PYTHON_LOCKFILES\")\n\ndef _pyodide_core(*, version, sha256, **_kwds):\n    # Use @workerd prefix on build_file so we can use this from edgeworker too\n    name = \"pyodide-%s\" % version\n    http_archive(\n        name = name,\n        build_file = \"@workerd//:build/BUILD.pyodide\",\n        sha256 = sha256,\n        urls = [\"https://github.com/pyodide/pyodide/releases/download/%s/pyodide-core-%s.tar.bz2\" % (version, version)],\n    )\n    return [name]\n\ndef _pyodide_packages(*, tag, lockfile_hash, all_wheels_hash, **_kwds):\n    lock_name = \"pyodide-lock_%s.json\" % tag\n    http_file(\n        name = lock_name,\n        sha256 = lockfile_hash,\n        url = \"https://github.com/cloudflare/pyodide-build-scripts/releases/download/%s/pyodide-lock.json\" % tag,\n    )\n\n    # Use @workerd prefix on build_file so we can use this from edgeworker too\n    archive_name = \"all_pyodide_wheels_%s\" % tag\n    http_archive(\n        name = archive_name,\n        build_file = \"@workerd//:build/BUILD.all_pyodide_wheels\",\n        sha256 = all_wheels_hash,\n        urls = [\"https://github.com/cloudflare/pyodide-build-scripts/releases/download/%s/all_wheels.zip\" % tag],\n    )\n    return [lock_name, archive_name]\n\nVENDOR_R2 = \"https://pub-25a5b2f2f1b84655b185a505c7a3ad23.r2.dev/\"\n\ndef _py_vendor_test_deps(version, name, sha256, abi, **_kwds):\n    pyver = \"-\" + abi.replace(\".\", \"\") if abi else \"\"\n    archive_name = name + \"_src_\" + version\n    http_archive(\n        name = archive_name,\n        build_file_content = \"\"\"\nfilegroup(\n    name = \"all_srcs\",\n    srcs = glob([\"**\"]),\n    visibility = [\"//visibility:public\"],\n)\n\"\"\",\n        sha256 = sha256,\n        url = VENDOR_R2 + name + pyver + \"-vendored-for-ew-testing.zip\",\n    )\n    return [archive_name]\n\nPYODIDE_CAPN_BIN = \"https://pyodide-capnp-bin.edgeworker.net/\"\n\ndef _capnp_bundle(id = None, integrity = None, **_kwds):\n    if not id or not integrity:\n        return []\n    name = \"pyodide_%s.capnp.bin\" % id\n    http_file(\n        name = name,\n        integrity = integrity,\n        url = PYODIDE_CAPN_BIN + name,\n    )\n    return [name]\n\ndef _snapshot_http_file(bundle_name, folder, snapshot, integrity, hash, r2_base = PYODIDE_CAPN_BIN):\n    if not snapshot:\n        return []\n    if not integrity:\n        fail(\"Snapshot %s from bundle %s has missing integrity\" % (snapshot, bundle_name))\n    if folder == \"baseline-snapshot/\":\n        key = hash\n    else:\n        key = snapshot\n    return [struct(\n        name = \"pyodide-snapshot-\" + snapshot,\n        snapshot = snapshot,\n        integrity = integrity,\n        url = r2_base + folder + key,\n    )]\n\ndef _snapshot_http_files_version(\n        name,\n        baseline_snapshot = None,\n        baseline_snapshot_hash = None,\n        baseline_snapshot_integrity = None,\n        numpy_snapshot = None,\n        numpy_snapshot_integrity = None,\n        fastapi_snapshot = None,\n        fastapi_snapshot_integrity = None,\n        dedicated_fastapi_snapshot = None,\n        dedicated_fastapi_snapshot_integrity = None,\n        **_kwds):\n    return (_snapshot_http_file(name, \"baseline-snapshot/\", baseline_snapshot, baseline_snapshot_integrity, baseline_snapshot_hash) +\n            _snapshot_http_file(name, \"test-snapshot/\", numpy_snapshot, numpy_snapshot_integrity, None) +\n            _snapshot_http_file(name, \"test-snapshot/\", fastapi_snapshot, fastapi_snapshot_integrity, None) +\n            _snapshot_http_file(name, \"\", dedicated_fastapi_snapshot, dedicated_fastapi_snapshot_integrity, None, VENDOR_R2))\n\ndef _snapshot_http_files():\n    files = []\n    for ver in BUNDLE_VERSION_INFO.values():\n        files += _snapshot_http_files_version(**ver)\n\n    # Deduplicate generated http_file rules.\n    http_files = {info.snapshot: info for info in files}\n    deps = []\n    for info in http_files.values():\n        http_file(\n            name = info.name,\n            integrity = info.integrity,\n            url = info.url,\n        )\n        deps.append(info.name)\n    return deps\n\ndef dep_pyodide():\n    deps = []\n    for info in PYODIDE_VERSIONS:\n        deps += _pyodide_core(**info)\n\n    for info in BUNDLE_VERSION_INFO.values():\n        deps += _capnp_bundle(**info)\n        for pkg in info[\"vendored_packages_for_tests\"].values():\n            deps += _py_vendor_test_deps(version = info[\"name\"], **pkg)\n\n    for info in PYTHON_LOCKFILES:\n        deps += _pyodide_packages(**info)\n\n    deps += _snapshot_http_files()\n    return deps\n\ndef _impl(module_ctx):\n    deps = dep_pyodide()\n    return module_ctx.extension_metadata(\n        root_module_direct_deps = deps,\n        root_module_direct_dev_deps = [],\n    )\n\npyodide = module_extension(implementation = _impl)\n"
  },
  {
    "path": "build/deps/deps.jsonc",
    "content": "{\n  \"$schema\": \"deps.schema.json\",\n  \"repositories\": [\n    {\n      \"name\": \"ada-url\",\n      \"type\": \"bazel_dep\"\n    },\n    // BoringSSL is more likely to break than other dependencies, because our downstream build uses\n    // a custom-built version of BoringSSL in FIPS mode.\n    {\n      \"name\": \"boringssl\",\n      \"type\": \"github_release\",\n      \"use_bazel_dep\": true,\n      \"owner\": \"google\",\n      \"repo\": \"boringssl\",\n      \"repo_name\": \"ssl\",\n      \"freeze_version\": \"0.20251002.0\",\n      \"patches\": [\n        \"//:patches/boringssl/0001-Expose-libdecrepit-so-NodeJS-can-use-it-for-ncrypto.patch\"\n      ]\n    },\n    {\n      \"name\": \"brotli\",\n      \"type\": \"bazel_dep\"\n    },\n    {\n      \"name\": \"zstd\",\n      \"type\": \"bazel_dep\"\n    },\n    {\n      \"name\": \"tcmalloc\",\n      \"type\": \"bazel_dep\"\n    },\n    {\n      \"name\": \"capnp-cpp\",\n      \"type\": \"github_tarball\",\n      \"owner\": \"capnproto\",\n      \"repo\": \"capnproto\",\n      \"branch\": \"v2\",\n      \"extra_strip_prefix\": \"/c++\"\n    },\n    // We want to avoid version skew with v8, so we use identical versions.\n    {\n      \"name\": \"dragonbox\",\n      \"type\": \"github_tarball\",\n      \"build_file_content\": \"cc_library(name = 'dragonbox', hdrs = glob(['include/dragonbox/*.h']), visibility = ['//visibility:public'], include_prefix = 'third_party/dragonbox/src')\",\n      \"owner\": \"jk-jeon\",\n      \"repo\": \"dragonbox\",\n      \"freeze_commit\": \"6c7c925b571d54486b9ffae8d9d18a822801cbda\"\n    },\n    // We want to avoid version skew with v8, so we use identical versions.\n    {\n      \"name\": \"fast_float\",\n      \"type\": \"github_tarball\",\n      \"use_bazel_dep\": true,\n      \"owner\": \"fastfloat\",\n      \"repo\": \"fast_float\",\n      \"branch\": \"main\",\n      \"freeze_commit\": \"cb1d42aaa1e14b09e1452cfdef373d051b8c02a4\",\n      \"build_file_content\": \"cc_library(name = 'fast_float', hdrs = glob(['include/fast_float/*.h']), visibility = ['//visibility:public'], include_prefix = 'third_party/fast_float/src')\",\n      \"use_module_bazel_from_bcr\": \"8.0.2\"\n    },\n    // We want to avoid version skew with v8, so we use identical versions.\n    {\n      \"name\": \"fp16\",\n      \"type\": \"github_tarball\",\n      \"use_bazel_dep\": true,\n      \"owner\": \"Maratyszcza\",\n      \"repo\": \"FP16\",\n      \"freeze_commit\": \"b3720617faf1a4581ed7e6787cc51722ec7751f0\",\n      \"build_file_content\": \"exports_files(glob(['**']))\",\n      \"use_module_bazel_from_bcr\": \"0.0.0-20210320-0a92994\"\n    },\n    // We want to avoid version skew with v8, so we use identical versions.\n    {\n      \"name\": \"highway\",\n      \"type\": \"github_tarball\",\n      \"use_bazel_dep\": true,\n      \"owner\": \"google\",\n      \"repo\": \"highway\",\n      \"freeze_commit\": \"00fe003dac355b979f36157f9407c7c46448958e\",\n      \"use_module_bazel_from_bcr\": \"1.3.0\"\n    },\n    {\n      \"name\": \"nbytes\",\n      \"type\": \"github_release\",\n      \"owner\": \"nodejs\",\n      \"repo\": \"nbytes\",\n      \"build_file\": \"//:build/BUILD.nbytes\"\n    },\n    {\n      \"name\": \"ncrypto\",\n      \"type\": \"github_release\",\n      \"owner\": \"nodejs\",\n      \"repo\": \"ncrypto\"\n    },\n    {\n      \"name\": \"perfetto\",\n      \"type\": \"github_release\",\n      \"use_bazel_dep\": true,\n      \"owner\": \"google\",\n      \"repo\": \"perfetto\",\n      \"patches\": [\n        \"//:patches/perfetto/0001-Don-t-attempt-to-use-rules_android.patch\",\n        \"//:patches/perfetto/0002-disable-info-level-logging.patch\"\n      ]\n    },\n    {\n      \"name\": \"simdutf\",\n      \"type\": \"github_release\",\n      \"owner\": \"simdutf\",\n      \"repo\": \"simdutf\",\n      \"build_file\": \"//:build/BUILD.simdutf\",\n      \"file_regex\": \"singleheader.zip\"\n    },\n    // In theory this should match the version specified in V8 DEPS, but in practice we should get\n    // the latest commit – zlib is very stable and its API has not changed in a long time, but we\n    // want to pick up any bug fixes, security patches and performance improvements more quickly\n    // than we would through V8 updates.\n    {\n      \"name\": \"zlib\",\n      \"type\": \"git_clone\",\n      \"use_bazel_dep\": true,\n      \"url\": \"https://chromium.googlesource.com/chromium/src/third_party/zlib.git\",\n      \"branch\": \"main\",\n      \"build_file\": \"//:build/BUILD.zlib\",\n      \"patches\": [\"//:patches/zlib/0001-Add-dummy-MODULE.bazel.patch\"]\n    },\n    {\n      \"name\": \"workerd-cxx\",\n      \"type\": \"github_tarball\",\n      \"owner\": \"cloudflare\",\n      \"repo\": \"workerd-cxx\",\n      \"branch\": \"master\"\n    }\n  ]\n}\n"
  },
  {
    "path": "build/deps/deps.schema.json",
    "content": "{\n  \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n  \"$id\": \"https://example.com/product.schema.json\",\n  \"title\": \"Dependencies\",\n  \"description\": \"Description of external dependencies for the project\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"repositories\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"oneOf\": [\n          {\n            \"type\": \"object\",\n            \"properties\": {\n              \"type\": {\n                \"const\": \"github_tarball\"\n              },\n              \"name\": {\n                \"type\": \"string\"\n              },\n              \"owner\": {\n                \"type\": \"string\"\n              },\n              \"repo\": {\n                \"type\": \"string\"\n              },\n              \"freeze_commit\": {\n                \"type\": \"string\"\n              },\n              \"freeze_sha256\": {\n                \"type\": \"string\"\n              },\n              \"build_file\": {\n                \"type\": \"string\"\n              },\n              \"build_file_content\": {\n                \"type\": \"string\"\n              },\n              \"patches\": {\n                \"type\": \"array\",\n                \"items\": {\n                  \"type\": \"string\"\n                }\n              },\n              \"repo_mapping\": {\n                \"type\": \"object\",\n                \"additionalProperties\": {\n                  \"type\": \"string\"\n                }\n              },\n              \"branch\": {\n                \"type\": \"string\"\n              },\n              \"extra_strip_prefix\": {\n                \"type\": \"string\"\n              },\n              \"use_bazel_dep\": {\n                \"type\": \"boolean\"\n              },\n              \"repo_name\": {\n                \"type\": \"string\"\n              },\n              \"use_module_bazel_from_bcr\": {\n                \"type\": \"string\"\n              }\n            },\n            \"required\": [\n              \"type\",\n              \"name\",\n              \"owner\",\n              \"repo\"\n            ],\n            \"additionalProperties\": false\n          },\n          {\n            \"type\": \"object\",\n            \"properties\": {\n              \"type\": {\n                \"const\": \"github_release\"\n              },\n              \"name\": {\n                \"type\": \"string\"\n              },\n              \"owner\": {\n                \"type\": \"string\"\n              },\n              \"repo\": {\n                \"type\": \"string\"\n              },\n              \"file_regex\": {\n                \"type\": \"string\"\n              },\n              \"file_type\": {\n                \"type\": \"string\",\n                \"enum\": [\n                  \"archive\",\n                  \"executable\"\n                ]\n              },\n              \"freeze_version\": {\n                \"type\": \"string\"\n              },\n              \"freeze_sha256\": {\n                \"type\": \"string\"\n              },\n              \"strip_prefix\": {\n                \"type\": \"string\"\n              },\n              \"build_file\": {\n                \"type\": \"string\"\n              },\n              \"build_file_content\": {\n                \"type\": \"string\"\n              },\n              \"patches\": {\n                \"type\": \"array\",\n                \"items\": {\n                  \"type\": \"string\"\n                }\n              },\n              \"repo_mapping\": {\n                \"type\": \"object\",\n                \"additionalProperties\": {\n                  \"type\": \"string\"\n                }\n              },\n              \"downloaded_file_path\": {\n                \"type\": \"string\"\n              },\n              \"use_bazel_dep\": {\n                \"type\": \"boolean\"\n              },\n              \"repo_name\": {\n                \"type\": \"string\"\n              },\n              \"use_module_bazel_from_bcr\": {\n                \"type\": \"string\"\n              }\n            },\n            \"required\": [\n              \"type\",\n              \"name\",\n              \"owner\",\n              \"repo\"\n            ],\n            \"additionalProperties\": false\n          },\n          {\n            \"type\": \"object\",\n            \"properties\": {\n              \"type\": {\n                \"const\": \"git_clone\"\n              },\n              \"name\": {\n                \"type\": \"string\"\n              },\n              \"url\": {\n                \"type\": \"string\"\n              },\n              \"branch\": {\n                \"type\": \"string\"\n              },\n              \"build_file\": {\n                \"type\": \"string\"\n              },\n              \"build_file_content\": {\n                \"type\": \"string\"\n              },\n              \"freeze_commit\": {\n                \"type\": \"string\"\n              },\n              \"patches\": {\n                \"type\": \"array\",\n                \"items\": {\n                  \"type\": \"string\"\n                }\n              },\n              \"repo_mapping\": {\n                \"type\": \"object\",\n                \"additionalProperties\": {\n                  \"type\": \"string\"\n                }\n              },\n              \"use_bazel_dep\": {\n                \"type\": \"boolean\"\n              },\n              \"repo_name\": {\n                \"type\": \"string\"\n              }\n            },\n            \"required\": [\n              \"type\",\n              \"name\",\n              \"url\"\n            ],\n            \"additionalProperties\": false\n          },\n          {\n            \"type\": \"object\",\n            \"properties\": {\n              \"type\": {\n                \"const\": \"crate\"\n              },\n              \"name\": {\n                \"type\": \"string\"\n              },\n              \"build_file\": {\n                \"type\": \"string\"\n              },\n              \"freeze_version\": {\n                \"type\": \"string\"\n              }\n            },\n            \"required\": [\n              \"type\",\n              \"name\",\n              \"build_file\"\n            ],\n            \"additionalProperties\": false\n          },\n          {\n            \"type\": \"object\",\n            \"properties\": {\n              \"type\": {\n                \"const\": \"bazel_dep\"\n              },\n              \"name\": {\n                \"type\": \"string\"\n              },\n              \"freeze_version\": {\n                \"type\": \"string\"\n              }\n            },\n            \"required\": [\n              \"type\",\n              \"name\"\n            ],\n            \"additionalProperties\": false\n          }\n        ]\n      }\n    }\n  },\n  \"required\": [\n    \"repositories\"\n  ]\n}\n"
  },
  {
    "path": "build/deps/formatters/BUILD",
    "content": "load(\"@bazel_skylib//rules:native_binary.bzl\", \"native_binary\")\nload(\"@npm//:prettier/package_json.bzl\", prettier = \"bin\")\n\nconfig_setting(\n    name = \"linux_amd64\",\n    constraint_values = [\n        \"@platforms//os:linux\",\n        \"@platforms//cpu:x86_64\",\n    ],\n)\n\nconfig_setting(\n    name = \"linux_arm64\",\n    constraint_values = [\n        \"@platforms//os:linux\",\n        \"@platforms//cpu:arm64\",\n    ],\n)\n\nconfig_setting(\n    name = \"darwin_amd64\",\n    constraint_values = [\n        \"@platforms//os:macos\",\n        \"@platforms//cpu:x86_64\",\n    ],\n)\n\nconfig_setting(\n    name = \"darwin_arm64\",\n    constraint_values = [\n        \"@platforms//os:macos\",\n        \"@platforms//cpu:arm64\",\n    ],\n)\n\nconfig_setting(\n    name = \"windows_amd64\",\n    constraint_values = [\n        \"@platforms//os:windows\",\n        \"@platforms//cpu:x86_64\",\n    ],\n)\n\nnative_binary(\n    name = \"clang-format@rule\",\n    src = select({\n        \":linux_amd64\": \"@clang-format-linux-amd64//file:file\",\n        \":linux_arm64\": \"@clang-format-linux-arm64//file:file\",\n        \":darwin_arm64\": \"@clang-format-darwin-arm64//file:file\",\n        \"//conditions:default\": \"@clang-format-linux-amd64//file:file\",\n    }),\n    out = \"clang-format\",\n    tags = [\"manual\"],\n    visibility = [\"//visibility:public\"],\n)\n\nnative_binary(\n    name = \"ruff@rule\",\n    src = select({\n        \":linux_amd64\": \"@ruff-linux-amd64//:file\",\n        \":linux_arm64\": \"@ruff-linux-arm64//:file\",\n        \":darwin_arm64\": \"@ruff-darwin-arm64//:file\",\n        \"//conditions:default\": \"@ruff-linux-amd64//:file\",\n    }),\n    out = \"ruff\",\n    tags = [\"manual\"],\n    visibility = [\"//visibility:public\"],\n)\n\nnative_binary(\n    name = \"buildifier@rule\",\n    src = select({\n        \":linux_amd64\": \"@buildifier-linux-amd64//file:file\",\n        \":linux_arm64\": \"@buildifier-linux-arm64//file:file\",\n        \":darwin_amd64\": \"@buildifier-darwin-amd64//file:file\",\n        \":darwin_arm64\": \"@buildifier-darwin-arm64//file:file\",\n        \":windows_amd64\": \"@buildifier-windows-amd64//file:file\",\n        \"//conditions:default\": \"@buildifier-linux-amd64//file:file\",\n    }),\n    out = \"buildifier\",\n    tags = [\"manual\"],\n    visibility = [\"//visibility:public\"],\n)\n\nnative_binary(\n    name = \"rustfmt@rule\",\n    src = select({\n        \":linux_amd64\": \"@rustfmt-standalone-linux-amd64//:rustfmt\",\n        \":linux_arm64\": \"@rustfmt-standalone-linux-arm64//:rustfmt\",\n        \":darwin_arm64\": \"@rustfmt-standalone-darwin-arm64//:rustfmt\",\n        \"//conditions:default\": \"@rustfmt-standalone-linux-amd64//:rustfmt\",\n    }),\n    out = \"rustfmt\",\n    tags = [\"manual\"],\n    visibility = [\"//visibility:public\"],\n)\n\nprettier.prettier_binary(\n    name = \"prettier@rule\",\n    # Include this js_library and its dependencies in the runfiles (runtime dependencies)\n    data = [\"//:prettierrc\"],\n    # Allow the binary to be run outside bazel\n    env = {\"BAZEL_BINDIR\": \".\"},\n    fixed_args = [\n        # `require` statements in the config file will be resolved relative to its location\n        # Therefore to make it hermetic, prettier must be pointed at the copy of the config file\n        # in the runfiles folder rather than the one in the source folder.\n        \"--config=\\\"$$JS_BINARY__RUNFILES\\\"/$(rlocationpath //:prettierrc)\",\n        \"--log-level=warn\",\n    ],\n)\n"
  },
  {
    "path": "build/deps/formatters/rustfmt.MODULE.bazel",
    "content": "# Standalone rustfmt binary + shared libraries, downloaded from static.rust-lang.org.\n# This avoids going through `bazel run @rules_rust//:rustfmt` which would trigger\n# full Rust toolchain resolution (and, in edgeworker, the V8 build cache download).\n#\n# To get sha256 values when adding a platform or updating the version:\n#   curl -sL \"https://static.rust-lang.org/dist/<DATE>/rustfmt-nightly-<TRIPLE>.tar.xz\" | sha256sum\n#   curl -sL \"https://static.rust-lang.org/dist/<DATE>/rustc-nightly-<TRIPLE>.tar.xz\" | sha256sum\n\nrustfmt_standalone = use_extension(\"//build/deps/formatters:rustfmt_repository.bzl\", \"rustfmt_standalone\")\nrustfmt_standalone.repo(\n    name = \"rustfmt-standalone-linux-amd64\",\n    iso_date = \"2026-02-12\",\n    rustc_sha256 = \"ecff4a1dd40734c2614c56b28f265eb8d771d7dd1a3c3cb4ed0740d95deec8a7\",\n    rustfmt_sha256 = \"b32c674f6f00407bcc8570f376defc89c768f9ebd87616c8d2d59393b54e179a\",\n    triple = \"x86_64-unknown-linux-gnu\",\n)\nrustfmt_standalone.repo(\n    name = \"rustfmt-standalone-linux-arm64\",\n    iso_date = \"2026-02-12\",\n    rustc_sha256 = \"f07f1a4071401235870022c6bd99f29ffe890ff239223e289555f827e9f0a16a\",\n    rustfmt_sha256 = \"7c10fd492c921a779c59072eb950816a09d9422c83c479d73e8129fc04bbfa1f\",\n    triple = \"aarch64-unknown-linux-gnu\",\n)\nrustfmt_standalone.repo(\n    name = \"rustfmt-standalone-darwin-arm64\",\n    iso_date = \"2026-02-12\",\n    rustc_sha256 = \"44dec0af289dd4c61d135222a2cee0de2f412804b2e9774639927e63f8d64e19\",\n    rustfmt_sha256 = \"b4397c2247726e328c857f576a40e7e02b016f38860711f98beca92ac841d75a\",\n    triple = \"aarch64-apple-darwin\",\n)\nuse_repo(\n    rustfmt_standalone,\n    \"rustfmt-standalone-darwin-arm64\",\n    \"rustfmt-standalone-linux-amd64\",\n    \"rustfmt-standalone-linux-arm64\",\n)\n"
  },
  {
    "path": "build/deps/formatters/rustfmt_repository.bzl",
    "content": "\"\"\"\nRepository rule that downloads a standalone rustfmt binary along with the rustc\nshared libraries it depends on.\n\nThis avoids needing `bazel run @rules_rust//:rustfmt`, which triggers C++ toolchain\nresolution and (in edgeworker) the V8 build cache download — a multi-GB fetch that\nis unnecessary for code formatting.\n\"\"\"\n\nload(\"@@//:build/http_proxy_config.bzl\", \"PROXY_URL\")\n\n_URL_TEMPLATE = \"https://static.rust-lang.org/dist/{iso_date}/{component}-nightly-{triple}.tar.xz\"\n\ndef _rustfmt_standalone_repository_impl(repository_ctx):\n    triple = repository_ctx.attr.triple\n    iso_date = repository_ctx.attr.iso_date\n    proxy = PROXY_URL\n\n    # Download the rustfmt component (contains bin/rustfmt and bin/cargo-fmt).\n    rustfmt_url = proxy + _URL_TEMPLATE.format(\n        iso_date = iso_date,\n        component = \"rustfmt\",\n        triple = triple,\n    )\n    repository_ctx.download_and_extract(\n        url = rustfmt_url,\n        sha256 = repository_ctx.attr.rustfmt_sha256,\n        stripPrefix = \"rustfmt-nightly-{}/rustfmt-preview\".format(triple),\n    )\n\n    # Download the rustc component (contains shared libraries that rustfmt\n    # dynamically links against, e.g. librustc_driver-*.so / .dylib).\n    rustc_url = proxy + _URL_TEMPLATE.format(\n        iso_date = iso_date,\n        component = \"rustc\",\n        triple = triple,\n    )\n    repository_ctx.download_and_extract(\n        url = rustc_url,\n        sha256 = repository_ctx.attr.rustc_sha256,\n        stripPrefix = \"rustc-nightly-{}/rustc\".format(triple),\n    )\n\n    # Create a wrapper script that sets the library path so the dynamically-linked\n    # rustfmt binary can find its libraries.  We embed absolute paths because the\n    # wrapper may be copied to bazel-bin by native_binary.\n    repo_dir = str(repository_ctx.path(\"\"))\n    is_macos = \"apple-darwin\" in triple\n    if is_macos:\n        wrapper = \"\"\"\\\n#!/bin/bash\nDYLD_LIBRARY_PATH=\"{lib_dir}:${{DYLD_LIBRARY_PATH:-}}\" exec \"{rustfmt_bin}\" \"$@\"\n\"\"\"\n    else:\n        wrapper = \"\"\"\\\n#!/bin/bash\nLD_LIBRARY_PATH=\"{lib_dir}:${{LD_LIBRARY_PATH:-}}\" exec \"{rustfmt_bin}\" \"$@\"\n\"\"\"\n    repository_ctx.file(\"rustfmt\", content = wrapper.format(\n        lib_dir = repo_dir + \"/lib\",\n        rustfmt_bin = repo_dir + \"/bin/rustfmt\",\n    ), executable = True)\n\n    repository_ctx.file(\"BUILD.bazel\", 'exports_files([\"rustfmt\"])\\n')\n\nrustfmt_standalone_repository = repository_rule(\n    doc = \"Downloads a standalone rustfmt binary and the rustc shared libraries it needs.\",\n    attrs = {\n        \"triple\": attr.string(mandatory = True, doc = \"Rust target triple, e.g. x86_64-unknown-linux-gnu\"),\n        \"iso_date\": attr.string(mandatory = True, doc = \"Nightly date, e.g. 2026-02-12\"),\n        \"rustfmt_sha256\": attr.string(mandatory = True, doc = \"SHA-256 of the rustfmt component tarball\"),\n        \"rustc_sha256\": attr.string(mandatory = True, doc = \"SHA-256 of the rustc component tarball\"),\n    },\n    implementation = _rustfmt_standalone_repository_impl,\n)\n\n# -- Module extension --\n\ndef _rustfmt_standalone_ext_impl(module_ctx):\n    for mod in module_ctx.modules:\n        for repo in mod.tags.repo:\n            rustfmt_standalone_repository(\n                name = repo.name,\n                triple = repo.triple,\n                iso_date = repo.iso_date,\n                rustfmt_sha256 = repo.rustfmt_sha256,\n                rustc_sha256 = repo.rustc_sha256,\n            )\n    return module_ctx.extension_metadata(\n        root_module_direct_deps = \"all\",\n        root_module_direct_dev_deps = [],\n    )\n\n_repo_tag = tag_class(attrs = {\n    \"name\": attr.string(mandatory = True),\n    \"triple\": attr.string(mandatory = True),\n    \"iso_date\": attr.string(mandatory = True),\n    \"rustfmt_sha256\": attr.string(mandatory = True),\n    \"rustc_sha256\": attr.string(mandatory = True),\n})\n\nrustfmt_standalone = module_extension(\n    implementation = _rustfmt_standalone_ext_impl,\n    tag_classes = {\"repo\": _repo_tag},\n)\n"
  },
  {
    "path": "build/deps/gen/build_deps.MODULE.bazel",
    "content": "# WARNING: THIS FILE IS AUTOGENERATED BY update-deps.py DO NOT EDIT\n\nhttp = use_extension(\"@//:build/exts/http.bzl\", \"http\")\n\ngit_repository = use_repo_rule(\"@bazel_tools//tools/build_defs/repo:git.bzl\", \"git_repository\")\n\n# abseil-cpp\nbazel_dep(name = \"abseil-cpp\", version = \"20260107.1\")\n\n# apple_support\nbazel_dep(name = \"apple_support\", version = \"2.3.0\")\n\n# aspect_rules_esbuild\nbazel_dep(name = \"aspect_rules_esbuild\", version = \"0.25.1\")\n\n# aspect_rules_js\nbazel_dep(name = \"aspect_rules_js\", version = \"3.0.3\")\n\n# aspect_rules_ts\nbazel_dep(name = \"aspect_rules_ts\", version = \"3.8.7\")\n\n# bazel_lib\nbazel_dep(name = \"bazel_lib\", version = \"3.2.2\")\n\n# bazel_skylib\nbazel_dep(name = \"bazel_skylib\", version = \"1.9.0\")\n\n# cargo_bazel_linux_arm64\nhttp.file(\n    name = \"cargo_bazel_linux_arm64\",\n    executable = True,\n    sha256 = \"2a293b1fbffff0e56780f9cabbc32cb6b2519d036ead614d3251e4c79135df7e\",\n    url = \"https://github.com/bazelbuild/rules_rust/releases/download/0.69.0/cargo-bazel-aarch64-unknown-linux-gnu\",\n)\nuse_repo(http, \"cargo_bazel_linux_arm64\")\n\n# cargo_bazel_linux_x64\nhttp.file(\n    name = \"cargo_bazel_linux_x64\",\n    executable = True,\n    sha256 = \"2727d6a1be44b6bf4935f09bc54ba3fd04ed602e012c51aafd1ba036580b229e\",\n    url = \"https://github.com/bazelbuild/rules_rust/releases/download/0.69.0/cargo-bazel-x86_64-unknown-linux-gnu\",\n)\nuse_repo(http, \"cargo_bazel_linux_x64\")\n\n# cargo_bazel_macos_arm64\nhttp.file(\n    name = \"cargo_bazel_macos_arm64\",\n    executable = True,\n    sha256 = \"9b41cac2040b3f4a1fdad34cb1e9a31afccaa56c60d156c163ded56eb23c0579\",\n    url = \"https://github.com/bazelbuild/rules_rust/releases/download/0.69.0/cargo-bazel-aarch64-apple-darwin\",\n)\nuse_repo(http, \"cargo_bazel_macos_arm64\")\n\n# cargo_bazel_macos_x64\nhttp.file(\n    name = \"cargo_bazel_macos_x64\",\n    executable = True,\n    sha256 = \"56b2f7306e2c5c3c3dca5579cb0a97e360fe0c6731f1698dc9a41864a3dc1842\",\n    url = \"https://github.com/bazelbuild/rules_rust/releases/download/0.69.0/cargo-bazel-x86_64-apple-darwin\",\n)\nuse_repo(http, \"cargo_bazel_macos_x64\")\n\n# cargo_bazel_win_x64\nhttp.file(\n    name = \"cargo_bazel_win_x64\",\n    downloaded_file_path = \"downloaded.exe\",\n    executable = True,\n    sha256 = \"1695baf5ac5d9f4c5328455e1e7e70c17745466e5f7b4ecdea7510d82d525865\",\n    url = \"https://github.com/bazelbuild/rules_rust/releases/download/0.69.0/cargo-bazel-x86_64-pc-windows-msvc.exe\",\n)\nuse_repo(http, \"cargo_bazel_win_x64\")\n\n# clang_tidy_darwin_arm64\nhttp.file(\n    name = \"clang_tidy_darwin_arm64\",\n    executable = True,\n    sha256 = \"65599ed9056d5da503cd4a0b179276d0676d959eb3a6b19c1720fe4ac697891a\",\n    url = \"https://github.com/cloudflare/workerd-tools/releases/download/clang-tidy-22.1.1/llvm-22.1.1-darwin-arm64-clang-tidy\",\n)\nuse_repo(http, \"clang_tidy_darwin_arm64\")\n\n# clang_tidy_linux_amd64\nhttp.file(\n    name = \"clang_tidy_linux_amd64\",\n    executable = True,\n    sha256 = \"52b56c8f46a80dbbde9334f3de0da45744e65757b1ab467e513d5d8cd1d0b771\",\n    url = \"https://github.com/cloudflare/workerd-tools/releases/download/clang-tidy-22.1.1/llvm-22.1.1-linux-amd64-clang-tidy\",\n)\nuse_repo(http, \"clang_tidy_linux_amd64\")\n\n# clang_tidy_linux_arm64\nhttp.file(\n    name = \"clang_tidy_linux_arm64\",\n    executable = True,\n    sha256 = \"1ac2e03fee590aaf920861e83be27e8936cd9a5476a90f43bf88c8e6eb424be6\",\n    url = \"https://github.com/cloudflare/workerd-tools/releases/download/clang-tidy-22.1.1/llvm-22.1.1-linux-arm64-clang-tidy\",\n)\nuse_repo(http, \"clang_tidy_linux_arm64\")\n\n# platforms\nbazel_dep(name = \"platforms\", version = \"1.0.0\")\n\n# rules_cc\nbazel_dep(name = \"rules_cc\", version = \"0.2.17\")\n\n# rules_multirun\nbazel_dep(name = \"rules_multirun\", version = \"0.13.0\")\n\n# rules_nodejs\nbazel_dep(name = \"rules_nodejs\", version = \"6.7.3\")\n\n# rules_oci\nbazel_dep(name = \"rules_oci\", version = \"2.3.0\")\n\n# rules_python\nbazel_dep(name = \"rules_python\", version = \"1.9.0\")\n\n# rules_shell\nbazel_dep(name = \"rules_shell\", version = \"0.6.1\")\n\n# wasm_tools_linux_arm64\nhttp.archive(\n    name = \"wasm_tools_linux_arm64\",\n    build_file_content = \"exports_files([\\\"wasm-tools\\\"])\",\n    sha256 = \"e01ef74b8e7b4a819d91122fdd87084fb25a938e4bfa4179cc5524b961468c85\",\n    strip_prefix = \"wasm-tools-1.245.1-aarch64-linux\",\n    type = \"tgz\",\n    url = \"https://github.com/bytecodealliance/wasm-tools/releases/download/v1.245.1/wasm-tools-1.245.1-aarch64-linux.tar.gz\",\n)\nuse_repo(http, \"wasm_tools_linux_arm64\")\n\n# wasm_tools_linux_x64\nhttp.archive(\n    name = \"wasm_tools_linux_x64\",\n    build_file_content = \"exports_files([\\\"wasm-tools\\\"])\",\n    sha256 = \"b171e20fd107e63e89ef6c936b5581597666a086af677d7818de92b7cdd5a86d\",\n    strip_prefix = \"wasm-tools-1.245.1-x86_64-linux\",\n    type = \"tgz\",\n    url = \"https://github.com/bytecodealliance/wasm-tools/releases/download/v1.245.1/wasm-tools-1.245.1-x86_64-linux.tar.gz\",\n)\nuse_repo(http, \"wasm_tools_linux_x64\")\n\n# wasm_tools_macos_arm64\nhttp.archive(\n    name = \"wasm_tools_macos_arm64\",\n    build_file_content = \"exports_files([\\\"wasm-tools\\\"])\",\n    sha256 = \"d69043b13f8ad4bc07c993e9630e795a7f2c2af488e5688d15044a1448dfa139\",\n    strip_prefix = \"wasm-tools-1.245.1-aarch64-macos\",\n    type = \"tgz\",\n    url = \"https://github.com/bytecodealliance/wasm-tools/releases/download/v1.245.1/wasm-tools-1.245.1-aarch64-macos.tar.gz\",\n)\nuse_repo(http, \"wasm_tools_macos_arm64\")\n\n# wasm_tools_macos_x64\nhttp.archive(\n    name = \"wasm_tools_macos_x64\",\n    build_file_content = \"exports_files([\\\"wasm-tools\\\"])\",\n    sha256 = \"dd718c5c9c6044f97e2d6ee076e91f6e448c8a3b31d3c5397b16f03c461857b7\",\n    strip_prefix = \"wasm-tools-1.245.1-x86_64-macos\",\n    type = \"tgz\",\n    url = \"https://github.com/bytecodealliance/wasm-tools/releases/download/v1.245.1/wasm-tools-1.245.1-x86_64-macos.tar.gz\",\n)\nuse_repo(http, \"wasm_tools_macos_x64\")\n\n# wasm_tools_windows_x64\nhttp.archive(\n    name = \"wasm_tools_windows_x64\",\n    build_file_content = \"exports_files([\\\"wasm-tools.exe\\\"])\",\n    sha256 = \"3d7896b11b419b64a5f1cf81cef92a8da3371babc620eaf75fba4bca4670a75b\",\n    strip_prefix = \"wasm-tools-1.245.1-x86_64-windows/\",\n    type = \"zip\",\n    url = \"https://github.com/bytecodealliance/wasm-tools/releases/download/v1.245.1/wasm-tools-1.245.1-x86_64-windows.zip\",\n)\nuse_repo(http, \"wasm_tools_windows_x64\")\n"
  },
  {
    "path": "build/deps/gen/deps.MODULE.bazel",
    "content": "# WARNING: THIS FILE IS AUTOGENERATED BY update-deps.py DO NOT EDIT\n\nhttp = use_extension(\"@//:build/exts/http.bzl\", \"http\")\n\ngit_repository = use_repo_rule(\"@bazel_tools//tools/build_defs/repo:git.bzl\", \"git_repository\")\n\n# ada-url\nbazel_dep(name = \"ada-url\", version = \"3.4.2\")\n\n# boringssl\nbazel_dep(name = \"boringssl\", repo_name = \"ssl\")\narchive_override(\n    module_name = \"boringssl\",\n    patch_strip = 1,\n    patches = [\n        \"//:patches/boringssl/0001-Expose-libdecrepit-so-NodeJS-can-use-it-for-ncrypto.patch\",\n    ],\n    sha256 = \"c54bb763140d86bc13ce1ec256f02189ddb59597c9765b75f945b7401b4916a7\",\n    strip_prefix = \"google-boringssl-bc2e0ff\",\n    type = \"tgz\",\n    url = \"https://api.github.com/repos/google/boringssl/tarball/0.20251002.0\",\n)\n\n# brotli\nbazel_dep(name = \"brotli\", version = \"1.2.0\")\n\n# capnp-cpp\nhttp.archive(\n    name = \"capnp-cpp\",\n    sha256 = \"403351b295d035d53356215a2ecd15627adfb80aa4b90c80f138904733bae2b7\",\n    strip_prefix = \"capnproto-capnproto-55ba056/c++\",\n    type = \"tgz\",\n    url = \"https://github.com/capnproto/capnproto/tarball/55ba0564e192677b2e63b092c55080f3173c669b\",\n)\nuse_repo(http, \"capnp-cpp\")\n\n# dragonbox\nhttp.archive(\n    name = \"dragonbox\",\n    build_file_content = \"cc_library(name = 'dragonbox', hdrs = glob(['include/dragonbox/*.h']), visibility = ['//visibility:public'], include_prefix = 'third_party/dragonbox/src')\",\n    sha256 = \"5892d183e651ffa37addeb26aa04c88168c4a509b235534f669748c3a275eb37\",\n    strip_prefix = \"jk-jeon-dragonbox-6c7c925\",\n    type = \"tgz\",\n    url = \"https://github.com/jk-jeon/dragonbox/tarball/6c7c925b571d54486b9ffae8d9d18a822801cbda\",\n)\nuse_repo(http, \"dragonbox\")\n\n# fast_float\nbazel_dep(name = \"fast_float\")\narchive_override(\n    module_name = \"fast_float\",\n    build_file_content = \"cc_library(name = 'fast_float', hdrs = glob(['include/fast_float/*.h']), visibility = ['//visibility:public'], include_prefix = 'third_party/fast_float/src')\",\n    remote_file_integrity = {\"MODULE.bazel\": \"sha256-Q1BGZO/fpMbPE0libIcTXJuHkmMlxyBFjzlu7iVWjto=\"},\n    remote_file_urls = {\"MODULE.bazel\": [\"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/refs/heads/main/modules/fast_float/8.0.2/MODULE.bazel\"]},\n    sha256 = \"8f1dc06ac2ea1a39343c1bfbd8319134f295677ed04f0a4e63c296f5bd4d20d6\",\n    strip_prefix = \"fastfloat-fast_float-cb1d42a\",\n    type = \"tgz\",\n    url = \"https://github.com/fastfloat/fast_float/tarball/cb1d42aaa1e14b09e1452cfdef373d051b8c02a4\",\n)\n\n# fp16\nbazel_dep(name = \"fp16\")\narchive_override(\n    module_name = \"fp16\",\n    build_file_content = \"exports_files(glob(['**']))\",\n    remote_file_integrity = {\"MODULE.bazel\": \"sha256-2YAHXAyoWo6FapWo2MBLpWyewByY+F4tSWUMbYt8gmg=\"},\n    remote_file_urls = {\"MODULE.bazel\": [\"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/refs/heads/main/modules/fp16/0.0.0-20210320-0a92994/MODULE.bazel\"]},\n    sha256 = \"adccd6b677a166a0f5073dc4dd0d897c6cc1ffe99a475997ed3627aca3909d83\",\n    strip_prefix = \"Maratyszcza-FP16-b372061\",\n    type = \"tgz\",\n    url = \"https://github.com/Maratyszcza/FP16/tarball/b3720617faf1a4581ed7e6787cc51722ec7751f0\",\n)\n\n# highway\nbazel_dep(name = \"highway\")\narchive_override(\n    module_name = \"highway\",\n    remote_file_integrity = {\"MODULE.bazel\": \"sha256-2UVSfmwaox6VsgqN+q+Ci+ofGKIJCDc+psSq2YsurfQ=\"},\n    remote_file_urls = {\"MODULE.bazel\": [\"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/refs/heads/main/modules/highway/1.3.0/MODULE.bazel\"]},\n    sha256 = \"716d5d824c0243a802c22549295ea983381325b45678d24cc171fc69b9cc2b97\",\n    strip_prefix = \"google-highway-00fe003\",\n    type = \"tgz\",\n    url = \"https://github.com/google/highway/tarball/00fe003dac355b979f36157f9407c7c46448958e\",\n)\n\n# nbytes\nhttp.archive(\n    name = \"nbytes\",\n    build_file = \"//:build/BUILD.nbytes\",\n    sha256 = \"bbe27b920660e50ad039b6f90827b1491061b3061fd7b0cf9ec3a5d52d552f4f\",\n    strip_prefix = \"nodejs-nbytes-1a62f8e\",\n    type = \"tgz\",\n    url = \"https://api.github.com/repos/nodejs/nbytes/tarball/v0.1.3\",\n)\nuse_repo(http, \"nbytes\")\n\n# ncrypto\nhttp.archive(\n    name = \"ncrypto\",\n    sha256 = \"def70950b184c16117a4c1f59bbe290427d71e03fde47e8126b47ec79158c5c3\",\n    strip_prefix = \"nodejs-ncrypto-c569580\",\n    type = \"tgz\",\n    url = \"https://api.github.com/repos/nodejs/ncrypto/tarball/v1.1.3\",\n)\nuse_repo(http, \"ncrypto\")\n\n# perfetto\nbazel_dep(name = \"perfetto\")\narchive_override(\n    module_name = \"perfetto\",\n    patch_strip = 1,\n    patches = [\n        \"//:patches/perfetto/0001-Don-t-attempt-to-use-rules_android.patch\",\n        \"//:patches/perfetto/0002-disable-info-level-logging.patch\",\n    ],\n    sha256 = \"90aea67f5ac88ae7bb56bc24574beb5cd924a5ae9d861826a6fd151c13b4767b\",\n    strip_prefix = \"google-perfetto-b34c975\",\n    type = \"tgz\",\n    url = \"https://api.github.com/repos/google/perfetto/tarball/v54.0\",\n)\n\n# simdutf\nhttp.archive(\n    name = \"simdutf\",\n    build_file = \"//:build/BUILD.simdutf\",\n    sha256 = \"dce5756f20c03202ad48de43ff306629f7e0dd2bed204dfa2967ea13fcab296e\",\n    strip_prefix = \"\",\n    type = \"zip\",\n    url = \"https://github.com/simdutf/simdutf/releases/download/v8.2.0/singleheader.zip\",\n)\nuse_repo(http, \"simdutf\")\n\n# tcmalloc\nbazel_dep(name = \"tcmalloc\", version = \"0.0.0-20250927-12f2552\")\n\n# workerd-cxx\nhttp.archive(\n    name = \"workerd-cxx\",\n    sha256 = \"8a580be60520488ab8bfb336bc0eb6b53a2d99989c82b4cd4c72f7db7bb4edf2\",\n    strip_prefix = \"cloudflare-workerd-cxx-c93b780\",\n    type = \"tgz\",\n    url = \"https://github.com/cloudflare/workerd-cxx/tarball/c93b7805fbe069d195049055a3ed21156fd76629\",\n)\nuse_repo(http, \"workerd-cxx\")\n\n# zlib\nbazel_dep(name = \"zlib\")\ngit_override(\n    module_name = \"zlib\",\n    build_file = \"//:build/BUILD.zlib\",\n    commit = \"b80f1d1e5256ac25f6aea3f31f13d458981cb1f9\",\n    patch_strip = 1,\n    patches = [\n        \"//:patches/zlib/0001-Add-dummy-MODULE.bazel.patch\",\n    ],\n    remote = \"https://chromium.googlesource.com/chromium/src/third_party/zlib.git\",\n)\n\n# zstd\nbazel_dep(name = \"zstd\", version = \"1.5.7.bcr.1\")\n"
  },
  {
    "path": "build/deps/gen/shared_deps.MODULE.bazel",
    "content": "# WARNING: THIS FILE IS AUTOGENERATED BY update-deps.py DO NOT EDIT\n\nhttp = use_extension(\"@//:build/exts/http.bzl\", \"http\")\n\ngit_repository = use_repo_rule(\"@bazel_tools//tools/build_defs/repo:git.bzl\", \"git_repository\")\n\n# buildifier-darwin-amd64\nhttp.file(\n    name = \"buildifier-darwin-amd64\",\n    executable = True,\n    sha256 = \"9f8cffceb82f4e6722a32a021cbc9a5344b386b77b9f79ee095c61d087aaea06\",\n    url = \"https://github.com/bazelbuild/buildtools/releases/download/v8.2.1/buildifier-darwin-amd64\",\n)\nuse_repo(http, \"buildifier-darwin-amd64\")\n\n# buildifier-darwin-arm64\nhttp.file(\n    name = \"buildifier-darwin-arm64\",\n    executable = True,\n    sha256 = \"cfab310ae22379e69a3b1810b433c4cd2fc2c8f4a324586dfe4cc199943b8d5a\",\n    url = \"https://github.com/bazelbuild/buildtools/releases/download/v8.2.1/buildifier-darwin-arm64\",\n)\nuse_repo(http, \"buildifier-darwin-arm64\")\n\n# buildifier-linux-amd64\nhttp.file(\n    name = \"buildifier-linux-amd64\",\n    executable = True,\n    sha256 = \"6ceb7b0ab7cf66fceccc56a027d21d9cc557a7f34af37d2101edb56b92fcfa1a\",\n    url = \"https://github.com/bazelbuild/buildtools/releases/download/v8.2.1/buildifier-linux-amd64\",\n)\nuse_repo(http, \"buildifier-linux-amd64\")\n\n# buildifier-linux-arm64\nhttp.file(\n    name = \"buildifier-linux-arm64\",\n    executable = True,\n    sha256 = \"3baa1cf7eb41d51f462fdd1fff3a6a4d81d757275d05b2dd5f48671284e9a1a5\",\n    url = \"https://github.com/bazelbuild/buildtools/releases/download/v8.2.1/buildifier-linux-arm64\",\n)\nuse_repo(http, \"buildifier-linux-arm64\")\n\n# buildifier-windows-amd64\nhttp.file(\n    name = \"buildifier-windows-amd64\",\n    executable = True,\n    sha256 = \"802104da0bcda0424a397ac5be0004c372665a70289a6d5146e652ee497c0dc6\",\n    url = \"https://github.com/bazelbuild/buildtools/releases/download/v8.2.1/buildifier-windows-amd64.exe\",\n)\nuse_repo(http, \"buildifier-windows-amd64\")\n\n# clang-format-darwin-arm64\nhttp.file(\n    name = \"clang-format-darwin-arm64\",\n    executable = True,\n    sha256 = \"08c90dad18580c61caed43b249704f4e8b1ca3dee6abb197185ecaef298149af\",\n    url = \"https://github.com/cloudflare/workerd-tools/releases/download/llvm-18.1.8/llvm-18.1.8-darwin-arm64-clang-format\",\n)\nuse_repo(http, \"clang-format-darwin-arm64\")\n\n# clang-format-linux-amd64\nhttp.file(\n    name = \"clang-format-linux-amd64\",\n    executable = True,\n    sha256 = \"34677b7593943121858197358481d6941d9be1977e024c9e21862bddef62c762\",\n    url = \"https://github.com/cloudflare/workerd-tools/releases/download/llvm-18.1.8/llvm-18.1.8-linux-amd64-clang-format\",\n)\nuse_repo(http, \"clang-format-linux-amd64\")\n\n# clang-format-linux-arm64\nhttp.file(\n    name = \"clang-format-linux-arm64\",\n    executable = True,\n    sha256 = \"e1eb317d1ccceb3d1b544a841bfb7a74a52969d8aa033f40b1796af3821c2a96\",\n    url = \"https://github.com/cloudflare/workerd-tools/releases/download/llvm-18.1.8/llvm-18.1.8-linux-arm64-clang-format\",\n)\nuse_repo(http, \"clang-format-linux-arm64\")\n\n# ruff-darwin-arm64\nhttp.archive(\n    name = \"ruff-darwin-arm64\",\n    build_file_content = \"filegroup(name='file', srcs=['ruff'], visibility=['//visibility:public'])\",\n    sha256 = \"f33ec69d83f713e0ff2cb720969325bb1553e43978e2f1c21498bd31e11fc643\",\n    strip_prefix = \"ruff-aarch64-apple-darwin\",\n    type = \"tgz\",\n    url = \"https://github.com/astral-sh/ruff/releases/download/0.12.1/ruff-aarch64-apple-darwin.tar.gz\",\n)\nuse_repo(http, \"ruff-darwin-arm64\")\n\n# ruff-linux-amd64\nhttp.archive(\n    name = \"ruff-linux-amd64\",\n    build_file_content = \"filegroup(name='file', srcs=['ruff'], visibility=['//visibility:public'])\",\n    sha256 = \"61357b8326d116113596a3d8e9f2398c33f0d21e0f99d50d9e03ac9578194d48\",\n    strip_prefix = \"ruff-x86_64-unknown-linux-gnu\",\n    type = \"tgz\",\n    url = \"https://github.com/astral-sh/ruff/releases/download/0.12.1/ruff-x86_64-unknown-linux-gnu.tar.gz\",\n)\nuse_repo(http, \"ruff-linux-amd64\")\n\n# ruff-linux-arm64\nhttp.archive(\n    name = \"ruff-linux-arm64\",\n    build_file_content = \"filegroup(name='file', srcs=['ruff'], visibility=['//visibility:public'])\",\n    sha256 = \"2e42713b3b544c382e969029cf6019c99df09b4069f6a3a828a9be259f3237c0\",\n    strip_prefix = \"ruff-aarch64-unknown-linux-gnu\",\n    type = \"tgz\",\n    url = \"https://github.com/astral-sh/ruff/releases/download/0.12.1/ruff-aarch64-unknown-linux-gnu.tar.gz\",\n)\nuse_repo(http, \"ruff-linux-arm64\")\n\n# wpt\nhttp.archive(\n    name = \"wpt\",\n    build_file = \"@workerd//:build/BUILD.wpt\",\n    sha256 = \"12e70405452dd56cbd92d4986d4e002fa91db2db1c3f1725d303692b6699974b\",\n    strip_prefix = \"wpt-11b9300f1\",\n    type = \"tgz\",\n    url = \"https://github.com/cloudflare/workerd-tools/releases/download/wpt-11b9300f1/wpt-11b9300f1.tar.gz\",\n)\nuse_repo(http, \"wpt\")\n"
  },
  {
    "path": "build/deps/nodejs.MODULE.bazel",
    "content": "NODE_VERSION = \"22.21.1\"\n\n# ========================================================================================\n# Node.js bootstrap\n#\n# workerd uses Node.js scripts for generating TypeScript types.\nnode = use_extension(\"@rules_nodejs//nodejs:extensions.bzl\", \"node\")\nnode.toolchain(\n    node_urls = [\n        # github workflows may substitute a mirror URL here to avoid fetch failures.\n        # \"WORKERS_MIRROR_URL/https://nodejs.org/dist/v{version}/{filename}\",\n        \"https://nodejs.org/dist/v{version}/{filename}\",\n    ],\n    node_version = NODE_VERSION,\n)\n\nnpm = use_extension(\"@aspect_rules_js//npm:extensions.bzl\", \"npm\")\nnpm.npm_translate_lock(\n    name = \"npm\",\n    bins = {\n        # Expose CLI binaries for OpenNext builds\n        \"esbuild\": [\"esbuild=bin/esbuild\"],\n        \"next\": [\"next=./dist/bin/next\"],\n        \"wrangler\": [\"wrangler=bin/wrangler.js\"],\n    },\n    data = [\n        \"//:package.json\",\n        \"//:pnpm-workspace.yaml\",\n        \"//images/container-client-test:package.json\",\n        \"//src/workerd/api/tests/opennextjs/src:package.json\",\n    ],\n    npmrc = \"//:.npmrc\",\n    pnpm_lock = \"//:pnpm-lock.yaml\",\n    verify_node_modules_ignored = \"//:.bazelignore\",\n)\nuse_repo(npm, \"npm\")\n\nrules_ts_ext = use_extension(\"@aspect_rules_ts//ts:extensions.bzl\", \"ext\")\nrules_ts_ext.deps(\n    # This keeps the TypeScript version in-sync with the editor, which is typically best.\n    ts_version_from = \"//:package.json\",\n)\nuse_repo(rules_ts_ext, \"npm_typescript\")\n"
  },
  {
    "path": "build/deps/oci.MODULE.bazel",
    "content": "# rules_oci and rules_multirun are used to build Docker images\noci = use_extension(\"@rules_oci//oci:extensions.bzl\", \"oci\")\n\n# Base image for container-client-test. This is the smallest container image\n# that meets all of the requirements for the test.\noci.pull(\n    name = \"node_25_slim\",\n    digest = \"sha256:ab1a20f7e76e89392c8a0cb3ef5a4c70f243c6bbb555da22ef4ac259a5466448\",\n    image = \"node:25.2.1-trixie-slim\",\n    platforms = [\n        \"linux/amd64\",\n        \"linux/arm64/v8\",\n    ],\n)\noci.pull(\n    name = \"proxy_everything\",\n    digest = \"sha256:0ef6716c52430096900b150d84a3302057d6cd2319dae7987128c85d0733e3c8\",\n    image = \"docker.io/cloudflare/proxy-everything\",\n    platforms = [\n        \"linux/amd64\",\n        \"linux/arm64\",\n    ],\n)\nuse_repo(oci, \"node_25_slim\", \"node_25_slim_linux_amd64\", \"node_25_slim_linux_arm64_v8\", \"proxy_everything\", \"proxy_everything_linux_amd64\", \"proxy_everything_linux_arm64\")\n"
  },
  {
    "path": "build/deps/python.MODULE.bazel",
    "content": "python = use_extension(\"@rules_python//python/extensions:python.bzl\", \"python\")\npython.toolchain(python_version = \"3.13\")\nuse_repo(python, \"python_3_13\")\n\npip = use_extension(\"@rules_python//python/extensions:pip.bzl\", \"pip\")\npip.parse(\n    hub_name = \"v8_python_deps\",\n    python_version = \"3.13\",\n    # TODO(alexeagle): switch back to upstream when v8 comes via bzlmod\n    # requirements_lock = \"@v8//:bazel/requirements.txt\",\n    requirements_lock = \"//build/deps:v8.requirements.txt\",\n)\npip.parse(\n    hub_name = \"py_deps\",\n    python_version = \"3.13\",\n    requirements_lock = \"//build/deps:requirements.txt\",\n)\nuse_repo(pip, \"py_deps\", \"v8_python_deps\")\n\npyodide = use_extension(\"//build/deps:dep_pyodide.bzl\", \"pyodide\")\nuse_repo(pyodide, \"all_pyodide_wheels_20240829.4\", \"all_pyodide_wheels_20250808\", \"beautifulsoup4_src_0.26.0a2\", \"beautifulsoup4_src_0.28.2\", \"beautifulsoup4_src_development\", \"fastapi_src_0.26.0a2\", \"fastapi_src_0.28.2\", \"fastapi_src_development\", \"pyodide-0.26.0a2\", \"pyodide-0.28.2\", \"pyodide-lock_20240829.4.json\", \"pyodide-lock_20250808.json\", \"pyodide-snapshot-baseline-4569679fb.bin\", \"pyodide-snapshot-baseline-61eedf943.bin\", \"pyodide-snapshot-ew-py-package-snapshot_fastapi-v2.bin\", \"pyodide-snapshot-ew-py-package-snapshot_numpy-v2.bin\", \"pyodide-snapshot-package_snapshot_fastapi-a6ccb56fe.bin\", \"pyodide-snapshot-package_snapshot_numpy-60c9cb28e.bin\", \"pyodide-snapshot-snapshot_a6b652a95810783f5078b9a5dbd4a07c30718acb4ff724e82c25db7353dd7f2d.bin\", \"pyodide_0.26.0a2_2024-03-01_78.capnp.bin\", \"pyodide_0.28.2_2025-01-16_9.capnp.bin\", \"pyodide_dev.capnp.bin\", \"pytest-asyncio_src_0.26.0a2\", \"pytest-asyncio_src_0.28.2\", \"pytest-asyncio_src_development\", \"python-workers-runtime-sdk_src_0.26.0a2\", \"python-workers-runtime-sdk_src_0.28.2\", \"python-workers-runtime-sdk_src_development\", \"scipy_src_0.26.0a2\", \"shapely_src_0.28.2\", \"shapely_src_development\")\n"
  },
  {
    "path": "build/deps/requirements.in",
    "content": "jinja2\n"
  },
  {
    "path": "build/deps/requirements.txt",
    "content": "#\n# This file is autogenerated by pip-compile with Python 3.12\n# by the following command:\n#\n#    pip-compile --generate-hashes requirements.in\n#\njinja2==3.1.6 \\\n    --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \\\n    --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67\n    # via -r requirements.in\nmarkupsafe==3.0.2 \\\n    --hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \\\n    --hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \\\n    --hash=sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0 \\\n    --hash=sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9 \\\n    --hash=sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396 \\\n    --hash=sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13 \\\n    --hash=sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028 \\\n    --hash=sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca \\\n    --hash=sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557 \\\n    --hash=sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832 \\\n    --hash=sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0 \\\n    --hash=sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b \\\n    --hash=sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579 \\\n    --hash=sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a \\\n    --hash=sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c \\\n    --hash=sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff \\\n    --hash=sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c \\\n    --hash=sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22 \\\n    --hash=sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094 \\\n    --hash=sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb \\\n    --hash=sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e \\\n    --hash=sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5 \\\n    --hash=sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a \\\n    --hash=sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d \\\n    --hash=sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a \\\n    --hash=sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b \\\n    --hash=sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8 \\\n    --hash=sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225 \\\n    --hash=sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c \\\n    --hash=sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144 \\\n    --hash=sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f \\\n    --hash=sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87 \\\n    --hash=sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d \\\n    --hash=sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93 \\\n    --hash=sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf \\\n    --hash=sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158 \\\n    --hash=sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84 \\\n    --hash=sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb \\\n    --hash=sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48 \\\n    --hash=sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171 \\\n    --hash=sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c \\\n    --hash=sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6 \\\n    --hash=sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd \\\n    --hash=sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d \\\n    --hash=sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1 \\\n    --hash=sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d \\\n    --hash=sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca \\\n    --hash=sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a \\\n    --hash=sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29 \\\n    --hash=sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe \\\n    --hash=sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798 \\\n    --hash=sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c \\\n    --hash=sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8 \\\n    --hash=sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f \\\n    --hash=sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f \\\n    --hash=sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a \\\n    --hash=sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178 \\\n    --hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0 \\\n    --hash=sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79 \\\n    --hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 \\\n    --hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50\n    # via jinja2\n"
  },
  {
    "path": "build/deps/rust.MODULE.bazel",
    "content": "RUST_STABLE_VERSION = \"1.91.0\"  # LLVM 21\n\nRUST_NIGHTLY_VERSION = \"nightly/2025-10-30\"\n\n# List of additional triples to be configured on top of the local platform triple\nRUST_TARGET_TRIPLES = [\n    # Add support for macOS cross-compilation\n    \"x86_64-apple-darwin\",\n    # Add support for macOS rosetta\n    \"aarch64-unknown-linux-gnu\",\n]\n\n# rules_rust\nbazel_dep(name = \"rules_rust\", version = \"0.68.1\")\n\n# Under Bazel 9, Rust coverage is broken when using experimental_split_coverage_postprocessing.\n# TODO(cleanup): https://github.com/bazelbuild/rules_rust/pull/3812 should fix this, we're using\n# that commit for now. Switch back to regular release once resolved.\narchive_override(\n    module_name = \"rules_rust\",\n    integrity = \"sha256-dtn87CNPspDl5xEfAGEl0eQ4HBf07vpxIdMNPRHqMqM=\",\n    strip_prefix = \"bazelbuild-rules_rust-cb9d412\",\n    type = \"tgz\",\n    url = \"https://github.com/bazelbuild/rules_rust/tarball/cb9d412afdb95578bd533b60483eecb373119c36\",\n)\n\nrust = use_extension(\"@rules_rust//rust:extensions.bzl\", \"rust\")\nrust.toolchain(\n    allocator_library = \"@rules_rust//ffi/rs:empty\",\n    edition = \"2024\",\n    extra_rustc_flags_triples = {\n        # Enable ISA extensions matching the ones used for C++. The clmul feature is not included as\n        # it is still \"unstable\" as of 1.86.0.\n        \"x86_64-unknown-linux-gnu\": [\"-Ctarget-feature=+sse4.2\"],\n        \"x86_64-apple-darwin\": [\"-Ctarget-feature=+sse4.2\"],\n        \"x86_64-pc-windows-msvc\": [\"-Ctarget-feature=+sse4.2\"],\n        \"aarch64-unknown-linux-gnu\": [\"-Ctarget-feature=+crc\"],\n        # No options needed for aarch64-apple-darwin: CRC feature is enabled by default.\n    },\n    extra_target_triples = RUST_TARGET_TRIPLES,\n    rustfmt_version = RUST_NIGHTLY_VERSION,\n    versions = [\n        RUST_STABLE_VERSION,\n        RUST_NIGHTLY_VERSION,\n    ],\n)\nuse_repo(rust, \"rust_toolchains\")\n\nregister_toolchains(\"@rust_toolchains//:all\")\n\ncrate_repositories = use_extension(\"//deps/rust:extension.bzl\", \"crate_repositories\")\nuse_repo(crate_repositories, \"crates_vendor\")\n\n# rust-based lolhtml dependency, including the API header.\n# Presented as a separate repository to allow overrides.\nnew_local_repository = use_repo_rule(\"@bazel_tools//tools/build_defs/repo:local.bzl\", \"new_local_repository\")\n\nnew_local_repository(\n    name = \"com_cloudflare_lol_html\",\n    build_file = \"@workerd//deps/rust:BUILD.lolhtml\",\n    path = \"empty\",\n)\n\n# workerd-cxx\nhttp = use_extension(\"@//:build/exts/http.bzl\", \"http\")\n\ninject_repo(\n    http,\n    **{\"crates.io\": \"crates_vendor\"}\n)\n"
  },
  {
    "path": "build/deps/shared_deps.jsonc",
    "content": "{\n  \"$schema\": \"deps.schema.json\",\n  \"repositories\": [\n    //buildifier\n    // Version is frozen to avoid to keep formatting consistent.\n    {\n      \"name\": \"buildifier-linux-amd64\",\n      \"type\": \"github_release\",\n      \"owner\": \"bazelbuild\",\n      \"repo\": \"buildtools\",\n      \"file_regex\": \"^buildifier-linux-amd64$\",\n      \"file_type\": \"executable\",\n      \"freeze_version\": \"v8.2.1\"\n    },\n    {\n      \"name\": \"buildifier-linux-arm64\",\n      \"type\": \"github_release\",\n      \"owner\": \"bazelbuild\",\n      \"repo\": \"buildtools\",\n      \"file_regex\": \"^buildifier-linux-arm64$\",\n      \"file_type\": \"executable\",\n      \"freeze_version\": \"v8.2.1\"\n\n    },\n    {\n      \"name\": \"buildifier-darwin-amd64\",\n      \"type\": \"github_release\",\n      \"owner\": \"bazelbuild\",\n      \"repo\": \"buildtools\",\n      \"file_regex\": \"^buildifier-darwin-amd64$\",\n      \"file_type\": \"executable\",\n      \"freeze_version\": \"v8.2.1\"\n    },\n    {\n      \"name\": \"buildifier-darwin-arm64\",\n      \"type\": \"github_release\",\n      \"owner\": \"bazelbuild\",\n      \"repo\": \"buildtools\",\n      \"file_regex\": \"^buildifier-darwin-arm64$\",\n      \"file_type\": \"executable\",\n      \"freeze_version\": \"v8.2.1\"\n    },\n    {\n      \"name\": \"buildifier-windows-amd64\",\n      \"type\": \"github_release\",\n      \"owner\": \"bazelbuild\",\n      \"repo\": \"buildtools\",\n      \"file_regex\": \"^buildifier-windows-amd64.exe$\",\n      \"file_type\": \"executable\",\n      \"freeze_version\": \"v8.2.1\"\n    },\n    //ruff\n    // Version is frozen to keep formatting consistent.\n    {\n      \"name\": \"ruff-darwin-arm64\",\n      \"type\": \"github_release\",\n      \"owner\": \"astral-sh\",\n      \"repo\": \"ruff\",\n      \"file_regex\": \"^ruff-aarch64-apple-darwin.tar.gz$\",\n      \"build_file_content\": \"filegroup(name='file', srcs=['ruff'], visibility=['//visibility:public'])\",\n      \"freeze_version\": \"0.12.1\"\n    },\n    {\n      \"name\": \"ruff-linux-arm64\",\n      \"type\": \"github_release\",\n      \"owner\": \"astral-sh\",\n      \"repo\": \"ruff\",\n      \"file_regex\": \"^ruff-aarch64-unknown-linux-gnu.tar.gz$\",\n      \"build_file_content\": \"filegroup(name='file', srcs=['ruff'], visibility=['//visibility:public'])\",\n      \"freeze_version\": \"0.12.1\"\n    },\n    {\n      \"name\": \"ruff-linux-amd64\",\n      \"type\": \"github_release\",\n      \"owner\": \"astral-sh\",\n      \"repo\": \"ruff\",\n      \"file_regex\": \"^ruff-x86_64-unknown-linux-gnu.tar.gz$\",\n      \"build_file_content\": \"filegroup(name='file', srcs=['ruff'], visibility=['//visibility:public'])\",\n      \"freeze_version\": \"0.12.1\"\n    },\n    //clang-format\n    // Version is frozen to keep formatting consistent.\n    {\n      \"name\": \"clang-format-linux-amd64\",\n      \"type\": \"github_release\",\n      \"owner\": \"cloudflare\",\n      \"repo\": \"workerd-tools\",\n      \"file_regex\": \"llvm-.*-linux-amd64-clang-format\",\n      \"file_type\": \"executable\",\n      \"freeze_version\": \"llvm-18.1.8\"\n    },\n    {\n      \"name\": \"clang-format-linux-arm64\",\n      \"type\": \"github_release\",\n      \"owner\": \"cloudflare\",\n      \"repo\": \"workerd-tools\",\n      \"file_regex\": \"llvm-.*-linux-arm64-clang-format\",\n      \"file_type\": \"executable\",\n      \"freeze_version\": \"llvm-18.1.8\"\n    },\n    {\n      \"name\": \"clang-format-darwin-arm64\",\n      \"type\": \"github_release\",\n      \"owner\": \"cloudflare\",\n      \"repo\": \"workerd-tools\",\n      \"file_regex\": \"llvm-.*-darwin-arm64-clang-format\",\n      \"file_type\": \"executable\",\n      \"freeze_version\": \"llvm-18.1.8\"\n    },\n    {\n      \"name\": \"wpt\",\n      \"type\": \"github_release\",\n      \"owner\": \"cloudflare\",\n      \"repo\": \"workerd-tools\",\n      \"file_regex\": \"wpt-.*.tar.gz\",\n      \"build_file\": \"@workerd//:build/BUILD.wpt\",\n      \"freeze_version\": \"wpt-11b9300f1\"\n    }\n  ]\n}\n"
  },
  {
    "path": "build/deps/update-deps.py",
    "content": "#!/usr/bin/python3\n\"\"\"\nUsage: update-deps.py [dep_name]\n\"\"\"\n\nimport base64\nimport datetime\nimport hashlib\nimport io\nimport json\nimport os\nimport re\nimport subprocess\nimport sys\nimport tarfile\nimport urllib.request\nimport zipfile\nfrom pathlib import Path\n\nTARGET_FILTER = None if len(sys.argv) < 2 else sys.argv[1]\n\nSCRIPT_DIR = Path(__file__).parent\nif \"BUILD_WORKSPACE_DIRECTORY\" in os.environ:\n    SCRIPT_DIR = Path(os.environ[\"BUILD_WORKSPACE_DIRECTORY\"]) / \"build\" / \"deps\"\n\nGEN_DIR = SCRIPT_DIR / \"gen\"\nALL_DEPS = [\"deps.jsonc\", \"build_deps.jsonc\", \"shared_deps.jsonc\"]\n\n\nTOP = \"\"\"# WARNING: THIS FILE IS AUTOGENERATED BY update-deps.py DO NOT EDIT\n\nhttp = use_extension(\"@//:build/exts/http.bzl\", \"http\")\n\ngit_repository = use_repo_rule(\"@bazel_tools//tools/build_defs/repo:git.bzl\", \"git_repository\")\n\n\"\"\"\n\nGITHUB_TAR_URL_TEMPLATE = \"https://github.com/{owner}/{repo}/tarball/{commit}\"\n\nGITHUB_RELEASE_FILE_URL_TEMPLATE = (\n    \"https://github.com/{owner}/{repo}/releases/download/v{version}/{file}\"\n)\n\nEXT_DEP_TEMPLATE = \"\"\"# {name}\n{ext_name}.{rule_name}({attrs}\n)\nuse_repo({ext_name}, \"{name}\")\n\n\"\"\"\n\n\nREPO_RULE_DEP_TEMPLATE = \"\"\"# {name}\n{rule_name}({attrs}\n)\n\n\"\"\"\n\n\nBAZEL_DEP_TEMPLATE = \"\"\"# {name}\nbazel_dep({attrs})\n\n\"\"\"\n\nBAZEL_DEP_OVERRIDE_TEMPLATE = \"\"\"# {name}\nbazel_dep({bazel_dep_attrs})\n{override_type}({override_attrs}\n)\n\n\"\"\"\n\nGITHUB_ACCESS_TOKEN = \"\"\n\n\ndef format_attr_list(attrs, single_line=False):\n    if not attrs:\n        return \"\"\n\n    # buildifier (Bazel build file formatter) requires keys to be sorted, except name and module_name go first\n    attr_list = sorted(\n        attrs.items(), key=lambda kv: \"\" if kv[0] in {\"name\", \"module_name\"} else kv[0]\n    )\n    attr_strs = (f\"{k} = {format_attr(v)}\" for k, v in attr_list)\n\n    if single_line:\n        # Print attributes on a single line (for short declarations)\n        return \", \".join(attr_strs)\n    else:\n        # Print one attribute per line (for long declarations)\n        return \"\\n\" + \"\\n\".join(f\"    {kv},\" for kv in attr_strs)\n\n\ndef format_attr(v):\n    if isinstance(v, (bool, int)):\n        return str(v)\n    elif isinstance(v, list):\n        # Format lists as multiline with proper indentation and trailing comma\n        return json.dumps(v, indent=8).replace(\"\\n]\", \",\\n    ]\")\n    else:\n        return json.dumps(v)\n\n\ndef format_repo_rule_dep(repo, rule_name, attrs):\n    return REPO_RULE_DEP_TEMPLATE.format(\n        rule_name=rule_name,\n        name=repo[\"name\"],\n        attrs=format_attr_list(repo_attributes(repo) | attrs),\n    )\n\n\ndef format_ext_dep(repo, ext_name, rule_name, attrs):\n    return EXT_DEP_TEMPLATE.format(\n        ext_name=ext_name,\n        rule_name=rule_name,\n        name=repo[\"name\"],\n        attrs=format_attr_list(repo_attributes(repo) | attrs),\n    )\n\n\nclass RateLimitedException(Exception):\n    pass\n\n\nclass AssetsException(Exception):\n    pass\n\n\nclass UnsupportedException(Exception):\n    pass\n\n\ndef github_urlopen(url):\n    \"\"\"\n    A wrapper around urllib.request.urlopen() which parses GitHub rate limit errors and\n    provides a more human-friendly explanation.\n    \"\"\"\n    if GITHUB_ACCESS_TOKEN != \"\":\n        url = urllib.request.Request(url)\n        url.add_header(\"Authorization\", f\"Bearer {GITHUB_ACCESS_TOKEN}\")\n    try:\n        return urllib.request.urlopen(url)\n    except urllib.error.HTTPError as e:\n        reset_ts = e.headers[\"x-ratelimit-reset\"]\n        if e.code != 403 or not reset_ts:\n            raise\n        reset_dt = datetime.datetime.fromtimestamp(int(reset_ts))\n        reset_iso_utc = reset_dt.astimezone(datetime.UTC).isoformat(\" \")\n        reset_iso_local = reset_dt.isoformat(\" \")\n        raise RateLimitedException(\n            f\"\"\"\nWe have been rate-limited by GitHub. We can make API calls again at:\n  {reset_iso_utc} UTC ({reset_iso_local} local time).\n\"\"\"\n            + \"\"\"\nYou can try re-running the script and specifying an access token since authenticated\nGitHub API requests have a higher rate limit.\n\"\"\"\n            if GITHUB_ACCESS_TOKEN == \"\"\n            else \"\"\n        ) from e\n\n\ndef github_last_commit(repo):\n    owner = repo[\"owner\"]\n    github_repo = repo[\"repo\"]\n    branch = repo.get(\"branch\", \"master\")\n    api_url = f\"https://api.github.com/repos/{owner}/{github_repo}/commits/{branch}\"\n    commits = json.loads(github_urlopen(api_url).read())\n    return commits[\"sha\"]\n\n\ndef get_url_content_sha256(url):\n    return hashlib.sha256(urllib.request.urlopen(url).read()).hexdigest()\n\n\ndef get_bcr_module_bazel_url(module_name, version):\n    \"\"\"Generate the URL for a MODULE.bazel file from BCR.\"\"\"\n    return f\"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/refs/heads/main/modules/{module_name}/{version}/MODULE.bazel\"\n\n\ndef get_bcr_module_bazel_integrity(module_name, version):\n    \"\"\"Fetch MODULE.bazel from BCR and compute its SHA256 integrity hash.\"\"\"\n    url = get_bcr_module_bazel_url(module_name, version)\n    content = urllib.request.urlopen(url).read()\n    sha256 = hashlib.sha256(content).digest()\n    return f\"sha256-{base64.b64encode(sha256).decode()}\"\n\n\ndef repo_attributes(repo):\n    repo_attrs = {}\n\n    for option in (\n        \"name\",\n        \"build_file\",\n        \"repo_mapping\",\n        \"downloaded_file_path\",\n        \"build_file_content\",\n        \"patches\",\n    ):\n        if option in repo:\n            repo_attrs[option] = repo[option]\n\n    if \"patches\" in repo_attrs:\n        repo_attrs[\"patch_strip\"] = 1\n\n    if \"use_module_bazel_from_bcr\" in repo:\n        url = get_bcr_module_bazel_url(repo[\"name\"], repo[\"use_module_bazel_from_bcr\"])\n        integrity = get_bcr_module_bazel_integrity(\n            repo[\"name\"], repo[\"use_module_bazel_from_bcr\"]\n        )\n        repo_attrs[\"remote_file_urls\"] = {\"MODULE.bazel\": [url]}\n        repo_attrs[\"remote_file_integrity\"] = {\"MODULE.bazel\": integrity}\n\n    return repo_attrs\n\n\ndef format_bazel_dep_with_override(repo, override_type, override_attrs):\n    \"\"\"Format bazel_dep + archive_override/git_override.\"\"\"\n    name = repo[\"name\"]\n\n    # bazel_dep attributes\n    bazel_dep_attrs = {\"name\": name}\n    if \"repo_name\" in repo:\n        bazel_dep_attrs[\"repo_name\"] = repo[\"repo_name\"]\n\n    # Override attributes - use repo_attributes but swap name for module_name\n    base_attrs = repo_attributes(repo)\n    base_attrs[\"module_name\"] = name\n    del base_attrs[\"name\"]\n\n    return BAZEL_DEP_OVERRIDE_TEMPLATE.format(\n        name=name,\n        bazel_dep_attrs=format_attr_list(bazel_dep_attrs, single_line=True),\n        override_type=override_type,\n        override_attrs=format_attr_list(base_attrs | override_attrs),\n    )\n\n\ndef gen_github_tarball(repo):\n    owner = repo[\"owner\"]\n    github_repo = repo[\"repo\"]\n\n    commit = github_last_commit(repo)\n    if \"freeze_commit\" in repo:\n        if repo[\"freeze_commit\"] != commit:\n            print(\n                \"frozen, update available \",\n                repo[\"freeze_commit\"][:7],\n                \" -> \",\n                commit[:7],\n                end=\"\",\n            )\n        commit = repo[\"freeze_commit\"]\n    else:\n        print(commit[:7], end=\"\")\n\n    prefix = f\"{owner}-{github_repo}-{commit[:7]}\"\n    if \"extra_strip_prefix\" in repo:\n        prefix = prefix + repo[\"extra_strip_prefix\"]\n\n    url = GITHUB_TAR_URL_TEMPLATE.format(\n        owner=owner,\n        repo=github_repo,\n        commit=commit,\n    )\n\n    if \"freeze_sha256\" in repo:\n        sha256 = repo[\"freeze_sha256\"]\n    else:\n        sha256 = get_url_content_sha256(url)\n\n    attrs = dict(\n        url=url,\n        strip_prefix=prefix,\n        sha256=sha256,\n        type=\"tgz\",\n    )\n\n    if repo.get(\"use_bazel_dep\"):\n        return format_bazel_dep_with_override(\n            repo,\n            override_type=\"archive_override\",\n            override_attrs=attrs,\n        )\n    else:\n        return format_ext_dep(\n            repo,\n            ext_name=\"http\",\n            rule_name=\"archive\",\n            attrs=attrs,\n        )\n\n\ndef github_last_release(repo):\n    owner = repo[\"owner\"]\n    github_repo = repo[\"repo\"]\n    api_url = f\"https://api.github.com/repos/{owner}/{github_repo}/releases/latest\"\n    return json.loads(github_urlopen(api_url).read())\n\n\ndef github_release(repo, tag_name):\n    owner = repo[\"owner\"]\n    github_repo = repo[\"repo\"]\n    api_url = (\n        f\"https://api.github.com/repos/{owner}/{github_repo}/releases/tags/{tag_name}\"\n    )\n    return json.loads(github_urlopen(api_url).read())\n\n\ndef gen_github_release(repo):\n    try:\n        release = github_last_release(repo)\n    except urllib.error.HTTPError as e:\n        # If a repo only has pre-releases, github_last_release will throw a 404 error.\n        # In that case, we must specify a \"freeze_version\".\n        if e.code != 404 or \"freeze_version\" not in repo:\n            raise\n        release = None\n\n    if \"freeze_version\" in repo:\n        frozen_release = github_release(repo, repo[\"freeze_version\"])\n        if release is not None and frozen_release[\"tag_name\"] != release[\"tag_name\"]:\n            print(\n                \"frozen, update available: {} -> {}\".format(\n                    frozen_release[\"tag_name\"], release[\"tag_name\"]\n                ),\n                end=\"\",\n            )\n        release = frozen_release\n    else:\n        print(release[\"tag_name\"], end=\"\")\n\n    if \"file_regex\" in repo:\n        # Using file_regex to select a user-uploaded asset\n        url = get_release_asset(repo, release)\n    else:\n        # Using Github-generated tarball\n        url = release[\"tarball_url\"]\n\n    type = \"tgz\"\n    if url.endswith(\".zip\"):\n        type = \"zip\"\n    elif url.endswith(\".xz\"):\n        type = \"xz\"\n    elif url.endswith(\".tar.bz2\"):\n        type = \"tar.bz2\"\n\n    content = urllib.request.urlopen(url).read()\n\n    if \"freeze_sha256\" in repo:\n        sha256 = repo[\"freeze_sha256\"]\n    else:\n        sha256 = hashlib.sha256(content).hexdigest()\n\n    file_type = repo.get(\"file_type\", \"archive\")\n    if file_type == \"archive\":\n        if \"strip_prefix\" in repo:\n            prefix = repo[\"strip_prefix\"]\n        elif url.endswith(\".zip\"):\n            with zipfile.ZipFile(io.BytesIO(content)) as zip:\n                prefix = os.path.commonprefix(zip.namelist())\n        else:\n            with tarfile.open(fileobj=io.BytesIO(content)) as tgz:\n                prefix = os.path.commonprefix(tgz.getnames())\n\n        attrs = dict(\n            url=url,\n            strip_prefix=prefix,\n            sha256=sha256,\n            type=type,\n        )\n\n        if repo.get(\"use_bazel_dep\"):\n            return format_bazel_dep_with_override(\n                repo,\n                override_type=\"archive_override\",\n                override_attrs=attrs,\n            )\n        else:\n            return format_ext_dep(\n                repo,\n                ext_name=\"http\",\n                rule_name=\"archive\",\n                attrs=attrs,\n            )\n    elif file_type == \"executable\":\n        if repo.get(\"use_bazel_dep\"):\n            raise UnsupportedException(\n                \"use_bazel_dep is not supported for executable file_type\"\n            )\n        return format_ext_dep(\n            repo,\n            ext_name=\"http\",\n            rule_name=\"file\",\n            attrs=dict(url=url, sha256=sha256, executable=True),\n        )\n    else:\n        raise UnsupportedException(\"Unsupported file_type: \" + file_type)\n\n\ndef get_release_asset(repo, release):\n    file_regex = re.compile(repo[\"file_regex\"])\n    assets = [a for a in release[\"assets\"] if file_regex.match(a[\"name\"])]\n\n    if len(assets) == 0:\n        raise AssetsException(\"No assets found: \" + json.dumps(release))\n\n    if len(assets) > 1:\n        raise AssetsException(\n            \"Too many assets, use more specific file_regex: \"\n            + str([a[\"name\"] for a in assets])\n        )\n\n    return assets[0][\"browser_download_url\"]\n\n\ndef gen_git_clone(repo):\n    url = repo[\"url\"]\n\n    # We used to clone the repository here to get a shallow_since timestamp, but based\n    # on # https://github.com/bazelbuild/bazel/issues/12857 it is unclear if this is\n    # actually helpful.\n    ls_remote = subprocess.run(\n        [\"git\", \"ls-remote\", url, repo[\"branch\"]], capture_output=True, text=True\n    )\n    ls_remote.check_returncode()\n    commit = ls_remote.stdout.strip().split()[0]\n\n    if \"freeze_commit\" in repo:\n        freeze_commit = repo[\"freeze_commit\"]\n        if freeze_commit != commit:\n            print(\n                \"frozen, update available \",\n                repo[\"freeze_commit\"][:7],\n                \" -> \",\n                commit[:7],\n                end=\"\",\n            )\n            commit = freeze_commit\n        else:\n            print(commit[:7], end=\"\")\n\n    attrs = dict(\n        remote=url,\n        commit=commit,\n    )\n\n    if repo.get(\"use_bazel_dep\"):\n        return format_bazel_dep_with_override(\n            repo,\n            override_type=\"git_override\",\n            override_attrs=attrs,\n        )\n    else:\n        return format_repo_rule_dep(\n            repo,\n            rule_name=\"git_repository\",\n            attrs=attrs,\n        )\n\n\ndef get_bcr_version(name: str) -> str:\n    module_versions_url = f\"https://bcr.bazel.build/modules/{name}/metadata.json\"\n    with urllib.request.urlopen(module_versions_url) as res:\n        meta = json.load(res)\n\n        # Assuming the newer versions are appended to the end of the list\n        for version in reversed(meta[\"versions\"]):\n            # Do not recommend a yanked version\n            if version in meta[\"yanked_versions\"]:\n                continue\n\n            # Make a best efforts attempt to exclude pre-releases\n            if re.search(r\"(beta|rc|alpha|dev)\", version):\n                continue\n\n            # TODO: Consider parsing versions with `packaging`. However, there is no standardized\n            # version scheme for BCR dependencies, so this would definitely fail on some deps.\n\n            return version\n\n\ndef gen_bazel_dep(repo):\n    name = repo[\"name\"]\n\n    latest_version = get_bcr_version(name)\n\n    if \"freeze_version\" in repo:\n        frozen_version = repo[\"freeze_version\"]\n        if frozen_version != latest_version:\n            print(\n                f\"frozen, update available: {frozen_version} -> {latest_version}\",\n                end=\"\",\n            )\n        version = frozen_version\n    else:\n        print(latest_version, end=\"\")\n        version = latest_version\n\n    return BAZEL_DEP_TEMPLATE.format(\n        name=name,\n        attrs=format_attr_list(dict(name=name, version=version), single_line=True),\n    )\n\n\ndef gen_repo_str(repo):\n    if repo[\"type\"] == \"github_tarball\":\n        return gen_github_tarball(repo)\n    elif repo[\"type\"] == \"github_release\":\n        return gen_github_release(repo)\n    elif repo[\"type\"] == \"git_clone\":\n        return gen_git_clone(repo)\n    elif repo[\"type\"] == \"bazel_dep\":\n        return gen_bazel_dep(repo)\n    else:\n        raise UnsupportedException(f\"Unsupported repo type: {repo['type']}\")\n\n\ndef gen_repo_bzl(repo, current_dep):\n    \"\"\"Generate and return the content for a repository dependency.\"\"\"\n    print(\"Checking\", repo[\"name\"], \"... \", end=\"\", flush=True)\n    if TARGET_FILTER is not None and not repo[\"name\"].startswith(TARGET_FILTER):\n        print(\"skipped\")\n        return current_dep\n\n    content = gen_repo_str(repo)\n    print()\n    return content\n\n\ndef gen_deps_bzl(repo_contents, deps_bzl):\n    \"\"\"Generate the index file by concatenating all repository contents.\"\"\"\n    deps_bzl_content = TOP\n\n    # Concatenate all repository contents\n    for content in repo_contents:\n        if content:\n            deps_bzl_content += content\n\n    with deps_bzl.open(\"w\") as f:\n        # Strip extra trailing newline but keep one for POSIX compliance\n        f.write(deps_bzl_content.rstrip(\"\\n\") + \"\\n\")\n\n\ndef process_deps(deps, current_deps, deps_bzl):\n    # Sort repositories by name for consistent ordering (buildifier preference)\n    sorted_repos = sorted(deps[\"repositories\"], key=lambda r: r[\"name\"])\n\n    # Generate content for each repository\n    repo_contents = []\n    for repo in sorted_repos:\n        content = gen_repo_bzl(repo, current_deps.get(repo[\"name\"]))\n        repo_contents.append(content)\n\n    gen_deps_bzl(repo_contents, deps_bzl)\n\n\ndef split_bzl_file(file: Path) -> dict[str, str]:\n    # Creates a map from dependency name to generated code for that dep\n    deps = {}\n    text = file.read_text()\n\n    blocks = iter(re.finditer(r\"^# (.*)$\", text, re.MULTILINE))\n    a = next(blocks)\n\n    for b in blocks:\n        # Key: dependency name from comment line\n        # Value: generated code (all text until the next comment line)\n        deps[a.groups()[0]] = text[a.start() : b.start()]\n        a = b\n\n    deps[a.groups()[0]] = text[a.start() :]\n\n    return deps\n\n\ndef strip_comments(text):\n    # capture string literals first, comments send\n    regex = re.compile(r\"(\\\".*\\\")|(//.*$)\", re.MULTILINE)\n    return regex.sub(\n        lambda match: \"\" if match.group(2) is not None else match.group(1), text\n    )\n\n\ndef read_access_token():\n    if not sys.stdin.isatty():\n        return \"\"\n\n    # 1. Try to obtain token from the gh tool\n    try:\n        res = subprocess.run([\"gh\", \"auth\", \"token\"], capture_output=True)\n        if res.returncode == 0:\n            return res.stdout.decode().strip()\n        else:\n            # User has gh but is not logged in\n            print(\"Please log in to Github\")\n            print(\"$ gh auth login\")\n            raise SystemExit\n    except FileNotFoundError:\n        pass  # User does not have gh tool installed\n\n        print(\n            \"\"\"Follow these steps to obtain a GitHub API access token with\nappropriate permissions:\n\n1. On github.com, go to\nSettings > Developer Settings > Personal access tokens > Fine-grained tokens.\n2. Generate a new token with default settings.\n\nAlternatively, install the gh CLI tool to save time <https://github.com/cli/cli#installation>.\n\"\"\"\n        )\n        print(\n            \"Please enter GitHub API access token (or empty to skip): \",\n            end=\"\",\n            flush=True,\n        )\n\n        return sys.stdin.readline().strip(\"\\n\")\n\n\ndef process_config(deps_file):\n    deps_path = SCRIPT_DIR / deps_file\n    bzl_path = GEN_DIR / Path(deps_file).with_suffix(\".MODULE.bazel\").name\n\n    # Create output directory if it doesn't exist\n    GEN_DIR.mkdir(parents=True, exist_ok=True)\n\n    # Load existing generated deps file (if any)\n    try:\n        current_deps = split_bzl_file(bzl_path)\n    except FileNotFoundError:\n        current_deps = {}\n\n    try:\n        with deps_path.open() as fp:\n            json_text = strip_comments(fp.read())\n            process_deps(json.loads(json_text), current_deps, bzl_path)\n    except FileNotFoundError:\n        pass\n\n\ndef run():\n    if TARGET_FILTER is None:\n        global GITHUB_ACCESS_TOKEN\n        GITHUB_ACCESS_TOKEN = read_access_token()\n\n        # Clean all generated .bazel files\n        for f in GEN_DIR.glob(\"*.bazel\"):\n            f.unlink()\n\n    for deps in ALL_DEPS:\n        process_config(deps)\n\n\nrun()\n"
  },
  {
    "path": "build/deps/v8.MODULE.bazel",
    "content": "\"\"\"\nV8 and its dependencies\n\nNote that googlesource does not generate tarballs deterministically, so we cannot use\nhttp_archive: https://github.com/google/gitiles/issues/84\n\nIt would seem that googlesource would rather we use git protocol.\nFine, we can do that.\n\nWe previously used shallow_since for our git-based dependencies, but this may actually be\nharmful: https://github.com/bazelbuild/bazel/issues/12857\n\nThere is an official mirror for V8 itself on GitHub, but not for dependencies like zlib (Chromium\nfork), icu (Chromium fork), and trace_event, so we still have to use git for them.\n\"\"\"\n\nhttp_archive = use_repo_rule(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\n\ngit_repository = use_repo_rule(\"@bazel_tools//tools/build_defs/repo:git.bzl\", \"git_repository\")\n\nVERSION = \"14.6.202.11\"\n\nINTEGRITY = \"sha256-Ju45oFJMyCWyT/r+d+MDsCbCeoDBisGaj4+KZYIIYSU=\"\n\nPATCHES = [\n    \"0001-Allow-manually-setting-ValueDeserializer-format-vers.patch\",\n    \"0002-Allow-manually-setting-ValueSerializer-format-versio.patch\",\n    \"0003-Allow-Windows-builds-under-Bazel.patch\",\n    \"0004-Disable-bazel-whole-archive-build.patch\",\n    \"0005-Speed-up-V8-bazel-build-by-always-using-target-cfg.patch\",\n    \"0006-Implement-Promise-Context-Tagging.patch\",\n    \"0007-Randomize-the-initial-ExecutionContextId-used-by-the.patch\",\n    \"0008-increase-visibility-of-virtual-method.patch\",\n    \"0009-Add-ValueSerializer-SetTreatFunctionsAsHostObjects.patch\",\n    \"0010-Modify-where-to-look-for-fp16-dependency.-This-depen.patch\",\n    \"0011-Revert-heap-Add-masm-specific-unwinding-annotations-.patch\",\n    \"0012-Update-illegal-invocation-error-message-in-v8.patch\",\n    \"0013-Implement-cross-request-context-promise-resolve-hand.patch\",\n    \"0014-Add-another-slot-in-the-isolate-for-embedder.patch\",\n    \"0015-Add-ValueSerializer-SetTreatProxiesAsHostObjects.patch\",\n    \"0016-Disable-memory-leak-assert-when-shutting-down-V8.patch\",\n    \"0017-Enable-V8-shared-linkage.patch\",\n    \"0018-Modify-where-to-look-for-fast_float-and-simdutf.patch\",\n    \"0019-Remove-unneded-latomic-linker-flag.patch\",\n    \"0020-Add-methods-to-get-heap-and-external-memory-sizes-di.patch\",\n    \"0021-Port-concurrent-mksnapshot-support.patch\",\n    \"0022-Port-V8_USE_ZLIB-support.patch\",\n    \"0023-Modify-where-to-look-for-dragonbox.patch\",\n    \"0024-Disable-slow-handle-check.patch\",\n    \"0025-Workaround-for-builtin-can-allocate-issue.patch\",\n    \"0026-Implement-additional-Exception-construction-methods.patch\",\n    \"0027-Export-icudata-file-to-facilitate-embedding-it.patch\",\n    \"0028-bind-icu-to-googlesource.patch\",\n    \"0029-Add-v8-String-IsFlat-API.patch\",\n    \"0030-Expose-AdjustAmountOfExternalAllocatedMemoryImpl-as-.patch\",\n    \"0031-Add-verify_write_barriers-flag-in-V8-s-bazel-config.patch\",\n    \"0032-Change-lamba-signature-to-get-around-windows-build-f.patch\",\n    \"0033-Return-false-on-Object.hasOwnProperty-with-intercept.patch\",\n    \"0034-Remove-V8-MODULE.bazel-llvm-toolchain-and-libcxx-rep.patch\",\n    \"0035-Remove-libcxx-dep-from-defs.bzl-not-resolvable-via-h.patch\",\n]\n\nhttp_archive(\n    name = \"v8\",\n    integrity = INTEGRITY,\n    patch_args = [\"-p1\"],\n    patches = [\"//:patches/v8/\" + p for p in PATCHES],\n    strip_prefix = \"v8-\" + VERSION,\n    url = \"https://github.com/v8/v8/archive/refs/tags/\" + VERSION + \".tar.gz\",\n)\n\ngit_repository(\n    name = \"com_googlesource_chromium_icu\",\n    build_file = \"@v8//:bazel/BUILD.icu\",\n    commit = \"a86a32e67b8d1384b33f8fa48c83a6079b86f8cd\",\n    patch_cmds = [\"find source -name BUILD.bazel | xargs rm\"],\n    patch_cmds_win = [\"Get-ChildItem -Path source -File -Include BUILD.bazel -Recurse | Remove-Item\"],\n    remote = \"https://chromium.googlesource.com/chromium/deps/icu.git\",\n)\n\n# For use with perfetto, see https://github.com/google/perfetto/blob/main/bazel/standalone/README.md\nlocal_path_override(\n    module_name = \"perfetto_cfg\",\n    path = \"build/perfetto\",\n)\n\n# perfetto is managed in deps.jsonc with use_bazel_dep: true\n\nbazel_dep(name = \"workerd-v8\")\n\n# Tell workerd code where to find v8.\n#\n# We indirect through `@workerd-v8` to allow dependents to override how and where `v8` is built.\n# This is overridden in the internal non-OSS repo.\n#\n# TODO(cleanup): There must be a better way to do this?\n# TODO(soon): Figure out how to build v8 with perfetto enabled. It does not appear\n#             as if the v8 bazel build currently includes support for building with\n#             perfetto enabled as an option.\nlocal_path_override(\n    module_name = \"workerd-v8\",\n    path = \"build/workerd-v8\",\n)\n\nbazel_dep(name = \"google_benchmark\")\nlocal_path_override(\n    module_name = \"google_benchmark\",\n    path = \"build/google-benchmark\",\n)\n"
  },
  {
    "path": "build/deps/v8.requirements.txt",
    "content": "#\n# This file is autogenerated by pip-compile with python 3.9\n# To update, run:\n#\n#    pip-compile --generate-hashes requirements.in\n#\njinja2==3.1.6 \\\n    --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \\\n    --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67\n    # via -r requirements.in\nmarkupsafe==2.0.1 \\\n    --hash=sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298 \\\n    --hash=sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64 \\\n    --hash=sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b \\\n    --hash=sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194 \\\n    --hash=sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567 \\\n    --hash=sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff \\\n    --hash=sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724 \\\n    --hash=sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74 \\\n    --hash=sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646 \\\n    --hash=sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35 \\\n    --hash=sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6 \\\n    --hash=sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a \\\n    --hash=sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6 \\\n    --hash=sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad \\\n    --hash=sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26 \\\n    --hash=sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38 \\\n    --hash=sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac \\\n    --hash=sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7 \\\n    --hash=sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6 \\\n    --hash=sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047 \\\n    --hash=sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75 \\\n    --hash=sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f \\\n    --hash=sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b \\\n    --hash=sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135 \\\n    --hash=sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8 \\\n    --hash=sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a \\\n    --hash=sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a \\\n    --hash=sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1 \\\n    --hash=sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9 \\\n    --hash=sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864 \\\n    --hash=sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914 \\\n    --hash=sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee \\\n    --hash=sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f \\\n    --hash=sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18 \\\n    --hash=sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8 \\\n    --hash=sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2 \\\n    --hash=sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d \\\n    --hash=sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b \\\n    --hash=sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b \\\n    --hash=sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86 \\\n    --hash=sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6 \\\n    --hash=sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f \\\n    --hash=sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb \\\n    --hash=sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833 \\\n    --hash=sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28 \\\n    --hash=sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e \\\n    --hash=sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415 \\\n    --hash=sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902 \\\n    --hash=sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f \\\n    --hash=sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d \\\n    --hash=sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9 \\\n    --hash=sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d \\\n    --hash=sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145 \\\n    --hash=sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066 \\\n    --hash=sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c \\\n    --hash=sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1 \\\n    --hash=sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a \\\n    --hash=sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207 \\\n    --hash=sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f \\\n    --hash=sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53 \\\n    --hash=sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd \\\n    --hash=sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134 \\\n    --hash=sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85 \\\n    --hash=sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9 \\\n    --hash=sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5 \\\n    --hash=sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94 \\\n    --hash=sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509 \\\n    --hash=sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51 \\\n    --hash=sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872\n    # via jinja2\n"
  },
  {
    "path": "build/exts/http.bzl",
    "content": "\"\"\"\nA Bazel module extension that wraps the http_archive and http_file repository rules, in order\nto allow the injection of a custom proxy URL.\n\"\"\"\n\nload(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\", \"http_file\")\nload(\"//:build/http_proxy_config.bzl\", \"PROXY_URL\")\n\ndef _http_ext_impl(ctx):\n    for mod in ctx.modules:\n        for archive in mod.tags.archive:\n            _http_ext_delegate(http_archive, archive)\n\n        for file in mod.tags.file:\n            _http_ext_delegate(http_file, file)\n\ndef _http_ext_delegate(repo_rule, tag_struct):\n    tag_dict = _struct_to_dict(tag_struct)\n    tag_dict[\"url\"] = PROXY_URL + tag_dict[\"url\"]\n    repo_rule(**tag_dict)\n\ndef _struct_to_dict(tag_struct):\n    new_dict = {}\n    for k in dir(tag_struct):\n        new_dict[k] = getattr(tag_struct, k)\n    return new_dict\n\n# The following content is taken from @bazel_tools//tools/build_defs/repo:http.bzl, with\n# documentation stripped to save space. See <https://bazel.build/rules/lib/repo/http> for docs.\n\n_http_archive_attrs = tag_class(attrs = {\n    \"name\": attr.string(),\n    \"url\": attr.string(),\n    \"urls\": attr.string_list(),\n    \"sha256\": attr.string(),\n    \"integrity\": attr.string(),\n    \"netrc\": attr.string(),\n    \"auth_patterns\": attr.string_dict(),\n    \"canonical_id\": attr.string(),\n    \"strip_prefix\": attr.string(),\n    \"add_prefix\": attr.string(\n        default = \"\",\n    ),\n    \"files\": attr.string_keyed_label_dict(),\n    \"type\": attr.string(),\n    \"patches\": attr.label_list(\n        default = [],\n    ),\n    \"remote_file_urls\": attr.string_list_dict(\n        default = {},\n    ),\n    \"remote_file_integrity\": attr.string_dict(\n        default = {},\n    ),\n    \"remote_module_file_urls\": attr.string_list(\n        default = [],\n    ),\n    \"remote_module_file_integrity\": attr.string(\n        default = \"\",\n    ),\n    \"remote_patches\": attr.string_dict(\n        default = {},\n    ),\n    \"remote_patch_strip\": attr.int(\n        default = 0,\n    ),\n    \"patch_tool\": attr.string(\n        default = \"\",\n    ),\n    \"patch_args\": attr.string_list(\n        default = [],\n    ),\n    \"patch_strip\": attr.int(\n        default = 0,\n    ),\n    \"patch_cmds\": attr.string_list(\n        default = [],\n    ),\n    \"patch_cmds_win\": attr.string_list(\n        default = [],\n    ),\n    \"build_file\": attr.label(\n        allow_single_file = True,\n    ),\n    \"build_file_content\": attr.string(),\n    \"workspace_file\": attr.label(),\n    \"workspace_file_content\": attr.string(),\n})\n\n_http_file_attrs = tag_class(attrs = {\n    \"name\": attr.string(),\n    \"executable\": attr.bool(),\n    \"downloaded_file_path\": attr.string(\n        default = \"downloaded\",\n    ),\n    \"sha256\": attr.string(),\n    \"integrity\": attr.string(),\n    \"canonical_id\": attr.string(),\n    \"url\": attr.string(),\n    \"urls\": attr.string_list(),\n    \"netrc\": attr.string(),\n    \"auth_patterns\": attr.string_dict(),\n})\n\n# Extension definition\n\nhttp = module_extension(\n    implementation = _http_ext_impl,\n    tag_classes = {\n        \"archive\": _http_archive_attrs,\n        \"file\": _http_file_attrs,\n    },\n)\n"
  },
  {
    "path": "build/fixtures/BUILD.bazel",
    "content": "exports_files([\"kj_test.sh\"])\n"
  },
  {
    "path": "build/fixtures/kj_test.sh",
    "content": "#!/bin/bash\nset -euo pipefail\n\nif [ -n \"${COVERAGE_DIR:-}\" ]; then\n    export LLVM_PROFILE_FILE=\"$COVERAGE_DIR/%p-%m.profraw\"\nfi\n\nexec \"$@\"\n"
  },
  {
    "path": "build/google-benchmark/BUILD",
    "content": "load(\"@rules_cc//cc:cc_library.bzl\", \"cc_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncc_library(\n    name = \"benchmark\",\n    deps = [\"@codspeed_google_benchmark_compat//:benchmark\"],\n)\n\ncc_library(\n    name = \"benchmark_main\",\n    deps = [\"@codspeed_google_benchmark_compat//:benchmark_main\"],\n)\n\nalias(\n    name = \"codspeed_mode\",\n    actual = \"@codspeed_google_benchmark_compat//:codspeed_mode\",\n)\n"
  },
  {
    "path": "build/google-benchmark/MODULE.bazel",
    "content": "module(name = \"google_benchmark\")\n\nbazel_dep(name = \"bazel_skylib\", version = \"1.8.1\")\nbazel_dep(name = \"platforms\", version = \"1.0.0\")\nbazel_dep(name = \"rules_cc\", version = \"0.2.8\")\nbazel_dep(name = \"codspeed_google_benchmark_compat\", version = \"2.0.0\")\n"
  },
  {
    "path": "build/http.bzl",
    "content": "load(\"@bazel_tools//tools/build_defs/repo:http.bzl\", _http_archive = \"http_archive\", _http_file = \"http_file\")\n\nhttp_archive = _http_archive\nhttp_file = _http_file\n"
  },
  {
    "path": "build/http_proxy_config.bzl",
    "content": "# A custom proxy URL may be set here. It will be used for all http.file and http.archive\n# dependencies declared via bzlmod\n\nPROXY_URL = \"\"\n"
  },
  {
    "path": "build/js_capnp_library.bzl",
    "content": "\"\"\"\nBazel rule to compile .capnp files into JS/TS using capnp-es.\nBased on https://github.com/capnproto/capnproto/blob/3b2e368cecc4b1419b40c5970d74a7a342224fac/c++/src/capnp/cc_capnp_library.bzl.\n\"\"\"\n\nload(\"@aspect_rules_js//js:defs.bzl\", \"js_library\")\n\ncapnp_provider = provider(\"Capnproto Provider\", fields = {\n    \"includes\": \"includes for this target (transitive)\",\n    \"inputs\": \"src + data for the target\",\n    \"src_prefix\": \"src_prefix of the target\",\n})\n\ndef _workspace_path(label, path):\n    if label.workspace_root == \"\":\n        return path\n    return label.workspace_root + \"/\" + path\n\ndef _capnp_plugin_gen(ctx, output_format, out_dir, inputs, includes, src_prefix, system_include):\n    \"\"\"Generate output files of the given output_format (\"js\" or \"ts\")\"\"\"\n\n    if not (output_format == \"js\" or output_format == \"ts\"):\n        fail(\"Only js and ts output formats are supported\")\n\n    # Filter the outputs to generate by requested output_format\n    outputs = [out for out in ctx.outputs.outs if out.path.endswith(\".%s\" % output_format)]\n    if (not outputs):\n        return\n\n    plugin_name = \"_%s_plugin\" % output_format\n    plugin_executable = getattr(ctx.executable, plugin_name)\n    plugin_files = getattr(ctx.files, plugin_name)\n\n    label = ctx.label\n\n    js_out = \"-o%s:%s\" % (plugin_executable.path, out_dir)\n    args = ctx.actions.args()\n    args.add_all([\"compile\", \"--verbose\", js_out])\n    args.add_all([\"-I\" + inc for inc in includes])\n    args.add_all([\"-I\", system_include])\n    if src_prefix != \"\":\n        args.add_all([\"--src-prefix\", src_prefix])\n\n    args.add_all([s for s in ctx.files.srcs])\n\n    ctx.actions.run(\n        inputs = inputs + plugin_files + ctx.files._capnpc_capnp + ctx.files._capnp_system,\n        tools = [plugin_executable],  # Include required js_binary runfiles\n        outputs = outputs,\n        executable = ctx.executable._capnpc,\n        arguments = [args],\n        mnemonic = \"GenCapnp\",\n    )\n\ndef _capnp_gen_impl(ctx):\n    label = ctx.label\n    src_prefix = _workspace_path(label, ctx.attr.src_prefix)\n    includes = []\n\n    inputs = ctx.files.srcs + ctx.files.data\n    for dep_target in ctx.attr.deps:\n        includes += dep_target[capnp_provider].includes\n        inputs += dep_target[capnp_provider].inputs\n\n    if src_prefix != \"\":\n        includes.append(src_prefix)\n\n    system_include = ctx.files._capnp_system[0].dirname.removesuffix(\"/capnp\")\n\n    out_dir = ctx.var[\"GENDIR\"]\n    if src_prefix != \"\":\n        out_dir = out_dir + \"/\" + src_prefix\n\n    _capnp_plugin_gen(\n        ctx,\n        output_format = \"js\",\n        out_dir = out_dir,\n        inputs = inputs,\n        includes = includes,\n        src_prefix = src_prefix,\n        system_include = system_include,\n    )\n\n    _capnp_plugin_gen(\n        ctx,\n        output_format = \"ts\",\n        out_dir = out_dir,\n        inputs = inputs,\n        includes = includes,\n        src_prefix = src_prefix,\n        system_include = system_include,\n    )\n\n    return [\n        capnp_provider(\n            includes = includes,\n            inputs = inputs,\n            src_prefix = src_prefix,\n        ),\n    ]\n\n_capnp_gen = rule(\n    attrs = {\n        \"srcs\": attr.label_list(allow_files = True),\n        \"deps\": attr.label_list(providers = [capnp_provider]),\n        \"data\": attr.label_list(allow_files = True),\n        \"outs\": attr.output_list(),\n        \"src_prefix\": attr.string(),\n        \"_capnpc\": attr.label(executable = True, allow_single_file = True, cfg = \"exec\", default = \"@capnp-cpp//src/capnp:capnp_tool\"),\n        \"_js_plugin\": attr.label(executable = True, allow_single_file = True, cfg = \"exec\", default = \"//:capnpc_js_plugin\"),\n        \"_ts_plugin\": attr.label(executable = True, allow_single_file = True, cfg = \"exec\", default = \"//:capnpc_ts_plugin\"),\n        \"_capnpc_capnp\": attr.label(executable = True, allow_single_file = True, cfg = \"exec\", default = \"@capnp-cpp//src/capnp:capnpc-capnp\"),\n        \"_capnp_system\": attr.label(default = \"@capnp-cpp//src/capnp:capnp_system_library\"),\n    },\n    output_to_genfiles = True,\n    implementation = _capnp_gen_impl,\n)\n\ndef js_capnp_library(\n        name,\n        srcs = [],\n        data = [],\n        outs = [],\n        deps = [],\n        src_prefix = \"\",\n        visibility = None,\n        target_compatible_with = None,\n        **kwargs):\n    \"\"\"Bazel rule to create a JavaScript capnproto library from capnp source files\n\n    Args:\n        name: library name\n        srcs: list of files to compile\n        data: additional files to provide to the compiler - data files and includes that need not to\n            be compiled\n        outs: expected output files - .js and .ts files\n        deps: other js_capnp_library rules to depend on\n        src_prefix: src_prefix for capnp compiler to the source root\n        visibility: rule visibility\n        target_compatible_with: target compatibility\n        **kwargs: rest of the arguments to js_library rule\n    \"\"\"\n\n    _capnp_gen(\n        name = name + \"_gen\",\n        srcs = srcs,\n        deps = [s + \"_gen\" for s in deps],\n        data = data,\n        outs = outs,\n        src_prefix = src_prefix,\n        visibility = visibility,\n        target_compatible_with = target_compatible_with,\n    )\n    js_library(\n        name = name,\n        srcs = outs,\n        deps = deps + [\"//:node_modules/capnp-es\"],\n        visibility = visibility,\n        target_compatible_with = target_compatible_with,\n        **kwargs\n    )\n"
  },
  {
    "path": "build/js_file.bzl",
    "content": "\"\"\"\nGive a collection of js files a JsInfo provider so it can be used as a dependency for aspect_rules.\n\"\"\"\n\nload(\"@aspect_rules_js//js:providers.bzl\", \"JsInfo\", \"js_info\")\nload(\"@bazel_lib//lib:copy_to_bin.bzl\", \"COPY_FILE_TO_BIN_TOOLCHAINS\", \"copy_file_to_bin_action\")\n\n_ATTRS = {\n    \"srcs\": attr.label_list(\n        allow_files = True,\n    ),\n    \"deps\": attr.label_list(),\n}\n\ndef _gather_sources_and_types(ctx, targets, files):\n    \"\"\"Gathers sources and types from a list of targets\n\n    Args:\n        ctx: the rule context\n\n        targets: List of targets to gather sources and types from their JsInfo providers.\n\n            These typically come from the `srcs` and/or `data` attributes of a rule\n\n        files: List of files to gather as sources and types.\n\n            These typically come from the `srcs` and/or `data` attributes of a rule\n\n    Returns:\n        Sources & declaration files depsets in the sequence (sources, types)\n    \"\"\"\n    sources = []\n    types = []\n\n    for file in files:\n        if file.is_source:\n            file = copy_file_to_bin_action(ctx, file)\n\n        if file.is_directory:\n            # assume a directory contains types since we can't know that it doesn't\n            types.append(file)\n            sources.append(file)\n        elif (\n            file.path.endswith((\".d.ts\", \".d.ts.map\", \".d.mts\", \".d.mts.map\", \".d.cts\", \".d.cts.map\"))\n        ):\n            types.append(file)\n        elif file.path.endswith(\".json\"):\n            # Any .json can produce types: https://www.typescriptlang.org/tsconfig/#resolveJsonModule\n            # package.json may be required to resolve types with the \"typings\" key\n            types.append(file)\n            sources.append(file)\n        else:\n            sources.append(file)\n\n    # sources as depset\n    sources = depset(sources, transitive = [\n        target[JsInfo].sources\n        for target in targets\n        if JsInfo in target\n    ])\n\n    # types as depset\n    types = depset(types, transitive = [\n        target[JsInfo].types\n        for target in targets\n        if JsInfo in target\n    ])\n\n    return (sources, types)\n\ndef _js_file_impl(ctx):\n    sources, types = _gather_sources_and_types(\n        ctx = ctx,\n        targets = ctx.attr.srcs,\n        files = ctx.files.srcs,\n    )\n\n    return [\n        js_info(\n            target = ctx.label,\n            sources = sources,\n            types = types,\n        ),\n        DefaultInfo(\n            files = sources,\n        ),\n        OutputGroupInfo(\n            types = types,\n        ),\n    ]\n\njs_file_lib = struct(\n    attrs = _ATTRS,\n    implementation = _js_file_impl,\n    provides = [DefaultInfo, JsInfo, OutputGroupInfo],\n)\n\njs_file = rule(\n    implementation = js_file_lib.implementation,\n    attrs = js_file_lib.attrs,\n    provides = js_file_lib.provides,\n    toolchains = COPY_FILE_TO_BIN_TOOLCHAINS,\n)\n"
  },
  {
    "path": "build/kj_test.bzl",
    "content": "load(\"@rules_cc//cc:cc_binary.bzl\", \"cc_binary\")\nload(\"@rules_shell//shell:sh_test.bzl\", \"sh_test\")\n\ndef kj_test(\n        src,\n        data = [],\n        deps = [],\n        tags = [],\n        size = \"medium\",\n        **kwargs):\n    test_name = src.removesuffix(\".c++\")\n    binary_name = test_name + \"_binary\"\n    cc_binary(\n        name = binary_name,\n        testonly = True,\n        srcs = [src],\n        deps = [\n            \"@capnp-cpp//src/kj:kj-test\",\n        ] + deps,\n        linkstatic = select({\n            \"@platforms//os:linux\": 0,\n            \"//conditions:default\": 1,\n        }),\n        # For test binaries, reduce thinLTO optimizations and inlining to speed up linking. This\n        # only has an effect if thinLTO is enabled. Also apply dead_strip on macOS to manage binary\n        # sizes.\n        linkopts = select({\n            \"@platforms//os:linux\": [\"-Wl,--lto-O1\", \"-Wl,-mllvm,-import-instr-limit=5\"],\n            \"@//:use_dead_strip\": [\"-Wl,-dead_strip\", \"-Wl,-no_exported_symbols\"],\n            \"//conditions:default\": [\"\"],\n        }),\n        data = data,\n        tags = tags,\n        target_compatible_with = select({\n            \"@//build/config:no_build\": [\"@platforms//:incompatible\"],\n            \"//conditions:default\": [],\n        }),\n        **kwargs\n    )\n\n    pkg = native.package_name()\n    cross_alias = binary_name + \"_cross\"\n    native.alias(\n        name = cross_alias,\n        actual = select({\n            \"@//build/config:prebuilt_binaries_arm64\": \"@//:bin.arm64/tmp/workerd/{}/{}.aarch64-linux-gnu\".format(pkg, binary_name),\n            \"//conditions:default\": binary_name,\n        }),\n    )\n\n    sh_test(\n        name = test_name + \"@\",\n        srcs = [\"//build/fixtures:kj_test.sh\"],\n        args = [\"$(location {})\".format(cross_alias)],\n        data = data + [cross_alias],\n        tags = tags,\n        size = size,\n    )\n\n    sh_test(\n        name = test_name + \"@all-autogates\",\n        srcs = [\"//build/fixtures:kj_test.sh\"],\n        args = [\"$(location {})\".format(cross_alias)],\n        data = data + [cross_alias],\n        env = {\"WORKERD_ALL_AUTOGATES\": \"1\"},\n        # Tag with no-coverage to reduce coverage CI time\n        tags = tags + [\"no-coverage\"],\n        size = size,\n    )\n"
  },
  {
    "path": "build/lint_test.bzl",
    "content": "load(\"@npm//:eslint/package_json.bzl\", eslint_bin = \"bin\")\n\ndef lint_test(\n        name,\n        eslintrc_json,\n        tsconfig_json,\n        srcs,\n        data = []):\n    js_srcs = [src for src in srcs if src.endswith(\".ts\") or src.endswith(\".mts\") or src.endswith(\".js\") or src.endswith(\".mjs\")]\n\n    eslint_bin.eslint_test(\n        size = \"large\",\n        name = name + \"@eslint\",\n        args = [\n            \"--config $(location {})\".format(eslintrc_json),\n            \"--report-unused-disable-directives\",\n        ] + [\"$(location \" + src + \")\" for src in js_srcs],\n        data = srcs + data + [\n            eslintrc_json,\n            tsconfig_json,\n            \"@workerd//tools:base-tsconfig\",\n            \"@workerd//tools:base-eslint\",\n        ],\n        tags = [\"lint\"],\n        target_compatible_with = select({\n            \"@platforms//os:windows\": [\"@platforms//:incompatible\"],\n            \"//conditions:default\": [],\n        }),\n    )\n"
  },
  {
    "path": "build/perfetto/BUILD",
    "content": ""
  },
  {
    "path": "build/perfetto/MODULE.bazel",
    "content": "module(name = \"perfetto_cfg\")\n\nbazel_dep(name = \"rules_python\", version = \"1.6.3\")\nbazel_dep(name = \"rules_cc\", version = \"0.2.11\")\nbazel_dep(name = \"protobuf\", version = \"33.4\")\n"
  },
  {
    "path": "build/perfetto/perfetto_cfg.bzl",
    "content": "# Copyright (C) 2019 The Android Open Source Project\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\nload(\"@protobuf//bazel:cc_proto_library.bzl\", \"cc_proto_library\")\nload(\"@protobuf//bazel:proto_library.bzl\", \"proto_library\")\nload(\"@rules_cc//cc:cc_binary.bzl\", \"cc_binary\")\nload(\"@rules_cc//cc:cc_library.bzl\", \"cc_library\")\nload(\"@rules_python//python:defs.bzl\", \"py_binary\", \"py_library\")\n\n# Noop function used to override rules we don't want to support in standalone.\ndef _noop_override(**kwargs):\n    pass\n\nPERFETTO_CONFIG = struct(\n    # This is used to refer to deps within perfetto's BUILD files.\n    # In standalone and bazel-based embedders use '//', because perfetto has its\n    # own repository, and //xxx would be relative to @perfetto//xxx.\n    # In Google internal builds, instead, this is set to //third_party/perfetto,\n    # because perfetto doesn't have its own repository there.\n    root = \"//\",\n\n    # These variables map dependencies to perfetto third-party projects. This is\n    # to allow perfetto embedders (e.g. gapid) and google internal builds to\n    # override paths and target names to their own third_party.\n    deps = struct(\n        # Target exposing the build config header. It should be a valid\n        # cc_library dependency as it will become a dependency of every\n        # perfetto_cc_library target. It needs to expose a\n        # \"perfetto_build_flags.h\" file that can be included via:\n        # #include \"perfetto_build_flags.h\".\n        build_config = [\"//:build_config_hdr\"],\n\n        # Target exposing the PERFETTO_VERSION_STRING() and\n        # PERFETTO_VERSION_SCM_REVISION() macros. This is overridden in google\n        # internal builds.\n        version_header = [\"//:cc_perfetto_version_header\"],\n\n        # Target exposing platform-specific functionality for base. This is\n        # overridden in Google internal builds.\n        base_platform = [\"//:perfetto_base_default_platform\"],\n        zlib = [\"@zlib//:zlib\"],\n        expat = [\"@perfetto_dep_expat//:expat\"],\n        jsoncpp = [\"@perfetto_dep_jsoncpp//:jsoncpp\"],\n        linenoise = [\"@perfetto_dep_linenoise//:linenoise\"],\n        sqlite = [\"@perfetto_dep_sqlite//:sqlite\"],\n        sqlite_ext_percentile = [\"@perfetto_dep_sqlite_src//:percentile_ext\"],\n        protoc = [\"@protobuf//:protoc\"],\n        protoc_lib = [\"@protobuf//:protoc_lib\"],\n        protobuf_lite = [\"@protobuf//:protobuf_lite\"],\n        protobuf_full = [\"@protobuf//:protobuf\"],\n        protobuf_descriptor_proto = [\"@protobuf//:descriptor_proto\"],\n\n        # The Python targets are empty on the standalone build because we assume\n        # any relevant deps are installed on the system or are not applicable.\n        protobuf_py = [],\n        pandas_py = [],\n        tp_vendor_py = [],\n        tp_resolvers_py = [],\n\n        # There are multiple configurations for the function name demangling\n        # logic in trace processor:\n        # (1) The following defaults include a subset of demangling sources\n        #     from llvm-project. This is the most complete implementation.\n        # (2) You can avoid the llvm dependency by setting \"llvm_demangle = []\"\n        #     here and PERFETTO_LLVM_DEMANGLE to false in your\n        #     perfetto_build_flags.h. Then the implementation will use a\n        #     demangler from the c++ runtime, which will most likely handle\n        #     only itanium mangling, and is unavailable on some platforms (e.g.\n        #     Windows, where it becomes a nop).\n        # (3) You can override the whole demangle_wrapper below, and provide\n        #     your own demangling implementation.\n        demangle_wrapper = [\"//:src_trace_processor_demangle\"],\n        llvm_demangle = [\"@perfetto_dep_llvm_demangle//:llvm_demangle\"],\n\n        # no open csd support needed\n        open_csd = [],\n        android_test_common = [],\n    ),\n\n    # This struct allows embedders to customize the cc_opts for Perfetto\n    # 3rd party dependencies. They only have an effect if the dependencies are\n    # initialized with the Perfetto build files (i.e. via perfetto_deps()).\n    deps_copts = struct(\n        zlib = [],\n        jsoncpp = [],\n        linenoise = [],\n        sqlite = [],\n        llvm_demangle = [],\n    ),\n\n    # Allow Bazel embedders to change the visibility of \"public\" targets.\n    # This variable has been introduced to limit the change to Bazel and avoid\n    # making the targets fully public in the google internal tree.\n    public_visibility = [\n        \"//visibility:public\",\n    ],\n\n    # Allow Bazel embedders to change the visibility of the proto targets.\n    # This variable has been introduced to limit the change to Bazel and avoid\n    # making the targets public in the google internal tree.\n    proto_library_visibility = \"//visibility:private\",\n\n    # Allow Bazel embedders to change the visibility of the Go protos.\n    # Go protos have all sorts of strange behavior in Google3 so need special\n    # handling as the rules for other languages do not work for Go.\n    go_proto_library_visibility = \"//visibility:private\",\n\n    # This struct allows the embedder to customize copts and other args passed\n    # to rules like cc_binary. Prefixed rules (e.g. perfetto_cc_binary) will\n    # look into this struct before falling back on native.cc_binary().\n    # This field is completely optional, the embedder can omit the whole\n    # |rule_overrides| or individual keys. They are assigned to None or noop\n    # actions here just for documentation purposes.\n    # TODO: (cleanup): Drop all of these again once they are properly imported in perfetto.\n    rule_overrides = struct(\n        proto_library = proto_library,\n        cc_binary = cc_binary,\n        cc_library = cc_library,\n        cc_proto_library = cc_proto_library,\n\n        # Supporting java rules pulls in the JDK and generally is not something\n        # we need for most embedders.\n        java_proto_library = _noop_override,\n        java_lite_proto_library = _noop_override,\n        py_binary = py_binary,\n        py_library = py_library,\n        py_proto_library = None,\n        go_proto_library = None,\n        jspb_proto_library = None,\n    ),\n\n    # The default copts used to compile perfetto. The cfg example sets the C++ standard here, but\n    # for now we shouldn't need any perfetto-specific compiler options, should already be set up\n    # properly through .bazelrc.\n    default_copts = [],\n    default_cxxopts = [],\n)\n"
  },
  {
    "path": "build/python/packages_20240829_4.bzl",
    "content": "# This file is automatically generated by the Pyodide build script repo\n# (https://github.com/cloudflare/pyodide-build-scripts) and should not be manually modified.\n\nPACKAGES_20240829_4 = {\n    \"info\": {\n        \"tag\": \"20240829.4\",\n        \"lockfile_hash\": \"c2d9c67ea55a672b95a3beb8d66bfbe7df736edb4bb657383b263151e7e85ef4\",\n        \"all_wheels_hash\": \"94653dc8cfbea62b8013db3b8584bc02544ad6fc647b0d83bdee5dfcda5d4b62\",\n    },\n    \"import_tests\": {\n        \"aiohttp\": [\n            \"aiohttp\",\n        ],\n        \"aiosignal\": [\n            \"aiosignal\",\n        ],\n        \"annotated-types\": [\n            \"annotated_types\",\n        ],\n        \"anyio\": [\n            \"anyio\",\n        ],\n        \"async-timeout\": [\n            \"async_timeout\",\n        ],\n        \"attrs\": [\n            \"attr\",\n            \"attrs\",\n        ],\n        \"certifi\": [\n            \"certifi\",\n        ],\n        \"charset-normalizer\": [\n            \"charset_normalizer\",\n        ],\n        \"distro\": [\n            \"distro\",\n        ],\n        \"fastapi\": [\n            \"fastapi\",\n        ],\n        \"frozenlist\": [\n            \"frozenlist\",\n        ],\n        \"h11\": [\n            \"h11\",\n        ],\n        \"hashlib\": [\n            \"_hashlib\",\n        ],\n        \"httpcore\": [\n            \"httpcore\",\n        ],\n        \"httpx\": [\n            \"httpx\",\n        ],\n        \"idna\": [\n            \"idna\",\n        ],\n        \"jsonpatch\": [\n            \"jsonpatch\",\n        ],\n        \"jsonpointer\": [\n            \"jsonpointer\",\n        ],\n        \"langchain\": [\n            \"langchain\",\n        ],\n        \"langchain-core\": [\n            \"langchain_core\",\n            \"langchain_core.callbacks\",\n            \"langchain_core.language_models.llms\",\n            \"langchain_core.output_parsers\",\n            \"langchain_core.prompts\",\n        ],\n        \"langchain_openai\": [\n            \"langchain_openai\",\n            \"langchain_openai.chat_models.base\",\n        ],\n        \"langsmith\": [\n            \"langsmith\",\n            \"langsmith.client\",\n        ],\n        \"lzma\": [\n            \"_lzma\",\n            \"lzma\",\n        ],\n        \"micropip\": [\n            \"micropip\",\n        ],\n        \"multidict\": [\n            \"multidict\",\n        ],\n        \"numpy\": [\n            \"numpy\",\n        ],\n        \"openai\": [\n            \"openai\",\n        ],\n        \"packaging\": [\n            \"packaging\",\n        ],\n        \"pydantic\": [\n            \"pydantic\",\n        ],\n        \"pydantic_core\": [\n            \"pydantic_core\",\n        ],\n        \"pydecimal\": [\n            \"_pydecimal\",\n        ],\n        \"pydoc_data\": [\n            \"pydoc_data\",\n        ],\n        \"pyyaml\": [\n            \"_yaml\",\n            \"yaml\",\n        ],\n        \"regex\": [\n            \"regex\",\n        ],\n        \"requests\": [\n            \"requests\",\n        ],\n        \"six\": [\n            \"six\",\n        ],\n        \"sniffio\": [\n            \"sniffio\",\n        ],\n        \"sqlite3\": [\n            \"_sqlite3\",\n            \"sqlite3\",\n        ],\n        \"ssl\": [\n            \"_ssl\",\n            \"ssl\",\n        ],\n        \"starlette\": [\n            \"starlette\",\n            \"starlette.applications\",\n            \"starlette.authentication\",\n            \"starlette.background\",\n            \"starlette.concurrency\",\n            \"starlette.config\",\n            \"starlette.convertors\",\n            \"starlette.datastructures\",\n            \"starlette.endpoints\",\n            \"starlette.exceptions\",\n            \"starlette.formparsers\",\n            \"starlette.middleware\",\n            \"starlette.middleware.base\",\n            \"starlette.requests\",\n            \"starlette.responses\",\n            \"starlette.routing\",\n            \"starlette.schemas\",\n        ],\n        \"tenacity\": [\n            \"tenacity\",\n        ],\n        \"tiktoken\": [\n            \"tiktoken\",\n            \"tiktoken_ext\",\n        ],\n        \"typing-extensions\": [\n            \"typing_extensions\",\n        ],\n        \"urllib3\": [\n            \"urllib3\",\n        ],\n        \"yarl\": [\n            \"yarl\",\n        ],\n    },\n}\n"
  },
  {
    "path": "build/python/packages_20250808.bzl",
    "content": "# This file is automatically generated by the Pyodide build script repo\n# (https://github.com/cloudflare/pyodide-build-scripts) and should not be manually modified.\n\nPACKAGES_20250808 = {\n    \"import_tests\": {\n        \"Jinja2\": [\n            \"jinja2\",\n        ],\n        \"MarkupSafe\": [\n            \"markupsafe\",\n        ],\n        \"aiohappyeyeballs\": [\n            \"aiohappyeyeballs\",\n        ],\n        \"aiohttp\": [\n            \"aiohttp\",\n        ],\n        \"aiohttp-tests\": [\n            \"aiohttp\",\n        ],\n        \"aiosignal\": [\n            \"aiosignal\",\n        ],\n        \"annotated-types\": [\n            \"annotated_types\",\n        ],\n        \"annotated-types-tests\": [\n            \"annotated_types\",\n        ],\n        \"anyio\": [\n            \"anyio\",\n        ],\n        \"async-timeout\": [\n            \"async_timeout\",\n        ],\n        \"attrs\": [\n            \"attr\",\n            \"attrs\",\n        ],\n        \"beautifulsoup4\": [\n            \"bs4\",\n        ],\n        \"beautifulsoup4-tests\": [\n            \"bs4\",\n        ],\n        \"certifi\": [\n            \"certifi\",\n        ],\n        \"cffi\": [\n            \"cffi\",\n        ],\n        \"charset-normalizer\": [\n            \"charset_normalizer\",\n        ],\n        \"cryptography\": [\n            \"cryptography\",\n            \"cryptography.fernet\",\n            \"cryptography.hazmat\",\n            \"cryptography.utils\",\n            \"cryptography.x509\",\n        ],\n        \"distro\": [\n            \"distro\",\n        ],\n        \"fastapi\": [\n            \"fastapi\",\n        ],\n        \"frozenlist\": [\n            \"frozenlist\",\n        ],\n        \"h11\": [\n            \"h11\",\n        ],\n        \"h11-tests\": [\n            \"h11\",\n        ],\n        \"hashlib\": [\n            \"_hashlib\",\n        ],\n        \"httpcore\": [\n            \"httpcore\",\n        ],\n        \"httpx\": [\n            \"httpx\",\n        ],\n        \"idna\": [\n            \"idna\",\n        ],\n        \"jiter\": [\n            \"jiter\",\n        ],\n        \"jsonpatch\": [\n            \"jsonpatch\",\n        ],\n        \"jsonpointer\": [\n            \"jsonpointer\",\n        ],\n        \"langchain\": [\n            \"langchain\",\n        ],\n        \"langchain-community\": [\n            \"langchain_community\",\n            \"langchain_community.chat_message_histories\",\n            \"langchain_community.utilities\",\n        ],\n        \"langchain-core\": [\n            \"langchain_core\",\n            \"langchain_core.callbacks\",\n            \"langchain_core.language_models.llms\",\n            \"langchain_core.output_parsers\",\n            \"langchain_core.prompts\",\n        ],\n        \"langchain-text-splitters\": [\n            \"langchain_text_splitters\",\n        ],\n        \"langchain_openai\": [\n            \"langchain_openai\",\n            \"langchain_openai.chat_models.base\",\n        ],\n        \"langsmith\": [\n            \"langsmith\",\n            \"langsmith.client\",\n        ],\n        \"lzma\": [\n            \"_lzma\",\n            \"lzma\",\n        ],\n        \"micropip\": [\n            \"micropip\",\n        ],\n        \"multidict\": [\n            \"multidict\",\n        ],\n        \"numpy\": [\n            \"numpy\",\n        ],\n        \"openai\": [\n            \"openai\",\n        ],\n        \"packaging\": [\n            \"packaging\",\n        ],\n        \"propcache\": [\n            \"propcache\",\n        ],\n        \"pycparser\": [\n            \"pycparser\",\n        ],\n        \"pydantic\": [\n            \"pydantic\",\n            \"pydantic.alias_generators\",\n            \"pydantic.aliases\",\n            \"pydantic.annotated_handlers\",\n            \"pydantic.class_validators\",\n            \"pydantic.color\",\n            \"pydantic.config\",\n            \"pydantic.dataclasses\",\n            \"pydantic.datetime_parse\",\n            \"pydantic.decorator\",\n            \"pydantic.deprecated\",\n            \"pydantic.env_settings\",\n            \"pydantic.error_wrappers\",\n            \"pydantic.errors\",\n            \"pydantic.experimental\",\n            \"pydantic.fields\",\n            \"pydantic.functional_serializers\",\n            \"pydantic.functional_validators\",\n            \"pydantic.generics\",\n            \"pydantic.json\",\n            \"pydantic.json_schema\",\n            \"pydantic.main\",\n            \"pydantic.networks\",\n            \"pydantic.parse\",\n            \"pydantic.plugin\",\n            \"pydantic.root_model\",\n            \"pydantic.schema\",\n            \"pydantic.tools\",\n            \"pydantic.type_adapter\",\n            \"pydantic.types\",\n            \"pydantic.typing\",\n            \"pydantic.utils\",\n            \"pydantic.v1\",\n            \"pydantic.validate_call_decorator\",\n            \"pydantic.validators\",\n            \"pydantic.version\",\n            \"pydantic.warnings\",\n        ],\n        \"pydantic_core\": [\n            \"pydantic_core\",\n        ],\n        \"pydecimal\": [\n            \"_pydecimal\",\n        ],\n        \"pydoc_data\": [\n            \"pydoc_data\",\n        ],\n        \"pyparsing\": [\n            \"pyparsing\",\n        ],\n        \"pyyaml\": [\n            \"_yaml\",\n            \"yaml\",\n        ],\n        \"regex\": [\n            \"regex\",\n        ],\n        \"regex-tests\": [\n            \"regex\",\n        ],\n        \"requests\": [\n            \"requests\",\n        ],\n        \"requests-toolbelt\": [\n            \"requests_toolbelt\",\n        ],\n        \"setuptools\": [\n            \"_distutils_hack\",\n            \"pkg_resources\",\n            \"setuptools\",\n        ],\n        \"setuptools-tests\": [\n            \"_distutils_hack\",\n            \"pkg_resources\",\n            \"setuptools\",\n        ],\n        \"six\": [\n            \"six\",\n        ],\n        \"sniffio\": [\n            \"sniffio\",\n        ],\n        \"sniffio-tests\": [\n            \"sniffio\",\n        ],\n        \"soupsieve\": [\n            \"soupsieve\",\n        ],\n        \"sqlalchemy\": [\n            \"sqlalchemy\",\n        ],\n        \"sqlalchemy-tests\": [\n            \"sqlalchemy\",\n        ],\n        \"sqlite3\": [\n            \"_sqlite3\",\n            \"sqlite3\",\n        ],\n        \"ssl\": [\n            \"_ssl\",\n            \"ssl\",\n        ],\n        \"starlette\": [\n            \"starlette\",\n        ],\n        \"tblib\": [\n            \"tblib\",\n        ],\n        \"tenacity\": [\n            \"tenacity\",\n        ],\n        \"tiktoken\": [\n            \"tiktoken\",\n            \"tiktoken_ext\",\n        ],\n        \"typing-extensions\": [\n            \"typing_extensions\",\n        ],\n        \"urllib3\": [\n            \"urllib3\",\n            \"urllib3.contrib.emscripten\",\n        ],\n        \"yarl\": [\n            \"yarl\",\n        ],\n        \"zstandard\": [\n            \"zstandard\",\n        ],\n    },\n    \"info\": {\n        \"all_wheels_hash\": \"7228cf17e569e31238f74b00e4cb702f0b4fc1fa55e6a5144be461e75240048b\",\n        \"lockfile_hash\": \"315f5f3922d40253b3d9dae9ecea08110a9764c43fdfb240276d902769684dee\",\n        \"tag\": \"20250808\",\n    },\n}\n"
  },
  {
    "path": "build/python_metadata.bzl",
    "content": "# After updating this file, make sure to run \"bazel mod tidy\"\nload(\"@bazel_lib//lib:base64.bzl\", \"base64\")\nload(\"@bazel_lib//lib:strings.bzl\", \"chr\")\nload(\"//:build/python/packages_20240829_4.bzl\", \"PACKAGES_20240829_4\")\nload(\"//:build/python/packages_20250808.bzl\", \"PACKAGES_20250808\")\n\ndef _chunk(data, length):\n    return [data[i:i + length] for i in range(0, len(data), length)]\n\ndef hex_to_b64(hex):\n    s = \"\"\n    for chunk in _chunk(hex, 2):\n        s += chr(int(chunk, 16))\n    return \"sha256-\" + base64.encode(s)\n\nPYODIDE_VERSIONS = [\n    {\n        \"version\": \"0.26.0a2\",\n        \"sha256\": \"fbda450a64093a8d246c872bb901ee172a57fe594c9f35bba61f36807c73300d\",\n    },\n    {\n        \"version\": \"0.28.2\",\n        \"sha256\": \"c9f6dd067d119e50850849f7428e3c636ecbc2684a0d2ff992f3bd48a1062b6c\",\n    },\n]\n\n# This is the list of all the package metadata that we use.\n#\n# IMPORTANT: packages that are present here should never be removed after the package version is\n# released to the public. This is so that we don't break workers using those packages.\n#\n# ORDER MATTERS: the order of the keys in this dictionary matters, older package bundles should come\n# first.\n_package_lockfiles = [\n    PACKAGES_20240829_4,\n    PACKAGES_20250808,\n]\n\n# The below is a list of pyodide-lock.json files for each package bundle version that we support.\n# Each of these gets embedded in the workerd and EW binary.\nPYTHON_LOCKFILES = [meta[\"info\"] for meta in _package_lockfiles]\n\n# Used to generate the import tests, where we import each top level name from each package and check\n# that it doesn't fail.\nPYTHON_IMPORTS_TO_TEST = {meta[\"info\"][\"tag\"]: meta[\"import_tests\"] for meta in _package_lockfiles}\n\n# Each new package bundle should contain the same packages as the previous. We verify this\n# constraint here.\ndef verify_no_packages_were_removed():\n    for curr_info, next_info in zip(_package_lockfiles[:-1], _package_lockfiles[1:]):\n        curr_pkgs = curr_info[\"import_tests\"]\n        next_pkgs = next_info[\"import_tests\"]\n        missing_pkgs = [pkg for pkg in curr_pkgs if pkg not in next_pkgs]\n        if missing_pkgs:\n            fail(\"Some packages from version \", curr_info[\"info\"][\"tag\"], \" missing in version\", next_info[\"info\"][\"tag\"], \":\\n\", \"   \", \", \".join(missing_pkgs), \"\\n\\n\")\n\nverify_no_packages_were_removed()\n\ndef _bundle_id(*, pyodide_version, pyodide_date, backport, **_kwds):\n    return \"%s_%s_%s\" % (pyodide_version, pyodide_date, backport)\n\ndef _add_integrity(entry):\n    for key, value in entry.items():\n        if not key.endswith(\"_hash\"):\n            continue\n        newkey = key.removesuffix(\"_hash\") + \"_integrity\"\n        entry[newkey] = hex_to_b64(value)\n\ndef _make_vendored_packages(entry):\n    if entry[\"name\"] == \"development\":\n        return\n    vendor_tests = {}\n    for e in entry[\"vendored_packages_for_tests\"]:\n        vendor_tests[e[\"name\"]] = e\n    entry[\"vendored_packages_for_tests\"] = vendor_tests\n\ndef _check_pyodide_versions(version_info):\n    pyodide_versions = {ver[\"version\"]: 1 for ver in PYODIDE_VERSIONS}\n    pyodide_versions[\"dev\"] = 1\n    for entry in version_info.values():\n        if entry[\"pyodide_version\"] not in pyodide_versions:\n            fail(\"Version %s of Pyodide not in PYODIDE_VERSIONS\" % entry[\"pyodide_version\"])\n\ndef _make_bundle_version_info(versions):\n    result = {}\n    for entry in versions:\n        name = entry[\"name\"]\n        if name != \"development\":\n            entry[\"id\"] = _bundle_id(**entry)\n            entry[\"real_pyodide_version\"] = entry[\"pyodide_version\"]\n        entry[\"feature_flags\"] = [entry[\"flag\"]]\n        entry[\"feature_string_flags\"] = [entry[\"enable_flag_name\"]]\n        if \"packages\" in entry:\n            entry[\"packages\"] = entry[\"packages\"][\"info\"][\"tag\"]\n        _add_integrity(entry)\n        result[name] = entry\n        _make_vendored_packages(entry)\n\n    dev = result[\"development\"]\n\n    # Uncomment to test with development = 0.26.0a2\n    # dev[\"real_pyodide_version\"] = \"0.26.0a2\"\n    result[\"development\"] = result[dev[\"real_pyodide_version\"]] | dev\n    _check_pyodide_versions(result)\n    return result\n\nVENDORED_VERSION_INDEPENDENT = [\n    {\n        # Downloaded from https://pub-25a5b2f2f1b84655b185a505c7a3ad23.r2.dev/beautifulsoup4-vendored-for-ew-testing.zip\n        \"name\": \"beautifulsoup4\",\n        \"abi\": None,\n        \"sha256\": \"5aa09c5f549443969dda260a70e58e3ac8537bd3d29155b307a3d98b36eb70fd\",\n    },\n    {\n        \"name\": \"pytest-asyncio\",\n        \"abi\": None,\n        \"sha256\": \"be25b788392d124cbdfbb9b3d13541da69a7b6b977bc9474dddaddeaab6421b4\",\n    },\n    {\n        \"name\": \"python-workers-runtime-sdk\",\n        \"abi\": None,\n        \"sha256\": \"fc4fb50f73973c257277155b3cb113aa2cf68e9da8ef424ecb049b41bc463183\",\n    },\n]\n\nBUNDLE_VERSION_INFO = _make_bundle_version_info([\n    {\n        \"name\": \"0.26.0a2\",\n        \"released\": True,\n        \"pyodide_version\": \"0.26.0a2\",\n        \"pyodide_date\": \"2024-03-01\",\n        \"packages\": PACKAGES_20240829_4,\n        \"backport\": \"78\",\n        \"integrity\": \"sha256-V1iAhkevGuXaTVQo+85FbC8CINU6F7F+H5uwnmfBHTU=\",\n        \"flag\": \"pythonWorkers\",\n        \"enable_flag_name\": \"python_workers\",\n        \"emscripten_version\": \"3.1.52\",\n        \"python_version\": \"3.12.1\",\n        \"baseline_snapshot\": \"baseline-61eedf943.bin\",\n        \"baseline_snapshot_hash\": \"61eedf9432d635bdf091b26efece020b3543429a609fad7af9e8d4de2ec44f47\",\n        \"numpy_snapshot\": \"ew-py-package-snapshot_numpy-v2.bin\",\n        \"numpy_snapshot_hash\": \"5055deb53f404afacba73642fd10e766b123e661847e8fdf4f1ec92d8ca624dc\",\n        \"fastapi_snapshot\": \"ew-py-package-snapshot_fastapi-v2.bin\",\n        \"fastapi_snapshot_hash\": \"d204956a074cd74f7fe72e029e9a82686fcb8a138b509f765e664a03bfdd50fb\",\n        \"vendored_packages_for_tests\": VENDORED_VERSION_INDEPENDENT + [\n            {\n                # Downloaded from https://pub-25a5b2f2f1b84655b185a505c7a3ad23.r2.dev/fastapi-312-vendored-for-ew-testing.zip\n                \"name\": \"fastapi\",\n                \"abi\": \"3.12\",\n                \"sha256\": \"5e6e21dbeda7c1eaadb99e6e52aa2ce45325b51e9a417198701e68e0cfd12a4c\",\n            },\n            {\n                \"name\": \"scipy\",\n                \"abi\": \"3.12\",\n                \"sha256\": \"787e45be6969a5609093b3df9cc2dba2afec9e10bace977f5045697cc329aa7c\",\n            },\n        ],\n    },\n    {\n        \"name\": \"0.28.2\",\n        \"pyodide_version\": \"0.28.2\",\n        \"pyodide_date\": \"2025-01-16\",\n        \"packages\": PACKAGES_20250808,\n        \"backport\": \"9\",\n        \"integrity\": \"sha256-uauWhzzVDWY9UUDpMOCOYxVbSHS9oCnjFizu039PL6U=\",\n        \"flag\": \"pythonWorkers20250116\",\n        \"enable_flag_name\": \"python_workers_20250116\",\n        \"emscripten_version\": \"4.0.9\",\n        \"python_version\": \"3.13.2\",\n        \"baseline_snapshot\": \"baseline-4569679fb.bin\",\n        \"baseline_snapshot_hash\": \"4569679fb78a3c5c8dbfa73d57c61c6a5394617632fbac7b5873ba322c85463d\",\n        \"numpy_snapshot\": \"package_snapshot_numpy-60c9cb28e.bin\",\n        \"numpy_snapshot_hash\": \"60c9cb28e6dc1ea6ab38b25471ddaa315b667637c9dd6f94aceb2acc6519c623\",\n        \"fastapi_snapshot\": \"package_snapshot_fastapi-a6ccb56fe.bin\",\n        \"fastapi_snapshot_hash\": \"a6ccb56fe9eac265d139727d0134e8d6432c5fe25c8c0b8ec95252b13493b297\",\n        \"dedicated_fastapi_snapshot\": \"snapshot_a6b652a95810783f5078b9a5dbd4a07c30718acb4ff724e82c25db7353dd7f2d.bin\",\n        \"dedicated_fastapi_snapshot_hash\": \"4af6f012a5fb32f31a426e6f109e88ae85b18ee3dd131e1caaaad989cd962bbe\",\n        \"vendored_packages_for_tests\": VENDORED_VERSION_INDEPENDENT + [\n            {\n                \"name\": \"fastapi\",\n                \"abi\": \"3.13\",\n                \"sha256\": \"955091f1bd2eb33255ff2633df990bedc96e2f6294e78f2b416078777394f942\",\n            },\n            # {\n            #     \"name\": \"scipy\",\n            #     \"abi\": \"3.13\",\n            #     \"sha256\": \"4f1b6fc179bd5c6d3de68abc4aa9fca2aaecd09c5c8d357c2ecfedce7d621f3d\",\n            # },\n            {\n                \"name\": \"shapely\",\n                \"abi\": \"3.13\",\n                \"sha256\": \"2e5c462cb32ee8697b3647dfc9d5c88dcdfd0702da34a2d7dc6b07b8090dd321\",\n            },\n        ],\n    },\n    {\n        \"real_pyodide_version\": \"0.28.2\",\n        \"name\": \"development\",\n        \"pyodide_version\": \"dev\",\n        \"pyodide_date\": \"dev\",\n        \"id\": \"dev\",\n        \"flag\": \"pythonWorkersDevPyodide\",\n        \"enable_flag_name\": \"python_workers_development\",\n    },\n])\n"
  },
  {
    "path": "build/run_binary_target.bzl",
    "content": "# Workaround for bazel not supporting a shared exec and target configuration, even when they are\n# identical. https://github.com/bazelbuild/bazel/issues/14848\n# Derived from the tensorflow project (https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/tools/api/generator/api_gen.bzl)\n# See https://github.com/tensorflow/tensorflow/issues/60167 for discussion\n\ndef _run_binary_target_impl(ctx):\n    tool = ctx.attr.tool[DefaultInfo].files_to_run.executable\n    flags = [ctx.expand_location(a) if \"$(location\" in a else a for a in ctx.attr.args]\n\n    cmd = \" \".join([tool.path] + flags)\n    ctx.actions.run_shell(\n        inputs = ctx.files.srcs,\n        outputs = ctx.outputs.outs,\n        tools = [tool],\n        use_default_shell_env = True,\n        command = cmd,\n    )\n\nrun_binary_target = rule(\n    implementation = _run_binary_target_impl,\n    attrs = {\n        \"outs\": attr.output_list(mandatory = True),\n        \"srcs\": attr.label_list(allow_files = True),\n        \"args\": attr.string_list(),\n        # Setting the configuration to \"target\" to avoid compiling code used in both this\n        # generator-like target and regular targets twice. For cross-compilation this would need to\n        # be set to \"exec\".\n        # Unfortunately bazel makes it very difficult to set the configuration at build time as\n        # macros are resolved before select() can be resolved based on the command line. This could\n        # alternatively be done by defining build targets and the rules used to declare them twice\n        # (once for exec and for target).\n        \"tool\": attr.label(\n            executable = True,\n            cfg = \"target\",\n            mandatory = True,\n        ),\n    },\n)\n"
  },
  {
    "path": "build/rust_lint.bazelrc",
    "content": "## rust lint configuration\n\n# turn rust warnings into errors\nbuild:lint --@rules_rust//:extra_rustc_flag=-Dwarnings\n\n\n# enable clippy checks during build\nbuild:rust-enable-clippy-checks --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect\nbuild:rust-enable-clippy-checks --output_groups=+clippy_checks\n\n# configure clippy\n\n# rationale for disabling checks:\n# - clippy::missing_const_for_fn - too annoying in practice\n# - clippy::option_if_let_else - arguably if else is easier to read\n# - clippy::trait_duplication_in_bounds - ffi trips this\n# - clippy::non_send_fields_in_send_ty - ffi trips this\n# - clippy::cognitive_complexity - even tests trigger this\n# - clippy::future_not_send - todo\n# - clippy::trivial_regex - there's nothing wrong with having trivial regex\n# - clippy::literal_string_with_formatting_args – somehow triggers on\n#   external/rules_rust+/rust/settings/.clippy.toml\n# - clippy::non_std_lazy_statics – triggers on lazy_static, non-trivial to replace\n# - clippy::format_push_string – avoids single memory allocation, but makes code less readable\n# - clippy::cast_possible_truncation - usize/u64 conversion warning is unbelievably noisy\nbuild:rust-enable-clippy-checks --@rules_rust//:clippy_flags=-Wclippy::pedantic,-Wclippy::redundant_clone,-Wclippy::str_to_string,-Wclippy::to_string_in_format_args,-Wclippy::unnecessary_to_owned,-Wclippy::implicit_clone,-Wclippy::suspicious_to_owned,-Wclippy::unnecessary_to_owned,-Wclippy::nursery,-Wclippy::dbg_macro,-Wclippy::unwrap_used,-Wclippy::allow_attributes,-Wclippy::undocumented_unsafe_blocks,-Aclippy::missing_const_for_fn,-Aclippy::cognitive_complexity,-Aclippy::trait_duplication_in_bounds,-Aclippy::non_send_fields_in_send_ty,-Aclippy::option_if_let_else,-Aclippy::missing_errors_doc,-Aclippy::must_use_candidate,-Aclippy::future_not_send,-Aclippy::trivial_regex,-Aclippy::literal_string_with_formatting_args,-Aclippy::non_std_lazy_statics,-Aclippy::format_push_string,-Aclippy::cast_possible_truncation,-Dwarnings\nbuild --@rules_rust//rust/settings:clippy.toml=//src/rust:clippy.toml\n\n# enable rustfmt checks\nbuild:rust-enable-rustfmt-checks --aspects=@rules_rust//rust:defs.bzl%rustfmt_aspect\nbuild:rust-enable-rustfmt-checks --output_groups=+rustfmt_checks\n# configure rustfmt\nbuild --@rules_rust//:rustfmt.toml=//src/rust:rustfmt.toml\n\n# enable clippy & rustfmt checks in lint configuration\nbuild:lint --config=rust-enable-clippy-checks --config=rust-enable-rustfmt-checks\n\n# convenience shortcuts\nbuild:clippy --config=rust-enable-clippy-checks\nbuild:rustfmt --config=rust-enable-rustfmt-checks\n"
  },
  {
    "path": "build/tools/clang_tidy/BUILD",
    "content": "exports_files([\"clang_tidy_wrapper.sh\"])\n"
  },
  {
    "path": "build/tools/clang_tidy/clang_tidy.bazelrc",
    "content": "# enable clang tidy checks with default configuration\nbuild:clang-tidy --aspects //build/tools/clang_tidy:clang_tidy.bzl%clang_tidy_aspect --output_groups=+clang_tidy_checks\n\n# enable clang tidy check with all issues reported as warnings\nbuild:clang-tidy-warnings --config=clang-tidy --aspects_parameters=clang_tidy_args=--warnings-as-errors=-*\n"
  },
  {
    "path": "build/tools/clang_tidy/clang_tidy.bzl",
    "content": "\"\"\"Clang tidy aspect.\n\nThe aspect, when enabled runs clang_tidy on every compiled c++ file.\n\"\"\"\n\nload(\"@rules_cc//cc:action_names.bzl\", \"ACTION_NAMES\")\nload(\"@rules_cc//cc:find_cc_toolchain.bzl\", \"find_cc_toolchain\")\nload(\"@rules_cc//cc/common:cc_common.bzl\", \"cc_common\")\nload(\"@rules_cc//cc/common:cc_info.bzl\", \"CcInfo\")\n\ndef _clang_tidy_aspect_impl(target, ctx):\n    # not a c++ target\n    if not CcInfo in target:\n        return []\n\n    cc_toolchain = find_cc_toolchain(ctx)\n    feature_configuration = cc_common.configure_features(\n        ctx = ctx,\n        cc_toolchain = cc_toolchain,\n    )\n    compile_variables = cc_common.create_compile_variables(\n        feature_configuration = feature_configuration,\n        cc_toolchain = cc_toolchain,\n        user_compile_flags = ctx.fragments.cpp.cxxopts + ctx.fragments.cpp.copts,\n    )\n    toolchain_flags = cc_common.get_memory_inefficient_command_line(\n        feature_configuration = feature_configuration,\n        action_name = ACTION_NAMES.cpp_compile,\n        variables = compile_variables,\n    )\n\n    compilation_context = target[CcInfo].compilation_context\n\n    rule_copts = getattr(ctx.rule.attr, \"copts\", [])\n\n    # we use $location in our copts, expand it\n    rule_copts = [ctx.expand_location(opt) for opt in rule_copts]\n\n    srcs = []\n    if hasattr(ctx.rule.attr, \"srcs\"):\n        for src in ctx.rule.attr.srcs:\n            srcs += [\n                src\n                for src in src.files.to_list()\n                if src.is_source and src.short_path.endswith((\".c++\", \".c\", \".h\"))\n            ]\n    if hasattr(ctx.rule.attr, \"hdrs\"):\n        for src in ctx.rule.attr.hdrs:\n            srcs += [\n                src\n                for src in src.files.to_list()\n                if src.is_source and src.short_path.endswith((\".c++\", \".c\", \".h\"))\n            ]\n\n    defines = compilation_context.defines.to_list()\n    local_defines = compilation_context.local_defines.to_list()\n    includes = compilation_context.includes.to_list()\n    quote_includes = compilation_context.quote_includes.to_list()\n    system_includes = compilation_context.system_includes.to_list()\n    external_includes = compilation_context.external_includes.to_list()\n    headers = compilation_context.headers\n\n    # disable clang tidy if no-clang-tidy tag is defined.\n    # todo: figure out a better way to control clang tidy on a per-target basis.\n    if \"no-clang-tidy\" in ctx.rule.attr.tags:\n        return []\n\n    # bazel doesn't expose implementation deps through compilation context\n    # https://github.com/bazelbuild/bazel/issues/19663\n    if hasattr(ctx.rule.attr, \"implementation_deps\"):\n        deps = [dep[CcInfo].compilation_context for dep in ctx.rule.attr.implementation_deps if CcInfo in dep]\n        defines = depset(\n            defines,\n            transitive = [dep.defines for dep in deps],\n        )\n        includes = depset(\n            includes,\n            transitive = [dep.includes for dep in deps],\n        )\n        system_includes = depset(\n            system_includes,\n            transitive = [dep.system_includes for dep in deps],\n        )\n        quote_includes = depset(\n            quote_includes,\n            transitive = [dep.quote_includes for dep in deps],\n        )\n        external_includes = depset(\n            external_includes,\n            transitive = [dep.external_includes for dep in deps],\n        )\n        headers = depset(\n            headers.to_list(),\n            transitive = [dep.headers for dep in deps],\n        )\n\n    tools = [\n        ctx.attr._clang_tidy_executable.files,\n        ctx.attr._clang_tidy_wrapper.files,\n        ctx.attr._clang_tidy_config.files,\n    ]\n\n    outs = []\n    for src in srcs:\n        # run actions need to produce something, declare a dummy file\n        # multiple labels can use the same path, so disambiguate.\n        out = ctx.actions.declare_file(src.path + \".\" + ctx.label.name + \".clang_tidy\")\n        outs.append(out)\n\n        args = ctx.actions.args()\n\n        # these are consumed by clang_tidy_wrapper,sh\n        args.add(ctx.attr._clang_tidy_executable.files_to_run.executable)\n        args.add(out)\n\n        # clang-tidy arguments\n        # do not print statistics\n        args.add(\"--quiet\")\n        args.add(\"--config-file=\" + ctx.attr._clang_tidy_config.files.to_list()[0].short_path)\n\n        if ctx.attr.clang_tidy_args:\n            args.add_all(ctx.attr.clang_tidy_args.split(\" \"))\n\n        args.add(src.path)\n\n        # compiler arguments\n        args.add(\"--\")\n\n        args.add(\"-xc++\")\n\n        args.add_all(ctx.attr._clang_tidy_compiler_flags)\n        args.add_all(rule_copts)\n        args.add_all(defines, before_each = \"-D\")\n        args.add_all(local_defines, before_each = \"-D\")\n        args.add_all(includes, before_each = \"-I\")\n        args.add_all(quote_includes, before_each = \"-iquote\")\n        args.add_all(system_includes, before_each = \"-isystem\")\n        args.add_all(external_includes, before_each = \"-isystem\")\n\n        args.add_all(toolchain_flags)\n\n        # Silence warnings about unused functions or #pragma once being present in header files. For\n        # source files, we already cover these warnings in regular compilation\n        args.add(\"-Wno-pragma-once-outside-header\")\n        args.add(\"-Wno-unused\")\n\n        # TODO(cleanup): These paths provide required includes, but if the toolchain was working\n        # properly we wouldn't need them in the first place...\n        # Linux includes\n        args.add(\"-isystem/usr/lib/llvm-19/include/c++/v1\")\n        args.add(\"-isystem/usr/lib/llvm-19/lib/clang/19/include\")\n        args.add(\"-isystem/usr/include\")\n        args.add(\"-isystem/usr/include/x86_64-linux-gnu\")\n\n        # macOS includes\n        args.add(\"-isystem/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1\")\n        args.add(\"-isystem/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/17/include\")\n        args.add(\"-isystem/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include\")\n\n        inputs = depset(\n            direct = [src],\n            transitive = [headers],\n        )\n\n        ctx.actions.run(\n            outputs = [out],\n            arguments = [args],\n            executable = ctx.attr._clang_tidy_wrapper.files_to_run.executable,\n            progress_message = \"Run clang-tidy on {}\".format(src.short_path),\n            tools = tools,\n            mnemonic = \"ClangTidy\",\n            inputs = inputs,\n        )\n\n    return [\n        OutputGroupInfo(clang_tidy_checks = depset(direct = outs)),\n    ]\n\nclang_tidy_aspect = aspect(\n    implementation = _clang_tidy_aspect_impl,\n    fragments = [\"cpp\"],\n    attrs = {\n        \"_clang_tidy_wrapper\": attr.label(\n            default = Label(\"@//build/tools/clang_tidy:clang_tidy_wrapper.sh\"),\n            allow_single_file = True,\n        ),\n        \"_clang_tidy_executable\": attr.label(\n            default = Label(\"//tools:clang-tidy\"),\n            allow_single_file = True,\n        ),\n        \"_clang_tidy_config\": attr.label(\n            default = Label(\"//:clang_tidy_config\"),\n            allow_single_file = True,\n        ),\n        \"_clang_tidy_compiler_flags\": attr.string_list(\n            default = [],\n        ),\n        \"clang_tidy_args\": attr.string(default = \"\"),\n    },\n    toolchains = [\"@bazel_tools//tools/cpp:toolchain_type\"],\n)\n"
  },
  {
    "path": "build/tools/clang_tidy/clang_tidy_wrapper.sh",
    "content": "#! /bin/bash\n# simple wrapper script to execute clang-tidy\n\nset -euo pipefail\n\nCLANG_TIDY_BIN=$1\nshift\n\nOUTPUT=$1\nshift\n\nPWD=$(pwd)/\nESCAPED_PWD=$(sed 's/[\\*\\.&/]/\\\\&/g' <<< \"$PWD\")\n\n# Interestingly clang-tidy prints real errors to stdout, but system message like\n# `4 warnings generated` when they are filtered out, to stderr.\n# Save stderr and print only on errors to reduce the clutter.\nCLANG_TIDY_STDERR=$(mktemp)\n\nset +e\n\"${CLANG_TIDY_BIN}\" \"$@\" 2>\"$CLANG_TIDY_STDERR\" | \\\n  # clang-tidy insists on printing absolute file paths, chop current dir off\n  sed \"s/$ESCAPED_PWD//g\"\nCLANG_TIDY_EXIT_CODE=$?\nset -e\n\nif [[ $CLANG_TIDY_EXIT_CODE -ne 0 ]]; then\n  cat \"$CLANG_TIDY_STDERR\" >&2\n  rm -f \"$CLANG_TIDY_STDERR\"\n  exit $CLANG_TIDY_EXIT_CODE\nfi\n\nrm -f \"$CLANG_TIDY_STDERR\"\n\n# bazel needs run action to produce some output, touch the file\ntouch \"$OUTPUT\"\n"
  },
  {
    "path": "build/typescript.bzl",
    "content": "def module_name(ts_name):\n    if ts_name.endswith(\".ts\"):\n        return ts_name.removesuffix(\".ts\")\n    if ts_name.endswith(\".mts\"):\n        return ts_name.removesuffix(\".mts\")\n    fail(\"Expected TypeScript source file, got \" + ts_name)\n\ndef js_name(ts_name):\n    if ts_name.endswith(\".ts\"):\n        return ts_name.removesuffix(\".ts\") + \".js\"\n    if ts_name.endswith(\".mts\"):\n        return ts_name.removesuffix(\".mts\") + \".mjs\"\n    fail(\"Expected TypeScript source file, got \" + ts_name)\n"
  },
  {
    "path": "build/wasm_tools_parse.bzl",
    "content": "load(\"@bazel_skylib//lib:paths.bzl\", \"paths\")\n\ndef _impl(ctx):\n    in_file = ctx.file.src\n    out_file = ctx.actions.declare_file(paths.replace_extension(in_file.basename, \".wasm\"))\n    ctx.actions.run_shell(\n        tools = [ctx.executable._wasm_tools],\n        inputs = [in_file],\n        outputs = [out_file],\n        arguments = [\n            ctx.executable._wasm_tools.path,\n            in_file.path,\n            out_file.path,\n        ],\n        progress_message = \"Running wasm-tools parse on %s\" % in_file.short_path,\n        command = \"$1 parse $2 -o $3\",\n    )\n\n    return [DefaultInfo(files = depset([out_file]))]\n\nwasm_tools_parse = rule(\n    implementation = _impl,\n    attrs = {\n        \"src\": attr.label(mandatory = True, allow_single_file = True),\n        \"_wasm_tools\": attr.label(\n            executable = True,\n            allow_files = True,\n            cfg = \"exec\",\n            default = Label(\"//tools:wasm-tools\"),\n        ),\n    },\n)\n"
  },
  {
    "path": "build/wd_capnp_library.bzl",
    "content": "load(\"//:build/wd_cc_capnp_library.bzl\", \"wd_cc_capnp_library\")\nload(\"//:build/wd_rust_capnp_library.bzl\", \"wd_rust_capnp_library\")\n\ndef wd_capnp_library(\n        src,\n        deps = [],\n        tags = [],\n        visibility = [\"//visibility:public\"]):\n    \"\"\"Generates capnp library for multiple languages.\n\n    For a given file-name.capnp it will produce:\n    - `file-name_capnp` c++ library\n    - `file-name_capnp_rust` rust library\n    \"\"\"\n    base_name = src.removesuffix(\".capnp\")\n    target_compatible_with = select({\n        \"@//build/config:no_build\": [\"@platforms//:incompatible\"],\n        \"//conditions:default\": [],\n    })\n\n    # json.capnp is available implicitly for c++ targets\n    cc_deps = [dep for dep in deps if dep != \"@capnp-cpp//src/capnp/compat:json_capnp\"]\n\n    wd_cc_capnp_library(\n        name = base_name + \"_capnp\",\n        visibility = visibility,\n        deps = cc_deps,\n        srcs = [src],\n        tags = [\"manual\"] + tags,\n        target_compatible_with = target_compatible_with,\n    )\n\n    rust_deps = [dep + \"_rust\" if dep.endswith(\"_capnp\") else dep for dep in deps]\n\n    wd_rust_capnp_library(\n        name = base_name + \"_capnp_rust\",\n        crate_name = base_name.replace(\"-\", \"_\") + \"_capnp\",\n        visibility = visibility,\n        deps = rust_deps,\n        srcs = [src],\n        tags = [\"manual\"] + tags,\n        target_compatible_with = target_compatible_with,\n    )\n"
  },
  {
    "path": "build/wd_cc_benchmark.bzl",
    "content": "\"\"\"wd_cc_benchmark definition\"\"\"\n\nload(\"@rules_cc//cc:cc_test.bzl\", \"cc_test\")\n\ndef wd_cc_benchmark(\n        name,\n        linkopts = [],\n        deps = [],\n        tags = [],\n        visibility = None,\n        **kwargs):\n    \"\"\"Wrapper for cc_binary that sets common attributes and links the benchmark library.\n    \"\"\"\n    cc_test(\n        name = name,\n        defines = [\"WD_IS_BENCHMARK\"],\n        # Use shared linkage for benchmarks, matching the approach used for tests. Unfortunately,\n        # bazel does not support shared linkage on macOS and it is broken on Windows, so only\n        # enable this on Linux.\n        linkstatic = select({\n            \"@platforms//os:linux\": 0,\n            \"//conditions:default\": 1,\n        }),\n        linkopts = linkopts + select({\n            \"@//:use_dead_strip\": [\"-Wl,-dead_strip\", \"-Wl,-no_exported_symbols\"],\n            \"//conditions:default\": [\"\"],\n        }),\n        visibility = visibility,\n        deps = deps + [\n            \"@google_benchmark//:benchmark_main\",\n            \"//src/workerd/tests:bench-tools\",\n        ],\n        # use the same malloc we use for server\n        malloc = \"//src/workerd/server:malloc\",\n        tags = [\"workerd-benchmark\", \"google_benchmark\"] + tags,\n        size = \"large\",\n        **kwargs\n    )\n\n    # generate benchmark report\n    native.genrule(\n        name = name + \"@benchmark.csv\",\n        outs = [name + \".benchmark.csv\"],\n        srcs = [name],\n        cmd = \"./$(location {}) --benchmark_format=csv > \\\"$@\\\"\".format(name),\n        tags = [\"off-by-default\", \"benchmark_report\", \"workerd-benchmark\"],\n    )\n"
  },
  {
    "path": "build/wd_cc_binary.bzl",
    "content": "\"\"\"wd_cc_binary definition\"\"\"\n\nload(\"@rules_cc//cc:cc_binary.bzl\", \"cc_binary\")\n\ndef wd_cc_binary(\n        name,\n        linkopts = [],\n        visibility = None,\n        deps = [],\n        target_compatible_with = [],\n        **kwargs):\n    \"\"\"Wrapper for cc_binary that sets common attributes\n    \"\"\"\n    cc_binary(\n        name = name,\n        # -dead_strip is the macOS equivalent of -ffunction-sections, -Wl,--gc-sections.\n        # -no_exported_symbols is used to not include the exports trie, which significantly reduces\n        # binary sizes. Unfortunately, the flag and the exports trie are poorly documented. Based\n        # on analyzing the binary sections with and without the flag, the information being removed\n        # consists of weak binding info, export binding info and stub bindings as described in\n        # http://www.newosxbook.com/articles/DYLD.html. The flag itself is described in\n        # https://www.wwdcnotes.com/notes/wwdc22/110362/.\n        # The affected sections appear to not be needed for debugging and are only used when\n        # external code needs to look up bindings in a binary, e.g. when loading a plugin\n        # (mac-specific feature). In particular, the symbol table is not affected (the name of the\n        # flag is misleading here).\n        linkopts = linkopts + select({\n            \"@//:use_dead_strip\": [\"-Wl,-dead_strip\", \"-Wl,-no_exported_symbols\"],\n            \"//conditions:default\": [\"\"],\n        }),\n        target_compatible_with = select({\n            \"@//build/config:no_build\": [\"@platforms//:incompatible\"],\n            \"//conditions:default\": [],\n        }) + target_compatible_with,\n        visibility = visibility,\n        deps = deps,\n        **kwargs\n    )\n\n    pkg = native.package_name().removeprefix(\"src/\")\n    cross_alias = name + \"_cross\"\n    prebuilt_binary_name = name.removesuffix(\"_bin\")\n    native.alias(\n        name = cross_alias,\n        visibility = visibility,\n        actual = select({\n            \"@//build/config:prebuilt_binaries_arm64\": \"@//:bin.arm64/tmp/{}/{}.aarch64-linux-gnu\".format(pkg, prebuilt_binary_name),\n            \"//conditions:default\": name,\n        }),\n        testonly = kwargs.get(\"testonly\", False),\n    )\n"
  },
  {
    "path": "build/wd_cc_capnp_library.bzl",
    "content": "\"\"\"wd_cc_capnp_library definition\"\"\"\n\nload(\"@capnp-cpp//src/capnp:cc_capnp_library.bzl\", \"cc_capnp_library\")\n\ndef wd_cc_capnp_library(target_compatible_with = None, **kwargs):\n    \"\"\"Wrapper for cc_capnp_library that sets common attributes\n    \"\"\"\n    if target_compatible_with == None:\n        target_compatible_with = select({\n            \"@//build/config:no_build\": [\"@platforms//:incompatible\"],\n            \"//conditions:default\": [],\n        })\n\n    cc_capnp_library(\n        target_compatible_with = target_compatible_with,\n        src_prefix = \"src\",\n        strip_include_prefix = \"/src\",\n        **kwargs\n    )\n"
  },
  {
    "path": "build/wd_cc_embed.bzl",
    "content": "load(\"@bazel_skylib//rules:write_file.bzl\", \"write_file\")\nload(\"//:build/wd_cc_library.bzl\", \"wd_cc_library\")\n\n# Normalizes an embed name constructed from the embed path.\ndef normalize_embed_name(file_name):\n    return file_name.replace(\".\", \"_\").replace(\"-\", \"_\").replace(\"/\", \"_\").upper()\n\n# Converts file name or label into string of the corresponding embed name.\ndef wd_cc_embed_name(src):\n    embed_filename = native.package_relative_label(src).name\n    return normalize_embed_name(embed_filename)\n\n# A high-performance embed mechanism built upon C23. Prefer this over capnp embeds unless the embed is needed within capnp definitions.\n# name: A valid C variable name.\n# src: The name of the file to be embedded.\n# Generates a ${name} library target and a ${base_name}.embed.h header for accessing the variable name as a const char array.\ndef wd_cc_embed(name, src, base_name = \"\", is_text = None, **kwargs):\n    embed_filename = native.package_relative_label(src).name\n    embed_package = native.package_relative_label(src).package\n    embed_name = normalize_embed_name(embed_filename)\n\n    # construct a usable workspace root, e.g. external/workerd/\n    workspace_root = native.package_relative_label(src).workspace_root\n    if (workspace_root != \"\"):\n        workspace_root += \"/\"\n\n    # Heuristically determine if file is intended to be used as text or binary.\n    if is_text == None:\n        is_text = embed_filename.endswith(\".txt\") or embed_filename.endswith(\".js\") or embed_filename.endswith(\".ts\")\n    pod_data_type = \"unsigned char\"\n    data_type = \"kj::ArrayPtr<const kj::byte>\"\n    suffix = \"\"\n    size_adjust = \"\"\n    if is_text:\n        pod_data_type = \"char\"\n        data_type = \"kj::StringPtr\"\n\n        # for text data, add terminating NUL byte, but don't count it to length so kj::StringPtr constructor works properly\n        suffix = \" suffix(, 0)\"\n        size_adjust = \" - 1\"\n\n    # Optionally construct output file names from embed file name\n    if (base_name == \"\"):\n        base_name = embed_filename.replace(\".\", \"_\").replace(\"-\", \"_\")\n    else:\n        embed_name = normalize_embed_name(base_name)\n\n    write_file(\n        name = embed_filename + \"@c\",\n        out = base_name + \".embed.c\",\n        content = [\"\"\"#include <stddef.h>\n// Some data (including ICU embed) requires 8-byte alignment\n__attribute__ ((aligned (8))) const {pod_data_type} {embed_name}_begin[] = {{\n  #embed <{embed}>{suffix}\n}};\nsize_t {embed_name}_size = sizeof({embed_name}_begin){size_adjust};\n\"\"\".format(embed_name = embed_name, embed = embed_filename, pod_data_type = pod_data_type, suffix = suffix, size_adjust = size_adjust)],\n    )\n    write_file(\n        name = embed_filename + \"@h\",\n        out = base_name + \".embed.h\",\n        content = [\"\"\"#pragma once\n#include <kj/common.h>\n#include <kj/string.h>\n#include <stddef.h>\n#ifdef __cplusplus\nextern \"C\" {{\n#endif\nextern const {pod_data_type} {embed_name}_begin[];\nextern size_t {embed_name}_size;\n#define {embed_name} ({data_type}({embed_name}_begin, {embed_name}_size))\n#ifdef __cplusplus\n}}\n#endif\"\"\".format(embed_name = embed_name, data_type = data_type, pod_data_type = pod_data_type)],\n    )\n\n    wd_cc_library(\n        name = name,\n        srcs = [base_name + \".embed.c\"],\n        hdrs = [base_name + \".embed.h\"],\n        additional_compiler_inputs = [src],\n        conlyopts = select({\n            \"@platforms//os:windows\": [\n                # Windows doesn't recognize C23 yet, and complains about C23 extensions even with clatest.\n                \"/std:clatest\",\n                \"-Wno-c23-extensions\",\n                \"-Wno-unused-command-line-argument\",\n                \"/clang:--embed-dir=\" + workspace_root + embed_package,\n                \"/clang:--embed-dir=$(GENDIR)/\" + workspace_root + embed_package,\n            ],\n            \"//conditions:default\": [\n                # no need to have debug info for the embed\n                \"-g0\",\n                # C23 is needed for N3017 #embed. Clang also supports it in C++26 mode but it is not\n                # officially part of that standard so far; no harm in using C on a per-file basis.\n                \"-std=c23\",\n                # Hack: We need to provide the path to the embed file directory here. We can access\n                # the embed file easily by adding it to additional_compiler_inputs but can't easily\n                # get its directory within a macro – provide both the path of the package and its\n                # output directory to support both pre-existing and generated files.\n                # To support using the macro with external repositories, we also add the file's\n                # workspace root.\n                \"--embed-dir=\" + workspace_root + embed_package,\n                \"--embed-dir=$(GENDIR)/\" + workspace_root + embed_package,\n            ],\n        }),\n        deps = [\n            \"@capnp-cpp//src/kj\",\n        ],\n        **kwargs\n    )\n"
  },
  {
    "path": "build/wd_cc_library.bzl",
    "content": "\"\"\"wd_cc_library definition\"\"\"\n\nload(\"@rules_cc//cc:cc_library.bzl\", \"cc_library\")\n\ndef wd_cc_library(strip_include_prefix = \"/src\", copts = [], **kwargs):\n    \"\"\"Wrapper for cc_library that sets common attributes\n    \"\"\"\n    cc_library(\n        target_compatible_with = select({\n            \"@//build/config:no_build\": [\"@platforms//:incompatible\"],\n            \"//conditions:default\": [],\n        }),\n        # global CPU-specific options are inconvenient to specify with bazel – just set the options we\n        # need for CRC32C used in in this target for now.\n        # TODO(cleanup): Come up with a better approach to specify these options for all C/C++\n        # targets - it appears to be impossible to set arch-specific copts without a custom\n        # toolchain or manually setting a config from the CLI interface (which we can do in CI, but\n        # can't easily do for local builds).\n        copts = copts + select({\n            \"@platforms//cpu:aarch64\": [\n                \"-mcrc\",\n            ],\n            \"@platforms//cpu:x86_64\": [\n                # Note that msse4.2 already includes CRC32C and SSSE3 extensions\n                \"-msse4.2\",\n            ],\n        }),\n        strip_include_prefix = strip_include_prefix,\n        **kwargs\n    )\n"
  },
  {
    "path": "build/wd_js_bundle.bzl",
    "content": "load(\"@bazel_skylib//rules:common_settings.bzl\", \"BuildSettingInfo\")\nload(\"@bazel_skylib//rules:copy_file.bzl\", \"copy_file\")\nload(\"@capnp-cpp//src/capnp:cc_capnp_library.bzl\", \"cc_capnp_library\")\n\nCAPNP_TEMPLATE = \"\"\"@{schema_id};\n\n# generated by @workerd//build/wd_js_bundle.bzl\n\nusing Modules = import \"/workerd/jsg/modules.capnp\";\n\nconst {const_name} :Modules.Bundle = (\n  modules = [\n{modules}\n]);\n\"\"\"\n\ndef _to_name(file_name):\n    return file_name.removesuffix(\".js\")\n\ndef _to_d_ts(file_name):\n    return file_name.removesuffix(\".js\") + \".d.ts\"\n\ndef _relative_path(file_path, dir_path):\n    if not file_path.startswith(dir_path):\n        fail(\"file_path need to start with dir_path: \" + file_path + \" vs \" + dir_path)\n    return file_path.removeprefix(dir_path)\n\ndef _gen_compile_cache_impl(ctx):\n    file_list = ctx.actions.declare_file(\"in\")\n\n    srcs = []\n    for src in ctx.attr.srcs:\n        srcs.extend(src.files.to_list())\n\n    outs = [ctx.actions.declare_file(src.basename + \"_cache\") for src in srcs]\n\n    content = []\n    for i in range(0, len(srcs)):\n        content.append(\"{} {}\".format(srcs[i].path, outs[i].path))\n\n    ctx.actions.write(\n        output = file_list,\n        content = \"\\n\".join(content) + \"\\n\",\n    )\n\n    args = ctx.actions.args()\n    args.add(file_list)\n\n    run_under = ctx.attr._run_under[BuildSettingInfo].value\n\n    # use run_shell together with cfg = target instead of ctx.actions.run\n    # to prevent double-compilation of v8.\n    ctx.actions.run_shell(\n        outputs = outs,\n        inputs = [file_list] + srcs,\n        command = run_under + \" \" + ctx.executable._tool.path + \" $@\",\n        arguments = [args],\n        use_default_shell_env = True,\n        tools = [ctx.executable._tool],\n    )\n\n    return [\n        DefaultInfo(files = depset(direct = outs)),\n    ]\n\n_gen_compile_cache = rule(\n    implementation = _gen_compile_cache_impl,\n    attrs = {\n        \"srcs\": attr.label_list(mandatory = True, allow_files = True),\n        \"_tool\": attr.label(\n            executable = True,\n            allow_single_file = True,\n            cfg = \"target\",\n            default = \"//src/rust/gen-compile-cache\",\n        ),\n        \"_run_under\": attr.label(default = \"//build/config:target_run_under\"),\n    },\n)\n\ndef _get_compile_cache(compile_cache, m):\n    if not compile_cache:\n        return None\n    files = m.files.to_list()\n\n    if len(files) != 1:\n        fail(\"only single file expected\")\n\n    return compile_cache.get(files[0].path)\n\nMODULE_TEMPLATE = \"\"\"    (name = \"{name}\", {src_type} = embed \"{path}\", type = {type}, {extras})\"\"\"\n\ndef _gen_api_bundle_capnpn_impl(ctx):\n    output_dir = ctx.outputs.out.dirname + \"/\"\n\n    def _render_module(name, label, src_type, type, cache = None):\n        ts_declaration_extra = (\n            \"tsDeclaration = embed \\\"\" + _relative_path(\n                ctx.expand_location(\"$(location {})\".format(ctx.attr.declarations[name]), ctx.attr.data),\n                output_dir,\n            ) + \"\\\", \"\n        ) if name in ctx.attr.declarations else \"\"\n        cache_extra = (\n            \"compileCache = embed \\\"{}\\\", \".format(_relative_path(cache, output_dir))\n        ) if cache else \"\"\n        extras = ts_declaration_extra + cache_extra\n\n        return MODULE_TEMPLATE.format(\n            name = name,\n            # capnp doesn't allow \"..\" dir escape, make paths relative.\n            # this won't work for embedding paths outside of rule directory subtree.\n            src_type = src_type,\n            path = _relative_path(\n                ctx.expand_location(\"$(location {})\".format(label), ctx.attr.data),\n                output_dir,\n            ),\n            type = type,\n            extras = extras,\n        )\n\n    compile_cache = {}\n    if ctx.attr.compile_cache:\n        locations = ctx.expand_location(\"$(locations {})\".format(ctx.attr.compile_cache.label)).split(\" \")\n        for loc in locations:\n            compile_cache[loc.removesuffix(\"_cache\")] = loc\n\n    modules = [\n        _render_module(ctx.attr.builtin_modules[m], m.label, \"src\", \"builtin\", _get_compile_cache(compile_cache, m))\n        for m in ctx.attr.builtin_modules\n    ]\n    modules += [\n        _render_module(ctx.attr.internal_modules[m], m.label, \"src\", \"internal\", _get_compile_cache(compile_cache, m))\n        for m in ctx.attr.internal_modules\n    ]\n    modules += [\n        _render_module(ctx.attr.internal_wasm_modules[m], m.label, \"wasm\", \"internal\")\n        for m in ctx.attr.internal_wasm_modules\n    ]\n    modules += [\n        _render_module(ctx.attr.internal_data_modules[m], m.label, \"data\", \"internal\")\n        for m in ctx.attr.internal_data_modules\n    ]\n    modules += [\n        _render_module(ctx.attr.internal_json_modules[m], m.label, \"json\", \"internal\")\n        for m in ctx.attr.internal_json_modules\n    ]\n\n    content = CAPNP_TEMPLATE.format(\n        schema_id = ctx.attr.schema_id,\n        modules = \",\\n\".join(modules),\n        const_name = ctx.attr.const_name,\n    )\n    ctx.actions.write(ctx.outputs.out, content)\n\ngen_api_bundle_capnpn = rule(\n    implementation = _gen_api_bundle_capnpn_impl,\n    attrs = {\n        \"schema_id\": attr.string(mandatory = True),\n        \"out\": attr.output(mandatory = True),\n        \"builtin_modules\": attr.label_keyed_string_dict(allow_files = True),\n        \"internal_modules\": attr.label_keyed_string_dict(allow_files = True),\n        \"internal_wasm_modules\": attr.label_keyed_string_dict(allow_files = True),\n        \"internal_data_modules\": attr.label_keyed_string_dict(allow_files = True),\n        \"internal_json_modules\": attr.label_keyed_string_dict(allow_files = True),\n        \"declarations\": attr.string_dict(),\n        \"data\": attr.label_list(allow_files = True),\n        \"const_name\": attr.string(mandatory = True),\n        \"deps\": attr.label_list(),\n        \"compile_cache\": attr.label(),\n    },\n)\n\ndef _copy_modules(modules, declarations, out_dir = \"\"):\n    \"\"\"Copy files from the modules map to the current package.\n\n    Returns new module map using file copies.\n    This is necessary since capnp compiler doesn't allow embeds outside of current subidrectory.\n    \"\"\"\n    result = dict()\n    declarations_result = dict()\n    for m in modules:\n        new_filename = out_dir + modules[m].replace(\":\", \"_\").replace(\"/\", \"_\")\n        copy_file(name = new_filename + \"@copy\", src = m, out = new_filename)\n\n        m_d_ts = _to_d_ts(m)\n        if m_d_ts in declarations:\n            new_d_ts_filename = new_filename + \".d.ts\"\n            copy_file(name = new_d_ts_filename + \"@copy\", src = m_d_ts, out = new_d_ts_filename)\n            declarations_result[modules[m]] = str(native.package_relative_label(new_d_ts_filename))\n\n        result[new_filename] = modules[m]\n    return result, declarations_result\n\ndef wd_js_bundle(\n        name,\n        import_name,\n        schema_id,\n        builtin_modules,\n        internal_modules = [],\n        internal_wasm_modules = [],\n        internal_data_modules = [],\n        internal_json_modules = [],\n        declarations = [],\n        deps = [],\n        gen_compile_cache = False,\n        out_dir = \"\"):\n    \"\"\"Generate cc capnp library with js api bundle.\n\n    NOTE: Due to capnpc embed limitation all modules must be in the same or sub directory of the\n          actual rule usage.\n\n    Args:\n     name: cc_capnp_library rule name\n     import_name: The js import specifier. builtin modules are accessible from\n                  user code under `<import_name>:<module_name>`, internal modules\n                  are accessible from builtin modules under\n                  `<import_name>-internal:<module_name>`\n                  The capnproto bundle object generated will be called\n                  `import_name` + \"Bundle\"\n     schema_id: capnpn schema id\n     builtin_modules: list of js source files for builtin modules\n     internal_modules: list of js source files for internal modules\n     internal_wasm_modules: list of wasm source files\n     internal_data_modules: list of data source files\n     internal_json_modules: list of json source files\n     declarations: d.ts label set\n     deps: dependency list\n     gen_compile_cache: generate compilation cache of every file and include into the bundle\n    \"\"\"\n    builtin_modules_dict = {\n        m: \"{}:{}\".format(import_name, _to_name(m).removeprefix(out_dir))\n        for m in builtin_modules\n    }\n    internal_modules_dict = {\n        m: \"{}-internal:{}\".format(import_name, _to_name(m.removeprefix(out_dir).removeprefix(\"internal/\")))\n        for m in internal_modules\n    }\n    internal_wasm_modules_dict = {\n        m: \"{}-internal:{}\".format(import_name, m.removeprefix(out_dir).removeprefix(\"internal/\"))\n        for m in internal_wasm_modules\n    }\n    internal_data_modules_dict = {\n        m: \"{}-internal:{}\".format(import_name, m.removeprefix(out_dir).removeprefix(\"internal/\"))\n        for m in internal_data_modules\n    }\n    internal_json_modules_dict = {\n        m: \"{}-internal:{}\".format(import_name, m.removeprefix(out_dir).removeprefix(\"internal/\"))\n        for m in internal_json_modules\n    }\n\n    builtin_modules_dict, builtin_declarations = _copy_modules(\n        builtin_modules_dict,\n        declarations,\n        out_dir,\n    )\n    internal_modules_dict, internal_declarations = _copy_modules(\n        internal_modules_dict,\n        declarations,\n        out_dir,\n    )\n    internal_wasm_modules_dict, _ = _copy_modules(\n        internal_wasm_modules_dict,\n        declarations,\n        out_dir,\n    )\n    internal_data_modules_dict, _ = _copy_modules(\n        internal_data_modules_dict,\n        declarations,\n        out_dir,\n    )\n    internal_json_modules_dict, _ = _copy_modules(\n        internal_json_modules_dict,\n        declarations,\n        out_dir,\n    )\n\n    data = (\n        list(builtin_modules_dict) +\n        list(internal_modules_dict) +\n        list(internal_wasm_modules_dict) +\n        list(internal_data_modules_dict) +\n        list(internal_json_modules_dict) +\n        list(builtin_declarations.values()) +\n        list(internal_declarations.values())\n    )\n\n    compile_cache = None\n    if gen_compile_cache:\n        _gen_compile_cache(\n            name = name + \"@compile_cache\",\n            srcs = builtin_modules_dict.keys() + internal_modules_dict.keys(),\n        )\n        compile_cache = name + \"@compile_cache\"\n        deps = deps + [compile_cache]\n        data = data + [compile_cache]\n\n    gen_api_bundle_capnpn(\n        name = name + \"@gen\",\n        out = name + \".capnp\",\n        schema_id = schema_id,\n        const_name = import_name + \"Bundle\",\n        builtin_modules = builtin_modules_dict,\n        internal_modules = internal_modules_dict,\n        internal_wasm_modules = internal_wasm_modules_dict,\n        internal_data_modules = internal_data_modules_dict,\n        internal_json_modules = internal_json_modules_dict,\n        declarations = builtin_declarations | internal_declarations,\n        data = data,\n        deps = deps,\n        compile_cache = compile_cache,\n    )\n\n    cc_capnp_library(\n        name = name,\n        srcs = [name + \".capnp\"],\n        strip_include_prefix = \"\",\n        visibility = [\"//visibility:public\"],\n        data = data,\n        deps = [\"@workerd//src/workerd/jsg:modules_capnp\"],\n        include_prefix = import_name,\n    )\n"
  },
  {
    "path": "build/wd_rust_binary.bzl",
    "content": "load(\"@rules_rust//rust:defs.bzl\", \"rust_binary\", \"rust_test\")\nload(\"@workerd//:build/wd_rust_crate.bzl\", \"rust_cxx_bridge\")\n\ndef wd_rust_binary(\n        name,\n        deps = [],\n        proc_macro_deps = [],\n        data = [],\n        rustc_env = {},\n        visibility = None,\n        tags = [],\n        cxx_bridge_src = None,\n        cxx_bridge_deps = [],\n        test_size = \"small\"):\n    \"\"\"Define rust binary.\n\n    Args:\n        name: crate name.\n        deps: crate dependencies: rust crates or c/c++ libraries.\n        visibility: crate visibility.\n        data: additional data files.\n        proc_macro_deps: proc_macro dependencies.\n        rustc_env: additional rustc environment variables,\n        tags: rule tags\n    \"\"\"\n    srcs = native.glob([\"**/*.rs\"])\n    crate_name = name.replace(\"-\", \"_\")\n\n    if cxx_bridge_src:\n        hdrs = native.glob([\"**/*.h\"], allow_empty = True)\n\n        rust_cxx_bridge(\n            name = name + \"@cxx\",\n            src = cxx_bridge_src,\n            hdrs = hdrs,\n            include_prefix = \"workerd/rust/\" + name,\n            strip_include_prefix = \"\",\n            # Not applying visibility here – if you import the cxxbridge header, you will likely\n            # also need the rust library itself to avoid linker errors.\n            deps = cxx_bridge_deps + [\n                \"@workerd-cxx//:core\",\n            ],\n        )\n\n        deps.append(\"@workerd-cxx//:cxx\")\n        deps.append(name + \"@cxx\")\n\n    rust_binary(\n        name = name,\n        crate_name = crate_name,\n        srcs = srcs,\n        rustc_env = rustc_env,\n        deps = deps,\n        visibility = visibility,\n        data = data,\n        proc_macro_deps = proc_macro_deps,\n        target_compatible_with = select({\n            \"@//build/config:no_build\": [\"@platforms//:incompatible\"],\n            \"//conditions:default\": [],\n        }),\n    )\n\n    rust_test(\n        name = name + \"_test\",\n        crate = \":\" + name,\n        env = {\n            \"RUST_BACKTRACE\": \"1\",\n            # rust test runner captures stderr by default, which makes debugging tests very hard\n            \"RUST_TEST_NOCAPTURE\": \"1\",\n            # our tests are usually very heavy and do not support concurrent invocation\n            \"RUST_TEST_THREADS\": \"1\",\n        },\n        target_compatible_with = select({\n            \"@//build/config:no_build\": [\"@platforms//:incompatible\"],\n            \"//conditions:default\": [],\n        }),\n        size = test_size,\n    )\n"
  },
  {
    "path": "build/wd_rust_capnp_library.bzl",
    "content": "\"\"\"wd_rust_capnp_library definition\"\"\"\n\nload(\"@capnp-cpp//src/capnp:rust_capnp_library.bzl\", \"rust_capnp_library\")\n\ndef wd_rust_capnp_library(**kwargs):\n    \"\"\"Wrapper for rust_capnp_library that sets common attributes\n    \"\"\"\n    rust_capnp_library(\n        src_prefix = \"src\",\n        **kwargs\n    )\n"
  },
  {
    "path": "build/wd_rust_crate.bzl",
    "content": "load(\"@rules_cc//cc:cc_library.bzl\", \"cc_library\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\", \"rust_test\", \"rust_unpretty\")\n\ndef rust_cxx_bridge(\n        name,\n        src,\n        hdrs = [],\n        deps = [],\n        visibility = [],\n        strip_include_prefix = None,\n        include_prefix = None,\n        tags = [],\n        local_defines = [],\n        features = []):\n    native.genrule(\n        name = \"%s/generated\" % name,\n        srcs = [src],\n        outs = [\n            src + \".h\",\n            src + \".cc\",\n        ],\n        cmd = \"$(location @workerd-cxx//:codegen) $(location %s) -o $(location %s.h) -o $(location %s.cc)\" % (src, src, src),\n        tools = [\"@workerd-cxx//:codegen\"],\n        target_compatible_with = select({\n            \"@//build/config:no_build\": [\"@platforms//:incompatible\"],\n            \"//conditions:default\": [],\n        }),\n    )\n    cc_library(\n        name = name,\n        srcs = [src + \".cc\"],\n        hdrs = [src + \".h\"] + hdrs,\n        strip_include_prefix = strip_include_prefix,\n        include_prefix = include_prefix,\n        local_defines = local_defines,\n        features = features,\n        linkstatic = select({\n            \"@platforms//os:windows\": True,\n            \"//conditions:default\": False,\n        }),\n        deps = deps,\n        visibility = visibility,\n        tags = tags,\n        target_compatible_with = select({\n            \"@//build/config:no_build\": [\"@platforms//:incompatible\"],\n            \"//conditions:default\": [],\n        }),\n    )\n\ndef wd_rust_crate(\n        name,\n        cxx_bridge_src = None,\n        cxx_bridge_srcs = [],\n        deps = [],\n        proc_macro_deps = [],\n        data = [],\n        test_env = {},\n        test_tags = [],\n        test_deps = [],\n        test_proc_macro_deps = [],\n        cxx_bridge_deps = [],\n        cxx_bridge_tags = [],\n        cxx_bridge_local_defines = [],\n        cxx_bridge_features = [],\n        visibility = None):\n    \"\"\"Define rust crate.\n\n    Args:\n        name: crate name.\n        cxx_bridge_src: (optional) .rs source file with cxx ffi bridge definition. The rule will\n            generation additional<name>@cxx c++ library with cxx bindings if this is set.\n        deps: crate dependencies: rust crates or c/c++ libraries.\n        visibility: crate visibility.\n        data: additional data files.\n        proc_macro_deps: proc_macro dependencies.\n        rustc_env: rustc environment variables,\n        test_env: additional test environment variable.\n        test_tags: additional test tags.\n        test_deps: test-only dependencies.\n        test_proc_macro_deps: test-only proc_macro dependencies.\n    \"\"\"\n    srcs = native.glob([\"**/*.rs\"])\n    crate_name = name.replace(\"-\", \"_\")\n\n    if cxx_bridge_src:\n        cxx_bridge_srcs = cxx_bridge_srcs + [cxx_bridge_src]\n\n    # Add cxx dependency if there are any cxx bridges\n    if len(cxx_bridge_srcs) > 0:\n        cxx_bridge_deps = cxx_bridge_deps + [\n            \"@workerd-cxx//kj-rs\",\n            \"@workerd-cxx//:cxx\",\n        ]\n        deps = deps + [\n            \"@workerd-cxx//kj-rs\",\n            \"@workerd-cxx//:cxx\",\n        ]\n\n    include_prefix = \"workerd/\" + native.package_name().removeprefix(\"src/\")\n\n    hdrs = native.glob([\"**/*.h\"], allow_empty = True)\n    for bridge_src in cxx_bridge_srcs:\n        rust_cxx_bridge(\n            name = bridge_src + \"@cxx\",\n            src = bridge_src,\n            hdrs = hdrs,\n            include_prefix = include_prefix,\n            strip_include_prefix = \"\",\n            # Not applying visibility here – if you import the cxxbridge header, you will likely\n            # also need the rust library itself to avoid linker errors.\n            deps = cxx_bridge_deps,\n            tags = cxx_bridge_tags,\n            local_defines = cxx_bridge_local_defines,\n            features = cxx_bridge_features,\n        )\n\n    for bridge_src in cxx_bridge_srcs:\n        deps.append(bridge_src + \"@cxx\")\n\n    crate_features = []\n\n    rust_library(\n        name = name,\n        crate_name = crate_name,\n        srcs = srcs,\n        deps = deps + [\"@workerd//deps/rust:runtime\"],\n        visibility = visibility,\n        data = data,\n        proc_macro_deps = proc_macro_deps,\n        crate_features = crate_features,\n        target_compatible_with = select({\n            \"@//build/config:no_build\": [\"@platforms//:incompatible\"],\n            \"//conditions:default\": [],\n        }),\n    )\n\n    rust_test(\n        name = name + \"_test\",\n        crate = \":\" + name,\n        env = {\n            \"RUST_BACKTRACE\": \"1\",\n            # rust test runner captures stderr by default, which makes debugging tests very hard\n            \"RUST_TEST_NOCAPTURE\": \"1\",\n            # our tests are usually very heavy and do not support concurrent invocation\n            \"RUST_TEST_THREADS\": \"1\",\n        } | test_env,\n        tags = test_tags,\n        crate_features = crate_features,\n        deps = test_deps,\n        proc_macro_deps = test_proc_macro_deps,\n    )\n\n    if len(proc_macro_deps) + len(cxx_bridge_srcs) > 0:\n        rust_unpretty(\n            name = name + \"@expand\",\n            deps = [\":\" + name],\n            tags = [\"manual\", \"off-by-default\"],\n        )\n\n    if len(test_proc_macro_deps) > 0:\n        rust_unpretty(\n            name = name + \"_test@expand\",\n            deps = [\":\" + name + \"_test\"],\n            tags = [\"manual\", \"off-by-default\"],\n            testonly = True,\n        )\n"
  },
  {
    "path": "build/wd_rust_proc_macro.bzl",
    "content": "load(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\", \"rust_test\")\n\ndef wd_rust_proc_macro(\n        name,\n        deps = [],\n        data = [],\n        test_env = {},\n        test_tags = [],\n        test_deps = [],\n        visibility = None):\n    \"\"\"Define rust procedural macro crate.\n\n    Args:\n        name: crate name.\n        deps: crate dependencies: rust crates (typically includes proc-macro2, quote, syn).\n        data: additional data files.\n        test_env: additional test environment variables.\n        test_tags: additional test tags.\n        test_deps: test-only dependencies.\n        visibility: crate visibility.\n    \"\"\"\n    srcs = native.glob([\"**/*.rs\"])\n    crate_name = name.replace(\"-\", \"_\")\n\n    rust_proc_macro(\n        name = name,\n        crate_name = crate_name,\n        srcs = srcs,\n        deps = deps + [\"@workerd//deps/rust:runtime\"],\n        visibility = visibility,\n        data = data,\n        target_compatible_with = select({\n            \"@//build/config:no_build\": [\"@platforms//:incompatible\"],\n            \"//conditions:default\": [],\n        }),\n    )\n\n    rust_test(\n        name = name + \"_test\",\n        crate = \":\" + name,\n        env = {\n            \"RUST_BACKTRACE\": \"1\",\n            # rust test runner captures stderr by default, which makes debugging tests very hard\n            \"RUST_TEST_NOCAPTURE\": \"1\",\n            # our tests are usually very heavy and do not support concurrent invocation\n            \"RUST_TEST_THREADS\": \"1\",\n        } | test_env,\n        tags = test_tags,\n        deps = test_deps,\n    )\n"
  },
  {
    "path": "build/wd_test.bzl",
    "content": "load(\"@aspect_rules_ts//ts:defs.bzl\", \"ts_config\", \"ts_project\")\n\ndef wd_test(\n        src,\n        data = [],\n        name = None,\n        args = [],\n        ts_deps = [],\n        python_snapshot_test = False,\n        generate_default_variant = True,\n        generate_all_autogates_variant = True,\n        generate_all_compat_flags_variant = True,\n        compat_date = \"\",\n        **kwargs):\n    \"\"\"Rule to define tests that run `workerd test` with a particular config.\n\n    Args:\n     src: A .capnp config file defining the test. (`name` will be derived from this if not\n        specified.) The extension `.wd-test` is also permitted instead of `.capnp`, in order to\n        avoid confusing other build systems that may assume a `.capnp` file should be compiled.\n     data: Additional files which the .capnp config file may embed. All TypeScript files will be compiled,\n     their resulting files will be passed to the test as well. Usually TypeScript or Javascript source files.\n     args: Additional arguments to pass to `workerd`. Typically used to pass `--experimental`.\n     generate_default_variant: If True (default), generate the default variant with oldest compat date.\n     generate_all_autogates_variant: If True (default), generate @all-autogates variants.\n     generate_all_compat_flags_variant: If True (default), generate @all-compat-flags variants.\n     compat_date: If specified, use this compat date for the default variant instead of 2000-01-01.\n        Does not affect the @all-compat-flags variant which always uses 2999-12-31.\n\n    The following test variants are generated based on the flags:\n     - name@ (if generate_default_variant): oldest compat date (2000-01-01)\n     - name@all-compat-flags (if generate_all_compat_flags_variant): newest compat date (2999-12-31)\n     - name@all-autogates (if generate_all_autogates_variant): all autogates + oldest compat date\n    \"\"\"\n\n    # Add workerd binary to \"data\" dependencies.\n    data = data + [src, \"//src/workerd/server:workerd_cross\"]\n\n    ts_srcs = [src for src in data if src.endswith(\".ts\")]\n\n    # Default name based on src.\n    if name == None:\n        name = src.removesuffix(\".capnp\").removesuffix(\".wd-test\").removesuffix(\".ts-wd-test\")\n\n    if len(ts_srcs) != 0:\n        # NOTE: We intentionally do not use isolated_typecheck here. While isolated_typecheck can\n        # improve build parallelism by separating transpilation from type-checking, it requires\n        # isolatedDeclarations in tsconfig (which mandates explicit return type annotations on all\n        # exports) and uses --noResolve during transpilation. The --noResolve flag prevents\n        # TypeScript from finding @types/node, breaking IDE support for Node.js imports like\n        # 'node:assert'. Since wd_test TypeScript files are typically standalone test files (leaf\n        # nodes in the dependency graph), the parallelization benefits would be minimal anyway.\n        ts_config(\n            name = name + \"@ts_config\",\n            src = \"tsconfig.json\",\n            deps = [\"@workerd//tools:base-tsconfig\"],\n        )\n        ts_project(\n            name = name + \"@ts_project\",\n            srcs = ts_srcs,\n            tsconfig = \":\" + name + \"@ts_config\",\n            deps = [\"//src/node:node@tsproject\"] + ts_deps,\n        )\n        data += [js_src.removesuffix(\".ts\") + \".js\" for js_src in ts_srcs]\n\n    # Add initial arguments for `workerd test` command.\n    base_args = [\n        \"$(location //src/workerd/server:workerd_cross)\",\n        \"test\",\n        \"$(location {})\".format(src),\n    ] + args\n\n    # Define the compat-date args for each variant\n    # Note: dates must be in range [2000-01-01, 2999-12-31] due to parsing constraints\n    default_compat_args = [\"--compat-date=2000-01-01\"]\n    newest_compat_args = [\"--compat-date=2999-12-31\"]\n\n    if compat_date:\n        default_compat_args = [\"--compat-date={}\".format(compat_date)]\n\n    # Generate variants based on the flags\n    # Default variant: oldest compat date\n    if generate_default_variant:\n        _wd_test(\n            src = src,\n            name = name + \"@\",\n            data = data,\n            args = base_args + default_compat_args,\n            python_snapshot_test = python_snapshot_test,\n            **kwargs\n        )\n\n    # All compat flags variant: newest compat date\n    if generate_all_compat_flags_variant:\n        _wd_test(\n            src = src,\n            name = name + \"@all-compat-flags\",\n            data = data,\n            args = base_args + newest_compat_args,\n            python_snapshot_test = python_snapshot_test,\n            **kwargs\n        )\n\n    # All autogates variant: all autogates + oldest compat date\n    if generate_all_autogates_variant:\n        _wd_test(\n            src = src,\n            name = name + \"@all-autogates\",\n            data = data,\n            args = base_args + default_compat_args + [\"--all-autogates\"],\n            python_snapshot_test = python_snapshot_test,\n            **kwargs\n        )\n\nWINDOWS_TEMPLATE = \"\"\"\n@echo off\nsetlocal EnableDelayedExpansion\n\nREM Run supervisor to start sidecar if specified\nif not \"{sidecar}\" == \"\" (\n    REM These environment variables are processed by the supervisor executable\n    set PORTS_TO_ASSIGN={port_bindings}\n    set RANDOMIZE_IP={randomize_ip}\n    set SIDECAR_COMMAND=\"{sidecar}\"\n    powershell -Command \\\"{supervisor} {runtest}\\\"\n) else (\n    {runtest}\n)\n\nset TEST_EXIT=!ERRORLEVEL!\nexit /b !TEST_EXIT!\n\"\"\"\n\nSH_TEMPLATE = \"\"\"#!/bin/bash\nset -euo pipefail\n\n# Set up coverage for workerd subprocess\nif [ -n \"${{COVERAGE_DIR:-}}\" ]; then\n    export LLVM_PROFILE_FILE=\"${{COVERAGE_DIR}}/%p-%m.profraw\"\n    export KJ_CLEAN_SHUTDOWN=1\nfi\n\n# Run supervisor to start sidecar if specified\nif [ ! -z \"{sidecar}\" ]; then\n    # These environment variables are processed by the supervisor executable\n    PORTS_TO_ASSIGN={port_bindings} RANDOMIZE_IP={randomize_ip} SIDECAR_COMMAND=\"{sidecar}\" {supervisor} {runtest}\nelse\n    {runtest}\nfi\n\"\"\"\n\nWINDOWS_RUNTEST_NORMAL = \"\"\"powershell -Command \\\"%*\\\" `-dTEST_TMPDIR=$ENV:TEST_TMPDIR\"\"\"\n\nSH_RUNTEST_NORMAL = \"\"\"\"$@\" -dTEST_TMPDIR=$TEST_TMPDIR\"\"\"\n\n# We need variants of the RUN_TEST command for Python memory snapshot tests. We have to invoke\n# workerd twice, once with --python-save-snapshot to produce the snapshot and once with\n# --python-load-snapshot to use it.\n#\n# We would like to implement this in py_wd_test and not have to complicate wd_test for it, but\n# unfortunately bazel provides no way for a test to create a file that is used by another test. So\n# we cannot do this with two separate `wd_test` rules. We _could_ use a build step to create the\n# snapshot, but then a failure at this stage would be reported as a build failure when really it\n# should count as a test failure. So the only option left is to make this modification to wd_test to\n# invoke workerd twice for snapshot tests.\n\nWINDOWS_RUNTEST_SNAPSHOT = \"\"\"\n$PYTHON_SAVE_SNAPSHOT_OPTIONS = $ENV:PYTHON_SAVE_SNAPSHOT_ARGS -split ' '\npowershell -Command \\\"%* --python-save-snapshot @PYTHON_SAVE_SNAPSHOT_OPTIONS\\\" `-dTEST_TMPDIR=$ENV:TEST_TMPDIR\nset TEST_EXIT=!ERRORLEVEL!\nif !TEST_EXIT! EQU 0 (\n    powershell -Command \\\"%* --python-load-snapshot snapshot.bin\\\" `-dTEST_TMPDIR=$ENV:TEST_TMPDIR\n    set TEST_EXIT=!ERRORLEVEL!\n)\n\"\"\"\n\nSH_RUNTEST_SNAPSHOT = \"\"\"\necho Creating Python Snapshot\n\"$@\" -dTEST_TMPDIR=$TEST_TMPDIR --python-save-snapshot $PYTHON_SAVE_SNAPSHOT_ARGS\necho \"\"\necho Using Python Snapshot\n\"$@\" -dTEST_TMPDIR=$TEST_TMPDIR --python-load-snapshot snapshot.bin\n\"\"\"\n\ndef _wd_test_impl(ctx):\n    is_windows = ctx.target_platform_has_constraint(ctx.attr._platforms_os_windows[platform_common.ConstraintValueInfo])\n\n    if ctx.file.sidecar and ctx.attr.python_snapshot_test:\n        # TODO(later): Implement support for generating a combined script with these two options\n        # if we have a test that requires it. Not doing it for now due to complexity.\n        return print(\"sidecar and python_snapshot_test currently cannot be used together\")\n\n    # Bazel insists that the rule must actually create the executable that it intends to run; it\n    # can't just specify some other executable with some args. OK, fine, we'll use a script that\n    # just execs its args.\n    if is_windows:\n        # Batch script executables must end with \".bat\"\n        executable = ctx.actions.declare_file(\"%s_wd_test.bat\" % ctx.label.name)\n        template = WINDOWS_TEMPLATE\n        runtest = WINDOWS_RUNTEST_SNAPSHOT if ctx.attr.python_snapshot_test else WINDOWS_RUNTEST_NORMAL\n    else:\n        executable = ctx.outputs.executable\n        template = SH_TEMPLATE\n        runtest = SH_RUNTEST_SNAPSHOT if ctx.attr.python_snapshot_test else SH_RUNTEST_NORMAL\n\n    content = template.format(\n        sidecar = ctx.file.sidecar.short_path if ctx.file.sidecar else \"\",\n        runtest = runtest,\n        supervisor = ctx.file.sidecar_supervisor.short_path if ctx.file.sidecar_supervisor else \"\",\n        port_bindings = \",\".join(ctx.attr.sidecar_port_bindings),\n        randomize_ip = \"true\" if ctx.attr.sidecar_randomize_ip else \"false\",\n    )\n\n    ctx.actions.write(\n        output = executable,\n        content = content,\n        is_executable = True,\n    )\n\n    runfiles = ctx.runfiles(files = ctx.files.data)\n    if ctx.file.sidecar:\n        runfiles = runfiles.merge(ctx.runfiles(files = [ctx.file.sidecar]))\n\n        # Also merge the sidecar's own runfiles if it has any\n        default_runfiles = ctx.attr.sidecar[DefaultInfo].default_runfiles\n        if default_runfiles:\n            runfiles = runfiles.merge(default_runfiles)\n\n        runfiles = runfiles.merge(ctx.runfiles(files = [ctx.file.sidecar_supervisor]))\n\n        # Also merge the supervisor's own runfiles if it has any\n        default_runfiles = ctx.attr.sidecar_supervisor[DefaultInfo].default_runfiles\n        if default_runfiles:\n            runfiles = runfiles.merge(default_runfiles)\n\n    # IMPORTANT: The workerd binary must be listed in dependency_attributes\n    # to ensure its transitive dependencies (all the C++ source files) are\n    # included in the coverage instrumentation. Without this, coverage data\n    # won't be collected for the actual workerd implementation code.\n    instrumented_files_info = coverage_common.instrumented_files_info(\n        ctx,\n        source_attributes = [\"src\", \"data\"],\n        dependency_attributes = [\"workerd\", \"sidecar\", \"sidecar_supervisor\"],\n    )\n    environment = dict(ctx.attr.env)\n    if ctx.attr.python_snapshot_test:\n        environment[\"PYTHON_SAVE_SNAPSHOT_ARGS\"] = \"\"\n    if ctx.attr.load_snapshot:\n        if ctx.attr.python_snapshot_test:\n            environment[\"PYTHON_SAVE_SNAPSHOT_ARGS\"] = \"--python-load-snapshot load_snapshot.bin\"\n        f = ctx.attr.load_snapshot.files.to_list()[0]\n        runfiles = runfiles.merge(ctx.runfiles(symlinks = {\"load_snapshot.bin\": f}))\n\n    return [\n        DefaultInfo(\n            executable = executable,\n            runfiles = runfiles,\n        ),\n        RunEnvironmentInfo(\n            environment = environment,\n        ),\n        instrumented_files_info,\n    ]\n\n_wd_test = rule(\n    implementation = _wd_test_impl,\n    test = True,\n    attrs = {\n        # Implicit dependencies used by Bazel to generate coverage reports.\n        \"_lcov_merger\": attr.label(\n            default = configuration_field(fragment = \"coverage\", name = \"output_generator\"),\n            executable = True,\n            cfg = config.exec(exec_group = \"test\"),\n        ),\n        \"_collect_cc_coverage\": attr.label(\n            default = \"@bazel_tools//tools/test:collect_cc_coverage\",\n            executable = True,\n            cfg = config.exec(exec_group = \"test\"),\n        ),\n        # Source file\n        \"src\": attr.label(allow_single_file = True),\n        # The workerd executable is used to run all tests.\n        # Using cfg = \"target\" instead of \"exec\" ensures workerd is built with coverage\n        # instrumentation when running `bazel coverage`. With cfg = \"exec\", the binary\n        # would be built in exec configuration which doesn't include coverage flags.\n        \"workerd\": attr.label(\n            allow_single_file = True,\n            executable = True,\n            cfg = \"target\",\n            default = \"//src/workerd/server:workerd_cross\",\n        ),\n        # A list of files that this test requires to be present in order to run.\n        \"data\": attr.label_list(allow_files = True),\n        # If set, an executable that is run in parallel with the test, and provides some functionality\n        # needed for the test. This is usually a backend server, with workerd serving as the client.\n        # The sidecar will be killed once the test completes.\n        \"sidecar\": attr.label(\n            allow_single_file = True,\n            executable = True,\n            cfg = \"exec\",\n        ),\n        # Sidecars\n        # ---------\n        # For detailed documentation, see src/workerd/api/node/tests/sidecar-supervisor.mjs\n\n        # A list of binding names which will be filled in with random port numbers that the sidecar\n        # and test can use for communication. The test will only begin once the sidecar is\n        # listening to these ports.\n        #\n        # In the sidecar, access these bindings as environment variables. In the wd-test file, add\n        # fromEnvironment bindings to expose them to the test.\n        #\n        # Reminder: you'll also need to add a network = ( allow = [\"private\"] ) service as well.\n        \"sidecar_port_bindings\": attr.string_list(),\n        # If true, a random IP address will be assigned to the sidecar process, and provided in the\n        # environment variable SIDECAR_HOSTNAME,\n        \"sidecar_randomize_ip\": attr.bool(default = True),\n        # An executable that is used to manage port assignments and child process creation when a\n        # sidecar is specified.\n        \"sidecar_supervisor\": attr.label(\n            allow_single_file = True,\n            executable = True,\n            cfg = \"exec\",\n            default = \"//src/workerd/api/node/tests:sidecar-supervisor\",\n        ),\n        \"python_snapshot_test\": attr.bool(),\n        \"load_snapshot\": attr.label(allow_single_file = True),\n        # Environment variables to set when running the test\n        \"env\": attr.string_dict(),\n        # A reference to the Windows platform label, needed for the implementation of wd_test\n        \"_platforms_os_windows\": attr.label(default = \"@platforms//os:windows\"),\n    },\n)\n"
  },
  {
    "path": "build/wd_ts_bundle.bzl",
    "content": "load(\"@aspect_rules_ts//ts:defs.bzl\", \"ts_config\", \"ts_project\")\nload(\"@workerd//:build/lint_test.bzl\", \"lint_test\")\nload(\"@workerd//:build/wd_js_bundle.bzl\", \"wd_js_bundle\")\n\ndef to_js(filenames):\n    return [_to_js(f) for f in filenames]\n\ndef _to_js(file_name):\n    if file_name.endswith(\".ts\"):\n        return file_name.removesuffix(\".ts\") + \".js\"\n    return file_name\n\ndef _to_d_ts(file_name):\n    return file_name.removesuffix(\".ts\") + \".d.ts\"\n\ndef wd_ts_bundle(\n        name,\n        import_name,\n        schema_id,\n        modules,\n        internal_modules,\n        tsconfig_json,\n        eslintrc_json,\n        internal_wasm_modules = [],\n        internal_data_modules = [],\n        internal_json_modules = [],\n        lint = True,\n        deps = [],\n        js_deps = [],\n        gen_compile_cache = False,\n        out_dir = \"\"):\n    \"\"\"Compiles typescript modules and generates api bundle with the result.\n\n    Args:\n      name: bundle target name\n      import_name: bundle import name. Modules will be accessible under\n          \"<import_name>:<module_name>\" and internal modules under\n          \"<import_name>-internal:<module_name>\".\n      schema_id: bundle capnp schema id,\n      modules: list of js and ts source files for builtin modules\n      internal_modules: list of js, ts, and d.ts source files for internal modules\n      tsconfig_json: tsconfig.json label\n      eslintrc_json: eslintrc.json label\n      internal_wasm_modules: list of wasm source files\n      internal_data_modules: list of data source files\n      internal_json_modules: list of json source files\n      lint: enables/disables source linting\n      deps: additional typescript dependencies\n      gen_compile_cache: generate compilation cache of every file and include into the bundle\n      js_deps: javascript dependencies\n    \"\"\"\n    ts_config(\n        name = name + \"@tsconfig\",\n        src = tsconfig_json,\n        deps = [\"@workerd//tools:base-tsconfig\"],\n    )\n\n    srcs = modules + internal_modules\n    ts_srcs = [src for src in srcs if src.endswith(\".ts\")]\n    declarations = [_to_d_ts(src) for src in ts_srcs if not src.endswith(\".d.ts\")]\n\n    ts_project(\n        name = name + \"@tsproject\",\n        srcs = ts_srcs,\n        allow_js = True,\n        declaration = True,\n        tsconfig = \":\" + name + \"@tsconfig\",\n        deps = deps,\n        out_dir = out_dir.removesuffix(\"/\"),\n        visibility = [\"//visibility:public\"],\n    )\n\n    wd_js_bundle(\n        name = name,\n        import_name = import_name,\n        # builtin modules are accessible under \"<import_name>:<module_name>\" name\n        builtin_modules = [out_dir + _to_js(m) for m in modules],\n        # internal modules are accessible under \"<import_name>-internal:<module_name>\" name\n        # without \"internal/\" folder prefix.\n        internal_modules = [out_dir + _to_js(m) for m in internal_modules if not m.endswith(\".d.ts\")],\n        internal_wasm_modules = internal_wasm_modules,\n        internal_data_modules = internal_data_modules,\n        internal_json_modules = internal_json_modules,\n        declarations = declarations,\n        schema_id = schema_id,\n        deps = deps + js_deps,\n        gen_compile_cache = gen_compile_cache,\n        out_dir = out_dir,\n    )\n\n    if lint:\n        lint_test(\n            name = name,\n            eslintrc_json = eslintrc_json,\n            tsconfig_json = tsconfig_json,\n            srcs = srcs,\n        )\n"
  },
  {
    "path": "build/wd_ts_project.bzl",
    "content": "load(\"@aspect_rules_ts//ts:defs.bzl\", \"ts_config\", \"ts_project\")\nload(\"@workerd//:build/lint_test.bzl\", \"lint_test\")\n\ndef wd_ts_project(name, srcs, deps, tsconfig_json, eslintrc_json = None, source_map = True, testonly = False, composite = False):\n    \"\"\"Bazel rule for a workerd TypeScript project, setting common options\"\"\"\n\n    ts_config(\n        name = name + \"@tsconfig\",\n        src = tsconfig_json,\n        deps = [\"@workerd//tools:base-tsconfig\"],\n    )\n\n    ts_project(\n        name = name,\n        srcs = srcs,\n        deps = deps,\n        tsconfig = \":\" + name + \"@tsconfig\",\n        allow_js = True,\n        source_map = source_map,\n        testonly = testonly,\n        composite = composite,\n    )\n\n    if eslintrc_json:\n        lint_test(\n            name = name,\n            eslintrc_json = eslintrc_json,\n            tsconfig_json = tsconfig_json,\n            srcs = srcs + deps,\n        )\n"
  },
  {
    "path": "build/wd_ts_test.bzl",
    "content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_test\")\nload(\"//:build/typescript.bzl\", \"js_name\", \"module_name\")\nload(\"//:build/wd_ts_project.bzl\", \"wd_ts_project\")\n\ndef wd_ts_test(src, tsconfig_json, deps = [], eslintrc_json = None, composite = False, **kwargs):\n    \"\"\"Bazel rule to compile and run a TypeScript test\"\"\"\n\n    name = module_name(src)\n\n    wd_ts_project(\n        name = name + \"@compile\",\n        srcs = [src],\n        deps = deps,\n        testonly = True,\n        eslintrc_json = eslintrc_json,\n        tsconfig_json = tsconfig_json,\n        composite = composite,\n    )\n\n    js_test(\n        name = name,\n        entry_point = js_name(src),\n        data = deps + [name + \"@compile\"],\n        tags = [\"no-arm64\", \"js-test\"],\n        **kwargs\n    )\n"
  },
  {
    "path": "build/wd_ts_type_test.bzl",
    "content": "load(\"@npm//:typescript/package_json.bzl\", tsc_bin = \"bin\")\nload(\"//:build/typescript.bzl\", \"module_name\")\n\ndef wd_ts_type_test(src, **kwargs):\n    \"\"\"Bazel rule to test TypeScript file type-checks under @cloudflare/workers-types\"\"\"\n    name = module_name(src)\n    tsc_bin.tsc_test(\n        name = name,\n        args = [\n            \"--project\",\n            \"$(location //types:test/types/tsconfig.json)\",\n            \"--types\",\n            \"../../definitions/experimental\",\n        ],\n        data = [\n            \"//types\",\n            \"//types:test/types/tsconfig.json\",\n            \"//:node_modules/expect-type\",\n            src,\n        ],\n        # Exclude from coverage: depends on //types which runs coverage-instrumented\n        # workerd binary that crashes during type generation\n        tags = [\"no-coverage\"],\n    )\n"
  },
  {
    "path": "build/workerd-v8/BUILD",
    "content": "load(\"@rules_cc//cc:cc_library.bzl\", \"cc_library\")\n\ncc_library(\n    name = \"v8\",\n    defines = [\"WORKERD_ICU_DATA_EMBED\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@workerd//:icudata-embed\",\n        \"@workerd//:v8_icu\",\n    ],\n)\n"
  },
  {
    "path": "build/workerd-v8/MODULE.bazel",
    "content": "\"shim to make a cc_library appear as a top-level repository\"\n\nmodule(name = \"workerd-v8\")\n\nbazel_dep(name = \"workerd\")\nlocal_path_override(\n    module_name = \"workerd\",\n    path = \"../..\",\n)\n\nbazel_dep(name = \"rules_cc\", version = \"0.2.11\")\n"
  },
  {
    "path": "build/wpt_get_directories.bzl",
    "content": "def wpt_all_dirs():\n    always_exclude = [\n        \".*/**\",  # dotfiles\n        \"tools/**\",  # backend\n    ]\n    files = native.glob([\"**/*\"], exclude_directories = 1)\n    return native.glob([\"**/*\"], exclude_directories = 0, exclude = files + always_exclude)\n"
  },
  {
    "path": "build/wpt_test.bzl",
    "content": "# Copyright (c) 2017-2022 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\nload(\"@bazel_skylib//lib:paths.bzl\", \"paths\")\nload(\"//:build/wd_test.bzl\", \"wd_test\")\n\nPORT_BINDINGS = [\n    \"HTTP_PORT\",\n    \"HTTPS_PORT\",\n    # The remaining ports are not currently used by tests but need to be assigned to wptserve anyway\n    \"HTTP_PORT_2\",\n    \"HTTPS_PORT_2\",\n    \"HTTP_PUBLIC_PORT\",\n    \"HTTPS_PUBLIC_PORT\",\n    \"HTTP_LOCAL_PORT\",\n    \"WSS_PORT\",\n    \"WS_PORT\",\n]\n\n### wpt_test macro\n### (Invokes wpt_js_test_gen, wpt_wd_test_gen and wd_test to assemble a complete test suite.)\n### -----------------------------------------------------------------------------------------\n\ndef wpt_test(name, wpt_directory, config, compat_date = \"\", compat_flags = [], autogates = [], start_server = False, **kwargs):\n    \"\"\"\n    Main entry point.\n\n    1. Generates a workerd test suite in JS. This contains the logic to run\n       each WPT test file, applying the relevant test config.\n    2. Generates a wd-test file for this test suite. This contains all of the\n       paths to modules needed to run the test: generated test suite, test config\n       file, WPT test scripts, associated JSON resources.\n    \"\"\"\n\n    js_test_gen_rule = \"{}@_wpt_js_test_gen\".format(name)\n    test_config_as_js = config.removesuffix(\".ts\") + \".js\"\n    wpt_tsproject = \"//src/wpt:wpt-all@tsproject\"\n    harness = \"//src/wpt:harness@js\"\n    wpt_cacert = \"@wpt//:tools/certs/cacert.pem\"\n    wpt_common = \"@wpt//:common@module\"\n    wpt_resources = \"@wpt//:resources@module\"\n    wpt_interfaces = \"@wpt//:interfaces@module\"\n\n    if compat_date == \"\":\n        compat_date = \"2999-12-31\"\n\n    _wpt_js_test_gen(\n        name = js_test_gen_rule,\n        test_name = name,\n        wpt_directory = wpt_directory,\n        test_config = test_config_as_js,\n        wpt_tsproject = wpt_tsproject,\n    )\n\n    wd_test_gen_rule = \"{}@_wpt_wd_test_gen\".format(name)\n    _wpt_wd_test_gen(\n        name = wd_test_gen_rule,\n        test_name = name,\n        wpt_directory = wpt_directory,\n        test_config = test_config_as_js,\n        test_js_generated = js_test_gen_rule,\n        harness = harness,\n        autogates = autogates,\n        wpt_cacert = wpt_cacert,\n        wpt_common = wpt_common,\n        wpt_resources = wpt_resources,\n        wpt_interfaces = wpt_interfaces,\n        compat_flags = compat_flags + [\"immutable_api_prototypes\", \"spec_compliant_property_attributes\", \"pedantic_wpt\", \"experimental\", \"nodejs_compat\", \"unsupported_process_actual_platform\"],\n    )\n\n    data = [\n        test_config_as_js,  # e.g. \"url-test.js\"\n        js_test_gen_rule,  # e.g. \"url-test.generated.js\",\n        wpt_directory,  # e.g. wpt/url/**\",\n        harness,  # e.g. wpt/harness/*.ts\n        wpt_cacert,  # i.e. \"wpt/tools/certs/cacert.pem\",\n        wpt_common,  # i.e. \"wpt/common/**\"\n        wpt_resources,  # i.e. \"wpt/resources/**\"\n        wpt_interfaces,  # i.e. \"wpt/interfaces/**\"\n    ]\n\n    wd_test(\n        name = \"{}\".format(name),\n        src = wd_test_gen_rule,\n        args = [\"--experimental\"],\n        sidecar_port_bindings = PORT_BINDINGS if start_server else [],\n        sidecar = \"@wpt//:entrypoint\" if start_server else None,\n        compat_date = compat_date,\n        generate_all_compat_flags_variant = False,  # Already using future date where possible.\n        data = data,\n        **kwargs\n    )\n\n### wpt_module macro and rule\n### (Discovers test files within the WPT directory tree)\n### ----------------------------------------------------\n\ndef wpt_module(name):\n    \"\"\"\n    Given the name of a directory within the WPT tree, creates a rule providing:\n\n    srcs: All files within the directory\n    dir: A reference to the directory itself.\n\n    This info is later used by wpt_test rules to generate JS code and wd-test config\n    for each test.\n    \"\"\"\n    return _wpt_module(\n        name = \"{}@module\".format(name),\n        dir = name,\n        srcs = native.glob([\"{}/**/*\".format(name)]),\n        visibility = [\"//visibility:public\"],\n    )\n\nWPTModuleInfo = provider(fields = [\"base\"])\n\ndef _wpt_module_impl(ctx):\n    return [\n        DefaultInfo(files = depset(ctx.files.srcs)),\n        WPTModuleInfo(base = ctx.attr.dir.files.to_list()[0]),\n    ]\n\n_wpt_module = rule(\n    implementation = _wpt_module_impl,\n    attrs = {\n        \"dir\": attr.label(allow_single_file = True),\n        \"srcs\": attr.label_list(allow_files = True),\n    },\n)\n\n### wpt_js_test_gen rule\n### (Generates a .js file for each test)\n### ------------------------------------\n\ndef _wpt_js_test_gen_impl(ctx):\n    \"\"\"\n    Generates a workerd test suite in JS.\n\n    This contains the logic to run each WPT test file, applying the relevant test config.\n    \"\"\"\n\n    src = ctx.actions.declare_file(\"{}-test.generated.js\".format(ctx.attr.test_name))\n    base = ctx.attr.wpt_directory[WPTModuleInfo].base\n\n    files = ctx.attr.wpt_directory.files.to_list()\n    test_files = [file for file in files if is_test_file(file)]\n\n    ctx.actions.write(\n        output = src,\n        content = WPT_JS_TEST_TEMPLATE.format(\n            test_config = ctx.file.test_config.basename,\n            cases = generate_external_cases(base, test_files),\n            all_test_files = generate_external_file_list(base, test_files),\n            test_name = ctx.attr.test_name,\n        ),\n    )\n\n    return DefaultInfo(\n        files = depset([src]),\n    )\n\n_wpt_js_test_gen = rule(\n    implementation = _wpt_js_test_gen_impl,\n    attrs = {\n        # A string to use as the test name. Used in the wd-test filename and the worker's name\n        \"test_name\": attr.string(),\n        # A file group representing a directory of wpt tests. All files in the group will be embedded.\n        \"wpt_directory\": attr.label(),\n        # A JS file containing the test configuration.\n        \"test_config\": attr.label(allow_single_file = True),\n        # Dependency: The ts_project rule that compiles the tests to JS\n        \"wpt_tsproject\": attr.label(),\n    },\n)\n\nWPT_JS_TEST_TEMPLATE = \"\"\"// This file is autogenerated by wpt_test.bzl\n// DO NOT EDIT.\nimport {{ createRunner }} from 'harness/harness';\nimport config from '{test_config}';\n\nconst allTestFiles = {all_test_files};\nconst {{ run, printResults }} = createRunner(config, '{test_name}', allTestFiles);\n\n{cases}\n\nexport const zzz_results = printResults();\n\"\"\"\n\ndef is_test_file(file):\n    if not file.path.endswith(\".js\"):\n        # Not JS code, not a test\n        return False\n\n    if is_in_resources_directory(file):\n        # If it's in a subdirectory named resources/, then it's meant to be included by tests,\n        # not to run on its own. This logic still isn't perfect; sometimes resources are dropped\n        # into the main directory, and would need to manually be marked as skipAllTests\n        return False\n\n    if \".tentative.\" in file.path or \"/tentative/\" in file.path:\n        # Tentative tests are for proposed features that are not yet standardized.\n        # We skip these to avoid noise from unstable specifications.\n        return False\n\n    # Probably an actual test\n    return True\n\ndef generate_external_cases(base, files):\n    \"\"\"\n    Generate a workerd test case that runs each test file in the WPT module.\n    \"\"\"\n\n    result = []\n    for file in files:\n        relative_path = module_relative_path(base, file)\n        result.append(\"export const {} = run('{}');\".format(test_case_name(relative_path), relative_path))\n\n    return \"\\n\".join(result)\n\ndef generate_external_file_list(base, files):\n    \"\"\"\n    Generate a JS list containing the name of every test file in the WPT module\n    \"\"\"\n\n    return \"[{}];\".format(\", \".join([\n        \"'{}'\".format(module_relative_path(base, file))\n        for file in files\n    ]))\n\n### wpt_wd_test_gen rule\n### (Generates a .wd-test file for each test)\n### -----------------------------------------\n\ndef _wpt_wd_test_gen_impl(ctx):\n    \"\"\"\n    Generates a wd-test file for this test suite.\n\n    This contains all of the paths to modules needed to run the test: generated test suite, test\n    config file, WPT test scripts, associated JSON resources.\n    \"\"\"\n    src = ctx.actions.declare_file(\"{}.wd-test\".format(ctx.attr.test_name))\n    base = ctx.attr.wpt_directory[WPTModuleInfo].base\n\n    # Generate bindings for test directory files plus common/**/*.js, resources/**/*.js, and interfaces/**/*.idl\n    bindings = generate_external_bindings(src, base, ctx.attr.wpt_directory.files)\n    common_base = ctx.attr.wpt_common[WPTModuleInfo].base\n    common_bindings = generate_common_bindings(src, common_base, ctx.attr.wpt_common.files)\n    resources_base = ctx.attr.wpt_resources[WPTModuleInfo].base\n    resources_bindings = generate_resources_bindings(src, resources_base, ctx.attr.wpt_resources.files)\n    interfaces_base = ctx.attr.wpt_interfaces[WPTModuleInfo].base\n    interfaces_bindings = generate_interfaces_bindings(src, interfaces_base, ctx.attr.wpt_interfaces.files)\n    all_bindings = \",\\n\".join([b for b in [bindings, common_bindings, resources_bindings, interfaces_bindings] if b])\n    ctx.actions.write(\n        output = src,\n        content = WPT_WD_TEST_TEMPLATE.format(\n            test_name = ctx.attr.test_name,\n            test_config = ctx.file.test_config.basename,\n            test_js_generated = ctx.file.test_js_generated.basename,\n            bindings = all_bindings,\n            harness_modules = generate_harness_modules(src, ctx.attr.harness.files),\n            wpt_cacert = wd_test_relative_path(src, ctx.file.wpt_cacert),\n            autogates = generate_autogates_field(ctx.attr.autogates),\n            compat_flags = generate_compat_flags_field(ctx.attr.compat_flags),\n        ),\n    )\n\n    return DefaultInfo(\n        files = depset([src]),\n    )\n\n_wpt_wd_test_gen = rule(\n    implementation = _wpt_wd_test_gen_impl,\n    attrs = {\n        # A string to use as the test name. Used in the wd-test filename and the worker's name\n        \"test_name\": attr.string(),\n        # A file group representing a directory of wpt tests. All files in the group will be embedded.\n        \"wpt_directory\": attr.label(),\n        # A JS file containing the test configuration.\n        \"test_config\": attr.label(allow_single_file = True),\n        # An auto-generated JS file containing the test logic.\n        \"test_js_generated\": attr.label(allow_single_file = True),\n        # Target specifying the files in the WPT test harness\n        \"harness\": attr.label(),\n        # Target specifying the location of the WPT CA certificate\n        \"wpt_cacert\": attr.label(allow_single_file = True),\n        # Target specifying the WPT common directory module\n        \"wpt_common\": attr.label(),\n        # Target specifying the WPT resources directory module\n        \"wpt_resources\": attr.label(),\n        # Target specifying the WPT interfaces directory module\n        \"wpt_interfaces\": attr.label(),\n        # A list of autogates to specify in the generated wd-test file\n        \"autogates\": attr.string_list(),\n        # A list of compatibility flags to specify in the generated wd-test file\n        \"compat_flags\": attr.string_list(),\n    },\n)\n\nWPT_WD_TEST_TEMPLATE = \"\"\"\nusing Workerd = import \"/workerd/workerd.capnp\";\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"{test_name}\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"{test_js_generated}\"),\n          (name = \"{test_config}\", esModule = embed \"{test_config}\"),\n          {harness_modules}\n        ],\n        bindings = [\n          (name = \"wpt\", service = \"wpt\"),\n          (name = \"unsafe\", unsafeEval = void),\n          (name = \"SIDECAR_HOSTNAME\", fromEnvironment = \"SIDECAR_HOSTNAME\"),\n          (name = \"HTTP_PORT\", fromEnvironment = \"HTTP_PORT\"),\n          (name = \"HTTPS_PORT\", fromEnvironment = \"HTTPS_PORT\"),\n          (name = \"WS_PORT\", fromEnvironment = \"WS_PORT\"),\n          (name = \"WSS_PORT\", fromEnvironment = \"WSS_PORT\"),\n          (name = \"GEN_TEST_CONFIG\", fromEnvironment = \"GEN_TEST_CONFIG\"),\n          (name = \"GEN_TEST_REPORT\", fromEnvironment = \"GEN_TEST_REPORT\"),\n          (name = \"GEN_TEST_STATS\", fromEnvironment = \"GEN_TEST_STATS\"),\n          {bindings}\n        ],\n        {compat_flags}\n      )\n    ),\n    ( name = \"internet\",\n      network = (\n        allow = [\"private\"],\n        tlsOptions = (\n          trustedCertificates = [\n            embed \"{wpt_cacert}\"\n          ]\n        )\n      )\n    ),\n    (\n      name = \"wpt\",\n      disk = \".\",\n    )\n  ],\n  v8Flags = [\"--expose-gc\"],\n  {autogates}\n);\"\"\"\n\ndef generate_autogates_field(autogates):\n    \"\"\"\n    Generates a capnproto fragment listing the specified autogates.\n    \"\"\"\n\n    if not autogates:\n        return \"\"\n\n    autogate_list = \", \".join(['\"{}\"'.format(autogate) for autogate in autogates])\n    return \"autogates = [{}],\".format(autogate_list)\n\ndef generate_compat_flags_field(flags):\n    \"\"\"\n    Generates a capnproto fragment listing the specified compatibility flags.\n    \"\"\"\n\n    if not flags:\n        return \"\"\n\n    flag_list = \", \".join(['\"{}\"'.format(flag) for flag in flags])\n    return \"compatibilityFlags = [{}],\".format(flag_list)\n\ndef generate_external_bindings(wd_test_file, base, files):\n    \"\"\"\n    Generates appropriate bindings for each file in the WPT module:\n\n    - JS files: text binding to allow code to be evaluated\n    - JSON files: JSON binding to allow test code to fetch resources\n    \"\"\"\n\n    result = []\n\n    for file in files.to_list():\n        if file.extension == \"js\":\n            binding_type = \"text\"\n        elif file.extension == \"json\":\n            binding_type = \"json\"\n        else:\n            # Unknown binding type, skip for now\n            continue\n\n        result.append('(name = \"{}\", {} = embed \"{}\")'.format(module_relative_path(base, file), binding_type, wd_test_relative_path(wd_test_file, file)))\n\n    return \",\\n\".join(result)\n\ndef generate_common_bindings(wd_test_file, base, files):\n    \"\"\"\n    Generates appropriate bindings for each file in the WPT common module.\n    Files are prefixed with /common/ path.\n\n    Only JS files are included as some JSON files in common/ contain\n    non-standard JSON (e.g., with comments) that would cause parsing errors.\n    \"\"\"\n\n    result = []\n\n    for file in files.to_list():\n        if file.extension == \"js\":\n            binding_type = \"text\"\n        else:\n            # Skip non-JS files to avoid issues with non-standard JSON\n            continue\n\n        relative_path = module_relative_path(base, file)\n        result.append('(name = \"/common/{}\", {} = embed \"{}\")'.format(relative_path, binding_type, wd_test_relative_path(wd_test_file, file)))\n\n    return \",\\n\".join(result)\n\ndef generate_resources_bindings(wd_test_file, base, files):\n    \"\"\"\n    Generates appropriate bindings for each file in the WPT resources module.\n    Files are prefixed with /resources/ path.\n\n    Only JS files are included. Also adds an alias for /resources/WebIDLParser.js\n    which points to /resources/webidl2/lib/webidl2.js (matching the WPT server\n    rewrite rule in tools/serve/serve.py).\n    \"\"\"\n\n    result = []\n    webidl2_file = None\n\n    for file in files.to_list():\n        if file.extension != \"js\":\n            # Skip non-JS files\n            continue\n\n        relative_path = module_relative_path(base, file)\n        file_path = wd_test_relative_path(wd_test_file, file)\n        result.append('(name = \"/resources/{}\", text = embed \"{}\")'.format(relative_path, file_path))\n\n        # Track the webidl2.js file for the alias\n        if relative_path == \"webidl2/lib/webidl2.js\":\n            webidl2_file = file_path\n\n    # Add the WebIDLParser.js alias (WPT server rewrites this to webidl2/lib/webidl2.js)\n    if webidl2_file:\n        result.append('(name = \"/resources/WebIDLParser.js\", text = embed \"{}\")'.format(webidl2_file))\n\n    return \",\\n\".join(result)\n\ndef generate_interfaces_bindings(wd_test_file, base, files):\n    \"\"\"\n    Generates appropriate bindings for each file in the WPT interfaces module.\n    Files are prefixed with /interfaces/ path.\n\n    Only IDL files are included (*.idl) which are used by idlharness tests.\n    \"\"\"\n\n    result = []\n\n    for file in files.to_list():\n        if file.extension != \"idl\":\n            # Skip non-IDL files\n            continue\n\n        relative_path = module_relative_path(base, file)\n        file_path = wd_test_relative_path(wd_test_file, file)\n        result.append('(name = \"/interfaces/{}\", text = embed \"{}\")'.format(relative_path, file_path))\n\n    return \",\\n\".join(result)\n\ndef generate_harness_modules(wd_test_file, files):\n    \"\"\"\n    Generates a module entry for each file in the harness package\n    \"\"\"\n    result = []\n\n    for file in files.to_list():\n        relative_path = wd_test_relative_path(wd_test_file, file)\n        import_path = paths.basename(relative_path).removesuffix(\".js\")\n        result.append('(name = \"harness/{}\", esModule = embed \"{}\")'.format(import_path, relative_path))\n\n    return \",\\n\".join(result)\n\n### WPT server entrypoint\n### ---------------------\n### (Create a single no-args script that starts the WPT server)\n\nWPT_ENTRYPOINT_SCRIPT_TEMPLATE = \"\"\"\n# Make /usr/sbin/sysctl visible (Python needs to call it on macOS)\nexport PATH=\"$PATH:/usr/sbin\"\n\ncd $(dirname $0)\n{python} wpt.py serve --no-h2 --config /dev/stdin <<EOF\n{{\n  \"server_host\": \"$SIDECAR_HOSTNAME\",\n  \"check_subdomains\": false,\n  \"ports\": {{\n    \"http\": [$HTTP_PORT, $HTTP_PORT_2],\n    \"https\": [$HTTPS_PORT, $HTTPS_PORT_2],\n    \"http-public\": [$HTTP_PUBLIC_PORT],\n    \"https-public\": [$HTTPS_PUBLIC_PORT],\n    \"http-local\": [$HTTP_LOCAL_PORT],\n    \"wss\": [$WSS_PORT],\n    \"ws\": [$WS_PORT]\n  }}\n}}\nEOF\n\"\"\"\n\ndef _wpt_server_entrypoint_impl(ctx):\n    \"\"\"\n    This rule generates a script that starts the wpt server.\n\n    This script is passed as the sidecar to wd_test.\n    We generate a script because Bazel doesn't want arguments in this context, and we use a custom\n    rule to do so because we want more control over paths and dependencies than with a genrule.\n    \"\"\"\n    start_src = ctx.actions.declare_file(\"start.sh\")\n    ctx.actions.write(\n        output = start_src,\n        is_executable = True,\n        content = WPT_ENTRYPOINT_SCRIPT_TEMPLATE.format(\n            python = ctx.file.python.short_path,\n        ),\n    )\n\n    return DefaultInfo(\n        runfiles = ctx.runfiles(\n            files = [start_src],\n            transitive_files = depset(ctx.files.srcs + [ctx.file.python]),\n        ),\n        executable = start_src,\n    )\n\nwpt_server_entrypoint = rule(\n    implementation = _wpt_server_entrypoint_impl,\n    attrs = {\n        # Python interpreter to use to run the WPT server\n        \"python\": attr.label(allow_single_file = True),\n        # All the Python code that should be visible\n        \"srcs\": attr.label_list(allow_files = True),\n    },\n)\n\n### Path manipulation\n### -----------------\n\ndef module_relative_path(module_base, file):\n    \"\"\"\n    Return the relative path of a file inside its parent WPT module.\n\n    For example, within the 'dom/abort' module, the path\n    'dom/abort/resources/abort-signal-any-tests.js' would be referred to as\n    'resources/abort-signal-any-tests.js'\n    \"\"\"\n\n    return paths.relativize(file.short_path, module_base.short_path)\n\ndef relative_path(from_path, to_path):\n    \"\"\"\n    Finds the relative path from from_path to to_path.\n    Based on <https://github.com/nodejs/node/blob/0a91e988cfe2a994e60780a4ce001e44812a9ad9/lib/path.js#L1280>\n    \"\"\"\n\n    # The algorithm we got from Node assumes both paths are absolute,\n    # so add a slash rather than change all the logic below\n    from_path = \"/{}\".format(from_path)\n    to_path = \"/{}\".format(to_path)\n\n    from_start = 1\n    from_end = len(from_path)\n    from_len = from_end - from_start\n    to_start = 1\n    to_len = len(to_path) - to_start\n\n    length = from_len if from_len < to_len else to_len\n    last_common_sep = -1\n    i = 0\n\n    for i in range(length):\n        from_ch = from_path[from_start + i]\n        if from_ch != to_path[to_start + i]:\n            break\n        elif from_ch == \"/\":\n            last_common_sep = i\n\n    # Compare paths to find the longest common path from root\n    if i == length:\n        if to_len > length:\n            if to_path[to_start + i] == \"/\":\n                # We get here if `from_path` is the exact base path for `to_path`.\n                # For example: from='/foo/bar'; to='/foo/bar/baz'\n                return to_path[to_start + i + 1:]\n\n            if i == 0:\n                # We get here if `from` is the root\n                # For example: from='/'; to='/foo'\n                return to_path[to_start + i:]\n    elif from_len > length:\n        if from_path[from_start + i] == \"/\":\n            # We get here if `to` is the exact base path for `from`.\n            # For example: from='/foo/bar/baz'; to='/foo/bar'\n            last_common_sep = i\n        elif i == 0:\n            # We get here if `to` is the root.\n            # For example: from='/foo/bar'; to='/'\n            last_common_sep = 0\n\n    out = \"\"\n\n    # Generate the relative path based on the path difference between `to`\n    # and `from`.\n    for i in range(from_start + last_common_sep + 1, from_end + 1):\n        if i == from_end or from_path[i] == \"/\":\n            out += \"..\" if len(out) == 0 else \"/..\"\n\n    # Lastly, append the rest of the destination (`to`) path that comes after\n    # the common path parts.\n    return out + to_path[to_start + last_common_sep:]\n\ndef wd_test_relative_path(wd_test_file, file):\n    \"\"\"\n    Generates a path that can be used in a .wd-test file to refer to another file.\n\n    Paths are relative to the .wd-test file.\n    \"\"\"\n    return relative_path(paths.dirname(wd_test_file.short_path), file.short_path)\n\ndef is_in_resources_directory(file):\n    \"\"\"\n    True if a given file is in the resources/ directory of a WPT module.\n    \"\"\"\n    immediate_parent = paths.basename(file.dirname)\n    return immediate_parent == \"resources\"\n\ndef test_case_name(filename):\n    \"\"\"\n    Converts a JS filename to a valid JS identifier for use as a test case name.\n\n    WPT files are named with the convention some-words-with-hyphens.some-suffix.js.\n    We would turn this into someWordsWithHyphensSomeSuffix.\n    \"\"\"\n\n    words = (filename\n        .removesuffix(\".js\")\n        .replace(\".\", \"-\")\n        .replace(\"/\", \"-\")\n        .split(\"-\"))\n\n    return words[0] + \"\".join([word.capitalize() for word in words[1:]])\n"
  },
  {
    "path": "build-releases.sh",
    "content": "#! /bin/bash\nset -euo pipefail\n\nif [[ $(uname -m) == 'x86_64' ]]; then\n  echo \"This _must_ be run on an Apple Silicon machine, since the macOS ARM build cannot be dockerised due to macOS license restrictions\"\n  exit 1\nfi\n\nrm -f workerd-darwin-arm64 workerd-darwin-arm64.gz\nrm -f workerd-linux-arm64 workerd-linux-arm64.gz\n\n# Get the tag associated with the latest release, to ensure parity between binaries\nTAG_NAME=$(curl -sL https://api.github.com/repos/cloudflare/workerd/releases/latest | jq -r \".tag_name\")\n\ngit checkout $TAG_NAME\n\n# Build macOS binary\npnpm exec bazel build --disk_cache=./.bazel-cache --config=release_macos //src/workerd/server:workerd\n\ncp bazel-bin/src/workerd/server/workerd ./workerd-darwin-arm64\n\ndocker buildx build --platform linux/arm64 -f Dockerfile.release -t workerd:$TAG_NAME --target=artifact --output type=local,dest=$(pwd) .\n\nchmod +x workerd*\n\nmv workerd-darwin-arm64 workerd\ngzip -9N workerd\nmv workerd.gz workerd-darwin-arm64.gz\n\nmv workerd-linux-arm64 workerd\ngzip -9N workerd\nmv workerd.gz workerd-linux-arm64.gz\n"
  },
  {
    "path": "codecov.yml",
    "content": "codecov:\n  # Should Codecov wait for all other statuses to pass before sending its status.\n  require_ci_to_pass: true\n\ncoverage:\n  # What precision do you want the coverage value to be\n  precision: 2\n  # Which direction to you want to round the coverage value\n  round: down\n  # The value range where you want the value to be green\n  range: \"70...100\"\n\ncomment:\n  # Only post if coverage drops\n  require_changes: \"coverage_drop\"\n"
  },
  {
    "path": "compile_flags.txt",
    "content": "-std=c++23\n-stdlib=libc++\n-xc++\n-nostdinc\n-Ibazel-bin/external/+new_local_repository+com_cloudflare_lol_html/_virtual_includes/lolhtml\n-Ibazel-bin/external/perfetto+/\n-Iexternal/ada-url+/\n-Iexternal/abseil-cpp+/\n-Iexternal/+http+simdutf/\n-Iexternal/+http+nbytes/include/\n-Iexternal/codspeed_google_benchmark_compat+/include/\n-Iexternal/perfetto+/include/\n-Iexternal/perfetto+/include/perfetto/base/build_configs/bazel/\n-Iexternal/boringssl+/include\n-Iexternal/+http+ncrypto/include\n-isystembazel-bin/external/sqlite3+\n-Isrc\n-isystem/usr/lib/llvm-22/include/c++/v1\n-isystem/usr/lib/llvm-22/lib/clang/22/include\n-isystem/usr/lib/llvm-21/include/c++/v1\n-isystem/usr/lib/llvm-21/lib/clang/21/include\n-isystem/usr/lib/llvm-20/include/c++/v1\n-isystem/usr/lib/llvm-20/lib/clang/20/include\n-isystem/usr/lib/llvm-19/include/c++/v1\n-isystem/usr/lib/llvm-19/lib/clang/19/include\n-isystem/usr/include/x86_64-linux-gnu\n-isystem/usr/include/aarch64-linux-gnu\n-isystem/usr/include\n-isystem/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1\n-isystem/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/17/include\n-isystem/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include\n-isystem/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Kernel.framework/Versions/Current/Headers\n-isystembazel-bin/_virtual_includes/icudata-embed\n-isystembazel-bin/external/+http+capnp-cpp/src\n-isystembazel-bin/external/+http+capnp-cpp/src/capnp/_virtual_includes/capnp\n-isystembazel-bin/external/+http+capnp-cpp/src/capnp/_virtual_includes/capnp-rpc\n-isystembazel-bin/external/+http+capnp-cpp/src/capnp/compat/_virtual_includes/http-over-capnp\n-isystembazel-bin/external/+http+capnp-cpp/src/capnp/compat/_virtual_includes/json\n-isystembazel-bin/external/+http+capnp-cpp/src/kj/_virtual_includes/kj\n-isystembazel-bin/external/+http+capnp-cpp/src/kj/_virtual_includes/kj-async\n-isystembazel-bin/external/+http+capnp-cpp/src/kj/compat/_virtual_includes/kj-brotli\n-isystembazel-bin/external/+http+capnp-cpp/src/kj/compat/_virtual_includes/kj-gzip\n-isystembazel-bin/external/+http+capnp-cpp/src/kj/compat/_virtual_includes/kj-http\n-isystembazel-bin/external/+http+capnp-cpp/src/kj/compat/_virtual_includes/kj-tls\n-isystembazel-bin/external/+http+workerd-cxx/_virtual_includes/core/\n-isystembazel-bin/external/+http+workerd-cxx/kj-rs/_virtual_includes/kj-rs-lib/\n-isystemexternal/+http_archive+v8/include\n-isystemexternal/+http_archive+v8/include/cppgc\n-isystemexternal/+_repo_rules2+v8/include\n-isystemexternal/+_repo_rules2+v8/include/cppgc\n-isystembazel-bin/src\n-isystemexternal/brotli+/c/include\n-isystembazel-bin/external/+git_repository+com_googlesource_chromium_icu/_virtual_includes/icu/third_party/icu/source/common\n-isystemexternal/zlib+\n-isystembazel-bin/src/rust/cxx-integration/_virtual_includes/lib.rs@cxx\n-isystembazel-bin/src/rust/cxx-integration-test/_virtual_includes/lib.rs@cxx\n-isystembazel-bin/src/rust/dns/_virtual_includes/lib.rs@cxx\n-isystembazel-bin/src/rust/kj/_virtual_includes/ffi\n-isystembazel-bin/src/rust/kj/_virtual_includes/http.rs@cxx\n-isystembazel-bin/src/rust/kj/_virtual_includes/io.rs@cxx\n-isystembazel-bin/src/rust/kj/tests/_virtual_includes/lib.rs@cxx\n-isystembazel-bin/src/rust/python-parser/_virtual_includes/lib.rs@cxx\n-isystembazel-bin/src/rust/net/_virtual_includes/lib.rs@cxx\n-isystembazel-bin/src/rust/transpiler/_virtual_includes/lib.rs@cxx\n-isystembazel-bin/src/rust/api/_virtual_includes/lib.rs@cxx\n-isystembazel-bin/src/rust/jsg/_virtual_includes/ffi\n-isystembazel-bin/src/rust/jsg/_virtual_includes/lib.rs@cxx\n-isystembazel-bin/src/rust/jsg/_virtual_includes/v8.rs@cxx\n-isystembazel-bin/src/rust/jsg-test/_virtual_includes/ffi-hdrs\n-isystembazel-bin/src/rust/encoding/_virtual_includes/lib.rs@cxx\n-isystembazel-bin/src/rust/jsg-test/_virtual_includes/lib.rs@cxx\n-isystembazel-bin/src/rust/gen-compile-cache/_virtual_includes/cxx-bridge\n-isystembazel-bin/src/rust/gen-compile-cache/_virtual_includes/gen-compile-cache@cxx\n-D_FORTIFY_SOURCE=1\n-D_LIBCPP_REMOVE_TRANSITIVE_INCLUDES\n-D_LIBCPP_NO_ABI_TAG\n-D_LIBCPP_HAS_NO_INCOMPLETE_PSTL\n-DCAPNP_VERSION=11000\n-DDEBUG\n-DGOOGLE3\n-DHAVE_DLOPEN=0\n-DKJ_HAS_LIBDL\n-DKJ_HAS_OPENSSL\n-DKJ_HAS_ZLIB\n-DKJ_SAVE_ACQUIRED_LOCK_INFO=0\n-DKJ_TRACK_LOCK_BLOCKING=0\n-DSQLITE_ENABLE_FTS5\n-DSQLITE_ENABLE_NORMALIZE\n-DSQLITE_MAX_ALLOCATION_SIZE=16777216\n-DSQLITE_PRINTF_PRECISION_LIMIT=100000\n-DUCONFIG_ONLY_HTML_CONVERSION=1\n-DUNISTR_FROM_CHAR_EXPLICIT=\n-DUNISTR_FROM_STRING_EXPLICIT=\n-DUSE_CHROMIUM_ICU=1\n-DU_CHARSET_IS_UTF8=1\n-DU_COMMON_IMPLEMENTATION\n-DU_ENABLE_DYLOAD=0\n-DU_ENABLE_RESOURCE_TRACING=0\n-DU_ENABLE_TRACING=1\n-DU_I18N_IMPLEMENTATION\n-DU_ICUDATAENTRY_IN_COMMON\n-DU_USING_ICU_NAMESPACE=0\n-DV8_31BIT_SMIS_ON_64BIT_ARCH\n-DV8_ADVANCED_BIGINT_ALGORITHMS\n-DV8_COMPRESS_POINTERS\n-DV8_COMPRESS_POINTERS_IN_SHARED_CAGE\n-DV8_CONCURRENT_MARKING\n-DV8_DEPRECATION_WARNINGS\n-DV8_ENABLE_CHECKS\n-DV8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA\n-DV8_ENABLE_LAZY_SOURCE_POSITIONS\n-DV8_ENABLE_MAGLEV\n-DV8_ENABLE_SPARKPLUG\n-DV8_ENABLE_TURBOFAN\n-DV8_ENABLE_WEBASSEMBLY\n-DV8_HAVE_TARGET_OS\n-DV8_IMMINENT_DEPRECATION_WARNINGS\n-DV8_SHORT_BUILTIN_CALLS\n-DV8_TARGET_ARCH_X64\n-DV8_TARGET_OS_LINUX\n-DV8_TYPED_ARRAY_MAX_SIZE_IN_HEAP=64\n-DWORKERD_USE_PERFETTO\n-DPERFETTO_ENABLE_LEGACY_TRACE_EVENTS=1\n-DWORKERD_ICU_DATA_EMBED\n-Wall\n-Werror=dangling\n-Werror=implicit-fallthrough\n-Werror=return-stack-address\n-Werror=return-type\n-Werror=switch\n-Werror=uninitialized\n-Werror=unreachable-code\n-Werror=unused-function\n-Werror=unused-lambda-capture\n-Werror=unused-variable\n-Wextra\n-Wno-builtin-macro-redefined\n-Wno-free-nonheap-object\n-Wno-missing-field-initializers\n-Wno-sign-compare\n-Wno-unused-parameter\n-Wself-assign\n-Wthread-safety\n-Wunused-but-set-parameter\n-Wunused-function\n-Wunused-lambda-capture\n-Wunused-variable\n-no-canonical-prefixes\n-fbracket-depth=512\n"
  },
  {
    "path": "deps/rust/BUILD.bazel",
    "content": "load(\"@bazel_skylib//lib:selects.bzl\", \"selects\")\nload(\"@bazel_skylib//rules:write_file.bzl\", \"write_file\")\nload(\"@rules_cc//cc:cc_library.bzl\", \"cc_library\")\nload(\"@rules_rust//crate_universe:defs.bzl\", \"crate\", \"crates_vendor\", \"render_config\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_static_library\")\nload(\"cargo.bzl\", \"PACKAGES\")\n\nselects.config_setting_group(\n    name = \"linux_x64\",\n    match_all = [\n        \"@platforms//os:linux\",\n        \"@platforms//cpu:x86_64\",\n    ],\n)\n\nselects.config_setting_group(\n    name = \"linux_arm64\",\n    match_all = [\n        \"@platforms//os:linux\",\n        \"@platforms//cpu:aarch64\",\n    ],\n)\n\nselects.config_setting_group(\n    name = \"macos_x64\",\n    match_all = [\n        \"@platforms//os:macos\",\n        \"@platforms//cpu:x86_64\",\n    ],\n)\n\nselects.config_setting_group(\n    name = \"macos_arm64\",\n    match_all = [\n        \"@platforms//os:macos\",\n        \"@platforms//cpu:aarch64\",\n    ],\n)\n\nselects.config_setting_group(\n    name = \"win_x64\",\n    match_all = [\n        \"@platforms//os:windows\",\n        \"@platforms//cpu:x86_64\",\n    ],\n)\n\nCARGO_BAZEL = select({\n    \":linux_x64\": \"@cargo_bazel_linux_x64//file:downloaded\",\n    \":linux_arm64\": \"@cargo_bazel_linux_arm64//file:downloaded\",\n    \":macos_x64\": \"@cargo_bazel_macos_x64//file:downloaded\",\n    \":macos_arm64\": \"@cargo_bazel_macos_arm64//file:downloaded\",\n    \":win_x64\": \"@cargo_bazel_win_x64//file:downloaded.exe\",\n})\n\n# Generates a repository containing all the crates we reference from our\n# rust workspace\n# To repin crates: bazel run //rust-deps:crates_vendor -- --repin\ncrates_vendor(\n    name = \"crates_vendor\",\n    annotations = {\n        # Provide access to lol-html header, without depending on the entire module.\n        \"lol_html_c_api\": [crate.annotation(\n            extra_aliased_targets = {\n                \"lol_html_c_api_include_lol_html_h\": \"include/lol_html.h\",\n            },\n        )],\n    },\n    cargo_bazel = CARGO_BAZEL,\n    cargo_lockfile = \"//deps/rust:Cargo.lock\",\n    generate_binaries = True,\n    mode = \"remote\",\n    packages = PACKAGES,\n    # Not needed, we have a well-defined set of supported platforms\n    render_config = render_config(\n        generate_cargo_toml_env_vars = False,\n        generate_target_compatible_with = False,\n    ),\n    supported_platform_triples = [\n        \"aarch64-apple-darwin\",\n        \"x86_64-apple-darwin\",\n        \"aarch64-unknown-linux-gnu\",\n        \"x86_64-unknown-linux-gnu\",\n        \"x86_64-pc-windows-msvc\",\n    ],\n)\n\n# Adds missing symbols following Rust mangling change, plus missing system library on Windows.\ncc_library(\n    name = \"runtime\",\n    linkopts = select({\n        \"@platforms//os:windows\": [\n            \"ntdll.lib\",\n        ],\n        \"//conditions:default\": [],\n    }),\n    visibility = [\"//visibility:public\"],\n    deps = [\":empty_lib\"],\n)\n\nwrite_file(\n    name = \"lib_rs\",\n    out = \"lib.rs\",\n    tags = [\"manual\"],\n)\n\nrust_static_library(\n    name = \"empty_lib\",\n    srcs = [\":lib.rs\"],\n    # When stamping is enabled this will be replaced by the corresponding\n    # value in ./bazel-out/volatile-status.txt\n    rustc_env = {\n        \"WORKERD_VERSION\": \"{WORKERD_VERSION}\",\n    },\n    stamp = -1,  # default to bazel --stamp flag\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/BUILD.lolhtml",
    "content": "load(\"@rules_cc//cc:cc_library.bzl\", \"cc_library\")\n\ncc_library(\n    name = \"lolhtml\",\n    hdrs = [\"@crates_vendor//:lol_html_c_api_include_lol_html_h\"],\n    strip_include_prefix = \"include\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@crates_vendor//:lol_html_c_api\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/cargo.bzl",
    "content": "\"\"\"workerd rust crate dependencies analogous to Cargo.toml file.\n\"\"\"\n\nload(\"@rules_rust//crate_universe:defs.bzl\", \"crate\")\nload(\"@workerd-cxx//third-party:cargo.bzl\", WORKERD_CXX_PACKAGES = \"PACKAGES\")\n\n# We prefer single-digit dependencies to stay up to date as much as possible\nPACKAGES = WORKERD_CXX_PACKAGES | {\n    # When adding packages here, please only enable features as needed to keep compile times and\n    # binary sizes bounded.\n    \"ada-url\": crate.spec(version = \"3\"),\n    \"anyhow\": crate.spec(version = \"1\"),\n    \"async-trait\": crate.spec(version = \"0\", default_features = False),\n    \"capnp\": crate.spec(version = \"0\"),\n    \"capnpc\": crate.spec(version = \"0\"),\n    \"capnp-rpc\": crate.spec(version = \"0\"),\n    \"clang-ast\": crate.spec(version = \"0\"),\n    \"clap\": crate.spec(version = \"4\", default_features = False, features = [\"derive\", \"std\", \"help\"]),\n    \"codespan-reporting\": crate.spec(version = \"0\"),\n    \"encoding_rs\": crate.spec(version = \"0\"),\n    \"flate2\": crate.spec(version = \"1\"),\n    \"futures\": crate.spec(version = \"0\"),\n    \"lol_html_c_api\": crate.spec(git = \"https://github.com/cloudflare/lol-html\", tag = \"v2.7.2\"),\n    \"nix\": crate.spec(version = \"0\"),\n    \"pico-args\": crate.spec(version = \"0\"),\n    \"quote\": crate.spec(version = \"1\"),\n    \"ruff_python_ast\": crate.spec(git = \"https://github.com/astral-sh/ruff\", tag = \"0.12.1\"),\n    \"ruff_python_parser\": crate.spec(git = \"https://github.com/astral-sh/ruff\", tag = \"0.12.1\"),\n    # param_extractor depends on unbounded_depth feature\n    \"serde_json\": crate.spec(version = \"1\", features = [\"unbounded_depth\"]),\n    \"serde\": crate.spec(version = \"1\", features = [\"derive\"]),\n    \"thiserror\": crate.spec(version = \"2\"),\n    # tokio is huge, let's enable only features when we actually need them.\n    \"tokio\": crate.spec(version = \"1\", default_features = False, features = [\"net\", \"rt\", \"rt-multi-thread\", \"time\"]),\n    \"tracing\": crate.spec(version = \"0\", default_features = False, features = [\"std\"]),\n    \"swc_common\": crate.spec(version = \"18\"),\n    \"swc_ts_fast_strip\": crate.spec(version = \"43\"),\n}\n"
  },
  {
    "path": "deps/rust/crates/BUILD.ada-url-3.4.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"ada_url\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2024\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=ada-url\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"3.4.2\",\n    deps = [\n        \"@crates_vendor__ada-url-3.4.2//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2024\",\n    pkg_name = \"ada-url\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=ada-url\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"3.4.2\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"@crates_vendor__cc-1.2.57//:cc\",\n        \"@crates_vendor__link_args-0.6.0//:link_args\",\n        \"@crates_vendor__regex-1.12.3//:regex\",\n    ],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.adler2-2.0.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"adler2\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=adler2\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.0.1\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.ahash-0.8.12.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"ahash\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=ahash\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.8.12\",\n    deps = [\n        \"@crates_vendor__ahash-0.8.12//:build_script_build\",\n        \"@crates_vendor__cfg-if-1.0.4//:cfg_if\",\n        \"@crates_vendor__zerocopy-0.8.42//:zerocopy\",\n    ] + select({\n        \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n            \"@crates_vendor__once_cell-1.21.4//:once_cell\",  # cfg(not(all(target_arch = \"arm\", target_os = \"none\")))\n        ],\n        \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n            \"@crates_vendor__once_cell-1.21.4//:once_cell\",  # cfg(not(all(target_arch = \"arm\", target_os = \"none\")))\n        ],\n        \"@rules_rust//rust/platform:x86_64-apple-darwin\": [\n            \"@crates_vendor__once_cell-1.21.4//:once_cell\",  # cfg(not(all(target_arch = \"arm\", target_os = \"none\")))\n        ],\n        \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n            \"@crates_vendor__once_cell-1.21.4//:once_cell\",  # cfg(not(all(target_arch = \"arm\", target_os = \"none\")))\n        ],\n        \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n            \"@crates_vendor__once_cell-1.21.4//:once_cell\",  # cfg(not(all(target_arch = \"arm\", target_os = \"none\")))\n        ],\n        \"//conditions:default\": [],\n    }),\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2018\",\n    pkg_name = \"ahash\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=ahash\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.8.12\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"@crates_vendor__version_check-0.9.5//:version_check\",\n    ],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.aho-corasick-1.1.4.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"aho_corasick\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"perf-literal\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=aho-corasick\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.1.4\",\n    deps = [\n        \"@crates_vendor__memchr-2.8.0//:memchr\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.allocator-api2-0.2.21.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"allocator_api2\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=allocator-api2\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.21\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.anstyle-1.0.14.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"anstyle\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=anstyle\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.14\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.anyhow-1.0.102.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"anyhow\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=anyhow\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.102\",\n    deps = [\n        \"@crates_vendor__anyhow-1.0.102//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    pkg_name = \"anyhow\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=anyhow\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.102\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.ascii-1.1.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"ascii\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2015\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=ascii\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.1.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.ast_node-5.0.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"ast_node\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=ast_node\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"5.0.0\",\n    deps = [\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__swc_macros_common-1.0.1//:swc_macros_common\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.async-trait-0.1.89.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"async_trait\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=async-trait\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.1.89\",\n    deps = [\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.autocfg-1.5.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"autocfg\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2015\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=autocfg\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.5.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.base64-0.22.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"base64\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=base64\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.22.1\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.base64-simd-0.8.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"base64_simd\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"default\",\n        \"detect\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=base64-simd\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.8.0\",\n    deps = [\n        \"@crates_vendor__outref-0.5.2//:outref\",\n        \"@crates_vendor__vsimd-0.8.0//:vsimd\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\npackage(default_visibility = [\"//visibility:public\"])\n\nexports_files(\n    [\n        \"cargo-bazel.json\",\n        \"crates.bzl\",\n        \"defs.bzl\",\n    ] + glob(\n        include = [\"*.bazel\"],\n        allow_empty = True,\n    ),\n)\n\nfilegroup(\n    name = \"srcs\",\n    srcs = glob(\n        include = [\n            \"*.bazel\",\n            \"*.bzl\",\n        ],\n        allow_empty = True,\n    ),\n)\n\n# Workspace Member Dependencies\nalias(\n    name = \"ada-url-3.4.2\",\n    actual = \"@crates_vendor__ada-url-3.4.2//:ada_url\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"ada-url\",\n    actual = \"@crates_vendor__ada-url-3.4.2//:ada_url\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"anyhow-1.0.102\",\n    actual = \"@crates_vendor__anyhow-1.0.102//:anyhow\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"anyhow\",\n    actual = \"@crates_vendor__anyhow-1.0.102//:anyhow\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"async-trait-0.1.89\",\n    actual = \"@crates_vendor__async-trait-0.1.89//:async_trait\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"async-trait\",\n    actual = \"@crates_vendor__async-trait-0.1.89//:async_trait\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"capnp-0.25.2\",\n    actual = \"@crates_vendor__capnp-0.25.2//:capnp\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"capnp\",\n    actual = \"@crates_vendor__capnp-0.25.2//:capnp\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"capnp-rpc-0.25.0\",\n    actual = \"@crates_vendor__capnp-rpc-0.25.0//:capnp_rpc\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"capnp-rpc\",\n    actual = \"@crates_vendor__capnp-rpc-0.25.0//:capnp_rpc\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"capnpc-0.25.0\",\n    actual = \"@crates_vendor__capnpc-0.25.0//:capnpc\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"capnpc\",\n    actual = \"@crates_vendor__capnpc-0.25.0//:capnpc\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"cc-1.2.57\",\n    actual = \"@crates_vendor__cc-1.2.57//:cc\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"cc\",\n    actual = \"@crates_vendor__cc-1.2.57//:cc\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"clang-ast-0.1.35\",\n    actual = \"@crates_vendor__clang-ast-0.1.35//:clang_ast\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"clang-ast\",\n    actual = \"@crates_vendor__clang-ast-0.1.35//:clang_ast\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"clap-4.6.0\",\n    actual = \"@crates_vendor__clap-4.6.0//:clap\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"clap\",\n    actual = \"@crates_vendor__clap-4.6.0//:clap\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"codespan-reporting-0.13.1\",\n    actual = \"@crates_vendor__codespan-reporting-0.13.1//:codespan_reporting\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"codespan-reporting\",\n    actual = \"@crates_vendor__codespan-reporting-0.13.1//:codespan_reporting\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"encoding_rs-0.8.35\",\n    actual = \"@crates_vendor__encoding_rs-0.8.35//:encoding_rs\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"encoding_rs\",\n    actual = \"@crates_vendor__encoding_rs-0.8.35//:encoding_rs\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"flate2-1.1.9\",\n    actual = \"@crates_vendor__flate2-1.1.9//:flate2\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"flate2\",\n    actual = \"@crates_vendor__flate2-1.1.9//:flate2\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"foldhash-0.2.0\",\n    actual = \"@crates_vendor__foldhash-0.2.0//:foldhash\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"foldhash\",\n    actual = \"@crates_vendor__foldhash-0.2.0//:foldhash\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"futures-0.3.32\",\n    actual = \"@crates_vendor__futures-0.3.32//:futures\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"futures\",\n    actual = \"@crates_vendor__futures-0.3.32//:futures\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"lol_html_c_api-1.3.1\",\n    actual = \"@crates_vendor__lol_html_c_api-1.3.1//:lolhtml\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"lol_html_c_api\",\n    actual = \"@crates_vendor__lol_html_c_api-1.3.1//:lolhtml\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"lol_html_c_api_include_lol_html_h\",\n    actual = \"@crates_vendor__lol_html_c_api-1.3.1//:include/lol_html.h\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"nix-0.31.2\",\n    actual = \"@crates_vendor__nix-0.31.2//:nix\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"nix\",\n    actual = \"@crates_vendor__nix-0.31.2//:nix\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"pico-args-0.5.0\",\n    actual = \"@crates_vendor__pico-args-0.5.0//:pico_args\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"pico-args\",\n    actual = \"@crates_vendor__pico-args-0.5.0//:pico_args\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"proc-macro2-1.0.106\",\n    actual = \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"proc-macro2\",\n    actual = \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"quote-1.0.45\",\n    actual = \"@crates_vendor__quote-1.0.45//:quote\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"quote\",\n    actual = \"@crates_vendor__quote-1.0.45//:quote\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"ruff_python_ast-0.0.0\",\n    actual = \"@crates_vendor__ruff_python_ast-0.0.0//:ruff_python_ast\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"ruff_python_ast\",\n    actual = \"@crates_vendor__ruff_python_ast-0.0.0//:ruff_python_ast\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"ruff_python_parser-0.0.0\",\n    actual = \"@crates_vendor__ruff_python_parser-0.0.0//:ruff_python_parser\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"ruff_python_parser\",\n    actual = \"@crates_vendor__ruff_python_parser-0.0.0//:ruff_python_parser\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"rustversion-1.0.22\",\n    actual = \"@crates_vendor__rustversion-1.0.22//:rustversion\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"rustversion\",\n    actual = \"@crates_vendor__rustversion-1.0.22//:rustversion\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"scratch-1.0.9\",\n    actual = \"@crates_vendor__scratch-1.0.9//:scratch\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"scratch\",\n    actual = \"@crates_vendor__scratch-1.0.9//:scratch\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"serde-1.0.228\",\n    actual = \"@crates_vendor__serde-1.0.228//:serde\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"serde\",\n    actual = \"@crates_vendor__serde-1.0.228//:serde\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"serde_json-1.0.149\",\n    actual = \"@crates_vendor__serde_json-1.0.149//:serde_json\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"serde_json\",\n    actual = \"@crates_vendor__serde_json-1.0.149//:serde_json\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"static_assertions-1.1.0\",\n    actual = \"@crates_vendor__static_assertions-1.1.0//:static_assertions\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"static_assertions\",\n    actual = \"@crates_vendor__static_assertions-1.1.0//:static_assertions\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"swc_common-18.0.1\",\n    actual = \"@crates_vendor__swc_common-18.0.1//:swc_common\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"swc_common\",\n    actual = \"@crates_vendor__swc_common-18.0.1//:swc_common\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"swc_ts_fast_strip-43.0.0\",\n    actual = \"@crates_vendor__swc_ts_fast_strip-43.0.0//:swc_ts_fast_strip\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"swc_ts_fast_strip\",\n    actual = \"@crates_vendor__swc_ts_fast_strip-43.0.0//:swc_ts_fast_strip\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"syn-2.0.117\",\n    actual = \"@crates_vendor__syn-2.0.117//:syn\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"syn\",\n    actual = \"@crates_vendor__syn-2.0.117//:syn\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"thiserror-2.0.18\",\n    actual = \"@crates_vendor__thiserror-2.0.18//:thiserror\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"thiserror\",\n    actual = \"@crates_vendor__thiserror-2.0.18//:thiserror\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"tokio-1.50.0\",\n    actual = \"@crates_vendor__tokio-1.50.0//:tokio\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"tokio\",\n    actual = \"@crates_vendor__tokio-1.50.0//:tokio\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"tracing-0.1.44\",\n    actual = \"@crates_vendor__tracing-0.1.44//:tracing\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"tracing\",\n    actual = \"@crates_vendor__tracing-0.1.44//:tracing\",\n    tags = [\"manual\"],\n)\n\n# Binaries\nalias(\n    name = \"capnpc__capnpc-rust\",\n    actual = \"@crates_vendor__capnpc-0.25.0//:capnpc-rust__bin\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"capnpc__capnpc-rust-bootstrap\",\n    actual = \"@crates_vendor__capnpc-0.25.0//:capnpc-rust-bootstrap__bin\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"clap__stdio-fixture\",\n    actual = \"@crates_vendor__clap-4.6.0//:stdio-fixture__bin\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"phf_generator-0.11.3__gen_hash_test\",\n    actual = \"@crates_vendor__phf_generator-0.11.3//:gen_hash_test__bin\",\n    tags = [\"manual\"],\n)\n\nalias(\n    name = \"phf_generator-0.13.1__gen_hash_test\",\n    actual = \"@crates_vendor__phf_generator-0.13.1//:gen_hash_test__bin\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.better_scoped_tls-1.0.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"better_scoped_tls\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=better_scoped_tls\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.1\",\n    deps = [\n        \"@crates_vendor__scoped-tls-1.0.1//:scoped_tls\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.bitflags-2.11.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"bitflags\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=bitflags\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.11.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.bitvec-1.0.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"bitvec\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"atomic\",\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=bitvec\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.1\",\n    deps = [\n        \"@crates_vendor__funty-2.0.0//:funty\",\n        \"@crates_vendor__radium-0.7.0//:radium\",\n        \"@crates_vendor__tap-1.0.1//:tap\",\n        \"@crates_vendor__wyz-0.5.1//:wyz\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.block-buffer-0.10.4.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"block_buffer\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=block-buffer\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.10.4\",\n    deps = [\n        \"@crates_vendor__generic-array-0.14.7//:generic_array\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.bstr-1.12.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"bstr\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"default\",\n        \"std\",\n        \"unicode\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=bstr\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.12.1\",\n    deps = [\n        \"@crates_vendor__memchr-2.8.0//:memchr\",\n        \"@crates_vendor__regex-automata-0.4.14//:regex_automata\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.bumpalo-3.20.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"bumpalo\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"allocator-api2\",\n        \"default\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=bumpalo\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"3.20.2\",\n    deps = [\n        \"@crates_vendor__allocator-api2-0.2.21//:allocator_api2\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.bytes-1.11.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"bytes\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=bytes\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.11.1\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.bytes-str-0.2.7.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"bytes_str\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"serde\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=bytes-str\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.7\",\n    deps = [\n        \"@crates_vendor__bytes-1.11.1//:bytes\",\n        \"@crates_vendor__serde-1.0.228//:serde\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.capnp-0.25.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"capnp\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=capnp\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.25.2\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.capnp-futures-0.25.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"capnp_futures\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=capnp-futures\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.25.2\",\n    deps = [\n        \"@crates_vendor__capnp-0.25.2//:capnp\",\n        \"@crates_vendor__futures-channel-0.3.32//:futures_channel\",\n        \"@crates_vendor__futures-util-0.3.32//:futures_util\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.capnp-rpc-0.25.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"capnp_rpc\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=capnp-rpc\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.25.0\",\n    deps = [\n        \"@crates_vendor__capnp-0.25.2//:capnp\",\n        \"@crates_vendor__capnp-futures-0.25.2//:capnp_futures\",\n        \"@crates_vendor__futures-0.3.32//:futures\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.capnpc-0.25.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\n    \"@rules_rust//rust:defs.bzl\",\n    \"rust_binary\",\n    \"rust_library\",\n)\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"capnpc\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=capnpc\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.25.0\",\n    deps = [\n        \"@crates_vendor__capnp-0.25.2//:capnp\",\n    ],\n)\n\nrust_binary(\n    name = \"capnpc-rust__bin\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/capnpc-rust.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=capnpc\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.25.0\",\n    deps = [\n        \":capnpc\",\n        \"@crates_vendor__capnp-0.25.2//:capnp\",\n    ],\n)\n\nrust_binary(\n    name = \"capnpc-rust-bootstrap__bin\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/capnpc-rust-bootstrap.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=capnpc\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.25.0\",\n    deps = [\n        \":capnpc\",\n        \"@crates_vendor__capnp-0.25.2//:capnp\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.castaway-0.2.4.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"castaway\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    proc_macro_deps = [\n        \"@crates_vendor__rustversion-1.0.22//:rustversion\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=castaway\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.4\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.cc-1.2.57.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"cc\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"parallel\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=cc\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.2.57\",\n    deps = [\n        \"@crates_vendor__find-msvc-tools-0.1.9//:find_msvc_tools\",\n        \"@crates_vendor__jobserver-0.1.34//:jobserver\",\n        \"@crates_vendor__shlex-1.3.0//:shlex\",\n    ] + select({\n        \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # aarch64-apple-darwin\n        ],\n        \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # aarch64-unknown-linux-gnu\n        ],\n        \"@rules_rust//rust/platform:x86_64-apple-darwin\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # x86_64-apple-darwin\n        ],\n        \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # x86_64-unknown-linux-gnu\n        ],\n        \"//conditions:default\": [],\n    }),\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.cfg-if-1.0.4.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"cfg_if\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=cfg-if\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.4\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.cfg_aliases-0.2.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"cfg_aliases\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=cfg_aliases\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.1\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.clang-ast-0.1.35.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"clang_ast\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=clang-ast\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.1.35\",\n    deps = [\n        \"@crates_vendor__foldhash-0.2.0//:foldhash\",\n        \"@crates_vendor__serde_core-1.0.228//:serde_core\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.clap-4.6.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\n    \"@rules_rust//rust:defs.bzl\",\n    \"rust_binary\",\n    \"rust_library\",\n)\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"clap\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"derive\",\n        \"help\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2024\",\n    proc_macro_deps = [\n        \"@crates_vendor__clap_derive-4.6.0//:clap_derive\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=clap\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"4.6.0\",\n    deps = [\n        \"@crates_vendor__clap_builder-4.6.0//:clap_builder\",\n    ],\n)\n\nrust_binary(\n    name = \"stdio-fixture__bin\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"derive\",\n        \"help\",\n        \"std\",\n    ],\n    crate_root = \"src/bin/stdio-fixture.rs\",\n    edition = \"2024\",\n    proc_macro_deps = [\n        \"@crates_vendor__clap_derive-4.6.0//:clap_derive\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=clap\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"4.6.0\",\n    deps = [\n        \":clap\",\n        \"@crates_vendor__clap_builder-4.6.0//:clap_builder\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.clap_builder-4.6.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"clap_builder\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"help\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2024\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=clap_builder\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"4.6.0\",\n    deps = [\n        \"@crates_vendor__anstyle-1.0.14//:anstyle\",\n        \"@crates_vendor__clap_lex-1.1.0//:clap_lex\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.clap_derive-4.6.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"clap_derive\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2024\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=clap_derive\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"4.6.0\",\n    deps = [\n        \"@crates_vendor__heck-0.5.0//:heck\",\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.clap_lex-1.1.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"clap_lex\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2024\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=clap_lex\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.1.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.codespan-reporting-0.13.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"codespan_reporting\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n        \"termcolor\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=codespan-reporting\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.13.1\",\n    deps = [\n        \"@crates_vendor__termcolor-1.4.1//:termcolor\",\n        \"@crates_vendor__unicode-width-0.2.2//:unicode_width\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.compact_str-0.7.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"compact_str\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=compact_str\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.7.1\",\n    deps = [\n        \"@crates_vendor__castaway-0.2.4//:castaway\",\n        \"@crates_vendor__cfg-if-1.0.4//:cfg_if\",\n        \"@crates_vendor__itoa-1.0.17//:itoa\",\n        \"@crates_vendor__ryu-1.0.23//:ryu\",\n        \"@crates_vendor__static_assertions-1.1.0//:static_assertions\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.compact_str-0.9.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"compact_str\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__rustversion-1.0.22//:rustversion\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=compact_str\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.9.0\",\n    deps = [\n        \"@crates_vendor__castaway-0.2.4//:castaway\",\n        \"@crates_vendor__cfg-if-1.0.4//:cfg_if\",\n        \"@crates_vendor__itoa-1.0.17//:itoa\",\n        \"@crates_vendor__ryu-1.0.23//:ryu\",\n        \"@crates_vendor__static_assertions-1.1.0//:static_assertions\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.cpufeatures-0.2.17.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"cpufeatures\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=cpufeatures\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.17\",\n    deps = select({\n        \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(all(target_arch = \"aarch64\", target_vendor = \"apple\"))\n        ],\n        \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(all(target_arch = \"aarch64\", target_os = \"linux\"))\n        ],\n        \"//conditions:default\": [],\n    }),\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.crc32fast-1.5.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"crc32fast\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=crc32fast\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.5.0\",\n    deps = [\n        \"@crates_vendor__cfg-if-1.0.4//:cfg_if\",\n        \"@crates_vendor__crc32fast-1.5.0//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    pkg_name = \"crc32fast\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=crc32fast\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.5.0\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.crypto-common-0.1.7.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"crypto_common\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=crypto-common\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.1.7\",\n    deps = [\n        \"@crates_vendor__generic-array-0.14.7//:generic_array\",\n        \"@crates_vendor__typenum-1.19.0//:typenum\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.cssparser-0.36.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"cssparser\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    proc_macro_deps = [\n        \"@crates_vendor__cssparser-macros-0.6.1//:cssparser_macros\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=cssparser\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.36.0\",\n    deps = [\n        \"@crates_vendor__dtoa-short-0.3.5//:dtoa_short\",\n        \"@crates_vendor__itoa-1.0.17//:itoa\",\n        \"@crates_vendor__phf-0.13.1//:phf\",\n        \"@crates_vendor__smallvec-1.15.1//:smallvec\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.cssparser-macros-0.6.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"cssparser_macros\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=cssparser-macros\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.6.1\",\n    deps = [\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.data-encoding-2.10.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"data_encoding\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=data-encoding\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.10.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.debugid-0.8.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"debugid\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"serde\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=debugid\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.8.0\",\n    deps = [\n        \"@crates_vendor__serde-1.0.228//:serde\",\n        \"@crates_vendor__uuid-1.22.0//:uuid\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.derive_more-2.1.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"derive_more\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"add\",\n        \"add_assign\",\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__derive_more-impl-2.1.1//:derive_more_impl\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=derive_more\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.1.1\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.derive_more-impl-2.1.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"derive_more_impl\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"add\",\n        \"add_assign\",\n        \"default\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=derive_more-impl\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.1.1\",\n    deps = [\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.digest-0.10.7.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"digest\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"block-buffer\",\n        \"core-api\",\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=digest\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.10.7\",\n    deps = [\n        \"@crates_vendor__block-buffer-0.10.4//:block_buffer\",\n        \"@crates_vendor__crypto-common-0.1.7//:crypto_common\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.displaydoc-0.2.5.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"displaydoc\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=displaydoc\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.5\",\n    deps = [\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.dragonbox_ecma-0.1.12.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"dragonbox_ecma\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"ecma\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=dragonbox_ecma\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.1.12\",\n    deps = [\n        \"@crates_vendor__dragonbox_ecma-0.1.12//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"ecma\",\n    ],\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    pkg_name = \"dragonbox_ecma\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=dragonbox_ecma\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.1.12\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.dtoa-1.0.11.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"dtoa\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=dtoa\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.11\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.dtoa-short-0.3.5.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"dtoa_short\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2015\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=dtoa-short\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.3.5\",\n    deps = [\n        \"@crates_vendor__dtoa-1.0.11//:dtoa\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.either-1.15.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"either\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n        \"use_std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=either\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.15.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.embedded-io-0.7.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"embedded_io\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=embedded-io\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.7.1\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.encoding_rs-0.8.35.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"encoding_rs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"default\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=encoding_rs\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.8.35\",\n    deps = [\n        \"@crates_vendor__cfg-if-1.0.4//:cfg_if\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.equivalent-1.0.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"equivalent\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2015\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=equivalent\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.2\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.fastrand-2.3.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"fastrand\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=fastrand\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.3.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.find-msvc-tools-0.1.9.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"find_msvc_tools\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=find-msvc-tools\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.1.9\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.flate2-1.1.9.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"flate2\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"any_impl\",\n        \"default\",\n        \"miniz_oxide\",\n        \"rust_backend\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=flate2\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.1.9\",\n    deps = [\n        \"@crates_vendor__crc32fast-1.5.0//:crc32fast\",\n        \"@crates_vendor__miniz_oxide-0.8.9//:miniz_oxide\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.foldhash-0.2.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"foldhash\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=foldhash\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.form_urlencoded-1.2.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"form_urlencoded\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=form_urlencoded\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.2.2\",\n    deps = [\n        \"@crates_vendor__percent-encoding-2.3.2//:percent_encoding\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.from_variant-3.0.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"from_variant\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=from_variant\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"3.0.0\",\n    deps = [\n        \"@crates_vendor__swc_macros_common-1.0.1//:swc_macros_common\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.funty-2.0.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"funty\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=funty\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.0.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.futures-0.3.32.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"futures\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"async-await\",\n        \"default\",\n        \"executor\",\n        \"futures-executor\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=futures\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.3.32\",\n    deps = [\n        \"@crates_vendor__futures-channel-0.3.32//:futures_channel\",\n        \"@crates_vendor__futures-core-0.3.32//:futures_core\",\n        \"@crates_vendor__futures-executor-0.3.32//:futures_executor\",\n        \"@crates_vendor__futures-io-0.3.32//:futures_io\",\n        \"@crates_vendor__futures-sink-0.3.32//:futures_sink\",\n        \"@crates_vendor__futures-task-0.3.32//:futures_task\",\n        \"@crates_vendor__futures-util-0.3.32//:futures_util\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.futures-channel-0.3.32.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"futures_channel\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"futures-sink\",\n        \"sink\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=futures-channel\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.3.32\",\n    deps = [\n        \"@crates_vendor__futures-core-0.3.32//:futures_core\",\n        \"@crates_vendor__futures-sink-0.3.32//:futures_sink\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.futures-core-0.3.32.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"futures_core\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=futures-core\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.3.32\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.futures-executor-0.3.32.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"futures_executor\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=futures-executor\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.3.32\",\n    deps = [\n        \"@crates_vendor__futures-core-0.3.32//:futures_core\",\n        \"@crates_vendor__futures-task-0.3.32//:futures_task\",\n        \"@crates_vendor__futures-util-0.3.32//:futures_util\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.futures-io-0.3.32.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"futures_io\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=futures-io\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.3.32\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.futures-macro-0.3.32.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"futures_macro\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=futures-macro\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.3.32\",\n    deps = [\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.futures-sink-0.3.32.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"futures_sink\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=futures-sink\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.3.32\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.futures-task-0.3.32.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"futures_task\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=futures-task\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.3.32\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.futures-util-0.3.32.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"futures_util\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"async-await\",\n        \"async-await-macro\",\n        \"channel\",\n        \"futures-channel\",\n        \"futures-io\",\n        \"futures-macro\",\n        \"futures-sink\",\n        \"io\",\n        \"memchr\",\n        \"sink\",\n        \"slab\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    proc_macro_deps = [\n        \"@crates_vendor__futures-macro-0.3.32//:futures_macro\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=futures-util\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.3.32\",\n    deps = [\n        \"@crates_vendor__futures-channel-0.3.32//:futures_channel\",\n        \"@crates_vendor__futures-core-0.3.32//:futures_core\",\n        \"@crates_vendor__futures-io-0.3.32//:futures_io\",\n        \"@crates_vendor__futures-sink-0.3.32//:futures_sink\",\n        \"@crates_vendor__futures-task-0.3.32//:futures_task\",\n        \"@crates_vendor__memchr-2.8.0//:memchr\",\n        \"@crates_vendor__pin-project-lite-0.2.17//:pin_project_lite\",\n        \"@crates_vendor__slab-0.4.12//:slab\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.generic-array-0.14.7.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"generic_array\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"more_lengths\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2015\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=generic-array\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.14.7\",\n    deps = [\n        \"@crates_vendor__generic-array-0.14.7//:build_script_build\",\n        \"@crates_vendor__typenum-1.19.0//:typenum\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"more_lengths\",\n    ],\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2015\",\n    pkg_name = \"generic-array\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=generic-array\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.14.7\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"@crates_vendor__version_check-0.9.5//:version_check\",\n    ],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.getopts-0.2.24.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"getopts\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"unicode\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=getopts\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.24\",\n    deps = [\n        \"@crates_vendor__unicode-width-0.2.2//:unicode_width\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.getrandom-0.2.17.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"getrandom\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=getrandom\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.17\",\n    deps = [\n        \"@crates_vendor__cfg-if-1.0.4//:cfg_if\",\n    ] + select({\n        \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(unix)\n        ],\n        \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(unix)\n        ],\n        \"@rules_rust//rust/platform:x86_64-apple-darwin\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(unix)\n        ],\n        \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(unix)\n        ],\n        \"//conditions:default\": [],\n    }),\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.getrandom-0.3.4.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"getrandom\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=getrandom\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.3.4\",\n    deps = [\n        \"@crates_vendor__cfg-if-1.0.4//:cfg_if\",\n        \"@crates_vendor__getrandom-0.3.4//:build_script_build\",\n    ] + select({\n        \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(any(target_os = \"macos\", target_os = \"openbsd\", target_os = \"vita\", target_os = \"emscripten\"))\n        ],\n        \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(all(any(target_os = \"linux\", target_os = \"android\"), not(any(all(target_os = \"linux\", target_env = \"\"), getrandom_backend = \"custom\", getrandom_backend = \"linux_raw\", getrandom_backend = \"rdrand\", getrandom_backend = \"rndr\"))))\n        ],\n        \"@rules_rust//rust/platform:x86_64-apple-darwin\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(any(target_os = \"macos\", target_os = \"openbsd\", target_os = \"vita\", target_os = \"emscripten\"))\n        ],\n        \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(all(any(target_os = \"linux\", target_os = \"android\"), not(any(all(target_os = \"linux\", target_env = \"\"), getrandom_backend = \"custom\", getrandom_backend = \"linux_raw\", getrandom_backend = \"rdrand\", getrandom_backend = \"rndr\"))))\n        ],\n        \"//conditions:default\": [],\n    }),\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"std\",\n    ],\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    pkg_name = \"getrandom\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=getrandom\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.3.4\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.hashbrown-0.14.5.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"hashbrown\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"ahash\",\n        \"allocator-api2\",\n        \"default\",\n        \"inline-more\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=hashbrown\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.14.5\",\n    deps = [\n        \"@crates_vendor__ahash-0.8.12//:ahash\",\n        \"@crates_vendor__allocator-api2-0.2.21//:allocator_api2\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.hashbrown-0.16.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"hashbrown\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"allocator-api2\",\n        \"default\",\n        \"default-hasher\",\n        \"equivalent\",\n        \"inline-more\",\n        \"raw-entry\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=hashbrown\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.16.1\",\n    deps = [\n        \"@crates_vendor__allocator-api2-0.2.21//:allocator_api2\",\n        \"@crates_vendor__equivalent-1.0.2//:equivalent\",\n        \"@crates_vendor__foldhash-0.2.0//:foldhash\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.heck-0.5.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"heck\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=heck\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.5.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.hermit-abi-0.5.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"hermit_abi\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=hermit-abi\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.5.2\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.hstr-3.0.4.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"hstr\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"serde\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=hstr\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"3.0.4\",\n    deps = [\n        \"@crates_vendor__hashbrown-0.14.5//:hashbrown\",\n        \"@crates_vendor__new_debug_unreachable-1.0.6//:debug_unreachable\",\n        \"@crates_vendor__once_cell-1.21.4//:once_cell\",\n        \"@crates_vendor__rustc-hash-2.1.1//:rustc_hash\",\n        \"@crates_vendor__serde-1.0.228//:serde\",\n        \"@crates_vendor__triomphe-0.1.15//:triomphe\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.icu_collections-2.1.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"icu_collections\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__displaydoc-0.2.5//:displaydoc\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=icu_collections\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.1.1\",\n    deps = [\n        \"@crates_vendor__potential_utf-0.1.4//:potential_utf\",\n        \"@crates_vendor__yoke-0.8.1//:yoke\",\n        \"@crates_vendor__zerofrom-0.1.6//:zerofrom\",\n        \"@crates_vendor__zerovec-0.11.5//:zerovec\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.icu_locale_core-2.1.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"icu_locale_core\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"zerovec\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__displaydoc-0.2.5//:displaydoc\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=icu_locale_core\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.1.1\",\n    deps = [\n        \"@crates_vendor__litemap-0.8.1//:litemap\",\n        \"@crates_vendor__tinystr-0.8.2//:tinystr\",\n        \"@crates_vendor__writeable-0.6.2//:writeable\",\n        \"@crates_vendor__zerovec-0.11.5//:zerovec\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.icu_normalizer-2.1.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"icu_normalizer\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"compiled_data\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=icu_normalizer\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.1.1\",\n    deps = [\n        \"@crates_vendor__icu_collections-2.1.1//:icu_collections\",\n        \"@crates_vendor__icu_normalizer_data-2.1.1//:icu_normalizer_data\",\n        \"@crates_vendor__icu_provider-2.1.1//:icu_provider\",\n        \"@crates_vendor__smallvec-1.15.1//:smallvec\",\n        \"@crates_vendor__zerovec-0.11.5//:zerovec\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.icu_normalizer_data-2.1.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"icu_normalizer_data\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=icu_normalizer_data\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.1.1\",\n    deps = [\n        \"@crates_vendor__icu_normalizer_data-2.1.1//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    pkg_name = \"icu_normalizer_data\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=icu_normalizer_data\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.1.1\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.icu_properties-2.1.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"icu_properties\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"compiled_data\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=icu_properties\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.1.2\",\n    deps = [\n        \"@crates_vendor__icu_collections-2.1.1//:icu_collections\",\n        \"@crates_vendor__icu_locale_core-2.1.1//:icu_locale_core\",\n        \"@crates_vendor__icu_properties_data-2.1.2//:icu_properties_data\",\n        \"@crates_vendor__icu_provider-2.1.1//:icu_provider\",\n        \"@crates_vendor__zerotrie-0.2.3//:zerotrie\",\n        \"@crates_vendor__zerovec-0.11.5//:zerovec\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.icu_properties_data-2.1.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"icu_properties_data\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=icu_properties_data\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.1.2\",\n    deps = [\n        \"@crates_vendor__icu_properties_data-2.1.2//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    pkg_name = \"icu_properties_data\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=icu_properties_data\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.1.2\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.icu_provider-2.1.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"icu_provider\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"baked\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__displaydoc-0.2.5//:displaydoc\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=icu_provider\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.1.1\",\n    deps = [\n        \"@crates_vendor__icu_locale_core-2.1.1//:icu_locale_core\",\n        \"@crates_vendor__writeable-0.6.2//:writeable\",\n        \"@crates_vendor__yoke-0.8.1//:yoke\",\n        \"@crates_vendor__zerofrom-0.1.6//:zerofrom\",\n        \"@crates_vendor__zerotrie-0.2.3//:zerotrie\",\n        \"@crates_vendor__zerovec-0.11.5//:zerovec\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.idna-1.1.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"idna\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"compiled_data\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=idna\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.1.0\",\n    deps = [\n        \"@crates_vendor__idna_adapter-1.2.1//:idna_adapter\",\n        \"@crates_vendor__smallvec-1.15.1//:smallvec\",\n        \"@crates_vendor__utf8_iter-1.0.4//:utf8_iter\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.idna_adapter-1.2.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"idna_adapter\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"compiled_data\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=idna_adapter\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.2.1\",\n    deps = [\n        \"@crates_vendor__icu_normalizer-2.1.1//:icu_normalizer\",\n        \"@crates_vendor__icu_properties-2.1.2//:icu_properties\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.if_chain-1.0.3.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"if_chain\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2015\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=if_chain\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.3\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.indexmap-2.13.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"indexmap\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=indexmap\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.13.0\",\n    deps = [\n        \"@crates_vendor__equivalent-1.0.2//:equivalent\",\n        \"@crates_vendor__hashbrown-0.16.1//:hashbrown\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.is-macro-0.3.7.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"is_macro\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=is-macro\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.3.7\",\n    deps = [\n        \"@crates_vendor__heck-0.5.0//:heck\",\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.itertools-0.14.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"itertools\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"use_alloc\",\n        \"use_std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=itertools\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.14.0\",\n    deps = [\n        \"@crates_vendor__either-1.15.0//:either\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.itoa-1.0.17.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"itoa\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=itoa\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.17\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.jobserver-0.1.34.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"jobserver\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=jobserver\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.1.34\",\n    deps = select({\n        \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(unix)\n        ],\n        \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(unix)\n        ],\n        \"@rules_rust//rust/platform:x86_64-apple-darwin\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(unix)\n        ],\n        \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n            \"@crates_vendor__getrandom-0.3.4//:getrandom\",  # cfg(windows)\n        ],\n        \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(unix)\n        ],\n        \"//conditions:default\": [],\n    }),\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.js-sys-0.3.91.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"js_sys\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=js-sys\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.3.91\",\n    deps = [\n        \"@crates_vendor__once_cell-1.21.4//:once_cell\",\n        \"@crates_vendor__wasm-bindgen-0.2.114//:wasm_bindgen\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.libc-0.2.183.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"libc\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"extra_traits\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=libc\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.183\",\n    deps = [\n        \"@crates_vendor__libc-0.2.183//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"extra_traits\",\n        \"std\",\n    ],\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    pkg_name = \"libc\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=libc\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.183\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.link_args-0.6.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"link_args\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=link_args\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.6.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.litemap-0.8.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"litemap\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=litemap\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.8.1\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.log-0.4.29.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"log\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=log\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.4.29\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.lol_html-2.7.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"lol_html\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2024\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=lol_html\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.7.2\",\n    deps = [\n        \"@crates_vendor__bitflags-2.11.0//:bitflags\",\n        \"@crates_vendor__cfg-if-1.0.4//:cfg_if\",\n        \"@crates_vendor__cssparser-0.36.0//:cssparser\",\n        \"@crates_vendor__encoding_rs-0.8.35//:encoding_rs\",\n        \"@crates_vendor__foldhash-0.2.0//:foldhash\",\n        \"@crates_vendor__hashbrown-0.16.1//:hashbrown\",\n        \"@crates_vendor__memchr-2.8.0//:memchr\",\n        \"@crates_vendor__mime-0.3.17//:mime\",\n        \"@crates_vendor__precomputed-hash-0.1.1//:precomputed_hash\",\n        \"@crates_vendor__selectors-0.33.0//:selectors\",\n        \"@crates_vendor__thiserror-2.0.18//:thiserror\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.lol_html_c_api-1.3.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"lolhtml\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"capi\",\n        \"default\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2024\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=lol_html_c_api\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.3.1\",\n    deps = [\n        \"@crates_vendor__encoding_rs-0.8.35//:encoding_rs\",\n        \"@crates_vendor__libc-0.2.183//:libc\",\n        \"@crates_vendor__lol_html-2.7.2//:lol_html\",\n        \"@crates_vendor__lol_html_c_api-1.3.1//:build_script_build\",\n        \"@crates_vendor__thiserror-2.0.18//:thiserror\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"capi\",\n        \"default\",\n    ],\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2024\",\n    links = \"lolhtml\",\n    pkg_name = \"lol_html_c_api\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=lol_html_c_api\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.3.1\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.memchr-2.8.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"memchr\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=memchr\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.8.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.mime-0.3.17.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"mime\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2015\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=mime\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.3.17\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.miniz_oxide-0.8.9.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"miniz_oxide\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"simd\",\n        \"simd-adler32\",\n        \"with-alloc\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=miniz_oxide\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.8.9\",\n    deps = [\n        \"@crates_vendor__adler2-2.0.1//:adler2\",\n        \"@crates_vendor__simd-adler32-0.3.8//:simd_adler32\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.mio-1.1.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"mio\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"net\",\n        \"os-ext\",\n        \"os-poll\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=mio\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.1.1\",\n    deps = select({\n        \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(unix)\n        ],\n        \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(unix)\n        ],\n        \"@rules_rust//rust/platform:x86_64-apple-darwin\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(unix)\n        ],\n        \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n            \"@crates_vendor__windows-sys-0.61.2//:windows_sys\",  # cfg(windows)\n        ],\n        \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(unix)\n        ],\n        \"//conditions:default\": [],\n    }),\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.new_debug_unreachable-1.0.6.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"debug_unreachable\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=new_debug_unreachable\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.6\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.nix-0.31.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"nix\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=nix\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.31.2\",\n    deps = [\n        \"@crates_vendor__bitflags-2.11.0//:bitflags\",\n        \"@crates_vendor__cfg-if-1.0.4//:cfg_if\",\n        \"@crates_vendor__libc-0.2.183//:libc\",\n        \"@crates_vendor__nix-0.31.2//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n    ],\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    pkg_name = \"nix\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=nix\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.31.2\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"@crates_vendor__cfg_aliases-0.2.1//:cfg_aliases\",\n    ],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.num-bigint-0.4.6.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"num_bigint\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"serde\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=num-bigint\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.4.6\",\n    deps = [\n        \"@crates_vendor__num-integer-0.1.46//:num_integer\",\n        \"@crates_vendor__num-traits-0.2.19//:num_traits\",\n        \"@crates_vendor__serde-1.0.228//:serde\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.num-integer-0.1.46.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"num_integer\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"i128\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=num-integer\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.1.46\",\n    deps = [\n        \"@crates_vendor__num-traits-0.2.19//:num_traits\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.num-traits-0.2.19.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"num_traits\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"i128\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=num-traits\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.19\",\n    deps = [\n        \"@crates_vendor__num-traits-0.2.19//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"i128\",\n        \"std\",\n    ],\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    pkg_name = \"num-traits\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=num-traits\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.19\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"@crates_vendor__autocfg-1.5.0//:autocfg\",\n    ],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.num_cpus-1.17.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"num_cpus\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2015\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=num_cpus\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.17.0\",\n    deps = select({\n        \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(not(windows))\n        ],\n        \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(not(windows))\n        ],\n        \"@rules_rust//rust/platform:x86_64-apple-darwin\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(not(windows))\n        ],\n        \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(not(windows))\n        ],\n        \"//conditions:default\": [],\n    }),\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.once_cell-1.21.4.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"once_cell\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"default\",\n        \"race\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=once_cell\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.21.4\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.outref-0.5.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"outref\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=outref\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.5.2\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.par-core-2.0.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"par_core\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=par-core\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.0.0\",\n    deps = [\n        \"@crates_vendor__once_cell-1.21.4//:once_cell\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.percent-encoding-2.3.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"percent_encoding\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=percent-encoding\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.3.2\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.phf-0.11.3.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"phf\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"macros\",\n        \"phf_macros\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__phf_macros-0.11.3//:phf_macros\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=phf\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.11.3\",\n    deps = [\n        \"@crates_vendor__phf_shared-0.11.3//:phf_shared\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.phf-0.13.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"phf\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"macros\",\n        \"phf_macros\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__phf_macros-0.13.1//:phf_macros\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=phf\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.13.1\",\n    deps = [\n        \"@crates_vendor__phf_shared-0.13.1//:phf_shared\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.phf_codegen-0.11.3.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"phf_codegen\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=phf_codegen\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.11.3\",\n    deps = [\n        \"@crates_vendor__phf_generator-0.11.3//:phf_generator\",\n        \"@crates_vendor__phf_shared-0.11.3//:phf_shared\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.phf_codegen-0.13.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"phf_codegen\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=phf_codegen\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.13.1\",\n    deps = [\n        \"@crates_vendor__phf_generator-0.13.1//:phf_generator\",\n        \"@crates_vendor__phf_shared-0.13.1//:phf_shared\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.phf_generator-0.11.3.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\n    \"@rules_rust//rust:defs.bzl\",\n    \"rust_binary\",\n    \"rust_library\",\n)\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"phf_generator\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=phf_generator\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.11.3\",\n    deps = [\n        \"@crates_vendor__phf_shared-0.11.3//:phf_shared\",\n        \"@crates_vendor__rand-0.8.5//:rand\",\n    ],\n)\n\nrust_binary(\n    name = \"gen_hash_test__bin\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/bin/gen_hash_test.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=phf_generator\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.11.3\",\n    deps = [\n        \":phf_generator\",\n        \"@crates_vendor__phf_shared-0.11.3//:phf_shared\",\n        \"@crates_vendor__rand-0.8.5//:rand\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.phf_generator-0.13.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\n    \"@rules_rust//rust:defs.bzl\",\n    \"rust_binary\",\n    \"rust_library\",\n)\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"phf_generator\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=phf_generator\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.13.1\",\n    deps = [\n        \"@crates_vendor__fastrand-2.3.0//:fastrand\",\n        \"@crates_vendor__phf_shared-0.13.1//:phf_shared\",\n    ],\n)\n\nrust_binary(\n    name = \"gen_hash_test__bin\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/bin/gen_hash_test.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=phf_generator\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.13.1\",\n    deps = [\n        \":phf_generator\",\n        \"@crates_vendor__fastrand-2.3.0//:fastrand\",\n        \"@crates_vendor__phf_shared-0.13.1//:phf_shared\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.phf_macros-0.11.3.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"phf_macros\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=phf_macros\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.11.3\",\n    deps = [\n        \"@crates_vendor__phf_generator-0.11.3//:phf_generator\",\n        \"@crates_vendor__phf_shared-0.11.3//:phf_shared\",\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.phf_macros-0.13.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"phf_macros\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=phf_macros\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.13.1\",\n    deps = [\n        \"@crates_vendor__phf_generator-0.13.1//:phf_generator\",\n        \"@crates_vendor__phf_shared-0.13.1//:phf_shared\",\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.phf_shared-0.11.3.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"phf_shared\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=phf_shared\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.11.3\",\n    deps = [\n        \"@crates_vendor__siphasher-1.0.2//:siphasher\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.phf_shared-0.13.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"phf_shared\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=phf_shared\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.13.1\",\n    deps = [\n        \"@crates_vendor__siphasher-1.0.2//:siphasher\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.pico-args-0.5.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"pico_args\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=pico-args\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.5.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.pin-project-lite-0.2.17.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"pin_project_lite\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=pin-project-lite\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.17\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.potential_utf-0.1.4.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"potential_utf\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"zerovec\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=potential_utf\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.1.4\",\n    deps = [\n        \"@crates_vendor__zerovec-0.11.5//:zerovec\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.ppv-lite86-0.2.21.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"ppv_lite86\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"simd\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=ppv-lite86\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.21\",\n    deps = [\n        \"@crates_vendor__zerocopy-0.8.42//:zerocopy\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.precomputed-hash-0.1.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"precomputed_hash\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2015\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=precomputed-hash\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.1.1\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.proc-macro2-1.0.106.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"proc_macro2\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"proc-macro\",\n        \"span-locations\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=proc-macro2\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.106\",\n    deps = [\n        \"@crates_vendor__proc-macro2-1.0.106//:build_script_build\",\n        \"@crates_vendor__unicode-ident-1.0.24//:unicode_ident\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"proc-macro\",\n        \"span-locations\",\n    ],\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    pkg_name = \"proc-macro2\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=proc-macro2\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.106\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.quote-1.0.45.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"quote\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"proc-macro\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=quote\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.45\",\n    deps = [\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"proc-macro\",\n    ],\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    pkg_name = \"quote\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=quote\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.45\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.r-efi-5.3.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"r_efi\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=r-efi\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"5.3.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.radium-0.7.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"radium\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=radium\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.7.0\",\n    deps = [\n        \"@crates_vendor__radium-0.7.0//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2018\",\n    pkg_name = \"radium\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=radium\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.7.0\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.rand-0.8.5.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"rand\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"default\",\n        \"getrandom\",\n        \"libc\",\n        \"rand_chacha\",\n        \"small_rng\",\n        \"std\",\n        \"std_rng\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=rand\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.8.5\",\n    deps = [\n        \"@crates_vendor__rand_chacha-0.3.1//:rand_chacha\",\n        \"@crates_vendor__rand_core-0.6.4//:rand_core\",\n    ] + select({\n        \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # aarch64-apple-darwin\n        ],\n        \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # aarch64-unknown-linux-gnu\n        ],\n        \"@rules_rust//rust/platform:x86_64-apple-darwin\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # x86_64-apple-darwin\n        ],\n        \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # x86_64-unknown-linux-gnu\n        ],\n        \"//conditions:default\": [],\n    }),\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.rand_chacha-0.3.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"rand_chacha\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=rand_chacha\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.3.1\",\n    deps = [\n        \"@crates_vendor__ppv-lite86-0.2.21//:ppv_lite86\",\n        \"@crates_vendor__rand_core-0.6.4//:rand_core\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.rand_core-0.6.4.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"rand_core\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"getrandom\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=rand_core\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.6.4\",\n    deps = [\n        \"@crates_vendor__getrandom-0.2.17//:getrandom\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.regex-1.12.3.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"regex\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"perf\",\n        \"perf-backtrack\",\n        \"perf-cache\",\n        \"perf-dfa\",\n        \"perf-inline\",\n        \"perf-literal\",\n        \"perf-onepass\",\n        \"std\",\n        \"unicode\",\n        \"unicode-age\",\n        \"unicode-bool\",\n        \"unicode-case\",\n        \"unicode-gencat\",\n        \"unicode-perl\",\n        \"unicode-script\",\n        \"unicode-segment\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=regex\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.12.3\",\n    deps = [\n        \"@crates_vendor__aho-corasick-1.1.4//:aho_corasick\",\n        \"@crates_vendor__memchr-2.8.0//:memchr\",\n        \"@crates_vendor__regex-automata-0.4.14//:regex_automata\",\n        \"@crates_vendor__regex-syntax-0.8.10//:regex_syntax\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.regex-automata-0.4.14.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"regex_automata\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"dfa-onepass\",\n        \"dfa-search\",\n        \"hybrid\",\n        \"meta\",\n        \"nfa-backtrack\",\n        \"nfa-pikevm\",\n        \"nfa-thompson\",\n        \"perf-inline\",\n        \"perf-literal\",\n        \"perf-literal-multisubstring\",\n        \"perf-literal-substring\",\n        \"std\",\n        \"syntax\",\n        \"unicode\",\n        \"unicode-age\",\n        \"unicode-bool\",\n        \"unicode-case\",\n        \"unicode-gencat\",\n        \"unicode-perl\",\n        \"unicode-script\",\n        \"unicode-segment\",\n        \"unicode-word-boundary\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=regex-automata\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.4.14\",\n    deps = [\n        \"@crates_vendor__aho-corasick-1.1.4//:aho_corasick\",\n        \"@crates_vendor__memchr-2.8.0//:memchr\",\n        \"@crates_vendor__regex-syntax-0.8.10//:regex_syntax\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.regex-syntax-0.8.10.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"regex_syntax\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n        \"unicode\",\n        \"unicode-age\",\n        \"unicode-bool\",\n        \"unicode-case\",\n        \"unicode-gencat\",\n        \"unicode-perl\",\n        \"unicode-script\",\n        \"unicode-segment\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=regex-syntax\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.8.10\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.ruff_python_ast-0.0.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"ruff_python_ast\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2024\",\n    proc_macro_deps = [\n        \"@crates_vendor__is-macro-0.3.7//:is_macro\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=ruff_python_ast\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.0.0\",\n    deps = [\n        \"@crates_vendor__aho-corasick-1.1.4//:aho_corasick\",\n        \"@crates_vendor__bitflags-2.11.0//:bitflags\",\n        \"@crates_vendor__compact_str-0.9.0//:compact_str\",\n        \"@crates_vendor__itertools-0.14.0//:itertools\",\n        \"@crates_vendor__memchr-2.8.0//:memchr\",\n        \"@crates_vendor__ruff_python_trivia-0.0.0//:ruff_python_trivia\",\n        \"@crates_vendor__ruff_source_file-0.0.0//:ruff_source_file\",\n        \"@crates_vendor__ruff_text_size-0.0.0//:ruff_text_size\",\n        \"@crates_vendor__rustc-hash-2.1.1//:rustc_hash\",\n        \"@crates_vendor__thiserror-2.0.18//:thiserror\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.ruff_python_parser-0.0.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"ruff_python_parser\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2024\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=ruff_python_parser\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.0.0\",\n    deps = [\n        \"@crates_vendor__bitflags-2.11.0//:bitflags\",\n        \"@crates_vendor__bstr-1.12.1//:bstr\",\n        \"@crates_vendor__compact_str-0.9.0//:compact_str\",\n        \"@crates_vendor__memchr-2.8.0//:memchr\",\n        \"@crates_vendor__ruff_python_ast-0.0.0//:ruff_python_ast\",\n        \"@crates_vendor__ruff_python_trivia-0.0.0//:ruff_python_trivia\",\n        \"@crates_vendor__ruff_text_size-0.0.0//:ruff_text_size\",\n        \"@crates_vendor__rustc-hash-2.1.1//:rustc_hash\",\n        \"@crates_vendor__static_assertions-1.1.0//:static_assertions\",\n        \"@crates_vendor__unicode-ident-1.0.24//:unicode_ident\",\n        \"@crates_vendor__unicode-normalization-0.1.25//:unicode_normalization\",\n        \"@crates_vendor__unicode_names2-1.3.0//:unicode_names2\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.ruff_python_trivia-0.0.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"ruff_python_trivia\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2024\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=ruff_python_trivia\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.0.0\",\n    deps = [\n        \"@crates_vendor__itertools-0.14.0//:itertools\",\n        \"@crates_vendor__ruff_source_file-0.0.0//:ruff_source_file\",\n        \"@crates_vendor__ruff_text_size-0.0.0//:ruff_text_size\",\n        \"@crates_vendor__unicode-ident-1.0.24//:unicode_ident\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.ruff_source_file-0.0.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"ruff_source_file\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2024\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=ruff_source_file\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.0.0\",\n    deps = [\n        \"@crates_vendor__memchr-2.8.0//:memchr\",\n        \"@crates_vendor__ruff_text_size-0.0.0//:ruff_text_size\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.ruff_text_size-0.0.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"ruff_text_size\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2024\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=ruff_text_size\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.0.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.rustc-hash-2.1.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"rustc_hash\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=rustc-hash\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.1.1\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.rustc_version-0.4.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"rustc_version\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=rustc_version\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.4.1\",\n    deps = [\n        \"@crates_vendor__semver-1.0.27//:semver\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.rustversion-1.0.22.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"rustversion\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=rustversion\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.22\",\n    deps = [\n        \"@crates_vendor__rustversion-1.0.22//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_name = \"build_script_build\",\n    crate_root = \"build/build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2018\",\n    pkg_name = \"rustversion\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=rustversion\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.22\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.ryu-1.0.23.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"ryu\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=ryu\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.23\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.scoped-tls-1.0.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"scoped_tls\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2015\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=scoped-tls\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.1\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.scratch-1.0.9.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"scratch\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2015\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=scratch\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.9\",\n    deps = [\n        \"@crates_vendor__scratch-1.0.9//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2015\",\n    pkg_name = \"scratch\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=scratch\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.9\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.selectors-0.33.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"selectors\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=selectors\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.33.0\",\n    deps = [\n        \"@crates_vendor__bitflags-2.11.0//:bitflags\",\n        \"@crates_vendor__cssparser-0.36.0//:cssparser\",\n        \"@crates_vendor__derive_more-2.1.1//:derive_more\",\n        \"@crates_vendor__log-0.4.29//:log\",\n        \"@crates_vendor__new_debug_unreachable-1.0.6//:debug_unreachable\",\n        \"@crates_vendor__phf-0.13.1//:phf\",\n        \"@crates_vendor__precomputed-hash-0.1.1//:precomputed_hash\",\n        \"@crates_vendor__rustc-hash-2.1.1//:rustc_hash\",\n        \"@crates_vendor__selectors-0.33.0//:build_script_build\",\n        \"@crates_vendor__servo_arc-0.4.3//:servo_arc\",\n        \"@crates_vendor__smallvec-1.15.1//:smallvec\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    pkg_name = \"selectors\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=selectors\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.33.0\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"@crates_vendor__phf_codegen-0.13.1//:phf_codegen\",\n    ],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.semver-1.0.27.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"semver\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=semver\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.27\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.seq-macro-0.3.6.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"seq_macro\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=seq-macro\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.3.6\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.serde-1.0.228.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"serde\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"derive\",\n        \"rc\",\n        \"serde_derive\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__serde_derive-1.0.228//:serde_derive\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=serde\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.228\",\n    deps = [\n        \"@crates_vendor__serde-1.0.228//:build_script_build\",\n        \"@crates_vendor__serde_core-1.0.228//:serde_core\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"derive\",\n        \"rc\",\n        \"serde_derive\",\n        \"std\",\n    ],\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    pkg_name = \"serde\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=serde\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.228\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.serde_core-1.0.228.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"serde_core\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"rc\",\n        \"result\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=serde_core\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.228\",\n    deps = [\n        \"@crates_vendor__serde_core-1.0.228//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"rc\",\n        \"result\",\n        \"std\",\n    ],\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    pkg_name = \"serde_core\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=serde_core\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.228\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.serde_derive-1.0.228.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"serde_derive\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=serde_derive\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.228\",\n    deps = [\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.serde_json-1.0.149.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"serde_json\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"raw_value\",\n        \"std\",\n        \"unbounded_depth\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=serde_json\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.149\",\n    deps = [\n        \"@crates_vendor__itoa-1.0.17//:itoa\",\n        \"@crates_vendor__memchr-2.8.0//:memchr\",\n        \"@crates_vendor__serde_core-1.0.228//:serde_core\",\n        \"@crates_vendor__serde_json-1.0.149//:build_script_build\",\n        \"@crates_vendor__zmij-1.0.21//:zmij\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"raw_value\",\n        \"std\",\n        \"unbounded_depth\",\n    ],\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    pkg_name = \"serde_json\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=serde_json\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.149\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.servo_arc-0.4.3.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"servo_arc\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"track_alloc_size\",\n    ],\n    crate_root = \"lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=servo_arc\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.4.3\",\n    deps = [\n        \"@crates_vendor__stable_deref_trait-1.2.1//:stable_deref_trait\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.sha1-0.10.6.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"sha1\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=sha1\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.10.6\",\n    deps = [\n        \"@crates_vendor__cfg-if-1.0.4//:cfg_if\",\n        \"@crates_vendor__digest-0.10.7//:digest\",\n    ] + select({\n        \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n            \"@crates_vendor__cpufeatures-0.2.17//:cpufeatures\",  # cfg(any(target_arch = \"aarch64\", target_arch = \"x86\", target_arch = \"x86_64\"))\n        ],\n        \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n            \"@crates_vendor__cpufeatures-0.2.17//:cpufeatures\",  # cfg(any(target_arch = \"aarch64\", target_arch = \"x86\", target_arch = \"x86_64\"))\n        ],\n        \"@rules_rust//rust/platform:x86_64-apple-darwin\": [\n            \"@crates_vendor__cpufeatures-0.2.17//:cpufeatures\",  # cfg(any(target_arch = \"aarch64\", target_arch = \"x86\", target_arch = \"x86_64\"))\n        ],\n        \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n            \"@crates_vendor__cpufeatures-0.2.17//:cpufeatures\",  # cfg(any(target_arch = \"aarch64\", target_arch = \"x86\", target_arch = \"x86_64\"))\n        ],\n        \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n            \"@crates_vendor__cpufeatures-0.2.17//:cpufeatures\",  # cfg(any(target_arch = \"aarch64\", target_arch = \"x86\", target_arch = \"x86_64\"))\n        ],\n        \"//conditions:default\": [],\n    }),\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.shlex-1.3.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"shlex\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2015\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=shlex\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.3.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.simd-adler32-0.3.8.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"simd_adler32\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=simd-adler32\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.3.8\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.siphasher-0.3.11.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"siphasher\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=siphasher\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.3.11\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.siphasher-1.0.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"siphasher\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=siphasher\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.2\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.slab-0.4.12.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"slab\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=slab\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.4.12\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.smallvec-1.15.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"smallvec\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"const_generics\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=smallvec\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.15.1\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.smartstring-1.0.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"smartstring\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=smartstring\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.1\",\n    deps = [\n        \"@crates_vendor__smartstring-1.0.1//:build_script_build\",\n        \"@crates_vendor__static_assertions-1.1.0//:static_assertions\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    pkg_name = \"smartstring\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=smartstring\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.1\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"@crates_vendor__autocfg-1.5.0//:autocfg\",\n        \"@crates_vendor__version_check-0.9.5//:version_check\",\n    ],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.socket2-0.6.3.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"socket2\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"all\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=socket2\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.6.3\",\n    deps = select({\n        \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(any(unix, target_os = \"wasi\"))\n        ],\n        \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(any(unix, target_os = \"wasi\"))\n        ],\n        \"@rules_rust//rust/platform:x86_64-apple-darwin\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(any(unix, target_os = \"wasi\"))\n        ],\n        \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n            \"@crates_vendor__windows-sys-0.61.2//:windows_sys\",  # cfg(windows)\n        ],\n        \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # cfg(any(unix, target_os = \"wasi\"))\n        ],\n        \"//conditions:default\": [],\n    }),\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.stable_deref_trait-1.2.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"stable_deref_trait\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2015\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=stable_deref_trait\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.2.1\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.static_assertions-1.1.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"static_assertions\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2015\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=static_assertions\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.1.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.string_enum-1.0.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"string_enum\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=string_enum\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.2\",\n    deps = [\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__swc_macros_common-1.0.1//:swc_macros_common\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_allocator-4.0.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"swc_allocator\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"hashbrown\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_allocator\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"4.0.1\",\n    deps = [\n        \"@crates_vendor__allocator-api2-0.2.21//:allocator_api2\",\n        \"@crates_vendor__bumpalo-3.20.2//:bumpalo\",\n        \"@crates_vendor__hashbrown-0.14.5//:hashbrown\",\n        \"@crates_vendor__rustc-hash-2.1.1//:rustc_hash\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_atoms-9.0.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"swc_atoms\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_atoms\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"9.0.0\",\n    deps = [\n        \"@crates_vendor__hstr-3.0.4//:hstr\",\n        \"@crates_vendor__once_cell-1.21.4//:once_cell\",\n        \"@crates_vendor__serde-1.0.228//:serde\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_common-18.0.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"swc_common\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"sourcemap\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__ast_node-5.0.0//:ast_node\",\n        \"@crates_vendor__from_variant-3.0.0//:from_variant\",\n        \"@crates_vendor__swc_eq_ignore_macros-1.0.1//:swc_eq_ignore_macros\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_common\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"18.0.1\",\n    deps = [\n        \"@crates_vendor__anyhow-1.0.102//:anyhow\",\n        \"@crates_vendor__better_scoped_tls-1.0.1//:better_scoped_tls\",\n        \"@crates_vendor__bytes-str-0.2.7//:bytes_str\",\n        \"@crates_vendor__either-1.15.0//:either\",\n        \"@crates_vendor__num-bigint-0.4.6//:num_bigint\",\n        \"@crates_vendor__once_cell-1.21.4//:once_cell\",\n        \"@crates_vendor__rustc-hash-2.1.1//:rustc_hash\",\n        \"@crates_vendor__serde-1.0.228//:serde\",\n        \"@crates_vendor__siphasher-0.3.11//:siphasher\",\n        \"@crates_vendor__swc_atoms-9.0.0//:swc_atoms\",\n        \"@crates_vendor__swc_sourcemap-9.3.4//:swc_sourcemap\",\n        \"@crates_vendor__swc_visit-2.0.1//:swc_visit\",\n        \"@crates_vendor__tracing-0.1.44//:tracing\",\n        \"@crates_vendor__unicode-width-0.2.2//:unicode_width\",\n        \"@crates_vendor__url-2.5.8//:url\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_config-3.1.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"swc_config\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__swc_config_macro-1.0.1//:swc_config_macro\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_config\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"3.1.2\",\n    deps = [\n        \"@crates_vendor__anyhow-1.0.102//:anyhow\",\n        \"@crates_vendor__bytes-str-0.2.7//:bytes_str\",\n        \"@crates_vendor__indexmap-2.13.0//:indexmap\",\n        \"@crates_vendor__serde-1.0.228//:serde\",\n        \"@crates_vendor__serde_json-1.0.149//:serde_json\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_config_macro-1.0.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"swc_config_macro\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_config_macro\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.1\",\n    deps = [\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__swc_macros_common-1.0.1//:swc_macros_common\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_ecma_ast-20.0.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"swc_ecma_ast\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__is-macro-0.3.7//:is_macro\",\n        \"@crates_vendor__string_enum-1.0.2//:string_enum\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_ecma_ast\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"20.0.1\",\n    deps = [\n        \"@crates_vendor__bitflags-2.11.0//:bitflags\",\n        \"@crates_vendor__num-bigint-0.4.6//:num_bigint\",\n        \"@crates_vendor__once_cell-1.21.4//:once_cell\",\n        \"@crates_vendor__phf-0.11.3//:phf\",\n        \"@crates_vendor__rustc-hash-2.1.1//:rustc_hash\",\n        \"@crates_vendor__swc_atoms-9.0.0//:swc_atoms\",\n        \"@crates_vendor__swc_common-18.0.1//:swc_common\",\n        \"@crates_vendor__swc_visit-2.0.1//:swc_visit\",\n        \"@crates_vendor__unicode-id-start-1.4.0//:unicode_id_start\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_ecma_codegen-23.0.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"swc_ecma_codegen\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__swc_ecma_codegen_macros-2.0.2//:swc_ecma_codegen_macros\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_ecma_codegen\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"23.0.0\",\n    deps = [\n        \"@crates_vendor__ascii-1.1.0//:ascii\",\n        \"@crates_vendor__compact_str-0.7.1//:compact_str\",\n        \"@crates_vendor__dragonbox_ecma-0.1.12//:dragonbox_ecma\",\n        \"@crates_vendor__memchr-2.8.0//:memchr\",\n        \"@crates_vendor__num-bigint-0.4.6//:num_bigint\",\n        \"@crates_vendor__once_cell-1.21.4//:once_cell\",\n        \"@crates_vendor__regex-1.12.3//:regex\",\n        \"@crates_vendor__rustc-hash-2.1.1//:rustc_hash\",\n        \"@crates_vendor__serde-1.0.228//:serde\",\n        \"@crates_vendor__swc_allocator-4.0.1//:swc_allocator\",\n        \"@crates_vendor__swc_atoms-9.0.0//:swc_atoms\",\n        \"@crates_vendor__swc_common-18.0.1//:swc_common\",\n        \"@crates_vendor__swc_ecma_ast-20.0.1//:swc_ecma_ast\",\n        \"@crates_vendor__tracing-0.1.44//:tracing\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_ecma_codegen_macros-2.0.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"swc_ecma_codegen_macros\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_ecma_codegen_macros\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.0.2\",\n    deps = [\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__swc_macros_common-1.0.1//:swc_macros_common\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_ecma_hooks-0.4.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"swc_ecma_hooks\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_ecma_hooks\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.4.0\",\n    deps = [\n        \"@crates_vendor__swc_atoms-9.0.0//:swc_atoms\",\n        \"@crates_vendor__swc_common-18.0.1//:swc_common\",\n        \"@crates_vendor__swc_ecma_ast-20.0.1//:swc_ecma_ast\",\n        \"@crates_vendor__swc_ecma_visit-20.0.0//:swc_ecma_visit\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_ecma_parser-33.0.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"swc_ecma_parser\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"typescript\",\n        \"unstable\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__seq-macro-0.3.6//:seq_macro\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_ecma_parser\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"33.0.1\",\n    deps = [\n        \"@crates_vendor__bitflags-2.11.0//:bitflags\",\n        \"@crates_vendor__either-1.15.0//:either\",\n        \"@crates_vendor__num-bigint-0.4.6//:num_bigint\",\n        \"@crates_vendor__phf-0.11.3//:phf\",\n        \"@crates_vendor__rustc-hash-2.1.1//:rustc_hash\",\n        \"@crates_vendor__serde-1.0.228//:serde\",\n        \"@crates_vendor__smartstring-1.0.1//:smartstring\",\n        \"@crates_vendor__swc_atoms-9.0.0//:swc_atoms\",\n        \"@crates_vendor__swc_common-18.0.1//:swc_common\",\n        \"@crates_vendor__swc_ecma_ast-20.0.1//:swc_ecma_ast\",\n        \"@crates_vendor__tracing-0.1.44//:tracing\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_ecma_transforms_base-36.0.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"swc_ecma_transforms_base\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_ecma_transforms_base\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"36.0.1\",\n    deps = [\n        \"@crates_vendor__better_scoped_tls-1.0.1//:better_scoped_tls\",\n        \"@crates_vendor__indexmap-2.13.0//:indexmap\",\n        \"@crates_vendor__once_cell-1.21.4//:once_cell\",\n        \"@crates_vendor__par-core-2.0.0//:par_core\",\n        \"@crates_vendor__phf-0.11.3//:phf\",\n        \"@crates_vendor__rustc-hash-2.1.1//:rustc_hash\",\n        \"@crates_vendor__serde-1.0.228//:serde\",\n        \"@crates_vendor__swc_atoms-9.0.0//:swc_atoms\",\n        \"@crates_vendor__swc_common-18.0.1//:swc_common\",\n        \"@crates_vendor__swc_ecma_ast-20.0.1//:swc_ecma_ast\",\n        \"@crates_vendor__swc_ecma_parser-33.0.1//:swc_ecma_parser\",\n        \"@crates_vendor__swc_ecma_utils-26.0.1//:swc_ecma_utils\",\n        \"@crates_vendor__swc_ecma_visit-20.0.0//:swc_ecma_visit\",\n        \"@crates_vendor__tracing-0.1.44//:tracing\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_ecma_transforms_react-40.0.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"swc_ecma_transforms_react\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"serde\",\n        \"serde-impl\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__string_enum-1.0.2//:string_enum\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_ecma_transforms_react\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"40.0.0\",\n    deps = [\n        \"@crates_vendor__base64-0.22.1//:base64\",\n        \"@crates_vendor__bytes-str-0.2.7//:bytes_str\",\n        \"@crates_vendor__indexmap-2.13.0//:indexmap\",\n        \"@crates_vendor__once_cell-1.21.4//:once_cell\",\n        \"@crates_vendor__rustc-hash-2.1.1//:rustc_hash\",\n        \"@crates_vendor__serde-1.0.228//:serde\",\n        \"@crates_vendor__sha1-0.10.6//:sha1\",\n        \"@crates_vendor__swc_atoms-9.0.0//:swc_atoms\",\n        \"@crates_vendor__swc_common-18.0.1//:swc_common\",\n        \"@crates_vendor__swc_config-3.1.2//:swc_config\",\n        \"@crates_vendor__swc_ecma_ast-20.0.1//:swc_ecma_ast\",\n        \"@crates_vendor__swc_ecma_hooks-0.4.0//:swc_ecma_hooks\",\n        \"@crates_vendor__swc_ecma_parser-33.0.1//:swc_ecma_parser\",\n        \"@crates_vendor__swc_ecma_transforms_base-36.0.1//:swc_ecma_transforms_base\",\n        \"@crates_vendor__swc_ecma_utils-26.0.1//:swc_ecma_utils\",\n        \"@crates_vendor__swc_ecma_visit-20.0.0//:swc_ecma_visit\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_ecma_transforms_typescript-40.0.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"swc_ecma_transforms_typescript\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_ecma_transforms_typescript\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"40.0.0\",\n    deps = [\n        \"@crates_vendor__bytes-str-0.2.7//:bytes_str\",\n        \"@crates_vendor__rustc-hash-2.1.1//:rustc_hash\",\n        \"@crates_vendor__serde-1.0.228//:serde\",\n        \"@crates_vendor__swc_atoms-9.0.0//:swc_atoms\",\n        \"@crates_vendor__swc_common-18.0.1//:swc_common\",\n        \"@crates_vendor__swc_ecma_ast-20.0.1//:swc_ecma_ast\",\n        \"@crates_vendor__swc_ecma_transforms_base-36.0.1//:swc_ecma_transforms_base\",\n        \"@crates_vendor__swc_ecma_transforms_react-40.0.0//:swc_ecma_transforms_react\",\n        \"@crates_vendor__swc_ecma_utils-26.0.1//:swc_ecma_utils\",\n        \"@crates_vendor__swc_ecma_visit-20.0.0//:swc_ecma_visit\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_ecma_utils-26.0.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"swc_ecma_utils\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_ecma_utils\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"26.0.1\",\n    deps = [\n        \"@crates_vendor__dragonbox_ecma-0.1.12//:dragonbox_ecma\",\n        \"@crates_vendor__indexmap-2.13.0//:indexmap\",\n        \"@crates_vendor__num_cpus-1.17.0//:num_cpus\",\n        \"@crates_vendor__once_cell-1.21.4//:once_cell\",\n        \"@crates_vendor__par-core-2.0.0//:par_core\",\n        \"@crates_vendor__rustc-hash-2.1.1//:rustc_hash\",\n        \"@crates_vendor__swc_atoms-9.0.0//:swc_atoms\",\n        \"@crates_vendor__swc_common-18.0.1//:swc_common\",\n        \"@crates_vendor__swc_ecma_ast-20.0.1//:swc_ecma_ast\",\n        \"@crates_vendor__swc_ecma_visit-20.0.0//:swc_ecma_visit\",\n        \"@crates_vendor__tracing-0.1.44//:tracing\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_ecma_visit-20.0.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"swc_ecma_visit\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_ecma_visit\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"20.0.0\",\n    deps = [\n        \"@crates_vendor__new_debug_unreachable-1.0.6//:debug_unreachable\",\n        \"@crates_vendor__num-bigint-0.4.6//:num_bigint\",\n        \"@crates_vendor__swc_atoms-9.0.0//:swc_atoms\",\n        \"@crates_vendor__swc_common-18.0.1//:swc_common\",\n        \"@crates_vendor__swc_ecma_ast-20.0.1//:swc_ecma_ast\",\n        \"@crates_vendor__swc_visit-2.0.1//:swc_visit\",\n        \"@crates_vendor__tracing-0.1.44//:tracing\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_eq_ignore_macros-1.0.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"swc_eq_ignore_macros\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_eq_ignore_macros\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.1\",\n    deps = [\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_macros_common-1.0.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"swc_macros_common\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_macros_common\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.1\",\n    deps = [\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_sourcemap-9.3.4.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"swc_sourcemap\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_sourcemap\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"9.3.4\",\n    deps = [\n        \"@crates_vendor__base64-simd-0.8.0//:base64_simd\",\n        \"@crates_vendor__bitvec-1.0.1//:bitvec\",\n        \"@crates_vendor__bytes-str-0.2.7//:bytes_str\",\n        \"@crates_vendor__data-encoding-2.10.0//:data_encoding\",\n        \"@crates_vendor__debugid-0.8.0//:debugid\",\n        \"@crates_vendor__if_chain-1.0.3//:if_chain\",\n        \"@crates_vendor__rustc-hash-2.1.1//:rustc_hash\",\n        \"@crates_vendor__serde-1.0.228//:serde\",\n        \"@crates_vendor__serde_json-1.0.149//:serde_json\",\n        \"@crates_vendor__unicode-id-start-1.4.0//:unicode_id_start\",\n        \"@crates_vendor__url-2.5.8//:url\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_ts_fast_strip-43.0.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"swc_ts_fast_strip\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_ts_fast_strip\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"43.0.0\",\n    deps = [\n        \"@crates_vendor__anyhow-1.0.102//:anyhow\",\n        \"@crates_vendor__bytes-str-0.2.7//:bytes_str\",\n        \"@crates_vendor__serde-1.0.228//:serde\",\n        \"@crates_vendor__swc_atoms-9.0.0//:swc_atoms\",\n        \"@crates_vendor__swc_common-18.0.1//:swc_common\",\n        \"@crates_vendor__swc_ecma_ast-20.0.1//:swc_ecma_ast\",\n        \"@crates_vendor__swc_ecma_codegen-23.0.0//:swc_ecma_codegen\",\n        \"@crates_vendor__swc_ecma_parser-33.0.1//:swc_ecma_parser\",\n        \"@crates_vendor__swc_ecma_transforms_base-36.0.1//:swc_ecma_transforms_base\",\n        \"@crates_vendor__swc_ecma_transforms_react-40.0.0//:swc_ecma_transforms_react\",\n        \"@crates_vendor__swc_ecma_transforms_typescript-40.0.0//:swc_ecma_transforms_typescript\",\n        \"@crates_vendor__swc_ecma_visit-20.0.0//:swc_ecma_visit\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.swc_visit-2.0.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"swc_visit\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=swc_visit\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.0.1\",\n    deps = [\n        \"@crates_vendor__either-1.15.0//:either\",\n        \"@crates_vendor__new_debug_unreachable-1.0.6//:debug_unreachable\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.syn-2.0.117.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"syn\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"clone-impls\",\n        \"default\",\n        \"derive\",\n        \"extra-traits\",\n        \"fold\",\n        \"full\",\n        \"parsing\",\n        \"printing\",\n        \"proc-macro\",\n        \"visit\",\n        \"visit-mut\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=syn\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.0.117\",\n    deps = [\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__unicode-ident-1.0.24//:unicode_ident\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.synstructure-0.13.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"synstructure\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"proc-macro\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=synstructure\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.13.2\",\n    deps = [\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.tap-1.0.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"tap\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2015\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=tap\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.1\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.termcolor-1.4.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"termcolor\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=termcolor\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.4.1\",\n    deps = select({\n        \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n            \"@crates_vendor__winapi-util-0.1.11//:winapi_util\",  # cfg(windows)\n        ],\n        \"//conditions:default\": [],\n    }),\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.thiserror-2.0.18.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"thiserror\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__thiserror-impl-2.0.18//:thiserror_impl\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=thiserror\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.0.18\",\n    deps = [\n        \"@crates_vendor__thiserror-2.0.18//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    pkg_name = \"thiserror\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=thiserror\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.0.18\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.thiserror-impl-2.0.18.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"thiserror_impl\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=thiserror-impl\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.0.18\",\n    deps = [\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.tinystr-0.8.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"tinystr\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"zerovec\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__displaydoc-0.2.5//:displaydoc\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=tinystr\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.8.2\",\n    deps = [\n        \"@crates_vendor__zerovec-0.11.5//:zerovec\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.tinyvec-1.11.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"tinyvec\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"default\",\n        \"tinyvec_macros\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=tinyvec\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.11.0\",\n    deps = [\n        \"@crates_vendor__tinyvec_macros-0.1.1//:tinyvec_macros\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.tinyvec_macros-0.1.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"tinyvec_macros\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=tinyvec_macros\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.1.1\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.tokio-1.50.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"tokio\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"libc\",\n        \"mio\",\n        \"net\",\n        \"rt\",\n        \"rt-multi-thread\",\n        \"socket2\",\n        \"time\",\n    ] + select({\n        \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n            \"windows-sys\",  # x86_64-pc-windows-msvc\n        ],\n        \"//conditions:default\": [],\n    }),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=tokio\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.50.0\",\n    deps = [\n        \"@crates_vendor__mio-1.1.1//:mio\",\n        \"@crates_vendor__pin-project-lite-0.2.17//:pin_project_lite\",\n        \"@crates_vendor__socket2-0.6.3//:socket2\",\n    ] + select({\n        \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # aarch64-apple-darwin\n        ],\n        \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # aarch64-unknown-linux-gnu\n        ],\n        \"@rules_rust//rust/platform:x86_64-apple-darwin\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # x86_64-apple-darwin\n        ],\n        \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n            \"@crates_vendor__windows-sys-0.61.2//:windows_sys\",  # x86_64-pc-windows-msvc\n        ],\n        \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n            \"@crates_vendor__libc-0.2.183//:libc\",  # x86_64-unknown-linux-gnu\n        ],\n        \"//conditions:default\": [],\n    }),\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.tracing-0.1.44.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"tracing\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"attributes\",\n        \"default\",\n        \"std\",\n        \"tracing-attributes\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    proc_macro_deps = [\n        \"@crates_vendor__tracing-attributes-0.1.31//:tracing_attributes\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=tracing\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.1.44\",\n    deps = [\n        \"@crates_vendor__pin-project-lite-0.2.17//:pin_project_lite\",\n        \"@crates_vendor__tracing-core-0.1.36//:tracing_core\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.tracing-attributes-0.1.31.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"tracing_attributes\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=tracing-attributes\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.1.31\",\n    deps = [\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.tracing-core-0.1.36.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"tracing_core\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"once_cell\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=tracing-core\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.1.36\",\n    deps = [\n        \"@crates_vendor__once_cell-1.21.4//:once_cell\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.triomphe-0.1.15.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"triomphe\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"serde\",\n        \"stable_deref_trait\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=triomphe\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.1.15\",\n    deps = [\n        \"@crates_vendor__serde-1.0.228//:serde\",\n        \"@crates_vendor__stable_deref_trait-1.2.1//:stable_deref_trait\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.typenum-1.19.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"typenum\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=typenum\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.19.0\",\n    deps = [\n        \"@crates_vendor__typenum-1.19.0//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2018\",\n    pkg_name = \"typenum\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=typenum\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.19.0\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.unicode-id-start-1.4.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"unicode_id_start\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=unicode-id-start\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.4.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.unicode-ident-1.0.24.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"unicode_ident\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=unicode-ident\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.24\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.unicode-normalization-0.1.25.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"unicode_normalization\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=unicode-normalization\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.1.25\",\n    deps = [\n        \"@crates_vendor__tinyvec-1.11.0//:tinyvec\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.unicode-width-0.2.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"unicode_width\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"cjk\",\n        \"default\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=unicode-width\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.2\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.unicode_names2-1.3.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"unicode_names2\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=unicode_names2\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.3.0\",\n    deps = [\n        \"@crates_vendor__phf-0.11.3//:phf\",\n        \"@crates_vendor__unicode_names2-1.3.0//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n    ],\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2018\",\n    pkg_name = \"unicode_names2\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=unicode_names2\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.3.0\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"@crates_vendor__unicode_names2_generator-1.3.0//:unicode_names2_generator\",\n    ],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.unicode_names2_generator-1.3.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"unicode_names2_generator\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=unicode_names2_generator\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.3.0\",\n    deps = [\n        \"@crates_vendor__getopts-0.2.24//:getopts\",\n        \"@crates_vendor__log-0.4.29//:log\",\n        \"@crates_vendor__phf_codegen-0.11.3//:phf_codegen\",\n        \"@crates_vendor__rand-0.8.5//:rand\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.url-2.5.8.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"url\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=url\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"2.5.8\",\n    deps = [\n        \"@crates_vendor__form_urlencoded-1.2.2//:form_urlencoded\",\n        \"@crates_vendor__idna-1.1.0//:idna\",\n        \"@crates_vendor__percent-encoding-2.3.2//:percent_encoding\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.utf8_iter-1.0.4.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"utf8_iter\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=utf8_iter\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.4\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.uuid-1.22.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"uuid\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"default\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=uuid\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.22.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.version_check-0.9.5.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"version_check\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2015\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=version_check\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.9.5\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.vsimd-0.8.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"vsimd\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"alloc\",\n        \"detect\",\n        \"std\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=vsimd\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.8.0\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.wasi-0.11.1+wasi-snapshot-preview1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"wasi\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=wasi\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.11.1+wasi-snapshot-preview1\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.wasip2-1.0.2+wasi-0.2.9.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"wasip2\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=wasip2\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.2+wasi-0.2.9\",\n    deps = [\n        \"@crates_vendor__wit-bindgen-0.51.0//:wit_bindgen\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.wasm-bindgen-0.2.114.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"wasm_bindgen\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__wasm-bindgen-macro-0.2.114//:wasm_bindgen_macro\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=wasm-bindgen\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.114\",\n    deps = [\n        \"@crates_vendor__cfg-if-1.0.4//:cfg_if\",\n        \"@crates_vendor__once_cell-1.21.4//:once_cell\",\n        \"@crates_vendor__wasm-bindgen-0.2.114//:build_script_build\",\n        \"@crates_vendor__wasm-bindgen-shared-0.2.114//:wasm_bindgen_shared\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    aliases = {\n        \"@crates_vendor__rustversion-1.0.22//:rustversion\": \"rustversion_compat\",\n    },\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    link_deps = [\n        \"@crates_vendor__wasm-bindgen-shared-0.2.114//:wasm_bindgen_shared\",\n    ],\n    pkg_name = \"wasm-bindgen\",\n    proc_macro_deps = [\n        \"@crates_vendor__rustversion-1.0.22//:rustversion\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=wasm-bindgen\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.114\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.wasm-bindgen-macro-0.2.114.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"wasm_bindgen_macro\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=wasm-bindgen-macro\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.114\",\n    deps = [\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__wasm-bindgen-macro-support-0.2.114//:wasm_bindgen_macro_support\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.wasm-bindgen-macro-support-0.2.114.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"wasm_bindgen_macro_support\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=wasm-bindgen-macro-support\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.114\",\n    deps = [\n        \"@crates_vendor__bumpalo-3.20.2//:bumpalo\",\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n        \"@crates_vendor__wasm-bindgen-shared-0.2.114//:wasm_bindgen_shared\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.wasm-bindgen-shared-0.2.114.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"wasm_bindgen_shared\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=wasm-bindgen-shared\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.114\",\n    deps = [\n        \"@crates_vendor__unicode-ident-1.0.24//:unicode_ident\",\n        \"@crates_vendor__wasm-bindgen-shared-0.2.114//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    links = \"wasm_bindgen\",\n    pkg_name = \"wasm-bindgen-shared\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=wasm-bindgen-shared\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.114\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.winapi-util-0.1.11.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"winapi_util\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=winapi-util\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.1.11\",\n    deps = select({\n        \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n            \"@crates_vendor__windows-sys-0.61.2//:windows_sys\",  # cfg(windows)\n        ],\n        \"//conditions:default\": [],\n    }),\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.windows-link-0.2.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"windows_link\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=windows-link\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.1\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.windows-sys-0.61.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"windows_sys\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"Wdk\",\n        \"Wdk_Foundation\",\n        \"Wdk_Storage\",\n        \"Wdk_Storage_FileSystem\",\n        \"Wdk_System\",\n        \"Wdk_System_IO\",\n        \"Win32\",\n        \"Win32_Foundation\",\n        \"Win32_Networking\",\n        \"Win32_Networking_WinSock\",\n        \"Win32_Security\",\n        \"Win32_Storage\",\n        \"Win32_Storage_FileSystem\",\n        \"Win32_System\",\n        \"Win32_System_Console\",\n        \"Win32_System_IO\",\n        \"Win32_System_Pipes\",\n        \"Win32_System_SystemInformation\",\n        \"Win32_System_SystemServices\",\n        \"Win32_System_Threading\",\n        \"Win32_System_WindowsProgramming\",\n        \"default\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=windows-sys\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.61.2\",\n    deps = [\n        \"@crates_vendor__windows-link-0.2.1//:windows_link\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.wit-bindgen-0.51.0.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"wit_bindgen\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2024\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=wit-bindgen\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.51.0\",\n    deps = [\n        \"@crates_vendor__wit-bindgen-0.51.0//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2024\",\n    pkg_name = \"wit-bindgen\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=wit-bindgen\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.51.0\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.writeable-0.6.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"writeable\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=writeable\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.6.2\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.wyz-0.5.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"wyz\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2018\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=wyz\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.5.1\",\n    deps = [\n        \"@crates_vendor__tap-1.0.1//:tap\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.yoke-0.8.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"yoke\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"derive\",\n        \"zerofrom\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__yoke-derive-0.8.1//:yoke_derive\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=yoke\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.8.1\",\n    deps = [\n        \"@crates_vendor__stable_deref_trait-1.2.1//:stable_deref_trait\",\n        \"@crates_vendor__zerofrom-0.1.6//:zerofrom\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.yoke-derive-0.8.1.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"yoke_derive\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=yoke-derive\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.8.1\",\n    deps = [\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n        \"@crates_vendor__synstructure-0.13.2//:synstructure\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.zerocopy-0.8.42.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"zerocopy\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"simd\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=zerocopy\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.8.42\",\n    deps = [\n        \"@crates_vendor__zerocopy-0.8.42//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"simd\",\n    ],\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    pkg_name = \"zerocopy\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=zerocopy\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.8.42\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.zerocopy-derive-0.8.42.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"zerocopy_derive\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=zerocopy-derive\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.8.42\",\n    deps = [\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.zerofrom-0.1.6.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"zerofrom\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"derive\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__zerofrom-derive-0.1.6//:zerofrom_derive\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=zerofrom\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.1.6\",\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.zerofrom-derive-0.1.6.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"zerofrom_derive\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=zerofrom-derive\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.1.6\",\n    deps = [\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n        \"@crates_vendor__synstructure-0.13.2//:synstructure\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.zerotrie-0.2.3.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"zerotrie\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"yoke\",\n        \"zerofrom\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__displaydoc-0.2.5//:displaydoc\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=zerotrie\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.2.3\",\n    deps = [\n        \"@crates_vendor__yoke-0.8.1//:yoke\",\n        \"@crates_vendor__zerofrom-0.1.6//:zerofrom\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.zerovec-0.11.5.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"zerovec\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_features = [\n        \"derive\",\n        \"yoke\",\n    ],\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    proc_macro_deps = [\n        \"@crates_vendor__zerovec-derive-0.11.2//:zerovec_derive\",\n    ],\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=zerovec\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.11.5\",\n    deps = [\n        \"@crates_vendor__yoke-0.8.1//:yoke\",\n        \"@crates_vendor__zerofrom-0.1.6//:zerofrom\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.zerovec-derive-0.11.2.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_proc_macro(\n    name = \"zerovec_derive\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=zerovec-derive\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"0.11.2\",\n    deps = [\n        \"@crates_vendor__proc-macro2-1.0.106//:proc_macro2\",\n        \"@crates_vendor__quote-1.0.45//:quote\",\n        \"@crates_vendor__syn-2.0.117//:syn\",\n    ],\n)\n"
  },
  {
    "path": "deps/rust/crates/BUILD.zmij-1.0.21.bazel",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_build_script\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nrust_library(\n    name = \"zmij\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_root = \"src/lib.rs\",\n    edition = \"2021\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=zmij\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.21\",\n    deps = [\n        \"@crates_vendor__zmij-1.0.21//:build_script_build\",\n    ],\n)\n\ncargo_build_script(\n    name = \"_bs\",\n    srcs = glob(\n        include = [\"**/*.rs\"],\n        allow_empty = True,\n    ),\n    compile_data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \"**/*.rs\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    crate_name = \"build_script_build\",\n    crate_root = \"build.rs\",\n    data = glob(\n        include = [\"**\"],\n        allow_empty = True,\n        exclude = [\n            \"**/* *\",\n            \".tmp_git_root/**/*\",\n            \"BUILD\",\n            \"BUILD.bazel\",\n            \"WORKSPACE\",\n            \"WORKSPACE.bazel\",\n        ],\n    ),\n    edition = \"2021\",\n    pkg_name = \"zmij\",\n    rustc_flags = [\n        \"--cap-lints=allow\",\n    ],\n    tags = [\n        \"cargo-bazel\",\n        \"crate-name=zmij\",\n        \"manual\",\n        \"noclippy\",\n        \"norustfmt\",\n    ],\n    version = \"1.0.21\",\n    visibility = [\"//visibility:private\"],\n)\n\nalias(\n    name = \"build_script_build\",\n    actual = \":_bs\",\n    tags = [\"manual\"],\n)\n"
  },
  {
    "path": "deps/rust/crates/alias_rules.bzl",
    "content": "\"\"\"Alias that transitions its target to `compilation_mode=opt`.  Use `transition_alias=\"opt\"` to enable.\"\"\"\n\nload(\"@rules_cc//cc:defs.bzl\", \"CcInfo\")\nload(\"@rules_rust//rust:rust_common.bzl\", \"COMMON_PROVIDERS\")\n\ndef _transition_alias_impl(ctx):\n    # `ctx.attr.actual` is a list of 1 item due to the transition\n    providers = [ctx.attr.actual[0][provider] for provider in COMMON_PROVIDERS]\n    if CcInfo in ctx.attr.actual[0]:\n        providers.append(ctx.attr.actual[0][CcInfo])\n    return providers\n\ndef _change_compilation_mode(compilation_mode):\n    def _change_compilation_mode_impl(_settings, _attr):\n        return {\n            \"//command_line_option:compilation_mode\": compilation_mode,\n        }\n\n    return transition(\n        implementation = _change_compilation_mode_impl,\n        inputs = [],\n        outputs = [\n            \"//command_line_option:compilation_mode\",\n        ],\n    )\n\ndef _transition_alias_rule(compilation_mode):\n    return rule(\n        implementation = _transition_alias_impl,\n        provides = COMMON_PROVIDERS,\n        attrs = {\n            \"actual\": attr.label(\n                mandatory = True,\n                doc = \"`rust_library()` target to transition to `compilation_mode=opt`.\",\n                providers = COMMON_PROVIDERS,\n                cfg = _change_compilation_mode(compilation_mode),\n            ),\n            \"_allowlist_function_transition\": attr.label(\n                default = \"@bazel_tools//tools/allowlists/function_transition_allowlist\",\n            ),\n        },\n        doc = \"Transitions a Rust library crate to the `compilation_mode=opt`.\",\n    )\n\ntransition_alias_dbg = _transition_alias_rule(\"dbg\")\ntransition_alias_fastbuild = _transition_alias_rule(\"fastbuild\")\ntransition_alias_opt = _transition_alias_rule(\"opt\")\n"
  },
  {
    "path": "deps/rust/crates/crates.bzl",
    "content": "###############################################################################\n# @generated\n# This file is auto-generated by the cargo-bazel tool.\n#\n# DO NOT MODIFY: Local changes may be replaced in future executions.\n###############################################################################\n\"\"\"Rules for defining repositories for remote `crates_vendor` repositories\"\"\"\n\nload(\"@bazel_tools//tools/build_defs/repo:utils.bzl\", \"maybe\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:crates_vendor.bzl\", \"crates_vendor_remote_repository\")\n\n# buildifier: disable=bzl-visibility\nload(\"//deps/rust/crates:defs.bzl\", _crate_repositories = \"crate_repositories\")\n\ndef crate_repositories():\n    \"\"\"Generates repositories for vendored crates.\n\n    Returns:\n      A list of repos visible to the module through the module extension.\n    \"\"\"\n    maybe(\n        crates_vendor_remote_repository,\n        name = \"crates_vendor\",\n        build_file = Label(\"//deps/rust/crates:BUILD.bazel\"),\n        defs_module = Label(\"//deps/rust/crates:defs.bzl\"),\n    )\n\n    direct_deps = [struct(repo = \"crates_vendor\", is_dev_dep = False)]\n    direct_deps.extend(_crate_repositories())\n    return direct_deps\n"
  },
  {
    "path": "deps/rust/crates/defs.bzl",
    "content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To\n# regenerate this file, run the following:\n#\n#     bazel run @@//deps/rust:crates_vendor\n###############################################################################\n\"\"\"\n# `crates_repository` API\n\n- [aliases](#aliases)\n- [crate_deps](#crate_deps)\n- [all_crate_deps](#all_crate_deps)\n- [crate_repositories](#crate_repositories)\n\n\"\"\"\n\nload(\"@bazel_skylib//lib:selects.bzl\", \"selects\")\nload(\"@bazel_tools//tools/build_defs/repo:git.bzl\", \"new_git_repository\")\nload(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\nload(\"@bazel_tools//tools/build_defs/repo:utils.bzl\", \"maybe\")\n\n###############################################################################\n# MACROS API\n###############################################################################\n\n# An identifier that represent common dependencies (unconditional).\n_COMMON_CONDITION = \"\"\n\ndef _flatten_dependency_maps(all_dependency_maps):\n    \"\"\"Flatten a list of dependency maps into one dictionary.\n\n    Dependency maps have the following structure:\n\n    ```python\n    DEPENDENCIES_MAP = {\n        # The first key in the map is a Bazel package\n        # name of the workspace this file is defined in.\n        \"workspace_member_package\": {\n\n            # Not all dependencies are supported for all platforms.\n            # the condition key is the condition required to be true\n            # on the host platform.\n            \"condition\": {\n\n                # An alias to a crate target.     # The label of the crate target the\n                # Aliases are only crate names.   # package name refers to.\n                \"package_name\":                   \"@full//:label\",\n            }\n        }\n    }\n    ```\n\n    Args:\n        all_dependency_maps (list): A list of dicts as described above\n\n    Returns:\n        dict: A dictionary as described above\n    \"\"\"\n    dependencies = {}\n\n    for workspace_deps_map in all_dependency_maps:\n        for pkg_name, conditional_deps_map in workspace_deps_map.items():\n            if pkg_name not in dependencies:\n                non_frozen_map = dict()\n                for key, values in conditional_deps_map.items():\n                    non_frozen_map.update({key: dict(values.items())})\n                dependencies.setdefault(pkg_name, non_frozen_map)\n                continue\n\n            for condition, deps_map in conditional_deps_map.items():\n                # If the condition has not been recorded, do so and continue\n                if condition not in dependencies[pkg_name]:\n                    dependencies[pkg_name].setdefault(condition, dict(deps_map.items()))\n                    continue\n\n                # Alert on any miss-matched dependencies\n                inconsistent_entries = []\n                for crate_name, crate_label in deps_map.items():\n                    existing = dependencies[pkg_name][condition].get(crate_name)\n                    if existing and existing != crate_label:\n                        inconsistent_entries.append((crate_name, existing, crate_label))\n                    dependencies[pkg_name][condition].update({crate_name: crate_label})\n\n    return dependencies\n\ndef crate_deps(deps, package_name = None):\n    \"\"\"Finds the fully qualified label of the requested crates for the package where this macro is called.\n\n    Args:\n        deps (list): The desired list of crate targets.\n        package_name (str, optional): The package name of the set of dependencies to look up.\n            Defaults to `native.package_name()`.\n\n    Returns:\n        list: A list of labels to generated rust targets (str)\n    \"\"\"\n\n    if not deps:\n        return []\n\n    if package_name == None:\n        package_name = native.package_name()\n\n    # Join both sets of dependencies\n    dependencies = _flatten_dependency_maps([\n        _NORMAL_DEPENDENCIES,\n        _NORMAL_DEV_DEPENDENCIES,\n        _PROC_MACRO_DEPENDENCIES,\n        _PROC_MACRO_DEV_DEPENDENCIES,\n        _BUILD_DEPENDENCIES,\n        _BUILD_PROC_MACRO_DEPENDENCIES,\n    ]).pop(package_name, {})\n\n    # Combine all conditional packages so we can easily index over a flat list\n    # TODO: Perhaps this should actually return select statements and maintain\n    # the conditionals of the dependencies\n    flat_deps = {}\n    for deps_set in dependencies.values():\n        for crate_name, crate_label in deps_set.items():\n            flat_deps.update({crate_name: crate_label})\n\n    missing_crates = []\n    crate_targets = []\n    for crate_target in deps:\n        if crate_target not in flat_deps:\n            missing_crates.append(crate_target)\n        else:\n            crate_targets.append(flat_deps[crate_target])\n\n    if missing_crates:\n        fail(\"Could not find crates `{}` among dependencies of `{}`. Available dependencies were `{}`\".format(\n            missing_crates,\n            package_name,\n            dependencies,\n        ))\n\n    return crate_targets\n\ndef all_crate_deps(\n        normal = False,\n        normal_dev = False,\n        proc_macro = False,\n        proc_macro_dev = False,\n        build = False,\n        build_proc_macro = False,\n        package_name = None):\n    \"\"\"Finds the fully qualified label of all requested direct crate dependencies \\\n    for the package where this macro is called.\n\n    If no parameters are set, all normal dependencies are returned. Setting any one flag will\n    otherwise impact the contents of the returned list.\n\n    Args:\n        normal (bool, optional): If True, normal dependencies are included in the\n            output list.\n        normal_dev (bool, optional): If True, normal dev dependencies will be\n            included in the output list.\n        proc_macro (bool, optional): If True, proc_macro dependencies are included\n            in the output list.\n        proc_macro_dev (bool, optional): If True, dev proc_macro dependencies are\n            included in the output list.\n        build (bool, optional): If True, build dependencies are included\n            in the output list.\n        build_proc_macro (bool, optional): If True, build proc_macro dependencies are\n            included in the output list.\n        package_name (str, optional): The package name of the set of dependencies to look up.\n            Defaults to `native.package_name()` when unset.\n\n    Returns:\n        list: A list of labels to generated rust targets (str)\n    \"\"\"\n\n    if package_name == None:\n        package_name = native.package_name()\n\n    # Determine the relevant maps to use\n    all_dependency_maps = []\n    if normal:\n        all_dependency_maps.append(_NORMAL_DEPENDENCIES)\n    if normal_dev:\n        all_dependency_maps.append(_NORMAL_DEV_DEPENDENCIES)\n    if proc_macro:\n        all_dependency_maps.append(_PROC_MACRO_DEPENDENCIES)\n    if proc_macro_dev:\n        all_dependency_maps.append(_PROC_MACRO_DEV_DEPENDENCIES)\n    if build:\n        all_dependency_maps.append(_BUILD_DEPENDENCIES)\n    if build_proc_macro:\n        all_dependency_maps.append(_BUILD_PROC_MACRO_DEPENDENCIES)\n\n    # Default to always using normal dependencies\n    if not all_dependency_maps:\n        all_dependency_maps.append(_NORMAL_DEPENDENCIES)\n\n    dependencies = _flatten_dependency_maps(all_dependency_maps).pop(package_name, None)\n\n    if not dependencies:\n        if dependencies == None:\n            fail(\"Tried to get all_crate_deps for package \" + package_name + \" but that package had no Cargo.toml file\")\n        else:\n            return []\n\n    crate_deps = list(dependencies.pop(_COMMON_CONDITION, {}).values())\n    for condition, deps in dependencies.items():\n        crate_deps += selects.with_or({\n            tuple(_CONDITIONS[condition]): deps.values(),\n            \"//conditions:default\": [],\n        })\n\n    return crate_deps\n\ndef aliases(\n        normal = False,\n        normal_dev = False,\n        proc_macro = False,\n        proc_macro_dev = False,\n        build = False,\n        build_proc_macro = False,\n        package_name = None):\n    \"\"\"Produces a map of Crate alias names to their original label\n\n    If no dependency kinds are specified, `normal` and `proc_macro` are used by default.\n    Setting any one flag will otherwise determine the contents of the returned dict.\n\n    Args:\n        normal (bool, optional): If True, normal dependencies are included in the\n            output list.\n        normal_dev (bool, optional): If True, normal dev dependencies will be\n            included in the output list..\n        proc_macro (bool, optional): If True, proc_macro dependencies are included\n            in the output list.\n        proc_macro_dev (bool, optional): If True, dev proc_macro dependencies are\n            included in the output list.\n        build (bool, optional): If True, build dependencies are included\n            in the output list.\n        build_proc_macro (bool, optional): If True, build proc_macro dependencies are\n            included in the output list.\n        package_name (str, optional): The package name of the set of dependencies to look up.\n            Defaults to `native.package_name()` when unset.\n\n    Returns:\n        dict: The aliases of all associated packages\n    \"\"\"\n    if package_name == None:\n        package_name = native.package_name()\n\n    # Determine the relevant maps to use\n    all_aliases_maps = []\n    if normal:\n        all_aliases_maps.append(_NORMAL_ALIASES)\n    if normal_dev:\n        all_aliases_maps.append(_NORMAL_DEV_ALIASES)\n    if proc_macro:\n        all_aliases_maps.append(_PROC_MACRO_ALIASES)\n    if proc_macro_dev:\n        all_aliases_maps.append(_PROC_MACRO_DEV_ALIASES)\n    if build:\n        all_aliases_maps.append(_BUILD_ALIASES)\n    if build_proc_macro:\n        all_aliases_maps.append(_BUILD_PROC_MACRO_ALIASES)\n\n    # Default to always using normal aliases\n    if not all_aliases_maps:\n        all_aliases_maps.append(_NORMAL_ALIASES)\n        all_aliases_maps.append(_PROC_MACRO_ALIASES)\n\n    aliases = _flatten_dependency_maps(all_aliases_maps).pop(package_name, None)\n\n    if not aliases:\n        return dict()\n\n    common_items = aliases.pop(_COMMON_CONDITION, {}).items()\n\n    # If there are only common items in the dictionary, immediately return them\n    if not len(aliases.keys()) == 1:\n        return dict(common_items)\n\n    # Build a single select statement where each conditional has accounted for the\n    # common set of aliases.\n    crate_aliases = {\"//conditions:default\": dict(common_items)}\n    for condition, deps in aliases.items():\n        condition_triples = _CONDITIONS[condition]\n        for triple in condition_triples:\n            if triple in crate_aliases:\n                crate_aliases[triple].update(deps)\n            else:\n                crate_aliases.update({triple: dict(deps.items() + common_items)})\n\n    return select(crate_aliases)\n\n###############################################################################\n# WORKSPACE MEMBER DEPS AND ALIASES\n###############################################################################\n\n_NORMAL_DEPENDENCIES = {\n    \"\": {\n        _COMMON_CONDITION: {\n            \"ada-url\": Label(\"@crates_vendor//:ada-url-3.4.2\"),\n            \"anyhow\": Label(\"@crates_vendor//:anyhow-1.0.102\"),\n            \"capnp\": Label(\"@crates_vendor//:capnp-0.25.2\"),\n            \"capnp-rpc\": Label(\"@crates_vendor//:capnp-rpc-0.25.0\"),\n            \"capnpc\": Label(\"@crates_vendor//:capnpc-0.25.0\"),\n            \"cc\": Label(\"@crates_vendor//:cc-1.2.57\"),\n            \"clang-ast\": Label(\"@crates_vendor//:clang-ast-0.1.35\"),\n            \"clap\": Label(\"@crates_vendor//:clap-4.6.0\"),\n            \"codespan-reporting\": Label(\"@crates_vendor//:codespan-reporting-0.13.1\"),\n            \"encoding_rs\": Label(\"@crates_vendor//:encoding_rs-0.8.35\"),\n            \"flate2\": Label(\"@crates_vendor//:flate2-1.1.9\"),\n            \"foldhash\": Label(\"@crates_vendor//:foldhash-0.2.0\"),\n            \"futures\": Label(\"@crates_vendor//:futures-0.3.32\"),\n            \"lol_html_c_api\": Label(\"@crates_vendor//:lol_html_c_api-1.3.1\"),\n            \"nix\": Label(\"@crates_vendor//:nix-0.31.2\"),\n            \"pico-args\": Label(\"@crates_vendor//:pico-args-0.5.0\"),\n            \"proc-macro2\": Label(\"@crates_vendor//:proc-macro2-1.0.106\"),\n            \"quote\": Label(\"@crates_vendor//:quote-1.0.45\"),\n            \"ruff_python_ast\": Label(\"@crates_vendor//:ruff_python_ast-0.0.0\"),\n            \"ruff_python_parser\": Label(\"@crates_vendor//:ruff_python_parser-0.0.0\"),\n            \"scratch\": Label(\"@crates_vendor//:scratch-1.0.9\"),\n            \"serde\": Label(\"@crates_vendor//:serde-1.0.228\"),\n            \"serde_json\": Label(\"@crates_vendor//:serde_json-1.0.149\"),\n            \"static_assertions\": Label(\"@crates_vendor//:static_assertions-1.1.0\"),\n            \"swc_common\": Label(\"@crates_vendor//:swc_common-18.0.1\"),\n            \"swc_ts_fast_strip\": Label(\"@crates_vendor//:swc_ts_fast_strip-43.0.0\"),\n            \"syn\": Label(\"@crates_vendor//:syn-2.0.117\"),\n            \"thiserror\": Label(\"@crates_vendor//:thiserror-2.0.18\"),\n            \"tokio\": Label(\"@crates_vendor//:tokio-1.50.0\"),\n            \"tracing\": Label(\"@crates_vendor//:tracing-0.1.44\"),\n        },\n    },\n}\n\n_NORMAL_ALIASES = {\n    \"\": {\n        _COMMON_CONDITION: {\n        },\n    },\n}\n\n_NORMAL_DEV_DEPENDENCIES = {\n    \"\": {\n    },\n}\n\n_NORMAL_DEV_ALIASES = {\n    \"\": {\n    },\n}\n\n_PROC_MACRO_DEPENDENCIES = {\n    \"\": {\n        _COMMON_CONDITION: {\n            \"async-trait\": Label(\"@crates_vendor//:async-trait-0.1.89\"),\n            \"rustversion\": Label(\"@crates_vendor//:rustversion-1.0.22\"),\n        },\n    },\n}\n\n_PROC_MACRO_ALIASES = {\n    \"\": {\n    },\n}\n\n_PROC_MACRO_DEV_DEPENDENCIES = {\n    \"\": {\n    },\n}\n\n_PROC_MACRO_DEV_ALIASES = {\n    \"\": {\n    },\n}\n\n_BUILD_DEPENDENCIES = {\n    \"\": {\n    },\n}\n\n_BUILD_ALIASES = {\n    \"\": {\n    },\n}\n\n_BUILD_PROC_MACRO_DEPENDENCIES = {\n    \"\": {\n    },\n}\n\n_BUILD_PROC_MACRO_ALIASES = {\n    \"\": {\n    },\n}\n\n_CONDITIONS = {\n    \"aarch64-apple-darwin\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n    \"aarch64-linux-android\": [],\n    \"aarch64-unknown-linux-gnu\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\"],\n    \"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), not(any(all(target_os = \\\"linux\\\", target_env = \\\"\\\"), getrandom_backend = \\\"custom\\\", getrandom_backend = \\\"linux_raw\\\", getrandom_backend = \\\"rdrand\\\", getrandom_backend = \\\"rndr\\\"))))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\", \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n    \"cfg(all(target_arch = \\\"aarch64\\\", target_os = \\\"linux\\\"))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\"],\n    \"cfg(all(target_arch = \\\"aarch64\\\", target_vendor = \\\"apple\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n    \"cfg(all(target_arch = \\\"loongarch64\\\", target_os = \\\"linux\\\"))\": [],\n    \"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"wasi\\\", target_env = \\\"p2\\\"))\": [],\n    \"cfg(all(target_os = \\\"uefi\\\", getrandom_backend = \\\"efi_rng\\\"))\": [],\n    \"cfg(any())\": [],\n    \"cfg(any(target_arch = \\\"aarch64\\\", target_arch = \\\"x86\\\", target_arch = \\\"x86_64\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\", \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\", \"@rules_rust//rust/platform:x86_64-apple-darwin\", \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\", \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n    \"cfg(any(target_os = \\\"dragonfly\\\", target_os = \\\"freebsd\\\", target_os = \\\"hurd\\\", target_os = \\\"illumos\\\", target_os = \\\"cygwin\\\", all(target_os = \\\"horizon\\\", target_arch = \\\"arm\\\")))\": [],\n    \"cfg(any(target_os = \\\"haiku\\\", target_os = \\\"redox\\\", target_os = \\\"nto\\\", target_os = \\\"aix\\\"))\": [],\n    \"cfg(any(target_os = \\\"ios\\\", target_os = \\\"visionos\\\", target_os = \\\"watchos\\\", target_os = \\\"tvos\\\"))\": [],\n    \"cfg(any(target_os = \\\"macos\\\", target_os = \\\"openbsd\\\", target_os = \\\"vita\\\", target_os = \\\"emscripten\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\", \"@rules_rust//rust/platform:x86_64-apple-darwin\"],\n    \"cfg(any(unix, target_os = \\\"wasi\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\", \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\", \"@rules_rust//rust/platform:x86_64-apple-darwin\", \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n    \"cfg(not(all(target_arch = \\\"arm\\\", target_os = \\\"none\\\")))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\", \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\", \"@rules_rust//rust/platform:x86_64-apple-darwin\", \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\", \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n    \"cfg(not(windows))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\", \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\", \"@rules_rust//rust/platform:x86_64-apple-darwin\", \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n    \"cfg(target_os = \\\"hermit\\\")\": [],\n    \"cfg(target_os = \\\"netbsd\\\")\": [],\n    \"cfg(target_os = \\\"solaris\\\")\": [],\n    \"cfg(target_os = \\\"vxworks\\\")\": [],\n    \"cfg(target_os = \\\"wasi\\\")\": [],\n    \"cfg(unix)\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\", \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\", \"@rules_rust//rust/platform:x86_64-apple-darwin\", \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n    \"cfg(windows)\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n    \"x86_64-apple-darwin\": [\"@rules_rust//rust/platform:x86_64-apple-darwin\"],\n    \"x86_64-pc-windows-msvc\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n    \"x86_64-unknown-linux-gnu\": [\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n}\n\n###############################################################################\n\ndef crate_repositories():\n    \"\"\"A macro for defining repositories for all generated crates.\n\n    Returns:\n      A list of repos visible to the module through the module extension.\n    \"\"\"\n    maybe(\n        http_archive,\n        name = \"crates_vendor__ada-url-3.4.2\",\n        sha256 = \"223dc5cdb30f6fa4e89d2626f0263393feca2fb15e9f7043b054707a6972b10e\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/ada-url/3.4.2/download\"],\n        strip_prefix = \"ada-url-3.4.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.ada-url-3.4.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__adler2-2.0.1\",\n        sha256 = \"320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/adler2/2.0.1/download\"],\n        strip_prefix = \"adler2-2.0.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.adler2-2.0.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__ahash-0.8.12\",\n        sha256 = \"5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/ahash/0.8.12/download\"],\n        strip_prefix = \"ahash-0.8.12\",\n        build_file = Label(\"//deps/rust/crates:BUILD.ahash-0.8.12.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__aho-corasick-1.1.4\",\n        sha256 = \"ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/aho-corasick/1.1.4/download\"],\n        strip_prefix = \"aho-corasick-1.1.4\",\n        build_file = Label(\"//deps/rust/crates:BUILD.aho-corasick-1.1.4.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__allocator-api2-0.2.21\",\n        sha256 = \"683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/allocator-api2/0.2.21/download\"],\n        strip_prefix = \"allocator-api2-0.2.21\",\n        build_file = Label(\"//deps/rust/crates:BUILD.allocator-api2-0.2.21.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__anstyle-1.0.14\",\n        sha256 = \"940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/anstyle/1.0.14/download\"],\n        strip_prefix = \"anstyle-1.0.14\",\n        build_file = Label(\"//deps/rust/crates:BUILD.anstyle-1.0.14.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__anyhow-1.0.102\",\n        sha256 = \"7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/anyhow/1.0.102/download\"],\n        strip_prefix = \"anyhow-1.0.102\",\n        build_file = Label(\"//deps/rust/crates:BUILD.anyhow-1.0.102.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__ascii-1.1.0\",\n        sha256 = \"d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/ascii/1.1.0/download\"],\n        strip_prefix = \"ascii-1.1.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.ascii-1.1.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__ast_node-5.0.0\",\n        sha256 = \"2eb025ef00a6da925cf40870b9c8d008526b6004ece399cb0974209720f0b194\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/ast_node/5.0.0/download\"],\n        strip_prefix = \"ast_node-5.0.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.ast_node-5.0.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__async-trait-0.1.89\",\n        sha256 = \"9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/async-trait/0.1.89/download\"],\n        strip_prefix = \"async-trait-0.1.89\",\n        build_file = Label(\"//deps/rust/crates:BUILD.async-trait-0.1.89.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__autocfg-1.5.0\",\n        sha256 = \"c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/autocfg/1.5.0/download\"],\n        strip_prefix = \"autocfg-1.5.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.autocfg-1.5.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__base64-0.22.1\",\n        sha256 = \"72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/base64/0.22.1/download\"],\n        strip_prefix = \"base64-0.22.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.base64-0.22.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__base64-simd-0.8.0\",\n        sha256 = \"339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/base64-simd/0.8.0/download\"],\n        strip_prefix = \"base64-simd-0.8.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.base64-simd-0.8.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__better_scoped_tls-1.0.1\",\n        sha256 = \"7cd228125315b132eed175bf47619ac79b945b26e56b848ba203ae4ea8603609\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/better_scoped_tls/1.0.1/download\"],\n        strip_prefix = \"better_scoped_tls-1.0.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.better_scoped_tls-1.0.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__bitflags-2.11.0\",\n        sha256 = \"843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/bitflags/2.11.0/download\"],\n        strip_prefix = \"bitflags-2.11.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.bitflags-2.11.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__bitvec-1.0.1\",\n        sha256 = \"1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/bitvec/1.0.1/download\"],\n        strip_prefix = \"bitvec-1.0.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.bitvec-1.0.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__block-buffer-0.10.4\",\n        sha256 = \"3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/block-buffer/0.10.4/download\"],\n        strip_prefix = \"block-buffer-0.10.4\",\n        build_file = Label(\"//deps/rust/crates:BUILD.block-buffer-0.10.4.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__bstr-1.12.1\",\n        sha256 = \"63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/bstr/1.12.1/download\"],\n        strip_prefix = \"bstr-1.12.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.bstr-1.12.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__bumpalo-3.20.2\",\n        sha256 = \"5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/bumpalo/3.20.2/download\"],\n        strip_prefix = \"bumpalo-3.20.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.bumpalo-3.20.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__bytes-1.11.1\",\n        sha256 = \"1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/bytes/1.11.1/download\"],\n        strip_prefix = \"bytes-1.11.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.bytes-1.11.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__bytes-str-0.2.7\",\n        sha256 = \"7c60b5ce37e0b883c37eb89f79a1e26fbe9c1081945d024eee93e8d91a7e18b3\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/bytes-str/0.2.7/download\"],\n        strip_prefix = \"bytes-str-0.2.7\",\n        build_file = Label(\"//deps/rust/crates:BUILD.bytes-str-0.2.7.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__capnp-0.25.2\",\n        sha256 = \"c982cc37b8f646c753f3b0a24d4d40ca2eac8a9c2b9ea6fff524be67ddc184cb\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/capnp/0.25.2/download\"],\n        strip_prefix = \"capnp-0.25.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.capnp-0.25.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__capnp-futures-0.25.2\",\n        sha256 = \"73b69dfddccc57844f9a90f9d72b44b97c326914851ea94fb7da40ef9cad6e8d\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/capnp-futures/0.25.2/download\"],\n        strip_prefix = \"capnp-futures-0.25.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.capnp-futures-0.25.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__capnp-rpc-0.25.0\",\n        sha256 = \"07ccca6d26009f4d6c12b741994f33b421da613b5dcf461508e236b53ef862f1\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/capnp-rpc/0.25.0/download\"],\n        strip_prefix = \"capnp-rpc-0.25.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.capnp-rpc-0.25.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__capnpc-0.25.0\",\n        sha256 = \"d6cdfa6b0df161a71201367910265b97180541ecdb48bd08e05ef8694c295d1f\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/capnpc/0.25.0/download\"],\n        strip_prefix = \"capnpc-0.25.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.capnpc-0.25.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__castaway-0.2.4\",\n        sha256 = \"dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/castaway/0.2.4/download\"],\n        strip_prefix = \"castaway-0.2.4\",\n        build_file = Label(\"//deps/rust/crates:BUILD.castaway-0.2.4.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__cc-1.2.57\",\n        sha256 = \"7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/cc/1.2.57/download\"],\n        strip_prefix = \"cc-1.2.57\",\n        build_file = Label(\"//deps/rust/crates:BUILD.cc-1.2.57.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__cfg-if-1.0.4\",\n        sha256 = \"9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/cfg-if/1.0.4/download\"],\n        strip_prefix = \"cfg-if-1.0.4\",\n        build_file = Label(\"//deps/rust/crates:BUILD.cfg-if-1.0.4.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__cfg_aliases-0.2.1\",\n        sha256 = \"613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/cfg_aliases/0.2.1/download\"],\n        strip_prefix = \"cfg_aliases-0.2.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.cfg_aliases-0.2.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__clang-ast-0.1.35\",\n        sha256 = \"2d76fac0a007075fa8d38a6ec08df340bea2035cc0a6e9b0f6594bff1627692b\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/clang-ast/0.1.35/download\"],\n        strip_prefix = \"clang-ast-0.1.35\",\n        build_file = Label(\"//deps/rust/crates:BUILD.clang-ast-0.1.35.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__clap-4.6.0\",\n        sha256 = \"b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/clap/4.6.0/download\"],\n        strip_prefix = \"clap-4.6.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.clap-4.6.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__clap_builder-4.6.0\",\n        sha256 = \"714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/clap_builder/4.6.0/download\"],\n        strip_prefix = \"clap_builder-4.6.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.clap_builder-4.6.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__clap_derive-4.6.0\",\n        sha256 = \"1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/clap_derive/4.6.0/download\"],\n        strip_prefix = \"clap_derive-4.6.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.clap_derive-4.6.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__clap_lex-1.1.0\",\n        sha256 = \"c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/clap_lex/1.1.0/download\"],\n        strip_prefix = \"clap_lex-1.1.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.clap_lex-1.1.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__codespan-reporting-0.13.1\",\n        sha256 = \"af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/codespan-reporting/0.13.1/download\"],\n        strip_prefix = \"codespan-reporting-0.13.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.codespan-reporting-0.13.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__compact_str-0.7.1\",\n        sha256 = \"f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/compact_str/0.7.1/download\"],\n        strip_prefix = \"compact_str-0.7.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.compact_str-0.7.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__compact_str-0.9.0\",\n        sha256 = \"3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/compact_str/0.9.0/download\"],\n        strip_prefix = \"compact_str-0.9.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.compact_str-0.9.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__cpufeatures-0.2.17\",\n        sha256 = \"59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/cpufeatures/0.2.17/download\"],\n        strip_prefix = \"cpufeatures-0.2.17\",\n        build_file = Label(\"//deps/rust/crates:BUILD.cpufeatures-0.2.17.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__crc32fast-1.5.0\",\n        sha256 = \"9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/crc32fast/1.5.0/download\"],\n        strip_prefix = \"crc32fast-1.5.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.crc32fast-1.5.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__crypto-common-0.1.7\",\n        sha256 = \"78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/crypto-common/0.1.7/download\"],\n        strip_prefix = \"crypto-common-0.1.7\",\n        build_file = Label(\"//deps/rust/crates:BUILD.crypto-common-0.1.7.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__cssparser-0.36.0\",\n        sha256 = \"dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/cssparser/0.36.0/download\"],\n        strip_prefix = \"cssparser-0.36.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.cssparser-0.36.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__cssparser-macros-0.6.1\",\n        sha256 = \"13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/cssparser-macros/0.6.1/download\"],\n        strip_prefix = \"cssparser-macros-0.6.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.cssparser-macros-0.6.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__data-encoding-2.10.0\",\n        sha256 = \"d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/data-encoding/2.10.0/download\"],\n        strip_prefix = \"data-encoding-2.10.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.data-encoding-2.10.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__debugid-0.8.0\",\n        sha256 = \"bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/debugid/0.8.0/download\"],\n        strip_prefix = \"debugid-0.8.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.debugid-0.8.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__derive_more-2.1.1\",\n        sha256 = \"d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/derive_more/2.1.1/download\"],\n        strip_prefix = \"derive_more-2.1.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.derive_more-2.1.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__derive_more-impl-2.1.1\",\n        sha256 = \"799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/derive_more-impl/2.1.1/download\"],\n        strip_prefix = \"derive_more-impl-2.1.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.derive_more-impl-2.1.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__digest-0.10.7\",\n        sha256 = \"9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/digest/0.10.7/download\"],\n        strip_prefix = \"digest-0.10.7\",\n        build_file = Label(\"//deps/rust/crates:BUILD.digest-0.10.7.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__displaydoc-0.2.5\",\n        sha256 = \"97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/displaydoc/0.2.5/download\"],\n        strip_prefix = \"displaydoc-0.2.5\",\n        build_file = Label(\"//deps/rust/crates:BUILD.displaydoc-0.2.5.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__dragonbox_ecma-0.1.12\",\n        sha256 = \"fd8e701084c37e7ef62d3f9e453b618130cbc0ef3573847785952a3ac3f746bf\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/dragonbox_ecma/0.1.12/download\"],\n        strip_prefix = \"dragonbox_ecma-0.1.12\",\n        build_file = Label(\"//deps/rust/crates:BUILD.dragonbox_ecma-0.1.12.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__dtoa-1.0.11\",\n        sha256 = \"4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/dtoa/1.0.11/download\"],\n        strip_prefix = \"dtoa-1.0.11\",\n        build_file = Label(\"//deps/rust/crates:BUILD.dtoa-1.0.11.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__dtoa-short-0.3.5\",\n        sha256 = \"cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/dtoa-short/0.3.5/download\"],\n        strip_prefix = \"dtoa-short-0.3.5\",\n        build_file = Label(\"//deps/rust/crates:BUILD.dtoa-short-0.3.5.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__either-1.15.0\",\n        sha256 = \"48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/either/1.15.0/download\"],\n        strip_prefix = \"either-1.15.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.either-1.15.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__embedded-io-0.7.1\",\n        sha256 = \"9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/embedded-io/0.7.1/download\"],\n        strip_prefix = \"embedded-io-0.7.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.embedded-io-0.7.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__encoding_rs-0.8.35\",\n        sha256 = \"75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/encoding_rs/0.8.35/download\"],\n        strip_prefix = \"encoding_rs-0.8.35\",\n        build_file = Label(\"//deps/rust/crates:BUILD.encoding_rs-0.8.35.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__equivalent-1.0.2\",\n        sha256 = \"877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/equivalent/1.0.2/download\"],\n        strip_prefix = \"equivalent-1.0.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.equivalent-1.0.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__fastrand-2.3.0\",\n        sha256 = \"37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/fastrand/2.3.0/download\"],\n        strip_prefix = \"fastrand-2.3.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.fastrand-2.3.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__find-msvc-tools-0.1.9\",\n        sha256 = \"5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/find-msvc-tools/0.1.9/download\"],\n        strip_prefix = \"find-msvc-tools-0.1.9\",\n        build_file = Label(\"//deps/rust/crates:BUILD.find-msvc-tools-0.1.9.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__flate2-1.1.9\",\n        sha256 = \"843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/flate2/1.1.9/download\"],\n        strip_prefix = \"flate2-1.1.9\",\n        build_file = Label(\"//deps/rust/crates:BUILD.flate2-1.1.9.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__foldhash-0.2.0\",\n        sha256 = \"77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/foldhash/0.2.0/download\"],\n        strip_prefix = \"foldhash-0.2.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.foldhash-0.2.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__form_urlencoded-1.2.2\",\n        sha256 = \"cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/form_urlencoded/1.2.2/download\"],\n        strip_prefix = \"form_urlencoded-1.2.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.form_urlencoded-1.2.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__from_variant-3.0.0\",\n        sha256 = \"e5ff35a391aef949120a0340d690269b3d9f63460a6106e99bd07b961f345ea9\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/from_variant/3.0.0/download\"],\n        strip_prefix = \"from_variant-3.0.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.from_variant-3.0.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__funty-2.0.0\",\n        sha256 = \"e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/funty/2.0.0/download\"],\n        strip_prefix = \"funty-2.0.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.funty-2.0.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__futures-0.3.32\",\n        sha256 = \"8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/futures/0.3.32/download\"],\n        strip_prefix = \"futures-0.3.32\",\n        build_file = Label(\"//deps/rust/crates:BUILD.futures-0.3.32.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__futures-channel-0.3.32\",\n        sha256 = \"07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/futures-channel/0.3.32/download\"],\n        strip_prefix = \"futures-channel-0.3.32\",\n        build_file = Label(\"//deps/rust/crates:BUILD.futures-channel-0.3.32.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__futures-core-0.3.32\",\n        sha256 = \"7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/futures-core/0.3.32/download\"],\n        strip_prefix = \"futures-core-0.3.32\",\n        build_file = Label(\"//deps/rust/crates:BUILD.futures-core-0.3.32.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__futures-executor-0.3.32\",\n        sha256 = \"baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/futures-executor/0.3.32/download\"],\n        strip_prefix = \"futures-executor-0.3.32\",\n        build_file = Label(\"//deps/rust/crates:BUILD.futures-executor-0.3.32.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__futures-io-0.3.32\",\n        sha256 = \"cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/futures-io/0.3.32/download\"],\n        strip_prefix = \"futures-io-0.3.32\",\n        build_file = Label(\"//deps/rust/crates:BUILD.futures-io-0.3.32.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__futures-macro-0.3.32\",\n        sha256 = \"e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/futures-macro/0.3.32/download\"],\n        strip_prefix = \"futures-macro-0.3.32\",\n        build_file = Label(\"//deps/rust/crates:BUILD.futures-macro-0.3.32.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__futures-sink-0.3.32\",\n        sha256 = \"c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/futures-sink/0.3.32/download\"],\n        strip_prefix = \"futures-sink-0.3.32\",\n        build_file = Label(\"//deps/rust/crates:BUILD.futures-sink-0.3.32.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__futures-task-0.3.32\",\n        sha256 = \"037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/futures-task/0.3.32/download\"],\n        strip_prefix = \"futures-task-0.3.32\",\n        build_file = Label(\"//deps/rust/crates:BUILD.futures-task-0.3.32.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__futures-util-0.3.32\",\n        sha256 = \"389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/futures-util/0.3.32/download\"],\n        strip_prefix = \"futures-util-0.3.32\",\n        build_file = Label(\"//deps/rust/crates:BUILD.futures-util-0.3.32.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__generic-array-0.14.7\",\n        sha256 = \"85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/generic-array/0.14.7/download\"],\n        strip_prefix = \"generic-array-0.14.7\",\n        build_file = Label(\"//deps/rust/crates:BUILD.generic-array-0.14.7.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__getopts-0.2.24\",\n        sha256 = \"cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/getopts/0.2.24/download\"],\n        strip_prefix = \"getopts-0.2.24\",\n        build_file = Label(\"//deps/rust/crates:BUILD.getopts-0.2.24.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__getrandom-0.2.17\",\n        sha256 = \"ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/getrandom/0.2.17/download\"],\n        strip_prefix = \"getrandom-0.2.17\",\n        build_file = Label(\"//deps/rust/crates:BUILD.getrandom-0.2.17.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__getrandom-0.3.4\",\n        sha256 = \"899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/getrandom/0.3.4/download\"],\n        strip_prefix = \"getrandom-0.3.4\",\n        build_file = Label(\"//deps/rust/crates:BUILD.getrandom-0.3.4.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__hashbrown-0.14.5\",\n        sha256 = \"e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/hashbrown/0.14.5/download\"],\n        strip_prefix = \"hashbrown-0.14.5\",\n        build_file = Label(\"//deps/rust/crates:BUILD.hashbrown-0.14.5.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__hashbrown-0.16.1\",\n        sha256 = \"841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/hashbrown/0.16.1/download\"],\n        strip_prefix = \"hashbrown-0.16.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.hashbrown-0.16.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__heck-0.5.0\",\n        sha256 = \"2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/heck/0.5.0/download\"],\n        strip_prefix = \"heck-0.5.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.heck-0.5.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__hermit-abi-0.5.2\",\n        sha256 = \"fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/hermit-abi/0.5.2/download\"],\n        strip_prefix = \"hermit-abi-0.5.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.hermit-abi-0.5.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__hstr-3.0.4\",\n        sha256 = \"faa57007c3c9dab34df2fa4c1fb52fe9c34ec5a27ed9d8edea53254b50cd7887\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/hstr/3.0.4/download\"],\n        strip_prefix = \"hstr-3.0.4\",\n        build_file = Label(\"//deps/rust/crates:BUILD.hstr-3.0.4.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__icu_collections-2.1.1\",\n        sha256 = \"4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/icu_collections/2.1.1/download\"],\n        strip_prefix = \"icu_collections-2.1.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.icu_collections-2.1.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__icu_locale_core-2.1.1\",\n        sha256 = \"edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/icu_locale_core/2.1.1/download\"],\n        strip_prefix = \"icu_locale_core-2.1.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.icu_locale_core-2.1.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__icu_normalizer-2.1.1\",\n        sha256 = \"5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/icu_normalizer/2.1.1/download\"],\n        strip_prefix = \"icu_normalizer-2.1.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.icu_normalizer-2.1.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__icu_normalizer_data-2.1.1\",\n        sha256 = \"7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/icu_normalizer_data/2.1.1/download\"],\n        strip_prefix = \"icu_normalizer_data-2.1.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.icu_normalizer_data-2.1.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__icu_properties-2.1.2\",\n        sha256 = \"020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/icu_properties/2.1.2/download\"],\n        strip_prefix = \"icu_properties-2.1.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.icu_properties-2.1.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__icu_properties_data-2.1.2\",\n        sha256 = \"616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/icu_properties_data/2.1.2/download\"],\n        strip_prefix = \"icu_properties_data-2.1.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.icu_properties_data-2.1.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__icu_provider-2.1.1\",\n        sha256 = \"85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/icu_provider/2.1.1/download\"],\n        strip_prefix = \"icu_provider-2.1.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.icu_provider-2.1.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__idna-1.1.0\",\n        sha256 = \"3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/idna/1.1.0/download\"],\n        strip_prefix = \"idna-1.1.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.idna-1.1.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__idna_adapter-1.2.1\",\n        sha256 = \"3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/idna_adapter/1.2.1/download\"],\n        strip_prefix = \"idna_adapter-1.2.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.idna_adapter-1.2.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__if_chain-1.0.3\",\n        sha256 = \"cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/if_chain/1.0.3/download\"],\n        strip_prefix = \"if_chain-1.0.3\",\n        build_file = Label(\"//deps/rust/crates:BUILD.if_chain-1.0.3.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__indexmap-2.13.0\",\n        sha256 = \"7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/indexmap/2.13.0/download\"],\n        strip_prefix = \"indexmap-2.13.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.indexmap-2.13.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__is-macro-0.3.7\",\n        sha256 = \"1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/is-macro/0.3.7/download\"],\n        strip_prefix = \"is-macro-0.3.7\",\n        build_file = Label(\"//deps/rust/crates:BUILD.is-macro-0.3.7.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__itertools-0.14.0\",\n        sha256 = \"2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/itertools/0.14.0/download\"],\n        strip_prefix = \"itertools-0.14.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.itertools-0.14.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__itoa-1.0.17\",\n        sha256 = \"92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/itoa/1.0.17/download\"],\n        strip_prefix = \"itoa-1.0.17\",\n        build_file = Label(\"//deps/rust/crates:BUILD.itoa-1.0.17.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__jobserver-0.1.34\",\n        sha256 = \"9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/jobserver/0.1.34/download\"],\n        strip_prefix = \"jobserver-0.1.34\",\n        build_file = Label(\"//deps/rust/crates:BUILD.jobserver-0.1.34.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__js-sys-0.3.91\",\n        sha256 = \"b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/js-sys/0.3.91/download\"],\n        strip_prefix = \"js-sys-0.3.91\",\n        build_file = Label(\"//deps/rust/crates:BUILD.js-sys-0.3.91.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__libc-0.2.183\",\n        sha256 = \"b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/libc/0.2.183/download\"],\n        strip_prefix = \"libc-0.2.183\",\n        build_file = Label(\"//deps/rust/crates:BUILD.libc-0.2.183.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__link_args-0.6.0\",\n        sha256 = \"2c7721e472624c9aaad27a5eb6b7c9c6045c7a396f2efb6dabaec1b640d5e89b\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/link_args/0.6.0/download\"],\n        strip_prefix = \"link_args-0.6.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.link_args-0.6.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__litemap-0.8.1\",\n        sha256 = \"6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/litemap/0.8.1/download\"],\n        strip_prefix = \"litemap-0.8.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.litemap-0.8.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__log-0.4.29\",\n        sha256 = \"5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/log/0.4.29/download\"],\n        strip_prefix = \"log-0.4.29\",\n        build_file = Label(\"//deps/rust/crates:BUILD.log-0.4.29.bazel\"),\n    )\n\n    maybe(\n        new_git_repository,\n        name = \"crates_vendor__lol_html-2.7.2\",\n        commit = \"e3aa54798602dd27250fafde1b5a66f080046252\",\n        init_submodules = True,\n        remote = \"https://github.com/cloudflare/lol-html\",\n        build_file = Label(\"//deps/rust/crates:BUILD.lol_html-2.7.2.bazel\"),\n    )\n\n    maybe(\n        new_git_repository,\n        name = \"crates_vendor__lol_html_c_api-1.3.1\",\n        commit = \"e3aa54798602dd27250fafde1b5a66f080046252\",\n        init_submodules = True,\n        remote = \"https://github.com/cloudflare/lol-html\",\n        build_file = Label(\"//deps/rust/crates:BUILD.lol_html_c_api-1.3.1.bazel\"),\n        strip_prefix = \"c-api\",\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__memchr-2.8.0\",\n        sha256 = \"f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/memchr/2.8.0/download\"],\n        strip_prefix = \"memchr-2.8.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.memchr-2.8.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__mime-0.3.17\",\n        sha256 = \"6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/mime/0.3.17/download\"],\n        strip_prefix = \"mime-0.3.17\",\n        build_file = Label(\"//deps/rust/crates:BUILD.mime-0.3.17.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__miniz_oxide-0.8.9\",\n        sha256 = \"1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/miniz_oxide/0.8.9/download\"],\n        strip_prefix = \"miniz_oxide-0.8.9\",\n        build_file = Label(\"//deps/rust/crates:BUILD.miniz_oxide-0.8.9.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__mio-1.1.1\",\n        sha256 = \"a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/mio/1.1.1/download\"],\n        strip_prefix = \"mio-1.1.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.mio-1.1.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__new_debug_unreachable-1.0.6\",\n        sha256 = \"650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/new_debug_unreachable/1.0.6/download\"],\n        strip_prefix = \"new_debug_unreachable-1.0.6\",\n        build_file = Label(\"//deps/rust/crates:BUILD.new_debug_unreachable-1.0.6.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__nix-0.31.2\",\n        sha256 = \"5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/nix/0.31.2/download\"],\n        strip_prefix = \"nix-0.31.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.nix-0.31.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__num-bigint-0.4.6\",\n        sha256 = \"a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/num-bigint/0.4.6/download\"],\n        strip_prefix = \"num-bigint-0.4.6\",\n        build_file = Label(\"//deps/rust/crates:BUILD.num-bigint-0.4.6.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__num-integer-0.1.46\",\n        sha256 = \"7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/num-integer/0.1.46/download\"],\n        strip_prefix = \"num-integer-0.1.46\",\n        build_file = Label(\"//deps/rust/crates:BUILD.num-integer-0.1.46.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__num-traits-0.2.19\",\n        sha256 = \"071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/num-traits/0.2.19/download\"],\n        strip_prefix = \"num-traits-0.2.19\",\n        build_file = Label(\"//deps/rust/crates:BUILD.num-traits-0.2.19.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__num_cpus-1.17.0\",\n        sha256 = \"91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/num_cpus/1.17.0/download\"],\n        strip_prefix = \"num_cpus-1.17.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.num_cpus-1.17.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__once_cell-1.21.4\",\n        sha256 = \"9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/once_cell/1.21.4/download\"],\n        strip_prefix = \"once_cell-1.21.4\",\n        build_file = Label(\"//deps/rust/crates:BUILD.once_cell-1.21.4.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__outref-0.5.2\",\n        sha256 = \"1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/outref/0.5.2/download\"],\n        strip_prefix = \"outref-0.5.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.outref-0.5.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__par-core-2.0.0\",\n        sha256 = \"e96cbd21255b7fb29a5d51ef38a779b517a91abd59e2756c039583f43ef4c90f\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/par-core/2.0.0/download\"],\n        strip_prefix = \"par-core-2.0.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.par-core-2.0.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__percent-encoding-2.3.2\",\n        sha256 = \"9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/percent-encoding/2.3.2/download\"],\n        strip_prefix = \"percent-encoding-2.3.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.percent-encoding-2.3.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__phf-0.11.3\",\n        sha256 = \"1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/phf/0.11.3/download\"],\n        strip_prefix = \"phf-0.11.3\",\n        build_file = Label(\"//deps/rust/crates:BUILD.phf-0.11.3.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__phf-0.13.1\",\n        sha256 = \"c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/phf/0.13.1/download\"],\n        strip_prefix = \"phf-0.13.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.phf-0.13.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__phf_codegen-0.11.3\",\n        sha256 = \"aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/phf_codegen/0.11.3/download\"],\n        strip_prefix = \"phf_codegen-0.11.3\",\n        build_file = Label(\"//deps/rust/crates:BUILD.phf_codegen-0.11.3.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__phf_codegen-0.13.1\",\n        sha256 = \"49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/phf_codegen/0.13.1/download\"],\n        strip_prefix = \"phf_codegen-0.13.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.phf_codegen-0.13.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__phf_generator-0.11.3\",\n        sha256 = \"3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/phf_generator/0.11.3/download\"],\n        strip_prefix = \"phf_generator-0.11.3\",\n        build_file = Label(\"//deps/rust/crates:BUILD.phf_generator-0.11.3.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__phf_generator-0.13.1\",\n        sha256 = \"135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/phf_generator/0.13.1/download\"],\n        strip_prefix = \"phf_generator-0.13.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.phf_generator-0.13.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__phf_macros-0.11.3\",\n        sha256 = \"f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/phf_macros/0.11.3/download\"],\n        strip_prefix = \"phf_macros-0.11.3\",\n        build_file = Label(\"//deps/rust/crates:BUILD.phf_macros-0.11.3.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__phf_macros-0.13.1\",\n        sha256 = \"812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/phf_macros/0.13.1/download\"],\n        strip_prefix = \"phf_macros-0.13.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.phf_macros-0.13.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__phf_shared-0.11.3\",\n        sha256 = \"67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/phf_shared/0.11.3/download\"],\n        strip_prefix = \"phf_shared-0.11.3\",\n        build_file = Label(\"//deps/rust/crates:BUILD.phf_shared-0.11.3.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__phf_shared-0.13.1\",\n        sha256 = \"e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/phf_shared/0.13.1/download\"],\n        strip_prefix = \"phf_shared-0.13.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.phf_shared-0.13.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__pico-args-0.5.0\",\n        sha256 = \"5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/pico-args/0.5.0/download\"],\n        strip_prefix = \"pico-args-0.5.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.pico-args-0.5.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__pin-project-lite-0.2.17\",\n        sha256 = \"a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/pin-project-lite/0.2.17/download\"],\n        strip_prefix = \"pin-project-lite-0.2.17\",\n        build_file = Label(\"//deps/rust/crates:BUILD.pin-project-lite-0.2.17.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__potential_utf-0.1.4\",\n        sha256 = \"b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/potential_utf/0.1.4/download\"],\n        strip_prefix = \"potential_utf-0.1.4\",\n        build_file = Label(\"//deps/rust/crates:BUILD.potential_utf-0.1.4.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__ppv-lite86-0.2.21\",\n        sha256 = \"85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/ppv-lite86/0.2.21/download\"],\n        strip_prefix = \"ppv-lite86-0.2.21\",\n        build_file = Label(\"//deps/rust/crates:BUILD.ppv-lite86-0.2.21.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__precomputed-hash-0.1.1\",\n        sha256 = \"925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/precomputed-hash/0.1.1/download\"],\n        strip_prefix = \"precomputed-hash-0.1.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.precomputed-hash-0.1.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__proc-macro2-1.0.106\",\n        sha256 = \"8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/proc-macro2/1.0.106/download\"],\n        strip_prefix = \"proc-macro2-1.0.106\",\n        build_file = Label(\"//deps/rust/crates:BUILD.proc-macro2-1.0.106.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__quote-1.0.45\",\n        sha256 = \"41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/quote/1.0.45/download\"],\n        strip_prefix = \"quote-1.0.45\",\n        build_file = Label(\"//deps/rust/crates:BUILD.quote-1.0.45.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__r-efi-5.3.0\",\n        sha256 = \"69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/r-efi/5.3.0/download\"],\n        strip_prefix = \"r-efi-5.3.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.r-efi-5.3.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__radium-0.7.0\",\n        sha256 = \"dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/radium/0.7.0/download\"],\n        strip_prefix = \"radium-0.7.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.radium-0.7.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__rand-0.8.5\",\n        sha256 = \"34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/rand/0.8.5/download\"],\n        strip_prefix = \"rand-0.8.5\",\n        build_file = Label(\"//deps/rust/crates:BUILD.rand-0.8.5.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__rand_chacha-0.3.1\",\n        sha256 = \"e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/rand_chacha/0.3.1/download\"],\n        strip_prefix = \"rand_chacha-0.3.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.rand_chacha-0.3.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__rand_core-0.6.4\",\n        sha256 = \"ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/rand_core/0.6.4/download\"],\n        strip_prefix = \"rand_core-0.6.4\",\n        build_file = Label(\"//deps/rust/crates:BUILD.rand_core-0.6.4.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__regex-1.12.3\",\n        sha256 = \"e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/regex/1.12.3/download\"],\n        strip_prefix = \"regex-1.12.3\",\n        build_file = Label(\"//deps/rust/crates:BUILD.regex-1.12.3.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__regex-automata-0.4.14\",\n        sha256 = \"6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/regex-automata/0.4.14/download\"],\n        strip_prefix = \"regex-automata-0.4.14\",\n        build_file = Label(\"//deps/rust/crates:BUILD.regex-automata-0.4.14.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__regex-syntax-0.8.10\",\n        sha256 = \"dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/regex-syntax/0.8.10/download\"],\n        strip_prefix = \"regex-syntax-0.8.10\",\n        build_file = Label(\"//deps/rust/crates:BUILD.regex-syntax-0.8.10.bazel\"),\n    )\n\n    maybe(\n        new_git_repository,\n        name = \"crates_vendor__ruff_python_ast-0.0.0\",\n        commit = \"32c54189cb45a9d0409a1140265ce6d5fcec214d\",\n        init_submodules = True,\n        remote = \"https://github.com/astral-sh/ruff\",\n        build_file = Label(\"//deps/rust/crates:BUILD.ruff_python_ast-0.0.0.bazel\"),\n        strip_prefix = \"crates/ruff_python_ast\",\n    )\n\n    maybe(\n        new_git_repository,\n        name = \"crates_vendor__ruff_python_parser-0.0.0\",\n        commit = \"32c54189cb45a9d0409a1140265ce6d5fcec214d\",\n        init_submodules = True,\n        remote = \"https://github.com/astral-sh/ruff\",\n        build_file = Label(\"//deps/rust/crates:BUILD.ruff_python_parser-0.0.0.bazel\"),\n        strip_prefix = \"crates/ruff_python_parser\",\n    )\n\n    maybe(\n        new_git_repository,\n        name = \"crates_vendor__ruff_python_trivia-0.0.0\",\n        commit = \"32c54189cb45a9d0409a1140265ce6d5fcec214d\",\n        init_submodules = True,\n        remote = \"https://github.com/astral-sh/ruff\",\n        build_file = Label(\"//deps/rust/crates:BUILD.ruff_python_trivia-0.0.0.bazel\"),\n        strip_prefix = \"crates/ruff_python_trivia\",\n    )\n\n    maybe(\n        new_git_repository,\n        name = \"crates_vendor__ruff_source_file-0.0.0\",\n        commit = \"32c54189cb45a9d0409a1140265ce6d5fcec214d\",\n        init_submodules = True,\n        remote = \"https://github.com/astral-sh/ruff\",\n        build_file = Label(\"//deps/rust/crates:BUILD.ruff_source_file-0.0.0.bazel\"),\n        strip_prefix = \"crates/ruff_source_file\",\n    )\n\n    maybe(\n        new_git_repository,\n        name = \"crates_vendor__ruff_text_size-0.0.0\",\n        commit = \"32c54189cb45a9d0409a1140265ce6d5fcec214d\",\n        init_submodules = True,\n        remote = \"https://github.com/astral-sh/ruff\",\n        build_file = Label(\"//deps/rust/crates:BUILD.ruff_text_size-0.0.0.bazel\"),\n        strip_prefix = \"crates/ruff_text_size\",\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__rustc-hash-2.1.1\",\n        sha256 = \"357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/rustc-hash/2.1.1/download\"],\n        strip_prefix = \"rustc-hash-2.1.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.rustc-hash-2.1.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__rustc_version-0.4.1\",\n        sha256 = \"cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/rustc_version/0.4.1/download\"],\n        strip_prefix = \"rustc_version-0.4.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.rustc_version-0.4.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__rustversion-1.0.22\",\n        sha256 = \"b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/rustversion/1.0.22/download\"],\n        strip_prefix = \"rustversion-1.0.22\",\n        build_file = Label(\"//deps/rust/crates:BUILD.rustversion-1.0.22.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__ryu-1.0.23\",\n        sha256 = \"9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/ryu/1.0.23/download\"],\n        strip_prefix = \"ryu-1.0.23\",\n        build_file = Label(\"//deps/rust/crates:BUILD.ryu-1.0.23.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__scoped-tls-1.0.1\",\n        sha256 = \"e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/scoped-tls/1.0.1/download\"],\n        strip_prefix = \"scoped-tls-1.0.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.scoped-tls-1.0.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__scratch-1.0.9\",\n        sha256 = \"d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/scratch/1.0.9/download\"],\n        strip_prefix = \"scratch-1.0.9\",\n        build_file = Label(\"//deps/rust/crates:BUILD.scratch-1.0.9.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__selectors-0.33.0\",\n        sha256 = \"feef350c36147532e1b79ea5c1f3791373e61cbd9a6a2615413b3807bb164fb7\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/selectors/0.33.0/download\"],\n        strip_prefix = \"selectors-0.33.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.selectors-0.33.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__semver-1.0.27\",\n        sha256 = \"d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/semver/1.0.27/download\"],\n        strip_prefix = \"semver-1.0.27\",\n        build_file = Label(\"//deps/rust/crates:BUILD.semver-1.0.27.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__seq-macro-0.3.6\",\n        sha256 = \"1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/seq-macro/0.3.6/download\"],\n        strip_prefix = \"seq-macro-0.3.6\",\n        build_file = Label(\"//deps/rust/crates:BUILD.seq-macro-0.3.6.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__serde-1.0.228\",\n        sha256 = \"9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/serde/1.0.228/download\"],\n        strip_prefix = \"serde-1.0.228\",\n        build_file = Label(\"//deps/rust/crates:BUILD.serde-1.0.228.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__serde_core-1.0.228\",\n        sha256 = \"41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/serde_core/1.0.228/download\"],\n        strip_prefix = \"serde_core-1.0.228\",\n        build_file = Label(\"//deps/rust/crates:BUILD.serde_core-1.0.228.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__serde_derive-1.0.228\",\n        sha256 = \"d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/serde_derive/1.0.228/download\"],\n        strip_prefix = \"serde_derive-1.0.228\",\n        build_file = Label(\"//deps/rust/crates:BUILD.serde_derive-1.0.228.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__serde_json-1.0.149\",\n        sha256 = \"83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/serde_json/1.0.149/download\"],\n        strip_prefix = \"serde_json-1.0.149\",\n        build_file = Label(\"//deps/rust/crates:BUILD.serde_json-1.0.149.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__servo_arc-0.4.3\",\n        sha256 = \"170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/servo_arc/0.4.3/download\"],\n        strip_prefix = \"servo_arc-0.4.3\",\n        build_file = Label(\"//deps/rust/crates:BUILD.servo_arc-0.4.3.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__sha1-0.10.6\",\n        sha256 = \"e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/sha1/0.10.6/download\"],\n        strip_prefix = \"sha1-0.10.6\",\n        build_file = Label(\"//deps/rust/crates:BUILD.sha1-0.10.6.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__shlex-1.3.0\",\n        sha256 = \"0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/shlex/1.3.0/download\"],\n        strip_prefix = \"shlex-1.3.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.shlex-1.3.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__simd-adler32-0.3.8\",\n        sha256 = \"e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/simd-adler32/0.3.8/download\"],\n        strip_prefix = \"simd-adler32-0.3.8\",\n        build_file = Label(\"//deps/rust/crates:BUILD.simd-adler32-0.3.8.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__siphasher-0.3.11\",\n        sha256 = \"38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/siphasher/0.3.11/download\"],\n        strip_prefix = \"siphasher-0.3.11\",\n        build_file = Label(\"//deps/rust/crates:BUILD.siphasher-0.3.11.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__siphasher-1.0.2\",\n        sha256 = \"b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/siphasher/1.0.2/download\"],\n        strip_prefix = \"siphasher-1.0.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.siphasher-1.0.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__slab-0.4.12\",\n        sha256 = \"0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/slab/0.4.12/download\"],\n        strip_prefix = \"slab-0.4.12\",\n        build_file = Label(\"//deps/rust/crates:BUILD.slab-0.4.12.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__smallvec-1.15.1\",\n        sha256 = \"67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/smallvec/1.15.1/download\"],\n        strip_prefix = \"smallvec-1.15.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.smallvec-1.15.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__smartstring-1.0.1\",\n        sha256 = \"3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/smartstring/1.0.1/download\"],\n        strip_prefix = \"smartstring-1.0.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.smartstring-1.0.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__socket2-0.6.3\",\n        sha256 = \"3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/socket2/0.6.3/download\"],\n        strip_prefix = \"socket2-0.6.3\",\n        build_file = Label(\"//deps/rust/crates:BUILD.socket2-0.6.3.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__stable_deref_trait-1.2.1\",\n        sha256 = \"6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/stable_deref_trait/1.2.1/download\"],\n        strip_prefix = \"stable_deref_trait-1.2.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.stable_deref_trait-1.2.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__static_assertions-1.1.0\",\n        sha256 = \"a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/static_assertions/1.1.0/download\"],\n        strip_prefix = \"static_assertions-1.1.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.static_assertions-1.1.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__string_enum-1.0.2\",\n        sha256 = \"ae36a4951ca7bd1cfd991c241584a9824a70f6aff1e7d4f693fb3f2465e4030e\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/string_enum/1.0.2/download\"],\n        strip_prefix = \"string_enum-1.0.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.string_enum-1.0.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_allocator-4.0.1\",\n        sha256 = \"9d7eefd2c8b228a8c73056482b2ae4b3a1071fbe07638e3b55ceca8570cc48bb\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_allocator/4.0.1/download\"],\n        strip_prefix = \"swc_allocator-4.0.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_allocator-4.0.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_atoms-9.0.0\",\n        sha256 = \"d4ccbe2ecad10ad7432100f878a107b1d972a8aee83ca53184d00c23a078bb8a\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_atoms/9.0.0/download\"],\n        strip_prefix = \"swc_atoms-9.0.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_atoms-9.0.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_common-18.0.1\",\n        sha256 = \"a1c06698254e9b47daaf9bbb062af489a350bd8d10dfaab0cabbd32d46cec69d\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_common/18.0.1/download\"],\n        strip_prefix = \"swc_common-18.0.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_common-18.0.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_config-3.1.2\",\n        sha256 = \"72e90b52ee734ded867104612218101722ad87ff4cf74fe30383bd244a533f97\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_config/3.1.2/download\"],\n        strip_prefix = \"swc_config-3.1.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_config-3.1.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_config_macro-1.0.1\",\n        sha256 = \"7b416e8ce6de17dc5ea496e10c7012b35bbc0e3fef38d2e065eed936490db0b3\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_config_macro/1.0.1/download\"],\n        strip_prefix = \"swc_config_macro-1.0.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_config_macro-1.0.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_ecma_ast-20.0.1\",\n        sha256 = \"252124d0d786aa2338860701a067c93488747dfadbfedb16ac78f386e16a0ac4\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_ecma_ast/20.0.1/download\"],\n        strip_prefix = \"swc_ecma_ast-20.0.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_ecma_ast-20.0.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_ecma_codegen-23.0.0\",\n        sha256 = \"56116de786118dce35e90b612a1f4d952116dd2728ecb197c8064cfccf527baf\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_ecma_codegen/23.0.0/download\"],\n        strip_prefix = \"swc_ecma_codegen-23.0.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_ecma_codegen-23.0.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_ecma_codegen_macros-2.0.2\",\n        sha256 = \"e276dc62c0a2625a560397827989c82a93fd545fcf6f7faec0935a82cc4ddbb8\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_ecma_codegen_macros/2.0.2/download\"],\n        strip_prefix = \"swc_ecma_codegen_macros-2.0.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_ecma_codegen_macros-2.0.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_ecma_hooks-0.4.0\",\n        sha256 = \"8c9adff1f01550ef653e262ad709a2914d3dd6cfd559aea2e28eeeab8f895981\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_ecma_hooks/0.4.0/download\"],\n        strip_prefix = \"swc_ecma_hooks-0.4.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_ecma_hooks-0.4.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_ecma_parser-33.0.1\",\n        sha256 = \"7d237cf212d1f3ff5c0cf6ab5070f0ed4d624a0ab032ac4f0451675d31890e71\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_ecma_parser/33.0.1/download\"],\n        strip_prefix = \"swc_ecma_parser-33.0.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_ecma_parser-33.0.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_ecma_transforms_base-36.0.1\",\n        sha256 = \"bdf93abf3dc6d2b21a2a29c62b0197cd270b6105a483236ecba91993f895204e\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_ecma_transforms_base/36.0.1/download\"],\n        strip_prefix = \"swc_ecma_transforms_base-36.0.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_ecma_transforms_base-36.0.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_ecma_transforms_react-40.0.0\",\n        sha256 = \"06920cb2277974a0000f58fb7a70d23f2419dddd53de0d5de8db014a9a1163f9\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_ecma_transforms_react/40.0.0/download\"],\n        strip_prefix = \"swc_ecma_transforms_react-40.0.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_ecma_transforms_react-40.0.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_ecma_transforms_typescript-40.0.0\",\n        sha256 = \"e1f479758dfdefc5f1af7df222c13c1521b176489aba4dc6d13c3e21739f6473\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_ecma_transforms_typescript/40.0.0/download\"],\n        strip_prefix = \"swc_ecma_transforms_typescript-40.0.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_ecma_transforms_typescript-40.0.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_ecma_utils-26.0.1\",\n        sha256 = \"3669c1d92ba315caff5a80df76141367acf61b2d846231a1960e25be65a20fbd\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_ecma_utils/26.0.1/download\"],\n        strip_prefix = \"swc_ecma_utils-26.0.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_ecma_utils-26.0.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_ecma_visit-20.0.0\",\n        sha256 = \"e971a258717db3dc8939c38410d8b8cb8126f1b05b9758672daa7cae3e0248c2\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_ecma_visit/20.0.0/download\"],\n        strip_prefix = \"swc_ecma_visit-20.0.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_ecma_visit-20.0.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_eq_ignore_macros-1.0.1\",\n        sha256 = \"c16ce73424a6316e95e09065ba6a207eba7765496fed113702278b7711d4b632\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_eq_ignore_macros/1.0.1/download\"],\n        strip_prefix = \"swc_eq_ignore_macros-1.0.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_eq_ignore_macros-1.0.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_macros_common-1.0.1\",\n        sha256 = \"aae1efbaa74943dc5ad2a2fb16cbd78b77d7e4d63188f3c5b4df2b4dcd2faaae\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_macros_common/1.0.1/download\"],\n        strip_prefix = \"swc_macros_common-1.0.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_macros_common-1.0.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_sourcemap-9.3.4\",\n        sha256 = \"de08ef00f816acdd1a58ee8a81c0e1a59eefef2093aefe5611f256fa6b64c4d7\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_sourcemap/9.3.4/download\"],\n        strip_prefix = \"swc_sourcemap-9.3.4\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_sourcemap-9.3.4.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_ts_fast_strip-43.0.0\",\n        sha256 = \"c3ddefda81e6ce9209e7debd9e2f04065cb3745ce22cb1642cd53249b2210327\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_ts_fast_strip/43.0.0/download\"],\n        strip_prefix = \"swc_ts_fast_strip-43.0.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_ts_fast_strip-43.0.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__swc_visit-2.0.1\",\n        sha256 = \"62fb71484b486c185e34d2172f0eabe7f4722742aad700f426a494bb2de232a2\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/swc_visit/2.0.1/download\"],\n        strip_prefix = \"swc_visit-2.0.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.swc_visit-2.0.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__syn-2.0.117\",\n        sha256 = \"e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/syn/2.0.117/download\"],\n        strip_prefix = \"syn-2.0.117\",\n        build_file = Label(\"//deps/rust/crates:BUILD.syn-2.0.117.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__synstructure-0.13.2\",\n        sha256 = \"728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/synstructure/0.13.2/download\"],\n        strip_prefix = \"synstructure-0.13.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.synstructure-0.13.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__tap-1.0.1\",\n        sha256 = \"55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/tap/1.0.1/download\"],\n        strip_prefix = \"tap-1.0.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.tap-1.0.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__termcolor-1.4.1\",\n        sha256 = \"06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/termcolor/1.4.1/download\"],\n        strip_prefix = \"termcolor-1.4.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.termcolor-1.4.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__thiserror-2.0.18\",\n        sha256 = \"4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/thiserror/2.0.18/download\"],\n        strip_prefix = \"thiserror-2.0.18\",\n        build_file = Label(\"//deps/rust/crates:BUILD.thiserror-2.0.18.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__thiserror-impl-2.0.18\",\n        sha256 = \"ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/thiserror-impl/2.0.18/download\"],\n        strip_prefix = \"thiserror-impl-2.0.18\",\n        build_file = Label(\"//deps/rust/crates:BUILD.thiserror-impl-2.0.18.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__tinystr-0.8.2\",\n        sha256 = \"42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/tinystr/0.8.2/download\"],\n        strip_prefix = \"tinystr-0.8.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.tinystr-0.8.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__tinyvec-1.11.0\",\n        sha256 = \"3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/tinyvec/1.11.0/download\"],\n        strip_prefix = \"tinyvec-1.11.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.tinyvec-1.11.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__tinyvec_macros-0.1.1\",\n        sha256 = \"1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/tinyvec_macros/0.1.1/download\"],\n        strip_prefix = \"tinyvec_macros-0.1.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.tinyvec_macros-0.1.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__tokio-1.50.0\",\n        sha256 = \"27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/tokio/1.50.0/download\"],\n        strip_prefix = \"tokio-1.50.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.tokio-1.50.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__tracing-0.1.44\",\n        sha256 = \"63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/tracing/0.1.44/download\"],\n        strip_prefix = \"tracing-0.1.44\",\n        build_file = Label(\"//deps/rust/crates:BUILD.tracing-0.1.44.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__tracing-attributes-0.1.31\",\n        sha256 = \"7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/tracing-attributes/0.1.31/download\"],\n        strip_prefix = \"tracing-attributes-0.1.31\",\n        build_file = Label(\"//deps/rust/crates:BUILD.tracing-attributes-0.1.31.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__tracing-core-0.1.36\",\n        sha256 = \"db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/tracing-core/0.1.36/download\"],\n        strip_prefix = \"tracing-core-0.1.36\",\n        build_file = Label(\"//deps/rust/crates:BUILD.tracing-core-0.1.36.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__triomphe-0.1.15\",\n        sha256 = \"dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/triomphe/0.1.15/download\"],\n        strip_prefix = \"triomphe-0.1.15\",\n        build_file = Label(\"//deps/rust/crates:BUILD.triomphe-0.1.15.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__typenum-1.19.0\",\n        sha256 = \"562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/typenum/1.19.0/download\"],\n        strip_prefix = \"typenum-1.19.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.typenum-1.19.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__unicode-id-start-1.4.0\",\n        sha256 = \"81b79ad29b5e19de4260020f8919b443b2ef0277d242ce532ec7b7a2cc8b6007\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/unicode-id-start/1.4.0/download\"],\n        strip_prefix = \"unicode-id-start-1.4.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.unicode-id-start-1.4.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__unicode-ident-1.0.24\",\n        sha256 = \"e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/unicode-ident/1.0.24/download\"],\n        strip_prefix = \"unicode-ident-1.0.24\",\n        build_file = Label(\"//deps/rust/crates:BUILD.unicode-ident-1.0.24.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__unicode-normalization-0.1.25\",\n        sha256 = \"5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/unicode-normalization/0.1.25/download\"],\n        strip_prefix = \"unicode-normalization-0.1.25\",\n        build_file = Label(\"//deps/rust/crates:BUILD.unicode-normalization-0.1.25.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__unicode-width-0.2.2\",\n        sha256 = \"b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/unicode-width/0.2.2/download\"],\n        strip_prefix = \"unicode-width-0.2.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.unicode-width-0.2.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__unicode_names2-1.3.0\",\n        sha256 = \"d1673eca9782c84de5f81b82e4109dcfb3611c8ba0d52930ec4a9478f547b2dd\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/unicode_names2/1.3.0/download\"],\n        strip_prefix = \"unicode_names2-1.3.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.unicode_names2-1.3.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__unicode_names2_generator-1.3.0\",\n        sha256 = \"b91e5b84611016120197efd7dc93ef76774f4e084cd73c9fb3ea4a86c570c56e\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/unicode_names2_generator/1.3.0/download\"],\n        strip_prefix = \"unicode_names2_generator-1.3.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.unicode_names2_generator-1.3.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__url-2.5.8\",\n        sha256 = \"ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/url/2.5.8/download\"],\n        strip_prefix = \"url-2.5.8\",\n        build_file = Label(\"//deps/rust/crates:BUILD.url-2.5.8.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__utf8_iter-1.0.4\",\n        sha256 = \"b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/utf8_iter/1.0.4/download\"],\n        strip_prefix = \"utf8_iter-1.0.4\",\n        build_file = Label(\"//deps/rust/crates:BUILD.utf8_iter-1.0.4.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__uuid-1.22.0\",\n        sha256 = \"a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/uuid/1.22.0/download\"],\n        strip_prefix = \"uuid-1.22.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.uuid-1.22.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__version_check-0.9.5\",\n        sha256 = \"0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/version_check/0.9.5/download\"],\n        strip_prefix = \"version_check-0.9.5\",\n        build_file = Label(\"//deps/rust/crates:BUILD.version_check-0.9.5.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__vsimd-0.8.0\",\n        sha256 = \"5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/vsimd/0.8.0/download\"],\n        strip_prefix = \"vsimd-0.8.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.vsimd-0.8.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__wasi-0.11.1-wasi-snapshot-preview1\",\n        sha256 = \"ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/wasi/0.11.1+wasi-snapshot-preview1/download\"],\n        strip_prefix = \"wasi-0.11.1+wasi-snapshot-preview1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.wasi-0.11.1+wasi-snapshot-preview1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__wasip2-1.0.2-wasi-0.2.9\",\n        sha256 = \"9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/wasip2/1.0.2+wasi-0.2.9/download\"],\n        strip_prefix = \"wasip2-1.0.2+wasi-0.2.9\",\n        build_file = Label(\"//deps/rust/crates:BUILD.wasip2-1.0.2+wasi-0.2.9.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__wasm-bindgen-0.2.114\",\n        sha256 = \"6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/wasm-bindgen/0.2.114/download\"],\n        strip_prefix = \"wasm-bindgen-0.2.114\",\n        build_file = Label(\"//deps/rust/crates:BUILD.wasm-bindgen-0.2.114.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__wasm-bindgen-macro-0.2.114\",\n        sha256 = \"18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/wasm-bindgen-macro/0.2.114/download\"],\n        strip_prefix = \"wasm-bindgen-macro-0.2.114\",\n        build_file = Label(\"//deps/rust/crates:BUILD.wasm-bindgen-macro-0.2.114.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__wasm-bindgen-macro-support-0.2.114\",\n        sha256 = \"03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/wasm-bindgen-macro-support/0.2.114/download\"],\n        strip_prefix = \"wasm-bindgen-macro-support-0.2.114\",\n        build_file = Label(\"//deps/rust/crates:BUILD.wasm-bindgen-macro-support-0.2.114.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__wasm-bindgen-shared-0.2.114\",\n        sha256 = \"75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/wasm-bindgen-shared/0.2.114/download\"],\n        strip_prefix = \"wasm-bindgen-shared-0.2.114\",\n        build_file = Label(\"//deps/rust/crates:BUILD.wasm-bindgen-shared-0.2.114.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__winapi-util-0.1.11\",\n        sha256 = \"c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/winapi-util/0.1.11/download\"],\n        strip_prefix = \"winapi-util-0.1.11\",\n        build_file = Label(\"//deps/rust/crates:BUILD.winapi-util-0.1.11.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__windows-link-0.2.1\",\n        sha256 = \"f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/windows-link/0.2.1/download\"],\n        strip_prefix = \"windows-link-0.2.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.windows-link-0.2.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__windows-sys-0.61.2\",\n        sha256 = \"ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/windows-sys/0.61.2/download\"],\n        strip_prefix = \"windows-sys-0.61.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.windows-sys-0.61.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__wit-bindgen-0.51.0\",\n        sha256 = \"d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/wit-bindgen/0.51.0/download\"],\n        strip_prefix = \"wit-bindgen-0.51.0\",\n        build_file = Label(\"//deps/rust/crates:BUILD.wit-bindgen-0.51.0.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__writeable-0.6.2\",\n        sha256 = \"9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/writeable/0.6.2/download\"],\n        strip_prefix = \"writeable-0.6.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.writeable-0.6.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__wyz-0.5.1\",\n        sha256 = \"05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/wyz/0.5.1/download\"],\n        strip_prefix = \"wyz-0.5.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.wyz-0.5.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__yoke-0.8.1\",\n        sha256 = \"72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/yoke/0.8.1/download\"],\n        strip_prefix = \"yoke-0.8.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.yoke-0.8.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__yoke-derive-0.8.1\",\n        sha256 = \"b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/yoke-derive/0.8.1/download\"],\n        strip_prefix = \"yoke-derive-0.8.1\",\n        build_file = Label(\"//deps/rust/crates:BUILD.yoke-derive-0.8.1.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__zerocopy-0.8.42\",\n        sha256 = \"f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/zerocopy/0.8.42/download\"],\n        strip_prefix = \"zerocopy-0.8.42\",\n        build_file = Label(\"//deps/rust/crates:BUILD.zerocopy-0.8.42.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__zerocopy-derive-0.8.42\",\n        sha256 = \"7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/zerocopy-derive/0.8.42/download\"],\n        strip_prefix = \"zerocopy-derive-0.8.42\",\n        build_file = Label(\"//deps/rust/crates:BUILD.zerocopy-derive-0.8.42.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__zerofrom-0.1.6\",\n        sha256 = \"50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/zerofrom/0.1.6/download\"],\n        strip_prefix = \"zerofrom-0.1.6\",\n        build_file = Label(\"//deps/rust/crates:BUILD.zerofrom-0.1.6.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__zerofrom-derive-0.1.6\",\n        sha256 = \"d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/zerofrom-derive/0.1.6/download\"],\n        strip_prefix = \"zerofrom-derive-0.1.6\",\n        build_file = Label(\"//deps/rust/crates:BUILD.zerofrom-derive-0.1.6.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__zerotrie-0.2.3\",\n        sha256 = \"2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/zerotrie/0.2.3/download\"],\n        strip_prefix = \"zerotrie-0.2.3\",\n        build_file = Label(\"//deps/rust/crates:BUILD.zerotrie-0.2.3.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__zerovec-0.11.5\",\n        sha256 = \"6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/zerovec/0.11.5/download\"],\n        strip_prefix = \"zerovec-0.11.5\",\n        build_file = Label(\"//deps/rust/crates:BUILD.zerovec-0.11.5.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__zerovec-derive-0.11.2\",\n        sha256 = \"eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/zerovec-derive/0.11.2/download\"],\n        strip_prefix = \"zerovec-derive-0.11.2\",\n        build_file = Label(\"//deps/rust/crates:BUILD.zerovec-derive-0.11.2.bazel\"),\n    )\n\n    maybe(\n        http_archive,\n        name = \"crates_vendor__zmij-1.0.21\",\n        sha256 = \"b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa\",\n        type = \"tar.gz\",\n        urls = [\"https://static.crates.io/crates/zmij/1.0.21/download\"],\n        strip_prefix = \"zmij-1.0.21\",\n        build_file = Label(\"//deps/rust/crates:BUILD.zmij-1.0.21.bazel\"),\n    )\n\n    return [\n        struct(repo = \"crates_vendor__ada-url-3.4.2\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__anyhow-1.0.102\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__async-trait-0.1.89\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__capnp-0.25.2\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__capnp-rpc-0.25.0\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__capnpc-0.25.0\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__cc-1.2.57\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__clang-ast-0.1.35\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__clap-4.6.0\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__codespan-reporting-0.13.1\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__encoding_rs-0.8.35\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__flate2-1.1.9\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__foldhash-0.2.0\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__futures-0.3.32\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__lol_html_c_api-1.3.1\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__nix-0.31.2\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__pico-args-0.5.0\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__proc-macro2-1.0.106\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__quote-1.0.45\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__ruff_python_ast-0.0.0\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__ruff_python_parser-0.0.0\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__rustversion-1.0.22\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__scratch-1.0.9\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__serde-1.0.228\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__serde_json-1.0.149\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__static_assertions-1.1.0\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__swc_common-18.0.1\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__swc_ts_fast_strip-43.0.0\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__syn-2.0.117\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__thiserror-2.0.18\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__tokio-1.50.0\", is_dev_dep = False),\n        struct(repo = \"crates_vendor__tracing-0.1.44\", is_dev_dep = False),\n    ]\n"
  },
  {
    "path": "deps/rust/extension.bzl",
    "content": "\"\"\"Module extensions for using vendored crates with bzlmod. Derived from https://github.com/bazelbuild/rules_rust/blob/0.68.1/examples/hello_world/third-party-in-workspace/extension.bzl. \"\"\"\n\nload(\"//deps/rust/crates:crates.bzl\", _crate_repositories = \"crate_repositories\")\n\ndef _crate_repositories_impl(module_ctx):\n    _crate_repositories()\n    return module_ctx.extension_metadata(\n        root_module_direct_deps = [\"crates_vendor\"],\n        root_module_direct_dev_deps = [],\n    )\n\ncrate_repositories = module_extension(\n    implementation = _crate_repositories_impl,\n)\n"
  },
  {
    "path": "docs/api-updates.md",
    "content": "# JavaScript API Updates\n\nWhen adding new JavaScript APIs to workerd, please remember that all compatibility flags MUST be\ndocumented BEFORE their enable date. This is important as users need to understand what they are\nopting into when they update the date. It is the responsibility of whoever introduces the flag to\nupdate the documentation at the same time as the flag.\n\n## Compatibility definitions for Workerd\n\nCompatibility flags and dates for use within Workerd are defined in [compatibility-date.capnp](../src/workerd/io/compatibility-date.capnp).\n\nWhen you create a Workerd pull request that contains new compatibility definitions, please\ncreate a pull request to update Cloudflare docs at the same time.\n\n## Compatibility documentation in Cloudflare docs\n\nCompatibility flags should be added to the [Cloudflare Workers Compatibility flags](https://developers.cloudflare.com/workers/configuration/compatibility-flags/) documentation.\n\nThe [cloudflare-docs](https://github.com/cloudflare/cloudflare-docs) repository is hosted on GitHub.\nCompatibility flags are documented in per-feature markdown files under [src/content/compatibility-flags](https://github.com/cloudflare/cloudflare-docs/tree/production/src/content/compatibility-flags).\n"
  },
  {
    "path": "docs/benchmarking.md",
    "content": "We use a combination of micro and macro benchmarks for performance testing workerd.\n\n# Building benchmarks\n\nBenchmarks should be built using `--config=benchmark` configuration, which builds a release\nbinary with additional debug info.\n\nTo obtain most consistent results it is recommended to disable CPU frequency scaling\n(use \"performance\" governor https://wiki.debian.org/CpuFrequencyScaling)\n\n# Micro benchmarks\n\nMicro benchmarks are defined using `wd_cc_benchmark` bazel macro. Use `bazel run --config=benchmark`\non a defined target to obtain benchmarking results.\n\nSee example in [bench-json.c++](../src/workerd/tests/bench-json.c++)\n\n"
  },
  {
    "path": "docs/development.md",
    "content": "# Development\n\n## Clangd\n\nTo get language support in VSCode and other IDEs we recommend using `clangd`-based language server.\nThe server reads supplied `compile_flags.txt` file for correct options and include paths to resolve symbols\nin opened files.\n\nTo support `clangd` project-level operations like `Find References` `compile_commands.json` file needs to\nbe generated that will list all files in the project. If you do this, then it needs to be re-generated periodically\nwhen new files appear.\n\n1.  Install `gen-compile-commands`\n\n```sh\njust prepare\n```\n\n2.  Generate `compile_commands.json`\n\n```sh\njust compile-commands\n```\n\n## Dependencies\n\nIn order to install dependencies, please run the following command. Also refer to Building workerd in README.md for the list of required dependencies by platform.\n\n- For Ubuntu:\n\n```sh\njust prepare-ubuntu\n```\n\n## Code Formatting\n\nworkerd code is automatically formatted by clang-format. Run `python ./tools/cross/format.py` to reformat the code\nor use the appropriate IDE extension.\nWhile building workerd currently requires LLVM 19 or above, formatting requires clang-format 18.1.8 as different\nversions result in different format suggestions. This is automatically fetched using Bazel and does not need to be\ninstalled manually.\n\nCode formatting is checked before check-in and during `Linting` CI build.\n"
  },
  {
    "path": "docs/jsg.md",
    "content": "# JSG (JavaScript Glue) — Tutorial Guide\n\nFor terse reference tables (type mappings, macro catalog, error catalog), see\n[`src/workerd/jsg/README.md`](../src/workerd/jsg/README.md).\n\nFor file map and coding invariants, see\n[`src/workerd/jsg/AGENTS.md`](../src/workerd/jsg/AGENTS.md).\n\n---\n\n## 1. Introduction\n\n`jsg` is an abstraction API used to hide many of the complexities of translating back and\nforth between JavaScript and C++ types. Think of it as a bridge layer between the kj-based\ninternals of the runtime and the v8-based JavaScript layer.\n\nIdeally, JSG would be a complete wrapper around V8, such that code using JSG does not need to\ninteract with V8 APIs at all. Perhaps, then, different implementations of JSG could provide\nthe same interface on top of different JavaScript engines. However, as of today, this is not\nquite the case. At present application code will still need to use V8 APIs directly in some\ncases. We would like to improve this in the future.\n\n### Prerequisites\n\nBefore working with JSG, read both the [\"KJ Style Guide\"][] and [\"KJ Tour\"][] documents.\nIn particular, the [\"KJ Style Guide\"][] introduces a differentiation between \"Value Types\"\nand \"Resource Types\":\n\n> There are two kinds of types: values and resources. Value types are simple data\n> structures; they serve no purpose except to represent pure data. Resource types\n> represent live objects with state and behavior, and often represent resources\n> external to the program.\n\nJSG embraces these concepts and offers a C++-to-JavaScript type-mapping layer that is\nexplicitly built around them.\n\n---\n\n## 2. Getting Started\n\n### `jsg::Lock&`\n\nIn order to execute JavaScript on the current thread, a lock must be acquired on the\n`v8::Isolate`. The `jsg::Lock&` represents the current lock. It is passed as an argument to\nmany methods that require access to the JavaScript isolate and context. By convention, this\nargument is always named `js`.\n\nThe `jsg::Lock` interface itself provides access to basic JavaScript functionality, such as\nthe ability to construct basic JavaScript values and call JavaScript functions.\n\nFor Resource Types, all methods declared with `JSG_METHOD` and similar macros optionally take\na `jsg::Lock&` as the first parameter:\n\n```cpp\nclass Foo: public jsg::Object {\npublic:\n  void foo(jsg::Lock& js, int x, int y) {\n    // ...\n  }\n\n  JSG_RESOURCE_TYPE(Foo) {\n    JSG_METHOD(foo);\n  }\n};\n```\n\nRefer to the `jsg.h` header file for more detail on the specific methods available on\n`jsg::Lock`.\n\n### V8 System Setup\n\nBefore using any JSG functionality, you must initialize V8 by creating a `V8System` instance.\nThis performs process-wide initialization and should be created once, typically in `main()`.\n\n```cpp\n#include <workerd/jsg/setup.h>\n\nint main() {\n  // Basic initialization with default platform\n  jsg::V8System v8System;\n\n  // Or with V8 flags\n  kj::ArrayPtr<const kj::StringPtr> flags = {\"--expose-gc\"_kj, \"--single-threaded-gc\"_kj};\n  jsg::V8System v8System(flags);\n\n  // Or with a custom platform\n  auto platform = jsg::defaultPlatform(4);  // 4 background threads\n  jsg::V8System v8System(*platform, flags, platform.get());\n\n  // ... use JSG APIs ...\n}\n```\n\n**Important:** Only one `V8System` can exist per process. It must be created before any other\nJSG operations and destroyed last.\n\n#### Custom Platform\n\nThe default V8 platform uses a background thread pool for tasks like garbage collection and\ncompilation. You can customize the thread pool size:\n\n```cpp\n// Create platform with specific thread count\n// Pass 0 for auto-detect (uses available CPU cores)\nkj::Own<v8::Platform> platform = jsg::defaultPlatform(0);\n```\n\nIn production (like workerd), you may want to wrap the platform to customize behavior.\nFor example, workerd wraps the platform to control `Date.now()` timing:\n\n```cpp\n// In workerd/server/v8-platform-impl.h\nclass WorkerdPlatform final: public v8::Platform {\npublic:\n  explicit WorkerdPlatform(v8::Platform& inner): inner(inner) {}\n\n  // Override to return KJ time instead of system time\n  double CurrentClockTimeMillis() noexcept override;\n\n  // All other methods delegate to inner platform\n  // ...\nprivate:\n  v8::Platform& inner;\n};\n\n// Usage in main():\nauto platform = jsg::defaultPlatform(0);\nWorkerdPlatform v8Platform(*platform);\njsg::V8System v8System(v8Platform, flags, platform.get());\n```\n\n#### Fatal Error Handling\n\nYou can set a custom callback for fatal V8 errors:\n\n```cpp\nvoid myFatalErrorHandler(kj::StringPtr location, kj::StringPtr message) {\n  KJ_LOG(FATAL, \"V8 fatal error\", location, message);\n}\n\njsg::V8System::setFatalErrorCallback(&myFatalErrorHandler);\n```\n\n### `Isolate<TypeWrapper>`\n\nAn Isolate represents an independent V8 execution environment. Multiple isolates can run\nconcurrently on different threads. Each isolate has its own heap and cannot directly share\nJavaScript objects with other isolates.\n\nTo create an isolate, first declare your isolate type using `JSG_DECLARE_ISOLATE_TYPE`:\n\n```cpp\n// Declare an isolate type that can use MyGlobalObject and MyApiClass\nJSG_DECLARE_ISOLATE_TYPE(MyIsolate, MyGlobalObject, MyApiClass, AnotherClass);\n```\n\nThen instantiate it:\n\n```cpp\n// Create an isolate observer (for metrics/debugging)\nauto observer = kj::heap<jsg::IsolateObserver>();\n\n// Create the isolate\nMyIsolate isolate(v8System, kj::mv(observer));\n```\n\n#### Isolate with Configuration\n\nIf your API types require configuration (e.g., compatibility flags):\n\n```cpp\nstruct MyConfiguration {\n  bool enableFeatureX;\n  kj::StringPtr version;\n};\n\nMyIsolate isolate(v8System, MyConfiguration{.enableFeatureX = true, .version = \"1.0\"_kj},\n                  kj::mv(observer));\n```\n\n#### Isolate Groups (V8 Sandboxing)\n\nWhen V8 sandboxing is enabled, isolates can be grouped to share a sandbox or isolated for\nstronger security boundaries:\n\n```cpp\n// Use the default group (shared sandbox - more memory efficient)\n// This is what workerd uses in production\nauto group = v8::IsolateGroup::GetDefault();\nMyIsolate isolate(v8System, group, config, kj::mv(observer));\n\n// Or create a new isolate group for stronger isolation\n// (separate sandbox, uses more memory)\nauto isolatedGroup = v8::IsolateGroup::Create();\nMyIsolate isolate(v8System, isolatedGroup, config, kj::mv(observer));\n```\n\nIn workerd, the default group is used for all worker isolates. This allows multiple\nisolates to share the V8 sandbox memory region, reducing overall memory usage while\nstill maintaining isolate-level JavaScript isolation.\n\n### Taking a Lock\n\nBefore executing JavaScript, you must acquire a lock on the isolate. Only one thread can\nhold the lock at a time:\n\n```cpp\n// Method 1: Using runInLockScope (recommended)\nisolate.runInLockScope([&](MyIsolate::Lock& lock) {\n  // JavaScript execution happens here\n  auto context = lock.newContext<MyGlobalObject>();\n  // ...\n});\n\n// Method 2: Manual lock (when you need more control)\njsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {\n  MyIsolate::Lock lock(isolate, stackScope);\n  lock.withinHandleScope([&] {\n    // ...\n  });\n});\n```\n\n### Creating a Context\n\nA context provides the global object and scope for JavaScript execution:\n\n```cpp\nisolate.runInLockScope([&](MyIsolate::Lock& lock) {\n  // Create a context with MyGlobalObject as the global\n  jsg::JsContext<MyGlobalObject> jsContext = lock.newContext<MyGlobalObject>();\n\n  // Access the context handle for V8 operations\n  v8::Local<v8::Context> v8Context = jsContext.getHandle(lock);\n\n  // Enter the context to execute JavaScript\n  v8::Context::Scope contextScope(v8Context);\n\n  // Now you can execute JavaScript...\n});\n```\n\n#### Context Options\n\n```cpp\njsg::NewContextOptions options {\n  // Add options as needed\n};\nauto context = lock.newContext<MyGlobalObject>(options, constructorArg1, constructorArg2);\n```\n\n### Using the Lock\n\nThe `Lock` class provides access to type wrapping and unwrapping:\n\n```cpp\nisolate.runInLockScope([&](MyIsolate::Lock& lock) {\n  auto jsContext = lock.newContext<MyGlobalObject>();\n  v8::Local<v8::Context> context = jsContext.getHandle(lock);\n  v8::Context::Scope contextScope(context);\n\n  // Wrap a C++ value to JavaScript\n  v8::Local<v8::Value> jsValue = lock.wrap(context, kj::str(\"hello\"));\n\n  // Unwrap a JavaScript value to C++\n  kj::String cppValue = lock.unwrap<kj::String>(context, jsValue);\n\n  // Get a type handler for specific operations\n  const auto& handler = lock.getTypeHandler<MyApiClass>();\n\n  // Get a constructor function\n  jsg::JsObject constructor = lock.getConstructor<MyApiClass>(context);\n});\n```\n\n### `IsolateBase`\n\n`IsolateBase` is the non-templated base class of `Isolate<T>`, providing common functionality:\n\n```cpp\n// Get the IsolateBase from a v8::Isolate pointer\njsg::IsolateBase& base = jsg::IsolateBase::from(v8Isolate);\n\n// Terminate JavaScript execution (can be called from another thread)\nbase.terminateExecution();\n\n// Configure isolate behavior\nbase.setAllowEval(lock, true);           // Enable/disable eval()\nbase.setCaptureThrowsAsRejections(lock, true);  // Convert throws to rejections\nbase.setNodeJsCompatEnabled(lock, true);  // Enable Node.js compatibility\n\n// Check configuration\nbool nodeCompat = base.isNodeJsCompatEnabled();\nbool topLevelAwait = base.isTopLevelAwaitEnabled();\n```\n\n### Complete Example\n\n```cpp\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/setup.h>\n\n// Define your API types\nclass MyGlobalObject: public jsg::Object, public jsg::ContextGlobal {\npublic:\n  kj::String greet(kj::String name) {\n    return kj::str(\"Hello, \", name, \"!\");\n  }\n\n  JSG_RESOURCE_TYPE(MyGlobalObject) {\n    JSG_METHOD(greet);\n  }\n};\n\n// Declare the isolate type\nJSG_DECLARE_ISOLATE_TYPE(MyIsolate, MyGlobalObject);\n\nint main() {\n  // Initialize V8\n  jsg::V8System v8System;\n\n  // Create isolate\n  auto observer = kj::heap<jsg::IsolateObserver>();\n  MyIsolate isolate(v8System, kj::mv(observer));\n\n  // Execute JavaScript\n  isolate.runInLockScope([&](MyIsolate::Lock& lock) {\n    auto jsContext = lock.newContext<MyGlobalObject>();\n    v8::Local<v8::Context> context = jsContext.getHandle(lock);\n    v8::Context::Scope contextScope(context);\n\n    // Compile and run JavaScript\n    auto source = lock.str(\"greet('World')\");\n    auto script = jsg::check(v8::Script::Compile(context, source));\n    auto result = jsg::check(script->Run(context));\n\n    // Convert result to C++ string\n    kj::String greeting = lock.unwrap<kj::String>(context, result);\n    KJ_LOG(INFO, greeting);  // \"Hello, World!\"\n  });\n\n  return 0;\n}\n```\n\n### Real-World Reference: workerd Initialization\n\nFor a production example, see how workerd initializes V8 in `src/workerd/server/workerd.c++`:\n\n```cpp\n// From workerd.c++ serveImpl()\nauto platform = jsg::defaultPlatform(0);\nWorkerdPlatform v8Platform(*platform);\njsg::V8System v8System(v8Platform,\n    KJ_MAP(flag, config.getV8Flags()) -> kj::StringPtr { return flag; },\n    platform.get());\n```\n\nAnd how isolates are created in `src/workerd/server/server.c++`:\n\n```cpp\n// From server.c++ when creating a worker\nauto isolateGroup = v8::IsolateGroup::GetDefault();\nauto api = kj::heap<WorkerdApi>(globalContext->v8System, def.featureFlags, extensions,\n    limitEnforcer->getCreateParams(), isolateGroup, kj::mv(jsgobserver),\n    *memoryCacheProvider, pythonConfig);\n```\n\n---\n\n## 3. Value Types\n\nValue Types in JSG include both primitives (e.g. strings, numbers, booleans, etc) and\nrelatively simple structured types that do nothing but convey data. See the README for the\ncomplete [primitive type mapping table](../src/workerd/jsg/README.md#primitive-type-mapping).\n\n### How Does JSG Convert Values?\n\nV8 provides an API for working with JavaScript types from within C++. Unfortunately, this API\ncan be a bit cumbersome to work with directly. JSG provides an additional set of mechanisms\nthat wrap the V8 APIs and translate those into more ergonomic C++ types and structures.\nExactly how this mechanism works is covered in the advanced section on\n[Wrappable Internals](#19-wrappable-internals).\n\nFor example, when mapping from JavaScript into C++, when JSG encounters a string value, it\ncan convert that into either a `kj::String` or `jsg::USVString`, depending on what is needed\nby the C++ layer. Likewise, when translating from C++ to JavaScript, JSG will generate a\nJavaScript `string` whenever it encounters a `kj::String`, `kj::StringPtr`, or\n`jsg::USVString`.\n\nJSG will _not_ translate JavaScript `string` to `kj::StringPtr`.\n\n### Nullable/Optional Types (`kj::Maybe<T>` and `jsg::Optional<T>`)\n\nA nullable type `T` can be either `null` or `T`, where `T` can be any Value or Resource type.\nThis is represented in JSG using `kj::Maybe<T>`.\n\nAn optional type `T` can be either `undefined` or `T`. This is represented in JSG using\n`jsg::Optional<T>`.\n\nTake careful note of the differences: `kj::Maybe<kj::String>` allows `null` or `undefined`;\n`jsg::Optional<kj::String>` only allows `undefined`.\n\nAt the C++ layer, `kj::Maybe<T>` and `jsg::Optional<T>` are semantically equivalent. Their\nvalue is one of either `kj::none` or type `T`. When an empty `kj::Maybe<T>` is passed out to\nJavaScript, it is mapped to `null`. When an empty `jsg::Optional<T>` is passed out to\nJavaScript, it is mapped to `undefined`.\n\nThis means that JavaScript `undefined` can be translated to `kj::Maybe<T>`, but\n`kj::Maybe<T>` passed back out to JavaScript will translate to JavaScript `null`.\n\nSee the README for the [nullable/optional semantics table](../src/workerd/jsg/README.md#nullableoptional-semantics).\n\n### Union Types (`kj::OneOf<T...>`)\n\nA union type `<T1, T2, ...>` is one whose value is one of the types listed. It is represented\nin JSG using `kj::OneOf<T...>`.\n\nFor example, `kj::OneOf<kj::String, uint16_t, kj::Maybe<bool>>` can be either: a) a string,\nb) a 16-bit integer, c) `null`, or (d) `true` or `false`.\n\n**Important:** JSG validates union types at compile-time according to Web IDL rules:\n\n- A union can have at most one nullable type (`kj::Maybe<T>`) or dictionary type\n- A union can have at most one numeric type, one string type, one boolean type, etc.\n- Multiple interface types (JSG_RESOURCE types) are allowed as long as they are distinct\n\nFor example:\n\n```cpp\n// Valid: different distinguishable categories\nkj::OneOf<kj::String, double, bool>  // string, numeric, boolean\n\n// Valid: multiple interface types (different classes)\nkj::OneOf<Ref<ClassA>, Ref<ClassB>>\n\n// Invalid: multiple numerics in same union\nkj::OneOf<int, double>  // Compile error!\n\n// Invalid: multiple strings in same union\nkj::OneOf<kj::String, USVString>  // Compile error!\n\n// Invalid: dictionary + nullable\nkj::OneOf<kj::Maybe<int>, MyStruct>  // Compile error!\n```\n\nSee the README's [Web IDL union validation rules](../src/workerd/jsg/README.md#web-idl-union-validation-rules)\nfor the complete list.\n\nWhen a value could match multiple types in a union, JSG attempts to match types in a specific\norder based on their Web IDL category. Interface types are checked first, then dictionaries,\nthen sequences, then primitives.\n\n#### Web IDL Type Categories\n\nWeb IDL defines nine distinguishable type categories. JSG maps these to C++ concepts\n(defined in `web-idl.h`):\n\n| Web IDL Category  | JSG Concept            | C++ Types                                          |\n| ----------------- | ---------------------- | -------------------------------------------------- |\n| Boolean           | `BooleanType`          | `bool`, `NonCoercible<bool>`                       |\n| Numeric           | `NumericType`          | `int`, `double`, `uint32_t`, etc.                  |\n| String            | `StringType`           | `kj::String`, `USVString`, `DOMString`, `JsString` |\n| Object            | `ObjectType`           | `v8::Local<v8::Object>`, `v8::Global<v8::Object>`  |\n| Symbol            | `SymbolType`           | (not yet implemented)                              |\n| Interface-like    | `InterfaceLikeType`    | `JSG_RESOURCE` types, `BufferSource`               |\n| Callback function | `CallbackFunctionType` | `kj::Function<T>`, `Constructor<T>`                |\n| Dictionary-like   | `DictionaryLikeType`   | `JSG_STRUCT` types, `Dict<V, K>`                   |\n| Sequence-like     | `SequenceLikeType`     | `kj::Array<T>`, `Sequence<T>`                      |\n\nYou can use these concepts in your own code for type introspection:\n\n```cpp\n#include <workerd/jsg/web-idl.h>\n\ntemplate <typename T>\nvoid process(T value) {\n  if constexpr (jsg::webidl::StringType<T>) {\n    // Handle string types\n  } else if constexpr (jsg::webidl::NumericType<T>) {\n    // Handle numeric types\n  } else if constexpr (jsg::webidl::NonCallbackInterfaceType<T>) {\n    // Handle JSG_RESOURCE types\n  }\n}\n```\n\n#### Nullable Type Counting\n\nThe `nullableTypeCount<T...>` template recursively counts nullable types in a union,\nincluding through nested `kj::OneOf` types:\n\n```cpp\n// Count = 1\nnullableTypeCount<kj::Maybe<int>>\n\n// Count = 2 (nested nullables)\nnullableTypeCount<kj::Maybe<kj::OneOf<kj::Maybe<int>, kj::String>>>\n```\n\n### Array Types (`kj::Array<T>` and `kj::ArrayPtr<T>`)\n\nThe `kj::Array<T>` and `kj::ArrayPtr<T>` types map to JavaScript arrays. Here, `T` can be\nany value or resource type. The types `kj::Array<kj::byte>` and `kj::ArrayPtr<kj::byte>` are\nhandled differently (see [TypedArrays](#typedarrays)).\n\n```cpp\nvoid doSomething(kj::Array<kj::String> strings) {\n  KJ_DBG(strings[0]);  // a\n  KJ_DBG(strings[1]);  // b\n  KJ_DBG(strings[2]);  // c\n}\n```\n\n```js\ndoSomething(['a', 'b', 'c']);\n```\n\n### Set Type (`kj::HashSet<T>`)\n\nThe `kj::HashSet<T>` type maps to JavaScript sets. There are currently some restrictions —\nfor example, you cannot have a `kj::HashSet<CustomStruct>`.\n\n```cpp\nvoid doSomething(kj::HashSet<kj::String> strings) {\n  KJ_DBG(strings.has(\"a\"));\n}\n```\n\n```js\ndoSomething(new Set(['a', 'b', 'c']));\n```\n\n### Sequence Types (`jsg::Sequence<T>`)\n\nA [Sequence][] is any JavaScript object that implements `Symbol.iterator`. At the C++ level,\na `jsg::Sequence<T>` is semantically identical to `kj::Array<T>` but JSG handles the mapping\ndifferently:\n\n- With `kj::Array<T>`, the input from JavaScript _must_ always be a JavaScript array.\n- With `jsg::Sequence<T>`, the input can be any object implementing `Symbol.iterator`.\n\nWhen a `jsg::Sequence<T>` is passed back out to JavaScript, JSG always produces an array.\n\n```cpp\nvoid doSomething(jsg::Sequence<kj::String> strings) {\n  KJ_DBG(strings[0]);  // a\n}\n```\n\n```js\ndoSomething({\n  *[Symbol.iterator]() {\n    yield 'a';\n    yield 'b';\n    yield 'c';\n  },\n});\n\n// or simply:\ndoSomething(['a', 'b', 'c']);\n```\n\n### Generator Types (`jsg::Generator<T>` and `jsg::AsyncGenerator<T>`)\n\n`jsg::Generator<T>` provides an alternative to `jsg::Sequence` for handling objects that\nimplement `Symbol.iterator`. Whereas `jsg::Sequence` produces a complete copy of the iterable\nsequence equivalent to `kj::Array`, `jsg::Generator<T>` provides an API for iterating over\neach item one at a time.\n\n```cpp\nvoid doSomething(jsg::Generator<kj::String> strings) {\n  strings.forEach([](jsg::Lock& js, kj::String value, jsg::GeneratorContext<kj::String> ctx) {\n    KJ_DBG(value);\n  });\n}\n```\n\nFor `jsg::Generator<T>`, iteration is fully synchronous. To support asynchronous generators\nand async iteration, JSG provides `jsg::AsyncGenerator<T>` with a nearly identical C++ API.\n\n### Record/Dictionary Types (`jsg::Dict<T>`)\n\nA [Record][] type is an ordered set of key-value pairs. In JavaScript they are ordinary\nobjects whose string-keys all map to the same type of value.\n\n```cpp\n// Given jsg::Dict<bool>, a matching JS object would be:\n// { \"abc\": true, \"xyz\": false, \"foo\": true }\n```\n\nImportantly, the keys are _always_ strings and the values are _always_ the same type.\n\n### Non-Coercible and Lenient Types\n\nBy default, JSG supports automatic coercion between JavaScript types where possible. For\ninstance, when a `kj::String` is expected, any JavaScript value that can be coerced into a\nstring can be passed (including `null` → `'null'`, `undefined` → `'undefined'`).\n\n`jsg::NonCoercible<T>` declares that you want a type `T` but do _not_ want automatic\ncoercion. For example, `jsg::NonCoercible<kj::String>` will _only_ accept string values.\nAt the time of this writing, the only supported values of `T` are `kj::String`, `bool`, and\n`double`.\n\n`jsg::LenientOptional<T>` is like `jsg::Optional<T>` but instead of throwing a type error\nfor incorrect values, it ignores them and passes `undefined` instead.\n\n### TypedArrays (`kj::Array<kj::byte>` and `jsg::BufferSource`)\n\nIn V8, `TypedArray`s and `ArrayBuffer`s are backed by a `v8::BackingStore` instance. JSG\nprovides two type mappings.\n\n**`kj::Array<kj::byte>`** — When receiving from JavaScript, the `kj::Array<kj::byte>`\nprovides a _view_ over the same underlying `v8::BackingStore` (no copy):\n\n```\n                   +------------------+\n                   | v8::BackingStore |\n                   +------------------+\n                     /             \\\n   +-----------------+             +---------------------+\n   | v8::ArrayBuffer | ----------> | kj::Array<kj::byte> |\n   +-----------------+             +---------------------+\n```\n\nBecause both are _mutable_, changes in one are immediately readable by the other. When a\n`kj::Array<kj::byte>` is passed _out_ to JavaScript, it maps to a _new_ `ArrayBuffer`\nover the same memory (ownership transfers to `v8::BackingStore`).\n\nNote that passing a single backing store back and forth across the JS/C++ boundary multiple\ntimes creates nested layers of `v8::BackingStore` and `kj::Array<kj::byte>` all pointing at\nthe same underlying allocation:\n\n```\n  std::shared_ptr<v8::BackingStore>\n                |\n        kj::Array<kj::byte>\n                |\n  std::shared_ptr<v8::BackingStore>\n                |\n        kj::Array<kj::byte>\n                |\n               ...\n```\n\n**`jsg::BufferSource`** — A more nuanced mapping that wraps the v8 handle of a given\n`TypedArray` or `ArrayBuffer` and remembers its type. When passed back out to JavaScript, it\nmaps to exactly the same kind of object that was passed in (e.g. `Uint16Array` → `Uint16Array`).\nThe same `std::shared_ptr<v8::BackingStore>` is maintained. Also supports detaching the\nbacking store, which is important for ownership transfer. See the\n[BackingStore & BufferSource](#21-backingstore-and-buffersource) advanced section for the\nfull API.\n\n### Functions (`jsg::Function<Ret(Args...)>`)\n\n`jsg::Function<Ret(Args...)>` provides a wrapper around JavaScript functions, making it easy\nto call them from C++, store references to them, and wrap C++ lambdas as JavaScript functions.\n\n**Taking a JS function and invoking it from C++:**\n\n```cpp\nvoid doSomething(jsg::Lock& js, jsg::Function<void(int)> callback) {\n  callback(js, 1);\n}\n```\n\n```js\ndoSomething((arg) => {\n  console.log(arg);\n}); // prints \"1\"\n```\n\n**Wrapping a C++ lambda as a JS function:**\n\n```cpp\njsg::Function<void(int)> getFunction() {\n  return jsg::Function<void(int)>([](jsg::Lock& js, int val) {\n    KJ_DBG(val);\n  });\n}\n```\n\n```js\nconst func = getFunction();\nfunc(1); // prints 1\n```\n\n### Promises (`jsg::Promise<T>`)\n\n`jsg::Promise<T>` wraps a JavaScript promise with a syntax that makes it more natural and\nergonomic to consume within C++.\n\n#### Creating Promises\n\n```cpp\n// Already-resolved promise\njsg::Promise<int> resolved = js.resolvedPromise(42);\njsg::Promise<void> resolvedVoid = js.resolvedPromise();\n\n// Rejected promise\njsg::Promise<int> rejected = js.rejectedPromise<int>(js.error(\"Something went wrong\"));\n\n// Promise with resolver for later fulfillment\nauto [promise, resolver] = js.newPromiseAndResolver<int>();\n// Later...\nresolver.resolve(js, 42);\n// Or reject:\nresolver.reject(js, js.error(\"Failed\"));\n```\n\n#### Chaining with `.then()` and `.catch_()`\n\n```cpp\njsg::Promise<int> promise = // ...\n\n// Chain with both success and error handlers\npromise.then(js, [](jsg::Lock& js, int value) {\n  return value * 2;  // Returns jsg::Promise<int>\n}, [](jsg::Lock& js, jsg::Value exception) {\n  return 0;  // Must return the same type as the success handler\n});\n\n// Chain with only a success handler (errors propagate)\npromise.then(js, [](jsg::Lock& js, int value) {\n  return kj::str(\"Value: \", value);  // Returns jsg::Promise<kj::String>\n});\n\n// Chain with only an error handler\npromise.catch_(js, [](jsg::Lock& js, jsg::Value exception) {\n  return 0;\n});\n```\n\n**Important:** Both handlers passed to `.then()` must return exactly the same type.\n\n#### `Promise<void>`\n\n```cpp\njsg::Promise<void> promise = // ...\n\npromise.then(js, [](jsg::Lock& js) {\n  // No value parameter for Promise<void>\n});\n```\n\n#### The Resolver\n\n`jsg::Promise<T>::Resolver` allows you to fulfill or reject a promise from elsewhere:\n\n```cpp\nclass MyAsyncOperation {\n  jsg::Promise<kj::String>::Resolver resolver;\n\npublic:\n  jsg::Promise<kj::String> start(jsg::Lock& js) {\n    auto [promise, r] = js.newPromiseAndResolver<kj::String>();\n    resolver = kj::mv(r);\n    return kj::mv(promise);\n  }\n\n  void complete(jsg::Lock& js, kj::String result) {\n    resolver.resolve(js, kj::mv(result));\n  }\n\n  void fail(jsg::Lock& js, kj::Exception error) {\n    resolver.reject(js, kj::mv(error));\n  }\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(resolver);  // Important for GC!\n  }\n};\n```\n\n#### `.whenResolved()` and `.consumeHandle()`\n\nUnlike `.then()`, `whenResolved()` doesn't consume the promise and can be called multiple\ntimes to create branches:\n\n```cpp\njsg::Promise<void> done = promise.whenResolved(js);\n```\n\nTo pass a promise to V8 APIs or return it directly:\n\n```cpp\nv8::Local<v8::Promise> handle = promise.consumeHandle(js);\n```\n\nAfter calling `consumeHandle()`, the `jsg::Promise` is consumed and cannot be used again.\n\n#### Returning Promises from Methods\n\n```cpp\nclass MyApi: public jsg::Object {\npublic:\n  jsg::Promise<kj::String> fetchData(jsg::Lock& js) {\n    auto [promise, resolver] = js.newPromiseAndResolver<kj::String>();\n    // ... start async operation ...\n    return kj::mv(promise);\n  }\n\n  JSG_RESOURCE_TYPE(MyApi) {\n    JSG_METHOD(fetchData);\n  }\n};\n```\n\n### Symbols with `jsg::Name`\n\n`jsg::Name` wraps values that can be used as property names in JavaScript (strings and\nsymbols). It is used when an API needs to accept either and treat them the same.\n\n### Reference Types (`jsg::V8Ref<T>`, `jsg::Value`, `jsg::Ref<T>`)\n\n#### `jsg::V8Ref<T>` (and `jsg::Value`, and `jsg::HashableV8Ref<T>`)\n\n`jsg::V8Ref<T>` holds a persistent reference to a JavaScript type. The `T` must be one of\nthe `v8` types (e.g. `v8::Object`, `v8::Function`, etc). A `jsg::V8Ref<T>` maintains a\n_strong_ reference, keeping the value from being garbage collected.\n\nThe `jsg::Value` type is a typedef alias for `jsg::V8Ref<v8::Value>`.\n\n`jsg::V8Ref<T>` are reference counted. Multiple instances can share references to the same\nunderlying JavaScript value. When the last instance is destroyed, the underlying value is\nfreed to be garbage collected.\n\n```cpp\njsg::V8Ref<v8::Boolean> boolRef1 = js.v8Ref(v8::True(js.v8Isolate));\njsg::V8Ref<v8::Boolean> boolRef2 = boolRef1.addRef(js);\n\nKJ_DBG(boolRef1 == boolRef2);  // prints \"true\"\n\n// Getting v8::Local<T> requires a v8::HandleScope on the stack:\nv8::Local<v8::Boolean> boolLocal = js.withinHandleScope([&] {\n  return boolRef1.getHandle(js);\n});\n```\n\n`jsg::HashableV8Ref<T>` is a subclass that also implements `hashCode()` for use as a key\nin a `kj::HashTable`.\n\n#### `jsg::Ref<T>`\n\n`jsg::Ref<T>` holds a persistent reference to a JSG Resource Type. It holds a _strong_\nreference, keeping the resource from being garbage collected. It is reference counted.\n\nImportantly, a resource type is a C++ object that _may_ have a corresponding JavaScript\n\"wrapper\" object. This wrapper is created lazily when the `jsg::Ref<T>` instance is first\npassed back out to JavaScript.\n\n```cpp\njsg::Ref<Foo> foo = js.alloc<Foo>();\njsg::Ref<Foo> foo2 = foo.addRef();\n\njs.withinHandleScope([&] {\n  KJ_IF_SOME(handle, foo.tryGetHandle(js.v8Isolate)) {\n    // handle is the Foo instance's JavaScript wrapper.\n  }\n});\n```\n\n### Memoized and Identified Types\n\n#### `jsg::MemoizedIdentity<T>`\n\nTypically, whenever a non-Resource Type is passed out to JavaScript, a _new_ JavaScript value\nis created each time. `jsg::MemoizedIdentity<T>` preserves the JavaScript value so identity\nis maintained across multiple passes:\n\n```cpp\njsg::MemoizedIdentity<Foo> echoFoo(jsg::MemoizedIdentity<Foo> foo) {\n  return kj::mv(foo);\n}\n```\n\n```js\nconst foo = { value: 1 };\nconst foo2 = echoFoo(foo);\nfoo === foo2; // true — same object\n```\n\n#### `jsg::Identified<T>`\n\n`jsg::Identified<T>` captures the identity of the JavaScript object that was passed, useful\nfor recognizing when the application passes the same value later:\n\n```cpp\nvoid doSomething(jsg::Identified<Foo> obj) {\n  auto identity = obj.identity; // HashableV8Ref<v8::Object>\n  Foo foo = obj.unwrapped;\n}\n```\n\n---\n\n## 4. Structured Types (`JSG_STRUCT`)\n\nA JSG \"struct\" is a JavaScript object that can be mapped to a C++ struct:\n\n```cpp\nstruct Foo {\n  kj::String abc;\n  jsg::Optional<bool> xyz;\n  jsg::Value val;\n\n  JSG_STRUCT(abc, xyz, val);\n\n  int onlyInternal = 1;  // Not included in the JS mapping\n};\n```\n\nOnly properties listed in the `JSG_STRUCT` macro are mapped. When passing from JavaScript\ninto C++, additional properties on the object are ignored.\n\nIf the struct has a `validate()` method, it is called when the struct is unwrapped from v8:\n\n```cpp\nstruct ValidatingFoo {\n  kj::String abc;\n\n  void validate(jsg::Lock& lock) {\n    JSG_REQUIRE(abc.size() != 0, TypeError, \"Field 'abc' had no length in 'ValidatingFoo'.\");\n  }\n\n  JSG_STRUCT(abc);\n};\n```\n\nTo be used, JSG structs must be declared as part of the type system via\n`JSG_DECLARE_ISOLATE_TYPE`.\n\n---\n\n## 5. Resource Types\n\nA Resource Type is a C++ type mapped to a JavaScript object that is _not_ a plain object. It\nis a JavaScript object backed by a corresponding C++ object.\n\nThree key components:\n\n- The underlying C++ type\n- The JavaScript wrapper object\n- The `v8::FunctionTemplate`/`v8::ObjectTemplate` that define the connection\n\n### `jsg::Object`\n\nAll Resource Types must inherit from `jsg::Object`:\n\n```cpp\nclass Foo: public jsg::Object {\npublic:\n  Foo(int value): value(value) {}\n  JSG_RESOURCE_TYPE(Foo) {}\nprivate:\n  int value;\n};\n```\n\n### Constructors\n\nFor a Resource Type to be constructible from JavaScript, it must have a static `constructor()`\nmethod returning `jsg::Ref<T>`:\n\n```cpp\nstatic jsg::Ref<Foo> constructor(jsg::Lock& js, int value) {\n  return js.alloc<Foo>(value);\n}\n```\n\nIf this method is not provided, attempts to create new instances using `new ...` will fail\nwith an error.\n\n### `JSG_RESOURCE_TYPE(T)`\n\nAll Resource Types must contain the `JSG_RESOURCE_TYPE(T)` macro. Within the block are\nadditional `JSG*` macros defining properties, methods, constants, etc. See the README for the\ncomplete [JSG macro catalog](../src/workerd/jsg/README.md#jsg-macro-catalog).\n\n### Methods\n\n#### `JSG_METHOD(name)` and `JSG_METHOD_NAMED(name, method)`\n\nDeclares a method callable from JavaScript on instances. Methods are exposed on the prototype:\n\n```cpp\nclass Foo: public jsg::Object {\npublic:\n  static jsg::Ref<Foo> constructor();\n  void bar();\n  void delete_();\n\n  JSG_RESOURCE_TYPE(Foo) {\n    JSG_METHOD(bar);\n    JSG_METHOD_NAMED(delete, delete_);  // JS name differs from C++ name\n  }\n};\n```\n\n```js\nconst foo = new Foo();\nfoo.bar();\nfoo.delete();\n\n// Because it's on the prototype, subclasses can override:\nclass MyFoo extends Foo {\n  delete() {\n    super.delete();\n  }\n}\n```\n\nArguments and return values are automatically marshalled using the JSG type mapping rules.\n\n#### `JSG_STATIC_METHOD(name)` and `JSG_STATIC_METHOD_NAMED(name, method)`\n\nDeclares a method callable on the class itself:\n\n```cpp\nstatic void bar();\nJSG_RESOURCE_TYPE(Foo) {\n  JSG_STATIC_METHOD(bar);\n}\n```\n\n```js\nFoo.bar();\n```\n\n### Properties\n\nProperties on JavaScript objects come in several varieties. See the README's\n[property type decision matrix](../src/workerd/jsg/README.md#property-type-decision-matrix)\nfor guidance on which to use.\n\n#### Prototype Properties (`JSG_READONLY_PROTOTYPE_PROPERTY`, `JSG_PROTOTYPE_PROPERTY`)\n\nDefined on the prototype, shared by all instances, overridable by subclasses:\n\n```cpp\nkj::String getAbc();\nint getXyz();\nvoid setXyz(int value);\n\nJSG_RESOURCE_TYPE(Foo) {\n  JSG_READONLY_PROTOTYPE_PROPERTY(abc, getAbc);\n  JSG_PROTOTYPE_PROPERTY(xyz, getXyz, setXyz);\n}\n```\n\n```js\nconst foo = new Foo();\nfoo.abc; // read-only\nfoo.xyz = 456; // read-write\n```\n\n#### Instance Properties (`JSG_READONLY_INSTANCE_PROPERTY`, `JSG_INSTANCE_PROPERTY`)\n\nDefined as own properties on each instance, _not_ overridable by subclasses:\n\n```cpp\nJSG_RESOURCE_TYPE(Foo) {\n  JSG_READONLY_INSTANCE_PROPERTY(abc, getAbc);\n  JSG_INSTANCE_PROPERTY(xyz, getXyz, setXyz);\n}\n```\n\nWhile these _appear_ the same in simple cases, a subclass's `get abc()` override on the\nprototype will not be called because the instance property shadows it.\n\n**Note:** Prefer `JSG_PROTOTYPE_PROPERTY` over `JSG_INSTANCE_PROPERTY` unless you have a\nspecific reason — instance properties break GC optimization.\n\n#### Lazy Instance Properties (`JSG_LAZY_READONLY_INSTANCE_PROPERTY`, `JSG_LAZY_INSTANCE_PROPERTY`)\n\nEvaluated once and cached. Useful when a default value should be easily overridable by users\n(typically used for introducing new global properties without breaking existing code):\n\n```cpp\nJSG_RESOURCE_TYPE(Foo) {\n  JSG_LAZY_READONLY_INSTANCE_PROPERTY(abc, getAbc);\n  JSG_LAZY_INSTANCE_PROPERTY(xyz, getXyz);\n}\n```\n\n```js\nfoo.abc; // 'hello'\nfoo.abc = 1; // ignored (readonly)\nfoo.xyz; // 123\nfoo.xyz = 'hello'; // value type not enforced\nfoo.xyz; // 'hello'\n```\n\n### Static Constants\n\n```cpp\nstatic const int ABC = 123;\nJSG_RESOURCE_TYPE(Foo) {\n  JSG_STATIC_CONSTANT(ABC);\n}\n```\n\n```js\nFoo.ABC; // 123\n```\n\n### Iterable and Async Iterable Objects\n\nImplemented using `JSG_ITERABLE`/`JSG_ASYNC_ITERABLE` macros with `JSG_ITERATOR`/`JSG_ASYNC_ITERATOR`:\n\n```cpp\nclass Foo: public jsg::Object {\nprivate:\n  using EntryIteratorType = kj::Array<kj::String>;\n  struct IteratorState final {\n    void visitForGc(jsg::GcVisitor& visitor) { /* ... */ }\n  };\n\npublic:\n  static jsg::Ref<Foo> constructor();\n  JSG_ITERATOR(Iterator, entries, EntryIteratorType, IteratorState, iteratorNext);\n\n  JSG_RESOURCE_TYPE(Foo) {\n    JSG_METHOD(entries);\n    JSG_ITERABLE(entries);\n  }\n};\n```\n\n```js\nfor (const entry of foo) {\n  /* ... */\n}\n```\n\n### Inheritance (`JSG_INHERIT`)\n\n```cpp\nclass Foo: public Bar {\npublic:\n  JSG_RESOURCE_TYPE(Foo) {\n    JSG_INHERIT(Bar);\n  }\n};\n```\n\n```js\nfoo instanceof Bar; // true\n```\n\n### Nested Types (`JSG_NESTED_TYPE`, `JSG_NESTED_TYPE_NAMED`)\n\nExposes the constructor of a nested Resource Type:\n\n```cpp\nJSG_RESOURCE_TYPE(Foo) {\n  JSG_NESTED_TYPE(Bar);\n  JSG_NESTED_TYPE_NAMED(OtherBar, Baz);\n}\n```\n\n```js\nconst bar = new Foo.Bar();\nconst baz = new Foo.Baz();\n```\n\n### Callable Objects (`JSG_CALLABLE`)\n\nMakes a Resource Type callable as a function:\n\n```cpp\nclass MyAssert: public jsg::Object {\npublic:\n  void ok(boolean condition) {\n    JSG_REQUIRE(condition, Error, \"Condition failed!\");\n  }\n  JSG_RESOURCE_TYPE(MyAssert) {\n    JSG_CALLABLE(ok);\n    JSG_METHOD(ok);\n  }\n};\n```\n\n```js\nassert.ok(true); // method call\nassert(true); // function call (same as ok)\n```\n\n### Compatibility Flags\n\n`JSG_RESOURCE_TYPE` accepts an optional `CompatibilityFlags::Reader` parameter to\nconditionally expose APIs:\n\n```cpp\nJSG_RESOURCE_TYPE(MyApi, workerd::CompatibilityFlags::Reader flags) {\n  JSG_METHOD(oldMethod);  // Always exposed\n  if (flags.getMyNewFeature()) {\n    JSG_METHOD(newMethod);  // Only with flag enabled\n  }\n}\n```\n\nThe compatibility flags are defined in `src/workerd/io/compatibility-date.capnp`.\n\n---\n\n## 6. Type System Declaration\n\nFor JSG's type system to function, you must declare all types via `JSG_DECLARE_ISOLATE_TYPE`.\nEvery resource type and `JSG_STRUCT` type must be listed:\n\n```cpp\nJSG_DECLARE_ISOLATE_TYPE(MyIsolate, api::Foo, api::Bar)\n```\n\nIn workerd, you'll see a pattern using `EW_*_ISOLATE_TYPES` macros:\n\n```cpp\nJSG_DECLARE_ISOLATE_TYPE(JsgServeIsolate,\n  EW_GLOBAL_SCOPE_ISOLATE_TYPES,\n  EW_ACTOR_ISOLATE_TYPES,\n  EW_ACTOR_STATE_ISOLATE_TYPES,\n  EW_ANALYTICS_ENGINE_ISOLATE_TYPES,\n  ...\n```\n\nEach `EW_*_ISOLATE_TYPES` macro is defined in its respective header file as a shortcut.\n`JSG_DECLARE_ISOLATE_TYPE` should only be defined once in your application.\n\n---\n\n## 7. Memory Management\n\n### Garbage Collection and GC Visitation\n\nV8's garbage collector is mark-and-sweep: it marks all reachable objects from the root set,\nthen frees unmarked objects. When using `jsg::V8Ref<T>` or `jsg::Ref<T>` to hold references,\nyou must mark reachable objects so the GC knows how to handle them.\n\nA Resource Type containing GC-visitable fields should implement `visitForGc()`:\n\n```cpp\nclass Foo: public jsg::Object {\npublic:\n  jsg::Ref<Bar> getBar();\n\n  JSG_RESOURCE_TYPE(Foo) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(bar, getBar);\n  }\n\nprivate:\n  jsg::Ref<Bar> bar;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(bar);\n  }\n};\n```\n\nJSG automatically detects the `visitForGc()` implementation. If your Resource Type owns no\nref types, implementing it is not necessary. As a best practice, implement it on all types\nthat contain refs, regardless of whether reference cycles are a concern.\n\n### Ownership Rules\n\nFor `jsg::Ref<T>`, garbage collection only applies to the JavaScript wrapper. From C++'s\nperspective, `jsg::Ref<T>` is a strong reference to a refcounted object. Unreachable cycles\nof `jsg::Ref<T>` will have their JavaScript objects collected, but the C++ objects will not\nbe freed until the cycle is broken.\n\n**Critical:** Think about ownership and only hold `jsg::Ref<T>` from owner to owned objects.\n\"Backwards\" references (owned→owner) should be regular C++ references or pointers. If the\nreference might be nulled out, use `kj::Maybe<T&>`.\n\nSee the README for the complete [GC-visitable types table](../src/workerd/jsg/README.md#gc-visitable-types).\n\n---\n\n## 8. JsValue Types\n\nThe `JsValue` family provides a modern abstraction layer over V8's `v8::Local<T>` handles.\nSee the README for the complete [JsValue type mapping table](../src/workerd/jsg/README.md#jsvalue-type-mapping).\n\n### Key Characteristics\n\n1. **Stack-only allocation** — enforced in debug builds. Lightweight wrappers around V8 handles.\n2. **Implicit conversion to V8 types** — all `JsValue` types convert to their `v8::Local<T>`.\n3. **Implicit conversion to `JsValue`** — all specific types upcast to `JsValue`.\n4. **Use `JsRef<T>` for persistence** — to store a value beyond the current scope.\n\n### Creating Values with `jsg::Lock`\n\n```cpp\nvoid createValues(jsg::Lock& js) {\n  // Primitives\n  JsValue undef = js.undefined();\n  JsValue null = js.null();\n  JsBoolean b = js.boolean(true);\n\n  // Numbers\n  JsNumber n = js.num(3.14);\n  JsInt32 i32 = js.num(42);\n  JsBigInt big = js.bigInt(INT64_MAX);\n\n  // Strings\n  JsString s = js.str(\"hello\");\n  JsString interned = js.strIntern(\"propertyName\");\n\n  // Objects and collections\n  JsObject obj = js.obj();\n  JsObject noProto = js.objNoProto();\n  JsMap map = js.map();\n  JsArray arr = js.arr(js.num(1), js.num(2), js.num(3));\n  JsSet set = js.set(js.str(\"a\"), js.str(\"b\"));\n\n  // Symbols, Dates, Errors\n  JsSymbol sym = js.symbol(\"mySymbol\");\n  JsDate date = js.date(1234567890.0);\n  JsValue err = js.error(\"Something went wrong\");\n  JsValue typeErr = js.typeError(\"Expected a string\");\n\n  // Global object\n  JsObject global = js.global();\n}\n```\n\n### Type Checking and Casting\n\n```cpp\nvoid checkTypes(jsg::Lock& js, JsValue value) {\n  if (value.isString()) { /* ... */ }\n  if (value.isNullOrUndefined()) { /* ... */ }\n\n  KJ_IF_SOME(str, value.tryCast<JsString>()) {\n    kj::String cppStr = str.toString(js);\n  }\n}\n```\n\n### Working with Objects\n\n```cpp\nvoid objectOps(jsg::Lock& js) {\n  JsObject obj = js.obj();\n\n  obj.set(js, \"name\", js.str(\"Alice\"));\n  obj.setReadOnly(js, \"version\", js.str(\"1.0\"));\n  JsValue name = obj.get(js, \"name\");\n\n  if (obj.has(js, \"name\", JsObject::HasOption::OWN)) { /* own property */ }\n  obj.delete_(js, \"name\");\n\n  JsArray names = obj.getPropertyNames(js,\n      KeyCollectionFilter::OWN_ONLY,\n      PropertyFilter::ONLY_ENUMERABLE,\n      IndexFilter::INCLUDE_INDICES);\n\n  if (obj.isInstanceOf<MyResourceType>(js)) {\n    auto ref = KJ_ASSERT_NONNULL(obj.tryUnwrapAs<MyResourceType>(js));\n  }\n\n  obj.seal(js);\n  obj.recursivelyFreeze(js);\n  JsObject clone = obj.jsonClone(js);\n}\n```\n\n### Working with Strings\n\n```cpp\nvoid stringOps(jsg::Lock& js) {\n  JsString str = js.str(\"Hello, World!\");\n  int len = str.length(js);           // UTF-16 code units\n  size_t utf8Len = str.utf8Length(js); // UTF-8 bytes\n  kj::String cppStr = str.toString(js);\n\n  // Check string properties\n  bool flat = str.isFlat();               // True if contiguous in memory\n  bool oneByte = str.containsOnlyOneByte(); // True if all chars fit in one byte\n\n  JsString combined = JsString::concat(js, js.str(\"Hello, \"), js.str(\"World!\"));\n  JsString interned = str.internalize(js);\n\n  // Write to buffer\n  kj::Array<char> buf = kj::heapArray<char>(100);\n  auto status = str.writeInto(js, buf, JsString::WriteFlags::NULL_TERMINATION);\n  // status.read = characters read from string\n  // status.written = bytes written to buffer\n}\n```\n\n### Working with Arrays and Functions\n\n```cpp\nvoid arrayOps(jsg::Lock& js) {\n  JsArray arr = js.arr(js.num(1), js.num(2), js.num(3));\n  uint32_t len = arr.size();\n  JsValue elem = arr.get(js, 0);\n  arr.add(js, js.str(\"new item\"));\n}\n\nvoid functionOps(jsg::Lock& js, JsFunction func) {\n  JsValue result = func.call(js, js.global(), js.num(1), js.str(\"arg\"));\n  JsValue result2 = func.callNoReceiver(js, js.num(42));\n  size_t length = func.length(js);\n  JsString name = func.name(js);\n}\n```\n\n### JSON and Structured Clone\n\n```cpp\nvoid jsonOps(jsg::Lock& js, JsValue value) {\n  kj::String json = value.toJson(js);\n  JsValue parsed = JsValue::fromJson(js, R\"({\"key\": \"value\"})\");\n}\n\nvoid cloneOps(jsg::Lock& js, JsValue value) {\n  JsValue cloned = value.structuredClone(js);\n  // Clone with transferables:\n  kj::Array<JsValue> transfers = kj::heapArray<JsValue>({someArrayBuffer});\n  JsValue cloned2 = value.structuredClone(js, kj::mv(transfers));\n}\n```\n\n### Persisting Values with `JsRef<T>`\n\n`JsValue` types are only valid within the current scope. To store a value persistently:\n\n```cpp\nclass MyClass: public jsg::Object {\npublic:\n  void storeValue(jsg::Lock& js, JsValue value) {\n    stored = value.addRef(js);\n  }\n\n  JsValue getStored(jsg::Lock& js) {\n    return stored.getHandle(js);\n  }\n\nprivate:\n  JsRef<JsValue> stored;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(stored);  // Important for garbage collection!\n  }\n};\n```\n\n### Comparison and Equality\n\n```cpp\nbool equal = (a == b);              // Abstract equality (==)\nbool strictEqual = a.strictEquals(b); // Strict equality (===)\nbool truthy = a.isTruthy(js);\nkj::String type = a.typeOf(js);     // \"string\", \"number\", etc.\n```\n\n### `JsPromise` vs `jsg::Promise<T>`\n\n- **`JsPromise`**: Thin wrapper around `v8::Promise` for inspecting state. Cannot be awaited\n  in C++. Use when you need to check pending/fulfilled/rejected or access the result directly.\n- **`jsg::Promise<T>`**: Higher-level abstraction with `.then()`, `.catch_()` and JSG type\n  system integration. Use this for most promise handling.\n\n---\n\n## 9. Serialization\n\nJSG provides serialization built on V8's `ValueSerializer` supporting the\n[structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm).\n\n### Basic Usage\n\n```cpp\nJsValue cloned = jsg::structuredClone(js, value);\n\n// With transferables:\nkj::Array<JsValue> transfers = kj::heapArray<JsValue>({someArrayBuffer});\nJsValue cloned2 = jsg::structuredClone(js, value, kj::mv(transfers));\n```\n\n### Serializer and Deserializer\n\n```cpp\n// Serialize\nSerializer::Released serialized = ({\n  Serializer ser(js);\n  ser.write(js, value);\n  ser.release();\n});\n// serialized.data, .sharedArrayBuffers, .transferredArrayBuffers\n\n// Deserialize\nJsValue result = ({\n  Deserializer deser(js, serialized);\n  deser.readValue(js);\n});\n```\n\nMultiple values can be serialized/deserialized in sequence.\n\n#### Serializer Options\n\n```cpp\nSerializer::Options options {\n  .version = kj::none,\n  .omitHeader = false,\n  .treatClassInstancesAsPlainObjects = true,\n  .externalHandler = kj::none,\n};\nSerializer ser(js, options);\n```\n\n#### Transferring ArrayBuffers\n\n```cpp\nSerializer ser(js);\nser.transfer(js, buffer);  // Mark for transfer before writing\nser.write(js, buffer);\nauto released = ser.release();\n// buffer is now detached (neutered)\n```\n\n### Making Resource Types Serializable (`JSG_SERIALIZABLE`)\n\n```cpp\nenum class SerializationTag {\n  MY_TYPE_V1 = 1,\n  MY_TYPE_V2 = 2,\n};\n\nclass MyType: public jsg::Object {\npublic:\n  MyType(uint32_t id, kj::String name): id(id), name(kj::mv(name)) {}\n\n  JSG_RESOURCE_TYPE(MyType) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(id, getId);\n    JSG_READONLY_PROTOTYPE_PROPERTY(name, getName);\n  }\n\n  void serialize(jsg::Lock& js, jsg::Serializer& serializer) {\n    serializer.writeRawUint32(id);\n    serializer.writeLengthDelimited(name);\n  }\n\n  static jsg::Ref<MyType> deserialize(\n      jsg::Lock& js, SerializationTag tag, jsg::Deserializer& deserializer) {\n    uint32_t id = deserializer.readRawUint32();\n    kj::String name = deserializer.readLengthDelimitedString();\n    return js.alloc<MyType>(id, kj::mv(name));\n  }\n\n  // MUST appear AFTER the JSG_RESOURCE_TYPE block, not inside it\n  JSG_SERIALIZABLE(SerializationTag::MY_TYPE_V1);\n\nprivate:\n  uint32_t id;\n  kj::String name;\n};\n```\n\n**Important notes:**\n\n- `JSG_SERIALIZABLE` must appear **after** the `JSG_RESOURCE_TYPE` block\n- Tag enum values must never change once data has been serialized\n- `deserialize()` receives the tag so it can handle multiple versions\n\n### Versioning Serializable Types\n\nList the current tag first, then old tags:\n\n```cpp\n// V2 adds optional description\nvoid serialize(jsg::Lock& js, jsg::Serializer& serializer) {\n  serializer.writeRawUint32(id);\n  serializer.writeLengthDelimited(name);\n  KJ_IF_SOME(desc, description) {\n    serializer.writeRawUint32(1);\n    serializer.writeLengthDelimited(desc);\n  } else {\n    serializer.writeRawUint32(0);\n  }\n}\n\nstatic jsg::Ref<MyType> deserialize(\n    jsg::Lock& js, SerializationTag tag, jsg::Deserializer& deserializer) {\n  uint32_t id = deserializer.readRawUint32();\n  kj::String name = deserializer.readLengthDelimitedString();\n  kj::Maybe<kj::String> description;\n  if (tag == SerializationTag::MY_TYPE_V2) {\n    if (deserializer.readRawUint32() == 1) {\n      description = deserializer.readLengthDelimitedString();\n    }\n  }\n  return js.alloc<MyType>(id, kj::mv(name), kj::mv(description));\n}\n\nJSG_SERIALIZABLE(SerializationTag::MY_TYPE_V2, SerializationTag::MY_TYPE_V1);\n```\n\n### TypeHandlers in Serialization\n\nBoth `serialize()` and `deserialize()` can request `TypeHandler` arguments:\n\n```cpp\nvoid serialize(jsg::Lock& js, jsg::Serializer& serializer,\n               const jsg::TypeHandler<kj::String>& stringHandler) {\n  serializer.write(js, JsValue(stringHandler.wrap(js, kj::str(text))));\n}\n```\n\n### Raw Read/Write Methods\n\nSee the README's [serialization pattern](../src/workerd/jsg/README.md#serialization-pattern)\nfor the complete list of raw serializer/deserializer methods.\n\n### One-Way Serialization (`JSG_SERIALIZABLE_ONEWAY`)\n\nFor types that serialize to a different type (e.g., a legacy type deserializing as a newer\ntype):\n\n```cpp\nclass LegacyType: public jsg::Object {\n  void serialize(jsg::Lock& js, jsg::Serializer& serializer) {\n    serializer.writeRawUint32(value);\n  }\n  // No deserialize() — uses NewType's deserializer\n  JSG_SERIALIZABLE_ONEWAY(SerializationTag::NEW_TYPE);\n};\n```\n\n---\n\n## 10. Error Handling\n\n### Error Macros\n\n```cpp\nJSG_REQUIRE(condition, TypeError, \"Expected a valid value, got \", value);\nauto& val = JSG_REQUIRE_NONNULL(maybeValue, TypeError, \"Value must not be null\");\nJSG_FAIL_REQUIRE(RangeError, \"Index \", index, \" is out of bounds\");\nJSG_ASSERT(condition, Error, \"Internal assertion failed\");\n```\n\nThe error type can be: `TypeError`, `Error`, `RangeError`, or DOMException types like\n`DOMOperationError`, `DOMDataError`, `DOMInvalidStateError`, etc. See the README for the\nfull [error type catalog](../src/workerd/jsg/README.md#error-type-catalog).\n\nUnlike `KJ_REQUIRE`, `JSG_REQUIRE` passes all message arguments through `kj::str()`, so you\nare responsible for formatting the entire message string.\n\n### `js.error()`, `js.throwException()`, and `JsExceptionThrown`\n\n```cpp\nvoid someMethod(jsg::Lock& js) {\n  if (somethingWrong) {\n    js.throwException(js.error(\"Something went wrong\"));\n  }\n}\n```\n\nUnder the hood, `js.throwException()` uses `isolate->ThrowException()` then throws\n`JsExceptionThrown` to unwind the C++ stack back to the JS/C++ boundary.\n\n### `JSG_TRY` and `JSG_CATCH`\n\nReplace normal `try`/`catch` when you need to catch exceptions as JavaScript exceptions:\n\n```cpp\nJSG_TRY(js) {\n  someThrowyCode();\n}\nJSG_CATCH(e) {\n  // 'e' is a JsValue wrapping the JavaScript error\n  // Useful for coercing any C++ exception into a JavaScript Error:\n  js.throwException(kj::mv(e));\n}\n```\n\n**Important:** `JSG_CATCH` is NOT a true catch — you cannot rethrow with `throw`.\n\n### `makeInternalError()` and `throwInternalError()`\n\nCreate JavaScript errors from internal C++ exceptions while obfuscating sensitive\nimplementation details. If the exception was created using `throwTunneledException()`, the\noriginal JavaScript exception is reconstructed instead.\n\n### `throwTypeError()`\n\nThrows a JavaScript `TypeError` with contextual information:\n\n```cpp\nthrowTypeError(isolate, TypeErrorContext::methodArgument(typeid(MyClass), \"doThing\", 0),\n               \"string\");\nthrowTypeError(isolate, \"Expected a string but got a number\"_kj);\n```\n\n### `check()`\n\nUnwraps V8's `MaybeLocal` and `Maybe` types, throwing `JsExceptionThrown` if empty:\n\n```cpp\nv8::Local<v8::String> str = check(maybeStr);\n```\n\n### `jsg::DOMException`\n\nImplements the standard Web IDL [DOMException][] interface. To throw from C++:\n\n```cpp\nJSG_REQUIRE(isValid, DOMInvalidStateError, \"The object is in an invalid state\");\nJSG_REQUIRE(hasAccess, DOMNotSupportedError, \"This operation is not supported\");\n```\n\n### Tunneled Exceptions\n\nJavaScript exceptions can be \"tunneled\" through KJ's exception system — thrown, caught as\n`kj::Exception`, passed across boundaries (like RPC), and reconstructed back:\n\n```cpp\nkj::Exception createTunneledException(v8::Isolate* isolate, v8::Local<v8::Value> exception);\n[[noreturn]] void throwTunneledException(v8::Isolate* isolate, v8::Local<v8::Value> exception);\n\n// Check if a kj::Exception contains a tunneled JS exception:\nif (jsg::isTunneledException(exception.getDescription())) { /* ... */ }\n```\n\nThe tunneling mechanism encodes the exception type and message in a special format using\nprefixes like `jsg.TypeError:` or `jsg.DOMException(NotFoundError):`.\n\n[DOMException]: https://webidl.spec.whatwg.org/#idl-DOMException\n\n---\n\n## 11. Utility Functions\n\n### `jsg::v8Str(...)` and `jsg::v8StrIntern(...)`\n\nCreate V8 string values from C++ strings:\n\n```cpp\nv8::Local<v8::String> v8Str(v8::Isolate* isolate, kj::StringPtr str);\nv8::Local<v8::String> v8Str(v8::Isolate* isolate, kj::ArrayPtr<const char16_t> ptr);\nv8::Local<v8::String> v8StrIntern(v8::Isolate* isolate, kj::StringPtr str);\n```\n\n**Note:** New code should prefer `js.str()` and `js.strIntern()` instead.\n\n### External Strings\n\nFor static constant strings that will never be deallocated:\n\n```cpp\nv8::Local<v8::String> newExternalOneByteString(Lock& js, kj::ArrayPtr<const char> buf);\nv8::Local<v8::String> newExternalTwoByteString(Lock& js, kj::ArrayPtr<const uint16_t> buf);\n```\n\n**Important:** The `OneByteString` variant interprets the buffer as Latin-1, not UTF-8.\n\n---\n\n## 12. V8 Fast API\n\nV8 Fast API allows V8 to compile JavaScript code that calls native functions by generating\nspecialized machine code that directly calls the C++ implementation, skipping the usual\nbinding layers.\n\n### Requirements\n\n1. **Return type**: `void`, `bool`, `int32_t`, `uint32_t`, `float`, or `double`\n2. **Parameter types**: The primitives above, V8 handle types, or TypeWrapper-unwrappable types\n3. **Method structure**: Regular/const instance method, optionally with `jsg::Lock&` first param\n\n### Usage\n\nBy default, any `JSG_METHOD(name)` executes the fast path if the method signature is\ncompatible. To explicitly assert compatibility:\n\n```cpp\nJSG_ASSERT_FASTAPI(MyClass::myMethod);\n```\n\nV8 determines at runtime whether to use the fast or slow path based on whether the call\noriginates from optimized code.\n\n---\n\n## 13. TypeScript Generation\n\nTypeScript definitions are automatically generated from JSG RTTI using scripts in `/types`.\nThree macros control auto-generation inside `JSG_RESOURCE_TYPE` blocks, plus struct variants.\n\n### `JSG_TS_ROOT` / `JSG_STRUCT_TS_ROOT`\n\nDeclares a type as a \"root\" for TypeScript generation. All roots and their recursively\nreferenced types are included. Roots exist because some types should only be included when\ncertain compatibility flags are enabled.\n\nNote roots are visited before overrides, so if an override references a new type that wasn't\nalready referenced, it needs to be declared a root itself.\n\n### `JSG_TS_OVERRIDE` / `JSG_STRUCT_TS_OVERRIDE`\n\nCustomises the generated TypeScript definition. Accepts a partial TypeScript statement.\nSee the README's [JSG_TS_OVERRIDE rules](../src/workerd/jsg/README.md#jsg_ts_override-rules)\nfor the complete rule set.\n\nExamples:\n\n```ts\n// Rename type\nKVNamespaceListOptions\n\n// Replace method, keep others\n{ json<T>(): Promise<T> }\n\n// Add type parameter and replace methods\n<R = any> {\n  read(): Promise<ReadableStreamReadResult<R>>;\n  tee(): [ReadableStream<R>, ReadableStream<R>];\n}\n\n// Remove a member\n{ actorState: never }\n\n// Replace heritage\nextends EventTarget<WorkerGlobalScopeEventMap>\n\n// Full replacement\nclass Body { json<T>(): Promise<T> }\n\n// Delete definition\ntype TransactionOptions = never\n```\n\nThese macros can be called conditionally based on compatibility flags. For compatibility-flag\ndependent `JSG_STRUCT` overrides, delete the original with a `never` type alias, then use\n`JSG_TS_DEFINE` in a nearby `JSG_RESOURCE_TYPE` to define an interface conditionally.\n\n### `JSG_TS_DEFINE` / `JSG_STRUCT_TS_DEFINE`\n\nInserts additional TypeScript definitions next to the generated definition. Can only be used\nonce per block. The `declare` modifier is automatically added to `class`, `enum`, `const`,\n`var`, and `function` definitions.\n\n---\n\n## 14. Async Context Tracking\n\nJSG provides rudimentary async context tracking via `AsyncContextFrame` in\n`src/workerd/jsg/async-context.h`, supporting the implementation of async local storage.\n\n`AsyncContextFrame`s form a logical stack. For every `v8::Isolate` there is always a root\nframe. Each frame has a storage context (a map of storage cells keyed by opaque keys).\nWhen a new frame is created, the current frame's storage context is propagated.\n\nAll JavaScript promises, timers, and microtasks propagate the async context.\n\n```js\nimport { default as async_hooks } from 'node:async_hooks';\nconst { AsyncLocalStorage } = async_hooks;\n\nconst als = new AsyncLocalStorage();\nals\n  .run(123, () => scheduler.wait(10))\n  .then(() => {\n    console.log(als.getStore()); // 123\n  });\nconsole.log(als.getStore()); // undefined\n```\n\n### C++ Usage\n\n```cpp\n// Capture the current async context\nauto maybeAsyncContext = jsg::AsyncContextFrame::current(js);\n// or jsg::AsyncContextFrame::currentRef(js) for a jsg::Ref\n\n// Enter the async resource scope\n{\n  jsg::AsyncContextFrame::Scope asyncScope(js, maybeAsyncContext);\n  // run synchronous code that requires the async context\n}\n// Scope exits automatically\n```\n\n### StorageScope (independent of Node.js API)\n\n```cpp\nkj::Own<AsyncContextFrame::StorageKey> key =\n    kj::refcounted<AsyncContextFrame::StorageKey>();\nKJ_DEFER(key->reset());  // Clear when done\n\n{\n  jsg::AsyncContextFrame::StorageScope(js, *key, value);\n  // code runs with this storage context\n}\n// Automatically reset to previous context\n```\n\n---\n\n## 15. Memory Tracking (`jsg::MemoryTracker`)\n\nIntegrates with V8's `BuildEmbedderGraph` API to include C++ object data in heap snapshots.\n\nA type must implement at least:\n\n- `kj::StringPtr jsgGetMemoryName() const;` — name in graph (prefixed `\"workerd / \"`)\n- `size_t jsgGetMemorySelfSize() const;` — shallow size (typically `sizeof(Type)`)\n- `void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const;` — collect details\n\nThe `JSG_MEMORY_INFO` macro provides shorthand:\n\n```cpp\nJSG_MEMORY_INFO(Foo) {\n  tracker.trackField(\"bar\", bar);\n}\n```\n\nFor `jsg::Object` subclasses, implement `visitForMemoryInfo()` instead:\n\n```cpp\nclass Foo : public jsg::Object {\npublic:\n  JSG_RESOURCE_TYPE(Foo) {}\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"bar\", bar);\n  }\n};\n```\n\nOptional additional methods: `jsgGetMemoryInfoWrapperObject()`,\n`jsgGetMemoryInfoDetachedState()`, `jsgGetMemoryInfoIsRootNode()`.\n\nThis code is only called during heap snapshot generation; it should have very little cost and\nideally should not allocate.\n\n---\n\n## 16. Script Utilities\n\n### `jsg::NonModuleScript`\n\nWraps a `v8::UnboundScript` — compiled but not yet bound to a specific context:\n\n```cpp\n#include <workerd/jsg/script.h>\n\nauto script = jsg::NonModuleScript::compile(js, \"console.log('Hello!'); return 42;\",\n                                            \"my-script.js\");\nscript.run(js);\njsg::JsValue result = script.runAndReturn(js);\n```\n\nScripts can be compiled once and run multiple times. Each run binds to the current context.\n\n---\n\n## 17. URL Utilities\n\nJSG provides WHATWG-compliant URL parsing in `url.h`, powered by the ada-url library.\n\n### `jsg::Url`\n\n```cpp\nKJ_IF_SOME(url, jsg::Url::tryParse(\"https://example.com:8080/path?query=1#hash\")) {\n  kj::ArrayPtr<const char> protocol = url.getProtocol();  // \"https:\"\n  kj::ArrayPtr<const char> hostname = url.getHostname();  // \"example.com\"\n  kj::ArrayPtr<const char> pathname = url.getPathname();  // \"/path\"\n\n  url.setPathname(\"/new/path\");\n  kj::ArrayPtr<const char> href = url.getHref();\n}\n\n// Parse with a base URL\nKJ_IF_SOME(resolved, jsg::Url::tryParse(\"../other\", \"https://example.com/path/\")) {\n  // resolved is \"https://example.com/other\"\n}\n\n// Check validity without creating a Url object\nif (jsg::Url::canParse(\"https://example.com\")) { /* valid */ }\n\n// Literal operator\njsg::Url url = \"https://example.com\"_url;\n```\n\n#### URL Comparison\n\n```cpp\nusing Option = jsg::Url::EquivalenceOption;\nif (a.equal(b, Option::IGNORE_FRAGMENTS)) { /* same ignoring #hash */ }\nif (a.equal(b, Option::IGNORE_FRAGMENTS | Option::IGNORE_SEARCH)) { /* ... */ }\n```\n\n### `jsg::UrlSearchParams`\n\n```cpp\nKJ_IF_SOME(params, jsg::UrlSearchParams::tryParse(\"foo=1&bar=2&foo=3\")) {\n  KJ_IF_SOME(value, params.get(\"foo\")) { /* \"1\" (first occurrence) */ }\n  auto allFoo = params.getAll(\"foo\");  // [\"1\", \"3\"]\n\n  // Check existence\n  bool hasFoo = params.has(\"foo\");\n  bool hasFooWithValue = params.has(\"foo\", \"1\"_kj);\n\n  // Modify\n  params.append(\"baz\", \"4\");\n  params.set(\"foo\", \"new\");  // Replaces all \"foo\" entries\n  params.delete_(\"bar\");\n\n  // Iterate\n  auto keys = params.getKeys();\n  while (keys.hasNext()) {\n    KJ_IF_SOME(key, keys.next()) { /* ... */ }\n  }\n\n  params.sort();\n  kj::Array<const char> str = params.toStr();  // \"baz=4&foo=new\"\n}\n```\n\n### `jsg::UrlPattern`\n\n```cpp\nauto result = jsg::UrlPattern::tryCompile(\"/users/:id\");\nKJ_SWITCH_ONEOF(result) {\n  KJ_CASE_ONEOF(pattern, jsg::UrlPattern) {\n    auto& pathname = pattern.getPathname();\n    kj::StringPtr regex = pathname.getRegex();\n    auto names = pathname.getNames();  // [\"id\"]\n  }\n  KJ_CASE_ONEOF(error, kj::String) {\n    KJ_LOG(ERROR, \"Invalid pattern\", error);\n  }\n}\n\n// Compile with options\njsg::UrlPattern::CompileOptions options {\n  .baseUrl = \"https://example.com\",\n  .ignoreCase = true,\n};\nauto result2 = jsg::UrlPattern::tryCompile(\"/path\", options);\n```\n\n---\n\n## 18. RTTI (Runtime Type Information)\n\nThe RTTI system in `rtti.h` introspects JSG types at runtime and produces Cap'n Proto\ndescriptions (schema in `rtti.capnp`). Used for TypeScript generation, dynamic invocation,\nfuzzing, and backward compatibility checks.\n\n### Using the RTTI Builder\n\n```cpp\n#include <workerd/jsg/rtti.h>\n\njsg::rtti::Builder<Config> rtti(config);\nauto intType = rtti.type<int>();\nauto myClassInfo = rtti.structure<MyClass>();\n\nfor (auto member : myClassInfo.getMembers()) {\n  KJ_LOG(INFO, \"Member:\", member.getName());\n}\n\n// Lookup structure by name (returns Maybe)\nKJ_IF_SOME(structInfo, rtti.structure(\"MyClass\"_kj)) {\n  // Use structInfo...\n}\n```\n\n### TypeScript Generation Process\n\n1. Instantiate RTTI builder with appropriate configuration\n2. Iterate through all registered types\n3. Convert Cap'n Proto metadata to TypeScript syntax\n4. Apply any `JSG_TS_OVERRIDE` customizations\n\nSee the README's [RTTI type mapping table](../src/workerd/jsg/README.md#rtti-type-mapping)\nfor the complete mapping.\n\n---\n\n## 19. Wrappable Internals\n\n### The `Wrappable` Base Class\n\n`Wrappable` is the base class for all C++ objects exposed to JavaScript. It manages the\nconnection between a C++ object and its JavaScript \"wrapper\" object.\n\n#### Key Concepts\n\n1. **Lazy Wrapper Creation** — Wrappers are created on-demand when a C++ object is first\n   passed to JavaScript, not when constructed.\n\n2. **Dual Reference Counting** — The `Wrappable` is ref-counted via `kj::Refcounted` (JS\n   wrapper holds a reference). A second \"strong ref\" count tracks `jsg::Ref<T>` pointers not\n   visible to GC tracing.\n\n3. **Identity Preservation** — The same C++ object always returns the same JS wrapper,\n   preserving object identity and monkey-patches.\n\n#### Internal Fields\n\nJavaScript wrapper objects have two internal fields:\n\n```cpp\nenum InternalFields : int {\n  WRAPPABLE_TAG_FIELD_INDEX = 0,    // Contains WORKERD_WRAPPABLE_TAG\n  WRAPPED_OBJECT_FIELD_INDEX = 1,   // Pointer back to the Wrappable\n  INTERNAL_FIELD_COUNT = 2,\n};\n```\n\nCheck if an object is a workerd API object: `jsg::Wrappable::isWorkerdApiObject(object)`\n\n#### Context Embedder Data Slots\n\nSee the README's [context embedder data slots table](../src/workerd/jsg/README.md#context-embedder-data-slots).\n\n```cpp\njsg::setAlignedPointerInEmbedderData(context, ContextPointerSlot::MODULE_REGISTRY, registry);\nKJ_IF_SOME(registry, jsg::getAlignedPointerFromEmbedderData<ModuleRegistry>(\n    context, ContextPointerSlot::MODULE_REGISTRY)) {\n  // Use registry...\n}\n```\n\n### `HeapTracer`\n\nImplements V8's `EmbedderRootsHandler` to integrate JSG's C++ object graph with V8's GC.\nTracks all `Wrappable` objects with JS wrappers, decides which can be collected, and manages\na freelist of reusable wrapper shim objects.\n\n```cpp\nif (jsg::HeapTracer::isInCppgcDestructor()) {\n  // Be careful — we're being destroyed during GC\n}\n```\n\n### Wrapper Lifecycle\n\n```\n1. C++ object created (no JS wrapper yet)\n         |\n2. Object passed to JavaScript\n         |\n3. attachWrapper() creates JS wrapper\n         |\n4. JS wrapper and C++ object linked\n         |\n5. GC may collect wrapper if:\n   - No JS references exist\n   - No strong Ref<T>s exist\n   - Wrapper is \"unmodified\"\n         |\n6. If wrapper collected but C++ object still alive:\n   - New wrapper created on next JS access\n         |\n7. When C++ object destroyed:\n   - detachWrapper() called\n   - JS wrapper becomes empty shell\n```\n\n### Async Destructor Safety\n\nJSG enforces that JavaScript heap objects don't hold KJ I/O objects directly:\n\n```cpp\nDISALLOW_KJ_IO_DESTRUCTORS_SCOPE;\n// If you need to store I/O objects, use IoOwn<T>\n```\n\n---\n\n## 20. Observers\n\nJSG provides an observer system for monitoring runtime events.\n\n### `IsolateObserver`\n\nThe main observer interface combining `CompilationObserver`, `ResolveObserver`, and\n`InternalExceptionObserver`:\n\n```cpp\nclass MyObserver: public jsg::IsolateObserver { /* ... */ };\nauto observer = kj::heap<MyObserver>();\nMyIsolate isolate(v8System, kj::mv(observer));\n```\n\n### Dynamic Code Generation Monitoring\n\n```cpp\nvoid onDynamicEval(v8::Local<v8::Context> context, v8::Local<v8::Value> source,\n                   jsg::IsCodeLike isCodeLike) override {\n  KJ_LOG(WARNING, \"Dynamic code generation detected\");\n}\n```\n\n### `CompilationObserver`\n\n`onXxxStart` methods return `kj::Own<void>` destroyed when compilation completes (RAII\ntiming):\n\n```cpp\nkj::Own<void> onEsmCompilationStart(v8::Isolate* isolate, kj::StringPtr name,\n                                     Option option) const override {\n  auto startTime = kj::systemCoarseMonotonicClock().now();\n  return kj::defer([startTime, name = kj::str(name)]() {\n    auto duration = kj::systemCoarseMonotonicClock().now() - startTime;\n    KJ_LOG(INFO, \"ESM compilation\", name, duration / kj::MILLISECONDS, \"ms\");\n  });\n}\n```\n\nCompilation `Option`: `BUNDLE` (user code) or `BUILTIN` (runtime modules).\n\nOther `CompilationObserver` hooks: `onScriptCompilationStart`, `onWasmCompilationStart`,\n`onWasmCompilationFromCacheStart`, `onJsonCompilationStart`, `onCompileCacheFound`,\n`onCompileCacheRejected`, `onCompileCacheGenerated`, `onCompileCacheGenerationFailed`.\n\n### `ResolveObserver`\n\nMonitors module resolution:\n\n```cpp\nkj::Own<ResolveStatus> onResolveModule(kj::StringPtr specifier, Context context,\n                                        Source source) const override { /* ... */ }\n```\n\n- `Context`: `BUNDLE`, `BUILTIN`, `BUILTIN_ONLY`\n- `Source`: `STATIC_IMPORT`, `DYNAMIC_IMPORT`, `REQUIRE`, `INTERNAL`\n\n`ResolveStatus` callbacks: `found()`, `notFound()`, `exception()`.\n\n### `InternalExceptionObserver`\n\n```cpp\nvoid reportInternalException(const kj::Exception& exception, Detail detail) override {\n  // detail.isInternal, detail.isFromRemote, detail.isDurableObjectReset, detail.internalErrorId\n}\n```\n\n---\n\n## 21. BackingStore and BufferSource\n\n### `jsg::BackingStore`\n\nWraps `v8::BackingStore` with type information. Once allocated, can be safely used outside\nthe isolate lock.\n\n#### Creating\n\n```cpp\n// From kj::Array (takes ownership)\nauto backing = jsg::BackingStore::from<v8::Uint8Array>(js, kj::mv(data));\n\n// Allocate new zero-initialized buffer\nauto backing = jsg::BackingStore::alloc<v8::Uint8Array>(js, 1024);\n\n// Wrap external data with custom disposer\nauto backing = jsg::BackingStore::wrap<v8::Uint8Array>(\n    externalData, 1024,\n    [](void* data, size_t len, void* ctx) { free(data); },\n    nullptr);\n```\n\nTemplate parameter specifies TypedArray type for JavaScript conversion.\n\n#### Accessing Data\n\n```cpp\nkj::ArrayPtr<kj::byte> bytes = backing.asArrayPtr();\nkj::ArrayPtr<uint32_t> u32s = backing.asArrayPtr<uint32_t>();\nsize_t size = backing.size();\nsize_t offset = backing.getOffset();\nsize_t elemSize = backing.getElementSize();\nbool isInt = backing.isIntegerType();\n```\n\n#### Manipulating Views\n\n```cpp\nauto uint16View = backing.getTypedView<v8::Uint16Array>();\nauto slice = backing.getTypedViewSlice<v8::Uint8Array>(10, 100);\nbacking.consume(10);  // Skip first 10 bytes\nbacking.trim(10);     // Remove last 10 bytes\nbacking.limit(100);   // Cap at 100 bytes\nauto cloned = backing.clone();  // Shares buffer\nauto copied = backing.copy<v8::Uint8Array>(js);  // New buffer\n```\n\n#### Converting Back to JavaScript\n\n```cpp\nv8::Local<v8::Value> handle = backing.createHandle(js);\n```\n\n### `jsg::BufferSource`\n\nWraps a JavaScript ArrayBuffer or ArrayBufferView, retaining the original reference and\nsupporting detachment.\n\n```cpp\nclass MyApi: public jsg::Object {\npublic:\n  jsg::BufferSource processData(jsg::Lock& js, jsg::BufferSource source) {\n    jsg::BackingStore backing = source.detach(js);\n    // Process backing data...\n    return jsg::BufferSource(js, kj::mv(backing));\n  }\n};\n```\n\n#### Creating\n\n```cpp\njsg::BufferSource source1(js, kj::mv(backing));     // From BackingStore\njsg::BufferSource source2(js, jsValue);              // From JS handle\nKJ_IF_SOME(s, jsg::BufferSource::tryAlloc(js, 1024)) { /* ... */ }\njsg::BufferSource source5 = js.arrayBuffer(kj::mv(data));  // From kj::Array\n```\n\n#### Detaching\n\n```cpp\nif (!source.isDetached() && source.canDetach(js)) {\n  jsg::BackingStore backing = source.detach(js);\n  // Original JS ArrayBuffer is now neutered (zero-length)\n}\n```\n\nDetach keys for security:\n\n```cpp\nv8::Local<v8::Value> key = js.str(\"secret-key\");\nsource.setDetachKey(js, key);\njsg::BackingStore backing = source.detach(js, key);\n```\n\n#### Other Operations\n\n```cpp\nvoid otherOps(jsg::Lock& js, jsg::BufferSource& source) {\n  v8::Local<v8::Value> handle = source.getHandle(js);\n\n  // Query properties\n  size_t size = source.size();\n  size_t offset = source.getOffset();\n  size_t elemSize = source.getElementSize();\n  bool isInt = source.isIntegerType();\n\n  // Get underlying ArrayBuffer size (if not detached)\n  KJ_IF_SOME(bufSize, source.underlyingArrayBufferSize(js)) {\n    // bufSize is the total ArrayBuffer size, not the view size\n  }\n\n  source.trim(js, 10);  // Remove last 10 bytes\n  jsg::BufferSource cloned = source.clone(js);   // Shares backing, new JS handle\n  jsg::BufferSource copied = source.copy<v8::Uint8Array>(js);  // New backing + handle\n  jsg::BufferSource slice = source.getTypedViewSlice<v8::Uint8Array>(js, 0, 100);\n  source.setToZero();\n}\n```\n\n#### GC Visitation\n\n```cpp\nclass MyClass: public jsg::Object {\nprivate:\n  kj::Maybe<jsg::BufferSource> buffer;\n  void visitForGc(jsg::GcVisitor& visitor) {\n    KJ_IF_SOME(b, buffer) {\n      visitor.visit(b);\n    }\n  }\n};\n```\n\n### V8 Sandbox Considerations\n\nWhen V8 sandboxing is enabled:\n\n- `BackingStore::from()` copies data if the source is outside the sandbox\n- `BackingStore::alloc()` always allocates inside the sandbox\n- A `BackingStore` cannot be passed to another isolate unless both are in the same `IsolateGroup`\n\n---\n\n## 22. V8 Platform Wrapper\n\n`V8PlatformWrapper` in `v8-platform-wrapper.h` wraps V8's platform interface to intercept\nand customize platform operations. It delegates most operations to an inner `v8::Platform`\nbut wraps `JobTask` objects. Used internally by `V8System` for monitoring background work,\nKJ event loop integration, and debugging/profiling V8's background tasks.\n\nMost users won't interact with it directly.\n\n---\n\n## 23. Module System\n\nJSG supports ES modules (ESM), CommonJS-style modules, and various synthetic module types\n(JSON, WASM, data, text). There are two implementations.\n\n### Concepts\n\n**Module Types:**\n\n- `Type::BUNDLE` — Modules from the worker bundle (user code)\n- `Type::BUILTIN` — Built-in runtime modules (e.g., `node:buffer`, `cloudflare:sockets`)\n- `Type::INTERNAL` — Internal modules only importable by other built-ins\n\n**Resolution Priority:**\n\n1. From bundle code: Bundle -> Builtin -> Fallback\n2. From builtin code: Builtin -> Internal\n3. From internal code: Internal only\n\n**Module Info Types:** ESM, CommonJsModuleInfo, DataModuleInfo, TextModuleInfo,\nWasmModuleInfo, JsonModuleInfo, ObjectModuleInfo, CapnpModuleInfo.\n\n### Original Module Registry (`modules.h`)\n\n```cpp\nauto registry = jsg::ModuleRegistryImpl<MyTypeWrapper>::install(isolate, context, observer);\n\n// Add a worker bundle module (ESM)\nregistry->add(specifier, kj::mv(moduleInfo));\n\n// Add built-in modules\nregistry->addBuiltinModule(\"node:buffer\", sourceCode, jsg::ModuleRegistry::Type::BUILTIN);\nregistry->addBuiltinBundle(bundle);  // Add modules from a capnp bundle\nregistry->addBuiltinModule<MyApiClass>(\"workerd:my-api\");\nregistry->addBuiltinModule(\"workerd:instance\", kj::mv(myRef));\n\n// Lazy factory\nregistry->addBuiltinModule(\"workerd:dynamic\",\n    [](jsg::Lock& js, auto method, auto& referrer) -> kj::Maybe<ModuleInfo> {\n      return ModuleInfo(js, \"workerd:dynamic\", kj::none,\n          ObjectModuleInfo(js, createMyObject(js)));\n    });\n```\n\n#### Resolving Modules\n\n```cpp\nauto* registry = jsg::ModuleRegistry::from(js);\nKJ_IF_SOME(info, registry->resolve(js, specifier, referrer)) {\n  v8::Local<v8::Module> module = info.module.getHandle(js);\n  jsg::instantiateModule(js, module);\n  v8::Local<v8::Value> ns = module->GetModuleNamespace();\n}\n```\n\n#### Synthetic Modules\n\n```cpp\n// Data module\nauto info = ModuleInfo(js, \"data.bin\", kj::none, DataModuleInfo(js, buffer));\n\n// Text module\nauto info = ModuleInfo(js, \"text.txt\", kj::none, TextModuleInfo(js, text));\n\n// JSON module\nauto info = ModuleInfo(js, \"config.json\", kj::none, JsonModuleInfo(js, jsonValue));\n\n// WASM module\nauto info = ModuleInfo(js, \"module.wasm\", kj::none, WasmModuleInfo(js, wasmModule));\n```\n\n#### CommonJS Modules\n\n```cpp\nclass MyModuleProvider: public ModuleRegistry::CommonJsModuleInfo::CommonJsModuleProvider {\npublic:\n  JsObject getContext(Lock& js) override {\n    return js.obj();  // 'this' context for the module\n  }\n  JsValue getExports(Lock& js) override {\n    return js.obj();  // Initial exports object\n  }\n};\n\nauto provider = kj::heap<MyModuleProvider>();\nauto info = ModuleRegistry::CommonJsModuleInfo(js, \"module.js\", sourceCode, kj::mv(provider));\n```\n\n### New Module Registry (`modules-new.h`)\n\nKey improvements: URL-based specifiers, thread safety across isolate replicas, modular\nbundles, import.meta support, import attributes.\n\n#### Creating Modules\n\n```cpp\nauto esmModule = Module::newEsm(\"file:///bundle/worker.js\"_url,\n    Module::Type::BUNDLE, kj::mv(code),\n    Module::Flags::MAIN | Module::Flags::ESM);\n\nauto syntheticModule = Module::newSynthetic(\"workerd:my-module\"_url,\n    Module::Type::BUILTIN,\n    [](Lock& js, const Url& id, const Module::ModuleNamespace& ns,\n       const CompilationObserver& observer) -> bool {\n      ns.setDefault(js, js.obj());\n      ns.set(js, \"foo\", js.str(\"bar\"));\n      return true;\n    },\n    kj::arr(\"foo\"_kj));\n```\n\n#### Module Handlers\n\n```cpp\nauto textHandler = Module::newTextModuleHandler(textContent);\nauto dataHandler = Module::newDataModuleHandler(binaryData);\nauto jsonHandler = Module::newJsonModuleHandler(jsonContent);\nauto wasmHandler = Module::newWasmModuleHandler(wasmBytes);\n```\n\n#### Building Module Bundles\n\n```cpp\n// Worker bundle\nUrl bundleBase = \"file:///bundle\"_url;\nModuleBundle::BundleBuilder bundleBuilder(bundleBase);\nbundleBuilder\n    .addEsmModule(\"worker.js\", workerSource, Module::Flags::MAIN | Module::Flags::ESM)\n    .addEsmModule(\"utils.js\", utilsSource)\n    .alias(\"./lib\", \"./utils.js\");\nauto workerBundle = bundleBuilder.finish();\n\n// Builtin bundle\nModuleBundle::BuiltinBuilder builtinBuilder(ModuleBundle::BuiltinBuilder::Type::BUILTIN);\nbuiltinBuilder\n    .addEsm(\"node:buffer\"_url, bufferSource)\n    .addSynthetic(\"cloudflare:sockets\"_url, socketsHandler)\n    .addObject<MyApiClass, MyTypeWrapper>(\"workerd:my-api\"_url);\nauto builtinBundle = builtinBuilder.finish();\n\n// Internal-only bundle (modules only importable by other built-ins, e.g. node-internal:*)\nModuleBundle::BuiltinBuilder internalBuilder(ModuleBundle::BuiltinBuilder::Type::BUILTIN_ONLY);\ninternalBuilder\n    .addEsm(\"node-internal:primordials\"_url, primordialsSource)\n    .addEsm(\"node-internal:errors\"_url, errorsSource);\nauto internalBundle = internalBuilder.finish();\n```\n\n#### Fallback Bundle\n\n```cpp\nauto fallbackBundle = ModuleBundle::newFallbackBundle(\n    [](const ResolveContext& context)\n        -> kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>> {\n      KJ_IF_SOME(code, fetchModule(context.normalizedSpecifier)) {\n        return Module::newEsm(context.normalizedSpecifier.clone(),\n                              Module::Type::FALLBACK, kj::mv(code));\n      }\n      if (shouldRedirect(context.normalizedSpecifier)) {\n        return kj::str(\"node:buffer\");  // Redirect\n      }\n      return kj::none;\n    });\n```\n\n#### Resolution Context\n\n```cpp\nstruct ResolveContext {\n  Type type;           // BUNDLE, BUILTIN, or BUILTIN_ONLY\n  Source source;       // STATIC_IMPORT, DYNAMIC_IMPORT, REQUIRE, or INTERNAL\n  const Url& normalizedSpecifier;\n  const Url& referrerNormalizedSpecifier;\n  kj::Maybe<kj::StringPtr> rawSpecifier;\n  kj::HashMap<kj::StringPtr, kj::StringPtr> attributes;\n};\n```\n\n### Module Instantiation\n\n```cpp\nv8::Local<v8::Module> module = moduleInfo.module.getHandle(js);\njsg::instantiateModule(js, module);\n// Or: jsg::instantiateModule(js, module, InstantiateModuleOptions::NO_TOP_LEVEL_AWAIT);\nv8::Local<v8::Value> ns = module->GetModuleNamespace();\n```\n\n### Dynamic Import and `require()`\n\nDynamic imports are handled automatically. For CommonJS compatibility:\n\n```cpp\nJsValue exports = ModuleRegistry::requireImpl(js, moduleInfo);\nJsValue defaultExport = ModuleRegistry::requireImpl(js, moduleInfo,\n    ModuleRegistry::RequireImplOptions::EXPORT_DEFAULT);\n```\n\n### Choosing Between Implementations\n\nUse **original** (`modules.h`) when: simpler control, kj::Path specifiers, no cross-replica\nsharing needed.\n\nUse **new** (`modules-new.h`) when: URL-based specifiers, import.meta, cross-replica sharing,\nimport attributes, new code from scratch.\n\n---\n\n## 24. CompileCache\n\nProcess-lifetime in-memory cache for V8 compilation data, specifically for built-in JavaScript\nmodules. Entries are never removed or replaced.\n\n```cpp\nconst jsg::CompileCache& cache = jsg::CompileCache::get();\n\nKJ_IF_SOME(cachedData, cache.find(cacheKey)) {\n  auto v8CachedData = cachedData.AsCachedData();\n  // Compile with cached data...\n} else {\n  // Compile without cache, then store:\n  cache.add(cacheKey, std::shared_ptr<v8::ScriptCompiler::CachedData>(\n      source.GetCachedData()));\n}\n```\n\n### `CompileCache::Data`\n\nThe cache stores `Data` objects that wrap V8's `ScriptCompiler::CachedData`:\n\n```cpp\nclass CompileCache::Data {\npublic:\n  // Create V8 cached data for use with ScriptCompiler\n  std::unique_ptr<v8::ScriptCompiler::CachedData> AsCachedData();\n\n  const uint8_t* data;  // Raw cached data\n  size_t length;        // Data length\n};\n```\n\nThe cache is internally mutex-guarded and safe for concurrent access from multiple threads.\n\n---\n\n[\"KJ Style Guide\"]: https://github.com/capnproto/capnproto/blob/master/style-guide.md\n[\"KJ Tour\"]: https://github.com/capnproto/capnproto/blob/master/kjdoc/tour.md\n[Record]: https://webidl.spec.whatwg.org/#idl-record\n[Sequence]: https://webidl.spec.whatwg.org/#idl-sequence\n"
  },
  {
    "path": "docs/pyodide.md",
    "content": "# Pyodide Package Indices\n\nworkerd is linked against a Pyodide lock file, which is located within an R2 bucket. At build time this lock file is fetched and bundled into the binary. (See WORKSPACE and search for `pyodide-lock.json`)\n\nIf you know where the R2 bucket is (See build/pyodide_bucket.bzl) then the `pyodide-lock.json` file is located inside the root of the R2 directory for the Pyodide package bundle release.\n\nThis lock file contains some information used by workerd to pull in package requirements, including but not limited to:\n\n- The versions of each package included in the package bundle\n- The file names and SHA hashes of each package available for download in the bucket\n- What the dependencies are for each package\n\n## Generating pyodide_bucket.bzl\n\nWe have scripts and GitHub actions set up for building and uploading Pyodide package bundles onto R2. These are available [here](https://github.com/cloudflare/pyodide-build-scripts). Simply follow the instructions on that repo to build a new version of Pyodide or a new package bundle release.\n"
  },
  {
    "path": "docs/reference/api-review-checklist.md",
    "content": "These checkists are to be used when performing code reviews for API design, performance optimization, security vulnerabilities, and standards compliance. They are not exhaustive but cover common patterns and issues to look for. Always consider the specific context of the code being reviewed and apply judgment accordingly.\n\n### Performance Optimization\n\nEnd-to-end, real-world performance is the priority over micro-optimizations.\n\n- **Always** identify unnecessary allocations and copies (keeping in mind that we're using tcmalloc)\n- **Always** identify opportunities for move semantics that avoid unnecessary copies\n- **Always** identify hot code paths that could benefit from optimization\n- **Always** review data structure choices that would improve performance (e.g. using `workerd::RingBuffer` instead of `kj::Vector` for bounded queues)\n- **Always** analyze and suggest improvements for cache locality and memory layout\n- **Always** identify lock contention and synchronization overhead\n- **Always** identify inefficient string operations or repeated parsing or copying\n- **Always** avoid premature optimization; focus on clear evidence of performance issues\n- **Never** make vague or grandiose claims of performance improvements without clear reasoning or evidence\n- **Always** suggest improvements for better use of KJ library features for performance\n- **Always** consider overall system performance, including interactions between components, I/O, and network latency.\n- **Always** avoid optimizations that improve microbenchmarks but do not translate to real-world gains.\n- **Always** evaluate trade-offs: an optimization that benefits one workload may degrade another. Aim for broad benefits.\n- **Always** consider scalability: solutions should maintain or improve performance as workloads increase.\n- It's ok to suggest low-risk micro-optimizations as low-hanging fruit, but they are not the primary focus.\n\n### API Design & Compatibility\n\n- **Always** evaluate API ergonomics and usability\n- **Always** review backward compatibility implications\n- **Always** check for proper use of compatibility flags (`compatibility-date.capnp`) and\n  autogates (`util/autogate.h/c++`). Use the `compat-date-at` tool to look up flag details.\n  Use the `next-capnp-ordinal` tool when adding new flags.\n- Use the `cross-reference` tool to look up JSG registration, type groups, and test\n  coverage for API classes under review.\n- Use the `jsg-interface` tool to extract the full structured JS API (methods,\n  properties, constants, nested types, inheritance) for a class under review.\n- **Always** identify breaking changes that need feature flags or autogates\n- **Error type changes are generally not breaking.** Changing the type of error thrown (e.g., from a\n  generic `kj::Exception` to a JS `TypeError`, or between JS error types) is not normally considered\n  a breaking change because well-written user code should not depend on specific error types. The\n  exception is when the change removes properties that code could reasonably depend on — for\n  instance, changing from a `DOMException` to a `TypeError` is breaking because `DOMException` has\n  properties like `code` and `name` with specific values that `TypeError` does not. Use judgment:\n  if the error type change could plausibly break real user code in a substantial way, treat it as\n  breaking and gate it behind a compat flag.\n- **Never** recommend or approve the removal of an existing compatibility flag.\n- **Never** recommend or approve changing/inverting the meaning of an existing compatibility flag.\n- **Never** suggest the compatibility flag checks are \"dead code\" that can be removed. Compatibility flags are permanent and must be maintained indefinitely, even if there is nothing apparently depending on them.\n- **Always** review consistency with existing API patterns\n\n### Security Vulnerabilities\n\n- **Always** identify memory safety issues using the `docs/reference/cpp-safety-review-checklist.md` checklist.\n- **Always** identify injection vulnerabilities\n- **Always** review input validation and sanitization.\n- **Always** identify potential timing side channels\n- **Always** analyze privilege boundaries and capability checks\n- **Always** look for information disclosure risks\n- **Always** review cryptographic usage patterns\n- **Always** identify TOCTOU (time-of-check-time-of-use) issues\n- workerd favors use of capability-based security\n\n### Standards Spec Compliance\n\n- **Always** review adherence to relevant web standards (Fetch, Streams, WebCrypto, etc.).\n  Variation from spec behavior is allowed but must be justified and documented.\n- **Always** identify deviations from spec behavior and suggest improvements for better alignment\n- **Always** review documentation accuracy against specs and implementation\n- **Always** identify missing features required by specs\n- **Always** identify interoperability issues with other implementations\n- **Always** identify edge cases not handled per specs\n- **Always** reference specific spec sections when flagging deviations\n\n### Runtime-Specific Notes\n\n- **KJ event loop**: workerd uses kj's single-threaded event loop, not Node.js-style libuv.\n\n"
  },
  {
    "path": "docs/reference/cpp-safety-review-checklist.md",
    "content": "These checkists are to be used when performing code reviews for memory safety, thread safety, and concurrency correctness. They are not exhaustive but cover common patterns and issues to look for. Always consider the specific context of the code being reviewed and apply judgment accordingly.\n\n### Memory Safety\n\n- **Always** identify potential memory leaks, use-after-free, and dangling pointers or references\n- **Always** review ownership semantics and lifetime management\n- **Always** analyze smart pointer usage (`kj::Own`, `kj::Rc`, `kj::Maybe`)\n- **Always** check for proper RAII, CRTP patterns\n- **Always** look for potential buffer overflows and bounds checking issues\n- **Always** identify raw pointer usage that could be safer with owning types\n- **Always** review destructor correctness and cleanup order\n- **Always** analyze lambda captures for lifetime safety\n- **Always** consider patterns where weakrefs (see `util/weak-refs.h`) or other techniques would be safer\n- **Always** verify that methods returning references, pointers, `kj::ArrayPtr`, `kj::StringPtr`, or other non-owning views into data owned by `this` (or by a parameter) are annotated with `KJ_LIFETIMEBOUND`. This expands to `[[clang::lifetimebound]]` and enables the compiler to warn when the returned\n  view outlives the object it borrows from.\n- Functions returning owned resources, `kj::Maybe`, error indicators, or expensive-to-compute\n  results **should** be annotated with `KJ_WARN_UNUSED_RESULT`. This catches two classes of problems:\n  silently discarding results the caller must act on (e.g., error codes, `kj::Promise`), and\n  performing expensive computation whose result is thrown away.\n- Lambdas capturing `jsg::Ref<T>` or other GC-traced references **should** use `JSG_VISITABLE_LAMBDA`\n  (see `jsg/function.h`) so V8's GC can trace through the captures.\n- **Always** verify that RAII scope guards, locks, and other types with positional semantics use\n  `KJ_DISALLOW_COPY_AND_MOVE` to prevent accidental moves that break scope invariants. Use\n  `KJ_DISALLOW_COPY` only when explicit move semantics are intentionally provided. Flag new\n  scope-guard or lock types that are missing these annotations.\n\n### Thread Safety & Concurrency\n\n- **Always** identify data races and race conditions\n- **Always** review lock ordering and deadlock potential\n- **Always** analyze shared state access patterns\n- **Always** check for proper synchronization primitives usage\n- **Always** reeview promise/async patterns for correctness\n- **Always** identify thread-unsafe code in concurrent contexts\n- **Always** analyze KJ event loop interactions\n- **Always** ensure that code does not attempt to use isolate locks across suspension points in coroutines.\n  Types that must never be held across `co_await` should carry the `KJ_DISALLOW_AS_COROUTINE_PARAM`\n  annotation for compile-time enforcement. This is already applied to `jsg::Lock`, `Worker::Lock`,\n  `jsg::V8StackScope`, and similar types. Flag new lock or scope types that are missing it.\n- **Always** ensure that RAII objects and other types that capture raw pointers or references are not unsafely\n  used across suspension points without validating that captured references and pointers remain valid.\n- **Always** pay particular attention to V8 garbage collection interactions and cleanup order\n- **Always** verify that kj I/O objects are never held by a V8-heap object (especially `jsg::Object` instances) without use of `IoOwn` or `IoPtr` (see `io/io-own.h`) to ensure proper lifetime and thread-safety.\n- **Always** watch carefully for places where `kj::Refcounted` is used when `kj::AtomicRefcounted` is required\n  for thread safety.\n\n### Critical Detection Patterns\n\n**Always** watch for these concrete patterns. When you encounter these, flag them at the indicated severity.\n\n**Always** watch for non-obvious complexity at V8/KJ boundaries, including:\n- re-entrancy bugs where a C++ callback unexpectedly re-enters JavaScript\n- subtle interactions between KJ event loop scheduling and V8 GC timing\n- cases where destruction order depends on runtime conditions.\n\n**CRITICAL / HIGH:**\n\n- **V8 callback throwing C++ exception**: A V8 callback (JSG method, property getter/setter) that\n  can throw a C++ exception without using `liftKj` (see `jsg/util.h`). V8 callbacks **must** catch\n  C++ exceptions and convert them to JS exceptions.\n- **V8 heap object holding kj I/O object directly**: A `jsg::Object` subclass storing `kj::Own<T>`,\n  `kj::Rc<T>`, `kj::Arc<T>` for an I/O-layer object without wrapping in `IoOwn` or `IoPtr` (see\n  `io/io-own.h`). Causes lifetime and thread-safety bugs.\n- **`kj::Refcounted` in cross-thread context**: A class using `kj::Refcounted` whose instances can\n  be accessed from both the I/O thread and the JS isolate thread. Needs `kj::AtomicRefcounted`.\n- **Isolate lock held across `co_await`**: Holding a `jsg::Lock`, V8 `HandleScope`, or similar V8\n  scope object across a coroutine suspension point. This is undefined behavior.\n- **RAII object with raw pointer/reference across `co_await`**: Any RAII type or variable capturing\n  a raw pointer or reference used across a coroutine suspension point without `kj::coCapture` to\n  ensure correct lifetime.\n- **Reference cycle between `jsg::Object` subclasses**: Two or more `jsg::Object` subclasses holding\n  strong references (`jsg::Ref<T>` or `kj::Own<T>`) to each other without GC tracing via `JSG_TRACE`.\n  Causes memory leaks invisible to V8's GC. This includes transitive cycles (e.g., A holds B, B\n  holds C, C holds A) and cycles involving more than two objects.\n\n**MEDIUM (safety-related):**\n\n- **Broad capture in async lambda**: Lambda passed to `.then()` or stored for deferred execution\n  using `[&]` or `[this]` when only specific members are needed. Prefer explicit captures and\n  carefully consider captured variable lifetimes.\n- **Implicit GC trigger in sensitive context**: V8 object allocations (e.g., `ArrayBuffer` backing\n  store creation, string flattening, `v8::Object::New()`) inside hot loops or time-sensitive\n  callbacks may trigger GC unexpectedly.\n- **Missing `DISALLOW_KJ_IO_DESTRUCTORS_SCOPE` awareness**: The `DISALLOW_KJ_IO_DESTRUCTORS_SCOPE`\n  macro (see `jsg/wrappable.h`) creates a scope that crashes the process if any KJ async/I/O object\n  is destroyed. It enforces that JS-heap objects use `IoOwn`/`IoPtr` rather than holding I/O objects\n  directly. `IoOwn`'s destructor creates a matching `AllowAsyncDestructorsScope` to permit safe\n  destruction. When reviewing code that introduces new GC or destructor paths, verify these scopes\n  are correctly nested.\n\n### Runtime-Specific Safety Notes\n\n- **tcmalloc**: workerd uses tcmalloc. Thread-local caches reduce contention but increase per-thread\n  memory overhead. Focus optimization on reducing allocation count (especially in hot paths) rather\n  than individual allocation sizes. Do not suggest switching to standard malloc.\n- **Cap'n Proto zero-copy**: Cap'n Proto messages are zero-copy and use arena allocation. Do not\n  suggest copying data out of Cap'n Proto messages \"for safety\" unless there is a concrete lifetime\n  issue. Suggest using Cap'n Proto's traversal limits to prevent resource exhaustion when processing\n  untrusted messages.\n- **V8 GC awareness**: V8 may trigger garbage collection at any allocation point. Operations that\n  create V8 objects (including string flattening, ArrayBuffer creation) can trigger GC. Be aware\n  of this when analyzing code that interleaves V8 allocations with raw pointer access to V8-managed\n  objects.\n- **Destructors may throw**: workerd follows KJ convention of `noexcept(false)` destructors. Do not\n  flag this as an issue unless there is a specific exception safety problem (e.g., double-exception\n  during stack unwinding).\n- **Cross-platform**: workerd runs on Linux in production but builds on macOS and Windows. Flag\n  platform-specific system calls or assumptions (e.g., Linux-only epoll, /proc filesystem) that\n  lack portable alternatives.\n"
  },
  {
    "path": "docs/reference/detail/api-patterns.md",
    "content": "Detailed code examples for idiomatic KJ API usage patterns in workerd.\n\n### Unwrapping `kj::Maybe`\n\n**Always** use `KJ_IF_SOME`. **Never** dereference directly.\n\n```cpp\n// Correct:\nKJ_IF_SOME(value, maybeValue) {\n  use(value);  // value is a reference to the contained T\n}\n\n// Correct:\nauto& value = KJ_ASSERT_NONNULL(maybeValue);  // asserts if maybeValue is none, otherwise gives T&\nauto& value = KJ_REQUIRE_NONNULL(maybeValue);  // same but for preconditions\nauto& value = JSG_REQUIRE_NONNULL(maybeValue, ErrorType, \"message\");  // same but with JavaScript exception type/message\n\n// Wrong\nauto& value = *maybeValue;      // doesn't exist\nauto& value = maybeValue.value(); // not how KJ works\n```\n\n**Maybe** use `maybe.map(fn)` and `maybe.orDefault(val)` are useful for simple transforms/fallbacks.\n\nWhen moving a value out of a `kj::Maybe`, use `kj::mv()` and remember to set the `kj::Maybe` to `kj::none` to avoid dangling references:\n\n```cpp\nKJ_IF_SOME(value, maybeValue) {\n  auto movedValue = kj::mv(value);  // move out of the Maybe\n  maybeValue = kj::none;            // set Maybe to none to avoid dangling reference\n  use(movedValue);\n}\n```\n\n### Unwrapping `kj::OneOf`\n\n**Always** use `KJ_SWITCH_ONEOF` / `KJ_CASE_ONEOF`:\n\n```cpp\nKJ_SWITCH_ONEOF(variant) {\n  KJ_CASE_ONEOF(s, kj::String) { handleString(s); }\n  KJ_CASE_ONEOF(i, int) { handleInt(i); }\n}\n```\n\n### Building Strings\n\n**Always** use `kj::str()`, never `std::to_string` or `+` concatenation:\n\n```cpp\nkj::String msg = kj::str(\"count: \", count, \", name: \", name);\n```\n\nUse `kj::hex(n)` for hex output. Extend with `KJ_STRINGIFY(MyType)` or a `.toString()` method.\nUse `\"literal\"_kj` suffix for `kj::StringPtr` literals (can be `constexpr`).\n\n### Scope-Exit Cleanup\n\nUse `KJ_DEFER` or `kj::defer()`:\n\n```cpp\nKJ_DEFER(close(fd));                          // block scope\nauto cleanup = kj::defer([&]() { close(fd); }); // movable, for non-block lifetimes\n```\n\nAlso: `KJ_ON_SCOPE_SUCCESS(...)` and `KJ_ON_SCOPE_FAILURE(...)` for conditional cleanup.\n\n### Syscall Error Checking\n\nUse `KJ_SYSCALL`, never manual errno checks:\n\n```cpp\nint n;\nKJ_SYSCALL(n = read(fd, buf, size));          // throws on error, retries EINTR\nKJ_SYSCALL_HANDLE_ERRORS(fd = open(path, O_RDONLY)) {\n  case ENOENT: return nullptr;                // handle specific errors\n  default: KJ_FAIL_SYSCALL(\"open()\", error);\n}\n```\n\n### Downcasting\n\nUse `kj::downcast<T>` (debug-checked), not `static_cast` or `dynamic_cast`:\n\n```cpp\nauto& derived = kj::downcast<DerivedType>(baseRef);  // asserts in debug builds\n```\n\nUse `kj::dynamicDowncastIfAvailable<T>` only for optimizations (returns null without RTTI).\n\n### Iteration Helpers\n\n```cpp\nfor (auto i: kj::zeroTo(n)) { ... }        // 0..n-1\nfor (auto i: kj::range(a, b)) { ... }      // a..b-1\nfor (auto i: kj::indices(array)) { ... }    // 0..array.size()-1\n```\n"
  },
  {
    "path": "docs/reference/detail/async-patterns.md",
    "content": "Promise lifetime management, background tasks, cancellation, and mutex usage in workerd.\n\n---\n\n### Promise Patterns\n\n**`.attach()` for lifetime management** — objects must outlive the promise that uses them:\n\n```cpp\n// Correct: stream stays alive until readAllText() completes\nreturn stream->readAllText().attach(kj::mv(stream));\n\n// Wrong: stream destroyed immediately, promise has dangling reference\nauto promise = stream->readAllText();\nreturn promise;  // stream is gone\n```\n\n**`.eagerlyEvaluate()` for background tasks** — without it, continuations are lazy\nand may never run:\n\n```cpp\npromise = doWork().then([]() {\n  KJ_LOG(INFO, \"done\");  // won't run unless someone .wait()s or .then()s\n}).eagerlyEvaluate([](kj::Exception&& e) {\n  KJ_LOG(ERROR, e);      // error handler is required\n});\n```\n\nWhen using coroutines, `eagerlyEvaluate()` is implied and not needed to be called explicitly.\n\nUse `kj::TaskSet` to manage many background tasks with a shared error handler.\n\n**Cancellation** — destroying a `kj::Promise` cancels it immediately. No continuations\nrun, only destructors. Use `.attach(kj::defer(...))` for cleanup that must happen\non both completion and cancellation.\n\n**`kj::evalNow()`** — wraps synchronous code to catch exceptions as rejected promises:\n\n```cpp\nreturn kj::evalNow([&]() {\n  // Any throw here becomes a rejected promise, not a synchronous exception\n  return doSomethingThatMightThrow();\n});\n```\n\n---\n\n### Mutex Patterns\n\n`kj::MutexGuarded<T>` ties locking to access — you can't touch the data without a lock:\n\n```cpp\n// Exclusive access for modification\n{\n  auto lock = guarded.lockExclusive();\n  lock->modify();\n  // lock is released at end of scope\n}\n\n// Multiple readers ok\n{\n  auto shared = guarded.lockShared();\n  shared->read();\n  // shared lock is released at end of scope\n}\n```\n\n`.wait(cond)` on a lock replaces condition variables:\n\n```cpp\nauto lock = guarded.lockExclusive();\nlock.wait([](const T& val) { return val.ready; });  // releases/reacquires automatically\n```\n\n#### Correctly Holding Locks\n\nWhen using `kj::MutexGuarded<T>`, ensure the lock is correctly held for the duration of any\naccess to the guarded data.\n\nDo this:\n\n```cpp\nauto lock = mutexGuarded.lockExclusive();\nKJ_IF_SOME(value, lock->maybeValue) {\n  // Access value safely while lock is held.\n}\n```\n\nNot this:\n\n```cpp\nKJ_IF_SOME(value, mutexGuarded.lockExclusive()->maybeValue) {\n  // Unsafe! The lock is released before we access maybeValue.\n}\n```\n"
  },
  {
    "path": "docs/reference/detail/review-checklist.md",
    "content": "# Code Review Checklist\n\nWhen reviewing workerd C++ code, check for each of these items.\n\n- **Always** check for STL leaking in: `std::string`, `std::vector`, `std::optional`, `std::unique_ptr`, etc.\n- **Never** allow raw `new`/`delete`**. Should be `kj::heap<T>()` or similar\n- **Never** use `throw` statements. Should use `KJ_ASSERT`/`KJ_REQUIRE`/`KJ_FAIL_ASSERT`/etc\n- **Never** use `noexcept`\n- **Always** use `noexcept(false)` on explicit destructors\n- **Never** use `[=]` lambda captures\n- **Never** use nullable raw pointers. Should be `kj::Maybe<T&>`\n- **Never** mixed interface and implementation classes. No data members in interfaces, no non-final virtuals in implementations. Intermediate subclasses are ok\n- **Never** use singletons or mutable globals\n- **Always** verify proper use of `.attach()` on promises. Objects must stay alive for the promise duration\n- **Always** use `.eagerlyEvaluate()` with background promises. Lazy continuations may never execute. Co-routines are eager by default.\n- **Never** use manual errno checks. Should use `KJ_SYSCALL` / `KJ_SYSCALL_HANDLE_ERRORS`\n- **Avoid** `static_cast` for downcasting where possible. Should be `kj::downcast<T>` (debug-checked)\n- **Avoid** `dynamic_cast` for dispatch where possible. Extend the interface instead\n- **Never** use `std::to_string` or `+` string concatenation**\n- **Never** use `/* */` block comments. Use `//` line comments\n- **Always** verify naming convention conformance. TitleCase types, camelCase functions/variables, CAPS constants\n- **Always** check for missing braces around blocks. Required unless entire statement is on one line\n- **Always** avoid `bool` function parameters. Prefer `enum class` or `WD_STRONG_BOOL` for clarity at call sites. E.g., `void connect(bool secure)` should be `void connect(SecureMode mode)`\n- **Always** use `[[nodiscard]]` with functions returning error codes, `kj::Maybe`, or success booleans that callers must check should be `[[nodiscard]]`\n- **Always** prefer coroutines over kj::Promise chains. Nested `.then()` chains with complex error handling that would be more readable as a coroutine with `co_await`. But **always** avoid suggesting sweeping rewrites\n- **Always** check for missing `constexpr` / `consteval` where they would be appropriate\n- **Always** avoid reinventing utility with custom code duplicating functionality already in `src/workerd/util/` (e.g., custom ring buffer, small set, state machine, weak reference pattern). **Always** check the util directory before suggesting a new abstraction.\n- **Always** check for missing `override` on virtual method overrides.\n- **Always** flag magic numbers (Numeric literals) without explanation or named constants.\n- **Always** check for copyright header on new files. Every new `.c++` and `.h` file must begin with the project copyright/license header using the current year (not copied from older files). Expected format:\n    ```\n    // Copyright (c) <current-year> Cloudflare, Inc.\n    // Licensed under the Apache 2.0 license found in the LICENSE file or at:\n    //     https://opensource.org/licenses/Apache-2.0\n    ```\n    Flag any new file that uses a stale year (e.g., `2017-2022` in a file created in 2026) or omits the header entirely.\n"
  },
  {
    "path": "docs/reference/detail/type-design.md",
    "content": "# Type Design, Inheritance & Thread Safety\n\nClass design rules, inheritance patterns, and constness semantics in workerd.\n\n---\n\n### Two Kinds of Types\n\n**Value types** (data):\n\n- Copyable/movable, compared by value, can be serialized\n- No virtual methods; use templates for polymorphism\n- Always have move constructors\n\n**Resource types** (objects with identity):\n\n- Not copyable, not movable — use `kj::Own<T>` on the heap for ownership transfer\n- Use `KJ_DISALLOW_COPY_AND_MOVE` to prevent accidental copying/moving\n- Compared by identity, not value\n- May use inheritance and virtual methods\n\n### Inheritance\n\n- A class is either an **interface** (no data members, only pure virtual methods) or an\n  **implementation** (no non-final virtual methods). Never mix — this causes fragile base class problems.\n- Interfaces should **NOT** declare a destructor.\n- Multiple inheritance is allowed and encouraged (typically inheriting multiple interfaces).\n- Implementation inheritance is acceptable for composition without extra heap allocations.\n\n### Constness and Thread Safety\n\n- Treat constness as **transitive** — a const pointer to a struct means its contained pointers\n  are also effectively const.\n- **`const` methods must be thread-safe** for concurrent calls. Non-const methods require\n  exclusive access. `kj::MutexGuarded<T>` enforces this: `.lockShared()` returns `const T&`,\n  `.lockExclusive()` returns `T&`.\n- Copyable classes with pointer members: declare copy constructor as `T(T& other)` (not\n  `T(const T& other)`) to prevent escalating transitively-const references. Or inherit\n  `kj::DisallowConstCopy`.\n- Only hold locks for the minimum duration necessary to access or modify the guarded data.\n\n### Other Rules\n\n- **No global constructors**: Don't declare static/global variables with dynamic constructors.\n  Global `constexpr` constants are fine.\n- **No `dynamic_cast` for polymorphism**: Don't use long if/else chains casting to derived types.\n  Extend the base interface instead. `dynamic_cast` is OK for optimizations or diagnostics\n  (test: if it always returned null, would the code still be correct?).\n- **No function/method pointers**: Use templates over functors, or `kj::Function<T>`.\n- **Prefer references over pointers**: Unambiguously non-null.\n"
  },
  {
    "path": "docs/reference/kj-style.md",
    "content": "This document provides a style guide for C++ code in the workerd project, based on the KJ style used in the Cap'n Proto project. It covers naming conventions, type usage, memory management, error handling, inheritance, constness, formatting, and other idioms. The guide emphasizes consistency and readability while allowing pragmatic exceptions when justified.\n\n- Full guide: https://github.com/capnproto/capnproto/blob/v2/kjdoc/style-guide.md\n- Full API tour: https://github.com/capnproto/capnproto/blob/v2/kjdoc/tour.md\n\nThe KJ style guide is self-described as suggestions, not rules. Consistency matters, but\npragmatic exceptions are fine.\n\nThese guidelines are split across multiple files for context efficiency.\n\n**You MUST read the relevant reference files before writing or reviewing code that touches\ntheir subject matter. Do not rely on memory or general knowledge — the reference files contain\nproject-specific patterns and idioms that override general C++ conventions. Skipping a relevant\nreference file WILL lead to incorrect suggestions.**\n\n- `detail/review-checklist.md` **must** be used when performing ANY code review of workerd C++ code\n- `detail/api-patterns.md` **must** be used when code uses or should use `kj::Maybe`, `kj::OneOf`,\n  `kj::str()`, `KJ_DEFER`, `KJ_SYSCALL`, `kj::downcast`, or KJ iteration helpers\n- `detail/async-patterns.md` **must** be used when code involves `kj::Promise`, `.then()`,\n  `.attach()`, `.eagerlyEvaluate()`, coroutines, `kj::MutexGuarded`, or `kj::TaskSet`\n- `detail/type-design.md` **must** be used when designing new classes, reviewing class hierarchies,\n  analyzing constness or thread safety semantics, or deciding value-type vs resource-type\n\nWhen in doubt about whether a reference file is relevant, load it.\n\n### KJ Types over STL\n\nThe project uses KJ types instead of the C++ standard library:\n\n| Instead of              | Use                                                 |\n| ----------------------- | --------------------------------------------------- |\n| `std::string`           | `kj::String` (owned) / `kj::StringPtr` (borrowed)   |\n| `std::vector`           | `kj::Array<T>` (fixed) / `kj::Vector<T>` (growable) |\n| `std::unique_ptr`       | `kj::Own<T>`                                        |\n| `std::shared_ptr`       | `kj::Rc<T>` / `kj::Arc<T>` (thread-safe)            |\n| `std::optional`         | `kj::Maybe<T>`                                      |\n| `std::function`         | `kj::Function<T>`                                   |\n| `std::variant`          | `kj::OneOf<T...>`                                   |\n| `std::span` / array ref | `kj::ArrayPtr<T>`                                   |\n| `std::exception`        | `kj::Exception`                                     |\n| `std::promise`/`future` | `kj::Promise<T>` / `kj::ForkedPromise<T>`           |\n| `T*` (nullable)         | `kj::Maybe<T&>`                                     |\n\n\n- **Always** avoid including `<string>`, `<vector>`, `<memory>`, `<optional>`, `<functional>`,\n  `<variant>`, or other std headers from header files. Source-file-only use of `std::` is acceptable\n  when KJ has no equivalent or when use is required when interacting with a dependency.\n- **Always** use KJ types when a KJ equivalent exists, even if it requires more effort.\n- **Never** use `FILE*`, `iostream`, or C stdio.\n- **Always** use `kj::str()` for formatting, `KJ_DBG()` for debug printing, and `kj/io.h` for I/O.\n- **Never** leave `KJ_DBG` statements in committed code.\n\n### Memory Management\n\n- **Never write `new` or `delete`**. Use:\n  - `kj::heap<T>(args...)` → returns `kj::Own<T>`\n  - `kj::heapArray<T>(size)` → returns `kj::Array<T>`\n  - `kj::heapArrayBuilder<T>(size)` → build arrays element-by-element\n  - `kj::rc<T>(args...)` → returns `kj::Rc<T>`\n  - `kj::arc<T>(args...)` → returns `kj::Arc<T>`\n\n- **Always** use heap alocations when you need moveability.\n- **Always** prefer stack or member variables when possible and moving is not needed.\n- **Always** prefer an expicit `clone()` method over copy constructors or copy assignment\n\n### Ownership Model\n\n- Every object has exactly **one owner** (another object or a stack frame)\n- `kj::Own<T>` is an owned pointer that transfers ownership when moved\n- Raw C++ pointers and references = **not owned**, borrowing only\n- An object can **never own itself**, even transitively (no reference cycles)\n- Default lifetime rules for raw pointer/reference parameters:\n  - Constructor param → valid for lifetime of the constructed object\n  - Function/method param → valid until function returns (or returned promise completes)\n  - Method return → valid until the object it was called on is destroyed\n\n**Reference counting** is allowed. If used:\n\n- **Always** avoid cycles\n- **Always** prefer non-atomic refcounting:\n  - Atomic refcounting (`kj::AtomicRefcounted`) is extremely slow\n  - `kj::AtomicRefcounted` is only appropriate for objects that whose refcounting needs to occur\n    across threads.\n- **Always** prefer using the newer `kj::Rc<T>`/`kj::rc<T>(...)` and `kj::Arc<T>`/`kj::arc<T>(...)`\n  instead of `kj::refcounted<T>`, `kj::atomicRefcounted<T>`, and `kj::addRef()` directly.\n\n### Error Handling\n\n- **Never use `throw` directly**. Use KJ assertion macros from `kj/debug.h`:\n  - `KJ_ASSERT(cond, msg, values...)` — checks invariants (bug in this code)\n  - `KJ_REQUIRE(cond, msg, values...)` — checks preconditions (bug in caller)\n  - `KJ_FAIL_ASSERT(msg, values...)` — unconditional failure\n  - `KJ_SYSCALL(expr, msg, values...)` — wraps C syscalls, checks return values\n  - `KJ_UNREACHABLE` — marks unreachable code\n  - `kj::throwFatalError(msg, values...)` — throws an exception with a stack trace\n\n  These macros automatically capture file/line, stringify operands, and generate stack traces.\n\n- **Never declare anything `noexcept`**. Bugs can happen anywhere; aborting is never correct.\n- **Always** ensure that explicit destructors use `noexcept(false)`**. Use `kj::UnwindDetector` or\n  recovery blocks in destructors to handle the unwinding-during-unwind problem.\n\n**Exceptions are for fault tolerance**, not control flow. They represent things that \"should never\nhappen\" — bugs, network failures, resource exhaustion. If callers need to catch an exception\nto operate correctly, the interface is wrong. Offer alternatives like `openIfExists()` returning\n`kj::Maybe`.\n\n### RAII\n\n**Always** prefer RAII for resource management.\n\nCleanup should happen in destructors. A destructor should perform no more than one cleanup action.\n\nUse `KJ_DEFER(code)` for scope-exit cleanup.\n\n### Naming Conventions\n\n| Kind                          | Style                                                            |\n| ----------------------------- | ---------------------------------------------------------------- |\n| Types (classes, structs)      | `TitleCase`                                                      |\n| Variables, functions, methods | `camelCase`                                                      |\n| Constants, enumerants         | `CAPITAL_WITH_UNDERSCORES`                                       |\n| Macros                        | `CAPITAL_WITH_UNDERSCORES` with project prefix (`KJ_`, `CAPNP_`) |\n| Namespaces                    | `oneword` (keep short); private namespace: `_`                   |\n| Files                         | `module-name.c++`, `module-name.h`, `module-name-test.c++`       |\n\n### Lambda Capture Rules\n\n- **Never** use `[=]` (capture-all-by-value) — makes lifetime analysis impossible during review\n- **May** use `[&]` (capture-all-by-reference) but **only** when the lambda will not outlive the\n  current stack frame. Using `[&]` signals \"this lambda doesn't escape.\"\n- For escaping lambdas, capture each variable explicitly.\n- Use `kj::coCapture(...)` to capture variables in a way that extends their lifetime for the duration of the promise.\n\n### No Singletons\n\n**Never** use mutable globals or global registries. The `main()` function or high-level code should\nexplicitly construct components and wire dependencies via constructor parameters.\n\n### Lazy Input Validation\n\n**Always** validate data at time-of-use, not upfront. Upfront validation creates false confidence\ndownstream, gets out of sync with usage code, and wastes cycles on unused fields.\n\n### Text Encoding\n\nAll text is UTF-8.\n\n**Never** assume text is _valid_ UTF-8. Be tolerant of malformed sequences.\n\n### Formatting\n\n- **2-space indents**, never tabs\n- Max 100 chars/line\n- Continuation lines: 4-space indent (or align with opening delimiter)\n- Space after keywords: `if (foo)`, `for (...)`, `while (...)`\n- No space after function names: `foo(bar)`\n- Opening brace at end of statement; closing brace on its own line\n- Always use braces for blocks, unless the entire statement fits on one line\n- `public:`/`private:`/`protected:` reverse-indented by one stop from class body\n- Namespace contents are NOT indented\n- Strip trailing whitespace\n\n### Comments\n\nWorkerd follows these convention over KJ's own comment style:\n\n- **Always** use `//` line comments. **Never** `/* */` block comments\n- **Always** put doc comments **before** the declaration following common C++ conventions.\n- **Never** state the obvious. Comments should add information not evident from the code\n- TODO format: `// TODO(type): description` where type is: `now`, `soon`, `someday`, `perf`,\n  `security`, `cleanup`, `port`, `test`\n- `TODO(now)` comments **must** be addressed before merging.\n- **May* use `TODO({project})` type comments where `{project}` is the name of an active project\n  for work that is carried out over multiple pull requests.\n"
  },
  {
    "path": "docs/reference/rust-review-checklist.md",
    "content": "This document provides a checklist for reviewing Rust code in the workerd project. It covers style guidelines, common pitfalls, and critical patterns to watch for when ensuring code quality.\n\nSee `../../src/rust/jsg/README.md` for additional context.\n\n### CXX Bridge Safety\n\nAll Rust/C++ interop uses the `cxx` crate. Each crate with a bridge declares\n`#[cxx::bridge(namespace = \"workerd::rust::<crate>\")]` and has companion `ffi.c++`/`ffi.h` files.\n\n- **Always** ensure that **shared structs** passed across the boundary are trivially safe to copy\n  between languages.\n- **Always** verify that shared types do not contain owning pointers, non-trivial destructors, or\n  types whose layout differs between Rust and C++.\n- **Always** verify that opaque types (`type Foo;` in CXX bridge) are passed by reference or\n  `Box`/`UniquePtr`.\n- **Always** verify that lifetimes are respected — an opaque C++ type behind `&T` must outlive the\n  Rust reference.\n- **Always** verify that bridges must use the `workerd::rust::<crate_name>` naming convention. The\n  only exception is `python-parser` which uses `edgeworker::rust::python_parser`.\n- **Always** verify companion files. Every CXX bridge should have hand-written `ffi.c++` and `ffi.h`\n  implementing the C++ side. Generated headers (`<file>.rs.h`) are produced by the build system.\n\n### Unsafe Code\n\nUnsafe code is concentrated at FFI boundaries. Review every `unsafe` block and `unsafe fn`:\n\n- **Always** verify that every `unsafe` block has a `// SAFETY:` comment explaining which invariants\n  the caller is responsible for and why they hold at this call site.\n- **Always** verify that functions receiving raw pointers from C++ are declared `unsafe fn`**.\n- **Always** verify that `unsafe impl Send` / `unsafe impl Sync` are justified with a comment\n  explaining why the type is safe to share across threads. `jsg::Ref<T>` is explicitly not `Send`\n  (uses `Rc` + `UnsafeCell` internally) — flag any attempt to send it across threads.\n\n#### Common unsafe patterns in this codebase:\n\n- **V8 handle operations**: `Local::from_ffi()`/`into_ffi()`, isolate pointer dereference. These are\n  safe only when the isolate is locked and the handle scope is active.\n- **Resource wrap/unwrap**: `Ref::into_raw()` leaks a ref-counted pointer as `usize`;\n  `Ref::from_raw()` reconstructs it. These must be balanced — every `into_raw` needs exactly one\n  `from_raw` to avoid leaks or double-frees.\n- **Trampoline closures**: A closure is cast to `usize` via raw pointer, passed through CXX, and\n  reconstructed on the other side. The closure must be consumed exactly once.\n\n### Error Handling\n\n- **Always** ensure that library crates (e.g., `dns`, `net`, `transpiler`) use `thiserror` for\n  domain-specific error types. These should implement `Display` and have descriptive error messages.\n- **Always** ensure that JSG-facing crates (e.g., `api`, `jsg`) use `jsg::Error` with an\n  `ExceptionType` variant (e.g., `TypeError`, `RangeError`). Domain errors should implement\n  `From<DomainError> for jsg::Error` for ergonomic `?` usage.\n- **Never** use `panic!` for expected errors. Panics across FFI are undefined behavior. Use\n  `Result` and propagate errors through the CXX bridge. `unwrap()` / `expect()` are acceptable in\n  tests (clippy is configured with `allow-unwrap-in-tests = true`).\n- **Error type changes are generally not breaking** — same policy as the C++ side. Changing the\n  JS exception type (e.g., from generic error to `TypeError`) is not normally a breaking change\n  unless it removes properties that user code could depend on (e.g., `DOMException` has `code`\n  that `TypeError` does not).\n\n### JSG Resource Conventions\n\nRust types exposed to JavaScript via the JSG bindings follow these patterns:\n\n- **`_state: jsg::ResourceState`** is a required field on all resource types. It holds the opaque\n  pointers used by the C++ JSG layer to wrap/unwrap the Rust object.\n- **`#[jsg_resource]`** on the impl block registers the type as a JS-visible resource.\n- **`#[jsg_method]`** auto-converts Rust `snake_case` method names to JavaScript `camelCase`.\n  Methods with a receiver (`&self`/`&mut self`) are registered as instance methods on the prototype;\n  methods without a receiver are registered as static methods on the constructor.\n  Verify the converted name is correct and matches the intended API surface.\n- **`#[jsg_static_constant]`** on a `const` item inside a `#[jsg_resource]` impl block exposes it\n  as a read-only numeric constant on both the constructor and prototype (Rust equivalent of\n  `JSG_STATIC_CONSTANT`). The name is used as-is (no camelCase conversion).\n- **`#[jsg_struct]`** is for value types (passed by value across the JS boundary).\n- **`#[jsg_oneof]`** is for union/variant types (mapped from JS values by trying each variant).\n- **Type mappings**: `jsg::Number` wraps JS numbers (distinct from `f64`). `Vec<u8>` maps to\n  `Uint8Array`, not a regular JS `Array`. `Option<T>` maps to `T | undefined` (rejects `null`).\n  `Nullable<T>` maps to `T | null | undefined`. `String`/`&str` map to JS strings.\n- **GC tracing**: `Ref<T>`, `Option<Ref<T>>`, and `Nullable<Ref<T>>` fields on `#[jsg_resource]`\n  structs are automatically traced during GC. `WeakRef<T>` fields are not traced (no-op). Verify\n  that any resource holding a `Ref<T>` or `Nullable<Ref<T>>` to another resource is properly\n  traced — missing traces cause use-after-free when the child is collected prematurely.\n\n### Linting & Style\n\n- **Clippy** runs with **pedantic + nursery** lint groups. Run `just clippy <crate>` on all\n  Rust changes.\n- **`#[expect(clippy::...)]` over `#[allow(clippy::...)]`** — `expect` is stricter: it warns if\n  the suppressed lint no longer fires, preventing stale suppressions from accumulating.\n- **Import formatting**: one `use` per import line, grouped as std / external / crate (enforced\n  by `rustfmt.toml`). Run `just format` to auto-fix.\n\n### Testing\n\n- **JSG test harness** (`jsg_test::Harness`): creates a V8 isolate, runs Rust code in a real V8\n  context via `run_in_context(|lock, ctx| { ... })`. Used for testing JSG resources.\n- **C++ KJ tests for Rust crates**: some crates have companion `.c++` test files using `kj_test()`.\n  These test the C++ side of the FFI bridge.\n- **Inline `#[cfg(test)]` modules**: standard Rust unit tests for pure-Rust logic.\n- All test targets run with `RUST_BACKTRACE=1` and `RUST_TEST_THREADS=1` (serial execution).\n\n### Review Checklist\n\nWhen reviewing Rust code in workerd, check for each of these items.\n\n- **Never** use **magic numbers** (Numeric literals) without explanation or named constants.\n- **Always** use `#[must_use]`. Functions returning `Result`, `Option`, or expensive-to-compute values\n   that callers should not silently discard.\n- **Never** use `bool` function parameters. Prefer an enum or options struct for clarity at call\n  sites. E.g., `fn connect(secure: bool)` should be `fn connect(mode: SecureMode)` or take an\n  options struct.\n- **Always** use `const fn` / `const` for compile-time evaluable functions or constants where\n  appropriate.\n- **Always** avoid reinvented utility. Avoid custom code duplicating functionality already in the\n  `jsg` or `kj` Rust crates, or in well-known ecosystem crates already vendored by the project.\n- **Always** avoid using `static mut`. Use `OnceLock`, `LazyLock`, or other safe synchronization\n  primitives where global state is genuinely needed.\n- **Always** flag missing `// SAFETY:` comments on `unsafe`.\n- **Always** flag unjustified `unsafe impl Send/Sync` uses.\n- **Always** flag unnecessary `.clone()`/copies where borrowing or moving would suffice.\n- **Always** flag unnecessary use of `String` where `&str` works, or `Vec<T>` where a slice would do.\n- **Always** flag locations where **`#[allow(...)]` is used where `#[expect(...)]` would work**.\n  Prefer `expect` to prevent stale suppressions.\n- **Always** ensure copyright header on new files, Every new `.rs` file must begin with the project\n  copyright/license header using the current year. Expected format:\n    ```\n    // Copyright (c) <current-year> Cloudflare, Inc.\n    // Licensed under the Apache 2.0 license found in the LICENSE file or at:\n    //     https://opensource.org/licenses/Apache-2.0\n    ```\n    Flag any new file that uses a stale year (e.g., `2017-2022` in a file created in 2026) or omits\n    the header entirely.\n"
  },
  {
    "path": "docs/reference/ts-style.md",
    "content": "This document provides a checklist for reviewing JavaScript and TypeScript code in the workerd project. It covers style guidelines, common pitfalls, and critical patterns to watch for when ensuring code quality.\n\n- ESLint config: `../../tools/base.eslint.config.mjs`\n- TypeScript config: `../../tools/base.tsconfig.json`\n- Prettier config: `../../.prettierrc.json`\n\nMany conventions below are enforced by ESLint and the TypeScript compiler. The checklist focuses\non what automated tools miss and on verifying that enforced rules are not being bypassed with\n`eslint-disable` comments.\n\nFor `../../src/node/` specifics, see `../../src/node/AGENTS.md` and `../../src/node/README.md`.\nFor `../../src/cloudflare/` specifics, see `../../src/cloudflare/AGENTS.md` and `../../src/cloudflare/README.md`.\n\n### TypeScript Strictness\n\nThe project uses strict TypeScript with several settings stricter than typical:\n\n- **`exactOptionalPropertyTypes: true`** — optional properties cannot be explicitly set to\n  `undefined` unless the type includes `| undefined`. E.g., `{ x?: string }` does not accept\n  `{ x: undefined }`.\n- **`noUncheckedIndexedAccess: true`** — indexed access on arrays and records returns `T | undefined`.\n  Code must narrow or assert before using the value.\n- **`verbatimModuleSyntax: true`** — type-only imports **must** use `import type { X }` or inline\n  `import { type X, Y }`. A bare `import { X }` where `X` is only used as a type is a compile error.\n- **`erasableSyntaxOnly: true`** — no TypeScript enums, no `namespace` declarations, no parameter\n  properties. Only syntax that erases cleanly to JavaScript is permitted.\n- **`noImplicitReturns: true`**, **`noUnusedLocals: true`**, **`noUnusedParameters: true`** —\n  unused variables are prefixed with `_` to signal intent.\n\n### Import Conventions\n\nImport specifiers follow a tiered module system:\n\n| Specifier prefix        | Visibility | Purpose                                                 |\n| ----------------------- | ---------- | ------------------------------------------------------- |\n| `node:*`                | Public     | Standard Node.js module API                             |\n| `cloudflare:*`          | Public     | Cloudflare product APIs                                 |\n| `node-internal:*`       | Private    | Internal Node.js implementation (not user-facing)       |\n| `cloudflare-internal:*` | Private    | Cloudflare runtime-provided internals (not user-facing) |\n\n- Public modules (`node:*`, `cloudflare:*`) must not import from internal specifiers of the\n  other tier. `src/node/` code must not import `cloudflare-internal:*` and vice versa.\n- Internal specifiers (`*-internal:*`) are not available to user code. Do not expose them in\n  public module exports.\n- `.d.ts` files without a matching `.ts` file declare the shape of C++ JSG modules imported\n  via internal specifiers.\n\n### Export Patterns\n\n**Public modules** (`src/node/*.ts`, `src/cloudflare/*.ts`) may use a dual-export pattern:\n\n```typescript\nexport { Foo, Bar, type Baz } from 'internal-module';\nexport default { Foo, Bar };\n```\n\nBoth named exports and a default export object are required for compatibility with different\nimport styles.\n\n**Internal modules** export directly:\n\n```typescript\nexport class Foo { ... }\nexport function bar(): void { ... }\n```\n\n**Never** re-export namespace imports as default. The ESLint rule\n`workerd/no-export-default-of-import-star` (in `src/node/`) enforces:\n\n```typescript\n// BAD\nimport * as X from 'module';\nexport default X;\n\n// GOOD — explicit named re-exports\nexport { foo, bar } from 'module';\nexport default { foo, bar };\n```\n\n### Private Fields\n\n**Always** use `#private` syntax for private members, not the `private` keyword:\n\n```typescript\n// GOOD\nclass Foo {\n  #bar: string;\n  #doSomething(): void { ... }\n}\n\n// BAD — flagged by ESLint no-restricted-syntax\nclass Foo {\n  private bar: string;\n  private doSomething(): void { ... }\n}\n```\n\nExceptions exist in older code with `eslint-disable` comments (e.g., when `#` syntax conflicts\nwith `implements` interface constraints). New code should not add new exceptions without\njustification.\n\n### Accessibility Modifiers\n\n**Never** use the `public` keyword. Members without an accessibility modifier are public by default.\n\n```typescript\n// GOOD\nclass Foo {\n  bar: string;\n  doSomething(): void { ... }\n}\n\n// BAD — flagged by ESLint\nclass Foo {\n  public bar: string;\n  public doSomething(): void { ... }\n}\n```\n\n### Error Handling\n\n#### In `../../src/node/`:\n\n**Always** use Node.js-style error classes from `node-internal:internal_errors`:\n\n```typescript\nimport { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors';\nthrow new ERR_METHOD_NOT_IMPLEMENTED('setEngine');\n```\n\n**Never** throw bare `Error` objects when a matching `ERR_*` class exists.\n\n#### In `../../src/cloudflare/`:\n\nDefine product-specific error classes extending `Error`:\n\n```typescript\nexport class InferenceUpstreamError extends Error {\n  constructor(message: string, name = 'InferenceUpstreamError') {\n    super(message);\n    this.name = name;\n  }\n}\n```\n\n### Feature Gating\n\nRuntime compat flags are available via the global `Cloudflare.compatibilityFlags`:\n\n```typescript\nif (!Cloudflare.compatibilityFlags.some_flag) {\n  // legacy behavior\n}\n```\n\n**Always** gate any behavioral change that could break existing workers behind a compat flag.\nSee `src/workerd/io/compatibility-date.capnp` for the flag schema.\n\n### Test Patterns\n\nTests are plain `.js` files (not `.ts`) using `node:assert`. No test frameworks.\n\n**Named export pattern** (preferred for multiple tests per file):\n\n```javascript\nexport const someFeatureTest = {\n  test() {\n    assert.strictEqual(actual, expected);\n  },\n};\n\nexport const anotherFeatureTest = {\n  test() {\n    assert.strictEqual(actual, expected);\n  },\n};\n```\n\n**Default export pattern** (for handler-based tests):\n\n```javascript\nexport default {\n  async fetch(request, env, ctx) { ... },\n  async test(ctrl, env, ctx) { ... },\n};\n```\n\nEach test `.js` file is paired with a `.wd-test` Cap'n Proto config. See the `wd-test-format`\nskill for config details.\n\n**Mock services** (`*-mock.js`) export a default handler for simulating external services:\n\n```javascript\nexport default {\n  async fetch(request, env, ctx) { ... },\n};\n```\n\n### Formatting\n\n- **Single quotes** (not double)\n- **2-space indentation**\n- **Trailing commas** in ES5-valid positions\n- **80-char line width**\n- Run `just format` before submitting\n\n### Review Checklist\n\nWhen reviewing JS/TS code in workerd, check for each of these items.\n\n- **Always** check for copyright header on new files: Every new `.ts` and `.js` file must begin\n  with the project copyright/license header using the current year. Expected format:\n  ```\n  // Copyright (c) <current-year> Cloudflare, Inc.\n  // Licensed under the Apache 2.0 license found in the LICENSE file or at:\n  //     https://opensource.org/licenses/Apache-2.0\n  ```\n  Files adapted from Node.js, Deno, or any other third-party open source project should include\n  attribution after the Cloudflare header.\n- **Always** use `import type` for type-only imports. With `verbatimModuleSyntax`, type-only imports\n  must use `import type { X }` or `import { type X, Y }`. The compiler catches this, but verify\n  it in review since CI may not always run on every file.\n- **Always** check for missing explicit return type. All functions in `.ts` files must have explicit\n  return types (enforced by ESLint). Check that new functions comply and that `eslint-disable` is\n  not being used to bypass this.\n- **Always** use `#` syntax for private members instead of the `private` keyword.\n- **Never** use the `public` keyword on class members.\n- **Never** use the non-null assertion (`!`). Prefer proper narrowing or `?? defaultValue`.\n   Flag any `eslint-disable` for `@typescript-eslint/no-non-null-assertion`.\n- **Never** use TypeScript enum or namespace**. We only use `erasableSyntaxOnly`. Use `as const`\n  objects for enum-like patterns.\n- **Always** use compat flag gating. Behavioral changes that could break existing workers must be\n   gated behind a compat flag. Check that the flag exists in `compatibility-date.capnp` and is\n   checked at runtime.\n- **Always** use dual export pattern in public modules. Top-level files in `src/node/` and `src/cloudflare/`\n    must provide both named exports and a default export object.\n- **Never** use `eslint-disable` without justification. Every `eslint-disable` comment should name a\n    specific rule and explain why the override is necessary. Blanket `eslint-disable` (no rule\n    name) is never acceptable.\n- **Never** use `@ts-expect-error` without explanation. Must include a comment explaining why the type\n    system cannot express the correct type. Prefer fixing the types over suppressing the error.\n- **Never** use `require()`. Banned. Use ESM `import` syntax and dynamic `import()` if necessary.\n- **Never** re-export module namespaces. Use explicit named re-exports.\n- **Never** allow unused `eslint-disable`. If the suppressed lint no longer fires, the disable comment\n    should be removed. Review for stale suppressions.\n"
  },
  {
    "path": "docs/streams.md",
    "content": "# Workers Streams Implementation Guide\n\nThis document walks through the streams implementation in workerd.\n\nFor terse reference material (classification tables, state machines, safety patterns), see\n[src/workerd/api/streams/README.md](../src/workerd/api/streams/README.md).\n\nFor the file map and coding invariants, see\n[src/workerd/api/streams/AGENTS.md](../src/workerd/api/streams/AGENTS.md).\n\n## Overview\n\nThe Workers runtime has **two separate implementations** of the Streams API, both\nexposed through the same `ReadableStream`, `WritableStream`, and `TransformStream`\nJavaScript APIs.\n\n**Internal streams** were the original implementation, built to serve workerd's own\nneeds: reading request bodies, writing response bodies, etc. They are a thin wrapper\naround kj's asynchronous I/O primitives (`kj::AsyncInputStream`, `kj::AsyncOutputStream`)\nand are only superficially related to the WHATWG Streams specification. Internal streams\nare exclusively byte-oriented (they only handle `TypedArray` and `ArrayBuffer` data).\n\n**Standard streams** are a separate, spec-compliant implementation of the WHATWG Streams\nstandard. They are driven entirely by JavaScript promises and user-provided callback\nfunctions. Standard streams can be either byte-oriented or value-oriented (handling any\nJavaScript value).\n\nBoth implementations coexist because removing the internal implementation would break\nbackwards compatibility. A compatibility flag system controls which behavior\n`new TransformStream()` uses, and various APIs like `request.body` always return\ninternal streams while `new ReadableStream(...)` with user callbacks always creates\nstandard streams.\n\n## Terminology\n\n### Controllers\n\nEvery `ReadableStream` and `WritableStream` has an underlying **controller** that\nprovides the actual implementation. Internal and Standard streams each have their\nown controller classes:\n\n| Stream type      | Internal controller                | Standard controller          | File                        |\n| ---------------- | ---------------------------------- | ---------------------------- | --------------------------- |\n| `ReadableStream` | `ReadableStreamInternalController` | `ReadableStreamJsController` | `internal.h` / `standard.h` |\n| `WritableStream` | `WritableStreamInternalController` | `WritableStreamJsController` | `internal.h` / `standard.h` |\n\n### Byte-oriented vs. Value-oriented\n\nA **byte-oriented** stream only handles `TypedArray` and `ArrayBuffer` data. A\n**value-oriented** stream can handle any JavaScript value, including `undefined` and\n`null`. A `TypedArray` or `ArrayBuffer` can pass through a value-oriented stream, but\nit is treated as an opaque JavaScript value -- the stream does not interpret it as byte\ndata. Internal streams are always byte-oriented. Standard streams can be either.\n\nNote that the `ReadableStream` and `WritableStream` APIs provide no way to inspect\nwhich kind of data a stream handles.\n\n### BYOB vs. Default Readers\n\nA **Reader** consumes data from a `ReadableStream`. There are two kinds:\n\n- A **BYOB Reader** (Bring Your Own Buffer) works only with byte-oriented streams.\n  The caller provides a destination `TypedArray` that the stream fills with data.\n- A **Default Reader** works with both byte-oriented and value-oriented streams.\n  The stream provides data in whatever form it produces.\n\n## How ReadableStream Works\n\nConsider the basic usage pattern:\n\n```js\nconst readableStream = getReadableSomehow();\nconst reader = readableStream.getReader();\n\nconst chunk = await reader.read();\nconsole.log(chunk.value); // the data that was read\nconsole.log(chunk.done); // true if the stream has completed\n```\n\nUser code calls `read()` repeatedly until `chunk.done` is `true`. What happens\nunder the covers depends on whether this is an Internal or Standard stream.\n\n### Standard ReadableStream\n\nA Standard `ReadableStream` maintains two internal queues: a **queue of available data**\nand a **queue of pending reads**. The controller uses four callback functions\n(what the spec calls \"algorithms\"):\n\n- **start** -- invoked immediately when the `ReadableStream` is created\n- **pull** -- invoked to request more data from the source\n- **cancel** -- invoked when the stream is explicitly canceled\n- **size** -- determines the size of a chunk for backpressure calculations\n\nWhen the stream is created, the start algorithm runs immediately. Once it completes,\nthe stream checks the **highwater mark** -- the maximum amount of data (calculated\nusing the size algorithm) that should be held in the data queue. If the current queue\nsize is below the highwater mark, the pull algorithm is called.\n\nThe pull algorithm pushes data into the ReadableStream (and yes, the irony of a \"pull\"\nthat pushes is not lost):\n\n- If there are no pending reads, or the queue already has data, the new data goes\n  into the queue.\n- If there is a pending read, it is immediately fulfilled with the data. Any excess\n  beyond what the read consumes goes into the queue.\n\n```\n        +----------------+\n        | pull algorithm | <------------------------------------------+\n        +----------------+                                            |\n                |                                                     |\n                v                                                     |\n        +---------------+                                             |\n        | enqueue(data) |                                             |\n        +---------------+                                             |\n                |                                                     |\n                v                                                     |\n      +--------------------+       +-------------------+              |\n      | has a pending read | ----> | has data in queue |              |\n      +--------------------+  yes  +-------------------+              |\n                |                     |      no|                      |\n              no|                     |        v                      |\n                v                     |  +----------------------+     |\n       +-------------------+    yes   |  | fulfill pending read |     |\n       | add data to queue | <--------+  +----------------------+     |\n       +-------------------+                    |                     |\n                       |                        |                     |\n                       |                        v                     |\n                       |    +-----------------------------+     no    |\n                       +--> | is queue at highwater mark? | -----------\n                            +-----------------------------+\n                                      yes |\n                                          |\n                                          v\n                                        (done)\n```\n\nWhen `reader.read()` is called:\n\n- If the queue has data, the read is fulfilled immediately. If this drops the queue\n  size below the highwater mark, the pull algorithm is invoked.\n- If the queue is empty, the read is added to the pending read queue. If the queue\n  size is below the highwater mark, the pull algorithm is invoked.\n\n```\n    +---------------+\n    | reader.read() |\n    +---------------+\n           |\n           v\n   +-----------------+       +------------------+       +---------------------+\n   | queue has data? | ----> | add pending read | ----> | call pull algorithm |\n   +-----------------+  no   +------------------+       +---------------------+\n           |\n       yes |\n           v\n    +--------------+\n    | fulfill read |\n    +--------------+\n           |\n           v\n         (done)\n```\n\n**Important caveats:**\n\n- The source can push data into the queue even when there are no active readers and\n  even after backpressure has been signaled. This data accumulates in memory.\n- It is possible to push more data than a reader can consume in one read. Excess\n  stays in the queue.\n- Once user code obtains a reference to the controller, it can enqueue data at any\n  time, independent of the pull algorithm.\n\n### Internal ReadableStream\n\nInternal streams work very differently. The key difference: an Internal stream only\nallows **a single pending read at a time**, has no internal data queue, and has no\npull algorithm.\n\n```js\nconst readable = new ReadableStream(); // Standard stream\nconst reader = readable.getReader();\nreader.read();\nreader.read(); // Works fine -- queued as a pending read.\n\nconst readable = request.body; // Internal stream\nconst reader = readable.getReader();\nreader.read();\nreader.read(); // Fails with an error!\n```\n\nAn Internal stream is backed by a `ReadableStreamSource`, which wraps a\n`kj::AsyncInputStream`. Calling `read()` translates directly to a `tryRead()` on the\nsource, returning a `kj::Promise` for data. Only one read can be in flight at a time.\n\n```\n    +---------------+\n    | reader.read() |\n    +---------------+\n            |\n            v\n  +-------------------+      +-----------------------------------+\n  | has pending read? | ---> | tryRead() on ReadableStreamSource |\n  +-------------------+  no  +-----------------------------------+\n        yes |\n            v\n        +-------+\n        | error |\n        +-------+\n```\n\nData only flows through an Internal stream when there is an active reader, and each\nread never exceeds the maximum byte count specified in the call.\n\n### Tee\n\nThe `ReadableStream.tee()` method splits data flow into two separate `ReadableStream`\ninstances (branches).\n\nIn the WHATWG spec, `tee()` creates two branches that share a single reader on the\noriginal stream (the \"trunk\"). When one branch pulls, the data fulfills that branch's\nread and a **copy** is pushed into the other branch's queue. This means one branch\nreading faster than the other causes unbounded memory growth in the slower branch.\n\nOur implementation modifies this behavior: instead of copying data, branches hold\n**refcounted references** (`kj::Rc<Entry>`) to shared data. Backpressure signaling\nto the trunk is based on the branch with the most unconsumed data.\n\n```\n   +----------------+\n   | pull algorithm |\n   +----------------+\n           |\n           v          ..........................................................\n   +---------------+  .   +---------------------+      +-------------------+\n   | enqueue(data) | ---> | push data to branch | ---> | has pending read? |\n   +---------------+  .   +---------------------+      +-------------------+\n               |      .                                no |    yes |\n               |      .       +-------------------+       |  +--------------+\n               |      .       | add data to queue | <-----+  | fulfill read |\n               |      .       +-------------------+          +--------------+\n               |      ............................................................\n               |      .   +---------------------+      +-------------------+\n               +--------> | push data to branch | ---> | has pending read? |\n                      .   +---------------------+      +-------------------+\n                      .                                no |    yes |\n                      .       +-------------------+       |  +--------------+\n                      .       | add data to queue | <-----+  | fulfill read |\n                      .       +-------------------+          +--------------+\n                      ............................................................\n```\n\nThis doesn't completely prevent one branch from reading much slower than the other,\nbut it avoids the memory pileup that would otherwise occur -- as long as the\nunderlying source pays attention to backpressure signals.\n\n## How WritableStream Works\n\n### Standard WritableStream\n\nA Standard `WritableStream` uses five algorithms:\n\n- **start** -- prepares the stream to receive data\n- **write** -- called for each chunk written\n- **abort** -- called on abrupt termination\n- **close** -- called after the last chunk\n- **size** -- calculates chunk size for the highwater mark\n\n```js\nconst writable = getWritableSomehow();\nconst writer = writable.getWriter();\n\nawait writer.write('chunk of data');\nawait writer.write('another chunk of data');\n```\n\nThe write algorithm is asynchronous: every `write(data)` invokes it, and the returned\npromise resolves when the write completes. This promise is the primary backpressure\nmechanism. Backpressure is signaled in two ways:\n\n- `writer.desiredSize` -- the amount of data that may be written before the queue is full\n- `writer.ready` -- a promise that resolves when backpressure clears (replaced with a\n  new promise each time backpressure is signaled)\n\nNote that the highwater mark is advisory. A write algorithm should always expect to be\ncalled regardless of the current highwater mark value.\n\n### Internal WritableStream\n\nAn Internal `WritableStream` is backed by a `WritableStreamSink`, a thin wrapper around\n`kj::AsyncOutputStream`. Every `write()` passes directly to the sink's `write()`. The\nbehavior from there depends on the specific sink implementation.\n\n## TransformStream\n\nA `TransformStream` connects a `ReadableStream` and a `WritableStream` -- data written\nto the writable side can be read from the readable side, potentially transformed.\n\n### IdentityTransformStream (Internal)\n\nThe original `TransformStream` was an identity transform: data passes through unmodified\nfrom the writable side to the readable side. It uses a single class implementing both\n`ReadableStreamSource` and `WritableStreamSink`.\n\nThis implementation is not spec-compliant. A compatibility flag changes `new TransformStream()`\nto create a Standard TransformStream instead. The old behavior is available as\n`new IdentityTransformStream()`.\n\n```js\nconst { readable, writable } = new IdentityTransformStream();\nconst enc = new TextEncoder();\n\nconst writer = writable.getWriter();\nconst reader = readable.getReader();\n\n// The write promise will not resolve until read() is called!\nawait Promise.all([writer.write(enc.encode('hello')), reader.read()]);\n```\n\nThe `IdentityTransformStream` is a simple state machine allowing only one in-flight\nread or write at a time. A `write()` promise won't resolve until a corresponding\n`read()` occurs, and vice versa. It only supports byte data (`ArrayBuffer` / `TypedArray`).\n\n### Standard TransformStream\n\nA Standard `TransformStream` uses three algorithms:\n\n- **start** -- initializes the transformation\n- **transform** -- receives a chunk, modifies it, enqueues the result\n- **flush** -- completes the transformation\n\n```js\nconst { writable, readable } = new TransformStream({\n  transform(chunk, controller) {\n    controller.enqueue(`${chunk}!`.toUpperCase());\n  },\n});\n\nconst writer = writable.getWriter();\nconst reader = readable.getReader();\n\n// The write promise does not wait for a read.\nawait writer.write('hello');\nawait reader.read(); // { value: 'HELLO!', done: false }\n```\n\nBoth sides operate as full Standard streams with their own queues and backpressure.\nUnlike the `IdentityTransformStream`, writes aren't blocked on reads (unless\nbackpressure signals the queue is full). Any JavaScript value can flow through.\n\n## Piping\n\nPiping sets up the flow of data from a `ReadableStream` to a `WritableStream`. The\ndestination writable determines how the pipe is implemented via its controller's\n`tryPipeFrom()` method. There are four pipe loop variants depending on the stream types:\n\n```\n         +---------------------------+\n         | readable.pipeTo(writable) |\n         +---------------------------+\n                       |\n                       v\n   +------------------------------------------+\n   | writableController.tryPipeFrom(readable) |\n   +------------------------------------------+\n                       |\n                       v\n           +-----------------------+       +-----------------------+\n           | is internal writable? | ----> | is internal readable? |\n           +-----------------------+  yes  +-----------------------+\n                       |                      no |         yes |\n                    no |                         |             |\n                       v                    +----+     +---------------+\n           +-----------------------+        |          | kj-to-kj pipe |\n           | is internal readable? |        |          |      loop     |\n           +-----------------------+        |          +---------------+\n            no  |           yes |           |\n                |               |           |\n                v               |           |\n         +---------------+      |           v\n         | JS-to-JS pipe |      |   +---------------+\n         |      loop     |      |   | JS-to-kj pipe |\n         +---------------+      |   |     loop      |\n                                v   +---------------+\n                        +---------------+\n                        | kj-to-JS pipe |\n                        |     loop      |\n                        +---------------+\n```\n\n**kj-to-kj**: The most optimized case. kj pipes data directly from `AsyncInputStream` to\n`AsyncOutputStream` outside the JavaScript isolate lock. No JavaScript runs at all.\n\n**kj-to-JS / JS-to-kj**: These bridge kj and JavaScript promises. The entire flow must\noccur under the isolate lock. Data is restricted to bytes because Standard streams\nmight be value-oriented and the API provides no way to inspect this.\n\n**JS-to-JS**: Pure JavaScript promise chaining. Conceptually equivalent to:\n\n```js\nasync function pipe(reader, writer) {\n  for await (const chunk of reader) {\n    await writer.write(chunk);\n  }\n}\n```\n\n### new Response(standardReadable)\n\nPassing a Standard `ReadableStream` to APIs like `new Response()` is complicated because\nthese APIs were built for Internal streams and use kj async I/O internally.\n\nTo bridge this, Standard `ReadableStream`s can be consumed via the `ReadableStreamSource`\nAPI (the same API Internal streams use). When `pumpTo()` is called on the adapter, it\nacquires the isolate lock and runs a promise loop: read from the JS stream, write to the\nkj output, repeat until the data is exhausted or an error occurs.\n\n## The Complexity Budget\n\nThe streams implementation balances several sources of complexity:\n\n- Two implementations of the same spec (Internal and Standard)\n- Two data orientations (byte and value)\n- Two memory heaps (JavaScript and kj)\n- Two async models (JavaScript promises and kj promises)\n- Strict backwards compatibility via feature flags\n\nInternal stream data flow happens **outside** the isolate lock via kj's event loop.\nStandard stream data flow happens **inside** the isolate lock via JavaScript promises.\nWhen these two worlds interact (piping between types, passing standard streams to\ninternal APIs), bridging code must carefully manage both async models.\n\n## Further Reading\n\n- [AGENTS.md](../src/workerd/api/streams/AGENTS.md) -- file map, architecture summary, coding invariants\n- [README.md](../src/workerd/api/streams/README.md) -- terse reference: classification, state machines, safety patterns\n- [WHATWG Streams spec](https://streams.spec.whatwg.org/)\n"
  },
  {
    "path": "docs/v8-updates.md",
    "content": "# V8 Updates\n\nTo update the version of V8 used by workerd, the steps are:\n\n1. Check <https://chromiumdash.appspot.com/> and identify the latest version of V8 used by the beta versions of Chrome beta.\n\n2. Install `depot_tools` if it is not already present on your machine.\n\n   <https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up>\n\n3. Fetch a local copy of V8:\n\n   ```sh\n   mkdir v8\n   cd v8\n   fetch v8\n   ```\n\n   You should probably put this outside of your workerd repo to avoid confusing Bazel.\n\n4. Sync the local copy of V8 to the version used by workerd.\n\n   First, find workerd's current version of V8 in `build/deps/v8.MODULE.bazel`. We will call this `<old_version>`.\n\n   Then sync your fetched version v8 based on the tag.\n\n   ```sh\n   cd <path_to_v8>/v8\n   git checkout <old_version>\n   gclient sync\n   ```\n\n5. Create a V8 branch for workerd's V8 patches in your local copy of V8.\n\n   ```sh\n   git checkout -b workerd-patches\n   git am <path_to_workerd>/patches/v8/*\n   ```\n\n6. Rebase the workerd V8 changes onto the new version of V8. For example, assuming\n   we are updating to `<new_version>`, the command would be:\n\n   ```sh\n   git rebase --onto <new_version> <old_version>\n   ```\n\n   There is usually some minor patch editing required during a rebase.\n\n   Ideally at this stage, you should be able to build and test the local V8 with the\n   patches applied. See the V8 [Testing](https://v8.dev/docs/test) page.\n\n7. Re-generate workerd's V8 patches.\n\n   ```sh\n   git format-patch --full-index -k --no-signature --no-stat --zero-commit <new_version>\n   ```\n\n8. Remove the existing patches from `<path_to_workerd>/patches/v8` and copy over the latest generated patches\nfrom the V8 directory.\n\n9. Update the `VERSION` for V8 in `build/deps/v8.MODULE.bazel`.\n\n    The list of patches should be refreshed if new patches are being added or existing\n    patches are being removed.\n\n    `INTEGRITY` needs to be updated to the new value. You can get the new value in\n    bazel's preferred format just by looking into the mismatch error while trying to compile\n    workerd using the newer V8 version or by running\n    `openssl dgst -sha256 -binary <tarball_filename> | openssl base64 -A`\n    where `<tarball_filename>` is the file available at\n    `https://github.com/v8/v8/archive/refs/tags/<new_version>.tar.gz`\n\n10. Update V8's dependencies in `v8.MODULE.bazel` and `deps.MODULE.bazel`.\n\n    You can find the commit versions for V8's dependencies under `<path_to_v8>/DEPS`.\n\n    These currently include `perfetto`, `com_googlesource_chromium_icu` and `simdutf`.\n    Note that V8 depends on `perfetto` and `simdutf` via chromium so you can't trivially\n    figure out what version of the github code V8 depends on. Instead, it should be safe\n    to just bump these dependencies to the latest version on github.\n\n11. Check workerd's tests pass with the updated V8.\n\n     ```sh\n     bazel test //...\n     ```\n\n12. Commit your workerd changes and push them for review.\n"
  },
  {
    "path": "docs/vscode.md",
    "content": "# Developing workerd with Visual Studio Code\n\nVisual Studio Code is a commonly used editor by workerd developers (other editors are great too!). These notes present some getting started tips. If you have tricks and tips that would improve the developer experience, please let us know!\n\n## Using the [dev container](https://containers.dev/)\n\nworkerd includes a [devcontainer](https://containers.dev/) setup that automates a majority of the developer environment setup by shifting the development to a container based setup. To make use of this, install the [devcontainer extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers). You can then either (a) use the **Dev Containers: Open Folder In Container** command and navigate to the checked out location or (b) open the project normally, in which case vscode should detect the presence of the devcontainer. Clicking **Reopen in Container** on the modal below will relaunch your workspaces in a container.\n\n![dev container modal](./assets/vscode-dev-container-modal.png)\n\n_Note_: It may take some time to initially bootstrap the dev container. To monitor its progress, click **show log** modal on the new window. Subsequent launches will be cached.\n\n![dev container bootstrap log](./assets/vscode-dev-container-progress.png)\n\n## Recommended extensions for developing workerd\n\nThe recommended extensions to install are:\n\n* [LLVM clangd extension](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd) for code completion and navigation.\n\n  This is described below in [Clangd code completion, navigation, language server](#clangd-code-completion-navigation-language-server).\n\n* [Microsoft C/C++ extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) for debugging, syntax highlighting, etc.\n\n  The Microsoft C/C++ extension has IntelliSense support that is not compatible with the clangd extension. We recommend disabling the Microsoft IntelliSense Engine for this project (\"Settings → C_Cpp.intelliSenseEngine → disabled\").\n\n* [Capnproto-syntax extension](https://marketplace.visualstudio.com/items?itemName=abronan.capnproto-syntax) for syntax highlighting if you are editing `.capnp` files.\n\n* [GitLens extension](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens) for super charged git functionality within Visual Studio Code.\n\n* [markdownlint](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) for creating well formed markdown documents.\n\nYou can install all of these extensions with the **Extensions: Configure Recommended Extensions (Workspace Folder)** command. You can find this through the Visual Studio Code Command Palette (`shift+ctrl+p` on Linux / Windows, `shift+cmd+p` on OS X) and typing \"Configure Recommended Extensions\". The recommendations that will be installed can be found in the [.vscode/extensions.json](../.vscode/extensions.json) file.\n\n## VSCode Tasks for workerd\n\nThe [.vscode/tasks.json](../.vscode/tasks.json) file provides a few useful tasks for use within VSCode:\n\n* Bazel build workerd (dbg)\n* Bazel build workerd (fastbuild)\n* Bazel build workerd (opt)\n* Bazel build all (dbg)\n* Bazel clean\n* Bazel clean --expunge\n* Bazel run all tests (dbg)\n* Bazel run all tests (fastbuild)\n* Bazel run all tests (opt)\n* Generate rust-project.json\n\nThe keyboard shortcut for **Tasks: Run Build Task** is `shift+ctrl+b` on Linux and Windows, `shift+cmd+b` on OS X.\n\nThe test tasks can be run with **Tasks: Run Test Task**, which does not have a default\nkeybinding in VSCode, but can be found with the command palette with `shift+ctrl+p` on Linux and Windows, and `shift+cmd+p` on OS X.\n\n## Running and debugging workerd in Visual Studio Code\n\nThere are workerd debugging targets within Visual Studio Code which are supported on Linux, OS X, and Windows.\n\nThe [.vscode/launch.json](../.vscode/launch.json) file has launch targets that can be debugged within VSCode.\n\nBefore you start debugging, ensure that you have saved a vscode workspace for workerd,\n\"File → Save Workspace As...\". For more information about workspaces, see <https://code.visualstudio.com/docs/editor/workspaces>.\n\nThe **Run and Debug** view in VSCode (accessible via `shift+ctrl+d` on Linux and Windows, `shift+cmd+d` on OS X) has a drop-down that allows you to choose which target to run and debug. After selecting a target, hitting `F5` will launch the\ntarget with the debugger attached.\n\nThe main targets of interest are:\n\n* workerd (dbg)\n* workerd with inspector enabled (dbg)\n* workerd test case (dbg)\n* workerd wd-test case (dbg)\n\nLaunching either \"workerd (dbg)\" or \"workerd with inspector enabled (dbg)\" will prompt for a workerd configuration for\nworkerd to serve, the default is [${workspaceFolder}/samples/helloworld/config.capnp](../samples/helloworld/config.capnp).\n\nLaunching \"workerd test case (dbg)\" will prompt for a test binary to debug, the default is `bazel-bin/src/workerd/jsg/jsg-test`.\n\nLaunching \"workerd wd-test case (dbg)\" will prompt for wd-test file to provide to workerd to debug, the default is `src/workerd/api/node/path-test.wd-test`.\n\n## Clangd code completion, navigation, language server\n\nWe use clangd for code completion and navigation within the Visual Code. We use the simple\n[compile_flags.txt](../compile_flags.txt) option to provide compiler arguments for clangd to analyze sources.\n\nIf `compile_flags.txt` is not working well on your system, try running:\n\n```sh\nbazel build --copt=\"-MD\" --cxxopt=\"-MD\" //src/workerd/server:workerd\n```\n\nto generate dependency files and:\n\n```sh\nfind bazel-out/ -name '*.d'\n```\n\nto locate the generated dependency files. These files will help you align the include paths in\n`compile_flags.txt` with the ones that the bazel build is using.\n\nThere is also a script [clangd-check.sh](../tools/unix/clangd-check.sh) that checks every `.h` and\n`.c++` file in the workerd source tree. Fixing errors there inevitably improves the experience in\nVisual Studio Code.\n\nIn the past we used [Hedron's Bazel Compile Commands Extractor](https://github.com/hedronvision/bazel-compile-commands-extractor)\nto generate a `compile_commands.json` file for clangd, but this was slow and unreliable for the `workerd` use case\n(see <https://github.com/cloudflare/workerd/issues/506>).\n\n## Miscellaneous tips\n\n* There is a handy guide to Visual Studio Code keyboard shortcuts at [aka.ms/vscodekeybindings](https://aka.ms/vscodekeybindings).\n* The Command Palette is a great way to find things (`shift+ctrl+p` on Linux / Windows, `shift+cmd+p` on OS X).\n* The Keyboard Shortcuts window is also a great resource (`ctrl+k ctrl+s` on Linux / Windows, `cmd+k cmd+s` on OS X).\n* Read [Visual Studio Code Tips and Tricks](https://code.visualstudio.com/docs/getstarted/tips-and-tricks).\n* Check out the [25 VS Code Productivity Tips and Speed Hacks](https://youtu.be/ifTF3ags0XI) video by Fireship.\n"
  },
  {
    "path": "doxyfile",
    "content": "# Doxyfile 1.9.1\n\n# This file describes the settings to be used by the documentation system\n# doxygen (www.doxygen.org) for a project.\n#\n# All text after a double hash (##) is considered a comment and is placed in\n# front of the TAG it is preceding.\n#\n# All text after a single hash (#) is considered a comment and will be ignored.\n# The format is:\n# TAG = value [value, ...]\n# For lists, items can also be appended using:\n# TAG += value [value, ...]\n# Values that contain spaces should be placed between quotes (\\\" \\\").\n\n#---------------------------------------------------------------------------\n# Project related configuration options\n#---------------------------------------------------------------------------\n\n# This tag specifies the encoding used for all characters in the configuration\n# file that follow. The default is UTF-8 which is also the encoding used for all\n# text before the first occurrence of this tag. Doxygen uses libiconv (or the\n# iconv built into libc) for the transcoding. See\n# https://www.gnu.org/software/libiconv/ for the list of possible encodings.\n# The default value is: UTF-8.\n\nDOXYFILE_ENCODING      = UTF-8\n\n# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by\n# double-quotes, unless you are using Doxywizard) that should identify the\n# project for which the documentation is generated. This name is used in the\n# title of most generated pages and in a few other places.\n# The default value is: My Project.\n\nPROJECT_NAME           = \"workerd\"\n\n# The PROJECT_NUMBER tag can be used to enter a project or revision number. This\n# could be handy for archiving the generated documentation or if some version\n# control system is used.\n\nPROJECT_NUMBER         =\n\n# Using the PROJECT_BRIEF tag one can provide an optional one line description\n# for a project that appears at the top of each page and should give viewer a\n# quick idea about the purpose of the project. Keep the description short.\n\nPROJECT_BRIEF          =\n\n# With the PROJECT_LOGO tag one can specify a logo or an icon that is included\n# in the documentation. The maximum height of the logo should not exceed 55\n# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy\n# the logo to the output directory.\n\nPROJECT_LOGO           =\n\n# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path\n# into which the generated documentation will be written. If a relative path is\n# entered, it will be relative to the location where doxygen was started. If\n# left blank the current directory will be used.\n\nOUTPUT_DIRECTORY       = docs/api\n\n# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-\n# directories (in 2 levels) under the output directory of each output format and\n# will distribute the generated files over these directories. Enabling this\n# option can be useful when feeding doxygen a huge amount of source files, where\n# putting all generated files in the same directory would otherwise causes\n# performance problems for the file system.\n# The default value is: NO.\n\nCREATE_SUBDIRS         = YES\n\n# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII\n# characters to appear in the names of generated files. If set to NO, non-ASCII\n# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode\n# U+3044.\n# The default value is: NO.\n\nALLOW_UNICODE_NAMES    = YES\n\n# The OUTPUT_LANGUAGE tag is used to specify the language in which all\n# documentation generated by doxygen is written. Doxygen will use this\n# information to generate all constant output in the proper language.\n# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,\n# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),\n# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,\n# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),\n# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,\n# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,\n# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,\n# Ukrainian and Vietnamese.\n# The default value is: English.\n\nOUTPUT_LANGUAGE        = English\n\n# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all\n# documentation generated by doxygen is written. Doxygen will use this\n# information to generate all generated output in the proper direction.\n# Possible values are: None, LTR, RTL and Context.\n# The default value is: None.\n\nOUTPUT_TEXT_DIRECTION  = None\n\n# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member\n# descriptions after the members that are listed in the file and class\n# documentation (similar to Javadoc). Set to NO to disable this.\n# The default value is: YES.\n\nBRIEF_MEMBER_DESC      = YES\n\n# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief\n# description of a member or function before the detailed description\n#\n# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the\n# brief descriptions will be completely suppressed.\n# The default value is: YES.\n\nREPEAT_BRIEF           = YES\n\n# This tag implements a quasi-intelligent brief description abbreviator that is\n# used to form the text in various listings. Each string in this list, if found\n# as the leading text of the brief description, will be stripped from the text\n# and the result, after processing the whole list, is used as the annotated\n# text. Otherwise, the brief description is used as-is. If left blank, the\n# following values are used ($name is automatically replaced with the name of\n# the entity):The $name class, The $name widget, The $name file, is, provides,\n# specifies, contains, represents, a, an and the.\n\nABBREVIATE_BRIEF       = \"The $name class\" \\\n                         \"The $name widget\" \\\n                         \"The $name file\" \\\n                         is \\\n                         provides \\\n                         specifies \\\n                         contains \\\n                         represents \\\n                         a \\\n                         an \\\n                         the\n\n# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then\n# doxygen will generate a detailed section even if there is only a brief\n# description.\n# The default value is: NO.\n\nALWAYS_DETAILED_SEC    = YES\n\n# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all\n# inherited members of a class in the documentation of that class as if those\n# members were ordinary class members. Constructors, destructors and assignment\n# operators of the base classes will not be shown.\n# The default value is: NO.\n\nINLINE_INHERITED_MEMB  = NO\n\n# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path\n# before files name in the file list and in the header files. If set to NO the\n# shortest path that makes the file name unique will be used\n# The default value is: YES.\n\nFULL_PATH_NAMES        = YES\n\n# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.\n# Stripping is only done if one of the specified strings matches the left-hand\n# part of the path. The tag can be used to show relative paths in the file list.\n# If left blank the directory from which doxygen is run is used as the path to\n# strip.\n#\n# Note that you can specify absolute paths here, but also relative paths, which\n# will be relative from the directory where doxygen is started.\n# This tag requires that the tag FULL_PATH_NAMES is set to YES.\n\nSTRIP_FROM_PATH        =\n\n# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the\n# path mentioned in the documentation of a class, which tells the reader which\n# header file to include in order to use a class. If left blank only the name of\n# the header file containing the class definition is used. Otherwise one should\n# specify the list of include paths that are normally passed to the compiler\n# using the -I flag.\n\nSTRIP_FROM_INC_PATH    =\n\n# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but\n# less readable) file names. This can be useful is your file systems doesn't\n# support long names like on DOS, Mac, or CD-ROM.\n# The default value is: NO.\n\nSHORT_NAMES            = NO\n\n# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the\n# first line (until the first dot) of a Javadoc-style comment as the brief\n# description. If set to NO, the Javadoc-style will behave just like regular Qt-\n# style comments (thus requiring an explicit @brief command for a brief\n# description.)\n# The default value is: NO.\n\nJAVADOC_AUTOBRIEF      = NO\n\n# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line\n# such as\n# /***************\n# as being the beginning of a Javadoc-style comment \"banner\". If set to NO, the\n# Javadoc-style will behave just like regular comments and it will not be\n# interpreted by doxygen.\n# The default value is: NO.\n\nJAVADOC_BANNER         = NO\n\n# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first\n# line (until the first dot) of a Qt-style comment as the brief description. If\n# set to NO, the Qt-style will behave just like regular Qt-style comments (thus\n# requiring an explicit \\brief command for a brief description.)\n# The default value is: NO.\n\nQT_AUTOBRIEF           = NO\n\n# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a\n# multi-line C++ special comment block (i.e. a block of //! or /// comments) as\n# a brief description. This used to be the default behavior. The new default is\n# to treat a multi-line C++ comment block as a detailed description. Set this\n# tag to YES if you prefer the old behavior instead.\n#\n# Note that setting this tag to YES also means that rational rose comments are\n# not recognized any more.\n# The default value is: NO.\n\nMULTILINE_CPP_IS_BRIEF = NO\n\n# By default Python docstrings are displayed as preformatted text and doxygen's\n# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the\n# doxygen's special commands can be used and the contents of the docstring\n# documentation blocks is shown as doxygen documentation.\n# The default value is: YES.\n\nPYTHON_DOCSTRING       = YES\n\n# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the\n# documentation from any documented member that it re-implements.\n# The default value is: YES.\n\nINHERIT_DOCS           = YES\n\n# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new\n# page for each member. If set to NO, the documentation of a member will be part\n# of the file/class/namespace that contains it.\n# The default value is: NO.\n\nSEPARATE_MEMBER_PAGES  = NO\n\n# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen\n# uses this value to replace tabs by spaces in code fragments.\n# Minimum value: 1, maximum value: 16, default value: 4.\n\nTAB_SIZE               = 4\n\n# This tag can be used to specify a number of aliases that act as commands in\n# the documentation. An alias has the form:\n# name=value\n# For example adding\n# \"sideeffect=@par Side Effects:\\n\"\n# will allow you to put the command \\sideeffect (or @sideeffect) in the\n# documentation, which will result in a user-defined paragraph with heading\n# \"Side Effects:\". You can put \\n's in the value part of an alias to insert\n# newlines (in the resulting output). You can put ^^ in the value part of an\n# alias to insert a newline as if a physical newline was in the original file.\n# When you need a literal { or } or , in the value part of an alias you have to\n# escape them by means of a backslash (\\), this can lead to conflicts with the\n# commands \\{ and \\} for these it is advised to use the version @{ and @} or use\n# a double escape (\\\\{ and \\\\})\n\nALIASES                =\n\n# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources\n# only. Doxygen will then generate output that is more tailored for C. For\n# instance, some of the names that are used will be different. The list of all\n# members will be omitted, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_FOR_C  = NO\n\n# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or\n# Python sources only. Doxygen will then generate output that is more tailored\n# for that language. For instance, namespaces will be presented as packages,\n# qualified scopes will look different, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_JAVA   = NO\n\n# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran\n# sources. Doxygen will then generate output that is tailored for Fortran.\n# The default value is: NO.\n\nOPTIMIZE_FOR_FORTRAN   = NO\n\n# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL\n# sources. Doxygen will then generate output that is tailored for VHDL.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_VHDL   = NO\n\n# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice\n# sources only. Doxygen will then generate output that is more tailored for that\n# language. For instance, namespaces will be presented as modules, types will be\n# separated into more groups, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_SLICE  = NO\n\n# Doxygen selects the parser to use depending on the extension of the files it\n# parses. With this tag you can assign which parser to use for a given\n# extension. Doxygen has a built-in mapping, but you can override or extend it\n# using this tag. The format is ext=language, where ext is a file extension, and\n# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,\n# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,\n# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:\n# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser\n# tries to guess whether the code is fixed or free formatted code, this is the\n# default for Fortran type files). For instance to make doxygen treat .inc files\n# as Fortran files (default is PHP), and .f files as C (default is Fortran),\n# use: inc=Fortran f=C.\n#\n# Note: For files without extension you can use no_extension as a placeholder.\n#\n# Note that for custom extensions you also need to set FILE_PATTERNS otherwise\n# the files are not read by doxygen. When specifying no_extension you should add\n# * to the FILE_PATTERNS.\n#\n# Note see also the list of default file extension mappings.\n\nEXTENSION_MAPPING      =\n\n# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments\n# according to the Markdown format, which allows for more readable\n# documentation. See https://daringfireball.net/projects/markdown/ for details.\n# The output of markdown processing is further processed by doxygen, so you can\n# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in\n# case of backward compatibilities issues.\n# The default value is: YES.\n\nMARKDOWN_SUPPORT       = YES\n\n# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up\n# to that level are automatically included in the table of contents, even if\n# they do not have an id attribute.\n# Note: This feature currently applies only to Markdown headings.\n# Minimum value: 0, maximum value: 99, default value: 5.\n# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.\n\nTOC_INCLUDE_HEADINGS   = 5\n\n# When enabled doxygen tries to link words that correspond to documented\n# classes, or namespaces to their corresponding documentation. Such a link can\n# be prevented in individual cases by putting a % sign in front of the word or\n# globally by setting AUTOLINK_SUPPORT to NO.\n# The default value is: YES.\n\nAUTOLINK_SUPPORT       = YES\n\n# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want\n# to include (a tag file for) the STL sources as input, then you should set this\n# tag to YES in order to let doxygen match functions declarations and\n# definitions whose arguments contain STL classes (e.g. func(std::string);\n# versus func(std::string) {}). This also make the inheritance and collaboration\n# diagrams that involve STL classes more complete and accurate.\n# The default value is: NO.\n\nBUILTIN_STL_SUPPORT    = YES\n\n# If you use Microsoft's C++/CLI language, you should set this option to YES to\n# enable parsing support.\n# The default value is: NO.\n\nCPP_CLI_SUPPORT        = NO\n\n# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:\n# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen\n# will parse them like normal C++ but will assume all classes use public instead\n# of private inheritance when no explicit protection keyword is present.\n# The default value is: NO.\n\nSIP_SUPPORT            = NO\n\n# For Microsoft's IDL there are propget and propput attributes to indicate\n# getter and setter methods for a property. Setting this option to YES will make\n# doxygen to replace the get and set methods by a property in the documentation.\n# This will only work if the methods are indeed getting or setting a simple\n# type. If this is not the case, or you want to show the methods anyway, you\n# should set this option to NO.\n# The default value is: YES.\n\nIDL_PROPERTY_SUPPORT   = YES\n\n# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC\n# tag is set to YES then doxygen will reuse the documentation of the first\n# member in the group (if any) for the other members of the group. By default\n# all members of a group must be documented explicitly.\n# The default value is: NO.\n\nDISTRIBUTE_GROUP_DOC   = NO\n\n# If one adds a struct or class to a group and this option is enabled, then also\n# any nested class or struct is added to the same group. By default this option\n# is disabled and one has to add nested compounds explicitly via \\ingroup.\n# The default value is: NO.\n\nGROUP_NESTED_COMPOUNDS = NO\n\n# Set the SUBGROUPING tag to YES to allow class member groups of the same type\n# (for instance a group of public functions) to be put as a subgroup of that\n# type (e.g. under the Public Functions section). Set it to NO to prevent\n# subgrouping. Alternatively, this can be done per class using the\n# \\nosubgrouping command.\n# The default value is: YES.\n\nSUBGROUPING            = YES\n\n# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions\n# are shown inside the group in which they are included (e.g. using \\ingroup)\n# instead of on a separate page (for HTML and Man pages) or section (for LaTeX\n# and RTF).\n#\n# Note that this feature does not work in combination with\n# SEPARATE_MEMBER_PAGES.\n# The default value is: NO.\n\nINLINE_GROUPED_CLASSES = NO\n\n# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions\n# with only public data fields or simple typedef fields will be shown inline in\n# the documentation of the scope in which they are defined (i.e. file,\n# namespace, or group documentation), provided this scope is documented. If set\n# to NO, structs, classes, and unions are shown on a separate page (for HTML and\n# Man pages) or section (for LaTeX and RTF).\n# The default value is: NO.\n\nINLINE_SIMPLE_STRUCTS  = NO\n\n# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or\n# enum is documented as struct, union, or enum with the name of the typedef. So\n# typedef struct TypeS {} TypeT, will appear in the documentation as a struct\n# with name TypeT. When disabled the typedef will appear as a member of a file,\n# namespace, or class. And the struct will be named TypeS. This can typically be\n# useful for C code in case the coding convention dictates that all compound\n# types are typedef'ed and only the typedef is referenced, never the tag name.\n# The default value is: NO.\n\nTYPEDEF_HIDES_STRUCT   = YES\n\n# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This\n# cache is used to resolve symbols given their name and scope. Since this can be\n# an expensive process and often the same symbol appears multiple times in the\n# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small\n# doxygen will become slower. If the cache is too large, memory is wasted. The\n# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range\n# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536\n# symbols. At the end of a run doxygen will report the cache usage and suggest\n# the optimal cache size from a speed point of view.\n# Minimum value: 0, maximum value: 9, default value: 0.\n\nLOOKUP_CACHE_SIZE      = 0\n\n# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use\n# during processing. When set to 0 doxygen will based this on the number of\n# cores available in the system. You can set it explicitly to a value larger\n# than 0 to get more control over the balance between CPU load and processing\n# speed. At this moment only the input processing can be done using multiple\n# threads. Since this is still an experimental feature the default is set to 1,\n# which efficively disables parallel processing. Please report any issues you\n# encounter. Generating dot graphs in parallel is controlled by the\n# DOT_NUM_THREADS setting.\n# Minimum value: 0, maximum value: 32, default value: 1.\n\nNUM_PROC_THREADS       = 4\n\n#---------------------------------------------------------------------------\n# Build related configuration options\n#---------------------------------------------------------------------------\n\n# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in\n# documentation are documented, even if no documentation was available. Private\n# class members and static file members will be hidden unless the\n# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.\n# Note: This will also disable the warnings about undocumented members that are\n# normally produced when WARNINGS is set to YES.\n# The default value is: NO.\n\nEXTRACT_ALL            = YES\n\n# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will\n# be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIVATE        = NO\n\n# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual\n# methods of a class will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIV_VIRTUAL   = NO\n\n# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal\n# scope will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PACKAGE        = NO\n\n# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be\n# included in the documentation.\n# The default value is: NO.\n\nEXTRACT_STATIC         = NO\n\n# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined\n# locally in source files will be included in the documentation. If set to NO,\n# only classes defined in header files are included. Does not have any effect\n# for Java sources.\n# The default value is: YES.\n\nEXTRACT_LOCAL_CLASSES  = YES\n\n# This flag is only useful for Objective-C code. If set to YES, local methods,\n# which are defined in the implementation section but not in the interface are\n# included in the documentation. If set to NO, only methods in the interface are\n# included.\n# The default value is: NO.\n\nEXTRACT_LOCAL_METHODS  = NO\n\n# If this flag is set to YES, the members of anonymous namespaces will be\n# extracted and appear in the documentation as a namespace called\n# 'anonymous_namespace{file}', where file will be replaced with the base name of\n# the file that contains the anonymous namespace. By default anonymous namespace\n# are hidden.\n# The default value is: NO.\n\nEXTRACT_ANON_NSPACES   = NO\n\n# If this flag is set to YES, the name of an unnamed parameter in a declaration\n# will be determined by the corresponding definition. By default unnamed\n# parameters remain unnamed in the output.\n# The default value is: YES.\n\nRESOLVE_UNNAMED_PARAMS = YES\n\n# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all\n# undocumented members inside documented classes or files. If set to NO these\n# members will be included in the various overviews, but no documentation\n# section is generated. This option has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_MEMBERS     = NO\n\n# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all\n# undocumented classes that are normally visible in the class hierarchy. If set\n# to NO, these classes will be included in the various overviews. This option\n# has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_CLASSES     = NO\n\n# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend\n# declarations. If set to NO, these declarations will be included in the\n# documentation.\n# The default value is: NO.\n\nHIDE_FRIEND_COMPOUNDS  = NO\n\n# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any\n# documentation blocks found inside the body of a function. If set to NO, these\n# blocks will be appended to the function's detailed documentation block.\n# The default value is: NO.\n\nHIDE_IN_BODY_DOCS      = NO\n\n# The INTERNAL_DOCS tag determines if documentation that is typed after a\n# \\internal command is included. If the tag is set to NO then the documentation\n# will be excluded. Set it to YES to include the internal documentation.\n# The default value is: NO.\n\nINTERNAL_DOCS          = NO\n\n# With the correct setting of option CASE_SENSE_NAMES doxygen will better be\n# able to match the capabilities of the underlying filesystem. In case the\n# filesystem is case sensitive (i.e. it supports files in the same directory\n# whose names only differ in casing), the option must be set to YES to properly\n# deal with such files in case they appear in the input. For filesystems that\n# are not case sensitive the option should be be set to NO to properly deal with\n# output files written for symbols that only differ in casing, such as for two\n# classes, one named CLASS and the other named Class, and to also support\n# references to files without having to specify the exact matching casing. On\n# Windows (including Cygwin) and MacOS, users should typically set this option\n# to NO, whereas on Linux or other Unix flavors it should typically be set to\n# YES.\n# The default value is: system dependent.\n\nCASE_SENSE_NAMES       = YES\n\n# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with\n# their full class and namespace scopes in the documentation. If set to YES, the\n# scope will be hidden.\n# The default value is: NO.\n\nHIDE_SCOPE_NAMES       = NO\n\n# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will\n# append additional text to a page's title, such as Class Reference. If set to\n# YES the compound reference will be hidden.\n# The default value is: NO.\n\nHIDE_COMPOUND_REFERENCE= NO\n\n# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of\n# the files that are included by a file in the documentation of that file.\n# The default value is: YES.\n\nSHOW_INCLUDE_FILES     = YES\n\n# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each\n# grouped member an include statement to the documentation, telling the reader\n# which file to include in order to use the member.\n# The default value is: NO.\n\nSHOW_GROUPED_MEMB_INC  = NO\n\n# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include\n# files with double quotes in the documentation rather than with sharp brackets.\n# The default value is: NO.\n\nFORCE_LOCAL_INCLUDES   = NO\n\n# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the\n# documentation for inline members.\n# The default value is: YES.\n\nINLINE_INFO            = YES\n\n# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the\n# (detailed) documentation of file and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order.\n# The default value is: YES.\n\nSORT_MEMBER_DOCS       = YES\n\n# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief\n# descriptions of file, namespace and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order. Note that\n# this will also influence the order of the classes in the class list.\n# The default value is: NO.\n\nSORT_BRIEF_DOCS        = NO\n\n# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the\n# (brief and detailed) documentation of class members so that constructors and\n# destructors are listed first. If set to NO the constructors will appear in the\n# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.\n# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief\n# member documentation.\n# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting\n# detailed member documentation.\n# The default value is: NO.\n\nSORT_MEMBERS_CTORS_1ST = NO\n\n# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy\n# of group names into alphabetical order. If set to NO the group names will\n# appear in their defined order.\n# The default value is: NO.\n\nSORT_GROUP_NAMES       = NO\n\n# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by\n# fully-qualified names, including namespaces. If set to NO, the class list will\n# be sorted only by class name, not including the namespace part.\n# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.\n# Note: This option applies only to the class list, not to the alphabetical\n# list.\n# The default value is: NO.\n\nSORT_BY_SCOPE_NAME     = NO\n\n# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper\n# type resolution of all parameters of a function it will reject a match between\n# the prototype and the implementation of a member function even if there is\n# only one candidate or it is obvious which candidate to choose by doing a\n# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still\n# accept a match between prototype and implementation in such cases.\n# The default value is: NO.\n\nSTRICT_PROTO_MATCHING  = NO\n\n# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo\n# list. This list is created by putting \\todo commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TODOLIST      = YES\n\n# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test\n# list. This list is created by putting \\test commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TESTLIST      = YES\n\n# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug\n# list. This list is created by putting \\bug commands in the documentation.\n# The default value is: YES.\n\nGENERATE_BUGLIST       = YES\n\n# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)\n# the deprecated list. This list is created by putting \\deprecated commands in\n# the documentation.\n# The default value is: YES.\n\nGENERATE_DEPRECATEDLIST= YES\n\n# The ENABLED_SECTIONS tag can be used to enable conditional documentation\n# sections, marked by \\if <section_label> ... \\endif and \\cond <section_label>\n# ... \\endcond blocks.\n\nENABLED_SECTIONS       =\n\n# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the\n# initial value of a variable or macro / define can have for it to appear in the\n# documentation. If the initializer consists of more lines than specified here\n# it will be hidden. Use a value of 0 to hide initializers completely. The\n# appearance of the value of individual variables and macros / defines can be\n# controlled using \\showinitializer or \\hideinitializer command in the\n# documentation regardless of this setting.\n# Minimum value: 0, maximum value: 10000, default value: 30.\n\nMAX_INITIALIZER_LINES  = 30\n\n# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at\n# the bottom of the documentation of classes and structs. If set to YES, the\n# list will mention the files that were used to generate the documentation.\n# The default value is: YES.\n\nSHOW_USED_FILES        = YES\n\n# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This\n# will remove the Files entry from the Quick Index and from the Folder Tree View\n# (if specified).\n# The default value is: YES.\n\nSHOW_FILES             = YES\n\n# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces\n# page. This will remove the Namespaces entry from the Quick Index and from the\n# Folder Tree View (if specified).\n# The default value is: YES.\n\nSHOW_NAMESPACES        = YES\n\n# The FILE_VERSION_FILTER tag can be used to specify a program or script that\n# doxygen should invoke to get the current version for each file (typically from\n# the version control system). Doxygen will invoke the program by executing (via\n# popen()) the command command input-file, where command is the value of the\n# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided\n# by doxygen. Whatever the program writes to standard output is used as the file\n# version. For an example see the documentation.\n\nFILE_VERSION_FILTER    =\n\n# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed\n# by doxygen. The layout file controls the global structure of the generated\n# output files in an output format independent way. To create the layout file\n# that represents doxygen's defaults, run doxygen with the -l option. You can\n# optionally specify a file name after the option, if omitted DoxygenLayout.xml\n# will be used as the name of the layout file.\n#\n# Note that if you run doxygen from a directory containing a file called\n# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE\n# tag is left empty.\n\nLAYOUT_FILE            =\n\n# The CITE_BIB_FILES tag can be used to specify one or more bib files containing\n# the reference definitions. This must be a list of .bib files. The .bib\n# extension is automatically appended if omitted. This requires the bibtex tool\n# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.\n# For LaTeX the style of the bibliography can be controlled using\n# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the\n# search path. See also \\cite for info how to create references.\n\nCITE_BIB_FILES         =\n\n#---------------------------------------------------------------------------\n# Configuration options related to warning and progress messages\n#---------------------------------------------------------------------------\n\n# The QUIET tag can be used to turn on/off the messages that are generated to\n# standard output by doxygen. If QUIET is set to YES this implies that the\n# messages are off.\n# The default value is: NO.\n\nQUIET                  = NO\n\n# The WARNINGS tag can be used to turn on/off the warning messages that are\n# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES\n# this implies that the warnings are on.\n#\n# Tip: Turn warnings on while writing the documentation.\n# The default value is: YES.\n\nWARNINGS               = YES\n\n# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate\n# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag\n# will automatically be disabled.\n# The default value is: YES.\n\nWARN_IF_UNDOCUMENTED   = YES\n\n# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for\n# potential errors in the documentation, such as not documenting some parameters\n# in a documented function, or documenting parameters that don't exist or using\n# markup commands wrongly.\n# The default value is: YES.\n\nWARN_IF_DOC_ERROR      = YES\n\n# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that\n# are documented, but have no documentation for their parameters or return\n# value. If set to NO, doxygen will only warn about wrong or incomplete\n# parameter documentation, but not about the absence of documentation. If\n# EXTRACT_ALL is set to YES then this flag will automatically be disabled.\n# The default value is: NO.\n\nWARN_NO_PARAMDOC       = NO\n\n# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when\n# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS\n# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but\n# at the end of the doxygen process doxygen will return with a non-zero status.\n# Possible values are: NO, YES and FAIL_ON_WARNINGS.\n# The default value is: NO.\n\nWARN_AS_ERROR          = NO\n\n# The WARN_FORMAT tag determines the format of the warning messages that doxygen\n# can produce. The string should contain the $file, $line, and $text tags, which\n# will be replaced by the file and line number from which the warning originated\n# and the warning text. Optionally the format may contain $version, which will\n# be replaced by the version of the file (if it could be obtained via\n# FILE_VERSION_FILTER)\n# The default value is: $file:$line: $text.\n\nWARN_FORMAT            = \"$file:$line: $text\"\n\n# The WARN_LOGFILE tag can be used to specify a file to which warning and error\n# messages should be written. If left blank the output is written to standard\n# error (stderr).\n\nWARN_LOGFILE           =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the input files\n#---------------------------------------------------------------------------\n\n# The INPUT tag is used to specify the files and/or directories that contain\n# documented source files. You may enter file names like myfile.cpp or\n# directories like /usr/src/myproject. Separate the files or directories with\n# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING\n# Note: If this tag is empty the current directory is searched.\n\nINPUT                  = src/workerd/api \\\n                         src/workerd/api/crypto \\\n                         src/workerd/api/node \\\n                         src/workerd/api/streams \\\n                         src/workerd/io \\\n                         src/workerd/jsg \\\n                         src/workerd/server \\\n                         src/workerd/util\n\n# This tag can be used to specify the character encoding of the source files\n# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses\n# libiconv (or the iconv built into libc) for the transcoding. See the libiconv\n# documentation (see:\n# https://www.gnu.org/software/libiconv/) for the list of possible encodings.\n# The default value is: UTF-8.\n\nINPUT_ENCODING         = UTF-8\n\n# If the value of the INPUT tag contains directories, you can use the\n# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and\n# *.h) to filter out the source-files in the directories.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# read by doxygen.\n#\n# Note the list of default checked file patterns might differ from the list of\n# default file extension mappings.\n#\n# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,\n# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,\n# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,\n# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),\n# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl,\n# *.ucf, *.qsf and *.ice.\n\nFILE_PATTERNS          = *.c++ \\\n                         *.h \\\n\n# The RECURSIVE tag can be used to specify whether or not subdirectories should\n# be searched for input files as well.\n# The default value is: NO.\n\nRECURSIVE              = NO\n\n# The EXCLUDE tag can be used to specify files and/or directories that should be\n# excluded from the INPUT source files. This way you can easily exclude a\n# subdirectory from a directory tree whose root is specified with the INPUT tag.\n#\n# Note that relative paths are relative to the directory from which doxygen is\n# run.\n\nEXCLUDE                =\n\n# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or\n# directories that are symbolic links (a Unix file system feature) are excluded\n# from the input.\n# The default value is: NO.\n\nEXCLUDE_SYMLINKS       = NO\n\n# If the value of the INPUT tag contains directories, you can use the\n# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude\n# certain files from those directories.\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories for example use the pattern */test/*\n\nEXCLUDE_PATTERNS       = *-test.c++\n\n# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names\n# (namespaces, classes, functions, etc.) that should be excluded from the\n# output. The symbol name can be a fully qualified name, a word, or if the\n# wildcard * is used, a substring. Examples: ANamespace, AClass,\n# AClass::ANamespace, ANamespace::*Test\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories use the pattern */test/*\n\nEXCLUDE_SYMBOLS        =\n\n# The EXAMPLE_PATH tag can be used to specify one or more files or directories\n# that contain example code fragments that are included (see the \\include\n# command).\n\nEXAMPLE_PATH           =\n\n# If the value of the EXAMPLE_PATH tag contains directories, you can use the\n# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank all\n# files are included.\n\nEXAMPLE_PATTERNS       = *\n\n# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be\n# searched for input files to be used with the \\include or \\dontinclude commands\n# irrespective of the value of the RECURSIVE tag.\n# The default value is: NO.\n\nEXAMPLE_RECURSIVE      = NO\n\n# The IMAGE_PATH tag can be used to specify one or more files or directories\n# that contain images that are to be included in the documentation (see the\n# \\image command).\n\nIMAGE_PATH             =\n\n# The INPUT_FILTER tag can be used to specify a program that doxygen should\n# invoke to filter for each input file. Doxygen will invoke the filter program\n# by executing (via popen()) the command:\n#\n# <filter> <input-file>\n#\n# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the\n# name of an input file. Doxygen will then use the output that the filter\n# program writes to standard output. If FILTER_PATTERNS is specified, this tag\n# will be ignored.\n#\n# Note that the filter must not add or remove lines; it is applied before the\n# code is scanned, but not when the output code is generated. If lines are added\n# or removed, the anchors will not be placed correctly.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nINPUT_FILTER           =\n\n# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern\n# basis. Doxygen will compare the file name with each pattern and apply the\n# filter if there is a match. The filters are a list of the form: pattern=filter\n# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how\n# filters are used. If the FILTER_PATTERNS tag is empty or if none of the\n# patterns match the file name, INPUT_FILTER is applied.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nFILTER_PATTERNS        =\n\n# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using\n# INPUT_FILTER) will also be used to filter the input files that are used for\n# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).\n# The default value is: NO.\n\nFILTER_SOURCE_FILES    = NO\n\n# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file\n# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and\n# it is also possible to disable source filtering for a specific pattern using\n# *.ext= (so without naming a filter).\n# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.\n\nFILTER_SOURCE_PATTERNS =\n\n# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that\n# is part of the input, its contents will be placed on the main page\n# (index.html). This can be useful if you have a project on for instance GitHub\n# and want to reuse the introduction page also for the doxygen output.\n\nUSE_MDFILE_AS_MAINPAGE =\n\n#---------------------------------------------------------------------------\n# Configuration options related to source browsing\n#---------------------------------------------------------------------------\n\n# If the SOURCE_BROWSER tag is set to YES then a list of source files will be\n# generated. Documented entities will be cross-referenced with these sources.\n#\n# Note: To get rid of all source code in the generated output, make sure that\n# also VERBATIM_HEADERS is set to NO.\n# The default value is: NO.\n\nSOURCE_BROWSER         = YES\n\n# Setting the INLINE_SOURCES tag to YES will include the body of functions,\n# classes and enums directly into the documentation.\n# The default value is: NO.\n\nINLINE_SOURCES         = NO\n\n# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any\n# special comment blocks from generated source code fragments. Normal C, C++ and\n# Fortran comments will always remain visible.\n# The default value is: YES.\n\nSTRIP_CODE_COMMENTS    = NO\n\n# If the REFERENCED_BY_RELATION tag is set to YES then for each documented\n# entity all documented functions referencing it will be listed.\n# The default value is: NO.\n\nREFERENCED_BY_RELATION = NO\n\n# If the REFERENCES_RELATION tag is set to YES then for each documented function\n# all documented entities called/used by that function will be listed.\n# The default value is: NO.\n\nREFERENCES_RELATION    = NO\n\n# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set\n# to YES then the hyperlinks from functions in REFERENCES_RELATION and\n# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will\n# link to the documentation.\n# The default value is: YES.\n\nREFERENCES_LINK_SOURCE = YES\n\n# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the\n# source code will show a tooltip with additional information such as prototype,\n# brief description and links to the definition and documentation. Since this\n# will make the HTML file larger and loading of large files a bit slower, you\n# can opt to disable this feature.\n# The default value is: YES.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nSOURCE_TOOLTIPS        = YES\n\n# If the USE_HTAGS tag is set to YES then the references to source code will\n# point to the HTML generated by the htags(1) tool instead of doxygen built-in\n# source browser. The htags tool is part of GNU's global source tagging system\n# (see https://www.gnu.org/software/global/global.html). You will need version\n# 4.8.6 or higher.\n#\n# To use it do the following:\n# - Install the latest version of global\n# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file\n# - Make sure the INPUT points to the root of the source tree\n# - Run doxygen as normal\n#\n# Doxygen will invoke htags (and that will in turn invoke gtags), so these\n# tools must be available from the command line (i.e. in the search path).\n#\n# The result: instead of the source browser generated by doxygen, the links to\n# source code will now point to the output of htags.\n# The default value is: NO.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nUSE_HTAGS              = NO\n\n# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a\n# verbatim copy of the header file for each class for which an include is\n# specified. Set to NO to disable this.\n# See also: Section \\class.\n# The default value is: YES.\n\nVERBATIM_HEADERS       = YES\n\n# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the\n# clang parser (see:\n# http://clang.llvm.org/) for more accurate parsing at the cost of reduced\n# performance. This can be particularly helpful with template rich C++ code for\n# which doxygen's built-in parser lacks the necessary type information.\n# Note: The availability of this option depends on whether or not doxygen was\n# generated with the -Duse_libclang=ON option for CMake.\n# The default value is: NO.\n\nCLANG_ASSISTED_PARSING = YES\n\n# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to\n# YES then doxygen will add the directory of each input to the include path.\n# The default value is: YES.\n\nCLANG_ADD_INC_PATHS    = YES\n\n# If clang assisted parsing is enabled you can provide the compiler with command\n# line options that you would normally use when invoking the compiler. Note that\n# the include paths will already be set by doxygen for the files and directories\n# specified with INPUT and INCLUDE_PATH.\n# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.\n\nCLANG_OPTIONS          = -std=c++23     \\\n                         -stdlib=libc++ \\\n                         -xc++          \\\n                         -nostdinc      \\\n                         -fbracket-depth=512 \\\n                         -D_FORTIFY_SOURCE=1 \\\n                         -DCAPNP_VERSION=11000 \\\n                         -DDEBUG \\\n                         -DGOOGLE3 \\\n                         -DHAVE_DLOPEN=0 \\\n                         -DKJ_HAS_LIBDL \\\n                         -DKJ_HAS_OPENSSL \\\n                         -DKJ_HAS_ZLIB \\\n                         -DKJ_SAVE_ACQUIRED_LOCK_INFO=0 \\\n                         -DKJ_TRACK_LOCK_BLOCKING=0 \\\n                         -DSQLITE_ENABLE_FTS5 \\\n                         -DSQLITE_ENABLE_NORMALIZE \\\n                         -DSQLITE_MAX_ALLOCATION_SIZE=16777216 \\\n                         -DSQLITE_PRINTF_PRECISION_LIMIT=100000 \\\n                         -DUCONFIG_ONLY_HTML_CONVERSION=1 \\\n                         -DUNISTR_FROM_CHAR_EXPLICIT= \\\n                         -DUNISTR_FROM_STRING_EXPLICIT= \\\n                         -DUSE_CHROMIUM_ICU=1 \\\n                         -DU_CHARSET_IS_UTF8=1 \\\n                         -DU_COMMON_IMPLEMENTATION \\\n                         -DU_ENABLE_DYLOAD=0 \\\n                         -DU_ENABLE_RESOURCE_TRACING=0 \\\n                         -DU_ENABLE_TRACING=1 \\\n                         -DU_I18N_IMPLEMENTATION \\\n                         -DU_ICUDATAENTRY_IN_COMMON \\\n                         -DU_USING_ICU_NAMESPACE=0 \\\n                         -DV8_31BIT_SMIS_ON_64BIT_ARCH \\\n                         -DV8_ADVANCED_BIGINT_ALGORITHMS \\\n                         -DV8_COMPRESS_POINTERS \\\n                         -DV8_COMPRESS_POINTERS_IN_SHARED_CAGE \\\n                         -DV8_CONCURRENT_MARKING \\\n                         -DV8_DEPRECATION_WARNINGS \\\n                         -DV8_ENABLE_CHECKS \\\n                         -DV8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA \\\n                         -DV8_ENABLE_LAZY_SOURCE_POSITIONS \\\n                         -DV8_ENABLE_TURBOFAN \\\n                         -DV8_ENABLE_WEBASSEMBLY \\\n                         -DV8_HAVE_TARGET_OS \\\n                         -DV8_IMMINENT_DEPRECATION_WARNINGS \\\n                         -DV8_SHORT_BUILTIN_CALLS \\\n                         -DV8_TARGET_ARCH_X64 \\\n                         -DV8_TARGET_OS_LINUX \\\n                         -DV8_TYPED_ARRAY_MAX_SIZE_IN_HEAP=64 \\\n                         -DWORKERD_ICU_DATA_EMBED \\\n                         -Wall \\\n                         -Werror=dangling \\\n                         -Werror=implicit-fallthrough \\\n                         -Werror=return-stack-address \\\n                         -Werror=return-type \\\n                         -Werror=switch \\\n                         -Werror=uninitialized \\\n                         -Werror=unreachable-code \\\n                         -Werror=unused-function \\\n                         -Werror=unused-lambda-capture \\\n                         -Werror=unused-variable \\\n                         -Wextra \\\n                         -Wno-builtin-macro-redefined \\\n                         -Wno-free-nonheap-object \\\n                         -Wno-missing-field-initializers \\\n                         -Wno-sign-compare \\\n                         -Wno-unused-parameter \\\n                         -Wself-assign \\\n                         -Wthread-safety \\\n                         -Wunused-but-set-parameter \\\n                         -Wunused-function \\\n                         -Wunused-lambda-capture \\\n                         -Wunused-variable \\\n                         -no-canonical-prefixes \\\n                         -Iexternal/google_benchmark++_repo_rules+codspeed/google_benchmark/include/ \\\n                         -Iexternal/ada-url+/ \\\n                         -Iexternal/+http+simdutf/ \\\n                         -Iexternal/+http+nbytes/ \\\n                         -Iexternal/boringssl+/include/ \\\n                         -I/usr/include \\\n                         -I/usr/include/c++/11 \\\n                         -I/usr/include/c++/11/bits \\\n                         -I/usr/include/x86_64-linux-gnu \\\n                         -I/usr/include/x86_64-linux-gnu/c++/11 \\\n                         -I/usr/lib/llvm-19/include/c++/v1 \\\n                         -I/usr/lib/llvm-19/lib/clang/19/include\n\n# If clang assisted parsing is enabled you can provide the clang parser with the\n# path to the directory containing a file called compile_commands.json. This\n# file is the compilation database (see:\n# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the\n# options used when the source files were built. This is equivalent to\n# specifying the -p option to a clang tool, such as clang-check. These options\n# will then be passed to the parser. Any options specified with CLANG_OPTIONS\n# will be added as well.\n# Note: The availability of this option depends on whether or not doxygen was\n# generated with the -Duse_libclang=ON option for CMake.\n\nCLANG_DATABASE_PATH    =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the alphabetical class index\n#---------------------------------------------------------------------------\n\n# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all\n# compounds will be generated. Enable this if the project contains a lot of\n# classes, structs, unions or interfaces.\n# The default value is: YES.\n\nALPHABETICAL_INDEX     = YES\n\n# In case all classes in a project start with a common prefix, all classes will\n# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag\n# can be used to specify a prefix (or a list of prefixes) that should be ignored\n# while generating the index headers.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nIGNORE_PREFIX          =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the HTML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output\n# The default value is: YES.\n\nGENERATE_HTML          = YES\n\n# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_OUTPUT            = html\n\n# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each\n# generated HTML page (for example: .htm, .php, .asp).\n# The default value is: .html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FILE_EXTENSION    = .html\n\n# The HTML_HEADER tag can be used to specify a user-defined HTML header file for\n# each generated HTML page. If the tag is left blank doxygen will generate a\n# standard header.\n#\n# To get valid HTML the header file that includes any scripts and style sheets\n# that doxygen needs, which is dependent on the configuration options used (e.g.\n# the setting GENERATE_TREEVIEW). It is highly recommended to start with a\n# default header using\n# doxygen -w html new_header.html new_footer.html new_stylesheet.css\n# YourConfigFile\n# and then modify the file new_header.html. See also section \"Doxygen usage\"\n# for information on how to generate the default header that doxygen normally\n# uses.\n# Note: The header is subject to change so you typically have to regenerate the\n# default header when upgrading to a newer version of doxygen. For a description\n# of the possible markers and block names see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_HEADER            =\n\n# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each\n# generated HTML page. If the tag is left blank doxygen will generate a standard\n# footer. See HTML_HEADER for more information on how to generate a default\n# footer and what special commands can be used inside the footer. See also\n# section \"Doxygen usage\" for information on how to generate the default footer\n# that doxygen normally uses.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FOOTER            =\n\n# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style\n# sheet that is used by each HTML page. It can be used to fine-tune the look of\n# the HTML output. If left blank doxygen will generate a default style sheet.\n# See also section \"Doxygen usage\" for information on how to generate the style\n# sheet that doxygen normally uses.\n# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as\n# it is more robust and this tag (HTML_STYLESHEET) will in the future become\n# obsolete.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_STYLESHEET        =\n\n# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# cascading style sheets that are included after the standard style sheets\n# created by doxygen. Using this option one can overrule certain style aspects.\n# This is preferred over using HTML_STYLESHEET since it does not replace the\n# standard style sheet and is therefore more robust against future updates.\n# Doxygen will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list). For an example see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_STYLESHEET  =\n\n# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the HTML output directory. Note\n# that these files will be copied to the base HTML output directory. Use the\n# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these\n# files. In the HTML_STYLESHEET file, use the file name only. Also note that the\n# files will be copied as-is; there are no commands or markers available.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_FILES       =\n\n# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen\n# will adjust the colors in the style sheet and background images according to\n# this color. Hue is specified as an angle on a colorwheel, see\n# https://en.wikipedia.org/wiki/Hue for more information. For instance the value\n# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300\n# purple, and 360 is red again.\n# Minimum value: 0, maximum value: 359, default value: 220.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_HUE    = 220\n\n# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors\n# in the HTML output. For a value of 0 the output will use grayscales only. A\n# value of 255 will produce the most vivid colors.\n# Minimum value: 0, maximum value: 255, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_SAT    = 100\n\n# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the\n# luminance component of the colors in the HTML output. Values below 100\n# gradually make the output lighter, whereas values above 100 make the output\n# darker. The value divided by 100 is the actual gamma applied, so 80 represents\n# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not\n# change the gamma.\n# Minimum value: 40, maximum value: 240, default value: 80.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_GAMMA  = 80\n\n# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML\n# page will contain the date and time when the page was generated. Setting this\n# to YES can help to show when doxygen was last run and thus if the\n# documentation is up to date.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_TIMESTAMP         = NO\n\n# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML\n# documentation will contain a main index with vertical navigation menus that\n# are dynamically created via JavaScript. If disabled, the navigation index will\n# consists of multiple levels of tabs that are statically embedded in every HTML\n# page. Disable this option to support browsers that do not have JavaScript,\n# like the Qt help browser.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_MENUS     = YES\n\n# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML\n# documentation will contain sections that can be hidden and shown after the\n# page has loaded.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_SECTIONS  = NO\n\n# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries\n# shown in the various tree structured indices initially; the user can expand\n# and collapse entries dynamically later on. Doxygen will expand the tree to\n# such a level that at most the specified number of entries are visible (unless\n# a fully collapsed tree already exceeds this amount). So setting the number of\n# entries 1 will produce a full collapsed tree by default. 0 is a special value\n# representing an infinite number of entries and will result in a full expanded\n# tree by default.\n# Minimum value: 0, maximum value: 9999, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_INDEX_NUM_ENTRIES = 100\n\n# If the GENERATE_DOCSET tag is set to YES, additional index files will be\n# generated that can be used as input for Apple's Xcode 3 integrated development\n# environment (see:\n# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To\n# create a documentation set, doxygen will generate a Makefile in the HTML\n# output directory. Running make will produce the docset in that directory and\n# running make install will install the docset in\n# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at\n# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy\n# genXcode/_index.html for more information.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_DOCSET        = NO\n\n# This tag determines the name of the docset feed. A documentation feed provides\n# an umbrella under which multiple documentation sets from a single provider\n# (such as a company or product suite) can be grouped.\n# The default value is: Doxygen generated docs.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_FEEDNAME        = \"Doxygen generated docs\"\n\n# This tag specifies a string that should uniquely identify the documentation\n# set bundle. This should be a reverse domain-name style string, e.g.\n# com.mycompany.MyDocSet. Doxygen will append .docset to the name.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_BUNDLE_ID       = org.doxygen.Project\n\n# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify\n# the documentation publisher. This should be a reverse domain-name style\n# string, e.g. com.mycompany.MyDocSet.documentation.\n# The default value is: org.doxygen.Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_ID    = org.doxygen.Publisher\n\n# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.\n# The default value is: Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_NAME  = Publisher\n\n# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three\n# additional HTML index files: index.hhp, index.hhc, and index.hhk. The\n# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop\n# (see:\n# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows.\n#\n# The HTML Help Workshop contains a compiler that can convert all HTML output\n# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML\n# files are now used as the Windows 98 help format, and will replace the old\n# Windows help format (.hlp) on all Windows platforms in the future. Compressed\n# HTML files also contain an index, a table of contents, and you can search for\n# words in the documentation. The HTML workshop also contains a viewer for\n# compressed HTML files.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_HTMLHELP      = NO\n\n# The CHM_FILE tag can be used to specify the file name of the resulting .chm\n# file. You can add a path in front of the file if the result should not be\n# written to the html output directory.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_FILE               =\n\n# The HHC_LOCATION tag can be used to specify the location (absolute path\n# including file name) of the HTML help compiler (hhc.exe). If non-empty,\n# doxygen will try to run the HTML help compiler on the generated index.hhp.\n# The file has to be specified with full path.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nHHC_LOCATION           =\n\n# The GENERATE_CHI flag controls if a separate .chi index file is generated\n# (YES) or that it should be included in the main .chm file (NO).\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nGENERATE_CHI           = NO\n\n# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)\n# and project file content.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_INDEX_ENCODING     =\n\n# The BINARY_TOC flag controls whether a binary table of contents is generated\n# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it\n# enables the Previous and Next buttons.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nBINARY_TOC             = NO\n\n# The TOC_EXPAND flag can be set to YES to add extra items for group members to\n# the table of contents of the HTML help documentation and to the tree view.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nTOC_EXPAND             = NO\n\n# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and\n# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that\n# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help\n# (.qch) of the generated HTML documentation.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_QHP           = NO\n\n# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify\n# the file name of the resulting .qch file. The path specified is relative to\n# the HTML output folder.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQCH_FILE               =\n\n# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help\n# Project output. For more information please see Qt Help Project / Namespace\n# (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_NAMESPACE          = org.doxygen.Project\n\n# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt\n# Help Project output. For more information please see Qt Help Project / Virtual\n# Folders (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).\n# The default value is: doc.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_VIRTUAL_FOLDER     = doc\n\n# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom\n# filter to add. For more information please see Qt Help Project / Custom\n# Filters (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_NAME   =\n\n# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the\n# custom filter to add. For more information please see Qt Help Project / Custom\n# Filters (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_ATTRS  =\n\n# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this\n# project's filter section matches. Qt Help Project / Filter Attributes (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_SECT_FILTER_ATTRS  =\n\n# The QHG_LOCATION tag can be used to specify the location (absolute path\n# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to\n# run qhelpgenerator on the generated .qhp file.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHG_LOCATION           =\n\n# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be\n# generated, together with the HTML files, they form an Eclipse help plugin. To\n# install this plugin and make it available under the help contents menu in\n# Eclipse, the contents of the directory containing the HTML and XML files needs\n# to be copied into the plugins directory of eclipse. The name of the directory\n# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.\n# After copying Eclipse needs to be restarted before the help appears.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_ECLIPSEHELP   = NO\n\n# A unique identifier for the Eclipse help plugin. When installing the plugin\n# the directory name containing the HTML and XML files should also have this\n# name. Each documentation set should have its own identifier.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.\n\nECLIPSE_DOC_ID         = org.doxygen.Project\n\n# If you want full control over the layout of the generated HTML pages it might\n# be necessary to disable the index and replace it with your own. The\n# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top\n# of each HTML page. A value of NO enables the index and the value YES disables\n# it. Since the tabs in the index contain the same information as the navigation\n# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nDISABLE_INDEX          = NO\n\n# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index\n# structure should be generated to display hierarchical information. If the tag\n# value is set to YES, a side panel will be generated containing a tree-like\n# index structure (just like the one that is generated for HTML Help). For this\n# to work a browser that supports JavaScript, DHTML, CSS and frames is required\n# (i.e. any modern browser). Windows users are probably better off using the\n# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can\n# further fine-tune the look of the index. As an example, the default style\n# sheet generated by doxygen has an example that shows how to put an image at\n# the root of the tree instead of the PROJECT_NAME. Since the tree basically has\n# the same information as the tab index, you could consider setting\n# DISABLE_INDEX to YES when enabling this option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_TREEVIEW      = NO\n\n# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that\n# doxygen will group on one line in the generated HTML documentation.\n#\n# Note that a value of 0 will completely suppress the enum values from appearing\n# in the overview section.\n# Minimum value: 0, maximum value: 20, default value: 4.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nENUM_VALUES_PER_LINE   = 4\n\n# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used\n# to set the initial width (in pixels) of the frame in which the tree is shown.\n# Minimum value: 0, maximum value: 1500, default value: 250.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nTREEVIEW_WIDTH         = 250\n\n# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to\n# external symbols imported via tag files in a separate window.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nEXT_LINKS_IN_WINDOW    = NO\n\n# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg\n# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see\n# https://inkscape.org) to generate formulas as SVG images instead of PNGs for\n# the HTML output. These images will generally look nicer at scaled resolutions.\n# Possible values are: png (the default) and svg (looks nicer but requires the\n# pdf2svg or inkscape tool).\n# The default value is: png.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FORMULA_FORMAT    = png\n\n# Use this tag to change the font size of LaTeX formulas included as images in\n# the HTML documentation. When you change the font size after a successful\n# doxygen run you need to manually remove any form_*.png images from the HTML\n# output directory to force them to be regenerated.\n# Minimum value: 8, maximum value: 50, default value: 10.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_FONTSIZE       = 10\n\n# Use the FORMULA_TRANSPARENT tag to determine whether or not the images\n# generated for formulas are transparent PNGs. Transparent PNGs are not\n# supported properly for IE 6.0, but are supported on all modern browsers.\n#\n# Note that when changing this option you need to delete any form_*.png files in\n# the HTML output directory before the changes have effect.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_TRANSPARENT    = YES\n\n# The FORMULA_MACROFILE can contain LaTeX \\newcommand and \\renewcommand commands\n# to create new LaTeX commands to be used in formulas as building blocks. See\n# the section \"Including formulas\" for details.\n\nFORMULA_MACROFILE      =\n\n# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see\n# https://www.mathjax.org) which uses client side JavaScript for the rendering\n# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX\n# installed or if you want to formulas look prettier in the HTML output. When\n# enabled you may also need to install MathJax separately and configure the path\n# to it using the MATHJAX_RELPATH option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nUSE_MATHJAX            = NO\n\n# When MathJax is enabled you can set the default output format to be used for\n# the MathJax output. See the MathJax site (see:\n# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details.\n# Possible values are: HTML-CSS (which is slower, but has the best\n# compatibility), NativeMML (i.e. MathML) and SVG.\n# The default value is: HTML-CSS.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_FORMAT         = HTML-CSS\n\n# When MathJax is enabled you need to specify the location relative to the HTML\n# output directory using the MATHJAX_RELPATH option. The destination directory\n# should contain the MathJax.js script. For instance, if the mathjax directory\n# is located at the same level as the HTML output directory, then\n# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax\n# Content Delivery Network so you can quickly see the result without installing\n# MathJax. However, it is strongly recommended to install a local copy of\n# MathJax from https://www.mathjax.org before deployment.\n# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_RELPATH        = https://cdn.jsdelivr.net/npm/mathjax@2\n\n# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax\n# extension names that should be enabled during MathJax rendering. For example\n# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_EXTENSIONS     =\n\n# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces\n# of code that will be used on startup of the MathJax code. See the MathJax site\n# (see:\n# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an\n# example see the documentation.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_CODEFILE       =\n\n# When the SEARCHENGINE tag is enabled doxygen will generate a search box for\n# the HTML output. The underlying search engine uses javascript and DHTML and\n# should work on any modern browser. Note that when using HTML help\n# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)\n# there is already a search function so this one should typically be disabled.\n# For large projects the javascript based search engine can be slow, then\n# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to\n# search using the keyboard; to jump to the search box use <access key> + S\n# (what the <access key> is depends on the OS and browser, but it is typically\n# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down\n# key> to jump into the search results window, the results can be navigated\n# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel\n# the search. The filter options can be selected when the cursor is inside the\n# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>\n# to select a filter and <Enter> or <escape> to activate or cancel the filter\n# option.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nSEARCHENGINE           = YES\n\n# When the SERVER_BASED_SEARCH tag is enabled the search engine will be\n# implemented using a web server instead of a web client using JavaScript. There\n# are two flavors of web server based searching depending on the EXTERNAL_SEARCH\n# setting. When disabled, doxygen will generate a PHP script for searching and\n# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing\n# and searching needs to be provided by external tools. See the section\n# \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSERVER_BASED_SEARCH    = NO\n\n# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP\n# script for searching. Instead the search results are written to an XML file\n# which needs to be processed by an external indexer. Doxygen will invoke an\n# external search engine pointed to by the SEARCHENGINE_URL option to obtain the\n# search results.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see:\n# https://xapian.org/).\n#\n# See the section \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH        = NO\n\n# The SEARCHENGINE_URL should point to a search engine hosted by a web server\n# which will return the search results when EXTERNAL_SEARCH is enabled.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see:\n# https://xapian.org/). See the section \"External Indexing and Searching\" for\n# details.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHENGINE_URL       =\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed\n# search data is written to a file for indexing by an external tool. With the\n# SEARCHDATA_FILE tag the name of this file can be specified.\n# The default file is: searchdata.xml.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHDATA_FILE        = searchdata.xml\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the\n# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is\n# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple\n# projects and redirect the results back to the right project.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH_ID     =\n\n# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen\n# projects other than the one defined by this configuration file, but that are\n# all added to the same external search index. Each project needs to have a\n# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of\n# to a relative location where the documentation can be found. The format is:\n# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTRA_SEARCH_MAPPINGS  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the LaTeX output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.\n# The default value is: YES.\n\nGENERATE_LATEX         = NO\n\n# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_OUTPUT           = latex\n\n# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be\n# invoked.\n#\n# Note that when not enabling USE_PDFLATEX the default is latex when enabling\n# USE_PDFLATEX the default is pdflatex and when in the later case latex is\n# chosen this is overwritten by pdflatex. For specific output languages the\n# default can have been set differently, this depends on the implementation of\n# the output language.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_CMD_NAME         =\n\n# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate\n# index for LaTeX.\n# Note: This tag is used in the Makefile / make.bat.\n# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file\n# (.tex).\n# The default file is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nMAKEINDEX_CMD_NAME     = makeindex\n\n# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to\n# generate index for LaTeX. In case there is no backslash (\\) as first character\n# it will be automatically added in the LaTeX code.\n# Note: This tag is used in the generated output file (.tex).\n# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.\n# The default value is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_MAKEINDEX_CMD    = makeindex\n\n# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nCOMPACT_LATEX          = NO\n\n# The PAPER_TYPE tag can be used to set the paper type that is used by the\n# printer.\n# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x\n# 14 inches) and executive (7.25 x 10.5 inches).\n# The default value is: a4.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPAPER_TYPE             = a4\n\n# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names\n# that should be included in the LaTeX output. The package can be specified just\n# by its name or with the correct syntax as to be used with the LaTeX\n# \\usepackage command. To get the times font for instance you can specify :\n# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}\n# To use the option intlimits with the amsmath package you can specify:\n# EXTRA_PACKAGES=[intlimits]{amsmath}\n# If left blank no extra packages will be included.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nEXTRA_PACKAGES         =\n\n# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the\n# generated LaTeX document. The header should contain everything until the first\n# chapter. If it is left blank doxygen will generate a standard header. See\n# section \"Doxygen usage\" for information on how to let doxygen write the\n# default header to a separate file.\n#\n# Note: Only use a user-defined header if you know what you are doing! The\n# following commands have a special meaning inside the header: $title,\n# $datetime, $date, $doxygenversion, $projectname, $projectnumber,\n# $projectbrief, $projectlogo. Doxygen will replace $title with the empty\n# string, for the replacement values of the other commands the user is referred\n# to HTML_HEADER.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HEADER           =\n\n# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the\n# generated LaTeX document. The footer should contain everything after the last\n# chapter. If it is left blank doxygen will generate a standard footer. See\n# LATEX_HEADER for more information on how to generate a default footer and what\n# special commands can be used inside the footer.\n#\n# Note: Only use a user-defined footer if you know what you are doing!\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_FOOTER           =\n\n# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# LaTeX style sheets that are included after the standard style sheets created\n# by doxygen. Using this option one can overrule certain style aspects. Doxygen\n# will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list).\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_STYLESHEET =\n\n# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the LATEX_OUTPUT output\n# directory. Note that the files will be copied as-is; there are no commands or\n# markers available.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_FILES      =\n\n# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is\n# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will\n# contain links (just like the HTML output) instead of page references. This\n# makes the output suitable for online browsing using a PDF viewer.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPDF_HYPERLINKS         = YES\n\n# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as\n# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX\n# files. Set this option to YES, to get a higher quality PDF documentation.\n#\n# See also section LATEX_CMD_NAME for selecting the engine.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nUSE_PDFLATEX           = YES\n\n# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode\n# command to the generated LaTeX files. This will instruct LaTeX to keep running\n# if errors occur, instead of asking the user for help. This option is also used\n# when generating formulas in HTML.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BATCHMODE        = NO\n\n# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the\n# index chapters (such as File Index, Compound Index, etc.) in the output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HIDE_INDICES     = NO\n\n# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source\n# code with syntax highlighting in the LaTeX output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_SOURCE_CODE      = NO\n\n# The LATEX_BIB_STYLE tag can be used to specify the style to use for the\n# bibliography, e.g. plainnat, or ieeetr. See\n# https://en.wikipedia.org/wiki/BibTeX and \\cite for more info.\n# The default value is: plain.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BIB_STYLE        = plain\n\n# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated\n# page will contain the date and time when the page was generated. Setting this\n# to NO can help when comparing the output of multiple runs.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_TIMESTAMP        = NO\n\n# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)\n# path from which the emoji images will be read. If a relative path is entered,\n# it will be relative to the LATEX_OUTPUT directory. If left blank the\n# LATEX_OUTPUT directory will be used.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EMOJI_DIRECTORY  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the RTF output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The\n# RTF output is optimized for Word 97 and may not look too pretty with other RTF\n# readers/editors.\n# The default value is: NO.\n\nGENERATE_RTF           = NO\n\n# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: rtf.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_OUTPUT             = rtf\n\n# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nCOMPACT_RTF            = NO\n\n# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will\n# contain hyperlink fields. The RTF file will contain links (just like the HTML\n# output) instead of page references. This makes the output suitable for online\n# browsing using Word or some other Word compatible readers that support those\n# fields.\n#\n# Note: WordPad (write) and others do not support links.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_HYPERLINKS         = NO\n\n# Load stylesheet definitions from file. Syntax is similar to doxygen's\n# configuration file, i.e. a series of assignments. You only have to provide\n# replacements, missing definitions are set to their default value.\n#\n# See also section \"Doxygen usage\" for information on how to generate the\n# default style sheet that doxygen normally uses.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_STYLESHEET_FILE    =\n\n# Set optional variables used in the generation of an RTF document. Syntax is\n# similar to doxygen's configuration file. A template extensions file can be\n# generated using doxygen -e rtf extensionFile.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_EXTENSIONS_FILE    =\n\n# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code\n# with syntax highlighting in the RTF output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_SOURCE_CODE        = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the man page output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for\n# classes and files.\n# The default value is: NO.\n\nGENERATE_MAN           = NO\n\n# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it. A directory man3 will be created inside the directory specified by\n# MAN_OUTPUT.\n# The default directory is: man.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_OUTPUT             = man\n\n# The MAN_EXTENSION tag determines the extension that is added to the generated\n# man pages. In case the manual section does not start with a number, the number\n# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is\n# optional.\n# The default value is: .3.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_EXTENSION          = .3\n\n# The MAN_SUBDIR tag determines the name of the directory created within\n# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by\n# MAN_EXTENSION with the initial . removed.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_SUBDIR             =\n\n# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it\n# will generate one additional man file for each entity documented in the real\n# man page(s). These additional files only source the real man page, but without\n# them the man command would be unable to find the correct page.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_LINKS              = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the XML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that\n# captures the structure of the code including all documentation.\n# The default value is: NO.\n\nGENERATE_XML           = NO\n\n# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: xml.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_OUTPUT             = xml\n\n# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program\n# listings (including syntax highlighting and cross-referencing information) to\n# the XML output. Note that enabling this will significantly increase the size\n# of the XML output.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_PROGRAMLISTING     = YES\n\n# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include\n# namespace members in file scope as well, matching the HTML output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_NS_MEMB_FILE_SCOPE = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the DOCBOOK output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files\n# that can be used to generate PDF.\n# The default value is: NO.\n\nGENERATE_DOCBOOK       = NO\n\n# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.\n# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in\n# front of it.\n# The default directory is: docbook.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_OUTPUT         = docbook\n\n# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the\n# program listings (including syntax highlighting and cross-referencing\n# information) to the DOCBOOK output. Note that enabling this will significantly\n# increase the size of the DOCBOOK output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_PROGRAMLISTING = NO\n\n#---------------------------------------------------------------------------\n# Configuration options for the AutoGen Definitions output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an\n# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures\n# the structure of the code including all documentation. Note that this feature\n# is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_AUTOGEN_DEF   = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the Perl module output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module\n# file that captures the structure of the code including all documentation.\n#\n# Note that this feature is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_PERLMOD       = NO\n\n# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary\n# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI\n# output from the Perl module output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_LATEX          = NO\n\n# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely\n# formatted so it can be parsed by a human reader. This is useful if you want to\n# understand what is going on. On the other hand, if this tag is set to NO, the\n# size of the Perl module output will be much smaller and Perl will parse it\n# just the same.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_PRETTY         = YES\n\n# The names of the make variables in the generated doxyrules.make file are\n# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful\n# so different doxyrules.make files included by the same Makefile don't\n# overwrite each other's variables.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_MAKEVAR_PREFIX =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the preprocessor\n#---------------------------------------------------------------------------\n\n# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all\n# C-preprocessor directives found in the sources and include files.\n# The default value is: YES.\n\nENABLE_PREPROCESSING   = YES\n\n# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names\n# in the source code. If set to NO, only conditional compilation will be\n# performed. Macro expansion can be done in a controlled way by setting\n# EXPAND_ONLY_PREDEF to YES.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nMACRO_EXPANSION        = YES\n\n# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then\n# the macro expansion is limited to the macros specified with the PREDEFINED and\n# EXPAND_AS_DEFINED tags.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_ONLY_PREDEF     = NO\n\n# If the SEARCH_INCLUDES tag is set to YES, the include files in the\n# INCLUDE_PATH will be searched if a #include is found.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSEARCH_INCLUDES        = YES\n\n# The INCLUDE_PATH tag can be used to specify one or more directories that\n# contain include files that are not input files but should be processed by the\n# preprocessor.\n# This tag requires that the tag SEARCH_INCLUDES is set to YES.\n\nINCLUDE_PATH           =\n\n# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard\n# patterns (like *.h and *.hpp) to filter out the header-files in the\n# directories. If left blank, the patterns specified with FILE_PATTERNS will be\n# used.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nINCLUDE_FILE_PATTERNS  =\n\n# The PREDEFINED tag can be used to specify one or more macro names that are\n# defined before the preprocessor is started (similar to the -D option of e.g.\n# gcc). The argument of the tag is a list of macros of the form: name or\n# name=definition (no spaces). If the definition and the \"=\" are omitted, \"=1\"\n# is assumed. To prevent a macro definition from being undefined via #undef or\n# recursively expanded use the := operator instead of the = operator.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nPREDEFINED             =\n\n# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this\n# tag can be used to specify a list of macro names that should be expanded. The\n# macro definition that is found in the sources will be used. Use the PREDEFINED\n# tag if you want to use a different macro definition that overrules the\n# definition found in the source code.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_AS_DEFINED      =\n\n# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will\n# remove all references to function-like macros that are alone on a line, have\n# an all uppercase name, and do not end with a semicolon. Such function macros\n# are typically used for boiler-plate code, and will confuse the parser if not\n# removed.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSKIP_FUNCTION_MACROS   = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to external references\n#---------------------------------------------------------------------------\n\n# The TAGFILES tag can be used to specify one or more tag files. For each tag\n# file the location of the external documentation should be added. The format of\n# a tag file without this location is as follows:\n# TAGFILES = file1 file2 ...\n# Adding location for the tag files is done as follows:\n# TAGFILES = file1=loc1 \"file2 = loc2\" ...\n# where loc1 and loc2 can be relative or absolute paths or URLs. See the\n# section \"Linking to external documentation\" for more information about the use\n# of tag files.\n# Note: Each tag file must have a unique name (where the name does NOT include\n# the path). If a tag file is not located in the directory in which doxygen is\n# run, you must also specify the path to the tagfile here.\n\nTAGFILES               =\n\n# When a file name is specified after GENERATE_TAGFILE, doxygen will create a\n# tag file that is based on the input files it reads. See section \"Linking to\n# external documentation\" for more information about the usage of tag files.\n\nGENERATE_TAGFILE       =\n\n# If the ALLEXTERNALS tag is set to YES, all external class will be listed in\n# the class index. If set to NO, only the inherited external classes will be\n# listed.\n# The default value is: NO.\n\nALLEXTERNALS           = NO\n\n# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed\n# in the modules index. If set to NO, only the current project's groups will be\n# listed.\n# The default value is: YES.\n\nEXTERNAL_GROUPS        = YES\n\n# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in\n# the related pages index. If set to NO, only the current project's pages will\n# be listed.\n# The default value is: YES.\n\nEXTERNAL_PAGES         = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the dot tool\n#---------------------------------------------------------------------------\n\n# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram\n# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to\n# NO turns the diagrams off. Note that this option also works with HAVE_DOT\n# disabled, but it is recommended to install and use dot, since it yields more\n# powerful graphs.\n# The default value is: YES.\n\nCLASS_DIAGRAMS         = YES\n\n# You can include diagrams made with dia in doxygen documentation. Doxygen will\n# then run dia to produce the diagram and insert it in the documentation. The\n# DIA_PATH tag allows you to specify the directory where the dia binary resides.\n# If left empty dia is assumed to be found in the default search path.\n\nDIA_PATH               =\n\n# If set to YES the inheritance and collaboration graphs will hide inheritance\n# and usage relations if the target is undocumented or is not a class.\n# The default value is: YES.\n\nHIDE_UNDOC_RELATIONS   = YES\n\n# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is\n# available from the path. This tool is part of Graphviz (see:\n# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent\n# Bell Labs. The other options in this section have no effect if this option is\n# set to NO\n# The default value is: YES.\n\nHAVE_DOT               = YES\n\n# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed\n# to run in parallel. When set to 0 doxygen will base this on the number of\n# processors available in the system. You can set it explicitly to a value\n# larger than 0 to get control over the balance between CPU load and processing\n# speed.\n# Minimum value: 0, maximum value: 32, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_NUM_THREADS        = 0\n\n# When you want a differently looking font in the dot files that doxygen\n# generates you can specify the font name using DOT_FONTNAME. You need to make\n# sure dot is able to find the font, which can be done by putting it in a\n# standard location or by setting the DOTFONTPATH environment variable or by\n# setting DOT_FONTPATH to the directory containing the font.\n# The default value is: Helvetica.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTNAME           = Helvetica\n\n# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of\n# dot graphs.\n# Minimum value: 4, maximum value: 24, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTSIZE           = 10\n\n# By default doxygen will tell dot to use the default font as specified with\n# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set\n# the path where dot can find it using this tag.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTPATH           =\n\n# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for\n# each documented class showing the direct and indirect inheritance relations.\n# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCLASS_GRAPH            = YES\n\n# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a\n# graph for each documented class showing the direct and indirect implementation\n# dependencies (inheritance, containment, and class references variables) of the\n# class with other documented classes.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCOLLABORATION_GRAPH    = YES\n\n# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for\n# groups, showing the direct groups dependencies.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGROUP_GRAPHS           = YES\n\n# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and\n# collaboration diagrams in a style similar to the OMG's Unified Modeling\n# Language.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LOOK               = NO\n\n# If the UML_LOOK tag is enabled, the fields and methods are shown inside the\n# class node. If there are many fields or methods and many nodes the graph may\n# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the\n# number of items for each type to make the size more manageable. Set this to 0\n# for no limit. Note that the threshold may be exceeded by 50% before the limit\n# is enforced. So when you set the threshold to 10, up to 15 fields may appear,\n# but if the number exceeds 15, the total amount of fields shown is limited to\n# 10.\n# Minimum value: 0, maximum value: 100, default value: 10.\n# This tag requires that the tag UML_LOOK is set to YES.\n\nUML_LIMIT_NUM_FIELDS   = 10\n\n# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and\n# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS\n# tag is set to YES, doxygen will add type and arguments for attributes and\n# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen\n# will not generate fields with class member information in the UML graphs. The\n# class diagrams will look similar to the default class diagrams but using UML\n# notation for the relationships.\n# Possible values are: NO, YES and NONE.\n# The default value is: NO.\n# This tag requires that the tag UML_LOOK is set to YES.\n\nDOT_UML_DETAILS        = NO\n\n# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters\n# to display on a single line. If the actual line length exceeds this threshold\n# significantly it will wrapped across multiple lines. Some heuristics are apply\n# to avoid ugly line breaks.\n# Minimum value: 0, maximum value: 1000, default value: 17.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_WRAP_THRESHOLD     = 17\n\n# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and\n# collaboration graphs will show the relations between templates and their\n# instances.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nTEMPLATE_RELATIONS     = NO\n\n# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to\n# YES then doxygen will generate a graph for each documented file showing the\n# direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDE_GRAPH          = YES\n\n# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are\n# set to YES then doxygen will generate a graph for each documented file showing\n# the direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDED_BY_GRAPH      = YES\n\n# If the CALL_GRAPH tag is set to YES then doxygen will generate a call\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable call graphs for selected\n# functions only using the \\callgraph command. Disabling a call graph can be\n# accomplished by means of the command \\hidecallgraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALL_GRAPH             = NO\n\n# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable caller graphs for selected\n# functions only using the \\callergraph command. Disabling a caller graph can be\n# accomplished by means of the command \\hidecallergraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALLER_GRAPH           = NO\n\n# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical\n# hierarchy of all classes instead of a textual one.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGRAPHICAL_HIERARCHY    = YES\n\n# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the\n# dependencies a directory has on other directories in a graphical way. The\n# dependency relations are determined by the #include relations between the\n# files in the directories.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDIRECTORY_GRAPH        = YES\n\n# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images\n# generated by dot. For an explanation of the image formats see the section\n# output formats in the documentation of the dot tool (Graphviz (see:\n# http://www.graphviz.org/)).\n# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order\n# to make the SVG files visible in IE 9+ (other browsers do not have this\n# requirement).\n# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,\n# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,\n# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo,\n# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and\n# png:gdiplus:gdiplus.\n# The default value is: png.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_IMAGE_FORMAT       = png\n\n# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to\n# enable generation of interactive SVG images that allow zooming and panning.\n#\n# Note that this requires a modern browser other than Internet Explorer. Tested\n# and working are Firefox, Chrome, Safari, and Opera.\n# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make\n# the SVG files visible. Older versions of IE do not have SVG support.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINTERACTIVE_SVG        = NO\n\n# The DOT_PATH tag can be used to specify the path where the dot tool can be\n# found. If left blank, it is assumed the dot tool can be found in the path.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_PATH               =\n\n# The DOTFILE_DIRS tag can be used to specify one or more directories that\n# contain dot files that are included in the documentation (see the \\dotfile\n# command).\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOTFILE_DIRS           =\n\n# The MSCFILE_DIRS tag can be used to specify one or more directories that\n# contain msc files that are included in the documentation (see the \\mscfile\n# command).\n\nMSCFILE_DIRS           =\n\n# The DIAFILE_DIRS tag can be used to specify one or more directories that\n# contain dia files that are included in the documentation (see the \\diafile\n# command).\n\nDIAFILE_DIRS           =\n\n# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the\n# path where java can find the plantuml.jar file. If left blank, it is assumed\n# PlantUML is not used or called during a preprocessing step. Doxygen will\n# generate a warning when it encounters a \\startuml command in this case and\n# will not generate output for the diagram.\n\nPLANTUML_JAR_PATH      =\n\n# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a\n# configuration file for plantuml.\n\nPLANTUML_CFG_FILE      =\n\n# When using plantuml, the specified paths are searched for files specified by\n# the !include statement in a plantuml block.\n\nPLANTUML_INCLUDE_PATH  =\n\n# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes\n# that will be shown in the graph. If the number of nodes in a graph becomes\n# larger than this value, doxygen will truncate the graph, which is visualized\n# by representing a node as a red box. Note that doxygen if the number of direct\n# children of the root node in a graph is already larger than\n# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that\n# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.\n# Minimum value: 0, maximum value: 10000, default value: 50.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_GRAPH_MAX_NODES    = 50\n\n# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs\n# generated by dot. A depth value of 3 means that only nodes reachable from the\n# root by following a path via at most 3 edges will be shown. Nodes that lay\n# further from the root node will be omitted. Note that setting this option to 1\n# or 2 may greatly reduce the computation time needed for large code bases. Also\n# note that the size of a graph can be further restricted by\n# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.\n# Minimum value: 0, maximum value: 1000, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nMAX_DOT_GRAPH_DEPTH    = 0\n\n# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent\n# background. This is disabled by default, because dot on Windows does not seem\n# to support this out of the box.\n#\n# Warning: Depending on the platform used, enabling this option may lead to\n# badly anti-aliased labels on the edges of a graph (i.e. they become hard to\n# read).\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_TRANSPARENT        = NO\n\n# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output\n# files in one run (i.e. multiple -o and -T options on the command line). This\n# makes dot run faster, but since only newer versions of dot (>1.8.10) support\n# this, this feature is disabled by default.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_MULTI_TARGETS      = NO\n\n# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page\n# explaining the meaning of the various boxes and arrows in the dot generated\n# graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGENERATE_LEGEND        = YES\n\n# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate\n# files that are used to generate the various graphs.\n#\n# Note: This setting is not only used for dot files but also for msc and\n# plantuml temporary files.\n# The default value is: YES.\n\nDOT_CLEANUP            = YES\n"
  },
  {
    "path": "empty/empty",
    "content": "This directory is used as the basis for a trivial BUILD file that just forwards elsewhere. We need\nan empty directory to feed into Bazel's new_local_repository rule. There is probably a better way\nto do this.\n"
  },
  {
    "path": "fuzzilli/BUILD.bazel",
    "content": "exports_files(\n    glob([\n        \"*.capnp\",\n        \"*.js\",\n    ]),\n)\n"
  },
  {
    "path": "fuzzilli/README.md",
    "content": "# Fuzz workerd with Fuzzilli\nThis folder contains the capnp configuration, JavaScript mocks for certain APIs which communicates with a Fuzzilli.\n\n\n## Capnp config and JavaScript\nThe base configuration can be found in `worker.js` and `config.capnp`.\nThe folder contains a `config-full.capnp`` and `worker-full.js` file which imports the to-date fuzzed APIs.\nTo test a certain API import it in the used `.js` file. \n\n## REPRL\n\nThe main execution looks as follows:\n- Fuzzilli starts workerd.\n- Workerd will read in the capnp configuration, pointing to a script that imports the custom API endpoints, e.g. (workerd config, worker.js).\n- Workerd will import the dependencies and call Stdin.reprl\n- Workerd then opens the pipes and a shared memory sending a HELO to Fuzzilli, which responds with a HELO back.\n- Fuzzilli sends the exec command, the length of the script to execute and the script itself.\n- Workerd will then compile and execute the script similar to the eval command in a separate scope\n\n\n## Test if REPRL works\n\n```bash\nbazel test --config=fuzzilli //src/workerd/tests:test-reprl-kj --action_env=CC=/usr/bin/clang-19 --test_timeout=5 --test_output=all\n```\n\nFrom the Fuzzilli directory test if the REPRL interface works by running:\n\n```bash\n# in Fuzzilli folder\nswift run REPRLRun <path-to-workerd> fuzzilli <path-to-capnp-config> --experimental\n```\n\n## Fuzz with corpus\n\n```bash\nswift run -c release FuzzilliCli --inspect=all --profile=workerd <path-to-workerd-root>/bazel-bin/src/workerd/server/workerd --additionalArguments=<path-to-workerd-root>/samples/reprl/config-full.capnp,--experimental --storagePath=fs-new --jobs=30 --importCorpus=<path-to-corpus> --corpusImportMode=full --staticCorpus\n```\n"
  },
  {
    "path": "fuzzilli/analytics-mock.js",
    "content": "function shouldChaos() {\n  return Math.random() < 0.1; // 10% chaos for Analytics\n}\n\nfunction getChaoticAnalyticsResponse() {\n  const chaos = Math.random();\n  if (chaos < 0.3) {\n    // Data ingestion limit exceeded\n    return new Response('Daily quota exceeded', { status: 429 });\n  } else if (chaos < 0.5) {\n    // Invalid data format\n    return new Response('Invalid analytics data format', { status: 400 });\n  } else if (chaos < 0.7) {\n    // Service temporarily down\n    return new Response('Analytics service temporarily unavailable', {\n      status: 503,\n    });\n  } else if (chaos < 0.9) {\n    // Success but with warning\n    return new Response('Data accepted with warnings', { status: 202 });\n  } else {\n    // Unexpected server behavior\n    return new Response('<html><body>Maintenance page</body></html>', {\n      status: 200,\n      headers: { 'Content-Type': 'text/html' },\n    });\n  }\n}\n\nexport default {\n  async fetch(request, env, ctx) {\n    // Random chaos injection\n    if (shouldChaos()) {\n      return getChaoticAnalyticsResponse();\n    }\n\n    // Analytics Engine accepts data points via HTTP POST\n    if (request.method === 'POST') {\n      const body = await request.text();\n      console.log('Analytics data received:', body.slice(0, 100));\n\n      // Sometimes simulate processing delay or partial acceptance\n      if (Math.random() < 0.05) {\n        return new Response('Partial data accepted', { status: 206 });\n      }\n\n      return new Response('', { status: 200 });\n    }\n\n    return new Response('Analytics mock ready', { status: 200 });\n  },\n};\n"
  },
  {
    "path": "fuzzilli/config-full.capnp",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst reprl :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .replServer),\n    (name = \"consumer\", worker = .consumerWorker),\n    # Add test services for Cloudflare API bindings\n    (name = \"test-kv\", worker = .kvMockWorker),\n    (name = \"test-d1-mock\", worker = .d1MockWorker),\n    (name = \"test-r2\", worker = .r2MockWorker),\n    (name = \"test-analytics\", worker = .analyticsMockWorker),\n    (name = \"test-queue\", worker = .queueMockWorker),\n  ],\n\n  # We don't need sockets for REPRL mode as it uses direct file descriptors\n  # sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\nconst replServer :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker-full.js\")\n  ],\n\n  # Add a comprehensive set of bindings for all APIs\n  bindings = [\n    (name = \"CONSUMER\", service = \"consumer\"),\n    (name = \"volatileCache\", memoryCache = (\n      id = \"abc123\",\n      limits = (\n        maxKeys = 100,\n        maxValueSize = 1024,\n        maxTotalValueSize = 102400,\n      ),\n    )),\n    # Cloudflare API bindings for testing\n    (name = \"MY_KV\", kvNamespace = \"test-kv\"),\n    (name = \"MY_D1\", wrapped = (\n      moduleName = \"cloudflare-internal:d1-api\",\n      innerBindings = [(name = \"fetcher\", service = \"test-d1-mock\")],\n    )),\n    (name = \"MY_R2\", r2Bucket = \"test-r2\"),\n    (name = \"ANALYTICS\", analyticsEngine = \"test-analytics\"),\n    (name = \"MY_QUEUE\", queue = \"test-queue\"),\n  ],\n\n  # Latest compatibility date\n  compatibilityDate = \"2025-05-01\",\n\n  # Enable all compatibility flags for maximum API coverage\n  compatibilityFlags = [\n    \"nodejs_compat\",\n    \"experimental\",\n    \"unsafe_module\",\n    \"enable_nodejs_fs_module\",\n    \"html_rewriter_treats_esi_include_as_void_tag\",\n    \"durable_object_rename\",\n    \"service_binding_extra_handlers\",\n    \"expose_global_message_channel\",\n    \"enable_web_file_system\",\n  ]\n);\n\nconst consumerWorker :Workerd.Worker = (\n\tmodules = [\n\t\t( name = \"consumer\", esModule = embed \"worker-consume-request.js\" )\n\t],\n\tcompatibilityDate = \"2024-12-01\"\n);\n\nconst kvMockWorker :Workerd.Worker = (\n\tmodules = [\n\t\t( name = \"kv\", esModule = embed \"kv-mock.js\" )\n\t],\n\tcompatibilityDate = \"2024-12-01\"\n);\n\nconst d1MockWorker :Workerd.Worker = (\n\tmodules = [\n\t\t( name = \"d1\", esModule = embed \"d1-mock.js\" )\n\t],\n\tcompatibilityDate = \"2024-12-01\"\n);\n\nconst r2MockWorker :Workerd.Worker = (\n\tmodules = [\n\t\t( name = \"r2\", esModule = embed \"r2-mock.js\" )\n\t],\n\tcompatibilityDate = \"2024-12-01\"\n);\n\nconst analyticsMockWorker :Workerd.Worker = (\n\tmodules = [\n\t\t( name = \"analytics\", esModule = embed \"analytics-mock.js\" )\n\t],\n\tcompatibilityDate = \"2024-12-01\"\n);\n\nconst queueMockWorker :Workerd.Worker = (\n\tmodules = [\n\t\t( name = \"queue\", esModule = embed \"queue-mock.js\" )\n\t],\n\tcompatibilityDate = \"2024-12-01\"\n);\n"
  },
  {
    "path": "fuzzilli/config.capnp",
    "content": "\nusing Workerd = import \"/workerd/workerd.capnp\";\n\nconst reprl :Workerd.Config = (\n  services = [ (name = \"main\", worker = .replServer) ],\n\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\nconst replServer :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\")\n  ],\n  bindings = [\n    (\n      name = \"secret\",\n      text = \"thisisasecret\"\n    ),\n    ( name = \"CACHE\", memoryCache = (\n      id = \"abc123\",\n      limits = (\n        maxKeys = 10,\n        maxValueSize = 1024,\n        maxTotalValueSize = 1024,\n        ),\n      )\n    )\n  ],\n  compatibilityDate = \"2023-02-28\",\n  compatibilityFlags = [\"nodejs_compat\", \"experimental\", \"unsafe_module\"]\n);\n"
  },
  {
    "path": "fuzzilli/d1-mock.js",
    "content": "// Based on edgeworker D1 mock implementation with chaos\nconst MOCK_USER_ROWS = {\n  1: { user_id: 1, name: 'Albert Ross', home: 'sky', features: 'wingspan' },\n  2: { user_id: 2, name: 'Al Dente', home: 'bowl', features: 'mouthfeel' },\n};\n\nfunction shouldChaos() {\n  return Math.random() < 0.15; // 15% chaos for D1\n}\n\nfunction getChaoticD1Response() {\n  const chaos = Math.random();\n  if (chaos < 0.25) {\n    // Database locked\n    return Response.json(\n      { error: 'database is locked', success: false },\n      { status: 500 }\n    );\n  } else if (chaos < 0.45) {\n    // Connection limit exceeded\n    return Response.json(\n      { error: 'too many connections', success: false },\n      { status: 503 }\n    );\n  } else if (chaos < 0.65) {\n    // Malformed response (missing success field)\n    return Response.json({ results: [], meta: { served_by: 'chaos' } });\n  } else if (chaos < 0.85) {\n    // SQL constraint violation\n    return Response.json(\n      {\n        error: 'UNIQUE constraint failed: users.email',\n        success: false,\n      },\n      { status: 400 }\n    );\n  } else {\n    // Completely wrong JSON structure\n    return Response.json({ chaos: true, random_field: 'unexpected' });\n  }\n}\n\nfunction mockQuery({ sql, params = [] }) {\n  // Occasional chaos even in successful queries\n  if (shouldChaos()) {\n    if (Math.random() < 0.5) {\n      throw new Error('Random SQL execution error');\n    }\n  }\n\n  switch (sql.trim()) {\n    case 'select 1;':\n    case 'SELECT 1':\n      return ok({ 1: 1 });\n    case 'select * from users;':\n      return ok(...Object.values(MOCK_USER_ROWS));\n    case 'select * from users where user_id = ?;':\n      return ok(MOCK_USER_ROWS[params[0]]);\n    default:\n      return ok(); // Empty result for unknown queries\n  }\n}\n\nfunction ok(...results) {\n  return {\n    success: true,\n    results,\n    meta: { duration: Math.random() * 0.1, served_by: 'd1-mock' }, // Random duration\n  };\n}\n\nexport default {\n  async fetch(request, env, ctx) {\n    // Random top-level chaos\n    if (shouldChaos()) {\n      return getChaoticD1Response();\n    }\n\n    try {\n      const { pathname } = new URL(request.url);\n\n      if (\n        request.method === 'POST' &&\n        (pathname.startsWith('/query') || pathname.startsWith('/execute'))\n      ) {\n        const body = await request.json();\n        return Response.json(\n          Array.isArray(body)\n            ? body.map((query) => mockQuery(query))\n            : mockQuery(body)\n        );\n      }\n\n      return new Response('D1 mock ready', { status: 200 });\n    } catch (err) {\n      return Response.json(\n        { error: err.message, success: false },\n        { status: 500 }\n      );\n    }\n  },\n};\n"
  },
  {
    "path": "fuzzilli/kv-mock.js",
    "content": "// Chaos helpers\nfunction shouldChaos() {\n  return Math.random() < 0.12; // 12% chaos\n}\n\nfunction getChaoticResponse() {\n  const chaos = Math.random();\n  if (chaos < 0.3) {\n    // Rate limited\n    return new Response('Rate limited', { status: 429 });\n  } else if (chaos < 0.5) {\n    // Service unavailable\n    return new Response('Service temporarily unavailable', { status: 503 });\n  } else if (chaos < 0.7) {\n    // Malformed JSON\n    return new Response('{\"broken\": json}', {\n      status: 200,\n      headers: { 'Content-Type': 'application/json' },\n    });\n  } else if (chaos < 0.9) {\n    // Timeout simulation (just return very slow)\n    return new Response('Request timeout', { status: 408 });\n  } else {\n    // Completely wrong content type\n    return new Response('<html>This is not JSON</html>', {\n      status: 200,\n      headers: { 'Content-Type': 'text/html' },\n    });\n  }\n}\n\nexport default {\n  async fetch(request, env, ctx) {\n    // Random chaos injection\n    if (shouldChaos()) {\n      return getChaoticResponse();\n    }\n\n    const { pathname } = new URL(request.url);\n    const method = request.method;\n\n    if (method === 'GET' && pathname.startsWith('/values/')) {\n      // GET /values/{key}\n      const key = pathname.slice(8);\n      if (key === 'test-key') {\n        return new Response('test-value', {\n          headers: { 'Content-Type': 'text/plain' },\n        });\n      }\n      return new Response(null, { status: 404 });\n    } else if (method === 'PUT' && pathname.startsWith('/values/')) {\n      // PUT /values/{key}\n      return new Response(null, { status: 200 });\n    } else if (method === 'GET' && pathname === '/keys') {\n      // LIST operation\n      return new Response(\n        JSON.stringify({\n          keys: [{ name: 'test-key' }],\n          list_complete: true,\n        }),\n        {\n          headers: { 'Content-Type': 'application/json' },\n        }\n      );\n    }\n\n    return new Response('KV mock ready', { status: 200 });\n  },\n};\n"
  },
  {
    "path": "fuzzilli/queue-mock.js",
    "content": "function shouldChaos() {\n  return Math.random() < 0.14; // 14% chaos for Queue\n}\n\nfunction getChaoticQueueResponse() {\n  const chaos = Math.random();\n  if (chaos < 0.2) {\n    // Queue is full\n    return new Response('Queue capacity exceeded', { status: 507 });\n  } else if (chaos < 0.4) {\n    // Message too large\n    return new Response('Message size limit exceeded', { status: 413 });\n  } else if (chaos < 0.6) {\n    // Rate limit hit\n    return new Response('Too many messages per second', { status: 429 });\n  } else if (chaos < 0.8) {\n    // Temporary network issue\n    return new Response('Network timeout', { status: 504 });\n  } else {\n    // Partial success (some messages accepted, some rejected)\n    return new Response(\n      JSON.stringify({\n        success: false,\n        failed_messages: Math.floor(Math.random() * 3) + 1,\n      }),\n      {\n        status: 207, // Multi-status\n        headers: { 'Content-Type': 'application/json' },\n      }\n    );\n  }\n}\n\nexport default {\n  async fetch(request, env, ctx) {\n    // Random chaos injection\n    if (shouldChaos()) {\n      return getChaoticQueueResponse();\n    }\n\n    const { pathname } = new URL(request.url);\n\n    if (request.method === 'POST' && pathname === '/message') {\n      // Single message\n      const format = request.headers.get('X-Msg-Fmt') || 'v8';\n      console.log('Queue message received with format:', format);\n\n      // Sometimes reject based on format\n      if (Math.random() < 0.03 && format === 'json') {\n        return new Response('JSON format not supported', { status: 400 });\n      }\n\n      return new Response('', { status: 200 });\n    } else if (request.method === 'POST' && pathname === '/batch') {\n      // Batch messages\n      const body = await request.json();\n      const messageCount = body.messages?.length || 0;\n      console.log('Queue batch received:', messageCount, 'messages');\n\n      // Sometimes simulate partial batch acceptance\n      if (Math.random() < 0.08 && messageCount > 1) {\n        return new Response(\n          JSON.stringify({\n            accepted: Math.floor(messageCount * 0.7),\n            rejected: Math.ceil(messageCount * 0.3),\n            errors: ['Message 2 invalid format', 'Message 4 too large'],\n          }),\n          {\n            status: 207,\n            headers: { 'Content-Type': 'application/json' },\n          }\n        );\n      }\n\n      return new Response('', { status: 200 });\n    }\n\n    return new Response('Queue mock ready', { status: 200 });\n  },\n};\n"
  },
  {
    "path": "fuzzilli/r2-mock.js",
    "content": "function shouldChaos() {\n  return Math.random() < 0.13; // 13% chaos for R2\n}\n\nfunction getChaoticR2Response() {\n  const chaos = Math.random();\n  if (chaos < 0.2) {\n    // Access denied\n    return new Response(\n      '<?xml version=\"1.0\" encoding=\"UTF-8\"?><Error><Code>AccessDenied</Code><Message>Access Denied</Message></Error>',\n      { status: 403, headers: { 'Content-Type': 'application/xml' } }\n    );\n  } else if (chaos < 0.4) {\n    // Object not found\n    return new Response(\n      '<?xml version=\"1.0\" encoding=\"UTF-8\"?><Error><Code>NoSuchKey</Code></Error>',\n      { status: 404, headers: { 'Content-Type': 'application/xml' } }\n    );\n  } else if (chaos < 0.6) {\n    // Bandwidth limit exceeded\n    return new Response('Rate limit exceeded', { status: 429 });\n  } else if (chaos < 0.8) {\n    // Corrupted metadata\n    return new Response('corrupted object content', {\n      headers: {\n        'Content-Type': 'text/plain',\n        'CF-R2-Metadata-Size': 'not-a-number', // Invalid metadata\n        ETag: 'invalid-etag-format',\n      },\n    });\n  } else {\n    // Wrong content type but valid data\n    return new Response(JSON.stringify({ not: 'xml', but: 'json' }), {\n      headers: { 'Content-Type': 'application/xml' }, // Lying about content type\n    });\n  }\n}\n\nexport default {\n  async fetch(request, env, ctx) {\n    // Random chaos injection\n    if (shouldChaos()) {\n      return getChaoticR2Response();\n    }\n\n    const { pathname } = new URL(request.url);\n    const method = request.method;\n\n    if (method === 'GET' && pathname !== '/') {\n      // GET object\n      return new Response('test-object-content', {\n        headers: {\n          'Content-Type': 'text/plain',\n          'CF-R2-Metadata-Size': '18',\n        },\n      });\n    } else if (method === 'PUT') {\n      // PUT object\n      return new Response(\n        JSON.stringify({\n          etag: 'mock-etag-' + Math.random().toString(36).substr(2, 9),\n        }),\n        {\n          headers: { 'Content-Type': 'application/json' },\n        }\n      );\n    } else if (method === 'GET' && pathname === '/') {\n      // LIST objects (sometimes return inconsistent results)\n      const objects = [{ key: 'test-object', metadata: { size: 18 } }];\n      if (Math.random() < 0.1) {\n        // Sometimes add random objects to list\n        objects.push({\n          key: 'mystery-object',\n          metadata: { size: Math.floor(Math.random() * 1000) },\n        });\n      }\n\n      return new Response(\n        JSON.stringify({\n          keys: objects,\n          is_truncated: Math.random() < 0.05, // Sometimes claim truncation\n        }),\n        {\n          headers: { 'Content-Type': 'application/json' },\n        }\n      );\n    }\n\n    return new Response('R2 mock ready', { status: 200 });\n  },\n};\n"
  },
  {
    "path": "fuzzilli/worker-consume-request.js",
    "content": "export default {\n  async fetch(request) {\n    const url = new URL(request.url);\n    const headers = new Headers(request.headers);\n    const userAgent = headers.get('User-Agent');\n    headers.set('Test', 'good');\n    await request.arrayBuffer();\n    return new Response(null);\n  },\n};\n"
  },
  {
    "path": "fuzzilli/worker-full.js",
    "content": "// Enhanced REPRL worker script with all API bindings exposed\nimport { default as Stdin } from 'workerd:stdin';\nimport crypto from 'crypto';\nimport * as fs from 'node:fs';\nimport { Buffer } from 'node:buffer';\nimport {\n  ok,\n  match,\n  rejects,\n  strictEqual,\n  throws,\n  deepStrictEqual,\n  notStrictEqual,\n} from 'node:assert';\nimport { mock } from 'node:test';\nimport { Writable, Readable, Transform } from 'node:stream';\nimport { text } from 'node:stream/consumers';\nimport { pipeline } from 'node:stream/promises';\nimport { StringDecoder } from 'node:string_decoder';\nimport { EventEmitter } from 'node:events';\nimport { env } from 'node:process';\nimport zlib from 'node:zlib';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport {\n  channel,\n  hasSubscribers,\n  subscribe,\n  unsubscribe,\n  tracingChannel,\n} from 'node:diagnostics_channel';\nimport dns from 'node:dns';\nimport path from 'node:path';\n\n// Expose all APIs to the global scope for fuzzing\nexport default {\n  async fetch(request, env, ctx) {\n    return new Response('REPRL mode active');\n  },\n\n  async test(request, env, ctx) {\n    // fuzzilli is already globally available when compiled with WORKERD_FUZZILLI\n    // Expose bindings\n    //globalThis.CONSUMER_FETCH = env.CONSUMER.fetch;\n\n    globalThis.env = env;\n    process = undefined;\n    globalThis.process = undefined;\n\n    // used for scheduler.await\n    globalThis.scheduler = scheduler;\n\n    // zlib\n    globalThis.zlib = zlib;\n\n    // Expose Web Crypto API\n    globalThis.crypto = crypto;\n\n    globalThis.AsyncLocalStorage = AsyncLocalStorage;\n\n    //path\n    globalThis.path = path;\n\n    // dns\n    globalThis.dns = dns;\n\n    // diagnostics\n    globalThis.channel = channel;\n    globalThis.hasSubscribers = hasSubscribers;\n    globalThis.subscribe = subscribe;\n    globalThis.unsubscribe = unsubscribe;\n    globalThis.tracingChannel = tracingChannel;\n\n    // Expose Web Standards\n    globalThis.Response = Response;\n    globalThis.Request = Request;\n    globalThis.Headers = Headers;\n    globalThis.URL = URL;\n    globalThis.URLPattern = URLPattern;\n    globalThis.URLSearchParams = URLSearchParams;\n    globalThis.TextEncoder = TextEncoder;\n    globalThis.TextDecoder = TextDecoder;\n\n    // Expose streams\n    globalThis.ReadableStream = ReadableStream;\n    globalThis.WritableStream = WritableStream;\n    globalThis.TransformStream = TransformStream;\n    globalThis.ByteLengthQueuingStrategy = ByteLengthQueuingStrategy;\n    globalThis.CountQueuingStrategy = CountQueuingStrategy;\n\n    // Expose compression/decompression streams\n    globalThis.CompressionStream = CompressionStream;\n    globalThis.DecompressionStream = DecompressionStream;\n\n    // Expose text encoding/decoding streams\n    globalThis.TextEncoderStream = TextEncoderStream;\n    globalThis.TextDecoderStream = TextDecoderStream;\n\n    // Expose identity/fixed-length streams\n    globalThis.IdentityTransformStream = IdentityTransformStream;\n    globalThis.FixedLengthStream = FixedLengthStream;\n\n    // Expose fetch API\n    globalThis.fetch = fetch;\n\n    // Expose WebSocket and EventSource APIs\n    globalThis.WebSocket = WebSocket;\n    globalThis.EventSource = EventSource;\n\n    // Expose Encoding APIs\n    globalThis.atob = atob;\n    globalThis.btoa = btoa;\n\n    // Expose Blob and FormData APIs\n    globalThis.Blob = Blob;\n    globalThis.File = File;\n    globalThis.FormData = FormData;\n\n    // Expose HTML Rewriter\n    globalThis.HTMLRewriter = HTMLRewriter;\n\n    // Expose MessageChannel APIs\n    globalThis.MessageChannel = MessageChannel;\n    globalThis.MessagePort = MessagePort;\n\n    // Expose AbortController for fetch\n    globalThis.AbortController = AbortController;\n    globalThis.AbortSignal = AbortSignal;\n\n    // Expose Node.js APIs\n    globalThis.fs = fs;\n    globalThis.Buffer = Buffer;\n    globalThis.Writable = Writable;\n    globalThis.Readable = Readable;\n    globalThis.Transform = Transform;\n    globalThis.EventEmitter = EventEmitter;\n    globalThis.StringDecoder = StringDecoder;\n\n    // Expose testing APIs\n    globalThis.ok = ok;\n    globalThis.match = match;\n    globalThis.rejects = rejects;\n    globalThis.throws = throws;\n    globalThis.strictEqual = strictEqual;\n    globalThis.deepStrictEqual = deepStrictEqual;\n    globalThis.notStrictEqual = notStrictEqual;\n    globalThis.mock = mock;\n\n    // Mock Cloudflare APIs for fuzzing\n    globalThis.MOCK_KV = {\n      get: (key, options) =>\n        Promise.resolve(\n          options?.type === 'json' ? { test: 'value' } : 'test-value'\n        ),\n      put: (key, value, options) => Promise.resolve(),\n      delete: (key) => Promise.resolve(),\n      list: (options) =>\n        Promise.resolve({ keys: [{ name: 'key1' }], list_complete: true }),\n    };\n\n    globalThis.MOCK_D1 = {\n      prepare: (query) => ({\n        bind: (...params) => ({\n          first: () => Promise.resolve({ id: 1, name: 'test' }),\n          all: () => Promise.resolve({ results: [{ id: 1 }], meta: {} }),\n          run: () => Promise.resolve({ success: true, meta: { changes: 1 } }),\n        }),\n      }),\n      batch: (stmts) => Promise.resolve(stmts.map(() => ({ success: true }))),\n    };\n\n    globalThis.MOCK_R2 = {\n      get: (key) =>\n        Promise.resolve({\n          body: new ReadableStream(),\n          text: () => Promise.resolve('content'),\n          json: () => Promise.resolve({ data: 'test' }),\n          arrayBuffer: () => Promise.resolve(new ArrayBuffer(8)),\n        }),\n      put: (key, value, options) => Promise.resolve({ etag: 'test-etag' }),\n      delete: (keys) =>\n        Promise.resolve({\n          deleted: Array.isArray(keys)\n            ? keys.map((k) => ({ key: k }))\n            : [{ key: keys }],\n        }),\n      list: (options) =>\n        Promise.resolve({ objects: [{ key: 'test.txt', size: 100 }] }),\n    };\n\n    // Enter the REPRL loop\n    Stdin.reprl();\n  },\n};\n"
  },
  {
    "path": "fuzzilli/worker.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { default as Stdin } from 'workerd:stdin';\nimport { default as util } from 'node:util';\n\nexport default {\n  async test() {\n    Stdin.reprl();\n  },\n};\n"
  },
  {
    "path": "githooks/README",
    "content": "This directory should be configured as git hooks in the development environment.\nworkspace-status.sh checks for this during each bazel build.\n"
  },
  {
    "path": "githooks/pre-commit",
    "content": "#!/bin/bash\n\ncd \"$(git rev-parse --show-toplevel)\"\nDISALLOWED_PATTERN='KJ_DBG'\n\nif git diff-index -G\"$DISALLOWED_PATTERN\" --cached HEAD --diff-filter=AM -U0 | grep -i --color -E \"$DISALLOWED_PATTERN\"\nthen\n  echo -e \"\\nERROR, KJ_DBG is not allowed in checked-in source code:\"\n  echo -e \"  see https://github.com/capnproto/capnproto/blob/v2/c%2B%2B/src/kj/debug.h#L42\\n\"\n  echo -e \"To commit anyway, use --no-verify\\n\"\n  exit 1\nfi\n\nclang_format_check() {\n  source \"$(dirname -- $BASH_SOURCE)/../tools/unix/find-python3.sh\"\n  PYTHON_PATH=$(get_python3)\n  if [[ -z \"$PYTHON_PATH\" ]]; then\n    echo\n    echo \"python3 is required for formatting and was not found\"\n    echo\n    echo \"ERROR: you must either install python3 and try pushing again or run `git push` with `--no-verify`\"\n    return 1\n  fi\n\n  set +e\n  $PYTHON_PATH \"$(dirname -- $BASH_SOURCE)/../tools/cross/format.py\" --check git --staged\n  EXIT_CODE=$?\n  set -e\n  case $EXIT_CODE in\n    0)\n      # No lint.\n      return 0\n      ;;\n    1)\n      echo\n      echo \"ERROR: changes staged for commit have lint. Pass '--no-verify' or '-n' to skip.\"\n      echo\n      echo \"To fix lint:\"\n      echo \"  just format\"\n      echo\n      return 1\n      ;;\n    2)\n      echo\n      echo \"ERROR: failed to run format.py, Pass '--no-verify' or '-n' to skip.\"\n      echo\n      return 1\n      ;;\n  esac\n}\n\nclang_format_check\n"
  },
  {
    "path": "githooks/pre-push",
    "content": "#!/usr/bin/env bash\n\nset -euo pipefail\n\n\nsource \"$(dirname -- $BASH_SOURCE)/../tools/unix/find-python3.sh\"\nPYTHON_PATH=$(get_python3)\nif [[ -z \"$PYTHON_PATH\" ]]; then\n  echo\n  echo \"python3 is required for formatting and was not found\"\n  echo\n  echo \"ERROR: you must either install python3 and try pushing again or run `git push` with `--no-verify`\"\n  exit 1\nfi\n\nwhile read LOCAL_REF LOCAL_SHA REMOTE_REF REMOTE_SHA\ndo\n  git fetch origin main &>/dev/null\n  # Check all local changes, not present in origin/main, for lint.\n  set +e\n  $PYTHON_PATH \"$(dirname -- $BASH_SOURCE)/../tools/cross/format.py\" --check git --source $LOCAL_SHA --target origin/main\n  EXIT_CODE=$?\n  set -e\n  case $EXIT_CODE in\n    0)\n      # No lint.\n      ;;\n    1)\n      echo\n      echo \"ERROR: changes in $LOCAL_REF have lint which may fail CI.\"\n      echo\n      echo \"To fix lint:\"\n      echo \"  just format\"\n      echo\n      exit 1\n      ;;\n    2)\n      echo\n      echo \"ERROR: failed to run format.py, Pass '--no-verify' or '-n' to skip.\"\n      echo\n      exit 1\n      ;;\n  esac\ndone\n"
  },
  {
    "path": "images/BUILD.bazel",
    "content": "load(\"@rules_multirun//:defs.bzl\", \"command\", \"multirun\")\n\nIMAGES = {\n    \"container-client-test\": \"//images/container-client-test:load\",\n    \"proxy-everything\": \"//images/container-client-test:load-proxy-everything\",\n}\n\n[\n    command(\n        name = name,\n        command = target,\n    )\n    for (name, target) in IMAGES.items()\n]\n\nmultirun(\n    name = \"load_all\",\n    commands = IMAGES.keys(),\n)\n"
  },
  {
    "path": "images/container-client-test/BUILD.bazel",
    "content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_binary\", \"js_image_layer\")\nload(\"@npm//:defs.bzl\", \"npm_link_all_packages\")\nload(\"@rules_oci//oci:defs.bzl\", \"oci_image\", \"oci_load\")\n\nnpm_link_all_packages(name = \"node_modules\")\n\njs_binary(\n    name = \"app\",\n    data = [\":node_modules/ws\"],\n    entry_point = \"app.js\",\n)\n\njs_image_layer(\n    name = \"layers\",\n    binary = \":app\",\n    root = \"/\",\n)\n\noci_image(\n    name = \"image\",\n    base = \"@node_25_slim\",\n    cmd = [\"/{}/app\".format(package_name())],\n    tags = [\n        \"no-remote\",\n        \"requires-container-engine\",\n    ],\n    target_compatible_with = [\n        \"@platforms//os:linux\",\n    ],\n    tars = [\n        \":layers\",\n    ],\n    workdir = \"/{}/app.runfiles/_main\".format(package_name()),\n)\n\noci_load(\n    name = \"load\",\n    image = \":image\",\n    repo_tags = [\"cloudflare/workerd/container-client-test:latest\"],\n    visibility = [\"//visibility:public\"],\n)\n\noci_load(\n    name = \"load-proxy-everything\",\n    image = \"@proxy_everything\",\n    repo_tags = [\"cloudflare/proxy-everything:main\"],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "images/container-client-test/app.js",
    "content": "const { createServer } = require('http');\n\nconst webSocketEnabled = process.env.WS_ENABLED === 'true';\nconst wsProxyTarget = process.env.WS_PROXY_TARGET || null;\n\nconst server = createServer(function (req, res) {\n  if (req.url === '/ws') {\n    return;\n  }\n\n  if (req.url === '/pid-namespace') {\n    // Return the PID namespace inode. When running in an isolated PID namespace,\n    // this will differ from the host's PID namespace. We return the inode so the\n    // test can verify isolation by comparing against a known value or checking\n    // that PID 1 in this namespace is NOT the host's init process.\n    const fs = require('fs');\n    try {\n      // Read /proc/1/cmdline to see what process is PID 1 in this namespace.\n      // In an isolated namespace, PID 1 will be our container's init process.\n      // In host namespace, PID 1 will be the host's init (e.g., systemd, launchd).\n      const init = fs.readFileSync('/proc/1/cmdline', 'utf8');\n      res.writeHead(200, { 'Content-Type': 'application/json' });\n      res.write(\n        JSON.stringify({\n          pid: process.pid,\n          ppid: process.ppid,\n          init: init.replace(/\\0/g, ' ').trim(),\n        })\n      );\n      res.end();\n    } catch (err) {\n      res.writeHead(500, { 'Content-Type': 'text/plain' });\n      res.write(`Error reading /proc/1/cmdline: ${err.message}`);\n      res.end();\n    }\n\n    return;\n  }\n\n  if (req.url === '/intercept') {\n    const targetHost = req.headers['x-host'] || '11.0.0.1';\n    fetch(`http://${targetHost}`)\n      .then((result) => result.text())\n      .then((body) => {\n        res.writeHead(200);\n        res.write(body);\n        res.end();\n      })\n      .catch((err) => {\n        res.writeHead(500);\n        res.write(`${targetHost} ${err.message}`);\n        res.end();\n      });\n\n    return;\n  }\n\n  res.writeHead(200, { 'Content-Type': 'text/plain' });\n  res.write('Hello World!');\n  res.end();\n});\n\nif (webSocketEnabled) {\n  const WebSocket = require('ws');\n  const wss = new WebSocket.Server({ server, path: '/ws' });\n\n  wss.on('connection', function (clientWs) {\n    if (wsProxyTarget) {\n      const targetWs = new WebSocket(`ws://${wsProxyTarget}/ws`);\n      const ready = new Promise(function (resolve) {\n        targetWs.on('open', resolve);\n      });\n\n      targetWs.on('message', (data) => clientWs.send(data));\n      clientWs.on('message', async function (data) {\n        await ready;\n        targetWs.send(data);\n      });\n\n      clientWs.on('close', targetWs.close);\n      targetWs.on('close', clientWs.close);\n    } else {\n      clientWs.on('message', function (data) {\n        clientWs.send('Echo: ' + data.toString());\n      });\n    }\n  });\n}\n\nserver.listen(8080, function () {\n  console.log('Server listening on port 8080');\n  if (webSocketEnabled) {\n    console.log('WebSocket support enabled');\n  }\n});\n"
  },
  {
    "path": "images/container-client-test/package.json",
    "content": "{\n  \"name\": \"container-client-test\",\n  \"private\": true,\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"scripts\": {},\n  \"devDependencies\": {\n    \"ws\": \"^8.19.0\"\n  }\n}\n"
  },
  {
    "path": "justfile",
    "content": "alias b := build\nalias t := test\nalias f := format\nalias st := stream-test\nalias c := coverage\nalias w := watch\n\ndefault:\n  @just --list\n\npwd := `pwd`\n\nprepare:\n  @if [ \"{{os()}}\" = \"macos\" ]; then just prepare-macos; elif [ \"{{os()}}\" = \"linux\" ]; then just prepare-ubuntu; else echo \"Unsupported OS: {{os()}}\"; exit 1; fi\n  cargo install gen-compile-commands watchexec-cli\n  just create-external\n  just compile-commands\n  just prepare-rust\n\nprepare-rust:\n  rustup install 1.91.0\n  rustup component add rust-analyzer --toolchain 1.91.0\n\nprepare-ubuntu:\n  sudo apt-get install -y --no-install-recommends libc++abi1-19 libc++1-19 libc++-19-dev lld-19 bazelisk python3 lcov fd-find\n\nprepare-macos:\n  brew install --quiet bazelisk python3 lcov fd\n\ncompile-commands:\n  rm -f compile_commands.json\n  gen-compile-commands --root {{pwd}} --compile-flags compile_flags.txt --out compile_commands.json --src-dir {{pwd}}/src\n\nclean:\n  rm -f compile_commands.json\n\nbuild *args=\"//...\":\n  bazel build {{args}}\n\n# example: just watch run -- serve $(pwd)/samples/helloworld/config.capnp\nrun *args=\"-- --help\":\n  bazel run //src/workerd/server:workerd -- {{args}} --watch --verbose --experimental\n\nbuild-asan *args=\"//...\":\n  just build {{args}} --config=asan\n\ntest *args=\"//...\":\n  bazel test {{args}}\n\ntest-asan *args=\"//...\":\n  just test {{args}} --config=asan\n\n# e.g. just stream-test //src/cloudflare:cloudflare.capnp@eslint\nstream-test *args:\n  bazel test {{args}} --test_output=streamed --nocache_test_results --test_tag_filters= --test_size_filters=\n\n# e.g. just node-test zlib\nnode-test test_name *args:\n  just stream-test //src/workerd/api/node/tests:{{test_name}}-nodejs-test@ {{args}}\n\n# e.g. just wpt-test urlpattern\nwpt-test test_name:\n  just stream-test //src/wpt:{{test_name}}\n\nlldb-wpt-test test_name: build\n  cd bazel-bin/src/wpt/{{test_name}}.runfiles/workerd/src/wpt; lldb ../workerd/server/workerd  -- test {{test_name}}.wd-test --experimental --directory-path=TEST_TMPDIR=/tmp\n\nasan-wpt-test test_name:\n  bazel test //src/workerd/api/wpt:{{test_name}} --config=asan\n\nnew-wpt-test test_name:\n  mkdir -p src/wpt/$(dirname {{test_name}})\n  echo \"export default {};\" > src/wpt/{{test_name}}-test.ts\n  git add src/wpt/{{test_name}}-test.ts\n\n  echo >> src/wpt/BUILD.bazel\n  echo 'wpt_test(name = \"{{test_name}}\", config = \"{{test_name}}-test.ts\", wpt_directory = \"@wpt//:{{test_name}}@module\")' >> src/wpt/BUILD.bazel\n\n  ./tools/cross/format.py\n  bazel test //src/wpt:{{test_name}}@ --test_env=GEN_TEST_CONFIG=1 --test_output=streamed\n\n# Specify the full Bazel target name for the test to be created.\n# e.g. just new-test //src/workerd/api/tests:v8-temporal-test\nnew-test test_name:\n  ./tools/unix/new-test.sh {{test_name}}\n\nformat:\n  python3 tools/cross/format.py\n\ninternal-pr:\n  ./tools/unix/create-internal-pr.sh\n\n# update dependencies with a given prefix (all by default)\nupdate-deps prefix=\"\":\n  ./build/deps/update-deps.py {{prefix}}\n\n# equivalent to `cargo update`; use `workspace` or <package> to limit update scope\nupdate-rust package=\"full\":\n  bazel run //deps/rust:crates_vendor -- --repin {{package}}\n\n# example: just bench mimetype\nbench path:\n  bazel run //src/workerd/tests:bench-{{path}} --config=benchmark\n\n# example: just clippy dns\nclippy package=\"...\":\n  bazel build //src/rust/{{package}} --config=lint\n\n# example: just clang-tidy //src/rust/jsg:ffi\nclang-tidy target=\"//...\":\n  bazel build {{target}} --config=clang-tidy\n\ngenerate-types:\n  bazel build //types\n  cp -r bazel-bin/types/definitions/latest types/generated-snapshot/\n  cp -r bazel-bin/types/definitions/experimental types/generated-snapshot/\n\nupdate-reported-node-version:\n  python3 tools/update_node_version.py src/workerd/api/node/node-version.h\n\nupdate-opencode:\n  python3 tools/update_opencode_version.py\n\n# called by rust-analyzer discoverConfig (quiet recipe with no output)\n# rust-analyzer doesn't like stderr output, redirect it to /dev/null\n@_rust-analyzer:\n  rm -rf ./rust-project.json\n  bazel run @rules_rust//tools/rust_analyzer:discover_bazel_rust_project 2>/dev/null\n\ncreate-external:\n  tools/unix/create-external.sh\n\nbench-all:\n  bazel query 'attr(tags, \"[\\[ ]google_benchmark[,\\]]\", //... + @capnp-cpp//...)' --output=label | xargs -I {} bazel run --config=benchmark {}\n\nlint: eslint\n\neslint:\n  just stream-test \\\n    //src/cloudflare:cloudflare@eslint \\\n    //src/node:node@eslint \\\n    //src/pyodide:pyodide_static@eslint \\\n    //src/wpt:wpt-all@tsproject@eslint \\\n    //types:types_lib@eslint\n\n# Generate code coverage report (Linux only)\ncoverage path=\"//...\":\n  bazel coverage --config=coverage {{path}}\n  genhtml --branch-coverage --ignore-errors category --output coverage \"$(bazel info output_path)/_coverage/_coverage_report.dat\"\n  open coverage/index.html\n\nprofile path:\n  bazel run //src/workerd/tests:bench-{{path}} --config=benchmark --run_under=\"perf record -F max --call-graph lbr\"\n  PERF_FILE=$(fdfind perf.data bazel-bin | grep \"{{path}}\" | head -1); \\\n  if [ -n \"$PERF_FILE\" ] && [ -s \"$PERF_FILE\" ]; then \\\n    cp $PERF_FILE perf.data; \\\n    perf report --input=$PERF_FILE; \\\n  else \\\n    echo \"No valid perf.data file found for {{path}}\"; \\\n  fi\n\nwatch *args=\"build\":\n  watchexec -rc -w src -w build just {{args}}\n"
  },
  {
    "path": "npm/lib/node-install.ts",
    "content": "// Adapted from github.com/evanw/esbuild\n// Original copyright and license:\n//     Copyright (c) 2020 Evan Wallace\n//     MIT License\n//\n//     Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n//     The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n//     THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\nimport {\n  downloadedBinPath,\n  pkgAndSubpathForCurrentPlatform,\n} from \"./node-platform\";\n\nimport fs from \"fs\";\nimport os from \"os\";\nimport path from \"path\";\nimport zlib from \"zlib\";\nimport https from \"https\";\nimport child_process from \"child_process\";\n\ndeclare const LATEST_COMPATIBILITY_DATE: string;\ndeclare const WORKERD_VERSION: string;\n\nconst toPath = path.join(__dirname, \"bin\", \"workerd\");\nlet isToPathJS = true;\n\nfunction validateBinaryVersion(...command: string[]): void {\n  command.push(\"--version\");\n  let stdout: string;\n  try {\n    stdout = child_process\n      .execFileSync(command.shift()!, command, {\n        // Without this, this install script strangely crashes with the error\n        // \"EACCES: permission denied, write\" but only on Ubuntu Linux when node is\n        // installed from the Snap Store. This is not a problem when you download\n        // the official version of node. The problem appears to be that stderr\n        // (i.e. file descriptor 2) isn't writable?\n        //\n        // More info:\n        // - https://snapcraft.io/ (what the Snap Store is)\n        // - https://nodejs.org/dist/ (download the official version of node)\n        // - https://github.com/evanw/esbuild/issues/1711#issuecomment-1027554035\n        //\n        stdio: [/* stdin */ \"pipe\", /* stdout */ \"pipe\", /* stderr */ \"inherit\"],\n      })\n      .toString()\n      .trim();\n  } catch (e) {\n    let msg = `[workerd] Failed to validate workerd binary\n\nLocal development will not work. This usually means you're on an unsupported\noperating system, or missing some shared libraries.`;\n    if (process.platform === \"linux\") {\n      msg += \" On Debian-based systems,\\nmake sure you've installed the `libc++1` package.\"\n    }\n    console.error(msg);\n    return;\n  }\n  if (stdout !== `workerd ${LATEST_COMPATIBILITY_DATE}`) {\n    throw new Error(\n      `Expected ${JSON.stringify(\n        LATEST_COMPATIBILITY_DATE\n      )} but got ${JSON.stringify(stdout)}`\n    );\n  }\n}\n\nfunction isYarn(): boolean {\n  const { npm_config_user_agent } = process.env;\n  if (npm_config_user_agent) {\n    return /\\byarn\\//.test(npm_config_user_agent);\n  }\n  return false;\n}\n\nfunction fetch(url: string): Promise<Buffer> {\n  return new Promise((resolve, reject) => {\n    https\n      .get(url, (res) => {\n        if (\n          (res.statusCode === 301 || res.statusCode === 302) &&\n          res.headers.location\n        )\n          return fetch(res.headers.location).then(resolve, reject);\n        if (res.statusCode !== 200)\n          return reject(new Error(`Server responded with ${res.statusCode}`));\n        let chunks: Buffer[] = [];\n        res.on(\"data\", (chunk) => chunks.push(chunk));\n        res.on(\"end\", () => resolve(Buffer.concat(chunks)));\n      })\n      .on(\"error\", reject);\n  });\n}\n\nfunction extractFileFromTarGzip(buffer: Buffer, subpath: string): Buffer {\n  try {\n    buffer = zlib.unzipSync(buffer);\n  } catch (err: any) {\n    throw new Error(\n      `Invalid gzip data in archive: ${(err && err.message) || err}`\n    );\n  }\n  let str = (i: number, n: number) =>\n    String.fromCharCode(...buffer.subarray(i, i + n)).replace(/\\0.*$/, \"\");\n  let offset = 0;\n  subpath = `package/${subpath}`;\n  while (offset < buffer.length) {\n    let name = str(offset, 100);\n    let size = parseInt(str(offset + 124, 12), 8);\n    offset += 512;\n    if (!isNaN(size)) {\n      if (name === subpath) return buffer.subarray(offset, offset + size);\n      offset += (size + 511) & ~511;\n    }\n  }\n  throw new Error(`Could not find ${JSON.stringify(subpath)} in archive`);\n}\n\nfunction installUsingNPM(pkg: string, subpath: string, binPath: string): void {\n  // Erase \"npm_config_global\" so that \"npm install --global workerd\" works.\n  // Otherwise this nested \"npm install\" will also be global, and the install\n  // will deadlock waiting for the global installation lock.\n  const env = { ...process.env, npm_config_global: undefined };\n\n  // Create a temporary directory inside the \"workerd\" package with an empty\n  // \"package.json\" file. We'll use this to run \"npm install\" in.\n  const libDir = path.dirname(require.resolve(\"workerd\"));\n  const installDir = path.join(libDir, \"npm-install\");\n  fs.mkdirSync(installDir);\n  try {\n    fs.writeFileSync(path.join(installDir, \"package.json\"), \"{}\");\n\n    // Run \"npm install\" in the temporary directory which should download the\n    // desired package. Try to avoid unnecessary log output. This uses the \"npm\"\n    // command instead of a HTTP request so that it hopefully works in situations\n    // where HTTP requests are blocked but the \"npm\" command still works due to,\n    // for example, a custom configured npm registry and special firewall rules.\n    child_process.execSync(\n      `npm install --loglevel=error --prefer-offline --no-audit --progress=false ${pkg}@${WORKERD_VERSION}`,\n      { cwd: installDir, stdio: \"pipe\", env }\n    );\n\n    // Move the downloaded binary executable into place. The destination path\n    // is the same one that the JavaScript API code uses so it will be able to\n    // find the binary executable here later.\n    const installedBinPath = path.join(\n      installDir,\n      \"node_modules\",\n      pkg,\n      subpath\n    );\n    fs.renameSync(installedBinPath, binPath);\n  } finally {\n    // Try to clean up afterward so we don't unnecessarily waste file system\n    // space. Leaving nested \"node_modules\" directories can also be problematic\n    // for certain tools that scan over the file tree and expect it to have a\n    // certain structure.\n    try {\n      removeRecursive(installDir);\n    } catch {\n      // Removing a file or directory can randomly break on Windows, returning\n      // EBUSY for an arbitrary length of time. I think this happens when some\n      // other program has that file or directory open (e.g. an anti-virus\n      // program). This is fine on Unix because the OS just unlinks the entry\n      // but keeps the reference around until it's unused. There's nothing we\n      // can do in this case so we just leave the directory there.\n    }\n  }\n}\n\nfunction removeRecursive(dir: string): void {\n  for (const entry of fs.readdirSync(dir)) {\n    const entryPath = path.join(dir, entry);\n    let stats;\n    try {\n      stats = fs.lstatSync(entryPath);\n    } catch {\n      continue; // Guard against https://github.com/nodejs/node/issues/4760\n    }\n    if (stats.isDirectory()) removeRecursive(entryPath);\n    else fs.unlinkSync(entryPath);\n  }\n  fs.rmdirSync(dir);\n}\n\nfunction maybeOptimizePackage(binPath: string): void {\n  // This package contains a \"bin/workerd\" JavaScript file that finds and runs\n  // the appropriate binary executable. However, this means that running the\n  // \"workerd\" command runs another instance of \"node\" which is way slower than\n  // just running the binary executable directly.\n  //\n  // Here we optimize for this by replacing the JavaScript file with the binary\n  // executable at install time. This optimization does not work on Windows\n  // because on Windows the binary executable must be called \"workerd.exe\"\n  // instead of \"workerd\".\n  //\n  // This doesn't work with Yarn both because of lack of support for binary\n  // files in Yarn 2+ (see https://github.com/yarnpkg/berry/issues/882) and\n  // because Yarn (even Yarn 1?) may run the same install scripts in the same\n  // place multiple times from different platforms, especially when people use\n  // Docker. Avoid idempotency issues by just not optimizing when using Yarn.\n  //\n  // This optimization also doesn't apply when npm's \"--ignore-scripts\" flag is\n  // used since in that case this install script will not be run.\n  if (os.platform() !== \"win32\" && !isYarn()) {\n    const tempPath = path.join(__dirname, \"bin-workerd\");\n    try {\n      // First link the binary with a temporary file. If this fails and throws an\n      // error, then we'll just end up doing nothing. This uses a hard link to\n      // avoid taking up additional space on the file system.\n      fs.linkSync(binPath, tempPath);\n\n      // Then use rename to atomically replace the target file with the temporary\n      // file. If this fails and throws an error, then we'll just end up leaving\n      // the temporary file there, which is harmless.\n      fs.renameSync(tempPath, toPath);\n\n      // If we get here, then we know that the target location is now a binary\n      // executable instead of a JavaScript file.\n      isToPathJS = false;\n\n      // If this install script is being re-run, then \"renameSync\" will fail\n      // since the underlying inode is the same (it just returns without doing\n      // anything, and without throwing an error). In that case we should remove\n      // the file manually.\n      fs.unlinkSync(tempPath);\n    } catch {\n      // Ignore errors here since this optimization is optional\n    }\n  }\n}\n\nasync function downloadDirectlyFromNPM(\n  pkg: string,\n  subpath: string,\n  binPath: string\n): Promise<void> {\n  // If that fails, the user could have npm configured incorrectly or could not\n  // have npm installed. Try downloading directly from npm as a last resort.\n  const unscopedPkg = pkg.substring(pkg.indexOf(\"/\") + 1);\n  const url = `https://registry.npmjs.org/${pkg}/-/${unscopedPkg}-${WORKERD_VERSION}.tgz`;\n  console.error(`[workerd] Trying to download ${JSON.stringify(url)}`);\n  try {\n    fs.writeFileSync(\n      binPath,\n      extractFileFromTarGzip(await fetch(url), subpath)\n    );\n    fs.chmodSync(binPath, 0o755);\n  } catch (e: any) {\n    console.error(\n      `[workerd] Failed to download ${JSON.stringify(url)}: ${\n        (e && e.message) || e\n      }`\n    );\n    throw e;\n  }\n}\n\nasync function checkAndPreparePackage(): Promise<void> {\n  const { pkg, subpath } = pkgAndSubpathForCurrentPlatform();\n\n  let binPath: string;\n  try {\n    // First check for the binary package from our \"optionalDependencies\". This\n    // package should have been installed alongside this package at install time.\n    binPath = require.resolve(`${pkg}/${subpath}`);\n  } catch (e) {\n    console.error(`[workerd] Failed to find package \"${pkg}\" on the file system\n\nThis can happen if you use the \"--no-optional\" flag. The \"optionalDependencies\"\npackage.json feature is used by workerd to install the correct binary executable\nfor your current platform. This install script will now attempt to work around\nthis. If that fails, you need to remove the \"--no-optional\" flag to use workerd.\n`);\n\n    // If that didn't work, then someone probably installed workerd with the\n    // \"--no-optional\" flag. Attempt to compensate for this by downloading the\n    // package using a nested call to \"npm\" instead.\n    //\n    // THIS MAY NOT WORK. Package installation uses \"optionalDependencies\" for\n    // a reason: manually downloading the package has a lot of obscure edge\n    // cases that fail because people have customized their environment in\n    // some strange way that breaks downloading. This code path is just here\n    // to be helpful but it's not the supported way of installing workerd.\n    binPath = downloadedBinPath(pkg, subpath);\n    try {\n      console.error(`[workerd] Trying to install package \"${pkg}\" using npm`);\n      installUsingNPM(pkg, subpath, binPath);\n    } catch (e2: any) {\n      console.error(\n        `[workerd] Failed to install package \"${pkg}\" using npm: ${\n          (e2 && e2.message) || e2\n        }`\n      );\n\n      // If that didn't also work, then something is likely wrong with the \"npm\"\n      // command. Attempt to compensate for this by manually downloading the\n      // package from the npm registry over HTTP as a last resort.\n      try {\n        await downloadDirectlyFromNPM(pkg, subpath, binPath);\n      } catch (e3: any) {\n        throw new Error(`Failed to install package \"${pkg}\"`);\n      }\n    }\n  }\n\n  maybeOptimizePackage(binPath);\n}\n\ncheckAndPreparePackage().then(() => {\n  if (isToPathJS) {\n    // We need \"node\" before this command since it's a JavaScript file\n    validateBinaryVersion(process.execPath, toPath);\n  } else {\n    // This is no longer a JavaScript file so don't run it using \"node\"\n    validateBinaryVersion(toPath);\n  }\n});\n"
  },
  {
    "path": "npm/lib/node-path.ts",
    "content": "#!/usr/bin/env node\n// Adapted from github.com/evanw/esbuild\n// Original copyright and license:\n//     Copyright (c) 2020 Evan Wallace\n//     MIT License\n//\n//     Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n//     The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n//     THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\nimport { generateBinPath } from \"./node-platform\";\nconst { binPath } = generateBinPath();\ndeclare const LATEST_COMPATIBILITY_DATE: string;\ndeclare const WORKERD_VERSION: string;\n\nexport default binPath;\nexport const compatibilityDate = LATEST_COMPATIBILITY_DATE;\nexport const version = WORKERD_VERSION;\n"
  },
  {
    "path": "npm/lib/node-platform.ts",
    "content": "// Adapted from github.com/evanw/esbuild\n// Original copyright and license:\n//     Copyright (c) 2020 Evan Wallace\n//     MIT License\n//\n//     Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n//     The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n//     THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\nimport fs from \"fs\";\nimport os from \"os\";\nimport path from \"path\";\n\ndeclare const WORKERD_VERSION: string;\n\nexport const knownPackages: Record<string, string> = {\n  \"darwin arm64 LE\": \"@cloudflare/workerd-darwin-arm64\",\n  \"darwin x64 LE\": \"@cloudflare/workerd-darwin-64\",\n  \"linux arm64 LE\": \"@cloudflare/workerd-linux-arm64\",\n  \"linux x64 LE\": \"@cloudflare/workerd-linux-64\",\n  \"win32 x64 LE\": \"@cloudflare/workerd-windows-64\",\n};\n\nconst maybeExeExtension = process.platform === \"win32\" ? \".exe\" : \"\";\n\nexport function pkgAndSubpathForCurrentPlatform(): {\n  pkg: string;\n  subpath: string;\n} {\n  let pkg: string;\n  let subpath: string;\n  let platformKey = `${process.platform} ${os.arch()} ${os.endianness()}`;\n\n  if (platformKey in knownPackages) {\n    pkg = knownPackages[platformKey];\n    subpath = `bin/workerd${maybeExeExtension}`;\n  } else {\n    throw new Error(`Unsupported platform: ${platformKey}`);\n  }\n\n  return { pkg, subpath };\n}\n\nfunction pkgForSomeOtherPlatform(): string | null {\n  const libMain = require.resolve(\"workerd\");\n  const nodeModulesDirectory = path.dirname(\n    path.dirname(path.dirname(libMain))\n  );\n\n  if (path.basename(nodeModulesDirectory) === \"node_modules\") {\n    for (const unixKey in knownPackages) {\n      try {\n        const pkg = knownPackages[unixKey];\n        if (fs.existsSync(path.join(nodeModulesDirectory, pkg))) return pkg;\n      } catch {}\n    }\n  }\n\n  return null;\n}\n\nexport function downloadedBinPath(pkg: string, subpath: string): string {\n  const libDir = path.dirname(require.resolve(\"workerd\"));\n  return path.join(libDir, `downloaded-${pkg.replace(\"/\", \"-\")}-${path.basename(subpath)}${maybeExeExtension}`);\n}\n\nexport function generateBinPath(): { binPath: string } {\n  const { pkg, subpath } = pkgAndSubpathForCurrentPlatform();\n  let binPath: string;\n\n  try {\n    // First check for the binary package from our \"optionalDependencies\". This\n    // package should have been installed alongside this package at install time.\n    binPath = require.resolve(`${pkg}/${subpath}`);\n  } catch (e) {\n    // If that didn't work, then someone probably installed workerd with the\n    // \"--no-optional\" flag. Our install script attempts to compensate for this\n    // by manually downloading the package instead. Check for that next.\n    binPath = downloadedBinPath(pkg, subpath);\n    if (!fs.existsSync(binPath)) {\n      // If that didn't work too, check to see whether the package is even there\n      // at all. It may not be (for a few different reasons).\n      try {\n        require.resolve(pkg);\n      } catch {\n        // If we can't find the package for this platform, then it's possible\n        // that someone installed this for some other platform and is trying\n        // to use it without reinstalling. That won't work of course, but\n        // people do this all the time with systems like Docker. Try to be\n        // helpful in that case.\n        const otherPkg = pkgForSomeOtherPlatform();\n        if (otherPkg) {\n          throw new Error(`\nYou installed workerd on another platform than the one you're currently using.\nThis won't work because workerd is written with native code and needs to\ninstall a platform-specific binary executable.\n\nSpecifically the \"${otherPkg}\" package is present but this platform\nneeds the \"${pkg}\" package instead. People often get into this\nsituation by installing workerd on macOS and copying \"node_modules\"\ninto a Docker image that runs Linux.\n\nIf you are installing with npm, you can try not copying the \"node_modules\"\ndirectory when you copy the files over, and running \"npm ci\" or \"npm install\"\non the destination platform after the copy. Or you could consider using yarn\ninstead which has built-in support for installing a package on multiple\nplatforms simultaneously.\n\nIf you are installing with yarn, you can try listing both this platform and the\nother platform in your \".yarnrc.yml\" file using the \"supportedArchitectures\"\nfeature: https://yarnpkg.com/configuration/yarnrc/#supportedArchitectures\nKeep in mind that this means multiple copies of workerd will be present.\n`);\n        }\n\n        // If that didn't work too, then maybe someone installed workerd with\n        // both the \"--no-optional\" and the \"--ignore-scripts\" flags. The fix\n        // for this is to just not do that. We don't attempt to handle this\n        // case at all.\n        //\n        // In that case we try to have a nice error message if we think we know\n        // what's happening. Otherwise we just rethrow the original error message.\n        throw new Error(`The package \"${pkg}\" could not be found, and is needed by workerd.\n\nIf you are installing workerd with npm, make sure that you don't specify the\n\"--no-optional\" flag. The \"optionalDependencies\" package.json feature is used\nby workerd to install the correct binary executable for your current platform.`);\n      }\n      throw e;\n    }\n  }\n\n  // The workerd binary executable can't be used in Yarn 2 in PnP mode because\n  // it's inside a virtual file system and the OS needs it in the real file\n  // system. So we need to copy the file out of the virtual file system into\n  // the real file system.\n  //\n  // You might think that we could use \"preferUnplugged: true\" in each of the\n  // platform-specific packages for this instead, since that tells Yarn to not\n  // use the virtual file system for those packages. This is not done because:\n  //\n  // * Really early versions of Yarn don't support \"preferUnplugged\", so package\n  //   installation would break on those Yarn versions if we did this.\n  //\n  // * Earlier Yarn versions always installed all optional dependencies for all\n  //   platforms even though most of them are incompatible. To minimize file\n  //   system space, we want these useless packages to take up as little space\n  //   as possible so they should remain unzipped inside their \".zip\" files.\n  //\n  //   We have to explicitly pass \"preferUnplugged: false\" instead of leaving\n  //   it up to Yarn's default behavior because Yarn's heuristics otherwise\n  //   automatically unzip packages containing \".exe\" files, and we don't want\n  //   our Windows-specific packages to be unzipped either.\n  //\n  let pnpapi: any;\n  try {\n    pnpapi = require(\"pnpapi\");\n  } catch (e) {}\n  if (pnpapi) {\n    const root = pnpapi.getPackageInformation(pnpapi.topLevel).packageLocation;\n    const binTargetPath = path.join(\n      root,\n      \"node_modules\",\n      \".cache\",\n      \"workerd\",\n      `pnpapi-${pkg.replace(\"/\", \"-\")}-${WORKERD_VERSION}-${path.basename(subpath)}`\n    );\n    if (!fs.existsSync(binTargetPath)) {\n      fs.mkdirSync(path.dirname(binTargetPath), { recursive: true });\n      fs.copyFileSync(binPath, binTargetPath);\n      fs.chmodSync(binTargetPath, 0o755);\n    }\n    return { binPath: binTargetPath };\n  }\n\n  return { binPath };\n}\n"
  },
  {
    "path": "npm/lib/node-shim.ts",
    "content": "#!/usr/bin/env node\n// Adapted from github.com/evanw/esbuild\n// Original copyright and license:\n//     Copyright (c) 2020 Evan Wallace\n//     MIT License\n//\n//     Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n//     The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n//     THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\nimport { generateBinPath } from './node-platform';\nconst { binPath } = generateBinPath();\n\nrequire('child_process').execFileSync(binPath, process.argv.slice(2), {\n  stdio: 'inherit'\n});\n"
  },
  {
    "path": "npm/scripts/build-shim-package.mjs",
    "content": "// Adapted from github.com/evanw/esbuild\n// Original copyright and license:\n//     Copyright (c) 2020 Evan Wallace\n//     MIT License\n//\n//     Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n//     The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n//     THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\nimport path from 'path';\nimport fs from 'fs';\n\nfunction buildNeutralLib() {\n  const pjPath = path.join('npm', 'workerd', 'package.json');\n  const package_json = JSON.parse(fs.readFileSync(pjPath, 'utf8'));\n  package_json.optionalDependencies = {\n    '@cloudflare/workerd-darwin-arm64': process.env.WORKERD_VERSION,\n    '@cloudflare/workerd-darwin-64': process.env.WORKERD_VERSION,\n    '@cloudflare/workerd-linux-arm64': process.env.WORKERD_VERSION,\n    '@cloudflare/workerd-linux-64': process.env.WORKERD_VERSION,\n    '@cloudflare/workerd-windows-64': process.env.WORKERD_VERSION,\n  };\n  fs.writeFileSync(pjPath, JSON.stringify(package_json, null, 2) + '\\n');\n\n  const capnpPath = path.join('src', 'workerd', 'server', 'workerd.capnp');\n\n  fs.copyFileSync(capnpPath, path.join('npm', 'workerd', 'workerd.capnp'));\n\n  const typeWorkerPath = path.join('bazel-bin', 'types', 'dist', 'index.mjs');\n\n  fs.copyFileSync(typeWorkerPath, path.join('npm', 'workerd', 'worker.mjs'));\n}\n\nbuildNeutralLib();\n"
  },
  {
    "path": "npm/scripts/build-types-package.mjs",
    "content": "import path from \"path\";\nimport fs from \"fs\";\n\nfunction buildTypesPackage() {\n  const packageJsonPath = path.join(\"npm\", \"workers-types\", \"package.json\");\n  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, \"utf8\"));\n  packageJson.version = process.env.WORKERD_VERSION;\n  fs.writeFileSync(\n    packageJsonPath,\n    JSON.stringify(packageJson, null, 2) + \"\\n\"\n  );\n}\n\nbuildTypesPackage();\n"
  },
  {
    "path": "npm/scripts/bump-version.mjs",
    "content": "// Adapted from github.com/evanw/esbuild\n// Original copyright and license:\n//     Copyright (c) 2020 Evan Wallace\n//     MIT License\n//\n//     Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n//     The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n//     THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\nimport fs from 'fs';\n\nfunction updateVersionPackageJSON(pathToPackageJSON) {\n  console.log(pathToPackageJSON);\n  const json = JSON.parse(fs.readFileSync(pathToPackageJSON, 'utf8'));\n\n  json.version = process.env.WORKERD_VERSION;\n\n  fs.writeFileSync(pathToPackageJSON, JSON.stringify(json, null, 2) + '\\n');\n}\n\nupdateVersionPackageJSON(process.argv[2]);\n"
  },
  {
    "path": "npm/workerd/.gitignore",
    "content": "node_modules"
  },
  {
    "path": "npm/workerd/README.md",
    "content": "# 👷 `workerd`, Cloudflare's JavaScript/Wasm Runtime\n\n`workerd` is a JavaScript / Wasm server runtime based on the same code that powers\n[Cloudflare Workers](https://workers.dev).\n\nSee https://github.com/cloudflare/workerd for details.\n"
  },
  {
    "path": "npm/workerd/package.json",
    "content": "{\n  \"name\": \"workerd\",\n  \"version\": \"1.20220926.0\",\n  \"description\": \"👷 workerd, Cloudflare's JavaScript/Wasm Runtime\",\n  \"repository\": \"https://github.com/cloudflare/workerd\",\n  \"scripts\": {\n    \"postinstall\": \"node install.js\"\n  },\n  \"main\": \"lib/main.js\",\n  \"engines\": {\n    \"node\": \">=16\"\n  },\n  \"bin\": {\n    \"workerd\": \"bin/workerd\"\n  },\n  \"optionalDependencies\": {\n    \"@cloudflare/workerd-darwin-arm64\": \"1.20220926.0\",\n    \"@cloudflare/workerd-darwin-64\": \"1.20220926.0\",\n    \"@cloudflare/workerd-linux-arm64\": \"1.20220926.0\",\n    \"@cloudflare/workerd-linux-64\": \"1.20220926.0\",\n    \"@cloudflare/workerd-windows-64\": \"1.20220926.0\"\n  },\n  \"license\": \"Apache-2.0\"\n}\n"
  },
  {
    "path": "npm/workerd-darwin-64/README.md",
    "content": "# 👷 `workerd` for macOS 64-bit, Cloudflare's JavaScript/Wasm Runtime\n\n`workerd` is a JavaScript / Wasm server runtime based on the same code that powers\n[Cloudflare Workers](https://workers.dev).\n\nSee https://github.com/cloudflare/workerd for details.\n"
  },
  {
    "path": "npm/workerd-darwin-64/package.json",
    "content": "{\n  \"name\": \"@cloudflare/workerd-darwin-64\",\n  \"description\": \"👷 workerd for macOS 64-bit, Cloudflare's JavaScript/Wasm Runtime\",\n  \"repository\": \"https://github.com/cloudflare/workerd\",\n  \"license\": \"Apache-2.0\",\n  \"preferUnplugged\": false,\n  \"engines\": {\n    \"node\": \">=16\"\n  },\n  \"os\": [\n    \"darwin\"\n  ],\n  \"cpu\": [\n    \"x64\"\n  ],\n  \"version\": \"1.20220926.0\"\n}\n"
  },
  {
    "path": "npm/workerd-darwin-arm64/README.md",
    "content": "# 👷 `workerd` for macOS ARM 64-bit, Cloudflare's JavaScript/Wasm Runtime\n\n`workerd` is a JavaScript / Wasm server runtime based on the same code that powers\n[Cloudflare Workers](https://workers.dev).\n\nSee https://github.com/cloudflare/workerd for details.\n"
  },
  {
    "path": "npm/workerd-darwin-arm64/package.json",
    "content": "{\n  \"name\": \"@cloudflare/workerd-darwin-arm64\",\n  \"description\": \"👷 workerd for macOS ARM 64-bit, Cloudflare's JavaScript/Wasm Runtime\",\n  \"repository\": \"https://github.com/cloudflare/workerd\",\n  \"license\": \"Apache-2.0\",\n  \"preferUnplugged\": false,\n  \"engines\": {\n    \"node\": \">=16\"\n  },\n  \"os\": [\n    \"darwin\"\n  ],\n  \"cpu\": [\n    \"arm64\"\n  ],\n  \"version\": \"1.20220926.0\"\n}\n"
  },
  {
    "path": "npm/workerd-linux-64/README.md",
    "content": "# 👷 `workerd` for Linux 64-bit, Cloudflare's JavaScript/Wasm Runtime\n\n`workerd` is a JavaScript / Wasm server runtime based on the same code that powers\n[Cloudflare Workers](https://workers.dev).\n\nSee https://github.com/cloudflare/workerd for details.\n"
  },
  {
    "path": "npm/workerd-linux-64/package.json",
    "content": "{\n  \"name\": \"@cloudflare/workerd-linux-64\",\n  \"description\": \"👷 workerd for Linux 64-bit, Cloudflare's JavaScript/Wasm Runtime\",\n  \"repository\": \"https://github.com/cloudflare/workerd\",\n  \"license\": \"Apache-2.0\",\n  \"preferUnplugged\": false,\n  \"engines\": {\n    \"node\": \">=16\"\n  },\n  \"os\": [\n    \"linux\"\n  ],\n  \"cpu\": [\n    \"x64\"\n  ],\n  \"version\": \"1.20220926.0\"\n}\n"
  },
  {
    "path": "npm/workerd-linux-arm64/README.md",
    "content": "# 👷 `workerd` for Linux ARM 64-bit, Cloudflare's JavaScript/Wasm Runtime\n\n`workerd` is a JavaScript / Wasm server runtime based on the same code that powers\n[Cloudflare Workers](https://workers.dev).\n\nSee https://github.com/cloudflare/workerd for details.\n"
  },
  {
    "path": "npm/workerd-linux-arm64/package.json",
    "content": "{\n  \"name\": \"@cloudflare/workerd-linux-arm64\",\n  \"description\": \"👷 workerd for Linux ARM 64-bit, Cloudflare's JavaScript/Wasm Runtime\",\n  \"repository\": \"https://github.com/cloudflare/workerd\",\n  \"license\": \"Apache-2.0\",\n  \"preferUnplugged\": false,\n  \"engines\": {\n    \"node\": \">=16\"\n  },\n  \"os\": [\n    \"linux\"\n  ],\n  \"cpu\": [\n    \"arm64\"\n  ],\n  \"version\": \"1.20220926.0\"\n}\n"
  },
  {
    "path": "npm/workerd-windows-64/README.md",
    "content": "# 👷 `workerd` for Windows 64-bit, Cloudflare's JavaScript/Wasm Runtime\n\n`workerd` is a JavaScript / Wasm server runtime based on the same code that powers\n[Cloudflare Workers](https://workers.dev).\n\nSee https://github.com/cloudflare/workerd for details.\n"
  },
  {
    "path": "npm/workerd-windows-64/package.json",
    "content": "{\n  \"name\": \"@cloudflare/workerd-windows-64\",\n  \"description\": \"👷 workerd for Windows 64-bit, Cloudflare's JavaScript/Wasm Runtime\",\n  \"repository\": \"https://github.com/cloudflare/workerd\",\n  \"license\": \"Apache-2.0\",\n  \"preferUnplugged\": false,\n  \"engines\": {\n    \"node\": \">=16\"\n  },\n  \"os\": [\n    \"win32\"\n  ],\n  \"cpu\": [\n    \"x64\"\n  ],\n  \"version\": \"1.20220926.0\"\n}\n"
  },
  {
    "path": "npm/workers-types/README.md",
    "content": "# Cloudflare Workers Types\n\n> **Note**\n>\n> We now recommend using the [Wrangler CLI](https://www.npmjs.com/package/wrangler) and the `wrangler types` command to generate types based on your compatibility date _and_ compatibility flags. You can learn more about this, and how to migrate from @cloudflare/workers-types [here in our docs](https://developers.cloudflare.com/workers/languages/typescript/#generate-types).\n>\n> @cloudflare/workers-types will continue to be published on the same schedule.\n\n\n## Install\n\n```bash\nnpm install -D @cloudflare/workers-types\n-- Or\nyarn add -D @cloudflare/workers-types\n```\n\n## Usage\n\nThe following is a minimal `tsconfig.json` for use alongside this package:\n\n**`tsconfig.json`**\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"module\": \"esnext\",\n    \"lib\": [\"esnext\"],\n    \"types\": [\"@cloudflare/workers-types\"]\n  }\n}\n```\n\n### Compatibility dates\n\n![Entrypoints for compatibility dates](./npm/workers-types/entrypoints.svg)\n\nThe Cloudflare Workers runtime manages backwards compatibility through the use of [Compatibility Dates](https://developers.cloudflare.com/workers/platform/compatibility-dates/). Using different compatibility dates affects the runtime types available to your Worker, and so it's important you specify the correct entrypoint to the `workers-types` package to match your compatibility date (which is usually set in your `wrangler.toml` configuration file). `workers-types` currently exposes the following entrypoints to choose from:\n\n- `@cloudflare/workers-types`\n\n  The default entrypoint exposes the runtime types for a compatibility date before `2021-11-03`.\n\n- `@cloudflare/workers-types/2021-11-03`\n\n  This entrypoint exposes the runtime types for a compatibility date between `2021-11-03` and `2022-01-31`.\n\n- `@cloudflare/workers-types/2022-01-31`\n\n  This entrypoint exposes the runtime types for a compatibility date between `2022-01-31` and `2022-03-21`.\n\n- `@cloudflare/workers-types/2022-03-21`\n\n  This entrypoint exposes the runtime types for a compatibility date between `2022-03-21` and `2022-08-04`.\n\n- `@cloudflare/workers-types/2022-08-04`\n\n  This entrypoint exposes the runtime types for a compatibility date between `2022-08-04` and `2022-10-31`.\n\n- `@cloudflare/workers-types/2022-10-31`\n\n  This entrypoint exposes the runtime types for a compatibility date between `2022-10-31` and `2022-11-30`.\n\n- `@cloudflare/workers-types/2022-11-30`\n\n  This entrypoint exposes the runtime types for a compatibility date between `2022-11-30` and `2023-03-01`.\n\n- `@cloudflare/workers-types/2023-03-01`\n\n  This entrypoint exposes the runtime types for a compatibility date between `2023-03-01` and `2023-07-01`.\n\n- `@cloudflare/workers-types/2023-07-01`\n\n  This entrypoint exposes the runtime types for a compatibility date after `2023-07-01`.\n\n- `@cloudflare/workers-types/experimental`\n\n  This entrypoint exposes the runtime types for the latest compatibility date. The types exposed by this entrypoint will change over time to always reflect the latest version of the Workers runtime.\n\nTo use one of these entrypoints, you need to specify them in your `tsconfig.json`. For example, this is a sample `tsconfig.json` for using the `2022-08-04` entrypoint.\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"module\": \"esnext\",\n    \"lib\": [\"esnext\"],\n    \"types\": [\"@cloudflare/workers-types/2022-08-04\"]\n  }\n}\n```\n\n### Importable Types\n\nIt's not always possible (or desirable) to modify the `tsconfig.json` settings for a project to include all the Cloudflare Workers types. For use cases like that, this package provides importable versions of its types, which are usable with no additional `tsconfig.json` setup. For example:\n\n```ts\nimport type { Request as WorkerRequest, ExecutionContext } from \"@cloudflare/workers-types/experimental\"\n\nexport default {\n  fetch(request: WorkerRequest, env: unknown, ctx: ExecutionContext) {\n    return new Response(\"OK\")\n  }\n}\n```\n\n\n### Using bindings\n\nIt's recommended that you create a type file for any bindings your Worker uses. Create a file named\n`worker-configuration.d.ts` in your src directory.\n\nIf you're using Module Workers, it should look like this:\n```typescript\n// worker-configuration.d.ts\ninterface Env {\n  MY_ENV_VAR: string;\n  MY_SECRET: string;\n  myKVNamespace: KVNamespace;\n}\n```\nFor Service Workers, it should augment the global scope:\n```typescript\n// worker-configuration.d.ts\ndeclare global {\n  const MY_ENV_VAR: string;\n  const MY_SECRET: string;\n  const myKVNamespace: KVNamespace;\n}\nexport {}\n```\n\nWrangler can also generate this for you automatically from your `wrangler.toml` configuration file, using the `wrangler types` command.\n\n"
  },
  {
    "path": "npm/workers-types/package.json",
    "content": "{\n  \"name\": \"@cloudflare/workers-types\",\n  \"description\": \"TypeScript typings for Cloudflare Workers\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/cloudflare/workerd\"\n  },\n  \"author\": \"Cloudflare Workers DevProd Team <workers-devprod@cloudflare.com> (https://workers.cloudflare.com)\",\n  \"license\": \"MIT OR Apache-2.0\"\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@cloudflare/workerd-root\",\n  \"private\": true,\n  \"scripts\": {},\n  \"dependencies\": {\n    \"prettier\": \"^3.8.1\",\n    \"typescript\": \"5.9.3\"\n  },\n  \"devDependencies\": {\n    \"@eslint/js\": \"^9.39.2\",\n    \"@types/node\": \"^25.2.1\",\n    \"capnp-es\": \"0.0.14\",\n    \"chrome-remote-interface\": \"^0.33.3\",\n    \"esbuild\": \"^0.27.2\",\n    \"eslint\": \"^9.39.2\",\n    \"expect-type\": \"^1.3.0\",\n    \"typescript-eslint\": \"^8.54.0\"\n  },\n  \"pnpm\": {\n    \"onlyBuiltDependencies\": [\n      \"esbuild\"\n    ],\n    \"packageExtensions\": {\n      \"@opennextjs/cloudflare\": {\n        \"dependencies\": {\n          \"esbuild\": \"^0.27.2\"\n        }\n      }\n    }\n  },\n  \"packageManager\": \"pnpm@10.28.2\"\n}\n"
  },
  {
    "path": "patches/boringssl/0001-Expose-libdecrepit-so-NodeJS-can-use-it-for-ncrypto.patch",
    "content": "From feda75fa93bb54bc697f1f771a0aec573ab87ab7 Mon Sep 17 00:00:00 2001\nFrom: Nicholas Paun <npaun@cloudflare.com>\nDate: Fri, 26 Sep 2025 14:57:57 -0700\nSubject: [PATCH] Expose libdecrepit so NodeJS can use it for ncrypto\n\n---\n BUILD.bazel | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/BUILD.bazel b/BUILD.bazel\nindex b7dc35932..7d214716c 100644\n--- a/BUILD.bazel\n+++ b/BUILD.bazel\n@@ -155,10 +155,10 @@ bssl_cc_binary(\n     ],\n )\n \n-# Build, but do not export libdecrepit.\n bssl_cc_library(\n     name = \"decrepit\",\n     srcs = decrepit_sources,\n+    visibility = [\"//visibility:public\"],\n     copts = [\"-DBORINGSSL_IMPLEMENTATION\"],\n     internal_hdrs = decrepit_internal_headers,\n     deps = [\n-- \n2.50.1 (Apple Git-155)\n\n"
  },
  {
    "path": "patches/perfetto/0001-Don-t-attempt-to-use-rules_android.patch",
    "content": "From 418089631def5cb0cb92b550f2500bcff1230980 Mon Sep 17 00:00:00 2001\nFrom: Felix Hanau <felix@cloudflare.com>\nDate: Sat, 15 Nov 2025 16:55:07 -0500\nSubject: Don't attempt to use rules_android\n\n\ndiff --git a/MODULE.bazel b/MODULE.bazel\nindex 47f7f25cfd..dc006ebba8 100644\n--- a/MODULE.bazel\n+++ b/MODULE.bazel\n@@ -14,102 +14,10 @@\n \n \"\"\"Perfetto Bazel module configuration for bzlmod.\"\"\"\n \n-module(\n-    name = \"perfetto\",\n-    version = \"0.0.0\",\n-)\n+module(name = \"perfetto\")\n+bazel_dep(name = \"perfetto_cfg\", version = \"0.0.0\")\n \n bazel_dep(name = \"bazel_skylib\", version = \"1.7.1\")\n-bazel_dep(name = \"platforms\", version = \"0.0.10\")\n-bazel_dep(name = \"protobuf\", version = \"31.1\", repo_name = \"com_google_protobuf\")\n+bazel_dep(name = \"platforms\", version = \"1.0.0\")\n+bazel_dep(name = \"protobuf\", version = \"33.4\")\n bazel_dep(name = \"rules_python\", version = \"1.0.0\")\n-bazel_dep(name = \"rules_android\", version = \"0.6.6\")\n-\n-remote_android_extensions = use_extension(\n-    \"@rules_android//bzlmod_extensions:android_extensions.bzl\",\n-    \"remote_android_tools_extensions\",\n-)\n-use_repo(remote_android_extensions, \"android_tools\")\n-\n-android_sdk_repository_extension = use_extension(\n-    \"@rules_android//rules/android_sdk_repository:rule.bzl\",\n-    \"android_sdk_repository_extension\",\n-)\n-\n-# When built using 'tools/bazel', 'ANDROID_HOME' environment variable points\n-# to the hermetic Android SDK installation, that should be downloaded first\n-# with 'tools/install-build-deps --android'\n-android_sdk_repository_extension.configure(\n-    api_level = 35,\n-    build_tools_version = \"35.0.1\",\n-)\n-use_repo(android_sdk_repository_extension, \"androidsdk\")\n-\n-register_toolchains(\"@androidsdk//:sdk-toolchain\", \"@androidsdk//:all\")\n-\n-bazel_dep(name = \"rules_jvm_external\", version = \"6.9\")\n-bazel_dep(name = \"rules_android_ndk\", version = \"0.1.3\")\n-\n-android_ndk_repository_extension = use_extension(\n-    \"@rules_android_ndk//:extension.bzl\",\n-    \"android_ndk_repository_extension\",\n-)\n-\n-# When built using 'tools/bazel', 'ANDROID_NDK_HOME' environment variable points\n-# to the hermetic Android NDK installation, that should be downloaded first\n-# with 'tools/install-build-deps --android'\n-android_ndk_repository_extension.configure(\n-    api_level = 26,\n-)\n-use_repo(android_ndk_repository_extension, \"androidndk\")\n-\n-register_toolchains(\"@androidndk//:all\")\n-\n-# Perfetto configuration repository extension.\n-# This creates @perfetto_cfg which provides PERFETTO_CONFIG struct.\n-perfetto_cfg_ext = use_extension(\n-    \"//bazel:perfetto_cfg_ext.bzl\",\n-    \"perfetto_cfg_ext\",\n-)\n-use_repo(perfetto_cfg_ext, \"perfetto_cfg\")\n-\n-# Perfetto third-party dependencies extension.\n-# These are the same deps defined in //bazel:deps.bzl for WORKSPACE users.\n-perfetto_deps = use_extension(\n-    \"//bazel:perfetto_deps.bzl\",\n-    \"perfetto_deps\",\n-)\n-use_repo(\n-    perfetto_deps,\n-    \"perfetto_dep_expat\",\n-    \"perfetto_dep_linenoise\",\n-    \"perfetto_dep_llvm_demangle\",\n-    \"perfetto_dep_open_csd\",\n-    \"perfetto_dep_sqlite\",\n-    \"perfetto_dep_sqlite_src\",\n-    \"perfetto_dep_zlib\",\n-)\n-\n-# Maven dependencies for Android instrumentation tests.\n-# Use a unique name to avoid conflicts with other modules (e.g., bazel_worker_java).\n-maven = use_extension(\n-    \"@rules_jvm_external//:extensions.bzl\",\n-    \"maven\",\n-)\n-maven.install(\n-    name = \"perfetto_maven\",\n-    artifacts = [\n-        \"androidx.test:runner:1.6.2\",\n-        \"androidx.test:monitor:1.7.2\",\n-        \"com.google.truth:truth:1.4.4\",\n-        \"junit:junit:4.13.2\",\n-        \"androidx.test.ext:junit:1.2.1\",\n-    ],\n-    repositories = [\n-        \"https://maven.google.com\",\n-        \"https://repo1.maven.org/maven2\",\n-    ],\n-    # Use rules_android's aar_import to avoid toolchain type mismatch.\n-    aar_import_bzl_label = \"@rules_android//rules:rules.bzl\",\n-)\n-use_repo(maven, \"perfetto_maven\")\ndiff --git a/bazel/rules.bzl b/bazel/rules.bzl\nindex 563382a18b..d7e77cf214 100644\n--- a/bazel/rules.bzl\n+++ b/bazel/rules.bzl\n@@ -13,9 +13,7 @@\n # limitations under the License.\n \n load(\"@perfetto//bazel:proto_gen.bzl\", \"proto_descriptor_gen\", \"proto_gen\")\n-load(\"@perfetto//bazel:run_ait_with_adb.bzl\", \"android_instrumentation_test\")\n load(\"@perfetto_cfg//:perfetto_cfg.bzl\", \"PERFETTO_CONFIG\")\n-load(\"@rules_android//android:rules.bzl\", \"android_binary\", \"android_library\")\n \n # +----------------------------------------------------------------------------+\n # | Base C++ rules.                                                            |\n@@ -112,12 +110,10 @@ def perfetto_jspb_proto_library(**kwargs):\n # | Android-related rules                                                      |\n # +----------------------------------------------------------------------------+\n def perfetto_android_binary(**kwargs):\n-    if not _rule_override(\"android_binary\", **kwargs):\n-        android_binary(**kwargs)\n+    pass\n \n def perfetto_android_library(**kwargs):\n-    if not _rule_override(\"android_library\", **kwargs):\n-        android_library(**kwargs)\n+    pass\n \n def perfetto_android_jni_library(**kwargs):\n     if not _rule_override(\"android_jni_library\", **kwargs):\n@@ -170,8 +166,7 @@ def _perfetto_android_jni_library(\n     )\n \n def perfetto_android_instrumentation_test(**kwargs):\n-    if not _rule_override(\"android_instrumentation_test\", **kwargs):\n-        android_instrumentation_test(**kwargs)\n+    pass\n \n # +----------------------------------------------------------------------------+\n # | Misc rules.                                                                |\n"
  },
  {
    "path": "patches/perfetto/0002-disable-info-level-logging.patch",
    "content": "From cf26a646ce050b734771bfc212e6c9f1cc9f7f14 Mon Sep 17 00:00:00 2001\nFrom: Dan Carney <dcarney@cloudflare.com>\nDate: Wed, 17 Dec 2025 10:59:18 +0000\nSubject: disable info level logging\n\n\ndiff --git a/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h b/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h\nindex a9bfdbc2aa..278785fb7b 100644\n--- a/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h\n+++ b/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h\n@@ -36,7 +36,7 @@\n #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_FORCE_DCHECK_ON() (0)\n #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_FORCE_DCHECK_OFF() (1)\n #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_HTTP_ADDITIONAL_CORS_ORIGINS() (\"\")\n-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_VERBOSE_LOGS() (1)\n+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_VERBOSE_LOGS() (0)\n #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_VERSION_GEN() (1)\n #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_PERCENTILE() (1)\n #define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_LINENOISE() (1)\n-- \n2.51.0\n\n"
  },
  {
    "path": "patches/sqlite/0001-row-counts-plain.patch",
    "content": "diff -u5 -r sqlite-src-pristine/src/shell.c.in sqlite-src-modified/src/shell.c.in\n--- sqlite-src-pristine/src/shell.c.in\t2024-10-21 11:47:53\n+++ sqlite-src-modified/src/shell.c.in\t2024-11-05 08:16:15\n@@ -3411,10 +3411,15 @@\n     sqlite3_fprintf(out,\n            \"Number of times run:                 %d\\n\", iCur);\n     iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_MEMUSED, bReset);\n     sqlite3_fprintf(out,\n            \"Memory used by prepared stmt:        %d\\n\", iCur);\n+\n+    iCur = sqlite3_stmt_status(pArg->pStmt, LIBSQL_STMTSTATUS_ROWS_READ, bReset);\n+    sqlite3_fprintf(pArg->out, \"Rows read:                           %d\\n\", iCur);\n+    iCur = sqlite3_stmt_status(pArg->pStmt, LIBSQL_STMTSTATUS_ROWS_WRITTEN, bReset);\n+    sqlite3_fprintf(pArg->out, \"Rows written:                        %d\\n\", iCur);\n   }\n \n #ifdef __linux__\n   displayLinuxIoStats(pArg->out);\n #endif\ndiff -u5 -r sqlite-src-pristine/src/sqlite.h.in sqlite-src-modified/src/sqlite.h.in\n--- sqlite-src-pristine/src/sqlite.h.in\t2024-10-21 11:47:53\n+++ sqlite-src-modified/src/sqlite.h.in\t2024-11-05 08:13:50\n@@ -8907,20 +8907,32 @@\n ** used to store the prepared statement.  ^This value is not actually\n ** a counter, and so the resetFlg parameter to sqlite3_stmt_status()\n ** is ignored when the opcode is SQLITE_STMTSTATUS_MEMUSED.\n ** </dd>\n ** </dl>\n+**\n+** [[LIBSQL_STMTSTATUS_ROWS_READ]]\n+** [[LIBSQL_STMTSTATUS_ROWS_WRITTEN]] \n+** <dt>LIBSQL_STMTSTATUS_ROWS_READ<br>\n+** LIBSQL_STMTSTATUS_ROWS_WRITTEN</dt>\n+** <dd>^LIBSQL_STMTSTATUS_ROWS_READ is the number of rows read when executing\n+** this statement. LIBSQL_STMTSTATUS_ROWS_WRITTEN value is the number of\n+** rows written.\n */\n #define SQLITE_STMTSTATUS_FULLSCAN_STEP     1\n #define SQLITE_STMTSTATUS_SORT              2\n #define SQLITE_STMTSTATUS_AUTOINDEX         3\n #define SQLITE_STMTSTATUS_VM_STEP           4\n #define SQLITE_STMTSTATUS_REPREPARE         5\n #define SQLITE_STMTSTATUS_RUN               6\n #define SQLITE_STMTSTATUS_FILTER_MISS       7\n #define SQLITE_STMTSTATUS_FILTER_HIT        8\n #define SQLITE_STMTSTATUS_MEMUSED           99\n+\n+#define LIBSQL_STMTSTATUS_BASE              1024\n+#define LIBSQL_STMTSTATUS_ROWS_READ         LIBSQL_STMTSTATUS_BASE + 1\n+#define LIBSQL_STMTSTATUS_ROWS_WRITTEN      LIBSQL_STMTSTATUS_BASE + 2\n \n /*\n ** CAPI3REF: Custom Page Cache Object\n **\n ** The sqlite3_pcache type is opaque.  It is implemented by\ndiff -u5 -r sqlite-src-pristine/src/vdbe.c sqlite-src-modified/src/vdbe.c\n--- sqlite-src-pristine/src/vdbe.c\t2024-10-21 11:47:53\n+++ sqlite-src-modified/src/vdbe.c\t2024-11-05 08:13:50\n@@ -3737,10 +3737,11 @@\n   if( pOp->p3 ){\n     nEntry = sqlite3BtreeRowCountEst(pCrsr);\n   }else{\n     nEntry = 0;  /* Not needed.  Only used to silence a warning. */\n     rc = sqlite3BtreeCount(db, pCrsr, &nEntry);\n+    p->aLibsqlCounter[LIBSQL_STMTSTATUS_ROWS_READ - LIBSQL_STMTSTATUS_BASE] += nEntry;\n     if( rc ) goto abort_due_to_error;\n   }\n   pOut = out2Prerelease(p, pOp);\n   pOut->u.i = nEntry;\n   goto check_for_interrupt;\n@@ -4900,10 +4901,11 @@\n     if( eqOnly && r.eqSeen==0 ){\n       assert( res!=0 );\n       goto seek_not_found;\n     }\n   }\n+  p->aLibsqlCounter[LIBSQL_STMTSTATUS_ROWS_READ - LIBSQL_STMTSTATUS_BASE]++;\n #ifdef SQLITE_TEST\n   sqlite3_search_count++;\n #endif\n   if( oc>=OP_SeekGE ){  assert( oc==OP_SeekGE || oc==OP_SeekGT );\n     if( res<0 || (res==0 && oc==OP_SeekGT) ){\n@@ -5470,10 +5472,11 @@\n   pC->nullRow = 0;\n   pC->cacheStatus = CACHE_STALE;\n   pC->deferredMoveto = 0;\n   VdbeBranchTaken(res!=0,2);\n   pC->seekResult = res;\n+  p->aLibsqlCounter[LIBSQL_STMTSTATUS_ROWS_READ - LIBSQL_STMTSTATUS_BASE]++;\n   if( res!=0 ){\n     assert( rc==SQLITE_OK );\n     if( pOp->p2==0 ){\n       rc = SQLITE_CORRUPT_BKPT;\n     }else{\n@@ -5727,10 +5730,11 @@\n   }\n   if( pOp->p5 & OPFLAG_ISNOOP ) break;\n #endif\n \n   assert( (pOp->p5 & OPFLAG_LASTROWID)==0 || (pOp->p5 & OPFLAG_NCHANGE)!=0 );\n+  if (!pC->isEphemeral) p->aLibsqlCounter[LIBSQL_STMTSTATUS_ROWS_WRITTEN - LIBSQL_STMTSTATUS_BASE]++;\n   if( pOp->p5 & OPFLAG_NCHANGE ){\n     p->nChange++;\n     if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = x.nKey;\n   }\n   assert( (pData->flags & (MEM_Blob|MEM_Str))!=0 || pData->n==0 );\n@@ -5920,10 +5924,11 @@\n   pC->seekResult = 0;\n   if( rc ) goto abort_due_to_error;\n \n   /* Invoke the update-hook if required. */\n   if( opflags & OPFLAG_NCHANGE ){\n+    if (!pC->isEphemeral) p->aLibsqlCounter[LIBSQL_STMTSTATUS_ROWS_WRITTEN - LIBSQL_STMTSTATUS_BASE]++;\n     p->nChange++;\n     if( db->xUpdateCallback && ALWAYS(pTab!=0) && HasRowid(pTab) ){\n       db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE, zDb, pTab->zName,\n           pC->movetoTarget);\n       assert( pC->iDb>=0 );\n@@ -6207,10 +6212,11 @@\n   rc = sqlite3BtreeLast(pCrsr, &res);\n   pC->nullRow = (u8)res;\n   pC->deferredMoveto = 0;\n   pC->cacheStatus = CACHE_STALE;\n   if( rc ) goto abort_due_to_error;\n+  p->aLibsqlCounter[LIBSQL_STMTSTATUS_ROWS_READ - LIBSQL_STMTSTATUS_BASE]++;\n   if( pOp->p2>0 ){\n     VdbeBranchTaken(res!=0,2);\n     if( res ) goto jump_to_p2;\n   }\n   break;\n@@ -6326,10 +6332,11 @@\n     pC->deferredMoveto = 0;\n     pC->cacheStatus = CACHE_STALE;\n   }\n   if( rc ) goto abort_due_to_error;\n   pC->nullRow = (u8)res;\n+  p->aLibsqlCounter[LIBSQL_STMTSTATUS_ROWS_READ - LIBSQL_STMTSTATUS_BASE]++;\n   if( pOp->p2>0 ){\n     VdbeBranchTaken(res!=0,2);\n     if( res ) goto jump_to_p2;\n   }\n   break;\n@@ -6431,10 +6438,11 @@\n   pC->cacheStatus = CACHE_STALE;\n   VdbeBranchTaken(rc==SQLITE_OK,2);\n   if( rc==SQLITE_OK ){\n     pC->nullRow = 0;\n     p->aCounter[pOp->p5]++;\n+    p->aLibsqlCounter[LIBSQL_STMTSTATUS_ROWS_READ - LIBSQL_STMTSTATUS_BASE]++;\n #ifdef SQLITE_TEST\n     sqlite3_search_count++;\n #endif\n     goto jump_to_p2_and_check_for_interrupt;\n   }\n@@ -6482,10 +6490,11 @@\n   assert( pC!=0 );\n   assert( !isSorter(pC) );\n   pIn2 = &aMem[pOp->p2];\n   assert( (pIn2->flags & MEM_Blob) || (pOp->p5 & OPFLAG_PREFORMAT) );\n   if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++;\n+  if (!pC->isEphemeral) p->aLibsqlCounter[LIBSQL_STMTSTATUS_ROWS_WRITTEN - LIBSQL_STMTSTATUS_BASE]++;\n   assert( pC->eCurType==CURTYPE_BTREE );\n   assert( pC->isTable==0 );\n   rc = ExpandBlob(pIn2);\n   if( rc ) goto abort_due_to_error;\n   x.nKey = pIn2->n;\n@@ -6882,10 +6891,11 @@\n   assert( p->readOnly==0 );\n   assert( DbMaskTest(p->btreeMask, pOp->p2) );\n   rc = sqlite3BtreeClearTable(db->aDb[pOp->p2].pBt, (u32)pOp->p1, &nChange);\n   if( pOp->p3 ){\n     p->nChange += nChange;\n+    p->aLibsqlCounter[LIBSQL_STMTSTATUS_ROWS_WRITTEN - LIBSQL_STMTSTATUS_BASE] += nChange;\n     if( pOp->p3>0 ){\n       assert( memIsValid(&aMem[pOp->p3]) );\n       memAboutToChange(p, &aMem[pOp->p3]);\n       aMem[pOp->p3].u.i += nChange;\n     }\n@@ -8466,10 +8476,11 @@\n   ** some other method is next invoked on the save virtual table cursor.\n   */\n   rc = pModule->xNext(pCur->uc.pVCur);\n   sqlite3VtabImportErrmsg(p, pVtab);\n   if( rc ) goto abort_due_to_error;\n+  p->aLibsqlCounter[LIBSQL_STMTSTATUS_ROWS_READ - LIBSQL_STMTSTATUS_BASE]++;\n   res = pModule->xEof(pCur->uc.pVCur);\n   VdbeBranchTaken(!res,2);\n   if( !res ){\n     /* If there is data, jump to P2 */\n     goto jump_to_p2_and_check_for_interrupt;\n@@ -8587,10 +8598,11 @@\n         rc = SQLITE_OK;\n       }else{\n         p->errorAction = ((pOp->p5==OE_Replace) ? OE_Abort : pOp->p5);\n       }\n     }else{\n+      p->aLibsqlCounter[LIBSQL_STMTSTATUS_ROWS_WRITTEN - LIBSQL_STMTSTATUS_BASE]++;\n       p->nChange++;\n     }\n     if( rc ) goto abort_due_to_error;\n   }\n   break;\ndiff -u5 -r sqlite-src-pristine/src/vdbeInt.h sqlite-src-modified/src/vdbeInt.h\n--- sqlite-src-pristine/src/vdbeInt.h\t2024-10-21 11:47:53\n+++ sqlite-src-modified/src/vdbeInt.h\t2024-11-05 08:13:50\n@@ -496,10 +496,11 @@\n   bft bIsReader:1;        /* True for statements that read */\n   bft haveEqpOps:1;       /* Bytecode supports EXPLAIN QUERY PLAN */\n   yDbMask btreeMask;      /* Bitmask of db->aDb[] entries referenced */\n   yDbMask lockMask;       /* Subset of btreeMask that requires a lock */\n   u32 aCounter[9];        /* Counters used by sqlite3_stmt_status() */\n+  u32 aLibsqlCounter[3];  /* libSQL extension: Counters used by sqlite3_stmt_status()*/\n   char *zSql;             /* Text of the SQL statement that generated this */\n #ifdef SQLITE_ENABLE_NORMALIZE\n   char *zNormSql;         /* Normalization of the associated SQL statement */\n   DblquoteStr *pDblStr;   /* List of double-quoted string literals */\n #endif\ndiff -u5 -r sqlite-src-pristine/src/vdbeapi.c sqlite-src-modified/src/vdbeapi.c\n--- sqlite-src-pristine/src/vdbeapi.c\t2024-10-21 11:47:53\n+++ sqlite-src-modified/src/vdbeapi.c\t2024-11-05 08:13:50\n@@ -2072,11 +2072,11 @@\n int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){\n   Vdbe *pVdbe = (Vdbe*)pStmt;\n   u32 v;\n #ifdef SQLITE_ENABLE_API_ARMOR\n   if( !pStmt\n-   || (op!=SQLITE_STMTSTATUS_MEMUSED && (op<0||op>=ArraySize(pVdbe->aCounter)))\n+   || (op!=SQLITE_STMTSTATUS_MEMUSED && (op<0||(op>=ArraySize(pVdbe->aCounter)&&op<LIBSQL_STMTSTATUS_BASE)))\n   ){\n     (void)SQLITE_MISUSE_BKPT;\n     return 0;\n   }\n #endif\n@@ -2089,10 +2089,13 @@\n     db->lookaside.pEnd = db->lookaside.pStart;\n     sqlite3VdbeDelete(pVdbe);\n     db->pnBytesFreed = 0;\n     db->lookaside.pEnd = db->lookaside.pTrueEnd;\n     sqlite3_mutex_leave(db->mutex);\n+  }else if( op>=LIBSQL_STMTSTATUS_BASE ){\n+    v = pVdbe->aLibsqlCounter[op - LIBSQL_STMTSTATUS_BASE];\n+    if( resetFlag ) pVdbe->aLibsqlCounter[op - LIBSQL_STMTSTATUS_BASE] = 0;\n   }else{\n     v = pVdbe->aCounter[op];\n     if( resetFlag ) pVdbe->aCounter[op] = 0;\n   }\n   return (int)v;\n"
  },
  {
    "path": "patches/sqlite/0002-macOS-missing-PATH-fix.patch",
    "content": "diff --color -u5 -r sqlite-src-3440000-pristine/tool/mksqlite3c.tcl sqlite-src-3440000-modified/tool/mksqlite3c.tcl\n--- sqlite-src-3440000-pristine/tool/mksqlite3c.tcl\t2023-11-01 07:31:37\n+++ sqlite-src-3440000-modified/tool/mksqlite3c.tcl\t2024-03-14 17:36:55\n@@ -84,11 +84,14 @@\n set fname sqlite3.c\n if {$enable_recover} { set fname sqlite3r.c }\n set out [open $fname w]\n # Force the output to use unix line endings, even on Windows.\n fconfigure $out -translation lf\n-set today [clock format [clock seconds] -format \"%Y-%m-%d %H:%M:%S UTC\" -gmt 1]\n+# The command below results in \"couldn't find HOME environment variable to\n+# expand path\" errors on macOS CI runs. today is unused, so it is safe to\n+# comment it out.\n+# set today [clock format [clock seconds] -format \"%Y-%m-%d %H:%M:%S UTC\" -gmt 1]\n puts $out [subst \\\n {/******************************************************************************\n ** This file is an amalgamation of many separate C source files from SQLite\n ** version $VERSION.  By combining all the individual C code files into this\n ** single large file, the entire code can be compiled as a single translation\n"
  },
  {
    "path": "patches/sqlite/0003-sqlite-complete-early-exit.patch",
    "content": "--- sqlite-src-pristine/src/complete.c\t2023-11-01 22:31:37\n+++ sqlite-src-modified/src/complete.c\t2024-03-14 12:51:32\n@@ -99,14 +99,21 @@\n **\n ** If we compile with SQLITE_OMIT_TRIGGER, all of the computation needed\n ** to recognize the end of a trigger can be omitted.  All we have to do\n ** is look for a semicolon that is not part of an string or comment.\n */\n-int sqlite3_complete(const char *zSql){\n+int sqlite3_complete(const char *zSql) {\n+    return sqlite3_complete_length(zSql, 0) != 0;\n+}\n+\n+int sqlite3_complete_length(const char *zSql, int firstOnly){\n   u8 state = 0;   /* Current state, using numbers defined in header comment */\n   u8 token;       /* Value of the next token */\n\n+  /* Store the original pointer for later index calculations */\n+  const char *initialZSql = zSql;\n+\n #ifndef SQLITE_OMIT_TRIGGER\n   /* A complex statement machine used to detect the end of a CREATE TRIGGER\n   ** statement.  This is the normal case.\n   */\n   static const u8 trans[8][8] = {\n@@ -254,12 +261,16 @@\n         break;\n       }\n     }\n     state = trans[state][token];\n     zSql++;\n+\n+    // Once we have found a single complete statement, return its length\n+    if (firstOnly && state==1) return zSql - initialZSql;\n   }\n-  return state==1;\n+  // Otherwise only return the length if the entire input is valid\n+  return state==1 ? zSql - initialZSql : 0;\n }\n\n #ifndef SQLITE_OMIT_UTF16\n /*\n ** This routine is the same as the sqlite3_complete() routine described\ndiff -u5 -r sqlite-src-pristine/src/sqlite.h.in sqlite-src-modified/src/sqlite.h.in\n--- sqlite-src-pristine/src/sqlite.h.in\t2023-11-01 22:31:37\n+++ sqlite-src-modified/src/sqlite.h.in\t2024-03-12 12:48:37\n@@ -2775,10 +2775,14 @@\n ** UTF-16 string in native byte order.\n */\n int sqlite3_complete(const char *sql);\n int sqlite3_complete16(const void *sql);\n\n+// workerd addition: measure the length of the first (or all) complete\n+// statements in the input.\n+int sqlite3_complete_length(const char *sql, int firstOnly);\n+\n /*\n ** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors\n ** KEYWORDS: {busy-handler callback} {busy handler}\n ** METHOD: sqlite3\n **\n"
  },
  {
    "path": "patches/sqlite/0004-invalid-wal-on-rollback-fix.patch",
    "content": "diff -u5 -r sqlite-src-pristine/src/wal.c sqlite-src-modified/src/wal.c\n--- sqlite-src-pristine/src/wal.c\t2024-10-21 09:47:53.000000000 -0700\n+++ sqlite-src-modified/src/wal.c\t2025-06-17 23:10:43.657220118 -0700\n@@ -3760,10 +3760,11 @@\n         rc = xUndo(pUndoCtx, walFramePgno(pWal, iFrame));\n       }\n       if( iMax!=pWal->hdr.mxFrame ) walCleanupHash(pWal);\n     }\n     SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; )\n+    pWal->iReCksum = 0;\n   }\n   return rc;\n }\n \n /*\n@@ -3807,10 +3808,13 @@\n     pWal->hdr.aFrameCksum[1] = aWalData[2];\n     SEH_TRY {\n       walCleanupHash(pWal);\n     }\n     SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; )\n+    if( pWal->iReCksum>pWal->hdr.mxFrame ){\n+      pWal->iReCksum = 0;\n+    }\n   }\n \n   return rc;\n }\n \n"
  },
  {
    "path": "patches/sqlite/README.md",
    "content": "# Patching SQLite\n\nThe patches for SQLite are applied against the plain source, not the amalgamated source.\n\nSQLite comes in two forms. First, there's the \"plain\" form. It looks a\nlot like a typical open-source C project: there's a bunch of .c and .h\nfiles, a Makefile, a configure script, and various other files.\nworkerd consumes this form.\n\nSecond, there's the \"amalgamation\". This is all of SQLite combined\ninto two .c and two .h files. https://www.sqlite.org/download.html\nputs the amalgamation front and center, but don't be fooled.\n\nTo update to a new SQLite version, obtain the new SQLite in plain\nform. Apply the patches to SQLite, fixing as necessary, and replace\nany patches that needed modification with their fixed versions.\n\nExample, assuming the new SQLite has been downloaded into the current\ndirectory:\n\n```bash\nexport VERSION=3470000\nunzip sqlite-src-$VERSION.zip\nmv sqlite-src-$VERSION sqlite-src-pristine\nunzip sqlite-src-$VERSION.zip  # yes, again\nmv sqlite-src-$VERSION sqlite-src-modified\n```\n\nNow patch:\n\n```bash\ncd sqlite-src-modified\npatch -p1 < /path/to/workerd/patches/sqlite/0001-row-counts-plain.patch\n./configure && make test\n```\n\nMake sure the tests pass. If the patch needed any modification, regenerate it:\n\n```bash\ndiff -u5 -r sqlite-src-pristine sqlite-src-modified \\\n    | grep -v \"Only in sqlite-src-modified\" \\\n    > /path/to/workerd/patches/sqlite/0001-row-counts-plain.patch\n```\n\nRepeat for each patch.\n"
  },
  {
    "path": "patches/v8/0001-Allow-manually-setting-ValueDeserializer-format-vers.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Alex Robinson <arobinson@cloudflare.com>\nDate: Wed, 2 Mar 2022 15:58:04 -0600\nSubject: Allow manually setting ValueDeserializer format version\n\nFor many years, V8's serialization version didn't change. In the meantime,\nwe accidentally stored data that was missing a version header. This patch\nallows us to start using a header while still being able to correctly\ninterpret our existing stored data that was missing a header, using code\nlike:\n\n  auto maybeHeader = deserializer.ReadHeader(isolate->GetCurrentContext());\n  KJ_ASSERT(maybeHeader.IsNothing() || maybeHeader.FromJust());\n  if (deserializer.GetWireFormatVersion() == 0) {\n    deserializer.SetWireFormatVersion(13);\n  }\n  auto maybeValue = deserializer.ReadValue(isolate->GetCurrentContext());\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/include/v8-value-serializer.h b/include/v8-value-serializer.h\nindex 0cb3e045bc46ec732956318b980e749d1847d06d..40ad805c7970cc9379e69f046205836dbd760373 100644\n--- a/include/v8-value-serializer.h\n+++ b/include/v8-value-serializer.h\n@@ -293,6 +293,13 @@ class V8_EXPORT ValueDeserializer {\n    */\n   uint32_t GetWireFormatVersion() const;\n \n+  /**\n+   * Sets the underlying wire format version. Should only be used if there's no\n+   * header specifying the wire format version but you're confident you know\n+   * what version was used to serialize the data.\n+   */\n+  void SetWireFormatVersion(uint32_t version);\n+\n   /**\n    * Reads raw data in various common formats to the buffer.\n    * Note that integer types are read in base-128 varint format, not with a\ndiff --git a/src/api/api.cc b/src/api/api.cc\nindex 25e7482f501e82dc34420434623e4a5e55c57e39..a66d8d8d975e21c1fc98912e6f21c44437616b3c 100644\n--- a/src/api/api.cc\n+++ b/src/api/api.cc\n@@ -3625,6 +3625,10 @@ uint32_t ValueDeserializer::GetWireFormatVersion() const {\n   return private_->deserializer.GetWireFormatVersion();\n }\n \n+void ValueDeserializer::SetWireFormatVersion(uint32_t version) {\n+  private_->deserializer.SetWireFormatVersion(version);\n+}\n+\n MaybeLocal<Value> ValueDeserializer::ReadValue(Local<Context> context) {\n   PrepareForExecutionScope api_scope{context,\n                                      RCCId::kAPI_ValueDeserializer_ReadValue};\ndiff --git a/src/objects/value-serializer.h b/src/objects/value-serializer.h\nindex 43dc34d6189d7332e019db758760eb5c71a9fe99..b84dcc77d4060d13c389b4afed101847c85998da 100644\n--- a/src/objects/value-serializer.h\n+++ b/src/objects/value-serializer.h\n@@ -221,6 +221,13 @@ class ValueDeserializer {\n    */\n   uint32_t GetWireFormatVersion() const { return version_; }\n \n+  /*\n+   * Sets the underlying wire format version. Should only be used if there's no\n+   * header specifying the wire format version but you're confident you know\n+   * what version was used to serialize the data.\n+   */\n+  void SetWireFormatVersion(uint32_t version) { version_ = version; }\n+\n   /*\n    * Deserializes a V8 object from the buffer.\n    */\n"
  },
  {
    "path": "patches/v8/0002-Allow-manually-setting-ValueSerializer-format-versio.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: James M Snell <jasnell@gmail.com>\nDate: Wed, 16 Mar 2022 08:59:21 -0700\nSubject: Allow manually setting ValueSerializer format version\n\nRefs: https://bitbucket.cfdata.org/projects/MIRRORS/repos/v8/pull-requests/2/overview\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/include/v8-value-serializer.h b/include/v8-value-serializer.h\nindex 40ad805c7970cc9379e69f046205836dbd760373..596be18adeb3a5a81794aaa44b1d347dec6c0c7d 100644\n--- a/include/v8-value-serializer.h\n+++ b/include/v8-value-serializer.h\n@@ -154,6 +154,11 @@ class V8_EXPORT ValueSerializer {\n   ValueSerializer(Isolate* isolate, Delegate* delegate);\n   ~ValueSerializer();\n \n+  /**\n+   * Instruct the ValueSerializer to write at a specific serializer version.\n+   */\n+  Maybe<bool> SetWriteVersion(uint32_t version);\n+\n   /**\n    * Writes out a header, which includes the format version.\n    */\ndiff --git a/src/api/api.cc b/src/api/api.cc\nindex a66d8d8d975e21c1fc98912e6f21c44437616b3c..54837358abfd6c30ce3298dc809d2d4d355983a6 100644\n--- a/src/api/api.cc\n+++ b/src/api/api.cc\n@@ -3497,6 +3497,10 @@ ValueSerializer::ValueSerializer(Isolate* v8_isolate, Delegate* delegate)\n \n ValueSerializer::~ValueSerializer() { delete private_; }\n \n+Maybe<bool> ValueSerializer::SetWriteVersion(uint32_t version) {\n+  return private_->serializer.SetWriteVersion(version);\n+}\n+\n void ValueSerializer::WriteHeader() { private_->serializer.WriteHeader(); }\n \n void ValueSerializer::SetTreatArrayBufferViewsAsHostObjects(bool mode) {\ndiff --git a/src/objects/value-serializer.cc b/src/objects/value-serializer.cc\nindex def468a7392071fdfc5d2cfcfc9eba35f5e34a7a..4ef0c1e7fd227e8943261b64a97e6d6088928621 100644\n--- a/src/objects/value-serializer.cc\n+++ b/src/objects/value-serializer.cc\n@@ -299,6 +299,7 @@ ValueSerializer::ValueSerializer(Isolate* isolate,\n     : isolate_(isolate),\n       delegate_(delegate),\n       zone_(isolate->allocator(), ZONE_NAME),\n+      version_(kLatestVersion),\n       id_map_(isolate->heap(), ZoneAllocationPolicy(&zone_)),\n       array_buffer_transfer_map_(isolate->heap(),\n                                  ZoneAllocationPolicy(&zone_)) {\n@@ -318,9 +319,17 @@ ValueSerializer::~ValueSerializer() {\n   }\n }\n \n+Maybe<bool> ValueSerializer::SetWriteVersion(uint32_t version) {\n+  if (version < 13 || version > kLatestVersion) {\n+    return ThrowDataCloneError(MessageTemplate::kDataCloneError);\n+  }\n+  version_ = version;\n+  return Just(true);\n+}\n+\n void ValueSerializer::WriteHeader() {\n   WriteTag(SerializationTag::kVersion);\n-  WriteVarint(kLatestVersion);\n+  WriteVarint(version_);\n }\n \n void ValueSerializer::SetTreatArrayBufferViewsAsHostObjects(bool mode) {\n@@ -1122,10 +1131,12 @@ Maybe<bool> ValueSerializer::WriteJSArrayBufferView(\n   WriteVarint(static_cast<uint8_t>(tag));\n   WriteVarint(static_cast<uint32_t>(view->byte_offset()));\n   WriteVarint(static_cast<uint32_t>(view->byte_length()));\n-  uint32_t flags =\n+  if (version_ >= 14) {\n+    uint32_t flags =\n       JSArrayBufferViewIsLengthTracking::encode(view->is_length_tracking()) |\n       JSArrayBufferViewIsBackedByRab::encode(view->is_backed_by_rab());\n-  WriteVarint(flags);\n+    WriteVarint(flags);\n+  }\n   return ThrowIfOutOfMemory();\n }\n \ndiff --git a/src/objects/value-serializer.h b/src/objects/value-serializer.h\nindex b84dcc77d4060d13c389b4afed101847c85998da..06475f7b9c2a797066f5cfd32b232e5aa55f1f75 100644\n--- a/src/objects/value-serializer.h\n+++ b/src/objects/value-serializer.h\n@@ -54,6 +54,11 @@ class ValueSerializer {\n   ValueSerializer(const ValueSerializer&) = delete;\n   ValueSerializer& operator=(const ValueSerializer&) = delete;\n \n+  /*\n+   * Instruct the ValueSerializer to write at a specific serializer version.\n+   */\n+  Maybe<bool> SetWriteVersion(uint32_t version);\n+\n   /*\n    * Writes out a header, which includes the format version.\n    */\n@@ -182,6 +187,7 @@ class ValueSerializer {\n   bool treat_array_buffer_views_as_host_objects_ = false;\n   bool out_of_memory_ = false;\n   Zone zone_;\n+  uint32_t version_;\n \n   // To avoid extra lookups in the identity map, ID+1 is actually stored in the\n   // map (checking if the used identity is zero is the fast way of checking if\n"
  },
  {
    "path": "patches/v8/0003-Allow-Windows-builds-under-Bazel.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Brendan Coll <bcoll@cloudflare.com>\nDate: Thu, 16 Mar 2023 11:56:10 +0000\nSubject: Allow Windows builds under Bazel\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/BUILD.bazel b/BUILD.bazel\nindex 85f31b7a31f604c68693f99d34b4a0017f54e3a1..cd8a37e2c5cc5e1196275b8de932d14de80d693c 100644\n--- a/BUILD.bazel\n+++ b/BUILD.bazel\n@@ -4068,6 +4068,8 @@ filegroup(\n         \"@v8//bazel/config:is_inline_asm_x64\": [\"src/heap/base/asm/x64/push_registers_asm.cc\"],\n         \"@v8//bazel/config:is_inline_asm_arm\": [\"src/heap/base/asm/arm/push_registers_asm.cc\"],\n         \"@v8//bazel/config:is_inline_asm_arm64\": [\"src/heap/base/asm/arm64/push_registers_asm.cc\"],\n+        \"@v8//bazel/config:is_windows_clang_asm_x64\": [\"src/heap/base/asm/x64/push_registers_asm.cc\"],\n+        \"@v8//bazel/config:is_windows_clang_asm_arm64\": [\"src/heap/base/asm/arm64/push_registers_asm.cc\"],\n         \"@v8//bazel/config:is_inline_asm_s390x\": [\"src/heap/base/asm/s390/push_registers_asm.cc\"],\n         \"@v8//bazel/config:is_inline_asm_riscv64\": [\"src/heap/base/asm/riscv64/push_registers_asm.cc\"],\n         \"@v8//bazel/config:is_inline_asm_ppc64le\": [\"src/heap/base/asm/ppc/push_registers_asm.cc\"],\ndiff --git a/bazel/config/BUILD.bazel b/bazel/config/BUILD.bazel\nindex 17e379b8e27baaa33f58ee852cfd919a9b39d729..7c2154b8ac2e817abebf89f5fa7d30354591d381 100644\n--- a/bazel/config/BUILD.bazel\n+++ b/bazel/config/BUILD.bazel\n@@ -270,6 +270,7 @@ selects.config_setting_group(\n     match_all = [\n         \":is_windows\",\n         \":is_x64\",\n+        \":is_compiler_default\",\n     ],\n )\n \n@@ -278,6 +279,7 @@ selects.config_setting_group(\n     match_all = [\n         \":is_windows\",\n         \":is_ia32\",\n+        \":is_compiler_default\",\n     ],\n )\n \n@@ -286,6 +288,34 @@ selects.config_setting_group(\n     match_all = [\n         \":is_windows\",\n         \":is_arm64\",\n+        \":is_compiler_default\",\n+    ],\n+)\n+\n+selects.config_setting_group(\n+    name = \"is_windows_clang_asm_x64\",\n+    match_all = [\n+        \":is_windows\",\n+        \":is_x64\",\n+        \":is_compiler_clang_cl\",\n+    ],\n+)\n+\n+selects.config_setting_group(\n+    name = \"is_windows_clang_asm_ia32\",\n+    match_all = [\n+        \":is_windows\",\n+        \":is_ia32\",\n+        \":is_compiler_clang_cl\",\n+    ],\n+)\n+\n+selects.config_setting_group(\n+    name = \"is_windows_clang_asm_arm64\",\n+    match_all = [\n+        \":is_windows\",\n+        \":is_arm64\",\n+        \":is_compiler_clang_cl\",\n     ],\n )\n \n@@ -319,6 +349,13 @@ config_setting(\n     },\n )\n \n+config_setting(\n+    name = \"is_compiler_clang_cl\",\n+    flag_values = {\n+        \"@bazel_tools//tools/cpp:compiler\": \"clang-cl\",\n+    },\n+)\n+\n selects.config_setting_group(\n     name = \"is_clang\",\n     match_any = [\ndiff --git a/bazel/defs.bzl b/bazel/defs.bzl\nindex 9648e4a541f99ff824d488efc372dc7482491d05..4b512ce7b26d0345aa025b1f2086578c2a9c757c 100644\n--- a/bazel/defs.bzl\n+++ b/bazel/defs.bzl\n@@ -126,6 +126,24 @@ def _default_args():\n                 \"-Wno-unnecessary-virtual-specifier\",\n                 \"-isystem .\",\n             ],\n+            \"@v8//bazel/config:is_windows\": [\n+                ## From BUILD.gn: `if (is_win)`\n+                \"/wd4245\",\n+                \"/wd4267\",\n+                \"/wd4324\",\n+                \"/wd4701\",\n+                \"/wd4702\",\n+                \"/wd4703\",\n+                \"/wd4709\",\n+                \"/wd4714\",\n+                \"/wd4715\",\n+                \"/wd4718\",\n+                \"/wd4723\",\n+                \"/wd4724\",\n+                \"/wd4800\",\n+                ## From BUILD.icu\n+                \"/wd4005\",\n+            ],\n             \"//conditions:default\": [],\n         }) + select({\n             \"@v8//bazel/config:is_clang\": [\n@@ -175,13 +193,23 @@ def _default_args():\n             ],\n             \"//conditions:default\": [\n             ],\n+        }) + select({\n+            \"@v8//bazel/config:is_compiler_clang_cl\": [\n+                \"-Wno-invalid-offsetof\",\n+            ],\n+            \"//conditions:default\": [],\n         }),\n         includes = [\"include\"],\n         linkopts = select({\n             \"@v8//bazel/config:is_windows\": [\n+                # Increase the initial stack size. The default is 1MB, this is 2MB. This\n+                # applies only to executables and shared libraries produced by V8 since\n+                # ldflags are not pushed to dependants.\n+                \"/STACK:2097152\",\n                 \"Winmm.lib\",\n                 \"DbgHelp.lib\",\n                 \"Advapi32.lib\",\n+                \"Shell32.lib\",\n             ],\n             \"@v8//bazel/config:is_macos\": [\"-pthread\"],\n             \"//conditions:default\": [\"-Wl,--no-as-needed -ldl -latomic -pthread\"],\n"
  },
  {
    "path": "patches/v8/0004-Disable-bazel-whole-archive-build.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Felix Hanau <felix@cloudflare.com>\nDate: Tue, 11 Apr 2023 14:41:31 -0400\nSubject: Disable bazel whole-archive build\n\nV8's bazel configuration system adds the alwayslink parameter (equivalent\nto --Wl,-whole-archive on Linux) to all V8 libraries, causing all V8\nobject files to be included even if they are not referenced. This is more\naggressive than what's done in the GN build system and not needed for\nworkerd - disabling alwayslink improves the binary size by ~ 1MB as it\nallows the linker to eliminate unused symbols.\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/bazel/BUILD.icu b/bazel/BUILD.icu\nindex 5fda2f468492a7a69266dba40c0862b0e31fa7a1..5127ceb7b783b11d5750c01977d7e34606c39667 100644\n--- a/bazel/BUILD.icu\n+++ b/bazel/BUILD.icu\n@@ -54,7 +54,7 @@ cc_library(\n         \"U_ICUDATAENTRY_IN_COMMON\",\n     ],\n     tags = [\"requires-rtti\"],\n-    alwayslink = 1,\n+    alwayslink = 0,\n )\n \n cc_library(\n@@ -78,7 +78,7 @@ cc_library(\n         \"U_I18N_IMPLEMENTATION\",\n     ],\n     deps = [\":icuuc\"],\n-    alwayslink = 1,\n+    alwayslink = 0,\n )\n \n cc_library(\n"
  },
  {
    "path": "patches/v8/0005-Speed-up-V8-bazel-build-by-always-using-target-cfg.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Felix Hanau <felix@cloudflare.com>\nDate: Wed, 7 Jun 2023 21:40:54 -0400\nSubject: Speed up V8 bazel build by always using target cfg\n\nSee the workerd build cfg changes for rationale. This provides a significant\nspeedup for the build: Components like ICU were previously compiled in\nboth target and exec configurations as generator tools depend on them.\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/BUILD.bazel b/BUILD.bazel\nindex cd8a37e2c5cc5e1196275b8de932d14de80d693c..3c931da31d9006c200ef2a7ddc358150b2743675 100644\n--- a/BUILD.bazel\n+++ b/BUILD.bazel\n@@ -19,6 +19,7 @@ load(\n     \"v8_mksnapshot\",\n     \"v8_string\",\n     \"v8_torque_files\",\n+    \"genrule_target\",\n )\n load(\":bazel/v8-non-pointer-compression.bzl\", \"v8_binary_non_pointer_compression\")\n \n@@ -4469,22 +4470,20 @@ filegroup(\n     ],\n )\n \n-genrule(\n+genrule_target(\n     name = \"generated_bytecode_builtins_list\",\n     srcs = [],\n     outs = [\"builtins-generated/bytecodes-builtins-list.h\"],\n-    cmd = \"$(location :bytecode_builtins_list_generator) $@\",\n-    cmd_bat = \"$(location :bytecode_builtins_list_generator) $@\",\n-    tools = [\":bytecode_builtins_list_generator\"],\n+    args = [\"$(location :builtins-generated/bytecodes-builtins-list.h)\"],\n+    tool = \":bytecode_builtins_list_generator\",\n )\n \n-genrule(\n+genrule_target(\n     name = \"generated_regexp_special_case\",\n     srcs = [],\n     outs = [\"src/regexp/special-case.cc\"],\n-    cmd = \"$(location :regexp_special_case_generator) $@\",\n-    cmd_bat = \"$(location :regexp_special_case_generator) $@\",\n-    tools = [\":regexp_special_case_generator\"],\n+    args = [\"$(location :src/regexp/special-case.cc)\"],\n+    tool = \":regexp_special_case_generator\",\n )\n \n v8_mksnapshot(\n@@ -4705,7 +4704,6 @@ v8_binary(\n     srcs = [\n         \"src/regexp/gen-regexp-special-case.cc\",\n         \"src/regexp/special-case.h\",\n-        \":v8_shared_internal_headers\",\n     ],\n     copts = [\"-Wno-implicit-fallthrough\"],\n     defines = [\ndiff --git a/bazel/defs.bzl b/bazel/defs.bzl\nindex 4b512ce7b26d0345aa025b1f2086578c2a9c757c..48b9f03fb6e0333137ab6753e31dada7034aa1c2 100644\n--- a/bazel/defs.bzl\n+++ b/bazel/defs.bzl\n@@ -348,6 +348,15 @@ def v8_library(\n             **kwargs\n         )\n \n+def get_cfg():\n+    # Setting the configuration to \"target\" allows us to avoid compiling code used in both V8 and a\n+    # generator tool twice. For cross-compilation this would need to be set to \"exec\" manually.\n+    # Unfortunately bazel makes it very difficult to set the configuration at build time as macros\n+    # are resolved before select() can be resolved based on the command line. This could\n+    # alternatively be done by defining build targets and the rules used to declare them twice\n+    # (once for exec and for target).\n+    return \"target\"\n+\n # Use a single generator target for torque definitions and initializers. We can\n # split the set of outputs by using OutputGroupInfo, that way we do not need to\n # run the torque generator twice.\n@@ -416,7 +425,7 @@ _v8_torque_files = rule(\n         \"tool\": attr.label(\n             allow_files = True,\n             executable = True,\n-            cfg = \"exec\",\n+            cfg = get_cfg(),\n         ),\n         \"args\": attr.string_list(),\n     },\n@@ -517,13 +526,16 @@ _v8_mksnapshot = rule(\n             mandatory = True,\n             allow_files = True,\n             executable = True,\n-            cfg = \"exec\",\n+            cfg = get_cfg(),\n         ),\n         \"target_os\": attr.string(mandatory = True),\n         \"prefix\": attr.string(mandatory = True),\n         \"suffix\": attr.string(mandatory = True),\n     },\n-    cfg = v8_target_cpu_transition,\n+    # This allows specifying a CPU architecture on the command line to be used when compiling\n+    # mksnapshot. Disable this functionality as we do not use it and it makes cfg changes more\n+    # difficult.\n+    # cfg = v8_target_cpu_transition,\n )\n \n def v8_mksnapshot(name, args, suffix = \"\"):\n@@ -650,3 +662,34 @@ def v8_build_config(name, arch):\n         outs = [\"icu/\" + name + \".json\"],\n         cmd = \"echo '\" + build_config_content(cpu, \"true\") + \"' > \\\"$@\\\"\",\n     )\n+\n+# Clone of genrule, but set up to compile for target configuration. Use with care, this may not\n+# support all features of genrule(), but is sufficient for this use case.\n+# Derived from the tensorflow project, see workerd build/run_binary_target.bzl for details.\n+def _genrule_target_impl(ctx):\n+    tool = ctx.attr.tool[DefaultInfo].files_to_run.executable\n+    flags = [ ctx.expand_location(a) if \"$(location\" in a else a for a in ctx.attr.args ]\n+\n+    cmd = \" \".join([tool.path] + flags)\n+    ctx.actions.run_shell(\n+        inputs = ctx.files.srcs,\n+        outputs = ctx.outputs.outs,\n+        tools = [tool],\n+        use_default_shell_env = True,\n+        command = cmd,\n+    )\n+\n+genrule_target = rule(\n+    implementation = _genrule_target_impl,\n+    output_to_genfiles = True,\n+    attrs = {\n+        \"outs\": attr.output_list(mandatory = True),\n+        \"srcs\": attr.label_list(allow_files = True),\n+        \"args\": attr.string_list(),\n+        \"tool\": attr.label(\n+            executable = True,\n+            cfg = get_cfg(),\n+            mandatory = True\n+        ),\n+    },\n+)\n"
  },
  {
    "path": "patches/v8/0006-Implement-Promise-Context-Tagging.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: James M Snell <jasnell@gmail.com>\nDate: Thu, 22 Jun 2023 15:29:26 -0700\nSubject: Implement Promise Context Tagging\n\n\ndiff --git a/include/v8-callbacks.h b/include/v8-callbacks.h\nindex e5eba5a203b8bc4d0c05b1f0d6cbdffd352d4a06..cfba4bb26f865c0e38574f796200ffc5e0dc60fc 100644\n--- a/include/v8-callbacks.h\n+++ b/include/v8-callbacks.h\n@@ -528,6 +528,14 @@ using FilterETWSessionByURL2Callback = FilterETWSessionByURLResult (*)(\n     Local<Context> context, const std::string& etw_filter_payload);\n #endif  // V8_OS_WIN\n \n+/**\n+ * PromiseCrossContextCallback is called when following a promise and the\n+ * promise's context tag is not strictly equal to the isolate's current\n+ * promise context tag.\n+ */\n+using PromiseCrossContextCallback = MaybeLocal<Promise> (*)(\n+    Local<Context> context, Local<Promise> promise, Local<Object> tag);\n+\n }  // namespace v8\n \n #endif  // INCLUDE_V8_ISOLATE_CALLBACKS_H_\ndiff --git a/include/v8-isolate.h b/include/v8-isolate.h\nindex f929f13a4de8ffb5b1f1222aa5034d186a29963e..2346df282c5ec8661924b008aefb2f1854e4d54a 100644\n--- a/include/v8-isolate.h\n+++ b/include/v8-isolate.h\n@@ -1873,6 +1873,9 @@ class V8_EXPORT Isolate {\n    */\n   uint64_t GetHashSeed();\n \n+  class PromiseContextScope;\n+  void SetPromiseCrossContextCallback(PromiseCrossContextCallback callback);\n+\n   Isolate() = delete;\n   ~Isolate() = delete;\n   Isolate(const Isolate&) = delete;\n@@ -1920,6 +1923,19 @@ MaybeLocal<T> Isolate::GetDataFromSnapshotOnce(size_t index) {\n   return {};\n }\n \n+class Isolate::PromiseContextScope {\n+ public:\n+  PromiseContextScope(Isolate* isolate, v8::Local<v8::Object> tag);\n+  ~PromiseContextScope();\n+  PromiseContextScope(const PromiseContextScope&) = delete;\n+  PromiseContextScope(PromiseContextScope&&) = delete;\n+  PromiseContextScope& operator=(const PromiseContextScope&) = delete;\n+  PromiseContextScope& operator=(PromiseContextScope&&) = delete;\n+\n+ private:\n+  internal::Isolate* isolate_;\n+};\n+\n }  // namespace v8\n \n #endif  // INCLUDE_V8_ISOLATE_H_\ndiff --git a/src/api/api.cc b/src/api/api.cc\nindex 54837358abfd6c30ce3298dc809d2d4d355983a6..058b2862cf31ddc2dc010f9b247ec6a0ddb277f1 100644\n--- a/src/api/api.cc\n+++ b/src/api/api.cc\n@@ -12614,6 +12614,25 @@ std::string SourceLocation::ToString() const {\n       .str();\n }\n \n+void Isolate::SetPromiseCrossContextCallback(\n+    PromiseCrossContextCallback callback) {\n+  i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);\n+  isolate->set_promise_cross_context_callback(callback);\n+}\n+\n+Isolate::PromiseContextScope::PromiseContextScope(Isolate* isolate,\n+                                                  v8::Local<v8::Object> tag)\n+    : isolate_(reinterpret_cast<i::Isolate*>(isolate)) {\n+  DCHECK(!isolate_->has_promise_context_tag());\n+  DCHECK(!tag.IsEmpty());\n+  i::Handle<i::Object> handle = Utils::OpenHandle(*tag);\n+  isolate_->set_promise_context_tag(*handle);\n+}\n+\n+Isolate::PromiseContextScope::~PromiseContextScope() {\n+  isolate_->clear_promise_context_tag();\n+}\n+\n }  // namespace v8\n \n #ifdef ENABLE_SLOW_DCHECKS\ndiff --git a/src/builtins/promise-abstract-operations.tq b/src/builtins/promise-abstract-operations.tq\nindex f50b5b75a0f326656d73105774d5eaa4acb0e6f4..b7caf0137c5ff99d9ac990ec324a5f2c6e13b8bf 100644\n--- a/src/builtins/promise-abstract-operations.tq\n+++ b/src/builtins/promise-abstract-operations.tq\n@@ -20,6 +20,9 @@ extern transitioning runtime PromiseResolveAfterResolved(\n \n extern transitioning runtime PromiseRejectEventFromStack(\n     implicit context: Context)(JSPromise, JSAny): JSAny;\n+\n+extern transitioning runtime PromiseContextCheck(\n+    implicit context: Context)(JSPromise): JSPromise;\n }\n \n // https://tc39.es/ecma262/#sec-promise-abstract-operations\n@@ -457,12 +460,14 @@ transitioning macro PerformPromiseThenImpl(\n     // PromiseReaction holding both the onFulfilled and onRejected callbacks.\n     // Once the {promise} is resolved we decide on the concrete handler to\n     // push onto the microtask queue.\n+    const delegate = runtime::PromiseContextCheck(promise);\n     const promiseReactions =\n-        UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);\n+        UnsafeCast<(Zero | PromiseReaction)>(delegate.reactions_or_result);\n \n     const reaction = NewPromiseReaction(\n         promiseReactions, resultPromiseOrCapability, onFulfilled, onRejected);\n-    promise.reactions_or_result = reaction;\n+    delegate.reactions_or_result = reaction;\n+    delegate.SetHasHandler();\n   } else {\n     const reactionsOrResult = promise.reactions_or_result;\n     let microtask: PromiseReactionJobTask;\n@@ -484,8 +489,8 @@ transitioning macro PerformPromiseThenImpl(\n         }\n       }\n     EnqueueMicrotask(handlerContext, microtask);\n+    promise.SetHasHandler();\n   }\n-  promise.SetHasHandler();\n }\n \n transitioning javascript builtin PerformPromiseThenFunction(\ndiff --git a/src/builtins/promise-constructor.tq b/src/builtins/promise-constructor.tq\nindex 50677631b5399453eebc6b149272431f74b1fce6..c652bd836b27805865e0a902ef9cf7c1ff254646 100644\n--- a/src/builtins/promise-constructor.tq\n+++ b/src/builtins/promise-constructor.tq\n@@ -8,6 +8,9 @@\n namespace runtime {\n extern transitioning runtime PromiseHookInit(\n     implicit context: Context)(Object, Object): JSAny;\n+\n+extern transitioning runtime PromiseContextInit(\n+    implicit context: Context)(JSPromise): JSAny;\n }\n \n // https://tc39.es/ecma262/#sec-promise-constructor\n@@ -62,6 +65,7 @@ transitioning javascript builtin PromiseConstructor(\n     result = UnsafeCast<JSPromise>(\n         FastNewObject(context, promiseFun, UnsafeCast<JSReceiver>(newTarget)));\n     PromiseInit(result);\n+    runtime::PromiseContextInit(result);\n     RunAnyPromiseHookInit(result, Undefined);\n   }\n \ndiff --git a/src/builtins/promise-misc.tq b/src/builtins/promise-misc.tq\nindex 702a3e622bf4fa65e41caee2be3cdd481384e314..257b966dc3c67174acfde1208ede2d6342b5e074 100644\n--- a/src/builtins/promise-misc.tq\n+++ b/src/builtins/promise-misc.tq\n@@ -55,6 +55,7 @@ macro PromiseInit(promise: JSPromise): void {\n     is_silent: false,\n     async_task_id: kInvalidAsyncTaskId\n   });\n+  promise.context_tag = kZero;\n   promise_internal::ZeroOutEmbedderOffsets(promise);\n }\n \n@@ -74,6 +75,7 @@ macro InnerNewJSPromise(implicit context: Context)(): JSPromise {\n     is_silent: false,\n     async_task_id: kInvalidAsyncTaskId\n   });\n+  promise.context_tag = kZero;\n   return promise;\n }\n \n@@ -273,6 +275,7 @@ transitioning macro NewJSPromise(implicit context: Context)(\n                                  parent: Object): JSPromise {\n   const instance = InnerNewJSPromise();\n   PromiseInit(instance);\n+  runtime::PromiseContextInit(instance);\n   RunAnyPromiseHookInit(instance, parent);\n   return instance;\n }\n@@ -296,6 +299,7 @@ transitioning macro NewJSPromise(\n   instance.reactions_or_result = result;\n   instance.SetStatus(status);\n   promise_internal::ZeroOutEmbedderOffsets(instance);\n+  runtime::PromiseContextInit(instance);\n   RunAnyPromiseHookInit(instance, Undefined);\n   return instance;\n }\ndiff --git a/src/compiler/js-create-lowering.cc b/src/compiler/js-create-lowering.cc\nindex 0dfc5da18119fe7d723314bb489995e8bb0b03c5..6ce3a938cd31a65b8574f3a1e589aa89455c6f47 100644\n--- a/src/compiler/js-create-lowering.cc\n+++ b/src/compiler/js-create-lowering.cc\n@@ -1121,10 +1121,12 @@ Reduction JSCreateLowering::ReduceJSCreatePromise(Node* node) {\n           jsgraph()->EmptyFixedArrayConstant());\n   a.Store(AccessBuilder::ForJSObjectOffset(JSPromise::kReactionsOrResultOffset),\n           jsgraph()->ZeroConstant());\n+  a.Store(AccessBuilder::ForJSObjectOffset(JSPromise::kContextTagOffset),\n+          jsgraph()->ZeroConstant());\n   static_assert(v8::Promise::kPending == 0);\n   a.Store(AccessBuilder::ForJSObjectOffset(JSPromise::kFlagsOffset),\n           jsgraph()->ZeroConstant());\n-  static_assert(JSPromise::kHeaderSize == 5 * kTaggedSize);\n+  static_assert(JSPromise::kHeaderSize == 6 * kTaggedSize);\n   for (int offset = JSPromise::kHeaderSize;\n        offset < JSPromise::kSizeWithEmbedderFields; offset += kTaggedSize) {\n     a.Store(AccessBuilder::ForJSObjectOffset(offset),\ndiff --git a/src/diagnostics/objects-printer.cc b/src/diagnostics/objects-printer.cc\nindex 8c33451d2ad80d5923ebe49c2e259e00da06edce..77d00f29b0c9247dad25f048010673e0358e6750 100644\n--- a/src/diagnostics/objects-printer.cc\n+++ b/src/diagnostics/objects-printer.cc\n@@ -987,6 +987,7 @@ void JSPromise::JSPromisePrint(std::ostream& os) {\n   }\n   os << \"\\n - has_handler: \" << has_handler();\n   os << \"\\n - is_silent: \" << is_silent();\n+  os << \"\\n - context_tag: \" << Brief(context_tag());\n   JSObjectPrintBody(os, *this);\n }\n \ndiff --git a/src/execution/isolate-inl.h b/src/execution/isolate-inl.h\nindex 393b3d611743c86e7760760a41bdd6a6c5216691..5e0c1c62b6168e12af1ad067cd57604c17b17ce2 100644\n--- a/src/execution/isolate-inl.h\n+++ b/src/execution/isolate-inl.h\n@@ -133,6 +133,25 @@ bool Isolate::is_execution_terminating() {\n          i::ReadOnlyRoots(this).termination_exception();\n }\n \n+Tagged<Object> Isolate::promise_context_tag() { return promise_context_tag_; }\n+\n+bool Isolate::has_promise_context_tag() {\n+  return promise_context_tag_ != ReadOnlyRoots(this).the_hole_value();\n+}\n+\n+void Isolate::clear_promise_context_tag() {\n+  set_promise_context_tag(ReadOnlyRoots(this).the_hole_value());\n+}\n+\n+void Isolate::set_promise_context_tag(Tagged<Object> tag) {\n+  promise_context_tag_ = tag;\n+}\n+\n+void Isolate::set_promise_cross_context_callback(\n+    PromiseCrossContextCallback callback) {\n+  promise_cross_context_callback_ = callback;\n+}\n+\n #ifdef DEBUG\n Tagged<Object> Isolate::VerifyBuiltinsResult(Tagged<Object> result) {\n   if (is_execution_terminating() && !v8_flags.strict_termination_checks) {\ndiff --git a/src/execution/isolate.cc b/src/execution/isolate.cc\nindex cebff6c533a49e11d387834e8414544387f4890c..b05a58391123bafe9e7d47987fb170c1e73b09f8 100644\n--- a/src/execution/isolate.cc\n+++ b/src/execution/isolate.cc\n@@ -628,6 +628,8 @@ void Isolate::Iterate(RootVisitor* v, ThreadLocalTop* thread) {\n                       FullObjectSlot(&thread->pending_message_));\n   v->VisitRootPointer(Root::kStackRoots, nullptr,\n                       FullObjectSlot(&thread->context_));\n+  v->VisitRootPointer(Root::kStackRoots, nullptr,\n+                      FullObjectSlot(&promise_context_tag_));\n \n   for (v8::TryCatch* block = thread->try_catch_handler_; block != nullptr;\n        block = block->next_) {\n@@ -5977,6 +5979,7 @@ bool Isolate::Init(SnapshotData* startup_snapshot_data,\n     shared_heap_object_cache_.push_back(ReadOnlyRoots(this).undefined_value());\n   }\n \n+  clear_promise_context_tag();\n   InitializeThreadLocal();\n \n   // Profiler has to be created after ThreadLocal is initialized\n@@ -8139,5 +8142,40 @@ void Isolate::PrintNumberStringCacheStats(const char* comment,\n   PrintF(\"\\n\");\n }\n \n+class Isolate::PromiseCrossContextCallbackScope {\n+ public:\n+  PromiseCrossContextCallbackScope(Isolate& isolate) : isolate_(isolate) {\n+    DCHECK(!isolate_.in_promise_cross_context_callback_);\n+    isolate_.in_promise_cross_context_callback_ = true;\n+  }\n+  ~PromiseCrossContextCallbackScope() {\n+    isolate_.in_promise_cross_context_callback_ = false;\n+  }\n+\n+ private:\n+  Isolate& isolate_;\n+};\n+\n+MaybeHandle<JSPromise> Isolate::RunPromiseCrossContextCallback(\n+    Handle<NativeContext> context, Handle<JSPromise> promise) {\n+  if (promise_cross_context_callback_ == nullptr ||\n+      in_promise_cross_context_callback_) {\n+    return promise;\n+  }\n+  PromiseCrossContextCallbackScope callback_scope(*this);\n+  CHECK(IsJSReceiver(promise->context_tag()));\n+\n+  Handle<JSObject> context_tag(Cast<JSObject>(promise->context_tag()), this);\n+  v8::Local<v8::Promise> result;\n+  API_ASSIGN_RETURN_ON_EXCEPTION_VALUE(\n+      this, result,\n+      promise_cross_context_callback_(Utils::ToLocal(context),\n+                                      v8::Utils::ToLocal(promise),\n+                                      v8::Utils::ToLocal(context_tag)),\n+      MaybeHandle<JSPromise>());\n+\n+  return v8::Utils::OpenHandle(*result);\n+}\n+\n }  // namespace internal\n }  // namespace v8\ndiff --git a/src/execution/isolate.h b/src/execution/isolate.h\nindex 9e0888eab6cad4ce87c9fd68ffd8f0bbd5a3da07..61f25b58bc163d14e16e3ad905507d95aa6c97c1 100644\n--- a/src/execution/isolate.h\n+++ b/src/execution/isolate.h\n@@ -2431,6 +2431,15 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {\n                                        v8::ExceptionContext callback_kind);\n   void SetExceptionPropagationCallback(ExceptionPropagationCallback callback);\n \n+  inline Tagged<Object> promise_context_tag();\n+  inline bool has_promise_context_tag();\n+  inline void clear_promise_context_tag();\n+  inline void set_promise_context_tag(Tagged<Object> tag);\n+  inline void set_promise_cross_context_callback(\n+      PromiseCrossContextCallback callback);\n+  MaybeHandle<JSPromise> RunPromiseCrossContextCallback(\n+      Handle<NativeContext> context, Handle<JSPromise> promise);\n+\n #ifdef V8_ENABLE_WASM_SIMD256_REVEC\n   void set_wasm_revec_verifier_for_test(\n       compiler::turboshaft::WasmRevecVerifier* verifier) {\n@@ -2958,6 +2967,12 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {\n \n   bool is_frozen_ = false;\n \n+  Tagged<Object> promise_context_tag_;\n+  PromiseCrossContextCallback promise_cross_context_callback_;\n+  bool in_promise_cross_context_callback_ = false;\n+\n+  class PromiseCrossContextCallbackScope;\n+\n   friend class GlobalSafepoint;\n   friend class heap::HeapTester;\n   friend class IsolateForPointerCompression;\n@@ -2965,6 +2980,7 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {\n   friend class IsolateGroup;\n   friend class TestSerializer;\n   friend class SharedHeapNoClientsTest;\n+  friend class PromiseCrossContextCallbackScope;\n };\n \n // The current entered Isolate and its thread data. Do not access these\ndiff --git a/src/heap/factory.cc b/src/heap/factory.cc\nindex b6f6938450c1a06047fa686cf26d979454c3db15..ada7bd7f163bd3f850f5f8f76206eedffc48a625 100644\n--- a/src/heap/factory.cc\n+++ b/src/heap/factory.cc\n@@ -4675,6 +4675,12 @@ Handle<JSPromise> Factory::NewJSPromiseWithoutHook() {\n   DisallowGarbageCollection no_gc;\n   Tagged<JSPromise> raw = *promise;\n   raw->set_reactions_or_result(Smi::zero(), SKIP_WRITE_BARRIER);\n+  if (!isolate()->has_promise_context_tag()) {\n+    raw->set_context_tag(Smi::zero(), SKIP_WRITE_BARRIER);\n+  } else {\n+    raw->set_context_tag(isolate()->promise_context_tag());\n+  }\n+\n   raw->set_flags(0);\n   // TODO(v8) remove once embedder data slots are always zero-initialized.\n   InitEmbedderFields(*promise, Smi::zero());\ndiff --git a/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.cc\nindex e334c256779f224d66e2ec3c9bac2ecd88f6708f..0ae2db1c1e966081dcebf34dd92473675211a3b9 100644\n--- a/src/maglev/maglev-graph-builder.cc\n+++ b/src/maglev/maglev-graph-builder.cc\n@@ -14746,9 +14746,10 @@ VirtualObject* MaglevGraphBuilder::CreateJSPromiseObject() {\n   vobj->set(JSPromise::kElementsOffset,\n             GetRootConstant(RootIndex::kEmptyFixedArray));\n   vobj->set(JSPromise::kReactionsOrResultOffset, GetSmiConstant(0));\n+  vobj->set(JSPromise::kContextTagOffset, GetSmiConstant(0));\n   static_assert(v8::Promise::kPending == 0);\n   vobj->set(JSPromise::kFlagsOffset, GetSmiConstant(0));\n-  static_assert(JSPromise::kHeaderSize == 5 * kTaggedSize);\n+  static_assert(JSPromise::kHeaderSize == 6 * kTaggedSize);\n   for (int offset = JSPromise::kHeaderSize;\n        offset < JSPromise::kSizeWithEmbedderFields; offset += kTaggedSize) {\n     vobj->set(offset, GetSmiConstant(0));\ndiff --git a/src/objects/js-promise.tq b/src/objects/js-promise.tq\nindex 93a4a34f9f1f7fad40611d4b9432aaf944c9fbae..188619ea64c9e307f014898e4023ce796c52c7e2 100644\n--- a/src/objects/js-promise.tq\n+++ b/src/objects/js-promise.tq\n@@ -32,6 +32,7 @@ extern class JSPromise extends JSObjectWithEmbedderSlots {\n   // Smi 0 terminated list of PromiseReaction objects in case the JSPromise was\n   // not settled yet, otherwise the result.\n   reactions_or_result: Zero|PromiseReaction|JSAny;\n+  context_tag: Object;\n   flags: SmiTagged<JSPromiseFlags>;\n }\n \ndiff --git a/src/objects/value-serializer.cc b/src/objects/value-serializer.cc\nindex 4ef0c1e7fd227e8943261b64a97e6d6088928621..01ae1676839b9888e740f88c90fff2c2b76e3339 100644\n--- a/src/objects/value-serializer.cc\n+++ b/src/objects/value-serializer.cc\n@@ -1133,8 +1133,8 @@ Maybe<bool> ValueSerializer::WriteJSArrayBufferView(\n   WriteVarint(static_cast<uint32_t>(view->byte_length()));\n   if (version_ >= 14) {\n     uint32_t flags =\n-      JSArrayBufferViewIsLengthTracking::encode(view->is_length_tracking()) |\n-      JSArrayBufferViewIsBackedByRab::encode(view->is_backed_by_rab());\n+        JSArrayBufferViewIsLengthTracking::encode(view->is_length_tracking()) |\n+        JSArrayBufferViewIsBackedByRab::encode(view->is_backed_by_rab());\n     WriteVarint(flags);\n   }\n   return ThrowIfOutOfMemory();\ndiff --git a/src/profiler/heap-snapshot-generator.cc b/src/profiler/heap-snapshot-generator.cc\nindex 27e748f204ff4b6fb9ab2d9c9ddf69968958a0a1..342d6e631362e0b9e1c15840c9175f366e7fbb1d 100644\n--- a/src/profiler/heap-snapshot-generator.cc\n+++ b/src/profiler/heap-snapshot-generator.cc\n@@ -2118,6 +2118,8 @@ void V8HeapExplorer::ExtractJSPromiseReferences(HeapEntry* entry,\n   SetInternalReference(entry, \"reactions_or_result\",\n                        promise->reactions_or_result(),\n                        JSPromise::kReactionsOrResultOffset);\n+  SetInternalReference(entry, \"context_tag\", promise->context_tag(),\n+                       JSPromise::kContextTagOffset);\n }\n \n void V8HeapExplorer::ExtractJSGeneratorObjectReferences(\ndiff --git a/src/runtime/runtime-promise.cc b/src/runtime/runtime-promise.cc\nindex 262b9aa5aa6974a4628d0679ada91aff76567906..9730731cd42c0ea6ce0d96ec250a11fcc434ebf8 100644\n--- a/src/runtime/runtime-promise.cc\n+++ b/src/runtime/runtime-promise.cc\n@@ -216,5 +216,41 @@ RUNTIME_FUNCTION(Runtime_ConstructSuppressedError) {\n   return *result;\n }\n \n+RUNTIME_FUNCTION(Runtime_PromiseContextInit) {\n+  HandleScope scope(isolate);\n+  DCHECK_EQ(1, args.length());\n+  if (!isolate->has_promise_context_tag()) {\n+    args.at<JSPromise>(0)->set_context_tag(Smi::zero());\n+  } else {\n+    CHECK(!IsUndefined(isolate->promise_context_tag()));\n+    args.at<JSPromise>(0)->set_context_tag(isolate->promise_context_tag());\n+  }\n+  return ReadOnlyRoots(isolate).undefined_value();\n+}\n+\n+RUNTIME_FUNCTION(Runtime_PromiseContextCheck) {\n+  HandleScope scope(isolate);\n+  DCHECK_EQ(1, args.length());\n+\n+  Handle<JSPromise> promise = args.at<JSPromise>(0);\n+\n+  // If promise.context_tag() is strict equal to isolate.promise_context_tag(),\n+  // or if the promise being checked does not have a context tag, we'll just\n+  // return promise directly.\n+  Tagged<Object> obj = promise->context_tag();\n+  if (obj == Smi::zero() || obj == isolate->promise_context_tag()) {\n+    return *promise;\n+  }\n+\n+  // Otherwise we defer to the PromiseCrossContextCallback. If the callback\n+  // has not been set, then it should just return the same promise back here.\n+  Handle<JSPromise> result;\n+  ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, result,\n+                                     isolate->RunPromiseCrossContextCallback(\n+                                         isolate->native_context(), promise));\n+\n+  return *result;\n+}\n+\n }  // namespace internal\n }  // namespace v8\ndiff --git a/src/runtime/runtime.h b/src/runtime/runtime.h\nindex 8c5eac0907b3056a86934f3ee5525107b5a05e08..802970ee0c1546c2f0c649e9a05d08554357389d 100644\n--- a/src/runtime/runtime.h\n+++ b/src/runtime/runtime.h\n@@ -432,20 +432,22 @@ constexpr bool CanTriggerGC(T... properties) {\n   F(StrictNotEqual, 2, 1)                  \\\n   F(ReferenceEqual, 2, 1)\n \n-#define FOR_EACH_INTRINSIC_PROMISE(F, I) \\\n-  F(EnqueueMicrotask, 1, 1)              \\\n-  F(PromiseHookAfter, 1, 1)              \\\n-  F(PromiseHookBefore, 1, 1)             \\\n-  F(PromiseHookInit, 2, 1)               \\\n-  F(PromiseRejectEventFromStack, 2, 1)   \\\n-  F(PromiseRevokeReject, 1, 1)           \\\n-  F(RejectPromise, 3, 1)                 \\\n-  F(ResolvePromise, 2, 1)                \\\n-  F(PromiseRejectAfterResolved, 2, 1)    \\\n-  F(PromiseResolveAfterResolved, 2, 1)   \\\n-  F(ConstructSuppressedError, 3, 1)      \\\n-  F(ConstructAggregateErrorHelper, 4, 1) \\\n-  F(ConstructInternalAggregateErrorHelper, -1 /* <= 5*/, 1)\n+#define FOR_EACH_INTRINSIC_PROMISE(F, I)                    \\\n+  F(EnqueueMicrotask, 1, 1)                                 \\\n+  F(PromiseHookAfter, 1, 1)                                 \\\n+  F(PromiseHookBefore, 1, 1)                                \\\n+  F(PromiseHookInit, 2, 1)                                  \\\n+  F(PromiseRejectEventFromStack, 2, 1)                      \\\n+  F(PromiseRevokeReject, 1, 1)                              \\\n+  F(RejectPromise, 3, 1)                                    \\\n+  F(ResolvePromise, 2, 1)                                   \\\n+  F(PromiseRejectAfterResolved, 2, 1)                       \\\n+  F(PromiseResolveAfterResolved, 2, 1)                      \\\n+  F(ConstructSuppressedError, 3, 1)                         \\\n+  F(ConstructAggregateErrorHelper, 4, 1)                    \\\n+  F(ConstructInternalAggregateErrorHelper, -1 /* <= 5*/, 1) \\\n+  F(PromiseContextInit, 1, 1)                               \\\n+  F(PromiseContextCheck, 1, 1)\n \n #define FOR_EACH_INTRINSIC_PROXY(F, I) \\\n   F(CheckProxyGetSetTrapResult, 2, 1)  \\\n"
  },
  {
    "path": "patches/v8/0007-Randomize-the-initial-ExecutionContextId-used-by-the.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Orion Hodson <orion@cloudflare.com>\nDate: Wed, 13 Sep 2023 15:38:15 +0100\nSubject: Randomize the initial ExecutionContextId used by the inspector\n\nThis is to help the devtools sources panel when workerd is re-started\nby miniflare. This happens because workerd does not support re-loading\nlive workers (https://chat.google.com/room/AAAAnS2bXT4/GX5-pa8O0ts).\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/src/inspector/v8-inspector-impl.cc b/src/inspector/v8-inspector-impl.cc\nindex 9dae9ef1f3693ddcf6759349c917dbe213cf3578..096fdd50f2e7e39b266d2094b873eb422851aa5f 100644\n--- a/src/inspector/v8-inspector-impl.cc\n+++ b/src/inspector/v8-inspector-impl.cc\n@@ -68,7 +68,7 @@ V8InspectorImpl::V8InspectorImpl(v8::Isolate* isolate,\n       m_client(client),\n       m_debugger(new V8Debugger(isolate, this)),\n       m_lastExceptionId(0),\n-      m_lastContextId(0) {\n+      m_lastContextId(static_cast<int32_t>(generateUniqueId() & 0x1fff'ffe0)) {\n   v8::debug::SetInspector(m_isolate, this);\n   v8::debug::SetConsoleDelegate(m_isolate, console());\n   v8::debug::SetIsolateId(m_isolate, generateUniqueId());\n"
  },
  {
    "path": "patches/v8/0008-increase-visibility-of-virtual-method.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Mike Aizatsky <maizatskyi@cloudflare.com>\nDate: Tue, 6 Feb 2024 12:55:07 -0800\nSubject: increase visibility of virtual method\n\nMethods were marked as protected, which conflicts with\nv8-platform-wrapper.h implementation.\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/include/v8-platform.h b/include/v8-platform.h\nindex 3484e988d9fec1a132a435c63d873225ab07b0ec..99ccec9e23c5b860a890b2c52253edb3e2f6ea90 100644\n--- a/include/v8-platform.h\n+++ b/include/v8-platform.h\n@@ -1516,7 +1516,7 @@ class Platform {\n     return &default_observer;\n   }\n \n- protected:\n+ public:\n   /**\n    * Default implementation of current wall-clock time in milliseconds\n    * since epoch. Useful for implementing |CurrentClockTimeMillis| if\n"
  },
  {
    "path": "patches/v8/0009-Add-ValueSerializer-SetTreatFunctionsAsHostObjects.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Kenton Varda <kenton@cloudflare.com>\nDate: Sat, 2 Mar 2024 09:00:18 -0600\nSubject: Add ValueSerializer::SetTreatFunctionsAsHostObjects().\n\nPreviously, ValueSerializer would always refuse to serialize functions. This commit gives the embedder the option to handle them as host objects.\n\nThis is intended for use in an RPC system, where a function can be \"serialized\" by replacing it with a stub which, when invoked, performs an RPC back to the originating isolate in order to execute the original function there.\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/include/v8-value-serializer.h b/include/v8-value-serializer.h\nindex 596be18adeb3a5a81794aaa44b1d347dec6c0c7d..141f138e08de849e3e02b3b2b346e643b9e40c70 100644\n--- a/include/v8-value-serializer.h\n+++ b/include/v8-value-serializer.h\n@@ -195,6 +195,15 @@ class V8_EXPORT ValueSerializer {\n    */\n   void SetTreatArrayBufferViewsAsHostObjects(bool mode);\n \n+  /**\n+   * Indicate whether to treat Functions as host objects,\n+   * i.e. pass them to Delegate::WriteHostObject. This should not be\n+   * called when no Delegate was passed.\n+   *\n+   * The default is not to treat Functions as host objects.\n+   */\n+  void SetTreatFunctionsAsHostObjects(bool mode);\n+\n   /**\n    * Write raw data in various common formats to the buffer.\n    * Note that integer types are written in base-128 varint format, not with a\ndiff --git a/src/api/api.cc b/src/api/api.cc\nindex 058b2862cf31ddc2dc010f9b247ec6a0ddb277f1..91d5a9f3655892641d7b9600485c4e19595c096b 100644\n--- a/src/api/api.cc\n+++ b/src/api/api.cc\n@@ -3507,6 +3507,10 @@ void ValueSerializer::SetTreatArrayBufferViewsAsHostObjects(bool mode) {\n   private_->serializer.SetTreatArrayBufferViewsAsHostObjects(mode);\n }\n \n+void ValueSerializer::SetTreatFunctionsAsHostObjects(bool mode) {\n+  private_->serializer.SetTreatFunctionsAsHostObjects(mode);\n+}\n+\n Maybe<bool> ValueSerializer::WriteValue(Local<Context> context,\n                                         Local<Value> value) {\n   auto i_isolate = i::Isolate::Current();\ndiff --git a/src/objects/value-serializer.cc b/src/objects/value-serializer.cc\nindex 01ae1676839b9888e740f88c90fff2c2b76e3339..40fe15b9175902d8772e95024739a7efef5918c2 100644\n--- a/src/objects/value-serializer.cc\n+++ b/src/objects/value-serializer.cc\n@@ -336,6 +336,10 @@ void ValueSerializer::SetTreatArrayBufferViewsAsHostObjects(bool mode) {\n   treat_array_buffer_views_as_host_objects_ = mode;\n }\n \n+void ValueSerializer::SetTreatFunctionsAsHostObjects(bool mode) {\n+  treat_functions_as_host_objects_ = mode;\n+}\n+\n void ValueSerializer::WriteTag(SerializationTag tag) {\n   uint8_t raw_tag = static_cast<uint8_t>(tag);\n   WriteRawBytes(&raw_tag, sizeof(raw_tag));\n@@ -605,13 +609,17 @@ Maybe<bool> ValueSerializer::WriteJSReceiver(\n \n   // Eliminate callable and exotic objects, which should not be serialized.\n   InstanceType instance_type = receiver->map()->instance_type();\n-  if (IsCallable(*receiver) ||\n-      (IsSpecialReceiverInstanceType(instance_type) &&\n+  if (IsCallable(*receiver)) {\n+    if (treat_functions_as_host_objects_) {\n+      return WriteHostObject(Cast<JSObject>(receiver));\n+    }\n+    return ThrowDataCloneError(MessageTemplate::kDataCloneError, receiver);\n+  } else if (IsSpecialReceiverInstanceType(instance_type) &&\n        instance_type != JS_SPECIAL_API_OBJECT_TYPE\n #if V8_ENABLE_WEBASSEMBLY\n        && instance_type != WASM_STRUCT_TYPE && instance_type != WASM_ARRAY_TYPE\n #endif\n-       )) {\n+       ) {\n     return ThrowDataCloneError(MessageTemplate::kDataCloneError, receiver);\n   }\n \ndiff --git a/src/objects/value-serializer.h b/src/objects/value-serializer.h\nindex 06475f7b9c2a797066f5cfd32b232e5aa55f1f75..ddc5f27a80f93bae209f3fe8731d4df4baa58ead 100644\n--- a/src/objects/value-serializer.h\n+++ b/src/objects/value-serializer.h\n@@ -102,6 +102,15 @@ class ValueSerializer {\n    */\n   void SetTreatArrayBufferViewsAsHostObjects(bool mode);\n \n+  /*\n+   * Indicate whether to treat Functions as host objects,\n+   * i.e. pass them to Delegate::WriteHostObject. This should not be\n+   * called when no Delegate was passed.\n+   *\n+   * The default is not to treat Functions as host objects.\n+   */\n+  void SetTreatFunctionsAsHostObjects(bool mode);\n+\n  private:\n   // Managing allocations of the internal buffer.\n   Maybe<bool> ExpandBuffer(size_t required_capacity);\n@@ -185,6 +194,7 @@ class ValueSerializer {\n   size_t buffer_capacity_ = 0;\n   bool has_custom_host_objects_ = false;\n   bool treat_array_buffer_views_as_host_objects_ = false;\n+  bool treat_functions_as_host_objects_ = false;\n   bool out_of_memory_ = false;\n   Zone zone_;\n   uint32_t version_;\n"
  },
  {
    "path": "patches/v8/0010-Modify-where-to-look-for-fp16-dependency.-This-depen.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Garrett Gu <garrett@cloudflare.com>\nDate: Mon, 22 Apr 2024 10:10:21 -0500\nSubject: Modify where to look for fp16 dependency. This dependency is normally\n downloaded by gn but we need to fetch it in Bazel. We do so in the workerd\n repo in the WORKSPACE file.\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/BUILD.bazel b/BUILD.bazel\nindex 3c931da31d9006c200ef2a7ddc358150b2743675..6035f31240dc1e0421be5b5e918d23bc5ad6b8d4 100644\n--- a/BUILD.bazel\n+++ b/BUILD.bazel\n@@ -4091,17 +4091,23 @@ v8_library(\n   ],\n )\n \n-v8_library(\n-  name = \"lib_fp16\",\n-  srcs = [\"third_party/fp16/src/include/fp16.h\"],\n-  hdrs = [\n-    \"third_party/fp16/src/include/fp16/fp16.h\",\n-    \"third_party/fp16/src/include/fp16/bitcasts.h\",\n-    \"third_party/fp16/src/include/fp16/macros.h\",\n-  ],\n-  includes = [\n-    \"third_party/fp16/src/include\",\n-  ],\n+cc_library(\n+    name = \"lib_fp16\",\n+    hdrs = [\n+        \"@fp16//:include/fp16.h\",\n+    ],\n+    include_prefix = \"third_party/fp16/src\",\n+    deps = [\":lib_fp16_includes\"],\n+)\n+\n+cc_library(\n+    name = \"lib_fp16_includes\",\n+    hdrs = [\n+        \"@fp16//:include/fp16/bitcasts.h\",\n+        \"@fp16//:include/fp16/macros.h\",\n+        \"@fp16//:include/fp16/fp16.h\",\n+    ],\n+    strip_include_prefix = \"include\",\n )\n \n filegroup(\n"
  },
  {
    "path": "patches/v8/0011-Revert-heap-Add-masm-specific-unwinding-annotations-.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Erik Corry <erikcorry@chromium.org>\nDate: Wed, 26 Feb 2025 18:44:05 +0100\nSubject: Revert \"heap: Add masm-specific unwinding annotations to GC\n\nThis reverts commit 42bc7bfdf56cc7a8cd7757d96aa70b83ce82d9ff.\n\nThis commit broke our build, because we patch V8 to build push_registers_asm.cc\nwith clang-cl on Windows, and this commit makes it an error to compile that\nfile on Windows with any compiler. Maybe clang-cl and masm can be used\ntogether? I'm not sure. In the meantime, I'm reverting this commit in the hope\nof getting our V8 upgrade unblocked.\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/BUILD.gn b/BUILD.gn\nindex 15de2179a0e5ce50d5c659a9d15a920c50124c3e..b9a3b2ed39123725ee88a35a635bdc2a086f67c0 100644\n--- a/BUILD.gn\n+++ b/BUILD.gn\n@@ -4579,8 +4579,8 @@ v8_header_set(\"v8_internal_headers\") {\n     \"src/tasks/operations-barrier.h\",\n     \"src/tasks/task-utils.h\",\n     \"src/torque/runtime-macro-shims.h\",\n-    \"src/tracing/trace-event-no-perfetto.h\",\n     \"src/tracing/trace-event.h\",\n+    \"src/tracing/trace-event-no-perfetto.h\",\n     \"src/tracing/trace-id.h\",\n     \"src/tracing/traced-value.h\",\n     \"src/tracing/tracing-category-observer.h\",\n@@ -7446,12 +7446,7 @@ v8_source_set(\"v8_heap_base\") {\n   ]\n \n   if (current_cpu == \"x64\") {\n-    if (is_win) {\n-      # Prefer a masm version with unwind directives.\n-      sources += [ \"src/heap/base/asm/x64/push_registers_masm.asm\" ]\n-    } else {\n-      sources += [ \"src/heap/base/asm/x64/push_registers_asm.cc\" ]\n-    }\n+    sources += [ \"src/heap/base/asm/x64/push_registers_asm.cc\" ]\n   } else if (current_cpu == \"x86\") {\n     sources += [ \"src/heap/base/asm/ia32/push_registers_asm.cc\" ]\n   } else if (current_cpu == \"arm\") {\ndiff --git a/src/heap/base/asm/x64/push_registers_asm.cc b/src/heap/base/asm/x64/push_registers_asm.cc\nindex d1d79dcfd5a8fce8240d8981b25c8027e220b03b..3f17db19310ff64e9316d0bb8308e399f5ce1422 100644\n--- a/src/heap/base/asm/x64/push_registers_asm.cc\n+++ b/src/heap/base/asm/x64/push_registers_asm.cc\n@@ -14,16 +14,61 @@\n \n // Do not depend on V8_TARGET_OS_* defines as some embedders may override the\n // GN toolchain (e.g. ChromeOS) and not provide them.\n+// _WIN64 Defined as 1 when the compilation target is 64-bit ARM or x64.\n+// Otherwise, undefined.\n+#ifdef _WIN64\n \n // We maintain 16-byte alignment at calls. There is an 8-byte return address\n-// on the stack and we push 56 bytes which maintains 16-byte stack alignment\n+// on the stack and we push 232 bytes which maintains 16-byte stack alignment\n // at the call.\n-// Source: https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf\n+// Source: https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention\n+asm(\".att_syntax                                        \\n\"\n+    \".globl PushAllRegistersAndIterateStack             \\n\"\n+    \"PushAllRegistersAndIterateStack:                   \\n\"\n+    // rbp is callee-saved. Maintain proper frame pointer for debugging.\n+    \"  push %rbp                                        \\n\"\n+    \"  mov %rsp, %rbp                                   \\n\"\n+    // Dummy for alignment.\n+    \"  push $0xCDCDCD                                   \\n\"\n+    \"  push %rsi                                        \\n\"\n+    \"  push %rdi                                        \\n\"\n+    \"  push %rbx                                        \\n\"\n+    \"  push %r12                                        \\n\"\n+    \"  push %r13                                        \\n\"\n+    \"  push %r14                                        \\n\"\n+    \"  push %r15                                        \\n\"\n+    \"  sub $160, %rsp                                   \\n\"\n+    // Use aligned instrs as we are certain that the stack is properly aligned.\n+    \"  movdqa %xmm6, 144(%rsp)                          \\n\"\n+    \"  movdqa %xmm7, 128(%rsp)                          \\n\"\n+    \"  movdqa %xmm8, 112(%rsp)                          \\n\"\n+    \"  movdqa %xmm9, 96(%rsp)                           \\n\"\n+    \"  movdqa %xmm10, 80(%rsp)                          \\n\"\n+    \"  movdqa %xmm11, 64(%rsp)                          \\n\"\n+    \"  movdqa %xmm12, 48(%rsp)                          \\n\"\n+    \"  movdqa %xmm13, 32(%rsp)                          \\n\"\n+    \"  movdqa %xmm14, 16(%rsp)                          \\n\"\n+    \"  movdqa %xmm15, (%rsp)                            \\n\"\n+    // Pass 1st parameter (rcx) unchanged (Stack*).\n+    // Pass 2nd parameter (rdx) unchanged (StackVisitor*).\n+    // Save 3rd parameter (r8; IterateStackCallback)\n+    \"  mov %r8, %r9                                     \\n\"\n+    // Pass 3rd parameter as rsp (stack pointer).\n+    \"  mov %rsp, %r8                                    \\n\"\n+    // Call the callback.\n+    \"  call *%r9                                        \\n\"\n+    // Pop the callee-saved registers.\n+    \"  add $224, %rsp                                   \\n\"\n+    // Restore rbp as it was used as frame pointer.\n+    \"  pop %rbp                                         \\n\"\n+    \"  ret                                              \\n\");\n \n-#ifdef _WIN64\n-#error \"The masm based version must be used for Windows\"\n-#endif\n+#else  // !_WIN64\n \n+// We maintain 16-byte alignment at calls. There is an 8-byte return address\n+// on the stack and we push 56 bytes which maintains 16-byte stack alignment\n+// at the call.\n+// Source: https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf\n asm(\n #ifdef __APPLE__\n     \".globl _PushAllRegistersAndIterateStack            \\n\"\n@@ -81,3 +126,5 @@ asm(\n     \".Lfunc_end0-PushAllRegistersAndIterateStack        \\n\"\n #endif  // !defined(__APPLE__)\n     \".cfi_endproc                                      \\n\");\n+\n+#endif  // !_WIN64\ndiff --git a/src/heap/base/asm/x64/push_registers_masm.asm b/src/heap/base/asm/x64/push_registers_masm.asm\nindex d0d05632d26883316cb7b39f8e7e8143d2b24bc3..a32e193c2f132cdbe1d0730831d8659be996f8e2 100644\n--- a/src/heap/base/asm/x64/push_registers_masm.asm\n+++ b/src/heap/base/asm/x64/push_registers_masm.asm\n@@ -8,7 +8,7 @@\n public PushAllRegistersAndIterateStack\n \n .code\n-PushAllRegistersAndIterateStack proc frame\n+PushAllRegistersAndIterateStack:\n     ;; Push all callee-saved registers to get them on the stack for conservative\n     ;; stack scanning.\n     ;;\n@@ -19,28 +19,16 @@ PushAllRegistersAndIterateStack proc frame\n     ;;\n     ;; rbp is callee-saved. Maintain proper frame pointer for debugging.\n     push rbp\n-    .pushreg rbp\n     mov rbp, rsp\n-    .setframe rbp, 0\n     push 0CDCDCDh  ;; Dummy for alignment.\n-    .allocstack 8\n     push rsi\n-    .pushreg rsi\n     push rdi\n-    .pushreg rdi\n     push rbx\n-    .pushreg rbx\n     push r12\n-    .pushreg r12\n     push r13\n-    .pushreg r13\n     push r14\n-    .pushreg r14\n     push r15\n-    .pushreg r15\n     sub rsp, 160\n-    .allocstack 160\n-    .endprolog\n     ;; Use aligned instrs as we are certain that the stack is properly aligned.\n     movdqa  xmmword ptr [rsp + 144], xmm6\n     movdqa  xmmword ptr [rsp + 128], xmm7\n@@ -65,6 +53,5 @@ PushAllRegistersAndIterateStack proc frame\n     ;; Restore rbp as it was used as frame pointer.\n     pop rbp\n     ret\n-    PushAllRegistersAndIterateStack endp\n \n end\n"
  },
  {
    "path": "patches/v8/0012-Update-illegal-invocation-error-message-in-v8.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: James M Snell <jasnell@gmail.com>\nDate: Tue, 9 Jul 2024 08:37:05 -0700\nSubject: Update illegal invocation error message in v8\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/src/common/message-template.h b/src/common/message-template.h\nindex 012ae2fecf5c5dc93cf41f5da4ee1f7ac80e710d..6119b7fc47dc614a60a2186f24066e4d447144a8 100644\n--- a/src/common/message-template.h\n+++ b/src/common/message-template.h\n@@ -125,7 +125,11 @@ namespace internal {\n     \"First argument to % must not be a regular expression\")                    \\\n   T(FunctionBind, \"Bind must be called on a function\")                         \\\n   T(GeneratorRunning, \"Generator is already running\")                          \\\n-  T(IllegalInvocation, \"Illegal invocation\")                                   \\\n+  T(IllegalInvocation,                                                         \\\n+    \"Illegal invocation: function called with incorrect `this` reference. \"    \\\n+    \"See \"                                                                     \\\n+    \"https://developers.cloudflare.com/workers/observability/errors/\"          \\\n+    \"#illegal-invocation-errors for details.\")                                 \\\n   T(ImmutablePrototypeSet,                                                     \\\n     \"Immutable prototype object '%' cannot have their prototype set\")          \\\n   T(ImportAttributesDuplicateKey, \"Import attribute has duplicate key '%'\")    \\\ndiff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc\nindex 8896acd700f829944ec4c85c51aaca0a3eb28ae9..753fcbe4e876003b7d4e688a25726cacaeab7560 100644\n--- a/test/cctest/test-api.cc\n+++ b/test/cctest/test-api.cc\n@@ -223,6 +223,17 @@ THREADED_TEST(IsolateOfContext) {\n   CHECK(isolate->IsCurrent());\n }\n \n+static bool ExceptionStartsWithIllegalInvocation(v8::Isolate* isolate,\n+                                                 v8::TryCatch& try_catch) {\n+  v8::Local<v8::String> error_text =\n+      try_catch.Exception()\n+          ->ToString(isolate->GetCurrentContext())\n+          .ToLocalChecked();\n+  const char* prefix = \"TypeError: Illegal invocation\";\n+  return strncmp(prefix, *v8::String::Utf8Value(isolate, error_text),\n+                 strlen(prefix)) == 0;\n+}\n+\n static void TestSignatureLooped(const char* operation, Local<Value> receiver,\n                                 v8::Isolate* isolate) {\n   v8::base::ScopedVector<char> source(200);\n@@ -240,12 +251,7 @@ static void TestSignatureLooped(const char* operation, Local<Value> receiver,\n   if (!expected_to_throw) {\n     CHECK_EQ(10, signature_callback_count);\n   } else {\n-    CHECK(v8_str(\"TypeError: Illegal invocation\")\n-              ->Equals(isolate->GetCurrentContext(),\n-                       try_catch.Exception()\n-                           ->ToString(isolate->GetCurrentContext())\n-                           .ToLocalChecked())\n-              .FromJust());\n+    CHECK(ExceptionStartsWithIllegalInvocation(isolate, try_catch));\n   }\n   signature_expected_receiver_global.Reset();\n }\n@@ -272,12 +278,7 @@ static void TestSignatureOptimized(const char* operation, Local<Value> receiver,\n   if (!expected_to_throw) {\n     CHECK_EQ(3, signature_callback_count);\n   } else {\n-    CHECK(v8_str(\"TypeError: Illegal invocation\")\n-              ->Equals(isolate->GetCurrentContext(),\n-                       try_catch.Exception()\n-                           ->ToString(isolate->GetCurrentContext())\n-                           .ToLocalChecked())\n-              .FromJust());\n+    CHECK(ExceptionStartsWithIllegalInvocation(isolate, try_catch));\n   }\n   signature_expected_receiver_global.Reset();\n }\ndiff --git a/test/mjsunit/ic-megadom-3.js b/test/mjsunit/ic-megadom-3.js\nindex f7a7634e7e11845e4b364fcc6942f1170b9ee8da..e811f9e04d60f7a2eaf3a5a48e3c09d23a7c854d 100644\n--- a/test/mjsunit/ic-megadom-3.js\n+++ b/test/mjsunit/ic-megadom-3.js\n@@ -42,7 +42,7 @@ function test() {\n     load(new d8.dom.EventTarget());\n   } catch (err) {\n     assertInstanceof(err, TypeError);\n-    assertEquals(\"Illegal invocation\", err.message, 'Error message');\n+    assertTrue(err.message.startsWith(\"Illegal invocation\"), 'Error message');\n   }\n \n   return result;\n"
  },
  {
    "path": "patches/v8/0013-Implement-cross-request-context-promise-resolve-hand.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: James M Snell <jasnell@gmail.com>\nDate: Mon, 16 Sep 2024 09:56:04 -0700\nSubject: Implement cross-request context promise resolve handling\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/BUILD.gn b/BUILD.gn\nindex b9a3b2ed39123725ee88a35a635bdc2a086f67c0..160ba7f567481dd4873df212f262030a4fe1fdb8 100644\n--- a/BUILD.gn\n+++ b/BUILD.gn\n@@ -4579,8 +4579,8 @@ v8_header_set(\"v8_internal_headers\") {\n     \"src/tasks/operations-barrier.h\",\n     \"src/tasks/task-utils.h\",\n     \"src/torque/runtime-macro-shims.h\",\n-    \"src/tracing/trace-event.h\",\n     \"src/tracing/trace-event-no-perfetto.h\",\n+    \"src/tracing/trace-event.h\",\n     \"src/tracing/trace-id.h\",\n     \"src/tracing/traced-value.h\",\n     \"src/tracing/tracing-category-observer.h\",\ndiff --git a/include/v8-callbacks.h b/include/v8-callbacks.h\nindex cfba4bb26f865c0e38574f796200ffc5e0dc60fc..d5d937b0e852066b95a62d7bcf49668205a55391 100644\n--- a/include/v8-callbacks.h\n+++ b/include/v8-callbacks.h\n@@ -536,6 +536,25 @@ using FilterETWSessionByURL2Callback = FilterETWSessionByURLResult (*)(\n using PromiseCrossContextCallback = MaybeLocal<Promise> (*)(\n     Local<Context> context, Local<Promise> promise, Local<Object> tag);\n \n+/**\n+ * PromiseCrossContextResolveCallback is called when resolving or rejecting a\n+ * pending promise whose context tag is not strictly equal to the isolate's\n+ * current promise context tag. The callback is called with the promise to be\n+ * resolved, its context tag, and a function that when called, causes the\n+ * reactions to the resolved promise to be enqueued. The idea is that the\n+ * embedder sets this callback in the case it needs to defer the actual\n+ * scheduling of the reactions to the given promise to a later time.\n+ * Importantly, when this callback is invoked, the state of the promise\n+ * should have already been updated. We're simply possibly deferring the\n+ * enqueue of the reactions to the promise.\n+ */\n+using PromiseCrossContextResolveCallback = Maybe<void> (*)(\n+    v8::Isolate* isolate, Local<Value> tag, Local<Data> reactions,\n+    Local<Value> argument,\n+    std::function<void(v8::Isolate* isolate, Local<Data> reactions,\n+                       Local<Value> argument)>\n+        callback);\n+\n }  // namespace v8\n \n #endif  // INCLUDE_V8_ISOLATE_CALLBACKS_H_\ndiff --git a/include/v8-isolate.h b/include/v8-isolate.h\nindex 2346df282c5ec8661924b008aefb2f1854e4d54a..33d21ee604b389966aefe3ba3b3eaa5e02f609f9 100644\n--- a/include/v8-isolate.h\n+++ b/include/v8-isolate.h\n@@ -1875,6 +1875,8 @@ class V8_EXPORT Isolate {\n \n   class PromiseContextScope;\n   void SetPromiseCrossContextCallback(PromiseCrossContextCallback callback);\n+  void SetPromiseCrossContextResolveCallback(\n+      PromiseCrossContextResolveCallback callback);\n \n   Isolate() = delete;\n   ~Isolate() = delete;\ndiff --git a/src/api/api.cc b/src/api/api.cc\nindex 91d5a9f3655892641d7b9600485c4e19595c096b..9dfad16174088cb5b007ffd6441f3998371d4655 100644\n--- a/src/api/api.cc\n+++ b/src/api/api.cc\n@@ -12630,7 +12630,13 @@ Isolate::PromiseContextScope::PromiseContextScope(Isolate* isolate,\n   DCHECK(!isolate_->has_promise_context_tag());\n   DCHECK(!tag.IsEmpty());\n   i::Handle<i::Object> handle = Utils::OpenHandle(*tag);\n-  isolate_->set_promise_context_tag(*handle);\n+  isolate_->set_promise_context_tag(handle);\n+}\n+\n+void Isolate::SetPromiseCrossContextResolveCallback(\n+    PromiseCrossContextResolveCallback callback) {\n+  i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);\n+  isolate->set_promise_cross_context_resolve_callback(callback);\n }\n \n Isolate::PromiseContextScope::~PromiseContextScope() {\ndiff --git a/src/builtins/promise-abstract-operations.tq b/src/builtins/promise-abstract-operations.tq\nindex b7caf0137c5ff99d9ac990ec324a5f2c6e13b8bf..eae40a71f01b055eb93227f42ba7dbbbd0fe5173 100644\n--- a/src/builtins/promise-abstract-operations.tq\n+++ b/src/builtins/promise-abstract-operations.tq\n@@ -23,6 +23,9 @@ extern transitioning runtime PromiseRejectEventFromStack(\n \n extern transitioning runtime PromiseContextCheck(\n     implicit context: Context)(JSPromise): JSPromise;\n+\n+extern transitioning runtime PromiseResolveContextCheck(\n+    implicit context: Context)(JSPromise): JSAny;\n }\n \n // https://tc39.es/ecma262/#sec-promise-abstract-operations\n@@ -239,7 +242,8 @@ transitioning builtin RejectPromise(\n   // the runtime handle this operation, which greatly reduces\n   // the complexity here and also avoids a couple of back and\n   // forth between JavaScript and C++ land.\n-  if (IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(\n+  if (ToBoolean(runtime::PromiseResolveContextCheck(promise)) ||\n+      IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(\n           promiseHookFlags) ||\n       !promise.HasHandler()) {\n     // 7. If promise.[[PromiseIsHandled]] is false, perform\ndiff --git a/src/builtins/promise-resolve.tq b/src/builtins/promise-resolve.tq\nindex 202180adbbae91a689a667c40d20b4b1b9cb6edd..c93ac5905d7b349d1c59e9fa86b48662313ea1c3 100644\n--- a/src/builtins/promise-resolve.tq\n+++ b/src/builtins/promise-resolve.tq\n@@ -96,7 +96,9 @@ transitioning builtin ResolvePromise(\n   // We also let the runtime handle it if promise == resolution.\n   // We can use pointer comparison here, since the {promise} is guaranteed\n   // to be a JSPromise inside this function and thus is reference comparable.\n-  if (IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() ||\n+\n+  if (ToBoolean(runtime::PromiseResolveContextCheck(promise)) ||\n+      IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() ||\n       TaggedEqual(promise, resolution))\n     deferred {\n       return runtime::ResolvePromise(promise, resolution);\ndiff --git a/src/execution/isolate-inl.h b/src/execution/isolate-inl.h\nindex 5e0c1c62b6168e12af1ad067cd57604c17b17ce2..c07ac183137862444753a96a0a80149bf85cc44a 100644\n--- a/src/execution/isolate-inl.h\n+++ b/src/execution/isolate-inl.h\n@@ -133,18 +133,20 @@ bool Isolate::is_execution_terminating() {\n          i::ReadOnlyRoots(this).termination_exception();\n }\n \n-Tagged<Object> Isolate::promise_context_tag() { return promise_context_tag_; }\n+Handle<Object> Isolate::promise_context_tag() {\n+  return root_handle(RootIndex::kPromiseContextTag);\n+}\n \n bool Isolate::has_promise_context_tag() {\n-  return promise_context_tag_ != ReadOnlyRoots(this).the_hole_value();\n+  return heap()->promise_context_tag() != ReadOnlyRoots(this).the_hole_value();\n }\n \n void Isolate::clear_promise_context_tag() {\n-  set_promise_context_tag(ReadOnlyRoots(this).the_hole_value());\n+  heap()->set_promise_context_tag(ReadOnlyRoots(this).the_hole_value());\n }\n \n-void Isolate::set_promise_context_tag(Tagged<Object> tag) {\n-  promise_context_tag_ = tag;\n+void Isolate::set_promise_context_tag(Handle<Object> tag) {\n+  heap()->set_promise_context_tag(*tag);\n }\n \n void Isolate::set_promise_cross_context_callback(\n@@ -152,6 +154,15 @@ void Isolate::set_promise_cross_context_callback(\n   promise_cross_context_callback_ = callback;\n }\n \n+void Isolate::set_promise_cross_context_resolve_callback(\n+    PromiseCrossContextResolveCallback callback) {\n+  promise_cross_context_resolve_callback_ = callback;\n+}\n+\n+bool Isolate::has_promise_context_resolve_callback() {\n+  return promise_cross_context_resolve_callback_ != nullptr;\n+}\n+\n #ifdef DEBUG\n Tagged<Object> Isolate::VerifyBuiltinsResult(Tagged<Object> result) {\n   if (is_execution_terminating() && !v8_flags.strict_termination_checks) {\ndiff --git a/src/execution/isolate.cc b/src/execution/isolate.cc\nindex b05a58391123bafe9e7d47987fb170c1e73b09f8..ced0868674d5a1e5bf15ce252feee2dd58db50f7 100644\n--- a/src/execution/isolate.cc\n+++ b/src/execution/isolate.cc\n@@ -628,8 +628,6 @@ void Isolate::Iterate(RootVisitor* v, ThreadLocalTop* thread) {\n                       FullObjectSlot(&thread->pending_message_));\n   v->VisitRootPointer(Root::kStackRoots, nullptr,\n                       FullObjectSlot(&thread->context_));\n-  v->VisitRootPointer(Root::kStackRoots, nullptr,\n-                      FullObjectSlot(&promise_context_tag_));\n \n   for (v8::TryCatch* block = thread->try_catch_handler_; block != nullptr;\n        block = block->next_) {\n@@ -8177,5 +8175,20 @@ MaybeHandle<JSPromise> Isolate::RunPromiseCrossContextCallback(\n   return v8::Utils::OpenHandle(*result);\n }\n \n+Maybe<void> Isolate::RunPromiseCrossContextResolveCallback(\n+    v8::Isolate* isolate, Handle<JSObject> tag, DirectHandle<Object> reactions,\n+    DirectHandle<Object> argument, PromiseReaction::Type type) {\n+  CHECK(promise_cross_context_resolve_callback_ != nullptr);\n+  return promise_cross_context_resolve_callback_(\n+      isolate, v8::Utils::ToLocal(tag), v8::Utils::ToLocal(reactions),\n+      v8::Utils::ToLocal(argument),\n+      [type](v8::Isolate* isolate, v8::Local<v8::Data> reactions,\n+             v8::Local<v8::Value> argument) {\n+        JSPromise::ContinueTriggerPromiseReactions(\n+            reinterpret_cast<Isolate*>(isolate), Utils::OpenHandle(*reactions),\n+            Utils::OpenHandle(*argument), type);\n+      });\n+}\n+\n }  // namespace internal\n }  // namespace v8\ndiff --git a/src/execution/isolate.h b/src/execution/isolate.h\nindex 61f25b58bc163d14e16e3ad905507d95aa6c97c1..542862b3f2798690209e278a978e30fbf02a0695 100644\n--- a/src/execution/isolate.h\n+++ b/src/execution/isolate.h\n@@ -45,6 +45,7 @@\n #include \"src/objects/contexts.h\"\n #include \"src/objects/debug-objects.h\"\n #include \"src/objects/js-objects.h\"\n+#include \"src/objects/promise.h\"\n #include \"src/objects/tagged.h\"\n #include \"src/runtime/runtime.h\"\n #include \"src/sandbox/code-pointer-table.h\"\n@@ -2431,14 +2432,22 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {\n                                        v8::ExceptionContext callback_kind);\n   void SetExceptionPropagationCallback(ExceptionPropagationCallback callback);\n \n-  inline Tagged<Object> promise_context_tag();\n+  inline Handle<Object> promise_context_tag();\n   inline bool has_promise_context_tag();\n   inline void clear_promise_context_tag();\n-  inline void set_promise_context_tag(Tagged<Object> tag);\n+  inline void set_promise_context_tag(Handle<Object> tag);\n   inline void set_promise_cross_context_callback(\n       PromiseCrossContextCallback callback);\n+  inline void set_promise_cross_context_resolve_callback(\n+      PromiseCrossContextResolveCallback callback);\n   MaybeHandle<JSPromise> RunPromiseCrossContextCallback(\n       Handle<NativeContext> context, Handle<JSPromise> promise);\n+  Maybe<void> RunPromiseCrossContextResolveCallback(\n+      v8::Isolate* isolate, Handle<JSObject> tag,\n+      DirectHandle<Object> reactions, DirectHandle<Object> argument,\n+      PromiseReaction::Type type);\n+\n+  inline bool has_promise_context_resolve_callback();\n \n #ifdef V8_ENABLE_WASM_SIMD256_REVEC\n   void set_wasm_revec_verifier_for_test(\n@@ -2967,9 +2976,11 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {\n \n   bool is_frozen_ = false;\n \n-  Tagged<Object> promise_context_tag_;\n-  PromiseCrossContextCallback promise_cross_context_callback_;\n+  PromiseCrossContextCallback promise_cross_context_callback_ = nullptr;\n+  PromiseCrossContextResolveCallback promise_cross_context_resolve_callback_ =\n+      nullptr;\n   bool in_promise_cross_context_callback_ = false;\n+  bool in_promise_cross_context_resolve_callback_ = false;\n \n   class PromiseCrossContextCallbackScope;\n \ndiff --git a/src/heap/factory.cc b/src/heap/factory.cc\nindex ada7bd7f163bd3f850f5f8f76206eedffc48a625..d49f486e9606fd92160d5fd6dcb3c1a5a5b8a39e 100644\n--- a/src/heap/factory.cc\n+++ b/src/heap/factory.cc\n@@ -4673,18 +4673,17 @@ Handle<JSPromise> Factory::NewJSPromiseWithoutHook() {\n   Handle<JSPromise> promise =\n       Cast<JSPromise>(NewJSObject(isolate()->promise_function()));\n   DisallowGarbageCollection no_gc;\n-  Tagged<JSPromise> raw = *promise;\n-  raw->set_reactions_or_result(Smi::zero(), SKIP_WRITE_BARRIER);\n+  promise->set_reactions_or_result(Smi::zero(), SKIP_WRITE_BARRIER);\n   if (!isolate()->has_promise_context_tag()) {\n-    raw->set_context_tag(Smi::zero(), SKIP_WRITE_BARRIER);\n+    promise->set_context_tag(Smi::zero(), SKIP_WRITE_BARRIER);\n   } else {\n-    raw->set_context_tag(isolate()->promise_context_tag());\n+    promise->set_context_tag(*isolate()->promise_context_tag());\n   }\n \n-  raw->set_flags(0);\n+  promise->set_flags(0);\n   // TODO(v8) remove once embedder data slots are always zero-initialized.\n   InitEmbedderFields(*promise, Smi::zero());\n-  DCHECK_EQ(raw->GetEmbedderFieldCount(), v8::Promise::kEmbedderFieldCount);\n+  DCHECK_EQ(promise->GetEmbedderFieldCount(), v8::Promise::kEmbedderFieldCount);\n   return promise;\n }\n \ndiff --git a/src/objects/js-promise.h b/src/objects/js-promise.h\nindex 056b539ac19ecaa703c6e0bf37937c5bd4546301..8e0ebcf758598933fc98bdb817e92f328a36f033 100644\n--- a/src/objects/js-promise.h\n+++ b/src/objects/js-promise.h\n@@ -82,6 +82,11 @@ class JSPromise\n   static_assert(v8::Promise::kFulfilled == 1);\n   static_assert(v8::Promise::kRejected == 2);\n \n+  static void ContinueTriggerPromiseReactions(Isolate* isolate,\n+                                              DirectHandle<Object> reactions,\n+                                              DirectHandle<Object> argument,\n+                                              PromiseReaction::Type type);\n+\n  private:\n   // ES section #sec-triggerpromisereactions\n   static Handle<Object> TriggerPromiseReactions(Isolate* isolate,\ndiff --git a/src/objects/objects.cc b/src/objects/objects.cc\nindex a5057ccd637eddfe748fe277074a4c3f1e7229c2..1ef334a6413cbfba59e2a5023ff2c1a25121f398 100644\n--- a/src/objects/objects.cc\n+++ b/src/objects/objects.cc\n@@ -4640,6 +4640,22 @@ Handle<Object> JSPromise::Fulfill(DirectHandle<JSPromise> promise,\n   // 6. Set promise.[[PromiseState]] to \"fulfilled\".\n   promise->set_status(Promise::kFulfilled);\n \n+  Handle<Object> obj(promise->context_tag(), isolate);\n+  bool needs_promise_context_switch =\n+      !(*obj == Smi::zero() ||\n+        obj.is_identical_to(isolate->promise_context_tag()) ||\n+        !isolate->has_promise_context_resolve_callback());\n+  if (needs_promise_context_switch) {\n+    if (isolate\n+            ->RunPromiseCrossContextResolveCallback(\n+                reinterpret_cast<v8::Isolate*>(isolate), Cast<JSObject>(obj),\n+                reactions, value, PromiseReaction::kFulfill)\n+            .IsNothing()) {\n+      return {};\n+    }\n+    return isolate->factory()->undefined_value();\n+  }\n+\n   // 7. Return TriggerPromiseReactions(reactions, value).\n   return TriggerPromiseReactions(isolate, reactions, value,\n                                  PromiseReaction::kFulfill);\n@@ -4698,6 +4714,22 @@ Handle<Object> JSPromise::Reject(DirectHandle<JSPromise> promise,\n     isolate->ReportPromiseReject(promise, reason, kPromiseRejectWithNoHandler);\n   }\n \n+  Handle<Object> obj(promise->context_tag(), isolate);\n+  bool needs_promise_context_switch =\n+      !(*obj == Smi::zero() ||\n+        obj.is_identical_to(isolate->promise_context_tag()) ||\n+        !isolate->has_promise_context_resolve_callback());\n+  if (needs_promise_context_switch) {\n+    if (isolate\n+            ->RunPromiseCrossContextResolveCallback(\n+                reinterpret_cast<v8::Isolate*>(isolate), Cast<JSObject>(obj),\n+                reactions, reason, PromiseReaction::kReject)\n+            .IsNothing()) {\n+      return {};\n+    }\n+    return isolate->factory()->undefined_value();\n+  }\n+\n   // 8. Return TriggerPromiseReactions(reactions, reason).\n   return TriggerPromiseReactions(isolate, reactions, reason,\n                                  PromiseReaction::kReject);\n@@ -4806,6 +4838,14 @@ MaybeHandle<Object> JSPromise::Resolve(DirectHandle<JSPromise> promise,\n }\n \n // static\n+\n+void JSPromise::ContinueTriggerPromiseReactions(Isolate* isolate,\n+                                                DirectHandle<Object> reactions,\n+                                                DirectHandle<Object> argument,\n+                                                PromiseReaction::Type type) {\n+  TriggerPromiseReactions(isolate, reactions, argument, type);\n+}\n+\n Handle<Object> JSPromise::TriggerPromiseReactions(\n     Isolate* isolate, DirectHandle<Object> reactions,\n     DirectHandle<Object> argument, PromiseReaction::Type type) {\ndiff --git a/src/objects/value-serializer.cc b/src/objects/value-serializer.cc\nindex 40fe15b9175902d8772e95024739a7efef5918c2..03a67c4b13e11ee6d1013c974175b26ead0e5ef4 100644\n--- a/src/objects/value-serializer.cc\n+++ b/src/objects/value-serializer.cc\n@@ -615,11 +615,12 @@ Maybe<bool> ValueSerializer::WriteJSReceiver(\n     }\n     return ThrowDataCloneError(MessageTemplate::kDataCloneError, receiver);\n   } else if (IsSpecialReceiverInstanceType(instance_type) &&\n-       instance_type != JS_SPECIAL_API_OBJECT_TYPE\n+             instance_type != JS_SPECIAL_API_OBJECT_TYPE\n #if V8_ENABLE_WEBASSEMBLY\n-       && instance_type != WASM_STRUCT_TYPE && instance_type != WASM_ARRAY_TYPE\n+             && instance_type != WASM_STRUCT_TYPE &&\n+             instance_type != WASM_ARRAY_TYPE\n #endif\n-       ) {\n+  ) {\n     return ThrowDataCloneError(MessageTemplate::kDataCloneError, receiver);\n   }\n \ndiff --git a/src/roots/roots.h b/src/roots/roots.h\nindex 08e1426adad23342772eba4351be8fdc7aa1db89..7c9738259fe1e83373403e3bd0c91d0695fe3011 100644\n--- a/src/roots/roots.h\n+++ b/src/roots/roots.h\n@@ -418,7 +418,8 @@ class RootVisitor;\n   V(FunctionTemplateInfo, error_stack_getter_fun_template,                  \\\n     ErrorStackGetterSharedFun)                                              \\\n   V(FunctionTemplateInfo, error_stack_setter_fun_template,                  \\\n-    ErrorStackSetterSharedFun)\n+    ErrorStackSetterSharedFun)                                              \\\n+  V(Object, promise_context_tag, PromiseContextTag)\n \n // Entries in this list are limited to Smis and are not visited during GC.\n #define SMI_ROOT_LIST(V)                                                       \\\ndiff --git a/src/runtime/runtime-promise.cc b/src/runtime/runtime-promise.cc\nindex 9730731cd42c0ea6ce0d96ec250a11fcc434ebf8..7cb9fe57f6afb76c450f3484a1198faac4df6598 100644\n--- a/src/runtime/runtime-promise.cc\n+++ b/src/runtime/runtime-promise.cc\n@@ -133,8 +133,10 @@ RUNTIME_FUNCTION(Runtime_RejectPromise) {\n   DirectHandle<JSPromise> promise = args.at<JSPromise>(0);\n   DirectHandle<Object> reason = args.at(1);\n   DirectHandle<Boolean> debug_event = args.at<Boolean>(2);\n-  return *JSPromise::Reject(promise, reason,\n-                            Object::BooleanValue(*debug_event, isolate));\n+  Handle<Object> result = JSPromise::Reject(\n+      promise, reason, Object::BooleanValue(*debug_event, isolate));\n+  RETURN_FAILURE_IF_EXCEPTION(isolate);\n+  return *result;\n }\n \n RUNTIME_FUNCTION(Runtime_ResolvePromise) {\n@@ -222,8 +224,8 @@ RUNTIME_FUNCTION(Runtime_PromiseContextInit) {\n   if (!isolate->has_promise_context_tag()) {\n     args.at<JSPromise>(0)->set_context_tag(Smi::zero());\n   } else {\n-    CHECK(!IsUndefined(isolate->promise_context_tag()));\n-    args.at<JSPromise>(0)->set_context_tag(isolate->promise_context_tag());\n+    CHECK(!IsUndefined(*isolate->promise_context_tag()));\n+    args.at<JSPromise>(0)->set_context_tag(*isolate->promise_context_tag());\n   }\n   return ReadOnlyRoots(isolate).undefined_value();\n }\n@@ -237,8 +239,9 @@ RUNTIME_FUNCTION(Runtime_PromiseContextCheck) {\n   // If promise.context_tag() is strict equal to isolate.promise_context_tag(),\n   // or if the promise being checked does not have a context tag, we'll just\n   // return promise directly.\n-  Tagged<Object> obj = promise->context_tag();\n-  if (obj == Smi::zero() || obj == isolate->promise_context_tag()) {\n+  Handle<Object> obj(promise->context_tag(), isolate);\n+  if (*obj == Smi::zero() ||\n+      obj.is_identical_to(isolate->promise_context_tag())) {\n     return *promise;\n   }\n \n@@ -252,5 +255,23 @@ RUNTIME_FUNCTION(Runtime_PromiseContextCheck) {\n   return *result;\n }\n \n+RUNTIME_FUNCTION(Runtime_PromiseResolveContextCheck) {\n+  HandleScope scope(isolate);\n+  DCHECK_EQ(1, args.length());\n+  Handle<JSPromise> promise = args.at<JSPromise>(0);\n+  // If promise.context_tag() is strict equal to isolate.promise_context_tag(),\n+  // or if the promise being checked does not have a context tag, or if the\n+  // resolve callback has not been set, we'll just return false here to indicate\n+  // that the default handling should be used.\n+  Handle<Object> obj(promise->context_tag(), isolate);\n+  if (*obj == Smi::zero() ||\n+      obj.is_identical_to(isolate->promise_context_tag()) ||\n+      !isolate->has_promise_context_resolve_callback()) {\n+    return isolate->heap()->ToBoolean(false);\n+  }\n+\n+  return isolate->heap()->ToBoolean(true);\n+}\n+\n }  // namespace internal\n }  // namespace v8\ndiff --git a/src/runtime/runtime.h b/src/runtime/runtime.h\nindex 802970ee0c1546c2f0c649e9a05d08554357389d..ab4f5f85a2b9284e9e1577d06da13a817979dc05 100644\n--- a/src/runtime/runtime.h\n+++ b/src/runtime/runtime.h\n@@ -447,7 +447,8 @@ constexpr bool CanTriggerGC(T... properties) {\n   F(ConstructAggregateErrorHelper, 4, 1)                    \\\n   F(ConstructInternalAggregateErrorHelper, -1 /* <= 5*/, 1) \\\n   F(PromiseContextInit, 1, 1)                               \\\n-  F(PromiseContextCheck, 1, 1)\n+  F(PromiseContextCheck, 1, 1)                              \\\n+  F(PromiseResolveContextCheck, 1, 1)\n \n #define FOR_EACH_INTRINSIC_PROXY(F, I) \\\n   F(CheckProxyGetSetTrapResult, 2, 1)  \\\n"
  },
  {
    "path": "patches/v8/0014-Add-another-slot-in-the-isolate-for-embedder.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Erik Corry <erikcorry@chromium.org>\nDate: Mon, 2 Dec 2024 14:16:37 +0100\nSubject: Add another slot in the isolate for embedder\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/include/v8-internal.h b/include/v8-internal.h\nindex c45a20133631443792d74c1473c86ef424d43ce3..d9ace30d25717e28c16a3e53381ba999e825d61c 100644\n--- a/include/v8-internal.h\n+++ b/include/v8-internal.h\n@@ -959,7 +959,7 @@ class Internals {\n   // AccessorInfo::data and InterceptorInfo::data field.\n   static const int kCallbackInfoDataOffset = 1 * kApiTaggedSize;\n \n-  static const uint32_t kNumIsolateDataSlots = 4;\n+  static const uint32_t kNumIsolateDataSlots = 6;\n   static const int kStackGuardSize = 8 * kApiSystemPointerSize;\n   static const int kNumberOfBooleanFlags = 6;\n   static const int kErrorMessageParamSize = 1;\n"
  },
  {
    "path": "patches/v8/0015-Add-ValueSerializer-SetTreatProxiesAsHostObjects.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Kenton Varda <kenton@cloudflare.com>\nDate: Wed, 4 Dec 2024 22:36:05 -0600\nSubject: Add ValueSerializer::SetTreatProxiesAsHostObjects().\n\nPreviously, ValueSerializer would always refuse to serialize Proxy objects. This commit gives the embedder the option to handle them as host objects.\n\nSimilar to the previous patch adding `SetTreatFunctionsAsHostObjects()`, this is intended for use in an RPC system, where an arbitrary object can be \"serialized\" by replacing it with a stub which, when invoked, performs an RPC back to the originating isolate in order to access the original object there.\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/include/v8-value-serializer.h b/include/v8-value-serializer.h\nindex 141f138e08de849e3e02b3b2b346e643b9e40c70..bdcb2831c55e21c6d511f56dfc79a5076871f05a 100644\n--- a/include/v8-value-serializer.h\n+++ b/include/v8-value-serializer.h\n@@ -204,6 +204,15 @@ class V8_EXPORT ValueSerializer {\n    */\n   void SetTreatFunctionsAsHostObjects(bool mode);\n \n+  /**\n+   * Indicate whether to treat Proxies as host objects,\n+   * i.e. pass them to Delegate::WriteHostObject. This should not be\n+   * called when no Delegate was passed.\n+   *\n+   * The default is not to treat Proxies as host objects.\n+   */\n+  void SetTreatProxiesAsHostObjects(bool mode);\n+\n   /**\n    * Write raw data in various common formats to the buffer.\n    * Note that integer types are written in base-128 varint format, not with a\ndiff --git a/src/api/api.cc b/src/api/api.cc\nindex 9dfad16174088cb5b007ffd6441f3998371d4655..f025e7dace968ceb9187f19222d90f557677dc4e 100644\n--- a/src/api/api.cc\n+++ b/src/api/api.cc\n@@ -3511,6 +3511,10 @@ void ValueSerializer::SetTreatFunctionsAsHostObjects(bool mode) {\n   private_->serializer.SetTreatFunctionsAsHostObjects(mode);\n }\n \n+void ValueSerializer::SetTreatProxiesAsHostObjects(bool mode) {\n+  private_->serializer.SetTreatProxiesAsHostObjects(mode);\n+}\n+\n Maybe<bool> ValueSerializer::WriteValue(Local<Context> context,\n                                         Local<Value> value) {\n   auto i_isolate = i::Isolate::Current();\ndiff --git a/src/objects/value-serializer.cc b/src/objects/value-serializer.cc\nindex 03a67c4b13e11ee6d1013c974175b26ead0e5ef4..abba3c959210ebf12d51668d38d5400e9a7c668f 100644\n--- a/src/objects/value-serializer.cc\n+++ b/src/objects/value-serializer.cc\n@@ -340,6 +340,10 @@ void ValueSerializer::SetTreatFunctionsAsHostObjects(bool mode) {\n   treat_functions_as_host_objects_ = mode;\n }\n \n+void ValueSerializer::SetTreatProxiesAsHostObjects(bool mode) {\n+  treat_proxies_as_host_objects_ = mode;\n+}\n+\n void ValueSerializer::WriteTag(SerializationTag tag) {\n   uint8_t raw_tag = static_cast<uint8_t>(tag);\n   WriteRawBytes(&raw_tag, sizeof(raw_tag));\n@@ -611,7 +615,12 @@ Maybe<bool> ValueSerializer::WriteJSReceiver(\n   InstanceType instance_type = receiver->map()->instance_type();\n   if (IsCallable(*receiver)) {\n     if (treat_functions_as_host_objects_) {\n-      return WriteHostObject(Cast<JSObject>(receiver));\n+      return WriteHostObject(receiver);\n+    }\n+    return ThrowDataCloneError(MessageTemplate::kDataCloneError, receiver);\n+  } else if (instance_type == JS_PROXY_TYPE) {\n+    if (treat_proxies_as_host_objects_) {\n+      return WriteHostObject(receiver);\n     }\n     return ThrowDataCloneError(MessageTemplate::kDataCloneError, receiver);\n   } else if (IsSpecialReceiverInstanceType(instance_type) &&\n@@ -1298,7 +1307,7 @@ Maybe<bool> ValueSerializer::WriteSharedObject(\n   return ThrowIfOutOfMemory();\n }\n \n-Maybe<bool> ValueSerializer::WriteHostObject(DirectHandle<JSObject> object) {\n+Maybe<bool> ValueSerializer::WriteHostObject(DirectHandle<JSReceiver> object) {\n   WriteTag(SerializationTag::kHostObject);\n   if (!delegate_) {\n     isolate_->Throw(*isolate_->factory()->NewError(\ndiff --git a/src/objects/value-serializer.h b/src/objects/value-serializer.h\nindex ddc5f27a80f93bae209f3fe8731d4df4baa58ead..496aab365007a45806264c8d3b981bd7a494f903 100644\n--- a/src/objects/value-serializer.h\n+++ b/src/objects/value-serializer.h\n@@ -111,6 +111,15 @@ class ValueSerializer {\n    */\n   void SetTreatFunctionsAsHostObjects(bool mode);\n \n+  /*\n+   * Indicate whether to treat Proxies as host objects,\n+   * i.e. pass them to Delegate::WriteHostObject. This should not be\n+   * called when no Delegate was passed.\n+   *\n+   * The default is not to treat Proxies as host objects.\n+   */\n+  void SetTreatProxiesAsHostObjects(bool mode);\n+\n  private:\n   // Managing allocations of the internal buffer.\n   Maybe<bool> ExpandBuffer(size_t required_capacity);\n@@ -161,8 +170,7 @@ class ValueSerializer {\n #endif  // V8_ENABLE_WEBASSEMBLY\n   Maybe<bool> WriteSharedObject(DirectHandle<HeapObject> object)\n       V8_WARN_UNUSED_RESULT;\n-  Maybe<bool> WriteHostObject(DirectHandle<JSObject> object)\n-      V8_WARN_UNUSED_RESULT;\n+  Maybe<bool> WriteHostObject(DirectHandle<JSReceiver> object) V8_WARN_UNUSED_RESULT;\n \n   /*\n    * Reads the specified keys from the object and writes key-value pairs to the\n@@ -195,6 +203,7 @@ class ValueSerializer {\n   bool has_custom_host_objects_ = false;\n   bool treat_array_buffer_views_as_host_objects_ = false;\n   bool treat_functions_as_host_objects_ = false;\n+  bool treat_proxies_as_host_objects_ = false;\n   bool out_of_memory_ = false;\n   Zone zone_;\n   uint32_t version_;\n"
  },
  {
    "path": "patches/v8/0016-Disable-memory-leak-assert-when-shutting-down-V8.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Erik Corry <erikcorry@chromium.org>\nDate: Thu, 5 Dec 2024 14:07:44 +0100\nSubject: Disable memory leak assert when shutting down V8\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/src/wasm/wasm-code-pointer-table.cc b/src/wasm/wasm-code-pointer-table.cc\nindex 51a20ad8d94e271218be4b993bce7067aaa176fa..c135cd7fc66b3f3ef0c9fba801a6936d9436c846 100644\n--- a/src/wasm/wasm-code-pointer-table.cc\n+++ b/src/wasm/wasm-code-pointer-table.cc\n@@ -14,7 +14,10 @@ void WasmCodePointerTable::Initialize() { Base::Initialize(); }\n void WasmCodePointerTable::TearDown() {\n   FreeNativeFunctionHandles();\n   SweepSegments(0);\n-  DCHECK(freelist_head_.load().is_empty());\n+  // This triggers for Cloudflare when shutting down V8, but that\n+  // is only something that happens in tests.  TODO: Investigate\n+  // whether this is leaking in regular use.\n+  // CHECK(freelist_head_.load().is_empty());\n   Base::TearDown();\n }\n \n"
  },
  {
    "path": "patches/v8/0017-Enable-V8-shared-linkage.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Felix Hanau <felix@cloudflare.com>\nDate: Sun, 9 Jul 2023 18:46:20 -0400\nSubject: Enable V8 shared linkage\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/BUILD.bazel b/BUILD.bazel\nindex 6035f31240dc1e0421be5b5e918d23bc5ad6b8d4..401ab449242f3e11028872baec6584b8169e11f1 100644\n--- a/BUILD.bazel\n+++ b/BUILD.bazel\n@@ -1473,6 +1473,7 @@ filegroup(\n         \"src/builtins/constants-table-builder.cc\",\n         \"src/builtins/constants-table-builder.h\",\n         \"src/builtins/data-view-ops.h\",\n+        \"src/builtins/profile-data-reader.cc\",\n         \"src/builtins/profile-data-reader.h\",\n         \"src/codegen/aligned-slot-allocator.cc\",\n         \"src/codegen/aligned-slot-allocator.h\",\n@@ -1657,7 +1658,6 @@ filegroup(\n         \"src/execution/futex-emulation.h\",\n         \"src/execution/interrupts-scope.cc\",\n         \"src/execution/interrupts-scope.h\",\n-        \"src/execution/isolate.cc\",\n         \"src/execution/isolate.h\",\n         \"src/execution/isolate-data.h\",\n         \"src/execution/isolate-data-fields.h\",\n@@ -3274,7 +3274,6 @@ filegroup(\n filegroup(\n     name = \"v8_compiler_files\",\n     srcs = [\n-        \"src/builtins/profile-data-reader.h\",\n         \"src/compiler/access-builder.cc\",\n         \"src/compiler/access-builder.h\",\n         \"src/compiler/access-info.cc\",\n@@ -3881,8 +3880,6 @@ filegroup(\n         \"src/builtins/growable-fixed-array-gen.cc\",\n         \"src/builtins/growable-fixed-array-gen.h\",\n         \"src/builtins/number-builtins-reducer-inl.h\",\n-        \"src/builtins/profile-data-reader.cc\",\n-        \"src/builtins/profile-data-reader.h\",\n         \"src/builtins/setup-builtins-internal.cc\",\n         \"src/builtins/torque-csa-header-includes.h\",\n         \"src/codegen/turboshaft-builtins-assembler-inl.h\",\n@@ -4158,6 +4155,7 @@ filegroup(\n         \"src/snapshot/snapshot-empty.cc\",\n         \"src/snapshot/static-roots-gen.cc\",\n         \"src/snapshot/static-roots-gen.h\",\n+        \"src/execution/isolate.cc\",\n     ],\n )\n \n@@ -4268,6 +4266,10 @@ filegroup(\n     name = \"noicu/snapshot_files\",\n     srcs = [\n         \"src/init/setup-isolate-deserialize.cc\",\n+        # Not a snapshot file per se, but depends on symbols only present in the snapshot (or\n+        # through a placeholder when building the snapshot itself); having it here makes using\n+        # shared linkage for the main v8 target possible.\n+        \"src/execution/isolate.cc\",\n     ] + select({\n         \"@v8//bazel/config:v8_target_arm\": [\n             \"google3/snapshots/arm/noicu/embedded.S\",\n@@ -4285,6 +4287,7 @@ filegroup(\n     name = \"icu/snapshot_files\",\n     srcs = [\n         \"src/init/setup-isolate-deserialize.cc\",\n+        \"src/execution/isolate.cc\",\n     ] + select({\n         \"@v8//bazel/config:v8_target_arm\": [\n             \"google3/snapshots/arm/icu/embedded.S\",\ndiff --git a/bazel/defs.bzl b/bazel/defs.bzl\nindex 48b9f03fb6e0333137ab6753e31dada7034aa1c2..efc01f6bfc1cef24e8554fd710910a5044367cd5 100644\n--- a/bazel/defs.bzl\n+++ b/bazel/defs.bzl\n@@ -305,7 +305,6 @@ def v8_library(\n             copts = copts + default.copts,\n             linkopts = linkopts + default.linkopts,\n             alwayslink = 1,\n-            linkstatic = 1,\n             **kwargs\n         )\n \n@@ -324,7 +323,6 @@ def v8_library(\n             copts = copts + default.copts + ENABLE_I18N_SUPPORT_DEFINES,\n             linkopts = linkopts + default.linkopts,\n             alwayslink = 1,\n-            linkstatic = 1,\n             **kwargs\n         )\n \n@@ -344,7 +342,6 @@ def v8_library(\n             copts = copts + default.copts,\n             linkopts = linkopts + default.linkopts,\n             alwayslink = 1,\n-            linkstatic = 1,\n             **kwargs\n         )\n \n"
  },
  {
    "path": "patches/v8/0018-Modify-where-to-look-for-fast_float-and-simdutf.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Erik Corry <erikcorry@chromium.org>\nDate: Mon, 3 Mar 2025 22:30:37 +0100\nSubject: Modify where to look for fast_float and simdutf.\n\nSimilar to fp16, these dependencies now needs to be downloaded by bazel.\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/BUILD.bazel b/BUILD.bazel\nindex 401ab449242f3e11028872baec6584b8169e11f1..8c4ce514cd6dbd4e2a389c04b591a26f56a76724 100644\n--- a/BUILD.bazel\n+++ b/BUILD.bazel\n@@ -4577,17 +4577,19 @@ cc_library(\n     ],\n )\n \n-cc_library(\n-  name = \"simdutf\",\n-  srcs = [\"third_party/simdutf/simdutf.cpp\"],\n-  hdrs = [\"third_party/simdutf/simdutf.h\"],\n-  copts = select({\n-        \"@v8//bazel/config:is_clang\": [\"-std=c++20\"],\n-        \"@v8//bazel/config:is_gcc\": [\"-std=gnu++2a\"],\n-        \"@v8//bazel/config:is_windows\": [\"/std:c++20\"],\n-        \"//conditions:default\": [],\n-    }),\n-)\n+# The simdutf library is commented out to avoid conflicts with\n+# the version we use in workers\n+# cc_library(\n+#   name = \"simdutf\",\n+#   srcs = [\"third_party/simdutf/simdutf.cpp\"],\n+#   hdrs = [\"third_party/simdutf/simdutf.h\"],\n+#   copts = select({\n+#         \"@v8//bazel/config:is_clang\": [\"-std=c++20\"],\n+#         \"@v8//bazel/config:is_gcc\": [\"-std=gnu++2a\"],\n+#         \"@v8//bazel/config:is_windows\": [\"/std:c++20\"],\n+#         \"//conditions:default\": [],\n+#     }),\n+# )\n \n v8_library(\n     name = \"v8_libshared\",\n@@ -4618,15 +4620,15 @@ v8_library(\n     ],\n     deps = [\n         \":lib_dragonbox\",\n-        \"//third_party/fast_float/src:fast_float\",\n         \":lib_fp16\",\n-        \":simdutf\",\n         \":v8_libbase\",\n         \"@abseil-cpp//absl/container:btree\",\n         \"@abseil-cpp//absl/container:flat_hash_map\",\n         \"@abseil-cpp//absl/container:flat_hash_set\",\n         \"@abseil-cpp//absl/functional:overload\",\n         \"@highway//:hwy\",\n+        \"@fast_float\",\n+        \"@simdutf\",\n     ],\n )\n \ndiff --git a/src/builtins/builtins-typed-array.cc b/src/builtins/builtins-typed-array.cc\nindex 06849f39146789df8eaa7d192803dc7a34f8838e..3ab036e76312f6554308136d1a758f26f775135f 100644\n--- a/src/builtins/builtins-typed-array.cc\n+++ b/src/builtins/builtins-typed-array.cc\n@@ -2,6 +2,7 @@\n // Use of this source code is governed by a BSD-style license that can be\n // found in the LICENSE file.\n \n+#include \"simdutf.h\"\n #include \"src/base/logging.h\"\n #include \"src/base/macros.h\"\n #include \"src/builtins/builtins-utils-inl.h\"\n@@ -15,7 +16,6 @@\n #include \"src/objects/objects-inl.h\"\n #include \"src/objects/option-utils.h\"\n #include \"src/objects/simd.h\"\n-#include \"third_party/simdutf/simdutf.h\"\n \n namespace v8::internal {\n \ndiff --git a/src/objects/string-inl.h b/src/objects/string-inl.h\nindex 175fabb486a8892771bd901e6dfda2d5af5ad305..8149433531edaca95431adee17e920ea46708ee1 100644\n--- a/src/objects/string-inl.h\n+++ b/src/objects/string-inl.h\n@@ -12,6 +12,8 @@\n #include <type_traits>\n \n #include \"absl/functional/overload.h\"\n+#include \"simdutf.h\"\n+#include \"src/base/template-utils.h\"\n #include \"src/common/assert-scope.h\"\n #include \"src/common/globals.h\"\n #include \"src/execution/isolate-utils.h\"\n@@ -38,7 +40,6 @@\n #include \"src/torque/runtime-macro-shims.h\"\n #include \"src/torque/runtime-support.h\"\n #include \"src/utils/utils.h\"\n-#include \"third_party/simdutf/simdutf.h\"\n \n // Has to be the last include (doesn't have include guards):\n #include \"src/objects/object-macros.h\"\ndiff --git a/src/objects/string.h b/src/objects/string.h\nindex c6003839758fa44cce5dbb1359b95605080ba22f..c8ef0434577d960f2d666b79e4b3896390ad270d 100644\n--- a/src/objects/string.h\n+++ b/src/objects/string.h\n@@ -8,6 +8,7 @@\n #include <memory>\n #include <optional>\n \n+#include \"simdutf.h\"\n #include \"src/base/bits.h\"\n #include \"src/base/export-template.h\"\n #include \"src/base/small-vector.h\"\n@@ -21,7 +22,6 @@\n #include \"src/objects/tagged.h\"\n #include \"src/sandbox/external-pointer.h\"\n #include \"src/strings/unicode-decoder.h\"\n-#include \"third_party/simdutf/simdutf.h\"\n \n // Has to be the last include (doesn't have include guards):\n #include \"src/objects/object-macros.h\"\ndiff --git a/src/strings/unicode-inl.h b/src/strings/unicode-inl.h\nindex 25f3d0375e7f1a71cb1e58f6244b3000b81a5bb2..3f1b5b33f3432ad8046d69382e25326a9776a9f9 100644\n--- a/src/strings/unicode-inl.h\n+++ b/src/strings/unicode-inl.h\n@@ -8,9 +8,9 @@\n #include \"src/strings/unicode.h\"\n // Include the non-inl header before the rest of the headers.\n \n+#include \"simdutf.h\"\n #include \"src/base/logging.h\"\n #include \"src/utils/utils.h\"\n-#include \"third_party/simdutf/simdutf.h\"\n \n namespace unibrow {\n \ndiff --git a/src/strings/unicode.cc b/src/strings/unicode.cc\nindex d213ea68e8ad1dde4eec3ba97b6f9f23db6173e5..74687f7463e3a133095a3cfd7a00e0bf7da0132b 100644\n--- a/src/strings/unicode.cc\n+++ b/src/strings/unicode.cc\n@@ -22,7 +22,7 @@\n #endif\n \n #include \"hwy/highway.h\"\n-#include \"third_party/simdutf/simdutf.h\"\n+#include \"simdutf.h\"\n \n namespace unibrow {\n \n"
  },
  {
    "path": "patches/v8/0019-Remove-unneded-latomic-linker-flag.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Erik Corry <erikcorry@chromium.org>\nDate: Mon, 7 Apr 2025 12:32:30 +0200\nSubject: Remove unneded -latomic linker flag\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/bazel/defs.bzl b/bazel/defs.bzl\nindex efc01f6bfc1cef24e8554fd710910a5044367cd5..60aa2bcf4dc983265a7b38aa6dcc104fbfda879d 100644\n--- a/bazel/defs.bzl\n+++ b/bazel/defs.bzl\n@@ -212,7 +212,7 @@ def _default_args():\n                 \"Shell32.lib\",\n             ],\n             \"@v8//bazel/config:is_macos\": [\"-pthread\"],\n-            \"//conditions:default\": [\"-Wl,--no-as-needed -ldl -latomic -pthread\"],\n+            \"//conditions:default\": [\"-Wl,--no-as-needed -ldl -pthread\"],\n         }) + select({\n             \":should_add_rdynamic\": [\"-rdynamic\"],\n             \"//conditions:default\": [],\n"
  },
  {
    "path": "patches/v8/0020-Add-methods-to-get-heap-and-external-memory-sizes-di.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Kenton Varda <kenton@cloudflare.com>\nDate: Thu, 26 Mar 2020 19:15:48 -0500\nSubject: Add methods to get heap and external memory sizes directly.\n\n`GetHeapStatistics()` exists for this, but also collects a lot of other info and apparently performs non-trivial computation. These new accessors are intended to be very fast.\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/include/v8-isolate.h b/include/v8-isolate.h\nindex 33d21ee604b389966aefe3ba3b3eaa5e02f609f9..067c9f3fec9e21890a1c8d5d1c430d1a473c1a4c 100644\n--- a/include/v8-isolate.h\n+++ b/include/v8-isolate.h\n@@ -1083,6 +1083,16 @@ class V8_EXPORT Isolate {\n   V8_DEPRECATE_SOON(\"Use ExternalMemoryAccounter instead.\")\n   int64_t AdjustAmountOfExternalAllocatedMemory(int64_t change_in_bytes);\n \n+  /**\n+   * Gets the current amount of external memory.\n+   */\n+  int64_t GetExternalMemory();\n+\n+  /**\n+   * Gets the current total size of the heap (internal memory).\n+   */\n+  size_t GetHeapSize();\n+\n   /**\n    * Returns heap profiler for this isolate. Will return NULL until the isolate\n    * is initialized.\ndiff --git a/src/api/api.cc b/src/api/api.cc\nindex f025e7dace968ceb9187f19222d90f557677dc4e..6563567f14ad294631cb9ff18be7da736df677b5 100644\n--- a/src/api/api.cc\n+++ b/src/api/api.cc\n@@ -10350,6 +10350,14 @@ void Isolate::GetHeapStatistics(HeapStatistics* heap_statistics) {\n #endif  // V8_ENABLE_WEBASSEMBLY\n }\n \n+int64_t Isolate::GetExternalMemory() {\n+  return reinterpret_cast<i::Isolate*>(this)->heap()->external_memory();\n+}\n+\n+size_t Isolate::GetHeapSize() {\n+  return reinterpret_cast<i::Isolate*>(this)->heap()->CommittedMemory();\n+}\n+\n size_t Isolate::NumberOfHeapSpaces() {\n   return i::LAST_SPACE - i::FIRST_SPACE + 1;\n }\n"
  },
  {
    "path": "patches/v8/0021-Port-concurrent-mksnapshot-support.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Felix Hanau <felix@cloudflare.com>\nDate: Sun, 8 Jun 2025 16:39:03 -0400\nSubject: Port concurrent mksnapshot support\n\nChange-Id: I57c8158ff5d624e5379e6b072f27ac7a40419522\n\ndiff --git a/BUILD.bazel b/BUILD.bazel\nindex 8c4ce514cd6dbd4e2a389c04b591a26f56a76724..a2848e9358ca331997061ae105498c07b7d5c775 100644\n--- a/BUILD.bazel\n+++ b/BUILD.bazel\n@@ -120,6 +120,11 @@ v8_flag(name = \"v8_enable_hugepage\")\n \n v8_flag(name = \"v8_enable_fast_mksnapshot\")\n \n+v8_flag(\n+    name = \"v8_enable_concurrent_mksnapshot\",\n+    default = True,\n+)\n+\n v8_flag(name = \"v8_enable_future\")\n \n # NOTE: Transitions are not recommended in library targets:\n@@ -4506,6 +4511,13 @@ v8_mksnapshot(\n             \"--no-turbo-verify-allocation\",\n         ],\n         \"//conditions:default\": [],\n+    }) + select({\n+        \":is_v8_enable_concurrent_mksnapshot\": [\n+            \"--concurrent-builtin-generation\",\n+            # Use all the cores for concurrent builtin generation.\n+            \"--concurrent-turbofan-max-threads=0\",\n+        ],\n+        \"//conditions:default\": [],\n     }) + select({\n         \":is_v8_enable_snapshot_code_comments\": [\"--code-comments\"],\n         \"//conditions:default\": [],\n"
  },
  {
    "path": "patches/v8/0022-Port-V8_USE_ZLIB-support.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Felix Hanau <felix@cloudflare.com>\nDate: Sun, 8 Jun 2025 16:40:37 -0400\nSubject: Port V8_USE_ZLIB support\n\nChange-Id: Icfedf3e90522f1ff5037517a39a5f0e3d44abace\n\ndiff --git a/BUILD.bazel b/BUILD.bazel\nindex a2848e9358ca331997061ae105498c07b7d5c775..369e3f975beacf9aea41f05aae919554a8944a61 100644\n--- a/BUILD.bazel\n+++ b/BUILD.bazel\n@@ -162,6 +162,11 @@ v8_flag(name = \"v8_enable_verify_predictable\")\n \n v8_flag(name = \"v8_enable_test_features\")\n \n+v8_flag(\n+    name = \"v8_use_zlib\",\n+    default = True,\n+)\n+\n v8_flag(name = \"v8_wasm_random_fuzzers\")\n \n v8_flag(\n@@ -521,6 +526,7 @@ v8_config(\n         \"v8_jitless\": \"V8_JITLESS\",\n         \"v8_enable_vtunejit\": \"ENABLE_VTUNE_JIT_INTERFACE\",\n         \"v8_enable_undefined_double\": \"V8_ENABLE_UNDEFINED_DOUBLE\",\n+        \"v8_use_zlib\": \"V8_USE_ZLIB\",\n     },\n     defines = [\n         \"GOOGLE3\",\n@@ -4641,6 +4647,8 @@ v8_library(\n         \"@highway//:hwy\",\n         \"@fast_float\",\n         \"@simdutf\",\n+        \"@zlib\",\n+        \"@zlib//:compression_utils_portable\",\n     ],\n )\n \ndiff --git a/src/deoptimizer/frame-translation-builder.cc b/src/deoptimizer/frame-translation-builder.cc\nindex 3c1ed7cf1e45ab4b87dd134430f7cda321e5e338..fa41e87973bebbe36da35e90a513653779bc1915 100644\n--- a/src/deoptimizer/frame-translation-builder.cc\n+++ b/src/deoptimizer/frame-translation-builder.cc\n@@ -11,7 +11,7 @@\n #include \"src/objects/fixed-array-inl.h\"\n \n #ifdef V8_USE_ZLIB\n-#include \"third_party/zlib/google/compression_utils_portable.h\"\n+#include <google/compression_utils_portable.h>\n #endif  // V8_USE_ZLIB\n \n namespace v8 {\ndiff --git a/src/objects/deoptimization-data.cc b/src/objects/deoptimization-data.cc\nindex fc9805d59779984bf8f038673e2371662181c3fa..717a0ea734d41538bd9aa4186140bfff6e5cf5de 100644\n--- a/src/objects/deoptimization-data.cc\n+++ b/src/objects/deoptimization-data.cc\n@@ -14,7 +14,7 @@\n #include \"src/objects/shared-function-info.h\"\n \n #ifdef V8_USE_ZLIB\n-#include \"third_party/zlib/google/compression_utils_portable.h\"\n+#include <google/compression_utils_portable.h>\n #endif  // V8_USE_ZLIB\n \n namespace v8 {\ndiff --git a/src/snapshot/snapshot-utils.cc b/src/snapshot/snapshot-utils.cc\nindex ea04fcec0ecebd409d1ed9839dd0db3ac73160a9..e67eb478a0f49fa2a6c4f338e86a7e6a5e6d686f 100644\n--- a/src/snapshot/snapshot-utils.cc\n+++ b/src/snapshot/snapshot-utils.cc\n@@ -7,7 +7,7 @@\n #include \"src/base/sanitizer/msan.h\"\n \n #ifdef V8_USE_ZLIB\n-#include \"third_party/zlib/zlib.h\"\n+#include <zlib.h>\n #endif\n \n namespace v8 {\n"
  },
  {
    "path": "patches/v8/0023-Modify-where-to-look-for-dragonbox.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Yagiz Nizipli <yagiz@nizipli.com>\nDate: Thu, 19 Jun 2025 10:05:19 -0400\nSubject: Modify where to look for dragonbox\n\n\ndiff --git a/BUILD.bazel b/BUILD.bazel\nindex 369e3f975beacf9aea41f05aae919554a8944a61..b3ae97e40f7fd77ba9122ad35421759540a0a100 100644\n--- a/BUILD.bazel\n+++ b/BUILD.bazel\n@@ -4089,14 +4089,9 @@ filegroup(\n )\n \n v8_library(\n-  name = \"lib_dragonbox\",\n-  srcs = [\"third_party/dragonbox/src/include/dragonbox/dragonbox.h\"],\n-  hdrs = [\n-    \"third_party/dragonbox/src/include/dragonbox/dragonbox.h\",\n-  ],\n-  includes = [\n-    \"third_party/dragonbox/src/include\",\n-  ],\n+    name = \"lib_dragonbox\",\n+    srcs = [],\n+    deps = [\"@dragonbox\"]\n )\n \n cc_library(\n"
  },
  {
    "path": "patches/v8/0024-Disable-slow-handle-check.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Erik Corry <erikcorry@chromium.org>\nDate: Tue, 22 Apr 2025 14:59:14 +0200\nSubject: Disable slow handle check\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/src/handles/handles.h b/src/handles/handles.h\nindex 9367344523f562d00a6bf129fa6ed053fa3d2ce1..bc84ff7f491f36625e72291e128e332b76eefa26 100644\n--- a/src/handles/handles.h\n+++ b/src/handles/handles.h\n@@ -636,11 +636,7 @@ IndirectHandle<T> indirect_handle(DirectHandle<T> handle,\n // does not implicitly convert to an IndirectHandle.\n template <typename T>\n class V8_TRIVIAL_ABI DirectHandle :\n-#ifdef ENABLE_SLOW_DCHECKS\n-    public api_internal::StackAllocated<true>\n-#else\n     public api_internal::StackAllocated<false>\n-#endif\n {\n  public:\n   V8_INLINE static const DirectHandle null() {\n"
  },
  {
    "path": "patches/v8/0025-Workaround-for-builtin-can-allocate-issue.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Erik Corry <erikcorry@chromium.org>\nDate: Fri, 20 Jun 2025 16:44:42 +0200\nSubject: Workaround for builtin-can-allocate issue\n\n\ndiff --git a/src/snapshot/builtins-effects-dummy.cc b/src/snapshot/builtins-effects-dummy.cc\nindex 495c65aabdf13c22f053ab492971d42f6c84cc8f..7df94c00f137098c3d76e455c08a3d3074f51b12 100644\n--- a/src/snapshot/builtins-effects-dummy.cc\n+++ b/src/snapshot/builtins-effects-dummy.cc\n@@ -13,6 +13,6 @@ namespace v8::internal {\n // TODO(dmercadier): try to compile builtins in an order such that callees are\n // compiled before callers, so that we can make use of the CanAllocate\n // information for callees when computing callers.\n-bool BuiltinCanAllocate(Builtin builtin) { return true; }\n+bool BuiltinCanAllocate(Builtin builtin) { return false; }\n \n }  // namespace v8::internal\n"
  },
  {
    "path": "patches/v8/0026-Implement-additional-Exception-construction-methods.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: James M Snell <jsnell@cloudflare.com>\nDate: Tue, 1 Jul 2025 17:33:43 -0700\nSubject: Implement additional Exception construction methods\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/include/v8-exception.h b/include/v8-exception.h\nindex f240d9a609e92b4a3055256996ad69d8fc14ac49..f8546f34d207e4e2e6fd1c5d8b87b83b956e4e23 100644\n--- a/include/v8-exception.h\n+++ b/include/v8-exception.h\n@@ -48,6 +48,14 @@ class V8_EXPORT Exception {\n   static Local<Value> WasmSuspendError(Local<String> message,\n                                        Local<Value> options = {});\n   static Local<Value> Error(Local<String> message, Local<Value> options = {});\n+  static Local<Value> URIError(Local<String> message,\n+                               Local<Value> options = {});\n+  static Local<Value> EvalError(Local<String> message,\n+                               Local<Value> options = {});\n+  static Local<Value> AggregateError(Local<String> message,\n+                                     Local<Value> options = {});\n+  static Local<Value> SuppressedError(Local<String> message,\n+                                      Local<Value> options = {});\n \n   /**\n    * Creates an error message for the given exception.\ndiff --git a/src/api/api.cc b/src/api/api.cc\nindex 6563567f14ad294631cb9ff18be7da736df677b5..7c40e29cd48da285e024c23c2853903522684aff 100644\n--- a/src/api/api.cc\n+++ b/src/api/api.cc\n@@ -11234,6 +11234,10 @@ DEFINE_ERROR(WasmCompileError, wasm_compile_error)\n DEFINE_ERROR(WasmLinkError, wasm_link_error)\n DEFINE_ERROR(WasmRuntimeError, wasm_runtime_error)\n DEFINE_ERROR(WasmSuspendError, wasm_suspend_error)\n+DEFINE_ERROR(EvalError, eval_error)\n+DEFINE_ERROR(URIError, uri_error)\n+DEFINE_ERROR(AggregateError, aggregate_error)\n+DEFINE_ERROR(SuppressedError, suppressed_error)\n DEFINE_ERROR(Error, error)\n \n #undef DEFINE_ERROR\ndiff --git a/src/logging/runtime-call-stats.h b/src/logging/runtime-call-stats.h\nindex 66b0450b2eab3105845d6742e8afc4c4d8072828..a0a866c61408a417749a9e9d52a53d145e91ca0f 100644\n--- a/src/logging/runtime-call-stats.h\n+++ b/src/logging/runtime-call-stats.h\n@@ -219,7 +219,11 @@ namespace v8::internal {\n   V(WeakMap_Delete)                                        \\\n   V(WeakMap_Get)                                           \\\n   V(WeakMap_New)                                           \\\n-  V(WeakMap_Set)\n+  V(WeakMap_Set)                                           \\\n+  V(EvalError_New)                                         \\\n+  V(URIError_New)                                          \\\n+  V(AggregateError_New)                                    \\\n+  V(SuppressedError_New)\n \n #define ADD_THREAD_SPECIFIC_COUNTER(V, Prefix, Suffix) \\\n   V(Prefix##Suffix)                                    \\\n"
  },
  {
    "path": "patches/v8/0027-Export-icudata-file-to-facilitate-embedding-it.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Felix Hanau <felix@cloudflare.com>\nDate: Fri, 8 Aug 2025 16:48:06 -0400\nSubject: Export icudata file to facilitate embedding it\n\nChange-Id: Ie8ccf14aa86bae3c3c7ff1d3c2b507e19c6178be\n\ndiff --git a/bazel/BUILD.icu b/bazel/BUILD.icu\nindex 5127ceb7b783b11d5750c01977d7e34606c39667..de8e20ac3ad50fe2199d3b56753e655d1355b19f 100644\n--- a/bazel/BUILD.icu\n+++ b/bazel/BUILD.icu\n@@ -1,6 +1,7 @@\n # Copyright 2021 the V8 project authors. All rights reserved.\n # Use of this source code is governed by a BSD-style license that can be\n # found in the LICENSE file.\n+exports_files([\"common/icudtl.dat\"])\n \n filegroup(\n     name = \"icudata\",\n"
  },
  {
    "path": "patches/v8/0028-bind-icu-to-googlesource.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Alex Eagle <alex@aspect.dev>\nDate: Thu, 2 Oct 2025 02:17:00 +0100\nSubject: bind icu to googlesource\n\n\ndiff --git a/BUILD.bazel b/BUILD.bazel\nindex b3ae97e40f7fd77ba9122ad35421759540a0a100..159142a018dd65f608c986cde611b0aed2df4bed 100644\n--- a/BUILD.bazel\n+++ b/BUILD.bazel\n@@ -4618,7 +4618,7 @@ v8_library(\n     copts = [\"-Wno-implicit-fallthrough\"],\n     icu_deps = [\n         \":icu/generated_torque_definitions_headers\",\n-        \"//external:icu\",\n+        \"@com_googlesource_chromium_icu//:icu\",\n     ],\n     icu_srcs = [\n         \":generated_regexp_special_case\",\n@@ -4741,7 +4741,7 @@ v8_binary(\n     ],\n     deps = [\n         \":v8_libbase\",\n-        \"//external:icu\",\n+        \"@com_googlesource_chromium_icu//:icu\",\n     ],\n )\n \n"
  },
  {
    "path": "patches/v8/0029-Add-v8-String-IsFlat-API.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: James M Snell <jsnell@cloudflare.com>\nDate: Thu, 6 Nov 2025 10:58:38 -0800\nSubject: Add v8::String::IsFlat API\n\nTells us if a string is already flattened or not.\n\nSigned-off-by: James M Snell <jsnell@cloudflare.com>\n\ndiff --git a/include/v8-primitive.h b/include/v8-primitive.h\nindex 457a8838461faa9fcb16af05f0f06fa01a6d93bb..28cf8b61f08563c3359fee37d50b6ec1df11439c 100644\n--- a/include/v8-primitive.h\n+++ b/include/v8-primitive.h\n@@ -157,6 +157,11 @@ class V8_EXPORT String : public Name {\n    */\n   bool ContainsOnlyOneByte() const;\n \n+  /**\n+   * Returns true if this string is flat\n+   */\n+  bool IsFlat() const;\n+\n   struct WriteFlags {\n     enum {\n       kNone = 0,\ndiff --git a/src/api/api.cc b/src/api/api.cc\nindex 7c40e29cd48da285e024c23c2853903522684aff..dd90403e2b1361017af4d393f90681214ee598f7 100644\n--- a/src/api/api.cc\n+++ b/src/api/api.cc\n@@ -5728,6 +5728,10 @@ bool String::IsOneByte() const {\n   return Utils::OpenDirectHandle(this)->IsOneByteRepresentation();\n }\n \n+bool String::IsFlat() const {\n+  return Utils::OpenDirectHandle(this)->IsFlat();\n+}\n+\n class ContainsOnlyOneByteHelper {\n  public:\n   ContainsOnlyOneByteHelper() : is_one_byte_(true) {}\n"
  },
  {
    "path": "patches/v8/0030-Expose-AdjustAmountOfExternalAllocatedMemoryImpl-as-.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Aditya Tewari <adityaatewari@gmail.com>\nDate: Wed, 15 Jan 2025 12:00:00 +0000\nSubject: Expose AdjustAmountOfExternalAllocatedMemoryImpl as public API\n\nThe deprecated AdjustAmountOfExternalAllocatedMemory is being replaced\nby ExternalMemoryAccounter, but ExternalMemoryAccounter requires that\nadjustments return to zero before destruction. This is incompatible with\nworkerd's design where external memory may outlive the isolate.\n\nV8 already has AdjustAmountOfExternalAllocatedMemoryImpl as a private\nmethod. This patch simply makes it public for embedder use.\n\nSigned-off-by: Aditya Tewari <adityaatewari@gmail.com>\n\ndiff --git a/include/v8-isolate.h b/include/v8-isolate.h\nindex 067c9f3fec9e21890a1c8d5d1c430d1a473c1a4c..07987b7af4d1dee4731c3006258fad67d02fd526 100644\n--- a/include/v8-isolate.h\n+++ b/include/v8-isolate.h\n@@ -1093,6 +1093,14 @@ class V8_EXPORT Isolate {\n    */\n   size_t GetHeapSize();\n \n+  /**\n+   * Adjusts external memory without destructor constraints.\n+   * This directly calls the internal implementation without the\n+   * zero-balance requirement of ExternalMemoryAccounter.\n+   * Use when external memory may outlive the isolate.\n+   */\n+  int64_t AdjustAmountOfExternalAllocatedMemoryImpl(int64_t change_in_bytes);\n+\n   /**\n    * Returns heap profiler for this isolate. Will return NULL until the isolate\n    * is initialized.\n@@ -1906,7 +1914,6 @@ class V8_EXPORT Isolate {\n \n   internal::ValueHelper::InternalRepresentationType GetDataFromSnapshotOnce(\n       size_t index);\n-  int64_t AdjustAmountOfExternalAllocatedMemoryImpl(int64_t change_in_bytes);\n   void HandleExternalMemoryInterrupt();\n };\n \n"
  },
  {
    "path": "patches/v8/0031-Add-verify_write_barriers-flag-in-V8-s-bazel-config.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Ketan Gupta <ketan@cloudflare.com>\nDate: Wed, 4 Feb 2026 15:05:21 +0000\nSubject: Add verify_write_barriers flag in V8's bazel config\n\n\ndiff --git a/BUILD.bazel b/BUILD.bazel\nindex 159142a018dd65f608c986cde611b0aed2df4bed..d954621014d8c3b8c5d005382edee9fd936709d0 100644\n--- a/BUILD.bazel\n+++ b/BUILD.bazel\n@@ -540,6 +540,7 @@ v8_config(\n         \"@v8//bazel/config:is_debug\": [\n             \"DEBUG\",\n             \"V8_ENABLE_CHECKS\",\n+            \"V8_VERIFY_WRITE_BARRIERS\",\n         ],\n         \"//conditions:default\": [],\n     }) + select({\n"
  },
  {
    "path": "patches/v8/0032-Change-lamba-signature-to-get-around-windows-build-f.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Ketan Gupta <ketan@cloudflare.com>\nDate: Wed, 4 Feb 2026 15:48:36 +0000\nSubject: Change lamba signature to get around windows build failure\n\n\ndiff --git a/src/objects/backing-store.cc b/src/objects/backing-store.cc\nindex 251ddd3c131866da41eb7c0ca47c790480d4d239..2bfba594a264093a72dc34c853c83863fa79cb8f 100644\n--- a/src/objects/backing-store.cc\n+++ b/src/objects/backing-store.cc\n@@ -310,7 +310,7 @@ std::unique_ptr<BackingStore> BackingStore::TryAllocateAndPartiallyCommitMemory(\n   // For accounting purposes, whether a GC was necessary.\n   bool did_retry = false;\n \n-  auto gc_retry = [&](const std::function<bool()>& fn) {\n+  auto gc_retry = [&](auto&& fn) {\n     if (fn()) return true;\n     // Collect garbage and retry.\n     did_retry = true;\n"
  },
  {
    "path": "patches/v8/0033-Return-false-on-Object.hasOwnProperty-with-intercept.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Dan Carney <dcarney@chromium.org>\nDate: Tue, 17 Feb 2026 08:45:16 +0000\nSubject: Return false on Object.hasOwnProperty with interceptors\n\n\ndiff --git a/src/objects/js-objects.cc b/src/objects/js-objects.cc\nindex 8e18b4fe6602dbdec9154c166d12e01b09cb0dd6..be5439d1f157ea4cdfc078c639a20ab93d4b16cf 100644\n--- a/src/objects/js-objects.cc\n+++ b/src/objects/js-objects.cc\n@@ -157,6 +157,9 @@ Maybe<bool> JSReceiver::HasOwnProperty(Isolate* isolate,\n   if (IsJSObject(*object)) {  // Shortcut.\n     PropertyKey key(isolate, name);\n     LookupIterator it(isolate, object, key, LookupIterator::OWN);\n+    if (it.state() == LookupIterator::INTERCEPTOR) {\n+      return Just(false);\n+    }\n     return HasProperty(&it);\n   }\n \ndiff --git a/src/runtime/runtime-object.cc b/src/runtime/runtime-object.cc\nindex 2daf5a3390b2f732eac934c279b3a2ae594058e9..6d8151b813dc9c86e3d00b092f3e912ec1fb7ac9 100644\n--- a/src/runtime/runtime-object.cc\n+++ b/src/runtime/runtime-object.cc\n@@ -203,6 +203,9 @@ RUNTIME_FUNCTION(Runtime_ObjectHasOwnProperty) {\n \n     // Slow case.\n     LookupIterator it(isolate, js_obj, key, js_obj, LookupIterator::OWN);\n+    if (it.state() == LookupIterator::INTERCEPTOR) {\n+      return isolate->heap()->ToBoolean(false);\n+    }\n     Maybe<bool> maybe = JSReceiver::HasProperty(&it);\n     if (maybe.IsNothing()) return ReadOnlyRoots(isolate).exception();\n     DCHECK(!isolate->has_exception());\n"
  },
  {
    "path": "patches/v8/0034-Remove-V8-MODULE.bazel-llvm-toolchain-and-libcxx-rep.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Workerd Maintainers <workers-dev@cloudflare.com>\nDate: Wed, 4 Mar 2026 12:37:49 -0800\nSubject: Remove V8 MODULE.bazel llvm toolchain and libcxx repo rules\n\nThese reference third_party/ sources that are not present in the GitHub\ntarball. Workerd provides its own toolchain, so these are not needed.\n\ndiff --git a/MODULE.bazel b/MODULE.bazel\nindex 7d7ba53b579605a6f469fe01ddf699d1284110e3..7ddd8259e790ebbfce0bfbb08e08ed4130592f9b 100644\n--- a/MODULE.bazel\n+++ b/MODULE.bazel\n@@ -22,167 +22,7 @@ pip.parse(\n )\n use_repo(pip, \"v8_python_deps\")\n \n-# Define the local LLVM toolchain repository\n-llvm_toolchain_repository = use_repo_rule(\"//bazel/toolchain:llvm_repository.bzl\", \"llvm_toolchain_repository\")\n \n-llvm_toolchain_repository(\n-    name = \"llvm_toolchain\",\n-    path = \"third_party/llvm-build/Release+Asserts\",\n-    config_file_content = \"\"\"\n-load(\"@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl\", \"feature\", \"flag_group\", \"flag_set\", \"tool_path\")\n-\n-def _impl(ctx):\n-    tool_paths = [\n-        tool_path(name = \"gcc\", path = \"bin/clang\"),\n-        tool_path(name = \"ld\", path = \"bin/lld\"),\n-        tool_path(name = \"ar\", path = \"bin/llvm-ar\"),\n-        tool_path(name = \"cpp\", path = \"bin/clang++\"),\n-        tool_path(name = \"gcov\", path = \"/bin/false\"),\n-        tool_path(name = \"nm\", path = \"bin/llvm-nm\"),\n-        tool_path(name = \"objdump\", path = \"bin/llvm-objdump\"),\n-        tool_path(name = \"strip\", path = \"bin/llvm-strip\"),\n-    ]\n-\n-    features = [\n-        feature(\n-            name = \"default_compile_flags\",\n-            enabled = True,\n-            flag_sets = [\n-                flag_set(\n-                    actions = [\n-                        \"c-compile\",\n-                        \"c++-compile\",\n-                        \"c++-header-parsing\",\n-                        \"c++-module-compile\",\n-                        \"c++-module-codegen\",\n-                        \"linkstamp-compile\",\n-                        \"assemble\",\n-                        \"preprocess-assemble\",\n-                    ],\n-                    flag_groups = [\n-                        flag_group(\n-                            flags = [\n-                                \"--sysroot={WORKSPACE_ROOT}/build/linux/debian_bullseye_amd64-sysroot\",\n-                                \"-nostdinc++\",\n-                                \"-isystem\",\n-                                \"{WORKSPACE_ROOT}/buildtools/third_party/libc++\",\n-                                \"-isystem\",\n-                                \"{WORKSPACE_ROOT}/third_party/libc++/src/include\",\n-                                \"-isystem\",\n-                                \"{WORKSPACE_ROOT}/third_party/libc++abi/src/include\",\n-                                \"-isystem\",\n-                                \"{WORKSPACE_ROOT}/third_party/libc++/src/src\",\n-                                \"-isystem\",\n-                                \"{WORKSPACE_ROOT}/third_party/llvm-libc/src\",\n-                                \"-D_LIBCPP_HARDENING_MODE_DEFAULT=_LIBCPP_HARDENING_MODE_NONE\",\n-                                \"-DLIBC_NAMESPACE=__llvm_libc_cr\",\n-                            ],\n-                        ),\n-                    ],\n-                ),\n-            ],\n-        ),\n-        feature(\n-            name = \"default_linker_flags\",\n-            enabled = True,\n-            flag_sets = [\n-                flag_set(\n-                    actions = [\n-                        \"c++-link-executable\",\n-                        \"c++-link-dynamic-library\",\n-                        \"c++-link-nodeps-dynamic-library\",\n-                    ],\n-                    flag_groups = [\n-                        flag_group(\n-                            flags = [\n-                                \"--sysroot={WORKSPACE_ROOT}/build/linux/debian_bullseye_amd64-sysroot\",\n-                                \"-fuse-ld=lld\",\n-                                \"-lm\",\n-                                \"-lpthread\",\n-                            ],\n-                        ),\n-                    ],\n-                ),\n-            ],\n-        ),\n-    ]\n-\n-    return cc_common.create_cc_toolchain_config_info(\n-        ctx = ctx,\n-        features = features,\n-        cxx_builtin_include_directories = [\n-            \"{WORKSPACE_ROOT}/buildtools/third_party/libc++\",\n-            \"{WORKSPACE_ROOT}/third_party/libc++/src/include\",\n-            \"{WORKSPACE_ROOT}/third_party/libc++abi/src/include\",\n-            \"{WORKSPACE_ROOT}/third_party/libc++/src/src\",\n-            \"{WORKSPACE_ROOT}/third_party/llvm-libc/src\",\n-            \"{WORKSPACE_ROOT}/third_party/llvm-build/Release+Asserts/lib/clang/22/include\",\n-            \"{WORKSPACE_ROOT}/third_party/llvm-build/Release+Asserts/lib/clang/23/include\",\n-            \"{WORKSPACE_ROOT}/build/linux/debian_bullseye_amd64-sysroot/usr/include\",\n-            \"{WORKSPACE_ROOT}/build/linux/debian_bullseye_amd64-sysroot/usr/local/include\",\n-        ],\n-        toolchain_identifier = \"local_clang\",\n-        host_system_name = \"local\",\n-        target_system_name = \"local\",\n-        target_cpu = \"k8\",\n-        target_libc = \"unknown\",\n-        compiler = \"clang\",\n-        abi_version = \"unknown\",\n-        abi_libc_version = \"unknown\",\n-        tool_paths = tool_paths,\n-    )\n-\n-cc_toolchain_config = rule(\n-    implementation = _impl,\n-    attrs = {},\n-    provides = [CcToolchainConfigInfo],\n-)\n-\"\"\",\n-    build_file_content = \"\"\"\n-load(\":cc_toolchain_config.bzl\", \"cc_toolchain_config\")\n-\n-package(default_visibility = [\"//visibility:public\"])\n-\n-filegroup(\n-    name = \"all_files\",\n-    srcs = glob([\"**/*\"]),\n-)\n-\n-filegroup(name = \"empty\")\n-\n-cc_toolchain_config(name = \"k8_toolchain_config\")\n-\n-cc_toolchain(\n-    name = \"k8_toolchain\",\n-    all_files = \":all_files\",\n-    ar_files = \":all_files\",\n-    compiler_files = \":all_files\",\n-    dwp_files = \":empty\",\n-    linker_files = \":all_files\",\n-    objcopy_files = \":all_files\",\n-    strip_files = \":all_files\",\n-    supports_param_files = 0,\n-    toolchain_config = \":k8_toolchain_config\",\n-    toolchain_identifier = \"local_clang\",\n-)\n-\n-toolchain(\n-    name = \"cc_toolchain_k8\",\n-    exec_compatible_with = [\n-        \"@platforms//cpu:x86_64\",\n-        \"@platforms//os:linux\",\n-    ],\n-    target_compatible_with = [\n-        \"@platforms//cpu:x86_64\",\n-        \"@platforms//os:linux\",\n-    ],\n-    toolchain = \":k8_toolchain\",\n-    toolchain_type = \"@bazel_tools//tools/cpp:toolchain_type\",\n-)\n-\"\"\",\n-)\n-\n-register_toolchains(\"@llvm_toolchain//:cc_toolchain_k8\")\n \n # Define local repository for libc++ from third_party sources\n libcxx_repository = use_repo_rule(\"//bazel/toolchain:libcxx_repository.bzl\", \"libcxx_repository\")\ndiff --git a/bazel/toolchain/libcxx_repository.bzl b/bazel/toolchain/libcxx_repository.bzl\nindex a7d5f11053dd333c5bec614c27168a0effb7b4aa..ed367ef64871133867e9eccb07c6482a4de08ceb 100644\n--- a/bazel/toolchain/libcxx_repository.bzl\n+++ b/bazel/toolchain/libcxx_repository.bzl\n@@ -1,99 +1,17 @@\n-\"\"\"Repository rule for building libc++ from third_party sources.\"\"\"\n+\"\"\"Stub repository rule: workerd uses the system/toolchain libc++, so @libcxx\n+is an empty shim that satisfies the dep in bazel/defs.bzl without pulling in\n+any third_party sources.\"\"\"\n \n def _libcxx_repository_impl(ctx):\n-    # Find the workspace root\n-    workspace_root = ctx.path(Label(\"@//:BUILD.bazel\")).dirname\n-\n-    # Symlink the source directories\n-    ctx.symlink(workspace_root.get_child(\"third_party\").get_child(\"libc++\"), \"libc++\")\n-    ctx.symlink(workspace_root.get_child(\"third_party\").get_child(\"libc++abi\"), \"libc++abi\")\n-    ctx.symlink(workspace_root.get_child(\"third_party\").get_child(\"llvm-libc\"), \"llvm-libc\")\n-    ctx.symlink(workspace_root.get_child(\"buildtools\").get_child(\"third_party\").get_child(\"libc++\"), \"buildtools_libc++\")\n-\n-    # Get the external repository path for include flags\n-    # In bzlmod, repo names may have prefixes, so we need to determine the actual path\n-    repo_path = \"external/\" + ctx.name\n-\n-    # Create the BUILD file\n-    # NOTE: We don't use 'includes' attribute here because it creates relative paths\n-    # that conflict with the toolchain's absolute paths, breaking #include_next.\n-    # The toolchain provides the libc++ include paths via -isystem flags.\n-    build_content = '''\n+    ctx.file(\"BUILD.bazel\", \"\"\"\n package(default_visibility = [\"//visibility:public\"])\n \n-LIBCXX_COPTS = [\n-    \"-std=c++23\",\n-    \"-fPIC\",\n-    \"-fstrict-aliasing\",\n-    \"-fexceptions\",\n-    \"-frtti\",\n-    \"-D_LIBCPP_BUILDING_LIBRARY\",\n-    \"-D_LIBCPP_HARDENING_MODE_DEFAULT=_LIBCPP_HARDENING_MODE_NONE\",\n-    \"-DLIBC_NAMESPACE=__llvm_libc_cr\",\n-]\n-\n-cc_library(\n-    name = \"libc++abi\",\n-    srcs = glob([\n-        \"libc++abi/src/src/*.cpp\",\n-        \"libc++abi/src/src/*.h\",\n-        \"libc++abi/src/src/demangle/*.h\",\n-    ], exclude = [\n-        # Exclude files not needed for Linux build\n-        \"libc++abi/src/src/cxa_noexception.cpp\",\n-        \"libc++abi/src/src/stdlib_new_delete.cpp\",\n-    ]),\n-    hdrs = glob([\n-        \"libc++abi/src/include/**/*.h\",\n-        \"libc++/src/include/**/*\",\n-        \"libc++/src/src/include/*.h\",\n-        \"libc++abi/src/src/demangle/*.def\",\n-        \"buildtools_libc++/__config_site\",\n-        \"buildtools_libc++/__assertion_handler\",\n-        \"llvm-libc/src/**/*.h\",\n-    ]),\n-    copts = LIBCXX_COPTS + [\n-        \"-DLIBCXXABI_SILENT_TERMINATE\",\n-        \"-iquote\", \"{REPO_PATH}/libc++abi/src/src\",\n-        \"-iquote\", \"{REPO_PATH}/libc++abi/src/src/demangle\",\n-    ],\n-    linkstatic = True,\n-)\n-\n-cc_library(\n-    name = \"libc++\",\n-    srcs = glob([\n-        \"libc++/src/src/*.cpp\",\n-        \"libc++/src/src/*.h\",\n-        \"libc++/src/src/filesystem/*.cpp\",\n-        \"libc++/src/src/filesystem/*.h\",\n-        \"libc++/src/src/ryu/*.cpp\",\n-        \"libc++/src/src/include/*.h\",\n-        \"libc++/src/src/include/ryu/*.h\",\n-    ]) + glob([\"libc++/src/src/support/**/*.ipp\"], allow_empty = True),\n-    hdrs = glob([\n-        \"libc++/src/include/**/*\",\n-        \"buildtools_libc++/__config_site\",\n-        \"buildtools_libc++/__assertion_handler\",\n-        \"llvm-libc/src/**/*.h\",\n-    ]),\n-    copts = LIBCXX_COPTS + [\n-        \"-DLIBCXX_BUILDING_LIBCXXABI\",\n-        \"-iquote\", \"{REPO_PATH}/libc++/src/src\",\n-        \"-iquote\", \"{REPO_PATH}/libc++/src/src/filesystem\",\n-    ],\n-    linkopts = [\n-        \"-lpthread\",\n-        \"-lm\",\n-    ],\n-    deps = [\":libc++abi\"],\n-    linkstatic = True,\n-)\n-'''.format(REPO_PATH=repo_path)\n-    ctx.file(\"BUILD.bazel\", build_content)\n+# Empty stub – the real libc++ is provided by the toolchain.\n+cc_library(name = \"libc++\")\n+cc_library(name = \"libc++abi\")\n+\"\"\")\n \n libcxx_repository = repository_rule(\n     implementation = _libcxx_repository_impl,\n     local = True,\n-    configure = True,\n )\n"
  },
  {
    "path": "patches/v8/0035-Remove-libcxx-dep-from-defs.bzl-not-resolvable-via-h.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Mike Aizatsky <maizatskyi@cloudflare.com>\nDate: Wed, 4 Mar 2026 13:19:20 -0800\nSubject: Remove @libcxx dep from defs.bzl: not resolvable via http_archive\n\nWhen V8 is loaded as an http_archive (not a bzlmod module), V8's own\nMODULE.bazel is not processed by Bazel, so @libcxx cannot be created\nvia use_repo_rule. Remove the dependency since workerd provides its\nown toolchain that includes libc++.\n\nAuthor: Workerd Maintainers <workers-dev@cloudflare.com>\n\ndiff --git a/bazel/defs.bzl b/bazel/defs.bzl\nindex 60aa2bcf4dc983265a7b38aa6dcc104fbfda879d..b22b7f7520461db8520cfd754ed6082d94544b4a 100644\n--- a/bazel/defs.bzl\n+++ b/bazel/defs.bzl\n@@ -97,7 +97,7 @@ v8_config = rule(\n \n def _default_args():\n     return struct(\n-        deps = [\":define_flags\", \"@libcxx//:libc++\"],\n+        deps = [\":define_flags\"],\n         defines = select({\n             \"@v8//bazel/config:is_windows\": [\n                 \"UNICODE\",\n"
  },
  {
    "path": "patches/zlib/0001-Add-dummy-MODULE.bazel.patch",
    "content": "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\nFrom: Felix Hanau <felix@cloudflare.com>\nDate: Sun, 8 Jun 2025 15:55:56 -0400\nSubject: Add dummy MODULE.bazel\n\n\ndiff --git a/MODULE.bazel b/MODULE.bazel\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..3611da399064ef179d4924a67c3bbdda5a765d15\n--- /dev/null\n+++ b/MODULE.bazel\n@@ -0,0 +1,10 @@\n+# dummy file used to support bzlmod\n+module(\n+    name = \"zlib\",\n+    version = \"0.0.0\",\n+    compatibility_level = 1,\n+)\n+\n+bazel_dep(name = \"platforms\", version = \"1.0.0\")\n+bazel_dep(name = \"rules_cc\", version = \"0.1.1\")\n+bazel_dep(name = \"bazel_skylib\", version = \"1.7.1\")\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "minimumReleaseAge: 1440\nminimumReleaseAgeExclude:\n  - '@types/node'\n  - 'wrangler'\n  - 'workerd'\n  - 'miniflare'\n  - '@cloudflare/workerd-*'\n  - '@opennextjs/cloudflare'\n  - '@opennextjs/aws'\npackages:\n  - .\n  - images/container-client-test\n  - src/workerd/api/tests/opennextjs/src\n"
  },
  {
    "path": "ruff.toml",
    "content": "target-version = \"py312\"\n\n[lint]\nselect = [\n  \"B0\",     # bugbear (all B0* checks enabled by default)\n  \"B904\",   # bugbear (Within an except clause, raise exceptions with raise ... from err)\n  \"B905\",   # bugbear (zip() without an explicit strict= parameter set.)\n  \"E\",      # pycodestyles\n  \"W\",      # pycodestyles\n  \"F\",      # pyflakes\n  \"FURB\",   # refurb\n  \"I\",      # isort\n  \"PERF\",   # Perflint\n  \"PGH\",    # pygrep-hooks\n  \"PLC\",    # pylint conventions\n  \"PLE\",    # pylint errors\n  \"PLR\",    # pylint refactor\n  \"PTH\",    # Use pathlib\n  \"RUF\",    # Ruff rules\n  \"TRY\",    # Exception related lints\n  \"UP\",     # pyupgrade\n]\nignore = [\n  \"E402\",  # module import not at top of file\n  \"E501\", # line too long\n  \"PLR0912\", # too many branches\n  \"PLR0915\", # too many statements\n  \"PLR2004\", # Magic value used in comparison\n  \"TRY003\", # Avoid specifying long messages outside the exception class\n  \"UP038\",  # Use X | Y in isinstance check instead of (X, Y)\n  \"PLR0911\", # too many return statements\n  \"PLC0415\", # `import` should be at the top-level of a file\n]\n\n[lint.per-file-ignores]\n# Want to preserve compatibility for old Python versions in tools directory so\n# disable pyupgrade. Oldest supported version is currently 3.9.\n\"tools/*\" = [\"UP\"]\n"
  },
  {
    "path": "samples/BUILD.bazel",
    "content": "exports_files([\n    \"helloworld/config.capnp\",\n    \"helloworld/worker.js\",\n])\n"
  },
  {
    "path": "samples/async-context/config.capnp",
    "content": "# Imports the base schema for workerd configuration files.\n\n# Refer to the comments in /src/workerd/server/workerd.capnp for more details.\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\n# A constant of type Workerd.Config defines the top-level configuration for an\n# instance of the workerd runtime. A single config file can contain multiple\n# Workerd.Config definitions and must have at least one.\nconst helloWorldExample :Workerd.Config = (\n\n  # Every workerd instance consists of a set of named services. A worker, for instance,\n  # is a type of service. Other types of services can include external servers, the\n  # ability to talk to a network, or accessing a disk directory. Here we create a single\n  # worker service. The configuration details for the worker are defined below.\n  services = [ (name = \"main\", worker = .helloWorld) ],\n\n  # Every configuration defines the one or more sockets on which the server will listene.\n  # Here, we create a single socket that will listen on localhost port 8080, and will\n  # dispatch to the \"main\" service that we defined above.\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\n# The definition of the actual helloWorld worker exposed using the \"main\" service.\n# In this example the worker is implemented as a single simple script (see worker.js).\n# The compatibilityDate is required. For more details on compatibility dates see:\n#   https://developers.cloudflare.com/workers/platform/compatibility-dates/\n\nconst helloWorld :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\")\n  ],\n  compatibilityDate = \"2023-02-28\",\n  # Only ask for workerd's implementation of `node:async_hooks`'s AsyncLocalStorage, \n  # instead of all of the node compatibility APIs, provided by `nodejs_compat`. See:\n  # https://developers.cloudflare.com/workers/configuration/compatibility-dates/#nodejs-compatibility-flag\n  compatibilityFlags = [\"nodejs_als\"]\n);\n"
  },
  {
    "path": "samples/async-context/worker.js",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { AsyncLocalStorage, AsyncResource } from 'node:async_hooks';\n\nconst als = new AsyncLocalStorage();\n\nexport default {\n  async fetch(request) {\n    const differentScope = als.run(123, () => AsyncResource.bind(() => {\n      console.log(als.getStore());\n    }));\n\n    return als.run(\"Hello World\", async () => {\n\n      // differentScope is attached to a different async context, so\n      // it will see a different value for als.getStore() (123)\n      setTimeout(differentScope, 5);\n\n      // Some simulated async delay.\n      await scheduler.wait(10);\n      return new Response(als.getStore());  // \"Hello World\"\n    });\n  }\n};\n"
  },
  {
    "path": "samples/durable-objects-chat/chat.html",
    "content": "<!DOCTYPE html>\n<!--\n  THIS IS NOT THE INTERESTING FILE\n\n  This is just some UI code. There's nothing interesting and unique in this file. The interesting\n  thing about this demo is the server side, which is in chat.mjs.\n\n  WARNING: This was written by a systems engineer, not a web developer. It's probably bad.\n-->\n\n<html>\n  <head>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n<!--===================================================================================-->\n<!-- Inline style to avoid an extra round trip before the page can render. -->\n<style type=\"text/css\">\n* {\n  box-sizing: border-box;\n}\nbody {\n  font-family: Arial, Helvetica, sans-serif;\n}\n\n#chatlog {\n  position: fixed;\n  top: 0;\n  bottom: 32px;\n  left: 0;\n  right: 200px;\n  overflow-y: auto;\n  padding: 8px;\n  overflow-wrap: break-word;\n}\n#chatlog span.username {\n  font-weight: bold;\n}\n#spacer {\n  height: calc(100vh - 32px - 5em);\n}\n\n#roster {\n  font-weight: bold;\n  padding: 8px;\n}\n\np {\n  margin-top: 0;\n  margin-bottom: 8px;\n}\np:last-of-type {\n  margin: 0;\n}\n\n#roster {\n  position: fixed;\n  right: 0;\n  top: 0;\n  bottom: 32px;\n  width: 200px;\n  border-left: none;\n}\n\n::-webkit-scrollbar {\n  display: none;\n}\n\n@media(max-width:600px) {\n  #roster { display: none; }\n  #chatlog { right: 0; }\n}\n\n#chat-input {\n  position: fixed;\n  width: 100%;\n  height: 32px;\n  bottom: 0;\n  left: 0;\n  border: none;\n  border-top: none;\n  padding-left: 32px;\n  outline: none;\n}\n#chatroom::before {\n  z-index: 1;\n  display: block;\n  content: \">\";\n  position: fixed;\n  bottom: 0;\n  left: 0;\n  width: 32px;\n  height: 32px;\n  line-height: 32px;\n  text-align: center;\n  font-weight: bold;\n  color: #888;\n  -webkit-text-stroke-width: 2px;\n}\n\n#name-form {\n  position: fixed;\n  z-index: 3;\n  top: 0;\n  bottom: 0;\n  left: 0;\n  right: 0;\n  background-color: white;\n}\n\n#name-input {\n  position: fixed;\n  font-size: 200%;\n  top: calc(50% - 1em);\n  left: calc(50% - 8em);\n  width: 16em;\n  height: 2em;\n  margin: 0;\n  text-align: center;\n  border: 1px solid #bbb;\n}\n\n#name-form p {\n  position: fixed;\n  top: calc(50% + 3em);\n  width: 100%;\n  text-align: center;\n}\n\n#room-form {\n  position: fixed;\n  z-index: 2;\n  top: 0;\n  bottom: 0;\n  left: 0;\n  right: 0;\n  background-color: white;\n  font-size: 200%;\n  margin-top: calc(50vh - 3em);\n  text-align: center;\n}\n\n#room-name {\n  font-size: inherit;\n  border: 1px solid #bbb;\n  height: 2em;\n  width: 16em;\n  padding-left: 1em;\n}\n\n#room-form button {\n  font-size: inherit;\n  border: 1px solid #bbb;\n  background-color: #eee;\n  height: 2em;\n}\n\n@media(max-width:660px) {\n  #name-input, #room-form { font-size: 150%; }\n  #name-form p { font-size: 75%; }\n}\n@media(max-width:500px) {\n  #name-input, #room-form { font-size: 100%; }\n  #name-form p { font-size: 50%; }\n}\n\n#go-public {\n  width: 4em;\n}\n#go-private {\n  width: 20em;\n}\n\n</style>\n\n<!--===================================================================================-->\n<!-- The actual HTML. There is not much of it. -->\n\n  </head>\n  <body>\n    <form id=\"name-form\" action=\"/fake-form-action\">\n      <input id=\"name-input\" placeholder=\"your name\">\n      <p>This chat runs entirely on the edge, powered by<br>\n        <a href=\"https://blog.cloudflare.com/introducing-workers-durable-objects\" target=\"_blank\">Cloudflare Workers Durable Objects</a></p>\n    </form>\n    <form id=\"room-form\" action=\"/fake-form-action\">\n      <p>Enter a public room:</p>\n      <input id=\"room-name\" placeholder=\"room name\"><button id=\"go-public\">Go &raquo;</button>\n      <p>OR</p>\n      <button id=\"go-private\">Create a Private Room &raquo;</button>\n    </form>\n    <form id=\"chatroom\" action=\"/fake-form-action\">\n      <div id=\"chatlog\">\n        <div id=\"spacer\"></div>\n      </div>\n      <div id=\"roster\"></div>\n      <input id=\"chat-input\">\n    </form>\n  </body>\n\n<!--===================================================================================-->\n<!-- Client-side JavaScript code for the app. -->\n\n<script type=\"text/javascript\">\nlet currentWebSocket = null;\n\nlet nameForm = document.querySelector(\"#name-form\");\nlet nameInput = document.querySelector(\"#name-input\");\nlet roomForm = document.querySelector(\"#room-form\");\nlet roomNameInput = document.querySelector(\"#room-name\");\nlet goPublicButton = document.querySelector(\"#go-public\");\nlet goPrivateButton = document.querySelector(\"#go-private\");\nlet chatroom = document.querySelector(\"#chatroom\");\nlet chatlog = document.querySelector(\"#chatlog\");\nlet chatInput = document.querySelector(\"#chat-input\");\nlet roster = document.querySelector(\"#roster\");\n\n// Is the chatlog scrolled to the bottom?\nlet isAtBottom = true;\n\nlet username;\nlet roomname;\n\nlet hostname = window.location.host;\nif (hostname == \"\") {\n  // Probably testing the HTML locally.\n  hostname = \"edge-chat-demo.cloudflareworkers.com\";\n}\n\nfunction startNameChooser() {\n  nameForm.addEventListener(\"submit\", event => {\n    event.preventDefault();\n    username = nameInput.value;\n    if (username.length > 0) {\n      startRoomChooser();\n    }\n  });\n\n  nameInput.addEventListener(\"input\", event => {\n    if (event.currentTarget.value.length > 32) {\n      event.currentTarget.value = event.currentTarget.value.slice(0, 32);\n    }\n  });\n\n  nameInput.focus();\n}\n\nfunction startRoomChooser() {\n  nameForm.remove();\n\n  if (document.location.hash.length > 1) {\n    roomname = document.location.hash.slice(1);\n    startChat();\n    return;\n  }\n\n  roomForm.addEventListener(\"submit\", event => {\n    event.preventDefault();\n    roomname = roomNameInput.value;\n    if (roomname.length > 0) {\n      startChat();\n    }\n  });\n\n  roomNameInput.addEventListener(\"input\", event => {\n    if (event.currentTarget.value.length > 32) {\n      event.currentTarget.value = event.currentTarget.value.slice(0, 32);\n    }\n  });\n\n  goPublicButton.addEventListener(\"click\", event => {\n    roomname = roomNameInput.value;\n    if (roomname.length > 0) {\n      startChat();\n    }\n  });\n\n  goPrivateButton.addEventListener(\"click\", async event => {\n    roomNameInput.disabled = true;\n    goPublicButton.disabled = true;\n    event.currentTarget.disabled = true;\n\n    let response = await fetch(\"https://\" + hostname + \"/api/room\", {method: \"POST\"});\n    if (!response.ok) {\n      alert(\"something went wrong\");\n      document.location.reload();\n      return;\n    }\n\n    roomname = await response.text();\n    startChat();\n  });\n\n  roomNameInput.focus();\n}\n\nfunction startChat() {\n  roomForm.remove();\n\n  // Normalize the room name a bit.\n  roomname = roomname.replace(/[^a-zA-Z0-9_-]/g, \"\").replace(/_/g, \"-\").toLowerCase();\n\n  if (roomname.length > 32 && !roomname.match(/^[0-9a-f]{64}$/)) {\n    addChatMessage(\"ERROR\", \"Invalid room name.\");\n    return;\n  }\n\n  document.location.hash = \"#\" + roomname;\n\n  chatInput.addEventListener(\"keydown\", event => {\n    if (event.keyCode == 38) {\n      // up arrow\n      chatlog.scrollBy(0, -50);\n    } else if (event.keyCode == 40) {\n      // down arrow\n      chatlog.scrollBy(0, 50);\n    } else if (event.keyCode == 33) {\n      // page up\n      chatlog.scrollBy(0, -chatlog.clientHeight + 50);\n    } else if (event.keyCode == 34) {\n      // page down\n      chatlog.scrollBy(0, chatlog.clientHeight - 50);\n    }\n  });\n\n  chatroom.addEventListener(\"submit\", event => {\n    event.preventDefault();\n\n    if (currentWebSocket) {\n      currentWebSocket.send(JSON.stringify({message: chatInput.value}));\n      chatInput.value = \"\";\n\n      // Scroll to bottom whenever sending a message.\n      chatlog.scrollBy(0, 1e8);\n    }\n  });\n\n  chatInput.addEventListener(\"input\", event => {\n    if (event.currentTarget.value.length > 256) {\n      event.currentTarget.value = event.currentTarget.value.slice(0, 256);\n    }\n  });\n\n  chatlog.addEventListener(\"scroll\", event => {\n    isAtBottom = chatlog.scrollTop + chatlog.clientHeight >= chatlog.scrollHeight;\n  });\n\n  chatInput.focus();\n  document.body.addEventListener(\"click\", event => {\n    // If the user clicked somewhere in the window without selecting any text, focus the chat\n    // input.\n    if (window.getSelection().toString() == \"\") {\n      chatInput.focus();\n    }\n  });\n\n  // Detect mobile keyboard appearing and disappearing, and adjust the scroll as appropriate.\n  if('visualViewport' in window) {\n    window.visualViewport.addEventListener('resize', function(event) {\n      if (isAtBottom) {\n        chatlog.scrollBy(0, 1e8);\n      }\n    });\n  }\n\n  join();\n}\n\nlet lastSeenTimestamp = 0;\nlet wroteWelcomeMessages = false;\n\nfunction join() {\n  // If we are running via wrangler dev, use ws:\n  const wss = document.location.protocol === \"http:\" ? \"ws://\" : \"wss://\";\n  let ws = new WebSocket(wss + hostname + \"/api/room/\" + roomname + \"/websocket\");\n  let rejoined = false;\n  let startTime = Date.now();\n\n  let rejoin = async () => {\n    if (!rejoined) {\n      rejoined = true;\n      currentWebSocket = null;\n\n      // Clear the roster.\n      while (roster.firstChild) {\n        roster.removeChild(roster.firstChild);\n      }\n\n      // Don't try to reconnect too rapidly.\n      let timeSinceLastJoin = Date.now() - startTime;\n      if (timeSinceLastJoin < 10000) {\n        // Less than 10 seconds elapsed since last join. Pause a bit.\n        await new Promise(resolve => setTimeout(resolve, 10000 - timeSinceLastJoin));\n      }\n\n      // OK, reconnect now!\n      join();\n    }\n  }\n\n  ws.addEventListener(\"open\", event => {\n    currentWebSocket = ws;\n\n    // Send user info message.\n    ws.send(JSON.stringify({name: username}));\n  });\n\n  ws.addEventListener(\"message\", event => {\n    let data = JSON.parse(event.data);\n\n    if (data.error) {\n      addChatMessage(null, \"* Error: \" + data.error);\n    } else if (data.joined) {\n      let p = document.createElement(\"p\");\n      p.innerText = data.joined;\n      roster.appendChild(p);\n    } else if (data.quit) {\n      for (let child of roster.childNodes) {\n        if (child.innerText == data.quit) {\n          roster.removeChild(child);\n          break;\n        }\n      }\n    } else if (data.ready) {\n      // All pre-join messages have been delivered.\n      if (!wroteWelcomeMessages) {\n        wroteWelcomeMessages = true;\n        addChatMessage(null,\n            \"* This is a demo app built with Cloudflare Workers Durable Objects. The source code \" +\n            \"can be found at: https://github.com/cloudflare/workers-chat-demo\");\n        addChatMessage(null,\n            \"* WARNING: Participants in this chat are random people on the internet. \" +\n            \"Names are not authenticated; anyone can pretend to be anyone. The people \" +\n            \"you are chatting with are NOT Cloudflare employees. Chat history is saved.\");\n        if (roomname.length == 64) {\n          addChatMessage(null,\n              \"* This is a private room. You can invite someone to the room by sending them the URL.\");\n        } else {\n          addChatMessage(null,\n              \"* Welcome to #\" + roomname + \". Say hi!\");\n        }\n      }\n    } else {\n      // A regular chat message.\n      if (data.timestamp > lastSeenTimestamp) {\n        addChatMessage(data.name, data.message);\n        lastSeenTimestamp = data.timestamp;\n      }\n    }\n  });\n\n  ws.addEventListener(\"close\", event => {\n    console.log(\"WebSocket closed, reconnecting:\", event.code, event.reason);\n    rejoin();\n  });\n  ws.addEventListener(\"error\", event => {\n    console.log(\"WebSocket error, reconnecting:\", event);\n    rejoin();\n  });\n}\n\nfunction addChatMessage(name, text) {\n  let p = document.createElement(\"p\");\n  if (name) {\n    let tag = document.createElement(\"span\");\n    tag.className = \"username\";\n    tag.innerText = name + \": \";\n    p.appendChild(tag);\n  }\n  p.appendChild(document.createTextNode(text));\n\n  // Append the new chat line, making sure that if the chatlog was scrolled to the bottom\n  // before, it remains scrolled to the bottom, and otherwise the scroll position doesn't\n  // change.\n  //\n  // TODO(bug): At some point this scrolling logic bitrotted and `isAtBottom` is apparently not\n  //   always true when it should be. No time to investigate now, so I'm hard-coding this to always\n  //   scroll to bottom on a new message so it doesn't look broken.\n  chatlog.appendChild(p);\n  if (isAtBottom || true) {\n    chatlog.scrollBy(0, 1e8);\n  }\n}\n\nstartNameChooser();\n</script>\n<!--===================================================================================-->\n</html>\n"
  },
  {
    "path": "samples/durable-objects-chat/chat.js",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// This is the Edge Chat Demo Worker, built using Durable Objects!\n\n// ===============================\n// Introduction to Modules\n// ===============================\n//\n// The first thing you might notice, if you are familiar with the Workers platform, is that this\n// Worker is written differently from others you may have seen. It even has a different file\n// extension. The `mjs` extension means this JavaScript is an ES Module, which, among other things,\n// means it has imports and exports. Unlike other Workers, this code doesn't use\n// `addEventListener(\"fetch\", handler)` to register its main HTTP handler; instead, it _exports_\n// a handler, as we'll see below.\n//\n// This is a new way of writing Workers that we expect to introduce more broadly in the future. We\n// like this syntax because it is *composable*: You can take two workers written this way and\n// merge them into one worker, by importing the two Workers' exported handlers yourself, and then\n// exporting a new handler that call into the other Workers as appropriate.\n//\n// This new syntax is required when using Durable Objects, because your Durable Objects are\n// implemented by classes, and those classes need to be exported. The new syntax can be used for\n// writing regular Workers (without Durable Objects) too, but for now, you must be in the Durable\n// Objects beta to be able to use the new syntax, while we work out the quirks.\n//\n// To see an example configuration for uploading module-based Workers, check out the wrangler.toml\n// file or one of our Durable Object templates for Wrangler:\n//   * https://github.com/cloudflare/durable-objects-template\n//   * https://github.com/cloudflare/durable-objects-rollup-esm\n//   * https://github.com/cloudflare/durable-objects-webpack-commonjs\n\n// ===============================\n// Required Environment\n// ===============================\n//\n// This worker, when deployed, must be configured with two environment bindings:\n// * rooms: A Durable Object namespace binding mapped to the ChatRoom class.\n// * limiters: A Durable Object namespace binding mapped to the RateLimiter class.\n//\n// Incidentally, in pre-modules Workers syntax, \"bindings\" (like KV bindings, secrets, etc.)\n// appeared in your script as global variables, but in the new modules syntax, this is no longer\n// the case. Instead, bindings are now delivered in an \"environment object\" when an event handler\n// (or Durable Object class constructor) is called. Look for the variable `env` below.\n//\n// We made this change, again, for composability: The global scope is global, but if you want to\n// call into existing code that has different environment requirements, then you need to be able\n// to pass the environment as a parameter instead.\n//\n// Once again, see the wrangler.toml file to understand how the environment is configured.\n\n// =======================================================================================\n// The regular Worker part...\n//\n// This section of the code implements a normal Worker that receives HTTP requests from external\n// clients. This part is stateless.\n\n// With the introduction of modules, we're experimenting with allowing text/data blobs to be\n// uploaded and exposed as synthetic modules. In wrangler.toml we specify a rule that files ending\n// in .html should be uploaded as \"Data\", equivalent to content-type `application/octet-stream`.\n// So when we import it as `HTML` here, we get the HTML content as an `ArrayBuffer`. This lets us\n// serve our app's static asset without relying on any separate storage. (However, the space\n// available for assets served this way is very limited; larger sites should continue to use Workers\n// KV to serve assets.)\nimport HTML from \"./chat.html\";\n\n// `handleErrors()` is a little utility function that can wrap an HTTP request handler in a\n// try/catch and return errors to the client. You probably wouldn't want to use this in production\n// code but it is convenient when debugging and iterating.\nasync function handleErrors(request, func) {\n  try {\n    return await func();\n  } catch (err) {\n    if (request.headers.get(\"Upgrade\") == \"websocket\") {\n      // Annoyingly, if we return an HTTP error in response to a WebSocket request, Chrome devtools\n      // won't show us the response body! So... let's send a WebSocket response with an error\n      // frame instead.\n      let pair = new WebSocketPair();\n      pair[1].accept();\n      pair[1].send(JSON.stringify({error: err.stack}));\n      pair[1].close(1011, \"Uncaught exception during session setup\");\n      return new Response(null, { status: 101, webSocket: pair[0] });\n    } else {\n      return new Response(err.stack, {status: 500});\n    }\n  }\n}\n\n// In modules-syntax workers, we use `export default` to export our script's main event handlers.\n// Here, we export one handler, `fetch`, for receiving HTTP requests. In pre-modules workers, the\n// fetch handler was registered using `addEventHandler(\"fetch\", event => { ... })`; this is just\n// new syntax for essentially the same thing.\n//\n// `fetch` isn't the only handler. If your worker runs on a Cron schedule, it will receive calls\n// to a handler named `scheduled`, which should be exported here in a similar way. We will be\n// adding other handlers for other types of events over time.\nexport default {\n  async fetch(request, env) {\n    return await handleErrors(request, async () => {\n      // We have received an HTTP request! Parse the URL and route the request.\n\n      let url = new URL(request.url);\n      let path = url.pathname.slice(1).split('/');\n\n      if (!path[0]) {\n        // Serve our HTML at the root path.\n        return new Response(HTML, {headers: {\"Content-Type\": \"text/html;charset=UTF-8\"}});\n      }\n\n      switch (path[0]) {\n        case \"api\":\n          // This is a request for `/api/...`, call the API handler.\n          return handleApiRequest(path.slice(1), request, env);\n\n        default:\n          return new Response(\"Not found\", {status: 404});\n      }\n    });\n  }\n}\n\n\nasync function handleApiRequest(path, request, env) {\n  // We've received at API request. Route the request based on the path.\n\n  switch (path[0]) {\n    case \"room\": {\n      // Request for `/api/room/...`.\n\n      if (!path[1]) {\n        // The request is for just \"/api/room\", with no ID.\n        if (request.method == \"POST\") {\n          // POST to /api/room creates a private room.\n          //\n          // Incidentally, this code doesn't actually store anything. It just generates a valid\n          // unique ID for this namespace. Each durable object namespace has its own ID space, but\n          // IDs from one namespace are not valid for any other.\n          //\n          // The IDs returned by `newUniqueId()` are unguessable, so are a valid way to implement\n          // \"anyone with the link can access\" sharing. Additionally, IDs generated this way have\n          // a performance benefit over IDs generated from names: When a unique ID is generated,\n          // the system knows it is unique without having to communicate with the rest of the\n          // world -- i.e., there is no way that someone in the UK and someone in New Zealand\n          // could coincidentally create the same ID at the same time, because unique IDs are,\n          // well, unique!\n          let id = env.rooms.newUniqueId();\n          return new Response(id.toString(), {headers: {\"Access-Control-Allow-Origin\": \"*\"}});\n        } else {\n          // If we wanted to support returning a list of public rooms, this might be a place to do\n          // it. The list of room names might be a good thing to store in KV, though a singleton\n          // Durable Object is also a possibility as long as the Cache API is used to cache reads.\n          // (A caching layer would be needed because a single Durable Object is single-threaded,\n          // so the amount of traffic it can handle is limited. Also, caching would improve latency\n          // for users who don't happen to be located close to the singleton.)\n          //\n          // For this demo, though, we're not implementing a public room list, mainly because\n          // inevitably some trolls would probably register a bunch of offensive room names. Sigh.\n          return new Response(\"Method not allowed\", {status: 405});\n        }\n      }\n\n      // OK, the request is for `/api/room/<name>/...`. It's time to route to the Durable Object\n      // for the specific room.\n      let name = path[1];\n\n      // Each Durable Object has a 256-bit unique ID. IDs can be derived from string names, or\n      // chosen randomly by the system.\n      let id;\n      if (name.match(/^[0-9a-f]{64}$/)) {\n        // The name is 64 hex digits, so let's assume it actually just encodes an ID. We use this\n        // for private rooms. `idFromString()` simply parses the text as a hex encoding of the raw\n        // ID (and verifies that this is a valid ID for this namespace).\n        id = env.rooms.idFromString(name);\n      } else if (name.length <= 32) {\n        // Treat as a string room name (limited to 32 characters). `idFromName()` consistently\n        // derives an ID from a string.\n        id = env.rooms.idFromName(name);\n      } else {\n        return new Response(\"Name too long\", {status: 404});\n      }\n\n      // Get the Durable Object stub for this room! The stub is a client object that can be used\n      // to send messages to the remote Durable Object instance. The stub is returned immediately;\n      // there is no need to await it. This is important because you would not want to wait for\n      // a network round trip before you could start sending requests. Since Durable Objects are\n      // created on-demand when the ID is first used, there's nothing to wait for anyway; we know\n      // an object will be available somewhere to receive our requests.\n      let roomObject = env.rooms.get(id);\n\n      // Compute a new URL with `/api/room/<name>` removed. We'll forward the rest of the path\n      // to the Durable Object.\n      let newUrl = new URL(request.url);\n      newUrl.pathname = \"/\" + path.slice(2).join(\"/\");\n\n      // Send the request to the object. The `fetch()` method of a Durable Object stub has the\n      // same signature as the global `fetch()` function, but the request is always sent to the\n      // object, regardless of the request's URL.\n      return roomObject.fetch(newUrl, request);\n    }\n\n    default:\n      return new Response(\"Not found\", {status: 404});\n  }\n}\n\n// =======================================================================================\n// The ChatRoom Durable Object Class\n\n// ChatRoom implements a Durable Object that coordinates an individual chat room. Participants\n// connect to the room using WebSockets, and the room broadcasts messages from each participant\n// to all others.\nexport class ChatRoom {\n  constructor(controller, env) {\n    // `controller.storage` provides access to our durable storage. It provides a simple KV\n    // get()/put() interface.\n    this.storage = controller.storage;\n\n    // `env` is our environment bindings (discussed earlier).\n    this.env = env;\n\n    // We will put the WebSocket objects for each client, along with some metadata, into\n    // `sessions`.\n    this.sessions = [];\n\n    // We keep track of the last-seen message's timestamp just so that we can assign monotonically\n    // increasing timestamps even if multiple messages arrive simultaneously (see below). There's\n    // no need to store this to disk since we assume if the object is destroyed and recreated, much\n    // more than a millisecond will have gone by.\n    this.lastTimestamp = 0;\n  }\n\n  // The system will call fetch() whenever an HTTP request is sent to this Object. Such requests\n  // can only be sent from other Worker code, such as the code above; these requests don't come\n  // directly from the internet. In the future, we will support other formats than HTTP for these\n  // communications, but we started with HTTP for its familiarity.\n  async fetch(request) {\n    return await handleErrors(request, async () => {\n      let url = new URL(request.url);\n\n      switch (url.pathname) {\n        case \"/websocket\": {\n          // The request is to `/api/room/<name>/websocket`. A client is trying to establish a new\n          // WebSocket session.\n          if (request.headers.get(\"Upgrade\") != \"websocket\") {\n            return new Response(\"expected websocket\", {status: 400});\n          }\n\n          // Get the client's IP address for use with the rate limiter.\n          let ip = request.headers.get(\"CF-Connecting-IP\");\n\n          // To accept the WebSocket request, we create a WebSocketPair (which is like a socketpair,\n          // i.e. two WebSockets that talk to each other), we return one end of the pair in the\n          // response, and we operate on the other end. Note that this API is not part of the\n          // Fetch API standard; unfortunately, the Fetch API / Service Workers specs do not define\n          // any way to act as a WebSocket server today.\n          let pair = new WebSocketPair();\n\n          // We're going to take pair[1] as our end, and return pair[0] to the client.\n          await this.handleSession(pair[1], ip);\n\n          // Now we return the other end of the pair to the client.\n          return new Response(null, { status: 101, webSocket: pair[0] });\n        }\n\n        default:\n          return new Response(\"Not found\", {status: 404});\n      }\n    });\n  }\n\n  // handleSession() implements our WebSocket-based chat protocol.\n  async handleSession(webSocket, ip) {\n    // Accept our end of the WebSocket. This tells the runtime that we'll be terminating the\n    // WebSocket in JavaScript, not sending it elsewhere.\n    webSocket.accept();\n\n    // Set up our rate limiter client.\n    let limiterId = this.env.limiters.idFromName(ip);\n    let limiter = new RateLimiterClient(\n        () => this.env.limiters.get(limiterId),\n        err => webSocket.close(1011, err.stack));\n\n    // Create our session and add it to the sessions list.\n    // We don't send any messages to the client until it has sent us the initial user info\n    // message. Until then, we will queue messages in `session.blockedMessages`.\n    let session = {webSocket, blockedMessages: []};\n    this.sessions.push(session);\n\n    // Queue \"join\" messages for all online users, to populate the client's roster.\n    this.sessions.forEach(otherSession => {\n      if (otherSession.name) {\n        session.blockedMessages.push(JSON.stringify({joined: otherSession.name}));\n      }\n    });\n\n    // Load the last 100 messages from the chat history stored on disk, and send them to the\n    // client.\n    let storage = await this.storage.list({reverse: true, limit: 100});\n    let backlog = [...storage.values()];\n    backlog.reverse();\n    backlog.forEach(value => {\n      session.blockedMessages.push(value);\n    });\n\n    // Set event handlers to receive messages.\n    let receivedUserInfo = false;\n    webSocket.addEventListener(\"message\", async msg => {\n      try {\n        if (session.quit) {\n          // Whoops, when trying to send to this WebSocket in the past, it threw an exception and\n          // we marked it broken. But somehow we got another message? I guess try sending a\n          // close(), which might throw, in which case we'll try to send an error, which will also\n          // throw, and whatever, at least we won't accept the message. (This probably can't\n          // actually happen. This is defensive coding.)\n          webSocket.close(1011, \"WebSocket broken.\");\n          return;\n        }\n\n        // Check if the user is over their rate limit and reject the message if so.\n        if (!limiter.checkLimit()) {\n          webSocket.send(JSON.stringify({\n            error: \"Your IP is being rate-limited, please try again later.\"\n          }));\n          return;\n        }\n\n        // I guess we'll use JSON.\n        let data = JSON.parse(msg.data);\n\n        if (!receivedUserInfo) {\n          // The first message the client sends is the user info message with their name. Save it\n          // into their session object.\n          session.name = \"\" + (data.name || \"anonymous\");\n\n          // Don't let people use ridiculously long names. (This is also enforced on the client,\n          // so if they get here they are not using the intended client.)\n          if (session.name.length > 32) {\n            webSocket.send(JSON.stringify({error: \"Name too long.\"}));\n            webSocket.close(1009, \"Name too long.\");\n            return;\n          }\n\n          // Deliver all the messages we queued up since the user connected.\n          session.blockedMessages.forEach(queued => {\n            webSocket.send(queued);\n          });\n          delete session.blockedMessages;\n\n          // Broadcast to all other connections that this user has joined.\n          this.broadcast({joined: session.name});\n\n          webSocket.send(JSON.stringify({ready: true}));\n\n          // Note that we've now received the user info message.\n          receivedUserInfo = true;\n\n          return;\n        }\n\n        // Construct sanitized message for storage and broadcast.\n        data = { name: session.name, message: \"\" + data.message };\n\n        // Block people from sending overly long messages. This is also enforced on the client,\n        // so to trigger this the user must be bypassing the client code.\n        if (data.message.length > 256) {\n          webSocket.send(JSON.stringify({error: \"Message too long.\"}));\n          return;\n        }\n\n        // Add timestamp. Here's where this.lastTimestamp comes in -- if we receive a bunch of\n        // messages at the same time (or if the clock somehow goes backwards????), we'll assign\n        // them sequential timestamps, so at least the ordering is maintained.\n        data.timestamp = Math.max(Date.now(), this.lastTimestamp + 1);\n        this.lastTimestamp = data.timestamp;\n\n        // Broadcast the message to all other WebSockets.\n        let dataStr = JSON.stringify(data);\n        this.broadcast(dataStr);\n\n        // Save message.\n        let key = new Date(data.timestamp).toISOString();\n        await this.storage.put(key, dataStr);\n      } catch (err) {\n        // Report any exceptions directly back to the client. As with our handleErrors() this\n        // probably isn't what you'd want to do in production, but it's convenient when testing.\n        webSocket.send(JSON.stringify({error: err.stack}));\n      }\n    });\n\n    // On \"close\" and \"error\" events, remove the WebSocket from the sessions list and broadcast\n    // a quit message.\n    let closeOrErrorHandler = evt => {\n      session.quit = true;\n      this.sessions = this.sessions.filter(member => member !== session);\n      if (session.name) {\n        this.broadcast({quit: session.name});\n      }\n    };\n    webSocket.addEventListener(\"close\", closeOrErrorHandler);\n    webSocket.addEventListener(\"error\", closeOrErrorHandler);\n  }\n\n  // broadcast() broadcasts a message to all clients.\n  broadcast(message) {\n    // Apply JSON if we weren't given a string to start with.\n    if (typeof message !== \"string\") {\n      message = JSON.stringify(message);\n    }\n\n    // Iterate over all the sessions sending them messages.\n    let quitters = [];\n    this.sessions = this.sessions.filter(session => {\n      if (session.name) {\n        try {\n          session.webSocket.send(message);\n          return true;\n        } catch (err) {\n          // Whoops, this connection is dead. Remove it from the list and arrange to notify\n          // everyone below.\n          session.quit = true;\n          quitters.push(session);\n          return false;\n        }\n      } else {\n        // This session hasn't sent the initial user info message yet, so we're not sending them\n        // messages yet (no secret lurking!). Queue the message to be sent later.\n        session.blockedMessages.push(message);\n        return true;\n      }\n    });\n\n    quitters.forEach(quitter => {\n      if (quitter.name) {\n        this.broadcast({quit: quitter.name});\n      }\n    });\n  }\n}\n\n// =======================================================================================\n// The RateLimiter Durable Object class.\n\n// RateLimiter implements a Durable Object that tracks the frequency of messages from a particular\n// source and decides when messages should be dropped because the source is sending too many\n// messages.\n//\n// We utilize this in ChatRoom, above, to apply a per-IP-address rate limit. These limits are\n// global, i.e. they apply across all chat rooms, so if a user spams one chat room, they will find\n// themselves rate limited in all other chat rooms simultaneously.\nexport class RateLimiter {\n  constructor(controller, env) {\n    // Timestamp at which this IP will next be allowed to send a message. Start in the distant\n    // past, i.e. the IP can send a message now.\n    this.nextAllowedTime = 0;\n  }\n\n  // Our protocol is: POST when the IP performs an action, or GET to simply read the current limit.\n  // Either way, the result is the number of seconds to wait before allowing the IP to perform its\n  // next action.\n  async fetch(request) {\n    return await handleErrors(request, async () => {\n      let now = Date.now() / 1000;\n\n      this.nextAllowedTime = Math.max(now, this.nextAllowedTime);\n\n      if (request.method == \"POST\") {\n        // POST request means the user performed an action.\n        // We allow one action per 5 seconds.\n        this.nextAllowedTime += 5;\n      }\n\n      // Return the number of seconds that the client needs to wait.\n      //\n      // We provide a \"grace\" period of 20 seconds, meaning that the client can make 4-5 requests\n      // in a quick burst before they start being limited.\n      let cooldown = Math.max(0, this.nextAllowedTime - now - 20);\n      return new Response(cooldown);\n    })\n  }\n}\n\n// RateLimiterClient implements rate limiting logic on the caller's side.\nclass RateLimiterClient {\n  // The constructor takes two functions:\n  // * getLimiterStub() returns a new Durable Object stub for the RateLimiter object that manages\n  //   the limit. This may be called multiple times as needed to reconnect, if the connection is\n  //   lost.\n  // * reportError(err) is called when something goes wrong and the rate limiter is broken. It\n  //   should probably disconnect the client, so that they can reconnect and start over.\n  constructor(getLimiterStub, reportError) {\n    this.getLimiterStub = getLimiterStub;\n    this.reportError = reportError;\n\n    // Call the callback to get the initial stub.\n    this.limiter = getLimiterStub();\n\n    // When `inCooldown` is true, the rate limit is currently applied and checkLimit() will return\n    // false.\n    this.inCooldown = false;\n  }\n\n  // Call checkLimit() when a message is received to decide if it should be blocked due to the\n  // rate limit. Returns `true` if the message should be accepted, `false` to reject.\n  checkLimit() {\n    if (this.inCooldown) {\n      return false;\n    }\n    this.inCooldown = true;\n    this.callLimiter();\n    return true;\n  }\n\n  // callLimiter() is an internal method which talks to the rate limiter.\n  async callLimiter() {\n    try {\n      let response;\n      try {\n        // Currently, fetch() needs a valid URL even though it's not actually going to the\n        // internet. We may loosen this in the future to accept an arbitrary string. But for now,\n        // we have to provide a dummy URL that will be ignored at the other end anyway.\n        response = await this.limiter.fetch(\"https://dummy-url\", {method: \"POST\"});\n      } catch (err) {\n        // `fetch()` threw an exception. This is probably because the limiter has been\n        // disconnected. Stubs implement E-order semantics, meaning that calls to the same stub\n        // are delivered to the remote object in order, until the stub becomes disconnected, after\n        // which point all further calls fail. This guarantee makes a lot of complex interaction\n        // patterns easier, but it means we must be prepared for the occasional disconnect, as\n        // networks are inherently unreliable.\n        //\n        // Anyway, get a new limiter and try again. If it fails again, something else is probably\n        // wrong.\n        this.limiter = this.getLimiterStub();\n        response = await this.limiter.fetch(\"https://dummy-url\", {method: \"POST\"});\n      }\n\n      // The response indicates how long we want to pause before accepting more requests.\n      let cooldown = +(await response.text());\n      await new Promise(resolve => setTimeout(resolve, cooldown * 1000));\n\n      // Done waiting.\n      this.inCooldown = false;\n    } catch (err) {\n      this.reportError(err);\n    }\n  }\n}\n"
  },
  {
    "path": "samples/durable-objects-chat/config.capnp",
    "content": "# This is the edge chat demo found at:\n#\n#     https://github.com/cloudflare/workers-chat-demo\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\n# A constant of type `Workerd.Config` will be recognized as the top-level configuration.\nconst config :Workerd.Config = (\n  # We have one nanoservice: the chat worker.\n  services = [ (name = \"chat\", worker = .chatWorker) ],\n\n  # We export it via HTTP on port 8080.\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"chat\" ) ],\n);\n\n# For legibility we define the Worker's config as a separate constant.\nconst chatWorker :Workerd.Worker = (\n  # All Workers must declare a compatibility date, which ensures that if `workerd` is updated to\n  # a newer version with breaking changes, it will emulate the API as it existed on this date, so\n  # the Worker won't break.\n  compatibilityDate = \"2023-02-28\",\n\n  # This worker is modules-based.\n  modules = [\n    # Our code is in an ES module (JavaScript).\n    (name = \"chat.js\", esModule = embed \"chat.js\"),\n\n    # We also have an HTML file containing the client side of the app. We embed this as a text\n    # module, so that it can be served to the client.\n    (name = \"chat.html\", text = embed \"chat.html\"),\n  ],\n\n  # The Worker has two Durable Object classes, each of which needs an attached namespace.\n  # The `uniqueKey`s can be any string, and are used to generate IDs. Keep the keys secret if you\n  # don't want clients to be able to forge valid IDs -- or don't, if you don't care about that.\n  #\n  # In the example here, we've generated 32-character random hex keys, but again, the string can\n  # be anything. These were generated specifically for this demo config; we do not use these\n  # values in production.\n  durableObjectNamespaces = [\n    (className = \"ChatRoom\", uniqueKey = \"210bd0cbd803ef7883a1ee9d86cce06e\"),\n    (className = \"RateLimiter\", uniqueKey = \"b37b1c65c4291f3170033b0e9dd30ee1\"),\n  ],\n\n  # To use Durable Objects we must declare how they are stored.\n  #\n  # As of this writing, `workerd` supports in-memory-only Durable Objects -- so, not really\n  # \"durable\", as all data is lost when workerd restarts. However, this still allows us to run the\n  # chat demo for testing purposes. (We plan to add actual storage for Durable Objects eventually,\n  # but the storage system behind Cloudflare Workers is inherently tied to our network so did not\n  # make sense to release as-is.)\n  durableObjectStorage = (inMemory = void),\n\n  # We must declare bindings to allow us to call back to our own Durable Object namespaces. These\n  # show up as properties on the `env` object passed to `fetch()`.\n  bindings = [\n    (name = \"rooms\", durableObjectNamespace = \"ChatRoom\"),\n    (name = \"limiters\", durableObjectNamespace = \"RateLimiter\"),\n  ],\n);\n\n"
  },
  {
    "path": "samples/eventsource/README.md",
    "content": "# EventSource Example\n\nTo run the example on http://localhost:8080\n\n```sh\n$ ./workerd serve config.capnp\n```\n\nTo run using bazel\n\n```sh\n$ bazel run //src/workerd/server:workerd -- serve ~/cloudflare/workerd/samples/eventsource/config.capnp\n```\n"
  },
  {
    "path": "samples/eventsource/config.capnp",
    "content": "# Copyright (c) 2017-2024 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\nconst eventSourceExample :Workerd.Config = (\n\n  services = [\n    (name = \"main\", worker = .eventsource),\n    (name = \"internet\", network = (allow = [\"private\"]))\n  ],\n\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\nconst eventsource :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\")\n  ],\n  compatibilityDate = \"2024-05-31\",\n);\n"
  },
  {
    "path": "samples/eventsource/server.js",
    "content": "// Copyright (c) 2017-2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// This is a simple SSE server written for Node.js ... it sends a message every second\n// for 10 seconds, then disconnects. The worker.js file will use the EventSource API to\n// connect to this server.\n\nconst { createServer } = require('http');\n\nlet counter = 0;\n\nfunction getMessage(txt) {\n  return `data: ${txt}\\nid: ${counter++}\\n\\n`;\n}\n\nconst server = createServer((req, res) => {\n  res.writeHead(200, {\n    'Content-Type': 'text/event-stream',\n    'Cache-Control': 'no-cache',\n    'Connection': 'keep-alive'\n  });\n\n  res.write(getMessage('Hello World'));\n\n  let t = setInterval(() => {\n    res.write(getMessage('Hello World'));\n    if (counter === 10) {\n      clearInterval(t);\n      res.end();\n      counter = 0;\n    }\n  }, 1000);\n});\n\nserver.listen(8888, () => {\n  console.log('Server is running...');\n});\n"
  },
  {
    "path": "samples/eventsource/worker.js",
    "content": "// Copyright (c) 2017-2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport default {\n  async fetch(req, env) {\n    const ev = new EventSource('http://localhost:8888');\n\n    // Note that as a non-standard extension, within workers it is possible to\n    // create an EventSource from an existing ReadableStream.\n    //\n    // const ev = EventSource.from(readable);\n\n    ev.onopen = () => {\n      console.log('open!');\n    };\n\n    ev.onerror = (event) => {\n      console.log('error!', event.error);\n    };\n\n    ev.onmessage = (event) => {\n      console.log('message!', event.data, event.lastEventId);\n    };\n\n    // We'll keep the connection open and running for 20 seconds...\n    await scheduler.wait(20000);\n\n    return new Response(\"Hello World\\n\");\n  }\n};\n"
  },
  {
    "path": "samples/extensions/README.md",
    "content": "# Extensions\n\nThis directory contains comprehensive samples of using workerd extensions.\n\nThis example defines a fictional burrito shop extension in\n[burrito-shop.capnp](burrito-shop.capnp)\nand demonstrates following features:\n\n- using modules to provide new user-level api: [burrito-shop.js](burrito-shop.js) and\n  [worker.js](worker.js)\n- using internal modules to hide implementation details from the user: [kitchen.js](kitchen.js)\n\nThe sample will be extended as more functionality is implemented.\n\n## Running\n\n```\n$ bazel run //src/workerd/server:workerd -- serve $(pwd)/samples/extensions/config.capnp\n$ curl localhost:8080 -X POST -d 'veggie'\n9\n```\n\n## Demonstrated Methods\n\n### Importable Module\n\nThis method demonstrates a publicly importable module, with the initialization being handled by the worker in  [worker.js](worker.js)\n\n#### Accessibility\n\nThe module `burrito-shop:burrito-shop`, which is defined in the config [burrito-shop.capnp](burrito-shop.capnp), is an importable module.\n\nYou can import it as demonstrated in [burrito-shop.js](burrito-shop.js)\n\n```javascript\nimport { BurritoShop } from \"burrito-shop:burrito-shop\";\n```\n\n#### Definition\n\nThis import definition is demonstrated in [burrito-shop.capnp](burrito-shop.capnp).\n\n```capnp\n...\n( name = \"burrito-shop:burrito-shop\", esModule = embed \"burrito-shop.js\" )\n...\n```\n\n### Environment Variable (with internal initialization from environment variables)\n\n#### Accessibility\n\nThe module `burrito-shop:binding` is defined in the config [burrito-shop.capnp](burrito-shop.capnp) and is provided as an environment variable in standard `workerd` fashion. As we're using an esmodule it's provided as an argument to the workers entrypoint.\n\nYou can access it as shown below in [worker.js](worker.js)\n\n```javascript\nreturn new Response(env.shop.makeBurrito(burritoType).price());\n```\n\nThe initialization for this module is demonstrated in [binding.js](binding.js), the default behavior of worker uses the exported function for initialization, and is called by `workerd`.\n\n#### Definition\n\n- The environment name of `shop` is provided in [config.capnp](config.capnp) with the field `name` located in the binding definition used by the worker.\n\n```capnp\nbindings = [\n( \n    ...\n    name = \"shop\"\n    ...\n)]\n```\n\n- **Note** An environment bindings module must be marked as internal, this is demonstrated in [burrito-shop.capnp](burrito-shop.capnp)\n\n#### Using a exported function that isn't default as the entrypoint\n\nA default exported entrypoint is not required for binding as long as you define a different entrypoint with `entrypoint = \"methodname\"`, for example you can define a non-default function as the entrypoint with `entrypoint = \"makeMagicBurritoBinding\"` in [config.capnp](config.capnp).  An example is provided below on how you could change the entrypoint.\n\nIn [binding.js](binding.js)\n\n```javascript\nexport function makeMagicBurritoBinding(env) {\n    return new BurritoShop(env.recipes);\n}\n```\n\nIn [config.capnp](config.capnp)\n\n```capnp\n(\n  name = \"shop\",\n  wrapped = (\n    ...\n    entrypoint = \"makeMagicBurritoBinding\" # The new entrypoint name\n    ...\n)\n```\n"
  },
  {
    "path": "samples/extensions/binding.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { BurritoShop } from \"burrito-shop-internal:burrito-shop-impl\";\n\nfunction makeBinding(env) {\n  return new BurritoShop(env.recipes);\n}\n\nexport default makeBinding;\n"
  },
  {
    "path": "samples/extensions/burrito-shop-impl.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// burrito-shop-impl is an internal module and can't be imported by user code\n\n\n// internal modules can import each other, but users still can't access them\nimport { makeBurritoImpl } from \"burrito-shop-internal:kitchen\";\n\nexport class BurritoShop {\n  #recipes;\n\n  constructor(recipes) {\n    this.#recipes = recipes;\n  }\n\n  makeBurrito(type) {\n    if (!(type in this.#recipes)) {\n      throw new Error(`recipe not found: ${type}`);\n    }\n    return makeBurritoImpl(this.#recipes[type]);\n  }\n}\n"
  },
  {
    "path": "samples/extensions/burrito-shop.capnp",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst extension :Workerd.Extension = (\n  modules = [\n    # this module will be directly importable by the user\n    ( name = \"burrito-shop:burrito-shop\", esModule = embed \"burrito-shop.js\" ),\n    # internal modules can be imported only by public extension modules\n    ( name = \"burrito-shop-internal:burrito-shop-impl\", esModule = embed \"burrito-shop-impl.js\", internal = true ),\n    ( name = \"burrito-shop-internal:kitchen\", esModule = embed \"kitchen.js\", internal = true ),\n    # only modules marked as internal can be used for bindings\n    ( name = \"burrito-shop:binding\", esModule = embed \"binding.js\", internal = true ),\n  ]\n);\n"
  },
  {
    "path": "samples/extensions/burrito-shop.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// implementation details are not directly accessible to the user\nimport { BurritoShop } from \"burrito-shop-internal:burrito-shop-impl\";\n\nexport { BurritoShop };\n"
  },
  {
    "path": "samples/extensions/config.capnp",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\nusing BurritoShop = import \"burrito-shop.capnp\";\n\nconst helloWorldExample :Workerd.Config = (\n  services = [ (name = \"main\", worker = .helloWorld) ],\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ],\n  extensions = [ BurritoShop.extension ],\n);\n\nconst helloWorld :Workerd.Worker = (\n  modules = [ (name = \"worker\", esModule = embed \"worker.js\") ],\n  compatibilityDate = \"2022-09-16\",\n  bindings = [\n    ( name = \"shop\",\n      # wrapped bindings provide specialized API access to inner bindings.\n      # inner binding will typically include internet service binding,\n      # but could be arbitrary, including configuration and data.\n      wrapped = (\n        moduleName = \"burrito-shop:binding\",\n        innerBindings = [(\n          name = \"recipes\",\n          json = embed \"recipes.json\"\n        )],\n      ))\n  ],\n);\n"
  },
  {
    "path": "samples/extensions/kitchen.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// kitchen.js is an internal module and can't be imported by user code.\n\nconst prices = { \"rice\": 1, \"meat\": 5, \"beans\": 1, \"cheese\": 1, \"salsa\": 1, \"guacamole\": 6, };\n\nclass Burrito {\n  #recipe;\n  constructor(recipe) {\n    this.#recipe = recipe;\n  }\n\n  price() {\n    return this.#recipe.reduce((acc, val) => acc + prices[val], 0);\n  }\n};\n\nexport function makeBurritoImpl(recipe) {\n  return new Burrito(recipe);\n}\n"
  },
  {
    "path": "samples/extensions/recipes.json",
    "content": "{\n  \"meat\": [\"rice\", \"meat\", \"beans\", \"cheese\", \"salsa\"],\n  \"veggie\": [\"rice\", \"guacamole\", \"beans\", \"salsa\"]\n}\n"
  },
  {
    "path": "samples/extensions/worker.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { BurritoShop } from \"burrito-shop:burrito-shop\";\n\nexport default {\n  async fetch(req, env) {\n    const burritoType = await req.text();\n\n    if (req.headers.has(\"X-Use-Direct-Api\")) {\n      // extensions can decide to provide direct API access to users\n      const shop = new BurritoShop({\n        \"meat\": [\"rice\", \"meat\", \"beans\", \"cheese\", \"salsa\"],\n        \"veggie\": [\"rice\", \"guacamole\", \"beans\", \"salsa\"],\n      });\n      return new Response(shop.makeBurrito(burritoType).price());\n    } else {\n      // or can be used to define wrapped bindings,\n      // which is more in line with capability-based design\n      return new Response(env.shop.makeBurrito(burritoType).price());\n    }\n  }\n};\n"
  },
  {
    "path": "samples/filesystem/README.md",
    "content": "# Filesystem Example\n\nTo run the example on http://localhost:8080\n\n```sh\n$ ./workerd serve config.capnp\n```\n\nTo run using bazel\n\n```sh\n$ bazel run //src/workerd/server:workerd -- serve ~/cloudflare/workerd/samples/helloworld_esm/config.capnp\n```\n\nTo create a standalone binary that can be run:\n\n```sh\n$ ./workerd compile config.capnp > helloworld\n\n$ ./helloworld\n```\n\nTo test:\n\n```sh\n% curl http://localhost:8080\nHello World\n```\n"
  },
  {
    "path": "samples/filesystem/config.capnp",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst helloWorldExample :Workerd.Config = (\n  services = [ (name = \"main\", worker = .helloWorld) ],\n\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\nconst helloWorld :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\")\n  ],\n  compatibilityDate = \"2025-07-01\",\n  compatibilityFlags = [\n    \"nodejs_compat\",\n    \"enable_web_file_system\",\n    \"experimental\",\n  ],\n);\n"
  },
  {
    "path": "samples/filesystem/worker.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { Buffer } from 'node:buffer';\n\nconst root = await navigator.storage.getDirectory();\nconst tmp = await root.getDirectoryHandle('tmp');\nexport default {\n  async fetch(req, env) {\n\n    // Any files that are created in the tmp file directory will be\n    // automatically cleaned up by the runtime after each request.\n    // The tmp dir is specific to each request, so concurrent requests\n    // will not interfere with each other and there is no shared state\n    // between requests.\n\n    // Web File System APIs...\n    const file = await tmp.getFileHandle(\"foo\", { create: true });\n    const writable = await file.createWritable();\n    await writable.write('Hello World\\n');\n    await writable.close();\n\n    // Node.js File System APIs...\n    const { readFileSync } = await import('node:fs');\n    const data = readFileSync('/tmp/foo', 'utf8');\n\n    return new Response(data);\n  }\n};\n"
  },
  {
    "path": "samples/hello-wasm/.gitignore",
    "content": "target\nbuild\n"
  },
  {
    "path": "samples/hello-wasm/Cargo.toml",
    "content": "[package]\nname = \"hello-wasm\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[lib]\ncrate-type = [\"cdylib\", \"rlib\"]\n\n[features]\ndefault = [\"console_error_panic_hook\"]\n\n[dependencies]\ncfg-if = \"1.0\"\nworker = \"0.6.0\"\nserde_json = \"1.0.140\"\n\n# The `console_error_panic_hook` crate provides better debugging of panics by\n# logging them with `console.error`. This is great for development, but requires\n# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for\n# code size when deploying.\nconsole_error_panic_hook = { version = \"0.1.7\", optional = true }\n\n[profile.release]\n# Tell `rustc` to optimize for small code size.\nopt-level = \"s\"\n"
  },
  {
    "path": "samples/hello-wasm/README.md",
    "content": "# Hello WASM Example\n\n## Build WASM Binary\n\n```sh\n$ cargo install worker-build\n$ worker-build --release\n```\n\n## Run with `workerd`\n\nTo run the example on http://localhost:8080\n\n```sh\n$ ./workerd serve config.capnp\n```\n\nTo run using bazel\n\n```sh\n$ bazel run //src/workerd/server:workerd -- serve $(pwd)/samples/hello-wasm/config.capnp\n```\n\nTo create a standalone binary that can be run:\n\n```sh\n$ ./workerd compile config.capnp > hellowasm\n\n$ ./hellowasm\n```\n\nTo test:\n\n```sh\n% curl http://localhost:8080\nHello WASM!\n```\n"
  },
  {
    "path": "samples/hello-wasm/config.capnp",
    "content": "# Imports the base schema for workerd configuration files.\n\n# Refer to the comments in /src/workerd/server/workerd.capnp for more details.\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\n# A constant of type Workerd.Config defines the top-level configuration for an\n# instance of the workerd runtime. A single config file can contain multiple\n# Workerd.Config definitions and must have at least one.\nconst helloWasmExample :Workerd.Config = (\n\n  # Every workerd instance consists of a set of named services. A worker, for instance,\n  # is a type of service. Other types of services can include external servers, the\n  # ability to talk to a network, or accessing a disk directory. Here we create a single\n  # worker service. The configuration details for the worker are defined below.\n  services = [ (name = \"main\", worker = .helloWasm) ],\n\n  # Every configuration defines the one or more sockets on which the server will listene.\n  # Here, we create a single socket that will listen on localhost port 8080, and will\n  # dispatch to the \"main\" service that we defined above.\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\n# The definition of the actual helloWasm worker exposed using the \"main\" service.\n# In this example the worker is implemented as a single simple script (see worker.js).\n# The compatibilityDate is required. For more details on compatibility dates see:\n#   https://developers.cloudflare.com/workers/platform/compatibility-dates/\n\nconst helloWasm :Workerd.Worker = (\n  modules = [\n    ( name = \"entrypoint\", esModule = embed \"./build/worker/shim.mjs\" ),\n    ( name = \"./index.wasm\", wasm = embed \"./build/worker/index.wasm\" )\n  ],\n  compatibilityDate = \"2023-03-14\",\n);\n"
  },
  {
    "path": "samples/hello-wasm/src/lib.rs",
    "content": "use worker::*;\n\nmod utils;\n\nfn log_request(req: &Request) {\n    console_log!(\n        \"{} - [{}], located at: {:?}, within: {}\",\n        Date::now().to_string(),\n        req.path(),\n        req.cf().unwrap().coordinates().unwrap_or_default(),\n        req.cf()\n            .unwrap()\n            .region()\n            .unwrap_or_else(|| \"unknown region\".into())\n    );\n}\n\n#[event(fetch)]\npub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {\n    log_request(&req);\n\n    // Optionally, get more helpful error messages written to the console in the case of a panic.\n    utils::set_panic_hook();\n\n    // Optionally, use the Router to handle matching endpoints, use \":name\" placeholders, or \"*name\"\n    // catch-alls to match on specific patterns. Alternatively, use `Router::with_data(D)` to\n    // provide arbitrary data that will be accessible in each route via the `ctx.data()` method.\n    let router = Router::new();\n\n    // Add as many routes as your Worker needs! Each route will get a `Request` for handling HTTP\n    // functionality and a `RouteContext` which you can use to  and get route parameters and\n    // Environment bindings like KV Stores, Durable Objects, Secrets, and Variables.\n    router\n        .get(\"/\", |_, _| Response::ok(\"Hello WASM!\"))\n        .get(\"/worker-version\", |_, ctx| {\n            let version = ctx.var(\"WORKERS_RS_VERSION\")?.to_string();\n            Response::ok(version)\n        })\n        .run(req, env)\n        .await\n}\n"
  },
  {
    "path": "samples/hello-wasm/src/utils.rs",
    "content": "use cfg_if::cfg_if;\n\ncfg_if! {\n    // https://github.com/rustwasm/console_error_panic_hook#readme\n    if #[cfg(feature = \"console_error_panic_hook\")] {\n        extern crate console_error_panic_hook;\n        pub use self::console_error_panic_hook::set_once as set_panic_hook;\n    } else {\n        #[inline]\n        pub fn set_panic_hook() {}\n    }\n}\n"
  },
  {
    "path": "samples/helloworld/README.md",
    "content": "# Hello World Example\n\nTo run the example on http://localhost:8080\n\n```sh\n$ ./workerd serve config.capnp\n```\n\nTo run using bazel\n\n```sh\n$ bazel run //src/workerd/server:workerd -- serve ~/cloudflare/workerd/samples/helloworld/config.capnp\n```\n\nTo create a standalone binary that can be run:\n\n```sh\n$ ./workerd compile config.capnp > helloworld\n\n$ ./helloworld\n```\n\nTo test:\n\n```sh\n% curl http://localhost:8080\nHello World\n```\n"
  },
  {
    "path": "samples/helloworld/config.capnp",
    "content": "# Imports the base schema for workerd configuration files.\n\n# Refer to the comments in /src/workerd/server/workerd.capnp for more details.\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\n# A constant of type Workerd.Config defines the top-level configuration for an\n# instance of the workerd runtime. A single config file can contain multiple\n# Workerd.Config definitions and must have at least one.\nconst helloWorldExample :Workerd.Config = (\n\n  # Every workerd instance consists of a set of named services. A worker, for instance,\n  # is a type of service. Other types of services can include external servers, the\n  # ability to talk to a network, or accessing a disk directory. Here we create a single\n  # worker service. The configuration details for the worker are defined below.\n  services = [ (name = \"main\", worker = .helloWorld) ],\n\n  # Each configuration defines the sockets on which the server will listen.\n  # Here, we create a single socket that will listen on localhost port 8080, and will\n  # dispatch to the \"main\" service that we defined above.\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\n# The definition of the actual helloWorld worker exposed using the \"main\" service.\n# In this example the worker is implemented as a single simple script (see worker.js).\n# The compatibilityDate is required. For more details on compatibility dates see:\n#   https://developers.cloudflare.com/workers/platform/compatibility-dates/\n\nconst helloWorld :Workerd.Worker = (\n  serviceWorkerScript = embed \"worker.js\",\n  compatibilityDate = \"2023-02-28\",\n);\n"
  },
  {
    "path": "samples/helloworld/worker.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\naddEventListener('fetch', event => {\n  event.respondWith(handle(event.request));\n});\n\nasync function handle(request) {\n  return new Response(\"Hello World\\n\");\n}\n"
  },
  {
    "path": "samples/helloworld-ts/README.md",
    "content": "# Hello World Typescript\n\nWorkerd experimental feature is to transpile typescript to javascript on load time.\nThis examples demonstrates how to use it.\n\nIt is important to note that workerd does not validate types, but barely strips them away.\nThe code must be syntactically correct, though.\n"
  },
  {
    "path": "samples/helloworld-ts/config.capnp",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst helloWorldExample :Workerd.Config = (\n  services = [ (name = \"main\", worker = .helloWorld) ],\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\nconst helloWorld :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.ts\")\n  ],\n  compatibilityDate = \"2025-08-01\",\n  compatibilityFlags = [\"typescript_strip_types\"]\n);\n"
  },
  {
    "path": "samples/helloworld-ts/worker.ts",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport default {\n\tasync fetch(request, env, ctx): Promise<Response> {\n\t\treturn new Response('Hello World from Typescript!');\n\t},\n} satisfies ExportedHandler<Env>;\n"
  },
  {
    "path": "samples/helloworld_esm/README.md",
    "content": "# Hello World Example\n\nTo run the example on http://localhost:8080\n\n```sh\n$ ./workerd serve config.capnp\n```\n\nTo run using bazel\n\n```sh\n$ bazel run //src/workerd/server:workerd -- serve ~/cloudflare/workerd/samples/helloworld_esm/config.capnp\n```\n\nTo create a standalone binary that can be run:\n\n```sh\n$ ./workerd compile config.capnp > helloworld\n\n$ ./helloworld\n```\n\nTo test:\n\n```sh\n% curl http://localhost:8080\nHello World\n```\n"
  },
  {
    "path": "samples/helloworld_esm/config.capnp",
    "content": "# Imports the base schema for workerd configuration files.\n\n# Refer to the comments in /src/workerd/server/workerd.capnp for more details.\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\n# A constant of type Workerd.Config defines the top-level configuration for an\n# instance of the workerd runtime. A single config file can contain multiple\n# Workerd.Config definitions and must have at least one.\nconst helloWorldExample :Workerd.Config = (\n\n  # Every workerd instance consists of a set of named services. A worker, for instance,\n  # is a type of service. Other types of services can include external servers, the\n  # ability to talk to a network, or accessing a disk directory. Here we create a single\n  # worker service. The configuration details for the worker are defined below.\n  services = [ (name = \"main\", worker = .helloWorld) ],\n\n  # Each configuration defines the sockets on which the server will listen.\n  # Here, we create a single socket that will listen on localhost port 8080, and will\n  # dispatch to the \"main\" service that we defined above.\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\n# The definition of the actual helloWorld worker exposed using the \"main\" service.\n# In this example the worker is implemented as an ESM module (see worker.js).\n# The compatibilityDate is required. For more details on compatibility dates see:\n#   https://developers.cloudflare.com/workers/platform/compatibility-dates/\n\nconst helloWorld :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\")\n  ],\n  compatibilityDate = \"2023-02-28\",\n);\n"
  },
  {
    "path": "samples/helloworld_esm/worker.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport default {\n  async fetch(req, env) {\n    return new Response(\"Hello World\\n\");\n  }\n};\n"
  },
  {
    "path": "samples/memory-cache/README.md",
    "content": "# Memory-Cache Example\n\nTo run the example on http://localhost:8080\n\n```sh\n$ ./workerd serve --experimental config.capnp\n```\n\nTo run using bazel\n\n```sh\n$ bazel run //src/workerd/server:workerd -- serve ~/cloudflare/workerd/samples/helloworld_esm/config.capnp --experimental\n```\n"
  },
  {
    "path": "samples/memory-cache/config.capnp",
    "content": "# Imports the base schema for workerd configuration files.\n\n# Refer to the comments in /src/workerd/server/workerd.capnp for more details.\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\n# A constant of type Workerd.Config defines the top-level configuration for an\n# instance of the workerd runtime. A single config file can contain multiple\n# Workerd.Config definitions and must have at least one.\nconst helloWorldExample :Workerd.Config = (\n\n  # Every workerd instance consists of a set of named services. A worker, for instance,\n  # is a type of service. Other types of services can include external servers, the\n  # ability to talk to a network, or accessing a disk directory. Here we create a single\n  # worker service. The configuration details for the worker are defined below.\n  services = [ (name = \"main\", worker = .helloWorld) ],\n\n  # Each configuration defines the sockets on which the server will listen.\n  # Here, we create a single socket that will listen on localhost port 8080, and will\n  # dispatch to the \"main\" service that we defined above.\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\n# The definition of the actual helloWorld worker exposed using the \"main\" service.\n# In this example the worker is implemented as an ESM module (see worker.js).\n# The compatibilityDate is required. For more details on compatibility dates see:\n#   https://developers.cloudflare.com/workers/platform/compatibility-dates/\n\nconst helloWorld :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\")\n  ],\n  compatibilityDate = \"2023-02-28\",\n\n  bindings = [\n    (name = \"CACHE\", memoryCache = (\n      id = \"abc123\",\n      limits = (\n        maxKeys = 10,\n        maxValueSize = 1024,\n        maxTotalValueSize = 1024,\n      ),\n    ))\n  ],\n);\n"
  },
  {
    "path": "samples/memory-cache/worker.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport default {\n  async fetch(req, env) {\n    const cached = await env.CACHE.read(\"hello\", async (key) => {\n      return {\n        value: 'World',\n        expiration: Date.now() + 10000,\n      };\n    });\n\n    return new Response(`Hello ${cached}\\n`);\n  }\n};\n"
  },
  {
    "path": "samples/module_fallback/README.md",
    "content": "# Module Fallback Example\n\nTo run the example on http://localhost:8080\n\n```sh\n$ ./workerd serve config.capnp\n```\n\nTo run using bazel\n\n```sh\n$ bazel run //src/workerd/server:workerd -- serve ~/cloudflare/workerd/samples/module_fallback/config.capnp\n```\n\nTo create a standalone binary that can be run:\n\n```sh\n$ ./workerd compile config.capnp > helloworld\n\n$ ./helloworld\n```\n\nTo test:\n\n```sh\n% curl http://localhost:8080\nHello World\n```\n"
  },
  {
    "path": "samples/module_fallback/cjs.js",
    "content": "const assert = require('assert');\nconst vm = require('vm');\nassert(vm !== undefined);\n\nmodule.exports = {};\n"
  },
  {
    "path": "samples/module_fallback/config.capnp",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst helloWorldExample :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .helloWorld),\n  ],\n\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\nconst helloWorld :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\"),\n    (name = \"cjs\", commonJsModule = embed \"cjs.js\"),\n  ],\n  compatibilityDate = \"2023-02-28\",\n  compatibilityFlags = [\"nodejs_compat\"],\n  moduleFallback = \"localhost:8888\",\n);\n"
  },
  {
    "path": "samples/module_fallback/fallback.js",
    "content": "#!/usr/bin/env node\n\nconst { createServer } = require('http');\n\nconst server = createServer((req, res) => {\n  // The response from the fallback service must be a valid JSON\n  // serialization of a Worker::Module config.\n\n  // The x-resolve-method tells us if the module was imported or required.\n  console.log(req.headers['x-resolve-method']);\n\n  // The req.url query params tell us what we are importing\n  const url = new URL(req.url, \"http://example.org\");\n  const specifier = url.searchParams.get('specifier');\n  const referrer = url.searchParams.get('referrer');\n  console.log(specifier, referrer);\n\n  // The fallback service can tell the client to map the request\n  // specifier to another specifier using a 301 redirect, using\n  // the location header to specify the alternative specifier.\n  if (specifier == \"/foo\") {\n    console.log('Redirecting /foo to /baz');\n    res.writeHead(301, { location: '/baz' });\n    res.end();\n    return;\n  }\n\n  if (specifier == \"/bar\") {\n    res.writeHead(404);\n    res.end();\n    return;\n  }\n\n  console.log(`Returning module spec for ${specifier}`);\n  // Returning the name is optional. If it is included, then it MUST match the\n  // request specifier!\n  res.end(`{\n  \"name\": \"${specifier}\",\n  \"esModule\":\"export default 1;\"\n}`);\n});\n\nserver.listen(8888, () => {\n  console.log('ready...');\n});\n"
  },
  {
    "path": "samples/module_fallback/worker.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport * as foo from 'foo';\nimport * as baz from 'baz';\nimport * as vm from 'node:vm';\nimport { strictEqual } from 'node:assert';\nimport * as cjs from 'cjs';\n\ntry {\n  await import('bar');\n  throw new Error('bar should not have been imported');\n} catch {\n  console.log('tried to import bar which does not exist');\n};\n\nconsole.log(foo, baz, vm);\n\nexport default {\n  async fetch(req, env) {\n    strictEqual(1 + 1, 2);\n    return new Response(\"Hello World\\n\");\n  }\n};\n"
  },
  {
    "path": "samples/nodejs-compat/README.md",
    "content": "# Node.js Compat Example\n\nTo run the example on http://localhost:8080\n\n```sh\n$ ./workerd serve config.capnp\n```\n\nTo run using bazel\n\n```sh\n$ bazel run //src/workerd/server:workerd -- serve ~/cloudflare/workerd/samples/nodejs-compat/config.capnp\n```\n\nTo create a standalone binary that can be run:\n\n```sh\n$ ./workerd compile config.capnp > nodejs-compat\n\n$ ./nodejs-compat\n```\n\nTo test:\n\n```sh\n% curl http://localhost:8080\nHello World\n```\n"
  },
  {
    "path": "samples/nodejs-compat/config.capnp",
    "content": "# Imports the base schema for workerd configuration files.\n\n# Refer to the comments in /src/workerd/server/workerd.capnp for more details.\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\n# A constant of type Workerd.Config defines the top-level configuration for an\n# instance of the workerd runtime. A single config file can contain multiple\n# Workerd.Config definitions and must have at least one.\nconst helloWorldExample :Workerd.Config = (\n\n  # Every workerd instance consists of a set of named services. A worker, for instance,\n  # is a type of service. Other types of services can include external servers, the\n  # ability to talk to a network, or accessing a disk directory. Here we create a single\n  # worker service. The configuration details for the worker are defined below.\n  services = [ (name = \"main\", worker = .helloWorld) ],\n\n  # Every configuration defines the one or more sockets on which the server will listene.\n  # Here, we create a single socket that will listen on localhost port 8080, and will\n  # dispatch to the \"main\" service that we defined above.\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\n# The definition of the actual helloWorld worker exposed using the \"main\" service.\n# In this example the worker is implemented as a single simple script (see worker.js).\n# The compatibilityDate is required. For more details on compatibility dates see:\n#   https://developers.cloudflare.com/workers/platform/compatibility-dates/\n\nconst helloWorld :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\")\n  ],\n  compatibilityDate = \"2023-02-28\",\n  compatibilityFlags = [\"nodejs_compat\"]\n);\n"
  },
  {
    "path": "samples/nodejs-compat/worker.js",
    "content": "import { EventEmitter } from 'node:events';\nimport { Buffer } from 'node:buffer';\nimport {\n  ok,\n  deepStrictEqual,\n  throws,\n} from 'node:assert';\nimport {\n  callbackify,\n  promisify,\n  format,\n} from 'node:util';\n\nimport { default as path } from 'node:path';\n\nconsole.log(path.resolve('a', 'b', 'c'));\nconsole.log(path.basename('/a/b/c/d.foo'));\nconsole.log(path.extname('/a/b/c/d.foo'));\n\n// Note that the path.win32 variants of the path API are not yet implemented.\n// While workerd is capable of running on Windows, we assume that the environment\n// is POSIX-like for now.\nthrows(() => path.win32.resolve('a', 'b', 'c'), {\n  message: 'path.win32.resolve() is not implemented.'\n});\n\n// Callback function\nfunction doSomething(a, cb) {\n  setTimeout(() => cb(null, a), 1);\n}\n\n// Async function\nasync function promiseSomething(a) {\n  await scheduler.wait(1);\n  return a;\n}\n\nconst promisified = promisify(doSomething);\nconst callbackified = callbackify(promiseSomething);\n\nexport default {\n  async fetch(request) {\n    let res;\n    const promise = new Promise((a) => res = a);\n\n    // Util promisify/callbackify\n    console.log(await promisified(321));\n\n    callbackified(123, (err, val) => {\n      console.log(err, val);\n    });\n\n    // The events module...\n    const ee = new EventEmitter();\n    ee.on('foo', () => {\n\n      // The assertion module...\n      ok(true);\n      deepStrictEqual({\n        a: {\n          b: new Set([1,2,3]),\n          c: [\n            new Map([['a','b']])\n          ]\n        }\n      }, {\n        a: {\n          b: new Set([1,2,3]),\n          c: [\n            new Map([['a','b']])\n          ]\n        }\n      });\n      throws(() => {\n        // util.format\n        throw new Error(format('%s', 'boom'));\n      }, new Error('boom'));\n      // The buffer module...\n      const buffer = Buffer.concat([Buffer.from('Hello '), Buffer.from('There')], 12);\n      buffer.fill(Buffer.from('!!'), 11);\n\n      res(new Response(buffer));\n    });\n\n    setTimeout(() => ee.emit('foo'), 10);\n\n    return promise;\n  }\n};\n"
  },
  {
    "path": "samples/nodejs-compat-crypto/README.md",
    "content": "# Node.js Compat Example\n\nTo run the example on http://localhost:8080\n\n```sh\n$ ./workerd serve config.capnp\n```\n\nTo run using bazel\n\n```sh\n$ bazel run //src/workerd/server:workerd -- serve ~/cloudflare/workerd/samples/nodejs-compat/config.capnp\n```\n\nTo create a standalone binary that can be run:\n\n```sh\n$ ./workerd compile config.capnp > nodejs-compat\n\n$ ./nodejs-compat\n```\n\nTo test:\n\n```sh\n% curl http://localhost:8080\nHello World\n```\n"
  },
  {
    "path": "samples/nodejs-compat-crypto/config.capnp",
    "content": "# Imports the base schema for workerd configuration files.\n\n# Refer to the comments in /src/workerd/server/workerd.capnp for more details.\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\n# A constant of type Workerd.Config defines the top-level configuration for an\n# instance of the workerd runtime. A single config file can contain multiple\n# Workerd.Config definitions and must have at least one.\nconst helloWorldExample :Workerd.Config = (\n\n  # Every workerd instance consists of a set of named services. A worker, for instance,\n  # is a type of service. Other types of services can include external servers, the\n  # ability to talk to a network, or accessing a disk directory. Here we create a single\n  # worker service. The configuration details for the worker are defined below.\n  services = [ (name = \"main\", worker = .helloWorld) ],\n\n  # Every configuration defines the one or more sockets on which the server will listene.\n  # Here, we create a single socket that will listen on localhost port 8080, and will\n  # dispatch to the \"main\" service that we defined above.\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\n# The definition of the actual helloWorld worker exposed using the \"main\" service.\n# In this example the worker is implemented as a single simple script (see worker.js).\n# The compatibilityDate is required. For more details on compatibility dates see:\n#   https://developers.cloudflare.com/workers/platform/compatibility-dates/\n\nconst helloWorld :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\")\n  ],\n  compatibilityDate = \"2024-05-03\",\n  compatibilityFlags = [\"nodejs_compat_v2\", \"experimental\"]\n);\n"
  },
  {
    "path": "samples/nodejs-compat-crypto/worker.js",
    "content": "import {\n  randomFill,\n  randomFillSync,\n  randomBytes,\n  randomInt,\n  randomUUID,\n  scryptSync,\n} from 'node:crypto';\n\nimport { promisify } from 'node:util';\n\nexport default {\n  async fetch() {\n\n    // Random bytes, numbers, and UUIDs\n    const buf = new Uint8Array(10);\n    randomFillSync(buf);\n    console.log(buf);\n    await promisify(randomFill)(buf);\n    console.log(buf);\n    console.log(randomBytes(10));\n    console.log(await promisify(randomBytes)(10));\n    console.log(randomInt(0, 10));\n    console.log(randomInt(10));\n    console.log(await promisify(randomInt)(10));\n    console.log(randomUUID());\n\n    // Scrypt\n    const password = 'password';\n    const salt = 'salt';\n    const keylen = 64;\n    const options = {\n      N: 16384,\n      r: 8,\n      p: 1,\n      maxmem: 32 << 20,\n    };\n    console.log(scryptSync(password, salt, keylen, options));\n\n    return new Response(\"ok\");\n  }\n};\n"
  },
  {
    "path": "samples/nodejs-compat-diagnosticschannel/README.md",
    "content": "# Node.js Compat Example\n\nTo run the example on http://localhost:8080\n\n```sh\n$ ./workerd serve config.capnp\n```\n\nTo run using bazel\n\n```sh\n$ bazel run //src/workerd/server:workerd -- serve ~/cloudflare/workerd/samples/nodejs-compat/config.capnp\n```\n\nTo create a standalone binary that can be run:\n\n```sh\n$ ./workerd compile config.capnp > nodejs-compat\n\n$ ./nodejs-compat\n```\n\nTo test:\n\n```sh\n% curl http://localhost:8080\nHello World\n```\n"
  },
  {
    "path": "samples/nodejs-compat-diagnosticschannel/config.capnp",
    "content": "# Imports the base schema for workerd configuration files.\n\n# Refer to the comments in /src/workerd/server/workerd.capnp for more details.\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\n# A constant of type Workerd.Config defines the top-level configuration for an\n# instance of the workerd runtime. A single config file can contain multiple\n# Workerd.Config definitions and must have at least one.\nconst helloWorldExample :Workerd.Config = (\n\n  # Every workerd instance consists of a set of named services. A worker, for instance,\n  # is a type of service. Other types of services can include external servers, the\n  # ability to talk to a network, or accessing a disk directory. Here we create a single\n  # worker service. The configuration details for the worker are defined below.\n  services = [ (name = \"main\", worker = .helloWorld) ],\n\n  # Every configuration defines the one or more sockets on which the server will listene.\n  # Here, we create a single socket that will listen on localhost port 8080, and will\n  # dispatch to the \"main\" service that we defined above.\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\n# The definition of the actual helloWorld worker exposed using the \"main\" service.\n# In this example the worker is implemented as a single simple script (see worker.js).\n# The compatibilityDate is required. For more details on compatibility dates see:\n#   https://developers.cloudflare.com/workers/platform/compatibility-dates/\n\nconst helloWorld :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\"),\n    (name = \"library\", esModule = embed \"library.js\"),\n  ],\n  compatibilityDate = \"2023-02-28\",\n  compatibilityFlags = [\"nodejs_compat\"]\n);\n"
  },
  {
    "path": "samples/nodejs-compat-diagnosticschannel/library.js",
    "content": "import { channel } from \"node:diagnostics_channel\";\n\nconst theChannel = channel('test');\n\nexport function doSomething() {\n  theChannel.publish('Hello from worker!');\n}\n"
  },
  {
    "path": "samples/nodejs-compat-diagnosticschannel/worker.js",
    "content": "import {\n  subscribe,\n  channel,\n  tracingChannel,\n  Channel,\n} from \"node:diagnostics_channel\";\nimport { doSomething } from \"library\";\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\n// The library will send a diagnostic message when the doSomething\n// method is called. We listen for that event here. When using\n// Tail Workers, the event will also be included in the tail events.\nsubscribe('test', (message) => {\n  console.log('Diagnostic message received:', message);\n});\n\nconst als1 = new AsyncLocalStorage();\nconst als2 = new AsyncLocalStorage();\nconst c = channel('bar');\nc.bindStore(als1);\nc.bindStore(als2, (data) => {return { data }});\n\nconst tc = tracingChannel('foo');\ntc.start.bindStore(als1);\ntc.start.subscribe(() => console.log('start', als1.getStore()));\ntc.end.subscribe(() => console.log('end'));\ntc.asyncStart.subscribe(() => console.log('async start'));\ntc.asyncEnd.subscribe(() => console.log('async end'));\n\nexport default {\n  async fetch(request) {\n    doSomething();\n\n    // prettier-ignore\n    console.log(c.runStores(1, (...args) => {\n      console.log(this, ...args, als1.getStore(), als2.getStore());\n      return 1;\n    }, {}, 1, 2, 3));\n\n    await tc.tracePromise(async function() {\n      console.log('....', this);\n    }, {a:1});\n\n    return new Response(\"ok\");\n  }\n};\n"
  },
  {
    "path": "samples/nodejs-compat-fs/README.md",
    "content": "# Node.js 'node:fs' Compat Example\n\nTo run the example on http://localhost:8080\n\n```sh\n$ ./workerd serve config.capnp\n```\n\nTo run using bazel\n\n```sh\n$ bazel run //src/workerd/server:workerd -- serve ~/cloudflare/workerd/samples/nodejs-compat/config.capnp\n```\n\nTo create a standalone binary that can be run:\n\n```sh\n$ ./workerd compile config.capnp > nodejs-compat\n\n$ ./nodejs-compat\n```\n\nTo test:\n\n```sh\n% curl http://localhost:8080\nHello World\n```\n"
  },
  {
    "path": "samples/nodejs-compat-fs/config.capnp",
    "content": "# Imports the base schema for workerd configuration files.\n\n# Refer to the comments in /src/workerd/server/workerd.capnp for more details.\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\n# A constant of type Workerd.Config defines the top-level configuration for an\n# instance of the workerd runtime. A single config file can contain multiple\n# Workerd.Config definitions and must have at least one.\nconst helloWorldExample :Workerd.Config = (\n\n  # Every workerd instance consists of a set of named services. A worker, for instance,\n  # is a type of service. Other types of services can include external servers, the\n  # ability to talk to a network, or accessing a disk directory. Here we create a single\n  # worker service. The configuration details for the worker are defined below.\n  services = [ (name = \"main\", worker = .helloWorld) ],\n\n  # Every configuration defines the one or more sockets on which the server will listene.\n  # Here, we create a single socket that will listen on localhost port 8080, and will\n  # dispatch to the \"main\" service that we defined above.\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\n# The definition of the actual helloWorld worker exposed using the \"main\" service.\n# In this example the worker is implemented as a single simple script (see worker.js).\n# The compatibilityDate is required. For more details on compatibility dates see:\n#   https://developers.cloudflare.com/workers/platform/compatibility-dates/\n\nconst helloWorld :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\"),\n    (name = \"config\", text = \"config file\"),\n  ],\n  compatibilityDate = \"2025-07-01\",\n  compatibilityFlags = [\n    \"nodejs_compat\",\n    \"enable_nodejs_fs_module\",\n  ]\n);\n"
  },
  {
    "path": "samples/nodejs-compat-fs/worker.js",
    "content": "import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { ok, strictEqual } from 'node:assert';\n\n// File system operations at the top level scope are allowed\nconst config = readFileSync('/bundle/config', 'utf8');\nstrictEqual(config, 'config file');\n\nexport default {\n  async fetch(request) {\n    // All files in the /tmp directory are always per-request.\n    // When the request ends, the files are deleted.\n    ok(!existsSync('/tmp/hello.txt'));\n    writeFileSync('/tmp/hello.txt', 'Hello, World!', 'utf8');\n    return new Response(readFileSync('/tmp/hello.txt'));\n  }\n};\n"
  },
  {
    "path": "samples/nodejs-compat-fs-graceful/README.md",
    "content": "# Node.js 'node:fs' + `graceful-fs` Compat Example\n\nDemonstrates the use of the `node:fs` built-in and the use of the `graceful-fs`\nmodule from npmjs being used in a worker without the need for any polyfills or\nmodifications to `graceful-fs`.\n\nTo run the example on http://localhost:8080\n\n```sh\n$ ./workerd serve config.capnp\n```\n\nTo run using bazel\n\n```sh\n$ bazel run //src/workerd/server:workerd -- serve ~/cloudflare/workerd/samples/nodejs-compat/config.capnp\n```\n\nTo create a standalone binary that can be run:\n\n```sh\n$ ./workerd compile config.capnp > nodejs-compat\n\n$ ./nodejs-compat\n```\n\nTo test:\n\n```sh\n% curl http://localhost:8080\nHello World\n```\n"
  },
  {
    "path": "samples/nodejs-compat-fs-graceful/config.capnp",
    "content": "# Imports the base schema for workerd configuration files.\n\n# Refer to the comments in /src/workerd/server/workerd.capnp for more details.\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\n# A constant of type Workerd.Config defines the top-level configuration for an\n# instance of the workerd runtime. A single config file can contain multiple\n# Workerd.Config definitions and must have at least one.\nconst helloWorldExample :Workerd.Config = (\n\n  # Every workerd instance consists of a set of named services. A worker, for instance,\n  # is a type of service. Other types of services can include external servers, the\n  # ability to talk to a network, or accessing a disk directory. Here we create a single\n  # worker service. The configuration details for the worker are defined below.\n  services = [ (name = \"main\", worker = .helloWorld) ],\n\n  # Every configuration defines the one or more sockets on which the server will listene.\n  # Here, we create a single socket that will listen on localhost port 8080, and will\n  # dispatch to the \"main\" service that we defined above.\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\n# The definition of the actual helloWorld worker exposed using the \"main\" service.\n# In this example the worker is implemented as a single simple script (see worker.js).\n# The compatibilityDate is required. For more details on compatibility dates see:\n#   https://developers.cloudflare.com/workers/platform/compatibility-dates/\n\nconst helloWorld :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\"),\n    (name = \"graceful-fs\", commonJsModule = embed \"graceful-fs/graceful-fs.js\"),\n    (name = \"clone.js\", commonJsModule = embed \"graceful-fs/clone.js\"),\n    (name = \"legacy-streams.js\", commonJsModule = embed \"graceful-fs/legacy-streams.js\"),\n    (name = \"polyfills.js\", commonJsModule = embed \"graceful-fs/polyfills.js\"),\n  ],\n  compatibilityDate = \"2025-07-01\",\n  compatibilityFlags = [\n    \"nodejs_compat\",\n    \"enable_nodejs_process_v2\",\n    \"enable_nodejs_fs_module\",\n  ]\n);\n"
  },
  {
    "path": "samples/nodejs-compat-fs-graceful/graceful-fs/LICENSE",
    "content": "The ISC License\n\nCopyright (c) 2011-2022 Isaac Z. Schlueter, Ben Noordhuis, and Contributors\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR\nIN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n"
  },
  {
    "path": "samples/nodejs-compat-fs-graceful/graceful-fs/README.md",
    "content": "# graceful-fs\n\ngraceful-fs functions as a drop-in replacement for the fs module,\nmaking various improvements.\n\nThe improvements are meant to normalize behavior across different\nplatforms and environments, and to make filesystem access more\nresilient to errors.\n\n## Improvements over [fs module](https://nodejs.org/api/fs.html)\n\n* Queues up `open` and `readdir` calls, and retries them once\n  something closes if there is an EMFILE error from too many file\n  descriptors.\n* fixes `lchmod` for Node versions prior to 0.6.2.\n* implements `fs.lutimes` if possible. Otherwise it becomes a noop.\n* ignores `EINVAL` and `EPERM` errors in `chown`, `fchown` or\n  `lchown` if the user isn't root.\n* makes `lchmod` and `lchown` become noops, if not available.\n* retries reading a file if `read` results in EAGAIN error.\n\nOn Windows, it retries renaming a file for up to one second if `EACCESS`\nor `EPERM` error occurs, likely because antivirus software has locked\nthe directory.\n\n## USAGE\n\n```javascript\n// use just like fs\nvar fs = require('graceful-fs')\n\n// now go and do stuff with it...\nfs.readFile('some-file-or-whatever', (err, data) => {\n  // Do stuff here.\n})\n```\n\n## Sync methods\n\nThis module cannot intercept or handle `EMFILE` or `ENFILE` errors from sync\nmethods.  If you use sync methods which open file descriptors then you are\nresponsible for dealing with any errors.\n\nThis is a known limitation, not a bug.\n\n## Global Patching\n\nIf you want to patch the global fs module (or any other fs-like\nmodule) you can do this:\n\n```javascript\n// Make sure to read the caveat below.\nvar realFs = require('fs')\nvar gracefulFs = require('graceful-fs')\ngracefulFs.gracefulify(realFs)\n```\n\nThis should only ever be done at the top-level application layer, in\norder to delay on EMFILE errors from any fs-using dependencies.  You\nshould **not** do this in a library, because it can cause unexpected\ndelays in other parts of the program.\n\n## Changes\n\nThis module is fairly stable at this point, and used by a lot of\nthings.  That being said, because it implements a subtle behavior\nchange in a core part of the node API, even modest changes can be\nextremely breaking, and the versioning is thus biased towards\nbumping the major when in doubt.\n\nThe main change between major versions has been switching between\nproviding a fully-patched `fs` module vs monkey-patching the node core\nbuiltin, and the approach by which a non-monkey-patched `fs` was\ncreated.\n\nThe goal is to trade `EMFILE` errors for slower fs operations.  So, if\nyou try to open a zillion files, rather than crashing, `open`\noperations will be queued up and wait for something else to `close`.\n\nThere are advantages to each approach.  Monkey-patching the fs means\nthat no `EMFILE` errors can possibly occur anywhere in your\napplication, because everything is using the same core `fs` module,\nwhich is patched.  However, it can also obviously cause undesirable\nside-effects, especially if the module is loaded multiple times.\n\nImplementing a separate-but-identical patched `fs` module is more\nsurgical (and doesn't run the risk of patching multiple times), but\nalso imposes the challenge of keeping in sync with the core module.\n\nThe current approach loads the `fs` module, and then creates a\nlookalike object that has all the same methods, except a few that are\npatched.  It is safe to use in all versions of Node from 0.8 through\n7.0.\n\n### v4\n\n* Do not monkey-patch the fs module.  This module may now be used as a\n  drop-in dep, and users can opt into monkey-patching the fs builtin\n  if their app requires it.\n\n### v3\n\n* Monkey-patch fs, because the eval approach no longer works on recent\n  node.\n* fixed possible type-error throw if rename fails on windows\n* verify that we *never* get EMFILE errors\n* Ignore ENOSYS from chmod/chown\n* clarify that graceful-fs must be used as a drop-in\n\n### v2.1.0\n\n* Use eval rather than monkey-patching fs.\n* readdir: Always sort the results\n* win32: requeue a file if error has an OK status\n\n### v2.0\n\n* A return to monkey patching\n* wrap process.cwd\n\n### v1.1\n\n* wrap readFile\n* Wrap fs.writeFile.\n* readdir protection\n* Don't clobber the fs builtin\n* Handle fs.read EAGAIN errors by trying again\n* Expose the curOpen counter\n* No-op lchown/lchmod if not implemented\n* fs.rename patch only for win32\n* Patch fs.rename to handle AV software on Windows\n* Close #4 Chown should not fail on einval or eperm if non-root\n* Fix isaacs/fstream#1 Only wrap fs one time\n* Fix #3 Start at 1024 max files, then back off on EMFILE\n* lutimes that doens't blow up on Linux\n* A full on-rewrite using a queue instead of just swallowing the EMFILE error\n* Wrap Read/Write streams as well\n\n### 1.0\n\n* Update engines for node 0.6\n* Be lstat-graceful on Windows\n* first\n"
  },
  {
    "path": "samples/nodejs-compat-fs-graceful/graceful-fs/clone.js",
    "content": "'use strict'\n\nmodule.exports = clone\n\nvar getPrototypeOf = Object.getPrototypeOf || function (obj) {\n  return obj.__proto__\n}\n\nfunction clone (obj) {\n  if (obj === null || typeof obj !== 'object')\n    return obj\n\n  if (obj instanceof Object)\n    var copy = { __proto__: getPrototypeOf(obj) }\n  else\n    var copy = Object.create(null)\n\n  Object.getOwnPropertyNames(obj).forEach(function (key) {\n    Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key))\n  })\n\n  return copy\n}\n"
  },
  {
    "path": "samples/nodejs-compat-fs-graceful/graceful-fs/graceful-fs.js",
    "content": "var fs = require('fs')\nvar polyfills = require('./polyfills.js')\nvar legacy = require('./legacy-streams.js')\nvar clone = require('./clone.js')\n\nvar util = require('util')\n\n/* istanbul ignore next - node 0.x polyfill */\nvar gracefulQueue\nvar previousSymbol\n\n/* istanbul ignore else - node 0.x polyfill */\nif (typeof Symbol === 'function' && typeof Symbol.for === 'function') {\n  gracefulQueue = Symbol.for('graceful-fs.queue')\n  // This is used in testing by future versions\n  previousSymbol = Symbol.for('graceful-fs.previous')\n} else {\n  gracefulQueue = '___graceful-fs.queue'\n  previousSymbol = '___graceful-fs.previous'\n}\n\nfunction noop () {}\n\nfunction publishQueue(context, queue) {\n  Object.defineProperty(context, gracefulQueue, {\n    get: function() {\n      return queue\n    }\n  })\n}\n\nvar debug = noop\nif (util.debuglog)\n  debug = util.debuglog('gfs4')\nelse if (/\\bgfs4\\b/i.test(process.env.NODE_DEBUG || ''))\n  debug = function() {\n    var m = util.format.apply(util, arguments)\n    m = 'GFS4: ' + m.split(/\\n/).join('\\nGFS4: ')\n    console.error(m)\n  }\n\n// Once time initialization\nif (!fs[gracefulQueue]) {\n  // This queue can be shared by multiple loaded instances\n  var queue = global[gracefulQueue] || []\n  publishQueue(fs, queue)\n\n  // Patch fs.close/closeSync to shared queue version, because we need\n  // to retry() whenever a close happens *anywhere* in the program.\n  // This is essential when multiple graceful-fs instances are\n  // in play at the same time.\n  fs.close = (function (fs$close) {\n    function close (fd, cb) {\n      return fs$close.call(fs, fd, function (err) {\n        // This function uses the graceful-fs shared queue\n        if (!err) {\n          resetQueue()\n        }\n\n        if (typeof cb === 'function')\n          cb.apply(this, arguments)\n      })\n    }\n\n    Object.defineProperty(close, previousSymbol, {\n      value: fs$close\n    })\n    return close\n  })(fs.close)\n\n  fs.closeSync = (function (fs$closeSync) {\n    function closeSync (fd) {\n      // This function uses the graceful-fs shared queue\n      fs$closeSync.apply(fs, arguments)\n      resetQueue()\n    }\n\n    Object.defineProperty(closeSync, previousSymbol, {\n      value: fs$closeSync\n    })\n    return closeSync\n  })(fs.closeSync)\n\n  if (/\\bgfs4\\b/i.test(process.env.NODE_DEBUG || '')) {\n    process.on('exit', function() {\n      debug(fs[gracefulQueue])\n      require('assert').equal(fs[gracefulQueue].length, 0)\n    })\n  }\n}\n\nif (!global[gracefulQueue]) {\n  publishQueue(global, fs[gracefulQueue]);\n}\n\nmodule.exports = patch(clone(fs))\nif (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) {\n    module.exports = patch(fs)\n    fs.__patched = true;\n}\n\nfunction patch (fs) {\n  // Everything that references the open() function needs to be in here\n  polyfills(fs)\n  fs.gracefulify = patch\n\n  fs.createReadStream = createReadStream\n  fs.createWriteStream = createWriteStream\n  var fs$readFile = fs.readFile\n  fs.readFile = readFile\n  function readFile (path, options, cb) {\n    if (typeof options === 'function')\n      cb = options, options = null\n\n    return go$readFile(path, options, cb)\n\n    function go$readFile (path, options, cb, startTime) {\n      return fs$readFile(path, options, function (err) {\n        if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))\n          enqueue([go$readFile, [path, options, cb], err, startTime || Date.now(), Date.now()])\n        else {\n          if (typeof cb === 'function')\n            cb.apply(this, arguments)\n        }\n      })\n    }\n  }\n\n  var fs$writeFile = fs.writeFile\n  fs.writeFile = writeFile\n  function writeFile (path, data, options, cb) {\n    if (typeof options === 'function')\n      cb = options, options = null\n\n    return go$writeFile(path, data, options, cb)\n\n    function go$writeFile (path, data, options, cb, startTime) {\n      return fs$writeFile(path, data, options, function (err) {\n        if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))\n          enqueue([go$writeFile, [path, data, options, cb], err, startTime || Date.now(), Date.now()])\n        else {\n          if (typeof cb === 'function')\n            cb.apply(this, arguments)\n        }\n      })\n    }\n  }\n\n  var fs$appendFile = fs.appendFile\n  if (fs$appendFile)\n    fs.appendFile = appendFile\n  function appendFile (path, data, options, cb) {\n    if (typeof options === 'function')\n      cb = options, options = null\n\n    return go$appendFile(path, data, options, cb)\n\n    function go$appendFile (path, data, options, cb, startTime) {\n      return fs$appendFile(path, data, options, function (err) {\n        if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))\n          enqueue([go$appendFile, [path, data, options, cb], err, startTime || Date.now(), Date.now()])\n        else {\n          if (typeof cb === 'function')\n            cb.apply(this, arguments)\n        }\n      })\n    }\n  }\n\n  var fs$copyFile = fs.copyFile\n  if (fs$copyFile)\n    fs.copyFile = copyFile\n  function copyFile (src, dest, flags, cb) {\n    if (typeof flags === 'function') {\n      cb = flags\n      flags = 0\n    }\n    return go$copyFile(src, dest, flags, cb)\n\n    function go$copyFile (src, dest, flags, cb, startTime) {\n      return fs$copyFile(src, dest, flags, function (err) {\n        if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))\n          enqueue([go$copyFile, [src, dest, flags, cb], err, startTime || Date.now(), Date.now()])\n        else {\n          if (typeof cb === 'function')\n            cb.apply(this, arguments)\n        }\n      })\n    }\n  }\n\n  var fs$readdir = fs.readdir\n  fs.readdir = readdir\n  var noReaddirOptionVersions = /^v[0-5]\\./\n  function readdir (path, options, cb) {\n    if (typeof options === 'function')\n      cb = options, options = null\n\n    var go$readdir = noReaddirOptionVersions.test(process.version)\n      ? function go$readdir (path, options, cb, startTime) {\n        return fs$readdir(path, fs$readdirCallback(\n          path, options, cb, startTime\n        ))\n      }\n      : function go$readdir (path, options, cb, startTime) {\n        return fs$readdir(path, options, fs$readdirCallback(\n          path, options, cb, startTime\n        ))\n      }\n\n    return go$readdir(path, options, cb)\n\n    function fs$readdirCallback (path, options, cb, startTime) {\n      return function (err, files) {\n        if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))\n          enqueue([\n            go$readdir,\n            [path, options, cb],\n            err,\n            startTime || Date.now(),\n            Date.now()\n          ])\n        else {\n          if (files && files.sort)\n            files.sort()\n\n          if (typeof cb === 'function')\n            cb.call(this, err, files)\n        }\n      }\n    }\n  }\n\n  if (process.version.substr(0, 4) === 'v0.8') {\n    var legStreams = legacy(fs)\n    ReadStream = legStreams.ReadStream\n    WriteStream = legStreams.WriteStream\n  }\n\n  var fs$ReadStream = fs.ReadStream\n  if (fs$ReadStream) {\n    ReadStream.prototype = Object.create(fs$ReadStream.prototype)\n    ReadStream.prototype.open = ReadStream$open\n  }\n\n  var fs$WriteStream = fs.WriteStream\n  if (fs$WriteStream) {\n    WriteStream.prototype = Object.create(fs$WriteStream.prototype)\n    WriteStream.prototype.open = WriteStream$open\n  }\n\n  Object.defineProperty(fs, 'ReadStream', {\n    get: function () {\n      return ReadStream\n    },\n    set: function (val) {\n      ReadStream = val\n    },\n    enumerable: true,\n    configurable: true\n  })\n  Object.defineProperty(fs, 'WriteStream', {\n    get: function () {\n      return WriteStream\n    },\n    set: function (val) {\n      WriteStream = val\n    },\n    enumerable: true,\n    configurable: true\n  })\n\n  // legacy names\n  var FileReadStream = ReadStream\n  Object.defineProperty(fs, 'FileReadStream', {\n    get: function () {\n      return FileReadStream\n    },\n    set: function (val) {\n      FileReadStream = val\n    },\n    enumerable: true,\n    configurable: true\n  })\n  var FileWriteStream = WriteStream\n  Object.defineProperty(fs, 'FileWriteStream', {\n    get: function () {\n      return FileWriteStream\n    },\n    set: function (val) {\n      FileWriteStream = val\n    },\n    enumerable: true,\n    configurable: true\n  })\n\n  function ReadStream (path, options) {\n    if (this instanceof ReadStream)\n      return fs$ReadStream.apply(this, arguments), this\n    else\n      return ReadStream.apply(Object.create(ReadStream.prototype), arguments)\n  }\n\n  function ReadStream$open () {\n    var that = this\n    open(that.path, that.flags, that.mode, function (err, fd) {\n      if (err) {\n        if (that.autoClose)\n          that.destroy()\n\n        that.emit('error', err)\n      } else {\n        that.fd = fd\n        that.emit('open', fd)\n        that.read()\n      }\n    })\n  }\n\n  function WriteStream (path, options) {\n    if (this instanceof WriteStream)\n      return fs$WriteStream.apply(this, arguments), this\n    else\n      return WriteStream.apply(Object.create(WriteStream.prototype), arguments)\n  }\n\n  function WriteStream$open () {\n    var that = this\n    open(that.path, that.flags, that.mode, function (err, fd) {\n      if (err) {\n        that.destroy()\n        that.emit('error', err)\n      } else {\n        that.fd = fd\n        that.emit('open', fd)\n      }\n    })\n  }\n\n  function createReadStream (path, options) {\n    return new fs.ReadStream(path, options)\n  }\n\n  function createWriteStream (path, options) {\n    return new fs.WriteStream(path, options)\n  }\n\n  var fs$open = fs.open\n  fs.open = open\n  function open (path, flags, mode, cb) {\n    if (typeof mode === 'function')\n      cb = mode, mode = null\n\n    return go$open(path, flags, mode, cb)\n\n    function go$open (path, flags, mode, cb, startTime) {\n      return fs$open(path, flags, mode, function (err, fd) {\n        if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))\n          enqueue([go$open, [path, flags, mode, cb], err, startTime || Date.now(), Date.now()])\n        else {\n          if (typeof cb === 'function')\n            cb.apply(this, arguments)\n        }\n      })\n    }\n  }\n\n  return fs\n}\n\nfunction enqueue (elem) {\n  debug('ENQUEUE', elem[0].name, elem[1])\n  fs[gracefulQueue].push(elem)\n  retry()\n}\n\n// keep track of the timeout between retry() calls\nvar retryTimer\n\n// reset the startTime and lastTime to now\n// this resets the start of the 60 second overall timeout as well as the\n// delay between attempts so that we'll retry these jobs sooner\nfunction resetQueue () {\n  var now = Date.now()\n  for (var i = 0; i < fs[gracefulQueue].length; ++i) {\n    // entries that are only a length of 2 are from an older version, don't\n    // bother modifying those since they'll be retried anyway.\n    if (fs[gracefulQueue][i].length > 2) {\n      fs[gracefulQueue][i][3] = now // startTime\n      fs[gracefulQueue][i][4] = now // lastTime\n    }\n  }\n  // call retry to make sure we're actively processing the queue\n  retry()\n}\n\nfunction retry () {\n  // clear the timer and remove it to help prevent unintended concurrency\n  clearTimeout(retryTimer)\n  retryTimer = undefined\n\n  if (fs[gracefulQueue].length === 0)\n    return\n\n  var elem = fs[gracefulQueue].shift()\n  var fn = elem[0]\n  var args = elem[1]\n  // these items may be unset if they were added by an older graceful-fs\n  var err = elem[2]\n  var startTime = elem[3]\n  var lastTime = elem[4]\n\n  // if we don't have a startTime we have no way of knowing if we've waited\n  // long enough, so go ahead and retry this item now\n  if (startTime === undefined) {\n    debug('RETRY', fn.name, args)\n    fn.apply(null, args)\n  } else if (Date.now() - startTime >= 60000) {\n    // it's been more than 60 seconds total, bail now\n    debug('TIMEOUT', fn.name, args)\n    var cb = args.pop()\n    if (typeof cb === 'function')\n      cb.call(null, err)\n  } else {\n    // the amount of time between the last attempt and right now\n    var sinceAttempt = Date.now() - lastTime\n    // the amount of time between when we first tried, and when we last tried\n    // rounded up to at least 1\n    var sinceStart = Math.max(lastTime - startTime, 1)\n    // backoff. wait longer than the total time we've been retrying, but only\n    // up to a maximum of 100ms\n    var desiredDelay = Math.min(sinceStart * 1.2, 100)\n    // it's been long enough since the last retry, do it again\n    if (sinceAttempt >= desiredDelay) {\n      debug('RETRY', fn.name, args)\n      fn.apply(null, args.concat([startTime]))\n    } else {\n      // if we can't do this job yet, push it to the end of the queue\n      // and let the next iteration check again\n      fs[gracefulQueue].push(elem)\n    }\n  }\n\n  // schedule our next run if one isn't already scheduled\n  if (retryTimer === undefined) {\n    retryTimer = setTimeout(retry, 0)\n  }\n}\n"
  },
  {
    "path": "samples/nodejs-compat-fs-graceful/graceful-fs/legacy-streams.js",
    "content": "var Stream = require('stream').Stream\n\nmodule.exports = legacy\n\nfunction legacy (fs) {\n  return {\n    ReadStream: ReadStream,\n    WriteStream: WriteStream\n  }\n\n  function ReadStream (path, options) {\n    if (!(this instanceof ReadStream)) return new ReadStream(path, options);\n\n    Stream.call(this);\n\n    var self = this;\n\n    this.path = path;\n    this.fd = null;\n    this.readable = true;\n    this.paused = false;\n\n    this.flags = 'r';\n    this.mode = 438; /*=0666*/\n    this.bufferSize = 64 * 1024;\n\n    options = options || {};\n\n    // Mixin options into this\n    var keys = Object.keys(options);\n    for (var index = 0, length = keys.length; index < length; index++) {\n      var key = keys[index];\n      this[key] = options[key];\n    }\n\n    if (this.encoding) this.setEncoding(this.encoding);\n\n    if (this.start !== undefined) {\n      if ('number' !== typeof this.start) {\n        throw TypeError('start must be a Number');\n      }\n      if (this.end === undefined) {\n        this.end = Infinity;\n      } else if ('number' !== typeof this.end) {\n        throw TypeError('end must be a Number');\n      }\n\n      if (this.start > this.end) {\n        throw new Error('start must be <= end');\n      }\n\n      this.pos = this.start;\n    }\n\n    if (this.fd !== null) {\n      process.nextTick(function() {\n        self._read();\n      });\n      return;\n    }\n\n    fs.open(this.path, this.flags, this.mode, function (err, fd) {\n      if (err) {\n        self.emit('error', err);\n        self.readable = false;\n        return;\n      }\n\n      self.fd = fd;\n      self.emit('open', fd);\n      self._read();\n    })\n  }\n\n  function WriteStream (path, options) {\n    if (!(this instanceof WriteStream)) return new WriteStream(path, options);\n\n    Stream.call(this);\n\n    this.path = path;\n    this.fd = null;\n    this.writable = true;\n\n    this.flags = 'w';\n    this.encoding = 'binary';\n    this.mode = 438; /*=0666*/\n    this.bytesWritten = 0;\n\n    options = options || {};\n\n    // Mixin options into this\n    var keys = Object.keys(options);\n    for (var index = 0, length = keys.length; index < length; index++) {\n      var key = keys[index];\n      this[key] = options[key];\n    }\n\n    if (this.start !== undefined) {\n      if ('number' !== typeof this.start) {\n        throw TypeError('start must be a Number');\n      }\n      if (this.start < 0) {\n        throw new Error('start must be >= zero');\n      }\n\n      this.pos = this.start;\n    }\n\n    this.busy = false;\n    this._queue = [];\n\n    if (this.fd === null) {\n      this._open = fs.open;\n      this._queue.push([this._open, this.path, this.flags, this.mode, undefined]);\n      this.flush();\n    }\n  }\n}\n"
  },
  {
    "path": "samples/nodejs-compat-fs-graceful/graceful-fs/package.json",
    "content": "{\n  \"name\": \"graceful-fs\",\n  \"description\": \"A drop-in replacement for fs, making various improvements.\",\n  \"version\": \"4.2.11\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/isaacs/node-graceful-fs\"\n  },\n  \"main\": \"graceful-fs.js\",\n  \"directories\": {\n    \"test\": \"test\"\n  },\n  \"scripts\": {\n    \"preversion\": \"npm test\",\n    \"postversion\": \"npm publish\",\n    \"postpublish\": \"git push origin --follow-tags\",\n    \"test\": \"nyc --silent node test.js | tap -c -\",\n    \"posttest\": \"nyc report\"\n  },\n  \"keywords\": [\n    \"fs\",\n    \"module\",\n    \"reading\",\n    \"retry\",\n    \"retries\",\n    \"queue\",\n    \"error\",\n    \"errors\",\n    \"handling\",\n    \"EMFILE\",\n    \"EAGAIN\",\n    \"EINVAL\",\n    \"EPERM\",\n    \"EACCESS\"\n  ],\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"import-fresh\": \"^2.0.0\",\n    \"mkdirp\": \"^0.5.0\",\n    \"rimraf\": \"^2.2.8\",\n    \"tap\": \"^16.3.4\"\n  },\n  \"files\": [\n    \"fs.js\",\n    \"graceful-fs.js\",\n    \"legacy-streams.js\",\n    \"polyfills.js\",\n    \"clone.js\"\n  ],\n  \"tap\": {\n    \"reporter\": \"classic\"\n  }\n}\n"
  },
  {
    "path": "samples/nodejs-compat-fs-graceful/graceful-fs/polyfills.js",
    "content": "var constants = require('constants')\n\nvar origCwd = process.cwd\nvar cwd = null\n\nvar platform = process.env.GRACEFUL_FS_PLATFORM || process.platform\n\nprocess.cwd = function() {\n  if (!cwd)\n    cwd = origCwd.call(process)\n  return cwd\n}\ntry {\n  process.cwd()\n} catch (er) {}\n\n// This check is needed until node.js 12 is required\nif (typeof process.chdir === 'function') {\n  var chdir = process.chdir\n  process.chdir = function (d) {\n    cwd = null\n    chdir.call(process, d)\n  }\n  if (Object.setPrototypeOf) Object.setPrototypeOf(process.chdir, chdir)\n}\n\nmodule.exports = patch\n\nfunction patch (fs) {\n  // (re-)implement some things that are known busted or missing.\n\n  // lchmod, broken prior to 0.6.2\n  // back-port the fix here.\n  if (constants.hasOwnProperty('O_SYMLINK') &&\n      process.version.match(/^v0\\.6\\.[0-2]|^v0\\.5\\./)) {\n    patchLchmod(fs)\n  }\n\n  // lutimes implementation, or no-op\n  if (!fs.lutimes) {\n    patchLutimes(fs)\n  }\n\n  // https://github.com/isaacs/node-graceful-fs/issues/4\n  // Chown should not fail on einval or eperm if non-root.\n  // It should not fail on enosys ever, as this just indicates\n  // that a fs doesn't support the intended operation.\n\n  fs.chown = chownFix(fs.chown)\n  fs.fchown = chownFix(fs.fchown)\n  fs.lchown = chownFix(fs.lchown)\n\n  fs.chmod = chmodFix(fs.chmod)\n  fs.fchmod = chmodFix(fs.fchmod)\n  fs.lchmod = chmodFix(fs.lchmod)\n\n  fs.chownSync = chownFixSync(fs.chownSync)\n  fs.fchownSync = chownFixSync(fs.fchownSync)\n  fs.lchownSync = chownFixSync(fs.lchownSync)\n\n  fs.chmodSync = chmodFixSync(fs.chmodSync)\n  fs.fchmodSync = chmodFixSync(fs.fchmodSync)\n  fs.lchmodSync = chmodFixSync(fs.lchmodSync)\n\n  fs.stat = statFix(fs.stat)\n  fs.fstat = statFix(fs.fstat)\n  fs.lstat = statFix(fs.lstat)\n\n  fs.statSync = statFixSync(fs.statSync)\n  fs.fstatSync = statFixSync(fs.fstatSync)\n  fs.lstatSync = statFixSync(fs.lstatSync)\n\n  // if lchmod/lchown do not exist, then make them no-ops\n  if (fs.chmod && !fs.lchmod) {\n    fs.lchmod = function (path, mode, cb) {\n      if (cb) process.nextTick(cb)\n    }\n    fs.lchmodSync = function () {}\n  }\n  if (fs.chown && !fs.lchown) {\n    fs.lchown = function (path, uid, gid, cb) {\n      if (cb) process.nextTick(cb)\n    }\n    fs.lchownSync = function () {}\n  }\n\n  // on Windows, A/V software can lock the directory, causing this\n  // to fail with an EACCES or EPERM if the directory contains newly\n  // created files.  Try again on failure, for up to 60 seconds.\n\n  // Set the timeout this long because some Windows Anti-Virus, such as Parity\n  // bit9, may lock files for up to a minute, causing npm package install\n  // failures. Also, take care to yield the scheduler. Windows scheduling gives\n  // CPU to a busy looping process, which can cause the program causing the lock\n  // contention to be starved of CPU by node, so the contention doesn't resolve.\n  if (platform === \"win32\") {\n    fs.rename = typeof fs.rename !== 'function' ? fs.rename\n    : (function (fs$rename) {\n      function rename (from, to, cb) {\n        var start = Date.now()\n        var backoff = 0;\n        fs$rename(from, to, function CB (er) {\n          if (er\n              && (er.code === \"EACCES\" || er.code === \"EPERM\" || er.code === \"EBUSY\")\n              && Date.now() - start < 60000) {\n            setTimeout(function() {\n              fs.stat(to, function (stater, st) {\n                if (stater && stater.code === \"ENOENT\")\n                  fs$rename(from, to, CB);\n                else\n                  cb(er)\n              })\n            }, backoff)\n            if (backoff < 100)\n              backoff += 10;\n            return;\n          }\n          if (cb) cb(er)\n        })\n      }\n      if (Object.setPrototypeOf) Object.setPrototypeOf(rename, fs$rename)\n      return rename\n    })(fs.rename)\n  }\n\n  // if read() returns EAGAIN, then just try it again.\n  fs.read = typeof fs.read !== 'function' ? fs.read\n  : (function (fs$read) {\n    function read (fd, buffer, offset, length, position, callback_) {\n      var callback\n      if (callback_ && typeof callback_ === 'function') {\n        var eagCounter = 0\n        callback = function (er, _, __) {\n          if (er && er.code === 'EAGAIN' && eagCounter < 10) {\n            eagCounter ++\n            return fs$read.call(fs, fd, buffer, offset, length, position, callback)\n          }\n          callback_.apply(this, arguments)\n        }\n      }\n      return fs$read.call(fs, fd, buffer, offset, length, position, callback)\n    }\n\n    // This ensures `util.promisify` works as it does for native `fs.read`.\n    if (Object.setPrototypeOf) Object.setPrototypeOf(read, fs$read)\n    return read\n  })(fs.read)\n\n  fs.readSync = typeof fs.readSync !== 'function' ? fs.readSync\n  : (function (fs$readSync) { return function (fd, buffer, offset, length, position) {\n    var eagCounter = 0\n    while (true) {\n      try {\n        return fs$readSync.call(fs, fd, buffer, offset, length, position)\n      } catch (er) {\n        if (er.code === 'EAGAIN' && eagCounter < 10) {\n          eagCounter ++\n          continue\n        }\n        throw er\n      }\n    }\n  }})(fs.readSync)\n\n  function patchLchmod (fs) {\n    fs.lchmod = function (path, mode, callback) {\n      fs.open( path\n             , constants.O_WRONLY | constants.O_SYMLINK\n             , mode\n             , function (err, fd) {\n        if (err) {\n          if (callback) callback(err)\n          return\n        }\n        // prefer to return the chmod error, if one occurs,\n        // but still try to close, and report closing errors if they occur.\n        fs.fchmod(fd, mode, function (err) {\n          fs.close(fd, function(err2) {\n            if (callback) callback(err || err2)\n          })\n        })\n      })\n    }\n\n    fs.lchmodSync = function (path, mode) {\n      var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode)\n\n      // prefer to return the chmod error, if one occurs,\n      // but still try to close, and report closing errors if they occur.\n      var threw = true\n      var ret\n      try {\n        ret = fs.fchmodSync(fd, mode)\n        threw = false\n      } finally {\n        if (threw) {\n          try {\n            fs.closeSync(fd)\n          } catch (er) {}\n        } else {\n          fs.closeSync(fd)\n        }\n      }\n      return ret\n    }\n  }\n\n  function patchLutimes (fs) {\n    if (constants.hasOwnProperty(\"O_SYMLINK\") && fs.futimes) {\n      fs.lutimes = function (path, at, mt, cb) {\n        fs.open(path, constants.O_SYMLINK, function (er, fd) {\n          if (er) {\n            if (cb) cb(er)\n            return\n          }\n          fs.futimes(fd, at, mt, function (er) {\n            fs.close(fd, function (er2) {\n              if (cb) cb(er || er2)\n            })\n          })\n        })\n      }\n\n      fs.lutimesSync = function (path, at, mt) {\n        var fd = fs.openSync(path, constants.O_SYMLINK)\n        var ret\n        var threw = true\n        try {\n          ret = fs.futimesSync(fd, at, mt)\n          threw = false\n        } finally {\n          if (threw) {\n            try {\n              fs.closeSync(fd)\n            } catch (er) {}\n          } else {\n            fs.closeSync(fd)\n          }\n        }\n        return ret\n      }\n\n    } else if (fs.futimes) {\n      fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) }\n      fs.lutimesSync = function () {}\n    }\n  }\n\n  function chmodFix (orig) {\n    if (!orig) return orig\n    return function (target, mode, cb) {\n      return orig.call(fs, target, mode, function (er) {\n        if (chownErOk(er)) er = null\n        if (cb) cb.apply(this, arguments)\n      })\n    }\n  }\n\n  function chmodFixSync (orig) {\n    if (!orig) return orig\n    return function (target, mode) {\n      try {\n        return orig.call(fs, target, mode)\n      } catch (er) {\n        if (!chownErOk(er)) throw er\n      }\n    }\n  }\n\n\n  function chownFix (orig) {\n    if (!orig) return orig\n    return function (target, uid, gid, cb) {\n      return orig.call(fs, target, uid, gid, function (er) {\n        if (chownErOk(er)) er = null\n        if (cb) cb.apply(this, arguments)\n      })\n    }\n  }\n\n  function chownFixSync (orig) {\n    if (!orig) return orig\n    return function (target, uid, gid) {\n      try {\n        return orig.call(fs, target, uid, gid)\n      } catch (er) {\n        if (!chownErOk(er)) throw er\n      }\n    }\n  }\n\n  function statFix (orig) {\n    if (!orig) return orig\n    // Older versions of Node erroneously returned signed integers for\n    // uid + gid.\n    return function (target, options, cb) {\n      if (typeof options === 'function') {\n        cb = options\n        options = null\n      }\n      function callback (er, stats) {\n        if (stats) {\n          if (stats.uid < 0) stats.uid += 0x100000000\n          if (stats.gid < 0) stats.gid += 0x100000000\n        }\n        if (cb) cb.apply(this, arguments)\n      }\n      return options ? orig.call(fs, target, options, callback)\n        : orig.call(fs, target, callback)\n    }\n  }\n\n  function statFixSync (orig) {\n    if (!orig) return orig\n    // Older versions of Node erroneously returned signed integers for\n    // uid + gid.\n    return function (target, options) {\n      var stats = options ? orig.call(fs, target, options)\n        : orig.call(fs, target)\n      if (stats) {\n        if (stats.uid < 0) stats.uid += 0x100000000\n        if (stats.gid < 0) stats.gid += 0x100000000\n      }\n      return stats;\n    }\n  }\n\n  // ENOSYS means that the fs doesn't support the op. Just ignore\n  // that, because it doesn't matter.\n  //\n  // if there's no getuid, or if getuid() is something other\n  // than 0, and the error is EINVAL or EPERM, then just ignore\n  // it.\n  //\n  // This specific case is a silent failure in cp, install, tar,\n  // and most other unix tools that manage permissions.\n  //\n  // When running as root, or if other types of errors are\n  // encountered, then it's strict.\n  function chownErOk (er) {\n    if (!er)\n      return true\n\n    if (er.code === \"ENOSYS\")\n      return true\n\n    var nonroot = !process.getuid || process.getuid() !== 0\n    if (nonroot) {\n      if (er.code === \"EINVAL\" || er.code === \"EPERM\")\n        return true\n    }\n\n    return false\n  }\n}\n"
  },
  {
    "path": "samples/nodejs-compat-fs-graceful/worker.js",
    "content": "import { default as fs } from 'graceful-fs';\n\nexport default {\n  async fetch(request) {\n    return new Response(fs.readFileSync('/bundle/worker'));\n  }\n};\n"
  },
  {
    "path": "samples/nodejs-compat-fs-yazl/README.md",
    "content": "# Node.js 'node:fs' Compat Example\n\nTo run the example on http://localhost:8080\n\n```sh\n$ ./workerd serve config.capnp\n```\n\nTo run using bazel\n\n```sh\n$ bazel run //src/workerd/server:workerd -- serve ~/cloudflare/workerd/samples/nodejs-compat/config.capnp\n```\n\nTo create a standalone binary that can be run:\n\n```sh\n$ ./workerd compile config.capnp > nodejs-compat\n\n$ ./nodejs-compat\n```\n\nTo test:\n\n```sh\n% curl http://localhost:8080\nHello World\n```\n"
  },
  {
    "path": "samples/nodejs-compat-fs-yazl/config.capnp",
    "content": "# Imports the base schema for workerd configuration files.\n\n# Refer to the comments in /src/workerd/server/workerd.capnp for more details.\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\n# A constant of type Workerd.Config defines the top-level configuration for an\n# instance of the workerd runtime. A single config file can contain multiple\n# Workerd.Config definitions and must have at least one.\nconst helloWorldExample :Workerd.Config = (\n\n  # Every workerd instance consists of a set of named services. A worker, for instance,\n  # is a type of service. Other types of services can include external servers, the\n  # ability to talk to a network, or accessing a disk directory. Here we create a single\n  # worker service. The configuration details for the worker are defined below.\n  services = [ (name = \"main\", worker = .helloWorld) ],\n\n  # Every configuration defines the one or more sockets on which the server will listene.\n  # Here, we create a single socket that will listen on localhost port 8080, and will\n  # dispatch to the \"main\" service that we defined above.\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\n# The definition of the actual helloWorld worker exposed using the \"main\" service.\n# In this example the worker is implemented as a single simple script (see worker.js).\n# The compatibilityDate is required. For more details on compatibility dates see:\n#   https://developers.cloudflare.com/workers/platform/compatibility-dates/\n\nconst helloWorld :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\"),\n    (name = \"yazl\", commonJsModule = embed \"node_modules/yazl/index.js\"),\n    (name = \"buffer-crc32\", esModule = embed \"node_modules/buffer-crc32/dist/index.mjs\"),\n    (name = \"abc.txt\", text = embed \"worker.js\"),\n  ],\n  compatibilityDate = \"2025-09-01\",\n  compatibilityFlags = [\n    \"nodejs_compat\",\n    \"enable_nodejs_fs_module\",\n  ]\n);\n"
  },
  {
    "path": "samples/nodejs-compat-fs-yazl/worker.js",
    "content": "import { default as yazl} from 'yazl';\nimport {\n  createWriteStream,\n  readFileSync,\n  readdirSync,\n} from 'node:fs';\n\nexport default {\n  fetch(request) {\n\n    const { promise, resolve, reject } = Promise.withResolvers();\n\n    const zipfile = new yazl.ZipFile();\n\n    const entries = readdirSync(\"/bundle\");\n    for (const entry of entries) {\n      zipfile.addFile(`/bundle/${entry}`, entry);\n    }\n\n    zipfile.outputStream.pipe(createWriteStream(\"/tmp/output.zip\"))\n      .on(\"close\", function() {\n        const buffer = readFileSync(\"/tmp/output.zip\");\n        resolve(new Response(buffer));\n    });\n\n    zipfile.end();\n\n    return promise;\n  }\n};\n"
  },
  {
    "path": "samples/nodejs-compat-http/README.md",
    "content": "# Node.js 'node:http' Compat Example\n\nTo run the example on http://localhost:8080\n\n```sh\n$ ./workerd serve config.capnp\n```\n\nTo run using bazel\n\n```sh\n$ bazel run //src/workerd/server:workerd -- serve ~/cloudflare/workerd/samples/nodejs-compat/config.capnp\n```\n\nTo create a standalone binary that can be run:\n\n```sh\n$ ./workerd compile config.capnp > nodejs-compat\n\n$ ./nodejs-compat\n```\n\nTo test:\n\n```sh\n% curl http://localhost:8080\nHello World\n```\n"
  },
  {
    "path": "samples/nodejs-compat-http/config.capnp",
    "content": "# Imports the base schema for workerd configuration files.\n\n# Refer to the comments in /src/workerd/server/workerd.capnp for more details.\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\n# A constant of type Workerd.Config defines the top-level configuration for an\n# instance of the workerd runtime. A single config file can contain multiple\n# Workerd.Config definitions and must have at least one.\nconst helloWorldExample :Workerd.Config = (\n\n  # Every workerd instance consists of a set of named services. A worker, for instance,\n  # is a type of service. Other types of services can include external servers, the\n  # ability to talk to a network, or accessing a disk directory. Here we create a single\n  # worker service. The configuration details for the worker are defined below.\n  services = [ (name = \"main\", worker = .helloWorld) ],\n\n  # Every configuration defines the one or more sockets on which the server will listene.\n  # Here, we create a single socket that will listen on localhost port 8080, and will\n  # dispatch to the \"main\" service that we defined above.\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\n# The definition of the actual helloWorld worker exposed using the \"main\" service.\n# In this example the worker is implemented as a single simple script (see worker.js).\n# The compatibilityDate is required. For more details on compatibility dates see:\n#   https://developers.cloudflare.com/workers/platform/compatibility-dates/\n\nconst helloWorld :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\")\n  ],\n  compatibilityDate = \"2025-07-01\",\n  compatibilityFlags = [\"nodejs_compat\", \"enable_nodejs_http_modules\"]\n);\n"
  },
  {
    "path": "samples/nodejs-compat-http/worker.js",
    "content": "import { get } from 'node:http';\nexport default {\n  async fetch(request) {\n    const { promise, resolve } = Promise.withResolvers();\n    get('http://example.com', {\n      headers: {\n        'accept': 'text/plain',\n      }\n    }, (res) => {\n      let data = '';\n      res.on('data', (chunk) => {\n        data += chunk;\n      });\n      res.on('end', () => {\n        console.log('...', data);\n        resolve(new Response(data));\n      });\n    });\n    return promise;\n  }\n};\n"
  },
  {
    "path": "samples/nodejs-compat-streams/README.md",
    "content": "# Node.js Compat Example\n\nTo run the example on http://localhost:8080\n\n```sh\n$ ./workerd serve config.capnp\n```\n\nTo run using bazel\n\n```sh\n$ bazel run //src/workerd/server:workerd -- serve ~/cloudflare/workerd/samples/nodejs-compat/config.capnp\n```\n\nTo create a standalone binary that can be run:\n\n```sh\n$ ./workerd compile config.capnp > nodejs-compat\n\n$ ./nodejs-compat\n```\n\nTo test:\n\n```sh\n% curl http://localhost:8080\nHello World\n```\n"
  },
  {
    "path": "samples/nodejs-compat-streams/config.capnp",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst helloWorldExample :Workerd.Config = (\n\n  services = [ (name = \"main\", worker = .helloWorld) ],\n\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\nconst helloWorld :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\")\n  ],\n  compatibilityDate = \"2022-11-08\",\n  compatibilityFlags = [\"nodejs_compat\"]\n);\n"
  },
  {
    "path": "samples/nodejs-compat-streams/worker.js",
    "content": "import {\n  Readable,\n  Transform,\n} from 'node:stream';\n\nimport {\n  text,\n} from 'node:stream/consumers';\n\nimport {\n  pipeline,\n} from 'node:stream/promises';\n\nclass MyTransform extends Transform {\n  constructor() {\n    super({ encoding: 'utf8' });\n  }\n  _transform(chunk, _, cb) {\n    this.push(chunk.toString().toUpperCase());\n    cb();\n  }\n  _flush(cb) {\n    this.push('\\n');\n    cb();\n  }\n}\n\nexport default {\n  async fetch() {\n    const chunks = [\n      \"hello \",\n      \"from \",\n      \"the \",\n      \"wonderful \",\n      \"world \",\n      \"of \",\n      \"node.js \",\n      \"streams!\"\n    ];\n\n    function nextChunk(readable) {\n      readable.push(chunks.shift());\n      if (chunks.length === 0) readable.push(null);\n      else queueMicrotask(() => nextChunk(readable));\n    }\n\n    const readable = new Readable({\n      encoding: 'utf8',\n      read() { nextChunk(readable); }\n    });\n\n    const transform = new MyTransform();\n\n    await pipeline(readable, transform);\n\n    return new Response(await text(transform));\n  }\n};\n"
  },
  {
    "path": "samples/nodejs-compat-streams-split2/README.md",
    "content": "# Node.js Compat Example\n\nTo run the example on http://localhost:8080\n\n```sh\n$ bazel-bin/src/workerd/server/workerd serve --experimental $(realpath samples/nodejs-compat-streams-split2/config.capnp)\n```\n\nTo run using bazel\n\n```sh\n$ bazel run //src/workerd/server:workerd -- serve $(realpath samples/nodejs-compat-streams-split2/config.capnp)\n```\n\nTo create a standalone binary that can be run:\n\n```sh\n$ ./workerd compile config.capnp > nodejs-compat\n\n$ ./nodejs-compat\n```\n\nTo test:\n\n```sh\n% curl http://localhost:8080\nhello\nfrom\nthe\nwonderful\nworld\nof\nnode.js\nstreams!\n```\n"
  },
  {
    "path": "samples/nodejs-compat-streams-split2/config.capnp",
    "content": "# Imports the base schema for workerd configuration files.\n\n# Refer to the comments in /src/workerd/server/workerd.capnp for more details.\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\n# A constant of type Workerd.Config defines the top-level configuration for an\n# instance of the workerd runtime. A single config file can contain multiple\n# Workerd.Config definitions and must have at least one.\nconst helloWorldExample :Workerd.Config = (\n\n  # Every workerd instance consists of a set of named services. A worker, for instance,\n  # is a type of service. Other types of services can include external servers, the\n  # ability to talk to a network, or accessing a disk directory. Here we create a single\n  # worker service. The configuration details for the worker are defined below.\n  services = [ (name = \"main\", worker = .helloWorld) ],\n\n  # Every configuration defines the one or more sockets on which the server will listene.\n  # Here, we create a single socket that will listen on localhost port 8080, and will\n  # dispatch to the \"main\" service that we defined above.\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\n# The definition of the actual helloWorld worker exposed using the \"main\" service.\n# In this example the worker is implemented as a single simple script (see worker.js).\n# The compatibilityDate is required. For more details on compatibility dates see:\n#   https://developers.cloudflare.com/workers/platform/compatibility-dates/\n\nconst helloWorld :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\"),\n    (name = \"split2\", commonJsModule = embed \"split2.js\")\n  ],\n  compatibilityDate = \"2025-03-01\",\n  compatibilityFlags = [\"nodejs_compat_v2\", \"experimental\"]\n);\n"
  },
  {
    "path": "samples/nodejs-compat-streams-split2/split2.js",
    "content": "// https://www.npmjs.com/package/split2\n\n/*\nCopyright (c) 2014-2021, Matteo Collina <hello@matteocollina.com>\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR\nIN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n*/\n\n'use strict'\n\nconst { Transform } = require('stream')\nconst { StringDecoder } = require('string_decoder')\nconst kLast = Symbol('last')\nconst kDecoder = Symbol('decoder')\n\nfunction transform (chunk, enc, cb) {\n  let list\n  if (this.overflow) { // Line buffer is full. Skip to start of next line.\n    const buf = this[kDecoder].write(chunk)\n    list = buf.split(this.matcher)\n\n    if (list.length === 1) return cb() // Line ending not found. Discard entire chunk.\n\n    // Line ending found. Discard trailing fragment of previous line and reset overflow state.\n    list.shift()\n    this.overflow = false\n  } else {\n    this[kLast] += this[kDecoder].write(chunk)\n    list = this[kLast].split(this.matcher)\n  }\n\n  this[kLast] = list.pop()\n\n  for (let i = 0; i < list.length; i++) {\n    try {\n      push(this, this.mapper(list[i]))\n    } catch (error) {\n      return cb(error)\n    }\n  }\n\n  this.overflow = this[kLast].length > this.maxLength\n  if (this.overflow && !this.skipOverflow) {\n    cb(new Error('maximum buffer reached'))\n    return\n  }\n\n  cb()\n}\n\nfunction flush (cb) {\n  // forward any gibberish left in there\n  this[kLast] += this[kDecoder].end()\n\n  if (this[kLast]) {\n    try {\n      push(this, this.mapper(this[kLast]))\n    } catch (error) {\n      return cb(error)\n    }\n  }\n\n  cb()\n}\n\nfunction push (self, val) {\n  if (val !== undefined) {\n    self.push(val)\n  }\n}\n\nfunction noop (incoming) {\n  return incoming\n}\n\nfunction split (matcher, mapper, options) {\n  // Set defaults for any arguments not supplied.\n  matcher = matcher || /\\r?\\n/\n  mapper = mapper || noop\n  options = options || {}\n\n  // Test arguments explicitly.\n  switch (arguments.length) {\n    case 1:\n      // If mapper is only argument.\n      if (typeof matcher === 'function') {\n        mapper = matcher\n        matcher = /\\r?\\n/\n      // If options is only argument.\n      } else if (typeof matcher === 'object' && !(matcher instanceof RegExp) && !matcher[Symbol.split]) {\n        options = matcher\n        matcher = /\\r?\\n/\n      }\n      break\n\n    case 2:\n      // If mapper and options are arguments.\n      if (typeof matcher === 'function') {\n        options = mapper\n        mapper = matcher\n        matcher = /\\r?\\n/\n      // If matcher and options are arguments.\n      } else if (typeof mapper === 'object') {\n        options = mapper\n        mapper = noop\n      }\n  }\n\n  options = Object.assign({}, options)\n  options.autoDestroy = true\n  options.transform = transform\n  options.flush = flush\n  options.readableObjectMode = true\n\n  const stream = new Transform(options)\n\n  stream[kLast] = ''\n  stream[kDecoder] = new StringDecoder('utf8')\n  stream.matcher = matcher\n  stream.mapper = mapper\n  stream.maxLength = options.maxLength\n  stream.skipOverflow = options.skipOverflow || false\n  stream.overflow = false\n  stream._destroy = function (err, cb) {\n    // Weird Node v12 bug that we need to work around\n    this._writableState.errorEmitted = false\n    cb(err)\n  }\n\n  return stream\n}\n\nmodule.exports = split\n"
  },
  {
    "path": "samples/nodejs-compat-streams-split2/worker.js",
    "content": "import {\n  PassThrough,\n  Transform,\n  Readable,\n} from 'node:stream';\n\nimport { default as split2 } from 'split2';\n\nconst enc = new TextEncoder();\n\nexport default {\n  async fetch() {\n    const pt = new PassThrough();\n\n    // split2 will remove the new lines from the single input stream,\n    // pushing each individual line as a separate chunk. We use this\n    // transform to add the new lines back in just to show the effect\n    // in the output.\n    const lb = new Transform({\n      transform(chunk, encoding, callback) {\n        callback(null, enc.encode(chunk + '\\n'));\n      }\n    });\n\n    const readable = pt.pipe(split2()).pipe(lb);\n\n    pt.end('hello\\nfrom\\nthe\\nwonderful\\nworld\\nof\\nnode.js\\nstreams!');\n\n    return new Response(Readable.toWeb(readable));\n  }\n};\n"
  },
  {
    "path": "samples/pyodide/config.capnp",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n  ],\n\n  sockets = [\n    # Serve HTTP on port 8080.\n    ( name = \"http\",\n      address = \"*:8080\",\n      http = (),\n      service = \"main\"\n    ),\n  ],\n);\n\nconst mainWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker.py\", pythonModule = embed \"./worker.py\"),\n  ],\n  compatibilityDate = \"2023-12-18\",\n  compatibilityFlags = [\"python_workers\", \"python_workers_20250116\"],\n  # Learn more about compatibility dates at:\n  # https://developers.cloudflare.com/workers/platform/compatibility-dates/\n);\n"
  },
  {
    "path": "samples/pyodide/worker.py",
    "content": "from js import Response\nfrom workers import WorkerEntrypoint\n\n\nclass Default(WorkerEntrypoint):\n    def fetch(self, request):\n        return Response.new(\"hello world\")\n\n\ndef test():\n    print(\"Hi there, this is a test\")\n"
  },
  {
    "path": "samples/pyodide-env/config.capnp",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n  ],\n\n  sockets = [\n    # Serve HTTP on port 8080.\n    ( name = \"http\",\n      address = \"*:8080\",\n      http = (),\n      service = \"main\"\n    ),\n  ],\n  autogates = [\n    # Pyodide is included as a builtin wasm module so it requires the\n    # corresponding autogate flag.\n    \"workerd-autogate-builtin-wasm-modules\",\n  ]\n);\n\nconst mainWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker.py\", pythonModule = embed \"./worker.py\"),\n  ],\n  compatibilityDate = \"2023-12-18\",\n  compatibilityFlags = [\"python_workers\"],\n  bindings = [\n    (\n      name = \"MY_ENV\",\n      text = \"some_value\"\n    ),\n  ],\n  # Learn more about compatibility dates at:\n  # https://developers.cloudflare.com/workers/platform/compatibility-dates/\n);\n"
  },
  {
    "path": "samples/pyodide-env/worker.py",
    "content": "from js import Response\nfrom workers import WorkerEntrypoint\n\n\nclass Default(WorkerEntrypoint):\n    def fetch(self, request):\n        print(self.env.MY_VAR)\n        return Response.new(\"hello world\")\n\n    def test(self, ctx):\n        print(self.env.MY_VAR)\n        print(\"Hi there, this is a test\")\n"
  },
  {
    "path": "samples/pyodide-fastapi/config.capnp",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n  ],\n\n  sockets = [\n    # Serve HTTP on port 8080.\n    ( name = \"http\",\n      address = \"*:8080\",\n      http = (),\n      service = \"main\"\n    ),\n  ],\n);\n\nconst mainWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker.py\", pythonModule = embed \"./worker.py\"),\n    (name = \"fastapi\", pythonRequirement = \"\"),\n    (name = \"anyio\", pythonRequirement = \"\"),\n  ],\n  bindings = [\n    (\n      name = \"secret\",\n      text = \"thisisasecret\"\n    ),\n  ],\n  compatibilityDate = \"2023-12-18\",\n  compatibilityFlags = [\"python_workers\"],\n  # Learn more about compatibility dates at:\n  # https://developers.cloudflare.com/workers/platform/compatibility-dates/\n);\n"
  },
  {
    "path": "samples/pyodide-fastapi/worker.py",
    "content": "from asgi import env\nfrom workers import WorkerEntrypoint\n\n\nclass Default(WorkerEntrypoint):\n    async def fetch(self, request, env):\n        import asgi\n\n        return await asgi.fetch(app, request, env)\n\n\n# Set up fastapi app\n\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel\n\napp = FastAPI()\n\n\n@app.get(\"/hello\")\nasync def hello(env=env):\n    return {\"message\": \"Hello World\", \"secret\": env.secret}\n\n\n@app.get(\"/route\")\nasync def route():\n    return {\"message\": \"this is my custom route\"}\n\n\n@app.get(\"/favicon.ico\")\nasync def favicon():\n    return {\"message\": \"here's a favicon I guess?\"}\n\n\n@app.get(\"/items/{item_id}\")\nasync def read_item(item_id: int):\n    return {\"item_id\": item_id}\n\n\nclass Item(BaseModel):\n    name: str\n    description: str | None = None\n    price: float\n    tax: float | None = None\n\n\n@app.post(\"/items/\")\nasync def create_item(item: Item):\n    return item\n\n\n@app.put(\"/items/{item_id}\")\nasync def create_item2(item_id: int, item: Item, q: str | None = None):\n    result = {\"item_id\": item_id, **item.model_dump()}\n    if q:\n        result.update({\"q\": q})\n    return result\n"
  },
  {
    "path": "samples/pyodide-langchain/config.capnp",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n  ],\n\n  sockets = [\n    # Serve HTTP on port 8080.\n    ( name = \"http\",\n      address = \"*:8080\",\n      http = (),\n      service = \"main\"\n    ),\n  ],\n);\n\nconst mainWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker.py\", pythonModule = embed \"./worker.py\"),\n    (name = \"langchain_core\", pythonRequirement = \"\"),\n    (name = \"langchain_openai\", pythonRequirement = \"\"),\n  ],\n  compatibilityDate = \"2023-12-18\",\n  compatibilityFlags = [\"python_workers\"],\n  # Learn more about compatibility dates at:\n  # https://developers.cloudflare.com/workers/platform/compatibility-dates/\n);\n"
  },
  {
    "path": "samples/pyodide-langchain/worker.py",
    "content": "from langchain_core.prompts import PromptTemplate\nfrom langchain_openai import OpenAI\n\nAPI_KEY = \"sk-abcdefg\"\n\n\nasync def test(request):\n    prompt = PromptTemplate.from_template(\n        \"Complete the following sentence: I am a {profession} and \"\n    )\n    llm = OpenAI(api_key=API_KEY)\n    chain = prompt | llm\n\n    res = await chain.ainvoke({\"profession\": \"electrician\"})\n    print(res)\n"
  },
  {
    "path": "samples/python-benchmark/README.md",
    "content": "To run:\n\n```bash\n$ bazel run --config=release @workerd//src/workerd/server:workerd -- serve $PWD/deps/workerd/samples/python-benchmark/config.capnp\n\n$ wrk -t4 -c64 -d30s --latency -s bench.lua http://127.0.0.1:8080\n\n$ curl -v -X POST http://127.0.0.1:8080\n```\n"
  },
  {
    "path": "samples/python-benchmark/bench.lua",
    "content": "wrk.method = \"POST\"\nwrk.body   = '{\"iters\": 500}'\nwrk.headers[\"Content-Type\"] = \"application/json\"\n"
  },
  {
    "path": "samples/python-benchmark/config.capnp",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n  ],\n\n  sockets = [\n    # Serve HTTP on port 8080.\n    ( name = \"http\",\n      address = \"*:8080\",\n      http = (),\n      service = \"main\"\n    ),\n  ],\n);\n\nconst mainWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker.py\", pythonModule = embed \"./worker.py\"),\n  ],\n  compatibilityDate = \"2025-11-15\",\n  compatibilityFlags = [\"python_workers\"],\n);\n"
  },
  {
    "path": "samples/python-benchmark/worker.py",
    "content": "import json\n\nfrom js import Response\nfrom workers import WorkerEntrypoint\n\nDEFAULT_ITERATIONS = 500\n\n\ndef json_loop(n: int) -> int:\n    count = 0\n    for i in range(n):\n        obj = {\n            \"id\": i,\n            \"name\": \"item-\" + str(i),\n            \"values\": [i, i * 2, i * 3],\n            \"nested\": {\"flag\": (i % 2 == 0), \"index\": i},\n        }\n        s = json.dumps(obj)\n        parsed = json.loads(s)\n        count += parsed[\"id\"]\n    return count\n\n\nclass Default(WorkerEntrypoint):\n    async def fetch(self, request):\n        iters = DEFAULT_ITERATIONS\n\n        try:\n            body_text = await request.text()\n            if body_text:\n                data = json.loads(body_text)\n                iters = int(data.get(\"iters\", iters))\n        except Exception:\n            iters = DEFAULT_ITERATIONS\n\n        result = json_loop(iters)\n\n        return Response.new(\n            json.dumps({\"result\": result, \"iters\": iters}),\n            {\"content-type\": \"application/json\"},\n        )\n\n\ndef test():\n    iters = DEFAULT_ITERATIONS\n    result = json_loop(iters)\n    print(f\"Ran json_loop({iters}) -> {result}\")\n"
  },
  {
    "path": "samples/repl-server/README.md",
    "content": "# REPL Server\n\nThis sample creates a simple REPL server which communicates with a Node.js\nclient over HTTP. This is very experimental and is only included for testing\npurposes for now.\n\n## How to use\n```\n./bazel-bin/src/workerd/server/workerd serve samples/repl-server/config.capnp --experimental\nnode samples/repl-server/client.js\n```\n"
  },
  {
    "path": "samples/repl-server/client.js",
    "content": "const repl = require(\"repl\");\n\nconst myeval = async (cmd, ctx, fname, callback) => {\n  const res = await fetch('http://localhost:8080', {\n    method: 'POST',\n    headers: {\n      'Accept': 'application/json',\n      'Content-Type': 'application/json'\n    },\n    body: JSON.stringify({cmd})\n  });\n\n  callback(null, await res.text());\n}\n\nrepl.start({\n  eval: myeval,\n  writer: a => a\n});\n"
  },
  {
    "path": "samples/repl-server/config.capnp",
    "content": "\nusing Workerd = import \"/workerd/workerd.capnp\";\n\nconst ReplServer :Workerd.Config = (\n  services = [ (name = \"main\", worker = .replServer) ],\n\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\nconst replServer :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\")\n  ],\n  bindings = [\n    (\n      name = \"secret\",\n      text = \"thisisasecret\"\n    ),\n  ],\n  compatibilityDate = \"2023-02-28\",\n  compatibilityFlags = [\"nodejs_compat\", \"experimental\", \"unsafe_module\"]\n);\n"
  },
  {
    "path": "samples/repl-server/worker.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { default as UnsafeEval } from \"workerd:unsafe-eval\";\nimport { default as util } from \"node:util\";\n\n// https://stackoverflow.com/questions/67322922/context-preserving-eval\nvar __EVAL = s => UnsafeEval.eval(`void (__EVAL = ${__EVAL.toString()}); ${s}`);\n\nasync function evaluate(expr) {\n  try {\n    const result = await __EVAL(expr);\n    return result;\n  } catch (err) {\n    return err;\n  }\n}\n\nglobalThis.consoleLogBuf = \"\";\n\nexport default {\n  async fetch(req, env) {\n    const {cmd} = await req.json();\n\n    globalThis.env = env;\n\n    globalThis.console.log = (...args) => {\n      const line = args.map(a => util.inspect(a, {colors: true})).join(\" \");\n      consoleLogBuf += line + \"\\n\";\n    };\n\n    const res = await evaluate(cmd);\n\n    const response = consoleLogBuf + util.inspect(res, {colors: true});\n\n    globalThis.consoleLogBuf = \"\";\n\n    return new Response(response);\n  }\n};\n"
  },
  {
    "path": "samples/repl-server-python/README.md",
    "content": "# REPL Server\n\nThis sample creates a simple REPL server which communicates with a Node.js\nclient over HTTP. This is very experimental and is only included for testing\npurposes for now.\n\nThis sample is the Python variant of `repl-server`.\n\n## How to use\n```\n./bazel-bin/src/workerd/server/workerd serve samples/repl-server-python/config.capnp --experimental\nnode samples/repl-server/client.js\n```\n"
  },
  {
    "path": "samples/repl-server-python/client.js",
    "content": "const repl = require(\"repl\");\n\nconst myeval = async (cmd, ctx, fname, callback) => {\n  const res = await fetch('http://localhost:8080', {\n    method: 'POST',\n    headers: {\n      'Accept': 'application/json',\n      'Content-Type': 'application/json'\n    },\n    body: JSON.stringify({cmd})\n  });\n\n  callback(null, await res.text());\n}\n\nrepl.start({\n  eval: myeval,\n  writer: a => a\n});\n"
  },
  {
    "path": "samples/repl-server-python/config.capnp",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n  ],\n\n  sockets = [\n    # Serve HTTP on port 8080.\n    ( name = \"http\",\n      address = \"*:8080\",\n      http = (),\n      service = \"main\"\n    ),\n  ],\n);\n\nconst mainWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker.py\", pythonModule = embed \"./worker.py\"),\n  ],\n  compatibilityDate = \"2023-12-18\",\n  compatibilityFlags = [\"python_workers\"],\n  # Learn more about compatibility dates at:\n  # https://developers.cloudflare.com/workers/platform/compatibility-dates/\n);\n"
  },
  {
    "path": "samples/repl-server-python/worker.py",
    "content": "import code\nimport sys\nfrom io import StringIO\n\nfrom js import Response\nfrom workers import WorkerEntrypoint\n\nsys.stdout = StringIO()\n\nii = code.InteractiveInterpreter()\n\n\nclass Default(WorkerEntrypoint):\n    async def fetch(self, request):\n        cmd = (await request.json()).cmd\n\n        ii.runsource(cmd)\n\n        res = sys.stdout.getvalue()\n        sys.stdout = StringIO()\n\n        return Response.new(res)\n"
  },
  {
    "path": "samples/static-files-from-disk/config.capnp",
    "content": "# This is a simple demo showing how to serve a static web site from disk.\n#\n# By default this will serve a subdirectory called `content-dir` from whatever directory `workerd`\n# runs in, but you can override the directory on the command-line like so:\n#\n#     workerd serve config.capnp --directory-path site-files=/path/to/files\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\n# A constant of type `Workerd.Config` will be recognized as the top-level configuration.\nconst config :Workerd.Config = (\n  services = [\n    # The site worker contains JavaScript logic to serve static files from a directory. The logic\n    # includes things like setting the right content-type (based on file name), defaulting to\n    # `index.html`, and so on.\n    (name = \"site-worker\", worker = .siteWorker),\n    \n    # Underneath the site worker we have a second service which provides direct access to files on\n    # disk. We only configure site-worker to be able to access this (via a binding, below), so it\n    # won't be served publicly as-is. (Note that disk access is read-only by default, but there is\n    # a `writable` option which enables PUT requests.)\n    (name = \"site-files\", disk = \"content-dir\"),\n  ],\n\n  # We export it via HTTP on port 8080.\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"site-worker\" ) ],\n);\n\n# For legibility we define the Worker's config as a separate constant.\nconst siteWorker :Workerd.Worker = (\n  # All Workers must declare a compatibility date, which ensures that if `workerd` is updated to\n  # a newer version with breaking changes, it will emulate the API as it existed on this date, so\n  # the Worker won't break.\n  compatibilityDate = \"2023-02-28\",\n\n  # This worker is modules-based.\n  modules = [\n    (name = \"static.js\", esModule = embed \"static.js\"),\n  ],\n\n  bindings = [\n    # Give this worker permission to request files on disk, via the \"site-files\" service we\n    # defined earlier.\n    (name = \"files\", service = \"site-files\"),\n\n    # This worker supports some configuration options via a JSON binding. Here we set the option\n    # so that we hide the `.html` extension from URLs. (See the code for all config options.)\n    (name = \"config\", json = \"{\\\"hideHtmlExtension\\\": true}\")\n  ],\n);\n\n"
  },
  {
    "path": "samples/static-files-from-disk/content-dir/index.html",
    "content": "<!DOCTYPE html>\n\n<html>\n  <body>\n    <h1>Hello world!</h1>\n  </body>\n</html>\n"
  },
  {
    "path": "samples/static-files-from-disk/static.js",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// An example Worker that serves static files from disk. This includes logic to do things like\n// set Content-Type based on file extension, look for `index.html` in directories, etc.\n//\n// This code supports several configuration options to control the serving logic, but, better\n// yet, because it's just JavaScript, you can freely edit it to suit your unique needs.\n\nexport default {\n  async fetch(req, env) {\n    if (req.method != \"GET\" && req.method != \"HEAD\") {\n      return new Response(\"Not Implemented\", {status: 501});\n    }\n\n    let url = new URL(req.url);\n    let path = url.pathname;\n    let origPath = path;\n\n    let config = env.config || {};\n\n    if (path.endsWith(\"/\") && !config.allowDirectoryListing) {\n      path = path + \"index.html\";\n    }\n\n    let content = await env.files.fetch(\"http://dummy\" + path, {method: req.method});\n\n    if (content.status == 404) {\n      if (config.hideHtmlExtension && !path.endsWith(\".html\")) {\n        // Try with the `.html` extension.\n        path = path + \".html\";\n        content = await env.files.fetch(\"http://dummy\" + path, {method: req.method});\n      }\n\n      if (!content.ok && config.singlePageApp) {\n        // For any file not found, serve the main page -- NOT as a 404.\n        path = \"/index.html\";\n        content = await env.files.fetch(\"http://dummy\" + path, {method: req.method});\n      }\n\n      if (!content.ok) {\n        // None of the fallbacks worked.\n        //\n        // Look for a 404 page.\n        content = await env.files.fetch(\"http://dummy/404.html\", {method: req.method});\n\n        if (content.ok) {\n          // Return it with a 404 status code.\n          return wrapContent(req, 404, \"404.html\", content.body, content.headers);\n        } else {\n          // Give up and return generic 404 message.\n          return new Response(\"404 Not found\", {status: 404});\n        }\n      }\n    }\n\n    if (!content.ok) {\n      // Some error other than 404?\n      console.error(\"Fetching '\" + path + \"' returned unexpected status: \" + content.status);\n      return new Response(\"Internal Server Error\", {status: 500});\n    }\n\n    if (content.headers.get(\"Content-Type\") == \"application/json\") {\n      // This is a directory.\n      if (path.endsWith(\"/\")) {\n        // This must be because `allowDirectoryListing` is `true`, so this is actually OK!\n        let listingHtml = null;\n        if (req.method != \"HEAD\") {\n          let html = await makeListingHtml(origPath, await content.json(), env.files);\n          return wrapContent(req, 200, \"listing.html\", html);\n        }\n      } else {\n        // redirect to add '/' suffix.\n        url.pathname = path + \"/\";\n        return Response.redirect(url);\n      }\n    }\n\n    if (origPath.endsWith(\"/index.html\")) {\n      // The file exists, but was requested as \"index.html\", which we want to hide, so redirect\n      // to remove it.\n      url.pathname = origPath.slice(0, -\"index.html\".length);\n      return Response.redirect(url);\n    }\n\n    if (config.hideHtmlExtension && origPath.endsWith(\".html\")) {\n      // The file exists, but was requested with the `.html` extension, which we want to hide, so\n      // redirect to remove it.\n      url.pathname = origPath.slice(0, -\".html\".length);\n      return Response.redirect(url);\n    }\n\n    return wrapContent(req, 200, path.split(\"/\").pop(), content.body, content.headers);\n  }\n}\n\nfunction wrapContent(req, status, filename, contentBody, contentHeaders) {\n  let type = TYPES[filename.split(\".\").pop().toLowerCase()] || \"application/octet-stream\";\n  let headers = { \"Content-Type\": type };\n  if (type.endsWith(\";charset=utf-8\")) {\n    let accept = req.headers.get(\"Accept-Encoding\") || \"\";\n    if (accept.split(\",\").map(s => s.trim()).includes(\"gzip\")) {\n      // Apply gzip encoding on the fly.\n      // TODO(someday): Support storing gziped copies of files on disk in advance so that gzip\n      //   doesn't need to be applied on the fly.\n      headers[\"Content-Encoding\"] = \"gzip\";\n    }\n  }\n\n  if (req.method == \"HEAD\" && contentHeaders) {\n    // Carry over Content-Length header on HEAD requests.\n    let len = contentHeaders.get(\"Content-Length\");\n    if (len) {\n      headers[\"Content-Length\"] = len;\n    }\n  }\n\n  return new Response(contentBody, {headers, status});\n}\n\nlet TYPES = {\n  txt: \"text/plain;charset=utf-8\",\n  html: \"text/html;charset=utf-8\",\n  htm: \"text/html;charset=utf-8\",\n  css: \"text/css;charset=utf-8\",\n  js: \"text/javascript;charset=utf-8\",\n  md: \"text/markdown;charset=utf-8\",\n  sh: \"application/x-shellscript;charset=utf-8\",\n  svg: \"image/svg+xml;charset=utf-8\",\n  xml: \"text/xml;charset=utf-8\",\n\n  png: \"image/png\",\n  jpeg: \"image/jpeg\",\n  jpg: \"image/jpeg\",\n  jpe: \"image/jpeg\",\n  gif: \"image/gif\",\n\n  ttf: \"font/ttf\",\n  woff: \"font/woff\",\n  woff2: \"font/woff2\",\n  eot: \"application/vnd.ms-fontobject\",\n\n  // When serving files with the .gz extension, we do NOT want to use `Content-Encoding: gzip`,\n  // because this will cause the user agent to unzip it, which is usually not what the user wants\n  // when downloading a gzipped archive.\n  gz: \"application/gzip\",\n  bz: \"application/x-bzip\",\n  bz2: \"application/x-bzip2\",\n  xz: \"application/x-xz\",\n  zst: \"application/zst\",\n}\n\nasync function makeListingHtml(path, listing, dir) {\n  if (!path.endsWith(\"/\")) path += \"/\";\n\n  let htmlList = [];\n  for (let file of listing) {\n    let len, modified;\n    if (file.type == \"file\" || file.type == \"directory\") {\n      let meta = await dir.fetch(\"http://dummy\" + path + file.name, {method: \"HEAD\"});\n      console.log(meta.status, \"http://dummy\" + path + file.name, meta.headers.get(\"Content-Length\"));\n      len = meta.headers.get(\"Content-Length\");\n      modified = meta.headers.get(\"Last-Modified\");\n    }\n\n    len = len || `(${file.type})`;\n    modified = modified || \"\";\n\n    htmlList.push(\n        `      <tr>` +\n        `<td><a href=\"${encodeURIComponent(file.name)}\">${file.name}</a></td>` +\n        `<td>${modified}</td><td>${len}</td></tr>`);\n  }\n\n  return `<!DOCTYPE html>\n<html>\n  <head>\n    <title>Index of ${path}</title>\n    <style type=\"text/css\">\n      td { padding-right: 16px; text-align: right; }\n      td:nth-of-type(1) { font-family: monospace; text-align: left; }\n      th { text-align: left; }\n    </style>\n  </head>\n  <body>\n    <h1>Index of ${path}</h1>\n    <table>\n      <tr><th>Filename</th><th>Modified</th><th>Size</th></tr>\n      ${htmlList.join(\"\\n\")}\n  </body>\n</html>\n`\n}\n"
  },
  {
    "path": "samples/tail-workers/config.capnp",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst tailWorkerExample :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .helloWorld),\n    (name = \"log\", worker = .logWorker),\n  ],\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ],\n);\n\nconst helloWorld :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\")\n  ],\n  compatibilityDate = \"2024-10-14\",\n  tails = [\"log\"],\n  streamingTails = [\"log\"],\n);\n\nconst logWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"tail.js\")\n  ],\n  compatibilityDate = \"2024-10-14\",\n);\n"
  },
  {
    "path": "samples/tail-workers/tail.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport default {\n  // https://developers.cloudflare.com/workers/observability/logs/tail-workers/\n  tail(traces) {\n    console.log(traces[0].logs);\n  },\n  tailStream(...args) {\n    console.log(...args);\n    return (...args) => {\n      console.log(...args);\n    };\n  },\n};\n"
  },
  {
    "path": "samples/tail-workers/worker.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport default {\n  async fetch(req, env) {\n    console.log('hello to the tail worker!');\n    reportError('boom');\n    reportError(new Error('test'));\n    return new Response(\"Hello World\\n\");\n  }\n};\n"
  },
  {
    "path": "samples/tcp/config.capnp",
    "content": "# This example defines the configuration for a simple Gopher\n# (https://en.wikipedia.org/wiki/Gopher_(protocol)) client, with an optional HTTP proxy\n# configuration as well.\n#\n# The comments in this file focus on TCP-related configuration. For details about the rest check\n# out the helloworld/config.capnp file (also under the samples directory).\n# Also be sure to refer to the comments in /src/workerd/server/workerd.capnp.\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\nconst helloWorldExample :Workerd.Config = (\n\n  # Every workerd instance consists of a set of named services. A worker, for instance,\n  # is a type of service. Here we create a single worker service and an ExternalServer which\n  # defines a local proxy. The configuration details for these are defined below.\n  services = [\n    (name = \"main\", worker = .gopherWorker),\n    (name = \"proxy\", external = .localProxy)\n  ],\n\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\n# The definition of the actual worker exposed using the \"main\" service.\nconst gopherWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"gopher.js\")\n  ],\n  compatibilityDate = \"2023-02-28\",\n  # In order to access our configured proxy we need to specify it as a binding. This will allow\n  # it to be accessible via `env.proxy` in the JS script.\n  bindings = [\n    (name = \"proxy\", service = \"proxy\")\n  ]\n);\n\n# The definition of an external server which in this case is an HTTP proxy. Any connections made\n# through it will be tunneled using HTTP CONNECT.\nconst localProxy :Workerd.ExternalServer = (\n  address = \"localhost:1234\",\n  http = (\n    style = proxy\n  )\n);\n"
  },
  {
    "path": "samples/tcp/gopher.js",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// This is an ESM module implementing a simple Gopher client. Gopher is an old alternative to HTTP,\n// it is also a simple protocol which makes it ideal for demoing TCP sockets in workerd.\n//\n// This module implements both direct and proxied TCP connections. In order to make use of the\n// proxy you'll need to have an HTTP proxy running on port 1234 (this can be changed in config.capnp).\n//\n// Example usage once you have workerd running:\n//\n// - curl localhost:8080/ # Retrieve the main segment in gopher.floodgap.com.\n// - curl localhost:8080/gstats # Retrieve the \"gstats\" segment.\n// - curl localhost:8080/gstats?use_proxy=1 # Retrieve the \"gstats\" segment using the configured proxy.\nimport { connect } from 'cloudflare:sockets';\n\nexport default {\n  async fetch(req, env) {\n    const gopherAddr = \"gopher.floodgap.com:70\";\n\n    const url = new URL(req.url);\n    const useProxy = url.searchParams.has(\"use_proxy\");\n\n    try {\n      const socket = useProxy ? env.proxy.connect(gopherAddr) : connect(gopherAddr);\n\n      // Write data to the socket. Specifically the \"selector\" followed by CR+LF.\n      const writer = socket.writable.getWriter()\n      const encoder = new TextEncoder();\n      const encoded = encoder.encode(url.pathname + \"\\r\\n\");\n      await writer.write(encoded);\n\n      // The Gopher server should now return a response to us and close the connection.\n      // So start reading from the socket.\n      const decoder = new TextDecoder();\n\n      let gopherResponse = \"\";\n      for await (const chunk of socket.readable) {\n        gopherResponse += decoder.decode(chunk, { stream: true });\n      }\n      gopherResponse += decoder.decode();\n\n      console.log(\"Read \", gopherResponse.length, \" from Gopher server\");\n\n      return new Response(gopherResponse, { headers: { \"Content-Type\": \"text/plain\" } });\n    } catch (error) {\n      return new Response(\"Socket connection failed: \" + error, { status: 500 });\n    }\n  }\n};\n"
  },
  {
    "path": "samples/unit-tests/config.capnp",
    "content": "# This is an example configuration for running unit tests.\n#\n# (If you're not already familiar with the basic config format, check out the hello world example\n# first.)\n#\n# Most projects will probably use some sort of script or tooling to auto-generate this\n# configuration (such as Wrangler), and instead focus on the contents of the JavaScript file.\n#\n# To run the tests, do:\n#\n#     workerd test config.capnp\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    # Define the service to be tested.\n    (name = \"main\", worker = .testWorker),\n\n    # Not required, but we can redefine the special \"internet\" service so that it disallows any\n    # outgoing connections. This prohibits the test from talking to the network.\n    (name = \"internet\", network = (allow = []))\n  ],\n\n  # For running tests, we do not need to define any sockets, since tests do not accept incoming\n  # connections.\n);\n\nconst testWorker :Workerd.Worker = (\n  # Just a regular old worker definition.\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\")\n  ],\n  compatibilityDate = \"2023-02-28\",\n);\n"
  },
  {
    "path": "samples/unit-tests/worker.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Exporting a test is like exporting a `fetch()` handler.\n//\n// It's common to want to write multiple tests in one file. We can export each\n// test under a different name. (BTW you can also export multiple `fetch()`\n// handlers this way! But that's less commonly-used.)\n\nexport let testStrings = {\n  async test(ctrl, env, ctx) {\n    if (\"foo\" + \"bar\" != \"foobar\") {\n      throw new Error(\"strings are broken!\");\n    }\n  }\n};\n\nexport let testMath = {\n  async test(ctrl, env, ctx) {\n    if (1 + 1 != 2) {\n      throw new Error(\"math is broken!\");\n    }\n  }\n};\n"
  },
  {
    "path": "samples/web-streams/README.md",
    "content": "# Web Streams Example\n\nDemonstrates Web Streams API features including custom ReadableStream, TransformStream,\nand byte streams.\n\n## Endpoints\n\n- `/sync` - Default stream with uppercase transform (synchronous)\n- `/async` - Default stream with uppercase transform (with delays)\n- `/bytes/sync` - Byte stream, no transform (synchronous)\n- `/bytes/async` - Byte stream, no transform (with delays)\n\n## Running\n\n```sh\n$ bazel run //src/workerd/server:workerd -- serve $(pwd)/samples/web-streams/config.capnp\n```\n\nOr with a local workerd binary:\n\n```sh\n$ ./workerd serve config.capnp\n```\n\n## Testing\n\n```sh\n$ curl http://localhost:8080/sync\n$ curl http://localhost:8080/async\n$ curl http://localhost:8080/bytes/sync\n$ curl http://localhost:8080/bytes/async\n```\n"
  },
  {
    "path": "samples/web-streams/config.capnp",
    "content": "# Copyright (c) 2017-2026 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\nconst webStreamsExample :Workerd.Config = (\n  services = [ (name = \"main\", worker = .webStreams) ],\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\nconst webStreams :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\"),\n    (name = \"streams-util\", esModule = embed \"streams-util.js\")\n  ],\n  compatibilityDate = \"2025-12-31\",\n);\n"
  },
  {
    "path": "samples/web-streams/streams-util.js",
    "content": "// Copyright (c) 2017-2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Future work: Additional stream patterns to demonstrate\n// - Tee: Split a stream with readable.tee()\n// - Chained transforms: Multiple TransformStreams piped together\n// - Built-in streams: CompressionStream, TextEncoderStream/TextDecoderStream\n// - Request body consumption: Pipe incoming POST body through transforms\n// - BYOB reading: Demonstrate \"bring your own buffer\" with byte streams\n\n// Sample words for generating lorem ipsum-style text\nconst WORDS = [\n  \"lorem\", \"ipsum\", \"dolor\", \"sit\", \"amet\", \"consectetur\", \"adipiscing\", \"elit\",\n  \"sed\", \"do\", \"eiusmod\", \"tempor\", \"incididunt\", \"ut\", \"labore\", \"et\", \"dolore\",\n  \"magna\", \"aliqua\", \"enim\", \"ad\", \"minim\", \"veniam\", \"quis\", \"nostrud\",\n  \"exercitation\", \"ullamco\", \"laboris\", \"nisi\", \"aliquip\", \"ex\", \"ea\", \"commodo\",\n  \"consequat\", \"duis\", \"aute\", \"irure\", \"in\", \"reprehenderit\", \"voluptate\",\n  \"velit\", \"esse\", \"cillum\", \"fugiat\", \"nulla\", \"pariatur\", \"excepteur\", \"sint\",\n  \"occaecat\", \"cupidatat\", \"non\", \"proident\", \"sunt\", \"culpa\", \"qui\", \"officia\",\n  \"deserunt\", \"mollit\", \"anim\", \"id\", \"est\", \"laborum\"\n];\n\nconst enc = new TextEncoder();\n\nfunction generateChunk() {\n  const wordsPerChunk = 50 + Math.floor(Math.random() * 50);\n  const words = [];\n  for (let i = 0; i < wordsPerChunk; i++) {\n    words.push(WORDS[Math.floor(Math.random() * WORDS.length)]);\n  }\n  return enc.encode(words.join(\" \") + \"\\n\\n\");\n}\n\n// Creates a ReadableStream that generates random lorem ipsum-style text synchronously\nexport function createSyncLoremStream(numChunks) {\n  let chunksRemaining = numChunks;\n\n  return new ReadableStream({\n    pull(controller) {\n      if (chunksRemaining <= 0) {\n        controller.close();\n        return;\n      }\n      controller.enqueue(generateChunk());\n      chunksRemaining--;\n    }\n  }, { highWaterMark: 16 });\n}\n\n// Creates a ReadableStream that generates random lorem ipsum-style text asynchronously\nexport function createAsyncLoremStream(numChunks) {\n  let chunksRemaining = numChunks;\n\n  return new ReadableStream({\n    async pull(controller) {\n      if (chunksRemaining <= 0) {\n        controller.close();\n        return;\n      }\n      await scheduler.wait(10);\n      controller.enqueue(generateChunk());\n      chunksRemaining--;\n    }\n  }, { highWaterMark: 16 });\n}\n\n// Creates a byte ReadableStream that generates random lorem ipsum-style text synchronously\nexport function createSyncLoremByteStream(numChunks) {\n  let chunksRemaining = numChunks;\n\n  return new ReadableStream({\n    type: \"bytes\",\n    pull(controller) {\n      if (chunksRemaining <= 0) {\n        controller.close();\n        return;\n      }\n      controller.enqueue(generateChunk());\n      chunksRemaining--;\n    }\n  }, { highWaterMark: 16 * 1024 });\n}\n\n// Creates a byte ReadableStream that generates random lorem ipsum-style text asynchronously\nexport function createAsyncLoremByteStream(numChunks) {\n  let chunksRemaining = numChunks;\n\n  return new ReadableStream({\n    type: \"bytes\",\n    async pull(controller) {\n      if (chunksRemaining <= 0) {\n        controller.close();\n        return;\n      }\n      await scheduler.wait(10);\n      controller.enqueue(generateChunk());\n      chunksRemaining--;\n    }\n  }, { highWaterMark: 16 * 1024 });\n}\n\n// Creates a TransformStream that converts text to uppercase synchronously\nexport function createSyncUppercaseTransform() {\n  const decoder = new TextDecoder();\n  const encoder = new TextEncoder();\n\n  return new TransformStream({\n    transform(chunk, controller) {\n      const text = decoder.decode(chunk, { stream: true });\n      controller.enqueue(encoder.encode(text.toUpperCase()));\n    },\n    flush(controller) {\n      const remaining = decoder.decode();\n      if (remaining) {\n        controller.enqueue(encoder.encode(remaining.toUpperCase()));\n      }\n    }\n  });\n}\n\n// Creates a TransformStream that converts text to uppercase asynchronously\nexport function createAsyncUppercaseTransform() {\n  const decoder = new TextDecoder();\n  const encoder = new TextEncoder();\n\n  return new TransformStream({\n    async transform(chunk, controller) {\n      await scheduler.wait(10);\n      const text = decoder.decode(chunk, { stream: true });\n      controller.enqueue(encoder.encode(text.toUpperCase()));\n    },\n    async flush(controller) {\n      await scheduler.wait(10);\n      const remaining = decoder.decode();\n      if (remaining) {\n        controller.enqueue(encoder.encode(remaining.toUpperCase()));\n      }\n    }\n  });\n}\n"
  },
  {
    "path": "samples/web-streams/worker.js",
    "content": "// Copyright (c) 2017-2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport {\n  createSyncLoremStream,\n  createAsyncLoremStream,\n  createSyncLoremByteStream,\n  createAsyncLoremByteStream,\n  createSyncUppercaseTransform,\n  createAsyncUppercaseTransform\n} from \"streams-util\";\n\nexport default {\n  async fetch(request) {\n    const url = new URL(request.url);\n\n    // Byte stream variants (no transform, returned directly)\n    if (url.pathname === \"/bytes/sync\") {\n      return new Response(createSyncLoremByteStream(20), {\n        headers: { \"Content-Type\": \"text/plain; charset=utf-8\" }\n      });\n    }\n    if (url.pathname === \"/bytes/async\") {\n      return new Response(createAsyncLoremByteStream(20), {\n        headers: { \"Content-Type\": \"text/plain; charset=utf-8\" }\n      });\n    }\n\n    // Default stream variants with uppercase transform\n    let loremStream;\n    let transform;\n    if (url.pathname === \"/sync\") {\n      loremStream = createSyncLoremStream(20);\n      transform = createSyncUppercaseTransform();\n    } else if (url.pathname === \"/async\") {\n      loremStream = createAsyncLoremStream(20);\n      transform = createAsyncUppercaseTransform();\n    } else {\n      return new Response(\"Use /sync, /async, /bytes/sync, or /bytes/async\\n\", { status: 404 });\n    }\n\n    const transformedStream = loremStream.pipeThrough(transform);\n\n    return new Response(transformedStream, {\n      headers: { \"Content-Type\": \"text/plain; charset=utf-8\" }\n    });\n  }\n};\n"
  },
  {
    "path": "samples/webfs/README.md",
    "content": "# Web File System API Example\n\nTo run the example on http://localhost:8080\n\n```sh\n$ ./workerd serve config.capnp\n```\n\nTo run using bazel\n\n```sh\n$ bazel run //src/workerd/server:workerd -- serve ~/cloudflare/workerd/samples/helloworld_esm/config.capnp\n```\n\nTo create a standalone binary that can be run:\n\n```sh\n$ ./workerd compile config.capnp > helloworld\n\n$ ./helloworld\n```\n\nTo test:\n\n```sh\n% curl http://localhost:8080\nHello World\n```\n"
  },
  {
    "path": "samples/webfs/config.capnp",
    "content": "# Imports the base schema for workerd configuration files.\n\n# Refer to the comments in /src/workerd/server/workerd.capnp for more details.\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\n# A constant of type Workerd.Config defines the top-level configuration for an\n# instance of the workerd runtime. A single config file can contain multiple\n# Workerd.Config definitions and must have at least one.\nconst helloWorldExample :Workerd.Config = (\n\n  # Every workerd instance consists of a set of named services. A worker, for instance,\n  # is a type of service. Other types of services can include external servers, the\n  # ability to talk to a network, or accessing a disk directory. Here we create a single\n  # worker service. The configuration details for the worker are defined below.\n  services = [ (name = \"main\", worker = .helloWorld) ],\n\n  # Each configuration defines the sockets on which the server will listen.\n  # Here, we create a single socket that will listen on localhost port 8080, and will\n  # dispatch to the \"main\" service that we defined above.\n  sockets = [ ( name = \"http\", address = \"*:8080\", http = (), service = \"main\" ) ]\n);\n\n# The definition of the actual helloWorld worker exposed using the \"main\" service.\n# In this example the worker is implemented as an ESM module (see worker.js).\n# The compatibilityDate is required. For more details on compatibility dates see:\n#   https://developers.cloudflare.com/workers/platform/compatibility-dates/\n\nconst helloWorld :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\")\n  ],\n  compatibilityDate = \"2025-05-01\",\n  compatibilityFlags = [\"enable_web_file_system\", \"experimental\"]\n);\n"
  },
  {
    "path": "samples/webfs/worker.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nconst dir = await navigator.storage.getDirectory();\nconst bundle = await dir.getDirectoryHandle(\"tmp\");\nconst file = await bundle.getFileHandle(\"foo\", { create: true });\nconst handle = await file.createSyncAccessHandle();\n\nconst enc = new TextEncoder();\nhandle.write(enc.encode(\"Hello World\"));\nhandle.write(enc.encode(\"!!!\\n\"));\nconsole.log(handle.getSize());\nhandle.close();\n\nconst data = await file.getFile();\nconsole.log(await data.text());\n\nexport default {\n  async fetch(req, env) {\n    return new Response(\"Hello World\\n\");\n  }\n};\n"
  },
  {
    "path": "src/cloudflare/AGENTS.md",
    "content": "# src/cloudflare/\n\n## OVERVIEW\n\nTypeScript implementations of Cloudflare product APIs (AI, D1, R2, Vectorize, etc.).\n\n- The `internal/*` directory contains internal implementation details not directly exposed to user code.\n- The top-level `.ts` files are the public API entry points exposed to user code.\n\nIt is common, but not required, for top-level `.ts` files to re-export from `internal/` via `cloudflare-internal:` specifiers. This allows for a clean separation between public API surface and internal implementation details.\n\n## STRUCTURE\n\n- `<product>.ts` — Public entry point; e.g `ai.ts` for AI maps to `cloudflare:ai`\n- `internal/*.ts` — Implementation; imports runtime types from sibling `.d.ts`\n- `internal/*.d.ts` — Type declarations for C++ runtime-provided modules (no implementation)\n- `internal/tracing-helpers.ts` — Shared instrumentation (`withSpan`); imported as `cloudflare-internal:tracing-helpers`\n- `internal/test/<product>/` — Per-product test directory\n\n## TEST PATTERN\n\nEach product test directory contains:\n\n| File                                    | Purpose                                                      |\n| --------------------------------------- | ------------------------------------------------------------ |\n| `<product>-api-test.js`                 | JS test; named exports with `test()` methods + `node:assert` |\n| `<product>-api-test.wd-test`            | Cap'n Proto config wiring test worker to mock                |\n| `<product>-mock.js`                     | Mock service simulating upstream API                         |\n| `<product>-api-test.py`                 | Python variant (optional)                                    |\n| `python-<product>-api-test.wd-test`     | Python test config (optional)                                |\n| `<product>-api-instrumentation-test.js` | Tracing/instrumentation tests (optional)                     |\n\nMock wiring uses `wrapped` bindings: `moduleName = \"cloudflare-internal:<product>-api\"` with `innerBindings` pointing `fetcher` at the mock service. Shared `instrumentation-test-helper.js` lives in `internal/test/`.\n"
  },
  {
    "path": "src/cloudflare/BUILD.bazel",
    "content": "load(\"@workerd//:build/wd_ts_bundle.bzl\", \"wd_ts_bundle\")\n\nwd_ts_bundle(\n    name = \"cloudflare\",\n    eslintrc_json = \"eslint.config.mjs\",\n    import_name = \"cloudflare\",\n    internal_modules = glob(\n        [\n            \"internal/*.ts\",\n        ],\n    ),\n    modules = glob(\n        [\n            \"*.ts\",\n        ],\n    ),\n    schema_id = \"0xbcc8f57c63814006\",\n    tsconfig_json = \"tsconfig.json\",\n)\n"
  },
  {
    "path": "src/cloudflare/README.md",
    "content": "# Cloudflare specific modules\n\n## tests\n\nThis codebase includes many unit tests. To run them, do:\n\n```\nbazel test  --test_output=all //src/cloudflare/...\n```\n\nRunning just a specific module tests:\n\n```\nbazel test  --test_output=all //src/cloudflare/internal/test/aig/...\n```\n\nRunning just eslint:\n\n```\nbazel test  --test_output=all //src/cloudflare:cloudflare@eslint\n```\n\n## Code formating\n\nYou need to format your code before opening a pull request, otherwise the CI will fail:\n\n```\nprettier src/cloudflare -w\nprettier types/defines -w\n```\n"
  },
  {
    "path": "src/cloudflare/ai.ts",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport {\n  type AiOptions,\n  InferenceUpstreamError,\n  Ai,\n} from 'cloudflare-internal:ai-api';\n\nexport {\n  AiGateway,\n  AiGatewayInternalError,\n  AiGatewayLogNotFound,\n} from 'cloudflare-internal:aig-api';\n\nexport {\n  AutoRAG,\n  AutoRAGInternalError,\n  AutoRAGNotFoundError,\n  AutoRAGUnauthorizedError,\n} from 'cloudflare-internal:autorag-api';\n\nexport {\n  ToMarkdownService,\n  type ConversionResponse,\n  type SupportedFileFormat,\n  type ConversionOptions,\n  type ConversionRequestOptions,\n  type EmbeddedImageConversionOptions,\n  type ImageConversionOptions,\n  type MarkdownDocument,\n} from 'cloudflare-internal:to-markdown-api';\n"
  },
  {
    "path": "src/cloudflare/br.ts",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// This binding is managed by the browser rendering team (aka brapi)\n// https://developers.cloudflare.com/browser-rendering/\n\nexport { BrowserRendering } from 'cloudflare-internal:br-api';\n"
  },
  {
    "path": "src/cloudflare/email.ts",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// TODO: c++ built-ins do not yet support named exports\nimport { default as email } from 'cloudflare-internal:email';\nexport const { EmailMessage } = email;\n"
  },
  {
    "path": "src/cloudflare/eslint.config.mjs",
    "content": "import { baseConfig } from '../../tools/base.eslint.config.mjs';\n\nexport default baseConfig();\n"
  },
  {
    "path": "src/cloudflare/internal/ai-api.ts",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { AiGateway, type GatewayOptions } from 'cloudflare-internal:aig-api';\nimport { AutoRAG } from 'cloudflare-internal:autorag-api';\nimport {\n  ToMarkdownService,\n  type ConversionRequestOptions,\n  type ConversionResponse,\n  type MarkdownDocument,\n} from 'cloudflare-internal:to-markdown-api';\n\ntype AiSearchService = object;\n\ninterface Fetcher {\n  fetch: typeof fetch;\n  aiSearch: () => AiSearchService;\n  gateway: (gatewayId: string) => AiGateway;\n}\n\ninterface AiError {\n  internalCode: number;\n  message: string;\n  name: string;\n  description: string;\n  errors?: Array<{ code: number; message: string }>;\n}\n\nexport type SessionOptions = {\n  // Deprecated, do not use this\n  extraHeaders?: object;\n};\n\nexport type AiOptions = {\n  gateway?: GatewayOptions;\n  websocket?: boolean;\n  /** If true it will return a Response object */\n  returnRawResponse?: boolean;\n  prefix?: string;\n  extraHeaders?: object;\n  /*\n   * @deprecated this option is deprecated, do not use this\n   */\n  sessionOptions?: SessionOptions;\n};\n\nexport type AiInputReadableStream = {\n  body: ReadableStream | FormData;\n  contentType: string;\n};\n\nexport type AiModelsSearchParams = {\n  author?: string;\n  hide_experimental?: boolean;\n  page?: number;\n  per_page?: number;\n  search?: string;\n  source?: number;\n  task?: string;\n};\n\nexport type AiModelsSearchObject = {\n  id: string;\n  source: number;\n  name: string;\n  description: string;\n  task: {\n    id: string;\n    name: string;\n    description: string;\n  };\n  tags: string[];\n  properties: {\n    property_id: string;\n    value: string;\n  }[];\n};\n\nexport class InferenceUpstreamError extends Error {\n  constructor(message: string, name = 'InferenceUpstreamError') {\n    super(message);\n    this.name = name;\n  }\n}\n\nexport class AiInternalError extends Error {\n  constructor(message: string, name = 'AiInternalError') {\n    super(message);\n    this.name = name;\n  }\n}\n\nfunction isReadableStream(obj: unknown): obj is ReadableStream {\n  return obj instanceof ReadableStream;\n}\n\nfunction isFormData(obj: unknown): obj is FormData {\n  return obj instanceof FormData;\n}\n\n/**\n * Find keys in inputs that have a ReadableStream\n * */\nfunction findReadableStreamKeys(\n  inputs: Record<string, unknown>\n): Array<string> {\n  const readableStreamKeys: Array<string> = [];\n\n  for (const [key, value] of Object.entries(inputs)) {\n    // Check if value has a body property that's a ReadableStream\n    const hasReadableStreamBody =\n      value &&\n      typeof value === 'object' &&\n      'body' in value &&\n      (isReadableStream(value.body) || isFormData(value.body));\n\n    if (hasReadableStreamBody || isReadableStream(value) || isFormData(value)) {\n      readableStreamKeys.push(key);\n    }\n  }\n\n  return readableStreamKeys;\n}\n\nexport class Ai {\n  #fetcher: Fetcher;\n\n  /*\n   * @deprecated this option is deprecated, do not use this\n   */\n  // @ts-expect-error: deprecated var\n  // eslint-disable-next-line no-unused-private-class-members\n  #logs: Array<string> = [];\n  #options: AiOptions = {};\n  #endpointURL = 'https://workers-binding.ai';\n  lastRequestId: string | null = null;\n  aiGatewayLogId: string | null = null;\n  lastRequestHttpStatusCode: number | null = null;\n  lastRequestInternalStatusCode: number | null = null;\n\n  constructor(fetcher: Fetcher) {\n    this.#fetcher = fetcher;\n  }\n\n  async fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n    return this.#fetcher.fetch(input, init);\n  }\n\n  /**\n   * Generate fetch call for JSON inputs\n   * */\n  async #generateFetch(\n    inputs: object,\n    options: AiOptions,\n    model: string\n  ): Promise<Response> {\n    // Treat inputs as regular JS objects\n    const body = JSON.stringify({\n      inputs,\n      options,\n    });\n\n    const fetchOptions = {\n      method: 'POST',\n      body: body,\n      headers: {\n        ...this.#options.sessionOptions?.extraHeaders,\n        ...this.#options.extraHeaders,\n        'content-type': 'application/json',\n        'cf-consn-sdk-version': '2.0.0',\n        'cf-consn-model-id': `${this.#options.prefix ? `${this.#options.prefix}:` : ''}${model}`,\n      },\n    };\n\n    let endpointUrl = `${this.#endpointURL}/run?version=3`;\n    if (options.gateway?.id) {\n      endpointUrl = `${this.#endpointURL}/ai-gateway/run?version=3`;\n    }\n\n    return await this.#fetcher.fetch(endpointUrl, fetchOptions);\n  }\n\n  /**\n   * Generate fetch call for inputs with ReadableStream\n   * */\n  async #generateStreamFetch(\n    inputs: Record<string, string | AiInputReadableStream>,\n    options: AiOptions,\n    model: string,\n    streamKeys: string[]\n  ): Promise<Response> {\n    const streamKey = streamKeys[0] ?? '';\n    const stream = streamKey ? inputs[streamKey] : null;\n    const body = (stream as AiInputReadableStream).body;\n    const contentType = (stream as AiInputReadableStream).contentType;\n\n    if (options.gateway?.id) {\n      throw new AiInternalError(\n        'AI Gateway does not support ReadableStreams yet.'\n      );\n    }\n\n    // Make sure user has supplied the Content-Type\n    // This allows AI binding to treat the ReadableStream correctly\n    if (!contentType) {\n      throw new AiInternalError(\n        'Content-Type is required with ReadableStream inputs'\n      );\n    }\n\n    // Pass single ReadableStream in request body\n    const fetchOptions = {\n      method: 'POST',\n      body: body,\n      headers: {\n        ...this.#options.sessionOptions?.extraHeaders,\n        ...this.#options.extraHeaders,\n        'content-type': contentType,\n        'cf-consn-sdk-version': '2.0.0',\n        'cf-consn-model-id': `${this.#options.prefix ? `${this.#options.prefix}:` : ''}${model}`,\n      },\n    };\n\n    // Fetch the additional input params\n    const { [streamKey]: streamInput, ...userInputs } = inputs;\n\n    // Construct query params\n    // Append inputs with ai.run options that are passed to the inference request\n    const query = {\n      ...options,\n      version: '3',\n      userInputs: JSON.stringify({ ...userInputs }),\n    };\n    const aiEndpoint = new URL(`${this.#endpointURL}/run`);\n    for (const [key, value] of Object.entries(query)) {\n      aiEndpoint.searchParams.set(key, value as string);\n    }\n\n    return await this.#fetcher.fetch(aiEndpoint, fetchOptions);\n  }\n\n  /**\n   * Generate call to open a websocket connection\n   * */\n  async #generateWebsocketFetch(\n    inputs: object,\n    options: AiOptions,\n    model: string\n  ): Promise<Response> {\n    // Treat inputs as regular JS objects\n    const body = JSON.stringify({\n      inputs,\n      options,\n    });\n\n    const fetchOptions = {\n      headers: {\n        ...this.#options.sessionOptions?.extraHeaders,\n        ...this.#options.extraHeaders,\n        'cf-consn-sdk-version': '2.0.0',\n        'cf-consn-model-id': `${this.#options.prefix ? `${this.#options.prefix}:` : ''}${model}`,\n        Upgrade: 'websocket',\n      },\n    };\n\n    const aiEndpoint = new URL(`${this.#endpointURL}/run`);\n    aiEndpoint.searchParams.set('version', '3');\n    aiEndpoint.searchParams.set('body', body);\n\n    return await this.#fetcher.fetch(aiEndpoint, fetchOptions);\n  }\n\n  async run(\n    model: string,\n    inputs: Record<string, string | AiInputReadableStream>,\n    options: AiOptions = {}\n  ): Promise<Response | ReadableStream<Uint8Array> | object | null> {\n    this.#options = options;\n    this.lastRequestId = '';\n\n    // This removes some unwanted options from getting sent in the body\n    const cleanedOptions = (({\n      prefix,\n      extraHeaders,\n      sessionOptions,\n      ...object\n    }): object => object)(this.#options);\n\n    let res: Response;\n\n    if (this.#options.websocket) {\n      res = await this.#generateWebsocketFetch(inputs, options, model);\n    } else {\n      /**\n       * Inputs that contain a ReadableStream which will be sent directly to\n       * the fetcher object along with other keys parsed as a query parameters\n       * */\n      const streamKeys = findReadableStreamKeys(inputs);\n\n      if (streamKeys.length === 0) {\n        res = await this.#generateFetch(inputs, cleanedOptions, model);\n      } else if (streamKeys.length > 1) {\n        throw new AiInternalError(\n          `Multiple ReadableStreams are not supported. Found streams in keys: [${streamKeys.join(', ')}]`\n        );\n      } else {\n        res = await this.#generateStreamFetch(\n          inputs,\n          options,\n          model,\n          streamKeys\n        );\n      }\n    }\n\n    this.lastRequestId = res.headers.get('cf-ai-req-id');\n    this.aiGatewayLogId = res.headers.get('cf-aig-log-id');\n    this.lastRequestHttpStatusCode = res.status;\n\n    if (this.#options.returnRawResponse || this.#options.websocket) {\n      return res;\n    }\n\n    if (!res.ok || !res.body) {\n      throw await this._parseError(res);\n    }\n\n    const contentType = res.headers.get('content-type');\n    if (contentType === 'application/json') {\n      return (await res.json()) as object;\n    }\n\n    return res.body;\n  }\n\n  /*\n   * @deprecated this method is deprecated, do not use this\n   */\n  getLogs(): string[] {\n    return [];\n  }\n\n  // TODO(soon): Can we use the # syntax here?\n  // eslint-disable-next-line no-restricted-syntax\n  private async _parseError(res: Response): Promise<InferenceUpstreamError> {\n    const content = await res.text();\n\n    try {\n      const parsedContent = JSON.parse(content) as AiError;\n      if (parsedContent.internalCode) {\n        this.lastRequestInternalStatusCode = parsedContent.internalCode;\n        return new InferenceUpstreamError(\n          `${parsedContent.internalCode}: ${parsedContent.description}`,\n          parsedContent.name\n        );\n      } else if (\n        parsedContent.errors &&\n        parsedContent.errors.length > 0 &&\n        parsedContent.errors[0]\n      ) {\n        return new InferenceUpstreamError(\n          `${parsedContent.errors[0].code}: ${parsedContent.errors[0].message}`\n        );\n      } else {\n        return new InferenceUpstreamError(content);\n      }\n    } catch {\n      return new InferenceUpstreamError(content);\n    }\n  }\n\n  async models(\n    params: AiModelsSearchParams = {}\n  ): Promise<AiModelsSearchObject[]> {\n    const url = new URL(`${this.#endpointURL}/ai-api/models/search`);\n\n    for (const [key, value] of Object.entries(params)) {\n      url.searchParams.set(key, value.toString());\n    }\n\n    const res = await this.#fetcher.fetch(url, { method: 'GET' });\n\n    switch (res.status) {\n      case 200: {\n        const data = (await res.json()) as { result: AiModelsSearchObject[] };\n        return data.result;\n      }\n      default: {\n        const data = (await res.json()) as { errors: { message: string }[] };\n\n        throw new AiInternalError(data.errors[0]?.message || 'Internal Error');\n      }\n    }\n  }\n\n  toMarkdown(): ToMarkdownService;\n  async toMarkdown(\n    files: MarkdownDocument[],\n    options?: ConversionRequestOptions\n  ): Promise<ConversionResponse[]>;\n  async toMarkdown(\n    files: MarkdownDocument,\n    options?: ConversionRequestOptions\n  ): Promise<ConversionResponse>;\n  toMarkdown(\n    files?: MarkdownDocument | MarkdownDocument[],\n    options?: ConversionRequestOptions\n  ): ToMarkdownService | Promise<ConversionResponse | ConversionResponse[]> {\n    const service = new ToMarkdownService(this.#fetcher);\n\n    if (arguments.length < 1 || !files) return service;\n\n    // NOTE(nunopereira): assuming type A = { name: string; blob: Blob }, 'files' here can be of type A | A[].\n    // However, 'service.transform' has no overload that accepts that union, rather it has one overload for each variant.\n    // We know the type of 'files' satisfies whatever type 'service.transform' expects and\n    // instead it's Typescript that is failing to narrow the type, so just ignore this error.\n    // @ts-expect-error unable to narrow type of file to either { name: string; blob: Blob } or { name: string; blob: Blob }[]\n    return service.transform(files, options);\n  }\n\n  gateway(gatewayId: string, options?: { beta?: boolean }): AiGateway {\n    if (options?.beta === true) {\n      return this.#fetcher.gateway(gatewayId);\n    }\n    return new AiGateway(this.#fetcher, gatewayId);\n  }\n\n  autorag(autoragId?: string): AutoRAG {\n    return new AutoRAG(this.#fetcher, autoragId);\n  }\n\n  aiSearch(): AiSearchService {\n    return this.#fetcher.aiSearch();\n  }\n}\n\nexport default function makeBinding(env: { fetcher: Fetcher }): Ai {\n  return new Ai(env.fetcher);\n}\n"
  },
  {
    "path": "src/cloudflare/internal/aig-api.ts",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ninterface Fetcher {\n  fetch: typeof fetch;\n}\n\ntype GatewayRetries = {\n  maxAttempts?: 1 | 2 | 3 | 4 | 5;\n  retryDelayMs?: number;\n  backoff?: 'constant' | 'linear' | 'exponential';\n};\n\nexport type GatewayOptions = {\n  id: string;\n  cacheKey?: string;\n  cacheTtl?: number;\n  skipCache?: boolean;\n  metadata?: Record<string, number | string | boolean | null | bigint>;\n  collectLog?: boolean;\n  eventId?: string;\n  requestTimeoutMs?: number;\n  retries?: GatewayRetries;\n};\n\nexport type UniversalGatewayOptions = Omit<GatewayOptions, 'id'> & {\n  /**\n   ** @deprecated\n   */\n  id?: string;\n};\n\nexport type AiGatewayPatchLog = {\n  score?: number | null;\n  feedback?: -1 | 1 | null;\n  metadata?: Record<string, number | string | boolean | null | bigint> | null;\n};\n\nexport type AiGatewayLog = {\n  id: string;\n  provider: string;\n  model: string;\n  model_type?: string;\n  path: string;\n  duration: number;\n  request_type?: string;\n  request_content_type?: string;\n  status_code: number;\n  response_content_type?: string;\n  success: boolean;\n  cached: boolean;\n  tokens_in?: number;\n  tokens_out?: number;\n  metadata?: Record<string, number | string | boolean | null | bigint>;\n  step?: number;\n  cost?: number;\n  custom_cost?: boolean;\n  request_size: number;\n  request_head?: string;\n  request_head_complete: boolean;\n  response_size: number;\n  response_head?: string;\n  response_head_complete: boolean;\n  created_at: Date;\n};\n\nexport type AIGatewayProviders =\n  | 'workers-ai'\n  | 'anthropic'\n  | 'aws-bedrock'\n  | 'azure-openai'\n  | 'google-vertex-ai'\n  | 'huggingface'\n  | 'openai'\n  | 'perplexity-ai'\n  | 'replicate'\n  | 'groq'\n  | 'cohere'\n  | 'google-ai-studio'\n  | 'mistral'\n  | 'grok'\n  | 'openrouter'\n  | 'deepseek'\n  | 'cerebras'\n  | 'cartesia'\n  | 'elevenlabs'\n  | 'adobe-firefly';\n\nexport type AIGatewayHeaders = {\n  'cf-aig-metadata':\n    | Record<string, number | string | boolean | null | bigint>\n    | string;\n  'cf-aig-custom-cost':\n    | { per_token_in?: number; per_token_out?: number }\n    | { total_cost?: number }\n    | string;\n  'cf-aig-cache-ttl': number | string;\n  'cf-aig-skip-cache': boolean | string;\n  'cf-aig-cache-key': string;\n  'cf-aig-event-id': string;\n  'cf-aig-request-timeout': number | string;\n  'cf-aig-max-attempts': number | string;\n  'cf-aig-retry-delay': number | string;\n  'cf-aig-backoff': string;\n  'cf-aig-collect-log': boolean | string;\n  Authorization: string;\n  'Content-Type': string;\n  [key: string]: string | number | boolean | object;\n};\n\nexport type AIGatewayUniversalRequest = {\n  provider: AIGatewayProviders | string; // eslint-disable-line\n  endpoint: string;\n  headers: Partial<AIGatewayHeaders>;\n  query: unknown;\n};\n\nexport class AiGatewayInternalError extends Error {\n  constructor(message: string) {\n    super(message);\n    this.name = 'AiGatewayInternalError';\n  }\n}\n\nexport class AiGatewayLogNotFound extends Error {\n  constructor(message: string) {\n    super(message);\n    this.name = 'AiGatewayLogNotFound';\n  }\n}\n\nasync function parseError(\n  res: Response,\n  defaultMsg = 'Internal Error',\n  errorCls = AiGatewayInternalError\n): Promise<Error> {\n  const content = await res.text();\n\n  try {\n    const parsedContent = JSON.parse(content) as {\n      errors: { message: string }[];\n    };\n\n    return new errorCls(parsedContent.errors.at(0)?.message || defaultMsg);\n  } catch {\n    return new AiGatewayInternalError(content);\n  }\n}\n\nexport class AiGateway {\n  readonly #fetcher: Fetcher;\n  readonly #gatewayId: string;\n\n  constructor(fetcher: Fetcher, gatewayId: string) {\n    this.#fetcher = fetcher;\n    this.#gatewayId = gatewayId;\n  }\n\n  // eslint-disable-next-line\n  async getUrl(provider?: AIGatewayProviders | string): Promise<string> {\n    const res = await this.#fetcher.fetch(\n      `https://workers-binding.ai/ai-gateway/gateways/${this.#gatewayId}/url/${provider ?? 'universal'}`,\n      { method: 'GET' }\n    );\n\n    if (!res.ok) {\n      throw await parseError(res);\n    }\n\n    const data = (await res.json()) as { result: { url: string } };\n\n    return data.result.url;\n  }\n\n  async getLog(logId: string): Promise<AiGatewayLog> {\n    const res = await this.#fetcher.fetch(\n      `https://workers-binding.ai/ai-gateway/gateways/${this.#gatewayId}/logs/${logId}`,\n      {\n        method: 'GET',\n      }\n    );\n\n    switch (res.status) {\n      case 200: {\n        const data = (await res.json()) as { result: AiGatewayLog };\n\n        return {\n          ...data.result,\n          created_at: new Date(data.result.created_at),\n        };\n      }\n      case 404: {\n        throw await parseError(res, 'Log Not Found', AiGatewayLogNotFound);\n      }\n      default: {\n        throw await parseError(res);\n      }\n    }\n  }\n\n  async patchLog(logId: string, data: AiGatewayPatchLog): Promise<void> {\n    const res = await this.#fetcher.fetch(\n      `https://workers-binding.ai/ai-gateway/gateways/${this.#gatewayId}/logs/${logId}`,\n      {\n        method: 'PATCH',\n        body: JSON.stringify(data),\n        headers: {\n          'content-type': 'application/json',\n        },\n      }\n    );\n\n    switch (res.status) {\n      case 200: {\n        return;\n      }\n      case 404: {\n        throw await parseError(res, 'Log Not Found', AiGatewayLogNotFound);\n      }\n      default: {\n        throw await parseError(res);\n      }\n    }\n  }\n\n  run(\n    data: AIGatewayUniversalRequest | AIGatewayUniversalRequest[],\n    options?: {\n      gateway?: UniversalGatewayOptions;\n      extraHeaders?: Record<string, string>;\n    }\n  ): Promise<Response> {\n    const input = Array.isArray(data) ? data : [data];\n\n    const headers = this.#getHeadersFromOptions(\n      options?.gateway,\n      options?.extraHeaders\n    );\n\n    // Convert header values to string\n    for (const req of input) {\n      for (const [k, v] of Object.entries(req.headers)) {\n        if (typeof v === 'number' || typeof v === 'boolean') {\n          req.headers[k] = v.toString();\n          // eslint-disable-next-line\n        } else if (typeof v === 'object' && v != null) {\n          req.headers[k] = JSON.stringify(v);\n        }\n      }\n    }\n\n    return this.#fetcher.fetch(\n      `https://workers-binding.ai/ai-gateway/universal/run/${this.#gatewayId}`,\n      {\n        method: 'POST',\n        body: JSON.stringify(input),\n        headers: headers,\n      }\n    );\n  }\n\n  #getHeadersFromOptions(\n    options?: UniversalGatewayOptions,\n    extraHeaders?: Record<string, string>\n  ): Headers {\n    const headers = new Headers();\n    headers.set('content-type', 'application/json');\n\n    if (options) {\n      if (options.skipCache !== undefined) {\n        headers.set('cf-aig-skip-cache', options.skipCache ? 'true' : 'false');\n      }\n\n      if (options.cacheTtl) {\n        headers.set('cf-aig-cache-ttl', options.cacheTtl.toString());\n      }\n\n      if (options.metadata) {\n        headers.set('cf-aig-metadata', JSON.stringify(options.metadata));\n      }\n\n      if (options.cacheKey) {\n        headers.set('cf-aig-cache-key', options.cacheKey);\n      }\n\n      if (options.collectLog !== undefined) {\n        headers.set(\n          'cf-aig-collect-log',\n          options.collectLog ? 'true' : 'false'\n        );\n      }\n\n      if (options.eventId !== undefined) {\n        headers.set('cf-aig-event-id', options.eventId);\n      }\n\n      if (options.requestTimeoutMs !== undefined) {\n        headers.set(\n          'cf-aig-request-timeout',\n          options.requestTimeoutMs.toString()\n        );\n      }\n\n      if (options.retries !== undefined) {\n        if (options.retries.maxAttempts !== undefined) {\n          headers.set(\n            'cf-aig-max-attempts',\n            options.retries.maxAttempts.toString()\n          );\n        }\n        if (options.retries.retryDelayMs !== undefined) {\n          headers.set(\n            'cf-aig-retry-delay',\n            options.retries.retryDelayMs.toString()\n          );\n        }\n        if (options.retries.backoff !== undefined) {\n          headers.set('cf-aig-backoff', options.retries.backoff);\n        }\n      }\n    }\n\n    if (extraHeaders) {\n      for (const [key, value] of Object.entries(extraHeaders)) {\n        headers.set(key, value);\n      }\n    }\n\n    return headers;\n  }\n}\n"
  },
  {
    "path": "src/cloudflare/internal/autorag-api.ts",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ninterface Fetcher {\n  fetch: typeof fetch;\n}\n\nexport class AutoRAGInternalError extends Error {\n  constructor(message: string, name = 'AutoRAGInternalError') {\n    super(message);\n    this.name = name;\n  }\n}\n\nexport class AutoRAGNotFoundError extends Error {\n  constructor(message: string, name = 'AutoRAGNotFoundError') {\n    super(message);\n    this.name = name;\n  }\n}\n\nexport class AutoRAGUnauthorizedError extends Error {\n  constructor(message: string, name = 'AutoRAGUnauthorizedError') {\n    super(message);\n    this.name = name;\n  }\n}\n\nexport class AutoRAGNameNotSetError extends Error {\n  constructor(message: string, name = 'AutoRAGNameNotSetError') {\n    super(message);\n    this.name = name;\n  }\n}\n\nasync function parseError(\n  res: Response,\n  defaultMsg = 'Internal Error',\n  errorCls = AutoRAGInternalError\n): Promise<Error> {\n  const content = await res.text();\n\n  try {\n    const parsedContent = JSON.parse(content) as {\n      errors: { message: string }[];\n    };\n\n    return new errorCls(parsedContent.errors.at(0)?.message || defaultMsg);\n  } catch {\n    return new AutoRAGInternalError(content);\n  }\n}\n\nexport type ComparisonFilter = {\n  key: string;\n  type: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte';\n  value: string | number | boolean;\n};\n\nexport type CompoundFilter = {\n  type: 'and' | 'or';\n  filters: ComparisonFilter[];\n};\n\nexport type AutoRagSearchRequest = {\n  query: string;\n  filters?: CompoundFilter | ComparisonFilter;\n  max_num_results?: number;\n  ranking_options?: {\n    ranker?: string;\n    score_threshold?: number;\n  };\n  reranking?: {\n    enabled?: boolean;\n    model?: string;\n  };\n  rewrite_query?: boolean;\n};\n\nexport type AutoRagAiSearchRequest = AutoRagSearchRequest & {\n  stream?: boolean;\n  system_prompt?: string;\n};\nexport type AutoRagAiSearchRequestStreaming = Omit<\n  AutoRagAiSearchRequest,\n  'stream'\n> & {\n  stream: true;\n};\n\nexport type AutoRagSearchResponse = {\n  object: 'vector_store.search_results.page';\n  search_query: string;\n  data: {\n    file_id: string;\n    filename: string;\n    score: number;\n    attributes: Record<string, string | number | boolean | null>;\n    content: {\n      type: 'text';\n      text: string;\n    }[];\n  }[];\n  has_more: boolean;\n  next_page: string | null;\n};\n\nexport type AutoRagListResponse = {\n  id: string;\n  enable: boolean;\n  type: string;\n  source: string;\n  vectorize_name: string;\n  paused: boolean;\n  status: string;\n}[];\n\nexport type AutoRagAiSearchResponse = AutoRagSearchResponse & {\n  response: string;\n};\n\nexport class AutoRAG {\n  readonly #fetcher: Fetcher;\n  readonly #autoragId: string | null;\n\n  constructor(fetcher: Fetcher, autoragId?: string) {\n    this.#fetcher = fetcher;\n    this.#autoragId = autoragId || null;\n  }\n\n  async list(): Promise<AutoRagListResponse> {\n    const res = await this.#fetcher.fetch(\n      `https://workers-binding.ai/autorag/rags`,\n      {\n        method: 'GET',\n        headers: {\n          'content-type': 'application/json',\n        },\n      }\n    );\n\n    if (!res.ok) {\n      throw await parseError(res);\n    }\n\n    const data = (await res.json()) as { result: AutoRagListResponse };\n\n    return data.result;\n  }\n\n  async search(params: AutoRagSearchRequest): Promise<AutoRagSearchResponse> {\n    if (!this.#autoragId) {\n      throw new AutoRAGNameNotSetError('AutoRAG name not defined');\n    }\n\n    const res = await this.#fetcher.fetch(\n      `https://workers-binding.ai/autorag/rags/${this.#autoragId}/search`,\n      {\n        method: 'POST',\n        body: JSON.stringify(params),\n        headers: {\n          'content-type': 'application/json',\n        },\n      }\n    );\n\n    if (!res.ok) {\n      if (res.status === 401) {\n        throw await parseError(\n          res,\n          \"You don't have access to this AutoRAG\",\n          AutoRAGUnauthorizedError\n        );\n      } else if (res.status === 404) {\n        throw await parseError(res, 'AutoRAG not found', AutoRAGNotFoundError);\n      }\n      throw await parseError(res);\n    }\n\n    const data = (await res.json()) as { result: AutoRagSearchResponse };\n\n    return data.result;\n  }\n\n  async aiSearch(params: AutoRagAiSearchRequestStreaming): Promise<Response>;\n  async aiSearch(\n    params: AutoRagAiSearchRequest\n  ): Promise<AutoRagAiSearchResponse>;\n  async aiSearch(\n    params: AutoRagAiSearchRequest\n  ): Promise<AutoRagAiSearchResponse | Response> {\n    if (!this.#autoragId) {\n      throw new AutoRAGNameNotSetError('AutoRAG name not defined');\n    }\n\n    const res = await this.#fetcher.fetch(\n      `https://workers-binding.ai/autorag/rags/${this.#autoragId}/ai-search`,\n      {\n        method: 'POST',\n        body: JSON.stringify(params),\n        headers: {\n          'content-type': 'application/json',\n        },\n      }\n    );\n\n    if (!res.ok) {\n      if (res.status === 401) {\n        throw await parseError(\n          res,\n          \"You don't have access to this AutoRAG\",\n          AutoRAGUnauthorizedError\n        );\n      } else if (res.status === 404) {\n        throw await parseError(res, 'AutoRAG not found', AutoRAGNotFoundError);\n      }\n      throw await parseError(res);\n    }\n\n    if (params.stream === true) {\n      return res;\n    }\n\n    const data = (await res.json()) as { result: AutoRagAiSearchResponse };\n\n    return data.result;\n  }\n}\n"
  },
  {
    "path": "src/cloudflare/internal/base64.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport function encodeArray(\n  input: ArrayBufferLike | ArrayBufferView\n): ArrayBuffer;\nexport function encodeArrayToString(\n  input: ArrayBufferLike | ArrayBufferView\n): string;\nexport function decodeArray(\n  input: ArrayBufferLike | ArrayBufferView\n): ArrayBuffer;\n"
  },
  {
    "path": "src/cloudflare/internal/br-api.ts",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ninterface Fetcher {\n  fetch: typeof fetch;\n}\n\nexport class BrowserRendering {\n  // TODO(soon): Can we use the # syntax here?\n  // eslint-disable-next-line no-restricted-syntax\n  private readonly fetcher: Fetcher;\n\n  constructor(fetcher: Fetcher) {\n    this.fetcher = fetcher;\n  }\n\n  async fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n    return this.fetcher.fetch(input, init);\n  }\n}\n\nexport default function makeBinding(env: {\n  fetcher: Fetcher;\n}): BrowserRendering {\n  return new BrowserRendering(env.fetcher);\n}\n"
  },
  {
    "path": "src/cloudflare/internal/d1-api.ts",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { withSpan } from 'cloudflare-internal:tracing-helpers';\nimport type { Span } from './tracing';\n\ninterface D1Meta {\n  duration: number;\n  size_after: number;\n  rows_read: number;\n  rows_written: number;\n  last_row_id: number;\n  changed_db: boolean;\n  changes: number;\n\n  /**\n   * The region of the database instance that executed the query.\n   */\n  served_by_region?: string;\n\n  /**\n   * True if-and-only-if the database instance that executed the query was the primary.\n   */\n  served_by_primary?: boolean;\n\n  timings?: {\n    /**\n     * The duration of the SQL query execution by the database instance. It doesn't include any network time.\n     */\n    sql_duration_ms: number;\n  };\n\n  /**\n   * Number of total attempts to execute the query, due to automatic retries.\n   * Note: All other fields in the response like `timings` only apply to the last attempt.\n   */\n  total_attempts?: number;\n}\n\ninterface Fetcher {\n  fetch: typeof fetch;\n}\n\ntype D1Response = {\n  success: true;\n  meta: D1Meta & Record<string, unknown>;\n  error?: never;\n};\n\ntype D1Result<T = unknown> = D1Response & {\n  results: T[];\n};\n\ntype D1RawOptions = {\n  columnNames?: boolean;\n};\n\ntype D1UpstreamFailure = {\n  results?: never;\n  error: string;\n  success: false;\n  meta?: never;\n};\n\ntype D1RowsColumns<T = unknown> = D1Response & {\n  results: {\n    columns: string[];\n    rows: T[][];\n  };\n};\n\ntype D1UpstreamSuccess<T = unknown> =\n  | D1Result<T>\n  | D1Response\n  | D1RowsColumns<T>;\n\ntype D1UpstreamResponse<T = unknown> = D1UpstreamSuccess<T> | D1UpstreamFailure;\n\ntype D1ExecResult = {\n  count: number;\n  duration: number;\n};\n\ntype SQLError = {\n  error: string;\n};\n\ntype ResultsFormat = 'ARRAY_OF_OBJECTS' | 'ROWS_AND_COLUMNS' | 'NONE';\n\ntype D1SessionBookmarkOrConstraint = string;\ntype D1SessionBookmark = string;\n// Indicates that the first query should go to the primary, and the rest queries\n// using the same D1DatabaseSession will go to any replica that is consistent with\n// the bookmark maintained by the session (returned by the first query).\nconst D1_SESSION_CONSTRAINT_FIRST_PRIMARY = 'first-primary';\n// Indicates that the first query can go anywhere (primary or replica), and the rest queries\n// using the same D1DatabaseSession will go to any replica that is consistent with\n// the bookmark maintained by the session (returned by the first query).\nconst D1_SESSION_CONSTRAINT_FIRST_UNCONSTRAINED = 'first-unconstrained';\n\n// Parsed by the D1 eyeball worker.\n// This header is internal only for our D1 binding, not part of the public D1 REST API.\n// Customers should not use this header otherwise their applications can break when we change this.\n// TODO Rename this to `x-cf-d1-session-bookmark`, with coordination with the D1 internal API.\nconst D1_SESSION_COMMIT_TOKEN_HTTP_HEADER = 'x-cf-d1-session-commit-token';\n\nclass D1Database {\n  // TODO(soon): Can we use the # syntax here?\n  // eslint-disable-next-line no-restricted-syntax\n  private readonly alwaysPrimarySession: D1DatabaseSessionAlwaysPrimary;\n  protected readonly fetcher: Fetcher;\n\n  constructor(fetcher: Fetcher) {\n    this.fetcher = fetcher;\n    this.alwaysPrimarySession = new D1DatabaseSessionAlwaysPrimary(\n      this.fetcher\n    );\n  }\n\n  prepare(query: string): D1PreparedStatement {\n    return new D1PreparedStatement(this.alwaysPrimarySession, query);\n  }\n\n  async batch<T = unknown>(\n    statements: D1PreparedStatement[]\n  ): Promise<D1Result<T>[]> {\n    return this.alwaysPrimarySession.batch(statements);\n  }\n\n  async exec(query: string): Promise<D1ExecResult> {\n    return this.alwaysPrimarySession.exec(query);\n  }\n\n  withSession(\n    constraintOrBookmark?: D1SessionBookmarkOrConstraint\n  ): D1DatabaseSession {\n    constraintOrBookmark = constraintOrBookmark?.trim();\n    if (constraintOrBookmark == null || constraintOrBookmark === '') {\n      constraintOrBookmark = D1_SESSION_CONSTRAINT_FIRST_UNCONSTRAINED;\n    }\n    return new D1DatabaseSession(this.fetcher, constraintOrBookmark);\n  }\n\n  /**\n   * @deprecated\n   */\n  async dump(): Promise<ArrayBuffer> {\n    return this.alwaysPrimarySession.dump();\n  }\n}\n\nclass D1DatabaseSession {\n  protected fetcher: Fetcher;\n  protected bookmarkOrConstraint: D1SessionBookmarkOrConstraint;\n\n  constructor(\n    fetcher: Fetcher,\n    bookmarkOrConstraint: D1SessionBookmarkOrConstraint\n  ) {\n    this.fetcher = fetcher;\n    this.bookmarkOrConstraint = bookmarkOrConstraint;\n\n    if (!this.bookmarkOrConstraint) {\n      throw new Error('D1_SESSION_ERROR: invalid bookmark or constraint');\n    }\n  }\n\n  // Update the bookmark IFF the given newBookmark is more recent.\n  // The bookmark held in the session should always be the latest value we\n  // have observed in the responses to our API. There can be cases where we have concurrent\n  // queries running within the same session, and therefore here we ensure we only\n  // retain the latest bookmark received.\n  // @returns the final bookmark after the update.\n  protected _updateBookmark(\n    newBookmark: D1SessionBookmark\n  ): D1SessionBookmark | null {\n    newBookmark = newBookmark.trim();\n    if (!newBookmark) {\n      // We should not be receiving invalid bookmarks, but just be defensive.\n      return this.getBookmark();\n    }\n    const currentBookmark = this.getBookmark();\n    if (\n      currentBookmark === null ||\n      currentBookmark.localeCompare(newBookmark) < 0\n    ) {\n      this.bookmarkOrConstraint = newBookmark;\n    }\n    return this.getBookmark();\n  }\n\n  prepare(sql: string): D1PreparedStatement {\n    return new D1PreparedStatement(this, sql);\n  }\n\n  async batch<T = unknown>(\n    statements: D1PreparedStatement[]\n  ): Promise<D1Result<T>[]> {\n    return withSpan('d1_batch', async (span) => {\n      span.setAttribute('db.system.name', 'cloudflare-d1');\n      span.setAttribute('db.operation.name', 'batch');\n      span.setAttribute(\n        'db.query.text',\n        statements.map((s: D1PreparedStatement) => s.statement).join('\\n')\n      );\n      span.setAttribute('db.operation.batch.size', statements.length);\n      span.setAttribute('cloudflare.binding.type', 'D1');\n      span.setAttribute(\n        'cloudflare.d1.query.bookmark',\n        this.getBookmark() ?? undefined\n      );\n\n      const exec = (await this._sendOrThrow(\n        '/query',\n        statements.map((s: D1PreparedStatement) => s.statement),\n        statements.map((s: D1PreparedStatement) => s.params),\n        'ROWS_AND_COLUMNS',\n        span\n      )) as D1UpstreamSuccess<T>[];\n\n      span.setAttribute(\n        'cloudflare.d1.response.bookmark',\n        this.getBookmark() ?? undefined\n      );\n      addAggregatedD1MetaToSpan(\n        span,\n        exec.map((e) => e.meta)\n      );\n\n      return exec.map(toArrayOfObjects);\n    });\n  }\n\n  // Returns the latest bookmark we received from all responses processed so far.\n  // It does not return constraints that might have be passed during the session creation.\n  getBookmark(): D1SessionBookmark | null {\n    switch (this.bookmarkOrConstraint) {\n      // First to any replica, and then anywhere that satisfies the bookmark.\n      case D1_SESSION_CONSTRAINT_FIRST_UNCONSTRAINED:\n        return null;\n      // First to primary, and then anywhere that satisfies the bookmark.\n      case D1_SESSION_CONSTRAINT_FIRST_PRIMARY:\n        return null;\n      default:\n        return this.bookmarkOrConstraint;\n    }\n  }\n\n  // fetch will append the bookmark header to all outgoing fetch calls.\n  // The response headers are parsed automatically, extracting the bookmark\n  // from the response headers and updating it through `_updateBookmark(token)`.\n  protected async _wrappedFetch(\n    input: RequestInfo | URL,\n    init?: RequestInit\n  ): Promise<Response> {\n    const h = new Headers(init?.headers);\n\n    // We send either a constraint, or a bookmark, and the eyeball worker will figure out\n    // what to do based on the value. This simulates the same flow as the REST API would behave too.\n    if (this.bookmarkOrConstraint) {\n      h.set(D1_SESSION_COMMIT_TOKEN_HTTP_HEADER, this.bookmarkOrConstraint);\n    }\n\n    if (!init) {\n      init = { headers: h };\n    } else {\n      init.headers = h;\n    }\n    return this.fetcher.fetch(input, init).then((resp) => {\n      const newBookmark = resp.headers.get(D1_SESSION_COMMIT_TOKEN_HTTP_HEADER);\n      if (newBookmark) {\n        this._updateBookmark(newBookmark);\n      }\n      return resp;\n    });\n  }\n\n  async _sendOrThrow<T = unknown>(\n    endpoint: string,\n    query: string | string[],\n    params: unknown[],\n    resultsFormat: ResultsFormat,\n    span: Span\n  ): Promise<D1UpstreamSuccess<T>[] | D1UpstreamSuccess<T>> {\n    const results = await this._send(\n      endpoint,\n      query,\n      params,\n      resultsFormat,\n      span\n    );\n    const firstResult = firstIfArray(results);\n    if (!firstResult.success) {\n      span.setAttribute('error.type', firstResult.error);\n      throw new Error(`D1_ERROR: ${firstResult.error}`, {\n        cause: new Error(firstResult.error),\n      });\n    } else {\n      return results as D1UpstreamSuccess<T>[] | D1UpstreamSuccess<T>;\n    }\n  }\n\n  async _send<T = unknown>(\n    endpoint: string,\n    query: string | string[],\n    params: unknown[],\n    resultsFormat: ResultsFormat,\n    span: Span\n  ): Promise<D1UpstreamResponse<T>[] | D1UpstreamResponse<T>> {\n    /* this needs work - we currently only support ordered ?n params */\n    const body = JSON.stringify(\n      Array.isArray(query)\n        ? query.map((s: string, index: number) => {\n            return { sql: s, params: params[index] };\n          })\n        : {\n            sql: query,\n            params: params,\n          }\n    );\n\n    const url = new URL(endpoint, 'http://d1');\n    url.searchParams.set('resultsFormat', resultsFormat);\n    const response = await this._wrappedFetch(url.href, {\n      method: 'POST',\n      headers: {\n        'content-type': 'application/json',\n      },\n      body,\n    });\n\n    try {\n      const answer = await toJson<\n        D1UpstreamResponse<T>[] | D1UpstreamResponse<T>\n      >(response);\n\n      if (Array.isArray(answer)) {\n        return answer.map((r: D1UpstreamResponse<T>) => mapD1Result<T>(r));\n      } else {\n        return mapD1Result<T>(answer);\n      }\n    } catch (_e: unknown) {\n      const e = _e as Error;\n      const message =\n        (e.cause as Error | undefined)?.message ||\n        e.message ||\n        'Something went wrong';\n      span.setAttribute('error.type', message);\n      throw new Error(`D1_ERROR: ${message}`, {\n        cause: new Error(message),\n      });\n    }\n  }\n}\n\nclass D1DatabaseSessionAlwaysPrimary extends D1DatabaseSession {\n  constructor(fetcher: Fetcher) {\n    // Will always go to primary, since we won't be ever updating this constraint.\n    super(fetcher, D1_SESSION_CONSTRAINT_FIRST_PRIMARY);\n  }\n\n  // We ignore bookmarks for this special type of session,\n  // since all queries are sent to the primary.\n  override _updateBookmark(\n    _newBookmark: D1SessionBookmark\n  ): D1SessionBookmark | null {\n    return null;\n  }\n\n  // There is no bookmark returned ever by this special type of session,\n  // since all queries are sent to the primary.\n  override getBookmark(): D1SessionBookmark | null {\n    return null;\n  }\n\n  //////////////////////////////////////////////////////////////////////////////////////////////\n  // These are only used by the D1Database which is our existing API pre-Sessions API.\n  // For backwards compatibility they always go to the primary database.\n  //\n\n  async exec(query: string): Promise<D1ExecResult> {\n    return withSpan('d1_exec', async (span) => {\n      span.setAttribute('db.system.name', 'cloudflare-d1');\n      span.setAttribute('db.operation.name', 'exec');\n      span.setAttribute('db.query.text', query);\n      span.setAttribute('cloudflare.binding.type', 'D1');\n\n      // TODO: splitting by lines is overly simplification because a single line\n      // can contain multiple statements (ex: `select 1; select 2;`).\n      // Also, a statement can span multiple lines...\n      // Either, we should do a more reasonable job to split the query into multiple statements\n      // like we do in the D1 codebase or we report a simpler error without the line number.\n      const lines = query.trim().split('\\n');\n      const _exec = await this._send('/execute', lines, [], 'NONE', span);\n      const exec = Array.isArray(_exec) ? _exec : [_exec];\n\n      let duration = 0;\n      const metas: D1Meta[] = [];\n      for (let i = 0; i < exec.length; i++) {\n        const res = exec[i];\n        if (!res?.success) {\n          span.setAttribute('error.type', `Error in line ${i + 1}`);\n          throw new Error(\n            `D1_EXEC_ERROR: Error in line ${i + 1}: ${lines[i]}${res?.error ? `: ${res.error}` : ''}`,\n            {\n              cause: new Error(\n                `Error in line ${i + 1}: ${lines[i]}${res?.error ? `: ${res.error}` : ''}`\n              ),\n            }\n          );\n        }\n\n        duration += res.meta.duration;\n        metas.push(res.meta);\n      }\n\n      if (metas.length) {\n        addAggregatedD1MetaToSpan(span, metas);\n      }\n      return {\n        count: exec.length,\n        duration,\n      };\n    });\n  }\n\n  /**\n   * DEPRECATED, TO BE REMOVED WITH NEXT BREAKING CHANGE\n   * Only applies to the deprecated v1 alpha databases.\n   */\n  async dump(): Promise<ArrayBuffer> {\n    const response = await this._wrappedFetch('http://d1/dump', {\n      method: 'POST',\n      headers: {\n        'content-type': 'application/json',\n      },\n    });\n    if (response.status !== 200) {\n      try {\n        const err = (await response.json()) as SQLError;\n        throw new Error(`D1_DUMP_ERROR: ${err.error}`, {\n          cause: new Error(err.error),\n        });\n      } catch {\n        throw new Error(`D1_DUMP_ERROR: Status + ${response.status}`, {\n          cause: new Error(`Status ${response.status}`),\n        });\n      }\n    }\n    return await response.arrayBuffer();\n  }\n}\n\nclass D1PreparedStatement {\n  // TODO(soon): Can we use the # syntax here?\n  // eslint-disable-next-line no-restricted-syntax\n  private readonly dbSession: D1DatabaseSession;\n  readonly statement: string;\n  readonly params: unknown[];\n\n  constructor(\n    dbSession: D1DatabaseSession,\n    statement: string,\n    values?: unknown[]\n  ) {\n    this.dbSession = dbSession;\n    this.statement = statement;\n    this.params = values || [];\n  }\n\n  bind(...values: unknown[]): D1PreparedStatement {\n    // Validate value types\n    const transformedValues = values.map((r: unknown): unknown => {\n      const rType = typeof r;\n      if (rType === 'number' || rType === 'string') {\n        return r;\n      } else if (rType === 'boolean') {\n        return r ? 1 : 0;\n      } else if (rType === 'object') {\n        // nulls are objects in javascript\n        if (r == null) return r;\n        // arrays with uint8's are good\n        if (\n          Array.isArray(r) &&\n          r.every((b: unknown) => {\n            return typeof b == 'number' && b >= 0 && b < 256;\n          })\n        )\n          return r as unknown[];\n        // convert ArrayBuffer to array\n        if (r instanceof ArrayBuffer) {\n          return Array.from(new Uint8Array(r));\n        }\n        // convert view to array\n        if (ArrayBuffer.isView(r)) {\n          // For some reason TS doesn't think this is valid, but it is!\n          return Array.from(r as unknown as ArrayLike<unknown>);\n        }\n      }\n\n      throw new Error(\n        `D1_TYPE_ERROR: Type '${rType}' not supported for value '${r}'`,\n        {\n          cause: new Error(`Type '${rType}' not supported for value '${r}'`),\n        }\n      );\n    });\n    return new D1PreparedStatement(\n      this.dbSession,\n      this.statement,\n      transformedValues\n    );\n  }\n\n  async first<T = unknown>(colName: string): Promise<T | null>;\n  async first<T = Record<string, unknown>>(): Promise<T | null>;\n  async first<T = unknown>(\n    colName?: string\n  ): Promise<Record<string, T> | T | null> {\n    return withSpan('d1_first', async (span) => {\n      span.setAttribute('db.system.name', 'cloudflare-d1');\n      span.setAttribute('db.operation.name', 'first');\n      span.setAttribute('db.query.text', this.statement);\n      span.setAttribute('cloudflare.binding.type', 'D1');\n      span.setAttribute(\n        'cloudflare.d1.query.bookmark',\n        this.dbSession.getBookmark() ?? undefined\n      );\n\n      const info = firstIfArray(\n        await this.dbSession._sendOrThrow<Record<string, T>>(\n          '/query',\n          this.statement,\n          this.params,\n          'ROWS_AND_COLUMNS',\n          span\n        )\n      );\n\n      span.setAttribute(\n        'cloudflare.d1.response.bookmark',\n        this.dbSession.getBookmark() ?? undefined\n      );\n      addD1MetaToSpan(span, info.meta);\n\n      const results = toArrayOfObjects(info).results;\n      const hasResults = results.length > 0;\n      if (!hasResults) return null;\n\n      const firstResult = results.at(0);\n      if (colName !== undefined) {\n        if (firstResult?.[colName] === undefined) {\n          span.setAttribute('error.type', 'Column not found');\n          throw new Error(`D1_COLUMN_NOTFOUND: Column not found (${colName})`, {\n            cause: new Error('Column not found'),\n          });\n        }\n        return firstResult[colName];\n      } else {\n        return firstResult as Record<string, T>;\n      }\n    });\n  }\n\n  /* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters */\n  async run<T = Record<string, unknown>>(): Promise<D1Response> {\n    return withSpan('d1_run', async (span) => {\n      span.setAttribute('db.system.name', 'cloudflare-d1');\n      span.setAttribute('db.operation.name', 'run');\n      span.setAttribute('db.query.text', this.statement);\n      span.setAttribute('cloudflare.binding.type', 'D1');\n      span.setAttribute(\n        'cloudflare.d1.query.bookmark',\n        this.dbSession.getBookmark() ?? undefined\n      );\n\n      const result = firstIfArray(\n        await this.dbSession._sendOrThrow<T>(\n          '/execute',\n          this.statement,\n          this.params,\n          'NONE',\n          span\n        )\n      );\n\n      span.setAttribute(\n        'cloudflare.d1.response.bookmark',\n        this.dbSession.getBookmark() ?? undefined\n      );\n      addD1MetaToSpan(span, result.meta);\n      return result;\n    });\n  }\n\n  async all<T = Record<string, unknown>>(): Promise<D1Result<T[]>> {\n    return withSpan('d1_all', async (span) => {\n      span.setAttribute('db.system.name', 'cloudflare-d1');\n      span.setAttribute('db.operation.name', 'all');\n      span.setAttribute('db.query.text', this.statement);\n      span.setAttribute('cloudflare.binding.type', 'D1');\n      span.setAttribute(\n        'cloudflare.d1.query.bookmark',\n        this.dbSession.getBookmark() ?? undefined\n      );\n\n      const result = firstIfArray(\n        await this.dbSession._sendOrThrow<T[]>(\n          '/query',\n          this.statement,\n          this.params,\n          'ROWS_AND_COLUMNS',\n          span\n        )\n      );\n\n      span.setAttribute(\n        'cloudflare.d1.response.bookmark',\n        this.dbSession.getBookmark() ?? undefined\n      );\n      addD1MetaToSpan(span, result.meta);\n\n      return toArrayOfObjects(result);\n    });\n  }\n\n  async raw<T = unknown[]>(options?: D1RawOptions): Promise<T[]> {\n    return withSpan('d1_all', async (span) => {\n      span.setAttribute('db.system.name', 'cloudflare-d1');\n      span.setAttribute('db.operation.name', 'raw');\n      span.setAttribute('db.query.text', this.statement);\n      span.setAttribute('cloudflare.binding.type', 'D1');\n      span.setAttribute(\n        'cloudflare.d1.query.bookmark',\n        this.dbSession.getBookmark() ?? undefined\n      );\n\n      const s = firstIfArray(\n        await this.dbSession._sendOrThrow<Record<string, unknown>>(\n          '/query',\n          this.statement,\n          this.params,\n          'ROWS_AND_COLUMNS',\n          span\n        )\n      );\n\n      span.setAttribute(\n        'cloudflare.d1.response.bookmark',\n        this.dbSession.getBookmark() ?? undefined\n      );\n      addD1MetaToSpan(span, s.meta);\n\n      // If no results returned, return empty array\n      if (!('results' in s)) return [];\n\n      // If ARRAY_OF_OBJECTS returned, extract cells\n      if (Array.isArray(s.results)) {\n        const raw: T[] = [];\n        for (const row of s.results) {\n          if (options?.columnNames && raw.length === 0) {\n            raw.push(Array.from(Object.keys(row)) as T);\n          }\n          const entry = Object.keys(row).map((k) => {\n            return row[k];\n          });\n          raw.push(entry as T);\n        }\n        return raw;\n      } else {\n        // Otherwise, data is already in the correct format\n        return [\n          ...(options?.columnNames ? [s.results.columns as T] : []),\n          ...(s.results.rows as T[]),\n        ];\n      }\n    });\n  }\n}\n\nfunction firstIfArray<T>(results: T | T[]): T {\n  return Array.isArray(results) ? (results.at(0) as T) : results;\n}\n\n// This shim may be used against an older version of D1 that doesn't support\n// the ROWS_AND_COLUMNS/NONE interchange format, so be permissive here\nfunction toArrayOfObjects<T>(response: D1UpstreamSuccess<T>): D1Result<T> {\n  // If 'results' is missing from upstream, add an empty array\n  if (!('results' in response))\n    return {\n      ...response,\n      results: [],\n    };\n\n  const results = response.results;\n  if (Array.isArray(results)) {\n    return { ...response, results };\n  } else {\n    const { rows, columns } = results;\n    return {\n      ...response,\n      results: rows.map(\n        (row) =>\n          Object.fromEntries(row.map((cell, i) => [columns[i], cell])) as T\n      ),\n    };\n  }\n}\n\nfunction mapD1Result<T>(result: D1UpstreamResponse<T>): D1UpstreamResponse<T> {\n  // The rest of the app can guarantee that success is true/false, but from the API\n  // we only guarantee that error is present/absent.\n  return result.error\n    ? {\n        success: false,\n        error: result.error,\n      }\n    : {\n        success: true,\n        meta: (result as D1UpstreamSuccess).meta,\n        ...('results' in result ? { results: result.results } : {}),\n      };\n}\n\nasync function toJson<T = unknown>(response: Response): Promise<T> {\n  const body = await response.text();\n  try {\n    return JSON.parse(body) as T;\n  } catch {\n    throw new Error(`Failed to parse body as JSON, got: ${body}`);\n  }\n}\n\ntype PartialD1Meta = Partial<D1Meta> | undefined;\n\nfunction addAggregatedD1MetaToSpan(span: Span, metas: PartialD1Meta[]): void {\n  if (!metas.length) {\n    return;\n  }\n  const aggregatedMeta = aggregateD1Meta(metas);\n  addD1MetaToSpan(span, aggregatedMeta);\n}\n\nfunction addD1MetaToSpan(span: Span, meta: D1Meta): void {\n  span.setAttribute('cloudflare.d1.response.size_after', meta.size_after);\n  span.setAttribute('cloudflare.d1.response.rows_read', meta.rows_read);\n  span.setAttribute('cloudflare.d1.response.rows_written', meta.rows_written);\n  span.setAttribute('cloudflare.d1.response.last_row_id', meta.last_row_id);\n  span.setAttribute('cloudflare.d1.response.changed_db', meta.changed_db);\n  span.setAttribute('cloudflare.d1.response.changes', meta.changes);\n  span.setAttribute(\n    'cloudflare.d1.response.served_by_region',\n    meta.served_by_region\n  );\n  span.setAttribute(\n    'cloudflare.d1.response.served_by_primary',\n    meta.served_by_primary\n  );\n  span.setAttribute(\n    'cloudflare.d1.response.sql_duration_ms',\n    meta.timings?.sql_duration_ms ?? undefined\n  );\n  span.setAttribute(\n    'cloudflare.d1.response.total_attempts',\n    meta.total_attempts\n  );\n}\n\n// When a query is executing multiple statements, and we receive a D1Meta\n// for each statement, we need to aggregate the meta data before we annotate\n// the telemetry, with different rules for each field.\nfunction aggregateD1Meta(metas: PartialD1Meta[]): D1Meta {\n  const aggregatedMeta: D1Meta = {\n    duration: 0,\n    size_after: 0,\n    rows_read: 0,\n    rows_written: 0,\n    last_row_id: 0,\n    changed_db: false,\n    changes: 0,\n  };\n\n  for (const meta of metas) {\n    if (!meta) {\n      continue;\n    }\n\n    aggregatedMeta.duration += meta.duration ?? 0;\n    // for size_after, we only want the last value\n    aggregatedMeta.size_after = meta.size_after ?? 0;\n    aggregatedMeta.rows_read += meta.rows_read ?? 0;\n    aggregatedMeta.rows_written += meta.rows_written ?? 0;\n    aggregatedMeta.last_row_id = meta.last_row_id ?? 0;\n    if (meta.served_by_region) {\n      aggregatedMeta.served_by_region = meta.served_by_region;\n    }\n    if (meta.served_by_primary) {\n      aggregatedMeta.served_by_primary = meta.served_by_primary;\n    }\n    if (meta.timings?.sql_duration_ms) {\n      aggregatedMeta.timings = {\n        sql_duration_ms:\n          (aggregatedMeta.timings?.sql_duration_ms ?? 0) +\n          meta.timings.sql_duration_ms,\n      };\n    }\n    if (meta.total_attempts) {\n      aggregatedMeta.total_attempts =\n        (aggregatedMeta.total_attempts ?? 0) + meta.total_attempts;\n    }\n    aggregatedMeta.changes += meta.changes ?? 0;\n    if (meta.changed_db) {\n      aggregatedMeta.changed_db = true;\n    }\n  }\n\n  return aggregatedMeta;\n}\n\nexport default function makeBinding(env: { fetcher: Fetcher }): D1Database {\n  return new D1Database(env.fetcher);\n}\n"
  },
  {
    "path": "src/cloudflare/internal/email.d.ts",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Type definitions for c++ implementation.\n\nexport class EmailMessage {\n  constructor(from: string, to: string, raw: ReadableStream | string);\n  readonly from: string;\n  readonly to: string;\n}\n"
  },
  {
    "path": "src/cloudflare/internal/env.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Get the current environment, if any\nexport function getCurrentEnv(): Record<string, unknown> | undefined;\nexport function getCurrentExports(): Record<string, unknown> | undefined;\nexport function withEnv(newEnv: unknown, fn: () => unknown): unknown;\nexport function withExports(newExports: unknown, fn: () => unknown): unknown;\nexport function withEnvAndExports(\n  newEnv: unknown,\n  newExports: unknown,\n  fn: () => unknown\n): unknown;\n"
  },
  {
    "path": "src/cloudflare/internal/global.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ndeclare namespace Cloudflare {\n  const compatibilityFlags: Record<string, boolean>;\n}\n"
  },
  {
    "path": "src/cloudflare/internal/http.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport interface FetchHandler {\n  // We don't use typeof fetch here because that would use the client-side signature,\n  // which is different from the server-side signature.\n  fetch: (request: Request, env?: unknown, ctx?: unknown) => Promise<Response>;\n}\n\nexport const portMapper = new Map<number, FetchHandler>();\n"
  },
  {
    "path": "src/cloudflare/internal/images-api.ts",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport { StreamableFormData } from 'cloudflare-internal:streaming-forms';\nimport {\n  createBase64DecoderTransformStream,\n  createBase64EncoderTransformStream,\n} from 'cloudflare-internal:streaming-base64';\nimport { withSpan, type Span } from 'cloudflare-internal:tracing-helpers';\n\ntype Fetcher = {\n  fetch: typeof fetch;\n};\n\ntype TargetedTransform = ImageTransform & {\n  imageIndex: number;\n};\n\n// Draw image drawImageIndex on image targetImageIndex\ntype DrawCommand = ImageDrawOptions & {\n  drawImageIndex: number;\n  targetImageIndex: number;\n};\n\ntype RawInfoResponse =\n  | { format: 'image/svg+xml' }\n  | {\n      format: string;\n      file_size: number;\n      width: number;\n      height: number;\n    };\n\nclass TransformationResultImpl implements ImageTransformationResult {\n  readonly #bindingsResponse: Response;\n\n  constructor(bindingsResponse: Response) {\n    this.#bindingsResponse = bindingsResponse;\n  }\n\n  contentType(): string {\n    const contentType = this.#bindingsResponse.headers.get('content-type');\n    if (!contentType) {\n      throw new ImagesErrorImpl(\n        'IMAGES_TRANSFORM_ERROR 9523: No content-type on bindings response',\n        9523\n      );\n    }\n\n    return contentType;\n  }\n\n  image(\n    options?: ImageTransformationOutputOptions\n  ): ReadableStream<Uint8Array> {\n    const stream = this.#bindingsResponse.body || new Blob().stream();\n\n    return options?.encoding === 'base64'\n      ? stream.pipeThrough(createBase64EncoderTransformStream())\n      : stream;\n  }\n\n  response(): Response {\n    return new Response(this.image(), {\n      headers: {\n        'content-type': this.contentType(),\n      },\n    });\n  }\n}\n\nclass DrawTransformer {\n  readonly child: ImageTransformerImpl;\n  readonly options: ImageDrawOptions;\n  constructor(child: ImageTransformerImpl, options: ImageDrawOptions) {\n    this.child = child;\n    this.options = options;\n  }\n}\n\nclass ImageTransformerImpl implements ImageTransformer {\n  readonly #fetcher: Fetcher;\n  readonly #stream: ReadableStream<Uint8Array>;\n\n  #transforms: (ImageTransform | DrawTransformer)[];\n  #consumed: boolean;\n\n  constructor(fetcher: Fetcher, stream: ReadableStream<Uint8Array>) {\n    this.#fetcher = fetcher;\n    this.#stream = stream;\n    this.#transforms = [];\n    this.#consumed = false;\n  }\n\n  transform(transform: ImageTransform): this {\n    this.#transforms.push(transform);\n    return this;\n  }\n\n  draw(\n    image: ReadableStream<Uint8Array> | ImageTransformer,\n    options: ImageDrawOptions = {}\n  ): this {\n    if (isTransformer(image)) {\n      image.#consume();\n      this.#transforms.push(new DrawTransformer(image, options));\n    } else {\n      this.#transforms.push(\n        new DrawTransformer(\n          new ImageTransformerImpl(\n            this.#fetcher,\n            image as ReadableStream<Uint8Array>\n          ),\n          options\n        )\n      );\n    }\n\n    return this;\n  }\n\n  async output(\n    options: ImageOutputOptions\n  ): Promise<ImageTransformationResult> {\n    return await withSpan('images_output', async (span) => {\n      span.setAttribute('cloudflare.binding.type', 'Images');\n      const formData = new StreamableFormData();\n\n      this.#consume();\n      formData.append('image', this.#stream, { type: 'file' });\n\n      this.#serializeTransforms(formData, span);\n\n      span.setAttribute('cloudflare.images.options.format', options.format);\n      formData.append('output_format', options.format);\n\n      if (options.quality !== undefined) {\n        span.setAttribute('cloudflare.images.options.quality', options.quality);\n        formData.append('output_quality', options.quality.toString());\n      }\n\n      if (options.background !== undefined) {\n        span.setAttribute(\n          'cloudflare.images.options.background',\n          options.background\n        );\n        formData.append('background', options.background);\n      }\n\n      if (options.anim !== undefined) {\n        span.setAttribute('cloudflare.images.options.anim', options.anim);\n        formData.append('anim', options.anim.toString());\n      }\n\n      const response = await this.#fetcher.fetch(\n        'https://js.images.cloudflare.com/transform',\n        {\n          method: 'POST',\n          headers: {\n            'content-type': formData.contentType(),\n          },\n          body: formData.stream(),\n        }\n      );\n\n      await throwErrorIfErrorResponse('TRANSFORM', response, span);\n\n      return new TransformationResultImpl(response);\n    });\n  }\n\n  #consume(): void {\n    if (this.#consumed) {\n      throw new ImagesErrorImpl(\n        'IMAGES_TRANSFORM_ERROR 9525: ImageTransformer consumed; you may only call .output() or draw a transformer once',\n        9525\n      );\n    }\n\n    this.#consumed = true;\n  }\n\n  #serializeTransforms(formData: StreamableFormData, span: Span): void {\n    const transforms: (TargetedTransform | DrawCommand)[] = [];\n\n    // image 0 is the canvas, so the first draw_image has index 1\n    let drawImageIndex = 1;\n    function appendDrawImage(stream: ReadableStream): number {\n      formData.append('draw_image', stream, { type: 'file' });\n      return drawImageIndex++;\n    }\n\n    function walkTransforms(\n      targetImageIndex: number,\n      imageTransforms: (ImageTransform | DrawTransformer)[]\n    ): void {\n      for (const transform of imageTransforms) {\n        if (!isDrawTransformer(transform)) {\n          // Simple transformation - we just have to tell the backend to run it\n          // against this image\n          transforms.push({\n            imageIndex: targetImageIndex,\n            ...transform,\n          });\n        } else {\n          // Drawn child image\n          // Set the input for the drawn image on the form\n          const drawImageIndex = appendDrawImage(transform.child.#stream);\n\n          // Tell the backend to run any transforms (possibly involving more draws)\n          // required to build this child\n          walkTransforms(drawImageIndex, transform.child.#transforms);\n\n          // Draw the child image on to the canvas\n          transforms.push({\n            drawImageIndex: drawImageIndex,\n            targetImageIndex: targetImageIndex,\n            ...transform.options,\n          });\n        }\n      }\n    }\n\n    walkTransforms(0, this.#transforms);\n\n    // The transforms are a set of operations which are applied to the image in order.\n    // Attaching an attribute as JSON is a little odd, but I'm not sure if there is\n    // a better way to do this.\n    if (transforms.length > 0) {\n      span.setAttribute(\n        'cloudflare.images.options.transforms',\n        JSON.stringify(transforms)\n      );\n    }\n    formData.append('transforms', JSON.stringify(transforms));\n  }\n}\n\nfunction isTransformer(input: unknown): input is ImageTransformerImpl {\n  return input instanceof ImageTransformerImpl;\n}\n\nfunction isDrawTransformer(input: unknown): input is DrawTransformer {\n  return input instanceof DrawTransformer;\n}\n\ninterface ServiceEntrypointStub {\n  details(imageId: string): Promise<ImageMetadata | null>;\n  image(imageId: string): Promise<ReadableStream<Uint8Array> | null>;\n  upload(\n    image: ReadableStream<Uint8Array> | ArrayBuffer,\n    options?: ImageUploadOptions\n  ): Promise<ImageMetadata>;\n  update(imageId: string, options: ImageUpdateOptions): Promise<ImageMetadata>;\n  delete(imageId: string): Promise<boolean>;\n  list(options?: ImageListOptions): Promise<ImageList>;\n}\n\nclass HostedImagesBindingImpl implements HostedImagesBinding {\n  readonly #fetcher: ServiceEntrypointStub;\n\n  constructor(fetcher: ServiceEntrypointStub) {\n    this.#fetcher = fetcher;\n  }\n\n  async details(imageId: string): Promise<ImageMetadata | null> {\n    return this.#fetcher.details(imageId);\n  }\n\n  async image(imageId: string): Promise<ReadableStream<Uint8Array> | null> {\n    return this.#fetcher.image(imageId);\n  }\n\n  async upload(\n    image: ReadableStream<Uint8Array> | ArrayBuffer,\n    options?: ImageUploadOptions\n  ): Promise<ImageMetadata> {\n    return this.#fetcher.upload(image, options);\n  }\n\n  async update(\n    imageId: string,\n    options: ImageUpdateOptions\n  ): Promise<ImageMetadata> {\n    return this.#fetcher.update(imageId, options);\n  }\n\n  async delete(imageId: string): Promise<boolean> {\n    return this.#fetcher.delete(imageId);\n  }\n\n  async list(options?: ImageListOptions): Promise<ImageList> {\n    return this.#fetcher.list(options);\n  }\n}\n\nclass ImagesBindingImpl implements ImagesBinding {\n  readonly #fetcher: Fetcher & ServiceEntrypointStub;\n  readonly #hosted: HostedImagesBinding;\n\n  constructor(fetcher: Fetcher & ServiceEntrypointStub) {\n    this.#fetcher = fetcher;\n    this.#hosted = new HostedImagesBindingImpl(fetcher);\n  }\n\n  get hosted(): HostedImagesBinding {\n    return this.#hosted;\n  }\n\n  async info(\n    stream: ReadableStream<Uint8Array>,\n    options?: ImageInputOptions\n  ): Promise<ImageInfoResponse> {\n    return await withSpan('images_info', async (span) => {\n      span.setAttribute('cloudflare.binding.type', 'Images');\n      const body = new StreamableFormData();\n\n      span.setAttribute(\n        'cloudflare.images.options.encoding',\n        options?.encoding ?? 'base64'\n      );\n      const decodedStream =\n        options?.encoding === 'base64'\n          ? stream.pipeThrough(createBase64DecoderTransformStream())\n          : stream;\n\n      body.append('image', decodedStream, { type: 'file' });\n\n      const response = await this.#fetcher.fetch(\n        'https://js.images.cloudflare.com/info',\n        {\n          method: 'POST',\n          headers: {\n            'content-type': body.contentType(),\n          },\n          body: body.stream(),\n        }\n      );\n\n      await throwErrorIfErrorResponse('INFO', response, span);\n\n      const r = (await response.json()) as RawInfoResponse;\n\n      span.setAttribute('cloudflare.images.result.format', r.format);\n\n      if ('file_size' in r) {\n        const ret = {\n          fileSize: r.file_size,\n          width: r.width,\n          height: r.height,\n          format: r.format,\n        };\n        span.setAttribute('cloudflare.images.result.file_size', ret.fileSize);\n        span.setAttribute('cloudflare.images.result.width', ret.width);\n        span.setAttribute('cloudflare.images.result.height', ret.height);\n        return ret;\n      }\n\n      return r;\n    });\n  }\n\n  input(\n    stream: ReadableStream<Uint8Array>,\n    options?: ImageInputOptions\n  ): ImageTransformer {\n    const decodedStream =\n      options?.encoding === 'base64'\n        ? stream.pipeThrough(createBase64DecoderTransformStream())\n        : stream;\n\n    return new ImageTransformerImpl(this.#fetcher, decodedStream);\n  }\n}\n\nclass ImagesErrorImpl extends Error implements ImagesError {\n  readonly code: number;\n  constructor(message: string, code: number) {\n    super(message);\n    this.code = code;\n  }\n}\n\nasync function throwErrorIfErrorResponse(\n  operation: string,\n  response: Response,\n  span: Span\n): Promise<void> {\n  const statusHeader = response.headers.get('cf-images-binding') || '';\n\n  const match = /err=(\\d+)/.exec(statusHeader);\n\n  if (match && match[1]) {\n    const errorMessage = await response.text();\n    span.setAttribute('cloudflare.images.error.code', match[1]);\n    span.setAttribute('error.type', errorMessage);\n    throw new ImagesErrorImpl(\n      `IMAGES_${operation}_${errorMessage}`.trim(),\n      Number.parseInt(match[1])\n    );\n  }\n\n  if (response.status > 399) {\n    const errorMessage = await response.text();\n    span.setAttribute('cloudflare.images.error.code', '9523');\n    span.setAttribute('error.type', errorMessage);\n    throw new ImagesErrorImpl(\n      `Unexpected error response ${response.status}: ${errorMessage.trim()}`,\n      9523\n    );\n  }\n}\n\nexport default function makeBinding(env: { fetcher: Fetcher }): ImagesBinding {\n  return new ImagesBindingImpl(env.fetcher as Fetcher & ServiceEntrypointStub);\n}\n"
  },
  {
    "path": "src/cloudflare/internal/images.d.ts",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ntype ImageInfoResponse =\n  | { format: 'image/svg+xml' }\n  | {\n      format: string;\n      fileSize: number;\n      width: number;\n      height: number;\n    };\n\ntype ImageTransform = {\n  width?: number;\n  height?: number;\n  background?: string;\n  blur?: number;\n  border?:\n    | {\n        color?: string;\n        width?: number;\n      }\n    | {\n        top?: number;\n        bottom?: number;\n        left?: number;\n        right?: number;\n      };\n  brightness?: number;\n  contrast?: number;\n  fit?: 'scale-down' | 'contain' | 'pad' | 'squeeze' | 'cover' | 'crop';\n  flip?: 'h' | 'v' | 'hv';\n  gamma?: number;\n  segment?: 'foreground';\n  gravity?:\n    | 'face'\n    | 'left'\n    | 'right'\n    | 'top'\n    | 'bottom'\n    | 'center'\n    | 'auto'\n    | 'entropy'\n    | {\n        x?: number;\n        y?: number;\n        mode: 'remainder' | 'box-center';\n      };\n  rotate?: 0 | 90 | 180 | 270;\n  saturation?: number;\n  sharpen?: number;\n  trim?:\n    | 'border'\n    | {\n        top?: number;\n        bottom?: number;\n        left?: number;\n        right?: number;\n        width?: number;\n        height?: number;\n        border?:\n          | boolean\n          | {\n              color?: string;\n              tolerance?: number;\n              keep?: number;\n            };\n      };\n};\n\ntype ImageDrawOptions = {\n  opacity?: number;\n  repeat?: boolean | string;\n  top?: number;\n  left?: number;\n  bottom?: number;\n  right?: number;\n};\n\ntype ImageInputOptions = {\n  encoding?: 'base64';\n};\n\ntype ImageOutputOptions = {\n  format:\n    | 'image/jpeg'\n    | 'image/png'\n    | 'image/gif'\n    | 'image/webp'\n    | 'image/avif'\n    | 'rgb'\n    | 'rgba';\n  quality?: number;\n  background?: string;\n  anim?: boolean;\n};\n\ninterface ImageMetadata {\n  id: string;\n  filename?: string;\n  uploaded?: string;\n  requireSignedURLs: boolean;\n  meta?: Record<string, unknown>;\n  variants: string[];\n  draft?: boolean;\n  creator?: string;\n}\n\ninterface ImageUploadOptions {\n  id?: string;\n  filename?: string;\n  requireSignedURLs?: boolean;\n  metadata?: Record<string, unknown>;\n  creator?: string;\n  /**\n   * If 'base64', the input data will be decoded from base64 before processing\n   */\n  encoding?: 'base64';\n}\n\ninterface ImageUpdateOptions {\n  requireSignedURLs?: boolean;\n  metadata?: Record<string, unknown>;\n  creator?: string;\n}\n\ninterface ImageListOptions {\n  limit?: number;\n  cursor?: string;\n  sortOrder?: 'asc' | 'desc';\n  creator?: string;\n}\n\ninterface ImageList {\n  images: ImageMetadata[];\n  cursor?: string;\n  listComplete: boolean;\n}\n\ninterface HostedImagesBinding {\n  /**\n   * Get metadata for a hosted image\n   * @param imageId The ID of the image (UUID or custom ID)\n   * @returns Image metadata, or null if not found\n   */\n  details(imageId: string): Promise<ImageMetadata | null>;\n\n  /**\n   * Get the raw image data for a hosted image\n   * @param imageId The ID of the image (UUID or custom ID)\n   * @returns ReadableStream of image bytes, or null if not found\n   */\n  image(imageId: string): Promise<ReadableStream<Uint8Array> | null>;\n\n  /**\n   * Upload a new hosted image\n   * @param image The image file to upload\n   * @param options Upload configuration\n   * @returns Metadata for the uploaded image\n   * @throws {@link ImagesError} if upload fails\n   */\n  upload(\n    image: ReadableStream<Uint8Array> | ArrayBuffer,\n    options?: ImageUploadOptions\n  ): Promise<ImageMetadata>;\n\n  /**\n   * Update hosted image metadata\n   * @param imageId The ID of the image\n   * @param options Properties to update\n   * @returns Updated image metadata\n   * @throws {@link ImagesError} if update fails\n   */\n  update(imageId: string, options: ImageUpdateOptions): Promise<ImageMetadata>;\n\n  /**\n   * Delete a hosted image\n   * @param imageId The ID of the image\n   * @returns True if deleted, false if not found\n   */\n  delete(imageId: string): Promise<boolean>;\n\n  /**\n   * List hosted images with pagination\n   * @param options List configuration\n   * @returns List of images with pagination info\n   * @throws {@link ImagesError} if list fails\n   */\n  list(options?: ImageListOptions): Promise<ImageList>;\n}\n\ninterface ImagesBinding {\n  /**\n   * Get image metadata (type, width and height)\n   * @throws {@link ImagesError} with code 9412 if input is not an image\n   * @param stream The image bytes\n   */\n  info(\n    stream: ReadableStream<Uint8Array>,\n    options?: ImageInputOptions\n  ): Promise<ImageInfoResponse>;\n  /**\n   * Begin applying a series of transformations to an image\n   * @param stream The image bytes\n   * @returns A transform handle\n   */\n  input(\n    stream: ReadableStream<Uint8Array>,\n    options?: ImageInputOptions\n  ): ImageTransformer;\n\n  /**\n   * Access hosted images CRUD operations\n   */\n  readonly hosted: HostedImagesBinding;\n}\n\ninterface ImageTransformer {\n  /**\n   * Apply transform next, returning a transform handle.\n   * You can then apply more transformations, draw, or retrieve the output.\n   * @param transform\n   */\n  transform(transform: ImageTransform): ImageTransformer;\n\n  /**\n   * Draw an image on this transformer, returning a transform handle.\n   * You can then apply more transformations, draw, or retrieve the output.\n   * @param image The image (or transformer that will give the image) to draw\n   * @param options The options configuring how to draw the image\n   */\n  draw(\n    image: ReadableStream<Uint8Array> | ImageTransformer,\n    options?: ImageDrawOptions\n  ): ImageTransformer;\n\n  /**\n   * Retrieve the image that results from applying the transforms to the\n   * provided input\n   * @param options Options that apply to the output e.g. output format\n   */\n  output(options: ImageOutputOptions): Promise<ImageTransformationResult>;\n}\n\ntype ImageTransformationOutputOptions = {\n  encoding?: 'base64';\n};\n\ninterface ImageTransformationResult {\n  /**\n   * The image as a response, ready to store in cache or return to users\n   */\n  response(): Response;\n  /**\n   * The content type of the returned image\n   */\n  contentType(): string;\n  /**\n   * The bytes of the response\n   */\n  image(options?: ImageTransformationOutputOptions): ReadableStream<Uint8Array>;\n}\n\ninterface ImagesError extends Error {\n  readonly code: number;\n  readonly message: string;\n  readonly stack?: string;\n}\n"
  },
  {
    "path": "src/cloudflare/internal/pipeline-transform.ts",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport entrypoints from 'cloudflare-internal:workers';\n\n/**\n * Reads a stream line by line, yielding each line as it becomes available.\n *\n * This function consumes a ReadableStream of Uint8Array chunks (binary data),\n * converts it to text using TextDecoderStream, and yields each line\n * encountered. Lines are delimited by newline characters ('\\n'). The final\n * line is yielded even if it doesn't end with a newline.\n *\n * @param stream - A ReadableStream containing binary data to be decoded as text\n * @returns An AsyncGenerator that yields each line from the stream\n */\nasync function* readLines(\n  stream: ReadableStream<Uint8Array>\n): AsyncGenerator<string> {\n  // @ts-expect-error TS2345 TODO(soon): Fix this.\n  const textStream = stream.pipeThrough(new TextDecoderStream());\n  const reader = textStream.getReader();\n\n  let buffer = '';\n\n  try {\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    while (true) {\n      const { done, value } = await reader.read();\n\n      // Add any new content to the buffer\n      if (value) buffer += value;\n\n      // Process complete lines\n      let lineEndIndex = buffer.indexOf('\\n');\n      while (lineEndIndex >= 0) {\n        yield buffer.substring(0, lineEndIndex);\n        buffer = buffer.substring(lineEndIndex + 1);\n        lineEndIndex = buffer.indexOf('\\n');\n      }\n\n      // If we're done and have processed all complete lines,\n      // yield any remaining content and exit\n      if (done) {\n        if (buffer.length > 0) {\n          yield buffer;\n        }\n        break;\n      }\n    }\n  } finally {\n    reader.releaseLock();\n  }\n}\n\ntype Batch = {\n  id: string; // unique identifier for the batch\n  shard: string; // assigned shard\n  ts: number; // creation timestamp of the batch\n\n  format: FormatType;\n  size: {\n    bytes: number;\n    rows: number;\n  };\n  data: unknown;\n};\n\nconst Format = {\n  JSON_STREAM: 'json_stream' as const, // jsonl\n};\ntype FormatType = (typeof Format)[keyof typeof Format];\n\ntype JsonStream = Batch & {\n  format: typeof Format.JSON_STREAM;\n  data: ReadableStream<Uint8Array>;\n};\n\ntype PipelineBatchMetadata = {\n  pipelineId: string;\n  pipelineName: string;\n};\n\ntype PipelineRecord = Record<string, unknown>;\n\nexport class PipelineTransformImpl<\n  I extends PipelineRecord,\n  O extends PipelineRecord,\n>\n  extends entrypoints.WorkerEntrypoint\n{\n  #batch?: Batch;\n  #initalized: boolean = false;\n\n  // stub overridden on the subclass\n  // eslint-disable-next-line @typescript-eslint/require-await\n  async run(_records: I[], _metadata: PipelineBatchMetadata): Promise<O[]> {\n    throw new Error('should be implemented by parent');\n  }\n\n  // called by the dispatcher to validate that run is properly implemented by the subclass\n  // @ts-expect-error This is OK. We use this method in tests.\n  // eslint-disable-next-line no-restricted-syntax\n  private async _ping(): Promise<void> {\n    // making sure the function was overridden by an implementing subclass\n    if (this.run !== PipelineTransformImpl.prototype.run) {\n      return Promise.resolve();\n    } else {\n      return Promise.reject(\n        new Error(\n          'the run method must be overridden by the PipelineTransformationEntrypoint subclass'\n        )\n      );\n    }\n  }\n\n  // called by the dispatcher which then calls the subclass methods\n  // the reason this is typescript private and not javascript private is that this must be\n  // able to be called by the dispatcher but should not be called by the class implementer\n  // @ts-expect-error This is OK. We use this method in tests.\n  // eslint-disable-next-line no-restricted-syntax\n  private async _run(\n    batch: Batch,\n    metadata: PipelineBatchMetadata\n  ): Promise<JsonStream> {\n    if (this.#initalized) {\n      throw new Error('pipeline entrypoint has already been initialized');\n    }\n\n    this.#batch = batch;\n    this.#initalized = true;\n\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    if (this.#batch.format === Format.JSON_STREAM) {\n      const records: I[] = await this.#readJsonStream();\n      const transformed = await this.run(records, metadata);\n      return this.#sendJson(transformed);\n    } else {\n      throw new Error(\n        'PipelineTransformationEntrypoint run supports only the JSON_STREAM batch format'\n      );\n    }\n  }\n\n  async #readJsonStream(): Promise<I[]> {\n    if (this.#batch?.format !== Format.JSON_STREAM) {\n      throw new Error(`expected JSON_STREAM not ${this.#batch?.format}`);\n    }\n\n    const batch = this.#batch.data as ReadableStream<Uint8Array>;\n\n    const data: I[] = [];\n    for await (const line of readLines(batch)) {\n      if (line.trim().length > 0) {\n        // guard against empty lines\n        data.push(JSON.parse(line) as I);\n      }\n    }\n\n    return data;\n  }\n\n  #sendJson(records: O[]): JsonStream {\n    if (!(records instanceof Array)) {\n      throw new Error('transformations must return an array of PipelineRecord');\n    }\n\n    let written = 0;\n    const encoder = new TextEncoder();\n    const readable = new ReadableStream<Uint8Array>({\n      start(controller): void {\n        for (const record of records) {\n          const encoded = encoder.encode(`${JSON.stringify(record)}\\n`);\n          written += encoded.length;\n          controller.enqueue(encoded);\n        }\n\n        controller.close();\n      },\n    });\n\n    if (!this.#batch) {\n      throw new Error('Batch should have been defined. Assertion error.');\n    }\n\n    return {\n      id: this.#batch.id,\n      shard: this.#batch.shard,\n      ts: this.#batch.ts,\n      format: Format.JSON_STREAM,\n      size: {\n        bytes: written,\n        rows: records.length,\n      },\n      data: readable,\n    };\n  }\n}\n"
  },
  {
    "path": "src/cloudflare/internal/sockets.d.ts",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Type definitions for c++ implementation.\n\nimport { type ServiceStub } from 'cloudflare-internal:workers';\n\nexport class Socket {\n  readonly readable: unknown;\n  readonly writable: unknown;\n  readonly closed: Promise<void>;\n  close(): Promise<void>;\n  startTls(options: TlsOptions): Socket;\n}\n\nexport type TlsOptions = {\n  expectedServerHostname?: string;\n};\n\nexport type SocketAddress = {\n  hostname: string;\n  port: number;\n};\n\nexport type SocketOptions = {\n  secureTransport?: 'off' | 'on' | 'starttls';\n  allowHalfOpen?: boolean;\n};\n\nexport function connect(\n  address: string | SocketAddress,\n  options?: SocketOptions\n): Socket;\n\nexport function internalNewHttpClient(socket: Socket): Promise<ServiceStub>;\n"
  },
  {
    "path": "src/cloudflare/internal/streaming-base64.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport base64 from 'cloudflare-internal:base64';\n\nfunction base64Error(cause: unknown): Error {\n  if (Error.isError(cause)) {\n    return new Error(`base64 error: ${cause.message}`, { cause });\n  } else {\n    return new Error('unknown base64 error');\n  }\n}\n\nfunction toBase64(input: Uint8Array): Uint8Array {\n  return new Uint8Array(base64.encodeArray(input));\n}\n\nfunction fromBase64(input: Uint8Array): Uint8Array {\n  return new Uint8Array(base64.decodeArray(input));\n}\n\nfunction combineArrays(a: Uint8Array, b: Uint8Array): Uint8Array {\n  const combined = new Uint8Array(a.length + b.length);\n  combined.set(a, 0);\n  combined.set(b, a.length);\n  return combined;\n}\n\nfunction buildChunkFromLeftover(\n  leftover: Uint8Array,\n  chunk: Uint8Array,\n  minimumChunkSize: number\n): { firstChunk: Uint8Array; remainder: Uint8Array } {\n  const offset = minimumChunkSize - leftover.length;\n  const firstChunk = new Uint8Array(minimumChunkSize);\n  firstChunk.set(leftover, 0);\n  firstChunk.set(chunk.subarray(0, offset), leftover.length);\n  return { firstChunk, remainder: chunk.subarray(offset) };\n}\n\nfunction getProcessableChunk(\n  chunk: Uint8Array,\n  segmentSize: number\n): { processable: Uint8Array; leftover: Uint8Array | null } {\n  const processableLength =\n    Math.trunc(chunk.length / segmentSize) * segmentSize;\n\n  const processable =\n    processableLength > 0\n      ? chunk.subarray(0, processableLength)\n      : new Uint8Array();\n  const leftover =\n    processableLength !== chunk.length\n      ? chunk.subarray(processableLength)\n      : null;\n\n  return { processable, leftover };\n}\n\nconst PADDING_CHAR_CODE = 61; // '='\nfunction isPaddedBase64Chunk(chunk: Uint8Array): boolean {\n  return chunk.length > 0 && chunk[chunk.length - 1] === PADDING_CHAR_CODE;\n}\n\nexport function createBase64EncoderTransformStream(\n  maxEncodeChunkSize: number = 32 * 1024 + 1\n): TransformStream<Uint8Array, Uint8Array> {\n  let leftover: Uint8Array | null = null;\n\n  if (maxEncodeChunkSize % 3 != 0) {\n    // Try to minimize padding\n    throw new Error('maxChunkSize must be a multiple of 3');\n  }\n\n  return new TransformStream<Uint8Array, Uint8Array>({\n    transform(chunk, controller): void {\n      if (leftover != null) {\n        const requiredBytes = 3 - leftover.length;\n\n        if (chunk.length < requiredBytes) {\n          // We don't have enough bytes in the chunk to encode, update leftovers\n          leftover = combineArrays(leftover, chunk);\n          // encode when we get more chars\n          return;\n        }\n\n        const { firstChunk, remainder } = buildChunkFromLeftover(\n          leftover,\n          chunk,\n          3\n        );\n        controller.enqueue(toBase64(firstChunk));\n        leftover = null;\n        chunk = remainder;\n      }\n\n      while (chunk.length >= maxEncodeChunkSize) {\n        controller.enqueue(toBase64(chunk.subarray(0, maxEncodeChunkSize)));\n        chunk = chunk.subarray(maxEncodeChunkSize);\n      }\n\n      // Encode what's encodable in what's left of the chunk\n      const { processable, leftover: nextLeftover } = getProcessableChunk(\n        chunk,\n        3\n      );\n\n      if (processable.length > 0) {\n        controller.enqueue(toBase64(processable));\n      }\n\n      leftover = nextLeftover;\n    },\n\n    flush(controller): void {\n      if (leftover != null) {\n        controller.enqueue(toBase64(leftover));\n      }\n    },\n  });\n}\n\nexport function createBase64DecoderTransformStream(\n  maxChunkSize: number = 32 * 1024\n): TransformStream<Uint8Array, Uint8Array> {\n  if (maxChunkSize % 4 !== 0 || maxChunkSize <= 0) {\n    throw new Error('maxChunkSize must be a positive multiple of 4.');\n  }\n\n  let leftover: Uint8Array | null = null;\n  let paddingSeen = false;\n\n  return new TransformStream<Uint8Array, Uint8Array>({\n    transform(chunk, controller): void {\n      try {\n        // If we see a chunk with padding, we aren't allowed to see any more data, as padding is allowed only at the end\n        if (paddingSeen && chunk.length > 0) {\n          throw new Error('Padding already seen, no further chunks allowed');\n        } else if (isPaddedBase64Chunk(chunk)) {\n          paddingSeen = true;\n        }\n\n        if (leftover != null) {\n          // We have leftovers - decode 4 bytes consisting of the leftovers + part of the chunk\n          const requiredBytes = 4 - leftover.length;\n\n          if (chunk.length < requiredBytes) {\n            // We don't have enough bytes in the chunk to decode, update leftovers\n            leftover = combineArrays(leftover, chunk);\n            // Decode when we get more chars\n            return;\n          }\n\n          const { firstChunk, remainder } = buildChunkFromLeftover(\n            leftover,\n            chunk,\n            4\n          );\n          controller.enqueue(fromBase64(firstChunk));\n          leftover = null;\n          chunk = remainder;\n        }\n\n        while (chunk.length >= maxChunkSize) {\n          controller.enqueue(fromBase64(chunk.subarray(0, maxChunkSize)));\n          chunk = chunk.subarray(maxChunkSize);\n        }\n\n        // Decode what's decodable in what's left of the chunk\n        const { processable, leftover: nextLeftover } = getProcessableChunk(\n          chunk,\n          4\n        );\n\n        if (processable.length > 0) {\n          controller.enqueue(fromBase64(processable));\n        }\n\n        leftover = nextLeftover;\n      } catch (e) {\n        controller.error(base64Error(e));\n      }\n    },\n\n    flush(controller): void {\n      if (leftover != null) {\n        // We have leftovers, but they aren't decodable\n        controller.error(\n          base64Error(new Error('Bytes left over when flushing controller'))\n        );\n      }\n    },\n  });\n}\n"
  },
  {
    "path": "src/cloudflare/internal/streaming-forms.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nconst CRLF = '\\r\\n';\n\nfunction isReadableStream(obj: unknown): obj is ReadableStream {\n  return !!(\n    obj &&\n    typeof obj === 'object' &&\n    'getReader' in obj &&\n    typeof obj.getReader === 'function'\n  );\n}\n\nfunction chainStreams<T>(streams: ReadableStream<T>[]): ReadableStream<T> {\n  const outputStream = new ReadableStream<T>({\n    async start(controller): Promise<void> {\n      for (const stream of streams) {\n        const reader = stream.getReader();\n\n        try {\n          // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n          while (true) {\n            const { done, value } = await reader.read();\n            if (done) break;\n            if (value !== undefined) controller.enqueue(value);\n          }\n        } finally {\n          reader.releaseLock();\n        }\n      }\n\n      controller.close();\n    },\n  });\n\n  return outputStream;\n}\n\ntype EntryOptions = { type: 'file' | 'string' };\n\nexport class StreamableFormData {\n  #entries: {\n    field: string;\n    value: ReadableStream;\n    options: EntryOptions;\n  }[];\n  #boundary: string;\n\n  constructor() {\n    this.#entries = [];\n\n    this.#boundary = '--------------------------';\n    for (let i = 0; i < 24; i++) {\n      this.#boundary += Math.floor(Math.random() * 10).toString(16);\n    }\n  }\n\n  append(\n    field: string,\n    value: ReadableStream | string,\n    options?: EntryOptions\n  ): void {\n    let valueStream: ReadableStream;\n    if (isReadableStream(value)) {\n      valueStream = value;\n    } else {\n      valueStream = new Blob([value]).stream();\n    }\n\n    this.#entries.push({\n      field,\n      value: valueStream,\n      options: options || { type: 'string' },\n    });\n  }\n\n  #multipartBoundary(): ReadableStream {\n    return new Blob(['--', this.#boundary, CRLF]).stream();\n  }\n\n  #multipartHeader(name: string, type: 'file' | 'string'): ReadableStream {\n    let filenamePart;\n\n    if (type === 'file') {\n      filenamePart = `; filename=\"${name}\"`;\n    } else {\n      filenamePart = '';\n    }\n\n    return new Blob([\n      `content-disposition: form-data; name=\"${name}\"${filenamePart}`,\n      CRLF,\n      CRLF,\n    ]).stream();\n  }\n\n  #multipartBody(stream: ReadableStream): ReadableStream {\n    return chainStreams([stream, new Blob([CRLF]).stream()]);\n  }\n\n  #multipartFooter(): ReadableStream {\n    return new Blob(['--', this.#boundary, '--', CRLF]).stream();\n  }\n\n  contentType(): string {\n    return `multipart/form-data; boundary=${this.#boundary}`;\n  }\n\n  stream(): ReadableStream {\n    const streams: ReadableStream[] = [this.#multipartBoundary()];\n\n    const valueStreams = [];\n    for (const { field, value, options } of this.#entries) {\n      valueStreams.push(this.#multipartHeader(field, options.type));\n      valueStreams.push(this.#multipartBody(value));\n      valueStreams.push(this.#multipartBoundary());\n    }\n\n    if (valueStreams.length) {\n      // Remove last boundary as we want a footer instead\n      valueStreams.pop();\n    }\n\n    streams.push(...valueStreams);\n\n    streams.push(this.#multipartFooter());\n\n    return chainStreams(streams);\n  }\n}\n"
  },
  {
    "path": "src/cloudflare/internal/test/BUILD.bazel",
    "content": "exports_files(\n    [\"instrumentation-test-helper.js\"],\n    visibility = [\n        \"//src/cloudflare/internal/test/d1:__pkg__\",\n        \"//src/cloudflare/internal/test/images:__pkg__\",\n        \"//src/cloudflare/internal/test/tracing:__pkg__\",\n    ],\n)\n"
  },
  {
    "path": "src/cloudflare/internal/test/ai/BUILD.bazel",
    "content": "load(\"//:build/wd_test.bzl\", \"wd_test\")\nload(\"//src/workerd/server/tests/python:py_wd_test.bzl\", \"py_wd_test\")\n\nwd_test(\n    src = \"ai-api-test.wd-test\",\n    args = [\"--experimental\"],\n    data = glob([\"*.js\"]),\n)\n\npy_wd_test(\n    size = \"large\",\n    src = \"python-ai-api-test.wd-test\",\n    args = [\"--experimental\"],\n    data = glob([\n        \"*.js\",\n        \"*.py\",\n    ]),\n    # Works but times out frequently\n    make_snapshot = False,\n    use_snapshot = None,\n)\n"
  },
  {
    "path": "src/cloudflare/internal/test/ai/ai-api-test.js",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport * as assert from 'node:assert';\n\nexport const tests = {\n  async test(_, env) {\n    {\n      // Test ai run response is object\n      const resp = await env.ai.run('testModel', { prompt: 'test' });\n      assert.deepStrictEqual(resp, { response: 'model response' });\n\n      // Test request id is present\n      assert.deepStrictEqual(\n        env.ai.lastRequestId,\n        '3a1983d7-1ddd-453a-ab75-c4358c91b582'\n      );\n      // Test request http status code is present\n      assert.deepStrictEqual(env.ai.lastRequestHttpStatusCode, 200);\n    }\n\n    {\n      // Test ai blob model run response is a blob/stream\n      const resp = await env.ai.run('blobResponseModel', { prompt: 'test' });\n      assert.deepStrictEqual(resp instanceof ReadableStream, true);\n    }\n\n    {\n      // Test legacy fetch\n      const resp = await env.ai.fetch(\n        'http://workers-binding.ai/run?version=2',\n        {\n          method: 'POST',\n          headers: { 'content-type': 'application/json' },\n          body: JSON.stringify({\n            inputs: { prompt: 'test' },\n            options: {},\n          }),\n        }\n      );\n      assert.deepStrictEqual(await resp.json(), { response: 'model response' });\n    }\n\n    {\n      // Test error response\n      try {\n        await env.ai.run('inputErrorModel', { prompt: 'test' });\n      } catch (e) {\n        assert.deepEqual(\n          {\n            name: e.name,\n            message: e.message,\n          },\n          {\n            name: 'InvalidInput',\n            message: '1001: prompt and messages are mutually exclusive',\n          }\n        );\n        // Test request internal status code is present\n        assert.deepEqual;\n        assert.deepStrictEqual(env.ai.lastRequestInternalStatusCode, 1001);\n      }\n    }\n\n    {\n      // Test error properties\n      const err = await env.ai._parseError(\n        Response.json({\n          internalCode: 1001,\n          message: 'InvalidInput: prompt and messages are mutually exclusive',\n          name: 'InvalidInput',\n          description: 'prompt and messages are mutually exclusive',\n        })\n      );\n      assert.equal(err.name, 'InvalidInput');\n      assert.equal(\n        err.message,\n        '1001: prompt and messages are mutually exclusive'\n      );\n    }\n\n    {\n      // Test error properties from non json response\n      const err = await env.ai._parseError(new Response('Unknown error'));\n      assert.equal(err.name, 'InferenceUpstreamError');\n      assert.equal(err.message, 'Unknown error');\n    }\n\n    {\n      // Test raw input\n      const resp = await env.ai.run('rawInputs', { prompt: 'test' });\n\n      assert.deepStrictEqual(resp, {\n        inputs: { prompt: 'test' },\n        options: {},\n        requestUrl: 'https://workers-binding.ai/run?version=3',\n      });\n    }\n\n    {\n      // Test one readable stream input\n      const encoder = new TextEncoder();\n      const arr = [1, 2, 3];\n      const resp = await env.ai.run('readableStreamIputs', {\n        audio: {\n          body: new ReadableStream({\n            start(controller) {\n              for (const ele of arr) {\n                controller.enqueue(encoder.encode(ele));\n              }\n              controller.close();\n            },\n          }),\n          contentType: 'audio/wav',\n        },\n      });\n\n      assert.deepStrictEqual(resp, {\n        inputs: {},\n        options: { userInputs: '{}', version: '3' },\n        requestUrl:\n          'https://workers-binding.ai/run?version=3&userInputs=%7B%7D',\n      });\n    }\n\n    {\n      // Test one readable stream input with additional parameters\n      const encoder = new TextEncoder();\n      const arr = [1, 2, 3];\n      const resp = await env.ai.run('readableStreamIputs', {\n        audio: {\n          body: new ReadableStream({\n            start(controller) {\n              for (const ele of arr) {\n                controller.enqueue(encoder.encode(ele));\n              }\n              controller.close();\n            },\n          }),\n          contentType: 'audio/wav',\n        },\n        detect_language: true,\n        prompt: 'test prompt',\n      });\n\n      assert.deepStrictEqual(resp, {\n        inputs: {},\n        options: {\n          userInputs: '{\"detect_language\":true,\"prompt\":\"test prompt\"}',\n          version: '3',\n        },\n        requestUrl:\n          'https://workers-binding.ai/run?version=3&userInputs=%7B%22detect_language%22%3Atrue%2C%22prompt%22%3A%22test+prompt%22%7D',\n      });\n    }\n\n    {\n      // Test errors from one readable stream input without content-type\n      await assert.rejects(\n        async () => {\n          const arr = [1, 2, 3];\n          const encoder = new TextEncoder();\n          const resp = await env.ai.run('readableStreamIputs', {\n            audio: {\n              body: new ReadableStream({\n                start(controller) {\n                  for (const ele of arr) {\n                    controller.enqueue(encoder.encode(ele));\n                  }\n                  controller.close();\n                },\n              }),\n            },\n          });\n        },\n        {\n          name: 'AiInternalError',\n          message: 'Content-Type is required with ReadableStream inputs',\n        }\n      );\n    }\n\n    {\n      // Test errors from two readable stream inputs\n      await assert.rejects(\n        async () => {\n          const arr = [1, 2, 3];\n          const stream = new ReadableStream({\n            start(controller) {\n              const encoder = new TextEncoder();\n              for (const ele of arr) {\n                controller.enqueue(encoder.encode(ele));\n              }\n              controller.close();\n            },\n          });\n          const resp = await env.ai.run('readableStreamIputs', {\n            audio: {\n              body: stream,\n              contentType: 'audio/wav',\n            },\n            image: {\n              body: stream,\n              contentType: 'image/png',\n            },\n          });\n        },\n        {\n          name: 'AiInternalError',\n          message:\n            'Multiple ReadableStreams are not supported. Found streams in keys: [audio, image]',\n        }\n      );\n    }\n\n    {\n      // Test form data input\n      const form = new FormData();\n      form.append('prompt', 'cat');\n      const resp = await env.ai.run('formDataInputs', {\n        audio: {\n          body: form,\n          contentType: 'multipart/form-data',\n        },\n      });\n\n      assert.deepStrictEqual(resp, {\n        inputs: {},\n        options: { userInputs: '{}', version: '3' },\n        requestUrl:\n          'https://workers-binding.ai/run?version=3&userInputs=%7B%7D',\n      });\n    }\n\n    {\n      // Test gateway option\n      const resp = await env.ai.run(\n        'rawInputs',\n        { prompt: 'test' },\n        { gateway: { id: 'my-gateway', skipCache: true } }\n      );\n\n      assert.deepStrictEqual(resp, {\n        inputs: { prompt: 'test' },\n        options: { gateway: { id: 'my-gateway', skipCache: true } },\n        requestUrl: 'https://workers-binding.ai/ai-gateway/run?version=3',\n      });\n    }\n\n    {\n      // Test unwanted options not getting sent upstream\n      const resp = await env.ai.run(\n        'rawInputs',\n        { prompt: 'test' },\n        {\n          extraHeaders: 'test',\n          example: 123,\n          gateway: { id: 'my-gateway', metadata: { employee: 1233 } },\n        }\n      );\n\n      assert.deepStrictEqual(resp, {\n        inputs: { prompt: 'test' },\n        options: {\n          example: 123,\n          gateway: { id: 'my-gateway', metadata: { employee: 1233 } },\n        },\n        requestUrl: 'https://workers-binding.ai/ai-gateway/run?version=3',\n      });\n    }\n\n    {\n      // Test models\n      const resp = await env.ai.models();\n\n      assert.deepStrictEqual(resp, [\n        {\n          id: 'f8703a00-ed54-4f98-bdc3-cd9a813286f3',\n          source: 1,\n          name: '@cf/qwen/qwen1.5-0.5b-chat',\n          description:\n            'Qwen1.5 is the improved version of Qwen, the large language model series developed by Alibaba Cloud.',\n          task: {\n            id: 'c329a1f9-323d-4e91-b2aa-582dd4188d34',\n            name: 'Text Generation',\n            description:\n              'Family of generative text models, such as large language models (LLM), that can be adapted for a variety of natural language tasks.',\n          },\n          tags: [],\n          properties: [\n            {\n              property_id: 'debug',\n              value: 'https://workers-binding.ai/ai-api/models/search',\n            },\n          ],\n        },\n      ]);\n    }\n\n    {\n      // Test models with params\n      const resp = await env.ai.models({\n        search: 'test',\n        per_page: 3,\n        page: 1,\n        task: 'asd',\n      });\n\n      assert.deepStrictEqual(resp, [\n        {\n          id: 'f8703a00-ed54-4f98-bdc3-cd9a813286f3',\n          source: 1,\n          name: '@cf/qwen/qwen1.5-0.5b-chat',\n          description:\n            'Qwen1.5 is the improved version of Qwen, the large language model series developed by Alibaba Cloud.',\n          task: {\n            id: 'c329a1f9-323d-4e91-b2aa-582dd4188d34',\n            name: 'Text Generation',\n            description:\n              'Family of generative text models, such as large language models (LLM), that can be adapted for a variety of natural language tasks.',\n          },\n          tags: [],\n          properties: [\n            {\n              property_id: 'debug',\n              value:\n                'https://workers-binding.ai/ai-api/models/search?search=test&per_page=3&page=1&task=asd',\n            },\n          ],\n        },\n      ]);\n    }\n\n    {\n      // Test `returnRawResponse` option is returning a Response object\n      const resp = await env.ai.run(\n        'rawInputs',\n        { prompt: 'test' },\n        { returnRawResponse: true }\n      );\n\n      assert.ok(resp instanceof Response);\n    }\n\n    {\n      // Test websocket option with basic inputs\n      const resp = await env.ai.run(\n        '@cf/test/websocket',\n        { encoding: 'utf8' },\n        { websocket: true }\n      );\n      assert.deepStrictEqual(resp instanceof Response, true);\n      const respData = await resp.json();\n      assert.deepStrictEqual(respData, {\n        inputs: { encoding: 'utf8' },\n        options: { websocket: true },\n        requestUrl:\n          'https://workers-binding.ai/run?version=3&body=%7B%22inputs%22%3A%7B%22encoding%22%3A%22utf8%22%7D%2C%22options%22%3A%7B%22websocket%22%3Atrue%7D%7D',\n        headers: {\n          'cf-consn-sdk-version': '2.0.0',\n          'cf-consn-model-id': '@cf/test/websocket',\n          upgrade: 'websocket',\n        },\n      });\n    }\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/ai/ai-api-test.py",
    "content": "# Copyright (c) 2024 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n\nasync def test(context, env):\n    resp = await env.ai.run(\"testModel\", {\"prompt\": \"test\"})\n    assert resp.response == \"model response\"\n\n    # Test request id is present\n    assert env.ai.lastRequestId == \"3a1983d7-1ddd-453a-ab75-c4358c91b582\"\n"
  },
  {
    "path": "src/cloudflare/internal/test/ai/ai-api-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"ai-api-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"ai-api-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"streams_enable_constructors\"],\n        bindings = [\n        (\n          name = \"ai\",\n          wrapped = (\n            moduleName = \"cloudflare-internal:ai-api\",\n            innerBindings = [(\n              name = \"fetcher\",\n              service = \"ai-mock\"\n            )],\n          )\n        )\n        ],\n      )\n    ),\n    ( name = \"ai-mock\",\n      worker = (\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n        modules = [\n          (name = \"worker\", esModule = embed \"ai-mock.js\")\n        ],\n      )\n    )\n  ]\n);\n"
  },
  {
    "path": "src/cloudflare/internal/test/ai/ai-mock.js",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nfunction base64ToBlob(base64, mimeType) {\n  const binaryString = atob(base64); // Decode Base64\n  const len = binaryString.length;\n  const bytes = new Uint8Array(len);\n  for (let i = 0; i < len; i++) {\n    bytes[i] = binaryString.charCodeAt(i);\n  }\n  return new Blob([bytes], { type: mimeType });\n}\n\nexport default {\n  async fetch(request, env, ctx) {\n    const url = new URL(request.url);\n\n    if (url.pathname === '/ai-api/models/search') {\n      return Response.json({\n        success: true,\n        result: [\n          {\n            id: 'f8703a00-ed54-4f98-bdc3-cd9a813286f3',\n            source: 1,\n            name: '@cf/qwen/qwen1.5-0.5b-chat',\n            description:\n              'Qwen1.5 is the improved version of Qwen, the large language model series developed by Alibaba Cloud.',\n            task: {\n              id: 'c329a1f9-323d-4e91-b2aa-582dd4188d34',\n              name: 'Text Generation',\n              description:\n                'Family of generative text models, such as large language models (LLM), that can be adapted for a variety of natural language tasks.',\n            },\n            tags: [],\n            properties: [\n              {\n                property_id: 'debug',\n                value: request.url,\n              },\n            ],\n          },\n        ],\n      });\n    }\n\n    const reqContentType = request.headers.get('content-type');\n\n    let data = {};\n    if (reqContentType === 'application/json') {\n      data = await request.json();\n    } else {\n      data = {\n        inputs: request.body,\n        options: Object.fromEntries(url.searchParams),\n      };\n    }\n\n    const modelName = request.headers.get('cf-consn-model-id');\n    const isWebsocket = request.headers.get('Upgrade') === 'websocket';\n\n    const respHeaders = {\n      'cf-ai-req-id': '3a1983d7-1ddd-453a-ab75-c4358c91b582',\n    };\n\n    if (modelName === 'blobResponseModel') {\n      let utf8Encode = new TextEncoder();\n      utf8Encode.encode('hello world');\n\n      return new Response(utf8Encode, {\n        headers: respHeaders,\n      });\n    }\n\n    if (modelName === 'rawInputs') {\n      return Response.json(\n        {\n          ...data,\n          requestUrl: request.url,\n        },\n        {\n          headers: respHeaders,\n        }\n      );\n    }\n\n    if (modelName === 'readableStreamIputs') {\n      return Response.json(\n        {\n          inputs: {},\n          options: { ...data.options },\n          requestUrl: request.url,\n        },\n        {\n          headers: respHeaders,\n        }\n      );\n    }\n\n    if (modelName === 'formDataInputs') {\n      return Response.json(\n        {\n          inputs: {},\n          options: { ...data.options },\n          requestUrl: request.url,\n        },\n        {\n          headers: respHeaders,\n        }\n      );\n    }\n\n    if (modelName === 'inputErrorModel') {\n      return Response.json(\n        {\n          internalCode: 1001,\n          message: 'InvalidInput: prompt and messages are mutually exclusive',\n          name: 'InvalidInput',\n          description: 'prompt and messages are mutually exclusive',\n        },\n        {\n          status: 400,\n          headers: {\n            'content-type': 'application/json',\n            ...respHeaders,\n          },\n        }\n      );\n    }\n\n    // Handle websocket requests\n    if (isWebsocket && modelName === '@cf/test/websocket') {\n      // For websocket requests, extract data from URL 'body' parameter\n      const bodyParam = url.searchParams.get('body');\n      let websocketData = {};\n      if (bodyParam) {\n        try {\n          // The AI API doesn't URL-encode the body parameter, so parse directly\n          websocketData = JSON.parse(bodyParam);\n        } catch (e) {\n          websocketData = { inputs: {}, options: {} };\n        }\n      }\n\n      return Response.json(\n        {\n          ...websocketData,\n          requestUrl: request.url,\n          headers: {\n            'cf-consn-sdk-version': '2.0.0',\n            'cf-consn-model-id': '@cf/test/websocket',\n            upgrade: 'websocket',\n          },\n        },\n        {\n          headers: respHeaders,\n        }\n      );\n    }\n\n    return Response.json(\n      { response: 'model response' },\n      {\n        headers: {\n          'content-type': 'application/json',\n          ...respHeaders,\n        },\n      }\n    );\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/ai/python-ai-api-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"ai-api-test\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"ai-api-test.py\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", %PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\"],\n        bindings = [\n        (\n          name = \"ai\",\n          wrapped = (\n            moduleName = \"cloudflare-internal:ai-api\",\n            innerBindings = [(\n              name = \"fetcher\",\n              service = \"ai-mock\"\n            )],\n          )\n        )\n        ],\n      )\n    ),\n    ( name = \"ai-mock\",\n      worker = (\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n        modules = [\n          (name = \"worker\", esModule = embed \"ai-mock.js\")\n        ],\n      )\n    )\n  ]\n);\n"
  },
  {
    "path": "src/cloudflare/internal/test/aig/BUILD.bazel",
    "content": "load(\"//:build/wd_test.bzl\", \"wd_test\")\nload(\"//src/workerd/server/tests/python:py_wd_test.bzl\", \"py_wd_test\")\n\nwd_test(\n    src = \"aig-api-test.wd-test\",\n    args = [\"--experimental\"],\n    data = glob([\"*.js\"]),\n)\n\npy_wd_test(\n    size = \"large\",\n    src = \"python-aig-api-test.wd-test\",\n    args = [\"--experimental\"],\n    data = glob([\n        \"*.js\",\n        \"*.py\",\n    ]),\n    # Works but times out\n    make_snapshot = False,\n    use_snapshot = None,\n)\n"
  },
  {
    "path": "src/cloudflare/internal/test/aig/aig-api-test.js",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport * as assert from 'node:assert';\n\nexport const tests = {\n  async test(_, env) {\n    {\n      // Test gateway get url\n      const resp = await env.ai.gateway('my-gateway').getUrl('openai');\n      assert.deepEqual(\n        resp,\n        'https://gateway.ai.cloudflare.com/v1/account-tag-abc/my-gateway/openai'\n      );\n    }\n\n    {\n      // Test gateway get log\n      const resp = await env.ai.gateway('my-gateway').getLog('my-log-123');\n      assert.deepEqual(resp, {\n        cached: false,\n        cost: 0,\n        created_at: new Date('2019-08-24T14:15:22Z'),\n        custom_cost: true,\n        duration: 0,\n        id: 'string',\n        metadata: 'string',\n        model: 'string',\n        model_type: 'string',\n        path: 'string',\n        provider: 'string',\n        request_content_type: 'string',\n        request_head: 'string',\n        request_head_complete: true,\n        request_size: 0,\n        request_type: 'string',\n        response_content_type: 'string',\n        response_head: 'string',\n        response_head_complete: true,\n        response_size: 0,\n        status_code: 0,\n        step: 0,\n        success: true,\n        tokens_in: 0,\n        tokens_out: 0,\n      });\n    }\n\n    {\n      // Test get log error responses\n      try {\n        await env.ai.gateway('my-gateway').getLog('404');\n      } catch (e) {\n        assert.deepEqual(\n          {\n            name: e.name,\n            message: e.message,\n          },\n          {\n            name: 'AiGatewayLogNotFound',\n            message: 'Not Found',\n          }\n        );\n      }\n    }\n\n    {\n      try {\n        await env.ai.gateway('my-gateway').getLog('500');\n      } catch (e) {\n        assert.deepEqual(\n          {\n            name: e.name,\n            message: e.message,\n          },\n          {\n            name: 'AiGatewayInternalError',\n            message: 'Internal Error',\n          }\n        );\n      }\n    }\n\n    {\n      // Test patch log error responses\n      try {\n        await env.ai.gateway('my-gateway').patchLog('404', { feedback: -1 });\n      } catch (e) {\n        assert.deepEqual(\n          {\n            name: e.name,\n            message: e.message,\n          },\n          {\n            name: 'AiGatewayLogNotFound',\n            message: 'Not Found',\n          }\n        );\n      }\n    }\n\n    {\n      try {\n        await env.ai.gateway('my-gateway').patchLog('500', { feedback: -1 });\n      } catch (e) {\n        assert.deepEqual(\n          {\n            name: e.name,\n            message: e.message,\n          },\n          {\n            name: 'AiGatewayInternalError',\n            message: 'Internal Error',\n          }\n        );\n      }\n    }\n\n    // Universal Run\n    {\n      const resp = await env.ai.gateway('my-gateway').run({\n        provider: 'workers-ai',\n        endpoint: '@cf/meta/llama-3.1-8b-instruct',\n        headers: {\n          Authorization: 'Bearer abcde',\n          'Content-Type': 'application/json',\n          'cf-aig-metadata': { user: 123 },\n          'cf-aig-custom-cost': { total_cost: 1.22 },\n          'cf-aig-skip-cache': false,\n          'cf-aig-cache-ttl': 123,\n        },\n        query: {\n          prompt: 'What is Cloudflare?',\n        },\n      });\n\n      const body = await resp.json();\n\n      assert.deepEqual(body, {\n        headers: {\n          'content-length': '322',\n          'content-type': 'application/json',\n        },\n        result: [\n          {\n            endpoint: '@cf/meta/llama-3.1-8b-instruct',\n            headers: {\n              'Content-Type': 'application/json',\n              'cf-aig-cache-ttl': '123',\n              'cf-aig-custom-cost': '{\"total_cost\":1.22}',\n              'cf-aig-metadata': '{\"user\":123}',\n              'cf-aig-skip-cache': 'false',\n              Authorization: 'Bearer abcde',\n            },\n            provider: 'workers-ai',\n            query: { prompt: 'What is Cloudflare?' },\n          },\n        ],\n        success: true,\n      });\n    }\n\n    // Universal Run with global options\n    {\n      const resp = await env.ai.gateway('my-gateway').run(\n        {\n          provider: 'workers-ai',\n          endpoint: '@cf/meta/llama-3.1-8b-instruct',\n          headers: {\n            Authorization: 'Bearer abcde',\n            'Content-Type': 'application/json',\n          },\n          query: {\n            prompt: 'What is Cloudflare?',\n          },\n        },\n        {\n          gateway: {\n            skipCache: false,\n            cacheTtl: 123,\n          },\n          extraHeaders: {\n            'x-custom': 'stuff',\n          },\n        }\n      );\n\n      const body = await resp.json();\n\n      assert.deepEqual(body, {\n        headers: {\n          'content-length': '189',\n          'content-type': 'application/json',\n          'cf-aig-skip-cache': 'false',\n          'cf-aig-cache-ttl': '123',\n          'x-custom': 'stuff',\n        },\n        result: [\n          {\n            endpoint: '@cf/meta/llama-3.1-8b-instruct',\n            headers: {\n              'Content-Type': 'application/json',\n              Authorization: 'Bearer abcde',\n            },\n            provider: 'workers-ai',\n            query: { prompt: 'What is Cloudflare?' },\n          },\n        ],\n        success: true,\n      });\n    }\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/aig/aig-api-test.py",
    "content": "# Copyright (c) 2024 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n\nasync def test(context, env):\n    resp = await env.ai.gateway(\"my-gateway\").getLog(\"my-log-123\")\n    assert resp.cached is False\n    assert resp.model == \"string\"\n"
  },
  {
    "path": "src/cloudflare/internal/test/aig/aig-api-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"aig-api-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"aig-api-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"],\n        bindings = [\n        (\n          name = \"ai\",\n          wrapped = (\n            moduleName = \"cloudflare-internal:ai-api\",\n            innerBindings = [(\n              name = \"fetcher\",\n              service = \"aig-mock\"\n            )],\n          )\n        )\n        ],\n      )\n    ),\n    ( name = \"aig-mock\",\n      worker = (\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n        modules = [\n          (name = \"worker\", esModule = embed \"aig-mock.js\")\n        ],\n      )\n    )\n  ]\n);\n"
  },
  {
    "path": "src/cloudflare/internal/test/aig/aig-mock.js",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport default {\n  async fetch(request, env, ctx) {\n    if (request.method === 'GET') {\n      if (request.url.endsWith('logs/404')) {\n        return Response.json(\n          {\n            errors: [\n              {\n                code: 7002,\n                message: 'Not Found',\n              },\n            ],\n            success: true,\n          },\n          { status: 404 }\n        );\n      }\n\n      if (request.url.endsWith('url/openai')) {\n        return Response.json({\n          result: {\n            url: 'https://gateway.ai.cloudflare.com/v1/account-tag-abc/my-gateway/openai',\n          },\n          success: true,\n        });\n      }\n\n      if (request.url.endsWith('logs/500')) {\n        return Response.json(\n          {\n            errors: [\n              {\n                code: 7000,\n                message: 'Internal Error',\n              },\n            ],\n            success: true,\n          },\n          { status: 500 }\n        );\n      }\n\n      return Response.json({\n        success: true,\n        result: {\n          cached: false,\n          cost: 0,\n          created_at: '2019-08-24T14:15:22Z',\n          custom_cost: true,\n          duration: 0,\n          id: 'string',\n          metadata: 'string',\n          model: 'string',\n          model_type: 'string',\n          path: 'string',\n          provider: 'string',\n          request_content_type: 'string',\n          request_head: 'string',\n          request_head_complete: true,\n          request_size: 0,\n          request_type: 'string',\n          response_content_type: 'string',\n          response_head: 'string',\n          response_head_complete: true,\n          response_size: 0,\n          status_code: 0,\n          step: 0,\n          success: true,\n          tokens_in: 0,\n          tokens_out: 0,\n        },\n      });\n    }\n\n    if (request.method === 'PATCH') {\n      if (request.url.endsWith('logs/404')) {\n        return Response.json(\n          {\n            errors: [\n              {\n                code: 7002,\n                message: 'Not Found',\n              },\n            ],\n            success: true,\n          },\n          { status: 404 }\n        );\n      }\n\n      if (request.url.endsWith('logs/500')) {\n        return Response.json(\n          {\n            errors: [\n              {\n                code: 7002,\n                message: 'Internal Error',\n              },\n            ],\n            success: true,\n          },\n          { status: 500 }\n        );\n      }\n\n      return Response.json({ success: true });\n    }\n\n    if (request.method === 'POST') {\n      const body = await request.json();\n\n      return Response.json({\n        success: true,\n        result: body,\n        headers: Object.fromEntries(request.headers.entries()),\n      });\n    }\n\n    return Response.json({ success: false }, { status: 500 });\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/aig/python-aig-api-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"aig-api-test\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"aig-api-test.py\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", %PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\"],\n        bindings = [\n        (\n          name = \"ai\",\n          wrapped = (\n            moduleName = \"cloudflare-internal:ai-api\",\n            innerBindings = [(\n              name = \"fetcher\",\n              service = \"aig-mock\"\n            )],\n          )\n        )\n        ],\n      )\n    ),\n    ( name = \"aig-mock\",\n      worker = (\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n        modules = [\n          (name = \"worker\", esModule = embed \"aig-mock.js\")\n        ],\n      )\n    )\n  ]\n);\n"
  },
  {
    "path": "src/cloudflare/internal/test/autorag/BUILD.bazel",
    "content": "load(\"//:build/wd_test.bzl\", \"wd_test\")\nload(\"//src/workerd/server/tests/python:py_wd_test.bzl\", \"py_wd_test\")\n\nwd_test(\n    src = \"autorag-api-test.wd-test\",\n    args = [\"--experimental\"],\n    data = glob([\"*.js\"]),\n)\n\npy_wd_test(\n    size = \"large\",\n    src = \"python-autorag-api-test.wd-test\",\n    args = [\"--experimental\"],\n    data = glob([\n        \"*.js\",\n        \"*.py\",\n    ]),\n    # Works but times out\n    make_snapshot = False,\n    use_snapshot = None,\n)\n"
  },
  {
    "path": "src/cloudflare/internal/test/autorag/autorag-api-test.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport * as assert from 'node:assert';\n\nexport const tests = {\n  async test(_, env) {\n    {\n      // Test search method\n      const resp = await env.ai\n        .autorag('my-rag')\n        .search({ query: 'example query' });\n\n      assert.deepEqual(\n        resp.data[0].content[0].text,\n        'According to the latest regulations, each passenger is allowed to carry up to two woodchucks.'\n      );\n    }\n\n    {\n      // Test ai search method\n      const resp = await env.ai\n        .autorag('my-rag')\n        .aiSearch({ query: 'example query' });\n\n      assert.deepEqual(resp.response, 'this is an example result');\n    }\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/autorag/autorag-api-test.py",
    "content": "# Copyright (c) 2025 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n\nasync def test(context, env):\n    resp = await env.ai.autorag(\"my-rag\").aiSearch({\"query\": \"example query\"})\n    assert resp.response == \"this is an example result\"\n"
  },
  {
    "path": "src/cloudflare/internal/test/autorag/autorag-api-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"autorag-api-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"autorag-api-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"],\n        bindings = [\n        (\n          name = \"ai\",\n          wrapped = (\n            moduleName = \"cloudflare-internal:ai-api\",\n            innerBindings = [(\n              name = \"fetcher\",\n              service = \"autorag-mock\"\n            )],\n          )\n        )\n        ],\n      )\n    ),\n    ( name = \"autorag-mock\",\n      worker = (\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n        modules = [\n          (name = \"worker\", esModule = embed \"autorag-mock.js\")\n        ],\n      )\n    )\n  ]\n);\n"
  },
  {
    "path": "src/cloudflare/internal/test/autorag/autorag-mock.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport default {\n  async fetch(request, env, ctx) {\n    if (request.method === 'POST') {\n      if (request.url.includes('/ai-search')) {\n        return Response.json({\n          result: {\n            object: 'vector_store.search_results.page',\n            search_query: 'How many woodchucks are allowed per passenger?',\n            data: [\n              {\n                file_id: 'file-12345',\n                filename: 'woodchuck_policy.txt',\n                score: 0.85,\n                attributes: {\n                  region: 'North America',\n                  author: 'Wildlife Department',\n                },\n                content: [\n                  {\n                    type: 'text',\n                    text: 'According to the latest regulations, each passenger is allowed to carry up to two woodchucks.',\n                  },\n                  {\n                    type: 'text',\n                    text: 'Ensure that the woodchucks are properly contained during transport.',\n                  },\n                ],\n              },\n              {\n                file_id: 'file-67890',\n                filename: 'transport_guidelines.txt',\n                score: 0.75,\n                attributes: {\n                  region: 'North America',\n                  author: 'Transport Authority',\n                },\n                content: [\n                  {\n                    type: 'text',\n                    text: 'Passengers must adhere to the guidelines set forth by the Transport Authority regarding the transport of woodchucks.',\n                  },\n                ],\n              },\n            ],\n            has_more: false,\n            next_page: null,\n            response: 'this is an example result',\n          },\n          success: true,\n        });\n      }\n\n      if (request.url.includes('/search')) {\n        return Response.json({\n          result: {\n            object: 'vector_store.search_results.page',\n            search_query: 'How many woodchucks are allowed per passenger?',\n            data: [\n              {\n                file_id: 'file-12345',\n                filename: 'woodchuck_policy.txt',\n                score: 0.85,\n                attributes: {\n                  region: 'North America',\n                  author: 'Wildlife Department',\n                },\n                content: [\n                  {\n                    type: 'text',\n                    text: 'According to the latest regulations, each passenger is allowed to carry up to two woodchucks.',\n                  },\n                  {\n                    type: 'text',\n                    text: 'Ensure that the woodchucks are properly contained during transport.',\n                  },\n                ],\n              },\n              {\n                file_id: 'file-67890',\n                filename: 'transport_guidelines.txt',\n                score: 0.75,\n                attributes: {\n                  region: 'North America',\n                  author: 'Transport Authority',\n                },\n                content: [\n                  {\n                    type: 'text',\n                    text: 'Passengers must adhere to the guidelines set forth by the Transport Authority regarding the transport of woodchucks.',\n                  },\n                ],\n              },\n            ],\n            has_more: false,\n            next_page: null,\n          },\n          success: true,\n        });\n      }\n    }\n\n    return Response.json({ success: false }, { status: 500 });\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/autorag/python-autorag-api-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"autorag-api-test\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"autorag-api-test.py\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", %PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\"],\n        bindings = [\n        (\n          name = \"ai\",\n          wrapped = (\n            moduleName = \"cloudflare-internal:ai-api\",\n            innerBindings = [(\n              name = \"fetcher\",\n              service = \"autorag-mock\"\n            )],\n          )\n        )\n        ],\n      )\n    ),\n    ( name = \"autorag-mock\",\n      worker = (\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n        modules = [\n          (name = \"worker\", esModule = embed \"autorag-mock.js\")\n        ],\n      )\n    )\n  ]\n);\n"
  },
  {
    "path": "src/cloudflare/internal/test/br/BUILD.bazel",
    "content": "load(\"//:build/wd_test.bzl\", \"wd_test\")\nload(\"//src/workerd/server/tests/python:py_wd_test.bzl\", \"py_wd_test\")\n\nwd_test(\n    src = \"br-api-test.wd-test\",\n    args = [\"--experimental\"],\n    data = glob([\"*.js\"]),\n)\n\npy_wd_test(\n    size = \"large\",\n    src = \"python-br-api-test.wd-test\",\n    args = [\"--experimental\"],\n    data = glob([\n        \"*.js\",\n        \"*.py\",\n    ]),\n    # Works but times out\n    make_snapshot = False,\n    use_snapshot = None,\n)\n"
  },
  {
    "path": "src/cloudflare/internal/test/br/br-api-test.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport * as assert from 'node:assert';\n\nexport const tests = {\n  async test(_, env) {\n    {\n      // Test legacy fetch\n      const resp = await env.browser.fetch('http://workers-binding.brapi/run', {\n        method: 'POST',\n        headers: { 'content-type': 'application/json' },\n        body: JSON.stringify({\n          inputs: { test: true },\n        }),\n      });\n      assert.deepStrictEqual(await resp.json(), { success: true });\n    }\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/br/br-api-test.py",
    "content": "# Copyright (c) 2025 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n\nasync def test(context, env):\n    resp = await env.browser.fetch(\"http://workers-binding.brapi/run\")\n    data = await resp.json()\n    assert data.success is True\n"
  },
  {
    "path": "src/cloudflare/internal/test/br/br-api-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"brapi-api-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"br-api-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"],\n        bindings = [\n        (\n          name = \"browser\",\n          wrapped = (\n            moduleName = \"cloudflare-internal:br-api\",\n            innerBindings = [(\n              name = \"fetcher\",\n              service = \"br-mock\"\n            )],\n          )\n        )\n        ],\n      )\n    ),\n    ( name = \"br-mock\",\n      worker = (\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n        modules = [\n          (name = \"worker\", esModule = embed \"br-mock.js\")\n        ],\n      )\n    )\n  ]\n);\n"
  },
  {
    "path": "src/cloudflare/internal/test/br/br-mock.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport default {\n  async fetch(request, env, ctx) {\n    return Response.json(\n      { success: true },\n      {\n        headers: {\n          'content-type': 'application/json',\n        },\n      }\n    );\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/br/python-br-api-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"br-api-test\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"br-api-test.py\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", %PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\"],\n        bindings = [\n        (\n          name = \"browser\",\n          wrapped = (\n            moduleName = \"cloudflare-internal:br-api\",\n            innerBindings = [(\n              name = \"fetcher\",\n              service = \"br-mock\"\n            )],\n          )\n        )\n        ],\n      )\n    ),\n    ( name = \"br-mock\",\n      worker = (\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n        modules = [\n          (name = \"worker\", esModule = embed \"br-mock.js\")\n        ],\n      )\n    )\n  ]\n);\n"
  },
  {
    "path": "src/cloudflare/internal/test/d1/BUILD.bazel",
    "content": "load(\"//:build/wd_test.bzl\", \"wd_test\")\nload(\"//src/workerd/server/tests/python:py_wd_test.bzl\", \"py_wd_test\")\n\nwd_test(\n    size = \"large\",\n    src = \"d1-api-test.wd-test\",\n    args = [\"--experimental\"],\n    data = glob(\n        [\"*.js\"],\n        exclude = [\"d1-api-test-with-sessions.js\"],\n    ) + [\"//src/cloudflare/internal/test:instrumentation-test-helper.js\"],\n)\n\nwd_test(\n    size = \"enormous\",\n    src = \"d1-api-test-with-sessions.wd-test\",\n    args = [\"--experimental\"],\n    data = glob([\"*.js\"]),\n)\n\npy_wd_test(\n    size = \"large\",\n    src = \"python-d1-api-test.wd-test\",\n    args = [\"--experimental\"],\n    data = glob([\n        \"*.py\",\n        \"*.js\",\n    ]),\n    make_snapshot = False,\n    use_snapshot = None,\n)\n"
  },
  {
    "path": "src/cloudflare/internal/test/d1/d1-api-instrumentation-test.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport {\n  createInstrumentationState,\n  createTailStreamHandler,\n  runInstrumentationTest,\n} from 'instrumentation-test-helper';\n\n// Create module-level state using the helper\nconst state = createInstrumentationState();\n\nexport default {\n  tailStream: createTailStreamHandler(state),\n};\n\nexport const test = {\n  async test() {\n    await runInstrumentationTest(state, expectedSpans, {\n      testName: 'D1 instrumentation',\n      mapFn: (span) => {\n        // varies test-by-test because random duration in d1-mock.js\n        if (span['http.response.body.size']) {\n          span['http.response.body.size'] = 0n;\n        }\n        return span;\n      },\n    });\n  },\n};\n\nconst expectedSpans = [\n  // testExec: exec() happy path and error handling (regression test for #5218).\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/execute?resultsFormat=NONE',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 20n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_exec',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'exec',\n    'db.query.text': 'select 1',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 4096,\n    'cloudflare.d1.response.rows_read': 0,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 0,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/execute?resultsFormat=NONE',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 23n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_exec',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'exec',\n    'db.query.text': 'INVALID SQL',\n    'cloudflare.binding.type': 'D1',\n    'error.type': 'Error in line 1',\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/execute?resultsFormat=NONE',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 231n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_run',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'run',\n    'db.query.text':\n      ' CREATE TABLE users\\n' +\n      '        (\\n' +\n      '            user_id    INTEGER PRIMARY KEY,\\n' +\n      '            name       TEXT,\\n' +\n      '            home       TEXT,\\n' +\n      '            features   TEXT,\\n' +\n      '            land_based BOOLEAN\\n' +\n      '        );',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 1,\n    'cloudflare.d1.response.rows_written': 2,\n    'cloudflare.d1.response.last_row_id': 0,\n    'cloudflare.d1.response.changed_db': true,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 42n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_all',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'all',\n    'db.query.text': 'SELECT * FROM users;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 1,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 0,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/execute?resultsFormat=NONE',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 42n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_run',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'run',\n    'db.query.text': 'SELECT * FROM users;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 1,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 0,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/execute?resultsFormat=NONE',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 40n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_run',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'run',\n    'db.query.text': 'DELETE FROM users;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 0,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 0,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 223n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_all',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'all',\n    'db.query.text':\n      '\\n' +\n      '        INSERT INTO users (name, home, features, land_based) VALUES\\n' +\n      \"          ('Albert Ross', 'sky', 'wingspan', false),\\n\" +\n      \"          ('Al Dente', 'bowl', 'mouthfeel', true)\\n\" +\n      '        RETURNING *\\n' +\n      '    ',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 4,\n    'cloudflare.d1.response.rows_written': 2,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': true,\n    'cloudflare.d1.response.changes': 2,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/execute?resultsFormat=NONE',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 40n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_run',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'run',\n    'db.query.text': 'DELETE FROM users;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 0,\n    'cloudflare.d1.response.rows_written': 2,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': true,\n    'cloudflare.d1.response.changes': 2,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/execute?resultsFormat=NONE',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 223n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_run',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'run',\n    'db.query.text':\n      '\\n' +\n      '        INSERT INTO users (name, home, features, land_based) VALUES\\n' +\n      \"          ('Albert Ross', 'sky', 'wingspan', false),\\n\" +\n      \"          ('Al Dente', 'bowl', 'mouthfeel', true)\\n\" +\n      '        RETURNING *\\n' +\n      '    ',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 4,\n    'cloudflare.d1.response.rows_written': 2,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': true,\n    'cloudflare.d1.response.changes': 2,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 31n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_all',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'all',\n    'db.query.text': 'select 1;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 0,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 31n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_all',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'raw',\n    'db.query.text': 'select 1;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 0,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 31n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_first',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'first',\n    'db.query.text': 'select 1;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 0,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 31n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_first',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'first',\n    'db.query.text': 'select 1;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 0,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 42n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_all',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'all',\n    'db.query.text': 'SELECT * FROM users;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 2,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 42n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_all',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'raw',\n    'db.query.text': 'SELECT * FROM users;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 2,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 42n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_first',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'first',\n    'db.query.text': 'SELECT * FROM users;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 2,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 42n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_first',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'first',\n    'db.query.text': 'SELECT * FROM users;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 2,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 61n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_all',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'all',\n    'db.query.text': 'SELECT * FROM users WHERE user_id = ?;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 1,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 61n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_all',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'raw',\n    'db.query.text': 'SELECT * FROM users WHERE user_id = ?;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 1,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 61n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_first',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'first',\n    'db.query.text': 'SELECT * FROM users WHERE user_id = ?;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 1,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 61n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_first',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'first',\n    'db.query.text': 'SELECT * FROM users WHERE user_id = ?;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 1,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 61n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_all',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'all',\n    'db.query.text': 'SELECT * FROM users WHERE user_id = ?;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 1,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 61n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_all',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'raw',\n    'db.query.text': 'SELECT * FROM users WHERE user_id = ?;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 1,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 61n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_first',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'first',\n    'db.query.text': 'SELECT * FROM users WHERE user_id = ?;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 1,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 61n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_first',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'first',\n    'db.query.text': 'SELECT * FROM users WHERE user_id = ?;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 1,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 125n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_batch',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'batch',\n    'db.query.text':\n      'SELECT * FROM users WHERE user_id = ?;\\n' +\n      'SELECT * FROM users WHERE user_id = ?;',\n    'db.operation.batch.size': 2,\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 2,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 79n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_first',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'first',\n    'db.query.text': 'SELECT count(1) as count FROM users WHERE land_based = ?',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 2,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 79n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_first',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'first',\n    'db.query.text': 'SELECT count(1) as count FROM users WHERE land_based = ?',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 2,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 79n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_first',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'first',\n    'db.query.text': 'SELECT count(1) as count FROM users WHERE land_based = ?',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 2,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 79n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_first',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'first',\n    'db.query.text': 'SELECT count(1) as count FROM users WHERE land_based = ?',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 2,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 79n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_first',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'first',\n    'db.query.text': 'SELECT count(1) as count FROM users WHERE land_based = ?',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 8192,\n    'cloudflare.d1.response.rows_read': 2,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 2,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 298n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_batch',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'batch',\n    'db.query.text':\n      'CREATE TABLE abc (a INT, b INT, c INT);\\n' +\n      'CREATE TABLE cde (c TEXT, d TEXT, e TEXT);\\n' +\n      'INSERT INTO abc VALUES (1,2,3),(4,5,6);\\n' +\n      'INSERT INTO cde VALUES (\"A\", \"B\", \"C\"),(\"D\",\"E\",\"F\"),(\"G\",\"H\",\"I\");',\n    'db.operation.batch.size': 4,\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 16384,\n    'cloudflare.d1.response.rows_read': 2,\n    'cloudflare.d1.response.rows_written': 9,\n    'cloudflare.d1.response.last_row_id': 3,\n    'cloudflare.d1.response.changed_db': true,\n    'cloudflare.d1.response.changes': 5,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 45n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_all',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'all',\n    'db.query.text': 'SELECT * FROM abc, cde;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 16384,\n    'cloudflare.d1.response.rows_read': 8,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 3,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 45n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_all',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'raw',\n    'db.query.text': 'SELECT * FROM abc, cde;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 16384,\n    'cloudflare.d1.response.rows_read': 8,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 3,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 45n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_all',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'raw',\n    'db.query.text': 'SELECT * FROM abc, cde;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 16384,\n    'cloudflare.d1.response.rows_read': 8,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 3,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 45n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_all',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'raw',\n    'db.query.text': 'SELECT * FROM abc, cde;',\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 16384,\n    'cloudflare.d1.response.rows_read': 8,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 3,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 76n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_all',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'all',\n    'db.query.text': \"SELECT * from cde WHERE c IN ('A','B','C','X','Y','Z')\",\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 16384,\n    'cloudflare.d1.response.rows_read': 3,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 3,\n    'cloudflare.d1.response.changed_db': false,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'http://d1/query?resultsFormat=ROWS_AND_COLUMNS',\n    'http.request.header.content-type': 'application/json',\n    'http.request.body.size': 117n,\n    'http.response.status_code': 200n,\n    'http.response.body.size': 0n,\n    closed: true,\n  },\n  {\n    name: 'd1_batch',\n    'db.system.name': 'cloudflare-d1',\n    'db.operation.name': 'batch',\n    'db.query.text': 'DROP TABLE users;\\nDROP TABLE abc;\\nDROP TABLE cde;',\n    'db.operation.batch.size': 3,\n    'cloudflare.binding.type': 'D1',\n    'cloudflare.d1.response.size_after': 4096,\n    'cloudflare.d1.response.rows_read': 9,\n    'cloudflare.d1.response.rows_written': 0,\n    'cloudflare.d1.response.last_row_id': 3,\n    'cloudflare.d1.response.changed_db': true,\n    'cloudflare.d1.response.changes': 0,\n    closed: true,\n  },\n];\n"
  },
  {
    "path": "src/cloudflare/internal/test/d1/d1-api-test-common.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport * as assert from 'node:assert';\n\n// Recurse through nested objects/arrays looking for 'anything' and deleting that\n// key/value from both objects. Gives us a way to get expect.toMatchObject behavior\n// with only deepEqual\nconst anything = Symbol('anything');\nconst deleteAnything = (expected, actual) => {\n  Object.entries(expected).forEach(([k, v]) => {\n    if (v === anything) {\n      delete actual[k];\n      delete expected[k];\n    } else if (typeof v === 'object' && typeof actual[k] === 'object') {\n      deleteAnything(expected[k], actual[k]);\n    }\n  });\n};\n\n// Test helpers, since I want everything to run in sequence but I don't\n// want to lose context about which assertion failed.\nexport const itShould = async (description, ...assertions) => {\n  if (assertions.length % 2 !== 0)\n    throw new Error('itShould takes pairs of cb, expected args');\n\n  try {\n    for (let i = 0; i < assertions.length; i += 2) {\n      const cb = assertions[i];\n      const expected = assertions[i + 1];\n      const actual = await cb();\n      deleteAnything(expected, actual);\n      try {\n        assert.deepEqual(actual, expected);\n      } catch (e) {\n        console.log(actual);\n        throw e;\n      }\n    }\n  } catch (e) {\n    throw new Error(`TEST ERROR!\\n❌ Failed to ${description}\\n${e.message}`);\n  }\n};\n\n// Make it easy to specify only a the meta properties we're interested in.\n// Anything specified here as `anything` won't be checked.\nconst meta = (values) => ({\n  duration: anything,\n  served_by: anything,\n  served_by_primary: anything,\n  served_by_region: anything,\n  served_by_colo: anything,\n  timings: anything,\n  changes: anything,\n  last_row_id: anything,\n  changed_db: anything,\n  size_after: anything,\n  rows_read: anything,\n  rows_written: anything,\n  total_attempts: anything,\n  ...values,\n});\n\nexport async function testD1ApiQueriesHappyPath(DB) {\n  await itShould(\n    'create a Users table',\n    () =>\n      DB.prepare(\n        ` CREATE TABLE users\n        (\n            user_id    INTEGER PRIMARY KEY,\n            name       TEXT,\n            home       TEXT,\n            features   TEXT,\n            land_based BOOLEAN\n        );`\n      ).run(),\n    { success: true, results: [], meta: anything }\n  );\n\n  await itShould(\n    'select an empty set',\n    () => DB.prepare(`SELECT * FROM users;`).all(),\n    {\n      success: true,\n      results: [],\n      meta: meta({ changed_db: false }),\n    }\n  );\n\n  await itShould(\n    'have no results for .run()',\n    () => DB.prepare(`SELECT * FROM users;`).run(),\n    { success: true, results: [], meta: anything }\n  );\n\n  await itShould(\n    'delete no rows ok',\n    () => DB.prepare(`DELETE FROM users;`).run(),\n    { success: true, results: [], meta: anything }\n  );\n\n  await itShould(\n    'insert a few rows with a returning statement',\n    () =>\n      DB.prepare(\n        `\n        INSERT INTO users (name, home, features, land_based) VALUES\n          ('Albert Ross', 'sky', 'wingspan', false),\n          ('Al Dente', 'bowl', 'mouthfeel', true)\n        RETURNING *\n    `\n      ).all(),\n    {\n      success: true,\n      results: [\n        {\n          user_id: 1,\n          name: 'Albert Ross',\n          home: 'sky',\n          features: 'wingspan',\n          land_based: 0,\n        },\n        {\n          user_id: 2,\n          name: 'Al Dente',\n          home: 'bowl',\n          features: 'mouthfeel',\n          land_based: 1,\n        },\n      ],\n      meta: anything,\n    }\n  );\n\n  await itShould(\n    'delete two rows ok',\n    () => DB.prepare(`DELETE FROM users;`).run(),\n    { success: true, results: [], meta: anything }\n  );\n\n  // In an earlier implementation, .run() called a different endpoint that threw on RETURNING clauses.\n  await itShould(\n    'insert a few rows with a returning statement, but ignore the result without erroring',\n    () =>\n      DB.prepare(\n        `\n        INSERT INTO users (name, home, features, land_based) VALUES\n          ('Albert Ross', 'sky', 'wingspan', false),\n          ('Al Dente', 'bowl', 'mouthfeel', true)\n        RETURNING *\n    `\n      ).run(),\n    {\n      success: true,\n      results: [\n        {\n          user_id: 1,\n          name: 'Albert Ross',\n          home: 'sky',\n          features: 'wingspan',\n          land_based: 0,\n        },\n        {\n          user_id: 2,\n          name: 'Al Dente',\n          home: 'bowl',\n          features: 'mouthfeel',\n          land_based: 1,\n        },\n      ],\n      meta: anything,\n    }\n  );\n\n  // Results format tests\n\n  const select_1 = DB.prepare(`select 1;`);\n  await itShould(\n    'return simple results for select 1',\n    () => select_1.all(),\n    {\n      results: [{ 1: 1 }],\n      meta: anything,\n      success: true,\n    },\n    () => select_1.raw(),\n    [[1]],\n    () => select_1.first(),\n    { 1: 1 },\n    () => select_1.first('1'),\n    1\n  );\n\n  const select_all = DB.prepare(`SELECT * FROM users;`);\n  await itShould(\n    'return all users',\n    () => select_all.all(),\n    {\n      results: [\n        {\n          user_id: 1,\n          name: 'Albert Ross',\n          home: 'sky',\n          features: 'wingspan',\n          land_based: 0,\n        },\n        {\n          user_id: 2,\n          name: 'Al Dente',\n          home: 'bowl',\n          features: 'mouthfeel',\n          land_based: 1,\n        },\n      ],\n      meta: anything,\n      success: true,\n    },\n    () => select_all.raw(),\n    [\n      [1, 'Albert Ross', 'sky', 'wingspan', 0],\n      [2, 'Al Dente', 'bowl', 'mouthfeel', 1],\n    ],\n    () => select_all.first(),\n    {\n      user_id: 1,\n      name: 'Albert Ross',\n      home: 'sky',\n      features: 'wingspan',\n      land_based: 0,\n    },\n    () => select_all.first('name'),\n    'Albert Ross'\n  );\n\n  const select_one = DB.prepare(`SELECT * FROM users WHERE user_id = ?;`);\n\n  await itShould(\n    'return the first user when bound with user_id = 1',\n    () => select_one.bind(1).all(),\n    {\n      results: [\n        {\n          user_id: 1,\n          name: 'Albert Ross',\n          home: 'sky',\n          features: 'wingspan',\n          land_based: 0,\n        },\n      ],\n      meta: anything,\n      success: true,\n    },\n    () => select_one.bind(1).raw(),\n    [[1, 'Albert Ross', 'sky', 'wingspan', 0]],\n    () => select_one.bind(1).first(),\n    {\n      user_id: 1,\n      name: 'Albert Ross',\n      home: 'sky',\n      features: 'wingspan',\n      land_based: 0,\n    },\n    () => select_one.bind(1).first('name'),\n    'Albert Ross'\n  );\n\n  await itShould(\n    'return the second user when bound with user_id = 2',\n    () => select_one.bind(2).all(),\n    {\n      results: [\n        {\n          user_id: 2,\n          name: 'Al Dente',\n          home: 'bowl',\n          features: 'mouthfeel',\n          land_based: 1,\n        },\n      ],\n      meta: anything,\n      success: true,\n    },\n    () => select_one.bind(2).raw(),\n    [[2, 'Al Dente', 'bowl', 'mouthfeel', 1]],\n    () => select_one.bind(2).first(),\n    {\n      user_id: 2,\n      name: 'Al Dente',\n      home: 'bowl',\n      features: 'mouthfeel',\n      land_based: 1,\n    },\n    () => select_one.bind(2).first('name'),\n    'Al Dente'\n  );\n\n  await itShould(\n    'return the results of two commands with batch',\n    () => DB.batch([select_one.bind(2), select_one.bind(1)]),\n    [\n      {\n        results: [\n          {\n            user_id: 2,\n            name: 'Al Dente',\n            home: 'bowl',\n            features: 'mouthfeel',\n            land_based: 1,\n          },\n        ],\n        meta: anything,\n        success: true,\n      },\n      {\n        results: [\n          {\n            user_id: 1,\n            name: 'Albert Ross',\n            home: 'sky',\n            features: 'wingspan',\n            land_based: 0,\n          },\n        ],\n        meta: anything,\n        success: true,\n      },\n    ]\n  );\n\n  await itShould(\n    'allow binding all types of parameters',\n    () =>\n      DB.prepare(`SELECT count(1) as count FROM users WHERE land_based = ?`)\n        .bind(true)\n        .first('count'),\n    1,\n    () =>\n      DB.prepare(`SELECT count(1) as count FROM users WHERE land_based = ?`)\n        .bind(false)\n        .first('count'),\n    1,\n    () =>\n      DB.prepare(`SELECT count(1) as count FROM users WHERE land_based = ?`)\n        .bind(0)\n        .first('count'),\n    1,\n    () =>\n      DB.prepare(`SELECT count(1) as count FROM users WHERE land_based = ?`)\n        .bind(1)\n        .first('count'),\n    1,\n    () =>\n      DB.prepare(`SELECT count(1) as count FROM users WHERE land_based = ?`)\n        .bind(2)\n        .first('count'),\n    0\n  );\n\n  await itShould(\n    'create two tables with overlapping column names',\n    () =>\n      DB.batch([\n        DB.prepare(`CREATE TABLE abc (a INT, b INT, c INT);`),\n        DB.prepare(`CREATE TABLE cde (c TEXT, d TEXT, e TEXT);`),\n        DB.prepare(`INSERT INTO abc VALUES (1,2,3),(4,5,6);`),\n        DB.prepare(\n          `INSERT INTO cde VALUES (\"A\", \"B\", \"C\"),(\"D\",\"E\",\"F\"),(\"G\",\"H\",\"I\");`\n        ),\n      ]),\n    [\n      {\n        success: true,\n        results: [],\n        meta: meta({\n          changed_db: true,\n          changes: 0,\n          last_row_id: 2,\n          rows_read: 1,\n          rows_written: 2,\n        }),\n      },\n      {\n        success: true,\n        results: [],\n        meta: meta({\n          changed_db: true,\n          changes: 0,\n          last_row_id: 2,\n          rows_read: 1,\n          rows_written: 2,\n        }),\n      },\n      {\n        success: true,\n        results: [],\n        meta: meta({\n          changed_db: true,\n          changes: 2,\n          last_row_id: 2,\n          rows_read: 0,\n          rows_written: 2,\n        }),\n      },\n      {\n        success: true,\n        results: [],\n        meta: meta({\n          changed_db: true,\n          changes: 3,\n          last_row_id: 3,\n          rows_read: 0,\n          rows_written: 3,\n        }),\n      },\n    ]\n  );\n\n  await itShould(\n    'still sadly lose data for duplicate columns in a join',\n    () => DB.prepare(`SELECT * FROM abc, cde;`).all(),\n    {\n      success: true,\n      results: [\n        { a: 1, b: 2, c: 'A', d: 'B', e: 'C' },\n        { a: 1, b: 2, c: 'D', d: 'E', e: 'F' },\n        { a: 1, b: 2, c: 'G', d: 'H', e: 'I' },\n        { a: 4, b: 5, c: 'A', d: 'B', e: 'C' },\n        { a: 4, b: 5, c: 'D', d: 'E', e: 'F' },\n        { a: 4, b: 5, c: 'G', d: 'H', e: 'I' },\n      ],\n      meta: meta({\n        changed_db: false,\n        changes: 0,\n        rows_read: 8,\n        rows_written: 0,\n      }),\n    }\n  );\n\n  await itShould(\n    'not lose data for duplicate columns in a join using raw()',\n    () => DB.prepare(`SELECT * FROM abc, cde;`).raw(),\n    [\n      [1, 2, 3, 'A', 'B', 'C'],\n      [1, 2, 3, 'D', 'E', 'F'],\n      [1, 2, 3, 'G', 'H', 'I'],\n      [4, 5, 6, 'A', 'B', 'C'],\n      [4, 5, 6, 'D', 'E', 'F'],\n      [4, 5, 6, 'G', 'H', 'I'],\n    ]\n  );\n\n  await itShould(\n    'add columns using  .raw({ columnNames: true })',\n    () => DB.prepare(`SELECT * FROM abc, cde;`).raw({ columnNames: true }),\n    [\n      ['a', 'b', 'c', 'c', 'd', 'e'],\n      [1, 2, 3, 'A', 'B', 'C'],\n      [1, 2, 3, 'D', 'E', 'F'],\n      [1, 2, 3, 'G', 'H', 'I'],\n      [4, 5, 6, 'A', 'B', 'C'],\n      [4, 5, 6, 'D', 'E', 'F'],\n      [4, 5, 6, 'G', 'H', 'I'],\n    ]\n  );\n\n  await itShould(\n    'not add columns using  .raw({ columnNames: false })',\n    () => DB.prepare(`SELECT * FROM abc, cde;`).raw({ columnNames: false }),\n    [\n      [1, 2, 3, 'A', 'B', 'C'],\n      [1, 2, 3, 'D', 'E', 'F'],\n      [1, 2, 3, 'G', 'H', 'I'],\n      [4, 5, 6, 'A', 'B', 'C'],\n      [4, 5, 6, 'D', 'E', 'F'],\n      [4, 5, 6, 'G', 'H', 'I'],\n    ]\n  );\n\n  await itShould(\n    'return 0 rows_written for IN clauses',\n    () =>\n      DB.prepare(\n        `SELECT * from cde WHERE c IN ('A','B','C','X','Y','Z')`\n      ).all(),\n    {\n      success: true,\n      results: [{ c: 'A', d: 'B', e: 'C' }],\n      meta: meta({ rows_read: 3, rows_written: 0 }),\n    }\n  );\n\n  await itShould(\n    'delete all created tables',\n    () =>\n      DB.batch([\n        DB.prepare(`DROP TABLE users;`),\n        DB.prepare(`DROP TABLE abc;`),\n        DB.prepare(`DROP TABLE cde;`),\n      ]),\n    [\n      {\n        success: true,\n        results: [],\n        meta: meta({\n          changed_db: true,\n          changes: 0,\n          last_row_id: 3,\n          rows_read: anything,\n          rows_written: 0,\n        }),\n      },\n      {\n        success: true,\n        results: [],\n        meta: meta({\n          changed_db: true,\n          changes: 0,\n          last_row_id: 3,\n          rows_read: anything,\n          rows_written: 0,\n        }),\n      },\n      {\n        success: true,\n        results: [],\n        meta: meta({\n          changed_db: true,\n          changes: 0,\n          last_row_id: 3,\n          rows_read: anything,\n          rows_written: 0,\n        }),\n      },\n    ]\n  );\n}\n\n// Regression test for https://github.com/cloudflare/workerd/pull/5218.\n// exec() with invalid SQL should throw a proper D1 error, not a TypeError\n// from accessing properties on undefined meta during span aggregation.\nexport async function testD1Exec(DB) {\n  await itShould('run a simple exec', () => DB.exec('select 1'), {\n    count: 1,\n    duration: anything,\n  });\n\n  await assert.rejects(\n    () => DB.exec('INVALID SQL'),\n    (e) => {\n      assert.notEqual(e.constructor, TypeError);\n      assert.ok(\n        e.message.includes('D1_EXEC_ERROR'),\n        `Expected D1 error, got: ${e.message}`\n      );\n      return true;\n    }\n  );\n}\n"
  },
  {
    "path": "src/cloudflare/internal/test/d1/d1-api-test-with-sessions.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport * as assert from 'node:assert';\n\nimport { itShould, testD1ApiQueriesHappyPath } from './d1-api-test-common';\n\nconst test = (fn, getDB = getDBFromEnv) => ({\n  async test(ctr, env) {\n    await fn(getDB(env), env.d1MockFetcher);\n  },\n});\n\nfunction getDBFromEnv(env) {\n  return env.d1;\n}\n\nexport const test_d1_api_happy_path = test(\n  testD1ApiQueriesHappyPath,\n  getDBFromEnv\n);\n\nexport const test_d1_api_happy_path_withsessions_default = test(\n  testD1ApiQueriesHappyPath,\n  (env) => getDBFromEnv(env).withSession()\n);\n\nexport const test_d1_api_happy_path_withsessions_first_unconstrained = test(\n  testD1ApiQueriesHappyPath,\n  (env) => getDBFromEnv(env).withSession('first-unconstrained')\n);\n\nexport const test_d1_api_happy_path_withsessions_first_primary = test(\n  testD1ApiQueriesHappyPath,\n  (env) => getDBFromEnv(env).withSession('first-primary')\n);\n\nexport const test_d1_api_happy_path_withsessions_some_ranomd_token = test(\n  testD1ApiQueriesHappyPath,\n  (env) => getDBFromEnv(env).withSession('token-doesnot-matter-for-now')\n);\n\n// envD1MockFetcher is the default export worker in `d1-mock.js`, i.e. the `fetch()` entry point.\nconst getCommitTokensSentFromBinding = async (envD1MockFetcher) =>\n  (\n    await (\n      await envD1MockFetcher.fetch(`http://d1-api-test/commitTokens`)\n    ).json()\n  ).commitTokensReceived;\nconst getCommitTokensReturnedFromEyeball = async (envD1MockFetcher) =>\n  (\n    await (\n      await envD1MockFetcher.fetch(`http://d1-api-test/commitTokens`)\n    ).json()\n  ).commitTokensReturned;\nconst resetCommitTokens = async (envD1MockFetcher) =>\n  await (\n    await envD1MockFetcher.fetch(`http://d1-api-test/commitTokens/reset`)\n  ).json();\nconst setNextCommitTokenFromEyeball = async (envD1MockFetcher, t) =>\n  await (\n    await envD1MockFetcher.fetch(\n      `http://d1-api-test/commitTokens/nextToken?t=${t}`\n    )\n  ).json();\n\nexport const test_d1_api_withsessions_token_handling = test(\n  testD1ApiWithSessionsTokensHandling,\n  getDBFromEnv\n);\n\nasync function testD1ApiWithSessionsTokensHandling(DB, envD1MockFetcher) {\n  const assertTokensSentReceived = async (firstTokenFromBinding) => {\n    const tokens = await getCommitTokensSentFromBinding(envD1MockFetcher);\n    assert.deepEqual(tokens[0], firstTokenFromBinding);\n    // Make sure we sent back whatever we received from the previous query.\n    assert.deepEqual(\n      tokens.slice(1),\n      (await getCommitTokensReturnedFromEyeball(envD1MockFetcher)).slice(0, -1)\n    );\n  };\n\n  // Assert tokens sent by the top level DB are always primary!\n  await resetCommitTokens(envD1MockFetcher);\n  await testD1ApiQueriesHappyPath(DB);\n  let tokens = await getCommitTokensSentFromBinding(envD1MockFetcher);\n  assert.deepEqual(\n    tokens.every((t) => t === 'first-primary'),\n    true\n  );\n  // Make sure we received different tokens, and still sent first-primary.\n  assert.deepEqual(\n    (await getCommitTokensReturnedFromEyeball(envD1MockFetcher)).every(\n      (t) => t !== 'first-primary'\n    ),\n    true\n  );\n\n  // Assert tokens sent by the DEFAULT DB.withSession()\n  await resetCommitTokens(envD1MockFetcher);\n  await testD1ApiQueriesHappyPath(DB.withSession());\n  await assertTokensSentReceived('first-unconstrained');\n\n  // Assert tokens sent by the DB.withSession(\"first-unconstrained\")\n  await resetCommitTokens(envD1MockFetcher);\n  await testD1ApiQueriesHappyPath(DB.withSession('first-unconstrained'));\n  await assertTokensSentReceived('first-unconstrained');\n\n  // Assert tokens sent by the DB.withSession(\"first-primary\")\n  await resetCommitTokens(envD1MockFetcher);\n  await testD1ApiQueriesHappyPath(DB.withSession('first-primary'));\n  await assertTokensSentReceived('first-primary');\n}\n\nexport const test_d1_api_withsessions_old_token_skipped = test(\n  testD1ApiWithSessionsOldTokensSkipped,\n  getDBFromEnv\n);\n\nasync function testD1ApiWithSessionsOldTokensSkipped(DB, envD1MockFetcher) {\n  const runTest = async (session) => {\n    await resetCommitTokens(envD1MockFetcher);\n    await session.prepare(`SELECT * FROM sqlite_master;`).all();\n    await session.prepare(`SELECT * FROM sqlite_master;`).all();\n\n    // This is alphanumerically smaller than the tokens generated by d1-mock.js.\n    await setNextCommitTokenFromEyeball(envD1MockFetcher, '------');\n\n    // The token from this should be ignored!\n    await session.prepare(`SELECT * FROM sqlite_master;`).all();\n\n    // But not from this since a normal larger value should be received.\n    await session.prepare(`SELECT * FROM sqlite_master;`).all();\n    await session.prepare(`SELECT * FROM sqlite_master;`).all();\n\n    const tokensFromBinding =\n      await getCommitTokensSentFromBinding(envD1MockFetcher);\n    const tokensFromEyeball =\n      await getCommitTokensReturnedFromEyeball(envD1MockFetcher);\n    const expectedTokensFromBinding = [\n      'first-unconstrained',\n      tokensFromEyeball[0],\n      tokensFromEyeball[1],\n      // We skip the commit token \"------\", since the previously received one was more recent.\n      tokensFromEyeball[1],\n      // The binding then sends back the next largest value.\n      tokensFromEyeball[3],\n    ];\n    assert.deepEqual(tokensFromBinding, expectedTokensFromBinding);\n\n    return { ok: true };\n  };\n\n  itShould('default DB', runTest(DB), { ok: true });\n  itShould('withSession()', runTest(DB.withSession()), { ok: true });\n  itShould(\n    'withSession(first-unconstrained)',\n    runTest(DB.withSession('first-unconstrained')),\n    { ok: true }\n  );\n  itShould(\n    'withSession(first-primary)',\n    runTest(DB.withSession('first-primary')),\n    { ok: true }\n  );\n}\n"
  },
  {
    "path": "src/cloudflare/internal/test/d1/d1-api-test-with-sessions.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"TEST_TMPDIR\", disk = (writable = true) ),\n    ( name = \"d1-api-test-with-sessions\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"d1-api-test-with-sessions.js\"),\n          (name = \"d1-api-test-common\", esModule = embed \"d1-api-test-common.js\"),\n        ],\n\n        # NOTE: We provide the extra compatibility flag to enable the Sessions API.\n        compatibilityFlags = [\"nodejs_compat\"],\n\n        bindings = [\n        (\n          name = \"d1\",\n          wrapped = (\n            moduleName = \"cloudflare-internal:d1-api\",\n            innerBindings = [(\n              name = \"fetcher\",\n              service = \"d1-mock\"\n            )],\n          ),\n        ),\n        (\n          name = \"d1MockFetcher\",\n          service = \"d1-mock\"\n        ),\n        ],\n      )\n    ),\n    ( name = \"d1-mock\",\n      worker = (\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n        modules = [\n          (name = \"worker\", esModule = embed \"d1-mock.js\")\n        ],\n        durableObjectNamespaces = [\n          (className = \"D1MockDO\", uniqueKey = \"210bd0cbd803ef7883a1ee9d86cce06e\"),\n        ],\n        durableObjectStorage = (localDisk = \"TEST_TMPDIR\"),\n        bindings = [\n          (name = \"db\", durableObjectNamespace = \"D1MockDO\"),\n        ],\n      )\n    )\n  ]\n);\n"
  },
  {
    "path": "src/cloudflare/internal/test/d1/d1-api-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { testD1ApiQueriesHappyPath, testD1Exec } from './d1-api-test-common';\n\nexport const testWithoutSessions = {\n  async test(_ctr, env) {\n    await testD1ApiQueriesHappyPath(env.d1);\n  },\n};\n\nexport const testExec = {\n  async test(_ctr, env) {\n    await testD1Exec(env.d1);\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/d1/d1-api-test.py",
    "content": "# Copyright (c) 2023 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n\ndef assertSuccess(ret):\n    # D1 operations return an object with a `success` field.\n    assert ret.success\n\n\nasync def test(context, env):\n    DB = env.d1\n\n    assertSuccess(\n        await DB.prepare(\n            \"\"\"CREATE TABLE users\n            (\n                user_id    INTEGER PRIMARY KEY,\n                name       TEXT,\n                home       TEXT,\n                features   TEXT,\n                land_based BOOLEAN\n            );\n            \"\"\"\n        ).run()\n    )\n\n    result = await DB.prepare(\n        \"\"\"\n        INSERT INTO users (name, home, features, land_based) VALUES\n          ('Albert Ross', 'sky', 'wingspan', false),\n          ('Al Dente', 'bowl', 'mouthfeel', true)\n        RETURNING *\n        \"\"\"\n    ).all()\n\n    assertSuccess(result)\n    assert len(result.results) == 2\n    assert result.results[0].name == \"Albert Ross\"\n"
  },
  {
    "path": "src/cloudflare/internal/test/d1/d1-api-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"TEST_TMPDIR\", disk = (writable = true) ),\n    ( name = \"d1-api-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"d1-api-test.js\"),\n          (name = \"d1-api-test-common\", esModule = embed \"d1-api-test-common.js\"),\n        ],\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\", \"service_binding_extra_handlers\"],\n        streamingTails = [\"tail\"],\n        bindings = [\n        (\n          name = \"d1\",\n          wrapped = (\n            moduleName = \"cloudflare-internal:d1-api\",\n            innerBindings = [(\n              name = \"fetcher\",\n              service = \"d1-mock\"\n            )],\n          )\n        )\n        ],\n      )\n    ),\n    ( name = \"d1-mock\",\n      worker = (\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n        modules = [\n          (name = \"worker\", esModule = embed \"d1-mock.js\")\n        ],\n        durableObjectNamespaces = [\n          (className = \"D1MockDO\", uniqueKey = \"210bd0cbd803ef7883a1ee9d86cce06e\"),\n        ],\n        durableObjectStorage = (localDisk = \"TEST_TMPDIR\"),\n        bindings = [\n          (name = \"db\", durableObjectNamespace = \"D1MockDO\"),\n        ],\n      )\n    ),\n    ( name = \"tail\", worker = .tailWorker, ),\n  ]\n);\n\nconst tailWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"d1-api-instrumentation-test.js\"),\n    (name = \"instrumentation-test-helper\", esModule = embed \"../instrumentation-test-helper.js\")\n  ],\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n);\n"
  },
  {
    "path": "src/cloudflare/internal/test/d1/d1-mock.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport class D1MockDO {\n  constructor(state, env) {\n    this.state = state;\n    this.sql = this.state.storage.sql;\n  }\n\n  async fetch(request) {\n    const { pathname, searchParams } = new URL(request.url);\n    const is_query = pathname === '/query';\n    const is_execute = pathname === '/execute';\n    if (request.method === 'POST' && (is_query || is_execute)) {\n      const body = await request.json();\n      const resultsFormatParam = searchParams.get('resultsFormat');\n      const resultsFormat =\n        resultsFormatParam === 'ROWS_AND_COLUMNS'\n          ? 'ROWS_AND_COLUMNS'\n          : resultsFormatParam === 'NONE'\n            ? 'NONE'\n            : 'ARRAY_OF_OBJECTS';\n      const safeRunQuery = (query) => {\n        try {\n          return this.runQuery(query, resultsFormat);\n        } catch (e) {\n          // Reproduce the production behavior by catching any error and returning a V4Failure\n          return { success: false, error: String(e.message) };\n        }\n      };\n      return Response.json(\n        Array.isArray(body)\n          ? body.map((query) => safeRunQuery(query))\n          : safeRunQuery(body)\n      );\n    } else {\n      return Response.json({ error: 'Not found' }, { status: 404 });\n    }\n  }\n\n  runQuery(query, resultsFormat) {\n    const { sql, params = [] } = query;\n\n    const changes_stmt = this.sql.prepare(\n      `SELECT total_changes() as changes, last_insert_rowid() as last_row_id`\n    );\n    const size_before = this.sql.databaseSize;\n    const [[changes_before, last_row_id_before]] = Array.from(\n      changes_stmt().raw()\n    );\n\n    const stmt = this.sql.prepare(sql)(...params);\n    const columnNames = stmt.columnNames;\n    const rawResults = Array.from(stmt.raw());\n\n    // Convert to object-style results if necessary (for backwards compatibility)\n    // .run() previously returned results. Folks relied on that, and we broke their running Workers, we shouldn't have done that.\n    // To make the existing workers (those that didn't update to fix their .run to be .all) work again, we're hardcoding ResultFormat.NONE to do the same thing as what .all used to do (send back the array of results)\n    const results =\n      resultsFormat === 'NONE'\n        ? rawResults.map((row) =>\n            Object.fromEntries(columnNames.map((c, i) => [c, row[i]]))\n          )\n        : resultsFormat === 'ROWS_AND_COLUMNS'\n          ? { columns: columnNames, rows: rawResults }\n          : rawResults.map((row) =>\n              Object.fromEntries(columnNames.map((c, i) => [c, row[i]]))\n            );\n\n    const [[changes_after, last_row_id_after]] = Array.from(\n      changes_stmt().raw()\n    );\n\n    const size_after = this.sql.databaseSize;\n    const num_changes = changes_after - changes_before;\n    const has_changes = num_changes !== 0;\n    const last_row_changed = last_row_id_after !== last_row_id_before;\n\n    const db_size_different = size_after != size_before;\n\n    // `changed_db` includes multiple ways the DB might be altered\n    const changed_db = has_changes || last_row_changed || db_size_different;\n\n    const { rowsRead: rows_read, rowsWritten: rows_written } = stmt;\n\n    return {\n      success: true,\n      results,\n      meta: {\n        duration: Math.random() * 0.01,\n        served_by: 'd1-mock',\n        changes: num_changes,\n        last_row_id: last_row_id_after,\n        changed_db,\n        size_after,\n        rows_read,\n        rows_written,\n      },\n    };\n  }\n}\n\nexport default {\n  commitTokenNum: 0,\n  commitTokensReceived: [],\n  commitTokensReturned: [],\n  nextTokenExpected: null,\n\n  async fetch(request, env, ctx) {\n    if (request.url.startsWith('http://d1-api-test/commitTokens')) {\n      return this.handleD1ApiTestRoutes(request);\n    }\n\n    // For our testing purposes, record any commit token passed through.\n    const reqCommitToken = request.headers.get('x-cf-d1-session-commit-token');\n    this.commitTokensReceived.push(reqCommitToken);\n\n    try {\n      const stub = env.db.get(env.db.idFromName('test'));\n\n      // Add a commitToken to all responses.\n      return stub\n        .fetch(request)\n        .then((resp) => this.buildResponseWithCommitToken(resp));\n    } catch (err) {\n      return Response.json(\n        { error: err.message, stack: err.stack },\n        { status: 500 }\n      );\n    }\n  },\n\n  async buildResponseWithCommitToken(resp) {\n    let newToken = `token-${(++this.commitTokenNum).toLocaleString('en-US', {\n      minimumIntegerDigits: 4,\n      // no commas\n      useGrouping: false,\n    })}`;\n    if (this.nextTokenExpected) {\n      newToken = this.nextTokenExpected;\n      this.nextTokenExpected = null;\n    }\n    this.commitTokensReturned.push(newToken);\n    // Append an ever increasing commit token to the response.\n    // Simulating the D1 eyeball worker.\n    const newHeaders = new Headers(resp.headers);\n    newHeaders.set('x-cf-d1-session-commit-token', newToken);\n    return Response.json(await resp.json(), {\n      status: resp.status,\n      statusText: resp?.statusText,\n      headers: newHeaders,\n    });\n  },\n\n  async handleD1ApiTestRoutes(request) {\n    const respondTokens = () =>\n      Response.json(\n        {\n          commitTokensReceived: this.commitTokensReceived,\n          commitTokensReturned: this.commitTokensReturned,\n        },\n        { status: 200 }\n      );\n\n    switch (new URL(request.url).pathname) {\n      case '/commitTokens':\n        // Special endpoints to accommodate our tests.\n        return respondTokens();\n\n      case '/commitTokens/nextToken':\n        this.nextTokenExpected = new URL(request.url).searchParams.get('t');\n        return respondTokens();\n\n      case '/commitTokens/reset':\n        this.commitTokensReceived = [];\n        this.commitTokensReturned = [];\n        this.commitTokenNum = 0;\n        this.nextTokenExpected = null;\n        return respondTokens();\n\n      default:\n        return Response.json({ error: 'invalid test route' }, { status: 404 });\n    }\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/d1/python-d1-api-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"TEST_TMPDIR\", disk = (writable = true) ),\n    ( name = \"d1-api-test\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"d1-api-test.py\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", %PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\"],\n        bindings = [\n        (\n          name = \"d1\",\n          wrapped = (\n            moduleName = \"cloudflare-internal:d1-api\",\n            innerBindings = [(\n              name = \"fetcher\",\n              service = \"d1-mock\"\n            )],\n          )\n        )\n        ],\n      )\n    ),\n    ( name = \"d1-mock\",\n      worker = (\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n        modules = [\n          (name = \"worker\", esModule = embed \"d1-mock.js\")\n        ],\n        durableObjectNamespaces = [\n          (className = \"D1MockDO\", uniqueKey = \"210bd0cbd803ef7883a1ee9d86cce06e\"),\n        ],\n        durableObjectStorage = (localDisk = \"TEST_TMPDIR\"),\n        bindings = [\n          (name = \"db\", durableObjectNamespace = \"D1MockDO\"),\n        ],\n      )\n    )\n  ]\n);\n"
  },
  {
    "path": "src/cloudflare/internal/test/images/BUILD.bazel",
    "content": "load(\"//:build/wd_test.bzl\", \"wd_test\")\n\nwd_test(\n    # Using size enormous because it takes a really long time executing it\n    # under coverage.\n    size = \"enormous\",\n    src = \"images-api-test.wd-test\",\n    args = [\"--experimental\"],\n    data = glob([\"*.js\"]) + [\"//src/cloudflare/internal/test:instrumentation-test-helper.js\"],\n)\n"
  },
  {
    "path": "src/cloudflare/internal/test/images/images-api-instrumentation-test.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// TODO(cleanup): Refactor to use one of the shared tail workers.\n\nimport {\n  createInstrumentationState,\n  runInstrumentationTest,\n} from 'instrumentation-test-helper';\n\n// Images test uses a different keying strategy (traceId-based)\n// So we need a custom tailStream handler\nconst state = createInstrumentationState();\n\nexport default {\n  tailStream(event, env, ctx) {\n    // For each \"onset\" event, store a promise which we will resolve when\n    // we receive the equivalent \"outcome\" event\n    let resolveFn;\n    state.invocationPromises.push(\n      new Promise((resolve, reject) => {\n        resolveFn = resolve;\n      })\n    );\n\n    // Accumulate the span info for easier testing\n    return (event) => {\n      switch (event.event.type) {\n        case 'spanOpen':\n          // The span ids will change between tests, but Map preserves insertion order\n          // Note: Images uses traceId instead of spanId for keying\n          state.spans.set(event.spanContext.traceId, {\n            name: event.event.name,\n          });\n          break;\n        case 'attributes': {\n          let span = state.spans.get(event.spanContext.traceId);\n          for (let { name, value } of event.event.info) {\n            span[name] = value;\n          }\n          state.spans.set(event.spanContext.traceId, span);\n          break;\n        }\n        case 'spanClose': {\n          let span = state.spans.get(event.spanContext.traceId);\n          span['closed'] = true;\n          state.spans.set(event.spanContext.traceId, span);\n          break;\n        }\n        case 'outcome':\n          resolveFn();\n          break;\n      }\n    };\n  },\n};\n\nexport const test = {\n  async test() {\n    // Use the helper with mapFn to normalize dynamic multipart boundaries\n    await runInstrumentationTest(state, expectedSpans, {\n      testName: 'Images instrumentation',\n      mapFn: (span) => {\n        const contentType = span['http.request.header.content-type'];\n        if (contentType?.startsWith('multipart/form-data; boundary=')) {\n          return {\n            ...span,\n            'http.request.header.content-type':\n              'multipart/form-data; boundary=<dynamic>',\n          };\n        }\n        return span;\n      },\n    });\n  },\n};\n\n// spans emitted by images-api-test.js in execution order\nconst expectedSpans = [\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'https://js.images.cloudflare.com/transform',\n    'http.request.header.content-type':\n      'multipart/form-data; boundary=<dynamic>',\n    'http.response.status_code': 200n,\n    'http.response.body.size': 63n,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'https://js.images.cloudflare.com/transform',\n    'http.request.header.content-type':\n      'multipart/form-data; boundary=<dynamic>',\n    'http.response.status_code': 200n,\n    'http.response.body.size': 57n,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'https://js.images.cloudflare.com/transform',\n    'http.request.header.content-type':\n      'multipart/form-data; boundary=<dynamic>',\n    'http.response.status_code': 200n,\n    'http.response.body.size': 491605n,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'https://js.images.cloudflare.com/transform',\n    'http.request.header.content-type':\n      'multipart/form-data; boundary=<dynamic>',\n    'http.response.status_code': 200n,\n    'http.response.body.size': 655465n,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'https://js.images.cloudflare.com/transform',\n    'http.request.header.content-type':\n      'multipart/form-data; boundary=<dynamic>',\n    'http.response.status_code': 200n,\n    'http.response.body.size': 60n,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'https://js.images.cloudflare.com/transform',\n    'http.request.header.content-type':\n      'multipart/form-data; boundary=<dynamic>',\n    'http.response.status_code': 200n,\n    'http.response.body.size': 63n,\n    closed: true,\n  },\n  {\n    name: 'images_info',\n    'cloudflare.binding.type': 'Images',\n    'cloudflare.images.options.encoding': 'base64',\n    'cloudflare.images.result.format': 'image/png',\n    'cloudflare.images.result.file_size': 123,\n    'cloudflare.images.result.width': 123,\n    'cloudflare.images.result.height': 123,\n    closed: true,\n  },\n  {\n    name: 'images_info',\n    'cloudflare.binding.type': 'Images',\n    'cloudflare.images.options.encoding': 'base64',\n    'cloudflare.images.result.format': 'image/png',\n    'cloudflare.images.result.file_size': 123,\n    'cloudflare.images.result.width': 123,\n    'cloudflare.images.result.height': 123,\n    closed: true,\n  },\n  {\n    name: 'images_info',\n    'cloudflare.binding.type': 'Images',\n    'cloudflare.images.options.encoding': 'base64',\n    'cloudflare.images.error.code': '123',\n    'error.type': 'ERROR 123: Bad request',\n    closed: true,\n  },\n  {\n    name: 'images_info',\n    'cloudflare.binding.type': 'Images',\n    'cloudflare.images.options.encoding': 'base64',\n    'cloudflare.images.result.format': 'image/svg+xml',\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'https://js.images.cloudflare.com/transform',\n    'http.request.header.content-type':\n      'multipart/form-data; boundary=<dynamic>',\n    'http.response.status_code': 200n,\n    'http.response.body.size': 359n,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'https://js.images.cloudflare.com/transform',\n    'http.request.header.content-type':\n      'multipart/form-data; boundary=<dynamic>',\n    'http.response.status_code': 200n,\n    'http.response.body.size': 102n,\n    closed: true,\n  },\n  {\n    name: 'images_output',\n    'cloudflare.binding.type': 'Images',\n    'cloudflare.images.options.transforms': '[{\"imageIndex\":0,\"rotate\":90}]',\n    'cloudflare.images.options.format': 'image/avif',\n    'cloudflare.images.error.code': '123',\n    'error.type': 'ERROR 123: Bad request',\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'https://js.images.cloudflare.com/transform',\n    'http.request.header.content-type':\n      'multipart/form-data; boundary=<dynamic>',\n    'http.response.status_code': 200n,\n    'http.response.body.size': 88n,\n    closed: true,\n  },\n  {\n    name: 'fetch',\n    'network.protocol.name': 'http',\n    'network.protocol.version': 'HTTP/1.1',\n    'http.request.method': 'POST',\n    'url.full': 'https://js.images.cloudflare.com/transform',\n    'http.request.header.content-type':\n      'multipart/form-data; boundary=<dynamic>',\n    'http.response.status_code': 200n,\n    'http.response.body.size': 60n,\n    closed: true,\n  },\n];\n"
  },
  {
    "path": "src/cloudflare/internal/test/images/images-api-test.js",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// @ts-ignore\nimport * as assert from 'node:assert';\n\nconst encoder = new TextEncoder();\nfunction inputStream(chunks) {\n  return new ReadableStream({\n    start(controller) {\n      for (const chunk of chunks) {\n        controller.enqueue(encoder.encode(chunk));\n      }\n      controller.close();\n    },\n  });\n}\n\n/**\n * @typedef {{'images': ImagesBinding}} Env\n *\n */\n\n/**\n * @param {Env} env\n * @param {string[]} chunks\n * @returns {Promise<string>}\n */\nasync function decodeBase64ThroughImagesBinding(env, chunks) {\n  const result = await env.images\n    .input(inputStream(chunks), { encoding: 'base64' })\n    .output({ format: 'image/avif' });\n\n  const body = await result.response().json();\n  return body.image;\n}\n\nexport const test_images_info_bitmap = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const info = await env.images.info(inputStream(['png']));\n    assert.deepStrictEqual(info, {\n      format: 'image/png',\n      fileSize: 123,\n      width: 123,\n      height: 123,\n    });\n  },\n};\n\nexport const test_images_info_bitmap_base64 = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const info = await env.images.info(inputStream([btoa('png')]), {\n      encoding: 'base64',\n    });\n    assert.deepStrictEqual(info, {\n      format: 'image/png',\n      fileSize: 123,\n      width: 123,\n      height: 123,\n    });\n  },\n};\n\nexport const test_images_info_svg = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const info = await env.images.info(inputStream(['<svg></svg>']));\n    assert.deepStrictEqual(info, {\n      format: 'image/svg+xml',\n    });\n  },\n};\n\nexport const test_images_info_error = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    /**\n     * @type {any} e;\n     */\n    let e;\n\n    try {\n      await env.images.info(inputStream(['BAD']));\n    } catch (e2) {\n      e = e2;\n    }\n\n    assert.equal(true, !!e);\n    assert.equal(e.code, 123);\n    assert.equal(e.message, 'IMAGES_INFO_ERROR 123: Bad request');\n  },\n};\n\nexport const test_images_transform = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n\n  async test(_, env) {\n    const result = await env.images\n      .input(inputStream(['png']))\n      .transform({ rotate: 90 })\n      .output({ format: 'image/avif', anim: true });\n\n    // Would be image/avif in real life, but mock always returns JSON\n    assert.strictEqual(result.contentType(), 'application/json');\n    const body = await result.response().json();\n\n    assert.deepStrictEqual(body, {\n      image: 'png',\n      output_format: 'image/avif',\n      transforms: [{ imageIndex: 0, rotate: 90 }],\n      anim: 'true',\n    });\n  },\n};\n\nexport const test_images_nested_draw = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n\n  async test(_, env) {\n    const result = await env.images\n      .input(inputStream(['png']))\n      .transform({ rotate: 90 })\n      .draw(env.images.input(inputStream(['png1'])).transform({ rotate: 180 }))\n      .draw(\n        env.images\n          .input(inputStream(['png2']))\n          .draw(inputStream(['png3']))\n          .transform({ rotate: 270 })\n      )\n      .draw(inputStream(['png4']))\n      .output({ format: 'image/avif' });\n\n    // Would be image/avif in real life, but mock always returns JSON\n    assert.strictEqual(result.contentType(), 'application/json');\n    const body = await result.response().json();\n\n    assert.deepStrictEqual(body, {\n      image: 'png',\n      draw_image: ['png1', 'png2', 'png3', 'png4'],\n      output_format: 'image/avif',\n      transforms: [\n        { imageIndex: 0, rotate: 90 },\n        { imageIndex: 1, rotate: 180 },\n        { drawImageIndex: 1, targetImageIndex: 0 },\n        { drawImageIndex: 3, targetImageIndex: 2 },\n        { imageIndex: 2, rotate: 270 },\n        { drawImageIndex: 2, targetImageIndex: 0 },\n        { drawImageIndex: 4, targetImageIndex: 0 },\n      ],\n    });\n  },\n};\n\nexport const test_images_transformer_draw_twice_disallowed = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n\n  async test(_, env) {\n    /**\n     * @type {any} e;\n     */\n    let e;\n\n    let t = env.images.input(inputStream(['png1']));\n\n    try {\n      await env.images\n        .input(inputStream(['png']))\n        .draw(t)\n        .draw(t)\n        .output({ format: 'image/avif' });\n    } catch (e1) {\n      e = e1;\n    }\n\n    assert.equal(true, !!e);\n    assert.equal(e.code, 9525);\n    assert.equal(\n      e.message,\n      'IMAGES_TRANSFORM_ERROR 9525: ImageTransformer consumed; you may only call .output() or draw a transformer once'\n    );\n  },\n};\n\nexport const test_images_transformer_already_consumed_disallowed = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n\n  async test(_, env) {\n    /**\n     * @type {any} e;\n     */\n    let e;\n\n    let t = env.images.input(inputStream(['png1']));\n\n    await t.output({});\n\n    try {\n      await env.images\n        .input(inputStream(['png']))\n        .draw(t)\n        .output({ format: 'image/avif' });\n    } catch (e1) {\n      e = e1;\n    }\n\n    assert.equal(true, !!e);\n    assert.equal(e.code, 9525);\n    assert.equal(\n      e.message,\n      'IMAGES_TRANSFORM_ERROR 9525: ImageTransformer consumed; you may only call .output() or draw a transformer once'\n    );\n  },\n};\n\nexport const test_images_transform_bad = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n\n  async test(_, env) {\n    /**\n     * @type {any} e;\n     */\n    let e;\n\n    try {\n      await env.images\n        .input(inputStream(['BAD']))\n        .transform({ rotate: 90 })\n        .output({ format: 'image/avif' });\n    } catch (e2) {\n      e = e2;\n    }\n\n    assert.equal(true, !!e);\n    assert.equal(e.code, 123);\n    assert.equal(e.message, 'IMAGES_TRANSFORM_ERROR 123: Bad request');\n  },\n};\n\nexport const test_images_transform_consumed = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n\n  async test(_, env) {\n    /**\n     * @type {any} e;\n     */\n    let e;\n\n    try {\n      let transformer = env.images\n        .input(inputStream(['png']))\n        .transform({ rotate: 90 });\n\n      await transformer.output({ format: 'image/avif' });\n      await transformer.output({ format: 'image/avif' });\n    } catch (e2) {\n      e = e2;\n    }\n\n    assert.equal(true, !!e);\n    assert.equal(e.code, 9525);\n    assert.equal(\n      e.message,\n      'IMAGES_TRANSFORM_ERROR 9525: ImageTransformer consumed; you may only call .output() or draw a transformer once'\n    );\n  },\n};\n\nconst ENCODE_BLOCKSIZE = 32 * 1024 + 1;\nconst DECODE_BLOCKSIZE = 32 * 1024;\n\nexport const test_images_base64_input = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const inputs = {\n      smallInput: 'QUFB',\n      blockSizeInput: 'QUFB'.repeat(DECODE_BLOCKSIZE / 'QUFB'.length),\n      twoBlockInput: 'QUFB'.repeat(DECODE_BLOCKSIZE / 'QUFB'.length + 1),\n      hugeInput: 'QUFB'.repeat((DECODE_BLOCKSIZE * 20) / 'QUFB'.length),\n    };\n\n    for (let testName in inputs) {\n      const input = inputs[testName];\n\n      const result = await env.images\n        .input(inputStream([input]), { encoding: 'base64' })\n        .transform({ rotate: 90 })\n        .output({ format: 'image/avif' });\n\n      // Would be image/avif in real life, but mock always returns JSON\n      assert.strictEqual(result.contentType(), 'application/json');\n      const body = await result.response().json();\n\n      assert.deepStrictEqual(body, {\n        image: atob(input),\n        output_format: 'image/avif',\n        transforms: [{ imageIndex: 0, rotate: 90 }],\n      });\n    }\n  },\n};\n\nexport const test_images_base64_output = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n\n  async test(_, env) {\n    // We can't really control the length of the output here as it also has\n    // a JSON body around it\n    const inputs = {\n      smallInput: 'A',\n      twoBlockInput: 'A'.repeat(ENCODE_BLOCKSIZE + 1),\n      hugeInput: 'A'.repeat(ENCODE_BLOCKSIZE * 20),\n    };\n\n    for (const testName in inputs) {\n      const input = inputs[testName];\n\n      const result = await env.images\n        .input(inputStream([input]))\n        .transform({ rotate: 90 })\n        .output({ format: 'image/avif' });\n\n      // Would be image/avif in real life, but mock always returns JSON\n      assert.strictEqual(result.contentType(), 'application/json');\n      const bodyBase64 = await new Response(\n        await result.image({ encoding: 'base64' })\n      ).text();\n      const body = JSON.parse(atob(bodyBase64));\n\n      assert.deepStrictEqual(body, {\n        image: input,\n        output_format: 'image/avif',\n        transforms: [{ imageIndex: 0, rotate: 90 }],\n      });\n    }\n  },\n};\n\nexport const test_images_base64_empty_input = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    assert.strictEqual(await decodeBase64ThroughImagesBinding(env, []), '');\n  },\n};\n\nexport const test_images_base64_padding = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    assert.strictEqual(\n      await decodeBase64ThroughImagesBinding(env, ['QQ==']),\n      'A'\n    );\n    assert.strictEqual(\n      await decodeBase64ThroughImagesBinding(env, ['QWI=']),\n      'Ab'\n    );\n    assert.strictEqual(\n      await decodeBase64ThroughImagesBinding(env, ['QWJj']),\n      'Abc'\n    );\n  },\n};\n\nexport const test_images_base64_chunk_boundaries = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    assert.strictEqual(\n      await decodeBase64ThroughImagesBinding(env, ['QUFB', 'QUFB']),\n      'AAAAAA'\n    );\n  },\n};\n\nexport const test_images_base64_small_chunks = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    // 1 byte chunks\n    assert.strictEqual(\n      await decodeBase64ThroughImagesBinding(env, [\n        'Q',\n        'U',\n        'F',\n        'B',\n        'Q',\n        'U',\n        'F',\n        'B',\n      ]),\n      'AAAAAA'\n    );\n\n    // 2 byte chunks\n    assert.strictEqual(\n      await decodeBase64ThroughImagesBinding(env, ['QU', 'FB', 'QU', 'FB']),\n      'AAAAAA'\n    );\n\n    // 3 byte chunks\n    assert.strictEqual(\n      await decodeBase64ThroughImagesBinding(env, ['QUF', 'BQU', 'FB']),\n      'AAAAAA'\n    );\n  },\n};\n// GET metadata\nexport const test_images_get_success = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const metadata = await env.images.hosted.details('test-image-id');\n    assert.notEqual(metadata, null);\n    assert.equal(metadata.id, 'test-image-id');\n    assert.equal(metadata.filename, 'test.jpg');\n    assert.equal(metadata.requireSignedURLs, false);\n    assert.equal(metadata.creator, 'test-creator');\n  },\n};\n\nexport const test_images_get_not_found = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const metadata = await env.images.hosted.details('not-found');\n    assert.equal(metadata, null);\n  },\n};\n\n// GET image blob\nexport const test_images_getImage_success = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const stream = await env.images.hosted.image('test-image-id');\n    assert.notEqual(stream, null);\n\n    const reader = stream.getReader();\n    let result = '';\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      result += new TextDecoder().decode(value);\n    }\n\n    assert.equal(result, 'MOCK_IMAGE_DATA_test-image-id');\n  },\n};\n\nexport const test_images_getImage_not_found = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const stream = await env.images.hosted.image('not-found');\n    assert.equal(stream, null);\n  },\n};\n\n// UPLOAD\nexport const test_images_upload_with_options = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const imageData = new Blob(['test image']).stream();\n    const metadata = await env.images.hosted.upload(imageData, {\n      id: 'custom-id',\n      filename: 'upload-test.jpg',\n      requireSignedURLs: true,\n      metadata: { key: 'value' },\n      creator: 'upload-creator',\n    });\n\n    assert.equal(metadata.id, 'custom-id');\n    assert.equal(metadata.filename, 'upload-test.jpg');\n    assert.equal(metadata.requireSignedURLs, true);\n    assert.deepStrictEqual(metadata.meta, { key: 'value' });\n    assert.equal(metadata.creator, 'upload-creator');\n  },\n};\n\nexport const test_images_upload_arraybuffer = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const buffer = new TextEncoder().encode('test image').buffer;\n    const metadata = await env.images.hosted.upload(buffer);\n\n    assert.notEqual(metadata, null);\n    assert.equal(typeof metadata.id, 'string');\n  },\n};\n\n// UPDATE\nexport const test_images_update_success = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const metadata = await env.images.hosted.update('test-image-id', {\n      requireSignedURLs: true,\n      metadata: { updated: true },\n      creator: 'update-creator',\n    });\n\n    assert.equal(metadata.id, 'test-image-id');\n    assert.equal(metadata.requireSignedURLs, true);\n    assert.deepStrictEqual(metadata.meta, { updated: true });\n    assert.equal(metadata.creator, 'update-creator');\n  },\n};\n\nexport const test_images_update_not_found = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    /**\n     * @type {any} e;\n     */\n    let e;\n    try {\n      await env.images.hosted.update('not-found', { requireSignedURLs: true });\n    } catch (err) {\n      e = err;\n    }\n    assert.notEqual(e, undefined);\n    assert.equal(e.message.includes('not found'), true);\n  },\n};\n\n// DELETE\nexport const test_images_delete_success = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const result = await env.images.hosted.delete('test-image-id');\n    assert.equal(result, true);\n  },\n};\n\nexport const test_images_delete_not_found = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const result = await env.images.hosted.delete('not-found');\n    assert.equal(result, false);\n  },\n};\n\n// LIST\nexport const test_images_list_default = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const result = await env.images.hosted.list();\n\n    assert.notEqual(result.images, null);\n    assert.equal(Array.isArray(result.images), true);\n    assert.equal(result.images.length, 2);\n    assert.equal(result.listComplete, true);\n  },\n};\n\nexport const test_images_list_with_options = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const result = await env.images.hosted.list({\n      limit: 1,\n      sortOrder: 'asc',\n    });\n\n    assert.equal(result.images.length, 1);\n  },\n};\n\n// UPLOAD with base64 encoding\nexport const test_images_upload_base64_stream = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    // Create base64-encoded data\n    const imageData = 'test image content';\n    const base64Data = btoa(imageData);\n    const stream = new Blob([base64Data]).stream();\n\n    const metadata = await env.images.hosted.upload(stream, {\n      filename: 'base64-test.jpg',\n      encoding: 'base64',\n    });\n\n    assert.equal(metadata.filename, 'base64-test.jpg');\n    assert.notEqual(metadata.id, null);\n  },\n};\n\nexport const test_images_upload_base64_arraybuffer = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    // Create base64-encoded ArrayBuffer\n    const imageData = 'test image content';\n    const base64Data = btoa(imageData);\n    const buffer = new TextEncoder().encode(base64Data).buffer;\n\n    const metadata = await env.images.hosted.upload(buffer, {\n      filename: 'base64-buffer-test.jpg',\n      encoding: 'base64',\n    });\n\n    assert.equal(metadata.filename, 'base64-buffer-test.jpg');\n    assert.notEqual(metadata.id, null);\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/images/images-api-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"images-api-test\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"images-api-test.js\" )\n        ],\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\", \"nodejs_compat_v2\", \"service_binding_extra_handlers\", \"rpc\", \"fetcher_no_get_put_delete\", \"streams_enable_constructors\", \"transformstream_enable_standard_constructor\", \"formdata_parser_supports_files\"],\n        streamingTails = [\"tail\"],\n        bindings = [\n          ( name = \"images\",\n            wrapped = (\n              moduleName = \"cloudflare-internal:images-api\",\n              innerBindings = [\n                (\n                  name = \"fetcher\",\n                  service = (\n                    name = \"images-upstream-mock\",\n                    entrypoint = \"ServiceEntrypoint\"\n                  )\n                )\n              ],\n            )\n          )\n        ],\n      )\n    ),\n    ( name = \"images-upstream-mock\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"images-upstream-mock.js\" )\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"fetcher_no_get_put_delete\", \"streams_enable_constructors\", \"transformstream_enable_standard_constructor\", \"formdata_parser_supports_files\"],\n      )\n    ),\n    ( name = \"tail\", worker = .tailWorker, ),\n  ]\n);\n\nconst tailWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"images-api-instrumentation-test.js\"),\n    (name = \"instrumentation-test-helper\", esModule = embed \"../instrumentation-test-helper.js\")\n  ],\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\", \"nodejs_compat_v2\"],\n);\n"
  },
  {
    "path": "src/cloudflare/internal/test/images/images-upstream-mock.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { WorkerEntrypoint } from 'cloudflare:workers';\n\n/**\n * @param {FormDataEntryValue | null} blob\n * @returns {Promise<string | null>}\n */\nasync function imageAsString(blob) {\n  if (blob === null) {\n    return null;\n  }\n\n  if (typeof blob === 'string') {\n    return null;\n  }\n\n  return blob.text();\n}\n\nexport class ServiceEntrypoint extends WorkerEntrypoint {\n  /**\n   * @param {string} imageId\n   * @returns {Promise<ImageMetadata | null>}\n   */\n  async details(imageId) {\n    if (imageId === 'not-found') {\n      return null;\n    }\n\n    return {\n      id: imageId,\n      filename: 'test.jpg',\n      uploaded: '2024-01-01T00:00:00Z',\n      requireSignedURLs: false,\n      variants: ['public'],\n      meta: {},\n      draft: false,\n      creator: 'test-creator',\n    };\n  }\n\n  async image(imageId) {\n    if (imageId === 'not-found') {\n      return null;\n    }\n\n    const mockData = `MOCK_IMAGE_DATA_${imageId}`;\n    return new Blob([mockData]).stream();\n  }\n\n  async upload(image, options) {\n    // Handle both ReadableStream and ArrayBuffer\n    const buffer =\n      image instanceof ArrayBuffer\n        ? image\n        : await new Response(image).arrayBuffer();\n\n    const decoder = new TextDecoder();\n    const text = decoder.decode(buffer);\n\n    if (text === 'INVALID') {\n      throw new Error('Invalid image data');\n    }\n\n    return {\n      id: options?.id || 'generated-id',\n      filename: options?.filename || 'uploaded.jpg',\n      uploaded: '2024-01-01T00:00:00Z',\n      requireSignedURLs: options?.requireSignedURLs || false,\n      variants: ['public'],\n      meta: options?.metadata || {},\n      draft: false,\n      creator: options?.creator,\n    };\n  }\n\n  /**\n   * @param {string} imageId\n   * @param {ImageUpdateOptions} body\n   * @returns {Promise<ImageMetadata>}\n   */\n  async update(imageId, body) {\n    if (imageId === 'not-found') {\n      throw new Error('Image not found');\n    }\n\n    return {\n      id: imageId,\n      filename: 'updated.jpg',\n      uploaded: '2024-01-01T00:00:00Z',\n      requireSignedURLs:\n        body.requireSignedURLs !== undefined ? body.requireSignedURLs : false,\n      variants: ['public'],\n      meta: body.metadata || {},\n      draft: false,\n      creator: body.creator,\n    };\n  }\n\n  /**\n   * @param {string} imageId\n   * @returns {Promise<boolean>}\n   */\n  async delete(imageId) {\n    return imageId !== 'not-found';\n  }\n\n  /**\n   * @param {ImageListOptions} [options]\n   * @returns {Promise<ImageList>}\n   */\n  async list(options) {\n    const images = [\n      {\n        id: 'image-1',\n        filename: 'test1.jpg',\n        uploaded: '2024-01-01T00:00:00Z',\n        requireSignedURLs: false,\n        variants: ['public'],\n        meta: {},\n        creator: 'test-creator',\n      },\n      {\n        id: 'image-2',\n        filename: 'test2.jpg',\n        uploaded: '2024-01-02T00:00:00Z',\n        requireSignedURLs: false,\n        variants: ['public'],\n        meta: {},\n        creator: 'test-creator',\n      },\n    ];\n\n    const limit = options?.limit || 50;\n    const slicedImages = images.slice(0, limit);\n\n    return {\n      images: slicedImages,\n      listComplete: true,\n    };\n  }\n\n  /**\n   * Handle HTTP requests for info and transform operations.\n   * In production these go to a separate transformation service,\n   * but in tests we mock both the ServiceEntrypoint and transformation service in one place.\n   * @param {Request} request\n   * @returns {Promise<Response>}\n   */\n  async fetch(request) {\n    const form = await request.formData();\n    const image = (await imageAsString(form.get('image'))) || '';\n    if (image.includes('BAD')) {\n      return new Response('ERROR 123: Bad request', {\n        status: 409,\n        headers: {\n          'cf-images-binding': 'err=123',\n        },\n      });\n    }\n\n    switch (new URL(request.url).pathname) {\n      case '/info':\n        if (image.includes('<svg')) {\n          return Response.json({\n            format: 'image/svg+xml',\n          });\n        } else {\n          return Response.json({\n            format: 'image/png',\n            file_size: 123,\n            width: 123,\n            height: 123,\n          });\n        }\n      case '/transform': {\n        /** @type {any} */\n        const obj = {\n          image: await imageAsString(form.get('image')),\n          // @ts-ignore\n          transforms: JSON.parse(form.get('transforms') || '{}'),\n        };\n        for (const x of [\n          'output_format',\n          'output_quality',\n          'background',\n          'anim',\n        ]) {\n          if (form.get(x)) {\n            obj[x] = form.get(x);\n          }\n        }\n\n        if (form.get('draw_image')) {\n          const drawImages = [];\n          for (const entry of form.getAll('draw_image')) {\n            drawImages.push(await imageAsString(entry));\n          }\n          obj['draw_image'] = drawImages;\n        }\n\n        return Response.json(obj);\n      }\n    }\n\n    throw new Error('Unexpected mock invocation');\n  }\n}\n\nexport default {\n  /**\n   * @param {Request} request\n   * @param {*} env\n   * @param {ExecutionContext} ctx\n   * @returns {Promise<Response>}\n   */\n  async fetch(request, env, ctx) {\n    const entrypoint = new ServiceEntrypoint(ctx, env);\n    return entrypoint.fetch(request);\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/instrumentation-test-helper.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// TODO(o11y): Refactor to remove redundant code, and merge with\n// src/workerd/api/tests/instrumentation-tail-worker.js\n\nimport * as assert from 'node:assert';\n\n/**\n * Common helper functions for instrumentation tests.\n * This module provides utilities for collecting and processing spans\n * from streaming tail workers during tests.\n */\n\n/**\n * Creates module-level state for instrumentation tests.\n * This mirrors the original test pattern with module-level variables.\n * @returns {Object} State object with invocationPromises and spans\n */\nexport function createInstrumentationState() {\n  return {\n    invocationPromises: [],\n    spans: new Map(),\n  };\n}\n\n/**\n * Creates the tailStream handler for instrumentation tests.\n * @param {Object} state - The state object from createInstrumentationState\n * @returns {Function} The tailStream handler function\n */\nexport function createTailStreamHandler(state) {\n  return (event, env, ctx) => {\n    // For each \"onset\" event, store a promise which we will resolve when\n    // we receive the equivalent \"outcome\" event\n    let resolveFn;\n    state.invocationPromises.push(\n      new Promise((resolve, reject) => {\n        resolveFn = resolve;\n      })\n    );\n\n    // Accumulate the span info for easier testing\n    return (event) => {\n      let spanKey = `${event.invocationId}#${event.event.spanId || event.spanContext.spanId}`;\n      switch (event.event.type) {\n        case 'spanOpen':\n          // The span ids will change between tests, but Map preserves insertion order\n          state.spans.set(spanKey, { name: event.event.name });\n          break;\n        case 'attributes': {\n          let span = state.spans.get(spanKey);\n          for (let { name, value } of event.event.info) {\n            span[name] = value;\n          }\n          state.spans.set(spanKey, span);\n          break;\n        }\n        case 'spanClose': {\n          let span = state.spans.get(spanKey);\n          span['closed'] = true;\n          state.spans.set(spanKey, span);\n          break;\n        }\n        case 'outcome':\n          resolveFn();\n          break;\n      }\n    };\n  };\n}\n\n/**\n * Runs instrumentation test assertions.\n * This mirrors the original test logic exactly.\n * @param {Object} state - The state object from createInstrumentationState\n * @param {Array} expectedSpans - The expected spans to compare against\n * @param {Object} options - Options for the test\n * @param {Function} options.mapFn - Map function to transform spans before comparison (default: x => x)\n * @param {Function} options.filterFn - Filter function for spans (default: filters out jsRpcSession)\n * @param {string} options.testName - Name for the test (default: 'instrumentation')\n * @param {boolean} options.logReceived - Log received spans for debugging (default: false)\n *\n * Usage for updating tests:\n *   await runInstrumentationTest(state, expectedSpans, { logReceived: true });\n *   // Copy the logged output to update expectedSpans\n */\nexport async function runInstrumentationTest(\n  state,\n  expectedSpans,\n  options = {}\n) {\n  const {\n    mapFn = (x) => x,\n    filterFn = (span) => span.name !== 'jsRpcSession',\n    testName = 'instrumentation',\n    logReceived = false,\n  } = options;\n\n  // Wait for all the tailStream executions to finish\n  await Promise.allSettled(state.invocationPromises);\n\n  // Recorded streaming tail worker events, in insertion order,\n  // mapping and filtering spans not associated with the test\n  let received = Array.from(state.spans.values()).map(mapFn).filter(filterFn);\n\n  // Log received spans for debugging/updating tests\n  if (logReceived) {\n    console.log(`Received spans for ${testName}:\\n`, received);\n  }\n\n  let failed = 0;\n  let i = -1;\n\n  try {\n    assert.equal(received.length, expectedSpans.length);\n    for (i = 0; i < received.length; i++) {\n      assert.deepStrictEqual(received[i], expectedSpans[i]);\n    }\n  } catch (e) {\n    failed++;\n    if (i >= 0) {\n      console.log('spans are not identical', e);\n    } else {\n      console.error(e);\n    }\n  }\n\n  if (failed > 0) {\n    throw `${testName} test failed`;\n  }\n}\n\n/**\n * Creates a tail stream collector for instrumentation tests with encapsulated state.\n * This provides a different API style where state is encapsulated in the returned object.\n * @returns {Object} An object with methods to handle spans\n */\nexport function createTailStreamCollector() {\n  let state = createInstrumentationState();\n\n  const tailStream = createTailStreamHandler(state);\n\n  let spans = state.spans;\n  let invocationPromises = state.invocationPromises;\n  const waitForCompletion = () => {\n    return Promise.allSettled(invocationPromises);\n  };\n\n  return {\n    tailStream,\n    waitForCompletion,\n    spans,\n  };\n}\n\n/**\n * Groups spans by a specific attribute.\n * @param {Array} spans - The spans to group\n * @param {string} attribute - The attribute to group by\n * @returns {Map} A map of attribute values to arrays of spans\n */\nexport function groupSpansBy(spans, attribute) {\n  const groups = new Map();\n\n  for (const span of spans) {\n    const key = span[attribute] || 'unknown';\n    if (!groups.has(key)) {\n      groups.set(key, []);\n    }\n    groups.get(key).push(span);\n  }\n\n  return groups;\n}\n"
  },
  {
    "path": "src/cloudflare/internal/test/pipeline-transform/BUILD.bazel",
    "content": "load(\"//:build/wd_test.bzl\", \"wd_test\")\n\nwd_test(\n    size = \"large\",\n    src = \"transform.wd-test\",\n    args = [\"--experimental\"],\n    data = glob([\"*.js\"]),\n)\n"
  },
  {
    "path": "src/cloudflare/internal/test/pipeline-transform/transform-test.js",
    "content": "// @ts-nocheck\n// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'node:assert';\nimport { PipelineTransformationEntrypoint } from 'cloudflare:pipelines';\n\n// this is how \"Pipeline\" would be implemented by the user\nconst customTransform = class MyEntrypoint extends PipelineTransformationEntrypoint {\n  /**\n   * @param {any} batch\n   * @override\n   */\n  async run(records, _) {\n    for (const record of records) {\n      record.dispatcher = 'was here!';\n      await new Promise((resolve) => setTimeout(resolve, 1));\n      record.wait = 'happened!';\n    }\n\n    return records;\n  }\n};\n\nconst lines = [];\nconst jimmy = `${JSON.stringify({ name: 'jimmy', age: '42', bio: 'hello\\nworld\\n\\n' })}\\n`;\nconst jonny = `${JSON.stringify({ name: 'jonny', age: '9' })}\\n`;\nconst joey = `${JSON.stringify({ name: 'joey', age: '108' })}\\n\\n`;\n\nfor (let i = 0; i < 1000; i++) {\n  lines.push(jimmy, jonny, joey);\n}\n\nfunction newBatch() {\n  return {\n    id: 'test',\n    shard: '0',\n    ts: Date.now(),\n    format: 'json_stream',\n    data: new ReadableStream({\n      start(controller) {\n        const encoder = new TextEncoder();\n        for (const line of lines) {\n          controller.enqueue(encoder.encode(line));\n        }\n        controller.close();\n      },\n    }),\n  };\n}\n\n// bazel test //src/cloudflare/internal/test/pipeline-transform:transform --test_output=errors --sandbox_debug\nexport const tests = {\n  async test(ctr, env, ctx) {\n    {\n      // should fail dispatcher test call when PipelineTransform class not extended\n      const transformer = new PipelineTransformationEntrypoint(ctx, env);\n      await assert.rejects(transformer._ping(), (err) => {\n        assert.strictEqual(\n          err.message,\n          'the run method must be overridden by the PipelineTransformationEntrypoint subclass'\n        );\n        return true;\n      });\n    }\n\n    {\n      // should correctly handle dispatcher test call\n      const transform = new customTransform(ctx, env);\n      await assert.doesNotReject(transform._ping());\n    }\n\n    {\n      // should return mutated batch\n      const transformer = new customTransform(ctx, env);\n      const batch = newBatch();\n\n      const result = await transformer._run(batch, {\n        id: 'abc',\n        name: 'mypipeline',\n      });\n      assert.equal(true, result.data instanceof ReadableStream);\n\n      const reader = result.data\n        .pipeThrough(new TextDecoderStream())\n        .getReader();\n\n      let data = '';\n      while (true) {\n        const { done, value } = await reader.read();\n        if (done) {\n          break;\n        } else {\n          data += value;\n        }\n      }\n      reader.releaseLock();\n\n      assert.notEqual(data.length, 0);\n\n      const objects = [];\n      const resultLines = data.split('\\n');\n      for (const line of resultLines) {\n        if (line.trim().length > 0) {\n          // Guard against empty lines\n          objects.push(JSON.parse(line));\n        }\n      }\n      assert.equal(objects.length, 3000);\n\n      let index = 0;\n      for (const obj of objects) {\n        assert.equal(obj.dispatcher, 'was here!');\n        delete obj.dispatcher;\n        assert.equal(obj.wait, 'happened!');\n        delete obj.wait;\n\n        assert.equal(`${JSON.stringify(obj)}`, lines[index].trim());\n        index++;\n      }\n    }\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/pipeline-transform/transform.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"transform-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"transform-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"experimental\", \"streams_enable_constructors\", \"transformstream_enable_standard_constructor\"]\n      )\n    )\n  ]\n);\n"
  },
  {
    "path": "src/cloudflare/internal/test/to-markdown/BUILD.bazel",
    "content": "load(\"//:build/wd_test.bzl\", \"wd_test\")\nload(\"//src/workerd/server/tests/python:py_wd_test.bzl\", \"py_wd_test\")\n\nwd_test(\n    src = \"to-markdown-api-test.wd-test\",\n    args = [\"--experimental\"],\n    data = glob([\"*.js\"]),\n)\n\npy_wd_test(\n    size = \"large\",\n    src = \"python-to-markdown-api-test.wd-test\",\n    args = [\"--experimental\"],\n    data = glob([\n        \"*.js\",\n        \"*.py\",\n    ]),\n    # Works but times out frequently\n    make_snapshot = False,\n    use_snapshot = None,\n)\n"
  },
  {
    "path": "src/cloudflare/internal/test/to-markdown/python-to-markdown-api-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"to-markdown-api-test\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"to-markdown-api-test.py\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"disable_python_no_global_handlers\", %PYTHON_FEATURE_FLAGS],\n        bindings = [\n        (\n          name = \"ai\",\n          wrapped = (\n            moduleName = \"cloudflare-internal:ai-api\",\n            innerBindings = [(\n              name = \"fetcher\",\n              service = \"to-markdown-mock\"\n            )],\n          )\n        )\n        ],\n      )\n    ),\n    ( name = \"to-markdown-mock\",\n      worker = (\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n        modules = [\n          (name = \"worker\", esModule = embed \"to-markdown-mock.js\")\n        ],\n      )\n    )\n  ]\n);\n"
  },
  {
    "path": "src/cloudflare/internal/test/to-markdown/to-markdown-api-test.js",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'node:assert';\n\nexport const tests = {\n  async test(_, env) {\n    {\n      // Test toMarkdown with single file\n      const resp = await env.ai.toMarkdown(\n        {\n          name: 'random.md',\n          blob: new Blob([`# Random Markdown`], { type: 'text/markdown' }),\n        },\n        { gateway: { id: 'my-gateway' } }\n      );\n\n      assert.deepStrictEqual(resp, {\n        name: 'random.md',\n        mimeType: 'text/markdown',\n        format: 'markdown',\n        tokens: 0,\n        data: '# Random Markdown',\n      });\n    }\n\n    {\n      // Test toMarkdown with extra headers\n      const resp = await env.ai.toMarkdown(\n        {\n          name: 'headers.md',\n          blob: new Blob([`# Random Markdown`], { type: 'text/markdown' }),\n        },\n        { extraHeaders: { example: 'header' } }\n      );\n\n      assert.deepStrictEqual(resp, {\n        name: 'headers.md',\n        mimeType: 'text/markdown',\n        format: 'markdown',\n        tokens: 0,\n        data: { 'content-type': 'application/json', example: 'header' },\n      });\n    }\n\n    {\n      // Test toMarkdown with multiple file\n      const resp = await env.ai.toMarkdown([\n        {\n          name: 'random.md',\n          blob: new Blob([`# Random Markdown`], { type: 'text/markdown' }),\n        },\n        {\n          name: 'cat.png',\n          blob: new Blob(\n            [\n              `The image shows a white and orange cat standing on a beige floor`,\n            ],\n            { type: 'image/png' }\n          ),\n        },\n      ]);\n\n      assert.deepStrictEqual(resp, [\n        {\n          name: 'random.md',\n          mimeType: 'text/markdown',\n          format: 'markdown',\n          tokens: 0,\n          data: '# Random Markdown',\n        },\n        {\n          name: 'cat.png',\n          mimeType: 'image/png',\n          format: 'markdown',\n          tokens: 0,\n          data: 'The image shows a white and orange cat standing on a beige floor',\n        },\n      ]);\n    }\n\n    {\n      // Test toMarkdown using the exported service\n      const resp = await env.ai.toMarkdown().transform([\n        {\n          name: 'random.md',\n          blob: new Blob([`# Random Markdown`], { type: 'text/markdown' }),\n        },\n        {\n          name: 'cat.png',\n          blob: new Blob(\n            [\n              `The image shows a white and orange cat standing on a beige floor`,\n            ],\n            { type: 'image/png' }\n          ),\n        },\n      ]);\n\n      assert.deepStrictEqual(resp, [\n        {\n          name: 'random.md',\n          mimeType: 'text/markdown',\n          format: 'markdown',\n          tokens: 0,\n          data: '# Random Markdown',\n        },\n        {\n          name: 'cat.png',\n          mimeType: 'image/png',\n          format: 'markdown',\n          tokens: 0,\n          data: 'The image shows a white and orange cat standing on a beige floor',\n        },\n      ]);\n    }\n\n    {\n      // Test supported endpoint of ToMarkdown service\n      const resp = await env.ai.toMarkdown().supported();\n\n      assert.deepStrictEqual(resp, [\n        {\n          extension: '.md',\n          mimeType: 'text/markdown',\n        },\n        {\n          extension: '.png',\n          mimeType: 'image/png',\n        },\n      ]);\n    }\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/to-markdown/to-markdown-api-test.py",
    "content": "# Copyright (c) 2024 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n\nasync def test(context, env):\n    resp = await env.ai.toMarkdown().supported()\n    assert len(resp) == 2\n    assert resp[0].extension == \".md\"\n    assert resp[1].extension == \".png\"\n"
  },
  {
    "path": "src/cloudflare/internal/test/to-markdown/to-markdown-api-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"to-markdown-api-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"to-markdown-api-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"],\n        bindings = [\n        (\n          name = \"ai\",\n          wrapped = (\n            moduleName = \"cloudflare-internal:ai-api\",\n            innerBindings = [(\n              name = \"fetcher\",\n              service = \"to-markdown-mock\"\n            )],\n          )\n        )\n        ],\n      )\n    ),\n    ( name = \"to-markdown-mock\",\n      worker = (\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n        modules = [\n          (name = \"worker\", esModule = embed \"to-markdown-mock.js\")\n        ],\n      )\n    )\n  ]\n);\n"
  },
  {
    "path": "src/cloudflare/internal/test/to-markdown/to-markdown-mock.js",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nfunction base64ToBlob(base64, mimeType) {\n  const binaryString = atob(base64); // Decode Base64\n  const len = binaryString.length;\n  const bytes = new Uint8Array(len);\n  for (let i = 0; i < len; i++) {\n    bytes[i] = binaryString.charCodeAt(i);\n  }\n  return new Blob([bytes], { type: mimeType });\n}\n\nexport default {\n  async fetch(request, env, ctx) {\n    const url = new URL(request.url);\n\n    if (url.pathname === '/to-everything/markdown/transformer') {\n      const body = await request.json();\n      const decoder = new TextDecoder('utf-8');\n\n      const result = [];\n\n      for (const file of body.files) {\n        if (file.name === 'headers.md') {\n          const newHeaders = new Headers(request.headers);\n          newHeaders.delete('content-length');\n          result.push({\n            name: file.name,\n            mimeType: file.mimeType,\n            format: 'markdown',\n            tokens: 0,\n            data: Object.fromEntries(newHeaders.entries()),\n          });\n        } else {\n          const fileblob = await base64ToBlob(file.data, file.mimeType);\n          const arr = await fileblob.arrayBuffer();\n          result.push({\n            name: file.name,\n            mimeType: file.mimeType,\n            format: 'markdown',\n            tokens: 0,\n            data: decoder.decode(arr),\n          });\n        }\n      }\n\n      return Response.json({\n        success: true,\n        result: result,\n      });\n    }\n\n    if (url.pathname === '/to-everything/markdown/supported') {\n      return Response.json({\n        success: true,\n        result: [\n          {\n            extension: '.md',\n            mimeType: 'text/markdown',\n          },\n          {\n            extension: '.png',\n            mimeType: 'image/png',\n          },\n        ],\n      });\n    }\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/tracing/BUILD.bazel",
    "content": "load(\"//:build/wd_test.bzl\", \"wd_test\")\n\nwd_test(\n    src = \"tracing-helpers-test.wd-test\",\n    args = [\"--experimental\"],\n    data = glob([\"*.js\"]) + [\"//src/cloudflare/internal/test:instrumentation-test-helper.js\"],\n)\n"
  },
  {
    "path": "src/cloudflare/internal/test/tracing/tracing-helpers-instrumentation-test.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'node:assert';\nimport {\n  createTailStreamCollector,\n  groupSpansBy,\n} from 'instrumentation-test-helper';\n\n// Create the collector and export it for the tail worker\nconst collector = createTailStreamCollector();\nexport default collector;\n\n// After all tests complete, validate the spans\nexport const validateSpans = {\n  async test() {\n    // Wait for all the tailStream executions to finish\n    await collector.waitForCompletion();\n\n    // Get all spans and prepare for validation\n    const allSpans = collector.spans.values();\n    const spansByTest = groupSpansBy(allSpans, 'test');\n\n    // Define the core tests that validate withSpan behavior\n    const testValidations = [\n      { test: 'syncFunction', expectedSpan: 'sync-op' },\n      { test: 'asyncFunction', expectedSpan: 'async-op' },\n      { test: 'syncError', expectedSpan: 'sync-error-op' },\n      { test: 'asyncError', expectedSpan: 'async-error-op' },\n    ];\n\n    // Validate each test's span\n    for (const { test, expectedSpan } of testValidations) {\n      const testSpans = spansByTest.get(test) || [];\n      const span = testSpans.find((s) => s.name === expectedSpan);\n\n      assert(span, `${test}: Should have created span '${expectedSpan}'`);\n      assert(span.closed, `${test}: Span '${expectedSpan}' should be closed`);\n    }\n\n    console.log('All tracing-helpers tests passed!');\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/tracing/tracing-helpers-test.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'node:assert';\n\nexport const syncFunction = {\n  async test(ctrl, env, ctx) {\n    const { withSpan } = env.tracingTest;\n\n    // Test: Synchronous function - span should end immediately\n    const result = withSpan('sync-op', (span) => {\n      span.setAttribute('type', 'sync');\n      span.setAttribute('test', 'syncFunction');\n      return 'sync-value';\n    });\n\n    assert.strictEqual(result, 'sync-value');\n  },\n};\n\nexport const asyncFunction = {\n  async test(ctrl, env, ctx) {\n    const { withSpan } = env.tracingTest;\n\n    // Test: Async function returning Promise - span should end after promise resolves\n    const result = await withSpan('async-op', async (span) => {\n      span.setAttribute('type', 'async');\n      span.setAttribute('test', 'asyncFunction');\n      await new Promise((resolve) => setTimeout(resolve, 10));\n      return 'async-value';\n    });\n\n    assert.strictEqual(result, 'async-value');\n  },\n};\n\nexport const syncError = {\n  async test(ctrl, env, ctx) {\n    const { withSpan } = env.tracingTest;\n\n    // Test: Synchronous error - span should end before throwing\n    let errorCaught = false;\n    try {\n      withSpan('sync-error-op', (span) => {\n        span.setAttribute('type', 'sync-error');\n        span.setAttribute('test', 'syncError');\n        throw new Error('sync error');\n      });\n    } catch (e) {\n      errorCaught = true;\n      assert.strictEqual(e.message, 'sync error');\n    }\n    assert(errorCaught, 'Sync error should have been caught');\n  },\n};\n\nexport const asyncError = {\n  async test(ctrl, env, ctx) {\n    const { withSpan } = env.tracingTest;\n\n    // Test: Async error (rejected promise) - span should end before rejection\n    let errorCaught = false;\n    try {\n      await withSpan('async-error-op', async (span) => {\n        span.setAttribute('type', 'async-error');\n        span.setAttribute('test', 'asyncError');\n        await new Promise((resolve) => setTimeout(resolve, 10));\n        throw new Error('async error');\n      });\n    } catch (e) {\n      errorCaught = true;\n      assert.strictEqual(e.message, 'async error');\n    }\n    assert(errorCaught, 'Async error should have been caught');\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/tracing/tracing-helpers-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"tracing-helpers-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"tracing-helpers-test.js\"),\n        ],\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n        streamingTails = [\"tail\"],\n        bindings = [\n          (\n            name = \"tracingTest\",\n            wrapped = (\n              moduleName = \"cloudflare-internal:test-tracing-wrapper\"\n            )\n          )\n        ],\n      )\n    ),\n    ( name = \"tail\", worker = .tailWorker, ),\n  ]\n);\n\nconst tailWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"tracing-helpers-instrumentation-test.js\"),\n    (name = \"instrumentation-test-helper\", esModule = embed \"../instrumentation-test-helper.js\")\n  ],\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n);\n"
  },
  {
    "path": "src/cloudflare/internal/test/vectorize/BUILD.bazel",
    "content": "load(\"//:build/wd_test.bzl\", \"wd_test\")\nload(\"//src/workerd/server/tests/python:py_wd_test.bzl\", \"py_wd_test\")\n\nwd_test(\n    src = \"vectorize-api-test.wd-test\",\n    data = glob([\"*.js\"]),\n)\n\npy_wd_test(\n    size = \"large\",\n    src = \"python-vectorize-api-test.wd-test\",\n    data = glob([\n        \"*.py\",\n        \"*.js\",\n    ]),\n    # Works but times out\n    make_snapshot = False,\n    use_snapshot = None,\n)\n"
  },
  {
    "path": "src/cloudflare/internal/test/vectorize/python-vectorize-api-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"vectorize-api-test\",\n      worker = (\n        modules = [\n          ( name = \"worker.py\", pythonModule = embed \"vectorize-api-test.py\" )\n        ],\n        compatibilityFlags = [\"nodejs_compat\", %PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\"],\n        bindings = [\n          ( name = \"vectorSearch\",\n            wrapped = (\n              moduleName = \"cloudflare-internal:vectorize-api\",\n              innerBindings = [(\n                name = \"fetcher\",\n                service = \"vector-search-mock\"\n              )],\n            )\n          )\n        ],\n      )\n    ),\n    ( name = \"vector-search-mock\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"vectorize-mock.js\" )\n        ],\n      )\n    )\n  ]\n);\n"
  },
  {
    "path": "src/cloudflare/internal/test/vectorize/vectorize-api-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// @ts-ignore\nimport * as assert from 'node:assert';\nimport { KnownModel, DistanceMetric } from 'cloudflare:vectorize';\n\n/**\n * @typedef {{'vector-search': Vectorize}} Env\n *\n */\n\nexport const test_vector_search_vector_query = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const IDX = env['vector-search'];\n    {\n      // with returnValues = true, returnMetadata = \"indexed\"\n      const results = await IDX.query(new Float32Array(5), {\n        topK: 3,\n        returnValues: true,\n        returnMetadata: 'indexed',\n      });\n      assert.equal(true, results.count > 0);\n      /** @type {VectorizeMatches}  */\n      const expected = {\n        matches: [\n          {\n            id: 'b0daca4a-ffd8-4865-926b-e24800af2a2d',\n            values: [0.2331, 1.0125, 0.6131, 0.9421, 0.9661, 0.8121],\n            metadata: { text: 'She sells seashells by the seashore' },\n            score: 0.71151,\n          },\n          {\n            id: 'a44706aa-a366-48bc-8cc1-3feffd87d548',\n            values: [0.2321, 0.8121, 0.6315, 0.6151, 0.4121, 0.1512],\n            metadata: {\n              text: 'Peter Piper picked a peck of pickled peppers',\n            },\n            score: 0.68913,\n          },\n          {\n            id: '43cfcb31-07e2-411f-8bf9-f82a95ba8b96',\n            values: [0.0515, 0.7512, 0.8612, 0.2153, 0.15121, 0.6812],\n            metadata: {\n              text: 'You know New York, you need New York, you know you need unique New York',\n            },\n            score: 0.94812,\n          },\n        ],\n        count: 3,\n      };\n      assert.deepStrictEqual(results, expected);\n    }\n\n    {\n      // with returnValues = true, returnMetadata = \"indexed\"\n      const results = await IDX.queryById('some-vector-id', {\n        topK: 3,\n        returnValues: true,\n        returnMetadata: 'indexed',\n      });\n      assert.equal(true, results.count > 0);\n      /** @type {VectorizeMatches}  */\n      const expected = {\n        matches: [\n          {\n            id: 'b0daca4a-ffd8-4865-926b-e24800af2a2d',\n            values: [0.2331, 1.0125, 0.6131, 0.9421, 0.9661, 0.8121],\n            metadata: { text: 'She sells seashells by the seashore' },\n            score: 0.71151,\n          },\n          {\n            id: 'a44706aa-a366-48bc-8cc1-3feffd87d548',\n            values: [0.2321, 0.8121, 0.6315, 0.6151, 0.4121, 0.1512],\n            metadata: {\n              text: 'Peter Piper picked a peck of pickled peppers',\n            },\n            score: 0.68913,\n          },\n          {\n            id: '43cfcb31-07e2-411f-8bf9-f82a95ba8b96',\n            values: [0.0515, 0.7512, 0.8612, 0.2153, 0.15121, 0.6812],\n            metadata: {\n              text: 'You know New York, you need New York, you know you need unique New York',\n            },\n            score: 0.94812,\n          },\n        ],\n        count: 3,\n      };\n      assert.deepStrictEqual(results, expected);\n    }\n\n    {\n      // with returnValues = unset (false), returnMetadata = false (\"none\")\n      const results = await IDX.query(new Float32Array(5), {\n        topK: 3,\n        returnMetadata: false,\n      });\n      assert.equal(true, results.count > 0);\n      /** @type {VectorizeMatches}  */\n      const expected = {\n        matches: [\n          {\n            id: 'b0daca4a-ffd8-4865-926b-e24800af2a2d',\n            score: 0.71151,\n          },\n          {\n            id: 'a44706aa-a366-48bc-8cc1-3feffd87d548',\n            score: 0.68913,\n          },\n          {\n            id: '43cfcb31-07e2-411f-8bf9-f82a95ba8b96',\n            score: 0.94812,\n          },\n        ],\n        count: 3,\n      };\n      assert.deepStrictEqual(results, expected);\n    }\n\n    {\n      // with returnValues = unset (false), returnMetadata = true (\"all\")\n      const results = await IDX.query(new Float32Array(5), {\n        topK: 3,\n        returnMetadata: true,\n      });\n      assert.equal(true, results.count > 0);\n      /** @type {VectorizeMatches}  */\n      const expected = {\n        matches: [\n          {\n            id: 'b0daca4a-ffd8-4865-926b-e24800af2a2d',\n            metadata: { text: 'She sells seashells by the seashore' },\n            score: 0.71151,\n          },\n          {\n            id: 'a44706aa-a366-48bc-8cc1-3feffd87d548',\n            metadata: {\n              text: 'Peter Piper picked a peck of pickled peppers',\n            },\n            score: 0.68913,\n          },\n          {\n            id: '43cfcb31-07e2-411f-8bf9-f82a95ba8b96',\n            metadata: {\n              text: 'You know New York, you need New York, you know you need unique New York',\n            },\n            score: 0.94812,\n          },\n        ],\n        count: 3,\n      };\n      assert.deepStrictEqual(results, expected);\n    }\n\n    {\n      // with returnValues = unset (false), returnMetadata = unset (none)\n      const results = await IDX.query(new Float32Array(5), {\n        topK: 3,\n      });\n      assert.equal(true, results.count > 0);\n      /** @type {VectorizeMatches}  */\n      const expected = {\n        matches: [\n          {\n            id: 'b0daca4a-ffd8-4865-926b-e24800af2a2d',\n            score: 0.71151,\n          },\n          {\n            id: 'a44706aa-a366-48bc-8cc1-3feffd87d548',\n            score: 0.68913,\n          },\n          {\n            id: '43cfcb31-07e2-411f-8bf9-f82a95ba8b96',\n            score: 0.94812,\n          },\n        ],\n        count: 3,\n      };\n      assert.deepStrictEqual(results, expected);\n    }\n\n    {\n      // with returnValues = unset (false), returnMetadata = unset (none), filter = \"Peter Piper picked a peck of pickled peppers\"\n      const results = await IDX.query(new Float32Array(5), {\n        topK: 1,\n        filter: {\n          text: { $eq: 'Peter Piper picked a peck of pickled peppers' },\n        },\n      });\n      assert.equal(true, results.count > 0);\n      /** @type {VectorizeMatches}  */\n      const expected = {\n        matches: [\n          {\n            id: 'a44706aa-a366-48bc-8cc1-3feffd87d548',\n            score: 0.68913,\n          },\n        ],\n        count: 1,\n      };\n      assert.deepStrictEqual(results, expected);\n    }\n\n    {\n      // with returnValues = unset (false), returnMetadata = unset (none), filter = \"Peter Piper picked a peck of pickled peppers\"\n      const results = await IDX.query(new Float32Array(5), {\n        topK: 1,\n        filter: {\n          text: {\n            $in: [\n              'Peter Piper picked a peck of pickled peppers',\n              'She sells seashells by the seashore',\n            ],\n          },\n        },\n      });\n      assert.equal(true, results.count > 0);\n      /** @type {VectorizeMatches}  */\n      const expected = {\n        matches: [\n          {\n            id: 'b0daca4a-ffd8-4865-926b-e24800af2a2d',\n            score: 0.71151,\n          },\n          {\n            id: 'a44706aa-a366-48bc-8cc1-3feffd87d548',\n            score: 0.68913,\n          },\n        ],\n        count: 2,\n      };\n      assert.deepStrictEqual(results, expected);\n    }\n\n    {\n      const results = await IDX.query(new Float32Array(5), {\n        topK: 1,\n        filter: {\n          text: {\n            $gt: 'Peter Piper picked a peck of pickled peppers',\n            $lt: 'You know New York, you need New York, you know you need unique New York',\n          },\n        },\n      });\n      assert.equal(true, results.count > 0);\n      /** @type {VectorizeMatches}  */\n      const expected = {\n        matches: [\n          {\n            id: 'b0daca4a-ffd8-4865-926b-e24800af2a2d',\n            score: 0.71151,\n          },\n        ],\n        count: 1,\n      };\n      assert.deepStrictEqual(results, expected);\n    }\n  },\n};\n\nexport const test_vector_search_vector_insert = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const IDX = env['vector-search'];\n    {\n      /** @type {Array<VectorizeVector>}  */\n      const newVectors = [\n        {\n          id: '15cc795d-93d3-416d-9a2a-36fa6fac73da',\n          values: new Float32Array(),\n          metadata: { text: 'He threw three free throws' },\n        },\n        {\n          id: '15cc795d-93d3-416d-9a2a-36fa6fac73da',\n          values: new Float32Array(),\n          metadata: { text: 'Which witch is which?' },\n        },\n      ];\n      const results = await IDX.insert(newVectors);\n      assert.equal(results.mutationId, `total vectors: 5`);\n    }\n  },\n};\n\nexport const test_vector_search_vector_insert_error = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const IDX = env['vector-search'];\n    {\n      /** @type {Array<VectorizeVector>}  */\n      const newVectors = [\n        {\n          id: 'fail-with-test-error',\n          values: new Float32Array(),\n        },\n      ];\n\n      /** @type {Error | null} */\n      let error = null;\n      try {\n        await IDX.insert(newVectors);\n      } catch (e) {\n        error = /** @type {Error} */ (e);\n      }\n\n      assert.equal(\n        error && error.message,\n        'VECTOR_INSERT_ERROR (code = 9999): You asked me for this error'\n      );\n    }\n  },\n};\n\nexport const test_vector_search_vector_upsert = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const IDX = env['vector-search'];\n    {\n      /** @type {Array<VectorizeVector>}  */\n      const newVectors = [\n        {\n          id: '15cc795d-93d3-416d-9a2a-36fa6fac73da',\n          values: new Float32Array(),\n          metadata: { text: 'He threw three free throws' },\n        },\n        {\n          id: '15cc795d-93d3-416d-9a2a-36fa6fac73da',\n          values: [0.3611, 0.9481, 0.8121, 0.7121, 0.8121, 0.0512],\n          metadata: { text: 'Which witch is which?' },\n        },\n      ];\n      const results = await IDX.upsert(newVectors);\n      assert.equal(results.mutationId, `total vectors: 4`);\n    }\n  },\n};\n\nexport const test_vector_search_vector_delete_ids = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const IDX = env['vector-search'];\n    {\n      const results = await IDX.deleteByIds([\n        'vector-a',\n        'vector-b',\n        'vector-c',\n      ]);\n      assert.equal(results.mutationId, `deleted vectors: 3`);\n    }\n  },\n};\n\nexport const test_vector_search_vector_get_ids = {\n  /**\n   * @param {unknown} _\n   * @param {Env} env\n   */\n  async test(_, env) {\n    const IDX = env['vector-search'];\n    {\n      const results = await IDX.getByIds([\n        'b0daca4a-ffd8-4865-926b-e24800af2a2d',\n        '43cfcb31-07e2-411f-8bf9-f82a95ba8b96',\n      ]);\n      assert.deepStrictEqual(results, [\n        {\n          id: 'b0daca4a-ffd8-4865-926b-e24800af2a2d',\n          values: [0.2331, 1.0125, 0.6131, 0.9421, 0.9661, 0.8121],\n          metadata: { text: 'She sells seashells by the seashore' },\n        },\n        {\n          id: '43cfcb31-07e2-411f-8bf9-f82a95ba8b96',\n          values: [0.0515, 0.7512, 0.8612, 0.2153, 0.15121, 0.6812],\n          metadata: {\n            text: 'You know New York, you need New York, you know you need unique New York',\n          },\n        },\n      ]);\n    }\n  },\n};\n\nexport const test_vector_search_can_use_enum_exports = {\n  async test() {\n    assert.equal(\n      KnownModel['openai/text-embedding-ada-002'],\n      'openai/text-embedding-ada-002'\n    );\n    assert.equal(DistanceMetric.COSINE, 'cosine');\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/vectorize/vectorize-api-test.py",
    "content": "# Copyright (c) 2023 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\nfrom js import Float32Array\n\n\nasync def test(context, env):\n    IDX = env.vectorSearch\n\n    res_array = [0] * 5\n    results = await IDX.query(\n        Float32Array.new(res_array),\n        topK=3,\n        returnValues=True,\n        returnMetadata=True,\n    )\n    assert results.count > 0\n    assert results.matches[0].id == \"b0daca4a-ffd8-4865-926b-e24800af2a2d\"\n    assert (\n        results.matches[1].metadata.text\n        == \"Peter Piper picked a peck of pickled peppers\"\n    )\n"
  },
  {
    "path": "src/cloudflare/internal/test/vectorize/vectorize-api-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"vectorize-api-test\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"vectorize-api-test.js\" )\n        ],\n        compatibilityFlags = [\"nodejs_compat\"],\n        bindings = [\n          ( name = \"vector-search\",\n            wrapped = (\n              moduleName = \"cloudflare-internal:vectorize-api\",\n              innerBindings = [(\n                name = \"fetcher\",\n                service = \"vector-search-mock\"\n              ),\n              (name = \"indexId\", text = \"an-index\"),\n              (name = \"indexVersion\", text = \"v2\")],\n            )\n          )\n        ],\n      )\n    ),\n    ( name = \"vector-search-mock\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"vectorize-mock.js\" )\n        ],\n      )\n    )\n  ]\n);\n"
  },
  {
    "path": "src/cloudflare/internal/test/vectorize/vectorize-mock.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n/** @type {Array<VectorizeMatch>} */\nconst exampleVectorMatches = [\n  {\n    id: 'b0daca4a-ffd8-4865-926b-e24800af2a2d',\n    values: [0.2331, 1.0125, 0.6131, 0.9421, 0.9661, 0.8121],\n    metadata: { text: 'She sells seashells by the seashore' },\n    score: 0.71151,\n  },\n  {\n    id: 'a44706aa-a366-48bc-8cc1-3feffd87d548',\n    values: [0.2321, 0.8121, 0.6315, 0.6151, 0.4121, 0.1512],\n    metadata: { text: 'Peter Piper picked a peck of pickled peppers' },\n    score: 0.68913,\n  },\n  {\n    id: '43cfcb31-07e2-411f-8bf9-f82a95ba8b96',\n    values: [0.0515, 0.7512, 0.8612, 0.2153, 0.15121, 0.6812],\n    metadata: {\n      text: 'You know New York, you need New York, you know you need unique New York',\n    },\n    score: 0.94812,\n  },\n];\n/** @type {Array<VectorizeVector>} */\nconst exampleVectors = exampleVectorMatches\n  .filter((m) => typeof m !== 'undefined')\n  .map(({ id, values, metadata }) => ({\n    id,\n    values: values ?? [],\n    metadata: metadata ?? {},\n  }));\n\nexport default {\n  /**\n   * @param {Request} request\n   */\n  async fetch(request) {\n    try {\n      const { pathname } = new URL(request.url);\n\n      if (request.method === 'POST' && pathname.endsWith('/create')) {\n        /** @type {VectorizeIndexConfig} */\n        const config = await request.json();\n        const name = pathname.split('/')[2];\n        /** @type {VectorizeIndexDetails} */\n        const index = {\n          id: 'ffeb30f5-d349-4ba5-8dde-79da543190fe',\n          name: name || 'my-index',\n          config: config,\n          vectorsCount: 0,\n        };\n        return Response.json(index);\n      } else if (request.method === 'GET' && pathname.endsWith('/list')) {\n        /** @type {Array<VectorizeIndexDetails>} */\n        const index = [\n          {\n            id: '0f48d520-5bf5-4980-acd9-98453fb8a27f',\n            name: 'my-first-index',\n            config: {\n              dimensions: 1536,\n              preset: 'openai/text-embedding-ada-002',\n              metric: 'euclidean',\n            },\n            vectorsCount: 500000,\n          },\n          {\n            id: 'b9fc84af-31f3-449c-bc61-b62abc86d5a1',\n            name: 'my-second-index',\n            config: {\n              dimensions: 1536,\n              metric: 'dot-product',\n            },\n            vectorsCount: 750000,\n          },\n        ];\n        return Response.json(index);\n      } else if (request.method === 'GET' && pathname.split('/').length === 3) {\n        /** @type {VectorizeIndexDetails} */\n        const index = {\n          id: 'ffeb30f5-d349-4ba5-8dde-79da543190fe',\n          name: pathname.split('/')[2] || 'my-index',\n          config: {\n            dimensions: 1536,\n            preset: 'openai/text-embedding-ada-002',\n            metric: 'euclidean',\n          },\n          vectorsCount: 850850,\n        };\n        return Response.json(index);\n      } else if (\n        request.method === 'DELETE' &&\n        pathname.split('/').length === 2\n      ) {\n        return Response.json({});\n      } else if (request.method === 'POST' && pathname.endsWith('/query')) {\n        /** @type {VectorizeQueryOptions & ({vector: number[]} | {vectorId: string})} */\n        const body = await request.json();\n        let returnSet = structuredClone(exampleVectorMatches);\n        if (\n          body?.filter?.['text'] &&\n          typeof body?.filter?.['text'] === 'object'\n        ) {\n          if (body?.filter?.['text']?.['$eq'] !== undefined) {\n            const criteria = body?.filter?.['text']?.['$eq'];\n            returnSet = returnSet.filter(\n              (m) => m.metadata?.['text'] === criteria\n            );\n          }\n          if (body?.filter?.['text']?.['$in'] !== undefined) {\n            const criteria = body?.filter?.['text']?.['$in'];\n            returnSet = returnSet.filter((m) =>\n              criteria.includes(m.metadata?.['text'])\n            );\n          }\n          if (body?.filter?.['text']?.['$gt'] !== undefined) {\n            const criteria = body?.filter?.['text']?.['$gt'];\n            returnSet = returnSet.filter(\n              (m) => m.metadata?.['text'] > criteria\n            );\n          }\n          if (body?.filter?.['text']?.['$lt'] !== undefined) {\n            const criteria = body?.filter?.['text']?.['$lt'];\n            returnSet = returnSet.filter(\n              (m) => m.metadata?.['text'] < criteria\n            );\n          }\n        }\n\n        if (!body?.returnValues)\n          returnSet.forEach((v) => {\n            delete v.values;\n          });\n        if (!body?.returnMetadata || body?.returnMetadata === 'none')\n          returnSet.forEach((v) => {\n            delete v.metadata;\n          });\n        return Response.json({\n          matches: returnSet,\n          count: returnSet.length,\n        });\n      } else if (request.method === 'POST' && pathname.endsWith('/insert')) {\n        /** @type {{vectors: Array<VectorizeVector>}} */\n        const data = await request.json();\n        if (data.vectors.find((v) => v.id === 'fail-with-test-error')) {\n          return Response.json(\n            {\n              code: 9999,\n              error: 'You asked me for this error',\n            },\n            {\n              status: 400,\n            }\n          );\n        }\n\n        /** @type {VectorizeAsyncMutation} */\n        const res = {\n          // fudge a bit and set the mutation id to some internals so our asserts can check more\n          mutationId: `total vectors: ${data.vectors.length + exampleVectors.length}`,\n        };\n        return Response.json(res);\n      } else if (request.method === 'POST' && pathname.endsWith('/upsert')) {\n        /** @type {{vectors: Array<VectorizeVector>}} */\n        let data = await request.json();\n        if (data.vectors.length > 1) data.vectors.splice(-1);\n        /** @type {VectorizeAsyncMutation} */\n        const res = {\n          // fudge a bit and set the mutation id to some internals so our asserts can check more\n          mutationId: `total vectors: ${data.vectors.length + exampleVectors.length}`,\n        };\n        return Response.json(res);\n      } else if (\n        request.method === 'POST' &&\n        pathname.endsWith('/deleteByIds')\n      ) {\n        /** @type {{ids: Array<string>}} */\n        const body = await request.json();\n        /** @type {VectorizeAsyncMutation} */\n        const res = {\n          // fudge a bit and set the mutation id to some internals so our asserts can check more\n          mutationId: `deleted vectors: ${body.ids.length}`,\n        };\n        return Response.json(res);\n      } else if (request.method === 'POST' && pathname.endsWith('/getByIds')) {\n        /** @type {{ids: Array<string>}} */\n        const body = await request.json();\n        return Response.json(\n          exampleVectors.filter(({ id }) => body.ids.includes(id))\n        );\n      } else {\n        return Response.json({ error: 'Not found' }, { status: 404 });\n      }\n    } catch (err) {\n      return Response.json(\n        // @ts-ignore\n        { error: err.message, stack: err.stack },\n        { status: 500 }\n      );\n    }\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/workflows/BUILD.bazel",
    "content": "load(\"//:build/wd_test.bzl\", \"wd_test\")\n\nwd_test(\n    src = \"workflows-api-test.wd-test\",\n    args = [\"--experimental\"],\n    data = glob([\"*.js\"]),\n)\n"
  },
  {
    "path": "src/cloudflare/internal/test/workflows/workflows-api-test.js",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport * as assert from 'node:assert';\n\nexport const tests = {\n  async test(_, env) {\n    {\n      // Test create instance\n      const instance = await env.workflow.create({\n        id: 'foo',\n        payload: { bar: 'baz' },\n      });\n      assert.deepStrictEqual(instance.id, 'foo');\n    }\n\n    {\n      // Test get instance\n      const instance = await env.workflow.get('bar');\n      assert.deepStrictEqual(instance.id, 'bar');\n    }\n\n    {\n      // Test createBatch\n      const instances = await env.workflow.createBatch([\n        {\n          id: 'foo',\n          payload: { bar: 'baz' },\n        },\n        {\n          id: 'bar',\n          payload: { bar: 'baz' },\n        },\n      ]);\n      assert.deepStrictEqual(instances[0].id, 'foo');\n      assert.deepStrictEqual(instances[1].id, 'bar');\n    }\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test/workflows/workflows-api-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"workflows-api-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"workflows-api-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"],\n        bindings = [\n        (\n          name = \"workflow\",\n          wrapped = (\n            moduleName = \"cloudflare-internal:workflows-api\",\n            innerBindings = [(\n              name = \"fetcher\",\n              service = \"workflows-mock\"\n            )],\n          )\n        )\n        ],\n      )\n    ),\n    ( name = \"workflows-mock\",\n      worker = (\n        compatibilityFlags = [\"nodejs_compat\"],\n        modules = [\n          (name = \"worker\", esModule = embed \"workflows-mock.js\")\n        ],\n      )\n    )\n  ]\n);\n"
  },
  {
    "path": "src/cloudflare/internal/test/workflows/workflows-mock.js",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport default {\n  async fetch(request, env, ctx) {\n    const data = await request.json();\n    const reqUrl = new URL(request.url);\n\n    if (reqUrl.pathname === '/get' && request.method === 'POST') {\n      return Response.json(\n        {\n          result: {\n            id: data.id,\n          },\n        },\n        {\n          status: 200,\n          headers: {\n            'content-type': 'application/json',\n          },\n        }\n      );\n    }\n\n    if (reqUrl.pathname === '/create' && request.method === 'POST') {\n      return Response.json(\n        {\n          result: {\n            id: data.id,\n          },\n        },\n        {\n          status: 201,\n          headers: {\n            'content-type': 'application/json',\n          },\n        }\n      );\n    }\n\n    if (reqUrl.pathname === '/createBatch' && request.method === 'POST') {\n      return Response.json(\n        {\n          result: data.map((val) => ({ id: val.id })),\n        },\n        {\n          status: 201,\n          headers: {\n            'content-type': 'application/json',\n          },\n        }\n      );\n    }\n  },\n};\n"
  },
  {
    "path": "src/cloudflare/internal/test-tracing-wrapper.ts",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// TEST-ONLY MODULE - DO NOT USE IN PRODUCTION\n// This wrapper exists solely to expose internal tracing utilities for testing.\n// It must be in the internal/ directory to be compiled as part of the cloudflare bundle,\n// but it should never be used outside of test configurations.\n\nimport { withSpan } from 'cloudflare-internal:tracing-helpers';\n\ninterface TestWrapper {\n  withSpan: typeof withSpan;\n}\n\n// Wrapper function that provides test utilities for tracing\nexport default function (_env: unknown): TestWrapper {\n  return {\n    // Export withSpan for testing\n    withSpan,\n  };\n}\n"
  },
  {
    "path": "src/cloudflare/internal/to-markdown-api.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { AiInternalError } from 'cloudflare-internal:ai-api';\nimport type { GatewayOptions } from 'cloudflare-internal:aig-api';\nimport base64 from 'cloudflare-internal:base64';\n\ninterface Fetcher {\n  fetch: typeof fetch;\n}\n\nexport type MarkdownDocument = {\n  name: string;\n  blob: Blob;\n};\n\nexport type ConversionResponse =\n  | {\n      id: string;\n      name: string;\n      mimeType: string;\n      format: 'markdown';\n      tokens: number;\n      data: string;\n    }\n  | {\n      id: string;\n      name: string;\n      mimeType: string;\n      format: 'error';\n      error: string;\n    };\n\nexport type ImageConversionOptions = {\n  descriptionLanguage?: 'en' | 'es' | 'fr' | 'it' | 'pt' | 'de';\n};\n\nexport type EmbeddedImageConversionOptions = ImageConversionOptions & {\n  convert?: boolean;\n  maxConvertedImages?: number;\n};\n\nexport type ConversionOptions = {\n  html?: {\n    images?: EmbeddedImageConversionOptions & { convertOGImage?: boolean };\n    hostname?: string;\n    cssSelector?: string;\n  };\n  docx?: {\n    images?: EmbeddedImageConversionOptions;\n  };\n  image?: ImageConversionOptions;\n  pdf?: {\n    images?: EmbeddedImageConversionOptions;\n    metadata?: boolean;\n  };\n};\n\nexport type ConversionRequestOptions = {\n  gateway?: GatewayOptions;\n  extraHeaders?: object;\n  conversionOptions?: ConversionOptions;\n};\n\nexport type SupportedFileFormat = {\n  mimeType: string;\n  extension: string;\n};\n\nasync function blobToBase64(blob: Blob): Promise<string> {\n  return base64.encodeArrayToString(await blob.arrayBuffer());\n}\n\nexport class ToMarkdownService {\n  #fetcher: Fetcher;\n  #endpointURL = 'https://workers-binding.ai';\n\n  constructor(fetcher: Fetcher) {\n    this.#fetcher = fetcher;\n  }\n\n  async transform(\n    files: MarkdownDocument[],\n    options?: ConversionRequestOptions\n  ): Promise<ConversionResponse[]>;\n  async transform(\n    files: MarkdownDocument,\n    options?: ConversionRequestOptions\n  ): Promise<ConversionResponse>;\n  async transform(\n    files: MarkdownDocument | MarkdownDocument[],\n    options?: ConversionRequestOptions\n  ): Promise<ConversionResponse | ConversionResponse[]> {\n    const input = Array.isArray(files) ? files : [files];\n\n    const processedFiles = [];\n    for (const file of input) {\n      processedFiles.push({\n        name: file.name,\n        mimeType: file.blob.type,\n        data: await blobToBase64(file.blob),\n      });\n    }\n\n    const fetchOptions = {\n      method: 'POST',\n      body: JSON.stringify({\n        files: processedFiles,\n        options: options,\n      }),\n      headers: {\n        ...options?.extraHeaders,\n        'content-type': 'application/json',\n      },\n    };\n\n    const endpointUrl = `${this.#endpointURL}/to-everything/markdown/transformer`;\n\n    const res = await this.#fetcher.fetch(endpointUrl, fetchOptions);\n\n    if (!res.ok) {\n      const content = (await res.json()) as {\n        errors: { message: string }[];\n      };\n\n      throw new AiInternalError(\n        content.errors.at(0)?.message || 'Internal Error'\n      );\n    }\n\n    const data = (await res.json()) as { result: ConversionResponse[] };\n\n    // If the user sent a list of files, return an array of results, otherwise, return just the first object\n    if (Array.isArray(files)) {\n      return data.result;\n    }\n\n    if (data.result.length === 0) {\n      throw new AiInternalError(\n        'Internal Error Converting files into Markdown'\n      );\n    }\n\n    const obj = data.result.at(0);\n    if (!obj) {\n      throw new AiInternalError(\n        'Internal Error Converting files into Markdown'\n      );\n    }\n\n    return obj;\n  }\n\n  async supported(): Promise<SupportedFileFormat[]> {\n    const fetchOptions = {\n      method: 'GET',\n      headers: {\n        'content-type': 'application/json',\n      },\n    };\n\n    const endpointUrl = `${this.#endpointURL}/to-everything/markdown/supported`;\n\n    const res = await this.#fetcher.fetch(endpointUrl, fetchOptions);\n\n    // This endpoint never fails so just parse the output\n    const { result: extensions } = (await res.json()) as {\n      result: SupportedFileFormat[];\n    };\n\n    return extensions;\n  }\n}\n"
  },
  {
    "path": "src/cloudflare/internal/tracing-helpers.ts",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport tracing from 'cloudflare-internal:tracing';\n\nexport type Span = ReturnType<typeof tracing.startSpan>;\n\n/**\n * Helper function to wrap operations with tracing spans.\n * Automatically handles span lifecycle for both sync and async operations.\n *\n * @param name - The operation name for the span\n * @param fn - The function to execute within the span context\n * @returns The result of the function\n *\n * @example\n * // Synchronous usage\n * const result = withSpan('prepare', (span) => {\n *   span.setAttribute('query', sql);\n *   return new PreparedStatement(sql);\n * });\n *\n * @example\n * // Asynchronous usage\n * const result = await withSpan('exec', async (span) => {\n *   span.setAttribute('query', sql);\n *   return await database.execute(sql);\n * });\n *\n * @note Generator functions are not currently supported and will have their\n * spans ended immediately after the generator object is returned, not when\n * the generator is exhausted.\n */\nexport function withSpan<T>(\n  name: string,\n  fn: (span: ReturnType<typeof tracing.startSpan>) => T\n): T {\n  const span = tracing.startSpan(name);\n\n  try {\n    const result = fn(span);\n\n    // Handle async results - ensure span ends after completion\n    if (result instanceof Promise) {\n      return Promise.resolve(result).finally(() => {\n        span.end();\n      }) as T;\n    }\n\n    // Synchronous result - end span immediately\n    span.end();\n    return result;\n  } catch (error) {\n    span.end();\n    throw error;\n  }\n}\n"
  },
  {
    "path": "src/cloudflare/internal/tracing.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport class Span {\n  // Sets an attribute on this span. If value is undefined, the attribute is not set.\n  setAttribute(key: string, value: string | number | boolean | undefined): void;\n  // Closes the span\n  end(): void;\n}\n\nfunction startSpan(name: string): Span;\n"
  },
  {
    "path": "src/cloudflare/internal/vectorize-api.ts",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nconst queryMetadataOptional =\n  !!Cloudflare.compatibilityFlags['vectorize_query_metadata_optional'];\n\ninterface Fetcher {\n  fetch: typeof fetch;\n}\n\nconst Operation = {\n  INDEX_GET: 'INDEX_GET',\n  VECTOR_QUERY: 'VECTOR_QUERY',\n  VECTOR_INSERT: 'VECTOR_INSERT',\n  VECTOR_UPSERT: 'VECTOR_UPSERT',\n  VECTOR_GET: 'VECTOR_GET',\n  VECTOR_DELETE: 'VECTOR_DELETE',\n} as const;\ntype OperationKey = keyof typeof Operation;\n\ntype VectorizeVersion = 'v1' | 'v2';\n\ntype QueryImplV2Params =\n  | { vector: VectorFloatArray | number[]; vectorId?: undefined }\n  | { vector?: undefined; vectorId: string };\n\nfunction toNdJson(arr: object[]): string {\n  return arr.reduce((acc, o) => acc + JSON.stringify(o) + '\\n', '').trim();\n}\n\n/*\n * The Vectorize beta VectorizeIndex shares the same methods, so to keep things simple, they share one implementation.\n * The types here are specific to Vectorize GA, but the types here don't actually matter as they are stripped away\n * and not visible to end users.\n */\nclass VectorizeIndexImpl implements Vectorize {\n  // eslint-disable-next-line no-restricted-syntax\n  private readonly fetcher: Fetcher;\n  // eslint-disable-next-line no-restricted-syntax\n  private readonly indexId: string;\n  // eslint-disable-next-line no-restricted-syntax\n  private readonly indexVersion: VectorizeVersion;\n  // eslint-disable-next-line no-restricted-syntax\n  private readonly useNdJson: boolean;\n\n  constructor(\n    fetcher: Fetcher,\n    indexId: string,\n    indexVersion: VectorizeVersion,\n    useNdJson: boolean\n  ) {\n    this.fetcher = fetcher;\n    this.indexId = indexId;\n    this.indexVersion = indexVersion;\n    this.useNdJson = useNdJson;\n  }\n\n  async describe(): Promise<VectorizeIndexInfo> {\n    const endpoint =\n      this.indexVersion === 'v2' ? `info` : `binding/indexes/${this.indexId}`;\n    const res = await this._send(Operation.INDEX_GET, endpoint, {\n      method: 'GET',\n    });\n\n    return await toJson<VectorizeIndexInfo>(res);\n  }\n\n  async query(\n    vector: VectorFloatArray | number[],\n    options?: VectorizeQueryOptions\n  ): Promise<VectorizeMatches> {\n    if (this.indexVersion === 'v2') {\n      return await this.queryImplV2(\n        { vector: Array.isArray(vector) ? vector : Array.from(vector) },\n        options\n      );\n    } else {\n      if (\n        options &&\n        options.returnMetadata &&\n        typeof options.returnMetadata !== 'boolean'\n      ) {\n        throw new Error(\n          `Invalid returnMetadata option. Expected boolean; got: ${options.returnMetadata}`\n        );\n      }\n      const compat = {\n        queryMetadataOptional,\n      };\n      const res = await this._send(\n        Operation.VECTOR_QUERY,\n        `binding/indexes/${this.indexId}/query`,\n        {\n          method: 'POST',\n          body: JSON.stringify({\n            ...options,\n            vector: Array.isArray(vector) ? vector : Array.from(vector),\n            compat,\n          }),\n          headers: {\n            'content-type': 'application/json',\n            accept: 'application/json',\n            'cf-vector-search-query-compat': JSON.stringify(compat),\n          },\n        }\n      );\n\n      return await toJson<VectorizeMatches>(res);\n    }\n  }\n\n  async queryById(\n    vectorId: string,\n    options?: VectorizeQueryOptions\n  ): Promise<VectorizeMatches> {\n    if (this.indexVersion === 'v1') {\n      throw new Error(`QueryById operation is not supported for v1 indexes.`);\n    } else {\n      return await this.queryImplV2({ vectorId }, options);\n    }\n  }\n\n  async insert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation> {\n    const endpoint =\n      this.indexVersion === 'v2'\n        ? `insert`\n        : `binding/indexes/${this.indexId}/insert`;\n    const bodyVecArr = vectors.map((vec) => ({\n      ...vec,\n      values: Array.isArray(vec.values) ? vec.values : Array.from(vec.values),\n    }));\n\n    const body = this.useNdJson\n      ? toNdJson(bodyVecArr)\n      : JSON.stringify({ vectors: bodyVecArr });\n\n    const contentType = this.useNdJson\n      ? 'application/x-ndjson'\n      : 'application/json';\n\n    const res = await this._send(Operation.VECTOR_INSERT, endpoint, {\n      method: 'POST',\n      body,\n      headers: {\n        'content-type': contentType,\n        'cf-vector-search-dim-width': String(\n          // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n          vectors.length ? vectors.at(0)?.values?.length : 0\n        ),\n        'cf-vector-search-dim-height': String(vectors.length),\n        accept: 'application/json',\n      },\n    });\n\n    return await toJson<VectorizeAsyncMutation>(res);\n  }\n\n  async upsert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation> {\n    const endpoint =\n      this.indexVersion === 'v2'\n        ? `upsert`\n        : `binding/indexes/${this.indexId}/upsert`;\n    const bodyVecArr = vectors.map((vec) => ({\n      ...vec,\n      values: Array.isArray(vec.values) ? vec.values : Array.from(vec.values),\n    }));\n\n    const body = this.useNdJson\n      ? toNdJson(bodyVecArr)\n      : JSON.stringify({ vectors: bodyVecArr });\n\n    const contentType = this.useNdJson\n      ? 'application/x-ndjson'\n      : 'application/json';\n\n    const res = await this._send(Operation.VECTOR_UPSERT, endpoint, {\n      method: 'POST',\n      body,\n      headers: {\n        'content-type': contentType,\n        'cf-vector-search-dim-width': String(\n          // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n          vectors.length ? vectors.at(0)?.values?.length : 0\n        ),\n        'cf-vector-search-dim-height': String(vectors.length),\n        accept: 'application/json',\n      },\n    });\n\n    return await toJson<VectorizeAsyncMutation>(res);\n  }\n\n  async getByIds(ids: string[]): Promise<VectorizeVector[]> {\n    const endpoint =\n      this.indexVersion === 'v2'\n        ? `getByIds`\n        : `binding/indexes/${this.indexId}/getByIds`;\n    const res = await this._send(Operation.VECTOR_GET, endpoint, {\n      method: 'POST',\n      body: JSON.stringify({ ids }),\n      headers: {\n        'content-type': 'application/json',\n        accept: 'application/json',\n      },\n    });\n\n    return await toJson<VectorizeVector[]>(res);\n  }\n\n  async deleteByIds(ids: string[]): Promise<VectorizeAsyncMutation> {\n    const endpoint =\n      this.indexVersion === 'v2'\n        ? `deleteByIds`\n        : `binding/indexes/${this.indexId}/deleteByIds`;\n    const res = await this._send(Operation.VECTOR_DELETE, endpoint, {\n      method: 'POST',\n      body: JSON.stringify({ ids }),\n      headers: {\n        'content-type': 'application/json',\n        accept: 'application/json',\n      },\n    });\n\n    return await toJson<VectorizeAsyncMutation>(res);\n  }\n\n  // eslint-disable-next-line no-restricted-syntax\n  private async _send(\n    operation: OperationKey,\n    endpoint: string,\n    init: RequestInit\n  ): Promise<Response> {\n    const res = await this.fetcher.fetch(\n      `http://vector-search/${endpoint}`, // `http://vector-search` is just a dummy host, the attached fetcher will receive the request\n      init\n    );\n    if (res.status !== 200) {\n      let err: Error | null = null;\n\n      try {\n        const errResponse = (await res.json()) as VectorizeError;\n        err = new Error(\n          `${Operation[operation]}_ERROR${\n            typeof errResponse.code === 'number'\n              ? ` (code = ${errResponse.code})`\n              : ''\n          }: ${errResponse.error}`,\n          {\n            cause: new Error(errResponse.error),\n          }\n        );\n      } catch {\n        // do nothing\n      }\n\n      if (err) {\n        throw err;\n      } else {\n        throw new Error(\n          `${Operation[operation]}_ERROR: Status + ${res.status}`,\n          {\n            cause: new Error(`Status ${res.status}`),\n          }\n        );\n      }\n    }\n\n    return res;\n  }\n\n  // eslint-disable-next-line no-restricted-syntax\n  private async queryImplV2(\n    vectorParams: QueryImplV2Params,\n    options?: VectorizeQueryOptions\n  ): Promise<VectorizeMatches> {\n    if (options?.returnMetadata) {\n      if (\n        typeof options.returnMetadata !== 'boolean' &&\n        !isVectorizeMetadataRetrievalLevel(options.returnMetadata)\n      ) {\n        throw new Error(\n          `Invalid returnMetadata option. Expected: true, false, \"none\", \"indexed\" or \"all\"; got: ${options.returnMetadata}`\n        );\n      }\n\n      if (typeof options.returnMetadata === 'boolean') {\n        // Allow boolean returnMetadata for backward compatibility. true converts to 'all' and false converts to 'none'\n        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n        options.returnMetadata = options.returnMetadata ? 'all' : 'none';\n      }\n    }\n    const res = await this._send(Operation.VECTOR_QUERY, `query`, {\n      method: 'POST',\n      body: JSON.stringify({\n        ...options,\n        ...(vectorParams.vector\n          ? { vector: vectorParams.vector }\n          : { vectorId: vectorParams.vectorId }),\n      }),\n      headers: {\n        'content-type': 'application/json',\n        accept: 'application/json',\n      },\n    });\n\n    return await toJson<VectorizeMatches>(res);\n  }\n}\n\nfunction isVectorizeMetadataRetrievalLevel(value: unknown): boolean {\n  return (\n    typeof value === 'string' &&\n    (value === 'all' || value === 'indexed' || value === 'none')\n  );\n}\n\nconst maxBodyLogChars = 1_000;\nasync function toJson<T = unknown>(response: Response): Promise<T> {\n  const body = await response.text();\n  try {\n    return JSON.parse(body) as T;\n  } catch {\n    throw new Error(\n      `Failed to parse body as JSON, got: ${\n        body.length > maxBodyLogChars\n          ? `${body.slice(0, maxBodyLogChars)}…`\n          : body\n      }`\n    );\n  }\n}\n\nexport function makeBinding(env: {\n  fetcher: Fetcher;\n  indexId: string;\n  indexVersion?: VectorizeVersion;\n  useNdJson?: boolean;\n}): Vectorize {\n  return new VectorizeIndexImpl(\n    env.fetcher,\n    env.indexId,\n    env.indexVersion ?? 'v1',\n    env.useNdJson ?? false\n  );\n}\n\nexport default makeBinding;\n"
  },
  {
    "path": "src/cloudflare/internal/vectorize.d.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n/*****************************\n *\n * !!! WARNING !!!\n * Changes should be made in `types/defines/vectorize.d.ts`\n * and then synced back here.\n *\n * This files was is copy & pasted from the types/ folder 2 years ago\n * because when bazel runs it doesn't have access to that directly (and thusly is sad).\n * TODO: come up with a better system for this.\n *\n ****************************** /\n\n/**\n * Data types supported for holding vector metadata.\n */\ntype VectorizeVectorMetadataValue = string | number | boolean | string[];\n/**\n * Additional information to associate with a vector.\n */\ntype VectorizeVectorMetadata =\n  | VectorizeVectorMetadataValue\n  | Record<string, VectorizeVectorMetadataValue>;\n\ntype VectorFloatArray = Float32Array | Float64Array;\n\ninterface VectorizeError {\n  code?: number;\n  error: string;\n}\n\n/**\n * Comparison logic/operation to use for metadata filtering.\n *\n * This list is expected to grow as support for more operations are released.\n */\ntype VectorizeVectorMetadataFilterOp =\n  | '$eq'\n  | '$ne'\n  | '$lt'\n  | '$lte'\n  | '$gt'\n  | '$gte';\ntype VectorizeVectorMetadataFilterCollectionOp = '$in' | '$nin';\n\n/**\n * Filter criteria for vector metadata used to limit the retrieved query result set.\n */\ntype VectorizeVectorMetadataFilter = {\n  [field: string]:\n    | Exclude<VectorizeVectorMetadataValue, string[]>\n    | null\n    | {\n        [Op in VectorizeVectorMetadataFilterOp]?: Exclude<\n          VectorizeVectorMetadataValue,\n          string[]\n        > | null;\n      }\n    | {\n        [Op in VectorizeVectorMetadataFilterCollectionOp]?: Exclude<\n          VectorizeVectorMetadataValue,\n          string[]\n        >[];\n      };\n};\n\n/**\n * Supported distance metrics for an index.\n * Distance metrics determine how other \"similar\" vectors are determined.\n */\ntype VectorizeDistanceMetric = 'euclidean' | 'cosine' | 'dot-product';\n\n/**\n * Metadata return levels for a Vectorize query.\n *\n * Default to \"none\".\n *\n * @property all      Full metadata for the vector return set, including all fields (including those un-indexed) without truncation. This is a more expensive retrieval, as it requires additional fetching & reading of un-indexed data.\n * @property indexed  Return all metadata fields configured for indexing in the vector return set. This level of retrieval is \"free\" in that no additional overhead is incurred returning this data. However, note that indexed metadata is subject to truncation (especially for larger strings).\n * @property none     No indexed metadata will be returned.\n */\ntype VectorizeMetadataRetrievalLevel = 'all' | 'indexed' | 'none';\n\ninterface VectorizeQueryOptions {\n  topK?: number;\n  namespace?: string;\n  returnValues?: boolean;\n  returnMetadata?: boolean | VectorizeMetadataRetrievalLevel;\n  filter?: VectorizeVectorMetadataFilter;\n}\n\n/**\n * Information about the configuration of an index.\n */\ntype VectorizeIndexConfig =\n  | {\n      dimensions: number;\n      metric: VectorizeDistanceMetric;\n    }\n  | {\n      preset: string; // keep this generic, as we'll be adding more presets in the future and this is only in a read capacity\n    };\n\n/**\n * Metadata about an existing index.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link VectorizeIndexInfo} for its post-beta equivalent.\n */\ninterface VectorizeIndexDetails {\n  /** The unique ID of the index */\n  readonly id: string;\n  /** The name of the index. */\n  name: string;\n  /** (optional) A human readable description for the index. */\n  description?: string;\n  /** The index configuration, including the dimension size and distance metric. */\n  config: VectorizeIndexConfig;\n  /** The number of records containing vectors within the index. */\n  vectorsCount: number;\n}\n\n/**\n * Metadata about an existing index.\n */\ninterface VectorizeIndexInfo {\n  /** The number of records containing vectors within the index. */\n  vectorCount: number;\n  /** Number of dimensions the index has been configured for. */\n  dimensions: number;\n  /** ISO 8601 datetime of the last processed mutation on in the index. All changes before this mutation will be reflected in the index state. */\n  processedUpToDatetime: number;\n  /** UUIDv4 of the last mutation processed by the index. All changes before this mutation will be reflected in the index state. */\n  processedUpToMutation: number;\n}\n\n/**\n * Represents a single vector value set along with its associated metadata.\n */\ninterface VectorizeVector {\n  /** The ID for the vector. This can be user-defined, and must be unique. It should uniquely identify the object, and is best set based on the ID of what the vector represents. */\n  id: string;\n  /** The vector values */\n  values: VectorFloatArray | number[];\n  /** The namespace this vector belongs to. */\n  namespace?: string;\n  /** Metadata associated with the vector. Includes the values of other fields and potentially additional details. */\n  metadata?: Record<string, VectorizeVectorMetadata>;\n}\n\n/**\n * Represents a matched vector for a query along with its score and (if specified) the matching vector information.\n */\ntype VectorizeMatch = Pick<Partial<VectorizeVector>, 'values'> &\n  Omit<VectorizeVector, 'values'> & {\n    /** The score or rank for similarity, when returned as a result */\n    score: number;\n  };\n\n/**\n * A set of matching {@link VectorizeMatch} for a particular query.\n */\ninterface VectorizeMatches {\n  matches: VectorizeMatch[];\n  count: number;\n}\n\n/**\n * Results of an operation that performed a mutation on a set of vectors.\n * Here, `ids` is a list of vectors that were successfully processed.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link VectorizeAsyncMutation} for its post-beta equivalent.\n */\ninterface VectorizeVectorMutation {\n  /* List of ids of vectors that were successfully processed. */\n  ids: string[];\n  /* Total count of the number of processed vectors. */\n  count: number;\n}\n\n/**\n * Result type indicating a mutation on the Vectorize Index.\n * Actual mutations are processed async where the `mutationId` is the unique identifier for the operation.\n */\ninterface VectorizeAsyncMutation {\n  /** The unique identifier for the async mutation operation containing the changeset. */\n  mutationId: string;\n}\n\n/**\n * A Vectorize Vector Search Index for querying vectors/embeddings.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link Vectorize} for its new implementation.\n */\ndeclare abstract class VectorizeIndex {\n  /**\n   * Get information about the currently bound index.\n   * @returns A promise that resolves with information about the current index.\n   */\n  describe(): Promise<VectorizeIndexDetails>;\n  /**\n   * Use the provided vector to perform a similarity search across the index.\n   * @param vector Input vector that will be used to drive the similarity search.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  query(\n    vector: VectorFloatArray | number[],\n    options?: VectorizeQueryOptions\n  ): Promise<VectorizeMatches>;\n  /**\n   * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown.\n   * @param vectors List of vectors that will be inserted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed.\n   */\n  insert(vectors: VectorizeVector[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values.\n   * @param vectors List of vectors that will be upserted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed.\n   */\n  upsert(vectors: VectorizeVector[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Delete a list of vectors with a matching id.\n   * @param ids List of vector ids that should be deleted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed (and thus deleted).\n   */\n  deleteByIds(ids: string[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Get a list of vectors with a matching id.\n   * @param ids List of vector ids that should be returned.\n   * @returns A promise that resolves with the raw unscored vectors matching the id set.\n   */\n  getByIds(ids: string[]): Promise<VectorizeVector[]>;\n}\n\n/**\n * A Vectorize Vector Search Index for querying vectors/embeddings.\n *\n * Mutations in this version are async, returning a mutation id.\n */\ndeclare abstract class Vectorize {\n  /**\n   * Get information about the currently bound index.\n   * @returns A promise that resolves with information about the current index.\n   */\n  describe(): Promise<VectorizeIndexInfo>;\n  /**\n   * Use the provided vector to perform a similarity search across the index.\n   * @param vector Input vector that will be used to drive the similarity search.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  query(\n    vector: VectorFloatArray | number[],\n    options?: VectorizeQueryOptions\n  ): Promise<VectorizeMatches>;\n  /**\n   * Use the provided vector-id to perform a similarity search across the index.\n   * @param vectorId Id for a vector in the index against which the index should be queried.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  queryById(\n    vectorId: string,\n    options?: VectorizeQueryOptions\n  ): Promise<VectorizeMatches>;\n  /**\n   * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown.\n   * @param vectors List of vectors that will be inserted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the insert changeset.\n   */\n  insert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values.\n   * @param vectors List of vectors that will be upserted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the upsert changeset.\n   */\n  upsert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Delete a list of vectors with a matching id.\n   * @param ids List of vector ids that should be deleted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the delete changeset.\n   */\n  deleteByIds(ids: string[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Get a list of vectors with a matching id.\n   * @param ids List of vector ids that should be returned.\n   * @returns A promise that resolves with the raw unscored vectors matching the id set.\n   */\n  getByIds(ids: string[]): Promise<VectorizeVector[]>;\n}\n\n/*****************************\n *\n * !!! WARNING !!!\n * Changes should be made in `types/defines/vectorize.d.ts`\n * and then synced back here.\n *\n * This files was is copy & pasted from the types/ folder 2 years ago\n * because when bazel runs it doesn't have access to that directly (and thusly is sad).\n * TODO: come up with a better system for this.\n *\n ******************************/\n"
  },
  {
    "path": "src/cloudflare/internal/workers.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport class DurableObject {\n  constructor(ctx: unknown, env: unknown);\n\n  ctx: unknown;\n  env: unknown;\n}\n\nexport class WorkerEntrypoint {\n  constructor(ctx: unknown, env: unknown);\n\n  ctx: unknown;\n  env: unknown;\n}\n\nexport class WorkflowEntrypoint {\n  constructor(ctx: unknown, env: unknown);\n\n  ctx: unknown;\n  env: unknown;\n}\n\nexport class RpcStub {\n  constructor(server: object);\n}\n\nexport class RpcPromise {}\n\nexport class RpcProperty {}\n\nexport class RpcTarget {}\n\nexport class ServiceStub {}\n\nexport function waitUntil(promise: Promise<unknown>): void;\n"
  },
  {
    "path": "src/cloudflare/internal/workflows-api.ts",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport class NonRetryableError extends Error {\n  constructor(message: string, name = 'NonRetryableError') {\n    super(message);\n    this.name = name;\n  }\n}\n\ninterface Fetcher {\n  fetch: typeof fetch;\n}\n\nasync function callFetcher<T>(\n  fetcher: Fetcher,\n  path: string,\n  body: object\n): Promise<T> {\n  const res = await fetcher.fetch(`http://workflow-binding.local${path}`, {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/json',\n      'X-Version': '1',\n    },\n    body: JSON.stringify(body),\n  });\n\n  const response = (await res.json()) as {\n    result: T;\n    error?: WorkflowError;\n  };\n\n  if (res.ok) {\n    return response.result;\n  } else {\n    throw new Error(response.error?.message);\n  }\n}\n\nclass InstanceImpl implements WorkflowInstance {\n  // TODO(soon): Can we use the # syntax here?\n  // eslint-disable-next-line no-restricted-syntax\n  private readonly fetcher: Fetcher;\n  readonly id: string;\n\n  constructor(id: string, fetcher: Fetcher) {\n    this.id = id;\n    this.fetcher = fetcher;\n  }\n\n  async pause(): Promise<void> {\n    await callFetcher(this.fetcher, '/pause', {\n      id: this.id,\n    });\n  }\n  async resume(): Promise<void> {\n    await callFetcher(this.fetcher, '/resume', {\n      id: this.id,\n    });\n  }\n\n  async terminate(): Promise<void> {\n    await callFetcher(this.fetcher, '/terminate', {\n      id: this.id,\n    });\n  }\n\n  async restart(): Promise<void> {\n    await callFetcher(this.fetcher, '/restart', {\n      id: this.id,\n    });\n  }\n\n  async status(): Promise<InstanceStatus> {\n    const result = await callFetcher<InstanceStatus>(this.fetcher, '/status', {\n      id: this.id,\n    });\n    return result;\n  }\n\n  async sendEvent({\n    type,\n    payload,\n  }: {\n    type: string;\n    payload: unknown;\n  }): Promise<void> {\n    await callFetcher(this.fetcher, '/send-event', {\n      type,\n      payload,\n      id: this.id,\n    });\n  }\n}\n\nclass WorkflowImpl {\n  // TODO(soon): Can we use the # syntax here?\n  // eslint-disable-next-line no-restricted-syntax\n  private readonly fetcher: Fetcher;\n\n  constructor(fetcher: Fetcher) {\n    this.fetcher = fetcher;\n  }\n\n  async get(id: string): Promise<WorkflowInstance> {\n    const result = await callFetcher<{\n      id: string;\n    }>(this.fetcher, '/get', { id });\n\n    return new InstanceImpl(result.id, this.fetcher);\n  }\n\n  async create(\n    options?: WorkflowInstanceCreateOptions\n  ): Promise<WorkflowInstance> {\n    const result = await callFetcher<{\n      id: string;\n    }>(this.fetcher, '/create', options ?? {});\n\n    return new InstanceImpl(result.id, this.fetcher);\n  }\n\n  async createBatch(\n    options: WorkflowInstanceCreateOptions[]\n  ): Promise<WorkflowInstance[]> {\n    const results = await callFetcher<\n      {\n        id: string;\n      }[]\n    >(this.fetcher, '/createBatch', options);\n\n    return results.map((result) => new InstanceImpl(result.id, this.fetcher));\n  }\n}\n\nexport function makeBinding(env: { fetcher: Fetcher }): Workflow {\n  return new WorkflowImpl(env.fetcher);\n}\n\nexport default makeBinding;\n"
  },
  {
    "path": "src/cloudflare/internal/workflows.d.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n/*****************************\n *\n * NOTE: this is copy & pasted from the types/ folder, as when bazel\n * runs it doesn't have access to that directly and thusly is sad.\n * TODO: come up with a better system for this.\n *\n ****************************** /\n\n/**\n * NonRetryableError allows for a user to throw a fatal error\n * that makes a Workflow instance fail immediately without triggering a retry\n */\ndeclare abstract class NonRetryableError extends Error {\n  /**\n   * `__brand` is used to differentiate between `NonRetryableError` and `Error`\n   * and is omitted from the constructor because users should not set it\n   */\n  constructor(message: string, name?: string);\n}\n\ndeclare abstract class Workflow<PARAMS = unknown> {\n  /**\n   * Get a handle to an existing instance of the Workflow.\n   * @param id Id for the instance of this Workflow\n   * @returns A promise that resolves with a handle for the Instance\n   */\n  get(id: string): Promise<WorkflowInstance>;\n\n  /**\n   * Create a new instance and return a handle to it. If a provided id exists, an error will be thrown.\n   * @param options Options when creating an instance including name and params\n   * @returns A promise that resolves with a handle for the Instance\n   */\n  create(\n    options?: WorkflowInstanceCreateOptions<PARAMS>\n  ): Promise<WorkflowInstance>;\n\n  /**\n   * Create a batch of instances and return handle for all of them. If a provided id exists, an error will be thrown.\n   * `createBatch` is limited at 100 instances at a time or when the RPC limit (1MiB) is reached.\n   * @param batch List of Options when creating an instance including name and params\n   * @returns A promise that resolves with a list of handles for the created instances.\n   */\n  createBatch(\n    batch: WorkflowInstanceCreateOptions<PARAMS>[]\n  ): Promise<WorkflowInstance[]>;\n}\n\ntype WorkflowDurationLabel =\n  | 'second'\n  | 'minute'\n  | 'hour'\n  | 'day'\n  | 'week'\n  | 'month'\n  | 'year';\n\ntype WorkflowSleepDuration =\n  | `${number} ${WorkflowDurationLabel}${'s' | ''}`\n  | number;\n\ntype WorkflowRetentionDuration = WorkflowSleepDuration;\n\ninterface WorkflowInstanceCreateOptions<PARAMS = unknown> {\n  /**\n   * An id for your Workflow instance. Must be unique within the Workflow.\n   * This is automatically generated if not passed in.\n   */\n  id?: string;\n  /**\n   * The event payload the Workflow instance is triggered with\n   */\n  params?: PARAMS;\n  /**\n   * The retention policy for the Workflow instance.\n   * Defaults to the maximum retention period available for the owner's account.\n   */\n  retention?: {\n    successRetention?: WorkflowRetentionDuration;\n    errorRetention?: WorkflowRetentionDuration;\n  };\n}\n\ntype InstanceStatus = {\n  status:\n    | 'queued' // means that instance is waiting to be started (see concurrency limits)\n    | 'running'\n    | 'paused'\n    | 'errored'\n    | 'terminated' // user terminated the instance while it was running\n    | 'complete'\n    | 'waiting' // instance is hibernating and waiting for sleep or event to finish\n    | 'waitingForPause' // instance is finishing the current work to pause\n    | 'unknown';\n  error?: {\n    name: string;\n    message: string;\n  };\n  output?: unknown;\n};\n\ninterface WorkflowError {\n  code?: number;\n  message: string;\n}\n\ndeclare abstract class WorkflowInstance {\n  id: string;\n\n  /**\n   * Pause the instance.\n   */\n  pause(): Promise<void>;\n\n  /**\n   * Resume the instance. If it is already running, an error will be thrown.\n   */\n  resume(): Promise<void>;\n\n  /**\n   * Terminate the instance. If it is errored, terminated or complete, an error will be thrown.\n   */\n  terminate(): Promise<void>;\n\n  /**\n   * Restart the instance.\n   */\n  restart(): Promise<void>;\n\n  /**\n   * Returns the current status of the instance.\n   */\n  status(): Promise<InstanceStatus>;\n\n  /**\n   * Send an event to this instance.\n   */\n  sendEvent({\n    type,\n    payload,\n  }: {\n    type: string;\n    payload: unknown;\n  }): Promise<void>;\n}\n"
  },
  {
    "path": "src/cloudflare/node.ts",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport {\n  portMapper,\n  type FetchHandler as Fetcher,\n} from 'cloudflare-internal:http';\n\ninterface ServerDescriptor {\n  port?: number | null | undefined;\n}\n\ninterface NodeStyleServer {\n  listen(...args: unknown[]): this;\n  address(): { port?: number | null | undefined };\n}\n\nfunction validatePort(port: unknown): number {\n  if (\n    !Number.isFinite(port) ||\n    (port as number) < 0 ||\n    (port as number) > 65535\n  ) {\n    throw new Error('Failed to determine port for server');\n  }\n  return port as number;\n}\n\nexport async function handleAsNodeRequest(\n  desc: number | ServerDescriptor,\n  request: Request,\n  env?: unknown,\n  ctx?: unknown\n): Promise<Response> {\n  if (typeof desc === 'number') {\n    desc = { port: desc };\n  }\n  // While TypeScript will complain if `desc` is null or undefined,\n  // JavaScript does not enforce this, so we need to check at runtime.\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  const port = validatePort(desc?.port);\n  const instance = portMapper.get(port);\n  if (!instance) {\n    const error = new Error(\n      `Http server with port ${port} not found. This is likely a bug with your code. ` +\n        `You should check if server.listen() was called with the same port (${port})`\n    );\n    // @ts-expect-error TS2339 We're imitating Node.js errors.\n    error.code = 'ERR_INVALID_ARG_VALUE';\n    throw error;\n  }\n  return await instance.fetch(request, env, ctx);\n}\n\nexport function httpServerHandler(\n  desc: number | ServerDescriptor | NodeStyleServer\n): Fetcher {\n  if (typeof desc === 'number') {\n    desc = { port: desc };\n  }\n  // While the TypeScript type system prevents `desc` from being null or undefined,\n  // JavaScript does not, so we need to check for it at runtime.\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  if (desc == null) {\n    throw new Error('Server descriptor cannot be null or undefined');\n  }\n\n  // The desc can be a ServerDescriptor or a Server (where \"Server\" is defined\n  // as a type that has a \"listen()\" method and an \"address()\" method).\n  let port: number | null = null;\n\n  // If there is no port defined and desc has a listen method, we will try to\n  // access it as a Server, calling listen() if necessary to determine the port.\n  if (\n    (desc as ServerDescriptor).port == null &&\n    typeof (desc as NodeStyleServer).listen === 'function'\n  ) {\n    const server = desc as NodeStyleServer;\n    // First, let's see if the server-like thing already has a port.\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    let serverPort = server.address()?.port;\n    if (typeof serverPort === 'number') {\n      // Nice, we're already bound to a port.\n      port = serverPort;\n    } else {\n      // We're not yet bound to a port. Try calling listen() to start the server\n      // and determine the port.\n      server.listen();\n      // Did it work? We do expect the listen in this case to synchronously\n      // assign the port so we can check it immediately.\n      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n      serverPort = server.address()?.port;\n      if (typeof serverPort === 'number') {\n        // Nice. We've got a port now.\n        port = serverPort;\n      }\n    }\n  } else if (typeof (desc as ServerDescriptor).port === 'number') {\n    // We're a ServerDescriptor with a port defined.\n    port = (desc as ServerDescriptor).port as number;\n  }\n\n  port = validatePort(port);\n\n  return {\n    async fetch(req: Request, env?: unknown, ctx?: unknown): Promise<Response> {\n      return await handleAsNodeRequest(\n        { port: validatePort(port) },\n        req,\n        env,\n        ctx\n      );\n    },\n  };\n}\n"
  },
  {
    "path": "src/cloudflare/pipelines.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { PipelineTransformImpl } from 'cloudflare-internal:pipeline-transform';\n\nexport const PipelineTransformationEntrypoint = PipelineTransformImpl;\n"
  },
  {
    "path": "src/cloudflare/sockets.ts",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// TODO: c++ built-ins do not yet support named exports\n\nimport sockets from 'cloudflare-internal:sockets';\nimport { type ServiceStub } from 'cloudflare-internal:workers';\n\nexport function connect(\n  address: string | sockets.SocketAddress,\n  options: sockets.SocketOptions\n): sockets.Socket {\n  return sockets.connect(address, options);\n}\n\nexport function internalNewHttpClient(\n  socket: sockets.Socket\n): Promise<ServiceStub> {\n  return sockets.internalNewHttpClient(socket);\n}\n"
  },
  {
    "path": "src/cloudflare/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tools/base.tsconfig.json\",\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"paths\": {\n      \"cloudflare:*\": [\"./*\"],\n      \"cloudflare-internal:*\": [\"./internal/*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "src/cloudflare/vectorize.ts",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n/**\n * A pre-configured list of known models.\n * These can be supplied in place of configuring explicit dimensions.\n */\nexport const KnownModel = {\n  'openai/text-embedding-ada-002': 'openai/text-embedding-ada-002',\n  'cohere/embed-multilingual-v2.0': 'cohere/embed-multilingual-v2.0',\n  '@cf/baai/bge-small-en-v1.5': '@cf/baai/bge-small-en-v1.5',\n  '@cf/baai/bge-base-en-v1.5': '@cf/baai/bge-base-en-v1.5',\n  '@cf/baai/bge-large-en-v1.5': '@cf/baai/bge-large-en-v1.5',\n} as const;\n\n/**\n * Supported distance metrics for an index.\n * Distance metrics determine how other \"similar\" vectors are determined.\n */\nexport const DistanceMetric = {\n  EUCLIDEAN: 'euclidean',\n  COSINE: 'cosine',\n  DOT_PRODUCT: 'dot-product',\n} as const;\n\n/**\n * Supported metadata return levels for a Vectorize query.\n */\nexport const MetadataRetrievalLevel = {\n  /**\n   * Full metadata for the vector return set, including all fields (including those un-indexed) without truncation.\n   *\n   * This is a more expensive retrieval, as it requires additional fetching & reading of un-indexed data.\n   */\n  ALL: 'all',\n  /**\n   * Return all metadata fields configured for indexing in the vector return set.\n   *\n   * This level of retrieval is \"free\" in that no additional overhead is incurred returning this data.\n   * However, note that indexed metadata is subject to truncation (especially for larger strings).\n   */\n  INDEXED: 'indexed',\n  /** No indexed metadata will be returned. */\n  NONE: 'none',\n} as const;\n"
  },
  {
    "path": "src/cloudflare/workers.ts",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// TODO(cleanup): C++ built-in modules do not yet support named exports, so we must define this\n//   wrapper module that simply re-exports the classes from the built-in module.\n\nimport entrypoints from 'cloudflare-internal:workers';\nimport innerEnv from 'cloudflare-internal:env';\n\nexport const WorkerEntrypoint = entrypoints.WorkerEntrypoint;\nexport const DurableObject = entrypoints.DurableObject;\nexport const RpcStub = entrypoints.RpcStub;\nexport const RpcPromise = entrypoints.RpcPromise;\nexport const RpcProperty = entrypoints.RpcProperty;\nexport const RpcTarget = entrypoints.RpcTarget;\nexport const ServiceStub = entrypoints.ServiceStub;\nexport const WorkflowEntrypoint = entrypoints.WorkflowEntrypoint;\n\nexport function withEnv(newEnv: unknown, fn: () => unknown): unknown {\n  return innerEnv.withEnv(newEnv, fn);\n}\n\nexport function withExports(newExports: unknown, fn: () => unknown): unknown {\n  return innerEnv.withExports(newExports, fn);\n}\n\nexport function withEnvAndExports(\n  newEnv: unknown,\n  newExports: unknown,\n  fn: () => unknown\n): unknown {\n  return innerEnv.withEnvAndExports(newEnv, newExports, fn);\n}\n\n// A proxy for the workers env/bindings. Since env is imported as a module-level\n// reference, the object identity cannot be changed. The proxy provides indirection,\n// delegating to different underlying env objects based on async context (see withEnv()).\n// Mutations via this proxy modify the current underlying env object in-place - if you're\n// inside a withEnv() scope, mutations affect the override object, not the base environment.\nexport const env = new Proxy(\n  {},\n  {\n    get(_: unknown, prop: string | symbol): unknown {\n      const inner = innerEnv.getCurrentEnv();\n      if (inner) {\n        return Reflect.get(inner, prop);\n      }\n      return undefined;\n    },\n\n    set(_: unknown, prop: string | symbol, newValue: unknown): boolean {\n      const inner = innerEnv.getCurrentEnv();\n      if (inner) {\n        return Reflect.set(inner, prop, newValue);\n      }\n      return true;\n    },\n\n    has(_: unknown, prop: string | symbol): boolean {\n      const inner = innerEnv.getCurrentEnv();\n      if (inner) {\n        return Reflect.has(inner, prop);\n      }\n      return false;\n    },\n\n    ownKeys(_: unknown): ArrayLike<string | symbol> {\n      const inner = innerEnv.getCurrentEnv();\n      if (inner) {\n        return Reflect.ownKeys(inner);\n      }\n      return [];\n    },\n\n    deleteProperty(_: unknown, prop: string | symbol): boolean {\n      const inner = innerEnv.getCurrentEnv();\n      if (inner) {\n        return Reflect.deleteProperty(inner, prop);\n      }\n      return true;\n    },\n\n    defineProperty(\n      _: unknown,\n      prop: string | symbol,\n      attr: PropertyDescriptor\n    ): boolean {\n      const inner = innerEnv.getCurrentEnv();\n      if (inner) {\n        return Reflect.defineProperty(inner, prop, attr);\n      }\n      return true;\n    },\n\n    getOwnPropertyDescriptor(\n      _: unknown,\n      prop: string | symbol\n    ): PropertyDescriptor | undefined {\n      const inner = innerEnv.getCurrentEnv();\n      if (inner) {\n        return Reflect.getOwnPropertyDescriptor(inner, prop);\n      }\n      return undefined;\n    },\n  }\n);\n\n// A proxy for the worker exports. Since exports is imported as a module-level\n// reference, the object identity cannot be changed. The proxy provides indirection,\n// delegating to different underlying exports objects based on async context (see\n// withExports()). This proxy is read-only - mutations are not supported.\nexport const exports = new Proxy(\n  {},\n  {\n    get(_: unknown, prop: string | symbol): unknown {\n      const inner = innerEnv.getCurrentExports();\n      if (inner) {\n        return Reflect.get(inner, prop);\n      }\n      return undefined;\n    },\n\n    has(_: unknown, prop: string | symbol): boolean {\n      const inner = innerEnv.getCurrentExports();\n      if (inner) {\n        return Reflect.has(inner, prop);\n      }\n      return false;\n    },\n\n    ownKeys(_: unknown): ArrayLike<string | symbol> {\n      const inner = innerEnv.getCurrentExports();\n      if (inner) {\n        return Reflect.ownKeys(inner);\n      }\n      return [];\n    },\n\n    getOwnPropertyDescriptor(\n      _: unknown,\n      prop: string | symbol\n    ): PropertyDescriptor | undefined {\n      const inner = innerEnv.getCurrentExports();\n      if (inner) {\n        return Reflect.getOwnPropertyDescriptor(inner, prop);\n      }\n      return undefined;\n    },\n  }\n);\n\nexport const waitUntil = entrypoints.waitUntil.bind(entrypoints);\n"
  },
  {
    "path": "src/cloudflare/workflows.ts",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport { NonRetryableError } from 'cloudflare-internal:workflows-api';\n"
  },
  {
    "path": "src/node/AGENTS.md",
    "content": "# src/node/ — Node.js Compatibility (TypeScript)\n\n## OVERVIEW\n\nTypeScript and JavaScript layer implementing Node.js compatible built-in modules for Workers.\n\nIt is split across multiple layers:\n\n1. An **internal layer** consisting of:\n  - Non-user-importable TypeScript and JavaScript files in `internal/` that implement core logic, utilities, and C++ JSG module declarations.\n  - C++ JSG modules  (`src/workerd/api/node/`) expose native ops via `node-internal:*` specifiers\n  - Some native internal modules may be implemented in Rust\n2. A **public layer** of TypeScript files at the top-level that are user-importable.\n\nIt is common, but not required, for top-level `.ts` files to re-export from `internal/` via `node-internal:` specifiers. This allows for a clean separation between public API surface and internal implementation details.\n\nSee `README.md` for 12 policy rules governing compat scope and philosophy.\n\n## MODULE GATING\n\n`node:*` modules are gated behind the `nodejs_compat` compatibility flag.\n\nThe `node:async_hooks` module can be enabled individually via the `nodejs_als` compatibility flag.\n\nRuntime compat flags checked via `Cloudflare.compatibilityFlags['flag_name']`:\n\n| Flag                                   | Guards                                                                |\n| -------------------------------------- | --------------------------------------------------------------------- |\n| `enable_nodejs_http_server_modules`    | http Server/createServer, https Server                                |\n| `nodejs_zlib`                          | zlib streaming classes (Deflate, Gzip, etc.)                          |\n| `enable_nodejs_process_v2`             | Extended process/events functionality                                 |\n| `remove_nodejs_compat_eol_v22/v23/v24` | EOL deprecation of specific API surfaces (crypto, util, tls, process) |\n\nMost modules require `nodejs_compat` + `nodejs_compat_v2` flags (enforced by C++ side, not visible here).\n\n## CONVENTIONS\n\n- **`internal/*.js`** (not `.ts`) = upstream Node.js ports (streams_readable.js, streams_writable.js, etc.); paired with `.d.ts` type declarations\n- **`internal/*.d.ts`** without matching `.ts` = declares C++ JSG module shape (crypto.d.ts → `node-internal:crypto`)\n- **`internal/*.d.ts`** with matching `.js` = types for ported JS (streams_readable.d.ts → streams_readable.js)\n- Top-level files are thin: import from `node-internal:*`, re-export with `export { ... }` / `export * from`\n- Feature-gated exports use `if (!flag) { throw ... }` or conditional class assignment patterns\n- Shared validators in `internal/validators.ts`; shared errors in `internal/internal_errors.ts`\n- `_` prefix files (e.g., `_http_agent.ts`, `_stream_readable.ts`) = Node.js legacy internal module aliases\n- Some Node.js compat APIs are non-functional stubs that are either non-ops or throw when called\n"
  },
  {
    "path": "src/node/BUILD.bazel",
    "content": "load(\"@workerd//:build/wd_ts_bundle.bzl\", \"wd_ts_bundle\")\n\nwd_ts_bundle(\n    name = \"node\",\n    eslintrc_json = \"eslint.config.mjs\",\n    gen_compile_cache = True,\n    import_name = \"node\",\n    internal_modules = glob([\n        \"internal/*.ts\",\n        \"internal/*.js\",\n    ]),\n    modules = glob([\n        \"*.ts\",\n        \"assert/*.ts\",\n        \"dns/*.ts\",\n        \"fs/*.ts\",\n        \"inspector/*.ts\",\n        \"path/*.ts\",\n        \"readline/*.ts\",\n        \"stream/*.js\",\n        \"timers/*.ts\",\n        \"util/*.ts\",\n    ]),\n    schema_id = \"0xbcc8f57c63814005\",\n    tsconfig_json = \"tsconfig.json\",\n    deps = [\"//:node_modules/@types/node\"],\n)\n"
  },
  {
    "path": "src/node/README.md",
    "content": "# Node.js Compatibility\n\n1. Node.js compatibility is best-effort. We intend to implement a subset of the Node.js API as closely as possible but we do not intend on being generally 100% compatible with Node.js as a whole.\n\n1. For Web Platform Standard APIs, the standard spec is the source of truth. If Node.js implements those APIs differently, we will implement those differences in nodejs_compat mode ONLY if it can be done so in a backwards-compatible way or ONLY with an explicitly opt-in compatibility flag.\n\n1. For Node.js APIs, Node.js is the source of truth. We will attempt, to the best of our ability within the constraints of our runtime, to implement the API as close to Node.js' defined behavior as possible. However, exceptions will be made when those cannot be implemented without breaking backwards compatibility. When Workers Runtime constraints, or this framework, make it impossible to implement a Node.js API in a manner that exactly matches Node.js, a best-effort attempt will be made to get as close as possible and any differences will be documented.\n\n1. When a Node.js API overlaps (even partially) in functionality with a Workers runtime or Web Platform Standard API, there is no requirement that the Node.js API implementation conform to, or even be consistent with, the standard or runtime API as long as it does not conflict with those or force backwards compatibility issues. The existence of a Web Platform or Workers specific API will not rule out implementing the Node.js API even if functionally equivalent.\n\n1. Just as we do with Web Platform APIs, we may choose not to implement some Node.js APIs simply because we decide they are not a good fit or experience for the Workers runtime environment (or, in some cases because doing so is simply not possible because of runtime constraints).\n\n1. When implementing a Node.js API adds significant runtime, or development time, overhead, partial implementations can be merged and evolved incrementally, taking care to avoid breaking changes and using compatibility flags where appropriate. The emphasis will be on making smaller incremental improvements over time.\n\n1. Enabling the nodejs_compat flag does not mean \"all APIs work the way they do in Node.js\" ... it just means that the Node.js APIs are available. When there is conflict between a Node.js API and a Web Platform API, or Workers-specific API, that is already implemented in the runtime, the Web Platform API / Workers API will take precedence. We may still implement the Node.js alternative behavior behind an explicitly opt-in compat flag.\n\n1. For the global scope, Node.js APIs and behaviors are second to web platform APIs and other existing runtime globals. We will implement the Node.js specific globals only if they do not break backwards compatibility. We may implement such changes behind an explicitly opt-in compat flag.\n\n1. We will seek to prioritize implementation of Node.js APIs that are needed by npm modules and other ecosystem dependencies that our customers want and are trying to use. When those modules require Node.js core APIs, we will implement those in accordance with this framework. In some cases that may mean it will not be possible to support the module as written. Like the Node.js APIs themselves, support will be best-effort. When we cannot fully implement support for these modules directly in the runtime, we will attempt to work with the maintainers of those modules to implement a workers-compatible alternative or work to develop alternative workarounds.\n\n1. Polyfills of Node.js APIs (that is, external implementations that are not bundled directly into the workers runtime) may be leveraged by tooling as a last-resort alternative to patch over parts of the Node.js API we choose not to implement in the runtime. When used, these must be generally available for any Workers user, not only those using wrangler. The built-in implementation of Node.js APIs should always take precedence in general but individual workers should be able to BYOI (\"bring your own implementation\") within the reasonable constraints of the runtime. Cloudflare tooling should avoid polyfilling an API that already exists within the runtime, and the existence of a polyfill implementation will not rule out implementing the API directly in the runtime. At the same time, the existence of a polyfill implementation does not guarantee that the API will be implemented in the runtime and the existence of the runtime API does not guarantee that a polyfill will not be implemented. It is also not guaranteed that the runtime implementation of the API will match exactly the implementation of the polyfill. When new APIs are added to the runtime, the polyfill should be removed if it is no longer needed but the runtime will not itself take steps to remove the polyfill from the tooling.\n\n1. Experimental APIs only recently added to Node.js should not be implemented immediately in the workers runtime. Such APIs may be unstable for some time and could cause long term backwards compatibility issues or other unfortunate complexities in the workers runtime due to our \"No Breaking Changes Without Compatibility Flags\" policy. It is better to allow these APIs to sit and mature a bit in the Node.js runtime to ensure they are stable before being implemented in the workers runtime. Exceptions can be made to address immediate priority use cases.\n\n1. When a decision is made to explicit *not* implement a particular Node.js API, that decision should be documented. Attempts to use such APIs should result in a runtime error rather than silent failure or silently ignoring.\n"
  },
  {
    "path": "src/node/_http_agent.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport { Agent, globalAgent } from 'node-internal:internal_http_agent';\n\nexport { Agent, globalAgent };\nexport default {\n  Agent,\n  globalAgent,\n};\n"
  },
  {
    "path": "src/node/_http_client.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport { ClientRequest } from 'node-internal:internal_http_client';\n\nexport { ClientRequest };\nexport default { ClientRequest };\n"
  },
  {
    "path": "src/node/_http_common.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport {\n  _checkIsHttpToken,\n  _checkInvalidHeaderChar,\n  chunkExpression,\n} from 'node-internal:internal_http';\nimport { METHODS } from 'node-internal:internal_http_constants';\nimport { kIncomingMessage } from 'node-internal:internal_http_util';\nexport {\n  _checkIsHttpToken,\n  _checkInvalidHeaderChar,\n  chunkExpression,\n  kIncomingMessage,\n};\nexport const continueExpression = /(?:^|\\W)100-continue(?:$|\\W)/i;\nexport const methods = METHODS;\n\nexport default {\n  _checkIsHttpToken,\n  _checkInvalidHeaderChar,\n  chunkExpression,\n  continueExpression,\n  methods,\n  kIncomingMessage,\n};\n"
  },
  {
    "path": "src/node/_http_incoming.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport { IncomingMessage } from 'node-internal:internal_http_incoming';\n\nexport { IncomingMessage };\nexport default {\n  IncomingMessage,\n};\n"
  },
  {
    "path": "src/node/_http_outgoing.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport {\n  validateHeaderName,\n  validateHeaderValue,\n} from 'node-internal:internal_http';\nimport {\n  kUniqueHeaders,\n  kHighWaterMark,\n  parseUniqueHeadersOption,\n  OutgoingMessage,\n} from 'node-internal:internal_http_outgoing';\n\nexport {\n  validateHeaderName,\n  validateHeaderValue,\n  kUniqueHeaders,\n  kHighWaterMark,\n  parseUniqueHeadersOption,\n  OutgoingMessage,\n};\nexport default {\n  validateHeaderName,\n  validateHeaderValue,\n  kUniqueHeaders,\n  kHighWaterMark,\n  parseUniqueHeadersOption,\n  OutgoingMessage,\n};\n"
  },
  {
    "path": "src/node/_http_server.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport {\n  Server,\n  ServerResponse,\n  setupConnectionsTracking,\n  storeHTTPOptions,\n  _connectionListener,\n  httpServerPreClose,\n  kConnectionsCheckingInterval,\n} from 'node-internal:internal_http_server';\nimport { kServerResponse } from 'node-internal:internal_http_util';\nimport { STATUS_CODES } from 'node-internal:internal_http_constants';\n\nexport { STATUS_CODES, kServerResponse };\nexport * from 'node-internal:internal_http_server';\n\nexport default {\n  STATUS_CODES,\n  Server,\n  ServerResponse,\n  setupConnectionsTracking,\n  storeHTTPOptions,\n  _connectionListener,\n  kServerResponse,\n  httpServerPreClose,\n  kConnectionsCheckingInterval,\n};\n"
  },
  {
    "path": "src/node/_stream_duplex.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n\nimport { Duplex, from, fromWeb, toWeb } from 'node-internal:streams_duplex';\n\nexport { Duplex, from, fromWeb, toWeb };\nexport default Duplex;\n"
  },
  {
    "path": "src/node/_stream_passthrough.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\nimport { PassThrough } from 'node-internal:streams_transform';\nexport { PassThrough };\nexport default PassThrough;\n"
  },
  {
    "path": "src/node/_stream_readable.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n\nimport {\n  Readable,\n  ReadableState,\n  toWeb,\n  fromWeb,\n  from,\n  wrap,\n} from 'node-internal:streams_readable';\n\nexport { Readable, ReadableState, toWeb, fromWeb, from, wrap };\nexport default Readable;\n"
  },
  {
    "path": "src/node/_stream_transform.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\nimport { Transform } from 'node-internal:streams_transform';\nexport { Transform };\nexport default Transform;\n"
  },
  {
    "path": "src/node/_stream_wrap.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\nimport { JSStreamSocket } from 'node-internal:internal_tls_jsstream';\n\nexport default JSStreamSocket;\n"
  },
  {
    "path": "src/node/_stream_writable.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\nimport {\n  Writable,\n  WritableState,\n  fromWeb,\n  toWeb,\n} from 'node-internal:streams_writable';\n\nexport { Writable, WritableState, fromWeb, toWeb };\nexport default Writable;\n"
  },
  {
    "path": "src/node/_tls_common.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport {\n  createSecureContext,\n  SecureContext,\n  translatePeerCertificate,\n} from 'node-internal:internal_tls_common';\n\nexport { createSecureContext, SecureContext, translatePeerCertificate };\nexport default { createSecureContext, SecureContext, translatePeerCertificate };\n"
  },
  {
    "path": "src/node/_tls_wrap.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { connect, TLSSocket } from 'node-internal:internal_tls_wrap';\nexport { connect, TLSSocket };\nexport default {\n  connect,\n  TLSSocket,\n};\n"
  },
  {
    "path": "src/node/assert/strict.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport * from 'node-internal:internal_assert';\nimport { default as assert } from 'node-internal:internal_assert';\nexport default assert;\n"
  },
  {
    "path": "src/node/assert.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport * from 'node-internal:internal_assert';\nimport { default as assert } from 'node-internal:internal_assert';\nexport default assert;\n"
  },
  {
    "path": "src/node/async_hooks.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n\nimport { default as async_hooks } from 'node-internal:async_hooks';\n\nclass AsyncHook {\n  enable(): this {\n    return this;\n  }\n\n  disable(): this {\n    return this;\n  }\n}\n\nexport const { AsyncLocalStorage, AsyncResource } = async_hooks;\n\n// We don't add all the async wrap providers since we don't use them\n// and will not expose any APIs that use them.\nexport const asyncWrapProviders: Record<string, number> = {\n  NONE: 0,\n};\n\nexport function createHook(): AsyncHook {\n  // Even though we don't implement this function, we return a default value\n  // in order to preserve backward compatibility and avoid breaking changes\n  // with unenv polyfills.\n  return new AsyncHook();\n}\n\nexport function executionAsyncId(): number {\n  // Even though we don't implement this function, we return a default value\n  // in order to preserve backward compatibility and avoid breaking changes\n  // with unenv polyfills.\n  return 0;\n}\n\nexport function executionAsyncResource(): Record<string, string> {\n  // Even though we don't implement this function, we return a default value\n  // in order to preserve backward compatibility and avoid breaking changes\n  // with unenv polyfills.\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n  return Object.create(null);\n}\n\nexport function triggerAsyncId(): number {\n  // Even though we don't implement this function, we return a default value\n  // in order to preserve backward compatibility and avoid breaking changes\n  // with unenv polyfills.\n  return 0;\n}\n\nexport default {\n  AsyncLocalStorage,\n  AsyncResource,\n  asyncWrapProviders,\n  createHook,\n  executionAsyncId,\n  executionAsyncResource,\n  triggerAsyncId,\n};\n"
  },
  {
    "path": "src/node/buffer.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport {\n  constants,\n  kMaxLength,\n  kStringMaxLength,\n  Buffer,\n  SlowBuffer,\n  isAscii,\n  isUtf8,\n  transcode,\n  INSPECT_MAX_BYTES,\n  resolveObjectURL,\n} from 'node-internal:internal_buffer';\n\nconst atob = globalThis.atob.bind(globalThis);\nconst btoa = globalThis.btoa.bind(globalThis);\nconst Blob = globalThis.Blob;\nconst File = globalThis.File;\n\nexport {\n  atob,\n  btoa,\n  constants,\n  kMaxLength,\n  kStringMaxLength,\n  Blob,\n  Buffer,\n  File,\n  SlowBuffer,\n  isAscii,\n  isUtf8,\n  transcode,\n  INSPECT_MAX_BYTES,\n  resolveObjectURL,\n};\n\nexport default {\n  atob,\n  btoa,\n  constants,\n  kMaxLength,\n  kStringMaxLength,\n  Blob,\n  Buffer,\n  File,\n  SlowBuffer,\n  isAscii,\n  isUtf8,\n  transcode,\n  INSPECT_MAX_BYTES,\n  resolveObjectURL,\n};\n"
  },
  {
    "path": "src/node/child_process.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport type {\n  SpawnSyncReturns,\n  SpawnSyncOptions,\n  ChildProcess as _ChildProcess,\n  ExecOptions,\n  ExecException,\n  ExecFileOptions,\n  ExecSyncOptions,\n  ForkOptions,\n} from 'node:child_process';\nimport { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors';\nimport { EventEmitter } from 'node-internal:events';\nimport {\n  validateFunction,\n  validateObject,\n  validateString,\n  validateArray,\n} from 'node-internal:validators';\nimport type { Readable, Writable } from 'node:stream';\n\nexport class ChildProcess extends EventEmitter implements _ChildProcess {\n  stdin: Writable | null = null;\n  stdout: Readable | null = null;\n  stderr: Readable | null = null;\n  killed: boolean = false;\n  stdio: [\n    Writable | null,\n    Readable | null,\n    Readable | null,\n    Writable | Readable | null | undefined,\n    Writable | Readable | null | undefined,\n  ] = [null, null, null, null, null];\n  connected: boolean = false;\n  exitCode: number | null = null;\n  signalCode: NodeJS.Signals | null = null;\n  spawnargs: string[] = [];\n  spawnfile: string;\n\n  constructor() {\n    super();\n    throw new ERR_METHOD_NOT_IMPLEMENTED('child_process.ChildProcess');\n  }\n\n  kill(_signal?: NodeJS.Signals | number): boolean {\n    return false;\n  }\n\n  send(\n    _message: unknown,\n    _sendHandle?: unknown,\n    _options?: unknown,\n    _callback?: unknown\n  ): boolean {\n    return false;\n  }\n\n  disconnect(): void {\n    // Do nothing.\n  }\n\n  unref(): void {\n    // Do nothing\n  }\n\n  ref(): void {\n    // Do nothing\n  }\n\n  [Symbol.dispose](): void {\n    this.kill();\n  }\n}\n\nexport function _forkChild(_fd: number, _serializationMode: number): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('child_process._forkChild');\n}\n\nexport function exec(\n  command: string,\n  options: ExecOptions | undefined | null,\n  callback?: (\n    error: ExecException | null,\n    stdout: string | Buffer,\n    stderr: string | Buffer\n  ) => void\n): ChildProcess {\n  validateString(command, 'command');\n  if (options != null) {\n    validateObject(options, 'options');\n  }\n  if (callback != null) {\n    validateFunction(callback, 'callback');\n  }\n  throw new ERR_METHOD_NOT_IMPLEMENTED('child_process.exec');\n}\n\nexport function execFile(\n  _file: string,\n  _args: string[],\n  _options?: ExecFileOptions | null,\n  _callback?: (\n    error?: Error,\n    stdout?: string | Buffer,\n    stderr?: string | Buffer\n  ) => unknown\n): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('child_process.execFile');\n}\n\nexport function execFileSync(\n  _file: string,\n  _args: string[] | ExecFileOptions | null,\n  _options?: ExecFileOptions | null\n): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('child_process.execFileSync');\n}\n\nexport function execSync(\n  _command: string,\n  _options?: ExecSyncOptions | null\n): Buffer | string {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('child_process.execSync');\n}\n\nexport function fork(\n  _modulePath: string | URL,\n  args: readonly string[] | ForkOptions | null,\n  options?: ForkOptions | null\n): ChildProcess {\n  if (args == null) {\n    args = [];\n  } else if (typeof args === 'object' && !Array.isArray(args)) {\n    // @ts-expect-error TS2322 This is intentional.\n    options = args;\n    args = [];\n  } else {\n    validateArray(args, 'args');\n  }\n\n  if (options != null) {\n    validateObject(options, 'options');\n  }\n\n  throw new ERR_METHOD_NOT_IMPLEMENTED('child_process.fork');\n}\n\nexport function spawn(): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('child_process.spawn');\n}\n\nexport function spawnSync(\n  command: string,\n  _args?: readonly string[] | SpawnSyncOptions,\n  _options?: SpawnSyncOptions\n): SpawnSyncReturns<string | Buffer> {\n  validateString(command, 'command');\n  throw new ERR_METHOD_NOT_IMPLEMENTED('child_process.spawnSync');\n}\n\nexport default {\n  ChildProcess,\n  _forkChild,\n  exec,\n  execFile,\n  execFileSync,\n  execSync,\n  fork,\n  spawn,\n  spawnSync,\n};\n"
  },
  {
    "path": "src/node/cluster.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\n// Usage of `any` is required because types/node is using it...\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type {\n  Cluster as _Cluster,\n  Worker as _Worker,\n  ClusterSettings,\n  WorkerOptions,\n} from 'node:cluster';\nimport { EventEmitter } from 'node-internal:events';\nimport { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors';\n\nexport const SCHED_NONE = 1;\nexport const SCHED_RR = 2;\n\nexport const isMaster = true;\nexport const isPrimary = true;\nexport const isWorker = false;\n\nexport const schedulingPolicy = SCHED_RR;\nexport const settings: ClusterSettings = {};\nexport const workers: NodeJS.Dict<Worker> = {};\n\nexport function fork(_env?: unknown): Worker {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('cluster.fork');\n}\n\nexport function disconnect(_callback?: () => void): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('cluster.disconnect');\n}\n\nexport function setupPrimary(_settings?: ClusterSettings): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('cluster.setupPrimary');\n}\n\nexport function setupMaster(_settings?: ClusterSettings): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('cluster.setupWorker');\n}\n\nexport const _events: unknown[] = [];\nexport const _eventsCount = 0;\nexport const _maxListeners = 0;\n\nexport class Worker extends EventEmitter implements _Worker {\n  _connected: boolean = false;\n  id = 0;\n\n  constructor(_options?: WorkerOptions) {\n    super();\n  }\n\n  get process(): any {\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n    return globalThis.process as any;\n  }\n  get exitedAfterDisconnect(): boolean {\n    return this._connected;\n  }\n  isConnected(): boolean {\n    return this._connected;\n  }\n  isDead(): boolean {\n    return true;\n  }\n  send(\n    _message: any,\n    _sendHandle?: any,\n    _options?: any,\n    _callback?: any\n  ): boolean {\n    return false;\n  }\n  kill(_signal?: string): void {\n    this._connected = false;\n  }\n  destroy(_signal?: string): void {\n    this._connected = false;\n  }\n  disconnect(): this {\n    this._connected = false;\n    return this;\n  }\n}\n\nexport class Cluster extends EventEmitter implements _Cluster {\n  Worker = Worker;\n  isMaster = isMaster;\n  isPrimary = isPrimary;\n  isWorker = isWorker;\n  SCHED_NONE = SCHED_NONE;\n  SCHED_RR = SCHED_RR;\n  schedulingPolicy = SCHED_RR;\n  settings = settings;\n  workers = workers;\n  setupPrimary(settings?: ClusterSettings): void {\n    setupPrimary(settings);\n  }\n  setupMaster(settings?: ClusterSettings): void {\n    setupMaster(settings);\n  }\n  disconnect(): void {\n    disconnect();\n  }\n  fork(env?: any): Worker {\n    return fork(env);\n  }\n}\n\nexport default new Cluster();\n"
  },
  {
    "path": "src/node/console.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\n/* eslint-disable @typescript-eslint/no-unsafe-member-access */\n\nimport { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors';\nimport processImpl from 'node-internal:public_process';\nimport type { Console as _Console, ConsoleOptions } from 'node:console';\n\nconst noop: VoidFunction = () => {};\n\n// TODO(soon): This class is Node.js compatible but globalThis.console is not.\n// This is because globalThis.console is provided by the v8 runtime.\n// We need to find a way to patch and add createTask and context methods whenever\n// `enable_nodejs_console_module` flag is enabled.\nconst globalConsole = globalThis.console as typeof globalThis.console & {\n  _times: Map<string, number>;\n  _ignoreErrors: boolean;\n  _stderrErrorHandler: () => void;\n  _stdoutErrorHandler: () => void;\n  _stderr: NodeJS.WritableStream;\n  _stdout: NodeJS.WritableStream;\n};\n\nglobalConsole._times = new Map<string, number>();\nglobalConsole._ignoreErrors = true;\nglobalConsole._stderrErrorHandler = noop;\nglobalConsole._stdoutErrorHandler = noop;\n\n// We have this assertion because node-internal:public_process has undefined as the type\nglobalConsole._stderr = processImpl.stderr as unknown as NodeJS.WritableStream;\nglobalConsole._stdout = processImpl.stdout as unknown as NodeJS.WritableStream;\n\nexport function Console(_options: ConsoleOptions): Console {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('Console');\n}\n// We want to make sure the following works:\n// ```js\n// import mod from 'node:console`;\n// new mod.Console(...)\n// ```\n// @ts-expect-error TS2322 This is intentional.\nglobalConsole.Console = Console;\n\nexport const log = console.log.bind(console);\nConsole.prototype.log = log;\nexport const info = console.info.bind(console);\nConsole.prototype.info = info;\nexport const debug = console.debug.bind(console);\nConsole.prototype.debug = debug;\nexport const warn = console.warn.bind(console);\nConsole.prototype.warn = warn;\nexport const error = console.error.bind(console);\nConsole.prototype.error = error;\nexport const dir = console.dir.bind(console);\nConsole.prototype.dir = dir;\nexport const time = console.time.bind(console);\nConsole.prototype.time = time;\nexport const timeEnd = console.timeEnd.bind(console);\nConsole.prototype.timeEnd = timeEnd;\nexport const timeLog = console.timeLog.bind(console);\nConsole.prototype.timeLog = timeLog;\nexport const trace = console.trace.bind(console);\nConsole.prototype.trace = trace;\nexport const assert = console.assert.bind(console);\nConsole.prototype.assert = assert;\nexport const clear = console.clear.bind(console);\nConsole.prototype.clear = clear;\nexport const count = console.count.bind(console);\nConsole.prototype.count = count;\nexport const countReset = console.countReset.bind(console);\nConsole.prototype.countReset = countReset;\nexport const group = console.group.bind(console);\nConsole.prototype.group = group;\nexport const groupEnd = console.groupEnd.bind(console);\nConsole.prototype.groupEnd = groupEnd;\nexport const table = console.table.bind(console);\nConsole.prototype.table = table;\n\nexport const dirxml = console.dirxml.bind(console);\nConsole.prototype.dirxml = dirxml;\nexport const groupCollapsed = console.groupCollapsed.bind(console);\nConsole.prototype.groupCollapsed = groupCollapsed;\nexport const profile = console.profile.bind(console);\nConsole.prototype.profile = profile;\nexport const profileEnd = console.profileEnd.bind(console);\nConsole.prototype.profileEnd = profileEnd;\nexport const timeStamp = console.timeStamp.bind(console);\nConsole.prototype.timeStamp = timeStamp;\n\nexport function context(): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('Console.context');\n}\n// @ts-expect-error TS2339 This is intentional.\nglobalConsole.context = context;\nConsole.prototype.context = context;\n\nexport function createTask(): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('Console.createTask');\n}\n// @ts-expect-error TS2339 This is intentional.\nglobalConsole.createTask = createTask;\nConsole.prototype.createTask = createTask;\n\nexport default globalConsole;\n"
  },
  {
    "path": "src/node/constants.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nexport const RTLD_LAZY = 1;\nexport const RTLD_NOW = 2;\nexport const RTLD_GLOBAL = 8;\nexport const RTLD_LOCAL = 4;\nexport const E2BIG = 7;\nexport const EACCES = 13;\nexport const EADDRINUSE = 48;\nexport const EADDRNOTAVAIL = 49;\nexport const EAFNOSUPPORT = 47;\nexport const EAGAIN = 35;\nexport const EALREADY = 37;\nexport const EBADF = 9;\nexport const EBADMSG = 94;\nexport const EBUSY = 16;\nexport const ECANCELED = 89;\nexport const ECHILD = 10;\nexport const ECONNABORTED = 53;\nexport const ECONNREFUSED = 61;\nexport const ECONNRESET = 54;\nexport const EDEADLK = 11;\nexport const EDESTADDRREQ = 39;\nexport const EDOM = 33;\nexport const EDQUOT = 69;\nexport const EEXIST = 17;\nexport const EFAULT = 14;\nexport const EFBIG = 27;\nexport const EHOSTUNREACH = 65;\nexport const EIDRM = 90;\nexport const EILSEQ = 92;\nexport const EINPROGRESS = 36;\nexport const EINTR = 4;\nexport const EINVAL = 22;\nexport const EIO = 5;\nexport const EISCONN = 56;\nexport const EISDIR = 21;\nexport const ELOOP = 62;\nexport const EMFILE = 24;\nexport const EMLINK = 31;\nexport const EMSGSIZE = 40;\nexport const EMULTIHOP = 95;\nexport const ENAMETOOLONG = 63;\nexport const ENETDOWN = 50;\nexport const ENETRESET = 52;\nexport const ENETUNREACH = 51;\nexport const ENFILE = 23;\nexport const ENOBUFS = 55;\nexport const ENODATA = 96;\nexport const ENODEV = 19;\nexport const ENOENT = 2;\nexport const ENOEXEC = 8;\nexport const ENOLCK = 77;\nexport const ENOLINK = 97;\nexport const ENOMEM = 12;\nexport const ENOMSG = 91;\nexport const ENOPROTOOPT = 42;\nexport const ENOSPC = 28;\nexport const ENOSR = 98;\nexport const ENOSTR = 99;\nexport const ENOSYS = 78;\nexport const ENOTCONN = 57;\nexport const ENOTDIR = 20;\nexport const ENOTEMPTY = 66;\nexport const ENOTSOCK = 38;\nexport const ENOTSUP = 45;\nexport const ENOTTY = 25;\nexport const ENXIO = 6;\nexport const EOPNOTSUPP = 102;\nexport const EOVERFLOW = 84;\nexport const EPERM = 1;\nexport const EPIPE = 32;\nexport const EPROTO = 100;\nexport const EPROTONOSUPPORT = 43;\nexport const EPROTOTYPE = 41;\nexport const ERANGE = 34;\nexport const EROFS = 30;\nexport const ESPIPE = 29;\nexport const ESRCH = 3;\nexport const ESTALE = 70;\nexport const ETIME = 101;\nexport const ETIMEDOUT = 60;\nexport const ETXTBSY = 26;\nexport const EWOULDBLOCK = 35;\nexport const EXDEV = 18;\nexport const PRIORITY_LOW = 19;\nexport const PRIORITY_BELOW_NORMAL = 10;\nexport const PRIORITY_NORMAL = 0;\nexport const PRIORITY_ABOVE_NORMAL = -7;\nexport const PRIORITY_HIGH = -14;\nexport const PRIORITY_HIGHEST = -20;\nexport const SIGHUP = 1;\nexport const SIGINT = 2;\nexport const SIGQUIT = 3;\nexport const SIGILL = 4;\nexport const SIGTRAP = 5;\nexport const SIGABRT = 6;\nexport const SIGIOT = 6;\nexport const SIGBUS = 10;\nexport const SIGFPE = 8;\nexport const SIGKILL = 9;\nexport const SIGUSR1 = 30;\nexport const SIGSEGV = 11;\nexport const SIGUSR2 = 31;\nexport const SIGPIPE = 13;\nexport const SIGALRM = 14;\nexport const SIGTERM = 15;\nexport const SIGCHLD = 20;\nexport const SIGCONT = 19;\nexport const SIGSTOP = 17;\nexport const SIGTSTP = 18;\nexport const SIGTTIN = 21;\nexport const SIGTTOU = 22;\nexport const SIGURG = 16;\nexport const SIGXCPU = 24;\nexport const SIGXFSZ = 25;\nexport const SIGVTALRM = 26;\nexport const SIGPROF = 27;\nexport const SIGWINCH = 28;\nexport const SIGIO = 23;\nexport const SIGINFO = 29;\nexport const SIGSYS = 12;\nexport const UV_FS_SYMLINK_DIR = 1;\nexport const UV_FS_SYMLINK_JUNCTION = 2;\nexport const O_RDONLY = 0;\nexport const O_WRONLY = 1;\nexport const O_RDWR = 2;\nexport const UV_DIRENT_UNKNOWN = 0;\nexport const UV_DIRENT_FILE = 1;\nexport const UV_DIRENT_DIR = 2;\nexport const UV_DIRENT_LINK = 3;\nexport const UV_DIRENT_FIFO = 4;\nexport const UV_DIRENT_SOCKET = 5;\nexport const UV_DIRENT_CHAR = 6;\nexport const UV_DIRENT_BLOCK = 7;\nexport const S_IFMT = 61440;\nexport const S_IFREG = 32768;\nexport const S_IFDIR = 16384;\nexport const S_IFCHR = 8192;\nexport const S_IFBLK = 24576;\nexport const S_IFIFO = 4096;\nexport const S_IFLNK = 40960;\nexport const S_IFSOCK = 49152;\nexport const O_CREAT = 512;\nexport const O_EXCL = 2048;\nexport const UV_FS_O_FILEMAP = 0;\nexport const O_NOCTTY = 131072;\nexport const O_TRUNC = 1024;\nexport const O_APPEND = 8;\nexport const O_DIRECTORY = 1048576;\nexport const O_NOFOLLOW = 256;\nexport const O_SYNC = 128;\nexport const O_DSYNC = 4194304;\nexport const O_SYMLINK = 2097152;\nexport const O_NONBLOCK = 4;\nexport const S_IRWXU = 448;\nexport const S_IRUSR = 256;\nexport const S_IWUSR = 128;\nexport const S_IXUSR = 64;\nexport const S_IRWXG = 56;\nexport const S_IRGRP = 32;\nexport const S_IWGRP = 16;\nexport const S_IXGRP = 8;\nexport const S_IRWXO = 7;\nexport const S_IROTH = 4;\nexport const S_IWOTH = 2;\nexport const S_IXOTH = 1;\nexport const F_OK = 0;\nexport const R_OK = 4;\nexport const W_OK = 2;\nexport const X_OK = 1;\nexport const UV_FS_COPYFILE_EXCL = 1;\nexport const COPYFILE_EXCL = 1;\nexport const UV_FS_COPYFILE_FICLONE = 2;\nexport const COPYFILE_FICLONE = 2;\nexport const UV_FS_COPYFILE_FICLONE_FORCE = 4;\nexport const COPYFILE_FICLONE_FORCE = 4;\nexport const OPENSSL_VERSION_NUMBER = 805306624;\nexport const SSL_OP_ALL = 2147485776;\nexport const SSL_OP_ALLOW_NO_DHE_KEX = 1024;\nexport const SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION = 262144;\nexport const SSL_OP_CIPHER_SERVER_PREFERENCE = 4194304;\nexport const SSL_OP_CISCO_ANYCONNECT = 32768;\nexport const SSL_OP_COOKIE_EXCHANGE = 8192;\nexport const SSL_OP_CRYPTOPRO_TLSEXT_BUG = 2147483648;\nexport const SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS = 2048;\nexport const SSL_OP_LEGACY_SERVER_CONNECT = 4;\nexport const SSL_OP_NO_COMPRESSION = 131072;\nexport const SSL_OP_NO_ENCRYPT_THEN_MAC = 524288;\nexport const SSL_OP_NO_QUERY_MTU = 4096;\nexport const SSL_OP_NO_RENEGOTIATION = 1073741824;\nexport const SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION = 65536;\nexport const SSL_OP_NO_SSLv2 = 0;\nexport const SSL_OP_NO_SSLv3 = 33554432;\nexport const SSL_OP_NO_TICKET = 16384;\nexport const SSL_OP_NO_TLSv1 = 67108864;\nexport const SSL_OP_NO_TLSv1_1 = 268435456;\nexport const SSL_OP_NO_TLSv1_2 = 134217728;\nexport const SSL_OP_NO_TLSv1_3 = 536870912;\nexport const SSL_OP_PRIORITIZE_CHACHA = 2097152;\nexport const SSL_OP_TLS_ROLLBACK_BUG = 8388608;\nexport const ENGINE_METHOD_RSA = 1;\nexport const ENGINE_METHOD_DSA = 2;\nexport const ENGINE_METHOD_DH = 4;\nexport const ENGINE_METHOD_RAND = 8;\nexport const ENGINE_METHOD_EC = 2048;\nexport const ENGINE_METHOD_CIPHERS = 64;\nexport const ENGINE_METHOD_DIGESTS = 128;\nexport const ENGINE_METHOD_PKEY_METHS = 512;\nexport const ENGINE_METHOD_PKEY_ASN1_METHS = 1024;\nexport const ENGINE_METHOD_ALL = 65535;\nexport const ENGINE_METHOD_NONE = 0;\nexport const DH_CHECK_P_NOT_SAFE_PRIME = 2;\nexport const DH_CHECK_P_NOT_PRIME = 1;\nexport const DH_UNABLE_TO_CHECK_GENERATOR = 4;\nexport const DH_NOT_SUITABLE_GENERATOR = 8;\nexport const RSA_PKCS1_PADDING = 1;\nexport const RSA_NO_PADDING = 3;\nexport const RSA_PKCS1_OAEP_PADDING = 4;\nexport const RSA_X931_PADDING = 5;\nexport const RSA_PKCS1_PSS_PADDING = 6;\nexport const RSA_PSS_SALTLEN_DIGEST = -1;\nexport const RSA_PSS_SALTLEN_MAX_SIGN = -2;\nexport const RSA_PSS_SALTLEN_AUTO = -2;\nexport const defaultCoreCipherList = '';\nexport const TLS1_VERSION = 769;\nexport const TLS1_1_VERSION = 770;\nexport const TLS1_2_VERSION = 771;\nexport const TLS1_3_VERSION = 772;\nexport const POINT_CONVERSION_COMPRESSED = 2;\nexport const POINT_CONVERSION_UNCOMPRESSED = 4;\nexport const POINT_CONVERSION_HYBRID = 6;\n\nconst constants = {\n  RTLD_LAZY,\n  RTLD_NOW,\n  RTLD_GLOBAL,\n  RTLD_LOCAL,\n  E2BIG,\n  EACCES,\n  EADDRINUSE,\n  EADDRNOTAVAIL,\n  EAFNOSUPPORT,\n  EAGAIN,\n  EALREADY,\n  EBADF,\n  EBADMSG,\n  EBUSY,\n  ECANCELED,\n  ECHILD,\n  ECONNABORTED,\n  ECONNREFUSED,\n  ECONNRESET,\n  EDEADLK,\n  EDESTADDRREQ,\n  EDOM,\n  EDQUOT,\n  EEXIST,\n  EFAULT,\n  EFBIG,\n  EHOSTUNREACH,\n  EIDRM,\n  EILSEQ,\n  EINPROGRESS,\n  EINTR,\n  EINVAL,\n  EIO,\n  EISCONN,\n  EISDIR,\n  ELOOP,\n  EMFILE,\n  EMLINK,\n  EMSGSIZE,\n  EMULTIHOP,\n  ENAMETOOLONG,\n  ENETDOWN,\n  ENETRESET,\n  ENETUNREACH,\n  ENFILE,\n  ENOBUFS,\n  ENODATA,\n  ENODEV,\n  ENOENT,\n  ENOEXEC,\n  ENOLCK,\n  ENOLINK,\n  ENOMEM,\n  ENOMSG,\n  ENOPROTOOPT,\n  ENOSPC,\n  ENOSR,\n  ENOSTR,\n  ENOSYS,\n  ENOTCONN,\n  ENOTDIR,\n  ENOTEMPTY,\n  ENOTSOCK,\n  ENOTSUP,\n  ENOTTY,\n  ENXIO,\n  EOPNOTSUPP,\n  EOVERFLOW,\n  EPERM,\n  EPIPE,\n  EPROTO,\n  EPROTONOSUPPORT,\n  EPROTOTYPE,\n  ERANGE,\n  EROFS,\n  ESPIPE,\n  ESRCH,\n  ESTALE,\n  ETIME,\n  ETIMEDOUT,\n  ETXTBSY,\n  EWOULDBLOCK,\n  EXDEV,\n  PRIORITY_LOW,\n  PRIORITY_BELOW_NORMAL,\n  PRIORITY_NORMAL,\n  PRIORITY_ABOVE_NORMAL,\n  PRIORITY_HIGH,\n  PRIORITY_HIGHEST,\n  SIGHUP,\n  SIGINT,\n  SIGQUIT,\n  SIGILL,\n  SIGTRAP,\n  SIGABRT,\n  SIGIOT,\n  SIGBUS,\n  SIGFPE,\n  SIGKILL,\n  SIGUSR1,\n  SIGSEGV,\n  SIGUSR2,\n  SIGPIPE,\n  SIGALRM,\n  SIGTERM,\n  SIGCHLD,\n  SIGCONT,\n  SIGSTOP,\n  SIGTSTP,\n  SIGTTIN,\n  SIGTTOU,\n  SIGURG,\n  SIGXCPU,\n  SIGXFSZ,\n  SIGVTALRM,\n  SIGPROF,\n  SIGWINCH,\n  SIGIO,\n  SIGINFO,\n  SIGSYS,\n  UV_FS_SYMLINK_DIR,\n  UV_FS_SYMLINK_JUNCTION,\n  O_RDONLY,\n  O_WRONLY,\n  O_RDWR,\n  UV_DIRENT_UNKNOWN,\n  UV_DIRENT_FILE,\n  UV_DIRENT_DIR,\n  UV_DIRENT_LINK,\n  UV_DIRENT_FIFO,\n  UV_DIRENT_SOCKET,\n  UV_DIRENT_CHAR,\n  UV_DIRENT_BLOCK,\n  S_IFMT,\n  S_IFREG,\n  S_IFDIR,\n  S_IFCHR,\n  S_IFBLK,\n  S_IFIFO,\n  S_IFLNK,\n  S_IFSOCK,\n  O_CREAT,\n  O_EXCL,\n  UV_FS_O_FILEMAP,\n  O_NOCTTY,\n  O_TRUNC,\n  O_APPEND,\n  O_DIRECTORY,\n  O_NOFOLLOW,\n  O_SYNC,\n  O_DSYNC,\n  O_SYMLINK,\n  O_NONBLOCK,\n  S_IRWXU,\n  S_IRUSR,\n  S_IWUSR,\n  S_IXUSR,\n  S_IRWXG,\n  S_IRGRP,\n  S_IWGRP,\n  S_IXGRP,\n  S_IRWXO,\n  S_IROTH,\n  S_IWOTH,\n  S_IXOTH,\n  F_OK,\n  R_OK,\n  W_OK,\n  X_OK,\n  UV_FS_COPYFILE_EXCL,\n  COPYFILE_EXCL,\n  UV_FS_COPYFILE_FICLONE,\n  COPYFILE_FICLONE,\n  UV_FS_COPYFILE_FICLONE_FORCE,\n  COPYFILE_FICLONE_FORCE,\n  OPENSSL_VERSION_NUMBER,\n  SSL_OP_ALL,\n  SSL_OP_ALLOW_NO_DHE_KEX,\n  SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION,\n  SSL_OP_CIPHER_SERVER_PREFERENCE,\n  SSL_OP_CISCO_ANYCONNECT,\n  SSL_OP_COOKIE_EXCHANGE,\n  SSL_OP_CRYPTOPRO_TLSEXT_BUG,\n  SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS,\n  SSL_OP_LEGACY_SERVER_CONNECT,\n  SSL_OP_NO_COMPRESSION,\n  SSL_OP_NO_ENCRYPT_THEN_MAC,\n  SSL_OP_NO_QUERY_MTU,\n  SSL_OP_NO_RENEGOTIATION,\n  SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION,\n  SSL_OP_NO_SSLv2,\n  SSL_OP_NO_SSLv3,\n  SSL_OP_NO_TICKET,\n  SSL_OP_NO_TLSv1,\n  SSL_OP_NO_TLSv1_1,\n  SSL_OP_NO_TLSv1_2,\n  SSL_OP_NO_TLSv1_3,\n  SSL_OP_PRIORITIZE_CHACHA,\n  SSL_OP_TLS_ROLLBACK_BUG,\n  ENGINE_METHOD_RSA,\n  ENGINE_METHOD_DSA,\n  ENGINE_METHOD_DH,\n  ENGINE_METHOD_RAND,\n  ENGINE_METHOD_EC,\n  ENGINE_METHOD_CIPHERS,\n  ENGINE_METHOD_DIGESTS,\n  ENGINE_METHOD_PKEY_METHS,\n  ENGINE_METHOD_PKEY_ASN1_METHS,\n  ENGINE_METHOD_ALL,\n  ENGINE_METHOD_NONE,\n  DH_CHECK_P_NOT_SAFE_PRIME,\n  DH_CHECK_P_NOT_PRIME,\n  DH_UNABLE_TO_CHECK_GENERATOR,\n  DH_NOT_SUITABLE_GENERATOR,\n  RSA_PKCS1_PADDING,\n  RSA_NO_PADDING,\n  RSA_PKCS1_OAEP_PADDING,\n  RSA_X931_PADDING,\n  RSA_PKCS1_PSS_PADDING,\n  RSA_PSS_SALTLEN_DIGEST,\n  RSA_PSS_SALTLEN_MAX_SIGN,\n  RSA_PSS_SALTLEN_AUTO,\n  defaultCoreCipherList,\n  TLS1_VERSION,\n  TLS1_1_VERSION,\n  TLS1_2_VERSION,\n  TLS1_3_VERSION,\n  POINT_CONVERSION_COMPRESSED,\n  POINT_CONVERSION_UNCOMPRESSED,\n  POINT_CONVERSION_HYBRID,\n};\nconst keys = Object.keys(constants);\nfor (const key of keys) {\n  Object.defineProperty(constants, key, { writable: false });\n}\n\nexport default constants;\n"
  },
  {
    "path": "src/node/crypto.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\nimport { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors';\n\nexport const getRandomValues = crypto.getRandomValues.bind(crypto);\nexport const subtle = crypto.subtle;\nexport const webcrypto = crypto;\n\nexport function timingSafeEqual(\n  a: NodeJS.ArrayBufferView,\n  b: NodeJS.ArrayBufferView\n): boolean {\n  return (subtle as any).timingSafeEqual(a, b); // eslint-disable-line\n}\n\nimport {\n  DiffieHellman,\n  DiffieHellmanGroup,\n  createDiffieHellman,\n  createDiffieHellmanGroup,\n  getDiffieHellman,\n  diffieHellman,\n  createECDH,\n  ECDH,\n} from 'node-internal:crypto_dh';\n\nimport {\n  randomBytes,\n  randomFillSync,\n  randomFill,\n  randomInt,\n  randomUUID,\n  type PrimeNum,\n  type GeneratePrimeOptions,\n  type CheckPrimeOptions,\n  generatePrime,\n  generatePrimeSync,\n  checkPrime,\n  checkPrimeSync,\n} from 'node-internal:crypto_random';\n\nimport {\n  createHash,\n  createHmac,\n  Hash,\n  type HashOptions,\n  Hmac,\n  hash,\n} from 'node-internal:crypto_hash';\n\nimport {\n  createSign,\n  createVerify,\n  sign,\n  verify,\n  Sign,\n  Verify,\n} from 'node-internal:crypto_sign';\n\nimport {\n  Cipheriv,\n  Decipheriv,\n  createCipheriv,\n  createDecipheriv,\n  publicDecrypt,\n  publicEncrypt,\n  privateDecrypt,\n  privateEncrypt,\n  getCipherInfo,\n  getCiphers,\n} from 'node-internal:crypto_cipher';\n\nimport { hkdf, hkdfSync } from 'node-internal:crypto_hkdf';\n\nimport {\n  pbkdf2,\n  pbkdf2Sync,\n  type ArrayLike,\n} from 'node-internal:crypto_pbkdf2';\n\nimport { scrypt, scryptSync } from 'node-internal:crypto_scrypt';\n\nimport {\n  KeyObject,\n  PublicKeyObject,\n  PrivateKeyObject,\n  SecretKeyObject,\n  generateKey,\n  generateKeyPair,\n  generateKeyPairSync,\n  generateKeySync,\n  createPrivateKey,\n  createPublicKey,\n  createSecretKey,\n} from 'node-internal:crypto_keys';\n\nimport { Certificate } from 'node-internal:crypto_spkac';\n\nimport { X509Certificate } from 'node-internal:crypto_x509';\n\nexport {\n  // DH\n  DiffieHellman,\n  DiffieHellmanGroup,\n  createDiffieHellman,\n  createDiffieHellmanGroup,\n  getDiffieHellman,\n  diffieHellman,\n  ECDH,\n  createECDH,\n  // Random\n  randomBytes,\n  randomFillSync,\n  randomFill,\n  randomInt,\n  randomUUID,\n  // Primes\n  type PrimeNum as primeNum,\n  type GeneratePrimeOptions as generatePrimeOptions,\n  type CheckPrimeOptions as checkPrimeOptions,\n  generatePrime,\n  generatePrimeSync,\n  checkPrime,\n  checkPrimeSync,\n  // Hash and Hmac\n  createHash,\n  createHmac,\n  Hash,\n  type HashOptions,\n  Hmac,\n  hash,\n  // Hkdf\n  hkdf,\n  hkdfSync,\n  // Pbkdf2\n  pbkdf2,\n  pbkdf2Sync,\n  // Scrypt\n  scrypt,\n  scryptSync,\n  type ArrayLike as arrayLike,\n  // Keys\n  KeyObject,\n  PublicKeyObject,\n  PrivateKeyObject,\n  SecretKeyObject,\n  generateKey,\n  generateKeyPair,\n  generateKeyPairSync,\n  generateKeySync,\n  createPrivateKey,\n  createPublicKey,\n  createSecretKey,\n  // Spkac\n  Certificate,\n  // X509\n  X509Certificate,\n  // Sign/Verify\n  createSign,\n  createVerify,\n  sign,\n  verify,\n  Sign,\n  Verify,\n  // Cipher/Decipher\n  Cipheriv,\n  Decipheriv,\n  createCipheriv,\n  createDecipheriv,\n  publicDecrypt,\n  publicEncrypt,\n  privateDecrypt,\n  privateEncrypt,\n  getCipherInfo,\n  getCiphers,\n};\n\nexport function getCurves(): string[] {\n  // Hardcoded list of supported curves. Note that prime256v1 is equivalent to secp256r1, we follow\n  // OpenSSL's and bssl's nomenclature here.\n\n  // prettier-ignore\n  return ['secp224r1', 'prime256v1', 'secp384r1', 'secp521r1'];\n}\n\nexport function getHashes(): string[] {\n  // Hardcoded list of hashes supported in BoringSSL, node's approach looks pretty clunky. This is\n  // expected to change infrequently based of bssl's stability-focused approach.\n\n  // prettier-ignore\n  return ['md4', 'md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512', 'md5-sha1', 'RSA-MD5',\n          'RSA-SHA1', 'RSA-SHA224', 'RSA-SHA256', 'RSA-SHA384', 'RSA-SHA512', 'DSA-SHA',\n          'DSA-SHA1', 'ecdsa-with-SHA1'];\n}\n\n// We do not implement the openssl secure heap.\nexport function secureHeapUsed(): Record<string, unknown> {\n  return {\n    total: 0,\n    used: 0,\n    utilization: 0,\n    min: 0,\n  };\n}\n\n// We do not allow users to set the engine used.\nexport function setEngine(_1: string, _2?: number): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('setEngine');\n}\n\n// We do not allow users to modify the FIPS enablement.\nexport function setFips(_: boolean): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('setFips');\n}\n\n// We always run in FIPS mode.\nexport const fips = true;\nexport function getFips(): boolean {\n  return fips;\n}\n\nexport const constants: Record<string, number | string> = Object.create(\n  null\n) as Record<string, number | string>;\nObject.defineProperties(constants, {\n  DH_CHECK_P_NOT_SAFE_PRIME: {\n    value: 2,\n    configurable: false,\n    writable: false,\n  },\n  DH_CHECK_P_NOT_PRIME: {\n    value: 1,\n    configurable: false,\n    writable: false,\n  },\n  DH_UNABLE_TO_CHECK_GENERATOR: {\n    value: 4,\n    configurable: false,\n    writable: false,\n  },\n  DH_NOT_SUITABLE_GENERATOR: {\n    value: 8,\n    configurable: false,\n    writable: false,\n  },\n  RSA_PKCS1_PADDING: {\n    value: 1,\n    configurable: false,\n    writable: false,\n  },\n  RSA_NO_PADDING: {\n    value: 3,\n    configurable: false,\n    writable: false,\n  },\n  RSA_PKCS1_OAEP_PADDING: {\n    value: 4,\n    configurable: false,\n    writable: false,\n  },\n  RSA_X931_PADDING: {\n    value: 5,\n    configurable: false,\n    writable: false,\n  },\n  RSA_PKCS1_PSS_PADDING: {\n    value: 6,\n    configurable: false,\n    writable: false,\n  },\n  RSA_PSS_SALTLEN_DIGEST: {\n    value: -1,\n    configurable: false,\n    writable: false,\n  },\n  RSA_PSS_SALTLEN_MAX_SIGN: {\n    value: -2,\n    configurable: false,\n    writable: false,\n  },\n  RSA_PSS_SALTLEN_AUTO: {\n    value: -2,\n    configurable: false,\n    writable: false,\n  },\n  POINT_CONVERSION_COMPRESSED: {\n    value: 2,\n    configurable: false,\n    writable: false,\n  },\n  POINT_CONVERSION_UNCOMPRESSED: {\n    value: 4,\n    configurable: false,\n    writable: false,\n  },\n  POINT_CONVERSION_HYBRID: {\n    value: 6,\n    configurable: false,\n    writable: false,\n  },\n\n  // The following constants aren't actually used by anything in workers and\n  // are provided solely for nomimal compatibility with Node.js.\n\n  // This one is particularly silly to define since we don't actually\n  // use openssl but the constant exists in Node.js so we'll define it\n  // also. However, we set the value to 0 instead of an actual openssl\n  // version number to hopefully avoid confusion ... we don't want code\n  // out there inspecting this and assuming openssl is present because\n  // we hard coded it to a real openssl version number.\n  OPENSSL_VERSION_NUMBER: {\n    value: 0,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_ALL: {\n    value: 2147485776,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_ALLOW_NO_DHE_KEX: {\n    value: 1024,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION: {\n    value: 262144,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_CIPHER_SERVER_PREFERENCE: {\n    value: 4194304,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_CISCO_ANYCONNECT: {\n    value: 32768,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_COOKIE_EXCHANGE: {\n    value: 8192,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_CRYPTOPRO_TLSEXT_BUG: {\n    value: 2147483648,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS: {\n    value: 2048,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_LEGACY_SERVER_CONNECT: {\n    value: 4,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_NO_COMPRESSION: {\n    value: 131072,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_NO_ENCRYPT_THEN_MAC: {\n    value: 524288,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_NO_QUERY_MTU: {\n    value: 4096,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_NO_RENEGOTIATION: {\n    value: 1073741824,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION: {\n    value: 65536,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_NO_SSLv2: {\n    value: 0,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_NO_SSLv3: {\n    value: 33554432,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_NO_TICKET: {\n    value: 16384,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_NO_TLSv1: {\n    value: 67108864,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_NO_TLSv1_1: {\n    value: 268435456,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_NO_TLSv1_2: {\n    value: 134217728,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_NO_TLSv1_3: {\n    value: 536870912,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_PRIORITIZE_CHACHA: {\n    value: 2097152,\n    configurable: false,\n    writable: false,\n  },\n  SSL_OP_TLS_ROLLBACK_BUG: {\n    value: 8388608,\n    configurable: false,\n    writable: false,\n  },\n  ENGINE_METHOD_RSA: {\n    value: 1,\n    configurable: false,\n    writable: false,\n  },\n  ENGINE_METHOD_DSA: {\n    value: 2,\n    configurable: false,\n    writable: false,\n  },\n  ENGINE_METHOD_DH: {\n    value: 4,\n    configurable: false,\n    writable: false,\n  },\n  ENGINE_METHOD_RAND: {\n    value: 8,\n    configurable: false,\n    writable: false,\n  },\n  ENGINE_METHOD_EC: {\n    value: 2048,\n    configurable: false,\n    writable: false,\n  },\n  ENGINE_METHOD_CIPHERS: {\n    value: 64,\n    configurable: false,\n    writable: false,\n  },\n  ENGINE_METHOD_DIGESTS: {\n    value: 128,\n    configurable: false,\n    writable: false,\n  },\n  ENGINE_METHOD_PKEY_METHS: {\n    value: 512,\n    configurable: false,\n    writable: false,\n  },\n  ENGINE_METHOD_PKEY_ASN1_METHS: {\n    value: 1024,\n    configurable: false,\n    writable: false,\n  },\n  ENGINE_METHOD_ALL: {\n    value: 65535,\n    configurable: false,\n    writable: false,\n  },\n  ENGINE_METHOD_NONE: {\n    value: 0,\n    configurable: false,\n    writable: false,\n  },\n  // The default coreCipherList in Node.js is configurable at build time.\n  // It is used as a configuration option in TLS client and server connections.\n  // We do not actually use this option in our implementation of TLS, however\n  // since we do not actually handle the TLS protocol directly in the runtime.\n  // There is no need for this value to match the defaultCoreCipherList in the\n  // official Node.js binary.\n  defaultCoreCipherList: {\n    value: '',\n    configurable: false,\n    writable: false,\n  },\n  TLS1_VERSION: {\n    value: 769,\n    configurable: false,\n    writable: false,\n  },\n  TLS1_1_VERSION: {\n    value: 770,\n    configurable: false,\n    writable: false,\n  },\n  TLS1_2_VERSION: {\n    value: 771,\n    configurable: false,\n    writable: false,\n  },\n  TLS1_3_VERSION: {\n    value: 772,\n    configurable: false,\n    writable: false,\n  },\n  // This last one is silly. It's defined on the crypto.constants object\n  // in Node.js but is not actually a constant. We also don't actually\n  // use it anywhere ourselves. Since we don't actually have a default\n  // cipher list in our implementation, we just set it to an empty string\n  // initially.\n  defaultCipherList: {\n    value: '',\n    configurable: true,\n    writable: true,\n  },\n});\n\n// Deprecated but required for backwards compatibility.\nexport const pseudoRandomBytes = randomBytes;\n\nexport const CryptoKey = globalThis.CryptoKey;\n\nexport let createCipher: (() => void) | undefined = undefined;\nexport let createDecipher: (() => void) | undefined = undefined;\nexport let Cipher: (() => void) | undefined = undefined;\nexport let Decipher: (() => void) | undefined = undefined;\n\nif (!Cloudflare.compatibilityFlags.remove_nodejs_compat_eol_v22) {\n  createCipher = (): void => {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('createCipher');\n  };\n  createDecipher = (): void => {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('createDecipher');\n  };\n  Cipher = (): void => {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Cipher');\n  };\n  Decipher = (): void => {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Decipher');\n  };\n}\nexport default {\n  constants,\n  // DH\n  DiffieHellman,\n  DiffieHellmanGroup,\n  createDiffieHellman,\n  createDiffieHellmanGroup,\n  getDiffieHellman,\n  ECDH,\n  createECDH,\n  // Keys,\n  KeyObject,\n  PublicKeyObject,\n  PrivateKeyObject,\n  SecretKeyObject,\n  generateKey,\n  generateKeyPair,\n  generateKeyPairSync,\n  generateKeySync,\n  createPrivateKey,\n  createPublicKey,\n  createSecretKey,\n  // Random\n  getRandomValues,\n  pseudoRandomBytes,\n  randomBytes,\n  randomFillSync,\n  randomFill,\n  randomInt,\n  randomUUID,\n  generatePrime,\n  generatePrimeSync,\n  checkPrime,\n  checkPrimeSync,\n  // Hash and Hmac\n  Hash,\n  Hmac,\n  createHash,\n  createHmac,\n  getHashes,\n  hash,\n  // Hkdf\n  hkdf,\n  hkdfSync,\n  // Pbkdf2\n  pbkdf2,\n  pbkdf2Sync,\n  // Scrypt\n  scrypt,\n  scryptSync,\n  // Misc\n  getCiphers,\n  getCurves,\n  secureHeapUsed,\n  setEngine,\n  timingSafeEqual,\n  // Fips\n  getFips,\n  setFips,\n  get fips(): boolean {\n    return getFips();\n  },\n  set fips(_: boolean) {\n    setFips(_);\n  },\n  // WebCrypto\n  subtle,\n  webcrypto,\n  // Spkac\n  Certificate,\n  // X509\n  X509Certificate,\n  // Sign/Verify\n  createSign,\n  createVerify,\n  sign,\n  verify,\n  Sign,\n  Verify,\n  // Cipher/Decipher\n  Cipheriv,\n  Decipheriv,\n  createCipheriv,\n  createDecipheriv,\n  publicDecrypt,\n  publicEncrypt,\n  privateDecrypt,\n  privateEncrypt,\n  getCipherInfo,\n  CryptoKey,\n\n  // EOL\n  createCipher,\n  createDecipher,\n  Cipher,\n  Decipher,\n};\n\n// Classes\n//   * [x] crypto.Certificate\n//   * [x] crypto.Cipher\n//   * [x] crypto.Decipher\n//   * [x] crypto.DiffieHellman\n//   * [x] crypto.DiffieHellmanGroup\n//   * [x] crypto.ECDH\n//   * [x] crypto.Hash\n//   * [x] crypto.Hmac\n//   * [x] crypto.KeyObject\n//   * [x] crypto.Sign\n//   * [x] crypto.Verify\n//   * [x] crypto.X509Certificate\n//   * [x] crypto.constants\n//   * [ ] crypto.DEFAULT_ENCODING\n// * Primes\n//   * [x] crypto.checkPrime(candidate[, options], callback)\n//   * [x] crypto.checkPrimeSync(candidate[, options])\n//   * [x] crypto.generatePrime(size[, options[, callback]])\n//   * [x] crypto.generatePrimeSync(size[, options])\n// * Ciphers\n//   * [x] crypto.createCipher(algorithm, password[, options])\n//   * [x] crypto.createCipheriv(algorithm, key, iv[, options])\n//   * [x] crypto.createDecipher(algorithm, password[, options])\n//   * [x] crypto.createDecipheriv(algorithm, key, iv[, options])\n//   * [x] crypto.privateDecrypt(privateKey, buffer)\n//   * [x] crypto.privateEncrypt(privateKey, buffer)\n//   * [x] crypto.publicDecrypt(key, buffer)\n//   * [x] crypto.publicEncrypt(key, buffer)\n//   * [x] crypto.Decipher\n//   * [x] crypto.Cipher\n// * DiffieHellman\n//   * [x] crypto.createDiffieHellman(prime[, primeEncoding][, generator][, generatorEncoding])\n//   * [x] crypto.createDiffieHellman(primeLength[, generator])\n//   * [x] crypto.createDiffieHellmanGroup(name)\n//   * [x] crypto.createECDH(curveName)\n//   * [x] crypto.diffieHellman(options)\n//   * [x] crypto.getDiffieHellman(groupName)\n// * Hash\n//   * [x] crypto.createHash(algorithm[, options])\n//   * [x] crypto.createHmac(algorithm, key[, options])\n//   * [x] crypto.getHashes()\n//   * [x] crypto.hash()\n// * Keys, not implemented yet. Calling the following APIs will throw a ERR_METHOD_NOT_IMPLEMENTED\n//   * [x] crypto.createPrivateKey(key)\n//   * [x] crypto.createPublicKey(key)\n//   * [x] crypto.createSecretKey(key[, encoding])\n//   * [x] crypto.generateKey(type, options, callback)\n//   * [x] crypto.generateKeyPair(type, options, callback)\n//   * [x] crypto.generateKeyPairSync(type, options)\n//   * [x] crypto.generateKeySync(type, options)\n// * Sign/Verify\n//   * [x] crypto.createSign(algorithm[, options])\n//   * [x] crypto.createVerify(algorithm[, options])\n//   * [x] crypto.sign(algorithm, data, key[, callback])\n//   * [x] crypto.verify(algorithm, data, key, signature[, callback])\n// * Misc\n//   * [x] crypto.getCipherInfo(nameOrNid[, options])\n//   * [x] crypto.getCiphers()\n//   * [x] crypto.getCurves()\n//   * [x] crypto.secureHeapUsed()\n//   * [x] crypto.setEngine(engine[, flags])\n//   * [x] crypto.timingSafeEqual(a, b)\n// * Fips\n//   * [x] crypto.getFips()\n//   * [x] crypto.fips\n//   * [x] crypto.setFips(bool)\n// * Random\n//   * [x] crypto.getRandomValues(typedArray)\n//   * [x] crypto.randomBytes(size[, callback])\n//   * [x] crypto.randomFillSync(buffer[, offset][, size])\n//   * [x] crypto.randomFill(buffer[, offset][, size], callback)\n//   * [x] crypto.randomInt([min, ]max[, callback])\n//   * [x] crypto.randomUUID([options])\n// * Key Derivation\n//   * [x] crypto.hkdf(digest, ikm, salt, info, keylen, callback)\n//   * [x] crypto.hkdfSync(digest, ikm, salt, info, keylen)\n//   * [x] crypto.pbkdf2(password, salt, iterations, keylen, digest, callback)\n//   * [x] crypto.pbkdf2Sync(password, salt, iterations, keylen, digest)\n//   * [x] crypto.scrypt(password, salt, keylen[, options], callback)\n//   * [x] crypto.scryptSync(password, salt, keylen[, options])\n// * WebCrypto\n//   * [x] crypto.subtle\n//   * [x] crypto.webcrypto\n//   * [x] crypto.CryptoKey\n"
  },
  {
    "path": "src/node/dgram.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { validateObject, validateFunction } from 'node-internal:validators';\nimport { Buffer } from 'node-internal:internal_buffer';\nimport { EventEmitter } from 'node-internal:events';\n\nimport type {\n  SocketOptions,\n  SocketType,\n  RemoteInfo,\n  Socket as DgramSocket,\n} from 'node:dgram';\n\ntype SocketClassType = typeof DgramSocket;\n\nexport function Socket(\n  this: SocketClassType,\n  type?: SocketType | SocketOptions,\n  callback?: (msg: Buffer, rinfo: RemoteInfo) => void\n): SocketClassType {\n  EventEmitter.call(this as unknown as EventEmitter);\n  if (typeof type === 'string') {\n    type = { type };\n  }\n  validateObject(type, 'type');\n  if (callback !== undefined) {\n    validateFunction(callback, 'callback');\n  }\n  return this;\n}\nObject.setPrototypeOf(Socket.prototype, EventEmitter.prototype);\nObject.setPrototypeOf(Socket, EventEmitter);\n\nexport function createSocket(\n  type?: SocketType | SocketOptions,\n  callback?: (msg: Buffer, rinfo: RemoteInfo) => void\n): SocketClassType {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call\n  return new (Socket as any)(type, callback) as SocketClassType;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.bind = function (\n  this: SocketClassType,\n  _1: unknown,\n  _2: unknown,\n  _3: unknown\n): SocketClassType {\n  return this;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.connect = function (\n  _1: unknown,\n  _2: unknown,\n  _3: unknown\n): void {\n  // no-op\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.disconnect = function (): void {\n  // no-op\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.sendto = function (\n  _1: unknown,\n  _2: unknown,\n  _3: unknown,\n  _4: unknown,\n  _5: unknown,\n  _6: unknown\n): void {\n  // no-op\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.send = function (\n  _1: unknown,\n  _2: unknown,\n  _3: unknown,\n  _4: unknown,\n  _5: unknown,\n  _6: unknown\n): void {\n  // no-op\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.close = function (\n  this: SocketClassType,\n  _1: unknown\n): SocketClassType {\n  return this;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype[Symbol.asyncDispose] = async function (): Promise<void> {\n  // no-op\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.address = function (): object {\n  return {};\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.remoteAddress = function (): object {\n  return {};\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.setBroadcast = function (_: unknown): void {};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.setTTL = function (_: unknown): void {};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.setMulticastTTL = function (_: unknown): void {};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.setMulticastLoopback = function (_: unknown): void {};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.setMulticastInterface = function (_: unknown): void {};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.addMembership = function (_1: unknown, _2: unknown): void {};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.dropMembership = function (_1: unknown, _2: unknown): void {};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.addSourceSpecificMembership = function (\n  _1: unknown,\n  _2: unknown,\n  _3: unknown\n): void {};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.dropSourceSpecificMembership = function (\n  _1: unknown,\n  _2: unknown,\n  _3: unknown\n): void {};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.ref = function (this: SocketClassType): SocketClassType {\n  return this;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.unref = function (this: SocketClassType): SocketClassType {\n  return this;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.setRecvBufferSize = function (_: number): void {};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.setSendBufferSize = function (_: number): void {};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.getRecvBufferSize = function (): number {\n  return 0;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.getSendBufferSize = function (): number {\n  return 0;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.getSendQueueSize = function (): number {\n  return 0;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nSocket.prototype.getSendQueueCount = function (): number {\n  return 0;\n};\n\nexport default {\n  createSocket,\n  Socket,\n};\n"
  },
  {
    "path": "src/node/diagnostics_channel.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { default as diagnosticsChannel } from 'node-internal:diagnostics_channel';\n\nimport type {\n  Channel as ChannelType,\n  MessageCallback,\n} from 'node-internal:diagnostics_channel';\n\nimport { ERR_INVALID_ARG_TYPE } from 'node-internal:internal_errors';\n\nimport { validateObject } from 'node-internal:validators';\n\nexport const { Channel } = diagnosticsChannel;\n\nexport function hasSubscribers(name: string | symbol): boolean {\n  return diagnosticsChannel.hasSubscribers(name);\n}\n\nexport function channel(name: string | symbol): ChannelType {\n  return diagnosticsChannel.channel(name);\n}\n\nexport function subscribe(\n  name: string | symbol,\n  callback: MessageCallback\n): void {\n  diagnosticsChannel.subscribe(name, callback);\n}\n\nexport function unsubscribe(\n  name: string | symbol,\n  callback: MessageCallback\n): void {\n  diagnosticsChannel.unsubscribe(name, callback);\n}\n\nexport interface TracingChannelSubscriptions {\n  start?: MessageCallback;\n  end?: MessageCallback;\n  asyncStart?: MessageCallback;\n  asyncEnd?: MessageCallback;\n  error?: MessageCallback;\n}\n\nexport interface TracingChannels {\n  start: ChannelType;\n  end: ChannelType;\n  asyncStart: ChannelType;\n  asyncEnd: ChannelType;\n  error: ChannelType;\n}\n\nconst kStart = Symbol('kStart');\nconst kEnd = Symbol('kEnd');\nconst kAsyncStart = Symbol('kAsyncStart');\nconst kAsyncEnd = Symbol('kAsyncEnd');\nconst kError = Symbol('kError');\n\nexport class TracingChannel {\n  private [kStart]?: ChannelType;\n  private [kEnd]?: ChannelType;\n  private [kAsyncStart]?: ChannelType;\n  private [kAsyncEnd]?: ChannelType;\n  private [kError]?: ChannelType;\n\n  constructor() {\n    throw new Error(\n      'Use diagnostic_channel.tracingChannels() to create TracingChannel'\n    );\n  }\n\n  get start(): ChannelType | undefined {\n    return this[kStart];\n  }\n  get end(): ChannelType | undefined {\n    return this[kEnd];\n  }\n  get asyncStart(): ChannelType | undefined {\n    return this[kAsyncStart];\n  }\n  get asyncEnd(): ChannelType | undefined {\n    return this[kAsyncEnd];\n  }\n  get error(): ChannelType | undefined {\n    return this[kError];\n  }\n\n  subscribe(subscriptions: TracingChannelSubscriptions): void {\n    if (subscriptions.start !== undefined)\n      this[kStart]?.subscribe(subscriptions.start);\n    if (subscriptions.end !== undefined)\n      this[kEnd]?.subscribe(subscriptions.end);\n    if (subscriptions.asyncStart !== undefined)\n      this[kAsyncStart]?.subscribe(subscriptions.asyncStart);\n    if (subscriptions.asyncEnd !== undefined)\n      this[kAsyncEnd]?.subscribe(subscriptions.asyncEnd);\n    if (subscriptions.error !== undefined)\n      this[kError]?.subscribe(subscriptions.error);\n  }\n\n  unsubscribe(subscriptions: TracingChannelSubscriptions): void {\n    if (subscriptions.start !== undefined)\n      this[kStart]?.unsubscribe(subscriptions.start);\n    if (subscriptions.end !== undefined)\n      this[kEnd]?.unsubscribe(subscriptions.end);\n    if (subscriptions.asyncStart !== undefined)\n      this[kAsyncStart]?.unsubscribe(subscriptions.asyncStart);\n    if (subscriptions.asyncEnd !== undefined)\n      this[kAsyncEnd]?.unsubscribe(subscriptions.asyncEnd);\n    if (subscriptions.error !== undefined)\n      this[kError]?.unsubscribe(subscriptions.error);\n  }\n\n  traceSync(\n    fn: (...args: unknown[]) => unknown,\n    context: Record<string, unknown> = {},\n    thisArg: unknown = globalThis,\n    ...args: unknown[]\n  ): unknown {\n    const { start, end, error } = this;\n\n    return start?.runStores(\n      context,\n      () => {\n        try {\n          const result = Reflect.apply(fn, thisArg, args);\n          context.result = result;\n          return result;\n        } catch (err) {\n          context.error = err;\n          error?.publish(context);\n          throw err;\n        } finally {\n          end?.publish(context);\n        }\n      },\n      thisArg\n    );\n  }\n\n  tracePromise(\n    fn: (...args: unknown[]) => unknown,\n    context: Record<string, unknown> = {},\n    thisArg: unknown = globalThis,\n    ...args: unknown[]\n  ): unknown {\n    const { start, end, asyncStart, asyncEnd, error } = this;\n\n    function reject(err: Error): Promise<unknown> {\n      context.error = err;\n      error?.publish(context);\n      asyncStart?.publish(context);\n      asyncEnd?.publish(context);\n      return Promise.reject(err);\n    }\n\n    function resolve(result: unknown): unknown {\n      context.result = result;\n      asyncStart?.publish(context);\n      asyncEnd?.publish(context);\n      return result;\n    }\n\n    return start?.runStores(\n      context,\n      () => {\n        try {\n          let promise = Reflect.apply(fn, thisArg, args) as Promise<unknown>;\n          // Convert thenables to native promises\n          if (!(promise instanceof Promise)) {\n            promise = Promise.resolve(promise);\n          }\n          return promise.then(resolve, reject);\n        } catch (err) {\n          context.error = err;\n          error?.publish(context);\n          throw err;\n        } finally {\n          end?.publish(context);\n        }\n      },\n      thisArg\n    );\n  }\n\n  traceCallback(\n    fn: (...args: unknown[]) => unknown,\n    position = -1,\n    context: Record<string, unknown> = {},\n    thisArg: unknown = globalThis,\n    ...args: unknown[]\n  ): unknown {\n    const { start, end, asyncStart, asyncEnd, error } = this;\n\n    function wrappedCallback(this: unknown, err: unknown, res: unknown): void {\n      if (err) {\n        context.error = err;\n        error?.publish(context);\n      } else {\n        context.result = res;\n      }\n\n      // Using runStores here enables manual context failure recovery\n      asyncStart?.runStores(\n        context,\n        () => {\n          try {\n            if (callback) {\n              // eslint-disable-next-line prefer-rest-params\n              Reflect.apply(callback, this, arguments);\n            }\n          } finally {\n            asyncEnd?.publish(context);\n          }\n        },\n        thisArg\n      );\n    }\n\n    const callback = args[position] as VoidFunction | undefined;\n    if (typeof callback !== 'function') {\n      throw new ERR_INVALID_ARG_TYPE('callback', ['function'], callback);\n    }\n    args.splice(position, 1, wrappedCallback);\n\n    return start?.runStores(\n      context,\n      () => {\n        try {\n          return Reflect.apply(fn, thisArg, args);\n        } catch (err) {\n          context.error = err;\n          error?.publish(context);\n          throw err;\n        } finally {\n          end?.publish(context);\n        }\n      },\n      thisArg\n    );\n  }\n}\n\nfunction validateChannel(channel: unknown, name: string): ChannelType {\n  if (!(channel instanceof Channel)) {\n    throw new ERR_INVALID_ARG_TYPE(name, 'Channel', channel);\n  }\n  return channel;\n}\n\nexport function tracingChannel(\n  nameOrChannels: string | TracingChannels\n): TracingChannel {\n  return Reflect.construct(\n    function (this: TracingChannel) {\n      if (typeof nameOrChannels === 'string') {\n        this[kStart] = channel(`tracing:${nameOrChannels}:start`);\n        this[kEnd] = channel(`tracing:${nameOrChannels}:end`);\n        this[kAsyncStart] = channel(`tracing:${nameOrChannels}:asyncStart`);\n        this[kAsyncEnd] = channel(`tracing:${nameOrChannels}:asyncEnd`);\n        this[kError] = channel(`tracing:${nameOrChannels}:error`);\n      } else {\n        validateObject(nameOrChannels, 'channels');\n        this[kStart] = validateChannel(nameOrChannels.start, 'channels.start');\n        this[kEnd] = validateChannel(nameOrChannels.end, 'channels.end');\n        this[kAsyncStart] = validateChannel(\n          nameOrChannels.asyncStart,\n          'channels.asyncStart'\n        );\n        this[kAsyncEnd] = validateChannel(\n          nameOrChannels.asyncEnd,\n          'channels.asyncEnd'\n        );\n        this[kError] = validateChannel(nameOrChannels.error, 'channels.error');\n      }\n    },\n    [],\n    TracingChannel\n  ) as TracingChannel;\n}\n\nexport default {\n  hasSubscribers,\n  channel,\n  subscribe,\n  unsubscribe,\n  tracingChannel,\n  Channel,\n};\n"
  },
  {
    "path": "src/node/dns/promises.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport {\n  reverse,\n  resolveTxt,\n  resolveCaa,\n  resolveMx,\n  resolveCname,\n  resolveNs,\n  resolvePtr,\n  resolveSrv,\n  resolveSoa,\n  resolveNaptr,\n  resolve4,\n  resolve6,\n  getServers,\n  setServers,\n  getDefaultResultOrder,\n  setDefaultResultOrder,\n  lookup,\n  lookupService,\n  resolve,\n  resolveAny,\n  Resolver,\n  NODATA,\n  FORMERR,\n  SERVFAIL,\n  NOTFOUND,\n  NOTIMP,\n  REFUSED,\n  BADQUERY,\n  BADNAME,\n  BADFAMILY,\n  BADRESP,\n  CONNREFUSED,\n  TIMEOUT,\n  EOF,\n  FILE,\n  NOMEM,\n  DESTRUCTION,\n  BADSTR,\n  BADFLAGS,\n  NONAME,\n  BADHINTS,\n  NOTINITIALIZED,\n  LOADIPHLPAPI,\n  ADDRGETNETWORKPARAMS,\n  CANCELLED,\n} from 'node-internal:internal_dns_promises';\n\nexport * from 'node-internal:internal_dns_promises';\nexport default {\n  reverse,\n  resolveTxt,\n  resolveCaa,\n  resolveMx,\n  resolveCname,\n  resolveNs,\n  resolvePtr,\n  resolveSrv,\n  resolveSoa,\n  resolveNaptr,\n  resolve4,\n  resolve6,\n  getServers,\n  setServers,\n  getDefaultResultOrder,\n  setDefaultResultOrder,\n  lookup,\n  lookupService,\n  resolve,\n  resolveAny,\n  Resolver,\n  NODATA,\n  FORMERR,\n  SERVFAIL,\n  NOTFOUND,\n  NOTIMP,\n  REFUSED,\n  BADQUERY,\n  BADNAME,\n  BADFAMILY,\n  BADRESP,\n  CONNREFUSED,\n  TIMEOUT,\n  EOF,\n  FILE,\n  NOMEM,\n  DESTRUCTION,\n  BADSTR,\n  BADFLAGS,\n  NONAME,\n  BADHINTS,\n  NOTINITIALIZED,\n  LOADIPHLPAPI,\n  ADDRGETNETWORKPARAMS,\n  CANCELLED,\n};\n"
  },
  {
    "path": "src/node/dns.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport type nodejsDns from 'node:dns';\nimport * as errorCodes from 'node-internal:internal_dns_constants';\nimport * as dns from 'node-internal:internal_dns';\nimport { callbackify } from 'node-internal:internal_utils';\nimport * as dnsPromises from 'node-internal:internal_dns_promises';\n\nexport * from 'node-internal:internal_dns_constants';\n\nexport const promises = dnsPromises;\nexport const reverse = callbackify(dns.reverse.bind(dns));\nexport const resolveTxt = callbackify(dns.resolveTxt.bind(dns));\nexport const resolveCaa = callbackify(dns.resolveCaa.bind(dns));\nexport const resolveMx = callbackify(dns.resolveMx.bind(dns));\nexport const resolveCname = callbackify(dns.resolveCname.bind(dns));\nexport const resolveNs = callbackify(dns.resolveNs.bind(dns));\nexport const resolvePtr = callbackify(dns.resolvePtr.bind(dns));\nexport const resolveSrv = callbackify(dns.resolveSrv.bind(dns));\nexport const resolveSoa = callbackify(dns.resolveSoa.bind(dns));\nexport const resolveNaptr = callbackify(dns.resolveNaptr.bind(dns));\nexport const resolve4 = callbackify(dns.resolve4.bind(dns));\nexport const resolve6 = callbackify(dns.resolve6.bind(dns));\nexport const getServers = dns.getServers.bind(dns);\nexport const setServers = dns.setServers.bind(dns);\nexport const getDefaultResultOrder = dns.getDefaultResultOrder.bind(dns);\nexport const setDefaultResultOrder = dns.setDefaultResultOrder.bind(dns);\nexport const lookup = dns.lookup.bind(dns);\nexport const lookupService = callbackify(dns.lookupService.bind(this));\nexport const resolve = callbackify(dns.resolve.bind(this));\nexport const resolveAny = callbackify(dns.resolveAny.bind(this));\n\nexport class Resolver implements nodejsDns.Resolver {\n  cancel(): void {\n    // TODO(soon): Implement this.\n    throw new Error('Not implemented');\n  }\n\n  setLocalAddress(): void {\n    // Does not apply to workerd implementation\n    throw new Error('Not implemented');\n  }\n\n  getServers(...args: Parameters<typeof getServers>): string[] {\n    return getServers(...args);\n  }\n\n  // @ts-expect-error TS2416 Type mismatch.\n  resolve(...args: Parameters<typeof resolve>): void {\n    resolve(...args);\n  }\n\n  // @ts-expect-error TS2416 Type mismatch.\n  resolve4(...args: Parameters<typeof resolve4>): void {\n    resolve4(...args);\n  }\n\n  // @ts-expect-error TS2416 Type mismatch.\n  resolve6(...args: Parameters<typeof resolve6>): void {\n    resolve6(...args);\n  }\n\n  // @ts-expect-error TS2416 Type mismatch.\n  resolveAny(...args: Parameters<typeof resolveAny>): void {\n    resolveAny(...args);\n  }\n\n  // @ts-expect-error TS2416 Type mismatch.\n  resolveCaa(...args: Parameters<typeof resolveCaa>): void {\n    resolveCaa(...args);\n  }\n\n  // @ts-expect-error TS2416 Type mismatch.\n  resolveCname(...args: Parameters<typeof resolveCname>): void {\n    resolveCname(...args);\n  }\n\n  // @ts-expect-error TS2416 Type mismatch.\n  resolveMx(...args: Parameters<typeof resolveMx>): void {\n    resolveMx(...args);\n  }\n\n  // @ts-expect-error TS2416 Type mismatch.\n  resolveNaptr(...args: Parameters<typeof resolveNaptr>): void {\n    resolveNaptr(...args);\n  }\n\n  // @ts-expect-error TS2416 Type mismatch.\n  resolveNs(...args: Parameters<typeof resolveNs>): void {\n    resolveNs(...args);\n  }\n\n  // @ts-expect-error TS2416 Type mismatch.\n  resolvePtr(...args: Parameters<typeof resolvePtr>): void {\n    resolvePtr(...args);\n  }\n\n  // @ts-expect-error TS2416 Type mismatch.\n  resolveSoa(...args: Parameters<typeof resolveSoa>): void {\n    resolveSoa(...args);\n  }\n\n  // @ts-expect-error TS2416 Type mismatch.\n  resolveSrv(...args: Parameters<typeof resolveSrv>): void {\n    resolveSrv(...args);\n  }\n\n  // @ts-expect-error TS2416 Type mismatch.\n  resolveTxt(...args: Parameters<typeof resolveTxt>): void {\n    resolveTxt(...args);\n  }\n\n  reverse(...args: Parameters<typeof reverse>): void {\n    reverse(...args);\n  }\n\n  setServers(...args: Parameters<typeof setServers>): void {\n    setServers(...args);\n  }\n}\n\nexport default {\n  getServers,\n  lookup,\n  lookupService,\n  resolve,\n  resolve4,\n  resolve6,\n  resolveAny,\n  resolveCname,\n  resolveCaa,\n  resolveMx,\n  resolveNaptr,\n  resolveNs,\n  resolvePtr,\n  resolveSoa,\n  resolveSrv,\n  resolveTxt,\n  reverse,\n  setDefaultResultOrder,\n  getDefaultResultOrder,\n  setServers,\n  Resolver,\n  promises,\n  ...errorCodes,\n};\n"
  },
  {
    "path": "src/node/domain.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n// Of all the deprecated Node.js modules that we need to have available\n// but don't want to actually implement, domain is the probably the\n// most least likely to ever be implemented.\n\nimport { EventEmitter } from 'node-internal:events';\nEventEmitter.usingDomains = false;\n\nexport class Domain extends EventEmitter {\n  members: unknown[] | undefined = undefined;\n\n  _errorHandler(_er: unknown): void {\n    // Should never be called since we override everything that would call it.\n    // But if it is, just throw the error that is passed in.\n    throw _er;\n  }\n\n  enter(): void {\n    // Instead of throwing, we just don't do anything here\n  }\n\n  exit(): void {\n    // Instead of throwing, we just don't do anything here.\n  }\n\n  add(_ee: unknown): void {\n    // Instead of throwing, we just don't do anything here.\n  }\n\n  remove(_ee: unknown): void {\n    // Instead of throwing, we just don't do anything here.\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n  run(fn: Function, ...args: unknown[]): unknown {\n    // This is non-operational. We end up just calling the function directly.\n    this.enter();\n    const ret: unknown = Reflect.apply(fn, this, args);\n    this.exit();\n    return ret;\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n  intercept(cb: Function): Function {\n    // This is non-operational. We end up just returning the callback directly.\n    return cb;\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n  bind(cb: Function): Function {\n    // This is non-operational. We end up just returning the callback directly.\n    return cb;\n  }\n}\n\nexport function createDomain(): Domain {\n  return new Domain();\n}\nexport const create = createDomain();\n\n// The active domain is always the one that we're currently in.\nexport const active: Domain | null = null;\n\nexport default {\n  Domain,\n  active,\n  createDomain,\n  create,\n};\n"
  },
  {
    "path": "src/node/eslint.config.mjs",
    "content": "import { baseConfig } from '../../tools/base.eslint.config.mjs';\n\nexport default [\n  ...baseConfig(),\n  {\n    rules: {\n      'workerd/no-export-default-of-import-star': 'error',\n    },\n  },\n];\n"
  },
  {
    "path": "src/node/events.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n\nexport * from 'node-internal:events';\nexport { default } from 'node-internal:events';\n"
  },
  {
    "path": "src/node/fs/promises.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport * as fs from 'node-internal:internal_fs_promises';\nimport * as constants from 'node-internal:internal_fs_constants';\n\nexport * from 'node-internal:internal_fs_promises';\nexport { constants };\n\nexport default {\n  constants,\n  ...fs,\n};\n"
  },
  {
    "path": "src/node/fs.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport * as promises from 'node-internal:internal_fs_promises';\nimport * as constants from 'node-internal:internal_fs_constants';\nimport * as callbackMethods from 'node-internal:internal_fs_callback';\nimport { Dirent, Dir } from 'node-internal:internal_fs';\nimport { Stats } from 'node-internal:internal_fs_utils';\n\nexport * from 'node-internal:internal_fs_callback';\nimport {\n  ReadStream,\n  WriteStream,\n  createReadStream,\n  createWriteStream,\n} from 'node-internal:internal_fs_streams';\nimport {\n  accessSync,\n  existsSync,\n  appendFileSync,\n  chmodSync,\n  chownSync,\n  closeSync,\n  copyFileSync,\n  cpSync,\n  fchmodSync,\n  fchownSync,\n  fdatasyncSync,\n  fstatSync,\n  fsyncSync,\n  ftruncateSync,\n  futimesSync,\n  globSync,\n  lchmodSync,\n  lchownSync,\n  lutimesSync,\n  linkSync,\n  lstatSync,\n  mkdirSync,\n  mkdtempSync,\n  opendirSync,\n  openSync,\n  readdirSync,\n  readFileSync,\n  readlinkSync,\n  readSync,\n  readvSync,\n  realpathSync,\n  renameSync,\n  rmdirSync,\n  rmSync,\n  statSync,\n  statfsSync,\n  symlinkSync,\n  truncateSync,\n  unlinkSync,\n  utimesSync,\n  writeFileSync,\n  writeSync,\n  writevSync,\n  openAsBlob,\n} from 'node-internal:internal_fs_sync';\n\nconst { F_OK, R_OK, W_OK, X_OK } = constants;\n\n// Node.js exports these as aliases\nexport const FileWriteStream = WriteStream;\nexport const FileReadStream = ReadStream;\n\nexport {\n  constants,\n  F_OK,\n  R_OK,\n  W_OK,\n  X_OK,\n  promises,\n  Dirent,\n  Dir,\n  accessSync,\n  existsSync,\n  appendFileSync,\n  chmodSync,\n  chownSync,\n  closeSync,\n  copyFileSync,\n  cpSync,\n  fchmodSync,\n  fchownSync,\n  fdatasyncSync,\n  fstatSync,\n  fsyncSync,\n  ftruncateSync,\n  futimesSync,\n  globSync,\n  lchmodSync,\n  lchownSync,\n  lutimesSync,\n  linkSync,\n  lstatSync,\n  mkdirSync,\n  mkdtempSync,\n  opendirSync,\n  openSync,\n  readdirSync,\n  readFileSync,\n  readlinkSync,\n  readSync,\n  readvSync,\n  realpathSync,\n  renameSync,\n  rmdirSync,\n  rmSync,\n  statSync,\n  statfsSync,\n  symlinkSync,\n  truncateSync,\n  unlinkSync,\n  utimesSync,\n  writeFileSync,\n  writeSync,\n  writevSync,\n  Stats,\n  ReadStream,\n  WriteStream,\n  createReadStream,\n  createWriteStream,\n  openAsBlob,\n};\n\nexport default {\n  constants,\n  F_OK,\n  R_OK,\n  W_OK,\n  X_OK,\n  promises,\n  Dirent,\n  Dir,\n  Stats,\n  ...callbackMethods,\n  accessSync,\n  existsSync,\n  appendFileSync,\n  chmodSync,\n  chownSync,\n  closeSync,\n  copyFileSync,\n  cpSync,\n  fchmodSync,\n  fchownSync,\n  fdatasyncSync,\n  fstatSync,\n  fsyncSync,\n  ftruncateSync,\n  futimesSync,\n  globSync,\n  lchmodSync,\n  lchownSync,\n  lutimesSync,\n  linkSync,\n  lstatSync,\n  mkdirSync,\n  mkdtempSync,\n  opendirSync,\n  openSync,\n  readdirSync,\n  readFileSync,\n  readlinkSync,\n  readSync,\n  readvSync,\n  realpathSync,\n  renameSync,\n  rmdirSync,\n  rmSync,\n  statSync,\n  statfsSync,\n  symlinkSync,\n  truncateSync,\n  unlinkSync,\n  utimesSync,\n  writeFileSync,\n  writeSync,\n  writevSync,\n  WriteStream,\n  ReadStream,\n  FileWriteStream,\n  FileReadStream,\n  createReadStream,\n  createWriteStream,\n  openAsBlob,\n};\n"
  },
  {
    "path": "src/node/http.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport {\n  validateHeaderName,\n  validateHeaderValue,\n} from 'node-internal:internal_http';\nimport { METHODS, STATUS_CODES } from 'node-internal:internal_http_constants';\nimport { ClientRequest } from 'node-internal:internal_http_client';\nimport { OutgoingMessage } from 'node-internal:internal_http_outgoing';\nimport { IncomingMessage } from 'node-internal:internal_http_incoming';\nimport { Agent, globalAgent } from 'node-internal:internal_http_agent';\nimport {\n  Server,\n  ServerResponse,\n  _connectionListener,\n} from 'node-internal:internal_http_server';\nimport { validateInteger } from 'node-internal:validators';\nimport { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors';\nimport type { IncomingMessageCallback } from 'node-internal:internal_http_util';\nimport type { RequestOptions, ServerOptions, RequestListener } from 'node:http';\n\nconst enableNodejsHttpServerModules =\n  !!Cloudflare.compatibilityFlags['enable_nodejs_http_server_modules'];\n\n// TODO(soon): Our global implementation of WebSocket does not match\n// Node.js' implementation which is compliant to the spec. However,\n// We were previously relying on the unenv polyfills for this, and\n// it just re-exported the global also, so this shouldn't be a breaking\n// change. Later, however, we'll need to reconcile the Node.js and\n// standard spec conformance here.\nexport const WebSocket = globalThis.WebSocket;\nexport const CloseEvent = globalThis.CloseEvent;\nexport const MessageEvent = globalThis.MessageEvent;\n\nexport function request(\n  url: string | URL | RequestOptions,\n  options?: RequestOptions | IncomingMessageCallback,\n  cb?: IncomingMessageCallback\n): ClientRequest {\n  return new ClientRequest(url, options, cb);\n}\n\nexport function get(\n  url: string | URL | RequestOptions,\n  options?: RequestOptions | IncomingMessageCallback,\n  cb?: IncomingMessageCallback\n): ClientRequest {\n  const req = request(url, options, cb);\n  req.end();\n  return req;\n}\n\nexport function createServer(\n  options: ServerOptions,\n  handler: RequestListener\n): Server {\n  if (!enableNodejsHttpServerModules) {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('createServer');\n  }\n\n  return new Server(options, handler);\n}\n\n// the maximum size of HTTP headers (default: 16384 (16KB))\n// ref: https://github.com/nodejs/node/blob/3b715d35440d509c6242ea61dec9d2802f219c83/src/node_options.cc#L787\nexport const maxHeaderSize = 16384;\n\nexport function setMaxIdleHTTPParsers(max: unknown): void {\n  validateInteger(max, 'max', 1);\n  throw new ERR_METHOD_NOT_IMPLEMENTED('setMaxIdleHTTPParsers');\n}\n\nexport {\n  validateHeaderName,\n  validateHeaderValue,\n  METHODS,\n  STATUS_CODES,\n  ClientRequest,\n  OutgoingMessage,\n  Agent,\n  globalAgent,\n  IncomingMessage,\n  Server,\n  ServerResponse,\n  _connectionListener,\n};\nexport default {\n  validateHeaderName,\n  validateHeaderValue,\n  METHODS,\n  STATUS_CODES,\n  ClientRequest,\n  request,\n  get,\n  OutgoingMessage,\n  Agent,\n  globalAgent,\n  IncomingMessage,\n  Server,\n  ServerResponse,\n  createServer,\n  maxHeaderSize,\n  setMaxIdleHTTPParsers,\n  _connectionListener,\n  WebSocket,\n  CloseEvent,\n  MessageEvent,\n};\n"
  },
  {
    "path": "src/node/http2.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n//\n// Portions of this code are based on unenv https://github.com/unjs/unenv/blob/main/LICENSE\n// MIT License, Copyright (c) Pooya Parsa <pooya@pi0.io>\n\n// We currently have no intention of implementing HTTP/2 in workerd but we want to\n// provide the non-op stubs so that polyfills are not necessary for modules that\n// unconditionally import 'http2'.\n\nimport type H2 from 'node:http2';\nimport type H1 from 'node:http';\n\nimport { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors';\nimport { Buffer } from 'node-internal:internal_buffer';\n\nimport { constants } from 'node-internal:internal_http2_constants';\n\nexport { constants };\n\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters\nfunction notImplementedClass<T = unknown>(name: string): T {\n  return class {\n    constructor() {\n      throw new ERR_METHOD_NOT_IMPLEMENTED(name);\n    }\n  } as T;\n}\n\nexport function createSecureServer(..._args: unknown[]): H2.Http2SecureServer {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('http2.createSecureServer');\n}\n\nexport function createServer(..._args: unknown[]): H2.Http2Server {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('http2.createServer');\n}\n\nexport function connect(..._args: unknown[]): H2.ClientHttp2Session {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('http2.connect');\n}\n\nexport function performServerHandshake<\n  Http1Request extends typeof H1.IncomingMessage = typeof H1.IncomingMessage,\n  Http1Response extends typeof H1.ServerResponse<InstanceType<Http1Request>> =\n    typeof H1.ServerResponse,\n  Http2Request extends typeof H2.Http2ServerRequest =\n    typeof H2.Http2ServerRequest,\n  Http2Response extends typeof H2.Http2ServerResponse<\n    InstanceType<Http2Request>\n  > = typeof H2.Http2ServerResponse,\n>(\n  ..._args: unknown[]\n): H2.ServerHttp2Session<\n  Http1Request,\n  Http1Response,\n  Http2Request,\n  Http2Response\n> {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('http2.performServerHandshake');\n}\n\nexport const Http2ServerRequest: H2.Http2ServerRequest = notImplementedClass(\n  'http2.Http2ServerRequest'\n);\n\nexport const Http2ServerResponse: H2.Http2ServerResponse = notImplementedClass(\n  'http2.Http2ServerResponse'\n);\n\nexport function getDefaultSettings(..._args: unknown[]): H2.Settings {\n  // We really ought to throw here but the unenv polyfill returns an object\n  // so let's do so also.\n  return Object.create({\n    headerTableSize: 4096,\n    enablePush: true,\n    initialWindowSize: 65_535,\n    maxFrameSize: 16_384,\n    maxConcurrentStreams: 4_294_967_295,\n    maxHeaderSize: 65_535,\n    maxHeaderListSize: 65_535,\n    enableConnectProtocol: false,\n  }) as H2.Settings;\n}\n\nexport function getPackedSettings(..._args: unknown[]): Buffer {\n  return Buffer.alloc(0);\n}\n\nexport function getUnpackedSettings(..._args: unknown[]): H2.Settings {\n  return {};\n}\n\nexport const sensitiveHeaders = Symbol('nodejs.http2.sensitiveHeaders');\n\nexport default {\n  constants,\n  createSecureServer,\n  createServer,\n  Http2ServerRequest,\n  Http2ServerResponse,\n  connect,\n  getDefaultSettings,\n  getPackedSettings,\n  getUnpackedSettings,\n  performServerHandshake,\n  sensitiveHeaders,\n};\n"
  },
  {
    "path": "src/node/https.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport { urlToHttpOptions, isURL } from 'node-internal:internal_url';\nimport { ClientRequest } from 'node-internal:internal_http_client';\nimport { Agent, globalAgent } from 'node-internal:internal_https_agent';\nimport { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors';\nimport { Server } from 'node-internal:internal_https_server';\nimport type { IncomingMessageCallback } from 'node-internal:internal_http_util';\nimport type { RequestOptions, RequestListener } from 'node:http';\nimport type { ServerOptions } from 'node:https';\n\nconst enableNodejsHttpServerModules =\n  !!Cloudflare.compatibilityFlags['enable_nodejs_http_server_modules'];\n\nexport function request(\n  url: string | URL | RequestOptions,\n  options?: RequestOptions | IncomingMessageCallback,\n  cb?: IncomingMessageCallback\n): ClientRequest;\nexport function request(...args: unknown[]): ClientRequest {\n  let options: RequestOptions = {};\n\n  if (typeof args[0] === 'string') {\n    const urlStr = args.shift() as string;\n    options = urlToHttpOptions(new URL(urlStr));\n  } else if (isURL(args[0])) {\n    options = urlToHttpOptions(args.shift() as URL);\n  }\n\n  if (args.length > 0 && typeof args[0] !== 'function') {\n    Object.assign(options, args.shift());\n  }\n\n  options._defaultAgent = globalAgent;\n  args.unshift(options);\n\n  // @ts-expect-error TS2556 This is OK.\n  return new ClientRequest(...args);\n}\n\nexport function get(\n  input: string | URL | RequestOptions,\n  options?: RequestOptions | IncomingMessageCallback,\n  cb?: IncomingMessageCallback\n): ClientRequest {\n  const req = request(input, options, cb);\n  req.end();\n  return req;\n}\n\nexport function createServer(\n  options: ServerOptions,\n  handler: RequestListener\n): Server {\n  if (!enableNodejsHttpServerModules) {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('createServer');\n  }\n\n  return new Server(options, handler);\n}\n\nexport { Agent, globalAgent, Server };\n\nexport default {\n  get,\n  request,\n  Agent,\n  globalAgent,\n  createServer,\n  Server,\n};\n"
  },
  {
    "path": "src/node/inspector/promises.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport { EventEmitter } from 'node-internal:events';\nimport type {\n  console as _console,\n  Network as _Network,\n  Session as _Session,\n} from 'node:inspector/promises';\nimport { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors';\n\nconst noop: VoidFunction = () => {};\nexport const console: typeof _console = {\n  debug: noop,\n  error: noop,\n  info: noop,\n  log: noop,\n  warn: noop,\n  dir: noop,\n  dirxml: noop,\n  table: noop,\n  trace: noop,\n  group: noop,\n  groupCollapsed: noop,\n  groupEnd: noop,\n  clear: noop,\n  count: noop,\n  countReset: noop,\n  assert: noop,\n  profile: noop,\n  profileEnd: noop,\n  time: noop,\n  timeLog: noop,\n  timeStamp: noop,\n};\n\nexport const Network: typeof _Network = {\n  requestWillBeSent(_params: _Network.RequestWillBeSentEventDataType): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Network.requestWillBeSent');\n  },\n  dataReceived(_params: _Network.DataReceivedEventDataType): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Network.dataReceived');\n  },\n  responseReceived(_params: _Network.ResponseReceivedEventDataType): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Network.responseReceived');\n  },\n  loadingFinished(_params: _Network.LoadingFinishedEventDataType): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Network.loadingFinished');\n  },\n  loadingFailed(_params: _Network.LoadingFailedEventDataType): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Network.loadingFailed');\n  },\n  webSocketHandshakeResponseReceived(\n    _params: _Network.WebSocketHandshakeResponseReceivedEventDataType\n  ): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED(\n      'Network.webSocketHandshakeResponseReceived'\n    );\n  },\n  webSocketClosed(_params: _Network.WebSocketClosedEventDataType): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Network.webSocketClosed');\n  },\n  webSocketCreated(_params: _Network.WebSocketCreatedEventDataType): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Network.webSocketCreated');\n  },\n  dataSent(_params: unknown): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Network.dataSent');\n  },\n};\n\nexport class Session extends EventEmitter implements _Session {\n  constructor() {\n    super();\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Session');\n  }\n\n  connect(): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Session.connect');\n  }\n\n  connectToMainThread(): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Session.connectToMainThread');\n  }\n\n  disconnect(): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Session.disconnect');\n  }\n\n  // @ts-expect-error TS2416 This is intentional\n  post(\n    _method: unknown,\n    _params?: unknown,\n    _callback?: unknown\n  ): Promise<unknown> {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Session.post');\n  }\n}\n\nexport function url(): string | undefined {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('Inspector.url');\n}\n\nexport function waitForDebugger(): Promise<void> {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('Inspector.waitForDebugger');\n}\n\nexport function open(): Disposable {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('Inspector.open');\n}\n\nexport function close(): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('Inspector.close');\n}\n\nexport default {\n  close,\n  console,\n  Network,\n  open,\n  Session,\n  url,\n  waitForDebugger,\n};\n"
  },
  {
    "path": "src/node/inspector.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport { EventEmitter } from 'node-internal:events';\nimport { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors';\nimport type {\n  InspectorConsole,\n  Session as _Session,\n  Network as _Network,\n} from 'node:inspector';\n\nexport function close(): void {\n  // Acts as a no-op.\n}\n\nconst noop: VoidFunction = () => {};\n\nexport const console: InspectorConsole = {\n  debug: noop,\n  error: noop,\n  info: noop,\n  log: noop,\n  warn: noop,\n  dir: noop,\n  dirxml: noop,\n  table: noop,\n  trace: noop,\n  group: noop,\n  groupCollapsed: noop,\n  groupEnd: noop,\n  clear: noop,\n  count: noop,\n  countReset: noop,\n  assert: noop,\n  profile: noop,\n  profileEnd: noop,\n  time: noop,\n  timeLog: noop,\n  timeStamp: noop,\n};\n\nexport function open(\n  _port?: number,\n  _host?: string,\n  _wait?: boolean\n): Disposable {\n  return {\n    [Symbol.dispose](): Promise<void> {\n      return Promise.resolve();\n    },\n  };\n}\n\nexport function url(): string | undefined {\n  return undefined;\n}\n\nexport function waitForDebugger(): void {\n  // Acts as a no-op.\n}\n\nexport class Session extends EventEmitter implements _Session {\n  constructor() {\n    super();\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Session');\n  }\n\n  connect(): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Session.connect');\n  }\n\n  connectToMainThread(): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Session.connectToMainThread');\n  }\n\n  disconnect(): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Session.disconnect');\n  }\n\n  post(_method: unknown, _params?: unknown, _callback?: unknown): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Session.post');\n  }\n}\n\nexport const Network: typeof _Network = {\n  requestWillBeSent(_params: _Network.RequestWillBeSentEventDataType): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Network.requestWillBeSent');\n  },\n  dataReceived(_params: _Network.DataReceivedEventDataType): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Network.dataReceived');\n  },\n  dataSent(_params: unknown): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Network.dataSent');\n  },\n  responseReceived(_params: _Network.ResponseReceivedEventDataType): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Network.responseReceived');\n  },\n  loadingFinished(_params: _Network.LoadingFinishedEventDataType): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Network.loadingFinished');\n  },\n  loadingFailed(_params: _Network.LoadingFailedEventDataType): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Network.loadingFailed');\n  },\n  webSocketCreated(_params: _Network.WebSocketCreatedEventDataType): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Network.webSocketCreated');\n  },\n  webSocketHandshakeResponseReceived(\n    _params: _Network.WebSocketHandshakeResponseReceivedEventDataType\n  ): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED(\n      'Network.webSocketHandshakeResponseReceived'\n    );\n  },\n  webSocketClosed(_params: _Network.WebSocketClosedEventDataType): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Network.webSocketClosed');\n  },\n};\n\nexport default {\n  Session,\n  close,\n  console,\n  open,\n  url,\n  waitForDebugger,\n  Network,\n};\n"
  },
  {
    "path": "src/node/internal/async_hooks.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Type definitions for c++ implementation.\n\nexport interface AsyncResourceOptions {\n  triggerAsyncId?: number;\n}\n\nexport class AsyncResource {\n  constructor(type: string, options?: AsyncResourceOptions);\n  runInAsyncScope<R>(fn: (...args: unknown[]) => R, ...args: unknown[]): R;\n\n  bind<Func extends (...args: unknown[]) => unknown>(\n    fn: Func\n  ): Func & { asyncResource: AsyncResource };\n\n  static bind<\n    Func extends (this: ThisArg, ...args: unknown[]) => unknown,\n    ThisArg,\n  >(\n    fn: Func,\n    type?: string,\n    thisArg?: ThisArg\n  ): Func & { asyncResource: AsyncResource };\n}\n\nexport class AsyncLocalStorage<T> {\n  run<R>(store: T, fn: (...args: unknown[]) => R, ...args: unknown[]): R;\n  exit<R>(fn: (...args: unknown[]) => R, ...args: unknown[]): R;\n  getStore(): T;\n}\n"
  },
  {
    "path": "src/node/internal/buffer.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Type definitions for c++ implementation\n\ninterface CompareOptions {\n  aStart?: number;\n  aEnd?: number;\n  bStart?: number;\n  bEnd?: number;\n}\n\ntype BufferSource = ArrayBufferView | ArrayBuffer;\n\nexport type Encoding = number;\n\nexport function byteLength(value: string): number;\nexport function compare(\n  a: Uint8Array,\n  b: Uint8Array,\n  options?: CompareOptions\n): number;\nexport function concat(list: Uint8Array[], length: number): Uint8Array;\nexport function decodeString(value: string, encoding: Encoding): Uint8Array;\nexport function fillImpl(\n  buffer: Uint8Array,\n  value: string | BufferSource,\n  start: number,\n  end: number,\n  encoding?: Encoding\n): void;\nexport function indexOf(\n  buffer: Uint8Array,\n  value: string | Uint8Array,\n  byteOffset?: number,\n  encoding?: Encoding,\n  findLast?: boolean\n): number | undefined;\nexport function swap(buffer: Uint8Array, size: 16 | 32 | 64): void;\nexport function toString(\n  buffer: Uint8Array,\n  start: number,\n  end: number,\n  encoding: Encoding\n): string;\nexport function write(\n  buffer: Uint8Array,\n  value: string,\n  offset: number,\n  length: number,\n  encoding: Encoding\n): void;\nexport function decode(buffer: Uint8Array, state: Uint8Array): string;\nexport function flush(state: Uint8Array): string;\nexport function isAscii(value: ArrayBufferView): boolean;\nexport function isUtf8(value: ArrayBufferView): boolean;\nexport function transcode(\n  source: ArrayBufferView,\n  fromEncoding: Encoding,\n  toEncoding: Encoding\n): Uint8Array;\n\nexport const ASCII: Encoding;\nexport const LATIN1: Encoding;\nexport const UTF8: Encoding;\nexport const UTF16LE: Encoding;\nexport const BASE64: Encoding;\nexport const BASE64URL: Encoding;\nexport const HEX: Encoding;\n"
  },
  {
    "path": "src/node/internal/constants.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nexport const CHAR_UPPERCASE_A = 65; /* A */\nexport const CHAR_LOWERCASE_A = 97; /* a */\nexport const CHAR_UPPERCASE_Z = 90; /* Z */\nexport const CHAR_LOWERCASE_Z = 122; /* z */\nexport const CHAR_UPPERCASE_C = 67; /* C */\nexport const CHAR_LOWERCASE_B = 98; /* b */\nexport const CHAR_LOWERCASE_E = 101; /* e */\nexport const CHAR_LOWERCASE_N = 110; /* n */\nexport const CHAR_DOT = 46; /* . */\nexport const CHAR_FORWARD_SLASH = 47; /* / */\nexport const CHAR_BACKWARD_SLASH = 92; /* \\ */\nexport const CHAR_VERTICAL_LINE = 124; /* | */\nexport const CHAR_COLON = 58; /* : */\nexport const CHAR_QUESTION_MARK = 63; /* ? */\nexport const CHAR_UNDERSCORE = 95; /* _ */\nexport const CHAR_LINE_FEED = 10; /* \\n */\nexport const CHAR_CARRIAGE_RETURN = 13; /* \\r */\nexport const CHAR_TAB = 9; /* \\t */\nexport const CHAR_FORM_FEED = 12; /* \\f */\nexport const CHAR_EXCLAMATION_MARK = 33; /* ! */\nexport const CHAR_HASH = 35; /* # */\nexport const CHAR_SPACE = 32; /*   */\nexport const CHAR_NO_BREAK_SPACE = 160; /* \\u00A0 */\nexport const CHAR_ZERO_WIDTH_NOBREAK_SPACE = 65279; /* \\uFEFF */\nexport const CHAR_LEFT_SQUARE_BRACKET = 91; /* [ */\nexport const CHAR_RIGHT_SQUARE_BRACKET = 93; /* ] */\nexport const CHAR_LEFT_ANGLE_BRACKET = 60; /* < */\nexport const CHAR_RIGHT_ANGLE_BRACKET = 62; /* > */\nexport const CHAR_LEFT_CURLY_BRACKET = 123; /* { */\nexport const CHAR_RIGHT_CURLY_BRACKET = 125; /* } */\nexport const CHAR_HYPHEN_MINUS = 45; /* - */\nexport const CHAR_PLUS = 43; /* + */\nexport const CHAR_DOUBLE_QUOTE = 34; /* \" */\nexport const CHAR_SINGLE_QUOTE = 39; /* ' */\nexport const CHAR_PERCENT = 37; /* % */\nexport const CHAR_SEMICOLON = 59; /* ; */\nexport const CHAR_CIRCUMFLEX_ACCENT = 94; /* ^ */\nexport const CHAR_GRAVE_ACCENT = 96; /* ` */\nexport const CHAR_AT = 64; /* @ */\nexport const CHAR_AMPERSAND = 38; /* & */\nexport const CHAR_EQUAL = 61; /* = */\nexport const CHAR_0 = 48; /* 0 */\nexport const CHAR_9 = 57; /* 9 */\nexport const EOL = ';';\n"
  },
  {
    "path": "src/node/internal/crypto.d.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { Buffer } from 'node-internal:internal_buffer';\n\n// random\nexport function checkPrimeSync(\n  candidate: ArrayBufferView,\n  num_checks: number\n): boolean;\nexport function randomPrime(\n  size: number,\n  safe: boolean,\n  add?: ArrayBufferView,\n  rem?: ArrayBufferView\n): ArrayBuffer;\n\nexport function statelessDH(\n  privateKey: CryptoKey,\n  publicKey: CryptoKey\n): ArrayBuffer;\n\n// X509Certificate\nexport interface CheckOptions {\n  subject?: string;\n  wildcards?: boolean;\n  partialWildcards?: boolean;\n  multiLabelWildcards?: boolean;\n  singleLabelSubdomains?: boolean;\n}\n\nexport class X509Certificate {\n  static parse(data: ArrayBuffer | ArrayBufferView): X509Certificate | null;\n  get subject(): string | undefined;\n  get subjectAltName(): string | undefined;\n  get infoAccess(): string | undefined;\n  get issuer(): string | undefined;\n  get issuerCert(): X509Certificate | undefined;\n  get validFrom(): string | undefined;\n  get validTo(): string | undefined;\n  get fingerprint(): string | undefined;\n  get fingerprint256(): string | undefined;\n  get fingerprint512(): string | undefined;\n  get keyUsage(): string[] | undefined;\n  get serialNumber(): string | undefined;\n  get pem(): string | undefined;\n  get raw(): ArrayBuffer | undefined;\n  get publicKey(): CryptoKey | undefined;\n  get isCA(): boolean;\n  checkHost(host: string, options?: CheckOptions): string | undefined;\n  checkEmail(email: string, options?: CheckOptions): string | undefined;\n  checkIp(ip: string, options?: CheckOptions): string | undefined;\n  checkIssued(cert: X509Certificate): boolean;\n  checkPrivateKey(key: CryptoKey): boolean;\n  verify(key: CryptoKey): boolean;\n  toLegacyObject(): object;\n}\n\n// Hash and Hmac\nexport class HashHandle {\n  constructor(algorithm: string, xofLen: number);\n  update(data: Buffer | ArrayBufferView): number;\n  digest(): ArrayBuffer;\n  copy(xofLen: number): HashHandle;\n}\n\nexport class SignHandle {\n  constructor(algorithm: string);\n  update(data: Buffer | ArrayBufferView): void;\n  sign(\n    key: CryptoKey,\n    rsaPadding?: number,\n    pssSaltLength?: number,\n    dsaSigEnc?: number\n  ): Uint8Array;\n}\n\nexport class VerifyHandle {\n  constructor(algorithm: string);\n  update(data: Buffer | ArrayBufferView): void;\n  verify(\n    key: CryptoKey,\n    signature: ArrayBufferView,\n    rsaPadding?: number,\n    pssSaltLength?: number,\n    dsaSigEnc?: number\n  ): boolean;\n}\n\nexport function signOneShot(\n  key: CryptoKey,\n  algorithm: string | undefined,\n  data: ArrayBufferView,\n  rsaPadding?: number,\n  pssSaltLength?: number,\n  dsaSigEnc?: number\n): ArrayBuffer;\nexport function verifyOneShot(\n  key: CryptoKey,\n  algorithm: string | undefined,\n  data: ArrayBufferView,\n  signature: ArrayBufferView,\n  rsaPadding?: number,\n  pssSaltLength?: number,\n  dsaSigEnc?: number\n): boolean;\n\nexport class CipherHandle {\n  update(data: ArrayBuffer | ArrayBufferView): ArrayBuffer;\n  final(): ArrayBuffer;\n  setAAD(data: ArrayBuffer | ArrayBufferView, plaintextLength?: number): void;\n  setAutoPadding(autoPadding: boolean): void;\n  getAuthTag(): ArrayBuffer | undefined;\n  setAuthTag(tag: ArrayBuffer | ArrayBufferView): void;\n}\n\nexport class AeadHandle {\n  update(data: ArrayBuffer | ArrayBufferView): ArrayBuffer;\n  final(): ArrayBuffer;\n  setAAD(data: ArrayBuffer | ArrayBufferView, plaintextLength?: number): void;\n  setAutoPadding(autoPadding: boolean): void;\n  getAuthTag(): ArrayBuffer | undefined;\n  setAuthTag(tag: ArrayBuffer | ArrayBufferView): void;\n}\n\nexport type CipherMode = {\n  CIPHER: 0;\n  DECIPHER: 1;\n};\n\nexport function newHandle(\n  mode: (typeof CipherMode)[keyof typeof CipherMode],\n  algorithm: string,\n  key: CryptoKey,\n  iv: ArrayBuffer | ArrayBufferView,\n  authTagLength?: number\n): CipherHandle | AeadHandle;\nexport interface PublicPrivateCipherOptions {\n  padding: number;\n  oaepHash: string;\n  oaepLabel: ArrayBuffer | ArrayBufferView | undefined;\n}\n\nexport function publicEncrypt(\n  key: CryptoKey,\n  buffer: ArrayBuffer | ArrayBufferView,\n  options: PublicPrivateCipherOptions\n): Buffer;\nexport function publicDecrypt(\n  key: CryptoKey,\n  buffer: ArrayBuffer | ArrayBufferView,\n  options: PublicPrivateCipherOptions\n): Buffer;\nexport function privateEncrypt(\n  key: CryptoKey,\n  buffer: ArrayBuffer | ArrayBufferView,\n  options: PublicPrivateCipherOptions\n): Buffer;\nexport function privateDecrypt(\n  key: CryptoKey,\n  buffer: ArrayBuffer | ArrayBufferView,\n  options: PublicPrivateCipherOptions\n): Buffer;\n\ninterface CipherInfo {\n  name: string;\n  nid: number;\n  blockSize?: number;\n  ivLength?: number;\n  keyLength: number;\n  mode:\n    | 'cbc'\n    | 'ccm'\n    | 'cfb'\n    | 'ctr'\n    | 'ecb'\n    | 'gcm'\n    | 'ocb'\n    | 'ofb'\n    | 'stream'\n    | 'wrap'\n    | 'xts';\n}\n\ninterface GetCipherInfoOptions {\n  ivLength?: number;\n  keyLength?: number;\n}\n\nexport function getCipherInfo(\n  nameOrId: string | number,\n  options: GetCipherInfoOptions\n): CipherInfo | undefined;\n\nexport function getCiphers(): string[];\n\nexport type ArrayLike = ArrayBuffer | string | Buffer | ArrayBufferView;\n\nexport class HmacHandle {\n  constructor(algorithm: string, key: ArrayLike | CryptoKey);\n  update(data: Buffer | ArrayBufferView): number;\n  digest(): ArrayBuffer;\n}\n\n// hkdf\nexport function getHkdf(\n  hash: string,\n  key: ArrayLike,\n  salt: ArrayLike,\n  info: ArrayLike,\n  length: number\n): ArrayBuffer;\n\n// pbkdf2\nexport function getPbkdf(\n  password: ArrayLike,\n  salt: ArrayLike,\n  iterations: number,\n  keylen: number,\n  digest: string\n): ArrayBuffer;\n\n// scrypt\nexport function getScrypt(\n  password: ArrayLike,\n  salt: ArrayLike,\n  N: number,\n  r: number,\n  p: number,\n  maxmem: number,\n  keylen: number\n): ArrayBuffer;\n\n// Keys\nexport function exportKey(\n  key: CryptoKey,\n  options?: InnerExportOptions\n): KeyExportResult;\nexport function equals(key: CryptoKey, otherKey: CryptoKey): boolean;\nexport function getAsymmetricKeyDetail(key: CryptoKey): AsymmetricKeyDetails;\nexport function getAsymmetricKeyType(key: CryptoKey): AsymmetricKeyType;\nexport function createSecretKey(key: ArrayBuffer | ArrayBufferView): CryptoKey;\nexport function createPrivateKey(\n  key: InnerCreateAsymmetricKeyOptions\n): CryptoKey;\nexport function createPublicKey(\n  key: InnerCreateAsymmetricKeyOptions\n): CryptoKey;\n\nexport interface RsaKeyPairOptions {\n  type: string;\n  modulusLength: number;\n  publicExponent: number;\n  saltLength?: number;\n  hashAlgorithm?: string;\n  mgf1HashAlgorithm?: string;\n}\n\nexport interface DsaKeyPairOptions {\n  modulusLength: number;\n  divisorLength: number;\n}\n\nexport interface EcKeyPairOptions {\n  namedCurve: string;\n  paramEncoding: ParamEncoding;\n}\n\nexport interface EdKeyPairOptions {\n  type: string;\n}\n\nexport interface DhKeyPairOptions {\n  primeOrGroup: BufferSource | number | string;\n  generator?: number | undefined;\n}\n\nexport function generateRsaKeyPair(options: RsaKeyPairOptions): CryptoKeyPair;\nexport function generateDsaKeyPair(options: DsaKeyPairOptions): CryptoKeyPair;\nexport function generateEcKeyPair(options: EcKeyPairOptions): CryptoKeyPair;\nexport function generateEdKeyPair(options: EdKeyPairOptions): CryptoKeyPair;\nexport function generateDhKeyPair(options: DhKeyPairOptions): CryptoKeyPair;\n\n// Spkac\nexport function verifySpkac(input: ArrayBufferView | ArrayBuffer): boolean;\nexport function exportPublicKey(\n  input: ArrayBufferView | ArrayBuffer\n): null | ArrayBuffer;\nexport function exportChallenge(\n  input: ArrayBufferView | ArrayBuffer\n): null | ArrayBuffer;\n\nexport type KeyData = string | ArrayBuffer | ArrayBufferView;\n\nexport interface RsaKeyAlgorithm {\n  name: 'rsa' | 'rsa-pss';\n  modulusLength: number;\n  publicExponent: Uint8Array;\n  hash?: string;\n}\n\nexport interface EcKeyAlgorithm {\n  name: 'ec';\n  namedCurve: string;\n}\n\nexport interface DhKeyAlgorithm {\n  name: 'dh';\n  prime: Uint8Array;\n  generator: Uint8Array;\n}\n\nexport interface DsaKeyAlgorithm {\n  name: 'dsa';\n  prime: Uint8Array;\n  divisorLength: number;\n}\n\nexport interface HmacKeyAlgorithm {\n  name: 'hmac';\n  hash: string;\n}\n\nexport interface AesKeyAlgorithm {\n  name: 'aes';\n  length: number;\n}\n\nexport type KeyAlgorithm =\n  | RsaKeyAlgorithm\n  | EcKeyAlgorithm\n  | DhKeyAlgorithm\n  | DsaKeyAlgorithm\n  | HmacKeyAlgorithm\n  | AesKeyAlgorithm;\n\nexport interface RsaOtherPrimesInfo {\n  d?: string;\n  r?: string;\n  t?: string;\n}\n\nexport interface JsonWebKey {\n  alg?: string;\n  crv?: string;\n  d?: string;\n  dp?: string;\n  dq?: string;\n  e?: string;\n  ext?: boolean;\n  k?: string;\n  key_ops?: string[];\n  kty?: string;\n  n?: string;\n  oth?: Array<RsaOtherPrimesInfo>;\n  p?: string;\n  q?: string;\n  qi?: string;\n  use?: string;\n  x?: string;\n  y?: string;\n}\n\nexport interface CryptoKeyPair {\n  privateKey: CryptoKey;\n  publicKey: CryptoKey;\n}\n\nexport type KeyObjectType = 'secret' | 'public' | 'private';\n\nexport type KeyExportResult = string | Buffer | JsonWebKey;\n\nexport type SecretKeyFormat = 'buffer' | 'jwk';\nexport type AsymmetricKeyFormat = 'pem' | 'der' | 'jwk';\nexport type PublicKeyEncoding = 'pkcs1' | 'spki';\nexport type PrivateKeyEncoding = 'pkcs1' | 'pkcs8' | 'sec1';\nexport type AsymmetricKeyType = 'rsa' | 'ec' | 'x25519' | 'ed25519' | 'dh';\nexport type SecretKeyType = 'hmac' | 'aes';\nexport type ParamEncoding = 'named' | 'explicit';\n\nexport interface SecretKeyExportOptions {\n  format?: SecretKeyFormat;\n}\n\nexport interface PublicKeyExportOptions {\n  type?: PublicKeyEncoding;\n  format?: AsymmetricKeyFormat;\n}\n\nexport interface PrivateKeyExportOptions {\n  type?: PrivateKeyEncoding;\n  format?: AsymmetricKeyFormat;\n  cipher?: string;\n  passphrase?: string | Uint8Array;\n  encoding?: string;\n}\n\nexport interface InnerPrivateKeyExportOptions {\n  type?: PrivateKeyEncoding;\n  format?: AsymmetricKeyFormat;\n  cipher?: string;\n  passphrase?: Uint8Array;\n}\n\nexport type ExportOptions =\n  | SecretKeyExportOptions\n  | PublicKeyExportOptions\n  | PrivateKeyExportOptions;\n\nexport type InnerExportOptions =\n  | SecretKeyExportOptions\n  | PublicKeyExportOptions\n  | InnerPrivateKeyExportOptions;\n\nexport interface AsymmetricKeyDetails {\n  modulusLength?: number;\n  publicExponent?: bigint;\n  hashAlgorithm?: string;\n  mgf1HashAlgorithm?: string;\n  saltLength?: number;\n  divisorLength?: number;\n  namedCurve?: string;\n}\n\n// The user-provided options passed to createPrivateKey or createPublicKey.\n// This will be processed into an InnerCreateAsymmetricKeyOptions.\nexport interface CreateAsymmetricKeyOptions {\n  key: string | ArrayBuffer | ArrayBufferView | JsonWebKey | null | undefined;\n  format?: AsymmetricKeyFormat;\n  type?: PublicKeyEncoding | PrivateKeyEncoding;\n  passphrase?: string | Uint8Array | Buffer;\n  encoding?: string;\n}\n\n// The processed key options. The key property will be one of either\n// an ArrayBuffer, an ArrayBufferView, a JWK, or a CryptoKey. The\n// format and type options will be validated to known good values,\n// and the passphrase will either be undefined or an ArrayBufferView.\nexport interface InnerCreateAsymmetricKeyOptions {\n  // CryptoKey is only used when importing a public key derived from\n  // an existing private key.\n  key: ArrayBuffer | ArrayBufferView | JsonWebKey | CryptoKey;\n  format: AsymmetricKeyFormat;\n  type: PublicKeyEncoding | PrivateKeyEncoding | undefined;\n  passphrase: Buffer | ArrayBuffer | ArrayBufferView | undefined;\n}\n\nexport interface GenerateKeyOptions {\n  length: number;\n}\n\nexport interface GenerateKeyPairOptions {\n  modulusLength?: number;\n  publicExponent?: number | bigint;\n  hash?: string;\n  hashAlgorithm?: string;\n  mgf1Hash?: string;\n  mgf1HashAlgorithm?: string;\n  saltLength?: number;\n  divisorLength?: number;\n  namedCurve?: string;\n  prime?: Uint8Array;\n  primeLength?: number;\n  generator?: number;\n  group?: string;\n  groupName?: string;\n  paramEncoding?: ParamEncoding;\n  publicKeyEncoding?: PublicKeyExportOptions;\n  privateKeyEncoding?: PrivateKeyExportOptions;\n}\n\n// DiffieHellman\nexport class DiffieHellmanHandle {\n  constructor(\n    sizeOrKey: number | ArrayBuffer | ArrayBufferView,\n    generator: number | ArrayBuffer | ArrayBufferView\n  );\n  setPublicKey(data: ArrayBuffer | ArrayBufferView | Buffer): void;\n  setPrivateKey(data: ArrayBuffer | ArrayBufferView | Buffer): void;\n  getPublicKey(): ArrayBuffer;\n  getPrivateKey(): ArrayBuffer;\n  getGenerator(): ArrayBuffer;\n  getPrime(): ArrayBuffer;\n\n  computeSecret(key: ArrayBuffer | ArrayBufferView): ArrayBuffer;\n  generateKeys(): ArrayBuffer;\n\n  getVerifyError(): number;\n}\n\nexport type ECDHFormat = 'compressed' | 'uncompressed' | 'hybrid';\nexport class ECDHHandle {\n  constructor(curveName: string);\n  computeSecret(otherPublicKey: ArrayBufferView): ArrayBuffer;\n  generateKeys(): ArrayBuffer;\n  getPrivateKey(): ArrayBuffer;\n  getPublicKey(format: ECDHFormat): ArrayBuffer;\n  setPrivateKey(key: ArrayBufferView): void;\n  static convertKey(\n    key: ArrayBufferView,\n    curveName: string,\n    format: ECDHFormat\n  ): ArrayBuffer;\n}\n\nexport function DiffieHellmanGroupHandle(name: string): DiffieHellmanHandle;\n"
  },
  {
    "path": "src/node/internal/crypto_cipher.ts",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport {\n  default as cryptoImpl,\n  type GetCipherInfoOptions,\n  type CipherInfo,\n  type PublicPrivateCipherOptions,\n  type CipherHandle,\n  type AeadHandle,\n  type CipherMode,\n} from 'node-internal:crypto';\n\nimport {\n  Transform,\n  type TransformOptions,\n  type TransformCallback,\n} from 'node-internal:streams_transform';\n\nimport {\n  type KeyObject,\n  createSecretKey,\n  createPublicKey,\n  createPrivateKey,\n  isKeyObject,\n  getKeyObjectHandle,\n} from 'node-internal:crypto_keys';\n\nimport {\n  validateNumber,\n  validateObject,\n  validateString,\n  validateUint32,\n} from 'node-internal:validators';\n\nimport {\n  ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_ARG_VALUE,\n  ERR_MISSING_ARGS,\n} from 'node-internal:internal_errors';\n\nimport {\n  isAnyArrayBuffer,\n  isArrayBufferView,\n} from 'node-internal:internal_types';\n\nimport {\n  type KeyData,\n  type CreateAsymmetricKeyOptions,\n} from 'node-internal:crypto';\n\nimport { Buffer } from 'node-internal:internal_buffer';\n\nexport interface AADOptions {\n  plaintextLength?: number;\n  encoding?: string;\n}\n\nconst kHandle = Symbol('kHandle');\n\nconst CipherMode: CipherMode = {\n  CIPHER: 0,\n  DECIPHER: 1,\n};\n\nexport interface Cipheriv extends Transform {\n  [kHandle]: CipherHandle | AeadHandle;\n  update(\n    data: string | ArrayBuffer | ArrayBufferView,\n    inputEncoding?: string,\n    outputEncoding?: string\n  ): string | Buffer;\n  final(outputEncoding?: string): string | Buffer;\n  setAAD(\n    buffer: string | ArrayBuffer | ArrayBufferView,\n    options?: AADOptions\n  ): this;\n  setAutoPadding(autoPadding?: boolean): this;\n  getAuthTag(): Buffer | undefined;\n}\n\nexport interface Decipheriv extends Transform {\n  [kHandle]: CipherHandle | AeadHandle;\n  update(\n    data: string | ArrayBuffer | ArrayBufferView,\n    inputEncoding?: string,\n    outputEncoding?: string\n  ): string | Buffer;\n  final(outputEncoding?: string): string | Buffer;\n  setAAD(\n    buffer: string | ArrayBuffer | ArrayBufferView,\n    options?: AADOptions\n  ): this;\n  setAutoPadding(autoPadding?: boolean): this;\n  setAuthTag(buffer: ArrayBuffer | ArrayBufferView): this;\n  setAuthTag(buffer: string, encoding?: string): this;\n}\n\nexport interface CipherOptions extends TransformOptions {\n  authLengthTag?: number;\n}\n\nfunction getSecretKey(\n  key: string | ArrayBuffer | ArrayBufferView | KeyObject | CryptoKey\n): CryptoKey {\n  if (key instanceof CryptoKey) {\n    if (key.type !== 'secret') {\n      throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'secret');\n    }\n    return key;\n  } else if (isKeyObject(key)) {\n    if (key.type !== 'secret') {\n      throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'secret');\n    }\n    return getKeyObjectHandle(key);\n  } else if (isAnyArrayBuffer(key) || isArrayBufferView(key)) {\n    return getKeyObjectHandle(createSecretKey(key));\n  } else if (typeof key === 'string') {\n    return getKeyObjectHandle(createSecretKey(key, 'utf8'));\n  }\n\n  throw new ERR_INVALID_ARG_TYPE(\n    'key',\n    ['string', 'Buffer', 'TypedArray', 'DataView', 'KeyObject', 'CryptoKey'],\n    key\n  );\n}\n\nfunction getIv(\n  iv: string | ArrayBuffer | ArrayBufferView | null\n): ArrayBuffer | ArrayBufferView {\n  if (isAnyArrayBuffer(iv) || isArrayBufferView(iv)) {\n    return iv;\n  } else if (typeof iv === 'string') {\n    return Buffer.from(iv, 'utf8');\n  }\n\n  throw new ERR_INVALID_ARG_TYPE(\n    'iv',\n    ['string', 'Buffer', 'TypedArray', 'DataView', 'null'],\n    iv\n  );\n}\n\nexport const Cipheriv = function (\n  this: Cipheriv,\n  algorithm: string,\n  key: string | ArrayBuffer | ArrayBufferView | KeyObject | CryptoKey,\n  iv: string | ArrayBuffer | ArrayBufferView | null,\n  options: CipherOptions = {}\n) {\n  validateString(algorithm, 'algorithm');\n  const secretKey = getSecretKey(key);\n  const ivBuf = getIv(iv);\n\n  if (options.authLengthTag !== undefined) {\n    validateUint32(options.authLengthTag, 'options.authLengthTag');\n  }\n  this[kHandle] = cryptoImpl.newHandle(\n    CipherMode.CIPHER,\n    algorithm,\n    secretKey,\n    ivBuf,\n    options.authLengthTag\n  );\n\n  Transform.call(this, options as TransformOptions);\n} as unknown as {\n  new (\n    algorithm: string,\n    key: string | ArrayBuffer | ArrayBufferView | KeyObject | CryptoKey,\n    iv: string | ArrayBuffer | ArrayBufferView | null,\n    options?: TransformOptions\n  ): Cipheriv;\n};\nObject.setPrototypeOf(Cipheriv.prototype, Transform.prototype);\nObject.setPrototypeOf(Cipheriv, Transform);\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nCipheriv.prototype.update = function (\n  this: Cipheriv,\n  data: string | ArrayBuffer | ArrayBufferView,\n  inputEncoding?: string,\n  outputEncoding?: string\n): string | Buffer {\n  let ret: ArrayBuffer;\n  if (typeof data === 'string') {\n    if (inputEncoding === undefined) {\n      throw new ERR_INVALID_ARG_VALUE(\n        'inputEncoding',\n        inputEncoding,\n        'If inputEncoding is not provided then the data must be a Buffer'\n      );\n    }\n    ret = this[kHandle].update(Buffer.from(data, inputEncoding));\n  } else if (isAnyArrayBuffer(data)) {\n    ret = this[kHandle].update(data);\n  } else if (isArrayBufferView(data)) {\n    ret = this[kHandle].update(data);\n  } else {\n    throw new ERR_INVALID_ARG_TYPE(\n      'data',\n      ['string', 'Buffer', 'TypedArray', 'DataView'],\n      data\n    );\n  }\n\n  if (outputEncoding === undefined) {\n    return Buffer.from(ret);\n  }\n\n  return Buffer.from(ret).toString(outputEncoding);\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nCipheriv.prototype.final = function (\n  this: Cipheriv,\n  outputEncoding?: string\n): string | Buffer {\n  const ret = this[kHandle].final();\n  if (outputEncoding === undefined) {\n    return Buffer.from(ret);\n  }\n  return Buffer.from(ret).toString(outputEncoding);\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nCipheriv.prototype.setAAD = function (\n  this: Cipheriv,\n  buffer: string | ArrayBuffer | ArrayBufferView,\n  options: AADOptions = {}\n): Cipheriv {\n  validateObject(options, 'options');\n  const { plaintextLength, encoding } = options;\n  if (plaintextLength !== undefined) {\n    validateUint32(plaintextLength, 'options.plaintextLength');\n  }\n  if (encoding !== undefined) {\n    validateString(encoding, 'options.encoding');\n  }\n\n  if (typeof buffer === 'string') {\n    this[kHandle].setAAD(Buffer.from(buffer, encoding), plaintextLength);\n  } else if (isAnyArrayBuffer(buffer) || isArrayBufferView(buffer)) {\n    this[kHandle].setAAD(buffer, plaintextLength);\n  }\n  return this;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nCipheriv.prototype.setAutoPadding = function (\n  this: Cipheriv,\n  autoPadding?: boolean\n): Cipheriv {\n  this[kHandle].setAutoPadding(autoPadding ? true : false);\n  return this;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nCipheriv.prototype.getAuthTag = function (this: Cipheriv): Buffer | undefined {\n  const ret = this[kHandle].getAuthTag();\n  if (ret === undefined) return undefined;\n  return Buffer.from(ret);\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nCipheriv.prototype._transform = function (\n  this: Cipheriv,\n  chunk: string | Buffer | ArrayBufferView,\n  encoding: string,\n  callback: TransformCallback\n): void {\n  if (typeof chunk === 'string') {\n    chunk = Buffer.from(chunk, encoding);\n  }\n  this.push(Buffer.from(this[kHandle].update(chunk)));\n  callback();\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nCipheriv.prototype._flush = function (\n  this: Cipheriv,\n  callback: TransformCallback\n): void {\n  this.push(Buffer.from(this[kHandle].final()));\n  callback();\n};\n\nexport const Decipheriv = function (\n  this: Decipheriv,\n  algorithm: string,\n  key: string | ArrayBuffer | ArrayBufferView | KeyObject | CryptoKey,\n  iv: string | ArrayBuffer | ArrayBufferView | null,\n  options: CipherOptions = {}\n) {\n  validateString(algorithm, 'algorithm');\n  const secretKey = getSecretKey(key);\n  const ivBuf = getIv(iv);\n\n  if (options.authLengthTag !== undefined) {\n    validateUint32(options.authLengthTag, 'options.authLengthTag');\n  }\n\n  this[kHandle] = cryptoImpl.newHandle(\n    CipherMode.DECIPHER,\n    algorithm,\n    secretKey,\n    ivBuf,\n    options.authLengthTag\n  );\n\n  Transform.call(this, options as TransformOptions);\n  return this;\n} as unknown as {\n  new (\n    algorithm: string,\n    key: string | ArrayBuffer | ArrayBufferView | KeyObject | CryptoKey,\n    iv: string | ArrayBuffer | ArrayBufferView | null,\n    options?: TransformOptions\n  ): Decipheriv;\n};\n\nObject.setPrototypeOf(Decipheriv.prototype, Transform.prototype);\nObject.setPrototypeOf(Decipheriv, Transform);\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nDecipheriv.prototype.update = function (\n  this: Decipheriv,\n  data: string | ArrayBuffer | ArrayBufferView,\n  inputEncoding?: string,\n  outputEncoding?: string\n): string | Buffer {\n  let ret: ArrayBuffer;\n  if (typeof data === 'string') {\n    if (inputEncoding === undefined) {\n      throw new ERR_INVALID_ARG_VALUE(\n        'inputEncoding',\n        inputEncoding,\n        'If inputEncoding is not provided then the data must be a Buffer'\n      );\n    }\n    ret = this[kHandle].update(Buffer.from(data, inputEncoding));\n  } else if (isAnyArrayBuffer(data)) {\n    ret = this[kHandle].update(data);\n  } else if (isArrayBufferView(data)) {\n    ret = this[kHandle].update(data);\n  } else {\n    throw new ERR_INVALID_ARG_TYPE(\n      'data',\n      ['string', 'Buffer', 'TypedArray', 'DataView'],\n      data\n    );\n  }\n\n  if (outputEncoding === undefined) {\n    return Buffer.from(ret);\n  }\n\n  return Buffer.from(ret).toString(outputEncoding);\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nDecipheriv.prototype.final = function (\n  this: Decipheriv,\n  outputEncoding?: string\n): string | Buffer {\n  const ret = this[kHandle].final();\n  if (outputEncoding === undefined) {\n    return Buffer.from(ret);\n  }\n  return Buffer.from(ret).toString(outputEncoding);\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nDecipheriv.prototype.setAAD = function (\n  this: Decipheriv,\n  buffer: string | ArrayBuffer | ArrayBufferView,\n  options: AADOptions = {}\n): Decipheriv {\n  validateObject(options, 'options');\n  const { plaintextLength, encoding } = options;\n  if (plaintextLength !== undefined) {\n    validateUint32(plaintextLength, 'options.plaintextLength');\n  }\n  if (encoding !== undefined) {\n    validateString(encoding, 'options.encoding');\n  }\n\n  if (typeof buffer === 'string') {\n    this[kHandle].setAAD(Buffer.from(buffer, encoding), plaintextLength);\n  } else if (isAnyArrayBuffer(buffer) || isArrayBufferView(buffer)) {\n    this[kHandle].setAAD(buffer, plaintextLength);\n  }\n  return this;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nDecipheriv.prototype.setAutoPadding = function (\n  this: Decipheriv,\n  autoPadding?: boolean\n): Decipheriv {\n  this[kHandle].setAutoPadding(autoPadding ? true : false);\n  return this;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nDecipheriv.prototype.setAuthTag = function (\n  this: Decipheriv,\n  buffer: ArrayBuffer | ArrayBufferView\n): Decipheriv {\n  this[kHandle].setAuthTag(buffer);\n  return this;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nDecipheriv.prototype._transform = function (\n  this: Decipheriv,\n  chunk: string | Buffer | ArrayBufferView,\n  encoding: string,\n  callback: TransformCallback\n): void {\n  if (typeof chunk === 'string') {\n    chunk = Buffer.from(chunk, encoding);\n  }\n  this.push(Buffer.from(this[kHandle].update(chunk)));\n  callback();\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nDecipheriv.prototype._flush = function (\n  this: Decipheriv,\n  callback: TransformCallback\n): void {\n  this.push(Buffer.from(this[kHandle].final()));\n  callback();\n};\n\nexport function createCipheriv(\n  algorithm: string,\n  key: string | ArrayBuffer | ArrayBufferView | KeyObject | CryptoKey,\n  iv: string | ArrayBuffer | ArrayBufferView | null,\n  options: TransformOptions = {}\n): Cipheriv {\n  return new Cipheriv(algorithm, key, iv, options);\n}\n\nexport function createDecipheriv(\n  algorithm: string,\n  key: string | ArrayBuffer | ArrayBufferView | KeyObject | CryptoKey,\n  iv: string | ArrayBuffer | ArrayBufferView | null,\n  options: TransformOptions = {}\n): Decipheriv {\n  return new Decipheriv(algorithm, key, iv, options);\n}\n\n// ======================================================================================\n\ninterface HashOptions {\n  padding?: number;\n  oaepHash?: string;\n  oaepLabel?: string | ArrayBuffer | ArrayBufferView;\n  encoding?: string;\n}\n\nexport const kRsaPkcs1Padding = 1;\nexport const kRsaNoPadding = 3;\nexport const kRsaPkcs1OaepPadding = 4;\n\nfunction getPaddingAndHash(options: HashOptions): PublicPrivateCipherOptions {\n  const {\n    padding = kRsaPkcs1Padding,\n    oaepHash = 'sha256',\n    oaepLabel,\n    encoding = 'utf8',\n  } = options;\n\n  validateNumber(padding, 'options.padding');\n  validateString(oaepHash, 'options.oaepHash');\n\n  let label: ArrayBufferView | ArrayBuffer | undefined;\n  if (oaepLabel !== undefined) {\n    if (typeof oaepLabel === 'string') {\n      label = Buffer.from(oaepLabel, encoding);\n    } else if (isAnyArrayBuffer(oaepLabel) || isArrayBufferView(oaepLabel)) {\n      label = oaepLabel;\n    } else {\n      throw new ERR_INVALID_ARG_TYPE(\n        'options.oaepLabel',\n        ['string', 'Buffer', 'TypedArray', 'DataView', 'ArrayBuffer'],\n        oaepLabel\n      );\n    }\n  }\n\n  return { padding, oaepHash, oaepLabel: label };\n}\n\nexport function privateEncrypt(\n  privateKey: CreateAsymmetricKeyOptions | KeyObject | CryptoKey | undefined,\n  buffer: string | ArrayBufferView | ArrayBuffer | undefined\n): Buffer {\n  if (privateKey === undefined) {\n    throw new ERR_MISSING_ARGS('privateKey');\n  }\n  if (buffer === undefined) {\n    throw new ERR_MISSING_ARGS('buffer');\n  }\n  const key = ((): CryptoKey => {\n    if (privateKey instanceof CryptoKey) {\n      if (privateKey.type !== 'private') {\n        throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(\n          privateKey.type,\n          'private'\n        );\n      }\n      return privateKey;\n    } else if (isKeyObject(privateKey)) {\n      if (privateKey.type !== 'private') {\n        throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(\n          privateKey.type,\n          'private'\n        );\n      }\n      return getKeyObjectHandle(privateKey);\n    } else {\n      const pvtKey = createPrivateKey(privateKey);\n      return getKeyObjectHandle(pvtKey);\n    }\n  })();\n  if (typeof buffer === 'string') {\n    const { encoding = 'utf8' } = privateKey as { encoding?: string };\n    buffer = Buffer.from(buffer, encoding);\n  }\n\n  return Buffer.from(\n    cryptoImpl.privateEncrypt(\n      key,\n      buffer,\n      getPaddingAndHash(privateKey as HashOptions)\n    )\n  );\n}\n\nexport function privateDecrypt(\n  privateKey: CreateAsymmetricKeyOptions | KeyData,\n  buffer: string | ArrayBufferView | ArrayBuffer\n): Buffer {\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  if (privateKey === undefined) {\n    throw new ERR_MISSING_ARGS('privateKey');\n  }\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  if (buffer === undefined) {\n    throw new ERR_MISSING_ARGS('buffer');\n  }\n  const key = ((): CryptoKey => {\n    if (privateKey instanceof CryptoKey) {\n      if (privateKey.type !== 'private') {\n        throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(\n          privateKey.type,\n          'private'\n        );\n      }\n      return privateKey;\n    } else if (isKeyObject(privateKey)) {\n      if (privateKey.type !== 'private') {\n        throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(\n          privateKey.type,\n          'private'\n        );\n      }\n      return getKeyObjectHandle(privateKey);\n    } else {\n      const pvtKey = createPrivateKey(privateKey as CreateAsymmetricKeyOptions);\n      return getKeyObjectHandle(pvtKey);\n    }\n  })();\n  if (typeof buffer === 'string') {\n    const { encoding = 'utf8' } = privateKey as { encoding?: string };\n    buffer = Buffer.from(buffer, encoding);\n  }\n\n  return Buffer.from(\n    cryptoImpl.privateDecrypt(\n      key,\n      buffer,\n      getPaddingAndHash(privateKey as HashOptions)\n    )\n  );\n}\n\nexport function publicEncrypt(\n  key: CreateAsymmetricKeyOptions | KeyData,\n  buffer: string | ArrayBufferView | ArrayBuffer\n): Buffer {\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  if (key === undefined) {\n    throw new ERR_MISSING_ARGS('key');\n  }\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  if (buffer === undefined) {\n    throw new ERR_MISSING_ARGS('buffer');\n  }\n  const pkey = ((): CryptoKey => {\n    if (key instanceof CryptoKey) {\n      if (key.type !== 'public') {\n        throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'public');\n      }\n      return key;\n    } else if (isKeyObject(key)) {\n      if (key.type !== 'public') {\n        throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'public');\n      }\n      return getKeyObjectHandle(key);\n    } else {\n      const pubKey = createPublicKey(key as CreateAsymmetricKeyOptions);\n      return getKeyObjectHandle(pubKey);\n    }\n  })();\n  if (typeof buffer === 'string') {\n    const { encoding = 'utf8' } = key as { encoding?: string };\n    buffer = Buffer.from(buffer, encoding);\n  }\n\n  return Buffer.from(\n    cryptoImpl.publicEncrypt(\n      pkey,\n      buffer,\n      getPaddingAndHash(key as HashOptions)\n    )\n  );\n}\n\nexport function publicDecrypt(\n  key: CreateAsymmetricKeyOptions | KeyData,\n  buffer: string | ArrayBufferView | ArrayBuffer\n): Buffer {\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  if (key === undefined) {\n    throw new ERR_MISSING_ARGS('key');\n  }\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  if (buffer === undefined) {\n    throw new ERR_MISSING_ARGS('buffer');\n  }\n  const pkey = ((): CryptoKey => {\n    if (key instanceof CryptoKey) {\n      if (key.type !== 'public') {\n        throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'public');\n      }\n      return key;\n    } else if (isKeyObject(key)) {\n      if (key.type !== 'public') {\n        throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'public');\n      }\n      return getKeyObjectHandle(key);\n    } else {\n      const pubKey = createPublicKey(key as CreateAsymmetricKeyOptions);\n      return getKeyObjectHandle(pubKey);\n    }\n  })();\n  if (typeof buffer === 'string') {\n    const { encoding = 'utf8' } = key as { encoding?: string };\n    buffer = Buffer.from(buffer, encoding);\n  }\n\n  return Buffer.from(\n    cryptoImpl.publicDecrypt(\n      pkey,\n      buffer,\n      getPaddingAndHash(key as HashOptions)\n    )\n  );\n}\n\nexport function getCipherInfo(\n  nameOrId: string | number,\n  options: GetCipherInfoOptions = {}\n): CipherInfo | undefined {\n  if (typeof nameOrId !== 'string' && typeof nameOrId !== 'number') {\n    throw new ERR_INVALID_ARG_TYPE('nameOrId', ['string', 'number'], nameOrId);\n  }\n\n  validateObject(options, 'options');\n\n  return cryptoImpl.getCipherInfo(nameOrId, options);\n}\n\nexport const getCiphers = cryptoImpl.getCiphers.bind(cryptoImpl);\n"
  },
  {
    "path": "src/node/internal/crypto_dh.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { Buffer } from 'node-internal:internal_buffer';\n\nimport { default as cryptoImpl, type ECDHFormat } from 'node-internal:crypto';\ntype ArrayLike = cryptoImpl.ArrayLike;\n\nimport {\n  isKeyObject,\n  getKeyObjectHandle,\n  type PrivateKeyObject,\n  type PublicKeyObject,\n} from 'node-internal:crypto_keys';\n\nimport {\n  ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY,\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_ARG_VALUE,\n} from 'node-internal:internal_errors';\n\nimport {\n  validateInt32,\n  validateObject,\n  validateOneOf,\n  validateString,\n} from 'node-internal:validators';\n\nimport {\n  isArrayBufferView,\n  isAnyArrayBuffer,\n} from 'node-internal:internal_types';\n\nimport {\n  getArrayBufferOrView,\n  toBuf,\n  kHandle,\n} from 'node-internal:crypto_util';\n\nconst DH_GENERATOR = 2;\n\ndeclare class SharedDiffieHellman {\n  generateKeys: typeof dhGenerateKeys;\n  computeSecret: typeof dhComputeSecret;\n  getPrime: typeof dhGetPrime;\n  getGenerator: typeof dhGetGenerator;\n  getPublicKey: typeof dhGetPublicKey;\n  getPrivateKey: typeof dhGetPrivateKey;\n  setPrivateKey: typeof dhSetPrivateKey;\n  setPublicKey: typeof dhSetPublicKey;\n}\n\ndeclare class DiffieHellman extends SharedDiffieHellman {\n  [kHandle]: cryptoImpl.DiffieHellmanHandle;\n\n  constructor(\n    sizeOrKey: number | ArrayLike,\n    keyEncoding?: number | string,\n    generator?: number | ArrayLike,\n    genEncoding?: string\n  );\n}\n\nfunction DiffieHellman(\n  this: unknown,\n  sizeOrKey: number | ArrayLike,\n  keyEncoding?: number | string,\n  generator?: number | ArrayLike,\n  genEncoding?: string\n): DiffieHellman {\n  if (!(this instanceof DiffieHellman)) {\n    return new DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding);\n  }\n  if (\n    typeof sizeOrKey !== 'number' &&\n    typeof sizeOrKey !== 'string' &&\n    !isArrayBufferView(sizeOrKey) &&\n    !isAnyArrayBuffer(sizeOrKey)\n  ) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'sizeOrKey',\n      ['number', 'string', 'ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],\n      sizeOrKey\n    );\n  }\n\n  // Sizes < 0 don't make sense but they _are_ accepted (and subsequently\n  // rejected with ERR_OSSL_BN_BITS_TOO_SMALL) by OpenSSL. The glue code\n  // in node_crypto.cc accepts values that are IsInt32() for that reason\n  // and that's why we do that here too.\n  if (typeof sizeOrKey === 'number') validateInt32(sizeOrKey, 'sizeOrKey');\n\n  if (\n    keyEncoding &&\n    keyEncoding !== 'buffer' &&\n    !Buffer.isEncoding(keyEncoding)\n  ) {\n    genEncoding = generator as string;\n    generator = keyEncoding;\n    keyEncoding = 'utf-8'; // default encoding\n  }\n\n  keyEncoding ??= 'utf-8';\n  genEncoding ??= 'utf-8';\n\n  if (typeof sizeOrKey !== 'number')\n    sizeOrKey = toBuf(sizeOrKey, keyEncoding as string);\n\n  if (!generator) {\n    generator = DH_GENERATOR;\n  } else if (typeof generator === 'number') {\n    validateInt32(generator, 'generator');\n  } else if (typeof generator === 'string') {\n    generator = toBuf(generator, genEncoding);\n  } else if (!isArrayBufferView(generator) && !isAnyArrayBuffer(generator)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'generator',\n      ['number', 'string', 'ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],\n      generator\n    );\n  }\n\n  this[kHandle] = new cryptoImpl.DiffieHellmanHandle(sizeOrKey, generator);\n  Object.defineProperty(DiffieHellman.prototype, 'verifyError', {\n    get: function (this: DiffieHellman) {\n      return this[kHandle].getVerifyError();\n    },\n    configurable: true,\n    enumerable: true,\n  });\n  return this;\n}\n\ndeclare class DiffieHellmanGroup extends SharedDiffieHellman {\n  [kHandle]: cryptoImpl.DiffieHellmanHandle;\n  constructor(name: string);\n}\n\nfunction DiffieHellmanGroup(this: unknown, name: string): DiffieHellmanGroup {\n  if (!(this instanceof DiffieHellmanGroup)) {\n    return new DiffieHellmanGroup(name);\n  }\n\n  // The C++-based handle is shared between both classes, so DiffieHellmanGroupHandle() is merely\n  // a different constructor for a DiffieHellmanHandle.\n  this[kHandle] = cryptoImpl.DiffieHellmanGroupHandle(name);\n  Object.defineProperty(DiffieHellmanGroup.prototype, 'verifyError', {\n    get: function (this: DiffieHellmanGroup): number {\n      return this[kHandle].getVerifyError();\n    },\n    configurable: true,\n    enumerable: true,\n  });\n  return this;\n}\n\nDiffieHellmanGroup.prototype.generateKeys =\n  DiffieHellman.prototype.generateKeys = dhGenerateKeys;\nDiffieHellmanGroup.prototype.computeSecret =\n  DiffieHellman.prototype.computeSecret = dhComputeSecret;\nDiffieHellmanGroup.prototype.getPrime = DiffieHellman.prototype.getPrime =\n  dhGetPrime;\nDiffieHellmanGroup.prototype.getGenerator =\n  DiffieHellman.prototype.getGenerator = dhGetGenerator;\nDiffieHellmanGroup.prototype.getPublicKey =\n  DiffieHellman.prototype.getPublicKey = dhGetPublicKey;\nDiffieHellmanGroup.prototype.getPrivateKey =\n  DiffieHellman.prototype.getPrivateKey = dhGetPrivateKey;\nDiffieHellman.prototype.setPublicKey = dhSetPublicKey;\nDiffieHellman.prototype.setPrivateKey = dhSetPrivateKey;\n\nexport { DiffieHellman, DiffieHellmanGroup };\n\ntype DHLike = DiffieHellman | DiffieHellmanGroup;\nfunction dhGenerateKeys(this: DHLike, encoding?: string): Buffer | string {\n  const keys = this[kHandle].generateKeys();\n  return encode(keys, encoding);\n}\n\nfunction dhComputeSecret(\n  this: DHLike,\n  key: ArrayLike,\n  inEnc?: string,\n  outEnc?: string\n): Buffer | string {\n  key = getArrayBufferOrView(key, 'key', inEnc);\n  const ret = this[kHandle].computeSecret(key);\n  if (typeof ret === 'string') throw new ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY();\n  return encode(ret, outEnc);\n}\n\nfunction dhGetPrime(this: DHLike, encoding?: string): Buffer | string {\n  const prime = this[kHandle].getPrime();\n  return encode(prime, encoding);\n}\n\nfunction dhGetGenerator(this: DHLike, encoding?: string): Buffer | string {\n  const generator = this[kHandle].getGenerator();\n  return encode(generator, encoding);\n}\n\nfunction dhGetPublicKey(this: DHLike, encoding?: string): Buffer | string {\n  const key = this[kHandle].getPublicKey();\n  return encode(key, encoding);\n}\n\nfunction dhGetPrivateKey(this: DHLike, encoding?: string): Buffer | string {\n  const key = this[kHandle].getPrivateKey();\n  return encode(key, encoding);\n}\n\nfunction dhSetPublicKey(\n  this: DiffieHellman,\n  key: ArrayLike,\n  encoding?: string\n): DiffieHellman {\n  key = getArrayBufferOrView(key, 'key', encoding);\n  this[kHandle].setPublicKey(key);\n  return this;\n}\n\nfunction dhSetPrivateKey(\n  this: DiffieHellman,\n  key: ArrayLike,\n  encoding?: string\n): DiffieHellman {\n  key = getArrayBufferOrView(key, 'key', encoding);\n  this[kHandle].setPrivateKey(key);\n  return this;\n}\n\nfunction encode(buffer: ArrayBuffer, encoding?: string): Buffer | string {\n  if (encoding && encoding !== 'buffer')\n    return Buffer.from(buffer).toString(encoding);\n  return Buffer.from(buffer);\n}\n\nexport function createDiffieHellman(\n  sizeOrKey: number | ArrayLike,\n  keyEncoding?: number | string,\n  generator?: number | ArrayLike,\n  genEncoding?: string\n): DiffieHellman {\n  return new DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding);\n}\n\nexport function createDiffieHellmanGroup(name: string): DiffieHellmanGroup {\n  return new DiffieHellmanGroup(name);\n}\n\nexport function getDiffieHellman(name: string): DiffieHellmanGroup {\n  return createDiffieHellmanGroup(name);\n}\n\nexport interface DiffieHellmanKeyPair {\n  publicKey: PublicKeyObject;\n  privateKey: PrivateKeyObject;\n}\n\nexport function diffieHellman(options: DiffieHellmanKeyPair): Buffer {\n  validateObject(options, 'options');\n  const { publicKey, privateKey } = options;\n  if (!isKeyObject(publicKey)) {\n    throw new ERR_INVALID_ARG_TYPE('options.publicKey', 'KeyObject', publicKey);\n  }\n  if (!isKeyObject(privateKey)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'options.privateKey',\n      'KeyObject',\n      privateKey\n    );\n  }\n  if (publicKey.type !== 'public') {\n    throw new ERR_INVALID_ARG_TYPE(\n      'options.publicKey',\n      'public key',\n      publicKey\n    );\n  }\n  if (privateKey.type !== 'private') {\n    throw new ERR_INVALID_ARG_TYPE(\n      'options.privateKey',\n      'private key',\n      privateKey\n    );\n  }\n  if (publicKey.asymmetricKeyType !== privateKey.asymmetricKeyType) {\n    throw new ERR_INVALID_ARG_VALUE(\n      'options.publicKey.asymmetricKeyType',\n      publicKey.asymmetricKeyType,\n      'must equal privateKey.asymmetricKeyType'\n    );\n  }\n  const res = cryptoImpl.statelessDH(\n    getKeyObjectHandle(privateKey),\n    getKeyObjectHandle(publicKey)\n  );\n  return Buffer.from(res);\n}\n\n// =============================================================================\n\nexport interface ECDH {\n  [kHandle]: cryptoImpl.ECDHHandle;\n  computeSecret(\n    otherPublicKey: string | ArrayBufferView | ArrayBuffer,\n    inputEncoding?: string,\n    outputEncoding?: string\n  ): Buffer | string;\n  generateKeys(encoding?: string, format?: string): Buffer | string;\n  getPrivateKey(encoding?: string): Buffer | string;\n  getPublicKey(encoding?: string, format?: string): Buffer | string;\n  setPrivateKey(\n    key: string | ArrayBufferView | ArrayBuffer,\n    encoding?: string\n  ): void;\n}\n\nexport const ECDH = function (this: ECDH, curveName: string) {\n  if (!(this instanceof ECDH)) {\n    return new ECDH(curveName);\n  }\n  validateString(curveName, 'curveName');\n  this[kHandle] = new cryptoImpl.ECDHHandle(curveName);\n  return this;\n} as unknown as {\n  new (curveName: string): ECDH;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nECDH.prototype.computeSecret = function (\n  this: ECDH,\n  otherPublicKey: string | ArrayBufferView | ArrayBuffer,\n  inputEncoding?: string,\n  outputEncoding?: string\n): Buffer | string {\n  if (typeof otherPublicKey === 'string') {\n    otherPublicKey = Buffer.from(otherPublicKey, inputEncoding);\n  }\n  if (!isArrayBufferView(otherPublicKey)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'otherPublicKey',\n      ['string', 'Buffer', 'TypedArray', 'DataView'],\n      otherPublicKey\n    );\n  }\n  if (inputEncoding != null) {\n    validateString(inputEncoding, 'inputEncoding');\n  }\n  if (outputEncoding != null) {\n    validateString(outputEncoding, 'outputEncoding');\n  }\n  const ret = this[kHandle].computeSecret(otherPublicKey);\n  if (typeof outputEncoding === 'string') {\n    return Buffer.from(ret).toString(outputEncoding);\n  }\n  return Buffer.from(ret);\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nECDH.prototype.generateKeys = function (\n  this: ECDH,\n  encoding?: string,\n  format?: string\n): Buffer | string {\n  if (encoding != null) {\n    validateString(encoding, 'encoding');\n  }\n  if (format != null) {\n    validateOneOf(format, 'format', ['compressed', 'uncompressed', 'hybrid']);\n  }\n  this[kHandle].generateKeys();\n  return this.getPublicKey(encoding, format);\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nECDH.prototype.getPrivateKey = function (\n  this: ECDH,\n  encoding?: string\n): Buffer | string {\n  if (encoding != null) {\n    validateString(encoding, 'encoding');\n  }\n  const pvt = this[kHandle].getPrivateKey();\n  if (typeof encoding === 'string') {\n    return Buffer.from(pvt).toString(encoding);\n  }\n  return Buffer.from(pvt);\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nECDH.prototype.getPublicKey = function (\n  this: ECDH,\n  encoding?: string,\n  format: ECDHFormat = 'uncompressed'\n): Buffer | string {\n  if (encoding != null) {\n    validateString(encoding, 'encoding');\n  }\n  validateOneOf(format, 'format', ['compressed', 'uncompressed', 'hybrid']);\n  const pub = this[kHandle].getPublicKey(format);\n  if (typeof encoding === 'string') {\n    return Buffer.from(pub).toString(encoding);\n  }\n  return Buffer.from(pub);\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nECDH.prototype.setPrivateKey = function (\n  this: ECDH,\n  key: string | ArrayBufferView | ArrayBuffer,\n  encoding?: string\n): void {\n  if (encoding != null) {\n    validateString(encoding, 'encoding');\n  }\n  if (typeof key === 'string') {\n    key = Buffer.from(key, encoding);\n  }\n  if (!isArrayBufferView(key)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'key',\n      ['string', 'Buffer', 'TypedArray', 'DataView'],\n      key\n    );\n  }\n  this[kHandle].setPrivateKey(key);\n};\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access\n(ECDH as any).convertKey = function (\n  key: string | ArrayBufferView | ArrayBuffer,\n  curve: string,\n  inputEncoding?: string,\n  outputEncoding?: string,\n  format: ECDHFormat = 'uncompressed'\n): Buffer | string {\n  if (typeof key === 'string') {\n    key = Buffer.from(key, inputEncoding);\n  }\n  if (!isArrayBufferView(key)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'key',\n      ['string', 'Buffer', 'TypedArray', 'DataView'],\n      key\n    );\n  }\n  validateString(curve, 'curve');\n  if (inputEncoding != null) {\n    validateString(inputEncoding, 'inputEncoding');\n  }\n  if (outputEncoding != null) {\n    validateString(outputEncoding, 'outputEncoding');\n  }\n  validateOneOf(format, 'format', ['compressed', 'uncompressed', 'hybrid']);\n  const ret = cryptoImpl.ECDHHandle.convertKey(key, curve, format);\n  if (typeof outputEncoding === 'string') {\n    return Buffer.from(ret).toString(outputEncoding);\n  }\n  return Buffer.from(ret);\n};\n\nexport function createECDH(curveName: string): ECDH {\n  return new ECDH(curveName);\n}\n"
  },
  {
    "path": "src/node/internal/crypto_hash.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { default as cryptoImpl } from 'node-internal:crypto';\ntype ArrayLike = cryptoImpl.ArrayLike;\n\nimport {\n  kFinalized,\n  kHandle,\n  kState,\n  getArrayBufferOrView,\n  getStringOption,\n} from 'node-internal:crypto_util';\n\nimport { Buffer } from 'node-internal:internal_buffer';\n\nimport {\n  ERR_CRYPTO_HASH_FINALIZED,\n  ERR_CRYPTO_HASH_UPDATE_FAILED,\n  ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,\n  ERR_INVALID_ARG_TYPE,\n} from 'node-internal:internal_errors';\n\nimport { validateString, validateUint32 } from 'node-internal:validators';\n\nimport {\n  isArrayBufferView,\n  isCryptoKey,\n  isAnyArrayBuffer,\n} from 'node-internal:internal_types';\n\nimport {\n  Transform,\n  type TransformOptions,\n  type TransformCallback,\n} from 'node-internal:streams_transform';\n\nimport { KeyObject } from 'node-internal:crypto_keys';\n\nexport interface HashOptions extends TransformOptions {\n  outputLength?: number;\n}\n\ninterface _kState {\n  [kFinalized]: boolean;\n}\n\ndeclare class Hash extends Transform {\n  [kHandle]: cryptoImpl.HashHandle;\n  [kState]: _kState;\n\n  constructor(algorithm: string | cryptoImpl.HashHandle, options?: HashOptions);\n\n  copy(options?: HashOptions): Hash;\n  update(\n    data: string | Buffer | ArrayBufferView,\n    encoding?: string\n  ): Hash | Hmac;\n  digest(outputEncoding?: string): Buffer | string;\n}\n\n// These helper functions are needed because the constructors can\n// use new, in which case V8 cannot inline the recursive constructor call\nexport function createHash(algorithm: string, options?: HashOptions): Hash {\n  return new Hash(algorithm, options);\n}\n\nfunction Hash(\n  this: unknown,\n  algorithm: string | cryptoImpl.HashHandle,\n  options?: HashOptions\n): Hash {\n  if (!(this instanceof Hash)) {\n    return new Hash(algorithm, options);\n  }\n\n  const xofLen = typeof options === 'object' ? options.outputLength : undefined;\n  if (xofLen !== undefined) validateUint32(xofLen, 'options.outputLength');\n  if (algorithm instanceof cryptoImpl.HashHandle) {\n    this[kHandle] = algorithm.copy(xofLen as number);\n  } else {\n    validateString(algorithm, 'algorithm');\n    this[kHandle] = new cryptoImpl.HashHandle(algorithm, xofLen as number);\n  }\n  this[kState] = {\n    [kFinalized]: false,\n  };\n\n  Transform.call(this, options);\n  return this;\n}\n\nObject.setPrototypeOf(Hash.prototype, Transform.prototype);\nObject.setPrototypeOf(Hash, Transform);\n\nHash.prototype.copy = function (this: Hash, options?: HashOptions): Hash {\n  const state = this[kState];\n  if (state[kFinalized]) throw new ERR_CRYPTO_HASH_FINALIZED();\n\n  return new Hash(this[kHandle], options);\n};\n\nHash.prototype._transform = function (\n  this: Hash | Hmac,\n  chunk: string | Buffer | ArrayBufferView,\n  encoding: string,\n  callback: TransformCallback\n): void {\n  if (typeof chunk === 'string') {\n    chunk = Buffer.from(chunk, encoding);\n  }\n  this[kHandle].update(chunk);\n  callback();\n};\n\nHash.prototype._flush = function (\n  this: Hash | Hmac,\n  callback: TransformCallback\n): void {\n  this.push(Buffer.from(this[kHandle].digest()));\n  callback();\n};\n\nHash.prototype.update = function (\n  this: Hash | Hmac,\n  data: string | Buffer | ArrayBufferView,\n  encoding?: string\n): Hash | Hmac {\n  encoding ??= 'utf8';\n  if (encoding === 'buffer') {\n    encoding = undefined;\n  }\n\n  const state = this[kState];\n  if (state[kFinalized]) throw new ERR_CRYPTO_HASH_FINALIZED();\n\n  if (typeof data === 'string') {\n    data = Buffer.from(data, encoding);\n  } else if (!isArrayBufferView(data)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'data',\n      ['string', 'Buffer', 'TypedArray', 'DataView'],\n      data\n    );\n  }\n\n  if (!this[kHandle].update(data)) throw new ERR_CRYPTO_HASH_UPDATE_FAILED();\n  return this;\n};\n\nHash.prototype.digest = function (\n  this: Hash,\n  outputEncoding?: string\n): Buffer | string {\n  const state = this[kState];\n  if (state[kFinalized]) throw new ERR_CRYPTO_HASH_FINALIZED();\n\n  // Explicit conversion for backward compatibility.\n  const ret = Buffer.from(this[kHandle].digest());\n  state[kFinalized] = true;\n  if (outputEncoding !== undefined && outputEncoding !== 'buffer') {\n    return ret.toString(outputEncoding);\n  } else {\n    return ret;\n  }\n};\n\n///////////////////////////\n\ndeclare class Hmac extends Transform {\n  [kHandle]: cryptoImpl.HmacHandle;\n  [kState]: _kState;\n  constructor(\n    hmac: string,\n    key: ArrayLike | KeyObject | CryptoKey,\n    options?: TransformOptions\n  );\n  copy(options?: HashOptions): Hash;\n  update(\n    data: string | Buffer | ArrayBufferView,\n    encoding?: string\n  ): Hash | Hmac;\n  digest(outputEncoding?: string): Buffer | string;\n}\n\nexport function createHmac(\n  hmac: string,\n  key: CryptoKey,\n  options?: TransformOptions\n): Hmac {\n  return new Hmac(hmac, key, options);\n}\n\nfunction Hmac(\n  this: Hmac,\n  hmac: string,\n  key: CryptoKey,\n  options?: TransformOptions\n): Hmac {\n  if (!(this instanceof Hmac)) {\n    return new Hmac(hmac, key, options);\n  }\n  validateString(hmac, 'hmac');\n  const encoding = getStringOption(options, 'encoding');\n\n  if (key instanceof KeyObject) {\n    if (key.type !== 'secret') {\n      throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'secret');\n    }\n    this[kHandle] = new cryptoImpl.HmacHandle(hmac, key[kHandle]);\n  } else if (isCryptoKey(key)) {\n    if (key.type !== 'secret') {\n      throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'secret');\n    }\n    this[kHandle] = new cryptoImpl.HmacHandle(hmac, key);\n  } else if (\n    typeof key !== 'string' &&\n    !isArrayBufferView(key) &&\n    !isAnyArrayBuffer(key)\n  ) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'key',\n      [\n        'ArrayBuffer',\n        'Buffer',\n        'ArrayBufferView',\n        'string',\n        'KeyObject',\n        'CryptoKey',\n      ],\n      key\n    );\n  } else {\n    this[kHandle] = new cryptoImpl.HmacHandle(\n      hmac,\n      getArrayBufferOrView(key, 'key', encoding)\n    );\n  }\n\n  this[kState] = {\n    [kFinalized]: false,\n  };\n  Transform.call(this, options);\n  return this;\n}\nObject.setPrototypeOf(Hmac.prototype, Transform.prototype);\nObject.setPrototypeOf(Hmac, Transform);\n\n// eslint-disable-next-line @typescript-eslint/unbound-method\nHmac.prototype.update = Hash.prototype.update;\n\nHmac.prototype.digest = function (\n  this: Hmac,\n  outputEncoding?: string\n): Buffer | string {\n  const state = this[kState];\n  if (state[kFinalized]) {\n    return !outputEncoding || outputEncoding === 'buffer'\n      ? Buffer.from('')\n      : '';\n  }\n\n  // Explicit conversion for backward compatibility.\n  const ret = Buffer.from(this[kHandle].digest());\n  state[kFinalized] = true;\n  if (outputEncoding !== undefined && outputEncoding !== 'buffer') {\n    return ret.toString(outputEncoding);\n  } else {\n    return ret;\n  }\n};\n\n// eslint-disable-next-line @typescript-eslint/unbound-method\nHmac.prototype._flush = Hash.prototype._flush;\n// eslint-disable-next-line @typescript-eslint/unbound-method\nHmac.prototype._transform = Hash.prototype._transform;\n\nexport function hash(\n  algorithm: string,\n  data: string | ArrayBufferView,\n  outputEncoding: string = 'hex'\n): string | Buffer {\n  validateString(algorithm, 'algorithm');\n  validateString(outputEncoding, 'outputEncoding');\n\n  if (typeof data === 'string') {\n    const hash = createHash(algorithm);\n    hash.update(data, 'utf8');\n    return hash.digest(outputEncoding);\n  } else if (isArrayBufferView(data)) {\n    const hash = createHash(algorithm);\n    hash.update(data, 'utf8');\n    return hash.digest(outputEncoding);\n  }\n\n  throw new ERR_INVALID_ARG_TYPE(\n    'data',\n    ['string', 'Buffer', 'TypedArray', 'DataView'],\n    data\n  );\n}\n\nexport { Hash, Hmac };\n"
  },
  {
    "path": "src/node/internal/crypto_hkdf.ts",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { default as cryptoImpl } from 'node-internal:crypto';\n\nimport {\n  validateFunction,\n  validateInteger,\n  validateString,\n} from 'node-internal:validators';\n\nimport { KeyObject } from 'node-internal:crypto_keys';\n\ntype ArrayLike = cryptoImpl.ArrayLike;\n\nimport { kMaxLength } from 'node-internal:internal_buffer';\n\nimport { toBuf, validateByteSource } from 'node-internal:crypto_util';\n\nimport {\n  isAnyArrayBuffer,\n  isArrayBufferView,\n} from 'node-internal:internal_types';\n\nimport {\n  ERR_INVALID_ARG_TYPE,\n  ERR_OUT_OF_RANGE,\n} from 'node-internal:internal_errors';\n\nfunction validateParameters(\n  hash: string,\n  key: ArrayLike | KeyObject,\n  salt: ArrayLike,\n  info: ArrayLike,\n  length: number\n): {\n  hash: string;\n  key: ArrayLike;\n  salt: ArrayLike;\n  info: ArrayLike;\n  length: number;\n} {\n  if (key instanceof KeyObject) {\n    key = key.export() as ArrayLike;\n  }\n\n  validateString(hash, 'digest');\n  key = prepareKey(key as unknown as ArrayLike);\n  salt = validateByteSource(salt, 'salt');\n  info = validateByteSource(info, 'info');\n\n  validateInteger(length, 'length', 0, kMaxLength);\n\n  if (info.byteLength > 1024) {\n    throw new ERR_OUT_OF_RANGE(\n      'info',\n      'must not contain more than 1024 bytes',\n      info.byteLength\n    );\n  }\n\n  return {\n    hash,\n    key,\n    salt,\n    info,\n    length,\n  };\n}\n\nfunction prepareKey(key: ArrayLike): ArrayLike {\n  key = toBuf(key);\n\n  if (!isAnyArrayBuffer(key) && !isArrayBufferView(key)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'ikm',\n      [\n        'string',\n        'SecretKeyObject',\n        'ArrayBuffer',\n        'TypedArray',\n        'DataView',\n        'Buffer',\n      ],\n      key\n    );\n  }\n\n  return key;\n}\n\nexport function hkdf(\n  hash: string,\n  key: ArrayLike | KeyObject,\n  salt: ArrayLike,\n  info: ArrayLike,\n  length: number,\n  callback: (err: Error | null, derivedKey?: ArrayBuffer) => void\n): void {\n  ({ hash, key, salt, info, length } = validateParameters(\n    hash,\n    key,\n    salt,\n    info,\n    length\n  ));\n\n  validateFunction(callback, 'callback');\n\n  new Promise<ArrayBuffer>((res, rej) => {\n    try {\n      res(cryptoImpl.getHkdf(hash, key, salt, info, length));\n    } catch (err) {\n      rej(err as Error);\n    }\n  }).then(\n    (val: ArrayBuffer): void => {\n      callback(null, val);\n    },\n    (err: unknown): void => {\n      callback(err);\n    }\n  );\n}\n\nexport function hkdfSync(\n  hash: string,\n  key: ArrayLike | KeyObject,\n  salt: ArrayLike,\n  info: ArrayLike,\n  length: number\n): ArrayBuffer {\n  ({ hash, key, salt, info, length } = validateParameters(\n    hash,\n    key,\n    salt,\n    info,\n    length\n  ));\n\n  return cryptoImpl.getHkdf(hash, key, salt, info, length);\n}\n"
  },
  {
    "path": "src/node/internal/crypto_keys.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT ORs\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { Buffer } from 'node-internal:internal_buffer';\n\nimport {\n  type KeyData,\n  type KeyObjectType,\n  type KeyExportResult,\n  type SecretKeyType,\n  type SecretKeyExportOptions,\n  type PublicKeyExportOptions,\n  type PrivateKeyExportOptions,\n  type ExportOptions,\n  type AsymmetricKeyDetails,\n  type AsymmetricKeyType,\n  type CreateAsymmetricKeyOptions,\n  type GenerateKeyOptions,\n  type GenerateKeyPairOptions,\n  type InnerExportOptions,\n  type InnerCreateAsymmetricKeyOptions,\n  type JsonWebKey,\n  default as cryptoImpl,\n} from 'node-internal:crypto';\n\nimport {\n  arrayBufferToUnsignedBigInt,\n  getArrayBufferOrView,\n  kHandle,\n} from 'node-internal:crypto_util';\n\nimport {\n  isAnyArrayBuffer,\n  isArrayBuffer,\n  isArrayBufferView,\n  isUint8Array,\n} from 'node-internal:internal_types';\n\nimport {\n  ERR_INCOMPATIBLE_OPTION_PAIR,\n  ERR_INVALID_ARG_TYPE,\n  ERR_METHOD_NOT_IMPLEMENTED,\n  ERR_MISSING_OPTION,\n} from 'node-internal:internal_errors';\n\nimport {\n  validateFunction,\n  validateInteger,\n  validateObject,\n  validateOneOf,\n  validateString,\n  validateInt32,\n  validateUint32,\n} from 'node-internal:validators';\n\nimport { inspect } from 'node-internal:internal_inspect';\nimport { randomBytes } from 'node-internal:crypto_random';\nconst kInspect = inspect.custom;\n\nconst kCustomPromisifyArgsSymbol = Symbol.for(\n  'nodejs.util.promisify.custom.args'\n);\n\n// Key input contexts.\nexport const KeyContext = {\n  kConsumePublic: 'kConsumePublic',\n  kConsumePrivate: 'kConsumePrivate',\n  kCreatePublic: 'kCreatePublic',\n  kCreatePrivate: 'kCreatePrivate',\n};\n\n// In Node.js, the definition of KeyObject is a bit complicated because\n// KeyObject instances in Node.js can be transferred via postMessage() and\n// structuredClone(), etc, allowing instances to be shared across multiple\n// worker threads. We do not implement that model so we're essentially\n// re-implementing the Node.js API here instead of just taking their code.\n// Also in Node.js, CryptoKey is layered on top of KeyObject since KeyObject\n// existed first. We're, however, going to layer our KeyObject on top of\n// CryptoKey with a few augmentations.\n\nfunction isStringOrBuffer(val: unknown): val is string | Buffer {\n  return (\n    typeof val === 'string' || isArrayBufferView(val) || isAnyArrayBuffer(val)\n  );\n}\n\nfunction validateExportOptions(\n  options: ExportOptions,\n  type: KeyObjectType,\n  name = 'options'\n): asserts options is ExportOptions {\n  validateObject(options, name);\n  // Yes, converting to any is a bit of a cheat, but it allows us to check\n  // each option individually without having to do a bunch of type guards.\n  const opts = options;\n  if (opts.format !== undefined) {\n    validateString(opts.format, `${name}.format`);\n  } else {\n    options.format = 'buffer';\n  }\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  if ('type' in opts && opts.type !== undefined) {\n    validateString(opts.type, `${name}.type`);\n  }\n  if (type === 'private') {\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    if ('cipher' in opts && opts.cipher !== undefined) {\n      validateString(opts.cipher, `${name}.cipher`);\n      if (typeof opts.passphrase === 'string') {\n        opts.passphrase = Buffer.from(opts.passphrase, opts.encoding);\n      }\n      if (!isUint8Array(opts.passphrase)) {\n        throw new ERR_INVALID_ARG_TYPE(\n          `${name}.passphrase`,\n          ['string', 'Uint8Array'],\n          opts.passphrase\n        );\n      }\n    }\n  }\n}\n\nexport abstract class KeyObject {\n  [kHandle]: CryptoKey;\n\n  constructor() {\n    // KeyObjects cannot be created with new ... use one of the\n    // create or generate methods, or use from to get from a\n    // CryptoKey.\n    throw new Error('Illegal constructor');\n  }\n\n  static from(key: CryptoKey): KeyObject {\n    if (!(key instanceof CryptoKey)) {\n      throw new ERR_INVALID_ARG_TYPE('key', 'CryptoKey', key);\n    }\n    switch (key.type) {\n      case 'secret':\n        return Reflect.construct(\n          function (this: SecretKeyObject): void {\n            this[kHandle] = key;\n          },\n          [],\n          SecretKeyObject\n        ) as KeyObject;\n      case 'private':\n        return Reflect.construct(\n          function (this: PrivateKeyObject): void {\n            this[kHandle] = key;\n          },\n          [],\n          PrivateKeyObject\n        ) as KeyObject;\n      case 'public':\n        return Reflect.construct(\n          function (this: PublicKeyObject): void {\n            this[kHandle] = key;\n          },\n          [],\n          PublicKeyObject\n        ) as KeyObject;\n    }\n  }\n\n  export(options: ExportOptions = {}): KeyExportResult {\n    validateObject(options, 'options');\n\n    validateExportOptions(options, this.type);\n\n    const ret = cryptoImpl.exportKey(\n      this[kHandle],\n      options as InnerExportOptions\n    );\n    if (typeof ret === 'string') return ret;\n    if (isUint8Array(ret)) {\n      return Buffer.from(\n        (ret as Uint8Array).buffer,\n        ret.byteOffset,\n        ret.byteLength\n      ) as KeyExportResult;\n    } else if (isArrayBuffer(ret)) {\n      return Buffer.from(\n        ret as ArrayBuffer,\n        0,\n        (ret as ArrayBuffer).byteLength\n      );\n    }\n    return ret;\n  }\n\n  equals(otherKeyObject: KeyObject): boolean {\n    if (this === otherKeyObject || this[kHandle] === otherKeyObject[kHandle])\n      return true;\n    if (this.type !== otherKeyObject.type) return false;\n    if (!(otherKeyObject[kHandle] instanceof CryptoKey)) {\n      throw new ERR_INVALID_ARG_TYPE(\n        'otherKeyObject',\n        'KeyObject',\n        otherKeyObject\n      );\n    }\n    return cryptoImpl.equals(this[kHandle], otherKeyObject[kHandle]);\n  }\n\n  abstract get type(): KeyObjectType;\n\n  get [Symbol.toStringTag](): string {\n    return 'KeyObject';\n  }\n}\n\nexport function isKeyObject(obj: unknown): obj is KeyObject {\n  return obj != null && typeof obj === 'object' && kHandle in obj;\n}\n\nexport function getKeyObjectHandle(obj: KeyObject): CryptoKey {\n  return obj[kHandle];\n}\n\nabstract class AsymmetricKeyObject extends KeyObject {\n  get asymmetricKeyDetails(): AsymmetricKeyDetails {\n    const detail = cryptoImpl.getAsymmetricKeyDetail(this[kHandle]);\n    if (isArrayBuffer(detail.publicExponent)) {\n      detail.publicExponent = arrayBufferToUnsignedBigInt(\n        detail.publicExponent\n      );\n    }\n    return detail;\n  }\n\n  get asymmetricKeyType(): AsymmetricKeyType {\n    return cryptoImpl.getAsymmetricKeyType(this[kHandle]);\n  }\n\n  toCryptoKey(): void {\n    // TODO(soon): Implement the toCryptoKey API (added in Node.js 23.0.0)\n    throw new ERR_METHOD_NOT_IMPLEMENTED('toCryptoKey');\n  }\n\n  [kInspect](\n    depth: number,\n    options: {\n      depth?: number;\n    }\n  ): string | this {\n    if (depth < 0) return this;\n\n    const opts = {\n      ...options,\n      depth: options.depth == null ? null : options.depth - 1,\n    };\n\n    return `${this.constructor.name} ${inspect(\n      {\n        type: this.asymmetricKeyType,\n        details: this.asymmetricKeyDetails,\n      },\n      opts\n    )}`;\n  }\n}\n\nexport class PublicKeyObject extends AsymmetricKeyObject {\n  override export(options?: PublicKeyExportOptions): KeyExportResult {\n    return super.export(options);\n  }\n\n  get type(): KeyObjectType {\n    return 'public';\n  }\n}\n\nexport class PrivateKeyObject extends AsymmetricKeyObject {\n  override export(options?: PrivateKeyExportOptions): KeyExportResult {\n    return super.export(options);\n  }\n\n  get type(): KeyObjectType {\n    return 'private';\n  }\n}\n\nexport class SecretKeyObject extends KeyObject {\n  get symmetricKeySize(): number {\n    return (this[kHandle].algorithm as unknown as string).length | 0;\n  }\n\n  override export(options?: SecretKeyExportOptions): KeyExportResult {\n    return super.export(options);\n  }\n\n  get type(): KeyObjectType {\n    return 'secret';\n  }\n\n  [kInspect](depth: number, options: { depth?: number }): string | this {\n    if (depth < 0) return this;\n\n    const opts = {\n      ...options,\n      depth: options.depth == null ? null : options.depth - 1,\n    };\n\n    return `${this.constructor.name} ${inspect(\n      {\n        size: this.symmetricKeySize,\n      },\n      opts\n    )}`;\n  }\n}\n\ntype ValidateKeyDataOptions = {\n  allowObject?: boolean;\n};\nfunction validateKeyData(\n  key: unknown,\n  name: string,\n  options: ValidateKeyDataOptions = {\n    allowObject: false,\n  }\n): void {\n  if (\n    key == null ||\n    (typeof key !== 'string' &&\n      options.allowObject &&\n      typeof key !== 'object' &&\n      !isArrayBufferView(key) &&\n      !isAnyArrayBuffer(key))\n  ) {\n    const expected = ['string', 'ArrayBuffer', 'TypedArray', 'DataView'];\n    if (options.allowObject) expected.push('object');\n    throw new ERR_INVALID_ARG_TYPE(name, expected, key);\n  }\n}\n\nexport function createSecretKey(\n  key: string,\n  encoding?: string\n): SecretKeyObject;\nexport function createSecretKey(\n  key: ArrayBuffer | ArrayBufferView\n): SecretKeyObject;\nexport function createSecretKey(\n  key: KeyData,\n  encoding?: string\n): SecretKeyObject {\n  validateKeyData(key, 'key');\n  if (typeof key === 'string') {\n    key = Buffer.from(key, encoding);\n  } else if (isAnyArrayBuffer(key)) {\n    // We want the key to be a copy of the original buffer, not a view.\n    key = Buffer.from(new Uint8Array(key));\n  } else if (isArrayBufferView(key)) {\n    // We want the key to be a copy of the original buffer, not a view.\n    key = Buffer.from(key as Buffer);\n  }\n\n  // Node.js requires that the key data be less than 2 ** 32 - 1,\n  // however it enforces the limit silently... returning an empty\n  // key as opposed to throwing an error. Silly Node.js.\n  // But, it's all good because our runtime limits the size of\n  // buffer allocations to a strict maximum of 2,147,483,646 ... way\n  // more than necessary... no one actually *needs* a 17,179,869,168\n  // bit secret key do they? Good luck to the poor soul who tries.\n\n  return KeyObject.from(cryptoImpl.createSecretKey(key)) as SecretKeyObject;\n}\n\nexport function prepareAsymmetricKey(\n  key: CreateAsymmetricKeyOptions | null | undefined,\n  ctx: (typeof KeyContext)[keyof typeof KeyContext]\n): InnerCreateAsymmetricKeyOptions {\n  // Safety check... key should not be undefined or null here.\n  if (key == null) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'key',\n      ['ArrayBuffer', 'Buffer', 'TypedArray', 'DataView', 'string', 'object'],\n      key\n    );\n  }\n\n  let normalized: CreateAsymmetricKeyOptions;\n  if (\n    isStringOrBuffer(key) ||\n    isAnyArrayBuffer(key) ||\n    isArrayBufferView(key)\n  ) {\n    normalized = { key, format: 'pem' } as CreateAsymmetricKeyOptions;\n  } else {\n    normalized = key;\n  }\n\n  const {\n    key: data,\n    encoding = 'utf8',\n    format = 'pem',\n    type,\n    passphrase,\n  } = normalized;\n\n  // The key data must be specified. The value has to be one of either a\n  // string, an ArrayBuffer, an ArrayBufferView, or a JWK object.\n  if (data == null || isKeyObject(data) || data instanceof CryptoKey) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'options.key',\n      ['ArrayBuffer', 'Buffer', 'TypedArray', 'DataView', 'string', 'object'],\n      data\n    );\n  }\n\n  if (isStringOrBuffer(data)) {\n    // When the key data is a string or buffer, the format must be\n    // one of either pem or der.\n    validateOneOf(format, 'format', ['pem', 'der']);\n    if (type !== undefined) {\n      if (ctx == KeyContext.kCreatePrivate) {\n        // When the key data is a string or buffer, the type must be\n        // one of either pkcs1, pkcs8, or sec1.\n        validateOneOf(type, 'type', ['pkcs1', 'pkcs8', 'sec1']);\n      } else if (ctx == KeyContext.kCreatePublic) {\n        validateOneOf(type, 'type', ['pkcs1', 'spki']);\n      }\n    }\n    return {\n      key: getArrayBufferOrView(data, 'key', encoding),\n      format,\n      type,\n      passphrase:\n        passphrase != null\n          ? getArrayBufferOrView(passphrase, 'passphrase', encoding)\n          : undefined,\n    };\n  }\n\n  // Final type check. The key data at this point has to be an object that\n  // we will interpret as a JWK.\n  if (typeof data !== 'object') {\n    throw new ERR_INVALID_ARG_TYPE(\n      'key',\n      ['ArrayBuffer', 'Buffer', 'TypedArray', 'DataView', 'string', 'object'],\n      key\n    );\n  }\n\n  // At this point we ignore all remaining options and assume the key is a\n  // JSON Web Key.\n  return {\n    key: data as JsonWebKey,\n    format: 'jwk',\n    type: undefined,\n    passphrase: undefined,\n  };\n}\n\nexport function createPrivateKey(key: string): PrivateKeyObject;\nexport function createPrivateKey(\n  key: ArrayBuffer | ArrayBufferView\n): PrivateKeyObject;\nexport function createPrivateKey(\n  key: CreateAsymmetricKeyOptions\n): PrivateKeyObject;\nexport function createPrivateKey(\n  key: CreateAsymmetricKeyOptions | KeyData\n): PrivateKeyObject {\n  const cryptoKey = cryptoImpl.createPrivateKey(\n    prepareAsymmetricKey(\n      key as CreateAsymmetricKeyOptions,\n      KeyContext.kCreatePrivate\n    )\n  );\n  return KeyObject.from(cryptoKey) as PrivateKeyObject;\n}\n\nexport function createPublicKey(key: string): PublicKeyObject;\nexport function createPublicKey(key: ArrayBuffer): PublicKeyObject;\nexport function createPublicKey(key: ArrayBufferView): PublicKeyObject;\nexport function createPublicKey(key: KeyObject): PublicKeyObject;\nexport function createPublicKey(key: CryptoKey): PublicKeyObject;\nexport function createPublicKey(\n  key: CreateAsymmetricKeyOptions\n): PublicKeyObject;\nexport function createPublicKey(\n  key: CreateAsymmetricKeyOptions | KeyData | CryptoKey | KeyObject\n): PublicKeyObject {\n  // Passing a KeyObject or a CryptoKey allows deriving the public key\n  // from an existing private key.\n\n  if (isKeyObject(key)) {\n    if (key.type !== 'private') {\n      throw new ERR_INVALID_ARG_TYPE('key', 'PrivateKeyObject', key);\n    }\n    return KeyObject.from(\n      cryptoImpl.createPublicKey({\n        key: key[kHandle],\n        // The following are ignored when key is a CryptoKey.\n        format: 'pem',\n        type: undefined,\n        passphrase: undefined,\n      })\n    ) as PublicKeyObject;\n  }\n\n  if (key instanceof CryptoKey) {\n    if (key.type !== 'private') {\n      throw new ERR_INVALID_ARG_TYPE('key', 'PrivateKeyObject', key);\n    }\n    return KeyObject.from(\n      cryptoImpl.createPublicKey({\n        key,\n        // The following are ignored when key is a CryptoKey.\n        format: 'pem',\n        type: undefined,\n        passphrase: undefined,\n      })\n    ) as PublicKeyObject;\n  }\n\n  const cryptoKey = cryptoImpl.createPublicKey(\n    prepareAsymmetricKey(\n      key as CreateAsymmetricKeyOptions,\n      KeyContext.kCreatePublic\n    )\n  );\n  return KeyObject.from(cryptoKey) as PublicKeyObject;\n}\n\n// ======================================================================================\n\nexport type PublicKeyResult = KeyExportResult | PublicKeyObject;\nexport type PrivateKeyResult = KeyExportResult | PrivateKeyObject;\nexport type GenerateKeyCallback = (\n  err?: unknown,\n  key?: SecretKeyObject\n) => void;\nexport type GenerateKeyPairCallback = (\n  err?: unknown,\n  publicKey?: PublicKeyResult,\n  privateKey?: PrivateKeyResult\n) => void;\n\nexport interface KeyObjectPair {\n  publicKey: PublicKeyResult;\n  privateKey: PrivateKeyResult;\n}\n\nexport function generateKey(\n  _type: SecretKeyType,\n  _options: GenerateKeyOptions,\n  callback: GenerateKeyCallback\n): void {\n  try {\n    // Unlike Node.js, which implements async crypto functions using the\n    // libuv thread pool, we don't actually perform async crypto operations.\n    // Here we just defer to the sync version of the function and then \"fake\"\n    // async by using queueMicrotask to call the callback.\n    const result = generateKeySync(_type, _options);\n    queueMicrotask(() => {\n      try {\n        callback(null, result);\n      } catch (err) {\n        reportError(err);\n      }\n    });\n  } catch (err) {\n    queueMicrotask(() => {\n      try {\n        callback(err);\n      } catch (otherErr) {\n        reportError(otherErr);\n      }\n    });\n  }\n}\n\nexport function generateKeyPair(\n  _type: AsymmetricKeyType,\n  _options: GenerateKeyPairOptions,\n  callback: GenerateKeyPairCallback\n): void {\n  validateFunction(callback, 'callback');\n  try {\n    // Unlike Node.js, which implements async crypto functions using the\n    // libuv thread pool, we don't actually perform async crypto operations.\n    // Here we just defer to the sync version of the function and then \"fake\"\n    // async by using queueMicrotask to call the callback.\n    const { publicKey, privateKey } = generateKeyPairSync(_type, _options);\n    queueMicrotask(() => {\n      try {\n        callback(null, publicKey, privateKey);\n      } catch (err) {\n        reportError(err);\n      }\n    });\n  } catch (err) {\n    queueMicrotask(() => {\n      try {\n        callback(err);\n      } catch (otherErr) {\n        reportError(otherErr);\n      }\n    });\n  }\n}\n\nObject.defineProperty(generateKeyPair, kCustomPromisifyArgsSymbol, {\n  value: ['publicKey', 'privateKey'],\n  enumerable: false,\n});\n\nexport function generateKeySync(\n  type: SecretKeyType,\n  options: GenerateKeyOptions\n): SecretKeyObject {\n  validateOneOf(type, 'type', ['hmac', 'aes']);\n  validateObject(options, 'options');\n  const { length } = options;\n\n  switch (type) {\n    case 'hmac': {\n      // The minimum is 8, and the maximum length is 65,536. If the length is\n      // not a multiple of 8, the generated key will be truncated to\n      // Math.floor(length / 8).\n      // Note that the upper bound of 65536 is intentionally more limited than\n      // what Node.js allows. This puts the maximum size limit on generated\n      // secret keys to 8192 bytes. We can adjust this up if necessary but\n      // it's a good starting point.\n      validateInteger(length, 'options.length', 8, 65536);\n      const buf = randomBytes(Math.floor(length / 8));\n      return createSecretKey(buf);\n    }\n    case 'aes': {\n      // The length must be one of 128, 192, or 256.\n      validateOneOf(length, 'options.length', [128, 192, 256]);\n      const buf = randomBytes(length / 8);\n      return createSecretKey(buf);\n    }\n  }\n}\n\nexport function generateKeyPairSync(\n  type: AsymmetricKeyType,\n  options: GenerateKeyPairOptions = {}\n): KeyObjectPair {\n  validateOneOf(type, 'type', [\n    'rsa',\n    'ec',\n    'ed25519',\n    'x25519',\n    'dh',\n    // BoringSSL does not support the 448 variants.\n    // 'dsa',\n    // 'rsa-pss',\n    // 'ed448',\n    // 'x448',\n  ]);\n\n  validateObject(options, 'options');\n\n  const {\n    modulusLength, // Used for RSA/DSA. number\n    publicExponent = 0x10001, // Used for RSA. number\n    // Historically Node.js had \"hash\" and \"mgf1Hash\" but these were deprecated.\n    // It is still possible to find uses of the old names in the wild.\n    // TODO(later): Uncomment the following when rsa-pss generation is supported.\n    // hashAlgorithm, // Used for RSA-PSS. string\n    // mgf1HashAlgorithm, // Used for RSA-PSS. string\n    // hash, // Deprecated, use hashAlgorithm instead. string\n    // mgf1Hash, // Deprecated, use mgf1HashAlgorithm instead. string\n    // saltLength, // Used for RSA-PSS. number\n    namedCurve, // Used for EC. string\n    prime, // Used for DH. Buffer/ArrayBufferView/ArrayBuffer\n    primeLength, // Used for DH. number\n    generator, // Used for DH. number\n    // This is fun... Node.js docs say the option is \"groupName\" while\n    // the code appears to check for \"group\".\n    group, // Used for DH (alias for groupName)\n    groupName, // Used for DH. string\n    paramEncoding = 'named', // For for EC. Value is 'named' or 'explicit'.\n    publicKeyEncoding, // value must be an object, same options as export\n    privateKeyEncoding, // value must be an object, same options as export\n  } = options;\n\n  // TODO(later): The divisorLength option is only used for generating DSA\n  // keypairs, which we currently do not support.\n  // let { divisorLength } = options;\n\n  if (publicKeyEncoding !== undefined) {\n    validateExportOptions(\n      publicKeyEncoding as ExportOptions,\n      'public',\n      'options.publicKeyEncoding'\n    );\n  }\n  if (privateKeyEncoding !== undefined) {\n    validateExportOptions(\n      privateKeyEncoding as ExportOptions,\n      'private',\n      'options.privateKeyEncoding'\n    );\n  }\n\n  const handleKeyEncoding = (\n    pair: CryptoKeyPair\n  ): {\n    publicKey: KeyObject | KeyExportResult;\n    privateKey: KeyObject | KeyExportResult;\n  } => {\n    let publicKey: KeyExportResult | KeyObject = KeyObject.from(pair.publicKey);\n    let privateKey: KeyExportResult | KeyObject = KeyObject.from(\n      pair.privateKey\n    );\n    if (publicKeyEncoding !== undefined) {\n      publicKey = publicKey.export(publicKeyEncoding);\n    }\n    if (privateKeyEncoding !== undefined) {\n      privateKey = privateKey.export(privateKeyEncoding);\n    }\n    return { publicKey, privateKey };\n  };\n\n  // Validation of the specific options depends on the type of key being\n  // generated.\n\n  switch (type) {\n    case 'rsa': {\n      validateUint32(modulusLength, 'options.modulusLength');\n      validateUint32(publicExponent, 'options.publicExponent');\n      return handleKeyEncoding(\n        cryptoImpl.generateRsaKeyPair({\n          type,\n          modulusLength: modulusLength,\n          publicExponent: publicExponent,\n        })\n      ) as KeyObjectPair;\n    }\n    // TODO(later): BoringSSL does not support RSA-PSS key generation in the\n    // same way as Node.js. Later see if there's an alternative approach.\n    // case 'rsa-pss': {\n    //   validateUint32(modulusLength, 'options.modulusLength');\n    //   validateUint32(publicExponent, 'options.publicExponent');\n\n    //     if (saltLength !== undefined) {\n    //       validateInt32(saltLength, 'options.saltLength', 0);\n    //     }\n    //     if (hashAlgorithm !== undefined) {\n    //       validateString(hashAlgorithm, 'options.hashAlgorithm');\n    //     }\n    //     if (mgf1HashAlgorithm !== undefined) {\n    //       validateString(mgf1HashAlgorithm, 'options.mgf1HashAlgorithm');\n    //     }\n    //     if (hash !== undefined) {\n    //       validateString(hash, 'options.hash');\n    //       if (hashAlgorithm && hash !== hashAlgorithm) {\n    //         throw new ERR_INVALID_ARG_VALUE('options.hash', hash);\n    //       }\n    //     }\n    //     if (mgf1Hash !== undefined) {\n    //       validateString(mgf1Hash, 'options.mgf1Hash');\n    //       if (mgf1HashAlgorithm && mgf1Hash !== mgf1HashAlgorithm) {\n    //         throw new ERR_INVALID_ARG_VALUE('options.mgf1Hash', mgf1Hash);\n    //       }\n    //     }\n    //     return handleKeyEncoding(\n    //       cryptoImpl.generateRsaKeyPair({\n    //         type,\n    //         modulusLength: modulusLength!,\n    //         publicExponent: publicExponent!,\n    //         saltLength: saltLength!,\n    //         hashAlgorithm: hash || hashAlgorithm!,\n    //         mgf1HashAlgorithm: mgf1Hash || mgf1HashAlgorithm!,\n    //       })\n    //     ) as KeyObjectPair;\n    //  }\n    // TODO(later): BoringSSL does not support DSA key generation in the\n    // same way as Node.js. Later see if there's an alternative approach.\n    // case 'dsa': {\n    //   validateUint32(modulusLength, 'options.modulusLength');\n    //   if (divisorLength == null) {\n    //     divisorLength = undefined;\n    //   } else {\n    //     validateInt32(divisorLength, 'options.divisorLength', 0);\n    //   }\n    //   return handleKeyEncoding(\n    //     cryptoImpl.generateDsaKeyPair({\n    //       modulusLength: modulusLength!,\n    //       divisorLength: divisorLength as number,\n    //     })\n    //   ) as KeyObjectPair;\n    // }\n    case 'ec': {\n      validateString(namedCurve, 'options.namedCurve');\n      validateOneOf(paramEncoding, 'options.paramEncoding', [\n        'named',\n        'explicit',\n      ]);\n      return handleKeyEncoding(\n        cryptoImpl.generateEcKeyPair({\n          namedCurve,\n          paramEncoding,\n        })\n      ) as KeyObjectPair;\n    }\n    case 'ed25519':\n    // Fall-through\n    // eslint-disable-next-line no-fallthrough\n    case 'x25519': {\n      // Nothing to validate...\n      return handleKeyEncoding(\n        cryptoImpl.generateEdKeyPair({ type })\n      ) as KeyObjectPair;\n    }\n    case 'dh': {\n      if (generator != null) {\n        validateInt32(generator, 'options.generator', 0);\n      }\n\n      if (group != null || groupName != null) {\n        if (prime != null) {\n          throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'prime');\n        }\n        if (primeLength != null) {\n          throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'primeLength');\n        }\n        if (generator != null) {\n          throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'generator');\n        }\n\n        const g = group || groupName;\n\n        validateString(g, 'options.group');\n\n        return handleKeyEncoding(\n          cryptoImpl.generateDhKeyPair({\n            primeOrGroup: g,\n            generator,\n          })\n        ) as KeyObjectPair;\n      }\n\n      if (prime != null) {\n        if (primeLength != null) {\n          throw new ERR_INCOMPATIBLE_OPTION_PAIR('prime', 'primeLength');\n        }\n\n        if (!isArrayBufferView(prime) && !isAnyArrayBuffer(prime)) {\n          throw new ERR_INVALID_ARG_TYPE(\n            'options.prime',\n            ['Buffer', 'TypedArray', 'ArrayBuffer'],\n            prime\n          );\n        }\n      } else if (primeLength != null) {\n        validateInt32(primeLength, 'options.primeLength', 0);\n      } else {\n        throw new ERR_MISSING_OPTION(\n          'At least one of the group, prime, or primeLength options'\n        );\n      }\n\n      if (prime) {\n        return handleKeyEncoding(\n          cryptoImpl.generateDhKeyPair({\n            primeOrGroup: prime as BufferSource,\n            generator: generator as number,\n          })\n        ) as KeyObjectPair;\n      }\n\n      return handleKeyEncoding(\n        cryptoImpl.generateDhKeyPair({\n          primeOrGroup: primeLength as number,\n          generator: generator as number,\n        })\n      ) as KeyObjectPair;\n    }\n  }\n}\n"
  },
  {
    "path": "src/node/internal/crypto_pbkdf2.ts",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { default as cryptoImpl } from 'node-internal:crypto';\n\nimport { Buffer } from 'node-internal:internal_buffer';\n\nimport {\n  validateInt32,\n  validateFunction,\n  validateString,\n} from 'node-internal:validators';\n\nimport { getArrayBufferOrView } from 'node-internal:crypto_util';\n\nexport type ArrayLike = cryptoImpl.ArrayLike;\n\nexport function pbkdf2Sync(\n  password: ArrayLike,\n  salt: ArrayLike,\n  iterations: number,\n  keylen: number,\n  digest: string\n): Buffer {\n  ({ password, salt, iterations, keylen, digest } = check(\n    password,\n    salt,\n    iterations,\n    keylen,\n    digest\n  ));\n\n  const result = cryptoImpl.getPbkdf(\n    password,\n    salt,\n    iterations,\n    keylen,\n    digest\n  );\n  return Buffer.from(result);\n}\n\nexport type Pbkdf2Callback = (err?: Error | null, result?: Buffer) => void;\nexport function pbkdf2(\n  password: ArrayLike,\n  salt: ArrayLike,\n  iterations: number,\n  keylen: number,\n  digest: string,\n  callback: Pbkdf2Callback\n): void {\n  if (typeof digest === 'function') {\n    // Appease node test cases\n    validateString(undefined, 'digest');\n  }\n  validateFunction(callback, 'callback');\n  ({ password, salt, iterations, keylen, digest } = check(\n    password,\n    salt,\n    iterations,\n    keylen,\n    digest\n  ));\n\n  new Promise<ArrayBuffer>((res, rej) => {\n    try {\n      res(cryptoImpl.getPbkdf(password, salt, iterations, keylen, digest));\n    } catch (err) {\n      rej(err as Error);\n    }\n  }).then(\n    (val: ArrayBuffer): unknown => callback(null, Buffer.from(val)),\n    (err: unknown): unknown => callback(err as Error)\n  );\n}\n\nfunction check(\n  password: ArrayLike | ArrayBufferView,\n  salt: ArrayLike | ArrayBufferView,\n  iterations: number,\n  keylen: number,\n  digest: string\n): {\n  password: ArrayLike | ArrayBufferView;\n  salt: ArrayLike | ArrayBufferView;\n  iterations: number;\n  keylen: number;\n  digest: string;\n} {\n  validateString(digest, 'digest');\n\n  password = getArrayBufferOrView(password, 'password');\n  salt = getArrayBufferOrView(salt, 'salt');\n  // OpenSSL uses a signed int to represent these values, so we are restricted\n  // to the 31-bit range here (which is plenty).\n  validateInt32(iterations, 'iterations', 1);\n  validateInt32(keylen, 'keylen', 0);\n\n  return { password, salt, iterations, keylen, digest };\n}\n"
  },
  {
    "path": "src/node/internal/crypto_random.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { default as cryptoImpl } from 'node-internal:crypto';\n\nimport {\n  validateObject,\n  validateBoolean,\n  validateFunction,\n  validateInt32,\n  validateInteger,\n} from 'node-internal:validators';\n\nimport {\n  isAnyArrayBuffer,\n  isArrayBufferView,\n} from 'node-internal:internal_types';\n\nimport {\n  ERR_INVALID_ARG_TYPE,\n  ERR_OUT_OF_RANGE,\n} from 'node-internal:internal_errors';\n\nimport { Buffer, kMaxLength } from 'node-internal:internal_buffer';\n\nimport { arrayBufferToUnsignedBigInt } from 'node-internal:crypto_util';\nimport type { RandomUUIDOptions } from 'node:crypto';\n\nexport type RandomBytesCallback = (\n  err: Error | null,\n  buffer: Uint8Array\n) => void;\nexport function randomBytes(size: number, callback: RandomBytesCallback): void;\nexport function randomBytes(size: number): Uint8Array;\nexport function randomBytes(\n  size: number,\n  callback?: RandomBytesCallback\n): Uint8Array | undefined {\n  validateInteger(size, 'size', 0, kMaxLength);\n  const buf = Buffer.alloc(size);\n  if (callback !== undefined) {\n    randomFill(buf, callback as RandomFillCallback);\n    return undefined;\n  } else {\n    randomFillSync(buf);\n    return buf;\n  }\n}\n\nexport function randomFillSync(\n  buffer: NodeJS.ArrayBufferView,\n  offset?: number,\n  size?: number\n): Uint8Array {\n  if (!isAnyArrayBuffer(buffer) && !isArrayBufferView(buffer)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'buffer',\n      ['TypedArray', 'DataView', 'ArrayBuffer', 'SharedArrayBuffer'],\n      buffer\n    );\n  }\n  const maxLength = (buffer as Uint8Array).length;\n  if (offset !== undefined) {\n    validateInteger(offset, 'offset', 0, kMaxLength);\n  } else offset = 0;\n  if (size !== undefined) {\n    validateInteger(size, 'size', 0, maxLength - offset);\n  } else size = maxLength - offset;\n  if (isAnyArrayBuffer(buffer)) {\n    buffer = Buffer.from(buffer);\n  }\n  buffer = (buffer as Buffer).subarray(offset, offset + size);\n  return crypto.getRandomValues(buffer);\n}\n\nexport type RandomFillCallback = (\n  err: Error | null,\n  buf?: NodeJS.ArrayBufferView\n) => void;\nexport function randomFill(\n  buffer: NodeJS.ArrayBufferView,\n  callback?: RandomFillCallback\n): void;\nexport function randomFill(\n  buffer: NodeJS.ArrayBufferView,\n  offset: number,\n  callback?: RandomFillCallback\n): void;\nexport function randomFill(\n  buffer: NodeJS.ArrayBufferView,\n  offset: number,\n  size: number,\n  callback?: RandomFillCallback\n): void;\nexport function randomFill(\n  buffer: NodeJS.ArrayBufferView,\n  offsetOrCallback?: number | RandomFillCallback,\n  sizeOrCallback?: number | RandomFillCallback,\n  callback?: RandomFillCallback\n): void {\n  if (!isAnyArrayBuffer(buffer) && !isArrayBufferView(buffer)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'buffer',\n      ['TypedArray', 'DataView', 'ArrayBuffer', 'SharedArrayBuffer'],\n      buffer\n    );\n  }\n\n  let offset = 0;\n  let size = 0;\n  const maxLength = (buffer as Uint8Array).length;\n  if (typeof callback === 'function') {\n    validateInteger(offsetOrCallback, 'offset', 0, maxLength);\n    offset = offsetOrCallback;\n\n    validateInteger(sizeOrCallback, 'size', 0, maxLength - offset);\n    size = sizeOrCallback;\n  } else if (typeof sizeOrCallback === 'function') {\n    validateInteger(offsetOrCallback, 'offset', 0, maxLength);\n    offset = offsetOrCallback;\n    size = maxLength - offset;\n    callback = sizeOrCallback;\n  } else if (typeof offsetOrCallback === 'function') {\n    offset = 0;\n    size = maxLength;\n    callback = offsetOrCallback;\n  }\n  validateFunction(callback, 'callback');\n\n  // We're currently not actually implementing the fill itself asynchronously,\n  // so we defer to randomFillSync here, but we invoke the callback asynchronously.\n  new Promise<void>((res) => {\n    randomFillSync(buffer, offset, size);\n    res();\n  }).then(\n    (): unknown => callback(null, buffer),\n    (err: unknown): unknown => callback(err as Error)\n  );\n}\n\nconst RAND_MAX = 0xffff_ffff_ffff;\n// Cache random data to use in randomInt. The cache size must be evenly\n// divisible by 6 because each attempt to obtain a random int uses 6 bytes.\nconst randomCache = Buffer.alloc(6 * 1024);\nlet randomCacheOffset = 0;\nlet initialized = false;\n\nfunction getRandomInt(min: number, max: number): number {\n  if (!initialized) {\n    randomFillSync(randomCache);\n    initialized = true;\n  }\n  // First we generate a random int between [0..range)\n  const range = max - min;\n\n  if (!(range <= RAND_MAX)) {\n    throw new ERR_OUT_OF_RANGE(\n      `max${max ? '' : ' - min'}`,\n      `<= ${RAND_MAX}`,\n      range\n    );\n  }\n\n  // For (x % range) to produce an unbiased value greater than or equal to 0 and\n  // less than range, x must be drawn randomly from the set of integers greater\n  // than or equal to 0 and less than randLimit.\n  const randLimit = RAND_MAX - (RAND_MAX % range);\n\n  // If we don't have a callback, or if there is still data in the cache, we can\n  // do this synchronously, which is super fast.\n  while (randomCacheOffset <= randomCache.length) {\n    if (randomCacheOffset === randomCache.length) {\n      // This might block the thread for a bit, but we are in sync mode.\n      randomFillSync(randomCache);\n      randomCacheOffset = 0;\n    }\n\n    const x = randomCache.readUIntBE(randomCacheOffset, 6);\n    randomCacheOffset += 6;\n    if (x < randLimit) {\n      return (x % range) + min;\n    }\n  }\n  return 0; // Should be unreachable.\n}\n\nexport type RandomIntCallback = (err: Error | null, n?: number) => void;\nexport function randomInt(max: number): number;\nexport function randomInt(min: number, max: number): number;\nexport function randomInt(max: number, callback: RandomIntCallback): void;\nexport function randomInt(\n  min: number,\n  max: number,\n  callback: RandomIntCallback\n): void;\nexport function randomInt(\n  minOrMax: number,\n  maxOrCallback?: number | RandomIntCallback,\n  callback?: RandomIntCallback\n): number | undefined {\n  let min = 0;\n  let max = 0;\n  if (typeof callback === 'function') {\n    validateInteger(minOrMax, 'min');\n    validateInteger(maxOrCallback, 'max');\n    min = minOrMax;\n    max = maxOrCallback;\n  } else if (typeof maxOrCallback === 'function') {\n    min = 0;\n    validateInteger(minOrMax, 'max');\n    max = minOrMax;\n    callback = maxOrCallback;\n  } else if (arguments.length === 2) {\n    validateInteger(minOrMax, 'min');\n    validateInteger(maxOrCallback, 'max');\n    min = minOrMax;\n    max = maxOrCallback;\n  } else {\n    min = 0;\n    validateInteger(minOrMax, 'max');\n    max = minOrMax;\n  }\n\n  if (min >= max) {\n    throw new ERR_OUT_OF_RANGE('min', 'min < max', min);\n  }\n\n  if (callback != null) {\n    new Promise<number>((res) => {\n      res(getRandomInt(min, max));\n    }).then(\n      (n: number): void => {\n        callback(null, n);\n      },\n      (err: unknown): void => {\n        callback(err as Error);\n      }\n    );\n  } else {\n    return getRandomInt(min, max);\n  }\n\n  return undefined;\n}\n\nexport function randomUUID(options?: RandomUUIDOptions): string {\n  // While we do not actually use the entropy cache, we go ahead and validate\n  // the input parameters as Node.js does.\n  if (options !== undefined) {\n    validateObject(options, 'options');\n    if (options.disableEntropyCache !== undefined) {\n      validateBoolean(\n        options.disableEntropyCache,\n        'options.disableEntropyCache'\n      );\n    }\n  }\n  return crypto.randomUUID();\n}\n\nexport type PrimeNum = ArrayBuffer | ArrayBufferView | Buffer | bigint;\nexport interface GeneratePrimeOptions {\n  add?: PrimeNum;\n  rem?: PrimeNum;\n  safe?: boolean;\n  bigint?: boolean;\n}\n\nexport interface CheckPrimeOptions {\n  checks?: number;\n}\n\nexport type GeneratePrimeCallback = (\n  err: Error | null,\n  prime?: bigint | ArrayBuffer\n) => void;\nexport type CheckPrimeCallback = (err: Error | null, prime?: boolean) => void;\n\nfunction processGeneratePrimeOptions(options: GeneratePrimeOptions): {\n  add: ArrayBufferView;\n  rem: ArrayBufferView;\n  safe: boolean;\n  bigint: boolean;\n} {\n  validateObject(options, 'options');\n  const { safe = false, bigint = false } = options;\n  let { add, rem } = options;\n  validateBoolean(safe, 'options.safe');\n  validateBoolean(bigint, 'options.bigint');\n\n  if (add !== undefined) {\n    if (typeof add === 'bigint') {\n      add = unsignedBigIntToBuffer(add, 'options.add');\n    } else if (!isAnyArrayBuffer(add) && !isArrayBufferView(add)) {\n      throw new ERR_INVALID_ARG_TYPE(\n        'options.add',\n        ['ArrayBuffer', 'TypedArray', 'Buffer', 'DataView', 'bigint'],\n        add\n      );\n    }\n  }\n\n  if (rem !== undefined) {\n    if (typeof rem === 'bigint') {\n      rem = unsignedBigIntToBuffer(rem, 'options.rem');\n    } else if (!isAnyArrayBuffer(rem) && !isArrayBufferView(rem)) {\n      throw new ERR_INVALID_ARG_TYPE(\n        'options.rem',\n        ['ArrayBuffer', 'TypedArray', 'Buffer', 'DataView', 'bigint'],\n        rem\n      );\n    }\n  }\n\n  return {\n    safe,\n    bigint,\n    add: add as ArrayBufferView,\n    rem: rem as ArrayBufferView,\n  };\n}\n\nexport function generatePrimeSync(\n  size: number,\n  options: GeneratePrimeOptions = {}\n): bigint | ArrayBuffer {\n  validateInt32(size, 'size', 1);\n  const { safe, bigint, add, rem } = processGeneratePrimeOptions(options);\n\n  const primeBuf = cryptoImpl.randomPrime(size, safe, add, rem);\n  return bigint ? arrayBufferToUnsignedBigInt(primeBuf) : primeBuf;\n}\n\nexport function generatePrime(\n  size: number,\n  options: GeneratePrimeOptions,\n  callback: GeneratePrimeCallback\n): void;\nexport function generatePrime(\n  size: number,\n  callback: GeneratePrimeCallback\n): void;\nexport function generatePrime(\n  size: number,\n  options: GeneratePrimeOptions | GeneratePrimeCallback,\n  callback?: GeneratePrimeCallback\n): void {\n  validateInt32(size, 'size', 1);\n  if (typeof options === 'function') {\n    callback = options;\n    options = {};\n  }\n  validateFunction(callback, 'callback');\n\n  const { safe, bigint, add, rem } = processGeneratePrimeOptions(options);\n\n  new Promise<bigint | ArrayBuffer>((res, rej) => {\n    try {\n      const primeBuf = cryptoImpl.randomPrime(size, safe, add, rem);\n      res(bigint ? arrayBufferToUnsignedBigInt(primeBuf) : primeBuf);\n    } catch (err) {\n      rej(err as Error);\n    }\n  }).then(\n    (val: bigint | ArrayBuffer): void => {\n      callback(null, val);\n    },\n    (err: unknown): void => {\n      callback(err as Error);\n    }\n  );\n}\n\nfunction unsignedBigIntToBuffer(bigint: bigint, name: string): Buffer {\n  if (bigint < 0) {\n    throw new ERR_OUT_OF_RANGE(name, '>= 0', bigint);\n  }\n\n  const hex = bigint.toString(16);\n  const padded = hex.padStart(hex.length + (hex.length % 2), '0');\n  return Buffer.from(padded, 'hex');\n}\n\nfunction validateCandidate(candidate: PrimeNum): Buffer {\n  if (typeof candidate === 'bigint')\n    candidate = unsignedBigIntToBuffer(candidate, 'candidate');\n  if (!isAnyArrayBuffer(candidate) && !isArrayBufferView(candidate)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'candidate',\n      ['ArrayBuffer', 'TypedArray', 'Buffer', 'DataView', 'bigint'],\n      candidate\n    );\n  }\n  return candidate as Buffer;\n}\n\nfunction validateChecks(options: CheckPrimeOptions): number {\n  const { checks = 0 } = options;\n  // The checks option is unsigned but must fit into a signed 32-bit integer for OpenSSL.\n  validateInt32(checks, 'options.checks', 0);\n  return checks;\n}\n\nexport function checkPrimeSync(\n  candidate: PrimeNum,\n  options: CheckPrimeOptions = {}\n): boolean {\n  candidate = validateCandidate(candidate);\n  validateObject(options, 'options');\n  const checks = validateChecks(options);\n  return cryptoImpl.checkPrimeSync(candidate as ArrayBufferView, checks);\n}\n\nexport function checkPrime(\n  candidate: PrimeNum,\n  options: CheckPrimeOptions,\n  callback: CheckPrimeCallback\n): void;\nexport function checkPrime(\n  candidate: PrimeNum,\n  callback: CheckPrimeCallback\n): void;\nexport function checkPrime(\n  candidate: PrimeNum,\n  options: CheckPrimeOptions | CheckPrimeCallback,\n  callback?: CheckPrimeCallback\n): void {\n  candidate = validateCandidate(candidate);\n  if (typeof options === 'function') {\n    callback = options;\n    options = {};\n  }\n  validateObject(options, 'options');\n  validateFunction(callback, 'callback');\n  const checks = validateChecks(options);\n  new Promise<boolean>((res, rej) => {\n    try {\n      res(cryptoImpl.checkPrimeSync(candidate as ArrayBufferView, checks));\n    } catch (err) {\n      rej(err as Error);\n    }\n  }).then(\n    (val: boolean): void => {\n      callback(null, val);\n    },\n    (err: unknown): void => {\n      callback(err);\n    }\n  );\n}\n"
  },
  {
    "path": "src/node/internal/crypto_scrypt.ts",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { default as cryptoImpl } from 'node-internal:crypto';\n\nimport {\n  validateFunction,\n  validateInteger,\n  validateUint32,\n} from 'node-internal:validators';\n\nimport { Buffer } from 'node-internal:internal_buffer';\n\ntype ArrayLike = cryptoImpl.ArrayLike;\n\nimport { kMaxLength } from 'node-internal:internal_buffer';\n\nimport { getArrayBufferOrView } from 'node-internal:crypto_util';\n\nimport { ERR_INVALID_ARG_VALUE } from 'node-internal:internal_errors';\n\nexport interface ValidatedScryptOptions {\n  password: ArrayLike;\n  salt: ArrayLike;\n  keylen: number;\n  N: number;\n  r: number;\n  p: number;\n  maxmem: number;\n}\n\nexport interface ScryptOptions {\n  N?: number;\n  r?: number;\n  p?: number;\n  maxmem?: number;\n  cost?: number;\n  blockSize?: number;\n  parallelization?: number;\n}\n\nconst defaults: ScryptOptions = {\n  N: 16384,\n  r: 8,\n  p: 1,\n  maxmem: 32 << 20,\n};\n\nfunction validateParameters(\n  password: ArrayLike,\n  salt: ArrayLike,\n  keylen: number,\n  options?: ScryptOptions\n): ValidatedScryptOptions {\n  // TODO(soon): Add support for KeyObject input.\n  password = getArrayBufferOrView(password, 'password');\n  salt = getArrayBufferOrView(salt, 'salt');\n\n  if (password.byteLength >= kMaxLength) {\n    throw new ERR_INVALID_ARG_VALUE('password', password, 'is too big');\n  }\n  if (salt.byteLength >= kMaxLength) {\n    throw new ERR_INVALID_ARG_VALUE('salt', salt, 'is too big');\n  }\n\n  validateInteger(keylen, 'keylen', 0, kMaxLength);\n\n  let { N, r, p, maxmem } = defaults;\n  if (options && options !== defaults) {\n    const has_N = options.N !== undefined;\n    if (has_N) {\n      N = options.N;\n      validateUint32(N, 'N');\n    }\n    if (options.cost !== undefined) {\n      if (has_N) throw new ERR_INVALID_ARG_VALUE('cost', options.cost);\n      N = options.cost;\n      validateUint32(N, 'cost');\n    }\n    const has_r = options.r !== undefined;\n    if (has_r) {\n      r = options.r;\n      validateUint32(r, 'r');\n    }\n    if (options.blockSize !== undefined) {\n      if (has_r)\n        throw new ERR_INVALID_ARG_VALUE('blockSize', options.blockSize);\n      r = options.blockSize;\n      validateUint32(r, 'blockSize');\n    }\n    const has_p = options.p !== undefined;\n    if (has_p) {\n      p = options.p;\n      validateUint32(p, 'p');\n    }\n    if (options.parallelization !== undefined) {\n      if (has_p)\n        throw new ERR_INVALID_ARG_VALUE(\n          'parallelization',\n          options.parallelization\n        );\n      p = options.parallelization;\n      validateUint32(p, 'parallelization');\n    }\n    if (options.maxmem !== undefined) {\n      maxmem = options.maxmem;\n      validateInteger(maxmem, 'maxmem', 0);\n    }\n    if (N === 0) N = defaults.N;\n    if (r === 0) r = defaults.r;\n    if (p === 0) p = defaults.p;\n    if (maxmem === 0) maxmem = defaults.maxmem;\n  }\n\n  return {\n    password,\n    salt,\n    keylen,\n    N: N as number,\n    r: r as number,\n    p: p as number,\n    maxmem: maxmem as number,\n  };\n}\n\ntype Callback = (err: Error | null, derivedKey?: Buffer) => void;\ntype OptionsOrCallback = ScryptOptions | Callback;\n\nexport function scrypt(\n  password: ArrayLike,\n  salt: ArrayLike,\n  keylen: number,\n  options: OptionsOrCallback,\n  callback: OptionsOrCallback = defaults\n): void {\n  if (callback === defaults) {\n    callback = options;\n    options = defaults;\n  }\n\n  let N: number;\n  let r: number;\n  let p: number;\n  let maxmem: number;\n  ({ password, salt, keylen, N, r, p, maxmem } = validateParameters(\n    password,\n    salt,\n    keylen,\n    options as ScryptOptions\n  ));\n\n  validateFunction(callback, 'callback');\n\n  new Promise<ArrayBuffer>((res, rej) => {\n    try {\n      res(cryptoImpl.getScrypt(password, salt, N, r, p, maxmem, keylen));\n    } catch (err) {\n      rej(err as Error);\n    }\n  }).then(\n    (val: ArrayBuffer) => {\n      (callback as Callback)(null, Buffer.from(val));\n    },\n    (err: unknown) => {\n      (callback as Callback)(err as Error);\n    }\n  );\n}\n\nexport function scryptSync(\n  password: ArrayLike,\n  salt: ArrayLike,\n  keylen: number,\n  options: ScryptOptions\n): Buffer {\n  let N: number;\n  let r: number;\n  let p: number;\n  let maxmem: number;\n  ({ password, salt, keylen, N, r, p, maxmem } = validateParameters(\n    password,\n    salt,\n    keylen,\n    options\n  ));\n\n  return Buffer.from(\n    cryptoImpl.getScrypt(password, salt, N, r, p, maxmem, keylen)\n  );\n}\n"
  },
  {
    "path": "src/node/internal/crypto_sign.ts",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n/* TODO: the following is adopted code, enabling linting one day */\n/* eslint-disable */\n\nimport {\n  default as cryptoImpl,\n  type SignHandle,\n  type VerifyHandle,\n  type CreateAsymmetricKeyOptions,\n} from 'node-internal:crypto';\n\nimport { validateString } from 'node-internal:validators';\n\nimport { Buffer } from 'node-internal:internal_buffer';\n\nimport {\n  KeyObject,\n  isKeyObject,\n  getKeyObjectHandle,\n  createPrivateKey,\n  createPublicKey,\n} from 'node-internal:crypto_keys';\n\nimport {\n  ERR_CRYPTO_SIGN_KEY_REQUIRED,\n  ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_ARG_VALUE,\n} from 'node-internal:internal_errors';\n\nimport { Writable } from 'node-internal:streams_writable';\nimport { isArrayBufferView } from 'node-internal:internal_types';\n\nexport interface SignOptions {}\n\nexport interface Sign extends Writable {\n  [kHandle]: SignHandle;\n  update(data: any): void;\n  sign(privateKey: any): any;\n}\nexport interface Verify extends Writable {\n  [kHandle]: VerifyHandle;\n  update(data: any): void;\n  verify(publicKey: any, signature: any): any;\n}\n\nconst kHandle = Symbol('kHandle');\n\n// Uses old-style class syntax for Node.js compatibility\nexport const Sign = function (this: Sign, algorithm: string, options: any) {\n  if (!(this instanceof Sign)) return new Sign(algorithm, options);\n  validateString(algorithm, 'algorithm');\n  this[kHandle] = new cryptoImpl.SignHandle(algorithm);\n  Writable.call(this, options);\n  return this;\n} as any as {\n  new (algorithm: string, options?: SignOptions): Sign;\n};\nObject.setPrototypeOf(Sign.prototype, Writable.prototype);\nObject.setPrototypeOf(Sign, Writable);\n\nSign.prototype._write = function _write(\n  chunk: string | ArrayBufferView,\n  encoding: string | undefined | null,\n  callback: (err?: any) => void\n) {\n  this.update(chunk, encoding);\n  callback();\n};\n\nSign.prototype.update = function (\n  this: Sign,\n  data: string | ArrayBufferView,\n  encoding?: string\n) {\n  if (typeof data === 'string') {\n    data = Buffer.from(data, encoding);\n  }\n  if (!isArrayBufferView(data)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'data',\n      ['string', 'Buffer', 'TypedArray', 'DataView'],\n      data\n    );\n  }\n  this[kHandle].update(data);\n  return this;\n};\n\nfunction getIntOption(name: string, options: any): number | undefined {\n  const value = options[name];\n  if (value !== undefined) {\n    if (value === value >> 0) {\n      return value;\n    }\n    throw new ERR_INVALID_ARG_VALUE(`options.${name}`, value);\n  }\n  return undefined;\n}\n\nfunction getDSASignatureEncoding(options: any): number {\n  if (typeof options === 'object') {\n    const { dsaEncoding = 'der' } = options;\n    if (dsaEncoding === 'der')\n      return 0; // kSigEncDER;\n    else if (dsaEncoding === 'ieee-p1363') return 1; // kSigEncP1363\n    throw new ERR_INVALID_ARG_VALUE('options.dsaEncoding', dsaEncoding);\n  }\n\n  return 0;\n}\n\nfunction getPrivateKey(options: any): CryptoKey {\n  if (options instanceof CryptoKey) {\n    return options as CryptoKey;\n  } else if (isKeyObject(options)) {\n    const keyObject = options as KeyObject;\n    if (keyObject.type === 'secret') {\n      throw new ERR_INVALID_ARG_TYPE(\n        'options',\n        ['PublicKeyObject', 'PrivateKeyObject', 'CryptoKey', 'object'],\n        keyObject.type\n      );\n    }\n    if (keyObject.type === 'public') {\n      throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE('public', 'private');\n    }\n    return getKeyObjectHandle(keyObject);\n  } else {\n    return getKeyObjectHandle(\n      createPrivateKey(options as CreateAsymmetricKeyOptions)\n    );\n  }\n}\n\nSign.prototype.sign = function (\n  this: Sign,\n  options: any,\n  encoding?: string\n): Buffer | string {\n  if (!options) {\n    throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();\n  }\n\n  const key = getPrivateKey(options);\n\n  // Options specific to RSA\n  const rsaPadding = getIntOption('padding', options);\n  const pssSaltLength = getIntOption('saltLength', options);\n\n  // Options specific to (EC)DSA\n  const dsaSigEnc = getDSASignatureEncoding(options);\n\n  const u8 = this[kHandle].sign(key, rsaPadding, pssSaltLength, dsaSigEnc);\n\n  const res = Buffer.from(u8.buffer, u8.byteOffset, u8.byteLength);\n\n  if (encoding && encoding !== 'buffer') {\n    return res.toString(encoding);\n  }\n\n  return res;\n};\n\n// Uses old-style class syntax for Node.js compatibility\nexport const Verify = function (this: Verify, algorithm: string, options: any) {\n  if (!(this instanceof Verify)) return new Verify(algorithm, options);\n  validateString(algorithm, 'algorithm');\n  this[kHandle] = new cryptoImpl.VerifyHandle(algorithm);\n  Writable.call(this, options);\n  return this;\n} as any as {\n  new (algorithm: string, options?: SignOptions): Verify;\n};\nObject.setPrototypeOf(Verify.prototype, Writable.prototype);\nObject.setPrototypeOf(Verify, Writable);\n\nVerify.prototype._write = function _write(\n  chunk: string | ArrayBufferView,\n  encoding: string | undefined | null,\n  callback: (err?: unknown) => void\n) {\n  this.update(chunk, encoding);\n  callback();\n};\n\nVerify.prototype.update = function (\n  this: Verify,\n  data: string | ArrayBufferView,\n  encoding?: string\n) {\n  if (typeof data === 'string') {\n    data = Buffer.from(data, encoding);\n  }\n  if (!isArrayBufferView(data)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'data',\n      ['string', 'Buffer', 'TypedArray', 'DataView'],\n      data\n    );\n  }\n  this[kHandle].update(data);\n  return this;\n};\n\nVerify.prototype.verify = function (\n  this: Verify,\n  options: any,\n  signature: ArrayBufferView | string,\n  encoding: string = 'utf8'\n) {\n  if (!options) {\n    throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();\n  }\n\n  let key: CryptoKey;\n  if (options instanceof CryptoKey) {\n    key = options as CryptoKey;\n  } else if (isKeyObject(options)) {\n    key = getKeyObjectHandle(options as KeyObject);\n  } else {\n    key = getKeyObjectHandle(\n      createPublicKey(options as CreateAsymmetricKeyOptions)\n    );\n  }\n  if (!(key instanceof CryptoKey)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'options',\n      ['KeyObject', 'CryptoKey', 'object'],\n      key\n    );\n  }\n\n  // Options specific to RSA\n  const rsaPadding = getIntOption('padding', options);\n  const pssSaltLength = getIntOption('saltLength', options);\n\n  // Options specific to (EC)DSA\n  const dsaSigEnc = getDSASignatureEncoding(options);\n\n  if (typeof signature === 'string') {\n    signature = Buffer.from(signature, encoding);\n  }\n\n  return this[kHandle].verify(\n    key,\n    signature,\n    rsaPadding,\n    pssSaltLength,\n    dsaSigEnc\n  );\n};\n\nexport function createSign(algorithm: string, options: any) {\n  return new Sign(algorithm, options);\n}\n\nexport function createVerify(algorithm: string, options: any) {\n  return new Verify(algorithm, options);\n}\n\ntype SignCallback = (err: any, signature?: Buffer) => void;\ntype VerifyCallback = (err: any, valid?: boolean) => void;\n\nexport function sign(\n  algorithm: string | null | undefined,\n  data: BufferSource,\n  options: any,\n  callback?: SignCallback\n): Buffer | void {\n  if (algorithm != null) {\n    validateString(algorithm, 'algorithm');\n  } else {\n    algorithm = undefined;\n  }\n  if (!isArrayBufferView(data)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'data',\n      ['Buffer', 'TypedArray', 'DataView'],\n      data\n    );\n  }\n\n  if (!options) {\n    throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();\n  }\n\n  const key = getPrivateKey(options);\n\n  // Options specific to RSA\n  const rsaPadding = getIntOption('padding', options);\n  const pssSaltLength = getIntOption('saltLength', options);\n\n  // Options specific to (EC)DSA\n  const dsaSigEnc = getDSASignatureEncoding(options);\n\n  if (callback === undefined) {\n    return Buffer.from(\n      cryptoImpl.signOneShot(\n        key,\n        algorithm,\n        data,\n        rsaPadding,\n        pssSaltLength,\n        dsaSigEnc\n      )\n    );\n  }\n\n  try {\n    const signature = Buffer.from(\n      cryptoImpl.signOneShot(\n        key,\n        algorithm,\n        data,\n        rsaPadding,\n        pssSaltLength,\n        dsaSigEnc\n      )\n    );\n    queueMicrotask(() => callback(null, signature));\n  } catch (err) {\n    queueMicrotask(() => callback(err));\n  }\n}\n\nexport function verify(\n  algorithm: string | null | undefined,\n  data: BufferSource,\n  options: any,\n  signature: BufferSource,\n  callback?: VerifyCallback\n): boolean | undefined {\n  // Node.js allows the algorithm to be either undefined or null, in which\n  // case we just normalize it to undefined.\n  if (algorithm != null) {\n    validateString(algorithm, 'algorithm');\n  } else {\n    algorithm = undefined;\n  }\n\n  if (!isArrayBufferView(data)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'data',\n      ['Buffer', 'TypedArray', 'DataView'],\n      data\n    );\n  }\n\n  if (!isArrayBufferView(signature)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'signature',\n      ['Buffer', 'TypedArray', 'DataView'],\n      signature\n    );\n  }\n\n  if (!options) {\n    throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();\n  }\n\n  let key: CryptoKey;\n  if (options instanceof CryptoKey) {\n    key = options;\n  } else if (isKeyObject(options)) {\n    key = getKeyObjectHandle(options as KeyObject);\n  } else {\n    key = getKeyObjectHandle(\n      createPublicKey(options as CreateAsymmetricKeyOptions)\n    );\n  }\n  if (!(key instanceof CryptoKey)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'options',\n      ['KeyObject', 'CryptoKey', 'object'],\n      key\n    );\n  }\n\n  // Options specific to RSA\n  const rsaPadding = getIntOption('padding', options);\n  const pssSaltLength = getIntOption('saltLength', options);\n\n  // Options specific to (EC)DSA\n  const dsaSigEnc = getDSASignatureEncoding(options);\n\n  if (callback === undefined) {\n    return cryptoImpl.verifyOneShot(\n      key,\n      algorithm,\n      data,\n      signature,\n      rsaPadding,\n      pssSaltLength,\n      dsaSigEnc\n    );\n  }\n\n  try {\n    callback(\n      null,\n      cryptoImpl.verifyOneShot(\n        key,\n        algorithm,\n        data,\n        signature,\n        rsaPadding,\n        pssSaltLength,\n        dsaSigEnc\n      )\n    );\n  } catch (err) {\n    queueMicrotask(() => callback(err));\n  }\n  return; // explicit return is necessary to squelch typescript warning.\n}\n"
  },
  {
    "path": "src/node/internal/crypto_spkac.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { default as cryptoImpl } from 'node-internal:crypto';\nimport { Buffer } from 'node-internal:internal_buffer';\n\nimport { getArrayBufferOrView } from 'node-internal:crypto_util';\n\nexport function verifySpkac(\n  spkac: Buffer | ArrayBuffer | ArrayBufferView | string,\n  encoding?: string\n): boolean {\n  return cryptoImpl.verifySpkac(getArrayBufferOrView(spkac, 'spkac', encoding));\n}\n\nexport function exportPublicKey(\n  spkac: Buffer | ArrayBuffer | ArrayBufferView | string,\n  encoding?: string\n): Buffer {\n  const ret = cryptoImpl.exportPublicKey(\n    getArrayBufferOrView(spkac, 'spkac', encoding)\n  );\n  return ret ? Buffer.from(ret) : Buffer.alloc(0);\n}\n\nexport function exportChallenge(\n  spkac: Buffer | ArrayBuffer | ArrayBufferView | string,\n  encoding?: string\n): Buffer {\n  const ret = cryptoImpl.exportChallenge(\n    getArrayBufferOrView(spkac, 'spkac', encoding)\n  );\n  return ret ? Buffer.from(ret) : Buffer.alloc(0);\n}\n\n// The legacy implementation of this exposed the Certificate\n// object and required that users create an instance before\n// calling the member methods. This API pattern has been\n// deprecated, however, as the method implementations do not\n// rely on any object state.\n\nexport declare class Certificate {\n  constructor();\n  verifySpkac: typeof verifySpkac;\n  exportPublicKey: typeof exportPublicKey;\n  exportChallenge: typeof exportChallenge;\n  static verifySpkac: typeof verifySpkac;\n  static exportPublicKey: typeof exportPublicKey;\n  static exportChallenge: typeof exportChallenge;\n}\n\n// For backwards compatibility reasons, this cannot be converted into a\n// ES6 Class.\nexport function Certificate(this: unknown): Certificate {\n  if (!(this instanceof Certificate)) {\n    return new Certificate();\n  }\n  return this;\n}\n\nCertificate.prototype.verifySpkac = verifySpkac;\nCertificate.prototype.exportPublicKey = exportPublicKey;\nCertificate.prototype.exportChallenge = exportChallenge;\n\nCertificate.exportChallenge = exportChallenge;\nCertificate.exportPublicKey = exportPublicKey;\nCertificate.verifySpkac = verifySpkac;\n"
  },
  {
    "path": "src/node/internal/crypto_util.ts",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { Buffer } from 'node-internal:internal_buffer';\n\nimport {\n  isAnyArrayBuffer,\n  isArrayBufferView,\n} from 'node-internal:internal_types';\n\nimport { ERR_INVALID_ARG_TYPE } from 'node-internal:internal_errors';\n\nimport { validateString } from 'node-internal:validators';\n\nimport { default as cryptoImpl } from 'node-internal:crypto';\ntype ArrayLike = cryptoImpl.ArrayLike;\n\nexport const kHandle = Symbol('kHandle');\nexport const kFinalized = Symbol('kFinalized');\nexport const kState = Symbol('kState');\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function getStringOption(options: any, key: string): string | undefined {\n  let value: string | undefined;\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition,@typescript-eslint/no-unsafe-member-access\n  if (options && (value = options[key] as string) != null)\n    validateString(value, `options.${key}`);\n  return value;\n}\n\nexport function getArrayBufferOrView(\n  buffer: CryptoKey | Buffer | ArrayBuffer | ArrayBufferView | string,\n  name: string,\n  encoding?: string\n): Buffer | ArrayBuffer | ArrayBufferView {\n  if (isAnyArrayBuffer(buffer)) return buffer;\n  if (typeof buffer === 'string') {\n    if (encoding === undefined || encoding === 'buffer') {\n      encoding = 'utf8';\n    }\n    return Buffer.from(buffer, encoding);\n  }\n  if (!isArrayBufferView(buffer)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      name,\n      ['string', 'ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],\n      buffer\n    );\n  }\n  return buffer;\n}\n\n/**\n * 48 is the ASCII code for '0', 97 is the ASCII code for 'a'.\n * @param {number} number An integer between 0 and 15.\n * @returns {number} corresponding to the ASCII code of the hex representation\n *                   of the parameter.\n */\nexport const numberToHexCharCode = (number: number): number =>\n  (number < 10 ? 48 : 87) + number;\n\n/**\n * @param {ArrayBuffer} buf An ArrayBuffer.\n * @return {bigint}\n */\nexport function arrayBufferToUnsignedBigInt(buf: ArrayBuffer): bigint {\n  const length = buf.byteLength;\n  const chars = new Array<number>(length * 2);\n  const view = new DataView(buf);\n\n  for (let i = 0; i < length; i++) {\n    const val = view.getUint8(i);\n    chars[2 * i] = numberToHexCharCode(val >> 4);\n    chars[2 * i + 1] = numberToHexCharCode(val & 0xf);\n  }\n\n  return BigInt(`0x${String.fromCharCode.apply(null, chars)}`);\n}\n\n// This is here because many functions accepted binary strings without\n// any explicit encoding in older versions of node, and we don't want\n// to break them unnecessarily.\nexport function toBuf(\n  val: ArrayLike,\n  encoding?: string\n): Buffer | ArrayBuffer | ArrayBufferView {\n  if (typeof val === 'string') {\n    if (encoding === 'buffer') {\n      encoding = 'utf8';\n    }\n    return Buffer.from(val, encoding);\n  }\n  return val;\n}\n\nexport function validateByteSource(\n  val: ArrayLike,\n  name: string\n): Buffer | ArrayBuffer | ArrayBufferView {\n  val = toBuf(val);\n\n  if (isAnyArrayBuffer(val) || isArrayBufferView(val)) {\n    return val;\n  }\n\n  throw new ERR_INVALID_ARG_TYPE(\n    name,\n    ['string', 'ArrayBuffer', 'TypedArray', 'DataView', 'Buffer'],\n    val\n  );\n}\n"
  },
  {
    "path": "src/node/internal/crypto_x509.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n/* TODO: the following is adopted code, enabling linting one day */\n/* eslint-disable */\n\nimport { default as cryptoImpl, type CheckOptions } from 'node-internal:crypto';\n\nimport {\n  validateString,\n  validateObject,\n  validateBoolean,\n} from 'node-internal:validators';\n\nimport { isArrayBufferView } from 'node-internal:internal_types';\n\nimport { Buffer } from 'node-internal:internal_buffer';\n\nimport {\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_ARG_VALUE,\n} from 'node-internal:internal_errors';\n\nimport { kHandle } from 'node-internal:crypto_util';\n\nimport { PublicKeyObject, PrivateKeyObject } from 'node-internal:crypto_keys';\n\nfunction translatePeerCertificate(c: any) {\n  if (!c) return null;\n\n  if (c.issuerCertificate != null && c.issuerCertificate !== c) {\n    c.issuerCertificate = translatePeerCertificate(c.issuerCertificate);\n  }\n  if (c.infoAccess != null) {\n    const info = c.infoAccess;\n    c.infoAccess = {};\n\n    // XXX: More key validation?\n    const regex = /([^\\n:]*):([^\\n]*)(?:\\n|$)/g;\n    regex[Symbol.replace](info, (_: any, key: any, val: any): any => {\n      if (val.charCodeAt(0) === 0x22) {\n        // The translatePeerCertificate function is only\n        // used on internally created legacy certificate\n        // objects, and any value that contains a quote\n        // will always be a valid JSON string literal,\n        // so this should never throw.\n        val = JSON.parse(val);\n      }\n      if (key in c.infoAccess) c.infoAccess[key].push(val);\n      else c.infoAccess[key] = [val];\n    });\n  }\n  return c;\n}\n\nfunction checkOptions(options?: CheckOptions) {\n  if (options == null) return;\n  validateObject(options, 'options');\n  if (options.multiLabelWildcards !== undefined)\n    validateBoolean(options.multiLabelWildcards, 'options.multiLabelWildcards');\n  if (options.partialWildcards !== undefined)\n    validateBoolean(options.partialWildcards, 'options.partialWildcards');\n  if (options.singleLabelSubdomains !== undefined)\n    validateBoolean(\n      options.singleLabelSubdomains,\n      'options.singleLabelSubdomains'\n    );\n  if (options.wildcards !== undefined)\n    validateBoolean(options.wildcards, 'options.wildcards');\n  if (options.subject !== undefined)\n    validateString(options.subject, 'options.subject');\n}\n\nexport class X509Certificate {\n  #handle?: cryptoImpl.X509Certificate = undefined;\n  #state = new Map();\n\n  constructor(\n    buffer: ArrayBufferView | ArrayBuffer | cryptoImpl.X509Certificate | string\n  ) {\n    if (buffer instanceof cryptoImpl.X509Certificate) {\n      this.#handle = buffer;\n      return;\n    }\n    if (typeof buffer === 'string') {\n      buffer = Buffer.from(buffer);\n    }\n    if (!isArrayBufferView(buffer)) {\n      throw new ERR_INVALID_ARG_TYPE(\n        'buffer',\n        ['string', 'Buffer', 'TypedArray', 'DataView'],\n        buffer\n      );\n    }\n    const handle = cryptoImpl.X509Certificate.parse(buffer);\n    if (handle == null) {\n      throw new ERR_INVALID_ARG_VALUE(\n        'buffer',\n        buffer,\n        'is not a valid certificate'\n      );\n    }\n    this.#handle = handle;\n  }\n\n  get subject() {\n    let value = this.#state.get('subject');\n    if (value === undefined) {\n      value = this.#handle!.subject;\n      this.#state.set('subject', value);\n    }\n    return value ?? undefined;\n  }\n\n  get subjectAltName() {\n    let value = this.#state.get('subjectAltName');\n    if (value === undefined) {\n      value = this.#handle!.subjectAltName;\n      this.#state.set('subjectAltName', value);\n    }\n    return value ?? undefined;\n  }\n\n  get issuer() {\n    let value = this.#state.get('issuer');\n    if (value === undefined) {\n      value = this.#handle!.issuer;\n      this.#state.set('issuer', value);\n    }\n    return value ?? undefined;\n  }\n\n  get issuerCertificate() {\n    let value = this.#state.get('issuerCertificate');\n    if (value === undefined) {\n      const cert = this.#handle!.issuerCert;\n      if (cert) value = new X509Certificate(cert);\n      this.#state.set('issuerCertificate', value);\n    }\n    return value ?? undefined;\n  }\n\n  get infoAccess() {\n    let value = this.#state.get('infoAccess');\n    if (value === undefined) {\n      value = this.#handle!.infoAccess;\n      this.#state.set('infoAccess', value);\n    }\n    return value ?? undefined;\n  }\n\n  get validFrom() {\n    let value = this.#state.get('validFrom');\n    if (value === undefined) {\n      value = this.#handle!.validFrom;\n      this.#state.set('validFrom', value);\n    }\n    return value ?? undefined;\n  }\n\n  get validTo() {\n    let value = this.#state.get('validTo');\n    if (value === undefined) {\n      value = this.#handle!.validTo;\n      this.#state.set('validTo', value);\n    }\n    return value ?? undefined;\n  }\n\n  get fingerprint() {\n    let value = this.#state.get('fingerprint');\n    if (value === undefined) {\n      value = this.#handle!.fingerprint;\n      this.#state.set('fingerprint', value);\n    }\n    return value ?? undefined;\n  }\n\n  get fingerprint256() {\n    let value = this.#state.get('fingerprint256');\n    if (value === undefined) {\n      value = this.#handle!.fingerprint256;\n      this.#state.set('fingerprint256', value);\n    }\n    return value ?? undefined;\n  }\n\n  get fingerprint512() {\n    let value = this.#state.get('fingerprint512');\n    if (value === undefined) {\n      value = this.#handle!.fingerprint512;\n      this.#state.set('fingerprint512', value);\n    }\n    return value ?? undefined;\n  }\n\n  get keyUsage() {\n    let value = this.#state.get('keyUsage');\n    if (value === undefined) {\n      value = this.#handle!.keyUsage;\n      this.#state.set('keyUsage', value);\n    }\n    return value ?? undefined;\n  }\n\n  get serialNumber() {\n    let value = this.#state.get('serialNumber');\n    if (value === undefined) {\n      value = this.#handle!.serialNumber;\n      if (value != null) value = value.toUpperCase();\n      this.#state.set('serialNumber', value);\n    }\n    return value ?? undefined;\n  }\n\n  get raw() {\n    let value = this.#state.get('raw');\n    if (value === undefined) {\n      value = this.#handle!.raw;\n      if (value != null) value = Buffer.from(value);\n      this.#state.set('raw', value);\n    }\n    return value ?? undefined;\n  }\n\n  get publicKey() {\n    let value = this.#state.get('publicKey');\n    if (value === undefined) {\n      const inner = this.#handle!.publicKey;\n      if (inner !== undefined) {\n        value = PublicKeyObject.from(inner);\n        this.#state.set('publicKey', value);\n      }\n    }\n    return value ?? undefined;\n  }\n\n  toString() {\n    let value = this.#state.get('pem');\n    if (value === undefined) {\n      value = this.#handle!.pem;\n      this.#state.set('pem', value);\n    }\n    return value ?? undefined;\n  }\n\n  // There's no standardized JSON encoding for X509 certs so we\n  // fallback to providing the PEM encoding as a string.\n  toJSON() {\n    return this.toString();\n  }\n\n  get ca() {\n    let value = this.#state.get('ca');\n    if (value === undefined) {\n      value = this.#handle!.isCA;\n      this.#state.set('ca', value);\n    }\n    return value ?? false;\n  }\n\n  checkHost(name: string, options?: CheckOptions) {\n    validateString(name, 'name');\n    checkOptions(options);\n    return this.#handle!.checkHost(name, options) ?? undefined;\n  }\n\n  checkEmail(email: string, options?: CheckOptions) {\n    validateString(email, 'email');\n    checkOptions(options);\n    return this.#handle!.checkEmail(email, options) ?? undefined;\n  }\n\n  checkIP(ip: string, options?: CheckOptions) {\n    validateString(ip, 'ip');\n    checkOptions(options);\n    // The options argument is currently undocumented since none of the options\n    // have any effect on the behavior of this function. However, we still parse\n    // the options argument in case OpenSSL adds flags in the future that do\n    // affect the behavior of X509_check_ip. This ensures that no invalid values\n    // are passed as the second argument in the meantime.\n    return this.#handle!.checkIp(ip, options) ?? undefined;\n  }\n\n  checkIssued(otherCert: X509Certificate) {\n    if (!(otherCert instanceof X509Certificate))\n      throw new ERR_INVALID_ARG_TYPE('otherCert', 'X509Certificate', otherCert);\n    return this.#handle!.checkIssued(otherCert.#handle!) ?? undefined;\n  }\n\n  checkPrivateKey(pkey: PrivateKeyObject) {\n    if (!(pkey instanceof PrivateKeyObject))\n      throw new ERR_INVALID_ARG_TYPE('pkey', 'KeyObject', pkey);\n    if (pkey.type !== 'private') throw new ERR_INVALID_ARG_VALUE('pkey', pkey);\n    return this.#handle!.checkPrivateKey(pkey[kHandle]) ?? undefined;\n  }\n\n  verify(pkey: PublicKeyObject) {\n    if (!(pkey instanceof PublicKeyObject))\n      throw new ERR_INVALID_ARG_TYPE('pkey', 'KeyObject', pkey);\n    if (pkey.type !== 'public') throw new ERR_INVALID_ARG_VALUE('pkey', pkey);\n    return this.#handle!.verify(pkey[kHandle]);\n  }\n\n  toLegacyObject() {\n    let value = this.#state.get('legacy');\n    if (value === undefined) {\n      let {\n        subject,\n        subjectAltName,\n        infoAccess,\n        issuer,\n        ca,\n        modulus,\n        bits,\n        exponent,\n        pubkey,\n        asn1Curve,\n        nistCurve,\n        valid_from,\n        valid_to,\n        fingerprint,\n        fingerprint256,\n        fingerprint512,\n        serialNumber,\n        ext_key_usage,\n        raw,\n      } = this.#handle!.toLegacyObject() as any;\n      if (raw != null) raw = Buffer.from(raw);\n      if (pubkey != null) pubkey = Buffer.from(pubkey);\n      if (modulus != null) modulus = modulus.toUpperCase();\n      if (fingerprint != null) fingerprint = fingerprint.toUpperCase();\n      if (fingerprint256 != null) fingerprint256 = fingerprint256.toUpperCase();\n      if (fingerprint512 != null) fingerprint512 = fingerprint512.toUpperCase();\n      if (serialNumber != null) serialNumber = serialNumber.toUpperCase();\n      value = translatePeerCertificate({\n        subject,\n        subjectAltName,\n        infoAccess,\n        issuer,\n        ca,\n        modulus,\n        bits,\n        exponent,\n        pubkey,\n        asn1Curve,\n        nistCurve,\n        valid_from,\n        valid_to,\n        fingerprint,\n        fingerprint256,\n        fingerprint512,\n        serialNumber,\n        ext_key_usage,\n        raw,\n      });\n      this.#state.set('legacy', value);\n    }\n    return value;\n  }\n}\n"
  },
  {
    "path": "src/node/internal/debuglog.ts",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n/* TODO: the following is adopted code, enabling linting one day */\n/* eslint-disable */\n\nimport { format, formatWithOptions } from 'node-internal:internal_inspect';\n\nlet debugImpls: object = {};\n\nfunction debuglogImpl(set: string) {\n  if ((debugImpls as any)[set] === undefined) {\n    (debugImpls as any)[set] = function debug(...args: any[]) {\n      const msg = formatWithOptions({}, ...args);\n      console.log(format('%s: %s\\n', set, msg));\n    };\n  }\n  return (debugImpls as any)[set];\n}\n\n// In Node.js' implementation, debuglog availability is determined by the NODE_DEBUG\n// environment variable. However, we don't have access to the environment variables\n// in the same way. Instead, we'll just always enable debuglog on the requested sets.\nexport function debuglog(\n  set: string,\n  cb?: (debug: (...args: any[]) => void) => void\n): any {\n  function init() {\n    set = set.toUpperCase();\n  }\n  let debug = (...args: any[]): void => {\n    init();\n    debug = debuglogImpl(set);\n    if (typeof cb === 'function') {\n      cb(debug);\n    }\n    switch (args.length) {\n      case 1:\n        return debug(args[0]);\n      case 2:\n        return debug(args[0], args[1]);\n      default:\n        return debug(...args);\n    }\n  };\n  const logger = (...args: any[]) => {\n    switch (args.length) {\n      case 1:\n        return debug(args[0]);\n      case 2:\n        return debug(args[0], args[1]);\n      default:\n        return debug(...args);\n    }\n  };\n  Object.defineProperty(logger, 'enabled', {\n    get() {\n      return true;\n    },\n    configurable: true,\n    enumerable: true,\n  });\n  return logger;\n}\n"
  },
  {
    "path": "src/node/internal/diagnostics_channel.d.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n\nexport * from 'node:diagnostics_channel';\nexport type TransformCallback = (value: unknown) => unknown;\nexport type MessageCallback = (message: unknown, name: string | symbol) => void;\n"
  },
  {
    "path": "src/node/internal/dns.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport function parseCaaRecord(record: string): {\n  critical: number;\n  field: 'issue' | 'iodef' | 'issuewild';\n  value: string;\n};\n\nexport function parseNaptrRecord(record: string): {\n  flags: string;\n  service: string;\n  regexp: string;\n  replacement: string;\n  order: number;\n  preference: number;\n};\n"
  },
  {
    "path": "src/node/internal/events.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Deno and Node.js:\n// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n/* eslint-disable */\n\nimport {\n  AbortError,\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_THIS,\n  ERR_OUT_OF_RANGE,\n  ERR_UNHANDLED_ERROR,\n} from 'node-internal:internal_errors';\nimport type {\n  EventEmitterAsyncResource as _EventEmitterAsyncResource,\n  EventEmitter as _EventEmitter,\n} from 'node:events';\nimport {\n  validateAbortSignal,\n  validateBoolean,\n  validateFunction,\n  validateObject,\n} from 'node-internal:validators';\nimport { spliceOne } from 'node-internal:internal_utils';\nimport { nextTick, emitWarning } from 'node-internal:internal_process';\nimport { default as async_hooks } from 'node-internal:async_hooks';\nconst { AsyncResource } = async_hooks;\n\nimport { inspect } from 'node-internal:internal_inspect';\n\nconst enableNodejsProcessV2 =\n  !!Cloudflare.compatibilityFlags['enable_nodejs_process_v2'];\n\nconst kRejection = Symbol.for('nodejs.rejection');\nconst kCapture = Symbol('kCapture');\nconst kErrorMonitor = Symbol('events.errorMonitor');\nconst kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners');\nconst kMaxEventTargetListenersWarned = Symbol(\n  'events.maxEventTargetListenersWarned'\n);\n\nexport interface EventEmitterOptions {\n  captureRejections?: boolean;\n}\n\nexport type EventName = string | symbol | number;\nexport type EventCallback = ((...args: any[]) => unknown) & {\n  listener?: EventCallback;\n};\n// @ts-expect-error TS2417 Unrelated error.\nexport declare class EventEmitter extends _EventEmitter {\n  constructor(opts?: EventEmitterOptions);\n  listenerCount(eventName: EventName): number;\n  emit(eventName: EventName, ...args: any[]): boolean;\n  on(eventName: EventName, listener: EventCallback): this;\n  once(eventName: EventName, listener: EventCallback): this;\n  addEventListener(eventName: EventName, listener: EventCallback): this;\n  removeEventListener(eventName: EventName, listener: EventCallback): this;\n\n  [kRejection](err: unknown, eventName: EventName, ...args: unknown[]): void;\n  [kCapture]: boolean;\n\n  _events: undefined | Record<EventName, EventCallback[]>;\n  _eventsCount: number;\n  _maxListeners: undefined | number;\n}\n\ntype AsyncResource = typeof AsyncResource;\n\ndeclare var EventTarget: Function;\n\nexport function EventEmitter(\n  this: EventEmitter,\n  opts?: EventEmitterOptions\n): EventEmitter {\n  EventEmitter.init.call(this, opts);\n  return this;\n}\n\nclass EventEmitterReferencingAsyncResource extends AsyncResource {\n  #eventEmitter: EventEmitter;\n  constructor(emitter: EventEmitter) {\n    super('');\n    this.#eventEmitter = emitter;\n  }\n\n  get eventEmitter() {\n    if (this.#eventEmitter === undefined)\n      throw new ERR_INVALID_THIS('EventEmitterReferencingAsyncResource');\n    return this.#eventEmitter;\n  }\n}\n\nexport class EventEmitterAsyncResource\n  extends EventEmitter\n  implements _EventEmitterAsyncResource\n{\n  #asyncResource: EventEmitterReferencingAsyncResource;\n\n  constructor(options?: EventEmitterOptions) {\n    super(options);\n    this.#asyncResource = new EventEmitterReferencingAsyncResource(this);\n  }\n\n  // @ts-expect-error TS2416 Not assignable to base type\n  get asyncResource(): AsyncResource {\n    if (this.#asyncResource === undefined)\n      throw new ERR_INVALID_THIS('EventEmitterAsyncResource');\n    // @ts-expect-error TS2741 Prototype is missing from type.\n    return this.#asyncResource;\n  }\n\n  override emit(event: string | symbol, ...args: any[]): boolean {\n    if (this.#asyncResource === undefined)\n      throw new ERR_INVALID_THIS('EventEmitterAsyncResource');\n    args.unshift(super.emit, this, event);\n    Reflect.apply(\n      this.#asyncResource.runInAsyncScope,\n      this.#asyncResource,\n      args\n    );\n    return true;\n  }\n}\n\nexport function addAbortListener(\n  signal: AbortSignal | undefined,\n  listener: any\n) {\n  if (signal === undefined) {\n    throw new ERR_INVALID_ARG_TYPE('signal', 'AbortSignal', signal);\n  }\n  validateAbortSignal(signal, 'signal');\n  validateFunction(listener, 'listener');\n\n  let removeEventListener: Function;\n  if (signal.aborted) {\n    queueMicrotask(() => listener());\n  } else {\n    signal.addEventListener('abort', listener, { once: true });\n    removeEventListener = () => {\n      signal.removeEventListener('abort', listener);\n    };\n  }\n  return {\n    __proto__: null,\n    [Symbol.dispose]() {\n      removeEventListener?.();\n    },\n  };\n}\n\nexport default EventEmitter;\n\nEventEmitter.on = on;\nEventEmitter.once = once;\nEventEmitter.getEventListeners = getEventListeners;\nEventEmitter.getMaxListeners = _getMaxListeners;\nEventEmitter.setMaxListeners = setMaxListeners;\nEventEmitter.listenerCount = listenerCount;\nEventEmitter.EventEmitter = EventEmitter;\nEventEmitter.usingDomains = false;\nEventEmitter.captureRejectionSymbol = kRejection;\nEventEmitter.errorMonitor = kErrorMonitor;\nEventEmitter.EventEmitterAsyncResource = EventEmitterAsyncResource;\n\nexport const captureRejectionSymbol = EventEmitter.captureRejectionSymbol;\nexport const errorMonitor = EventEmitter.errorMonitor;\nexport const getMaxListeners = _getMaxListeners;\nexport const usingDomains = EventEmitter.usingDomains;\nexport let defaultMaxListeners = 10;\n\nObject.defineProperties(EventEmitter, {\n  captureRejections: {\n    get(this: EventEmitter) {\n      return EventEmitter.prototype[kCapture];\n    },\n    set(this: EventEmitter, value: unknown): void {\n      validateBoolean(value, 'EventEmitter.captureRejections');\n\n      EventEmitter.prototype[kCapture] = value;\n    },\n    enumerable: true,\n  },\n  defaultMaxListeners: {\n    enumerable: true,\n    get: function (this: EventEmitter): number {\n      return defaultMaxListeners;\n    },\n    set: function (this: EventEmitter, arg: unknown): void {\n      if (typeof arg !== 'number' || arg < 0 || Number.isNaN(arg)) {\n        throw new ERR_OUT_OF_RANGE(\n          'defaultMaxListeners',\n          'a non-negative number',\n          arg\n        );\n      }\n      defaultMaxListeners = arg;\n    },\n  },\n  kMaxEventTargetListeners: {\n    value: kMaxEventTargetListeners,\n    enumerable: false,\n    configurable: false,\n    writable: false,\n  },\n  kMaxEventTargetListenersWarned: {\n    value: kMaxEventTargetListenersWarned,\n    enumerable: false,\n    configurable: false,\n    writable: false,\n  },\n});\n\n// The default for captureRejections is false\nObject.defineProperty(EventEmitter.prototype, kCapture, {\n  value: false,\n  writable: true,\n  enumerable: false,\n});\n\nEventEmitter.init = function (this: EventEmitter, opts?: EventEmitterOptions) {\n  if (\n    this._events === undefined ||\n    this._events === Object.getPrototypeOf(this)._events\n  ) {\n    this._events = Object.create(null);\n    this._eventsCount = 0;\n  }\n\n  (this as any)._maxListeners ??= undefined;\n\n  if (opts?.captureRejections) {\n    validateBoolean(opts.captureRejections, 'options.captureRejections');\n    (this as any)[kCapture] = Boolean(opts.captureRejections);\n  } else {\n    // Assigning the kCapture property directly saves an expensive\n    // prototype lookup in a very sensitive hot path.\n    (this as any)[kCapture] = EventEmitter.prototype[kCapture];\n  }\n};\n\nexport function setMaxListeners(\n  n = defaultMaxListeners,\n  ...eventTargets: any[]\n) {\n  if (typeof n !== 'number' || n < 0 || Number.isNaN(n)) {\n    throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n);\n  }\n  if (eventTargets.length === 0) {\n    defaultMaxListeners = n;\n  } else {\n    for (let i = 0; i < eventTargets.length; i++) {\n      const target = eventTargets[i];\n      if (target instanceof EventTarget) {\n        (target as any)[kMaxEventTargetListeners] = n;\n        (target as any)[kMaxEventTargetListenersWarned] = false;\n      } else if (typeof target.setMaxListeners === 'function') {\n        target.setMaxListeners(n);\n      } else {\n        throw new ERR_INVALID_ARG_TYPE(\n          'eventTargets',\n          ['EventEmitter', 'EventTarget'],\n          target\n        );\n      }\n    }\n  }\n}\n\nEventEmitter.prototype._events = undefined;\nEventEmitter.prototype._eventsCount = 0;\nEventEmitter.prototype._maxListeners = undefined;\nEventEmitter.addAbortListener = addAbortListener;\n\nfunction addCatch(\n  that: any,\n  promise: Promise<unknown>,\n  type: string | symbol,\n  args: any[]\n) {\n  if (!that[kCapture]) {\n    return;\n  }\n\n  // Handle Promises/A+ spec, then could be a getter\n  // that throws on second use.\n  try {\n    const then = promise.then;\n\n    if (typeof then === 'function') {\n      then.call(promise, undefined, function (err) {\n        // The callback is called with nextTick to avoid a follow-up\n        // rejection from this promise.\n        nextTick(emitUnhandledRejectionOrErr, that, err, type, args);\n      });\n    }\n  } catch (err) {\n    that.emit('error', err);\n  }\n}\n\nfunction emitUnhandledRejectionOrErr(\n  ee: any,\n  err: any,\n  type: string | symbol,\n  args: any[]\n) {\n  if (typeof ee[kRejection] === 'function') {\n    ee[kRejection](err, type, ...args);\n  } else {\n    // We have to disable the capture rejections mechanism, otherwise\n    // we might end up in an infinite loop.\n    const prev = ee[kCapture];\n\n    // If the error handler throws, it is not catchable and it\n    // will end up in 'uncaughtException'. We restore the previous\n    // value of kCapture in case the uncaughtException is present\n    // and the exception is handled.\n    try {\n      ee[kCapture] = false;\n      ee.emit('error', err);\n    } finally {\n      ee[kCapture] = prev;\n    }\n  }\n}\n\nEventEmitter.prototype.setMaxListeners = function setMaxListeners(n: number) {\n  if (typeof n !== 'number' || n < 0 || Number.isNaN(n)) {\n    throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n);\n  }\n  this._maxListeners = n;\n  return this;\n};\n\nfunction _getMaxListeners(that: any) {\n  if (that._maxListeners === undefined) {\n    return (EventEmitter as any).defaultMaxListeners;\n  }\n  return that._maxListeners;\n}\n\nEventEmitter.prototype.getMaxListeners = function getMaxListeners(\n  this: EventEmitter\n): number {\n  return _getMaxListeners(this);\n};\n\nEventEmitter.prototype.emit = function emit(\n  this: EventEmitter,\n  type: string | symbol,\n  ...args: any[]\n): boolean {\n  let doError = type === 'error';\n\n  const events = this._events;\n  if (events !== undefined) {\n    if (doError && events[kErrorMonitor] !== undefined) {\n      this.emit(kErrorMonitor, ...args);\n    }\n    doError = doError && events.error === undefined;\n  } else if (!doError) {\n    return false;\n  }\n\n  // If there is no 'error' event listener then throw.\n  if (doError) {\n    let er;\n    if (args.length > 0) {\n      er = args[0];\n    }\n    if (er instanceof Error) {\n      try {\n        const capture = {};\n        (Error as any).captureStackTrace(capture, EventEmitter.prototype.emit);\n      } catch {\n        // pass\n      }\n\n      // Note: The comments on the `throw` lines are intentional, they show\n      // up in Node's output if this results in an unhandled exception.\n      throw er; // Unhandled 'error' event\n    }\n\n    let stringifiedEr;\n    try {\n      stringifiedEr = inspect(er);\n    } catch {\n      stringifiedEr = er;\n    }\n\n    // At least give some kind of context to the user\n    const err = new ERR_UNHANDLED_ERROR(stringifiedEr);\n    (err as any).context = er;\n    throw err; // Unhandled 'error' event\n  }\n\n  const handler = events?.[type];\n\n  if (handler === undefined) {\n    return false;\n  }\n\n  if (typeof handler === 'function') {\n    const result = (handler as Function).apply(this, args);\n\n    // We check if result is undefined first because that\n    // is the most common case so we do not pay any perf\n    // penalty\n    if (result !== undefined && result !== null) {\n      addCatch(this, result, type, args);\n    }\n  } else {\n    const len = handler.length;\n    const listeners = arrayClone(handler);\n    for (let i = 0; i < len; ++i) {\n      const result = listeners[i].apply(this, args);\n\n      // We check if result is undefined first because that\n      // is the most common case so we do not pay any perf\n      // penalty.\n      // This code is duplicated because extracting it away\n      // would make it non-inlineable.\n      if (result !== undefined && result !== null) {\n        addCatch(this, result, type, args);\n      }\n    }\n  }\n\n  return true;\n};\n\nfunction _addListener(\n  target: any,\n  type: string | symbol,\n  listener: unknown,\n  prepend: boolean\n) {\n  let m;\n  let events;\n  let existing;\n\n  validateFunction(listener, 'listener');\n\n  events = target._events;\n  if (events === undefined) {\n    events = target._events = Object.create(null);\n    target._eventsCount = 0;\n  } else {\n    // To avoid recursion in the case that type === \"newListener\"! Before\n    // adding it to the listeners, first emit \"newListener\".\n    if (events.newListener !== undefined) {\n      target.emit('newListener', type, (listener as any).listener ?? listener);\n\n      // Re-assign `events` because a newListener handler could have caused the\n      // this._events to be assigned to a new object\n      events = target._events;\n    }\n    existing = events[type];\n  }\n\n  if (existing === undefined) {\n    // Optimize the case of one listener. Don't need the extra array object.\n    events[type] = listener;\n    ++target._eventsCount;\n  } else {\n    if (typeof existing === 'function') {\n      // Adding the second element, need to change to array.\n      existing = events[type] = prepend\n        ? [listener, existing]\n        : [existing, listener];\n      // If we've already got an array, just append.\n    } else if (prepend) {\n      existing.unshift(listener);\n    } else {\n      existing.push(listener);\n    }\n\n    // Check for listener leak\n    m = _getMaxListeners(target);\n    if (m > 0 && existing.length > m && !existing.warned) {\n      existing.warned = true;\n      console.log(\n        'Possible EventEmitter memory leak detected. ' +\n          `${existing.length} ${String(type)} listeners ` +\n          `added to an EventEmitter. Use ` +\n          'emitter.setMaxListeners() to increase limit'\n      );\n      const w: EventEmitterError = Object.assign(\n        new Error(\n          'Possible EventEmitter memory leak detected. ' +\n            `${existing.length} ${String(type)} listeners ` +\n            `added to ${inspect(target, { depth: -1 })}. Use ` +\n            'emitter.setMaxListeners() to increase limit'\n        ),\n        {\n          name: 'MaxListenersExceededWarning',\n          emitter: target,\n          type: type,\n          count: existing.length,\n        }\n      );\n      // Only the newer process version compat adds process.emitWarning support.\n      if (enableNodejsProcessV2) emitWarning(w);\n    }\n  }\n\n  return target;\n}\n\ninterface EventEmitterError extends Error {\n  name: string;\n  emitter: unknown;\n  type: string | symbol;\n  count: number;\n}\n\nEventEmitter.prototype.addListener = function addListener(\n  this: EventEmitter,\n  type: string | symbol,\n  listener: unknown\n) {\n  return _addListener(this, type, listener, false);\n};\n\nEventEmitter.prototype.on = EventEmitter.prototype.addListener;\n\nEventEmitter.prototype.prependListener = function prependListener(\n  type: string | symbol,\n  listener: unknown\n) {\n  return _addListener(this, type, listener, true);\n};\n\nfunction onceWrapper(this: any) {\n  if (!this.fired) {\n    this.target.removeListener(this.type, this.wrapFn);\n    this.fired = true;\n    if (arguments.length === 0) {\n      return this.listener.call(this.target);\n    }\n    return this.listener.apply(this.target, arguments);\n  }\n}\n\nfunction _onceWrap(target: any, type: string | symbol, listener: unknown) {\n  const state = { fired: false, wrapFn: undefined, target, type, listener };\n  const wrapped = onceWrapper.bind(state);\n  (wrapped as any).listener = listener;\n  (state as any).wrapFn = wrapped;\n  return wrapped;\n}\n\nEventEmitter.prototype.once = function once(\n  type: string | symbol,\n  listener: unknown\n) {\n  validateFunction(listener, 'listener');\n\n  this.on(type, _onceWrap(this, type, listener));\n  return this;\n};\n\nEventEmitter.prototype.prependOnceListener = function prependOnceListener(\n  type: string | symbol,\n  listener: unknown\n) {\n  validateFunction(listener, 'listener');\n\n  this.prependListener(type, _onceWrap(this, type, listener));\n  return this;\n};\n\nEventEmitter.prototype.removeListener = function removeListener(\n  type: string | symbol,\n  listener: unknown\n) {\n  validateFunction(listener, 'listener');\n\n  const events = this._events;\n  if (events === undefined) {\n    return this;\n  }\n\n  const list = events[type] as EventCallback | EventCallback[] | undefined;\n  if (list === undefined) {\n    return this;\n  }\n\n  if (list === listener || ('listener' in list && list.listener === listener)) {\n    if (--this._eventsCount === 0) {\n      this._events = Object.create(null);\n    } else {\n      delete events[type];\n      if (events.removeListener) {\n        this.emit('removeListener', type, list.listener || listener);\n      }\n    }\n  } else if (typeof list !== 'function') {\n    let position = -1;\n\n    for (let i = list.length - 1; i >= 0; i--) {\n      if (\n        list[i] === listener ||\n        (list[i] as EventCallback).listener === listener\n      ) {\n        position = i;\n        break;\n      }\n    }\n\n    if (position < 0) {\n      return this;\n    }\n\n    if (position === 0) {\n      list.shift();\n    } else {\n      spliceOne(list, position);\n    }\n\n    if (list.length === 1) {\n      events[type] = list.at(0) as unknown as EventCallback[];\n    }\n\n    if (events.removeListener !== undefined) {\n      this.emit('removeListener', type, listener);\n    }\n  }\n\n  return this;\n};\n\nEventEmitter.prototype.off = EventEmitter.prototype.removeListener;\n\nEventEmitter.prototype.removeAllListeners = function removeAllListeners(\n  type: string | symbol\n) {\n  const events = this._events;\n  if (events === undefined) {\n    return this;\n  }\n\n  // Not listening for removeListener, no need to emit\n  if (events.removeListener === undefined) {\n    if (arguments.length === 0) {\n      this._events = Object.create(null);\n      this._eventsCount = 0;\n    } else if (events[type] !== undefined) {\n      if (--this._eventsCount === 0) {\n        this._events = Object.create(null);\n      } else {\n        delete events[type];\n      }\n    }\n    return this;\n  }\n\n  // Emit removeListener for all listeners on all events\n  if (arguments.length === 0) {\n    for (const key of Reflect.ownKeys(events)) {\n      if (key === 'removeListener') continue;\n      this.removeAllListeners(key);\n    }\n    this.removeAllListeners('removeListener');\n    this._events = Object.create(null);\n    this._eventsCount = 0;\n    return this;\n  }\n\n  const listeners = events[type];\n\n  if (typeof listeners === 'function') {\n    this.removeListener(type, listeners);\n  } else if (listeners !== undefined) {\n    // LIFO order\n    for (let i = listeners.length - 1; i >= 0; i--) {\n      this.removeListener(type, listeners[i] as EventListener);\n    }\n  }\n\n  return this;\n};\n\nfunction _listeners(target: any, type: string | symbol, unwrap: boolean) {\n  const events = target._events;\n\n  if (events === undefined) {\n    return [];\n  }\n\n  const evlistener = events[type];\n  if (evlistener === undefined) {\n    return [];\n  }\n\n  if (typeof evlistener === 'function') {\n    return unwrap ? [evlistener.listener || evlistener] : [evlistener];\n  }\n\n  return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener);\n}\n\nEventEmitter.prototype.listeners = function listeners(type: string | symbol) {\n  return _listeners(this, type, true);\n};\n\nEventEmitter.prototype.rawListeners = function rawListeners(\n  type: string | symbol\n) {\n  return _listeners(this, type, false);\n};\n\nconst _listenerCount = function listenerCount(\n  this: any,\n  type: string | symbol\n) {\n  const events = this._events;\n\n  if (events !== undefined) {\n    const evlistener = events[type];\n\n    if (typeof evlistener === 'function') {\n      return 1;\n    } else if (evlistener !== undefined) {\n      return evlistener.length;\n    }\n  }\n\n  return 0;\n};\n\nEventEmitter.prototype.listenerCount = _listenerCount;\n\nexport function listenerCount(emitter: any, type: string | symbol) {\n  if (typeof emitter.listenerCount === 'function') {\n    return emitter.listenerCount(type);\n  }\n  return _listenerCount.call(emitter, type);\n}\n\nEventEmitter.prototype.eventNames = function eventNames() {\n  return this._eventsCount > 0 ? Reflect.ownKeys(this._events || {}) : [];\n};\n\nfunction arrayClone(arr: any[]) {\n  // At least since V8 8.3, this implementation is faster than the previous\n  // which always used a simple for-loop\n  switch (arr.length) {\n    case 2:\n      return [arr[0], arr[1]];\n    case 3:\n      return [arr[0], arr[1], arr[2]];\n    case 4:\n      return [arr[0], arr[1], arr[2], arr[3]];\n    case 5:\n      return [arr[0], arr[1], arr[2], arr[3], arr[4]];\n    case 6:\n      return [arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]];\n  }\n  return arr.slice();\n}\n\nfunction unwrapListeners(arr: any[]) {\n  const ret = arrayClone(arr);\n  for (let i = 0; i < ret.length; ++i) {\n    const orig = ret[i].listener;\n    if (typeof orig === 'function') {\n      ret[i] = orig;\n    }\n  }\n  return ret;\n}\n\nexport function getEventListeners(emitterOrTarget: any, type: string | symbol) {\n  // First check if EventEmitter\n  if (typeof emitterOrTarget.listeners === 'function') {\n    return emitterOrTarget.listeners(type);\n  }\n  if (emitterOrTarget instanceof EventTarget) {\n    // Workers does not implement the ability to get the event listeners on an\n    // EventTarget the way that Node.js does. We simply return empty here.\n    return [];\n  }\n  throw new ERR_INVALID_ARG_TYPE(\n    'emitter',\n    ['EventEmitter', 'EventTarget'],\n    emitterOrTarget\n  );\n}\n\nexport interface OnceOptions {\n  signal?: AbortSignal;\n}\n\nexport async function once(\n  emitter: any,\n  name: string | symbol,\n  options: OnceOptions = {}\n) {\n  validateObject(options, 'options');\n  const { signal } = options;\n  validateAbortSignal(signal, 'options.signal');\n  if (signal?.aborted) {\n    throw new AbortError(undefined, { cause: signal.reason });\n  }\n  return new Promise((resolve, reject) => {\n    const errorListener = (err: any) => {\n      emitter.removeListener(name, resolver);\n      if (signal != null) {\n        eventTargetAgnosticRemoveListener(signal, 'abort', abortListener);\n      }\n      reject(err);\n    };\n    const resolver = (...args: any[]) => {\n      if (typeof emitter.removeListener === 'function') {\n        emitter.removeListener('error', errorListener);\n      }\n      if (signal != null) {\n        eventTargetAgnosticRemoveListener(signal, 'abort', abortListener);\n      }\n      resolve(args);\n    };\n    eventTargetAgnosticAddListener(emitter, name, resolver, { once: true });\n    if (name !== 'error' && typeof emitter.once === 'function') {\n      // EventTarget does not have `error` event semantics like Node\n      // EventEmitters, we listen to `error` events only on EventEmitters.\n      emitter.once('error', errorListener);\n    }\n    function abortListener() {\n      eventTargetAgnosticRemoveListener(emitter, name, resolver);\n      eventTargetAgnosticRemoveListener(emitter, 'error', errorListener);\n      reject(new AbortError());\n    }\n    if (signal != null) {\n      eventTargetAgnosticAddListener(signal, 'abort', abortListener, {\n        once: true,\n      });\n    }\n  });\n}\n\nconst AsyncIteratorPrototype = Object.getPrototypeOf(\n  Object.getPrototypeOf(async function* () {}).prototype\n);\n\nfunction createIterResult(value: any, done: boolean) {\n  return { value, done };\n}\n\nfunction eventTargetAgnosticRemoveListener(\n  emitter: any,\n  name: string | symbol,\n  listener: unknown,\n  flags: unknown = undefined\n) {\n  if (typeof emitter.removeListener === 'function') {\n    emitter.removeListener(name, listener);\n  } else if (typeof emitter.removeEventListener === 'function') {\n    emitter.removeEventListener(name, listener, flags);\n  } else {\n    throw new ERR_INVALID_ARG_TYPE('emitter', 'EventEmitter', emitter);\n  }\n}\n\ninterface AddListenerFlags {\n  once?: boolean;\n}\n\nfunction eventTargetAgnosticAddListener(\n  emitter: any,\n  name: string | symbol,\n  listener: unknown,\n  flags: AddListenerFlags = {}\n) {\n  if (typeof emitter.on === 'function') {\n    if (flags?.once) {\n      emitter.once(name, listener);\n    } else {\n      emitter.on(name, listener);\n    }\n  } else if (typeof emitter.addEventListener === 'function') {\n    // EventTarget does not have `error` event semantics like Node\n    // EventEmitters, we do not listen to `error` events here.\n    emitter.addEventListener(\n      name,\n      (arg: unknown) => {\n        (listener as any)(arg);\n      },\n      flags\n    );\n  } else {\n    throw new ERR_INVALID_ARG_TYPE('emitter', 'EventEmitter', emitter);\n  }\n}\n\ninterface OnOptions {\n  signal?: AbortSignal;\n}\n\nexport function on(\n  emitter: any,\n  event: string | symbol,\n  options: OnOptions = {}\n) {\n  const signal = options?.signal;\n  validateAbortSignal(signal, 'options.signal');\n  if (signal?.aborted) {\n    throw new AbortError();\n  }\n\n  const unconsumedEvents: any[] = [];\n  const unconsumedPromises: any[] = [];\n  let error: any = null;\n  let finished = false;\n\n  const iterator = Object.setPrototypeOf(\n    {\n      next() {\n        // First, we consume all unread events\n        const value = unconsumedEvents.shift();\n        if (value) {\n          return Promise.resolve(createIterResult(value, false));\n        }\n\n        // Then we error, if an error happened\n        // This happens one time if at all, because after 'error'\n        // we stop listening\n        if (error) {\n          const p = Promise.reject(error);\n          // Only the first element errors\n          error = null;\n          return p;\n        }\n\n        // If the iterator is finished, resolve to done\n        if (finished) {\n          return Promise.resolve(createIterResult(undefined, true));\n        }\n\n        // Wait until an event happens\n        return new Promise(function (resolve, reject) {\n          unconsumedPromises.push({ resolve, reject });\n        });\n      },\n\n      return() {\n        eventTargetAgnosticRemoveListener(emitter, event, eventHandler);\n        eventTargetAgnosticRemoveListener(emitter, 'error', errorHandler);\n\n        if (signal) {\n          eventTargetAgnosticRemoveListener(signal, 'abort', abortListener, {\n            once: true,\n          });\n        }\n\n        finished = true;\n\n        for (const promise of unconsumedPromises) {\n          promise.resolve(createIterResult(undefined, true));\n        }\n\n        return Promise.resolve(createIterResult(undefined, true));\n      },\n\n      throw(err: any) {\n        if (!err || !(err instanceof Error)) {\n          throw new ERR_INVALID_ARG_TYPE(\n            'EventEmitter.AsyncIterator',\n            'Error',\n            err\n          );\n        }\n        error = err;\n        eventTargetAgnosticRemoveListener(emitter, event, eventHandler);\n        eventTargetAgnosticRemoveListener(emitter, 'error', errorHandler);\n      },\n\n      [Symbol.asyncIterator]() {\n        return this;\n      },\n    },\n    AsyncIteratorPrototype\n  );\n\n  eventTargetAgnosticAddListener(emitter, event, eventHandler);\n  if (event !== 'error' && typeof emitter.on === 'function') {\n    emitter.on('error', errorHandler);\n  }\n\n  if (signal) {\n    eventTargetAgnosticAddListener(signal, 'abort', abortListener, {\n      once: true,\n    });\n  }\n\n  return iterator;\n\n  function abortListener() {\n    errorHandler(new AbortError());\n  }\n\n  function eventHandler(...args: any[]) {\n    const promise = unconsumedPromises.shift();\n    if (promise) {\n      promise.resolve(createIterResult(args, false));\n    } else {\n      unconsumedEvents.push(args);\n    }\n  }\n\n  function errorHandler(err: any) {\n    finished = true;\n\n    const toError = unconsumedPromises.shift();\n\n    if (toError) {\n      toError.reject(err);\n    } else {\n      // The next time we call next()\n      error = err;\n    }\n\n    iterator.return();\n  }\n}\n"
  },
  {
    "path": "src/node/internal/filesystem.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport interface StatOptions {\n  followSymlinks?: boolean;\n}\n\nexport interface Stat {\n  type: 'file' | 'directory' | 'symlink';\n  size: number;\n  lastModified: bigint;\n  created: bigint;\n  writable: boolean;\n  device: boolean;\n}\n\nexport function stat(pathOrFd: number | URL, options: StatOptions): Stat | null;\nexport function setLastModified(\n  pathOrFd: number | URL,\n  mtime: Date,\n  options: StatOptions\n): void;\n\nexport function truncate(pathOrFd: number | URL, length: number): void;\n\nexport function readLink(\n  path: URL,\n  options: { failIfNotSymlink: boolean }\n): string;\n\nexport function link(from: URL, to: URL, options: { symbolic: boolean }): void;\n\nexport function unlink(path: URL): void;\n\nexport function open(\n  path: URL,\n  options: {\n    read: boolean;\n    write: boolean;\n    append: boolean;\n    exclusive: boolean;\n    followSymlinks: boolean;\n  }\n): number;\n\nexport function close(fd: number): void;\n\nexport function write(\n  fd: number,\n  buffers: ArrayBufferView[],\n  options: {\n    position: number | bigint | null;\n  }\n): number;\n\nexport function read(\n  fd: number,\n  buffers: ArrayBufferView[],\n  options: {\n    position: number | bigint | null;\n  }\n): number;\n\nexport function readAll(pathOrFd: number | URL): Uint8Array;\n\nexport function writeAll(\n  pathOrFd: number | URL,\n  data: ArrayBufferView,\n  options: { append: boolean; exclusive: boolean }\n): number;\n\nexport function renameOrCopy(\n  from: URL,\n  to: URL,\n  options: { copy: boolean }\n): void;\n\nexport function mkdir(\n  path: URL,\n  options: { recursive: boolean; tmp: boolean }\n): string | undefined;\n\nexport function rm(\n  path: URL,\n  options: { recursive: boolean; force: boolean; dironly: boolean }\n): void;\n\nexport interface DirEntryHandle {\n  name: string;\n  parentPath: string;\n  type: number;\n}\n\nexport function readdir(\n  path: URL,\n  options: { recursive: boolean }\n): DirEntryHandle[];\n\nexport function cp(\n  src: URL,\n  dest: URL,\n  options: {\n    deferenceSymlinks: boolean;\n    recursive: boolean;\n    force: boolean;\n    errorOnExist: boolean;\n  }\n): void;\n\ninterface FdHandle {\n  close(): void;\n}\n\nexport function getFdHandle(fd: number): FdHandle;\n\nexport interface OpenAsBlobOptions {\n  type?: string | undefined;\n}\n\nexport function openAsBlob(path: URL, options: OpenAsBlobOptions): Blob;\n"
  },
  {
    "path": "src/node/internal/http.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport const portMapper: Map<number, { fetch: typeof fetch }>;\n"
  },
  {
    "path": "src/node/internal/internal_assert.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Deno and Node.js:\n// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n/* eslint-disable */\n\nimport {\n  AssertionError,\n  type AssertionErrorConstructorOptions,\n} from 'node-internal:internal_assertionerror';\n\nimport { diffstr, diff, buildMessage } from 'node-internal:internal_diffs';\n\nimport { isDeepStrictEqual } from 'node-internal:internal_comparisons';\n\nimport {\n  ERR_AMBIGUOUS_ARGUMENT,\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_ARG_VALUE,\n  ERR_INVALID_RETURN_VALUE,\n  ERR_MISSING_ARGS,\n} from 'node-internal:internal_errors';\n\nimport { inspect } from 'node-internal:internal_inspect';\n\ninterface ExtendedAssertionErrorConstructorOptions extends AssertionErrorConstructorOptions {\n  generatedMessage?: boolean;\n}\n\nfunction createAssertionError(\n  options: ExtendedAssertionErrorConstructorOptions\n): AssertionError {\n  const error = new AssertionError(options);\n  if (options.generatedMessage) {\n    (error as any).generatedMessage = true;\n  }\n  return error;\n}\n\n// Note: Intentionally not exported, use assert.ok() instead.\nfunction assert(actual: unknown, message?: string | Error): asserts actual {\n  if (arguments.length === 0) {\n    throw new AssertionError({\n      message: 'No value argument passed to `assert.ok()`',\n    });\n  }\n  if (!actual) {\n    throw new AssertionError({\n      message,\n      actual,\n      expected: true,\n      operator: '==',\n    } as AssertionErrorConstructorOptions);\n  }\n}\n\ntype Assert = (actual: unknown, message?: string | Error) => asserts actual;\nexport const ok: Assert = assert;\n\nexport function throws(\n  fn: () => void,\n  error?: RegExp | Function | Error,\n  message?: string\n) {\n  // Check arg types\n  if (typeof fn !== 'function') {\n    throw new ERR_INVALID_ARG_TYPE('fn', 'function', fn);\n  }\n  if (\n    typeof error === 'object' &&\n    error !== null &&\n    Object.getPrototypeOf(error) === Object.prototype &&\n    Object.keys(error).length === 0\n  ) {\n    // error is an empty object\n    throw new ERR_INVALID_ARG_VALUE(\n      'error',\n      error,\n      'may not be an empty object'\n    );\n  }\n  if (typeof message === 'string') {\n    if (\n      !(error instanceof RegExp) &&\n      typeof error !== 'function' &&\n      !(error instanceof Error) &&\n      typeof error !== 'object'\n    ) {\n      throw new ERR_INVALID_ARG_TYPE(\n        'error',\n        ['Function', 'Error', 'RegExp', 'Object'],\n        error\n      );\n    }\n  } else {\n    if (\n      typeof error !== 'undefined' &&\n      typeof error !== 'string' &&\n      !(error instanceof RegExp) &&\n      typeof error !== 'function' &&\n      !(error instanceof Error) &&\n      typeof error !== 'object'\n    ) {\n      throw new ERR_INVALID_ARG_TYPE(\n        'error',\n        ['Function', 'Error', 'RegExp', 'Object'],\n        error\n      );\n    }\n  }\n\n  // Checks test function\n  try {\n    fn();\n  } catch (e) {\n    if (\n      validateThrownError(e, error, message, {\n        operator: throws,\n      })\n    ) {\n      return;\n    }\n  }\n  if (message) {\n    let msg = `Missing expected exception: ${message}`;\n    if (typeof error === 'function' && error?.name) {\n      msg = `Missing expected exception (${error.name}): ${message}`;\n    }\n    throw new AssertionError({\n      message: msg,\n      operator: 'throws',\n      actual: undefined,\n      expected: error,\n    });\n  } else if (typeof error === 'string') {\n    // Use case of throws(fn, message)\n    throw new AssertionError({\n      message: `Missing expected exception: ${error}`,\n      operator: 'throws',\n      actual: undefined,\n      expected: undefined,\n    });\n  } else if (typeof error === 'function' && error?.prototype !== undefined) {\n    throw new AssertionError({\n      message: `Missing expected exception (${error.name}).`,\n      operator: 'throws',\n      actual: undefined,\n      expected: error,\n    });\n  } else {\n    throw new AssertionError({\n      message: 'Missing expected exception.',\n      operator: 'throws',\n      actual: undefined,\n      expected: error,\n    });\n  }\n}\n\nexport function doesNotThrow(fn: () => void, message?: string): void;\nexport function doesNotThrow(\n  fn: () => void,\n  error?: Function,\n  message?: string | Error\n): void;\nexport function doesNotThrow(\n  fn: () => void,\n  error?: RegExp,\n  message?: string\n): void;\nexport function doesNotThrow(\n  fn: () => void,\n  expected?: Function | RegExp | string,\n  message?: string | Error\n) {\n  // Check arg type\n  if (typeof fn !== 'function') {\n    throw new ERR_INVALID_ARG_TYPE('fn', 'function', fn);\n  } else if (\n    !(expected instanceof RegExp) &&\n    typeof expected !== 'function' &&\n    typeof expected !== 'string' &&\n    typeof expected !== 'undefined'\n  ) {\n    throw new ERR_INVALID_ARG_TYPE('expected', ['Function', 'RegExp'], fn);\n  }\n\n  // Checks test function\n  try {\n    fn();\n  } catch (e) {\n    gotUnwantedException(e, expected, message, doesNotThrow);\n  }\n}\n\nexport function equal(\n  actual: unknown,\n  expected: unknown,\n  message?: string | Error\n) {\n  return strictEqual(actual, expected, message);\n}\n\nexport function notEqual(\n  actual: unknown,\n  expected: unknown,\n  message?: string | Error\n) {\n  return notStrictEqual(actual, expected, message);\n}\n\nexport function strictEqual(\n  actual: unknown,\n  expected: unknown,\n  message?: string | Error\n) {\n  if (arguments.length < 2) {\n    throw new ERR_MISSING_ARGS('actual', 'expected');\n  }\n\n  if (Object.is(actual, expected)) {\n    return;\n  }\n\n  if (message) {\n    message = `${message}`;\n  } else {\n    const actualString = inspect(actual);\n    const expectedString = inspect(expected);\n\n    if (actualString === expectedString) {\n      const withOffset = actualString\n        .split('\\n')\n        .map((l) => `    ${l}`)\n        .join('\\n');\n      message = `Values have the same structure but are not reference-equal:\\n\\n${withOffset}\\n`;\n    } else {\n      try {\n        const stringDiff =\n          typeof actual === 'string' && typeof expected === 'string';\n        const diffResult = stringDiff\n          ? diffstr(actual as string, expected as string)\n          : diff(actualString.split('\\n'), expectedString.split('\\n'));\n        const diffMsg = buildMessage(diffResult, { stringDiff }).join('\\n');\n        message = `Values are not strictly equal:\\n${diffMsg}`;\n      } catch {\n        message = '\\n$[Cannot display] + \\n\\n';\n      }\n    }\n  }\n\n  throw new AssertionError({\n    message,\n    actual,\n    expected,\n    operator: 'strictEqual',\n  });\n}\n\nexport function notStrictEqual(\n  actual: unknown,\n  expected: unknown,\n  message?: string | Error\n) {\n  if (arguments.length < 2) {\n    throw new ERR_MISSING_ARGS('actual', 'expected');\n  }\n\n  if (!Object.is(actual, expected)) {\n    return;\n  }\n\n  if (message) {\n    message = `${message}`;\n  } else {\n    message = 'Expected actual to be strictly unequal to expected';\n  }\n\n  throw new AssertionError({\n    message,\n    actual,\n    expected,\n    operator: 'notStrictEqual',\n  });\n}\n\nexport function deepEqual(\n  actual: unknown,\n  expected: unknown,\n  message?: string | Error\n) {\n  return deepStrictEqual(actual, expected, message);\n}\n\nexport function notDeepEqual(\n  actual: unknown,\n  expected: unknown,\n  message?: string | Error\n) {\n  return notDeepStrictEqual(actual, expected, message);\n}\n\nexport function deepStrictEqual(\n  actual: unknown,\n  expected: unknown,\n  message?: string | Error\n) {\n  if (arguments.length < 2) {\n    throw new ERR_MISSING_ARGS('actual', 'expected');\n  }\n\n  if (isDeepStrictEqual(actual, expected)) {\n    return;\n  }\n\n  if (message) {\n    message = `${message}`;\n  }\n\n  throw new AssertionError({\n    message,\n    actual,\n    expected,\n    operator: 'deepStrictEqual',\n  });\n}\n\nexport function notDeepStrictEqual(\n  actual: unknown,\n  expected: unknown,\n  message?: string | Error\n) {\n  if (arguments.length < 2) {\n    throw new ERR_MISSING_ARGS('actual', 'expected');\n  }\n\n  if (isDeepStrictEqual(actual, expected)) {\n    if (message) {\n      message = `${message}`;\n    } else {\n      message = 'Expected actual to not be deeply strictly equal to expected';\n    }\n\n    throw new AssertionError({\n      message,\n      actual,\n      expected,\n      operator: 'notDeepStrictEqual',\n    });\n  }\n}\n\n// Helper function to check if actual contains all properties from expected with deep strict equality\nfunction isPartialDeepStrictEqual(\n  actual: unknown,\n  expected: unknown,\n  memo?: Map<unknown, Set<unknown>>\n): boolean {\n  // Handle primitive cases first\n  if (expected === null || typeof expected !== 'object') {\n    return Object.is(actual, expected);\n  }\n\n  if (actual === null || typeof actual !== 'object') {\n    return false;\n  }\n\n  // Initialize memo for cycle detection on first call\n  memo ??= new Map();\n\n  // Cycle detection: check if we're already comparing this pair\n  if (memo.has(actual)) {\n    const actualSet = memo.get(actual)!;\n    if (actualSet.has(expected)) {\n      // We're already comparing this pair, assume they match to avoid infinite recursion\n      return true;\n    }\n    actualSet.add(expected);\n  } else {\n    memo.set(actual, new Set([expected]));\n  }\n\n  try {\n    // Handle Date objects\n    if (expected instanceof Date) {\n      return actual instanceof Date && actual.getTime() === expected.getTime();\n    }\n\n    // Handle RegExp objects\n    if (expected instanceof RegExp) {\n      return (\n        actual instanceof RegExp &&\n        actual.source === expected.source &&\n        actual.flags === expected.flags &&\n        actual.lastIndex === expected.lastIndex\n      );\n    }\n\n    // Handle Error objects\n    if (expected instanceof Error) {\n      return (\n        actual instanceof Error &&\n        actual.name === expected.name &&\n        actual.message === expected.message\n      );\n    }\n\n    // Handle arrays\n    if (Array.isArray(expected)) {\n      if (!Array.isArray(actual)) {\n        return false;\n      }\n\n      // For arrays, check if all expected elements are present in actual\n      // This allows for sparse checking - actual can have more elements\n      for (let i = 0; i < expected.length; i++) {\n        if (i in expected) {\n          if (\n            !(i in actual) ||\n            !isPartialDeepStrictEqual(actual[i], expected[i], memo)\n          ) {\n            return false;\n          }\n        }\n      }\n      return true;\n    }\n\n    // Handle Sets\n    if (expected instanceof Set) {\n      if (!(actual instanceof Set)) {\n        return false;\n      }\n\n      // Check if all expected values are in actual set\n      for (const expectedValue of expected) {\n        let found = false;\n        for (const actualValue of actual) {\n          if (isDeepStrictEqual(expectedValue, actualValue)) {\n            found = true;\n            break;\n          }\n        }\n        if (!found) {\n          return false;\n        }\n      }\n      return true;\n    }\n\n    // Handle Maps\n    if (expected instanceof Map) {\n      if (!(actual instanceof Map)) {\n        return false;\n      }\n\n      // Check if all expected key-value pairs are in actual map\n      for (const [expectedKey, expectedValue] of expected) {\n        let found = false;\n        for (const [actualKey, actualValue] of actual) {\n          if (isDeepStrictEqual(expectedKey, actualKey)) {\n            if (!isPartialDeepStrictEqual(actualValue, expectedValue, memo)) {\n              return false;\n            }\n            found = true;\n            break;\n          }\n        }\n        if (!found) {\n          return false;\n        }\n      }\n      return true;\n    }\n\n    // Check prototypes are the same (required for deepStrictEqual behavior)\n    if (Object.getPrototypeOf(actual) !== Object.getPrototypeOf(expected)) {\n      return false;\n    }\n\n    // Handle regular objects\n    const expectedKeys = Object.keys(expected);\n\n    for (const key of expectedKeys) {\n      if (!(key in actual)) {\n        return false;\n      }\n\n      if (\n        !isPartialDeepStrictEqual(\n          (actual as any)[key],\n          (expected as any)[key],\n          memo\n        )\n      ) {\n        return false;\n      }\n    }\n\n    // Handle symbol properties\n    const expectedSymbols = Object.getOwnPropertySymbols(expected);\n    for (const symbol of expectedSymbols) {\n      if (expected.propertyIsEnumerable(symbol)) {\n        if (\n          !(symbol in actual) ||\n          !isPartialDeepStrictEqual(\n            (actual as any)[symbol],\n            (expected as any)[symbol],\n            memo\n          )\n        ) {\n          return false;\n        }\n      }\n    }\n\n    return true;\n  } finally {\n    // Clean up memo to prevent memory leaks\n    if (memo.has(actual)) {\n      const actualSet = memo.get(actual)!;\n      actualSet.delete(expected);\n      if (actualSet.size === 0) {\n        memo.delete(actual);\n      }\n    }\n  }\n}\n\nexport function partialDeepStrictEqual(\n  actual: unknown,\n  expected: unknown,\n  message?: string | Error\n): void {\n  if (arguments.length < 2) {\n    throw new ERR_MISSING_ARGS('actual', 'expected');\n  }\n\n  if (isPartialDeepStrictEqual(actual, expected)) {\n    return;\n  }\n\n  if (message) {\n    message = `${message}`;\n  } else {\n    message =\n      'Expected actual to be partially deeply strictly equal to expected';\n  }\n\n  throw new AssertionError({\n    message,\n    actual,\n    expected,\n    operator: 'partialDeepStrictEqual',\n  });\n}\n\nexport function fail(message?: string | Error): never {\n  if (typeof message === 'string' || message == null) {\n    throw createAssertionError({\n      message: message ?? 'Failed',\n      operator: 'fail',\n      generatedMessage: message == null,\n    });\n  } else {\n    throw message;\n  }\n}\n\nexport function match(\n  actual: string,\n  regexp: RegExp,\n  message?: string | Error\n) {\n  if (arguments.length < 2) {\n    throw new ERR_MISSING_ARGS('actual', 'regexp');\n  }\n  if (!(regexp instanceof RegExp)) {\n    throw new ERR_INVALID_ARG_TYPE('regexp', 'RegExp', regexp);\n  }\n\n  if (!regexp.test(actual)) {\n    if (!message) {\n      message = `actual: \"${actual}\" expected to match: \"${regexp}\"`;\n    } else {\n      message = `${message}`;\n    }\n    throw new AssertionError({\n      message,\n      actual,\n      expected: regexp,\n      operator: 'match',\n    });\n  }\n}\n\nexport function doesNotMatch(\n  string: string,\n  regexp: RegExp,\n  message?: string | Error\n) {\n  if (arguments.length < 2) {\n    throw new ERR_MISSING_ARGS('string', 'regexp');\n  }\n  if (!(regexp instanceof RegExp)) {\n    throw new ERR_INVALID_ARG_TYPE('regexp', 'RegExp', regexp);\n  }\n  if (typeof string !== 'string') {\n    if (message instanceof Error) {\n      throw message;\n    }\n    throw new AssertionError({\n      message:\n        message ||\n        `The \"string\" argument must be of type string. Received type ${typeof string} (${inspect(\n          string\n        )})`,\n      actual: string,\n      expected: regexp,\n      operator: 'doesNotMatch',\n    });\n  }\n\n  if (regexp.test(string)) {\n    if (!message) {\n      message = `actual: \"${string}\" expected to not match: \"${regexp}\"`;\n    } else {\n      message = `${message}`;\n    }\n    throw new AssertionError({\n      message,\n      actual: string,\n      expected: regexp,\n      operator: 'doesNotMatch',\n    });\n  }\n}\n\nexport function strict(\n  actual: unknown,\n  message?: string | Error\n): asserts actual {\n  if (arguments.length === 0) {\n    throw new AssertionError({\n      message: 'No value argument passed to `assert.ok()`',\n    });\n  }\n  assert(actual, message);\n}\n\nexport function rejects(\n  asyncFn: Promise<any> | (() => Promise<any>),\n  error?: RegExp | Function | Error\n): Promise<void>;\n\nexport function rejects(\n  asyncFn: Promise<any> | (() => Promise<any>),\n  message?: string\n): Promise<void>;\n\nexport function rejects(\n  asyncFn: Promise<any> | (() => Promise<any>),\n  error?: RegExp | Function | Error | string,\n  message?: string\n) {\n  let promise: Promise<void>;\n  if (typeof asyncFn === 'function') {\n    try {\n      promise = asyncFn();\n    } catch (err) {\n      return Promise.reject(err);\n    }\n\n    if (!isValidThenable(promise)) {\n      return Promise.reject(\n        new ERR_INVALID_RETURN_VALUE(\n          'instance of Promise',\n          'promiseFn',\n          promise\n        )\n      );\n    }\n  } else if (!isValidThenable(asyncFn)) {\n    return Promise.reject(\n      new ERR_INVALID_ARG_TYPE('promiseFn', ['function', 'Promise'], asyncFn)\n    );\n  } else {\n    promise = asyncFn;\n  }\n\n  function onFulfilled() {\n    let message = 'Missing expected rejection';\n    if (typeof error === 'string') {\n      message += `: ${error}`;\n    } else if (typeof error === 'function' && error.prototype !== undefined) {\n      message += ` (${error.name}).`;\n    } else {\n      message += '.';\n    }\n    return Promise.reject(\n      createAssertionError({\n        message,\n        operator: 'rejects',\n        generatedMessage: true,\n      })\n    );\n  }\n\n  function rejects_onRejected(e: Error) {\n    if (\n      validateThrownError(e, error, message, {\n        operator: rejects,\n        validationFunctionName: 'validate',\n      })\n    ) {\n      return;\n    }\n  }\n\n  return promise.then(onFulfilled, rejects_onRejected);\n}\n\nexport function doesNotReject(\n  asyncFn: Promise<any> | (() => Promise<any>),\n  error?: RegExp | Function\n): Promise<void>;\n\nexport function doesNotReject(\n  asyncFn: Promise<any> | (() => Promise<any>),\n  message?: string\n): Promise<void>;\n\nexport function doesNotReject(\n  asyncFn: Promise<any> | (() => Promise<any>),\n  error?: RegExp | Function | string,\n  message?: string\n) {\n  let promise: Promise<any>;\n  if (typeof asyncFn === 'function') {\n    try {\n      const value = asyncFn();\n      if (!isValidThenable(value)) {\n        return Promise.reject(\n          new ERR_INVALID_RETURN_VALUE(\n            'instance of Promise',\n            'promiseFn',\n            value\n          )\n        );\n      }\n      promise = value;\n    } catch (e) {\n      return Promise.reject(e);\n    }\n  } else if (!isValidThenable(asyncFn)) {\n    return Promise.reject(\n      new ERR_INVALID_ARG_TYPE('promiseFn', ['function', 'Promise'], asyncFn)\n    );\n  } else {\n    promise = asyncFn;\n  }\n\n  return promise.then(\n    () => {},\n    (e) => gotUnwantedException(e, error, message, doesNotReject)\n  );\n}\n\nfunction gotUnwantedException(\n  e: any,\n  expected: RegExp | Function | string | null | undefined,\n  message: string | Error | null | undefined,\n  operator: Function\n): never {\n  if (typeof expected === 'string') {\n    // The use case of doesNotThrow(fn, message);\n    throw new AssertionError({\n      message: `Got unwanted exception: ${expected}\\nActual message: \"${e.message}\"`,\n      operator: operator.name,\n    });\n  } else if (\n    typeof expected === 'function' &&\n    expected.prototype !== undefined\n  ) {\n    // The use case of doesNotThrow(fn, Error, message);\n    if (e instanceof expected) {\n      let msg = `Got unwanted exception: ${e.constructor?.name}`;\n      if (message) {\n        msg += ` ${String(message)}`;\n      }\n      throw new AssertionError({\n        message: msg,\n        operator: operator.name,\n      });\n    } else if (expected.prototype instanceof Error) {\n      throw e;\n    } else {\n      const result = expected(e);\n      if (result === true) {\n        let msg = `Got unwanted rejection.\\nActual message: \"${e.message}\"`;\n        if (message) {\n          msg += ` ${String(message)}`;\n        }\n        throw new AssertionError({\n          message: msg,\n          operator: operator.name,\n        });\n      }\n    }\n    throw e;\n  } else {\n    if (message) {\n      throw new AssertionError({\n        message: `Got unwanted exception: ${message}\\nActual message: \"${\n          e ? e.message : String(e)\n        }\"`,\n        operator: operator.name,\n      });\n    }\n    throw new AssertionError({\n      message: `Got unwanted exception.\\nActual message: \"${\n        e ? e.message : String(e)\n      }\"`,\n      operator: operator.name,\n    });\n  }\n}\n\nexport function ifError(err: any) {\n  if (err !== null && err !== undefined) {\n    let message = 'ifError got unwanted exception: ';\n\n    if (typeof err === 'object' && typeof err.message === 'string') {\n      if (err.message.length === 0 && err.constructor) {\n        message += err.constructor.name;\n      } else {\n        message += err.message;\n      }\n    } else {\n      message += inspect(err);\n    }\n\n    const newErr = new AssertionError({\n      actual: err,\n      expected: null,\n      operator: 'ifError',\n      message,\n      stackStartFn: ifError,\n    });\n\n    // Make sure we actually have a stack trace!\n    const origStack = err.stack;\n\n    if (typeof origStack === 'string') {\n      const tmp2 = origStack.split('\\n');\n      tmp2.shift();\n      let tmp1 = newErr!.stack?.split('\\n');\n\n      for (const errFrame of tmp2) {\n        const pos = tmp1?.indexOf(errFrame);\n\n        if (pos !== -1) {\n          tmp1 = tmp1?.slice(0, pos);\n\n          break;\n        }\n      }\n\n      newErr.stack = `${tmp1?.join('\\n')}\\n${tmp2.join('\\n')}`;\n    }\n\n    throw newErr;\n  }\n}\n\ninterface ValidateThrownErrorOptions {\n  operator: Function;\n  validationFunctionName?: string;\n}\n\nfunction validateThrownError(\n  e: any,\n  error: RegExp | Function | Error | string | null | undefined,\n  message: string | undefined | null,\n  options: ValidateThrownErrorOptions\n): boolean {\n  if (typeof error === 'string') {\n    if (message != null) {\n      throw new ERR_INVALID_ARG_TYPE(\n        'error',\n        ['Object', 'Error', 'Function', 'RegExp'],\n        error\n      );\n    } else if (typeof e === 'object' && e !== null) {\n      if (e.message === error) {\n        throw new ERR_AMBIGUOUS_ARGUMENT(\n          'error/message',\n          `The error message \"${e.message}\" is identical to the message.`\n        );\n      }\n    } else if (e === error) {\n      throw new ERR_AMBIGUOUS_ARGUMENT(\n        'error/message',\n        `The error \"${e}\" is identical to the message.`\n      );\n    }\n    message = error;\n    error = undefined;\n  }\n  if (\n    error instanceof Function &&\n    error.prototype !== undefined &&\n    (error === Error || error.prototype instanceof Error)\n  ) {\n    // error is a constructor (Error itself or a subclass of Error)\n    if (e instanceof error) {\n      return true;\n    }\n    throw createAssertionError({\n      message: `The error is expected to be an instance of \"${error.name}\". Received \"${e?.constructor?.name}\"\\n\\nError message:\\n\\n${e?.message}`,\n      actual: e,\n      expected: error,\n      operator: options.operator.name,\n      generatedMessage: true,\n    });\n  }\n  if (error instanceof Function) {\n    const received = error(e);\n    if (received === true) {\n      return true;\n    }\n    throw createAssertionError({\n      message: `The ${\n        options.validationFunctionName\n          ? `\"${options.validationFunctionName}\" validation`\n          : 'validation'\n      } function is expected to return \"true\". Received ${inspect(\n        received\n      )}\\n\\nCaught error:\\n\\n${e}`,\n      actual: e,\n      expected: error,\n      operator: options.operator.name,\n      generatedMessage: true,\n    });\n  }\n  if (error instanceof RegExp) {\n    if (error.test(String(e))) {\n      return true;\n    }\n    throw createAssertionError({\n      message: `The input did not match the regular expression ${error.toString()}. Input:\\n\\n'${String(\n        e\n      )}'\\n`,\n      actual: e,\n      expected: error,\n      operator: options.operator.name,\n      generatedMessage: true,\n    });\n  }\n  if (typeof error === 'object' && error !== null) {\n    const keys = Object.keys(error);\n    if (error instanceof Error) {\n      keys.push('name', 'message');\n    }\n    for (const k of keys) {\n      if (e == null) {\n        throw createAssertionError({\n          message: message || 'object is expected to thrown, but got null',\n          actual: e,\n          expected: error,\n          operator: options.operator.name,\n          generatedMessage: message == null,\n        });\n      }\n\n      if (typeof e === 'string') {\n        throw createAssertionError({\n          message:\n            message || `object is expected to thrown, but got string: ${e}`,\n          actual: e,\n          expected: error,\n          operator: options.operator.name,\n          generatedMessage: message == null,\n        });\n      }\n      if (typeof e === 'number') {\n        throw createAssertionError({\n          message:\n            message || `object is expected to thrown, but got number: ${e}`,\n          actual: e,\n          expected: error,\n          operator: options.operator.name,\n          generatedMessage: message == null,\n        });\n      }\n      if (!(k in e)) {\n        throw createAssertionError({\n          message: message || `A key in the expected object is missing: ${k}`,\n          actual: e,\n          expected: error,\n          operator: options.operator.name,\n          generatedMessage: message == null,\n        });\n      }\n      const actual = e[k];\n\n      const expected = (error as any)[k];\n      if (typeof actual === 'string' && expected instanceof RegExp) {\n        match(actual, expected);\n      } else {\n        deepStrictEqual(actual, expected);\n      }\n    }\n    return true;\n  }\n  if (typeof error === 'undefined') {\n    return true;\n  }\n  throw createAssertionError({\n    message: `Invalid expectation: ${error}`,\n    operator: options.operator.name,\n    generatedMessage: true,\n  });\n}\n\nfunction isValidThenable(maybeThennable: any): boolean {\n  if (!maybeThennable) {\n    return false;\n  }\n\n  if (maybeThennable instanceof Promise) {\n    return true;\n  }\n\n  return typeof maybeThennable.then === 'function';\n}\n\nexport { AssertionError };\n\nObject.assign(strict, {\n  AssertionError,\n  deepEqual: deepStrictEqual,\n  deepStrictEqual,\n  doesNotMatch,\n  doesNotReject,\n  doesNotThrow,\n  equal: strictEqual,\n  fail,\n  ifError,\n  match,\n  notDeepEqual: notDeepStrictEqual,\n  notDeepStrictEqual,\n  notEqual: notStrictEqual,\n  notStrictEqual,\n  ok,\n  partialDeepStrictEqual,\n  rejects,\n  strict,\n  strictEqual,\n  throws,\n});\n\nexport default Object.assign(assert, {\n  AssertionError,\n  deepEqual,\n  deepStrictEqual,\n  doesNotMatch,\n  doesNotReject,\n  doesNotThrow,\n  equal,\n  fail,\n  ifError,\n  match,\n  notDeepEqual,\n  notDeepStrictEqual,\n  notEqual,\n  notStrictEqual,\n  ok,\n  partialDeepStrictEqual,\n  rejects,\n  strict,\n  strictEqual,\n  throws,\n});\n"
  },
  {
    "path": "src/node/internal/internal_assertionerror.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Deno and Node.js:\n// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n/* TODO: the following is adopted code, enabling linting one day */\n/* eslint-disable */\n\nimport { ERR_INVALID_ARG_TYPE } from 'node-internal:internal_errors';\nimport { inspect } from 'node-internal:internal_inspect';\n\nlet blue = '';\nlet green = '';\nlet red = '';\nlet defaultColor = '';\n\nconst kReadableOperator: { [key: string]: string } = {\n  deepStrictEqual: 'Expected values to be strictly deep-equal:',\n  strictEqual: 'Expected values to be strictly equal:',\n  strictEqualObject: 'Expected \"actual\" to be reference-equal to \"expected\":',\n  deepEqual: 'Expected values to be loosely deep-equal:',\n  notDeepStrictEqual: 'Expected \"actual\" not to be strictly deep-equal to:',\n  notStrictEqual: 'Expected \"actual\" to be strictly unequal to:',\n  notStrictEqualObject:\n    'Expected \"actual\" not to be reference-equal to \"expected\":',\n  notDeepEqual: 'Expected \"actual\" not to be loosely deep-equal to:',\n  notIdentical: 'Values have same structure but are not reference-equal:',\n  notDeepEqualUnequal: 'Expected values not to be loosely deep-equal:',\n};\n\n// Comparing short primitives should just show === / !== instead of using the\n// diff.\nconst kMaxShortLength = 12;\n\nexport function copyError(source: any): Error {\n  const keys = Object.keys(source);\n  const target = Object.create(Object.getPrototypeOf(source));\n  for (const key of keys) {\n    const desc = Object.getOwnPropertyDescriptor(source, key);\n\n    if (desc !== undefined) {\n      Object.defineProperty(target, key, desc);\n    }\n  }\n  Object.defineProperty(target, 'message', { value: source.message });\n  return target;\n}\n\nexport function inspectValue(val: unknown): string {\n  return inspect(val, {\n    compact: true,\n    customInspect: false,\n    depth: 1000,\n    maxArrayLength: Infinity,\n    // Assert compares only enumerable properties (with a few exceptions).\n    showHidden: false,\n    // Assert does not detect proxies currently.\n    showProxy: false,\n    sorted: true,\n    // Inspect getters as we also check them when comparing entries.\n    getters: true,\n  });\n}\n\nexport function createErrDiff(\n  actual: unknown,\n  expected: unknown,\n  operator: string\n): string {\n  let other = '';\n  let res = '';\n  let end = '';\n  let skipped = false;\n  const actualInspected = inspectValue(actual);\n  const actualLines = actualInspected.split('\\n');\n  const expectedLines = inspectValue(expected).split('\\n');\n\n  let i = 0;\n  let indicator = '';\n\n  // In case both values are objects or functions explicitly mark them as not\n  // reference equal for the `strictEqual` operator.\n  if (\n    operator === 'strictEqual' &&\n    ((typeof actual === 'object' &&\n      actual !== null &&\n      typeof expected === 'object' &&\n      expected !== null) ||\n      (typeof actual === 'function' && typeof expected === 'function'))\n  ) {\n    operator = 'strictEqualObject';\n  }\n\n  // If \"actual\" and \"expected\" fit on a single line and they are not strictly\n  // equal, check further special handling.\n  if (\n    actualLines.length === 1 &&\n    expectedLines.length === 1 &&\n    actualLines[0] !== expectedLines[0]\n  ) {\n    const actualRaw = actualLines[0];\n    const expectedRaw = expectedLines[0];\n    const inputLength =\n      (actualRaw as string).length + (expectedRaw as string).length;\n    // If the character length of \"actual\" and \"expected\" together is less than\n    // kMaxShortLength and if neither is an object and at least one of them is\n    // not `zero`, use the strict equal comparison to visualize the output.\n    if (inputLength <= kMaxShortLength) {\n      if (\n        (typeof actual !== 'object' || actual === null) &&\n        (typeof expected !== 'object' || expected === null) &&\n        (actual !== 0 || expected !== 0)\n      ) {\n        // -0 === +0\n        return (\n          `${kReadableOperator[operator]}\\n\\n` +\n          `${actualLines[0]} !== ${expectedLines[0]}\\n`\n        );\n      }\n    } else if (operator !== 'strictEqualObject') {\n      // If the stderr is a tty and the input length is lower than the current\n      // columns per line, add a mismatch indicator below the output. If it is\n      // not a tty, use a default value of 80 characters.\n      const maxLength = 80;\n      if (inputLength < maxLength) {\n        while ((actualRaw as string)[i] === (expectedRaw as string)[i]) {\n          i++;\n        }\n        // Ignore the first characters.\n        if (i > 2) {\n          // Add position indicator for the first mismatch in case it is a\n          // single line and the input length is less than the column length.\n          indicator = `\\n  ${' '.repeat(i)}^`;\n          i = 0;\n        }\n      }\n    }\n  }\n\n  // Remove all ending lines that match (this optimizes the output for\n  // readability by reducing the number of total changed lines).\n  let a = actualLines[actualLines.length - 1];\n  let b = expectedLines[expectedLines.length - 1];\n  while (a === b) {\n    if (i++ < 3) {\n      end = `\\n  ${a}${end}`;\n    } else {\n      other = a as string;\n    }\n    actualLines.pop();\n    expectedLines.pop();\n    if (actualLines.length === 0 || expectedLines.length === 0) {\n      break;\n    }\n    a = actualLines[actualLines.length - 1];\n    b = expectedLines[expectedLines.length - 1];\n  }\n\n  const maxLines = Math.max(actualLines.length, expectedLines.length);\n  // Strict equal with identical objects that are not identical by reference.\n  // E.g., assert.deepStrictEqual({ a: Symbol() }, { a: Symbol() })\n  if (maxLines === 0) {\n    // We have to get the result again. The lines were all removed before.\n    const actualLines = actualInspected.split('\\n');\n\n    // Only remove lines in case it makes sense to collapse those.\n    if (actualLines.length > 50) {\n      actualLines[46] = `${blue}...${defaultColor}`;\n      while (actualLines.length > 47) {\n        actualLines.pop();\n      }\n    }\n\n    return `${kReadableOperator['notIdentical']}\\n\\n${actualLines.join('\\n')}\\n`;\n  }\n\n  // There were at least five identical lines at the end. Mark a couple of\n  // skipped.\n  if (i >= 5) {\n    end = `\\n${blue}...${defaultColor}${end}`;\n    skipped = true;\n  }\n  if (other !== '') {\n    end = `\\n  ${other}${end}`;\n    other = '';\n  }\n\n  let printedLines = 0;\n  let identical = 0;\n  const msg =\n    kReadableOperator[operator] +\n    `\\n${green}+ actual${defaultColor} ${red}- expected${defaultColor}`;\n  const skippedMsg = ` ${blue}...${defaultColor} Lines skipped`;\n\n  let lines = actualLines;\n  let plusMinus = `${green}+${defaultColor}`;\n  let maxLength = expectedLines.length;\n  if (actualLines.length < maxLines) {\n    lines = expectedLines;\n    plusMinus = `${red}-${defaultColor}`;\n    maxLength = actualLines.length;\n  }\n\n  for (i = 0; i < maxLines; i++) {\n    if (maxLength < i + 1) {\n      // If more than two former lines are identical, print them. Collapse them\n      // in case more than five lines were identical.\n      if (identical > 2) {\n        if (identical > 3) {\n          if (identical > 4) {\n            if (identical === 5) {\n              res += `\\n  ${lines[i - 3]}`;\n              printedLines++;\n            } else {\n              res += `\\n${blue}...${defaultColor}`;\n              skipped = true;\n            }\n          }\n          res += `\\n  ${lines[i - 2]}`;\n          printedLines++;\n        }\n        res += `\\n  ${lines[i - 1]}`;\n        printedLines++;\n      }\n      // No identical lines before.\n      identical = 0;\n      // Add the expected line to the cache.\n      if (lines === actualLines) {\n        res += `\\n${plusMinus} ${lines[i]}`;\n      } else {\n        other += `\\n${plusMinus} ${lines[i]}`;\n      }\n      printedLines++;\n      // Only extra actual lines exist\n      // Lines diverge\n    } else {\n      const expectedLine = expectedLines[i];\n      let actualLine = actualLines[i];\n      // If the lines diverge, specifically check for lines that only diverge by\n      // a trailing comma. In that case it is actually identical and we should\n      // mark it as such.\n      let divergingLines =\n        actualLine !== expectedLine &&\n        (!(actualLine as string).endsWith(',') ||\n          (actualLine as string).slice(0, -1) !== expectedLine);\n      // If the expected line has a trailing comma but is otherwise identical,\n      // add a comma at the end of the actual line. Otherwise the output could\n      // look weird as in:\n      //\n      //   [\n      //     1         // No comma at the end!\n      // +   2\n      //   ]\n      //\n      if (\n        divergingLines &&\n        (expectedLine as string).endsWith(',') &&\n        (expectedLine as string).slice(0, -1) === actualLine\n      ) {\n        divergingLines = false;\n        actualLine += ',';\n      }\n      if (divergingLines) {\n        // If more than two former lines are identical, print them. Collapse\n        // them in case more than five lines were identical.\n        if (identical > 2) {\n          if (identical > 3) {\n            if (identical > 4) {\n              if (identical === 5) {\n                res += `\\n  ${actualLines[i - 3]}`;\n                printedLines++;\n              } else {\n                res += `\\n${blue}...${defaultColor}`;\n                skipped = true;\n              }\n            }\n            res += `\\n  ${actualLines[i - 2]}`;\n            printedLines++;\n          }\n          res += `\\n  ${actualLines[i - 1]}`;\n          printedLines++;\n        }\n        // No identical lines before.\n        identical = 0;\n        // Add the actual line to the result and cache the expected diverging\n        // line so consecutive diverging lines show up as +++--- and not +-+-+-.\n        res += `\\n${green}+${defaultColor} ${actualLine}`;\n        other += `\\n${red}-${defaultColor} ${expectedLine}`;\n        printedLines += 2;\n        // Lines are identical\n      } else {\n        // Add all cached information to the result before adding other things\n        // and reset the cache.\n        res += other;\n        other = '';\n        identical++;\n        // The very first identical line since the last diverging line is be\n        // added to the result.\n        if (identical <= 2) {\n          res += `\\n  ${actualLine}`;\n          printedLines++;\n        }\n      }\n    }\n    // Inspected object to big (Show ~50 rows max)\n    if (printedLines > 50 && i < maxLines - 2) {\n      return (\n        `${msg}${skippedMsg}\\n${res}\\n${blue}...${defaultColor}${other}\\n` +\n        `${blue}...${defaultColor}`\n      );\n    }\n  }\n\n  return `${msg}${skipped ? skippedMsg : ''}\\n${res}${other}${end}${indicator}`;\n}\n\nexport interface AssertionErrorDetailsDescriptor {\n  message: string;\n  actual: unknown;\n  expected: unknown;\n  operator: string;\n  stack: Error;\n}\n\nexport interface AssertionErrorConstructorOptions {\n  message: string | Error | undefined;\n  actual?: unknown;\n  expected?: unknown;\n  operator?: string;\n  details?: AssertionErrorDetailsDescriptor[];\n  // deno-lint-ignore ban-types\n  stackStartFn?: Function;\n  // Compatibility with older versions.\n  // deno-lint-ignore ban-types\n  stackStartFunction?: Function;\n}\n\ninterface ErrorWithStackTraceLimit extends ErrorConstructor {\n  stackTraceLimit: number;\n}\n\nexport class AssertionError extends Error {\n  [key: string]: unknown;\n\n  // deno-lint-ignore constructor-super\n  constructor(options: AssertionErrorConstructorOptions) {\n    if (typeof options !== 'object' || options === null) {\n      throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);\n    }\n    const {\n      message,\n      operator,\n      stackStartFn,\n      details,\n      // Compatibility with older versions.\n      stackStartFunction,\n    } = options;\n    let { actual, expected } = options;\n\n    // TODO(schwarzkopfb): `stackTraceLimit` should be added to `ErrorConstructor` in\n    // cli/dts/lib.deno.shared_globals.d.ts\n    const limit = (Error as ErrorWithStackTraceLimit).stackTraceLimit;\n    (Error as ErrorWithStackTraceLimit).stackTraceLimit = 0;\n\n    if (message != null) {\n      super(String(message));\n    } else {\n      // Prevent the error stack from being visible by duplicating the error\n      // in a very close way to the original in case both sides are actually\n      // instances of Error.\n      if (\n        typeof actual === 'object' &&\n        actual !== null &&\n        typeof expected === 'object' &&\n        expected !== null &&\n        'stack' in actual &&\n        actual instanceof Error &&\n        'stack' in expected &&\n        expected instanceof Error\n      ) {\n        actual = copyError(actual);\n        expected = copyError(expected);\n      }\n\n      if (operator === 'deepStrictEqual' || operator === 'strictEqual') {\n        super(createErrDiff(actual, expected, operator));\n      } else if (\n        operator === 'notDeepStrictEqual' ||\n        operator === 'notStrictEqual'\n      ) {\n        // In case the objects are equal but the operator requires unequal, show\n        // the first object and say A equals B\n        let base = kReadableOperator[operator];\n        const res = inspectValue(actual).split('\\n');\n\n        // In case \"actual\" is an object or a function, it should not be\n        // reference equal.\n        if (\n          operator === 'notStrictEqual' &&\n          ((typeof actual === 'object' && actual !== null) ||\n            typeof actual === 'function')\n        ) {\n          base = kReadableOperator['notStrictEqualObject'];\n        }\n\n        // Only remove lines in case it makes sense to collapse those.\n        if (res.length > 50) {\n          res[46] = `${blue}...${defaultColor}`;\n          while (res.length > 47) {\n            res.pop();\n          }\n        }\n\n        // Only print a single input.\n        if (res.length === 1) {\n          super(`${base}${res[0]!.length > 5 ? '\\n\\n' : ' '}${res[0]}`);\n        } else {\n          super(`${base}\\n\\n${res.join('\\n')}\\n`);\n        }\n      } else {\n        let res = inspectValue(actual);\n        let other = inspectValue(expected);\n        const knownOperator = kReadableOperator[operator ?? ''];\n        if (operator === 'notDeepEqual' && res === other) {\n          res = `${knownOperator}\\n\\n${res}`;\n          if (res.length > 1024) {\n            res = `${res.slice(0, 1021)}...`;\n          }\n          super(res);\n        } else {\n          if (res.length > 512) {\n            res = `${res.slice(0, 509)}...`;\n          }\n          if (other.length > 512) {\n            other = `${other.slice(0, 509)}...`;\n          }\n          if (operator === 'deepEqual') {\n            res = `${knownOperator}\\n\\n${res}\\n\\nshould loosely deep-equal\\n\\n`;\n          } else {\n            const newOp = kReadableOperator[`${operator}Unequal`];\n            if (newOp) {\n              res = `${newOp}\\n\\n${res}\\n\\nshould not loosely deep-equal\\n\\n`;\n            } else {\n              other = ` ${operator} ${other}`;\n            }\n          }\n          super(`${res}${other}`);\n        }\n      }\n    }\n\n    (Error as ErrorWithStackTraceLimit).stackTraceLimit = limit;\n\n    (this as any).generatedMessage = !message;\n    Object.defineProperty(this, 'name', {\n      __proto__: null,\n      value: 'AssertionError [ERR_ASSERTION]',\n      enumerable: false,\n      writable: true,\n      configurable: true,\n      // deno-lint-ignore no-explicit-any\n    } as any);\n    (this as any).code = 'ERR_ASSERTION';\n\n    if (details) {\n      (this as any).actual = undefined;\n      (this as any).expected = undefined;\n      (this as any).operator = undefined;\n\n      for (let i = 0; i < details.length; i++) {\n        this['message ' + i] = (details[i] as any).message;\n        this['actual ' + i] = (details[i] as any).actual;\n        this['expected ' + i] = (details[i] as any).expected;\n        this['operator ' + i] = (details[i] as any).operator;\n        this['stack trace ' + i] = (details[i] as any).stack;\n      }\n    } else {\n      (this as any).actual = actual;\n      (this as any).expected = expected;\n      (this as any).operator = operator;\n    }\n\n    // @ts-ignore this function is not available in lib.dom.d.ts\n    Error.captureStackTrace(this, stackStartFn || stackStartFunction);\n    // Create error message including the error code in the name.\n    this.stack;\n    // Reset the name.\n    this.name = 'AssertionError';\n  }\n\n  override toString() {\n    return `${this.name} [${(this as any).code}]: ${this.message}`;\n  }\n\n  [inspect.custom](_recurseTimes: number, ctx: Record<string, unknown>) {\n    // Long strings should not be fully inspected.\n    const tmpActual = (this as any).actual;\n    const tmpExpected = (this as any).expected;\n\n    for (const name of ['actual', 'expected']) {\n      if (typeof this[name] === 'string') {\n        const value = this[name] as string;\n        const lines = value.split('\\n');\n        if (lines.length > 10) {\n          lines.length = 10;\n          this[name] = `${lines.join('\\n')}\\n...`;\n        } else if (value.length > 512) {\n          this[name] = `${value.slice(512)}...`;\n        }\n      }\n    }\n\n    // This limits the `actual` and `expected` property default inspection to\n    // the minimum depth. Otherwise those values would be too verbose compared\n    // to the actual error message which contains a combined view of these two\n    // input values.\n    const result = inspect(this, {\n      ...ctx,\n      customInspect: false,\n      depth: 0,\n    });\n\n    // Reset the properties after inspection.\n    (this as any).actual = tmpActual;\n    (this as any).expected = tmpExpected;\n\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/node/internal/internal_buffer.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n\n// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n// Copyright Feross Aboukhadijeh, and other contributors. All rights reserved. MIT license.\n\n/* TODO: the following is adopted code, enabling linting one day */\n/* eslint-disable */\n\nimport {\n  ERR_BUFFER_OUT_OF_BOUNDS,\n  ERR_OUT_OF_RANGE,\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_ARG_VALUE,\n  ERR_INVALID_BUFFER_SIZE,\n  ERR_UNKNOWN_ENCODING,\n} from 'node-internal:internal_errors';\n\nimport { default as bufferUtil } from 'node-internal:buffer';\nimport type { Encoding } from 'node-internal:buffer';\n\nimport {\n  isAnyArrayBuffer,\n  isArrayBufferView,\n  isUint8Array,\n} from 'node-internal:internal_types';\n\nimport {\n  normalizeEncoding,\n  getEncodingOps,\n} from 'node-internal:internal_utils';\n\nimport { validateString } from 'node-internal:validators';\n\nimport internalUtil from 'node-internal:util';\nimport {\n  type InspectOptionsStylized,\n  inspect as utilInspect,\n} from 'node-internal:internal_inspect';\n\nconst { ASCII, BASE64, BASE64URL, HEX, LATIN1, UTF16LE, UTF8 } = bufferUtil;\n\n// Temporary buffers to convert numbers.\nconst float32Array = new Float32Array(1);\nconst uInt8Float32Array = new Uint8Array(float32Array.buffer);\nconst float64Array = new Float64Array(1);\nconst uInt8Float64Array = new Uint8Array(float64Array.buffer);\n\n// Check endianness.\nfloat32Array[0] = -1; // 0xBF800000\n// Either it is [0, 0, 128, 191] or [191, 128, 0, 0]. It is not possible to\n// check this with `os.endianness()` because that is determined at compile time.\nexport const bigEndian = uInt8Float32Array[3] === 0;\n\n// Node.js caps its max length at uint32_t max, we are very intentionally more\n// conservative here, capping at int32_t max.\nexport const kMaxLength = 2147483647;\nexport const kStringMaxLength = 536870888;\nconst MAX_UINT32 = 2 ** 32;\nconst kIsBuffer = Symbol('kIsBuffer');\n\nconst customInspectSymbol =\n  typeof Symbol === 'function' && typeof Symbol['for'] === 'function'\n    ? Symbol['for']('nodejs.util.inspect.custom')\n    : null;\n\n// One difference between Node.js and workerd is that, workerd\n// doesn't expose a setter for this, whereas Node.js does.\nexport const INSPECT_MAX_BYTES = 50;\n\nexport const constants = {\n  MAX_LENGTH: kMaxLength,\n  MAX_STRING_LENGTH: kStringMaxLength,\n};\n\nfunction createBuffer(length: number): Buffer {\n  if (length > kMaxLength) {\n    throw new ERR_OUT_OF_RANGE(\n      'The given length is invalid',\n      `0 to ${kMaxLength}`,\n      length\n    );\n  }\n  const buf = new Uint8Array(length);\n  Object.setPrototypeOf(buf, Buffer.prototype);\n  return buf as Buffer;\n}\n\ntype WithImplicitCoercion<T> = T | { valueOf(): T };\nexport type StringLike =\n  | WithImplicitCoercion<string>\n  | { [Symbol.toPrimitive](hint: 'string'): string };\ntype ArrayBufferLike = WithImplicitCoercion<ArrayBuffer | SharedArrayBuffer>;\ntype BufferSource =\n  | StringLike\n  | ArrayBufferLike\n  | Uint8Array\n  | ReadonlyArray<number>;\n\nexport interface Buffer extends Uint8Array {\n  readonly buffer: ArrayBuffer;\n  readonly parent: ArrayBuffer;\n  readonly byteOffset: number;\n  readonly length: number;\n  compare(\n    target: Uint8Array,\n    targetStart?: number,\n    targetEnd?: number,\n    sourceStart?: number,\n    sourceEnd?: number\n  ): number;\n  copy(\n    target: Uint8Array,\n    targetStart?: number,\n    sourceStart?: number,\n    sourceEnd?: number\n  ): number;\n  equals(other: Uint8Array): boolean;\n  fill(value: number, offset?: number, end?: number): this;\n  fill(value: string, encoding?: string): this;\n  fill(value: string, offset?: number, end?: number, encoding?: string): this;\n  fill(value: Uint8Array, offset?: number, end?: number): this;\n  includes(value: number, byteOffset?: number): boolean;\n  includes(value: string, encoding?: string): boolean;\n  includes(value: string, byteOffset?: number, encoding?: string): boolean;\n  includes(value: Uint8Array, byteOffset?: number): boolean;\n  indexOf(value: number, byteOffset?: number): number;\n  indexOf(value: string, encoding?: string): number;\n  indexOf(value: string, byteOffset?: number, encoding?: string): number;\n  indexOf(value: Uint8Array, byteOffset?: number): number;\n  lastIndexOf(value: number, byteOffset?: number): number;\n  lastIndexOf(value: string, encoding?: string): number;\n  lastIndexOf(value: string, byteOffset?: number, encoding?: string): number;\n  lastIndexOf(value: Uint8Array, byteOffset?: number): number;\n  readBigInt64BE(offset?: number): bigint;\n  readBigInt64LE(offset?: number): bigint;\n  readBigUInt64BE(offset?: number): bigint;\n  readBigUInt64LE(offset?: number): bigint;\n  readDoubleBE(offset?: number): number;\n  readDoubleLE(offset?: number): number;\n  readFloatBE(offset?: number): number;\n  readFloatLE(offset?: number): number;\n  readInt8(offset?: number): number;\n  readInt16BE(offset?: number): number;\n  readInt16LE(offset?: number): number;\n  readInt32BE(offset?: number): number;\n  readInt32LE(offset?: number): number;\n  readIntBE(offset?: number, byteLength?: number): number;\n  readIntLE(offset?: number, byteLength?: number): number;\n  readUInt8(offset?: number): number;\n  readUInt16BE(offset?: number): number;\n  readUInt16LE(offset?: number): number;\n  readUInt32BE(offset?: number): number;\n  readUInt32LE(offset?: number): number;\n  readUIntBE(offset?: number, byteLength?: number): number;\n  readUIntLE(offset?: number, byteLength?: number): number;\n  swap16(): this;\n  swap32(): this;\n  swap64(): this;\n  toJSON(): { type: 'Buffer'; data: number[] };\n  toString(encoding?: string, start?: number, end?: number): string;\n  write(string: string, encoding?: string): number;\n  write(string: string, offset?: number, encoding?: string): number;\n  write(\n    string: string,\n    offset?: number,\n    length?: number,\n    encoding?: string\n  ): number;\n  writeBigInt64BE(value: bigint, offset?: number): number;\n  writeBigInt64LE(value: bigint, offset?: number): number;\n  writeBigUInt64BE(value: bigint, offset?: number): number;\n  writeBigUInt64LE(value: bigint, offset?: number): number;\n  writeDoubleBE(value: number, offset?: number): number;\n  writeDoubleLE(value: number, offset?: number): number;\n  writeFloatBE(value: number, offset?: number): number;\n  writeFloatLE(value: number, offset?: number): number;\n  writeInt8(value: number, offset?: number): number;\n  writeInt16BE(value: number, offset?: number): number;\n  writeInt16LE(value: number, offset?: number): number;\n  writeInt32BE(value: number, offset?: number): number;\n  writeInt32LE(value: number, offset?: number): number;\n  writeIntBE(value: number, offset?: number, byteLength?: number): number;\n  writeIntLE(value: number, offset?: number, byteLength?: number): number;\n  writeUInt8(value: number, offset?: number): number;\n  writeUInt16BE(value: number, offset?: number): number;\n  writeUInt16LE(value: number, offset?: number): number;\n  writeUInt32BE(value: number, offset?: number): number;\n  writeUInt32LE(value: number, offset?: number): number;\n  writeUIntBE(value: number, offset?: number, byteLength?: number): number;\n  writeUIntLE(value: number, offset?: number, byteLength?: number): number;\n  new (array: Iterable<number>): Buffer;\n  new (\n    arrayBuffer: ArrayBufferLike,\n    byteOffset?: number,\n    length?: number\n  ): Buffer;\n  new (buffer: ArrayBufferView): Buffer;\n  new (size: number): Buffer;\n  new (string: string, encoding?: string): Buffer;\n}\n\ntype FillValue = string | number | ArrayBufferView;\n\nexport function Buffer(value: number): Buffer;\nexport function Buffer(value: StringLike, encoding?: string): Buffer;\nexport function Buffer(\n  value: ArrayBufferLike,\n  byteOffset?: number,\n  length?: number\n): Buffer;\nexport function Buffer(\n  value: Uint8Array | ReadonlyArray<number>,\n  byteOffset?: number,\n  length?: number\n): Buffer;\nexport function Buffer(value: StringLike, encoding?: string): Buffer;\nexport function Buffer(\n  value: number | BufferSource,\n  encodingOrOffset?: string | number,\n  length?: number\n): Buffer {\n  if (typeof value === 'number') {\n    if (typeof encodingOrOffset === 'string') {\n      throw new ERR_INVALID_ARG_TYPE('string', 'string', value);\n    }\n    return allocUnsafe(value);\n  }\n\n  return _from(value, encodingOrOffset, length);\n}\n\nObject.setPrototypeOf(Buffer.prototype, Uint8Array.prototype);\nObject.setPrototypeOf(Buffer, Uint8Array);\n\nObject.defineProperties(Buffer, {\n  // By default, Node.js allocates Buffers from a shared slab allocation\n  // as a performance optimization. While this is fast, it's not ideal\n  // in our environment as it could potentially be used to leak buffer\n  // data across multiple requests. We always want our Buffer instances\n  // to be distinct allocations. To signal this, we keep our poolSize\n  // always set to 0.\n  poolSize: {\n    enumerable: true,\n    value: 0,\n    writable: false,\n  },\n});\n\nObject.defineProperties(Buffer.prototype, {\n  parent: {\n    enumerable: true,\n    get() {\n      if (!Buffer.isBuffer(this)) {\n        return void 0;\n      }\n      return this.buffer;\n    },\n  },\n  offset: {\n    enumerable: true,\n    get() {\n      if (!Buffer.isBuffer(this)) {\n        return void 0;\n      }\n      return this.byteOffset;\n    },\n  },\n  [kIsBuffer]: {\n    enumerable: false,\n    configurable: false,\n    value: true,\n  },\n});\n\nfunction _from(\n  value: BufferSource,\n  encodingOrOffset?: string | number,\n  length?: number\n): Buffer {\n  if (typeof value === 'string') {\n    return fromString(value, encodingOrOffset as string | undefined) as Buffer;\n  }\n\n  if (typeof value === 'object' && value != null) {\n    if (isAnyArrayBuffer(value)) {\n      return fromArrayBuffer(\n        value as ArrayBufferLike,\n        encodingOrOffset as number,\n        length\n      ) as Buffer;\n    }\n\n    const valueOf = value?.valueOf();\n    if (\n      valueOf != null &&\n      valueOf !== value &&\n      (typeof valueOf === 'string' || typeof valueOf === 'object')\n    ) {\n      return _from(valueOf as BufferSource, encodingOrOffset, length);\n    }\n\n    if (\n      (value as any).length !== undefined ||\n      isAnyArrayBuffer((value as any).buffer)\n    ) {\n      if (typeof (value as any).length !== 'number') {\n        return createBuffer(0);\n      }\n\n      return fromArrayLike(value as any) as Buffer;\n    }\n\n    if (\n      (value as any).type === 'Buffer' &&\n      Array.isArray((value as any).data)\n    ) {\n      return fromArrayLike((value as any).data) as Buffer;\n    }\n\n    const toPrimitive = (value as any)[Symbol.toPrimitive];\n    if (typeof toPrimitive === 'function') {\n      const primitive = toPrimitive('string');\n      if (typeof primitive === 'string') {\n        return fromString(\n          primitive,\n          encodingOrOffset as string | undefined\n        ) as Buffer;\n      }\n    }\n  }\n\n  throw new ERR_INVALID_ARG_TYPE(\n    'first argument',\n    [\n      'string',\n      'Buffer',\n      'TypedArray',\n      'ArrayBuffer',\n      'SharedArrayBuffer',\n      'Array',\n      'Array-like Object',\n    ],\n    value\n  );\n}\n\nfunction from(value: StringLike, encoding?: string): Buffer;\nfunction from(\n  value: ArrayBufferLike,\n  byteOffset?: number,\n  length?: number\n): Buffer;\nfunction from(\n  value: Uint8Array | ReadonlyArray<number>,\n  byteOffset?: number,\n  length?: number\n): Buffer;\nfunction from(\n  value: BufferSource,\n  encodingOrOffset?: string | number,\n  length?: number\n) {\n  return _from(value, encodingOrOffset, length);\n}\n\nfunction fromString(string: StringLike, encoding?: string) {\n  if (typeof encoding !== 'string' || encoding === '') {\n    encoding = 'utf8';\n  }\n  const normalizedEncoding = normalizeEncoding(encoding);\n  if (normalizedEncoding === undefined) {\n    throw new ERR_UNKNOWN_ENCODING(`${encoding}`);\n  }\n\n  const ab = bufferUtil.decodeString(`${string}`, normalizedEncoding);\n  if (ab === undefined) {\n    throw new ERR_INVALID_ARG_VALUE(\n      'string',\n      string,\n      `Unable to decode string using encoding ${encoding}`\n    );\n  }\n  return fromArrayBuffer(ab.buffer, 0, ab.byteLength);\n}\n\nfunction fromArrayLike(array: Uint8Array | ReadonlyArray<number>) {\n  const u8 = Uint8Array.from(array);\n  return fromArrayBuffer(u8.buffer, u8.byteOffset, u8.byteLength);\n}\n\nfunction fromArrayBuffer(\n  obj: ArrayBufferLike,\n  byteOffset: number,\n  length?: number\n) {\n  // Convert byteOffset to integer\n  if (byteOffset === undefined) {\n    byteOffset = 0;\n  } else {\n    byteOffset = +byteOffset;\n    if (Number.isNaN(byteOffset)) {\n      byteOffset = 0;\n    }\n  }\n\n  const maxLength = (obj as ArrayBuffer).byteLength - byteOffset;\n\n  if (maxLength < 0) {\n    throw new ERR_BUFFER_OUT_OF_BOUNDS('offset');\n  }\n\n  if (length === undefined) {\n    length = maxLength;\n  } else {\n    // Convert length to non-negative integer.\n    length = +length;\n    if (length > 0) {\n      if (length > maxLength) {\n        throw new ERR_BUFFER_OUT_OF_BOUNDS('length');\n      }\n    } else {\n      length = 0;\n    }\n  }\n\n  const buffer = new Uint8Array(obj as ArrayBuffer, byteOffset, length);\n  Object.setPrototypeOf(buffer, Buffer.prototype);\n  return buffer;\n}\n\nBuffer.from = from;\n\nfunction of(...args: number[]) {\n  const buf = Buffer.alloc(args.length);\n  for (let k = 0; k < args.length; k++) buf[k] = args[k]!;\n  return buf;\n}\n\nBuffer.of = of;\n\nfunction alloc(size: number, fill?: FillValue, encoding?: string): Buffer {\n  validateNumber(size, 'size');\n  if (Number.isNaN(size)) {\n    throw new ERR_INVALID_ARG_VALUE.RangeError('size', size);\n  }\n  if (size >= kMaxLength) {\n    throw new ERR_OUT_OF_RANGE('size', `0 to ${kMaxLength}`, size);\n  }\n\n  const buffer = createBuffer(size);\n  if (fill !== undefined) {\n    if (encoding !== undefined) {\n      validateString(encoding, 'encoding');\n    }\n    return buffer.fill(fill as any, encoding);\n  }\n  return buffer;\n}\n\nBuffer.alloc = alloc;\n\nfunction allocUnsafe(size: number): Buffer {\n  return alloc(size);\n}\n\nBuffer.allocUnsafe = allocUnsafe;\nBuffer.allocUnsafeSlow = allocUnsafe;\n\nexport function SlowBuffer(length: number) {\n  return alloc(+length);\n}\n\nObject.setPrototypeOf(SlowBuffer.prototype, Uint8Array.prototype);\nObject.setPrototypeOf(SlowBuffer, Uint8Array);\n\nBuffer.isBuffer = function isBuffer(b: unknown): b is Buffer {\n  return b instanceof Buffer;\n};\n\nexport function compare(a: Buffer | Uint8Array, b: Buffer | Uint8Array) {\n  if (isInstance(a, Uint8Array)) {\n    const buf = a as Uint8Array;\n    a = fromArrayBuffer(buf.buffer, buf.byteOffset, buf.byteLength);\n  }\n  if (isInstance(b, Uint8Array)) {\n    const buf = b as Uint8Array;\n    b = fromArrayBuffer(buf.buffer, buf.byteOffset, buf.byteLength);\n  }\n  if (!Buffer.isBuffer(a)) {\n    throw new ERR_INVALID_ARG_TYPE('a', ['Buffer', 'Uint8Array'], typeof a);\n  }\n  if (!Buffer.isBuffer(b)) {\n    throw new ERR_INVALID_ARG_TYPE('b', ['Buffer', 'Uint8Array'], typeof b);\n  }\n  if (a === b) return 0;\n\n  return bufferUtil.compare(a, b);\n}\n\nBuffer.compare = compare;\n\nexport function isEncoding(encoding: unknown): encoding is string {\n  return (\n    typeof encoding === 'string' &&\n    encoding.length !== 0 &&\n    normalizeEncoding(encoding) !== undefined\n  );\n}\n\nBuffer.isEncoding = isEncoding;\n\nBuffer.concat = function concat(\n  list: (Buffer | Uint8Array)[],\n  length?: number\n) {\n  if (!Array.isArray(list)) {\n    throw new ERR_INVALID_ARG_TYPE('list', '(Buffer|Uint8Array)[]', list);\n  }\n\n  if (list.length === 0) return alloc(0);\n\n  if (length === undefined) {\n    length = 0;\n    for (let i = 0; i < list.length; i++) {\n      if (list[i]!.length !== undefined) {\n        length += list[i]!.length;\n      } else {\n        throw new ERR_INVALID_ARG_TYPE(\n          'list',\n          '(Buffer|Uint8Array)[]',\n          list[i]\n        );\n      }\n    }\n  }\n  validateOffset(length, 'length');\n\n  const ab = bufferUtil.concat(list, length as number);\n  return fromArrayBuffer(ab.buffer, 0, length);\n};\n\nfunction base64ByteLength(str: string) {\n  let len = str.length;\n  if (str.charCodeAt(len - 1) === 0x3d) {\n    len--;\n  }\n  if (len > 1 && str.charCodeAt(len - 1) === 0x3d) len--;\n\n  // Base64 ratio: 3/4\n  return (len * 3) >>> 2;\n}\n\nfunction byteLength(\n  string: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer,\n  encoding?: string\n) {\n  if (typeof string !== 'string') {\n    if (isArrayBufferView(string) || isAnyArrayBuffer(string)) {\n      return string.byteLength;\n    }\n\n    throw new ERR_INVALID_ARG_TYPE(\n      'string',\n      ['string', 'Buffer', 'ArrayBuffer'],\n      string\n    );\n  }\n\n  string = `${string}`;\n  const normalizedEncoding = normalizeEncoding(encoding) ?? UTF8;\n\n  switch (normalizedEncoding) {\n    case ASCII:\n    // Fall through\n    case LATIN1:\n      return (string as string).length;\n    case UTF16LE:\n      return (string as string).length * 2;\n    case BASE64:\n    // Fall through\n    case BASE64URL:\n      return base64ByteLength(string as string);\n    case HEX:\n      return (string as string).length >>> 1;\n    case UTF8:\n    // Fall-through\n    default:\n      return bufferUtil.byteLength(string as string);\n  }\n}\n\nBuffer.byteLength = byteLength;\n\nBuffer.prototype.swap16 = function swap16() {\n  const len = this.length;\n  if (len % 2 !== 0) {\n    throw new ERR_INVALID_BUFFER_SIZE(16);\n  }\n  bufferUtil.swap(this, 16);\n  return this;\n};\n\nBuffer.prototype.swap32 = function swap32() {\n  const len = this.length;\n  if (len % 4 !== 0) {\n    throw new ERR_INVALID_BUFFER_SIZE(32);\n  }\n  bufferUtil.swap(this, 32);\n  return this;\n};\n\nBuffer.prototype.swap64 = function swap64() {\n  const len = this.length;\n  if (len % 8 !== 0) {\n    throw new ERR_INVALID_BUFFER_SIZE(64);\n  }\n  bufferUtil.swap(this, 64);\n  return this;\n};\n\nBuffer.prototype.toString = function toString(\n  encoding?: string,\n  start?: number,\n  end?: number\n) {\n  if (arguments.length === 0) {\n    return bufferUtil.toString(this, 0, this.length, UTF8);\n  }\n\n  const len = this.length;\n\n  if (start === undefined || start <= 0) {\n    start = 0;\n  } else if (start >= len) {\n    return '';\n  } else {\n    start |= 0;\n  }\n\n  if (end === undefined || end > len) {\n    end = len;\n  } else {\n    end |= 0;\n  }\n\n  if ((end as number) <= start) {\n    return '';\n  }\n\n  const normalizedEncoding = getEncodingOps(encoding);\n  if (normalizedEncoding === undefined) {\n    throw new ERR_UNKNOWN_ENCODING(`${encoding}`);\n  }\n\n  return bufferUtil.toString(\n    this,\n    start as number,\n    end as number,\n    normalizedEncoding\n  );\n};\n\nBuffer.prototype.toLocaleString = Buffer.prototype.toString;\n\nBuffer.prototype.equals = function equals(b: Buffer | Uint8Array) {\n  return compare(this, b) === 0;\n};\n\nBuffer.prototype.inspect = function inspect(\n  _recurseTimes: number,\n  ctx: InspectOptionsStylized\n) {\n  let str = '';\n  const max = Math.min(this.byteLength, INSPECT_MAX_BYTES);\n\n  str = this.toString('hex', 0, max)\n    .replace(/(.{2})/g, '$1 ')\n    .trim();\n  const remaining = this.byteLength - max;\n  if (remaining > 0) {\n    str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`;\n  }\n  // Inspect special properties as well, if possible.\n  if (ctx) {\n    let extras = false;\n    const filter = ctx.showHidden\n      ? internalUtil.ALL_PROPERTIES\n      : internalUtil.ONLY_ENUMERABLE;\n    const obj: Record<PropertyKey, unknown> = { __proto__: null };\n    internalUtil.getOwnNonIndexProperties(this, filter).forEach((key) => {\n      extras = true;\n      obj[key] = this[key];\n    });\n    if (extras) {\n      if (this.length !== 0) str += ', ';\n      // '[Object: null prototype] {'.length === 26\n      // This is guarded with a test.\n      str += utilInspect(obj, {\n        ...ctx,\n        breakLength: Infinity,\n        compact: true,\n      }).slice(27, -2);\n    }\n  }\n  return '<Buffer ' + str + '>';\n};\n\nif (customInspectSymbol) {\n  Buffer.prototype[customInspectSymbol] = Buffer.prototype.inspect;\n}\n\nBuffer.prototype.compare = function compare(\n  target: Buffer | Uint8Array,\n  start?: number,\n  end?: number,\n  thisStart?: number,\n  thisEnd?: number\n) {\n  if (isInstance(target, Uint8Array)) {\n    target = fromArrayBuffer(\n      target.buffer,\n      target.byteOffset,\n      target.byteLength\n    );\n  }\n  if (!Buffer.isBuffer(target)) {\n    throw new ERR_INVALID_ARG_TYPE('target', ['Buffer', 'Uint8Array'], target);\n  }\n\n  if (start === undefined) {\n    start = 0;\n  } else {\n    validateOffset(start, 'targetStart', 0, kMaxLength);\n  }\n\n  if (end === undefined) {\n    end = target.length;\n  } else {\n    validateOffset(end, 'targetEnd', 0, target.length);\n  }\n\n  if (thisStart === undefined) {\n    thisStart = 0;\n  } else {\n    validateOffset(thisStart as number, 'sourceStart', 0, kMaxLength);\n  }\n\n  if (thisEnd === undefined) {\n    thisEnd = this.length;\n  } else {\n    validateOffset(thisEnd as number, 'sourceEnd', 0, this.length);\n  }\n\n  return bufferUtil.compare(this, target, {\n    aStart: thisStart as number,\n    aEnd: thisEnd as number,\n    bStart: start as number,\n    bEnd: end as number,\n  });\n};\n\nfunction includes(\n  this: Buffer,\n  val: string | number | Buffer | Uint8Array,\n  byteOffset?: number,\n  encoding?: string\n) {\n  return this.indexOf(val as any, byteOffset, encoding) !== -1;\n}\n\nBuffer.prototype.includes = includes;\n\n// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`,\n// OR the last index of `val` in `buffer` at offset <= `byteOffset`.\n//\n// Arguments:\n// - buffer - a Buffer to search\n// - val - a string, Buffer, or number\n// - byteOffset - an index into `buffer`; will be clamped to an int32\n// - encoding - an optional encoding, relevant if val is a string\n// - dir - true for indexOf, false for lastIndexOf\nfunction bidirectionalIndexOf(\n  buffer: Uint8Array,\n  val: string | number | Buffer | Uint8Array,\n  byteOffset: number | string | undefined,\n  encoding: string | undefined,\n  dir: boolean | undefined\n) {\n  if (Buffer.isBuffer(val) && !isUint8Array(val)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'val',\n      ['string', 'number', 'Buffer', 'Uint8Array'],\n      val\n    );\n  }\n\n  if (typeof byteOffset === 'string') {\n    encoding = byteOffset;\n    byteOffset = undefined;\n  } else if ((byteOffset as number) > 0x7fffffff) {\n    byteOffset = 0x7fffffff;\n  } else if ((byteOffset as number) < -0x80000000) {\n    byteOffset = -0x80000000;\n  }\n  // Coerce to Number. Values like null and [] become 0.\n  byteOffset = +(byteOffset as number);\n  // If the offset is undefined, \"foo\", {}, coerces to NaN, search whole buffer.\n  if (Number.isNaN(byteOffset)) {\n    byteOffset = dir ? 0 : buffer.length || buffer.byteLength;\n  }\n  dir = !!dir; // Cast to bool.\n\n  if (typeof val === 'number') {\n    val = (val >>> 0) & 0xff;\n    if (dir) {\n      return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset);\n    } else {\n      return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset);\n    }\n  }\n\n  if (typeof val !== 'string' && !isUint8Array(val) && !Buffer.isBuffer(val)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'value',\n      ['number', 'string', 'Buffer', 'Uint8Array'],\n      val\n    );\n  }\n\n  const normalizedEncoding = normalizeEncoding(encoding);\n  if (normalizedEncoding === undefined) {\n    throw new ERR_UNKNOWN_ENCODING(`${encoding}`);\n  }\n\n  const result = bufferUtil.indexOf(\n    buffer,\n    val,\n    byteOffset,\n    normalizedEncoding,\n    dir\n  );\n  return result == null ? -1 : result;\n}\n\nBuffer.prototype.indexOf = function indexOf(\n  val: string | number | Buffer | Uint8Array,\n  byteOffset?: number | string,\n  encoding?: string\n) {\n  return bidirectionalIndexOf(this, val, byteOffset, encoding, true);\n};\n\nBuffer.prototype.lastIndexOf = function lastIndexOf(\n  val: string | number | Buffer | Uint8Array,\n  byteOffset?: number | string,\n  encoding?: string\n) {\n  return bidirectionalIndexOf(this, val, byteOffset, encoding, false);\n};\n\nBuffer.prototype.asciiSlice = function asciiSlice(start: number, end: number) {\n  validateOffset(start, 'start', 0, this.length);\n  validateOffset(end, 'end', 0, this.length);\n  return bufferUtil.toString(this, start, end, ASCII);\n};\n\nBuffer.prototype.base64Slice = function base64Slice(\n  start: number,\n  end: number\n) {\n  validateOffset(start, 'start', 0, this.length);\n  validateOffset(end, 'end', 0, this.length);\n  return bufferUtil.toString(this, start, end, BASE64);\n};\n\nBuffer.prototype.base64urlSlice = function base64urlSlice(\n  start: number,\n  end: number\n) {\n  validateOffset(start, 'start', 0, this.length);\n  validateOffset(end, 'end', 0, this.length);\n  return bufferUtil.toString(this, start, end, BASE64URL);\n};\n\nBuffer.prototype.hexSlice = function hexSlice(start: number, end: number) {\n  validateOffset(start, 'start', 0, this.length);\n  validateOffset(end, 'end', 0, this.length);\n  return bufferUtil.toString(this, start, end, HEX);\n};\n\nBuffer.prototype.latin1Slice = function latin1Slice(\n  start: number,\n  end: number\n) {\n  validateOffset(start, 'start', 0, this.length);\n  validateOffset(end, 'end', 0, this.length);\n  return bufferUtil.toString(this, start, end, LATIN1);\n};\n\nBuffer.prototype.ucs2Slice = function ucs2Slice(start: number, end: number) {\n  validateOffset(start, 'start', 0, this.length);\n  validateOffset(end, 'end', 0, this.length);\n  return bufferUtil.toString(this, start, end, UTF16LE);\n};\n\nBuffer.prototype.utf8Slice = function utf8Slice(start: number, end: number) {\n  validateOffset(start, 'start', 0, this.length);\n  validateOffset(end, 'end', 0, this.length);\n  return bufferUtil.toString(this, start, end, UTF8);\n};\n\nBuffer.prototype.asciiWrite = function asciiWrite(\n  string: StringLike,\n  offset?: number,\n  length?: number\n) {\n  offset ??= 0;\n  length ??= this.length;\n  validateOffset(offset as number, 'offset', 0, this.length);\n  validateOffset(length as number, 'length', 0, this.length - offset);\n  return bufferUtil.write(\n    this,\n    `${string}`,\n    offset as number,\n    length as number,\n    ASCII\n  );\n};\n\nBuffer.prototype.base64Write = function base64Write(\n  string: StringLike,\n  offset?: number,\n  length?: number\n) {\n  offset ??= 0;\n  length ??= this.length;\n  validateOffset(offset as number, 'offset', 0, this.length);\n  validateOffset(length as number, 'length', 0, this.length - offset);\n  return bufferUtil.write(\n    this,\n    `${string}`,\n    offset as number,\n    length as number,\n    BASE64\n  );\n};\n\nBuffer.prototype.base64urlWrite = function base64urlWrite(\n  string: StringLike,\n  offset?: number,\n  length?: number\n) {\n  offset ??= 0;\n  length ??= this.length;\n  validateOffset(offset as number, 'offset', 0, this.length);\n  validateOffset(length as number, 'length', 0, this.length - offset);\n  return bufferUtil.write(\n    this,\n    `${string}`,\n    offset as number,\n    length as number,\n    BASE64URL\n  );\n};\n\nBuffer.prototype.hexWrite = function hexWrite(\n  string: StringLike,\n  offset: number,\n  length: number\n) {\n  offset ??= 0;\n  length ??= this.length;\n  validateOffset(offset as number, 'offset', 0, this.length);\n  validateOffset(length as number, 'length', 0, this.length - offset);\n  return bufferUtil.write(\n    this,\n    `${string}`,\n    offset as number,\n    length as number,\n    HEX\n  );\n};\n\nBuffer.prototype.latin1Write = function latin1Write(\n  string: StringLike,\n  offset: number,\n  length: number\n) {\n  offset ??= 0;\n  length ??= this.length;\n  validateOffset(offset as number, 'offset', 0, this.length);\n  validateOffset(length as number, 'length', 0, this.length - offset);\n  return bufferUtil.write(\n    this,\n    `${string}`,\n    offset as number,\n    length as number,\n    LATIN1\n  );\n};\n\nBuffer.prototype.ucs2Write = function ucs2Write(\n  string: StringLike,\n  offset: number,\n  length: number\n) {\n  offset ??= 0;\n  length ??= this.length;\n  validateOffset(offset as number, 'offset', 0, this.length);\n  validateOffset(length as number, 'length', 0, this.length - offset);\n  return bufferUtil.write(\n    this,\n    `${string}`,\n    offset as number,\n    length as number,\n    UTF16LE\n  );\n};\n\nBuffer.prototype.utf8Write = function utf8Write(\n  string: StringLike,\n  offset: number,\n  length: number\n) {\n  offset ??= 0;\n  length ??= this.length;\n  validateOffset(offset as number, 'offset', 0, this.length);\n  validateOffset(length as number, 'length', 0, this.length - offset);\n  return bufferUtil.write(\n    this,\n    `${string}`,\n    offset as number,\n    length as number,\n    UTF8\n  );\n};\n\nBuffer.prototype.write = function write(\n  string: StringLike,\n  offset?: number | string,\n  length?: number | string,\n  encoding?: string\n) {\n  string = `${string}`;\n  if (offset === undefined) {\n    // Buffer#write(string)\n    return bufferUtil.write(this, string as string, 0, this.length, UTF8);\n  }\n\n  if (length === undefined && typeof offset === 'string') {\n    // Buffer#write(string, encoding)\n    encoding = offset;\n    length = this.length;\n    offset = 0;\n  } else {\n    // Buffer#write(string, offset[, length][, encoding])\n    validateOffset(offset as number, 'offset', 0, this.length);\n\n    const remaining = this.length - (offset as number);\n\n    if (length === undefined) {\n      length = remaining;\n    } else if (typeof length === 'string') {\n      encoding = length;\n      length = remaining;\n    } else {\n      validateOffset(length, 'length', 0, this.length);\n      if (length > remaining) {\n        length = remaining;\n      }\n    }\n  }\n\n  if (!encoding) {\n    return bufferUtil.write(\n      this,\n      string as string,\n      offset as number,\n      length as number,\n      UTF8\n    );\n  }\n\n  const normalizedEncoding = normalizeEncoding(encoding);\n  if (normalizedEncoding === undefined) {\n    throw new ERR_UNKNOWN_ENCODING(`${encoding}`);\n  }\n\n  return bufferUtil.write(\n    this,\n    string as string,\n    offset as number,\n    length as number,\n    normalizedEncoding\n  );\n};\n\nBuffer.prototype.toJSON = function toJSON() {\n  return {\n    type: 'Buffer',\n    data: Array.prototype.slice.call(this._arr || this, 0),\n  };\n};\n\nBuffer.prototype.slice = function slice(start: number, end?: number) {\n  const len = this.length;\n  start = ~~start;\n  end = end === void 0 ? len : ~~end;\n  if (start < 0) {\n    start += len;\n    if (start < 0) {\n      start = 0;\n    }\n  } else if (start > len) {\n    start = len;\n  }\n  if (end === undefined) {\n    end = this.byteLength;\n  } else if (end < 0) {\n    end += len;\n    if (end < 0) {\n      end = 0;\n    }\n  } else if (end > len) {\n    end = len;\n  }\n  if ((end as number) < start) {\n    end = start;\n  }\n  const newBuf = this.subarray(start, end);\n  Object.setPrototypeOf(newBuf, Buffer.prototype);\n  return newBuf;\n};\n\nBuffer.prototype.readUintLE = Buffer.prototype.readUIntLE = function readUIntLE(\n  offset: number,\n  byteLength: number\n) {\n  if (offset === undefined) {\n    throw new ERR_INVALID_ARG_TYPE('offset', 'number', offset);\n  }\n  switch (byteLength) {\n    case 1:\n      return this.readUInt8(offset);\n    case 2:\n      return this.readUInt16LE(offset);\n    case 3:\n      return readUInt24LE(this, offset);\n    case 4:\n      return this.readUInt32LE(offset);\n    case 5:\n      return readUInt40LE(this, offset);\n    case 6:\n      return readUInt48LE(this, offset);\n    default:\n      boundsError(byteLength, 6, 'byteLength');\n  }\n};\n\nBuffer.prototype.readUintBE = Buffer.prototype.readUIntBE = function readUIntBE(\n  offset: number,\n  byteLength: number\n) {\n  if (offset === undefined) {\n    throw new ERR_INVALID_ARG_TYPE('offset', 'number', offset);\n  }\n  switch (byteLength) {\n    case 1:\n      return this.readUInt8(offset);\n    case 2:\n      return this.readUInt16BE(offset);\n    case 3:\n      return readUInt24BE(this, offset);\n    case 4:\n      return this.readUInt32BE(offset);\n    case 5:\n      return readUInt40BE(this, offset);\n    case 6:\n      return readUInt48BE(this, offset);\n    default:\n      boundsError(byteLength, 6, 'byteLength');\n  }\n};\n\nBuffer.prototype.readUint8 = Buffer.prototype.readUInt8 = function readUInt8(\n  offset: number = 0\n) {\n  validateOffset(offset, 'offset', 0, this.length);\n  const val = this[offset];\n  if (val === undefined) {\n    boundsError(offset, this.length - 1);\n  }\n\n  return val;\n};\n\nBuffer.prototype.readUint16BE = Buffer.prototype.readUInt16BE = readUInt16BE;\n\nBuffer.prototype.readUint16LE = Buffer.prototype.readUInt16LE =\n  function readUInt16LE(offset: number = 0) {\n    validateOffset(offset, 'offset', 0, this.length);\n    const first = this[offset];\n    const last = this[offset + 1];\n    if (first === undefined || last === undefined) {\n      boundsError(offset, this.length - 2);\n    }\n\n    return first + last * 2 ** 8;\n  };\n\nBuffer.prototype.readUint32LE = Buffer.prototype.readUInt32LE =\n  function readUInt32LE(this: Buffer, offset: number = 0) {\n    validateOffset(offset, 'offset', 0, this.length);\n    const first = this[offset];\n    const last = this[offset + 3];\n    if (first === undefined || last === undefined) {\n      boundsError(offset, this.length - 4);\n    }\n\n    return (\n      first +\n      this[++offset]! * 2 ** 8 +\n      this[++offset]! * 2 ** 16 +\n      last * 2 ** 24\n    );\n  };\n\nBuffer.prototype.readUint32BE = Buffer.prototype.readUInt32BE = readUInt32BE;\n\nBuffer.prototype.readBigUint64LE = Buffer.prototype.readBigUInt64LE =\n  function readBigUInt64LE(this: Buffer, offset: number = 0) {\n    offset = offset >>> 0;\n    validateOffset(offset, 'offset', 0, this.length);\n    const first = this[offset];\n    const last = this[offset + 7];\n    if (first === undefined || last === undefined) {\n      boundsError(offset, this.length - 8);\n    }\n    const lo =\n      first +\n      this[++offset]! * 2 ** 8 +\n      this[++offset]! * 2 ** 16 +\n      this[++offset]! * 2 ** 24;\n    const hi =\n      this[++offset]! +\n      this[++offset]! * 2 ** 8 +\n      this[++offset]! * 2 ** 16 +\n      last * 2 ** 24;\n    return BigInt(lo) + (BigInt(hi) << BigInt(32));\n  };\n\nBuffer.prototype.readBigUint64BE = Buffer.prototype.readBigUInt64BE =\n  function readBigUInt64BE(this: Buffer, offset: number = 0) {\n    offset = offset >>> 0;\n    validateOffset(offset, 'offset', 0, this.length);\n    const first = this[offset];\n    const last = this[offset + 7];\n    if (first === undefined || last === undefined) {\n      boundsError(offset, this.length - 8);\n    }\n    const hi =\n      first * 2 ** 24 +\n      this[++offset]! * 2 ** 16 +\n      this[++offset]! * 2 ** 8 +\n      this[++offset]!;\n    const lo =\n      this[++offset]! * 2 ** 24 +\n      this[++offset]! * 2 ** 16 +\n      this[++offset]! * 2 ** 8 +\n      last;\n    return (BigInt(hi) << BigInt(32)) + BigInt(lo);\n  };\n\nBuffer.prototype.readIntLE = function readIntLE(\n  offset: number,\n  byteLength: number\n) {\n  if (offset === undefined) {\n    throw new ERR_INVALID_ARG_TYPE('offset', 'number', offset);\n  }\n  switch (byteLength) {\n    case 1:\n      return this.readInt8(offset);\n    case 2:\n      return this.readInt16LE(offset);\n    case 3:\n      return readInt24LE(this, offset);\n    case 4:\n      return this.readInt32LE(offset);\n    case 5:\n      return readInt40LE(this, offset);\n    case 6:\n      return readInt48LE(this, offset);\n    default:\n      boundsError(byteLength, 6, 'byteLength');\n  }\n};\n\nBuffer.prototype.readIntBE = function readIntBE(\n  offset: number,\n  byteLength: number\n) {\n  if (offset === undefined) {\n    throw new ERR_INVALID_ARG_TYPE('offset', 'number', offset);\n  }\n  switch (byteLength) {\n    case 1:\n      return this.readInt8(offset);\n    case 2:\n      return this.readInt16BE(offset);\n    case 3:\n      return readInt24BE(this, offset);\n    case 4:\n      return this.readInt32BE(offset);\n    case 5:\n      return readInt40BE(this, offset);\n    case 6:\n      return readInt48BE(this, offset);\n    default:\n      boundsError(byteLength, 6, 'byteLength');\n  }\n};\n\nBuffer.prototype.readInt8 = function readInt8(offset: number = 0) {\n  validateOffset(offset, 'offset', 0, this.length);\n  const val = this[offset];\n  if (val === undefined) {\n    boundsError(offset, this.length - 1);\n  }\n\n  return val | ((val & (2 ** 7)) * 0x1fffffe);\n};\n\nBuffer.prototype.readInt16LE = function readInt16LE(offset: number = 0) {\n  validateOffset(offset, 'offset', 0, this.length);\n  const first = this[offset];\n  const last = this[offset + 1];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, this.length - 2);\n  }\n\n  const val = first + last * 2 ** 8;\n  return val | ((val & (2 ** 15)) * 0x1fffe);\n};\n\nBuffer.prototype.readInt16BE = function readInt16BE(offset: number = 0) {\n  validateOffset(offset, 'offset', 0, this.length);\n  const first = this[offset];\n  const last = this[offset + 1];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, this.length - 2);\n  }\n\n  const val = first * 2 ** 8 + last;\n  return val | ((val & (2 ** 15)) * 0x1fffe);\n};\n\nBuffer.prototype.readInt32LE = function readInt32LE(offset: number = 0) {\n  validateOffset(offset, 'offset', 0, this.length);\n  const first = this[offset];\n  const last = this[offset + 3];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, this.length - 4);\n  }\n\n  return (\n    first + this[++offset] * 2 ** 8 + this[++offset] * 2 ** 16 + (last << 24)\n  ); // Overflow\n};\n\nBuffer.prototype.readInt32BE = function readInt32BE(offset: number = 0) {\n  validateOffset(offset, 'offset', 0, this.length);\n  const first = this[offset];\n  const last = this[offset + 3];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, this.length - 4);\n  }\n\n  return (\n    (first << 24) + // Overflow\n    this[++offset] * 2 ** 16 +\n    this[++offset] * 2 ** 8 +\n    last\n  );\n};\n\nBuffer.prototype.readBigInt64LE = function readBigInt64LE(\n  this: Buffer,\n  offset: number = 0\n) {\n  offset = offset >>> 0;\n  validateOffset(offset, 'offset', 0, this.length);\n  const first = this[offset];\n  const last = this[offset + 7];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, this.length - 8);\n  }\n  const val =\n    this[offset + 4]! +\n    this[offset + 5]! * 2 ** 8 +\n    this[offset + 6]! * 2 ** 16 +\n    (last << 24);\n  return (\n    (BigInt(val) << BigInt(32)) +\n    BigInt(\n      first +\n        this[++offset]! * 2 ** 8 +\n        this[++offset]! * 2 ** 16 +\n        this[++offset]! * 2 ** 24\n    )\n  );\n};\n\nBuffer.prototype.readBigInt64BE = function readBigInt64BE(\n  this: Buffer,\n  offset: number = 0\n) {\n  offset = offset >>> 0;\n  validateOffset(offset, 'offset', 0, this.length);\n  const first = this[offset];\n  const last = this[offset + 7];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, this.length - 8);\n  }\n  const val =\n    (first << 24) +\n    this[++offset]! * 2 ** 16 +\n    this[++offset]! * 2 ** 8 +\n    this[++offset]!;\n  return (\n    (BigInt(val) << BigInt(32)) +\n    BigInt(\n      this[++offset]! * 2 ** 24 +\n        this[++offset]! * 2 ** 16 +\n        this[++offset]! * 2 ** 8 +\n        last\n    )\n  );\n};\n\nBuffer.prototype.readFloatLE = function readFloatLE(offset: number = 0) {\n  return bigEndian\n    ? readFloatBackwards(this, offset)\n    : readFloatForwards(this, offset);\n};\n\nBuffer.prototype.readFloatBE = function readFloatBE(offset: number = 0) {\n  return bigEndian\n    ? readFloatForwards(this, offset)\n    : readFloatBackwards(this, offset);\n};\n\nBuffer.prototype.readDoubleLE = function readDoubleLE(offset: number = 0) {\n  return bigEndian\n    ? readDoubleBackwards(this, offset)\n    : readDoubleForwards(this, offset);\n};\n\nBuffer.prototype.readDoubleBE = function readDoubleBE(offset: number = 0) {\n  return bigEndian\n    ? readDoubleForwards(this, offset)\n    : readDoubleBackwards(this, offset);\n};\n\nBuffer.prototype.writeUintLE = Buffer.prototype.writeUIntLE =\n  function writeUIntLE(value: number, offset: number, byteLength: number) {\n    switch (byteLength) {\n      case 1:\n        return writeU_Int8(this, value, offset, 0, 0xff);\n      case 2:\n        return writeU_Int16LE(this, value, offset, 0, 0xffff);\n      case 3:\n        return writeU_Int24LE(this, value, offset, 0, 0xffffff);\n      case 4:\n        return writeU_Int32LE(this, value, offset, 0, 0xffffffff);\n      case 5:\n        return writeU_Int40LE(this, value, offset, 0, 0xffffffffff);\n      case 6:\n        return writeU_Int48LE(this, value, offset, 0, 0xffffffffffff);\n      default:\n        boundsError(byteLength, 6, 'byteLength');\n    }\n  };\n\nBuffer.prototype.writeUintBE = Buffer.prototype.writeUIntBE =\n  function writeUIntBE(value: number, offset: number, byteLength: number) {\n    switch (byteLength) {\n      case 1:\n        return writeU_Int8(this, value, offset, 0, 0xff);\n      case 2:\n        return writeU_Int16BE(this, value, offset, 0, 0xffff);\n      case 3:\n        return writeU_Int24BE(this, value, offset, 0, 0xffffff);\n      case 4:\n        return writeU_Int32BE(this, value, offset, 0, 0xffffffff);\n      case 5:\n        return writeU_Int40BE(this, value, offset, 0, 0xffffffffff);\n      case 6:\n        return writeU_Int48BE(this, value, offset, 0, 0xffffffffffff);\n      default:\n        boundsError(byteLength, 6, 'byteLength');\n    }\n  };\n\nBuffer.prototype.writeUint8 = Buffer.prototype.writeUInt8 = function writeUInt8(\n  value: number,\n  offset: number = 0\n) {\n  return writeU_Int8(this, value, offset, 0, 0xff);\n};\n\nBuffer.prototype.writeUint16LE = Buffer.prototype.writeUInt16LE =\n  function writeUInt16LE(value: number, offset: number = 0) {\n    return writeU_Int16LE(this, value, offset, 0, 0xffff);\n  };\n\nBuffer.prototype.writeUint16BE = Buffer.prototype.writeUInt16BE =\n  function writeUInt16BE(value: number, offset: number = 0) {\n    return writeU_Int16BE(this, value, offset, 0, 0xffff);\n  };\n\nBuffer.prototype.writeUint32LE = Buffer.prototype.writeUInt32LE =\n  function writeUInt32LE(value: number, offset: number = 0) {\n    return _writeUInt32LE(this, value, offset, 0, 0xffffffff);\n  };\n\nBuffer.prototype.writeUint32BE = Buffer.prototype.writeUInt32BE =\n  function writeUInt32BE(value: number, offset: number = 0) {\n    return _writeUInt32BE(this, value, offset, 0, 0xffffffff);\n  };\n\nfunction wrtBigUInt64LE(\n  buf: Buffer,\n  value: bigint,\n  offset: number,\n  min: bigint,\n  max: bigint\n) {\n  checkIntBI(value, min, max, buf, offset, 7);\n  let lo = Number(value & BigInt(4294967295));\n  buf[offset++] = lo;\n  lo = lo >> 8;\n  buf[offset++] = lo;\n  lo = lo >> 8;\n  buf[offset++] = lo;\n  lo = lo >> 8;\n  buf[offset++] = lo;\n  let hi = Number((value >> BigInt(32)) & BigInt(4294967295));\n  buf[offset++] = hi;\n  hi = hi >> 8;\n  buf[offset++] = hi;\n  hi = hi >> 8;\n  buf[offset++] = hi;\n  hi = hi >> 8;\n  buf[offset++] = hi;\n  return offset;\n}\n\nfunction wrtBigUInt64BE(\n  buf: Buffer,\n  value: bigint,\n  offset: number,\n  min: bigint,\n  max: bigint\n) {\n  checkIntBI(value, min, max, buf, offset, 7);\n  let lo = Number(value & BigInt(4294967295));\n  buf[offset + 7] = lo;\n  lo = lo >> 8;\n  buf[offset + 6] = lo;\n  lo = lo >> 8;\n  buf[offset + 5] = lo;\n  lo = lo >> 8;\n  buf[offset + 4] = lo;\n  let hi = Number((value >> BigInt(32)) & BigInt(4294967295));\n  buf[offset + 3] = hi;\n  hi = hi >> 8;\n  buf[offset + 2] = hi;\n  hi = hi >> 8;\n  buf[offset + 1] = hi;\n  hi = hi >> 8;\n  buf[offset] = hi;\n  return offset + 8;\n}\n\nBuffer.prototype.writeBigUint64LE = Buffer.prototype.writeBigUInt64LE =\n  function writeBigUInt64LE(this: Buffer, value: bigint, offset: number = 0) {\n    return wrtBigUInt64LE(this, value, offset, 0n, 0xffffffffffffffffn);\n  };\n\nBuffer.prototype.writeBigUint64BE = Buffer.prototype.writeBigUInt64BE =\n  function writeBigUInt64BE(this: Buffer, value: bigint, offset: number = 0) {\n    return wrtBigUInt64BE(this, value, offset, 0n, 0xffffffffffffffffn);\n  };\n\nBuffer.prototype.writeIntLE = function writeIntLE(\n  value: number,\n  offset: number,\n  byteLength: number\n) {\n  switch (byteLength) {\n    case 1:\n      return writeU_Int8(this, value, offset, -0x80, 0x7f);\n    case 2:\n      return writeU_Int16LE(this, value, offset, -0x8000, 0x7fff);\n    case 3:\n      return writeU_Int24LE(this, value, offset, -0x800000, 0x7fffff);\n    case 4:\n      return writeU_Int32LE(this, value, offset, -0x80000000, 0x7fffffff);\n    case 5:\n      return writeU_Int40LE(this, value, offset, -0x8000000000, 0x7fffffffff);\n    case 6:\n      return writeU_Int48LE(\n        this,\n        value,\n        offset,\n        -0x800000000000,\n        0x7fffffffffff\n      );\n    default:\n      boundsError(byteLength, 6, 'byteLength');\n  }\n};\n\nBuffer.prototype.writeIntBE = function writeIntBE(\n  value: number,\n  offset: number,\n  byteLength: number\n) {\n  switch (byteLength) {\n    case 1:\n      return writeU_Int8(this, value, offset, -0x80, 0x7f);\n    case 2:\n      return writeU_Int16BE(this, value, offset, -0x8000, 0x7fff);\n    case 3:\n      return writeU_Int24BE(this, value, offset, -0x800000, 0x7fffff);\n    case 4:\n      return writeU_Int32BE(this, value, offset, -0x80000000, 0x7fffffff);\n    case 5:\n      return writeU_Int40BE(this, value, offset, -0x8000000000, 0x7fffffffff);\n    case 6:\n      return writeU_Int48BE(\n        this,\n        value,\n        offset,\n        -0x800000000000,\n        0x7fffffffffff\n      );\n    default:\n      boundsError(byteLength, 6, 'byteLength');\n  }\n};\n\nBuffer.prototype.writeInt8 = function writeInt8(\n  value: number,\n  offset: number = 0\n) {\n  return writeU_Int8(this, value, offset, -0x80, 0x7f);\n};\n\nBuffer.prototype.writeInt16LE = function writeInt16LE(\n  value: number,\n  offset: number = 0\n) {\n  return writeU_Int16LE(this, value, offset, -0x8000, 0x7fff);\n};\n\nBuffer.prototype.writeInt16BE = function writeInt16BE(\n  value: number,\n  offset: number = 0\n) {\n  return writeU_Int16BE(this, value, offset, -0x8000, 0x7fff);\n};\n\nBuffer.prototype.writeInt32LE = function writeInt32LE(\n  value: number,\n  offset: number = 0\n) {\n  return writeU_Int32LE(this, value, offset, -0x80000000, 0x7fffffff);\n};\n\nBuffer.prototype.writeInt32BE = function writeInt32BE(\n  value: number,\n  offset: number = 0\n) {\n  return writeU_Int32BE(this, value, offset, -0x80000000, 0x7fffffff);\n};\n\nBuffer.prototype.writeBigInt64LE = function writeBigInt64LE(\n  this: Buffer,\n  value: bigint,\n  offset: number = 0\n) {\n  return wrtBigUInt64LE(\n    this,\n    value,\n    offset,\n    -0x8000000000000000n,\n    0x7fffffffffffffffn\n  );\n};\n\nBuffer.prototype.writeBigInt64BE = function writeBigInt64BE(\n  this: Buffer,\n  value: bigint,\n  offset: number = 0\n) {\n  return wrtBigUInt64BE(\n    this,\n    value,\n    offset,\n    -0x8000000000000000n,\n    0x7fffffffffffffffn\n  );\n};\n\nBuffer.prototype.writeFloatLE = function writeFloatLE(\n  value: number,\n  offset: number\n) {\n  return bigEndian\n    ? writeFloatBackwards(this, value, offset)\n    : writeFloatForwards(this, value, offset);\n};\n\nBuffer.prototype.writeFloatBE = function writeFloatBE(\n  value: number,\n  offset: number\n) {\n  return bigEndian\n    ? writeFloatForwards(this, value, offset)\n    : writeFloatBackwards(this, value, offset);\n};\n\nBuffer.prototype.writeDoubleLE = function writeDoubleLE(\n  value: number,\n  offset: number\n) {\n  return bigEndian\n    ? writeDoubleBackwards(this, value, offset)\n    : writeDoubleForwards(this, value, offset);\n};\n\nBuffer.prototype.writeDoubleBE = function writeDoubleBE(\n  value: number,\n  offset: number\n) {\n  return bigEndian\n    ? writeDoubleForwards(this, value, offset)\n    : writeDoubleBackwards(this, value, offset);\n};\n\nBuffer.prototype.copy = function copy(\n  target: Buffer | Uint8Array,\n  targetStart?: number,\n  sourceStart?: number,\n  sourceEnd?: number\n) {\n  if (!isUint8Array(target)) {\n    throw new ERR_INVALID_ARG_TYPE('target', ['Buffer', 'Uint8Array'], target);\n  }\n\n  targetStart = toInteger(targetStart, 0);\n  if ((targetStart as number) < 0) {\n    throw new ERR_OUT_OF_RANGE('targetStart', '>= 0', targetStart);\n  }\n\n  sourceStart = toInteger(sourceStart, 0);\n  if ((sourceStart as number) < 0) {\n    throw new ERR_OUT_OF_RANGE('sourceStart', '>= 0', sourceStart);\n  }\n  if ((sourceStart as number) >= MAX_UINT32) {\n    throw new ERR_OUT_OF_RANGE('sourceStart', `< ${MAX_UINT32}`, sourceStart);\n  }\n\n  sourceEnd ??= this.length;\n  sourceEnd = toInteger(sourceEnd, 0);\n  if ((sourceEnd as number) < 0) {\n    throw new ERR_OUT_OF_RANGE('sourceEnd', '>= 0', sourceEnd);\n  }\n  if ((sourceEnd as number) >= MAX_UINT32) {\n    throw new ERR_OUT_OF_RANGE('sourceEnd', `< ${MAX_UINT32}`, sourceEnd);\n  }\n\n  if ((targetStart as number) >= target.length) {\n    return 0;\n  }\n\n  if (\n    (sourceEnd as number) > 0 &&\n    (sourceEnd as number) < (sourceStart as number)\n  ) {\n    sourceEnd = sourceStart;\n  }\n  if (sourceEnd === sourceStart) {\n    return 0;\n  }\n  if (target.length === 0 || this.length === 0) {\n    return 0;\n  }\n\n  if ((sourceEnd as number) > this.length) {\n    sourceEnd = this.length;\n  }\n\n  if (\n    target.length - (targetStart as number) <\n    (sourceEnd as number) - (sourceStart as number)\n  ) {\n    sourceEnd =\n      target.length - (targetStart as number) + (sourceStart as number);\n  }\n\n  const len = (sourceEnd as number) - (sourceStart as number);\n  if (this === target) {\n    this.copyWithin(\n      targetStart as number,\n      sourceStart as number,\n      sourceEnd as number\n    );\n  } else {\n    const sub = this.subarray(sourceStart, sourceEnd);\n    target.set(sub, targetStart);\n  }\n\n  return len;\n};\n\nBuffer.prototype.fill = function fill(\n  val: string | number | Buffer | Uint8Array,\n  start?: number | string,\n  end?: number,\n  encoding?: string\n) {\n  let normalizedEncoding: Encoding | undefined;\n  if (typeof val === 'string') {\n    if (typeof start === 'string') {\n      encoding = start;\n      start = 0;\n      end = this.length;\n    } else if (typeof end === 'string') {\n      encoding = end;\n      end = this.length;\n    }\n    normalizedEncoding = normalizeEncoding(encoding);\n    if (normalizedEncoding === undefined) {\n      throw new ERR_UNKNOWN_ENCODING(`${encoding}`);\n    }\n    if (val.length === 1) {\n      const code = val.charCodeAt(0);\n      if ((encoding === 'utf8' && code < 128) || encoding === 'latin1') {\n        val = code;\n      }\n    }\n  }\n\n  if (start !== undefined) {\n    validateNumber(start, 'start');\n  }\n  if (end !== undefined) {\n    validateNumber(end, 'end');\n  }\n\n  if ((end as number) < 0 || (end as number) > this.length) {\n    throw new ERR_OUT_OF_RANGE('end', `0 to ${this.length}`, end);\n  }\n  if (\n    (start as number) < 0 ||\n    this.length < (start as number) ||\n    this.length < (end as number)\n  ) {\n    throw new ERR_OUT_OF_RANGE('start', '0 to end', start);\n  }\n  if ((end as number) <= (start as number)) {\n    return this;\n  }\n  start = (start as number) >>> 0;\n  end = end === void 0 ? this.length : end >>> 0;\n\n  if (typeof val === 'string') {\n    bufferUtil.fillImpl(\n      this,\n      val as string,\n      start as number,\n      end as number,\n      normalizedEncoding\n    );\n    return this;\n  }\n\n  if (isArrayBufferView(val)) {\n    if ((val as ArrayBufferView).byteLength === 0) {\n      throw new ERR_INVALID_ARG_VALUE('value', 'zero-length');\n    }\n    bufferUtil.fillImpl(\n      this,\n      val as ArrayBufferView,\n      start as number,\n      end as number\n    );\n    return this;\n  }\n\n  if (typeof val === 'number') {\n    val = val & 255;\n  } else if (typeof val === 'boolean') {\n    val = Number(val);\n  }\n  val ??= 0;\n\n  Uint8Array.prototype.fill.call(this, val as number, start, end);\n\n  return this;\n};\n\nfunction checkBounds(buf: Buffer, offset: number, byteLength2: number) {\n  validateOffset(offset, 'offset', 0, buf.length);\n  if (buf[offset] === undefined || buf[offset + byteLength2] === undefined) {\n    boundsError(offset, buf.length - (byteLength2 + 1));\n  }\n}\n\nfunction checkIntBI(\n  value: bigint | number,\n  min: bigint | number,\n  max: bigint | number,\n  buf: Buffer,\n  offset: number,\n  byteLength2: number\n) {\n  if (value > max || value < min) {\n    const n = typeof min === 'bigint' ? 'n' : '';\n    let range;\n    if (byteLength2 > 3) {\n      if (min === 0 || min === BigInt(0)) {\n        range = `>= 0${n} and < 2${n} ** ${(byteLength2 + 1) * 8}${n}`;\n      } else {\n        range = `>= -(2${n} ** ${(byteLength2 + 1) * 8 - 1}${n}) and < 2 ** ${\n          (byteLength2 + 1) * 8 - 1\n        }${n}`;\n      }\n    } else {\n      range = `>= ${min}${n} and <= ${max}${n}`;\n    }\n    throw new ERR_OUT_OF_RANGE('value', range, value);\n  }\n  checkBounds(buf, offset, byteLength2);\n}\n\nfunction isInstance(obj: unknown, type: Function) {\n  return (\n    obj instanceof type ||\n    (obj != null &&\n      obj.constructor != null &&\n      obj.constructor.name != null &&\n      obj.constructor.name === type.name)\n  );\n}\n\nfunction readUInt48LE(buf: Buffer | Uint8Array, offset: number = 0) {\n  validateOffset(offset, 'offset', 0, buf.length);\n  const first = buf[offset];\n  const last = buf[offset + 5];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, buf.length - 6);\n  }\n\n  return (\n    first +\n    buf[++offset]! * 2 ** 8 +\n    buf[++offset]! * 2 ** 16 +\n    buf[++offset]! * 2 ** 24 +\n    (buf[++offset]! + last * 2 ** 8) * 2 ** 32\n  );\n}\n\nfunction readUInt40LE(buf: Buffer | Uint8Array, offset: number = 0) {\n  validateOffset(offset, 'offset', 0, buf.length);\n  const first = buf[offset];\n  const last = buf[offset + 4];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, buf.length - 5);\n  }\n\n  return (\n    first +\n    buf[++offset]! * 2 ** 8 +\n    buf[++offset]! * 2 ** 16 +\n    buf[++offset]! * 2 ** 24 +\n    last * 2 ** 32\n  );\n}\n\nfunction readUInt24LE(buf: Buffer | Uint8Array, offset: number = 0) {\n  validateOffset(offset, 'offset', 0, buf.length);\n  const first = buf[offset];\n  const last = buf[offset + 2];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, buf.length - 3);\n  }\n\n  return first + buf[++offset]! * 2 ** 8 + last * 2 ** 16;\n}\n\nfunction readUInt48BE(buf: Buffer | Uint8Array, offset: number = 0) {\n  validateOffset(offset, 'offset', 0, buf.length);\n  const first = buf[offset];\n  const last = buf[offset + 5];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, buf.length - 6);\n  }\n\n  return (\n    (first * 2 ** 8 + buf[++offset]!) * 2 ** 32 +\n    buf[++offset]! * 2 ** 24 +\n    buf[++offset]! * 2 ** 16 +\n    buf[++offset]! * 2 ** 8 +\n    last\n  );\n}\n\nfunction readUInt40BE(buf: Buffer | Uint8Array, offset: number = 0) {\n  validateOffset(offset, 'offset', 0, buf.length);\n  const first = buf[offset];\n  const last = buf[offset + 4];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, buf.length - 5);\n  }\n\n  return (\n    first * 2 ** 32 +\n    buf[++offset]! * 2 ** 24 +\n    buf[++offset]! * 2 ** 16 +\n    buf[++offset]! * 2 ** 8 +\n    last\n  );\n}\n\nfunction readUInt24BE(buf: Buffer | Uint8Array, offset: number = 0) {\n  validateOffset(offset, 'offset', 0, buf.length);\n  const first = buf[offset];\n  const last = buf[offset + 2];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, buf.length - 3);\n  }\n\n  return first * 2 ** 16 + buf[++offset]! * 2 ** 8 + last;\n}\n\nfunction readUInt16BE(this: Buffer, offset: number = 0) {\n  validateOffset(offset, 'offset', 0, this.length);\n  const first = this[offset];\n  const last = this[offset + 1];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, this.length - 2);\n  }\n\n  return first * 2 ** 8 + last;\n}\n\nfunction readUInt32BE(this: Buffer, offset: number = 0) {\n  validateOffset(offset, 'offset', 0, this.length);\n  const first = this[offset];\n  const last = this[offset + 3];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, this.length - 4);\n  }\n\n  return (\n    first * 2 ** 24 +\n    this[++offset]! * 2 ** 16 +\n    this[++offset]! * 2 ** 8 +\n    last\n  );\n}\n\nfunction readDoubleBackwards(buffer: Buffer | Uint8Array, offset: number = 0) {\n  validateOffset(offset, 'offset', 0, buffer.length);\n  const first = buffer[offset];\n  const last = buffer[offset + 7];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, buffer.length - 8);\n  }\n\n  uInt8Float64Array[7] = first;\n  uInt8Float64Array[6] = buffer[++offset]!;\n  uInt8Float64Array[5] = buffer[++offset]!;\n  uInt8Float64Array[4] = buffer[++offset]!;\n  uInt8Float64Array[3] = buffer[++offset]!;\n  uInt8Float64Array[2] = buffer[++offset]!;\n  uInt8Float64Array[1] = buffer[++offset]!;\n  uInt8Float64Array[0] = last;\n  return float64Array[0];\n}\n\nfunction readDoubleForwards(buffer: Buffer | Uint8Array, offset: number = 0) {\n  validateOffset(offset, 'offset', 0, buffer.length);\n  const first = buffer[offset];\n  const last = buffer[offset + 7];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, buffer.length - 8);\n  }\n\n  uInt8Float64Array[0] = first;\n  uInt8Float64Array[1] = buffer[++offset]!;\n  uInt8Float64Array[2] = buffer[++offset]!;\n  uInt8Float64Array[3] = buffer[++offset]!;\n  uInt8Float64Array[4] = buffer[++offset]!;\n  uInt8Float64Array[5] = buffer[++offset]!;\n  uInt8Float64Array[6] = buffer[++offset]!;\n  uInt8Float64Array[7] = last;\n  return float64Array[0];\n}\n\nfunction writeDoubleForwards(\n  buffer: Buffer | Uint8Array,\n  val: number,\n  offset: number = 0\n) {\n  val = +val;\n  checkBounds(buffer as any, offset, 7);\n\n  float64Array[0] = val;\n  buffer[offset++] = uInt8Float64Array[0]!;\n  buffer[offset++] = uInt8Float64Array[1]!;\n  buffer[offset++] = uInt8Float64Array[2]!;\n  buffer[offset++] = uInt8Float64Array[3]!;\n  buffer[offset++] = uInt8Float64Array[4]!;\n  buffer[offset++] = uInt8Float64Array[5]!;\n  buffer[offset++] = uInt8Float64Array[6]!;\n  buffer[offset++] = uInt8Float64Array[7]!;\n  return offset;\n}\n\nfunction writeDoubleBackwards(\n  buffer: Buffer | Uint8Array,\n  val: number,\n  offset: number = 0\n) {\n  val = +val;\n  checkBounds(buffer as any, offset, 7);\n\n  float64Array[0] = val;\n  buffer[offset++] = uInt8Float64Array[7]!;\n  buffer[offset++] = uInt8Float64Array[6]!;\n  buffer[offset++] = uInt8Float64Array[5]!;\n  buffer[offset++] = uInt8Float64Array[4]!;\n  buffer[offset++] = uInt8Float64Array[3]!;\n  buffer[offset++] = uInt8Float64Array[2]!;\n  buffer[offset++] = uInt8Float64Array[1]!;\n  buffer[offset++] = uInt8Float64Array[0]!;\n  return offset;\n}\n\nfunction readFloatBackwards(buffer: Buffer | Uint8Array, offset: number = 0) {\n  validateOffset(offset, 'offset', 0, buffer.length);\n  const first = buffer[offset];\n  const last = buffer[offset + 3];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, buffer.length - 4);\n  }\n\n  uInt8Float32Array[3] = first;\n  uInt8Float32Array[2] = buffer[++offset]!;\n  uInt8Float32Array[1] = buffer[++offset]!;\n  uInt8Float32Array[0] = last;\n  return float32Array[0];\n}\n\nfunction readFloatForwards(buffer: Buffer | Uint8Array, offset: number = 0) {\n  validateOffset(offset, 'offset', 0, buffer.length);\n  const first = buffer[offset];\n  const last = buffer[offset + 3];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, buffer.length - 4);\n  }\n\n  uInt8Float32Array[0] = first;\n  uInt8Float32Array[1] = buffer[++offset]!;\n  uInt8Float32Array[2] = buffer[++offset]!;\n  uInt8Float32Array[3] = last;\n  return float32Array[0];\n}\n\nfunction writeFloatForwards(\n  buffer: Buffer | Uint8Array,\n  val: number,\n  offset: number = 0\n) {\n  val = +val;\n  checkBounds(buffer as any, offset, 3);\n\n  float32Array[0] = val;\n  buffer[offset++] = uInt8Float32Array[0]!;\n  buffer[offset++] = uInt8Float32Array[1]!;\n  buffer[offset++] = uInt8Float32Array[2]!;\n  buffer[offset++] = uInt8Float32Array[3]!;\n  return offset;\n}\n\nfunction writeFloatBackwards(\n  buffer: Buffer | Uint8Array,\n  val: number,\n  offset: number = 0\n) {\n  val = +val;\n  checkBounds(buffer as any, offset, 3);\n\n  float32Array[0] = val;\n  buffer[offset++] = uInt8Float32Array[3]!;\n  buffer[offset++] = uInt8Float32Array[2]!;\n  buffer[offset++] = uInt8Float32Array[1]!;\n  buffer[offset++] = uInt8Float32Array[0]!;\n  return offset;\n}\n\nfunction readInt24LE(buf: Buffer | Uint8Array, offset: number = 0) {\n  validateOffset(offset, 'offset', 0, buf.length);\n  const first = buf[offset];\n  const last = buf[offset + 2];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, buf.length - 3);\n  }\n\n  const val = first + buf[++offset]! * 2 ** 8 + last * 2 ** 16;\n  return val | ((val & (2 ** 23)) * 0x1fe);\n}\n\nfunction readInt40LE(buf: Buffer | Uint8Array, offset: number = 0) {\n  validateOffset(offset, 'offset', 0, buf.length);\n  const first = buf[offset];\n  const last = buf[offset + 4];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, buf.length - 5);\n  }\n\n  return (\n    (last | ((last & (2 ** 7)) * 0x1fffffe)) * 2 ** 32 +\n    first +\n    buf[++offset]! * 2 ** 8 +\n    buf[++offset]! * 2 ** 16 +\n    buf[++offset]! * 2 ** 24\n  );\n}\n\nfunction readInt48LE(buf: Buffer | Uint8Array, offset: number = 0) {\n  validateOffset(offset, 'offset', 0, buf.length);\n  const first = buf[offset];\n  const last = buf[offset + 5];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, buf.length - 6);\n  }\n\n  const val = buf[offset + 4]! + last * 2 ** 8;\n  return (\n    (val | ((val & (2 ** 15)) * 0x1fffe)) * 2 ** 32 +\n    first +\n    buf[++offset]! * 2 ** 8 +\n    buf[++offset]! * 2 ** 16 +\n    buf[++offset]! * 2 ** 24\n  );\n}\n\nfunction readInt24BE(buf: Buffer | Uint8Array, offset: number = 0) {\n  validateOffset(offset, 'offset', 0, buf.length);\n  const first = buf[offset];\n  const last = buf[offset + 2];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, buf.length - 3);\n  }\n\n  const val = first * 2 ** 16 + buf[++offset]! * 2 ** 8 + last;\n  return val | ((val & (2 ** 23)) * 0x1fe);\n}\n\nfunction readInt48BE(buf: Buffer | Uint8Array, offset: number = 0) {\n  validateOffset(offset, 'offset', 0, buf.length);\n  const first = buf[offset];\n  const last = buf[offset + 5];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, buf.length - 6);\n  }\n\n  const val = buf[++offset]! + first * 2 ** 8;\n  return (\n    (val | ((val & (2 ** 15)) * 0x1fffe)) * 2 ** 32 +\n    buf[++offset]! * 2 ** 24 +\n    buf[++offset]! * 2 ** 16 +\n    buf[++offset]! * 2 ** 8 +\n    last\n  );\n}\n\nfunction readInt40BE(buf: Buffer | Uint8Array, offset: number = 0) {\n  validateOffset(offset, 'offset', 0, buf.length);\n  const first = buf[offset];\n  const last = buf[offset + 4];\n  if (first === undefined || last === undefined) {\n    boundsError(offset, buf.length - 5);\n  }\n\n  return (\n    (first | ((first & (2 ** 7)) * 0x1fffffe)) * 2 ** 32 +\n    buf[++offset]! * 2 ** 24 +\n    buf[++offset]! * 2 ** 16 +\n    buf[++offset]! * 2 ** 8 +\n    last\n  );\n}\n\nfunction boundsError(value: number, length: number, type?: string): never {\n  if (Math.floor(value) !== value) {\n    throw new ERR_OUT_OF_RANGE(type || 'offset', 'an integer', value);\n  }\n\n  if (length < 0) {\n    throw new ERR_BUFFER_OUT_OF_BOUNDS();\n  }\n\n  throw new ERR_OUT_OF_RANGE(\n    type || 'offset',\n    `>= ${type ? 1 : 0} and <= ${length}`,\n    value\n  );\n}\n\nfunction validateNumber(value: unknown, name: string) {\n  if (typeof value !== 'number') {\n    throw new ERR_INVALID_ARG_TYPE(name, 'number', value);\n  }\n}\n\nfunction checkInt(\n  value: number | bigint,\n  min: number | bigint,\n  max: number | bigint,\n  buf: Buffer,\n  offset: number,\n  byteLength: number\n) {\n  if (value > max || value < min) {\n    const n = typeof min === 'bigint' ? 'n' : '';\n    let range;\n    if (byteLength > 3) {\n      if (min === 0 || min === 0n) {\n        range = `>= 0${n} and < 2${n} ** ${(byteLength + 1) * 8}${n}`;\n      } else {\n        range =\n          `>= -(2${n} ** ${(byteLength + 1) * 8 - 1}${n}) and ` +\n          `< 2${n} ** ${(byteLength + 1) * 8 - 1}${n}`;\n      }\n    } else {\n      range = `>= ${min}${n} and <= ${max}${n}`;\n    }\n    throw new ERR_OUT_OF_RANGE('value', range, value);\n  }\n  checkBounds(buf, offset, byteLength);\n}\n\nfunction toInteger(n: number | undefined, defaultVal: number) {\n  if (n === undefined) n = 0;\n  n = +(n as number);\n  if (\n    !Number.isNaN(n) &&\n    n >= Number.MIN_SAFE_INTEGER &&\n    n <= Number.MAX_SAFE_INTEGER\n  ) {\n    return n % 1 === 0 ? n : Math.floor(n);\n  }\n  return defaultVal;\n}\n\nfunction writeU_Int8(\n  buf: Buffer,\n  value: number,\n  offset: number,\n  min: number,\n  max: number\n) {\n  value = +value;\n  validateOffset(offset, 'offset', 0, buf.length);\n  if (value > max || value < min) {\n    throw new ERR_OUT_OF_RANGE('value', `>= ${min} and <= ${max}`, value);\n  }\n  if (buf[offset] === undefined) {\n    boundsError(offset, buf.length - 1);\n  }\n\n  buf[offset] = value;\n  return offset + 1;\n}\n\nfunction writeU_Int16BE(\n  buf: Buffer,\n  value: number,\n  offset: number,\n  min: number,\n  max: number\n) {\n  value = +value;\n  validateOffset(offset, 'offset', 0, buf.length);\n  checkInt(value, min, max, buf, offset, 1);\n\n  buf[offset++] = value >>> 8;\n  buf[offset++] = value;\n  return offset;\n}\n\nfunction _writeUInt32LE(\n  buf: Buffer,\n  value: number,\n  offset: number,\n  min: number,\n  max: number\n) {\n  value = +value;\n  validateOffset(offset, 'offset', 0, buf.length);\n  checkInt(value, min, max, buf, offset, 3);\n\n  buf[offset++] = value;\n  value = value >>> 8;\n  buf[offset++] = value;\n  value = value >>> 8;\n  buf[offset++] = value;\n  value = value >>> 8;\n  buf[offset++] = value;\n  return offset;\n}\n\nfunction writeU_Int16LE(\n  buf: Buffer,\n  value: number,\n  offset: number,\n  min: number,\n  max: number\n) {\n  value = +value;\n  validateOffset(offset, 'offset', 0, buf.length);\n  checkInt(value, min, max, buf, offset, 1);\n\n  buf[offset++] = value;\n  buf[offset++] = value >>> 8;\n  return offset;\n}\n\nfunction _writeUInt32BE(\n  buf: Buffer,\n  value: number,\n  offset: number,\n  min: number,\n  max: number\n) {\n  value = +value;\n  validateOffset(offset, 'offset', 0, buf.length);\n  checkInt(value, min, max, buf, offset, 3);\n\n  buf[offset + 3] = value;\n  value = value >>> 8;\n  buf[offset + 2] = value;\n  value = value >>> 8;\n  buf[offset + 1] = value;\n  value = value >>> 8;\n  buf[offset] = value;\n  return offset + 4;\n}\n\nfunction writeU_Int48BE(\n  buf: Buffer,\n  value: number,\n  offset: number,\n  min: number,\n  max: number\n) {\n  value = +value;\n  validateOffset(offset, 'offset', 0, buf.length);\n  checkInt(value, min, max, buf, offset, 5);\n\n  const newVal = Math.floor(value * 2 ** -32);\n  buf[offset++] = newVal >>> 8;\n  buf[offset++] = newVal;\n  buf[offset + 3] = value;\n  value = value >>> 8;\n  buf[offset + 2] = value;\n  value = value >>> 8;\n  buf[offset + 1] = value;\n  value = value >>> 8;\n  buf[offset] = value;\n  return offset + 4;\n}\n\nfunction writeU_Int40BE(\n  buf: Buffer,\n  value: number,\n  offset: number,\n  min: number,\n  max: number\n) {\n  value = +value;\n  validateOffset(offset, 'offset', 0, buf.length);\n  checkInt(value, min, max, buf, offset, 4);\n\n  buf[offset++] = Math.floor(value * 2 ** -32);\n  buf[offset + 3] = value;\n  value = value >>> 8;\n  buf[offset + 2] = value;\n  value = value >>> 8;\n  buf[offset + 1] = value;\n  value = value >>> 8;\n  buf[offset] = value;\n  return offset + 4;\n}\n\nfunction writeU_Int32BE(\n  buf: Buffer,\n  value: number,\n  offset: number,\n  min: number,\n  max: number\n) {\n  value = +value;\n  validateOffset(offset, 'offset', 0, buf.length);\n  checkInt(value, min, max, buf, offset, 3);\n\n  buf[offset + 3] = value;\n  value = value >>> 8;\n  buf[offset + 2] = value;\n  value = value >>> 8;\n  buf[offset + 1] = value;\n  value = value >>> 8;\n  buf[offset] = value;\n  return offset + 4;\n}\n\nfunction writeU_Int24BE(\n  buf: Buffer,\n  value: number,\n  offset: number,\n  min: number,\n  max: number\n) {\n  value = +value;\n  validateOffset(offset, 'offset', 0, buf.length);\n  checkInt(value, min, max, buf, offset, 2);\n\n  buf[offset + 2] = value;\n  value = value >>> 8;\n  buf[offset + 1] = value;\n  value = value >>> 8;\n  buf[offset] = value;\n  return offset + 3;\n}\n\nfunction validateOffset(\n  value: number,\n  name: string,\n  min: number = 0,\n  max: number = Number.MAX_SAFE_INTEGER\n) {\n  if (typeof value !== 'number') {\n    throw new ERR_INVALID_ARG_TYPE(name, 'number', value);\n  }\n  if (!Number.isInteger(value)) {\n    throw new ERR_OUT_OF_RANGE(name, 'an integer', value);\n  }\n  if (value < min || value > max) {\n    throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value);\n  }\n}\n\nfunction writeU_Int48LE(\n  buf: Buffer,\n  value: number,\n  offset: number,\n  min: number,\n  max: number\n) {\n  value = +value;\n  validateOffset(offset, 'offset', 0, buf.length);\n  checkInt(value, min, max, buf, offset, 5);\n\n  const newVal = Math.floor(value * 2 ** -32);\n  buf[offset++] = value;\n  value = value >>> 8;\n  buf[offset++] = value;\n  value = value >>> 8;\n  buf[offset++] = value;\n  value = value >>> 8;\n  buf[offset++] = value;\n  buf[offset++] = newVal;\n  buf[offset++] = newVal >>> 8;\n  return offset;\n}\n\nfunction writeU_Int40LE(\n  buf: Buffer,\n  value: number,\n  offset: number,\n  min: number,\n  max: number\n) {\n  value = +value;\n  validateOffset(offset, 'offset', 0, buf.length);\n  checkInt(value, min, max, buf, offset, 4);\n\n  const newVal = value;\n  buf[offset++] = value;\n  value = value >>> 8;\n  buf[offset++] = value;\n  value = value >>> 8;\n  buf[offset++] = value;\n  value = value >>> 8;\n  buf[offset++] = value;\n  buf[offset++] = Math.floor(newVal * 2 ** -32);\n  return offset;\n}\n\nfunction writeU_Int32LE(\n  buf: Buffer,\n  value: number,\n  offset: number,\n  min: number,\n  max: number\n) {\n  value = +value;\n  validateOffset(offset, 'offset', 0, buf.length);\n  checkInt(value, min, max, buf, offset, 3);\n\n  buf[offset++] = value;\n  value = value >>> 8;\n  buf[offset++] = value;\n  value = value >>> 8;\n  buf[offset++] = value;\n  value = value >>> 8;\n  buf[offset++] = value;\n  return offset;\n}\n\nfunction writeU_Int24LE(\n  buf: Buffer,\n  value: number,\n  offset: number,\n  min: number,\n  max: number\n) {\n  value = +value;\n  validateOffset(offset, 'offset', 0, buf.length);\n  checkInt(value, min, max, buf, offset, 2);\n\n  buf[offset++] = value;\n  value = value >>> 8;\n  buf[offset++] = value;\n  value = value >>> 8;\n  buf[offset++] = value;\n  return offset;\n}\n\nexport function isAscii(value: ArrayBufferView) {\n  if ((value as any)?.detached || (value as any)?.buffer?.detached) {\n    throw new Error(\n      'Unable to determine if buffer is ASCII when it is detached'\n    );\n  }\n  return bufferUtil.isAscii(value);\n}\n\nexport function isUtf8(value: ArrayBufferView) {\n  if ((value as any)?.detached || (value as any)?.buffer?.detached) {\n    throw new Error(\n      'Unable to determine if buffer is UTF8 when it is detached'\n    );\n  }\n  return bufferUtil.isUtf8(value);\n}\n\nexport function transcode(\n  source: ArrayBufferView,\n  fromEncoding: string,\n  toEncoding: string\n) {\n  if (!isArrayBufferView(source)) {\n    throw new ERR_INVALID_ARG_TYPE('source', 'ArrayBufferView', typeof source);\n  }\n  const normalizedFromEncoding = normalizeEncoding(fromEncoding);\n  if (normalizedFromEncoding === undefined) {\n    throw new ERR_UNKNOWN_ENCODING(fromEncoding);\n  }\n  const normalizedToEncoding = normalizeEncoding(toEncoding);\n  if (normalizedToEncoding === undefined) {\n    throw new ERR_UNKNOWN_ENCODING(toEncoding);\n  }\n\n  const u8: Uint8Array = bufferUtil.transcode(\n    source,\n    normalizedFromEncoding,\n    normalizedToEncoding\n  );\n  return Buffer.from(u8.buffer, u8.byteOffset, u8.byteLength);\n}\n\nexport function resolveObjectURL(_id: string): unknown {\n  throw new Error('resolveObjectURL is not implemented');\n}\n\nexport default {\n  Buffer,\n  constants,\n  kMaxLength,\n  kStringMaxLength,\n  SlowBuffer,\n  isAscii,\n  isUtf8,\n  INSPECT_MAX_BYTES,\n  transcode,\n  resolveObjectURL,\n};\n"
  },
  {
    "path": "src/node/internal/internal_comparisons.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n/* TODO: the following is adopted code, enabling linting one day */\n/* eslint-disable */\n\nimport { compare } from 'node-internal:internal_buffer';\n\nimport {\n  isAnyArrayBuffer,\n  isArrayBufferView,\n  isDate,\n  isMap,\n  isRegExp,\n  isSet,\n  isNativeError,\n  isBoxedPrimitive,\n  isNumberObject,\n  isStringObject,\n  isBooleanObject,\n  isBigIntObject,\n  isSymbolObject,\n  isFloat16Array,\n  isFloat32Array,\n  isFloat64Array,\n} from 'node-internal:internal_types';\n\nimport {\n  ONLY_ENUMERABLE,\n  SKIP_SYMBOLS,\n  getOwnNonIndexProperties,\n} from 'node-internal:internal_utils';\n\nconst kStrict = true;\n\nconst kNoIterator = 0;\nconst kIsArray = 1;\nconst kIsSet = 2;\nconst kIsMap = 3;\n\nfunction areSimilarRegExps(a: RegExp, b: RegExp) {\n  return (\n    a.source === b.source && a.flags === b.flags && a.lastIndex === b.lastIndex\n  );\n}\n\ntype FloatArray = Float16Array | Float32Array | Float64Array;\ntype AnyArrayBuffer = ArrayBuffer | SharedArrayBuffer;\n\ntype Memos = {\n  val1: Map<unknown, unknown>;\n  val2: Map<unknown, unknown>;\n  position: number;\n};\n\nfunction areSimilarFloatArrays(a: FloatArray, b: FloatArray) {\n  if (a.byteLength !== b.byteLength) {\n    return false;\n  }\n  for (let offset = 0; offset < a.byteLength; offset++) {\n    if (a[offset] !== b[offset]) {\n      return false;\n    }\n  }\n  return true;\n}\n\nfunction areSimilarTypedArrays(a: ArrayBufferView, b: ArrayBufferView) {\n  if (a.byteLength !== b.byteLength) {\n    return false;\n  }\n  return (\n    compare(\n      new Uint8Array(a.buffer, a.byteOffset, a.byteLength),\n      new Uint8Array(b.buffer, b.byteOffset, b.byteLength)\n    ) === 0\n  );\n}\n\nfunction areEqualArrayBuffers(buf1: AnyArrayBuffer, buf2: AnyArrayBuffer) {\n  return (\n    buf1.byteLength === buf2.byteLength &&\n    compare(new Uint8Array(buf1), new Uint8Array(buf2)) === 0\n  );\n}\n\nfunction areEqualBoxedPrimitives(val1: unknown, val2: unknown) {\n  if (isNumberObject(val1)) {\n    return (\n      isNumberObject(val2) &&\n      Object.is(\n        Number.prototype.valueOf.call(val1),\n        Number.prototype.valueOf.call(val2)\n      )\n    );\n  }\n  if (isStringObject(val1)) {\n    return (\n      isStringObject(val2) &&\n      String.prototype.valueOf.call(val1) ===\n        String.prototype.valueOf.call(val2)\n    );\n  }\n  if (isBooleanObject(val1)) {\n    return (\n      isBooleanObject(val2) &&\n      Boolean.prototype.valueOf.call(val1) ===\n        Boolean.prototype.valueOf.call(val2)\n    );\n  }\n  if (isBigIntObject(val1)) {\n    return (\n      isBigIntObject(val2) &&\n      BigInt.prototype.valueOf.call(val1) ===\n        BigInt.prototype.valueOf.call(val2)\n    );\n  }\n  if (isSymbolObject(val1)) {\n    return (\n      isSymbolObject(val2) &&\n      Symbol.prototype.valueOf.call(val1) ===\n        Symbol.prototype.valueOf.call(val2)\n    );\n  }\n\n  // Should be unreachable, here just as a backup.\n  throw new Error(`Unknown boxed type ${val1}`);\n}\n\nfunction innerDeepEqual(\n  val1: unknown,\n  val2: unknown,\n  strict: boolean,\n  memos?: Memos\n) {\n  // All identical values are equivalent, as determined by ===.\n  if (val1 === val2) {\n    if (val1 !== 0) return true;\n    return strict ? Object.is(val1, val2) : true;\n  }\n\n  // Check more closely if val1 and val2 are equal.\n  if (strict) {\n    if (typeof val1 !== 'object') {\n      return (\n        typeof val1 === 'number' && Number.isNaN(val1) && Number.isNaN(val2)\n      );\n    }\n    if (typeof val2 !== 'object' || val1 === null || val2 === null) {\n      return false;\n    }\n    if (Object.getPrototypeOf(val1) !== Object.getPrototypeOf(val2)) {\n      return false;\n    }\n  } else {\n    if (val1 === null || typeof val1 !== 'object') {\n      if (val2 === null || typeof val2 !== 'object') {\n        // TODO: eslint-disable-next-line eqeqeq\n        return val1 == val2 || (Number.isNaN(val1) && Number.isNaN(val2));\n      }\n      return false;\n    }\n    if (val2 === null || typeof val2 !== 'object') {\n      return false;\n    }\n  }\n  const val1Tag = Object.prototype.toString.call(val1);\n  const val2Tag = Object.prototype.toString.call(val2);\n\n  if (val1Tag !== val2Tag) {\n    return false;\n  }\n\n  if (Array.isArray(val1)) {\n    // Check for sparse arrays and general fast path\n    if (!Array.isArray(val2) || val1.length !== val2.length) {\n      return false;\n    }\n    const filter = strict ? ONLY_ENUMERABLE : ONLY_ENUMERABLE | SKIP_SYMBOLS;\n    const keys1 = getOwnNonIndexProperties(val1, filter);\n    const keys2 = getOwnNonIndexProperties(val2, filter);\n    if (keys1.length !== keys2.length) {\n      return false;\n    }\n    return keyCheck(val1, val2, strict, memos, kIsArray, keys1);\n  } else if (val1Tag === '[object Object]') {\n    return keyCheck(val1, val2, strict, memos, kNoIterator);\n  } else if (isDate(val1)) {\n    if (\n      !isDate(val2) ||\n      Date.prototype.getTime.call(val1) !== Date.prototype.getTime.call(val2)\n    ) {\n      return false;\n    }\n  } else if (isRegExp(val1)) {\n    if (!isRegExp(val2) || !areSimilarRegExps(val1 as RegExp, val2 as RegExp)) {\n      return false;\n    }\n  } else if (isNativeError(val1) || val1 instanceof Error) {\n    // Do not compare the stack as it might differ even though the error itself\n    // is otherwise identical.\n    if (\n      (!isNativeError(val2) && !(val2 instanceof Error)) ||\n      (val1 as Error).message !== (val2 as Error).message ||\n      (val1 as Error).name !== (val2 as Error).name\n    ) {\n      return false;\n    }\n  } else if (isArrayBufferView(val1)) {\n    if (!isArrayBufferView(val2)) return false;\n    if (\n      (val1 as any)[Symbol.toStringTag] !== (val2 as any)[Symbol.toStringTag]\n    ) {\n      return false;\n    }\n    if (\n      !strict &&\n      (isFloat16Array(val1) || isFloat32Array(val1) || isFloat64Array(val1))\n    ) {\n      if (!areSimilarFloatArrays(val1 as FloatArray, val2 as FloatArray)) {\n        return false;\n      }\n    } else if (\n      !areSimilarTypedArrays(val1 as ArrayBufferView, val2 as ArrayBufferView)\n    ) {\n      return false;\n    }\n    // Buffer.compare returns true, so val1.length === val2.length. If they both\n    // only contain numeric keys, we don't need to exam further than checking\n    // the symbols.\n    const filter = strict ? ONLY_ENUMERABLE : ONLY_ENUMERABLE | SKIP_SYMBOLS;\n    const keys1 = getOwnNonIndexProperties(val1, filter);\n    const keys2 = getOwnNonIndexProperties(val2, filter);\n    if (keys1.length !== keys2.length) {\n      return false;\n    }\n    return keyCheck(val1, val2, strict, memos, kNoIterator, keys1);\n  } else if (isSet(val1)) {\n    if (\n      !isSet(val2) ||\n      (val1 as Set<unknown>).size !== (val2 as Set<unknown>).size\n    ) {\n      return false;\n    }\n    return keyCheck(val1, val2, strict, memos, kIsSet);\n  } else if (isMap(val1)) {\n    if (\n      !isMap(val2) ||\n      (val1 as Map<unknown, unknown>).size !==\n        (val2 as Map<unknown, unknown>).size\n    ) {\n      return false;\n    }\n    return keyCheck(val1, val2, strict, memos, kIsMap);\n  } else if (isAnyArrayBuffer(val1)) {\n    if (\n      !isAnyArrayBuffer(val2) ||\n      !areEqualArrayBuffers(val1 as ArrayBuffer, val2 as ArrayBuffer)\n    ) {\n      return false;\n    }\n  } else if (isBoxedPrimitive(val1)) {\n    if (!areEqualBoxedPrimitives(val1, val2)) {\n      return false;\n    }\n  } else if (\n    Array.isArray(val2) ||\n    isArrayBufferView(val2) ||\n    isSet(val2) ||\n    isMap(val2) ||\n    isDate(val2) ||\n    isRegExp(val2) ||\n    isAnyArrayBuffer(val2) ||\n    isBoxedPrimitive(val2) ||\n    isNativeError(val2) ||\n    val2 instanceof Error\n  ) {\n    return false;\n  }\n  return keyCheck(val1, val2, strict, memos, kNoIterator);\n}\n\nfunction getEnumerables(val: Object, keys: (string | symbol)[]) {\n  return keys.filter((k) => val.propertyIsEnumerable(k));\n}\n\nfunction keyCheck(\n  val1: Object,\n  val2: Object,\n  strict: boolean,\n  memos?: Memos,\n  iterationType?: number,\n  aKeys?: (string | symbol)[]\n) {\n  // For all remaining Object pairs, including Array, objects and Maps,\n  // equivalence is determined by having:\n  // a) The same number of owned enumerable properties\n  // b) The same set of keys/indexes (although not necessarily the same order)\n  // c) Equivalent values for every corresponding key/index\n  // d) For Sets and Maps, equal contents\n  // Note: this accounts for both named and indexed properties on Arrays.\n  if (arguments.length === 5) {\n    aKeys = Object.keys(val1 as Object);\n    const bKeys = Object.keys(val2 as Object);\n\n    // The pair must have the same number of owned properties.\n    if (aKeys.length !== bKeys.length) {\n      return false;\n    }\n  }\n\n  // Cheap key test\n  let i = 0;\n  for (; i < aKeys!.length; i++) {\n    if (!val2.propertyIsEnumerable(aKeys![i]!)) {\n      return false;\n    }\n  }\n\n  if (strict && arguments.length === 5) {\n    const symbolKeysA = Object.getOwnPropertySymbols(val1);\n    if (symbolKeysA.length !== 0) {\n      let count = 0;\n      for (i = 0; i < symbolKeysA.length; i++) {\n        const key = symbolKeysA[i];\n        if (val1.propertyIsEnumerable(key!)) {\n          if (!val2.propertyIsEnumerable(key!)) {\n            return false;\n          }\n          aKeys!.push(key!);\n          count++;\n        } else if (val2.propertyIsEnumerable(key!)) {\n          return false;\n        }\n      }\n      const symbolKeysB = Object.getOwnPropertySymbols(val2);\n      if (\n        symbolKeysA.length !== symbolKeysB.length &&\n        getEnumerables(val2, symbolKeysB).length !== count\n      ) {\n        return false;\n      }\n    } else {\n      const symbolKeysB = Object.getOwnPropertySymbols(val2);\n      if (\n        symbolKeysB.length !== 0 &&\n        getEnumerables(val2, symbolKeysB).length !== 0\n      ) {\n        return false;\n      }\n    }\n  }\n\n  if (\n    aKeys!.length === 0 &&\n    (iterationType === kNoIterator ||\n      (iterationType === kIsArray && (val1 as any[]).length === 0) ||\n      (val1 as any).size === 0)\n  ) {\n    return true;\n  }\n\n  // Use memos to handle cycles.\n  if (memos === undefined) {\n    memos = {\n      val1: new Map(),\n      val2: new Map(),\n      position: 0,\n    };\n  } else {\n    // We prevent up to two map.has(x) calls by directly retrieving the value\n    // and checking for undefined. The map can only contain numbers, so it is\n    // safe to check for undefined only.\n    const val2MemoA = memos.val1.get(val1);\n    if (val2MemoA !== undefined) {\n      const val2MemoB = memos.val2.get(val2);\n      if (val2MemoB !== undefined) {\n        return val2MemoA === val2MemoB;\n      }\n    }\n    memos.position++;\n  }\n\n  memos.val1.set(val1, memos.position);\n  memos.val2.set(val2, memos.position);\n\n  const areEq = objEquiv(val1, val2, strict, aKeys!, memos, iterationType);\n\n  memos.val1.delete(val1);\n  memos.val2.delete(val2);\n\n  return areEq;\n}\n\nfunction setHasEqualElement(\n  set: Set<unknown>,\n  val1: unknown,\n  strict: boolean,\n  memo: Memos\n) {\n  // Go looking.\n  for (const val2 of set) {\n    if (innerDeepEqual(val1, val2, strict, memo)) {\n      // Remove the matching element to make sure we do not check that again.\n      set.delete(val2);\n      return true;\n    }\n  }\n\n  return false;\n}\n\nfunction findLooseMatchingPrimitives(prim: unknown) {\n  switch (typeof prim) {\n    case 'undefined':\n      return null;\n    case 'object': // Only pass in null as object!\n      return undefined;\n    case 'symbol':\n      return false;\n    case 'string':\n      return !Number.isNaN(+prim);\n    // Loose equal entries exist only if the string is possible to convert to\n    // a regular number and not NaN.\n    case 'number':\n      return !Number.isNaN(prim);\n  }\n  return true;\n}\n\nfunction setMightHaveLoosePrim(\n  a: Set<unknown>,\n  b: Set<unknown>,\n  prim: unknown\n) {\n  const altValue = findLooseMatchingPrimitives(prim);\n  if (altValue != null) return altValue;\n\n  return b.has(altValue) && !a.has(altValue);\n}\n\nfunction mapMightHaveLoosePrim(\n  a: Map<unknown, unknown>,\n  b: Map<unknown, unknown>,\n  prim: unknown,\n  item: unknown,\n  memo: Memos\n) {\n  const altValue = findLooseMatchingPrimitives(prim);\n  if (altValue != null) {\n    return altValue;\n  }\n  const curB = b.get(altValue);\n  if (\n    (curB === undefined && !b.has(altValue)) ||\n    !innerDeepEqual(item, curB, false, memo)\n  ) {\n    return false;\n  }\n  return !a.has(altValue) && innerDeepEqual(item, curB, false, memo);\n}\n\nfunction setEquiv(\n  a: Set<unknown>,\n  b: Set<unknown>,\n  strict: boolean,\n  memo: Memos\n) {\n  // This is a lazily initiated Set of entries which have to be compared\n  // pairwise.\n  let set = null;\n  for (const val of a) {\n    // Note: Checking for the objects first improves the performance for object\n    // heavy sets but it is a minor slow down for primitives. As they are fast\n    // to check this improves the worst case scenario instead.\n    if (typeof val === 'object' && val !== null) {\n      if (set === null) {\n        set = new Set();\n      }\n      // If the specified value doesn't exist in the second set it's a non-null\n      // object (or non strict only: a not matching primitive) we'll need to go\n      // hunting for something that's deep-(strict-)equal to it. To make this\n      // O(n log n) complexity we have to copy these values in a new set first.\n      set.add(val);\n    } else if (!b.has(val)) {\n      if (strict) return false;\n\n      // Fast path to detect missing string, symbol, undefined and null values.\n      if (!setMightHaveLoosePrim(a, b, val)) {\n        return false;\n      }\n\n      if (set === null) {\n        set = new Set();\n      }\n      set.add(val);\n    }\n  }\n\n  if (set !== null) {\n    for (const val of b) {\n      // We have to check if a primitive value is already\n      // matching and only if it's not, go hunting for it.\n      if (typeof val === 'object' && val !== null) {\n        if (!setHasEqualElement(set, val, strict, memo)) return false;\n      } else if (\n        !strict &&\n        !a.has(val) &&\n        !setHasEqualElement(set, val, strict, memo)\n      ) {\n        return false;\n      }\n    }\n    return set.size === 0;\n  }\n\n  return true;\n}\n\nfunction mapHasEqualEntry(\n  set: Set<unknown>,\n  map: Map<unknown, unknown>,\n  key1: unknown,\n  item1: unknown,\n  strict: boolean,\n  memo: Memos\n) {\n  // To be able to handle cases like:\n  //   Map([[{}, 'a'], [{}, 'b']]) vs Map([[{}, 'b'], [{}, 'a']])\n  // ... we need to consider *all* matching keys, not just the first we find.\n  for (const key2 of set) {\n    if (\n      innerDeepEqual(key1, key2, strict, memo) &&\n      innerDeepEqual(item1, map.get(key2), strict, memo)\n    ) {\n      set.delete(key2);\n      return true;\n    }\n  }\n\n  return false;\n}\n\nfunction mapEquiv(\n  a: Map<unknown, unknown>,\n  b: Map<unknown, unknown>,\n  strict: boolean,\n  memo: Memos\n) {\n  let set = null;\n\n  for (const { 0: key, 1: item1 } of a) {\n    if (typeof key === 'object' && key !== null) {\n      if (set === null) {\n        set = new Set();\n      }\n      set.add(key);\n    } else {\n      // By directly retrieving the value we prevent another b.has(key) check in\n      // almost all possible cases.\n      const item2 = b.get(key);\n      if (\n        (item2 === undefined && !b.has(key)) ||\n        !innerDeepEqual(item1, item2, strict, memo)\n      ) {\n        if (strict) return false;\n        // Fast path to detect missing string, symbol, undefined and null\n        // keys.\n        if (!mapMightHaveLoosePrim(a, b, key, item1, memo)) return false;\n        if (set === null) {\n          set = new Set();\n        }\n        set.add(key);\n      }\n    }\n  }\n\n  if (set !== null) {\n    for (const { 0: key, 1: item } of b) {\n      if (typeof key === 'object' && key !== null) {\n        if (!mapHasEqualEntry(set, a, key, item, strict, memo)) return false;\n      } else if (\n        !strict &&\n        (!a.has(key) || !innerDeepEqual(a.get(key), item, false, memo)) &&\n        !mapHasEqualEntry(set, a, key, item, false, memo)\n      ) {\n        return false;\n      }\n    }\n    return set.size === 0;\n  }\n\n  return true;\n}\n\nfunction objEquiv(\n  a: Object,\n  b: Object,\n  strict: boolean,\n  keys: (string | symbol)[],\n  memos: Memos,\n  iterationType?: number\n) {\n  // Sets and maps don't have their entries accessible via normal object\n  // properties.\n  let i = 0;\n\n  if (iterationType === kIsSet) {\n    if (!setEquiv(a as Set<unknown>, b as Set<unknown>, strict, memos)) {\n      return false;\n    }\n  } else if (iterationType === kIsMap) {\n    if (\n      !mapEquiv(\n        a as Map<unknown, unknown>,\n        b as Map<unknown, unknown>,\n        strict,\n        memos\n      )\n    ) {\n      return false;\n    }\n  } else if (iterationType === kIsArray) {\n    for (; i < (a as [any]).length; i++) {\n      if (a.hasOwnProperty(i)) {\n        if (\n          !b.hasOwnProperty(i) ||\n          !innerDeepEqual((a as [any])[i], (b as [any])[i], strict, memos)\n        ) {\n          return false;\n        }\n      } else if (b.hasOwnProperty(i)) {\n        return false;\n      } else {\n        // Array is sparse.\n        const keysA = Object.keys(a);\n        for (; i < keysA.length; i++) {\n          const key = keysA[i];\n          if (\n            !b.hasOwnProperty(key!) ||\n            !innerDeepEqual((a as any)[key!], (b as any)[key!], strict, memos)\n          ) {\n            return false;\n          }\n        }\n        if (keysA.length !== Object.keys(b).length) {\n          return false;\n        }\n        return true;\n      }\n    }\n  }\n\n  // The pair must have equivalent values for every corresponding key.\n  // Possibly expensive deep test:\n  for (i = 0; i < keys.length; i++) {\n    const key = keys[i];\n    if (!innerDeepEqual((a as any)[key!], (b as any)[key!], strict, memos)) {\n      return false;\n    }\n  }\n  return true;\n}\n\nexport function isDeepStrictEqual(val1: unknown, val2: unknown) {\n  return innerDeepEqual(val1, val2, kStrict);\n}\n"
  },
  {
    "path": "src/node/internal/internal_diffs.ts",
    "content": "// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.\n// This module is browser compatible.\n\n/* TODO: the following is adopted code, enabling linting one day */\n/* eslint-disable */\n\ninterface FarthestPoint {\n  y: number;\n  id: number;\n}\n\nexport const DiffType = {\n  removed: 'removed',\n  common: 'common',\n  added: 'added',\n};\n\nexport interface DiffResult<T> {\n  type: (typeof DiffType)[keyof typeof DiffType];\n  value: T;\n  details?: Array<DiffResult<T>>;\n}\n\nconst REMOVED = 1;\nconst COMMON = 2;\nconst ADDED = 3;\n\nfunction createCommon<T>(A: T[], B: T[], reverse?: boolean): T[] {\n  const common = [];\n  if (A.length === 0 || B.length === 0) return [];\n  for (let i = 0; i < Math.min(A.length, B.length); i += 1) {\n    if (\n      A[reverse ? A.length - i - 1 : i] === B[reverse ? B.length - i - 1 : i]\n    ) {\n      common.push(A[reverse ? A.length - i - 1 : i]);\n    } else {\n      // @ts-ignore\n      return common;\n    }\n  }\n  // @ts-ignore\n  return common;\n}\n\n/**\n * Renders the differences between the actual and expected values\n * @param A Actual value\n * @param B Expected value\n */\nexport function diff<T>(A: T[], B: T[]): Array<DiffResult<T>> {\n  const prefixCommon = createCommon(A, B);\n  const suffixCommon = createCommon(\n    A.slice(prefixCommon.length),\n    B.slice(prefixCommon.length),\n    true\n  ).reverse();\n  A = suffixCommon.length\n    ? A.slice(prefixCommon.length, -suffixCommon.length)\n    : A.slice(prefixCommon.length);\n  B = suffixCommon.length\n    ? B.slice(prefixCommon.length, -suffixCommon.length)\n    : B.slice(prefixCommon.length);\n  const swapped = B.length > A.length;\n  [A, B] = swapped ? [B, A] : [A, B];\n  const M = A.length;\n  const N = B.length;\n  if (!M && !N && !suffixCommon.length && !prefixCommon.length) return [];\n  if (!N) {\n    return [\n      ...prefixCommon.map(\n        (c): DiffResult<typeof c> => ({ type: DiffType.common, value: c })\n      ),\n      ...A.map(\n        (a): DiffResult<typeof a> => ({\n          type: swapped ? DiffType.added : DiffType.removed,\n          value: a,\n        })\n      ),\n      ...suffixCommon.map(\n        (c): DiffResult<typeof c> => ({ type: DiffType.common, value: c })\n      ),\n    ];\n  }\n  const offset = N;\n  const delta = M - N;\n  const size = M + N + 1;\n  const fp: FarthestPoint[] = Array.from({ length: size }, () => ({\n    y: -1,\n    id: -1,\n  }));\n  /**\n   * INFO:\n   * This buffer is used to save memory and improve performance.\n   * The first half is used to save route and last half is used to save diff\n   * type.\n   * This is because, when I kept new uint8array area to save type,performance\n   * worsened.\n   */\n  const routes = new Uint32Array((M * N + size + 1) * 2);\n  const diffTypesPtrOffset = routes.length / 2;\n  let ptr = 0;\n  let p = -1;\n\n  function backTrace<T>(\n    A: T[],\n    B: T[],\n    current: FarthestPoint,\n    swapped: boolean\n  ): Array<{\n    type: (typeof DiffType)[keyof typeof DiffType];\n    value: T;\n  }> {\n    const M = A.length;\n    const N = B.length;\n    const result = [];\n    let a = M - 1;\n    let b = N - 1;\n    let j = routes[current.id];\n    let type = routes[current.id + diffTypesPtrOffset];\n    while (true) {\n      if (!j && !type) break;\n      const prev = j;\n      if (type === REMOVED) {\n        result.unshift({\n          type: swapped ? DiffType.removed : DiffType.added,\n          value: B[b],\n        });\n        b -= 1;\n      } else if (type === ADDED) {\n        result.unshift({\n          type: swapped ? DiffType.added : DiffType.removed,\n          value: A[a],\n        });\n        a -= 1;\n      } else {\n        result.unshift({ type: DiffType.common, value: A[a] });\n        a -= 1;\n        b -= 1;\n      }\n      // @ts-ignore\n      j = routes[prev];\n      // @ts-ignore\n      type = routes[prev + diffTypesPtrOffset];\n    }\n    // @ts-ignore\n    return result;\n  }\n\n  function createFP(\n    slide: FarthestPoint,\n    down: FarthestPoint,\n    k: number,\n    M: number\n  ): FarthestPoint {\n    if (slide && slide.y === -1 && down && down.y === -1) {\n      return { y: 0, id: 0 };\n    }\n    if (\n      (down && down.y === -1) ||\n      k === M ||\n      (slide && slide.y) > (down && down.y) + 1\n    ) {\n      const prev = slide.id;\n      ptr++;\n      routes[ptr] = prev;\n      routes[ptr + diffTypesPtrOffset] = ADDED;\n      return { y: slide.y, id: ptr };\n    } else {\n      const prev = down.id;\n      ptr++;\n      routes[ptr] = prev;\n      routes[ptr + diffTypesPtrOffset] = REMOVED;\n      return { y: down.y + 1, id: ptr };\n    }\n  }\n\n  function snake<T>(\n    k: number,\n    slide: FarthestPoint,\n    down: FarthestPoint,\n    _offset: number,\n    A: T[],\n    B: T[]\n  ): FarthestPoint {\n    const M = A.length;\n    const N = B.length;\n    if (k < -N || M < k) return { y: -1, id: -1 };\n    const fp = createFP(slide, down, k, M);\n    while (fp.y + k < M && fp.y < N && A[fp.y + k] === B[fp.y]) {\n      const prev = fp.id;\n      ptr++;\n      fp.id = ptr;\n      fp.y += 1;\n      routes[ptr] = prev;\n      routes[ptr + diffTypesPtrOffset] = COMMON;\n    }\n    return fp;\n  }\n\n  // @ts-ignore\n  while (fp[delta + offset].y < N) {\n    p = p + 1;\n    for (let k = -p; k < delta; ++k) {\n      fp[k + offset] = snake(\n        k,\n        // @ts-ignore\n        fp[k - 1 + offset],\n        fp[k + 1 + offset],\n        offset,\n        A,\n        B\n      );\n    }\n    for (let k = delta + p; k > delta; --k) {\n      fp[k + offset] = snake(\n        k,\n        // @ts-ignore\n        fp[k - 1 + offset],\n        fp[k + 1 + offset],\n        offset,\n        A,\n        B\n      );\n    }\n    fp[delta + offset] = snake(\n      delta,\n      // @ts-ignore\n      fp[delta - 1 + offset],\n      fp[delta + 1 + offset],\n      offset,\n      A,\n      B\n    );\n  }\n  return [\n    ...prefixCommon.map(\n      (c): DiffResult<typeof c> => ({ type: DiffType.common, value: c })\n    ),\n    // @ts-ignore\n    ...backTrace(A, B, fp[delta + offset], swapped),\n    ...suffixCommon.map(\n      (c): DiffResult<typeof c> => ({ type: DiffType.common, value: c })\n    ),\n  ];\n}\n\n/**\n * Renders the differences between the actual and expected strings\n * Partially inspired from https://github.com/kpdecker/jsdiff\n * @param A Actual string\n * @param B Expected string\n */\nexport function diffstr(A: string, B: string) {\n  function unescape(string: string): string {\n    // unescape invisible characters.\n    // ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#escape_sequences\n    return string\n      .replaceAll('\\b', '\\\\b')\n      .replaceAll('\\f', '\\\\f')\n      .replaceAll('\\t', '\\\\t')\n      .replaceAll('\\v', '\\\\v')\n      .replaceAll(\n        // does not remove line breaks\n        /\\r\\n|\\r|\\n/g,\n        (str) => (str === '\\r' ? '\\\\r' : str === '\\n' ? '\\\\n\\n' : '\\\\r\\\\n\\r\\n')\n      );\n  }\n\n  function tokenize(string: string, { wordDiff = false } = {}): string[] {\n    if (wordDiff) {\n      // Split string on whitespace symbols\n      const tokens = string.split(/([^\\S\\r\\n]+|[()[\\]{}'\"\\r\\n]|\\b)/);\n      // Extended Latin character set\n      const words =\n        /^[a-zA-Z\\u{C0}-\\u{FF}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}]+$/u;\n\n      // Join boundary splits that we do not consider to be boundaries and merge empty strings surrounded by word chars\n      for (let i = 0; i < tokens.length - 1; i++) {\n        if (\n          !tokens[i + 1] &&\n          tokens[i + 2] &&\n          // @ts-ignore\n          words.test(tokens[i]) &&\n          // @ts-ignore\n          words.test(tokens[i + 2])\n        ) {\n          tokens[i] += tokens[i + 2] as string;\n          tokens.splice(i + 1, 2);\n          i--;\n        }\n      }\n      return tokens.filter((token) => token);\n    } else {\n      // Split string on new lines symbols\n      const tokens = [],\n        lines = string.split(/(\\n|\\r\\n)/);\n\n      // Ignore final empty token when text ends with a newline\n      if (!lines[lines.length - 1]) {\n        lines.pop();\n      }\n\n      // Merge the content and line separators into single tokens\n      for (let i = 0; i < lines.length; i++) {\n        if (i % 2) {\n          // @ts-ignore\n          tokens[tokens.length - 1] += lines[i];\n        } else {\n          tokens.push(lines[i]);\n        }\n      }\n      // @ts-ignore\n      return tokens;\n    }\n  }\n\n  // Create details by filtering relevant word-diff for current line\n  // and merge \"space-diff\" if surrounded by word-diff for cleaner displays\n  function createDetails(\n    line: DiffResult<string>,\n    tokens: Array<DiffResult<string>>\n  ) {\n    return tokens\n      .filter(({ type }) => type === line.type || type === DiffType.common)\n      .map((result, i, t) => {\n        if (\n          result.type === DiffType.common &&\n          t[i - 1] &&\n          t[i - 1]?.type === t[i + 1]?.type &&\n          /\\s+/.test(result.value)\n        ) {\n          return {\n            ...result,\n            // @ts-ignore\n            type: t[i - 1].type,\n          };\n        }\n        return result;\n      });\n  }\n\n  // Compute multi-line diff\n  const diffResult = diff(\n    tokenize(`${unescape(A)}\\n`),\n    tokenize(`${unescape(B)}\\n`)\n  );\n\n  const added = [],\n    removed = [];\n  for (const result of diffResult) {\n    if (result.type === DiffType.added) {\n      added.push(result);\n    }\n    if (result.type === DiffType.removed) {\n      removed.push(result);\n    }\n  }\n\n  // Compute word-diff\n  const aLines = added.length < removed.length ? added : removed;\n  const bLines = aLines === removed ? added : removed;\n  for (const a of aLines) {\n    let tokens = [] as Array<DiffResult<string>>,\n      b: undefined | DiffResult<string>;\n    // Search another diff line with at least one common token\n    while (bLines.length) {\n      b = bLines.shift();\n      tokens = diff(\n        tokenize(a.value, { wordDiff: true }),\n        tokenize(b?.value ?? '', { wordDiff: true })\n      );\n      if (\n        tokens.some(\n          ({ type, value }) => type === DiffType.common && value.trim().length\n        )\n      ) {\n        break;\n      }\n    }\n    // Register word-diff details\n    a.details = createDetails(a, tokens);\n    if (b) {\n      b.details = createDetails(b, tokens);\n    }\n  }\n\n  return diffResult;\n}\n\n/**\n * Prefixes `+` or `-` in diff output\n * @param diffType Difference type, either added or removed\n */\nfunction createSign(\n  diffType: (typeof DiffType)[keyof typeof DiffType]\n): string {\n  switch (diffType) {\n    case DiffType.added:\n      return '+   ';\n    case DiffType.removed:\n      return '-   ';\n    default:\n      return '    ';\n  }\n}\n\nexport function buildMessage(\n  diffResult: ReadonlyArray<DiffResult<string>>,\n  { stringDiff = false } = {}\n): string[] {\n  const messages: string[] = [],\n    diffMessages: string[] = [];\n  messages.push('');\n  messages.push('');\n  messages.push('[Diff] Actual / Expected');\n  messages.push('');\n  messages.push('');\n  diffResult.forEach((result: DiffResult<string>) => {\n    const line =\n      result.details?.map((detail) => detail.value).join('') ?? result.value;\n    diffMessages.push(`${createSign(result.type)}${line}`);\n  });\n  messages.push(...(stringDiff ? [diffMessages.join('')] : diffMessages));\n  messages.push('');\n\n  return messages;\n}\n"
  },
  {
    "path": "src/node/internal/internal_dns.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\n/* eslint-disable @typescript-eslint/no-deprecated */\nimport {\n  sendDnsRequest,\n  validateAnswer,\n  normalizeMx,\n  normalizeCname,\n  normalizeCaa,\n  normalizePtr,\n  normalizeNaptr,\n  normalizeNs,\n  normalizeSoa,\n  normalizeTxt,\n  normalizeSrv,\n  type MX,\n  type CAA,\n  type NAPTR,\n  type SOA,\n  type SRV,\n  type TTLResponse,\n} from 'node-internal:internal_dns_client';\nimport {\n  DnsError,\n  ERR_INVALID_ARG_TYPE,\n  ERR_OPTION_NOT_IMPLEMENTED,\n  ERR_INVALID_ARG_VALUE,\n} from 'node-internal:internal_errors';\nimport {\n  validateString,\n  validateFunction,\n  validateOneOf,\n  validateNumber,\n  validateBoolean,\n} from 'node-internal:validators';\nimport * as errorCodes from 'node-internal:internal_dns_constants';\nimport { isIP } from 'node-internal:internal_net';\nimport type dns from 'node:dns';\n\ntype DnsOrder = 'verbatim' | 'ipv4first' | 'ipv6first';\n\nexport const validFamilies = [0, 4, 6];\nexport const validDnsOrders: DnsOrder[] = [\n  'verbatim',\n  'ipv4first',\n  'ipv6first',\n];\n\nlet defaultDnsOrder: DnsOrder = 'verbatim';\n\nexport function getServers(): ReturnType<(typeof dns)['getServers']> {\n  return ['1.1.1.1', '2606:4700:4700::1111', '1.0.0.1', '2606:4700:4700::1001'];\n}\n\nexport type LookupCallback = (\n  err: Error | null,\n  address?: string | { address: string; family: number }[],\n  family?: number\n) => void;\n\nexport function lookup(\n  hostname: string,\n  options?: dns.LookupOptions | LookupCallback,\n  callback?: LookupCallback\n): void {\n  let family: 0 | 4 | 6 = 0;\n  let all = false;\n  let dnsOrder: DnsOrder = getDefaultResultOrder();\n\n  // Parse arguments\n  if (hostname) {\n    validateString(hostname, 'hostname');\n  }\n\n  if (typeof options === 'function') {\n    callback = options;\n    family = 0;\n  } else if (typeof options === 'number') {\n    validateFunction(callback, 'callback');\n\n    validateOneOf(options, 'family', validFamilies);\n    family = options;\n  } else if (options !== undefined && typeof options !== 'object') {\n    validateFunction(arguments.length === 2 ? options : callback, 'callback');\n    throw new ERR_INVALID_ARG_TYPE('options', ['integer', 'object'], options);\n  } else {\n    validateFunction(callback, 'callback');\n\n    if (options?.hints != null) {\n      validateNumber(options.hints, 'options.hints');\n      throw new ERR_OPTION_NOT_IMPLEMENTED('options.hints');\n    }\n    if (options?.family != null) {\n      switch (options.family) {\n        case 'IPv4':\n          family = 4;\n          break;\n        case 'IPv6':\n          family = 6;\n          break;\n        default:\n          validateOneOf(options.family, 'options.family', validFamilies);\n          family = options.family as 0 | 4 | 6;\n          break;\n      }\n    }\n    if (options?.all != null) {\n      validateBoolean(options.all, 'options.all');\n      all = options.all;\n    }\n    if (options?.verbatim != null) {\n      validateBoolean(options.verbatim, 'options.verbatim');\n      dnsOrder = options.verbatim ? 'verbatim' : 'ipv4first';\n    }\n    if (options?.order != null) {\n      validateOneOf(options.order, 'options.order', validDnsOrders);\n      dnsOrder = options.order;\n    }\n  }\n\n  if (!hostname) {\n    if (all) {\n      process.nextTick(callback, null, []);\n    } else {\n      process.nextTick(callback, null, null, family === 6 ? 6 : 4);\n    }\n    return;\n  }\n\n  const matchedFamily = isIP(hostname);\n  if (matchedFamily) {\n    if (all) {\n      process.nextTick(callback, null, [\n        { address: hostname, family: matchedFamily },\n      ]);\n    } else {\n      process.nextTick(callback, null, hostname, matchedFamily);\n    }\n    return;\n  }\n\n  // If all is true and family is 0, we need to query both A and AAAA records\n  if (all && family === 0) {\n    Promise.all([\n      sendDnsRequest(hostname, 'A').catch(() => ({ Answer: [] })),\n      sendDnsRequest(hostname, 'AAAA').catch(() => ({ Answer: [] })),\n    ])\n      .then(([ipv4Response, ipv6Response]): void => {\n        const ipv4Addresses: { address: string; family: 4 }[] =\n          ipv4Response.Answer?.map((answer) => ({\n            address: answer.data,\n            family: 4,\n          })) ?? [];\n        const ipv6Addresses: { address: string; family: 6 }[] =\n          ipv6Response.Answer?.map((answer) => ({\n            address: answer.data,\n            family: 6,\n          })) ?? [];\n\n        // No addresses found\n        if (ipv4Addresses.length === 0 && ipv6Addresses.length === 0) {\n          callback(new DnsError(hostname, errorCodes.NOTFOUND, 'queryA'));\n          return;\n        }\n\n        // Order addresses based on dnsOrder\n        if (dnsOrder === 'ipv6first') {\n          callback(null, [...ipv6Addresses, ...ipv4Addresses]);\n        } else {\n          // verbatim - preserve order as received (still separating by type)\n          // ipv4first = same as verbatim\n          callback(null, [...ipv4Addresses, ...ipv6Addresses]);\n        }\n      })\n      .catch((error: unknown): void => {\n        process.nextTick(callback, error);\n      });\n  } else if (family === 0) {\n    // family=0, all=false: query both A and AAAA, return first result based on dnsOrder\n    Promise.all([\n      sendDnsRequest(hostname, 'A').catch(() => ({ Answer: [] })),\n      sendDnsRequest(hostname, 'AAAA').catch(() => ({ Answer: [] })),\n    ])\n      .then(([ipv4Response, ipv6Response]): void => {\n        const ipv4 = ipv4Response.Answer?.at(0)?.data;\n        const ipv6 = ipv6Response.Answer?.at(0)?.data;\n\n        if (ipv4 == null && ipv6 == null) {\n          callback(new DnsError(hostname, errorCodes.NOTFOUND, 'queryA'));\n          return;\n        }\n\n        // Return the preferred address based on dnsOrder\n        if (dnsOrder === 'ipv6first') {\n          if (ipv6 != null) {\n            callback(null, ipv6, 6);\n          } else {\n            callback(null, ipv4 as string, 4);\n          }\n        } else {\n          if (ipv4 != null) {\n            callback(null, ipv4, 4);\n          } else {\n            callback(null, ipv6 as string, 6);\n          }\n        }\n      })\n      .catch((error: unknown): void => {\n        process.nextTick(callback, error);\n      });\n  } else {\n    const requestType = family === 4 ? 'A' : 'AAAA';\n\n    // Single request when family is specified (with or without all=true)\n    sendDnsRequest(hostname, requestType)\n      .then((json): void => {\n        validateAnswer(json.Answer, hostname, `query${requestType}`);\n\n        if (all) {\n          // Return all addresses with the specified family\n          callback(\n            null,\n            json.Answer.map((answer) => ({\n              address: answer.data,\n              family,\n            }))\n          );\n        } else {\n          // Return just the first address\n          callback(null, json.Answer.at(0)?.data as string, family);\n        }\n      })\n      .catch((error: unknown): void => {\n        process.nextTick(callback, error);\n      });\n  }\n}\n\n// eslint-disable-next-line @typescript-eslint/require-await\nexport async function lookupService(): Promise<void> {\n  // TODO(soon): Implement this.\n  throw new Error('Not implemented');\n}\n\nexport function resolve(\n  name: string,\n  rrtype: string\n): Promise<unknown> | Promise<void> {\n  validateString(name, 'name');\n\n  switch (rrtype) {\n    case 'A':\n      return resolve4(name);\n    case 'AAAA':\n      return resolve6(name);\n    case 'ANY':\n      return resolveAny();\n    case 'CAA':\n      return resolveCaa(name);\n    case 'CNAME':\n      return resolveCname(name);\n    case 'MX':\n      return resolveMx(name);\n    case 'NAPTR':\n      return resolveNaptr(name);\n    case 'NS':\n      return resolveNs(name);\n    case 'PTR':\n      return resolvePtr(name);\n    case 'SOA':\n      return resolveSoa(name);\n    case 'SRV':\n      return resolveSrv(name);\n    case 'TXT':\n      return resolveTxt(name);\n    default: {\n      throw new ERR_INVALID_ARG_VALUE('rrtype', rrtype);\n    }\n  }\n}\n\nexport function resolve4(\n  name: string,\n  options?: { ttl?: boolean }\n): Promise<(string | TTLResponse)[]> {\n  validateString(name, 'name');\n\n  // The following change is done to comply with Node.js behavior\n  const ttl = !!options?.ttl;\n\n  // Validation errors needs to be sync.\n  // Return a promise rather than using async qualifier.\n  return sendDnsRequest(name, 'A').then((json) => {\n    validateAnswer(json.Answer, name, 'queryA');\n\n    return json.Answer.map((a) =>\n      ttl ? { ttl: a.TTL, address: a.data } : a.data\n    );\n  });\n}\n\nexport function resolve6(\n  name: string,\n  options?: { ttl?: boolean }\n): Promise<(string | TTLResponse)[]> {\n  validateString(name, 'name');\n\n  // The following change is done to comply with Node.js behavior\n  const ttl = !!options?.ttl;\n\n  // Validation errors needs to be sync.\n  // Return a promise rather than using async qualifier.\n  return sendDnsRequest(name, 'AAAA').then((json) => {\n    validateAnswer(json.Answer, name, 'queryAaaa');\n\n    return json.Answer.map((a) =>\n      ttl ? { ttl: a.TTL, address: a.data } : a.data\n    );\n  });\n}\n\n// eslint-disable-next-line @typescript-eslint/require-await\nexport async function resolveAny(): Promise<void> {\n  // TODO(soon): Implement this\n  throw new Error('Not implemented');\n}\n\nexport function resolveCname(name: string): Promise<string[]> {\n  validateString(name, 'name');\n\n  // Validation errors needs to be sync.\n  // Return a promise rather than using async qualifier.\n  return sendDnsRequest(name, 'CNAME').then((json) => {\n    validateAnswer(json.Answer, name, 'queryCname');\n\n    return json.Answer.map(normalizeCname);\n  });\n}\n\nexport function resolveCaa(name: string): Promise<CAA[]> {\n  validateString(name, 'name');\n\n  // Validation errors needs to be sync.\n  // Return a promise rather than using async qualifier.\n  return sendDnsRequest(name, 'CAA').then((json) => {\n    validateAnswer(json.Answer, name, 'queryCaa');\n\n    return json.Answer.map(normalizeCaa);\n  });\n}\n\nexport function resolveMx(name: string): Promise<MX[]> {\n  validateString(name, 'name');\n\n  // Validation errors needs to be sync.\n  // Return a promise rather than using async qualifier.\n  return sendDnsRequest(name, 'MX').then((json) => {\n    validateAnswer(json.Answer, name, 'queryMx');\n\n    return json.Answer.map((answer) => normalizeMx(name, answer));\n  });\n}\n\nexport function resolveNaptr(name: string): Promise<NAPTR[]> {\n  validateString(name, 'name');\n\n  // Validation errors needs to be sync.\n  // Return a promise rather than using async qualifier.\n  return sendDnsRequest(name, 'NAPTR').then((json) => {\n    validateAnswer(json.Answer, name, 'queryNaptr');\n\n    return json.Answer.map(normalizeNaptr);\n  });\n}\n\nexport function resolveNs(name: string): Promise<string[]> {\n  validateString(name, 'name');\n\n  // Validation errors needs to be sync.\n  // Return a promise rather than using async qualifier.\n  return sendDnsRequest(name, 'NS').then((json) => {\n    validateAnswer(json.Answer, name, 'queryNs');\n\n    return json.Answer.map(normalizeNs);\n  });\n}\n\nexport function resolvePtr(name: string): Promise<string[]> {\n  validateString(name, 'name');\n\n  // Validation errors needs to be sync.\n  // Return a promise rather than using async qualifier.\n  return sendDnsRequest(name, 'PTR').then((json) => {\n    validateAnswer(json.Answer, name, 'queryPtr');\n\n    return json.Answer.map(normalizePtr);\n  });\n}\n\nexport function resolveSoa(name: string): Promise<SOA> {\n  validateString(name, 'name');\n\n  // Validation errors needs to be sync.\n  // Return a promise rather than using async qualifier.\n  return sendDnsRequest(name, 'SOA').then((json) => {\n    validateAnswer(json.Answer, name, 'querySoa');\n\n    // This is highly unlikely, but let's assert length just to be safe.\n    const firstElement = json.Answer.at(0);\n    if (!firstElement) {\n      throw new DnsError(name, errorCodes.NOTFOUND, 'querySoa');\n    }\n\n    return normalizeSoa(firstElement);\n  });\n}\n\nexport function resolveSrv(name: string): Promise<SRV[]> {\n  validateString(name, 'name');\n\n  // Validation errors needs to be sync.\n  // Return a promise rather than using async qualifier.\n  return sendDnsRequest(name, 'SRV').then((json) => {\n    validateAnswer(json.Answer, name, 'querySrv');\n\n    return json.Answer.map(normalizeSrv);\n  });\n}\n\nexport function resolveTxt(name: string): Promise<string[][]> {\n  validateString(name, 'name');\n\n  // Validation errors needs to be sync.\n  // Return a promise rather than using async qualifier.\n  return sendDnsRequest(name, 'TXT').then((json) => {\n    validateAnswer(json.Answer, name, 'queryTxt');\n\n    return json.Answer.map(normalizeTxt);\n  });\n}\n\nexport function reverse(name: string): Promise<string[]> {\n  validateString(name, 'name');\n\n  // Validation errors needs to be sync.\n  // Return a promise rather than using async qualifier.\n  return sendDnsRequest(name, 'PTR').then((json) => {\n    validateAnswer(json.Answer, name, 'queryPtr');\n\n    return json.Answer.map(({ data }) => {\n      if (data.endsWith('.')) {\n        return data.slice(0, -1);\n      }\n      return data;\n    });\n  });\n}\n\nexport function setDefaultResultOrder(value: unknown): void {\n  validateOneOf(value, 'dnsOrder', validDnsOrders);\n  defaultDnsOrder = value as DnsOrder;\n}\n\nexport function getDefaultResultOrder(): DnsOrder {\n  return defaultDnsOrder;\n}\n\nexport function setServers(): void {\n  // This function does not apply to workerd model.\n  // Our implementation always use Cloudflare DNS and does not\n  // allow users to change the underlying DNS server.\n  throw new Error('Not implemented');\n}\n"
  },
  {
    "path": "src/node/internal/internal_dns_client.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { default as dnsUtil } from 'node-internal:dns';\nimport * as errorCodes from 'node-internal:internal_dns_constants';\nimport { DnsError } from 'node-internal:internal_errors';\nimport { validateString } from 'node-internal:validators';\n\nexport type TTLResponse = {\n  ttl: number;\n  address: string;\n};\nexport interface Answer {\n  // The record owner.\n  name: string;\n  // The type of DNS record.\n  // These are defined here: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4\n  type: number;\n  // The number of seconds the answer can be stored in cache before it is considered stale.\n  TTL: number;\n  // The value of the DNS record for the given name and type. The data will be in text for standardized record types and in hex for unknown types.\n  data: string;\n}\nexport interface FailedResponse {\n  error: string;\n}\nexport interface SuccessResponse {\n  // The Response Code of the DNS Query.\n  // These are defined here: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6\n  Status: number;\n  // If true, it means the truncated bit was set.\n  // This happens when the DNS answer is larger than a single UDP or TCP packet.\n  // TC will almost always be false with Cloudflare DNS over HTTPS because Cloudflare supports the maximum response size.\n  TC: boolean;\n  // If true, it means the Recursive Desired bit was set.\n  RD: boolean;\n  // If true, it means the Recursion Available bit was set.\n  RA: boolean;\n  // \tIf true, it means that every record in the answer was verified with DNSSEC.\n  AD: boolean;\n  // \tIf true, the client asked to disable DNSSEC validation.\n  CD: boolean;\n  Question: {\n    // The record name requested.\n    name: string;\n    // The type of DNS record requested.\n    // These are defined here: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4\n    type: number;\n  }[];\n  Answer?: Answer[];\n  Authority: Answer[];\n  Additional?: Answer;\n}\n\nexport async function sendDnsRequest(\n  name: string,\n  type: string\n): Promise<SuccessResponse> {\n  // We are using cloudflare-dns.com and not 1.1.1.1 because of certificate issues.\n  // TODO(soon): Replace this when KJ certificate issues are resolved.\n  const server = new URL('https://cloudflare-dns.com/dns-query');\n  server.searchParams.set('name', name);\n  server.searchParams.set('type', type);\n\n  // syscall needs to be in format of `queryTxt`\n  const syscall = `query${type.at(0)?.toUpperCase()}${type.slice(1)}`;\n\n  let json: SuccessResponse | FailedResponse;\n  try {\n    const response = await fetch(server, {\n      headers: {\n        Accept: 'application/dns-json',\n      },\n      method: 'GET',\n    });\n    if (!response.ok) {\n      throw new DnsError(name, errorCodes.BADRESP, syscall);\n    }\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n    json = await response.json();\n  } catch (e) {\n    throw e instanceof DnsError\n      ? e\n      : new DnsError(name, errorCodes.BADQUERY, syscall);\n  }\n\n  if ('error' in json) {\n    throw new DnsError(name, errorCodes.BADRESP, syscall);\n  }\n\n  if (!json.Question.at(0)) {\n    // Some APIs depend on Question being existent.\n    throw new DnsError(name, errorCodes.BADRESP, syscall);\n  }\n\n  if (json.Answer?.at(0)?.name === '') {\n    throw new DnsError(name, errorCodes.NOTFOUND, syscall);\n  }\n\n  return json;\n}\n\nexport function validateAnswer(\n  answer: unknown,\n  name: string,\n  query: string\n): asserts answer is Answer[] {\n  if (answer == null) {\n    throw new DnsError(name, errorCodes.NOTFOUND, query);\n  }\n}\n\nexport type MX = {\n  exchange: string;\n  priority: number;\n};\nexport function normalizeMx(name: string, answer: Answer): MX {\n  const [priority, exchange]: string[] = answer.data.split(' ');\n  if (priority == null || exchange == null) {\n    throw new DnsError(name, errorCodes.BADRESP, 'queryMx');\n  }\n\n  // Cloudflare API returns \"data\": \"10 smtp.google.com.\" hence\n  // we need to parse it. Let's play it safe.\n  if (exchange.endsWith('.')) {\n    return {\n      exchange: exchange.slice(0, -1),\n      priority: parseInt(priority, 10),\n    };\n  }\n\n  return {\n    exchange,\n    priority: parseInt(priority, 10),\n  };\n}\n\nexport function normalizeCname({ data }: Answer): string {\n  // Cloudflare DNS returns \"nodejs.org.\" whereas\n  // Node.js returns \"nodejs.org\" as a CNAME data.\n  if (data.endsWith('.')) {\n    return data.slice(0, -1);\n  }\n  return data;\n}\n\nexport type CAA = {\n  critical: number;\n  issue?: string;\n  iodef?: string;\n  issuewild?: string;\n};\nexport function normalizeCaa({ data }: Answer): CAA {\n  // CAA API returns \"hex\", so we need to convert it to UTF-8\n  const record = dnsUtil.parseCaaRecord(data);\n  const obj: CAA = { critical: record.critical };\n  obj[record.field] = record.value;\n  return obj;\n}\n\nexport type NAPTR = {\n  flags: string;\n  service: string;\n  regexp: string;\n  replacement: string;\n  order: number;\n  preference: number;\n};\nexport function normalizeNaptr({ data }: Answer): NAPTR {\n  // Cloudflare DNS appends \".\" at the end whereas Node.js doesn't.\n  return dnsUtil.parseNaptrRecord(data);\n}\n\nexport function normalizePtr({ data }: Answer): string {\n  if (data.endsWith('.')) {\n    return data.slice(0, -1);\n  }\n  return data;\n}\n\nexport function normalizeNs({ data }: Answer): string {\n  if (data.endsWith('.')) {\n    return data.slice(0, -1);\n  }\n  return data;\n}\n\nexport type SOA = {\n  nsname: string;\n  hostmaster: string;\n  serial: number;\n  refresh: number;\n  retry: number;\n  expire: number;\n  minttl: number;\n};\nexport function normalizeSoa({ data }: Answer): SOA {\n  // Cloudflare DNS returns \"\"meera.ns.cloudflare.com. dns.cloudflare.com. 2357999196 10000 2400 604800 1800\"\"\n  const [nsname, hostmaster, serial, refresh, retry, expire, minttl] =\n    data.split(' ');\n\n  validateString(nsname, 'nsname');\n  validateString(hostmaster, 'hostmaster');\n  validateString(serial, 'serial');\n  validateString(refresh, 'refresh');\n  validateString(retry, 'retry');\n  validateString(expire, 'expire');\n  validateString(minttl, 'minttl');\n\n  return {\n    nsname,\n    hostmaster,\n    serial: parseInt(serial, 10),\n    refresh: parseInt(refresh, 10),\n    retry: parseInt(retry, 10),\n    expire: parseInt(expire, 10),\n    minttl: parseInt(minttl, 10),\n  };\n}\n\nexport type SRV = {\n  name: string;\n  port: number;\n  priority: number;\n  weight: number;\n};\nexport function normalizeSrv({ data }: Answer): SRV {\n  // Cloudflare DNS returns \"5 0 80 calendar.google.com\"\n  const [priority, weight, port, name] = data.split(' ');\n  validateString(priority, 'priority');\n  validateString(weight, 'weight');\n  validateString(port, 'port');\n  validateString(name, 'name');\n  return {\n    priority: parseInt(priority, 10),\n    weight: parseInt(weight, 10),\n    port: parseInt(port, 10),\n    name,\n  };\n}\n\n// This regex works by:\n//\n// `\"` - Matches an opening quote\n// ([^\"]|\"(?!\"))* - Matches either:\n//   [^\"] - Any character that's not a quote\n//   \"(?!\") - A quote that's not followed by another quote\n// `\"` - Matches a closing quote\n// /g - Global flag to match all occurrences\nconst SPLIT_REGEX = /\"([^\"]|\"(?!\"))*\"/g;\n\nexport function normalizeTxt({ data }: Answer): string[] {\n  // Each entry has quotation marks as a prefix and suffix.\n  // Node.js APIs doesn't have them.\n  if (data.startsWith('\"') && data.endsWith('\"')) {\n    // If the input starts and ends with a quotation mark, we need to split\n    // each occurrence and remove the leading/trailing characters.\n    // For example, for the input `\"test\"\"test\"\"test with \" quote\"`\n    // It returns: ['test', 'test', 'test with \" quote']\n    return (\n      data.match(SPLIT_REGEX)?.map((s) => {\n        if (s.startsWith('\"') && s.endsWith('\"')) {\n          return s.slice(1, -1);\n        }\n        return s;\n      }) ?? []\n    );\n  }\n  return [data];\n}\n"
  },
  {
    "path": "src/node/internal/internal_dns_constants.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport const NODATA = 'ENODATA';\nexport const FORMERR = 'EFORMERR';\nexport const SERVFAIL = 'ESERVFAIL';\nexport const NOTFOUND = 'ENOTFOUND';\nexport const NOTIMP = 'ENOTIMP';\nexport const REFUSED = 'EREFUSED';\nexport const BADQUERY = 'EBADQUERY';\nexport const BADNAME = 'EBADNAME';\nexport const BADFAMILY = 'EBADFAMILY';\nexport const BADRESP = 'EBADRESP';\nexport const CONNREFUSED = 'ECONNREFUSED';\nexport const TIMEOUT = 'ETIMEOUT';\nexport const EOF = 'EOF';\nexport const FILE = 'EFILE';\nexport const NOMEM = 'ENOMEM';\nexport const DESTRUCTION = 'EDESTRUCTION';\nexport const BADSTR = 'EBADSTR';\nexport const BADFLAGS = 'EBADFLAGS';\nexport const NONAME = 'ENONAME';\nexport const BADHINTS = 'EBADHINTS';\nexport const NOTINITIALIZED = 'ENOTINITIALIZED';\nexport const LOADIPHLPAPI = 'ELOADIPHLPAPI';\nexport const ADDRGETNETWORKPARAMS = 'EADDRGETNETWORKPARAMS';\nexport const CANCELLED = 'ECANCELLED';\nexport const ADDRCONFIG = 1024;\nexport const ALL = 256;\nexport const V4MAPPED = 2048;\n"
  },
  {
    "path": "src/node/internal/internal_dns_promises.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport type dns from 'node:dns/promises';\nimport type {\n  LookupAddress,\n  LookupAllOptions,\n  LookupOneOptions,\n  LookupOptions,\n} from 'node:dns';\nimport * as errorCodes from 'node-internal:internal_dns_constants';\nimport {\n  reverse,\n  resolveTxt,\n  resolveCaa,\n  resolveMx,\n  resolveCname,\n  resolveNs,\n  resolvePtr,\n  resolveSrv,\n  resolveSoa,\n  resolveNaptr,\n  resolve4,\n  resolve6,\n  getServers,\n  setServers,\n  getDefaultResultOrder,\n  setDefaultResultOrder,\n  lookup as internalLookup,\n  lookupService,\n  resolve,\n  resolveAny,\n} from 'node-internal:internal_dns';\nimport type {\n  CAA,\n  MX,\n  NAPTR,\n  SOA,\n  SRV,\n  TTLResponse,\n} from 'node-internal:internal_dns_client';\n\nexport * from 'node-internal:internal_dns_constants';\nexport {\n  reverse,\n  resolveTxt,\n  resolveCaa,\n  resolveMx,\n  resolveCname,\n  resolveNs,\n  resolvePtr,\n  resolveSrv,\n  resolveSoa,\n  resolveNaptr,\n  resolve4,\n  resolve6,\n  getServers,\n  setServers,\n  getDefaultResultOrder,\n  setDefaultResultOrder,\n  lookupService,\n  resolve,\n  resolveAny,\n} from 'node-internal:internal_dns';\n\nexport class Resolver implements dns.Resolver {\n  cancel(): void {\n    // TODO(soon): Implement this.\n    throw new Error('Not implemented');\n  }\n\n  setLocalAddress(): void {\n    // Does not apply to workerd implementation\n    throw new Error('Not implemented');\n  }\n\n  getServers(): string[] {\n    return getServers();\n  }\n\n  // @ts-expect-error TS2769 No matching overload.\n  resolve(name: string, rrtype: string): ReturnType<typeof resolve> {\n    return resolve(name, rrtype);\n  }\n\n  // @ts-expect-error TS2769 No matching overload.\n  resolve4(\n    input: string,\n    options?: { ttl?: boolean }\n  ): Promise<(string | TTLResponse)[]> {\n    return resolve4(input, options);\n  }\n\n  // @ts-expect-error TS2769 No matching overload.\n  resolve6(\n    input: string,\n    options?: { ttl?: boolean }\n  ): Promise<(string | TTLResponse)[]> {\n    return resolve6(input, options);\n  }\n\n  // @ts-expect-error TS2769 No matching overload.\n  resolveAny(): Promise<void> {\n    return resolveAny();\n  }\n\n  resolveCaa(name: string): Promise<CAA[]> {\n    return resolveCaa(name);\n  }\n\n  resolveCname(name: string): Promise<string[]> {\n    return resolveCname(name);\n  }\n\n  resolveMx(name: string): Promise<MX[]> {\n    return resolveMx(name);\n  }\n\n  resolveNaptr(name: string): Promise<NAPTR[]> {\n    return resolveNaptr(name);\n  }\n\n  resolveNs(name: string): Promise<string[]> {\n    return resolveNs(name);\n  }\n\n  resolvePtr(name: string): Promise<string[]> {\n    return resolvePtr(name);\n  }\n\n  resolveSoa(name: string): Promise<SOA> {\n    return resolveSoa(name);\n  }\n\n  resolveSrv(name: string): Promise<SRV[]> {\n    return resolveSrv(name);\n  }\n\n  resolveTxt(name: string): Promise<string[][]> {\n    return resolveTxt(name);\n  }\n\n  reverse(name: string): Promise<string[]> {\n    return reverse(name);\n  }\n\n  setServers(): void {\n    setServers();\n  }\n}\n\nexport function lookup(\n  hostname: string,\n  options?: LookupOptions | LookupOneOptions | LookupAllOptions\n): Promise<LookupAddress | LookupAddress[]> {\n  const { promise, resolve, reject } = Promise.withResolvers<\n    LookupAddress | LookupAddress[]\n  >();\n  internalLookup(hostname, options, (error, address, family) => {\n    if (error) {\n      reject(error);\n    } else if (Array.isArray(address)) {\n      resolve(address);\n    } else {\n      resolve({\n        address,\n        family,\n      } as LookupAddress);\n    }\n  });\n  return promise;\n}\n\nexport default {\n  reverse,\n  resolveTxt,\n  resolveCaa,\n  resolveMx,\n  resolveCname,\n  resolveNs,\n  resolvePtr,\n  resolveSrv,\n  resolveSoa,\n  resolveNaptr,\n  resolve4,\n  resolve6,\n  getServers,\n  setServers,\n  getDefaultResultOrder,\n  setDefaultResultOrder,\n  lookup,\n  lookupService,\n  resolve,\n  resolveAny,\n  Resolver,\n  ...errorCodes,\n};\n"
  },
  {
    "path": "src/node/internal/internal_errors.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Deno and Node.js:\n// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { inspect } from 'node-internal:internal_inspect';\nimport type { PeerCertificate } from 'node:tls';\n\nconst classRegExp = /^([A-Z][a-z0-9]*)+$/;\n\nconst kTypes = [\n  'string',\n  'function',\n  'number',\n  'object',\n  'Function',\n  'Object',\n  'boolean',\n  'bigint',\n  'symbol',\n];\n\nexport class NodeErrorAbstraction extends Error {\n  code: string;\n\n  constructor(name: string, code: string, message: string) {\n    super(message);\n    this.code = code;\n    this.name = name;\n  }\n\n  override toString(): string {\n    return `${this.name} [${this.code}]: ${this.message}`;\n  }\n}\n\nexport class NodeError extends NodeErrorAbstraction {\n  constructor(code: string, message: string) {\n    super(Error.prototype.name, code, message);\n  }\n}\n\nexport class NodeRangeError extends NodeErrorAbstraction {\n  constructor(code: string, message: string) {\n    super(RangeError.prototype.name, code, message);\n    Object.setPrototypeOf(this, RangeError.prototype);\n    this.toString = function (): string {\n      return `${this.name} [${this.code}]: ${this.message}`;\n    };\n  }\n}\n\nexport class NodeTypeError extends NodeErrorAbstraction implements TypeError {\n  constructor(code: string, message: string) {\n    super(TypeError.prototype.name, code, message);\n    Object.setPrototypeOf(this, TypeError.prototype);\n    this.toString = function (): string {\n      return `${this.name} [${this.code}]: ${this.message}`;\n    };\n  }\n}\n\nexport class NodeSyntaxError\n  extends NodeErrorAbstraction\n  implements SyntaxError\n{\n  constructor(code: string, message: string) {\n    super(SyntaxError.prototype.name, code, message);\n    Object.setPrototypeOf(this, SyntaxError.prototype);\n    this.toString = function (): string {\n      return `${this.name} [${this.code}]: ${this.message}`;\n    };\n  }\n}\n\nfunction createInvalidArgType(\n  name: string,\n  expected: string | string[]\n): string {\n  // https://github.com/nodejs/node/blob/f3eb224/lib/internal/errors.js#L1037-L1087\n  expected = Array.isArray(expected) ? expected : [expected];\n  let msg = 'The ';\n  if (name.endsWith(' argument')) {\n    // For cases like 'first argument'\n    msg += `${name} `;\n  } else {\n    const type = name.includes('.') ? 'property' : 'argument';\n    msg += `\"${name}\" ${type} `;\n  }\n  msg += 'must be ';\n\n  const types = [];\n  const instances = [];\n  const other = [];\n  for (const value of expected) {\n    if (kTypes.includes(value)) {\n      types.push(value.toLocaleLowerCase());\n    } else if (classRegExp.test(value)) {\n      instances.push(value);\n    } else {\n      other.push(value);\n    }\n  }\n\n  // Special handle `object` in case other instances are allowed to outline\n  // the differences between each other.\n  if (instances.length > 0) {\n    const pos = types.indexOf('object');\n    if (pos !== -1) {\n      types.splice(pos, 1);\n      instances.push('Object');\n    }\n  }\n\n  if (types.length > 0) {\n    if (types.length > 2) {\n      const last = types.pop();\n      msg += `one of type ${types.join(', ')}, or ${last}`;\n    } else if (types.length === 2) {\n      msg += `one of type ${types[0]} or ${types[1]}`;\n    } else {\n      msg += `of type ${types[0]}`;\n    }\n    if (instances.length > 0 || other.length > 0) {\n      msg += ' or ';\n    }\n  }\n\n  if (instances.length > 0) {\n    if (instances.length > 2) {\n      const last = instances.pop();\n      msg += `an instance of ${instances.join(', ')}, or ${last}`;\n    } else {\n      msg += `an instance of ${instances[0]}`;\n      if (instances.length === 2) {\n        msg += ` or ${instances[1]}`;\n      }\n    }\n    if (other.length > 0) {\n      msg += ' or ';\n    }\n  }\n\n  if (other.length > 0) {\n    if (other.length > 2) {\n      const last = other.pop();\n      msg += `one of ${other.join(', ')}, or ${last}`;\n    } else if (other.length === 2) {\n      msg += `one of ${other[0]} or ${other[1]}`;\n    } else {\n      if (other[0]?.toLowerCase() !== other[0]) {\n        msg += 'an ';\n      }\n      msg += `${other[0]}`;\n    }\n  }\n\n  return msg;\n}\n\nfunction invalidArgTypeHelper(input: unknown): string {\n  if (input == null) {\n    return ` Received ${input}`;\n  }\n  if (typeof input === 'function' && input.name) {\n    return ` Received function ${input.name}`;\n  }\n  if (typeof input === 'object') {\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    if (input.constructor?.name) {\n      return ` Received an instance of ${input.constructor.name}`;\n    }\n    return ` Received ${inspect(input, { depth: -1 })}`;\n  }\n  let inspected = inspect(input, { colors: false });\n  if (inspected.length > 25) {\n    inspected = `${inspected.slice(0, 25)}...`;\n  }\n  return ` Received type ${typeof input} (${inspected})`;\n}\n\nfunction addNumericalSeparator(val: string): string {\n  let res = '';\n  let i = val.length;\n  const start = val[0] === '-' ? 1 : 0;\n  for (; i >= start + 4; i -= 3) {\n    res = `_${val.slice(i - 3, i)}${res}`;\n  }\n  return `${val.slice(0, i)}${res}`;\n}\n\nexport class ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY extends NodeError {\n  constructor() {\n    super(\n      'ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY',\n      'Public key is not valid for specified curve'\n    );\n  }\n}\n\nexport class ERR_CRYPTO_HASH_FINALIZED extends NodeError {\n  constructor() {\n    super('ERR_CRYPTO_HASH_FINALIZED', 'Digest already called');\n  }\n}\n\nexport class ERR_CRYPTO_HASH_UPDATE_FAILED extends NodeError {\n  constructor() {\n    super('ERR_CRYPTO_HASH_UPDATE_FAILED', 'Hash update failed');\n  }\n}\n\nexport class ERR_CRYPTO_INCOMPATIBLE_KEY extends NodeError {\n  constructor(name: string, msg: string) {\n    super('ERR_CRYPTO_INCOMPATIBLE_KEY', `Incompatible ${name}: ${msg}`);\n  }\n}\n\nexport class ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE extends NodeError {\n  constructor(actual: string, expected: string) {\n    super(\n      'ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE',\n      `Invalid key object type ${actual}, expected ${expected}.`\n    );\n  }\n}\n\nexport class ERR_INVALID_ARG_TYPE_RANGE extends NodeRangeError {\n  constructor(name: string, expected: string | string[], actual: unknown) {\n    const msg = createInvalidArgType(name, expected);\n\n    super('ERR_INVALID_ARG_TYPE', `${msg}.${invalidArgTypeHelper(actual)}`);\n  }\n}\n\nexport class ERR_INVALID_ARG_TYPE extends NodeTypeError {\n  constructor(name: string, expected: string | string[], actual: unknown) {\n    const msg = createInvalidArgType(name, expected);\n\n    super('ERR_INVALID_ARG_TYPE', `${msg}.${invalidArgTypeHelper(actual)}`);\n  }\n\n  static RangeError = ERR_INVALID_ARG_TYPE_RANGE;\n}\n\nexport class ERR_INVALID_ARG_VALUE_RANGE extends NodeRangeError {\n  constructor(name: string, value: unknown, reason: string = 'is invalid') {\n    const type = name.includes('.') ? 'property' : 'argument';\n    const inspected = inspect(value);\n\n    super(\n      'ERR_INVALID_ARG_VALUE',\n      `The ${type} '${name}' ${reason}. Received ${inspected}`\n    );\n  }\n}\n\nexport class ERR_INVALID_ARG_VALUE extends NodeTypeError {\n  constructor(name: string, value: unknown, reason: string = 'is invalid') {\n    const type = name.includes('.') ? 'property' : 'argument';\n    const inspected = inspect(value);\n\n    super(\n      'ERR_INVALID_ARG_VALUE',\n      `The ${type} '${name}' ${reason}. Received ${inspected}`\n    );\n  }\n\n  static RangeError = ERR_INVALID_ARG_VALUE_RANGE;\n}\n\nexport class ERR_OUT_OF_RANGE extends RangeError {\n  code = 'ERR_OUT_OF_RANGE';\n\n  constructor(\n    str: string,\n    range: string,\n    input: unknown,\n    replaceDefaultBoolean = false\n  ) {\n    // TODO(later): Implement internal assert?\n    // assert(range, 'Missing \"range\" argument');\n    let msg = replaceDefaultBoolean\n      ? str\n      : `The value of \"${str}\" is out of range.`;\n    let received;\n    if (Number.isInteger(input) && Math.abs(input as number) > 2 ** 32) {\n      received = addNumericalSeparator(String(input));\n    } else if (typeof input === 'bigint') {\n      received = String(input);\n      if (input > 2n ** 32n || input < -(2n ** 32n)) {\n        received = addNumericalSeparator(received);\n      }\n      received += 'n';\n    } else {\n      received = inspect(input);\n    }\n    msg += ` It must be ${range}. Received ${received}`;\n\n    super(msg);\n\n    const { name } = this;\n    // Add the error code to the name to include it in the stack trace.\n    this.name = `${name} [${this.code}]`;\n    // Access the stack to generate the error message including the error code from the name.\n    // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n    this.stack;\n    // Reset the name to the actual name.\n    this.name = name;\n  }\n}\n\nexport class ERR_UNHANDLED_ERROR extends NodeError {\n  constructor(x: string) {\n    super('ERR_UNHANDLED_ERROR', `Unhandled error. (${x})`);\n  }\n}\n\nexport class ERR_INVALID_THIS extends NodeTypeError {\n  constructor(x: string) {\n    super('ERR_INVALID_THIS', `Value of \"this\" must be of type ${x}`);\n  }\n}\n\nexport class ERR_BUFFER_OUT_OF_BOUNDS extends NodeRangeError {\n  constructor(name?: string) {\n    super(\n      'ERR_BUFFER_OUT_OF_BOUNDS',\n      name\n        ? `\"${name}\" is outside of buffer bounds`\n        : 'Attempt to access memory outside buffer bounds'\n    );\n  }\n}\n\nexport class ERR_INVALID_BUFFER_SIZE extends NodeRangeError {\n  constructor(size: number) {\n    super(\n      'ERR_INVALID_BUFFER_SIZE',\n      `Buffer size must be a multiple of ${size}-bits`\n    );\n  }\n}\n\nexport class ERR_UNKNOWN_ENCODING extends NodeTypeError {\n  constructor(x: string) {\n    super('ERR_UNKNOWN_ENCODING', `Unknown encoding: ${x}`);\n  }\n}\n\nexport class ERR_STREAM_PREMATURE_CLOSE extends NodeTypeError {\n  constructor() {\n    super('ERR_STREAM_PREMATURE_CLOSE', 'Premature close');\n  }\n}\n\nexport class AbortError extends Error {\n  code: string;\n\n  constructor(message = 'The operation was aborted', options?: ErrorOptions) {\n    if (options !== undefined && typeof options !== 'object') {\n      throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);\n    }\n    super(message, options);\n    this.code = 'ABORT_ERR';\n    this.name = 'AbortError';\n  }\n}\n\nfunction determineSpecificType(value: unknown): string {\n  if (value == null) {\n    // eslint-disable-next-line @typescript-eslint/restrict-plus-operands\n    return '' + value;\n  }\n  if (typeof value === 'function' && value.name) {\n    return `function ${value.name}`;\n  }\n  if (typeof value === 'object') {\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    if (value.constructor?.name) {\n      return `an instance of ${value.constructor.name}`;\n    }\n    return inspect(value, { depth: -1 });\n  }\n  let inspected = inspect(value, { colors: false });\n  if (inspected.length > 28) inspected = `${inspected.slice(0, 25)}...`;\n\n  return `type ${typeof value} (${inspected})`;\n}\n\nexport class ERR_AMBIGUOUS_ARGUMENT extends NodeTypeError {\n  constructor(x: string, y: string) {\n    super('ERR_AMBIGUOUS_ARGUMENT', `The \"${x}\" argument is ambiguous. ${y}`);\n  }\n}\n\nexport class ERR_INVALID_RETURN_VALUE extends NodeTypeError {\n  constructor(input: string, name: string, value: unknown) {\n    super(\n      'ERR_INVALID_RETURN_VALUE',\n      `Expected ${input} to be returned from the \"${name}\" function but got ${determineSpecificType(\n        value\n      )}.`\n    );\n  }\n}\n\nexport class ERR_MULTIPLE_CALLBACK extends NodeError {\n  constructor() {\n    super('ERR_MULTIPLE_CALLBACK', 'Callback called multiple times');\n  }\n}\n\nexport class ERR_MISSING_ARGS extends NodeTypeError {\n  constructor(...args: (string | string[])[]) {\n    let msg = 'The ';\n\n    const len = args.length;\n\n    const wrap = (a: unknown): string => `\"${a}\"`;\n\n    args = args.map((a) =>\n      Array.isArray(a) ? a.map(wrap).join(' or ') : wrap(a)\n    );\n\n    switch (len) {\n      case 1:\n        msg += `${args[0]} argument`;\n        break;\n      case 2:\n        msg += `${args[0]} and ${args[1]} arguments`;\n        break;\n      default:\n        msg += args.slice(0, len - 1).join(', ');\n        msg += `, and ${args[len - 1]} arguments`;\n        break;\n    }\n\n    super('ERR_MISSING_ARGS', `${msg} must be specified`);\n  }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents,@typescript-eslint/no-duplicate-type-constituents\nexport type Falsy = false | 0 | -0 | 0n | '' | null | undefined | typeof NaN;\nexport class ERR_FALSY_VALUE_REJECTION extends NodeError {\n  reason: Falsy;\n  constructor(reason: Falsy) {\n    super('ERR_FALSY_VALUE_REJECTION', 'Promise was rejected with falsy value');\n    this.reason = reason;\n  }\n}\n\nexport class ERR_METHOD_NOT_IMPLEMENTED extends NodeError {\n  constructor(name: string | symbol) {\n    if (typeof name === 'symbol') {\n      name = name.description as string;\n    }\n    super(\n      'ERR_METHOD_NOT_IMPLEMENTED',\n      `The ${name} method is not implemented`\n    );\n  }\n}\n\nexport class ERR_STREAM_CANNOT_PIPE extends NodeError {\n  constructor() {\n    super('ERR_STREAM_CANNOT_PIPE', 'Cannot pipe, not readable');\n  }\n}\nexport class ERR_STREAM_DESTROYED extends NodeError {\n  constructor(name: string | symbol) {\n    if (typeof name === 'symbol') {\n      name = name.description as string;\n    }\n    super(\n      'ERR_STREAM_DESTROYED',\n      `Cannot call ${name} after a stream was destroyed`\n    );\n  }\n}\nexport class ERR_STREAM_ALREADY_FINISHED extends NodeError {\n  constructor(name: string | symbol) {\n    if (typeof name === 'symbol') {\n      name = name.description as string;\n    }\n    super(\n      'ERR_STREAM_ALREADY_FINISHED',\n      `Cannot call ${name} after a stream was finished`\n    );\n  }\n}\nexport class ERR_STREAM_NULL_VALUES extends NodeTypeError {\n  constructor() {\n    super('ERR_STREAM_NULL_VALUES', 'May not write null values to stream');\n  }\n}\nexport class ERR_STREAM_WRITE_AFTER_END extends NodeError {\n  constructor() {\n    super('ERR_STREAM_WRITE_AFTER_END', 'write after end');\n  }\n}\n\nexport class ERR_STREAM_PUSH_AFTER_EOF extends NodeError {\n  constructor() {\n    super('ERR_STREAM_PUSH_AFTER_EOF', 'stream.push() after EOF');\n  }\n}\n\nexport class ERR_STREAM_UNSHIFT_AFTER_END_EVENT extends NodeError {\n  constructor() {\n    super(\n      'ERR_STREAM_UNSHIFT_AFTER_END_EVENT',\n      'stream.unshift() after end event'\n    );\n  }\n}\n\nexport class ERR_BUFFER_TOO_LARGE extends NodeRangeError {\n  constructor(value: number) {\n    super(\n      'ERR_BUFFER_TOO_LARGE',\n      `Cannot create a Buffer larger than ${value} bytes`\n    );\n  }\n}\n\nexport class ERR_BROTLI_INVALID_PARAM extends NodeRangeError {\n  constructor(value: unknown) {\n    super(\n      'ERR_BROTLI_INVALID_PARAM',\n      `${value} is not a valid Brotli parameter`\n    );\n  }\n}\n\nexport class ERR_ZSTD_INVALID_PARAM extends NodeRangeError {\n  constructor(value: unknown) {\n    super('ERR_ZSTD_INVALID_PARAM', `${value} is not a valid Zstd parameter`);\n  }\n}\n\nexport class ERR_ZLIB_INITIALIZATION_FAILED extends NodeError {\n  constructor() {\n    super('ERR_ZLIB_INITIALIZATION_FAILED', 'Initialization failed');\n  }\n}\n\nexport class ERR_INVALID_URL extends NodeError {\n  input: string;\n\n  constructor(url: string) {\n    super('ERR_INVALID_URL', 'Invalid URL');\n    this.input = url;\n  }\n}\n\nexport class ERR_INVALID_URL_SCHEME extends NodeError {\n  constructor(scheme: string) {\n    super('ERR_INVALID_URL_SCHEME', `The URL must be of scheme ${scheme}`);\n  }\n}\n\nexport class ERR_INVALID_FILE_URL_HOST extends NodeError {\n  constructor(input: string) {\n    super(\n      'ERR_INVALID_FILE_URL_HOST',\n      `File URL host must be \"localhost\" or empty on ${input}`\n    );\n  }\n}\n\nexport class ERR_INVALID_FILE_URL_PATH extends NodeError {\n  constructor(input: string) {\n    super('ERR_INVALID_FILE_URL_PATH', `File URL path ${input}`);\n  }\n}\n\nexport class ERR_INVALID_URI extends NodeError {\n  constructor() {\n    super('ERR_INVALID_URI', 'URI malformed');\n    this.name = 'URIError';\n  }\n}\n\n// Example:\n//\n// Error: queryTxt ENOTFOUND google.com2\n//     at QueryReqWrap.onresolve [as oncomplete] (node:internal/dns/callback_resolver:45:19)\n//     at QueryReqWrap.callbackTrampoline (node:internal/async_hooks:130:17) {\n//   errno: undefined,\n//   code: 'ENOTFOUND',\n//   syscall: 'queryTxt',\n//   hostname: 'google.com2'\n// }\nexport class DnsError extends NodeError {\n  errno = undefined;\n  hostname: string;\n  syscall: string;\n\n  constructor(hostname: string, code: string, syscall: string) {\n    super(code, `${syscall} ${code} ${hostname}`);\n    this.hostname = hostname;\n    this.syscall = syscall;\n  }\n}\n\nexport class ERR_OPTION_NOT_IMPLEMENTED extends NodeError {\n  constructor(name: string | symbol) {\n    if (typeof name === 'symbol') {\n      name = name.description as string;\n    }\n    super(\n      'ERR_OPTION_NOT_IMPLEMENTED',\n      `The ${name} option is not implemented`\n    );\n  }\n}\n\nexport class ERR_SOCKET_BAD_PORT extends NodeError {\n  constructor(name: string, port: unknown, allowZero: boolean) {\n    const operator = allowZero ? '>=' : '>';\n    super(\n      'ERR_SOCKET_BAD_PORT',\n      `${name} should be ${operator} 0 and < 65536. Received ${typeof port}.`\n    );\n  }\n}\n\nexport class EPIPE extends NodeError {\n  constructor() {\n    super('EPIPE', 'This socket has been ended by the other party');\n  }\n}\n\nexport class ERR_SOCKET_CLOSED_BEFORE_CONNECTION extends NodeError {\n  constructor() {\n    super(\n      'ERR_SOCKET_CLOSED_BEFORE_CONNECTION',\n      'Socket closed before connection established'\n    );\n  }\n}\n\nexport class ERR_SOCKET_CLOSED extends NodeError {\n  constructor() {\n    super('ERR_SOCKET_CLOSED', 'Socket is closed');\n  }\n}\n\nexport class ERR_SOCKET_CONNECTING extends NodeError {\n  constructor() {\n    super('ERR_SOCKET_CONNECTING', 'Socket is already connecting');\n  }\n}\n\nexport class ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS extends NodeError {\n  constructor(arg0: string, arg1: string) {\n    super(\n      'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS',\n      `The selected key encoding ${arg0} ${arg1}.`\n    );\n  }\n}\n\nexport class ERR_CRYPTO_INVALID_JWK extends NodeTypeError {\n  constructor() {\n    super('ERR_CRYPTO_INVALID_JWK', 'Invalid JWK data');\n  }\n}\n\nexport class ERR_INCOMPATIBLE_OPTION_PAIR extends NodeError {\n  constructor(a: string, b: string) {\n    super(\n      'ERR_INCOMPATIBLE_OPTION_PAIR',\n      `The options \"${a}\" and \"${b}\" are mutually exclusive.`\n    );\n  }\n}\n\nexport class ERR_MISSING_OPTION extends NodeError {\n  constructor(name: string) {\n    super('ERR_MISSING_OPTION', name);\n  }\n}\n\nexport class ERR_UNSUPPORTED_OPERATION extends NodeError {\n  constructor() {\n    super(\n      'ERR_UNSUPPORTED_OPERATION',\n      'The requested operation is unsupported'\n    );\n  }\n}\n\nexport class ERR_CRYPTO_SIGN_KEY_REQUIRED extends NodeError {\n  constructor() {\n    super('ERR_CRYPTO_SIGN_KEY_REQUIRED', 'No key provided to sign');\n  }\n}\n\nexport class ERR_TLS_HANDSHAKE_TIMEOUT extends NodeError {\n  constructor() {\n    super('ERR_TLS_HANDSHAKE_TIMEOUT', 'TLS handshake timeout');\n  }\n}\n\nexport class ERR_TLS_INVALID_CONTEXT extends NodeTypeError {\n  constructor(field: string) {\n    super('ERR_TLS_INVALID_CONTEXT', `${field} must be a SecureContext`);\n  }\n}\n\nexport class ERR_TLS_CERT_ALTNAME_INVALID extends NodeError {\n  reason: string;\n  host: string;\n  cert?: Partial<PeerCertificate> | undefined;\n\n  constructor(reason: string, host: string, cert?: Partial<PeerCertificate>) {\n    super('ERR_TLS_CERT_ALTNAME_INVALID', reason);\n    this.reason = reason;\n    this.host = host;\n    this.cert = cert;\n  }\n}\n\nexport class ERR_TLS_CERT_ALTNAME_FORMAT extends NodeSyntaxError {\n  constructor() {\n    super(\n      'ERR_TLS_CERT_ALTNAME_FORMAT',\n      'Invalid subject alternative name string'\n    );\n  }\n}\n\nexport class ConnResetException extends NodeError {\n  path?: string | undefined;\n  host?: string | undefined;\n  port?: number | undefined;\n  localAddress?: string | undefined;\n\n  constructor(message: string) {\n    super('ECONNRESET', message);\n  }\n}\n\nexport function aggregateTwoErrors(\n  innerError: unknown,\n  outerError: Error | null | undefined\n): AggregateError {\n  if (innerError && outerError && innerError !== outerError) {\n    if ('errors' in outerError && Array.isArray(outerError.errors)) {\n      // If `outerError` is already an `AggregateError`.\n      outerError.errors.push(innerError);\n      return outerError as AggregateError;\n    }\n    const err = new AggregateError(\n      [outerError, innerError],\n      outerError.message\n    );\n    (err as unknown as NodeError).code = (outerError as NodeError).code;\n    return err;\n  }\n  return (innerError || outerError) as AggregateError;\n}\n\nexport class ERR_INVALID_IP_ADDRESS extends NodeTypeError {\n  constructor(ipAddress: string) {\n    super('ERR_INVALID_IP_ADDRESS', `Invalid IP address: ${ipAddress}`);\n  }\n}\n\nexport class ERR_INVALID_ADDRESS extends NodeTypeError {\n  constructor() {\n    super('ERR_INVALID_ADDRESS', 'Invalid socket address');\n  }\n}\n\nexport class ERR_STREAM_WRAP extends NodeError {\n  constructor() {\n    super(\n      'ERR_STREAM_WRAP',\n      'Stream has StringDecoder set or is in objectMode'\n    );\n  }\n}\n\nexport class ERR_INVALID_HTTP_TOKEN extends NodeTypeError {\n  constructor(label: string, name: string | undefined) {\n    super(\n      'ERR_INVALID_HTTP_TOKEN',\n      `${label} must be a valid HTTP token [\"${name}\"]`\n    );\n  }\n}\n\nexport class ERR_HTTP_INVALID_HEADER_VALUE extends NodeTypeError {\n  constructor(value: string | undefined, header: string | undefined) {\n    super(\n      'ERR_HTTP_INVALID_HEADER_VALUE',\n      `Invalid value \"${value}\" for header \"${header}\"`\n    );\n  }\n}\n\nexport class ERR_INVALID_CHAR extends NodeTypeError {\n  constructor(name: string, field?: string) {\n    let msg = `Invalid character in ${name}`;\n    if (field !== undefined) {\n      msg += ` [\"${field}\"]`;\n    }\n    super('ERR_INVALID_CHAR', msg);\n  }\n}\n\nexport class NodeSyscallError extends NodeError {\n  syscall: string;\n  errno?: number;\n  constructor(code: string, message: string, syscall: string) {\n    super(code, message);\n    this.syscall = syscall;\n  }\n}\n\nexport class ERR_ENOENT extends NodeSyscallError {\n  path: string;\n  constructor(path: string, options: { syscall: string }) {\n    super(\n      'ENOENT',\n      `no such file or directory, ${options.syscall} '${path}'`,\n      options.syscall\n    );\n    this.path = path;\n    this.errno = -2; // ENOENT\n  }\n}\n\nexport class ERR_EBADF extends NodeSyscallError {\n  constructor(options: { syscall: string }) {\n    super('EBADF', 'Bad file descriptor', options.syscall);\n    this.errno = -9; // EBADF\n  }\n}\n\nexport class ERR_EINVAL extends NodeSyscallError {\n  path?: string;\n  constructor(options: { syscall: string; path?: string }) {\n    const message = options.path\n      ? `invalid argument, ${options.syscall} '${options.path}'`\n      : 'invalid argument';\n    super('EINVAL', message, options.syscall);\n    if (options.path !== undefined) {\n      this.path = options.path;\n    }\n    this.errno = -22; // EINVAL\n  }\n}\n\nexport class ERR_EEXIST extends NodeSyscallError {\n  path?: string;\n  constructor(options: { syscall: string; path?: string }) {\n    const message = options.path\n      ? `file already exists, ${options.syscall} '${options.path}'`\n      : 'file already exists';\n    super('EEXIST', message, options.syscall);\n    if (options.path !== undefined) {\n      this.path = options.path;\n    }\n    this.errno = -17; // EEXIST\n  }\n}\n\nexport class ERR_EPERM extends NodeSyscallError {\n  path?: string;\n  constructor(options: { syscall: string; errno?: number; path?: string }) {\n    const message = options.path\n      ? `operation not permitted, ${options.syscall} '${options.path}'`\n      : 'operation not permitted';\n    super('EPERM', message, options.syscall);\n    if (options.path !== undefined) {\n      this.path = options.path;\n    }\n    this.errno = options.errno ?? 1; // EPERM\n  }\n}\n\nexport class ERR_DIR_CLOSED extends NodeError {\n  constructor() {\n    super('ERR_DIR_CLOSED', 'Directory is closed');\n  }\n}\n\nexport class ERR_HTTP_HEADERS_SENT extends NodeError {\n  constructor(action: string) {\n    super(\n      'ERR_HTTP_HEADERS_SENT',\n      `Cannot ${action} headers after they are sent to the client`\n    );\n  }\n}\n\nexport class ERR_HTTP_CONTENT_LENGTH_MISMATCH extends NodeError {\n  constructor(actual: number, expected: number) {\n    super(\n      'ERR_HTTP_CONTENT_LENGTH_MISMATCH',\n      `Request body length does not match content-length header. Expected: ${expected}, Actual: ${actual}`\n    );\n  }\n}\n\nexport class ERR_HTTP_BODY_NOT_ALLOWED extends NodeError {\n  constructor() {\n    super(\n      'ERR_HTTP_BODY_NOT_ALLOWED',\n      'Request with GET/HEAD method cannot have body'\n    );\n  }\n}\n\nexport class ERR_INVALID_PROTOCOL extends NodeTypeError {\n  constructor(actual: string, expected: string) {\n    super(\n      'ERR_INVALID_PROTOCOL',\n      `Protocol \"${actual}\" not supported. Expected \"${expected}\"`\n    );\n  }\n}\n\nexport class ERR_UNESCAPED_CHARACTERS extends NodeTypeError {\n  constructor(field: string) {\n    super('ERR_UNESCAPED_CHARACTERS', `${field} contains unescaped characters`);\n  }\n}\n\nexport class ERR_SYSTEM_ERROR extends NodeError {\n  constructor(message: string) {\n    super('ERR_SYSTEM_ERROR', message);\n  }\n}\n\nexport class ERR_HTTP_INVALID_STATUS_CODE extends NodeRangeError {\n  constructor(statusCode: string | number) {\n    super('ERR_HTTP_INVALID_STATUS_CODE', `Invalid status code: ${statusCode}`);\n  }\n}\n\nexport class ERR_SERVER_ALREADY_LISTEN extends NodeError {\n  constructor() {\n    super(\n      'ERR_SERVER_ALREADY_LISTEN',\n      'Listen method has been called more than once without closing.'\n    );\n  }\n}\n\nexport class ERR_ILLEGAL_CONSTRUCTOR extends NodeTypeError {\n  constructor() {\n    super('ERR_ILLEGAL_CONSTRUCTOR', 'Illegal constructor');\n  }\n}\n\nexport class ERR_PERFORMANCE_INVALID_TIMESTAMP extends NodeTypeError {\n  constructor(value: unknown) {\n    super(\n      'ERR_PERFORMANCE_INVALID_TIMESTAMP',\n      `${value} is not a valid timestamp`\n    );\n  }\n}\n\nexport class ERR_TRACE_EVENTS_CATEGORY_REQUIRED extends NodeTypeError {\n  constructor() {\n    super(\n      'ERR_TRACE_EVENTS_CATEGORY_REQUIRED',\n      'At least one category is required'\n    );\n  }\n}\n"
  },
  {
    "path": "src/node/internal/internal_fs.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport {\n  UV_DIRENT_DIR,\n  UV_DIRENT_FILE,\n  UV_DIRENT_BLOCK,\n  UV_DIRENT_CHAR,\n  UV_DIRENT_LINK,\n  UV_DIRENT_FIFO,\n  UV_DIRENT_SOCKET,\n} from 'node-internal:internal_fs_constants';\nimport {\n  type FilePath,\n  type ValidEncoding,\n} from 'node-internal:internal_fs_utils';\nimport {\n  default as cffs,\n  type DirEntryHandle,\n} from 'cloudflare-internal:filesystem';\nimport {\n  validateFunction,\n  validateObject,\n  validateString,\n} from 'node-internal:validators';\nimport {\n  ERR_DIR_CLOSED,\n  ERR_MISSING_ARGS,\n} from 'node-internal:internal_errors';\nimport { Buffer } from 'node-internal:internal_buffer';\nconst kType = Symbol('type');\n\nexport class Dirent {\n  name: string | Buffer;\n  parentPath: string | Buffer;\n  private [kType]: number;\n\n  constructor(name: string | Buffer, type: number, path: string | Buffer) {\n    this.name = name;\n    this.parentPath = path;\n    this[kType] = type;\n  }\n\n  isDirectory(): boolean {\n    return this[kType] === UV_DIRENT_DIR;\n  }\n\n  isFile(): boolean {\n    return this[kType] === UV_DIRENT_FILE;\n  }\n\n  isBlockDevice(): boolean {\n    return this[kType] === UV_DIRENT_BLOCK;\n  }\n\n  isCharacterDevice(): boolean {\n    return this[kType] === UV_DIRENT_CHAR;\n  }\n\n  isSymbolicLink(): boolean {\n    return this[kType] === UV_DIRENT_LINK;\n  }\n\n  isFIFO(): boolean {\n    return this[kType] === UV_DIRENT_FIFO;\n  }\n\n  isSocket(): boolean {\n    return this[kType] === UV_DIRENT_SOCKET;\n  }\n}\n\nexport interface DirOptions {\n  encoding?: ValidEncoding | undefined;\n}\n\nexport type DirentReadCallback = (\n  err: Error | null,\n  dirent: Dirent | null\n) => void;\n\nexport class Dir {\n  // Unlike our Node.js counterpart, we do not not use an underlying\n  // native handle for the directory. Instead we use a simple array\n  // of DirEntryHandle objects just like the one used in the readdir\n  // API.\n  #handle: DirEntryHandle[] | undefined;\n  #encoding: ValidEncoding | undefined;\n  #path: FilePath;\n\n  constructor(\n    handle: cffs.DirEntryHandle[] | undefined,\n    path: FilePath,\n    options: DirOptions\n  ) {\n    if (handle == null) {\n      throw new ERR_MISSING_ARGS('handle');\n    }\n    this.#handle = handle;\n    this.#path = path;\n\n    validateObject(options, 'options');\n    const { encoding = 'utf8' } = options;\n    validateString(encoding, 'options.encoding');\n    this.#encoding = encoding as BufferEncoding;\n  }\n\n  get path(): FilePath {\n    return this.#path;\n  }\n\n  read(): Promise<Dirent | null>;\n  read(callback: DirentReadCallback): void;\n  read(callback?: DirentReadCallback): Promise<Dirent | null> | undefined {\n    if (typeof callback === 'function') {\n      validateFunction(callback, 'callback');\n      try {\n        const ent = this.readSync();\n        queueMicrotask(() => {\n          callback(null, ent);\n        });\n      } catch (err) {\n        queueMicrotask(() => {\n          callback(err as Error, null);\n        });\n      }\n      return;\n    }\n\n    try {\n      const ent = this.readSync();\n      return Promise.resolve(ent);\n    } catch (err: unknown) {\n      return Promise.reject(err as Error);\n    }\n  }\n\n  readSync(): Dirent | null {\n    if (this.#handle === undefined) throw new ERR_DIR_CLOSED();\n    const ent = this.#handle.shift();\n    if (ent == null) return null;\n    const buf = Buffer.from(ent.name);\n    if (this.#encoding === 'buffer') {\n      return new Dirent(buf, ent.type, ent.parentPath);\n    }\n    return new Dirent(\n      buf.toString(this.#encoding != null ? this.#encoding : undefined),\n      ent.type,\n      ent.parentPath\n    );\n  }\n\n  close(callback?: (err: unknown) => void): Promise<void> | undefined {\n    this.closeSync();\n    if (typeof callback === 'function') {\n      validateFunction(callback, 'callback');\n      queueMicrotask(() => {\n        callback(null);\n      });\n      return undefined;\n    }\n    return Promise.resolve();\n  }\n\n  closeSync(): void {\n    if (this.#handle === undefined) {\n      throw new ERR_DIR_CLOSED();\n    }\n    this.#handle = undefined;\n  }\n\n  async *entries(): AsyncGenerator<Dirent, unknown, unknown> {\n    for (;;) {\n      const ent = await this.read();\n      if (ent == null) return;\n      yield ent;\n    }\n  }\n\n  [Symbol.asyncIterator](): AsyncGenerator<Dirent, unknown, unknown> {\n    return this.entries();\n  }\n\n  async [Symbol.asyncDispose](): Promise<void> {\n    await this.close();\n  }\n\n  [Symbol.dispose](): void {\n    this.closeSync();\n  }\n}\n"
  },
  {
    "path": "src/node/internal/internal_fs_callback.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\nimport * as fssync from 'node-internal:internal_fs_sync';\nimport { default as cffs } from 'cloudflare-internal:filesystem';\nimport type {\n  FStatOptions,\n  MkdirTempSyncOptions,\n  ReadDirResult,\n  ReadFileSyncOptions,\n  ReadLinkSyncOptions,\n  StatOptions,\n} from 'node-internal:internal_fs_sync';\nimport {\n  validatePosition,\n  getDate,\n  validateAccessArgs,\n  validateChownArgs,\n  validateChmodArgs,\n  validateStatArgs,\n  validateMkDirArgs,\n  validateOpendirArgs,\n  validateRmArgs,\n  validateRmDirArgs,\n  validateReaddirArgs,\n  validateWriteArgs,\n  validateWriteFileArgs,\n  normalizePath,\n  Stats,\n  type FilePath,\n  type Position,\n  type RawTime,\n  type SymlinkType,\n  type ReadDirOptions,\n  type WriteSyncOptions,\n  type ValidEncoding,\n  getValidatedFd,\n  validateBufferArray,\n  stringToFlags,\n} from 'node-internal:internal_fs_utils';\nimport {\n  F_OK,\n  COPYFILE_EXCL,\n  COPYFILE_FICLONE,\n  COPYFILE_FICLONE_FORCE,\n} from 'node-internal:internal_fs_constants';\nimport {\n  ERR_EBADF,\n  ERR_ENOENT,\n  ERR_EEXIST,\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_ARG_VALUE,\n  ERR_UNSUPPORTED_OPERATION,\n} from 'node-internal:internal_errors';\nimport { type Dir } from 'node-internal:internal_fs';\nimport { Buffer } from 'node-internal:internal_buffer';\nimport { isArrayBufferView } from 'node-internal:internal_types';\nimport {\n  parseFileMode,\n  validateBoolean,\n  validateObject,\n  validateOneOf,\n  validateUint32,\n} from 'node-internal:validators';\nimport type {\n  BigIntStatsFs,\n  CopySyncOptions,\n  GlobOptions,\n  GlobOptionsWithFileTypes,\n  GlobOptionsWithoutFileTypes,\n  MakeDirectoryOptions,\n  OpenDirOptions,\n  ReadOptionsWithBuffer,\n  RmOptions,\n  StatsFs,\n  WriteFileOptions,\n} from 'node:fs';\nimport type { RmDirOptions } from 'node-internal:internal_fs_utils';\n\nexport type ErrorOnlyCallback = (err: unknown) => void;\nexport type SingleArgCallback<T> = (err: unknown, result?: T) => void;\nexport type DoubleArgCallback<T, U> = (\n  err: unknown,\n  result1?: T,\n  result2?: U\n) => void;\n\nfunction callWithErrorOnlyCallback(\n  fn: () => void,\n  callback: undefined | ErrorOnlyCallback\n): void {\n  if (typeof callback !== 'function') {\n    throw new ERR_INVALID_ARG_TYPE('callback', ['function'], callback);\n  }\n  try {\n    fn();\n    // Note that any errors thrown by the callback will be \"handled\" by passing\n    // them along to the reportError function, which logs them and triggers the\n    // global \"error\" event.\n    queueMicrotask(() => {\n      callback(null);\n    });\n  } catch (err) {\n    queueMicrotask(() => {\n      callback(err);\n    });\n  }\n}\n\nfunction callWithSingleArgCallback<T>(\n  fn: () => T,\n  callback: undefined | SingleArgCallback<T>\n): void {\n  if (typeof callback !== 'function') {\n    throw new ERR_INVALID_ARG_TYPE('callback', ['function'], callback);\n  }\n  try {\n    const result = fn();\n    queueMicrotask(() => {\n      callback(null, result);\n    });\n  } catch (err) {\n    queueMicrotask(() => {\n      callback(err);\n    });\n  }\n}\n\nexport function access(\n  path: FilePath,\n  modeOrCallback: number | ErrorOnlyCallback = F_OK,\n  callback?: ErrorOnlyCallback\n): void {\n  let mode: number;\n  if (typeof modeOrCallback === 'function') {\n    callback = modeOrCallback;\n    mode = F_OK;\n  } else {\n    mode = modeOrCallback;\n  }\n\n  const { path: actualPath, mode: actualMode } = validateAccessArgs(path, mode);\n\n  callWithErrorOnlyCallback(() => {\n    fssync.accessSyncImpl(actualPath, actualMode, true);\n  }, callback);\n}\n\nexport type ExistsCallback = (result: boolean) => void;\n\nexport function exists(path: FilePath, callback: ExistsCallback): void {\n  // With the other methods we perform the method and *then* pass the results\n  // back to the callback using queueMicrotask. Here, however, we wait to\n  // perform the method until we are in the mcirotask. This is so we can\n  // best avoid the race condition that has long existed with the exists\n  // method in Node.js where the file may be deleted between the time we\n  // check for its existence and the time we call the callback.\n  queueMicrotask(() => {\n    callback(fssync.existsSync(path));\n  });\n}\n\nexport function appendFile(\n  path: number | FilePath,\n  data: string | ArrayBufferView,\n  optionsOrCallback: WriteFileOptions | ErrorOnlyCallback,\n  callback?: ErrorOnlyCallback\n): void {\n  let options: WriteFileOptions;\n  if (typeof optionsOrCallback === 'function') {\n    callback = optionsOrCallback;\n    options = {\n      encoding: 'utf8',\n      mode: 0o666,\n      flag: 'a',\n      flush: false,\n    };\n  } else {\n    options = optionsOrCallback;\n  }\n  writeFile(path, data, options, callback);\n}\n\nexport function chmod(\n  path: FilePath,\n  mode: number | string,\n  callback: ErrorOnlyCallback\n): void {\n  const { pathOrFd } = validateChmodArgs(path, mode);\n  callWithErrorOnlyCallback(() => {\n    if (cffs.stat(pathOrFd as URL, { followSymlinks: true }) == null) {\n      throw new ERR_ENOENT((pathOrFd as URL).pathname, { syscall: 'chmod' });\n    }\n  }, callback);\n}\n\nexport function chown(\n  path: FilePath,\n  uid: number,\n  gid: number,\n  callback: ErrorOnlyCallback\n): void {\n  const { pathOrFd } = validateChownArgs(path, uid, gid);\n  callWithErrorOnlyCallback(() => {\n    if (cffs.stat(pathOrFd as URL, { followSymlinks: true }) == null) {\n      throw new ERR_ENOENT((path as URL).pathname, { syscall: 'chown' });\n    }\n  }, callback);\n}\n\nexport function close(\n  fd: number,\n  callback: ErrorOnlyCallback = () => {}\n): void {\n  fd = getValidatedFd(fd);\n  callWithErrorOnlyCallback(() => {\n    fssync.closeSync(fd);\n  }, callback);\n}\n\nexport function copyFile(\n  src: FilePath,\n  dest: FilePath,\n  modeOrCallback: number | ErrorOnlyCallback = 0,\n  callback?: ErrorOnlyCallback\n): void {\n  let mode: number;\n  if (typeof modeOrCallback === 'function') {\n    callback = modeOrCallback;\n    mode = 0;\n  } else {\n    mode = modeOrCallback;\n  }\n  const normalizedSrc = normalizePath(src);\n  const normalizedDest = normalizePath(dest);\n\n  validateOneOf(mode, 'mode', [\n    0,\n    COPYFILE_EXCL,\n    COPYFILE_FICLONE_FORCE,\n    COPYFILE_FICLONE,\n  ]);\n  if (mode & COPYFILE_FICLONE_FORCE) {\n    throw new ERR_UNSUPPORTED_OPERATION();\n  }\n  if (mode & COPYFILE_EXCL && fssync.existsSync(dest)) {\n    throw new ERR_EEXIST({\n      syscall: 'copyFile',\n      path: normalizedDest.pathname,\n    });\n  }\n\n  callWithErrorOnlyCallback(() => {\n    fssync.copyFileSync(normalizedSrc, normalizedDest, mode);\n  }, callback);\n}\n\nexport function cp(\n  src: FilePath,\n  dest: FilePath,\n  optionsOrCallback: CopySyncOptions | ErrorOnlyCallback,\n  callback?: ErrorOnlyCallback\n): void {\n  let options: CopySyncOptions;\n  if (typeof optionsOrCallback === 'function') {\n    callback = optionsOrCallback;\n    options = {};\n  } else {\n    options = optionsOrCallback;\n  }\n\n  validateObject(options, 'options');\n  const {\n    dereference = false,\n    errorOnExist = false,\n    force = true,\n    mode = 0,\n    preserveTimestamps = false,\n    recursive = false,\n    verbatimSymlinks = false,\n  } = options;\n\n  validateBoolean(dereference, 'options.dereference');\n  validateBoolean(errorOnExist, 'options.errorOnExist');\n  validateBoolean(force, 'options.force');\n  validateBoolean(preserveTimestamps, 'options.preserveTimestamps');\n  validateBoolean(recursive, 'options.recursive');\n  validateBoolean(verbatimSymlinks, 'options.verbatimSymlinks');\n  validateUint32(mode, 'options.mode');\n\n  if (mode & COPYFILE_FICLONE_FORCE) {\n    throw new ERR_INVALID_ARG_VALUE(\n      'options.mode',\n      'COPYFILE_FICLONE_FORCE is not supported'\n    );\n  }\n\n  if (options.filter !== undefined) {\n    if (typeof options.filter !== 'function') {\n      throw new ERR_INVALID_ARG_TYPE(\n        'options.filter',\n        'function',\n        options.filter\n      );\n    }\n    // We do not implement the filter option currently. There's a bug in the Node.js\n    // implementation of fs.cp and the option.filter in which non-UTF-8 encoded file\n    // names are not handled correctly and the option.filter fails when the src or\n    // dest is passed in as a Buffer. Fixing this bug in Node.js will require a breaking\n    // change to the API or a new API that appropriately handles Buffer inputs and non\n    // UTF-8 encoded names. We want to avoid implementing the filter option for now\n    // until Node.js settles on a better implementation and API.\n    throw new ERR_UNSUPPORTED_OPERATION();\n  }\n\n  const exclusive = Boolean(mode & COPYFILE_EXCL);\n  // We're not current implementing the exclusive flag. We're validating\n  // it here just to use it so the compiler doesn't complain.\n  validateBoolean(exclusive, '');\n\n  // We're not current implementing verbatimSymlinks in any meaningful way.\n  // Our symlinks are always fully qualfied. That is, they always point to\n  // an absolute path and never to a relative path, so there is no distinction\n  // between verbatimSymlinks and non-verbatimSymlinks. We validate the option\n  // value above but otherwise we ignore it.\n\n  src = normalizePath(src);\n  dest = normalizePath(dest);\n\n  callWithErrorOnlyCallback(() => {\n    cffs.cp(src, dest, {\n      deferenceSymlinks: dereference,\n      recursive,\n      force,\n      errorOnExist,\n    });\n  }, callback);\n}\n\nexport function fchmod(\n  fd: number,\n  mode: string | number,\n  callback: ErrorOnlyCallback\n): void {\n  fd = getValidatedFd(fd);\n  parseFileMode(mode, 'mode');\n  callWithErrorOnlyCallback(() => {\n    fssync.fchmodSync(fd, mode);\n  }, callback);\n}\n\nexport function fchown(\n  fd: number,\n  uid: number,\n  gid: number,\n  callback: ErrorOnlyCallback\n): void {\n  const { pathOrFd } = validateChownArgs(fd, uid, gid);\n  callWithErrorOnlyCallback(() => {\n    if (cffs.stat(pathOrFd as URL, { followSymlinks: false }) == null) {\n      throw new ERR_EBADF({ syscall: 'fchown' });\n    }\n  }, callback);\n}\n\nexport function fdatasync(fd: number, callback: ErrorOnlyCallback): void {\n  getValidatedFd(fd);\n  callWithErrorOnlyCallback(() => {\n    fssync.fdatasyncSync(fd);\n  }, callback);\n}\n\nexport function fstat(\n  fd: number,\n  optionsOrCallback: SingleArgCallback<Stats> | FStatOptions,\n  callback?: SingleArgCallback<Stats>\n): void {\n  let options: FStatOptions;\n  if (typeof optionsOrCallback === 'function') {\n    callback = optionsOrCallback;\n    options = { bigint: false };\n  } else {\n    options = optionsOrCallback;\n  }\n  validateStatArgs(fd, options, true /* is fstat */);\n  callWithSingleArgCallback(() => fssync.fstatSync(fd, options), callback);\n}\n\nexport function fsync(\n  fd: number,\n  callback: ErrorOnlyCallback = () => {}\n): void {\n  getValidatedFd(fd);\n  callWithErrorOnlyCallback(() => {\n    fssync.fsyncSync(fd);\n  }, callback);\n}\n\nexport function ftruncate(\n  fd: number,\n  lenOrCallback: number | ErrorOnlyCallback,\n  callback?: ErrorOnlyCallback\n): void {\n  let len: number;\n  if (typeof lenOrCallback === 'function') {\n    callback = lenOrCallback;\n    len = 0;\n  } else {\n    len = lenOrCallback;\n  }\n  fd = getValidatedFd(fd);\n  validateUint32(len, 'len');\n  callWithErrorOnlyCallback(() => {\n    fssync.ftruncateSync(fd, len);\n  }, callback);\n}\n\nexport function futimes(\n  fd: number,\n  atime: RawTime | Date,\n  mtime: RawTime | Date,\n  callback: ErrorOnlyCallback\n): void {\n  fd = getValidatedFd(fd);\n  atime = getDate(atime);\n  mtime = getDate(mtime);\n  callWithErrorOnlyCallback(() => {\n    fssync.futimesSync(fd, atime, mtime);\n  }, callback);\n}\n\nexport function lchmod(\n  path: FilePath,\n  mode: string | number,\n  callback: ErrorOnlyCallback\n): void {\n  const { pathOrFd } = validateChmodArgs(path, mode);\n  callWithErrorOnlyCallback(() => {\n    if (cffs.stat(pathOrFd as URL, { followSymlinks: false }) == null) {\n      throw new ERR_ENOENT((pathOrFd as URL).pathname, { syscall: 'lchmod' });\n    }\n  }, callback);\n}\n\nexport function lchown(\n  path: FilePath,\n  uid: number,\n  gid: number,\n  callback: ErrorOnlyCallback\n): void {\n  const { pathOrFd } = validateChownArgs(path, uid, gid);\n  callWithErrorOnlyCallback(() => {\n    if (cffs.stat(pathOrFd as URL, { followSymlinks: false }) == null) {\n      throw new ERR_ENOENT((path as URL).pathname, { syscall: 'lchown' });\n    }\n  }, callback);\n}\n\nexport function lutimes(\n  path: FilePath,\n  atime: RawTime | Date,\n  mtime: RawTime | Date,\n  callback: ErrorOnlyCallback\n): void {\n  atime = getDate(atime);\n  mtime = getDate(mtime);\n  path = normalizePath(path);\n  callWithErrorOnlyCallback(() => {\n    fssync.lutimesSync(path, atime, mtime);\n  }, callback);\n}\n\nexport function link(\n  src: FilePath,\n  dest: FilePath,\n  callback: ErrorOnlyCallback\n): void {\n  const normalizedSrc = normalizePath(src);\n  const normalizedDest = normalizePath(dest);\n  callWithErrorOnlyCallback(() => {\n    fssync.linkSync(normalizedSrc, normalizedDest);\n  }, callback);\n}\n\nexport function lstat(\n  path: FilePath,\n  optionsOrCallback: SingleArgCallback<Stats | undefined> | StatOptions,\n  callback?: SingleArgCallback<Stats | undefined>\n): void {\n  let options: StatOptions;\n  if (typeof optionsOrCallback === 'function') {\n    callback = optionsOrCallback;\n    options = { bigint: false };\n  } else {\n    options = optionsOrCallback;\n  }\n  const {\n    pathOrFd: normalizedPath,\n    bigint,\n    throwIfNoEntry,\n  } = validateStatArgs(path, options);\n  callWithSingleArgCallback(\n    () =>\n      fssync.lstatSync(normalizedPath as FilePath, {\n        bigint,\n        throwIfNoEntry,\n      }),\n    callback\n  );\n}\n\nexport function mkdir(\n  path: FilePath,\n  optionsOrCallback:\n    | number\n    | SingleArgCallback<string | undefined>\n    | MakeDirectoryOptions,\n  callback?: SingleArgCallback<string | undefined>\n): void {\n  let options: number | MakeDirectoryOptions;\n  if (typeof optionsOrCallback === 'function') {\n    callback = optionsOrCallback;\n    options = {};\n  } else {\n    options = optionsOrCallback;\n  }\n  const { path: normalizedPath, recursive } = validateMkDirArgs(path, options);\n  callWithSingleArgCallback(\n    () => fssync.mkdirSync(normalizedPath, { recursive }),\n    callback\n  );\n}\n\nexport function mkdtemp(\n  prefix: FilePath,\n  optionsOrCallback:\n    | SingleArgCallback<string>\n    | MkdirTempSyncOptions\n    | ValidEncoding,\n  callback?: SingleArgCallback<string>\n): void {\n  let options: MkdirTempSyncOptions | ValidEncoding;\n  if (typeof optionsOrCallback === 'function') {\n    callback = optionsOrCallback;\n    options = {};\n  } else {\n    options = optionsOrCallback;\n  }\n  if (typeof options === 'string' || options == null) {\n    options = { encoding: options };\n  }\n  validateObject(options, 'options');\n  const { encoding = null } = options;\n  if (\n    encoding !== null &&\n    encoding !== 'buffer' &&\n    !Buffer.isEncoding(encoding)\n  ) {\n    throw new ERR_INVALID_ARG_VALUE('options.encoding', encoding);\n  }\n\n  callWithSingleArgCallback(\n    () => fssync.mkdtempSync(prefix, options),\n    callback\n  );\n}\n\nexport function open(\n  path: FilePath,\n  flagsOrCallback: string | number | SingleArgCallback<number> = 'r',\n  modeOrCallback: string | number | SingleArgCallback<number> = 0o666,\n  callback?: SingleArgCallback<number>\n): void {\n  let flags: string | number;\n  let mode: string | number;\n  if (typeof flagsOrCallback === 'function') {\n    callback = flagsOrCallback;\n    flags = 'r';\n    mode = 0o666;\n  } else if (typeof modeOrCallback === 'function') {\n    callback = modeOrCallback;\n    flags = flagsOrCallback;\n    mode = 0o666;\n  } else {\n    flags = flagsOrCallback;\n    mode = modeOrCallback;\n  }\n  path = normalizePath(path);\n  mode = parseFileMode(mode, 'mode');\n  flags = stringToFlags(flags);\n  callWithSingleArgCallback(() => fssync.openSync(path, flags, mode), callback);\n}\n\nexport function opendir(\n  path: FilePath,\n  optionsOrCallback: SingleArgCallback<Dir> | OpenDirOptions,\n  callback?: SingleArgCallback<Dir>\n): void {\n  let options: OpenDirOptions;\n  if (typeof optionsOrCallback === 'function') {\n    callback = optionsOrCallback;\n    options = {\n      encoding: 'utf8',\n      bufferSize: 32,\n      recursive: false,\n    };\n  } else {\n    options = optionsOrCallback;\n  }\n\n  const {\n    path: validatedPath,\n    encoding,\n    recursive,\n  } = validateOpendirArgs(path, options);\n\n  callWithSingleArgCallback(() => {\n    return fssync.opendirSync(validatedPath, {\n      encoding: encoding as BufferEncoding,\n      recursive,\n    });\n  }, callback);\n}\n\n// read has a complex polymorphic signature so this is a bit gnarly.\n// The various signatures include:\n//   fs.read(fd, buffer, offset, length, position, callback)\n//   fs.read(fd, callback)\n//   fs.read(fd, buffer, callback)\n//   fs.read(fd, buffer, { offset, length, position }, callback)\n//   fs.read(fd, { buffer, offset, length, position }, callback)\n//\n// Where fd is always a number, buffer is an ArrayBufferView, offset and\n// length are numbers, but position can be a number or bigint, and offset\n// length, and position are optional. The callback is always a function\n// that receives three arguments: err, bytesRead, and buffer.\nexport function read<T extends NodeJS.ArrayBufferView>(\n  fd: number,\n  bufferOptionsOrCallback:\n    | T\n    | ReadOptionsWithBuffer<T>\n    | DoubleArgCallback<number, T>,\n  offsetOptionsOrCallback?:\n    | ReadOptionsWithBuffer<T>\n    | number\n    | DoubleArgCallback<number, T>,\n  lengthOrCallback?: null | number | DoubleArgCallback<number, T>,\n  position?: Position,\n  callback?: DoubleArgCallback<number, T>\n): void {\n  // Node.js... you're killing me here with these polymorphic signatures.\n  //\n  // We're going to normalize the arguments so that we can defer to the\n  // readSync variant using the signature readSync(fd, buffer, options)\n  //\n  // The callback is always the last argument but may appear in the second,\n  // third, fourth, or sixth position depending on the signature used. When we\n  // find it, we can ignore the remaining arguments that come after it,\n  // defaulting any missing arguments to whatever default is defined for\n  // them.\n  //\n  // The second argument is always either a buffer, an options object that\n  // contains a buffer property, or the callback. If it's the callback,\n  // then we will allocate a new buffer for the read with size 16384 bytes,\n  // and default the offset to 0, length to the buffer size, and position to\n  // null (indicating that the internal read position for the fd is to be\n  // used). If it's an options object, we will use the buffer property from it\n  // if it exists. If the buffer property is not present, we will allocate a\n  // new buffer for the read with size 16384 bytes. The offset, length, and\n  // position properties will be used if they are present in the options\n  // object or defaulted to 0, the buffer size, and null respectively.\n  // If the second argument is a buffer, we will use it and look for the\n  // offset, length, position, and callback arguments in the remaining arguments.\n  //\n  // The third argument is either ignored (if the second argument is the\n  // callback), or it is one of either the offset, the options object, or\n  // the callback. If it is the callback, we will default the offset to 0,\n  // length to the buffer size (that had to have been provided by the second\n  // argument), and position to null. If it is the options object, we will\n  // get the offset, length, and position properties from it if they exist,\n  // or default them to 0, the buffer size, and null respectively. If it is\n  // the offset, we will look for the length, position, and callback in the\n  // remaining arguments.\n  //\n  // The fourth argument is either ignored (if the second or third argument is\n  // the callback), or it is the length as either a number, null, or explicitly\n  // passed as undefined, or it is the callback. If it is the callback, we will\n  // default the length to the buffer size (that had to have been provided by\n  // the second argument), and default position to null, then look for the\n  // callback in the sixth argument. If it is the length, we will look for the\n  // position and callback in the remaining arguments.\n  //\n  // The fifth argument is either ignored (if the callback has already been\n  // seen) or it is the position as either a number, bigint, null, or explicitly\n  // undefined. Any other type in this position is an error.\n  //\n  // The sixth argument is either ignored (if the callback has already been\n  // seen) or it is the callback. If it is not a function then an error is\n  // thrown.\n  //\n  // Once we have collected all of the arguments, we will call the readSync\n  // method with signature readSync(fd, buffer, { offset, length, position })\n  // and pass the return value, and the buffer, to the Node.js-style callback\n  // with the signature callback(null, returnValue, buffer). If the call throws,\n  // then we will pass the error to the callback as the first argument.\n\n  let actualCallback: undefined | DoubleArgCallback<number, T>;\n  let actualBuffer: T; // Buffer, TypedArray, or DataView\n  let actualOffset = 0; // Offset from the beginning of the buffer\n  let actualLength: number; // Length of the data to read into the buffer\n  // Should never be negative and never extends\n  // beyond the end of the buffer (that is,\n  // actualOffset + actualLength <= actualBuffer.byteLength)\n  let actualPosition: Position = null; // The position within the\n  // file to read from. If null,\n  // the current position for the fd\n  // is used.\n\n  // Handle the case where the second argument is the callback\n  if (typeof bufferOptionsOrCallback === 'function') {\n    actualCallback = bufferOptionsOrCallback;\n    // Default buffer size when not provided\n    // The use of as unknown as T here is a bit of a hack to satisfy the types...\n    actualBuffer = Buffer.alloc(16384) as unknown as T;\n    actualLength = actualBuffer.byteLength;\n  }\n  // Handle the case where the second argument is an options object\n  else if (\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    bufferOptionsOrCallback != null &&\n    typeof bufferOptionsOrCallback === 'object' &&\n    !isArrayBufferView(bufferOptionsOrCallback)\n  ) {\n    // It's an options object\n    const {\n      buffer = Buffer.alloc(16384),\n      offset = buffer.byteOffset,\n      length = buffer.byteLength,\n      position = null,\n    } = bufferOptionsOrCallback;\n    if (!isArrayBufferView(buffer)) {\n      throw new ERR_INVALID_ARG_TYPE(\n        'options.buffer',\n        ['Buffer', 'TypedArray', 'DataView'],\n        buffer\n      );\n    }\n    validateUint32(offset, 'options.offset');\n    validateUint32(length, 'options.length');\n    validatePosition(position, 'options.position');\n\n    actualBuffer = buffer as unknown as T;\n    actualOffset = offset;\n    actualLength = length;\n    actualPosition = position;\n\n    // The callback must be in the third argument\n    if (typeof offsetOptionsOrCallback !== 'function') {\n      throw new ERR_INVALID_ARG_TYPE(\n        'callback',\n        ['function'],\n        offsetOptionsOrCallback\n      );\n    }\n    actualCallback = offsetOptionsOrCallback;\n  }\n  // Handle the case where the second argument is a buffer\n  else {\n    actualBuffer = bufferOptionsOrCallback;\n\n    if (!isArrayBufferView(actualBuffer)) {\n      throw new ERR_INVALID_ARG_TYPE(\n        'buffer',\n        ['Buffer', 'TypedArray', 'DataView'],\n        actualBuffer\n      );\n    }\n\n    actualLength = actualBuffer.byteLength;\n    actualOffset = actualBuffer.byteOffset;\n\n    // Now we need to find the callback and other parameters\n    if (typeof offsetOptionsOrCallback === 'function') {\n      // fs.read(fd, buffer, callback)\n      actualCallback = offsetOptionsOrCallback;\n    } else if (\n      typeof offsetOptionsOrCallback === 'object' &&\n      !(offsetOptionsOrCallback instanceof Number)\n    ) {\n      // fs.read(fd, buffer, options, callback)\n      const {\n        offset = actualOffset,\n        length = actualLength,\n        position = null,\n      } = offsetOptionsOrCallback;\n      validateUint32(offset, 'options.offset');\n      validateUint32(length, 'options.length');\n      validatePosition(position, 'options.position');\n      actualOffset = offset;\n      actualLength = length;\n      actualPosition = position;\n\n      // The callback must be in the fourth argument.\n      if (typeof lengthOrCallback !== 'function') {\n        throw new ERR_INVALID_ARG_TYPE(\n          'callback',\n          ['function'],\n          lengthOrCallback\n        );\n      }\n      actualCallback = lengthOrCallback;\n    } else {\n      // fs.read(fd, buffer, offset, length, position, callback)\n      actualOffset =\n        typeof offsetOptionsOrCallback === 'number'\n          ? offsetOptionsOrCallback\n          : 0;\n\n      if (typeof lengthOrCallback === 'function') {\n        actualCallback = lengthOrCallback;\n        actualPosition = null;\n        actualLength = actualBuffer.byteLength;\n      } else {\n        actualLength = lengthOrCallback ?? actualBuffer.byteLength;\n\n        validateUint32(position, 'position');\n        actualPosition = position;\n\n        actualCallback = callback;\n      }\n    }\n  }\n\n  // We know that the function must be called with at least 3 arguments and\n  // that the first argument is always a number (the fd) and the last must\n  // always be the callback.\n  // If the actualCallback is not set at this point, then we have a problem.\n  if (typeof actualCallback !== 'function') {\n    throw new ERR_INVALID_ARG_TYPE('callback', ['function'], actualCallback);\n  }\n\n  // We also have a problem if the actualBuffer is not set here correctly.\n  if (!isArrayBufferView(actualBuffer)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'buffer',\n      ['Buffer', 'TypedArray', 'DataView'],\n      actualBuffer\n    );\n  }\n\n  // At this point we have the following:\n  // - actualBuffer: The buffer to read into\n  // - actualOffset: The offset into the buffer to start writing at\n  // - actualLength: The length of the data to read into the buffer\n  // - actualPosition: The position within the file to read from (or null)\n  // - actualCallback: The callback to call when done\n  // - fd: The file descriptor to read from\n  // Let actualOffset + actualLength should never be greater than the\n  // buffer size. Let's check that.\n  if (\n    actualOffset < 0 ||\n    actualLength < 0 ||\n    actualOffset + actualLength > actualBuffer.byteLength\n  ) {\n    throw new ERR_INVALID_ARG_VALUE(\n      'offset',\n      'must be >= 0 and <= buffer.length'\n    );\n  }\n  // The actualOffset, actualLength, and actualPosition values should always\n  // be greater or equal to 0 (unless actualPosition is null)... keeping in\n  // mind that actualPosition can be a number or a bigint.\n\n  // As a special case, if the actualBuffer length is 0, or if actualLength\n  // is 0, then can just call the callback with 0 bytes read and return.\n  if (actualBuffer.byteLength === 0 || actualLength === 0) {\n    queueMicrotask(() => {\n      actualCallback(null, 0, actualBuffer);\n    });\n    return;\n  }\n\n  // Now that we've normalized all the parameters, call readSync\n  try {\n    const bytesRead = fssync.readSync(fd, actualBuffer, {\n      offset: actualOffset,\n      length: actualLength,\n      position: actualPosition,\n    });\n    queueMicrotask(() => {\n      actualCallback(null, bytesRead, actualBuffer);\n    });\n  } catch (err) {\n    queueMicrotask(() => {\n      actualCallback(err);\n    });\n  }\n}\n\nexport function readdir(\n  path: FilePath,\n  optionsOrCallback:\n    | SingleArgCallback<ReadDirResult>\n    | ReadDirOptions\n    | ValidEncoding,\n  callback?: SingleArgCallback<ReadDirResult>\n): void {\n  let options: ReadDirOptions | ValidEncoding;\n  if (typeof optionsOrCallback === 'function') {\n    callback = optionsOrCallback;\n    options = {\n      encoding: 'utf8',\n      withFileTypes: false,\n      recursive: false,\n    };\n  } else {\n    options = optionsOrCallback;\n  }\n  const {\n    path: normalizedPath,\n    recursive,\n    withFileTypes,\n    encoding,\n  } = validateReaddirArgs(path, options);\n  callWithSingleArgCallback(() => {\n    return fssync.readdirSync(normalizedPath, {\n      recursive,\n      withFileTypes,\n      encoding,\n    });\n  }, callback);\n}\n\nexport function readFile(\n  path: FilePath,\n  optionsOrCallback:\n    | SingleArgCallback<string | Buffer>\n    | ValidEncoding\n    | ReadFileSyncOptions,\n  callback?: SingleArgCallback<string | Buffer>\n): void {\n  let options: ValidEncoding | ReadFileSyncOptions;\n  if (typeof optionsOrCallback === 'function') {\n    callback = optionsOrCallback;\n    options = {};\n  } else {\n    options = optionsOrCallback;\n  }\n\n  if (typeof options === 'string' || options == null) {\n    options = { encoding: options };\n  }\n  validateObject(options, 'options');\n  const { encoding = null } = options;\n  if (\n    encoding !== null &&\n    encoding !== 'buffer' &&\n    !Buffer.isEncoding(encoding)\n  ) {\n    throw new ERR_INVALID_ARG_VALUE('options.encoding', encoding);\n  }\n\n  callWithSingleArgCallback(() => fssync.readFileSync(path, options), callback);\n}\n\nexport function readlink(\n  path: FilePath,\n  optionsOrCallback:\n    | SingleArgCallback<string | Buffer>\n    | ValidEncoding\n    | ReadLinkSyncOptions,\n  callback?: SingleArgCallback<string | Buffer>\n): void {\n  let options: ValidEncoding | ReadLinkSyncOptions;\n  if (typeof optionsOrCallback === 'function') {\n    callback = optionsOrCallback;\n    options = {};\n  } else {\n    options = optionsOrCallback;\n  }\n  if (typeof options === 'string' || options == null) {\n    options = { encoding: options };\n  }\n  const normalizedPath = normalizePath(path);\n  validateObject(options, 'options');\n  const { encoding = 'utf8' } = options;\n  if (\n    encoding !== null &&\n    encoding !== 'buffer' &&\n    !Buffer.isEncoding(encoding)\n  ) {\n    throw new ERR_INVALID_ARG_VALUE('options.encoding', encoding);\n  }\n  callWithSingleArgCallback(\n    () => fssync.readlinkSync(normalizedPath, { encoding }),\n    callback\n  );\n}\n\nexport function readv<T extends NodeJS.ArrayBufferView>(\n  fd: number,\n  buffers: T[],\n  positionOrCallback: undefined | Position | DoubleArgCallback<number, T[]>,\n  callback?: DoubleArgCallback<number, T[]>\n): void {\n  if (typeof positionOrCallback === 'function') {\n    callback = positionOrCallback;\n    positionOrCallback = null;\n  }\n  if (typeof callback !== 'function') {\n    throw new ERR_INVALID_ARG_TYPE('callback', ['function'], callback);\n  }\n\n  validatePosition(positionOrCallback, 'position');\n\n  try {\n    const read = fssync.readvSync(fd, buffers, positionOrCallback);\n    queueMicrotask(() => {\n      callback(null, read, buffers);\n    });\n  } catch (err) {\n    queueMicrotask(() => {\n      callback(err);\n    });\n  }\n}\n\nexport function realpath(\n  path: FilePath,\n  optionsOrCallback:\n    | SingleArgCallback<string | Buffer>\n    | ValidEncoding\n    | ReadLinkSyncOptions,\n  callback?: SingleArgCallback<string | Buffer>\n): void {\n  let options: ValidEncoding | ReadLinkSyncOptions;\n  if (typeof optionsOrCallback === 'function') {\n    callback = optionsOrCallback;\n    options = {};\n  } else {\n    options = optionsOrCallback;\n  }\n\n  if (typeof options === 'string' || options == null) {\n    options = { encoding: options };\n  }\n\n  validateObject(options, 'options');\n  const { encoding = 'utf8' } = options;\n  if (\n    encoding !== null &&\n    encoding !== 'buffer' &&\n    !Buffer.isEncoding(encoding)\n  ) {\n    throw new ERR_INVALID_ARG_VALUE('options.encoding', encoding);\n  }\n\n  const normalizedPath = normalizePath(path);\n\n  callWithSingleArgCallback(\n    () => fssync.realpathSync(normalizedPath, { encoding }),\n    callback\n  );\n}\n\nrealpath.native = realpath;\n\nexport function rename(\n  oldPath: FilePath,\n  newPath: FilePath,\n  callback: ErrorOnlyCallback\n): void {\n  const normalizedOldPath = normalizePath(oldPath);\n  const normalizedNewPath = normalizePath(newPath);\n  callWithErrorOnlyCallback(() => {\n    fssync.renameSync(normalizedOldPath, normalizedNewPath);\n  }, callback);\n}\n\nexport function rmdir(\n  path: FilePath,\n  optionsOrCallback: ErrorOnlyCallback | RmDirOptions,\n  callback?: ErrorOnlyCallback\n): void {\n  let options: RmDirOptions;\n  let cb: ErrorOnlyCallback | undefined = callback;\n  if (typeof optionsOrCallback === 'function') {\n    cb = optionsOrCallback;\n    options = {};\n  } else {\n    options = optionsOrCallback;\n  }\n  const { path: normalizedPath, recursive } = validateRmDirArgs(path, options);\n  callWithErrorOnlyCallback(() => {\n    fssync.rmdirSync(normalizedPath, { recursive });\n  }, cb);\n}\n\nexport function rm(\n  path: FilePath,\n  optionsOrCallback: ErrorOnlyCallback | RmOptions,\n  callback?: ErrorOnlyCallback\n): void {\n  let options: RmOptions;\n  if (typeof optionsOrCallback === 'function') {\n    callback = optionsOrCallback;\n    options = {};\n  } else {\n    options = optionsOrCallback;\n  }\n  const {\n    path: normalizedPath,\n    recursive,\n    force,\n  } = validateRmArgs(path, options);\n  callWithErrorOnlyCallback(() => {\n    fssync.rmSync(normalizedPath, { recursive, force });\n  }, callback);\n}\n\nexport function stat(\n  path: FilePath,\n  optionsOrCallback: SingleArgCallback<Stats | undefined> | StatOptions,\n  callback?: SingleArgCallback<Stats | undefined>\n): void {\n  let options: StatOptions;\n  if (typeof optionsOrCallback === 'function') {\n    callback = optionsOrCallback;\n    options = { bigint: false };\n  } else {\n    options = optionsOrCallback;\n  }\n  const {\n    pathOrFd: normalizedPath,\n    bigint,\n    throwIfNoEntry,\n  } = validateStatArgs(path, options);\n  callWithSingleArgCallback(\n    () =>\n      fssync.statSync(normalizedPath as FilePath, {\n        bigint,\n        throwIfNoEntry,\n      }),\n    callback\n  );\n}\n\nexport function statfs(\n  path: FilePath,\n  optionsOrCallback:\n    | SingleArgCallback<StatsFs | BigIntStatsFs>\n    | { bigint?: boolean | undefined },\n  callback?: SingleArgCallback<StatsFs | BigIntStatsFs>\n): void {\n  let options: { bigint?: boolean | undefined };\n  if (typeof optionsOrCallback === 'function') {\n    callback = optionsOrCallback;\n    options = { bigint: false };\n  } else {\n    options = optionsOrCallback;\n  }\n  const normalizedPath = normalizePath(path);\n\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  if (options !== undefined) {\n    validateObject(options, 'options');\n    const { bigint } = options;\n    if (bigint !== undefined) {\n      validateBoolean(bigint, 'options.bigint');\n    }\n  }\n\n  callWithSingleArgCallback(\n    () => fssync.statfsSync(normalizedPath, options),\n    callback\n  );\n}\n\nexport function symlink(\n  target: FilePath,\n  path: FilePath,\n  typeOrCallback: SymlinkType | ErrorOnlyCallback,\n  callback?: ErrorOnlyCallback\n): void {\n  let type: SymlinkType;\n  if (typeof typeOrCallback === 'function') {\n    callback = typeOrCallback;\n    type = null;\n  } else {\n    type = typeOrCallback;\n  }\n  const normalizedTarget = normalizePath(target);\n  const normalizedPath = normalizePath(path);\n  if (type != null) {\n    validateOneOf(type, 'type', ['dir', 'file', 'junction', null]);\n  }\n  callWithErrorOnlyCallback(() => {\n    fssync.symlinkSync(normalizedTarget, normalizedPath, type);\n  }, callback);\n}\n\nexport function truncate(\n  path: FilePath,\n  lenOrCallback: number | ErrorOnlyCallback,\n  callback?: ErrorOnlyCallback\n): void {\n  let len: number;\n  if (typeof lenOrCallback === 'function') {\n    callback = lenOrCallback;\n    len = 0;\n  } else {\n    len = lenOrCallback;\n  }\n  const normalizedPath = normalizePath(path);\n  validateUint32(len, 'len');\n  callWithErrorOnlyCallback(() => {\n    fssync.truncateSync(normalizedPath, len);\n  }, callback);\n}\n\nexport function unlink(path: FilePath, callback: ErrorOnlyCallback): void {\n  const normalizedPath = normalizePath(path);\n  callWithErrorOnlyCallback(() => {\n    fssync.unlinkSync(normalizedPath);\n  }, callback);\n}\n\nexport function utimes(\n  path: FilePath,\n  atime: RawTime | Date,\n  mtime: RawTime | Date,\n  callback: ErrorOnlyCallback\n): void {\n  atime = getDate(atime);\n  mtime = getDate(mtime);\n  const normalizedPath = normalizePath(path);\n  callWithErrorOnlyCallback(() => {\n    fssync.utimesSync(normalizedPath, atime, mtime);\n  }, callback);\n}\n\nexport function write<T extends NodeJS.ArrayBufferView>(\n  fd: number,\n  buffer: T | string,\n  offsetOptionsPositionOrCallback?:\n    | WriteSyncOptions\n    | Position\n    | DoubleArgCallback<number, T>,\n  encodingLengthOrCallback?:\n    | number\n    | ValidEncoding\n    | DoubleArgCallback<number, T>,\n  positionOrCallback?: Position | DoubleArgCallback<number, T>,\n  callback?: DoubleArgCallback<number, T>\n): void {\n  let offsetOrOptions: WriteSyncOptions | Position | undefined;\n  let lengthOrEncoding: number | ValidEncoding | undefined;\n  let position: Position | undefined;\n  if (typeof offsetOptionsPositionOrCallback === 'function') {\n    callback = offsetOptionsPositionOrCallback;\n    offsetOrOptions = undefined;\n  } else {\n    offsetOrOptions = offsetOptionsPositionOrCallback;\n  }\n  if (typeof encodingLengthOrCallback === 'function') {\n    callback = encodingLengthOrCallback;\n    lengthOrEncoding = undefined;\n  } else {\n    lengthOrEncoding = encodingLengthOrCallback;\n  }\n  if (typeof positionOrCallback === 'function') {\n    callback = positionOrCallback;\n    position = undefined;\n  } else {\n    position = positionOrCallback;\n  }\n  if (typeof callback !== 'function') {\n    throw new ERR_INVALID_ARG_TYPE('callback', ['function'], callback);\n  }\n  // Because the callback expects the buffer to be returned in the callback,\n  // we need to make sure that the buffer is not a string here rather than\n  // relying on the transformation in the writeSync call.\n  if (typeof buffer === 'string') {\n    let encoding = 'utf8';\n    if (typeof lengthOrEncoding === 'string') {\n      encoding = lengthOrEncoding;\n      lengthOrEncoding = undefined;\n    }\n    buffer = Buffer.from(buffer, encoding) as unknown as T;\n  }\n\n  const {\n    fd: validatedFd,\n    buffer: actualBuffer,\n    position: actualPosition,\n  } = validateWriteArgs(\n    fd,\n    buffer,\n    offsetOrOptions,\n    lengthOrEncoding,\n    position\n  );\n\n  try {\n    const written = fssync.writevSync(\n      validatedFd,\n      actualBuffer,\n      actualPosition\n    );\n    queueMicrotask(() => {\n      callback(null, written, buffer as unknown as T);\n    });\n  } catch (err) {\n    queueMicrotask(() => {\n      callback(err);\n    });\n  }\n}\n\nexport function writeFile(\n  path: number | FilePath,\n  data: string | ArrayBufferView,\n  optionsOrCallback: ErrorOnlyCallback | ValidEncoding | WriteFileOptions,\n  callback?: ErrorOnlyCallback\n): void {\n  let options: ValidEncoding | WriteFileOptions;\n  if (typeof optionsOrCallback === 'function') {\n    callback = optionsOrCallback;\n    options = {\n      encoding: 'utf8',\n      mode: 0o666,\n      flag: 'w',\n      flush: false,\n    };\n  } else {\n    options = optionsOrCallback;\n  }\n\n  const {\n    path: validatedPath,\n    data: validatedData,\n    append,\n    exclusive,\n  } = validateWriteFileArgs(path, data, options);\n\n  callWithSingleArgCallback<number>(\n    () => cffs.writeAll(validatedPath, validatedData, { append, exclusive }),\n    callback\n  );\n}\n\nexport function writev<T extends NodeJS.ArrayBufferView>(\n  fd: number,\n  buffers: T[],\n  positionOrCallback?: Position | DoubleArgCallback<number, T[]>,\n  callback?: DoubleArgCallback<number, T[]>\n): void {\n  if (typeof positionOrCallback === 'function') {\n    callback = positionOrCallback;\n    positionOrCallback = null;\n  }\n  if (typeof callback !== 'function') {\n    throw new ERR_INVALID_ARG_TYPE('callback', ['function'], callback);\n  }\n\n  fd = getValidatedFd(fd);\n  validateBufferArray(buffers);\n  validatePosition(positionOrCallback, 'position');\n\n  try {\n    const written = fssync.writevSync(fd, buffers, positionOrCallback);\n    queueMicrotask(() => {\n      callback(null, written, buffers);\n    });\n  } catch (err) {\n    queueMicrotask(() => {\n      callback(err);\n    });\n  }\n}\n\nexport function unwatchFile(): void {\n  // We currently do not implement file watching.\n  throw new ERR_UNSUPPORTED_OPERATION();\n}\n\nexport function watch(): void {\n  // We currently do not implement file watching.\n  throw new ERR_UNSUPPORTED_OPERATION();\n}\n\nexport function watchFile(): void {\n  // We currently do not implement file watching.\n  throw new ERR_UNSUPPORTED_OPERATION();\n}\n\nexport function glob(\n  _pattern: string | readonly string[],\n  _options:\n    | GlobOptions\n    | GlobOptionsWithFileTypes\n    | GlobOptionsWithoutFileTypes,\n  _callback: ErrorOnlyCallback\n): void {\n  // We do not yet implement the globSync function. In Node.js, this\n  // function depends heavily on the third party minimatch library\n  // which is not yet available in the workers runtime. This will be\n  // explored for implementation separately in the future.\n  throw new ERR_UNSUPPORTED_OPERATION();\n}\n\n// An API is considered stubbed if it is not implemented by the function\n// exists with the correct signature and throws an error if called. If\n// a function exists that does not have the correct signature, it is\n// not considered fully stubbed.\n// An API is considered optimized if the API has been implemented and\n// tested and then optimized for performance.\n// Implemented APIs here are a bit different than in the sync version\n// since most of these are implemented in terms of calling the sync\n// version. We consider it implemented here if the code is present and\n// calls the sync api even if the sync api itself it not fully implemented.\n//\n// (S == Stubbed, I == Implemented, T == Tested, O == Optimized)\n//  S  I  T  O\n// [x][x][x][x] fs.access(path[, mode], callback)\n// [x][x][x][x] fs.chmod(path, mode, callback)\n// [x][x][x][x] fs.chown(path, uid, gid, callback)\n// [x][x][x][x] fs.exists(path, callback)\n// [x][x][x][x] fs.fchmod(fd, mode, callback)\n// [x][x][x][x] fs.fchown(fd, uid, gid, callback)\n// [x][x][x][x] fs.futimes(fd, atime, mtime, callback)\n// [x][x][x][x] fs.lchmod(path, mode, callback)\n// [x][x][x][x] fs.lchown(path, uid, gid, callback)\n// [x][x][x][x] fs.lutimes(path, atime, mtime, callback)\n// [x][x][x][x] fs.utimes(path, atime, mtime, callback)\n// [x][x][x][x] fs.fstat(fd[, options], callback)\n// [x][x][x][x] fs.lstat(path[, options], callback)\n// [x][x][x][x] fs.stat(path[, options], callback)\n// [x][x][x][x] fs.statfs(path[, options], callback)\n// [x][x][x][x] fs.fdatasync(fd, callback)\n// [x][x][x][x] fs.fsync(fd, callback)\n// [x][x][x][x] fs.link(existingPath, newPath, callback)\n// [x][x][x][x] fs.readlink(path[, options], callback)\n// [x][x][x][x] fs.realpath(path[, options], callback)\n// [x][x][x][x] fs.realpath.native(path[, options], callback)\n// [x][x][x][x] fs.symlink(target, path[, type], callback)\n// [x][x][x][x] fs.unlink(path, callback)\n// [x][x][x][x] fs.mkdir(path[, options], callback)\n// [x][x][x][x] fs.mkdtemp(prefix[, options], callback)\n// [x][x][x][x] fs.readdir(path[, options], callback)\n// [x][x][x][x] fs.rmdir(path[, options], callback)\n// [x][x][x][x] fs.rm(path[, options], callback)\n// [x][x][x][x] fs.appendFile(path, data[, options], callback)\n// [x][x][x][x] fs.close(fd[, callback])\n// [x][x][x][x] fs.copyFile(src, dest[, mode], callback)\n// [x][x][x][x] fs.ftruncate(fd[, len], callback)\n// [x][x][x][x] fs.open(path[, flags[, mode]], callback)\n// [x][x][x][x] fs.read(fd, buffer, offset, length, position, callback)\n// [x][x][x][x] fs.read(fd[, options], callback)\n// [x][x][x][x] fs.read(fd, buffer[, options], callback)\n// [x][x][x][x] fs.readFile(path[, options], callback)\n// [x][x][x][x] fs.readv(fd, buffers[, position], callback)\n// [x][x][x][x] fs.rename(oldPath, newPath, callback)\n// [x][x][x][x] fs.truncate(path[, len], callback)\n// [x][x][x][x] fs.write(fd, buffer, offset[, length[, position]], callback)\n// [x][x][x][x] fs.write(fd, buffer[, options], callback)\n// [x][x][x][x] fs.write(fd, string[, position[, encoding]], callback)\n// [x][x][x][x] fs.writeFile(file, data[, options], callback)\n// [x][x][x][x] fs.writev(fd, buffers[, position], callback)//\n// [x][x][x][x] fs.opendir(path[, options], callback)\n// [-][-][-][-] fs.unwatchFile(filename[, listener])\n// [-][-][-][-] fs.watch(filename[, options][, listener])\n// [-][-][-][-] fs.watchFile(filename[, options], listener)\n// [x][x][x][x] fs.cp(src, dest[, options], callback)\n//\n// [ ][ ][ ][ ] fs.createReadStream(path[, options])\n// [ ][ ][ ][ ] fs.createWriteStream(path[, options])\n//\n// [ ][ ][ ][ ] fs.glob(pattern[, options], callback)\n// [ ][ ][ ][ ] fs.openAsBlob(path[, options])\n"
  },
  {
    "path": "src/node/internal/internal_fs_constants.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nexport const UV_FS_SYMLINK_DIR = 1;\nexport const UV_FS_SYMLINK_JUNCTION = 2;\nexport const O_RDONLY = 0;\nexport const O_WRONLY = 1;\nexport const O_RDWR = 2;\nexport const UV_DIRENT_UNKNOWN = 0;\nexport const UV_DIRENT_FILE = 1;\nexport const UV_DIRENT_DIR = 2;\nexport const UV_DIRENT_LINK = 3;\nexport const UV_DIRENT_FIFO = 4;\nexport const UV_DIRENT_SOCKET = 5;\nexport const UV_DIRENT_CHAR = 6;\nexport const UV_DIRENT_BLOCK = 7;\nexport const EXTENSIONLESS_FORMAT_JAVASCRIPT = 0;\nexport const EXTENSIONLESS_FORMAT_WASM = 1;\nexport const S_IFMT = 61440;\nexport const S_IFREG = 32768;\nexport const S_IFDIR = 16384;\nexport const S_IFCHR = 8192;\nexport const S_IFBLK = 24576;\nexport const S_IFIFO = 4096;\nexport const S_IFLNK = 40960;\nexport const S_IFSOCK = 49152;\nexport const O_CREAT = 64;\nexport const O_EXCL = 128;\nexport const UV_FS_O_FILEMAP = 0;\nexport const O_NOCTTY = 256;\nexport const O_TRUNC = 512;\nexport const O_APPEND = 1024;\nexport const O_DIRECTORY = 65536;\nexport const O_NOATIME = 262144;\nexport const O_NOFOLLOW = 131072;\nexport const O_SYNC = 1052672;\nexport const O_DSYNC = 4096;\nexport const O_DIRECT = 16384;\nexport const O_NONBLOCK = 2048;\nexport const S_IRWXU = 448;\nexport const S_IRUSR = 256;\nexport const S_IWUSR = 128;\nexport const S_IXUSR = 64;\nexport const S_IRWXG = 56;\nexport const S_IRGRP = 32;\nexport const S_IWGRP = 16;\nexport const S_IXGRP = 8;\nexport const S_IRWXO = 7;\nexport const S_IROTH = 4;\nexport const S_IWOTH = 2;\nexport const S_IXOTH = 1;\nexport const F_OK = 0;\nexport const R_OK = 4;\nexport const W_OK = 2;\nexport const X_OK = 1;\nexport const UV_FS_COPYFILE_EXCL = 1;\nexport const COPYFILE_EXCL = UV_FS_COPYFILE_EXCL;\nexport const UV_FS_COPYFILE_FICLONE = 2;\nexport const COPYFILE_FICLONE = UV_FS_COPYFILE_FICLONE;\nexport const UV_FS_COPYFILE_FICLONE_FORCE = 4;\nexport const COPYFILE_FICLONE_FORCE = UV_FS_COPYFILE_FICLONE_FORCE;\n"
  },
  {
    "path": "src/node/internal/internal_fs_promises.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport * as fssync from 'node-internal:internal_fs_sync';\nimport { EventEmitter } from 'node-internal:events';\nimport { default as cffs } from 'cloudflare-internal:filesystem';\nimport type {\n  MkdirTempSyncOptions,\n  ReadDirResult,\n  ReadFileSyncOptions,\n  ReadLinkSyncOptions,\n  StatOptions,\n} from 'node-internal:internal_fs_sync';\nimport {\n  type ReadStream,\n  type ReadStreamOptions,\n  type WriteStream,\n  type WriteStreamOptions,\n  createReadStream,\n  createWriteStream,\n} from 'node-internal:internal_fs_streams';\nimport {\n  kBadge,\n  kFileHandle,\n  Stats,\n  validatePosition,\n  type Position,\n  type RawTime,\n  type SymlinkType,\n  type FilePath,\n  type ReadDirOptions,\n  type WriteSyncOptions,\n  type ValidEncoding,\n} from 'node-internal:internal_fs_utils';\nimport type { Dirent } from 'node-internal:internal_fs';\nimport { Buffer } from 'node-internal:internal_buffer';\nimport { type Dir } from 'node-internal:internal_fs';\nimport {\n  ERR_EBADF,\n  ERR_UNSUPPORTED_OPERATION,\n} from 'node-internal:internal_errors';\nimport {\n  validateBoolean,\n  validateObject,\n  validateUint32,\n} from 'node-internal:validators';\nimport * as constants from 'node-internal:internal_fs_constants';\nimport type {\n  BigIntStatsFs,\n  CopySyncOptions,\n  GlobOptions,\n  GlobOptionsWithFileTypes,\n  GlobOptionsWithoutFileTypes,\n  MakeDirectoryOptions,\n  OpenDirOptions,\n  ReadOptionsWithBuffer,\n  RmOptions,\n  StatsFs,\n  WriteFileOptions,\n} from 'node:fs';\nimport type { RmDirOptions } from 'node-internal:internal_fs_utils';\nimport type {\n  ReadableWebStreamOptions,\n  CreateReadStreamOptions,\n} from 'node:fs/promises';\nimport { isArrayBufferView } from 'node-internal:internal_types';\n\nexport class FileHandle extends EventEmitter {\n  // The FileHandle class is a wrapper around a file descriptor.\n  // When the #handle is cleared, the reference to the underlying\n  // file descriptor is dropped. The user is expected to call\n  // close() explicitly but if they do not, the file descriptor\n  // will still be closed when the underlying handle is garbage\n  // collected.\n  #fd: number | undefined;\n  #handle: cffs.FdHandle | undefined;\n  [kFileHandle] = true;\n\n  constructor(badge: symbol, fd: number) {\n    if (badge !== kBadge) {\n      throw new TypeError('Illegal constructor');\n    }\n    super();\n    this.#fd = fd;\n    this.#handle = cffs.getFdHandle(fd);\n  }\n\n  get fd(): number | undefined {\n    // The fd property will be undefined if the handle has been closed.\n    return this.#fd;\n  }\n\n  async appendFile(\n    data: string | ArrayBufferView,\n    options: WriteFileOptions = {}\n  ): Promise<void> {\n    if (this.#fd === undefined) {\n      throw new ERR_EBADF({ syscall: 'stat' });\n    }\n    await appendFile(this.#fd, data, options);\n  }\n\n  async chmod(mode: string | number): Promise<void> {\n    if (this.#fd === undefined) {\n      throw new ERR_EBADF({ syscall: 'stat' });\n    }\n    await fchmod(this.#fd, mode);\n  }\n\n  async chown(uid: number, gid: number): Promise<void> {\n    if (this.#fd === undefined) {\n      throw new ERR_EBADF({ syscall: 'stat' });\n    }\n    await fchown(this.#fd, uid, gid);\n  }\n\n  async datasync(): Promise<void> {\n    if (this.#fd === undefined) {\n      throw new ERR_EBADF({ syscall: 'stat' });\n    }\n    await fdatasync(this.#fd);\n  }\n\n  async sync(): Promise<void> {\n    if (this.#fd === undefined) {\n      throw new ERR_EBADF({ syscall: 'stat' });\n    }\n    await fsync(this.#fd);\n  }\n\n  read<T extends NodeJS.ArrayBufferView>(\n    bufferOrOptions: T | ReadOptionsWithBuffer<T> = {},\n    offsetOrOptions: number | ReadOptionsWithBuffer<T> = {},\n    length?: number,\n    position: Position = null\n  ): Promise<{ bytesRead: number; buffer: T }> {\n    try {\n      if (this.#fd === undefined) {\n        throw new ERR_EBADF({ syscall: 'stat' });\n      }\n\n      let options: ReadOptionsWithBuffer<T>;\n      if (isArrayBufferView(bufferOrOptions)) {\n        if (typeof offsetOrOptions === 'number') {\n          options = {\n            buffer: bufferOrOptions,\n            offset: offsetOrOptions,\n            length: length ?? bufferOrOptions.byteLength,\n            position: position ?? null,\n          };\n        } else {\n          options = {\n            buffer: bufferOrOptions,\n            ...offsetOrOptions,\n          };\n        }\n      } else {\n        options = bufferOrOptions;\n      }\n\n      const {\n        buffer = Buffer.alloc(16384),\n        offset: actualOffset = buffer.byteOffset,\n        length: actualLength = buffer.byteLength - buffer.byteOffset,\n        position: actualPosition = null,\n      } = options;\n\n      validateUint32(actualOffset, 'offset');\n      validateUint32(actualLength, 'length');\n      validatePosition(actualPosition, 'position');\n\n      const bytesRead = fssync.readSync(\n        this.#fd,\n        buffer as NodeJS.ArrayBufferView,\n        actualOffset,\n        actualLength,\n        actualPosition\n      );\n\n      return Promise.resolve({\n        bytesRead,\n        buffer: buffer as T,\n      });\n    } catch (err) {\n      return Promise.reject(err as Error);\n    }\n  }\n\n  readv<T extends NodeJS.ArrayBufferView>(\n    buffers: T[],\n    position: Position = null\n  ): Promise<{ bytesRead: number; buffers: T[] }> {\n    try {\n      if (this.#fd === undefined) {\n        throw new ERR_EBADF({ syscall: 'stat' });\n      }\n      const bytesRead = fssync.readvSync(this.#fd, buffers, position);\n      return Promise.resolve({\n        bytesRead,\n        buffers,\n      });\n    } catch (err) {\n      return Promise.reject(err as Error);\n    }\n  }\n\n  async readFile(\n    options: ValidEncoding | ReadFileSyncOptions = {}\n  ): Promise<string | Buffer> {\n    if (this.#fd === undefined) {\n      throw new ERR_EBADF({ syscall: 'stat' });\n    }\n    return await readFile(this.#fd, options);\n  }\n\n  readLines(_options: CreateReadStreamOptions = {}): void {\n    // TODO(node-fs): This method is not yet implemented. In Node.js,\n    // it depends on the readline module which has not yet been\n    // implemented in the workers runtime. We'll want to implement\n    // it first then come back and implement this method.\n    if (this.#fd === undefined) {\n      throw new ERR_EBADF({ syscall: 'stat' });\n    }\n    throw new Error('not implemented');\n  }\n\n  async stat(options: StatOptions = {}): Promise<Stats | undefined> {\n    if (this.#fd === undefined) {\n      throw new ERR_EBADF({ syscall: 'stat' });\n    }\n    return await fstat(this.#fd, options);\n  }\n\n  async truncate(len: number = 0): Promise<void> {\n    if (this.#fd === undefined) {\n      throw new ERR_EBADF({ syscall: 'stat' });\n    }\n    await ftruncate(this.#fd, len);\n  }\n\n  async utimes(atime: RawTime | Date, mtime: RawTime | Date): Promise<void> {\n    if (this.#fd === undefined) {\n      throw new ERR_EBADF({ syscall: 'stat' });\n    }\n    await futimes(this.#fd, atime, mtime);\n  }\n\n  write(\n    buffer: NodeJS.ArrayBufferView | string,\n    offsetPositionOrOptions: WriteSyncOptions | Position = null,\n    lengthOrEncoding?: number | ValidEncoding,\n    position: Position = null\n  ): Promise<{ bytesWritten: number; buffer: NodeJS.ArrayBufferView }> {\n    try {\n      if (this.#fd === undefined) {\n        throw new ERR_EBADF({ syscall: 'stat' });\n      }\n\n      if (typeof buffer === 'string') {\n        buffer = Buffer.from(buffer, lengthOrEncoding as string);\n      }\n\n      const bytesWritten = fssync.writeSync(\n        this.#fd,\n        buffer,\n        offsetPositionOrOptions,\n        lengthOrEncoding,\n        position\n      );\n\n      return Promise.resolve({\n        bytesWritten,\n        buffer,\n      });\n    } catch (err) {\n      return Promise.reject(err as Error);\n    }\n  }\n\n  writev(\n    buffers: NodeJS.ArrayBufferView[],\n    position: Position = null\n  ): Promise<{ bytesWritten: number; buffers: NodeJS.ArrayBufferView[] }> {\n    try {\n      if (this.#fd === undefined) {\n        throw new ERR_EBADF({ syscall: 'stat' });\n      }\n      const bytesWritten = fssync.writevSync(this.#fd, buffers, position);\n      return Promise.resolve({\n        bytesWritten,\n        buffers,\n      });\n    } catch (err) {\n      return Promise.reject(err as Error);\n    }\n  }\n\n  writeFile(\n    data: string | Buffer,\n    options: ValidEncoding | WriteFileOptions = {}\n  ): Promise<{ bytesWritten: number; buffer: Buffer }> {\n    try {\n      if (this.#fd === undefined) {\n        throw new ERR_EBADF({ syscall: 'stat' });\n      }\n      const bytesWritten = fssync.writeFileSync(this.#fd, data, options);\n      return Promise.resolve({\n        bytesWritten,\n        buffer: isArrayBufferView(data)\n          ? data\n          : Buffer.from(data, options as BufferEncoding),\n      });\n    } catch (err) {\n      return Promise.reject(err as Error);\n    }\n  }\n\n  close(): Promise<void> {\n    try {\n      this.#handle?.close();\n      this.#fd = undefined;\n      this.#handle = undefined;\n      (this as unknown as EventEmitter).emit('close');\n      return Promise.resolve();\n    } catch (err) {\n      return Promise.reject(err as Error);\n    }\n  }\n\n  async [Symbol.asyncDispose](): Promise<void> {\n    await this.close();\n  }\n\n  readableWebStream(\n    options: ReadableWebStreamOptions = {}\n  ): ReadableStream<Uint8Array> {\n    if (this.#fd === undefined) {\n      throw new ERR_EBADF({ syscall: 'stat' });\n    }\n    validateObject(options, 'options');\n    // Node.js actually defaults autoClose to false here because of backwards\n    // compatibility issues but will change to autoClose = true in a semver-major\n    // soon.\n    const { autoClose = true } = options;\n    validateBoolean(autoClose, 'options.autoClose');\n    return getReadableWebStream(this, { autoClose });\n  }\n\n  createReadStream(options: ReadStreamOptions = {}): ReadStream {\n    return createReadStream('', { ...options, fd: this });\n  }\n\n  createWriteStream(_options: WriteStreamOptions = {}): WriteStream {\n    return createWriteStream('', { ..._options, fd: this });\n  }\n}\n\nexport function access(\n  path: FilePath,\n  mode: number = constants.F_OK\n): Promise<void> {\n  // Unlike the callback version, which throws input validation errors synchronously,\n  // rather than forwarding them to the callback, the promise version throws a errors\n  // asynchronously by rejecting the promise. So we can rely on accessSync to do the\n  // input validation for us.\n  return Promise.try(() => {\n    fssync.accessSync(path, mode);\n  });\n}\n\nexport function appendFile(\n  path: number | FilePath,\n  data: string | ArrayBufferView,\n  options: WriteFileOptions = {}\n): Promise<void> {\n  return Promise.try(() => {\n    // While appendFileSync returns the number of bytes written,\n    // the promise version does not return anything when successful.\n    fssync.appendFileSync(path, data, options);\n  });\n}\n\nexport function chmod(path: FilePath, mode: number): Promise<void> {\n  return Promise.try(() => {\n    fssync.chmodSync(path, mode);\n  });\n}\n\nexport function chown(path: FilePath, uid: number, gid: number): Promise<void> {\n  return Promise.try(() => {\n    fssync.chownSync(path, uid, gid);\n  });\n}\n\nexport function copyFile(\n  src: FilePath,\n  dest: FilePath,\n  mode: number\n): Promise<void> {\n  return Promise.try(() => {\n    fssync.copyFileSync(src, dest, mode);\n  });\n}\n\nexport function cp(\n  src: FilePath,\n  dest: FilePath,\n  options: CopySyncOptions\n): Promise<void> {\n  return Promise.try(() => {\n    fssync.cpSync(src, dest, options);\n  });\n}\n\nfunction fchmod(fd: number, mode: string | number): Promise<void> {\n  return Promise.try(() => {\n    fssync.fchmodSync(fd, mode);\n  });\n}\n\nfunction fchown(fd: number, uid: number, gid: number): Promise<void> {\n  return Promise.try(() => {\n    fssync.fchownSync(fd, uid, gid);\n  });\n}\n\nfunction fdatasync(fd: number): Promise<void> {\n  return Promise.try(() => {\n    fssync.fdatasyncSync(fd);\n  });\n}\n\nfunction fsync(fd: number): Promise<void> {\n  return Promise.try(() => {\n    fssync.fsyncSync(fd);\n  });\n}\n\nfunction fstat(\n  fd: number,\n  options: StatOptions = {}\n): Promise<Stats | undefined> {\n  return Promise.try(() => {\n    return fssync.fstatSync(fd, options);\n  });\n}\n\nfunction ftruncate(fd: number, len: number = 0): Promise<void> {\n  return Promise.try(() => {\n    fssync.ftruncateSync(fd, len);\n  });\n}\n\nfunction futimes(\n  fd: number,\n  atime: RawTime | Date,\n  mtime: RawTime | Date\n): Promise<void> {\n  return Promise.try(() => {\n    fssync.futimesSync(fd, atime, mtime);\n  });\n}\n\nexport function lchmod(path: FilePath, mode: number): Promise<void> {\n  return Promise.try(() => {\n    fssync.lchmodSync(path, mode);\n  });\n}\n\nexport function lchown(\n  path: FilePath,\n  uid: number,\n  gid: number\n): Promise<void> {\n  return Promise.try(() => {\n    fssync.lchownSync(path, uid, gid);\n  });\n}\n\nexport function lutimes(\n  path: FilePath,\n  atime: RawTime | Date,\n  mtime: RawTime | Date\n): Promise<void> {\n  return Promise.try(() => {\n    fssync.lutimesSync(path, atime, mtime);\n  });\n}\n\nexport function link(existingPath: FilePath, newPath: FilePath): Promise<void> {\n  return Promise.try(() => {\n    fssync.linkSync(existingPath, newPath);\n  });\n}\n\nexport function lstat(\n  path: FilePath,\n  options: StatOptions = {}\n): Promise<Stats | undefined> {\n  return Promise.try(() => {\n    return fssync.lstatSync(path, options);\n  });\n}\n\nexport function mkdir(\n  path: FilePath,\n  options: number | MakeDirectoryOptions = {}\n): Promise<string | undefined> {\n  return Promise.try(() => fssync.mkdirSync(path, options));\n}\n\nexport function mkdtemp(\n  prefix: FilePath,\n  options: MkdirTempSyncOptions = {}\n): Promise<string> {\n  return Promise.try(() => fssync.mkdtempSync(prefix, options));\n}\n\nexport function open(\n  path: FilePath,\n  flags: number | string,\n  mode: number | string\n): Promise<FileHandle> {\n  return Promise.try(\n    () => new FileHandle(kBadge, fssync.openSync(path, flags, mode))\n  );\n}\n\nexport function opendir(\n  path: FilePath,\n  options: OpenDirOptions = {}\n): Promise<Dir> {\n  return Promise.try(() => fssync.opendirSync(path, options));\n}\n\nexport function readdir(\n  path: FilePath,\n  options: ReadDirOptions = {}\n): Promise<ReadDirResult> {\n  return Promise.try(() => fssync.readdirSync(path, options));\n}\n\nexport function readFile(\n  path: number | FilePath,\n  options: ValidEncoding | ReadFileSyncOptions = {}\n): Promise<string | Buffer> {\n  return Promise.try(() => fssync.readFileSync(path, options));\n}\n\nexport function readlink(\n  path: FilePath,\n  options: ValidEncoding | ReadLinkSyncOptions = {}\n): Promise<string | Buffer> {\n  return Promise.try(() => fssync.readlinkSync(path, options));\n}\n\nexport function realpath(\n  path: FilePath,\n  options: ValidEncoding | ReadLinkSyncOptions = {}\n): Promise<string | Buffer> {\n  return Promise.try(() => fssync.realpathSync(path, options));\n}\n\nexport function rename(oldPath: FilePath, newPath: FilePath): Promise<void> {\n  return Promise.try(() => {\n    fssync.renameSync(oldPath, newPath);\n  });\n}\n\nexport function rmdir(path: FilePath, options: RmDirOptions): Promise<void> {\n  return Promise.try(() => {\n    fssync.rmdirSync(path, options);\n  });\n}\n\nexport function rm(path: FilePath, options: RmOptions = {}): Promise<void> {\n  return Promise.try(() => {\n    fssync.rmSync(path, options);\n  });\n}\n\nexport function stat(\n  path: FilePath,\n  options: StatOptions = {}\n): Promise<Stats | undefined> {\n  return Promise.try(() => fssync.statSync(path, options));\n}\n\nexport function statfs(\n  path: FilePath,\n  options: { bigint?: boolean | undefined } = {}\n): Promise<StatsFs | BigIntStatsFs> {\n  return Promise.try(() => fssync.statfsSync(path, options));\n}\n\nexport function symlink(\n  target: FilePath,\n  path: FilePath,\n  type: SymlinkType = null\n): Promise<void> {\n  return Promise.try(() => {\n    fssync.symlinkSync(target, path, type);\n  });\n}\n\nexport function truncate(path: FilePath, len: number = 0): Promise<void> {\n  return Promise.try(() => {\n    fssync.truncateSync(path, len);\n  });\n}\n\nexport function unlink(path: FilePath): Promise<void> {\n  return Promise.try(() => {\n    fssync.unlinkSync(path);\n  });\n}\n\nexport function utimes(\n  path: FilePath,\n  atime: RawTime | Date,\n  mtime: RawTime | Date\n): Promise<void> {\n  return Promise.try(() => {\n    fssync.utimesSync(path, atime, mtime);\n  });\n}\n\nexport function watch(): Promise<void> {\n  // We do not implement the watch function.\n  throw new Error('Not implemented');\n}\n\nexport function writeFile(\n  path: number | FilePath,\n  data: string | ArrayBufferView,\n  options: ValidEncoding | WriteFileOptions = {}\n): Promise<void> {\n  return Promise.try(() => {\n    // While writeFileSync returns the number of bytes written,\n    // the promise version does not return anything when successful.\n    fssync.writeFileSync(path, data, options);\n  });\n}\n\nexport function glob(\n  _pattern: string | readonly string[],\n  _options:\n    | GlobOptions\n    | GlobOptionsWithFileTypes\n    | GlobOptionsWithoutFileTypes = {}\n): NodeJS.AsyncIterator<string | Dirent> {\n  // We do not yet implement the globSync function. In Node.js, this\n  // function depends heavily on the third party minimatch library\n  // which is not yet available in the workers runtime. This will be\n  // explored for implementation separately in the future.\n  throw new ERR_UNSUPPORTED_OPERATION();\n}\n\nfunction getReadableWebStream(\n  fh: FileHandle,\n  options: ReadableWebStreamOptions\n): ReadableStream<Uint8Array> {\n  const readFn = fh.read.bind(fh);\n  const { autoClose } = options;\n  let controller: ReadableByteStreamController;\n  const ondone = async (): Promise<void> => {\n    if (autoClose) await fh.close();\n  };\n\n  const readable = new ReadableStream({\n    type: 'bytes',\n    autoAllocateChunkSize: 16384,\n    start(c: ReadableByteStreamController): void {\n      controller = c;\n    },\n\n    async pull(controller: ReadableByteStreamController): Promise<void> {\n      const req = controller.byobRequest as ReadableStreamBYOBRequest;\n      const view = req.view;\n      const { bytesRead } = await readFn(\n        view as Uint8Array,\n        (view as Uint8Array).byteOffset,\n        (view as Uint8Array).byteLength\n      );\n\n      if (bytesRead === 0) {\n        controller.close();\n        await ondone();\n      }\n\n      req.respond(bytesRead);\n    },\n\n    async cancel(): Promise<void> {\n      await ondone();\n    },\n  });\n\n  fh.once('close', () => {\n    controller.close();\n  });\n\n  return readable;\n}\n"
  },
  {
    "path": "src/node/internal/internal_fs_streams.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { Readable } from 'node-internal:streams_readable';\nimport { Writable } from 'node-internal:streams_writable';\nimport { Buffer } from 'node-internal:internal_buffer';\nimport { type EventEmitter } from 'node-internal:events';\nimport {\n  normalizePath,\n  getValidatedFd,\n  isFileHandle,\n  type FilePath,\n  type Position,\n  type WriteSyncOptions,\n  type ValidEncoding,\n} from 'node-internal:internal_fs_utils';\nimport { toPathIfFileURL } from 'node-internal:internal_url';\n\nimport * as fs from 'node-internal:internal_fs_callback';\n\nimport {\n  parseFileMode,\n  validateAbortSignal,\n  validateBoolean,\n  validateFunction,\n  validateObject,\n  validateString,\n  validateUint32,\n  validateThisInternalField,\n} from 'node-internal:validators';\n\nimport type {\n  DoubleArgCallback,\n  SingleArgCallback,\n  ErrorOnlyCallback,\n  open as OpenType,\n  close as CloseType,\n  fsync as FsyncType,\n  read as ReadType,\n  write as WriteType,\n  writev as WritevType,\n} from 'node-internal:internal_fs_callback';\n\nimport type { FileHandle } from 'node-internal:internal_fs_promises';\n\nimport { errorOrDestroy } from 'node-internal:streams_destroy';\nimport { eos } from 'node-internal:streams_end_of_stream';\n\nimport {\n  ERR_INVALID_ARG_VALUE,\n  ERR_OUT_OF_RANGE,\n  ERR_MISSING_ARGS,\n  ERR_METHOD_NOT_IMPLEMENTED,\n  ERR_STREAM_DESTROYED,\n  ERR_SYSTEM_ERROR,\n} from 'node-internal:internal_errors';\n\nimport type { ReadAsyncOptions } from 'node:fs';\n\nexport interface FsOperations {\n  open?: typeof OpenType | undefined;\n  close?: typeof CloseType | undefined;\n  fsync?: typeof FsyncType | undefined;\n  read?: typeof ReadType | undefined;\n  write?: typeof WriteType | undefined;\n  writev?: typeof WritevType | undefined;\n}\n\nexport interface RealizedFsOperations {\n  open: typeof OpenType;\n  close: typeof CloseType;\n  fsync: typeof FsyncType;\n  read: typeof ReadType;\n  write: typeof WriteType;\n  writev: typeof WritevType;\n}\n\n// Temporary while developing...\n/* eslint-disable */\n\nlet lazyFs: RealizedFsOperations | undefined;\nasync function getLazyFs(): Promise<RealizedFsOperations> {\n  if (lazyFs == undefined) {\n    lazyFs = {\n      open: (...args): void => {\n        fs.open(...args);\n      },\n      close: (...args): void => {\n        fs.close(...args);\n      },\n      fsync: (...args): void => {\n        fs.fsync(...args);\n      },\n      read: (...args): void => {\n        fs.read(...args);\n      },\n      write: (...args): void => {\n        fs.write(...args);\n      },\n      writev: (...args): void => {\n        fs.writev(...args);\n      },\n    };\n  }\n  return lazyFs;\n}\n\nconst kDefaultFsOperations: RealizedFsOperations = {\n  open(\n    path: FilePath,\n    flags: string | number | SingleArgCallback<number> = 'r',\n    mode: string | number | SingleArgCallback<number> = 0o666,\n    cb?: SingleArgCallback<number>\n  ): void {\n    let callback: SingleArgCallback<number>;\n    if (typeof flags === 'function') {\n      callback = flags;\n    } else if (typeof mode === 'function') {\n      callback = mode;\n    } else if (typeof cb === 'function') {\n      callback = cb;\n    } else {\n      throw new ERR_MISSING_ARGS('fs.open callback');\n    }\n    validateFunction(callback, 'fs.open callback');\n\n    getLazyFs().then(\n      (fs: RealizedFsOperations) => {\n        fs.open(path, (err: unknown, fd: number | undefined) => {\n          if (err) {\n            try {\n              callback(err);\n            } catch (e: unknown) {\n              reportError(e);\n            }\n            return;\n          }\n          try {\n            callback(null, fd);\n          } catch (e: unknown) {\n            reportError(e);\n          }\n        });\n      },\n      (err: unknown) => {\n        try {\n          callback(err);\n        } catch (e: unknown) {\n          reportError(e);\n        }\n      }\n    );\n  },\n  close(fd: number, cb: ErrorOnlyCallback = () => {}): void {\n    getLazyFs().then(\n      (fs: RealizedFsOperations) => {\n        fs.close(fd, (err: unknown) => {\n          if (err) {\n            try {\n              cb(err);\n            } catch (e: unknown) {\n              reportError(e);\n            }\n            return;\n          }\n          try {\n            cb(null);\n          } catch (e: unknown) {\n            reportError(e);\n          }\n        });\n      },\n      (err: unknown) => {\n        try {\n          cb(err);\n        } catch (e: unknown) {\n          reportError(e);\n        }\n      }\n    );\n  },\n  fsync(fd: number, cb: ErrorOnlyCallback = () => {}): void {\n    getLazyFs().then(\n      (fs: RealizedFsOperations) => {\n        fs.fsync(fd, (err: unknown) => {\n          if (err) {\n            try {\n              cb(err);\n            } catch (e: unknown) {\n              reportError(e);\n            }\n            return;\n          }\n          try {\n            cb(null);\n          } catch (e: unknown) {\n            reportError(e);\n          }\n        });\n      },\n      (err: unknown) => {\n        try {\n          cb(err);\n        } catch (e: unknown) {\n          reportError(e);\n        }\n      }\n    );\n  },\n  read<T extends NodeJS.ArrayBufferView>(\n    fd: number,\n    buffer: T | ReadAsyncOptions<T> | DoubleArgCallback<number, T>,\n    offset?: ReadAsyncOptions<T> | number | DoubleArgCallback<number, T>,\n    length?: null | number | DoubleArgCallback<number, T>,\n    position?: Position,\n    cb?: DoubleArgCallback<number, T>\n  ): void {\n    getLazyFs().then(\n      (fs: RealizedFsOperations) => {\n        fs.read(\n          fd,\n          buffer,\n          offset,\n          length,\n          position,\n          (\n            err: unknown,\n            bytesRead: number | undefined,\n            buffer: T | undefined\n          ) => {\n            if (err) {\n              try {\n                cb?.(err);\n              } catch (e: unknown) {\n                reportError(e);\n              }\n              return;\n            }\n            try {\n              cb?.(null, bytesRead, buffer);\n            } catch (e: unknown) {\n              reportError(e);\n            }\n          }\n        );\n      },\n      (err: unknown) => {\n        try {\n          cb?.(err);\n        } catch (e: unknown) {\n          reportError(e);\n        }\n      }\n    );\n  },\n  write<T extends NodeJS.ArrayBufferView>(\n    fd: number,\n    buffer: T | string,\n    offset?: WriteSyncOptions | Position | DoubleArgCallback<number, T>,\n    length?: number | ValidEncoding | DoubleArgCallback<number, T>,\n    position?: Position | DoubleArgCallback<number, T>,\n    cb?: DoubleArgCallback<number, T>\n  ): void {\n    getLazyFs().then(\n      (fs: RealizedFsOperations) => {\n        fs.write(\n          fd,\n          buffer,\n          offset,\n          length,\n          position,\n          (\n            err: unknown,\n            bytesWritten: number | undefined,\n            buffer: T | undefined\n          ) => {\n            if (err) {\n              try {\n                cb?.(err);\n              } catch (e: unknown) {\n                reportError(e);\n              }\n              return;\n            }\n            try {\n              cb?.(null, bytesWritten, buffer);\n            } catch (e: unknown) {\n              reportError(e);\n            }\n          }\n        );\n      },\n      (err: unknown) => {\n        try {\n          cb?.(err);\n        } catch (e: unknown) {\n          reportError(e);\n        }\n      }\n    );\n  },\n  writev<T extends NodeJS.ArrayBufferView>(\n    fd: number,\n    buffers: T[],\n    position?: Position | SingleArgCallback<number>,\n    cb?: DoubleArgCallback<number, T[]>\n  ): void {\n    getLazyFs().then(\n      (fs: RealizedFsOperations) => {\n        fs.writev(\n          fd,\n          buffers,\n          position,\n          (err: unknown, bytesWritten?: number, buffers?: T[]) => {\n            if (err) {\n              try {\n                cb?.(err);\n              } catch (e: unknown) {\n                reportError(e);\n              }\n              return;\n            }\n            try {\n              cb?.(null, bytesWritten, buffers);\n            } catch (e: unknown) {\n              reportError(e);\n            }\n          }\n        );\n      },\n      (err: unknown) => {\n        try {\n          cb?.(err);\n        } catch (e: unknown) {\n          reportError(e);\n        }\n      }\n    );\n  },\n};\n\nexport type ReadStreamOptions = {\n  encoding?: string | undefined;\n  autoClose?: boolean | undefined;\n  autoDestroy?: boolean | undefined;\n  emitClose?: boolean | undefined;\n  start?: number | undefined;\n  end?: number | undefined;\n  highWaterMark?: number | undefined;\n  signal?: AbortSignal | undefined;\n  flags?: string | undefined;\n  fd?: number | FileHandle | undefined;\n  mode?: number | undefined;\n  fs?: FsOperations | undefined;\n};\n\nconst kFs = Symbol('kFs');\nconst kIsPerformingIO = Symbol('kIsPerformingIO');\nconst kIoDone = Symbol('kIoDone');\nconst kHandle = Symbol('kHandle');\n\n// @ts-expect-error TS2323 Cannot redeclare.\nexport declare class ReadStream extends Readable {\n  fd: number | null;\n  flags: string;\n  path: string;\n  mode: number;\n  start: number;\n  end: number;\n  pos: number | undefined;\n  bytesRead: number;\n  flush: boolean;\n  [kFs]: RealizedFsOperations;\n  [kIsPerformingIO]: boolean;\n  [kHandle]: FileHandle | undefined;\n  constructor(path: FilePath | null, options?: ReadStreamOptions);\n  push(chunk: NodeJS.ArrayBufferView | null): boolean;\n  close(callback?: ErrorOnlyCallback): void;\n}\n\nfunction construct(\n  this: ReadStream | WriteStream,\n  callback: (err: unknown) => void\n): void {\n  const stream = this;\n  if (typeof stream.fd === 'number') {\n    callback(null);\n    return;\n  }\n\n  const ee = stream as unknown as EventEmitter;\n\n  if (typeof (stream as any).open === 'function') {\n    // Backwards compat for monkey patching open().\n    const orgEmit = ee.emit;\n    ee.emit = function (...args): boolean {\n      if (args[0] === 'open') {\n        this.emit = orgEmit;\n        callback(null);\n        Reflect.apply(orgEmit, this, args);\n      } else if (args[0] === 'error') {\n        this.emit = orgEmit;\n        callback(args[1]);\n      } else {\n        Reflect.apply(orgEmit, this, args);\n      }\n      return true;\n    };\n    (stream as any).open();\n    return;\n  }\n  stream[kFs].open(stream.path, (er: unknown, fd: number | undefined) => {\n    if (er) {\n      callback(er);\n      return;\n    }\n    if (fd === undefined) {\n      callback(new ERR_INVALID_ARG_VALUE('fd', 'undefined'));\n      return;\n    }\n    stream.fd = fd;\n    callback(null);\n    ee.emit('open', stream.fd);\n    ee.emit('ready');\n  });\n}\n\nfunction getValidatedFsOptions(fs: FsOperations): RealizedFsOperations {\n  validateObject(fs, 'options.fs');\n\n  if (isFileHandle(fs)) {\n    const handle = fs as unknown as FileHandle;\n    const open = function (..._args: unknown[]): void {\n      throw new ERR_METHOD_NOT_IMPLEMENTED('open()');\n    };\n    const close = function (...args: unknown[]): void {\n      const cb = args[args.length - 1] as ErrorOnlyCallback;\n      handle.close().then(\n        () => cb(null),\n        (err: unknown) => cb(err)\n      );\n    };\n    const fsync = function (...args: unknown[]): void {\n      const cb = args[args.length - 1] as ErrorOnlyCallback;\n      handle.sync().then(\n        () => cb(null),\n        (err: unknown) => cb(err)\n      );\n    };\n    const read = function (...args: unknown[]): void {\n      const cb = args[args.length - 1] as DoubleArgCallback<\n        number,\n        NodeJS.ArrayBufferView\n      >;\n      // @ts-expect-error TS2345\n      handle.read(...args.slice(1, -1)).then(\n        (result: { bytesRead: number; buffer: NodeJS.ArrayBufferView }) => {\n          cb(null, result.bytesRead, result.buffer);\n        },\n        (err: unknown) => cb(err)\n      );\n    };\n    const write = function (...args: unknown[]): void {\n      const cb = args[args.length - 1] as DoubleArgCallback<\n        number,\n        NodeJS.ArrayBufferView\n      >;\n      // @ts-expect-error TS2556\n      handle.write(...args.slice(1, -1)).then(\n        (result: { bytesWritten: number; buffer: NodeJS.ArrayBufferView }) => {\n          cb(null, result.bytesWritten, result.buffer);\n        },\n        (err: unknown) => cb(err)\n      );\n    };\n    const writev = function (...args: unknown[]): void {\n      const cb = args[args.length - 1] as DoubleArgCallback<\n        number,\n        NodeJS.ArrayBufferView[]\n      >;\n      // @ts-expect-error TS2556\n      handle.writev(...args.slice(1, -1)).then(\n        (result: {\n          bytesWritten: number;\n          buffers: NodeJS.ArrayBufferView[];\n        }) => {\n          cb(null, result.bytesWritten, result.buffers);\n        },\n        (err: unknown) => cb(err)\n      );\n    };\n    return {\n      open,\n      close,\n      fsync,\n      read,\n      write,\n      writev,\n    };\n  }\n\n  let {\n    open = kDefaultFsOperations.open,\n    close = kDefaultFsOperations.close,\n    fsync = kDefaultFsOperations.fsync,\n    read = kDefaultFsOperations.read,\n    write = kDefaultFsOperations.write,\n    writev = kDefaultFsOperations.writev,\n  } = fs as FsOperations;\n\n  validateFunction(open, 'options.fs.open');\n  validateFunction(read, 'options.fs.read');\n  validateFunction(close, 'options.fs.close');\n  validateFunction(fsync, 'options.fs.fsync');\n  validateFunction(write, 'options.fs.write');\n  validateFunction(writev, 'options.fs.writev');\n  return { open, close, fsync, read, write, writev };\n}\n\nfunction readImpl(this: ReadStream, n: number): void {\n  n =\n    this.pos !== undefined\n      ? Math.min(this.end - this.pos + 1, n)\n      : Math.min(this.end - this.bytesRead + 1, n);\n  if (n <= 0) {\n    this.push(null);\n    return;\n  }\n\n  const buf = Buffer.allocUnsafeSlow(n);\n  const ee = this as unknown as EventEmitter;\n\n  this[kIsPerformingIO] = true;\n  if (this.fd == null) {\n    this.push(null);\n    return;\n  }\n  this[kFs].read(this.fd, buf, 0, n, this.pos, (er, bytesRead, buf) => {\n    this[kIsPerformingIO] = false;\n\n    // Tell ._destroy() that it's safe to close the fd now.\n    if (this.destroyed) {\n      ee.emit(kIoDone, er);\n      return;\n    }\n\n    if (er) {\n      errorOrDestroy(this, er as Error);\n      return;\n    }\n\n    if (buf == null) {\n      errorOrDestroy(this, new ERR_INVALID_ARG_VALUE('buf', 'null'));\n      return;\n    }\n\n    if (bytesRead != null && bytesRead > 0) {\n      if (this.pos !== undefined) {\n        this.pos += bytesRead;\n      }\n\n      this.bytesRead += bytesRead;\n\n      if (bytesRead !== buf.byteLength) {\n        // Slow path. Shrink to fit.\n        // Copy instead of slice so that we don't retain\n        // large backing buffer for small reads.\n        const dst = Buffer.allocUnsafeSlow(bytesRead);\n        if (Buffer.isBuffer(buf)) {\n          (buf as Buffer).copy(dst, 0, 0, bytesRead);\n        } else {\n          const buffer = Buffer.from(buf.buffer, buf.byteOffset, bytesRead);\n          buffer.copy(dst, 0, 0, bytesRead);\n        }\n        buf = dst;\n      }\n\n      this.push(buf);\n    } else {\n      this.push(null);\n    }\n  });\n}\n\nfunction actualCloseImpl(\n  stream: ReadStream | WriteStream,\n  err: unknown,\n  cb: ErrorOnlyCallback\n): void {\n  if (stream.fd == null) return;\n  stream[kFs].close(stream.fd, (er) => {\n    cb(er || err);\n  });\n  stream.fd = null;\n}\n\nfunction closeImpl(\n  stream: ReadStream | WriteStream,\n  err: unknown,\n  cb: ErrorOnlyCallback\n): void {\n  if (stream.fd == null) {\n    cb(err);\n  } else if (stream.flush) {\n    stream[kFs].fsync(stream.fd, (flushErr) => {\n      actualCloseImpl(stream, err || flushErr, cb);\n    });\n  } else {\n    actualCloseImpl(stream, err, cb);\n  }\n}\n\nfunction destroyImpl(\n  this: ReadStream | WriteStream,\n  err: unknown,\n  cb: ErrorOnlyCallback\n): void {\n  // Usually for async IO it is safe to close a file descriptor\n  // even when there are pending operations. However, due to platform\n  // differences file IO is implemented using synchronous operations\n  // running in a thread pool. Therefore, file descriptors are not safe\n  // to close while used in a pending read or write operation. Wait for\n  // any pending IO (kIsPerformingIO) to complete (kIoDone).\n  const ee = this as unknown as EventEmitter;\n  if (this[kIsPerformingIO]) {\n    ee.once(kIoDone, (er) => {\n      closeImpl(this, err || er, cb);\n    });\n  } else {\n    closeImpl(this, err, cb);\n  }\n}\n\n// @ts-expect-error TS2323 Cannot redeclare.\nexport function ReadStream(\n  this: ReadStream,\n  path: FilePath,\n  options: ReadStreamOptions = {}\n): ReadStream {\n  if (!(this instanceof ReadStream)) {\n    return new ReadStream(path, options);\n  }\n  if (options === null) {\n    options = {};\n  } else if (typeof options === 'string') {\n    options = { encoding: options };\n  }\n\n  validateObject(options, 'options');\n  const {\n    encoding = null,\n    autoClose = true,\n    emitClose = true,\n    start = 0,\n    end = Infinity,\n    highWaterMark = 64 * 1024,\n    signal = null,\n    flags = 'r',\n    fd = null,\n    mode = 0o666,\n    fs = kDefaultFsOperations,\n  } = options;\n  const autoDestroy = autoClose;\n\n  if (\n    encoding !== 'buffer' &&\n    encoding !== null &&\n    !Buffer.isEncoding(encoding)\n  ) {\n    throw new ERR_INVALID_ARG_VALUE('options.encoding', encoding);\n  }\n  validateBoolean(autoClose, 'options.autoClose');\n  validateBoolean(emitClose, 'options.emitClose');\n  validateUint32(start, 'options.start');\n  if (end !== Infinity) {\n    validateUint32(end, 'options.end');\n    if (start > end) {\n      throw new ERR_OUT_OF_RANGE('start', `<= \"end\" (here: ${end})`, start);\n    }\n  }\n  validateUint32(highWaterMark, 'options.highWaterMark');\n  if (signal != null) {\n    validateAbortSignal(signal, 'options.signal');\n  }\n\n  validateString(flags, 'options.flags');\n\n  // We don't actually use the mode in our implementation. Parsing it is\n  // just to ensure that it is validated.\n  parseFileMode(mode, 'options.mode', 0o666);\n\n  this[kFs] = getValidatedFsOptions(fs);\n\n  if (fd == null) {\n    this.fd = null;\n    // Path will be ignored when fd is specified, so it can be falsy\n    this.path = toPathIfFileURL(normalizePath(path));\n    this.flags = options.flags === undefined ? 'r' : options.flags;\n    this.mode = options.mode === undefined ? 0o666 : options.mode;\n  } else {\n    if (isFileHandle(fd)) {\n      if (fs !== kDefaultFsOperations) {\n        throw new ERR_METHOD_NOT_IMPLEMENTED('FileHandle with fs');\n      }\n      this[kHandle] = fd as FileHandle;\n      this[kFs] = getValidatedFsOptions(fd as unknown as FsOperations);\n      const ee = fd as unknown as EventEmitter;\n      ee.on('close', () => this.close());\n      this.fd = (fd as FileHandle).fd || null;\n    } else {\n      this.fd = getValidatedFd(fd as number);\n    }\n  }\n\n  this.start = start;\n  this.end = end;\n  this.pos = start;\n  this.bytesRead = 0;\n  this[kIsPerformingIO] = false;\n\n  Reflect.apply(Readable, this, [\n    {\n      highWaterMark,\n      encoding,\n      emitClose,\n      autoDestroy,\n      signal,\n      construct: construct.bind(this),\n      read: readImpl.bind(this),\n      destroy: destroyImpl.bind(this),\n    },\n  ]);\n  return this;\n}\nObject.setPrototypeOf(ReadStream.prototype, Readable.prototype);\nObject.setPrototypeOf(ReadStream, Readable);\n\nObject.defineProperty(ReadStream.prototype, 'autoClose', {\n  get(this: ReadStream): boolean {\n    validateThisInternalField(this, kFs, 'ReadStream');\n    return this._readableState?.autoDestroy || false;\n  },\n  set(this: ReadStream, _val: boolean): void {\n    validateThisInternalField(this, kFs, 'ReadStream');\n    if (this._readableState !== undefined) {\n      this._readableState.autoDestroy = _val;\n    }\n  },\n});\n\nReadStream.prototype.close = function (\n  cb: ErrorOnlyCallback = (_err: unknown): void => {}\n): void {\n  if (typeof cb === 'function') eos(this, cb);\n  this.destroy();\n};\n\nObject.defineProperty(ReadStream.prototype, 'pending', {\n  get(this: ReadStream): boolean {\n    return this.fd === null;\n  },\n  configurable: true,\n});\n\n// ======================================================================================\n\nexport type WriteStreamOptions = {\n  encoding?: string | undefined;\n  autoClose?: boolean | undefined;\n  autoDestroy?: boolean | undefined;\n  emitClose?: boolean | undefined;\n  start?: number | undefined;\n  highWaterMark?: number | undefined;\n  signal?: AbortSignal | undefined;\n  flags?: string | undefined;\n  fd?: number | FileHandle | undefined;\n  mode?: number | undefined;\n  fs?: FsOperations | undefined;\n  flush?: boolean | undefined;\n};\n\ndeclare type WriteVChunk = {\n  chunk: string | NodeJS.ArrayBufferView;\n  encoding: ValidEncoding;\n};\n\n// @ts-expect-error TS2323 Cannot redeclare.\nexport declare class WriteStream extends Writable {\n  fd: number | null;\n  path: string;\n  flags: string;\n  mode: number;\n  flush: boolean;\n  autoClose: boolean;\n  destroyed: boolean;\n  start: number;\n  pos: number;\n  bytesRead: number;\n  bytesWritten: number;\n  [kIsPerformingIO]: boolean;\n  [kFs]: RealizedFsOperations;\n  [kHandle]?: FileHandle;\n  constructor(path: FilePath, options?: WriteStreamOptions);\n  close(cb?: ErrorOnlyCallback): void;\n  destroySoon(): void;\n}\n\nfunction writeAll(\n  this: WriteStream,\n  data: string | NodeJS.ArrayBufferView,\n  size: number,\n  pos: number,\n  cb: ErrorOnlyCallback,\n  retries = 0\n) {\n  if (this.fd == null) {\n    return cb(new ERR_INVALID_ARG_VALUE('fd', 'null'));\n  }\n\n  this[kFs].write(\n    this.fd,\n    data,\n    0,\n    size,\n    pos,\n    (\n      er: unknown,\n      bytesWritten?: number,\n      buffer?: string | NodeJS.ArrayBufferView\n    ) => {\n      // No data currently available and operation should be retried later.\n      if ((er as any)?.code === 'EAGAIN') {\n        er = null;\n        bytesWritten = 0;\n      }\n\n      if (this.destroyed || er) {\n        return cb(er || new ERR_STREAM_DESTROYED('write'));\n      }\n\n      // The value should be set but let's suppress the possible\n      // typescript error here.\n      bytesWritten = bytesWritten ?? 0;\n\n      this.bytesWritten += bytesWritten;\n\n      retries = bytesWritten ? 0 : retries + 1;\n      size -= bytesWritten;\n      pos += bytesWritten;\n\n      // Try writing non-zero number of bytes up to 5 times.\n      if (retries > 5) {\n        cb(new ERR_SYSTEM_ERROR('write failed'));\n      } else if (size) {\n        if (buffer == null) {\n          cb(null);\n          return;\n        }\n        const buf =\n          typeof buffer === 'string'\n            ? Buffer.from(buffer as string)\n            : Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);\n        writeAll.call(this, buf.slice(bytesWritten), size, pos, cb, retries);\n      } else {\n        cb(null);\n      }\n    }\n  );\n}\n\nfunction writevAll(\n  this: WriteStream,\n  chunks: NodeJS.ArrayBufferView[],\n  size: number,\n  pos: number,\n  cb: ErrorOnlyCallback,\n  retries = 0\n) {\n  if (this.fd == null) {\n    return cb(new ERR_INVALID_ARG_VALUE('fd', 'null'));\n  }\n  this[kFs].writev(\n    this.fd,\n    chunks,\n    this.pos,\n    (\n      er: unknown,\n      bytesWritten?: number,\n      buffers?: NodeJS.ArrayBufferView[]\n    ) => {\n      // No data currently available and operation should be retried later.\n      if ((er as any)?.code === 'EAGAIN') {\n        er = null;\n        bytesWritten = 0;\n      }\n\n      if (this.destroyed || er) {\n        return cb(er || new ERR_STREAM_DESTROYED('writev'));\n      }\n\n      bytesWritten = bytesWritten ?? 0;\n\n      this.bytesWritten += bytesWritten;\n\n      retries = bytesWritten ? 0 : retries + 1;\n      size -= bytesWritten;\n      pos += bytesWritten;\n\n      // Try writing non-zero number of bytes up to 5 times.\n      if (retries > 5) {\n        cb(new ERR_SYSTEM_ERROR('writev failed'));\n      } else if (size) {\n        buffers ??= [];\n        const bufs = buffers.map((b): Buffer => {\n          return Buffer.from(b.buffer, b.byteOffset, b.byteLength);\n        });\n        if (buffers.length === 0) {\n          cb(null);\n          return;\n        }\n        writevAll.call(\n          this,\n          [Buffer.concat(bufs).slice(bytesWritten)],\n          size,\n          pos,\n          cb,\n          retries\n        );\n      } else {\n        cb(null);\n      }\n    }\n  );\n}\n\nfunction writeImpl(\n  this: WriteStream,\n  data: string | NodeJS.ArrayBufferView,\n  encodingOrCallback: ValidEncoding | ErrorOnlyCallback | undefined,\n  cb?: ErrorOnlyCallback\n): void {\n  let callback: ErrorOnlyCallback;\n  let encoding: ValidEncoding = null;\n  if (typeof encodingOrCallback === 'function') {\n    callback = encodingOrCallback;\n  } else if (encodingOrCallback != null) {\n    encoding = encodingOrCallback;\n    if (cb !== undefined) callback = cb;\n  }\n  // @ts-expect-error TS2345\n  validateFunction(callback, 'write callback');\n\n  if (typeof data === 'string') {\n    data = Buffer.from(data, encoding as any);\n  }\n\n  this[kIsPerformingIO] = true;\n  const ee = this as unknown as EventEmitter;\n  writeAll.call(this, data, data.byteLength, this.pos, (er: unknown) => {\n    this[kIsPerformingIO] = false;\n    if (this.destroyed) {\n      // Tell ._destroy() that it's safe to close the fd now.\n      callback(er);\n      ee.emit(kIoDone, er);\n      return;\n    }\n\n    callback(er);\n  });\n\n  if (this.pos !== undefined) this.pos += data.byteLength;\n}\n\nfunction writevImpl(\n  this: WriteStream,\n  data: WriteVChunk[],\n  callback: ErrorOnlyCallback\n) {\n  let size = 0;\n  const chunks = data.map((d) => {\n    const chunk = (d as any).chunk;\n    size += chunk.length;\n    return chunk;\n  });\n\n  this[kIsPerformingIO] = true;\n  writevAll.call(this, chunks, size, this.pos, (er: unknown) => {\n    this[kIsPerformingIO] = false;\n    const ee = this as unknown as EventEmitter;\n    if (this.destroyed) {\n      // Tell ._destroy() that it's safe to close the fd now.\n      callback(er);\n      ee.emit(kIoDone, er);\n      return;\n    }\n\n    callback(er);\n  });\n\n  if (this.pos !== undefined) this.pos += size;\n}\n\n// @ts-expect-error TS2323 Cannot redeclare.\nexport function WriteStream(\n  this: WriteStream,\n  path: FilePath,\n  options: WriteStreamOptions = {}\n): WriteStream {\n  if (!(this instanceof WriteStream)) {\n    return new WriteStream(path, options);\n  }\n\n  if (options === null) {\n    options = {};\n  } else if (typeof options === 'string') {\n    options = { encoding: options };\n  }\n\n  validateObject(options, 'options');\n  const {\n    encoding = null,\n    autoClose = true,\n    emitClose = true,\n    start = 0,\n    highWaterMark = 64 * 1024,\n    signal = null,\n    flags = 'r',\n    fd = null,\n    mode = 0o666,\n    fs = kDefaultFsOperations,\n  } = options;\n  let { flush = false } = options;\n  const autoDestroy = autoClose;\n\n  if (\n    encoding !== 'buffer' &&\n    encoding !== null &&\n    !Buffer.isEncoding(encoding)\n  ) {\n    throw new ERR_INVALID_ARG_VALUE('options.encoding', encoding);\n  }\n  validateBoolean(autoClose, 'options.autoClose');\n  validateBoolean(emitClose, 'options.emitClose');\n  if (flush === null) flush = false;\n  validateBoolean(flush, 'options.flush');\n  validateUint32(start, 'options.start');\n  validateUint32(highWaterMark, 'options.highWaterMark');\n  if (signal != null) {\n    validateAbortSignal(signal, 'options.signal');\n  }\n\n  validateString(flags, 'options.flags');\n\n  // We don't actually use the mode in our implementation. Parsing it is\n  // just to ensure that it is validated.\n  parseFileMode(mode, 'options.mode', 0o666);\n\n  this[kFs] = getValidatedFsOptions(fs);\n  this.flush = flush;\n\n  if (fd == null) {\n    this.fd = null;\n    // Path will be ignored when fd is specified, so it can be falsy\n    this.path = toPathIfFileURL(normalizePath(path));\n    this.flags = options.flags === undefined ? 'r' : options.flags;\n    this.mode = options.mode === undefined ? 0o666 : options.mode;\n  } else {\n    if (isFileHandle(fd)) {\n      if (fs !== kDefaultFsOperations) {\n        throw new ERR_METHOD_NOT_IMPLEMENTED('FileHandle with fs');\n      }\n      this[kHandle] = fd as FileHandle;\n      this[kFs] = getValidatedFsOptions(fd as unknown as FsOperations);\n      const ee = fd as unknown as EventEmitter;\n      ee.on('close', () => this.close());\n      this.fd = (fd as FileHandle).fd || null;\n    } else {\n      this.fd = getValidatedFd(fd as number);\n    }\n  }\n\n  this.start = start;\n  this.pos = start;\n  this.bytesRead = 0;\n  this.bytesWritten = 0;\n  this[kIsPerformingIO] = false;\n\n  Reflect.apply(Writable, this, [\n    {\n      highWaterMark,\n      encoding,\n      emitClose,\n      autoDestroy,\n      signal,\n      construct: construct.bind(this),\n      write: writeImpl.bind(this),\n      writev: writevImpl.bind(this),\n      destroy: destroyImpl.bind(this),\n    },\n  ]);\n\n  if (encoding != null && encoding !== 'buffer') {\n    (this as unknown as Writable).setDefaultEncoding(\n      encoding as BufferEncoding\n    );\n  }\n\n  return this;\n}\nObject.setPrototypeOf(WriteStream.prototype, Writable.prototype);\nObject.setPrototypeOf(WriteStream, Writable);\n\nObject.defineProperty(WriteStream.prototype, 'autoClose', {\n  get(this: WriteStream): boolean {\n    validateThisInternalField(this, kFs, 'WriteStream');\n    return this._writableState?.autoDestroy || false;\n  },\n  set(this: WriteStream, val: boolean): void {\n    validateThisInternalField(this, kFs, 'WriteStream');\n    if (this._writableState !== undefined) {\n      this._writableState.autoDestroy = val;\n    }\n  },\n});\n\nWriteStream.prototype.close = function (\n  this: WriteStream,\n  cb: ErrorOnlyCallback = (_err: unknown): void => {}\n): void {\n  const writable = this as unknown as Writable;\n  const ee = this as unknown as EventEmitter;\n  if (cb) {\n    if (writable.closed) {\n      queueMicrotask(() => cb(null));\n      return;\n    }\n    ee.on('close', cb);\n  }\n\n  // If we are not autoClosing, we should call\n  // destroy on 'finish'.\n  if (!this.autoClose) {\n    ee.on('finish', () => writable.destroy());\n  }\n\n  // We use end() instead of destroy() because of\n  // https://github.com/nodejs/node/issues/2006\n  writable.end();\n};\n\n// There is no shutdown() for files.\nWriteStream.prototype.destroySoon = WriteStream.prototype.end;\n\nObject.defineProperty(WriteStream.prototype, 'pending', {\n  get(this: WriteStream): boolean {\n    return this.fd === null;\n  },\n  configurable: true,\n});\n\nexport function createReadStream(\n  path: FilePath,\n  options: ReadStreamOptions = {}\n): ReadStream {\n  return new ReadStream(path, options);\n}\nexport function createWriteStream(\n  path: FilePath,\n  options: WriteStreamOptions = {}\n): WriteStream {\n  return new WriteStream(path, options);\n}\n"
  },
  {
    "path": "src/node/internal/internal_fs_sync.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n/* eslint-disable @typescript-eslint/no-unused-vars,@typescript-eslint/no-unnecessary-condition */\n\nimport {\n  stringToFlags,\n  getValidatedFd,\n  validateBufferArray,\n  normalizePath,\n  getDate,\n  Stats,\n  kBadge,\n  type FilePath,\n  type Position,\n  type RawTime,\n  type SymlinkType,\n  type ReadDirOptions,\n  type WriteSyncOptions,\n  type ValidEncoding,\n  validatePosition,\n  validateAccessArgs,\n  validateChownArgs,\n  validateChmodArgs,\n  validateStatArgs,\n  validateMkDirArgs,\n  validateRmArgs,\n  validateRmDirArgs,\n  validateReaddirArgs,\n  validateReadArgs,\n  validateWriteArgs,\n  validateWriteFileArgs,\n  validateOpendirArgs,\n} from 'node-internal:internal_fs_utils';\nimport {\n  parseFileMode,\n  validateBoolean,\n  validateObject,\n  validateOneOf,\n  validateString,\n  validateUint32,\n} from 'node-internal:validators';\nimport {\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_ARG_VALUE,\n  ERR_ENOENT,\n  ERR_EBADF,\n  ERR_EINVAL,\n  ERR_EEXIST,\n  ERR_UNSUPPORTED_OPERATION,\n} from 'node-internal:internal_errors';\n\nimport {\n  F_OK,\n  X_OK,\n  W_OK,\n  O_WRONLY,\n  O_RDWR,\n  O_APPEND,\n  O_EXCL,\n  COPYFILE_EXCL,\n  COPYFILE_FICLONE,\n  COPYFILE_FICLONE_FORCE,\n} from 'node-internal:internal_fs_constants';\nimport { Dir, Dirent } from 'node-internal:internal_fs';\nimport { default as cffs } from 'cloudflare-internal:filesystem';\n\nimport { Buffer } from 'node-internal:internal_buffer';\nimport type {\n  BigIntStatsFs,\n  CopySyncOptions,\n  GlobOptions,\n  GlobOptionsWithFileTypes,\n  GlobOptionsWithoutFileTypes,\n  MakeDirectoryOptions,\n  OpenDirOptions,\n  ReadOptions,\n  RmOptions,\n  StatsFs,\n  WriteFileOptions,\n} from 'node:fs';\nimport type { RmDirOptions } from 'node-internal:internal_fs_utils';\n\nexport function accessSyncImpl(\n  path: URL,\n  mode: number,\n  followSymlinks: boolean\n): void {\n  // Input validation should have already been done by the caller.\n\n  // If the X_OK flag is set we will always throw because we don't\n  // support executable files.\n  if (mode & X_OK) {\n    throw new ERR_ENOENT(path.pathname, { syscall: 'access' });\n  }\n\n  const stat = cffs.stat(path, { followSymlinks });\n\n  // Similar to node.js, we make no differentiation between the file\n  // not existing and the file existing but not being accessible.\n  if (stat == null || (mode & W_OK && !stat.writable)) {\n    // Not found... or not writable\n    throw new ERR_ENOENT(path.pathname, { syscall: 'access' });\n  }\n\n  // We always assume that files are readable, so if we get here the\n  // path is accessible.\n}\n\nexport function accessSync(path: FilePath, mode: number = F_OK): void {\n  const { path: actualPath, mode: actualMode } = validateAccessArgs(path, mode);\n  accessSyncImpl(actualPath, actualMode, true);\n}\n\nexport function appendFileSync(\n  path: number | FilePath,\n  data: string | ArrayBufferView,\n  options: ValidEncoding | WriteFileOptions = {}\n): number {\n  if (typeof options === 'string' || options == null) {\n    options = { encoding: options as BufferEncoding };\n  }\n  const {\n    encoding = 'utf8',\n    mode = 0o666,\n    flag = 'a',\n    flush = false,\n  } = options ?? {};\n  // We defer to writeFileSync for the actual implementation and validation\n  return writeFileSync(path, data, { encoding, mode, flag, flush });\n}\n\nexport function chmodSync(path: FilePath, mode: string | number): void {\n  const { pathOrFd } = validateChmodArgs(path, mode);\n  if (cffs.stat(pathOrFd as URL, { followSymlinks: true }) == null) {\n    throw new ERR_ENOENT((pathOrFd as URL).pathname, { syscall: 'chmod' });\n  }\n  // We do not implement chmod in any meaningful way as our filesystem\n  // has no concept of user-defined permissions. Once we validate the inputs\n  // we just return as a non-op.\n  // The reason we call accessSync is to ensure, at the very least, that\n  // the path exists and would otherwise be accessible.\n}\n\nexport function chownSync(path: FilePath, uid: number, gid: number): void {\n  const { pathOrFd } = validateChownArgs(path, uid, gid);\n  if (cffs.stat(pathOrFd as URL, { followSymlinks: true }) == null) {\n    throw new ERR_ENOENT((pathOrFd as URL).pathname, { syscall: 'chown' });\n  }\n  // We do not implement chown in any meaningful way as our filesystem\n  // has no concept of ownership. Once we validate the inputs we just\n  // return as a non-op.\n  // The reason we call accessSync is to ensure, at the very least, that\n  // the path exists and would otherwise be accessible.\n}\n\nexport function closeSync(fd: number): void {\n  cffs.close(getValidatedFd(fd));\n}\n\nexport function copyFileSync(\n  src: FilePath,\n  dest: FilePath,\n  mode: number = 0\n): void {\n  validateOneOf(mode, 'mode', [\n    0,\n    COPYFILE_EXCL,\n    COPYFILE_FICLONE_FORCE,\n    COPYFILE_FICLONE,\n  ]);\n  if (mode & COPYFILE_FICLONE_FORCE) {\n    throw new ERR_UNSUPPORTED_OPERATION();\n  }\n  if (mode & COPYFILE_EXCL && existsSync(dest)) {\n    throw new ERR_EEXIST({\n      syscall: 'copyFile',\n      path: normalizePath(dest).pathname,\n    });\n  }\n  cffs.renameOrCopy(normalizePath(src), normalizePath(dest), { copy: true });\n}\n\nexport function cpSync(\n  src: FilePath,\n  dest: FilePath,\n  options: CopySyncOptions = {}\n): void {\n  validateObject(options, 'options');\n  const {\n    dereference = false,\n    errorOnExist = false,\n    force = true,\n    mode = 0,\n    preserveTimestamps = false,\n    recursive = false,\n    verbatimSymlinks = false,\n  } = options;\n\n  validateBoolean(dereference, 'options.dereference');\n  validateBoolean(errorOnExist, 'options.errorOnExist');\n  validateBoolean(force, 'options.force');\n  validateBoolean(preserveTimestamps, 'options.preserveTimestamps');\n  validateBoolean(recursive, 'options.recursive');\n  validateBoolean(verbatimSymlinks, 'options.verbatimSymlinks');\n  validateUint32(mode, 'options.mode');\n\n  if (mode & COPYFILE_FICLONE_FORCE) {\n    throw new ERR_INVALID_ARG_VALUE(\n      'options.mode',\n      'COPYFILE_FICLONE_FORCE is not supported'\n    );\n  }\n\n  // We do not implement the filter option currently. There's a bug in the Node.js\n  // implementation of fs.cp and the option.filter in which non-UTF-8 encoded file\n  // names are not handled correctly and the option.filter fails when the src or\n  // dest is passed in as a Buffer. Fixing this bug in Node.js will require a breaking\n  // change to the API or a new API that appropriately handles Buffer inputs and non\n  // UTF-8 encoded names. We want to avoid implementing the filter option for now\n  // until Node.js settles on a better implementation and API.\n  if (options.filter !== undefined) {\n    if (typeof options.filter !== 'function') {\n      throw new ERR_INVALID_ARG_TYPE(\n        'options.filter',\n        'function',\n        options.filter\n      );\n    }\n    throw new ERR_UNSUPPORTED_OPERATION();\n  }\n\n  const exclusive = Boolean(mode & COPYFILE_EXCL);\n  // We're not current implementing the exclusive flag. We're validating\n  // it here just to use it so the compiler doesn't complain.\n  validateBoolean(exclusive, '');\n\n  // We're not currently implementing verbatimSymlinks in any meaningful way.\n  // Our symlinks are always fully qualfied. That is, they always point to\n  // an absolute path and never to a relative path, so there is no distinction\n  // between verbatimSymlinks and non-verbatimSymlinks. We validate the option\n  // value above but otherwise we ignore it.\n\n  // We're also not currently implementing the preserveTimestamps option.\n  // Timestamps in our virtual filesystem aren't super meaningful given\n  // that most files in the current implementation are either created\n  // at startup and use the EPOCH as their timestamp, or are temporary files\n  // that are deleted when the request completes.\n  // TODO(node-fs): Decide if we want to implement preserveTimestamps in the future.\n\n  cffs.cp(normalizePath(src), normalizePath(dest), {\n    deferenceSymlinks: dereference,\n    recursive,\n    force,\n    errorOnExist,\n  });\n}\n\nexport function existsSync(path: FilePath): boolean {\n  try {\n    // The existsSync function follows symlinks. If the symlink is broken,\n    // it will return false.\n    accessSync(path);\n    return true;\n  } catch {\n    // It's always odd to swallow errors but this is how Node.js does it.\n    // The existsSync function never throws and returns false if any error\n    // occurs.\n    return false;\n  }\n}\n\nexport function fchmodSync(fd: number, mode: string | number): void {\n  fd = getValidatedFd(fd);\n  parseFileMode(mode, 'mode');\n  if (cffs.stat(fd, { followSymlinks: true }) == null) {\n    throw new ERR_EBADF({ syscall: 'fchmod' });\n  }\n  // We do not implement chmod in any meaningful way as our filesystem\n  // has no concept of user-defined permissions. Once we validate the inputs\n  // we just return as a non-op.\n  // The reason we call cffs.stat is to ensure, at the very least, that\n  // the fd is valid and would otherwise be accessible.\n}\n\nexport function fchownSync(fd: number, uid: number, gid: number): void {\n  const { pathOrFd } = validateChownArgs(fd, uid, gid);\n  if (cffs.stat(pathOrFd as number, { followSymlinks: true }) == null) {\n    throw new ERR_EBADF({ syscall: 'fchown' });\n  }\n  // We do not implement chown in any meaningful way as our filesystem\n  // has no concept of ownership. Once we validate the inputs we just\n  // return as a non-op.\n  // The reason we call accessSync is to ensure, at the very least, that\n  // the path exists and would otherwise be accessible.\n}\n\nexport function fdatasyncSync(fd: number): void {\n  fd = getValidatedFd(fd);\n  // We do not implement fdatasync in any meaningful way. At most we\n  // will validate that the fd is valid and would otherwise be accessible.\n  if (cffs.stat(fd, { followSymlinks: true }) == null) {\n    throw new ERR_EBADF({ syscall: 'datasync' });\n  }\n}\n\nexport type FStatOptions = {\n  bigint?: boolean | undefined;\n};\n\nexport function fstatSync(fd: number, options: FStatOptions = {}): Stats {\n  validateObject(options, 'options');\n  const { pathOrFd: validatedFd, bigint } = validateStatArgs(\n    fd,\n    options,\n    true /* is fstat */\n  );\n  const stat = cffs.stat(validatedFd as number, { followSymlinks: true });\n  if (stat == null) {\n    throw new ERR_EBADF({ syscall: 'stat' });\n  }\n  return new Stats(kBadge, stat, { bigint });\n}\n\nexport function fsyncSync(fd: number): void {\n  fd = getValidatedFd(fd);\n  // We do not implement fdatasync in any meaningful way. At most we\n  // will validate that the fd is valid and would otherwise be accessible.\n  if (cffs.stat(fd, { followSymlinks: true }) == null) {\n    throw new ERR_EBADF({ syscall: 'sync' });\n  }\n}\n\nexport function ftruncateSync(fd: number, len: number = 0): void {\n  validateUint32(len, 'len');\n  cffs.truncate(getValidatedFd(fd), len);\n}\n\nexport function futimesSync(\n  fd: number,\n  atime: RawTime | Date,\n  mtime: RawTime | Date\n): void {\n  // We do not actually make use of access time in our filesystem. We just\n  // validate the inputs here.\n  atime = getDate(atime);\n  mtime = getDate(mtime);\n  cffs.setLastModified(getValidatedFd(fd), mtime, {});\n}\n\nexport function lchmodSync(path: FilePath, mode: string | number): void {\n  const { pathOrFd } = validateChmodArgs(path, mode);\n  if (cffs.stat(pathOrFd as URL, { followSymlinks: false }) == null) {\n    throw new ERR_ENOENT((pathOrFd as URL).pathname, { syscall: 'lchmod' });\n  }\n  // We do not implement chmod in any meaningful way as our filesystem\n  // has no concept of user-defined permissions. Once we validate the inputs\n  // we just return as a non-op.\n  // The reason we call cffs.stat is to ensure, at the very least, that\n  // the fd is valid and would otherwise be accessible.\n}\n\nexport function lchownSync(path: FilePath, uid: number, gid: number): void {\n  const { pathOrFd } = validateChownArgs(path, uid, gid);\n  if (cffs.stat(pathOrFd as URL, { followSymlinks: false }) == null) {\n    throw new ERR_ENOENT((path as URL).pathname, { syscall: 'lchown' });\n  }\n  // We do not implement chown in any meaningful way as our filesystem\n  // has no concept of user-defined permissions. Once we validate the inputs\n  // we just return as a non-op.\n  // The reason we call cffs.stat is to ensure, at the very least, that\n  // the fd is valid and would otherwise be accessible.\n}\n\nexport function lutimesSync(\n  path: FilePath,\n  atime: RawTime | Date,\n  mtime: RawTime | Date\n): void {\n  // We do not actually make use of access time in our filesystem. We just\n  // validate the inputs here.\n  atime = getDate(atime);\n  mtime = getDate(mtime);\n  cffs.setLastModified(normalizePath(path), mtime, { followSymlinks: false });\n}\n\nexport function linkSync(existingPath: FilePath, newPath: FilePath): void {\n  cffs.link(normalizePath(newPath), normalizePath(existingPath), {\n    symbolic: false,\n  });\n}\n\n// We could use the StatSyncOptions from @types:node here but the definition\n// of that in @types:node is bit overly complex for our use here.\nexport type StatOptions = {\n  bigint?: boolean | undefined;\n  throwIfNoEntry?: boolean | undefined;\n};\n\nexport function lstatSync(\n  path: FilePath,\n  options: StatOptions = {}\n): Stats | undefined {\n  const {\n    pathOrFd: validatedPath,\n    bigint,\n    throwIfNoEntry,\n  } = validateStatArgs(path, options);\n  const stat = cffs.stat(validatedPath as URL, { followSymlinks: false });\n  if (stat == null) {\n    if (throwIfNoEntry) {\n      throw new ERR_ENOENT((validatedPath as URL).pathname, {\n        syscall: 'lstat',\n      });\n    }\n    return undefined;\n  }\n  return new Stats(kBadge, stat, { bigint });\n}\n\nexport function mkdirSync(\n  path: FilePath,\n  options: number | MakeDirectoryOptions = {}\n): string | undefined {\n  const { path: normalizedPath, recursive } = validateMkDirArgs(path, options);\n\n  return cffs.mkdir(normalizedPath, { recursive, tmp: false });\n}\n\nexport type MkdirTempSyncOptions = {\n  encoding?: ValidEncoding | undefined;\n};\n\nexport function mkdtempSync(\n  prefix: FilePath,\n  options: ValidEncoding | MkdirTempSyncOptions = {}\n): string {\n  if (typeof options === 'string' || options == null) {\n    options = { encoding: options };\n  }\n  validateObject(options, 'options');\n  const { encoding = 'utf8' } = options;\n  if (!Buffer.isEncoding(encoding) && encoding !== 'buffer') {\n    throw new ERR_INVALID_ARG_VALUE('options.encoding', encoding);\n  }\n  prefix = normalizePath(prefix, encoding);\n  const ret = cffs.mkdir(normalizePath(prefix), {\n    recursive: false,\n    tmp: true,\n  });\n  if (ret === undefined) {\n    // If mkdir failed it should throw a meaningful error. If we get\n    // here, it means something else went wrong and we're just going\n    // to throw a generic EINVAL error.\n    throw new ERR_EINVAL({ syscall: 'mkdtemp' });\n  }\n  return ret;\n}\n\nexport function opendirSync(path: FilePath, options: OpenDirOptions = {}): Dir {\n  const {\n    path: validatedPath,\n    encoding,\n    recursive,\n  } = validateOpendirArgs(path, options);\n\n  const handles = cffs.readdir(validatedPath, { recursive });\n  return new Dir(handles, path, { encoding });\n}\n\nexport function openSync(\n  path: FilePath,\n  flags: string | number = 'r',\n  mode: string | number = 0o666\n): number {\n  // We don't actually the the mode in any meaningful way. We just validate it.\n  parseFileMode(mode, 'mode', 0o666);\n  const newFlags = stringToFlags(flags);\n\n  const read = !(newFlags & O_WRONLY) || Boolean(newFlags & O_RDWR);\n  const write = Boolean(newFlags & O_WRONLY) || Boolean(newFlags & O_RDWR);\n  const append = Boolean(newFlags & O_APPEND);\n  const exclusive = Boolean(newFlags & O_EXCL);\n  const followSymlinks = true;\n\n  return cffs.open(normalizePath(path), {\n    read,\n    write,\n    append,\n    exclusive,\n    followSymlinks,\n  });\n}\n\nexport type ReadDirResult = string[] | Buffer[] | Dirent[];\n\nexport function readdirSync(\n  path: FilePath,\n  options: ValidEncoding | ReadDirOptions = {}\n): ReadDirResult {\n  if (typeof options === 'string' || options == null) {\n    options = { encoding: options };\n  }\n  const {\n    path: normalizedPath,\n    encoding,\n    withFileTypes,\n    recursive,\n  } = validateReaddirArgs(path, options);\n\n  const handles = cffs.readdir(normalizedPath, { recursive });\n\n  if (withFileTypes) {\n    if ((encoding as string) === 'buffer') {\n      return handles.map((handle) => {\n        return new Dirent(\n          Buffer.from(handle.name),\n          handle.type,\n          handle.parentPath\n        );\n      });\n    }\n    return handles.map((handle) => {\n      return new Dirent(handle.name, handle.type, handle.parentPath);\n    });\n  }\n\n  if ((encoding as string) === 'buffer') {\n    return handles.map((handle) => {\n      return Buffer.from(handle.name);\n    });\n  }\n\n  return handles.map((handle) => {\n    return Buffer.from(handle.name).toString(encoding as string);\n  });\n}\n\nexport type ReadFileSyncOptions = {\n  encoding?: ValidEncoding | undefined;\n  flag?: string | number | undefined;\n};\n\nexport function readFileSync(\n  pathOrFd: number | FilePath,\n  options: ValidEncoding | ReadFileSyncOptions = {}\n): string | Buffer {\n  if (typeof options === 'string' || options == null) {\n    options = { encoding: options };\n  }\n  validateObject(options, 'options');\n  const { encoding = null, flag = 'r' } = options;\n  if (\n    encoding !== null &&\n    encoding !== 'buffer' &&\n    !Buffer.isEncoding(encoding)\n  ) {\n    throw new ERR_INVALID_ARG_VALUE('options.encoding', encoding);\n  }\n  stringToFlags(flag);\n\n  // TODO(node:fs): We are currently ignoring flags on readFileSync.\n\n  const u8 = ((): Uint8Array => {\n    if (typeof pathOrFd === 'number') {\n      return cffs.readAll(getValidatedFd(pathOrFd));\n    }\n    return cffs.readAll(normalizePath(pathOrFd));\n  })();\n\n  const buf = Buffer.from(u8.buffer, u8.byteOffset, u8.byteLength);\n  if (typeof encoding === 'string') {\n    return buf.toString(encoding);\n  }\n  return buf;\n}\n\nexport type ReadLinkSyncOptions = {\n  encoding?: ValidEncoding | undefined;\n};\n\nexport function readlinkSync(\n  path: FilePath,\n  options: ValidEncoding | ReadLinkSyncOptions = {}\n): string | Buffer {\n  if (typeof options === 'string' || options == null) {\n    options = { encoding: options };\n  }\n  validateObject(options, 'options');\n  const { encoding = 'utf8' } = options;\n  if (!Buffer.isEncoding(encoding) && encoding !== 'buffer') {\n    throw new ERR_INVALID_ARG_VALUE('options.encoding', encoding);\n  }\n  const dest = Buffer.from(\n    cffs.readLink(normalizePath(path), { failIfNotSymlink: true })\n  );\n  if (typeof encoding === 'string') {\n    return dest.toString(encoding);\n  }\n  return dest;\n}\n\n// readSync is overloaded to support two different signatures:\n//   fs.readSync(fd, buffer, offset, length, position)\n//   fs.readSync(fd, buffer, options)\n//\n// fd is always a number, buffer is an ArrayBufferView, offset and length\n// are numbers, and position is either a number, bigint, or null.\nexport function readSync(\n  fd: number,\n  buffer: NodeJS.ArrayBufferView,\n  offsetOrOptions: ReadOptions | number = {},\n  length?: number,\n  position: Position = null\n): number {\n  const {\n    fd: validatedFd,\n    buffer: actualBuffer,\n    length: actualLength,\n    position: actualPosition,\n  } = validateReadArgs(fd, buffer, offsetOrOptions, length, position);\n\n  if (actualLength === 0 || buffer.byteLength === 0) {\n    return 0;\n  }\n\n  return readvSync(validatedFd, actualBuffer, actualPosition);\n}\n\nexport function readvSync(\n  fd: number,\n  buffers: NodeJS.ArrayBufferView[],\n  position: Position = null\n): number {\n  fd = getValidatedFd(fd);\n  validateBufferArray(buffers);\n  validatePosition(position, 'position');\n\n  if (buffers.length === 0) {\n    return 0;\n  }\n  return cffs.read(fd, buffers, { position });\n}\n\nexport function realpathSync(\n  p: FilePath,\n  options: ValidEncoding | ReadLinkSyncOptions = {}\n): string | Buffer {\n  if (typeof options === 'string' || options == null) {\n    options = { encoding: options };\n  }\n  validateObject(options, 'options');\n  const { encoding = 'utf8' } = options;\n  if (!Buffer.isEncoding(encoding) && encoding !== 'buffer') {\n    throw new ERR_INVALID_ARG_VALUE('options.encoding', encoding);\n  }\n  const dest = Buffer.from(\n    cffs.readLink(normalizePath(p), { failIfNotSymlink: false })\n  );\n  if (typeof encoding === 'string') {\n    return dest.toString(encoding);\n  }\n  return dest;\n}\n\nrealpathSync.native = realpathSync;\n\nexport function renameSync(src: FilePath, dest: FilePath): void {\n  cffs.renameOrCopy(normalizePath(src), normalizePath(dest), { copy: false });\n}\n\nexport function rmdirSync(path: FilePath, options: RmDirOptions = {}): void {\n  const { path: normalizedPath, recursive } = validateRmDirArgs(path, options);\n\n  cffs.rm(normalizedPath, { recursive, force: false, dironly: true });\n}\n\nexport function rmSync(path: FilePath, options: RmOptions = {}): void {\n  const {\n    path: normalizedPath,\n    recursive,\n    force,\n  } = validateRmArgs(path, options);\n\n  cffs.rm(normalizedPath, { recursive, force, dironly: false });\n}\n\nexport function statSync(\n  path: FilePath,\n  options: StatOptions = {}\n): Stats | undefined {\n  const {\n    pathOrFd: validatedPath,\n    bigint,\n    throwIfNoEntry,\n  } = validateStatArgs(path, options);\n  const stat = cffs.stat(validatedPath as URL, { followSymlinks: true });\n  if (stat == null) {\n    if (throwIfNoEntry) {\n      throw new ERR_ENOENT((validatedPath as URL).pathname, {\n        syscall: 'stat',\n      });\n    }\n    return undefined;\n  }\n  return new Stats(kBadge, stat, { bigint });\n}\n\nexport function statfsSync(\n  path: FilePath,\n  options: { bigint?: boolean | undefined } = {}\n): StatsFs | BigIntStatsFs {\n  normalizePath(path);\n  validateObject(options, 'options');\n  const { bigint = false } = options;\n  validateBoolean(bigint, 'options.bigint');\n  // We don't implement statfs in any meaningful way. Nor will we actually\n  // validate that the path exists. We just return a non-op dummy object.\n  if (bigint) {\n    return {\n      type: 0n,\n      bsize: 0n,\n      blocks: 0n,\n      bfree: 0n,\n      bavail: 0n,\n      files: 0n,\n      ffree: 0n,\n    };\n  } else {\n    return {\n      type: 0,\n      bsize: 0,\n      blocks: 0,\n      bfree: 0,\n      bavail: 0,\n      files: 0,\n      ffree: 0,\n    };\n  }\n}\n\nexport function symlinkSync(\n  target: FilePath,\n  path: FilePath,\n  type: SymlinkType = null\n): void {\n  // We don't implement type in any meaningful way but we do validate it.\n  validateOneOf(type, 'type', ['dir', 'file', 'junction', null]);\n  cffs.link(normalizePath(path), normalizePath(target), { symbolic: true });\n}\n\nexport function truncateSync(path: FilePath, len: number = 0): void {\n  validateUint32(len, 'len');\n  cffs.truncate(normalizePath(path), len);\n}\n\nexport function unlinkSync(path: FilePath): void {\n  cffs.unlink(normalizePath(path));\n}\n\nexport function utimesSync(\n  path: FilePath,\n  atime: RawTime | Date,\n  mtime: RawTime | Date\n): void {\n  // We do not actually make use of access time in our filesystem. We just\n  // validate the inputs here.\n  atime = getDate(atime);\n  mtime = getDate(mtime);\n  cffs.setLastModified(normalizePath(path), mtime, { followSymlinks: true });\n}\n\nexport function writeFileSync(\n  path: number | FilePath,\n  data: string | ArrayBufferView,\n  options: ValidEncoding | WriteFileOptions = {}\n): number {\n  const {\n    path: validatedPath,\n    data: actualData,\n    append,\n    exclusive,\n  } = validateWriteFileArgs(path, data, options);\n\n  return cffs.writeAll(validatedPath, actualData, { append, exclusive });\n}\n\nexport function writeSync(\n  fd: number,\n  buffer: NodeJS.ArrayBufferView | string,\n  offsetOrOptions: WriteSyncOptions | Position = null,\n  length?: number | ValidEncoding,\n  position?: Position\n): number {\n  const {\n    fd: validatedFd,\n    buffer: actualBuffer,\n    position: actualPosition,\n  } = validateWriteArgs(fd, buffer, offsetOrOptions, length, position);\n\n  return writevSync(validatedFd, actualBuffer, actualPosition);\n}\n\nexport function writevSync(\n  fd: number,\n  buffers: NodeJS.ArrayBufferView[],\n  position: Position = null\n): number {\n  fd = getValidatedFd(fd);\n  validateBufferArray(buffers);\n  validatePosition(position, 'position');\n\n  if (buffers.length === 0) {\n    return 0;\n  }\n\n  return cffs.write(fd, buffers, { position });\n}\n\nexport function globSync(\n  _pattern: string | readonly string[],\n  _options:\n    | GlobOptions\n    | GlobOptionsWithFileTypes\n    | GlobOptionsWithoutFileTypes = {}\n): string[] {\n  // We do not yet implement the globSync function. In Node.js, this\n  // function depends heavily on the third party minimatch library\n  // which is not yet available in the workers runtime. This will be\n  // explored for implementation separately in the future.\n  throw new ERR_UNSUPPORTED_OPERATION();\n}\n\nexport interface OpenAsBlobOptions {\n  type?: string | undefined;\n}\nexport function openAsBlob(\n  path: FilePath,\n  options: OpenAsBlobOptions = {}\n): Blob {\n  // TODO(node-fs): We do not yet implement the openAsBlob API. We will implement\n  // this soon.\n  normalizePath(path);\n  validateObject(options, 'options');\n  const { type = '' } = options;\n  validateString(type, 'options.type');\n\n  return cffs.openAsBlob(normalizePath(path), { type });\n}\n\n// An API is considered stubbed if it is not implemented by the function\n// exists with the correct signature and throws an error if called. If\n// a function exists that does not have the correct signature, it is\n// not considered fully stubbed.\n// An API is considered optimized if the API has been implemented and\n// tested and then optimized for performance.\n//\n// (S == Stubbed, I == Implemented, T == Tested, O == Optimized, V = Verified)\n// For T, 1 == basic tests, 2 == node.js tests ported\n// Verified means that the behavior or the API has been verified to be\n// consistent with the node.js API. This does not mean that the behaviors.\n// We can only determine verification status once the node.js tests are\n// ported and verified to work correctly.\n// match exactly, just that they are consistent.\n//  S  I  T  V  O\n// [x][x][2][x][x] fs.accessSync(path[, mode])\n// [x][x][2][x][x] fs.existsSync(path)\n// [x][x][2][x][x] fs.chmodSync(path, mode)\n// [x][x][2][x][x] fs.chownSync(path, uid, gid)\n// [x][x][2][x][x] fs.closeSync(fd)\n// [x][x][2][x][x] fs.fchmodSync(fd, mode)\n// [x][x][2][x][x] fs.fchownSync(fd, uid, gid)\n// [x][x][2][x][x] fs.lchmodSync(path, mode)\n// [x][x][2][x][x] fs.lchownSync(path, uid, gid)\n// [x][x][2][x][x] fs.futimesSync(fd, atime, mtime)\n// [x][x][2][x][x] fs.lutimesSync(path, atime, mtime)\n// [x][x][2][x][x] fs.utimesSync(path, atime, mtime)\n// [x][x][2][x][x] fs.fstatSync(fd[, options])\n// [x][x][2][x][x] fs.lstatSync(path[, options])\n// [x][x][2][x][x] fs.statSync(path[, options])\n// [x][x][2][x][x] fs.statfsSync(path[, options])\n// [x][x][2][x][x] fs.fdatasyncSync(fd)\n// [x][x][2][x][x] fs.fsyncSync(fd)\n// [x][x][2][x][x] fs.linkSync(existingPath, newPath)\n// [x][x][2][x][x] fs.readlinkSync(path[, options])\n// [x][x][2][x][x] fs.realpathSync(path[, options])\n// [x][x][2][x][x] fs.realpathSync.native(path[, options])\n// [x][x][2][x][x] fs.symlinkSync(target, path[, type])\n// [x][x][2][x][x] fs.unlinkSync(path)\n// [x][x][2][x][x] fs.mkdirSync(path[, options])\n// [x][x][2][x][x] fs.mkdtempSync(prefix[, options])\n// [x][x][2][x][x] fs.rmdirSync(path[, options])\n// [x][x][2][x][x] fs.rmSync(path[, options])\n// [x][x][2][x][x] fs.ftruncateSync(fd[, len])\n// [x][x][2][x][x] fs.truncateSync(path[, len])\n// [x][x][2][x][x] fs.openSync(path[, flags[, mode]])\n// [x][x][2][x][x] fs.readdirSync(path[, options])\n// [x][x][2][x][x] fs.readFileSync(path[, options])\n// [x][x][2][x][x] fs.readSync(fd, buffer, offset, length[, position])\n// [x][x][2][x][x] fs.readSync(fd, buffer[, options])\n// [x][x][2][x][x] fs.readvSync(fd, buffers[, position])\n// [x][x][2][x][x] fs.renameSync(oldPath, newPath)\n// [x][x][2][x][x] fs.writeFileSync(file, data[, options])\n// [x][x][2][x][x] fs.writeSync(fd, buffer, offset[, length[, position]])\n// [x][x][2][x][x] fs.writeSync(fd, buffer[, options])\n// [x][x][2][x][x] fs.writeSync(fd, string[, position[, encoding]])\n// [x][x][2][x][x] fs.writevSync(fd, buffers[, position])\n// [x][x][2][x][x] fs.appendFileSync(path, data[, options])\n// [x][x][2][x][x] fs.copyFileSync(src, dest[, mode])\n// [x][x][2][x][x] fs.opendirSync(path[, options])\n// [x][x][2][x][x] fs.cpSync(src, dest[, options])\n// [ ][ ][ ][ ][ ] fs.globSync(pattern[, options])\n"
  },
  {
    "path": "src/node/internal/internal_fs_utils.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport {\n  ERR_BUFFER_OUT_OF_BOUNDS,\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_ARG_VALUE,\n} from 'node-internal:internal_errors';\nimport {\n  validateAbortSignal,\n  validateObject,\n  validateBoolean,\n  validateInteger,\n  validateInt32,\n  validateUint32,\n  validateEncoding,\n  parseFileMode,\n} from 'node-internal:validators';\nimport { isArrayBufferView } from 'node-internal:internal_types';\nimport {\n  F_OK,\n  W_OK,\n  R_OK,\n  X_OK,\n  COPYFILE_EXCL,\n  COPYFILE_FICLONE,\n  O_RDONLY,\n  O_APPEND,\n  O_CREAT,\n  O_RDWR,\n  O_EXCL,\n  O_SYNC,\n  O_TRUNC,\n  O_WRONLY,\n  S_IFCHR,\n  S_IFDIR,\n  S_IFREG,\n  S_IFLNK,\n  S_IFMT,\n  S_IFSOCK,\n  S_IFIFO,\n  S_IFBLK,\n  UV_FS_COPYFILE_FICLONE_FORCE,\n} from 'node-internal:internal_fs_constants';\n\nimport { strictEqual } from 'node-internal:internal_assert';\n\nimport { Buffer } from 'node-internal:internal_buffer';\nimport processImpl from 'node-internal:process';\nexport type FilePath = string | URL | Buffer;\n\nimport type {\n  MakeDirectoryOptions,\n  OpenDirOptions,\n  ReadOptions,\n  RmDirOptions as NodeRmDirOptions,\n  RmOptions,\n  WriteFileOptions,\n} from 'node:fs';\n\n// Extended RmDirOptions that includes deprecated properties we still support\n// eslint-disable-next-line @typescript-eslint/no-deprecated\nexport type RmDirOptions = NodeRmDirOptions & {\n  /** @deprecated Use `fs.rm()` with `recursive` option instead */\n  maxRetries?: number | undefined;\n  /** @deprecated Use `fs.rm()` with `recursive` option instead */\n  recursive?: boolean | undefined;\n  /** @deprecated Use `fs.rm()` with `recursive` option instead */\n  retryDelay?: number | undefined;\n};\n\nexport type ValidEncoding = BufferEncoding | 'buffer' | null;\n\nimport type { Stat as InternalStat } from 'cloudflare-internal:filesystem';\n\n// A non-public symbol used to ensure that certain constructors cannot\n// be called from user-code\nexport const kBadge = Symbol('kBadge');\nexport const kFileHandle = Symbol('kFileHandle');\n\nexport function isFileHandle(object: unknown): boolean {\n  if (typeof object !== 'object' || object === null) return false;\n  return Reflect.has(object, kFileHandle);\n}\n\nexport type RawTime = string | number | bigint;\nexport type SymlinkType = 'dir' | 'file' | 'junction' | null | undefined;\n\n// Normalizes the input time to a Date object.\nexport function getDate(time: RawTime | Date): Date {\n  if (typeof time === 'number') {\n    return new Date(time);\n  } else if (typeof time === 'bigint') {\n    return new Date(Number(time));\n  } else if (typeof time === 'string') {\n    return new Date(time);\n  } else if (time instanceof Date) {\n    return time;\n  }\n  throw new ERR_INVALID_ARG_TYPE(\n    'time',\n    ['string', 'number', 'bigint', 'Date'],\n    time\n  );\n}\n\n// Normalizes the input file path to a URL object.\nexport function normalizePath(path: FilePath, encoding: string = 'utf8'): URL {\n  // We treat all of our virtual file system paths as file URLs\n  // as a way of normalizing them. Because our file system is\n  // fully virtual, we don't need to worry about a number of the\n  // issues that real file system paths have and don't need to\n  // worry quite as much about strictly checking for null bytes\n  // in the path. The URL parsing will take care of those details.\n  // We do, however, need to be sensitive to the fact that there\n  // are two different URL impls in the runtime that are selected\n  // based on compat flags. A worker that is using the legacy URL\n  // implementation will end up seeing slightly different behavior\n  // here but that's not something we need to worry about for now.\n  if (typeof path === 'string') {\n    // fallthrough for typical case\n  } else if (path instanceof URL) {\n    return path;\n  } else if (Buffer.isBuffer(path)) {\n    path = path.toString(encoding);\n  } else {\n    throw new ERR_INVALID_ARG_TYPE('path', ['string', 'Buffer', 'URL'], path);\n  }\n\n  if (path.indexOf('\\0') !== -1) {\n    throw new ERR_INVALID_ARG_VALUE(\n      'path',\n      path,\n      'must not contain null bytes'\n    );\n  }\n\n  // In this case, we have a string path. Any ? or # characters in the\n  // path should be percent-encoded to ensure they are not treated as\n  // special characters in the URL.\n  path = path.replace(/[#?]/g, encodeURIComponent);\n\n  // Node.js will also ignore empty path segments (e.g. `//` in the path).\n  // Let's normalize those out here as well.\n  path = path.replace(/\\/\\//g, '/');\n\n  return new URL(\n    path,\n    path.startsWith('/') ? 'file://' : `file://${processImpl.getCwd()}/`\n  );\n}\n\nexport const kMaxUserId = 2 ** 32 - 1;\n\n// In Node.js async callback APIs, input arguments are always validated\n// with input validation errors thrown synchronously. Only errors that\n// occur during the actual operation (e.g. file not found) are reported\n// via the callback. The validateAccessArgs function is used by both the\n// accessSync and access-with-callback APIs to validate the input args.\nexport function validateAccessArgs(\n  rawPath: FilePath,\n  mode: number\n): { path: URL; mode: number } {\n  return {\n    path: normalizePath(rawPath),\n    mode: validateMode(mode),\n  };\n}\n\nexport function validateChownArgs(\n  pathOrFd: FilePath | number,\n  uid: number,\n  gid: number\n): { pathOrFd: URL | number; uid: number; gid: number } {\n  validateInteger(uid, 'uid', -1, kMaxUserId);\n  validateInteger(gid, 'gid', -1, kMaxUserId);\n  if (typeof pathOrFd === 'number') {\n    return {\n      pathOrFd: getValidatedFd(pathOrFd, 'fd'),\n      uid,\n      gid,\n    };\n  }\n  return {\n    pathOrFd: normalizePath(pathOrFd),\n    uid,\n    gid,\n  };\n}\n\nexport function validateStatArgs(\n  path: number | FilePath,\n  options: {\n    bigint?: boolean | undefined;\n    throwIfNoEntry?: boolean | undefined;\n  } = {},\n  isfstat = false\n): { pathOrFd: number | URL; bigint: boolean; throwIfNoEntry: boolean } {\n  validateObject(options, 'options');\n  const { bigint = false, throwIfNoEntry = true } = options;\n  validateBoolean(bigint, 'options.bigint');\n  validateBoolean(throwIfNoEntry, 'options.throwIfNoEntry');\n  if (typeof path === 'number') {\n    return {\n      pathOrFd: getValidatedFd(path, 'fd'),\n      bigint,\n      throwIfNoEntry,\n    };\n  }\n  if (isfstat) {\n    throw new ERR_INVALID_ARG_TYPE('fd', 'number', path);\n  }\n  return {\n    pathOrFd: normalizePath(path),\n    bigint,\n    throwIfNoEntry,\n  };\n}\nexport function validateChmodArgs(\n  pathOrFd: FilePath | number,\n  mode: number | string\n): { pathOrFd: URL | number; mode: number } {\n  const actualMode = parseFileMode(mode, 'mode');\n  if (typeof pathOrFd === 'number') {\n    return {\n      pathOrFd: getValidatedFd(pathOrFd, 'fd'),\n      mode: actualMode,\n    };\n  }\n  return {\n    pathOrFd: normalizePath(pathOrFd),\n    mode: actualMode,\n  };\n}\n\nexport function validateMkDirArgs(\n  path: FilePath,\n  options: number | MakeDirectoryOptions\n): { path: URL; recursive: boolean } {\n  const { recursive = false, mode = 0o777 } = ((): MakeDirectoryOptions => {\n    if (typeof options === 'number') {\n      return { mode: options };\n    } else {\n      validateObject(options, 'options');\n      return options;\n    }\n  })();\n\n  validateBoolean(recursive, 'options.recursive');\n\n  // We don't implement the mode option in any meaningful way. We just validate it.\n  parseFileMode(mode, 'mode');\n\n  return {\n    path: normalizePath(path),\n    recursive,\n  };\n}\n\nexport function validateRmArgs(\n  path: FilePath,\n  options: RmOptions\n): { path: URL; recursive: boolean; force: boolean } {\n  validateObject(options, 'options');\n  const {\n    force = false,\n    maxRetries = 0,\n    recursive = false,\n    retryDelay = 0,\n  } = options;\n  // We do not implement the maxRetries or retryDelay options in any meaningful\n  // way. We just validate them.\n  validateBoolean(force, 'options.force');\n  validateUint32(maxRetries, 'options.maxRetries');\n  validateBoolean(recursive, 'options.recursive');\n  validateUint32(retryDelay, 'options.retryDelay');\n  return {\n    path: normalizePath(path),\n    recursive,\n    force,\n  };\n}\n\nexport function validateRmDirArgs(\n  path: FilePath,\n  options: RmDirOptions\n): { path: URL; recursive: boolean } {\n  validateObject(options, 'options');\n  const { maxRetries = 0, recursive = false, retryDelay = 0 } = options; // eslint-disable-line @typescript-eslint/no-deprecated\n  // We do not implement the maxRetries or retryDelay options in any meaningful\n  // way. We just validate them.\n  validateUint32(maxRetries, 'options.maxRetries');\n  validateBoolean(recursive, 'options.recursive');\n  validateUint32(retryDelay, 'options.retryDelay');\n  return {\n    path: normalizePath(path),\n    recursive,\n  };\n}\n\n// We could use the @types/node definition here but it's a bit overly\n// complex for our needs here.\nexport type ReadDirOptions = {\n  encoding?: ValidEncoding | undefined;\n  withFileTypes?: boolean | undefined;\n  recursive?: boolean | undefined;\n};\n\nexport function validateReaddirArgs(\n  path: FilePath,\n  options: ReadDirOptions | ValidEncoding\n): {\n  path: URL;\n  encoding: ValidEncoding;\n  withFileTypes: boolean;\n  recursive: boolean;\n} {\n  if (typeof options === 'string' || options == null) {\n    options = { encoding: options };\n  }\n  validateObject(options, 'options');\n  const {\n    encoding = 'utf8',\n    withFileTypes = false,\n    recursive = false,\n  } = options;\n  if (encoding !== 'buffer' && !Buffer.isEncoding(encoding)) {\n    throw new ERR_INVALID_ARG_VALUE('options.encoding', encoding);\n  }\n  validateBoolean(withFileTypes, 'options.withFileTypes');\n  validateBoolean(recursive, 'options.recursive');\n  return {\n    path: normalizePath(path),\n    encoding,\n    withFileTypes,\n    recursive,\n  };\n}\n\nexport function validateOpendirArgs(\n  path: FilePath,\n  options: OpenDirOptions\n): {\n  path: URL;\n  encoding: ValidEncoding;\n  recursive: boolean;\n} {\n  validateObject(options, 'options');\n  const { encoding = 'utf8', bufferSize = 32, recursive = false } = options;\n  if (!Buffer.isEncoding(encoding) && encoding !== 'buffer') {\n    throw new ERR_INVALID_ARG_VALUE('options.encoding', encoding);\n  }\n\n  // We don't implement the bufferSize option in any meaningful way but we\n  // do at least validate it.\n  validateUint32(bufferSize, 'options.bufferSize');\n  validateBoolean(recursive, 'options.recursive');\n  return {\n    path: normalizePath(path),\n    encoding,\n    recursive,\n  };\n}\n\nexport type WriteSyncOptions = {\n  offset?: number | undefined;\n  length?: number | undefined;\n  position?: Position | undefined;\n};\n\nexport function validateWriteArgs(\n  fd: number,\n  buffer: NodeJS.ArrayBufferView | string,\n  offsetOrOptions: WriteSyncOptions | Position | undefined,\n  length: number | ValidEncoding | undefined,\n  position: Position | undefined\n): { fd: number; buffer: Buffer[]; position: Position } {\n  fd = getValidatedFd(fd);\n\n  let offset: number | undefined | null = offsetOrOptions as number;\n  if (isArrayBufferView(buffer)) {\n    if (typeof offsetOrOptions === 'object' && offsetOrOptions != null) {\n      ({\n        offset = 0,\n        length = buffer.byteLength,\n        position = null,\n      } = (offsetOrOptions as WriteSyncOptions | null) || {});\n      offset ??= 0;\n      validateInteger(offset, 'offset', 0);\n      offset += buffer.byteOffset;\n    } else {\n      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n      if (offset != null) {\n        validateInteger(offset, 'offset', 0);\n      }\n      offset ??= 0;\n      offset += buffer.byteOffset;\n      length ??= buffer.byteLength;\n      position ??= null;\n    }\n\n    validatePosition(position, 'position');\n    validateInteger(length, 'length', 0);\n\n    // Validate that the offset + length do not exceed the buffer's byte length.\n    if (length > buffer.byteLength) {\n      throw new ERR_BUFFER_OUT_OF_BOUNDS('length');\n    }\n    if (offset > length) {\n      throw new ERR_BUFFER_OUT_OF_BOUNDS('offset');\n    }\n\n    return {\n      fd,\n      buffer: [Buffer.from(buffer.buffer, offset, length)],\n      position,\n    };\n  }\n\n  if (typeof buffer !== 'string') {\n    throw new ERR_INVALID_ARG_TYPE(\n      'buffer',\n      ['string', 'Buffer', 'TypedArray', 'DataView'],\n      buffer\n    );\n  }\n\n  // In this case, offsetOrOptions must either be a number, bigint, or null.\n  validatePosition(offsetOrOptions, 'position');\n  position = offsetOrOptions;\n\n  // In this instance, buffer is a string and the length arg specifies\n  // the encoding to use.\n  validateEncoding(buffer, length as string);\n  return {\n    fd,\n    buffer: [Buffer.from(buffer, length as string /* encoding */)],\n    position,\n  };\n}\n\nexport function validateWriteFileArgs(\n  path: number | FilePath,\n  data: string | ArrayBufferView,\n  options: ValidEncoding | WriteFileOptions\n): {\n  path: number | URL;\n  data: NodeJS.ArrayBufferView;\n  append: boolean;\n  exclusive: boolean;\n} {\n  if (typeof path === 'number') {\n    path = getValidatedFd(path);\n  } else {\n    path = normalizePath(path);\n  }\n\n  if (typeof options === 'string' || options == null) {\n    options = { encoding: options as BufferEncoding | null };\n  }\n\n  validateObject(options, 'options');\n  const {\n    encoding = 'utf8',\n    mode = 0o666,\n    flag = 'w',\n    flush = false,\n  } = options;\n  // @ts-expect-error TS2367 types does not overlap.\n  if (encoding !== 'buffer' && !Buffer.isEncoding(encoding)) {\n    throw new ERR_INVALID_ARG_VALUE('options.encoding', encoding);\n  }\n  validateBoolean(flush, 'options.flush');\n  parseFileMode(mode, 'options.mode', 0o666);\n  const newFlag = stringToFlags(flag);\n\n  const append = Boolean(newFlag & O_APPEND);\n  const write = Boolean(newFlag & O_WRONLY || newFlag & O_RDWR) || append;\n  const exclusive = Boolean(newFlag & O_EXCL);\n\n  if (!write) {\n    throw new ERR_INVALID_ARG_VALUE(\n      'flag',\n      flag,\n      'must be indicate write or append'\n    );\n  }\n\n  // We're not currently implementing the exclusive flag. We're validating\n  // it here just to use it so the compiler doesn't complain.\n  validateBoolean(exclusive, 'options.exclusive');\n\n  if (typeof data === 'string') {\n    data = Buffer.from(data, encoding);\n  }\n\n  if (!isArrayBufferView(data)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'data',\n      ['string', 'Buffer', 'TypedArray', 'DataView'],\n      data\n    );\n  }\n\n  return {\n    path,\n    data: data as NodeJS.ArrayBufferView,\n    append,\n    exclusive,\n  };\n}\n\nexport function validateReadArgs(\n  fd: number,\n  buffer: NodeJS.ArrayBufferView,\n  offsetOrOptions: ReadOptions | number | null,\n  length: number | undefined,\n  position: Position | undefined\n): { fd: number; buffer: Buffer[]; length: number; position: Position } {\n  fd = getValidatedFd(fd);\n\n  // Great fun with polymorphism here. We're going to normalize the arguments\n  // to match the first signature (fd, buffer, offset, length, position).\n  //\n  // If the third argument is an object, then we will pull the offset, length,\n  // and position from it, ignoring the remaining arguments. If the third\n  // argument is a number, then we will use it as the offset and pull the\n  // length and position from the fourth and fifth arguments. If the third\n  // position is any other type, then we will throw an error.\n\n  if (!isArrayBufferView(buffer)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'buffer',\n      ['Buffer', 'TypedArray', 'DataView'],\n      buffer\n    );\n  }\n\n  let actualOffset = buffer.byteOffset;\n  let actualLength = buffer.byteLength;\n  let actualPosition = position;\n\n  // Handle the case where the third argument is an options object\n  if (offsetOrOptions != null && typeof offsetOrOptions === 'object') {\n    const {\n      offset = 0,\n      length = buffer.byteLength - offset,\n      position = null,\n    } = offsetOrOptions;\n    actualOffset = offset;\n    actualLength = length;\n    actualPosition = position;\n  }\n  // Handle the case where the third argument is a number (offset)\n  else if (typeof offsetOrOptions === 'number') {\n    actualOffset = offsetOrOptions;\n    actualLength = length ?? buffer.byteLength - actualOffset;\n    actualPosition = position;\n  } else {\n    throw new ERR_INVALID_ARG_TYPE(\n      'offset',\n      ['number', 'object'],\n      offsetOrOptions\n    );\n  }\n\n  validateUint32(actualOffset, 'offset');\n  validateUint32(actualLength, 'length');\n  validatePosition(actualPosition, 'position');\n\n  // The actualOffset plus actualLength must not exceed the buffer's byte length.\n  if (actualOffset + actualLength > buffer.byteLength) {\n    throw new ERR_INVALID_ARG_VALUE('offset', actualOffset, 'out of bounds');\n  }\n\n  return {\n    fd,\n    buffer: [Buffer.from(buffer.buffer, actualOffset, actualLength)],\n    length: actualLength,\n    position: actualPosition,\n  };\n}\n\n// Validate the mode argument for either copyFile or access operations.\n// The mode argument is a bitmask that can be used to specify the access\n// permissions for a file. The valid modes depend on the operation type:\n// - For copyFile, the valid modes are COPYFILE_EXCL, COPYFILE_FICLONE, and\n//   COPYFILE_FICLONE_FORCE, with a default of 0.\n// - For access, the valid modes are F_OK, R_OK, W_OK, and X_OK, with a\n//   default of F_OK.\n// In either case, the mode must be a valid bitmask integer within the\n// a given range. If the mode is not provided, the default value is used.\n// Throws ERR_INVALID_ARG_TYPE if the mode is not a valid integer, or\n// ERR_OUT_OF_RANGE if the mode is outside the valid range.\nfunction validateMode(\n  mode: number | undefined,\n  type: 'copyFile' | 'access' = 'access'\n): number {\n  // The access modes can be any of F_OK, R_OK, W_OK or X_OK. Some might not be\n  // available on specific systems. They can be used in combination as well\n  // (F_OK | R_OK | W_OK | X_OK).\n  let min = Math.min(F_OK, W_OK, R_OK, X_OK);\n  let max = F_OK | W_OK | R_OK | X_OK;\n  let def = F_OK;\n  if (type === 'copyFile') {\n    // The copy modes can be any of COPYFILE_EXCL, COPYFILE_FICLONE or\n    // COPYFILE_FICLONE_FORCE. They can be used in combination as well\n    // (COPYFILE_EXCL | COPYFILE_FICLONE | COPYFILE_FICLONE_FORCE).\n    min = Math.min(\n      0,\n      COPYFILE_EXCL,\n      COPYFILE_FICLONE,\n      UV_FS_COPYFILE_FICLONE_FORCE\n    );\n    max = COPYFILE_EXCL | COPYFILE_FICLONE | UV_FS_COPYFILE_FICLONE_FORCE;\n    def = mode || 0;\n  } else {\n    strictEqual(type, 'access');\n  }\n  mode ??= def;\n  validateInteger(mode, 'mode', min, max);\n  return mode;\n}\n\nfunction assertEncoding(encoding: unknown): asserts encoding is string {\n  if (\n    encoding &&\n    encoding !== 'buffer' &&\n    !Buffer.isEncoding(encoding as string)\n  ) {\n    const reason = 'is invalid encoding';\n    throw new ERR_INVALID_ARG_VALUE('encoding', encoding, reason);\n  }\n}\n\nexport function getOptions(\n  options: string | Record<string, unknown> | null,\n  defaultOptions: Record<string, unknown> = {}\n): Record<string, unknown> {\n  if (options == null || typeof options === 'function') {\n    return defaultOptions;\n  }\n\n  if (typeof options === 'string') {\n    defaultOptions = { ...defaultOptions };\n    defaultOptions.encoding = options;\n    options = defaultOptions;\n  } else if (typeof options !== 'object') {\n    throw new ERR_INVALID_ARG_TYPE('options', ['string', 'Object'], options);\n  }\n\n  if (options.encoding !== 'buffer') assertEncoding(options.encoding);\n\n  if (options.signal !== undefined) {\n    validateAbortSignal(options.signal, 'options.signal');\n  }\n\n  return options;\n}\n\nexport function stringToFlags(\n  flags: number | null | undefined | string,\n  name: string = 'flags'\n): number {\n  if (typeof flags === 'number') {\n    validateInt32(flags, name);\n    return flags;\n  }\n\n  if (flags == null) {\n    return O_RDONLY;\n  }\n\n  switch (flags) {\n    case 'r':\n      return O_RDONLY;\n    case 'rs': // Fall through.\n    case 'sr':\n      return O_RDONLY | O_SYNC;\n    case 'r+':\n      return O_RDWR;\n    case 'rs+': // Fall through.\n    case 'sr+':\n      return O_RDWR | O_SYNC;\n\n    case 'w':\n      return O_TRUNC | O_CREAT | O_WRONLY;\n    case 'wx': // Fall through.\n    case 'xw':\n      return O_TRUNC | O_CREAT | O_WRONLY | O_EXCL;\n\n    case 'w+':\n      return O_TRUNC | O_CREAT | O_RDWR;\n    case 'wx+': // Fall through.\n    case 'xw+':\n      return O_TRUNC | O_CREAT | O_RDWR | O_EXCL;\n\n    case 'a':\n      return O_APPEND | O_CREAT | O_WRONLY;\n    case 'ax': // Fall through.\n    case 'xa':\n      return O_APPEND | O_CREAT | O_WRONLY | O_EXCL;\n    case 'as': // Fall through.\n    case 'sa':\n      return O_APPEND | O_CREAT | O_WRONLY | O_SYNC;\n\n    case 'a+':\n      return O_APPEND | O_CREAT | O_RDWR;\n    case 'ax+': // Fall through.\n    case 'xa+':\n      return O_APPEND | O_CREAT | O_RDWR | O_EXCL;\n    case 'as+': // Fall through.\n    case 'sa+':\n      return O_APPEND | O_CREAT | O_RDWR | O_SYNC;\n  }\n\n  throw new ERR_INVALID_ARG_VALUE('flags', flags);\n}\n\nexport type Position = number | null | bigint;\n\nexport function validatePosition(\n  position: unknown,\n  name: string\n): asserts position is Position {\n  if (typeof position === 'number') {\n    validateUint32(position, name);\n  } else if (typeof position !== 'bigint' && position !== null) {\n    throw new ERR_INVALID_ARG_TYPE(\n      name,\n      ['integer', 'bigint', 'null'],\n      position\n    );\n  }\n}\n\nexport function getValidatedFd(fd: number, propName: string = 'fd'): number {\n  if (Object.is(fd, -0)) {\n    return 0;\n  }\n\n  validateInt32(fd, propName, 0);\n\n  return fd;\n}\n\nexport function validateBufferArray(\n  buffers: unknown,\n  propName: string = 'buffer'\n): ArrayBufferView[] {\n  if (!Array.isArray(buffers))\n    throw new ERR_INVALID_ARG_TYPE(propName, 'ArrayBufferView[]', buffers);\n\n  for (let i = 0; i < buffers.length; i++) {\n    if (!isArrayBufferView(buffers[i]))\n      throw new ERR_INVALID_ARG_TYPE(propName, 'ArrayBufferView[]', buffers);\n  }\n\n  return buffers as ArrayBufferView[];\n}\n\n// Our implementation of the Stats class differs a bit from Node.js' in that\n// the one in Node.js uses the older function-style class. However, use of\n// new fs.Stats(...) has been deprecated in Node.js for quite some time and\n// users really aren't supposed to be trying to create their own Stats objects.\n// Therefore, we intentionally use a class-style object here and make it an\n// error to try to create your own Stats object using the constructor.\nexport class Stats {\n  dev: number | bigint;\n  ino: number | bigint;\n  mode: number | bigint;\n  nlink: number | bigint;\n  uid: number | bigint;\n  gid: number | bigint;\n  rdev: number | bigint;\n  size: number | bigint;\n  blksize: number | bigint;\n  blocks: number | bigint;\n  atimeMs: number | bigint;\n  mtimeMs: number | bigint;\n  ctimeMs: number | bigint;\n  birthtimeMs: number | bigint;\n  atimeNs?: bigint;\n  mtimeNs?: bigint;\n  ctimeNs?: bigint;\n  birthtimeNs?: bigint;\n  atime: Date;\n  mtime: Date;\n  ctime: Date;\n  birthtime: Date;\n\n  constructor(badge: symbol, stat: InternalStat, options: { bigint: boolean }) {\n    // The kBadge symbol is never exported for users. We use it as an internal\n    // marker to ensure that only internal code can create a Stats object using\n    // the constructor.\n    if (badge !== kBadge) {\n      throw new TypeError('Illegal constructor');\n    }\n\n    // All nodes are always readable\n    this.mode = 0o444;\n    if (stat.writable) {\n      this.mode |= 0o222; // writable\n    }\n\n    if (stat.device) {\n      this.mode |= S_IFCHR;\n    } else {\n      switch (stat.type) {\n        case 'file':\n          this.mode |= S_IFREG;\n          break;\n        case 'directory':\n          this.mode |= S_IFDIR;\n          break;\n        case 'symlink':\n          this.mode |= S_IFLNK;\n          break;\n      }\n    }\n\n    if (options.bigint) {\n      this.dev = BigInt(stat.device);\n      this.size = BigInt(stat.size);\n\n      this.mode = BigInt(this.mode);\n      this.atimeNs = 0n;\n      this.mtimeNs = stat.lastModified;\n      this.ctimeNs = stat.lastModified;\n      this.birthtimeNs = stat.created;\n      this.atimeMs = this.atimeNs / 1_000_000n;\n      this.mtimeMs = this.mtimeNs / 1_000_000n;\n      this.ctimeMs = this.ctimeNs / 1_000_000n;\n      this.birthtimeMs = this.birthtimeNs / 1_000_000n;\n      this.atime = new Date(Number(this.atimeMs));\n      this.mtime = new Date(Number(this.mtimeMs));\n      this.ctime = new Date(Number(this.ctimeMs));\n      this.birthtime = new Date(Number(this.birthtimeMs));\n\n      // We have no meaningful definition of these values.\n      this.ino = 0n;\n      this.nlink = 1n;\n      this.uid = 0n;\n      this.gid = 0n;\n      this.rdev = 0n;\n      this.blksize = 0n;\n      this.blocks = 0n;\n    } else {\n      this.dev = Number(stat.device);\n      this.size = stat.size;\n\n      this.atimeMs = 0;\n      this.mtimeMs = Number(stat.lastModified) / 1_000_000;\n      this.ctimeMs = Number(stat.lastModified) / 1_000_000;\n      this.birthtimeMs = Number(stat.created) / 1_000_000;\n      this.atime = new Date(this.atimeMs);\n      this.mtime = new Date(this.mtimeMs);\n      this.ctime = new Date(this.ctimeMs);\n      this.birthtime = new Date(this.birthtimeMs);\n\n      // We have no meaningful definition of these values.\n      this.ino = 0;\n      this.nlink = 1;\n      this.uid = 0;\n      this.gid = 0;\n      this.rdev = 0;\n      this.blksize = 0;\n      this.blocks = 0;\n    }\n  }\n\n  isBlockDevice(): boolean {\n    return (Number(this.mode) & S_IFMT) === S_IFBLK;\n  }\n\n  isCharacterDevice(): boolean {\n    return (Number(this.mode) & S_IFMT) === S_IFCHR;\n  }\n\n  isDirectory(): boolean {\n    return (Number(this.mode) & S_IFMT) === S_IFDIR;\n  }\n\n  isFIFO(): boolean {\n    return (Number(this.mode) & S_IFMT) === S_IFIFO;\n  }\n\n  isFile(): boolean {\n    return (Number(this.mode) & S_IFMT) === S_IFREG;\n  }\n\n  isSocket(): boolean {\n    return (Number(this.mode) & S_IFMT) === S_IFSOCK;\n  }\n\n  isSymbolicLink(): boolean {\n    return (Number(this.mode) & S_IFMT) === S_IFLNK;\n  }\n}\n"
  },
  {
    "path": "src/node/internal/internal_http.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport {\n  ERR_INVALID_HTTP_TOKEN,\n  ERR_INVALID_CHAR,\n  ERR_HTTP_INVALID_HEADER_VALUE,\n} from 'node-internal:internal_errors';\n\nconst tokenRegExp = /^[\\^_`a-zA-Z\\-0-9!#$%&'*+.|~]+$/;\n/**\n * Verifies that the given val is a valid HTTP token\n * per the rules defined in RFC 7230\n * See https://tools.ietf.org/html/rfc7230#section-3.2.6\n */\nexport function _checkIsHttpToken(val: string): boolean {\n  return tokenRegExp.test(val);\n}\n\nconst headerCharRegex = /[^\\t\\x20-\\x7e\\x80-\\xff]/;\n/**\n * True if val contains an invalid field-vchar\n *  field-value    = *( field-content / obs-fold )\n *  field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]\n *  field-vchar    = VCHAR / obs-text\n */\nexport function _checkInvalidHeaderChar(\n  val: number | string | ReadonlyArray<string>\n): boolean {\n  if (Array.isArray(val)) {\n    return val.some((v: string) => headerCharRegex.test(v));\n  }\n  return headerCharRegex.test(`${val}`);\n}\n\nexport function validateHeaderName(\n  name: string,\n  label: string = 'Header name'\n): void {\n  if (typeof name !== 'string' || !name || !_checkIsHttpToken(name)) {\n    throw new ERR_INVALID_HTTP_TOKEN(label, name);\n  }\n}\n\nexport function validateHeaderValue(\n  name: string,\n  value: number | string | ReadonlyArray<string> | undefined\n): asserts value is string {\n  if (value === undefined) {\n    throw new ERR_HTTP_INVALID_HEADER_VALUE(value, name);\n  }\n  if (_checkInvalidHeaderChar(value)) {\n    throw new ERR_INVALID_CHAR('header content', name);\n  }\n}\n\nexport function utcDate(): string {\n  return new Date().toUTCString();\n}\n\nexport const chunkExpression = /(?:^|\\W)chunked(?:$|\\W)/i;\n"
  },
  {
    "path": "src/node/internal/internal_http2_constants.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// More beautiful numbers\nconst NGHTTP2_ERR_FRAME_SIZE_ERROR = -522;\nconst NGHTTP2_SESSION_SERVER = 0;\nconst NGHTTP2_SESSION_CLIENT = 1;\nconst NGHTTP2_STREAM_STATE_IDLE = 1;\nconst NGHTTP2_STREAM_STATE_OPEN = 2;\nconst NGHTTP2_STREAM_STATE_RESERVED_LOCAL = 3;\nconst NGHTTP2_STREAM_STATE_RESERVED_REMOTE = 4;\nconst NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL = 5;\nconst NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE = 6;\nconst NGHTTP2_STREAM_STATE_CLOSED = 7;\nconst NGHTTP2_FLAG_NONE = 0;\nconst NGHTTP2_FLAG_END_STREAM = 1;\nconst NGHTTP2_FLAG_END_HEADERS = 4;\nconst NGHTTP2_FLAG_ACK = 1;\nconst NGHTTP2_FLAG_PADDED = 8;\nconst NGHTTP2_FLAG_PRIORITY = 32;\nconst DEFAULT_SETTINGS_HEADER_TABLE_SIZE = 4096;\nconst DEFAULT_SETTINGS_ENABLE_PUSH = 1;\nconst DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS = 4_294_967_295;\nconst DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE = 65_535;\nconst DEFAULT_SETTINGS_MAX_FRAME_SIZE = 16_384;\nconst DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE = 65_535;\nconst DEFAULT_SETTINGS_ENABLE_CONNECT_PROTOCOL = 0;\nconst MAX_MAX_FRAME_SIZE = 16_777_215;\nconst MIN_MAX_FRAME_SIZE = 16_384;\nconst MAX_INITIAL_WINDOW_SIZE = 2_147_483_647;\nconst NGHTTP2_SETTINGS_HEADER_TABLE_SIZE = 1;\nconst NGHTTP2_SETTINGS_ENABLE_PUSH = 2;\nconst NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS = 3;\nconst NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE = 4;\nconst NGHTTP2_SETTINGS_MAX_FRAME_SIZE = 5;\nconst NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE = 6;\nconst NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL = 8;\nconst PADDING_STRATEGY_NONE = 0;\nconst PADDING_STRATEGY_ALIGNED = 1;\nconst PADDING_STRATEGY_MAX = 2;\nconst PADDING_STRATEGY_CALLBACK = 1;\nconst NGHTTP2_NO_ERROR = 0;\nconst NGHTTP2_PROTOCOL_ERROR = 1;\nconst NGHTTP2_INTERNAL_ERROR = 2;\nconst NGHTTP2_FLOW_CONTROL_ERROR = 3;\nconst NGHTTP2_SETTINGS_TIMEOUT = 4;\nconst NGHTTP2_STREAM_CLOSED = 5;\nconst NGHTTP2_FRAME_SIZE_ERROR = 6;\nconst NGHTTP2_REFUSED_STREAM = 7;\nconst NGHTTP2_CANCEL = 8;\nconst NGHTTP2_COMPRESSION_ERROR = 9;\nconst NGHTTP2_CONNECT_ERROR = 10;\nconst NGHTTP2_ENHANCE_YOUR_CALM = 11;\nconst NGHTTP2_INADEQUATE_SECURITY = 12;\nconst NGHTTP2_HTTP_1_1_REQUIRED = 13;\nconst NGHTTP2_DEFAULT_WEIGHT = 16;\nconst HTTP2_HEADER_STATUS = ':status';\nconst HTTP2_HEADER_METHOD = ':method';\nconst HTTP2_HEADER_AUTHORITY = ':authority';\nconst HTTP2_HEADER_SCHEME = ':scheme';\nconst HTTP2_HEADER_PATH = ':path';\nconst HTTP2_HEADER_PROTOCOL = ':protocol';\nconst HTTP2_HEADER_ACCEPT_ENCODING = 'accept-encoding';\nconst HTTP2_HEADER_ACCEPT_LANGUAGE = 'accept-language';\nconst HTTP2_HEADER_ACCEPT_RANGES = 'accept-ranges';\nconst HTTP2_HEADER_ACCEPT = 'accept';\nconst HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS =\n  'access-control-allow-credentials';\nconst HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS =\n  'access-control-allow-headers';\nconst HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS =\n  'access-control-allow-methods';\nconst HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN = 'access-control-allow-origin';\nconst HTTP2_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS =\n  'access-control-expose-headers';\nconst HTTP2_HEADER_ACCESS_CONTROL_REQUEST_HEADERS =\n  'access-control-request-headers';\nconst HTTP2_HEADER_ACCESS_CONTROL_REQUEST_METHOD =\n  'access-control-request-method';\nconst HTTP2_HEADER_AGE = 'age';\nconst HTTP2_HEADER_AUTHORIZATION = 'authorization';\nconst HTTP2_HEADER_CACHE_CONTROL = 'cache-control';\nconst HTTP2_HEADER_CONNECTION = 'connection';\nconst HTTP2_HEADER_CONTENT_DISPOSITION = 'content-disposition';\nconst HTTP2_HEADER_CONTENT_ENCODING = 'content-encoding';\nconst HTTP2_HEADER_CONTENT_LENGTH = 'content-length';\nconst HTTP2_HEADER_CONTENT_TYPE = 'content-type';\nconst HTTP2_HEADER_COOKIE = 'cookie';\nconst HTTP2_HEADER_DATE = 'date';\nconst HTTP2_HEADER_ETAG = 'etag';\nconst HTTP2_HEADER_FORWARDED = 'forwarded';\nconst HTTP2_HEADER_HOST = 'host';\nconst HTTP2_HEADER_IF_MODIFIED_SINCE = 'if-modified-since';\nconst HTTP2_HEADER_IF_NONE_MATCH = 'if-none-match';\nconst HTTP2_HEADER_IF_RANGE = 'if-range';\nconst HTTP2_HEADER_LAST_MODIFIED = 'last-modified';\nconst HTTP2_HEADER_LINK = 'link';\nconst HTTP2_HEADER_LOCATION = 'location';\nconst HTTP2_HEADER_RANGE = 'range';\nconst HTTP2_HEADER_REFERER = 'referer';\nconst HTTP2_HEADER_SERVER = 'server';\nconst HTTP2_HEADER_SET_COOKIE = 'set-cookie';\nconst HTTP2_HEADER_STRICT_TRANSPORT_SECURITY = 'strict-transport-security';\nconst HTTP2_HEADER_TRANSFER_ENCODING = 'transfer-encoding';\nconst HTTP2_HEADER_TE = 'te';\nconst HTTP2_HEADER_UPGRADE_INSECURE_REQUESTS = 'upgrade-insecure-requests';\nconst HTTP2_HEADER_UPGRADE = 'upgrade';\nconst HTTP2_HEADER_USER_AGENT = 'user-agent';\nconst HTTP2_HEADER_VARY = 'vary';\nconst HTTP2_HEADER_X_CONTENT_TYPE_OPTIONS = 'x-content-type-options';\nconst HTTP2_HEADER_X_FRAME_OPTIONS = 'x-frame-options';\nconst HTTP2_HEADER_KEEP_ALIVE = 'keep-alive';\nconst HTTP2_HEADER_PROXY_CONNECTION = 'proxy-connection';\nconst HTTP2_HEADER_X_XSS_PROTECTION = 'x-xss-protection';\nconst HTTP2_HEADER_ALT_SVC = 'alt-svc';\nconst HTTP2_HEADER_CONTENT_SECURITY_POLICY = 'content-security-policy';\nconst HTTP2_HEADER_EARLY_DATA = 'early-data';\nconst HTTP2_HEADER_EXPECT_CT = 'expect-ct';\nconst HTTP2_HEADER_ORIGIN = 'origin';\nconst HTTP2_HEADER_PURPOSE = 'purpose';\nconst HTTP2_HEADER_TIMING_ALLOW_ORIGIN = 'timing-allow-origin';\nconst HTTP2_HEADER_X_FORWARDED_FOR = 'x-forwarded-for';\nconst HTTP2_HEADER_PRIORITY = 'priority';\nconst HTTP2_HEADER_ACCEPT_CHARSET = 'accept-charset';\nconst HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE = 'access-control-max-age';\nconst HTTP2_HEADER_ALLOW = 'allow';\nconst HTTP2_HEADER_CONTENT_LANGUAGE = 'content-language';\nconst HTTP2_HEADER_CONTENT_LOCATION = 'content-location';\nconst HTTP2_HEADER_CONTENT_MD5 = 'content-md5';\nconst HTTP2_HEADER_CONTENT_RANGE = 'content-range';\nconst HTTP2_HEADER_DNT = 'dnt';\nconst HTTP2_HEADER_EXPECT = 'expect';\nconst HTTP2_HEADER_EXPIRES = 'expires';\nconst HTTP2_HEADER_FROM = 'from';\nconst HTTP2_HEADER_IF_MATCH = 'if-match';\nconst HTTP2_HEADER_IF_UNMODIFIED_SINCE = 'if-unmodified-since';\nconst HTTP2_HEADER_MAX_FORWARDS = 'max-forwards';\nconst HTTP2_HEADER_PREFER = 'prefer';\nconst HTTP2_HEADER_PROXY_AUTHENTICATE = 'proxy-authenticate';\nconst HTTP2_HEADER_PROXY_AUTHORIZATION = 'proxy-authorization';\nconst HTTP2_HEADER_REFRESH = 'refresh';\nconst HTTP2_HEADER_RETRY_AFTER = 'retry-after';\nconst HTTP2_HEADER_TRAILER = 'trailer';\nconst HTTP2_HEADER_TK = 'tk';\nconst HTTP2_HEADER_VIA = 'via';\nconst HTTP2_HEADER_WARNING = 'warning';\nconst HTTP2_HEADER_WWW_AUTHENTICATE = 'www-authenticate';\nconst HTTP2_HEADER_HTTP2_SETTINGS = 'http2-settings';\nconst HTTP2_METHOD_ACL = 'ACL';\nconst HTTP2_METHOD_BASELINE_CONTROL = 'BASELINE-CONTROL';\nconst HTTP2_METHOD_BIND = 'BIND';\nconst HTTP2_METHOD_CHECKIN = 'CHECKIN';\nconst HTTP2_METHOD_CHECKOUT = 'CHECKOUT';\nconst HTTP2_METHOD_CONNECT = 'CONNECT';\nconst HTTP2_METHOD_COPY = 'COPY';\nconst HTTP2_METHOD_DELETE = 'DELETE';\nconst HTTP2_METHOD_GET = 'GET';\nconst HTTP2_METHOD_HEAD = 'HEAD';\nconst HTTP2_METHOD_LABEL = 'LABEL';\nconst HTTP2_METHOD_LINK = 'LINK';\nconst HTTP2_METHOD_LOCK = 'LOCK';\nconst HTTP2_METHOD_MERGE = 'MERGE';\nconst HTTP2_METHOD_MKACTIVITY = 'MKACTIVITY';\nconst HTTP2_METHOD_MKCALENDAR = 'MKCALENDAR';\nconst HTTP2_METHOD_MKCOL = 'MKCOL';\nconst HTTP2_METHOD_MKREDIRECTREF = 'MKREDIRECTREF';\nconst HTTP2_METHOD_MKWORKSPACE = 'MKWORKSPACE';\nconst HTTP2_METHOD_MOVE = 'MOVE';\nconst HTTP2_METHOD_OPTIONS = 'OPTIONS';\nconst HTTP2_METHOD_ORDERPATCH = 'ORDERPATCH';\nconst HTTP2_METHOD_PATCH = 'PATCH';\nconst HTTP2_METHOD_POST = 'POST';\nconst HTTP2_METHOD_PRI = 'PRI';\nconst HTTP2_METHOD_PROPFIND = 'PROPFIND';\nconst HTTP2_METHOD_PROPPATCH = 'PROPPATCH';\nconst HTTP2_METHOD_PUT = 'PUT';\nconst HTTP2_METHOD_REBIND = 'REBIND';\nconst HTTP2_METHOD_REPORT = 'REPORT';\nconst HTTP2_METHOD_SEARCH = 'SEARCH';\nconst HTTP2_METHOD_TRACE = 'TRACE';\nconst HTTP2_METHOD_UNBIND = 'UNBIND';\nconst HTTP2_METHOD_UNCHECKOUT = 'UNCHECKOUT';\nconst HTTP2_METHOD_UNLINK = 'UNLINK';\nconst HTTP2_METHOD_UNLOCK = 'UNLOCK';\nconst HTTP2_METHOD_UPDATE = 'UPDATE';\nconst HTTP2_METHOD_UPDATEREDIRECTREF = 'UPDATEREDIRECTREF';\nconst HTTP2_METHOD_VERSION_CONTROL = 'VERSION-CONTROL';\nconst HTTP_STATUS_CONTINUE = 100;\nconst HTTP_STATUS_SWITCHING_PROTOCOLS = 101;\nconst HTTP_STATUS_PROCESSING = 102;\nconst HTTP_STATUS_EARLY_HINTS = 103;\nconst HTTP_STATUS_OK = 200;\nconst HTTP_STATUS_CREATED = 201;\nconst HTTP_STATUS_ACCEPTED = 202;\nconst HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION = 203;\nconst HTTP_STATUS_NO_CONTENT = 204;\nconst HTTP_STATUS_RESET_CONTENT = 205;\nconst HTTP_STATUS_PARTIAL_CONTENT = 206;\nconst HTTP_STATUS_MULTI_STATUS = 207;\nconst HTTP_STATUS_ALREADY_REPORTED = 208;\nconst HTTP_STATUS_IM_USED = 226;\nconst HTTP_STATUS_MULTIPLE_CHOICES = 300;\nconst HTTP_STATUS_MOVED_PERMANENTLY = 301;\nconst HTTP_STATUS_FOUND = 302;\nconst HTTP_STATUS_SEE_OTHER = 303;\nconst HTTP_STATUS_NOT_MODIFIED = 304;\nconst HTTP_STATUS_USE_PROXY = 305;\nconst HTTP_STATUS_TEMPORARY_REDIRECT = 307;\nconst HTTP_STATUS_PERMANENT_REDIRECT = 308;\nconst HTTP_STATUS_BAD_REQUEST = 400;\nconst HTTP_STATUS_UNAUTHORIZED = 401;\nconst HTTP_STATUS_PAYMENT_REQUIRED = 402;\nconst HTTP_STATUS_FORBIDDEN = 403;\nconst HTTP_STATUS_NOT_FOUND = 404;\nconst HTTP_STATUS_METHOD_NOT_ALLOWED = 405;\nconst HTTP_STATUS_NOT_ACCEPTABLE = 406;\nconst HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED = 407;\nconst HTTP_STATUS_REQUEST_TIMEOUT = 408;\nconst HTTP_STATUS_CONFLICT = 409;\nconst HTTP_STATUS_GONE = 410;\nconst HTTP_STATUS_LENGTH_REQUIRED = 411;\nconst HTTP_STATUS_PRECONDITION_FAILED = 412;\nconst HTTP_STATUS_PAYLOAD_TOO_LARGE = 413;\nconst HTTP_STATUS_URI_TOO_LONG = 414;\nconst HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415;\nconst HTTP_STATUS_RANGE_NOT_SATISFIABLE = 416;\nconst HTTP_STATUS_EXPECTATION_FAILED = 417;\nconst HTTP_STATUS_TEAPOT = 418;\nconst HTTP_STATUS_MISDIRECTED_REQUEST = 421;\nconst HTTP_STATUS_UNPROCESSABLE_ENTITY = 422;\nconst HTTP_STATUS_LOCKED = 423;\nconst HTTP_STATUS_FAILED_DEPENDENCY = 424;\nconst HTTP_STATUS_TOO_EARLY = 425;\nconst HTTP_STATUS_UPGRADE_REQUIRED = 426;\nconst HTTP_STATUS_PRECONDITION_REQUIRED = 428;\nconst HTTP_STATUS_TOO_MANY_REQUESTS = 429;\nconst HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431;\nconst HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = 451;\nconst HTTP_STATUS_INTERNAL_SERVER_ERROR = 500;\nconst HTTP_STATUS_NOT_IMPLEMENTED = 501;\nconst HTTP_STATUS_BAD_GATEWAY = 502;\nconst HTTP_STATUS_SERVICE_UNAVAILABLE = 503;\nconst HTTP_STATUS_GATEWAY_TIMEOUT = 504;\nconst HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED = 505;\nconst HTTP_STATUS_VARIANT_ALSO_NEGOTIATES = 506;\nconst HTTP_STATUS_INSUFFICIENT_STORAGE = 507;\nconst HTTP_STATUS_LOOP_DETECTED = 508;\nconst HTTP_STATUS_BANDWIDTH_LIMIT_EXCEEDED = 509;\nconst HTTP_STATUS_NOT_EXTENDED = 510;\nconst HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511;\n\nexport const constants = {\n  NGHTTP2_ERR_FRAME_SIZE_ERROR,\n  NGHTTP2_SESSION_SERVER,\n  NGHTTP2_SESSION_CLIENT,\n  NGHTTP2_STREAM_STATE_IDLE,\n  NGHTTP2_STREAM_STATE_OPEN,\n  NGHTTP2_STREAM_STATE_RESERVED_LOCAL,\n  NGHTTP2_STREAM_STATE_RESERVED_REMOTE,\n  NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL,\n  NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE,\n  NGHTTP2_STREAM_STATE_CLOSED,\n  NGHTTP2_FLAG_NONE,\n  NGHTTP2_FLAG_END_STREAM,\n  NGHTTP2_FLAG_END_HEADERS,\n  NGHTTP2_FLAG_ACK,\n  NGHTTP2_FLAG_PADDED,\n  NGHTTP2_FLAG_PRIORITY,\n  DEFAULT_SETTINGS_HEADER_TABLE_SIZE,\n  DEFAULT_SETTINGS_ENABLE_PUSH,\n  DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS,\n  DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE,\n  DEFAULT_SETTINGS_MAX_FRAME_SIZE,\n  DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE,\n  DEFAULT_SETTINGS_ENABLE_CONNECT_PROTOCOL,\n  MAX_MAX_FRAME_SIZE,\n  MIN_MAX_FRAME_SIZE,\n  MAX_INITIAL_WINDOW_SIZE,\n  NGHTTP2_SETTINGS_HEADER_TABLE_SIZE,\n  NGHTTP2_SETTINGS_ENABLE_PUSH,\n  NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,\n  NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE,\n  NGHTTP2_SETTINGS_MAX_FRAME_SIZE,\n  NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE,\n  NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL,\n  PADDING_STRATEGY_NONE,\n  PADDING_STRATEGY_ALIGNED,\n  PADDING_STRATEGY_MAX,\n  PADDING_STRATEGY_CALLBACK,\n  NGHTTP2_NO_ERROR,\n  NGHTTP2_PROTOCOL_ERROR,\n  NGHTTP2_INTERNAL_ERROR,\n  NGHTTP2_FLOW_CONTROL_ERROR,\n  NGHTTP2_SETTINGS_TIMEOUT,\n  NGHTTP2_STREAM_CLOSED,\n  NGHTTP2_FRAME_SIZE_ERROR,\n  NGHTTP2_REFUSED_STREAM,\n  NGHTTP2_CANCEL,\n  NGHTTP2_COMPRESSION_ERROR,\n  NGHTTP2_CONNECT_ERROR,\n  NGHTTP2_ENHANCE_YOUR_CALM,\n  NGHTTP2_INADEQUATE_SECURITY,\n  NGHTTP2_HTTP_1_1_REQUIRED,\n  NGHTTP2_DEFAULT_WEIGHT,\n  HTTP2_HEADER_STATUS,\n  HTTP2_HEADER_METHOD,\n  HTTP2_HEADER_AUTHORITY,\n  HTTP2_HEADER_SCHEME,\n  HTTP2_HEADER_PATH,\n  HTTP2_HEADER_PROTOCOL,\n  HTTP2_HEADER_ACCEPT_ENCODING,\n  HTTP2_HEADER_ACCEPT_LANGUAGE,\n  HTTP2_HEADER_ACCEPT_RANGES,\n  HTTP2_HEADER_ACCEPT,\n  HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS,\n  HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS,\n  HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS,\n  HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,\n  HTTP2_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS,\n  HTTP2_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,\n  HTTP2_HEADER_ACCESS_CONTROL_REQUEST_METHOD,\n  HTTP2_HEADER_AGE,\n  HTTP2_HEADER_AUTHORIZATION,\n  HTTP2_HEADER_CACHE_CONTROL,\n  HTTP2_HEADER_CONNECTION,\n  HTTP2_HEADER_CONTENT_DISPOSITION,\n  HTTP2_HEADER_CONTENT_ENCODING,\n  HTTP2_HEADER_CONTENT_LENGTH,\n  HTTP2_HEADER_CONTENT_TYPE,\n  HTTP2_HEADER_COOKIE,\n  HTTP2_HEADER_DATE,\n  HTTP2_HEADER_ETAG,\n  HTTP2_HEADER_FORWARDED,\n  HTTP2_HEADER_HOST,\n  HTTP2_HEADER_IF_MODIFIED_SINCE,\n  HTTP2_HEADER_IF_NONE_MATCH,\n  HTTP2_HEADER_IF_RANGE,\n  HTTP2_HEADER_LAST_MODIFIED,\n  HTTP2_HEADER_LINK,\n  HTTP2_HEADER_LOCATION,\n  HTTP2_HEADER_RANGE,\n  HTTP2_HEADER_REFERER,\n  HTTP2_HEADER_SERVER,\n  HTTP2_HEADER_SET_COOKIE,\n  HTTP2_HEADER_STRICT_TRANSPORT_SECURITY,\n  HTTP2_HEADER_TRANSFER_ENCODING,\n  HTTP2_HEADER_TE,\n  HTTP2_HEADER_UPGRADE_INSECURE_REQUESTS,\n  HTTP2_HEADER_UPGRADE,\n  HTTP2_HEADER_USER_AGENT,\n  HTTP2_HEADER_VARY,\n  HTTP2_HEADER_X_CONTENT_TYPE_OPTIONS,\n  HTTP2_HEADER_X_FRAME_OPTIONS,\n  HTTP2_HEADER_KEEP_ALIVE,\n  HTTP2_HEADER_PROXY_CONNECTION,\n  HTTP2_HEADER_X_XSS_PROTECTION,\n  HTTP2_HEADER_ALT_SVC,\n  HTTP2_HEADER_CONTENT_SECURITY_POLICY,\n  HTTP2_HEADER_EARLY_DATA,\n  HTTP2_HEADER_EXPECT_CT,\n  HTTP2_HEADER_ORIGIN,\n  HTTP2_HEADER_PURPOSE,\n  HTTP2_HEADER_TIMING_ALLOW_ORIGIN,\n  HTTP2_HEADER_X_FORWARDED_FOR,\n  HTTP2_HEADER_PRIORITY,\n  HTTP2_HEADER_ACCEPT_CHARSET,\n  HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE,\n  HTTP2_HEADER_ALLOW,\n  HTTP2_HEADER_CONTENT_LANGUAGE,\n  HTTP2_HEADER_CONTENT_LOCATION,\n  HTTP2_HEADER_CONTENT_MD5,\n  HTTP2_HEADER_CONTENT_RANGE,\n  HTTP2_HEADER_DNT,\n  HTTP2_HEADER_EXPECT,\n  HTTP2_HEADER_EXPIRES,\n  HTTP2_HEADER_FROM,\n  HTTP2_HEADER_IF_MATCH,\n  HTTP2_HEADER_IF_UNMODIFIED_SINCE,\n  HTTP2_HEADER_MAX_FORWARDS,\n  HTTP2_HEADER_PREFER,\n  HTTP2_HEADER_PROXY_AUTHENTICATE,\n  HTTP2_HEADER_PROXY_AUTHORIZATION,\n  HTTP2_HEADER_REFRESH,\n  HTTP2_HEADER_RETRY_AFTER,\n  HTTP2_HEADER_TRAILER,\n  HTTP2_HEADER_TK,\n  HTTP2_HEADER_VIA,\n  HTTP2_HEADER_WARNING,\n  HTTP2_HEADER_WWW_AUTHENTICATE,\n  HTTP2_HEADER_HTTP2_SETTINGS,\n  HTTP2_METHOD_ACL,\n  HTTP2_METHOD_BASELINE_CONTROL,\n  HTTP2_METHOD_BIND,\n  HTTP2_METHOD_CHECKIN,\n  HTTP2_METHOD_CHECKOUT,\n  HTTP2_METHOD_CONNECT,\n  HTTP2_METHOD_COPY,\n  HTTP2_METHOD_DELETE,\n  HTTP2_METHOD_GET,\n  HTTP2_METHOD_HEAD,\n  HTTP2_METHOD_LABEL,\n  HTTP2_METHOD_LINK,\n  HTTP2_METHOD_LOCK,\n  HTTP2_METHOD_MERGE,\n  HTTP2_METHOD_MKACTIVITY,\n  HTTP2_METHOD_MKCALENDAR,\n  HTTP2_METHOD_MKCOL,\n  HTTP2_METHOD_MKREDIRECTREF,\n  HTTP2_METHOD_MKWORKSPACE,\n  HTTP2_METHOD_MOVE,\n  HTTP2_METHOD_OPTIONS,\n  HTTP2_METHOD_ORDERPATCH,\n  HTTP2_METHOD_PATCH,\n  HTTP2_METHOD_POST,\n  HTTP2_METHOD_PRI,\n  HTTP2_METHOD_PROPFIND,\n  HTTP2_METHOD_PROPPATCH,\n  HTTP2_METHOD_PUT,\n  HTTP2_METHOD_REBIND,\n  HTTP2_METHOD_REPORT,\n  HTTP2_METHOD_SEARCH,\n  HTTP2_METHOD_TRACE,\n  HTTP2_METHOD_UNBIND,\n  HTTP2_METHOD_UNCHECKOUT,\n  HTTP2_METHOD_UNLINK,\n  HTTP2_METHOD_UNLOCK,\n  HTTP2_METHOD_UPDATE,\n  HTTP2_METHOD_UPDATEREDIRECTREF,\n  HTTP2_METHOD_VERSION_CONTROL,\n  HTTP_STATUS_CONTINUE,\n  HTTP_STATUS_SWITCHING_PROTOCOLS,\n  HTTP_STATUS_PROCESSING,\n  HTTP_STATUS_EARLY_HINTS,\n  HTTP_STATUS_OK,\n  HTTP_STATUS_CREATED,\n  HTTP_STATUS_ACCEPTED,\n  HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION,\n  HTTP_STATUS_NO_CONTENT,\n  HTTP_STATUS_RESET_CONTENT,\n  HTTP_STATUS_PARTIAL_CONTENT,\n  HTTP_STATUS_MULTI_STATUS,\n  HTTP_STATUS_ALREADY_REPORTED,\n  HTTP_STATUS_IM_USED,\n  HTTP_STATUS_MULTIPLE_CHOICES,\n  HTTP_STATUS_MOVED_PERMANENTLY,\n  HTTP_STATUS_FOUND,\n  HTTP_STATUS_SEE_OTHER,\n  HTTP_STATUS_NOT_MODIFIED,\n  HTTP_STATUS_USE_PROXY,\n  HTTP_STATUS_TEMPORARY_REDIRECT,\n  HTTP_STATUS_PERMANENT_REDIRECT,\n  HTTP_STATUS_BAD_REQUEST,\n  HTTP_STATUS_UNAUTHORIZED,\n  HTTP_STATUS_PAYMENT_REQUIRED,\n  HTTP_STATUS_FORBIDDEN,\n  HTTP_STATUS_NOT_FOUND,\n  HTTP_STATUS_METHOD_NOT_ALLOWED,\n  HTTP_STATUS_NOT_ACCEPTABLE,\n  HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED,\n  HTTP_STATUS_REQUEST_TIMEOUT,\n  HTTP_STATUS_CONFLICT,\n  HTTP_STATUS_GONE,\n  HTTP_STATUS_LENGTH_REQUIRED,\n  HTTP_STATUS_PRECONDITION_FAILED,\n  HTTP_STATUS_PAYLOAD_TOO_LARGE,\n  HTTP_STATUS_URI_TOO_LONG,\n  HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE,\n  HTTP_STATUS_RANGE_NOT_SATISFIABLE,\n  HTTP_STATUS_EXPECTATION_FAILED,\n  HTTP_STATUS_TEAPOT,\n  HTTP_STATUS_MISDIRECTED_REQUEST,\n  HTTP_STATUS_UNPROCESSABLE_ENTITY,\n  HTTP_STATUS_LOCKED,\n  HTTP_STATUS_FAILED_DEPENDENCY,\n  HTTP_STATUS_TOO_EARLY,\n  HTTP_STATUS_UPGRADE_REQUIRED,\n  HTTP_STATUS_PRECONDITION_REQUIRED,\n  HTTP_STATUS_TOO_MANY_REQUESTS,\n  HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE,\n  HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS,\n  HTTP_STATUS_INTERNAL_SERVER_ERROR,\n  HTTP_STATUS_NOT_IMPLEMENTED,\n  HTTP_STATUS_BAD_GATEWAY,\n  HTTP_STATUS_SERVICE_UNAVAILABLE,\n  HTTP_STATUS_GATEWAY_TIMEOUT,\n  HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED,\n  HTTP_STATUS_VARIANT_ALSO_NEGOTIATES,\n  HTTP_STATUS_INSUFFICIENT_STORAGE,\n  HTTP_STATUS_LOOP_DETECTED,\n  HTTP_STATUS_BANDWIDTH_LIMIT_EXCEEDED,\n  HTTP_STATUS_NOT_EXTENDED,\n  HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED,\n};\n\nfor (const key of Object.keys(constants)) {\n  Object.defineProperty(constants, key, { writable: false });\n}\n"
  },
  {
    "path": "src/node/internal/internal_http_agent.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport { EventEmitter } from 'node-internal:events';\nimport { validateOneOf, validateNumber } from 'node-internal:validators';\nimport { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors';\nimport type {\n  RequestOptions,\n  Agent as _Agent,\n  AgentOptions,\n  ClientRequest,\n  IncomingMessage,\n} from 'node:http';\nimport type { Socket, NetConnectOpts } from 'node:net';\nimport type { Duplex } from 'node:stream';\n\n// This is mostly a stub implementation.\n// We don't intend to support the Agent API right now beyond providing a very limited stub API.\n//\nexport class Agent extends EventEmitter implements _Agent {\n  defaultPort = 80;\n  protocol: string = 'http:';\n\n  options: AgentOptions & { __proto__: null };\n  keepAliveMsecs: number = 1000;\n  keepAlive: boolean = false;\n  maxSockets: number;\n  maxFreeSockets: number;\n  scheduling: 'lifo' | 'fifo' = 'lifo';\n  maxTotalSockets: number;\n  totalSocketCount: number;\n  readonly freeSockets: NodeJS.ReadOnlyDict<Socket[]> = {};\n  readonly sockets: NodeJS.ReadOnlyDict<Socket[]> = {};\n  readonly requests: NodeJS.ReadOnlyDict<IncomingMessage[]> = {};\n\n  constructor(options?: AgentOptions) {\n    super({});\n    this.options = { __proto__: null, ...options };\n\n    if (this.options.noDelay === undefined) this.options.noDelay = true;\n\n    // Don't confuse net and make it think that we're connecting to a pipe\n    this.keepAliveMsecs = this.options.keepAliveMsecs || 1000;\n    this.keepAlive = this.options.keepAlive || false;\n    this.maxSockets = this.options.maxSockets || Agent.defaultMaxSockets;\n    this.maxFreeSockets = this.options.maxFreeSockets || 256;\n    this.scheduling = this.options.scheduling || 'lifo';\n    this.totalSocketCount = 0;\n\n    validateOneOf(this.scheduling, 'scheduling', ['fifo', 'lifo']);\n\n    if (this.options.maxTotalSockets !== undefined) {\n      validateNumber(this.options.maxTotalSockets, 'maxTotalSockets', 1);\n      this.maxTotalSockets = this.options.maxTotalSockets;\n    } else {\n      this.maxTotalSockets = Infinity;\n    }\n  }\n\n  static defaultMaxSockets: number = Infinity;\n\n  createConnection(\n    _options: NetConnectOpts,\n    _callback?: (err: Error | null, stream: Duplex) => void\n  ): Duplex {\n    // We can't create a connection like this because our implementation\n    // relies on fetch() not on node:net sockets. In node.js, method\n    // calls node:net's createConnection method.\n    throw new ERR_METHOD_NOT_IMPLEMENTED('createConnection');\n  }\n\n  getName(options: RequestOptions = {}): string {\n    let name = options.host || 'localhost';\n\n    name += ':';\n    if (options.port) name += options.port.toString();\n\n    name += ':';\n    if (options.localAddress) name += options.localAddress;\n\n    // Pacify parallel/test-http-agent-getname by only appending\n    // the ':' when options.family is set.\n    if (options.family === 4 || options.family === 6)\n      name += `:${options.family}`;\n\n    if (options.socketPath) name += `:${options.socketPath}`;\n\n    return name;\n  }\n\n  addRequest(_req: ClientRequest, _options: unknown): void {\n    // Not implemented. Acts as a no-op.\n  }\n\n  createSocket(\n    _req: ClientRequest,\n    _options: unknown,\n    _callback: VoidFunction\n  ): void {\n    // Not implemented. Acts as a no-op.\n  }\n\n  removeSocket(_socket: Socket): void {\n    // Not implemented. Acts as a no-op.\n  }\n\n  keepSocketAlive(_socket: Socket): boolean {\n    // Not implemented. Acts as a no-op.\n    return false;\n  }\n\n  reuseSocket(_socket: Socket, _req: ClientRequest): void {\n    // Not implemented. Acts as a no-op.\n  }\n\n  destroy(_error?: Error): void {\n    // Not implemented. Acts as a no-op.\n  }\n}\n\nexport const globalAgent = new Agent({\n  keepAlive: true,\n  scheduling: 'lifo',\n  timeout: 5000,\n});\n"
  },
  {
    "path": "src/node/internal/internal_http_client.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport { _checkIsHttpToken as checkIsHttpToken } from 'node-internal:internal_http';\nimport {\n  kOutHeaders,\n  kUniqueHeaders,\n  parseUniqueHeadersOption,\n} from 'node-internal:internal_http_outgoing';\nimport { Buffer } from 'node-internal:internal_buffer';\nimport { urlToHttpOptions, isURL } from 'node-internal:internal_url';\nimport {\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_HTTP_TOKEN,\n  ERR_OPTION_NOT_IMPLEMENTED,\n  ERR_UNESCAPED_CHARACTERS,\n  ERR_INVALID_PROTOCOL,\n  ERR_INVALID_ARG_VALUE,\n  ERR_HTTP_HEADERS_SENT,\n  ERR_METHOD_NOT_IMPLEMENTED,\n} from 'node-internal:internal_errors';\nimport {\n  validateInteger,\n  validateBoolean,\n  validateFunction,\n  validateString,\n  validateNumber,\n} from 'node-internal:validators';\nimport { getTimerDuration } from 'node-internal:internal_net';\nimport { addAbortSignal } from 'node-internal:streams_add_abort_signal';\nimport { Writable } from 'node-internal:streams_writable';\nimport type {\n  ClientRequest as _ClientRequest,\n  RequestOptions,\n  OutgoingHttpHeaders,\n} from 'node:http';\nimport {\n  IncomingMessage,\n  setIncomingMessageFetchResponse,\n} from 'node-internal:internal_http_incoming';\nimport { OutgoingMessage } from 'node-internal:internal_http_outgoing';\nimport { Agent, globalAgent } from 'node-internal:internal_http_agent';\nimport type { IncomingMessageCallback } from 'node-internal:internal_http_util';\nimport type { Socket } from 'node:net';\n\nconst INVALID_PATH_REGEX = /[^\\u0021-\\u00ff]/;\n\ntype WriteCallback = (err?: Error) => void;\n\nfunction validateHost(host: unknown, name: string): string {\n  if (host != null && typeof host !== 'string') {\n    throw new ERR_INVALID_ARG_TYPE(\n      `options.${name}`,\n      ['string', 'undefined', 'null'],\n      host\n    );\n  }\n  return host as string;\n}\n\n// @ts-expect-error TS2720 Complaining due to \"override req\" being undefined.\nexport class ClientRequest extends OutgoingMessage implements _ClientRequest {\n  #abortController = new AbortController();\n  #body: (Buffer | Uint8Array)[] = [];\n  #incomingMessage?: IncomingMessage;\n  #timer: number | null = null;\n\n  _ended: boolean = false;\n\n  timeout?: number;\n  method: string = 'GET';\n  path: string = '/';\n  host: string;\n  protocol: string = 'http:';\n  port: string = '80';\n  joinDuplicateHeaders: boolean | undefined;\n  agent: Agent | undefined;\n\n  // Unused fields required to be Node.js compatible.\n  override aborted: boolean = false;\n  reusedSocket: boolean = false;\n  maxHeadersCount: number = Infinity;\n  connection: Socket | null = null;\n  socket: Socket | null = null;\n\n  [kUniqueHeaders]: Set<string> | null = null;\n\n  constructor(\n    input: string | URL | RequestOptions | null,\n    options?: RequestOptions | IncomingMessageCallback,\n    cb?: IncomingMessageCallback\n  ) {\n    super();\n\n    if (typeof input === 'string') {\n      input = urlToHttpOptions(new URL(input));\n    } else if (isURL(input)) {\n      // url.URL instance\n      input = urlToHttpOptions(input);\n    } else {\n      cb = options as IncomingMessageCallback;\n      options = input as RequestOptions;\n      input = null;\n    }\n\n    if (typeof options === 'function') {\n      cb = options;\n      options = input ?? {};\n    } else {\n      options = Object.assign(input ?? {}, options);\n    }\n\n    if (options.path) {\n      if (INVALID_PATH_REGEX.test(options.path)) {\n        throw new ERR_UNESCAPED_CHARACTERS('Request path');\n      }\n    }\n\n    type AgentLike = Agent | boolean | null | undefined;\n    let agent = options.agent as unknown as AgentLike;\n    // TODO(soon): Rather than using RequestOptions use our own type that includes our own Agent class type.\n    const defaultAgent =\n      (options._defaultAgent as unknown as AgentLike) || globalAgent;\n    if (agent === false) {\n      // @ts-expect-error TS2351 This expression is not constructable.\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call\n      agent = new defaultAgent.constructor();\n    } else if (agent == null) {\n      if (typeof options.createConnection !== 'function') {\n        agent = defaultAgent as Agent;\n      }\n    } else if (\n      typeof agent === 'object' &&\n      typeof agent.addRequest !== 'function'\n    ) {\n      throw new ERR_INVALID_ARG_TYPE(\n        'options.agent',\n        ['Agent-like Object', 'undefined', 'false'],\n        agent\n      );\n    }\n    this.agent = agent as Agent | undefined;\n\n    let expectedProtocol = (defaultAgent as Agent).protocol;\n    const protocol = options.protocol || expectedProtocol;\n    if (this.agent?.protocol) expectedProtocol = this.agent.protocol;\n    const defaultPort = options.defaultPort || this.agent?.defaultPort || 80;\n\n    if (protocol !== expectedProtocol) {\n      throw new ERR_INVALID_PROTOCOL(protocol, expectedProtocol);\n    }\n    this.protocol = protocol;\n    const port = (options.port = options.port || defaultPort || 80);\n    this.port = port.toString();\n    const host = (options.host =\n      validateHost(options.hostname, 'hostname') ||\n      validateHost(options.host, 'host') ||\n      'localhost');\n\n    const setHost =\n      options.setHost !== undefined\n        ? Boolean(options.setHost) // eslint-disable-line @typescript-eslint/no-unnecessary-type-conversion\n        : options.setDefaultHeaders !== false;\n    if (options.timeout !== undefined)\n      this.timeout = getTimerDuration(options.timeout, 'timeout');\n\n    const signal = options.signal;\n    if (signal) {\n      addAbortSignal(signal, this as unknown as Writable);\n    }\n    let method = options.method;\n    const methodIsString = typeof method === 'string';\n    if (method != null && !methodIsString) {\n      throw new ERR_INVALID_ARG_TYPE('options.method', 'string', method);\n    }\n\n    if (methodIsString && method) {\n      if (!checkIsHttpToken(method)) {\n        throw new ERR_INVALID_HTTP_TOKEN('Method', method);\n      }\n      method = this.method = method.toUpperCase();\n    } else {\n      method = this.method = 'GET';\n    }\n\n    const maxHeaderSize = options.maxHeaderSize;\n    if (maxHeaderSize !== undefined) {\n      // This overrides the maximum length of response headers in bytes.\n      // It doesn't make sense to override the maximum length for Workerd implementation\n      // which is based on the original \"fetch\" API.\n      validateInteger(maxHeaderSize, 'maxHeaderSize', 0);\n      throw new ERR_OPTION_NOT_IMPLEMENTED('options.maxHeaderSize');\n    }\n\n    if (options.insecureHTTPParser !== undefined) {\n      // If enabled it will use a HTTP parser with leniency flags enabled.\n      // Since our implementation does not use any http parser, and uses \"fetch\" API,\n      // it doesn't make sense to support this option.\n      validateBoolean(options.insecureHTTPParser, 'options.insecureHTTPParser');\n    }\n\n    if (options.createConnection !== undefined) {\n      // Our implementation is based on the original \"fetch\" API, which doesn't support\n      // custom socket creation. Therefore, this option is not applicable.\n      validateFunction(options.createConnection, 'options.createConnection');\n      throw new ERR_OPTION_NOT_IMPLEMENTED('options.createConnection');\n    }\n\n    if (options.lookup !== undefined) {\n      // Our implementation is based on the original \"fetch\" API, which doesn't support\n      // custom DNS resolution. Therefore, this option is not applicable.\n      validateFunction(options.lookup, 'options.lookup');\n      throw new ERR_OPTION_NOT_IMPLEMENTED('options.lookup');\n    }\n\n    if (options.socketPath !== undefined) {\n      // Unix domain socket. Cannot be used if one of host or port is specified, as those specify a TCP Socket.\n      // This option is not applicable for our \"fetch\" based implementation.\n      validateString(options.socketPath, 'options.socketPath');\n      throw new ERR_OPTION_NOT_IMPLEMENTED('options.socketPath');\n    }\n\n    if (options.joinDuplicateHeaders !== undefined) {\n      validateBoolean(\n        options.joinDuplicateHeaders,\n        'options.joinDuplicateHeaders'\n      );\n    }\n    this.joinDuplicateHeaders = options.joinDuplicateHeaders;\n\n    this.path = options.path || '/';\n    if (cb) {\n      this.once('response', cb);\n    }\n\n    this.host = host;\n\n    const headers = options.headers;\n    if (!Array.isArray(headers)) {\n      if (headers != null) {\n        if ('host' in headers) {\n          validateString(headers.host, 'host');\n        }\n        for (const [key, value] of Object.entries(headers)) {\n          this.setHeader(key, value as unknown as string);\n        }\n      }\n\n      if (host && !this.getHeader('host') && setHost) {\n        let hostHeader = host;\n\n        // For the Host header, ensure that IPv6 addresses are enclosed\n        // in square brackets, as defined by URI formatting\n        // https://tools.ietf.org/html/rfc3986#section-3.2.2\n        const posColon = hostHeader.indexOf(':');\n        if (\n          posColon !== -1 &&\n          hostHeader.includes(':', posColon + 1) &&\n          hostHeader.charCodeAt(0) !== 91 /* '[' */\n        ) {\n          hostHeader = `[${hostHeader}]`;\n        }\n\n        if (port && +port !== defaultPort) {\n          // eslint-disable-next-line @typescript-eslint/restrict-plus-operands\n          hostHeader += ':' + port;\n        }\n        this.setHeader('Host', hostHeader);\n      }\n\n      if (options.auth && !this.getHeader('Authorization')) {\n        this.setHeader(\n          'Authorization',\n          'Basic ' + Buffer.from(options.auth).toString('base64')\n        );\n      }\n    } else {\n      if (headers.length % 2 !== 0) {\n        throw new ERR_INVALID_ARG_VALUE('headers', headers);\n      }\n\n      for (let n = 0; n < headers.length; n += 2) {\n        this.setHeader(headers[n + 0] as string, headers[n + 1] as string);\n      }\n    }\n\n    this.on('finish', () => {\n      this.#onFinish();\n    });\n\n    this[kUniqueHeaders] = parseUniqueHeadersOption(options.uniqueHeaders);\n  }\n\n  #onFinish(): void {\n    if (this.destroyed) return;\n\n    let body: BodyInit | null = null;\n    if (this.method !== 'GET' && this.method !== 'HEAD') {\n      if (this.#body.length > 0) {\n        const value = this.getHeader('content-type') ?? '';\n        body = new Blob(this.#body as BlobPart[], {\n          type: Array.isArray(value) ? value.join(', ') : `${value}`,\n        });\n      }\n    }\n\n    const headers: [string, string][] = [];\n    for (const [_lowerCaseName, [originalName, value]] of Object.entries(\n      this[kOutHeaders] ?? {}\n    )) {\n      if (Array.isArray(value)) {\n        if (this.joinDuplicateHeaders) {\n          headers.push([originalName, value.join(', ')]);\n        } else {\n          for (const item of value) {\n            headers.push([originalName, item]);\n          }\n        }\n      } else {\n        headers.push([originalName, value]);\n      }\n    }\n\n    if (this.timeout) {\n      this.#timer = setTimeout(() => {\n        this.emit('timeout');\n        this.#incomingMessage?.emit('timeout');\n        this.#abortController.abort();\n      }, this.timeout) as unknown as number;\n    }\n\n    if (\n      this.host &&\n      !this.getHeader('host') &&\n      Object.keys(this[kOutHeaders] ?? {}).length === 0\n    ) {\n      // From RFC 7230 5.4 https://datatracker.ietf.org/doc/html/rfc7230#section-5.4\n      // A server MUST respond with a 400 (Bad Request) status code to any\n      // HTTP/1.1 request message that lacks a Host header field\n      queueMicrotask(() => {\n        this.#handleFetchResponse(\n          new Response(null, {\n            status: 400,\n            statusText: 'Bad Request',\n            headers: {\n              connection: 'close',\n            },\n          })\n        );\n      });\n      return;\n    }\n\n    const host = this.getHeader('host') ?? this.host;\n    let url = new URL(`http://${host}`);\n    url.protocol = this.protocol;\n    url.port = this.port;\n\n    if (this.path.length > 0 && this.path !== '/') {\n      // We pass `path` as the first argument since it can contain search and hash components.\n      // Therefore, running the pathname setter will not work.\n      // Since this is an extremely costly operation, we only do it if necessary.\n      url = new URL(this.path, url);\n    }\n\n    // Our fetch implementation has the following limitation:\n    //\n    // Nothing is directly waiting for fetch promise here.\n    // It's up to the user of the HTTP API to arrange for\n    // the request to be held open until the fetch completes,\n    // typically by passing some promise to ctx.waitUntil()\n    // and resolving that promise when the request is complete.\n    //\n    // TODO(soon): Address this limitation.\n\n    // We use encodeResponseBody: 'manual' to prevent fetch from automatically\n    // decompressing the response body. Node.js http does not auto-decompress;\n    // callers are expected to handle Content-Encoding themselves.\n    // The type assertion is needed because the DOM RequestInit type does not\n    // include the workerd-specific encodeResponseBody property.\n    fetch(url, {\n      method: this.method,\n      headers,\n      body: body ?? null,\n      signal: this.#abortController.signal,\n      redirect: 'manual',\n      encodeResponseBody: 'manual',\n    } as RequestInit & { encodeResponseBody: 'manual' })\n      .then(this.#handleFetchResponse.bind(this))\n      .catch(this.#handleFetchError.bind(this));\n  }\n\n  #handleFetchResponse(response: Response): void {\n    // Sets headersSent\n    this._header = Array.from(response.headers.keys())\n      .map((key) => `${key}=${response.headers.get(key)}}`)\n      .join('\\r\\n');\n    const incoming = new IncomingMessage();\n    setIncomingMessageFetchResponse(incoming, response);\n    incoming.on('error', (error) => {\n      this.emit('error', error);\n    });\n\n    this.emit('response', incoming);\n    // @ts-expect-error TS2540 This is a read-only property.\n    this.req = this.#incomingMessage;\n    this.#incomingMessage = incoming;\n  }\n\n  #handleFetchError(error: Error): void {\n    if (!this.destroyed) {\n      this.emit('error', error);\n    } else {\n      console.log(error);\n    }\n    this.destroyed = true;\n    this._ended = true;\n  }\n\n  onSocket(_socket: Socket): void {\n    // Do nothing. Our implementation does not depend on socket class.\n  }\n\n  addTrailers(\n    _headers: OutgoingHttpHeaders | ReadonlyArray<[string, string]>\n  ): void {\n    // We don't support trailers.\n    throw new ERR_METHOD_NOT_IMPLEMENTED('addTrailers');\n  }\n\n  abort(error?: Error | null): void {\n    this.destroyed = true;\n    this.#resetTimers({ finished: true });\n    if (this.#incomingMessage) {\n      this.#incomingMessage.destroyed = true;\n    }\n    this.#abortController.abort();\n    if (error) {\n      this.emit('error', error);\n    }\n  }\n\n  override _write(\n    chunk: Buffer,\n    _encoding: BufferEncoding,\n    callback: VoidFunction\n  ): boolean {\n    this.#body.push(chunk);\n    callback();\n    return true;\n  }\n\n  setNoDelay(noDelay?: boolean): void {\n    validateBoolean(noDelay, 'noDelay');\n    // Not implemented\n  }\n\n  setSocketKeepAlive(enable?: boolean, initialDelay?: number): void {\n    validateBoolean(enable, 'enable');\n    validateNumber(initialDelay, 'initialDelay');\n    // Not implemented\n  }\n\n  clearTimeout(cb?: VoidFunction): void {\n    this.setTimeout(0, cb);\n  }\n\n  setTimeout(msecs: number, callback?: VoidFunction): this {\n    if (this.#timer) {\n      clearTimeout(this.#timer);\n      this.#timer = null;\n    }\n\n    this.timeout = getTimerDuration(msecs, 'msecs');\n    this.#resetTimers({ finished: false });\n\n    if (callback) this.once('timeout', callback);\n\n    return this;\n  }\n\n  override write(\n    chunk: string | Buffer | Uint8Array,\n    encoding?: BufferEncoding | WriteCallback | null,\n    callback?: WriteCallback\n  ): boolean {\n    // Capture the data for the request body\n    if (this.method !== 'GET' && this.method !== 'HEAD' && chunk) {\n      if (typeof chunk === 'string') {\n        this.#body.push(\n          Buffer.from(chunk, typeof encoding === 'string' ? encoding : 'utf8')\n        );\n      } else {\n        this.#body.push(chunk);\n      }\n    }\n\n    // Call the parent write method\n    return super.write(chunk, encoding, callback);\n  }\n\n  override end(\n    data?: Buffer | string | VoidFunction,\n    encoding?: BufferEncoding | VoidFunction,\n    callback?: VoidFunction\n  ): this {\n    this._ended = true;\n\n    if (typeof data === 'function') {\n      callback = data as VoidFunction;\n      data = undefined;\n    }\n\n    // Don't duplicate data here - let the parent's end() call write() which will handle it\n    Writable.prototype.end.call(\n      this,\n      data,\n      encoding as BufferEncoding,\n      callback\n    );\n    return this;\n  }\n\n  #resetTimers({ finished }: { finished: boolean }): void {\n    if (finished) {\n      clearTimeout(this.#timer as number);\n      this.#timer = null;\n    } else if (this.timeout) {\n      if (this.#timer) {\n        clearTimeout(this.#timer);\n      }\n      this.#timer = setTimeout(() => {\n        this.emit('timeout');\n        this.#incomingMessage?.emit('timeout');\n        this.#abortController.abort();\n      }, this.timeout) as unknown as number;\n    }\n  }\n\n  override _implicitHeader(): void {\n    if (this._header) {\n      throw new ERR_HTTP_HEADERS_SENT('render');\n    }\n    this._storeHeader(\n      this.method + ' ' + this.path + ' HTTP/1.1\\r\\n',\n      this[kOutHeaders] as OutgoingHttpHeaders\n    );\n  }\n}\n"
  },
  {
    "path": "src/node/internal/internal_http_constants.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nexport const METHODS = [\n  'ACL',\n  'BIND',\n  'CHECKOUT',\n  'CONNECT',\n  'COPY',\n  'DELETE',\n  'GET',\n  'HEAD',\n  'LINK',\n  'LOCK',\n  'M-SEARCH',\n  'MERGE',\n  'MKACTIVITY',\n  'MKCALENDAR',\n  'MKCOL',\n  'MOVE',\n  'NOTIFY',\n  'OPTIONS',\n  'PATCH',\n  'POST',\n  'PROPFIND',\n  'PROPPATCH',\n  'PURGE',\n  'PUT',\n  'QUERY',\n  'REBIND',\n  'REPORT',\n  'SEARCH',\n  'SOURCE',\n  'SUBSCRIBE',\n  'TRACE',\n  'UNBIND',\n  'UNLINK',\n  'UNLOCK',\n  'UNSUBSCRIBE',\n].toSorted();\n\nexport const STATUS_CODES: Record<string, string> = {\n  '100': 'Continue',\n  '101': 'Switching Protocols',\n  '102': 'Processing',\n  '103': 'Early Hints',\n  '200': 'OK',\n  '201': 'Created',\n  '202': 'Accepted',\n  '203': 'Non-Authoritative Information',\n  '204': 'No Content',\n  '205': 'Reset Content',\n  '206': 'Partial Content',\n  '207': 'Multi-Status',\n  '208': 'Already Reported',\n  '226': 'IM Used',\n  '300': 'Multiple Choices',\n  '301': 'Moved Permanently',\n  '302': 'Found',\n  '303': 'See Other',\n  '304': 'Not Modified',\n  '305': 'Use Proxy',\n  '307': 'Temporary Redirect',\n  '308': 'Permanent Redirect',\n  '400': 'Bad Request',\n  '401': 'Unauthorized',\n  '402': 'Payment Required',\n  '403': 'Forbidden',\n  '404': 'Not Found',\n  '405': 'Method Not Allowed',\n  '406': 'Not Acceptable',\n  '407': 'Proxy Authentication Required',\n  '408': 'Request Timeout',\n  '409': 'Conflict',\n  '410': 'Gone',\n  '411': 'Length Required',\n  '412': 'Precondition Failed',\n  '413': 'Payload Too Large',\n  '414': 'URI Too Long',\n  '415': 'Unsupported Media Type',\n  '416': 'Range Not Satisfiable',\n  '417': 'Expectation Failed',\n  '418': \"I'm a Teapot\",\n  '421': 'Misdirected Request',\n  '422': 'Unprocessable Entity',\n  '423': 'Locked',\n  '424': 'Failed Dependency',\n  '425': 'Too Early',\n  '426': 'Upgrade Required',\n  '428': 'Precondition Required',\n  '429': 'Too Many Requests',\n  '431': 'Request Header Fields Too Large',\n  '451': 'Unavailable For Legal Reasons',\n  '500': 'Internal Server Error',\n  '501': 'Not Implemented',\n  '502': 'Bad Gateway',\n  '503': 'Service Unavailable',\n  '504': 'Gateway Timeout',\n  '505': 'HTTP Version Not Supported',\n  '506': 'Variant Also Negotiates',\n  '507': 'Insufficient Storage',\n  '508': 'Loop Detected',\n  '509': 'Bandwidth Limit Exceeded',\n  '510': 'Not Extended',\n  '511': 'Network Authentication Required',\n};\n"
  },
  {
    "path": "src/node/internal/internal_http_incoming.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport { EventEmitter } from 'node-internal:events';\nimport { Readable } from 'node-internal:streams_readable';\nimport { isIPv4, Socket } from 'node-internal:internal_net';\nimport type {\n  IncomingMessage as _IncomingMessage,\n  IncomingHttpHeaders,\n} from 'node:http';\nconst kHeaders = Symbol('kHeaders');\nconst kHeadersDistinct = Symbol('kHeadersDistinct');\nconst kHeadersCount = Symbol('kHeadersCount');\n\nexport let setIncomingMessageFetchResponse: (\n  incoming: IncomingMessage,\n  response: Response,\n  resetTimers?: (opts: { finished: boolean }) => void\n) => void;\n\nexport let setIncomingMessageSocket: (\n  incoming: IncomingMessage,\n  options: {\n    headers: Headers;\n    localPort: number;\n  }\n) => void;\n\nexport let setIncomingRequestBody: (\n  incoming: IncomingMessage,\n  body: ReadableStream | null\n) => void;\n\nexport class IncomingMessage extends Readable implements _IncomingMessage {\n  #response?: Response;\n  #reader?: ReadableStreamDefaultReader<Uint8Array>;\n  #reading = false;\n  #socket: unknown;\n  #stream: ReadableStream | null = null;\n\n  override aborted = false;\n  url: string = '';\n  // @ts-expect-error TS2416 Type-inconsistencies\n  method: string | null = null;\n  // @ts-expect-error TS2416 Type-inconsistencies\n  statusCode: number | null = null;\n  // @ts-expect-error TS2416 Type-inconsistencies\n  statusMessage: string | null = null;\n  httpVersionMajor = 1;\n  httpVersionMinor = 1;\n  httpVersion: string = '1.1';\n  complete = false;\n  rawHeaders: string[] = [];\n  joinDuplicateHeaders = false;\n\n  // The cloudflare property is currently only used on the server-side\n  // to access properties like `req.cf`, and the `env` and `ctx`\n  // objects.\n  cloudflare: {\n    // Technically, the type should be IncomingRequestCfProperties but\n    // we don't have that type in the workerd runtime at the moment.\n    cf?: Record<string, unknown> | undefined;\n    env?: unknown;\n    ctx?: unknown;\n  } = { cf: undefined, env: undefined, ctx: undefined };\n\n  [kHeaders]: IncomingHttpHeaders | null = null;\n  [kHeadersDistinct]: Record<string, string[]> | null = null;\n  [kHeadersCount]: number = 0;\n\n  // Flag for when we decide that this message cannot possibly be\n  // read by the user, so there's no point continuing to handle it.\n  _dumped = false;\n  _consuming = false;\n  _paused = false;\n\n  static {\n    setIncomingMessageFetchResponse = (\n      incoming: IncomingMessage,\n      response: Response\n    ): void => {\n      incoming.#setFetchResponse(response);\n    };\n\n    // This method sets the socket property of the IncomingMessage object.\n    // Please rest assured that this method implements a subset of Socket since\n    // in Node.js it's net.Socket which isn't possible to implement within our own\n    // implementation since our implementation is based on Request and Response objects.\n    setIncomingMessageSocket = (\n      incoming: IncomingMessage,\n      { headers, localPort }: { headers: Headers; localPort: number }\n    ): void => {\n      const connectingIp = headers.get('cf-connecting-ip');\n      const isConnectingIpIpv4 = connectingIp ? isIPv4(connectingIp) : true;\n      // Return a port number between 2^15 and 2^16.\n      const remotePort = (Math.random() * 0x8000) | 0x8000;\n\n      // Some libraries such as on-finished (which Express.js depends on)\n      // Ref: https://github.com/jshttp/on-finished/blob/d2974f5a18f468ea56f58acb2f6d402f4b5142f0/index.js\n      // calls EventEmitter events on socket attribute.\n      const socket = new EventEmitter();\n\n      Object.defineProperties(socket, {\n        encrypted: {\n          value: headers.get('x-forwarded-proto') === 'https',\n          writable: false,\n          configurable: true,\n        },\n        readable: {\n          get: () => {\n            return incoming.readable;\n          },\n          configurable: true,\n        },\n        remoteFamily: {\n          get: () => {\n            if (incoming.destroyed) {\n              return undefined;\n            }\n            return isConnectingIpIpv4 ? 'IPv4' : 'IPv6';\n          },\n          configurable: true,\n        },\n        remoteAddress: {\n          get: () => {\n            // This is defined in production, and will fallback to localhost on local development\n            // where request headers does not contain cf-connecting-ip.\n            return incoming.destroyed\n              ? undefined\n              : (connectingIp ?? '127.0.0.1');\n          },\n          configurable: true,\n        },\n        remotePort: {\n          get: () => {\n            // Return a port in the ephemeral range (32768-65535) as clients would use,\n            // and undefined if the socket is destroyed.\n            return incoming.destroyed ? undefined : remotePort;\n          },\n          configurable: true,\n        },\n        localAddress: {\n          // Host will have a value like \"my-worker.yagiz.workers.dev\",\n          value: headers.get('host') ?? '127.0.0.1',\n          writable: false,\n          configurable: true,\n        },\n        localPort: {\n          // This is the port defined by the `server.listen(port)` call.\n          value: localPort,\n          writable: false,\n          configurable: true,\n        },\n        destroy: {\n          value: (err: Error | undefined): IncomingMessage =>\n            incoming.destroy(err),\n          writable: false,\n          configurable: true,\n        },\n      });\n\n      incoming.#socket = socket;\n    };\n\n    setIncomingRequestBody = (\n      incoming: IncomingMessage,\n      stream: ReadableStream | null\n    ): void => {\n      incoming.#stream = stream;\n    };\n  }\n\n  constructor() {\n    super({});\n    this._readableState.readingMore = true;\n  }\n\n  #setFetchResponse(response: Response): void {\n    this[kHeaders] = {};\n    this[kHeadersDistinct] = {};\n    for (const header of response.headers.keys()) {\n      const value = response.headers.get(header) as string;\n      this[kHeaders][header] = value;\n      this[kHeadersDistinct][header] = [value];\n      this[kHeadersCount]++;\n    }\n\n    this.#response = response;\n    this._readableState.readingMore = true;\n\n    this.url = response.url;\n    this.statusCode = response.status;\n    this.statusMessage = response.statusText;\n\n    this.once('end', () => {\n      // We need to emit close in a queueMicrotask because\n      // this is the only way we can ensure that the close event is emitted after destroy.\n      queueMicrotask(() => this.emit('close'));\n    });\n\n    this.on('timeout', () => {\n      this._consuming = false;\n    });\n\n    this.#stream = this.#response.body;\n  }\n\n  async #tryRead(): Promise<void> {\n    if (this.#stream == null || this.#reading) return;\n\n    this.#reading = true;\n\n    try {\n      this.#reader ??= this.#stream.getReader();\n\n      while (!this.destroyed) {\n        const data = await this.#reader.read();\n        if (data.done) {\n          this.complete = true;\n          this.push(null);\n          break;\n        }\n\n        // Backpressure - stop reading until _read() is called again\n        if (!this.push(data.value)) {\n          break;\n        }\n      }\n    } catch (e) {\n      this.destroy(e as Error);\n    } finally {\n      this.#reading = false;\n      this.#reader?.releaseLock();\n    }\n  }\n\n  // As this is an implementation of stream.Readable, we provide a _read()\n  // function that pumps the next chunk out of the underlying ReadableStream.\n  override _read(_n: number): void {\n    if (!this._consuming) {\n      this._readableState.readingMore = false;\n      this._consuming = true;\n    }\n\n    // Difference from Node.js -\n    // The Node.js implementation will already have its internal buffer\n    // filled by the parserOnBody function.\n    // For our implementation, we use the ReadableStream instance.\n    if (this.#stream == null) {\n      // For GET and HEAD requests, the stream would be empty.\n      // Simply signal that we're done.\n      this.complete = true;\n      this.push(null);\n      return;\n    }\n\n    this.#tryRead(); // eslint-disable-line @typescript-eslint/no-floating-promises\n  }\n\n  #onError(error: Error | null, cb: (err?: Error | null) => void): void {\n    // This is to keep backward compatible behavior.\n    // An error is emitted only if there are listeners attached to the event.\n    if (this.listenerCount('error') === 0) {\n      cb();\n    } else {\n      cb(error);\n    }\n  }\n\n  override _destroy(\n    error: Error | null,\n    callback: (error?: Error | null) => void\n  ): void {\n    if (!this.readableEnded || !this.complete) {\n      this.aborted = true;\n      this.emit('aborted');\n    }\n\n    queueMicrotask(() => {\n      this.#onError(error, callback);\n    });\n  }\n\n  // Add the given (field, value) pair to the message\n  //\n  // Per RFC2616, section 4.2 it is acceptable to join multiple instances of the\n  // same header with a ', ' if the header in question supports specification of\n  // multiple values this way. The one exception to this is the Cookie header,\n  // which has multiple values joined with a '; ' instead. If a header's values\n  // cannot be joined in either of these ways, we declare the first instance the\n  // winner and drop the second. Extended header fields (those beginning with\n  // 'x-') are always joined.\n  _addHeaderLine(\n    field: string,\n    value: string,\n    dest: IncomingHttpHeaders\n  ): void {\n    field = matchKnownFields(field);\n    const flag = field.charCodeAt(0);\n    if (flag === 0 || flag === 2) {\n      field = field.slice(1);\n      // Make a delimited list\n      if (typeof dest[field] === 'string') {\n        // eslint-disable-next-line @typescript-eslint/restrict-plus-operands\n        dest[field] += (flag === 0 ? ', ' : '; ') + value;\n      } else {\n        dest[field] = value;\n      }\n    } else if (flag === 1) {\n      // Array header -- only Set-Cookie at the moment\n      if (dest['set-cookie'] !== undefined) {\n        dest['set-cookie'].push(value);\n      } else {\n        dest['set-cookie'] = [value];\n      }\n    } else if (this.joinDuplicateHeaders) {\n      // RFC 9110 https://www.rfc-editor.org/rfc/rfc9110#section-5.2\n      // https://github.com/nodejs/node/issues/45699\n      // allow authorization multiple fields\n      // Make a delimited list\n      if (dest[field] === undefined) {\n        dest[field] = value;\n      } else {\n        // eslint-disable-next-line @typescript-eslint/restrict-plus-operands\n        dest[field] += ', ' + value;\n      }\n    } else if (dest[field] === undefined) {\n      // Drop duplicates\n      dest[field] = value;\n    }\n  }\n\n  _addHeaderLines(headers: string[] | null, n: number): void {\n    if (Array.isArray(headers) && !this.complete) {\n      this.rawHeaders = headers;\n      this[kHeadersCount] = n;\n\n      if (this[kHeaders]) {\n        for (let i = 0; i < n; i += 2) {\n          this._addHeaderLine(\n            headers[i] as string,\n            headers[i + 1] as string,\n            this[kHeaders]\n          );\n        }\n      }\n    }\n  }\n\n  get headers(): Record<string, string | string[] | undefined> {\n    if (!this[kHeaders]) {\n      this[kHeaders] = {};\n\n      const src = this.rawHeaders;\n      const dst = this[kHeaders];\n\n      for (let n = 0; n < this[kHeadersCount]; n += 2) {\n        this._addHeaderLine(src[n] as string, src[n + 1] as string, dst);\n      }\n    }\n    return this[kHeaders];\n  }\n\n  set headers(val: IncomingHttpHeaders) {\n    this[kHeaders] = val;\n  }\n\n  get headersDistinct(): Record<string, string[]> {\n    if (!this[kHeadersDistinct]) {\n      this[kHeadersDistinct] = {};\n\n      const src = this.rawHeaders;\n      const dst = this[kHeadersDistinct];\n\n      for (let n = 0; n < this[kHeadersCount]; n += 2) {\n        this._addHeaderLineDistinct(\n          src[n] as string,\n          src[n + 1] as string,\n          dst\n        );\n      }\n    }\n    return this[kHeadersDistinct];\n  }\n\n  set headersDistinct(val: Record<string, string[]>) {\n    this[kHeadersDistinct] = val;\n  }\n\n  get trailers(): Record<string, string | undefined> {\n    return {};\n  }\n\n  set trailers(_val: NodeJS.Dict<string>) {\n    // Workerd doesn't support trailers.\n  }\n\n  get trailersDistinct(): Record<string, string[]> {\n    return {};\n  }\n\n  set trailersDistinct(_val: Record<string, string[]>) {\n    // Workerd doesn't support trailers.\n  }\n\n  _addHeaderLineDistinct(\n    field: string,\n    value: string,\n    dest: Record<string, string[]>\n  ): void {\n    field = field.toLowerCase();\n    if (!dest[field]) {\n      dest[field] = [value];\n    } else {\n      dest[field]?.push(value);\n    }\n  }\n\n  // Call this instead of resume() if we want to just\n  // dump all the data to /dev/null\n  _dump(): void {\n    if (!this._dumped) {\n      this._dumped = true;\n      // If there is buffered data, it may trigger 'data' events.\n      // Remove 'data' event listeners explicitly.\n      this.removeAllListeners('data');\n      this.resume();\n    }\n  }\n\n  setTimeout(_msecs: number, callback?: () => void): this {\n    if (callback) {\n      this.on('timeout', callback);\n    }\n    return this;\n  }\n\n  override pipe<T extends NodeJS.WritableStream>(\n    destination: T,\n    options?: { end?: boolean }\n  ): T {\n    const shouldEnd = options?.end !== false;\n\n    // Handle the piping manually for better control\n    this.on('data', (chunk: string | Uint8Array) => {\n      destination.write(chunk);\n    });\n\n    this.once('end', () => {\n      if (shouldEnd) {\n        destination.end();\n      }\n    });\n\n    this.once('error', (err: unknown) => {\n      destination.emit('error', err);\n    });\n\n    // Always ensure reading starts - call resume to trigger the stream\n    this.resume();\n\n    return destination;\n  }\n\n  set connection(value: unknown) {\n    this.#socket = value;\n  }\n\n  get connection(): Socket {\n    return this.#socket as Socket;\n  }\n\n  get socket(): Socket {\n    return this.#socket as Socket;\n  }\n}\n\n// This function is used to help avoid the lowercasing of a field name if it\n// matches a 'traditional cased' version of a field name. It then returns the\n// lowercased name to both avoid calling toLowerCase() a second time and to\n// indicate whether the field was a 'no duplicates' field. If a field is not a\n// 'no duplicates' field, a `0` byte is prepended as a flag. The one exception\n// to this is the Set-Cookie header which is indicated by a `1` byte flag, since\n// it is an 'array' field and thus is treated differently in _addHeaderLines().\n// TODO: perhaps http_parser could be returning both raw and lowercased versions\n// of known header names to avoid us having to call toLowerCase() for those\n// headers.\nfunction matchKnownFields(field: string, lowercased: boolean = false): string {\n  switch (field.length) {\n    case 3:\n      if (field === 'Age' || field === 'age') return 'age';\n      break;\n    case 4:\n      if (field === 'Host' || field === 'host') return 'host';\n      if (field === 'From' || field === 'from') return 'from';\n      if (field === 'ETag' || field === 'etag') return 'etag';\n      if (field === 'Date' || field === 'date') return '\\u0000date';\n      if (field === 'Vary' || field === 'vary') return '\\u0000vary';\n      break;\n    case 6:\n      if (field === 'Server' || field === 'server') return 'server';\n      if (field === 'Cookie' || field === 'cookie') return '\\u0002cookie';\n      if (field === 'Origin' || field === 'origin') return '\\u0000origin';\n      if (field === 'Expect' || field === 'expect') return '\\u0000expect';\n      if (field === 'Accept' || field === 'accept') return '\\u0000accept';\n      break;\n    case 7:\n      if (field === 'Referer' || field === 'referer') return 'referer';\n      if (field === 'Expires' || field === 'expires') return 'expires';\n      if (field === 'Upgrade' || field === 'upgrade') return '\\u0000upgrade';\n      break;\n    case 8:\n      if (field === 'Location' || field === 'location') return 'location';\n      if (field === 'If-Match' || field === 'if-match') return '\\u0000if-match';\n      break;\n    case 10:\n      if (field === 'User-Agent' || field === 'user-agent') return 'user-agent';\n      if (field === 'Set-Cookie' || field === 'set-cookie') return '\\u0001';\n      if (field === 'Connection' || field === 'connection')\n        return '\\u0000connection';\n      break;\n    case 11:\n      if (field === 'Retry-After' || field === 'retry-after')\n        return 'retry-after';\n      break;\n    case 12:\n      if (field === 'Content-Type' || field === 'content-type')\n        return 'content-type';\n      if (field === 'Max-Forwards' || field === 'max-forwards')\n        return 'max-forwards';\n      break;\n    case 13:\n      if (field === 'Authorization' || field === 'authorization')\n        return 'authorization';\n      if (field === 'Last-Modified' || field === 'last-modified')\n        return 'last-modified';\n      if (field === 'Cache-Control' || field === 'cache-control')\n        return '\\u0000cache-control';\n      if (field === 'If-None-Match' || field === 'if-none-match')\n        return '\\u0000if-none-match';\n      break;\n    case 14:\n      if (field === 'Content-Length' || field === 'content-length')\n        return 'content-length';\n      break;\n    case 15:\n      if (field === 'Accept-Encoding' || field === 'accept-encoding')\n        return '\\u0000accept-encoding';\n      if (field === 'Accept-Language' || field === 'accept-language')\n        return '\\u0000accept-language';\n      if (field === 'X-Forwarded-For' || field === 'x-forwarded-for')\n        return '\\u0000x-forwarded-for';\n      break;\n    case 16:\n      if (field === 'Content-Encoding' || field === 'content-encoding')\n        return '\\u0000content-encoding';\n      if (field === 'X-Forwarded-Host' || field === 'x-forwarded-host')\n        return '\\u0000x-forwarded-host';\n      break;\n    case 17:\n      if (field === 'If-Modified-Since' || field === 'if-modified-since')\n        return 'if-modified-since';\n      if (field === 'Transfer-Encoding' || field === 'transfer-encoding')\n        return '\\u0000transfer-encoding';\n      if (field === 'X-Forwarded-Proto' || field === 'x-forwarded-proto')\n        return '\\u0000x-forwarded-proto';\n      break;\n    case 19:\n      if (field === 'Proxy-Authorization' || field === 'proxy-authorization')\n        return 'proxy-authorization';\n      if (field === 'If-Unmodified-Since' || field === 'if-unmodified-since')\n        return 'if-unmodified-since';\n      break;\n  }\n  if (lowercased) {\n    return '\\u0000' + field;\n  }\n  return matchKnownFields(field.toLowerCase(), true);\n}\n"
  },
  {
    "path": "src/node/internal/internal_http_outgoing.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\n// We have deprecations because @types/node defines this.finished as deprecated.\n/* eslint-disable @typescript-eslint/no-deprecated */\n\nimport { validateString } from 'node-internal:validators';\nimport { Writable } from 'node-internal:streams_writable';\nimport { getDefaultHighWaterMark } from 'node-internal:streams_state';\nimport type { DataWrittenEvent } from 'node-internal:internal_http_server';\nimport {\n  ERR_HTTP_HEADERS_SENT,\n  ERR_INVALID_ARG_TYPE,\n  ERR_STREAM_CANNOT_PIPE,\n  ERR_STREAM_DESTROYED,\n  ERR_METHOD_NOT_IMPLEMENTED,\n  ERR_STREAM_WRITE_AFTER_END,\n  ERR_HTTP_CONTENT_LENGTH_MISMATCH,\n  ERR_HTTP_BODY_NOT_ALLOWED,\n  ERR_STREAM_NULL_VALUES,\n  ERR_STREAM_ALREADY_FINISHED,\n  ERR_INVALID_ARG_VALUE,\n} from 'node-internal:internal_errors';\nimport { isUint8Array } from 'node-internal:internal_types';\nimport {\n  validateHeaderName,\n  validateHeaderValue,\n  chunkExpression as RE_TE_CHUNKED,\n  utcDate,\n} from 'node-internal:internal_http';\nimport { IncomingMessage } from 'node-internal:internal_http_incoming';\nimport { EventEmitter } from 'node-internal:events';\nimport type {\n  OutgoingMessage as _OutgoingMessage,\n  OutgoingHttpHeaders,\n  ServerResponse,\n  OutgoingHttpHeader,\n} from 'node:http';\n\ntype WriteCallback = (err?: Error) => void;\nexport type OutputData = {\n  data: string | Buffer | Uint8Array | null;\n  encoding?: BufferEncoding | null | undefined;\n  callback?: WriteCallback | null | undefined;\n};\nexport type WrittenDataBufferEntry = OutputData & {\n  length: number;\n  written: boolean;\n};\nexport type HeadersSentEvent = {\n  statusCode: number;\n  statusMessage: string;\n  headers: Headers;\n};\n\nexport const kUniqueHeaders = Symbol('kUniqueHeaders');\nexport const kHighWaterMark = Symbol('kHighWaterMark');\nexport const kNeedDrain = Symbol('kNeedDrain');\nexport const kOutHeaders = Symbol('kOutHeaders');\nexport const kErrored = Symbol('kErrored');\nconst kCorked = Symbol('corked');\nconst kChunkedBuffer = Symbol('kChunkedBuffer');\nconst kChunkedLength = Symbol('kChunkedLength');\nconst kBytesWritten = Symbol('kBytesWritten');\nconst kRejectNonStandardBodyWrites = Symbol('kRejectNonStandardBodyWrites');\n\nconst RE_CONN_CLOSE = /(?:^|\\W)close(?:$|\\W)/i;\n\ntype HeaderState = {\n  connection: boolean;\n  contLen: boolean;\n  te: boolean;\n  date: boolean;\n  expect: boolean;\n  trailer: boolean;\n  header: string;\n};\n\nexport function parseUniqueHeadersOption(\n  headers?: (string | string[])[]\n): Set<string> | null {\n  if (!Array.isArray(headers)) {\n    return null;\n  }\n\n  const unique = new Set<string>();\n  for (const header of headers) {\n    if (Array.isArray(header)) {\n      for (const h of header) {\n        unique.add(h.toLowerCase());\n      }\n    } else {\n      unique.add(header.toLowerCase());\n    }\n  }\n  return unique;\n}\n\n// Most of the code in this class is derived from Michael Hart's project\n// Ref: https://github.com/mhart/fetch-to-node/blob/main/src/fetch-to-node/http-outgoing.ts\nclass MessageBuffer {\n  #corked = 0;\n  #index = 0;\n  #onWrite: (data: DataWrittenEvent[]) => void;\n  #bufferedWrites: { index: number; entry: WrittenDataBufferEntry }[] = [];\n  #highWaterMark: number;\n\n  constructor(\n    onWrite: (data: DataWrittenEvent[]) => void,\n    options: { highWaterMark: number }\n  ) {\n    this.#onWrite = onWrite;\n    this.#highWaterMark = options.highWaterMark;\n  }\n\n  write(\n    data: WrittenDataBufferEntry['data'],\n    encoding: WrittenDataBufferEntry['encoding'],\n    callback: WrittenDataBufferEntry['callback'],\n    onDrain?: (dataLength: number) => void\n  ): boolean {\n    const entry: WrittenDataBufferEntry = {\n      data,\n      length: data?.length ?? 0,\n      encoding,\n      callback,\n      written: true,\n    };\n\n    const index = this.#index++;\n\n    if (this.#corked === 0) {\n      this.#onWrite([{ index, entry }]);\n      queueMicrotask(() => {\n        onDrain?.(entry.length);\n        callback?.();\n      });\n    } else {\n      // Buffer the write when corked\n      this.#bufferedWrites.push({ index, entry });\n      queueMicrotask(() => {\n        callback?.();\n      });\n    }\n\n    return true;\n  }\n\n  cork(): void {\n    this.#corked++;\n  }\n\n  uncork(): void {\n    this.#corked--;\n    this._flush();\n  }\n\n  _flush(): void {\n    // If fully uncorked, flush all buffered writes\n    if (this.#corked <= 0) {\n      this.#onWrite(this.#bufferedWrites.splice(0));\n    }\n  }\n\n  get writableLength(): number {\n    return this.#bufferedWrites.reduce((total, { entry }) => {\n      return total + (entry.length || 0);\n    }, 0);\n  }\n\n  get writableHighWaterMark(): number {\n    return this.#highWaterMark;\n  }\n\n  get writableCorked(): number {\n    return this.#corked;\n  }\n}\n\nexport type OutgoingMessageOptions = {\n  highWaterMark?: number | undefined;\n  rejectNonStandardBodyWrites?: boolean | undefined;\n};\n\n// Most of the code in this class is derived from Michael Hart's project\n// Ref: https://github.com/mhart/fetch-to-node/blob/main/src/fetch-to-node/http-outgoing.ts\nexport class OutgoingMessage extends Writable implements _OutgoingMessage {\n  [kOutHeaders]: Record<string, [string, string | string[]]> | null = null;\n  [kErrored]: Error | null = null;\n  [kCorked] = 0;\n  [kChunkedBuffer]: OutputData[] = [];\n  [kChunkedLength]: number = 0;\n  [kNeedDrain] = false;\n  [kRejectNonStandardBodyWrites]: boolean;\n  [kHighWaterMark]: number;\n  [kBytesWritten] = 0;\n\n  // @ts-expect-error TS2416 IncomingMessage is not feature complete yet.\n  readonly req?: IncomingMessage | undefined;\n  #buffer: MessageBuffer | undefined | null;\n\n  // Queue that holds all currently pending data, until the response will be\n  // assigned to the socket (until it will its turn in the HTTP pipeline).\n  outputData: OutputData[] = [];\n\n  // `outputSize` is an approximate measure of how much data is queued on this\n  // response. `_onPendingData` will be invoked to update similar global\n  // per-connection counter. That counter will be used to pause/unpause the\n  // TCP socket and HTTP Parser and thus handle the backpressure.\n  outputSize: number = 0;\n\n  // `writtenHeaderBytes` is the number of bytes the header has taken.\n  // Since Node.js writes both the headers and body into the same outgoing\n  // stream, it helps to keep track of this so that we can skip that many bytes\n  // from the beginning of the stream when providing the outgoing stream.\n  writtenHeaderBytes = 0;\n\n  strictContentLength = false;\n  chunkedEncoding = false;\n  sendDate = false;\n  shouldKeepAlive = true;\n  override writable = true;\n  finished = false;\n  override destroyed = false;\n  useChunkedEncodingByDefault = true;\n  maxRequestsOnConnectionReached = false;\n\n  // These are attributes provided by the Node.js implementation.\n  override _closed = false;\n  _headerSent = false;\n  _onPendingData: (delta: number) => void = () => {};\n  _header: string | null = null;\n  _contentLength: number | null = null;\n  _hasBody = true;\n  _removedContLen = false;\n  _removedConnection = false;\n  _removedTE = false;\n  _last = false;\n  _defaultKeepAlive = true;\n  _maxRequestsPerSocket: number | undefined;\n  _keepAliveTimeout = 0;\n\n  constructor(req?: IncomingMessage, options?: OutgoingMessageOptions) {\n    super();\n    this.req = req;\n    this[kHighWaterMark] = options?.highWaterMark ?? getDefaultHighWaterMark();\n    this[kRejectNonStandardBodyWrites] =\n      options?.rejectNonStandardBodyWrites ?? false;\n    this.#buffer = new MessageBuffer(this.#onDataWritten.bind(this), {\n      highWaterMark: this[kHighWaterMark],\n    });\n\n    this.once('end', () => {\n      // We need to emit close in a queueMicrotask because\n      // this is the only way we can ensure that the close event is emitted after destroy.\n      queueMicrotask(() => {\n        this._closed = true;\n        this.emit('close');\n      });\n    });\n  }\n\n  #onDataWritten(data: DataWrittenEvent[]): void {\n    this.emit('_dataWritten', data);\n  }\n\n  override cork(): void {\n    this[kCorked]++;\n    this.#buffer?.cork();\n  }\n\n  override uncork(): void {\n    this[kCorked]--;\n    this.#buffer?.uncork();\n\n    if (this[kCorked] || this[kChunkedBuffer].length === 0) {\n      return;\n    }\n\n    for (const { data, encoding, callback } of this[kChunkedBuffer]) {\n      this._send(data ?? '', encoding, callback);\n    }\n\n    this[kChunkedBuffer].length = 0;\n    this[kChunkedLength] = 0;\n  }\n\n  _storeHeader(\n    firstLine: string,\n    headers: OutgoingHttpHeaders | OutgoingHttpHeader[] | null\n  ): void {\n    // firstLine in the case of request is: 'GET /index.html HTTP/1.1\\r\\n'\n    // in the case of response it is: 'HTTP/1.1 200 OK\\r\\n'\n    const state: HeaderState = {\n      connection: false,\n      contLen: false,\n      te: false,\n      date: false,\n      expect: false,\n      trailer: false,\n      header: firstLine,\n    };\n\n    if (headers != null) {\n      if (headers === this[kOutHeaders]) {\n        for (const key in headers) {\n          const entry = headers[key] as [string, string];\n          processHeader(this, state, entry[0], entry[1], false);\n        }\n      } else if (Array.isArray(headers)) {\n        if (headers.length && Array.isArray(headers[0])) {\n          for (let i = 0; i < headers.length; i++) {\n            const entry = headers[i] as unknown as [string, string];\n            processHeader(this, state, entry[0], entry[1], true);\n          }\n        } else {\n          if (headers.length % 2 !== 0) {\n            throw new ERR_INVALID_ARG_VALUE('headers', headers);\n          }\n\n          for (let n = 0; n < headers.length; n += 2) {\n            processHeader(\n              this,\n              state,\n              headers[n] as string,\n              headers[n + 1] as string,\n              true\n            );\n          }\n        }\n      } else {\n        for (const key in headers) {\n          // eslint-disable-next-line no-prototype-builtins\n          if (headers.hasOwnProperty(key)) {\n            const _headers = headers;\n            processHeader(\n              this,\n              state,\n              key,\n              _headers[key] as OutgoingHttpHeader,\n              true\n            );\n          }\n        }\n      }\n    }\n\n    let { header } = state;\n\n    // Date header\n    if (this.sendDate && !state.date) {\n      header += 'Date: ' + utcDate() + '\\r\\n';\n    }\n\n    // Force the connection to close when the response is a 204 No Content or\n    // a 304 Not Modified and the user has set a \"Transfer-Encoding: chunked\"\n    // header.\n    //\n    // RFC 2616 mandates that 204 and 304 responses MUST NOT have a body but\n    // node.js used to send out a zero chunk anyway to accommodate clients\n    // that don't have special handling for those responses.\n    //\n    // It was pointed out that this might confuse reverse proxies to the point\n    // of creating security liabilities, so suppress the zero chunk and force\n    // the connection to close.\n    if (\n      this.chunkedEncoding &&\n      ((this as unknown as ServerResponse).statusCode === 204 ||\n        (this as unknown as ServerResponse).statusCode === 304)\n    ) {\n      this.chunkedEncoding = false;\n      this.shouldKeepAlive = false;\n    }\n\n    // keep-alive logic\n    if (this._removedConnection) {\n      // shouldKeepAlive is generally true for HTTP/1.1. In that common case,\n      // even if the connection header isn't sent, we still persist by default.\n      this._last = !this.shouldKeepAlive;\n    } else if (!state.connection) {\n      const shouldSendKeepAlive =\n        this.shouldKeepAlive &&\n        (state.contLen ||\n          this.useChunkedEncodingByDefault ||\n          (this as unknown as { agent: unknown }).agent);\n      if (shouldSendKeepAlive && this.maxRequestsOnConnectionReached) {\n        header += 'Connection: close\\r\\n';\n      } else if (shouldSendKeepAlive) {\n        header += 'Connection: keep-alive\\r\\n';\n        if (this._keepAliveTimeout && this._defaultKeepAlive) {\n          const timeoutSeconds = Math.floor(this._keepAliveTimeout / 1000);\n          let max = '';\n          if (\n            this._maxRequestsPerSocket != null &&\n            ~~this._maxRequestsPerSocket > 0\n          ) {\n            max = `, max=${this._maxRequestsPerSocket}`;\n          }\n          header += `Keep-Alive: timeout=${timeoutSeconds}${max}\\r\\n`;\n        }\n      } else {\n        this._last = true;\n        header += 'Connection: close\\r\\n';\n      }\n    }\n\n    if (!state.contLen && !state.te) {\n      if (!this._hasBody) {\n        // Make sure we don't end the 0\\r\\n\\r\\n at the end of the message.\n        this.chunkedEncoding = false;\n      } else if (!this.useChunkedEncodingByDefault) {\n        this._last = true;\n      } else if (\n        !state.trailer &&\n        !this._removedContLen &&\n        typeof this._contentLength === 'number'\n      ) {\n        // eslint-disable-next-line @typescript-eslint/restrict-plus-operands\n        header += 'Content-Length: ' + this._contentLength + '\\r\\n';\n      } else if (!this._removedTE) {\n        header += 'Transfer-Encoding: chunked\\r\\n';\n        this.chunkedEncoding = true;\n      } else {\n        // We can't keep alive in this case, because with no header info the body\n        // is defined as all data until the connection is closed.\n        this._last = true;\n      }\n    }\n\n    this._header = header + '\\r\\n';\n    this._headerSent = false;\n\n    // Wait until the first body chunk, or close(), is sent to flush,\n    // UNLESS we're sending Expect: 100-continue.\n    if (state.expect) this._send('');\n  }\n\n  _finish(): void {\n    this.emit('prefinish');\n  }\n\n  _flushOutput(buffer: MessageBuffer): boolean | undefined {\n    const outputData = this.outputData;\n    if (outputData.length === 0) {\n      return undefined;\n    }\n\n    buffer.cork();\n    for (const { data, encoding, callback } of outputData) {\n      buffer.write(data, encoding, callback);\n    }\n    buffer.uncork();\n\n    this.outputData = [];\n    this._onPendingData(-this.outputSize);\n    this.outputSize = 0;\n\n    return true;\n  }\n\n  _flush(): void {\n    if (this.#buffer != null) {\n      const ret = this._flushOutput(this.#buffer);\n\n      if (this.finished) {\n        this._finish();\n      } else if (ret && this[kNeedDrain]) {\n        this[kNeedDrain] = false;\n        this.emit('drain');\n      }\n    }\n  }\n\n  // @ts-expect-error TS2611 Required for accessor\n  get writableLength(): number {\n    // If using buffer with headers, include buffer's writable length\n    if (this.#buffer != null && (this._header !== null || this._headerSent)) {\n      return (\n        this.outputSize + this.#buffer.writableLength + this[kChunkedLength]\n      );\n    }\n    return this.outputSize + this[kChunkedLength];\n  }\n\n  // @ts-expect-error TS2611 Required for accessor\n  get writableCorked(): number {\n    return this[kCorked];\n  }\n\n  // @ts-expect-error TS2611 Required for accessor\n  get writableNeedDrain(): boolean {\n    return !this.destroyed && !this.finished && this[kNeedDrain];\n  }\n\n  setHeader(name: string, value: number | string | string[]): this {\n    if (this._header) {\n      throw new ERR_HTTP_HEADERS_SENT('set');\n    }\n    validateHeaderName(name);\n    validateHeaderValue(name, value);\n\n    let headers = this[kOutHeaders];\n    if (headers === null) {\n      this[kOutHeaders] = headers = {};\n    }\n\n    headers[name.toLowerCase()] = [name, value];\n    return this;\n  }\n  setHeaders(\n    headers: Headers | Map<string, string | number | readonly string[]>\n  ): this {\n    if (this.headersSent) {\n      throw new ERR_HTTP_HEADERS_SENT('set');\n    }\n\n    if (\n      Array.isArray(headers) ||\n      typeof headers !== 'object' ||\n      !('keys' in headers) ||\n      !('get' in headers) ||\n      typeof headers.keys !== 'function' ||\n      typeof headers.get !== 'function'\n    ) {\n      throw new ERR_INVALID_ARG_TYPE('headers', ['Headers', 'Map'], headers);\n    }\n\n    // Headers object joins multiple cookies with a comma when using\n    // the getter to retrieve the value,\n    // unless iterating over the headers directly.\n    // We also cannot safely split by comma.\n    // To avoid setHeader overwriting the previous value we push\n    // set-cookie values in array and set them all at once.\n    const cookies: string[] = [];\n\n    for (const { 0: key, 1: value } of headers) {\n      if (key === 'set-cookie') {\n        if (Array.isArray(value)) {\n          cookies.push(...(value as string[]));\n        } else {\n          cookies.push(value as string);\n        }\n        continue;\n      }\n      this.setHeader(key, value as string | string[]);\n    }\n    if (cookies.length) {\n      this.setHeader('set-cookie', cookies);\n    }\n\n    return this;\n  }\n\n  appendHeader(\n    name: string,\n    value: number | string | ReadonlyArray<string> | OutgoingHttpHeader\n  ): this {\n    if (this._header) {\n      throw new ERR_HTTP_HEADERS_SENT('append');\n    }\n    validateHeaderName(name);\n    validateHeaderValue(name, value);\n\n    const field = name.toLowerCase();\n    const headers = this[kOutHeaders];\n    if (headers === null || !headers[field]) {\n      return this.setHeader(name, value);\n    }\n\n    // Prepare the field for appending, if required\n    if (!Array.isArray(headers[field][1])) {\n      headers[field][1] = [headers[field][1]];\n    }\n\n    const existingValues = headers[field][1];\n    if (Array.isArray(value)) {\n      for (let i = 0, length = value.length; i < length; i++) {\n        existingValues.push(value[i] as string);\n      }\n    } else {\n      existingValues.push(value);\n    }\n\n    return this;\n  }\n\n  getHeader(name: string): number | string | string[] | undefined {\n    validateString(name, 'name');\n\n    const headers = this[kOutHeaders];\n    if (headers === null) {\n      return;\n    }\n\n    const entry = headers[name.toLowerCase()];\n    return entry?.[1] as string;\n  }\n\n  hasHeader(name: unknown): boolean {\n    validateString(name, 'name');\n    return Boolean(this[kOutHeaders]?.[name.toLowerCase()]);\n  }\n\n  removeHeader(name: string): void {\n    validateString(name, 'name');\n\n    if (this._header) {\n      throw new ERR_HTTP_HEADERS_SENT('remove');\n    }\n\n    const key = name.toLowerCase();\n\n    switch (key) {\n      case 'connection':\n        this._removedConnection = true;\n        break;\n      case 'content-length':\n        this._removedContLen = true;\n        break;\n      case 'transfer-encoding':\n        this._removedTE = true;\n        break;\n      case 'date':\n        this.sendDate = false;\n        break;\n    }\n\n    if (this[kOutHeaders] !== null) {\n      // eslint-disable-next-line @typescript-eslint/no-dynamic-delete\n      delete this[kOutHeaders][key];\n    }\n  }\n\n  // Returns an array of the names of the current outgoing headers.\n  getHeaderNames(): string[] {\n    return this[kOutHeaders] !== null ? Object.keys(this[kOutHeaders]) : [];\n  }\n\n  // Returns an array of the names of the current outgoing raw headers.\n  getRawHeaderNames(): string[] {\n    const headersMap = this[kOutHeaders];\n    if (headersMap === null) return [];\n\n    const values = Object.values(headersMap);\n    const headers = Array.from<string>({ length: values.length });\n    // Retain for(;;) loop for performance reasons\n    // Refs: https://github.com/nodejs/node/pull/30958\n    for (let i = 0, l = values.length; i < l; i++) {\n      headers[i] = (values[i] as [string, string])[0];\n    }\n\n    return headers;\n  }\n\n  flushHeaders(): void {\n    if (!this._header) {\n      this._implicitHeader();\n    }\n\n    // Force-flush the headers.\n    this._send('');\n  }\n\n  getHeaders(): OutgoingHttpHeaders {\n    const headers = this[kOutHeaders];\n    const ret: Record<string, string> = {};\n    if (headers) {\n      const keys = Object.keys(headers);\n      // Retain for(;;) loop for performance reasons\n      // Refs: https://github.com/nodejs/node/pull/30958\n      for (let i = 0; i < keys.length; ++i) {\n        const key = keys[i] as keyof typeof headers;\n        const val = (headers[key] as [string, string])[1];\n        ret[key] = val;\n      }\n    }\n    return ret;\n  }\n\n  get headersSent(): boolean {\n    return !!this._header;\n  }\n\n  override pipe<T extends NodeJS.WritableStream>(destination: T): T {\n    this.emit('error', new ERR_STREAM_CANNOT_PIPE());\n    return destination;\n  }\n\n  [EventEmitter.captureRejectionSymbol](error: Error): void {\n    this.destroy(error);\n  }\n\n  _implicitHeader(): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('_implicitHeader()');\n  }\n\n  _renderHeaders(): Record<string, string> {\n    if (this._header) {\n      throw new ERR_HTTP_HEADERS_SENT('render');\n    }\n\n    const headersMap = this[kOutHeaders];\n    const headers: Record<string, string> = {};\n\n    if (headersMap !== null) {\n      const keys = Object.keys(headersMap);\n      // Retain for(;;) loop for performance reasons\n      // Refs: https://github.com/nodejs/node/pull/30958\n      for (let i = 0; i < keys.length; i++) {\n        const key = keys[i] as keyof typeof headersMap;\n        headers[(headersMap[key] as [string, string])[0]] = (\n          headersMap[key] as [string, string]\n        )[1];\n      }\n    }\n    return headers;\n  }\n\n  _send(\n    data: string | Uint8Array,\n    encoding?: BufferEncoding | WriteCallback | null,\n    callback?: WriteCallback | null,\n    byteLength?: number\n  ): boolean {\n    // This is a shameful hack to get the headers and first body chunk onto\n    // the same packet. Future versions of Node are going to take care of\n    // this at a lower level and in a more general way.\n    if (!this._headerSent && this._header !== null) {\n      const header = this._header;\n      if (\n        typeof data === 'string' &&\n        (encoding === 'utf8' || encoding === 'latin1' || !encoding)\n      ) {\n        data = header + data;\n      } else {\n        this.outputData.unshift({\n          data: header,\n          encoding: 'latin1',\n          callback: null,\n        });\n        this.outputSize += header.length;\n        this._onPendingData(header.length);\n      }\n\n      this._headerSent = true;\n      this.writtenHeaderBytes = header.length;\n\n      // Difference from Node.js: Parse response headers to emit _headersSent event.\n      // This deviates from Node.js behavior but is required for ServerResponse compatibility.\n      //\n      // The same OutgoingMessage class is used for both:\n      // - Client requests: header starts with request line \"POST /path HTTP/1.1\"\n      // - Server responses: header starts with status line \"HTTP/1.1 200 OK\"\n      //\n      // We only parse and emit events for server responses (status lines that match the HTTP response format).\n      // Client requests are ignored to avoid parsing errors when request lines don't match response format.\n      const [statusLine, ...headerLines] = header.split('\\r\\n') as [\n        string,\n        ...string[],\n      ];\n\n      const STATUS_LINE_REGEXP =\n        /^HTTP\\/\\d+\\.\\d+ (?<statusCode>\\d+) (?<statusMessage>.*)$/;\n      const statusLineResult = STATUS_LINE_REGEXP.exec(statusLine);\n\n      if (statusLineResult != null) {\n        const { statusCode: statusCodeText, statusMessage } =\n          statusLineResult.groups ?? {};\n        const headers = new Headers();\n\n        for (const headerLine of headerLines) {\n          if (headerLine !== '') {\n            const pos = headerLine.indexOf(': ');\n            headers.append(headerLine.slice(0, pos), headerLine.slice(pos + 2));\n          }\n        }\n        this.emit('_headersSent', {\n          statusCode: Number(statusCodeText as string),\n          statusMessage,\n          headers,\n        } as HeadersSentEvent);\n      }\n    }\n    return this._writeRaw(data, encoding, callback, byteLength);\n  }\n\n  override write(\n    chunk: string | Buffer | Uint8Array,\n    encoding?: BufferEncoding | WriteCallback | null,\n    callback?: WriteCallback\n  ): boolean {\n    if (typeof encoding === 'function') {\n      callback = encoding;\n      encoding = null;\n    }\n\n    const ret = this.#write(chunk, encoding, callback, false);\n    if (!ret) {\n      this[kNeedDrain] = true;\n    }\n    return ret;\n  }\n\n  override end(\n    chunk?: string | Buffer | Uint8Array | WriteCallback | null,\n    encoding?: BufferEncoding | WriteCallback | null,\n    callback?: WriteCallback\n  ): this {\n    if (typeof chunk === 'function') {\n      callback = chunk;\n      chunk = null;\n      encoding = null;\n    } else if (typeof encoding === 'function') {\n      callback = encoding;\n      encoding = null;\n    }\n\n    if (chunk) {\n      if (this.finished) {\n        this.#onError(\n          new ERR_STREAM_WRITE_AFTER_END(),\n          typeof callback !== 'function' ? (): void => {} : callback\n        );\n        return this;\n      }\n\n      // Difference from Node.js -\n      // In Node.js, if a socket exists, we would also call socket.cork() at this point.\n      // For our implementation we do the same for the \"written data buffer\"\n      this.#buffer?.cork();\n      this.#write(chunk, encoding, null, true);\n    } else if (this.finished) {\n      if (typeof callback === 'function') {\n        if (!this.writableFinished) {\n          this.on('finish', callback);\n        } else {\n          callback(new ERR_STREAM_ALREADY_FINISHED('end'));\n        }\n      }\n      return this;\n    } else if (!this._header) {\n      // Difference from Node.js -\n      // In Node.js, if a socket exists, we would also call socket.cork() at this point.\n      // For our implementation we do the same for the \"written data buffer\"\n      this.#buffer?.cork();\n      this._contentLength = 0;\n      this._implicitHeader();\n    }\n\n    if (typeof callback === 'function') this.once('finish', callback);\n\n    if (\n      this.#checkStrictContentLength() &&\n      this[kBytesWritten] !== this._contentLength\n    ) {\n      throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH(\n        this[kBytesWritten],\n        this._contentLength ?? 0\n      );\n    }\n\n    const finish = onFinish.bind(undefined, this);\n\n    if (this._hasBody && this.chunkedEncoding) {\n      // Difference from Node.js -\n      // Chunked transfer encoding doesn't need to use the low-level protocol\n      // (with each chunk preceded by its length)\n      // So here we just send an empty chunk. Trailers are not supported\n\n      // this._send(\"0\\r\\n\" + this._trailer + \"\\r\\n\", \"latin1\", finish);\n      this._send('', 'latin1', finish);\n    } else if (!this._headerSent || this.writableLength || chunk) {\n      this._send('', 'latin1', finish);\n    } else {\n      queueMicrotask(finish);\n    }\n\n    // Difference from Node.js -\n    // In Node.js, if a socket exists, we would also call socket.uncork() at this point.\n    // For our implementation we do the same for the \"written data buffer\"\n    this.#buffer?.uncork();\n    this[kCorked] = 1;\n    this.uncork();\n\n    this.finished = true;\n    this._writableState.finished = true;\n    this._writableState.corked = 1;\n\n    // Difference from Node.js -\n    // In Node.js, if a socket exists, and there is no pending output data,\n    // we would also call this._finish() at this point.\n    // For our implementation we do the same for the \"written data buffer\"\n    if (this.outputData.length === 0 && this.#buffer != null) {\n      this._finish();\n    }\n\n    return this;\n  }\n\n  _writeRaw(\n    data: string | Uint8Array,\n    encoding?: BufferEncoding | WriteCallback | null,\n    callback?: WriteCallback | null,\n    _size?: number\n  ): boolean {\n    if (this.destroyed) {\n      return false;\n    }\n\n    // Difference from Node.js -\n    // In Node.js, we would check for an underlying socket, and if that socket\n    // exists and is already destroyed, simply return false.\n\n    if (typeof encoding === 'function') {\n      callback = encoding;\n      encoding = null;\n    }\n\n    // Difference from Node.js -\n    // In Node.js, we would check for an underlying socket, and if that socket\n    // exists and is currently writable, it would flush any pending data to the socket and then\n    // write the current chunk's data directly into the socket. Afterwards, it would return with the\n    // value returned from socket.write().\n    if (this.#buffer != null && (this._header !== null || this._headerSent)) {\n      if (this.outputData.length) {\n        this._flushOutput(this.#buffer);\n      }\n\n      this.#buffer.write(data, encoding, callback);\n\n      // Always return true for corked writes (imitating Node.js behavior)\n      if (this.#buffer.writableCorked > 0) {\n        return true;\n      }\n\n      // For uncorked writes, check if we need to signal backpressure\n      // based on the buffer's high water mark\n      return this.#buffer.writableLength < this.#buffer.writableHighWaterMark;\n    }\n\n    this.outputData.push({ data, encoding, callback });\n    this.outputSize += data.length;\n    this._onPendingData(data.length);\n    return this.outputSize < this[kHighWaterMark];\n  }\n\n  override destroy(err?: unknown, _cb?: (err?: unknown) => void): this {\n    if (this.destroyed) {\n      return this;\n    }\n    if (err != null) {\n      this.emit('error', err);\n    }\n    this.destroyed = true;\n    this[kErrored] = err as Error;\n\n    return this;\n  }\n\n  // @ts-expect-error TS2611 Property accessor.\n  get errored(): Error | null {\n    return this[kErrored];\n  }\n\n  // @ts-expect-error TS2611 Property accessor.\n  get closed(): boolean {\n    return this._closed;\n  }\n\n  // @ts-expect-error TS2611 Property accessor.\n  get writableEnded(): boolean {\n    return this.finished;\n  }\n\n  // @ts-expect-error TS2611 Property accessor.\n  get writableHighWaterMark(): number {\n    return this.#buffer?.writableHighWaterMark ?? this[kHighWaterMark];\n  }\n\n  // @ts-expect-error TS2611 Property accessor.\n  get writableObjectMode(): boolean {\n    return false;\n  }\n\n  #checkStrictContentLength(): boolean {\n    return (\n      this.strictContentLength &&\n      this._contentLength != null &&\n      this._hasBody &&\n      !this._removedContLen &&\n      !this.chunkedEncoding &&\n      !this.hasHeader('transfer-encoding')\n    );\n  }\n\n  #onError(err: Error, callback: WriteCallback): void {\n    if (this.destroyed) {\n      return;\n    }\n\n    queueMicrotask(() => {\n      emitErrorNt(this, err, callback);\n    });\n  }\n\n  #write(\n    this: OutgoingMessage,\n    chunk: string | Buffer | Uint8Array | null,\n    encoding: BufferEncoding | undefined | null,\n    callback: WriteCallback | undefined | null,\n    fromEnd: boolean\n  ): boolean {\n    if (typeof callback !== 'function') {\n      callback = (): void => {};\n    }\n\n    if (chunk === null) {\n      throw new ERR_STREAM_NULL_VALUES();\n    } else if (typeof chunk !== 'string' && !isUint8Array(chunk)) {\n      throw new ERR_INVALID_ARG_TYPE(\n        'chunk',\n        ['string', 'Buffer', 'Uint8Array'],\n        chunk\n      );\n    }\n\n    let err: Error | undefined = undefined;\n    if (this.finished) {\n      err = new ERR_STREAM_WRITE_AFTER_END();\n    } else if (this.destroyed) {\n      err = new ERR_STREAM_DESTROYED('write');\n    }\n\n    if (err) {\n      if (!this.destroyed) {\n        this.#onError(err, callback);\n      } else {\n        queueMicrotask(() => {\n          callback(err);\n        });\n      }\n      return false;\n    }\n\n    let len: number | undefined = undefined;\n\n    if (this.strictContentLength) {\n      len ??=\n        typeof chunk === 'string'\n          ? Buffer.byteLength(chunk, encoding ?? undefined)\n          : chunk.byteLength;\n\n      if (\n        this.#checkStrictContentLength() &&\n        (fromEnd\n          ? this[kBytesWritten] + len !== this._contentLength\n          : this[kBytesWritten] + len > (this._contentLength ?? 0))\n      ) {\n        throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH(\n          len + this[kBytesWritten],\n          this._contentLength ?? 0\n        );\n      }\n\n      this[kBytesWritten] += len;\n    }\n\n    if (!this._header) {\n      if (fromEnd) {\n        len ??=\n          typeof chunk === 'string'\n            ? Buffer.byteLength(chunk, encoding ?? undefined)\n            : chunk.byteLength;\n        this._contentLength = len;\n      }\n      this._implicitHeader();\n    }\n\n    if (!this._hasBody) {\n      if (this[kRejectNonStandardBodyWrites]) {\n        throw new ERR_HTTP_BODY_NOT_ALLOWED();\n      } else {\n        queueMicrotask(callback);\n        return true;\n      }\n    }\n\n    if (!fromEnd && this.#buffer != null && !this.#buffer.writableCorked) {\n      this.#buffer.cork();\n      queueMicrotask(() => {\n        connectionCorkNT(this.#buffer as MessageBuffer);\n      });\n    }\n\n    let ret;\n    if (this.chunkedEncoding && chunk.length !== 0) {\n      len ??=\n        typeof chunk === 'string'\n          ? Buffer.byteLength(chunk, encoding ?? undefined)\n          : chunk.byteLength;\n      if (this[kCorked] && this._headerSent) {\n        this[kChunkedBuffer].push({ data: chunk, encoding, callback });\n        this[kChunkedLength] += len;\n        ret = this[kChunkedLength] < this[kHighWaterMark];\n      } else {\n        ret = this._send(chunk, encoding, callback, len);\n      }\n    } else {\n      ret = this._send(chunk, encoding, callback, len);\n    }\n\n    return ret;\n  }\n}\n\nfunction emitErrorNt(\n  msg: OutgoingMessage,\n  err: Error,\n  callback: WriteCallback\n): void {\n  callback(err);\n  if (typeof msg.emit === 'function' && !msg.destroyed) {\n    msg.emit('error', err);\n  }\n}\n\nfunction onFinish(outmsg: OutgoingMessage): void {\n  outmsg.emit('finish');\n}\n\nfunction connectionCorkNT(buffer: MessageBuffer): void {\n  buffer.uncork();\n}\n\n// isCookieField performs a case-insensitive comparison of a provided string\n// against the word \"cookie.\" As of V8 6.6 this is faster than handrolling or\n// using a case-insensitive RegExp.\nfunction isCookieField(s: string): boolean {\n  return s.length === 6 && s.toLowerCase() === 'cookie';\n}\n\nfunction isContentDispositionField(s: string): boolean {\n  return s.length === 19 && s.toLowerCase() === 'content-disposition';\n}\n\nfunction processHeader(\n  self: OutgoingMessage,\n  state: HeaderState,\n  key: string,\n  value: OutgoingHttpHeader,\n  validate: boolean\n): void {\n  if (validate) {\n    validateHeaderName(key);\n  }\n\n  // If key is content-disposition and there is content-length\n  // encode the value in latin1\n  // https://www.rfc-editor.org/rfc/rfc6266#section-4.3\n  // Refs: https://github.com/nodejs/node/pull/46528\n  if (isContentDispositionField(key) && self._contentLength) {\n    // The value could be an array here\n    if (Array.isArray(value)) {\n      for (let i = 0; i < value.length; i++) {\n        value[i] = String(Buffer.from(String(value[i]), 'latin1'));\n      }\n    } else {\n      value = String(Buffer.from(String(value), 'latin1'));\n    }\n  }\n\n  if (Array.isArray(value)) {\n    if (\n      (value.length < 2 || !isCookieField(key)) &&\n      (!(kUniqueHeaders in self) ||\n        !(self[kUniqueHeaders] as Set<string>).has(key.toLowerCase()))\n    ) {\n      // Retain for(;;) loop for performance reasons\n      // Refs: https://github.com/nodejs/node/pull/30958\n      for (let i = 0; i < value.length; i++) {\n        storeHeader(self, state, key, value[i] as string, validate);\n      }\n      return;\n    }\n    value = value.join('; ');\n  }\n  storeHeader(self, state, key, String(value), validate);\n}\n\nfunction storeHeader(\n  self: OutgoingMessage,\n  state: HeaderState,\n  key: string,\n  value: string,\n  validate: boolean\n): void {\n  if (validate) {\n    validateHeaderValue(key, value);\n  }\n  state.header += key + ': ' + value + '\\r\\n';\n  matchHeader(self, state, key, value);\n}\n\nfunction matchHeader(\n  self: OutgoingMessage,\n  state: HeaderState,\n  field: string,\n  value: string\n): void {\n  if (field.length < 4 || field.length > 17) return;\n  field = field.toLowerCase();\n  switch (field) {\n    case 'connection':\n      state.connection = true;\n      self._removedConnection = false;\n      if (RE_CONN_CLOSE.exec(value) !== null) self._last = true;\n      else self.shouldKeepAlive = true;\n      break;\n    case 'transfer-encoding':\n      state.te = true;\n      self._removedTE = false;\n      if (RE_TE_CHUNKED.exec(value) !== null) self.chunkedEncoding = true;\n      break;\n    case 'content-length':\n      state.contLen = true;\n      self._contentLength = +value;\n      self._removedContLen = false;\n      break;\n    case 'date':\n    case 'expect':\n    case 'trailer':\n      state[field] = true;\n      break;\n    case 'keep-alive':\n      self._defaultKeepAlive = false;\n      break;\n  }\n}\n"
  },
  {
    "path": "src/node/internal/internal_http_server.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\n// This module implements Node.js-compatible HTTP server functionality on top of\n// the fetch API due to workerd limitations. The key challenge is bridging Node.js's\n// stream-based API with the Fetch API's Request/Response model.\n//\n// The ServerResponse class implements a single-buffer strategy to minimize memory\n// usage when converting from Node.js streams to Fetch Response bodies:\n// - Pre-header data is temporarily buffered until headers are sent\n// - Post-header data streams directly without intermediate buffering\n// - Memory is freed immediately after the transition point\n\nimport {\n  ERR_METHOD_NOT_IMPLEMENTED,\n  ERR_HTTP_HEADERS_SENT,\n  ERR_HTTP_INVALID_STATUS_CODE,\n  ERR_INVALID_CHAR,\n  ERR_INVALID_ARG_VALUE,\n  ERR_OUT_OF_RANGE,\n  ERR_OPTION_NOT_IMPLEMENTED,\n  ERR_SERVER_ALREADY_LISTEN,\n} from 'node-internal:internal_errors';\nimport { EventEmitter } from 'node-internal:events';\nimport { getDefaultHighWaterMark } from 'node-internal:streams_state';\nimport {\n  kUniqueHeaders,\n  OutgoingMessage,\n  parseUniqueHeadersOption,\n} from 'node-internal:internal_http_outgoing';\nimport {\n  validateBoolean,\n  validateFunction,\n  validateInteger,\n  validateObject,\n  validatePort,\n  validateNumber,\n} from 'node-internal:validators';\nimport { portMapper } from 'cloudflare-internal:http';\nimport {\n  IncomingMessage,\n  setIncomingMessageSocket,\n  setIncomingRequestBody,\n} from 'node-internal:internal_http_incoming';\nimport { STATUS_CODES } from 'node-internal:internal_http_constants';\nimport {\n  kServerResponse,\n  kIncomingMessage,\n  splitHeaderValue,\n} from 'node-internal:internal_http_util';\nimport {\n  kOutHeaders,\n  type WrittenDataBufferEntry,\n  type HeadersSentEvent,\n} from 'node-internal:internal_http_outgoing';\nimport {\n  chunkExpression,\n  _checkInvalidHeaderChar,\n} from 'node-internal:internal_http';\nimport { _normalizeArgs } from 'node-internal:internal_net';\nimport { Buffer } from 'node-internal:internal_buffer';\n\nimport type {\n  Server as _Server,\n  ServerResponse as _ServerResponse,\n  RequestListener,\n  ServerOptions,\n  OutgoingHttpHeaders,\n  OutgoingHttpHeader,\n} from 'node:http';\nimport type { Socket, AddressInfo } from 'node:net';\n\nconst enableNodejsHttpServerModules =\n  !!Cloudflare.compatibilityFlags['enable_nodejs_http_server_modules'];\n\nexport const kConnectionsCheckingInterval = Symbol(\n  'http.server.connectionsCheckingInterval'\n);\n\nexport type DataWrittenEvent = {\n  index: number;\n  entry: WrittenDataBufferEntry;\n};\n\n// By default Node.js forbids the following headers to be joined by comma.\n// Cloudflare workers implementation of Server, uses Fetch and by default\n// fetch joins them. Therefore, we need to maintain this list of headers\n// to filter and only return the first match to be Node.js compatible.\n//\n// For more reference, here is a Node.js test that validates this behavior:\n// https://github.com/nodejs/node/blob/af77e4bf2f8bee0bc23f6ee129d6ca97511d34b9/test/parallel/test-http-server-multiheaders2.js\nconst multipleForbiddenHeaders = [\n  'host',\n  'content-type',\n  'user-agent',\n  'referer',\n  'authorization',\n  'proxy-authorization',\n  'if-modified-since',\n  'if-unmodified-since',\n  'from',\n  'location',\n  'max-forwards',\n];\n\nexport class Server\n  extends EventEmitter\n  implements _Server, BaseWithHttpOptions\n{\n  // @ts-expect-error TS2416 Server is not assignable to same property in base type.\n  [kIncomingMessage]: typeof IncomingMessage = IncomingMessage;\n  // @ts-expect-error TS2416 Server is not assignable to same property in base type.\n  [kServerResponse]: typeof ServerResponse = ServerResponse;\n  [kConnectionsCheckingInterval]?: number;\n  [kUniqueHeaders]: Set<string> | null = null;\n\n  // Similar option to this. Too lazy to write my own docs.\n  // http://www.squid-cache.org/Doc/config/half_closed_clients/\n  // https://wiki.squid-cache.org/SquidFaq/InnerWorkings#What_is_a_half-closed_filedescriptor.3F\n  httpAllowHalfOpen = false;\n  timeout = 0;\n  maxHeadersCount: number | null = null;\n  maxRequestsPerSocket = 0;\n  connectionsCheckingInterval = 30_000;\n  requestTimeout: number = 300_000;\n  headersTimeout: number = 60_000;\n  requireHostHeader: boolean = false;\n  joinDuplicateHeaders: boolean = false;\n  rejectNonStandardBodyWrites: boolean = false;\n  keepAliveTimeout: number = 5_000;\n  keepAliveTimeoutBuffer: number = 1_000;\n  highWaterMark: number = getDefaultHighWaterMark();\n  #port: number | null = null;\n\n  constructor(options?: ServerOptions, requestListener?: RequestListener) {\n    if (!enableNodejsHttpServerModules) {\n      throw new ERR_METHOD_NOT_IMPLEMENTED('Server');\n    }\n    super();\n\n    if (options != null) {\n      // @ts-expect-error TS2345 TODO(soon): Find a better way to handle this type mismatch.\n      storeHTTPOptions.call(this, options);\n    }\n\n    if (options?.highWaterMark !== undefined) {\n      validateNumber(options.highWaterMark, 'options.highWaterMark');\n      if (options.highWaterMark > 0) {\n        this.highWaterMark = options.highWaterMark;\n      }\n    }\n\n    if (typeof options === 'function') {\n      requestListener = options;\n      options = {};\n    } else if (options == null) {\n      options = {};\n    } else {\n      validateObject(options, 'options');\n    }\n\n    if (requestListener) {\n      this.on('request', requestListener);\n    }\n\n    this[kUniqueHeaders] = parseUniqueHeadersOption(\n      options.uniqueHeaders as (string | string[])[]\n    );\n  }\n\n  // Failing to call close() on a http server may result in the server being leaked.\n  // To prevent this, call close() when you're done with the server, or use\n  // explicit resource management. (example: await using s = createServer())\n  close(callback?: VoidFunction): this {\n    httpServerPreClose(this);\n    if (this.#port != null) {\n      portMapper.delete(this.#port);\n      this.#port = null;\n    }\n    if (typeof callback === 'function') {\n      this.once('close', callback);\n    }\n    queueMicrotask(() => {\n      this.emit('close');\n    });\n    return this;\n  }\n\n  closeAllConnections(): void {\n    // It doesn't make sense to support this method.\n    // Leave it as a noop.\n  }\n\n  closeIdleConnections(): void {\n    // It doesn't make sense to support this method.\n    // Leave it as a noop.\n  }\n\n  setTimeout(\n    msecs?: number | ((socket: Socket) => void),\n    callback?: (socket: Socket) => void\n  ): this {\n    if (typeof msecs === 'function') {\n      callback = msecs;\n      msecs = undefined;\n    } else if (typeof msecs === 'number') {\n      this.timeout = msecs;\n    }\n\n    if (typeof callback === 'function') {\n      this.once('timeout', callback);\n    }\n    return this;\n  }\n\n  async #onRequest(\n    request: Request,\n    env: unknown,\n    ctx: unknown\n  ): Promise<Response> {\n    const { incoming, response } = this.#toReqRes(request, env, ctx);\n    try {\n      this.emit('connection', this, incoming);\n      this.emit('request', incoming, response);\n      return await getServerResponseFetchResponse(response);\n    } catch (error: unknown) {\n      response.destroy(error);\n      throw error;\n    }\n  }\n\n  #toReqRes(\n    request: Request,\n    env: unknown,\n    ctx: unknown\n  ): {\n    incoming: IncomingMessage;\n    response: ServerResponse;\n  } {\n    const incoming = new this[kIncomingMessage]();\n    setIncomingMessageSocket(incoming, {\n      headers: request.headers,\n      localPort: this.#port as number,\n    });\n    const reqUrl = new URL(request.url);\n    incoming.url = reqUrl.pathname + reqUrl.search;\n\n    const headers = [];\n    for (const [key, value] of request.headers) {\n      if (multipleForbiddenHeaders.includes(key)) {\n        // By default fetch implementation will join the following header values with a comma.\n        // But in order to be node.js compatible, we need to select the first if possible.\n        // Use RFC 7230 compliant splitting that respects quoted-string constructions.\n        headers.push(key, splitHeaderValue(value));\n      } else {\n        headers.push(key, value);\n      }\n    }\n    incoming._addHeaderLines(headers, headers.length);\n\n    incoming.method = request.method;\n    setIncomingRequestBody(incoming, request.body);\n\n    // We provide a way for users to access to the Cloudflare-specific\n    // request properties, such as `cf` for accessing Cloudflare-specific request metadata.\n    incoming.cloudflare = {\n      env,\n      ctx,\n    };\n    if ('cf' in request) {\n      incoming.cloudflare.cf = request.cf as Record<string, unknown>;\n    }\n\n    const response = new this[kServerResponse](incoming, {\n      highWaterMark: this.highWaterMark,\n    });\n    return { incoming, response };\n  }\n\n  // We only support the listen() variant where a port number is passed or left\n  // unspecified. Such cases are listen(), listen(0, () => {}), listen(() => {}) etc.\n  listen(...args: unknown[]): this {\n    const [options, callback] = _normalizeArgs(args);\n    let port: number | undefined;\n    if (typeof options.port === 'number' || typeof options.port === 'string') {\n      port = validatePort(options.port, 'options.port');\n    }\n\n    // If port number is not provided, default to 0, just like Node.js\n    if (port == null) {\n      port = 0;\n    }\n\n    if (this.#port != null || portMapper.has(port)) {\n      throw new ERR_SERVER_ALREADY_LISTEN();\n    }\n\n    if (callback !== null) {\n      this.once('listening', callback as (...args: unknown[]) => unknown);\n    }\n\n    this.#port = this.#findSuitablePort(port);\n    // @ts-expect-error TS2322 Type mismatch. Not needed.\n    portMapper.set(this.#port, { fetch: this.#onRequest.bind(this) });\n    queueMicrotask(() => {\n      // If any of the listening handlers (here and in any of the other queueMicrotask(...) instances here,\n      // if the listening handlers throw an error, that will end up being reported to\n      // reportError(...) and will cause the globalThis error event to be triggered.\n      this.emit('listening');\n    });\n    return this;\n  }\n\n  #findSuitablePort(port: number): number {\n    // We don't have to check if portMapper has it because the caller\n    // already validates the uniqueness of the port and calls this method.\n    if (port !== 0) {\n      return port;\n    }\n\n    // Let's try at most 10 times to find a suitable port.\n    // If we can't find by that time, let's bail and throw an error.\n    for (let i = 0; i < 10; i++) {\n      port = Math.floor(Math.random() * 65535) + 1;\n      if (!portMapper.has(port)) {\n        return port;\n      }\n    }\n\n    // This is unlikely to happen, but just in case.\n    throw new Error('Failed to find a suitable port after 10 attempts');\n  }\n\n  getConnections(callback?: (err: Error | null, count: number) => void): this {\n    if (callback) {\n      validateFunction(callback, 'callback');\n      queueMicrotask(() => {\n        callback(null, 0);\n      });\n    }\n    return this;\n  }\n\n  ref(): this {\n    // It doesn't make sense to implement these at all as they are very specific to the way\n    // Node.js' event loop and process model works.\n    return this;\n  }\n\n  unref(): this {\n    // It doesn't make sense to implement these at all as they are very specific to the way\n    // Node.js' event loop and process model works.\n    return this;\n  }\n\n  get listening(): boolean {\n    return this.#port != null;\n  }\n\n  // We always return 127.0.0.1 as the address().address.\n  address(): string | AddressInfo | null {\n    if (this.#port == null) return null;\n    return { port: this.#port, family: 'IPv4', address: '127.0.0.1' };\n  }\n\n  get maxConnections(): number {\n    return Infinity;\n  }\n\n  get connections(): number {\n    return 0;\n  }\n\n  async [Symbol.asyncDispose](): Promise<void> {\n    // eslint-disable-next-line @typescript-eslint/no-invalid-void-type\n    const { promise, resolve } = Promise.withResolvers<void>();\n    this.close(resolve);\n    return promise;\n  }\n}\n\n// We use this handler to not expose this.#fetchResponse to outside world.\nlet getServerResponseFetchResponse: (\n  response: ServerResponse\n) => Promise<Response>;\n\n// Data flow:\n// 1. Before headers are sent: Data is buffered in a chunks array\n//    - MessageBuffer emits '_dataWritten' events with sequential indices (0, 1, 2...)\n//    - Each chunk is stored at its index position\n// 2. When headers are sent: Create Response with ReadableStream\n//    - Flush all buffered chunks to the stream\n//    - Clear the array with chunks.length = 0 to free memory\n//    - Set up listeners for future data\n// 3. After headers: Data streams directly without buffering\n//    - New '_dataWritten' events are immediately enqueued to the stream\n// 4. Completion: 'finish' event closes the ReadableStream\n// @ts-expect-error TS2720 Trailers related methods/attributes are missing.\nexport class ServerResponse<Req extends IncomingMessage = IncomingMessage>\n  extends OutgoingMessage\n  // @ts-expect-error TS2720 `rawTrailers` attribute is missing.\n  implements _ServerResponse<Req>\n{\n  override [kOutHeaders]: Record<string, [string, string | string[]]> | null =\n    null;\n\n  statusCode = 200;\n  statusMessage = 'unknown';\n\n  #fetchResponse: Promise<Response>;\n\n  static {\n    getServerResponseFetchResponse = (\n      response: ServerResponse\n    ): Promise<Response> => {\n      return response.#fetchResponse;\n    };\n  }\n\n  constructor(req: Req, options: ServerOptions = {}) {\n    if (!enableNodejsHttpServerModules) {\n      throw new ERR_METHOD_NOT_IMPLEMENTED('ServerResponse');\n    }\n\n    super(req, options);\n\n    if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) {\n      this.useChunkedEncodingByDefault = chunkExpression.test(\n        (req.headers.te as string | undefined) ?? ''\n      );\n      this.shouldKeepAlive = false;\n    }\n\n    const { promise, resolve, reject } = Promise.withResolvers<Response>();\n\n    let streamController: ReadableStreamController<Uint8Array> | null = null;\n    const chunks: (Buffer | Uint8Array)[] = [];\n    const state: { bytesWritten: number; contentLength: number | null } = {\n      bytesWritten: 0,\n      contentLength: null,\n    };\n\n    const handleData = (events: DataWrittenEvent[]): void => {\n      for (const event of events) {\n        let chunk = this.#dataFromDataWrittenEvent(event);\n\n        // Trim chunk if it would exceed content-length\n        if (\n          state.contentLength !== null &&\n          state.bytesWritten + chunk.length > state.contentLength\n        ) {\n          const remainingBytes = state.contentLength - state.bytesWritten;\n          if (remainingBytes > 0) {\n            chunk = chunk.slice(0, remainingBytes);\n          } else {\n            continue; // Skip this chunk entirely\n          }\n        }\n\n        state.bytesWritten += chunk.length;\n\n        if (streamController) {\n          if (chunk.length > 0) {\n            // @ts-expect-error TS2345 Buffer extends Uint8Array, but has ArrayBufferLike instead of ArrayBuffer.\n            streamController.enqueue(chunk);\n          }\n        } else {\n          chunks[event.index] = chunk;\n        }\n      }\n    };\n\n    this.on('_dataWritten', handleData);\n    this.once('error', reject);\n\n    this.once(\n      '_headersSent',\n      ({ statusCode, statusMessage, headers }: HeadersSentEvent) => {\n        for (const [name, value] of headers) {\n          // Optimization: Avoid unnecessary string comparison by checking length first\n          if (name.length === 14 && name.toLowerCase() === 'content-length') {\n            state.contentLength = parseInt(value, 10);\n            break;\n          }\n        }\n\n        resolve(\n          this.#toFetchResponse({\n            statusCode,\n            statusText: statusMessage,\n            headers,\n            onStreamStart: (controller) => {\n              streamController = controller;\n              for (const chunk of chunks) {\n                // @ts-expect-error TS2345 Buffer extends Uint8Array, but has ArrayBufferLike instead of ArrayBuffer.\n                controller.enqueue(chunk);\n              }\n              chunks.length = 0;\n            },\n          })\n        );\n\n        this._closed = true;\n        this.emit('close');\n      }\n    );\n\n    this.#fetchResponse = promise;\n  }\n\n  #toFetchResponse({\n    statusCode,\n    statusText,\n    headers,\n    onStreamStart,\n  }: {\n    statusCode: number;\n    statusText: string;\n    headers: Headers;\n    onStreamStart: (controller: ReadableStreamController<Uint8Array>) => void;\n  }): Response {\n    let body = null;\n\n    if (this._hasBody) {\n      body = new ReadableStream<Uint8Array>({\n        type: 'bytes',\n        start: (controller): void => {\n          onStreamStart(controller);\n          this.once('finish', () => {\n            controller.close();\n          });\n          this.on('error', controller.error.bind(controller));\n        },\n        cancel: (reason: unknown): void => {\n          this.destroy(reason);\n        },\n      });\n    }\n\n    return new Response(body, {\n      status: statusCode,\n      statusText,\n      headers,\n    });\n  }\n\n  #dataFromDataWrittenEvent({\n    index,\n    entry: { data, encoding },\n  }: DataWrittenEvent): Buffer | Uint8Array {\n    if (typeof data === 'string') {\n      // First chunk includes headers - skip them\n      if (index === 0) {\n        data = data.slice(this.writtenHeaderBytes);\n      }\n\n      return Buffer.from(data, encoding ?? undefined);\n    }\n\n    return data ?? Buffer.alloc(0);\n  }\n\n  assignSocket(_socket: Socket): void {\n    // We don't plan to support this method, since our\n    // implementation is based on fetch and not Node.js sockets.\n    throw new ERR_METHOD_NOT_IMPLEMENTED('assignSocket');\n  }\n\n  detachSocket(_socket: Socket): void {\n    // We don't plan to support this method, since our\n    // implementation is based on fetch and not Node.js sockets.\n    throw new ERR_METHOD_NOT_IMPLEMENTED('detachSocket');\n  }\n\n  writeContinue(_cb: VoidFunction): void {\n    // There is no path forward to support this with fetch or kj.\n    throw new ERR_METHOD_NOT_IMPLEMENTED('writeContinue');\n  }\n\n  writeProcessing(_cb: VoidFunction): void {\n    // There is no path forward to support this with fetch or kj.\n    throw new ERR_METHOD_NOT_IMPLEMENTED('writeProcessing');\n  }\n\n  writeEarlyHints(_hints: unknown, _cb: VoidFunction): void {\n    // There is no path forward to support this with fetch or kj.\n    throw new ERR_METHOD_NOT_IMPLEMENTED('writeEarlyHints');\n  }\n\n  override _implicitHeader(): void {\n    this.writeHead(this.statusCode);\n  }\n\n  writeHead(\n    statusCode: number,\n    reason?: string | OutgoingHttpHeaders | OutgoingHttpHeader[],\n    obj?: OutgoingHttpHeaders | OutgoingHttpHeader[]\n  ): this {\n    if (this._header) {\n      throw new ERR_HTTP_HEADERS_SENT('write');\n    }\n\n    const originalStatusCode = statusCode;\n\n    statusCode |= 0;\n    if (statusCode < 200 || statusCode > 999) {\n      // < 100 status codes are not supported by cloudflare workers.\n      throw new ERR_HTTP_INVALID_STATUS_CODE(originalStatusCode);\n    }\n\n    if (typeof reason === 'string') {\n      // writeHead(statusCode, reasonPhrase[, headers])\n      this.statusMessage = reason;\n    } else {\n      // writeHead(statusCode[, headers])\n      this.statusMessage ||= STATUS_CODES[`${statusCode}`] || 'unknown';\n      obj ??= reason;\n    }\n    this.statusCode = statusCode;\n\n    let headers;\n    if (this[kOutHeaders]) {\n      // Slow-case: when progressive API and header fields are passed.\n      if (Array.isArray(obj)) {\n        if (obj.length % 2 !== 0) {\n          throw new ERR_INVALID_ARG_VALUE('headers', obj);\n        }\n\n        // Headers in obj should override previous headers but still\n        // allow explicit duplicates. To do so, we first remove any\n        // existing conflicts, then use appendHeader.\n\n        for (let n = 0; n < obj.length; n += 2) {\n          this.removeHeader(`${obj[n]}`);\n        }\n\n        for (let n = 0; n < obj.length; n += 2) {\n          this.appendHeader(`${obj[n]}`, obj[n + 1] as OutgoingHttpHeader);\n        }\n      } else if (obj) {\n        for (const key of Object.keys(obj)) {\n          if (obj[key]) {\n            this.setHeader(key, obj[key]);\n          }\n        }\n      }\n      // Only progressive api is used\n      headers = this[kOutHeaders];\n    } else {\n      // Only writeHead() called\n      headers = obj;\n    }\n\n    if (_checkInvalidHeaderChar(this.statusMessage)) {\n      throw new ERR_INVALID_CHAR('statusMessage');\n    }\n\n    // TODO(soon): Unnecessary additional complexity when we could just build\n    // the Headers object up directly and skip the additional string wrangling.\n    const statusLine = `HTTP/1.1 ${statusCode} ${this.statusMessage}\\r\\n`;\n\n    if (statusCode === 204 || statusCode === 304) {\n      // RFC 2616, 10.2.5:\n      // The 204 response MUST NOT include a message-body, and thus is always\n      // terminated by the first empty line after the header fields.\n      // RFC 2616, 10.3.5:\n      // The 304 response MUST NOT contain a message-body, and thus is always\n      // terminated by the first empty line after the header fields.\n      // RFC 2616, 10.1 Informational 1xx:\n      // This class of status code indicates a provisional response,\n      // consisting only of the Status-Line and optional headers, and is\n      // terminated by an empty line.\n      this._hasBody = false;\n    }\n\n    // Convert headers to a compatible type for _storeHeader\n    const convertedHeaders =\n      headers && !Array.isArray(headers)\n        ? (headers as OutgoingHttpHeaders)\n        : headers;\n    this._storeHeader(statusLine, convertedHeaders ?? null);\n\n    return this;\n  }\n\n  writeHeader = this.writeHead.bind(this);\n}\n\nexport function setupConnectionsTracking(): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('setupConnectionsTracking');\n}\n\nexport interface BaseWithHttpOptions<\n  IM extends IncomingMessage = IncomingMessage,\n  SR extends ServerResponse = ServerResponse,\n> {\n  [kIncomingMessage]: IM;\n  [kServerResponse]: SR;\n  requestTimeout: number;\n  headersTimeout: number;\n  requireHostHeader: boolean;\n  joinDuplicateHeaders: boolean;\n  rejectNonStandardBodyWrites: boolean;\n}\n\nexport function storeHTTPOptions(\n  this: BaseWithHttpOptions,\n  options: ServerOptions\n): void {\n  // @ts-expect-error TS2322 Type mismatch.\n  this[kIncomingMessage] = options.IncomingMessage || IncomingMessage;\n  // @ts-expect-error TS2322 Type mismatch.\n  this[kServerResponse] = options.ServerResponse || ServerResponse;\n\n  const maxHeaderSize = options.maxHeaderSize;\n  if (maxHeaderSize !== undefined) {\n    validateInteger(maxHeaderSize, 'maxHeaderSize', 0);\n    throw new ERR_OPTION_NOT_IMPLEMENTED('maxHeaderSize');\n  }\n\n  const insecureHTTPParser = options.insecureHTTPParser;\n  if (insecureHTTPParser !== undefined) {\n    // If enabled it will use a HTTP parser with leniency flags enabled.\n    // Since our implementation does not use any http parser, and uses \"fetch\" API,\n    // it doesn't make sense to support this option.\n    validateBoolean(insecureHTTPParser, 'options.insecureHTTPParser');\n  }\n\n  const requestTimeout = options.requestTimeout;\n  if (requestTimeout !== undefined) {\n    validateInteger(requestTimeout, 'requestTimeout', 0);\n    this.requestTimeout = requestTimeout;\n  } else {\n    this.requestTimeout = 300_000; // 5 minutes\n  }\n\n  const headersTimeout = options.headersTimeout;\n  if (headersTimeout !== undefined) {\n    validateInteger(headersTimeout, 'headersTimeout', 0);\n    this.headersTimeout = headersTimeout;\n  } else {\n    this.headersTimeout = Math.min(60_000, this.requestTimeout); // Minimum between 60 seconds or requestTimeout\n  }\n\n  if (\n    this.requestTimeout > 0 &&\n    this.headersTimeout > 0 &&\n    this.headersTimeout > this.requestTimeout\n  ) {\n    throw new ERR_OUT_OF_RANGE(\n      'headersTimeout',\n      '<= requestTimeout',\n      headersTimeout\n    );\n  }\n\n  const keepAliveTimeout = options.keepAliveTimeout;\n  if (keepAliveTimeout !== undefined) {\n    validateInteger(keepAliveTimeout, 'keepAliveTimeout', 0);\n    throw new ERR_OPTION_NOT_IMPLEMENTED('keepAliveTimeout');\n  }\n\n  const keepAliveTimeoutBuffer = options.keepAliveTimeoutBuffer;\n  if (keepAliveTimeoutBuffer !== undefined) {\n    validateInteger(keepAliveTimeoutBuffer, 'keepAliveTimeoutBuffer');\n    throw new ERR_OPTION_NOT_IMPLEMENTED('keepAliveTimeoutBuffer');\n  }\n\n  const connectionsCheckingInterval = options.connectionsCheckingInterval;\n  if (connectionsCheckingInterval !== undefined) {\n    validateInteger(\n      connectionsCheckingInterval,\n      'connectionsCheckingInterval',\n      0\n    );\n    throw new ERR_OPTION_NOT_IMPLEMENTED('connectionsCheckingInterval');\n  }\n\n  const requireHostHeader = options.requireHostHeader;\n  if (requireHostHeader !== undefined) {\n    validateBoolean(requireHostHeader, 'options.requireHostHeader');\n    this.requireHostHeader = requireHostHeader;\n  } else {\n    this.requireHostHeader = true;\n  }\n\n  const joinDuplicateHeaders = options.joinDuplicateHeaders;\n  if (joinDuplicateHeaders !== undefined) {\n    validateBoolean(joinDuplicateHeaders, 'options.joinDuplicateHeaders');\n  }\n  this.joinDuplicateHeaders = joinDuplicateHeaders ?? false;\n\n  const rejectNonStandardBodyWrites = options.rejectNonStandardBodyWrites;\n  if (rejectNonStandardBodyWrites !== undefined) {\n    validateBoolean(\n      rejectNonStandardBodyWrites,\n      'options.rejectNonStandardBodyWrites'\n    );\n    this.rejectNonStandardBodyWrites = rejectNonStandardBodyWrites;\n  } else {\n    this.rejectNonStandardBodyWrites = false;\n  }\n}\n\nexport function _connectionListener(): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('_connectionListener');\n}\n\nexport function httpServerPreClose(server: Server): void {\n  server.closeIdleConnections();\n  clearInterval(server[kConnectionsCheckingInterval]);\n}\n"
  },
  {
    "path": "src/node/internal/internal_http_util.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\nimport type { IncomingMessage } from 'node:http';\n\nexport type IncomingMessageCallback = (req: IncomingMessage) => void;\n\ninterface OnceOptions {\n  preserveReturnValue?: boolean;\n}\n\nexport function once<RT>(\n  this: unknown,\n  callback: (...allArgs: unknown[]) => RT,\n  options?: OnceOptions\n): (...all: unknown[]) => RT {\n  const preserveReturnValue = options?.preserveReturnValue ?? false;\n  let called = false;\n  let returnValue: RT;\n  return function (this: unknown, ...args: unknown[]): RT {\n    if (called) return returnValue;\n    called = true;\n    const result = Reflect.apply(callback, this, args);\n    returnValue = preserveReturnValue ? result : (undefined as RT);\n    return result;\n  };\n}\n\nexport const kServerResponse = Symbol('ServerResponse');\nexport const kIncomingMessage = Symbol('IncomingMessage');\n\n// RFC 7230 compliant header value splitting that respects quoted-string constructions\n// Returns only the first value up to the first comma (outside of quoted strings)\n// Spec: https://www.rfc-editor.org/rfc/rfc7230.html#appendix-B\n// Edge cases handled:\n// - Quoted strings with commas: 'text/plain; param=\"a, b\"' -> 'text/plain; param=\"a, b\"'\n// - Escaped quotes in strings: 'text/plain; param=\"a \\\"quoted\\\" value\"' -> 'text/plain; param=\"a \\\"quoted\\\" value\"'\n// - Multiple values: 'text/plain; f=\"a, b\", text/html' -> 'text/plain; f=\"a, b\"'\n// - Whitespace before commas: 'value1 \\t , value2' -> 'value1'\n// - No commas: 'single-value' -> 'single-value'\nexport function splitHeaderValue(value: string): string {\n  let inQuotes = false;\n\n  for (let i = 0; i < value.length; i++) {\n    const char = value[i];\n\n    if (char === '\"') {\n      inQuotes = !inQuotes;\n    } else if (char === '\\\\' && inQuotes) {\n      i++; // Skip next character (it's escaped)\n    } else if (!inQuotes && char === ',') {\n      // Found unquoted comma, return\n      return value.slice(0, i);\n    }\n  }\n\n  return value;\n}\n"
  },
  {
    "path": "src/node/internal/internal_https_agent.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\n/* eslint-disable @typescript-eslint/no-deprecated,@typescript-eslint/restrict-plus-operands */\n\nimport { Agent as HttpAgent } from 'node-internal:internal_http_agent';\nimport type { RequestOptions } from 'node:https';\n\nexport class Agent extends HttpAgent {\n  override defaultPort = 443;\n  override protocol: string = 'https:';\n\n  override getName(options: RequestOptions = {}): string {\n    let name = super.getName(options);\n\n    name += ':';\n    if (options.ca) name += options.ca;\n\n    name += ':';\n    if (options.cert) name += options.cert;\n\n    name += ':';\n    if (options.clientCertEngine) name += options.clientCertEngine;\n\n    name += ':';\n    if (options.ciphers) name += options.ciphers;\n\n    name += ':';\n    // eslint-disable-next-line @typescript-eslint/no-base-to-string\n    if (options.key) name += options.key;\n\n    name += ':';\n    // eslint-disable-next-line @typescript-eslint/no-base-to-string\n    if (options.pfx) name += options.pfx;\n\n    name += ':';\n    if (options.rejectUnauthorized !== undefined)\n      name += options.rejectUnauthorized;\n\n    name += ':';\n    if (options.servername && options.servername !== options.host)\n      name += options.servername;\n\n    name += ':';\n    if (options.minVersion) name += options.minVersion;\n\n    name += ':';\n    if (options.maxVersion) name += options.maxVersion;\n\n    name += ':';\n    if (options.secureProtocol) name += options.secureProtocol;\n\n    name += ':';\n    if (options.crl) name += options.crl;\n\n    name += ':';\n    if (options.honorCipherOrder !== undefined)\n      name += options.honorCipherOrder;\n\n    name += ':';\n    if (options.ecdhCurve) name += options.ecdhCurve;\n\n    name += ':';\n    if (options.dhparam) name += options.dhparam;\n\n    name += ':';\n    if (options.secureOptions !== undefined) name += options.secureOptions;\n\n    name += ':';\n    if (options.sessionIdContext) name += options.sessionIdContext;\n\n    name += ':';\n    if (options.sigalgs) name += JSON.stringify(options.sigalgs);\n\n    name += ':';\n    if (options.privateKeyIdentifier) name += options.privateKeyIdentifier;\n\n    name += ':';\n    if (options.privateKeyEngine) name += options.privateKeyEngine;\n\n    return name;\n  }\n}\n\nexport const globalAgent = new Agent({\n  keepAlive: true,\n  scheduling: 'lifo',\n  timeout: 5000,\n});\n"
  },
  {
    "path": "src/node/internal/internal_https_server.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { Server as HttpServer } from 'node-internal:internal_http_server';\nimport { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors';\nimport type { Server as _Server } from 'node:https';\n\nexport class Server extends HttpServer implements _Server {\n  addContext(): void {\n    // Workerd don't support TLS SecureContext. It doesn't\n    // make sense to support this for us.\n    throw new ERR_METHOD_NOT_IMPLEMENTED('addContext');\n  }\n\n  getTicketKeys(): Buffer<ArrayBuffer> {\n    // We don't support TLS ticket keys.\n    throw new ERR_METHOD_NOT_IMPLEMENTED('getTicketKeys');\n  }\n\n  setSecureContext(): void {\n    // We don't support this for now.\n    // TODO(soon): Investigate if we want to have a implementation\n    // that just makes input validations and ignores the context.\n    throw new ERR_METHOD_NOT_IMPLEMENTED('setSecureContext');\n  }\n\n  setTicketKeys(): void {\n    // We don't support TLS ticket keys.\n    throw new ERR_METHOD_NOT_IMPLEMENTED('setTicketKeys');\n  }\n}\n"
  },
  {
    "path": "src/node/internal/internal_inspect.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Deno, Node.js and DefinitelyTyped:\n// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n/* TODO: the following is adopted code, enabling linting one day */\n/* eslint-disable */\n\nimport internalWorkers from 'cloudflare-internal:workers';\nimport internal from 'node-internal:util';\n\nimport { Buffer } from 'node-internal:internal_buffer';\nimport {\n  isAsyncFunction,\n  isGeneratorFunction,\n  isAnyArrayBuffer,\n  isArrayBuffer,\n  isArgumentsObject,\n  isBoxedPrimitive,\n  isDataView,\n  isMap,\n  isMapIterator,\n  isModuleNamespaceObject,\n  isNativeError,\n  isPromise,\n  isSet,\n  isSetIterator,\n  isWeakMap,\n  isWeakSet,\n  isRegExp,\n  isDate,\n  isTypedArray,\n  isStringObject,\n  isNumberObject,\n  isBooleanObject,\n  isBigIntObject,\n} from 'node-internal:internal_types';\n// import { ALL_PROPERTIES, ONLY_ENUMERABLE, getOwnNonIndexProperties } from \"node-internal:internal_utils\";\nimport {\n  validateObject,\n  validateString,\n  kValidateObjectAllowArray,\n} from 'node-internal:validators';\n\n// Simplified assertions to avoid `Assertions require every name in the call target to be\n// declared with an explicit type` TypeScript error\nfunction assert(value: boolean, message = 'Assertion failed'): asserts value {\n  if (!value) throw new Error(message);\n}\nassert.fail = function (message = 'Assertion failed'): never {\n  throw new Error(message);\n};\n\nfunction isError(e: unknown): e is Error {\n  // An error could be an instance of Error while not being a native error\n  // or could be from a different realm and not be instance of Error but still\n  // be a native error.\n  return isNativeError(e) || e instanceof Error;\n}\n\nconst typedArrayPrototype = Object.getPrototypeOf(Uint8Array).prototype;\nconst typedArrayPrototypeLength: (this: NodeJS.TypedArray) => number =\n  Object.getOwnPropertyDescriptor(typedArrayPrototype, 'length')!.get!;\nconst typedArrayPrototypeToStringTag: (this: NodeJS.TypedArray) => string =\n  Object.getOwnPropertyDescriptor(\n    typedArrayPrototype,\n    Symbol.toStringTag\n  )!.get!;\n\nconst setPrototypeSize: (this: Set<unknown>) => number =\n  Object.getOwnPropertyDescriptor(Set.prototype, 'size')!.get!;\nconst mapPrototypeSize: (this: Map<unknown, unknown>) => number =\n  Object.getOwnPropertyDescriptor(Map.prototype, 'size')!.get!;\n\nlet maxStack_ErrorName: string;\nlet maxStack_ErrorMessage: string;\nfunction isStackOverflowError(err: Error): boolean {\n  if (maxStack_ErrorMessage === undefined) {\n    try {\n      function overflowStack() {\n        overflowStack();\n      }\n      overflowStack();\n    } catch (err) {\n      assert(isError(err));\n      maxStack_ErrorMessage = err.message;\n      maxStack_ErrorName = err.name;\n    }\n  }\n\n  return (\n    err &&\n    err.name === maxStack_ErrorName &&\n    err.message === maxStack_ErrorMessage\n  );\n}\n\nexport const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom');\n\nconst colorRegExp = /\\u001b\\[\\d\\d?m/g;\n\nfunction removeColors(str: string): string {\n  return str.replace(colorRegExp, '');\n}\n\nexport interface InspectOptions {\n  /**\n   * If `true`, object's non-enumerable symbols and properties are included in the formatted result.\n   * `WeakMap` and `WeakSet` entries are also included as well as user defined prototype properties (excluding method properties).\n   * @default false\n   */\n  showHidden?: boolean;\n  /**\n   * Specifies the number of times to recurse while formatting object.\n   * This is useful for inspecting large objects.\n   * To recurse up to the maximum call stack size pass `Infinity` or `null`.\n   * @default 2\n   */\n  depth?: number | null;\n  /**\n   * If `true`, the output is styled with ANSI color codes. Colors are customizable.\n   */\n  colors?: boolean;\n  /**\n   * If `false`, `[util.inspect.custom](depth, opts, inspect)` functions are not invoked.\n   * @default true\n   */\n  customInspect?: boolean;\n  /**\n   * If `true`, `Proxy` inspection includes the target and handler objects.\n   * @default false\n   */\n  showProxy?: boolean;\n  /**\n   * Specifies the maximum number of `Array`, `TypedArray`, `WeakMap`, and `WeakSet` elements\n   * to include when formatting. Set to `null` or `Infinity` to show all elements.\n   * Set to `0` or negative to show no elements.\n   * @default 100\n   */\n  maxArrayLength?: number | null;\n  /**\n   * Specifies the maximum number of characters to\n   * include when formatting. Set to `null` or `Infinity` to show all elements.\n   * Set to `0` or negative to show no characters.\n   * @default 10000\n   */\n  maxStringLength?: number | null;\n  /**\n   * The length at which input values are split across multiple lines.\n   * Set to `Infinity` to format the input as a single line\n   * (in combination with `compact` set to `true` or any number >= `1`).\n   * @default 80\n   */\n  breakLength?: number;\n  /**\n   * Setting this to `false` causes each object key\n   * to be displayed on a new line. It will also add new lines to text that is\n   * longer than `breakLength`. If set to a number, the most `n` inner elements\n   * are united on a single line as long as all properties fit into\n   * `breakLength`. Short array elements are also grouped together. Note that no\n   * text will be reduced below 16 characters, no matter the `breakLength` size.\n   * For more information, see the example below.\n   * @default true\n   */\n  compact?: boolean | number;\n  /**\n   * If set to `true` or a function, all properties of an object, and `Set` and `Map`\n   * entries are sorted in the resulting string.\n   * If set to `true` the default sort is used.\n   * If set to a function, it is used as a compare function.\n   */\n  sorted?: boolean | ((a: string, b: string) => number);\n  /**\n   * If set to `true`, getters are going to be\n   * inspected as well. If set to `'get'` only getters without setter are going\n   * to be inspected. If set to `'set'` only getters having a corresponding\n   * setter are going to be inspected. This might cause side effects depending on\n   * the getter function.\n   * @default false\n   */\n  getters?: 'get' | 'set' | boolean;\n  /**\n   * If set to `true`, an underscore is used to separate every three digits in all bigints and numbers.\n   * @default false\n   */\n  numericSeparator?: boolean;\n}\nexport type Style =\n  | 'special'\n  | 'number'\n  | 'bigint'\n  | 'boolean'\n  | 'undefined'\n  | 'null'\n  | 'string'\n  | 'symbol'\n  | 'date'\n  | 'regexp'\n  | 'module'\n  | 'name';\nexport type CustomInspectFunction = (\n  depth: number,\n  options: InspectOptionsStylized\n) => any; // TODO: , inspect: inspect\nexport interface InspectOptionsStylized extends InspectOptions {\n  stylize(text: string, styleType: Style): string;\n}\n\nconst builtInObjects = new Set(\n  Object.getOwnPropertyNames(globalThis).filter(\n    (e) => /^[A-Z][a-zA-Z0-9]+$/.exec(e) !== null\n  )\n);\n\n// https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot\nconst isUndetectableObject = (v: unknown): boolean =>\n  typeof v === 'undefined' && v !== undefined;\n\n// These options must stay in sync with `getUserOptions`. So if any option will\n// be added or removed, `getUserOptions` must also be updated accordingly.\nexport const inspectDefaultOptions = Object.seal({\n  showHidden: false,\n  depth: 2,\n  colors: false,\n  customInspect: true,\n  showProxy: false,\n  maxArrayLength: 100,\n  maxStringLength: 10000,\n  breakLength: 80,\n  compact: 3,\n  sorted: false,\n  getters: false,\n  numericSeparator: false,\n} as const);\n\nconst kObjectType = 0;\nconst kArrayType = 1;\nconst kArrayExtrasType = 2;\n\nconst strEscapeSequencesRegExp =\n  /[\\x00-\\x1f\\x27\\x5c\\x7f-\\x9f]|[\\ud800-\\udbff](?![\\udc00-\\udfff])|(?<![\\ud800-\\udbff])[\\udc00-\\udfff]/;\nconst strEscapeSequencesReplacer =\n  /[\\x00-\\x1f\\x27\\x5c\\x7f-\\x9f]|[\\ud800-\\udbff](?![\\udc00-\\udfff])|(?<![\\ud800-\\udbff])[\\udc00-\\udfff]/g;\nconst strEscapeSequencesRegExpSingle =\n  /[\\x00-\\x1f\\x5c\\x7f-\\x9f]|[\\ud800-\\udbff](?![\\udc00-\\udfff])|(?<![\\ud800-\\udbff])[\\udc00-\\udfff]/;\nconst strEscapeSequencesReplacerSingle =\n  /[\\x00-\\x1f\\x5c\\x7f-\\x9f]|[\\ud800-\\udbff](?![\\udc00-\\udfff])|(?<![\\ud800-\\udbff])[\\udc00-\\udfff]/g;\n\nconst keyStrRegExp = /^[a-zA-Z_][a-zA-Z_0-9]*$/;\nconst numberRegExp = /^(0|[1-9][0-9]*)$/;\n\nconst nodeModulesRegExp = /[/\\\\]node_modules[/\\\\](.+?)(?=[/\\\\])/g;\n\nconst classRegExp = /^(\\s+[^(]*?)\\s*{/;\n// eslint-disable-next-line node-core/no-unescaped-regexp-dot\nconst stripCommentsRegExp = /(\\/\\/.*?\\n)|(\\/\\*(.|\\n)*?\\*\\/)/g;\n\nconst kMinLineLength = 16;\n\n// Constants to map the iterator state.\nconst kWeak = 0;\nconst kIterator = 1;\nconst kMapEntries = 2;\n\n// Escaped control characters (plus the single quote and the backslash). Use\n// empty strings to fill up unused entries.\nconst meta = [\n  '\\\\x00',\n  '\\\\x01',\n  '\\\\x02',\n  '\\\\x03',\n  '\\\\x04',\n  '\\\\x05',\n  '\\\\x06',\n  '\\\\x07', // x07\n  '\\\\b',\n  '\\\\t',\n  '\\\\n',\n  '\\\\x0B',\n  '\\\\f',\n  '\\\\r',\n  '\\\\x0E',\n  '\\\\x0F', // x0F\n  '\\\\x10',\n  '\\\\x11',\n  '\\\\x12',\n  '\\\\x13',\n  '\\\\x14',\n  '\\\\x15',\n  '\\\\x16',\n  '\\\\x17', // x17\n  '\\\\x18',\n  '\\\\x19',\n  '\\\\x1A',\n  '\\\\x1B',\n  '\\\\x1C',\n  '\\\\x1D',\n  '\\\\x1E',\n  '\\\\x1F', // x1F\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  \"\\\\'\",\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '', // x2F\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '', // x3F\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '', // x4F\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '\\\\\\\\',\n  '',\n  '',\n  '', // x5F\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '', // x6F\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '',\n  '\\\\x7F', // x7F\n  '\\\\x80',\n  '\\\\x81',\n  '\\\\x82',\n  '\\\\x83',\n  '\\\\x84',\n  '\\\\x85',\n  '\\\\x86',\n  '\\\\x87', // x87\n  '\\\\x88',\n  '\\\\x89',\n  '\\\\x8A',\n  '\\\\x8B',\n  '\\\\x8C',\n  '\\\\x8D',\n  '\\\\x8E',\n  '\\\\x8F', // x8F\n  '\\\\x90',\n  '\\\\x91',\n  '\\\\x92',\n  '\\\\x93',\n  '\\\\x94',\n  '\\\\x95',\n  '\\\\x96',\n  '\\\\x97', // x97\n  '\\\\x98',\n  '\\\\x99',\n  '\\\\x9A',\n  '\\\\x9B',\n  '\\\\x9C',\n  '\\\\x9D',\n  '\\\\x9E',\n  '\\\\x9F', // x9F\n];\n\n// Regex used for ansi escape code splitting\n// Adopted from https://github.com/chalk/ansi-regex/blob/HEAD/index.js\n// License: MIT, authors: @sindresorhus, Qix-, arjunmehta and LitoMore\n// Matches all ansi escape code sequences in a string\nconst ansiPattern = new RegExp(\n  '[\\\\u001B\\\\u009B][[\\\\]()#;?]*' +\n    '(?:(?:(?:(?:;[-a-zA-Z\\\\d\\\\/\\\\#&.:=?%@~_]+)*' +\n    '|[a-zA-Z\\\\d]+(?:;[-a-zA-Z\\\\d\\\\/\\\\#&.:=?%@~_]*)*)?' +\n    '(?:\\\\u0007|\\\\u001B\\\\u005C|\\\\u009C))' +\n    '|(?:(?:\\\\d{1,4}(?:;\\\\d{0,4})*)?' +\n    '[\\\\dA-PR-TZcf-nq-uy=><~]))',\n  'g'\n);\nconst ansi = new RegExp(ansiPattern, 'g');\n\ninterface Context extends Required<InspectOptionsStylized> {\n  maxArrayLength: number;\n  maxStringLength: number;\n  budget: Record<string, number>;\n  indentationLvl: number;\n  seen: unknown[];\n  currentDepth: number;\n  userOptions?: InspectOptions;\n  circular?: Map<unknown, number>;\n}\n\nfunction getUserOptions(\n  ctx: Context,\n  isCrossContext: boolean\n): InspectOptionsStylized {\n  const ret: InspectOptionsStylized = {\n    stylize: ctx.stylize,\n    showHidden: ctx.showHidden,\n    depth: ctx.depth,\n    colors: ctx.colors,\n    customInspect: ctx.customInspect,\n    showProxy: ctx.showProxy,\n    maxArrayLength: ctx.maxArrayLength,\n    maxStringLength: ctx.maxStringLength,\n    breakLength: ctx.breakLength,\n    compact: ctx.compact,\n    sorted: ctx.sorted,\n    getters: ctx.getters,\n    numericSeparator: ctx.numericSeparator,\n    ...ctx.userOptions,\n  };\n\n  // Typically, the target value will be an instance of `Object`. If that is\n  // *not* the case, the object may come from another vm.Context, and we want\n  // to avoid passing it objects from this Context in that case, so we remove\n  // the prototype from the returned object itself + the `stylize()` function,\n  // and remove all other non-primitives, including non-primitive user options.\n  if (isCrossContext) {\n    Object.setPrototypeOf(ret, null);\n    for (const key of Object.keys(ret) as (keyof InspectOptionsStylized)[]) {\n      if (\n        (typeof ret[key] === 'object' || typeof ret[key] === 'function') &&\n        ret[key] !== null\n      ) {\n        delete ret[key];\n      }\n    }\n    ret.stylize = Object.setPrototypeOf((value: string, flavour: Style) => {\n      let stylized;\n      try {\n        stylized = `${ctx.stylize(value, flavour)}`;\n      } catch {\n        // Continue regardless of error.\n      }\n\n      if (typeof stylized !== 'string') return value;\n      // `stylized` is a string as it should be, which is safe to pass along.\n      return stylized;\n    }, null);\n  }\n\n  return ret;\n}\n\n/**\n * Echos the value of any input. Tries to print the value out\n * in the best way possible given the different types.\n * @param {any} value The value to print out.\n * @param {object} opts Optional options object that alters the output.\n */\n/* Legacy: value, showHidden, depth, colors */\nexport function inspect(\n  value: unknown,\n  showHidden?: boolean,\n  depth?: number | null,\n  color?: boolean\n): string;\nexport function inspect(value: unknown, opts?: InspectOptions): string;\nexport function inspect(\n  value: unknown,\n  opts?: Partial<InspectOptionsStylized> | boolean\n): string {\n  // Default options\n  const ctx: Context = {\n    budget: {},\n    indentationLvl: 0,\n    seen: [],\n    currentDepth: 0,\n    stylize: stylizeNoColor,\n    showHidden: inspectDefaultOptions.showHidden,\n    depth: inspectDefaultOptions.depth,\n    colors: inspectDefaultOptions.colors,\n    customInspect: inspectDefaultOptions.customInspect,\n    showProxy: inspectDefaultOptions.showProxy,\n    maxArrayLength: inspectDefaultOptions.maxArrayLength,\n    maxStringLength: inspectDefaultOptions.maxStringLength,\n    breakLength: inspectDefaultOptions.breakLength,\n    compact: inspectDefaultOptions.compact,\n    sorted: inspectDefaultOptions.sorted,\n    getters: inspectDefaultOptions.getters,\n    numericSeparator: inspectDefaultOptions.numericSeparator,\n  };\n  if (arguments.length > 1) {\n    // Legacy...\n    if (arguments.length > 2) {\n      if (arguments[2] !== undefined) {\n        ctx.depth = arguments[2];\n      }\n      if (arguments.length > 3 && arguments[3] !== undefined) {\n        ctx.colors = arguments[3];\n      }\n    }\n    // Set user-specified options\n    if (typeof opts === 'boolean') {\n      ctx.showHidden = opts;\n    } else if (opts) {\n      const optKeys = Object.keys(opts) as (keyof InspectOptionsStylized)[];\n      for (let i = 0; i < optKeys.length; ++i) {\n        const key = optKeys[i]!;\n        // TODO(BridgeAR): Find a solution what to do about stylize. Either make\n        // this function public or add a new API with a similar or better\n        // functionality.\n        if (\n          Object.prototype.hasOwnProperty.call(inspectDefaultOptions, key) ||\n          key === 'stylize'\n        ) {\n          (ctx as Record<keyof InspectOptionsStylized, unknown>)[key] =\n            opts[key];\n        } else if (ctx.userOptions === undefined) {\n          // This is required to pass through the actual user input.\n          ctx.userOptions = opts;\n        }\n      }\n    }\n  }\n  if (ctx.colors) ctx.stylize = stylizeWithColor;\n  if (ctx.maxArrayLength === null) ctx.maxArrayLength = Infinity;\n  if (ctx.maxStringLength === null) ctx.maxStringLength = Infinity;\n  return formatValue(ctx, value, 0);\n}\ninspect.custom = customInspectSymbol;\n\nObject.defineProperty(inspect, 'defaultOptions', {\n  get() {\n    return inspectDefaultOptions;\n  },\n  set(options) {\n    validateObject(options, 'options');\n    return Object.assign(inspectDefaultOptions, options);\n  },\n});\n\n// Set Graphics Rendition https://en.wikipedia.org/wiki/ANSI_escape_code#graphics\n// Each color consists of an array with the color code as first entry and the\n// reset code as second entry.\nconst defaultFG = 39;\nconst defaultBG = 49;\nconst colors: Record<string, [number, number]> = {\n  // @ts-ignore\n  __proto__: null,\n  reset: [0, 0],\n  bold: [1, 22],\n  dim: [2, 22], // Alias: faint\n  italic: [3, 23],\n  underline: [4, 24],\n  blink: [5, 25],\n  // Swap foreground and background colors\n  inverse: [7, 27], // Alias: swapcolors, swapColors\n  hidden: [8, 28], // Alias: conceal\n  strikethrough: [9, 29], // Alias: strikeThrough, crossedout, crossedOut\n  doubleunderline: [21, 24], // Alias: doubleUnderline\n  black: [30, defaultFG],\n  red: [31, defaultFG],\n  green: [32, defaultFG],\n  yellow: [33, defaultFG],\n  blue: [34, defaultFG],\n  magenta: [35, defaultFG],\n  cyan: [36, defaultFG],\n  white: [37, defaultFG],\n  bgBlack: [40, defaultBG],\n  bgRed: [41, defaultBG],\n  bgGreen: [42, defaultBG],\n  bgYellow: [43, defaultBG],\n  bgBlue: [44, defaultBG],\n  bgMagenta: [45, defaultBG],\n  bgCyan: [46, defaultBG],\n  bgWhite: [47, defaultBG],\n  framed: [51, 54],\n  overlined: [53, 55],\n  gray: [90, defaultFG], // Alias: grey, blackBright\n  redBright: [91, defaultFG],\n  greenBright: [92, defaultFG],\n  yellowBright: [93, defaultFG],\n  blueBright: [94, defaultFG],\n  magentaBright: [95, defaultFG],\n  cyanBright: [96, defaultFG],\n  whiteBright: [97, defaultFG],\n  bgGray: [100, defaultBG], // Alias: bgGrey, bgBlackBright\n  bgRedBright: [101, defaultBG],\n  bgGreenBright: [102, defaultBG],\n  bgYellowBright: [103, defaultBG],\n  bgBlueBright: [104, defaultBG],\n  bgMagentaBright: [105, defaultBG],\n  bgCyanBright: [106, defaultBG],\n  bgWhiteBright: [107, defaultBG],\n};\ninspect.colors = colors;\n\nfunction defineColorAlias(target: string, alias: string) {\n  Object.defineProperty(inspect.colors, alias, {\n    get() {\n      return this[target];\n    },\n    set(value) {\n      this[target] = value;\n    },\n    configurable: true,\n    enumerable: false,\n  });\n}\n\ndefineColorAlias('gray', 'grey');\ndefineColorAlias('gray', 'blackBright');\ndefineColorAlias('bgGray', 'bgGrey');\ndefineColorAlias('bgGray', 'bgBlackBright');\ndefineColorAlias('dim', 'faint');\ndefineColorAlias('strikethrough', 'crossedout');\ndefineColorAlias('strikethrough', 'strikeThrough');\ndefineColorAlias('strikethrough', 'crossedOut');\ndefineColorAlias('hidden', 'conceal');\ndefineColorAlias('inverse', 'swapColors');\ndefineColorAlias('inverse', 'swapcolors');\ndefineColorAlias('doubleunderline', 'doubleUnderline');\n\n// TODO(BridgeAR): Add function style support for more complex styles.\n// Don't use 'blue' not visible on cmd.exe\ninspect.styles = {\n  __proto__: null,\n  special: 'cyan',\n  number: 'yellow',\n  bigint: 'yellow',\n  boolean: 'yellow',\n  undefined: 'grey',\n  null: 'bold',\n  string: 'green',\n  symbol: 'green',\n  date: 'magenta',\n  name: undefined,\n  // TODO(BridgeAR): Highlight regular expressions properly.\n  regexp: 'red',\n  module: 'underline',\n};\n\nfunction addQuotes(str: string, quotes: number): string {\n  if (quotes === -1) {\n    return `\"${str}\"`;\n  }\n  if (quotes === -2) {\n    return `\\`${str}\\``;\n  }\n  return `'${str}'`;\n}\n\nfunction escapeFn(str: string): string {\n  const charCode = str.charCodeAt(0);\n  return meta.length > charCode\n    ? meta[charCode]!\n    : `\\\\u${charCode.toString(16)}`;\n}\n\n// Escape control characters, single quotes and the backslash.\n// This is similar to JSON stringify escaping.\nfunction strEscape(str: string): string {\n  let escapeTest = strEscapeSequencesRegExp;\n  let escapeReplace = strEscapeSequencesReplacer;\n  let singleQuote = 39;\n\n  // Check for double quotes. If not present, do not escape single quotes and\n  // instead wrap the text in double quotes. If double quotes exist, check for\n  // backticks. If they do not exist, use those as fallback instead of the\n  // double quotes.\n  if (str.includes(\"'\")) {\n    // This invalidates the charCode and therefore can not be matched for\n    // anymore.\n    if (!str.includes('\"')) {\n      singleQuote = -1;\n    } else if (!str.includes('`') && !str.includes('${')) {\n      singleQuote = -2;\n    }\n    if (singleQuote !== 39) {\n      escapeTest = strEscapeSequencesRegExpSingle;\n      escapeReplace = strEscapeSequencesReplacerSingle;\n    }\n  }\n\n  // Some magic numbers that worked out fine while benchmarking with v8 6.0\n  if (str.length < 5000 && escapeTest.exec(str) === null)\n    return addQuotes(str, singleQuote);\n  if (str.length > 100) {\n    str = str.replace(escapeReplace, escapeFn);\n    return addQuotes(str, singleQuote);\n  }\n\n  let result = '';\n  let last = 0;\n  for (let i = 0; i < str.length; i++) {\n    const point = str.charCodeAt(i);\n    if (\n      point === singleQuote ||\n      point === 92 ||\n      point < 32 ||\n      (point > 126 && point < 160)\n    ) {\n      if (last === i) {\n        result += meta[point];\n      } else {\n        result += `${str.slice(last, i)}${meta[point]}`;\n      }\n      last = i + 1;\n    } else if (point >= 0xd800 && point <= 0xdfff) {\n      if (point <= 0xdbff && i + 1 < str.length) {\n        const point = str.charCodeAt(i + 1);\n        if (point >= 0xdc00 && point <= 0xdfff) {\n          i++;\n          continue;\n        }\n      }\n      result += `${str.slice(last, i)}\\\\u${point.toString(16)}`;\n      last = i + 1;\n    }\n  }\n\n  if (last !== str.length) {\n    result += str.slice(last);\n  }\n  return addQuotes(result, singleQuote);\n}\n\nfunction stylizeWithColor(str: string, styleType: Style): string {\n  const style = inspect.styles[styleType];\n  if (style !== undefined) {\n    const color = (\n      inspect.colors as unknown as Record<string, number[] | undefined>\n    )[style];\n    if (color !== undefined)\n      return `\\u001b[${color[0]}m${str}\\u001b[${color[1]}m`;\n  }\n  return str;\n}\n\nfunction stylizeNoColor(str: string): string {\n  return str;\n}\n\n// Return a new empty array to push in the results of the default formatter.\nfunction getEmptyFormatArray(): string[] {\n  return [];\n}\n\nfunction isInstanceof(object: unknown, proto: Function): boolean {\n  try {\n    return object instanceof proto;\n  } catch {\n    return false;\n  }\n}\n\n// Special-case for some builtin prototypes in case their `constructor` property has been tampered.\nconst wellKnownPrototypes = new Map()\n  .set(Array.prototype, { name: 'Array', constructor: Array })\n  .set(ArrayBuffer.prototype, { name: 'ArrayBuffer', constructor: ArrayBuffer })\n  .set(Function.prototype, { name: 'Function', constructor: Function })\n  .set(Map.prototype, { name: 'Map', constructor: Map })\n  .set(Set.prototype, { name: 'Set', constructor: Set })\n  .set(Object.prototype, { name: 'Object', constructor: Object })\n  .set(Object.getPrototypeOf(Uint8Array).prototype, {\n    name: 'TypedArray',\n    constructor: Object.getPrototypeOf(Uint8Array),\n  })\n  .set(RegExp.prototype, { name: 'RegExp', constructor: RegExp })\n  .set(Date.prototype, { name: 'Date', constructor: Date })\n  .set(DataView.prototype, { name: 'DataView', constructor: DataView })\n  .set(Error.prototype, { name: 'Error', constructor: Error })\n  .set(Boolean.prototype, { name: 'Boolean', constructor: Boolean })\n  .set(Number.prototype, { name: 'Number', constructor: Number })\n  .set(String.prototype, { name: 'String', constructor: String })\n  .set(Promise.prototype, { name: 'Promise', constructor: Promise })\n  .set(WeakMap.prototype, { name: 'WeakMap', constructor: WeakMap })\n  .set(WeakSet.prototype, { name: 'WeakSet', constructor: WeakSet });\n\nfunction getConstructorName(\n  obj: object,\n  ctx: Context,\n  recurseTimes: number,\n  protoProps?: string[]\n): string | null {\n  let firstProto: unknown;\n  const tmp = obj;\n  while (obj || isUndetectableObject(obj)) {\n    const wellKnownPrototypeNameAndConstructor = wellKnownPrototypes.get(obj);\n    if (wellKnownPrototypeNameAndConstructor !== undefined) {\n      const { name, constructor } = wellKnownPrototypeNameAndConstructor;\n      if (Function.prototype[Symbol.hasInstance].call(constructor, tmp)) {\n        if (protoProps !== undefined && firstProto !== obj) {\n          addPrototypeProperties(\n            ctx,\n            tmp,\n            firstProto || tmp,\n            recurseTimes,\n            protoProps\n          );\n        }\n        return name;\n      }\n    }\n    const descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor');\n    if (\n      descriptor !== undefined &&\n      typeof descriptor.value === 'function' &&\n      descriptor.value.name !== '' &&\n      isInstanceof(tmp, descriptor.value)\n    ) {\n      if (\n        protoProps !== undefined &&\n        (firstProto !== obj || !builtInObjects.has(descriptor.value.name))\n      ) {\n        addPrototypeProperties(\n          ctx,\n          tmp,\n          firstProto || tmp,\n          recurseTimes,\n          protoProps\n        );\n      }\n      return String(descriptor.value.name);\n    }\n\n    obj = Object.getPrototypeOf(obj);\n    if (firstProto === undefined) {\n      firstProto = obj;\n    }\n  }\n\n  if (firstProto === null) {\n    return null;\n  }\n\n  const res = internal.getConstructorName(tmp);\n\n  if (ctx.depth !== null && recurseTimes > ctx.depth) {\n    return `${res} <Complex prototype>`;\n  }\n\n  const protoConstr = getConstructorName(\n    firstProto!,\n    ctx,\n    recurseTimes + 1,\n    protoProps\n  );\n\n  if (protoConstr === null) {\n    return `${res} <${inspect(firstProto, {\n      ...ctx,\n      customInspect: false,\n      depth: -1,\n    })}>`;\n  }\n\n  return `${res} <${protoConstr}>`;\n}\n\n// This function has the side effect of adding prototype properties to the\n// `output` argument (which is an array). This is intended to highlight user\n// defined prototype properties.\nfunction addPrototypeProperties(\n  ctx: Context,\n  main: object,\n  obj: Object,\n  recurseTimes: number,\n  output: string[]\n): void {\n  let depth = 0;\n  let keys: PropertyKey[] | undefined;\n  let keySet: Set<PropertyKey> | undefined;\n  do {\n    if (depth !== 0 || main === obj) {\n      obj = Object.getPrototypeOf(obj);\n      // Stop as soon as a null prototype is encountered.\n      if (obj === null) {\n        return;\n      }\n      // Stop as soon as a built-in object type is detected.\n      const descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor');\n      if (\n        descriptor !== undefined &&\n        typeof descriptor.value === 'function' &&\n        builtInObjects.has(descriptor.value.name)\n      ) {\n        return;\n      }\n    }\n\n    if (depth === 0) {\n      keySet = new Set();\n    } else {\n      keys!.forEach((key) => keySet!.add(key));\n    }\n    // Get all own property names and symbols.\n    keys = Reflect.ownKeys(obj);\n    ctx.seen.push(main);\n    for (const key of keys) {\n      // Ignore the `constructor` property and keys that exist on layers above.\n      if (\n        key === 'constructor' ||\n        Object.prototype.hasOwnProperty.call(main, key) ||\n        (depth !== 0 && keySet!.has(key))\n      ) {\n        continue;\n      }\n      const desc = Object.getOwnPropertyDescriptor(obj, key);\n      if (typeof desc?.value === 'function') {\n        continue;\n      }\n      const value = formatProperty(\n        ctx,\n        obj,\n        recurseTimes,\n        key,\n        kObjectType,\n        desc,\n        main\n      );\n      if (ctx.colors) {\n        // Faint!\n        output.push(`\\u001b[2m${value}\\u001b[22m`);\n      } else {\n        output.push(value);\n      }\n    }\n    ctx.seen.pop();\n    // Limit the inspection to up to three prototype layers. Using `recurseTimes`\n    // is not a good choice here, because it's as if the properties are declared\n    // on the current object from the users perspective.\n  } while (++depth !== 3);\n}\n\nfunction getPrefix(\n  constructor: string | null,\n  tag: string,\n  fallback: string,\n  size = ''\n): string {\n  if (constructor === null) {\n    if (tag !== '' && fallback !== tag) {\n      return `[${fallback}${size}: null prototype] [${tag}] `;\n    }\n    return `[${fallback}${size}: null prototype] `;\n  }\n\n  if (tag !== '' && constructor !== tag) {\n    return `${constructor}${size} [${tag}] `;\n  }\n  return `${constructor}${size} `;\n}\n\n// Look up the keys of the object.\nfunction getKeys(value: object, showHidden: boolean): PropertyKey[] {\n  let keys: PropertyKey[];\n  const symbols = Object.getOwnPropertySymbols(value);\n  if (showHidden) {\n    keys = Object.getOwnPropertyNames(value);\n    if (symbols.length !== 0) keys.push(...symbols);\n  } else {\n    // This might throw if `value` is a Module Namespace Object from an\n    // unevaluated module, but we don't want to perform the actual type\n    // check because it's expensive.\n    // TODO(devsnek): track https://github.com/tc39/ecma262/issues/1209\n    // and modify this logic as needed.\n    try {\n      keys = Object.keys(value);\n    } catch (err: any) {\n      assert(\n        isNativeError(err) &&\n          err.name === 'ReferenceError' &&\n          isModuleNamespaceObject(value)\n      );\n      keys = Object.getOwnPropertyNames(value);\n    }\n    if (symbols.length !== 0) {\n      const filter = (key: PropertyKey) =>\n        Object.prototype.propertyIsEnumerable.call(value, key);\n      keys.push(...symbols.filter(filter));\n    }\n  }\n  return keys;\n}\n\nfunction getCtxStyle(\n  value: unknown,\n  constructor: string | null,\n  tag: string\n): string {\n  let fallback = '';\n  if (constructor === null) {\n    fallback = internal.getConstructorName(value);\n    if (fallback === tag) {\n      fallback = 'Object';\n    }\n  }\n  return getPrefix(constructor, tag, fallback);\n}\n\nfunction formatProxy(\n  ctx: Context,\n  proxy: internal.ProxyDetails,\n  recurseTimes: number\n): string {\n  if (ctx.depth !== null && recurseTimes > ctx.depth) {\n    return ctx.stylize('Proxy [Array]', 'special');\n  }\n  recurseTimes += 1;\n  ctx.indentationLvl += 2;\n  const res: string[] = [\n    formatValue(ctx, proxy.target, recurseTimes),\n    formatValue(ctx, proxy.handler, recurseTimes),\n  ];\n  ctx.indentationLvl -= 2;\n  return reduceToSingleString(\n    ctx,\n    res,\n    '',\n    ['Proxy [', ']'],\n    kArrayExtrasType,\n    recurseTimes\n  );\n}\n\n// Note: using `formatValue` directly requires the indentation level to be\n// corrected by setting `ctx.indentationLvL += diff` and then to decrease the\n// value afterwards again.\nfunction formatValue(\n  ctx: Context,\n  value: unknown,\n  recurseTimes: number,\n  typedArray?: unknown\n): string {\n  // Primitive types cannot have properties.\n  if (\n    typeof value !== 'object' &&\n    typeof value !== 'function' &&\n    !isUndetectableObject(value)\n  ) {\n    return formatPrimitive(ctx.stylize, value as Primitive, ctx);\n  }\n  if (value === null) {\n    return ctx.stylize('null', 'null');\n  }\n\n  // Memorize the context for custom inspection on proxies.\n  const context = value;\n  let proxies = 0;\n  // Always check for proxies to prevent side effects and to prevent triggering\n  // any proxy handlers.\n  let proxy = internal.getProxyDetails(value);\n  if (proxy !== undefined) {\n    if (proxy === null || proxy.target === null) {\n      return ctx.stylize('<Revoked Proxy>', 'special');\n    }\n    if (ctx.showProxy) {\n      return formatProxy(ctx, proxy, recurseTimes);\n    }\n    do {\n      if (proxy === null || proxy.target === null) {\n        let formatted = ctx.stylize('<Revoked Proxy>', 'special');\n        for (let i = 0; i < proxies; i++) {\n          formatted = `${ctx.stylize('Proxy(', 'special')}${formatted}${ctx.stylize(')', 'special')}`;\n        }\n        return formatted;\n      }\n      value = proxy.target;\n      proxy = internal.getProxyDetails(value);\n      proxies += 1;\n    } while (proxy !== undefined);\n  }\n\n  // Provide a hook for user-specified inspect functions.\n  // Check that value is an object with an inspect function on it.\n  if (ctx.customInspect) {\n    let maybeCustom = (value as Record<PropertyKey, unknown>)[\n      customInspectSymbol\n    ];\n\n    // WORKERD SPECIFIC PATCH: if `value` is a JSG resource type, use a well-known custom inspect\n    const maybeResourceTypeInspect = (value as Record<PropertyKey, unknown>)[\n      internal.kResourceTypeInspect\n    ];\n    if (typeof maybeResourceTypeInspect === 'object') {\n      maybeCustom = formatJsgResourceType.bind(\n        context as Record<PropertyKey, unknown>,\n        maybeResourceTypeInspect as Record<string, symbol>\n      );\n    }\n\n    if (\n      typeof maybeCustom === 'function' &&\n      // Filter out the util module, its inspect function is special.\n      maybeCustom !== inspect &&\n      // Also filter out any prototype objects using the circular check.\n      !(\n        (value as object).constructor &&\n        (value as object).constructor.prototype === value\n      )\n    ) {\n      // This makes sure the recurseTimes are reported as before while using\n      // a counter internally.\n      const depth = ctx.depth === null ? null : ctx.depth - recurseTimes;\n      const isCrossContext = proxies !== 0 || !(context instanceof Object);\n      const ret = Function.prototype.call.call(\n        maybeCustom,\n        context,\n        depth,\n        getUserOptions(ctx, isCrossContext),\n        inspect\n      );\n      // If the custom inspection method returned `this`, don't go into\n      // infinite recursion.\n      if (ret !== context) {\n        if (typeof ret !== 'string') {\n          return formatValue(ctx, ret, recurseTimes);\n        }\n        return ret.replaceAll('\\n', `\\n${' '.repeat(ctx.indentationLvl)}`);\n      }\n    }\n  }\n\n  // Using an array here is actually better for the average case than using\n  // a Set. `seen` will only check for the depth and will never grow too large.\n  if (ctx.seen.includes(value)) {\n    let index: number | undefined = 1;\n    if (ctx.circular === undefined) {\n      ctx.circular = new Map();\n      ctx.circular.set(value, index);\n    } else {\n      index = ctx.circular.get(value);\n      if (index === undefined) {\n        index = ctx.circular.size + 1;\n        ctx.circular.set(value, index);\n      }\n    }\n    return ctx.stylize(`[Circular *${index}]`, 'special');\n  }\n\n  let formatted = formatRaw(ctx, value, recurseTimes, typedArray);\n\n  if (proxies !== 0) {\n    for (let i = 0; i < proxies; i++) {\n      formatted = `${ctx.stylize('Proxy(', 'special')}${formatted}${ctx.stylize(')', 'special')}`;\n    }\n  }\n\n  return formatted;\n}\n\nfunction formatRaw(\n  ctx: Context,\n  value: unknown,\n  recurseTimes: number,\n  typedArray: unknown\n): string {\n  let keys: PropertyKey[] | undefined;\n  let protoProps: string[] | undefined;\n  if (ctx.showHidden && (ctx.depth === null || recurseTimes <= ctx.depth)) {\n    protoProps = [];\n  }\n\n  const constructor = getConstructorName(\n    value as object,\n    ctx,\n    recurseTimes,\n    protoProps\n  );\n  // Reset the variable to check for this later on.\n  if (protoProps !== undefined && protoProps.length === 0) {\n    protoProps = undefined;\n  }\n\n  let tag = (value as { [Symbol.toStringTag]?: string })[Symbol.toStringTag];\n  // Only list the tag in case it's non-enumerable / not an own property.\n  // Otherwise we'd print this twice.\n  if (\n    typeof tag !== 'string' ||\n    (tag !== '' &&\n      (ctx.showHidden\n        ? Object.prototype.hasOwnProperty\n        : Object.prototype.propertyIsEnumerable\n      ).call(value, Symbol.toStringTag))\n  ) {\n    tag = '';\n  }\n  let base = '';\n  let formatter: (ctx: Context, value: any, recurseTimes: number) => string[] =\n    getEmptyFormatArray;\n  let braces: [string, string] | undefined;\n  let noIterator = true;\n  let i = 0;\n  const filter = ctx.showHidden\n    ? internal.ALL_PROPERTIES\n    : internal.ONLY_ENUMERABLE;\n\n  let extrasType = kObjectType;\n\n  // Iterators and the rest are split to reduce checks.\n  // We have to check all values in case the constructor is set to null.\n  // Otherwise it would not possible to identify all types properly.\n\n  const isEntriesObject = hasEntries(value);\n  if (\n    Symbol.iterator in (value as object) ||\n    constructor === null ||\n    isEntriesObject\n  ) {\n    noIterator = false;\n    if (isEntriesObject) {\n      // WORKERD SPECIFIC PATCH: if `value` is an object with entries, format them like a map\n      const size = value[kEntries].length;\n      const prefix = getPrefix(constructor, tag, 'Object', `(${size})`);\n      keys = getKeys(value, ctx.showHidden);\n\n      // Remove `kEntries` and `size` from keys\n      keys.splice(keys.indexOf(kEntries), 1);\n      const sizeIndex = keys.indexOf('size');\n      if (sizeIndex !== -1) keys.splice(sizeIndex, 1);\n\n      formatter = formatMap.bind(null, value[kEntries][Symbol.iterator]());\n      if (size === 0 && keys.length === 0 && protoProps === undefined)\n        return `${prefix}{}`;\n      braces = [`${prefix}{`, '}'];\n    } else if (Array.isArray(value)) {\n      // Only set the constructor for non ordinary (\"Array [...]\") arrays.\n      const prefix =\n        constructor !== 'Array' || tag !== ''\n          ? getPrefix(constructor, tag, 'Array', `(${value.length})`)\n          : '';\n      keys = internal.getOwnNonIndexProperties(value, filter);\n      braces = [`${prefix}[`, ']'];\n      if (value.length === 0 && keys.length === 0 && protoProps === undefined)\n        return `${braces[0]}]`;\n      extrasType = kArrayExtrasType;\n      formatter = formatArray;\n    } else if (isSet(value)) {\n      const size = setPrototypeSize.call(value);\n      const prefix = getPrefix(constructor, tag, 'Set', `(${size})`);\n      keys = getKeys(value, ctx.showHidden);\n      formatter =\n        constructor !== null\n          ? formatSet.bind(null, value)\n          : formatSet.bind(null, Set.prototype.values.call(value));\n      if (size === 0 && keys.length === 0 && protoProps === undefined)\n        return `${prefix}{}`;\n      braces = [`${prefix}{`, '}'];\n    } else if (isMap(value)) {\n      const size = mapPrototypeSize.call(value);\n      const prefix = getPrefix(constructor, tag, 'Map', `(${size})`);\n      keys = getKeys(value, ctx.showHidden);\n      formatter =\n        constructor !== null\n          ? formatMap.bind(null, value)\n          : formatMap.bind(null, Map.prototype.entries.call(value));\n      if (size === 0 && keys.length === 0 && protoProps === undefined)\n        return `${prefix}{}`;\n      braces = [`${prefix}{`, '}'];\n    } else if (isTypedArray(value)) {\n      keys = internal.getOwnNonIndexProperties(value, filter);\n      let bound = value;\n      let fallback = '';\n      if (constructor === null) {\n        fallback = typedArrayPrototypeToStringTag.call(value);\n        // Reconstruct the array information.\n        bound = new (\n          globalThis as unknown as Record<\n            string,\n            { new (value: NodeJS.TypedArray): NodeJS.TypedArray }\n          >\n        )[fallback]!(value);\n      }\n      const size = typedArrayPrototypeLength.call(value);\n      const prefix = getPrefix(constructor, tag, fallback, `(${size})`);\n      braces = [`${prefix}[`, ']'];\n      if (value.length === 0 && keys.length === 0 && !ctx.showHidden)\n        return `${braces[0]}]`;\n      // Special handle the value. The original value is required below. The\n      // bound function is required to reconstruct missing information.\n      formatter = formatTypedArray.bind(null, bound, size);\n      extrasType = kArrayExtrasType;\n    } else if (isMapIterator(value)) {\n      keys = getKeys(value, ctx.showHidden);\n      braces = getIteratorBraces('Map', tag);\n      // Add braces to the formatter parameters.\n      formatter = formatIterator.bind(null, braces);\n    } else if (isSetIterator(value)) {\n      keys = getKeys(value, ctx.showHidden);\n      braces = getIteratorBraces('Set', tag);\n      // Add braces to the formatter parameters.\n      formatter = formatIterator.bind(null, braces);\n    } else {\n      noIterator = true;\n    }\n  }\n  if (noIterator) {\n    keys = getKeys(value as object, ctx.showHidden);\n    braces = ['{', '}'];\n    if (constructor === 'Object') {\n      if (isArgumentsObject(value)) {\n        braces[0] = '[Arguments] {';\n      } else if (tag !== '') {\n        braces[0] = `${getPrefix(constructor, tag, 'Object')}{`;\n      }\n      if (keys.length === 0 && protoProps === undefined) {\n        return `${braces[0]}}`;\n      }\n    } else if (typeof value === 'function') {\n      base = getFunctionBase(ctx, value, constructor, tag);\n      if (keys.length === 0 && protoProps === undefined)\n        return ctx.stylize(base, 'special');\n    } else if (isRegExp(value)) {\n      // Make RegExps say that they are RegExps\n      base = RegExp.prototype.toString.call(\n        constructor !== null ? value : new RegExp(value)\n      );\n      const prefix = getPrefix(constructor, tag, 'RegExp');\n      if (prefix !== 'RegExp ') base = `${prefix}${base}`;\n      if (\n        (keys.length === 0 && protoProps === undefined) ||\n        (ctx.depth !== null && recurseTimes > ctx.depth)\n      ) {\n        return ctx.stylize(base, 'regexp');\n      }\n    } else if (isDate(value)) {\n      // Make dates with properties first say the date\n      base = Number.isNaN(Date.prototype.getTime.call(value))\n        ? Date.prototype.toString.call(value)\n        : Date.prototype.toISOString.call(value);\n      const prefix = getPrefix(constructor, tag, 'Date');\n      if (prefix !== 'Date ') base = `${prefix}${base}`;\n      if (keys.length === 0 && protoProps === undefined) {\n        return ctx.stylize(base, 'date');\n      }\n    } else if (isError(value)) {\n      base = formatError(value, constructor, tag, ctx, keys);\n      if (keys.length === 0 && protoProps === undefined) return base;\n    } else if (isAnyArrayBuffer(value)) {\n      // Fast path for ArrayBuffer and SharedArrayBuffer.\n      // Can't do the same for DataView because it has a non-primitive\n      // .buffer property that we need to recurse for.\n      const arrayType = isArrayBuffer(value)\n        ? 'ArrayBuffer'\n        : 'SharedArrayBuffer';\n      const prefix = getPrefix(constructor, tag, arrayType);\n      if (typedArray === undefined) {\n        formatter = formatArrayBuffer;\n      } else if (keys.length === 0 && protoProps === undefined) {\n        return (\n          prefix +\n          `{ byteLength: ${formatNumber(ctx.stylize, value.byteLength, false)} }`\n        );\n      }\n      braces[0] = `${prefix}{`;\n      keys.unshift('byteLength');\n    } else if (isDataView(value)) {\n      braces[0] = `${getPrefix(constructor, tag, 'DataView')}{`;\n      // .buffer goes last, it's not a primitive like the others.\n      keys.unshift('byteLength', 'byteOffset', 'buffer');\n    } else if (isPromise(value)) {\n      braces[0] = `${getPrefix(constructor, tag, 'Promise')}{`;\n      formatter = formatPromise;\n    } else if (isWeakSet(value)) {\n      braces[0] = `${getPrefix(constructor, tag, 'WeakSet')}{`;\n      formatter = ctx.showHidden ? formatWeakSet : formatWeakCollection;\n    } else if (isWeakMap(value)) {\n      braces[0] = `${getPrefix(constructor, tag, 'WeakMap')}{`;\n      formatter = ctx.showHidden ? formatWeakMap : formatWeakCollection;\n    } else if (isModuleNamespaceObject(value)) {\n      braces[0] = `${getPrefix(constructor, tag, 'Module')}{`;\n      // Special handle keys for namespace objects.\n      formatter = formatNamespaceObject.bind(null, keys);\n    } else if (isBoxedPrimitive(value)) {\n      base = getBoxedBase(value, ctx, keys, constructor, tag);\n      if (keys.length === 0 && protoProps === undefined) {\n        return base;\n      }\n    } else {\n      if (keys.length === 0 && protoProps === undefined) {\n        return `${getCtxStyle(value, constructor, tag)}{}`;\n      }\n      braces[0] = `${getCtxStyle(value, constructor, tag)}{`;\n    }\n  }\n\n  if (ctx.depth !== null && recurseTimes > ctx.depth) {\n    let constructorName = getCtxStyle(value, constructor, tag).slice(0, -1);\n    if (constructor !== null) constructorName = `[${constructorName}]`;\n    return ctx.stylize(constructorName, 'special');\n  }\n  recurseTimes += 1;\n\n  ctx.seen.push(value);\n  ctx.currentDepth = recurseTimes;\n  let output;\n  const indentationLvl = ctx.indentationLvl;\n  try {\n    output = formatter(ctx, value, recurseTimes);\n    for (i = 0; i < keys!.length; i++) {\n      output.push(\n        formatProperty(\n          ctx,\n          value as object,\n          recurseTimes,\n          keys![i]!,\n          extrasType\n        )\n      );\n    }\n    if (protoProps !== undefined) {\n      output.push(...protoProps);\n    }\n  } catch (err) {\n    const constructorName = getCtxStyle(value, constructor, tag).slice(0, -1);\n    return handleMaxCallStackSize(\n      ctx,\n      err as Error,\n      constructorName,\n      indentationLvl\n    );\n  }\n  if (ctx.circular !== undefined) {\n    const index = ctx.circular.get(value);\n    if (index !== undefined) {\n      const reference = ctx.stylize(`<ref *${index}>`, 'special');\n      // Add reference always to the very beginning of the output.\n      if (ctx.compact !== true) {\n        base = base === '' ? reference : `${reference} ${base}`;\n      } else {\n        braces![0] = `${reference} ${braces![0]}`;\n      }\n    }\n  }\n  ctx.seen.pop();\n\n  if (ctx.sorted) {\n    const comparator = ctx.sorted === true ? undefined : ctx.sorted;\n    if (extrasType === kObjectType) {\n      output.sort(comparator);\n    } else if (keys!.length > 1) {\n      const sorted = output\n        .slice(output.length - keys!.length)\n        .sort(comparator);\n      output.splice(output.length - keys!.length, keys!.length, ...sorted);\n    }\n  }\n\n  const res = reduceToSingleString(\n    ctx,\n    output,\n    base,\n    braces!,\n    extrasType,\n    recurseTimes,\n    value\n  );\n  const budget = ctx.budget[ctx.indentationLvl] || 0;\n  const newLength = budget + res.length;\n  ctx.budget[ctx.indentationLvl] = newLength;\n  // If any indentationLvl exceeds this limit, limit further inspecting to the\n  // minimum. Otherwise the recursive algorithm might continue inspecting the\n  // object even though the maximum string size (~2 ** 28 on 32 bit systems and\n  // ~2 ** 30 on 64 bit systems) exceeded. The actual output is not limited at\n  // exactly 2 ** 27 but a bit higher. This depends on the object shape.\n  // This limit also makes sure that huge objects don't block the event loop\n  // significantly.\n  if (newLength > 2 ** 27) {\n    ctx.depth = -1;\n  }\n  return res;\n}\n\nfunction getIteratorBraces(type: string, tag: string): [string, string] {\n  if (tag !== `${type} Iterator`) {\n    if (tag !== '') tag += '] [';\n    tag += `${type} Iterator`;\n  }\n  return [`[${tag}] {`, '}'];\n}\n\nfunction getBoxedBase(\n  value: unknown,\n  ctx: Context,\n  keys: PropertyKey[],\n  constructor: string | null,\n  tag: string\n): string {\n  let fn: (this: unknown) => Primitive;\n  let type: Capitalize<Exclude<Style, 'bigint'>> | 'BigInt';\n  if (isNumberObject(value)) {\n    fn = Number.prototype.valueOf;\n    type = 'Number';\n  } else if (isStringObject(value)) {\n    fn = String.prototype.valueOf;\n    type = 'String';\n    // For boxed Strings, we have to remove the 0-n indexed entries,\n    // since they just noisy up the output and are redundant\n    // Make boxed primitive Strings look like such\n    keys.splice(0, value.length);\n  } else if (isBooleanObject(value)) {\n    fn = Boolean.prototype.valueOf;\n    type = 'Boolean';\n  } else if (isBigIntObject(value)) {\n    fn = BigInt.prototype.valueOf;\n    type = 'BigInt';\n  } else {\n    fn = Symbol.prototype.valueOf;\n    type = 'Symbol';\n  }\n  let base = `[${type}`;\n  if (type !== constructor) {\n    if (constructor === null) {\n      base += ' (null prototype)';\n    } else {\n      base += ` (${constructor})`;\n    }\n  }\n  base += `: ${formatPrimitive(stylizeNoColor, fn.call(value), ctx)}]`;\n  if (tag !== '' && tag !== constructor) {\n    base += ` [${tag}]`;\n  }\n  if (keys.length !== 0 || ctx.stylize === stylizeNoColor) return base;\n  return ctx.stylize(base, type.toLowerCase() as Style);\n}\n\nfunction getClassBase(\n  value: any,\n  constructor: string | null,\n  tag: string\n): string {\n  const hasName = Object.prototype.hasOwnProperty.call(value, 'name');\n  const name = (hasName && value.name) || '(anonymous)';\n  let base = `class ${name}`;\n  if (constructor !== 'Function' && constructor !== null) {\n    base += ` [${constructor}]`;\n  }\n  if (tag !== '' && constructor !== tag) {\n    base += ` [${tag}]`;\n  }\n  if (constructor !== null) {\n    const superName = Object.getPrototypeOf(value).name;\n    if (superName) {\n      base += ` extends ${superName}`;\n    }\n  } else {\n    base += ' extends [null prototype]';\n  }\n  return `[${base}]`;\n}\n\nfunction getFunctionBase(\n  ctx: Context,\n  value: Function,\n  constructor: string | null,\n  tag: string\n): string {\n  const stringified = Function.prototype.toString.call(value);\n  if (stringified.startsWith('class') && stringified.endsWith('}')) {\n    const slice = stringified.slice(5, -1);\n    const bracketIndex = slice.indexOf('{');\n    if (\n      bracketIndex !== -1 &&\n      (!slice.slice(0, bracketIndex).includes('(') ||\n        // Slow path to guarantee that it's indeed a class.\n        classRegExp.exec(slice.replace(stripCommentsRegExp, '')) !== null)\n    ) {\n      return getClassBase(value, constructor, tag);\n    }\n  }\n  let type = 'Function';\n  if (isGeneratorFunction(value)) {\n    type = `Generator${type}`;\n  }\n  if (isAsyncFunction(value)) {\n    type = `Async${type}`;\n  }\n  let base = `[${type}`;\n  if (constructor === null) {\n    base += ' (null prototype)';\n  }\n  if (value.name === '') {\n    base += ' (anonymous)';\n  } else {\n    base += `: ${typeof value.name === 'string' ? value.name : formatValue(ctx, value.name, NaN)}`;\n  }\n  base += ']';\n  if (constructor !== type && constructor !== null) {\n    base += ` ${constructor}`;\n  }\n  if (tag !== '' && constructor !== tag) {\n    base += ` [${tag}]`;\n  }\n  return base;\n}\n\nexport function identicalSequenceRange(\n  a: unknown[],\n  b: unknown[]\n): { len: number; offset: number } {\n  for (let i = 0; i < a.length - 3; i++) {\n    // Find the first entry of b that matches the current entry of a.\n    const pos = b.indexOf(a[i]);\n    if (pos !== -1) {\n      const rest = b.length - pos;\n      if (rest > 3) {\n        let len = 1;\n        const maxLen = Math.min(a.length - i, rest);\n        // Count the number of consecutive entries.\n        while (maxLen > len && a[i + len] === b[pos + len]) {\n          len++;\n        }\n        if (len > 3) {\n          return { len, offset: i };\n        }\n      }\n    }\n  }\n\n  return { len: 0, offset: 0 };\n}\n\nfunction getStackString(ctx: Context, error: Error): string {\n  if (error.stack) {\n    if (typeof error.stack === 'string') {\n      return error.stack;\n    }\n    // This 'NaN' is a very strange Nodeism, but is necessary for correct behaviour!\n    return formatValue(ctx, error.stack, NaN);\n  }\n  return Error.prototype.toString.call(error);\n}\n\nfunction getStackFrames(ctx: Context, err: Error, stack: string): string[] {\n  const frames = stack.split('\\n');\n\n  let cause;\n  try {\n    ({ cause } = err);\n  } catch {\n    // If 'cause' is a getter that throws, ignore it.\n  }\n\n  // Remove stack frames identical to frames in cause.\n  if (cause != null && isError(cause)) {\n    const causeStack = getStackString(ctx, cause);\n    const causeStackStart = causeStack.indexOf('\\n    at');\n    if (causeStackStart !== -1) {\n      const causeFrames = causeStack.slice(causeStackStart + 1).split('\\n');\n      const { len, offset } = identicalSequenceRange(frames, causeFrames);\n      if (len > 0) {\n        const skipped = len - 2;\n        const msg = `    ... ${skipped} lines matching cause stack trace ...`;\n        frames.splice(offset + 1, skipped, ctx.stylize(msg, 'undefined'));\n      }\n    }\n  }\n  return frames;\n}\n\nfunction improveStack(\n  stack: string,\n  constructor: string | null,\n  name: string | object,\n  tag: string\n): string {\n  if (typeof name !== 'string') {\n    stack = stack.replace(\n      `${name}`,\n      `${name} [${getPrefix(constructor, tag, 'Error').slice(0, -1)}]`\n    );\n  }\n\n  // A stack trace may contain arbitrary data. Only manipulate the output\n  // for \"regular errors\" (errors that \"look normal\") for now.\n  let len = typeof name === 'string' ? name.length : undefined;\n\n  if (\n    constructor === null ||\n    (typeof name === 'string' &&\n      name.endsWith('Error') &&\n      stack.startsWith(name) &&\n      (stack.length === len ||\n        stack[len as number] === ':' ||\n        stack[len as number] === '\\n'))\n  ) {\n    let fallback = 'Error';\n    if (constructor === null) {\n      const start =\n        /^([A-Z][a-z_ A-Z0-9[\\]()-]+)(?::|\\n {4}at)/.exec(stack) ||\n        /^([a-z_A-Z0-9-]*Error)$/.exec(stack);\n      fallback = (start && start[1]) || '';\n      len = fallback.length;\n      fallback = fallback || 'Error';\n    }\n    const prefix = getPrefix(constructor, tag, fallback).slice(0, -1);\n    if (name !== prefix) {\n      if (typeof name === 'string' && prefix.includes(name)) {\n        if (len === 0) {\n          stack = `${prefix}: ${stack}`;\n        } else {\n          stack = `${prefix}${stack.slice(len)}`;\n        }\n      } else {\n        stack = `${prefix} [${name}]${stack.slice(len)}`;\n      }\n    }\n  }\n  return stack;\n}\n\nfunction removeDuplicateErrorKeys(\n  ctx: Context,\n  keys: PropertyKey[],\n  err: Error,\n  stack: string\n): void {\n  if (!ctx.showHidden && keys.length !== 0) {\n    for (const name of ['name', 'message', 'stack'] as const) {\n      const index = keys.indexOf(name);\n      // Only hide the property in case it's part of the original stack\n      if (\n        index !== -1 &&\n        (typeof err[name] !== 'string' || stack.includes(err[name]!))\n      ) {\n        keys.splice(index, 1);\n      }\n    }\n  }\n}\n\nfunction markNodeModules(ctx: Context, line: string): string {\n  let tempLine = '';\n  let nodeModule;\n  let pos = 0;\n  while ((nodeModule = nodeModulesRegExp.exec(line)) !== null) {\n    // '/node_modules/'.length === 14\n    tempLine += line.slice(pos, nodeModule.index + 14);\n    tempLine += ctx.stylize(nodeModule[1]!, 'module');\n    pos = nodeModule.index + nodeModule[0].length;\n  }\n  if (pos !== 0) {\n    line = tempLine + line.slice(pos);\n  }\n  return line;\n}\n\nfunction formatError(\n  err: Error,\n  constructor: string | null,\n  tag: string,\n  ctx: Context,\n  keys: PropertyKey[]\n): string {\n  const name = err.name != null ? (err.name as string | object) : 'Error';\n  let stack = getStackString(ctx, err);\n\n  removeDuplicateErrorKeys(ctx, keys, err, stack);\n\n  if ('cause' in err && (keys.length === 0 || !keys.includes('cause'))) {\n    keys.push('cause');\n  }\n\n  // Print errors aggregated into AggregateError\n  if (\n    Array.isArray((err as { errors?: unknown }).errors) &&\n    (keys.length === 0 || !keys.includes('errors'))\n  ) {\n    keys.push('errors');\n  }\n\n  stack = improveStack(stack, constructor, name, tag);\n\n  // Ignore the error message if it's contained in the stack.\n  let pos = (err.message && stack.indexOf(err.message)) || -1;\n  if (pos !== -1) pos += err.message.length;\n  // Wrap the error in brackets in case it has no stack trace.\n  const stackStart = stack.indexOf('\\n    at', pos);\n  if (stackStart === -1) {\n    stack = `[${stack}]`;\n  } else {\n    let newStack = stack.slice(0, stackStart);\n    const stackFramePart = stack.slice(stackStart + 1);\n    const lines = getStackFrames(ctx, err, stackFramePart);\n    if (ctx.colors) {\n      // Highlight userland code and node modules.\n      for (let line of lines) {\n        newStack += '\\n';\n\n        line = markNodeModules(ctx, line);\n\n        newStack += line;\n      }\n    } else {\n      newStack += `\\n${lines.join('\\n')}`;\n    }\n    stack = newStack;\n  }\n  // The message and the stack have to be indented as well!\n  if (ctx.indentationLvl !== 0) {\n    const indentation = ' '.repeat(ctx.indentationLvl);\n    stack = stack.replaceAll('\\n', `\\n${indentation}`);\n  }\n  return stack;\n}\n\nfunction groupArrayElements(\n  ctx: Context,\n  output: string[],\n  value: unknown[] | undefined\n): string[] {\n  let totalLength = 0;\n  let maxLength = 0;\n  let i = 0;\n  let outputLength = output.length;\n  if (ctx.maxArrayLength !== null && ctx.maxArrayLength < output.length) {\n    // This makes sure the \"... n more items\" part is not taken into account.\n    outputLength--;\n  }\n  const separatorSpace = 2; // Add 1 for the space and 1 for the separator.\n  const dataLen = Array.from<number>({ length: outputLength });\n  // Calculate the total length of all output entries and the individual max\n  // entries length of all output entries. We have to remove colors first,\n  // otherwise the length would not be calculated properly.\n  for (; i < outputLength; i++) {\n    const len = getStringWidth(output[i] as string, ctx.colors);\n    dataLen[i] = len;\n    totalLength += len + separatorSpace;\n    if (maxLength < len) maxLength = len;\n  }\n  // Add two to `maxLength` as we add a single whitespace character plus a comma\n  // in-between two entries.\n  const actualMax = maxLength + separatorSpace;\n  // Check if at least three entries fit next to each other and prevent grouping\n  // of arrays that contains entries of very different length (i.e., if a single\n  // entry is longer than 1/5 of all other entries combined). Otherwise the\n  // space in-between small entries would be enormous.\n  if (\n    actualMax * 3 + ctx.indentationLvl < ctx.breakLength &&\n    (totalLength / actualMax > 5 || maxLength <= 6)\n  ) {\n    const approxCharHeights = 2.5;\n    const averageBias = Math.sqrt(actualMax - totalLength / output.length);\n    const biasedMax = Math.max(actualMax - 3 - averageBias, 1);\n    // Dynamically check how many columns seem possible.\n    const columns = Math.min(\n      // Ideally a square should be drawn. We expect a character to be about 2.5\n      // times as high as wide. This is the area formula to calculate a square\n      // which contains n rectangles of size `actualMax * approxCharHeights`.\n      // Divide that by `actualMax` to receive the correct number of columns.\n      // The added bias increases the columns for short entries.\n      Math.round(\n        Math.sqrt(approxCharHeights * biasedMax * outputLength) / biasedMax\n      ),\n      // Do not exceed the breakLength.\n      Math.floor((ctx.breakLength - ctx.indentationLvl) / actualMax),\n      // Limit array grouping for small `compact` modes as the user requested\n      // minimal grouping.\n      (ctx.compact === false\n        ? 0\n        : ctx.compact === true\n          ? inspectDefaultOptions.compact\n          : ctx.compact) * 4,\n      // Limit the columns to a maximum of fifteen.\n      15\n    );\n    // Return with the original output if no grouping should happen.\n    if (columns <= 1) {\n      return output;\n    }\n    const tmp: string[] = [];\n    const maxLineLength: number[] = [];\n    for (let i = 0; i < columns; i++) {\n      let lineMaxLength = 0;\n      for (let j = i; j < output.length; j += columns) {\n        if ((dataLen[j] as number) > lineMaxLength) {\n          lineMaxLength = dataLen[j] as number;\n        }\n      }\n      lineMaxLength += separatorSpace;\n      maxLineLength[i] = lineMaxLength;\n    }\n    let order = String.prototype.padStart;\n    if (value !== undefined) {\n      for (let i = 0; i < output.length; i++) {\n        if (typeof value[i] !== 'number' && typeof value[i] !== 'bigint') {\n          order = String.prototype.padEnd;\n          break;\n        }\n      }\n    }\n    // Each iteration creates a single line of grouped entries.\n    for (let i = 0; i < outputLength; i += columns) {\n      // The last lines may contain less entries than columns.\n      const max = Math.min(i + columns, outputLength);\n      let str = '';\n      let j = i;\n      for (; j < max - 1; j++) {\n        // Calculate extra color padding in case it's active. This has to be\n        // done line by line as some lines might contain more colors than\n        // others.\n        const padding =\n          maxLineLength[j - i]! + output[j]!.length - (dataLen[j] as number);\n        str += order.call(`${output[j]}, `, padding, ' ');\n      }\n      if (order === String.prototype.padStart) {\n        const padding =\n          maxLineLength[j - i]! +\n          output[j]!.length -\n          (dataLen[j] as number) -\n          separatorSpace;\n        str += output[j]!.padStart(padding, ' ');\n      } else {\n        str += output[j];\n      }\n      tmp.push(str);\n    }\n    if (ctx.maxArrayLength !== null && ctx.maxArrayLength < output.length) {\n      tmp.push(output[outputLength]!);\n    }\n    output = tmp;\n  }\n  return output;\n}\n\nfunction handleMaxCallStackSize(\n  ctx: Context,\n  err: Error,\n  constructorName: string,\n  indentationLvl: number\n): string {\n  if (isStackOverflowError(err)) {\n    ctx.seen.pop();\n    ctx.indentationLvl = indentationLvl;\n    return ctx.stylize(\n      `[${constructorName}: Inspection interrupted ` +\n        'prematurely. Maximum call stack size exceeded.]',\n      'special'\n    );\n  }\n  /* c8 ignore next */\n  assert.fail(err.stack);\n}\n\nfunction addNumericSeparator(integerString: string): string {\n  let result = '';\n  let i = integerString.length;\n  const start = integerString.startsWith('-') ? 1 : 0;\n  for (; i >= start + 4; i -= 3) {\n    result = `_${integerString.slice(i - 3, i)}${result}`;\n  }\n  return i === integerString.length\n    ? integerString\n    : `${integerString.slice(0, i)}${result}`;\n}\n\nfunction addNumericSeparatorEnd(integerString: string): string {\n  let result = '';\n  let i = 0;\n  for (; i < integerString.length - 3; i += 3) {\n    result += `${integerString.slice(i, i + 3)}_`;\n  }\n  return i === 0 ? integerString : `${result}${integerString.slice(i)}`;\n}\n\nconst remainingText = (remaining: number) =>\n  `... ${remaining} more item${remaining > 1 ? 's' : ''}`;\n\nfunction formatNumber(\n  fn: InspectOptionsStylized['stylize'],\n  number: number,\n  numericSeparator?: boolean\n): string {\n  if (!numericSeparator) {\n    // Format -0 as '-0'. Checking `number === -0` won't distinguish 0 from -0.\n    if (Object.is(number, -0)) {\n      return fn('-0', 'number');\n    }\n    return fn(`${number}`, 'number');\n  }\n  const integer = Math.trunc(number);\n  const string = String(integer);\n  if (integer === number) {\n    if (!Number.isFinite(number) || string.includes('e')) {\n      return fn(string, 'number');\n    }\n    return fn(`${addNumericSeparator(string)}`, 'number');\n  }\n  if (Number.isNaN(number)) {\n    return fn(string, 'number');\n  }\n  return fn(\n    `${addNumericSeparator(string)}.${addNumericSeparatorEnd(\n      String(number).slice(string.length + 1)\n    )}`,\n    'number'\n  );\n}\n\nfunction formatBigInt(\n  fn: InspectOptionsStylized['stylize'],\n  bigint: bigint,\n  numericSeparator?: boolean\n): string {\n  const string = String(bigint);\n  if (!numericSeparator) {\n    return fn(`${string}n`, 'bigint');\n  }\n  return fn(`${addNumericSeparator(string)}n`, 'bigint');\n}\n\ntype Primitive = string | number | bigint | boolean | undefined | symbol;\nfunction formatPrimitive(\n  fn: InspectOptionsStylized['stylize'],\n  value: Primitive,\n  ctx: Context\n): string {\n  if (typeof value === 'string') {\n    let trailer = '';\n    if (ctx.maxStringLength !== null && value.length > ctx.maxStringLength) {\n      const remaining = value.length - ctx.maxStringLength;\n      value = value.slice(0, ctx.maxStringLength);\n      trailer = `... ${remaining} more character${remaining > 1 ? 's' : ''}`;\n    }\n    if (\n      ctx.compact !== true &&\n      // We do not support handling Unicode characters width with\n      // the readline getStringWidth function as there are\n      // performance implications.\n      value.length > kMinLineLength &&\n      value.length > ctx.breakLength - ctx.indentationLvl - 4\n    ) {\n      return (\n        value\n          .split(/(?<=\\n)/)\n          .map((line) => fn(strEscape(line), 'string'))\n          .join(` +\\n${' '.repeat(ctx.indentationLvl + 2)}`) + trailer\n      );\n    }\n    return fn(strEscape(value), 'string') + trailer;\n  }\n  if (typeof value === 'number')\n    return formatNumber(fn, value, ctx.numericSeparator);\n  if (typeof value === 'bigint')\n    return formatBigInt(fn, value, ctx.numericSeparator);\n  if (typeof value === 'boolean') return fn(`${value}`, 'boolean');\n  if (typeof value === 'undefined') return fn('undefined', 'undefined');\n  // es6 symbol primitive\n  return fn(Symbol.prototype.toString.call(value), 'symbol');\n}\n\nfunction formatNamespaceObject(\n  keys: PropertyKey[],\n  ctx: Context,\n  value: object,\n  recurseTimes: number\n): string[] {\n  const output = new Array<string>(keys.length);\n  for (let i = 0; i < keys.length; i++) {\n    try {\n      output[i] = formatProperty(\n        ctx,\n        value,\n        recurseTimes,\n        keys[i]!,\n        kObjectType\n      );\n    } catch (err) {\n      assert(isNativeError(err) && err.name === 'ReferenceError');\n      // Use the existing functionality. This makes sure the indentation and\n      // line breaks are always correct. Otherwise it is very difficult to keep\n      // this aligned, even though this is a hacky way of dealing with this.\n      const tmp = { [keys[i]!]: '' };\n      output[i] = formatProperty(ctx, tmp, recurseTimes, keys[i]!, kObjectType);\n      const pos = output[i]!.lastIndexOf(' ');\n      // We have to find the last whitespace and have to replace that value as\n      // it will be visualized as a regular string.\n      output[i] =\n        output[i]!.slice(0, pos + 1) +\n        ctx.stylize('<uninitialized>', 'special');\n    }\n  }\n  // Reset the keys to an empty array. This prevents duplicated inspection.\n  keys.length = 0;\n  return output;\n}\n\n// The array is sparse and/or has extra keys\nfunction formatSpecialArray(\n  ctx: Context,\n  value: unknown[],\n  recurseTimes: number,\n  maxLength: number,\n  output: string[],\n  i: number\n): string[] {\n  const keys = Object.keys(value);\n  let index = i;\n  for (; i < keys.length && output.length < maxLength; i++) {\n    const key = keys[i]!;\n    const tmp = +key;\n    // Arrays can only have up to 2^32 - 1 entries\n    if (tmp > 2 ** 32 - 2) {\n      break;\n    }\n    if (`${index}` !== key) {\n      if (numberRegExp.exec(key) === null) {\n        break;\n      }\n      const emptyItems = tmp - index;\n      const ending = emptyItems > 1 ? 's' : '';\n      const message = `<${emptyItems} empty item${ending}>`;\n      output.push(ctx.stylize(message, 'undefined'));\n      index = tmp;\n      if (output.length === maxLength) {\n        break;\n      }\n    }\n    output.push(formatProperty(ctx, value, recurseTimes, key, kArrayType));\n    index++;\n  }\n  const remaining = value.length - index;\n  if (output.length !== maxLength) {\n    if (remaining > 0) {\n      const ending = remaining > 1 ? 's' : '';\n      const message = `<${remaining} empty item${ending}>`;\n      output.push(ctx.stylize(message, 'undefined'));\n    }\n  } else if (remaining > 0) {\n    output.push(remainingText(remaining));\n  }\n  return output;\n}\n\nfunction formatArrayBuffer(ctx: Context, value: ArrayBuffer): string[] {\n  let buffer;\n  try {\n    buffer = new Uint8Array(value);\n  } catch {\n    return [ctx.stylize('(detached)', 'special')];\n  }\n  const maxArrayLength = ctx.maxArrayLength;\n  let str = Buffer.prototype.hexSlice\n    .call(buffer, 0, Math.min(maxArrayLength, buffer.length))\n    .replace(/(.{2})/g, '$1 ')\n    .trim();\n  const remaining = buffer.length - maxArrayLength;\n  if (remaining > 0)\n    str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`;\n  return [`${ctx.stylize('[Uint8Contents]', 'special')}: <${str}>`];\n}\n\nfunction formatArray(\n  ctx: Context,\n  value: unknown[],\n  recurseTimes: number\n): string[] {\n  const valLen = value.length;\n  const len = Math.min(Math.max(0, ctx.maxArrayLength), valLen);\n\n  const remaining = valLen - len;\n  const output: string[] = [];\n  for (let i = 0; i < len; i++) {\n    // Special handle sparse arrays.\n    if (!Object.prototype.hasOwnProperty.call(value, i)) {\n      return formatSpecialArray(ctx, value, recurseTimes, len, output, i);\n    }\n    output.push(formatProperty(ctx, value, recurseTimes, i, kArrayType));\n  }\n  if (remaining > 0) {\n    output.push(remainingText(remaining));\n  }\n  return output;\n}\n\nfunction formatTypedArray(\n  value: NodeJS.TypedArray,\n  length: number,\n  ctx: Context,\n  _ignored: unknown,\n  recurseTimes: number\n): string[] {\n  const maxLength = Math.min(Math.max(0, ctx.maxArrayLength), length);\n  const remaining = value.length - maxLength;\n  const output = new Array<string>(maxLength);\n  const elementFormatter =\n    value.length > 0 && typeof value[0] === 'number'\n      ? formatNumber\n      : formatBigInt;\n  for (let i = 0; i < maxLength; ++i) {\n    // @ts-expect-error `value[i]` assumed to be of correct numeric type\n    output[i] = elementFormatter(ctx.stylize, value[i], ctx.numericSeparator);\n  }\n  if (remaining > 0) {\n    output[maxLength] = remainingText(remaining);\n  }\n  if (ctx.showHidden) {\n    // .buffer goes last, it's not a primitive like the others.\n    // All besides `BYTES_PER_ELEMENT` are actually getters.\n    ctx.indentationLvl += 2;\n    for (const key of [\n      'BYTES_PER_ELEMENT',\n      'length',\n      'byteLength',\n      'byteOffset',\n      'buffer',\n    ] as const) {\n      const str = formatValue(ctx, value[key], recurseTimes, true);\n      output.push(`[${key}]: ${str}`);\n    }\n    ctx.indentationLvl -= 2;\n  }\n  return output;\n}\n\nfunction formatSet(\n  value: Set<unknown> | IterableIterator<unknown>,\n  ctx: Context,\n  _ignored: unknown,\n  recurseTimes: number\n): string[] {\n  const length = isSet(value) ? value.size : NaN;\n  const maxLength = Math.min(Math.max(0, ctx.maxArrayLength), length);\n  const remaining = length - maxLength;\n  const output: string[] = [];\n  ctx.indentationLvl += 2;\n  let i = 0;\n  for (const v of value) {\n    if (i >= maxLength) break;\n    output.push(formatValue(ctx, v, recurseTimes));\n    i++;\n  }\n  if (remaining > 0) {\n    output.push(remainingText(remaining));\n  }\n  ctx.indentationLvl -= 2;\n  return output;\n}\n\nfunction formatMap(\n  value: Map<unknown, unknown> | IterableIterator<[unknown, unknown]>,\n  ctx: Context,\n  _ignored: unknown,\n  recurseTimes: number\n): string[] {\n  const length = isMap(value) ? value.size : NaN;\n  const maxLength = Math.min(Math.max(0, ctx.maxArrayLength), length);\n  const remaining = length - maxLength;\n  const output: string[] = [];\n  ctx.indentationLvl += 2;\n  let i = 0;\n  for (const { 0: k, 1: v } of value) {\n    if (i >= maxLength) break;\n    output.push(\n      `${formatValue(ctx, k, recurseTimes)} => ${formatValue(ctx, v, recurseTimes)}`\n    );\n    i++;\n  }\n  if (remaining > 0) {\n    output.push(remainingText(remaining));\n  }\n  ctx.indentationLvl -= 2;\n  return output;\n}\n\nfunction formatSetIterInner(\n  ctx: Context,\n  recurseTimes: number,\n  entries: unknown[],\n  state: number\n): string[] {\n  const maxArrayLength = Math.max(ctx.maxArrayLength, 0);\n  const maxLength = Math.min(maxArrayLength, entries.length);\n  const output = new Array<string>(maxLength);\n  ctx.indentationLvl += 2;\n  for (let i = 0; i < maxLength; i++) {\n    output[i] = formatValue(ctx, entries[i], recurseTimes);\n  }\n  ctx.indentationLvl -= 2;\n  if (state === kWeak && !ctx.sorted) {\n    // Sort all entries to have a halfway reliable output (if more entries than\n    // retrieved ones exist, we can not reliably return the same output) if the\n    // output is not sorted anyway.\n    output.sort();\n  }\n  const remaining = entries.length - maxLength;\n  if (remaining > 0) {\n    output.push(remainingText(remaining));\n  }\n  return output;\n}\n\nfunction formatMapIterInner(\n  ctx: Context,\n  recurseTimes: number,\n  entries: unknown[],\n  state: number\n): string[] {\n  const maxArrayLength = Math.max(ctx.maxArrayLength, 0);\n  // Entries exist as [key1, val1, key2, val2, ...]\n  const len = entries.length / 2;\n  const remaining = len - maxArrayLength;\n  const maxLength = Math.min(maxArrayLength, len);\n  const output = new Array<string>(maxLength);\n  let i = 0;\n  ctx.indentationLvl += 2;\n  if (state === kWeak) {\n    for (; i < maxLength; i++) {\n      const pos = i * 2;\n      output[i] =\n        `${formatValue(ctx, entries[pos], recurseTimes)} => ${formatValue(ctx, entries[pos + 1], recurseTimes)}`;\n    }\n    // Sort all entries to have a halfway reliable output (if more entries than\n    // retrieved ones exist, we can not reliably return the same output) if the\n    // output is not sorted anyway.\n    if (!ctx.sorted) output.sort();\n  } else {\n    for (; i < maxLength; i++) {\n      const pos = i * 2;\n      const res = [\n        formatValue(ctx, entries[pos], recurseTimes),\n        formatValue(ctx, entries[pos + 1], recurseTimes),\n      ];\n      output[i] = reduceToSingleString(\n        ctx,\n        res,\n        '',\n        ['[', ']'],\n        kArrayExtrasType,\n        recurseTimes\n      );\n    }\n  }\n  ctx.indentationLvl -= 2;\n  if (remaining > 0) {\n    output.push(remainingText(remaining));\n  }\n  return output;\n}\n\nfunction formatWeakCollection(ctx: Context): string[] {\n  return [ctx.stylize('<items unknown>', 'special')];\n}\n\nfunction formatWeakSet(\n  ctx: Context,\n  value: WeakSet<any>,\n  recurseTimes: number\n): string[] {\n  const { entries } = internal.previewEntries(value)!;\n  return formatSetIterInner(ctx, recurseTimes, entries, kWeak);\n}\n\nfunction formatWeakMap(\n  ctx: Context,\n  value: WeakMap<any, unknown>,\n  recurseTimes: number\n): string[] {\n  const { entries } = internal.previewEntries(value)!;\n  return formatMapIterInner(ctx, recurseTimes, entries, kWeak);\n}\n\nfunction formatIterator(\n  braces: [string, string],\n  ctx: Context,\n  value: Iterator<unknown>,\n  recurseTimes: number\n): string[] {\n  const { entries, isKeyValue } = internal.previewEntries(value)!;\n  if (isKeyValue) {\n    // Mark entry iterators as such.\n    braces[0] = braces[0].replace(/ Iterator] {$/, ' Entries] {');\n    return formatMapIterInner(ctx, recurseTimes, entries, kMapEntries);\n  }\n\n  return formatSetIterInner(ctx, recurseTimes, entries, kIterator);\n}\n\nfunction formatPromise(\n  ctx: Context,\n  value: Promise<unknown>,\n  recurseTimes: number\n): string[] {\n  let output: string[];\n  const { state, result } = internal.getPromiseDetails(value)!;\n  if (state === internal.kPending) {\n    output = [ctx.stylize('<pending>', 'special')];\n  } else {\n    ctx.indentationLvl += 2;\n    const str = formatValue(ctx, result, recurseTimes);\n    ctx.indentationLvl -= 2;\n    output = [\n      state === internal.kRejected\n        ? `${ctx.stylize('<rejected>', 'special')} ${str}`\n        : str,\n    ];\n  }\n  return output;\n}\n\nfunction formatProperty(\n  ctx: Context,\n  value: object,\n  recurseTimes: number,\n  key: PropertyKey,\n  type: number,\n  desc?: PropertyDescriptor,\n  original = value\n): string {\n  let name: string, str: string;\n  let extra = ' ';\n  desc = desc ||\n    Object.getOwnPropertyDescriptor(value, key) || {\n      value: (value as Record<PropertyKey, unknown>)[key],\n      enumerable: true,\n    };\n  if (desc.value !== undefined) {\n    const diff = ctx.compact !== true || type !== kObjectType ? 2 : 3;\n    ctx.indentationLvl += diff;\n    str = formatValue(ctx, desc.value, recurseTimes);\n    if (diff === 3 && ctx.breakLength < getStringWidth(str, ctx.colors)) {\n      extra = `\\n${' '.repeat(ctx.indentationLvl)}`;\n    }\n    ctx.indentationLvl -= diff;\n  } else if (desc.get !== undefined) {\n    const label = desc.set !== undefined ? 'Getter/Setter' : 'Getter';\n    const s = ctx.stylize;\n    const sp = 'special';\n    if (\n      ctx.getters &&\n      (ctx.getters === true ||\n        (ctx.getters === 'get' && desc.set === undefined) ||\n        (ctx.getters === 'set' && desc.set !== undefined))\n    ) {\n      try {\n        const tmp = desc.get.call(original);\n        ctx.indentationLvl += 2;\n        if (tmp === null) {\n          str = `${s(`[${label}:`, sp)} ${s('null', 'null')}${s(']', sp)}`;\n        } else if (typeof tmp === 'object') {\n          str = `${s(`[${label}]`, sp)} ${formatValue(ctx, tmp, recurseTimes)}`;\n        } else {\n          const primitive = formatPrimitive(s, tmp, ctx);\n          str = `${s(`[${label}:`, sp)} ${primitive}${s(']', sp)}`;\n        }\n        ctx.indentationLvl -= 2;\n      } catch (err) {\n        const message = `<Inspection threw (${isError(err) ? err.message : String(err)})>`;\n        str = `${s(`[${label}:`, sp)} ${message}${s(']', sp)}`;\n      }\n    } else {\n      str = ctx.stylize(`[${label}]`, sp);\n    }\n  } else if (desc.set !== undefined) {\n    str = ctx.stylize('[Setter]', 'special');\n  } else {\n    str = ctx.stylize('undefined', 'undefined');\n  }\n  if (type === kArrayType) {\n    return str;\n  }\n  if (typeof key === 'symbol') {\n    const tmp = Symbol.prototype.toString\n      .call(key)\n      .replace(strEscapeSequencesReplacer, escapeFn);\n    name = ctx.stylize(tmp, 'symbol');\n  } else if (keyStrRegExp.exec(key as string) !== null) {\n    name =\n      key === '__proto__'\n        ? \"['__proto__']\"\n        : ctx.stylize(key as string, 'name');\n  } else {\n    name = ctx.stylize(strEscape(key as string), 'string');\n  }\n  if (desc.enumerable === false) {\n    name = `[${name}]`;\n  }\n  return `${name}:${extra}${str}`;\n}\n\nfunction isBelowBreakLength(\n  ctx: Context,\n  output: string[],\n  start: number,\n  base: string\n): boolean {\n  // Each entry is separated by at least a comma. Thus, we start with a total\n  // length of at least `output.length`. In addition, some cases have a\n  // whitespace in-between each other that is added to the total as well.\n  // TODO(BridgeAR): Add Unicode support. Use the readline getStringWidth\n  // function. Check the performance overhead and make it an opt-in in case it's\n  // significant.\n  let totalLength = output.length + start;\n  if (totalLength + output.length > ctx.breakLength) return false;\n  for (let i = 0; i < output.length; i++) {\n    if (ctx.colors) {\n      totalLength += removeColors(output[i]!).length;\n    } else {\n      totalLength += output[i]!.length;\n    }\n    if (totalLength > ctx.breakLength) {\n      return false;\n    }\n  }\n  // Do not line up properties on the same line if `base` contains line breaks.\n  return base === '' || !base.includes('\\n');\n}\n\nfunction reduceToSingleString(\n  ctx: Context,\n  output: string[],\n  base: string,\n  braces: [string, string],\n  extrasType: number,\n  recurseTimes: number,\n  value?: unknown\n): string {\n  if (ctx.compact !== true) {\n    if (typeof ctx.compact === 'number' && ctx.compact >= 1) {\n      // Memorize the original output length. In case the output is grouped,\n      // prevent lining up the entries on a single line.\n      const entries = output.length;\n      // Group array elements together if the array contains at least six\n      // separate entries.\n      if (extrasType === kArrayExtrasType && entries > 6) {\n        output = groupArrayElements(ctx, output, value as unknown[]);\n      }\n      // `ctx.currentDepth` is set to the most inner depth of the currently\n      // inspected object part while `recurseTimes` is the actual current depth\n      // that is inspected.\n      //\n      // Example:\n      //\n      // const a = { first: [ 1, 2, 3 ], second: { inner: [ 1, 2, 3 ] } }\n      //\n      // The deepest depth of `a` is 2 (a.second.inner) and `a.first` has a max\n      // depth of 1.\n      //\n      // Consolidate all entries of the local most inner depth up to\n      // `ctx.compact`, as long as the properties are smaller than\n      // `ctx.breakLength`.\n      if (\n        ctx.currentDepth - recurseTimes < ctx.compact &&\n        entries === output.length\n      ) {\n        // Line up all entries on a single line in case the entries do not\n        // exceed `breakLength`. Add 10 as constant to start next to all other\n        // factors that may reduce `breakLength`.\n        const start =\n          output.length +\n          ctx.indentationLvl +\n          braces[0].length +\n          base.length +\n          10;\n        if (isBelowBreakLength(ctx, output, start, base)) {\n          const joinedOutput = output.join(', ');\n          if (!joinedOutput.includes('\\n')) {\n            return (\n              `${base ? `${base} ` : ''}${braces[0]} ${joinedOutput}` +\n              ` ${braces[1]}`\n            );\n          }\n        }\n      }\n    }\n    // Line up each entry on an individual line.\n    const indentation = `\\n${' '.repeat(ctx.indentationLvl)}`;\n    return (\n      `${base ? `${base} ` : ''}${braces[0]}${indentation}  ` +\n      `${output.join(`,${indentation}  `)}${indentation}${braces[1]}`\n    );\n  }\n  // Line up all entries on a single line in case the entries do not exceed\n  // `breakLength`.\n  if (isBelowBreakLength(ctx, output, 0, base)) {\n    return (\n      `${braces[0]}${base ? ` ${base}` : ''} ${output.join(', ')} ` + braces[1]\n    );\n  }\n  const indentation = ' '.repeat(ctx.indentationLvl);\n  // If the opening \"brace\" is too large, like in the case of \"Set {\",\n  // we need to force the first item to be on the next line or the\n  // items will not line up correctly.\n  const ln =\n    base === '' && braces[0].length === 1\n      ? ' '\n      : `${base ? ` ${base}` : ''}\\n${indentation}  `;\n  // Line up each entry on an individual line.\n  return `${braces[0]}${ln}${output.join(`,\\n${indentation}  `)} ${braces[1]}`;\n}\n\nfunction hasBuiltInToString(value: object): boolean {\n  // Prevent triggering proxy traps.\n  const proxyTarget = internal.getProxyDetails(value);\n  if (proxyTarget !== undefined) {\n    if (proxyTarget === null || proxyTarget.target === null) {\n      return true;\n    }\n    return hasBuiltInToString(proxyTarget.target as object);\n  }\n\n  // Count objects that have no `toString` function as built-in.\n  if (typeof value?.toString !== 'function') {\n    return true;\n  }\n\n  // The object has a own `toString` property. Thus it's not not a built-in one.\n  if (Object.prototype.hasOwnProperty.call(value, 'toString')) {\n    return false;\n  }\n\n  // Find the object that has the `toString` property as own property in the\n  // prototype chain.\n  let pointer = value;\n  do {\n    pointer = Object.getPrototypeOf(pointer);\n  } while (!Object.prototype.hasOwnProperty.call(pointer, 'toString'));\n\n  // Check closer if the object is a built-in.\n  const descriptor = Object.getOwnPropertyDescriptor(pointer, 'constructor');\n  return (\n    descriptor !== undefined &&\n    typeof descriptor.value === 'function' &&\n    builtInObjects.has(descriptor.value.name)\n  );\n}\n\nconst firstErrorLine = (error: unknown) =>\n  (isError(error) ? error.message : String(error)).split('\\n', 1)[0];\nlet CIRCULAR_ERROR_MESSAGE: string | undefined;\nfunction tryStringify(arg: unknown): string {\n  try {\n    return JSON.stringify(arg);\n  } catch (err) {\n    // Populate the circular error message lazily\n    if (!CIRCULAR_ERROR_MESSAGE) {\n      try {\n        const a: { a?: unknown } = {};\n        a.a = a;\n        JSON.stringify(a);\n      } catch (circularError) {\n        CIRCULAR_ERROR_MESSAGE = firstErrorLine(circularError);\n      }\n    }\n    if (\n      typeof err === 'object' &&\n      err !== null &&\n      'name' in err &&\n      err.name === 'TypeError' &&\n      firstErrorLine(err) === CIRCULAR_ERROR_MESSAGE\n    ) {\n      return '[Circular]';\n    }\n    throw err;\n  }\n}\n\nexport function format(...args: unknown[]): string {\n  return formatWithOptionsInternal(undefined, args);\n}\n\nexport function formatWithOptions(\n  inspectOptions: InspectOptions,\n  ...args: unknown[]\n): string {\n  validateObject(inspectOptions, 'inspectOptions', kValidateObjectAllowArray);\n  return formatWithOptionsInternal(inspectOptions, args);\n}\n\nfunction formatNumberNoColor(number: number, options?: InspectOptions): string {\n  return formatNumber(\n    stylizeNoColor,\n    number,\n    options?.numericSeparator ?? inspectDefaultOptions.numericSeparator\n  );\n}\n\nfunction formatBigIntNoColor(bigint: bigint, options?: InspectOptions): string {\n  return formatBigInt(\n    stylizeNoColor,\n    bigint,\n    options?.numericSeparator ?? inspectDefaultOptions.numericSeparator\n  );\n}\n\nfunction formatWithOptionsInternal(\n  inspectOptions: InspectOptions | undefined,\n  args: unknown[]\n): string {\n  const first = args[0];\n  let a = 0;\n  let str = '';\n  let join = '';\n\n  if (typeof first === 'string') {\n    if (args.length === 1) {\n      return first;\n    }\n    let tempStr;\n    let lastPos = 0;\n\n    for (let i = 0; i < first.length - 1; i++) {\n      if (first.charCodeAt(i) === 37) {\n        // '%'\n        const nextChar = first.charCodeAt(++i);\n        if (a + 1 !== args.length) {\n          switch (nextChar) {\n            case 115: {\n              // 's'\n              const tempArg = args[++a];\n              if (typeof tempArg === 'number') {\n                tempStr = formatNumberNoColor(tempArg, inspectOptions);\n              } else if (typeof tempArg === 'bigint') {\n                tempStr = formatBigIntNoColor(tempArg, inspectOptions);\n              } else if (\n                typeof tempArg !== 'object' ||\n                tempArg === null ||\n                !hasBuiltInToString(tempArg)\n              ) {\n                tempStr = String(tempArg);\n              } else {\n                tempStr = inspect(tempArg, {\n                  ...inspectOptions,\n                  compact: 3,\n                  colors: false,\n                  depth: 0,\n                });\n              }\n              break;\n            }\n            case 106: // 'j'\n              tempStr = tryStringify(args[++a]);\n              break;\n            case 100: {\n              // 'd'\n              const tempNum = args[++a];\n              if (typeof tempNum === 'bigint') {\n                tempStr = formatBigIntNoColor(tempNum, inspectOptions);\n              } else if (typeof tempNum === 'symbol') {\n                tempStr = 'NaN';\n              } else {\n                tempStr = formatNumberNoColor(Number(tempNum), inspectOptions);\n              }\n              break;\n            }\n            case 79: // 'O'\n              tempStr = inspect(args[++a], inspectOptions);\n              break;\n            case 111: // 'o'\n              tempStr = inspect(args[++a], {\n                ...inspectOptions,\n                showHidden: true,\n                showProxy: true,\n                depth: 4,\n              });\n              break;\n            case 105: {\n              // 'i'\n              const tempInteger = args[++a];\n              if (typeof tempInteger === 'bigint') {\n                tempStr = formatBigIntNoColor(tempInteger, inspectOptions);\n              } else if (typeof tempInteger === 'symbol') {\n                tempStr = 'NaN';\n              } else {\n                tempStr = formatNumberNoColor(\n                  Number.parseInt(tempInteger as unknown as string),\n                  inspectOptions\n                );\n              }\n              break;\n            }\n            case 102: {\n              // 'f'\n              const tempFloat = args[++a];\n              if (typeof tempFloat === 'symbol') {\n                tempStr = 'NaN';\n              } else {\n                tempStr = formatNumberNoColor(\n                  Number.parseFloat(tempFloat as unknown as string),\n                  inspectOptions\n                );\n              }\n              break;\n            }\n            case 99: // 'c'\n              a += 1;\n              tempStr = '';\n              break;\n            case 37: // '%'\n              str += first.slice(lastPos, i);\n              lastPos = i + 1;\n              continue;\n            default: // Any other character is not a correct placeholder\n              continue;\n          }\n          if (lastPos !== i - 1) {\n            str += first.slice(lastPos, i - 1);\n          }\n          str += tempStr;\n          lastPos = i + 1;\n        } else if (nextChar === 37) {\n          str += first.slice(lastPos, i);\n          lastPos = i + 1;\n        }\n      }\n    }\n    if (lastPos !== 0) {\n      a++;\n      join = ' ';\n      if (lastPos < first.length) {\n        str += first.slice(lastPos);\n      }\n    }\n  }\n\n  while (a < args.length) {\n    const value = args[a];\n    str += join;\n    str += typeof value !== 'string' ? inspect(value, inspectOptions) : value;\n    join = ' ';\n    a++;\n  }\n  return str;\n}\n\nexport function isZeroWidthCodePoint(code: number): boolean {\n  return (\n    code <= 0x1f || // C0 control codes\n    (code >= 0x7f && code <= 0x9f) || // C1 control codes\n    (code >= 0x300 && code <= 0x36f) || // Combining Diacritical Marks\n    (code >= 0x200b && code <= 0x200f) || // Modifying Invisible Characters\n    // Combining Diacritical Marks for Symbols\n    (code >= 0x20d0 && code <= 0x20ff) ||\n    (code >= 0xfe00 && code <= 0xfe0f) || // Variation Selectors\n    (code >= 0xfe20 && code <= 0xfe2f) || // Combining Half Marks\n    (code >= 0xe0100 && code <= 0xe01ef)\n  ); // Variation Selectors\n}\n\n/**\n * Returns the number of columns required to display the given string.\n */\nexport function getStringWidth(str: string, removeControlChars = true): number {\n  let width = 0;\n\n  if (removeControlChars) str = stripVTControlCharacters(str);\n  str = str.normalize('NFC');\n  for (const char of str) {\n    const code = char.codePointAt(0)!;\n    if (isFullWidthCodePoint(code)) {\n      width += 2;\n    } else if (!isZeroWidthCodePoint(code)) {\n      width++;\n    }\n  }\n\n  return width;\n}\n\n/**\n * Returns true if the character represented by a given\n * Unicode code point is full-width. Otherwise returns false.\n */\nconst isFullWidthCodePoint = (code: number) => {\n  // Code points are partially derived from:\n  // https://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt\n  return (\n    code >= 0x1100 &&\n    (code <= 0x115f || // Hangul Jamo\n      code === 0x2329 || // LEFT-POINTING ANGLE BRACKET\n      code === 0x232a || // RIGHT-POINTING ANGLE BRACKET\n      // CJK Radicals Supplement .. Enclosed CJK Letters and Months\n      (code >= 0x2e80 && code <= 0x3247 && code !== 0x303f) ||\n      // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A\n      (code >= 0x3250 && code <= 0x4dbf) ||\n      // CJK Unified Ideographs .. Yi Radicals\n      (code >= 0x4e00 && code <= 0xa4c6) ||\n      // Hangul Jamo Extended-A\n      (code >= 0xa960 && code <= 0xa97c) ||\n      // Hangul Syllables\n      (code >= 0xac00 && code <= 0xd7a3) ||\n      // CJK Compatibility Ideographs\n      (code >= 0xf900 && code <= 0xfaff) ||\n      // Vertical Forms\n      (code >= 0xfe10 && code <= 0xfe19) ||\n      // CJK Compatibility Forms .. Small Form Variants\n      (code >= 0xfe30 && code <= 0xfe6b) ||\n      // Halfwidth and Fullwidth Forms\n      (code >= 0xff01 && code <= 0xff60) ||\n      (code >= 0xffe0 && code <= 0xffe6) ||\n      // Kana Supplement\n      (code >= 0x1b000 && code <= 0x1b001) ||\n      // Enclosed Ideographic Supplement\n      (code >= 0x1f200 && code <= 0x1f251) ||\n      // Miscellaneous Symbols and Pictographs 0x1f300 - 0x1f5ff\n      // Emoticons 0x1f600 - 0x1f64f\n      (code >= 0x1f300 && code <= 0x1f64f) ||\n      // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane\n      (code >= 0x20000 && code <= 0x3fffd))\n  );\n};\n\n/**\n * Remove all VT control characters. Use to estimate displayed string width.\n */\nexport function stripVTControlCharacters(str: string): string {\n  validateString(str, 'str');\n\n  return str.replace(ansi, '');\n}\n\n// ================================================================================================\n// WORKERD SPECIFIC CODE\n\n// Called from C++ on `console.log()`s to format values\nexport function formatLog(\n  ...args: [\n    ...values: unknown[],\n    colors: boolean,\n    structuredLogging: boolean,\n    level: string,\n  ]\n): string {\n  const level = args.pop() as string;\n  const structuredLogging = args.pop() as boolean;\n  const colors = args.pop() as boolean;\n  const inspectOptions: InspectOptions = { colors };\n\n  try {\n    const message = formatWithOptions(inspectOptions, ...args);\n    if (structuredLogging) {\n      return JSON.stringify({\n        timestamp: Date.now(),\n        level,\n        message,\n      });\n    } else {\n      return message;\n    }\n  } catch (err) {\n    return `<Formatting threw (${isError(err) ? err.stack : String(err)})>`;\n  }\n}\n\nfunction isBuiltinPrototype(proto: unknown) {\n  if (proto === null) return true;\n  // JSG resource type prototypes carry the kResourceTypeInspect symbol.\n  // These are not \"built-in\" in the JS-engine sense even though their\n  // constructors are own properties of globalThis (per Web IDL).  The\n  // prototype walk must continue through them to collect accessor\n  // properties (e.g. Blob.prototype.size).\n  if (\n    typeof proto === 'object' &&\n    proto !== null &&\n    internal.kResourceTypeInspect in (proto as Record<PropertyKey, unknown>)\n  ) {\n    return false;\n  }\n  const descriptor = Object.getOwnPropertyDescriptor(proto, 'constructor');\n  return (\n    descriptor !== undefined &&\n    typeof descriptor.value === 'function' &&\n    builtInObjects.has(descriptor.value.name)\n  );\n}\n\nfunction isRpcWildcardType(value: unknown) {\n  return (\n    value instanceof internalWorkers.RpcStub ||\n    value instanceof internalWorkers.RpcPromise ||\n    value instanceof internalWorkers.RpcProperty\n  );\n}\n\nfunction isEntry(value: unknown): value is [unknown, unknown] {\n  return Array.isArray(value) && value.length === 2;\n}\nfunction maybeGetEntries(\n  value: Record<PropertyKey, unknown>\n): [unknown, unknown][] | undefined {\n  // If this value is an RPC type with a wildcard property handler (e.g. `RpcStub`), don't try to\n  // call `entries()` on it. This won't be an `entries()` function, and calling it with `.call()`\n  // would dispose the stub.\n  if (isRpcWildcardType(value)) return;\n\n  const entriesFunction = value['entries'] as any;\n  if (typeof entriesFunction !== 'function') return;\n  const entriesIterator: unknown = entriesFunction.call(value);\n  if (typeof entriesIterator !== 'object' || entriesIterator === null) return;\n  if (!(Symbol.iterator in entriesIterator)) return;\n  const entries = Array.from(entriesIterator as Iterable<unknown>);\n  if (!entries.every(isEntry)) return;\n  return entries;\n}\n\nconst kEntries = Symbol('kEntries');\nfunction hasEntries(\n  value: unknown\n): value is { [kEntries]: [unknown, unknown][] } {\n  return typeof value === 'object' && value !== null && kEntries in value;\n}\n\n// Default custom inspect implementation for JSG resource types\nfunction formatJsgResourceType(\n  this: Record<PropertyKey, unknown>,\n  additionalProperties: Record<\n    string,\n    symbol /* value-func */ | false /* unimplemented-marker */\n  >,\n  depth: number,\n  options: InspectOptionsStylized\n): unknown {\n  const name = this.constructor.name;\n  if (depth < 0) return options.stylize(`[${name}]`, 'special');\n\n  // Build a plain object for inspection. If this value has an `entries()` function, add those\n  // entries for map-like `K => V` formatting. Note we can't use a `Map` here as a key may have\n  // multiple values (e.g. URLSearchParams).\n  const record: Record<PropertyKey, unknown> = {};\n  const maybeEntries = maybeGetEntries(this);\n  if (maybeEntries !== undefined) record[kEntries] = maybeEntries;\n\n  // Add all instance and prototype non-function-valued properties\n  let current: object = this;\n  do {\n    // `Object.getOwnPropertyDescriptor()` throws `Illegal Invocation` for our prototypes here.\n    for (const key of Object.getOwnPropertyNames(current)) {\n      // If this property is unimplemented, don't try to log it\n      if (additionalProperties[key] === false) continue;\n      const value = this[key];\n      // Ignore function-valued and static properties\n      if (\n        typeof value === 'function' ||\n        this.constructor.propertyIsEnumerable(key)\n      )\n        continue;\n      record[key] = value;\n    }\n  } while (!isBuiltinPrototype((current = Object.getPrototypeOf(current))));\n\n  // Add additional inspect-only properties as non-enumerable so they appear in square brackets\n  for (const [key, symbol] of Object.entries(additionalProperties)) {\n    // This is an additional property if it's not an unimplemented marker\n    if (symbol !== false) {\n      Object.defineProperty(record, key, {\n        value: this[symbol],\n        enumerable: false,\n      });\n    }\n  }\n\n  // Format the plain object\n  const inspected = inspect(record, {\n    ...options,\n    depth: options.depth == null ? null : depth,\n    showHidden: true, // Show non-enumerable inspect-only properties\n  });\n\n  if (maybeEntries === undefined) {\n    return `${name} ${inspected}`;\n  } else {\n    // Inspecting a entries object gives something like `Object(1) { 'a' => '1' }`, whereas we want\n    // something like `Headers(1) { 'a' => '1' }`.\n    return `${name}${inspected.replace('Object', '')}`;\n  }\n}\n"
  },
  {
    "path": "src/node/internal/internal_module.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n// Intentionally does not include modules with mandatory 'node:'\n// prefix like `node:test`.\n// See: See https://nodejs.org/docs/latest/api/modules.html#built-in-modules-with-mandatory-node-prefix\n// TODO(later): This list duplicates the list that is in\n// workerd/jsg/modules.c++. Later we should source these\n// from the same place so we don't have to maintain two lists.\nexport const builtinModules = [\n  '_http_agent',\n  '_http_client',\n  '_http_common',\n  '_http_incoming',\n  '_http_outgoing',\n  '_http_server',\n  '_stream_duplex',\n  '_stream_passthrough',\n  '_stream_readable',\n  '_stream_transform',\n  '_stream_wrap',\n  '_stream_writable',\n  '_tls_common',\n  '_tls_wrap',\n  'assert',\n  'assert/strict',\n  'async_hooks',\n  'buffer',\n  'child_process',\n  'cluster',\n  'console',\n  'constants',\n  'crypto',\n  'dgram',\n  'diagnostics_channel',\n  'dns',\n  'dns/promises',\n  'domain',\n  'events',\n  'fs',\n  'fs/promises',\n  'http',\n  'http2',\n  'https',\n  'inspector',\n  'inspector/promises',\n  'module',\n  'net',\n  'os',\n  'path',\n  'path/posix',\n  'path/win32',\n  'perf_hooks',\n  'process',\n  'punycode',\n  'querystring',\n  'readline',\n  'readline/promises',\n  'repl',\n  'stream',\n  'stream/consumers',\n  'stream/promises',\n  'stream/web',\n  'string_decoder',\n  'sys',\n  'timers',\n  'timers/promises',\n  'tls',\n  'trace_events',\n  'tty',\n  'url',\n  'util',\n  'util/types',\n  'v8',\n  'vm',\n  'wasi',\n  'worker_threads',\n  'zlib',\n];\nObject.freeze(builtinModules);\n"
  },
  {
    "path": "src/node/internal/internal_net.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n/* eslint-disable @typescript-eslint/no-empty-object-type */\n\nimport inner from 'cloudflare-internal:sockets';\n\nimport {\n  AbortError,\n  ERR_INVALID_ARG_VALUE,\n  ERR_INVALID_ARG_TYPE,\n  ERR_MISSING_ARGS,\n  ERR_OUT_OF_RANGE,\n  ERR_OPTION_NOT_IMPLEMENTED,\n  ERR_SOCKET_CLOSED,\n  ERR_SOCKET_CLOSED_BEFORE_CONNECTION,\n  ERR_SOCKET_CONNECTING,\n  ERR_INVALID_IP_ADDRESS,\n  ERR_INVALID_ADDRESS,\n  EPIPE,\n} from 'node-internal:internal_errors';\n\nimport {\n  validateAbortSignal,\n  validateArray,\n  validateFunction,\n  validateInt32,\n  validateNumber,\n  validatePort,\n  validateObject,\n  validateOneOf,\n  validateBoolean,\n  validateString,\n  validateUint32,\n} from 'node-internal:validators';\n\nimport { isUint8Array, isArrayBufferView } from 'node-internal:internal_types';\nimport { Duplex } from 'node-internal:streams_duplex';\nimport { Buffer } from 'node-internal:internal_buffer';\nimport {\n  kDestroyed,\n  kIsReadable,\n  kIsWritable,\n} from 'node-internal:streams_util';\nimport type {\n  IpcSocketConnectOpts,\n  SocketConnectOpts,\n  TcpSocketConnectOpts,\n  AddressInfo,\n  Socket as _Socket,\n  SocketAddress as _SocketAddress,\n  SocketAddressInitOptions,\n  IPVersion,\n  OnReadOpts,\n} from 'node:net';\nimport type { InspectOptions } from 'node:util';\nimport type { Writable } from 'node:stream';\nimport { JSStreamSocket } from 'node-internal:internal_tls_jsstream';\n\nimport { inspect } from 'node-internal:internal_inspect';\nimport type { FixedLengthArray } from 'node-internal:internal_utils';\n\nconst kInspect = inspect.custom;\n\nconst kLastWriteQueueSize = Symbol('kLastWriteQueueSize');\nconst kTimeout = Symbol('kTimeout');\nconst kBuffer = Symbol('kBuffer');\nconst kBufferCb = Symbol('kBufferCb');\nconst kBufferGen = Symbol('kBufferGen');\nconst kBytesRead = Symbol('kBytesRead');\nconst kBytesWritten = Symbol('kBytesWritten');\nconst kUpdateTimer = Symbol('kUpdateTimer');\nexport const normalizedArgsSymbol = Symbol('normalizedArgs');\nexport const kReinitializeHandle = Symbol('kReinitializeHandle');\n\n// Once the socket has been opened, the socket info provided by the\n// socket.opened promise will be stored here.\nconst kSocketInfo = Symbol('kSocketInfo');\n\n// IPv4 Segment\nconst v4Seg = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])';\nconst v4Str = `(?:${v4Seg}\\\\.){3}${v4Seg}`;\nconst IPv4Reg = new RegExp(`^${v4Str}$`);\n\n// IPv6 Segment\nconst v6Seg = '(?:[0-9a-fA-F]{1,4})';\nconst IPv6Reg = new RegExp(\n  '^(?:' +\n    `(?:${v6Seg}:){7}(?:${v6Seg}|:)|` +\n    `(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` +\n    `(?:${v6Seg}:){5}(?::${v4Str}|(?::${v6Seg}){1,2}|:)|` +\n    `(?:${v6Seg}:){4}(?:(?::${v6Seg}){0,1}:${v4Str}|(?::${v6Seg}){1,3}|:)|` +\n    `(?:${v6Seg}:){3}(?:(?::${v6Seg}){0,2}:${v4Str}|(?::${v6Seg}){1,4}|:)|` +\n    `(?:${v6Seg}:){2}(?:(?::${v6Seg}){0,3}:${v4Str}|(?::${v6Seg}){1,5}|:)|` +\n    `(?:${v6Seg}:){1}(?:(?::${v6Seg}){0,4}:${v4Str}|(?::${v6Seg}){1,6}|:)|` +\n    `(?::(?:(?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` +\n    ')(?:%[0-9a-zA-Z-.:]{1,})?$'\n);\n\nconst TIMEOUT_MAX = 2 ** 31 - 1;\n\n// ======================================================================================\n\nexport type SocketOptions = {\n  timeout?: number;\n  writable?: boolean;\n  readable?: boolean;\n  decodeStrings?: boolean;\n  autoDestroy?: boolean;\n  objectMode?: boolean;\n  readableObjectMode?: boolean;\n  writableObjectMode?: boolean;\n  keepAliveInitialDelay?: number;\n  fd?: number;\n  handle?: Socket['_handle'];\n  noDelay?: boolean;\n  keepAlive?: boolean;\n  allowHalfOpen?: boolean;\n  emitClose?: boolean;\n  signal?: AbortSignal;\n  onread?:\n    | ({ callback?: () => Uint8Array; buffer?: Uint8Array } & OnReadOpts)\n    | null;\n};\n\nexport function Server(): void {\n  throw new Error('Server is not implemented');\n}\n\nexport type SocketWriteData = Array<{\n  chunk: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n  encoding: BufferEncoding;\n}>;\n\n// @ts-expect-error TS2323 Redeclare error.\nexport declare class Socket extends _Socket {\n  timeout: number;\n  connecting: boolean;\n  _aborted: boolean;\n  _hadError: boolean;\n  _parent: null | Socket['_handle'];\n  _parentWrap: null | Socket | JSStreamSocket;\n  _host: null | string;\n  _peername: null | string;\n  _getsockname(): {\n    address?: string;\n    port?: number;\n    family?: string;\n  };\n  [kLastWriteQueueSize]: number | null | undefined;\n  [kTimeout]: Socket | null | undefined;\n  [kBuffer]: null | boolean | Uint8Array;\n  [kBufferCb]:\n    | null\n    | undefined\n    | ((len?: number, buf?: Buffer) => boolean | Uint8Array);\n  [kBufferGen]: null | (() => undefined | Uint8Array);\n  [kSocketInfo]: null | {\n    address?: string;\n    port?: number;\n    family?: number | string;\n    remoteAddress?: Record<string, unknown>;\n  };\n  [kBytesRead]: number;\n  [kBytesWritten]: number;\n  [kReinitializeHandle](handle: Socket['_handle']): void;\n  _closeAfterHandlingError: boolean;\n  _handle: null | {\n    writeQueueSize?: number;\n    lastWriteQueueSize?: number;\n    reading: boolean | undefined;\n    bytesRead: number;\n    bytesWritten: number;\n    socket: ReturnType<typeof inner.connect>;\n    reader: ReadableStreamBYOBReader;\n    writer: WritableStreamDefaultWriter<unknown>;\n    options: {\n      host: string;\n      port: number;\n      addressType: number;\n    };\n  };\n  _sockname?: null | AddressInfo;\n  _onTimeout(): void;\n  _unrefTimer(): void;\n  _writeGeneric(\n    writev: boolean,\n    data: SocketWriteData,\n    encoding: string,\n    cb: (err?: Error) => void\n  ): void;\n  _final(cb: (err?: Error) => void): void;\n  _read(n: number): void;\n  _reset(): void;\n  _getpeername(): Record<string, unknown>;\n  _readableState: undefined;\n  _closed: boolean;\n  writableErrored: boolean;\n  readableErrored: boolean;\n  [kIsReadable]: boolean;\n  [kIsWritable]: boolean;\n  [kDestroyed]: boolean;\n  _writableState: undefined;\n  _bytesDispatched: number;\n  _pendingData: SocketWriteData | null;\n  _pendingEncoding: string;\n  _undestroy(): void;\n  // This should have existed in _Socket type but it's not...\n  writableBuffer?: Writable & {\n    allBuffers: boolean;\n    length: number;\n  };\n\n  // Defined by TLSSocket\n  encrypted?: boolean;\n  _finishInit(): void;\n\n  constructor(options?: SocketOptions);\n  prototype: Socket;\n  resetAndClosing?: boolean;\n}\n\n// @ts-expect-error TS2323 Redeclare error.\nexport function Socket(this: Socket, options?: SocketOptions): Socket {\n  if (!(this instanceof Socket)) {\n    return new Socket(options);\n  }\n\n  if (options?.objectMode) {\n    throw new ERR_INVALID_ARG_VALUE(\n      'options.objectMode',\n      options.objectMode,\n      'is not supported'\n    );\n  } else if (options?.readableObjectMode || options?.writableObjectMode) {\n    throw new ERR_INVALID_ARG_VALUE(\n      `options.${\n        options.readableObjectMode ? 'readableObjectMode' : 'writableObjectMode'\n      }`,\n      options.readableObjectMode || options.writableObjectMode,\n      'is not supported'\n    );\n  }\n  if (options?.keepAliveInitialDelay !== undefined) {\n    validateNumber(\n      options.keepAliveInitialDelay,\n      'options.keepAliveInitialDelay'\n    );\n\n    if (options.keepAliveInitialDelay < 0) {\n      options.keepAliveInitialDelay = 0;\n    }\n  }\n\n  if (typeof options === 'number') {\n    options = { fd: options as number };\n  } else {\n    options = { ...options };\n  }\n\n  if (options.fd !== undefined) {\n    // We are not supporting the options.fd option for now. This is the option\n    // that allows users to pass in a file descriptor to an existing socket.\n    // Workers doesn't have file descriptors and does not use them in any way.\n    throw new ERR_OPTION_NOT_IMPLEMENTED('options.fd');\n  }\n\n  // We do not support the noDelay and keepAlive options at this\n  // time and will just ignore them if they are passed.\n  //\n  // We shouldn't throw an error for the following validations,\n  // because it breaks packages such as redis.\n  //\n  // if (options.noDelay) {\n  //   throw new ERR_OPTION_NOT_IMPLEMENTED('truthy options.noDelay');\n  // }\n  //\n  // if (options.keepAlive) {\n  //   throw new ERR_OPTION_NOT_IMPLEMENTED('truthy options.keepAlive');\n  // }\n\n  options.allowHalfOpen = Boolean(options.allowHalfOpen);\n  // TODO(now): Match behavior with Node.js\n  // In Node.js, emitClose and autoDestroy are false by default so that\n  // the socket must handle those itself, including emitting the close\n  // event with the hadError argument. We should match that behavior.\n  options.emitClose = false;\n  options.autoDestroy = true;\n  options.decodeStrings = false;\n\n  // In Node.js, these are meaningful when the options.fd is used.\n  // We do not support options.fd so we just ignore whatever value\n  // is given and always pass true.\n  options.readable = true;\n  options.writable = true;\n\n  this._handle = null;\n  this.connecting = false;\n  this._hadError = false;\n  this._parent = null;\n  this._parentWrap = null;\n  this._host = null;\n  this[kLastWriteQueueSize] = 0;\n  this[kTimeout] = null;\n  this[kBuffer] = null;\n  this[kBufferCb] = null;\n  this[kBufferGen] = null;\n  this[kSocketInfo] = null;\n  this[kBytesRead] = 0;\n  this[kBytesWritten] = 0;\n  this._closeAfterHandlingError = false;\n  // @ts-expect-error TS2540 Required due to types\n  this.autoSelectFamilyAttemptedAddresses = [];\n\n  this._undestroy();\n  this._sockname = null;\n  this._pendingData = null;\n  this._pendingEncoding = '';\n\n  // Call Duplex constructor before setting up the abort signal\n  // This ensures the stream methods are properly set up before\n  // any abort handling that might call destroy()\n  Duplex.call(this, options);\n\n  if (options.handle) {\n    validateObject(options.handle, 'options.handle');\n    this._handle = options.handle;\n  }\n\n  // We explicitly listen for all 'end' events, not only for once\n  // because Socket class supports reconnection through s.connect();\n  this.on('end', onReadableStreamEnd);\n\n  const onread = options.onread;\n  if (\n    onread != null &&\n    typeof onread === 'object' &&\n    // The onread.buffer can either be a Uint8Array or a function that returns\n    // a Uint8Array.\n    (isUint8Array(onread.buffer) || typeof onread.buffer === 'function') &&\n    // The onread.callback is the function used to deliver the read buffer to\n    // the application.\n    typeof onread.callback === 'function'\n  ) {\n    if (typeof onread.buffer === 'function') {\n      this[kBuffer] = true;\n      this[kBufferGen] = onread.buffer;\n    } else {\n      this[kBuffer] = onread.buffer;\n      this[kBufferGen] = (): Uint8Array | undefined => onread.buffer;\n    }\n    this[kBufferCb] = onread.callback;\n  } else {\n    this[kBuffer] = true;\n    this[kBufferGen] = (): Uint8Array => new Uint8Array(4096);\n    this[kBufferCb] = undefined;\n  }\n\n  // Now set up abort signal handling after the Duplex constructor\n  if (options.signal) {\n    addClientAbortSignalOption(this, options.signal);\n  }\n\n  this[kBytesRead] = 0;\n  this[kBytesWritten] = 0;\n\n  // TODO(soon): Enable this once blockList is implemented.\n  // if (options.blockList) {\n  //     if (!BlockList.isBlockList(options.blockList)) {\n  //       throw new ERR_INVALID_ARG_TYPE('options.blockList', 'net.BlockList', options.blockList);\n  //     }\n  //     this.blockList = options.blockList;\n  //   }\n\n  return this;\n}\n\nObject.setPrototypeOf(Socket.prototype, Duplex.prototype);\nObject.setPrototypeOf(Socket, Duplex);\n\nSocket.prototype._unrefTimer = function _unrefTimer(this: Socket): void {\n  // eslint-disable-next-line @typescript-eslint/no-this-alias\n  for (let s: Socket | null = this; s != null; s = s._parentWrap) {\n    if (s[kTimeout] != null) {\n      clearTimeout(s[kTimeout] as unknown as number);\n      s[kTimeout] = this.setTimeout(s.timeout, (): void => {\n        s._onTimeout();\n      });\n    }\n  }\n};\n\nSocket.prototype.setTimeout = function (\n  this: Socket,\n  msecs: number,\n  callback?: () => void\n): Socket {\n  if (this.destroyed) return this;\n\n  this.timeout = msecs;\n\n  // Type checking identical to timers.enroll()\n  msecs = getTimerDuration(msecs, 'msecs');\n\n  // Attempt to clear an existing timer in both cases -\n  // even if it will be rescheduled we don't want to leak an existing timer.\n  clearTimeout(this[kTimeout] as unknown as number);\n\n  if (msecs === 0) {\n    if (callback !== undefined) {\n      validateFunction(callback, 'callback');\n      this.removeListener('timeout', callback);\n    }\n  } else {\n    // @ts-expect-error TS2740 Required to not overcomplicate types\n    this[kTimeout] = setTimeout((): void => {\n      this._onTimeout();\n    }, msecs);\n    if (callback !== undefined) {\n      validateFunction(callback, 'callback');\n      this.once('timeout', callback);\n    }\n  }\n  return this;\n};\n\nSocket.prototype._onTimeout = function (this: Socket): void {\n  const handle = this._handle;\n  const lastWriteQueueSize = this[kLastWriteQueueSize] as number;\n  if (lastWriteQueueSize > 0 && handle) {\n    // `lastWriteQueueSize !== writeQueueSize` means there is\n    // an active write in progress, so we suppress the timeout.\n    const { writeQueueSize } = handle;\n    if (lastWriteQueueSize !== writeQueueSize) {\n      this[kLastWriteQueueSize] = writeQueueSize;\n      this._unrefTimer();\n      return;\n    }\n  }\n  this.emit('timeout');\n};\n\nSocket.prototype._getpeername = function (\n  this: Socket\n): Record<string, unknown> {\n  if (this._handle == null || this[kSocketInfo] == null) {\n    return {};\n  }\n\n  return { ...this[kSocketInfo].remoteAddress };\n};\n\nSocket.prototype._getsockname = function (this: Socket): AddressInfo | {} {\n  if (this._handle == null) {\n    return {};\n  }\n  this._sockname ??= {\n    address: '0.0.0.0',\n    port: 0,\n    family: 'IPv4',\n  };\n  return this._sockname;\n};\n\nSocket.prototype.address = function (this: Socket): {} | AddressInfo {\n  return this._getsockname();\n};\n\n// ======================================================================================\n// Writable side ...\n\nSocket.prototype._writeGeneric = function (\n  this: Socket,\n  writev: boolean,\n  data: SocketWriteData,\n  encoding: string,\n  cb: (err?: Error) => void\n  // eslint-disable-next-line @typescript-eslint/no-invalid-void-type\n): false | void {\n  // If we are still connecting, buffer this for later.\n  // The writable logic will buffer up any more writes while\n  // waiting for this one to be done.\n  try {\n    if (this.connecting) {\n      this._pendingData = data;\n      this._pendingEncoding = encoding;\n      function onClose(): void {\n        cb(new ERR_SOCKET_CLOSED_BEFORE_CONNECTION());\n      }\n      this.once('connect', () => {\n        this.off('close', onClose);\n        this._writeGeneric(writev, data, encoding, cb);\n      });\n      this.once('close', onClose);\n      return;\n    }\n\n    this._pendingData = null;\n    this._pendingEncoding = '';\n\n    if (this._handle?.writer === undefined) {\n      cb(new ERR_SOCKET_CLOSED());\n      return false;\n    }\n\n    this._unrefTimer();\n\n    let lastWriteSize = 0;\n    if (writev) {\n      // data is an array of strings or ArrayBufferViews. We're going to concat\n      // them all together into a single buffer so we can write all at once. This\n      // trades higher memory use by copying the input buffers for fewer round trips\n      // through the write loop in the stream. Since the write loops involve bouncing\n      // back and forth across the kj event loop boundary and requires reacquiring the\n      // isolate lock after each write, this should be more efficient in the long run.\n      const buffers = [];\n      for (const d of data) {\n        if (typeof d.chunk === 'string') {\n          const buf = Buffer.from(d.chunk, d.encoding);\n          buffers.push(buf);\n          lastWriteSize += buf.byteLength;\n        } else if (isArrayBufferView(d.chunk)) {\n          buffers.push(\n            new Uint8Array(\n              d.chunk.buffer,\n              d.chunk.byteOffset,\n              d.chunk.byteLength\n            )\n          );\n          lastWriteSize += d.chunk.byteLength;\n        } else {\n          throw new ERR_INVALID_ARG_TYPE(\n            'chunk',\n            ['string', 'ArrayBufferView'],\n            d.chunk\n          );\n        }\n      }\n      this._unrefTimer();\n\n      this._handle.writer.write(Buffer.concat(buffers)).then(\n        () => {\n          if (this._handle != null) {\n            this._handle.bytesWritten += this[kLastWriteQueueSize] ?? 0;\n          } else {\n            this[kBytesWritten] += this[kLastWriteQueueSize] ?? 0;\n          }\n          this[kLastWriteQueueSize] = 0;\n          this._unrefTimer();\n          cb();\n        },\n        (err: unknown): void => {\n          this[kLastWriteQueueSize] = 0;\n          this._unrefTimer();\n          cb(err as Error);\n        }\n      );\n    } else {\n      let bufferData: Buffer;\n      if (typeof data === 'string') {\n        bufferData = Buffer.from(data, encoding);\n      } else {\n        bufferData = data as unknown as Buffer;\n      }\n      this._handle.writer.write(bufferData).then(\n        () => {\n          if (this._handle != null) {\n            this._handle.bytesWritten += this[kLastWriteQueueSize] ?? 0;\n          } else {\n            this[kBytesWritten] += this[kLastWriteQueueSize] ?? 0;\n          }\n          this[kLastWriteQueueSize] = 0;\n          this._unrefTimer();\n          cb();\n        },\n        (err: unknown): void => {\n          this[kLastWriteQueueSize] = 0;\n          this._unrefTimer();\n\n          // Think of the following code:\n          //\n          // const socket = net.connect(env.SERVER_THAT_DIES_PORT);\n          // socket.on('end', () => {\n          //   strictEqual(socket.writable, true);\n          //   socket.write('hello world');\n          //   resolve();\n          // });\n          //\n          // If we don't omit the error message for CLOSED, socket.write()\n          // will throw an error. This is not compliant with Node.js behavior.\n          if (\n            (err as Error).message !== 'This WritableStream has been closed.'\n          ) {\n            cb(err as Error);\n          } else {\n            cb();\n          }\n        }\n      );\n      lastWriteSize = (data as unknown as Buffer).byteLength;\n    }\n    this[kLastWriteQueueSize] = lastWriteSize;\n  } catch (err) {\n    this.destroy(err as Error);\n  }\n};\n\nSocket.prototype._writev = function (\n  this: Socket,\n  chunks: SocketWriteData,\n  cb: () => void\n): void {\n  this._writeGeneric(true, chunks, '', cb);\n};\n\nSocket.prototype._write = function (\n  this: Socket,\n  data: SocketWriteData,\n  encoding: string,\n  cb: (err?: Error) => void\n): void {\n  this._writeGeneric(false, data, encoding, cb);\n};\n\nSocket.prototype._final = function (\n  this: Socket,\n  cb: (err?: Error) => void\n): void {\n  if (this.connecting) {\n    this.once('connect', () => {\n      this._final(cb);\n    });\n    return;\n  }\n\n  // If there is no writer, then there's really nothing left to do here.\n  if (this._handle == null) {\n    cb();\n    return;\n  }\n\n  this._handle.writer.close().then(\n    (): void => {\n      cb();\n    },\n    (err: unknown): void => {\n      cb(err as Error);\n    }\n  );\n};\n\nSocket.prototype.end = function (\n  this: Socket,\n  // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents\n  data?: string | Uint8Array | NodeJS.BufferEncoding | VoidFunction,\n  encoding?: NodeJS.BufferEncoding | VoidFunction,\n  cb?: VoidFunction\n): Socket {\n  // @ts-expect-error this fails after upgrading to @types/node@22.14\n  Duplex.prototype.end.call(this, data, encoding, cb);\n  return this;\n};\n\n// ======================================================================================\n// Readable side\n\nSocket.prototype.pause = function (this: Socket): Socket {\n  if (this[kBuffer] && !this.connecting && this._handle?.reading) {\n    // If the read loop is already running, setting reading to false\n    // will interrupt it after the current read completes (if any)\n    if (!this.destroyed) {\n      this._handle.reading = false;\n    }\n  }\n  return Duplex.prototype.pause.call(this) as unknown as Socket;\n};\n\nSocket.prototype.resume = function (this: Socket): Socket {\n  if (\n    this[kBuffer] &&\n    !this.connecting &&\n    this._handle &&\n    !this._handle.reading\n  ) {\n    tryReadStart(this);\n  }\n  return Duplex.prototype.resume.call(this) as unknown as Socket;\n};\n\nSocket.prototype.read = function (\n  this: Socket,\n  n: number\n): ReturnType<typeof Duplex.prototype.read> {\n  if (\n    this[kBuffer] &&\n    !this.connecting &&\n    this._handle &&\n    !this._handle.reading\n  ) {\n    tryReadStart(this);\n  }\n\n  return Duplex.prototype.read.call(this, n);\n};\n\nSocket.prototype._read = function (this: Socket, n: number): void {\n  if (this.connecting || !this._handle) {\n    this.once('connect', () => {\n      this._read(n);\n    });\n  } else if (!this._handle.reading) {\n    tryReadStart(this);\n  }\n};\n\n// ======================================================================================\n// Destroy and reset\n\nSocket.prototype._reset = function (this: Socket): Socket {\n  this.resetAndClosing = true;\n  return this.destroy();\n};\n\nSocket.prototype.resetAndDestroy = function (this: Socket): Socket {\n  // In Node.js, the resetAndDestroy method is used to \"[close] the TCP connection by\n  // sending an RST packet and destroy the stream. If this TCP socket is in connecting\n  // status, it will send an RST packet and destroy this TCP socket once it is connected.\n  // Otherwise, it will call socket.destroy with an ERR_SOCKET_CLOSED Error. If this is\n  // not a TCP socket (for example, a pipe), calling this method will immediately throw\n  // an ERR_INVALID_HANDLE_TYPE Error.\" In our implementation we really don't have a way\n  // of ensuring whether or not an RST packet is actually sent so this is largely an\n  // alias for the existing destroy. If the socket is still connecting, it will be\n  // destroyed immediately after the connection is established.\n  if (this._handle) {\n    if (this.connecting) {\n      this.once('connect', () => {\n        this._reset();\n      });\n    } else {\n      this._reset();\n    }\n  } else {\n    this.destroy(new ERR_SOCKET_CLOSED());\n  }\n  return this;\n};\n\nSocket.prototype.destroySoon = function (this: Socket): void {\n  if (this.writable) {\n    this.end();\n  }\n\n  if (this.writableFinished) {\n    this.destroy();\n  } else {\n    this.once('finish', () => {\n      // Do not call this.destroy.bind(this) since user can override it.\n      this.destroy();\n    });\n  }\n};\n\nSocket.prototype._destroy = function (\n  this: Socket,\n  exception: Error,\n  cb: (err?: Error) => void\n): void {\n  this.connecting = false;\n\n  // eslint-disable-next-line @typescript-eslint/no-this-alias\n  for (let s: Socket | null = this; s !== null; s = s._parentWrap) {\n    clearTimeout(s[kTimeout] as unknown as number);\n  }\n\n  if (this._handle != null) {\n    this._handle.socket.close().then(\n      () => {\n        cleanupAfterDestroy(this, cb, exception);\n      },\n      (err: unknown) => {\n        cleanupAfterDestroy(this, cb, (err || exception) as Error);\n      }\n    );\n  } else {\n    cleanupAfterDestroy(this, cb, exception);\n  }\n};\n\n// ======================================================================================\n// Connection\n\n// @ts-expect-error TS2322 Type inconsistencies between types/node\nSocket.prototype.connect = function (\n  this: Socket,\n  ...args: unknown[]\n): Socket | undefined {\n  let normalized;\n  // @ts-expect-error TS7015 Required not to overcomplicate types\n  if (Array.isArray(args[0]) && args[0][normalizedArgsSymbol]) {\n    normalized = args[0];\n  } else {\n    normalized = _normalizeArgs(args);\n  }\n  const options = normalized[0] as TcpSocketConnectOpts & IpcSocketConnectOpts;\n  const cb = normalized[1] as ((err: Error | null) => void) | null;\n\n  if (this.connecting) {\n    throw new ERR_SOCKET_CONNECTING();\n  }\n  if (this._aborted) {\n    if (cb) {\n      cb(new AbortError());\n    } else {\n      throw new AbortError();\n    }\n    return undefined;\n  }\n\n  if (cb !== null) {\n    this.once('connect', cb);\n  }\n\n  if (this._parentWrap?.connecting) {\n    return this;\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  if (options.port === undefined && options.path == null) {\n    throw new ERR_MISSING_ARGS(['options', 'port', 'path']);\n  }\n\n  if (this.write !== Socket.prototype.write) {\n    // eslint-disable-next-line @typescript-eslint/unbound-method\n    this.write = Socket.prototype.write;\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  if (options.path != null) {\n    throw new ERR_INVALID_ARG_VALUE('path', options.path, 'is not supported');\n  }\n\n  this[kBytesRead] = 0;\n  this[kBytesWritten] = 0;\n\n  // This is required for \"reconnection\".\n  // Previous connected handle needs to emit \"close\" event.\n  // This ensures that the previous handle is closed before initializing a new one.\n  if (this._handle) {\n    this._handle.socket.close().then(\n      () => {\n        initializeConnection(this, options);\n      },\n      () => {\n        initializeConnection(this, options);\n      }\n    );\n  } else {\n    initializeConnection(this, options);\n  }\n\n  return this;\n};\n\nSocket.prototype[kReinitializeHandle] = function reinitializeHandle(\n  handle: Socket['_handle']\n): void {\n  this._handle?.socket.close().then(\n    () => {\n      cleanupAfterDestroy(this);\n    },\n    (err: unknown) => {\n      cleanupAfterDestroy(this, null, err as Error);\n    }\n  );\n\n  this._handle = handle;\n  this._undestroy();\n  this._sockname = null;\n};\n\n// ======================================================================================\n// Socket methods that are not no-ops or nominal impls\n\nSocket.prototype.setNoDelay = function (\n  this: Socket,\n  _enable?: boolean\n): Socket {\n  // Ignore this for now.\n  // Cloudflare connect() does not support this.\n  return this;\n};\n\nSocket.prototype.setKeepAlive = function (\n  this: Socket,\n  _enable?: boolean,\n  _initialDelay?: number\n): Socket {\n  // Ignore this for now.\n  // This is used by services like mySQL.\n  // TODO(soon): Investigate supporting this.\n  return this;\n};\n\n// @ts-expect-error TS2322 Intentionally no-op\nSocket.prototype.ref = function (this: Socket): void {\n  // Intentional no-op\n};\n\n// @ts-expect-error TS2322 Intentionally no-op\nSocket.prototype.unref = function (this: Socket): void {\n  // Intentional no-op\n};\n\nObject.defineProperties(Socket.prototype, {\n  _connecting: {\n    // @ts-expect-error TS2353 Required for __proto__\n    __proto__: null,\n    get(this: Socket): boolean {\n      return this.connecting;\n    },\n  },\n  pending: {\n    // @ts-expect-error TS2353 Required for __proto__\n    __proto__: null,\n    get(this: Socket): boolean {\n      return !this._handle || this.connecting;\n    },\n    configurable: true,\n  },\n  readyState: {\n    // @ts-expect-error TS2353 Required for __proto__\n    __proto__: null,\n    get(this: Socket): string {\n      if (this.connecting) {\n        return 'opening';\n      } else if (this.readable && this.writable) {\n        return 'open';\n      } else if (this.readable && !this.writable) {\n        return 'readOnly';\n      } else if (!this.readable && this.writable) {\n        return 'writeOnly';\n      }\n      return 'closed';\n    },\n  },\n  bufferSize: {\n    // @ts-expect-error TS2353 Required for __proto__\n    __proto__: null,\n    get(this: Socket): number | undefined {\n      if (this._handle) {\n        return this.writableLength;\n      }\n      return undefined;\n    },\n  },\n  [kUpdateTimer]: {\n    // @ts-expect-error TS2353 Required for __proto__\n    __proto__: null,\n    get(this: Socket): VoidFunction {\n      // eslint-disable-next-line @typescript-eslint/unbound-method\n      return this._unrefTimer;\n    },\n  },\n  bytesRead: {\n    // @ts-expect-error TS2353 Required for __proto__\n    __proto__: null,\n    configurable: false,\n    enumerable: true,\n    get(this: Socket): number {\n      return this._handle ? this._handle.bytesRead : this[kBytesRead];\n    },\n  },\n  _bytesDispatched: {\n    // @ts-expect-error TS2353 Required for __proto__\n    __proto__: null,\n    configurable: false,\n    enumerable: true,\n    get(this: Socket): number {\n      return this._handle ? this._handle.bytesWritten : this[kBytesWritten];\n    },\n  },\n  bytesWritten: {\n    // @ts-expect-error TS2353 Required for __proto__\n    __proto__: null,\n    configurable: false,\n    enumerable: true,\n    get(this: Socket): number | undefined {\n      let bytes = this._bytesDispatched;\n      const data = this._pendingData as unknown as Buffer | string | null;\n      const encoding = this._pendingEncoding;\n      const writableBuffer = this.writableBuffer;\n\n      if (!writableBuffer) return undefined;\n\n      if (Array.isArray(data)) {\n        // Was a writev, iterate over chunks to get total length\n        for (let i = 0; i < data.length; i++) {\n          const chunk = data[i] as Buffer | null;\n\n          if (chunk == null) {\n            continue;\n          }\n\n          // @ts-expect-error TS2339 allBuffers doesn't exist on type.\n          if (chunk instanceof Buffer || data.allBuffers) bytes += chunk.length;\n          else {\n            bytes += Buffer.byteLength(\n              // @ts-expect-error TS2339 TODO(soon): Use correct type here.\n              chunk.chunk as Buffer,\n              // @ts-expect-error TS2339 TODO(soon): Use correct type here.\n              chunk.encoding as string\n            );\n          }\n        }\n      } else if (data) {\n        // Writes are either a string or a Buffer.\n        if (typeof data !== 'string') {\n          bytes += data.length;\n        } else {\n          bytes += Buffer.byteLength(data, encoding);\n        }\n      } else {\n        const flushed =\n          this._handle != null\n            ? this._handle.bytesWritten\n            : this[kBytesWritten];\n        return this.writableLength + flushed;\n      }\n\n      return bytes;\n    },\n  },\n  remoteAddress: {\n    // @ts-expect-error TS2353 Required for __proto__\n    __proto__: null,\n    configurable: false,\n    enumerable: true,\n    get(this: Socket): unknown {\n      return this._getpeername().address;\n    },\n  },\n  remoteFamily: {\n    // @ts-expect-error TS2353 Required for __proto__\n    __proto__: null,\n    configurable: false,\n    enumerable: true,\n    get(this: Socket): unknown {\n      return this._getpeername().family;\n    },\n  },\n  remotePort: {\n    // @ts-expect-error TS2353 Required for __proto__\n    __proto__: null,\n    configurable: false,\n    enumerable: true,\n    get(this: Socket): unknown {\n      return this._getpeername().port;\n    },\n  },\n  localAddress: {\n    // @ts-expect-error TS2353 Required for __proto__\n    __proto__: null,\n    configurable: false,\n    enumerable: true,\n    get(this: Socket): string | undefined {\n      return this._getsockname().address;\n    },\n  },\n  localPort: {\n    // @ts-expect-error TS2353 Required for __proto__\n    __proto__: null,\n    configurable: false,\n    enumerable: true,\n    get(this: Socket): number | undefined {\n      return this._getsockname().port;\n    },\n  },\n  localFamily: {\n    // @ts-expect-error TS2353 Required for __proto__\n    __proto__: null,\n    configurable: false,\n    enumerable: true,\n    get(this: Socket): string | undefined {\n      return this._getsockname().family;\n    },\n  },\n});\n\n// ======================================================================================\n// Helper/utility methods\n\nfunction cleanupAfterDestroy(\n  socket: Socket,\n  cb?: ((err?: Error) => void) | null,\n  error?: Error\n): void {\n  const isException = error != null;\n  if (socket._handle != null) {\n    socket[kBytesRead] = socket._handle.bytesRead;\n    socket[kBytesWritten] = socket._handle.bytesWritten;\n    socket.resetAndClosing = false;\n  }\n  socket[kLastWriteQueueSize] = 0;\n  socket[kSocketInfo] = null;\n\n  // If there's an error, emit it before the close event\n  if (error != null) {\n    socket.emit('error', error);\n  }\n\n  cb?.(error);\n  socket.emit('close', isException);\n}\n\nfunction initializeConnection(\n  socket: Socket,\n  options: TcpSocketConnectOpts\n): void {\n  const {\n    host = 'localhost',\n    family,\n    hints,\n    autoSelectFamily,\n    autoSelectFamilyAttemptTimeout,\n    lookup,\n    localAddress,\n    localPort,\n  } = options;\n  let { port } = options;\n  if (localAddress && !isIP(localAddress)) {\n    throw new ERR_INVALID_IP_ADDRESS(localAddress);\n  }\n\n  if (localPort) {\n    validateNumber(localPort, 'options.localPort');\n  }\n\n  if (typeof port !== 'number' && typeof port !== 'string') {\n    throw new ERR_INVALID_ARG_TYPE('options.port', ['number', 'string'], port);\n  }\n  validatePort(port);\n  port |= 0;\n\n  if (autoSelectFamily != null) {\n    // We don't support this option.\n    // We shouldn't throw this because services like mongodb depends on it.\n    // TODO(soon): Investigate supporting this.\n    validateBoolean(autoSelectFamily, 'options.autoSelectFamily');\n  }\n\n  if (autoSelectFamilyAttemptTimeout != null) {\n    // We don't support this option.\n    // We shouldn't throw this because services like mongodb depends on it.\n    // TODO(soon): Investigate supporting this.\n    validateInt32(\n      autoSelectFamilyAttemptTimeout,\n      'options.autoSelectFamilyAttemptTimeout',\n      1\n    );\n  }\n\n  socket._unrefTimer();\n  socket.connecting = true;\n\n  const continueConnection = (\n    host: unknown,\n    port: number,\n    family: number | string\n  ): void => {\n    socket[kSocketInfo] = {\n      remoteAddress: {\n        address: host,\n        port,\n        family: family === 4 ? 'IPv4' : family === 6 ? 'IPv6' : undefined,\n      },\n    };\n\n    if (family === 6) {\n      // The host is an IPv6 address. We need to wrap it in square brackets.\n      host = `[${host}]`;\n    }\n\n    // @ts-expect-error TS2540 Unnecessary error due to using @types/node\n    socket.autoSelectFamilyAttemptedAddresses = [`${host}:${port}`];\n\n    socket.emit('connectionAttempt', host, port, addressType);\n\n    socket._host = `${host}`;\n\n    try {\n      const handle = inner.connect(`${host}:${port}`, {\n        allowHalfOpen: socket.allowHalfOpen,\n        // A Node.js socket is always capable of being upgraded to the TLS socket.\n        secureTransport: socket.encrypted ? 'on' : 'starttls',\n        // We are not going to pass the high water-mark here. The outer Node.js\n        // stream will implement the appropriate backpressure for us.\n      });\n\n      // Our version of the socket._handle is necessarily different from Node.js'.\n      // It serves the same purpose but any code that may exist that is depending\n      // on `_handle` being a particular type (which it shouldn't be) will fail.\n      socket._handle = {\n        socket: handle,\n        writer: handle.writable.getWriter(),\n        reader: handle.readable.getReader({ mode: 'byob' }),\n        bytesRead: 0,\n        bytesWritten: 0,\n        reading: false,\n        options: {\n          host: socket._host,\n          port,\n          addressType,\n        },\n      };\n\n      // We need to undestroy the stream to connect to it.\n      socket._undestroy();\n      socket._sockname = null;\n\n      handle.opened.then(onConnectionOpened.bind(socket), (err: unknown) => {\n        socket.emit('connectionAttemptFailed', host, port, addressType, err);\n        socket.destroy(err as Error);\n      });\n\n      handle.closed.then(\n        onConnectionClosed.bind(socket),\n        (error: unknown): void => {\n          // Do not call socket.destroy.bind(socket) since user can override it.\n          socket.destroy(error as Error);\n        }\n      );\n    } catch (err) {\n      socket.destroy(err as Error);\n    }\n  };\n\n  if (lookup != null) {\n    validateFunction(lookup, 'options.lookup');\n  }\n\n  const addressType = isIP(host);\n\n  // The host is not an IP address. That's allowed in our implementation, but let's\n  // see if the user provided a lookup function. If not, we'll skip.\n  if (addressType === 0 && lookup != null) {\n    // Looks like we have a lookup function! Let's call it. The expectation is that\n    // the lookup function will produce a good IP address from the non-IP address\n    // that is given. How that is done is left entirely up to the application code.\n    // The connection attempt will continue once the lookup function invokes the\n    // given callback.\n    const lookupOptions = { family: family || addressType, hints };\n    lookup(\n      host,\n      lookupOptions,\n      (err: Error | null, address: string, family: number | string): void => {\n        socket.emit('lookup', err, address, family, host);\n        if (err) {\n          socket.destroy(err);\n          return;\n        }\n        if (isIP(address) === 0) {\n          throw new ERR_INVALID_IP_ADDRESS(address);\n        }\n        if (\n          family !== 4 &&\n          family !== 6 &&\n          family !== 'IPv4' &&\n          family !== 'IPv6'\n        ) {\n          throw new ERR_INVALID_ARG_VALUE('family', family, 'must be 4 or 6');\n        }\n        continueConnection(address, port, family);\n      }\n    );\n  } else {\n    continueConnection(host, port, addressType);\n  }\n}\n\nexport function onConnectionOpened(this: Socket): void {\n  // Callback may come after call to destroy\n  if (this.destroyed) {\n    return;\n  }\n\n  this.connecting = false;\n  this._sockname = null;\n  this._unrefTimer();\n  this.emit('connect');\n  this.emit('ready');\n  if (!this.isPaused()) {\n    tryReadStart(this);\n  }\n}\n\nexport function onConnectionClosed(this: Socket): void {\n  if (this._handle?.socket.upgraded) {\n    // The socket is being upgraded from insecure to TLS.\n    // No need to handle this particular close event.\n    return;\n  }\n  // eslint-disable-next-line @typescript-eslint/no-this-alias\n  for (let s: Socket | null = this; s !== null; s = s._parentWrap) {\n    clearTimeout(s[kTimeout] as unknown as number);\n  }\n\n  if (!this.destroyed) {\n    // We have to manually trigger an 'end' event because we are using\n    // BYOB buffers with Socket class.\n    this.emit('end');\n  }\n}\n\nasync function startRead(socket: Socket): Promise<void> {\n  if (!socket._handle) return;\n  const reader = socket._handle.reader;\n  try {\n    while (socket._handle.reading === true) {\n      const generatedBuffer = socket[kBufferGen]?.();\n\n      // Let's be extra cautious here and handle nullish values.\n      if (generatedBuffer == null || generatedBuffer.length === 0) {\n        // When reading a static buffer with fixed length, it's highly likely to\n        // read the whole buffer in a single take, which will make the second\n        // operation to read an empty buffer.\n        //\n        // Workerd throws the following exception when reading empty buffers\n        // TypeError: You must call read() on a \"byob\" reader with a positive-sized TypedArray object.\n        // Therefore, let's skip calling read operation and stop reading here.\n        break;\n      }\n\n      // The [kBufferGen] function should always be a function that returns\n      // a Uint8Array we can read into.\n      const { value, done } = await reader.read(generatedBuffer);\n\n      // Make sure the socket was not destroyed while we were waiting.\n      // If it was, we're going to throw away the chunk of data we just\n      // read.\n      if (socket.destroyed) {\n        // Doh! Well, this is awkward. Let's just stop reading and return.\n        // There's really nothing else we should try to do here.\n        break;\n      }\n\n      // Reset the timeout timer since we received data.\n      socket._unrefTimer();\n\n      if (done) {\n        // All done! If allowHalfOpen is true, then this will just end the\n        // readable side of the socket. If allowHalfOpen is false, then this\n        // should allow the current write queue to drain but not allow any\n        // further writes to be queued.\n        socket.push(null);\n        break;\n      }\n\n      // If the byteLength is zero, skip the push.\n      if (value.byteLength === 0) {\n        continue;\n      }\n      socket._handle.bytesRead += value.byteLength;\n\n      // The socket API is expected to produce Buffer instances, not Uint8Arrays\n      const buffer = Buffer.from(\n        value.buffer,\n        value.byteOffset,\n        value.byteLength\n      );\n\n      if (typeof socket[kBufferCb] === 'function') {\n        if (socket[kBufferCb](buffer.byteLength, buffer) === false) {\n          // If the callback returns explicitly false (not falsy) then\n          // we're being asked to stop reading for now.\n          break;\n        }\n        continue;\n      }\n\n      // Because we're pushing the buffer onto the stream we can't use the shared\n      // buffer here or the next read will overwrite it! We need to copy. For the\n      // more efficient version, use onread.\n      if (!socket.push(Buffer.from(buffer))) {\n        // If push returns false, we've hit the high water mark and should stop\n        // reading until the stream requests to start reading again.\n        break;\n      }\n    }\n  } catch (_err) {\n    // Ignore error, and don't log them.\n    // This is mostly triggered for invalid sockets with following errors:\n    // - \"This ReadableStream belongs to an object that is closing.\"\n  } finally {\n    // Disable eslint to match Node.js behavior\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    if (socket._handle != null) {\n      socket._handle.reading = false;\n    }\n  }\n}\n\nexport function tryReadStart(socket: Socket): void {\n  if (socket._handle != null) {\n    socket._handle.reading = true;\n  }\n  startRead(socket).catch((err: unknown) => socket.destroy(err as Error));\n}\n\nfunction writeAfterFIN(\n  this: Socket,\n  chunk: Uint8Array | string,\n  encoding?: NodeJS.BufferEncoding | null | ((err?: Error) => void),\n  cb?: (err?: Error) => void\n): boolean {\n  if (!this.writableEnded) {\n    // @ts-expect-error TS2554 Required due to @types/node\n    return Duplex.prototype.write.call(this, chunk, encoding, cb);\n  }\n\n  if (typeof encoding === 'function') {\n    cb = encoding;\n    encoding = null;\n  }\n\n  const er = new EPIPE();\n\n  if (cb != null && typeof cb === 'function') {\n    queueMicrotask(() => {\n      cb(er);\n    });\n  }\n  this.destroy(er);\n\n  return false;\n}\n\nfunction onReadableStreamEnd(this: Socket): void {\n  if (!this.allowHalfOpen) {\n    this.write = writeAfterFIN;\n  }\n}\n\nexport function getTimerDuration(msecs: unknown, name: string): number {\n  validateNumber(msecs, name);\n  if (msecs < 0 || !Number.isFinite(msecs)) {\n    throw new ERR_OUT_OF_RANGE(name, 'a non-negative finite number', msecs);\n  }\n\n  // Ensure that msecs fits into signed int32\n  if (msecs > TIMEOUT_MAX) {\n    return TIMEOUT_MAX;\n  }\n\n  return msecs;\n}\n\nexport function toNumber(x: unknown): number | false {\n  return (x = Number(x)) >= 0 ? (x as number) : false;\n}\n\nexport function isPipeName(s: unknown): boolean {\n  return typeof s === 'string' && toNumber(s) === false;\n}\n\nexport type NormalizedArgs = [\n  {\n    path?: string;\n    port?: number;\n    host?: string;\n  },\n  ((...args: unknown[]) => void) | null,\n];\n\nexport function _normalizeArgs(args: unknown[]): NormalizedArgs {\n  let arr: NormalizedArgs;\n\n  if (args.length === 0) {\n    arr = [{}, null];\n    // @ts-expect-error TS2554 Required due to @types/node\n    arr[normalizedArgsSymbol] = true;\n    return arr;\n  }\n\n  const arg0 = args[0];\n  let options: {\n    path?: string;\n    port?: number;\n    host?: string;\n  } = {};\n  if (typeof arg0 === 'object' && arg0 !== null) {\n    // (options[...][, cb])\n    options = arg0;\n  } else if (isPipeName(arg0)) {\n    // (path[...][, cb])\n    options.path = arg0 as string;\n  } else {\n    // ([port][, host][...][, cb])\n    options.port = arg0 as number;\n    if (args.length > 1 && typeof args[1] === 'string') {\n      // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\n      options.host = args[1] as string;\n    }\n  }\n\n  const cb = args[args.length - 1];\n  if (typeof cb !== 'function') arr = [options, null];\n  else arr = [options, cb as (...args: unknown[]) => unknown];\n\n  // @ts-expect-error TS2554 Required due to @types/node\n  arr[normalizedArgsSymbol] = true;\n  return arr;\n}\n\nfunction addClientAbortSignalOption(self: Socket, signal: AbortSignal): void {\n  validateAbortSignal(signal, 'options.signal');\n  let disposable: Disposable | undefined;\n\n  function onAbort(): void {\n    disposable?.[Symbol.dispose]();\n    self._aborted = true;\n  }\n\n  if (signal.aborted) {\n    queueMicrotask(onAbort);\n  } else {\n    queueMicrotask(() => {\n      disposable = addAbortListener(signal, onAbort);\n    });\n  }\n}\n\nfunction addAbortListener(\n  signal: AbortSignal | undefined,\n  listener: VoidFunction\n): Disposable {\n  if (signal === undefined) {\n    throw new ERR_INVALID_ARG_TYPE('signal', 'AbortSignal', signal);\n  }\n  validateAbortSignal(signal, 'signal');\n  validateFunction(listener, 'listener');\n\n  let removeEventListener: undefined | (() => void);\n  if (signal.aborted) {\n    queueMicrotask(() => {\n      listener();\n    });\n  } else {\n    signal.addEventListener('abort', listener, { once: true });\n    removeEventListener = (): void => {\n      signal.removeEventListener('abort', listener);\n    };\n  }\n  return {\n    // @ts-expect-error TS2353 Required for __proto__\n    __proto__: null,\n    [Symbol.dispose](): void {\n      removeEventListener?.();\n    },\n  };\n}\n\n// ======================================================================================\n// The rest of the exports\n\nexport function connect(...args: unknown[]): Socket {\n  const normalized = _normalizeArgs(args);\n  const options = normalized[0] as SocketOptions;\n  const socket: Socket = new Socket(options);\n  if (options.timeout) {\n    socket.setTimeout(options.timeout);\n  }\n  if (socket.destroyed) {\n    return socket;\n  }\n  return socket.connect(normalized as unknown as SocketConnectOpts);\n}\n\nexport const createConnection = connect;\n\nexport function createServer(): void {\n  throw new Error('createServer() is not implemented');\n}\n\nexport function getDefaultAutoSelectFamily(): boolean {\n  // This is the only value we support.\n  return false;\n}\n\nexport function setDefaultAutoSelectFamily(val: unknown): void {\n  if (!val) return;\n  throw new ERR_INVALID_ARG_VALUE('val', val);\n}\n\n// We don't actually make use of this. It's here only for compatibility.\n// The value is not used anywhere.\nlet autoSelectFamilyAttemptTimeout: number = 10;\n\nexport function getDefaultAutoSelectFamilyAttemptTimeout(): number {\n  return autoSelectFamilyAttemptTimeout;\n}\n\nexport function setDefaultAutoSelectFamilyAttemptTimeout(val: unknown): void {\n  validateInt32(val, 'val', 1);\n  if (val < 10) val = 10;\n  autoSelectFamilyAttemptTimeout = val as number;\n}\n\nexport function isIP(input: unknown): number {\n  if (isIPv4(input)) return 4;\n  if (isIPv6(input)) return 6;\n  return 0;\n}\n\nexport function isIPv4(input: unknown): boolean {\n  input = typeof input !== 'string' ? `${input}` : input;\n  return IPv4Reg.test(input as string);\n}\n\nexport function isIPv6(input: unknown): boolean {\n  input = typeof input !== 'string' ? `${input}` : input;\n  return IPv6Reg.test(input as string);\n}\n\n// ======================================================================================\n\nexport class SocketAddress implements _SocketAddress {\n  #address: string;\n  #port: number;\n  #family: IPVersion;\n  #flowlabel: number;\n\n  static isSocketAddress(value: unknown): value is SocketAddress {\n    return value instanceof SocketAddress;\n  }\n\n  constructor(options: SocketAddressInitOptions = {}) {\n    validateObject(options, 'options');\n    this.#family = options.family || 'ipv4';\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    if (typeof this.#family?.toLowerCase === 'function')\n      this.#family = this.#family.toLowerCase() as IPVersion;\n    validateOneOf(this.#family, 'options.family', ['ipv4', 'ipv6']);\n\n    const {\n      address = this.#family === 'ipv4' ? '127.0.0.1' : '::',\n      port = 0,\n      flowlabel = 0,\n    } = options;\n\n    validateString(address, 'options.address');\n    const _port = validatePort(port, 'options.port');\n    validateUint32(flowlabel, 'options.flowlabel', false);\n\n    switch (this.#family) {\n      case 'ipv4':\n        if (!isIPv4(address)) {\n          throw new ERR_INVALID_ADDRESS();\n        }\n        break;\n      case 'ipv6':\n        if (!isIPv6(address)) {\n          throw new ERR_INVALID_ADDRESS();\n        }\n        break;\n    }\n\n    // Node.js' implementation is a bit more complicated since it is backed\n    // by a C++ class wrapping an actual socket address structure. We don't\n    // need that here, so we keep things simple.\n\n    this.#address = address;\n    this.#port = _port;\n    this.#flowlabel = flowlabel;\n  }\n\n  get address(): string {\n    return this.#address;\n  }\n\n  get port(): number {\n    return this.#port;\n  }\n\n  get family(): IPVersion {\n    return this.#family;\n  }\n\n  get flowlabel(): number {\n    return this.#flowlabel;\n  }\n\n  [kInspect](depth: number, options: InspectOptions): string | this {\n    if (depth < 0) return this;\n\n    const opts: InspectOptions = {\n      ...options,\n      depth: options.depth == null ? null : options.depth - 1,\n    };\n\n    // @ts-expect-error TS2769 not all the overloads are compatible\n    return `SocketAddress ${inspect(this.toJSON(), opts)}`;\n  }\n\n  toJSON(): {\n    address: string;\n    port: number;\n    family: IPVersion;\n    flowlabel: number;\n  } {\n    return {\n      address: this.address,\n      port: this.port,\n      family: this.family,\n      flowlabel: this.flowlabel,\n    };\n  }\n\n  static parse(input: string): SocketAddress | undefined {\n    validateString(input, 'input');\n    try {\n      const parsed = URL.parse(`http://${input}`);\n      if (parsed == null) {\n        return undefined;\n      }\n      const { hostname: address, port } = parsed;\n      if (address.startsWith('[') && address.endsWith(']')) {\n        return new SocketAddress({\n          address: address.slice(1, -1),\n          // @ts-expect-error TS2362 port will be a string, this converts it\n          port: port | 0,\n          family: 'ipv6',\n        });\n      }\n      // @ts-expect-error TS2362 port will be a string, this converts it\n      return new SocketAddress({ address, port: port | 0 });\n    } catch {\n      // Ignore errors here. Return undefined if the input cannot\n      // be successfully parsed or is not a proper socket address.\n    }\n    return undefined;\n  }\n}\n\n// ======================================================================================\n\n// Note: the bulk of the following BlockList related code was authored by claude...\n// The implementation in Node.js is split between javascript and C++.\n// Here we do the entire implementation as TypeScript simply because\n// we don't need the C++ parts at all.\n\nconst kIpv6Regex = /^::ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)$/i;\nconst kIpv6RangeRegex =\n  /Range: IPv6 ([0-9a-fA-F:]{1,39})-([0-9a-fA-F:]{1,39})/i;\nconst kIpv6AddressRegex = /Address: IPv6 ([0-9a-fA-F:]{1,39})/i;\nconst kIpv6SubnetRegex = /Subnet: IPv6 ([0-9a-fA-F:]{1,39})\\/(\\d{1,3})/i;\nconst kIpv4RangeRegex =\n  /Range: IPv4 (\\d{1,3}(?:\\.\\d{1,3}){3})-(\\d{1,3}(?:\\.\\d{1,3}){3})/;\nconst kIpv4AddressRegex = /Address: IPv4 (\\d{1,3}(?:\\.\\d{1,3}){3})/;\nconst kIpv4SubnetRegex = /Subnet: IPv4 (\\d{1,3}(?:\\.\\d{1,3}){3})\\/(\\d{1,2})/;\n\n// IPv4/IPv6 CIDR network utilities\nfunction parseIPv4(ip: string): FixedLengthArray<number, 4> | undefined {\n  // When the input is not a valid IPv4 address, return an empty array.\n  const parts = ip.split('.');\n  if (parts.length !== 4) return undefined;\n\n  const nums = [0, 0, 0, 0] as FixedLengthArray<number, 4>;\n\n  for (let n = 0; n < parts.length; n++) {\n    const part = parts[n];\n    if (part === undefined) return undefined;\n    const num = parseInt(part, 10);\n    if (isNaN(num) || num < 0 || num > 255) return undefined;\n    nums[n] = num;\n  }\n\n  return nums;\n}\n\nfunction parseIPv6(ip: string): FixedLengthArray<number, 8> | undefined {\n  // Handle IPv4-mapped IPv6 addresses\n  if (ip.includes('.')) {\n    const match = ip.match(kIpv6Regex);\n    if (match != null && match[1]) {\n      const ipv4Parts = parseIPv4(match[1]);\n      if (ipv4Parts !== undefined) {\n        return [\n          0,\n          0,\n          0,\n          0,\n          0,\n          0xffff,\n          (ipv4Parts[0] << 8) | ipv4Parts[1],\n          (ipv4Parts[2] << 8) | ipv4Parts[3],\n        ];\n      }\n    }\n  }\n\n  // Expand :: notation\n  let expanded = ip;\n  if (ip.includes('::')) {\n    const parts = ip.split('::');\n    if (parts.length === 2) {\n      const left = parts[0]?.split(':') ?? [];\n      const right = parts[1]?.split(':') ?? [];\n      const missing = 8 - left.length - right.length;\n      const middle = Array.from({ length: missing }).fill('0');\n      expanded = [...left, ...middle, ...right].join(':');\n    }\n  }\n\n  const parts = expanded.split(':');\n  if (parts.length !== 8) return undefined;\n\n  const nums = parts.map((p) => {\n    const num = parseInt(p || '0', 16);\n    return num >= 0 && num <= 0xffff ? num : -1;\n  }) as FixedLengthArray<number, 8>;\n\n  return nums.includes(-1) ? undefined : nums;\n}\n\nfunction ipv4ToNumber(ip: string): number {\n  const parts = parseIPv4(ip);\n  if (parts === undefined)\n    throw new ERR_INVALID_IP_ADDRESS('Invalid IPv4 address');\n  return (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3];\n}\n\nfunction ipv6ToBigInt(ip: string): bigint {\n  const parts = parseIPv6(ip);\n  if (parts === undefined)\n    throw new ERR_INVALID_IP_ADDRESS('Invalid IPv6 address');\n\n  let result = 0n;\n  for (let i = 0; i < 8; i++) {\n    const part = parts[i];\n    result = (result << 16n) | BigInt(part as number);\n  }\n  return result;\n}\n\nfunction isInIPv4Subnet(ip: string, network: string, prefix: number): boolean {\n  if (prefix < 0 || prefix > 32) return false;\n  if (prefix === 0) return true;\n\n  const ipNum = ipv4ToNumber(ip);\n  const netNum = ipv4ToNumber(network);\n  // Create a subnet mask by shifting all 1s left by (32 - prefix) bits\n  // This creates a mask with 'prefix' number of leading 1s followed by 0s\n  // For example, prefix=24 creates mask 0xffffff00 (255.255.255.0)\n  const mask = (0xffffffff << (32 - prefix)) >>> 0;\n\n  return (ipNum & mask) === (netNum & mask);\n}\n\nfunction isInIPv6Subnet(ip: string, network: string, prefix: number): boolean {\n  if (prefix < 0 || prefix > 128) return false;\n  if (prefix === 0) return true;\n\n  const ipBig = ipv6ToBigInt(ip);\n  const netBig = ipv6ToBigInt(network);\n\n  // Create a 128-bit subnet mask for IPv6 by shifting all 1s left by (128 - prefix) bits\n  // This creates a mask with 'prefix' number of leading 1s followed by 0s\n  // The AND with 0xfff...f ensures we stay within 128 bits\n  // For example, prefix=64 creates a mask with 64 leading 1s and 64 trailing 0s\n  const mask =\n    (0xffffffffffffffffffffffffffffffffn << BigInt(128 - prefix)) &\n    0xffffffffffffffffffffffffffffffffn;\n\n  return (ipBig & mask) === (netBig & mask);\n}\n\nfunction compareIPv4(a: string, b: string): number {\n  const aNum = ipv4ToNumber(a);\n  const bNum = ipv4ToNumber(b);\n  return aNum - bNum;\n}\n\nfunction compareIPv6(a: string, b: string): number {\n  const aBig = ipv6ToBigInt(a);\n  const bBig = ipv6ToBigInt(b);\n  return aBig < bBig ? -1 : aBig > bBig ? 1 : 0;\n}\n\nfunction formatIPFamily(family: 'ipv4' | 'ipv6'): 'IPv4' | 'IPv6' {\n  return family === 'ipv4' ? 'IPv4' : 'IPv6';\n}\n\ninterface BlockListRule {\n  type: 'address' | 'range' | 'subnet';\n  family: 'ipv4' | 'ipv6';\n  toString(): string;\n  check(address: string, family: 'ipv4' | 'ipv6'): boolean;\n}\n\nclass AddressRule implements BlockListRule {\n  type = 'address' as const;\n  address: string;\n  family: 'ipv4' | 'ipv6';\n\n  constructor(address: string, family: 'ipv4' | 'ipv6') {\n    this.address = address;\n    this.family = family;\n  }\n\n  toString(): string {\n    return `Address: ${formatIPFamily(this.family)} ${this.address}`;\n  }\n\n  check(address: string, family: 'ipv4' | 'ipv6'): boolean {\n    if (this.family !== family) {\n      // Handle IPv4-mapped IPv6 addresses\n      if (\n        this.family === 'ipv4' &&\n        family === 'ipv6' &&\n        address.startsWith('::ffff:')\n      ) {\n        const ipv4Part = address.substring(7);\n        return this.address === ipv4Part;\n      }\n      return false;\n    }\n    return this.address === address;\n  }\n}\n\nclass RangeRule implements BlockListRule {\n  type = 'range' as const;\n  start: string;\n  end: string;\n  family: 'ipv4' | 'ipv6';\n\n  constructor(start: string, end: string, family: 'ipv4' | 'ipv6') {\n    this.start = start;\n    this.end = end;\n    this.family = family;\n  }\n\n  toString(): string {\n    return `Range: ${formatIPFamily(this.family)} ${this.start}-${this.end}`;\n  }\n\n  check(address: string, family: 'ipv4' | 'ipv6'): boolean {\n    if (this.family !== family) {\n      // Handle IPv4-mapped IPv6 for IPv4 ranges\n      if (\n        this.family === 'ipv4' &&\n        family === 'ipv6' &&\n        address.startsWith('::ffff:')\n      ) {\n        const ipv4Part = address.substring(7);\n        const cmpStart = compareIPv4(ipv4Part, this.start);\n        const cmpEnd = compareIPv4(ipv4Part, this.end);\n        return cmpStart >= 0 && cmpEnd <= 0;\n      }\n      return false;\n    }\n\n    if (family === 'ipv4') {\n      const cmpStart = compareIPv4(address, this.start);\n      const cmpEnd = compareIPv4(address, this.end);\n      return cmpStart >= 0 && cmpEnd <= 0;\n    } else {\n      const cmpStart = compareIPv6(address, this.start);\n      const cmpEnd = compareIPv6(address, this.end);\n      return cmpStart >= 0 && cmpEnd <= 0;\n    }\n  }\n}\n\nclass SubnetRule implements BlockListRule {\n  type = 'subnet' as const;\n  network: string;\n  prefix: number;\n  family: 'ipv4' | 'ipv6';\n\n  constructor(network: string, prefix: number, family: 'ipv4' | 'ipv6') {\n    this.network = network;\n    this.prefix = prefix;\n    this.family = family;\n  }\n\n  toString(): string {\n    return `Subnet: ${formatIPFamily(this.family)} ${this.network}/${this.prefix}`;\n  }\n\n  check(address: string, family: 'ipv4' | 'ipv6'): boolean {\n    if (this.family !== family) {\n      // Handle IPv4-mapped IPv6 for IPv4 subnets\n      if (\n        this.family === 'ipv4' &&\n        family === 'ipv6' &&\n        address.startsWith('::ffff:')\n      ) {\n        const ipv4Part = address.substring(7);\n        return isInIPv4Subnet(ipv4Part, this.network, this.prefix);\n      }\n      return false;\n    }\n\n    if (family === 'ipv4') {\n      return isInIPv4Subnet(address, this.network, this.prefix);\n    } else {\n      return isInIPv6Subnet(address, this.network, this.prefix);\n    }\n  }\n}\n\nexport class BlockList {\n  #rules: BlockListRule[] = [];\n\n  static isBlockList(value: unknown): value is BlockList {\n    return value instanceof BlockList;\n  }\n\n  addAddress(\n    address: string | SocketAddress,\n    family: 'ipv4' | 'ipv6' = 'ipv4'\n  ): void {\n    if (SocketAddress.isSocketAddress(address)) {\n      this.#rules.push(new AddressRule(address.address, address.family));\n    } else {\n      validateString(address, 'address');\n      validateOneOf(family, 'family', ['ipv4', 'ipv6']);\n\n      // Validate the address format\n      if (family === 'ipv4' && !isIPv4(address)) {\n        throw new ERR_INVALID_IP_ADDRESS('Invalid IPv4 address');\n      }\n      if (family === 'ipv6' && !isIPv6(address)) {\n        throw new ERR_INVALID_IP_ADDRESS('Invalid IPv6 address');\n      }\n\n      this.#rules.push(new AddressRule(address, family));\n    }\n  }\n\n  addRange(\n    start: string | SocketAddress,\n    end: string | SocketAddress,\n    family: 'ipv4' | 'ipv6' = 'ipv4'\n  ): void {\n    let startAddr: string;\n    let endAddr: string;\n    let addrFamily: 'ipv4' | 'ipv6';\n\n    if (SocketAddress.isSocketAddress(start)) {\n      startAddr = start.address;\n      addrFamily = start.family;\n    } else {\n      validateString(start, 'start');\n      validateOneOf(family, 'family', ['ipv4', 'ipv6']);\n      startAddr = start;\n      addrFamily = family;\n    }\n\n    if (SocketAddress.isSocketAddress(end)) {\n      endAddr = end.address;\n      if (SocketAddress.isSocketAddress(start) && end.family !== addrFamily) {\n        throw new ERR_INVALID_ARG_VALUE(\n          'end',\n          end,\n          'must be same family as start'\n        );\n      }\n    } else {\n      validateString(end, 'end');\n      endAddr = end;\n    }\n\n    // Validate addresses\n    if (addrFamily === 'ipv4') {\n      if (!isIPv4(startAddr) || !isIPv4(endAddr)) {\n        throw new ERR_INVALID_IP_ADDRESS('Invalid IPv4 address');\n      }\n      if (compareIPv4(startAddr, endAddr) > 0) {\n        throw new ERR_INVALID_ARG_VALUE(\n          'start',\n          startAddr,\n          'must come before end'\n        );\n      }\n    } else {\n      if (!isIPv6(startAddr) || !isIPv6(endAddr)) {\n        throw new ERR_INVALID_IP_ADDRESS('Invalid IPv6 address');\n      }\n      if (compareIPv6(startAddr, endAddr) > 0) {\n        throw new ERR_INVALID_ARG_VALUE(\n          'start',\n          startAddr,\n          'must come before end'\n        );\n      }\n    }\n\n    this.#rules.push(new RangeRule(startAddr, endAddr, addrFamily));\n  }\n\n  addSubnet(\n    network: string | SocketAddress,\n    prefix: number,\n    family: 'ipv4' | 'ipv6' = 'ipv4'\n  ): void {\n    let networkAddr: string;\n    let addrFamily: 'ipv4' | 'ipv6';\n\n    if (SocketAddress.isSocketAddress(network)) {\n      networkAddr = network.address;\n      addrFamily = network.family;\n    } else {\n      validateString(network, 'network');\n      validateOneOf(family, 'family', ['ipv4', 'ipv6']);\n      networkAddr = network;\n      addrFamily = family;\n    }\n\n    validateInt32(prefix, 'prefix');\n\n    // Validate prefix range\n    if (addrFamily === 'ipv4') {\n      if (prefix < 0 || prefix > 32) {\n        throw new ERR_OUT_OF_RANGE('prefix', 'between 0 and 32', prefix);\n      }\n      if (!isIPv4(networkAddr)) {\n        throw new ERR_INVALID_IP_ADDRESS('Invalid IPv4 address');\n      }\n    } else {\n      if (prefix < 0 || prefix > 128) {\n        throw new ERR_OUT_OF_RANGE('prefix', 'between 0 and 128', prefix);\n      }\n      if (!isIPv6(networkAddr)) {\n        throw new ERR_INVALID_IP_ADDRESS('Invalid IPv6 address');\n      }\n    }\n\n    this.#rules.push(new SubnetRule(networkAddr, prefix, addrFamily));\n  }\n\n  check(\n    address: string | SocketAddress,\n    family: 'ipv4' | 'ipv6' = 'ipv4'\n  ): boolean {\n    let checkAddr: string;\n    let checkFamily: 'ipv4' | 'ipv6';\n\n    if (SocketAddress.isSocketAddress(address)) {\n      checkAddr = address.address;\n      checkFamily = address.family;\n    } else {\n      validateString(address, 'address');\n      validateOneOf(family, 'family', ['ipv4', 'ipv6']);\n      checkAddr = address;\n      checkFamily = family.toLowerCase() as 'ipv4' | 'ipv6';\n    }\n\n    // Validate address format\n    try {\n      if (checkFamily === 'ipv4' && !isIPv4(checkAddr)) {\n        return false;\n      }\n      if (checkFamily === 'ipv6' && !isIPv6(checkAddr)) {\n        return false;\n      }\n    } catch {\n      return false;\n    }\n\n    // Check against all rules\n    for (const rule of this.#rules) {\n      if (rule.check(checkAddr, checkFamily)) {\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  get rules(): string[] {\n    return this.#rules.map((rule) => rule.toString());\n  }\n\n  [kInspect](depth: number, options: InspectOptions): string {\n    if (depth < 0) return '[BlockList]';\n\n    const opts: InspectOptions = {\n      ...options,\n      depth: options.depth == null ? null : options.depth - 1,\n    };\n\n    // @ts-expect-error TS2769 not all the overloads are compatible\n    return `BlockList {\\n  rules: ${inspect(this.rules, opts)}\\n}`;\n  }\n\n  toJSON(): string[] {\n    return this.rules;\n  }\n\n  fromJSON(data: string | string[]): void {\n    let rules: string[];\n\n    if (typeof data === 'string') {\n      try {\n        rules = JSON.parse(data) as string[];\n      } catch {\n        throw new ERR_INVALID_ARG_VALUE('data', data, 'must be valid JSON');\n      }\n    } else {\n      rules = data;\n    }\n\n    validateArray(rules, 'data');\n\n    for (const item of data) {\n      if (item.includes('IPv4')) {\n        // IPv4 subnet pattern\n        const subnetMatch = item.match(kIpv4SubnetRegex);\n        if (subnetMatch) {\n          const [, network, prefix] = subnetMatch;\n          if (network === undefined || prefix === undefined) {\n            // Skip the rule if parsing failed.\n            continue;\n          }\n          this.addSubnet(network, parseInt(prefix, 10));\n          continue;\n        }\n\n        // IPv4 address pattern\n        const addressMatch = item.match(kIpv4AddressRegex);\n\n        if (addressMatch) {\n          const [, address] = addressMatch;\n          if (address === undefined) {\n            // Skip the rule if parsing failed.\n            continue;\n          }\n          this.addAddress(address);\n          continue;\n        }\n\n        // IPv4 range pattern\n        const rangeMatch = item.match(kIpv4RangeRegex);\n        if (rangeMatch) {\n          const [, start, end] = rangeMatch;\n          if (start === undefined || end === undefined) {\n            // Skip the rule if parsing failed.\n            continue;\n          }\n          this.addRange(start, end);\n          continue;\n        }\n      }\n\n      if (item.includes('IPv6')) {\n        // IPv6 subnet pattern\n        const ipv6SubnetMatch = item.match(kIpv6SubnetRegex);\n        if (ipv6SubnetMatch) {\n          const [, network, prefix] = ipv6SubnetMatch;\n          if (network === undefined || prefix === undefined) {\n            // Skip the rule if parsing failed.\n            continue;\n          }\n          this.addSubnet(network, parseInt(prefix, 10), 'ipv6');\n          continue;\n        }\n\n        // IPv6 address pattern\n        const ipv6AddressMatch = item.match(kIpv6AddressRegex);\n        if (ipv6AddressMatch) {\n          const [, address] = ipv6AddressMatch;\n          if (address === undefined) {\n            // Skip the rule if parsing failed.\n            continue;\n          }\n          this.addAddress(address, 'ipv6');\n          continue;\n        }\n\n        // IPv6 range pattern\n        const ipv6RangeMatch = item.match(kIpv6RangeRegex);\n        if (ipv6RangeMatch) {\n          const [, start, end] = ipv6RangeMatch;\n          if (start === undefined || end === undefined) {\n            // Skip the rule if parsing failed.\n            continue;\n          }\n          this.addRange(start, end, 'ipv6');\n          continue;\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/node/internal/internal_path.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport {\n  CHAR_DOT,\n  CHAR_FORWARD_SLASH,\n  CHAR_COLON,\n  CHAR_UPPERCASE_A,\n  CHAR_UPPERCASE_Z,\n  CHAR_LOWERCASE_A,\n  CHAR_LOWERCASE_Z,\n  CHAR_BACKWARD_SLASH,\n  CHAR_QUESTION_MARK,\n} from 'node-internal:constants';\n\nimport { validateObject, validateString } from 'node-internal:validators';\n\nfunction isPathSeparator(code: number): boolean {\n  return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;\n}\n\nfunction isPosixPathSeparator(code: number): boolean {\n  return code === CHAR_FORWARD_SLASH;\n}\n\nfunction isWindowsDeviceRoot(code: number): boolean {\n  return (\n    (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) ||\n    (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z)\n  );\n}\n\n// Resolves . and .. elements in a path with directory names\nfunction normalizeString(\n  path: string,\n  allowAboveRoot: boolean,\n  separator: string,\n  isPathSeparator: (code: number) => boolean\n): string {\n  let res = '';\n  let lastSegmentLength = 0;\n  let lastSlash = -1;\n  let dots = 0;\n  let code = 0;\n  for (let i = 0; i <= path.length; ++i) {\n    if (i < path.length) code = path.charCodeAt(i);\n    else if (isPathSeparator(code)) break;\n    else code = CHAR_FORWARD_SLASH;\n\n    if (isPathSeparator(code)) {\n      if (lastSlash === i - 1 || dots === 1) {\n        // NOOP\n      } else if (dots === 2) {\n        if (\n          res.length < 2 ||\n          lastSegmentLength !== 2 ||\n          res.charCodeAt(res.length - 1) !== CHAR_DOT ||\n          res.charCodeAt(res.length - 2) !== CHAR_DOT\n        ) {\n          if (res.length > 2) {\n            const lastSlashIndex = res.lastIndexOf(separator);\n            if (lastSlashIndex === -1) {\n              res = '';\n              lastSegmentLength = 0;\n            } else {\n              res = res.slice(0, lastSlashIndex);\n              lastSegmentLength = res.length - 1 - res.lastIndexOf(separator);\n            }\n            lastSlash = i;\n            dots = 0;\n            continue;\n          } else if (res.length !== 0) {\n            res = '';\n            lastSegmentLength = 0;\n            lastSlash = i;\n            dots = 0;\n            continue;\n          }\n        }\n        if (allowAboveRoot) {\n          res += res.length > 0 ? `${separator}..` : '..';\n          lastSegmentLength = 2;\n        }\n      } else {\n        if (res.length > 0)\n          res += `${separator}${path.slice(lastSlash + 1, i)}`;\n        else res = path.slice(lastSlash + 1, i);\n        lastSegmentLength = i - lastSlash - 1;\n      }\n      lastSlash = i;\n      dots = 0;\n    } else if (code === CHAR_DOT && dots !== -1) {\n      ++dots;\n    } else {\n      dots = -1;\n    }\n  }\n  return res;\n}\n\nfunction formatExt(ext: string): string {\n  return ext ? `${ext[0] === '.' ? '' : '.'}${ext}` : '';\n}\n\n/**\n * @param {string} sep\n * @param {{\n *  dir?: string;\n *  root?: string;\n *  base?: string;\n *  name?: string;\n *  ext?: string;\n *  }} pathObject\n * @returns {string}\n */\n\ntype PathObject = {\n  dir?: string;\n  root?: string;\n  base?: string;\n  name?: string;\n  ext?: string;\n};\n\nfunction _format(sep: string, pathObject: PathObject): string {\n  validateObject(pathObject, 'pathObject');\n  const dir = pathObject.dir || pathObject.root;\n  const base =\n    pathObject.base ||\n    `${pathObject.name || ''}${formatExt(pathObject.ext as string)}`;\n  if (!dir) {\n    return base;\n  }\n  return dir === pathObject.root ? `${dir}${base}` : `${dir}${sep}${base}`;\n}\n\nconst win32 = {\n  resolve(...args: string[]): string {\n    let resolvedDevice = '';\n    let resolvedTail = '';\n    let resolvedAbsolute = false;\n\n    for (let i = args.length - 1; i >= -1; i--) {\n      let path;\n      if (i >= 0) {\n        path = args[i];\n        validateString(path, `paths[${i}]`);\n\n        // Skip empty entries\n        if (path.length === 0) {\n          continue;\n        }\n      } else if (resolvedDevice.length === 0) {\n        path = '/';\n      } else {\n        // Windows has the concept of drive-specific current working\n        // directories. If we've resolved a drive letter but not yet an\n        // absolute path, get cwd for that drive, or the process cwd if\n        // the drive cwd is not available. We're sure the device is not\n        // a UNC path at this points, because UNC paths are always absolute.\n        path = '/';\n\n        // Verify that a cwd was found and that it actually points\n        // to our drive. If not, default to the drive's root.\n        if (\n          path.slice(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() &&\n          path.charCodeAt(2) === CHAR_BACKWARD_SLASH\n        ) {\n          path = `${resolvedDevice}\\\\`;\n        }\n      }\n\n      const len = path.length;\n      let rootEnd = 0;\n      let device = '';\n      let isAbsolute = false;\n      const code = path.charCodeAt(0);\n\n      // Try to match a root\n      if (len === 1) {\n        if (isPathSeparator(code)) {\n          // `path` contains just a path separator\n          rootEnd = 1;\n          isAbsolute = true;\n        }\n      } else if (isPathSeparator(code)) {\n        // Possible UNC root\n\n        // If we started with a separator, we know we at least have an\n        // absolute path of some kind (UNC or otherwise)\n        isAbsolute = true;\n\n        if (isPathSeparator(path.charCodeAt(1))) {\n          // Matched double path separator at beginning\n          let j = 2;\n          let last = j;\n          // Match 1 or more non-path separators\n          while (j < len && !isPathSeparator(path.charCodeAt(j))) {\n            j++;\n          }\n          if (j < len && j !== last) {\n            const firstPart = path.slice(last, j);\n            // Matched!\n            last = j;\n            // Match 1 or more path separators\n            while (j < len && isPathSeparator(path.charCodeAt(j))) {\n              j++;\n            }\n            if (j < len && j !== last) {\n              // Matched!\n              last = j;\n              // Match 1 or more non-path separators\n              while (j < len && !isPathSeparator(path.charCodeAt(j))) {\n                j++;\n              }\n              if (j === len || j !== last) {\n                // We matched a UNC root\n                device = `\\\\\\\\${firstPart}\\\\${path.slice(last, j)}`;\n                rootEnd = j;\n              }\n            }\n          }\n        } else {\n          rootEnd = 1;\n        }\n      } else if (\n        isWindowsDeviceRoot(code) &&\n        path.charCodeAt(1) === CHAR_COLON\n      ) {\n        // Possible device root\n        device = path.slice(0, 2);\n        rootEnd = 2;\n        if (len > 2 && isPathSeparator(path.charCodeAt(2))) {\n          // Treat separator following drive name as an absolute path\n          // indicator\n          isAbsolute = true;\n          rootEnd = 3;\n        }\n      }\n\n      if (device.length > 0) {\n        if (resolvedDevice.length > 0) {\n          if (device.toLowerCase() !== resolvedDevice.toLowerCase())\n            // This path points to another device so it is not applicable\n            continue;\n        } else {\n          resolvedDevice = device;\n        }\n      }\n\n      if (resolvedAbsolute) {\n        if (resolvedDevice.length > 0) break;\n      } else {\n        resolvedTail = `${path.slice(rootEnd)}\\\\${resolvedTail}`;\n        resolvedAbsolute = isAbsolute;\n        if (isAbsolute && resolvedDevice.length > 0) {\n          break;\n        }\n      }\n    }\n\n    // At this point the path should be resolved to a full absolute path,\n    // but handle relative paths to be safe (might happen when process.cwd()\n    // fails)\n\n    // Normalize the tail path\n    resolvedTail = normalizeString(\n      resolvedTail,\n      !resolvedAbsolute,\n      '\\\\',\n      isPathSeparator\n    );\n\n    return resolvedAbsolute\n      ? `${resolvedDevice}\\\\${resolvedTail}`\n      : `${resolvedDevice}${resolvedTail}` || '.';\n  },\n\n  normalize(path: string): string {\n    validateString(path, 'path');\n    const len = path.length;\n    if (len === 0) return '.';\n    let rootEnd = 0;\n    let device;\n    let isAbsolute = false;\n    const code = path.charCodeAt(0);\n\n    // Try to match a root\n    if (len === 1) {\n      // `path` contains just a single char, exit early to avoid\n      // unnecessary work\n      return isPosixPathSeparator(code) ? '\\\\' : path;\n    }\n    if (isPathSeparator(code)) {\n      // Possible UNC root\n\n      // If we started with a separator, we know we at least have an absolute\n      // path of some kind (UNC or otherwise)\n      isAbsolute = true;\n\n      if (isPathSeparator(path.charCodeAt(1))) {\n        // Matched double path separator at beginning\n        let j = 2;\n        let last = j;\n        // Match 1 or more non-path separators\n        while (j < len && !isPathSeparator(path.charCodeAt(j))) {\n          j++;\n        }\n        if (j < len && j !== last) {\n          const firstPart = path.slice(last, j);\n          // Matched!\n          last = j;\n          // Match 1 or more path separators\n          while (j < len && isPathSeparator(path.charCodeAt(j))) {\n            j++;\n          }\n          if (j < len && j !== last) {\n            // Matched!\n            last = j;\n            // Match 1 or more non-path separators\n            while (j < len && !isPathSeparator(path.charCodeAt(j))) {\n              j++;\n            }\n            if (j === len) {\n              // We matched a UNC root only\n              // Return the normalized version of the UNC root since there\n              // is nothing left to process\n              return `\\\\\\\\${firstPart}\\\\${path.slice(last)}\\\\`;\n            }\n            if (j !== last) {\n              // We matched a UNC root with leftovers\n              device = `\\\\\\\\${firstPart}\\\\${path.slice(last, j)}`;\n              rootEnd = j;\n            }\n          }\n        }\n      } else {\n        rootEnd = 1;\n      }\n    } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {\n      // Possible device root\n      device = path.slice(0, 2);\n      rootEnd = 2;\n      if (len > 2 && isPathSeparator(path.charCodeAt(2))) {\n        // Treat separator following drive name as an absolute path\n        // indicator\n        isAbsolute = true;\n        rootEnd = 3;\n      }\n    }\n\n    let tail =\n      rootEnd < len\n        ? normalizeString(\n            path.slice(rootEnd),\n            !isAbsolute,\n            '\\\\',\n            isPathSeparator\n          )\n        : '';\n    if (tail.length === 0 && !isAbsolute) tail = '.';\n    if (tail.length > 0 && isPathSeparator(path.charCodeAt(len - 1)))\n      tail += '\\\\';\n    if (device === undefined) {\n      return isAbsolute ? `\\\\${tail}` : tail;\n    }\n    return isAbsolute ? `${device}\\\\${tail}` : `${device}${tail}`;\n  },\n\n  isAbsolute(path: string): boolean {\n    validateString(path, 'path');\n    const len = path.length;\n    if (len === 0) return false;\n\n    const code = path.charCodeAt(0);\n    return (\n      isPathSeparator(code) ||\n      // Possible device root\n      (len > 2 &&\n        isWindowsDeviceRoot(code) &&\n        path.charCodeAt(1) === CHAR_COLON &&\n        isPathSeparator(path.charCodeAt(2)))\n    );\n  },\n\n  join(...args: string[]): string {\n    if (args.length === 0) return '.';\n\n    let joined: string | undefined;\n    let firstPart: string | undefined;\n    for (let i = 0; i < args.length; ++i) {\n      const arg = args[i];\n      validateString(arg, 'path');\n      if (arg.length > 0) {\n        if (joined === undefined) {\n          joined = arg;\n          firstPart = arg;\n        } else joined += `\\\\${arg}`;\n      }\n    }\n\n    if (joined === undefined || firstPart === undefined) return '.';\n\n    // Make sure that the joined path doesn't start with two slashes, because\n    // normalize() will mistake it for a UNC path then.\n    //\n    // This step is skipped when it is very clear that the user actually\n    // intended to point at a UNC path. This is assumed when the first\n    // non-empty string arguments starts with exactly two slashes followed by\n    // at least one more non-slash character.\n    //\n    // Note that for normalize() to treat a path as a UNC path it needs to\n    // have at least 2 components, so we don't filter for that here.\n    // This means that the user can use join to construct UNC paths from\n    // a server name and a share name; for example:\n    //   path.join('//server', 'share') -> '\\\\\\\\server\\\\share\\\\')\n    let needsReplace = true;\n    let slashCount = 0;\n    if (isPathSeparator(firstPart.charCodeAt(0))) {\n      ++slashCount;\n      const firstLen = firstPart.length;\n      if (firstLen > 1 && isPathSeparator(firstPart.charCodeAt(1))) {\n        ++slashCount;\n        if (firstLen > 2) {\n          if (isPathSeparator(firstPart.charCodeAt(2))) ++slashCount;\n          else {\n            // We matched a UNC path in the first part\n            needsReplace = false;\n          }\n        }\n      }\n    }\n    if (needsReplace) {\n      // Find any more consecutive slashes we need to replace\n      while (\n        slashCount < joined.length &&\n        isPathSeparator(joined.charCodeAt(slashCount))\n      ) {\n        slashCount++;\n      }\n\n      // Replace the slashes if needed\n      if (slashCount >= 2) joined = `\\\\${joined.slice(slashCount)}`;\n    }\n\n    return win32.normalize(joined);\n  },\n\n  relative(from: string, to: string): string {\n    validateString(from, 'from');\n    validateString(to, 'to');\n\n    if (from === to) return '';\n\n    const fromOrig = win32.resolve(from);\n    const toOrig = win32.resolve(to);\n\n    if (fromOrig === toOrig) return '';\n\n    from = fromOrig.toLowerCase();\n    to = toOrig.toLowerCase();\n\n    if (from === to) return '';\n\n    if (fromOrig.length !== from.length || toOrig.length !== to.length) {\n      const fromSplit = fromOrig.split('\\\\');\n      const toSplit = toOrig.split('\\\\');\n      if (fromSplit[fromSplit.length - 1] === '') {\n        fromSplit.pop();\n      }\n      if (toSplit[toSplit.length - 1] === '') {\n        toSplit.pop();\n      }\n\n      const fromLen = fromSplit.length;\n      const toLen = toSplit.length;\n      const length = fromLen < toLen ? fromLen : toLen;\n\n      let i;\n      for (i = 0; i < length; i++) {\n        if (fromSplit[i]?.toLowerCase() !== toSplit[i]?.toLowerCase()) {\n          break;\n        }\n      }\n\n      if (i === 0) {\n        return toOrig;\n      } else if (i === length) {\n        if (toLen > length) {\n          return toSplit.slice(i).join('\\\\');\n        }\n        if (fromLen > length) {\n          return '..\\\\'.repeat(fromLen - 1 - i) + '..';\n        }\n        return '';\n      }\n\n      return '..\\\\'.repeat(fromLen - i) + toSplit.slice(i).join('\\\\');\n    }\n\n    // Trim any leading backslashes\n    let fromStart = 0;\n    while (\n      fromStart < from.length &&\n      from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH\n    ) {\n      fromStart++;\n    }\n    // Trim trailing backslashes (applicable to UNC paths only)\n    let fromEnd = from.length;\n    while (\n      fromEnd - 1 > fromStart &&\n      from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH\n    ) {\n      fromEnd--;\n    }\n    const fromLen = fromEnd - fromStart;\n\n    // Trim any leading backslashes\n    let toStart = 0;\n    while (\n      toStart < to.length &&\n      to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH\n    ) {\n      toStart++;\n    }\n    // Trim trailing backslashes (applicable to UNC paths only)\n    let toEnd = to.length;\n    while (\n      toEnd - 1 > toStart &&\n      to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH\n    ) {\n      toEnd--;\n    }\n    const toLen = toEnd - toStart;\n\n    // Compare paths to find the longest common path from root\n    const length = fromLen < toLen ? fromLen : toLen;\n    let lastCommonSep = -1;\n    let i = 0;\n    for (; i < length; i++) {\n      const fromCode = from.charCodeAt(fromStart + i);\n      if (fromCode !== to.charCodeAt(toStart + i)) break;\n      else if (fromCode === CHAR_BACKWARD_SLASH) lastCommonSep = i;\n    }\n\n    // We found a mismatch before the first common path separator was seen, so\n    // return the original `to`.\n    if (i !== length) {\n      if (lastCommonSep === -1) return toOrig;\n    } else {\n      if (toLen > length) {\n        if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) {\n          // We get here if `from` is the exact base path for `to`.\n          // For example: from='C:\\\\foo\\\\bar'; to='C:\\\\foo\\\\bar\\\\baz'\n          return toOrig.slice(toStart + i + 1);\n        }\n        if (i === 2) {\n          // We get here if `from` is the device root.\n          // For example: from='C:\\\\'; to='C:\\\\foo'\n          return toOrig.slice(toStart + i);\n        }\n      }\n      if (fromLen > length) {\n        if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) {\n          // We get here if `to` is the exact base path for `from`.\n          // For example: from='C:\\\\foo\\\\bar'; to='C:\\\\foo'\n          lastCommonSep = i;\n        } else if (i === 2) {\n          // We get here if `to` is the device root.\n          // For example: from='C:\\\\foo\\\\bar'; to='C:\\\\'\n          lastCommonSep = 3;\n        }\n      }\n      if (lastCommonSep === -1) lastCommonSep = 0;\n    }\n\n    let out = '';\n    // Generate the relative path based on the path difference between `to` and\n    // `from`\n    for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {\n      if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) {\n        out += out.length === 0 ? '..' : '\\\\..';\n      }\n    }\n\n    toStart += lastCommonSep;\n\n    // Lastly, append the rest of the destination (`to`) path that comes after\n    // the common path parts\n    if (out.length > 0) return `${out}${toOrig.slice(toStart, toEnd)}`;\n\n    if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) ++toStart;\n    return toOrig.slice(toStart, toEnd);\n  },\n\n  toNamespacedPath(path: string): string {\n    // Note: this will *probably* throw somewhere.\n    if (typeof path !== 'string' || path.length === 0) return path;\n\n    const resolvedPath = win32.resolve(path);\n\n    if (resolvedPath.length <= 2) return path;\n\n    if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) {\n      // Possible UNC root\n      if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) {\n        const code = resolvedPath.charCodeAt(2);\n        if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) {\n          // Matched non-long UNC root, convert the path to a long UNC path\n          return `\\\\\\\\?\\\\UNC\\\\${resolvedPath.slice(2)}`;\n        }\n      }\n    } else if (\n      isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) &&\n      resolvedPath.charCodeAt(1) === CHAR_COLON &&\n      resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH\n    ) {\n      // Matched device root, convert the path to a long UNC path\n      return `\\\\\\\\?\\\\${resolvedPath}`;\n    }\n\n    return resolvedPath;\n  },\n\n  dirname(path: string): string {\n    validateString(path, 'path');\n    const len = path.length;\n    if (len === 0) return '.';\n    let rootEnd = -1;\n    let offset = 0;\n    const code = path.charCodeAt(0);\n\n    if (len === 1) {\n      // `path` contains just a path separator, exit early to avoid\n      // unnecessary work or a dot.\n      return isPathSeparator(code) ? path : '.';\n    }\n\n    // Try to match a root\n    if (isPathSeparator(code)) {\n      // Possible UNC root\n\n      rootEnd = offset = 1;\n\n      if (isPathSeparator(path.charCodeAt(1))) {\n        // Matched double path separator at beginning\n        let j = 2;\n        let last = j;\n        // Match 1 or more non-path separators\n        while (j < len && !isPathSeparator(path.charCodeAt(j))) {\n          j++;\n        }\n        if (j < len && j !== last) {\n          // Matched!\n          last = j;\n          // Match 1 or more path separators\n          while (j < len && isPathSeparator(path.charCodeAt(j))) {\n            j++;\n          }\n          if (j < len && j !== last) {\n            // Matched!\n            last = j;\n            // Match 1 or more non-path separators\n            while (j < len && !isPathSeparator(path.charCodeAt(j))) {\n              j++;\n            }\n            if (j === len) {\n              // We matched a UNC root only\n              return path;\n            }\n            if (j !== last) {\n              // We matched a UNC root with leftovers\n\n              // Offset by 1 to include the separator after the UNC root to\n              // treat it as a \"normal root\" on top of a (UNC) root\n              rootEnd = offset = j + 1;\n            }\n          }\n        }\n      }\n      // Possible device root\n    } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {\n      rootEnd = len > 2 && isPathSeparator(path.charCodeAt(2)) ? 3 : 2;\n      offset = rootEnd;\n    }\n\n    let end = -1;\n    let matchedSlash = true;\n    for (let i = len - 1; i >= offset; --i) {\n      if (isPathSeparator(path.charCodeAt(i))) {\n        if (!matchedSlash) {\n          end = i;\n          break;\n        }\n      } else {\n        // We saw the first non-path separator\n        matchedSlash = false;\n      }\n    }\n\n    if (end === -1) {\n      if (rootEnd === -1) return '.';\n\n      end = rootEnd;\n    }\n    return path.slice(0, end);\n  },\n\n  basename(path: string, suffix?: string): string {\n    if (suffix !== undefined) validateString(suffix, 'suffix');\n    validateString(path, 'path');\n    let start = 0;\n    let end = -1;\n    let matchedSlash = true;\n\n    // Check for a drive letter prefix so as not to mistake the following\n    // path separator as an extra separator at the end of the path that can be\n    // disregarded\n    if (\n      path.length >= 2 &&\n      isWindowsDeviceRoot(path.charCodeAt(0)) &&\n      path.charCodeAt(1) === CHAR_COLON\n    ) {\n      start = 2;\n    }\n\n    if (\n      suffix !== undefined &&\n      suffix.length > 0 &&\n      suffix.length <= path.length\n    ) {\n      if (suffix === path) return '';\n      let extIdx = suffix.length - 1;\n      let firstNonSlashEnd = -1;\n      for (let i = path.length - 1; i >= start; --i) {\n        const code = path.charCodeAt(i);\n        if (isPathSeparator(code)) {\n          // If we reached a path separator that was not part of a set of path\n          // separators at the end of the string, stop now\n          if (!matchedSlash) {\n            start = i + 1;\n            break;\n          }\n        } else {\n          if (firstNonSlashEnd === -1) {\n            // We saw the first non-path separator, remember this index in case\n            // we need it if the extension ends up not matching\n            matchedSlash = false;\n            firstNonSlashEnd = i + 1;\n          }\n          if (extIdx >= 0) {\n            // Try to match the explicit extension\n            if (code === suffix.charCodeAt(extIdx)) {\n              if (--extIdx === -1) {\n                // We matched the extension, so mark this as the end of our path\n                // component\n                end = i;\n              }\n            } else {\n              // Extension does not match, so our result is the entire path\n              // component\n              extIdx = -1;\n              end = firstNonSlashEnd;\n            }\n          }\n        }\n      }\n\n      if (start === end) end = firstNonSlashEnd;\n      else if (end === -1) end = path.length;\n      return path.slice(start, end);\n    }\n    for (let i = path.length - 1; i >= start; --i) {\n      if (isPathSeparator(path.charCodeAt(i))) {\n        // If we reached a path separator that was not part of a set of path\n        // separators at the end of the string, stop now\n        if (!matchedSlash) {\n          start = i + 1;\n          break;\n        }\n      } else if (end === -1) {\n        // We saw the first non-path separator, mark this as the end of our\n        // path component\n        matchedSlash = false;\n        end = i + 1;\n      }\n    }\n\n    if (end === -1) return '';\n    return path.slice(start, end);\n  },\n\n  extname(path: string): string {\n    validateString(path, 'path');\n    let start = 0;\n    let startDot = -1;\n    let startPart = 0;\n    let end = -1;\n    let matchedSlash = true;\n    // Track the state of characters (if any) we see before our first dot and\n    // after any path separator we find\n    let preDotState = 0;\n\n    // Check for a drive letter prefix so as not to mistake the following\n    // path separator as an extra separator at the end of the path that can be\n    // disregarded\n\n    if (\n      path.length >= 2 &&\n      path.charCodeAt(1) === CHAR_COLON &&\n      isWindowsDeviceRoot(path.charCodeAt(0))\n    ) {\n      start = startPart = 2;\n    }\n\n    for (let i = path.length - 1; i >= start; --i) {\n      const code = path.charCodeAt(i);\n      if (isPathSeparator(code)) {\n        // If we reached a path separator that was not part of a set of path\n        // separators at the end of the string, stop now\n        if (!matchedSlash) {\n          startPart = i + 1;\n          break;\n        }\n        continue;\n      }\n      if (end === -1) {\n        // We saw the first non-path separator, mark this as the end of our\n        // extension\n        matchedSlash = false;\n        end = i + 1;\n      }\n      if (code === CHAR_DOT) {\n        // If this is our first dot, mark it as the start of our extension\n        if (startDot === -1) startDot = i;\n        else if (preDotState !== 1) preDotState = 1;\n      } else if (startDot !== -1) {\n        // We saw a non-dot and non-path separator before our dot, so we should\n        // have a good chance at having a non-empty extension\n        preDotState = -1;\n      }\n    }\n\n    if (\n      startDot === -1 ||\n      end === -1 ||\n      // We saw a non-dot character immediately before the dot\n      preDotState === 0 ||\n      // The (right-most) trimmed path component is exactly '..'\n      (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)\n    ) {\n      return '';\n    }\n    return path.slice(startDot, end);\n  },\n\n  format(path: PathObject): string {\n    return _format('\\\\', path);\n  },\n\n  parse(path: string): PathObject {\n    validateString(path, 'path');\n\n    const ret = { root: '', dir: '', base: '', ext: '', name: '' };\n    if (path.length === 0) return ret;\n\n    const len = path.length;\n    let rootEnd = 0;\n    let code = path.charCodeAt(0);\n\n    if (len === 1) {\n      if (isPathSeparator(code)) {\n        // `path` contains just a path separator, exit early to avoid\n        // unnecessary work\n        ret.root = ret.dir = path;\n        return ret;\n      }\n      ret.base = ret.name = path;\n      return ret;\n    }\n    // Try to match a root\n    if (isPathSeparator(code)) {\n      // Possible UNC root\n\n      rootEnd = 1;\n      if (isPathSeparator(path.charCodeAt(1))) {\n        // Matched double path separator at beginning\n        let j = 2;\n        let last = j;\n        // Match 1 or more non-path separators\n        while (j < len && !isPathSeparator(path.charCodeAt(j))) {\n          j++;\n        }\n        if (j < len && j !== last) {\n          // Matched!\n          last = j;\n          // Match 1 or more path separators\n          while (j < len && isPathSeparator(path.charCodeAt(j))) {\n            j++;\n          }\n          if (j < len && j !== last) {\n            // Matched!\n            last = j;\n            // Match 1 or more non-path separators\n            while (j < len && !isPathSeparator(path.charCodeAt(j))) {\n              j++;\n            }\n            if (j === len) {\n              // We matched a UNC root only\n              rootEnd = j;\n            } else if (j !== last) {\n              // We matched a UNC root with leftovers\n              rootEnd = j + 1;\n            }\n          }\n        }\n      }\n    } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {\n      // Possible device root\n      if (len <= 2) {\n        // `path` contains just a drive root, exit early to avoid\n        // unnecessary work\n        ret.root = ret.dir = path;\n        return ret;\n      }\n      rootEnd = 2;\n      if (isPathSeparator(path.charCodeAt(2))) {\n        if (len === 3) {\n          // `path` contains just a drive root, exit early to avoid\n          // unnecessary work\n          ret.root = ret.dir = path;\n          return ret;\n        }\n        rootEnd = 3;\n      }\n    }\n    if (rootEnd > 0) ret.root = path.slice(0, rootEnd);\n\n    let startDot = -1;\n    let startPart = rootEnd;\n    let end = -1;\n    let matchedSlash = true;\n    let i = path.length - 1;\n\n    // Track the state of characters (if any) we see before our first dot and\n    // after any path separator we find\n    let preDotState = 0;\n\n    // Get non-dir info\n    for (; i >= rootEnd; --i) {\n      code = path.charCodeAt(i);\n      if (isPathSeparator(code)) {\n        // If we reached a path separator that was not part of a set of path\n        // separators at the end of the string, stop now\n        if (!matchedSlash) {\n          startPart = i + 1;\n          break;\n        }\n        continue;\n      }\n      if (end === -1) {\n        // We saw the first non-path separator, mark this as the end of our\n        // extension\n        matchedSlash = false;\n        end = i + 1;\n      }\n      if (code === CHAR_DOT) {\n        // If this is our first dot, mark it as the start of our extension\n        if (startDot === -1) startDot = i;\n        else if (preDotState !== 1) preDotState = 1;\n      } else if (startDot !== -1) {\n        // We saw a non-dot and non-path separator before our dot, so we should\n        // have a good chance at having a non-empty extension\n        preDotState = -1;\n      }\n    }\n\n    if (end !== -1) {\n      if (\n        startDot === -1 ||\n        // We saw a non-dot character immediately before the dot\n        preDotState === 0 ||\n        // The (right-most) trimmed path component is exactly '..'\n        (preDotState === 1 &&\n          startDot === end - 1 &&\n          startDot === startPart + 1)\n      ) {\n        ret.base = ret.name = path.slice(startPart, end);\n      } else {\n        ret.name = path.slice(startPart, startDot);\n        ret.base = path.slice(startPart, end);\n        ret.ext = path.slice(startDot, end);\n      }\n    }\n\n    // If the directory is the root, use the entire root as the `dir` including\n    // the trailing slash if any (`C:\\abc` -> `C:\\`). Otherwise, strip out the\n    // trailing slash (`C:\\abc\\def` -> `C:\\abc`).\n    if (startPart > 0 && startPart !== rootEnd)\n      ret.dir = path.slice(0, startPart - 1);\n    else ret.dir = ret.root;\n\n    return ret;\n  },\n\n  matchesGlob(_path: string, _pattern: string): boolean {\n    throw new Error('path.win32.matchesGlob() is not implemented.');\n  },\n\n  sep: '\\\\',\n  delimiter: ';',\n  win32: null as object | null,\n  posix: null as object | null,\n};\n\nconst posix = {\n  /**\n   * path.resolve([from ...], to)\n   * @param {...string} args\n   * @returns {string}\n   */\n  resolve(...args: string[]): string {\n    let resolvedPath = '';\n    let resolvedAbsolute = false;\n\n    for (let i = args.length - 1; i >= 0 && !resolvedAbsolute; i--) {\n      const path = args[i];\n      validateString(path, `paths[${i}]`);\n\n      // Skip empty entries\n      if (path.length === 0) {\n        continue;\n      }\n\n      resolvedPath = `${path}/${resolvedPath}`;\n      resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH;\n    }\n\n    if (!resolvedAbsolute) {\n      const cwd = '/';\n      resolvedPath = `${cwd}/${resolvedPath}`;\n      resolvedAbsolute = true;\n    }\n\n    // At this point the path should be resolved to a full absolute path, but\n    // handle relative paths to be safe (might happen when process.cwd() fails)\n\n    // Normalize the path\n    resolvedPath = normalizeString(\n      resolvedPath,\n      !resolvedAbsolute,\n      '/',\n      isPosixPathSeparator\n    );\n\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    if (resolvedAbsolute) {\n      return `/${resolvedPath}`;\n    }\n    return resolvedPath.length > 0 ? resolvedPath : '.';\n  },\n\n  /**\n   * @param {string} path\n   * @returns {string}\n   */\n  normalize(path: string): string {\n    validateString(path, 'path');\n\n    if (path.length === 0) return '.';\n\n    const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH;\n    const trailingSeparator =\n      path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH;\n\n    // Normalize the path\n    path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator);\n\n    if (path.length === 0) {\n      if (isAbsolute) return '/';\n      return trailingSeparator ? './' : '.';\n    }\n    if (trailingSeparator) path += '/';\n\n    return isAbsolute ? `/${path}` : path;\n  },\n\n  /**\n   * @param {string} path\n   * @returns {boolean}\n   */\n  isAbsolute(path: string): boolean {\n    validateString(path, 'path');\n    return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH;\n  },\n\n  /**\n   * @param {...string} args\n   * @returns {string}\n   */\n  join(...args: string[]): string {\n    if (args.length === 0) return '.';\n    let joined;\n    for (let i = 0; i < args.length; ++i) {\n      const arg = args[i];\n      validateString(arg, 'path');\n      if (arg.length > 0) {\n        if (joined === undefined) joined = arg;\n        else joined += `/${arg}`;\n      }\n    }\n    if (joined === undefined) return '.';\n    return posix.normalize(joined);\n  },\n\n  /**\n   * @param {string} from\n   * @param {string} to\n   * @returns {string}\n   */\n  relative(from: string, to: string): string {\n    validateString(from, 'from');\n    validateString(to, 'to');\n\n    if (from === to) return '';\n\n    // Trim leading forward slashes.\n    from = posix.resolve(from);\n    to = posix.resolve(to);\n\n    if (from === to) return '';\n\n    // Trim any leading slashes\n    let fromStart = 0;\n    while (\n      fromStart < from.length &&\n      from.charCodeAt(fromStart) === CHAR_FORWARD_SLASH\n    ) {\n      fromStart++;\n    }\n    // Trim trailing slashes\n    let fromEnd = from.length;\n    while (\n      fromEnd - 1 > fromStart &&\n      from.charCodeAt(fromEnd - 1) === CHAR_FORWARD_SLASH\n    ) {\n      fromEnd--;\n    }\n    const fromLen = fromEnd - fromStart;\n\n    // Trim any leading slashes\n    let toStart = 0;\n    while (\n      toStart < to.length &&\n      to.charCodeAt(toStart) === CHAR_FORWARD_SLASH\n    ) {\n      toStart++;\n    }\n    // Trim trailing slashes\n    let toEnd = to.length;\n    while (\n      toEnd - 1 > toStart &&\n      to.charCodeAt(toEnd - 1) === CHAR_FORWARD_SLASH\n    ) {\n      toEnd--;\n    }\n    const toLen = toEnd - toStart;\n\n    // Compare paths to find the longest common path from root\n    const length = fromLen < toLen ? fromLen : toLen;\n    let lastCommonSep = -1;\n    let i = 0;\n    for (; i < length; i++) {\n      const fromCode = from.charCodeAt(fromStart + i);\n      if (fromCode !== to.charCodeAt(toStart + i)) break;\n      else if (fromCode === CHAR_FORWARD_SLASH) lastCommonSep = i;\n    }\n    if (i === length) {\n      if (toLen > length) {\n        if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) {\n          // We get here if `from` is the exact base path for `to`.\n          // For example: from='/foo/bar'; to='/foo/bar/baz'\n          return to.slice(toStart + i + 1);\n        }\n        if (i === 0) {\n          // We get here if `from` is the root\n          // For example: from='/'; to='/foo'\n          return to.slice(toStart + i);\n        }\n      } else if (fromLen > length) {\n        if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) {\n          // We get here if `to` is the exact base path for `from`.\n          // For example: from='/foo/bar/baz'; to='/foo/bar'\n          lastCommonSep = i;\n        } else if (i === 0) {\n          // We get here if `to` is the root.\n          // For example: from='/foo/bar'; to='/'\n          lastCommonSep = 0;\n        }\n      }\n    }\n\n    let out = '';\n    // Generate the relative path based on the path difference between `to`\n    // and `from`.\n    for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {\n      if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) {\n        out += out.length === 0 ? '..' : '/..';\n      }\n    }\n\n    // Lastly, append the rest of the destination (`to`) path that comes after\n    // the common path parts.\n    return `${out}${to.slice(toStart + lastCommonSep)}`;\n  },\n\n  /**\n   * @param {string} path\n   * @returns {string}\n   */\n  toNamespacedPath(path: string): string {\n    // No-op on Posix systems\n    return path;\n  },\n\n  /**\n   * @param {string} path\n   * @returns {string}\n   */\n  dirname(path: string): string {\n    validateString(path, 'path');\n    if (path.length === 0) return '.';\n    const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH;\n    let end = -1;\n    let matchedSlash = true;\n    for (let i = path.length - 1; i >= 1; --i) {\n      if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {\n        if (!matchedSlash) {\n          end = i;\n          break;\n        }\n      } else {\n        // We saw the first non-path separator\n        matchedSlash = false;\n      }\n    }\n\n    if (end === -1) return hasRoot ? '/' : '.';\n    if (hasRoot && end === 1) return '//';\n    return path.slice(0, end);\n  },\n\n  /**\n   * @param {string} path\n   * @param {string} [suffix]\n   * @returns {string}\n   */\n  basename(path: string, suffix?: string): string {\n    if (suffix !== undefined) validateString(suffix, 'ext');\n    validateString(path, 'path');\n\n    let start = 0;\n    let end = -1;\n    let matchedSlash = true;\n\n    if (\n      suffix !== undefined &&\n      suffix.length > 0 &&\n      suffix.length <= path.length\n    ) {\n      if (suffix === path) return '';\n      let extIdx = suffix.length - 1;\n      let firstNonSlashEnd = -1;\n      for (let i = path.length - 1; i >= 0; --i) {\n        const code = path.charCodeAt(i);\n        if (code === CHAR_FORWARD_SLASH) {\n          // If we reached a path separator that was not part of a set of path\n          // separators at the end of the string, stop now\n          if (!matchedSlash) {\n            start = i + 1;\n            break;\n          }\n        } else {\n          if (firstNonSlashEnd === -1) {\n            // We saw the first non-path separator, remember this index in case\n            // we need it if the extension ends up not matching\n            matchedSlash = false;\n            firstNonSlashEnd = i + 1;\n          }\n          if (extIdx >= 0) {\n            // Try to match the explicit extension\n            if (code === suffix.charCodeAt(extIdx)) {\n              if (--extIdx === -1) {\n                // We matched the extension, so mark this as the end of our path\n                // component\n                end = i;\n              }\n            } else {\n              // Extension does not match, so our result is the entire path\n              // component\n              extIdx = -1;\n              end = firstNonSlashEnd;\n            }\n          }\n        }\n      }\n\n      if (start === end) end = firstNonSlashEnd;\n      else if (end === -1) end = path.length;\n      return path.slice(start, end);\n    }\n    for (let i = path.length - 1; i >= 0; --i) {\n      if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {\n        // If we reached a path separator that was not part of a set of path\n        // separators at the end of the string, stop now\n        if (!matchedSlash) {\n          start = i + 1;\n          break;\n        }\n      } else if (end === -1) {\n        // We saw the first non-path separator, mark this as the end of our\n        // path component\n        matchedSlash = false;\n        end = i + 1;\n      }\n    }\n\n    if (end === -1) return '';\n    return path.slice(start, end);\n  },\n\n  /**\n   * @param {string} path\n   * @returns {string}\n   */\n  extname(path: string): string {\n    validateString(path, 'path');\n    let startDot = -1;\n    let startPart = 0;\n    let end = -1;\n    let matchedSlash = true;\n    // Track the state of characters (if any) we see before our first dot and\n    // after any path separator we find\n    let preDotState = 0;\n    for (let i = path.length - 1; i >= 0; --i) {\n      const code = path.charCodeAt(i);\n      if (code === CHAR_FORWARD_SLASH) {\n        // If we reached a path separator that was not part of a set of path\n        // separators at the end of the string, stop now\n        if (!matchedSlash) {\n          startPart = i + 1;\n          break;\n        }\n        continue;\n      }\n      if (end === -1) {\n        // We saw the first non-path separator, mark this as the end of our\n        // extension\n        matchedSlash = false;\n        end = i + 1;\n      }\n      if (code === CHAR_DOT) {\n        // If this is our first dot, mark it as the start of our extension\n        if (startDot === -1) startDot = i;\n        else if (preDotState !== 1) preDotState = 1;\n      } else if (startDot !== -1) {\n        // We saw a non-dot and non-path separator before our dot, so we should\n        // have a good chance at having a non-empty extension\n        preDotState = -1;\n      }\n    }\n\n    if (\n      startDot === -1 ||\n      end === -1 ||\n      // We saw a non-dot character immediately before the dot\n      preDotState === 0 ||\n      // The (right-most) trimmed path component is exactly '..'\n      (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)\n    ) {\n      return '';\n    }\n    return path.slice(startDot, end);\n  },\n\n  format: _format.bind(null, '/'),\n\n  /**\n   * @param {string} path\n   * @returns {{\n   *   dir: string;\n   *   root: string;\n   *   base: string;\n   *   name: string;\n   *   ext: string;\n   *   }}\n   */\n  parse(path: string): PathObject {\n    validateString(path, 'path');\n\n    const ret = { root: '', dir: '', base: '', ext: '', name: '' };\n    if (path.length === 0) return ret;\n    const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH;\n    let start;\n    if (isAbsolute) {\n      ret.root = '/';\n      start = 1;\n    } else {\n      start = 0;\n    }\n    let startDot = -1;\n    let startPart = 0;\n    let end = -1;\n    let matchedSlash = true;\n    let i = path.length - 1;\n\n    // Track the state of characters (if any) we see before our first dot and\n    // after any path separator we find\n    let preDotState = 0;\n\n    // Get non-dir info\n    for (; i >= start; --i) {\n      const code = path.charCodeAt(i);\n      if (code === CHAR_FORWARD_SLASH) {\n        // If we reached a path separator that was not part of a set of path\n        // separators at the end of the string, stop now\n        if (!matchedSlash) {\n          startPart = i + 1;\n          break;\n        }\n        continue;\n      }\n      if (end === -1) {\n        // We saw the first non-path separator, mark this as the end of our\n        // extension\n        matchedSlash = false;\n        end = i + 1;\n      }\n      if (code === CHAR_DOT) {\n        // If this is our first dot, mark it as the start of our extension\n        if (startDot === -1) startDot = i;\n        else if (preDotState !== 1) preDotState = 1;\n      } else if (startDot !== -1) {\n        // We saw a non-dot and non-path separator before our dot, so we should\n        // have a good chance at having a non-empty extension\n        preDotState = -1;\n      }\n    }\n\n    if (end !== -1) {\n      const start = startPart === 0 && isAbsolute ? 1 : startPart;\n      if (\n        startDot === -1 ||\n        // We saw a non-dot character immediately before the dot\n        preDotState === 0 ||\n        // The (right-most) trimmed path component is exactly '..'\n        (preDotState === 1 &&\n          startDot === end - 1 &&\n          startDot === startPart + 1)\n      ) {\n        ret.base = ret.name = path.slice(start, end);\n      } else {\n        ret.name = path.slice(start, startDot);\n        ret.base = path.slice(start, end);\n        ret.ext = path.slice(startDot, end);\n      }\n    }\n\n    if (startPart > 0) ret.dir = path.slice(0, startPart - 1);\n    else if (isAbsolute) ret.dir = '/';\n\n    return ret;\n  },\n\n  matchesGlob(_path: string, _pattern: string): boolean {\n    throw new Error('path.posix.matchesGlob() is not implemented.');\n  },\n\n  sep: '/',\n  delimiter: ':',\n  win32: null as object | null,\n  posix: null as object | null,\n};\n\nposix.win32 = win32.win32 = win32;\nposix.posix = win32.posix = posix;\n\n// TODO(soon): Maybe we should export depending on the host operating system\n// Ref: https://github.com/nodejs/node/blob/10addb0a208c0356a0358cfd2a7b80a0932cd108/lib/path.js#L1696\nexport default posix;\nexport { posix, win32 };\n"
  },
  {
    "path": "src/node/internal/internal_process.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Our implementation of process.nextTick is just queueMicrotask. The timing\n// of when the queue is drained is different, as is the error handling so this\n// is only an approximation of process.nextTick semantics. Hopefully it's good\n// enough because we really do not want to implement Node.js' idea of a nextTick\n// queue.\n\nimport { validateObject } from 'node-internal:validators';\nimport {\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_ARG_VALUE,\n  NodeError,\n} from 'node-internal:internal_errors';\nimport {\n  type EmitWarningOptions,\n  type ErrorWithDetail,\n  default as processImpl,\n} from 'node-internal:process';\nimport type publicProcessType from 'node-internal:public_process';\nimport type legacyProcessType from 'node-internal:legacy_process';\n\nexport const platform = processImpl.platform;\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\nexport function nextTick(cb: Function, ...args: unknown[]): void {\n  queueMicrotask(() => {\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-call\n    cb(...args);\n  });\n}\n\n// Decide if a value can round-trip to JSON without losing any information.\nfunction isJsonSerializable(\n  value: unknown,\n  seen: Set<unknown> = new Set()\n): boolean {\n  switch (typeof value) {\n    case 'boolean':\n    case 'number':\n    case 'string':\n      return true;\n\n    case 'object': {\n      if (value === null) {\n        return true;\n      }\n\n      if (seen.has(value)) {\n        // Don't allow cycles or aliases. (Non-cyclic aliases technically could be OK, but a\n        // round trip to JSON would lose the fact that they are aliases.)\n        return false;\n      }\n      seen.add(value);\n\n      // TODO(revisit): While any object that implements the toJSON function is\n      // generally expected to be JSON serializable, when working with jsrpc\n      // targets, the `toJSON` property ends up being Proxied and appears to be\n      // a legit property on some object types when it really shouldn't be, causing\n      // issues with certain types of bindings. Fun!\n      // Commenting this out instead of removing it because it would be great if\n      // we could find a way to support this reliably.\n      //\n      // if (typeof value.toJSON === 'function') {\n      //   // This type is explicitly designed to be JSON-serialized so we'll accept it.\n      //   return true;\n      // }\n\n      // We only consider objects to be serializable if they are plain objects or plain arrays.\n      // Technically, JSON can serialize any subclass of Object (as well as objects with null\n      // prototypes), but the round trip would lose information about the original type. Hence,\n      // we assume that any env var containing such things is not intended to be appear as JSON\n      // in process.env. For example, we wouldn't want a KV namespace to show up in process.env as\n      // \"{}\" -- this would be weird.\n      switch (Object.getPrototypeOf(value)) {\n        case Object.prototype:\n          // Note that Object.values() only returns string-keyed values, not symbol-keyed.\n          return Object.values(value).every((prop) =>\n            isJsonSerializable(prop, seen)\n          );\n        case Array.prototype:\n          return (value as Array<unknown>).every((elem: unknown) =>\n            isJsonSerializable(elem, seen)\n          );\n        default:\n          return false;\n      }\n    }\n\n    default:\n      return false;\n  }\n}\n\nfunction getInitialEnv(): Record<string, string> {\n  const env: Record<string, string> = {};\n  for (const [key, value] of Object.entries(processImpl.getEnvObject())) {\n    // Workers environment variables can have a variety of types, but process.env vars are\n    // strictly strings. We want to convert our workers env into process.env, but allowing\n    // process.env to contain non-strings would probably break Node apps.\n    //\n    // As a compromise, we say:\n    // - Workers env vars that are plain strings are unchanged in process.env.\n    // - Workers env vars that can be represented as JSON will be JSON-stringified in process.env.\n    // - Anything else will be omitted.\n    //\n    // Note that you might argue that, at the config layer, it's possible to differentiate between\n    // plain strings and JSON values that evaluated to strings. Wouldn't it be nice if we could\n    // check which way the binding was originally configured in order to decide whether to\n    // represent it plain or as JSON here. However, there is no way to tell just by looking at\n    // the `env` object inside a Worker whether a particular var was originally configured as\n    // plain text, or as JSON that evaluated to a string. Either way, you just get a string. And\n    // indeed, the Workers Runtime itself does not necessarily know this. In many cases it does\n    // know, but in general the abstraction the Runtime intends to provide is that `env` is just\n    // a JavaScript object, and how exactly the contents were originally represented is not\n    // intended to be conveyed. This is important because, for example, we could extend dynamic\n    // dispatch bindings in the future such that the caller can specify `env` directly, and in\n    // that case the caller would simply specify a JS object, without JSON or any other\n    // serialization involved. In this case, there would be no way to know if a string var was\n    // \"supposed to be\" raw text vs. JSON.\n    //\n    // So, we have to do the best we can given just what we know -- the JavaScript object that is\n    // `env`.\n    //\n    // As a consolation, this is consistent with how variables are defined in wrangler.toml: you\n    // do not explicitly specify whether a variable is text or JSON. If you define a variable with\n    // a simple string value, it gets configured as a text var. If you specify an object, then it's\n    // configured as JSON.\n\n    if (typeof value === 'string') {\n      env[key] = value;\n    } else if (isJsonSerializable(value)) {\n      env[key] = JSON.stringify(value);\n    }\n  }\n  return env;\n}\n\nexport const env = new Proxy(getInitialEnv(), {\n  // Per Node.js rules. process.env values must be coerced to strings.\n  // When defined using defineProperty, the property descriptor must be writable,\n  // configurable, and enumerable using just a falsy check. Getters and setters\n  // are not permitted.\n  set(obj: object, prop: PropertyKey, value: unknown): boolean {\n    if (typeof prop === 'symbol' || typeof value === 'symbol')\n      throw new TypeError(`Cannot convert a symbol value to a string`);\n    return Reflect.set(obj, prop, `${value}`);\n  },\n  defineProperty(\n    obj: object,\n    prop: PropertyKey,\n    descriptor: PropertyDescriptor\n  ): boolean {\n    validateObject(descriptor, 'descriptor');\n    if (Reflect.has(descriptor, 'get') || Reflect.has(descriptor, 'set')) {\n      throw new ERR_INVALID_ARG_VALUE(\n        'descriptor',\n        descriptor,\n        'process.env value must not have getter/setter'\n      );\n    }\n    if (!descriptor.configurable) {\n      throw new ERR_INVALID_ARG_VALUE(\n        'descriptor.configurable',\n        descriptor,\n        'process.env value must be configurable'\n      );\n    }\n    if (!descriptor.enumerable) {\n      throw new ERR_INVALID_ARG_VALUE(\n        'descriptor.enumerable',\n        descriptor,\n        'process.env value must be enumerable'\n      );\n    }\n    if (!descriptor.writable) {\n      throw new ERR_INVALID_ARG_VALUE(\n        'descriptor.writable',\n        descriptor,\n        'process.env value must be writable'\n      );\n    }\n    if (Reflect.has(descriptor, 'value')) {\n      if (typeof prop === 'symbol' || typeof descriptor.value === 'symbol')\n        throw new TypeError(`Cannot convert a symbol value to a string`);\n      Reflect.set(descriptor, 'value', `${descriptor.value}`);\n    } else {\n      throw new ERR_INVALID_ARG_VALUE(\n        'descriptor.value',\n        descriptor,\n        'process.env value must be specified explicitly'\n      );\n    }\n    if (typeof prop === 'symbol')\n      throw new TypeError(`Cannot convert a symbol value to a string`);\n    return Reflect.defineProperty(obj, prop, descriptor);\n  },\n});\n\n// The following features does not include deprecated or experimental flags mentioned in\n// https://nodejs.org/docs/latest/api/process.html\nexport const features = Object.freeze({\n  // A boolean value that is true if the current Node.js build is caching builtin modules.\n  cached_builtins: true,\n  // A boolean value that is true if the current Node.js build is a debug build.\n  debug: false,\n  // A boolean value that is true if the current Node.js build includes the inspector.\n  inspector: false,\n  // A boolean value that is true if the current Node.js build supports loading ECMAScript modules using require().\n  // TODO(soon): Update this when we support ESM modules through require().\n  require_module: false,\n  // A boolean value that is true if the current Node.js build includes support for TLS.\n  tls: true,\n});\n\nexport function emitWarning(\n  warning: string | Error,\n  ctor?: ErrorConstructor\n): void;\nexport function emitWarning(\n  warning: string | Error,\n  type?: string,\n  ctor?: ErrorConstructor\n): void;\nexport function emitWarning(\n  warning: string | Error,\n  type?: string,\n  code?: string,\n  ctor?: ErrorConstructor\n): void;\nexport function emitWarning(\n  warning: string | Error,\n  options?: ErrorConstructor | string | EmitWarningOptions,\n  codeOrCtor?: ErrorConstructor | string,\n  maybeCtor?: ErrorConstructor\n): void {\n  let err: Error;\n  let name = 'Warning';\n  let detail: string | undefined;\n  let code: string | undefined;\n  let ctor: ErrorConstructor | undefined;\n\n  // Handle different overloads\n  if (typeof options === 'object' && !Array.isArray(options)) {\n    // emitWarning(warning, options)\n    if (options.type) name = options.type;\n    if (options.code) code = options.code;\n    if (options.detail) detail = options.detail;\n    ctor = options.ctor;\n  } else if (typeof options === 'string') {\n    // emitWarning(warning, type, ...)\n    name = options;\n    if (typeof codeOrCtor === 'string') {\n      // emitWarning(warning, type, code, ctor)\n      code = codeOrCtor;\n      if (typeof maybeCtor === 'function') {\n        ctor = maybeCtor;\n      } else if ((maybeCtor as unknown) !== undefined) {\n        throw new ERR_INVALID_ARG_TYPE('ctor', 'function', maybeCtor);\n      }\n    } else if (typeof codeOrCtor === 'function') {\n      // emitWarning(warning, type, ctor)\n      ctor = codeOrCtor;\n    } else if ((codeOrCtor as unknown) !== undefined) {\n      throw new ERR_INVALID_ARG_TYPE('ctor', 'function', codeOrCtor);\n    }\n  } else if (typeof options === 'function') {\n    // emitWarning(warning, ctor)\n    ctor = options;\n  } else if (options !== undefined) {\n    throw new ERR_INVALID_ARG_TYPE('options', 'object', options);\n  }\n\n  // Convert string warning to Error\n  if (typeof warning === 'string') {\n    // Use the provided constructor if available, otherwise use Error\n    const ErrorConstructor = ctor || Error;\n    err = new ErrorConstructor(warning);\n    err.name = name;\n  } else if (warning instanceof Error) {\n    err = warning;\n    // Override name if provided\n    if (name && name !== 'Warning') {\n      err.name = name;\n    }\n  } else {\n    throw new ERR_INVALID_ARG_TYPE('warning', 'string or Error', warning);\n  }\n\n  // Add code if provided\n  if (code) {\n    (err as NodeError).code = code;\n  }\n\n  // Add detail if provided\n  if (detail && typeof detail === 'string') {\n    (err as ErrorWithDetail).detail = detail;\n  }\n\n  // Capture stack trace using the provided constructor or emitWarning itself\n  // This excludes the constructor (and frames above it) from the stack trace\n  Error.captureStackTrace(err, ctor || emitWarning);\n\n  // Emit the warning event on the process object\n  // Use nextTick to ensure the warning is emitted asynchronously\n  queueMicrotask(() => {\n    (_process as typeof publicProcessType).emit('warning', err);\n  });\n}\n\n// Events has a cycle with process, so to resolve that we lazily bind\n// this _process for events usage only. All other internal importers should\n// import 'node:process' directly rather, as _eventsProcess is only guaranteed\n// to be available when that has been imported.\nexport let _process: typeof legacyProcessType | typeof publicProcessType;\nexport function _setEventsProcess(\n  process: typeof legacyProcessType | typeof publicProcessType\n): void {\n  _process = process;\n}\n"
  },
  {
    "path": "src/node/internal/internal_querystring.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport { Buffer } from 'node-internal:internal_buffer';\nimport { ERR_INVALID_URI } from 'node-internal:internal_errors';\n\ntype EncodeFunction = (value: string) => string;\ntype DecodeFunction = (value: string) => string;\n\nexport const hexTable = Array.from({ length: 256 }, (_, i) => {\n  return '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase();\n});\n\n// prettier-ignore\nconst isHexTable = new Int8Array([\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 32 - 47\n  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63\n  0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 64 - 79\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80 - 95\n  0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 96 - 111\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 112 - 127\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128 ...\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // ... 256\n]);\n\n/* eslint-disable */\nexport function encodeStr(\n  str: string,\n  noEscapeTable: Int8Array,\n  hexTable: string[]\n): string {\n  const len = str.length;\n  if (len === 0) return '';\n\n  let out = '';\n  let lastPos = 0;\n  let i = 0;\n\n  outer: for (; i < len; i++) {\n    let c = str.charCodeAt(i);\n\n    // ASCII\n    while (c < 0x80) {\n      if (noEscapeTable[c] !== 1) {\n        if (lastPos < i) out += str.slice(lastPos, i);\n        lastPos = i + 1;\n        out += hexTable[c]!;\n      }\n\n      if (++i === len) break outer;\n\n      c = str.charCodeAt(i);\n    }\n\n    if (lastPos < i) out += str.slice(lastPos, i);\n\n    // Multi-byte characters ...\n    if (c < 0x800) {\n      lastPos = i + 1;\n      out += hexTable[0xc0 | (c >> 6)]! + hexTable[0x80 | (c & 0x3f)]!;\n      continue;\n    }\n    if (c < 0xd800 || c >= 0xe000) {\n      lastPos = i + 1;\n      out +=\n        hexTable[0xe0 | (c >> 12)]! +\n        hexTable[0x80 | ((c >> 6) & 0x3f)]! +\n        hexTable[0x80 | (c & 0x3f)];\n      continue;\n    }\n    // Surrogate pair\n    ++i;\n\n    // This branch should never happen because all URLSearchParams entries\n    // should already be converted to USVString. But, included for\n    // completion's sake anyway.\n    if (i >= len) throw new ERR_INVALID_URI();\n\n    const c2 = str.charCodeAt(i) & 0x3ff;\n\n    lastPos = i + 1;\n    c = 0x10000 + (((c & 0x3ff) << 10) | c2);\n    out +=\n      hexTable[0xf0 | (c >> 18)]! +\n      hexTable[0x80 | ((c >> 12) & 0x3f)]! +\n      hexTable[0x80 | ((c >> 6) & 0x3f)] +\n      hexTable[0x80 | (c & 0x3f)];\n  }\n  if (lastPos === 0) return str;\n  if (lastPos < len) return out + str.slice(lastPos);\n  return out;\n}\n/* eslint-enable */\n\n/* eslint-disable @typescript-eslint/no-unnecessary-type-conversion */\n\n// prettier-ignore\nconst unhexTable = new Int8Array([\n  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0 - 15\n  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16 - 31\n  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 32 - 47\n  +0, +1, +2, +3, +4, +5, +6, +7, +8, +9, -1, -1, -1, -1, -1, -1, // 48 - 63\n  -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 64 - 79\n  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80 - 95\n  -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 96 - 111\n  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 112 - 127\n  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 128 ...\n  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  // ... 255\n]);\n\nexport function unescapeBuffer(\n  s: string,\n  decodeSpaces: boolean\n): Buffer | Uint8Array {\n  const out = Buffer.allocUnsafe(s.length);\n  let index: number = 0;\n  let outIndex: number = 0;\n  let currentChar: number;\n  let nextChar: number;\n  let hexHigh: number;\n  let hexLow: number;\n  const maxLength = s.length - 2;\n  // Flag to know if some hex chars have been decoded\n  let hasHex = false;\n  while (index < s.length) {\n    currentChar = s.charCodeAt(index);\n    if (currentChar === 43 /* '+' */ && decodeSpaces) {\n      out[outIndex++] = 32; // ' '\n      index++;\n      continue;\n    }\n    if (currentChar === 37 /* '%' */ && index < maxLength) {\n      currentChar = s.charCodeAt(++index);\n      hexHigh = unhexTable[currentChar] as number;\n      if (!(hexHigh >= 0)) {\n        out[outIndex++] = 37; // '%'\n        continue;\n      } else {\n        nextChar = s.charCodeAt(++index);\n        hexLow = unhexTable[nextChar] as number;\n        if (!(hexLow >= 0)) {\n          out[outIndex++] = 37; // '%'\n          index--;\n        } else {\n          hasHex = true;\n          currentChar = hexHigh * 16 + hexLow;\n        }\n      }\n    }\n    out[outIndex++] = currentChar;\n    index++;\n  }\n  return hasHex ? out.slice(0, outIndex) : out;\n}\n\n/**\n * @param {string} s\n * @param {boolean} decodeSpaces\n * @returns {string}\n */\nexport function unescape(s: string, decodeSpaces: boolean): string {\n  try {\n    return decodeURIComponent(s);\n  } catch {\n    return unescapeBuffer(s, decodeSpaces).toString();\n  }\n}\n\n// These characters do not need escaping when generating query strings:\n// ! - . _ ~\n// ' ( ) *\n// digits\n// alpha (uppercase)\n// alpha (lowercase)\n\n// prettier-ignore\nconst noEscape = new Int8Array([\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31\n  0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, // 32 - 47\n  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63\n  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79\n  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 80 - 95\n  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111\n  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,  // 112 - 127\n]);\n\n/**\n * QueryString.escape() replaces encodeURIComponent()\n * @see https://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.4\n * @param {any} input\n * @returns {string}\n */\nexport function escape(input: unknown): string {\n  let str: string;\n  if (typeof input !== 'string') {\n    // eslint-disable-next-line @typescript-eslint/no-base-to-string\n    if (typeof input === 'object') str = String(input);\n    // eslint-disable-next-line @typescript-eslint/restrict-plus-operands,@typescript-eslint/no-base-to-string\n    else str = input + '';\n  } else {\n    str = input;\n  }\n\n  return encodeStr(str, noEscape, hexTable);\n}\n\n/**\n * @param {string | number | bigint | boolean | symbol | undefined | null} v\n * @returns {string}\n */\nfunction stringifyPrimitive(v: unknown): string {\n  if (typeof v === 'string') return v;\n  // eslint-disable-next-line @typescript-eslint/restrict-plus-operands\n  if (typeof v === 'number' && Number.isFinite(v)) return '' + v;\n  // eslint-disable-next-line @typescript-eslint/restrict-plus-operands\n  if (typeof v === 'bigint') return '' + v;\n  if (typeof v === 'boolean') return v ? 'true' : 'false';\n  return '';\n}\n\nfunction encodeStringified(v: unknown, encode: EncodeFunction): string {\n  if (typeof v === 'string') return v.length ? encode(v) : '';\n  if (typeof v === 'number' && Number.isFinite(v)) {\n    // Values >= 1e21 automatically switch to scientific notation which requires\n    // escaping due to the inclusion of a '+' in the output\n    // eslint-disable-next-line @typescript-eslint/restrict-plus-operands\n    return Math.abs(v) < 1e21 ? '' + v : encode('' + v);\n  }\n  // eslint-disable-next-line @typescript-eslint/restrict-plus-operands\n  if (typeof v === 'bigint') return '' + v;\n  if (typeof v === 'boolean') return v ? 'true' : 'false';\n  return '';\n}\n\nfunction encodeStringifiedCustom(v: unknown, encode: EncodeFunction): string {\n  return encode(stringifyPrimitive(v));\n}\n\nexport function stringify(\n  obj: unknown,\n  sep?: string,\n  eq?: string,\n  options?: { encodeURIComponent?: EncodeFunction }\n): string {\n  sep ||= '&';\n  eq ||= '=';\n\n  let encode = escape as EncodeFunction;\n  if (options && typeof options.encodeURIComponent === 'function') {\n    encode = options.encodeURIComponent;\n  }\n  const convert =\n    encode === escape ? encodeStringified : encodeStringifiedCustom;\n\n  if (obj !== null && typeof obj === 'object') {\n    const keys = Object.keys(obj);\n    const len = keys.length;\n    let fields = '';\n    for (let i = 0; i < len; ++i) {\n      const k = keys[i] as keyof typeof obj;\n      const v = obj[k] as unknown;\n      let ks = convert(k, encode);\n      ks += eq;\n\n      if (Array.isArray(v)) {\n        const vlen = v.length;\n        if (vlen === 0) continue;\n        if (fields) fields += sep;\n        for (let j = 0; j < vlen; ++j) {\n          if (j) fields += sep;\n          fields += ks;\n          fields += convert(v[j], encode);\n        }\n      } else {\n        if (fields) fields += sep;\n        fields += ks;\n        fields += convert(v, encode);\n      }\n    }\n    return fields;\n  }\n  return '';\n}\n\n/**\n * @param {string} str\n * @returns {number[]}\n */\nfunction charCodes(str: string): number[] {\n  if (str.length === 0) return [];\n  if (str.length === 1) return [str.charCodeAt(0)];\n  return Array.from({ length: str.length }, (_, i) => str.charCodeAt(i));\n}\nconst defSepCodes = [38]; // &\nconst defEqCodes = [61]; // =\n\nfunction addKeyVal(\n  obj: Record<string, unknown>,\n  key: string,\n  value: string,\n  keyEncoded: boolean,\n  valEncoded: boolean,\n  decode: DecodeFunction\n): void {\n  if (key.length > 0 && keyEncoded) key = decodeStr(key, decode);\n  if (value.length > 0 && valEncoded) value = decodeStr(value, decode);\n\n  if (obj[key] === undefined) {\n    obj[key] = value;\n  } else {\n    const curValue = obj[key];\n    // A simple Array-specific property check is enough here to\n    // distinguish from a string value and is faster and still safe\n    // since we are generating all of the values being assigned.\n    // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n    // @ts-expect-error TS18046\n    if (curValue.pop) {\n      // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n      // @ts-expect-error TS18046\n      curValue[curValue.length] = value; // eslint-disable-line @typescript-eslint/no-unsafe-member-access\n    } else {\n      obj[key] = [curValue, value];\n    }\n  }\n}\n\nexport function parse(\n  qs: string,\n  sep?: string,\n  eq?: string,\n  options?: {\n    maxKeys?: number;\n    decodeURIComponent?: DecodeFunction;\n  }\n): Record<string, unknown> {\n  const obj = { __proto__: null };\n\n  if (typeof qs !== 'string' || qs.length === 0) {\n    return obj;\n  }\n\n  const sepCodes = !sep ? defSepCodes : charCodes(String(sep));\n  const eqCodes = !eq ? defEqCodes : charCodes(String(eq));\n  const sepLen = sepCodes.length;\n  const eqLen = eqCodes.length;\n\n  let pairs = 1000;\n  if (options && typeof options.maxKeys === 'number') {\n    // -1 is used in place of a value like Infinity for meaning\n    // \"unlimited pairs\" because of additional checks V8 (at least as of v5.4)\n    // has to do when using variables that contain values like Infinity. Since\n    // `pairs` is always decremented and checked explicitly for 0, -1 works\n    // effectively the same as Infinity, while providing a significant\n    // performance boost.\n    pairs = options.maxKeys > 0 ? options.maxKeys : -1;\n  }\n\n  let decode = unescape as DecodeFunction;\n  if (options && typeof options.decodeURIComponent === 'function') {\n    decode = options.decodeURIComponent;\n  }\n  const customDecode = decode !== unescape;\n\n  let lastPos = 0;\n  let sepIdx = 0;\n  let eqIdx = 0;\n  let key = '';\n  let value = '';\n  let keyEncoded = customDecode;\n  let valEncoded = customDecode;\n  const plusChar = customDecode ? '%20' : ' ';\n  let encodeCheck = 0;\n  for (let i = 0; i < qs.length; ++i) {\n    const code = qs.charCodeAt(i);\n\n    // Try matching key/value pair separator (e.g. '&')\n    if (code === sepCodes[sepIdx]) {\n      if (++sepIdx === sepLen) {\n        // Key/value pair separator match!\n        const end = i - sepIdx + 1;\n        if (eqIdx < eqLen) {\n          // We didn't find the (entire) key/value separator\n          if (lastPos < end) {\n            // Treat the substring as part of the key instead of the value\n            key += qs.slice(lastPos, end);\n          } else if (key.length === 0) {\n            // We saw an empty substring between separators\n            if (--pairs === 0) return obj;\n            lastPos = i + 1;\n            sepIdx = eqIdx = 0;\n            continue;\n          }\n        } else if (lastPos < end) {\n          value += qs.slice(lastPos, end);\n        }\n\n        addKeyVal(obj, key, value, keyEncoded, valEncoded, decode);\n\n        if (--pairs === 0) return obj;\n        keyEncoded = valEncoded = customDecode;\n        key = value = '';\n        encodeCheck = 0;\n        lastPos = i + 1;\n        sepIdx = eqIdx = 0;\n      }\n    } else {\n      sepIdx = 0;\n      // Try matching key/value separator (e.g. '=') if we haven't already\n      if (eqIdx < eqLen) {\n        if (code === eqCodes[eqIdx]) {\n          if (++eqIdx === eqLen) {\n            // Key/value separator match!\n            const end = i - eqIdx + 1;\n            if (lastPos < end) key += qs.slice(lastPos, end);\n            encodeCheck = 0;\n            lastPos = i + 1;\n          }\n          continue;\n        } else {\n          eqIdx = 0;\n          if (!keyEncoded) {\n            // Try to match an (valid) encoded byte once to minimize unnecessary\n            // calls to string decoding functions\n            if (code === 37 /* % */) {\n              encodeCheck = 1;\n              continue;\n            } else if (encodeCheck > 0) {\n              if (isHexTable[code] === 1) {\n                if (++encodeCheck === 3) keyEncoded = true;\n                continue;\n              } else {\n                encodeCheck = 0;\n              }\n            }\n          }\n        }\n        if (code === 43 /* + */) {\n          if (lastPos < i) key += qs.slice(lastPos, i);\n          key += plusChar;\n          lastPos = i + 1;\n          continue;\n        }\n      }\n      if (code === 43 /* + */) {\n        if (lastPos < i) value += qs.slice(lastPos, i);\n        value += plusChar;\n        lastPos = i + 1;\n      } else if (!valEncoded) {\n        // Try to match an (valid) encoded byte (once) to minimize unnecessary\n        // calls to string decoding functions\n        if (code === 37 /* % */) {\n          encodeCheck = 1;\n        } else if (encodeCheck > 0) {\n          if (isHexTable[code] === 1) {\n            if (++encodeCheck === 3) valEncoded = true;\n          } else {\n            encodeCheck = 0;\n          }\n        }\n      }\n    }\n  }\n\n  // Deal with any leftover key or value data\n  if (lastPos < qs.length) {\n    if (eqIdx < eqLen) key += qs.slice(lastPos);\n    else if (sepIdx < sepLen) value += qs.slice(lastPos);\n  } else if (eqIdx === 0 && key.length === 0) {\n    // We ended on an empty substring\n    return obj;\n  }\n\n  addKeyVal(obj, key, value, keyEncoded, valEncoded, decode);\n\n  return obj;\n}\n\n/**\n * V8 does not optimize functions with try-catch blocks, so we isolate them here\n * to minimize the damage (Note: no longer true as of V8 5.4 -- but still will\n * not be inlined).\n */\nfunction decodeStr(s: string, decoder: DecodeFunction): string {\n  try {\n    return decoder(s);\n  } catch {\n    return unescape(s, true);\n  }\n}\n"
  },
  {
    "path": "src/node/internal/internal_readline.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { EventEmitter } from 'node-internal:events';\nimport type { Abortable } from 'node:events';\nimport type Readline from 'node:readline';\n\n// This class provides a no-op stub implementation that matches unenv's behavior.\n// See: https://github.com/unjs/unenv/blob/main/src/runtime/node/internal/readline/interface.ts\n// Methods are no-ops or return sensible defaults rather than throwing errors,\n// which allows code that depends on readline to work without crashing.\nexport class Interface extends EventEmitter implements Readline.Interface {\n  terminal = false;\n  line = '';\n  cursor = 0;\n\n  getPrompt(): string {\n    return '';\n  }\n\n  setPrompt(_prompt: string): void {\n    // No-op\n  }\n\n  prompt(_preserveCursor?: boolean): void {\n    // No-op\n  }\n\n  question(query: string, callback: (answer: string) => void): void;\n  question(\n    query: string,\n    options: Abortable,\n    callback: (answer: string) => void\n  ): void;\n  question(\n    _query: string,\n    _optionsOrCallback: Abortable | ((answer: string) => void),\n    _callback?: (answer: string) => void\n  ): void {\n    // If callback is the second argument (no options)\n    if (typeof _optionsOrCallback === 'function') {\n      _optionsOrCallback('');\n    } else if (typeof _callback === 'function') {\n      _callback('');\n    }\n  }\n\n  pause(): this {\n    return this;\n  }\n\n  resume(): this {\n    return this;\n  }\n\n  close(): void {\n    // No-op\n  }\n\n  write(_data: unknown, _key?: unknown): void {\n    // No-op\n  }\n\n  getCursorPos(): Readline.CursorPos {\n    return {\n      rows: 0,\n      cols: 0,\n    };\n  }\n\n  [Symbol.dispose](): void {\n    this.close();\n  }\n\n  // eslint-disable-next-line @typescript-eslint/require-await\n  async [Symbol.asyncDispose](): Promise<void> {\n    this.close();\n  }\n\n  // Yield a single empty string so that `for await...of` loops complete\n  // immediately without blocking, consistent with no-op stub behavior.\n  // eslint-disable-next-line @typescript-eslint/require-await\n  async *[Symbol.asyncIterator](): NodeJS.AsyncIterator<string> {\n    yield '';\n  }\n}\n"
  },
  {
    "path": "src/node/internal/internal_readline_promises.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { EventEmitter } from 'node-internal:events';\nimport type { Abortable } from 'node:events';\nimport type ReadlineType from 'node:readline/promises';\nimport type { CursorPos, Direction } from 'node:readline';\n\n// This class provides a no-op stub implementation that matches unenv's behavior.\n// See: https://github.com/unjs/unenv/blob/main/src/runtime/node/internal/readline/promises/interface.ts\n// Methods are no-ops or return sensible defaults rather than throwing errors,\n// which allows code that depends on readline to work without crashing.\nexport class Interface extends EventEmitter implements ReadlineType.Interface {\n  terminal = false;\n  line = '';\n  cursor = 0;\n\n  getPrompt(): string {\n    return '';\n  }\n\n  setPrompt(_prompt: string): void {\n    // No-op\n  }\n\n  prompt(_preserveCursor?: boolean): void {\n    // No-op\n  }\n\n  question(query: string): Promise<string>;\n  question(query: string, options: Abortable): Promise<string>;\n  question(_query: string, _options?: Abortable): Promise<string> {\n    return Promise.resolve('');\n  }\n\n  pause(): this {\n    return this;\n  }\n\n  resume(): this {\n    return this;\n  }\n\n  close(): void {\n    // No-op\n  }\n\n  write(_data: unknown, _key?: unknown): void {\n    // No-op\n  }\n\n  getCursorPos(): CursorPos {\n    return {\n      rows: 0,\n      cols: 0,\n    };\n  }\n\n  [Symbol.dispose](): void {\n    this.close();\n  }\n\n  // eslint-disable-next-line @typescript-eslint/require-await\n  async [Symbol.asyncDispose](): Promise<void> {\n    this.close();\n  }\n\n  // Yield a single empty string so that `for await...of` loops complete\n  // immediately without blocking, consistent with no-op stub behavior.\n  // eslint-disable-next-line @typescript-eslint/require-await\n  async *[Symbol.asyncIterator](): NodeJS.AsyncIterator<string> {\n    yield '';\n  }\n}\n\n// This class provides a no-op stub implementation that matches unenv's behavior.\n// See: https://github.com/unjs/unenv/blob/main/src/runtime/node/internal/readline/promises/readline.ts\nexport class Readline implements ReadlineType.Readline {\n  clearLine(_dir: Direction): this {\n    return this;\n  }\n\n  clearScreenDown(): this {\n    return this;\n  }\n\n  commit(): Promise<void> {\n    return Promise.resolve();\n  }\n\n  cursorTo(_x: number, _y?: number): this {\n    return this;\n  }\n\n  moveCursor(_dx: number, _dy: number): this {\n    return this;\n  }\n\n  rollback(): this {\n    return this;\n  }\n}\n\nexport function createInterface(): ReadlineType.Interface {\n  return new Interface();\n}\n"
  },
  {
    "path": "src/node/internal/internal_stringdecoder.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { Buffer } from 'node-internal:internal_buffer';\nimport { normalizeEncoding } from 'node-internal:internal_utils';\nimport {\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_THIS,\n  ERR_UNKNOWN_ENCODING,\n} from 'node-internal:internal_errors';\nimport { default as bufferUtil } from 'node-internal:buffer';\nimport type { StringDecoder as _StringDecoder } from 'node:string_decoder';\n\nconst kIncompleteCharactersStart = 0;\nconst kIncompleteCharactersEnd = 4;\nconst kMissingBytes = 4;\nconst kBufferedBytes = 5;\nconst kEncoding = 6;\nconst kSize = 7;\n\n// The order of this array should be in sync with i18n.h\n// Encoding enum. Index of this array defines the uint8_t\n// value of an encoding.\nconst encodings = [\n  'ascii',\n  'latin1',\n  'utf8',\n  'utf16le',\n  'base64',\n  'base64url',\n  'hex',\n];\n\nconst kNativeDecoder = Symbol('kNativeDecoder');\n\n// @ts-expect-error TS2323 Cannot redeclare exported variable\nexport declare class StringDecoder extends _StringDecoder {\n  [kNativeDecoder]?: Buffer & {\n    [kBufferedBytes]?: number;\n    [kMissingBytes]?: number;\n    [kEncoding]?: number;\n  };\n\n  constructor(encoding?: string);\n\n  encoding: string;\n  readonly lastChar: Uint8Array;\n  readonly lastNeed: number;\n  readonly lastTotal: number;\n  write(buf: ArrayBufferView | DataView | string): string;\n  end(buf?: ArrayBufferView | DataView | string): string;\n  text(buf: ArrayBufferView | DataView | string, offset?: number): string;\n}\n\n// @ts-expect-error TS2323 Cannot redeclare exported variable\nexport function StringDecoder(\n  this: StringDecoder,\n  encoding: string = 'utf8'\n): StringDecoder {\n  const normalizedEncoding = normalizeEncoding(encoding);\n  if (normalizedEncoding === undefined) {\n    throw new ERR_UNKNOWN_ENCODING(encoding);\n  }\n  this[kNativeDecoder] = Buffer.alloc(kSize);\n  this[kNativeDecoder][kEncoding] = normalizedEncoding;\n  this.encoding = encodings[normalizedEncoding] as string;\n  return this;\n}\n\nfunction write(\n  this: StringDecoder,\n  buf: ArrayBufferView | DataView | string\n): string {\n  if (this[kNativeDecoder] === undefined) {\n    throw new ERR_INVALID_THIS('StringDecoder');\n  }\n  if (typeof buf === 'string') {\n    return buf;\n  }\n  if (!ArrayBuffer.isView(buf)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'buf',\n      ['Buffer', 'TypedArray', 'DataView', 'string'],\n      buf\n    );\n  }\n  const buffer = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);\n  return bufferUtil.decode(buffer, this[kNativeDecoder]);\n}\n\nfunction end(\n  this: StringDecoder,\n  buf?: ArrayBufferView | DataView | string\n): string {\n  if (this[kNativeDecoder] === undefined) {\n    throw new ERR_INVALID_THIS('StringDecoder');\n  }\n  let ret = '';\n  if (buf !== undefined) {\n    ret = this.write(buf);\n  }\n  if ((this[kNativeDecoder][kBufferedBytes] as number) > 0) {\n    ret += bufferUtil.flush(this[kNativeDecoder]);\n  }\n  return ret;\n}\n\nfunction text(\n  this: StringDecoder,\n  buf: NodeJS.TypedArray | string,\n  offset?: number\n): string {\n  if (this[kNativeDecoder] === undefined) {\n    throw new ERR_INVALID_THIS('StringDecoder');\n  }\n  this[kNativeDecoder][kMissingBytes] = 0;\n  this[kNativeDecoder][kBufferedBytes] = 0;\n  return this.write(buf.slice(offset));\n}\n\nStringDecoder.prototype.write = write;\nStringDecoder.prototype.end = end;\nStringDecoder.prototype.text = text;\n\nObject.defineProperties(StringDecoder.prototype, {\n  lastChar: {\n    enumerable: true,\n    get(this: StringDecoder): Buffer {\n      if (this[kNativeDecoder] === undefined) {\n        throw new ERR_INVALID_THIS('StringDecoder');\n      }\n      return this[kNativeDecoder].subarray(\n        kIncompleteCharactersStart,\n        kIncompleteCharactersEnd\n      ) as Buffer;\n    },\n  },\n  lastNeed: {\n    enumerable: true,\n    get(this: StringDecoder): number {\n      if (this[kNativeDecoder] === undefined) {\n        throw new ERR_INVALID_THIS('StringDecoder');\n      }\n      return this[kNativeDecoder][kMissingBytes] as number;\n    },\n  },\n  lastTotal: {\n    enumerable: true,\n    get(this: StringDecoder): number {\n      if (this[kNativeDecoder] === undefined) {\n        throw new ERR_INVALID_THIS('StringDecoder');\n      }\n      return (\n        (this[kNativeDecoder][kBufferedBytes] ?? 0) +\n        (this[kNativeDecoder][kMissingBytes] ?? 0)\n      );\n    },\n  },\n});\n\nexport default {\n  StringDecoder,\n};\n"
  },
  {
    "path": "src/node/internal/internal_timers.ts",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { validateFunction } from 'node-internal:validators';\nimport { default as timersUtil } from 'node-internal:timers';\n\n// Capture original global timer functions at module load time, before they might be\n// overridden by the enable_nodejs_global_timers compat flag. This avoids circular\n// dependency issues when our setTimeout/setInterval return Timeout objects.\n// Cast to number-returning functions since we know we're capturing the original\n// implementations before any override.\nconst originalSetTimeout = globalThis.setTimeout.bind(globalThis) as (\n  callback: (...args: unknown[]) => unknown,\n  ms?: number,\n  ...args: unknown[]\n) => number;\nconst originalSetInterval = globalThis.setInterval.bind(globalThis) as (\n  callback: (...args: unknown[]) => unknown,\n  ms?: number,\n  ...args: unknown[]\n) => number;\nconst originalClearTimeout = globalThis.clearTimeout.bind(globalThis) as (\n  id?: number\n) => void;\nconst originalClearInterval = globalThis.clearInterval.bind(globalThis) as (\n  id?: number\n) => void;\n\nlet clearTimeoutImpl: (obj: Timeout) => void;\n\nexport class Timeout {\n  #timer: number;\n  #callback: (...args: unknown[]) => unknown;\n  #after: number;\n  #args: unknown[];\n  #isRepeat: boolean;\n  #isRefed: boolean;\n\n  constructor(\n    callback: (...args: unknown[]) => unknown,\n    after: number = 1,\n    args: unknown[] = [],\n    isRepeat: boolean = false,\n    isRefed: boolean = false\n  ) {\n    this.#callback = callback;\n    // Left it as multiply by 1 due to make the behavior as similar to Node.js\n    // as possible.\n    this.#after = after * 1;\n    this.#args = args;\n    this.#isRepeat = isRepeat;\n    this.#isRefed = isRefed;\n    this.#timer = this.#constructTimer();\n  }\n\n  #constructTimer(): number {\n    this.#timer = this.#isRepeat\n      ? originalSetInterval(this.#callback, this.#after, ...this.#args)\n      : originalSetTimeout(this.#callback, this.#after, ...this.#args);\n    return this.#timer;\n  }\n\n  #clearTimeout(): void {\n    if (this.#isRepeat) {\n      originalClearInterval(this.#timer);\n    } else {\n      originalClearTimeout(this.#timer);\n    }\n  }\n\n  refresh(): this {\n    this.#clearTimeout();\n    this.#constructTimer();\n    return this;\n  }\n\n  unref(): this {\n    // Intentionally left as no-op.\n    this.#isRefed = false;\n    return this;\n  }\n\n  ref(): this {\n    // Intentionally left as no-op.\n    this.#isRefed = true;\n    return this;\n  }\n\n  hasRef(): boolean {\n    return this.#isRefed;\n  }\n\n  close(): this {\n    this.#clearTimeout();\n    return this;\n  }\n\n  [Symbol.dispose](): void {\n    this.#clearTimeout();\n  }\n\n  [Symbol.toPrimitive](): number {\n    return this.#timer;\n  }\n\n  static {\n    clearTimeoutImpl = (obj: Timeout): void => {\n      obj.#clearTimeout();\n    };\n  }\n}\n\nexport function setTimeout<TArgs extends unknown[]>(\n  callback: (...args: TArgs) => unknown,\n  delay?: number,\n  ...args: TArgs\n): Timeout {\n  validateFunction(callback, 'callback');\n\n  return new Timeout(\n    callback,\n    delay,\n    args,\n    /* isRepeat */ false,\n    /* isRefed */ true\n  );\n}\n\nexport function clearTimeout(\n  timer: Timeout | string | number | undefined\n): void {\n  if (timer instanceof Timeout) {\n    clearTimeoutImpl(timer);\n  } else if (typeof timer === 'number') {\n    originalClearTimeout(timer);\n  }\n}\n\nexport const setImmediate = timersUtil.setImmediate.bind(timersUtil);\n\nexport const clearImmediate = timersUtil.clearImmediate.bind(timersUtil);\n\nexport function setInterval<TArgs extends unknown[]>(\n  callback: (...args: TArgs) => void,\n  repeat?: number,\n  ...args: TArgs\n): Timeout {\n  validateFunction(callback, 'callback');\n  return new Timeout(\n    callback,\n    repeat,\n    args,\n    /* isRepeat */ true,\n    /* isRefed */ true\n  );\n}\n\nexport function clearInterval(\n  timer: Timeout | string | number | undefined\n): void {\n  if (timer instanceof Timeout) {\n    clearTimeoutImpl(timer);\n  } else if (typeof timer === 'number') {\n    originalClearInterval(timer);\n  }\n}\n\n/**\n * @deprecated Please use timeout.refresh() instead.\n */\nexport function active(timer: Timeout | string | number | undefined): void {\n  if (timer instanceof Timeout) {\n    timer.refresh();\n  }\n}\n\n/**\n * @deprecated Please use clearTimeout instead.\n */\nexport function unenroll(timer: unknown): void {\n  if (timer instanceof Timeout) {\n    clearTimeoutImpl(timer);\n  } else if (typeof timer === 'number') {\n    originalClearTimeout(timer);\n  }\n}\n\n/**\n * @deprecated Please use setTimeout instead.\n */\nexport function enroll(_item: unknown, _msecs: number): void {\n  throw new Error('Not implemented. Please use setTimeout() instead.');\n}\n"
  },
  {
    "path": "src/node/internal/internal_timers_global_override.ts",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// This module overrides globalThis timer functions with Node.js-compatible versions\n// when loaded. It is loaded by worker.c++ when enable_nodejs_global_timers compat\n// flag is enabled.\n//\n// After loading:\n// - globalThis.setTimeout and globalThis.setInterval return Timeout objects\n//   with methods like refresh(), ref(), unref(), and hasRef() instead of numeric IDs\n// - globalThis.setImmediate and globalThis.clearImmediate are available\n\nimport {\n  setTimeout,\n  setInterval,\n  clearTimeout,\n  clearInterval,\n} from 'node-internal:internal_timers';\n\nglobalThis.setTimeout = setTimeout as unknown as typeof globalThis.setTimeout;\nglobalThis.setInterval =\n  setInterval as unknown as typeof globalThis.setInterval;\nglobalThis.clearTimeout =\n  clearTimeout as unknown as typeof globalThis.clearTimeout;\nglobalThis.clearInterval =\n  clearInterval as unknown as typeof globalThis.clearInterval;\n"
  },
  {
    "path": "src/node/internal/internal_timers_promises.ts",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport * as timers from 'node-internal:internal_timers';\nimport { ERR_INVALID_THIS, AbortError } from 'node-internal:internal_errors';\nimport {\n  validateNumber,\n  validateAbortSignal,\n  validateBoolean,\n  validateObject,\n} from 'node-internal:validators';\n\nconst kScheduler = Symbol.for('kScheduler');\n\ntype OnCancelCallback = (() => void) | undefined;\n\nexport async function setTimeout<T = void>(\n  delay?: number,\n  value?: T,\n  options: { signal?: AbortSignal | undefined; ref?: boolean | undefined } = {}\n): Promise<T> {\n  if (delay !== undefined) {\n    validateNumber(delay, 'delay');\n  }\n\n  validateObject(options, 'options');\n\n  // Ref options is a no-op.\n  const { signal, ref } = options;\n\n  if (signal !== undefined) {\n    validateAbortSignal(signal, 'options.signal');\n  }\n\n  // This is required due to consistency/compat reasons, even if it's a no-op.\n  if (ref !== undefined) {\n    validateBoolean(ref, 'options.ref');\n  }\n\n  if (signal?.aborted) {\n    throw new AbortError(undefined, { cause: signal.reason });\n  }\n\n  const { promise, resolve, reject } = Promise.withResolvers<T>();\n  let onCancel: OnCancelCallback;\n\n  const timer = timers.setTimeout(() => {\n    resolve(value as T);\n    if (onCancel) {\n      signal?.removeEventListener('abort', onCancel);\n    }\n  }, delay ?? 1);\n\n  if (signal) {\n    onCancel = (): void => {\n      timers.clearTimeout(timer);\n      reject(new AbortError(undefined, { cause: signal.reason }));\n    };\n    signal.addEventListener('abort', onCancel, { once: true });\n  }\n\n  return promise;\n}\n\nexport async function setImmediate<T = void>(\n  value?: T,\n  options: { signal?: AbortSignal | undefined; ref?: boolean | undefined } = {}\n): Promise<T> {\n  validateObject(options, 'options');\n\n  // Ref options is a no-op.\n  const { signal, ref } = options;\n\n  if (signal !== undefined) {\n    validateAbortSignal(signal, 'options.signal');\n  }\n\n  // This is required due to consistency/compat reasons, even if it's a no-op.\n  if (ref !== undefined) {\n    validateBoolean(ref, 'options.ref');\n  }\n\n  if (signal?.aborted) {\n    throw new AbortError(undefined, { cause: signal.reason });\n  }\n\n  const { promise, resolve, reject } = Promise.withResolvers<T>();\n  let onCancel: OnCancelCallback;\n\n  const timer = timers.setImmediate(() => {\n    resolve(value as T);\n    if (onCancel) {\n      signal?.removeEventListener('abort', onCancel);\n    }\n  });\n\n  if (signal) {\n    onCancel = (): void => {\n      timers.clearImmediate(timer);\n      reject(new AbortError(undefined, { cause: signal.reason }));\n    };\n    signal.addEventListener('abort', onCancel, { once: true });\n  }\n\n  return promise;\n}\n\nexport async function* setInterval<T = void>(\n  delay?: number,\n  value?: T,\n  options: {\n    signal?: AbortSignal | undefined;\n    ref?: boolean | undefined;\n  } = {}\n): AsyncGenerator<T, undefined> {\n  if (delay !== undefined) {\n    validateNumber(delay, 'delay');\n  }\n\n  validateObject(options, 'options');\n\n  // Ref options is a no-op.\n  const { signal, ref } = options;\n\n  if (signal !== undefined) {\n    validateAbortSignal(signal, 'options.signal');\n  }\n\n  if (ref !== undefined) {\n    validateBoolean(ref, 'options.ref');\n  }\n\n  if (signal?.aborted) {\n    throw new AbortError(undefined, { cause: signal.reason });\n  }\n\n  let onCancel: OnCancelCallback;\n  let interval: timers.Timeout;\n  try {\n    let notYielded = 0;\n    let callback: ((promise?: Promise<void>) => void) | undefined;\n    interval = new timers.Timeout(\n      () => {\n        notYielded++;\n        callback?.();\n        callback = undefined;\n      },\n      delay,\n      undefined,\n      true,\n      ref\n    );\n\n    if (signal) {\n      onCancel = (): void => {\n        timers.clearInterval(interval);\n        callback?.(\n          Promise.reject(new AbortError(undefined, { cause: signal.reason }))\n        );\n        callback = undefined;\n      };\n      signal.addEventListener('abort', onCancel, { once: true });\n    }\n\n    while (!signal?.aborted) {\n      if (notYielded === 0) {\n        await new Promise((resolve) => (callback = resolve));\n      }\n      for (; notYielded > 0; notYielded--) {\n        yield value as T;\n      }\n    }\n    throw new AbortError(undefined, { cause: signal.reason });\n  } finally {\n    // @ts-expect-error TS2454 TS detects invalid use before assignment.\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    if (interval) {\n      timers.clearInterval(interval);\n    }\n    if (onCancel) {\n      signal?.removeEventListener('abort', onCancel);\n    }\n  }\n}\n\ndeclare global {\n  var scheduler: {\n    wait: (delay: number, options?: { signal?: AbortSignal }) => Promise<void>;\n  };\n}\n\n// TODO(@jasnell): Scheduler is an API currently being discussed by WICG\n// for Web Platform standardization: https://github.com/WICG/scheduling-apis\n// The scheduler.yield() and scheduler.wait() methods correspond roughly to\n// the awaitable setTimeout and setImmediate implementations here. This api\n// should be considered to be experimental until the spec for these are\n// finalized. Note, also, that Scheduler is expected to be defined as a global,\n// but while the API is experimental we shouldn't expose it as such.\nclass Scheduler {\n  [kScheduler] = true;\n\n  yield(): Promise<void> {\n    if (!this[kScheduler]) throw new ERR_INVALID_THIS('Scheduler');\n    return setImmediate();\n  }\n\n  wait(...args: Parameters<typeof globalThis.scheduler.wait>): Promise<void> {\n    if (!this[kScheduler]) throw new ERR_INVALID_THIS('Scheduler');\n    return globalThis.scheduler.wait(...args);\n  }\n}\n\nexport const scheduler = new Scheduler();\n"
  },
  {
    "path": "src/node/internal/internal_tls.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport type { PeerCertificate } from 'node:tls';\nimport { isIP } from 'node-internal:internal_net';\nimport { default as urlUtil } from 'node-internal:url';\nimport {\n  ERR_TLS_CERT_ALTNAME_INVALID,\n  ERR_TLS_CERT_ALTNAME_FORMAT,\n  ERR_OUT_OF_RANGE,\n} from 'node-internal:internal_errors';\nimport { isUint8Array, isArrayBufferView } from 'node-internal:internal_types';\n\n// String#toLowerCase() is locale-sensitive so we use\n// a conservative version that only lowercases A-Z.\nfunction toLowerCase(c: string): string {\n  return String.fromCharCode(32 + c.charCodeAt(0));\n}\n\nfunction unfqdn(host: string): string {\n  return host.replace(/[.]$/, '');\n}\n\nfunction splitHost(host: string): string[] {\n  return unfqdn(host).replace(/[A-Z]/g, toLowerCase).split('.');\n}\n\nfunction check(\n  hostParts: string[],\n  pattern: string | undefined | null,\n  wildcards: boolean\n): boolean {\n  // Empty strings, null, undefined, etc. never match.\n  if (!pattern) return false;\n\n  const patternParts = splitHost(pattern);\n\n  if (hostParts.length !== patternParts.length) return false;\n\n  // Pattern has empty components, e.g. \"bad..example.com\".\n  if (patternParts.includes('')) return false;\n\n  // RFC 6125 allows IDNA U-labels (Unicode) in names but we have no\n  // good way to detect their encoding or normalize them so we simply\n  // reject them.  Control characters and blanks are rejected as well\n  // because nothing good can come from accepting them.\n  const isBad = (s: string): boolean => /[^\\u0021-\\u007F]/u.test(s);\n  if (patternParts.some(isBad)) return false;\n\n  // Check host parts from right to left first.\n  for (let i = hostParts.length - 1; i > 0; i -= 1) {\n    if (hostParts[i] !== patternParts[i]) return false;\n  }\n\n  const hostSubdomain = hostParts[0] as string;\n  const patternSubdomain = patternParts[0] as string;\n  const patternSubdomainParts = patternSubdomain.split('*', 3);\n\n  // Short-circuit when the subdomain does not contain a wildcard.\n  // RFC 6125 does not allow wildcard substitution for components\n  // containing IDNA A-labels (Punycode) so match those verbatim.\n  if (patternSubdomainParts.length === 1 || patternSubdomain.includes('xn--'))\n    return hostSubdomain === patternSubdomain;\n\n  if (!wildcards) return false;\n\n  // More than one wildcard is always wrong.\n  if (patternSubdomainParts.length > 2) return false;\n\n  // *.tld wildcards are not allowed.\n  if (patternParts.length <= 2) return false;\n\n  const { 0: prefix, 1: suffix } = patternSubdomainParts as [string, string];\n\n  if (prefix.length + suffix.length > hostSubdomain.length) return false;\n\n  if (!hostSubdomain.startsWith(prefix)) return false;\n\n  if (!hostSubdomain.endsWith(suffix)) return false;\n\n  return true;\n}\n\n// This pattern is used to determine the length of escaped sequences within\n// the subject alt names string. It allows any valid JSON string literal.\n// This MUST match the JSON specification (ECMA-404 / RFC8259) exactly.\nconst jsonStringPattern =\n  // eslint-disable-next-line no-control-regex\n  /^\"(?:[^\"\\\\\\u0000-\\u001f]|\\\\(?:[\"\\\\/bfnrt]|u[0-9a-fA-F]{4}))*\"/;\n\nfunction splitEscapedAltNames(altNames: string): string[] {\n  const result = [];\n  let currentToken = '';\n  let offset = 0;\n  while (offset !== altNames.length) {\n    const nextSep = altNames.indexOf(',', offset);\n    const nextQuote = altNames.indexOf('\"', offset);\n    if (nextQuote !== -1 && (nextSep === -1 || nextQuote < nextSep)) {\n      // There is a quote character and there is no separator before the quote.\n      currentToken += altNames.substring(offset, nextQuote);\n      const match = jsonStringPattern.exec(altNames.substring(nextQuote));\n      if (!match) {\n        throw new ERR_TLS_CERT_ALTNAME_FORMAT();\n      }\n      currentToken += JSON.parse(match[0]) as string;\n      offset = nextQuote + match[0].length;\n    } else if (nextSep !== -1) {\n      // There is a separator and no quote before it.\n      currentToken += altNames.substring(offset, nextSep);\n      result.push(currentToken);\n      currentToken = '';\n      offset = nextSep + 2;\n    } else {\n      currentToken += altNames.substring(offset);\n      offset = altNames.length;\n    }\n  }\n  result.push(currentToken);\n  return result;\n}\n\nexport function checkServerIdentity(\n  hostname: string,\n  cert: Partial<PeerCertificate>\n): Error | undefined {\n  const subject = cert.subject;\n  const altNames = cert.subjectaltname;\n  const dnsNames: string[] = [];\n  const ips: string[] = [];\n\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion\n  hostname = '' + hostname;\n\n  if (altNames) {\n    const splitAltNames = altNames.includes('\"')\n      ? splitEscapedAltNames(altNames)\n      : altNames.split(', ');\n    splitAltNames.forEach((name) => {\n      if (name.startsWith('DNS:')) {\n        dnsNames.push(name.slice(4));\n      } else if (name.startsWith('IP Address:')) {\n        ips.push(urlUtil.canonicalizeIp(name.slice(11)));\n      }\n    });\n  }\n\n  let valid = false;\n  let reason = 'Unknown reason';\n\n  hostname = unfqdn(hostname); // Remove trailing dot for error messages.\n\n  if (isIP(hostname)) {\n    valid = ips.includes(urlUtil.canonicalizeIp(hostname));\n    if (!valid)\n      reason = `IP: ${hostname} is not in the cert's list: ` + ips.join(', ');\n  } else if (dnsNames.length > 0 || subject?.CN) {\n    const hostParts = splitHost(hostname);\n    const wildcard = (pattern: string): boolean =>\n      check(hostParts, pattern, true);\n\n    if (dnsNames.length > 0) {\n      valid = dnsNames.some(wildcard);\n      if (!valid)\n        reason = `Host: ${hostname}. is not in the cert's altnames: ${altNames}`;\n    } else {\n      // Match against Common Name only if no supported identifiers exist.\n      const cn = subject?.CN;\n\n      if (Array.isArray(cn)) valid = cn.some(wildcard);\n      else if (cn) valid = wildcard(cn);\n\n      if (!valid) reason = `Host: ${hostname}. is not cert's CN: ${cn}`;\n    }\n  } else {\n    reason = 'Cert does not contain a DNS name';\n  }\n\n  if (!valid) {\n    return new ERR_TLS_CERT_ALTNAME_INVALID(reason, hostname, cert);\n  }\n  return undefined;\n}\n\n// Convert protocols array into valid OpenSSL protocols list\n// (\"\\x06spdy/2\\x08http/1.1\\x08http/1.0\")\nfunction convertProtocols(protocols: string[]): Buffer {\n  const lens = Array.from({ length: protocols.length }, () => 0);\n  const buff = Buffer.allocUnsafe(\n    protocols.reduce((p, c, i) => {\n      const len = Buffer.byteLength(c);\n      if (len > 255) {\n        throw new ERR_OUT_OF_RANGE(\n          'The byte length of the protocol at index ' +\n            `${i} exceeds the maximum length.`,\n          '<= 255',\n          len,\n          true\n        );\n      }\n      lens[i] = len;\n      return p + 1 + len;\n    }, 0)\n  );\n\n  let offset = 0;\n  for (let i = 0, c = protocols.length; i < c; i++) {\n    buff[offset++] = lens[i] as number;\n    buff.write(protocols[i] as string, offset);\n    offset += lens[i] as number;\n  }\n\n  return buff;\n}\n\nexport function convertALPNProtocols(\n  protocols: unknown,\n  out: {\n    ALPNProtocols: Buffer;\n  }\n): void {\n  // If protocols is Array - translate it into buffer\n  if (Array.isArray(protocols)) {\n    out.ALPNProtocols = convertProtocols(protocols as string[]);\n  } else if (isUint8Array(protocols)) {\n    // Copy new buffer not to be modified by user.\n    out.ALPNProtocols = Buffer.from(protocols);\n  } else if (isArrayBufferView(protocols)) {\n    out.ALPNProtocols = Buffer.from(\n      protocols.buffer.slice(\n        protocols.byteOffset,\n        protocols.byteOffset + protocols.byteLength\n      )\n    );\n  }\n}\n\nexport function createServer(): void {\n  throw new Error('Not implemented');\n}\n\nexport function Server(): void {\n  throw new Error('Not implemented');\n}\n\nexport function getCiphers(): void {\n  throw new Error('Not implemented');\n}\n"
  },
  {
    "path": "src/node/internal/internal_tls_common.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport type tls from 'node:tls';\nimport { validateInteger } from 'node-internal:validators';\n\n// @ts-expect-error TS2323 Redeclare error.\nexport declare class SecureContext {\n  context: unknown;\n  constructor(\n    _secureProtocol?: string,\n    secureOptions?: number,\n    minVersion?: string,\n    maxVersion?: string\n  );\n}\n\n// This is intentionally not fully compatible with Node.js implementation\n// since creating and customizing an actually equivalent SecureContext is not supported.\n// @ts-expect-error TS2323 Redeclare error.\nexport function SecureContext(\n  this: SecureContext,\n  secureProtocol?: string,\n  secureOptions?: number,\n  minVersion?: string,\n  maxVersion?: string\n): SecureContext {\n  if (!(this instanceof SecureContext)) {\n    return new SecureContext(\n      secureProtocol,\n      secureOptions,\n      minVersion,\n      maxVersion\n    );\n  }\n  // We do not support the minVersion and maxVersion options at this\n  // time and will just ignore them if they are passed.\n  // We have to omit these errors in order to support mssql.\n  //\n  // if (minVersion !== undefined) {\n  //   throw new ERR_OPTION_NOT_IMPLEMENTED('minVersion');\n  // }\n  // if (maxVersion !== undefined) {\n  //   throw new ERR_OPTION_NOT_IMPLEMENTED('maxVersion');\n  // }\n\n  if (secureOptions !== undefined) {\n    validateInteger(secureOptions, 'secureOptions');\n  }\n\n  this.context = undefined;\n  return this;\n}\n\nexport function createSecureContext(\n  options: tls.SecureContextOptions = {}\n): SecureContext {\n  return new SecureContext(\n    options.secureProtocol,\n    options.secureOptions,\n    options.minVersion,\n    options.maxVersion\n  );\n}\n\n// Translate some fields from the handle's C-friendly format into more idiomatic\n// javascript object representations before passing them back to the user.  Can\n// be used on any cert object, but changing the name would be semver-major.\nexport function translatePeerCertificate(\n  c?: tls.DetailedPeerCertificate\n): null | tls.DetailedPeerCertificate {\n  if (!c) return null;\n\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  if (c.issuerCertificate != null && c.issuerCertificate !== c) {\n    c.issuerCertificate = translatePeerCertificate(\n      c.issuerCertificate\n    ) as tls.DetailedPeerCertificate;\n  }\n  if (c.infoAccess != null) {\n    // Type is ignored due to @types/node inconsistency\n    const info = c.infoAccess as unknown as string;\n    // @ts-expect-error TS2322 Ignored due to missing __proto__ type.\n    c.infoAccess = { __proto__: null };\n\n    // XXX: More key validation?\n    info.replace(\n      /([^\\n:]*):([^\\n]*)(?:\\n|$)/g,\n      // @ts-expect-error TS2349 @types/node inconsistency\n      (_all: string, key: string, val: string): void => {\n        if (val.charCodeAt(0) === 0x22) {\n          // The translatePeerCertificate function is only\n          // used on internally created legacy certificate\n          // objects, and any value that contains a quote\n          // will always be a valid JSON string literal,\n          // so this should never throw.\n          val = JSON.parse(val) as string;\n        }\n        if (c.infoAccess != null) {\n          c.infoAccess[key] ??= [];\n          c.infoAccess[key].push(val);\n        }\n      }\n    );\n  }\n  return c;\n}\n"
  },
  {
    "path": "src/node/internal/internal_tls_constants.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations\n// every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more\n// renegotiations are seen. The settings are applied to all remote client\n// connections.\nexport const CLIENT_RENEG_LIMIT = 3;\nexport const CLIENT_RENEG_WINDOW = 600;\nexport const DEFAULT_CIPHERS = '';\nexport const DEFAULT_ECDH_CURVE = 'auto';\nexport const DEFAULT_MIN_VERSION = 'TLSv1.2';\nexport const DEFAULT_MAX_VERSION = 'TLSv1.3';\nexport const rootCertificates = [];\n"
  },
  {
    "path": "src/node/internal/internal_tls_jsstream.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n/* eslint-disable @typescript-eslint/no-redundant-type-constituents */\n\nimport { notStrictEqual } from 'node-internal:internal_assert';\nimport { Socket } from 'node-internal:internal_net';\nimport { ERR_STREAM_WRAP } from 'node-internal:internal_errors';\nimport { Duplex, toBYOBWeb } from 'node-internal:streams_duplex';\nimport type {\n  SocketInfo,\n  Writer,\n  Socket as CloudflareSocket,\n} from 'node-internal:sockets';\n\nconst kCurrentWriteRequest = Symbol('kCurrentWriteRequest');\nconst kCurrentShutdownRequest = Symbol('kCurrentShutdownRequest');\nconst kPendingShutdownRequest = Symbol('kPendingShutdownRequest');\nconst kPendingClose = Symbol('kPendingClose');\n\n/* This class serves as a wrapper for when the C++ side of Node wants access\n * to a standard JS stream. For example, TLS or HTTP do not operate on network\n * resources conceptually, although that is the common case and what we are\n * optimizing for; in theory, they are completely composable and can work with\n * any stream resource they see.\n *\n * For the common case, i.e. a TLS socket wrapping around a net.Socket, we\n * can skip going through the JS layer and let TLS access the raw C++ handle\n * of a net.Socket. The flipside of this is that, to maintain composability,\n * we need a way to create \"fake\" net.Socket instances that call back into a\n * \"real\" JavaScript stream. JSStreamSocket is exactly this.\n */\nexport class JSStreamSocket extends Socket {\n  stream: Duplex;\n  [kCurrentWriteRequest]: null | unknown;\n  [kCurrentShutdownRequest]: null | unknown;\n  [kPendingShutdownRequest]: null | unknown;\n  [kPendingClose]: boolean;\n\n  constructor(stream: Duplex) {\n    // eslint-disable-next-line @typescript-eslint/no-invalid-void-type\n    const closePromise = Promise.withResolvers<void>();\n    const openPromise = Promise.withResolvers<SocketInfo>();\n\n    const webStream = toBYOBWeb(stream);\n    Object.assign(webStream.writable, {\n      // eslint-disable-next-line @typescript-eslint/require-await\n      write: async (data: string | ArrayBufferView): Promise<void> => {\n        stream.write(data);\n      },\n      closed: closePromise.promise,\n      releaseLock: async (): Promise<void> => {},\n    });\n    const handle: Socket['_handle'] = {\n      reading: true,\n      bytesRead: 0,\n      bytesWritten: 0,\n      socket: {\n        startTls(): CloudflareSocket {\n          throw new Error(\n            'startTls() should not be called for a duplex stream'\n          );\n        },\n        upgraded: false,\n        secureTransport: 'off',\n        closed: closePromise.promise,\n        close: async (): Promise<void> => {\n          queueMicrotask(() => {\n            closePromise.resolve();\n          });\n          return closePromise.promise;\n        },\n        opened: openPromise.promise,\n        readable: webStream.readable,\n        writable: webStream.writable as unknown as Writer,\n      },\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n      reader: new ReadableStreamBYOBReader(webStream.readable),\n      writer: new WritableStreamDefaultWriter<unknown>(webStream.writable),\n      options: {\n        host: '0.0.0.0',\n        port: 0,\n        addressType: 4,\n      },\n    };\n\n    stream.pause();\n    stream.on('error', (err) => this.emit('error', err));\n    const ondata = (chunk: string | Buffer): void => {\n      // eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare\n      if (typeof chunk === 'string' || stream.readableObjectMode === true) {\n        // Make sure that no further `data` events will happen.\n        stream.pause();\n        stream.removeListener('data', ondata);\n\n        this.emit('error', new ERR_STREAM_WRAP());\n        return;\n      }\n\n      // TODO(soon): We need to trigger read() result for _handle.reader.read(buf) call\n      // in node:net.\n    };\n    stream.on('data', ondata);\n    stream.once('end', () => {\n      closePromise.resolve();\n    });\n    // Some `Stream` don't pass `hasError` parameters when closed.\n    stream.once('close', () => {\n      // Errors emitted from `stream` have also been emitted to this instance\n      // so that we don't pass errors to `destroy()` again.\n      this.destroy();\n    });\n\n    super({ handle });\n    this.stream = stream;\n    this[kCurrentWriteRequest] = null;\n    this[kCurrentShutdownRequest] = null;\n    this[kPendingShutdownRequest] = null;\n    this[kPendingClose] = false;\n    this.readable = stream.readable;\n    this.writable = stream.writable;\n\n    // eslint-disable-next-line @typescript-eslint/no-floating-promises\n    handle.socket.closed.then(this.doClose.bind(this));\n\n    openPromise.resolve({});\n\n    // Start reading.\n    this.read(0);\n  }\n\n  isClosing(): boolean {\n    return !this.readable || !this.writable;\n  }\n\n  readStart(): number {\n    this.stream.resume();\n    return 0;\n  }\n\n  readStop(): number {\n    this.stream.pause();\n    return 0;\n  }\n\n  doShutdown(req: unknown): number {\n    // TODO(addaleax): It might be nice if we could get into a state where\n    // DoShutdown() is not called on streams while a write is still pending.\n    //\n    // Currently, the only part of the code base where that happens is the\n    // TLS implementation, which calls both DoWrite() and DoShutdown() on the\n    // underlying network stream inside of its own DoShutdown() method.\n    // Working around that on the native side is not quite trivial (yet?),\n    // so for now that is supported here.\n\n    if (this[kCurrentWriteRequest] !== null) {\n      this[kPendingShutdownRequest] = req;\n      return 0;\n    }\n\n    this[kCurrentShutdownRequest] = req;\n\n    if (this[kPendingClose]) {\n      // If doClose is pending, the stream & this._handle are gone. We can't do\n      // anything. doClose will call finishShutdown with ECANCELED for us shortly.\n      return 0;\n    }\n\n    const handle = this._handle;\n    notStrictEqual(handle, null);\n\n    queueMicrotask(() => {\n      // Ensure that write is dispatched asynchronously.\n      this.stream.end();\n    });\n    return 0;\n  }\n\n  doClose(): void {\n    this[kPendingClose] = true;\n\n    const handle = this._handle;\n\n    // When sockets of the \"net\" module destroyed, they will call\n    // `this._handle.close()` which will also emit EOF if not emitted before.\n    // This feature makes sockets on the other side emit \"end\" and \"close\"\n    // even though we haven't called `end()`. As `stream` are likely to be\n    // instances of `net.Socket`, calling `stream.destroy()` manually will\n    // avoid issues that don't properly close wrapped connections.\n    this.stream.destroy();\n\n    queueMicrotask(() => {\n      notStrictEqual(handle, null);\n      this[kPendingClose] = false;\n    });\n  }\n}\n"
  },
  {
    "path": "src/node/internal/internal_tls_wrap.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport {\n  Socket,\n  type SocketOptions,\n  _normalizeArgs,\n  onConnectionOpened,\n  onConnectionClosed,\n  tryReadStart,\n} from 'node-internal:internal_net';\nimport { JSStreamSocket } from 'node-internal:internal_tls_jsstream';\nimport { checkServerIdentity } from 'node-internal:internal_tls';\nimport type {\n  ConnectionOptions,\n  TlsOptions,\n  TLSSocket as TLSSocketType,\n} from 'node:tls';\nimport type { Duplex } from 'node-internal:streams_duplex';\nimport type { OnReadOpts, TcpSocketConnectOpts } from 'node:net';\nimport {\n  validateBuffer,\n  validateString,\n  validateObject,\n  validateNumber,\n  validateFunction,\n  validateInt32,\n  validateUint32,\n} from 'node-internal:validators';\nimport {\n  ConnResetException,\n  ERR_TLS_HANDSHAKE_TIMEOUT,\n  ERR_OPTION_NOT_IMPLEMENTED,\n  ERR_TLS_INVALID_CONTEXT,\n} from 'node-internal:internal_errors';\nimport { SecureContext } from 'node-internal:internal_tls_common';\nimport { ok } from 'node-internal:internal_assert';\n\nconst kConnectOptions = Symbol('connect-options');\nconst kErrorEmitted = Symbol('error-emitted');\nconst kRes = Symbol('res');\nconst kPendingSession = Symbol('pendingSession');\nconst kIsVerified = Symbol('verified');\n\n// @ts-expect-error TS2323 Cannot redeclare error.\nexport declare class TLSSocket extends Socket {\n  _hadError: boolean;\n  _handle: Socket['_handle'];\n  _init(): void;\n  _tlsOptions: TlsOptions &\n    ConnectionOptions &\n    SocketOptions &\n    TcpSocketConnectOpts & {\n      isServer?: boolean;\n      requestOCSP?: boolean;\n      server?: unknown;\n      onread?: OnReadOpts;\n    };\n  _secureEstablished: boolean;\n  _securePending: boolean;\n  _newSessionPending: boolean;\n  _controlReleased: boolean;\n  authorized: boolean;\n  encrypted: boolean;\n  handle: ReturnType<TLSSocket['_wrapHandle']>;\n  servername: null | string;\n  secureConnecting: boolean;\n  ssl: TLSSocket['_handle'];\n  [kRes]: null | Socket['_handle'];\n  [kIsVerified]: boolean;\n  [kPendingSession]: null | Buffer;\n  [kErrorEmitted]: boolean;\n  [kConnectOptions]?: NormalizedConnectionOptions;\n\n  constructor(\n    socket: Socket | Duplex | undefined,\n    opts: TLSSocket['_tlsOptions']\n  );\n  prototype: TLSSocket;\n\n  _destroySSL(): void;\n  _emitTLSError(error: Error): void;\n  _finishInit(): void;\n  _handleTimeout(): void;\n  _releaseControl(): boolean;\n  _start(): void;\n  _tlsError(error: Error): Error | null;\n  _wrapHandle(\n    wrap: null | Socket,\n    handle: Socket['_handle'] | null | undefined,\n    wrapHasActiveWriteFromPrevOwner: boolean\n  ): unknown;\n  disableRenegotiation(): void;\n  getX509Certificate(): ReturnType<TLSSocketType['getX509Certificate']>;\n  setKeyCert(context: unknown): void;\n  setServername(name: string): void;\n  setSession(session: string | Buffer): void;\n  setMaxSendFragment(size: number): boolean;\n  getCertificate(): ReturnType<TLSSocketType['getCertificate']>;\n  getPeerX509Certificate(): ReturnType<TLSSocketType['getPeerX509Certificate']>;\n  renegotiate(\n    options: {\n      rejectUnauthorized?: boolean | undefined;\n      requestCert?: boolean | undefined;\n    },\n    callback?: (error: Error | null) => void\n  ): boolean;\n  exportKeyingMaterial(length: number, label: string, context?: Buffer): Buffer;\n}\n\nfunction onnewsessionclient(\n  this: TLSSocket,\n  _sessionId: string,\n  session: Buffer\n): void {\n  if (this[kIsVerified]) {\n    this.emit('session', session);\n  } else {\n    this[kPendingSession] = session;\n  }\n}\n\nfunction onerror(this: TLSSocket, err: Error): void {\n  if (this._hadError) return;\n\n  this._hadError = true;\n\n  // Destroy socket if error happened before handshake's finish\n  if (!this._secureEstablished) {\n    // When handshake fails control is not yet released,\n    // so self._tlsError will return null instead of actual error\n\n    // Set closing the socket after emitting an event since the socket needs to\n    // be accessible when the `tlsClientError` event is emitted.\n    this.destroy(err);\n  } else {\n    // Emit error\n    this._emitTLSError(err);\n  }\n}\n\n// We are using old style function classes for node.js compat.\n// @ts-expect-error TS2323 Cannot redeclare error.\nexport function TLSSocket(\n  this: TLSSocket,\n  socket: Socket | Duplex | undefined,\n  opts: TLSSocket['_tlsOptions']\n): void {\n  const tlsOptions = { ...opts };\n\n  if (tlsOptions.enableTrace) {\n    throw new ERR_OPTION_NOT_IMPLEMENTED('options.enableTrace');\n  }\n\n  if (tlsOptions.isServer) {\n    throw new ERR_OPTION_NOT_IMPLEMENTED('options.isServer');\n  }\n\n  if (tlsOptions.server) {\n    throw new ERR_OPTION_NOT_IMPLEMENTED('options.server');\n  }\n\n  if (tlsOptions.requestCert) {\n    // Servers will request certificate from clients.\n    // Does not apply to Cloudflare Workers.\n    throw new ERR_OPTION_NOT_IMPLEMENTED('options.requestCert');\n  }\n\n  if (tlsOptions.rejectUnauthorized === false) {\n    // TODO(soon): We don't support rejectUnauthorized=false\n    throw new ERR_OPTION_NOT_IMPLEMENTED('options.rejectUnauthorized');\n  }\n\n  if (tlsOptions.ALPNProtocols !== undefined) {\n    // Does not apply to Cloudflare Workers.\n    throw new ERR_OPTION_NOT_IMPLEMENTED('options.ALPNProtocols');\n  }\n\n  if (tlsOptions.SNICallback !== undefined) {\n    // Does not apply to Cloudflare Workers.\n    throw new ERR_OPTION_NOT_IMPLEMENTED('options.SNICallback');\n  }\n\n  if (tlsOptions.requestOCSP) {\n    // Not yet supported. Can be implemented in the future.\n    throw new ERR_OPTION_NOT_IMPLEMENTED('options.requestOCSP');\n  }\n\n  if (\n    tlsOptions.secureContext !== undefined &&\n    !(tlsOptions.secureContext instanceof SecureContext)\n  ) {\n    throw new ERR_TLS_INVALID_CONTEXT('context');\n  }\n\n  if (tlsOptions.pskCallback !== undefined) {\n    // Used for TLS-PSK negotiation. We do not support it.\n    throw new ERR_OPTION_NOT_IMPLEMENTED('options.pskCallback');\n  }\n\n  // TODO(soon): Call this on secureConnect once connect() api supports\n  // getting peer certificate.\n  if (tlsOptions.checkServerIdentity !== undefined) {\n    validateFunction(\n      tlsOptions.checkServerIdentity,\n      'options.checkServerIdentity'\n    );\n  }\n\n  this._tlsOptions = tlsOptions;\n  this._secureEstablished = false;\n  this._securePending = false;\n  this._newSessionPending = false;\n  this._controlReleased = false;\n  this.secureConnecting = true;\n  this.servername = null;\n  this.authorized = false;\n  this[kRes] = null;\n  this[kIsVerified] = false;\n  this[kPendingSession] = null;\n  this[kErrorEmitted] = false;\n\n  let wrap: Socket | null = null;\n  let handle: Socket['_handle'] | null = null;\n  let wrapHasActiveWriteFromPrevOwner = false;\n\n  if (socket) {\n    if (socket instanceof Socket) {\n      wrap = socket;\n    } else {\n      wrap = new JSStreamSocket(socket);\n    }\n\n    handle = wrap._handle;\n    wrapHasActiveWriteFromPrevOwner = wrap.writableLength > 0;\n  }\n\n  // Just a documented property to make secure sockets\n  // distinguishable from regular ones.\n  this.encrypted = true;\n\n  Reflect.apply(Socket, this, [\n    {\n      handle: this._wrapHandle(wrap, handle, wrapHasActiveWriteFromPrevOwner),\n      allowHalfOpen: socket ? socket.allowHalfOpen : tlsOptions.allowHalfOpen,\n      pauseOnCreate: tlsOptions.pauseOnConnect,\n      manualStart: true,\n      highWaterMark: tlsOptions.highWaterMark,\n      onread: !socket ? tlsOptions.onread : null,\n      signal: tlsOptions.signal,\n      lookup: tlsOptions.lookup,\n    } as SocketOptions,\n  ]);\n\n  this._parent = handle;\n  this._parentWrap = wrap;\n\n  // Proxy for API compatibility\n  this.ssl = this._handle; // C++ TLSWrap object\n\n  this.on('error', this._tlsError.bind(this));\n\n  this._init();\n}\nObject.setPrototypeOf(TLSSocket.prototype, Socket.prototype);\nObject.setPrototypeOf(TLSSocket, Socket);\n\nTLSSocket.prototype.disableRenegotiation = function disableRenegotiation(\n  this: TLSSocket\n): void {\n  // Do nothing.\n};\n\nTLSSocket.prototype._wrapHandle = function _wrapHandle(\n  this: TLSSocket,\n  wrap: null | Socket,\n  handle: Socket['_handle'] | null | undefined,\n  _wrapHasActiveWriteFromPrevOwner: boolean\n): unknown {\n  if (!handle) {\n    return null;\n  }\n  this[kRes] = handle;\n\n  // Guard against adding multiple listeners, as this method may be called\n  // repeatedly on the same socket by reinitializeHandle\n  if (this.listenerCount('close', onSocketCloseDestroySSL) === 0) {\n    this.once('close', onSocketCloseDestroySSL);\n  }\n\n  wrap?.once('close', () => this.destroy());\n\n  return handle;\n};\n\nfunction onSocketCloseDestroySSL(this: TLSSocket): void {\n  // We call destroySSL directly because it is already an async process.\n  this._destroySSL();\n  this[kRes] = null;\n}\n\nTLSSocket.prototype._destroySSL = function _destroySSL(this: TLSSocket): void {\n  // We disable floating promises rule because we don't want to change the\n  // function signature and return Promise<void>\n  //\n  // eslint-disable-next-line @typescript-eslint/no-floating-promises\n  this._handle?.socket.close().then(() => {\n    this[kPendingSession] = null;\n    this[kIsVerified] = false;\n  });\n};\n\nTLSSocket.prototype._init = function _init(this: TLSSocket): void {\n  const options = this._tlsOptions;\n\n  this.on('error', onerror.bind(this));\n\n  // This is emitted from net.Socket class.\n  this.on('connectionAttempt', onnewsessionclient.bind(this));\n\n  if (options.handshakeTimeout && options.handshakeTimeout > 0)\n    this.setTimeout(options.handshakeTimeout, this._handleTimeout.bind(this));\n\n  // TLSSocket can be initialized with 2 different handles.\n  //\n  // 1. Socket instance created by \"node:net\". In this scenario, we need\n  //    to wait for 'connect' event to be emitted in order to trigger _finishInit().\n  // 2. Duplex stream. Duplex streams are initialized through JSStreamSocket class.\n  //    If that's the scenario, we can trigger _finishInit() immediately. Since, there\n  //    is no async calls required to wait.\n  if (this._parentWrap != null && this._parentWrap instanceof JSStreamSocket) {\n    queueMicrotask(() => {\n      this._finishInit();\n      ok(this._parentWrap instanceof JSStreamSocket);\n      this._parentWrap.readStart();\n      tryReadStart(this);\n    });\n  } else {\n    this.on('connect', () => {\n      this._finishInit();\n    });\n  }\n};\n\nTLSSocket.prototype.renegotiate = function (\n  this: TLSSocket,\n  options: {\n    rejectUnauthorized?: boolean | undefined;\n    requestCert?: boolean | undefined;\n  },\n  callback?: (error: Error | null) => void\n): boolean {\n  validateObject(options, 'options');\n  if (callback !== undefined) {\n    validateFunction(callback, 'callback');\n  }\n  // TLS renegotiation is not supported.\n  return false;\n};\n\nTLSSocket.prototype.exportKeyingMaterial = function exportKeyingMaterial(\n  this: TLSSocket,\n  length: number,\n  label: string,\n  context?: Buffer\n): Buffer {\n  validateUint32(length, 'length', true);\n  validateString(label, 'label');\n  if (context !== undefined) {\n    validateBuffer(context, 'context');\n  }\n\n  throw new Error('exportKeyingMaterial is not implemented');\n};\n\nTLSSocket.prototype.setMaxSendFragment = function setMaxSendFragment(\n  this: TLSSocket,\n  size: number\n): boolean {\n  validateInt32(size, 'size');\n  // Setting maximum TLS fragment size is not supported.\n  return false;\n};\n\nTLSSocket.prototype._handleTimeout = function _handleTimeout(\n  this: TLSSocket\n): void {\n  this._emitTLSError(new ERR_TLS_HANDSHAKE_TIMEOUT());\n};\n\nTLSSocket.prototype._emitTLSError = function _emitTLSError(\n  this: TLSSocket,\n  err: Error\n): void {\n  const e = this._tlsError(err);\n  if (e) this.emit('error', e);\n};\n\nTLSSocket.prototype._tlsError = function _tlsError(\n  this: TLSSocket,\n  err: Error\n): Error | null {\n  this.emit('_tlsError', err);\n  if (this._controlReleased) return err;\n  return null;\n};\n\nTLSSocket.prototype._releaseControl = function _releaseControl(\n  this: TLSSocket\n): boolean {\n  if (this._controlReleased) return false;\n  this._controlReleased = true;\n  this.removeListener('error', this._tlsError.bind(this));\n  return true;\n};\n\n// This function is called from net.Socket onConnectionOpened() handler.\nTLSSocket.prototype._finishInit = function _finishInit(this: TLSSocket): void {\n  // Guard against getting onhandshakedone() after .destroy().\n  // * 1.2: If destroy() during onocspresponse(), then write of next handshake\n  // record fails, the handshake done info callbacks does not occur, and the\n  // socket closes.\n  // * 1.3: The OCSP response comes in the same record that finishes handshake,\n  // so even after .destroy(), the handshake done info callback occurs\n  // immediately after onocspresponse(). Ignore it.\n  if (!this._handle) return;\n\n  this._secureEstablished = true;\n  if (\n    this._tlsOptions.handshakeTimeout &&\n    this._tlsOptions.handshakeTimeout > 0\n  ) {\n    this.setTimeout(0, this._handleTimeout.bind(this));\n  }\n\n  this.emit('secure');\n};\n\nTLSSocket.prototype._start = function _start(this: TLSSocket): void {\n  if (this.connecting) {\n    this.once('connect', this._start.bind(this));\n    return;\n  }\n\n  // If a user calls tls.connect({ socket }) with a socket that is not initialized\n  // and the socket is not connected, we need to wait for the socket to connect\n  // before we can complete the process.\n  //\n  // Take a look at the following test for this particular edge case:\n  // https://github.com/nodejs/node/blob/91d8a524ada001103a2d1c6825ca17b8393c183f/test/parallel/test-tls-on-empty-socket.js\n  if (this._parentWrap != null && this._parentWrap._handle == null) {\n    this._parentWrap.once('connect', () => {\n      // We need to update the Socket handle of this TLSSocket\n      // since it was created after TLSSocket is initialized.\n      if (this._parentWrap?._handle != null) {\n        this._handle = this._parentWrap._handle;\n      }\n      this._start();\n    });\n    return;\n  }\n\n  // Guard against the following cases:\n  // - Socket was destroyed before the connection was established\n  // - TLSSocket can not be upgraded if the secureTransport does not support 'starttls'\n  if (this._handle?.socket.secureTransport !== 'starttls') {\n    return;\n  }\n\n  // We first need to release the lock\n  this._handle.writer.releaseLock();\n  this._handle.reader.releaseLock();\n\n  try {\n    const { host, port, addressType } = this._handle.options;\n    const socket = this._handle.socket.startTls();\n\n    this._handle = {\n      socket: socket,\n      writer: socket.writable.getWriter(),\n      reader: socket.readable.getReader({ mode: 'byob' }),\n      bytesRead: 0,\n      bytesWritten: 0,\n      reading: true,\n      options: this._handle.options,\n    };\n\n    // This is now an encrypted connection.\n    // There are cases where in node:net we have to distinguish between\n    // encrypted and unencrypted connections.\n    this.encrypted = true;\n\n    this._handle.socket.opened.then(\n      onConnectionOpened.bind(this),\n      (err: unknown) => {\n        this.emit('connectionAttemptFailed', host, port, addressType, err);\n        this.destroy(err as Error);\n      }\n    );\n\n    this._handle.socket.closed.then(\n      onConnectionClosed.bind(this),\n      (error: unknown): void => {\n        // Do not call this.destroy.bind(this) since user can override it.\n        this.destroy(error as Error);\n      }\n    );\n  } catch (error) {\n    this.destroy(error as Error);\n  }\n};\n\nTLSSocket.prototype.setServername = function setServername(\n  this: TLSSocket,\n  name: string\n): void {\n  validateString(name, 'name');\n  // Pipefitter currently does not provide us a way on the internal\n  // system and possibly KJ's TLS implementation doesn't provides a way,\n  // but it is something we will need sooner than later.\n};\n\nTLSSocket.prototype.setSession = function (_session: string | Buffer): void {\n  // Do nothing. We don't support setting session.\n};\n\n// @ts-expect-error TS2322 Inconsistencies between @types/node\nTLSSocket.prototype.getPeerCertificate = function (\n  _detailed?: boolean\n): ReturnType<TLSSocketType['getPeerCertificate']> {\n  // Returns an object representing a peer certificate.\n  // This function is not supported.\n  throw new Error('getPeerCertificate is not implemented');\n};\n\nTLSSocket.prototype.getCertificate = function (\n  this: TLSSocket\n): ReturnType<TLSSocketType['getCertificate']> {\n  // Returns an object representing the local certificate.\n  // This function is not supported.\n  throw new Error('TLSSocket.getCertificate is not implemented');\n};\n\nTLSSocket.prototype.getPeerX509Certificate = function (\n  this: TLSSocket,\n  _detailed?: boolean\n): ReturnType<TLSSocketType['getPeerX509Certificate']> {\n  // Returns the peer certificate as an X509 certificate.\n  // This function is not supported.\n  throw new Error('TLSSocket.getPeerX509Certificate is not implemented');\n};\n\nTLSSocket.prototype.getX509Certificate = function (\n  this: TLSSocket\n): ReturnType<TLSSocketType['getX509Certificate']> {\n  // Returns the local certificate as an X509 certificate.\n  // This function is not supported.\n  throw new Error('TLSSocket.getX509Certificate is not implemented');\n};\n\nTLSSocket.prototype.setKeyCert = function (\n  this: TLSSocket,\n  _context: unknown\n): void {\n  // Changing private key and certificate to be used with TCP connection\n  // is not supported due to the limitations of connect() api.\n  throw new Error('TLSSocket.setKeyCert is not implemented');\n};\n\n// We have this syntax because of the original Node.js implementation.\n// They are not supported by Cloudflare Workers but in Node.js\n// they are all properties of this._handle[PROP_NAME]\n[\n  'getCipher',\n  'getSharedSigalgs',\n  'getEphemeralKeyInfo',\n  'getFinished',\n  'getPeerFinished',\n  'getProtocol',\n  'getSession',\n  'getTLSTicket',\n  'isSessionReused',\n  'enableTrace',\n].forEach((method) => {\n  // @ts-expect-error TS7053 Omitting...\n  TLSSocket.prototype[method] = function (): null {\n    // None of these functions are supported by connect() api.\n    return null;\n  };\n});\n\ntype NormalizedConnectionOptions = ConnectionOptions &\n  SocketOptions &\n  TlsOptions & {\n    host?: string;\n    port: number;\n  };\nfunction normalizeConnectArgs(\n  listArgs: unknown[]\n): [NormalizedConnectionOptions] | [NormalizedConnectionOptions, VoidFunction] {\n  const args = _normalizeArgs(listArgs);\n  const options = args[0] as NormalizedConnectionOptions;\n  const cb = args[1] as VoidFunction | undefined;\n\n  // If args[0] was options, then normalize dealt with it.\n  // If args[0] is port, or args[0], args[1] is host, port, we need to\n  // find the options and merge them in, normalize's options has only\n  // the host/port/path args that it knows about, not the tls options.\n  // This means that options.host overrides a host arg.\n  if (listArgs[1] !== null && typeof listArgs[1] === 'object') {\n    Object.assign(options, listArgs[1]);\n  } else if (listArgs[2] !== null && typeof listArgs[2] === 'object') {\n    Object.assign(options, listArgs[2]);\n  }\n\n  return cb ? [options, cb] : [options];\n}\n\nfunction onConnectSecure(this: TLSSocket): void {\n  this.authorized = true;\n  this.secureConnecting = false;\n  this.emit('secureConnect');\n\n  this[kIsVerified] = true;\n  const session = this[kPendingSession];\n  this[kPendingSession] = null;\n  if (session) this.emit('session', session);\n\n  this.removeListener('end', onConnectEnd);\n}\n\nfunction onConnectEnd(this: TLSSocket): void {\n  // NOTE: This logic is shared with _http_client.js\n  if (!this._hadError) {\n    const options = this[kConnectOptions];\n    this._hadError = true;\n    const error = new ConnResetException(\n      'Client network socket disconnected ' +\n        'before secure TLS connection was ' +\n        'established'\n    );\n    error.path = options?.path;\n    error.host = options?.host;\n    error.port = options?.port;\n    // @ts-expect-error TS2339 Missing types\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n    error.localAddress = options?.localAddress;\n    this.destroy(error);\n  }\n}\n\n// Arguments: [port,] [host,] [options,] [cb]\nexport function connect(...args: unknown[]): TLSSocket {\n  args = normalizeConnectArgs(args);\n  const options = args[0] as NormalizedConnectionOptions;\n  const cb = args[1] as VoidFunction | undefined;\n\n  if (options.minDHSize !== undefined) {\n    // We leave this validation for node.js compat.\n    validateNumber(options.minDHSize, 'options.minDHSize', 1);\n    // Not supported.\n    throw new ERR_OPTION_NOT_IMPLEMENTED('options.minDHSize');\n  }\n\n  if (options.rejectUnauthorized === false) {\n    // TODO(soon): We don't support rejectUnauthorized=false\n    throw new ERR_OPTION_NOT_IMPLEMENTED('options.rejectUnauthorized');\n  }\n\n  if (options.pskCallback !== undefined) {\n    // Used for TLS-PSK negotiation.\n    // Does not make sense for Cloudflare Workers.\n    throw new ERR_OPTION_NOT_IMPLEMENTED('options.pskCallback');\n  }\n\n  if (options.checkServerIdentity !== undefined) {\n    validateFunction(\n      options.checkServerIdentity,\n      'options.checkServerIdentity'\n    );\n  }\n\n  // @ts-expect-error TS2345 Type incompatibility between Node.js Duplex and internal Duplex\n  const tlssock = new TLSSocket(options.socket, {\n    allowHalfOpen: options.allowHalfOpen,\n    pipe: !!options.path,\n    ALPNProtocols: options.ALPNProtocols,\n    enableTrace: options.enableTrace,\n    highWaterMark: options.highWaterMark,\n    secureContext: options.secureContext,\n    checkServerIdentity: options.checkServerIdentity ?? checkServerIdentity,\n    onread: options.onread,\n    signal: options.signal,\n    lookup: options.lookup,\n    rejectUnauthorized:\n      options.rejectUnauthorized !== undefined\n        ? Boolean(options.rejectUnauthorized) // eslint-disable-line @typescript-eslint/no-unnecessary-type-conversion\n        : true,\n  });\n\n  tlssock[kConnectOptions] = options;\n\n  if (cb) {\n    tlssock.once('secureConnect', cb);\n  }\n\n  if (!options.socket) {\n    // If user provided the socket, it's their responsibility to manage its\n    // connectivity. If we created one internally, we connect it.\n    if (options.timeout) {\n      tlssock.setTimeout(options.timeout);\n    }\n\n    tlssock.connect(options, tlssock._start.bind(tlssock));\n  }\n\n  tlssock._releaseControl();\n\n  if (options.session) {\n    tlssock.setSession(options.session);\n  }\n\n  if (options.socket) {\n    tlssock._start();\n  }\n\n  // The 'secure' event is emitted by the SecurePair object once a secure connection has been established.\n  tlssock.on('secure', onConnectSecure);\n  tlssock.prependListener('end', onConnectEnd);\n\n  return tlssock;\n}\n"
  },
  {
    "path": "src/node/internal/internal_types.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport internal from 'node-internal:util';\n\nimport { kHandle } from 'node-internal:crypto_util';\n\nexport function isCryptoKey(value: unknown): boolean {\n  return value instanceof CryptoKey;\n}\n\nexport function isKeyObject(_value: unknown): boolean {\n  return _value != null && typeof _value === 'object' && kHandle in _value;\n}\n\nexport const isAsyncFunction = internal.isAsyncFunction.bind(internal);\nexport const isGeneratorFunction = internal.isGeneratorFunction.bind(internal);\nexport const isGeneratorObject = internal.isGeneratorObject.bind(internal);\nexport const isAnyArrayBuffer = internal.isAnyArrayBuffer.bind(internal);\nexport const isArrayBuffer = internal.isArrayBuffer.bind(internal);\nexport const isArgumentsObject = internal.isArgumentsObject.bind(internal);\nexport const isBoxedPrimitive = internal.isBoxedPrimitive.bind(internal);\nexport const isDataView = internal.isDataView.bind(internal);\nexport const isExternal = internal.isExternal.bind(internal);\nexport const isMap = internal.isMap.bind(internal);\nexport const isMapIterator = internal.isMapIterator.bind(internal);\nexport const isModuleNamespaceObject =\n  internal.isModuleNamespaceObject.bind(internal);\nexport const isNativeError = internal.isNativeError.bind(internal);\nexport const isPromise = internal.isPromise.bind(internal);\nexport const isProxy = internal.isProxy.bind(internal);\nexport const isSet = internal.isSet.bind(internal);\nexport const isSetIterator = internal.isSetIterator.bind(internal);\nexport const isSharedArrayBuffer = internal.isSharedArrayBuffer.bind(internal);\nexport const isWeakMap = internal.isWeakMap.bind(internal);\nexport const isWeakSet = internal.isWeakSet.bind(internal);\nexport const isRegExp = internal.isRegExp.bind(internal);\nexport const isDate = internal.isDate.bind(internal);\nexport const isStringObject = internal.isStringObject.bind(internal);\nexport const isSymbolObject = internal.isSymbolObject.bind(internal);\nexport const isNumberObject = internal.isNumberObject.bind(internal);\nexport const isBooleanObject = internal.isBooleanObject.bind(internal);\nexport const isBigIntObject = internal.isBigIntObject.bind(internal);\nexport const isArrayBufferView = internal.isArrayBufferView.bind(internal);\nexport const isBigInt64Array = internal.isBigInt64Array.bind(internal);\nexport const isBigUint64Array = internal.isBigUint64Array.bind(internal);\nexport const isFloat16Array = internal.isFloat16Array.bind(internal);\nexport const isFloat32Array = internal.isFloat32Array.bind(internal);\nexport const isFloat64Array = internal.isFloat64Array.bind(internal);\nexport const isInt8Array = internal.isInt8Array.bind(internal);\nexport const isInt16Array = internal.isInt16Array.bind(internal);\nexport const isInt32Array = internal.isInt32Array.bind(internal);\nexport const isTypedArray = internal.isTypedArray.bind(internal);\nexport const isUint8Array = internal.isUint8Array.bind(internal);\nexport const isUint8ClampedArray = internal.isUint8ClampedArray.bind(internal);\nexport const isUint16Array = internal.isUint16Array.bind(internal);\nexport const isUint32Array = internal.isUint32Array.bind(internal);\n\nexport default {\n  isCryptoKey,\n  isKeyObject,\n\n  isAsyncFunction,\n  isGeneratorFunction,\n  isGeneratorObject,\n  isAnyArrayBuffer,\n  isArrayBuffer,\n  isArgumentsObject,\n  isBoxedPrimitive,\n  isDataView,\n  isMap,\n  isMapIterator,\n  isModuleNamespaceObject,\n  isNativeError,\n  isPromise,\n  isProxy,\n  isSet,\n  isSetIterator,\n  isSharedArrayBuffer,\n  isWeakMap,\n  isWeakSet,\n  isRegExp,\n  isDate,\n  isStringObject,\n  isSymbolObject,\n  isNumberObject,\n  isBooleanObject,\n  isBigIntObject,\n  isArrayBufferView,\n  isBigInt64Array,\n  isBigUint64Array,\n  isFloat16Array,\n  isFloat32Array,\n  isFloat64Array,\n  isInt8Array,\n  isInt16Array,\n  isInt32Array,\n  isTypedArray,\n  isUint8Array,\n  isUint8ClampedArray,\n  isUint16Array,\n  isUint32Array,\n  isExternal,\n};\n"
  },
  {
    "path": "src/node/internal/internal_url.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport {\n  ERR_INVALID_FILE_URL_HOST,\n  ERR_INVALID_FILE_URL_PATH,\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_ARG_VALUE,\n  ERR_INVALID_URL_SCHEME,\n} from 'node-internal:internal_errors';\nimport { default as urlUtil } from 'node-internal:url';\nimport {\n  CHAR_LOWERCASE_A,\n  CHAR_LOWERCASE_Z,\n  CHAR_FORWARD_SLASH,\n  CHAR_BACKWARD_SLASH,\n} from 'node-internal:constants';\nimport {\n  win32 as pathWin32,\n  posix as pathPosix,\n} from 'node-internal:internal_path';\n\nconst FORWARD_SLASH = /\\//g;\n\n// RFC1738 defines the following chars as \"unsafe\" for URLs\n// @see https://www.ietf.org/rfc/rfc1738.txt 2.2. URL Character Encoding Issues\nconst percentRegEx = /%/g;\nconst newlineRegEx = /\\n/g;\nconst carriageReturnRegEx = /\\r/g;\nconst tabRegEx = /\\t/g;\nconst quoteRegEx = /\"/g;\nconst hashRegex = /#/g;\nconst spaceRegEx = / /g;\nconst questionMarkRegex = /\\?/g;\nconst openSquareBracketRegEx = /\\[/g;\nconst backslashRegEx = /\\\\/g;\nconst closeSquareBracketRegEx = /]/g;\nconst caretRegEx = /\\^/g;\nconst verticalBarRegEx = /\\|/g;\nconst tildeRegEx = /~/g;\n\nfunction encodePathChars(\n  filepath: string,\n  options?: { windows: boolean | undefined }\n): string {\n  if (filepath.includes('%')) {\n    filepath = filepath.replace(percentRegEx, '%25');\n  }\n\n  if (filepath.includes('\\t')) {\n    filepath = filepath.replace(tabRegEx, '%09');\n  }\n  if (filepath.includes('\\n')) {\n    filepath = filepath.replace(newlineRegEx, '%0A');\n  }\n  if (filepath.includes('\\r')) {\n    filepath = filepath.replace(carriageReturnRegEx, '%0D');\n  }\n  if (filepath.includes(' ')) {\n    filepath = filepath.replace(spaceRegEx, '%20');\n  }\n  if (filepath.includes('\"')) {\n    filepath = filepath.replace(quoteRegEx, '%22');\n  }\n  if (filepath.includes('#')) {\n    filepath = filepath.replace(hashRegex, '%23');\n  }\n  if (filepath.includes('?')) {\n    filepath = filepath.replace(questionMarkRegex, '%3F');\n  }\n  if (filepath.includes('[')) {\n    filepath = filepath.replace(openSquareBracketRegEx, '%5B');\n  }\n  // Back-slashes must be special-cased on Windows, where they are treated as path separator.\n  if (!options?.windows && filepath.includes('\\\\')) {\n    filepath = filepath.replace(backslashRegEx, '%5C');\n  }\n  if (filepath.includes(']')) {\n    filepath = filepath.replace(closeSquareBracketRegEx, '%5D');\n  }\n  if (filepath.includes('^')) {\n    filepath = filepath.replace(caretRegEx, '%5E');\n  }\n  if (filepath.includes('|')) {\n    filepath = filepath.replace(verticalBarRegEx, '%7C');\n  }\n  if (filepath.includes('~')) {\n    filepath = filepath.replace(tildeRegEx, '%7E');\n  }\n\n  return filepath;\n}\n\n/**\n * Checks if a value has the shape of a WHATWG URL object.\n *\n * Using a symbol or instanceof would not be able to recognize URL objects\n * coming from other implementations (e.g. in Electron), so instead we are\n * checking some well known properties for a lack of a better test.\n *\n * We use `href` and `protocol` as they are the only properties that are\n * easy to retrieve and calculate due to the lazy nature of the getters.\n *\n * We check for `auth` and `path` attribute to distinguish legacy url instance with\n * WHATWG URL instance.\n */\n/* eslint-disable */\nexport function isURL(self?: any): self is URL {\n  return Boolean(\n    self?.href &&\n    self.protocol &&\n    self.auth === undefined &&\n    self.path === undefined\n  );\n}\n/* eslint-enable */\n\nexport function getPathFromURLPosix(url: URL): string {\n  if (url.hostname !== '') {\n    // Note: Difference between Node.js and Workerd.\n    // Node.js uses `process.platform` whereas workerd hard codes it to linux.\n    // This is done to avoid confusion regarding non-linux support and conformance.\n    throw new ERR_INVALID_FILE_URL_HOST('linux');\n  }\n  const pathname = url.pathname;\n  for (let n = 0; n < pathname.length; n++) {\n    if (pathname[n] === '%') {\n      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n      const third = pathname.codePointAt(n + 2)! | 0x20;\n      if (pathname[n + 1] === '2' && third === 102) {\n        throw new ERR_INVALID_FILE_URL_PATH(\n          'must not include encoded / characters'\n        );\n      }\n    }\n  }\n  return decodeURIComponent(pathname);\n}\n\nexport function getPathFromURLWin32(url: URL): string {\n  const hostname = url.hostname;\n  let pathname = url.pathname;\n  for (let n = 0; n < pathname.length; n++) {\n    if (pathname[n] === '%') {\n      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n      const third = pathname.codePointAt(n + 2)! | 0x20;\n      if (\n        (pathname[n + 1] === '2' && third === 102) || // 2f 2F /\n        (pathname[n + 1] === '5' && third === 99)\n      ) {\n        // 5c 5C \\\n        throw new ERR_INVALID_FILE_URL_PATH(\n          'must not include encoded \\\\ or / characters'\n        );\n      }\n    }\n  }\n  pathname = pathname.replace(FORWARD_SLASH, '\\\\');\n  pathname = decodeURIComponent(pathname);\n  if (hostname !== '') {\n    // If hostname is set, then we have a UNC path\n    // Pass the hostname through domainToUnicode just in case\n    // it is an IDN using punycode encoding. We do not need to worry\n    // about percent encoding because the URL parser will have\n    // already taken care of that for us. Note that this only\n    // causes IDNs with an appropriate `xn--` prefix to be decoded.\n    return `\\\\\\\\${urlUtil.domainToUnicode(hostname)}${pathname}`;\n  }\n  // Otherwise, it's a local path that requires a drive letter\n  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n  const letter = pathname.codePointAt(1)! | 0x20;\n  const sep = pathname.charAt(2);\n  if (\n    letter < CHAR_LOWERCASE_A ||\n    letter > CHAR_LOWERCASE_Z || // a..z A..Z\n    sep !== ':'\n  ) {\n    throw new ERR_INVALID_FILE_URL_PATH('must be absolute');\n  }\n  return pathname.slice(1);\n}\n\nexport function fileURLToPath(\n  input: string | URL,\n  options?: { windows?: boolean }\n): string {\n  const windows = options?.windows;\n  let path: URL;\n  if (typeof input === 'string') {\n    path = new URL(input);\n  } else if (!isURL(input)) {\n    throw new ERR_INVALID_ARG_TYPE('path', ['string', 'URL'], input);\n  } else {\n    path = input;\n  }\n  if (path.protocol !== 'file:') {\n    throw new ERR_INVALID_URL_SCHEME('file');\n  }\n  return windows ? getPathFromURLWin32(path) : getPathFromURLPosix(path);\n}\n\nexport function pathToFileURL(\n  filepath: string,\n  options?: { windows?: boolean }\n): URL {\n  const windows = options?.windows;\n  // IMPORTANT: Difference between Node.js and workerd.\n  // The following check does not exist in Node.js due to primordial usage.\n  if (typeof filepath !== 'string') {\n    throw new ERR_INVALID_ARG_TYPE('filepath', 'string', filepath);\n  }\n  if (windows && filepath.startsWith('\\\\\\\\')) {\n    const outURL = new URL('file://');\n    // UNC path format: \\\\server\\share\\resource\n    // Handle extended UNC path and standard UNC path\n    // \"\\\\?\\UNC\\\" path prefix should be ignored.\n    // Ref: https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation\n    const isExtendedUNC = filepath.startsWith('\\\\\\\\?\\\\UNC\\\\');\n    const prefixLength = isExtendedUNC ? 8 : 2;\n    const hostnameEndIndex = filepath.indexOf('\\\\', prefixLength);\n    if (hostnameEndIndex === -1) {\n      throw new ERR_INVALID_ARG_VALUE(\n        'path',\n        filepath,\n        'Missing UNC resource path'\n      );\n    }\n    if (hostnameEndIndex === 2) {\n      throw new ERR_INVALID_ARG_VALUE('path', filepath, 'Empty UNC servername');\n    }\n    const hostname = filepath.slice(prefixLength, hostnameEndIndex);\n    outURL.hostname = urlUtil.domainToASCII(hostname);\n    outURL.pathname = encodePathChars(\n      filepath.slice(hostnameEndIndex).replace(backslashRegEx, '/'),\n      { windows }\n    );\n    return outURL;\n  }\n  let resolved = windows\n    ? pathWin32.resolve(filepath)\n    : pathPosix.resolve(filepath);\n  const sep = windows ? pathWin32.sep : pathPosix.sep;\n  // path.resolve strips trailing slashes so we must add them back\n  const filePathLast = filepath.charCodeAt(filepath.length - 1);\n  if (\n    (filePathLast === CHAR_FORWARD_SLASH ||\n      (windows && filePathLast === CHAR_BACKWARD_SLASH)) &&\n    resolved[resolved.length - 1] !== sep\n  )\n    resolved += '/';\n\n  return new URL(`file://${encodePathChars(resolved, { windows })}`);\n}\n\nexport function toPathIfFileURL(fileURLOrPath: URL | string): string {\n  if (!isURL(fileURLOrPath)) return fileURLOrPath;\n  return fileURLToPath(fileURLOrPath);\n}\n\n/**\n * Utility function that converts a URL object into an ordinary options object\n * as expected by the `http.request` and `https.request` APIs.\n * @param {URL} url\n * @returns {Record<string, unknown>}\n */\nexport function urlToHttpOptions(url: URL): Record<string, unknown> {\n  const { hostname, pathname, port, username, password, search } = url;\n  const options: Record<string, unknown> = {\n    __proto__: null,\n    // eslint-disable-next-line @typescript-eslint/no-misused-spread\n    ...url, // In case the url object was extended by the user.\n    protocol: url.protocol,\n    hostname:\n      hostname && hostname[0] === '[' ? hostname.slice(1, -1) : hostname,\n    hash: url.hash,\n    search: search,\n    pathname: pathname,\n    path: `${pathname || ''}${search || ''}`,\n    href: url.href,\n  };\n  if (port !== '') {\n    options.port = Number(port);\n  }\n  if (username || password) {\n    options.auth = `${decodeURIComponent(username)}:${decodeURIComponent(password)}`;\n  }\n  return options;\n}\n\n// Protocols that can allow \"unsafe\" and \"unwise\" chars.\nexport const unsafeProtocol = new Set<string>(['javascript', 'javascript:']);\n// Protocols that never have a hostname.\nexport const hostlessProtocol = new Set<string>(['javascript', 'javascript:']);\n// Protocols that always contain a // bit.\nexport const slashedProtocol = new Set<string>([\n  'http',\n  'http:',\n  'https',\n  'https:',\n  'ftp',\n  'ftp:',\n  'gopher',\n  'gopher:',\n  'file',\n  'file:',\n  'ws',\n  'ws:',\n  'wss',\n  'wss:',\n]);\n"
  },
  {
    "path": "src/node/internal/internal_utils.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Deno and Node.js:\n// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { default as bufferUtil } from 'node-internal:buffer';\nimport type { Encoding } from 'node-internal:buffer';\nimport { validateFunction, validateString } from 'node-internal:validators';\nimport {\n  ERR_FALSY_VALUE_REJECTION,\n  type Falsy,\n} from 'node-internal:internal_errors';\n\nconst { UTF8, UTF16LE, HEX, ASCII, BASE64, BASE64URL, LATIN1 } = bufferUtil;\n\nexport function normalizeEncoding(enc?: string): Encoding | undefined {\n  if (\n    enc == null ||\n    enc === 'utf8' ||\n    enc === 'utf-8' ||\n    enc === 'UTF8' ||\n    enc === 'UTF-8'\n  )\n    return UTF8;\n  return getEncodingOps(enc);\n}\n\nexport function getEncodingOps(enc: unknown): Encoding | undefined {\n  if (enc === undefined) return UTF8;\n  // eslint-disable-next-line @typescript-eslint/no-base-to-string\n  let encoding = `${enc}`;\n  switch (encoding.length) {\n    case 4:\n      if (encoding === 'UTF8') return UTF8;\n      if (encoding === 'ucs2' || encoding === 'UCS2') return UTF16LE;\n      encoding = encoding.toLowerCase();\n      if (encoding === 'utf8') return UTF8;\n      if (encoding === 'ucs2') return UTF16LE;\n      break;\n    case 3:\n      if (\n        encoding === 'hex' ||\n        encoding === 'HEX' ||\n        encoding.toLowerCase() === 'hex'\n      ) {\n        return HEX;\n      }\n      break;\n    case 5:\n      if (encoding === 'ascii') return ASCII;\n      if (encoding === 'ucs-2') return UTF16LE;\n      if (encoding === 'UTF-8') return UTF8;\n      if (encoding === 'ASCII') return ASCII;\n      if (encoding === 'UCS-2') return UTF16LE;\n      encoding = encoding.toLowerCase();\n      if (encoding === 'utf-8') return UTF8;\n      if (encoding === 'ascii') return ASCII;\n      if (encoding === 'ucs-2') return UTF16LE;\n      break;\n    case 6:\n      if (encoding === 'base64') return BASE64;\n      if (encoding === 'latin1' || encoding === 'binary') return LATIN1;\n      if (encoding === 'BASE64') return BASE64;\n      if (encoding === 'LATIN1' || encoding === 'BINARY') return LATIN1;\n      encoding = encoding.toLowerCase();\n      if (encoding === 'base64') return BASE64;\n      if (encoding === 'latin1' || encoding === 'binary') return LATIN1;\n      break;\n    case 7:\n      if (\n        encoding === 'utf16le' ||\n        encoding === 'UTF16LE' ||\n        encoding.toLowerCase() === 'utf16le'\n      ) {\n        return UTF16LE;\n      }\n      break;\n    case 8:\n      if (\n        encoding === 'utf-16le' ||\n        encoding === 'UTF-16LE' ||\n        encoding.toLowerCase() === 'utf-16le'\n      ) {\n        return UTF16LE;\n      }\n      break;\n    case 9:\n      if (\n        encoding === 'base64url' ||\n        encoding === 'BASE64URL' ||\n        encoding.toLowerCase() === 'base64url'\n      ) {\n        return BASE64URL;\n      }\n      break;\n    default:\n      if (encoding === '') return UTF8;\n  }\n  return undefined;\n}\n\nexport function spliceOne(list: unknown[], index: number): void {\n  for (; index + 1 < list.length; index++) list[index] = list[index + 1];\n  list.pop();\n}\n\nexport const ALL_PROPERTIES = 0;\nexport const ONLY_WRITABLE = 1;\nexport const ONLY_ENUMERABLE = 2;\nexport const ONLY_CONFIGURABLE = 4;\nexport const ONLY_ENUM_WRITABLE = 6;\nexport const SKIP_STRINGS = 8;\nexport const SKIP_SYMBOLS = 16;\n\nconst isNumericLookup: Record<string, boolean> = {};\nexport function isArrayIndex(value: unknown): value is number | string {\n  switch (typeof value) {\n    case 'number':\n      return value >= 0 && (value | 0) === value;\n    case 'string': {\n      const result = isNumericLookup[value];\n      if (result !== void 0) {\n        return result;\n      }\n      const length = value.length;\n      if (length === 0) {\n        return (isNumericLookup[value] = false);\n      }\n      let ch = 0;\n      let i = 0;\n      for (; i < length; ++i) {\n        ch = value.charCodeAt(i);\n        if (\n          (i === 0 && ch === 0x30 && length > 1) /* must not start with 0 */ ||\n          ch < 0x30 /* 0 */ ||\n          ch > 0x39 /* 9 */\n        ) {\n          return (isNumericLookup[value] = false);\n        }\n      }\n      return (isNumericLookup[value] = true);\n    }\n    default:\n      return false;\n  }\n}\n\nexport function getOwnNonIndexProperties(\n  // deno-lint-ignore ban-types\n  obj: object,\n  filter: number\n): (string | symbol)[] {\n  let allProperties = [\n    ...Object.getOwnPropertyNames(obj),\n    ...Object.getOwnPropertySymbols(obj),\n  ];\n\n  if (Array.isArray(obj)) {\n    allProperties = allProperties.filter((k) => !isArrayIndex(k));\n  }\n\n  if (filter === ALL_PROPERTIES) {\n    return allProperties;\n  }\n\n  const result: (string | symbol)[] = [];\n  for (const key of allProperties) {\n    const desc = Object.getOwnPropertyDescriptor(obj, key);\n    if (desc === undefined) {\n      continue;\n    }\n    if (filter & ONLY_WRITABLE && !desc.writable) {\n      continue;\n    }\n    if (filter & ONLY_ENUMERABLE && !desc.enumerable) {\n      continue;\n    }\n    if (filter & ONLY_CONFIGURABLE && !desc.configurable) {\n      continue;\n    }\n    if (filter & SKIP_STRINGS && typeof key === 'string') {\n      continue;\n    }\n    if (filter & SKIP_SYMBOLS && typeof key === 'symbol') {\n      continue;\n    }\n    result.push(key);\n  }\n  return result;\n}\n\nfunction callbackifyOnRejected(\n  reason: unknown,\n  cb: (error?: unknown) => void\n): void {\n  // `!reason` guard inspired by bluebird (https://github.com/petkaantonov/bluebird/blob/2207fae3572f03b089bc92d3a6cefdd278cff7ab/src/nodeify.js#L30-L43).\n  // Because `null` is a special error value in callbacks which means \"no error\n  // occurred\", we error-wrap so the callback consumer can distinguish between\n  // \"the promise rejected with null\" or \"the promise fulfilled with undefined\".\n  if (!reason) {\n    reason = new ERR_FALSY_VALUE_REJECTION(reason as Falsy);\n  }\n  cb(reason);\n}\n\n// Types are taken from @types/node package\n// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/dccb0e78c4d3265ae06985789156451bd73312c0/types/node/util.d.ts#L1054\nexport function callbackify(\n  fn: () => Promise<void>\n): (callback: (err: Error) => void) => void;\nexport function callbackify<TResult>(\n  fn: () => Promise<TResult>\n): (callback: (err: Error, result: TResult) => void) => void;\nexport function callbackify<T1>(\n  fn: (arg1: T1) => Promise<void>\n): (arg1: T1, callback: (err: Error) => void) => void;\nexport function callbackify<T1, TResult>(\n  fn: (arg1: T1) => Promise<TResult>\n): (arg1: T1, callback: (err: Error, result: TResult) => void) => void;\nexport function callbackify<T1, T2>(\n  fn: (arg1: T1, arg2: T2) => Promise<void>\n): (arg1: T1, arg2: T2, callback: (err: Error) => void) => void;\nexport function callbackify<T1, T2, TResult>(\n  fn: (arg1: T1, arg2: T2) => Promise<TResult>\n): (\n  arg1: T1,\n  arg2: T2,\n  callback: (err: Error | null, result: TResult) => void\n) => void;\nexport function callbackify<T1, T2, T3>(\n  fn: (arg1: T1, arg2: T2, arg3: T3) => Promise<void>\n): (arg1: T1, arg2: T2, arg3: T3, callback: (err: Error) => void) => void;\nexport function callbackify<T1, T2, T3, TResult>(\n  fn: (arg1: T1, arg2: T2, arg3: T3) => Promise<TResult>\n): (\n  arg1: T1,\n  arg2: T2,\n  arg3: T3,\n  callback: (err: Error | null, result: TResult) => void\n) => void;\nexport function callbackify<T1, T2, T3, T4>(\n  fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => Promise<void>\n): (\n  arg1: T1,\n  arg2: T2,\n  arg3: T3,\n  arg4: T4,\n  callback: (err: Error) => void\n) => void;\nexport function callbackify<T1, T2, T3, T4, TResult>(\n  fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => Promise<TResult>\n): (\n  arg1: T1,\n  arg2: T2,\n  arg3: T3,\n  arg4: T4,\n  callback: (err: Error | null, result: TResult) => void\n) => void;\nexport function callbackify<T1, T2, T3, T4, T5>(\n  fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5) => Promise<void>\n): (\n  arg1: T1,\n  arg2: T2,\n  arg3: T3,\n  arg4: T4,\n  arg5: T5,\n  callback: (err: Error) => void\n) => void;\nexport function callbackify<T1, T2, T3, T4, T5, TResult>(\n  fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5) => Promise<TResult>\n): (\n  arg1: T1,\n  arg2: T2,\n  arg3: T3,\n  arg4: T4,\n  arg5: T5,\n  callback: (err: Error | null, result: TResult) => void\n) => void;\nexport function callbackify<T1, T2, T3, T4, T5, T6>(\n  fn: (\n    arg1: T1,\n    arg2: T2,\n    arg3: T3,\n    arg4: T4,\n    arg5: T5,\n    arg6: T6\n  ) => Promise<void>\n): (\n  arg1: T1,\n  arg2: T2,\n  arg3: T3,\n  arg4: T4,\n  arg5: T5,\n  arg6: T6,\n  callback: (err: Error) => void\n) => void;\nexport function callbackify<T1, T2, T3, T4, T5, T6, TResult>(\n  fn: (\n    arg1: T1,\n    arg2: T2,\n    arg3: T3,\n    arg4: T4,\n    arg5: T5,\n    arg6: T6\n  ) => Promise<TResult>\n): (\n  arg1: T1,\n  arg2: T2,\n  arg3: T3,\n  arg4: T4,\n  arg5: T5,\n  arg6: T6,\n  callback: (err: Error | null, result: TResult) => void\n) => void;\nexport function callbackify<T extends (...args: unknown[]) => Promise<unknown>>(\n  original: T\n): T extends (...args: infer TArgs) => Promise<infer TReturn>\n  ? (...params: [...TArgs, (err: Error, ret: TReturn) => unknown]) => void\n  : never {\n  validateFunction(original, 'original');\n\n  function callbackified(\n    this: unknown,\n    ...args: [...unknown[], (err: unknown, ret: unknown) => void]\n  ): void {\n    const maybeCb = args.pop();\n    validateFunction(maybeCb, 'last argument');\n    const cb = maybeCb.bind(this);\n    Reflect.apply(original, this, args).then(\n      (ret: unknown) => {\n        queueMicrotask(() => cb(null, ret));\n      },\n      (rej: unknown) => {\n        queueMicrotask(() => {\n          callbackifyOnRejected(rej, cb);\n        });\n      }\n    );\n  }\n\n  const descriptors = Object.getOwnPropertyDescriptors(original);\n  if (typeof descriptors.length?.value === 'number') {\n    descriptors.length.value++;\n  }\n  if (typeof descriptors.name?.value === 'string') {\n    descriptors.name.value += 'Callbackified';\n  }\n  const propertiesValues = Object.values(descriptors);\n  for (let i = 0; i < propertiesValues.length; i++) {\n    Object.setPrototypeOf(propertiesValues[i], null);\n  }\n  Object.defineProperties(callbackified, descriptors);\n  // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n  // @ts-expect-error\n  return callbackified;\n}\n\nexport function parseEnv(content: string): Record<string, string> {\n  validateString(content, 'content');\n\n  const result: Record<string, string> = {};\n  const lines = content.split('\\n');\n\n  for (let i = 0; i < lines.length; i++) {\n    let line = lines[i];\n    if (line === undefined) continue;\n\n    if (!line.trim()) continue;\n    if (line.trimStart().startsWith('#')) continue;\n    if (line.trimStart().startsWith('export '))\n      line = line.substring(line.indexOf('export ') + 7);\n\n    const equalIndex = line.indexOf('=');\n    if (equalIndex === -1) continue;\n\n    const key = line.substring(0, equalIndex).trim();\n    if (!key) continue;\n\n    let value = line.substring(equalIndex + 1).trimStart();\n    if (value.length > 0) {\n      const maybeQuote = value[0];\n      if (maybeQuote === '\"' || maybeQuote === \"'\" || maybeQuote === '`') {\n        // Check if the closing quote is on the same line\n        const closeIndex = value.indexOf(maybeQuote, 1);\n\n        if (closeIndex !== -1) {\n          // Found closing quote on same line\n          value = value.substring(1, closeIndex);\n          // Only handle escape sequences for double quotes\n          if (maybeQuote === '\"') value = value.replace(/\\\\n/g, '\\n');\n          // For single quotes and backticks, keep \\n as literal\n        } else {\n          // Check for multiline strings\n          let fullValue = value.substring(1); // Remove opening quote\n          let currentLine = i;\n          let foundClosingQuote = false;\n\n          // Look for closing quote in subsequent lines\n          while (currentLine < lines.length - 1) {\n            currentLine++;\n            const nextLine = lines[currentLine];\n            if (nextLine !== undefined) {\n              const closeInNextLine = nextLine.indexOf(maybeQuote);\n              if (closeInNextLine !== -1) {\n                // Found closing quote\n                fullValue += '\\n' + nextLine.substring(0, closeInNextLine);\n                value = fullValue;\n\n                // Only handle escape sequences for double quotes\n                if (maybeQuote === '\"') {\n                  value = value.replace(/\\\\n/g, '\\n');\n                }\n\n                foundClosingQuote = true;\n                i = currentLine; // Update line counter\n                break;\n              } else {\n                // Continue building multiline value\n                fullValue += '\\n' + nextLine;\n              }\n            }\n          }\n\n          if (!foundClosingQuote) {\n            if (value.length === 1) {\n              // Just the quote character, return it as the value\n              value = maybeQuote;\n            } else {\n              // Return content after the opening quote\n              value = value.substring(1);\n            }\n          }\n        }\n      } else {\n        const hashIndex = value.indexOf('#');\n        if (hashIndex !== -1) value = value.substring(0, hashIndex);\n        value = value.trimEnd();\n      }\n    }\n    result[key] = value;\n  }\n  return result;\n}\n\nexport type NonEmptyArray<T> = [T, ...T[]];\nexport type PositiveInteger<T extends number> = T extends 0\n  ? never\n  : `${T}` extends `${infer _}.${infer _}`\n    ? never\n    : `${T}` extends `-${infer _}`\n      ? never\n      : T;\nexport type FixedLengthArray<\n  T,\n  Length extends number,\n  Accumulator extends NonEmptyArray<T> = [T],\n> =\n  Length extends PositiveInteger<Length>\n    ? number extends Length\n      ? NonEmptyArray<T>\n      : Length extends Accumulator['length']\n        ? Accumulator\n        : FixedLengthArray<T, Length, [T, ...Accumulator]>\n    : never;\n"
  },
  {
    "path": "src/node/internal/internal_zlib.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport {\n  default as zlibUtil,\n  type ZlibOptions,\n  type BrotliOptions,\n  type ZstdOptions,\n} from 'node-internal:zlib';\nimport { Buffer } from 'node-internal:internal_buffer';\nimport { validateUint32 } from 'node-internal:validators';\nimport { ERR_INVALID_ARG_TYPE } from 'node-internal:internal_errors';\nimport {\n  Zlib,\n  Brotli,\n  Zstd,\n  zstdInitCParamsArray,\n  zstdInitDParamsArray,\n  kMaxZstdCParam,\n  kMaxZstdDParam,\n  type ZlibBase,\n} from 'node-internal:internal_zlib_base';\n\ntype ZlibResult = Buffer | { buffer: Buffer; engine: ZlibBase };\ntype CompressCallback = (err: Error | null, result?: ZlibResult) => void;\n\nconst {\n  CONST_DEFLATE,\n  CONST_DEFLATERAW,\n  CONST_INFLATE,\n  CONST_INFLATERAW,\n  CONST_GUNZIP,\n  CONST_GZIP,\n  CONST_UNZIP,\n  CONST_BROTLI_DECODE,\n  CONST_BROTLI_ENCODE,\n  CONST_ZSTD_ENCODE,\n  CONST_ZSTD_DECODE,\n} = zlibUtil;\n\nconst ZlibMode = {\n  DEFLATE: 1,\n  INFLATE: 2,\n  GZIP: 3,\n  GUNZIP: 4,\n  DEFLATERAW: 5,\n  INFLATERAW: 6,\n  UNZIP: 7,\n};\n\nexport function crc32(\n  data: ArrayBufferView | string,\n  value: number = 0\n): number {\n  validateUint32(value, 'value');\n  return zlibUtil.crc32(data, value);\n}\n\nfunction processChunk(\n  engine: ZlibBase,\n  data: ArrayBufferView | string\n): ZlibResult {\n  return {\n    engine,\n    // TODO(soon): What is the proper way to deal with ArrayBufferView to Buffer typing issues?\n    buffer: engine._processChunk(\n      typeof data === 'string' ? Buffer.from(data) : (data as Buffer),\n      engine._finishFlushFlag\n    ),\n  };\n}\n\nfunction zlibSyncImpl(\n  data: ArrayBufferView | string,\n  options: ZlibOptions,\n  mode: (typeof ZlibMode)[keyof typeof ZlibMode]\n): ZlibResult {\n  if (!options.info) {\n    // Fast path, where we send the data directly to C++\n    return Buffer.from(zlibUtil.zlibSync(data, options, mode));\n  }\n\n  // Else, use the Engine class in sync mode\n  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n  return processChunk(new CLASS_BY_MODE[mode]!(options), data);\n}\n\nexport function inflateSync(\n  data: ArrayBufferView | string,\n  options: ZlibOptions = {}\n): ZlibResult {\n  return zlibSyncImpl(data, options, CONST_INFLATE);\n}\n\nexport function deflateSync(\n  data: ArrayBufferView | string,\n  options: ZlibOptions = {}\n): ZlibResult {\n  return zlibSyncImpl(data, options, CONST_DEFLATE);\n}\n\nexport function gunzipSync(\n  data: ArrayBufferView | string,\n  options: ZlibOptions = {}\n): ZlibResult {\n  return zlibSyncImpl(data, options, CONST_GUNZIP);\n}\n\nexport function gzipSync(\n  data: ArrayBufferView | string,\n  options: ZlibOptions = {}\n): ZlibResult {\n  return zlibSyncImpl(data, options, CONST_GZIP);\n}\n\nexport function inflateRawSync(\n  data: ArrayBufferView | string,\n  options: ZlibOptions = {}\n): ZlibResult {\n  return zlibSyncImpl(data, options, CONST_INFLATERAW);\n}\n\nexport function deflateRawSync(\n  data: ArrayBufferView | string,\n  options: ZlibOptions = {}\n): ZlibResult {\n  return zlibSyncImpl(data, options, CONST_DEFLATERAW);\n}\n\nexport function unzipSync(\n  data: ArrayBufferView | string,\n  options: ZlibOptions = {}\n): ZlibResult {\n  return zlibSyncImpl(data, options, CONST_UNZIP);\n}\n\nexport function brotliDecompressSync(\n  data: ArrayBufferView | string,\n  options: BrotliOptions = {}\n): ZlibResult {\n  if (!options.info) {\n    // Fast path, where we send the data directly to C++\n    return Buffer.from(zlibUtil.brotliDecompressSync(data, options));\n  }\n\n  // Else, use the Engine class in sync mode\n  return processChunk(new BrotliDecompress(options), data);\n}\n\nexport function brotliCompressSync(\n  data: ArrayBufferView | string,\n  options: BrotliOptions = {}\n): ZlibResult {\n  if (!options.info) {\n    // Fast path, where we send the data directly to C++\n    return Buffer.from(zlibUtil.brotliCompressSync(data, options));\n  }\n\n  // Else, use the Engine class in sync mode\n  return processChunk(new BrotliCompress(options), data);\n}\n\nfunction normalizeArgs(\n  options: ZlibOptions | CompressCallback,\n  callback?: CompressCallback\n): [ZlibOptions, CompressCallback] {\n  if (typeof options === 'function') {\n    return [{}, options];\n  } else if (typeof callback === 'function') {\n    return [options, callback];\n  }\n\n  throw new ERR_INVALID_ARG_TYPE('callback', 'Function', callback);\n}\n\nfunction processChunkCaptureError(\n  engine: ZlibBase,\n  data: ArrayBufferView | string,\n  cb: CompressCallback\n): void {\n  try {\n    const res = processChunk(engine, data);\n    queueMicrotask(() => {\n      cb(null, res);\n    });\n  } catch (err: unknown) {\n    if (err instanceof Error) {\n      queueMicrotask(() => {\n        cb(err);\n      });\n      return;\n    }\n\n    const unreachable = new Error('Unreachable');\n    unreachable.cause = err;\n    throw unreachable;\n  }\n}\n\nfunction zlibImpl(\n  mode: (typeof ZlibMode)[keyof typeof ZlibMode],\n  data: ArrayBufferView | string,\n  optionsOrCallback: ZlibOptions | CompressCallback,\n  callbackOrUndefined?: CompressCallback\n): void {\n  const [options, callback] = normalizeArgs(\n    optionsOrCallback,\n    callbackOrUndefined\n  );\n\n  if (!options.info) {\n    // Fast path\n    zlibUtil.zlib(data, options, mode, (res) => {\n      queueMicrotask(() => {\n        if (res instanceof Error) {\n          callback(res);\n        } else {\n          callback(null, Buffer.from(res));\n        }\n      });\n    });\n\n    return;\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n  processChunkCaptureError(new CLASS_BY_MODE[mode]!(options), data, callback);\n}\n\nexport function inflate(\n  data: ArrayBufferView | string,\n  options: ZlibOptions | CompressCallback,\n  callback?: CompressCallback\n): void {\n  zlibImpl(CONST_INFLATE, data, options, callback);\n}\n\nexport function unzip(\n  data: ArrayBufferView | string,\n  options: ZlibOptions | CompressCallback,\n  callback?: CompressCallback\n): void {\n  zlibImpl(CONST_UNZIP, data, options, callback);\n}\n\nexport function inflateRaw(\n  data: ArrayBufferView | string,\n  options: ZlibOptions | CompressCallback,\n  callback?: CompressCallback\n): void {\n  zlibImpl(CONST_INFLATERAW, data, options, callback);\n}\n\nexport function gunzip(\n  data: ArrayBufferView | string,\n  options: ZlibOptions | CompressCallback,\n  callback?: CompressCallback\n): void {\n  zlibImpl(CONST_GUNZIP, data, options, callback);\n}\n\nexport function deflate(\n  data: ArrayBufferView | string,\n  options: ZlibOptions | CompressCallback,\n  callback?: CompressCallback\n): void {\n  zlibImpl(CONST_DEFLATE, data, options, callback);\n}\n\nexport function deflateRaw(\n  data: ArrayBufferView | string,\n  options: ZlibOptions | CompressCallback,\n  callback?: CompressCallback\n): void {\n  zlibImpl(CONST_DEFLATERAW, data, options, callback);\n}\n\nexport function gzip(\n  data: ArrayBufferView | string,\n  options: ZlibOptions | CompressCallback,\n  callback?: CompressCallback\n): void {\n  zlibImpl(CONST_GZIP, data, options, callback);\n}\n\nexport function brotliDecompress(\n  data: ArrayBufferView | string,\n  optionsOrCallback: BrotliOptions | CompressCallback,\n  callbackOrUndefined?: CompressCallback\n): void {\n  const [options, callback] = normalizeArgs(\n    optionsOrCallback,\n    callbackOrUndefined\n  );\n\n  if (!options.info) {\n    // Fast path\n    zlibUtil.brotliDecompress(data, options, (res) => {\n      queueMicrotask(() => {\n        if (res instanceof Error) {\n          callback(res);\n        } else {\n          callback(null, Buffer.from(res));\n        }\n      });\n    });\n\n    return;\n  }\n\n  processChunkCaptureError(new BrotliDecompress(options), data, callback);\n}\n\nexport function brotliCompress(\n  data: ArrayBufferView | string,\n  optionsOrCallback: BrotliOptions | CompressCallback,\n  callbackOrUndefined?: CompressCallback\n): void {\n  const [options, callback] = normalizeArgs(\n    optionsOrCallback,\n    callbackOrUndefined\n  );\n\n  if (!options.info) {\n    // Fast path\n    zlibUtil.brotliCompress(data, options, (res) => {\n      queueMicrotask(() => {\n        if (res instanceof Error) {\n          callback(res);\n        } else {\n          callback(null, Buffer.from(res));\n        }\n      });\n    });\n\n    return;\n  }\n\n  processChunkCaptureError(new BrotliCompress(options), data, callback);\n}\n\nexport function zstdDecompressSync(\n  data: ArrayBufferView | string,\n  options: ZstdOptions = {}\n): ZlibResult {\n  if (!options.info) {\n    // Fast path, where we send the data directly to C++\n    return Buffer.from(zlibUtil.zstdDecompressSync(data, options));\n  }\n\n  // Else, use the Engine class in sync mode\n  return processChunk(new ZstdDecompress(options), data);\n}\n\nexport function zstdCompressSync(\n  data: ArrayBufferView | string,\n  options: ZstdOptions = {}\n): ZlibResult {\n  if (!options.info) {\n    // Fast path, where we send the data directly to C++\n    return Buffer.from(zlibUtil.zstdCompressSync(data, options));\n  }\n\n  // Else, use the Engine class in sync mode\n  return processChunk(new ZstdCompress(options), data);\n}\n\nexport function zstdDecompress(\n  data: ArrayBufferView | string,\n  optionsOrCallback: ZstdOptions | CompressCallback,\n  callbackOrUndefined?: CompressCallback\n): void {\n  const [options, callback] = normalizeArgs(\n    optionsOrCallback,\n    callbackOrUndefined\n  );\n\n  if (!options.info) {\n    // Fast path\n    zlibUtil.zstdDecompress(data, options, (res) => {\n      queueMicrotask(() => {\n        if (res instanceof Error) {\n          callback(res);\n        } else {\n          callback(null, Buffer.from(res));\n        }\n      });\n    });\n\n    return;\n  }\n\n  processChunkCaptureError(new ZstdDecompress(options), data, callback);\n}\n\nexport function zstdCompress(\n  data: ArrayBufferView | string,\n  optionsOrCallback: ZstdOptions | CompressCallback,\n  callbackOrUndefined?: CompressCallback\n): void {\n  const [options, callback] = normalizeArgs(\n    optionsOrCallback,\n    callbackOrUndefined\n  );\n\n  if (!options.info) {\n    // Fast path\n    zlibUtil.zstdCompress(data, options, (res) => {\n      queueMicrotask(() => {\n        if (res instanceof Error) {\n          callback(res);\n        } else {\n          callback(null, Buffer.from(res));\n        }\n      });\n    });\n\n    return;\n  }\n\n  processChunkCaptureError(new ZstdCompress(options), data, callback);\n}\nexport class Gzip extends Zlib {\n  constructor(options: ZlibOptions) {\n    super(options, CONST_GZIP);\n  }\n}\n\nexport class Gunzip extends Zlib {\n  constructor(options: ZlibOptions) {\n    super(options, CONST_GUNZIP);\n  }\n}\n\nexport class Deflate extends Zlib {\n  constructor(options: ZlibOptions) {\n    super(options, CONST_DEFLATE);\n  }\n}\n\nexport class DeflateRaw extends Zlib {\n  constructor(options?: ZlibOptions) {\n    if (options?.windowBits === 8) {\n      options.windowBits = 9;\n    }\n    super(options, CONST_DEFLATERAW);\n  }\n}\n\nexport class Inflate extends Zlib {\n  constructor(options: ZlibOptions) {\n    super(options, CONST_INFLATE);\n  }\n}\n\nexport class InflateRaw extends Zlib {\n  constructor(options: ZlibOptions) {\n    super(options, CONST_INFLATERAW);\n  }\n}\n\nexport class Unzip extends Zlib {\n  constructor(options: ZlibOptions) {\n    super(options, CONST_UNZIP);\n  }\n}\n\nexport class BrotliCompress extends Brotli {\n  constructor(options: BrotliOptions) {\n    super(options, CONST_BROTLI_ENCODE);\n  }\n}\n\nexport class BrotliDecompress extends Brotli {\n  constructor(options: BrotliOptions) {\n    super(options, CONST_BROTLI_DECODE);\n  }\n}\n\nexport class ZstdCompress extends Zstd {\n  constructor(options: ZstdOptions) {\n    super(options, CONST_ZSTD_ENCODE, zstdInitCParamsArray, kMaxZstdCParam);\n  }\n}\n\nexport class ZstdDecompress extends Zstd {\n  constructor(options: ZstdOptions) {\n    super(options, CONST_ZSTD_DECODE, zstdInitDParamsArray, kMaxZstdDParam);\n  }\n}\n\nconst CLASS_BY_MODE: Record<\n  (typeof ZlibMode)[keyof typeof ZlibMode],\n  | typeof Deflate\n  | typeof Inflate\n  | typeof InflateRaw\n  | typeof Unzip\n  | typeof BrotliCompress\n  | typeof BrotliDecompress\n> = {\n  [ZlibMode.DEFLATE]: Deflate,\n  [ZlibMode.INFLATE]: Inflate,\n  [ZlibMode.DEFLATERAW]: DeflateRaw,\n  [ZlibMode.INFLATERAW]: InflateRaw,\n  [ZlibMode.GZIP]: Gzip,\n  [ZlibMode.GUNZIP]: Gunzip,\n  [ZlibMode.UNZIP]: Unzip,\n};\n\nexport function createGzip(options: ZlibOptions): Gzip {\n  return new Gzip(options);\n}\n\nexport function createGunzip(options: ZlibOptions): Gunzip {\n  return new Gunzip(options);\n}\n\nexport function createDeflate(options: ZlibOptions): Deflate {\n  return new Deflate(options);\n}\n\nexport function createDeflateRaw(options: ZlibOptions): DeflateRaw {\n  return new DeflateRaw(options);\n}\n\nexport function createInflate(options: ZlibOptions): Inflate {\n  return new Inflate(options);\n}\n\nexport function createInflateRaw(options: ZlibOptions): InflateRaw {\n  return new InflateRaw(options);\n}\n\nexport function createUnzip(options: ZlibOptions): Unzip {\n  return new Unzip(options);\n}\n\nexport function createBrotliCompress(options: BrotliOptions): BrotliCompress {\n  return new BrotliCompress(options);\n}\n\nexport function createBrotliDecompress(\n  options: BrotliOptions\n): BrotliDecompress {\n  return new BrotliDecompress(options);\n}\n\nexport function createZstdCompress(options: ZstdOptions): ZstdCompress {\n  return new ZstdCompress(options);\n}\n\nexport function createZstdDecompress(options: ZstdOptions): ZstdDecompress {\n  return new ZstdDecompress(options);\n}\n"
  },
  {
    "path": "src/node/internal/internal_zlib_base.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport {\n  default as zlibUtil,\n  type ZlibOptions,\n  type BrotliOptions,\n  type ZstdOptions,\n} from 'node-internal:zlib';\nimport { Buffer, kMaxLength } from 'node-internal:internal_buffer';\nimport {\n  checkRangesOrGetDefault,\n  checkFiniteNumber,\n} from 'node-internal:validators';\nimport {\n  ERR_OUT_OF_RANGE,\n  ERR_BUFFER_TOO_LARGE,\n  ERR_INVALID_ARG_TYPE,\n  ERR_BROTLI_INVALID_PARAM,\n  ERR_ZSTD_INVALID_PARAM,\n  ERR_ZLIB_INITIALIZATION_FAILED,\n  NodeError,\n} from 'node-internal:internal_errors';\nimport { Transform, type DuplexOptions } from 'node-internal:streams_transform';\nimport { eos as finished } from 'node-internal:streams_end_of_stream';\nimport {\n  isArrayBufferView,\n  isAnyArrayBuffer,\n} from 'node-internal:internal_types';\nimport { constants } from 'node-internal:internal_zlib_constants';\n\n// Explicitly import `ok()` to avoid typescript error requiring every name in the call target to\n// be annotated with an explicit type annotation.\nimport assert, { ok } from 'node-internal:internal_assert';\n\nconst {\n  CONST_INFLATE,\n  CONST_GUNZIP,\n  CONST_GZIP,\n  CONST_UNZIP,\n  CONST_Z_DEFAULT_CHUNK,\n  CONST_Z_DEFAULT_STRATEGY,\n  CONST_Z_DEFAULT_MEMLEVEL,\n  CONST_Z_DEFAULT_WINDOWBITS,\n  CONST_Z_DEFAULT_COMPRESSION,\n  CONST_Z_FIXED,\n  CONST_Z_MAX_LEVEL,\n  CONST_Z_MAX_MEMLEVEL,\n  CONST_Z_MAX_WINDOWBITS,\n  CONST_Z_MIN_LEVEL,\n  CONST_Z_MIN_MEMLEVEL,\n  CONST_Z_SYNC_FLUSH,\n  CONST_Z_NO_FLUSH,\n  CONST_Z_BLOCK,\n  CONST_Z_MIN_CHUNK,\n  CONST_Z_PARTIAL_FLUSH,\n  CONST_Z_FULL_FLUSH,\n  CONST_Z_FINISH,\n  CONST_BROTLI_ENCODE,\n  CONST_BROTLI_DECODE,\n  CONST_BROTLI_OPERATION_PROCESS,\n  CONST_BROTLI_OPERATION_EMIT_METADATA,\n  CONST_BROTLI_OPERATION_FINISH,\n  CONST_BROTLI_OPERATION_FLUSH,\n  CONST_ZSTD_ENCODE,\n  CONST_ZSTD_DECODE,\n  CONST_ZSTD_e_continue,\n  CONST_ZSTD_e_end,\n  CONST_ZSTD_e_flush,\n} = zlibUtil;\n\n// This type contains all possible handler types.\ntype ZlibHandleType =\n  | zlibUtil.ZlibStream\n  | zlibUtil.BrotliEncoder\n  | zlibUtil.BrotliDecoder\n  | zlibUtil.ZstdEncoder\n  | zlibUtil.ZstdDecoder;\nexport const owner_symbol = Symbol('owner');\n\nconst FLUSH_BOUND_IDX_NORMAL: number = 0;\nconst FLUSH_BOUND_IDX_BROTLI: number = 1;\nconst FLUSH_BOUND_IDX_ZSTD: number = 2;\nconst FLUSH_BOUND: [[number, number], [number, number], [number, number]] = [\n  [CONST_Z_NO_FLUSH, CONST_Z_BLOCK],\n  [CONST_BROTLI_OPERATION_PROCESS, CONST_BROTLI_OPERATION_EMIT_METADATA],\n  [CONST_ZSTD_e_continue, CONST_ZSTD_e_end],\n];\n\nconst kFlushFlag = Symbol('kFlushFlag');\nconst kError = Symbol('kError');\n\nfunction processCallback(this: ZlibHandleType): void {\n  // This callback's context (`this`) is the `_handle` (ZCtx) object. It is\n  // important to null out the values once they are no longer needed since\n  // `_handle` can stay in memory long after the buffer is needed.\n  // eslint-disable-next-line @typescript-eslint/no-this-alias\n  const handle = this;\n  const self = this[owner_symbol];\n  ok(self, 'Owner symbol should exist');\n  const state = self._writeState;\n\n  if (self.destroyed) {\n    this.buffer = null;\n    this.cb();\n    return;\n  }\n\n  const availOutAfter = state[0] as number;\n  const availInAfter = state[1] as number;\n\n  const inDelta = handle.availInBefore - availInAfter;\n  self.bytesWritten += inDelta;\n\n  const have = handle.availOutBefore - availOutAfter;\n  let streamBufferIsFull = false;\n  if (have > 0) {\n    const out = self._outBuffer.slice(self._outOffset, self._outOffset + have);\n    self._outOffset += have;\n    streamBufferIsFull = !self.push(out);\n  } else {\n    assert.strictEqual(have, 0, 'have should not go down');\n  }\n\n  /* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition */\n  if (self.destroyed) {\n    this.cb();\n    return;\n  }\n\n  // Exhausted the output buffer, or used all the input create a new one.\n  if (availOutAfter === 0 || self._outOffset >= self._chunkSize) {\n    handle.availOutBefore = self._chunkSize;\n    self._outOffset = 0;\n    self._outBuffer = Buffer.allocUnsafe(self._chunkSize);\n  }\n\n  if (availOutAfter === 0) {\n    // Not actually done. Need to reprocess.\n    // Also, update the availInBefore to the availInAfter value,\n    // so that if we have to hit it a third (fourth, etc.) time,\n    // it'll have the correct byte counts.\n    handle.inOff += inDelta;\n    handle.availInBefore = availInAfter;\n\n    if (!streamBufferIsFull) {\n      ok(this.buffer, 'Buffer should not have been null');\n      this.write(\n        handle.flushFlag,\n        this.buffer, // in\n        handle.inOff, // in_off\n        handle.availInBefore, // in_len\n        self._outBuffer, // out\n        self._outOffset, // out_off\n        self._chunkSize\n      ); // out_len\n    } else {\n      // eslint-disable-next-line @typescript-eslint/unbound-method\n      const oldRead = self._read;\n      self._read = (n): void => {\n        ok(this.buffer, 'Buffer should not have been null');\n        self._read = oldRead;\n        this.write(\n          handle.flushFlag,\n          this.buffer, // in\n          handle.inOff, // in_off\n          handle.availInBefore, // in_len\n          self._outBuffer, // out\n          self._outOffset, // out_off\n          self._chunkSize // out_len\n        );\n        self._read(n);\n      };\n    }\n    return;\n  }\n\n  if (availInAfter > 0) {\n    // If we have more input that should be written, but we also have output\n    // space available, that means that the compression library was not\n    // interested in receiving more data, and in particular that the input\n    // stream has ended early.\n    // This applies to streams where we don't check data past the end of\n    // what was consumed; that is, everything except Gunzip/Unzip.\n    self.push(null);\n  }\n\n  // Finished with the chunk.\n  this.buffer = null;\n  this.cb();\n}\n\n// If a flush is scheduled while another flush is still pending, a way to figure\n// out which one is the \"stronger\" flush is needed.\n// This is currently only used to figure out which flush flag to use for the\n// last chunk.\n// Roughly, the following holds:\n// Z_NO_FLUSH (< Z_TREES) < Z_BLOCK < Z_PARTIAL_FLUSH <\n//     Z_SYNC_FLUSH < Z_FULL_FLUSH < Z_FINISH\nconst flushiness: number[] = [];\nconst kFlushFlagList: number[] = [\n  CONST_Z_NO_FLUSH,\n  CONST_Z_BLOCK,\n  CONST_Z_PARTIAL_FLUSH,\n  CONST_Z_SYNC_FLUSH,\n  CONST_Z_FULL_FLUSH,\n  CONST_Z_FINISH,\n];\nfor (let i = 0; i < kFlushFlagList.length; i++) {\n  flushiness[kFlushFlagList[i] as number] = i;\n}\n\nfunction maxFlush(a: number, b: number): number {\n  return (flushiness[a] as number) > (flushiness[b] as number) ? a : b;\n}\n\n// Set up a list of 'special' buffers that can be written using .write()\n// from the .flush() code as a way of introducing flushing operations into the\n// write sequence.\nconst kFlushBuffers: (Buffer & { [kFlushFlag]: number })[] = [];\n{\n  const dummyArrayBuffer = new ArrayBuffer(0);\n  for (const flushFlag of kFlushFlagList) {\n    const buf = Buffer.from(dummyArrayBuffer) as Buffer & {\n      [kFlushFlag]: number;\n    };\n    buf[kFlushFlag] = flushFlag;\n    kFlushBuffers[flushFlag] = buf;\n  }\n}\n\nfunction zlibOnError(\n  this: ZlibHandleType,\n  errno: number,\n  code: string,\n  message: string\n): void {\n  const self = this[owner_symbol];\n  ok(self, 'Owner symbol should exist');\n  const error = new NodeError(code, message);\n  // @ts-expect-error Err number is expected.\n  error.errno = errno;\n  self.destroy(error);\n  self[kError] = error;\n}\n\nfunction processChunkSync(\n  self: Zlib,\n  chunk: Buffer,\n  flushFlag: number\n): Buffer | Uint8Array {\n  let availInBefore = chunk.byteLength;\n  let availOutBefore = self._chunkSize - self._outOffset;\n  let inOff = 0;\n  let availOutAfter;\n  let availInAfter;\n\n  const buffers: (Buffer | Uint8Array)[] = [];\n  let nread = 0;\n  let inputRead = 0;\n  const state = self._writeState;\n  const handle = self._handle;\n  let buffer = self._outBuffer;\n  let offset = self._outOffset;\n  const chunkSize = self._chunkSize;\n\n  let error: Error | undefined;\n  self.on('error', function onError(er) {\n    error = er;\n  });\n\n  /* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition */\n  while (true) {\n    ok(handle, 'Handle should have been defined');\n    handle.writeSync(\n      flushFlag,\n      chunk, // in\n      inOff, // in_off\n      availInBefore, // in_len\n      buffer, // out\n      offset, // out_off\n      availOutBefore // out_len\n    );\n    if (error) throw error;\n    else if (self[kError]) throw self[kError];\n\n    [availOutAfter, availInAfter] = state as unknown as [number, number];\n\n    const inDelta = availInBefore - availInAfter;\n    inputRead += inDelta;\n\n    const have = availOutBefore - availOutAfter;\n    if (have > 0) {\n      const out = buffer.slice(offset, offset + have);\n      offset += have;\n      buffers.push(out);\n      nread += out.byteLength;\n\n      if (nread > self._maxOutputLength) {\n        _close(self);\n        throw new ERR_BUFFER_TOO_LARGE(self._maxOutputLength);\n      }\n    } else {\n      assert.strictEqual(have, 0, 'have should not go down');\n    }\n\n    // Exhausted the output buffer, or used all the input create a new one.\n    if (availOutAfter === 0 || offset >= chunkSize) {\n      availOutBefore = chunkSize;\n      offset = 0;\n      buffer = Buffer.allocUnsafe(chunkSize);\n    }\n\n    if (availOutAfter === 0) {\n      // Not actually done. Need to reprocess.\n      // Also, update the availInBefore to the availInAfter value,\n      // so that if we have to hit it a third (fourth, etc.) time,\n      // it'll have the correct byte counts.\n      inOff += inDelta;\n      availInBefore = availInAfter;\n    } else {\n      break;\n    }\n  }\n\n  self.bytesWritten = inputRead;\n  _close(self);\n\n  if (nread === 0) return Buffer.alloc(0);\n\n  return buffers.length === 1\n    ? (buffers[0] as Buffer)\n    : Buffer.concat(buffers, nread);\n}\n\nfunction _close(engine: ZlibBase): void {\n  engine._handle?.close();\n  engine._handle = null;\n}\n\ntype ZlibDefaultOptions = {\n  flush: number;\n  finishFlush: number;\n  fullFlush: number;\n};\n\nconst zlibDefaultOptions = {\n  flush: CONST_Z_NO_FLUSH,\n  finishFlush: CONST_Z_FINISH,\n  fullFlush: CONST_Z_FULL_FLUSH,\n};\n\nexport class ZlibBase extends Transform {\n  bytesWritten: number = 0;\n\n  _maxOutputLength: number;\n  _outBuffer: Buffer;\n  _outOffset: number = 0;\n  _chunkSize: number;\n  _defaultFlushFlag: number;\n  _finishFlushFlag: number;\n  _defaultFullFlushFlag: number;\n  _info: boolean;\n  _handle: ZlibHandleType | null = null;\n  _writeState = new Uint32Array(2);\n  _writesInProgress: number = 0;\n  _hadWrites: boolean = false;\n\n  [kError]: NodeError | undefined;\n\n  constructor(\n    opts: ZlibOptions & DuplexOptions,\n    mode: number,\n    handle: ZlibHandleType,\n    { flush, finishFlush, fullFlush }: ZlibDefaultOptions = zlibDefaultOptions\n  ) {\n    let chunkSize = CONST_Z_DEFAULT_CHUNK;\n    let maxOutputLength = kMaxLength;\n\n    let flushBoundIdx;\n    if (mode === CONST_BROTLI_ENCODE || mode === CONST_BROTLI_DECODE) {\n      flushBoundIdx = FLUSH_BOUND_IDX_BROTLI;\n    } else if (mode === CONST_ZSTD_ENCODE || mode === CONST_ZSTD_DECODE) {\n      flushBoundIdx = FLUSH_BOUND_IDX_ZSTD;\n    } else {\n      flushBoundIdx = FLUSH_BOUND_IDX_NORMAL;\n    }\n\n    /* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition */\n    if (opts) {\n      if (opts.chunkSize != null) {\n        chunkSize = opts.chunkSize;\n      }\n      if (!checkFiniteNumber(chunkSize, 'options.chunkSize')) {\n        chunkSize = CONST_Z_DEFAULT_CHUNK;\n      } else if (chunkSize < CONST_Z_MIN_CHUNK) {\n        throw new ERR_OUT_OF_RANGE(\n          'options.chunkSize',\n          `>= ${CONST_Z_MIN_CHUNK}`,\n          chunkSize\n        );\n      }\n\n      flush = checkRangesOrGetDefault(\n        opts.flush,\n        'options.flush',\n        FLUSH_BOUND[flushBoundIdx]?.[0] as number,\n        FLUSH_BOUND[flushBoundIdx]?.[1] as number,\n        flush\n      );\n\n      finishFlush = checkRangesOrGetDefault(\n        opts.finishFlush,\n        'options.finishFlush',\n        FLUSH_BOUND[flushBoundIdx]?.[0] as number,\n        FLUSH_BOUND[flushBoundIdx]?.[1] as number,\n        finishFlush\n      );\n\n      maxOutputLength = checkRangesOrGetDefault(\n        opts.maxOutputLength,\n        'options.maxOutputLength',\n        1,\n        kMaxLength,\n        kMaxLength\n      );\n\n      if (opts.encoding || opts.objectMode || opts.writableObjectMode) {\n        opts = { ...opts };\n        opts.encoding = undefined;\n        opts.objectMode = false;\n        opts.writableObjectMode = false;\n      }\n    }\n\n    // @ts-expect-error TODO: Find a way to avoid having \"unknown\"\n    super({ autoDestroy: true, ...opts } as unknown);\n\n    // Error handler by processCallback() and zlibOnError()\n    handle.setErrorHandler(zlibOnError.bind(handle));\n    handle[owner_symbol] = this as never;\n    this._handle = handle;\n    this._outBuffer = Buffer.allocUnsafe(chunkSize);\n    this._outOffset = 0;\n    this._chunkSize = chunkSize;\n    this._defaultFlushFlag = flush;\n    this._finishFlushFlag = finishFlush;\n    this._defaultFullFlushFlag = fullFlush;\n    this._info = Boolean(opts.info);\n    this._maxOutputLength = maxOutputLength;\n  }\n\n  // Note: This is intentionally a getter that shadows the property from Transform\n  // @ts-expect-error TS2611 Property/accessor mismatch with Transform._closed\n  get _closed(): boolean {\n    return this._handle == null;\n  }\n\n  // @deprecated Use `bytesWritten` instead.\n  get bytesRead(): number {\n    return this.bytesWritten;\n  }\n\n  // @deprecated Use `bytesWritten` instead.\n  set bytesRead(value: number) {\n    this.bytesWritten = value;\n  }\n\n  reset(): void {\n    ok(this._handle, 'zlib binding closed');\n    this._handle.reset();\n  }\n\n  // This is the _flush function called by the transform class,\n  // internally, when the last chunk has been written.\n  override _flush(callback: () => void): void {\n    // If there are writes in progress, wait for them to complete\n    if (this._writesInProgress > 0) {\n      // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression\n      queueMicrotask(() => this._flush(callback));\n      return;\n    }\n\n    // If there were writes, add extra microtask to ensure data from processCallback has been pushed\n    if (this._hadWrites) {\n      queueMicrotask(() => {\n        const chunk = Buffer.alloc(0) as Buffer & { [kFlushFlag]?: number };\n        chunk[kFlushFlag] = this._finishFlushFlag;\n        this._transform(chunk, 'utf8', callback);\n      });\n    } else {\n      // No writes occurred, flush immediately\n      const chunk = Buffer.alloc(0) as Buffer & { [kFlushFlag]?: number };\n      chunk[kFlushFlag] = this._finishFlushFlag;\n      this._transform(chunk, 'utf8', callback);\n    }\n  }\n\n  // Force Transform compat behavior.\n  override _final(callback: () => void): void {\n    callback();\n  }\n\n  flush(kind: number, callback: () => void): void;\n  flush(callback?: () => void): void;\n  flush(kind?: number | (() => void), callback?: () => void): void {\n    if (typeof kind === 'function' || (kind === undefined && !callback)) {\n      callback = kind as (() => void) | undefined;\n      kind = this._defaultFullFlushFlag;\n    }\n\n    if (this.writableFinished) {\n      if (callback) {\n        queueMicrotask(callback);\n      }\n    } else if (this.writableEnded) {\n      if (callback) {\n        this.once('end', callback);\n      }\n    } else {\n      this.write(kFlushBuffers[kind as number], 'utf8', callback);\n    }\n  }\n\n  close(callback?: () => void): void {\n    if (callback) {\n      finished(this, callback);\n    }\n    this.destroy();\n  }\n\n  override _destroy<T extends Error>(\n    err: T,\n    callback: (err: T) => never\n  ): void {\n    _close(this);\n    callback(err);\n  }\n\n  override _transform(\n    chunk: Buffer & { [kFlushFlag]?: number },\n    _encoding: BufferEncoding,\n    cb: () => void\n  ): void {\n    let flushFlag = this._defaultFlushFlag;\n    // We use a 'fake' zero-length chunk to carry information about flushes from\n    // the public API to the actual stream implementation.\n    if (typeof chunk[kFlushFlag] === 'number') {\n      flushFlag = chunk[kFlushFlag];\n    }\n\n    // For the last chunk, also apply `_finishFlushFlag`.\n    if (this.writableEnded && this.writableLength === chunk.byteLength) {\n      flushFlag = maxFlush(flushFlag, this._finishFlushFlag);\n    }\n    this.#processChunk(chunk, flushFlag, cb);\n  }\n\n  // This function is left for backwards compatibility.\n  _processChunk(chunk: Buffer, flushFlag: number, cb?: undefined): Buffer;\n  _processChunk(chunk: Buffer, flushFlag: number, cb: () => void): undefined;\n  _processChunk(\n    chunk: Buffer,\n    flushFlag: number,\n    cb?: () => void\n  ): Buffer | Uint8Array | undefined {\n    if (cb != null && typeof cb === 'function') {\n      this.#processChunk(chunk, flushFlag, cb);\n      return;\n    }\n    return processChunkSync(this as never, chunk, flushFlag);\n  }\n\n  #processChunk(chunk: Buffer, flushFlag: number, cb: () => void): void {\n    if (!this._handle) {\n      queueMicrotask(cb);\n      return;\n    }\n\n    // Track that we have a write in progress\n    if (chunk.byteLength > 0) {\n      this._hadWrites = true;\n    }\n    this._writesInProgress++;\n    const originalCb = cb;\n    const wrappedCb = (): void => {\n      this._writesInProgress--;\n      originalCb();\n    };\n\n    this._handle.buffer = chunk;\n    this._handle.cb = wrappedCb;\n    this._handle.availOutBefore = this._chunkSize - this._outOffset;\n    this._handle.availInBefore = chunk.byteLength;\n    this._handle.inOff = 0;\n    this._handle.flushFlag = flushFlag;\n\n    this._handle.write(\n      flushFlag,\n      chunk, // in\n      0, // in_off\n      this._handle.availInBefore, // in_len\n      this._outBuffer, // out\n      this._outOffset, // out_off\n      this._handle.availOutBefore // out_len\n    );\n  }\n}\n\nexport class Zlib extends ZlibBase {\n  _level = CONST_Z_DEFAULT_COMPRESSION;\n  _strategy = CONST_Z_DEFAULT_STRATEGY;\n\n  constructor(options: ZlibOptions | null | undefined, mode: number) {\n    let windowBits = CONST_Z_DEFAULT_WINDOWBITS;\n    let level = CONST_Z_DEFAULT_COMPRESSION;\n    let memLevel = CONST_Z_DEFAULT_MEMLEVEL;\n    let strategy = CONST_Z_DEFAULT_STRATEGY;\n    let dictionary: ZlibOptions['dictionary'];\n\n    if (options != null) {\n      // Special case:\n      // - Compression: 0 is an invalid case.\n      // - Decompression: 0 indicates zlib to use the window size in the header of the compressed stream.\n      if (\n        (options.windowBits == null || options.windowBits === 0) &&\n        (mode === CONST_INFLATE ||\n          mode === CONST_GUNZIP ||\n          mode === CONST_UNZIP)\n      ) {\n        windowBits = 0;\n      } else {\n        // `{ windowBits: 8 }` is valid for DEFLATE but not for GZIP.\n        const min =\n          zlibUtil.CONST_Z_MIN_WINDOWBITS + (mode === CONST_GZIP ? 1 : 0);\n        windowBits = checkRangesOrGetDefault(\n          options.windowBits,\n          'options.windowBits',\n          min,\n          CONST_Z_MAX_WINDOWBITS,\n          CONST_Z_DEFAULT_WINDOWBITS\n        );\n      }\n\n      level = checkRangesOrGetDefault(\n        options.level,\n        'options.level',\n        CONST_Z_MIN_LEVEL,\n        CONST_Z_MAX_LEVEL,\n        CONST_Z_DEFAULT_COMPRESSION\n      );\n      memLevel = checkRangesOrGetDefault(\n        options.memLevel,\n        'options.memLevel',\n        CONST_Z_MIN_MEMLEVEL,\n        CONST_Z_MAX_MEMLEVEL,\n        CONST_Z_DEFAULT_MEMLEVEL\n      );\n      strategy = checkRangesOrGetDefault(\n        options.strategy,\n        'options.strategy',\n        CONST_Z_DEFAULT_STRATEGY,\n        CONST_Z_FIXED,\n        CONST_Z_DEFAULT_STRATEGY\n      );\n      dictionary = options.dictionary;\n\n      if (dictionary !== undefined && !isArrayBufferView(dictionary)) {\n        if (isAnyArrayBuffer(dictionary)) {\n          dictionary = Buffer.from(dictionary);\n        } else {\n          throw new ERR_INVALID_ARG_TYPE(\n            'options.dictionary',\n            ['Buffer', 'TypedArray', 'DataView', 'ArrayBuffer'],\n            dictionary\n          );\n        }\n      }\n    }\n\n    const writeState = new Uint32Array(2);\n    const handle = new zlibUtil.ZlibStream(mode);\n\n    handle.initialize(\n      windowBits,\n      level,\n      memLevel,\n      strategy,\n      writeState,\n\n      () => {\n        queueMicrotask(processCallback.bind(handle));\n      },\n      dictionary\n    );\n    super(options ?? {}, mode, handle);\n    this._level = level;\n    this._strategy = strategy;\n    this._handle = handle;\n    this._writeState = writeState;\n  }\n\n  params(level: number, strategy: number, callback: () => void): void {\n    checkRangesOrGetDefault(\n      level,\n      'level',\n      CONST_Z_MIN_LEVEL,\n      CONST_Z_MAX_LEVEL\n    );\n    checkRangesOrGetDefault(\n      strategy,\n      'strategy',\n      CONST_Z_DEFAULT_STRATEGY,\n      CONST_Z_FIXED\n    );\n\n    if (this._level !== level || this._strategy !== strategy) {\n      this.flush(\n        CONST_Z_SYNC_FLUSH,\n        this.#paramsAfterFlushCallback.bind(this, level, strategy, callback)\n      );\n    } else {\n      queueMicrotask(callback);\n    }\n  }\n\n  // This callback is used by `.params()` to wait until a full flush happened\n  // before adjusting the parameters. In particular, the call to the native\n  // `params()` function should not happen while a write is currently in progress\n  // on the threadpool.\n  #paramsAfterFlushCallback(\n    level: number,\n    strategy: number,\n    callback?: () => void\n  ): void {\n    ok(this._handle, 'zlib binding closed');\n    this._handle.params(level, strategy);\n    if (!this.destroyed) {\n      this._level = level;\n      this._strategy = strategy;\n      callback?.();\n    }\n  }\n}\n\nconst kMaxBrotliParam = Math.max(\n  ...Object.entries(constants).map(([key, value]) =>\n    key.startsWith('BROTLI_PARAM_') ? value : 0\n  )\n);\nconst brotliInitParamsArray = new Uint32Array(kMaxBrotliParam + 1);\nconst brotliDefaultOptions: ZlibDefaultOptions = {\n  flush: CONST_BROTLI_OPERATION_PROCESS,\n  finishFlush: CONST_BROTLI_OPERATION_FINISH,\n  fullFlush: CONST_BROTLI_OPERATION_FLUSH,\n};\n\nexport class Brotli extends ZlibBase {\n  constructor(options: BrotliOptions | undefined | null, mode: number) {\n    ok(mode === CONST_BROTLI_DECODE || mode === CONST_BROTLI_ENCODE);\n    brotliInitParamsArray.fill(-1);\n\n    if (options?.params) {\n      for (const [origKey, value] of Object.entries(options.params)) {\n        const key = +origKey;\n        if (\n          Number.isNaN(key) ||\n          key < 0 ||\n          key > kMaxBrotliParam ||\n          ((brotliInitParamsArray[key] as number) | 0) !== -1\n        ) {\n          throw new ERR_BROTLI_INVALID_PARAM(origKey);\n        }\n\n        if (typeof value !== 'number' && typeof value !== 'boolean') {\n          throw new ERR_INVALID_ARG_TYPE(\n            'options.params[key]',\n            'number',\n            value\n          );\n        }\n        // as number is required to avoid force type coercion on runtime.\n        // boolean has number representation, but typescript doesn't understand it.\n        brotliInitParamsArray[key] = value as number;\n      }\n    }\n\n    const handle =\n      mode === CONST_BROTLI_DECODE\n        ? new zlibUtil.BrotliDecoder(mode)\n        : new zlibUtil.BrotliEncoder(mode);\n\n    const _writeState = new Uint32Array(2);\n\n    // TODO(addaleax): Sometimes we generate better error codes in C++ land,\n    // e.g. ERR_BROTLI_PARAM_SET_FAILED -- it's hard to access them with\n    // the current bindings setup, though.\n    if (\n      !handle.initialize(\n        brotliInitParamsArray,\n        _writeState,\n        processCallback.bind(handle)\n      )\n    ) {\n      throw new ERR_ZLIB_INITIALIZATION_FAILED();\n    }\n\n    super(options ?? {}, mode, handle, brotliDefaultOptions);\n    this._writeState = _writeState;\n  }\n}\n\nexport const kMaxZstdCParam = Math.max(\n  ...Object.entries(constants).map(([key, value]) =>\n    key.startsWith('ZSTD_c_') ? value : 0\n  )\n);\nexport const zstdInitCParamsArray = new Int32Array(kMaxZstdCParam + 1);\n\nexport const kMaxZstdDParam = Math.max(\n  ...Object.entries(constants).map(([key, value]) =>\n    key.startsWith('ZSTD_d_') ? value : 0\n  )\n);\nexport const zstdInitDParamsArray = new Int32Array(kMaxZstdDParam + 1);\n\nconst zstdDefaultOptions: ZlibDefaultOptions = {\n  flush: CONST_ZSTD_e_continue,\n  finishFlush: CONST_ZSTD_e_end,\n  fullFlush: CONST_ZSTD_e_flush,\n};\n\nexport class Zstd extends ZlibBase {\n  constructor(\n    options: ZstdOptions | undefined | null,\n    mode: number,\n    initParamsArray: Int32Array,\n    maxParam: number\n  ) {\n    ok(mode === CONST_ZSTD_DECODE || mode === CONST_ZSTD_ENCODE);\n    initParamsArray.fill(-1);\n\n    if (options?.params) {\n      for (const [origKey, value] of Object.entries(options.params)) {\n        const key = +origKey;\n        if (\n          Number.isNaN(key) ||\n          key < 0 ||\n          key > maxParam ||\n          ((initParamsArray[key] as number) | 0) !== -1\n        ) {\n          throw new ERR_ZSTD_INVALID_PARAM(origKey);\n        }\n\n        if (typeof value !== 'number' && typeof value !== 'boolean') {\n          throw new ERR_INVALID_ARG_TYPE(\n            'options.params[key]',\n            'number',\n            value\n          );\n        }\n        // as number is required to avoid force type coercion on runtime.\n        // boolean has number representation, but typescript doesn't understand it.\n        initParamsArray[key] = value as number;\n      }\n    }\n\n    const handle =\n      mode === CONST_ZSTD_DECODE\n        ? new zlibUtil.ZstdDecoder(mode)\n        : new zlibUtil.ZstdEncoder(mode);\n\n    const _writeState = new Uint32Array(2);\n\n    const pledgedSrcSize = options?.pledgedSrcSize;\n\n    if (\n      !handle.initialize(\n        initParamsArray,\n        _writeState,\n        () => {\n          queueMicrotask(processCallback.bind(handle));\n        },\n        pledgedSrcSize\n      )\n    ) {\n      throw new ERR_ZLIB_INITIALIZATION_FAILED();\n    }\n\n    super(options ?? {}, mode, handle, zstdDefaultOptions);\n    this._writeState = _writeState;\n  }\n}\n"
  },
  {
    "path": "src/node/internal/internal_zlib_constants.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { default as zlibUtil } from 'node-internal:zlib';\n\nconst {\n  CONST_Z_OK,\n  CONST_Z_STREAM_END,\n  CONST_Z_NEED_DICT,\n  CONST_Z_ERRNO,\n  CONST_Z_STREAM_ERROR,\n  CONST_Z_DATA_ERROR,\n  CONST_Z_MEM_ERROR,\n  CONST_Z_BUF_ERROR,\n  CONST_Z_VERSION_ERROR,\n} = zlibUtil;\n\nconst constPrefix = 'CONST_';\nexport const constants: Record<string, number> = {};\n\nObject.defineProperties(\n  constants,\n  Object.fromEntries(\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n    Object.entries(Object.getPrototypeOf(zlibUtil))\n      .filter(([k]) => k.startsWith(constPrefix))\n      .map(([k, v]) => [\n        k.slice(constPrefix.length),\n        {\n          value: v,\n          writable: false,\n          configurable: false,\n          enumerable: true,\n        },\n      ])\n  )\n);\n\n// Translation table for return codes.\nconst rawCodes: Record<string, number | string> = {\n  Z_OK: CONST_Z_OK,\n  Z_STREAM_END: CONST_Z_STREAM_END,\n  Z_NEED_DICT: CONST_Z_NEED_DICT,\n  Z_ERRNO: CONST_Z_ERRNO,\n  Z_STREAM_ERROR: CONST_Z_STREAM_ERROR,\n  Z_DATA_ERROR: CONST_Z_DATA_ERROR,\n  Z_MEM_ERROR: CONST_Z_MEM_ERROR,\n  Z_BUF_ERROR: CONST_Z_BUF_ERROR,\n  Z_VERSION_ERROR: CONST_Z_VERSION_ERROR,\n};\n\nfor (const key of Object.keys(rawCodes)) {\n  rawCodes[rawCodes[key] as number] = key;\n}\n\nexport const codes = Object.freeze(rawCodes);\n"
  },
  {
    "path": "src/node/internal/legacy_process.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n\n// Legacy process wrapper only exports limited process exports.\n// This is used when the enable_nodejs_process_v2 compat flag is disabled.\n// node:process re-mapping for this flag is done via module resolution.\nimport { default as processImpl } from 'node-internal:process';\n\nimport {\n  platform,\n  nextTick,\n  env,\n  features,\n  _setEventsProcess,\n} from 'node-internal:internal_process';\n\nexport { platform, nextTick, env, features };\n\nexport function getBuiltinModule(id: string): object {\n  return processImpl.getBuiltinModule(id);\n}\n\nexport function exit(code: number): void {\n  processImpl.exitImpl(code);\n}\n\nconst process = {\n  nextTick,\n  env,\n  exit,\n  getBuiltinModule,\n  platform,\n  features,\n};\n\n_setEventsProcess(process);\n\nexport default process;\n"
  },
  {
    "path": "src/node/internal/legacy_url.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { validateString, validateObject } from 'node-internal:validators';\nimport {\n  CHAR_SPACE,\n  CHAR_TAB,\n  CHAR_CARRIAGE_RETURN,\n  CHAR_LINE_FEED,\n  CHAR_NO_BREAK_SPACE,\n  CHAR_ZERO_WIDTH_NOBREAK_SPACE,\n  CHAR_HASH,\n  CHAR_FORWARD_SLASH,\n  CHAR_LEFT_SQUARE_BRACKET,\n  CHAR_RIGHT_SQUARE_BRACKET,\n  CHAR_LEFT_ANGLE_BRACKET,\n  CHAR_RIGHT_ANGLE_BRACKET,\n  CHAR_LEFT_CURLY_BRACKET,\n  CHAR_RIGHT_CURLY_BRACKET,\n  CHAR_QUESTION_MARK,\n  CHAR_DOUBLE_QUOTE,\n  CHAR_SINGLE_QUOTE,\n  CHAR_PERCENT,\n  CHAR_SEMICOLON,\n  CHAR_BACKWARD_SLASH,\n  CHAR_CIRCUMFLEX_ACCENT,\n  CHAR_GRAVE_ACCENT,\n  CHAR_VERTICAL_LINE,\n  CHAR_AT,\n  CHAR_COLON,\n} from 'node-internal:constants';\nimport {\n  parse as querystringParse,\n  stringify as querystringStringify,\n  encodeStr,\n  hexTable,\n} from 'node-internal:internal_querystring';\nimport {\n  slashedProtocol,\n  hostlessProtocol,\n  unsafeProtocol,\n} from 'node-internal:internal_url';\nimport {\n  ERR_INVALID_URL,\n  ERR_INVALID_ARG_TYPE,\n} from 'node-internal:internal_errors';\nimport { default as urlUtil } from 'node-internal:url';\nimport { spliceOne } from 'node-internal:internal_utils';\nimport type { URLFormatOptions } from 'node:url';\n\n// Reference: RFC 3986, RFC 1808, RFC 2396\n\n// define these here so at least they only have to be\n// compiled once on the first module load.\nconst protocolPattern = /^[a-z0-9.+-]+:/i;\nconst portPattern = /:[0-9]*$/;\nconst hostPattern = /^\\/\\/[^@/]+@[^@/]+/;\n\n// Special case for a simple path URL\nconst simplePathPattern = /^(\\/\\/?(?!\\/)[^?\\s]*)(\\?[^\\s]*)?$/;\n\nconst hostnameMaxLen = 255;\n\n// This prevents some common spoofing bugs due to our use of IDNA toASCII. For\n// compatibility, the set of characters we use here is the *intersection* of\n// \"forbidden host code point\" in the WHATWG URL Standard [1] and the\n// characters in the host parsing loop in Url.prototype.parse, with the\n// following additions:\n//\n// - ':' since this could cause a \"protocol spoofing\" bug\n// - '@' since this could cause parts of the hostname to be confused with auth\n// - '[' and ']' since this could cause a non-IPv6 hostname to be interpreted\n//   as IPv6 by isIpv6Hostname above\n//\n// [1]: https://url.spec.whatwg.org/#forbidden-host-code-point\nconst forbiddenHostChars = /[\\0\\t\\n\\r #%/:<>?@[\\\\\\]^|]/;\n// For IPv6, permit '[', ']', and ':'.\nconst forbiddenHostCharsIpv6 = /[\\0\\t\\n\\r #%/<>?@\\\\^|]/;\n\nfunction getHostname(self: typeof Url, rest: string, hostname: string): string {\n  for (let i = 0; i < hostname.length; ++i) {\n    const code = hostname.charCodeAt(i);\n    const isValid =\n      code !== CHAR_FORWARD_SLASH &&\n      code !== CHAR_BACKWARD_SLASH &&\n      code !== CHAR_HASH &&\n      code !== CHAR_QUESTION_MARK &&\n      code !== CHAR_COLON;\n\n    if (!isValid) {\n      self.hostname = hostname.slice(0, i);\n      return `/${hostname.slice(i)}${rest}`;\n    }\n  }\n  return rest;\n}\n\nfunction isIpv6Hostname(hostname: string): boolean {\n  return (\n    hostname.charCodeAt(0) === CHAR_LEFT_SQUARE_BRACKET &&\n    hostname.charCodeAt(hostname.length - 1) === CHAR_RIGHT_SQUARE_BRACKET\n  );\n}\n\n// Escaped characters. Use empty strings to fill up unused entries.\n// Using Array is faster than Object/Map\n// prettier-ignore\nconst escapedCodes = [\n  /* 0 - 9 */ '', '', '', '', '', '', '', '', '', '%09',\n  /* 10 - 19 */ '%0A', '', '', '%0D', '', '', '', '', '', '',\n  /* 20 - 29 */ '', '', '', '', '', '', '', '', '', '',\n  /* 30 - 39 */ '', '', '%20', '', '%22', '', '', '', '', '%27',\n  /* 40 - 49 */ '', '', '', '', '', '', '', '', '', '',\n  /* 50 - 59 */ '', '', '', '', '', '', '', '', '', '',\n  /* 60 - 69 */ '%3C', '', '%3E', '', '', '', '', '', '', '',\n  /* 70 - 79 */ '', '', '', '', '', '', '', '', '', '',\n  /* 80 - 89 */ '', '', '', '', '', '', '', '', '', '',\n  /* 90 - 99 */ '', '', '%5C', '', '%5E', '', '%60', '', '', '',\n  /* 100 - 109 */ '', '', '', '', '', '', '', '', '', '',\n  /* 110 - 119 */ '', '', '', '', '', '', '', '', '', '',\n  /* 120 - 125 */ '', '', '', '%7B', '%7C', '%7D',\n];\n\n// Automatically escape all delimiters and unwise characters from RFC 2396.\n// Also escape single quotes in case of an XSS attack.\n// Return the escaped string.\nfunction autoEscapeStr(rest: string): string {\n  let escaped = '';\n  let lastEscapedPos = 0;\n  for (let i = 0; i < rest.length; ++i) {\n    // `escaped` contains substring up to the last escaped character.\n    const escapedChar = escapedCodes[rest.charCodeAt(i)];\n    if (escapedChar) {\n      // Concat if there are ordinary characters in the middle.\n      if (i > lastEscapedPos) escaped += rest.slice(lastEscapedPos, i);\n      escaped += escapedChar;\n      lastEscapedPos = i + 1;\n    }\n  }\n  if (lastEscapedPos === 0)\n    // Nothing has been escaped.\n    return rest;\n\n  // There are ordinary characters at the end.\n  if (lastEscapedPos < rest.length) escaped += rest.slice(lastEscapedPos);\n\n  return escaped;\n}\n\nexport const Url = function Url(this: Record<string, null>) {\n  this.protocol = null;\n  this.slashes = null;\n  this.auth = null;\n  this.host = null;\n  this.port = null;\n  this.hostname = null;\n  this.hash = null;\n  this.search = null;\n  this.query = null;\n  this.pathname = null;\n  this.path = null;\n  this.href = null;\n} as unknown as {\n  protocol?: string | null | undefined;\n  slashes?: boolean | null | undefined;\n  auth?: string | null | undefined;\n  host?: string | null | undefined;\n  port?: string | null | undefined;\n  hostname?: string | null | undefined;\n  hash?: string | null | undefined;\n  search?: string | null | undefined;\n  query?: Record<string, unknown> | string | null | undefined;\n  pathname?: string | null | undefined;\n  path?: string | null | undefined;\n  href?: string | null | undefined;\n\n  new (): typeof Url;\n  prototype: typeof Url;\n\n  parse(\n    url: string | typeof Url,\n    parseQueryString?: boolean,\n    slashesDenoteHost?: boolean\n  ): typeof Url;\n  parseHost(): void;\n  format(): string;\n  resolve(from: string): string;\n  resolveObject(input: string | typeof Url): typeof Url;\n  parseHost(): void;\n};\n\nUrl.prototype.parse = function parse(\n  this: typeof Url,\n  url: string | typeof Url,\n  parseQueryString?: boolean,\n  slashesDenoteHost?: boolean\n): typeof Url {\n  validateString(url, 'url');\n\n  // Copy chrome, IE, opera backslash-handling behavior.\n  // Backslashes before the query string get converted to forward slashes\n  // See: https://code.google.com/p/chromium/issues/detail?id=25916\n  let hasHash = false;\n  let hasAt = false;\n  let start = -1;\n  let end = -1;\n  let rest = '';\n  let lastPos = 0;\n  for (let i = 0, inWs = false, split = false; i < url.length; ++i) {\n    const code = url.charCodeAt(i);\n\n    // Find first and last non-whitespace characters for trimming\n    const isWs =\n      code < 33 ||\n      code === CHAR_NO_BREAK_SPACE ||\n      code === CHAR_ZERO_WIDTH_NOBREAK_SPACE;\n    if (start === -1) {\n      if (isWs) continue;\n      lastPos = start = i;\n    } else if (inWs) {\n      if (!isWs) {\n        end = -1;\n        inWs = false;\n      }\n    } else if (isWs) {\n      end = i;\n      inWs = true;\n    }\n\n    // Only convert backslashes while we haven't seen a split character\n    if (!split) {\n      switch (code) {\n        case CHAR_AT:\n          hasAt = true;\n          break;\n        case CHAR_HASH:\n          hasHash = true;\n          split = true;\n          break;\n        case CHAR_QUESTION_MARK:\n          split = true;\n          break;\n        case CHAR_BACKWARD_SLASH:\n          if (i - lastPos > 0) rest += url.slice(lastPos, i);\n          rest += '/';\n          lastPos = i + 1;\n          break;\n      }\n    } else if (!hasHash && code === CHAR_HASH) {\n      hasHash = true;\n    }\n  }\n\n  // Check if string was non-empty (including strings with only whitespace)\n  if (start !== -1) {\n    if (lastPos === start) {\n      // We didn't convert any backslashes\n\n      if (end === -1) {\n        if (start === 0) rest = url;\n        else rest = url.slice(start);\n      } else {\n        rest = url.slice(start, end);\n      }\n    } else if (end === -1 && lastPos < url.length) {\n      // We converted some backslashes and have only part of the entire string\n      rest += url.slice(lastPos);\n    } else if (end !== -1 && lastPos < end) {\n      // We converted some backslashes and have only part of the entire string\n      rest += url.slice(lastPos, end);\n    }\n  }\n\n  if (!slashesDenoteHost && !hasHash && !hasAt) {\n    // Try fast path regexp\n    const simplePath = simplePathPattern.exec(rest);\n    if (simplePath) {\n      this.path = rest;\n      this.href = rest;\n      this.pathname = simplePath[1];\n      if (simplePath[2]) {\n        this.search = simplePath[2];\n        if (parseQueryString) {\n          this.query = querystringParse(this.search.slice(1));\n        } else {\n          this.query = this.search.slice(1);\n        }\n      } else if (parseQueryString) {\n        this.search = null;\n        this.query = { __proto__: null };\n      }\n      return this;\n    }\n  }\n\n  let proto: string | RegExpExecArray | null = protocolPattern.exec(rest);\n  let lowerProto;\n  if (proto) {\n    proto = proto[0];\n    lowerProto = proto.toLowerCase();\n    this.protocol = lowerProto;\n    rest = rest.slice(proto.length);\n  }\n\n  // Figure out if it's got a host\n  // user@server is *always* interpreted as a hostname, and url\n  // resolution will treat //foo/bar as host=foo,path=bar because that's\n  // how the browser resolves relative URLs.\n  let slashes;\n  if (slashesDenoteHost || proto || hostPattern.test(rest)) {\n    slashes =\n      rest.charCodeAt(0) === CHAR_FORWARD_SLASH &&\n      rest.charCodeAt(1) === CHAR_FORWARD_SLASH;\n    if (slashes && !(proto && hostlessProtocol.has(lowerProto as string))) {\n      rest = rest.slice(2);\n      this.slashes = true;\n    }\n  }\n\n  if (\n    !hostlessProtocol.has(lowerProto as string) &&\n    (slashes || (proto && !slashedProtocol.has(proto)))\n  ) {\n    // there's a hostname.\n    // the first instance of /, ?, ;, or # ends the host.\n    //\n    // If there is an @ in the hostname, then non-host chars *are* allowed\n    // to the left of the last @ sign, unless some host-ending character\n    // comes *before* the @-sign.\n    // URLs are obnoxious.\n    //\n    // ex:\n    // http://a@b@c/ => user:a@b host:c\n    // http://a@b?@c => user:a host:b path:/?@c\n\n    let hostEnd = -1;\n    let atSign = -1;\n    let nonHost = -1;\n    for (let i = 0; i < rest.length; ++i) {\n      switch (rest.charCodeAt(i)) {\n        case CHAR_TAB:\n        case CHAR_LINE_FEED:\n        case CHAR_CARRIAGE_RETURN:\n          // WHATWG URL removes tabs, newlines, and carriage returns. Let's do that too.\n          rest = rest.slice(0, i) + rest.slice(i + 1);\n          i -= 1;\n          break;\n        case CHAR_SPACE:\n        case CHAR_DOUBLE_QUOTE:\n        case CHAR_PERCENT:\n        case CHAR_SINGLE_QUOTE:\n        case CHAR_SEMICOLON:\n        case CHAR_LEFT_ANGLE_BRACKET:\n        case CHAR_RIGHT_ANGLE_BRACKET:\n        case CHAR_BACKWARD_SLASH:\n        case CHAR_CIRCUMFLEX_ACCENT:\n        case CHAR_GRAVE_ACCENT:\n        case CHAR_LEFT_CURLY_BRACKET:\n        case CHAR_VERTICAL_LINE:\n        case CHAR_RIGHT_CURLY_BRACKET:\n          // Characters that are never ever allowed in a hostname from RFC 2396\n          if (nonHost === -1) nonHost = i;\n          break;\n        case CHAR_HASH:\n        case CHAR_FORWARD_SLASH:\n        case CHAR_QUESTION_MARK:\n          // Find the first instance of any host-ending characters\n          if (nonHost === -1) nonHost = i;\n          hostEnd = i;\n          break;\n        case CHAR_AT:\n          // At this point, either we have an explicit point where the\n          // auth portion cannot go past, or the last @ char is the decider.\n          atSign = i;\n          nonHost = -1;\n          break;\n      }\n      if (hostEnd !== -1) break;\n    }\n    start = 0;\n    if (atSign !== -1) {\n      this.auth = decodeURIComponent(rest.slice(0, atSign));\n      start = atSign + 1;\n    }\n    if (nonHost === -1) {\n      this.host = rest.slice(start);\n      rest = '';\n    } else {\n      this.host = rest.slice(start, nonHost);\n      rest = rest.slice(nonHost);\n    }\n\n    // pull out port.\n    this.parseHost();\n\n    // We've indicated that there is a hostname,\n    // so even if it's empty, it has to be present.\n    if (typeof this.hostname !== 'string') this.hostname = '';\n\n    const hostname = this.hostname;\n\n    // If hostname begins with [ and ends with ]\n    // assume that it's an IPv6 address.\n    const ipv6Hostname = isIpv6Hostname(hostname);\n\n    // validate a little.\n    if (!ipv6Hostname) {\n      rest = getHostname(this, rest, hostname);\n    }\n\n    if (this.hostname.length > hostnameMaxLen) {\n      this.hostname = '';\n    } else {\n      // Hostnames are always lower case.\n      this.hostname = this.hostname.toLowerCase();\n    }\n\n    if (this.hostname !== '') {\n      if (ipv6Hostname) {\n        if (forbiddenHostCharsIpv6.test(this.hostname)) {\n          throw new ERR_INVALID_URL(url);\n        }\n      } else {\n        // IDNA Support: Returns a punycoded representation of \"domain\".\n        // It only converts parts of the domain name that\n        // have non-ASCII characters, i.e. it doesn't matter if\n        // you call it with a domain that already is ASCII-only.\n        this.hostname = urlUtil.toASCII(this.hostname);\n\n        // Prevent two potential routes of hostname spoofing.\n        // 1. If this.hostname is empty, it must have become empty due to toASCII\n        //    since we checked this.hostname above.\n        // 2. If any of forbiddenHostChars appears in this.hostname, it must have\n        //    also gotten in due to toASCII. This is since getHostname would have\n        //    filtered them out otherwise.\n        // Rather than trying to correct this by moving the non-host part into\n        // the pathname as we've done in getHostname, throw an exception to\n        // convey the severity of this issue.\n        if (this.hostname === '' || forbiddenHostChars.test(this.hostname)) {\n          throw new ERR_INVALID_URL(url);\n        }\n      }\n    }\n\n    const p = this.port ? ':' + this.port : '';\n    const h = this.hostname || '';\n    this.host = h + p;\n\n    // strip [ and ] from the hostname\n    // the host field still retains them, though\n    if (ipv6Hostname) {\n      this.hostname = this.hostname.slice(1, -1);\n      if (rest[0] !== '/') {\n        rest = '/' + rest;\n      }\n    }\n  }\n\n  // Now rest is set to the post-host stuff.\n  // Chop off any delim chars.\n  if (!unsafeProtocol.has(lowerProto as string)) {\n    // First, make 100% sure that any \"autoEscape\" chars get\n    // escaped, even if encodeURIComponent doesn't think they\n    // need to be.\n    rest = autoEscapeStr(rest);\n  }\n\n  let questionIdx = -1;\n  let hashIdx = -1;\n  for (let i = 0; i < rest.length; ++i) {\n    const code = rest.charCodeAt(i);\n    if (code === CHAR_HASH) {\n      this.hash = rest.slice(i);\n      hashIdx = i;\n      break;\n    } else if (code === CHAR_QUESTION_MARK && questionIdx === -1) {\n      questionIdx = i;\n    }\n  }\n\n  if (questionIdx !== -1) {\n    if (hashIdx === -1) {\n      this.search = rest.slice(questionIdx);\n      this.query = rest.slice(questionIdx + 1);\n    } else {\n      this.search = rest.slice(questionIdx, hashIdx);\n      this.query = rest.slice(questionIdx + 1, hashIdx);\n    }\n    if (parseQueryString) {\n      this.query = querystringParse(this.query);\n    }\n  } else if (parseQueryString) {\n    // No query string, but parseQueryString still requested\n    this.search = null;\n    this.query = { __proto__: null };\n  }\n\n  const useQuestionIdx =\n    questionIdx !== -1 && (hashIdx === -1 || questionIdx < hashIdx);\n  const firstIdx = useQuestionIdx ? questionIdx : hashIdx;\n  if (firstIdx === -1) {\n    if (rest.length > 0) this.pathname = rest;\n  } else if (firstIdx > 0) {\n    this.pathname = rest.slice(0, firstIdx);\n  }\n  if (\n    slashedProtocol.has(lowerProto as string) &&\n    this.hostname &&\n    !this.pathname\n  ) {\n    this.pathname = '/';\n  }\n\n  // To support http.request\n  if (this.pathname || this.search) {\n    const p = this.pathname || '';\n    const s = this.search || '';\n    this.path = p + s;\n  }\n\n  // Finally, reconstruct the href based on what has been validated.\n  this.href = this.format();\n  return this;\n};\n\n// These characters do not need escaping:\n// ! - . _ ~\n// ' ( ) * :\n// digits\n// alpha (uppercase)\n// alpha (lowercase)\n// prettier-ignore\nconst noEscapeAuth = new Int8Array([\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00 - 0x0F\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10 - 0x1F\n  0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, // 0x20 - 0x2F\n  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 0x30 - 0x3F\n  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4F\n  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 0x50 - 0x5F\n  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6F\n  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, // 0x70 - 0x7F\n]);\n\nUrl.prototype.format = function format(this: typeof Url): string {\n  let auth = this.auth || '';\n  if (auth) {\n    auth = encodeStr(auth, noEscapeAuth, hexTable);\n    auth += '@';\n  }\n\n  let protocol = this.protocol || '';\n  let pathname = this.pathname || '';\n  let hash = this.hash || '';\n  let host = '';\n  let query = '';\n\n  if (this.host) {\n    host = auth + this.host;\n  } else if (this.hostname) {\n    host =\n      auth +\n      (this.hostname.includes(':') && !isIpv6Hostname(this.hostname)\n        ? '[' + this.hostname + ']'\n        : this.hostname);\n    if (this.port) {\n      host += ':' + this.port;\n    }\n  }\n\n  if (this.query !== null && typeof this.query === 'object') {\n    query = querystringStringify(this.query);\n  }\n\n  let search = this.search || (query && '?' + query) || '';\n\n  if (protocol && protocol.charCodeAt(protocol.length - 1) !== 58 /* : */)\n    protocol += ':';\n\n  let newPathname = '';\n  let lastPos = 0;\n  for (let i = 0; i < pathname.length; ++i) {\n    switch (pathname.charCodeAt(i)) {\n      case CHAR_HASH:\n        if (i - lastPos > 0) newPathname += pathname.slice(lastPos, i);\n        newPathname += '%23';\n        lastPos = i + 1;\n        break;\n      case CHAR_QUESTION_MARK:\n        if (i - lastPos > 0) newPathname += pathname.slice(lastPos, i);\n        newPathname += '%3F';\n        lastPos = i + 1;\n        break;\n    }\n  }\n  if (lastPos > 0) {\n    if (lastPos !== pathname.length)\n      pathname = newPathname + pathname.slice(lastPos);\n    else pathname = newPathname;\n  }\n\n  // Only the slashedProtocols get the //.  Not mailto:, xmpp:, etc.\n  // unless they had them to begin with.\n  if (this.slashes || slashedProtocol.has(protocol)) {\n    if (this.slashes || host) {\n      if (pathname && pathname.charCodeAt(0) !== CHAR_FORWARD_SLASH)\n        pathname = '/' + pathname;\n      host = '//' + host;\n    } else if (\n      protocol.length >= 4 &&\n      protocol.charCodeAt(0) === 102 /* f */ &&\n      protocol.charCodeAt(1) === 105 /* i */ &&\n      protocol.charCodeAt(2) === 108 /* l */ &&\n      protocol.charCodeAt(3) === 101 /* e */\n    ) {\n      host = '//';\n    }\n  }\n\n  search = search.replace(/#/g, '%23');\n\n  if (hash && hash.charCodeAt(0) !== CHAR_HASH) hash = '#' + hash;\n  if (search && search.charCodeAt(0) !== CHAR_QUESTION_MARK)\n    search = '?' + search;\n\n  return protocol + host + pathname + search + hash;\n};\n\nexport function resolve(source: typeof Url | string, relative: string): string {\n  return parse(source, false, true).resolve(relative);\n}\n\nUrl.prototype.resolveObject = function resolveObject(\n  relative: string | typeof Url\n): typeof Url {\n  if (typeof relative === 'string') {\n    const rel = new Url();\n    rel.parse(relative, false, true);\n    relative = rel;\n  }\n\n  const result = new Url();\n  Object.assign(result, this);\n\n  // Hash is always overridden, no matter what.\n  // even href=\"\" will remove it.\n  result.hash = relative.hash;\n\n  // If the relative url is empty, then there's nothing left to do here.\n  if (relative.href === '') {\n    result.href = result.format();\n    return result;\n  }\n\n  // Hrefs like //foo/bar always cut to the protocol.\n  if (relative.slashes && !relative.protocol) {\n    // Take everything except the protocol from relative\n    const relativeWithoutProtocol = Object.keys(relative).reduce((acc, key) => {\n      if (key !== 'protocol') {\n        // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n        // @ts-expect-error\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n        acc[key] = relative[key];\n      }\n      return acc;\n    }, {});\n    Object.assign(result, relativeWithoutProtocol);\n\n    // urlParse appends trailing / to urls like http://www.example.com\n    if (\n      slashedProtocol.has(result.protocol as string) &&\n      result.hostname &&\n      !result.pathname\n    ) {\n      result.path = result.pathname = '/';\n    }\n\n    result.href = result.format();\n    return result;\n  }\n\n  if (relative.protocol && relative.protocol !== result.protocol) {\n    // If it's a known url protocol, then changing\n    // the protocol does weird things\n    // first, if it's not file:, then we MUST have a host,\n    // and if there was a path\n    // to begin with, then we MUST have a path.\n    // if it is file:, then the host is dropped,\n    // because that's known to be hostless.\n    // anything else is assumed to be absolute.\n    if (!slashedProtocol.has(relative.protocol)) {\n      Object.assign(result, relative);\n      result.href = result.format();\n      return result;\n    }\n\n    result.protocol = relative.protocol;\n    if (\n      !relative.host &&\n      !/^file:?$/.test(relative.protocol) &&\n      !hostlessProtocol.has(relative.protocol)\n    ) {\n      const relPath = (relative.pathname || '').split('/');\n      while (relPath.length && !(relative.host = relPath.shift())) {\n        // keep this empty.\n      }\n      relative.host ||= '';\n      relative.hostname ||= '';\n      if (relPath[0] !== '') relPath.unshift('');\n      if (relPath.length < 2) relPath.unshift('');\n      result.pathname = relPath.join('/');\n    } else {\n      result.pathname = relative.pathname;\n    }\n    result.search = relative.search;\n    result.query = relative.query;\n    result.host = relative.host || '';\n    result.auth = relative.auth;\n    result.hostname = relative.hostname || relative.host;\n    result.port = relative.port;\n    // To support http.request\n    if (result.pathname || result.search) {\n      const p = result.pathname || '';\n      const s = result.search || '';\n      result.path = p + s;\n    }\n    result.slashes ||= relative.slashes;\n    result.href = result.format();\n    return result;\n  }\n\n  const isSourceAbs = result.pathname && result.pathname.charAt(0) === '/';\n  const isRelAbs =\n    relative.host || (relative.pathname && relative.pathname.charAt(0) === '/');\n  let mustEndAbs =\n    !!isRelAbs ||\n    !!isSourceAbs ||\n    (!!result.host && !!relative.pathname) ||\n    false;\n  const removeAllDots = mustEndAbs;\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  let srcPath = (result.pathname && result.pathname.split('/')) || [];\n\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  const relPath = (relative.pathname && relative.pathname.split('/')) || [];\n  const noLeadingSlashes =\n    result.protocol && !slashedProtocol.has(result.protocol);\n\n  // If the url is a non-slashed url, then relative\n  // links like ../.. should be able\n  // to crawl up to the hostname, as well.  This is strange.\n  // result.protocol has already been set by now.\n  // Later on, put the first path part into the host field.\n  if (noLeadingSlashes) {\n    result.hostname = '';\n    result.port = null;\n    if (result.host) {\n      if (srcPath[0] === '') srcPath[0] = result.host;\n      else srcPath.unshift(result.host);\n    }\n    result.host = '';\n    if (relative.protocol) {\n      relative.hostname = null;\n      relative.port = null;\n      result.auth = null;\n      if (relative.host) {\n        if (relPath[0] === '') relPath[0] = relative.host;\n        else relPath.unshift(relative.host);\n      }\n      relative.host = null;\n    }\n    mustEndAbs &&= relPath[0] === '' || srcPath[0] === '';\n  }\n\n  if (isRelAbs) {\n    // it's absolute.\n    if (relative.host || relative.host === '') {\n      if (result.host !== relative.host) result.auth = null;\n      result.host = relative.host;\n      result.port = relative.port;\n    }\n    if (relative.hostname || relative.hostname === '') {\n      if (result.hostname !== relative.hostname) result.auth = null;\n      result.hostname = relative.hostname;\n    }\n    result.search = relative.search;\n    result.query = relative.query;\n    srcPath = relPath;\n    // Fall through to the dot-handling below.\n  } else if (relPath.length) {\n    // it's relative\n    // throw away the existing file, and take the new path instead.\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    srcPath ||= [];\n    srcPath.pop();\n    srcPath = srcPath.concat(relPath);\n    result.search = relative.search;\n    result.query = relative.query;\n  } else if (relative.search !== null && relative.search !== undefined) {\n    // Just pull out the search.\n    // like href='?foo'.\n    // Put this after the other two cases because it simplifies the booleans\n    if (noLeadingSlashes) {\n      result.hostname = result.host = srcPath.shift();\n      // Occasionally the auth can get stuck only in host.\n      // This especially happens in cases like\n      // url.resolveObject('mailto:local1@domain1', 'local2@domain2')\n      const authInHost =\n        result.host && result.host.indexOf('@') > 0 && result.host.split('@');\n      if (authInHost) {\n        result.auth = authInHost.shift();\n        result.host = result.hostname = authInHost.shift();\n      }\n    }\n    result.search = relative.search;\n    result.query = relative.query;\n    // To support http.request\n\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    if (result.pathname !== null || result.search !== null) {\n      result.path =\n        (result.pathname ? result.pathname : '') +\n        (result.search ? result.search : '');\n    }\n    result.href = result.format();\n    return result;\n  }\n\n  if (!srcPath.length) {\n    // No path at all. All other things were already handled above.\n    result.pathname = null;\n    // To support http.request\n    if (result.search) {\n      result.path = '/' + result.search;\n    } else {\n      result.path = null;\n    }\n    result.href = result.format();\n    return result;\n  }\n\n  // If a url ENDs in . or .., then it must get a trailing slash.\n  // however, if it ends in anything else non-slashy,\n  // then it must NOT get a trailing slash.\n  let last = srcPath.slice(-1)[0];\n  const hasTrailingSlash =\n    ((result.host || relative.host || srcPath.length > 1) &&\n      (last === '.' || last === '..')) ||\n    last === '';\n\n  // Strip single dots, resolve double dots to parent dir\n  // if the path tries to go above the root, `up` ends up > 0\n  let up = 0;\n  for (let i = srcPath.length - 1; i >= 0; i--) {\n    last = srcPath[i];\n    if (last === '.') {\n      spliceOne(srcPath, i);\n    } else if (last === '..') {\n      spliceOne(srcPath, i);\n      up++;\n    } else if (up) {\n      spliceOne(srcPath, i);\n      up--;\n    }\n  }\n\n  // If the path is allowed to go above the root, restore leading ..s\n  if (!mustEndAbs && !removeAllDots) {\n    while (up--) {\n      srcPath.unshift('..');\n    }\n  }\n\n  if (\n    mustEndAbs &&\n    srcPath[0] !== '' &&\n    (!srcPath[0] || srcPath[0].charAt(0) !== '/')\n  ) {\n    srcPath.unshift('');\n  }\n\n  if (hasTrailingSlash && srcPath.join('/').slice(-1) !== '/') {\n    srcPath.push('');\n  }\n\n  const isAbsolute =\n    srcPath[0] === '' || (srcPath[0] && srcPath[0].charAt(0) === '/');\n\n  // put the host back\n  if (noLeadingSlashes) {\n    result.hostname = result.host = isAbsolute\n      ? ''\n      : srcPath.length\n        ? srcPath.shift()\n        : '';\n    // Occasionally the auth can get stuck only in host.\n    // This especially happens in cases like\n    // url.resolveObject('mailto:local1@domain1', 'local2@domain2')\n    const authInHost =\n      result.host && result.host.indexOf('@') > 0\n        ? result.host.split('@')\n        : false;\n    if (authInHost) {\n      result.auth = authInHost.shift();\n      result.host = result.hostname = authInHost.shift();\n    }\n  }\n\n  mustEndAbs ||= Boolean(result.host && srcPath.length);\n\n  if (mustEndAbs && !isAbsolute) {\n    srcPath.unshift('');\n  }\n\n  if (!srcPath.length) {\n    result.pathname = null;\n    result.path = null;\n  } else {\n    result.pathname = srcPath.join('/');\n  }\n\n  // To support request.http\n  if (result.pathname !== null || result.search !== null) {\n    result.path =\n      (result.pathname ? result.pathname : '') +\n      (result.search ? result.search : '');\n  }\n  result.auth = relative.auth || result.auth;\n  result.slashes ||= relative.slashes;\n  result.href = result.format();\n  return result;\n};\n\nUrl.prototype.resolve = function resolve(\n  this: typeof Url,\n  relative: string\n): string {\n  return this.resolveObject(parse(relative, false, true)).format();\n};\n\nUrl.prototype.parseHost = function parseHost(this: typeof Url): void {\n  let host = this.host as string;\n  let port: RegExpExecArray | string | null = portPattern.exec(host);\n  if (port) {\n    port = port[0];\n    if (port !== ':') {\n      this.port = port.slice(1);\n    }\n    host = host.slice(0, host.length - port.length);\n  }\n  if (host) this.hostname = host;\n};\n\nexport function parse(\n  url: typeof Url | string,\n  parseQueryString?: boolean,\n  slashesDenoteHost?: boolean\n): typeof Url {\n  if (url instanceof Url) return url;\n\n  const urlObject = new Url();\n  urlObject.parse(url, parseQueryString, slashesDenoteHost);\n  return urlObject;\n}\n\n// Format a parsed object into a url string\nexport function format(\n  urlObject: typeof Url | URL | string | null,\n  options?: URLFormatOptions\n): string {\n  // Ensure it's an object, and not a string url.\n  // If it's an object, this is a no-op.\n  // this way, you can call urlParse() on strings\n  // to clean up potentially wonky urls.\n  if (typeof urlObject === 'string') {\n    urlObject = parse(urlObject);\n  } else if (typeof urlObject !== 'object' || urlObject === null) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'urlObject',\n      ['Object', 'string'],\n      urlObject\n    );\n  } else if (urlObject instanceof URL) {\n    let fragment = true;\n    let unicode = false;\n    let search = true;\n    let auth = true;\n\n    if (options) {\n      validateObject(options, 'options');\n\n      if (options.fragment != null) {\n        fragment = Boolean(options.fragment); // eslint-disable-line @typescript-eslint/no-unnecessary-type-conversion\n      }\n\n      if (options.unicode != null) {\n        unicode = Boolean(options.unicode); // eslint-disable-line @typescript-eslint/no-unnecessary-type-conversion\n      }\n\n      if (options.search != null) {\n        search = Boolean(options.search); // eslint-disable-line @typescript-eslint/no-unnecessary-type-conversion\n      }\n\n      if (options.auth != null) {\n        auth = Boolean(options.auth); // eslint-disable-line @typescript-eslint/no-unnecessary-type-conversion\n      }\n    }\n\n    return urlUtil.format(urlObject.href, fragment, unicode, search, auth);\n  }\n\n  return Url.prototype.format.call(urlObject);\n}\n\nexport function resolveObject(\n  source: string | typeof Url,\n  relative: string | typeof Url\n): typeof Url | string {\n  if (!source) {\n    return relative;\n  }\n  return parse(source, false, true).resolveObject(relative);\n}\n"
  },
  {
    "path": "src/node/internal/messagechannel.d.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Type definitions for cloudflare-internal:messagechannel\n// This internal module exposes MessageChannel and MessagePort for use by\n// built-in modules like node:worker_threads, independent of the\n// expose_global_message_channel compatibility flag.\n\ndeclare namespace _default {\n  const MessageChannel: typeof globalThis.MessageChannel;\n  const MessagePort: typeof globalThis.MessagePort;\n}\nexport default _default;\n"
  },
  {
    "path": "src/node/internal/mock.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport declare class MockTracker {\n  constructor();\n}\n\nexport declare class MockFunctionContext {\n  constructor();\n}\n"
  },
  {
    "path": "src/node/internal/mock.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT ORs\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport {\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_ARG_VALUE,\n} from 'node-internal:internal_errors';\n\nimport {\n  validateBoolean,\n  validateFunction,\n  validateInteger,\n  validateObject,\n} from 'node-internal:validators';\n\nconst kEmptyObject = Object.create(null);\nfunction kDefaultFunction() {}\n\n// TODO(soon): MockTimers are currently still fairly experimental in Node.js.\n// The intention is to implement them but for now, I'm skipping it.\n//const { MockTimers } = require('internal/test_runner/mock/mock_timers');\n\nexport class MockFunctionContext {\n  #calls;\n  #mocks;\n  #implementation;\n  #restore;\n  #times;\n\n  constructor(implementation, restore, times) {\n    this.#calls = [];\n    this.#mocks = new Map();\n    this.#implementation = implementation;\n    this.#restore = restore;\n    this.#times = times;\n  }\n\n  /**\n   * Gets an array of recorded calls made to the mock function.\n   * @returns {Array} An array of recorded calls.\n   */\n  get calls() {\n    return this.#calls.slice(0);\n  }\n\n  /**\n   * Retrieves the number of times the mock function has been called.\n   * @returns {number} The call count.\n   */\n  callCount() {\n    return this.#calls.length;\n  }\n\n  /**\n   * Sets a new implementation for the mock function.\n   * @param {Function} implementation - The new implementation for the mock function.\n   */\n  mockImplementation(implementation) {\n    validateFunction(implementation, 'implementation');\n    this.#implementation = implementation;\n  }\n\n  /**\n   * Replaces the implementation of the function only once.\n   * @param {Function} implementation - The substitute function.\n   * @param {number} [onCall] - The call index to be replaced.\n   */\n  mockImplementationOnce(implementation, onCall) {\n    validateFunction(implementation, 'implementation');\n    const nextCall = this.#calls.length;\n    const call = onCall ?? nextCall;\n    validateInteger(call, 'onCall', nextCall);\n    this.#mocks.set(call, implementation);\n  }\n\n  /**\n   * Restores the original function that was mocked.\n   */\n  restore() {\n    const { descriptor, object, original, methodName } = this.#restore;\n\n    if (typeof methodName === 'string') {\n      // This is an object method spy.\n      Object.defineProperty(object, methodName, descriptor);\n    } else {\n      // This is a bare function spy. There isn't much to do here but make\n      // the mock call the original function.\n      this.#implementation = original;\n    }\n  }\n\n  /**\n   * Resets the recorded calls to the mock function\n   */\n  resetCalls() {\n    this.#calls = [];\n  }\n\n  /**\n   * Tracks a call made to the mock function.\n   * @param {object} call - The call details.\n   */\n  trackCall(call) {\n    this.#calls.push(call);\n  }\n\n  /**\n   * Gets the next implementation to use for the mock function.\n   * @returns {Function} The next implementation.\n   */\n  nextImpl() {\n    const nextCall = this.#calls.length;\n    const mock = this.#mocks.get(nextCall);\n    const impl = mock ?? this.#implementation;\n\n    if (nextCall + 1 === this.#times) {\n      this.restore();\n    }\n\n    this.#mocks.delete(nextCall);\n    return impl;\n  }\n}\n\nconst { nextImpl, restore, trackCall } = MockFunctionContext.prototype;\ndelete MockFunctionContext.prototype.trackCall;\ndelete MockFunctionContext.prototype.nextImpl;\n\nexport class MockTracker {\n  #mocks = [];\n\n  // TODO(soon): MockTimers are currently still fairly experimental in Node.js.\n  // The intention is to implement them but for now, I'm skipping it.\n  // #timers;\n\n  // /**\n  //  * Returns the mock timers of this MockTracker instance.\n  //  * @returns {MockTimers} The mock timers instance.\n  //  */\n  // get timers() {\n  //   this.#timers ??= new MockTimers();\n  //   return this.#timers;\n  // }\n\n  /**\n   * Creates a mock function tracker.\n   * @param {Function} [original] - The original function to be tracked.\n   * @param {Function} [implementation] - An optional replacement function for the original one.\n   * @param {object} [options] - Additional tracking options.\n   * @param {number} [options.times=Infinity] - The maximum number of times the mock function can be called.\n   * @returns {ProxyConstructor} The mock function tracker.\n   */\n  fn(\n    original = function () {},\n    implementation = original,\n    options = kEmptyObject\n  ) {\n    if (original !== null && typeof original === 'object') {\n      options = original;\n      original = function () {};\n      implementation = original;\n    } else if (implementation !== null && typeof implementation === 'object') {\n      options = implementation;\n      implementation = original;\n    }\n\n    validateFunction(original, 'original');\n    validateFunction(implementation, 'implementation');\n    validateObject(options, 'options');\n    const { times = Infinity } = options;\n    validateTimes(times, 'options.times');\n    const ctx = new MockFunctionContext(\n      implementation,\n      { __proto__: null, original },\n      times\n    );\n    return this.#setupMock(ctx, original);\n  }\n\n  /**\n   * Creates a method tracker for a specified object or function.\n   * @param {(object | Function)} objectOrFunction - The object or function containing the method to be tracked.\n   * @param {string} methodName - The name of the method to be tracked.\n   * @param {Function} [implementation] - An optional replacement function for the original method.\n   * @param {object} [options] - Additional tracking options.\n   * @param {boolean} [options.getter=false] - Indicates whether this is a getter method.\n   * @param {boolean} [options.setter=false] - Indicates whether this is a setter method.\n   * @param {number} [options.times=Infinity] - The maximum number of times the mock method can be called.\n   * @returns {ProxyConstructor} The mock method tracker.\n   */\n  method(\n    objectOrFunction,\n    methodName,\n    implementation = kDefaultFunction,\n    options = kEmptyObject\n  ) {\n    validateStringOrSymbol(methodName, 'methodName');\n    if (typeof objectOrFunction !== 'function') {\n      validateObject(objectOrFunction, 'object');\n    }\n\n    if (implementation !== null && typeof implementation === 'object') {\n      options = implementation;\n      implementation = kDefaultFunction;\n    }\n\n    validateFunction(implementation, 'implementation');\n    validateObject(options, 'options');\n\n    const { getter = false, setter = false, times = Infinity } = options;\n\n    validateBoolean(getter, 'options.getter');\n    validateBoolean(setter, 'options.setter');\n    validateTimes(times, 'options.times');\n\n    if (setter && getter) {\n      throw new ERR_INVALID_ARG_VALUE(\n        'options.setter',\n        setter,\n        \"cannot be used with 'options.getter'\"\n      );\n    }\n    const descriptor = findMethodOnPrototypeChain(objectOrFunction, methodName);\n\n    let original;\n\n    if (getter) {\n      original = descriptor?.get;\n    } else if (setter) {\n      original = descriptor?.set;\n    } else {\n      original = descriptor?.value;\n    }\n\n    if (typeof original !== 'function') {\n      throw new ERR_INVALID_ARG_VALUE(\n        'methodName',\n        original,\n        'must be a method'\n      );\n    }\n\n    const restore = {\n      __proto__: null,\n      descriptor,\n      object: objectOrFunction,\n      methodName,\n    };\n    const impl =\n      implementation === kDefaultFunction ? original : implementation;\n    const ctx = new MockFunctionContext(impl, restore, times);\n    const mock = this.#setupMock(ctx, original);\n    const mockDescriptor = {\n      __proto__: null,\n      configurable: descriptor.configurable,\n      enumerable: descriptor.enumerable,\n    };\n\n    if (getter) {\n      mockDescriptor.get = mock;\n      mockDescriptor.set = descriptor.set;\n    } else if (setter) {\n      mockDescriptor.get = descriptor.get;\n      mockDescriptor.set = mock;\n    } else {\n      mockDescriptor.writable = descriptor.writable;\n      mockDescriptor.value = mock;\n    }\n\n    Object.defineProperty(objectOrFunction, methodName, mockDescriptor);\n\n    return mock;\n  }\n\n  /**\n   * Mocks a getter method of an object.\n   * This is a syntax sugar for the MockTracker.method with options.getter set to true\n   * @param {object} object - The target object.\n   * @param {string} methodName - The name of the getter method to be mocked.\n   * @param {Function} [implementation] - An optional replacement function for the targeted method.\n   * @param {object} [options] - Additional tracking options.\n   * @param {boolean} [options.getter=true] - Indicates whether this is a getter method.\n   * @param {boolean} [options.setter=false] - Indicates whether this is a setter method.\n   * @param {number} [options.times=Infinity] - The maximum number of times the mock method can be called.\n   * @returns {ProxyConstructor} The mock method tracker.\n   */\n  getter(\n    object,\n    methodName,\n    implementation = kDefaultFunction,\n    options = kEmptyObject\n  ) {\n    if (implementation !== null && typeof implementation === 'object') {\n      options = implementation;\n      implementation = kDefaultFunction;\n    } else {\n      validateObject(options, 'options');\n    }\n\n    const { getter = true } = options;\n\n    if (getter === false) {\n      throw new ERR_INVALID_ARG_VALUE(\n        'options.getter',\n        getter,\n        'cannot be false'\n      );\n    }\n\n    return this.method(object, methodName, implementation, {\n      __proto__: null,\n      ...options,\n      getter,\n    });\n  }\n\n  /**\n   * Mocks a setter method of an object.\n   * This function is a syntax sugar for MockTracker.method with options.setter set to true.\n   * @param {object} object - The target object.\n   * @param {string} methodName  - The setter method to be mocked.\n   * @param {Function} [implementation] - An optional replacement function for the targeted method.\n   * @param {object} [options] - Additional tracking options.\n   * @param {boolean} [options.getter=false] - Indicates whether this is a getter method.\n   * @param {boolean} [options.setter=true] - Indicates whether this is a setter method.\n   * @param {number} [options.times=Infinity] - The maximum number of times the mock method can be called.\n   * @returns {ProxyConstructor} The mock method tracker.\n   */\n  setter(\n    object,\n    methodName,\n    implementation = kDefaultFunction,\n    options = kEmptyObject\n  ) {\n    if (implementation !== null && typeof implementation === 'object') {\n      options = implementation;\n      implementation = kDefaultFunction;\n    } else {\n      validateObject(options, 'options');\n    }\n\n    const { setter = true } = options;\n\n    if (setter === false) {\n      throw new ERR_INVALID_ARG_VALUE(\n        'options.setter',\n        setter,\n        'cannot be false'\n      );\n    }\n\n    return this.method(object, methodName, implementation, {\n      __proto__: null,\n      ...options,\n      setter,\n    });\n  }\n\n  /**\n   * Resets the mock tracker, restoring all mocks and clearing timers.\n   */\n  reset() {\n    this.restoreAll();\n    // this.#timers?.reset();\n    this.#mocks = [];\n  }\n\n  /**\n   * Restore all mocks created by this MockTracker instance.\n   */\n  restoreAll() {\n    for (let i = 0; i < this.#mocks.length; i++) {\n      restore.call(this.#mocks[i]);\n    }\n  }\n\n  #setupMock(ctx, fnToMatch) {\n    const mock = new Proxy(fnToMatch, {\n      __proto__: null,\n      apply(_fn, thisArg, argList) {\n        const fn = nextImpl.call(ctx);\n        let result;\n        let error;\n\n        try {\n          result = Reflect.apply(fn, thisArg, argList);\n        } catch (err) {\n          error = err;\n          throw err;\n        } finally {\n          trackCall.call(ctx, {\n            __proto__: null,\n            arguments: argList,\n            error,\n            result,\n            stack: new Error(),\n            target: undefined,\n            this: thisArg,\n          });\n        }\n\n        return result;\n      },\n      construct(target, argList, newTarget) {\n        const realTarget = nextImpl.call(ctx);\n        let result;\n        let error;\n\n        try {\n          result = Reflect.construct(realTarget, argList, newTarget);\n        } catch (err) {\n          error = err;\n          throw err;\n        } finally {\n          trackCall.call(ctx, {\n            __proto__: null,\n            arguments: argList,\n            error,\n            result,\n            stack: new Error(),\n            target,\n            this: result,\n          });\n        }\n\n        return result;\n      },\n      get(target, property, receiver) {\n        if (property === 'mock') {\n          return ctx;\n        }\n\n        return Reflect.get(target, property, receiver);\n      },\n    });\n\n    this.#mocks.push(ctx);\n    return mock;\n  }\n}\n\nfunction validateStringOrSymbol(value, name) {\n  if (typeof value !== 'string' && typeof value !== 'symbol') {\n    throw new ERR_INVALID_ARG_TYPE(name, ['string', 'symbol'], value);\n  }\n}\n\nfunction validateTimes(value, name) {\n  if (value === Infinity) {\n    return;\n  }\n\n  validateInteger(value, name, 1);\n}\n\nfunction findMethodOnPrototypeChain(instance, methodName) {\n  let host = instance;\n  let descriptor;\n\n  while (host !== null) {\n    descriptor = Object.getOwnPropertyDescriptor(host, methodName);\n\n    if (descriptor) {\n      break;\n    }\n\n    host = Object.getPrototypeOf(host);\n  }\n\n  return descriptor;\n}\n"
  },
  {
    "path": "src/node/internal/module.d.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport function createRequire(path: string): (specifier: string) => unknown;\nexport function isBuiltin(specifier: string): boolean;\n"
  },
  {
    "path": "src/node/internal/process.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport function getEnvObject(): Record<string, string>;\nexport function getBuiltinModule(id: string): object;\nexport function exitImpl(code: number): void;\nexport function getCwd(): string;\nexport function setCwd(path: string): void;\nexport const versions: Record<string, string>;\nexport const platform: string;\n\ndeclare global {\n  const Cloudflare: {\n    readonly compatibilityFlags: Record<string, boolean> & {\n      enable_streams_nodejs_v24_compat: boolean;\n    };\n  };\n}\n\ninterface ErrorWithDetail extends Error {\n  detail?: unknown;\n}\n\ninterface EmitWarningOptions {\n  type?: string | undefined;\n  code?: string | undefined;\n  ctor?: ErrorConstructor | undefined;\n  detail?: string | undefined;\n}\n"
  },
  {
    "path": "src/node/internal/public_process.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n\n// Process is implemented under node-internal: so that we can have node:process\n// resolve to node-internal:public_process OR node-internal:legacy_process depending\n// on whether the enable_nodejs_process_v2 compat flag is set.\n\nimport { default as EventEmitter } from 'node-internal:events';\nimport {\n  ERR_EPERM,\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_ARG_VALUE,\n  ERR_OUT_OF_RANGE,\n  ERR_METHOD_NOT_IMPLEMENTED,\n} from 'node-internal:internal_errors';\nimport processImpl from 'node-internal:process';\nimport { Buffer } from 'node-internal:internal_buffer';\nimport { parseEnv } from 'node-internal:internal_utils';\nimport { Writable } from 'node-internal:streams_writable';\nimport { writeSync } from 'node-internal:internal_fs_sync';\nimport { ReadStream } from 'node-internal:internal_fs_streams';\nimport {\n  platform,\n  nextTick,\n  emitWarning,\n  env,\n  features,\n  _setEventsProcess,\n} from 'node-internal:internal_process';\nimport { validateString } from 'node-internal:validators';\nimport internalAssert from 'node-internal:internal_assert';\nimport type { Readable } from 'node-internal:streams_readable';\nimport type * as NodeFS from 'node:fs';\n\nexport { platform, nextTick, emitWarning, env, features };\n\n// For stdin, we emulate `node foo.js < /dev/null`\nexport const stdin = new ReadStream(null, {\n  fd: 0,\n  autoClose: false,\n}) as Readable & {\n  fd: number;\n};\nstdin.fd = 0;\n\nfunction chunkToBuffer(\n  chunk: Buffer | ArrayBufferView | DataView | string,\n  encoding: BufferEncoding\n): Buffer {\n  if (Buffer.isBuffer(chunk)) {\n    return chunk;\n  }\n  if (typeof chunk === 'string') {\n    return Buffer.from(chunk, encoding);\n  }\n  return Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);\n}\n\n// For stdout, we emulate `nohup node foo.js`\nclass SyncWriteStream extends Writable {\n  fd: number;\n  override readable: boolean = false;\n  _type = 'fs';\n  _isStdio = true;\n  constructor(fd: number) {\n    super({ autoDestroy: true });\n    this.fd = fd;\n  }\n  override _write(\n    chunk: string | Buffer | ArrayBufferView | DataView,\n    encoding: BufferEncoding,\n    cb: (error?: Error | null) => void\n  ): void {\n    try {\n      writeSync(this.fd, chunkToBuffer(chunk, encoding));\n    } catch (e: unknown) {\n      cb(e as Error);\n      return;\n    }\n    cb();\n  }\n}\n\nexport const stdout = new SyncWriteStream(1);\nexport const stderr = new SyncWriteStream(2);\n\nexport function chdir(path: string | Buffer | URL): void {\n  validateString(path, 'directory');\n  processImpl.setCwd(path);\n}\n\nexport const cwd = processImpl.getCwd.bind(processImpl);\n\n// We do not support setting the umask as we do not have a fine-grained\n// permissions on the VFS. Instead we only support validation of input\n// and then ignoring it.\nexport function umask(mask: number | string | undefined): number {\n  if (mask !== undefined) {\n    if (typeof mask === 'string') {\n      if (!/^[0-7]+$/.test(mask)) {\n        throw new ERR_INVALID_ARG_VALUE(\n          'mask',\n          mask,\n          'must be a 32-bit unsigned integer or an octal string'\n        );\n      }\n      mask = parseInt(mask, 8);\n    } else if (typeof mask !== 'number') {\n      throw new ERR_INVALID_ARG_TYPE('mask', 'number', mask);\n    }\n    if (mask < 0 || mask > 0xffffffff || !Number.isInteger(mask)) {\n      throw new ERR_INVALID_ARG_VALUE(\n        'mask',\n        mask,\n        'must be a 32-bit unsigned integer or an octal string'\n      );\n    }\n  }\n  // just return Node.js default of 18\n  return 18;\n}\n\nexport const versions = processImpl.versions;\n\nexport const version = `v${processImpl.versions.node}`;\n\nexport const title = 'workerd';\n\nexport const argv = ['workerd'];\n\nexport const argv0 = 'workerd';\n\nexport const execArgv = [];\n\nexport const arch = 'x64';\n\nexport const config = {\n  target_defaults: {},\n  variables: {},\n};\n\n// For simplicity and polyfill expectations, we assign pid 1 to the process and pid 0 to the \"parent\".\nexport const pid = 1;\nexport const ppid = 0;\n\nexport const allowedNodeEnvironmentFlags = new Set();\n\nexport function setSourceMapsEnabled(\n  _enabled: boolean,\n  _options?: { nodeModules: boolean; generatedCode: boolean }\n): void {\n  // no-op since we do not support disabling source maps\n}\n\nexport function getSourceMapsSupport(): {\n  enabled: boolean;\n  nodeModules: boolean;\n  generatedCode: boolean;\n} {\n  return {\n    enabled: false,\n    nodeModules: false,\n    generatedCode: false,\n  };\n}\n\nexport const hrtime = Object.assign(\n  function hrtime(time?: [number, number]): [number, number] {\n    if (time !== undefined) {\n      if (!Array.isArray(time))\n        throw new ERR_INVALID_ARG_TYPE('time', 'tuple of integers', time);\n      else if ((time as number[]).length !== 2)\n        throw new ERR_OUT_OF_RANGE('time', '2', time);\n    }\n    const nanoTime = BigInt(Date.now()) * 1_000_000n;\n    const nanosBigint = nanoTime % 1_000_000_000n;\n    let seconds = Number((nanoTime - nanosBigint) / 1_000_000_000n);\n    let nanos = Number(nanosBigint);\n    if (time) {\n      const [prevSeconds, prevNanos] = time;\n      seconds -= prevSeconds;\n      nanos -= prevNanos;\n      // We don't have nanosecond-level precision, but if we\n      // did (say with the introduction of randomness), this\n      // is how we would handle nanosecond underflow\n      if (nanos < 0) {\n        seconds -= 1;\n        nanos += 1e9;\n      }\n    }\n    return [seconds, nanos];\n  },\n  {\n    bigint: function (): bigint {\n      return BigInt(Date.now()) * 1_000_000n;\n    },\n  }\n);\n\nexport function ref(): void {\n  // no-op\n}\n\nexport function unref(): void {\n  // no-op\n}\n\nexport function setUncaughtExceptionCaptureCallback(\n  _fn?: (...args: unknown[]) => void\n): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED(\n    'process.setUncaughtExceptionCaptureCallback'\n  );\n}\n\nexport function hasUncaughtExceptionCaptureCallback(): boolean {\n  throw new ERR_METHOD_NOT_IMPLEMENTED(\n    'process.hasUncaughtExceptionCaptureCallback'\n  );\n}\n\nexport function kill(_pid: number, _signal?: string | number): boolean {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('process.kill');\n}\n\nexport function binding(_name: string): unknown {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('process.binding');\n}\n\nexport function dlopen(\n  _module: unknown,\n  _filename: string,\n  _flags?: number\n): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('process.dlopen');\n}\n\nexport const exitCode: number | undefined = undefined;\n\nexport function getActiveResourcesInfo(): string[] {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('process.getActiveResourcesInfo');\n}\n\nexport function memoryUsage(): {\n  rss: number;\n  heapTotal: number;\n  heapUsed: number;\n  external: number;\n  arrayBuffers: number;\n} {\n  return {\n    rss: 0,\n    heapTotal: 0,\n    heapUsed: 0,\n    external: 0,\n    arrayBuffers: 0,\n  };\n}\n\nexport function resourceUsage(): {\n  userCPUTime: number;\n  systemCPUTime: number;\n  maxRSS: number;\n  sharedMemorySize: number;\n  unsharedDataSize: number;\n  unsharedStackSize: number;\n  minorPageFault: number;\n  majorPageFault: number;\n  swappedOut: number;\n  fsRead: number;\n  fsWrite: number;\n  ipcSent: number;\n  ipcReceived: number;\n  signalsCount: number;\n  voluntaryContextSwitches: number;\n  involuntaryContextSwitches: number;\n} {\n  return {\n    userCPUTime: 0,\n    systemCPUTime: 0,\n    maxRSS: 0,\n    sharedMemorySize: 0,\n    unsharedDataSize: 0,\n    unsharedStackSize: 0,\n    minorPageFault: 0,\n    majorPageFault: 0,\n    swappedOut: 0,\n    fsRead: 0,\n    fsWrite: 0,\n    ipcSent: 0,\n    ipcReceived: 0,\n    signalsCount: 0,\n    voluntaryContextSwitches: 0,\n    involuntaryContextSwitches: 0,\n  };\n}\n\nexport function threadCpuUsage(): {\n  user: number;\n  system: number;\n} {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('process.threadCpuUsage');\n}\n\nexport function cpuUsage(_previousValue?: { user: number; system: number }): {\n  user: number;\n  system: number;\n} {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('process.cpuUsage');\n}\n\n// Properties and constants\nexport const channel = null;\nexport const connected = false;\nexport const debugPort = 0; // may be implemented to align with inspector in future\nexport const noDeprecation = false;\nexport const traceDeprecation = false;\nexport const throwDeprecation = false;\nexport const sourceMapsEnabled = false;\nexport const execPath = '';\n\nexport const permission = {\n  has: (): boolean => {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('process.permission.has');\n  },\n};\n\nexport const release = {\n  name: 'node',\n  lts: true,\n  sourceUrl: '',\n  headersUrl: '',\n};\n\nexport const report = {\n  compact: false,\n  directory: '',\n  filename: '',\n  getReport: (): Record<string, unknown> => {\n    // We do not intend to implement process.report in workerd, but there\n    // are some modules in the ecosystem that call it expecting it to exist and\n    // not throw. Returning an empty object for now to satisfy that use case.\n    return {};\n  },\n  reportOnFatalError: false,\n  reportOnSignal: false,\n  reportOnUncaughtException: false,\n  excludeNetwork: false,\n  excludeEnv: false,\n  signal: 'SIGUSR2',\n  writeReport: (): string => {\n    // In this case, there's an expectation that the function will produce\n    // a report on disk and return the filename. Since we do not intend\n    // to implement this API in workerd, we throw to indicate that.\n    throw new ERR_METHOD_NOT_IMPLEMENTED('process.report.writeReport');\n  },\n};\n\nexport const finalization = {\n  register: (): void => {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('process.finalization.register');\n  },\n  registerBeforeExit: (): void => {\n    throw new ERR_METHOD_NOT_IMPLEMENTED(\n      'process.finalization.registerBeforeExit'\n    );\n  },\n  unregister: (): void => {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('process.finalization.unregister');\n  },\n};\n\n// Additional undocumented APIs\nexport function _rawDebug(...args: unknown[]): void {\n  console.log(...args);\n}\n\nexport const moduleLoadList: string[] = [];\nexport const _preload_modules: string[] = [];\n\nexport function _linkedBinding(_name: string): unknown {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('process._linkedBinding');\n}\n\nexport const domain = null;\nexport const _exiting = false;\n\nexport function _getActiveRequests(): unknown[] {\n  return [];\n}\n\nexport function _getActiveHandles(): unknown[] {\n  return [];\n}\n\nexport function reallyExit(code?: number): void {\n  processImpl.exitImpl(code || 0);\n}\n\nexport function _kill(_pid: number, _signal: number): boolean {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('process._kill');\n}\n\nexport function constrainedMemory(): number {\n  return 0;\n}\n\nexport function availableMemory(): number {\n  // This may be implemented in future, for now this matches unenv\n  return 0;\n}\n\nexport function execve(\n  _file: string,\n  _args: string[],\n  _env: Record<string, string>\n): never {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('process.execve');\n}\n\nexport function openStdin(): typeof stdin {\n  return stdin;\n}\n\nexport function _fatalException(_err: Error): boolean {\n  return false;\n}\n\nexport function _tickCallback(): void {\n  // no-op\n}\n\nexport function _debugProcess(_pid: number): void {\n  // no-op\n}\n\nexport function _debugEnd(): void {\n  // no-op\n}\n\nexport function _startProfilerIdleNotifier(): void {\n  // no-op\n}\n\nexport function _stopProfilerIdleNotifier(): void {\n  // no-op\n}\n\nexport function send(_message: unknown, _sendHandle?: unknown): boolean {\n  return false;\n}\n\nexport function getBuiltinModule(id: string): object {\n  return processImpl.getBuiltinModule(id);\n}\n\nexport function exit(code: number): void {\n  processImpl.exitImpl(code);\n}\n\nexport function abort(): void {\n  exit(1);\n}\n\n// In future we may return accurate uptime information here, but returning\n// zero at least allows code paths to work correctly in most cases, and is\n// not a completely unreasonable interpretation of Cloudflare's execution\n// model assumptions.\nexport function uptime(): number {\n  return 0;\n}\n\nexport function loadEnvFile(path: string | URL | Buffer = '.env'): void {\n  const { readFileSync } = process.getBuiltinModule('fs') as typeof NodeFS;\n  const parsed = parseEnv(\n    readFileSync(path instanceof URL ? path : path.toString(), 'utf8')\n  );\n  Object.assign(process.env, parsed);\n}\n\n// On the virtual filesystem, we only support group 0\nexport function getegid(): number {\n  return 0;\n}\n\nexport function getgid(): number {\n  return 0;\n}\n\nexport function getgroups(): number[] {\n  return [0];\n}\n\n// On the virtual filesystem, we only support user 0\nexport function geteuid(): number {\n  return 0;\n}\n\nexport function getuid(): number {\n  return 0;\n}\n\n// We support group 0 but no others, with the virtual filesystem.\nexport function setegid(id: number | string): void {\n  if (id !== 0) throw new ERR_EPERM({ syscall: 'setegid' });\n}\n\n// We support group 0 but no others, with the virtual filesystem.\nexport function setgid(id: number | string): void {\n  if (id !== 0) throw new ERR_EPERM({ syscall: 'setgid' });\n}\n\n// We support group 0 but no others, with the virtual filesystem.\nexport function seteuid(id: number | string): void {\n  if (id !== 0) throw new ERR_EPERM({ syscall: 'seteuid' });\n}\n\n// We support group 0 but no others, with the virtual filesystem.\nexport function setuid(id: number | string): void {\n  if (id !== 0) throw new ERR_EPERM({ syscall: 'setuid' });\n}\n\n// We don't support setting or initializing arbitrary groups on the virtual filesystem.\nexport function setgroups(_groups: number[]): void {\n  throw new ERR_EPERM({ syscall: 'setgroups' });\n}\n\nexport function initgroups(\n  _user: number | string,\n  _extractGroup: number | string\n): void {\n  throw new ERR_EPERM({ syscall: 'initgroups' });\n}\n\ninterface Process extends EventEmitter {\n  version: typeof version;\n  versions: typeof versions;\n  title: typeof title;\n  argv: typeof argv;\n  argv0: typeof argv0;\n  execArgv: typeof execArgv;\n  arch: typeof arch;\n  platform: typeof platform;\n  config: typeof config;\n  pid: typeof pid;\n  ppid: typeof ppid;\n  getegid: typeof getegid;\n  getgid: typeof getgid;\n  getgroups: typeof getgroups;\n  geteuid: typeof geteuid;\n  getuid: typeof getuid;\n  setegid: typeof setegid;\n  setgid: typeof setgid;\n  setgroups: typeof setgroups;\n  seteuid: typeof seteuid;\n  setuid: typeof setuid;\n  initgroups: typeof initgroups;\n  setSourceMapsEnabled: typeof setSourceMapsEnabled;\n  getSourceMapsSupport: typeof getSourceMapsSupport;\n  nextTick: typeof nextTick;\n  emitWarning: typeof emitWarning;\n  env: typeof env;\n  exit: typeof exit;\n  abort: typeof abort;\n  getBuiltinModule: typeof getBuiltinModule;\n  features: typeof features;\n  allowedNodeEnvironmentFlags: typeof allowedNodeEnvironmentFlags;\n  kill: typeof kill;\n  ref: typeof ref;\n  unref: typeof unref;\n  chdir: typeof chdir;\n  cwd: typeof cwd;\n  umask: typeof umask;\n  hrtime: typeof hrtime;\n  uptime: typeof uptime;\n  loadEnvFile: typeof loadEnvFile;\n  exitCode: typeof exitCode;\n  channel: typeof channel;\n  connected: typeof connected;\n  binding: typeof binding;\n  debugPort: typeof debugPort;\n  dlopen: typeof dlopen;\n  finalization: typeof finalization;\n  getActiveResourcesInfo: typeof getActiveResourcesInfo;\n  setUncaughtExceptionCaptureCallback: typeof setUncaughtExceptionCaptureCallback;\n  hasUncaughtExceptionCaptureCallback: typeof hasUncaughtExceptionCaptureCallback;\n  memoryUsage: typeof memoryUsage;\n  noDeprecation: typeof noDeprecation;\n  permission: typeof permission;\n  release: typeof release;\n  report: typeof report;\n  resourceUsage: typeof resourceUsage;\n  send: typeof send;\n  traceDeprecation: typeof traceDeprecation;\n  throwDeprecation: typeof throwDeprecation;\n  sourceMapsEnabled: typeof sourceMapsEnabled;\n  stdin: typeof stdin;\n  stdout: typeof stdout;\n  stderr: typeof stderr;\n  threadCpuUsage: typeof threadCpuUsage;\n  cpuUsage: typeof cpuUsage;\n  execPath: typeof execPath;\n  constrainedMemory: typeof constrainedMemory;\n  availableMemory: typeof availableMemory;\n  execve: typeof execve;\n  openStdin: typeof openStdin;\n  _rawDebug: typeof _rawDebug;\n  moduleLoadList: typeof moduleLoadList;\n  _linkedBinding: typeof _linkedBinding;\n  domain: typeof domain;\n  _exiting: typeof _exiting;\n  _getActiveRequests: typeof _getActiveRequests;\n  _getActiveHandles: typeof _getActiveHandles;\n  reallyExit: typeof reallyExit;\n  _kill: typeof _kill;\n  _fatalException: typeof _fatalException;\n  _tickCallback: typeof _tickCallback;\n  _debugProcess: typeof _debugProcess;\n  _debugEnd: typeof _debugEnd;\n  _startProfilerIdleNotifier: typeof _startProfilerIdleNotifier;\n  _stopProfilerIdleNotifier: typeof _stopProfilerIdleNotifier;\n  _preload_modules: typeof _preload_modules;\n}\n\nexport const _disconnect = undefined,\n  _handleQueue = undefined,\n  _pendingMessage = undefined,\n  _channel = undefined,\n  _send = undefined;\n\nexport let assert: typeof internalAssert.ok | undefined = undefined;\n\nif (!Cloudflare.compatibilityFlags.remove_nodejs_compat_eol_v23) {\n  assert = internalAssert.ok.bind(internalAssert);\n}\n\nconst _process = {\n  assert,\n  version,\n  versions,\n  title,\n  argv,\n  argv0,\n  execArgv,\n  arch,\n  platform,\n  config,\n  pid,\n  ppid,\n  getegid,\n  getgid,\n  getgroups,\n  geteuid,\n  getuid,\n  setegid,\n  setgid,\n  setgroups,\n  seteuid,\n  setuid,\n  initgroups,\n  setSourceMapsEnabled,\n  getSourceMapsSupport,\n  nextTick,\n  emitWarning,\n  env,\n  exit,\n  abort,\n  getBuiltinModule,\n  features,\n  allowedNodeEnvironmentFlags,\n  kill,\n  ref,\n  unref,\n  chdir,\n  cwd,\n  umask,\n  hrtime,\n  uptime,\n  loadEnvFile,\n  exitCode,\n  channel,\n  connected,\n  binding,\n  debugPort,\n  dlopen,\n  finalization,\n  getActiveResourcesInfo,\n  setUncaughtExceptionCaptureCallback,\n  hasUncaughtExceptionCaptureCallback,\n  memoryUsage,\n  noDeprecation,\n  permission,\n  release,\n  report,\n  resourceUsage,\n  send,\n  traceDeprecation,\n  throwDeprecation,\n  sourceMapsEnabled,\n  stdin,\n  stdout,\n  stderr,\n  threadCpuUsage,\n  cpuUsage,\n  execPath,\n  constrainedMemory,\n  availableMemory,\n  execve,\n  openStdin,\n  _rawDebug,\n  moduleLoadList,\n  _linkedBinding,\n  domain,\n  _exiting,\n  _getActiveRequests,\n  _getActiveHandles,\n  reallyExit,\n  _kill,\n  _fatalException,\n  _tickCallback,\n  _debugProcess,\n  _debugEnd,\n  _startProfilerIdleNotifier,\n  _stopProfilerIdleNotifier,\n  _preload_modules,\n  _disconnect,\n  _handleQueue,\n  _pendingMessage,\n  _channel,\n  _send,\n};\n\nconst process: Process = Object.setPrototypeOf(\n  _process,\n  EventEmitter.prototype as object\n) as Process;\nEventEmitter.call(process);\n\nObject.defineProperty(process, Symbol.toStringTag, {\n  value: 'process',\n  configurable: true,\n});\n\n// We lazily attach unhandled rejection and rejection handled listeners\n// to ensure performance optimizations remain in the no listener case\nlet addedUnhandledRejection = false,\n  addedRejectionHandled = false;\nprocess.on('newListener', (name) => {\n  if (name === 'unhandledRejection' && !addedUnhandledRejection) {\n    addEventListener(\n      'unhandledrejection',\n      function (evt: Event & { reason: unknown; promise: Promise<unknown> }) {\n        process.emit('unhandledRejection', evt.reason, evt.promise);\n      }\n    );\n    addedUnhandledRejection = true;\n  }\n  if (name === 'rejectionHandled' && !addedRejectionHandled) {\n    addEventListener(\n      'rejectionhandled',\n      function (evt: Event & { reason: unknown; promise: Promise<unknown> }) {\n        process.emit('rejectionHandled', evt.promise);\n      }\n    );\n    addedRejectionHandled = true;\n  }\n});\n\n_setEventsProcess(process);\n\nexport const _events = process._events,\n  _eventsCount = process._eventsCount,\n  _maxListeners = process._maxListeners;\n\nexport default process;\n"
  },
  {
    "path": "src/node/internal/sockets.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport interface SocketInfo {\n  remoteAddress?: string | null;\n  localAddress?: string | null;\n}\n\nexport type Reader = ReadableStream;\n\nexport interface Writer extends WritableStream {\n  close(): Promise<void>;\n  write(data: string | ArrayBufferView): Promise<void>;\n  closed: Promise<void>;\n  releaseLock(): void;\n}\n\nexport interface Socket {\n  opened: Promise<SocketInfo>;\n  closed: Promise<void>;\n  close(): Promise<void>;\n  readable: Reader;\n  writable: Writer;\n  startTls(): Socket;\n\n  readonly upgraded: boolean;\n  readonly secureTransport: 'on' | 'off' | 'starttls';\n}\n\ndeclare namespace sockets {\n  function connect(\n    input: string,\n    options: {\n      allowHalfOpen?: boolean | undefined;\n      highWatermark?: number | undefined;\n      secureTransport: 'on' | 'off' | 'starttls';\n    }\n  ): Socket;\n}\nexport default sockets;\n"
  },
  {
    "path": "src/node/internal/sqlite.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { DatabaseSync, StatementSync, backup } from 'node:sqlite';\n\nexport { DatabaseSync, StatementSync, backup };\n\nexport const SQLITE_CHANGESET_OMIT: number;\nexport const SQLITE_CHANGESET_REPLACE: number;\nexport const SQLITE_CHANGESET_ABORT: number;\nexport const SQLITE_CHANGESET_DATA: number;\nexport const SQLITE_CHANGESET_NOTFOUND: number;\nexport const SQLITE_CHANGESET_CONFLICT: number;\nexport const SQLITE_CHANGESET_CONSTRAINT: number;\nexport const SQLITE_CHANGESET_FOREIGN_KEY: number;\n"
  },
  {
    "path": "src/node/internal/streams_add_abort_signal.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport {\n  AbortError,\n  ERR_INVALID_ARG_TYPE,\n} from 'node-internal:internal_errors';\n\nimport {\n  isNodeStream,\n  isWebStream,\n  kControllerErrorFunction,\n} from 'node-internal:streams_util';\n\nimport { eos } from 'node-internal:streams_end_of_stream';\nimport type { Readable } from 'node-internal:streams_readable';\nimport type { Writable } from 'node-internal:streams_writable';\nimport type { Transform } from 'node-internal:streams_transform';\nimport { addAbortListener } from 'node-internal:events';\n\n// This method is inlined here for readable-stream\n// It also does not allow for signal to not exist on the stream\n// https://github.com/nodejs/node/pull/36061#discussion_r533718029\nfunction validateAbortSignal(\n  signal: unknown,\n  name: string\n): asserts signal is AbortSignal {\n  if (signal == null || typeof signal !== 'object' || !('aborted' in signal)) {\n    throw new ERR_INVALID_ARG_TYPE(name, 'AbortSignal', signal);\n  }\n}\n\ntype StreamType =\n  | Readable\n  | Writable\n  | Transform\n  | ReadableStream\n  | WritableStream;\n\nexport function addAbortSignal<T extends StreamType>(\n  signal: unknown,\n  stream: T\n): T {\n  validateAbortSignal(signal, 'signal');\n  if (!isNodeStream(stream) && !isWebStream(stream)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'stream',\n      ['ReadableStream', 'WritableStream', 'Stream'],\n      stream\n    );\n  }\n  return addAbortSignalNoValidate(signal, stream);\n}\n\nexport function addAbortSignalNoValidate<T extends StreamType>(\n  signal: AbortSignal | null | undefined,\n  stream: T\n): T {\n  if (signal == null || typeof signal !== 'object' || !('aborted' in signal)) {\n    return stream;\n  }\n  const onAbort = isNodeStream(stream)\n    ? (): void => {\n        stream.destroy(new AbortError(undefined, { cause: signal.reason }));\n      }\n    : (): void => {\n        (\n          stream as ReadableStream & {\n            [kControllerErrorFunction]: (err: Error) => void;\n          }\n        )[kControllerErrorFunction](\n          new AbortError(undefined, { cause: signal.reason })\n        );\n      };\n  if (signal.aborted) {\n    onAbort();\n  } else {\n    const disposable = addAbortListener(signal, onAbort);\n    eos(\n      stream as Readable | Writable | Transform,\n      disposable[Symbol.dispose] as () => void\n    );\n  }\n  return stream;\n}\n"
  },
  {
    "path": "src/node/internal/streams_compose.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport { compose } from 'node:stream';\n"
  },
  {
    "path": "src/node/internal/streams_compose.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { pipeline } from 'node-internal:streams_pipeline';\nimport { Duplex } from 'node-internal:streams_duplex';\nimport {\n  Readable as ReadableConstructor,\n  from,\n} from 'node-internal:streams_readable';\nimport {\n  isNodeStream,\n  isReadable,\n  isWritable,\n} from 'node-internal:streams_util';\nimport { destroyer } from 'node-internal:streams_destroy';\nimport {\n  AbortError,\n  ERR_INVALID_ARG_VALUE,\n  ERR_MISSING_ARGS,\n} from 'node-internal:internal_errors';\n\nexport function compose(...streams) {\n  if (streams.length === 0) {\n    throw new ERR_MISSING_ARGS('streams');\n  }\n  if (streams.length === 1) {\n    return from(Duplex, streams[0]);\n  }\n  const orgStreams = [...streams];\n  if (typeof streams[0] === 'function') {\n    streams[0] = from(Duplex, streams[0]);\n  }\n  if (typeof streams[streams.length - 1] === 'function') {\n    const idx = streams.length - 1;\n    streams[idx] = from(Duplex, streams[idx]);\n  }\n  for (let n = 0; n < streams.length; ++n) {\n    if (!isNodeStream(streams[n])) {\n      // TODO(ronag): Add checks for non streams.\n      continue;\n    }\n    if (n < streams.length - 1 && !isReadable(streams[n])) {\n      throw new ERR_INVALID_ARG_VALUE(\n        `streams[${n}]`,\n        orgStreams[n],\n        'must be readable'\n      );\n    }\n    if (n > 0 && !isWritable(streams[n])) {\n      throw new ERR_INVALID_ARG_VALUE(\n        `streams[${n}]`,\n        orgStreams[n],\n        'must be writable'\n      );\n    }\n  }\n  let ondrain;\n  let onfinish;\n  let onreadable;\n  let onclose;\n  let d;\n  function onfinished(err) {\n    const cb = onclose;\n    onclose = null;\n    if (cb) {\n      cb(err);\n    } else if (err) {\n      d.destroy(err);\n    } else if (!readable && !writable) {\n      d.destroy();\n    }\n  }\n  const head = streams[0];\n  const tail = pipeline(streams, onfinished);\n  const writable = !!isWritable(head);\n  const readable = !!isReadable(tail);\n\n  // TODO(ronag): Avoid double buffering.\n  // Implement Writable/Readable/Duplex traits.\n  // See, https://github.com/nodejs/node/pull/33515.\n  d = new Duplex({\n    // TODO (ronag): highWaterMark?\n    writableObjectMode: !!(\n      head !== null &&\n      head !== undefined &&\n      head.writableObjectMode\n    ),\n    readableObjectMode: !!(\n      tail !== null &&\n      tail !== undefined &&\n      tail.writableObjectMode\n    ),\n    writable,\n    readable,\n  });\n  if (writable) {\n    const w = head;\n    d._write = function (chunk, encoding, callback) {\n      if (head.write(chunk, encoding)) {\n        callback();\n      } else {\n        ondrain = callback;\n      }\n    };\n    d._final = function (callback) {\n      w.end();\n      onfinish = callback;\n    };\n    w.on('drain', function () {\n      if (ondrain) {\n        const cb = ondrain;\n        ondrain = null;\n        cb();\n      }\n    });\n    tail.on('finish', function () {\n      if (onfinish) {\n        const cb = onfinish;\n        onfinish = null;\n        cb();\n      }\n    });\n  }\n  if (readable) {\n    tail.on('readable', function () {\n      if (onreadable) {\n        const cb = onreadable;\n        onreadable = null;\n        cb();\n      }\n    });\n    tail.on('end', function () {\n      d.push(null);\n    });\n    d._read = function (_n) {\n      while (true) {\n        const buf = tail.read();\n        if (buf === null) {\n          onreadable = d._read;\n          return;\n        }\n        if (!d.push(buf)) {\n          return;\n        }\n      }\n    };\n  }\n  d._destroy = function (err, callback) {\n    if (!err && onclose !== null) {\n      err = new AbortError();\n    }\n    onreadable = null;\n    ondrain = null;\n    onfinish = null;\n    if (onclose === null) {\n      callback(err);\n    } else {\n      onclose = callback;\n      destroyer(tail, err);\n    }\n  };\n  return d;\n}\n\nReadableConstructor.prototype.compose = function (...streams) {\n  return compose(this, ...streams);\n};\n"
  },
  {
    "path": "src/node/internal/streams_destroy.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n/* eslint-disable @typescript-eslint/no-redundant-type-constituents, @typescript-eslint/no-unsafe-call */\n\nimport { nextTick } from 'node-internal:internal_process';\n\nimport type { Writable, WritableState } from 'node-internal:streams_writable';\nimport type { Readable, ReadableState } from 'node-internal:streams_readable';\n\nimport {\n  AbortError,\n  aggregateTwoErrors,\n  ERR_MULTIPLE_CALLBACK,\n} from 'node-internal:internal_errors';\nimport {\n  kIsDestroyed,\n  isDestroyed,\n  isFinished,\n  isServerRequest,\n  kState,\n  kErrorEmitted,\n  kEmitClose,\n  kClosed,\n  kCloseEmitted,\n  kConstructed,\n  kDestroyed,\n  kAutoDestroy,\n  kErrored,\n} from 'node-internal:streams_util';\nimport type { OutgoingMessage } from 'node-internal:internal_http_outgoing';\n\nconst kConstruct = Symbol('kConstruct');\nconst kDestroy = Symbol('kDestroy');\n\nfunction checkError(\n  err?: Error | null,\n  w?: WritableState,\n  r?: ReadableState\n): void {\n  if (err) {\n    // Avoid V8 leak, https://github.com/nodejs/node/pull/34103#issuecomment-652002364\n    err.stack; // eslint-disable-line @typescript-eslint/no-unused-expressions\n\n    if (w && !w.errored) {\n      w.errored = err;\n    }\n    if (r && !r.errored) {\n      r.errored = err;\n    }\n  }\n}\n\n// Backwards compat. cb() is undocumented and unused in core but\n// unfortunately might be used by modules.\nexport function destroy(\n  this: Readable | Writable,\n  err?: Error,\n  cb?: VoidFunction\n  // @ts-expect-error TS2526 Returning this is not allowed.\n): this {\n  const r = this._readableState;\n  const w = this._writableState;\n  // With duplex streams we use the writable side for state.\n  const s = w || r;\n  if (\n    (w && (w[kState] & kDestroyed) !== 0) ||\n    (r && (r[kState] & kDestroyed) !== 0)\n  ) {\n    if (typeof cb === 'function') {\n      cb();\n    }\n    return this;\n  }\n\n  // We set destroyed to true before firing error callbacks in order\n  // to make it re-entrance safe in case destroy() is called within callbacks\n  checkError(err, w, r);\n  if (w) {\n    w[kState] |= kDestroyed;\n  }\n  if (r) {\n    r[kState] |= kDestroyed;\n  }\n\n  // If still constructing then defer calling _destroy.\n  // @ts-expect-error TS18048 `s` will always be defined here.\n  if ((s[kState] & kConstructed) === 0) {\n    this.once(kDestroy, function (this: Readable | Writable, er: Error) {\n      _destroy(this, aggregateTwoErrors(er, err), cb);\n    });\n  } else {\n    _destroy(this, err, cb);\n  }\n  return this;\n}\n\nfunction _destroy(\n  self: Readable | Writable,\n  err?: Error,\n  cb?: (err?: Error | null) => void\n): void {\n  let called = false;\n\n  function onDestroy(err?: Error | null): void {\n    if (called) {\n      return;\n    }\n    called = true;\n\n    const r = self._readableState;\n    const w = self._writableState;\n    checkError(err, w, r);\n    if (w) {\n      w[kState] |= kClosed;\n    }\n    if (r) {\n      r[kState] |= kClosed;\n    }\n\n    if (typeof cb === 'function') {\n      cb(err);\n    }\n\n    if (err) {\n      nextTick(emitErrorCloseNT, self, err);\n    } else {\n      nextTick(emitCloseNT, self);\n    }\n  }\n  try {\n    self._destroy(err || null, onDestroy);\n  } catch (err) {\n    onDestroy(err as Error);\n  }\n}\n\nfunction emitErrorCloseNT(self: Readable | Writable, err: Error): void {\n  emitErrorNT(self, err);\n  emitCloseNT(self);\n}\n\nfunction emitCloseNT(self: Readable | Writable): void {\n  const r = self._readableState;\n  const w = self._writableState;\n  if (w) {\n    w[kState] |= kCloseEmitted;\n  }\n  if (r) {\n    r[kState] |= kCloseEmitted;\n  }\n  if (\n    (w && (w[kState] & kEmitClose) !== 0) ||\n    (r && (r[kState] & kEmitClose) !== 0)\n  ) {\n    self.emit('close');\n  }\n}\n\nfunction emitErrorNT(self: Readable | Writable, err: Error): void {\n  const r = self._readableState;\n  const w = self._writableState;\n  if (\n    (w && (w[kState] & kErrorEmitted) !== 0) ||\n    (r && (r[kState] & kErrorEmitted) !== 0)\n  ) {\n    return;\n  }\n\n  if (w) {\n    w[kState] |= kErrorEmitted;\n  }\n  if (r) {\n    r[kState] |= kErrorEmitted;\n  }\n  self.emit('error', err);\n}\n\nexport function undestroy(this: Readable | Writable): void {\n  const r = this._readableState;\n  const w = this._writableState;\n  if (r) {\n    r.constructed = true;\n    r.closed = false;\n    r.closeEmitted = false;\n    r.destroyed = false;\n    r.errored = null;\n    r.errorEmitted = false;\n    r.reading = false;\n    r.ended = r.readable === false;\n    r.endEmitted = r.readable === false;\n  }\n  if (w) {\n    w.constructed = true;\n    w.destroyed = false;\n    w.closed = false;\n    w.closeEmitted = false;\n    w.errored = null;\n    w.errorEmitted = false;\n    w.finalCalled = false;\n    w.prefinished = false;\n    w.ended = w.writable === false;\n    w.ending = w.writable === false;\n    w.finished = w.writable === false;\n  }\n}\n\nexport function errorOrDestroy(\n  stream: Readable | Writable,\n  err?: Error,\n  sync: boolean = false\n  // @ts-expect-error TS2526 Apparently `this` is disallowed.\n): this | undefined {\n  // We have tests that rely on errors being emitted\n  // in the same tick, so changing this is semver major.\n  // For now when you opt-in to autoDestroy we allow\n  // the error to be emitted nextTick. In a future\n  // semver major update we should change the default to this.\n\n  const r = stream._readableState;\n  const w = stream._writableState;\n  if (\n    (w && (w[kState] ? (w[kState] & kDestroyed) !== 0 : w.destroyed)) ||\n    (r && (r[kState] ? (r[kState] & kDestroyed) !== 0 : r.destroyed))\n  ) {\n    // @ts-expect-error TS2683 This should be somehow type-defined.\n    return this;\n  }\n  if (\n    (r && (r[kState] & kAutoDestroy) !== 0) ||\n    (w && (w[kState] & kAutoDestroy) !== 0)\n  ) {\n    stream.destroy(err);\n  } else if (err) {\n    // Avoid V8 leak, https://github.com/nodejs/node/pull/34103#issuecomment-652002364\n    err.stack; // eslint-disable-line @typescript-eslint/no-unused-expressions\n\n    if (w && (w[kState] & kErrored) === 0) {\n      w.errored = err;\n    }\n    if (r && (r[kState] & kErrored) === 0) {\n      r.errored = err;\n    }\n    if (sync) {\n      nextTick(emitErrorNT, stream, err);\n    } else {\n      emitErrorNT(stream, err);\n    }\n  }\n\n  return undefined;\n}\n\nexport function construct(stream: Readable | Writable, cb: VoidFunction): void {\n  if (typeof stream._construct !== 'function') {\n    return;\n  }\n  const r = stream._readableState;\n  const w = stream._writableState;\n\n  if (r) {\n    r[kState] &= ~kConstructed;\n  }\n  if (w) {\n    w[kState] &= ~kConstructed;\n  }\n\n  stream.once(kConstruct, cb);\n\n  if (stream.listenerCount(kConstruct) > 1) {\n    // Duplex\n    return;\n  }\n\n  nextTick(constructNT, stream);\n}\n\nfunction constructNT(this: unknown, stream: Readable | Writable): void {\n  let called = false;\n\n  function onConstruct(err?: Error): void {\n    if (called) {\n      errorOrDestroy(stream, err ?? new ERR_MULTIPLE_CALLBACK());\n      return;\n    }\n    called = true;\n\n    const r = stream._readableState;\n    const w = stream._writableState;\n    const s = w || r;\n\n    if (r) {\n      r[kState] |= kConstructed;\n    }\n    if (w) {\n      w[kState] |= kConstructed;\n    }\n\n    if (s?.destroyed) {\n      stream.emit(kDestroy, err);\n    } else if (err) {\n      errorOrDestroy(stream, err, true);\n    } else {\n      stream.emit(kConstruct);\n    }\n  }\n\n  try {\n    stream._construct?.((err) => {\n      nextTick(onConstruct, err);\n    });\n  } catch (err) {\n    nextTick(onConstruct, err);\n  }\n}\n\nfunction isRequest(stream: unknown): stream is OutgoingMessage {\n  return (\n    stream != null &&\n    typeof stream === 'object' &&\n    'setHeader' in stream &&\n    'abort' in stream &&\n    typeof stream.abort === 'function'\n  );\n}\n\nfunction emitCloseLegacy(stream: Readable | Writable): void {\n  stream.emit('close');\n}\n\nfunction emitErrorCloseLegacy(stream: Readable | Writable, err?: Error): void {\n  stream.emit('error', err);\n  nextTick(emitCloseLegacy, stream);\n}\n\n// Normalize destroy for legacy.\nexport function destroyer(\n  stream: Readable | Writable | null | undefined,\n  err?: Error\n): void {\n  if (!stream || isDestroyed(stream)) {\n    return;\n  }\n\n  if (!err && !isFinished(stream)) {\n    err = new AbortError();\n  }\n\n  // TODO: Remove isRequest branches.\n  if (isServerRequest(stream)) {\n    // @ts-expect-error TS2540 - socket is read-only but we need to set it to null for cleanup\n    stream.socket = null;\n    stream.destroy(err);\n  } else if (isRequest(stream)) {\n    // @ts-expect-error TS2339 - abort exists on OutgoingMessage but not in types\n    stream.abort();\n  } else if (isRequest(stream.req)) {\n    // @ts-expect-error TS2339 - abort exists on req but not in all types\n    stream.req.abort();\n  } else if (typeof stream.destroy === 'function') {\n    stream.destroy(err);\n  } else if ('close' in stream && typeof stream.close === 'function') {\n    // TODO: Don't lose err?\n    stream.close();\n  } else if (err) {\n    nextTick(emitErrorCloseLegacy, stream, err);\n  } else {\n    nextTick(emitCloseLegacy, stream);\n  }\n  if (!stream.destroyed) {\n    stream[kIsDestroyed] = true;\n  }\n}\n"
  },
  {
    "path": "src/node/internal/streams_duplex.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { Duplex as _Duplex } from 'node:stream';\nimport type {\n  kIsReadable,\n  kIsWritable,\n  kDestroyed,\n} from 'node-internal:streams_util';\n\nexport {\n  Writable,\n  WritableOptions,\n  Readable,\n  ReadableOptions,\n  duplexPair,\n} from 'node:stream';\n\nexport const from: typeof _Duplex.from;\nexport const fromWeb: typeof _Duplex.fromWeb;\nexport const toWeb: typeof _Duplex.toWeb;\n\nexport declare class Duplex extends _Duplex {\n  _readableState: undefined;\n  _writableState: undefined;\n  _closed: boolean;\n\n  [kIsReadable]: boolean;\n  [kIsWritable]: boolean;\n  [kDestroyed]: boolean;\n\n  writableErrored: boolean;\n  readableErrored: boolean;\n}\n\nexport function toBYOBWeb(duplex: Duplex): {\n  readable: ReadableStream;\n  writable: WritableStream;\n};\n"
  },
  {
    "path": "src/node/internal/streams_duplex.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n/* TODO: the following is adopted code, enabling linting one day */\n/* eslint-disable */\n\nimport { Buffer } from 'node-internal:internal_buffer';\nimport {\n  Readable,\n  newReadableStreamFromStreamReadable,\n} from 'node-internal:streams_readable';\nimport {\n  Writable,\n  newWritableStreamFromStreamWritable,\n} from 'node-internal:streams_writable';\nimport { ok as assert } from 'node-internal:internal_assert';\nimport { Stream } from 'node-internal:streams_legacy';\nimport { nextTick } from 'node-internal:internal_process';\nimport { validateBoolean, validateObject } from 'node-internal:validators';\nimport { normalizeEncoding } from 'node-internal:internal_utils';\nimport { addAbortSignal } from 'node-internal:streams_add_abort_signal';\n\nimport {\n  isDestroyed,\n  isReadable,\n  isWritable,\n  isIterable,\n  isNodeStream,\n  isWritableEnded,\n  isReadableNodeStream,\n  isWritableNodeStream,\n  isDuplexNodeStream,\n  kOnConstructed,\n} from 'node-internal:streams_util';\nimport {\n  construct as destroyConstruct,\n  destroyer,\n} from 'node-internal:streams_destroy';\nimport { eos } from 'node-internal:streams_end_of_stream';\n\nimport {\n  AbortError,\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_ARG_VALUE,\n  ERR_INVALID_RETURN_VALUE,\n  ERR_STREAM_PREMATURE_CLOSE,\n} from 'node-internal:internal_errors';\n\n/**\n * @typedef {import('./readablestream').ReadableWritablePair\n * } ReadableWritablePair\n * @typedef {import('../../stream').Duplex} Duplex\n */\nconst encoder = new TextEncoder();\n\nObject.setPrototypeOf(Duplex.prototype, Readable.prototype);\nObject.setPrototypeOf(Duplex, Readable);\n{\n  const keys = Object.keys(Writable.prototype);\n  // Allow the keys array to be GC'ed.\n  for (let i = 0; i < keys.length; i++) {\n    const method = keys[i];\n    Duplex.prototype[method] ||= Writable.prototype[method];\n  }\n}\n\n// Use the `destroy` method of `Writable`.\nDuplex.prototype.destroy = Writable.prototype.destroy;\n\nexport function Duplex(options) {\n  if (!(this instanceof Duplex)) return new Duplex(options);\n\n  this._events ??= {\n    close: undefined,\n    error: undefined,\n    prefinish: undefined,\n    finish: undefined,\n    drain: undefined,\n    data: undefined,\n    end: undefined,\n    readable: undefined,\n    // Skip uncommon events...\n    // pause: undefined,\n    // resume: undefined,\n    // pipe: undefined,\n    // unpipe: undefined,\n    // [destroyImpl.kConstruct]: undefined,\n    // [destroyImpl.kDestroy]: undefined,\n  };\n\n  this._readableState = new Readable.ReadableState(options, this, true);\n  this._writableState = new Writable.WritableState(options, this, true);\n\n  if (options) {\n    this.allowHalfOpen = options.allowHalfOpen !== false;\n\n    if (options.readable === false) {\n      this._readableState.readable = false;\n      this._readableState.ended = true;\n      this._readableState.endEmitted = true;\n    }\n\n    if (options.writable === false) {\n      this._writableState.writable = false;\n      this._writableState.ending = true;\n      this._writableState.ended = true;\n      this._writableState.finished = true;\n    }\n\n    if (typeof options.read === 'function') this._read = options.read;\n\n    if (typeof options.write === 'function') this._write = options.write;\n\n    if (typeof options.writev === 'function') this._writev = options.writev;\n\n    if (typeof options.destroy === 'function') this._destroy = options.destroy;\n\n    if (typeof options.final === 'function') this._final = options.final;\n\n    if (typeof options.construct === 'function')\n      this._construct = options.construct;\n\n    if (options.signal) {\n      addAbortSignal(options.signal, this);\n    }\n  } else {\n    this.allowHalfOpen = true;\n  }\n\n  Stream.call(this, options);\n\n  if (this._construct != null) {\n    destroyConstruct(this, () => {\n      this._readableState[kOnConstructed](this);\n      this._writableState[kOnConstructed](this);\n    });\n  }\n}\n\n// Use the `destroy` method of `Writable`.\nDuplex.prototype.destroy = Writable.prototype.destroy;\n\nObject.defineProperties(Duplex.prototype, {\n  writable: {\n    __proto__: null,\n    ...Object.getOwnPropertyDescriptor(Writable.prototype, 'writable'),\n  },\n  writableHighWaterMark: {\n    __proto__: null,\n    ...Object.getOwnPropertyDescriptor(\n      Writable.prototype,\n      'writableHighWaterMark'\n    ),\n  },\n  writableObjectMode: {\n    __proto__: null,\n    ...Object.getOwnPropertyDescriptor(\n      Writable.prototype,\n      'writableObjectMode'\n    ),\n  },\n  writableBuffer: {\n    __proto__: null,\n    ...Object.getOwnPropertyDescriptor(Writable.prototype, 'writableBuffer'),\n  },\n  writableLength: {\n    __proto__: null,\n    ...Object.getOwnPropertyDescriptor(Writable.prototype, 'writableLength'),\n  },\n  writableFinished: {\n    __proto__: null,\n    ...Object.getOwnPropertyDescriptor(Writable.prototype, 'writableFinished'),\n  },\n  writableCorked: {\n    __proto__: null,\n    ...Object.getOwnPropertyDescriptor(Writable.prototype, 'writableCorked'),\n  },\n  writableEnded: {\n    __proto__: null,\n    ...Object.getOwnPropertyDescriptor(Writable.prototype, 'writableEnded'),\n  },\n  writableNeedDrain: {\n    __proto__: null,\n    ...Object.getOwnPropertyDescriptor(Writable.prototype, 'writableNeedDrain'),\n  },\n\n  destroyed: {\n    __proto__: null,\n    get() {\n      if (\n        this._readableState === undefined ||\n        this._writableState === undefined\n      ) {\n        return false;\n      }\n      return this._readableState.destroyed && this._writableState.destroyed;\n    },\n    set(value) {\n      // Backward compatibility, the user is explicitly\n      // managing destroyed.\n      if (this._readableState && this._writableState) {\n        this._readableState.destroyed = value;\n        this._writableState.destroyed = value;\n      }\n    },\n  },\n});\n\nexport function fromWeb(pair, options) {\n  return newStreamDuplexFromReadableWritablePair(pair, options);\n}\n\nexport function toWeb(duplex) {\n  return newReadableWritablePairFromDuplex(duplex);\n}\n\nexport function toBYOBWeb(duplex) {\n  return newReadableWritablePairFromDuplex(duplex, true /* createTypeBytes */);\n}\n\nexport function from(body) {\n  return duplexify(body, 'body');\n}\n\nDuplex.fromWeb = fromWeb;\nDuplex.toWeb = toWeb;\nDuplex.from = from;\n\n// ======================================================================================\n\nfunction isBlob(b) {\n  return b instanceof Blob;\n}\n\n// This is needed for pre node 17.\nclass Duplexify extends Duplex {\n  constructor(options) {\n    super(options);\n    // https://github.com/nodejs/node/pull/34385\n\n    if (\n      (options === null || options === undefined\n        ? undefined\n        : options.readable) === false\n    ) {\n      this['_readableState'].readable = false;\n      this['_readableState'].ended = true;\n      this['_readableState'].endEmitted = true;\n    }\n    if (\n      (options === null || options === undefined\n        ? undefined\n        : options.writable) === false\n    ) {\n      this['_readableState'].writable = false;\n      this['_readableState'].ending = true;\n      this['_readableState'].ended = true;\n      this['_readableState'].finished = true;\n    }\n  }\n}\n\nfunction duplexify(body, name) {\n  if (isDuplexNodeStream(body)) {\n    return body;\n  }\n  if (isReadableNodeStream(body)) {\n    return _duplexify({\n      readable: body,\n    });\n  }\n  if (isWritableNodeStream(body)) {\n    return _duplexify({\n      writable: body,\n    });\n  }\n  if (isNodeStream(body)) {\n    return _duplexify({\n      writable: false,\n      readable: false,\n    });\n  }\n\n  if (body instanceof ReadableStream) {\n    return _duplexify({ readable: Readable.fromWeb(body) });\n  }\n\n  if (body instanceof WritableStream) {\n    return _duplexify({ writable: Writable.fromWeb(body) });\n  }\n\n  if (typeof body === 'function') {\n    const { value, write, final, destroy } = fromAsyncGen(body);\n    if (isIterable(value)) {\n      return Readable.from(Duplexify, value, {\n        // TODO (ronag): highWaterMark?\n        objectMode: true,\n        write,\n        final,\n        destroy,\n      });\n    }\n    const then = value.then;\n    if (typeof then === 'function') {\n      let d;\n      const promise = Reflect.apply(then, value, [\n        (val) => {\n          if (val != null) {\n            throw new ERR_INVALID_RETURN_VALUE('nully', 'body', val);\n          }\n        },\n        (err) => {\n          destroyer(d, err);\n        },\n      ]);\n\n      return (d = new Duplexify({\n        // TODO (ronag): highWaterMark?\n        objectMode: true,\n        readable: false,\n        write,\n        final(cb) {\n          final(async () => {\n            try {\n              await promise;\n              nextTick(cb, null);\n            } catch (err) {\n              nextTick(cb, err);\n            }\n          });\n        },\n        destroy,\n      }));\n    }\n    throw new ERR_INVALID_RETURN_VALUE(\n      'Iterable, AsyncIterable or AsyncFunction',\n      name,\n      value\n    );\n  }\n  if (isBlob(body)) {\n    return duplexify(body.arrayBuffer(), name);\n  }\n  if (isIterable(body)) {\n    return Readable.from(Duplexify, body, {\n      // TODO (ronag): highWaterMark?\n      objectMode: true,\n      writable: false,\n    });\n  }\n\n  if (\n    body?.readable instanceof ReadableStream &&\n    body?.writable instanceof WritableStream\n  ) {\n    return Duplexify.fromWeb(body);\n  }\n\n  if (\n    typeof (body === null || body === undefined ? undefined : body.writable) ===\n      'object' ||\n    typeof (body === null || body === undefined ? undefined : body.readable) ===\n      'object'\n  ) {\n    const readable =\n      body !== null && body !== undefined && body.readable\n        ? isReadableNodeStream(\n            body === null || body === undefined ? undefined : body.readable\n          )\n          ? body === null || body === undefined\n            ? undefined\n            : body.readable\n          : duplexify(body.readable, name)\n        : undefined;\n    const writable =\n      body !== null && body !== undefined && body.writable\n        ? isWritableNodeStream(\n            body === null || body === undefined ? undefined : body.writable\n          )\n          ? body === null || body === undefined\n            ? undefined\n            : body.writable\n          : duplexify(body.writable, name)\n        : undefined;\n    return _duplexify({\n      readable,\n      writable,\n    });\n  }\n  const then = body?.then;\n  if (typeof then === 'function') {\n    let d;\n    Reflect.apply(then, body, [\n      (val) => {\n        if (val != null) {\n          d.push(val);\n        }\n        d.push(null);\n      },\n      (err) => {\n        destroyer(d, err);\n      },\n    ]);\n\n    return (d = new Duplexify({\n      objectMode: true,\n      writable: false,\n      read() {},\n    }));\n  }\n  throw new ERR_INVALID_ARG_TYPE(\n    name,\n    [\n      'Blob',\n      'ReadableStream',\n      'WritableStream',\n      'Stream',\n      'Iterable',\n      'AsyncIterable',\n      'Function',\n      '{ readable, writable } pair',\n      'Promise',\n    ],\n    body\n  );\n}\n\nfunction fromAsyncGen(fn) {\n  let { promise, resolve } = Promise.withResolvers();\n  const ac = new AbortController();\n  const signal = ac.signal;\n  const value = fn(\n    (async function* () {\n      while (true) {\n        const _promise = promise;\n        promise = null;\n        const { chunk, done, cb } = await _promise;\n        nextTick(cb);\n        if (done) return;\n        if (signal.aborted)\n          throw new AbortError(undefined, {\n            cause: signal.reason,\n          });\n        ({ promise, resolve } = Promise.withResolvers());\n        yield chunk;\n      }\n    })(),\n    {\n      signal,\n    }\n  );\n  return {\n    value,\n    write(chunk, _encoding, cb) {\n      const _resolve = resolve;\n      resolve = null;\n      _resolve({\n        chunk,\n        done: false,\n        cb,\n      });\n    },\n    final(cb) {\n      const _resolve = resolve;\n      resolve = null;\n      _resolve({\n        done: true,\n        cb,\n      });\n    },\n    destroy(err, cb) {\n      ac.abort();\n      cb(err);\n    },\n  };\n}\n\nfunction _duplexify(pair) {\n  const r =\n    pair.readable && typeof pair.readable.read !== 'function'\n      ? Readable.wrap(pair.readable)\n      : pair.readable;\n  const w = pair.writable;\n  let readable = !!isReadable(r);\n  let writable = !!isWritable(w);\n  let ondrain;\n  let onfinish;\n  let onreadable;\n  let onclose;\n  let d;\n  function onfinished(err) {\n    const cb = onclose;\n    onclose = null;\n    if (cb) {\n      cb(err);\n    } else if (err) {\n      d.destroy(err);\n    } else if (!readable && !writable) {\n      d.destroy();\n    }\n  }\n\n  // TODO(ronag): Avoid double buffering.\n  // Implement Writable/Readable/Duplex traits.\n  // See, https://github.com/nodejs/node/pull/33515.\n  d = new Duplexify({\n    // TODO (ronag): highWaterMark?\n    readableObjectMode: !!(\n      r !== null &&\n      r !== undefined &&\n      r.readableObjectMode\n    ),\n    writableObjectMode: !!(\n      w !== null &&\n      w !== undefined &&\n      w.writableObjectMode\n    ),\n    readable,\n    writable,\n  });\n  if (writable) {\n    eos(w, (err) => {\n      writable = false;\n      if (err) {\n        destroyer(r, err);\n      }\n      onfinished(err);\n    });\n    d._write = function (chunk, encoding, callback) {\n      if (w.write(chunk, encoding)) {\n        callback();\n      } else {\n        ondrain = callback;\n      }\n    };\n    d._final = function (callback) {\n      w.end();\n      onfinish = callback;\n    };\n    w.on('drain', function () {\n      if (ondrain) {\n        const cb = ondrain;\n        ondrain = null;\n        cb();\n      }\n    });\n    w.on('finish', function () {\n      if (onfinish) {\n        const cb = onfinish;\n        onfinish = null;\n        cb();\n      }\n    });\n  }\n  if (readable) {\n    eos(r, (err) => {\n      readable = false;\n      if (err) {\n        destroyer(r, err);\n      }\n      onfinished(err);\n    });\n    r.on('readable', function () {\n      if (onreadable) {\n        const cb = onreadable;\n        onreadable = null;\n        cb();\n      }\n    });\n    r.on('end', function () {\n      d.push(null);\n    });\n    d._read = function () {\n      while (true) {\n        const buf = r.read();\n        if (buf === null) {\n          onreadable = d._read;\n          return;\n        }\n        if (!d.push(buf)) {\n          return;\n        }\n      }\n    };\n  }\n  d._destroy = function (err, callback) {\n    if (!err && onclose !== null) {\n      err = new AbortError();\n    }\n    onreadable = null;\n    ondrain = null;\n    onfinish = null;\n    if (onclose === null) {\n      callback(err);\n    } else {\n      onclose = callback;\n      destroyer(w, err);\n      destroyer(r, err);\n    }\n  };\n  return d;\n}\n\nconst kCallback = Symbol('Callback');\nconst kInitOtherSide = Symbol('InitOtherSide');\n\nclass DuplexSide extends Duplex {\n  #otherSide = null;\n\n  constructor(options) {\n    super(options);\n    this[kCallback] = null;\n    this.#otherSide = null;\n  }\n\n  [kInitOtherSide](otherSide) {\n    // Ensure this can only be set once, to enforce encapsulation.\n    if (this.#otherSide === null) {\n      this.#otherSide = otherSide;\n    } else {\n      assert(this.#otherSide === null);\n    }\n  }\n\n  _read() {\n    const callback = this[kCallback];\n    if (callback) {\n      this[kCallback] = null;\n      callback();\n    }\n  }\n\n  _write(chunk, encoding, callback) {\n    assert(this.#otherSide !== null);\n    assert(this.#otherSide[kCallback] === null);\n    if (chunk.length === 0) {\n      nextTick(callback);\n    } else {\n      this.#otherSide.push(chunk);\n      this.#otherSide[kCallback] = callback;\n    }\n  }\n\n  _final(callback) {\n    this.#otherSide.on('end', callback);\n    this.#otherSide.push(null);\n  }\n}\n\nexport function duplexPair(options) {\n  const side0 = new DuplexSide(options);\n  const side1 = new DuplexSide(options);\n  side0[kInitOtherSide](side1);\n  side1[kInitOtherSide](side0);\n  return [side0, side1];\n}\n\n/**\n * @param {Duplex} duplex\n * @returns {ReadableWritablePair}\n */\nexport function newReadableWritablePairFromDuplex(\n  duplex,\n  createTypeBytes = false\n) {\n  // Not using the internal/streams/utils isWritableNodeStream and\n  // isReadableNodeStream utilities here because they will return false\n  // if the duplex was created with writable or readable options set to\n  // false. Instead, we'll check the readable and writable state after\n  // and return closed WritableStream or closed ReadableStream as\n  // necessary.\n  if (\n    typeof duplex?._writableState !== 'object' ||\n    typeof duplex?._readableState !== 'object'\n  ) {\n    throw new ERR_INVALID_ARG_TYPE('duplex', 'stream.Duplex', duplex);\n  }\n\n  if (isDestroyed(duplex)) {\n    const writable = new WritableStream();\n    const readable = new ReadableStream();\n    writable.close();\n    readable.cancel();\n    return { readable, writable };\n  }\n\n  const writable = isWritable(duplex)\n    ? newWritableStreamFromStreamWritable(duplex)\n    : new WritableStream();\n\n  if (!isWritable(duplex)) writable.close();\n\n  const readableOptions = createTypeBytes ? { type: 'bytes' } : {};\n  const readable = isReadable(duplex)\n    ? newReadableStreamFromStreamReadable(duplex, {}, createTypeBytes)\n    : new ReadableStream(readableOptions);\n\n  if (!isReadable(duplex)) readable.cancel();\n\n  return { writable, readable };\n}\n\n/**\n * @param {ReadableWritablePair} pair\n * @param {{\n *   allowHalfOpen? : boolean,\n *   decodeStrings? : boolean,\n *   encoding? : string,\n *   highWaterMark? : number,\n *   objectMode? : boolean,\n *   signal? : AbortSignal,\n * }} [options]\n * @returns {Duplex}\n */\nexport function newStreamDuplexFromReadableWritablePair(\n  pair = {},\n  options = {}\n) {\n  validateObject(pair, 'pair');\n  const { readable: readableStream, writable: writableStream } = pair;\n\n  if (!(readableStream instanceof ReadableStream)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'pair.readable',\n      'ReadableStream',\n      readableStream\n    );\n  }\n  if (!(writableStream instanceof WritableStream)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'pair.writable',\n      'WritableStream',\n      writableStream\n    );\n  }\n\n  validateObject(options, 'options');\n  const {\n    allowHalfOpen = false,\n    objectMode = false,\n    encoding,\n    decodeStrings = true,\n    highWaterMark,\n    signal,\n  } = options;\n\n  validateBoolean(objectMode, 'options.objectMode');\n  if (encoding !== undefined && !Buffer.isEncoding(encoding))\n    throw new ERR_INVALID_ARG_VALUE(encoding, 'options.encoding');\n\n  const writer = writableStream.getWriter();\n  const reader = readableStream.getReader();\n  let writableClosed = false;\n  let readableClosed = false;\n\n  const duplex = new Duplex({\n    allowHalfOpen,\n    highWaterMark,\n    objectMode,\n    encoding,\n    decodeStrings,\n    signal,\n\n    writev(chunks, callback) {\n      function done(error) {\n        error = error.filter((e) => e);\n        try {\n          callback(error.length === 0 ? undefined : error);\n        } catch (error) {\n          // In a next tick because this is happening within\n          // a promise context, and if there are any errors\n          // thrown we don't want those to cause an unhandled\n          // rejection. Let's just escape the promise and\n          // handle it separately.\n          nextTick(() => destroy.call(duplex, error));\n        }\n      }\n\n      writer.ready.then(() => {\n        return Promise.all(\n          chunks.map((data) => {\n            return writer.write(data.chunk);\n          })\n        ).then(done, done);\n      }, done);\n    },\n\n    write(chunk, encoding, callback) {\n      if (typeof chunk === 'string' && decodeStrings && !objectMode) {\n        const enc = normalizeEncoding(encoding);\n\n        if (enc === 'utf8') {\n          chunk = encoder.encode(chunk);\n        } else {\n          chunk = Buffer.from(chunk, encoding);\n          chunk = new Uint8Array(\n            chunk.buffer,\n            chunk.byteOffset,\n            chunk.byteLength\n          );\n        }\n      }\n\n      function done(error) {\n        try {\n          callback(error);\n        } catch (error) {\n          destroy.call(duplex, error);\n        }\n      }\n\n      writer.ready.then(() => {\n        return writer.write(chunk).then(done, done);\n      }, done);\n    },\n\n    final(callback) {\n      function done(error) {\n        try {\n          callback(error);\n        } catch (error) {\n          // In a next tick because this is happening within\n          // a promise context, and if there are any errors\n          // thrown we don't want those to cause an unhandled\n          // rejection. Let's just escape the promise and\n          // handle it separately.\n          nextTick(() => destroy.call(duplex, error));\n        }\n      }\n\n      if (!writableClosed) {\n        writer.close().then(done, done);\n      }\n    },\n\n    read() {\n      reader.read().then(\n        (chunk) => {\n          if (chunk.done) {\n            duplex.push(null);\n          } else {\n            duplex.push(chunk.value);\n          }\n        },\n        (error) => destroy.call(duplex, error)\n      );\n    },\n\n    destroy(error, callback) {\n      function done() {\n        try {\n          callback(error);\n        } catch (error) {\n          // In a next tick because this is happening within\n          // a promise context, and if there are any errors\n          // thrown we don't want those to cause an unhandled\n          // rejection. Let's just escape the promise and\n          // handle it separately.\n          nextTick(() => {\n            throw error;\n          });\n        }\n      }\n\n      async function closeWriter() {\n        if (!writableClosed) await writer.abort(error);\n      }\n\n      async function closeReader() {\n        if (!readableClosed) await reader.cancel(error);\n      }\n\n      if (!writableClosed || !readableClosed) {\n        Promise.all([closeWriter(), closeReader()]).then(done, done);\n        return;\n      }\n\n      done();\n    },\n  });\n\n  writer.closed.then(\n    () => {\n      writableClosed = true;\n      if (!isWritableEnded(duplex))\n        destroy.call(duplex, new ERR_STREAM_PREMATURE_CLOSE());\n    },\n    (error) => {\n      writableClosed = true;\n      readableClosed = true;\n      destroy.call(duplex, error);\n    }\n  );\n\n  reader.closed.then(\n    () => {\n      readableClosed = true;\n    },\n    (error) => {\n      writableClosed = true;\n      readableClosed = true;\n      destroy.call(duplex, error);\n    }\n  );\n\n  return duplex;\n}\n"
  },
  {
    "path": "src/node/internal/streams_end_of_stream.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n// Ported from https://github.com/mafintosh/end-of-stream with\n// permission from the author, Mathias Buus (@mafintosh).\n\n/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unnecessary-condition, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */\n\nimport { Readable } from 'node-internal:streams_readable';\nimport { Writable } from 'node-internal:streams_writable';\nimport type { Transform } from 'node-internal:streams_transform';\nimport { nextTick } from 'node-internal:internal_process';\nimport type { EventEmitter } from 'node:events';\nimport {\n  AbortError,\n  ERR_INVALID_ARG_TYPE,\n  ERR_STREAM_PREMATURE_CLOSE,\n} from 'node-internal:internal_errors';\nimport { once } from 'node-internal:internal_http_util';\nimport {\n  validateAbortSignal,\n  validateFunction,\n  validateObject,\n  validateBoolean,\n} from 'node-internal:validators';\n\nimport {\n  isClosed,\n  isReadable,\n  isReadableNodeStream,\n  isReadableStream,\n  isReadableFinished,\n  isReadableErrored,\n  isWritable,\n  isWritableNodeStream,\n  isWritableStream,\n  isWritableFinished,\n  isWritableErrored,\n  isNodeStream,\n  willEmitClose as _willEmitClose,\n  kIsClosedPromise,\n} from 'node-internal:streams_util';\nimport { addAbortListener } from 'node-internal:events';\nimport type Stream from 'node:stream';\n\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters\nfunction isRequest<T extends EventEmitter>(stream: any): stream is T {\n  return 'setHeader' in stream && typeof stream.abort === 'function';\n}\n\nexport const nop = (): void => {};\n\ntype EOSOptions = {\n  cleanup?: boolean;\n  error?: boolean;\n  readable?: boolean;\n  writable?: boolean;\n  signal?: AbortSignal;\n};\n\ntype Callback = (...args: unknown[]) => void;\n\nexport function eos(\n  stream: Readable | Writable | Transform,\n  options: EOSOptions,\n  callback: Callback\n): Callback;\nexport function eos(options: EOSOptions, callback: Callback): Callback;\nexport function eos(\n  stream: Readable | Writable | Transform | EOSOptions,\n  options: EOSOptions | Callback,\n  callback?: Callback\n): Callback {\n  if (arguments.length === 2) {\n    // @ts-expect-error TS2322 Supports overloads\n    callback = options;\n    options = {} as EOSOptions;\n  } else if (options == null) {\n    options = {} as EOSOptions;\n  } else {\n    validateObject(options, 'options');\n  }\n  validateFunction(callback, 'callback');\n  validateAbortSignal((options as EOSOptions).signal, 'options.signal');\n\n  // Avoid AsyncResource.bind() because it calls Object.defineProperties which\n  // is a bottleneck here.\n  callback = once(callback) as Callback;\n\n  if (isReadableStream(stream) || isWritableStream(stream)) {\n    return eosWeb(stream, options as EOSOptions, callback);\n  }\n\n  if (!isNodeStream(stream)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'stream',\n      ['ReadableStream', 'WritableStream', 'Stream'],\n      stream\n    );\n  }\n\n  const readable =\n    (options as EOSOptions).readable ?? isReadableNodeStream(stream);\n  const writable =\n    (options as EOSOptions).writable ?? isWritableNodeStream(stream);\n\n  const wState = stream._writableState;\n  const rState = stream._readableState;\n  // Extract req as EventEmitter to avoid union type incompatibility with on/removeListener\n  const req = stream.req as EventEmitter | undefined;\n\n  const onlegacyfinish = (): void => {\n    if (!stream.writable) {\n      onfinish();\n    }\n  };\n\n  // TODO (ronag): Improve soft detection to include core modules and\n  // common ecosystem modules that do properly emit 'close' but fail\n  // this generic check.\n  let willEmitClose =\n    _willEmitClose(stream) &&\n    isReadableNodeStream(stream) === readable &&\n    isWritableNodeStream(stream) === writable;\n\n  let writableFinished = isWritableFinished(stream, false);\n  const onfinish = (): void => {\n    writableFinished = true;\n    // Stream should not be destroyed here. If it is that\n    // means that user space is doing something differently and\n    // we cannot trust willEmitClose.\n    if (stream.destroyed) {\n      willEmitClose = false;\n    }\n\n    if (willEmitClose && (!stream.readable || readable)) {\n      return;\n    }\n\n    if (!readable || readableFinished) {\n      callback?.call(stream);\n    }\n  };\n\n  let readableFinished = isReadableFinished(stream, false);\n  const onend = (): void => {\n    readableFinished = true;\n    // Stream should not be destroyed here. If it is that\n    // means that user space is doing something differently and\n    // we cannot trust willEmitClose.\n    if (stream.destroyed) {\n      willEmitClose = false;\n    }\n\n    if (willEmitClose && (!stream.writable || writable)) {\n      return;\n    }\n\n    if (!writable || writableFinished) {\n      callback?.call(stream);\n    }\n  };\n\n  const onerror = (err: Error): void => {\n    callback?.call(stream, err);\n  };\n\n  let closed = isClosed(stream);\n\n  const onclose = (): void => {\n    closed = true;\n\n    const errored = isWritableErrored(stream) || isReadableErrored(stream);\n\n    if (errored && typeof errored !== 'boolean') {\n      return callback?.call(stream, errored);\n    }\n\n    if (readable && !readableFinished && isReadableNodeStream(stream, true)) {\n      if (!isReadableFinished(stream, false))\n        return callback?.call(stream, new ERR_STREAM_PREMATURE_CLOSE());\n    }\n    if (writable && !writableFinished) {\n      if (!isWritableFinished(stream, false))\n        return callback?.call(stream, new ERR_STREAM_PREMATURE_CLOSE());\n    }\n\n    callback?.call(stream);\n  };\n\n  const onclosed = (): void => {\n    closed = true;\n\n    const errored = isWritableErrored(stream) || isReadableErrored(stream);\n\n    if (errored && typeof errored !== 'boolean') {\n      callback?.call(stream, errored);\n      return;\n    }\n\n    callback?.call(stream);\n  };\n\n  const onrequest = (): void => {\n    req?.on('finish', onfinish);\n  };\n\n  if (isRequest(stream)) {\n    stream.on('complete', onfinish);\n    if (!willEmitClose) {\n      stream.on('abort', onclose);\n    }\n    if (stream.req) {\n      onrequest();\n    } else {\n      stream.on('request', onrequest);\n    }\n  } else if (writable && !wState) {\n    // legacy streams\n    (stream as Stream).on('end', onlegacyfinish).on('close', onlegacyfinish);\n  }\n\n  // Not all streams will emit 'close' after 'aborted'.\n  if (\n    !willEmitClose &&\n    'aborted' in stream &&\n    typeof stream.aborted === 'boolean'\n  ) {\n    stream.on('aborted', onclose);\n  }\n\n  stream.on('end', onend);\n  stream.on('finish', onfinish);\n  if ((options as EOSOptions).error !== false) {\n    stream.on('error', onerror);\n  }\n  stream.on('close', onclose);\n\n  if (closed) {\n    nextTick(onclose);\n  } else if (wState?.errorEmitted || rState?.errorEmitted) {\n    if (!willEmitClose) {\n      nextTick(onclosed);\n    }\n  } else if (\n    !readable &&\n    (!willEmitClose || isReadable(stream)) &&\n    (writableFinished || isWritable(stream) === false) &&\n    (wState == null || wState.pendingcb === undefined || wState.pendingcb === 0)\n  ) {\n    nextTick(onclosed);\n  } else if (\n    !writable &&\n    (!willEmitClose || isWritable(stream)) &&\n    (readableFinished || isReadable(stream) === false)\n  ) {\n    nextTick(onclosed);\n  } else if (rState && stream.req && stream.aborted) {\n    nextTick(onclosed);\n  }\n\n  const cleanup = (): void => {\n    callback = nop;\n    stream.removeListener('aborted', onclose);\n    stream.removeListener('complete', onfinish);\n    stream.removeListener('abort', onclose);\n    stream.removeListener('request', onrequest);\n    req?.removeListener('finish', onfinish);\n    stream.removeListener('end', onlegacyfinish);\n    stream.removeListener('close', onlegacyfinish);\n    stream.removeListener('finish', onfinish);\n    stream.removeListener('end', onend);\n    stream.removeListener('error', onerror);\n    stream.removeListener('close', onclose);\n  };\n\n  if ((options as EOSOptions).signal && !closed) {\n    const abort = (): void => {\n      // Keep it because cleanup removes it.\n      const endCallback = callback;\n      cleanup();\n      endCallback?.call(\n        stream,\n        new AbortError(undefined, {\n          cause: (options as EOSOptions).signal?.reason,\n        })\n      );\n    };\n    if ((options as EOSOptions).signal?.aborted) {\n      nextTick(abort);\n    } else {\n      const disposable = addAbortListener(\n        (options as EOSOptions).signal,\n        abort\n      );\n      const originalCallback = callback;\n      callback = once((...args: unknown[]): void => {\n        disposable[Symbol.dispose]();\n        originalCallback.apply(stream, args);\n      });\n    }\n  }\n\n  return cleanup;\n}\n\nfunction eosWeb(\n  stream: ReadableStream | WritableStream,\n  options: { signal?: AbortSignal },\n  callback: (...args: unknown[]) => void\n): () => void {\n  let isAborted = false;\n  let abort = nop;\n  if (options.signal) {\n    abort = (): void => {\n      isAborted = true;\n      callback.call(\n        stream,\n        new AbortError(undefined, { cause: options.signal?.reason })\n      );\n    };\n    if (options.signal.aborted) {\n      nextTick(abort);\n    } else {\n      const disposable = addAbortListener(options.signal, abort);\n      const originalCallback = callback;\n      callback = once((...args: unknown[]): void => {\n        disposable[Symbol.dispose]();\n        originalCallback.apply(stream, args);\n      });\n    }\n  }\n  const resolverFn = (...args: unknown[]): void => {\n    if (!isAborted) {\n      nextTick(() => {\n        callback.apply(stream, args);\n      });\n    }\n  };\n  // @ts-expect-error TS7053 Symbols are not defined in types yet.\n  stream[kIsClosedPromise].promise.then(resolverFn, resolverFn);\n  return nop;\n}\n\nexport function finished(\n  stream: Readable | Writable,\n  opts: EOSOptions = {}\n): Promise<void> {\n  let autoCleanup = false;\n  if (opts.cleanup) {\n    validateBoolean(opts.cleanup, 'cleanup');\n    autoCleanup = opts.cleanup;\n  }\n  return new Promise<void>((resolve, reject) => {\n    const cleanup = eos(stream, opts, (err: unknown) => {\n      if (autoCleanup) {\n        cleanup();\n      }\n      if (err) {\n        // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors\n        reject(err);\n      } else {\n        resolve();\n      }\n    });\n  });\n}\n\neos.finished = finished;\n"
  },
  {
    "path": "src/node/internal/streams_legacy.d.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport NodeJSStream from 'node:stream';\nimport EventEmitter from 'node:events';\n\nexport declare class Stream extends NodeJSStream.Stream {\n  static isWritable: typeof NodeJSStream.isWritable;\n  static isDistributed: typeof NodeJSStream.isDistributed;\n  static compose: typeof NodeJSStream.compose;\n  static destroy: typeof NodeJSStream.destroy;\n  static finished: typeof NodeJSStream.finished;\n  static pipeline: typeof NodeJSStream.pipeline;\n  static isDisturbed: typeof NodeJSStream.isDisturbed;\n  static isDestroyed: typeof NodeJSStream.isDestroyed;\n\n  static isReadable(stream: NodeStreamLike): boolean | null;\n  static _isArrayBufferView(value: unknown): value is ArrayBufferView;\n  static _isUint8Array(value: unknown): value is Uint8Array;\n  static _uint8ArrayToBuffer(value: Uint8Array): NodeJS.Buffer;\n\n  static Stream: typeof Stream;\n\n  aborted?: boolean;\n  readable: boolean;\n  writable: boolean;\n  destroyed: boolean;\n  req: EventEmitter;\n\n  _writableState: {\n    errorEmitted: boolean;\n    pendingcb?: number;\n  };\n  _readableState: {\n    errorEmitted: boolean;\n  };\n}\n"
  },
  {
    "path": "src/node/internal/streams_legacy.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { EventEmitter } from 'node-internal:events';\n\nimport { Buffer } from 'node-internal:internal_buffer';\n\nexport function Stream(opts) {\n  EventEmitter.call(this, opts || {});\n}\n\nObject.setPrototypeOf(Stream.prototype, EventEmitter.prototype);\nObject.setPrototypeOf(Stream, EventEmitter);\n\nStream.prototype.pipe = function (dest, options) {\n  const source = this; // eslint-disable-line @typescript-eslint/no-this-alias\n  function ondata(chunk) {\n    if (dest.writable && dest.write(chunk) === false && source.pause) {\n      source.pause();\n    }\n  }\n  source.on('data', ondata);\n  function ondrain() {\n    if (source.readable && source.resume) {\n      source.resume();\n    }\n  }\n  dest.on('drain', ondrain);\n\n  // If the 'end' option is not supplied, dest.end() will be called when\n  // source gets the 'end' or 'close' events.  Only dest.end() once.\n  if (!dest._isStdio && (!options || options.end !== false)) {\n    source.on('end', onend);\n    source.on('close', onclose);\n  }\n  let didOnEnd = false;\n  function onend() {\n    if (didOnEnd) return;\n    didOnEnd = true;\n    dest.end();\n  }\n  function onclose() {\n    if (didOnEnd) return;\n    didOnEnd = true;\n    if (typeof dest.destroy === 'function') dest.destroy();\n  }\n\n  // Don't leave dangling pipes when there are errors.\n  function onerror(er) {\n    cleanup();\n    if (EventEmitter.listenerCount(this, 'error') === 0) {\n      this.emit('error', er);\n    }\n  }\n  source.prependListener('error', onerror);\n  dest.prependListener('error', onerror);\n\n  // Remove all the event listeners that were added.\n  function cleanup() {\n    source.removeListener('data', ondata);\n    dest.removeListener('drain', ondrain);\n    source.removeListener('end', onend);\n    source.removeListener('close', onclose);\n    source.removeListener('error', onerror);\n    dest.removeListener('error', onerror);\n    source.removeListener('end', cleanup);\n    source.removeListener('close', cleanup);\n    dest.removeListener('close', cleanup);\n  }\n  source.on('end', cleanup);\n  source.on('close', cleanup);\n  dest.on('close', cleanup);\n  dest.emit('pipe', source);\n\n  // Allow for unix-like usage: A.pipe(B).pipe(C)\n  return dest;\n};\n\n// Backwards-compat with node 0.4.x\nStream.Stream = Stream;\nStream._isUint8Array = function isUint8Array(value) {\n  return value instanceof Uint8Array;\n};\nStream._isArrayBufferView = function isArrayBufferView(value) {\n  return ArrayBuffer.isView(value);\n};\nStream._uint8ArrayToBuffer = function _uint8ArrayToBuffer(chunk) {\n  return Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);\n};\n"
  },
  {
    "path": "src/node/internal/streams_pipeline.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport { pipeline } from 'node:stream';\n\nexport function pipelineImpl(\n  streams: unknown,\n  callback: (err: Error | null, value?: unknown) => void,\n  opts: { signal?: AbortSignal | undefined; end?: boolean | undefined }\n): unknown;\n"
  },
  {
    "path": "src/node/internal/streams_pipeline.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n/* TODO: the following is adopted code, enabling linting one day */\n/* eslint-disable */\n\nimport {\n  isIterable,\n  isReadable,\n  isReadableNodeStream,\n  isNodeStream,\n} from 'node-internal:streams_util';\nimport { eos } from 'node-internal:streams_end_of_stream';\nimport { destroyer as destroyerImpl } from 'node-internal:streams_destroy';\nimport { once } from 'node-internal:internal_http_util';\n\nimport { nextTick } from 'node-internal:internal_process';\nimport { PassThrough } from 'node-internal:streams_transform';\nimport { Duplex } from 'node-internal:streams_duplex';\nimport { Readable, from } from 'node-internal:streams_readable';\nimport {\n  aggregateTwoErrors,\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_RETURN_VALUE,\n  ERR_MISSING_ARGS,\n  ERR_STREAM_DESTROYED,\n  ERR_STREAM_PREMATURE_CLOSE,\n  AbortError,\n} from 'node-internal:internal_errors';\nimport {\n  validateFunction,\n  validateAbortSignal,\n} from 'node-internal:validators';\n\nfunction destroyer(stream, reading, writing) {\n  let finished = false;\n  stream.on('close', () => {\n    finished = true;\n  });\n  const cleanup = eos(\n    stream,\n    {\n      readable: reading,\n      writable: writing,\n    },\n    (err) => {\n      finished = !err;\n    }\n  );\n  return {\n    destroy: (err) => {\n      if (finished) return;\n      finished = true;\n      destroyerImpl(stream, err || new ERR_STREAM_DESTROYED('pipe'));\n    },\n    cleanup,\n  };\n}\n\nfunction popCallback(streams) {\n  // Streams should never be an empty array. It should always contain at least\n  // a single stream. Therefore optimize for the average case instead of\n  // checking for length === 0 as well.\n  validateFunction(streams[streams.length - 1], 'streams[stream.length - 1]');\n  return streams.pop();\n}\n\nfunction makeAsyncIterable(val) {\n  if (isIterable(val)) {\n    return val;\n  } else if (isReadableNodeStream(val)) {\n    // Legacy streams are not Iterable.\n    return fromReadable(val);\n  }\n  throw new ERR_INVALID_ARG_TYPE(\n    'val',\n    ['Readable', 'Iterable', 'AsyncIterable'],\n    val\n  );\n}\n\nasync function* fromReadable(val) {\n  yield* Readable.prototype[Symbol.asyncIterator].call(val);\n}\n\nasync function pump(iterable, writable, finish, { end }) {\n  let error;\n  let onresolve = null;\n  const resume = (err) => {\n    if (err) {\n      error = err;\n    }\n    if (onresolve) {\n      const callback = onresolve;\n      onresolve = null;\n      callback();\n    }\n  };\n  const wait = () => {\n    return new Promise((resolve, reject) => {\n      if (error) {\n        reject(error);\n      } else {\n        onresolve = () => {\n          if (error) {\n            reject(error);\n          } else {\n            resolve();\n          }\n        };\n      }\n    });\n  };\n  writable.on('drain', resume);\n  const cleanup = eos(\n    writable,\n    {\n      readable: false,\n    },\n    resume\n  );\n  try {\n    if (writable.writableNeedDrain) {\n      await wait();\n    }\n    for await (const chunk of iterable) {\n      if (!writable.write(chunk)) {\n        await wait();\n      }\n    }\n    if (end) {\n      writable.end();\n    }\n    await wait();\n    finish();\n  } catch (err) {\n    finish(error !== err ? aggregateTwoErrors(error, err) : err);\n  } finally {\n    cleanup();\n    writable.off('drain', resume);\n  }\n}\n\nexport function pipeline(...streams) {\n  return pipelineImpl(streams, once(popCallback(streams)));\n}\n\nexport function pipelineImpl(streams, callback, opts) {\n  if (streams.length === 1 && Array.isArray(streams[0])) {\n    streams = streams[0];\n  }\n  if (streams.length < 2) {\n    throw new ERR_MISSING_ARGS('streams');\n  }\n  const ac = new AbortController();\n  const signal = ac.signal;\n  const outerSignal = opts?.signal;\n\n  // Need to cleanup event listeners if last stream is readable\n  // https://github.com/nodejs/node/issues/35452\n  const lastStreamCleanup = [];\n  validateAbortSignal(outerSignal, 'options.signal');\n  function abort() {\n    finishImpl(new AbortError());\n  }\n  outerSignal === null || outerSignal === undefined\n    ? undefined\n    : outerSignal.addEventListener('abort', abort);\n  let error;\n  let value;\n  const destroys = [];\n  let finishCount = 0;\n  function finish(err) {\n    finishImpl(err, --finishCount === 0);\n  }\n  function finishImpl(err, final) {\n    if (err && (!error || error.code === 'ERR_STREAM_PREMATURE_CLOSE')) {\n      error = err;\n    }\n    if (!error && !final) {\n      return;\n    }\n    while (destroys.length) {\n      destroys.shift()(error);\n    }\n    outerSignal === null || outerSignal === undefined\n      ? undefined\n      : outerSignal.removeEventListener('abort', abort);\n    ac.abort();\n    if (final) {\n      if (!error) {\n        lastStreamCleanup.forEach((fn) => fn());\n      }\n      nextTick(callback, error, value);\n    }\n  }\n  let ret;\n  for (let i = 0; i < streams.length; i++) {\n    const stream = streams[i];\n    const reading = i < streams.length - 1;\n    const writing = i > 0;\n    const end =\n      reading ||\n      (opts === null || opts === undefined ? undefined : opts.end) !== false;\n    const isLastStream = i === streams.length - 1;\n    if (isNodeStream(stream)) {\n      if (end) {\n        const { destroy, cleanup } = destroyer(stream, reading, writing);\n        destroys.push(destroy);\n        if (isReadable(stream) && isLastStream) {\n          lastStreamCleanup.push(cleanup);\n        }\n      }\n\n      // Catch stream errors that occur after pipe/pump has completed.\n      function onError(err) {\n        if (\n          err &&\n          err.name !== 'AbortError' &&\n          err.code !== 'ERR_STREAM_PREMATURE_CLOSE'\n        ) {\n          finish(err);\n        }\n      }\n      stream.on('error', onError);\n      if (isReadable(stream) && isLastStream) {\n        lastStreamCleanup.push(() => {\n          stream.removeListener('error', onError);\n        });\n      }\n    }\n    if (i === 0) {\n      if (typeof stream === 'function') {\n        ret = stream({\n          signal,\n        });\n        if (!isIterable(ret)) {\n          throw new ERR_INVALID_RETURN_VALUE(\n            'Iterable, AsyncIterable or Stream',\n            'source',\n            ret\n          );\n        }\n      } else if (isIterable(stream) || isReadableNodeStream(stream)) {\n        ret = stream;\n      } else {\n        ret = from(Duplex, stream);\n      }\n    } else if (typeof stream === 'function') {\n      ret = makeAsyncIterable(ret);\n      ret = stream(ret, {\n        signal,\n      });\n      if (reading) {\n        if (!isIterable(ret, true)) {\n          throw new ERR_INVALID_RETURN_VALUE(\n            'AsyncIterable',\n            `transform[${i - 1}]`,\n            ret\n          );\n        }\n      } else {\n        let _ret;\n        // If the last argument to pipeline is not a stream\n        // we must create a proxy stream so that pipeline(...)\n        // always returns a stream which can be further\n        // composed through `.pipe(stream)`.\n\n        const pt = new PassThrough({\n          objectMode: true,\n        });\n\n        // Handle Promises/A+ spec, `then` could be a getter that throws on\n        // second use.\n        const then =\n          (_ret = ret) === null || _ret === undefined ? undefined : _ret.then;\n        if (typeof then === 'function') {\n          finishCount++;\n          then.call(\n            ret,\n            (val) => {\n              value = val;\n              if (val != null) {\n                pt.write(val);\n              }\n              if (end) {\n                pt.end();\n              }\n              nextTick(finish);\n            },\n            (err) => {\n              pt.destroy(err);\n              nextTick(finish, err);\n            }\n          );\n        } else if (isIterable(ret, true)) {\n          finishCount++;\n          pump(ret, pt, finish, {\n            end,\n          });\n        } else {\n          throw new ERR_INVALID_RETURN_VALUE(\n            'AsyncIterable or Promise',\n            'destination',\n            ret\n          );\n        }\n        ret = pt;\n        const { destroy, cleanup } = destroyer(ret, false, true);\n        destroys.push(destroy);\n        if (isLastStream) {\n          lastStreamCleanup.push(cleanup);\n        }\n      }\n    } else if (isNodeStream(stream)) {\n      if (isReadableNodeStream(ret)) {\n        finishCount += 2;\n        const cleanup = pipe(ret, stream, finish, {\n          end,\n        });\n        if (isReadable(stream) && isLastStream) {\n          lastStreamCleanup.push(cleanup);\n        }\n      } else if (isIterable(ret)) {\n        finishCount++;\n        pump(ret, stream, finish, {\n          end,\n        });\n      } else {\n        throw new ERR_INVALID_ARG_TYPE(\n          'val',\n          ['Readable', 'Iterable', 'AsyncIterable'],\n          ret\n        );\n      }\n      ret = stream;\n    } else {\n      ret = from(Duplex, stream);\n    }\n  }\n  if (\n    (signal !== null && signal !== undefined && signal.aborted) ||\n    (outerSignal !== null && outerSignal !== undefined && outerSignal.aborted)\n  ) {\n    nextTick(abort);\n  }\n  return ret;\n}\n\nexport function pipe(src, dst, finish, { end }) {\n  let ended = false;\n  dst.on('close', () => {\n    if (!ended) {\n      // Finish if the destination closes before the source has completed.\n      finish(new ERR_STREAM_PREMATURE_CLOSE());\n    }\n  });\n  src.pipe(dst, {\n    end,\n  });\n  if (end) {\n    // Compat. Before node v10.12.0 stdio used to throw an error so\n    // pipe() did/does not end() stdio destinations.\n    // Now they allow it but \"secretly\" don't close the underlying fd.\n    src.once('end', () => {\n      ended = true;\n      dst.end();\n    });\n  } else {\n    finish();\n  }\n  eos(\n    src,\n    {\n      readable: true,\n      writable: false,\n    },\n    (err) => {\n      const rState = src._readableState;\n      if (\n        err &&\n        err.code === 'ERR_STREAM_PREMATURE_CLOSE' &&\n        rState &&\n        rState.ended &&\n        !rState.errored &&\n        !rState.errorEmitted\n      ) {\n        // Some readable streams will emit 'close' before 'end'. However, since\n        // this is on the readable side 'end' should still be emitted if the\n        // stream has been ended and no error emitted. This should be allowed in\n        // favor of backwards compatibility. Since the stream is piped to a\n        // destination this should not result in any observable difference.\n        // We don't need to check if this is a writable premature close since\n        // eos will only fail with premature close on the reading side for\n        // duplex streams.\n        src.once('end', finish).once('error', finish);\n      } else {\n        finish(err);\n      }\n    }\n  );\n  return eos(\n    dst,\n    {\n      readable: false,\n      writable: true,\n    },\n    finish\n  );\n}\n"
  },
  {
    "path": "src/node/internal/streams_promises.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { isIterable, isNodeStream } from 'node-internal:streams_util';\nimport { finished } from 'node-internal:streams_end_of_stream';\n\nimport { pipelineImpl as pl } from 'node-internal:streams_pipeline';\n\nexport { finished };\n\nexport function pipeline(...streams: unknown[]): Promise<unknown> {\n  return new Promise((resolve, reject) => {\n    let signal: AbortSignal | undefined;\n    let end: boolean | undefined;\n    const lastArg = streams[streams.length - 1];\n    if (\n      lastArg &&\n      typeof lastArg === 'object' &&\n      !isNodeStream(lastArg) &&\n      !isIterable(lastArg)\n    ) {\n      const options = streams.pop() as { signal?: AbortSignal; end?: boolean };\n      signal = options.signal;\n      end = options.end;\n    }\n    pl(\n      streams,\n      (err: Error | null, value: unknown) => {\n        if (err) {\n          reject(err);\n        } else {\n          resolve(value);\n        }\n      },\n      {\n        signal,\n        end,\n      }\n    );\n  });\n}\n\nexport const promises = {\n  pipeline,\n  finished,\n};\n"
  },
  {
    "path": "src/node/internal/streams_readable.d.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { Readable as _Readable, Duplex } from 'node:stream';\nimport type EventEmitter from 'node:events';\nimport {\n  kState,\n  kIsWritable,\n  kIsReadable,\n  kIsClosedPromise,\n  kIsDestroyed,\n} from 'node-internal:streams_util';\n\nexport declare class ReadableState {\n  readable?: boolean;\n  ended?: boolean;\n  endEmitted?: boolean;\n  reading?: boolean;\n  constructed?: boolean;\n  closed?: boolean;\n  closeEmitted?: boolean;\n  destroyed?: boolean;\n  errored?: Error | null;\n  errorEmitted?: boolean;\n  length?: number;\n  highWaterMark?: number;\n  autoDestroy?: boolean;\n  emitClose?: boolean;\n  readingMore?: boolean;\n  readableDidRead?: boolean;\n  readableAborted?: boolean;\n  [kState]: number;\n}\n\nexport declare class Readable extends _Readable {\n  _readableState: ReadableState;\n  _writableState: undefined;\n  _closed: boolean;\n  errored?: Error | null;\n  writableErrored: boolean;\n  readableErrored: boolean;\n  writableFinished: undefined;\n  readableFinished?: boolean;\n  readable: boolean;\n  writable?: boolean;\n  aborted?: boolean;\n  req?: EventEmitter;\n\n  writableEnded?: boolean;\n\n  [kIsWritable]: boolean;\n  [kIsReadable]: boolean;\n  [kIsClosedPromise]: Promise<unknown>;\n  [kIsDestroyed]: boolean;\n}\n\nexport const from: typeof Duplex.from;\nexport const fromWeb: typeof Duplex.fromWeb;\nexport const toWeb: typeof Duplex.toWeb;\nexport const wrap: typeof Duplex.wrap;\n"
  },
  {
    "path": "src/node/internal/streams_readable.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport {\n  kState,\n  // bitfields\n  kObjectMode,\n  kErrorEmitted,\n  kAutoDestroy,\n  kEmitClose,\n  kDestroyed,\n  kClosed,\n  kCloseEmitted,\n  kErrored,\n  kConstructed,\n  kOnConstructed,\n  isDestroyed,\n  isReadable,\n  isReadableStream,\n  handleKnownInternalErrors,\n} from 'node-internal:streams_util';\nimport { nextTick } from 'node-internal:internal_process';\nimport {\n  destroy,\n  undestroy,\n  errorOrDestroy,\n  destroyer,\n  construct,\n} from 'node-internal:streams_destroy';\nimport { eos, finished, nop } from 'node-internal:streams_end_of_stream';\nimport {\n  getHighWaterMark,\n  getDefaultHighWaterMark,\n} from 'node-internal:streams_state';\nimport { addAbortSignal } from 'node-internal:streams_add_abort_signal';\nimport { EventEmitter } from 'node-internal:events';\nimport { Stream } from 'node-internal:streams_legacy';\nimport { Buffer } from 'node-internal:internal_buffer';\n\nimport {\n  AbortError,\n  aggregateTwoErrors,\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_ARG_VALUE,\n  ERR_METHOD_NOT_IMPLEMENTED,\n  ERR_MISSING_ARGS,\n  ERR_OUT_OF_RANGE,\n  ERR_STREAM_PUSH_AFTER_EOF,\n  ERR_STREAM_UNSHIFT_AFTER_END_EVENT,\n  ERR_STREAM_NULL_VALUES,\n  ERR_UNKNOWN_ENCODING,\n} from 'node-internal:internal_errors';\n\nimport {\n  validateObject,\n  validateAbortSignal,\n  validateBoolean,\n  validateInteger,\n} from 'node-internal:validators';\n\nimport { StringDecoder } from 'node-internal:internal_stringdecoder';\n\nconst streamsNodejsV24Compat =\n  Cloudflare.compatibilityFlags.enable_streams_nodejs_v24_compat; // eslint-disable-line no-undef\n\nconst kErroredValue = Symbol('kErroredValue');\nconst kDefaultEncodingValue = Symbol('kDefaultEncodingValue');\nconst kDecoderValue = Symbol('kDecoderValue');\nconst kEncodingValue = Symbol('kEncodingValue');\n\n// Bitfield flag constants for ReadableState. Each constant uses left-shift (<<) to set a specific\n// bit position, allowing multiple boolean flags to be stored efficiently in a single integer (kState).\n// For example, `1 << 9` creates a value with only bit 9 set (value: 512).\nconst kEnded = 1 << 9;\nconst kEndEmitted = 1 << 10;\nconst kReading = 1 << 11;\nconst kSync = 1 << 12;\nconst kNeedReadable = 1 << 13;\nconst kEmittedReadable = 1 << 14;\nconst kReadableListening = 1 << 15;\nconst kResumeScheduled = 1 << 16;\nconst kMultiAwaitDrain = 1 << 17;\nconst kReadingMore = 1 << 18;\nconst kDataEmitted = 1 << 19;\nconst kDefaultUTF8Encoding = 1 << 20;\nconst kDecoder = 1 << 21;\nconst kEncoding = 1 << 22;\nconst kHasFlowing = 1 << 23;\nconst kFlowing = 1 << 24;\nconst kHasPaused = 1 << 25;\nconst kPaused = 1 << 26;\nconst kDataListening = 1 << 27;\n\n// ======================================================================================\n// ReadableState\n\n// TODO(benjamingr) it is likely slower to do it this way than with free functions\nfunction makeBitMapDescriptor(bit) {\n  return {\n    enumerable: false,\n    get() {\n      return (this[kState] & bit) !== 0;\n    },\n    set(value) {\n      if (value) this[kState] |= bit;\n      else this[kState] &= ~bit;\n    },\n  };\n}\nObject.defineProperties(ReadableState.prototype, {\n  objectMode: makeBitMapDescriptor(kObjectMode),\n  ended: makeBitMapDescriptor(kEnded),\n  endEmitted: makeBitMapDescriptor(kEndEmitted),\n  reading: makeBitMapDescriptor(kReading),\n  // Stream is still being constructed and cannot be\n  // destroyed until construction finished or failed.\n  // Async construction is opt in, therefore we start as\n  // constructed.\n  constructed: makeBitMapDescriptor(kConstructed),\n  // A flag to be able to tell if the event 'readable'/'data' is emitted\n  // immediately, or on a later tick.  We set this to true at first, because\n  // any actions that shouldn't happen until \"later\" should generally also\n  // not happen before the first read call.\n  sync: makeBitMapDescriptor(kSync),\n  // Whenever we return null, then we set a flag to say\n  // that we're awaiting a 'readable' event emission.\n  needReadable: makeBitMapDescriptor(kNeedReadable),\n  emittedReadable: makeBitMapDescriptor(kEmittedReadable),\n  readableListening: makeBitMapDescriptor(kReadableListening),\n  resumeScheduled: makeBitMapDescriptor(kResumeScheduled),\n  // True if the error was already emitted and should not be thrown again.\n  errorEmitted: makeBitMapDescriptor(kErrorEmitted),\n  emitClose: makeBitMapDescriptor(kEmitClose),\n  autoDestroy: makeBitMapDescriptor(kAutoDestroy),\n  // Has it been destroyed.\n  destroyed: makeBitMapDescriptor(kDestroyed),\n  // Indicates whether the stream has finished destroying.\n  closed: makeBitMapDescriptor(kClosed),\n  // True if close has been emitted or would have been emitted\n  // depending on emitClose.\n  closeEmitted: makeBitMapDescriptor(kCloseEmitted),\n  multiAwaitDrain: makeBitMapDescriptor(kMultiAwaitDrain),\n  // If true, a maybeReadMore has been scheduled.\n  readingMore: makeBitMapDescriptor(kReadingMore),\n  dataEmitted: makeBitMapDescriptor(kDataEmitted),\n\n  // Indicates whether the stream has errored. When true no further\n  // _read calls, 'data' or 'readable' events should occur. This is needed\n  // since when autoDestroy is disabled we need a way to tell whether the\n  // stream has failed.\n  errored: {\n    __proto__: null,\n    enumerable: false,\n    get() {\n      return (this[kState] & kErrored) !== 0 ? this[kErroredValue] : null;\n    },\n    set(value) {\n      if (value) {\n        this[kErroredValue] = value;\n        this[kState] |= kErrored;\n      } else {\n        this[kState] &= ~kErrored;\n      }\n    },\n  },\n\n  defaultEncoding: {\n    __proto__: null,\n    enumerable: false,\n    get() {\n      return (this[kState] & kDefaultUTF8Encoding) !== 0\n        ? 'utf8'\n        : this[kDefaultEncodingValue];\n    },\n    set(value) {\n      if (value === 'utf8' || value === 'utf-8') {\n        this[kState] |= kDefaultUTF8Encoding;\n      } else {\n        this[kState] &= ~kDefaultUTF8Encoding;\n        this[kDefaultEncodingValue] = value;\n      }\n    },\n  },\n\n  decoder: {\n    __proto__: null,\n    enumerable: false,\n    get() {\n      return (this[kState] & kDecoder) !== 0 ? this[kDecoderValue] : null;\n    },\n    set(value) {\n      if (value) {\n        this[kDecoderValue] = value;\n        this[kState] |= kDecoder;\n      } else {\n        this[kState] &= ~kDecoder;\n      }\n    },\n  },\n\n  encoding: {\n    __proto__: null,\n    enumerable: false,\n    get() {\n      return (this[kState] & kEncoding) !== 0 ? this[kEncodingValue] : null;\n    },\n    set(value) {\n      if (value) {\n        this[kEncodingValue] = value;\n        this[kState] |= kEncoding;\n      } else {\n        this[kState] &= ~kEncoding;\n      }\n    },\n  },\n\n  flowing: {\n    __proto__: null,\n    enumerable: false,\n    get() {\n      return (this[kState] & kHasFlowing) !== 0\n        ? (this[kState] & kFlowing) !== 0\n        : null;\n    },\n    set(value) {\n      if (value == null) {\n        this[kState] &= ~(kHasFlowing | kFlowing);\n      } else if (value) {\n        this[kState] |= kHasFlowing | kFlowing;\n      } else {\n        this[kState] |= kHasFlowing;\n        this[kState] &= ~kFlowing;\n      }\n    },\n  },\n});\n\nexport function ReadableState(options, _stream, isDuplex) {\n  // Bit map field to store ReadableState more efficiently with 1 bit per field\n  // instead of a V8 slot per field.\n  this[kState] = kEmitClose | kAutoDestroy | kConstructed | kSync;\n\n  // Object stream flag. Used to make read(n) ignore n and to\n  // make all the buffer merging and length checks go away.\n  if (options?.objectMode) this[kState] |= kObjectMode;\n\n  if (isDuplex && options?.readableObjectMode) this[kState] |= kObjectMode;\n\n  // The point at which it stops calling _read() to fill the buffer\n  // Note: 0 is a valid value, means \"don't call _read preemptively ever\"\n  this.highWaterMark = options\n    ? getHighWaterMark(this, options, 'readableHighWaterMark', isDuplex)\n    : getDefaultHighWaterMark(false);\n\n  this.buffer = [];\n  this.bufferIndex = 0;\n  this.length = 0;\n  this.pipes = [];\n\n  // Should close be emitted on destroy. Defaults to true.\n  if (options && options.emitClose === false) this[kState] &= ~kEmitClose;\n\n  // Should .destroy() be called after 'end' (and potentially 'finish').\n  if (options && options.autoDestroy === false) this[kState] &= ~kAutoDestroy;\n\n  // Crypto is kind of old and crusty.  Historically, its default string\n  // encoding is 'binary' so we have to make this configurable.\n  // Everything else in the universe uses 'utf8', though.\n  const defaultEncoding = options?.defaultEncoding;\n  if (\n    defaultEncoding == null ||\n    defaultEncoding === 'utf8' ||\n    defaultEncoding === 'utf-8'\n  ) {\n    this[kState] |= kDefaultUTF8Encoding;\n  } else if (Buffer.isEncoding(defaultEncoding)) {\n    this.defaultEncoding = defaultEncoding;\n  } else if (streamsNodejsV24Compat) {\n    // This is a semver-major change. Ref: https://github.com/nodejs/node/pull/46430\n    throw new ERR_UNKNOWN_ENCODING(defaultEncoding);\n  } else {\n    this.defaultEncoding = defaultEncoding;\n  }\n\n  // Ref the piped dest which we need a drain event on it\n  // type: null | Writable | Set<Writable>.\n  this.awaitDrainWriters = null;\n\n  if (options?.encoding) {\n    this.decoder = new StringDecoder(options.encoding);\n    this.encoding = options.encoding;\n  }\n}\n\nReadableState.prototype[kOnConstructed] = function onConstructed(stream) {\n  if ((this[kState] & kNeedReadable) !== 0) {\n    maybeReadMore(stream, this);\n  }\n};\n\n// ======================================================================================\n// Readable\n\nReadable.ReadableState = ReadableState;\n\nObject.setPrototypeOf(Readable.prototype, Stream.prototype);\nObject.setPrototypeOf(Readable, Stream);\n\nexport function Readable(options) {\n  if (!(this instanceof Readable)) return new Readable(options);\n\n  this._events ??= {\n    close: undefined,\n    error: undefined,\n    data: undefined,\n    end: undefined,\n    readable: undefined,\n    // Skip uncommon events...\n    // pause: undefined,\n    // resume: undefined,\n    // pipe: undefined,\n    // unpipe: undefined,\n    // [destroyImpl.kConstruct]: undefined,\n    // [destroyImpl.kDestroy]: undefined,\n  };\n\n  this._readableState = new ReadableState(options, this, false);\n\n  if (options) {\n    if (typeof options.read === 'function') this._read = options.read;\n\n    if (typeof options.destroy === 'function') this._destroy = options.destroy;\n\n    if (typeof options.construct === 'function')\n      this._construct = options.construct;\n\n    if (options.signal) addAbortSignal(options.signal, this);\n  }\n\n  Stream.call(this, options);\n\n  if (this._construct != null) {\n    construct(this, () => {\n      this._readableState[kOnConstructed](this);\n    });\n  }\n}\nReadable.prototype.destroy = destroy;\nReadable.prototype._undestroy = undestroy;\nReadable.prototype._destroy = function (err, cb) {\n  if (cb) cb(err);\n};\n\nReadable.prototype[EventEmitter.captureRejectionSymbol] = function (err) {\n  this.destroy(err);\n};\n\nReadable.prototype[Symbol.asyncDispose] = async function () {\n  let error;\n  if (!this.destroyed) {\n    error = this.readableEnded ? null : new AbortError();\n    this.destroy(error);\n  }\n  await new Promise((resolve, reject) =>\n    eos(this, (err) => (err && err !== error ? reject(err) : resolve(null)))\n  );\n};\n\n// Manually shove something into the read() buffer.\n// This returns true if the highWaterMark has not been hit yet,\n// similar to how Writable.write() returns true if you should\n// write() some more.\nReadable.prototype.push = function (chunk, encoding) {\n  const state = this._readableState;\n  return (state[kState] & kObjectMode) === 0\n    ? readableAddChunkPushByteMode(this, state, chunk, encoding)\n    : readableAddChunkPushObjectMode(this, state, chunk, encoding);\n};\n\n// Unshift should *always* be something directly out of read().\nReadable.prototype.unshift = function (chunk, encoding) {\n  const state = this._readableState;\n  return (state[kState] & kObjectMode) === 0\n    ? readableAddChunkUnshiftByteMode(this, state, chunk, encoding)\n    : readableAddChunkUnshiftObjectMode(this, state, chunk);\n};\n\nfunction readableAddChunkUnshiftByteMode(stream, state, chunk, encoding) {\n  if (chunk === null) {\n    state[kState] &= ~kReading;\n    onEofChunk(stream, state);\n\n    return false;\n  }\n\n  if (typeof chunk === 'string') {\n    encoding ||= state.defaultEncoding;\n    if (state.encoding !== encoding) {\n      if (state.encoding) {\n        // When unshifting, if state.encoding is set, we have to save\n        // the string in the BufferList with the state encoding.\n        chunk = Buffer.from(chunk, encoding).toString(state.encoding);\n      } else {\n        chunk = Buffer.from(chunk, encoding);\n      }\n    }\n  } else if (Stream._isArrayBufferView(chunk)) {\n    chunk = Stream._uint8ArrayToBuffer(chunk);\n  } else if (chunk !== undefined && !(chunk instanceof Buffer)) {\n    errorOrDestroy(\n      stream,\n      new ERR_INVALID_ARG_TYPE(\n        'chunk',\n        ['string', 'Buffer', 'TypedArray', 'DataView'],\n        chunk\n      )\n    );\n    return false;\n  }\n\n  if (!(chunk && chunk.length > 0)) {\n    return canPushMore(state);\n  }\n\n  return readableAddChunkUnshiftValue(stream, state, chunk);\n}\n\nfunction readableAddChunkUnshiftObjectMode(stream, state, chunk) {\n  if (chunk === null) {\n    state[kState] &= ~kReading;\n    onEofChunk(stream, state);\n\n    return false;\n  }\n\n  return readableAddChunkUnshiftValue(stream, state, chunk);\n}\n\nfunction readableAddChunkUnshiftValue(stream, state, chunk) {\n  if ((state[kState] & kEndEmitted) !== 0)\n    errorOrDestroy(stream, new ERR_STREAM_UNSHIFT_AFTER_END_EVENT());\n  else if ((state[kState] & (kDestroyed | kErrored)) !== 0) return false;\n  else addChunk(stream, state, chunk, true);\n\n  return canPushMore(state);\n}\n\nfunction readableAddChunkPushByteMode(stream, state, chunk, encoding) {\n  if (chunk === null) {\n    state[kState] &= ~kReading;\n    onEofChunk(stream, state);\n    return false;\n  }\n\n  if (typeof chunk === 'string') {\n    encoding ||= state.defaultEncoding;\n    if (state.encoding !== encoding) {\n      chunk = Buffer.from(chunk, encoding);\n      encoding = '';\n    }\n  } else if (chunk instanceof Buffer) {\n    encoding = '';\n  } else if (Stream._isArrayBufferView(chunk)) {\n    chunk = Stream._uint8ArrayToBuffer(chunk);\n    encoding = '';\n  } else if (chunk !== undefined) {\n    errorOrDestroy(\n      stream,\n      new ERR_INVALID_ARG_TYPE(\n        'chunk',\n        ['string', 'Buffer', 'TypedArray', 'DataView'],\n        chunk\n      )\n    );\n    return false;\n  }\n\n  if (!chunk || chunk.length <= 0) {\n    state[kState] &= ~kReading;\n    maybeReadMore(stream, state);\n\n    return canPushMore(state);\n  }\n\n  if ((state[kState] & kEnded) !== 0) {\n    errorOrDestroy(stream, new ERR_STREAM_PUSH_AFTER_EOF());\n    return false;\n  }\n\n  if ((state[kState] & (kDestroyed | kErrored)) !== 0) {\n    return false;\n  }\n\n  state[kState] &= ~kReading;\n  if ((state[kState] & kDecoder) !== 0 && !encoding) {\n    chunk = state[kDecoderValue].write(chunk);\n    if (chunk.length === 0) {\n      maybeReadMore(stream, state);\n      return canPushMore(state);\n    }\n  }\n\n  addChunk(stream, state, chunk, false);\n  return canPushMore(state);\n}\n\nfunction readableAddChunkPushObjectMode(stream, state, chunk, encoding) {\n  if (chunk === null) {\n    state[kState] &= ~kReading;\n    onEofChunk(stream, state);\n    return false;\n  }\n\n  if ((state[kState] & kEnded) !== 0) {\n    errorOrDestroy(stream, new ERR_STREAM_PUSH_AFTER_EOF());\n    return false;\n  }\n\n  if ((state[kState] & (kDestroyed | kErrored)) !== 0) {\n    return false;\n  }\n\n  state[kState] &= ~kReading;\n\n  if ((state[kState] & kDecoder) !== 0 && !encoding) {\n    chunk = state[kDecoderValue].write(chunk);\n  }\n\n  addChunk(stream, state, chunk, false);\n  return canPushMore(state);\n}\n\nfunction canPushMore(state) {\n  // We can push more data if we are below the highWaterMark.\n  // Also, if we have no data yet, we can stand some more bytes.\n  // This is to work around cases where hwm=0, such as the repl.\n  return (\n    (state[kState] & kEnded) === 0 &&\n    (state.length < state.highWaterMark || state.length === 0)\n  );\n}\n\nfunction addChunk(stream, state, chunk, addToFront) {\n  if (\n    (state[kState] & (kFlowing | kSync | kDataListening)) ===\n      (kFlowing | kDataListening) &&\n    state.length === 0\n  ) {\n    // Use the guard to avoid creating `Set()` repeatedly\n    // when we have multiple pipes.\n    if ((state[kState] & kMultiAwaitDrain) !== 0) {\n      state.awaitDrainWriters.clear();\n    } else {\n      state.awaitDrainWriters = null;\n    }\n\n    state[kState] |= kDataEmitted;\n    stream.emit('data', chunk);\n  } else {\n    // Update the buffer info.\n    state.length += (state[kState] & kObjectMode) !== 0 ? 1 : chunk.length;\n    if (addToFront) {\n      if (state.bufferIndex > 0) {\n        state.buffer[--state.bufferIndex] = chunk;\n      } else {\n        state.buffer.unshift(chunk); // Slow path\n      }\n    } else {\n      state.buffer.push(chunk);\n    }\n\n    if ((state[kState] & kNeedReadable) !== 0) emitReadable(stream);\n  }\n  maybeReadMore(stream, state);\n}\n\nReadable.prototype.isPaused = function () {\n  const state = this._readableState;\n  return (\n    (state[kState] & kPaused) !== 0 ||\n    (state[kState] & (kHasFlowing | kFlowing)) === kHasFlowing\n  );\n};\n\n// Backwards compatibility.\nReadable.prototype.setEncoding = function (enc) {\n  const state = this._readableState;\n\n  const decoder = new StringDecoder(enc);\n  state.decoder = decoder;\n  // If setEncoding(null), decoder.encoding equals utf8.\n  state.encoding = state.decoder.encoding;\n\n  // Iterate over current buffer to convert already stored Buffers:\n  let content = '';\n  for (const data of state.buffer.slice(state.bufferIndex)) {\n    content += decoder.write(data);\n  }\n  state.buffer.length = 0;\n  state.bufferIndex = 0;\n\n  if (content !== '') state.buffer.push(content);\n  state.length = content.length;\n  return this;\n};\n\n// Don't raise the hwm > 1GB.\nconst MAX_HWM = 0x40000000;\nfunction computeNewHighWaterMark(n) {\n  if (n > MAX_HWM) {\n    throw new ERR_OUT_OF_RANGE('size', '<= 1GiB', n);\n  } else {\n    // Get the next highest power of 2 to prevent increasing hwm excessively in\n    // tiny amounts.\n    n--;\n    n |= n >>> 1;\n    n |= n >>> 2;\n    n |= n >>> 4;\n    n |= n >>> 8;\n    n |= n >>> 16;\n    n++;\n  }\n  return n;\n}\n\n// This function is designed to be inlinable, so please take care when making\n// changes to the function body.\nfunction howMuchToRead(n, state) {\n  if (n <= 0 || (state.length === 0 && (state[kState] & kEnded) !== 0))\n    return 0;\n  if ((state[kState] & kObjectMode) !== 0) return 1;\n  if (Number.isNaN(n)) {\n    // Only flow one buffer at a time.\n    if ((state[kState] & kFlowing) !== 0 && state.length)\n      return state.buffer[state.bufferIndex].length;\n    return state.length;\n  }\n  if (n <= state.length) return n;\n  return (state[kState] & kEnded) !== 0 ? state.length : 0;\n}\n\n// You can override either this method, or the async _read(n) below.\nReadable.prototype.read = function (n) {\n  // Same as parseInt(undefined, 10), however V8 7.3 performance regressed\n  // in this scenario, so we are doing it manually.\n  if (n === undefined) {\n    n = NaN;\n  } else if (!Number.isInteger(n)) {\n    n = Number.parseInt(n, 10);\n  }\n  const state = this._readableState;\n  const nOrig = n;\n\n  // If we're asking for more than the current hwm, then raise the hwm.\n  if (n > state.highWaterMark) state.highWaterMark = computeNewHighWaterMark(n);\n\n  if (n !== 0) state[kState] &= ~kEmittedReadable;\n\n  // If we're doing read(0) to trigger a readable event, but we\n  // already have a bunch of data in the buffer, then just trigger\n  // the 'readable' event and move on.\n  if (\n    n === 0 &&\n    (state[kState] & kNeedReadable) !== 0 &&\n    ((state.highWaterMark !== 0\n      ? state.length >= state.highWaterMark\n      : state.length > 0) ||\n      (state[kState] & kEnded) !== 0)\n  ) {\n    if (state.length === 0 && (state[kState] & kEnded) !== 0) endReadable(this);\n    else emitReadable(this);\n    return null;\n  }\n\n  n = howMuchToRead(n, state);\n\n  // If we've ended, and we're now clear, then finish it up.\n  if (n === 0 && (state[kState] & kEnded) !== 0) {\n    if (state.length === 0) endReadable(this);\n    return null;\n  }\n\n  // All the actual chunk generation logic needs to be\n  // *below* the call to _read.  The reason is that in certain\n  // synthetic stream cases, such as passthrough streams, _read\n  // may be a completely synchronous operation which may change\n  // the state of the read buffer, providing enough data when\n  // before there was *not* enough.\n  //\n  // So, the steps are:\n  // 1. Figure out what the state of things will be after we do\n  // a read from the buffer.\n  //\n  // 2. If that resulting state will trigger a _read, then call _read.\n  // Note that this may be asynchronous, or synchronous.  Yes, it is\n  // deeply ugly to write APIs this way, but that still doesn't mean\n  // that the Readable class should behave improperly, as streams are\n  // designed to be sync/async agnostic.\n  // Take note if the _read call is sync or async (ie, if the read call\n  // has returned yet), so that we know whether or not it's safe to emit\n  // 'readable' etc.\n  //\n  // 3. Actually pull the requested chunks out of the buffer and return.\n\n  // if we need a readable event, then we need to do some reading.\n  let doRead = (state[kState] & kNeedReadable) !== 0;\n\n  // If we currently have less than the highWaterMark, then also read some.\n  if (state.length === 0 || state.length - n < state.highWaterMark) {\n    doRead = true;\n  }\n\n  // However, if we've ended, then there's no point, if we're already\n  // reading, then it's unnecessary, if we're constructing we have to wait,\n  // and if we're destroyed or errored, then it's not allowed,\n  if (\n    (state[kState] &\n      (kReading | kEnded | kDestroyed | kErrored | kConstructed)) !==\n    kConstructed\n  ) {\n    doRead = false;\n  } else if (doRead) {\n    state[kState] |= kReading | kSync;\n    // If the length is currently zero, then we *need* a readable event.\n    if (state.length === 0) state[kState] |= kNeedReadable;\n\n    // Call internal read method\n    try {\n      this._read(state.highWaterMark);\n    } catch (err) {\n      errorOrDestroy(this, err);\n    }\n    state[kState] &= ~kSync;\n\n    // If _read pushed data synchronously, then `reading` will be false,\n    // and we need to re-evaluate how much data we can return to the user.\n    if ((state[kState] & kReading) === 0) n = howMuchToRead(nOrig, state);\n  }\n\n  let ret;\n  if (n > 0) ret = fromList(n, state);\n  else ret = null;\n\n  if (ret === null) {\n    state[kState] |= state.length <= state.highWaterMark ? kNeedReadable : 0;\n    n = 0;\n  } else {\n    state.length -= n;\n    if ((state[kState] & kMultiAwaitDrain) !== 0) {\n      state.awaitDrainWriters.clear();\n    } else {\n      state.awaitDrainWriters = null;\n    }\n  }\n\n  if (state.length === 0) {\n    // If we have nothing in the buffer, then we want to know\n    // as soon as we *do* get something into the buffer.\n    if ((state[kState] & kEnded) === 0) state[kState] |= kNeedReadable;\n\n    // If we tried to read() past the EOF, then emit end on the next tick.\n    if (nOrig !== n && (state[kState] & kEnded) !== 0) endReadable(this);\n  }\n\n  if (ret !== null && (state[kState] & (kErrorEmitted | kCloseEmitted)) === 0) {\n    state[kState] |= kDataEmitted;\n    this.emit('data', ret);\n  }\n\n  return ret;\n};\n\nfunction onEofChunk(stream, state) {\n  if ((state[kState] & kEnded) !== 0) return;\n  const decoder =\n    (state[kState] & kDecoder) !== 0 ? state[kDecoderValue] : null;\n  if (decoder) {\n    const chunk = decoder.end();\n    if (chunk?.length) {\n      state.buffer.push(chunk);\n      state.length += (state[kState] & kObjectMode) !== 0 ? 1 : chunk.length;\n    }\n  }\n  state[kState] |= kEnded;\n\n  if ((state[kState] & kSync) !== 0) {\n    // If we are sync, wait until next tick to emit the data.\n    // Otherwise we risk emitting data in the flow()\n    // the readable code triggers during a read() call.\n    emitReadable(stream);\n  } else {\n    // Emit 'readable' now to make sure it gets picked up.\n    state[kState] &= ~kNeedReadable;\n    state[kState] |= kEmittedReadable;\n    // We have to emit readable now that we are EOF. Modules\n    // in the ecosystem (e.g. dicer) rely on this event being sync.\n    emitReadable_(stream);\n  }\n}\n\n// Don't emit readable right away in sync mode, because this can trigger\n// another read() call => stack overflow.  This way, it might trigger\n// a nextTick recursion warning, but that's not so bad.\nfunction emitReadable(stream) {\n  const state = stream._readableState;\n  state[kState] &= ~kNeedReadable;\n  if ((state[kState] & kEmittedReadable) === 0) {\n    state[kState] |= kEmittedReadable;\n    nextTick(emitReadable_, stream);\n  }\n}\n\nfunction emitReadable_(stream) {\n  const state = stream._readableState;\n  if (\n    (state[kState] & (kDestroyed | kErrored)) === 0 &&\n    (state.length || (state[kState] & kEnded) !== 0)\n  ) {\n    stream.emit('readable');\n    state[kState] &= ~kEmittedReadable;\n  }\n\n  // The stream needs another readable event if:\n  // 1. It is not flowing, as the flow mechanism will take\n  //    care of it.\n  // 2. It is not ended.\n  // 3. It is below the highWaterMark, so we can schedule\n  //    another readable later.\n  state[kState] |=\n    (state[kState] & (kFlowing | kEnded)) === 0 &&\n    state.length <= state.highWaterMark\n      ? kNeedReadable\n      : 0;\n  flow(stream);\n}\n\n// At this point, the user has presumably seen the 'readable' event,\n// and called read() to consume some data.  that may have triggered\n// in turn another _read(n) call, in which case reading = true if\n// it's in progress.\n// However, if we're not ended, or reading, and the length < hwm,\n// then go ahead and try to read some more preemptively.\nfunction maybeReadMore(stream, state) {\n  if ((state[kState] & (kReadingMore | kConstructed)) === kConstructed) {\n    state[kState] |= kReadingMore;\n    nextTick(maybeReadMore_, stream, state);\n  }\n}\n\nfunction maybeReadMore_(stream, state) {\n  // Attempt to read more data if we should.\n  //\n  // The conditions for reading more data are (one of):\n  // - Not enough data buffered (state.length < state.highWaterMark). The loop\n  //   is responsible for filling the buffer with enough data if such data\n  //   is available. If highWaterMark is 0 and we are not in the flowing mode\n  //   we should _not_ attempt to buffer any extra data. We'll get more data\n  //   when the stream consumer calls read() instead.\n  // - No data in the buffer, and the stream is in flowing mode. In this mode\n  //   the loop below is responsible for ensuring read() is called. Failing to\n  //   call read here would abort the flow and there's no other mechanism for\n  //   continuing the flow if the stream consumer has just subscribed to the\n  //   'data' event.\n  //\n  // In addition to the above conditions to keep reading data, the following\n  // conditions prevent the data from being read:\n  // - The stream has ended (state.ended).\n  // - There is already a pending 'read' operation (state.reading). This is a\n  //   case where the stream has called the implementation defined _read()\n  //   method, but they are processing the call asynchronously and have _not_\n  //   called push() with new data. In this case we skip performing more\n  //   read()s. The execution ends in this method again after the _read() ends\n  //   up calling push() with more data.\n  while (\n    (state[kState] & (kReading | kEnded)) === 0 &&\n    (state.length < state.highWaterMark ||\n      ((state[kState] & kFlowing) !== 0 && state.length === 0))\n  ) {\n    const len = state.length;\n    stream.read(0);\n    if (len === state.length)\n      // Didn't get any data, stop spinning.\n      break;\n  }\n  state[kState] &= ~kReadingMore;\n}\n\n// Abstract method.  to be overridden in specific implementation classes.\n// call cb(er, data) where data is <= n in length.\n// for virtual (non-string, non-buffer) streams, \"length\" is somewhat\n// arbitrary, and perhaps not very meaningful.\nReadable.prototype._read = function (_size) {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('_read()');\n};\n\nReadable.prototype.pipe = function (dest, pipeOpts) {\n  const src = this; // eslint-disable-line @typescript-eslint/no-this-alias\n  const state = this._readableState;\n\n  if (state.pipes.length === 1) {\n    if ((state[kState] & kMultiAwaitDrain) === 0) {\n      state[kState] |= kMultiAwaitDrain;\n      state.awaitDrainWriters = new Set(\n        state.awaitDrainWriters ? [state.awaitDrainWriters] : []\n      );\n    }\n  }\n\n  state.pipes.push(dest);\n\n  const doEnd = !pipeOpts || pipeOpts.end !== false;\n\n  const endFn = doEnd ? onend : unpipe;\n  if ((state[kState] & kEndEmitted) !== 0) nextTick(endFn);\n  else src.once('end', endFn);\n\n  dest.on('unpipe', onunpipe);\n  function onunpipe(readable, unpipeInfo) {\n    if (readable === src) {\n      if (unpipeInfo && unpipeInfo.hasUnpiped === false) {\n        unpipeInfo.hasUnpiped = true;\n        cleanup();\n      }\n    }\n  }\n\n  function onend() {\n    dest.end();\n  }\n\n  let ondrain;\n\n  let cleanedUp = false;\n  function cleanup() {\n    // Cleanup event handlers once the pipe is broken.\n    dest.removeListener('close', onclose);\n    dest.removeListener('finish', onfinish);\n    if (ondrain) {\n      dest.removeListener('drain', ondrain);\n    }\n    dest.removeListener('error', onerror);\n    dest.removeListener('unpipe', onunpipe);\n    src.removeListener('end', onend);\n    src.removeListener('end', unpipe);\n    src.removeListener('data', ondata);\n\n    cleanedUp = true;\n\n    // If the reader is waiting for a drain event from this\n    // specific writer, then it would cause it to never start\n    // flowing again.\n    // So, if this is awaiting a drain, then we just call it now.\n    // If we don't know, then assume that we are waiting for one.\n    if (\n      ondrain &&\n      state.awaitDrainWriters &&\n      (!dest._writableState || dest._writableState.needDrain)\n    )\n      ondrain();\n  }\n\n  function pause() {\n    // If the user unpiped during `dest.write()`, it is possible\n    // to get stuck in a permanently paused state if that write\n    // also returned false.\n    // => Check whether `dest` is still a piping destination.\n    if (!cleanedUp) {\n      if (state.pipes.length === 1 && state.pipes[0] === dest) {\n        state.awaitDrainWriters = dest;\n        state[kState] &= ~kMultiAwaitDrain;\n      } else if (state.pipes.length > 1 && state.pipes.includes(dest)) {\n        state.awaitDrainWriters.add(dest);\n      }\n      src.pause();\n    }\n    if (!ondrain) {\n      // When the dest drains, it reduces the awaitDrain counter\n      // on the source.  This would be more elegant with a .once()\n      // handler in flow(), but adding and removing repeatedly is\n      // too slow.\n      ondrain = pipeOnDrain(src, dest);\n      dest.on('drain', ondrain);\n    }\n  }\n\n  src.on('data', ondata);\n  function ondata(chunk) {\n    // This is a semver-major change. Ref: https://github.com/nodejs/node/pull/55270\n    if (streamsNodejsV24Compat) {\n      try {\n        const ret = dest.write(chunk);\n        if (ret === false) {\n          pause();\n        }\n      } catch (error) {\n        dest.destroy(error);\n      }\n    } else {\n      const ret = dest.write(chunk);\n      if (ret === false) {\n        pause();\n      }\n    }\n  }\n\n  // If the dest has an error, then stop piping into it.\n  // However, don't suppress the throwing behavior for this.\n  function onerror(er) {\n    unpipe();\n    dest.removeListener('error', onerror);\n    if (dest.listenerCount('error') === 0) {\n      const s = dest._writableState || dest._readableState;\n      if (s && !s.errorEmitted) {\n        // User incorrectly emitted 'error' directly on the stream.\n        errorOrDestroy(dest, er);\n      } else {\n        dest.emit('error', er);\n      }\n    }\n  }\n\n  // Make sure our error handler is attached before userland ones.\n  dest.prependListener('error', onerror);\n\n  // Both close and finish should trigger unpipe, but only once.\n  function onclose() {\n    dest.removeListener('finish', onfinish);\n    unpipe();\n  }\n  dest.once('close', onclose);\n  function onfinish() {\n    dest.removeListener('close', onclose);\n    unpipe();\n  }\n  dest.once('finish', onfinish);\n\n  function unpipe() {\n    src.unpipe(dest);\n  }\n\n  // Tell the dest that it's being piped to.\n  dest.emit('pipe', src);\n\n  // Start the flow if it hasn't been started already.\n\n  if (dest.writableNeedDrain === true) {\n    pause();\n  } else if ((state[kState] & kFlowing) === 0) {\n    src.resume();\n  }\n\n  return dest;\n};\n\nfunction pipeOnDrain(src, dest) {\n  return function pipeOnDrainFunctionResult() {\n    const state = src._readableState;\n\n    // `ondrain` will call directly,\n    // `this` maybe not a reference to dest,\n    // so we use the real dest here.\n    if (state.awaitDrainWriters === dest) {\n      state.awaitDrainWriters = null;\n    } else if ((state[kState] & kMultiAwaitDrain) !== 0) {\n      state.awaitDrainWriters.delete(dest);\n    }\n\n    if (\n      (!state.awaitDrainWriters || state.awaitDrainWriters.size === 0) &&\n      (state[kState] & kDataListening) !== 0\n    ) {\n      src.resume();\n    }\n  };\n}\n\nReadable.prototype.unpipe = function (dest) {\n  const state = this._readableState;\n  const unpipeInfo = { hasUnpiped: false };\n\n  // If we're not piping anywhere, then do nothing.\n  if (state.pipes.length === 0) return this;\n\n  if (!dest) {\n    // remove all.\n    const dests = state.pipes;\n    state.pipes = [];\n    this.pause();\n\n    for (let i = 0; i < dests.length; i++)\n      dests[i].emit('unpipe', this, { hasUnpiped: false });\n    return this;\n  }\n\n  // Try to find the right one.\n  const index = state.pipes.indexOf(dest);\n  if (index === -1) return this;\n\n  state.pipes.splice(index, 1);\n  if (state.pipes.length === 0) this.pause();\n  dest.emit('unpipe', this, unpipeInfo);\n  return this;\n};\n\n// Set up data events if they are asked for\n// Ensure readable listeners eventually get something.\nReadable.prototype.on = function (ev, fn) {\n  const res = Stream.prototype.on.call(this, ev, fn);\n  const state = this._readableState;\n\n  if (ev === 'data') {\n    state[kState] |= kDataListening;\n\n    // Update readableListening so that resume() may be a no-op\n    // a few lines down. This is needed to support once('readable').\n    state[kState] |=\n      this.listenerCount('readable') > 0 ? kReadableListening : 0;\n\n    // Try start flowing on next tick if stream isn't explicitly paused.\n    if ((state[kState] & (kHasFlowing | kFlowing)) !== kHasFlowing) {\n      this.resume();\n    }\n  } else if (ev === 'readable') {\n    if ((state[kState] & (kEndEmitted | kReadableListening)) === 0) {\n      state[kState] |= kReadableListening | kNeedReadable | kHasFlowing;\n      state[kState] &= ~(kFlowing | kEmittedReadable);\n      if (state.length) {\n        emitReadable(this);\n      } else if ((state[kState] & kReading) === 0) {\n        nextTick(nReadingNextTick, this);\n      }\n    }\n  }\n\n  return res;\n};\nReadable.prototype.addListener = Readable.prototype.on;\n\nReadable.prototype.removeListener = function (ev, fn) {\n  const state = this._readableState;\n\n  const res = Stream.prototype.removeListener.call(this, ev, fn);\n\n  if (ev === 'readable') {\n    // We need to check if there is someone still listening to\n    // readable and reset the state. However this needs to happen\n    // after readable has been emitted but before I/O (nextTick) to\n    // support once('readable', fn) cycles. This means that calling\n    // resume within the same tick will have no\n    // effect.\n    nextTick(updateReadableListening, this);\n  } else if (ev === 'data' && this.listenerCount('data') === 0) {\n    state[kState] &= ~kDataListening;\n  }\n\n  return res;\n};\nReadable.prototype.off = Readable.prototype.removeListener;\n\nReadable.prototype.removeAllListeners = function (ev) {\n  const res = Stream.prototype.removeAllListeners.apply(this, arguments);\n\n  if (ev === 'readable' || ev === undefined) {\n    // We need to check if there is someone still listening to\n    // readable and reset the state. However this needs to happen\n    // after readable has been emitted but before I/O (nextTick) to\n    // support once('readable', fn) cycles. This means that calling\n    // resume within the same tick will have no\n    // effect.\n    nextTick(updateReadableListening, this);\n  }\n\n  return res;\n};\n\nfunction updateReadableListening(self) {\n  const state = self._readableState;\n\n  if (self.listenerCount('readable') > 0) {\n    state[kState] |= kReadableListening;\n  } else {\n    state[kState] &= ~kReadableListening;\n  }\n\n  if (\n    (state[kState] & (kHasPaused | kPaused | kResumeScheduled)) ===\n    (kHasPaused | kResumeScheduled)\n  ) {\n    // Flowing needs to be set to true now, otherwise\n    // the upcoming resume will not flow.\n    state[kState] |= kHasFlowing | kFlowing;\n\n    // Crude way to check if we should resume.\n  } else if ((state[kState] & kDataListening) !== 0) {\n    self.resume();\n  } else if ((state[kState] & kReadableListening) === 0) {\n    state[kState] &= ~(kHasFlowing | kFlowing);\n  }\n}\n\nfunction nReadingNextTick(self) {\n  self.read(0);\n}\n\n// pause() and resume() are remnants of the legacy readable stream API\n// If the user uses them, then switch into old mode.\nReadable.prototype.resume = function () {\n  const state = this._readableState;\n  if ((state[kState] & kFlowing) === 0) {\n    // We flow only if there is no one listening\n    // for readable, but we still have to call\n    // resume().\n    state[kState] |= kHasFlowing;\n    if ((state[kState] & kReadableListening) === 0) {\n      state[kState] |= kFlowing;\n    } else {\n      state[kState] &= ~kFlowing;\n    }\n    resume(this, state);\n  }\n  state[kState] |= kHasPaused;\n  state[kState] &= ~kPaused;\n  return this;\n};\n\nfunction resume(stream, state) {\n  if ((state[kState] & kResumeScheduled) === 0) {\n    state[kState] |= kResumeScheduled;\n    nextTick(resume_, stream, state);\n  }\n}\n\nfunction resume_(stream, state) {\n  if ((state[kState] & kReading) === 0) {\n    stream.read(0);\n  }\n\n  state[kState] &= ~kResumeScheduled;\n  stream.emit('resume');\n  flow(stream);\n  if ((state[kState] & (kFlowing | kReading)) === kFlowing) stream.read(0);\n}\n\nReadable.prototype.pause = function () {\n  const state = this._readableState;\n  if ((state[kState] & (kHasFlowing | kFlowing)) !== kHasFlowing) {\n    state[kState] |= kHasFlowing;\n    state[kState] &= ~kFlowing;\n    this.emit('pause');\n  }\n  state[kState] |= kHasPaused | kPaused;\n  return this;\n};\n\nfunction flow(stream) {\n  const state = stream._readableState;\n  while ((state[kState] & kFlowing) !== 0 && stream.read() !== null);\n}\n\n// Wrap an old-style stream as the async data source.\n// This is *not* part of the readable stream interface.\n// It is an ugly unfortunate mess of history.\nReadable.prototype.wrap = function (stream) {\n  let paused = false;\n\n  // TODO (ronag): Should this.destroy(err) emit\n  // 'error' on the wrapped stream? Would require\n  // a static factory method, e.g. Readable.wrap(stream).\n\n  stream.on('data', (chunk) => {\n    if (!this.push(chunk) && stream.pause) {\n      paused = true;\n      stream.pause();\n    }\n  });\n\n  stream.on('end', () => {\n    this.push(null);\n  });\n\n  stream.on('error', (err) => {\n    errorOrDestroy(this, err);\n  });\n\n  stream.on('close', () => {\n    this.destroy();\n  });\n\n  stream.on('destroy', () => {\n    this.destroy();\n  });\n\n  this._read = () => {\n    if (paused && stream.resume) {\n      paused = false;\n      stream.resume();\n    }\n  };\n\n  // Proxy all the other methods. Important when wrapping filters and duplexes.\n  const streamKeys = Object.keys(stream);\n  for (let j = 1; j < streamKeys.length; j++) {\n    const i = streamKeys[j];\n    if (this[i] === undefined && typeof stream[i] === 'function') {\n      this[i] = stream[i].bind(stream);\n    }\n  }\n\n  return this;\n};\n\nReadable.prototype[Symbol.asyncIterator] = function () {\n  return streamToAsyncIterator(this);\n};\n\nReadable.prototype.iterator = function (options) {\n  if (options !== undefined) {\n    validateObject(options, 'options');\n  }\n  return streamToAsyncIterator(this, options);\n};\n\nfunction streamToAsyncIterator(stream, options) {\n  if (typeof stream.read !== 'function') {\n    stream = Readable.wrap(stream, { objectMode: true });\n  }\n\n  const iter = createAsyncIterator(stream, options);\n  iter.stream = stream;\n  return iter;\n}\n\nasync function* createAsyncIterator(stream, options) {\n  let callback = nop;\n\n  function next(resolve) {\n    if (this === stream) {\n      callback();\n      callback = nop;\n    } else {\n      callback = resolve;\n    }\n  }\n\n  stream.on('readable', next);\n\n  let error;\n  const cleanup = eos(stream, { writable: false }, (err) => {\n    error = err ? aggregateTwoErrors(error, err) : null;\n    callback();\n    callback = nop;\n  });\n\n  try {\n    while (true) {\n      const chunk = stream.destroyed ? null : stream.read();\n      if (chunk !== null) {\n        yield chunk;\n      } else if (error) {\n        throw error;\n      } else if (error === null) {\n        return;\n      } else {\n        await new Promise(next);\n      }\n    }\n  } catch (err) {\n    error = aggregateTwoErrors(error, err);\n    throw error;\n  } finally {\n    if (\n      (error || options?.destroyOnReturn !== false) &&\n      (error === undefined || stream._readableState.autoDestroy)\n    ) {\n      destroyer(stream, null);\n    } else {\n      stream.off('readable', next);\n      cleanup();\n    }\n  }\n}\n\n// Making it explicit these properties are not enumerable\n// because otherwise some prototype manipulation in\n// userland will fail.\nObject.defineProperties(Readable.prototype, {\n  readable: {\n    __proto__: null,\n    get() {\n      const r = this._readableState;\n      // r.readable === false means that this is part of a Duplex stream\n      // where the readable side was disabled upon construction.\n      // Compat. The user might manually disable readable side through\n      // deprecated setter.\n      return (\n        !!r &&\n        r.readable !== false &&\n        !r.destroyed &&\n        !r.errorEmitted &&\n        !r.endEmitted\n      );\n    },\n    set(val) {\n      // Backwards compat.\n      if (this._readableState) {\n        this._readableState.readable = !!val;\n      }\n    },\n  },\n\n  readableDidRead: {\n    __proto__: null,\n    enumerable: false,\n    get: function () {\n      return this._readableState.dataEmitted;\n    },\n  },\n\n  readableAborted: {\n    __proto__: null,\n    enumerable: false,\n    get: function () {\n      return !!(\n        this._readableState.readable !== false &&\n        (this._readableState.destroyed || this._readableState.errored) &&\n        !this._readableState.endEmitted\n      );\n    },\n  },\n\n  readableHighWaterMark: {\n    __proto__: null,\n    enumerable: false,\n    get: function () {\n      return this._readableState.highWaterMark;\n    },\n  },\n\n  readableBuffer: {\n    __proto__: null,\n    enumerable: false,\n    get: function () {\n      return this._readableState?.buffer;\n    },\n  },\n\n  readableFlowing: {\n    __proto__: null,\n    enumerable: false,\n    get: function () {\n      return this._readableState.flowing;\n    },\n    set: function (state) {\n      if (this._readableState) {\n        this._readableState.flowing = state;\n      }\n    },\n  },\n\n  readableLength: {\n    __proto__: null,\n    enumerable: false,\n    get() {\n      return this._readableState.length;\n    },\n  },\n\n  readableObjectMode: {\n    __proto__: null,\n    enumerable: false,\n    get() {\n      return this._readableState ? this._readableState.objectMode : false;\n    },\n  },\n\n  readableEncoding: {\n    __proto__: null,\n    enumerable: false,\n    get() {\n      return this._readableState ? this._readableState.encoding : null;\n    },\n  },\n\n  errored: {\n    __proto__: null,\n    enumerable: false,\n    get() {\n      return this._readableState ? this._readableState.errored : null;\n    },\n  },\n\n  closed: {\n    __proto__: null,\n    get() {\n      return this._readableState ? this._readableState.closed : false;\n    },\n  },\n\n  destroyed: {\n    __proto__: null,\n    enumerable: false,\n    get() {\n      return this._readableState ? this._readableState.destroyed : false;\n    },\n    set(value) {\n      // We ignore the value if the stream\n      // has not been initialized yet.\n      if (!this._readableState) {\n        return;\n      }\n\n      // Backward compatibility, the user is explicitly\n      // managing destroyed.\n      this._readableState.destroyed = value;\n    },\n  },\n\n  readableEnded: {\n    __proto__: null,\n    enumerable: false,\n    get() {\n      return this._readableState ? this._readableState.endEmitted : false;\n    },\n  },\n});\n\nObject.defineProperties(ReadableState.prototype, {\n  // Legacy getter for `pipesCount`.\n  pipesCount: {\n    __proto__: null,\n    get() {\n      return this.pipes.length;\n    },\n  },\n\n  // Legacy property for `paused`.\n  paused: {\n    __proto__: null,\n    get() {\n      return (this[kState] & kPaused) !== 0;\n    },\n    set(value) {\n      this[kState] |= kHasPaused;\n      if (value) {\n        this[kState] |= kPaused;\n      } else {\n        this[kState] &= ~kPaused;\n      }\n    },\n  },\n});\n\n// Exposed for testing purposes only.\nReadable._fromList = fromList;\n\n// Pluck off n bytes from an array of buffers.\n// Length is the combined lengths of all the buffers in the list.\n// This function is designed to be inlinable, so please take care when making\n// changes to the function body.\nfunction fromList(n, state) {\n  // nothing buffered.\n  if (state.length === 0) return null;\n\n  let idx = state.bufferIndex;\n  let ret;\n\n  const buf = state.buffer;\n  const len = buf.length;\n\n  if ((state[kState] & kObjectMode) !== 0) {\n    ret = buf[idx];\n    buf[idx++] = null;\n  } else if (!n || n >= state.length) {\n    // Read it all, truncate the list.\n    if ((state[kState] & kDecoder) !== 0) {\n      ret = '';\n      while (idx < len) {\n        ret += buf[idx];\n        buf[idx++] = null;\n      }\n    } else if (len - idx === 0) {\n      ret = Buffer.alloc(0);\n    } else if (len - idx === 1) {\n      ret = buf[idx];\n      buf[idx++] = null;\n    } else {\n      ret = Buffer.allocUnsafe(state.length);\n\n      let i = 0;\n      while (idx < len) {\n        ret.set(buf[idx], i);\n        i += buf[idx].length;\n        buf[idx++] = null;\n      }\n    }\n  } else if (n < buf[idx].length) {\n    // `slice` is the same for buffers and strings.\n    ret = buf[idx].slice(0, n);\n    buf[idx] = buf[idx].slice(n);\n  } else if (n === buf[idx].length) {\n    // First chunk is a perfect match.\n    ret = buf[idx];\n    buf[idx++] = null;\n  } else if ((state[kState] & kDecoder) !== 0) {\n    ret = '';\n    while (idx < len) {\n      const str = buf[idx];\n      if (n > str.length) {\n        ret += str;\n        n -= str.length;\n        buf[idx++] = null;\n      } else {\n        if (n === buf.length) {\n          ret += str;\n          buf[idx++] = null;\n        } else {\n          ret += str.slice(0, n);\n          buf[idx] = str.slice(n);\n        }\n        break;\n      }\n    }\n  } else {\n    ret = Buffer.allocUnsafe(n);\n\n    const retLen = n;\n    while (idx < len) {\n      const data = buf[idx];\n      if (n > data.length) {\n        ret.set(data, retLen - n);\n        n -= data.length;\n        buf[idx++] = null;\n      } else {\n        if (n === data.length) {\n          ret.set(data, retLen - n);\n          buf[idx++] = null;\n        } else {\n          ret.set(Buffer.from(data.buffer, data.byteOffset, n), retLen - n);\n          buf[idx] = Buffer.from(\n            data.buffer,\n            data.byteOffset + n,\n            data.length - n\n          );\n        }\n        break;\n      }\n    }\n  }\n\n  if (idx === len) {\n    state.buffer.length = 0;\n    state.bufferIndex = 0;\n  } else if (idx > 1024) {\n    state.buffer.splice(0, idx);\n    state.bufferIndex = 0;\n  } else {\n    state.bufferIndex = idx;\n  }\n\n  return ret;\n}\n\nfunction endReadable(stream) {\n  const state = stream._readableState;\n\n  if ((state[kState] & kEndEmitted) === 0) {\n    state[kState] |= kEnded;\n    nextTick(endReadableNT, state, stream);\n  }\n}\n\nfunction endReadableNT(state, stream) {\n  // Check that we didn't get one last unshift.\n  if (\n    (state[kState] & (kErrored | kCloseEmitted | kEndEmitted)) === 0 &&\n    state.length === 0\n  ) {\n    state[kState] |= kEndEmitted;\n    stream.emit('end');\n\n    if (stream.writable && stream.allowHalfOpen === false) {\n      nextTick(endWritableNT, stream);\n    } else if (state.autoDestroy) {\n      // In case of duplex streams we need a way to detect\n      // if the writable side is ready for autoDestroy as well.\n      const wState = stream._writableState;\n      const autoDestroy =\n        !wState ||\n        (wState.autoDestroy &&\n          // We don't expect the writable to ever 'finish'\n          // if writable is explicitly set to false.\n          (wState.finished || wState.writable === false));\n\n      if (autoDestroy) {\n        stream.destroy();\n      }\n    }\n  }\n}\n\nfunction endWritableNT(stream) {\n  const writable =\n    stream.writable && !stream.writableEnded && !stream.destroyed;\n  if (writable) {\n    stream.end();\n  }\n}\n\nexport function fromWeb(readableStream, options) {\n  return newStreamReadableFromReadableStream(readableStream, options);\n}\n\nexport function toWeb(streamReadable, options) {\n  return newReadableStreamFromStreamReadable(streamReadable, options);\n}\n\nexport function wrap(src, options) {\n  let _ref, _src$readableObjectMo;\n  return new Readable({\n    objectMode:\n      (_ref =\n        (_src$readableObjectMo = src.readableObjectMode) !== null &&\n        _src$readableObjectMo !== undefined\n          ? _src$readableObjectMo\n          : src.objectMode) !== null && _ref !== undefined\n        ? _ref\n        : true,\n    ...options,\n    destroy(err, callback) {\n      destroyer(src, err);\n      callback(err);\n    },\n  }).wrap(src);\n}\n\nReadable.toWeb = toWeb;\nReadable.fromWeb = fromWeb;\nReadable.wrap = wrap;\n\n// ======================================================================================\n//\n\nReadable.from = function (iterable, opts) {\n  return from(Readable, iterable, opts);\n};\n\nexport function from(Readable, iterable, opts) {\n  let iterator;\n  if (typeof iterable === 'string' || iterable instanceof Buffer) {\n    return new Readable({\n      objectMode: true,\n      ...opts,\n      read() {\n        this.push(iterable);\n        this.push(null);\n      },\n    });\n  }\n  let isAsync;\n  if (iterable && iterable[Symbol.asyncIterator]) {\n    isAsync = true;\n    iterator = iterable[Symbol.asyncIterator]();\n  } else if (iterable && iterable[Symbol.iterator]) {\n    isAsync = false;\n    iterator = iterable[Symbol.iterator]();\n  } else {\n    throw new ERR_INVALID_ARG_TYPE('iterable', ['Iterable'], iterable);\n  }\n  const readable = new Readable({\n    objectMode: true,\n    highWaterMark: 1,\n    // TODO(ronag): What options should be allowed?\n    ...opts,\n  });\n\n  // Flag to protect against _read\n  // being called before last iteration completion.\n  let reading = false;\n  readable._read = function () {\n    if (!reading) {\n      reading = true;\n      next();\n    }\n  };\n  readable._destroy = function (error, cb) {\n    close(error).then(\n      () => nextTick(cb, error),\n      (err) => nextTick(cb, err || error)\n    );\n  };\n  async function close(error) {\n    const hadError = error !== undefined && error !== null;\n    const hasThrow = typeof iterator.throw === 'function';\n    if (hadError && hasThrow) {\n      const { value, done } = await iterator.throw(error);\n      await value;\n      if (done) {\n        return;\n      }\n    }\n    if (typeof iterator.return === 'function') {\n      const { value } = await iterator.return();\n      await value;\n    }\n  }\n  async function next() {\n    for (;;) {\n      try {\n        const { value, done } = isAsync\n          ? await iterator.next()\n          : iterator.next();\n        if (done) {\n          readable.push(null);\n        } else {\n          const res =\n            value && typeof value.then === 'function' ? await value : value;\n          if (res === null) {\n            reading = false;\n            throw new ERR_STREAM_NULL_VALUES();\n          } else if (readable.push(res)) {\n            continue;\n          } else {\n            reading = false;\n          }\n        }\n      } catch (err) {\n        readable.destroy(err);\n      }\n      break;\n    }\n  }\n  return readable;\n}\n\n// ======================================================================================\n// Operators\n\nconst kWeakHandler = Symbol('kWeak');\nconst kEmpty = Symbol('kEmpty');\nconst kEof = Symbol('kEof');\n\nfunction map(fn, options) {\n  if (typeof fn !== 'function') {\n    throw new ERR_INVALID_ARG_TYPE('fn', ['Function', 'AsyncFunction'], fn);\n  }\n  if (options != null) {\n    validateObject(options, 'options', options);\n  }\n  if (options?.signal != null) {\n    validateAbortSignal(options.signal, 'options.signal');\n  }\n  let concurrency = 1;\n  if (options?.concurrency != null) {\n    concurrency = Math.floor(options.concurrency);\n  }\n  validateInteger(concurrency, 'concurrency', 1);\n  return async function* map() {\n    let _options$signal, _options$signal2;\n    const ac = new globalThis.AbortController();\n    const stream = this; // eslint-disable-line @typescript-eslint/no-this-alias\n    const queue = [];\n    const signal = ac.signal;\n    const signalOpt = {\n      signal,\n    };\n    const abort = () => ac.abort();\n    if (\n      options != null &&\n      (_options$signal = options.signal) !== null &&\n      _options$signal !== undefined &&\n      _options$signal.aborted\n    ) {\n      abort();\n    }\n    // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n    options == null\n      ? undefined\n      : (_options$signal2 = options.signal) === null ||\n          _options$signal2 === undefined\n        ? undefined\n        : _options$signal2.addEventListener('abort', abort);\n    let next;\n    let resume;\n    let done = false;\n    function onDone() {\n      done = true;\n    }\n    async function pump() {\n      try {\n        for await (let val of stream) {\n          let _val;\n          if (done) {\n            return;\n          }\n          if (signal.aborted) {\n            throw new AbortError();\n          }\n          try {\n            val = fn(val, signalOpt);\n          } catch (err) {\n            val = Promise.reject(err);\n          }\n          if (val === kEmpty) {\n            continue;\n          }\n          if (\n            typeof ((_val = val) === null || _val === undefined\n              ? undefined\n              : _val.catch) === 'function'\n          ) {\n            val.catch(onDone);\n          }\n          queue.push(val);\n          if (next) {\n            next();\n            next = null;\n          }\n          if (!done && queue.length && queue.length >= concurrency) {\n            await new Promise((resolve) => {\n              resume = resolve;\n            });\n          }\n        }\n        queue.push(kEof);\n      } catch (err) {\n        const val = Promise.reject(err);\n        val.then(undefined, onDone);\n        queue.push(val);\n      } finally {\n        let _options$signal3;\n        done = true;\n        if (next) {\n          next();\n          next = null;\n        }\n        // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n        options == null\n          ? undefined\n          : (_options$signal3 = options.signal) === null ||\n              _options$signal3 === undefined\n            ? undefined\n            : _options$signal3.removeEventListener('abort', abort);\n      }\n    }\n    pump();\n    try {\n      while (true) {\n        while (queue.length > 0) {\n          const val = await queue[0];\n          if (val === kEof) {\n            return;\n          }\n          if (signal.aborted) {\n            throw new AbortError();\n          }\n          if (val !== kEmpty) {\n            yield val;\n          }\n          queue.shift();\n          if (resume) {\n            resume();\n            resume = null;\n          }\n        }\n        await new Promise((resolve) => {\n          next = resolve;\n        });\n      }\n    } finally {\n      ac.abort();\n      done = true;\n      if (resume) {\n        resume();\n        resume = null;\n      }\n    }\n  }.call(this);\n}\n\nfunction asIndexedPairs(options) {\n  if (options != null) {\n    validateObject(options, 'options', options);\n  }\n  if ((options == null ? undefined : options.signal) != null) {\n    validateAbortSignal(options.signal, 'options.signal');\n  }\n  return async function* asIndexedPairs() {\n    let index = 0;\n    for await (const val of this) {\n      let _options$signal4;\n      if (\n        options !== null &&\n        options !== undefined &&\n        (_options$signal4 = options.signal) !== null &&\n        _options$signal4 !== undefined &&\n        _options$signal4.aborted\n      ) {\n        throw new AbortError('Aborted', {\n          cause: options.signal?.reason,\n        });\n      }\n      yield [index++, val];\n    }\n  }.call(this);\n}\n\nasync function some(fn, options) {\n  if (typeof fn !== 'function') {\n    throw new ERR_INVALID_ARG_TYPE('fn', ['Function', 'AsyncFunction'], fn);\n  }\n  for await (const _ of filter.call(this, fn, options)) {\n    return true;\n  }\n  return false;\n}\n\nasync function every(fn, options) {\n  if (typeof fn !== 'function') {\n    throw new ERR_INVALID_ARG_TYPE('fn', ['Function', 'AsyncFunction'], fn);\n  }\n  // https://en.wikipedia.org/wiki/De_Morgan%27s_laws\n  return !(await some.call(\n    this,\n    async (...args) => {\n      return !(await fn(...args));\n    },\n    options\n  ));\n}\n\nasync function find(fn, options) {\n  for await (const result of filter.call(this, fn, options)) {\n    return result;\n  }\n  return undefined;\n}\n\nasync function forEach(fn, options) {\n  if (typeof fn !== 'function') {\n    throw new ERR_INVALID_ARG_TYPE('fn', ['Function', 'AsyncFunction'], fn);\n  }\n  async function forEachFn(value, options) {\n    await fn(value, options);\n    return kEmpty;\n  }\n  for await (const _ of map.call(this, forEachFn, options));\n}\n\nfunction filter(fn, options) {\n  if (typeof fn !== 'function') {\n    throw new ERR_INVALID_ARG_TYPE('fn', ['Function', 'AsyncFunction'], fn);\n  }\n  async function filterFn(value, options) {\n    if (await fn(value, options)) {\n      return value;\n    }\n    return kEmpty;\n  }\n  return map.call(this, filterFn, options);\n}\n\n// Specific to provide better error to reduce since the argument is only\n// missing if the stream has no items in it - but the code is still appropriate\nclass ReduceAwareErrMissingArgs extends ERR_MISSING_ARGS {\n  constructor() {\n    super('reduce');\n    this.message = 'Reduce of an empty stream requires an initial value';\n  }\n}\n\nasync function reduce(reducer, initialValue, options) {\n  let _options$signal5;\n  if (typeof reducer !== 'function') {\n    throw new ERR_INVALID_ARG_TYPE(\n      'reducer',\n      ['Function', 'AsyncFunction'],\n      reducer\n    );\n  }\n  if (options != null) {\n    validateObject(options, 'options', options);\n  }\n  if (options?.signal != null) {\n    validateAbortSignal(options?.signal, 'options.signal');\n  }\n  let hasInitialValue = arguments.length > 1;\n  if (\n    options !== null &&\n    options !== undefined &&\n    (_options$signal5 = options.signal) !== null &&\n    _options$signal5 !== undefined &&\n    _options$signal5.aborted\n  ) {\n    const err = new AbortError(undefined, {\n      cause: options.signal?.reason,\n    });\n    this.once('error', () => {}); // The error is already propagated\n    await finished(this.destroy(err));\n    throw err;\n  }\n  const ac = new globalThis.AbortController();\n  const signal = ac.signal;\n  if (options?.signal) {\n    const opts = {\n      once: true,\n      [kWeakHandler]: this,\n    };\n    options.signal.addEventListener('abort', () => ac.abort(), opts);\n  }\n  let gotAnyItemFromStream = false;\n  try {\n    for await (const value of this) {\n      let _options$signal6;\n      gotAnyItemFromStream = true;\n      if (\n        options !== null &&\n        options !== undefined &&\n        (_options$signal6 = options.signal) !== null &&\n        _options$signal6 !== undefined &&\n        _options$signal6.aborted\n      ) {\n        throw new AbortError();\n      }\n      if (!hasInitialValue) {\n        initialValue = value;\n        hasInitialValue = true;\n      } else {\n        initialValue = await reducer(initialValue, value, {\n          signal,\n        });\n      }\n    }\n    if (!gotAnyItemFromStream && !hasInitialValue) {\n      throw new ReduceAwareErrMissingArgs();\n    }\n  } finally {\n    ac.abort();\n  }\n  return initialValue;\n}\n\nasync function toArray(options) {\n  if (options != null) {\n    validateObject(options, 'options', options);\n  }\n  if (options?.signal != null) {\n    validateAbortSignal(options?.signal, 'options.signal');\n  }\n  const result = [];\n  for await (const val of this) {\n    let _options$signal7;\n    if (\n      options !== null &&\n      options !== undefined &&\n      (_options$signal7 = options.signal) !== null &&\n      _options$signal7 !== undefined &&\n      _options$signal7.aborted\n    ) {\n      throw new AbortError(undefined, {\n        cause: options.signal?.reason,\n      });\n    }\n    result.push(val);\n  }\n  return result;\n}\n\nfunction flatMap(fn, options) {\n  const values = map.call(this, fn, options);\n  return async function* flatMap() {\n    for await (const val of values) {\n      yield* val;\n    }\n  }.call(this);\n}\n\nfunction toIntegerOrInfinity(number) {\n  // We coerce here to align with the spec\n  // https://github.com/tc39/proposal-iterator-helpers/issues/169\n  number = Number(number);\n  if (Number.isNaN(number)) {\n    return 0;\n  }\n  if (number < 0) {\n    throw new ERR_OUT_OF_RANGE('number', '>= 0', number);\n  }\n  return number;\n}\n\nfunction drop(number, options) {\n  if (options != null) {\n    validateObject(options, 'options', options);\n  }\n  if (options?.signal != null) {\n    validateAbortSignal(options?.signal, 'options.signal');\n  }\n  number = toIntegerOrInfinity(number);\n  return async function* drop() {\n    let _options$signal8;\n    if (\n      options !== null &&\n      options !== undefined &&\n      (_options$signal8 = options.signal) !== null &&\n      _options$signal8 !== undefined &&\n      _options$signal8.aborted\n    ) {\n      throw new AbortError();\n    }\n    for await (const val of this) {\n      let _options$signal9;\n      if (\n        options !== null &&\n        options !== undefined &&\n        (_options$signal9 = options.signal) !== null &&\n        _options$signal9 !== undefined &&\n        _options$signal9.aborted\n      ) {\n        throw new AbortError();\n      }\n      if (number-- <= 0) {\n        yield val;\n      }\n    }\n  }.call(this);\n}\n\nfunction take(number, options) {\n  if (options != null) {\n    validateObject(options, 'options', options);\n  }\n  if (options?.signal != null) {\n    validateAbortSignal(options?.signal, 'options.signal');\n  }\n  number = toIntegerOrInfinity(number);\n  return async function* take() {\n    let _options$signal10;\n    if (\n      options !== null &&\n      options !== undefined &&\n      (_options$signal10 = options.signal) !== null &&\n      _options$signal10 !== undefined &&\n      _options$signal10.aborted\n    ) {\n      throw new AbortError();\n    }\n    for await (const val of this) {\n      let _options$signal11;\n      if (\n        options !== null &&\n        options !== undefined &&\n        (_options$signal11 = options.signal) !== null &&\n        _options$signal11 !== undefined &&\n        _options$signal11.aborted\n      ) {\n        throw new AbortError();\n      }\n      if (number-- > 0) {\n        yield val;\n      } else {\n        return;\n      }\n    }\n  }.call(this);\n}\n\nReadable.prototype.map = function (fn, options) {\n  return from(Readable, map.call(this, fn, options));\n};\n\nReadable.prototype.asIndexedPairs = function (options) {\n  return from(Readable, asIndexedPairs.call(this, options));\n};\n\nReadable.prototype.drop = function (number, options) {\n  return from(Readable, drop.call(this, number, options));\n};\n\nReadable.prototype.filter = function (fn, options) {\n  return from(Readable, filter.call(this, fn, options));\n};\n\nReadable.prototype.flatMap = function (fn, options) {\n  return from(Readable, flatMap.call(this, fn, options));\n};\n\nReadable.prototype.take = function (number, options) {\n  return from(Readable, take.call(this, number, options));\n};\n\nReadable.prototype.every = every;\nReadable.prototype.forEach = forEach;\nReadable.prototype.reduce = reduce;\nReadable.prototype.toArray = toArray;\nReadable.prototype.some = some;\nReadable.prototype.find = find;\n\n/**\n * @typedef {import('./queuingstrategies').QueuingStrategy} QueuingStrategy\n * @param {Readable} streamReadable\n * @param {{\n *  strategy : QueuingStrategy\n * }} [options]\n * @returns {ReadableStream}\n */\nexport function newReadableStreamFromStreamReadable(\n  streamReadable,\n  options = {},\n  createTypeBytes = false\n) {\n  // Not using the internal/streams/utils isReadableNodeStream utility\n  // here because it will return false if streamReadable is a Duplex\n  // whose readable option is false. For a Duplex that is not readable,\n  // we want it to pass this check but return a closed ReadableStream.\n  if (typeof streamReadable?._readableState !== 'object') {\n    throw new ERR_INVALID_ARG_TYPE(\n      'streamReadable',\n      'stream.Readable',\n      streamReadable\n    );\n  }\n\n  if (isDestroyed(streamReadable) || !isReadable(streamReadable)) {\n    const readable = new globalThis.ReadableStream();\n    readable.cancel();\n    return readable;\n  }\n\n  const objectMode = streamReadable.readableObjectMode;\n  const highWaterMark = streamReadable.readableHighWaterMark;\n\n  const evaluateStrategyOrFallback = (strategy) => {\n    // If there is a strategy available, use it\n    if (strategy) return strategy;\n\n    if (objectMode) {\n      // When running in objectMode explicitly but no strategy, we just fall\n      // back to CountQueuingStrategy\n      return new globalThis.CountQueuingStrategy({ highWaterMark });\n    }\n\n    return new globalThis.ByteLengthQueuingStrategy({ highWaterMark });\n  };\n\n  const strategy = evaluateStrategyOrFallback(options?.strategy);\n\n  let controller;\n  let wasCanceled = false;\n\n  function onData(chunk) {\n    // Copy the Buffer to detach it from the pool.\n    if (Buffer.isBuffer(chunk) && !objectMode) chunk = new Uint8Array(chunk);\n    controller.enqueue(chunk);\n    if (controller.desiredSize <= 0) streamReadable.pause();\n  }\n\n  streamReadable.pause();\n\n  const cleanup = eos(streamReadable, (error) => {\n    error = handleKnownInternalErrors(error);\n\n    cleanup();\n    // This is a protection against non-standard, legacy streams\n    // that happen to emit an error event again after finished is called.\n    streamReadable.on('error', () => {});\n    if (error) return controller.error(error);\n    // Was already canceled\n    if (wasCanceled) {\n      return;\n    }\n    controller.close();\n  });\n\n  streamReadable.on('data', onData);\n\n  return new globalThis.ReadableStream(\n    {\n      start(c) {\n        controller = c;\n      },\n\n      pull() {\n        streamReadable.resume();\n      },\n\n      cancel(reason) {\n        wasCanceled = true;\n        destroy(streamReadable, reason);\n      },\n      type: createTypeBytes ? 'bytes' : undefined,\n    },\n    strategy\n  );\n}\n\n/**\n * @param {ReadableStream} readableStream\n * @param {{\n *   highWaterMark? : number,\n *   encoding? : string,\n *   objectMode? : boolean,\n *   signal? : AbortSignal,\n * }} [options]\n * @returns {Readable}\n */\nexport function newStreamReadableFromReadableStream(\n  readableStream,\n  options = {}\n) {\n  if (!isReadableStream(readableStream)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'readableStream',\n      'ReadableStream',\n      readableStream\n    );\n  }\n\n  validateObject(options, 'options');\n  const { highWaterMark, encoding, objectMode = false, signal } = options;\n\n  if (encoding !== undefined && !Buffer.isEncoding(encoding))\n    throw new ERR_INVALID_ARG_VALUE('options.encoding', encoding);\n  validateBoolean(objectMode, 'options.objectMode');\n\n  const reader = readableStream.getReader();\n  let closed = false;\n\n  const readable = new Readable({\n    objectMode,\n    highWaterMark,\n    encoding,\n    signal,\n\n    read() {\n      reader.read().then(\n        (chunk) => {\n          if (chunk.done) {\n            // Value should always be undefined here.\n            readable.push(null);\n          } else {\n            readable.push(chunk.value);\n          }\n        },\n        (error) => destroy.call(readable, error)\n      );\n    },\n\n    destroy(error, callback) {\n      function done() {\n        try {\n          callback(error);\n        } catch (error) {\n          // In a next tick because this is happening within\n          // a promise context, and if there are any errors\n          // thrown we don't want those to cause an unhandled\n          // rejection. Let's just escape the promise and\n          // handle it separately.\n          nextTick(() => {\n            throw error;\n          });\n        }\n      }\n\n      if (!closed) {\n        reader.cancel(error).then(done, done);\n        return;\n      }\n      done();\n    },\n  });\n\n  reader.closed.then(\n    () => {\n      closed = true;\n    },\n    (error) => {\n      closed = true;\n      destroy.call(readable, error);\n    }\n  );\n\n  return readable;\n}\n"
  },
  {
    "path": "src/node/internal/streams_state.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { validateInteger } from 'node-internal:validators';\nimport { ERR_INVALID_ARG_VALUE } from 'node-internal:internal_errors';\n\nlet defaultHighWaterMarkBytes = 64 * 1024;\nlet defaultHighWaterMarkObjectMode = 16;\n\nexport type HighWaterMarkFromOptions = { highWaterMark?: number };\n\nfunction highWaterMarkFrom(\n  options: HighWaterMarkFromOptions,\n  isDuplex: boolean,\n  duplexKey: string\n): number | null {\n  return options.highWaterMark != null\n    ? options.highWaterMark\n    : isDuplex\n      ? // @ts-expect-error TS7053 Fix this soon.\n        (options[duplexKey] as number)\n      : null;\n}\n\nexport function getDefaultHighWaterMark(objectMode?: boolean): number {\n  return objectMode\n    ? defaultHighWaterMarkObjectMode\n    : defaultHighWaterMarkBytes;\n}\n\nexport function setDefaultHighWaterMark(\n  objectMode: boolean,\n  value: unknown\n): void {\n  validateInteger(value, 'value', 0);\n  if (objectMode) {\n    defaultHighWaterMarkObjectMode = value;\n  } else {\n    defaultHighWaterMarkBytes = value;\n  }\n}\n\nexport function getHighWaterMark(\n  state: { objectMode?: boolean },\n  options: HighWaterMarkFromOptions,\n  duplexKey: string,\n  isDuplex: boolean\n): number {\n  const hwm = highWaterMarkFrom(options, isDuplex, duplexKey);\n  if (hwm != null) {\n    if (!Number.isInteger(hwm) || hwm < 0) {\n      const name = isDuplex ? `options.${duplexKey}` : 'options.highWaterMark';\n      throw new ERR_INVALID_ARG_VALUE(name, hwm);\n    }\n    return Math.floor(hwm);\n  }\n\n  // Default value\n  return getDefaultHighWaterMark(state.objectMode);\n}\n"
  },
  {
    "path": "src/node/internal/streams_transform.d.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport type EventEmitter from 'node:events';\nimport { Transform as _Transform } from 'node:stream';\nimport type { ReadableState } from 'node-internal:streams_readable';\nimport type { WritableState } from 'node-internal:streams_writable';\nimport {\n  kIsWritable,\n  kIsReadable,\n  kIsDestroyed,\n} from 'node-internal:streams_util';\n\nexport {\n  Readable,\n  Writable,\n  Duplex,\n  Transform as _Transform,\n  Stream,\n  TransformOptions,\n  TransformCallback,\n  DuplexOptions,\n  PassThrough,\n} from 'node:stream';\n\nexport declare class Transform extends _Transform {\n  _writableState: WritableState;\n  _readableState: ReadableState;\n  writableErrored: boolean;\n  readableErrored: boolean;\n  _closed: boolean;\n  readable: boolean;\n  writable: boolean;\n  errored?: Error | null;\n  readableFinished?: boolean;\n  writableFinished?: boolean;\n  writableEnded?: boolean;\n  aborted?: boolean;\n  req?: EventEmitter;\n\n  [kIsWritable]: boolean;\n  [kIsReadable]: boolean;\n  [kIsDestroyed]: boolean;\n}\n"
  },
  {
    "path": "src/node/internal/streams_transform.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n// a transform stream is a readable/writable stream where you do\n// something with the data.  Sometimes it's called a \"filter\",\n// but that's not a great name for it, since that implies a thing where\n// some bits pass through, and others are simply ignored.  (That would\n// be a valid example of a transform, of course.)\n//\n// While the output is causally related to the input, it's not a\n// necessarily symmetric or synchronous transformation.  For example,\n// a zlib stream might take multiple plain-text writes(), and then\n// emit a single compressed chunk some time in the future.\n//\n// Here's how this works:\n//\n// The Transform stream has all the aspects of the readable and writable\n// stream classes.  When you write(chunk), that calls _write(chunk,cb)\n// internally, and returns false if there's a lot of pending writes\n// buffered up.  When you call read(), that calls _read(n) until\n// there's enough pending readable data buffered up.\n//\n// In a transform stream, the written data is placed in a buffer.  When\n// _read(n) is called, it transforms the queued up data, calling the\n// buffered _write cb's as it consumes chunks.  If consuming a single\n// written chunk would result in multiple output chunks, then the first\n// outputted bit calls the readcb, and subsequent chunks just go into\n// the read buffer, and will cause it to emit 'readable' if necessary.\n//\n// This way, back-pressure is actually determined by the reading side,\n// since _read has to be called to start processing a new chunk.  However,\n// a pathological inflate type of transform can cause excessive buffering\n// here.  For example, imagine a stream where every byte of input is\n// interpreted as an integer from 0-255, and then results in that many\n// bytes of output.  Writing the 4 bytes {ff,ff,ff,ff} would result in\n// 1kb of data being output.  In this case, you could write a very small\n// amount of input, and end up with a very large amount of output.  In\n// such a pathological inflating mechanism, there'd be no way to tell\n// the system to stop doing the transform.  A single 4MB write could\n// cause the system to run out of memory.\n//\n// However, even in such a pathological case, only a single written chunk\n// would be consumed, and then the rest would wait (un-transformed) until\n// the results of the previous transformed chunk were consumed.\n\n'use strict';\n\nimport { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors';\nimport { nextTick } from 'node-internal:internal_process';\n\nimport { Duplex } from 'node-internal:streams_duplex';\n\nimport { getHighWaterMark } from 'node-internal:streams_state';\n\nconst streamsNodejsV24Compat =\n  Cloudflare.compatibilityFlags.enable_streams_nodejs_v24_compat; // eslint-disable-line no-undef\n\nObject.setPrototypeOf(Transform.prototype, Duplex.prototype);\nObject.setPrototypeOf(Transform, Duplex);\n\nconst kCallback = Symbol('kCallback');\n\nexport function Transform(options) {\n  if (!(this instanceof Transform)) return new Transform(options);\n\n  // TODO (ronag): This should preferably always be\n  // applied but would be semver-major. Or even better;\n  // make Transform a Readable with the Writable interface.\n  const readableHighWaterMark = options\n    ? getHighWaterMark(this, options, 'readableHighWaterMark', true)\n    : null;\n  if (readableHighWaterMark === 0) {\n    // A Duplex will buffer both on the writable and readable side while\n    // a Transform just wants to buffer hwm number of elements. To avoid\n    // buffering twice we disable buffering on the writable side.\n    options = {\n      ...options,\n      highWaterMark: null,\n      readableHighWaterMark,\n      writableHighWaterMark: options.writableHighWaterMark || 0,\n    };\n  }\n  Duplex.call(this, options);\n\n  // We have implemented the _read method, and done the other things\n  // that Readable wants before the first _read call, so unset the\n  // sync guard flag.\n  this._readableState.sync = false;\n  this[kCallback] = null;\n  if (options) {\n    if (typeof options.transform === 'function')\n      this._transform = options.transform;\n    if (typeof options.flush === 'function') this._flush = options.flush;\n  }\n\n  // When the writable side finishes, then flush out anything remaining.\n  // Backwards compat. Some Transform streams incorrectly implement _final\n  // instead of or in addition to _flush. By using 'prefinish' instead of\n  // implementing _final we continue supporting this unfortunate use case.\n  this.on('prefinish', prefinish);\n}\n\nfunction final(cb) {\n  if (typeof this._flush === 'function' && !this.destroyed) {\n    this._flush((er, data) => {\n      if (er) {\n        if (cb) {\n          cb(er);\n        } else {\n          this.destroy(er);\n        }\n        return;\n      }\n      if (data != null) {\n        this.push(data);\n      }\n      this.push(null);\n      if (cb) {\n        cb();\n      }\n    });\n  } else {\n    this.push(null);\n    if (cb) {\n      cb();\n    }\n  }\n}\n\nfunction prefinish() {\n  if (this._final !== final) {\n    final.call(this);\n  }\n}\nTransform.prototype._final = final;\n\nTransform.prototype._transform = function () {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('_transform()');\n};\n\nTransform.prototype._write = function (chunk, encoding, callback) {\n  const rState = this._readableState;\n  const wState = this._writableState;\n  const length = rState.length;\n  this._transform(chunk, encoding, (err, val) => {\n    if (err) {\n      callback(err);\n      return;\n    }\n    if (val != null) {\n      this.push(val);\n    }\n    // This is a semver-major change. Ref: https://github.com/nodejs/node/commit/557044af407376aff28a0a0800f3053bb58e9239\n    if (streamsNodejsV24Compat && rState.ended) {\n      // If user has called this.push(null) we have to delay the callback to properly propagate the new\n      // state.\n      nextTick(callback);\n      return;\n    } else if (\n      wState.ended ||\n      // Backwards compat.\n      length === rState.length ||\n      // Backwards compat.\n      rState.length < rState.highWaterMark\n    ) {\n      callback();\n    } else {\n      this[kCallback] = callback;\n    }\n  });\n};\n\nTransform.prototype._read = function (_size) {\n  if (this[kCallback]) {\n    const callback = this[kCallback];\n    this[kCallback] = null;\n    callback();\n  }\n};\n\nObject.setPrototypeOf(PassThrough.prototype, Transform.prototype);\nObject.setPrototypeOf(PassThrough, Transform);\n\nexport function PassThrough(options) {\n  if (!(this instanceof PassThrough)) return new PassThrough(options);\n  Transform.call(this, {\n    ...options,\n    transform: undefined,\n    flush: undefined,\n  });\n}\n\nPassThrough.prototype._transform = function (chunk, _, cb) {\n  cb(null, chunk);\n};\n"
  },
  {
    "path": "src/node/internal/streams_util.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unnecessary-condition */\n\nimport { AbortError } from 'node-internal:internal_errors';\nimport { constants } from 'node-internal:internal_zlib_constants';\n\nimport type { Writable } from 'node-internal:streams_writable';\nimport type { Readable } from 'node-internal:streams_readable';\nimport type { Transform } from 'node-internal:streams_transform';\nimport type { OutgoingMessage } from 'node-internal:internal_http_outgoing';\nimport type { ServerResponse } from 'node-internal:internal_http_server';\nimport type { IncomingMessage } from 'node-internal:internal_http_incoming';\n\n// We need to use Symbol.for to make these globally available\n// for interoperability with readable-stream, i.e. readable-stream\n// and node core needs to be able to read/write private state\n// from each other for proper interoperability.\nexport const kIsDestroyed = Symbol.for('nodejs.stream.destroyed');\nexport const kIsErrored = Symbol.for('nodejs.stream.errored');\nexport const kIsReadable = Symbol.for('nodejs.stream.readable');\nexport const kIsWritable = Symbol.for('nodejs.stream.writable');\nexport const kIsDisturbed = Symbol.for('nodejs.stream.disturbed');\n\nexport const kOnConstructed = Symbol('kOnConstructed');\n\nexport const kIsClosedPromise = Symbol.for('nodejs.webstream.isClosedPromise');\nexport const kControllerErrorFunction = Symbol.for(\n  'nodejs.webstream.controllerErrorFunction'\n);\n\nexport const kState = Symbol('kState');\nexport const kObjectMode = 1 << 0;\nexport const kErrorEmitted = 1 << 1;\nexport const kAutoDestroy = 1 << 2;\nexport const kEmitClose = 1 << 3;\nexport const kDestroyed = 1 << 4;\nexport const kClosed = 1 << 5;\nexport const kCloseEmitted = 1 << 6;\nexport const kErrored = 1 << 7;\nexport const kConstructed = 1 << 8;\n\nexport function isReadableNodeStream(\n  obj: any,\n  strict: boolean = false\n): boolean {\n  return !!(\n    obj &&\n    typeof obj.pipe === 'function' &&\n    typeof obj.on === 'function' &&\n    (!strict ||\n      (typeof obj.pause === 'function' && typeof obj.resume === 'function')) &&\n    (!obj._writableState || obj._readableState?.readable !== false) && // Duplex\n    (!obj._writableState || obj._readableState) // Writable has .pipe.\n  );\n}\n\nexport function isWritableNodeStream(obj: any): boolean {\n  return !!(\n    obj &&\n    typeof obj.write === 'function' &&\n    typeof obj.on === 'function' &&\n    (!obj._readableState || obj._writableState?.writable !== false) // Duplex\n  );\n}\n\nexport function isDuplexNodeStream(obj: any): boolean {\n  return !!(\n    obj &&\n    typeof obj.pipe === 'function' &&\n    obj._readableState &&\n    typeof obj.on === 'function' &&\n    typeof obj.write === 'function'\n  );\n}\n\nexport function isNodeStream(obj: any): obj is Readable | Writable | Transform {\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n  return (\n    obj &&\n    (obj._readableState != null ||\n      obj._writableState != null ||\n      (typeof obj.write === 'function' && typeof obj.on === 'function') ||\n      (typeof obj.pipe === 'function' && typeof obj.on === 'function'))\n  );\n}\n\nexport function isReadableStream(obj: any): obj is ReadableStream {\n  return !!(\n    obj &&\n    !isNodeStream(obj) &&\n    typeof obj.pipeThrough === 'function' &&\n    typeof obj.getReader === 'function' &&\n    typeof obj.cancel === 'function'\n  );\n}\n\nexport function isWritableStream(obj: any): obj is WritableStream {\n  return !!(\n    obj &&\n    !isNodeStream(obj) &&\n    typeof obj.getWriter === 'function' &&\n    typeof obj.abort === 'function'\n  );\n}\n\nexport function isTransformStream(obj: any): obj is TransformStream {\n  return !!(\n    obj &&\n    !isNodeStream(obj) &&\n    typeof obj.readable === 'object' &&\n    typeof obj.writable === 'object'\n  );\n}\n\nexport function isWebStream(\n  obj: any\n): obj is ReadableStream | WritableStream | TransformStream {\n  return (\n    isReadableStream(obj) || isWritableStream(obj) || isTransformStream(obj)\n  );\n}\n\nexport function isIterable(obj: any, isAsync?: true | false): boolean {\n  if (obj == null) return false;\n  if (isAsync === true) return typeof obj[Symbol.asyncIterator] === 'function';\n  if (isAsync === false) return typeof obj[Symbol.iterator] === 'function';\n  return (\n    typeof obj[Symbol.asyncIterator] === 'function' ||\n    typeof obj[Symbol.iterator] === 'function'\n  );\n}\n\nexport function isDestroyed(stream: any): boolean | null {\n  if (!isNodeStream(stream)) return null;\n  const wState = stream._writableState;\n  const rState = stream._readableState;\n  const state = wState || rState;\n  return !!(stream.destroyed || stream[kIsDestroyed] || state?.destroyed);\n}\n\n// Have been end():d.\nexport function isWritableEnded(\n  stream: Writable | Readable | Transform\n): boolean | null {\n  if (!isWritableNodeStream(stream)) return null;\n  if (stream.writableEnded === true) return true;\n  const wState = stream._writableState;\n  if (wState?.errored) return false;\n  if (typeof wState?.ended !== 'boolean') return null;\n  return wState.ended;\n}\n\n// Have emitted 'finish'.\nexport function isWritableFinished(\n  stream: Writable | Readable | Transform,\n  strict?: true | false | null\n): boolean | null {\n  if (!isWritableNodeStream(stream)) return null;\n  if (stream.writableFinished === true) return true;\n  const wState = stream._writableState;\n  if (wState?.errored) return false;\n  if (typeof wState?.finished !== 'boolean') return null;\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion\n  return !!(\n    wState.finished ||\n    (strict === false && wState.ended === true && wState.length === 0)\n  );\n}\n\n// Have been push(null):d.\nexport function isReadableEnded(\n  stream: Readable | Writable | Transform\n): boolean | null {\n  if (!isReadableNodeStream(stream)) return null;\n  if (stream.readableEnded === true) return true;\n  const rState = stream._readableState;\n  if (!rState || rState.errored) return false;\n  if (typeof rState?.ended !== 'boolean') return null;\n  return rState.ended;\n}\n\n// Have emitted 'end'.\nexport function isReadableFinished(\n  stream: Readable | Writable | Transform,\n  strict?: boolean\n): boolean | null {\n  if (!isReadableNodeStream(stream)) return null;\n  const rState = stream._readableState;\n  if (rState?.errored) return false;\n  if (typeof rState?.endEmitted !== 'boolean') return null;\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion\n  return !!(\n    rState.endEmitted ||\n    (strict === false && rState.ended === true && rState.length === 0)\n  );\n}\n\nexport function isReadable(\n  stream: Readable | Writable | Transform\n): boolean | null {\n  if (stream && stream[kIsReadable] != null) return stream[kIsReadable];\n  if (typeof stream?.readable !== 'boolean') return null;\n  if (isDestroyed(stream)) return false;\n  return (\n    isReadableNodeStream(stream) &&\n    stream.readable &&\n    !isReadableFinished(stream)\n  );\n}\n\nexport function isWritable(\n  stream: Readable | Writable | Transform\n): boolean | null {\n  if (stream && stream[kIsWritable] != null) return stream[kIsWritable];\n  if (typeof stream?.writable !== 'boolean') return null;\n  if (isDestroyed(stream)) return false;\n  return (\n    isWritableNodeStream(stream) && stream.writable && !isWritableEnded(stream)\n  );\n}\n\nexport function isFinished(\n  stream: Readable | Writable | Transform,\n  opts?: { readable?: boolean; writable?: boolean }\n): boolean | null {\n  if (!isNodeStream(stream)) {\n    return null;\n  }\n\n  if (isDestroyed(stream)) {\n    return true;\n  }\n\n  if (opts?.readable !== false && isReadable(stream)) {\n    return false;\n  }\n\n  if (opts?.writable !== false && isWritable(stream)) {\n    return false;\n  }\n\n  return true;\n}\n\nexport function isWritableErrored(\n  stream: Writable | Readable | Transform\n): Error | boolean | null {\n  if (!isNodeStream(stream)) {\n    return null;\n  }\n\n  if (stream.writableErrored) {\n    return stream.writableErrored;\n  }\n\n  return stream._writableState?.errored ?? null;\n}\n\nexport function isReadableErrored(\n  stream: Readable | Writable | Transform\n): Error | boolean | null {\n  if (!isNodeStream(stream)) {\n    return null;\n  }\n\n  if (stream.readableErrored) {\n    return stream.readableErrored;\n  }\n\n  return stream._readableState?.errored ?? null;\n}\n\nexport function isClosed(\n  stream: Readable | Writable | Transform\n): boolean | null {\n  if (!isNodeStream(stream)) {\n    return null;\n  }\n\n  if (typeof stream.closed === 'boolean') {\n    return stream.closed;\n  }\n\n  const wState = stream._writableState;\n  const rState = stream._readableState;\n\n  if (\n    typeof wState?.closed === 'boolean' ||\n    typeof rState?.closed === 'boolean'\n  ) {\n    return (wState?.closed || rState?.closed) ?? null;\n  }\n\n  if (typeof stream._closed === 'boolean' && isOutgoingMessage(stream)) {\n    return stream._closed;\n  }\n\n  return null;\n}\n\nexport function isOutgoingMessage(stream: unknown): stream is OutgoingMessage {\n  return (\n    stream != null &&\n    typeof stream === 'object' &&\n    '_closed' in stream &&\n    typeof stream._closed === 'boolean' &&\n    '_defaultKeepAlive' in stream &&\n    typeof stream._defaultKeepAlive === 'boolean' &&\n    '_removedConnection' in stream &&\n    typeof stream._removedConnection === 'boolean' &&\n    '_removedContLen' in stream &&\n    typeof stream._removedContLen === 'boolean'\n  );\n}\n\n// This function includes the following check that we don't include, because\n// our ServerResponse implementation does not implement it.\n// `typeof stream._sent100 === 'boolean'`\nexport function isServerResponse(stream: unknown): stream is ServerResponse {\n  return isOutgoingMessage(stream);\n}\n\nexport function isServerRequest(stream: any): stream is IncomingMessage {\n  return (\n    typeof stream._consuming === 'boolean' &&\n    typeof stream._dumped === 'boolean' &&\n    stream.req?.upgradeOrConnect === undefined\n  );\n}\n\nexport function willEmitClose(stream: any): boolean | null {\n  if (!isNodeStream(stream)) return null;\n\n  const wState = stream._writableState;\n  const rState = stream._readableState;\n  const state = wState || rState;\n\n  return (\n    (!state && isServerResponse(stream)) ||\n    !!(state?.autoDestroy && state.emitClose && state.closed === false)\n  );\n}\n\nexport function isDisturbed(stream: any): boolean {\n  return !!(\n    stream &&\n    (stream[kIsDisturbed] ?? (stream.readableDidRead || stream.readableAborted))\n  );\n}\n\nexport function isErrored(stream: any): boolean {\n  return !!(\n    stream &&\n    (stream[kIsErrored] ??\n      stream.readableErrored ??\n      stream.writableErrored ??\n      stream._readableState?.errorEmitted ??\n      stream._writableState?.errorEmitted ??\n      stream._readableState?.errored ??\n      stream._writableState?.errored)\n  );\n}\n\nconst ZLIB_FAILURES = new Set<string>([\n  ...Object.entries(constants).flatMap(([code, value]) =>\n    value < 0 ? code : []\n  ),\n  'Z_NEED_DICT',\n]);\n\nexport function handleKnownInternalErrors(\n  cause?: Error & { code?: string }\n): (Error & { code?: string }) | undefined {\n  switch (true) {\n    case cause?.code === 'ERR_STREAM_PREMATURE_CLOSE': {\n      return new AbortError(undefined, { cause });\n    }\n    case ZLIB_FAILURES.has(cause?.code ?? ''): {\n      const error = new TypeError(undefined, { cause }) as Error & {\n        code?: string;\n      };\n      error.code = cause?.code as string;\n      return error;\n    }\n    default:\n      return cause;\n  }\n}\n"
  },
  {
    "path": "src/node/internal/streams_writable.d.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport type { OutgoingMessage } from 'node:http';\nimport { Writable as _Writable, Duplex } from 'node:stream';\nimport {\n  kState,\n  kIsWritable,\n  kIsReadable,\n  kIsClosedPromise,\n  kIsDestroyed,\n} from 'node-internal:streams_util';\n\nexport declare class WritableState {\n  writable?: boolean;\n  ended?: boolean;\n  ending?: boolean;\n  finished?: boolean;\n  constructed?: boolean;\n  closed?: boolean;\n  closeEmitted?: boolean;\n  destroyed?: boolean;\n  errored?: Error | null;\n  errorEmitted?: boolean;\n  length?: number;\n  highWaterMark?: number;\n  autoDestroy?: boolean;\n  emitClose?: boolean;\n  corked?: number;\n  pendingcb?: number;\n  writing?: boolean;\n  needDrain?: boolean;\n  prefinished?: boolean;\n  finalCalled?: boolean;\n  writelen?: number;\n  [kState]: number;\n}\n\nexport declare class Writable extends _Writable {\n  _writableState: WritableState;\n  _readableState: undefined;\n  _closed: boolean;\n\n  destroy(err?: unknown, cb?: (err?: unknown) => void): this;\n  req?: OutgoingMessage;\n  errored?: Error | null;\n  writableErrored: boolean;\n  readableErrored: boolean;\n  writableFinished?: boolean;\n  writableEnded?: boolean;\n  writable: boolean;\n  readable: boolean;\n  readableEnded: undefined;\n  aborted?: boolean;\n\n  [kDestroyed]: boolean;\n  [kIsReadable]: boolean;\n  [kIsWritable]: boolean;\n  [kIsDestroyed]: boolean;\n  [kIsClosedPromise]: Promise<unknown>;\n}\n\nexport const fromWeb: typeof Duplex.fromWeb;\nexport const toWeb: typeof Duplex.toWeb;\n"
  },
  {
    "path": "src/node/internal/streams_writable.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { EventEmitter } from 'node-internal:events';\nimport * as destroyImpl from 'node-internal:streams_destroy';\nimport { Stream } from 'node-internal:streams_legacy';\nimport { Buffer } from 'node-internal:internal_buffer';\nimport { nextTick } from 'node-internal:internal_process';\nimport { normalizeEncoding } from 'node-internal:internal_utils';\nimport { validateBoolean, validateObject } from 'node-internal:validators';\nimport {\n  kState,\n  // bitfields\n  kObjectMode,\n  kErrorEmitted,\n  kAutoDestroy,\n  kEmitClose,\n  kDestroyed,\n  kClosed,\n  kCloseEmitted,\n  kErrored,\n  kConstructed,\n  kOnConstructed,\n  isDestroyed,\n  isWritable,\n  isWritableEnded,\n  isWritableStream,\n  handleKnownInternalErrors,\n} from 'node-internal:streams_util';\nimport { finished, eos, nop } from 'node-internal:streams_end_of_stream';\nimport { addAbortSignal } from 'node-internal:streams_add_abort_signal';\nimport {\n  getHighWaterMark,\n  getDefaultHighWaterMark,\n} from 'node-internal:streams_state';\n\nimport {\n  AbortError,\n  ERR_INVALID_ARG_TYPE,\n  ERR_METHOD_NOT_IMPLEMENTED,\n  ERR_MULTIPLE_CALLBACK,\n  ERR_STREAM_CANNOT_PIPE,\n  ERR_STREAM_DESTROYED,\n  ERR_STREAM_ALREADY_FINISHED,\n  ERR_STREAM_NULL_VALUES,\n  ERR_STREAM_WRITE_AFTER_END,\n  ERR_UNKNOWN_ENCODING,\n  ERR_STREAM_PREMATURE_CLOSE,\n} from 'node-internal:internal_errors';\n\nconst streamsNodejsV24Compat =\n  Cloudflare.compatibilityFlags.enable_streams_nodejs_v24_compat; // eslint-disable-line no-undef\n\nconst encoder = new globalThis.TextEncoder();\n\nconst kOnFinishedValue = Symbol('kOnFinishedValue');\nconst kErroredValue = Symbol('kErroredValue');\nconst kDefaultEncodingValue = Symbol('kDefaultEncodingValue');\nconst kWriteCbValue = Symbol('kWriteCbValue');\nconst kAfterWriteTickInfoValue = Symbol('kAfterWriteTickInfoValue');\nconst kBufferedValue = Symbol('kBufferedValue');\n\n// Bitfield flag constants for WritableState. Each constant uses left-shift (<<) to set a specific\n// bit position, allowing multiple boolean flags to be stored efficiently in a single integer (kState).\n// For example, `1 << 9` creates a value with only bit 9 set (value: 512).\nconst kSync = 1 << 9;\nconst kFinalCalled = 1 << 10;\nconst kNeedDrain = 1 << 11;\nconst kEnding = 1 << 12;\nconst kFinished = 1 << 13;\nconst kDecodeStrings = 1 << 14;\nconst kWriting = 1 << 15;\nconst kBufferProcessing = 1 << 16;\nconst kPrefinished = 1 << 17;\nconst kAllBuffers = 1 << 18;\nconst kAllNoop = 1 << 19;\nconst kOnFinished = 1 << 20;\nconst kHasWritable = 1 << 21;\nconst kWritable = 1 << 22;\nconst kCorked = 1 << 23;\nconst kDefaultUTF8Encoding = 1 << 24;\nconst kWriteCb = 1 << 25;\nconst kExpectWriteCb = 1 << 26;\nconst kAfterWriteTickInfo = 1 << 27;\nconst kAfterWritePending = 1 << 28;\nconst kBuffered = 1 << 29;\nconst kEnded = 1 << 30;\n\n// TODO(benjamingr) it is likely slower to do it this way than with free functions\nfunction makeBitMapDescriptor(bit) {\n  return {\n    // This is not a breaking change according to Node.js but better safe than sorry.\n    // Ref: https://github.com/nodejs/node/pull/49834\n    enumerable: !streamsNodejsV24Compat,\n    get() {\n      return (this[kState] & bit) !== 0;\n    },\n    set(value) {\n      if (value) this[kState] |= bit;\n      else this[kState] &= ~bit;\n    },\n  };\n}\nObject.defineProperties(WritableState.prototype, {\n  // Object stream flag to indicate whether or not this stream\n  // contains buffers or objects.\n  objectMode: makeBitMapDescriptor(kObjectMode),\n\n  // if _final has been called.\n  finalCalled: makeBitMapDescriptor(kFinalCalled),\n\n  // drain event flag.\n  needDrain: makeBitMapDescriptor(kNeedDrain),\n\n  // At the start of calling end()\n  ending: makeBitMapDescriptor(kEnding),\n\n  // When end() has been called, and returned.\n  ended: makeBitMapDescriptor(kEnded),\n\n  // When 'finish' is emitted.\n  finished: makeBitMapDescriptor(kFinished),\n\n  // Has it been destroyed.\n  destroyed: makeBitMapDescriptor(kDestroyed),\n\n  // Should we decode strings into buffers before passing to _write?\n  // this is here so that some node-core streams can optimize string\n  // handling at a lower level.\n  decodeStrings: makeBitMapDescriptor(kDecodeStrings),\n\n  // A flag to see when we're in the middle of a write.\n  writing: makeBitMapDescriptor(kWriting),\n\n  // A flag to be able to tell if the onwrite cb is called immediately,\n  // or on a later tick.  We set this to true at first, because any\n  // actions that shouldn't happen until \"later\" should generally also\n  // not happen before the first write call.\n  sync: makeBitMapDescriptor(kSync),\n\n  // A flag to know if we're processing previously buffered items, which\n  // may call the _write() callback in the same tick, so that we don't\n  // end up in an overlapped onwrite situation.\n  bufferProcessing: makeBitMapDescriptor(kBufferProcessing),\n\n  // Stream is still being constructed and cannot be\n  // destroyed until construction finished or failed.\n  // Async construction is opt in, therefore we start as\n  // constructed.\n  constructed: makeBitMapDescriptor(kConstructed),\n\n  // Emit prefinish if the only thing we're waiting for is _write cbs\n  // This is relevant for synchronous Transform streams.\n  prefinished: makeBitMapDescriptor(kPrefinished),\n\n  // True if the error was already emitted and should not be thrown again.\n  errorEmitted: makeBitMapDescriptor(kErrorEmitted),\n\n  // Should close be emitted on destroy. Defaults to true.\n  emitClose: makeBitMapDescriptor(kEmitClose),\n\n  // Should .destroy() be called after 'finish' (and potentially 'end').\n  autoDestroy: makeBitMapDescriptor(kAutoDestroy),\n\n  // Indicates whether the stream has finished destroying.\n  closed: makeBitMapDescriptor(kClosed),\n\n  // True if close has been emitted or would have been emitted\n  // depending on emitClose.\n  closeEmitted: makeBitMapDescriptor(kCloseEmitted),\n\n  allBuffers: makeBitMapDescriptor(kAllBuffers),\n  allNoop: makeBitMapDescriptor(kAllNoop),\n\n  // Indicates whether the stream has errored. When true all write() calls\n  // should return false. This is needed since when autoDestroy\n  // is disabled we need a way to tell whether the stream has failed.\n  // This is/should be a cold path.\n  errored: {\n    __proto__: null,\n    enumerable: false,\n    get() {\n      return (this[kState] & kErrored) !== 0 ? this[kErroredValue] : null;\n    },\n    set(value) {\n      if (value) {\n        this[kErroredValue] = value;\n        this[kState] |= kErrored;\n      } else {\n        this[kState] &= ~kErrored;\n      }\n    },\n  },\n\n  writable: {\n    __proto__: null,\n    enumerable: false,\n    get() {\n      return (this[kState] & kHasWritable) !== 0\n        ? (this[kState] & kWritable) !== 0\n        : undefined;\n    },\n    set(value) {\n      if (value == null) {\n        this[kState] &= ~(kHasWritable | kWritable);\n      } else if (value) {\n        this[kState] |= kHasWritable | kWritable;\n      } else {\n        this[kState] |= kHasWritable;\n        this[kState] &= ~kWritable;\n      }\n    },\n  },\n\n  defaultEncoding: {\n    __proto__: null,\n    enumerable: false,\n    get() {\n      return (this[kState] & kDefaultUTF8Encoding) !== 0\n        ? 'utf8'\n        : this[kDefaultEncodingValue];\n    },\n    set(value) {\n      if (value === 'utf8' || value === 'utf-8') {\n        this[kState] |= kDefaultUTF8Encoding;\n      } else {\n        this[kState] &= ~kDefaultUTF8Encoding;\n        this[kDefaultEncodingValue] = value;\n      }\n    },\n  },\n\n  // The callback that the user supplies to write(chunk, encoding, cb).\n  writecb: {\n    __proto__: null,\n    enumerable: false,\n    get() {\n      return (this[kState] & kWriteCb) !== 0 ? this[kWriteCbValue] : nop;\n    },\n    set(value) {\n      this[kWriteCbValue] = value;\n      if (value) {\n        this[kState] |= kWriteCb;\n      } else {\n        this[kState] &= ~kWriteCb;\n      }\n    },\n  },\n\n  // Storage for data passed to the afterWrite() callback in case of\n  // synchronous _write() completion.\n  afterWriteTickInfo: {\n    __proto__: null,\n    enumerable: false,\n    get() {\n      return (this[kState] & kAfterWriteTickInfo) !== 0\n        ? this[kAfterWriteTickInfoValue]\n        : null;\n    },\n    set(value) {\n      this[kAfterWriteTickInfoValue] = value;\n      if (value) {\n        this[kState] |= kAfterWriteTickInfo;\n      } else {\n        this[kState] &= ~kAfterWriteTickInfo;\n      }\n    },\n  },\n\n  buffered: {\n    __proto__: null,\n    enumerable: false,\n    get() {\n      return (this[kState] & kBuffered) !== 0 ? this[kBufferedValue] : [];\n    },\n    set(value) {\n      this[kBufferedValue] = value;\n      if (value) {\n        this[kState] |= kBuffered;\n      } else {\n        this[kState] &= ~kBuffered;\n      }\n    },\n  },\n});\n\n// ======================================================================================\n// WritableState\n\nexport function WritableState(options, stream, isDuplex) {\n  // Bit map field to store WritableState more efficiently with 1 bit per field\n  // instead of a V8 slot per field.\n  this[kState] = kSync | kConstructed | kEmitClose | kAutoDestroy;\n\n  if (options?.objectMode) this[kState] |= kObjectMode;\n\n  if (isDuplex && options?.writableObjectMode) this[kState] |= kObjectMode;\n\n  // The point at which write() starts returning false\n  // Note: 0 is a valid value, means that we always return false if\n  // the entire buffer is not flushed immediately on write().\n  this.highWaterMark = options\n    ? getHighWaterMark(this, options, 'writableHighWaterMark', isDuplex)\n    : getDefaultHighWaterMark(false);\n\n  if (!options || options.decodeStrings !== false)\n    this[kState] |= kDecodeStrings;\n\n  // Should close be emitted on destroy. Defaults to true.\n  if (options && options.emitClose === false) this[kState] &= ~kEmitClose;\n\n  // Should .destroy() be called after 'end' (and potentially 'finish').\n  if (options && options.autoDestroy === false) this[kState] &= ~kAutoDestroy;\n\n  // Crypto is kind of old and crusty.  Historically, its default string\n  // encoding is 'binary' so we have to make this configurable.\n  // Everything else in the universe uses 'utf8', though.\n  const defaultEncoding = options ? options.defaultEncoding : null;\n  if (\n    defaultEncoding == null ||\n    defaultEncoding === 'utf8' ||\n    defaultEncoding === 'utf-8'\n  ) {\n    this[kState] |= kDefaultUTF8Encoding;\n  } else if (Buffer.isEncoding(defaultEncoding)) {\n    this[kState] &= ~kDefaultUTF8Encoding;\n    this[kDefaultEncodingValue] = defaultEncoding;\n  } else if (streamsNodejsV24Compat) {\n    // This is a semver-major change. Ref: https://github.com/nodejs/node/pull/46322\n    throw new ERR_UNKNOWN_ENCODING(defaultEncoding);\n  } else {\n    this[kDefaultEncodingValue] = defaultEncoding;\n  }\n\n  // Not an actual buffer we keep track of, but a measurement\n  // of how much we're waiting to get pushed to some underlying\n  // socket or file.\n  this.length = 0;\n\n  // When true all writes will be buffered until .uncork() call.\n  this.corked = 0;\n\n  // The callback that's passed to _write(chunk, cb).\n  this.onwrite = onwrite.bind(undefined, stream);\n\n  // The amount that is being written when _write is called.\n  this.writelen = 0;\n\n  resetBuffer(this);\n\n  // Number of pending user-supplied write callbacks\n  // this must be 0 before 'finish' can be emitted.\n  this.pendingcb = 0;\n}\n\nfunction resetBuffer(state) {\n  state[kBufferedValue] = null;\n  state.bufferedIndex = 0;\n  state[kState] |= kAllBuffers | kAllNoop;\n  state[kState] &= ~kBuffered;\n}\n\nWritableState.prototype.getBuffer = function getBuffer() {\n  return (this[kState] & kBuffered) === 0\n    ? []\n    : this.buffered.slice(this.bufferedIndex);\n};\n\nObject.defineProperty(WritableState.prototype, 'bufferedRequestCount', {\n  __proto__: null,\n  get() {\n    return (this[kState] & kBuffered) === 0\n      ? 0\n      : this[kBufferedValue].length - this.bufferedIndex;\n  },\n});\n\nWritableState.prototype[kOnConstructed] = function onConstructed(stream) {\n  if ((this[kState] & kWriting) === 0) {\n    clearBuffer(stream, this);\n  }\n\n  if ((this[kState] & kEnding) !== 0) {\n    finishMaybe(stream, this);\n  }\n};\n\n// ======================================================================================\n// Writable\n\nWritable.WritableState = WritableState;\n\nObject.setPrototypeOf(Writable.prototype, Stream.prototype);\nObject.setPrototypeOf(Writable, Stream);\n\nexport function Writable(options) {\n  if (!(this instanceof Writable)) return new Writable(options);\n\n  this._events ??= {\n    close: undefined,\n    error: undefined,\n    prefinish: undefined,\n    finish: undefined,\n    drain: undefined,\n    // Skip uncommon events...\n    // [destroyImpl.kConstruct]: undefined,\n    // [destroyImpl.kDestroy]: undefined,\n  };\n\n  this._writableState = new WritableState(options, this, false);\n\n  if (options) {\n    if (typeof options.write === 'function') this._write = options.write;\n\n    if (typeof options.writev === 'function') this._writev = options.writev;\n\n    if (typeof options.destroy === 'function') this._destroy = options.destroy;\n\n    if (typeof options.final === 'function') this._final = options.final;\n\n    if (typeof options.construct === 'function')\n      this._construct = options.construct;\n\n    if (options.signal) addAbortSignal(options.signal, this);\n  }\n\n  Stream.call(this, options);\n\n  if (this._construct != null) {\n    destroyImpl.construct(this, () => {\n      this._writableState[kOnConstructed](this);\n    });\n  }\n}\n\nObject.defineProperty(Writable, Symbol.hasInstance, {\n  __proto__: null,\n  value: function (object) {\n    if (Function.prototype[Symbol.hasInstance].call(this, object)) return true;\n    if (this !== Writable) return false;\n\n    return object && object._writableState instanceof WritableState;\n  },\n});\n\n// Otherwise people can pipe Writable streams, which is just wrong.\nWritable.prototype.pipe = function () {\n  destroyImpl.errorOrDestroy(this, new ERR_STREAM_CANNOT_PIPE());\n};\n\nfunction _write(stream, chunk, encoding, cb) {\n  const state = stream._writableState;\n\n  if (cb == null || typeof cb !== 'function') {\n    cb = nop;\n  }\n\n  if (chunk === null) {\n    throw new ERR_STREAM_NULL_VALUES();\n  }\n\n  if ((state[kState] & kObjectMode) === 0) {\n    if (!encoding) {\n      encoding =\n        (state[kState] & kDefaultUTF8Encoding) !== 0\n          ? 'utf8'\n          : state.defaultEncoding;\n    } else if (encoding !== 'buffer' && !Buffer.isEncoding(encoding)) {\n      throw new ERR_UNKNOWN_ENCODING(encoding);\n    }\n\n    if (typeof chunk === 'string') {\n      if ((state[kState] & kDecodeStrings) !== 0) {\n        chunk = Buffer.from(chunk, encoding);\n        encoding = 'buffer';\n      }\n    } else if (chunk instanceof Buffer) {\n      encoding = 'buffer';\n    } else if (Stream._isArrayBufferView(chunk)) {\n      chunk = Stream._uint8ArrayToBuffer(chunk);\n      encoding = 'buffer';\n    } else {\n      throw new ERR_INVALID_ARG_TYPE(\n        'chunk',\n        ['string', 'Buffer', 'TypedArray', 'DataView'],\n        chunk\n      );\n    }\n  }\n\n  let err;\n  if ((state[kState] & kEnding) !== 0) {\n    err = new ERR_STREAM_WRITE_AFTER_END();\n  } else if ((state[kState] & kDestroyed) !== 0) {\n    err = new ERR_STREAM_DESTROYED('write');\n  }\n\n  if (err) {\n    nextTick(cb, err);\n    destroyImpl.errorOrDestroy(stream, err, true);\n    return err;\n  }\n\n  state.pendingcb++;\n  return writeOrBuffer(stream, state, chunk, encoding, cb);\n}\n\nWritable.prototype.write = function (chunk, encoding, cb) {\n  if (encoding != null && typeof encoding === 'function') {\n    cb = encoding;\n    encoding = null;\n  }\n\n  return _write(this, chunk, encoding, cb) === true;\n};\n\nWritable.prototype.cork = function () {\n  const state = this._writableState;\n\n  state[kState] |= kCorked;\n  state.corked++;\n};\n\nWritable.prototype.uncork = function () {\n  const state = this._writableState;\n\n  if (state.corked) {\n    state.corked--;\n\n    if (!state.corked) {\n      state[kState] &= ~kCorked;\n    }\n\n    if ((state[kState] & kWriting) === 0) clearBuffer(this, state);\n  }\n};\n\nWritable.prototype.setDefaultEncoding = function setDefaultEncoding(encoding) {\n  // node::ParseEncoding() requires lower case.\n  if (typeof encoding === 'string') encoding = encoding.toLowerCase();\n  if (!Buffer.isEncoding(encoding)) throw new ERR_UNKNOWN_ENCODING(encoding);\n  this._writableState.defaultEncoding = encoding;\n  return this;\n};\n\n// If we're already writing something, then just put this\n// in the queue, and wait our turn.  Otherwise, call _write\n// If we return false, then we need a drain event, so set that flag.\nfunction writeOrBuffer(stream, state, chunk, encoding, callback) {\n  const len = (state[kState] & kObjectMode) !== 0 ? 1 : chunk.length;\n\n  state.length += len;\n\n  // This is a semver-major change. Ref: https://github.com/nodejs/node/commit/557044af407376aff28a0a0800f3053bb58e9239\n  //\n  // The timing of backpressure (ret) calculation relative to _write() execution is critical.\n  // When _write() completes synchronously and modifies state.length, calculating ret before\n  // the write uses stale buffer state, leading to incorrect needDrain signaling and stream hangs.\n  // v24+ calculates ret after _write() to ensure backpressure reflects post-write buffer state.\n  let ret;\n  if (!streamsNodejsV24Compat) {\n    ret = state.length < state.highWaterMark || state.length === 0;\n    // We must ensure that previous needDrain will not be reset to false.\n    if (!ret) {\n      state[kState] |= kNeedDrain;\n    }\n  }\n\n  if (\n    (state[kState] & (kWriting | kErrored | kCorked | kConstructed)) !==\n    kConstructed\n  ) {\n    if ((state[kState] & kBuffered) === 0) {\n      state[kState] |= kBuffered;\n      state[kBufferedValue] = [];\n    }\n\n    state[kBufferedValue].push({ chunk, encoding, callback });\n    if ((state[kState] & kAllBuffers) !== 0 && encoding !== 'buffer') {\n      state[kState] &= ~kAllBuffers;\n    }\n    if ((state[kState] & kAllNoop) !== 0 && callback !== nop) {\n      state[kState] &= ~kAllNoop;\n    }\n  } else {\n    state.writelen = len;\n    if (callback !== nop) {\n      state.writecb = callback;\n    }\n    state[kState] |= kWriting | kSync | kExpectWriteCb;\n    stream._write(chunk, encoding, state.onwrite);\n    state[kState] &= ~kSync;\n  }\n\n  // This is a semver-major change. Ref: https://github.com/nodejs/node/commit/557044af407376aff28a0a0800f3053bb58e9239\n  // For v24+, calculate ret after _write() to observe post-write buffer state.\n  if (streamsNodejsV24Compat) {\n    ret = state.length < state.highWaterMark || state.length === 0;\n    // We must ensure that previous needDrain will not be reset to false.\n    if (!ret) {\n      state[kState] |= kNeedDrain;\n    }\n  }\n\n  // Return false if errored or destroyed in order to break\n  // any synchronous while(stream.write(data)) loops.\n  return ret && (state[kState] & (kDestroyed | kErrored)) === 0;\n}\n\nfunction doWrite(stream, state, writev, len, chunk, encoding, cb) {\n  state.writelen = len;\n  if (cb !== nop) {\n    state.writecb = cb;\n  }\n  state[kState] |= kWriting | kSync | kExpectWriteCb;\n  if ((state[kState] & kDestroyed) !== 0)\n    state.onwrite(new ERR_STREAM_DESTROYED('write'));\n  else if (writev) stream._writev(chunk, state.onwrite);\n  else stream._write(chunk, encoding, state.onwrite);\n  state[kState] &= ~kSync;\n}\n\nfunction onwriteError(stream, state, er, cb) {\n  --state.pendingcb;\n  cb(er);\n  // Ensure callbacks are invoked even when autoDestroy is\n  // not enabled. Passing `er` here doesn't make sense since\n  // it's related to one specific write, not to the buffered\n  // writes.\n  errorBuffer(state);\n  // This can emit error, but error must always follow cb.\n  destroyImpl.errorOrDestroy(stream, er);\n}\n\nfunction onwrite(stream, er) {\n  const state = stream._writableState;\n\n  if ((state[kState] & kExpectWriteCb) === 0) {\n    destroyImpl.errorOrDestroy(stream, new ERR_MULTIPLE_CALLBACK());\n    return;\n  }\n\n  const sync = (state[kState] & kSync) !== 0;\n  const cb = (state[kState] & kWriteCb) !== 0 ? state[kWriteCbValue] : nop;\n\n  state.writecb = null;\n  state[kState] &= ~(kWriting | kExpectWriteCb);\n  state.length -= state.writelen;\n  state.writelen = 0;\n\n  if (er) {\n    // Avoid V8 leak, https://github.com/nodejs/node/pull/34103#issuecomment-652002364\n    er.stack; // eslint-disable-line @typescript-eslint/no-unused-expressions\n\n    if ((state[kState] & kErrored) === 0) {\n      state[kErroredValue] = er;\n      state[kState] |= kErrored;\n    }\n\n    // In case of duplex streams we need to notify the readable side of the\n    // error.\n    if (stream._readableState && !stream._readableState.errored) {\n      stream._readableState.errored = er;\n    }\n\n    if (sync) {\n      nextTick(onwriteError, stream, state, er, cb);\n    } else {\n      onwriteError(stream, state, er, cb);\n    }\n  } else {\n    if ((state[kState] & kBuffered) !== 0) {\n      clearBuffer(stream, state);\n    }\n\n    if (sync) {\n      const needDrain =\n        (state[kState] & kNeedDrain) !== 0 && state.length === 0;\n      const needTick =\n        needDrain || state[kState] & (kDestroyed !== 0) || cb !== nop;\n\n      // It is a common case that the callback passed to .write() is always\n      // the same. In that case, we do not schedule a new nextTick(), but\n      // rather just increase a counter, to improve performance and avoid\n      // memory allocations.\n      if (cb === nop) {\n        if ((state[kState] & kAfterWritePending) === 0 && needTick) {\n          nextTick(afterWrite, stream, state, 1, cb);\n          state[kState] |= kAfterWritePending;\n        } else {\n          state.pendingcb--;\n          if ((state[kState] & kEnding) !== 0) {\n            finishMaybe(stream, state, true);\n          }\n        }\n      } else if (\n        (state[kState] & kAfterWriteTickInfo) !== 0 &&\n        state[kAfterWriteTickInfoValue].cb === cb\n      ) {\n        state[kAfterWriteTickInfoValue].count++;\n      } else if (needTick) {\n        state[kAfterWriteTickInfoValue] = { count: 1, cb, stream, state };\n        nextTick(afterWriteTick, state[kAfterWriteTickInfoValue]);\n        state[kState] |= kAfterWritePending | kAfterWriteTickInfo;\n      } else {\n        state.pendingcb--;\n        if ((state[kState] & kEnding) !== 0) {\n          finishMaybe(stream, state, true);\n        }\n      }\n    } else {\n      afterWrite(stream, state, 1, cb);\n    }\n  }\n}\n\nfunction afterWriteTick({ stream, state, count, cb }) {\n  state[kState] &= ~kAfterWriteTickInfo;\n  state[kAfterWriteTickInfoValue] = null;\n  return afterWrite(stream, state, count, cb);\n}\n\nfunction afterWrite(stream, state, count, cb) {\n  state[kState] &= ~kAfterWritePending;\n\n  const needDrain =\n    (state[kState] & (kEnding | kNeedDrain | kDestroyed)) === kNeedDrain &&\n    state.length === 0;\n  if (needDrain) {\n    state[kState] &= ~kNeedDrain;\n    stream.emit('drain');\n  }\n\n  // This is a semver-major change. Ref: https://github.com/nodejs/node/pull/44312/files\n  const callbackValue = streamsNodejsV24Compat ? null : undefined;\n  while (count-- > 0) {\n    state.pendingcb--;\n    cb(callbackValue);\n  }\n\n  if ((state[kState] & kDestroyed) !== 0) {\n    errorBuffer(state);\n  }\n\n  if ((state[kState] & kEnding) !== 0) {\n    finishMaybe(stream, state, true);\n  }\n}\n\n// If there's something in the buffer waiting, then invoke callbacks.\nfunction errorBuffer(state) {\n  if ((state[kState] & kWriting) !== 0) {\n    return;\n  }\n\n  if ((state[kState] & kBuffered) !== 0) {\n    for (let n = state.bufferedIndex; n < state.buffered.length; ++n) {\n      const { chunk, callback } = state[kBufferedValue][n];\n      const len = (state[kState] & kObjectMode) !== 0 ? 1 : chunk.length;\n      state.length -= len;\n      callback(state.errored ?? new ERR_STREAM_DESTROYED('write'));\n    }\n  }\n\n  callFinishedCallbacks(\n    state,\n    state.errored ?? new ERR_STREAM_DESTROYED('end')\n  );\n\n  resetBuffer(state);\n}\n\n// If there's something in the buffer waiting, then process it.\nfunction clearBuffer(stream, state) {\n  if (\n    (state[kState] &\n      (kDestroyed | kBufferProcessing | kCorked | kBuffered | kConstructed)) !==\n    (kBuffered | kConstructed)\n  ) {\n    return;\n  }\n\n  const objectMode = (state[kState] & kObjectMode) !== 0;\n  const { [kBufferedValue]: buffered, bufferedIndex } = state;\n  const bufferedLength = buffered.length - bufferedIndex;\n\n  if (!bufferedLength) {\n    return;\n  }\n\n  let i = bufferedIndex;\n\n  state[kState] |= kBufferProcessing;\n  if (bufferedLength > 1 && stream._writev) {\n    state.pendingcb -= bufferedLength - 1;\n\n    const callback =\n      (state[kState] & kAllNoop) !== 0\n        ? nop\n        : (err) => {\n            for (let n = i; n < buffered.length; ++n) {\n              buffered[n].callback(err);\n            }\n          };\n    // Make a copy of `buffered` if it's going to be used by `callback` above,\n    // since `doWrite` will mutate the array.\n    const chunks =\n      (state[kState] & kAllNoop) !== 0 && i === 0\n        ? buffered\n        : buffered.slice(i);\n    chunks.allBuffers = (state[kState] & kAllBuffers) !== 0;\n\n    doWrite(stream, state, true, state.length, chunks, '', callback);\n\n    resetBuffer(state);\n  } else {\n    do {\n      const { chunk, encoding, callback } = buffered[i];\n      buffered[i++] = null;\n      const len = objectMode ? 1 : chunk.length;\n      doWrite(stream, state, false, len, chunk, encoding, callback);\n    } while (i < buffered.length && (state[kState] & kWriting) === 0);\n\n    if (i === buffered.length) {\n      resetBuffer(state);\n    } else if (i > 256) {\n      buffered.splice(0, i);\n      state.bufferedIndex = 0;\n    } else {\n      state.bufferedIndex = i;\n    }\n  }\n  state[kState] &= ~kBufferProcessing;\n}\n\nWritable.prototype._write = function (chunk, encoding, cb) {\n  if (this._writev) {\n    this._writev([{ chunk, encoding }], cb);\n  } else {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('_write()');\n  }\n};\n\nWritable.prototype._writev = null;\n\nWritable.prototype.end = function (chunk, encoding, cb) {\n  const state = this._writableState;\n\n  if (typeof chunk === 'function') {\n    cb = chunk;\n    chunk = null;\n    encoding = null;\n  } else if (typeof encoding === 'function') {\n    cb = encoding;\n    encoding = null;\n  }\n\n  let err;\n\n  if (chunk != null) {\n    const ret = _write(this, chunk, encoding);\n    if (ret instanceof Error) {\n      err = ret;\n    }\n  }\n\n  // .end() fully uncorks.\n  if ((state[kState] & kCorked) !== 0) {\n    state.corked = 1;\n    this.uncork();\n  }\n\n  if (err) {\n    // Do nothing...\n  } else if ((state[kState] & (kEnding | kErrored)) === 0) {\n    // This is forgiving in terms of unnecessary calls to end() and can hide\n    // logic errors. However, usually such errors are harmless and causing a\n    // hard error can be disproportionately destructive. It is not always\n    // trivial for the user to determine whether end() needs to be called\n    // or not.\n\n    state[kState] |= kEnding;\n    finishMaybe(this, state, true);\n    state[kState] |= kEnded;\n  } else if ((state[kState] & kFinished) !== 0) {\n    err = new ERR_STREAM_ALREADY_FINISHED('end');\n  } else if ((state[kState] & kDestroyed) !== 0) {\n    err = new ERR_STREAM_DESTROYED('end');\n  }\n\n  if (typeof cb === 'function') {\n    // This is a semver-major change. Ref: https://github.com/nodejs/node/pull/44312\n    if (streamsNodejsV24Compat) {\n      if (err) {\n        nextTick(cb, err);\n      } else if ((state[kState] & kErrored) !== 0) {\n        nextTick(cb, state[kErroredValue]);\n      } else if ((state[kState] & kFinished) !== 0) {\n        nextTick(cb, null);\n      } else {\n        state[kState] |= kOnFinished;\n        state[kOnFinishedValue] ??= [];\n        state[kOnFinishedValue].push(cb);\n      }\n    } else {\n      if (err || (state[kState] & kFinished) !== 0) {\n        nextTick(cb, err);\n      } else if ((state[kState] & kErrored) !== 0) {\n        nextTick(cb, state[kErroredValue]);\n      } else {\n        state[kState] |= kOnFinished;\n        state[kOnFinishedValue] ??= [];\n        state[kOnFinishedValue].push(cb);\n      }\n    }\n  }\n\n  return this;\n};\n\nfunction needFinish(state) {\n  return (\n    // State is ended && constructed but not destroyed, finished, writing, errorEmitted or closedEmitted\n    (state[kState] &\n      (kEnding |\n        kDestroyed |\n        kConstructed |\n        kFinished |\n        kWriting |\n        kErrorEmitted |\n        kCloseEmitted |\n        kErrored |\n        kBuffered)) ===\n      (kEnding | kConstructed) && state.length === 0\n  );\n}\nfunction onFinish(stream, state, err) {\n  if ((state[kState] & kPrefinished) !== 0) {\n    destroyImpl.errorOrDestroy(stream, err ?? new ERR_MULTIPLE_CALLBACK());\n    return;\n  }\n  state.pendingcb--;\n  if (err) {\n    callFinishedCallbacks(state, err);\n    destroyImpl.errorOrDestroy(stream, err, (state[kState] & kSync) !== 0);\n  } else if (needFinish(state)) {\n    state[kState] |= kPrefinished;\n    stream.emit('prefinish');\n    // Backwards compat. Don't check state.sync here.\n    // Some streams assume 'finish' will be emitted\n    // asynchronously relative to _final callback.\n    state.pendingcb++;\n    nextTick(finish, stream, state);\n  }\n}\n\nfunction prefinish(stream, state) {\n  if ((state[kState] & (kPrefinished | kFinalCalled)) !== 0) {\n    return;\n  }\n\n  if (\n    typeof stream._final === 'function' &&\n    (state[kState] & kDestroyed) === 0\n  ) {\n    state[kState] |= kFinalCalled | kSync;\n    state.pendingcb++;\n\n    try {\n      stream._final((err) => onFinish(stream, state, err));\n    } catch (err) {\n      onFinish(stream, state, err);\n    }\n\n    state[kState] &= ~kSync;\n  } else {\n    state[kState] |= kFinalCalled | kPrefinished;\n    stream.emit('prefinish');\n  }\n}\n\nfunction finishMaybe(stream, state, sync) {\n  if (needFinish(state)) {\n    prefinish(stream, state);\n    if (state.pendingcb === 0) {\n      if (sync) {\n        state.pendingcb++;\n        nextTick(\n          (stream, state) => {\n            if (needFinish(state)) {\n              finish(stream, state);\n            } else {\n              state.pendingcb--;\n            }\n          },\n          stream,\n          state\n        );\n      } else if (needFinish(state)) {\n        state.pendingcb++;\n        finish(stream, state);\n      }\n    }\n  }\n}\n\nfunction finish(stream, state) {\n  state.pendingcb--;\n  state[kState] |= kFinished;\n\n  callFinishedCallbacks(state, null);\n\n  stream.emit('finish');\n\n  if ((state[kState] & kAutoDestroy) !== 0) {\n    // In case of duplex streams we need a way to detect\n    // if the readable side is ready for autoDestroy as well.\n    const rState = stream._readableState;\n    const autoDestroy =\n      !rState ||\n      (rState.autoDestroy &&\n        // We don't expect the readable to ever 'end'\n        // if readable is explicitly set to false.\n        (rState.endEmitted || rState.readable === false));\n    if (autoDestroy) {\n      stream.destroy();\n    }\n  }\n}\n\nfunction callFinishedCallbacks(state, err) {\n  if ((state[kState] & kOnFinished) === 0) {\n    return;\n  }\n\n  const onfinishCallbacks = state[kOnFinishedValue];\n  // This is a semver-major change. Ref: https://github.com/nodejs/node/pull/44312\n  state[kOnFinishedValue] = streamsNodejsV24Compat ? null : undefined;\n  state[kState] &= ~kOnFinished;\n  for (let i = 0; i < onfinishCallbacks.length; i++) {\n    onfinishCallbacks[i](err);\n  }\n}\n\nObject.defineProperties(Writable.prototype, {\n  closed: {\n    __proto__: null,\n    get() {\n      return this._writableState\n        ? (this._writableState[kState] & kClosed) !== 0\n        : false;\n    },\n  },\n\n  destroyed: {\n    __proto__: null,\n    get() {\n      return this._writableState\n        ? (this._writableState[kState] & kDestroyed) !== 0\n        : false;\n    },\n    set(value) {\n      // Backward compatibility, the user is explicitly managing destroyed.\n      if (!this._writableState) return;\n\n      if (value) this._writableState[kState] |= kDestroyed;\n      else this._writableState[kState] &= ~kDestroyed;\n    },\n  },\n\n  writable: {\n    __proto__: null,\n    get() {\n      const w = this._writableState;\n      // w.writable === false means that this is part of a Duplex stream\n      // where the writable side was disabled upon construction.\n      // Compat. The user might manually disable writable side through\n      // deprecated setter.\n      return (\n        !!w &&\n        w.writable !== false &&\n        (w[kState] & (kEnding | kEnded | kDestroyed | kErrored)) === 0\n      );\n    },\n    set(val) {\n      // Backwards compatible.\n      if (this._writableState) {\n        this._writableState.writable = !!val;\n      }\n    },\n  },\n\n  writableFinished: {\n    __proto__: null,\n    get() {\n      const state = this._writableState;\n      return state ? (state[kState] & kFinished) !== 0 : false;\n    },\n  },\n\n  writableObjectMode: {\n    __proto__: null,\n    get() {\n      const state = this._writableState;\n      return state ? (state[kState] & kObjectMode) !== 0 : false;\n    },\n  },\n\n  writableBuffer: {\n    __proto__: null,\n    get() {\n      const state = this._writableState;\n      return state && state.getBuffer();\n    },\n  },\n\n  writableEnded: {\n    __proto__: null,\n    get() {\n      const state = this._writableState;\n      return state ? (state[kState] & kEnding) !== 0 : false;\n    },\n  },\n\n  writableNeedDrain: {\n    __proto__: null,\n    get() {\n      const state = this._writableState;\n      return state\n        ? (state[kState] & (kDestroyed | kEnding | kNeedDrain)) === kNeedDrain\n        : false;\n    },\n  },\n\n  writableHighWaterMark: {\n    __proto__: null,\n    get() {\n      const state = this._writableState;\n      return state?.highWaterMark;\n    },\n  },\n\n  writableCorked: {\n    __proto__: null,\n    get() {\n      const state = this._writableState;\n      return state ? state.corked : 0;\n    },\n  },\n\n  writableLength: {\n    __proto__: null,\n    get() {\n      const state = this._writableState;\n      return state?.length;\n    },\n  },\n\n  errored: {\n    __proto__: null,\n    enumerable: false,\n    get() {\n      const state = this._writableState;\n      return state ? state.errored : null;\n    },\n  },\n\n  writableAborted: {\n    __proto__: null,\n    get: function () {\n      const state = this._writableState;\n      return (\n        (state[kState] & (kHasWritable | kWritable)) !== kHasWritable &&\n        (state[kState] & (kDestroyed | kErrored)) !== 0 &&\n        (state[kState] & kFinished) === 0\n      );\n    },\n  },\n});\n\nconst destroy = destroyImpl.destroy;\nWritable.prototype.destroy = function (err, cb) {\n  const state = this._writableState;\n\n  // Invoke pending callbacks.\n  if (\n    (state[kState] & (kBuffered | kOnFinished)) !== 0 &&\n    (state[kState] & kDestroyed) === 0\n  ) {\n    nextTick(errorBuffer, state);\n  }\n\n  destroy.call(this, err, cb);\n  return this;\n};\n\nWritable.prototype._undestroy = destroyImpl.undestroy;\nWritable.prototype._destroy = function (err, cb) {\n  cb(err);\n};\n\nWritable.prototype[EventEmitter.captureRejectionSymbol] = function (err) {\n  this.destroy(err);\n};\n\nexport function fromWeb(writableStream, options) {\n  return newStreamWritableFromWritableStream(writableStream, options);\n}\n\nexport function toWeb(streamWritable) {\n  return newWritableStreamFromStreamWritable(streamWritable);\n}\n\nWritable.fromWeb = fromWeb;\nWritable.toWeb = toWeb;\n\nWritable.prototype[Symbol.asyncDispose] = async function () {\n  let error;\n  if (!this.destroyed) {\n    error = this.writableFinished ? null : new AbortError();\n    this.destroy(error);\n  }\n  await new Promise((resolve, reject) =>\n    eos(this, (err) =>\n      err && err.name !== 'AbortError' ? reject(err) : resolve(null)\n    )\n  );\n};\n\n/**\n * @param {Writable} streamWritable\n * @returns {WritableStream}\n */\nexport function newWritableStreamFromStreamWritable(streamWritable) {\n  // Not using the internal/streams/utils isWritableNodeStream utility\n  // here because it will return false if streamWritable is a Duplex\n  // whose writable option is false. For a Duplex that is not writable,\n  // we want it to pass this check but return a closed WritableStream.\n  // We check if the given stream is a stream.Writable or http.OutgoingMessage\n  const checkIfWritableOrOutgoingMessage =\n    streamWritable &&\n    typeof streamWritable?.write === 'function' &&\n    typeof streamWritable?.on === 'function';\n  if (!checkIfWritableOrOutgoingMessage) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'streamWritable',\n      'stream.Writable',\n      streamWritable\n    );\n  }\n\n  if (isDestroyed(streamWritable) || !isWritable(streamWritable)) {\n    const writable = new globalThis.WritableStream();\n    writable.close();\n    return writable;\n  }\n\n  const highWaterMark = streamWritable.writableHighWaterMark;\n  const strategy = streamWritable.writableObjectMode\n    ? new globalThis.CountQueuingStrategy({ highWaterMark })\n    : { highWaterMark };\n\n  let controller;\n  let backpressurePromise;\n  let closed;\n\n  function onDrain() {\n    if (backpressurePromise !== undefined) backpressurePromise.resolve();\n  }\n\n  const cleanup = finished(streamWritable, (error) => {\n    error = handleKnownInternalErrors(error);\n\n    cleanup();\n    // This is a protection against non-standard, legacy streams\n    // that happen to emit an error event again after finished is called.\n    streamWritable.on('error', () => {});\n    if (error != null) {\n      if (backpressurePromise !== undefined) backpressurePromise.reject(error);\n      // If closed is not undefined, the error is happening\n      // after the WritableStream close has already started.\n      // We need to reject it here.\n      if (closed !== undefined) {\n        closed.reject(error);\n        closed = undefined;\n      }\n      controller.error(error);\n      controller = undefined;\n      return;\n    }\n\n    if (closed !== undefined) {\n      closed.resolve();\n      closed = undefined;\n      return;\n    }\n    controller.error(new AbortError());\n    controller = undefined;\n  });\n\n  streamWritable.on('drain', onDrain);\n\n  return new globalThis.WritableStream(\n    {\n      start(c) {\n        controller = c;\n      },\n\n      write(chunk) {\n        if (streamWritable.writableNeedDrain || !streamWritable.write(chunk)) {\n          backpressurePromise = Promise.withResolvers();\n          return backpressurePromise.promise.finally(() => {\n            backpressurePromise = undefined;\n          });\n        }\n      },\n\n      abort(reason) {\n        destroy(streamWritable, reason);\n      },\n\n      close() {\n        if (closed === undefined && !isWritableEnded(streamWritable)) {\n          closed = Promise.withResolvers();\n          streamWritable.end();\n          return closed.promise;\n        }\n\n        controller = undefined;\n        return Promise.resolve();\n      },\n    },\n    strategy\n  );\n}\n\n/**\n * @param {WritableStream} writableStream\n * @param {{\n *   decodeStrings? : boolean,\n *   highWaterMark? : number,\n *   objectMode? : boolean,\n *   signal? : AbortSignal,\n * }} [options]\n * @returns {Writable}\n */\nexport function newStreamWritableFromWritableStream(\n  writableStream,\n  options = {}\n) {\n  if (!isWritableStream(writableStream)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'writableStream',\n      'WritableStream',\n      writableStream\n    );\n  }\n\n  validateObject(options, 'options');\n  const {\n    highWaterMark,\n    decodeStrings = true,\n    objectMode = false,\n    signal,\n  } = options;\n\n  validateBoolean(objectMode, 'options.objectMode');\n  validateBoolean(decodeStrings, 'options.decodeStrings');\n\n  const writer = writableStream.getWriter();\n  let closed = false;\n\n  const writable = new Writable({\n    highWaterMark,\n    objectMode,\n    decodeStrings,\n    signal,\n\n    writev(chunks, callback) {\n      function done(error) {\n        error = error.filter((e) => e);\n        try {\n          callback(error.length === 0 ? undefined : error);\n        } catch (error) {\n          // In a next tick because this is happening within\n          // a promise context, and if there are any errors\n          // thrown we don't want those to cause an unhandled\n          // rejection. Let's just escape the promise and\n          // handle it separately.\n          nextTick(() => destroy(writable, error));\n        }\n      }\n\n      writer.ready.then(() => {\n        return Promise.all(chunks.map((data) => writer.write(data))).then(\n          done,\n          done\n        );\n      }, done);\n    },\n\n    write(chunk, encoding, callback) {\n      if (typeof chunk === 'string' && decodeStrings && !objectMode) {\n        const enc = normalizeEncoding(encoding);\n\n        if (enc === 'utf8') {\n          chunk = encoder.encode(chunk);\n        } else {\n          chunk = Buffer.from(chunk, encoding);\n          chunk = new Uint8Array(\n            chunk.buffer,\n            chunk.byteOffset,\n            chunk.byteLength\n          );\n        }\n      }\n\n      function done(error) {\n        try {\n          callback(error);\n        } catch (error) {\n          destroy(writable, error);\n        }\n      }\n\n      writer.ready.then(() => {\n        return writer.write(chunk).then(done, done);\n      }, done);\n    },\n\n    destroy(error, callback) {\n      function done() {\n        try {\n          callback(error);\n        } catch (error) {\n          // In a next tick because this is happening within\n          // a promise context, and if there are any errors\n          // thrown we don't want those to cause an unhandled\n          // rejection. Let's just escape the promise and\n          // handle it separately.\n          nextTick(() => {\n            throw error;\n          });\n        }\n      }\n\n      if (!closed) {\n        if (error != null) {\n          writer.abort(error).then(done, done);\n        } else {\n          writer.close().then(done, done);\n        }\n        return;\n      }\n\n      done();\n    },\n\n    final(callback) {\n      function done(error) {\n        try {\n          callback(error);\n        } catch (error) {\n          // In a next tick because this is happening within\n          // a promise context, and if there are any errors\n          // thrown we don't want those to cause an unhandled\n          // rejection. Let's just escape the promise and\n          // handle it separately.\n          nextTick(() => destroy(writable, error));\n        }\n      }\n\n      if (!closed) {\n        writer.close().then(done, done);\n      }\n    },\n  });\n\n  writer.closed.then(\n    () => {\n      // If the WritableStream closes before the stream.Writable has been\n      // ended, we signal an error on the stream.Writable.\n      closed = true;\n      if (!isWritableEnded(writable))\n        destroy(writable, new ERR_STREAM_PREMATURE_CLOSE());\n    },\n    (error) => {\n      // If the WritableStream errors before the stream.Writable has been\n      // destroyed, signal an error on the stream.Writable.\n      closed = true;\n      destroy(writable, error);\n    }\n  );\n\n  return writable;\n}\n"
  },
  {
    "path": "src/node/internal/timers.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport type {\n  setImmediate as setImmediateImpl,\n  clearImmediate as clearImmediateImpl,\n} from 'node:timers';\n\nexport const setImmediate: typeof setImmediateImpl;\nexport const clearImmediate: typeof clearImmediateImpl;\n"
  },
  {
    "path": "src/node/internal/url.d.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nexport function domainToASCII(domain: string): string;\nexport function domainToUnicode(domain: string): string;\nexport function format(\n  href: string,\n  hash: boolean,\n  unicode: boolean,\n  search: boolean,\n  auth: boolean\n): string;\nexport function toASCII(input: string): string;\nexport function canonicalizeIp(input: string): string;\n"
  },
  {
    "path": "src/node/internal/util.d.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport perfHooks from 'node:perf_hooks';\n\nexport const Performance = perfHooks.Performance;\nexport const PerformanceEntry = perfHooks.PerformanceEntry;\nexport const PerformanceMeasure = perfHooks.PerformanceMeasure;\nexport const PerformanceMark = perfHooks.PerformanceMark;\nexport const PerformanceObserver = perfHooks.PerformanceObserver;\nexport const PerformanceObserverEntryList =\n  perfHooks.PerformanceObserverEntryList;\nexport const PerformanceResourceTiming = perfHooks.PerformanceResourceTiming;\n\nexport abstract class MIMEType {\n  constructor(input: string);\n  type: string;\n  subtype: string;\n  readonly essence: string;\n  readonly params: MIMEParams;\n  toString(): string;\n  toJSON(): string;\n}\n\nexport abstract class MIMEParams {\n  constructor();\n  delete(name: string): void;\n  get(name: string): string | undefined;\n  has(name: string): boolean;\n  set(name: string, value: string): void;\n  entries(): Iterable<string[]>;\n  keys(): Iterable<string>;\n  values(): Iterable<string>;\n}\n\nexport const kResourceTypeInspect: unique symbol;\n\nexport const ALL_PROPERTIES: 0;\nexport const ONLY_ENUMERABLE: 1;\nexport function getOwnNonIndexProperties(\n  value: unknown,\n  filter: typeof ALL_PROPERTIES | typeof ONLY_ENUMERABLE\n): PropertyKey[];\n\nexport const kPending: 0;\nexport const kFulfilled: 1;\nexport const kRejected: 2;\nexport interface PromiseDetails {\n  state: typeof kPending | typeof kFulfilled | typeof kRejected;\n  result: unknown;\n}\nexport function getPromiseDetails(value: unknown): PromiseDetails | undefined;\n\nexport interface ProxyDetails {\n  target: unknown;\n  handler: unknown;\n}\nexport function getProxyDetails(value: unknown): ProxyDetails | undefined;\n\nexport interface PreviewedEntries {\n  entries: unknown[];\n  isKeyValue: boolean;\n}\nexport function previewEntries(value: unknown): PreviewedEntries | undefined;\n\nexport function getConstructorName(value: unknown): string;\n\nexport function isArrayBufferView(value: unknown): value is ArrayBufferView;\nexport function isArgumentsObject(value: unknown): value is IArguments;\nexport function isArrayBuffer(value: unknown): value is ArrayBuffer;\n/* eslint-disable-next-line @typescript-eslint/no-unsafe-function-type */\nexport function isAsyncFunction(value: unknown): value is Function;\nexport function isBigInt64Array(value: unknown): value is BigInt64Array;\nexport function isBigIntObject(value: unknown): value is bigint;\nexport function isBigUint64Array(value: unknown): value is BigUint64Array;\nexport function isBooleanObject(value: unknown): value is boolean;\nexport function isDataView(value: unknown): value is DataView;\nexport function isDate(value: unknown): value is Date;\nexport function isExternal(value: unknown): boolean;\nexport function isFloat16Array(value: unknown): value is Float16Array;\nexport function isFloat32Array(value: unknown): value is Float32Array;\nexport function isFloat64Array(value: unknown): value is Float64Array;\nexport function isGeneratorFunction(value: unknown): value is GeneratorFunction;\nexport function isGeneratorObject(value: unknown): value is Generator;\nexport function isInt8Array(value: unknown): value is Int8Array;\nexport function isInt16Array(value: unknown): value is Int16Array;\nexport function isInt32Array(value: unknown): value is Int32Array;\nexport function isMap(value: unknown): value is Map<unknown, unknown>;\nexport function isMapIterator(\n  value: unknown\n): value is IterableIterator<unknown>;\nexport function isModuleNamespaceObject(value: unknown): boolean;\nexport function isNativeError(value: unknown): value is Error;\nexport function isNumberObject(value: unknown): value is number;\nexport function isPromise(value: unknown): value is Promise<unknown>;\nexport function isProxy(value: unknown): boolean;\nexport function isRegExp(value: unknown): value is RegExp;\nexport function isSet(value: unknown): value is Set<unknown>;\nexport function isSetIterator(\n  value: unknown\n): value is IterableIterator<unknown>;\nexport function isSharedArrayBuffer(value: unknown): value is SharedArrayBuffer;\nexport function isStringObject(value: unknown): value is string;\nexport function isSymbolObject(value: unknown): value is symbol;\nexport function isTypedArray(value: unknown): value is NodeJS.TypedArray;\nexport function isUint8Array(value: unknown): value is Uint8Array;\nexport function isUint8ClampedArray(value: unknown): value is Uint8ClampedArray;\nexport function isUint16Array(value: unknown): value is Uint16Array;\nexport function isUint32Array(value: unknown): value is Uint32Array;\nexport function isWeakMap(value: unknown): value is WeakMap<unknown, unknown>;\nexport function isWeakSet(value: unknown): value is WeakSet<unknown>;\nexport function isAnyArrayBuffer(\n  value: unknown\n): value is ArrayBuffer | SharedArrayBuffer;\nexport function isBoxedPrimitive(\n  value: unknown\n): value is number | string | boolean | bigint | symbol;\nexport function getCallSites(frames?: number): Record<string, string>[];\n"
  },
  {
    "path": "src/node/internal/validators.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Deno and Node.js:\n// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\nimport { isArrayBufferView } from 'node-internal:internal_types';\nimport { normalizeEncoding } from 'node-internal:internal_utils';\nimport {\n  ERR_INVALID_ARG_TYPE,\n  ERR_INVALID_ARG_VALUE,\n  ERR_SOCKET_BAD_PORT,\n  ERR_OUT_OF_RANGE,\n  ERR_INVALID_THIS,\n} from 'node-internal:internal_errors';\nimport { default as bufferUtil } from 'node-internal:buffer';\n\n// TODO(someday): Not current implementing parseFileMode\n\nexport function isInt32(value: unknown): value is number {\n  if (typeof value !== 'number') return false;\n  return value === (value | 0);\n}\nexport function isUint32(value: unknown): value is number {\n  // @ts-expect-error Due to value being unknown\n  return value === value >>> 0;\n}\n\nexport function validateBuffer(\n  buffer: unknown,\n  name = 'buffer'\n): asserts buffer is Buffer {\n  if (!isArrayBufferView(buffer)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      name,\n      ['Buffer', 'TypedArray', 'DataView'],\n      buffer\n    );\n  }\n}\n\nexport function validateInteger(\n  value: unknown,\n  name: string,\n  min = Number.MIN_SAFE_INTEGER,\n  max = Number.MAX_SAFE_INTEGER\n): asserts value is number {\n  if (typeof value !== 'number') {\n    throw new ERR_INVALID_ARG_TYPE(name, 'number', value);\n  }\n  if (!Number.isInteger(value)) {\n    throw new ERR_OUT_OF_RANGE(name, 'an integer', value);\n  }\n  if (value < min || value > max) {\n    throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value);\n  }\n}\n\nexport function validateObject<T extends object>(\n  value: T | null | undefined,\n  name: string,\n  options?: number\n): asserts value is T;\nexport function validateObject(\n  value: unknown,\n  name: string,\n  options?: number\n): asserts value is Record<string, unknown>;\nexport function validateObject(\n  value: unknown,\n  name: string,\n  options: number = kValidateObjectNone\n): asserts value is Record<string, unknown> {\n  if (options === kValidateObjectNone) {\n    if (value === null || Array.isArray(value)) {\n      throw new ERR_INVALID_ARG_TYPE(name, 'Object', value);\n    }\n\n    if (typeof value !== 'object') {\n      throw new ERR_INVALID_ARG_TYPE(name, 'Object', value);\n    }\n  } else {\n    const throwOnNullable = (kValidateObjectAllowNullable & options) === 0;\n\n    if (throwOnNullable && value === null) {\n      throw new ERR_INVALID_ARG_TYPE(name, 'Object', value);\n    }\n\n    const throwOnArray = (kValidateObjectAllowArray & options) === 0;\n\n    if (throwOnArray && Array.isArray(value)) {\n      throw new ERR_INVALID_ARG_TYPE(name, 'Object', value);\n    }\n\n    const throwOnFunction = (kValidateObjectAllowFunction & options) === 0;\n    const typeofValue = typeof value;\n\n    if (\n      typeofValue !== 'object' &&\n      (throwOnFunction || typeofValue !== 'function')\n    ) {\n      throw new ERR_INVALID_ARG_TYPE(name, 'Object', value);\n    }\n  }\n}\n\nexport function validateInt32(\n  value: unknown,\n  name: string,\n  min = -2147483648,\n  max = 2147483647\n): asserts value is number {\n  if (!isInt32(value)) {\n    if (typeof value !== 'number') {\n      throw new ERR_INVALID_ARG_TYPE(name, 'number', value);\n    }\n\n    if (!Number.isInteger(value)) {\n      throw new ERR_OUT_OF_RANGE(name, 'an integer', value);\n    }\n\n    throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value);\n  }\n\n  if (value < min || value > max) {\n    throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value);\n  }\n}\n\nexport function validateUint32(\n  value: unknown,\n  name: string,\n  positive?: boolean\n): asserts value is number {\n  if (!isUint32(value)) {\n    if (typeof value !== 'number') {\n      throw new ERR_INVALID_ARG_TYPE(name, 'number', value);\n    }\n    if (!Number.isInteger(value)) {\n      throw new ERR_OUT_OF_RANGE(name, 'an integer', value);\n    }\n    const min = positive ? 1 : 0;\n    // 2 ** 32 === 4294967296\n    throw new ERR_OUT_OF_RANGE(name, `>= ${min} && < 4294967296`, value);\n  }\n  if (positive && value === 0) {\n    throw new ERR_OUT_OF_RANGE(name, '>= 1 && < 4294967296', value);\n  }\n}\n\nexport function validateString(\n  value: unknown,\n  name: string\n): asserts value is string {\n  if (typeof value !== 'string') {\n    throw new ERR_INVALID_ARG_TYPE(name, 'string', value);\n  }\n}\n\nexport function validateStringArray(\n  value: unknown,\n  name: string\n): asserts value is string[] {\n  validateArray(value, name);\n  for (let i = 0; i < value.length; ++i) {\n    // Don't use validateString here for performance reasons, as\n    // we would generate intermediate strings for the name.\n    if (typeof value[i] !== 'string') {\n      throw new ERR_INVALID_ARG_TYPE(`${name}[${i}]`, 'string', value[i]);\n    }\n  }\n}\n\nexport function validateNumber(\n  value: unknown,\n  name: string,\n  min?: number,\n  max?: number\n): asserts value is number {\n  if (typeof value !== 'number') {\n    throw new ERR_INVALID_ARG_TYPE(name, 'number', value);\n  }\n\n  if (\n    (min != null && value < min) ||\n    (max != null && value > max) ||\n    ((min != null || max != null) && Number.isNaN(value))\n  ) {\n    throw new ERR_OUT_OF_RANGE(\n      name,\n      `${min != null ? `>= ${min}` : ''}${min != null && max != null ? ' && ' : ''}${max != null ? `<= ${max}` : ''}`,\n      value\n    );\n  }\n}\n\nexport function validateBoolean(\n  value: unknown,\n  name: string\n): asserts value is boolean {\n  if (typeof value !== 'boolean') {\n    throw new ERR_INVALID_ARG_TYPE(name, 'boolean', value);\n  }\n}\n\nexport function validateOneOf<T>(\n  value: T,\n  name: string,\n  oneOf: T[]\n): asserts value is T {\n  if (!Array.prototype.includes.call(oneOf, value)) {\n    const allowed = Array.prototype.join.call(\n      Array.prototype.map.call(oneOf, (v) =>\n        typeof v === 'string' ? `'${v}'` : String(v)\n      ),\n      ', '\n    );\n    const reason = 'must be one of: ' + allowed;\n\n    throw new ERR_INVALID_ARG_VALUE(name, value, reason);\n  }\n}\n\nexport function validateEncoding(\n  data: unknown,\n  encoding: string\n): asserts data is string {\n  const normalizedEncoding = normalizeEncoding(encoding);\n  const length = (data as any).length; // eslint-disable-line\n\n  if (normalizedEncoding === bufferUtil.HEX && length % 2 !== 0) {\n    throw new ERR_INVALID_ARG_VALUE(\n      'encoding',\n      encoding,\n      `is invalid for data of length ${length}`\n    );\n  }\n}\n\nexport function validateAbortSignal(\n  signal: unknown,\n  name: string\n): asserts signal is AbortSignal {\n  if (\n    signal !== undefined &&\n    (signal === null || typeof signal !== 'object' || !('aborted' in signal))\n  ) {\n    throw new ERR_INVALID_ARG_TYPE(name, 'AbortSignal', signal);\n  }\n}\n\nexport function validateFunction(\n  value: unknown,\n  name: string\n): asserts value is (...args: unknown[]) => unknown {\n  if (typeof value !== 'function') {\n    throw new ERR_INVALID_ARG_TYPE(name, 'Function', value);\n  }\n}\n\nexport function validateArray(\n  value: unknown,\n  name: string,\n  minLength = 0\n): asserts value is unknown[] {\n  if (!Array.isArray(value)) {\n    throw new ERR_INVALID_ARG_TYPE(name, 'Array', value);\n  }\n  if (value.length < minLength) {\n    const reason = `must be longer than ${minLength}`;\n    throw new ERR_INVALID_ARG_VALUE(name, value, reason);\n  }\n}\n\n// 1. Returns false for undefined and NaN\n// 2. Returns true for finite numbers\n// 3. Throws ERR_INVALID_ARG_TYPE for non-numbers\n// 4. Throws ERR_OUT_OF_RANGE for infinite numbers\nexport function checkFiniteNumber(\n  number: unknown,\n  name: string\n): number is number {\n  // Common case\n  if (number === undefined) {\n    return false;\n  }\n\n  if (Number.isFinite(number)) {\n    return true; // Is a valid number\n  }\n\n  if (Number.isNaN(number)) {\n    return false;\n  }\n\n  validateNumber(number, name);\n\n  // Infinite numbers\n  throw new ERR_OUT_OF_RANGE(name, 'a finite number', number);\n}\n\n// 1. Returns def for number when it's undefined or NaN\n// 2. Returns number for finite numbers >= lower and <= upper\n// 3. Throws ERR_INVALID_ARG_TYPE for non-numbers\n// 4. Throws ERR_OUT_OF_RANGE for infinite numbers or numbers > upper or < lower\nexport function checkRangesOrGetDefault(\n  number: unknown,\n  name: string,\n  lower: number,\n  upper: number,\n  def: number\n): number;\nexport function checkRangesOrGetDefault(\n  number: unknown,\n  name: string,\n  lower: number,\n  upper: number,\n  def?: number\n): number | undefined;\nexport function checkRangesOrGetDefault(\n  number: unknown,\n  name: string,\n  lower: number,\n  upper: number,\n  def?: number\n): number | undefined {\n  if (!checkFiniteNumber(number, name)) {\n    return def;\n  }\n  if (number < lower || number > upper) {\n    throw new ERR_OUT_OF_RANGE(name, `>= ${lower} and <= ${upper}`, number);\n  }\n  return number;\n}\n\nexport function validatePort(\n  port: unknown,\n  name = 'Port',\n  allowZero = true\n): number {\n  if (\n    (typeof port !== 'number' && typeof port !== 'string') ||\n    (typeof port === 'string' && port.trim().length === 0) ||\n    +port !== +port >>> 0 ||\n    +port > 0xffff ||\n    (port === 0 && !allowZero)\n  ) {\n    throw new ERR_SOCKET_BAD_PORT(name, port, allowZero);\n  }\n  return +port | 0;\n}\n\nconst octalReg = /^[0-7]+$/;\nconst modeDesc = 'must be a 32-bit unsigned integer or an octal string';\n\nexport function parseFileMode(\n  value: unknown,\n  name: string,\n  def?: number\n): number {\n  if (def != null) {\n    value ??= def;\n  }\n  if (typeof value === 'string') {\n    if (!octalReg.test(value)) {\n      throw new ERR_INVALID_ARG_VALUE(name, value, modeDesc);\n    }\n    value = Number.parseInt(value, 8);\n  }\n\n  validateUint32(value, name);\n  return value;\n}\n\nexport function validateThisInternalField(\n  object: object | null,\n  fieldKey: string | symbol,\n  className: string\n): void {\n  if (\n    typeof object !== 'object' ||\n    object === null ||\n    !Object.prototype.hasOwnProperty.call(object, fieldKey)\n  ) {\n    throw new ERR_INVALID_THIS(className);\n  }\n}\n\nexport const kValidateObjectNone = 0;\nexport const kValidateObjectAllowNullable = 1 << 0;\nexport const kValidateObjectAllowArray = 1 << 1;\nexport const kValidateObjectAllowFunction = 1 << 2;\nexport const kValidateObjectAllowObjects =\n  kValidateObjectAllowArray | kValidateObjectAllowFunction;\nexport const kValidateObjectAllowObjectsAndNull =\n  kValidateObjectAllowNullable |\n  kValidateObjectAllowArray |\n  kValidateObjectAllowFunction;\n\nexport default {\n  isInt32,\n  isUint32,\n  validateAbortSignal,\n  validateArray,\n  validateBoolean,\n  validateBuffer,\n  validateFunction,\n  validateInt32,\n  validateInteger,\n  validateNumber,\n  validateObject,\n  validateOneOf,\n  validateString,\n  validateUint32,\n  validatePort,\n  validateThisInternalField,\n\n  // Zlib specific\n  checkFiniteNumber,\n  checkRangesOrGetDefault,\n\n  // Filesystem specific\n  parseFileMode,\n  kValidateObjectNone,\n  kValidateObjectAllowNullable,\n  kValidateObjectAllowArray,\n  kValidateObjectAllowFunction,\n  kValidateObjectAllowObjects,\n  kValidateObjectAllowObjectsAndNull,\n};\n"
  },
  {
    "path": "src/node/internal/workers.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ndeclare namespace _default {\n  class WorkerEntrypoint {}\n  class WorkflowEntrypoint {}\n  class DurableObject {}\n  class RpcPromise {}\n  class RpcProperty {}\n  class RpcStub {}\n  class RpcTarget {}\n}\nexport default _default;\n"
  },
  {
    "path": "src/node/internal/zlib.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { owner_symbol, type Zlib } from 'node-internal:internal_zlib_base';\n\ntype InternalCompressCallback = (res: Error | ArrayBuffer) => void;\n\nexport function crc32(data: ArrayBufferView | string, value: number): number;\n\nexport function zlibSync(\n  data: ArrayBufferView | string,\n  options: ZlibOptions,\n  mode: number\n): ArrayBuffer;\nexport function zlib(\n  data: ArrayBufferView | string,\n  options: ZlibOptions,\n  mode: number,\n  cb: InternalCompressCallback\n): void;\n\nexport function brotliDecompressSync(\n  data: ArrayBufferView | string,\n  options: BrotliOptions\n): ArrayBuffer;\nexport function brotliDecompress(\n  data: ArrayBufferView | string,\n  options: BrotliOptions,\n  cb: InternalCompressCallback\n): void;\n\nexport function brotliCompressSync(\n  data: ArrayBufferView | string,\n  options: BrotliOptions\n): ArrayBuffer;\nexport function brotliCompress(\n  data: ArrayBufferView | string,\n  options: BrotliOptions,\n  cb: InternalCompressCallback\n): void;\n\nexport function zstdDecompressSync(\n  data: ArrayBufferView | string,\n  options: ZstdOptions\n): ArrayBuffer;\nexport function zstdDecompress(\n  data: ArrayBufferView | string,\n  options: ZstdOptions,\n  cb: InternalCompressCallback\n): void;\n\nexport function zstdCompressSync(\n  data: ArrayBufferView | string,\n  options: ZstdOptions\n): ArrayBuffer;\nexport function zstdCompress(\n  data: ArrayBufferView | string,\n  options: ZstdOptions,\n  cb: InternalCompressCallback\n): void;\n\n// zlib.constants (part of the API contract for node:zlib)\nexport const CONST_Z_NO_FLUSH: number;\nexport const CONST_Z_PARTIAL_FLUSH: number;\nexport const CONST_Z_SYNC_FLUSH: number;\nexport const CONST_Z_FULL_FLUSH: number;\nexport const CONST_Z_FINISH: number;\nexport const CONST_Z_BLOCK: number;\n\nexport const CONST_Z_OK: number;\nexport const CONST_Z_STREAM_END: number;\nexport const CONST_Z_NEED_DICT: number;\nexport const CONST_Z_ERRNO: number;\nexport const CONST_Z_STREAM_ERROR: number;\nexport const CONST_Z_DATA_ERROR: number;\nexport const CONST_Z_MEM_ERROR: number;\nexport const CONST_Z_BUF_ERROR: number;\nexport const CONST_Z_VERSION_ERROR: number;\n\nexport const CONST_Z_NO_COMPRESSION: number;\nexport const CONST_Z_BEST_SPEED: number;\nexport const CONST_Z_BEST_COMPRESSION: number;\nexport const CONST_Z_DEFAULT_COMPRESSION: number;\nexport const CONST_Z_FILTERED: number;\nexport const CONST_Z_HUFFMAN_ONLY: number;\nexport const CONST_Z_RLE: number;\nexport const CONST_Z_FIXED: number;\nexport const CONST_Z_DEFAULT_STRATEGY: number;\nexport const CONST_ZLIB_VERNUM: number;\n\nexport const CONST_DEFLATE: number;\nexport const CONST_INFLATE: number;\nexport const CONST_GZIP: number;\nexport const CONST_GUNZIP: number;\nexport const CONST_DEFLATERAW: number;\nexport const CONST_INFLATERAW: number;\nexport const CONST_UNZIP: number;\nexport const CONST_BROTLI_DECODE: number;\nexport const CONST_BROTLI_ENCODE: number;\nexport const CONST_ZSTD_ENCODE: number;\nexport const CONST_ZSTD_DECODE: number;\n\nexport const CONST_Z_MIN_WINDOWBITS: number;\nexport const CONST_Z_MAX_WINDOWBITS: number;\nexport const CONST_Z_DEFAULT_WINDOWBITS: number;\nexport const CONST_Z_MIN_CHUNK: number;\nexport const CONST_Z_MAX_CHUNK: number;\nexport const CONST_Z_DEFAULT_CHUNK: number;\nexport const CONST_Z_MIN_MEMLEVEL: number;\nexport const CONST_Z_MAX_MEMLEVEL: number;\nexport const CONST_Z_DEFAULT_MEMLEVEL: number;\nexport const CONST_Z_MIN_LEVEL: number;\nexport const CONST_Z_MAX_LEVEL: number;\nexport const CONST_Z_DEFAULT_LEVEL: number;\n\nexport const CONST_BROTLI_OPERATION_PROCESS: number;\nexport const CONST_BROTLI_OPERATION_FLUSH: number;\nexport const CONST_BROTLI_OPERATION_FINISH: number;\nexport const CONST_BROTLI_OPERATION_EMIT_METADATA: number;\nexport const CONST_BROTLI_PARAM_MODE: number;\nexport const CONST_BROTLI_MODE_GENERIC: number;\nexport const CONST_BROTLI_MODE_TEXT: number;\nexport const CONST_BROTLI_MODE_FONT: number;\nexport const CONST_BROTLI_DEFAULT_MODE: number;\nexport const CONST_BROTLI_PARAM_QUALITY: number;\nexport const CONST_BROTLI_MIN_QUALITY: number;\nexport const CONST_BROTLI_MAX_QUALITY: number;\nexport const CONST_BROTLI_DEFAULT_QUALITY: number;\nexport const CONST_BROTLI_PARAM_LGWIN: number;\nexport const CONST_BROTLI_MIN_WINDOW_BITS: number;\nexport const CONST_BROTLI_MAX_WINDOW_BITS: number;\nexport const CONST_BROTLI_LARGE_MAX_WINDOW_BITS: number;\nexport const CONST_BROTLI_DEFAULT_WINDOW: number;\nexport const CONST_BROTLI_PARAM_LGBLOCK: number;\nexport const CONST_BROTLI_MIN_INPUT_BLOCK_BITS: number;\nexport const CONST_BROTLI_MAX_INPUT_BLOCK_BITS: number;\nexport const CONST_BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING: number;\nexport const CONST_BROTLI_PARAM_SIZE_HINT: number;\nexport const CONST_BROTLI_PARAM_LARGE_WINDOW: number;\nexport const CONST_BROTLI_PARAM_NPOSTFIX: number;\nexport const CONST_BROTLI_PARAM_NDIRECT: number;\nexport const CONST_BROTLI_DECODER_RESULT_ERROR: number;\nexport const CONST_BROTLI_DECODER_RESULT_SUCCESS: number;\nexport const CONST_BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT: number;\nexport const CONST_BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT: number;\nexport const CONST_BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION: number;\nexport const CONST_BROTLI_DECODER_PARAM_LARGE_WINDOW: number;\nexport const CONST_BROTLI_DECODER_NO_ERROR: number;\nexport const CONST_BROTLI_DECODER_SUCCESS: number;\nexport const CONST_BROTLI_DECODER_NEEDS_MORE_INPUT: number;\nexport const CONST_BROTLI_DECODER_NEEDS_MORE_OUTPUT: number;\nexport const CONST_BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE: number;\nexport const CONST_BROTLI_DECODER_ERROR_FORMAT_RESERVED: number;\nexport const CONST_BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE: number;\nexport const CONST_BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET: number;\nexport const CONST_BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME: number;\nexport const CONST_BROTLI_DECODER_ERROR_FORMAT_CL_SPACE: number;\nexport const CONST_BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE: number;\nexport const CONST_BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT: number;\nexport const CONST_BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1: number;\nexport const CONST_BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2: number;\nexport const CONST_BROTLI_DECODER_ERROR_FORMAT_TRANSFORM: number;\nexport const CONST_BROTLI_DECODER_ERROR_FORMAT_DICTIONARY: number;\nexport const CONST_BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS: number;\nexport const CONST_BROTLI_DECODER_ERROR_FORMAT_PADDING_1: number;\nexport const CONST_BROTLI_DECODER_ERROR_FORMAT_PADDING_2: number;\nexport const CONST_BROTLI_DECODER_ERROR_FORMAT_DISTANCE: number;\nexport const CONST_BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET: number;\nexport const CONST_BROTLI_DECODER_ERROR_INVALID_ARGUMENTS: number;\nexport const CONST_BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES: number;\nexport const CONST_BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS: number;\nexport const CONST_BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP: number;\nexport const CONST_BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1: number;\nexport const CONST_BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2: number;\nexport const CONST_BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES: number;\nexport const CONST_BROTLI_DECODER_ERROR_UNREACHABLE: number;\n\n// Zstd flush directives\nexport const CONST_ZSTD_e_continue: number;\nexport const CONST_ZSTD_e_flush: number;\nexport const CONST_ZSTD_e_end: number;\n\n// Zstd compression parameters\nexport const CONST_ZSTD_c_compressionLevel: number;\nexport const CONST_ZSTD_c_windowLog: number;\nexport const CONST_ZSTD_c_hashLog: number;\nexport const CONST_ZSTD_c_chainLog: number;\nexport const CONST_ZSTD_c_searchLog: number;\nexport const CONST_ZSTD_c_minMatch: number;\nexport const CONST_ZSTD_c_targetLength: number;\nexport const CONST_ZSTD_c_strategy: number;\nexport const CONST_ZSTD_c_enableLongDistanceMatching: number;\nexport const CONST_ZSTD_c_ldmHashLog: number;\nexport const CONST_ZSTD_c_ldmMinMatch: number;\nexport const CONST_ZSTD_c_ldmBucketSizeLog: number;\nexport const CONST_ZSTD_c_ldmHashRateLog: number;\nexport const CONST_ZSTD_c_contentSizeFlag: number;\nexport const CONST_ZSTD_c_checksumFlag: number;\nexport const CONST_ZSTD_c_dictIDFlag: number;\nexport const CONST_ZSTD_c_nbWorkers: number;\nexport const CONST_ZSTD_c_jobSize: number;\nexport const CONST_ZSTD_c_overlapLog: number;\n\n// Zstd decompression parameters\nexport const CONST_ZSTD_d_windowLogMax: number;\n\n// Zstd strategy constants\nexport const CONST_ZSTD_fast: number;\nexport const CONST_ZSTD_dfast: number;\nexport const CONST_ZSTD_greedy: number;\nexport const CONST_ZSTD_lazy: number;\nexport const CONST_ZSTD_lazy2: number;\nexport const CONST_ZSTD_btlazy2: number;\nexport const CONST_ZSTD_btopt: number;\nexport const CONST_ZSTD_btultra: number;\nexport const CONST_ZSTD_btultra2: number;\n\n// Zstd default compression level\nexport const CONST_ZSTD_CLEVEL_DEFAULT: number;\n\nexport interface ZlibOptions {\n  flush?: number | undefined;\n  finishFlush?: number | undefined;\n  chunkSize?: number | undefined;\n  windowBits?: number | undefined;\n  level?: number | undefined; // compression only\n  memLevel?: number | undefined; // compression only\n  strategy?: number | undefined; // compression only\n  dictionary?: ArrayBufferView | undefined; // deflate/inflate only, empty dictionary by default\n  info?: boolean | undefined;\n  maxOutputLength?: number | undefined;\n}\n\nexport interface BrotliOptions {\n  flush?: number | undefined;\n  finishFlush?: number | undefined;\n  chunkSize?: number | undefined;\n  params?:\n    | {\n        [key: number]: boolean | number;\n      }\n    | undefined;\n  maxOutputLength?: number | undefined;\n  // Not specified in NodeJS docs but the tests expect it\n  info?: boolean | undefined;\n}\n\nexport interface ZstdOptions {\n  flush?: number | undefined;\n  finishFlush?: number | undefined;\n  chunkSize?: number | undefined;\n  params?:\n    | {\n        [key: number]: boolean | number;\n      }\n    | undefined;\n  maxOutputLength?: number | undefined;\n  pledgedSrcSize?: number | undefined;\n  // Not specified in NodeJS docs but the tests expect it\n  info?: boolean | undefined;\n}\n\ntype ErrorHandler = (errno: number, code: string, message: string) => void;\ntype ProcessHandler = () => void;\n\nexport abstract class CompressionStream {\n  [owner_symbol]: Zlib;\n  // Not used by C++ implementation but required to be Node.js compatible.\n  inOff: number;\n  buffer: NodeJS.TypedArray | null;\n  cb: () => void;\n  availOutBefore: number;\n  availInBefore: number;\n  flushFlag: number;\n\n  constructor(mode: number);\n  close(): void;\n  write(\n    flushFlag: number,\n    inputBuffer: NodeJS.TypedArray,\n    inputOffset: number,\n    inputLength: number,\n    outputBuffer: NodeJS.TypedArray,\n    outputOffset: number,\n    outputLength: number\n  ): void;\n  writeSync(\n    flushFlag: number,\n    inputBuffer: NodeJS.TypedArray,\n    inputOffset: number,\n    inputLength: number,\n    outputBuffer: NodeJS.TypedArray,\n    outputOffset: number,\n    outputLength: number\n  ): void;\n  reset(): void;\n\n  // Workerd specific functions\n  setErrorHandler(cb: ErrorHandler): void;\n}\n\nexport class ZlibStream extends CompressionStream {\n  initialize(\n    windowBits: number,\n    level: number,\n    memLevel: number,\n    strategy: number,\n    writeState: NodeJS.TypedArray,\n    processCallback: ProcessHandler,\n    dictionary: ZlibOptions['dictionary']\n  ): void;\n  params(level: number, strategy: number): void;\n}\n\nexport class BrotliDecoder extends CompressionStream {\n  initialize(\n    params: Uint32Array,\n    writeResult: Uint32Array,\n    writeCallback: () => void\n  ): boolean;\n  params(): void;\n}\n\nexport class BrotliEncoder extends CompressionStream {\n  initialize(\n    params: Uint32Array,\n    writeResult: Uint32Array,\n    writeCallback: () => void\n  ): boolean;\n  params(): void;\n}\n\nexport class ZstdDecoder extends CompressionStream {\n  initialize(\n    params: Int32Array,\n    writeResult: Uint32Array,\n    writeCallback: () => void,\n    pledgedSrcSize?: number\n  ): boolean;\n  params(): void;\n}\n\nexport class ZstdEncoder extends CompressionStream {\n  initialize(\n    params: Int32Array,\n    writeResult: Uint32Array,\n    writeCallback: () => void,\n    pledgedSrcSize?: number\n  ): boolean;\n  params(): void;\n}\n"
  },
  {
    "path": "src/node/module.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { default as moduleUtil } from 'node-internal:module';\nimport {\n  ERR_INVALID_ARG_VALUE,\n  ERR_METHOD_NOT_IMPLEMENTED,\n} from 'node-internal:internal_errors';\nimport { builtinModules } from 'node-internal:internal_module';\n\nexport function enableCompileCache(): void {\n  // We don't plan to support this in the future.\n  // Since, this method shouldn't throw an error, we just act like a no-op.\n}\n\nexport function getCompileCacheDir(): undefined {\n  // We don't plan to support this in the future.\n  // Since, compile cache acts as a no-op, we just return undefined.\n  return undefined;\n}\n\n// We are unlikely to implement this in the future.\nexport const _extensions = {\n  '.js': (): void => {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('module._extensions.js');\n  },\n  '.json': (): void => {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('module._extensions.json');\n  },\n  '.node': (): void => {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('module._extensions.node');\n  },\n};\n\nexport function createRequire(\n  path: string | URL\n): (specifier: string) => unknown {\n  // Note that per Node.js' requirements, path must be one of either\n  // an absolute file path or a file URL. We do not currently handle\n  // module specifiers as URLs yet, but we'll try to get close.\n\n  const normalizedPath = `${path}`;\n  if (!normalizedPath.startsWith('/') && !normalizedPath.startsWith('file:')) {\n    throw new ERR_INVALID_ARG_VALUE(\n      'path',\n      normalizedPath,\n      'The argument must be a file URL object, a file URL string, or an absolute path string.'\n    );\n  }\n\n  return moduleUtil.createRequire(normalizedPath);\n}\n\nObject.defineProperties(createRequire, {\n  resolve: {\n    value: (): void => {\n      // TODO(soon): We could support this in the future.\n      throw new ERR_METHOD_NOT_IMPLEMENTED('module.createRequire.resolve');\n    },\n    enumerable: true,\n    writable: true,\n  },\n  paths: {\n    value: (): void => {\n      throw new ERR_METHOD_NOT_IMPLEMENTED(\n        'module.createRequire.resolve.paths'\n      );\n    },\n    enumerable: true,\n    writable: true,\n  },\n  cache: {\n    value: { __proto__: null },\n    enumerable: true,\n    writable: true,\n  },\n  extensions: {\n    value: _extensions,\n    enumerable: true,\n    writable: true,\n  },\n  main: {\n    value: undefined,\n    enumerable: true,\n    writable: true,\n  },\n});\n\n// Indicates only that the given specifier is known to be a\n// Node.js built-in module specifier with or with the the\n// 'node:' prefix. A true return value does not guarantee that\n// the module is actually implemented in the runtime.\nexport function isBuiltin(specifier: string): boolean {\n  return moduleUtil.isBuiltin(specifier);\n}\n\nexport { builtinModules };\n\nexport function register(): void {\n  // TODO(soon): We might support this in the future.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('module.register');\n}\n\nexport function runMain(): void {\n  // We don't plan to support this in the future.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('module.runMain');\n}\n\nexport function syncBuiltinESMExports(): void {\n  // We are unlikely to ever support this.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('module.syncBuiltinESMExports');\n}\n\nexport function wrap(): void {\n  // TODO(soon): Implement this feature.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('module.wrap');\n}\n\nexport const globalPaths: string[] = [];\n\nconst compileCacheStatus = Object.freeze({\n  __proto__: null,\n  FAILED: 0,\n  ENABLED: 1,\n  ALREADY_ENABLED: 2,\n  DISABLED: 3,\n});\n\nexport const constants = Object.freeze({\n  __proto__: null,\n  compileCacheStatus,\n});\n\nexport const _cache = { __proto__: null };\nexport const _pathCache = { __proto__: null };\n\nexport function _debug(): void {\n  // This is deprecated and will be removed in the future.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('module._debug');\n}\n\nexport function _findPath(): void {\n  // It doesn't make sense to support this.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('module._findPath');\n}\n\nexport function _initPaths(): void {\n  // It doesn't make sense to support this.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('module._initPaths');\n}\n\nexport function _load(): void {\n  // TODO(soon): Investigate the possibility of supporting this in the future.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('module._load');\n}\n\nexport function _preloadModules(): void {\n  // It doesn't make sense to support this.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('module._preloadModules');\n}\n\nexport function _resolveFilename(): void {\n  // TODO(soon): Investigate the possibility of supporting this in the future.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('module._resolveFilename');\n}\n\nexport function _resolveLookupPaths(): void {\n  // TODO(soon): Investigate the possibility of supporting this in the future.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('module._resolveLookupPaths');\n}\n\nexport function _nodeModulePaths(): void {\n  // It doesn't make sense to support this.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('module._nodeModulePaths');\n}\n\nexport function findSourceMap(): void {\n  // TODO(soon): Investigate the possibility of supporting this in the future.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('module.findSourceMap');\n}\n\nexport function findPackageJSON(): void {\n  // It doesn't make sense to support this.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('module.findPackageJSON');\n}\n\nexport function flushCompileCache(): void {\n  // We don't implement compile cache. This acts as a no-op.\n}\n\nexport function getSourceMapsSupport(): Record<string, boolean | null> {\n  return Object.freeze({\n    __proto__: null,\n    enabled: false,\n    nodeModules: false,\n    generatedCode: false,\n  });\n}\n\nexport function setSourceMapsSupport(): void {\n  // We don't implement source maps support.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('module.setSourceMapsSupport');\n}\n\nexport function stripTypeScriptTypes(): void {\n  // We don't implement stripping TypeScript types.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('module.stripTypeScriptTypes');\n}\n\nexport function registerHooks(): void {\n  // We don't implement hooks registration.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('module.registerHooks');\n}\n\nexport function SourceMap(): void {\n  // We don't support source maps.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('module.SourceMap');\n}\n\nexport function Module(): void {\n  // TODO(soon): Investigate implementing Module class fully.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('module.Module');\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nModule.prototype.load = function load(): void {\n  // Acts as a no-op.\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nModule.prototype.require = function require(): void {\n  // Acts as a no-op.\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nModule.prototype._compile = function _compile(): void {\n  // Acts as a no-op.\n};\n\nObject.defineProperties(Module, {\n  register: {\n    value: register,\n    writable: true,\n    enumerable: true,\n  },\n  constants: {\n    value: constants,\n    writable: true,\n    enumerable: true,\n  },\n  enableCompileCache: {\n    value: enableCompileCache,\n    writable: true,\n    enumerable: true,\n  },\n  findPackageJSON: {\n    value: findPackageJSON,\n    writable: true,\n    enumerable: true,\n  },\n  flushCompileCache: {\n    value: flushCompileCache,\n    writable: true,\n    enumerable: true,\n  },\n  getCompileCacheDir: {\n    value: getCompileCacheDir,\n    writable: true,\n    enumerable: true,\n  },\n  stripTypeScriptTypes: {\n    value: stripTypeScriptTypes,\n    writable: true,\n    enumerable: true,\n  },\n  findSourceMap: {\n    value: findSourceMap,\n    writable: true,\n    enumerable: true,\n  },\n  SourceMap: {\n    value: SourceMap,\n    writable: true,\n    enumerable: true,\n  },\n  getSourceMapsSupport: {\n    value: getSourceMapsSupport,\n    writable: true,\n    enumerable: true,\n  },\n  setSourceMapsSupport: {\n    value: setSourceMapsSupport,\n    writable: true,\n    enumerable: true,\n  },\n  createRequire: {\n    value: createRequire,\n    writable: true,\n    enumerable: true,\n  },\n  builtinModules: {\n    value: builtinModules,\n    writable: true,\n    enumerable: true,\n  },\n  globalPaths: {\n    value: globalPaths,\n    writable: true,\n    enumerable: true,\n  },\n  isBuiltin: {\n    value: isBuiltin,\n    writable: true,\n    enumerable: true,\n  },\n  runMain: {\n    value: runMain,\n    writable: true,\n    enumerable: true,\n  },\n  syncBuiltinESMExports: {\n    value: syncBuiltinESMExports,\n    writable: true,\n    enumerable: true,\n  },\n  wrap: {\n    value: wrap,\n    writable: true,\n    enumerable: true,\n  },\n  _cache: {\n    value: _cache,\n    writable: true,\n    enumerable: true,\n  },\n  _debug: {\n    value: _debug,\n    writable: true,\n    enumerable: true,\n  },\n  _extensions: {\n    value: _extensions,\n    writable: true,\n    enumerable: true,\n  },\n  _findPath: {\n    value: _findPath,\n    writable: true,\n    enumerable: true,\n  },\n  _initPaths: {\n    value: _initPaths,\n    writable: true,\n    enumerable: true,\n  },\n  _load: {\n    value: _load,\n    writable: true,\n    enumerable: true,\n  },\n  _pathCache: {\n    value: _pathCache,\n    writable: true,\n    enumerable: true,\n  },\n  _preloadModules: {\n    value: _preloadModules,\n    writable: true,\n    enumerable: true,\n  },\n  _resolveFilename: {\n    value: _resolveFilename,\n    writable: true,\n    enumerable: true,\n  },\n  _resolveLookupPaths: {\n    value: _resolveLookupPaths,\n    writable: true,\n    enumerable: true,\n  },\n  _nodeModulePaths: {\n    value: _nodeModulePaths,\n    writable: true,\n    enumerable: true,\n  },\n  Module: {\n    value: Module,\n    writable: true,\n    enumerable: true,\n  },\n  registerHooks: {\n    value: registerHooks,\n    writable: true,\n    enumerable: true,\n  },\n});\n\nexport default Module;\n"
  },
  {
    "path": "src/node/net.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport {\n  BlockList,\n  SocketAddress,\n  Server,\n  Socket,\n  connect,\n  createConnection,\n  createServer,\n  getDefaultAutoSelectFamily,\n  setDefaultAutoSelectFamily,\n  getDefaultAutoSelectFamilyAttemptTimeout,\n  setDefaultAutoSelectFamilyAttemptTimeout,\n  isIP,\n  isIPv4,\n  isIPv6,\n  _normalizeArgs,\n} from 'node-internal:internal_net';\n\nexport const Stream = Socket;\n\nexport {\n  BlockList,\n  SocketAddress,\n  Server,\n  Socket,\n  connect,\n  createConnection,\n  createServer,\n  getDefaultAutoSelectFamily,\n  setDefaultAutoSelectFamily,\n  getDefaultAutoSelectFamilyAttemptTimeout,\n  setDefaultAutoSelectFamilyAttemptTimeout,\n  isIP,\n  isIPv4,\n  isIPv6,\n  _normalizeArgs,\n};\n\nexport default {\n  BlockList,\n  SocketAddress,\n  Stream: Socket,\n  Server,\n  Socket,\n  connect,\n  createConnection,\n  createServer,\n  getDefaultAutoSelectFamily,\n  setDefaultAutoSelectFamily,\n  getDefaultAutoSelectFamilyAttemptTimeout,\n  setDefaultAutoSelectFamilyAttemptTimeout,\n  isIP,\n  isIPv4,\n  isIPv6,\n  _normalizeArgs,\n};\n"
  },
  {
    "path": "src/node/os.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Exceedingly minimal implementation of the `node:os` module for Workers.\n// Generally all stubs that return empty or default values since we don't\n// ever expose any actual OS information to the user.\n\nimport { validateNumber, validateObject } from 'node-internal:validators';\nimport { Buffer } from 'node-internal:internal_buffer';\nimport { ERR_INVALID_ARG_VALUE } from 'node-internal:internal_errors';\nimport type { ValidEncoding } from 'node-internal:internal_fs_utils';\nimport type { UserInfo, CpuInfo, NetworkInterfaceInfo } from 'node:os';\n\n// We always assume POSIX in the workers environment\nexport const EOL = '\\n';\nexport const devNull = '/dev/null';\n\nexport function availableParallelism(): number {\n  return 1;\n}\n\n// While Workers does support arm in production, that fact is not exposed\n// to the user. Let's just always report x64.\nexport function arch(): string {\n  return 'x64';\n}\n\n// We do not expose CPU information to Workers.\nexport function cpus(): CpuInfo[] {\n  return [];\n}\n\nexport function endianness(): 'LE' | 'BE' {\n  return 'LE';\n}\n\nexport function freemem(): number {\n  // We do not currently expose memory information to Workers. We might\n  // be able to in the future.\n  return 0;\n}\n\nexport function getPriority(pid?: number): number {\n  // Workers does not support process priority.\n  if (pid !== undefined) validateNumber(pid, 'pid');\n  return 0;\n}\n\nexport function homedir(): string {\n  // Workers really does not have a home directory. Return /tmp\n  // in preparation for the availability of node:fs in the future.\n  return '/tmp/';\n}\n\nexport function hostname(): string {\n  // Workers does not have a hostname. Return 'localhost' for compatibility.\n  return 'localhost';\n}\n\nexport function loadavg(): number[] {\n  // Workers does not expose load average information.\n  return [0, 0, 0];\n}\n\nexport function machine(): string {\n  // Workers does not expose the machine architecture.\n  return 'x86_64';\n}\n\nexport function networkInterfaces(): NetworkInterfaceInfo[] {\n  // Workers does not expose network interfaces.\n  // @ts-expect-error TS2740 We don't export properties here.\n  return {};\n}\n\nexport function platform(): NodeJS.Platform {\n  // Workers only supports POSIX platforms.\n  return 'linux';\n}\n\nexport function release(): string {\n  // Workers does not expose the OS release information.\n  return '';\n}\n\nexport function setPriority(pid: number, priority: number): void {\n  // Workers does not support process priority.\n  // Just ignore.\n  validateNumber(pid, 'pid');\n  validateNumber(priority, 'priority');\n}\n\nexport function tmpdir(): string {\n  return '/tmp/';\n}\n\nexport function totalmem(): number {\n  // We do not currently expose memory information to Workers. We might\n  // be able to in the future.\n  return 0;\n}\n\nexport function type(): string {\n  return 'Linux';\n}\n\nexport function uptime(): number {\n  // For now, we do not report uptime in Workers as it is not clear\n  // exactly what it should be. We might be able to in the future\n  // but we'll need to define what it means first.\n  return 0;\n}\n\nexport function userInfo(\n  options: { encoding?: ValidEncoding | undefined } = {}\n): UserInfo<unknown> {\n  // We really do not have user information to share.\n  validateObject(options, 'options');\n  const { encoding = null } = options;\n  if (\n    encoding !== null &&\n    encoding !== 'buffer' &&\n    !Buffer.isEncoding(encoding)\n  ) {\n    throw new ERR_INVALID_ARG_VALUE(\n      'options.encoding',\n      encoding,\n      'must be a valid encoding'\n    );\n  }\n  // @ts-expect-error TS2739 We don't export properties here.\n  return {};\n}\n\nexport function version(): string {\n  // Workers does not expose the OS version.\n  return '';\n}\n\nexport const constants = {\n  __proto__: null,\n  UV_UDP_REUSEADDR: 4,\n  dlopen: {\n    __proto__: null,\n    RTLD_LAZY: 1,\n    RTLD_NOW: 2,\n    RTLD_GLOBAL: 256,\n    RTLD_LOCAL: 0,\n    RTLD_DEEPBIND: 8,\n  },\n  errno: {\n    __proto__: null,\n    E2BIG: 7,\n    EACCES: 13,\n    EADDRINUSE: 98,\n    EADDRNOTAVAIL: 99,\n    EAFNOSUPPORT: 97,\n    EAGAIN: 11,\n    EALREADY: 114,\n    EBADF: 9,\n    EBADMSG: 74,\n    EBUSY: 16,\n    ECANCELED: 125,\n    ECHILD: 10,\n    ECONNABORTED: 103,\n    ECONNREFUSED: 111,\n    ECONNRESET: 104,\n    EDEADLK: 35,\n    EDESTADDRREQ: 89,\n    EDOM: 33,\n    EDQUOT: 122,\n    EEXIST: 17,\n    EFAULT: 14,\n    EFBIG: 27,\n    EHOSTUNREACH: 113,\n    EIDRM: 43,\n    EILSEQ: 84,\n    EINPROGRESS: 115,\n    EINTR: 4,\n    EINVAL: 22,\n    EIO: 5,\n    EISCONN: 106,\n    EISDIR: 21,\n    ELOOP: 40,\n    EMFILE: 24,\n    EMLINK: 31,\n    EMSGSIZE: 90,\n    EMULTIHOP: 72,\n    ENAMETOOLONG: 36,\n    ENETDOWN: 100,\n    ENETRESET: 102,\n    ENETUNREACH: 101,\n    ENFILE: 23,\n    ENOBUFS: 105,\n    ENODATA: 61,\n    ENODEV: 19,\n    ENOENT: 2,\n    ENOEXEC: 8,\n    ENOLCK: 37,\n    ENOLINK: 67,\n    ENOMEM: 12,\n    ENOMSG: 42,\n    ENOPROTOOPT: 92,\n    ENOSPC: 28,\n    ENOSR: 63,\n    ENOSTR: 60,\n    ENOSYS: 38,\n    ENOTCONN: 107,\n    ENOTDIR: 20,\n    ENOTEMPTY: 39,\n    ENOTSOCK: 88,\n    ENOTSUP: 95,\n    ENOTTY: 25,\n    ENXIO: 6,\n    EOPNOTSUPP: 95,\n    EOVERFLOW: 75,\n    EPERM: 1,\n    EPIPE: 32,\n    EPROTO: 71,\n    EPROTONOSUPPORT: 93,\n    EPROTOTYPE: 91,\n    ERANGE: 34,\n    EROFS: 30,\n    ESPIPE: 29,\n    ESRCH: 3,\n    ESTALE: 116,\n    ETIME: 62,\n    ETIMEDOUT: 110,\n    ETXTBSY: 26,\n    EWOULDBLOCK: 11,\n    EXDEV: 18,\n  },\n  signals: {\n    __proto__: null,\n    SIGHUP: 1,\n    SIGINT: 2,\n    SIGQUIT: 3,\n    SIGILL: 4,\n    SIGTRAP: 5,\n    SIGABRT: 6,\n    SIGIOT: 6,\n    SIGBUS: 7,\n    SIGFPE: 8,\n    SIGKILL: 9,\n    SIGUSR1: 10,\n    SIGSEGV: 11,\n    SIGUSR2: 12,\n    SIGPIPE: 13,\n    SIGALRM: 14,\n    SIGTERM: 15,\n    SIGCHLD: 17,\n    SIGSTKFLT: 16,\n    SIGCONT: 18,\n    SIGSTOP: 19,\n    SIGTSTP: 20,\n    SIGTTIN: 21,\n    SIGTTOU: 22,\n    SIGURG: 23,\n    SIGXCPU: 24,\n    SIGXFSZ: 25,\n    SIGVTALRM: 26,\n    SIGPROF: 27,\n    SIGWINCH: 28,\n    SIGIO: 29,\n    SIGPOLL: 29,\n    SIGPWR: 30,\n    SIGSYS: 31,\n  },\n  priority: {\n    __proto__: null,\n    PRIORITY_LOW: 19,\n    PRIORITY_BELOW_NORMAL: 10,\n    PRIORITY_NORMAL: 0,\n    PRIORITY_ABOVE_NORMAL: -7,\n    PRIORITY_HIGH: -14,\n    PRIORITY_HIGHEST: -20,\n  },\n};\nObject.freeze(constants);\nObject.freeze(constants.dlopen);\nObject.freeze(constants.errno);\nObject.freeze(constants.signals);\nObject.freeze(constants.priority);\n\nexport default {\n  EOL,\n  devNull,\n  constants,\n  availableParallelism,\n  arch,\n  cpus,\n  endianness,\n  freemem,\n  getPriority,\n  homedir,\n  hostname,\n  loadavg,\n  machine,\n  networkInterfaces,\n  platform,\n  release,\n  setPriority,\n  tmpdir,\n  totalmem,\n  type,\n  uptime,\n  userInfo,\n  version,\n};\n"
  },
  {
    "path": "src/node/path/posix.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n/* eslint-disable @typescript-eslint/unbound-method */\nimport { posix, win32 } from 'node-internal:internal_path';\nconst {\n  basename,\n  dirname,\n  extname,\n  format,\n  isAbsolute,\n  join,\n  matchesGlob,\n  normalize,\n  parse,\n  relative,\n  resolve,\n  toNamespacedPath,\n  delimiter,\n  sep,\n} = posix;\nexport {\n  basename,\n  dirname,\n  extname,\n  format,\n  isAbsolute,\n  join,\n  matchesGlob,\n  normalize,\n  parse,\n  relative,\n  resolve,\n  toNamespacedPath,\n  delimiter,\n  posix,\n  sep,\n  win32,\n};\nexport default posix;\n"
  },
  {
    "path": "src/node/path/win32.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n/* eslint-disable @typescript-eslint/unbound-method */\nimport { win32, posix } from 'node-internal:internal_path';\nconst {\n  basename,\n  dirname,\n  extname,\n  format,\n  isAbsolute,\n  join,\n  matchesGlob,\n  normalize,\n  parse,\n  relative,\n  resolve,\n  toNamespacedPath,\n  delimiter,\n  sep,\n} = win32;\nexport {\n  basename,\n  dirname,\n  extname,\n  format,\n  isAbsolute,\n  join,\n  matchesGlob,\n  normalize,\n  parse,\n  relative,\n  resolve,\n  toNamespacedPath,\n  delimiter,\n  posix,\n  sep,\n  win32,\n};\nexport default win32;\n"
  },
  {
    "path": "src/node/path.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n/* eslint-disable @typescript-eslint/unbound-method */\nimport { posix, win32 } from 'node-internal:internal_path';\n\nconst {\n  resolve,\n  normalize,\n  isAbsolute,\n  join,\n  relative,\n  toNamespacedPath,\n  dirname,\n  basename,\n  extname,\n  format,\n  parse,\n  sep,\n  delimiter,\n  matchesGlob,\n} = posix;\n\nexport {\n  resolve,\n  normalize,\n  isAbsolute,\n  join,\n  relative,\n  toNamespacedPath,\n  dirname,\n  basename,\n  extname,\n  format,\n  parse,\n  sep,\n  delimiter,\n  posix,\n  win32,\n  matchesGlob,\n};\n\nexport { default } from 'node-internal:internal_path';\n"
  },
  {
    "path": "src/node/perf_hooks.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors';\nimport { default as util } from 'node-internal:util';\n\nexport const constants = Object.freeze({\n  // GC type constants\n  NODE_PERFORMANCE_GC_MAJOR: 4,\n  NODE_PERFORMANCE_GC_MINOR: 1,\n  NODE_PERFORMANCE_GC_INCREMENTAL: 8,\n  NODE_PERFORMANCE_GC_WEAKCB: 16,\n\n  // GC flags constants\n  NODE_PERFORMANCE_GC_FLAGS_NO: 0,\n  NODE_PERFORMANCE_GC_FLAGS_CONSTRUCT_RETAINED: 2,\n  NODE_PERFORMANCE_GC_FLAGS_FORCED: 4,\n  NODE_PERFORMANCE_GC_FLAGS_SYNCHRONOUS_PHANTOM_PROCESSING: 8,\n  NODE_PERFORMANCE_GC_FLAGS_ALL_AVAILABLE_GARBAGE: 16,\n  NODE_PERFORMANCE_GC_FLAGS_ALL_EXTERNAL_MEMORY: 32,\n  NODE_PERFORMANCE_GC_FLAGS_SCHEDULE_IDLE: 64,\n\n  // Entry type constants\n  NODE_PERFORMANCE_ENTRY_TYPE_GC: 0,\n  NODE_PERFORMANCE_ENTRY_TYPE_HTTP: 1,\n  NODE_PERFORMANCE_ENTRY_TYPE_HTTP2: 2,\n  NODE_PERFORMANCE_ENTRY_TYPE_NET: 3,\n  NODE_PERFORMANCE_ENTRY_TYPE_DNS: 4,\n\n  // Milestone constants\n  NODE_PERFORMANCE_MILESTONE_TIME_ORIGIN_TIMESTAMP: 0,\n  NODE_PERFORMANCE_MILESTONE_TIME_ORIGIN: 1,\n  NODE_PERFORMANCE_MILESTONE_ENVIRONMENT: 2,\n  NODE_PERFORMANCE_MILESTONE_NODE_START: 3,\n  NODE_PERFORMANCE_MILESTONE_V8_START: 4,\n  NODE_PERFORMANCE_MILESTONE_LOOP_START: 5,\n  NODE_PERFORMANCE_MILESTONE_LOOP_EXIT: 6,\n  NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE: 7,\n});\n\n// Type definitions for Node.js-specific extensions\nexport interface EventLoopUtilization {\n  idle: number;\n  active: number;\n  utilization: number;\n}\n\n// Standalone function exports for Node.js compatibility\nexport function eventLoopUtilization(\n  _utilization1?: EventLoopUtilization,\n  _utilization2?: EventLoopUtilization\n): EventLoopUtilization {\n  // Return stub values - actual event loop utilization is not available in workerd\n  return { idle: 0, active: 0, utilization: 0 };\n}\n\nexport function timerify<T extends (...params: unknown[]) => unknown>(\n  fn: T\n): T {\n  // Return the function as-is - timing wrapper is not implemented in workerd\n  return fn;\n}\n\n// Re-export globalThis.performance which includes nodeTiming when the\n// enable_nodejs_perf_hooks_module flag is enabled (handled in C++).\nexport const performance = globalThis.performance;\n\nexport function createHistogram(): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('createHistogram');\n}\n\nexport function monitorEventLoopDelay(): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('monitorEventLoopDelay');\n}\n\nexport const Performance = util.Performance;\nexport const PerformanceEntry = util.PerformanceEntry;\nexport const PerformanceMeasure = util.PerformanceMeasure;\nexport const PerformanceMark = util.PerformanceMark;\nexport const PerformanceObserver = util.PerformanceObserver;\nexport const PerformanceObserverEntryList = util.PerformanceObserverEntryList;\nexport const PerformanceResourceTiming = util.PerformanceResourceTiming;\n\n// We intentionally not actually implement support for perf_hooks feedback into the worker.\n// Even though, classes like PerformanceEntry and PerformanceObserver are here, they are\n// not actually expected to be used for anything, at least not for the foreseeable future.\nexport default {\n  Performance,\n  PerformanceEntry,\n  PerformanceMark,\n  PerformanceMeasure,\n  PerformanceObserver,\n  PerformanceObserverEntryList,\n  PerformanceResourceTiming,\n  monitorEventLoopDelay,\n  createHistogram,\n  eventLoopUtilization,\n  timerify,\n  performance,\n  constants,\n};\n"
  },
  {
    "path": "src/node/punycode.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//    Copyright Mathias Bynens <https://mathiasbynens.be/>\n//\n//    Permission is hereby granted, free of charge, to any person obtaining\n//    a copy of this software and associated documentation files (the\n//    \"Software\"), to deal in the Software without restriction, including\n//    without limitation the rights to use, copy, modify, merge, publish,\n//    distribute, sublicense, and/or sell copies of the Software, and to\n//    permit persons to whom the Software is furnished to do so, subject to\n//    the following conditions:\n//\n//    The above copyright notice and this permission notice shall be\n//    included in all copies or substantial portions of the Software.\n//\n//    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n//    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n//    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n//    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n//    LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n//    OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n//    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n/** Highest positive signed 32-bit float value */\nconst maxInt = 2147483647; // aka. 0x7FFFFFFF or 2^31-1\n\n/** Bootstring parameters */\nconst base = 36;\nconst tMin = 1;\nconst tMax = 26;\nconst skew = 38;\nconst damp = 700;\nconst initialBias = 72;\nconst initialN = 128; // 0x80\nconst delimiter = '-'; // '\\x2D'\n\n/** Regular expressions */\nconst regexPunycode = /^xn--/;\nconst regexNonASCII = /[^\\0-\\x7F]/; // Note: U+007F DEL is excluded too.\nconst regexSeparators = /[\\x2E\\u3002\\uFF0E\\uFF61]/g; // RFC 3490 separators\n\n/** Error messages */\nconst errors = {\n  overflow: 'Overflow: input needs wider integers to process',\n  'not-basic': 'Illegal input >= 0x80 (not a basic code point)',\n  'invalid-input': 'Invalid input',\n};\n\n/** Convenience shortcuts */\nconst baseMinusTMin = base - tMin;\nconst floor = Math.floor;\nconst stringFromCharCode = String.fromCharCode;\n\n/*--------------------------------------------------------------------------*/\n\n/**\n * A generic error utility function.\n * @private\n * @param {String} type The error type.\n * @returns {Error} Throws a `RangeError` with the applicable error message.\n */\nfunction error(type: keyof typeof errors): void {\n  throw new RangeError(errors[type]);\n}\n\n/**\n * A generic `Array#map` utility function.\n * @private\n * @param {Array} array The array to iterate over.\n * @param {Function} callback The function that gets called for every array\n * item.\n * @returns {Array} A new array of values returned by the callback function.\n */\nfunction map<T>(array: T[], callback: (item: T) => T): T[] {\n  const result = [];\n  let length = array.length;\n  while (length--) {\n    result[length] = callback(array[length] as T);\n  }\n  return result;\n}\n\n/**\n * A simple `Array#map`-like wrapper to work with domain name strings or email\n * addresses.\n * @private\n * @param {String} domain The domain name or email address.\n * @param {Function} callback The function that gets called for every\n * character.\n * @returns {String} A new string of characters returned by the callback\n * function.\n */\nfunction mapDomain(domain: string, callback: (char: string) => string): string {\n  const parts = domain.split('@');\n  let result = '';\n  if (parts.length > 1) {\n    // In email addresses, only the domain name should be punycoded. Leave\n    // the local part (i.e. everything up to `@`) intact.\n    result = parts[0] + '@'; // eslint-disable-line @typescript-eslint/restrict-plus-operands\n    domain = parts[1] as string;\n  }\n  // Avoid `split(regex)` for IE8 compatibility. See #17.\n  domain = domain.replace(regexSeparators, '\\x2E');\n  const labels = domain.split('.');\n  const encoded = map(labels, callback).join('.');\n  return result + encoded;\n}\n\n/**\n * Creates an array containing the numeric code points of each Unicode\n * character in the string. While JavaScript uses UCS-2 internally,\n * this function will convert a pair of surrogate halves (each of which\n * UCS-2 exposes as separate characters) into a single code point,\n * matching UTF-16.\n * @see `punycode.ucs2.encode`\n * @see <https://mathiasbynens.be/notes/javascript-encoding>\n * @memberOf punycode.ucs2\n * @name decode\n * @param {String} string The Unicode input string (UCS-2).\n * @returns {Array} The new array of code points.\n */\nfunction ucs2decode(string: string): number[] {\n  const output = [];\n  let counter = 0;\n  const length = string.length;\n  while (counter < length) {\n    const value = string.charCodeAt(counter++);\n    if (value >= 0xd800 && value <= 0xdbff && counter < length) {\n      // It's a high surrogate, and there is a next character.\n      const extra = string.charCodeAt(counter++);\n      if ((extra & 0xfc00) == 0xdc00) {\n        // Low surrogate.\n        output.push(((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000);\n      } else {\n        // It's an unmatched surrogate; only append this code unit, in case the\n        // next code unit is the high surrogate of a surrogate pair.\n        output.push(value);\n        counter--;\n      }\n    } else {\n      output.push(value);\n    }\n  }\n  return output;\n}\n\n/**\n * Creates a string based on an array of numeric code points.\n * @see `punycode.ucs2.decode`\n * @memberOf punycode.ucs2\n * @name encode\n * @param {Array} codePoints The array of numeric code points.\n * @returns {String} The new Unicode string (UCS-2).\n */\nconst ucs2encode = (codePoints: number[]): string =>\n  String.fromCodePoint(...codePoints);\n\n/**\n * Converts a basic code point into a digit/integer.\n * @see `digitToBasic()`\n * @private\n * @param {Number} codePoint The basic numeric code point value.\n * @returns {Number} The numeric value of a basic code point (for use in\n * representing integers) in the range `0` to `base - 1`, or `base` if\n * the code point does not represent a value.\n */\nconst basicToDigit = function (codePoint: number): number {\n  if (codePoint >= 0x30 && codePoint < 0x3a) {\n    return 26 + (codePoint - 0x30);\n  }\n  if (codePoint >= 0x41 && codePoint < 0x5b) {\n    return codePoint - 0x41;\n  }\n  if (codePoint >= 0x61 && codePoint < 0x7b) {\n    return codePoint - 0x61;\n  }\n  return base;\n};\n\n/**\n * Converts a digit/integer into a basic code point.\n * @see `basicToDigit()`\n * @private\n * @param {Number} digit The numeric value of a basic code point.\n * @returns {Number} The basic code point whose value (when used for\n * representing integers) is `digit`, which needs to be in the range\n * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is\n * used; else, the lowercase form is used. The behavior is undefined\n * if `flag` is non-zero and `digit` has no uppercase form.\n */\nconst digitToBasic = function (digit: number, flag: number): number {\n  //  0..25 map to ASCII a..z or A..Z\n  // 26..35 map to ASCII 0..9\n  return digit + 22 + 75 * Number(digit < 26) - (Number(flag != 0) << 5);\n};\n\n/**\n * Bias adaptation function as per section 3.4 of RFC 3492.\n * https://tools.ietf.org/html/rfc3492#section-3.4\n * @private\n */\nconst adapt = function (\n  delta: number,\n  numPoints: number,\n  firstTime: boolean\n): number {\n  let k = 0;\n  delta = firstTime ? floor(delta / damp) : delta >> 1;\n  delta += floor(delta / numPoints);\n  for (\n    ;\n    /* no initialization */ delta > (baseMinusTMin * tMax) >> 1;\n    k += base\n  ) {\n    delta = floor(delta / baseMinusTMin);\n  }\n  return floor(k + ((baseMinusTMin + 1) * delta) / (delta + skew));\n};\n\n/**\n * Converts a Punycode string of ASCII-only symbols to a string of Unicode\n * symbols.\n * @memberOf punycode\n * @param {String} input The Punycode string of ASCII-only symbols.\n * @returns {String} The resulting string of Unicode symbols.\n */\nexport const decode = function (input: string): string {\n  // Don't use UCS-2.\n  const output = [];\n  const inputLength = input.length;\n  let i = 0;\n  let n = initialN;\n  let bias = initialBias;\n\n  // Handle the basic code points: let `basic` be the number of input code\n  // points before the last delimiter, or `0` if there is none, then copy\n  // the first basic code points to the output.\n\n  let basic = input.lastIndexOf(delimiter);\n  if (basic < 0) {\n    basic = 0;\n  }\n\n  for (let j = 0; j < basic; ++j) {\n    // if it's not a basic code point\n    if (input.charCodeAt(j) >= 0x80) {\n      error('not-basic');\n    }\n    output.push(input.charCodeAt(j));\n  }\n\n  // Main decoding loop: start just after the last delimiter if any basic code\n  // points were copied; start at the beginning otherwise.\n\n  for (\n    let index = basic > 0 ? basic + 1 : 0;\n    index < inputLength /* no final expression */;\n  ) {\n    // `index` is the index of the next character to be consumed.\n    // Decode a generalized variable-length integer into `delta`,\n    // which gets added to `i`. The overflow checking is easier\n    // if we increase `i` as we go, then subtract off its starting\n    // value at the end to obtain `delta`.\n    const oldi = i;\n    for (let w = 1, k = base /* no condition */; ; k += base) {\n      if (index >= inputLength) {\n        error('invalid-input');\n      }\n\n      const digit = basicToDigit(input.charCodeAt(index++));\n\n      if (digit >= base) {\n        error('invalid-input');\n      }\n      if (digit > floor((maxInt - i) / w)) {\n        error('overflow');\n      }\n\n      i += digit * w;\n      const t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias;\n\n      if (digit < t) {\n        break;\n      }\n\n      const baseMinusT = base - t;\n      if (w > floor(maxInt / baseMinusT)) {\n        error('overflow');\n      }\n\n      w *= baseMinusT;\n    }\n\n    const out = output.length + 1;\n    bias = adapt(i - oldi, out, oldi == 0);\n\n    // `i` was supposed to wrap around from `out` to `0`,\n    // incrementing `n` each time, so we'll fix that now:\n    if (floor(i / out) > maxInt - n) {\n      error('overflow');\n    }\n\n    n += floor(i / out);\n    i %= out;\n\n    // Insert `n` at position `i` of the output.\n    output.splice(i++, 0, n);\n  }\n\n  return String.fromCodePoint(...output);\n};\n\n/**\n * Converts a string of Unicode symbols (e.g. a domain name label) to a\n * Punycode string of ASCII-only symbols.\n * @memberOf punycode\n * @param {String} input The string of Unicode symbols.\n * @returns {String} The resulting Punycode string of ASCII-only symbols.\n */\nexport const encode = function (input: string | number[]): string {\n  const output = [];\n\n  // Convert the input in UCS-2 to an array of Unicode code points.\n  input = ucs2decode(input as string);\n\n  // Cache the length.\n  const inputLength = input.length;\n\n  // Initialize the state.\n  let n = initialN;\n  let delta = 0;\n  let bias = initialBias;\n\n  // Handle the basic code points.\n  for (const currentValue of input) {\n    if (currentValue < 0x80) {\n      output.push(stringFromCharCode(currentValue));\n    }\n  }\n\n  const basicLength = output.length;\n  let handledCPCount = basicLength;\n\n  // `handledCPCount` is the number of code points that have been handled;\n  // `basicLength` is the number of basic code points.\n\n  // Finish the basic string with a delimiter unless it's empty.\n  if (basicLength) {\n    output.push(delimiter);\n  }\n\n  // Main encoding loop:\n  while (handledCPCount < inputLength) {\n    // All non-basic code points < n have been handled already. Find the next\n    // larger one:\n    let m = maxInt;\n    for (const currentValue of input) {\n      if (currentValue >= n && currentValue < m) {\n        m = currentValue;\n      }\n    }\n\n    // Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,\n    // but guard against overflow.\n    const handledCPCountPlusOne = handledCPCount + 1;\n    if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {\n      error('overflow');\n    }\n\n    delta += (m - n) * handledCPCountPlusOne;\n    n = m;\n\n    for (const currentValue of input) {\n      if (currentValue < n && ++delta > maxInt) {\n        error('overflow');\n      }\n      if (currentValue === n) {\n        // Represent delta as a generalized variable-length integer.\n        let q = delta;\n        for (let k = base /* no condition */; ; k += base) {\n          const t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias;\n          if (q < t) {\n            break;\n          }\n          const qMinusT = q - t;\n          const baseMinusT = base - t;\n          output.push(\n            stringFromCharCode(digitToBasic(t + (qMinusT % baseMinusT), 0))\n          );\n          q = floor(qMinusT / baseMinusT);\n        }\n\n        output.push(stringFromCharCode(digitToBasic(q, 0)));\n        bias = adapt(\n          delta,\n          handledCPCountPlusOne,\n          handledCPCount === basicLength\n        );\n        delta = 0;\n        ++handledCPCount;\n      }\n    }\n\n    ++delta;\n    ++n;\n  }\n  return output.join('');\n};\n\n/**\n * Converts a Punycode string representing a domain name or an email address\n * to Unicode. Only the Punycoded parts of the input will be converted, i.e.\n * it doesn't matter if you call it on a string that has already been\n * converted to Unicode.\n * @memberOf punycode\n * @param {String} input The Punycoded domain name or email address to\n * convert to Unicode.\n * @returns {String} The Unicode representation of the given Punycode\n * string.\n */\nexport const toUnicode = function (input: string): string {\n  return mapDomain(input, function (string) {\n    return regexPunycode.test(string)\n      ? decode(string.slice(4).toLowerCase())\n      : string;\n  });\n};\n\n/**\n * Converts a Unicode string representing a domain name or an email address to\n * Punycode. Only the non-ASCII parts of the domain name will be converted,\n * i.e. it doesn't matter if you call it with a domain that's already in\n * ASCII.\n * @memberOf punycode\n * @param {String} input The domain name or email address to convert, as a\n * Unicode string.\n * @returns {String} The Punycode representation of the given domain name or\n * email address.\n */\nexport const toASCII = function (input: string): string {\n  return mapDomain(input, function (string) {\n    return regexNonASCII.test(string) ? 'xn--' + encode(string) : string;\n  });\n};\n\n/*--------------------------------------------------------------------------*/\n\n/** Define the public API */\nexport const version = '2.1.0';\n\nexport const ucs2 = {\n  decode: ucs2decode,\n  encode: ucs2encode,\n};\n\nexport default {\n  /**\n   * A string representing the current Punycode.js version number.\n   * @memberOf punycode\n   * @type String\n   */\n  version,\n  /**\n   * An object of methods to convert from JavaScript's internal character\n   * representation (UCS-2) to Unicode code points, and back.\n   * @see <https://mathiasbynens.be/notes/javascript-encoding>\n   * @memberOf punycode\n   * @type Object\n   */\n  ucs2,\n  decode,\n  encode,\n  toASCII,\n  toUnicode,\n};\n"
  },
  {
    "path": "src/node/querystring.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport {\n  escape,\n  parse,\n  stringify,\n  unescape,\n  unescapeBuffer,\n} from 'node-internal:internal_querystring';\n\nexport const encode = stringify;\nexport const decode = parse;\n\nexport { escape, parse, stringify, unescape, unescapeBuffer };\n\nexport default {\n  decode,\n  encode,\n  escape,\n  parse,\n  stringify,\n  unescape,\n  unescapeBuffer,\n};\n"
  },
  {
    "path": "src/node/readline/promises.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport {\n  Interface,\n  Readline,\n  createInterface,\n} from 'node-internal:internal_readline_promises';\n\nexport * from 'node-internal:internal_readline_promises';\n\nexport default {\n  Interface,\n  Readline,\n  createInterface,\n};\n"
  },
  {
    "path": "src/node/readline.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport * as promises from 'node-internal:internal_readline_promises';\nimport { Interface } from 'node-internal:internal_readline';\nimport type Readline from 'node:readline';\n\nexport { promises, Interface };\n\nexport function clearLine(): boolean {\n  return false;\n}\n\nexport function clearScreenDown(): boolean {\n  return false;\n}\n\nexport function createInterface(): Readline.Interface {\n  return new Interface();\n}\n\nexport function cursorTo(): boolean {\n  return false;\n}\n\nexport function emitKeypressEvents(): void {\n  // Acts as a no-op.\n}\nexport function moveCursor(): boolean {\n  return false;\n}\n\nexport default {\n  clearLine,\n  clearScreenDown,\n  createInterface,\n  cursorTo,\n  emitKeypressEvents,\n  moveCursor,\n  Interface,\n  promises,\n};\n"
  },
  {
    "path": "src/node/repl.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors';\nimport { builtinModules as _builtinModules } from 'node-internal:internal_module';\nimport { Interface } from 'node-internal:internal_readline_promises';\nimport type { InspectOptions } from 'node:util';\nimport type { Context } from 'node:vm';\nimport type ReplType from 'node:repl';\nimport type { Completer, AsyncCompleter } from 'readline';\n\nexport function writer(): ReplType.REPLWriter & { options: InspectOptions } {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('writer');\n}\n\nconst writerOptions: InspectOptions = {\n  showHidden: false,\n  depth: 2,\n  colors: true,\n  customInspect: true,\n  showProxy: true,\n  maxArrayLength: 100,\n  maxStringLength: 10000,\n  breakLength: 80,\n  compact: 3,\n  sorted: false,\n  getters: false,\n  numericSeparator: false,\n};\nObject.assign(writer, { options: writerOptions });\n\nexport function start(): ReplType.REPLServer {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('start');\n}\n\nexport class Recoverable extends SyntaxError implements ReplType.Recoverable {\n  err: Error;\n  constructor(err: Error) {\n    super();\n    this.err = err;\n  }\n}\n\nexport class REPLServer extends Interface implements ReplType.REPLServer {\n  context: Context;\n  inputStream: NodeJS.ReadableStream;\n  input: NodeJS.ReadableStream;\n  outputStream: NodeJS.WritableStream;\n  output: NodeJS.WritableStream;\n  commands: NodeJS.ReadOnlyDict<ReplType.REPLCommand>;\n  editorMode: boolean;\n  underscoreAssigned: boolean;\n  underscoreErrAssigned: boolean;\n  last: unknown;\n  lastError: unknown;\n  eval: ReplType.REPLEval;\n  useColors: boolean;\n  useGlobal: boolean;\n  ignoreUndefined: boolean;\n  writer: ReplType.REPLWriter;\n  completer: Completer | AsyncCompleter;\n  replMode: typeof ReplType.REPL_MODE_SLOPPY | typeof ReplType.REPL_MODE_STRICT;\n\n  constructor() {\n    super();\n    throw new ERR_METHOD_NOT_IMPLEMENTED('REPLServer');\n  }\n\n  setupHistory(_historyConfig?: unknown, _callback?: unknown): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('setupHistory');\n  }\n\n  defineCommand(\n    _keyword: string,\n    _cmd: ReplType.REPLCommandAction | ReplType.REPLCommand\n  ): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('defineCommand');\n  }\n\n  displayPrompt(_preserveCursor?: boolean): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('displayPrompt');\n  }\n\n  clearBufferedCommand(): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('clearBufferedCommand');\n  }\n}\n\nexport const REPL_MODE_SLOPPY: unique symbol = Symbol('repl-sloppy');\n\nexport const REPL_MODE_STRICT: unique symbol = Symbol('repl-strict');\n\nexport const builtinModules: string[] = _builtinModules.filter(\n  (m) => m[0] !== '_'\n);\n\nexport const _builtinLibs = builtinModules;\n\nexport default {\n  writer,\n  start,\n  Recoverable,\n  REPLServer,\n  builtinModules,\n  _builtinLibs,\n  REPL_MODE_SLOPPY,\n  REPL_MODE_STRICT,\n};\n"
  },
  {
    "path": "src/node/sqlite.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { default as sqliteUtil } from 'node-internal:sqlite';\nimport type sqlite from 'node:sqlite';\n\nconst {\n  DatabaseSync,\n  StatementSync,\n  SQLITE_CHANGESET_OMIT,\n  SQLITE_CHANGESET_REPLACE,\n  SQLITE_CHANGESET_ABORT,\n  SQLITE_CHANGESET_DATA,\n  SQLITE_CHANGESET_NOTFOUND,\n  SQLITE_CHANGESET_CONFLICT,\n  SQLITE_CHANGESET_CONSTRAINT,\n  SQLITE_CHANGESET_FOREIGN_KEY,\n} = sqliteUtil;\n\nexport const backup = sqliteUtil.backup.bind(sqliteUtil);\n\nexport const constants: typeof sqlite.constants = {\n  SQLITE_CHANGESET_OMIT,\n  SQLITE_CHANGESET_REPLACE,\n  SQLITE_CHANGESET_ABORT,\n  SQLITE_CHANGESET_DATA,\n  SQLITE_CHANGESET_NOTFOUND,\n  SQLITE_CHANGESET_CONFLICT,\n  // @ts-expect-error TS2561 This is missing from node.js types\n  SQLITE_CHANGESET_CONSTRAINT,\n  SQLITE_CHANGESET_FOREIGN_KEY,\n};\n\nexport { DatabaseSync, StatementSync };\n\nexport default {\n  DatabaseSync,\n  StatementSync,\n  constants,\n  backup,\n} satisfies typeof sqlite;\n"
  },
  {
    "path": "src/node/stream/consumers.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { Buffer } from 'node-internal:internal_buffer';\n\nexport async function blob(stream) {\n  return new Blob(await Array.fromAsync(stream)); // eslint-disable-line no-undef\n}\n\nexport async function arrayBuffer(stream) {\n  const ret = await blob(stream);\n  return ret.arrayBuffer();\n}\n\nexport async function buffer(stream) {\n  return Buffer.from(await arrayBuffer(stream));\n}\n\nexport async function text(stream) {\n  const dec = new TextDecoder(); // eslint-disable-line no-undef\n  let str = '';\n  for await (const chunk of stream) {\n    if (typeof chunk === 'string') str += chunk;\n    else str += dec.decode(chunk, { stream: true });\n  }\n  // Flush the streaming TextDecoder so that any pending\n  // incomplete multibyte characters are handled.\n  str += dec.decode(undefined, { stream: false });\n  return str;\n}\n\nexport async function json(stream) {\n  const str = await text(stream);\n  return JSON.parse(str);\n}\n\nexport default {\n  arrayBuffer,\n  blob,\n  buffer,\n  text,\n  json,\n};\n"
  },
  {
    "path": "src/node/stream/promises.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\nimport { promises } from 'node-internal:streams_promises';\nexport * from 'node-internal:streams_promises';\nexport default promises;\n"
  },
  {
    "path": "src/node/stream/web.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n\nexport const ReadableStream = globalThis.ReadableStream;\nexport const ReadableStreamDefaultReader =\n  globalThis.ReadableStreamDefaultReader;\nexport const ReadableStreamBYOBReader = globalThis.ReadableStreamBYOBReader;\nexport const ReadableStreamBYOBRequest = globalThis.ReadableStreamBYOBRequest;\nexport const ReadableByteStreamController =\n  globalThis.ReadableByteStreamController;\nexport const ReadableStreamDefaultController =\n  globalThis.ReadableStreamDefaultController;\nexport const TransformStream = globalThis.TransformStream;\nexport const TransformStreamDefaultController =\n  globalThis.TransformStreamDefaultController;\nexport const WritableStream = globalThis.WritableStream;\nexport const WritableStreamDefaultWriter =\n  globalThis.WritableStreamDefaultWriter;\nexport const WritableStreamDefaultController =\n  globalThis.WritableStreamDefaultController;\nexport const ByteLengthQueuingStrategy = globalThis.ByteLengthQueuingStrategy;\nexport const CountQueuingStrategy = globalThis.CountQueuingStrategy;\nexport const TextEncoderStream = globalThis.TextEncoderStream;\nexport const TextDecoderStream = globalThis.TextDecoderStream;\nexport const CompressionStream = globalThis.CompressionStream;\nexport const DecompressionStream = globalThis.DecompressionStream;\n\nexport default {\n  ReadableStream,\n  ReadableStreamDefaultReader,\n  ReadableStreamBYOBReader,\n  ReadableStreamBYOBRequest,\n  ReadableByteStreamController,\n  ReadableStreamDefaultController,\n  TransformStream,\n  TransformStreamDefaultController,\n  WritableStream,\n  WritableStreamDefaultWriter,\n  WritableStreamDefaultController,\n  ByteLengthQueuingStrategy,\n  CountQueuingStrategy,\n  TextEncoderStream,\n  TextDecoderStream,\n  CompressionStream,\n  DecompressionStream,\n};\n"
  },
  {
    "path": "src/node/stream.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n\nimport { pipeline } from 'node-internal:streams_pipeline';\nimport { addAbortSignal } from 'node-internal:streams_add_abort_signal';\nimport { eos } from 'node-internal:streams_end_of_stream';\nimport {\n  isErrored,\n  isDisturbed,\n  isDestroyed,\n  isReadable,\n  isWritable,\n} from 'node-internal:streams_util';\nimport { destroyer } from 'node-internal:streams_destroy';\nimport {\n  getDefaultHighWaterMark,\n  setDefaultHighWaterMark,\n} from 'node-internal:streams_state';\nimport { compose } from 'node-internal:streams_compose';\nimport { Stream } from 'node-internal:streams_legacy';\nimport { Writable } from 'node-internal:streams_writable';\nimport { Readable } from 'node-internal:streams_readable';\nimport { Duplex, duplexPair } from 'node-internal:streams_duplex';\nimport { Transform, PassThrough } from 'node-internal:streams_transform';\nimport { promises } from 'node-internal:streams_promises';\n\n// eslint-disable-next-line @typescript-eslint/unbound-method\nexport const _isArrayBufferView = Stream._isArrayBufferView;\n// eslint-disable-next-line @typescript-eslint/unbound-method\nexport const _isUint8Array = Stream._isUint8Array;\n// eslint-disable-next-line @typescript-eslint/unbound-method\nexport const _uint8ArrayToBuffer = Stream._uint8ArrayToBuffer;\nconst destroy = destroyer;\nconst finished = eos;\n\nexport {\n  addAbortSignal,\n  compose,\n  destroy,\n  finished,\n  isErrored,\n  isDisturbed,\n  isReadable,\n  pipeline,\n  Stream,\n  Writable,\n  Readable,\n  Duplex,\n  Transform,\n  PassThrough,\n  promises,\n  getDefaultHighWaterMark,\n  setDefaultHighWaterMark,\n  isDestroyed,\n  isWritable,\n  duplexPair,\n};\n\n// @ts-expect-error TS2322 Type incompatibility between internal and Node.js stream types\nStream.addAbortSignal = addAbortSignal;\nStream.compose = compose;\nStream.destroy = destroy;\n// @ts-expect-error TS2741 __promisify__ is missing.\nStream.finished = finished;\nStream.isReadable = isReadable;\n// @ts-expect-error TS2322 Type incompatibility between internal and Node.js stream types\nStream.isWritable = isWritable;\nStream.isErrored = isErrored;\nStream.isDisturbed = isDisturbed;\nStream.pipeline = pipeline;\nStream.Stream = Stream;\n// @ts-expect-error TS2419 Type incompatibility due to exactOptionalPropertyTypes\nStream.Writable = Writable;\n// @ts-expect-error TS2419 Type incompatibility due to exactOptionalPropertyTypes\nStream.Readable = Readable;\nStream.Duplex = Duplex;\n// @ts-expect-error TS2419 Type incompatibility due to exactOptionalPropertyTypes\nStream.Transform = Transform;\nStream.PassThrough = PassThrough;\n// @ts-expect-error TS2322 Type incompatibility due to exactOptionalPropertyTypes\nStream.promises = promises;\nStream.getDefaultHighWaterMark = getDefaultHighWaterMark;\nStream.setDefaultHighWaterMark = setDefaultHighWaterMark;\nStream.isDestroyed = isDestroyed;\nStream.duplexPair = duplexPair;\n\nexport default Stream;\n"
  },
  {
    "path": "src/node/string_decoder.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { StringDecoder } from 'node-internal:internal_stringdecoder';\nexport { StringDecoder };\nexport default { StringDecoder };\n"
  },
  {
    "path": "src/node/test.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport { MockTracker, MockFunctionContext } from 'node-internal:mock';\n\nconst mock = new MockTracker();\n\nexport { mock, MockFunctionContext, MockTracker };\nexport default {\n  mock,\n  MockFunctionContext,\n  MockTracker,\n};\n"
  },
  {
    "path": "src/node/timers/promises.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport type promises from 'node:timers/promises';\n\nimport {\n  setTimeout,\n  setImmediate,\n  setInterval,\n  scheduler,\n} from 'node-internal:internal_timers_promises';\n\nexport * from 'node-internal:internal_timers_promises';\n\nexport default {\n  setTimeout,\n  setImmediate,\n  setInterval,\n  scheduler: scheduler as typeof promises.scheduler,\n} satisfies typeof promises;\n"
  },
  {
    "path": "src/node/timers.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport * as _promises from 'node-internal:internal_timers_promises';\nimport {\n  setTimeout,\n  clearTimeout,\n  setImmediate,\n  clearImmediate,\n  setInterval,\n  clearInterval,\n  active,\n  unenroll,\n  enroll,\n} from 'node-internal:internal_timers';\n\nexport * from 'node-internal:internal_timers';\nexport const promises = _promises;\n\nexport default {\n  promises: _promises,\n  setTimeout,\n  clearTimeout,\n  setImmediate,\n  clearImmediate,\n  setInterval,\n  clearInterval,\n  active,\n  unenroll,\n  enroll,\n};\n"
  },
  {
    "path": "src/node/tls.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport {\n  checkServerIdentity,\n  convertALPNProtocols,\n  createServer,\n  Server,\n  getCiphers,\n} from 'node-internal:internal_tls';\nimport {\n  createSecureContext,\n  SecureContext,\n} from 'node-internal:internal_tls_common';\nimport * as constants from 'node-internal:internal_tls_constants';\nimport { TLSSocket, connect } from 'node-internal:internal_tls_wrap';\nimport { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors';\n\nlet createSecurePair = undefined;\n\nif (!Cloudflare.compatibilityFlags.remove_nodejs_compat_eol_v24) {\n  createSecurePair = function createSecurePair(): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('createSecurePair');\n  };\n}\n\nexport * from 'node-internal:internal_tls_constants';\nexport {\n  TLSSocket,\n  connect,\n  createSecureContext,\n  createServer,\n  checkServerIdentity,\n  SecureContext,\n  Server,\n  convertALPNProtocols,\n  getCiphers,\n  createSecurePair,\n};\nexport default {\n  SecureContext,\n  Server,\n  TLSSocket,\n  connect,\n  createSecureContext,\n  createServer,\n  checkServerIdentity,\n  convertALPNProtocols,\n  getCiphers,\n  createSecurePair,\n  ...constants,\n};\n"
  },
  {
    "path": "src/node/trace_events.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport type * as TraceEvents from 'node:trace_events';\nimport { ERR_TRACE_EVENTS_CATEGORY_REQUIRED } from 'node-internal:internal_errors';\nimport { validateObject, validateStringArray } from 'node-internal:validators';\nimport { format, customInspectSymbol } from 'node-internal:internal_inspect';\n\n// TODO(soon): It is conceivable that we might implement this as part of\n// the workers observability features in the future. For now it's a non\n// functional stub.\n\nclass Tracing implements TraceEvents.Tracing {\n  #categories: string[];\n  #enabled = false;\n\n  constructor(categories: string[]) {\n    this.#categories = categories;\n  }\n\n  enable(): void {\n    this.#enabled = true;\n    // non-op\n  }\n\n  disable(): void {\n    this.#enabled = false;\n    // non-op\n  }\n\n  get enabled(): boolean {\n    return this.#enabled;\n  }\n\n  get categories(): string {\n    return this.#categories.join(',');\n  }\n\n  [customInspectSymbol](depth?: number, _: object = {}): string | object {\n    if (typeof depth === 'number' && depth < 0) return this;\n\n    const obj = {\n      enabled: this.enabled,\n      categories: this.categories,\n    };\n    return `Tracing ${format(obj)}`;\n  }\n}\n\nexport function createTracing(\n  options: TraceEvents.CreateTracingOptions\n): Tracing {\n  validateObject(options, 'options');\n  validateStringArray(options.categories, 'options.categories');\n  if (options.categories.length <= 0) {\n    throw new ERR_TRACE_EVENTS_CATEGORY_REQUIRED();\n  }\n  return new Tracing(options.categories);\n}\n\nexport function getEnabledCategories(): string | undefined {\n  return undefined;\n}\n\nexport default {\n  createTracing,\n  getEnabledCategories,\n} satisfies typeof TraceEvents;\n"
  },
  {
    "path": "src/node/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tools/base.tsconfig.json\",\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"types\": [\"@types/node\"],\n    \"paths\": {\n      \"node:*\": [\"./*\"],\n      \"node:assert/*\": [\"./*\"],\n      \"node:dns/*\": [\"./*\"],\n      \"node:util/*\": [\"./*\"],\n      \"node:path/*\": [\"./*\"],\n      \"node:stream/*\": [\"./*\"],\n      \"node:timers/*\": [\"./*\"],\n      \"node:sqlite/*\": [\"./*\"],\n      \"node-internal:*\": [\"./internal/*\"],\n      \"cloudflare-internal:http\": [\"./internal/http.d.ts\"],\n      \"cloudflare-internal:sockets\": [\"./internal/sockets.d.ts\"],\n      \"cloudflare-internal:workers\": [\"./internal/workers.d.ts\"],\n      \"cloudflare-internal:tracing\": [\"./internal/tracing.d.ts\"],\n      \"cloudflare-internal:filesystem\": [\"./internal/filesystem.d.ts\"],\n      \"cloudflare-internal:messagechannel\": [\"./internal/messagechannel.d.ts\"]\n    }\n  }\n}\n"
  },
  {
    "path": "src/node/tty.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { Socket } from 'node-internal:internal_net';\n\nimport type {\n  ReadStream as ReadStreamType,\n  WriteStream as WriteStreamType,\n} from 'node:tty';\n\nimport type { SocketConstructorOpts } from 'node:net';\n\n// Extended types to include fd property which exists at runtime\ninterface ReadStreamInstance extends ReadStreamType {\n  fd: number;\n}\n\ninterface WriteStreamInstance extends WriteStreamType {\n  fd: number;\n}\n\nexport function isatty(_fd: number): boolean {\n  return false;\n}\n\nexport function ReadStream(\n  this: ReadStreamInstance,\n  fd: number,\n  _options: SocketConstructorOpts = {}\n): ReadStreamInstance {\n  this.fd = fd;\n  this.isRaw = false;\n  this.isTTY = false;\n  return this;\n}\n\nObject.setPrototypeOf(ReadStream.prototype, Socket.prototype);\nObject.setPrototypeOf(ReadStream, Socket);\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nReadStream.prototype.setRawMode = function (\n  this: ReadStreamInstance,\n  mode: boolean\n): ReadStreamInstance {\n  this.isRaw = mode;\n  return this;\n};\n\nexport function WriteStream(\n  this: WriteStreamInstance,\n  fd: number,\n  _options: SocketConstructorOpts = {}\n): WriteStreamInstance {\n  this.fd = fd;\n  this.columns = 0;\n  this.rows = 0;\n  return this;\n}\n\nObject.setPrototypeOf(WriteStream.prototype, Socket.prototype);\nObject.setPrototypeOf(WriteStream, Socket);\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nWriteStream.prototype.isTTY = true;\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nWriteStream.prototype.getColorDepth = function (): number {\n  return 8; // In Node.js, this means 256 colors, but we don't support colors at all.\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nWriteStream.prototype.hasColors = function (): boolean {\n  return false;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nWriteStream.prototype._refreshSize = function (): void {\n  // no-op\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nWriteStream.prototype.cursorTo = function (\n  _x: number,\n  _y?: number | (() => void),\n  _callback?: () => void\n): boolean {\n  if (typeof _y === 'function') {\n    _y();\n  } else if (typeof _callback === 'function') {\n    _callback();\n  }\n  return false;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nWriteStream.prototype.moveCursor = function (\n  _dx: number,\n  _dy: number,\n  _callback?: () => void\n): boolean {\n  if (typeof _callback === 'function') {\n    _callback();\n  }\n  return false;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nWriteStream.prototype.clearLine = function (\n  _dir: number,\n  _callback?: () => void\n): boolean {\n  if (typeof _callback === 'function') {\n    _callback();\n  }\n  return false;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nWriteStream.prototype.clearScreenDown = function (\n  _callback?: () => void\n): boolean {\n  if (typeof _callback === 'function') {\n    _callback();\n  }\n  return false;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\nWriteStream.prototype.getWindowSize = function (): [number, number] {\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n  return [this.columns ?? 0, this.rows ?? 0];\n};\n\nexport default { isatty, ReadStream, WriteStream };\n"
  },
  {
    "path": "src/node/url.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport { default as urlUtil } from 'node-internal:url';\nimport { ERR_MISSING_ARGS } from 'node-internal:internal_errors';\nimport {\n  fileURLToPath,\n  pathToFileURL,\n  toPathIfFileURL,\n  urlToHttpOptions,\n} from 'node-internal:internal_url';\nimport {\n  format,\n  parse,\n  resolve,\n  resolveObject,\n  Url,\n} from 'node-internal:legacy_url';\n\nconst { URL, URLSearchParams } = globalThis;\n\nexport function domainToASCII(domain?: unknown): string {\n  if (arguments.length < 1) {\n    throw new ERR_MISSING_ARGS('domain');\n  }\n\n  return urlUtil.domainToASCII(`${domain}`);\n}\n\nexport function domainToUnicode(domain?: unknown): string {\n  if (arguments.length < 1) {\n    throw new ERR_MISSING_ARGS('domain');\n  }\n\n  return urlUtil.domainToUnicode(`${domain}`);\n}\n\nexport {\n  fileURLToPath,\n  pathToFileURL,\n  toPathIfFileURL,\n  urlToHttpOptions,\n} from 'node-internal:internal_url';\nexport {\n  format,\n  parse,\n  resolve,\n  resolveObject,\n  Url,\n} from 'node-internal:legacy_url';\nexport { URL, URLSearchParams };\n\nexport default {\n  domainToASCII,\n  domainToUnicode,\n  URL,\n  URLSearchParams,\n  fileURLToPath,\n  pathToFileURL,\n  toPathIfFileURL,\n  urlToHttpOptions,\n  // Legacy APIs\n  format,\n  parse,\n  resolve,\n  resolveObject,\n  Url,\n};\n"
  },
  {
    "path": "src/node/util/types.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nexport * from 'node-internal:internal_types';\nimport { default as types } from 'node-internal:internal_types';\nexport {\n  isCryptoKey,\n  isKeyObject,\n  isAsyncFunction,\n  isGeneratorFunction,\n  isGeneratorObject,\n  isAnyArrayBuffer,\n  isArrayBuffer,\n  isArgumentsObject,\n  isBoxedPrimitive,\n  isDataView,\n  isMap,\n  isMapIterator,\n  isModuleNamespaceObject,\n  isNativeError,\n  isPromise,\n  isProxy,\n  isSet,\n  isSetIterator,\n  isSharedArrayBuffer,\n  isWeakMap,\n  isWeakSet,\n  isRegExp,\n  isDate,\n  isStringObject,\n  isSymbolObject,\n  isNumberObject,\n  isBooleanObject,\n  isBigIntObject,\n  isArrayBufferView,\n  isBigInt64Array,\n  isBigUint64Array,\n  isFloat16Array,\n  isFloat32Array,\n  isFloat64Array,\n  isInt8Array,\n  isInt16Array,\n  isInt32Array,\n  isTypedArray,\n  isUint8Array,\n  isUint8ClampedArray,\n  isUint16Array,\n  isUint32Array,\n  isExternal,\n} from 'node-internal:internal_types';\nexport default types;\n"
  },
  {
    "path": "src/node/util.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { default as internalTypes } from 'node-internal:internal_types';\nimport { default as utilImpl } from 'node-internal:util';\nimport { isDeepStrictEqual as _isDeepStrictEqual } from 'node-internal:internal_comparisons';\nimport { Buffer } from 'node-internal:internal_buffer';\n\nimport {\n  validateFunction,\n  validateAbortSignal,\n  validateObject,\n  kValidateObjectAllowObjects,\n  validateString,\n  validateBoolean,\n  validateOneOf,\n} from 'node-internal:validators';\n\nimport { debuglog } from 'node-internal:debuglog';\nexport const debug = debuglog;\nexport { debuglog };\n\nimport {\n  ERR_INVALID_ARG_TYPE,\n  ERR_METHOD_NOT_IMPLEMENTED,\n} from 'node-internal:internal_errors';\n\nimport {\n  inspect,\n  format,\n  formatWithOptions,\n  stripVTControlCharacters,\n} from 'node-internal:internal_inspect';\n\nimport { callbackify, parseEnv } from 'node-internal:internal_utils';\nimport {\n  isReadableStream,\n  isWritableStream,\n  isNodeStream,\n} from 'node-internal:streams_util';\n\nlet isBoolean: ((val: unknown) => boolean) | undefined = undefined;\nlet isBuffer: ((val: unknown) => boolean) | undefined = undefined;\nlet isDate: ((val: unknown) => boolean) | undefined = undefined;\nlet isError: ((val: unknown) => boolean) | undefined = undefined;\nlet isFunction: ((val: unknown) => boolean) | undefined = undefined;\nlet isNull: ((val: unknown) => boolean) | undefined = undefined;\nlet isNullOrUndefined: ((val: unknown) => boolean) | undefined = undefined;\nlet isNumber: ((val: unknown) => boolean) | undefined = undefined;\nlet isObject: ((val: unknown) => boolean) | undefined = undefined;\nlet isPrimitive: ((val: unknown) => boolean) | undefined = undefined;\nlet isRegExp: ((val: unknown) => boolean) | undefined = undefined;\nlet isString: ((val: unknown) => boolean) | undefined = undefined;\nlet isSymbol: ((val: unknown) => boolean) | undefined = undefined;\nlet isUndefined: ((val: unknown) => boolean) | undefined = undefined;\n\nif (!Cloudflare.compatibilityFlags.remove_nodejs_compat_eol_v23) {\n  isBoolean = (val: unknown): boolean => typeof val === 'boolean';\n  isBuffer = (val: unknown): boolean => Buffer.isBuffer(val);\n  isDate = (val: unknown): boolean => val instanceof Date;\n  isError = (val: unknown): boolean => Error.isError(val);\n  isFunction = (val: unknown): boolean => typeof val === 'function';\n  isNull = (val: unknown): boolean => val === null;\n  isNullOrUndefined = (val: unknown): boolean => val == null;\n  isNumber = (val: unknown): boolean => typeof val === 'number';\n  isObject = (val: unknown): boolean => val != null && typeof val === 'object';\n  isPrimitive = (val: unknown): boolean =>\n    val === null || (typeof val !== 'object' && typeof val !== 'function');\n  isRegExp = (val: unknown): boolean => val instanceof RegExp;\n  isString = (val: unknown): boolean => typeof val === 'string';\n  isSymbol = (val: unknown): boolean => typeof val === 'symbol';\n  isUndefined = (val: unknown): boolean => val === undefined;\n}\n\nexport {\n  inspect,\n  format,\n  formatWithOptions,\n  stripVTControlCharacters,\n\n  // EOL methods\n  isBoolean,\n  isBuffer,\n  isDate,\n  isError,\n  isFunction,\n  isNull,\n  isNullOrUndefined,\n  isNumber,\n  isObject,\n  isPrimitive,\n  isRegExp,\n  isString,\n  isSymbol,\n  isUndefined,\n};\nexport function isArray(val: unknown): boolean {\n  return Array.isArray(val);\n}\nexport { callbackify, parseEnv } from 'node-internal:internal_utils';\nexport const types = internalTypes;\n\nexport const { MIMEParams, MIMEType } = utilImpl;\n\nconst kCustomPromisifiedSymbol = Symbol.for('nodejs.util.promisify.custom');\nconst kCustomPromisifyArgsSymbol = Symbol.for(\n  'nodejs.util.promisify.custom.args'\n);\n\n// TODO(later): Proper type signature for promisify.\n/* eslint-disable */\nexport function promisify(original: Function): Function {\n  validateFunction(original, 'original');\n\n  if ((original as any)[kCustomPromisifiedSymbol]) {\n    const fn = (original as any)[kCustomPromisifiedSymbol];\n\n    validateFunction(fn, 'util.promisify.custom');\n\n    return Object.defineProperty(fn, kCustomPromisifiedSymbol, {\n      value: fn,\n      enumerable: false,\n      writable: false,\n      configurable: true,\n    });\n  }\n\n  // Names to create an object from in case the callback receives multiple\n  // arguments, e.g. ['bytesRead', 'buffer'] for fs.read.\n  const argumentNames = (original as any)[kCustomPromisifyArgsSymbol];\n\n  function fn(this: any, ...args: any[]) {\n    return new Promise((resolve, reject) => {\n      args.push((err: unknown, ...values: unknown[]) => {\n        if (err) {\n          return reject(err);\n        }\n        if (argumentNames !== undefined && values.length > 1) {\n          const obj = {};\n          for (let i = 0; i < argumentNames.length; i++)\n            (obj as any)[argumentNames[i]] = values[i];\n          resolve(obj);\n        } else {\n          resolve(values[0]);\n        }\n      });\n      Reflect.apply(original, this, args);\n    });\n  }\n\n  Object.setPrototypeOf(fn, Object.getPrototypeOf(original));\n\n  Object.defineProperty(fn, kCustomPromisifiedSymbol, {\n    value: fn,\n    enumerable: false,\n    writable: false,\n    configurable: true,\n  });\n\n  const descriptors = Object.getOwnPropertyDescriptors(original);\n  const propertiesValues = Object.values(descriptors);\n  for (let i = 0; i < propertiesValues.length; i++) {\n    // We want to use null-prototype objects to not rely on globally mutable\n    // %Object.prototype%.\n    Object.setPrototypeOf(propertiesValues[i], null);\n  }\n  return Object.defineProperties(fn, descriptors);\n}\n\npromisify.custom = kCustomPromisifiedSymbol;\n/* eslint-enable */\n\nexport function inherits(\n  ctor: Function | null | undefined, // eslint-disable-line @typescript-eslint/no-unsafe-function-type\n  superCtor: Function | null | undefined // eslint-disable-line @typescript-eslint/no-unsafe-function-type\n): void {\n  if (ctor == null) throw new ERR_INVALID_ARG_TYPE('ctor', 'Function', ctor);\n\n  if (superCtor === undefined || superCtor === null)\n    throw new ERR_INVALID_ARG_TYPE('superCtor', 'Function', superCtor);\n\n  if (superCtor.prototype === undefined) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'superCtor.prototype',\n      'Object',\n      superCtor.prototype\n    );\n  }\n  Object.defineProperty(ctor, 'super_', {\n    value: superCtor,\n    writable: true,\n    configurable: true,\n  });\n  Object.setPrototypeOf(ctor.prototype, superCtor.prototype); // eslint-disable-line @typescript-eslint/no-unsafe-argument\n}\n\nexport function _extend<T extends Record<string, unknown>>(\n  target: T,\n  source: unknown\n): T {\n  // Don't do anything if source isn't an object\n  if (source === null || typeof source !== 'object') return target;\n\n  const keys = Object.keys(source);\n  let i = keys.length;\n  while (i--) {\n    (target as Record<string, unknown>)[keys[i] as keyof typeof source] = (\n      source as Record<string, unknown>\n    )[keys[i] as keyof typeof source];\n  }\n  return target;\n}\n\nexport const TextDecoder = globalThis.TextDecoder;\nexport const TextEncoder = globalThis.TextEncoder;\n\nexport function toUSVString(input: string): string {\n  return input.toWellFormed();\n}\n\nfunction pad(n: unknown): string {\n  return `${n}`.padStart(2, '0');\n}\n\n// prettier-ignore\nconst months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',\n                'Oct', 'Nov', 'Dec'];\n\nfunction timestamp(): string {\n  const d = new Date();\n  const t = [pad(d.getHours()), pad(d.getMinutes()), pad(d.getSeconds())].join(\n    ':'\n  );\n  return `${d.getDate()} ${months[d.getMonth()]} ${t}`;\n}\n\nexport function log(...args: unknown[]): void {\n  console.log('%s - %s', timestamp(), format(...args));\n}\n\nexport function parseArgs(): unknown {\n  // We currently have no plans to implement the util.parseArgs API.\n  throw new Error('node:util parseArgs is not implemented');\n}\n\nexport function transferableAbortController(): void {\n  throw new Error('node:util transferableAbortController is not implemented');\n}\n\nexport function transferableAbortSignal(): void {\n  throw new Error('node:util transferableAbortSignal is not implemented');\n}\n\nexport async function aborted(\n  signal: unknown,\n  resource: unknown\n  // eslint-disable-next-line @typescript-eslint/no-invalid-void-type\n): Promise<Event | void> {\n  if (signal === undefined) {\n    throw new ERR_INVALID_ARG_TYPE('signal', 'AbortSignal', signal);\n  }\n  // Node.js defines that the resource is held weakly such that if it is GC'ed, we\n  // will drop the event handler on the signal and the promise will remain pending\n  // forever. We don't want GC to be observable in the same way so we won't support\n  // this additional option. Unfortunately Node.js does not make this argument optional.\n  // We'll just ignore it.\n  validateAbortSignal(signal, 'signal');\n  validateObject(resource, 'resource', kValidateObjectAllowObjects);\n  if (signal.aborted) return Promise.resolve();\n  const { promise, resolve } = Promise.withResolvers<Event>();\n  const opts = { __proto__: null, once: true };\n  signal.addEventListener('abort', resolve, opts);\n  return promise;\n}\n\nexport function deprecate<T = unknown>(\n  fn: T,\n  _1?: string,\n  _2?: string,\n  _3?: boolean\n): T {\n  // TODO(soon): Node.js's implementation wraps the given function in a new function that\n  // logs a warning to the console if the function is called. Do we want to support that?\n  // For now, we're just going to silently return the input method unmodified.\n  return fn;\n}\n\n// Node.js originally introduced the API with the name `getCallSite()` as an experimental\n// API but then renamed it to `getCallSites()` soon after. We had already implemented the\n// API with the original name in a release. To avoid the possibility of breaking, we export\n// the function using both names.\nexport const getCallSite = utilImpl.getCallSites.bind(utilImpl);\nexport const getCallSites = utilImpl.getCallSites.bind(utilImpl);\n\nexport function isDeepStrictEqual(a: unknown, b: unknown): boolean {\n  return _isDeepStrictEqual(a, b);\n}\n\nexport function getSystemErrorMap(): void {\n  throw new Error('node:util getSystemErrorMap is not implemented');\n}\n\nexport function getSystemErrorName(): void {\n  throw new Error('node:util getSystemErrorName is not implemented');\n}\n\nexport function getSystemErrorMessage(): void {\n  throw new Error('node:util getSystemErrorMessage is not implemented');\n}\n\nfunction escapeStyleCode(code: number | undefined): string {\n  if (code === undefined) return '';\n  return `\\u001b[${code}m`;\n}\n\n// TODO(soon): We do not yet implement process.stdout, so to ensure the correct\n// behaviour, we use a placeholder value internally.\n\ninterface StyleTextOptions {\n  validateStream?: boolean;\n  stream?: ReadableStream | WritableStream;\n}\n\ntype TTYStream = (ReadableStream | WritableStream) & {\n  isTTY: true;\n  getColorDepth(): number;\n};\n\nfunction isTTYStream(stream: unknown): stream is TTYStream {\n  return (\n    stream != null &&\n    typeof stream === 'object' &&\n    'isTTY' in stream &&\n    !!stream.isTTY &&\n    typeof (stream as TTYStream).getColorDepth === 'function'\n  );\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\nconst stdoutPlaceholder: StyleTextOptions['stream'] = Object.create(null);\nexport function styleText(\n  format: string | string[],\n  text: unknown,\n  { validateStream = true, stream = stdoutPlaceholder }: StyleTextOptions = {}\n): string {\n  validateString(text, 'text');\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare\n  if (validateStream !== true)\n    validateBoolean(validateStream, 'options.validateStream');\n\n  let skipColorize: boolean = false;\n  if (validateStream) {\n    if (\n      !isReadableStream(stream) &&\n      !isWritableStream(stream) &&\n      !isNodeStream(stream) &&\n      stream !== stdoutPlaceholder\n    ) {\n      throw new ERR_INVALID_ARG_TYPE(\n        'stream',\n        ['ReadableStream', 'WritableStream', 'Stream'],\n        stream\n      );\n    }\n\n    // If the stream is falsy or should not be colorized, set skipColorize to true\n    skipColorize = isTTYStream(stream) ? stream.getColorDepth() > 2 : true;\n  }\n\n  // If the format is not an array, convert it to an array\n  const formatArray = Array.isArray(format) ? format : [format];\n\n  let left = '';\n  let right = '';\n  for (const key of formatArray) {\n    if (key === 'none') continue;\n    const formatCodes =\n      typeof key === 'string' ? inspect.colors[key] : undefined;\n    // If the format is not a valid style, throw an error\n    if (formatCodes == null) {\n      validateOneOf(key, 'format', Object.keys(inspect.colors));\n    }\n    if (skipColorize) continue;\n    left += escapeStyleCode(formatCodes ? formatCodes[0] : undefined);\n    right = `${escapeStyleCode(formatCodes ? formatCodes[1] : undefined)}${right}`;\n  }\n\n  return skipColorize ? text : `${left}${text}${right}`;\n}\n\nexport function _errnoException(): void {\n  // TODO(soon): We might support this in the future.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('_errnoException');\n}\n\nexport function _exceptionWithHostPort(): void {\n  // TODO(soon): We might support this in the future.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('_exceptionWithHostPort');\n}\n\nexport default {\n  types,\n  callbackify,\n  promisify,\n  inspect,\n  format,\n  formatWithOptions,\n  stripVTControlCharacters,\n  inherits,\n  _extend,\n  MIMEParams,\n  MIMEType,\n  toUSVString,\n  log,\n  aborted,\n  debuglog,\n  debug,\n  deprecate,\n  getSystemErrorMap,\n  getSystemErrorMessage,\n  getSystemErrorName,\n  // Node.js originally exposed TextEncoder and TextDecoder off the util\n  // module originally, so let's just go ahead and do the same.\n  TextEncoder,\n  TextDecoder,\n  parseArgs,\n  parseEnv,\n  styleText,\n  transferableAbortController,\n  transferableAbortSignal,\n  getCallSite,\n  getCallSites,\n  isDeepStrictEqual,\n  _errnoException,\n  _exceptionWithHostPort,\n\n  isArray,\n  // EOL methods\n  isBoolean,\n  isBuffer,\n  isDate,\n  isError,\n  isFunction,\n  isNull,\n  isNullOrUndefined,\n  isNumber,\n  isObject,\n  isPrimitive,\n  isRegExp,\n  isString,\n  isSymbol,\n  isUndefined,\n};\n"
  },
  {
    "path": "src/node/v8.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright (c) 2014, StrongLoop Inc.\n//\n// Permission to use, copy, modify, and/or distribute this software for any\n// purpose with or without fee is hereby granted, provided that the above\n// copyright notice and this permission notice appear in all copies.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n'use strict';\n\nimport { Buffer } from 'node-internal:internal_buffer';\nimport {\n  validateObject,\n  validateFunction,\n  validateBoolean,\n  validateString,\n  validateUint32,\n  validateOneOf,\n} from 'node-internal:validators';\n\nimport type {\n  HeapSnapshotOptions,\n  HeapInfo,\n  HeapSpaceStatistics,\n  HeapCodeStatistics,\n  Init,\n  Before,\n  After,\n  Settled,\n  HookCallbacks,\n} from 'node:v8';\nimport type { Readable } from 'node:stream';\n\nimport {\n  ERR_INVALID_ARG_VALUE,\n  ERR_METHOD_NOT_IMPLEMENTED,\n} from 'node-internal:internal_errors';\n\nexport function cachedDataVersionTag(): number {\n  return 0;\n}\n\nfunction getHeapSnapshotOptions(options: HeapSnapshotOptions = {}): void {\n  validateObject(options, 'options');\n  const { exposeInternals = false, exposeNumericValues = false } = options;\n  validateBoolean(exposeInternals, 'options.exposeInternals');\n  validateBoolean(exposeNumericValues, 'options.exposeNumericValues');\n}\n\n// Other than the Serializer/Deserializer classes, most of the API here is not\n// something that we intend to implement.\n\nexport function writeHeapSnapshot(\n  filename?: string,\n  options: HeapSnapshotOptions = {}\n): void {\n  if (filename !== undefined) {\n    validateString(filename, 'filename');\n  }\n  getHeapSnapshotOptions(options);\n  throw new ERR_METHOD_NOT_IMPLEMENTED('writeHeapSnapshot');\n}\n\nexport function getHeapSnapshot(options: HeapSnapshotOptions = {}): Readable {\n  getHeapSnapshotOptions(options);\n  throw new ERR_METHOD_NOT_IMPLEMENTED('getHeapSnapshot');\n}\n\nexport function setFlagsFromString(flags: string): void {\n  validateString(flags, 'flags');\n  throw new ERR_METHOD_NOT_IMPLEMENTED('setFlagsFromString');\n}\n\nexport function isStringOneByteRepresentation(content: string): boolean {\n  // TODO(later): We can implement this later\n  validateString(content, 'content');\n  throw new ERR_METHOD_NOT_IMPLEMENTED('isStringOneByteRepresentation');\n}\n\nexport function getHeapStatistics(): HeapInfo {\n  return {\n    total_heap_size: 0,\n    total_heap_size_executable: 0,\n    total_physical_size: 0,\n    total_available_size: 0,\n    used_heap_size: 0,\n    heap_size_limit: 0,\n    malloced_memory: 0,\n    peak_malloced_memory: 0,\n    does_zap_garbage: 0,\n    number_of_native_contexts: 0,\n    number_of_detached_contexts: 0,\n    total_global_handles_size: 0,\n    used_global_handles_size: 0,\n    external_memory: 0,\n    total_allocated_bytes: 0,\n  };\n}\n\nexport function getHeapSpaceStatistics(): HeapSpaceStatistics[] {\n  return [];\n}\n\nexport function getHeapCodeStatistics(): HeapCodeStatistics {\n  return {\n    code_and_metadata_size: 0,\n    bytecode_and_metadata_size: 0,\n    external_script_source_size: 0,\n    // @ts-expect-error TS2353 cpu_profiler_metadata_size is not in node/types yet\n    cpu_profiler_metadata_size: 0,\n  };\n}\n\nexport function setHeapSnapshotNearHeapLimit(limit: number): void {\n  validateUint32(limit, 'limit', true);\n  throw new ERR_METHOD_NOT_IMPLEMENTED('setHeapSnapshotNearHeapLimit');\n}\n\nexport function getCppHeapStatistics(type = 'detailed'): object {\n  validateOneOf(type, 'type', ['brief', 'detailed']);\n  return {\n    committed_size_bytes: 0,\n    resident_size_bytes: 0,\n    used_size_bytes: 0,\n    space_statistics: [],\n    type_names: [],\n    detail_level: type,\n  };\n}\n\n// TODO(later): Implement Serializer/Deserializer later\nexport class Serializer {\n  constructor() {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Serializer');\n  }\n\n  writeHeader(): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('writeHeader');\n  }\n  writeValue(_value: unknown): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('writeValue');\n  }\n  releaseBuffer(): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('releaseBuffer');\n  }\n  transferArrayBuffer(_id: number, _arrayBuffer: ArrayBuffer): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('transferArrayBuffer');\n  }\n  writeUint32(_value: number): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('writeUint32');\n  }\n  writeUint64(_hi: number, _lo: number): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('writeUint64');\n  }\n  writeDouble(_value: number): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('writeDouble');\n  }\n  writeRawBytes(_buffer: NodeJS.TypedArray | Buffer): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('writeRawBytes');\n  }\n  _getDataCloneError(_message: unknown): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('_getDataCloneError');\n  }\n  _getSharedArrayBufferId(_sharedArrayBuffer: SharedArrayBuffer): number {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('_getSharedArrayBufferId');\n  }\n  _setTreatArrayBufferViewsAsHostObjects(_flag: boolean): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED(\n      '_setTreatArrayBufferViewsAsHostObjects'\n    );\n  }\n\n  _writeHostObject(_abView: NodeJS.TypedArray | Buffer): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('_writeHostObject');\n  }\n}\n\nexport class Deserializer {\n  constructor(_buffer: NodeJS.TypedArray | Buffer) {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Deserializer');\n  }\n  readHeader(): boolean {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('readHeader');\n  }\n\n  readValue(): unknown {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('readValue');\n  }\n\n  transferArrayBuffer(_id: number, _arrayBuffer: ArrayBuffer): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('transferArrayBuffer');\n  }\n\n  getWireFormatVersion(): number {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('getWireFormatVersion');\n  }\n\n  readUint32(): number {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('readUint32');\n  }\n\n  readUint64(): [number, number] {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('readUint64');\n  }\n\n  readDouble(): number {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('readDouble');\n  }\n  readRawBytes(_length: number): Buffer {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('readRawBytes');\n  }\n\n  _readHostObject(): object {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('_readHostObject');\n  }\n}\n\nexport class DefaultSerializer extends Serializer {\n  constructor() {\n    super();\n    throw new ERR_METHOD_NOT_IMPLEMENTED('DefaultSerializer');\n  }\n}\n\nexport class DefaultDeserializer extends Deserializer {\n  constructor(buffer: NodeJS.TypedArray | Buffer) {\n    super(buffer);\n    throw new ERR_METHOD_NOT_IMPLEMENTED('DefaultDeserializer');\n  }\n}\n\nexport function serialize(_value: unknown): Buffer {\n  // TODO(later): This is one that we can implement later.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('serialize');\n}\n\nexport function deserialize(_buffer: NodeJS.TypedArray | Buffer): unknown {\n  // TODO(;later): This is one that we can implement later.\n  throw new ERR_METHOD_NOT_IMPLEMENTED('deserialize');\n}\n\nexport class GCProfiler {\n  start(): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('GCProfiler.start');\n  }\n\n  stop(): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('GCProfiler.stop');\n  }\n}\n\nexport const promiseHooks = {\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n  createHook(_: HookCallbacks): Function {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('promiseHooks.createHook');\n  },\n\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n  onInit(_: Init): Function {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('promiseHooks.onInit');\n  },\n\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n  onBefore(_: Before): Function {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('promiseHooks.onBefore');\n  },\n\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n  onAfter(_: After): Function {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('promiseHooks.onAfter');\n  },\n\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n  onSettled(_: Settled): Function {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('promiseHooks.onSettled');\n  },\n};\n\nexport function queryObjects(\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n  ctor: Function,\n  options: { format?: 'count' | 'summary' } = {}\n): number | string[] {\n  validateFunction(ctor, 'constructor');\n  validateObject(options, 'options');\n  const format = options.format || 'count';\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  if (format !== 'count' && format !== 'summary') {\n    throw new ERR_INVALID_ARG_VALUE('options.format', format);\n  }\n  throw new ERR_METHOD_NOT_IMPLEMENTED('queryObjects');\n}\n\nexport function takeCoverage(): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('takeCoverage');\n}\n\nexport function stopCoverage(): void {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('stopCoverage');\n}\n\nexport const startupSnapshot = {\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n  addSerializeCallback(_callback: Function, _data: unknown): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED(\n      'startupSnapshot.addSerializeCallback'\n    );\n  },\n\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n  addDeserializeCallback(_callback: Function, _data: unknown): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED(\n      'startupSnapshot.addDeserializeCallback'\n    );\n  },\n\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n  setDeserializeMainFunction(_callback: Function, _data: unknown): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED(\n      'startupSnapshot.setDeserializeMainFunction'\n    );\n  },\n  isBuildingSnapshot(): boolean {\n    return false;\n  },\n};\n\nexport default {\n  cachedDataVersionTag,\n  getHeapSnapshot,\n  getHeapStatistics,\n  getHeapSpaceStatistics,\n  getHeapCodeStatistics,\n  getCppHeapStatistics,\n  setFlagsFromString,\n  Serializer,\n  Deserializer,\n  DefaultSerializer,\n  DefaultDeserializer,\n  deserialize,\n  takeCoverage,\n  stopCoverage,\n  serialize,\n  writeHeapSnapshot,\n  promiseHooks,\n  queryObjects,\n  startupSnapshot,\n  setHeapSnapshotNearHeapLimit,\n  GCProfiler,\n  isStringOneByteRepresentation,\n};\n"
  },
  {
    "path": "src/node/vm.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport {\n  ERR_INVALID_ARG_TYPE,\n  ERR_METHOD_NOT_IMPLEMENTED,\n} from 'node-internal:internal_errors';\n\nimport {\n  validateArray,\n  validateBoolean,\n  validateBuffer,\n  validateInt32,\n  validateOneOf,\n  validateObject,\n  validateString,\n  validateStringArray,\n  validateUint32,\n  kValidateObjectAllowArray,\n  kValidateObjectAllowNullable,\n} from 'node-internal:validators';\n\nimport { Buffer } from 'node-internal:internal_buffer';\n\nimport type {\n  CreateContextOptions,\n  RunningScriptOptions,\n  RunningScriptInNewContextOptions,\n  ScriptOptions,\n  CompileFunctionOptions,\n  MeasureMemoryOptions,\n  MemoryMeasurement,\n} from 'node:vm';\n\nexport const constants = {\n  __proto__: null,\n  USE_MAIN_CONTEXT_DEFAULT_LOADER: Symbol(\n    'vm_dynamic_import_main_context_default'\n  ),\n  DONT_CONTEXTIFY: Symbol('vm_context_no_contextify'),\n};\nObject.freeze(constants);\n\nconst kContextSymbol = Symbol('vm_context_symbol');\n\nexport function isContext(object: unknown): boolean {\n  validateObject(object, 'object', kValidateObjectAllowArray);\n  return kContextSymbol in object && object[kContextSymbol] === true;\n}\n\nexport class Script {\n  cachedDataRejected: undefined | boolean = undefined;\n  cachedDataProduced: undefined | boolean = undefined;\n  cachedData: undefined | Buffer = undefined;\n  sourceMapURL: undefined | string = undefined;\n\n  constructor(_code: string, options: ScriptOptions | string = {}) {\n    _code = `${_code}`; // eslint-disable-line @typescript-eslint/no-unnecessary-template-expression\n    if (typeof options === 'string') {\n      options = { filename: options };\n    } else {\n      validateObject(options, 'options');\n    }\n\n    const {\n      filename = 'evalmachine.<anonymous>',\n      lineOffset = 0,\n      columnOffset = 0,\n      cachedData,\n      produceCachedData = false, // eslint-disable-line @typescript-eslint/no-deprecated\n      importModuleDynamically = false,\n    } = options;\n\n    validateBoolean(importModuleDynamically, 'options.importModuleDynamically');\n    validateString(filename, 'options.filename');\n    validateInt32(lineOffset, 'options.lineOffset');\n    validateInt32(columnOffset, 'options.columnOffset');\n    if (cachedData !== undefined) {\n      validateBuffer(cachedData, 'options.cachedData');\n    }\n    validateBoolean(produceCachedData, 'options.produceCachedData');\n  }\n\n  createCachedData(): Buffer {\n    return Buffer.alloc(0);\n  }\n\n  runInThisContext(options: RunningScriptOptions = {}): unknown {\n    validateObject(options, 'options');\n    const { breakOnSigint = false, displayErrors = true, timeout } = options;\n    validateBoolean(breakOnSigint, 'options.breakOnSigint');\n    validateBoolean(displayErrors, 'options.displayErrors');\n    if (timeout !== undefined) {\n      validateUint32(timeout, 'options.timeout', true);\n    }\n    throw new ERR_METHOD_NOT_IMPLEMENTED('runInThisContext');\n  }\n\n  runInContext(\n    contextifiedObject: object,\n    options: RunningScriptOptions = {}\n  ): unknown {\n    validateContext(contextifiedObject);\n    validateObject(options, 'options');\n    const { breakOnSigint = false, displayErrors = true, timeout } = options;\n    validateBoolean(breakOnSigint, 'options.breakOnSigint');\n    validateBoolean(displayErrors, 'options.displayErrors');\n    if (timeout !== undefined) {\n      validateUint32(timeout, 'options.timeout', true);\n    }\n    throw new ERR_METHOD_NOT_IMPLEMENTED('runInThisContext');\n  }\n\n  runInNewContext(\n    contextObject: object | symbol,\n    options: RunningScriptInNewContextOptions = {}\n  ): unknown {\n    const context = createContext(contextObject, getContextOptions(options));\n    return this.runInContext(context, options);\n  }\n}\n\nfunction validateContext(contextifiedObject: object): void {\n  if (!isContext(contextifiedObject)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      'contextifiedObject',\n      'vm.Context',\n      contextifiedObject\n    );\n  }\n}\n\nfunction getContextOptions(\n  options?: RunningScriptInNewContextOptions\n): CreateContextOptions {\n  if (!options) return {};\n  const contextOptions: CreateContextOptions = {\n    name: options.contextName,\n    origin: options.contextOrigin,\n    codeGeneration: undefined,\n    microtaskMode: options.microtaskMode,\n  };\n  if (contextOptions.name !== undefined)\n    validateString(contextOptions.name, 'options.contextName');\n  if (contextOptions.origin !== undefined)\n    validateString(contextOptions.origin, 'options.contextOrigin');\n  if (options.contextCodeGeneration !== undefined) {\n    validateObject(\n      options.contextCodeGeneration,\n      'options.contextCodeGeneration'\n    );\n    const { strings, wasm } = options.contextCodeGeneration;\n    if (strings !== undefined)\n      validateBoolean(strings, 'options.contextCodeGeneration.strings');\n    if (wasm !== undefined)\n      validateBoolean(wasm, 'options.contextCodeGeneration.wasm');\n    contextOptions.codeGeneration = { strings, wasm };\n  }\n  if (options.microtaskMode !== undefined)\n    validateString(options.microtaskMode, 'options.microtaskMode');\n  return contextOptions;\n}\n\nexport function createContext(\n  contextObject = {},\n  options: CreateContextOptions = {}\n): object {\n  if (contextObject !== constants.DONT_CONTEXTIFY && isContext(contextObject)) {\n    return contextObject;\n  }\n\n  validateObject(options, 'options');\n\n  const {\n    name = `VM Context`,\n    origin,\n    codeGeneration,\n    microtaskMode,\n    importModuleDynamically = false,\n  } = options;\n\n  validateString(name, 'options.name');\n  if (origin !== undefined) validateString(origin, 'options.origin');\n  if (codeGeneration !== undefined)\n    validateObject(codeGeneration, 'options.codeGeneration');\n  validateBoolean(importModuleDynamically, 'options.importModuleDynamically');\n\n  let strings = true;\n  let wasm = true;\n  if (codeGeneration !== undefined) {\n    ({ strings = true, wasm = true } = codeGeneration as {\n      strings?: boolean | undefined;\n      wasm?: boolean | undefined;\n    });\n    validateBoolean(strings, 'options.codeGeneration.strings');\n    validateBoolean(wasm, 'options.codeGeneration.wasm');\n  }\n\n  validateOneOf(microtaskMode, 'options.microtaskMode', [\n    'afterEvaluate',\n    undefined,\n  ]);\n\n  return Object.create(null, {\n    [kContextSymbol]: {\n      value: true,\n    },\n  }) as object;\n}\n\nexport function createScript(\n  code: string,\n  options: ScriptOptions = {}\n): Script {\n  return new Script(code, options);\n}\n\nexport function runInContext(\n  code: string,\n  contextifiedObject: object,\n  options: RunningScriptOptions = {}\n): unknown {\n  validateContext(contextifiedObject);\n  if (typeof options === 'string') {\n    options = {\n      filename: options,\n    };\n  }\n  return createScript(code, options).runInContext(contextifiedObject, options);\n}\n\nexport function runInNewContext(\n  code: string,\n  contextObject: object | RunningScriptInNewContextOptions | symbol,\n  options?: RunningScriptInNewContextOptions | string\n): unknown {\n  if (typeof options === 'string') {\n    options = { filename: options };\n  } else {\n    options = { ...options };\n  }\n  contextObject = createContext(contextObject, getContextOptions(options));\n  return createScript(code, options).runInNewContext(contextObject, options);\n}\n\nexport function runInThisContext(\n  code: string,\n  options: RunningScriptOptions | string = {}\n): unknown {\n  if (typeof options === 'string') {\n    options = { filename: options };\n  }\n  return createScript(code, options).runInThisContext(options);\n}\n\nexport function compileFunction(\n  code: string,\n  params: readonly string[] = [],\n  options: CompileFunctionOptions = {}\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n): Function & {\n  cachedData?: Script['cachedData'] | undefined;\n  cachedDataProduced?: Script['cachedDataProduced'] | undefined;\n  cachedDataRejected?: Script['cachedDataRejected'] | undefined;\n} {\n  validateString(code, 'code');\n  validateObject(options, 'options');\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  if (params !== undefined) {\n    validateStringArray(params, 'params');\n  }\n  const {\n    filename = '',\n    columnOffset = 0,\n    lineOffset = 0,\n    cachedData,\n    produceCachedData = false, // eslint-disable-line @typescript-eslint/no-deprecated\n    parsingContext,\n    contextExtensions = [],\n    importModuleDynamically = false,\n  } = options;\n\n  validateString(filename, 'options.filename');\n  validateInt32(columnOffset, 'options.columnOffset');\n  validateInt32(lineOffset, 'options.lineOffset');\n  if (cachedData !== undefined)\n    validateBuffer(cachedData, 'options.cachedData');\n  validateBoolean(produceCachedData, 'options.produceCachedData');\n  validateBoolean(importModuleDynamically, 'options.importModuleDynamically');\n  if (parsingContext !== undefined) {\n    if (\n      typeof parsingContext !== 'object' ||\n      parsingContext === null || // eslint-disable-line @typescript-eslint/no-unnecessary-condition\n      !isContext(parsingContext)\n    ) {\n      throw new ERR_INVALID_ARG_TYPE(\n        'options.parsingContext',\n        'Context',\n        parsingContext\n      );\n    }\n  }\n  validateArray(contextExtensions, 'options.contextExtensions');\n  contextExtensions.forEach((extension, i) => {\n    const name = `options.contextExtensions[${i}]`;\n    validateObject(extension, name, kValidateObjectAllowNullable);\n  });\n\n  throw new ERR_METHOD_NOT_IMPLEMENTED('compileFunction');\n}\n\nexport function measureMemory(\n  options: MeasureMemoryOptions = {}\n): Promise<MemoryMeasurement> {\n  validateObject(options, 'options');\n  const { mode = 'summary', execution = 'default' } = options;\n  validateOneOf(mode, 'options.mode', ['summary', 'detailed']);\n  validateOneOf(execution, 'options.execution', ['default', 'eager']);\n  throw new ERR_METHOD_NOT_IMPLEMENTED('measureMemory');\n}\n\nexport default {\n  Script,\n  createContext,\n  createScript,\n  runInContext,\n  runInNewContext,\n  runInThisContext,\n  isContext,\n  compileFunction,\n  measureMemory,\n  constants,\n  // TODO(later): They are still experimental, but eventually we should have non-op\n  // exports for vm.Module, vm.SourceTextModule, and vm.SyntheticModule\n};\n"
  },
  {
    "path": "src/node/wasi.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport {\n  validateArray,\n  validateBoolean,\n  validateObject,\n  validateOneOf,\n  validateInt32,\n} from 'node-internal:validators';\n\nimport { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors';\n\nimport type { WASIOptions } from 'node:wasi';\n\ninterface FinalizeBindingsOptions {\n  memory?: WebAssembly.Memory | undefined;\n}\n\n// TODO(later): This is one we actually might want to implement at some point.\n\nexport class WASI {\n  constructor(options: WASIOptions = { version: 'unstable' }) {\n    validateObject(options, 'options');\n    validateOneOf(options.version, 'options.version', ['unstable', 'preview1']);\n    if (options.args !== undefined) {\n      validateArray(options.args, 'options.args');\n    }\n    if (options.env !== undefined) {\n      validateObject(options.env, 'options.env');\n    }\n    if (options.preopens !== undefined) {\n      validateObject(options.preopens, 'options.preopens');\n    }\n    const { stdin = 0, stdout = 1, stderr = 2 } = options;\n    validateInt32(stdin, 'options.stdin', 0);\n    validateInt32(stdout, 'options.stdout', 0);\n    validateInt32(stderr, 'options.stderr', 0);\n\n    if (options.returnOnExit !== undefined) {\n      validateBoolean(options.returnOnExit, 'options.returnOnExit');\n    }\n\n    throw new ERR_METHOD_NOT_IMPLEMENTED('WASI');\n  }\n\n  finalizeBindings(_1: object, _2: FinalizeBindingsOptions = {}): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('WASI.finalizeBindings');\n  }\n\n  start(_: object): number {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('WASI.start');\n  }\n\n  initialize(_: object): void {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('WASI.initialize');\n  }\n\n  getImportObject(): object {\n    return {};\n  }\n}\n\nexport default { WASI };\n"
  },
  {
    "path": "src/node/worker_threads.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors';\nimport { EventEmitter } from 'node-internal:events';\nimport type {\n  ResourceLimits,\n  Serializable,\n  Worker as _Worker,\n} from 'node:worker_threads';\nimport type { Context } from 'node:vm';\nimport type { Readable, Writable } from 'node:stream';\nimport type { Transferable, WorkerPerformance } from 'node:worker_threads';\nimport type { CPUProfileHandle, HeapInfo, HeapProfileHandle } from 'node:v8';\n\n// Import MessageChannel and MessagePort from the internal module to avoid\n// dependency on the expose_global_message_channel compatibility flag.\nimport internalMessageChannel from 'cloudflare-internal:messagechannel';\n\nexport const MessageChannel = internalMessageChannel.MessageChannel;\nexport const MessagePort = internalMessageChannel.MessagePort;\n\n// TODO(soon): Use globalThis.BroadcastChannel once it's available.\nexport class BroadcastChannel {\n  constructor() {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('BroadcastChannel');\n  }\n}\n\nexport class Worker extends EventEmitter implements _Worker {\n  stdin: Writable | null = null;\n  stderr: Readable;\n  stdout: Readable;\n  threadId: number;\n  threadName: string = 'workerd';\n  performance: WorkerPerformance;\n\n  constructor() {\n    super();\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Worker');\n  }\n\n  cpuUsage(_prev?: NodeJS.CpuUsage): Promise<NodeJS.CpuUsage> {\n    return Promise.reject(new ERR_METHOD_NOT_IMPLEMENTED('Worker.cpuUsage'));\n  }\n\n  postMessage(_value: unknown, _transferList?: readonly Transferable[]): void {\n    // Acts as a no-op\n  }\n\n  async postMessageToThread(\n    _threadId: unknown,\n    _value: unknown,\n    _transferList?: unknown,\n    _timeout?: unknown\n  ): Promise<void> {\n    // Acts as a noop.\n  }\n\n  ref(): void {\n    // Acts as a noop.\n  }\n\n  unref(): void {\n    // Acts as a noop.\n  }\n\n  // eslint-disable-next-line @typescript-eslint/require-await\n  async terminate(): Promise<number> {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Worker.terminate');\n  }\n\n  // eslint-disable-next-line @typescript-eslint/require-await\n  async getHeapSnapshot(): Promise<Readable> {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Worker.getHeapSnapshot');\n  }\n\n  // eslint-disable-next-line @typescript-eslint/require-await\n  async getHeapStatistics(): Promise<HeapInfo> {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Worker.getHeapStatistics');\n  }\n\n  // eslint-disable-next-line @typescript-eslint/require-await\n  async startCpuProfile(): Promise<CPUProfileHandle> {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Worker.startCpuProfile');\n  }\n\n  // eslint-disable-next-line @typescript-eslint/require-await\n  async startHeapProfile(): Promise<HeapProfileHandle> {\n    throw new ERR_METHOD_NOT_IMPLEMENTED('Worker.startHeapProfile');\n  }\n\n  async [Symbol.asyncDispose](): Promise<void> {\n    // Do nothing\n  }\n}\n\nconst environmentData = new Map<string, Serializable>();\nexport function getEnvironmentData(key: string): Serializable | undefined {\n  return environmentData.get(key);\n}\n\nexport function setEnvironmentData(key: string, value: Serializable): void {\n  environmentData.set(key, value);\n}\n\nexport const isMainThread = true;\n\nexport function isMarkedAsUntransferable(_value: unknown): boolean {\n  return false;\n}\n\nexport function markAsUntransferable(_value: unknown): void {\n  // This is implement as a no-op.\n}\n\nexport function markAsUncloneable(_value: unknown): void {\n  // This is implement as a no-op.\n}\n\nexport function moveMessagePortToContext(\n  _port: MessagePort,\n  _contextifiedSandbox: Context\n): MessagePort {\n  return new MessagePort();\n}\n\nexport const parentPort: number | null = null;\n\nexport function receiveMessageOnPort(\n  _port: MessagePort\n): undefined | { message: unknown } {\n  return undefined;\n}\n\nexport const SHARE_ENV = Symbol.for('nodejs.worker_threads.SHARE_ENV');\n\nexport const resourceLimits: ResourceLimits = {};\n\nexport const threadId: number = 0;\n\nexport const workerData: Record<string, unknown> | null = null;\n\nexport function postMessageToThread(\n  threadId: number,\n  value: unknown,\n  timeout?: number\n): Promise<void>;\nexport function postMessageToThread(\n  _threadId: number,\n  _value: unknown,\n  _transferList?: number | readonly Transferable[],\n  _timeout?: number\n): Promise<void> {\n  throw new ERR_METHOD_NOT_IMPLEMENTED('postMessageToThread');\n}\n\nexport const isInternalThread = false;\n\nexport default {\n  BroadcastChannel,\n  MessageChannel,\n  MessagePort,\n  Worker,\n  SHARE_ENV,\n  getEnvironmentData,\n  isMainThread,\n  isMarkedAsUntransferable,\n  markAsUntransferable,\n  markAsUncloneable,\n  moveMessagePortToContext,\n  parentPort,\n  receiveMessageOnPort,\n  resourceLimits,\n  setEnvironmentData,\n  postMessageToThread,\n  threadId,\n  workerData,\n  isInternalThread,\n};\n"
  },
  {
    "path": "src/node/zlib.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\nimport * as zlib from 'node-internal:internal_zlib';\nimport { crc32 } from 'node-internal:internal_zlib';\nimport { constants, codes } from 'node-internal:internal_zlib_constants';\n\nconst nodeJsZlib = !!Cloudflare.compatibilityFlags['nodejs_zlib'];\nconst {\n  Z_NO_FLUSH,\n  Z_PARTIAL_FLUSH,\n  Z_SYNC_FLUSH,\n  Z_FULL_FLUSH,\n  Z_FINISH,\n  Z_BLOCK,\n\n  Z_OK,\n  Z_STREAM_END,\n  Z_NEED_DICT,\n  Z_ERRNO,\n  Z_STREAM_ERROR,\n  Z_DATA_ERROR,\n  Z_MEM_ERROR,\n  Z_BUF_ERROR,\n  Z_VERSION_ERROR,\n\n  Z_NO_COMPRESSION,\n  Z_BEST_SPEED,\n  Z_BEST_COMPRESSION,\n  Z_DEFAULT_COMPRESSION,\n  Z_FILTERED,\n  Z_HUFFMAN_ONLY,\n  Z_RLE,\n  Z_FIXED,\n  Z_DEFAULT_STRATEGY,\n  ZLIB_VERNUM,\n\n  DEFLATE,\n  INFLATE,\n  GZIP,\n  GUNZIP,\n  DEFLATERAW,\n  INFLATERAW,\n  UNZIP,\n  BROTLI_DECODE,\n  BROTLI_ENCODE,\n  ZSTD_ENCODE,\n  ZSTD_DECODE,\n\n  Z_MIN_WINDOWBITS,\n  Z_MAX_WINDOWBITS,\n  Z_DEFAULT_WINDOWBITS,\n  Z_MIN_CHUNK,\n  Z_MAX_CHUNK,\n  Z_DEFAULT_CHUNK,\n  Z_MIN_MEMLEVEL,\n  Z_MAX_MEMLEVEL,\n  Z_DEFAULT_MEMLEVEL,\n  Z_MIN_LEVEL,\n  Z_MAX_LEVEL,\n  Z_DEFAULT_LEVEL,\n\n  BROTLI_OPERATION_PROCESS,\n  BROTLI_OPERATION_FLUSH,\n  BROTLI_OPERATION_FINISH,\n  BROTLI_OPERATION_EMIT_METADATA,\n  BROTLI_PARAM_MODE,\n  BROTLI_MODE_GENERIC,\n  BROTLI_MODE_TEXT,\n  BROTLI_MODE_FONT,\n  BROTLI_DEFAULT_MODE,\n  BROTLI_PARAM_QUALITY,\n  BROTLI_MIN_QUALITY,\n  BROTLI_MAX_QUALITY,\n  BROTLI_DEFAULT_QUALITY,\n  BROTLI_PARAM_LGWIN,\n  BROTLI_MIN_WINDOW_BITS,\n  BROTLI_MAX_WINDOW_BITS,\n  BROTLI_LARGE_MAX_WINDOW_BITS,\n  BROTLI_DEFAULT_WINDOW,\n  BROTLI_PARAM_LGBLOCK,\n  BROTLI_MIN_INPUT_BLOCK_BITS,\n  BROTLI_MAX_INPUT_BLOCK_BITS,\n  BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING,\n  BROTLI_PARAM_LARGE_WINDOW,\n  BROTLI_PARAM_NPOSTFIX,\n  BROTLI_PARAM_NDIRECT,\n  BROTLI_DECODER_RESULT_ERROR,\n  BROTLI_DECODER_RESULT_SUCCESS,\n  BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT,\n  BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT,\n  BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION,\n  BROTLI_DECODER_PARAM_LARGE_WINDOW,\n  BROTLI_DECODER_NO_ERROR,\n  BROTLI_DECODER_SUCCESS,\n  BROTLI_DECODER_NEEDS_MORE_INPUT,\n  BROTLI_DECODER_NEEDS_MORE_OUTPUT,\n  BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE,\n  BROTLI_DECODER_ERROR_FORMAT_RESERVED,\n  BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE,\n  BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET,\n  BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME,\n  BROTLI_DECODER_ERROR_FORMAT_CL_SPACE,\n  BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE,\n  BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT,\n  BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1,\n  BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2,\n  BROTLI_DECODER_ERROR_FORMAT_TRANSFORM,\n  BROTLI_DECODER_ERROR_FORMAT_DICTIONARY,\n  BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS,\n  BROTLI_DECODER_ERROR_FORMAT_PADDING_1,\n  BROTLI_DECODER_ERROR_FORMAT_PADDING_2,\n  BROTLI_DECODER_ERROR_FORMAT_DISTANCE,\n  BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET,\n  BROTLI_DECODER_ERROR_INVALID_ARGUMENTS,\n  BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES,\n  BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS,\n  BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP,\n  BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1,\n  BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2,\n  BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES,\n  BROTLI_DECODER_ERROR_UNREACHABLE,\n} = constants;\n\nfunction protectMethod(method: unknown): unknown {\n  if (!nodeJsZlib) {\n    return function notImplemented() {\n      throw new Error('Compatibility flag \"nodejs_zlib\" is not enabled');\n    };\n  }\n\n  return method;\n}\n\nconst Gzip = protectMethod(zlib.Gzip);\nconst Gunzip = protectMethod(zlib.Gunzip);\nconst Deflate = protectMethod(zlib.Deflate);\nconst DeflateRaw = protectMethod(zlib.DeflateRaw);\nconst Inflate = protectMethod(zlib.Inflate);\nconst InflateRaw = protectMethod(zlib.InflateRaw);\nconst Unzip = protectMethod(zlib.Unzip);\nconst BrotliCompress = protectMethod(zlib.BrotliCompress);\nconst BrotliDecompress = protectMethod(zlib.BrotliDecompress);\nconst ZstdCompress = protectMethod(zlib.ZstdCompress);\nconst ZstdDecompress = protectMethod(zlib.ZstdDecompress);\n\nconst createGzip = protectMethod(zlib.createGzip);\nconst createGunzip = protectMethod(zlib.createGunzip);\nconst createDeflate = protectMethod(zlib.createDeflate);\nconst createDeflateRaw = protectMethod(zlib.createDeflateRaw);\nconst createInflate = protectMethod(zlib.createInflate);\nconst createInflateRaw = protectMethod(zlib.createInflateRaw);\nconst createUnzip = protectMethod(zlib.createUnzip);\nconst createBrotliCompress = protectMethod(zlib.createBrotliCompress);\nconst createBrotliDecompress = protectMethod(zlib.createBrotliDecompress);\nconst createZstdCompress = protectMethod(zlib.createZstdCompress);\nconst createZstdDecompress = protectMethod(zlib.createZstdDecompress);\n\nconst inflate = protectMethod(zlib.inflate);\nconst inflateSync = protectMethod(zlib.inflateSync);\nconst deflate = protectMethod(zlib.deflate);\nconst deflateSync = protectMethod(zlib.deflateSync);\nconst inflateRaw = protectMethod(zlib.inflateRaw);\nconst inflateRawSync = protectMethod(zlib.inflateRawSync);\nconst deflateRaw = protectMethod(zlib.deflateRaw);\nconst deflateRawSync = protectMethod(zlib.deflateRawSync);\nconst gzip = protectMethod(zlib.gzip);\nconst gzipSync = protectMethod(zlib.gzipSync);\nconst gunzip = protectMethod(zlib.gunzip);\nconst gunzipSync = protectMethod(zlib.gunzipSync);\nconst unzip = protectMethod(zlib.unzip);\nconst unzipSync = protectMethod(zlib.unzipSync);\nconst brotliCompress = protectMethod(zlib.brotliCompress);\nconst brotliCompressSync = protectMethod(zlib.brotliCompressSync);\nconst brotliDecompress = protectMethod(zlib.brotliDecompress);\nconst brotliDecompressSync = protectMethod(zlib.brotliDecompressSync);\nconst zstdCompress = protectMethod(zlib.zstdCompress);\nconst zstdCompressSync = protectMethod(zlib.zstdCompressSync);\nconst zstdDecompress = protectMethod(zlib.zstdDecompress);\nconst zstdDecompressSync = protectMethod(zlib.zstdDecompressSync);\n\nexport {\n  crc32,\n  codes,\n  constants,\n\n  // Classes\n  Gzip,\n  Gunzip,\n  Deflate,\n  DeflateRaw,\n  Inflate,\n  InflateRaw,\n  Unzip,\n  BrotliCompress,\n  BrotliDecompress,\n  ZstdCompress,\n  ZstdDecompress,\n\n  // Convenience methods to create classes\n  createGzip,\n  createGunzip,\n  createDeflate,\n  createDeflateRaw,\n  createInflate,\n  createInflateRaw,\n  createUnzip,\n  createBrotliCompress,\n  createBrotliDecompress,\n  createZstdCompress,\n  createZstdDecompress,\n\n  // One-shot methods\n  inflate,\n  inflateSync,\n  deflate,\n  deflateSync,\n  inflateRaw,\n  inflateRawSync,\n  deflateRaw,\n  deflateRawSync,\n  gzip,\n  gzipSync,\n  gunzip,\n  gunzipSync,\n  unzip,\n  unzipSync,\n  brotliDecompress,\n  brotliDecompressSync,\n  brotliCompress,\n  brotliCompressSync,\n  zstdCompress,\n  zstdCompressSync,\n  zstdDecompress,\n  zstdDecompressSync,\n\n  // NodeJS also exports all constants directly under zlib, but this is deprecated\n  Z_NO_FLUSH,\n  Z_PARTIAL_FLUSH,\n  Z_SYNC_FLUSH,\n  Z_FULL_FLUSH,\n  Z_FINISH,\n  Z_BLOCK,\n  Z_OK,\n  Z_STREAM_END,\n  Z_NEED_DICT,\n  Z_ERRNO,\n  Z_STREAM_ERROR,\n  Z_DATA_ERROR,\n  Z_MEM_ERROR,\n  Z_BUF_ERROR,\n  Z_VERSION_ERROR,\n  Z_NO_COMPRESSION,\n  Z_BEST_SPEED,\n  Z_BEST_COMPRESSION,\n  Z_DEFAULT_COMPRESSION,\n  Z_FILTERED,\n  Z_HUFFMAN_ONLY,\n  Z_RLE,\n  Z_FIXED,\n  Z_DEFAULT_STRATEGY,\n  ZLIB_VERNUM,\n  DEFLATE,\n  INFLATE,\n  GZIP,\n  GUNZIP,\n  DEFLATERAW,\n  INFLATERAW,\n  UNZIP,\n  BROTLI_DECODE,\n  BROTLI_ENCODE,\n  ZSTD_ENCODE,\n  ZSTD_DECODE,\n  Z_MIN_WINDOWBITS,\n  Z_MAX_WINDOWBITS,\n  Z_DEFAULT_WINDOWBITS,\n  Z_MIN_CHUNK,\n  Z_MAX_CHUNK,\n  Z_DEFAULT_CHUNK,\n  Z_MIN_MEMLEVEL,\n  Z_MAX_MEMLEVEL,\n  Z_DEFAULT_MEMLEVEL,\n  Z_MIN_LEVEL,\n  Z_MAX_LEVEL,\n  Z_DEFAULT_LEVEL,\n  BROTLI_OPERATION_PROCESS,\n  BROTLI_OPERATION_FLUSH,\n  BROTLI_OPERATION_FINISH,\n  BROTLI_OPERATION_EMIT_METADATA,\n  BROTLI_PARAM_MODE,\n  BROTLI_MODE_GENERIC,\n  BROTLI_MODE_TEXT,\n  BROTLI_MODE_FONT,\n  BROTLI_DEFAULT_MODE,\n  BROTLI_PARAM_QUALITY,\n  BROTLI_MIN_QUALITY,\n  BROTLI_MAX_QUALITY,\n  BROTLI_DEFAULT_QUALITY,\n  BROTLI_PARAM_LGWIN,\n  BROTLI_MIN_WINDOW_BITS,\n  BROTLI_MAX_WINDOW_BITS,\n  BROTLI_LARGE_MAX_WINDOW_BITS,\n  BROTLI_DEFAULT_WINDOW,\n  BROTLI_PARAM_LGBLOCK,\n  BROTLI_MIN_INPUT_BLOCK_BITS,\n  BROTLI_MAX_INPUT_BLOCK_BITS,\n  BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING,\n  BROTLI_PARAM_LARGE_WINDOW,\n  BROTLI_PARAM_NPOSTFIX,\n  BROTLI_PARAM_NDIRECT,\n  BROTLI_DECODER_RESULT_ERROR,\n  BROTLI_DECODER_RESULT_SUCCESS,\n  BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT,\n  BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT,\n  BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION,\n  BROTLI_DECODER_PARAM_LARGE_WINDOW,\n  BROTLI_DECODER_NO_ERROR,\n  BROTLI_DECODER_SUCCESS,\n  BROTLI_DECODER_NEEDS_MORE_INPUT,\n  BROTLI_DECODER_NEEDS_MORE_OUTPUT,\n  BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE,\n  BROTLI_DECODER_ERROR_FORMAT_RESERVED,\n  BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE,\n  BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET,\n  BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME,\n  BROTLI_DECODER_ERROR_FORMAT_CL_SPACE,\n  BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE,\n  BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT,\n  BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1,\n  BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2,\n  BROTLI_DECODER_ERROR_FORMAT_TRANSFORM,\n  BROTLI_DECODER_ERROR_FORMAT_DICTIONARY,\n  BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS,\n  BROTLI_DECODER_ERROR_FORMAT_PADDING_1,\n  BROTLI_DECODER_ERROR_FORMAT_PADDING_2,\n  BROTLI_DECODER_ERROR_FORMAT_DISTANCE,\n  BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET,\n  BROTLI_DECODER_ERROR_INVALID_ARGUMENTS,\n  BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES,\n  BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS,\n  BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP,\n  BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1,\n  BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2,\n  BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES,\n  BROTLI_DECODER_ERROR_UNREACHABLE,\n};\n\nexport default {\n  crc32,\n  codes,\n  constants,\n  // NodeJS also exports all constants directly under zlib, but this is deprecated\n  ...constants,\n\n  // Classes\n  Gzip,\n  Gunzip,\n  Deflate,\n  DeflateRaw,\n  Inflate,\n  InflateRaw,\n  Unzip,\n  BrotliCompress,\n  BrotliDecompress,\n  ZstdCompress,\n  ZstdDecompress,\n\n  // Convenience methods to create classes\n  createGzip,\n  createGunzip,\n  createDeflate,\n  createDeflateRaw,\n  createInflate,\n  createInflateRaw,\n  createUnzip,\n  createBrotliCompress,\n  createBrotliDecompress,\n  createZstdCompress,\n  createZstdDecompress,\n\n  // One-shot methods\n  inflate,\n  inflateSync,\n  deflate,\n  deflateSync,\n  inflateRaw,\n  inflateRawSync,\n  deflateRaw,\n  deflateRawSync,\n  gzip,\n  gzipSync,\n  gunzip,\n  gunzipSync,\n  unzip,\n  unzipSync,\n  brotliDecompress,\n  brotliDecompressSync,\n  brotliCompress,\n  brotliCompressSync,\n  zstdCompress,\n  zstdCompressSync,\n  zstdDecompress,\n  zstdDecompressSync,\n};\n"
  },
  {
    "path": "src/pyodide/AGENTS.md",
    "content": "# src/pyodide/\n\n## OVERVIEW\n\nPython Workers runtime layer. Replaces Pyodide's loader with a minimal substitute adding memory snapshot support. TS+Python; modules registered as `pyodide-internal:*` BUILTIN modules. `pool/emscriptenSetup.ts` runs in vanilla V8 isolate -- CANNOT import C++ extension modules.\n\n## KEY COMPONENTS\n\n| File/Dir                                      | Role                                                                                                  |\n| --------------------------------------------- | ----------------------------------------------------------------------------------------------------- |\n| `python-entrypoint-helper.ts`                 | BUILTIN module backing the USER `python-entrypoint.js`; request dispatch, handler wiring              |\n| `internal/python.ts`                          | Core bridge: Emscripten init, Pyodide bootstrap, snapshot orchestration                               |\n| `internal/snapshot.ts`                        | Memory snapshot collect/restore; baseline vs dedicated snapshot types                                 |\n| `internal/setupPackages.ts`, `loadPackage.ts` | Package mounting, sys.path, vendor dir setup                                                          |\n| `internal/tar.ts`, `tarfs.ts`                 | Tar archive parsing + read-only filesystem for bundles                                                |\n| `internal/topLevelEntropy/`                   | TS+Python: patches `getRandomValues` with deterministic entropy during import, reseeds before request |\n| `internal/pool/`                              | Emscripten setup in plain V8 isolate; `emscriptenSetup.ts` has NO access to C++ extensions            |\n| `internal/workers-api/`                       | Python SDK package (frozen)                                            |\n| `internal/metadata.ts`                        | Config flags: `IS_WORKERD`, `LOCKFILE`, `MAIN_MODULE_NAME`, etc.                                      |\n| `pyodide_extra.capnp`                         | Cap'n Proto schema for Pyodide bundle metadata                                                        |\n\n## Python SDK\n\nPython SDK (`internal/workers-api/`) now lives in [cloudflare/workers-py](https://github.com/cloudflare/workers-py) and is installed from PyPI. Keep existing code for backward compatibility; new features go to workers-py.\n\n\n## TESTING\n\nTests live in `src/workerd/server/tests/python/`. `py_wd_test.bzl` macro: expands `%PYTHON_FEATURE_FLAGS` template, handles multiple Pyodide versions, snapshot generation/loading, per-version compat flag isolation. Tests are `size=\"enormous\"` by default. Each test generates variants per supported Pyodide version (`0.26.0a2`, newer).\n"
  },
  {
    "path": "src/pyodide/BUILD.bazel",
    "content": "load(\"@bazel_skylib//rules:write_file.bzl\", \"write_file\")\nload(\"@rules_python//python:defs.bzl\", \"py_test\")\nload(\"//:build/python_metadata.bzl\", \"BUNDLE_VERSION_INFO\")\nload(\"//:build/wd_cc_embed.bzl\", \"wd_cc_embed\")\nload(\":helpers.bzl\", \"pyodide_extra\", \"pyodide_static\", \"python_bundles\")\n\npyodide_extra()\n\npython_bundles()\n\npyodide_static()\n\nDEV_VERSION = BUNDLE_VERSION_INFO[\"development\"][\"real_pyodide_version\"]\n\nalias(\n    name = \"pyodide.capnp.bin\",\n    actual = \"pyodide.capnp.bin@rule@\" + DEV_VERSION,\n    visibility = [\"//visibility:public\"],\n)\n\nwd_cc_embed(\n    name = \"python-entrypoint\",\n    src = \"python-entrypoint.js\",\n    base_name = \"python-entrypoint\",\n    visibility = [\"//visibility:public\"],\n)\n\n# In case we have a runner which doesn't support building pyodide.capnp.bin we\n# want to use a pre-built/cross-compiled artifact. This is just a bazel alias\n# serving as a redirect when referenced by other targets instead of changing\n# the output filename.\nalias(\n    name = \"pyodide.capnp.bin_cross\",\n    actual = select({\n        \"@//build/config:prebuilt_binaries_arm64\": \"@//:bin.arm64/tmp/pyodide/pyodide.capnp.bin.aarch64-linux-gnu\",\n        \"//conditions:default\": \"pyodide.capnp.bin\",\n    }),\n    visibility = [\"//visibility:public\"],\n)\n\nalias(\n    name = \"pyodide.capnp.bin@rule\",\n    actual = \"pyodide.capnp.bin@rule@\" + DEV_VERSION,\n    visibility = [\"//visibility:public\"],\n)\n\nalias(\n    name = \"pyodide\",\n    actual = \"pyodide@\" + DEV_VERSION,\n    visibility = [\"//visibility:public\"],\n)\n\nwrite_file(\n    name = \"bundle_version_info\",\n    out = \"bundle_version_info.json\",\n    content = [json.encode(BUNDLE_VERSION_INFO)],\n)\n\npy_test(\n    name = \"unit-test-introspection\",\n    srcs = [\n        \"internal/introspection.py\",\n        \"internal/test_introspection.py\",\n    ] + glob([\"internal/workers-api/src/workers/*\"]),\n    main = \"internal/test_introspection.py\",\n)\n\npy_test(\n    name = \"unit-test-frozen-sdk\",\n    srcs = [\"internal/test_frozen_sdk.py\"],\n    data = glob([\"internal/workers-api/**\"]),\n    main = \"internal/test_frozen_sdk.py\",\n    target_compatible_with = [\"@platforms//os:linux\"],\n)\n"
  },
  {
    "path": "src/pyodide/README.md",
    "content": "# Pyodide\n\nThis folder includes the code needed to generate the Pyodide bundle which we\nbuild into workerd. There is also `build/BUILD.pyodide` (which just exposes the\nfiles from the Pyodide release as public to workerd) and\n`workerd/api/pyodide/pyodide.h` which adds the bundle to the module registry if\nthe appropriate flags are set.\n\nIt requires the `experimental` compatibility flag to enable.\n\nSee `samples/pyodide/` for an example using this in its current state.\n\n## Do we really want to be compiling this stuff into workerd?\n\nUpdating Pyodide's major version carries significant risk of breaking scripts.\nCurrently each Pyodide version comes with a specific Python version and a\nspecific lockfile of packages. Any script that breaks on newest Python or with a\nnewer version of a package will break on upgrade.\n\nThus, we have to let people pin older versions of Pyodide. Before we start\nsupporting multiple Pyodide, we'll want to be able to dynamically fetch the\nappropriate Pyodide version at runtime rather than bloating binary size by\nincluding lots of potentially unused code. Similarly, we will likely want to\ndynamically fetch the Python packages people use.\n\nThe present approach is just the fastest way to get something working.\n\n## What's happening here?\n\nPyodide's distribution consists of:\n\n1. The main \"emscripten binary\" which is `pyodide.asm.js` and `pyodide.asm.wasm`\n2. A loader `pyodide.js`\n3. The Python + Pyodide stdlib `python_stdlib.zip`\n4. A package lockfile `pyodide-lock.json` containing version and dependency\n   information for all packages that were built as part of the release.\n5. ... other stuff\n\nWe are currently taking just `pyodide.asm.js`, `pyodide.asm.wasm`, and\n`python_stdlib.zip`. We disable package loading entirely by not setting up the\nlock file. We'll eventually want our own mechanism for setting up packages that\nprobably happens at configuration time. The file `src/pyodide/python.js` serves\nas a simplified substitute for `pyodide.js` which drops the package loader and\nmost other config, but adds support for memory snapshots. We want to upstream\nthe memory snapshot support into Pyodide, but having our own reduced loader will\nprobably continue to be beneficial in the future.\n\nTo reduce memory usage and startup time, we'll also want to link our own version\nof the Emscripten binary `pyodide.asm.js` and `pyodide.asm.wasm`. Due to\nlimitations in Emscripten's dynamic linking, they are bloated by statically\nlinked junk that is rarely used. My hope is to include all the static libraries\nthat go into Pyodide in a release artifact for the next Pyodide version. Then as\npart of our build process we can do a modified final link step that drops stuff\nwe don't need.\n"
  },
  {
    "path": "src/pyodide/create_vendor_zip.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nScript to vendor a Python package for vendored_py_wd_test.\n\nThis script creates a pyproject.toml with the specified package as a dependency,\nruns `uv run pywrangler sync` to download and prepare the package, and then\ncreates a zip file of the resulting python_modules directory.\n\"\"\"\n\nimport argparse\nimport os\nimport shutil\nimport sys\nimport zipfile\nfrom pathlib import Path\nfrom typing import Literal\n\nfrom tool_utils import hexdigest, run\n\nPYPROJECT_TEMPLATE = \"\"\"[project]\nname = \"vendor-test\"\nversion = \"0.1.0\"\nrequires-python = \">=3.12\"\ndependencies = {dependencies}\n\n[dependency-groups]\ndev = [\"workers-py>=1.6\"]\n\"\"\"\n\nWRANGLER_TOML = \"\"\"\nname = \"hello-python-bindings\"\nmain = \"src/entry.py\"\ncompatibility_flags = {compat_flags}\ncompatibility_date = \"2025-08-14\"\n\"\"\"\n\ntype PyVer = Literal[\"3.12\", \"3.13\"]\n\n\ndef create_pyproject_toml(package_names: list[str], target_dir: Path) -> Path:\n    \"\"\"Create a pyproject.toml file with the specified package as a dependency.\"\"\"\n    pyproject_content = PYPROJECT_TEMPLATE.format(dependencies=repr(package_names))\n    pyproject_path = target_dir / \"pyproject.toml\"\n    pyproject_path.write_text(pyproject_content)\n    return pyproject_path\n\n\ndef create_wrangler_toml(target_dir: Path, python: PyVer):\n    compat_flags = [\"python_workers\"]\n    match python:\n        case \"3.13\":\n            compat_flags.append(\"python_workers_20250116\")\n\n    (target_dir / \"wrangler.toml\").write_text(\n        WRANGLER_TOML.format(compat_flags=repr(compat_flags))\n    )\n\n\ndef run_pywrangler_sync(work_dir: Path) -> Path:\n    \"\"\"Run `uv run pywrangler sync` in the specified directory.\"\"\"\n    env = os.environ.copy()\n    env[\"_PYODIDE_EXTRA_MOUNTS\"] = str(work_dir)\n    # TODO: Make pywrangler understand how to use Python 3.13 correctly and\n    # remove these extra commands\n    run([\"uv\", \"run\", \"pywrangler\", \"sync\"], cwd=work_dir, env=env)\n    python_modules_dir = work_dir / \"python_modules\"\n    if not python_modules_dir.exists():\n        print(f\"Error: python_modules directory not found at {python_modules_dir}\")\n        sys.exit(1)\n    return python_modules_dir\n\n\ndef create_zip_archive(source_dir: Path, output_dir: Path) -> bool:\n    \"\"\"Create a zip archive of the python_modules directory.\n\n    Return value indicates whether the archive includes any binary modules (.so\n    files).\n    \"\"\"\n    tmp_path = output_dir / \"tmp.zip\"\n    native = False\n\n    with zipfile.ZipFile(tmp_path, \"w\", zipfile.ZIP_DEFLATED) as zipf:\n        for file_path in source_dir.rglob(\"*\"):\n            if file_path.is_file():\n                # Store relative path from python_modules directory\n                arcname = file_path.relative_to(source_dir)\n                zipf.write(file_path, arcname)\n                if file_path.suffix == \".so\":\n                    native = True\n\n    return native\n\n\ndef vendor_package(package_names: list[str], python: PyVer) -> tuple[Path, bool]:\n    \"\"\"Main function to vendor a Python package.\"\"\"\n    tmp_dir = Path(\"/tmp\")\n    vendor_name = \"-\".join(package_names)\n    work_dir = tmp_dir / f\"vendor-{vendor_name}\"\n\n    # Clean up any existing work directory\n    if work_dir.exists():\n        shutil.rmtree(work_dir)\n    work_dir.mkdir(parents=True)\n\n    try:\n        # Create pyproject.toml\n        print(f\"Creating pyproject.toml in {work_dir}\")\n        create_pyproject_toml(package_names, work_dir)\n        create_wrangler_toml(work_dir, python)\n\n        # Run pywrangler sync\n        print(\"Running uv run pywrangler sync...\")\n        python_modules_dir = run_pywrangler_sync(work_dir)\n\n        # Create zip archive\n        print(\"Creating zip archive...\")\n        native = create_zip_archive(python_modules_dir, tmp_dir)\n        py = f\"-{python}\" if native else \"\"\n        zip_name = f\"{vendor_name}{py}-vendored-for-ew-testing.zip\"\n        zip_path = tmp_dir / zip_name\n        shutil.move(tmp_dir / \"tmp.zip\", zip_path)\n    except Exception as e:\n        print(f\"Error vendoring packages {package_names!r}: {e}\")\n        sys.exit(1)\n    else:\n        print(f\"Successfully created: {zip_path}\")\n        print(\n            \"Upload this zip file to the ew-snapshot-tests R2 bucket: \"\n            + \"https://dash.cloudflare.com/e415f1017791ced9d5f3eb0df2b31c9e/r2/default/buckets/ew-snapshot-tests\"\n        )\n        return zip_path, native\n    finally:\n        # Clean up work directory\n        if work_dir.exists():\n            shutil.rmtree(work_dir)\n\n\ndef main() -> int:\n    \"\"\"Main entry point.\"\"\"\n    parser = argparse.ArgumentParser(\n        description=\"Create a zip file of a vendored Python package's source files for vendored_py_wd_test.\"\n    )\n    parser.add_argument(\n        \"package_name\", help=\"Name of the Python package to vendor\", nargs=\"*\"\n    )\n    parser.add_argument(\"-p\", \"--python\", help=\"Name of the Python version to use\")\n\n    args = parser.parse_args()\n    if args.python is None:\n        args.python = \"3.12\"\n\n    if not args.package_name:\n        print(\"Error: Package name is required\")\n        return 1\n\n    try:\n        zip_path, native = vendor_package(args.package_name, args.python)\n        print(\"Update python_metadata.bzl with:\\n\")\n        abi = args.python if native else None\n        i1 = \" \" * 12\n        i2 = \" \" * 16\n        print(i1 + \"{\")\n        print(i2 + f'\"name\": \"{\"-\".join(args.package_name)}\",')\n        print(i2 + f'\"abi\": {abi!r},')\n        print(i2 + f'\"sha256\": \"{hexdigest(zip_path)}\",')\n        print(i1 + \"},\")\n        print()\n    except KeyboardInterrupt:\n        print(\"\\nOperation cancelled by user\")\n        return 1\n    else:\n        return 0\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  },
  {
    "path": "src/pyodide/eslint.config.mjs",
    "content": "import { baseConfig } from '../../tools/base.eslint.config.mjs';\n\nexport default [\n  ...baseConfig(),\n  {\n    rules: {\n      '@typescript-eslint/no-explicit-any': 'off',\n      '@typescript-eslint/no-non-null-assertion': 'off',\n      '@typescript-eslint/no-unnecessary-condition': 'off',\n      'no-empty': [\n        'error',\n        {\n          allowEmptyCatch: true,\n        },\n      ],\n    },\n  },\n];\n"
  },
  {
    "path": "src/pyodide/helpers.bzl",
    "content": "load(\"@aspect_rules_esbuild//esbuild:defs.bzl\", \"esbuild\")\nload(\"@bazel_skylib//rules:copy_file.bzl\", \"copy_file\")\nload(\"@bazel_skylib//rules:expand_template.bzl\", \"expand_template\")\nload(\"@capnp-cpp//src/capnp:cc_capnp_library.bzl\", \"cc_capnp_library\")\nload(\"//:build/capnp_embed.bzl\", \"capnp_embed\")\nload(\"//:build/js_file.bzl\", \"js_file\")\nload(\"//:build/python_metadata.bzl\", \"BUNDLE_VERSION_INFO\", \"PYODIDE_VERSIONS\", \"PYTHON_LOCKFILES\")\nload(\"//:build/wd_js_bundle.bzl\", \"wd_js_bundle\")\nload(\"//:build/wd_ts_bundle.bzl\", \"wd_ts_bundle\")\n\ndef _out_name(src):\n    src = src.removesuffix(\"//file\")\n    src = src.removeprefix(\"@\")\n    return src.rsplit(\":\", 2)[-1].rsplit(\"/\", 2)[-1]\n\ndef _out_path(name, version):\n    res = \"generated/\" + name\n    if version:\n        res = version + \"/\" + res\n    return res\n\ndef _ts_bundle_out(prefix, name, version):\n    return \":\" + _out_path(prefix + name.removeprefix(\"internal/\").replace(\"/\", \"_\"), version)\n\ndef _copy_to_generated(src, version = None, out_name = None, name = None):\n    out_name = out_name or _out_name(src)\n    if name == None:\n        name = out_name + \"@copy\"\n    if version:\n        name += \"@\" + version\n    copy_file(name = name, src = src, out = _out_path(out_name, version))\n\ndef _copy_and_capnp_embed(src):\n    out_name = _out_name(src)\n    _copy_to_generated(src)\n    capnp_embed(\n        name = out_name + \"@capnp\",\n        src = _out_path(out_name, None),\n        deps = [out_name + \"@copy\"],\n    )\n\ndef _fmt_python_snapshot_release(\n        pyodide_version,\n        pyodide_date,\n        packages,\n        backport,\n        baseline_snapshot_hash,\n        flag,\n        real_pyodide_version,\n        **_kwds):\n    content = \", \".join(\n        [\n            'pyodide = \"%s\"' % pyodide_version,\n            'realPyodideVersion = \"%s\"' % real_pyodide_version,\n            'pyodideRevision = \"%s\"' % pyodide_date,\n            'packages = \"%s\"' % packages,\n            \"backport = %s\" % backport,\n            'baselineSnapshotHash = \"%s\"' % baseline_snapshot_hash,\n            'flagName = \"%s\"' % flag,\n        ],\n    )\n    return \"(%s)\" % content\n\ndef pyodide_extra():\n    _copy_to_generated(\n        \"pyodide_extra.capnp\",\n        out_name = \"pyodide_extra_tmpl.capnp\",\n    )\n\n    package_tags = [info[\"tag\"] for info in PYTHON_LOCKFILES]\n\n    expand_template(\n        name = \"pyodide_extra_expand_template@rule\",\n        out = \"generated/pyodide_extra.capnp\",\n        substitutions = {\n            \"%PACKAGE_LOCKS\": \",\".join(\n                [\n                    '(packageDate = \"%s\", lock = embed \"pyodide-lock_%s.json\")' %\n                    (tag, tag)\n                    for tag in package_tags\n                ],\n            ),\n            \"%PYTHON_RELEASES\": \", \".join(\n                [_fmt_python_snapshot_release(**info) for info in BUNDLE_VERSION_INFO.values()],\n            ),\n        },\n        template = \"generated/pyodide_extra_tmpl.capnp\",\n    )\n\n    for tag in package_tags:\n        _copy_and_capnp_embed(\"@pyodide-lock_\" + tag + \".json//file\")\n\n    cc_capnp_library(\n        name = \"pyodide_extra_capnp\",\n        srcs = [\"generated/pyodide_extra.capnp\"],\n        visibility = [\"//visibility:public\"],\n        deps = [\n        ] + [\n            \":pyodide-lock_%s.json@capnp\" % tag\n            for tag in package_tags\n        ],\n        target_compatible_with = select({\n            \"@//build/config:no_build\": [\"@platforms//:incompatible\"],\n            \"//conditions:default\": [],\n        }),\n    )\n\ndef python_bundles(overrides = {}):\n    srcs = [_python_bundle_helper(info, overrides) for info in PYODIDE_VERSIONS]\n    native.filegroup(\n        name = \"python_bundles\",\n        srcs = srcs + [\":bundle_version_info\"],\n    )\n\ndef _python_bundle_helper(info, overrides):\n    version = info[\"version\"]\n    override = overrides.get(version, {})\n    return _python_bundle(version, **override)\n\ndef pyodide_static():\n    internal_data_modules = native.glob([\n        \"internal/*.py\",\n        \"internal/workers-api/src/*.py\",\n        \"internal/workers-api/src/workers/*.py\",\n        \"internal/patches/*.py\",\n        \"internal/topLevelEntropy/*.py\",\n    ])\n    internal_modules = native.glob(\n        [\n            \"internal/*.ts\",\n            \"internal/topLevelEntropy/*.ts\",\n            \"types/*.ts\",\n            \"types/*/*.ts\",\n        ],\n    )\n    modules = [\"python-entrypoint-helper.ts\"]\n\n    wd_ts_bundle(\n        name = \"pyodide_static\",\n        eslintrc_json = \"eslint.config.mjs\",\n        import_name = \"pyodide\",\n        internal_data_modules = internal_data_modules,\n        internal_modules = internal_modules,\n        lint = True,\n        modules = modules,\n        schema_id = \"0xdc8d02dfbdf14025\",\n        tsconfig_json = \"tsconfig.json\",\n    )\n\n_PRELUDE = \"\"\"\nimport {\n    addEventListener,\n    getRandomValues,\n    location,\n    monotonicDateNow,\n    newWasmModule,\n    patchedApplyFunc,\n    patchedLoadLibData,\n    reportUndefinedSymbolsPatched,\n    wasmInstantiate,\n    patched_PyEM_CountFuncParams,\n} from \"pyodide-internal:pool/builtin_wrappers\";\n\"\"\"\n\n# pyodide.asm.js patches\n# TODO: all of these should be fixed by linking our own Pyodide or by upstreaming.\n_REPLACEMENTS = [\n    [\n        # Convert pyodide.asm.js into an es6 module.\n        # When we link our own we can pass `-sES6_MODULE` to the linker and it will do this for us\n        # automatically.\n        \"var _createPyodideModule\",\n        _PRELUDE + \"export const _createPyodideModule\",\n    ],\n    [\n        \"globalThis._createPyodideModule = _createPyodideModule;\",\n        \"\",\n    ],\n    [\n        \"new WebAssembly.Module\",\n        \"newWasmModule\",\n    ],\n    [\n        \"WebAssembly.instantiate\",\n        \"wasmInstantiate\",\n    ],\n    [\n        \"Date.now\",\n        \"monotonicDateNow\",\n    ],\n    [\n        \"reportUndefinedSymbols()\",\n        \"reportUndefinedSymbolsPatched(Module)\",\n    ],\n    [\n        \"crypto.getRandomValues(\",\n        \"getRandomValues(Module, \",\n    ],\n    [\n        # Direct eval disallowed in esbuild, see:\n        # https://esbuild.github.io/content-types/#direct-eval\n        \"eval(func)\",\n        \"(() => {throw new Error('Internal Emscripten code tried to eval, this should not happen, please file a bug report with your requirements.txt file\\\\'s contents')})()\",\n    ],\n    [\n        \"eval(data)\",\n        \"(() => {throw new Error('Internal Emscripten code tried to eval, this should not happen, please file a bug report with your requirements.txt file\\\\'s contents')})()\",\n    ],\n    [\n        \"eval(UTF8ToString(ptr))\",\n        \"(() => {throw new Error('Internal Emscripten code tried to eval, this should not happen, please file a bug report with your requirements.txt file\\\\'s contents')})()\",\n    ],\n    # Dynamic linking patches:\n    # library lookup\n    [\n        \"function loadLibData(){\",\n        \"\"\"\n        function loadLibData(){\n            var libData = patchedLoadLibData(Module, libName, flags.rpath);\n            return flags.loadAsync ? Promise.resolve(libData) : libData;\n        }\n        function dummiedOutOrigLoadLibData(){\n        \"\"\",\n    ],\n    # for ensuring memory base of dynlib is stable when restoring snapshots\n    [\n        \"getMemory(\",\n        \"Module.getMemoryPatched(Module, libName, \",\n    ],\n    # to fix RPC, applies https://github.com/pyodide/pyodide/commit/8da1f38f7\n    [\n        \"nullToUndefined(func.apply(\",\n        \"nullToUndefined(patchedApplyFunc(API, func, \",\n    ],\n    [\n        \"nullToUndefined(Function.prototype.apply.apply\",\n        \"nullToUndefined(API.config.jsglobals.Function.prototype.apply.apply\",\n    ],\n    [\n        \"function _PyEM_CountFuncParams(func){\",\n        \"function _PyEM_CountFuncParams(func){ return patched_PyEM_CountFuncParams(Module, func);\",\n    ],\n    [\n        \"var tableBase=metadata.tableSize?wasmTable.length:0;\",\n        \"var tableBase=metadata.tableSize?wasmTable.length:0;\" +\n        \"Module.snapshotDebug && console.log('loadWebAssemblyModule', libName, memoryBase, tableBase);\",\n    ],\n    # to ensure we report every fatal error, not just the first one\n    [\n        'console.error(\"Recursive call to fatal_error. Inner error was:\");',\n        'console.error(\"Recursive call to fatal_error. Inner error was:\");\\n' +\n        \"try { API.on_fatal?.(e); } catch(e2) { console.error(e2); }\\n\",\n    ],\n]\n\ndef _python_bundle(version, *, pyodide_asm_wasm = None, pyodide_asm_js = None, python_stdlib_zip = None, emscripten_setup_override = None):\n    pyodide_package = \"@pyodide-%s//\" % version\n    if not pyodide_asm_wasm:\n        pyodide_asm_wasm = pyodide_package + \":pyodide/pyodide.asm.wasm\"\n\n    if not pyodide_asm_js:\n        pyodide_asm_js = pyodide_package + \":pyodide/pyodide.asm.js\"\n\n    if not python_stdlib_zip:\n        python_stdlib_zip = pyodide_package + \":pyodide/python_stdlib.zip\"\n\n    _copy_to_generated(pyodide_asm_wasm, version, out_name = \"pyodide.asm.wasm\")\n\n    _copy_to_generated(python_stdlib_zip, version, out_name = \"python_stdlib.zip\")\n\n    expand_template(\n        name = \"pyodide.asm.js@rule@\" + version,\n        out = _out_path(\"pyodide.asm.js\", version),\n        substitutions = dict(_REPLACEMENTS),\n        template = pyodide_asm_js,\n    )\n\n    js_file(\n        name = \"pyodide.asm.js@rule_js@\" + version,\n        srcs = [_out_path(\"pyodide.asm.js\", version)],\n        deps = [\"pyodide.asm.js@rule@\" + version],\n    )\n\n    if emscripten_setup_override:\n        _copy_to_generated(out_name = \"emscriptenSetup.js\", name = \"emscriptenSetup\", src = emscripten_setup_override, version = version)\n    else:\n        esbuild(\n            name = \"emscriptenSetup@\" + version,\n            # exclude emscriptenSetup from source set so that rules_ts won't also try to create a JS output\n            # for it. The file is provided in entry_point instead.\n            srcs = native.glob([\n                \"internal/pool/*.ts\",\n            ], exclude = [\"internal/pool/emscriptenSetup.ts\"]) + [\n                _out_path(\"pyodide.asm.js\", version),\n                \"internal/util.ts\",\n                \"internal/const.ts\",\n            ],\n            config = \"internal/pool/esbuild.config.mjs\",\n            entry_point = \"internal/pool/emscriptenSetup.ts\",\n            external = [\n                \"child_process\",\n                \"crypto\",\n                \"fs\",\n                \"path\",\n                \"url\",\n                \"vm\",\n                \"ws\",\n                \"node:child_process\",\n                \"node:crypto\",\n                \"node:fs\",\n                \"node:path\",\n                \"node:url\",\n                \"node:vm\",\n            ],\n            format = \"esm\",\n            output = _out_path(\"emscriptenSetup.js\", version),\n            target = \"esnext\",\n            deps = [\"pyodide.asm.js@rule_js@\" + version],\n        )\n\n    import_name = \"pyodideRuntime\"\n    wd_js_bundle(\n        name = \"pyodide@\" + version,\n        import_name = import_name,\n        builtin_modules = [],\n        schema_id = \"0xbcc8f57c63814005\",\n        internal_data_modules = [\n            _out_path(\"python_stdlib.zip\", version),\n            _out_path(\"pyodide.asm.wasm\", version),\n            _out_path(\"emscriptenSetup.js\", version),\n        ],\n        deps = [\n            \"emscriptenSetup@\" + version,\n            \"pyodide.asm.wasm@copy@\" + version,\n            \"python_stdlib.zip@copy@\" + version,\n        ],\n        out_dir = _out_path(\"\", version),\n    )\n\n    pyodide_cappn_bin_rule = \"pyodide.capnp.bin@rule@\" + version\n    native.genrule(\n        name = pyodide_cappn_bin_rule,\n        srcs = [\n            \":pyodide@%s.capnp\" % version,\n            \"//src/workerd/jsg:modules.capnp\",\n            _ts_bundle_out(import_name + \"-internal_\", \"emscriptenSetup.js\", version),\n            _ts_bundle_out(import_name + \"-internal_\", \"pyodide.asm.wasm\", version),\n            _ts_bundle_out(import_name + \"-internal_\", \"python_stdlib.zip\", version),\n        ],\n        outs = [_out_path(\"pyodide.capnp.bin\", version)],\n        cmd = \" \".join([\n            # Annoying logic to deal with different paths in workerd vs downstream.\n            # Either need \"-I src\" in workerd or -I external/+dep_workerd+workerd/src downstream\n            \"INCLUDE=$$(stat src > /dev/null 2>&1 && echo src || echo external/+dep_workerd+workerd/src);\",\n            \"$(execpath @capnp-cpp//src/capnp:capnp_tool)\",\n            \"eval\",\n            \"$(location :pyodide@%s.capnp)\" % version,\n            import_name + \"Bundle\",\n            \"-I $$INCLUDE\",\n            \"-o binary\",\n            \"> $@\",\n        ]),\n        tools = [\"@capnp-cpp//src/capnp:capnp_tool\"],\n        visibility = [\"//visibility:public\"],\n        target_compatible_with = select({\n            \"@//build/config:no_build\": [\"@platforms//:incompatible\"],\n            \"//conditions:default\": [],\n        }),\n    )\n    return pyodide_cappn_bin_rule\n"
  },
  {
    "path": "src/pyodide/internal/const.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport const PyodideVersion = {\n  V0_26_0a2: '0.26.0a2',\n  V0_28_2: '0.28.2',\n} as const;\n"
  },
  {
    "path": "src/pyodide/internal/envHelpers.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport innerEnv from 'cloudflare-internal:env';\n\n// A generator that keeps the environment patched until it is resolved.\n// Used to define the patch_env context manager, see\n// src/pyodide/internal/workers-api/src/workers/_workers.py\nexport function* patch_env_helper(patch: unknown): Generator<void> {\n  using _ = innerEnv.pythonPatchEnv(patch);\n  yield;\n}\n"
  },
  {
    "path": "src/pyodide/internal/introspection.py",
    "content": "import signal\nimport sys\nfrom inspect import isawaitable, isclass\nfrom types import FunctionType\n\nimport js\nfrom workers import (\n    DurableObject,\n    WorkerEntrypoint,\n    WorkflowEntrypoint,\n    python_from_rpc,\n    python_to_rpc,\n)\n\nfrom pyodide.code import relaxed_call\nfrom pyodide.ffi import to_js\n\n\ndef getattr_no_get(cls, name):\n    \"\"\"Get attribute from class, don't run __get__\"\"\"\n    for base in cls.__mro__:\n        if name in base.__dict__:\n            return base.__dict__[name]\n    return None\n\n\ndef collect_methods(cls):\n    \"\"\"\n    Iterates through the methods in `cls` and returns only public non-static/non-class methods\n    defined on that class.\n    \"\"\"\n    return sorted(\n        name\n        for name in dir(cls)\n        if not name.startswith(\"_\")\n        and isinstance(getattr_no_get(cls, name), FunctionType)\n    )\n\n\ndef collect_classes(user_mod, base_cls):\n    \"\"\"\n    Iterates through the defined symbols in the input module. Returns any classes which extend\n    `base_cls` (where `base_cls` is one of DurableObject, WorkerEntrypoint or WorkflowEntrypoint).\n\n    This method returns a list of JS objects like [{\"className\": \"MyClass\", \"methodNames\": [\"foo\"]}].]\n    \"\"\"\n    if hasattr(user_mod, \"__all__\"):\n        keys = user_mod.__all__\n    else:\n        keys = (key for key in dir(user_mod) if not key.startswith(\"_\"))\n\n    exported_attrs = [getattr(user_mod, key) for key in keys]\n\n    def filter(val):\n        return isclass(val) and issubclass(val, base_cls) and val is not base_cls\n\n    class_attrs = [attr for attr in exported_attrs if filter(attr)]\n    result = [\n        {\"className\": attr.__name__, \"methodNames\": collect_methods(attr)}\n        for attr in class_attrs\n    ]\n    return to_js(result, dict_converter=js.Object.fromEntries)\n\n\ndef collect_entrypoint_classes(user_mod):\n    return to_js(\n        {\n            \"durableObjects\": collect_classes(user_mod, DurableObject),\n            \"workerEntrypoints\": collect_classes(user_mod, WorkerEntrypoint),\n            \"workflowEntrypoints\": collect_classes(user_mod, WorkflowEntrypoint),\n        },\n        dict_converter=js.Object.fromEntries,\n    )\n\n\nasync def wrapper_func(relaxed, inst, prop, *args, **kwargs):\n    method = getattr(inst, prop)\n\n    py_args = [python_from_rpc(arg) for arg in args]\n    py_kwargs = {k: python_from_rpc(v) for k, v in kwargs.items()}\n    result = (\n        relaxed_call(method, *py_args, **py_kwargs)\n        if relaxed\n        else method(*py_args, **py_kwargs)\n    )\n\n    if isawaitable(result):\n        return python_to_rpc(await result)\n    else:\n        return python_to_rpc(result)\n\n\nclass CpuLimitExceeded(BaseException):\n    pass\n\n\ndef raise_cpu_limit_exceeded(signum, frame):\n    raise CpuLimitExceeded(\"Python Worker exceeded CPU time limit\")\n\n\nif sys.platform == \"emscripten\":\n    signal.signal(signal.SIGXCPU, raise_cpu_limit_exceeded)\n"
  },
  {
    "path": "src/pyodide/internal/jaeger.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { default as internalJaeger } from 'pyodide-internal:internalJaeger';\nimport { IS_TRACING } from 'pyodide-internal:metadata';\n\n/**\n * Used for tracing via Jaeger.\n */\nexport function enterJaegerSpan<T>(span: string, callback: () => T): T {\n  if (!IS_TRACING || !internalJaeger.traceId) {\n    // Jaeger tracing not enabled or traceId is not present in request.\n    return callback();\n  }\n\n  return internalJaeger.enterSpan(span, callback);\n}\n"
  },
  {
    "path": "src/pyodide/internal/loadPackage.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n/**\n * This file contains code that roughly replaces pyodide.loadPackage, with workerd-specific\n * optimizations:\n * - Wheels are decompressed with a DecompressionStream instead of in Python\n * - Wheels are overlaid onto the site-packages dir instead of actually being copied\n * - Wheels are fetched from a disk cache if available.\n *\n * Note that loadPackages is only used in local dev for now, internally we use the full big bundle\n * that contains all the packages ready to go.\n */\n\nimport {\n  LOCKFILE,\n  PACKAGES_VERSION,\n  USING_OLDEST_PACKAGES_VERSION,\n} from 'pyodide-internal:metadata';\nimport {\n  VIRTUALIZED_DIR,\n  STDLIB_PACKAGES,\n} from 'pyodide-internal:setupPackages';\nimport { parseTarInfo } from 'pyodide-internal:tar';\nimport { createTarFS } from 'pyodide-internal:tarfs';\nimport { default as ArtifactBundler } from 'pyodide-internal:artifacts';\nimport {\n  PythonUserError,\n  PythonWorkersInternalError,\n} from 'pyodide-internal:util';\n\nfunction getPackageMetadata(requirement: string): PackageDeclaration {\n  const obj = LOCKFILE['packages'][requirement];\n  if (!obj) {\n    throw new PythonUserError(\n      'Requirement ' + requirement + ' not found in lockfile'\n    );\n  }\n\n  return obj;\n}\n\nfunction loadBundleFromArtifactBundler(requirement: string): Reader {\n  const filename = getPackageMetadata(requirement).file_name;\n  const fullPath = `python-package-bucket/${PACKAGES_VERSION}/${filename}`;\n  const reader = ArtifactBundler.getPackage(fullPath);\n  if (!reader) {\n    throw new PythonWorkersInternalError(\n      'Failed to get package ' + fullPath + ' from ArtifactBundler'\n    );\n  }\n  return reader;\n}\n\n/**\n * Downloads the requirements specified and loads them into Pyodide. Note that this does not\n * do any dependency resolution, it just installs the requirements that are specified. See\n * `getTransitiveRequirements` for the code that deals with this.\n */\nexport function loadPackages(Module: Module, requirements: Set<string>): void {\n  let pkgsToLoad = requirements;\n  // TODO: Package snapshot created with '20240829.4' needs the stdlib packages to be added here.\n  // We should remove this check once the next Python and packages versions are rolled\n  // out.\n  if (USING_OLDEST_PACKAGES_VERSION) {\n    pkgsToLoad = pkgsToLoad.union(new Set(STDLIB_PACKAGES));\n  }\n\n  for (const req of pkgsToLoad) {\n    if (req === 'test') {\n      continue; // Skip the test package, it is only useful for internal Python regression testing.\n    }\n    if (VIRTUALIZED_DIR.hasRequirementLoaded(req)) {\n      continue;\n    }\n\n    const reader = loadBundleFromArtifactBundler(req);\n    const [tarInfo, soFiles] = parseTarInfo(reader);\n    const pkg = getPackageMetadata(req);\n    VIRTUALIZED_DIR.addSmallBundle(tarInfo, soFiles, req, pkg.install_dir);\n  }\n\n  const tarFS = createTarFS(Module);\n  VIRTUALIZED_DIR.mount(Module, tarFS);\n}\n"
  },
  {
    "path": "src/pyodide/internal/metadata.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { default as MetadataReader } from 'pyodide-internal:runtime-generated/metadata';\nimport { default as ArtifactBundler } from 'pyodide-internal:artifacts';\n\nexport const IS_WORKERD = MetadataReader.isWorkerd();\nexport const IS_TRACING = MetadataReader.isTracing();\n\n// Snapshots\nexport const SHOULD_SNAPSHOT_TO_DISK = MetadataReader.shouldSnapshotToDisk();\nexport const IS_CREATING_BASELINE_SNAPSHOT =\n  MetadataReader.isCreatingBaselineSnapshot();\nexport const IS_EW_VALIDATING = ArtifactBundler.isEwValidating();\nexport const IS_CREATING_SNAPSHOT = IS_EW_VALIDATING || SHOULD_SNAPSHOT_TO_DISK;\n\n// There are two validations that we perform. The first one runs without a _bundled_ memory snapshot\n// (but may use a baseline snapshot, though this is stored in the ArtfactBundler). The second one\n// runs with a _bundled_ memory snapshot, which is expected to be a dedicated snapshot. When this\n// bool is true, we verify that the bundled memory snapshot we receive is a dedicated snapshot.\nexport const IS_SECOND_VALIDATION_PHASE =\n  MetadataReader.hasMemorySnapshot() && IS_EW_VALIDATING;\nexport const MEMORY_SNAPSHOT_READER = MetadataReader.hasMemorySnapshot()\n  ? MetadataReader\n  : ArtifactBundler.hasMemorySnapshot()\n    ? ArtifactBundler\n    : undefined;\n\n// Packages\nexport const PACKAGES_VERSION = MetadataReader.getPackagesVersion();\nexport const USING_OLDEST_PACKAGES_VERSION = PACKAGES_VERSION === '20240829.4';\n// TODO: pyodide-packages.runtime-playground.workers.dev points at a worker which redirects requests\n// to the public R2 bucket URL at pub-45d734c4145d4285b343833ee450ef38.r2.dev. We should remove\n// this worker and point at our prod bucket.\nexport const WORKERD_INDEX_URL = USING_OLDEST_PACKAGES_VERSION\n  ? 'https://pyodide-packages.runtime-playground.workers.dev/' +\n    PACKAGES_VERSION +\n    '/'\n  : 'https://python-packages.edgeworker.net/' + PACKAGES_VERSION + '/';\n// The package lock is embedded in the binary. See `getPyodideLock` and `packageLocks`.\nexport const LOCKFILE = JSON.parse(\n  MetadataReader.getPackagesLock()\n) as PackageLock;\n\nexport const REQUIREMENTS = MetadataReader.getRequirements();\nexport const TRANSITIVE_REQUIREMENTS =\n  MetadataReader.getTransitiveRequirements();\n\n// Entrypoints\nexport const MAIN_MODULE_NAME = MetadataReader.getMainModule();\n\nexport type CompatibilityFlags = MetadataReader.CompatibilityFlags;\nexport const COMPATIBILITY_FLAGS: MetadataReader.CompatibilityFlags = {\n  // Compat flags returned from getCompatibilityFlags is immutable,\n  // but in Pyodide 0.26, we modify the JS object that is exposed to the Python through\n  // registerJsModule so we create a new object here by copying the values.\n  ...MetadataReader.getCompatibilityFlags(),\n};\nexport const WORKFLOWS_ENABLED: boolean =\n  !!COMPATIBILITY_FLAGS.python_workflows;\nconst NO_GLOBAL_HANDLERS: boolean =\n  !!COMPATIBILITY_FLAGS.python_no_global_handlers;\nconst FORCE_NEW_VENDOR_PATH: boolean =\n  !!COMPATIBILITY_FLAGS.python_workers_force_new_vendor_path;\nexport const IS_DEDICATED_SNAPSHOT_ENABLED: boolean =\n  !!COMPATIBILITY_FLAGS.python_dedicated_snapshot;\nexport const EXTERNAL_SDK = !!COMPATIBILITY_FLAGS.enable_python_external_sdk;\n\nexport const LEGACY_GLOBAL_HANDLERS = !NO_GLOBAL_HANDLERS;\nexport const LEGACY_VENDOR_PATH = !FORCE_NEW_VENDOR_PATH;\nexport const CHECK_RNG_STATE = !!COMPATIBILITY_FLAGS.python_check_rng_state;\n\nexport const setCpuLimitNearlyExceededCallback =\n  MetadataReader.setCpuLimitNearlyExceededCallback.bind(MetadataReader);\n"
  },
  {
    "path": "src/pyodide/internal/metadatafs.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { default as MetadataReader } from 'pyodide-internal:runtime-generated/metadata';\nimport { createReadonlyFS } from 'pyodide-internal:readOnlyFS';\nimport { PythonWorkersInternalError } from 'pyodide-internal:util';\n\nfunction createTree(paths: string[]): MetadataDirInfo {\n  const tree: MetadataFSInfo = new Map();\n  paths.forEach((elt: string, idx: number) => {\n    let subTree: MetadataFSInfo = tree;\n    const parts = elt.split('/');\n    const name = parts.pop()!;\n    for (const part of parts) {\n      if (typeof subTree === 'number') {\n        throw new PythonWorkersInternalError(\n          'expected subtree to not be a number'\n        );\n      }\n      let next: MetadataFSInfo | undefined = subTree.get(part);\n      if (!next) {\n        next = new Map();\n        subTree.set(part, next);\n      }\n      subTree = next;\n    }\n    if (typeof subTree === 'number') {\n      throw new PythonWorkersInternalError(\n        'expected subtree to not be a number'\n      );\n    }\n    subTree.set(name, idx);\n  });\n  return tree;\n}\n\nexport function createMetadataFS(Module: Module): object {\n  // TODO: Make this type more specific\n  const TIMESTAMP = Date.now();\n  const names = MetadataReader.getNames();\n  const sizes = MetadataReader.getSizes();\n  const rootInfo = createTree(names);\n  const FSOps: FSOps<MetadataFSInfo> = {\n    getNodeMode(_parent, _name, info) {\n      return {\n        permissions: 0o555, // read and execute but not write\n        isDir: typeof info !== 'number',\n      };\n    },\n    setNodeAttributes(node, info, isDir) {\n      node.modtime = TIMESTAMP;\n      node.usedBytes = 0;\n      if (info === undefined) {\n        info = rootInfo;\n      }\n      if (isDir) {\n        node.tree = info as MetadataDirInfo;\n      } else {\n        node.index = info as number;\n        node.usedBytes = sizes[info as number]!;\n      }\n    },\n    readdir(node) {\n      if (node.tree == undefined) {\n        throw new PythonWorkersInternalError(\n          'cannot read directory, tree is undefined'\n        );\n      }\n      return Array.from(node.tree.keys());\n    },\n    lookup(parent, name) {\n      // Parent is not a directory so we always raise ENOENT (44)\n      if (parent.tree == undefined) {\n        throw new Module.FS.ErrnoError(44);\n      }\n      const res = parent.tree.get(name);\n      if (res === undefined) {\n        throw new Module.FS.ErrnoError(44);\n      }\n      return res;\n    },\n    read(stream, position, buffer) {\n      return MetadataReader.read(stream.node.index!, position, buffer);\n    },\n  };\n\n  return createReadonlyFS(FSOps, Module);\n}\n"
  },
  {
    "path": "src/pyodide/internal/patches/aiohttp.py",
    "content": "\"\"\"\nMonkeypatch aiohttp to introduce Fetch API support.\n\nBased on https://github.com/pyodide/pyodide/issues/3711#issuecomment-1773523301\nwith some modifications.\n\"\"\"\n\n# ruff: noqa: PLR0913, TRY301, TRY300\n\nfrom collections.abc import Iterable\nfrom contextlib import suppress\nfrom typing import Any\n\nfrom aiohttp import ClientSession, ClientTimeout, CookieJar, InvalidURL, hdrs, payload\nfrom aiohttp.client_reqrep import _merge_ssl_params\nfrom aiohttp.helpers import TimeoutHandle, get_env_proxy_for_url, strip_auth_from_url\nfrom multidict import CIMultiDict, istr\nfrom yarl import URL\n\n\nclass Content:\n    __slots__ = (\"_exception\", \"_jsresp\")\n\n    def __init__(self, _jsresp):\n        self._jsresp = _jsresp\n        self._exception = None\n\n    async def read(self):\n        if self._exception:\n            raise self._exception\n        buf = await self._jsresp.arrayBuffer()\n        self._jsresp = None\n        return buf.to_bytes()\n\n    def exception(self):\n        return self._exception\n\n    def set_exception(self, exc: BaseException) -> None:\n        self._exception = exc\n\n\nasync def _request(\n    self,\n    method: str,\n    str_or_url,\n    *,\n    params=None,\n    data: Any = None,\n    json: Any = None,\n    cookies=None,\n    headers=None,\n    skip_auto_headers: Iterable[str] | None = None,\n    auth=None,\n    allow_redirects: bool = True,\n    max_redirects: int = 10,\n    compress: str | None = None,\n    chunked: bool | None = None,\n    expect100: bool = False,\n    raise_for_status=None,\n    read_until_eof: bool = True,\n    proxy=None,\n    proxy_auth=None,\n    timeout=None,\n    verify_ssl: bool | None = None,\n    fingerprint: bytes | None = None,\n    ssl_context=None,\n    ssl=None,\n    proxy_headers=None,\n    trace_request_ctx=None,\n    read_bufsize: int | None = None,\n):\n    # NOTE: timeout clamps existing connect and read timeouts.  We cannot\n    # set the default to None because we need to detect if the user wants\n    # to use the existing timeouts by setting timeout to None.\n\n    if self.closed:\n        raise RuntimeError(\"Session is closed\")\n\n    ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint)\n\n    if data is not None and json is not None:\n        raise ValueError(\"data and json parameters can not be used at the same time\")\n    elif json is not None:\n        data = payload.JsonPayload(json, dumps=self._json_serialize)\n\n    history = []\n    version = self._version\n    params = params or {}\n\n    # Merge with default headers and transform to CIMultiDict\n    headers = self._prepare_headers(headers)\n    proxy_headers = self._prepare_headers(proxy_headers)\n\n    try:\n        url = self._build_url(str_or_url)\n    except ValueError as e:\n        raise InvalidURL(str_or_url) from e\n\n    skip_headers = set(self._skip_auto_headers)\n    if skip_auto_headers is not None:\n        for i in skip_auto_headers:\n            skip_headers.add(istr(i))\n\n    if proxy is not None:\n        try:\n            proxy = URL(proxy)\n        except ValueError as e:\n            raise InvalidURL(proxy) from e\n\n    if timeout is None:\n        real_timeout = self._timeout\n    elif not isinstance(timeout, ClientTimeout):\n        real_timeout = ClientTimeout(total=timeout)  # type: ignore[arg-type]\n    else:\n        real_timeout = timeout\n    # timeout is cumulative for all request operations\n    # (request, redirects, responses, data consuming)\n    tm = TimeoutHandle(self._loop, real_timeout.total)\n    handle = tm.start()\n\n    if read_bufsize is None:\n        read_bufsize = self._read_bufsize\n\n    traces = []\n\n    timer = tm.timer()\n    try:\n        with timer:\n            url, auth_from_url = strip_auth_from_url(url)\n            if auth and auth_from_url:\n                raise ValueError(\n                    \"Cannot combine AUTH argument with credentials encoded in URL\"\n                )\n\n            if auth is None:\n                auth = auth_from_url\n            if auth is None:\n                auth = self._default_auth\n            # It would be confusing if we support explicit\n            # Authorization header with auth argument\n            if auth is not None and hdrs.AUTHORIZATION in headers:\n                raise ValueError(\n                    \"Cannot combine AUTHORIZATION header \"\n                    \"with AUTH argument or credentials \"\n                    \"encoded in URL\"\n                )\n\n            all_cookies = self._cookie_jar.filter_cookies(url)\n\n            if cookies is not None:\n                tmp_cookie_jar = CookieJar()\n                tmp_cookie_jar.update_cookies(cookies)\n                req_cookies = tmp_cookie_jar.filter_cookies(url)\n                if req_cookies:\n                    all_cookies.load(req_cookies)\n\n            if proxy is not None:\n                proxy = URL(proxy)\n            elif self._trust_env:\n                with suppress(LookupError):\n                    proxy, proxy_auth = get_env_proxy_for_url(url)\n\n            req = self._request_class(\n                method,\n                url,\n                params=params,\n                headers=headers,\n                skip_auto_headers=skip_headers,\n                data=data,\n                cookies=all_cookies,\n                auth=auth,\n                version=version,\n                compress=compress,\n                chunked=chunked,\n                expect100=expect100,\n                loop=self._loop,\n                response_class=self._response_class,\n                proxy=proxy,\n                proxy_auth=proxy_auth,\n                timer=timer,\n                session=self,\n                ssl=ssl,\n                proxy_headers=proxy_headers,\n                traces=traces,\n            )\n\n            req.response = resp = req.response_class(\n                req.method,\n                req.original_url,\n                writer=None,\n                continue100=req._continue,\n                timer=req._timer,\n                request_info=req.request_info,\n                traces=req._traces,\n                loop=req.loop,\n                session=req._session,\n            )\n            from js import Headers, fetch\n\n            from pyodide.ffi import to_js\n\n            body = None\n            if req.body:\n                body = to_js(req.body._value)\n            jsheaders = Headers.new()\n            for k, v in headers.items():\n                jsheaders.append(k, v)\n            jsresp = await fetch(\n                str(req.url), method=req.method, headers=jsheaders, body=body\n            )\n            resp.version = version\n            resp.status = jsresp.status\n            resp.reason = jsresp.statusText\n            # This is not quite correct in handling of repeated headers\n            resp._headers = CIMultiDict(jsresp.headers)\n            resp._raw_headers = tuple(tuple(e) for e in jsresp.headers)\n            resp.content = Content(jsresp)\n\n        # check response status\n        if raise_for_status is None:\n            raise_for_status = self._raise_for_status\n\n        if raise_for_status is None:\n            pass\n        elif callable(raise_for_status):\n            await raise_for_status(resp)\n        elif raise_for_status:\n            resp.raise_for_status()\n\n        # register connection\n        if handle is not None:\n            if resp.connection is not None:\n                resp.connection.add_callback(handle.cancel)\n            else:\n                handle.cancel()\n\n        resp._history = tuple(history)\n\n        for trace in traces:\n            await trace.send_request_end(\n                method, url.update_query(params), headers, resp\n            )\n        return resp\n\n    except BaseException as e:\n        # cleanup timer\n        tm.close()\n        if handle:\n            handle.cancel()\n            handle = None\n\n        for trace in traces:\n            await trace.send_request_exception(\n                method, url.update_query(params), headers, e\n            )\n        raise\n\n\nClientSession._request = _request\n"
  },
  {
    "path": "src/pyodide/internal/patches/httpx.py",
    "content": "\"\"\"A patch to make async httpx work using JavaScript fetch.\"\"\"\n\nfrom contextlib import contextmanager\n\nfrom httpx._client import AsyncClient, BoundAsyncStream, logger\nfrom httpx._models import Headers, Request, Response\nfrom httpx._transports.default import AsyncResponseStream\nfrom httpx._types import AsyncByteStream\nfrom httpx._utils import Timer\nfrom js import Headers as js_Headers\nfrom js import fetch\n\nfrom pyodide.ffi import create_proxy\n\n\n@contextmanager\ndef acquire_buffer(content):\n    \"\"\"Acquire a Uint8Array view of a bytes object\"\"\"\n    if not content:\n        yield None\n        return\n    body_px = create_proxy(content)\n    body_buf = body_px.getBuffer(\"u8\")\n    try:\n        yield body_buf.data\n    finally:\n        body_px.destroy()\n        body_buf.release()\n\n\nasync def js_readable_stream_iter(js_readable_stream):\n    \"\"\"Readable streams are supposed to be async iterators some day but they\n    aren't yet. In the meantime, this is an adaptor that produces an async\n    iterator from a readable stream.\n    \"\"\"\n    reader = js_readable_stream.getReader()\n    while True:\n        res = await reader.read()\n        if res.done:\n            return\n        b = res.value.to_bytes()\n        print(\"js_readable_stream_iter\", b)\n        yield b\n\n\nasync def _send_single_request(self, request: Request) -> Response:\n    \"\"\"\n    Sends a single request, without handling any redirections.\n\n    This is the function we're patching here...\n    \"\"\"\n    timer = Timer()\n    await timer.async_start()\n\n    if not isinstance(request.stream, AsyncByteStream):\n        raise TypeError(\n            \"Attempted to send an sync request with an AsyncClient instance.\"\n        )\n\n    # BEGIN MODIFIED PART\n    js_headers = js_Headers.new(request.headers.multi_items())\n    with acquire_buffer(request.content) as body:\n        js_resp = await fetch(\n            str(request.url), method=request.method, headers=js_headers, body=body\n        )\n\n    py_headers = Headers(js_resp.headers)\n    # Unset content-encoding b/c Javascript fetch already handled unpacking. If\n    # we leave it we will get errors when httpx tries to unpack a second time.\n    py_headers.pop(\"content-encoding\", None)\n    response = Response(\n        status_code=js_resp.status,\n        headers=py_headers,\n        stream=AsyncResponseStream(js_readable_stream_iter(js_resp.body)),\n    )\n    # END MODIFIED PART\n\n    assert isinstance(response.stream, AsyncByteStream)\n    response.request = request\n    response.stream = BoundAsyncStream(response.stream, response=response, timer=timer)\n    self.cookies.extract_cookies(response)\n    response.default_encoding = self._default_encoding\n\n    logger.info(\n        'HTTP Request: %s %s \"%s %d %s\"',\n        request.method,\n        request.url,\n        response.http_version,\n        response.status_code,\n        response.reason_phrase,\n    )\n\n    return response\n\n\nAsyncClient._send_single_request = _send_single_request\n"
  },
  {
    "path": "src/pyodide/internal/pool/builtin_wrappers.ts",
    "content": "import type { getRandomValues as getRandomValuesType } from 'pyodide-internal:topLevelEntropy/lib';\nimport type { default as UnsafeEvalType } from 'internal:unsafe-eval';\nimport { PythonWorkersInternalError } from 'pyodide-internal:util';\nimport { PyodideVersion } from 'pyodide-internal:const';\n\nif (typeof FinalizationRegistry === 'undefined') {\n  // @ts-expect-error cannot assign to globalThis\n  globalThis.FinalizationRegistry = class FinalizationRegistry {\n    register(): void {}\n    unregister(): void {}\n  };\n}\n\n// Pyodide uses `new URL(some_url, location)` to resolve the path in `loadPackage`. Setting\n// `location = undefined` makes this throw an error if some_url is not an absolute url. Which is what\n// we want here, it doesn't make sense to load a package from a relative URL.\nexport const location = undefined;\n\nexport function addEventListener(): void {}\n\n// Mostly we use the `jsglobals` variable for everything except for:\n// * Pyodide's scheduler.ts\n// * Emscripten's implementation of syscalls etc\n//\n// These locations use the globals directly so they will get them from the pool isolate unless we\n// modify globalThis to include setTimeout etc from the main isolate.\n// We could just change scheduler.ts to use jsglobals but it won't fix Emscripten so we'll need to\n// do this either way.\nexport function setSetTimeout(\n  st: typeof setTimeout,\n  ct: typeof clearTimeout,\n  si: typeof setInterval,\n  ci: typeof clearInterval\n): void {\n  globalThis.setTimeout = st;\n  globalThis.clearTimeout = ct;\n  globalThis.setInterval = si;\n  globalThis.clearInterval = ci;\n}\n\nexport function reportUndefinedSymbolsPatched(Module: Module): void {\n  if (Module.API.version === PyodideVersion.V0_26_0a2) {\n    return;\n  }\n  Module.reportUndefinedSymbols();\n}\n\nfunction dynlibLookup026Helper(\n  Module: Module,\n  path: string\n): string | undefined {\n  try {\n    Module.FS.lookupPath(path);\n  } catch (e) {\n    return undefined;\n  }\n  return path;\n}\n\nfunction dynlibLookup026(Module: Module, libName: string): string {\n  // This function is for 0.26.0a2 only. In newer versions, we set LD_LIBRARY_PATH instead.\n  if (Module.API.version !== PyodideVersion.V0_26_0a2) {\n    throw new PythonWorkersInternalError('Should not happen');\n  }\n  // Most libraries are loaded from /usr/lib. For scipy and similar libraries that depend on\n  // Pyodide's dynamic library deps, we may need extra \"system libraries\". These we'll put in\n  // python_modules/lib. So try loading system libraries from there too.\n  const result =\n    dynlibLookup026Helper(Module, '/usr/lib/' + libName) ??\n    dynlibLookup026Helper(\n      Module,\n      '/session/metadata/python_modules/lib/' + libName\n    );\n  if (!result) {\n    console.error('Failed to read ', libName);\n    throw new PythonWorkersInternalError('Should not happen');\n  }\n  return result;\n}\n\nexport function patchedLoadLibData(\n  Module: Module,\n  path: string,\n  rpath: any\n): WebAssembly.Module {\n  if (!path.startsWith('/')) {\n    if (Module.API.version === PyodideVersion.V0_26_0a2) {\n      path = dynlibLookup026(Module, path);\n    } else {\n      path = Module.findLibraryFS(path, rpath);\n    }\n  }\n  return Module.compileModuleFromReadOnlyFS(Module, path);\n}\n\nexport function patchedApplyFunc(\n  API: API,\n  func: (...params: any[]) => any,\n  this_: object,\n  args: any[]\n): any {\n  return API.config.jsglobals.Function.prototype.apply.apply(func, [\n    this_,\n    args,\n  ]);\n}\n\nlet getRandomValuesInner: typeof getRandomValuesType;\nexport function setGetRandomValues(func: typeof getRandomValuesType): void {\n  getRandomValuesInner = func;\n}\n\nexport function getRandomValues(Module: Module, arr: Uint8Array): Uint8Array {\n  return getRandomValuesInner(Module, arr);\n}\n\n// We can't import UnsafeEval directly here because it isn't available when setting up Python pool.\n// Thus, we inject it from outside via this function.\nlet UnsafeEval: typeof UnsafeEvalType;\nexport function setUnsafeEval(mod: typeof UnsafeEvalType): void {\n  UnsafeEval = mod;\n}\n\nlet lastTime: number;\nlet lastDelta = 0;\n/**\n * Wrapper for Date.now that always advances by at least a millisecond. So that\n * directories change their modification time when updated so that Python\n * doesn't use stale directory contents in its import system.\n */\nexport function monotonicDateNow(): number {\n  const now = Date.now();\n  if (now === lastTime) {\n    lastDelta++;\n  } else {\n    lastTime = now;\n    lastDelta = 0;\n  }\n  return now + lastDelta;\n}\n\n/**\n * First check that the callee is what we expect, then use `UnsafeEval` to\n * construct a `WasmModule`.\n *\n * What we expect of the callee is that:\n * 1. it's in pyodide.asm.js\n * 2. it's in one of the locations that are required for it to work. We can\n *    pretty easily make a whitelist of these.\n *\n * In particular, we specifically don't want to allow calls from places that\n * call arbitrary functions for the user like `JsvFunction_CallBound` or\n * `raw_call_js`; if a user somehow gets their hands on a reference to\n * `newWasmModule` and tries to call it from Python the call would come from one\n * of these places. Currently we only need to allow `convertJsFunctionToWasm`\n * but if we enable JSPI we'll need to whitelist a few more locations.\n *\n * Some remarks:\n * 1. I don't really think that this `builtin_wrappers.newWasmModule` function\n *    can leak from `pyodide.asm.js`, but the code for `pyodide.asm.js` is\n *    generated and so difficult to analyze. I think the correct thing to do\n *    from a security analysis perspective is to assume that unreviewed\n *    generated code leaks all permissions it receives.\n * 2. Assuming user code somehow gets direct access to\n *    `builtin_wrappers.newWasmModule` I don't think it can spoof a call that\n *    passes this check.\n * 3. In normal Python code, this will only be called a fixed number of times\n *    every time we load a .so file. If we ever get to the position where\n *    `checkCallee` is a performance bottleneck, that would be a great success.\n *    Using ctypes, one can arrange to call a lot more times by repeatedly\n *    allocating and discarding closures. But:\n *      - ctypes is quite slow even by Python's standards\n *      - Normally ctypes allocates all closures up front\n */\nlet finishedSetup = false;\nexport function finishSetup(): void {\n  finishedSetup = true;\n}\n\nexport function newWasmModule(buffer: Uint8Array): WebAssembly.Module {\n  if (!UnsafeEval) {\n    return new WebAssembly.Module(buffer);\n  }\n  if (finishedSetup) {\n    checkCallee();\n  }\n  return UnsafeEval.newWasmModule(buffer);\n}\n\nexport function wasmInstantiate(\n  mod: WebAssembly.Module | Uint8Array,\n  imports: WebAssembly.Imports\n): Promise<{ module: WebAssembly.Module; instance: WebAssembly.Instance }> {\n  let module;\n  if (mod instanceof WebAssembly.Module) {\n    module = mod;\n  } else {\n    if (finishedSetup) {\n      checkCallee();\n    }\n    module = UnsafeEval.newWasmModule(mod);\n  }\n  const instance = new WebAssembly.Instance(module, imports);\n  return Promise.resolve({ module, instance });\n}\n\n/**\n * Check that the callee is `convertJsFunctionToWasm` by formatting a stack\n * trace and using `prepareStackTrace` to read out the callee. It should be\n * `convertJsFunctionToWasm` in `\"pyodide-internal:generated/pyodide.asm\"`,\n * if it's anything else we'll bail.\n */\nfunction checkCallee(): void {\n  const origPrepareStackTrace = Error.prepareStackTrace;\n  let isOkay, funcName;\n  try {\n    Error.prepareStackTrace = prepareStackTrace;\n    [isOkay, funcName] = new Error().stack as unknown as ReturnType<\n      typeof prepareStackTrace\n    >;\n  } finally {\n    Error.prepareStackTrace = origPrepareStackTrace;\n  }\n  if (!isOkay) {\n    console.warn('Invalid call to `WebAssembly.Module`', funcName);\n    throw new PythonWorkersInternalError(\n      'Invalid call to `WebAssembly.Module`'\n    );\n  }\n}\n\n/**\n * Helper function for checkCallee, returns `true` if the callee is `convertJsFunctionToWasm`,\n * `generate`, or `getPyEMCountArgsPtr` in `pyodide.asm.js`, `false` if not. This will set the\n * `stack` field in the error so we can read back the result there.\n */\nfunction prepareStackTrace(\n  _error: Error,\n  stack: StackItem[]\n): [boolean, string] {\n  // In case a logic error is ever introduced in this function, defend against\n  // reentrant calls by setting `prepareStackTrace` to `undefined`.\n  Error.prepareStackTrace = undefined;\n  // Counting up, the bottom of the stack is `checkCallee`, then\n  // `newWasmModule`, and the third entry should be our callee.\n  if (stack.length < 3) {\n    return [false, ''];\n  }\n  try {\n    const funcName = stack[2].getFunctionName();\n    const fileName = stack[2].getFileName();\n    if (fileName !== 'pyodide-internal:generated/emscriptenSetup') {\n      return [false, funcName];\n    }\n    return [\n      ['convertJsFunctionToWasm', 'generate', 'getPyEMCountArgsPtr'].includes(\n        funcName\n      ),\n      funcName,\n    ];\n  } catch (e) {\n    console.warn(e);\n    return [false, ''];\n  }\n}\n\n/**\n * This is a fix for a problem with package snapshots in 0.26.0a2. 0.26.0a2 tests if\n * wasm-type-reflection is supported by the runtime and if so uses it to avoid function pointer\n * casting instead of a JS trampoline. We cannot stack switch through the JS trampoline so we need\n * to make sure that when stack switching is available, we don't use JS trampolines. When 0.26.0a2\n * was released, wasm-stack-switching implied wasm-type-reflection.\n *\n * Unfortunately there is a bug (fixed in 0.26.0a3...) that made the assumption that if the runtime\n * that we use to make a snapshot supports wasm-type-reflection, the runtime we use it in will too.\n *\n * Later, JSPI was rewritten not to depend on wasm-type-reflection, but the implication was left in\n * the v8 codebase until a few weeks ago. So our snapshots expect to be able to use an implementation\n * of `patched_PyEM_CountFuncParams` based on wasm-type-reflection, but it's not on anymore.\n *\n * Luckily, in the meantime there is a way to count the arguments of a webassembly function using\n * wasm-gc. It's not exactly pretty though.\n * This is copied from https://github.com/python/cpython/blob/main/Python/emscripten_trampoline.c\n */\n// prettier-ignore\nfunction getCountFuncParams(Module: Module): (funcPtr: number) => number {\n  const code = new Uint8Array([\n    0x00, 0x61, 0x73, 0x6d, // \\0asm magic number\n    0x01, 0x00, 0x00, 0x00, // version 1\n    0x01, 0x1b, // Type section, body is 0x1b bytes\n        0x05, // 6 entries\n        0x60, 0x00, 0x01, 0x7f,                         // (type $type0 (func (param) (result i32)))\n        0x60, 0x01, 0x7f, 0x01, 0x7f,                   // (type $type1 (func (param i32) (result i32)))\n        0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f,             // (type $type2 (func (param i32 i32) (result i32)))\n        0x60, 0x03, 0x7f, 0x7f, 0x7f, 0x01, 0x7f,       // (type $type3 (func (param i32 i32 i32) (result i32)))\n        0x60, 0x01, 0x7f, 0x00,                         // (type $blocktype (func (param i32) (result)))\n    0x02, 0x09, // Import section, 0x9 byte body\n        0x01, // 1 import (table $funcs (import \"e\" \"t\") 0 funcref)\n        0x01, 0x65, // \"e\"\n        0x01, 0x74, // \"t\"\n        0x01,       // importing a table\n        0x70,       // of entry type funcref\n        0x00, 0x00, // table limits: no max, min of 0\n    0x03, 0x02,   // Function section\n        0x01, 0x01, // We're going to define one function of type 1 (func (param i32) (result i32))\n    0x07, 0x05, // export section\n        0x01, // 1 export\n        0x01, 0x66, // called \"f\"\n        0x00, // a function\n        0x00, // at index 0\n\n    0x0a, 0x44,  // Code section,\n        0x01, 0x42, // one entry of length 50\n        0x01, 0x01, 0x70, // one local of type funcref\n        // Body of the function\n        0x20, 0x00,       // local.get $fptr\n        0x25, 0x00,       // table.get $funcs\n        0x22, 0x01,       // local.tee $fref\n        0xfb, 0x14, 0x03, // ref.test $type3\n        0x02, 0x04,       // block $b (type $blocktype)\n            0x45,         //   i32.eqz\n            0x0d, 0x00,   //   br_if $b\n            0x41, 0x03,   //   i32.const 3\n            0x0f,         //   return\n        0x0b,             // end block\n\n        0x20, 0x01,       // local.get $fref\n        0xfb, 0x14, 0x02, // ref.test $type2\n        0x02, 0x04,       // block $b (type $blocktype)\n            0x45,         //   i32.eqz\n            0x0d, 0x00,   //   br_if $b\n            0x41, 0x02,   //   i32.const 2\n            0x0f,         //   return\n        0x0b,             // end block\n\n        0x20, 0x01,       // local.get $fref\n        0xfb, 0x14, 0x01, // ref.test $type1\n        0x02, 0x04,       // block $b (type $blocktype)\n            0x45,         //   i32.eqz\n            0x0d, 0x00,   //   br_if $b\n            0x41, 0x01,   //   i32.const 1\n            0x0f,         //   return\n        0x0b,             // end block\n\n        0x20, 0x01,       // local.get $fref\n        0xfb, 0x14, 0x00, // ref.test $type0\n        0x02, 0x04,       // block $b (type $blocktype)\n            0x45,         //   i32.eqz\n            0x0d, 0x00,   //   br_if $b\n            0x41, 0x00,   //   i32.const 0\n            0x0f,         //   return\n        0x0b,             // end block\n\n        0x41, 0x7f,       // i32.const -1\n        0x0b // end function\n  ]);\n  const mod = UnsafeEval.newWasmModule(code);\n  const inst = new WebAssembly.Instance(mod, { e: { t: Module.wasmTable } });\n  return inst.exports.f as ReturnType<typeof getCountFuncParams>;\n}\n\nlet countFuncParams: (funcPtr: number) => number;\n\nexport function patched_PyEM_CountFuncParams(Module: Module, funcPtr: any) {\n  countFuncParams ??= getCountFuncParams(Module);\n  return countFuncParams(funcPtr);\n}\n"
  },
  {
    "path": "src/pyodide/internal/pool/emscriptenSetup.ts",
    "content": "/**\n * This file is intended to be executed in the Python pool (once it exists). As such, it cannot\n * import anything that transitively uses C++ extension modules. It has to work in a vanilla v8\n * isolate. Also, we will have to bundle this file and all of its transitive imports into a single\n * js file.\n */\n\n/**\n * _createPyodideModule and pyodideWasmModule together are produced by the\n * Emscripten linker\n */\nimport { _createPyodideModule } from 'pyodide-internal:generated/pyodide.asm';\n\nimport {\n  setUnsafeEval,\n  setGetRandomValues,\n  setSetTimeout,\n  finishSetup,\n} from 'pyodide-internal:pool/builtin_wrappers';\n\nimport { getSentinelImport } from 'pyodide-internal:pool/sentinel';\n\n/**\n * A preRun hook. Make sure environment variables are visible at runtime.\n */\nfunction setEnv(Module: Module): void {\n  Object.assign(Module.ENV, Module.API.config.env);\n}\n\nfunction getWaitForDynlibs(resolveReadyPromise: PreRunHook): PreRunHook {\n  return function waitForDynlibs(Module: Module): void {\n    // Block the instantiation of the runtime until we can preload the dynamic libraries. The\n    // promise returned by _createPyodideModule won't resolve until we call\n    // `removeRunDependency('dynlibs')` so we use `emscriptenSettings.readyPromise` to continue\n    // execution when we've gotten to this point.\n    Module.addRunDependency('dynlibs');\n    resolveReadyPromise(Module);\n  };\n}\n\nfunction computeVersionTuple(Module: Module): [number, number, number] {\n  if (Module._py_version_major) {\n    const pymajor = Module._py_version_major();\n    const pyminor = Module._py_version_minor();\n    const micro = Module._py_version_micro();\n    return [pymajor, pyminor, micro];\n  }\n  const versionInt = Module.HEAPU32[Module._Py_Version >>> 2];\n  const major = (versionInt >>> 24) & 0xff;\n  const minor = (versionInt >>> 16) & 0xff;\n  const micro = (versionInt >>> 8) & 0xff;\n  return [major, minor, micro];\n}\n\n/**\n * This is passed as a preRun hook in EmscriptenSettings, run just before\n * main(). It ensures that the file system includes the stuff that main() needs,\n * most importantly the Python standard library.\n *\n * Put the Python + Pyodide standard libraries into a zip file in the\n * appropriate location /lib/python311.zip . Python will import stuff directly\n * from this zip file using ZipImporter.\n *\n * ZipImporter is quite useful here -- the Python runtime knows how to unpack a\n * bunch of different archive formats but it is not possible to use these until\n * the runtime state is initialized. So ZipImporter breaks this bootstrapping\n * knot for us.\n *\n * We also make an empty home directory and an empty global site-packages\n * directory `/lib/pythonv.vv/site-packages`.\n *\n * This is a simplified version of the `prepareFileSystem` function here:\n * https://github.com/pyodide/pyodide/blob/main/src/js/module.ts\n */\nfunction getPrepareFileSystem(pythonStdlib: ArrayBuffer): PreRunHook {\n  return function prepareFileSystem(Module: Module): void {\n    Module.API.pyVersionTuple = computeVersionTuple(Module);\n    const [pymajor, pyminor] = Module.API.pyVersionTuple;\n    Module.FS.sitePackages = `/lib/python${pymajor}.${pyminor}/site-packages`;\n    Module.LD_LIBRARY_PATH = [\n      '/usr/lib',\n      Module.FS.sitePackages,\n      '/session/metadata/python_modules/lib/',\n    ].join(':');\n    Module.ENV.LD_LIBRARY_PATH = Module.LD_LIBRARY_PATH;\n    Module.FS.sessionSitePackages = '/session' + Module.FS.sitePackages;\n    Module.FS.mkdirTree(Module.FS.sitePackages);\n    Module.FS.writeFile(\n      `/lib/python${pymajor}${pyminor}.zip`,\n      new Uint8Array(pythonStdlib),\n      { canOwn: true }\n    );\n    Module.FS.mkdirTree(Module.API.config.env.HOME);\n  };\n}\n\n/**\n * A hook that the Emscripten runtime calls to perform the WebAssembly\n * instantiation action. Once instantiated, this callback function should call\n * ``successCallback()`` with the generated WebAssembly Instance object.\n *\n * @param wasmImports a JS object which contains all the function imports that\n * need to be passed to the WebAssembly Module when instantiating\n * @param successCallback A callback to indicate that instantiation was\n * successful,\n * @returns The return value of this function should contain the ``exports`` object of\n * the instantiated WebAssembly Module, or an empty dictionary object ``{}`` if\n * the instantiation is performed asynchronously, or ``false`` if instantiation\n * synchronously failed. There is no way to indicate asynchronous failure.\n */\nfunction getInstantiateWasm(\n  pyodideWasmModule: WebAssembly.Module\n): EmscriptenSettings['instantiateWasm'] {\n  const sentinelImportPromise = getSentinelImport();\n  return function instantiateWasm(\n    wasmImports: WebAssembly.Imports,\n    successCallback: (\n      inst: WebAssembly.Instance,\n      mod: WebAssembly.Module\n    ) => void\n  ): WebAssembly.Exports {\n    (async function (): Promise<void> {\n      wasmImports.sentinel = await sentinelImportPromise;\n      // Instantiate pyodideWasmModule with wasmImports\n      const instance = await WebAssembly.instantiate(\n        pyodideWasmModule,\n        wasmImports\n      );\n      successCallback(instance, pyodideWasmModule);\n    })().catch((e: unknown) => {\n      console.error(\n        'Internal error: wasm instantiation failed. This should never happen.',\n        e\n      );\n      // Execution hangs at this point.\n    });\n\n    return {};\n  };\n}\n\n/**\n * The Emscripten settings object\n *\n * This isn't public API of Pyodide so it's a bit fiddly.\n */\nfunction getEmscriptenSettings(\n  isWorkerd: boolean,\n  pythonStdlib: ArrayBuffer,\n  pyodideWasmModule: WebAssembly.Module\n): EmscriptenSettings {\n  const config: PyodideConfig = {\n    // jsglobals is used for the js module.\n    jsglobals: globalThis,\n    // environment variables go here\n    env: {\n      HOME: '/session',\n      // We don't have access to entropy at startup so we cannot support hash\n      // randomization. Setting `PYTHONHASHSEED` disables it. See further\n      // discussion in topLevelEntropy/entropy_patches.py\n      PYTHONHASHSEED: '111',\n    },\n    lockFileURL: '',\n    enableRunUntilComplete: true,\n  };\n  let lockFilePromise;\n  if (isWorkerd) {\n    lockFilePromise = new Promise(\n      (res) => (config.resolveLockFilePromise = res)\n    );\n  }\n  const API = { config, lockFilePromise };\n  let resolveReadyPromise: (mod: Module) => void;\n  let rejectReadyPromise: (e: any) => void = () => {};\n  const readyPromise: Promise<Module> = new Promise((res, rej) => {\n    resolveReadyPromise = res;\n    rejectReadyPromise = rej;\n  });\n  const waitForDynlibs = getWaitForDynlibs(resolveReadyPromise!);\n  const prepareFileSystem = getPrepareFileSystem(pythonStdlib);\n  const instantiateWasm = getInstantiateWasm(pyodideWasmModule);\n\n  // Emscripten settings to control runtime instantiation.\n  return {\n    // preRun hook to set up the file system before running main\n    // The preRun hook gets run independently of noInitialRun, which is\n    // important because the file system lives outside of linear memory.\n    preRun: [prepareFileSystem, setEnv, waitForDynlibs],\n    instantiateWasm,\n    reportUndefinedSymbolsNoOp(): void {},\n    readyPromise,\n    rejectReadyPromise,\n    API, // Pyodide requires we pass this in.\n  };\n}\n\n/**\n * Force Emscripten to feature detect the way we want.\n * We want it to think we're the browser main thread.\n */\n/* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */\nfunction* featureDetectionMonkeyPatchesContextManager(): Generator<void> {\n  const global = globalThis as any;\n  // Make Emscripten think we're in the browser main thread\n  global.window = { sessionStorage: {} };\n  global.document = { createElement(): void {} };\n  global.sessionStorage = {};\n  // Make Emscripten think we're not in a worker\n  global.importScripts = 1;\n  global.WorkerGlobalScope = undefined;\n  try {\n    yield;\n  } finally {\n    delete global.window;\n    delete global.document;\n    delete global.sessionStorage;\n    delete global.importScripts;\n  }\n}\n/* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */\n\n/**\n * Simple wrapper around _createPyodideModule that applies some monkey patches\n * to force the environment to be detected the way we want.\n *\n * In the long run we should fix this in `pyodide.asm.js` instead.\n *\n * Returns the instantiated emscriptenModule object.\n */\nexport async function instantiateEmscriptenModule(\n  isWorkerd: boolean,\n  pythonStdlib: ArrayBuffer,\n  wasmModule: WebAssembly.Module\n): Promise<Module> {\n  const emscriptenSettings = getEmscriptenSettings(\n    isWorkerd,\n    pythonStdlib,\n    wasmModule\n  );\n  for (const _ of featureDetectionMonkeyPatchesContextManager()) {\n    // Ignore the returned promise, it won't resolve until we're done preloading dynamic\n    // libraries.\n    const _promise = _createPyodideModule(emscriptenSettings).catch((e) =>\n      emscriptenSettings.rejectReadyPromise(e)\n    );\n  }\n\n  // Wait until we've executed all the preRun hooks before proceeding\n  const emscriptenModule = await emscriptenSettings.readyPromise;\n  emscriptenModule.setUnsafeEval = setUnsafeEval;\n  emscriptenModule.setGetRandomValues = setGetRandomValues;\n  emscriptenModule.setSetTimeout = setSetTimeout;\n  finishSetup();\n  return emscriptenModule;\n}\n"
  },
  {
    "path": "src/pyodide/internal/pool/esbuild.config.mjs",
    "content": "import { dirname, join } from 'node:path';\nimport { existsSync, readdirSync } from 'node:fs';\nimport { fileURLToPath } from 'url';\n\nconst pyodideRootDir = dirname(\n  dirname(dirname(fileURLToPath(import.meta.url)))\n);\n\nlet resolvePlugin = {\n  name: 'pyodide-internal',\n  setup(build) {\n    // Redirect all paths starting with \"images/\" to \"./public/images/\"\n    build.onResolve({ filter: /pyodide-internal:.*/ }, (args) => {\n      let rest = args.path.split(':')[1];\n      let path;\n      if (rest.startsWith('generated')) {\n        // I couldn't figure out how to pass down the version, so instead we'll look through the\n        // directories in `pyodideRootDir` and find one that starts with a 0. This will work until\n        // Pyodide has a 1.0 release.\n        const dir = readdirSync(pyodideRootDir).filter((x) =>\n          x.startsWith('0')\n        )[0];\n        path = join(pyodideRootDir, dir, rest);\n        if (!existsSync(path)) {\n          path += '.js';\n        }\n      } else {\n        path = join(pyodideRootDir, 'internal', rest);\n        if (!existsSync(path)) {\n          path += '.ts';\n        }\n      }\n      return { path };\n    });\n  },\n};\n\nexport default {\n  plugins: [resolvePlugin],\n  logOverride: {\n    // Suppress warning about CommonJS 'module' variable in pyodide.asm.js (ES module context)\n    // The pyodide.asm.js file contains embedded libraries (like MiniLZ4) that use CommonJS\n    // style module.exports checks for compatibility. These are safe to ignore as they won't\n    // execute in our ES module context.\n    'commonjs-variable-in-esm': 'silent',\n  },\n};\n"
  },
  {
    "path": "src/pyodide/internal/pool/sentinel.ts",
    "content": "// This is https://github.com/pyodide/pyodide/blob/main/src/core/sentinel.ts.\n// It goes into `pyodide.js` not `pyodide.asm.js`. We don't use `pyodide.js` so we have to reproduce\n// it here.\n\n// Per https://github.com/tc39/proposal-arraybuffer-base64/issues/51#issuecomment-3010378969, we\n// should be able to replace `decodeBase64()` with `Uint8Array.fromBase64()` once we update to a v8\n// released after September 2nd.\nconst base64Lookup = new Uint8Array(128);\nfor (let t = 0; t < 64; t++) {\n  let idx;\n  if (t < 26) {\n    idx = t + 65;\n  } else if (t < 52) {\n    idx = t + 71;\n  } else if (t < 62) {\n    idx = t - 4;\n  } else {\n    idx = t * 4 - 205;\n  }\n  base64Lookup[idx] = t;\n}\n\nfunction decodeBase64(input: string): Uint8Array {\n  const outputSize = ((input.length * 3) / 4) | 0;\n  const output = new Uint8Array(outputSize);\n  let inIdx = 0;\n  let outIdx = 0;\n  while (inIdx < input.length) {\n    const chunk1 = base64Lookup[input.charCodeAt(inIdx++)]!;\n    const chunk2 = base64Lookup[input.charCodeAt(inIdx++)]!;\n    const chunk3 = base64Lookup[input.charCodeAt(inIdx++)]!;\n    const chunk4 = base64Lookup[input.charCodeAt(inIdx++)]!;\n    output[outIdx++] = (chunk1 << 2) | (chunk2 >> 4);\n    output[outIdx++] = (chunk2 << 4) | (chunk3 >> 2);\n    output[outIdx++] = (chunk3 << 6) | chunk4;\n  }\n  return output;\n}\n\n// This string is https://github.com/pyodide/pyodide/blob/main/src/core/sentinel.wat assembled and\n// hex encoded.\nconst sentinelWasm = decodeBase64(\n  'AGFzbQEAAAABDANfAGAAAW9gAW8BfwMDAgECByECD2NyZWF0ZV9zZW50aW5lbAAAC2lzX3NlbnRpbmVsAAEKEwIHAPsBAPsbCwkAIAD7GvsUAAs'\n);\n\nexport async function getSentinelImport() {\n  const module: WebAssembly.Module = new WebAssembly.Module(sentinelWasm);\n  const instance = await WebAssembly.instantiate(module);\n  return instance.exports;\n}\n"
  },
  {
    "path": "src/pyodide/internal/python.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { enterJaegerSpan } from 'pyodide-internal:jaeger';\nimport {\n  adjustSysPath,\n  mountWorkerFiles,\n} from 'pyodide-internal:setupPackages';\nimport {\n  maybeCollectSnapshot,\n  maybeRestoreSnapshot,\n  finalizeBootstrap,\n  isRestoringSnapshot,\n  type CustomSerializedObjects,\n} from 'pyodide-internal:snapshot';\nimport {\n  entropyMountFiles,\n  entropyAfterRuntimeInit,\n  entropyBeforeTopLevel,\n  getRandomValues,\n  entropyBeforeRequest,\n} from 'pyodide-internal:topLevelEntropy/lib';\nimport {\n  LEGACY_VENDOR_PATH,\n  setCpuLimitNearlyExceededCallback,\n} from 'pyodide-internal:metadata';\nimport { default as FatalReporter } from 'pyodide-internal:fatal-reporter';\n\n/**\n * SetupEmscripten is an internal module defined in setup-emscripten.h the module instantiates\n * emscripten seperately from this code in another context.\n * The underlying code for it can be found in pool/emscriptenSetup.ts.\n */\nimport { default as SetupEmscripten } from 'internal:setup-emscripten';\n\nimport { default as UnsafeEval } from 'internal:unsafe-eval';\nimport {\n  PythonUserError,\n  PythonWorkersInternalError,\n  reportError,\n  unreachable,\n} from 'pyodide-internal:util';\nimport { loadPackages } from 'pyodide-internal:loadPackage';\nimport { default as MetadataReader } from 'pyodide-internal:runtime-generated/metadata';\nimport { TRANSITIVE_REQUIREMENTS } from 'pyodide-internal:metadata';\nimport { getTrustedReadFunc } from 'pyodide-internal:readOnlyFS';\nimport { PyodideVersion } from 'pyodide-internal:const';\n\n/**\n * After running `instantiateEmscriptenModule` but before calling into any C\n * APIs, we call this function. If `MEMORY` is defined, then we will have passed\n * `noInitialRun: true` and so the C runtime is in an incoherent state until we\n * restore the linear memory from the snapshot.\n */\nfunction prepareWasmLinearMemory(\n  Module: Module,\n  customSerializedObjects: CustomSerializedObjects\n): void {\n  maybeRestoreSnapshot(Module);\n  // entropyAfterRuntimeInit adjusts JS state ==> always needs to be called.\n  entropyAfterRuntimeInit(Module);\n  if (!isRestoringSnapshot()) {\n    // The effects of these are purely in Python state so they only need to be run\n    // if we didn't restore a snapshot.\n    entropyBeforeTopLevel(Module);\n    // Note that setupPythonSearchPath runs after adjustSysPath and rearranges where\n    // the /session/metadata path is added.\n    adjustSysPath(Module);\n  }\n  if (Module.API.version !== PyodideVersion.V0_26_0a2) {\n    finalizeBootstrap(Module, customSerializedObjects);\n  }\n}\n\nfunction setupPythonSearchPath(pyodide: Pyodide): void {\n  pyodide.runPython(`\n    def _tmp():\n      import sys\n      from pathlib import Path\n\n      LEGACY_VENDOR_PATH = \"${LEGACY_VENDOR_PATH}\" == \"true\"\n      VENDOR_PATH = \"/session/metadata/vendor\"\n      PYTHON_MODULES_PATH = \"/session/metadata/python_modules\"\n\n      # adjustSysPath adds the session path, but it is immortalised by the memory snapshot. This\n      # code runs irrespective of the memory snapshot.\n      if VENDOR_PATH in sys.path and LEGACY_VENDOR_PATH:\n        sys.path.remove(VENDOR_PATH)\n\n      if PYTHON_MODULES_PATH in sys.path:\n        sys.path.remove(PYTHON_MODULES_PATH)\n\n      # Insert vendor path after system paths but before site-packages\n      # System paths are typically: ['/session', '/lib/python312.zip', '/lib/python3.12', '/lib/python3.12/lib-dynload']\n      # We want to insert before '/lib/python3.12/site-packages' and other site-packages\n      #\n      # We also need the session path to be before the vendor path, if we don't do so then a local\n      # import will pick a module from the vendor path rather than the local path. We've got a test\n      # that reproduces this (vendor_dir).\n      for i, path in enumerate(sys.path):\n        if 'site-packages' in path:\n          if LEGACY_VENDOR_PATH:\n            sys.path.insert(i, VENDOR_PATH)\n          sys.path.insert(i, PYTHON_MODULES_PATH)\n          break\n      else:\n        # If no site-packages found, fail\n        raise ValueError(\"No site-packages found in sys.path\")\n\n    _tmp()\n    del _tmp\n  `);\n}\n\n/**\n * Verifies that the Pyodide version in our compat flag matches our actual version. This is to\n * prevent us accidentally releasing a Pyodide bundle built against a different version than one\n * we expect.\n */\nfunction validatePyodideVersion(pyodide: Pyodide): void {\n  const expectedPyodideVersion = MetadataReader.getPyodideVersion();\n  if (expectedPyodideVersion == 'dev') {\n    return;\n  }\n  if (pyodide.version !== expectedPyodideVersion) {\n    throw new PythonWorkersInternalError(\n      `Pyodide version mismatch, expected '${expectedPyodideVersion}'`\n    );\n  }\n}\n\nconst origSetTimeout = globalThis.setTimeout.bind(this);\n\nfunction makeSetTimeout(Module: Module): typeof setTimeout {\n  return function setTimeoutTopLevelPatch(\n    handler: () => void,\n    timeout: number | undefined\n  ): number {\n    // Redirect top level setTimeout(cb, 0) to queueMicrotask().\n    // If we don't know how to handle it, call normal setTimeout() to force failure.\n    if (typeof handler === 'string') {\n      return origSetTimeout(handler, timeout);\n    }\n    function wrappedHandler(): void {\n      // In case an Exceeded CPU occurred just as Python was exiting, there may be one waiting that\n      // will interrupt the wrong task. Clear signals before entering the task.\n      // This is covered by cpu-limit-exceeded.ew-test \"async_trip\" test.\n      clearSignals(Module);\n      handler();\n    }\n    if (timeout) {\n      return origSetTimeout(wrappedHandler, timeout);\n    }\n    queueMicrotask(wrappedHandler);\n    return 0;\n  } as typeof setTimeout;\n}\n\nfunction getSignalClockAddr(Module: Module): number {\n  if (Module.API.version !== PyodideVersion.V0_28_2) {\n    throw new PythonWorkersInternalError(\n      'getSignalClockAddr only supported in 0.28.2'\n    );\n  }\n  // This is the address here:\n  // https://github.com/python/cpython/blob/main/Python/emscripten_signal.c#L42\n  //\n  // Since the symbol isn't exported, we can't access it directly. Instead, we used wasm-objdump and\n  // searched for the call site to _Py_CheckEmscriptenSignals_Helper(), then read the offset out of\n  // the assembly code.\n  //\n  // TODO: Export this symbol in the next Pyodide release so we can stop using the magic number.\n  const emscripten_signal_clock_offset = 3171536;\n  return Module.___memory_base.value + emscripten_signal_clock_offset;\n}\n\nfunction setupRuntimeSignalHandling(Module: Module): void {\n  Module.Py_EmscriptenSignalBuffer = new Uint8Array(1);\n  const version = Module.API.version;\n  if (version === PyodideVersion.V0_26_0a2) {\n    return;\n  }\n  if (version === PyodideVersion.V0_28_2) {\n    // The callback sets signal_clock to 0 and signal_handling to 1. It has to be in C++ because we\n    // don't hold the isolate lock when we call it. JS code would be:\n    //\n    // function callback() { Module.HEAP8[getSignalClockAddr(Module)] = 0;\n    //    Module.HEAP8[Module._Py_EMSCRIPTEN_SIGNAL_HANDLING] = 1;\n    // }\n    setCpuLimitNearlyExceededCallback(\n      Module.HEAP8,\n      getSignalClockAddr(Module),\n      Module._Py_EMSCRIPTEN_SIGNAL_HANDLING\n    );\n    return;\n  }\n  unreachable(version);\n}\n\nconst SIGXCPU = 24;\n\nexport function clearSignals(Module: Module): void {\n  if (Module.API.version === PyodideVersion.V0_28_2) {\n    // In case the previous request was aborted, make sure that:\n    // 1. a sigint is waiting in the signal buffer\n    // 2. signal handling is off\n    //\n    // We will turn signal handling on as part of triggering the interrupt, having it on otherwise\n    // just wastes cycles.\n    Module.Py_EmscriptenSignalBuffer[0] = SIGXCPU;\n    Module.HEAPU32[getSignalClockAddr(Module) / 4] = 1;\n    Module.HEAPU32[Module._Py_EMSCRIPTEN_SIGNAL_HANDLING / 4] = 0;\n  }\n}\n\nfunction compileModuleFromReadOnlyFS(\n  Module: Module,\n  path: string\n): WebAssembly.Module {\n  const { node } = Module.FS.lookupPath(path);\n  // Get the trusted read function from our private Map, not from the node\n  // or filesystem object (which could have been tampered with by user code)\n  const trustedRead = getTrustedReadFunc(node);\n  if (!trustedRead) {\n    throw new PythonUserError(\n      'Can only load shared libraries from read only file systems.'\n    );\n  }\n  const stat = node.node_ops.getattr(node);\n  const buffer = new Uint8Array(stat.size);\n  // Create a minimal stream object and read using trusted read function\n  const stream = { node, position: 0 };\n  trustedRead(stream, buffer, 0, stat.size, 0);\n  return UnsafeEval.newWasmModule(buffer);\n}\n\nexport function loadPyodide(\n  isWorkerd: boolean,\n  lockfile: PackageLock,\n  indexURL: string,\n  customSerializedObjects: CustomSerializedObjects\n): Pyodide {\n  try {\n    const Module = enterJaegerSpan('instantiate_emscripten', () =>\n      SetupEmscripten.getModule()\n    );\n    Module.compileModuleFromReadOnlyFS = compileModuleFromReadOnlyFS;\n    Module.API.config.jsglobals = globalThis;\n    if (isWorkerd) {\n      Module.API.config.indexURL = indexURL;\n      Module.API.config.resolveLockFilePromise!(lockfile);\n    }\n    Module.setUnsafeEval(UnsafeEval);\n    Module.setGetRandomValues(getRandomValues);\n    Module.setSetTimeout(\n      makeSetTimeout(Module),\n      clearTimeout,\n      setInterval,\n      clearInterval\n    );\n\n    entropyMountFiles(Module);\n    enterJaegerSpan('load_packages', () => {\n      // NB. loadPackages adds the packages to the `VIRTUALIZED_DIR` global which then gets used in\n      // preloadDynamicLibs.\n      loadPackages(Module, TRANSITIVE_REQUIREMENTS);\n    });\n\n    enterJaegerSpan('prepare_wasm_linear_memory', () => {\n      prepareWasmLinearMemory(Module, customSerializedObjects);\n    });\n\n    maybeCollectSnapshot(Module, customSerializedObjects);\n    // Mount worker files after doing snapshot upload so we ensure that data from the files is never\n    // present in snapshot memory.\n    mountWorkerFiles(Module);\n\n    if (Module.API.version === PyodideVersion.V0_26_0a2) {\n      // Finish setting up Pyodide's ffi so we can use the nice Python interface\n      // In newer versions we already did this in prepareWasmLinearMemory.\n      finalizeBootstrap(Module, customSerializedObjects);\n    }\n    const pyodide = Module.API.public_api;\n\n    validatePyodideVersion(pyodide);\n\n    // Need to set these here so that the logs go to the right context. If we don't they will go\n    // to SetupEmscripten's context and end up being KJ_LOG'd, which we do not want.\n    Module.API.initializeStreams(\n      null,\n      (msg) => {\n        console.log(msg);\n      },\n      (msg) => {\n        console.error(msg);\n      }\n    );\n    setupPythonSearchPath(pyodide);\n    setupRuntimeSignalHandling(Module);\n    Module.API.on_fatal = (error: unknown): void => {\n      try {\n        FatalReporter.reportFatal(String(error));\n      } catch (_e) {\n        FatalReporter.reportFatal('Internal error reporting fatal error');\n      }\n    };\n    return pyodide;\n  } catch (e) {\n    // In edgeworker test suite, without this we get the file name and line number of the exception\n    // but no traceback. This gives us a full traceback.\n    reportError(e as Error);\n  }\n}\n\nexport function beforeRequest(Module: Module): void {\n  entropyBeforeRequest(Module);\n  Module.setSetTimeout(setTimeout, clearTimeout, setInterval, clearInterval);\n}\n"
  },
  {
    "path": "src/pyodide/internal/readOnlyFS.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ntype ReadFn<Info> = FSStreamOps<Info>['read'];\n\n// When we load shared libraries we need to ensure they come from a read only file system.\n\n// Map to store the original trusted read function for each read-only filesystem. We store the\n// function itself to prevent attacks where user code modifies stream_ops.read after filesystem\n// creation and tricks us into loading a dynamically generated so file.\nconst TRUSTED_READ_FUNCS: Map<object, ReadFn<any>> = new Map();\n\nexport function getTrustedReadFunc<Info>(\n  node: FSNode<Info>\n): ReadFn<Info> | undefined {\n  return TRUSTED_READ_FUNCS.get(node.mount.type);\n}\n\nexport function createReadonlyFS<Info>(\n  FSOps: FSOps<Info>,\n  Module: Module\n): EmscriptenFS<Info> {\n  const FS = Module.FS;\n  const ReadOnlyFS: EmscriptenFS<Info> = {\n    mount(mount) {\n      return ReadOnlyFS.createNode(null, '/', mount.opts.info);\n    },\n    createNode(parent, name, info): FSNode<Info> {\n      // eslint-disable-next-line prefer-const\n      let { permissions: mode, isDir } = FSOps.getNodeMode(parent, name, info);\n      if (isDir) {\n        mode |= 1 << 14; // set S_IFDIR\n      } else {\n        mode |= 1 << 15; // set S_IFREG\n      }\n      const node = FS.createNode(parent, name, mode);\n      node.node_ops = ReadOnlyFS.node_ops;\n      node.stream_ops = ReadOnlyFS.stream_ops;\n      FSOps.setNodeAttributes(node, info, isDir);\n      return node;\n    },\n    node_ops: {\n      getattr(node) {\n        const size = node.usedBytes;\n        const mode = node.mode;\n        const t = new Date(node.modtime);\n        const blksize = 4096;\n        const blocks = ((size + blksize - 1) / blksize) | 0;\n        return {\n          dev: 1,\n          ino: node.id,\n          mode,\n          nlink: 1,\n          uid: 0,\n          gid: 0,\n          rdev: 0,\n          size,\n          atime: t,\n          mtime: t,\n          ctime: t,\n          blksize,\n          blocks,\n        };\n      },\n      readdir(node) {\n        return FSOps.readdir(node);\n      },\n      lookup(parent, name) {\n        const child = FSOps.lookup(parent, name);\n        if (child === undefined) {\n          throw FS.genericErrors?.[44] ?? new FS.ErrnoError(44); // ENOENT\n        }\n        return ReadOnlyFS.createNode(parent, name, child);\n      },\n    },\n    stream_ops: {\n      llseek(stream, offset, whence) {\n        let position = offset;\n        if (whence === 1) {\n          // SEEK_CUR\n          position += stream.position;\n        } else if (whence === 2) {\n          // SEEK_END\n          if (FS.isFile(stream.node.mode)) {\n            position += stream.node.usedBytes;\n          }\n        }\n        return position;\n      },\n      read(stream, buffer, offset, length, position) {\n        if (position >= stream.node.usedBytes) return 0;\n        const size = Math.min(stream.node.usedBytes - position, length);\n        buffer = buffer.subarray(offset, offset + size);\n        return FSOps.read(stream, position, buffer);\n      },\n    },\n  };\n  // Register this filesystem as read-only and store its trusted read function so we can load so\n  // files from it.\n  TRUSTED_READ_FUNCS.set(ReadOnlyFS, ReadOnlyFS.stream_ops.read);\n  return ReadOnlyFS;\n}\n"
  },
  {
    "path": "src/pyodide/internal/serializeJsModule.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n/* eslint-disable prefer-rest-params */\n/* eslint-disable @typescript-eslint/no-unsafe-member-access */\n/* eslint-disable @typescript-eslint/no-unsafe-argument */\n/* eslint-disable @typescript-eslint/no-unsafe-assignment */\nimport { IS_CREATING_SNAPSHOT } from 'pyodide-internal:metadata';\n\nexport type SerializedJsModule = {\n  jsModule: true;\n  moduleName: string;\n  accessorList: string[];\n};\n\nconst importName = Symbol('importName');\nconst getAccessorList = Symbol('getAccessorList');\nconst getObject = Symbol('getObject');\nconst getPrototypeOfKey = 'Reflect.getProtoTypeOf';\n\nexport function maybeSerializeJsModule(\n  obj_: any,\n  modules: Set<string>\n): SerializedJsModule | undefined {\n  const obj = obj_ as\n    | { [importName]: string; [getAccessorList]: string[] }\n    | undefined;\n  const moduleName = obj?.[importName];\n  if (!moduleName) {\n    return undefined;\n  }\n  modules.add(moduleName);\n  const accessorList: string[] = obj[getAccessorList];\n  return { jsModule: true, moduleName, accessorList };\n}\n\ninterface JsModules {\n  [a: string]: JsModules;\n}\n\nexport function deserializeJsModule(\n  obj: SerializedJsModule,\n  jsModules: JsModules\n): unknown {\n  return (\n    obj.accessorList.reduce((x: JsModules, y: string): JsModules => {\n      if (y === getPrototypeOfKey) {\n        return Reflect.getPrototypeOf(x) as JsModules;\n      }\n      return x[y]!;\n    }, jsModules[obj.moduleName]!) ?? null\n  );\n}\n\n// This tracks the information needed to \"serialize\" attributes of js modules. We need the name and\n// the sequence of attribute accesses. We store the name and accessorList under the importName and\n// getAccessorList symbols.\n//\n// If the receiver of a function call is an import proxy, this can cause the call to crash, so we\n// unwrap the receiver using the getObject symbol.\nexport function createImportProxy(\n  name: string,\n  mod: any,\n  accessorList: (string | symbol)[] = []\n): any {\n  if (!IS_CREATING_SNAPSHOT) {\n    return mod;\n  }\n  if (!mod || typeof mod !== 'object') {\n    return mod;\n  }\n  return new Proxy(mod, {\n    get(target: any, prop: string | symbol, _receiver): any {\n      if (prop === importName) {\n        return name;\n      }\n      if (prop === getAccessorList) {\n        return accessorList;\n      }\n      if (prop === getObject) {\n        return target;\n      }\n      // @ts-expect-error untyped Reflect.get\n      const orig = Reflect.get(...arguments);\n      const descr = Reflect.getOwnPropertyDescriptor(target, prop);\n      // We're required to return the original value unmodified if it's an own\n      // property with a non-writable, non-configurable data descriptor\n      if (descr && descr.writable === false && !descr.configurable) {\n        return orig;\n      }\n      // Or an accessor descriptor with a setter but no getter\n      if (descr && descr.set && !descr.get) {\n        return orig;\n      }\n      if (!['object', 'function'].includes(typeof orig)) {\n        return orig;\n      }\n      return createImportProxy(name, orig, [...accessorList, prop]);\n    },\n    apply(target: any, thisArg: any, argumentList: any[]): any {\n      // If thisArg is a GlobalsProxy it may break APIs that expect the receiver\n      // to be unmodified. Unwrap any GlobalsProxy before making the call.\n      thisArg = thisArg?.[getObject] ?? thisArg;\n      return Reflect.apply(target, thisArg, argumentList);\n    },\n    getPrototypeOf(target: object): any {\n      return createImportProxy(name, Reflect.getPrototypeOf(target), [\n        ...accessorList,\n        getPrototypeOfKey,\n      ]);\n    },\n  });\n}\n"
  },
  {
    "path": "src/pyodide/internal/setupPackages.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { parseTarInfo } from 'pyodide-internal:tar';\nimport { createMetadataFS } from 'pyodide-internal:metadatafs';\nimport { LOCKFILE } from 'pyodide-internal:metadata';\nimport {\n  invalidateCaches,\n  PythonWorkersInternalError,\n  PythonUserError,\n  simpleRunPython,\n} from 'pyodide-internal:util';\nimport { default as EmbeddedPackagesTarReader } from 'pyodide-internal:packages_tar_reader';\n\nconst canonicalizeNameRegex = /[-_.]+/g;\nconst DYNLIB_PATH = '/usr/lib';\n\n/**\n * Canonicalize a package name. Port of Python's packaging.utils.canonicalize_name.\n * @param name The package name to canonicalize.\n * @returns The canonicalize package name.\n * @private\n */\nfunction canonicalizePackageName(name: string): string {\n  return name.replace(canonicalizeNameRegex, '-').toLowerCase();\n}\n\n// The \"name\" field in the lockfile is not canonicalized\nexport const STDLIB_PACKAGES: string[] = Object.values(LOCKFILE.packages)\n  .filter(({ package_type }) => package_type === 'cpython_module')\n  .map(({ name }) => canonicalizePackageName(name));\n\n// Each item in the list is an element of the file path, for example\n// `folder/file.txt` -> `[\"folder\", \"file.txt\"]\nexport type FilePath = string[];\n\nfunction createTarFsInfo(): TarFSInfo {\n  return {\n    children: new Map(),\n    mode: 0o777,\n    type: '5',\n    modtime: 0,\n    size: 0,\n    path: '',\n    name: '',\n    parts: [],\n    reader: null,\n  };\n}\n\n/**\n * VirtualizedDir keeps track of the virtualized view of the site-packages\n * directory generated for each worker as well as a virtualized view of the dynamic libraries stored\n * in /usr/lib.\n */\nclass VirtualizedDir {\n  // TODO(soon): Can we use the # syntax here?\n  // eslint-disable-next-line no-restricted-syntax\n  private rootInfo: TarFSInfo; // site-packages directory\n  // TODO(soon): Can we use the # syntax here?\n  // eslint-disable-next-line no-restricted-syntax\n  private dynlibTarFs: TarFSInfo; // /usr/lib directory\n  // TODO(soon): Can we use the # syntax here?\n  // eslint-disable-next-line no-restricted-syntax\n  private soFiles: FilePath[];\n  // TODO(soon): Can we use the # syntax here?\n  // eslint-disable-next-line no-restricted-syntax\n  private loadedRequirements: Set<string>;\n  constructor() {\n    this.rootInfo = createTarFsInfo();\n    this.dynlibTarFs = createTarFsInfo();\n    this.soFiles = [];\n    this.loadedRequirements = new Set();\n  }\n\n  /**\n   * mountOverlay \"overlays\" a directory onto the site-packages root directory.\n   * All files and subdirectories in the overlay will be accessible at site-packages by the worker.\n   * If a file or directory already exists, an error is thrown.\n   * @param {TarInfo} overlayInfo The directory that is to be \"copied\" into site-packages\n   */\n  mountOverlay(overlayInfo: TarFSInfo, dir: InstallDir): void {\n    const dest = dir == 'dynlib' ? this.dynlibTarFs : this.rootInfo;\n    overlayInfo.children!.forEach((val, key) => {\n      if (dest.children!.has(key)) {\n        throw new PythonWorkersInternalError(\n          `File/folder ${key} being written by multiple packages`\n        );\n      }\n      dest.children!.set(key, val);\n    });\n  }\n\n  /**\n   * A small bundle contains just a single package, it can be thought of as a wheel.\n   *\n   * The entire bundle will be overlaid onto site-packages or /usr/lib depending on its install_dir.\n   *\n   * @param {TarInfo} tarInfo The root tarInfo for the small bundle (See tar.js)\n   * @param {List<String>} soFiles A list of .so files contained in the small bundle\n   * @param {String} requirement The canonicalized package name this small bundle corresponds to\n   * @param {InstallDir} installDir The `install_dir` field from the metadata about the package taken from the lockfile\n   */\n  addSmallBundle(\n    tarInfo: TarFSInfo,\n    soFiles: string[],\n    requirement: string,\n    installDir: InstallDir\n  ): void {\n    for (const soFile of soFiles) {\n      this.soFiles.push(soFile.split('/'));\n    }\n    this.mountOverlay(tarInfo, installDir);\n    this.loadedRequirements.add(requirement);\n  }\n\n  /**\n   * A big bundle contains multiple packages, each package contained in a folder whose name is the canonicalized package name.\n   * This function overlays the requested packages onto the site-packages directory.\n   * @param {TarInfo} tarInfo The root tarInfo for the big bundle (See tar.js)\n   * @param {List<String>} soFiles A list of .so files contained in the big bundle\n   * @param {List<String>} requirements canonicalized list of packages to pick from the big bundle\n   */\n  addBigBundle(\n    tarInfo: TarFSInfo,\n    soFiles: string[],\n    requirements: Set<string>\n  ): void {\n    // add all the .so files we will need to preload from the big bundle\n    for (const soFile of soFiles) {\n      // If folder is in list of requirements include .so file in list to preload.\n      const [pkg, ...rest] = soFile.split('/');\n      if (requirements.has(pkg!)) {\n        this.soFiles.push(rest);\n      }\n    }\n\n    for (const req of requirements) {\n      const child = tarInfo.children!.get(req);\n      if (!child) {\n        throw new PythonUserError(\n          `Requirement ${req} not found in pyodide packages tar`\n        );\n      }\n      this.mountOverlay(child, 'site');\n      this.loadedRequirements.add(req);\n    }\n  }\n\n  getSitePackagesRoot(): TarFSInfo {\n    return this.rootInfo;\n  }\n\n  getDynlibRoot(): TarFSInfo {\n    return this.dynlibTarFs;\n  }\n\n  /** Only used for Pyodide 0.26.0a2 */\n  getSoFilesToLoad(): FilePath[] {\n    return this.soFiles;\n  }\n\n  hasRequirementLoaded(req: string): boolean {\n    return this.loadedRequirements.has(req);\n  }\n\n  mount(Module: Module, tarFS: EmscriptenFS<TarFSInfo>): void {\n    Module.FS.mkdirTree(Module.FS.sessionSitePackages);\n    Module.FS.mount(\n      tarFS,\n      { info: this.rootInfo },\n      Module.FS.sessionSitePackages\n    );\n    Module.FS.mkdirTree(DYNLIB_PATH);\n    Module.FS.mount(tarFS, { info: this.dynlibTarFs }, DYNLIB_PATH);\n  }\n}\n\n/**\n * This stitches together the view of the site packages directory. Each\n * requirement corresponds to a folder in the original tar file. For each\n * requirement in the list we grab the corresponding folder and stitch them\n * together into a combined folder.\n *\n * This also returns the list of soFiles in the resulting site-packages\n * directory so we can preload them.\n *\n * TODO(later): This needs to be removed when external package loading is enabled.\n */\nexport function buildVirtualizedDir(): VirtualizedDir {\n  if (EmbeddedPackagesTarReader.read === undefined) {\n    // Package retrieval is enabled, so the embedded tar reader isn't initialized.\n    // All packages, including STDLIB_PACKAGES, are loaded in `loadPackages`.\n    return new VirtualizedDir();\n  }\n\n  const [bigTarInfo, bigTarSoFiles] = parseTarInfo(EmbeddedPackagesTarReader);\n\n  const requirementsInBigBundle = new Set(STDLIB_PACKAGES);\n  const res = new VirtualizedDir();\n  res.addBigBundle(bigTarInfo, bigTarSoFiles, requirementsInBigBundle);\n\n  return res;\n}\n\n/**\n * Patch loadPackage:\n *  - in workerd, disable integrity checks\n *  - otherwise, disable it entirely\n *\n * TODO: stop using loadPackage in workerd.\n */\nexport function patchLoadPackage(pyodide: Pyodide): void {\n  pyodide.loadPackage = disabledLoadPackage;\n  return;\n}\n\nfunction disabledLoadPackage(): never {\n  throw new PythonWorkersInternalError(\n    'pyodide.loadPackage is disabled because packages are encoded in the binary'\n  );\n}\n\n/**\n * This mounts the metadataFS (which contains user code).\n */\nexport function mountWorkerFiles(Module: Module): void {\n  Module.FS.mkdirTree('/session/metadata');\n  const mdFS = createMetadataFS(Module);\n  Module.FS.mount(mdFS, {}, '/session/metadata');\n  invalidateCaches(Module);\n}\n\n/**\n * Add the directories created by mountLib to sys.path.\n * Has to run after the runtime is initialized but before memory snapshot is collected.\n */\nexport function adjustSysPath(Module: Module): void {\n  const site_packages = Module.FS.sessionSitePackages;\n  simpleRunPython(\n    Module,\n    `import sys; sys.path.append(\"/session/metadata\"); sys.path.append(\"${site_packages}\"); del sys`\n  );\n}\n\nexport const VIRTUALIZED_DIR = buildVirtualizedDir();\n"
  },
  {
    "path": "src/pyodide/internal/snapshot.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { enterJaegerSpan } from 'pyodide-internal:jaeger';\nimport { default as ArtifactBundler } from 'pyodide-internal:artifacts';\nimport { default as UnsafeEval } from 'internal:unsafe-eval';\nimport { default as DiskCache } from 'pyodide-internal:disk_cache';\nimport { type FilePath, VIRTUALIZED_DIR } from 'pyodide-internal:setupPackages';\nimport { default as EmbeddedPackagesTarReader } from 'pyodide-internal:packages_tar_reader';\nimport {\n  SHOULD_SNAPSHOT_TO_DISK,\n  IS_CREATING_BASELINE_SNAPSHOT,\n  MEMORY_SNAPSHOT_READER,\n  REQUIREMENTS,\n  IS_CREATING_SNAPSHOT,\n  IS_EW_VALIDATING,\n  IS_DEDICATED_SNAPSHOT_ENABLED,\n  COMPATIBILITY_FLAGS,\n  type CompatibilityFlags,\n  IS_SECOND_VALIDATION_PHASE,\n} from 'pyodide-internal:metadata';\nimport {\n  invalidateCaches,\n  PythonWorkersInternalError,\n  PythonUserError,\n  simpleRunPython,\n  unreachable,\n} from 'pyodide-internal:util';\nimport { default as MetadataReader } from 'pyodide-internal:runtime-generated/metadata';\nimport type { PyodideEntrypointHelper } from 'pyodide:python-entrypoint-helper';\nimport { entropyAfterSnapshot } from 'pyodide-internal:topLevelEntropy/lib';\nimport {\n  deserializeJsModule,\n  maybeSerializeJsModule,\n  type SerializedJsModule,\n} from 'pyodide-internal:serializeJsModule';\nimport { PyodideVersion } from 'pyodide-internal:const';\n\n// A handle is the pointer into the linear memory returned by dlopen. Multiple dlopens will return\n// multiple pointers.\ntype DsoHandles = {\n  [name: string]: { handles: number[] };\n};\n\n// This is the info about Dsos that we record in getMemoryPatched. Namely the load order and where\n// their metadata is allocated. It would be natural to also have dsoHandles in here, but we don't\n// need a global variable to calculate dsoHandles since it is calculable from the information that\n// Emscripten stores in Module.LDSO (see recordDsoHandles).\ntype DsoLoadInfo = {\n  readonly loadOrder: string[];\n  readonly soMemoryBases: { [name: string]: number };\n  readonly soTableBases?: { [name: string]: number };\n};\n\n// This is the old wire format, where \"settings\" is mixed with the DsoHandles information and\n// DsoLoadInfo is not present because we used to preload all dynamic libraries in a standard order.\n// The \"version\" field is never present in the old wire format, but at runtime accessing it will\n// give undefined. We need to include it here so that we can use the version field as the\n// descriminator for `OldSnapshotMeta | SnapshotMeta`.\ntype OldSnapshotMeta = DsoHandles & {\n  readonly settings?: { readonly baselineSnapshot?: boolean };\n  readonly version?: undefined;\n};\n\ntype LoadedSnapshotSettings = {\n  readonly snapshotType: ArtifactBundler.SnapshotType;\n  readonly compatFlags: CompatibilityFlags;\n};\n\ntype SnapshotSettings = {\n  readonly baselineSnapshot?: boolean;\n} & Partial<LoadedSnapshotSettings>;\n\n// The new wire format, with additional information about the hiwire state, the order that dsos were\n// loaded in, and their memory bases. We also moved settings out of the dsoHandles.\ntype SnapshotMeta = {\n  // We just store importedModulesList to help with testing and introspection\n  readonly importedModulesList: ReadonlyArray<string> | undefined;\n  readonly hiwire: SnapshotConfig | undefined;\n  readonly dsoHandles: DsoHandles;\n  readonly settings: SnapshotSettings;\n  readonly version: 1;\n  readonly jsModuleNames?: ReadonlyArray<string>;\n} & DsoLoadInfo;\n\n// MEMORY_SNAPSHOT_READER has type SnapshotReader | undefined\ntype SnapshotReader = {\n  readMemorySnapshot: (offset: number, buf: Uint32Array | Uint8Array) => void;\n  getMemorySnapshotSize: () => number;\n  disposeMemorySnapshot: () => void;\n};\n\n// Extra info that isn't stored in the snapshot but is calculated at runtime: snapshotSize and\n// snapshotOffset would be awkward to store in the snapshot. snapshotReader is equal to\n// MEMORY_SNAPSHOT_READER, but typescript knows it is defined.\ntype LoadedSnapshotExtras = {\n  snapshotSize: number;\n  snapshotOffset: number;\n  snapshotReader: SnapshotReader;\n};\n\ntype LoadedSnapshotMeta = SnapshotMeta &\n  LoadedSnapshotExtras & { readonly settings: LoadedSnapshotSettings };\n\n/**\n * Constants\n */\n// \"\\x00snp\"\nconst SNAPSHOT_MAGIC = 0x706e7300;\nconst CREATE_SNAPSHOT_VERSION = 2;\nconst HEADER_SIZE = 4 * 4;\n\n/**\n * Global variables for the memory snapshot.\n */\nconst LOADED_SNAPSHOT_META: LoadedSnapshotMeta | undefined = decodeSnapshot(\n  MEMORY_SNAPSHOT_READER\n);\nconst JS_MODULES: Record<string, any> = await importJsModulesFromSnapshot(\n  LOADED_SNAPSHOT_META?.jsModuleNames\n);\nconst CREATED_SNAPSHOT_META: Required<DsoLoadInfo> = {\n  soMemoryBases: {},\n  soTableBases: {},\n  loadOrder: [],\n};\nif (LOADED_SNAPSHOT_META) {\n  // Make sure we include the soMemoryBases and loadOrder from the baseline snapshot when we are\n  // generating stacked snapshots.\n  Object.assign(\n    CREATED_SNAPSHOT_META.soMemoryBases,\n    LOADED_SNAPSHOT_META.soMemoryBases\n  );\n  Object.assign(\n    CREATED_SNAPSHOT_META.soTableBases,\n    LOADED_SNAPSHOT_META.soTableBases\n  );\n  CREATED_SNAPSHOT_META.loadOrder.push(...LOADED_SNAPSHOT_META.loadOrder);\n}\nexport const LOADED_SNAPSHOT_TYPE = LOADED_SNAPSHOT_META?.settings.snapshotType;\n\n/**\n * Preload a dynamic library.\n *\n * Emscripten would usually figure out all of these details for us\n * automatically. These defaults work for shared libs that are configured as\n * standard Python extensions. This naive approach will not work for libraries\n * like scipy, shapely, geos...\n * TODO(someday) fix this.\n */\nfunction loadDynlib(\n  Module: Module,\n  path: string,\n  wasmModuleData: Uint8Array\n): void {\n  const wasmModule = UnsafeEval.newWasmModule(wasmModuleData);\n  const dso = Module.newDSO(path, undefined, 'loading');\n  // even though these are used via dlopen, we are allocating them in an arena\n  // outside the heap and the memory cannot be reclaimed. So I don't think it\n  // would help us to allow them to be dealloc'd.\n  dso.refcount = Infinity;\n  // Hopefully they are used with dlopen\n  dso.global = false;\n  const options = {};\n  // Passing this empty object as dylibLocalScope fixes symbol lookup in dependent shared libraries\n  // that are not loaded globally, thus fixing one of our problems with the upstream shift away from\n  // RLTD_GLOBAL. Emscripten should probably be updated so that if dylibLocalScope is undefined it\n  // will give the dynamic library a new empty loading scope.\n  const dylibLocalScope = {};\n  dso.exports = Module.loadWebAssemblyModule(\n    wasmModule,\n    options,\n    path,\n    dylibLocalScope\n  );\n  // \"handles\" are dlopen handles. There will be one entry in the `handles` list\n  // for each dlopen handle that has not been dlclosed. We need to keep track of\n  // these across\n  const { handles } = LOADED_SNAPSHOT_META?.dsoHandles[path] || { handles: [] };\n  for (const handle of handles) {\n    Module.LDSO.loadedLibsByHandle[handle] = dso;\n  }\n  Module.LDSO.loadedLibsByName[path.split('/').at(-1)!] = dso;\n}\n\n/**\n * This function is used to ensure the order in which we load SO_FILES stays the same. It is only\n * used for 0.26.0a2, later we look at SNAPSHOT_META.loadOrder to decide what order to load libs.\n *\n * The sort always puts _lzma.so and _ssl.so first, because these SO_FILES are loaded in the\n * baseline snapshot, and if we want to generate a package snapshot while a baseline snapshot is\n * loaded we need them to be first. The rest of the files are sorted alphabetically.\n *\n * The `filePaths` list is of the form [[\"folder\", \"file.so\"], [\"file.so\"]], so each element in it\n * is effectively a file path.\n */\nfunction sortSoFiles(filePaths: FilePath[]): FilePath[] {\n  let result = [];\n  let hasLzma = false;\n  let hasSsl = false;\n  const lzmaFile = '_lzma.so';\n  const sslFile = '_ssl.so';\n  for (const path of filePaths) {\n    if (path.length == 1 && path[0] == lzmaFile) {\n      hasLzma = true;\n    } else if (path.length == 1 && path[0] == sslFile) {\n      hasSsl = true;\n    } else {\n      result.push(path);\n    }\n  }\n\n  // JS might handle sorting lists of lists fine, but I'd rather be explicit here and make it compare\n  // strings.\n  result = result\n    .map((x) => x.join('/'))\n    .sort()\n    .map((x) => x.split('/'));\n  if (hasSsl) {\n    result.unshift([sslFile]);\n  }\n  if (hasLzma) {\n    result.unshift([lzmaFile]);\n  }\n\n  return result;\n}\n\n/**\n * Used to ensure that the memoryBase of the dynamic library is stable when restoring snapshots.\n * If the dynamic library was loaded in a memory snapshot,\n */\nfunction getMemoryPatched(\n  Module: Module,\n  libPath: string,\n  size: number\n): number {\n  if (Module.API.version === PyodideVersion.V0_26_0a2) {\n    return Module.getMemory(size);\n  }\n  // Sometimes the module is loaded once by path and once by name, in either order. I'm not really\n  // sure why. But let's check if we snapshoted a load of the library by name.\n  const libName = libPath.split('/').at(-1)!;\n  // 1. Is it loaded in the snapshot? Replay the memory base.\n  {\n    const { soMemoryBases, soTableBases } = LOADED_SNAPSHOT_META ?? {};\n    // If we loaded this library before taking the snapshot, we already allocated the memory and the\n    // allocator remembers because its state is in the linear memory. We just have to look it up.\n    const tableBase = Module.wasmTable.length;\n    const expectedTableBase =\n      soTableBases?.[libPath] ?? soTableBases?.[libName];\n    if (expectedTableBase && tableBase !== expectedTableBase) {\n      // If this happens, we will segfault if we ever try to use this dynamic library.\n      // Save ourselves some debugging pain by crashing early.\n      throw new PythonWorkersInternalError(\n        `Error loading ${libName}: Expected table base ${expectedTableBase} but got table base ${tableBase}`\n      );\n    }\n    const memoryBase = soMemoryBases?.[libPath] ?? soMemoryBases?.[libName];\n    if (memoryBase) {\n      return memoryBase;\n    }\n  }\n  // 2. It's not loaded in the snapshot. Record\n  {\n    const { loadOrder, soMemoryBases, soTableBases } = CREATED_SNAPSHOT_META;\n    // Okay, we didn't load this before so we need to allocate new memory for it. Also record what we\n    // did in case someone makes a snapshot from this run.\n    loadOrder.push(libPath);\n    const memoryBase = Module.getMemory(size);\n    // Track both by full path and by name. That gives us a chance to resolve conflicts in name by the\n    // full path.\n    soMemoryBases[libPath] = memoryBase;\n    soMemoryBases[libName] = memoryBase;\n    soTableBases[libPath] = Module.wasmTable.length;\n    soTableBases[libName] = Module.wasmTable.length;\n    return memoryBase;\n  }\n}\n\nfunction loadDynlibFromTarFs(\n  Module: Module,\n  base: string,\n  node: TarFSInfo | undefined,\n  soFile: string[]\n): void {\n  for (const part of soFile) {\n    node = node?.children?.get(part);\n  }\n  if (!node?.contentsOffset) {\n    node = VIRTUALIZED_DIR.getDynlibRoot();\n    for (const part of soFile) {\n      node = node?.children?.get(part);\n    }\n  }\n  if (!node?.contentsOffset) {\n    throw Error(`fs node could not be found for ${soFile.join('/')}`);\n  }\n  const { contentsOffset, size } = node;\n  if (contentsOffset === undefined) {\n    throw Error(`contentsOffset not defined for ${soFile.join('/')}`);\n  }\n  const wasmModuleData = new Uint8Array(size);\n  (node.reader ?? EmbeddedPackagesTarReader).read(\n    contentsOffset,\n    wasmModuleData\n  );\n  const path = base + soFile.join('/');\n  loadDynlib(Module, path, wasmModuleData);\n}\n\nfunction loadDynlibFromVendor(\n  Module: Module,\n  soFile: string[],\n  userBundleNames: string[]\n): void {\n  const path = soFile.slice(3).join('/');\n  const index = userBundleNames.indexOf(path);\n  if (index == -1) {\n    throw new PythonWorkersInternalError(\n      `Could not find ${path} in user bundle, which is required by the snapshot.`\n    );\n  }\n  // The MetadataReader holds the user bundle's contents.\n  const buffer = new Uint8Array(MetadataReader.getSizes()[index]!);\n  MetadataReader.read(index, 0, buffer);\n  loadDynlib(Module, path, buffer);\n}\n\n/**\n * Preloading for the legacy 0.26 version. This loads all dynamic libraries\n * visible in the site-packages directory. They are loaded before the runtime is\n * initialized outside of the heap, using the same mechanism for DT_NEEDED libs\n * (i.e., the libs that are loaded before the program starts because you passed\n * them as linker args).\n */\nfunction preloadDynamicLibs026(Module: Module): void {\n  const sitePackages = Module.FS.sessionSitePackages + '/';\n  const sitePackagesRoot = VIRTUALIZED_DIR.getSitePackagesRoot();\n  const loadedBaselineSnapshot =\n    LOADED_SNAPSHOT_META?.settings?.baselineSnapshot;\n  let SO_FILES_TO_LOAD: string[][];\n  if (IS_CREATING_BASELINE_SNAPSHOT || loadedBaselineSnapshot) {\n    SO_FILES_TO_LOAD = [['_lzma.so'], ['_ssl.so']];\n  } else {\n    SO_FILES_TO_LOAD = sortSoFiles(VIRTUALIZED_DIR.getSoFilesToLoad());\n  }\n  for (const soFile of SO_FILES_TO_LOAD) {\n    loadDynlibFromTarFs(Module, sitePackages, sitePackagesRoot, soFile);\n  }\n}\n\n/**\n * If we're restoring from a snapshot, we need to preload dynamic libraries so that any function\n * pointers that point into the dylib symbols work correctly.\n * Load the dynamic libraries in loadOrder. Mostly logic dealing with paths.\n */\nfunction preloadDynamicLibsMain(Module: Module, loadOrder: string[]): void {\n  const sitePackages = Module.FS.sessionSitePackages + '/';\n  const sitePackagesRoot = VIRTUALIZED_DIR.getSitePackagesRoot();\n  const dynlibRoot = VIRTUALIZED_DIR.getDynlibRoot();\n  const dynlibPath = '/usr/lib/';\n  const userBundleNames = MetadataReader.getNames();\n  for (let path of loadOrder) {\n    let root = sitePackagesRoot;\n    let base = '';\n    if (path.startsWith(sitePackages)) {\n      path = path.slice(sitePackages.length);\n      base = sitePackages;\n    } else if (path.startsWith(dynlibPath)) {\n      path = path.slice(dynlibPath.length);\n      root = dynlibRoot;\n      base = dynlibPath;\n    }\n\n    const pathSplit = Module.PATH.normalizeArray(path.split('/'), true);\n    if (pathSplit[0] == '') {\n      // This is a file path beginning with `/`, like /session/metadata/vendor/pkg/lib.so. So we\n      // are loading the vendored package's dynlibs here.\n      //\n      // TODO(EW-9508): support .so's in user bundle outside vendor dir.\n      loadDynlibFromVendor(Module, pathSplit, userBundleNames);\n    } else {\n      // This is a file path relative to the site-packages directory, like pkg/lib.so. So we are\n      // loading the built-in package's dynlibs here.\n      loadDynlibFromTarFs(Module, base, root, pathSplit);\n    }\n  }\n}\n\nfunction preloadDynamicLibs(Module: Module): void {\n  if (Module.API.version === PyodideVersion.V0_26_0a2) {\n    // In 0.26.0a2 we need to preload dynamic libraries even if we aren't restoring a snapshot.\n    preloadDynamicLibs026(Module);\n    return;\n  }\n  //\n  const loadOrder = LOADED_SNAPSHOT_META?.loadOrder;\n  if (!loadOrder) {\n    // In newer versions we only need to do the preloading if there is a snapshot to restore.\n    return;\n  }\n  // In Pyodide 0.28 we switched from using top level EM_JS to initialize the CountArgs function\n  // pointer to using an initializer to work around a regression in Emscripten 4.0.3 and 4.0.4. We\n  // could drop this patch because we are now on Emscripten 4.0.9.\n  // https://github.com/pyodide/pyodide/blob/main/cpython/patches/0008-Fix-Emscripten-call-trampoline-compatibility-with-Em.patch\n  //\n  // Unfortunately, this initializer allocates a function table slot and is called before dynamic\n  // loading when taking the snapshot but after when restoring the snapshot. Thus, when restoring a\n  // snapshot, before loading dynamic libraries we reserve a function pointer for the\n  // CountArgsPointer and after loading dynamic libraries, we put it in the free list so it will be\n  // used at the right moment.\n  const PyEMCountArgsPtr = Module.getEmptyTableSlot();\n  preloadDynamicLibsMain(Module, loadOrder);\n  Module.freeTableIndexes.push(PyEMCountArgsPtr);\n}\n\n/**\n * This records which dynamic libraries have open handles (handed out by dlopen,\n * not yet dlclosed). We'll need to track this information so that we don't\n * crash if we dlsym the handle after restoring from the snapshot\n */\nfunction recordDsoHandles(Module: Module): DsoHandles {\n  const dylinkInfo: DsoHandles = {};\n  for (const [h, { name }] of Object.entries(Module.LDSO.loadedLibsByHandle)) {\n    const handle = Number(h);\n    if (handle === 0) {\n      continue;\n    }\n    dylinkInfo[name] ??= { handles: [] };\n    dylinkInfo[name].handles.push(handle);\n  }\n  return dylinkInfo;\n}\n\n/**\n * Python modules do a lot of work the first time they are imported. The memory\n * snapshot will save more time the more of this work is included. However, we\n * can't snapshot the JS runtime state so we have no ffi. Thus some imports from\n * user code will fail.\n *\n * If we are doing a baseline snapshot, just import everything from\n * baselineSnapshotImports. These will all succeed.\n *\n * If doing a more dedicated \"package\" snap shot, also try to import each\n * user import that is importing non-vendored modules.\n *\n * All of this is being done in the __main__ global scope, so be careful not to\n * pollute it with extra included-by-default names (user code is executed in its\n * own separate module scope though so it's not _that_ important).\n *\n * This function returns a list of modules that have been imported.\n */\nfunction memorySnapshotDoImports(Module: Module): string[] {\n  const baselineSnapshotImports =\n    MetadataReader.constructor.getBaselineSnapshotImports();\n  const toImport = baselineSnapshotImports.join(',');\n  const toDelete = Array.from(\n    new Set(baselineSnapshotImports.map((x) => x.split('.', 1)[0]))\n  ).join(',');\n\n  simpleRunPython(Module, `import ${toImport}`);\n  simpleRunPython(Module, 'sysconfig.get_config_vars()');\n  // Delete to avoid polluting globals\n  simpleRunPython(Module, `del ${toDelete}`);\n  if (IS_CREATING_BASELINE_SNAPSHOT) {\n    // We've done all the imports for the baseline snapshot.\n    return [];\n  }\n  if (REQUIREMENTS.length == 0) {\n    // Don't attempt to scan for package imports if the Worker has specified no package\n    // requirements, as this means their code isn't going to be importing any modules that we need\n    // to include in a snapshot.\n    return [];\n  }\n\n  // The `importedModules` list will contain all modules that have been imported, including local\n  // modules, the usual `js` and other stdlib modules. We want to filter out local imports, so we\n  // grab them and put them into a set for fast filtering.\n  const importedModules: string[] = MetadataReader.getPackageSnapshotImports(\n    Module.API.version\n  );\n  const deduplicatedModules = [...new Set(importedModules)];\n\n  // Import the modules list so they are included in the snapshot.\n  if (deduplicatedModules.length > 0) {\n    simpleRunPython(Module, 'import ' + deduplicatedModules.join(','));\n  }\n\n  return deduplicatedModules;\n}\n\nfunction describeValue(val: any): string {\n  try {\n    const out = [];\n\n    const type = typeof val;\n    const isObject = type === 'object';\n    out.push(`Value: ${val}`);\n    out.push(`Type: ${type}`);\n    try {\n      const constructorName = val?.constructor?.name; // eslint-disable-line\n      if (constructorName) {\n        out.push(`Constructor name: ${constructorName}`);\n      }\n    } catch {\n      // Ignore errors when getting keys\n    }\n\n    if (val && isObject) {\n      try {\n        out.push(\n          `Keys: ${Object.keys(val as Record<string, unknown>)\n            .slice(0, 10)\n            .join(', ')}`\n        );\n      } catch {\n        // Ignore errors when getting keys\n      }\n    }\n\n    try {\n      if (val && isObject && typeof (val as Error).stack === 'string') {\n        out.push(`Stack:\\n${(val as Error).stack}`);\n      }\n    } catch {\n      // Ignore errors when getting stack\n    }\n\n    try {\n      out.push(`toStringTag: ${Object.prototype.toString.call(val)}`);\n    } catch {\n      // Ignore errors when getting object type\n    }\n\n    try {\n      const contents = JSON.stringify(val);\n      if (contents?.length > 100) {\n        out.push(`Contents: ${contents.slice(0, 100)}...`);\n      } else if (contents) {\n        out.push(`Contents: ${contents}`);\n      }\n    } catch {\n      // Ignore JSON stringify errors\n    }\n\n    return out.join('\\n');\n  } catch (err) {\n    return `Error describing value: ${err}`;\n  }\n}\n\n/**\n * When we create a dedicated memory snapshot, we capture all the globals in the user's top-level\n * scope. If those globals refer to JS objects, then we may fail to serialise them. This function\n * creates a user error to inform the user of this issue.\n *\n * It's important that we give the user as much information about this as possible, so they can\n * understand what they need to change in order to resolve the problem on their end.\n */\nfunction createUnserializableObjectError(obj: any): PythonUserError {\n  // TODO: Create docs for this and link them here.\n  const error = `Can't serialize top-level variable.\nPlease review any global variables you or your imported modules create at the top-level of your code, and consider deleting them.\n\nDescription of the value:\n${describeValue(obj)}\n`;\n  return new PythonUserError(error);\n}\n\nasync function importJsModulesFromSnapshot(\n  jsModuleNames: ReadonlyArray<string> | undefined\n): Promise<Record<string, any>> {\n  if (jsModuleNames === undefined) {\n    return {};\n  }\n  return Object.fromEntries(\n    await Promise.all(\n      jsModuleNames.map(\n        async (x): Promise<[string, any]> => [x, await import(x)]\n      )\n    )\n  );\n}\n\ntype CustomSerialized =\n  | { pyodide_entrypoint_helper: true }\n  | { cloudflare_compat_flags: true }\n  | SerializedJsModule;\n/**\n * Global objects that need a custom serializer\n */\nexport type CustomSerializedObjects = {\n  pyodide_entrypoint_helper: PyodideEntrypointHelper;\n  cloudflare_compat_flags: CompatibilityFlags;\n};\n\nfunction getHiwireSerializer(\n  globalObj: CustomSerializedObjects,\n  modules: Set<string>\n): (obj: any) => CustomSerialized {\n  return function serializer(obj: any): CustomSerialized {\n    if (obj === globalObj.pyodide_entrypoint_helper) {\n      return { pyodide_entrypoint_helper: true };\n    } else if (obj === globalObj.cloudflare_compat_flags) {\n      return { cloudflare_compat_flags: true };\n    }\n    const serializedModule = maybeSerializeJsModule(obj, modules);\n    if (serializedModule) {\n      return serializedModule;\n    }\n    throw createUnserializableObjectError(obj);\n  };\n}\n\nfunction getHiwireDeserializer(\n  globalObj: CustomSerializedObjects\n): (obj: CustomSerialized) => any {\n  return function deserializer(obj) {\n    if ('pyodide_entrypoint_helper' in obj) {\n      return globalObj.pyodide_entrypoint_helper;\n    } else if ('cloudflare_compat_flags' in obj) {\n      return globalObj.cloudflare_compat_flags;\n    }\n    if ('jsModule' in obj) {\n      return deserializeJsModule(obj, JS_MODULES);\n    }\n    unreachable(obj, `Can't deserialize ${obj}`);\n  };\n}\n\n/**\n * Create memory snapshot by importing SNAPSHOT_IMPORTS to ensure these packages\n * are initialized in the linear memory snapshot and then saving a copy of the\n * linear memory into MEMORY.\n */\nfunction makeLinearMemorySnapshot(\n  Module: Module,\n  importedModulesList: string[],\n  customSerializedObjects: CustomSerializedObjects,\n  snapshotType: ArtifactBundler.SnapshotType\n): Uint8Array {\n  const dsoHandles = recordDsoHandles(Module);\n  let hiwire: SnapshotConfig | undefined;\n  const jsModuleNames: Set<string> = new Set();\n  if (Module.API.version !== PyodideVersion.V0_26_0a2) {\n    hiwire = Module.API.serializeHiwireState(\n      getHiwireSerializer(customSerializedObjects, jsModuleNames)\n    );\n  }\n  const settings: SnapshotSettings = {\n    baselineSnapshot: IS_CREATING_BASELINE_SNAPSHOT,\n    snapshotType,\n    compatFlags: COMPATIBILITY_FLAGS,\n  };\n  return encodeSnapshot(Module.HEAP8, {\n    version: 1,\n    dsoHandles,\n    hiwire,\n    importedModulesList,\n    jsModuleNames: Array.from(jsModuleNames),\n    settings,\n    ...CREATED_SNAPSHOT_META,\n  });\n}\n\n/**\n * Encode heap and dsoJSON into the memory snapshot artifact that we'll upload\n */\nfunction encodeSnapshot(heap: Uint8Array, meta: SnapshotMeta): Uint8Array {\n  const json = JSON.stringify(meta);\n  let snapshotOffset = HEADER_SIZE + 2 * json.length;\n  // align to 8 bytes\n  snapshotOffset = Math.ceil(snapshotOffset / 8) * 8;\n  const toUpload = new Uint8Array(snapshotOffset + heap.length);\n  const encoder = new TextEncoder();\n  const { written: jsonByteLength } = encoder.encodeInto(\n    json,\n    toUpload.subarray(HEADER_SIZE)\n  );\n  const header = new Uint32Array(toUpload.buffer);\n  header[0] = SNAPSHOT_MAGIC;\n  header[1] = CREATE_SNAPSHOT_VERSION;\n  header[2] = snapshotOffset;\n  header[3] = jsonByteLength;\n  toUpload.subarray(snapshotOffset).set(heap);\n  return toUpload;\n}\n\n/**\n * Decode heap and dsoJSON from the memory snapshot reader\n */\nfunction decodeSnapshot(\n  reader: SnapshotReader | undefined\n): LoadedSnapshotMeta | undefined {\n  if (!reader) {\n    return undefined;\n  }\n  if (reader.getMemorySnapshotSize() === 0) {\n    throw new PythonWorkersInternalError(\n      `SnapshotReader returned memory snapshot size of 0`\n    );\n  }\n  const header = new Uint32Array(4);\n  reader.readMemorySnapshot(0, header);\n  if (header[0] !== SNAPSHOT_MAGIC) {\n    throw new PythonWorkersInternalError(\n      `Invalid magic number ${header[0]}, expected ${SNAPSHOT_MAGIC}`\n    );\n  }\n  // buf[1] is SNAPSHOT_VERSION (unused currently)\n  const snapshotOffset = header[2]!;\n  const jsonByteLength = header[3]!;\n\n  const snapshotSize = reader.getMemorySnapshotSize() - snapshotOffset;\n  const jsonBuf = new Uint8Array(jsonByteLength);\n  const offset = header.byteLength; // the json starts after the header\n  reader.readMemorySnapshot(offset, jsonBuf);\n  const json = new TextDecoder().decode(jsonBuf);\n  const meta = JSON.parse(json) as OldSnapshotMeta | SnapshotMeta;\n  const extras: LoadedSnapshotExtras = {\n    snapshotSize,\n    snapshotOffset,\n    snapshotReader: reader,\n  };\n  if (!meta?.version) {\n    return {\n      version: 1,\n      importedModulesList: undefined,\n      dsoHandles: meta,\n      hiwire: undefined,\n      loadOrder: [],\n      soMemoryBases: {},\n      settings: {\n        snapshotType: meta.settings?.baselineSnapshot ? 'baseline' : 'package',\n        compatFlags: {},\n        ...meta.settings,\n      },\n      jsModuleNames: [],\n      ...extras,\n    };\n  }\n  return {\n    ...meta,\n    ...extras,\n    settings: {\n      ...meta.settings,\n      snapshotType:\n        meta.settings.snapshotType ??\n        (meta.settings.baselineSnapshot ? 'baseline' : 'package'),\n      compatFlags: meta.settings.compatFlags ?? {},\n    },\n  };\n}\n\nexport function isRestoringSnapshot(): boolean {\n  return !!LOADED_SNAPSHOT_META;\n}\n\nfunction checkSnapshotType(snapshotType: string): void {\n  if (SHOULD_SNAPSHOT_TO_DISK) {\n    return;\n  }\n  if (\n    !IS_EW_VALIDATING &&\n    snapshotType === 'dedicated' &&\n    !IS_DEDICATED_SNAPSHOT_ENABLED\n  ) {\n    throw new PythonWorkersInternalError(\n      'Received dedicated snapshot but compat flag for dedicated snapshots is not enabled'\n    );\n  }\n\n  if (\n    !IS_EW_VALIDATING &&\n    snapshotType !== 'dedicated' &&\n    IS_DEDICATED_SNAPSHOT_ENABLED\n  ) {\n    throw new PythonWorkersInternalError(\n      'Received non-dedicated snapshot but compat flag for dedicated snapshots is enabled'\n    );\n  }\n\n  // If we have a snapshot in the bundle and the dedicated snapshot flag is enabled, then we\n  // should verify that the snapshot in the bundle is a dedicated snapshot. If it is not\n  // we should fail with an error.\n  if (snapshotType !== 'dedicated' && IS_SECOND_VALIDATION_PHASE) {\n    throw new PythonWorkersInternalError(\n      'The second validation phase should receive a dedicated snapshot, got ' +\n        snapshotType\n    );\n  }\n}\n\nexport function maybeRestoreSnapshot(Module: Module): void {\n  Module.noInitialRun = isRestoringSnapshot();\n  Module.getMemoryPatched = getMemoryPatched;\n  // Make sure memory is large enough\n  Module.growMemory(LOADED_SNAPSHOT_META?.snapshotSize ?? 0);\n  enterJaegerSpan('preload_dynamic_libs', () => {\n    preloadDynamicLibs(Module);\n  });\n  // TODO: Remove the jaeger span here\n  enterJaegerSpan('remove_run_dependency', () => {\n    Module.removeRunDependency('dynlibs');\n  });\n  if (!LOADED_SNAPSHOT_META) {\n    return;\n  }\n  const { snapshotSize, snapshotOffset, snapshotReader, settings } =\n    LOADED_SNAPSHOT_META;\n  checkSnapshotType(settings.snapshotType);\n\n  Module.growMemory(snapshotSize);\n  snapshotReader.readMemorySnapshot(snapshotOffset, Module.HEAP8);\n  snapshotReader.disposeMemorySnapshot();\n  // Invalidate caches if we have a snapshot because the contents of site-packages\n  // may have changed.\n  invalidateCaches(Module);\n}\n\nfunction collectSnapshot(\n  Module: Module,\n  importedModulesList: string[],\n  customSerializedObjects: CustomSerializedObjects,\n  snapshotType: ArtifactBundler.SnapshotType\n): void {\n  if (!IS_EW_VALIDATING && !SHOULD_SNAPSHOT_TO_DISK) {\n    throw new PythonWorkersInternalError(\n      \"Attempted to collect snapshot outside of context where it's supported.\"\n    );\n  }\n  const snapshot = makeLinearMemorySnapshot(\n    Module,\n    importedModulesList,\n    customSerializedObjects,\n    snapshotType\n  );\n  entropyAfterSnapshot(Module);\n  if (IS_EW_VALIDATING) {\n    ArtifactBundler.storeMemorySnapshot({\n      snapshot,\n      importedModulesList,\n      snapshotType,\n    });\n  } else if (SHOULD_SNAPSHOT_TO_DISK) {\n    DiskCache.putSnapshot('snapshot.bin', snapshot);\n  } else {\n    throw new PythonWorkersInternalError('Unreachable');\n  }\n}\n\n/**\n * Collects a dedicated snapshot. This is only called after the top-level of the worker has been\n * run.\n */\nexport function maybeCollectDedicatedSnapshot(\n  Module: Module,\n  customSerializedObjects: CustomSerializedObjects | null\n): void {\n  if (!IS_CREATING_SNAPSHOT) {\n    return;\n  }\n\n  if (!IS_DEDICATED_SNAPSHOT_ENABLED) {\n    return;\n  }\n\n  if (Module.API.version == PyodideVersion.V0_26_0a2) {\n    // 0.26.0a2 does not support serialisation of the hiwire state, so it cannot support dedicated\n    // snapshots.\n    throw new PythonWorkersInternalError(\n      'Dedicated snapshot is not supported for Python runtime version 0.26.0a2'\n    );\n  }\n\n  if (!customSerializedObjects) {\n    throw new PythonWorkersInternalError(\n      'customSerializedObjects is required for dedicated snapshot'\n    );\n  }\n  collectSnapshot(Module, [], customSerializedObjects, 'dedicated');\n}\n\n/**\n * Collects either a baseline or package snapshot. This is called prior to running the top-level\n * of the worker and crucially before the worker files are mounted.\n *\n * Dedicated snapshots are collected in `maybeCollectDedicatedSnapshot`.\n */\nexport function maybeCollectSnapshot(\n  Module: Module,\n  customSerializedObjects: CustomSerializedObjects\n): void {\n  // In order to surface any problems that occur in `memorySnapshotDoImports` to\n  // users in local development, always call it even if we aren't actually\n  const importedModulesList = memorySnapshotDoImports(Module);\n  if (!IS_CREATING_SNAPSHOT) {\n    return;\n  }\n\n  if (IS_DEDICATED_SNAPSHOT_ENABLED) {\n    // We are not interested in collecting a baseline/package snapshot here if this feature flag\n    // is enabled.\n    return;\n  }\n\n  collectSnapshot(\n    Module,\n    importedModulesList,\n    customSerializedObjects,\n    IS_CREATING_BASELINE_SNAPSHOT ? 'baseline' : 'package'\n  );\n}\n\nexport function finalizeBootstrap(\n  Module: Module,\n  customSerializedObjects: CustomSerializedObjects\n): void {\n  Module.API.config._makeSnapshot =\n    IS_CREATING_SNAPSHOT && Module.API.version !== PyodideVersion.V0_26_0a2;\n  enterJaegerSpan('finalize_bootstrap', () => {\n    Module.API.finalizeBootstrap(\n      LOADED_SNAPSHOT_META?.hiwire,\n      getHiwireDeserializer(customSerializedObjects)\n    );\n  });\n  // finalizeBootstrap overrides LD_LIBRARY_PATH. Restore it.\n  simpleRunPython(\n    Module,\n    `import os; os.environ[\"LD_LIBRARY_PATH\"] += \":/session/metadata/python_modules/lib/\"; del os`\n  );\n  if (IS_CREATING_SNAPSHOT) {\n    return;\n  }\n  Module.API.public_api.registerJsModule('_cf_internal_snapshot_info', {\n    loadedSnapshot: !!LOADED_SNAPSHOT_META,\n    loadedBaselineSnapshot: LOADED_SNAPSHOT_META?.settings.baselineSnapshot,\n    importedModulesList: LOADED_SNAPSHOT_META?.importedModulesList,\n  });\n}\n"
  },
  {
    "path": "src/pyodide/internal/sphinx/.gitignore",
    "content": "_build\n"
  },
  {
    "path": "src/pyodide/internal/sphinx/Makefile",
    "content": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line, and also\n# from the environment for the first two.\nSPHINXOPTS    ?=\nSPHINXBUILD   ?= sphinx-build\nSOURCEDIR     = .\nBUILDDIR      = _build\n\n# Put it first so that \"make\" without argument is like \"make help\".\nhelp:\n\t@$(SPHINXBUILD) -M help \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\n.PHONY: help Makefile\n\n# Catch-all target: route all unknown targets to Sphinx using the new\n# \"make mode\" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).\n%: Makefile\n\t@$(SPHINXBUILD) -M $@ \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n"
  },
  {
    "path": "src/pyodide/internal/sphinx/README.md",
    "content": "## Python SDK docs markdown generator\n\nThis directory contains config files for Sphinx, which can generate docs based on the exported methods in asgi.py and workers.py. These files are a part of the Python SDK.\n\n### Usage\n\n\n```\npython -m venv .venv\nsource .venv/bin/activate\npip install -r requirements-doc.txt\nmake markdown\n```\n\nYou'll then find the markdown in _build/markdown/docs/workers.md.\n\nMake a PR in the cloudflare-docs repo to update the appropriate files.\n"
  },
  {
    "path": "src/pyodide/internal/sphinx/conf.py",
    "content": "# Configuration file for the Sphinx documentation builder.\n#\n# For the full list of built-in configuration values, see the documentation:\n# https://www.sphinx-doc.org/en/master/usage/configuration.html\n\n# -- Project information -----------------------------------------------------\n# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information\n\nproject = \"Python Workers API\"\ncopyright = \"2025, Cloudflare\"\nauthor = \"Cloudflare\"\n\n# -- General configuration ---------------------------------------------------\n# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration\n\nextensions = [\n    \"sphinx.ext.autodoc\",\n    \"sphinx_markdown_builder\",\n    \"sphinx.ext.autosummary\",\n    \"myst_parser\",\n]\nautodoc_mock_imports = [\"js\", \"pyodide.http\", \"pyodide.ffi\", \"fastapi\", \"pyodide\"]\n\n\ntemplates_path = [\"_templates\"]\nexclude_patterns = [\"_build\", \"Thumbs.db\", \".DS_Store\"]\n\n\n# -- Options for HTML output -------------------------------------------------\n# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output\n\nhtml_theme = \"alabaster\"\nhtml_static_path = [\"_static\"]\n\n# Add the parent dir to the search path, so that the .py files can be accessed.\nimport sys\nfrom pathlib import Path\n\nsys.path.insert(0, str(Path.resolve(Path(\"..\"))))\n"
  },
  {
    "path": "src/pyodide/internal/sphinx/docs/asgi.rst",
    "content": "asgi module\n===========\n\n.. automodule:: asgi\n   :members:\n   :undoc-members:\n   :show-inheritance:\n"
  },
  {
    "path": "src/pyodide/internal/sphinx/docs/modules.rst",
    "content": "SDK\n===\n\n.. toctree::\n   :maxdepth: 4\n\n   asgi\n   workers\n"
  },
  {
    "path": "src/pyodide/internal/sphinx/docs/workers.rst",
    "content": "workers module\n==============\n\n.. automodule:: workers\n   :members:\n   :undoc-members:\n   :show-inheritance:\n"
  },
  {
    "path": "src/pyodide/internal/sphinx/index.rst",
    "content": ".. Python Workers API documentation master file, created by\n   sphinx-quickstart on Tue Jan  7 15:01:02 2025.\n   You can adapt this file completely to your liking, but it should at least\n   contain the root `toctree` directive.\n\nPython Workers API documentation\n================================\n\nAdd your content using ``reStructuredText`` syntax. See the\n`reStructuredText <https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html>`_\ndocumentation for details.\n\n\n.. toctree::\n   :maxdepth: 2\n   :caption: Contents:\n\n"
  },
  {
    "path": "src/pyodide/internal/sphinx/make.bat",
    "content": "@ECHO OFF\r\n\r\npushd %~dp0\r\n\r\nREM Command file for Sphinx documentation\r\n\r\nif \"%SPHINXBUILD%\" == \"\" (\r\n\tset SPHINXBUILD=sphinx-build\r\n)\r\nset SOURCEDIR=.\r\nset BUILDDIR=_build\r\n\r\n%SPHINXBUILD% >NUL 2>NUL\r\nif errorlevel 9009 (\r\n\techo.\r\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\r\n\techo.installed, then set the SPHINXBUILD environment variable to point\r\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\r\n\techo.may add the Sphinx directory to PATH.\r\n\techo.\r\n\techo.If you don't have Sphinx installed, grab it from\r\n\techo.https://www.sphinx-doc.org/\r\n\texit /b 1\r\n)\r\n\r\nif \"%1\" == \"\" goto help\r\n\r\n%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%\r\ngoto end\r\n\r\n:help\r\n%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%\r\n\r\n:end\r\npopd\r\n"
  },
  {
    "path": "src/pyodide/internal/sphinx/requirements-doc.txt",
    "content": "alabaster==1.0.0\nbabel==2.16.0\ncertifi==2024.12.14\ncharset-normalizer==3.4.1\ndocutils==0.21.2\nidna==3.10\nimagesize==1.4.1\nJinja2==3.1.6\nmarkdown-it-py==3.0.0\nMarkupSafe==3.0.2\nmdit-py-plugins==0.4.2\nmdurl==0.1.2\nmyst-parser==4.0.0\npackaging==24.2\nPygments==2.19.1\nPyYAML==6.0.2\nrequests==2.32.5\nsnowballstemmer==2.2.0\nSphinx==8.1.3\nsphinx-markdown-builder==0.6.7\nsphinxcontrib-applehelp==2.0.0\nsphinxcontrib-devhelp==2.0.0\nsphinxcontrib-htmlhelp==2.1.0\nsphinxcontrib-jsmath==1.0.1\nsphinxcontrib-qthelp==2.0.0\nsphinxcontrib-serializinghtml==2.0.0\ntabulate==0.9.0\nurllib3==2.6.3\n"
  },
  {
    "path": "src/pyodide/internal/tar.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// This is based on the info about the tar file format on wikipedia\n// And some trial and error with real tar files.\n// https://en.wikipedia.org/wiki/Tar_(computing)#File_format\n\nimport { PythonWorkersInternalError } from 'pyodide-internal:util';\n\nconst decoder = new TextDecoder();\nfunction decodeString(buf: Uint8Array): string {\n  const nullIdx = buf.indexOf(0);\n  if (nullIdx >= 0) {\n    buf = buf.subarray(0, nullIdx);\n  }\n  return decoder.decode(buf);\n}\nfunction decodeField(buf: Uint8Array, offset: number, size: number): string {\n  return decodeString(buf.subarray(offset, offset + size));\n}\nfunction decodeNumber(buf: Uint8Array, offset: number, size: number): number {\n  return parseInt(decodeField(buf, offset, size), 8);\n}\n\nfunction decodeHeader(buf: Uint8Array, reader: Reader): TarFSInfo {\n  const nameBase = decodeField(buf, 0, 100);\n  const namePrefix = decodeField(buf, 345, 155);\n  let path = namePrefix + nameBase;\n  // Trim possible leading ./\n  if (path.startsWith('./')) {\n    path = path.slice(2);\n  }\n  const mode = decodeNumber(buf, 100, 8);\n  const size = decodeNumber(buf, 124, 12);\n  const modtime = decodeNumber(buf, 136, 12);\n  const type = String.fromCharCode(buf[156]!);\n  return {\n    path,\n    name: path,\n    mode,\n    size,\n    modtime,\n    type,\n    parts: [],\n    children: undefined,\n    reader,\n  };\n}\n\nexport function parseTarInfo(reader: Reader): [TarFSInfo, string[]] {\n  const directories: TarFSInfo[] = [];\n  const soFiles = [];\n  const root: TarFSInfo = {\n    children: new Map(),\n    mode: 0o777,\n    type: '5',\n    modtime: 0,\n    size: 0,\n    path: '',\n    name: '',\n    parts: [],\n    reader,\n  };\n  let directory = root;\n  const buf = new Uint8Array(512);\n  let offset = 0;\n  let longName = null; // if truthy, overwrites the filename of the next header\n  while (true) {\n    reader.read(offset, buf);\n    const info = decodeHeader(buf, reader);\n    if (isNaN(info.mode)) {\n      // Invalid mode means we're done\n      return [root, soFiles];\n    }\n    if (longName) {\n      info.path = longName;\n      info.name = longName;\n      longName = null;\n    }\n    const contentsOffset = offset + 512;\n    offset += 512 * Math.ceil(info.size / 512 + 1);\n    if (info.path === '') {\n      // skip possible leading ./ directory\n      continue;\n    }\n    if (info.path.includes('PaxHeader')) {\n      // Ignore PaxHeader extension\n      // These metadata directories don't actually have a directory entry which\n      // is going to cause us to crash below.\n      // Our tar files shouldn't have these anyways...\n      continue;\n    }\n    if (info.type === 'L') {\n      const buf = new Uint8Array(info.size);\n      reader.read(contentsOffset, buf);\n      longName = decodeString(buf);\n      continue;\n    }\n\n    // Navigate to the correct directory by going up until we're at the common\n    // ancestor of the current position and the target then back down.\n    //\n    // Most tar files I run into are lexicographically sorted, so the \"go back\n    // down\" step is not necessary. But some tar files are a bit out of order.\n    //\n    // We do rely on the fact that the entry for a given directory appears\n    // before any files in the directory. I don't see anywhere in the spec where\n    // it says this is required but I think it would be weird and annoying for a\n    // tar file to violate this property.\n\n    // go up to common ancestor\n    while (directories.length && !info.name.startsWith(directory.path)) {\n      directory = directories.pop()!;\n    }\n    // go down to target (in many tar files this second loop body is evaluated 0\n    // times)\n    const parts = info.path.slice(0, -1).split('/');\n    for (let i = directories.length; i < parts.length - 1; i++) {\n      directories.push(directory);\n      directory = directory.children!.get(parts[i]!)!;\n    }\n    if (info.type === '5') {\n      // a directory\n      directories.push(directory);\n      info.parts = parts;\n      info.name = info.parts.at(-1)!;\n      info.children = new Map();\n      directory.children!.set(info.name, info);\n      directory = info;\n    } else if (info.type === '0') {\n      // a normal file\n      info.contentsOffset = contentsOffset;\n      info.name = info.path.slice(directory.path.length);\n      if (info.name.endsWith('.so')) {\n        soFiles.push(info.path);\n      }\n      directory.children!.set(info.name, info);\n    } else {\n      // fail if we encounter other values of type (e.g., symlink, LongName, etc)\n      throw new PythonWorkersInternalError(\n        `Python TarFS error: Unexpected type ${info.type}`\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "src/pyodide/internal/tarfs.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { createReadonlyFS } from 'pyodide-internal:readOnlyFS';\nimport { PythonWorkersInternalError } from 'pyodide-internal:util';\n\nconst FSOps: FSOps<TarFSInfo> = {\n  getNodeMode(_parent, _name, info) {\n    return {\n      permissions: info.mode,\n      isDir: info.children !== undefined,\n    };\n  },\n  setNodeAttributes(node, info, isDir) {\n    node.info = info!;\n    node.modtime = node.info.modtime;\n    node.usedBytes = 0;\n    if (!isDir) {\n      node.contentsOffset = node.info.contentsOffset;\n      node.usedBytes = node.info.size;\n    }\n  },\n  readdir(node) {\n    if (!node.info.children) {\n      throw new PythonWorkersInternalError(\n        'Trying to readdir from a non-dir node'\n      );\n    }\n    return Array.from(node.info.children.keys());\n  },\n  lookup(parent, name) {\n    if (!parent.info.children) {\n      throw new PythonWorkersInternalError(\n        'Trying to lookup from a non-dir node'\n      );\n    }\n    return parent.info.children.get(name)!;\n  },\n  read(stream, position, buffer) {\n    if (stream.node.contentsOffset == undefined) {\n      throw new PythonWorkersInternalError('contentsOffset is undefined');\n    }\n    if (!stream.node.info.reader) {\n      throw new PythonWorkersInternalError('reader is undefined');\n    }\n    return stream.node.info.reader.read(\n      stream.node.contentsOffset + position,\n      buffer\n    );\n  },\n};\n\nexport function createTarFS(Module: Module): EmscriptenFS<TarFSInfo> {\n  return createReadonlyFS(FSOps, Module);\n}\n"
  },
  {
    "path": "src/pyodide/internal/test_frozen_sdk.py",
    "content": "\"\"\"Test that the in-tree Python SDK (internal/workers-api/) is frozen.\n\nThe Python SDK now lives in https://github.com/cloudflare/workers-py and is\ninstalled from PyPI. The copy in this repository is kept only for backward\ncompatibility and MUST NOT be modified.\n\nIf this test fails, it means a file under internal/workers-api/ was changed.\nNew features should go to cloudflare/workers-py instead. If the change is a\ndeliberate backward-compatibility fix, update EXPECTED_HASHES below.\n\"\"\"\n\nimport hashlib\nimport unittest\nfrom pathlib import Path\n\nSDK_DIR = Path(__file__).parent / \"workers-api\"\n\n# SHA-256 hashes of every file in the frozen SDK, keyed by path relative to\n# internal/workers-api/.\n# Calculate with: find internal/workers-api -type f -exec sha256sum {} \\;\nEXPECTED_HASHES = {\n    \"pyproject.toml\": \"2ba30eeea93f2cf161fce735981b382d2ca1f5aee77e663f447743aabe8575cf\",\n    \"src/asgi.py\": \"da171340aa361f733d99d4a1e09c7fe440dd6c79fbca83e4f11d7c42f7622549\",\n    \"src/workers/__init__.py\": \"5db8f21adacc3ba625c8763c6e8c47220325109bdc4ec301d76925037844cfd7\",\n    \"src/workers/_workers.py\": \"a1e2d9b8199e4bb88c3e89e82dca037772d223bf58bcb1a241d9f03bc54282bd\",\n    \"src/workers/workflows.py\": \"9379fdf416da56d2400369165a51b72da83f724aea893ac785285631d09803bf\",\n    \"uv.lock\": \"8945fab16bffb1ea1fe5740ca677e40bf8fe28010325e4c389cd11b8a072a9fc\",\n}\n\n\ndef _sha256(path: Path) -> str:\n    return hashlib.sha256(path.read_bytes()).hexdigest()\n\n\nclass TestFrozenSDK(unittest.TestCase):\n    def test_no_files_modified(self):\n        \"\"\"Ensure no existing SDK files were modified.\"\"\"\n        for rel_path, expected_hash in sorted(EXPECTED_HASHES.items()):\n            file_path = SDK_DIR / rel_path\n            if not file_path.exists():\n                continue\n            actual_hash = _sha256(file_path)\n            self.assertEqual(\n                actual_hash,\n                expected_hash,\n                f\"Python SDK is frozen — {rel_path} was modified. \"\n                f\"New features belong in cloudflare/workers-py. \"\n                f\"If this is a deliberate backward-compat fix, update \"\n                f\"EXPECTED_HASHES in this test.\",\n            )\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "src/pyodide/internal/test_introspection.py",
    "content": "import sys\nimport unittest\nfrom contextlib import ExitStack\nfrom pathlib import Path\nfrom unittest.mock import MagicMock, patch\n\nDIR = Path(__file__).parent\n\nsys.path.append(str(DIR))\nsys.path.append(str(DIR / \"workers-api/src\"))\n\n\nclass TestIntrospection(unittest.TestCase):\n    @classmethod\n    def setup_gen(cls):\n        with ExitStack() as stack:\n            stack.enter_context(patch.dict(\"sys.modules\", js=MagicMock()))\n            stack.enter_context(\n                patch.dict(\"sys.modules\", _pyodide_entrypoint_helper=MagicMock())\n            )\n            stack.enter_context(\n                patch.dict(\"sys.modules\", _cloudflare_compat_flags=MagicMock())\n            )\n            stack.enter_context(\n                patch.dict(\"sys.modules\", {\"pyodide\": MagicMock(__version__=2)})\n            )\n            stack.enter_context(\n                patch.dict(\"sys.modules\", {\"pyodide.code\": MagicMock()})\n            )\n            stack.enter_context(patch.dict(\"sys.modules\", {\"pyodide.ffi\": MagicMock()}))\n            stack.enter_context(\n                patch.dict(\"sys.modules\", {\"pyodide.http\": MagicMock()})\n            )\n            yield\n\n    @classmethod\n    def setUpClass(cls):\n        cls.gen = cls.setup_gen()\n        next(cls.gen)\n\n    @classmethod\n    def tearDownClass(cls):\n        next(cls.gen, None)\n\n    def test_collect_methods(self):\n        from introspection import collect_methods\n\n        class A:\n            x = 2\n\n            @staticmethod\n            def f():\n                pass\n\n            @classmethod\n            def g(cls):\n                pass\n\n            @property\n            def y(self):\n                return 7\n\n            async def fetch(request):\n                pass\n\n        self.assertEqual(collect_methods(A), [\"fetch\"])\n\n        class B(A):\n            def some_method(self):\n                pass\n\n        self.assertEqual(collect_methods(B), [\"fetch\", \"some_method\"])\n\n        from workers import WorkerEntrypoint\n\n        class C(WorkerEntrypoint, B):\n            def third_method(self):\n                pass\n\n        self.assertEqual(collect_methods(C), [\"fetch\", \"some_method\", \"third_method\"])\n\n        class D(B, WorkerEntrypoint):\n            def third_method(self):\n                pass\n\n        self.assertEqual(collect_methods(C), [\"fetch\", \"some_method\", \"third_method\"])\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "src/pyodide/internal/topLevelEntropy/__init__.py",
    "content": ""
  },
  {
    "path": "src/pyodide/internal/topLevelEntropy/allow_entropy.py",
    "content": "# Control number of allowed entropy calls.\nimport sys\nfrom array import array\nfrom contextlib import contextmanager\n\nALLOWED_ENTROPY_CALLS = array(\"b\", [0])\nIN_REQUEST_CONTEXT = False\n\n\ndef in_request_context():\n    return IN_REQUEST_CONTEXT\n\n\ndef _set_in_request_context():\n    global IN_REQUEST_CONTEXT\n\n    IN_REQUEST_CONTEXT = True\n\n\ndef should_allow_entropy_call():\n    \"\"\"This helps us raise Python errors rather than fatal errors in some cases.\n\n    It doesn't really matter that much since we're not likely to recover from\n    these anyways but it feels better.\n    \"\"\"\n    # Allow if we've either entered request context or if we've temporarily\n    # enabled entropy.\n    return IN_REQUEST_CONTEXT or is_bad_entropy_enabled()\n\n\ndef raise_unless_entropy_allowed():\n    if not should_allow_entropy_call():\n        EIO = 29\n        raise OSError(EIO, \"Cannot get entropy outside of request context\")\n\n\ndef get_bad_entropy_flag():\n    # simpleRunPython reads out stderr. We put the address there so we can fish\n    # it out... We could use ctypes instead of array but ctypes weighs an extra\n    # 100kb compared to array.\n    print(ALLOWED_ENTROPY_CALLS.buffer_info()[0], file=sys.stderr)\n\n\ndef is_bad_entropy_enabled():\n    \"\"\"This is used in entropy_patches.py to let calls to disabled functions\n    through if we are allowing bad entropy\n    \"\"\"\n    return ALLOWED_ENTROPY_CALLS[0] > 0\n\n\n@contextmanager\ndef allow_bad_entropy_calls(n):\n    old_allowed_entropy_calls = ALLOWED_ENTROPY_CALLS[0]\n    ALLOWED_ENTROPY_CALLS[0] = n\n    yield\n    if ALLOWED_ENTROPY_CALLS[0] > 0:\n        raise RuntimeError(\n            f\"{ALLOWED_ENTROPY_CALLS[0]} unexpected leftover getentropy calls \"\n        )\n    ALLOWED_ENTROPY_CALLS[0] = old_allowed_entropy_calls\n"
  },
  {
    "path": "src/pyodide/internal/topLevelEntropy/allow_entropy.py.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ndeclare const buf: ArrayBuffer;\nexport default buf;\n"
  },
  {
    "path": "src/pyodide/internal/topLevelEntropy/entropy_import_context.py",
    "content": "\"\"\"\nManage import context for modules that use getentropy() at startup.\n\nWe install a metapath finder in import_patch_manager.py which executes the\nmodule in the context manager returned by\nget_entropy_import_context(module_name).\n\nThis module defines get_entropy_import_context which\n\n\"random\" and \"numpy.random.mtrand\" also have some additional patches that need\nto be installed as part of their import context to prevent top level crashes.\n\nOther rust packages are likely to need similar treatment to pydantic_core.\n\"\"\"\n\nimport sys\nfrom contextlib import contextmanager\nfrom functools import wraps\n\nfrom .allow_entropy import (\n    allow_bad_entropy_calls,\n    get_bad_entropy_flag,\n    raise_unless_entropy_allowed,\n)\nfrom .import_patch_manager import (\n    block_calls,\n    register_after_snapshot,\n    register_before_first_request,\n    register_create_patch,\n    register_exec_patch,\n)\n\n# Exported for snapshot backwards compatibility\n__all__ = [\"get_bad_entropy_flag\"]\n\nIMPORTED_RUST_PACKAGE = False\n\n\n@register_create_patch(\"tiktoken._tiktoken\")\n@register_exec_patch(\"cryptography.exceptions\")\n@register_exec_patch(\"jiter\")\n@contextmanager\ndef rust_package_context(module):\n    \"\"\"Rust packages need one entropy call if they create a rust hash map at\n    init time.\n\n    For reasons I don't entirely understand, in Pyodide 0.28 only the first Rust package to be\n    imported makes the get_entropy call. See gen_rust_import_tests() which tests that importing\n    four rust packages in different permutations works correctly.\n    \"\"\"\n    global IMPORTED_RUST_PACKAGE\n    if sys.version_info >= (3, 13) and IMPORTED_RUST_PACKAGE:\n        yield\n        return\n    IMPORTED_RUST_PACKAGE = True\n    with allow_bad_entropy_calls(1):\n        yield\n\n\nRANDOM_STATE = None\n\n\n@register_exec_patch(\"random\")\n@contextmanager\ndef random_exec(random):\n    \"\"\"Importing random calls getentropy() 10 times it seems\"\"\"\n    with allow_bad_entropy_calls(10):\n        yield\n\n    # Block calls to functions that use the bad random seed we produced from the\n    # ten getentropy() calls. Instantiating Random with a given seed is fine,\n    # instantiating it without a seed will call getentropy() and fail.\n    # Instantiating SystemRandom is fine, calling its methods will call\n    # getentropy() and fail.\n    global RANDOM_STATE\n    RANDOM_STATE = random.getstate()\n    block_calls(random, allowlist=(\"Random\", \"SystemRandom\"))\n\n\n@register_after_snapshot(\"random\")\ndef random_after_snapshot(random):\n    # Check that random seed hasn't been advanced somehow while executing top level scope\n    r1 = random.random()\n    random.setstate(RANDOM_STATE)\n    r2 = random.random()\n    if r1 != r2:\n        raise RuntimeError(\"random seed in bad state\")\n\n\n@register_before_first_request(\"random\")\ndef random_before_first_request(random):\n    random.seed()\n\n\norig_Random_seed = None\n\n\n@register_exec_patch(\"_random\")\n@contextmanager\ndef _random_exec(_random):\n    yield\n    global orig_Random_seed\n    orig_Random_seed = _random.Random.seed\n\n    @wraps(orig_Random_seed)\n    def patched_seed(self, val):\n        \"\"\"\n        Random.seed calls _PyOs_URandom which will fatally fail in top level.\n        Prevent this by raising a RuntimeError instead.\n        \"\"\"\n        if val is None:\n            raise_unless_entropy_allowed()\n        return orig_Random_seed(self, val)\n\n    _random.Random.seed = patched_seed\n\n\n@register_before_first_request(\"_random\")\ndef _random_before_first_request(_random):\n    _random.Random.seed = orig_Random_seed\n\n\nNUMPY_RANDOM_STATE = None\n\n\n@register_exec_patch(\"numpy.random\")\n@contextmanager\ndef numpy_random_context(numpy_random):\n    \"\"\"numpy.random doesn't call getentropy() itself, but we want to block calls\n    that might use the bad seed.\n\n    TODO: Maybe there are more calls we can whitelist?\n    TODO: Is it not enough to just block numpy.random.mtrand calls?\n    \"\"\"\n    yield\n    # Calling default_rng() with a given seed is fine, calling it without a seed\n    # will call getentropy() and fail.\n    block_calls(numpy_random, allowlist=(\"default_rng\", \"RandomState\"))\n\n\n@register_after_snapshot(\"numpy.random\")\ndef numpy_random_after_snapshot(numpy_random):\n    r1 = numpy_random.random()\n    numpy_random.set_state(NUMPY_RANDOM_STATE)\n    r2 = numpy_random.random()\n    if r1 != r2:\n        raise RuntimeError(\"random seed in bad state\")\n\n\n@register_before_first_request(\"numpy.random\")\ndef numpy_random_before_first_request(numpy_random):\n    numpy_random.seed()\n\n\n@register_exec_patch(\"numpy.random.mtrand\")\n@contextmanager\ndef numpy_random_mtrand_context(module):\n    # numpy.random.mtrand calls secrets.randbits at top level to seed itself.\n    # This will fail if we don't let it through.\n    with allow_bad_entropy_calls(1):\n        yield\n    # Block calls until we get a chance to replace the bad random seed.\n    global NUMPY_RANDOM_STATE\n    NUMPY_RANDOM_STATE = module.get_state()\n    block_calls(module, allowlist=(\"RandomState\",))\n\n\n@register_exec_patch(\"pydantic_core\")\n@contextmanager\ndef pydantic_core_context(module):\n    try:\n        # Initial import needs one entropy call to initialize\n        # std::collections::HashMap hash seed\n        with allow_bad_entropy_calls(1):\n            yield\n    finally:\n        try:\n            with allow_bad_entropy_calls(1):\n                # validate_core_schema makes an ahash::AHashMap which makes\n                # another entropy call for its hash seed. It will throw an error\n                # but only after making the needed entropy call.\n                module.validate_core_schema(None)\n        except module.SchemaError:\n            pass\n\n\n@register_exec_patch(\"aiohttp.http_websocket\")\n@contextmanager\ndef aiohttp_http_websocket_context(module):\n    import random\n\n    Random = random.Random\n\n    def patched_Random():\n        return random\n\n    random.Random = patched_Random\n    try:\n        yield\n    finally:\n        random.Random = Random\n\n\nclass NoSslFinder:\n    def find_spec(self, fullname, path, target):\n        if fullname == \"ssl\":\n            raise ModuleNotFoundError(\n                f\"No module named {fullname!r}\", name=fullname\n            ) from None\n\n\n@contextmanager\ndef no_ssl():\n    \"\"\"\n    Various packages will call ssl.create_default_context() at top level which uses entropy if they\n    can import ssl. By temporarily making importing ssl raise an import error, we exercise the\n    workaround code and so avoid the entropy calls. After, we put the ssl module back to the normal\n    value.\n    \"\"\"\n    try:\n        f = NoSslFinder()\n        ssl = sys.modules.pop(\"ssl\", None)\n        sys.meta_path.insert(0, f)\n        yield\n    finally:\n        sys.meta_path.remove(f)\n        if ssl:\n            sys.modules[\"ssl\"] = ssl\n\n\n@register_exec_patch(\"aiohttp.connector\")\n@contextmanager\ndef aiohttp_connector_context(module):\n    with no_ssl():\n        yield\n\n\n@register_exec_patch(\"requests.adapters\")\n@contextmanager\ndef requests_adapters_context(module):\n    with no_ssl():\n        yield\n\n\n@register_exec_patch(\"urllib3.util.ssl_\")\n@contextmanager\ndef urllib3_util_ssl__context(module):\n    with no_ssl():\n        yield\n\n\nclass DeterministicRandomNameSequence:\n    characters = \"abcdefghijklmnopqrstuvwxyz0123456789_\"\n\n    def __init__(self):\n        self.index = 0\n\n    def __iter__(self):\n        return self\n\n    def index_to_chars(self):\n        base = len(self.characters)\n        idx = self.index\n        s = []\n        for _ in range(8):\n            s.append(self.characters[idx % base])\n            idx //= base\n        return \"\".join(s)\n\n    def __next__(self):\n        self.index += 1\n        return self.index_to_chars()\n\n\n@register_exec_patch(\"tempfile\")\n@contextmanager\ndef tempfile_context(module):\n    yield\n    module._orig_RandomNameSequence = module._RandomNameSequence\n    module._RandomNameSequence = DeterministicRandomNameSequence\n\n\n@register_before_first_request(\"tempfile\")\ndef tempfile_restore_random_name_sequence(tempfile):\n    tempfile._RandomNameSequence = tempfile._orig_RandomNameSequence\n    del tempfile._orig_RandomNameSequence\n\n\n@register_exec_patch(\"multiprocessing.process\")\n@contextmanager\ndef multiprocessing_process_context(module):\n    # multiprocessing.process calls os.urandom() on import. It's harmless b/c multiprocessing is\n    # useless.\n    with allow_bad_entropy_calls(1):\n        yield\n\n\n@register_exec_patch(\"langsmith._internal._constants\")\n@contextmanager\ndef langsmith__internal__constants_context(module):\n    # Langsmith uses a UUID to communicate with a background thread. This obviously won't work so we\n    # might as well allow it to make a UUID.\n    with allow_bad_entropy_calls(1):\n        yield\n\n\n@register_exec_patch(\"langchain_openai.chat_models.base\")\n@contextmanager\ndef langchain_openai_chat_models_base_context(module):\n    if sys.version_info >= (3, 13):\n        # Creates an ssl context in the version used with 3.13\n        with allow_bad_entropy_calls(1):\n            yield\n    else:\n        yield\n"
  },
  {
    "path": "src/pyodide/internal/topLevelEntropy/entropy_import_context.py.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ndeclare const buf: ArrayBuffer;\nexport default buf;\n"
  },
  {
    "path": "src/pyodide/internal/topLevelEntropy/entropy_patches.py",
    "content": "\"\"\"\nHandle the top level getentropy() mess:\n\nThe C stdlib function getentropy() `getentropy()` calls\n`crpyto.getRandomValues()` but this throws an error at top level which causes a\nfatal error.\n\nGoals:\n\n1. Avoid top-level calls to the C stdlib function getentropy(), these fatally\n   fail. Patch these to raise Python errors instead.\n2. Allow top level import of `random` and `numpy.random` modules. These seed\n   themselves with the functions that we patched in step 1, we temporarily\n   replace the `getentropy()` calls with no-ops to let them through.\n3. Install wrapper modules at top level that only allow calls to a whitelisted\n   set of functions from `random` and `numpy.random` that don't use the bad\n   seeds that came from step 2.\n4. Put it all back.\n5. Reseed the rng before entering the request scope for the first time.\n\nSteps 1, part of 4, and 5 are handled here, steps 2, 3, and part of 4 are\nhandled in _cloudflare_random_overlays.\n\"\"\"\n\nimport os\nfrom functools import wraps\n\n# Import entropy_import_context for side effects\nfrom . import entropy_import_context  # noqa: F401\nfrom .allow_entropy import _set_in_request_context, raise_unless_entropy_allowed\nfrom .import_patch_manager import (\n    after_snapshot_handlers,\n    before_first_request_handlers,\n    install_import_patch_manager,\n    remove_import_patch_manager,\n)\n\n# Prevent calls to getentropy(). The intended way for `getentropy()` to fail is\n# to set an EIO error, which turns into a Python OSError, so we raise this same\n# error so that if we patch `getentropy` from the Emscripten C stdlib we can\n# remove these patches without changing the behavior.\n\n\norig_urandom = os.urandom\n\n\n@wraps(orig_urandom)\ndef patch_urandom(*args):\n    raise_unless_entropy_allowed()\n    return orig_urandom(*args)\n\n\ndef disable_urandom():\n    \"\"\"\n    Python os.urandom() calls C getentropy() which calls JS\n    crypto.getRandomValues() which throws at top level, fatally crashing the\n    interpreter.\n\n    TODO: Patch Emscripten's getentropy() to return EIO if\n    `crypto.getRandomValues()` throws. Then we can remove this.\n    \"\"\"\n    os.urandom = patch_urandom\n\n\ndef restore_urandom():\n    os.urandom = orig_urandom\n\n\ndef before_top_level():\n    disable_urandom()\n    install_import_patch_manager()\n\n\ndef after_snapshot():\n    remove_import_patch_manager()\n    for cb in after_snapshot_handlers:\n        cb()\n\n\ndef before_first_request():\n    _set_in_request_context()\n    restore_urandom()\n    remove_import_patch_manager()\n    for cb in before_first_request_handlers:\n        cb()\n"
  },
  {
    "path": "src/pyodide/internal/topLevelEntropy/entropy_patches.py.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ndeclare const buf: ArrayBuffer;\nexport default buf;\n"
  },
  {
    "path": "src/pyodide/internal/topLevelEntropy/import_patch_manager.py",
    "content": "\"\"\"\nA metapath finder which calls get_import_context(module_name). If it returns a\nvalue that is not None, this is interpreted as a context manager that should be\nused when executing the module top level scope.\n\nWhen we're done, we put back the original module. The wrapper module and wrapper\nstubs will persist in the wild, so we need to make sure they behave the same way\nas the originals after we put them back. This is controlled by the\nIN_REQUEST_CONTEXT variable.\n\"\"\"\n\nimport sys\nfrom collections.abc import Callable\nfrom contextlib import AbstractContextManager, nullcontext\nfrom dataclasses import dataclass\nfrom functools import partial, wraps\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from importlib.abc import Loader\n    from importlib.machinery import ModuleSpec\n    from types import ModuleType\n\n    CreateImportContext = Callable[[ModuleSpec], AbstractContextManager]\n    ExecImportContext = Callable[[ModuleType], AbstractContextManager]\n    Handler = Callable[[ModuleType], None]\nelse:\n    from typing import Any\n\n    Handler = Any\n    CreateImportContext = Any\n    ExecImportContext = Any\n    Loader = Any\n    ModuleSpec = Any\n    ModuleType = Any\n\n\n@dataclass\nclass PatchInfo:\n    create: CreateImportContext = nullcontext\n    exec: ExecImportContext = nullcontext\n    after_snapshot: Handler | None = None\n    before_first_request: Handler | None = None\n\n\npatches: dict[str, PatchInfo] = {}\n\n\n# The public-facing interface here is:\n# * register_create_patch\n# * register_exec_patch\n# * register_before_first_request\n# * block_calls\n\n\ndef register_create_patch(\n    name: str, context_manager: CreateImportContext | None = None\n) -> None:\n    \"\"\"This registers a context_manager that will be used around the create_module call when the\n    package named \"name\" is imported.\n\n    It can either be used like register_exec_patch(\"cryptography.exceptions\", rust_package_context)\n    or as a decorator.\n    \"\"\"\n    if context_manager is None:\n        return partial(register_create_patch, name)\n    d = patches.setdefault(name, PatchInfo())\n    d.create = context_manager\n    return context_manager\n\n\ndef register_exec_patch(\n    name: str, context_manager: ExecImportContext | None = None\n) -> None:\n    \"\"\"This registers a context_manager that will be used around the exec_module call when the\n    package named \"name\" is imported.\n\n    It can either be used like register_create_patch(\"tiktoken._tiktoken\", rust_package_context)\n    or as a decorator.\n    \"\"\"\n    if context_manager is None:\n        return partial(register_exec_patch, name)\n    d = patches.setdefault(name, PatchInfo())\n    d.exec = context_manager\n    return context_manager\n\n\n# Question: How do I decide whether to use register_create_patch or register_exec_patch?\n#\n# Answer: For pure Python packages, use register_exec_patch(). For extension modules, figure it out\n# by trial and error.\n\n\ndef register_after_snapshot(name: str, handler: Handler | None = None):\n    if handler is None:\n        return partial(register_after_snapshot, name)\n    d = patches.setdefault(name, PatchInfo())\n    d.after_snapshot = handler\n    return handler\n\n\ndef register_before_first_request(name: str, handler: Handler | None = None) -> None:\n    \"\"\"This registers a callback that will be called before the first request if the package named\n    \"name\" was imported. Used for reseeding rng for instance.\n    \"\"\"\n    if handler is None:\n        return partial(register_before_first_request, name)\n    d = patches.setdefault(name, PatchInfo())\n    d.before_first_request = handler\n    return handler\n\n\nafter_snapshot_handlers: list[Handler] = []\nbefore_first_request_handlers: list[Handler] = []\n\n\nclass PatchLoader:\n    \"\"\"Loader that calls the original exec_module in the given context manager\"\"\"\n\n    def __init__(self, orig_loader: Loader, patch_info: PatchInfo):\n        self.orig_loader = orig_loader\n        self.patch_info = patch_info\n\n    def __getattr__(self, name):\n        return getattr(self.orig_loader, name)\n\n    def create_module(self, spec: ModuleSpec) -> ModuleType | None:\n        with self.patch_info.create(spec):\n            return self.orig_loader.create_module(spec)\n\n    def exec_module(self, module: ModuleType) -> None:\n        if handler := self.patch_info.after_snapshot:\n            after_snapshot_handlers.append(partial(handler, module))\n        if handler := self.patch_info.before_first_request:\n            before_first_request_handlers.append(partial(handler, module))\n        with self.patch_info.exec(module):\n            self.orig_loader.exec_module(module)\n\n\nclass PatchFinder:\n    \"\"\"Finder that returns our PatchLoader if get_import_context returns an import\n    context for the module. Otherwise, return None.\n    \"\"\"\n\n    def invalidate_caches(self):\n        pass\n\n    def find_spec(\n        self,\n        fullname: str,\n        path,\n        target,\n    ):\n        import_context = patches.get(fullname, None)\n        if import_context is None:\n            # Not ours\n            return None\n\n        for finder in sys.meta_path:\n            if isinstance(finder, PatchFinder):\n                # Avoid infinite recurse. Presumably this is the first entry.\n                continue\n            spec = finder.find_spec(fullname, path, target)\n            if spec:\n                # Found original module spec\n                break\n        else:\n            # Not found. This is going to be an ImportError.\n            return None\n        # Overwrite the loader with our wrapped loader\n        spec.loader = PatchLoader(spec.loader, import_context)\n        return spec\n\n    @staticmethod\n    def install():\n        sys.meta_path.insert(0, PatchFinder())\n\n    @staticmethod\n    def remove():\n        for idx, val in enumerate(sys.meta_path):  # noqa:B007\n            if isinstance(val, PatchFinder):\n                break\n        del sys.meta_path[idx]\n\n\ndef install_import_patch_manager():\n    PatchFinder.install()\n\n\ndef remove_import_patch_manager():\n    PatchFinder.remove()\n    unblock_calls()\n\n\n# We remove the metapath entry and replace the patched sys.modules entries with\n# the original modules before the request context, but the patched copies can\n# still be used from top level imports. When IN_REQUEST_CONTEXT is True, we need\n# to make sure that our patches behave like the original imports.\nIN_REQUEST_CONTEXT = False\n# Keep track of the unblocked modules so we can put them backk into sys.modules\n# when we're done.\nORIG_MODULES = {}\n\n\ndef block_calls(module, *, allowlist=()):\n    \"\"\"Make top level calls to methods from the module that are not in allowlist fail.\n\n    It gets removed automatically before the first request. Generally used with\n    register_before_first_request.\n    \"\"\"\n    sys.modules[module.__name__] = BlockedCallModule(module, allowlist)\n    ORIG_MODULES[module.__name__] = module\n\n\ndef unblock_calls():\n    # Remove the patches when we're ready to enable entropy calls.\n    global IN_REQUEST_CONTEXT\n\n    IN_REQUEST_CONTEXT = True\n    for name, val in ORIG_MODULES.items():\n        sys.modules[name] = val\n\n\nclass BlockedCallModule:\n    \"\"\"A proxy class that wraps a module that we want to block calls to\n\n    Attribute access is passed on to the original module but if the result is a\n    callable that isn't in the allow list, we wrap it with a function that\n    raises an error unless IN_REQUEST_CONTEXT is true.\n\n    Note that because we define __getattribute__ and __setattr__, we cannot do\n    direct reads or assignments e.g., `self.a = 1`. This risks recursion errors\n    if there is a typo. Instead, we have to call super().__setattr__.\n\n    This has the advantage that it avoids name clashes if the proxied module\n    actually defines variables called _mod or _allow_list.\n    \"\"\"\n\n    def __init__(self, module, allowlist):\n        super().__setattr__(\"_mod\", module)\n        super().__setattr__(\"_allow_list\", allowlist)\n\n    def __getattribute__(self, key):\n        mod = super().__getattribute__(\"_mod\")\n        orig = getattr(mod, key)\n        if IN_REQUEST_CONTEXT:\n            return orig\n        if not callable(orig):\n            return orig\n\n        if key in super().__getattribute__(\"_allow_list\"):\n            return orig\n\n        # If we aren't in a request scope, the value is a callable, and it's not\n        # in the allow_list, return a wrapper that raises an error if it's\n        # called before entering the request scope.\n        # TODO: this doesn't wrap classes correctly, does it matter?\n        @wraps(orig)\n        def wrapper(*args, **kwargs):\n            if not IN_REQUEST_CONTEXT:\n                raise RuntimeError(\n                    f\"Cannot use {mod.__name__}.{key}() outside of request context\"\n                )\n            return orig(*args, **kwargs)\n\n        return wrapper\n\n    def __setattr__(self, key, val):\n        mod = super().__getattribute__(\"_mod\")\n        setattr(mod, key, val)\n\n    def __dir__(self):\n        mod = super().__getattribute__(\"_mod\")\n        return dir(mod)\n"
  },
  {
    "path": "src/pyodide/internal/topLevelEntropy/import_patch_manager.py.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ndeclare const buf: ArrayBuffer;\nexport default buf;\n"
  },
  {
    "path": "src/pyodide/internal/topLevelEntropy/lib.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n/**\n * Handle the top level getentropy() mess. See entropy_patches.py which is the\n * main file for the entropy patches.\n *\n * This file installs the relevant files and calls the exports from\n * entropy_patches.py. setupShouldAllowBadEntropy reads out the address of the\n * byte that we use to control calls to crypto.getRandomValues from Python.\n */\nimport { default as entropyPatches } from 'pyodide-internal:topLevelEntropy/entropy_patches.py';\nimport { default as entropyImportContext } from 'pyodide-internal:topLevelEntropy/entropy_import_context.py';\nimport { default as importPatchManager } from 'pyodide-internal:topLevelEntropy/import_patch_manager.py';\nimport { default as allowEntropy } from 'pyodide-internal:topLevelEntropy/allow_entropy.py';\nimport { simpleRunPython, PythonUserError } from 'pyodide-internal:util';\nimport { CHECK_RNG_STATE } from 'pyodide-internal:metadata';\n\nlet allowed_entropy_calls_addr: number;\n\n/**\n * Set up a byte for communication between JS and Python.\n *\n * We make an array in Python and then get its address in JavaScript so\n * shouldAllowBadEntropy can check / write back the value\n */\nfunction setupShouldAllowBadEntropy(Module: Module): void {\n  // get_bad_entropy_flag prints the address we want into stderr which is returned into res.\n  // We parse this as an integer.\n  const res = simpleRunPython(\n    Module,\n    'from _cloudflare.entropy_import_context import get_bad_entropy_flag;' +\n      'get_bad_entropy_flag();' +\n      'del get_bad_entropy_flag'\n  );\n  allowed_entropy_calls_addr = Number(res);\n}\n\nfunction shouldAllowBadEntropy(Module: Module): boolean {\n  const val = Module.HEAP8[allowed_entropy_calls_addr];\n  if (val) {\n    Module.HEAP8[allowed_entropy_calls_addr]!--;\n    return true;\n  }\n  return false;\n}\n\nlet IN_REQUEST_CONTEXT = false;\n\n/**\n * Some packages need hash or random seeds at import time. We carefully track\n * how much bad entropy we're giving everyone so that hopefully none of it ends\n * up in a place where the end user needed good entropy. In particular, we think\n * it's acceptable to give poor entropy for hash seeds but not for random seeds.\n * The random libraries are allowed to initialize themselves with a bad seed but\n * we disable them until we have a chance to reseed.\n *\n * See entropy_import_context.py where `allow_bad_entropy_calls` is used to dole\n * out the bad entropy.\n */\nexport function getRandomValues(Module: Module, arr: Uint8Array): Uint8Array {\n  if (IN_REQUEST_CONTEXT) {\n    return crypto.getRandomValues(arr);\n  }\n  if (!shouldAllowBadEntropy(Module)) {\n    console.log('Entropy call failed');\n    console.log('JS stack:', new Error().stack);\n    console.log('Python stack:');\n    Module._dump_traceback();\n    throw new PythonUserError(\n      'Disallowed operation called within global scope'\n    );\n  }\n  // \"entropy\" in the test suite is a bunch of 42's. Good to use a readily identifiable pattern\n  // here which is different than the test suite.\n  arr.fill(43);\n  return arr;\n}\n\n/**\n * We call this regardless of whether we are restoring from a snapshot or not,\n * after instantiating the Emscripten module but before restoring the snapshot.\n * Hypothetically, we could skip it for new dedicated snapshots.\n */\nexport function entropyMountFiles(Module: Module): void {\n  const cloudflareDir = Module.FS.sitePackages + '/_cloudflare';\n  Module.FS.mkdir(cloudflareDir);\n  Module.FS.writeFile(cloudflareDir + '/__init__.py', new Uint8Array(0), {\n    canOwn: true,\n  });\n  Module.FS.writeFile(\n    cloudflareDir + '/entropy_patches.py',\n    new Uint8Array(entropyPatches),\n    { canOwn: true }\n  );\n  Module.FS.writeFile(\n    cloudflareDir + '/entropy_import_context.py',\n    new Uint8Array(entropyImportContext),\n    { canOwn: true }\n  );\n  Module.FS.writeFile(\n    cloudflareDir + '/import_patch_manager.py',\n    new Uint8Array(importPatchManager),\n    { canOwn: true }\n  );\n  Module.FS.writeFile(\n    cloudflareDir + '/allow_entropy.py',\n    new Uint8Array(allowEntropy),\n    { canOwn: true }\n  );\n}\n\n/**\n * This prepares us to execute the top level scope. It changes JS state so it\n * needs to be called whether restoring snapshot or not. We have to call this\n * after the runtime is ready, so after restoring the snapshot in the snapshot\n * branch and after entropyMountFiles in the no-snapshot branch.\n */\nexport function entropyAfterRuntimeInit(Module: Module): void {\n  setupShouldAllowBadEntropy(Module);\n}\n\n/**\n * This prepares us to execute the top level scope. It changes only Python state\n * so it doesn't need to be called when restoring from snapshot.\n */\nexport function entropyBeforeTopLevel(Module: Module): void {\n  simpleRunPython(\n    Module,\n    `\nfrom _cloudflare.entropy_patches import before_top_level\nbefore_top_level()\ndel before_top_level\n`\n  );\n}\n\n/**\n * Called to check that random number generator state was not advanced by top level calls. We\n * manually install overlays that crash when they are called in order to prevent this situation.\n */\nexport function entropyAfterSnapshot(Module: Module): void {\n  if (!CHECK_RNG_STATE) {\n    return;\n  }\n  simpleRunPython(\n    Module,\n    `\nfrom _cloudflare.entropy_patches import after_snapshot\nafter_snapshot()\ndel after_snapshot\n    `\n  );\n}\n\nlet isReady = false;\n/**\n * Called to reseed rngs and turn off blocks that prevent access to rng APIs.\n */\nexport function entropyBeforeRequest(Module: Module): void {\n  if (isReady) {\n    // I think this is only ever called once, but we guard it just to be sure.\n    return;\n  }\n  IN_REQUEST_CONTEXT = true;\n  isReady = true;\n  simpleRunPython(\n    Module,\n    `\nfrom _cloudflare.entropy_patches import before_first_request\nbefore_first_request()\ndel before_first_request\n    `\n  );\n}\n"
  },
  {
    "path": "src/pyodide/internal/util.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n/**\n * This is an exception we should be throwing whenever there is something unexpected in our runtime\n * that is **not** a result of the user doing something wrong, i.e. it's an internal error that is\n * a result of a bug in our runtime.\n */\nexport class PythonWorkersInternalError extends Error {\n  override get name(): string {\n    return this.constructor.name;\n  }\n}\n\n/**\n * This is an exception we throw whenever there is an issue with the user's code, i.e. it's a result\n * of the user doing something wrong.\n */\nexport class PythonUserError extends Error {\n  override get name(): string {\n    return this.constructor.name;\n  }\n}\n\n// Split the stack into lines and print them individually.\n// We do this because edgeworker's test runner will put a multiline log all on one line. This is\n// very hard to read.\nexport function reportError(e: Error): never {\n  e.stack?.split('\\n').forEach((s: string) => {\n    console.warn(s);\n  });\n  throw e;\n}\n\n/**\n *  Simple as possible runPython function which works with no foreign function\n *  interface. We need to use this rather than the normal easier to use\n *  interface because the normal interface doesn't work until after\n *  `API.finalizeBootstrap`, but `API.finalizeBootstrap` makes changes inside\n *  and outside the linear memory which have to stay in sync. It's hard to keep\n *  track of the invariants that `finalizeBootstrap` introduces between JS land\n *  and the linear memory so we do this.\n *\n *  We wrap API.rawRun which does the following steps:\n *  1. use textEncoder.encode to convert `code` into UTF8 bytes\n *  2. malloc space for `code` in the wasm linear memory and copy the encoded\n *      `code` to this pointer\n *  3. redirect standard error to a temporary buffer\n *  4. call `PyRun_SimpleString`, which either works and returns 0 or formats a\n *      traceback to stderr and returns -1\n *      https://docs.python.org/3/c-api/veryhigh.html?highlight=simplestring#c.PyRun_SimpleString\n *  5. frees the `code` pointer\n *  6. Returns the return value from `PyRun_SimpleString` and whatever\n *      information went to stderr.\n *\n *  PyRun_SimpleString executes the code at top level in the `__main__` module,\n *  so all variables defined get leaked into the global namespace unless we\n *  clean them up explicitly.\n */\nexport function simpleRunPython(\n  emscriptenModule: Module,\n  code: string\n): string {\n  const [status, cause] = emscriptenModule.API.rawRun(code);\n  // status 0: Ok\n  // status -1: Error\n  if (status === -1) {\n    // PyRun_SimpleString will have written a Python traceback to stderr.\n    console.warn('Command failed:', code);\n    console.warn(cause);\n    throw new PythonWorkersInternalError(\n      'Failed to run Python code:\\n' + code + '\\n\\nError:\\n' + cause\n    );\n  }\n  return cause;\n}\n\nexport function invalidateCaches(Module: Module): void {\n  simpleRunPython(\n    Module,\n    `from importlib import invalidate_caches; invalidate_caches(); del invalidate_caches`\n  );\n}\n\nexport function unreachable(obj: never, msg?: string): never {\n  if (msg === undefined) {\n    msg = obj;\n  }\n  throw new PythonWorkersInternalError(`Unreachable: ${msg}`);\n}\n"
  },
  {
    "path": "src/pyodide/internal/workers-api/pyproject.toml",
    "content": "[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"python-workers-runtime-types\"\nversion = \"0.1.0\"\ndescription = \"Python runtime types for Cloudflare Workers\"\nrequires-python = \">=3.12\"\n\n[tool.hatch.build.targets.wheel]\nsources = [\"src\"]\ninclude = [\"src/*\"]\n\n[dependency-groups]\ndev = [\n    \"build\",\n]\n"
  },
  {
    "path": "src/pyodide/internal/workers-api/src/asgi.py",
    "content": "import logging\nfrom asyncio import Event, Future, Queue, create_task, ensure_future, sleep\nfrom collections.abc import Awaitable\nfrom contextlib import contextmanager\nfrom inspect import isawaitable\nfrom typing import Any\n\nimport js\nfrom workers import Context, Request\n\nASGI = {\"spec_version\": \"2.0\", \"version\": \"3.0\"}\nlogger = logging.getLogger(\"asgi\")\nbackground_tasks = set()\n\n\ndef run_in_background(coro: Awaitable[Any]) -> None:\n    fut = ensure_future(coro)\n    background_tasks.add(fut)\n\n    def _on_done(f):\n        background_tasks.discard(f)\n        exc = f.exception() if not f.cancelled() else None\n        if exc is not None:\n            logger.error(\"Unhandled exception in background task\", exc_info=exc)\n\n    fut.add_done_callback(_on_done)\n\n\n@contextmanager\ndef acquire_js_buffer(pybuffer):\n    from pyodide.ffi import create_proxy\n\n    px = create_proxy(pybuffer)\n    buf = px.getBuffer()\n    px.destroy()\n    try:\n        yield buf.data\n    finally:\n        buf.release()\n\n\ndef request_to_scope(req, env, ws=False):\n    from js import URL\n\n    # @app.get(\"/example\")\n    # async def example(request: Request):\n    #     request.headers.get(\"content-type\")\n    # - this will error if header is not \"bytes\" as in ASGI spec.\n\n    # Support both JS and Python http.client.HTTPMessage headers.\n    req_headers = req.headers.items() if isinstance(req, Request) else req.headers\n\n    headers = [(k.lower().encode(), v.encode()) for k, v in req_headers]\n    url = URL.new(req.url)\n    assert url.protocol[-1] == \":\"\n    scheme = url.protocol[:-1]\n    path = url.pathname\n    assert \"?\".startswith(url.search[0:1])\n    query_string = url.search[1:].encode()\n    if ws:\n        ty = \"websocket\"\n    else:\n        ty = \"http\"\n    return {\n        \"asgi\": ASGI,\n        \"headers\": headers,\n        \"http_version\": \"1.1\",\n        \"method\": req.method,\n        \"scheme\": scheme,\n        \"path\": path,\n        \"query_string\": query_string,\n        \"type\": ty,\n        \"env\": env,\n    }\n\n\nasync def start_application(app):\n    shutdown_future = Future()\n\n    async def shutdown():\n        shutdown_future.set_result(None)\n        await sleep(0)\n\n    it = iter([{\"type\": \"lifespan.startup\"}, Future()])\n\n    async def receive():\n        res = next(it)\n        if isawaitable(res):\n            await res\n        return res\n\n    ready = Future()\n\n    async def send(got):\n        if got[\"type\"] == \"lifespan.startup.complete\":\n            ready.set_result(None)\n            return\n        if got[\"type\"] == \"lifespan.shutdown.complete\":\n            return\n        raise RuntimeError(f\"Unexpected lifespan event {got['type']}\")\n\n    run_in_background(\n        app(\n            {\n                \"asgi\": ASGI,\n                \"state\": {},\n                \"type\": \"lifespan\",\n            },\n            receive,\n            send,\n        )\n    )\n    await ready\n    return shutdown\n\n\nasync def process_request(\n    app: Any, req: \"Request | js.Request\", env: Any, ctx: Context\n) -> js.Response:\n    from js import Object, Response, TransformStream\n\n    from pyodide.ffi import create_proxy\n\n    status = None\n    headers = None\n    result = Future()\n    is_sse = False\n    finished_response = Event()\n\n    receive_queue = Queue()\n    if req.body:\n        async for data in req.body:\n            await receive_queue.put(\n                {\n                    \"body\": data.to_bytes(),\n                    \"more_body\": True,\n                    \"type\": \"http.request\",\n                }\n            )\n    await receive_queue.put({\"body\": b\"\", \"more_body\": False, \"type\": \"http.request\"})\n\n    async def receive():\n        message = None\n        if not receive_queue.empty():\n            message = await receive_queue.get()\n        else:\n            await finished_response.wait()\n            message = {\"type\": \"http.disconnect\"}\n        return message\n\n    # Create a transform stream for handling streaming responses\n    transform_stream = TransformStream.new()\n    readable = transform_stream.readable\n    writable = transform_stream.writable\n    writer = writable.getWriter()\n\n    async def send(got):\n        nonlocal status\n        nonlocal headers\n        nonlocal is_sse\n\n        if got[\"type\"] == \"http.response.start\":\n            status = got[\"status\"]\n            # Like above, we need to convert byte-pairs into string explicitly.\n            headers = [(k.decode(), v.decode()) for k, v in got[\"headers\"]]\n            # Check if this is a server-sent events response\n            for k, v in headers:\n                if k.lower() == \"content-type\" and v.lower().startswith(\n                    \"text/event-stream\"\n                ):\n                    is_sse = True\n                    break\n            if is_sse:\n                # For SSE, create and return the response immediately after http.response.start\n                resp = Response.new(\n                    readable, headers=Object.fromEntries(headers), status=status\n                )\n                result.set_result(resp)\n\n        elif got[\"type\"] == \"http.response.body\":\n            body = got[\"body\"]\n            more_body = got.get(\"more_body\", False)\n\n            # Convert body to JS buffer\n            px = create_proxy(body)\n            buf = px.getBuffer()\n            px.destroy()\n\n            if is_sse:\n                # For SSE, write chunk to the stream\n                await writer.write(buf.data)\n                # If this is the last chunk, close the writer\n                if not more_body:\n                    await writer.close()\n                    finished_response.set()\n            else:\n                resp = Response.new(\n                    buf.data, headers=Object.fromEntries(headers), status=status\n                )\n                result.set_result(resp)\n                await writer.close()\n                finished_response.set()\n\n    # Run the application in the background to handle SSE\n    async def run_app():\n        try:\n            await app(request_to_scope(req, env), receive, send)\n\n            # If we get here and no response has been set yet, the app didn't generate a response\n            if not result.done():\n                raise RuntimeError(\"The application did not generate a response\")  # noqa: TRY301\n        except Exception as e:\n            if not result.done():\n                result.set_exception(e)\n                await writer.close()  # Close the writer\n                finished_response.set()\n            else:\n                # Response already sent — exception can't be propagated to the\n                # client, so log it to avoid silently swallowing errors.\n                logger.exception(\"Exception in ASGI application after response started\")\n\n    # Create task to run the application in the background\n    app_task = create_task(run_app())\n\n    # Wait for the result (the response)\n    response = await result\n\n    # For non-SSE responses, we need to wait for the application to complete\n    if not is_sse:\n        await app_task\n    else:  # noqa: PLR5501\n        if ctx is not None:\n            ctx.waitUntil(create_proxy(app_task))\n        else:\n            raise RuntimeError(\n                \"Server-Side-Events require ctx to be passed to asgi.fetch\"\n            )\n    return response\n\n\nasync def process_websocket(app: Any, req: \"Request | js.Request\") -> js.Response:\n    from js import Response, WebSocketPair\n\n    client, server = WebSocketPair.new().object_values()\n    server.accept()\n    queue = Queue()\n\n    def onopen(evt):\n        msg = {\"type\": \"websocket.connect\"}\n        queue.put_nowait(msg)\n\n    # onopen doesn't seem to get called. WS lifecycle events are a bit messed up\n    # here.\n    onopen(1)\n\n    def onclose(evt):\n        msg = {\"type\": \"websocket.close\", \"code\": evt.code, \"reason\": evt.reason}\n        queue.put_nowait(msg)\n\n    def onmessage(evt):\n        msg = {\"type\": \"websocket.receive\", \"text\": evt.data}\n        queue.put_nowait(msg)\n\n    server.onopen = onopen\n    server.onopen = onclose\n    server.onmessage = onmessage\n\n    async def ws_send(got):\n        if got[\"type\"] == \"websocket.send\":\n            b = got.get(\"bytes\", None)\n            s = got.get(\"text\", None)\n            if b:\n                with acquire_js_buffer(b) as jsbytes:\n                    # Unlike the `Response` constructor,  server.send seems to\n                    # eagerly copy the source buffer\n                    server.send(jsbytes)\n            if s:\n                server.send(s)\n\n        else:\n            logger.warning(\" == Not implemented %s\", got[\"type\"])\n\n    async def ws_receive():\n        received = await queue.get()\n        return received\n\n    env = {}\n    run_in_background(app(request_to_scope(req, env, ws=True), ws_receive, ws_send))\n\n    return Response.new(None, status=101, webSocket=client)\n\n\nasync def fetch(\n    app: Any, req: \"Request | js.Request\", env: Any, ctx: Context | None = None\n) -> js.Response:\n    logger.debug(\"ASGI request: %s %s\", req.method, req.url)\n    shutdown = await start_application(app)\n    try:\n        result = await process_request(app, req, env, ctx)\n    except Exception:\n        logger.exception(\"ASGI request failed\")\n        raise\n    await shutdown()\n    return result\n\n\nasync def websocket(app: Any, req: \"Request | js.Request\") -> js.Response:\n    return await process_websocket(app, req)\n\n\ndef __getattr__(name):\n    if name == \"env\":\n        from fastapi import Depends, Request\n\n        @Depends\n        async def env(request: Request):\n            return request.scope[\"env\"]\n\n        return env\n\n    raise AttributeError(f\"module {__name__!r} has no attribute {name!r}\")\n"
  },
  {
    "path": "src/pyodide/internal/workers-api/src/workers/__init__.py",
    "content": "# Python SDK for workerd now lives in https://github.com/cloudflare/workers-py\n# All new features should be implemented there and this module is only kept for backward compatibility\n\nfrom ._workers import (\n    Blob,\n    BlobEnding,\n    BlobValue,\n    Body,\n    Context,\n    DurableObject,\n    FetchKwargs,\n    FetchResponse,\n    File,\n    FormData,\n    FormDataValue,\n    Headers,\n    JSBody,\n    Request,\n    RequestInitCfProperties,\n    Response,\n    WorkerEntrypoint,\n    WorkflowEntrypoint,\n    fetch,\n    handler,\n    import_from_javascript,\n    patch_env,\n    python_from_rpc,\n    python_to_rpc,\n)\n\n__all__ = [\n    \"Blob\",\n    \"BlobEnding\",\n    \"BlobValue\",\n    \"Body\",\n    \"Context\",\n    \"DurableObject\",\n    \"FetchKwargs\",\n    \"FetchResponse\",\n    \"File\",\n    \"FormData\",\n    \"FormDataValue\",\n    \"Headers\",\n    \"JSBody\",\n    \"Request\",\n    \"RequestInitCfProperties\",\n    \"Response\",\n    \"WorkerEntrypoint\",\n    \"WorkflowEntrypoint\",\n    \"env\",\n    \"fetch\",\n    \"handler\",\n    \"import_from_javascript\",\n    \"patch_env\",\n    \"python_from_rpc\",\n    \"python_to_rpc\",\n    \"waitUntil\",\n]\n\n\ndef __getattr__(key):\n    if key == \"env\":\n        cloudflare_workers = import_from_javascript(\"cloudflare:workers\")\n        return cloudflare_workers.env\n    if key == \"waitUntil\":\n        cloudflare_workers = import_from_javascript(\"cloudflare:workers\")\n        return cloudflare_workers.waitUntil\n    raise AttributeError(f\"module {__name__!r} has no attribute {key!r}\")\n"
  },
  {
    "path": "src/pyodide/internal/workers-api/src/workers/_workers.py",
    "content": "# This module defines a Workers API for Python. It is similar to the API provided by\n# JS Workers, but with changes and additions to be more idiomatic to the Python\n# programming language.\nimport datetime\nimport functools\nimport inspect\nimport json\nfrom asyncio import create_task, gather\nfrom collections.abc import (\n    Awaitable,\n    Generator,\n    Iterable,\n    Iterator,\n    MutableMapping,\n    Sequence,\n)\nfrom contextlib import ExitStack, contextmanager\nfrom enum import StrEnum\nfrom http import HTTPMethod, HTTPStatus\nfrom types import LambdaType\nfrom typing import Any, Never, Protocol, TypedDict, Unpack\n\nimport _cloudflare_compat_flags\n\n# Get globals modules and import function from the entrypoint-helper\nimport _pyodide_entrypoint_helper\nimport js\nfrom js import Object\n\nimport pyodide.http\nfrom pyodide import __version__ as pyodide_version\nfrom pyodide.ffi import (\n    JsBuffer,\n    JsException,\n    JsProxy,\n    create_proxy,\n    destroy_proxies,\n    to_js,\n)\nfrom pyodide.http import pyfetch\nfrom workers.workflows import NonRetryableError\n\n\nclass Context(Protocol):\n    def waitUntil(self, other: Awaitable[Any]) -> None: ...\n\n\ntry:\n    from pyodide.ffi import jsnull\nexcept ImportError:\n    jsnull = None\n\n\ndef _jsnull_to_none(x):\n    if x is jsnull:\n        return None\n    return x\n\n\ndef import_from_javascript(module_name: str) -> Any:\n    \"\"\"\n    Import a JavaScript ES module from Python.\n\n    Args:\n        module_name: The name of the module to import. This can be a module name or a path.\n\n    Returns:\n        The imported module object.\n\n    Example:\n        cloudflare_workers = import_from_javascript(\"cloudflare:workers\")\n        env = cloudflare_workers.env\n\n    Note:\n        Behind the scenes import_from_javascript uses JSPI to do imports but that means we need an\n        async context. To enable importing cloudflare:workers and cloudflare:sockets in the global\n        scope we specifically imported them in the global scope and exposed them here.\n    \"\"\"\n    # Special case for global scope available modules\n    # JSPI won't work in the global scope in 0.26.0a2 so we need modules importable in the global\n    # scope to be imported beforehand.\n    if module_name == \"cloudflare:workers\":\n        return _pyodide_entrypoint_helper.cloudflareWorkersModule\n    elif module_name == \"cloudflare:sockets\":\n        return _pyodide_entrypoint_helper.cloudflareSocketsModule\n\n    try:\n        from pyodide.ffi import run_sync\n\n        # Call the JavaScript import function\n        return run_sync(_pyodide_entrypoint_helper.doAnImport(module_name))\n    except JsException as e:\n        raise ImportError(f\"Failed to import '{module_name}': {e}\") from e\n    except RuntimeError as e:\n        if e.args[0] == \"No suspender\":\n            raise ImportError(\n                f\"Failed to import '{module_name}': Only 'cloudflare:workers' and 'cloudflare:sockets' are available in the global scope.\"\n            ) from e\n        raise\n    except ImportError as e:\n        if e.args[0].startswith(\"cannot import name 'run_sync' from 'pyodide.ffi'\"):\n            raise ImportError(\n                f\"Failed to import '{module_name}': Only 'cloudflare:workers' and 'cloudflare:sockets' are available until the next python runtime version.\"\n            ) from e\n        raise\n\n\n@contextmanager\ndef patch_env(\n    d: dict[str, Any] | Sequence[tuple[str, Any]] | None = None, **kwds: dict[str, Any]\n) -> Iterator[None]:\n    if d:\n        kwds = dict(d) | kwds\n    yield from _pyodide_entrypoint_helper.patch_env_helper(to_js(kwds))\n\n\ntype JSBody = (\n    \"js.Blob | JsBuffer | js.FormData | js.ReadableStream | js.URLSearchParams\"\n)\ntype Body = \"str | FormData | JSBody\"\ntype Headers = \"dict[str, str] | list[tuple[str, str]] | js.Headers\"\n\n\n# https://developers.cloudflare.com/workers/runtime-apis/request/#the-cf-property-requestinitcfproperties\nclass RequestInitCfProperties(TypedDict, total=False):\n    apps: bool | None\n    cacheEverything: bool | None\n    cacheKey: str | None\n    cacheTags: list[str] | None\n    cacheTtl: int\n    cacheTtlByStatus: dict[str, int]\n    image: (\n        Any | None\n    )  # TODO: https://developers.cloudflare.com/images/transform-images/transform-via-workers/\n    mirage: bool | None\n    polish: str | None\n    resolveOverride: str | None\n    scrapeShield: bool | None\n    webp: bool | None\n\n\n# This matches the Request options:\n# https://developers.cloudflare.com/workers/runtime-apis/request/#options\nclass FetchKwargs(TypedDict, total=False):\n    headers: \"Headers | None\"\n    body: \"Body | None\"\n    method: HTTPMethod | None\n    redirect: str | None\n    cf: RequestInitCfProperties | None\n    fetcher: type[pyfetch] | None\n\n\n# TODO: Pyodide's FetchResponse.headers returns a dict[str, str] which means\n#       duplicates are lost, we should fix that so it returns a http.client.HTTPMessage\nclass FetchResponse(pyodide.http.FetchResponse):\n    # TODO: Consider upstreaming the `body` attribute\n    # TODO: Behind a compat flag make this return a native stream (StreamReader?), or perhaps\n    #       behind a different name, maybe `stream`?\n    @property\n    def body(self) -> \"js.ReadableStream\":\n        \"\"\"\n        Returns the body as a JavaScript ReadableStream from the JavaScript Response instance.\n        \"\"\"\n        return _jsnull_to_none(self.js_response.body)\n\n    @property\n    def js_object(self) -> \"js.Response\":\n        return self.js_response\n\n    \"\"\"\n    Instance methods defined below.\n\n    Some methods are implemented by `FetchResponse`, these include `buffer`\n    (replacing JavaScript's `arrayBuffer`), `bytes`, `json`, and `text`.\n\n    There are also some additional methods implemented by `FetchResponse`.\n    See https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.FetchResponse\n    for details.\n    \"\"\"\n\n    async def formData(self) -> \"FormData\":  # TODO: Remove after certain compat date.\n        return await self.form_data()\n\n    async def form_data(self) -> \"FormData\":\n        self._raise_if_failed()\n        try:\n            return FormData(await self.js_response.formData())\n        except JsException as exc:\n            raise _to_python_exception(exc) from exc\n\n    def replace_body(self, body: Body) -> \"Response\":\n        \"\"\"\n        Returns a new Response object with the same options (status, headers, etc) as\n        the original but with an updated body.\n        \"\"\"\n        b = body.js_object if isinstance(body, FormData) else body\n        js_resp = js.Response.new(b, self.js_response)\n        return Response(js_resp)\n\n    async def blob(self) -> \"Blob\":\n        self._raise_if_failed()\n        return Blob(await self.js_object.blob())\n\n    \"\"\"\n    Static methods defined below. The `error` static method is not implemented as\n    it is not useful for the Workers use case.\n    \"\"\"\n\n    @staticmethod\n    def redirect(url: str, status: HTTPStatus | int = HTTPStatus.FOUND):\n        code = status.value if isinstance(status, HTTPStatus) else status\n        try:\n            return js.Response.redirect(url, code)\n        except JsException as exc:\n            raise _to_python_exception(exc) from exc\n\n    @staticmethod\n    def from_json(\n        data: str | dict[str, Any] | list[Any] | JsProxy,\n        status: HTTPStatus | int = HTTPStatus.OK,\n        status_text=\"\",\n        headers: Headers = None,\n    ) -> \"Response\":\n        options = Response._create_options(status, status_text, headers)\n        js_resp = None\n        try:\n            if isinstance(data, JsProxy):\n                js_resp = js.Response.json(data, **options)\n            else:\n                if \"headers\" not in options:\n                    options[\"headers\"] = _to_js_headers(\n                        {\"content-type\": \"application/json\"}\n                    )\n                elif not options[\"headers\"].has(\"content-type\"):\n                    options[\"headers\"].set(\"content-type\", \"application/json\")\n                js_resp = js.Response.new(json.dumps(data), **options)\n        except JsException as exc:\n            raise _to_python_exception(exc) from exc\n\n        return Response(js_resp)\n\n    def json(self, *args: Never, **kwargs: Never):\n        if isinstance(self, Response):\n            return super().json()\n        # For compatibility, allow static use of Response.json() to mean Response.from_json().\n        data = self\n        return Response.from_json(data, *args, **kwargs)\n\n\nif pyodide_version == \"0.26.0a2\":\n\n    async def _pyfetch_patched(\n        request: \"str | js.Request\", **kwargs: Any\n    ) -> \"Response\":\n        # This is copied from https://github.com/pyodide/pyodide/blob/d3f99e1d/src/py/pyodide/http.py\n        custom_fetch = kwargs[\"fetcher\"] if \"fetcher\" in kwargs else js.fetch\n        kwargs[\"fetcher\"] = None\n        try:\n            return Response(\n                await custom_fetch(\n                    request, to_js(kwargs, dict_converter=Object.fromEntries)\n                ),\n            )\n        except JsException as e:\n            raise OSError(e.message) from None\nelse:\n    _pyfetch_patched = pyfetch\n\n\nasync def fetch(\n    resource: \"str | Request | js.Request\",\n    **other_options: Unpack[FetchKwargs],\n) -> \"Response\":\n    if isinstance(resource, Request):\n        resource = resource.js_object\n    if \"method\" in other_options and isinstance(other_options[\"method\"], HTTPMethod):\n        other_options[\"method\"] = other_options[\"method\"].value\n\n    resp = await _pyfetch_patched(resource, **other_options)\n    return Response(resp.js_response)\n\n\ndef _to_python_exception(exc: JsException) -> Exception:\n    if exc.name == \"RangeError\":\n        return ValueError(exc.message)\n    elif exc.name == \"TypeError\":\n        return TypeError(exc.message)\n    else:\n        return exc\n\n\ndef _from_js_error(exc: JsException) -> Exception:\n    # convert into Python exception after a full round trip\n    # Python - JS - Python\n    if not exc.message or not exc.message.startswith(\"PythonError\"):\n        return _to_python_exception(exc)\n\n    # extract the Python exception type from the traceback\n    error_message_last_line = exc.message.split(\"\\n\")[-2]\n    if error_message_last_line.startswith(\"TypeError\"):\n        return TypeError(error_message_last_line)\n    elif error_message_last_line.startswith(\"ValueError\"):\n        return ValueError(error_message_last_line)\n    elif error_message_last_line.startswith(\"workers.workflows.NonRetryableError\"):\n        return NonRetryableError(error_message_last_line)\n    else:\n        return _to_python_exception(exc)\n\n\n@contextmanager\ndef _manage_pyproxies():\n    proxies = js.Array.new()\n    try:\n        yield proxies\n    finally:\n        destroy_proxies(proxies)\n\n\ndef _is_js_instance(val, js_cls_name):\n    return hasattr(val, \"constructor\") and val.constructor.name == js_cls_name\n\n\ntry:\n    import _cloudflare_compat_flags\nexcept ImportError:\n    _cloudflare_compat_flags = object()\n\n\ndef get_compat_flag(flag: str) -> bool:\n    return getattr(_cloudflare_compat_flags, flag, False)\n\n\ndef _to_js_headers(headers: Headers):\n    if isinstance(headers, list):\n        # We should have a list[tuple[str, str]]\n        return js.Headers.new(headers)\n    elif isinstance(headers, dict):\n        return js.Headers.new(headers.items())\n    elif _is_js_instance(headers, \"Headers\"):\n        return headers\n    else:\n        raise TypeError(\"Received unexpected type for headers argument\")\n\n\n@contextmanager\ndef _get_js_body(body):\n    if isinstance(body, bytes):\n        proxy_bytes = create_proxy(body)\n        proxy_buffer = proxy_bytes.getBuffer()\n        try:\n            yield proxy_buffer.data\n            return\n        finally:\n            proxy_buffer.release()\n            proxy_bytes.destroy()\n    if isinstance(body, FormData):\n        yield body.js_object\n        return\n    yield body\n\n\nRESPONSE_ACCEPTED_TYPES = {\n    # BufferSource types\n    \"Blob\",\n    \"ArrayBuffer\",\n    \"TypedArray\",\n    \"DataView\",\n    \"Uint8Array\",\n    \"Uint8ClampedArray\",\n    \"Int8Array\",\n    \"Uint16Array\",\n    \"Int16Array\",\n    \"Uint32Array\",\n    \"Int32Array\",\n    \"Float16Array\",\n    \"Float32Array\",\n    \"Float64Array\",\n    \"BigInt64Array\",\n    \"BigUint64Array\",\n    # Other types\n    \"FormData\",\n    \"ReadableStream\",\n    \"URLSearchParams\",\n    \"Response\",\n}\n\n\nclass Response(FetchResponse):\n    \"\"\"\n    This class represents the response to an HTTP request, with a similar API to that of the web\n    `Response` API: https://developer.mozilla.org/en-US/docs/Web/API/Response.\n    \"\"\"\n\n    def __init__(\n        self,\n        body: Body = None,\n        status: HTTPStatus | int | None = None,\n        status_text=\"\",\n        headers: Headers = None,\n        web_socket: \"js.WebSocket | None\" = None,\n    ):\n        \"\"\"\n        Represents the response to a request.\n\n        Based on the JS API of the same name:\n        https://developer.mozilla.org/en-US/docs/Web/API/Response/Response.\n        \"\"\"\n        # Verify passed in types.\n        if hasattr(body, \"constructor\"):\n            if body.constructor.name not in RESPONSE_ACCEPTED_TYPES:\n                raise TypeError(\n                    f\"Unsupported type in Response: {body.constructor.name}\"\n                )\n        elif not isinstance(body, str | FormData | bytes) and body is not None:\n            raise TypeError(f\"Unsupported type in Response: {type(body).__name__}\")\n\n        # Handle constructing a Response from a JS Response.\n        if _is_js_instance(body, \"Response\"):\n            if status is not None or len(status_text) > 0 or headers is not None:\n                raise ValueError(\n                    \"Expected no options when constructing Response from a js.Response\"\n                )\n            super().__init__(body.url, body)\n            return\n\n        options = self._create_options(status, status_text, headers, web_socket)\n\n        # To avoid unnecessary copies we use this context manager.\n        with _get_js_body(body) as js_body:\n            # Initialize via the FetchResponse super-class which gives us access to\n            # methods that we would ordinarily have to redeclare.\n            js_resp = js.Response.new(js_body, **options)\n        super().__init__(js_resp.url, js_resp)\n\n    def __repr__(self):\n        body = [f\"status={self.status}\"]\n        if self.js_object.statusText:\n            body.append(f\"status_text={self.status_text!r}\")\n        if \"content-type\" in self.headers:\n            body.append(f\"content_type={self.headers['content-type']!r}\")\n        if self.js_object.url:\n            body.append(f\"url={self.js_object.url!r}\")\n        if self.js_object.type != \"default\":\n            body.append(f\"type={self.js_object.type!r}\")\n        return f\"Response({', '.join(body)})\"\n\n    @staticmethod\n    def _create_options(\n        status: HTTPStatus | int | None = HTTPStatus.OK,\n        status_text=\"\",\n        headers: Headers = None,\n        web_socket: \"js.WebSocket | None\" = None,\n    ):\n        options = {}\n        if status:\n            options[\"status\"] = (\n                status.value if isinstance(status, HTTPStatus) else status\n            )\n        if status_text:\n            options[\"statusText\"] = status_text\n        if headers:\n            options[\"headers\"] = _to_js_headers(headers)\n        if web_socket:\n            options[\"webSocket\"] = web_socket\n        return options\n\n\nFormDataValue = \"str | js.Blob | Blob\"\n\n\ndef _py_value_to_js(item: FormDataValue) -> \"str | js.Blob\":\n    if isinstance(item, Blob):\n        return item.js_object\n    else:\n        return item\n\n\ndef _js_value_to_py(item: FormDataValue) -> \"str | Blob | File\":\n    if hasattr(item, \"constructor\") and (item.constructor.name in (\"Blob\", \"File\")):\n        if item.constructor.name == \"File\":\n            return File(item, item.name)\n        else:\n            return Blob(item)\n    else:\n        return item\n\n\nclass FormData(MutableMapping[str, FormDataValue]):\n    \"\"\"\n    This class represents a set of key/value pairs for forms.\n\n    The API of this class follows that of https://pypi.org/project/multidict/ and\n    https://developer.mozilla.org/en-US/docs/Web/API/FormData.\n    \"\"\"\n\n    def __init__(\n        self, form_data: \"js.FormData | None | dict[str, FormDataValue]\" = None\n    ):\n        if not form_data:\n            self._js_form_data = js.FormData.new()\n            return\n\n        if isinstance(form_data, dict):\n            self._js_form_data = js.FormData.new()\n            for k, v in form_data.items():\n                self._js_form_data.append(k, _py_value_to_js(v))\n            return\n\n        if _is_js_instance(form_data, \"FormData\"):\n            self._js_form_data = form_data\n            return\n\n        raise TypeError(\"Expected form_data to be a dict or an instance of FormData\")\n\n    def __getitem__(self, key: str) -> FormDataValue:\n        return _js_value_to_py(self._js_form_data.get(key))\n\n    def __setitem__(self, key: str, value: FormDataValue):\n        if isinstance(value, list):\n            raise TypeError(\"Expected single item in arguments to FormData.__setitem__\")\n        self._js_form_data.set(key, _py_value_to_js(value))\n\n    def append(self, key: str, value: FormDataValue, filename: str | None = None):\n        self._js_form_data.append(key, _py_value_to_js(value), filename)\n\n    def delete(self, key: str):\n        self._js_form_data.delete(key)\n\n    def __contains__(self, key: str) -> bool:\n        return self._js_form_data.has(key)\n\n    def values(self) -> Generator[FormDataValue, None, None]:\n        for val in self._js_form_data.values():\n            yield _js_value_to_py(val)\n\n    def keys(self) -> Generator[str, None, None]:\n        yield from self._js_form_data.keys()\n\n    def __iter__(self):\n        yield from self.keys()\n\n    def items(self) -> Generator[tuple[str, FormDataValue], None, None]:\n        for k, v in self._js_form_data.entries():\n            yield (k, _js_value_to_py(v))\n\n    def __delitem__(self, key: str):\n        self.delete(key)\n\n    def __len__(self):\n        return len(self.keys())\n\n    def get_all(self, key: str) -> list[FormDataValue]:\n        return [_js_value_to_py(x) for x in self._js_form_data.getAll(key)]\n\n    @property\n    def js_object(self) -> \"js.FormData\":\n        return self._js_form_data\n\n\ndef _supports_buffer_protocol(o):\n    try:\n        # memoryview used only for testing type; 'with' releases the view instantly\n        with memoryview(o):\n            return True\n    except TypeError:\n        return False\n\n\n@contextmanager\ndef _make_blob_entry(e):\n    if isinstance(e, str):\n        yield e\n        return\n    if isinstance(e, Blob):\n        yield e._js_blob\n        return\n    if hasattr(e, \"constructor\") and (e.constructor.name in (\"Blob\", \"File\")):\n        yield e\n        return\n    if _supports_buffer_protocol(e):\n        px = create_proxy(e)\n        buf = px.getBuffer()\n        try:\n            yield buf.data\n            return\n        finally:\n            buf.release()\n            px.destroy()\n    raise TypeError(f\"Don't know how to handle {type(e)} for Blob()\")\n\n\ndef _is_iterable(obj):\n    if isinstance(obj, (str, bytes)):\n        return False\n    try:\n        iter(obj)\n    except TypeError:\n        return False\n    else:\n        return True\n\n\nBlobValue = (\n    \"str | bytes | js.ArrayBuffer | js.TypedArray | js.DataView | js.Blob | Blob | File\"\n)\n\n\nclass BlobEnding(StrEnum):\n    TRANSPARENT = \"transparent\"\n    NATIVE = \"native\"\n\n\nclass Blob:\n    def __init__(\n        self,\n        blob_parts: \"Iterable[BlobValue] | BlobValue\",\n        content_type: str | None = None,\n        endings: BlobEnding | str | None = None,\n    ):\n        if endings:\n            endings = str(endings)\n\n        is_single_item = not _is_iterable(blob_parts)\n        if is_single_item:\n            # Inherit the content_type if we have a single item. If a File is passed\n            # in then its metadata is lost.\n            if not content_type and isinstance(blob_parts, Blob):\n                content_type = blob_parts.content_type\n            if hasattr(blob_parts, \"constructor\") and (\n                blob_parts.constructor.name in (\"Blob\", \"File\")\n            ):\n                if not content_type:\n                    content_type = blob_parts.type\n\n            # Otherwise create a new Blob below.\n            blob_parts = [blob_parts]\n\n        with ExitStack() as stack:\n            args = [stack.enter_context(_make_blob_entry(e)) for e in blob_parts]\n            with _manage_pyproxies() as pyproxies:\n                self._js_blob = js.Blob.new(\n                    to_js(args, pyproxies=pyproxies),\n                    type=content_type,\n                    endings=endings,\n                )\n\n    @property\n    def size(self) -> int:\n        return self._js_blob.size\n\n    @property\n    def content_type(self) -> str:\n        return self._js_blob.type\n\n    @property\n    def js_object(self) -> \"js.Blob\":\n        return self._js_blob\n\n    async def text(self) -> str:\n        return await self.js_object.text()\n\n    async def bytes(self) -> bytes:\n        return (await self.js_object.arrayBuffer()).to_bytes()\n\n    def slice(\n        self,\n        start: int | None = None,\n        end: int | None = None,\n        content_type: str | None = None,\n    ):\n        js_sliced_blob = self.js_object.slice(start, end, content_type)\n        return Blob([js_sliced_blob])\n\n\nclass File(Blob):\n    def __init__(\n        self,\n        blob_parts: \"Iterable[BlobValue] | BlobValue\",\n        filename: str,\n        content_type: str | None = None,\n        endings: BlobEnding | str | None = None,\n        last_modified: int | None = None,\n    ):\n        if endings:\n            endings = str(endings)\n\n        is_single_item = not _is_iterable(blob_parts)\n        if is_single_item:\n            # Inherit the content_type and lastModified if we have a\n            # single item.\n            if not content_type and isinstance(blob_parts, Blob):\n                content_type = blob_parts.content_type\n            if not last_modified and isinstance(blob_parts, File):\n                last_modified = blob_parts.last_modified\n            if hasattr(blob_parts, \"constructor\") and (\n                blob_parts.constructor.name in (\"Blob\", \"File\")\n            ):\n                if not content_type:\n                    content_type = blob_parts.type\n                if blob_parts.constructor.name == \"File\":\n                    if not last_modified:\n                        last_modified = blob_parts.lastModified\n\n            # Otherwise create a new File below.\n            blob_parts = [blob_parts]\n\n        with ExitStack() as stack:\n            args = [stack.enter_context(_make_blob_entry(e)) for e in blob_parts]\n            with _manage_pyproxies() as pyproxies:\n                self._js_blob = js.File.new(\n                    to_js(args, pyproxies=pyproxies),\n                    filename,\n                    type=content_type,\n                    endings=endings,\n                    lastModified=last_modified,\n                )\n\n    @property\n    def name(self) -> str:\n        return self._js_blob.name\n\n    @property\n    def last_modified(self) -> int:\n        return self._js_blob.lastModified\n\n\nclass Request:\n    def __init__(\n        self, input: \"Request | str | js.Request\", **other_options: Unpack[FetchKwargs]\n    ):\n        if _is_js_instance(input, \"Request\"):\n            if len(other_options) > 0:\n                raise ValueError(\n                    \"Expected no options when constructing Request from a js.Request\"\n                )\n            self._js_request = input\n            return\n\n        if \"method\" in other_options and isinstance(\n            other_options[\"method\"], HTTPMethod\n        ):\n            other_options[\"method\"] = other_options[\"method\"].value\n\n        if \"headers\" in other_options:\n            other_options[\"headers\"] = _to_js_headers(other_options[\"headers\"])\n        self._js_request = js.Request.new(\n            input._js_request if isinstance(input, Request) else input, **other_options\n        )\n\n    def __repr__(self):\n        return (\n            f\"Request(method={self._js_request.method!r}, url={self._js_request.url!r})\"\n        )\n\n    @property\n    def js_object(self) -> \"js.Request\":\n        return self._js_request\n\n    # TODO: expose `body` as a native Python stream in the future, follow how we define `Response`\n    @property\n    def body(self) -> \"js.ReadableStream\":\n        return self.js_object.body\n\n    @property\n    def body_used(self) -> bool:\n        return self.js_object.bodyUsed\n\n    @property\n    def cache(self) -> str:\n        return self.js_object.cache\n\n    @property\n    def credentials(self) -> str:\n        return self.js_object.credentials\n\n    @property\n    def destination(self) -> str:\n        return self.js_object.destination\n\n    @property\n    def headers(self):\n        # This is imported here because it costs a lot of CPU time when imported at the top-level.\n        # At least it does when we do so in our validator tests, doesn't seem to cause trouble in\n        # production. So as a workaround we do the import here.\n        #\n        # TODO(later): when dedicated snapshots are default we can move this import to the top-level.\n        import http.client\n\n        result = http.client.HTTPMessage()\n        if not get_compat_flag(\"python_request_headers_preserve_commas\"):\n            for key, val in self.js_object.headers:\n                result[key] = val.strip()\n\n            return result\n\n        # With the exception of Set-Cookie, duplicate headers can and are combined with a comma\n        # in the JS Headers API. We do the same when returning the headers to Python.\n        #\n        # See https://httpwg.org/specs/rfc9110.html#rfc.section.5.3.\n        js_headers = self.js_object.headers\n        set_cookie_headers = js_headers.getSetCookie()\n        if set_cookie_headers:\n            for value in set_cookie_headers:\n                result.add_header(\"Set-Cookie\", value.strip())\n\n        for key, val in js_headers:\n            if key.lower() == \"set-cookie\":\n                continue\n            result.add_header(key, val.strip())\n\n        return result\n\n    @property\n    def integrity(self) -> str:\n        return self.js_object.integrity\n\n    @property\n    def is_history_navigation(self) -> bool:\n        return self.js_object.isHistoryNavigation\n\n    @property\n    def keepalive(self) -> bool:\n        return self.js_object.keepalive\n\n    @property\n    def method(self) -> HTTPMethod:\n        return HTTPMethod[self.js_object.method]\n\n    @property\n    def mode(self) -> str:\n        return self.js_object.mode\n\n    @property\n    def redirect(self) -> str:\n        return self.js_object.redirect\n\n    @property\n    def referrer(self) -> str:\n        return self.js_object.referrer\n\n    @property\n    def referrer_policy(self) -> str:\n        return self.js_object.referrerPolicy\n\n    @property\n    def url(self) -> str:\n        return self.js_object.url\n\n    def _raise_if_failed(self) -> None:\n        # TODO: https://github.com/pyodide/pyodide/blob/a53c17fd8/src/py/pyodide/http.py#L252\n        if self.body_used:\n            # TODO: Use BodyUsedError in newer Pyodide versions.\n            raise OSError(\"Body already used\")\n\n    \"\"\"\n    Instance methods defined below.\n\n    The naming of these methods should match Request's methods when possible.\n\n    TODO: AbortController support.\n    \"\"\"\n\n    async def buffer(self) -> \"js.ArrayBuffer\":\n        # The naming of this method matches that of Response.\n        self._raise_if_failed()\n        return await self.js_object.arrayBuffer()\n\n    async def form_data(self) -> \"FormData\":\n        self._raise_if_failed()\n        try:\n            return FormData(await self.js_object.formData())\n        except JsException as exc:\n            raise _to_python_exception(exc) from exc\n\n    async def blob(self) -> Blob:\n        self._raise_if_failed()\n        return Blob(await self.js_object.blob())\n\n    async def bytes(self) -> bytes:\n        self._raise_if_failed()\n        return (await self.buffer()).to_bytes()\n\n    def clone(self) -> \"Request\":\n        if self.body_used:\n            # TODO: Use BodyUsedError in newer Pyodide versions.\n            raise OSError(\"Body already used\")\n        return Request(\n            self.js_object.clone(),\n        )\n\n    async def json(self, **kwargs: Any) -> Any:\n        self._raise_if_failed()\n        return json.loads(await self.text(), **kwargs)\n\n    async def text(self) -> str:\n        self._raise_if_failed()\n        return await self.js_object.text()\n\n\ndef _python_from_rpc_default_converter(value, convert, cache):\n    if not hasattr(value, \"constructor\"):\n        # Assume that the object doesn't need conversion as it's not a JS object.\n        return value\n\n    if value.constructor.name == \"Response\":\n        return Response(value)\n    elif value.constructor.name == \"FormData\":\n        return FormData(value)\n    elif value.constructor.name == \"Blob\":\n        return Blob(value)\n    elif value.constructor.name == \"File\":\n        return File(value)\n    elif value.constructor.name == \"Request\":\n        return Request(value)\n    elif value.constructor.name == \"Date\":\n        # TODO: Pyodide should gain support for this, we should upstream this.\n        return datetime.datetime.fromtimestamp(value.getTime() / 1000)\n    elif value.constructor.name == \"Error\":\n        return Exception(value.toString())\n    elif value.constructor.name == \"Number\":\n        return value.valueOf()\n\n    # We used to throw an error here, but since these conversions are now automatic when the default\n    # entrypoint is being used, it makes sense to be less loud about it and just pass through the\n    # JS value un-modified.\n    #\n    # This does mean that in the future we need to be careful when adding type wrappers for new\n    # types here, so if you're doing this make sure to do so behind a compat flag.\n    return value\n\n\ndef python_from_rpc(obj: \"JsProxy\"):\n    \"\"\"\n    Converts JS objects like Response, Request, Blob, etc. to equivalent Python objects defined in\n    this module and also other JS objects like Map, Set, etc. to equivalent Python stdlib objects.\n\n    This method is used for Workers RPC in Python to convert JavaScript objects to Python. As such\n    it does not support serializing all JS object types.\n    \"\"\"\n\n    if not hasattr(obj, \"constructor\"):\n        return obj\n\n    if obj.constructor.name == \"TestController\":\n        # This object currently has no methods defined on it. If this changes we should\n        # implement a Python wrapper for it, but for now we'll just pass in None.\n        return None\n\n    result = obj.to_py(default_converter=_python_from_rpc_default_converter)\n\n    return result\n\n\ndef _raise_on_disabled_type(value):\n    if _is_js_instance(value, \"RegExp\"):\n        raise TypeError(f\"{value.constructor.name} cannot be sent over RPC.\")\n\n    if isinstance(value, (tuple, bytearray, LambdaType)):\n        raise TypeError(f\"{type(value)} cannot be sent over RPC.\")\n\n    if inspect.isawaitable(value):\n        # The caller is expected to await the value prior to conversion.\n        raise TypeError(f\"Awaitable {type(value)} cannot be sent over RPC.\")\n\n    if _is_iterable(value):\n        if isinstance(value, dict):\n            for v in value.values():\n                _raise_on_disabled_type(v)\n        else:\n            for v in value:\n                _raise_on_disabled_type(v)\n\n\ndef _python_to_rpc_default_converter(obj, convert, cache):\n    if obj is None:\n        return obj\n\n    if hasattr(obj, \"js_object\"):\n        return obj.js_object\n\n    if isinstance(obj, datetime.datetime):\n        # TODO: Pyodide should gain support for this, we should upstream this.\n        return js.Date.new(obj.timestamp() * 1000)\n\n    if isinstance(obj, Exception):\n        return js.Error.new(str(obj))\n\n    _raise_on_disabled_type(obj)\n\n    return obj\n\n\ndef python_to_rpc(value) -> JsProxy:\n    \"\"\"\n    Converts Python objects defined in this module (Response, Request, etc) and native Python types\n    like Map, Set, datetime to equivalent JavaScript types.\n\n    This method is used for Workers RPC in Python to convert Python objects to JavaScript. As such\n    it does not support serializing all Python object types.\n    \"\"\"\n\n    # `to_js` won't always call the default_converter, for example when a list of tuples is passed\n    _raise_on_disabled_type(value)\n\n    result = to_js(\n        value,\n        default_converter=_python_to_rpc_default_converter,\n        dict_converter=js.Map.new,\n    )\n\n    return result\n\n\nclass _FetcherWrapper:\n    def __init__(self, binding):\n        self._binding = binding\n\n    def _getattr_helper(self, name):\n        attr = getattr(self._binding, name)\n\n        if not callable(attr):\n            return attr\n\n        # Not using `@functools.wraps(attr)` here because `attr` is a JS proxy.\n        async def wrapper(*args, **kwargs):\n            js_args = [python_to_rpc(arg) for arg in args]\n            js_kwargs = {k: python_to_rpc(v) for k, v in kwargs.items()}\n            result = attr(*js_args, **js_kwargs)\n            if hasattr(result, \"then\") and callable(result.then):\n                return python_from_rpc(await result)\n            else:\n                return python_from_rpc(result)\n\n        return wrapper\n\n    def __getattr__(self, name):\n        result = self._getattr_helper(name)\n        setattr(self, name, result)\n        return result\n\n    def fetch(self, *args, **kwargs):\n        return fetch(*args, fetcher=self._binding.fetch, **kwargs)\n\n\nclass _DurableObjectNamespaceWrapper:\n    def __init__(self, binding):\n        self._binding = binding\n\n    def __getattr__(self, name):\n        return getattr(self._binding, name)\n\n    def get(self, *args, **kwargs):\n        return _FetcherWrapper(self._binding.get(*args, **kwargs))\n\n    def getByName(self, *args, **kwargs):\n        return _FetcherWrapper(self._binding.getByName(*args, **kwargs))\n\n    def jurisdiction(self, *args, **kwargs):\n        return _DurableObjectNamespaceWrapper(\n            self._binding.jurisdiction(*args, **kwargs)\n        )\n\n\nclass _WorkflowInstanceWrapper:\n    def __init__(self, binding):\n        self._binding = binding\n\n    def __getattr__(self, name):\n        return getattr(self._binding, name)\n\n    async def send_event(self, *args, **kwargs):\n        return self._binding.sendEvent(*args, **kwargs)\n\n    async def pause(self, *args, **kwargs):\n        return self._binding.pause(*args, **kwargs)\n\n    async def resume(self, *args, **kwargs):\n        return self._binding.resume(*args, **kwargs)\n\n    async def terminate(self, *args, **kwargs):\n        return self._binding.terminate(*args, **kwargs)\n\n    async def restart(self, *args, **kwargs):\n        return self._binding.restart(*args, **kwargs)\n\n    async def status(self, *args, **kwargs):\n        return self._binding.status(*args, **kwargs)\n\n\nclass _WorkflowBindingWrapper:\n    def __init__(self, binding):\n        self._binding = binding\n\n    def __getattr__(self, name):\n        return getattr(self._binding, name)\n\n    async def get(self, *args, **kwargs):\n        return _WorkflowInstanceWrapper(await self._binding.get(*args, **kwargs))\n\n    async def create(self, *args, **kwargs):\n        return _WorkflowInstanceWrapper(await self._binding.create(*args, **kwargs))\n\n    async def create_batch(self, *args, **kwargs):\n        return [\n            _WorkflowInstanceWrapper(w)\n            for w in await self._binding.createBatch(*args, **kwargs)\n        ]\n\n\nclass _EnvWrapper:\n    def __init__(self, env: Any):\n        self._env = env\n\n    def _getattr_helper(self, name):\n        binding = getattr(self._env, name)\n        if _is_js_instance(binding, \"Fetcher\"):\n            return _FetcherWrapper(binding)\n\n        if _is_js_instance(binding, \"DurableObjectNamespace\"):\n            return _DurableObjectNamespaceWrapper(binding)\n\n        if _is_js_instance(binding, \"WorkflowImpl\"):\n            return _WorkflowBindingWrapper(binding)\n\n        # TODO: Implement APIs for bindings.\n        return binding\n\n    def __getattr__(self, name):\n        result = self._getattr_helper(name)\n        setattr(self, name, result)\n        return result\n\n\ndef handler(func):\n    \"\"\"\n    When applied to handlers such as `on_fetch` it will rewrite arguments passed in to native Python\n    types defined in this module. For example, the `request` argument to `on_fetch` gets converted\n    to an instance of the Request class defined in this module.\n    \"\"\"\n\n    @functools.wraps(func)\n    def wrapper(*args, **kwargs):\n        # TODO: support transforming kwargs\n        if len(args) > 0 and _is_js_instance(args[0], \"Request\"):\n            args = (Request(args[0]), *args[1:])\n\n        # Wrap `env` so that bindings can be used without to_js.\n        if len(args) > 1:\n            args = (args[0], _EnvWrapper(args[1]), *args[2:])\n\n        return func(*args, **kwargs)\n\n    return wrapper\n\n\nclass _WorkflowStepWrapper:\n    def __init__(self, js_step):\n        self._js_step = js_step\n        self._memoized_dependencies = {}\n        self._in_flight = {}\n        self.step_closures = {}\n\n        # Assign the appropriate method based on compat flag\n        if _cloudflare_compat_flags.python_workflows_implicit_dependencies:\n            self.do = self._do_implicit\n        else:\n            self.do = self._do_legacy\n\n    def _do_legacy(self, name, depends=None, concurrent=False, config=None):\n        \"\"\"Original signature - positional args allowed, explicit depends parameter.\"\"\"\n        return self._create_step_decorator(\n            name=name,\n            depends=depends,\n            concurrent=concurrent,\n            config=config,\n            implicit=False,\n        )\n\n    def _do_implicit(self, name=None, *, concurrent=False, config=None):\n        \"\"\"New signature - keyword-only args, dependencies resolved from param names.\"\"\"\n        return self._create_step_decorator(\n            name=name,\n            depends=None,\n            concurrent=concurrent,\n            config=config,\n            implicit=True,\n        )\n\n    def _create_step_decorator(self, name, depends, concurrent, config, implicit):\n        \"\"\"Shared decorator factory for both legacy and implicit modes.\"\"\"\n\n        def decorator(func):\n            step_name = func.__name__ if name is None else name\n\n            async def wrapper():\n                results_future_list = self._build_dependency_list(\n                    func, depends, implicit\n                )\n                results = await self._gather_results(results_future_list, concurrent)\n                return await _do_call(self, step_name, config, func, *results)\n\n            wrapper._step_name = step_name\n            self.step_closures[step_name] = wrapper\n            return wrapper\n\n        return decorator\n\n    def _build_dependency_list(self, func, depends, implicit):\n        \"\"\"Build the dependency list based on mode (implicit vs legacy).\"\"\"\n        sig = inspect.signature(func)\n        results_future_list = []\n\n        if implicit:\n            # Implicit mode: resolve dependencies from parameter names\n            for p in sig.parameters.values():\n                if p.name in self.step_closures:\n                    results_future_list.append(self.step_closures[p.name])\n                elif p.name == \"ctx\":\n                    results_future_list.append(p)\n                else:\n                    raise TypeError(f\"Received unexpected parameter {p.name}\")\n        else:\n            # Legacy mode: use explicit depends list, support ctx parameter\n            non_ctx_params = [p for p in sig.parameters.values() if p.name != \"ctx\"]\n\n            if depends is None and len(non_ctx_params) > 0:\n                raise TypeError(\n                    f\"Step has {len(non_ctx_params)} non-ctx parameter(s) but no 'depends' list provided\"\n                )\n\n            elif depends is not None and len(depends) != len(non_ctx_params):\n                raise TypeError(\n                    f\"Step declares {len(non_ctx_params)} non-ctx parameter(s) but 'depends' has {len(depends)} item(s)\"\n                )\n\n            curr = 0\n            for p in sig.parameters.values():\n                if p.name == \"ctx\":\n                    results_future_list.append(p)\n                else:\n                    results_future_list.append(depends[curr])\n                    curr += 1\n\n        return results_future_list\n\n    async def _gather_results(self, results_future_list, concurrent):\n        \"\"\"Resolve dependencies concurrently or sequentially.\"\"\"\n        if concurrent:\n            return await gather(\n                *[self._resolve_dependency(dep) for dep in results_future_list or []]\n            )\n        else:\n            return [\n                await self._resolve_dependency(dep) for dep in results_future_list or []\n            ]\n\n    def sleep(self, *args, **kwargs):\n        return self._js_step.sleep(*args, **kwargs)\n\n    def sleep_until(self, name, timestamp):\n        if not isinstance(timestamp, str):\n            timestamp = python_to_rpc(timestamp)\n\n        return self._js_step.sleepUntil(name, timestamp)\n\n    def wait_for_event(self, name, event_type, /, timeout=\"24 hours\"):\n        return self._js_step.waitForEvent(\n            name,\n            to_js(\n                {\"type\": event_type, \"timeout\": timeout},\n                dict_converter=Object.fromEntries,\n            ),\n        )\n\n    async def _resolve_dependency(self, dep):\n        if hasattr(dep, \"name\") and dep.name == \"ctx\":\n            return dep\n        elif dep._step_name in self._memoized_dependencies:\n            return self._memoized_dependencies[dep._step_name]\n        elif dep._step_name in self._in_flight:\n            return await self._in_flight[dep._step_name]\n\n        return await dep()\n\n\nasync def _do_call(entrypoint, name, config, callback, *results):\n    async def _callback(ctx=None):\n        # deconstruct the actual ctx object\n        resolved_results = tuple(\n            python_from_rpc(ctx)\n            if isinstance(r, inspect.Parameter) and r.name == \"ctx\"\n            else r\n            for r in results\n        )\n        result = callback(*resolved_results)\n\n        if inspect.iscoroutine(result):\n            result = await result\n        return to_js(result, dict_converter=Object.fromEntries)\n\n    async def _closure():\n        try:\n            if config is None:\n                coroutine = await entrypoint._js_step.do(name, _callback)\n            else:\n                coroutine = await entrypoint._js_step.do(\n                    name, to_js(config, dict_converter=Object.fromEntries), _callback\n                )\n\n            return python_from_rpc(coroutine)\n        except Exception as exc:\n            raise _from_js_error(exc) from exc\n\n    task = create_task(_closure())\n    entrypoint._in_flight[name] = task\n\n    try:\n        result = await task\n        entrypoint._memoized_dependencies[name] = result\n    finally:\n        del entrypoint._in_flight[name]\n\n    return result\n\n\ndef _wrap_subclass(cls):\n    # Override the class __init__ so that we can wrap the `env` in the constructor.\n    original_init = cls.__init__\n\n    def wrapped_init(self, *args, **kwargs):\n        if len(args) > 0:\n            _pyodide_entrypoint_helper.patchWaitUntil(args[0])\n        if len(args) > 1:\n            args = list(args)\n            args[1] = _EnvWrapper(args[1])\n\n        original_init(self, *args, **kwargs)\n\n    cls.__init__ = wrapped_init\n\n\ndef _wrap_workflow_step(cls):\n    run_fn = getattr(cls, \"run\", None)\n    if run_fn is None:\n        return\n\n    # Only patch `on_run` for subclasses of WorkflowEntrypoint.\n    if not issubclass(cls, WorkflowEntrypoint):\n        # Not a workflow subclass, so don't wrap `on_run`.\n        return\n\n    @functools.wraps(run_fn)\n    async def wrapped_run(self, event=None, step=None, /, *args, **kwargs):\n        if event is not None:\n            event = python_from_rpc(event)\n        if step is not None:\n            step = _WorkflowStepWrapper(step)\n\n        result = run_fn(self, event, step, *args, **kwargs)\n\n        if inspect.iscoroutine(result):\n            result = await result\n\n        return result\n\n    cls.run = wrapped_run\n\n\nclass DurableObject:\n    \"\"\"\n    Base class used to define a Durable Object.\n    \"\"\"\n\n    def __init__(self, ctx: Context, env: Any):\n        self.ctx = ctx\n        self.env = env\n\n    def __init_subclass__(cls, **_kwargs):\n        _wrap_subclass(cls)\n\n\nclass WorkerEntrypoint:\n    \"\"\"\n    Base class used to define a Worker Entrypoint.\n    \"\"\"\n\n    def __init__(self, ctx: Context, env: Any):\n        self.ctx = ctx\n        self.env = env\n\n    def __init_subclass__(cls, **_kwargs: Any):\n        _wrap_subclass(cls)\n\n\nclass WorkflowEntrypoint:\n    \"\"\"\n    Base class used to define a Workflow Entrypoint.\n    \"\"\"\n\n    def __init__(self, ctx: Context, env: Any):\n        self.ctx = ctx\n        self.env = env\n\n    def __init_subclass__(cls, **_kwargs: Any):\n        _wrap_subclass(cls)\n        _wrap_workflow_step(cls)\n"
  },
  {
    "path": "src/pyodide/internal/workers-api/src/workers/workflows.py",
    "content": "\"\"\"\nWorkflow-specific classes and exceptions for the workers module.\n\"\"\"\n\n\nclass NonRetryableError(Exception):\n    \"\"\"\n    A marker exception used to signal that a workflow step should not be retried.\n    This is a special exception used by workflows.\n    \"\"\"\n\n    pass\n"
  },
  {
    "path": "src/pyodide/make_snapshots.py",
    "content": "import argparse\nimport json\nimport re\nimport shutil\nimport subprocess\nimport sys\nfrom copy import deepcopy\nfrom functools import cache\nfrom os import environ\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\nfrom textwrap import dedent, indent\n\nfrom tool_utils import hexdigest, run, timing\n\n\ndef cquery(rule):\n    res = subprocess.run(\n        [\n            \"bazel\",\n            \"cquery\",\n            rule,\n            \"--output=files\",\n        ],\n        capture_output=True,\n        text=True,\n        check=False,\n    )\n    if res.returncode:\n        print(res.stdout)\n        print(res.stderr)\n        sys.exit(res.returncode)\n    return res.stdout.strip()\n\n\n@cache\ndef _bundle_version_info():\n    with Path(cquery(\"@workerd//src/pyodide:bundle_version_info\")).open() as f:\n        return json.load(f)\n\n\ndef bundle_version_info():\n    return deepcopy(_bundle_version_info())\n\n\nTEMPLATE = \"\"\"\nusing Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n  ],\n);\n\nconst mainWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker.py\", pythonModule = embed \"./worker.py\"),\n    {requirements}\n  ],\n  compatibilityDate = \"2025-08-05\",\n  compatibilityFlags = [\"python_no_global_handlers\", {compat_flags}],\n  # Learn more about compatibility dates at:\n  # https://developers.cloudflare.com/workers/platform/compatibility-dates/\n);\n\"\"\"\n\n\ndef make_config(\n    flags: list[str],\n    reqs: list[str],\n) -> str:\n    requirements = \"\"\n    for name in reqs:\n        requirements += f'(name=\"{name}\", pythonRequirement=\"\"),'\n\n    compat_flags = \"\"\n    for flag in flags:\n        compat_flags += f'\"{flag}\", '\n    return TEMPLATE.format(requirements=requirements, compat_flags=compat_flags)\n\n\ndef make_worker(imports: list[str]) -> str:\n    contents = \"\"\n    for i in imports:\n        contents += f\"import {i}\\n\"\n    contents += dedent(\"\"\"\\\n    from workers import WorkerEntrypoint\n    class Default(WorkerEntrypoint):\n        def test(self):\n            pass\n    \"\"\")\n    return contents\n\n\ndef make_snapshot(  # noqa: PLR0913\n    d: Path,\n    outdir: Path,\n    outprefix: str,\n    compat_flags: list[str],\n    requirements: list[str],\n    imports: list[str],\n) -> str:\n    config_path = d / \"config.capnp\"\n    config_path.write_text(make_config(compat_flags, requirements))\n    worker_path = d / \"worker.py\"\n    worker_path.write_text(make_worker(imports))\n    if imports:\n        snapshot_flag = \"--python-save-snapshot\"\n    else:\n        snapshot_flag = \"--python-save-baseline-snapshot\"\n\n    if \"WORKERD_BINARY\" in environ:\n        workerd = [environ[\"WORKERD_BINARY\"]]\n    else:\n        workerd = [\n            \"bazel\",\n            \"run\",\n            \"@workerd//src/workerd/server:workerd\",\n            \"--\",\n        ]\n    run(\n        [\n            *workerd,\n            \"test\",\n            config_path,\n            snapshot_flag,\n            \"--pyodide-bundle-disk-cache-dir\",\n            d,\n            \"--pyodide-package-disk-cache-dir\",\n            d,\n            \"--experimental\",\n        ],\n    )\n    snapshot_path = d / \"snapshot.bin\"\n    digest = hexdigest(snapshot_path)\n    digest9 = digest[:9]\n    outname = f\"{outprefix}-{digest9}.bin\"\n    outfile = outdir / outname\n    shutil.copyfile(snapshot_path, outfile)\n    snapshot_path.unlink()\n    return [outname, digest]\n\n\ndef make_baseline_snapshot(\n    cache: Path, outdir: Path, compat_flags: list[str]\n) -> list[tuple[str, str]]:\n    name, digest = make_snapshot(cache, outdir, \"baseline\", compat_flags, [], [])\n    return [\n        (\"baseline_snapshot\", name),\n        (\"baseline_snapshot_hash\", digest),\n    ]\n\n\ndef make_numpy_snapshot(\n    cache: Path, outdir: Path, compat_flags: list[str]\n) -> list[tuple[str, str]]:\n    name, digest = make_snapshot(\n        cache, outdir, \"package_snapshot_numpy\", compat_flags, [\"numpy\"], [\"numpy\"]\n    )\n    return [\n        (\"numpy_snapshot\", name),\n        (\"numpy_snapshot_hash\", digest),\n    ]\n\n\ndef make_fastapi_snapshot(\n    cache: Path, outdir: Path, compat_flags: list[str]\n) -> list[tuple[str, str]]:\n    name, digest = make_snapshot(\n        cache,\n        outdir,\n        \"package_snapshot_fastapi\",\n        compat_flags,\n        [\"fastapi\"],\n        [\"fastapi\", \"pydantic\"],\n    )\n    return [\n        (\"fastapi_snapshot\", name),\n        (\"fastapi_snapshot_hash\", digest),\n    ]\n\n\ndef make_snapshots(\n    cache: Path, outdir: Path, update_released: bool\n) -> tuple[str, tuple[str, str]]:\n    res = []\n    for ver, info in bundle_version_info().items():\n        if ver.startswith(\"dev\"):\n            continue\n        if not update_released and info.get(\"released\", False):\n            continue\n        compat_flags = list({\"python_workers\", info[\"enable_flag_name\"]})\n\n        ver_info = []\n        with timing(f\"version {ver} snapshots\"):\n            with timing(\"baseline snapshot\"):\n                ver_info += make_baseline_snapshot(cache, outdir, compat_flags)\n            with timing(\"numpy snapshot\"):\n                ver_info += make_numpy_snapshot(cache, outdir, compat_flags)\n            with timing(\"fastapi snapshot\"):\n                ver_info += make_fastapi_snapshot(cache, outdir, compat_flags)\n        res.append((ver, ver_info))\n    return res\n\n\ndef update_python_metadata_bzl(res: tuple[str, tuple[str, str]]):\n    \"\"\"Update python_metadata.bzl file with new snapshot values.\"\"\"\n    metadata_path = (\n        Path(__file__).parent.parent.parent / \"build\" / \"python_metadata.bzl\"\n    )\n    content = metadata_path.read_text()\n\n    for ver, kvs in res:\n        # Find the version block and update snapshot values\n        version_pattern = rf'(\\s+{{\\s*\\n\\s*\"name\":\\s*\"{re.escape(ver)}\",.*?)}}'\n\n        def replace_version_block(match, *, kvs=kvs):\n            block = match.group(1)\n            # Update each key-value pair\n            for key, val in kvs:\n                key_pattern = rf'(\"{re.escape(key)}\":\\s*)\"[^\"]*\"'\n                block = re.sub(key_pattern, rf'\\1\"{val}\"', block)\n            return block + \"}\"\n\n        content = re.sub(\n            version_pattern, replace_version_block, content, flags=re.DOTALL\n        )\n\n    metadata_path.write_text(content)\n\n\ndef upload_snapshots(outdir: Path):\n    from boto3 import client\n\n    s3 = client(\n        \"s3\",\n        endpoint_url=f\"https://{environ['R2_ACCOUNT_ID']}.r2.cloudflarestorage.com\",\n        aws_access_key_id=environ[\"R2_ACCESS_KEY_ID\"],\n        aws_secret_access_key=environ[\"R2_SECRET_ACCESS_KEY\"],\n        region_name=\"auto\",\n    )\n\n    for file in outdir.glob(\"*.bin\"):\n        if file.name.startswith(\"baseline-\"):\n            key = \"baseline-snapshot/\" + hexdigest(file)\n        else:\n            key = \"test-snapshot/\" + file.name\n        s3.upload_file(str(file), \"pyodide-capnp-bin\", key)\n\n\ndef main() -> int:\n    parser = argparse.ArgumentParser(\n        description=\"Upload Pyodide bundles and update metadata\"\n    )\n    parser.add_argument(\n        \"--update-released\",\n        action=\"store_true\",\n        help=\"Update already released versions?\",\n    )\n    args = parser.parse_args()\n\n    subprocess.run(\n        [\n            \"bazel\",\n            \"build\",\n            \"@workerd//src/pyodide:bundle_version_info\",\n        ],\n        check=True,\n    )\n\n    # Create generated-snapshots directory\n    outdir = Path(__file__).parent / \"generated-snapshots\"\n    if outdir.exists() and outdir.is_dir() and any(outdir.iterdir()):\n        print(f\"Error: Directory {outdir} exists and is not empty\", file=sys.stderr)\n        return 1\n    outdir.mkdir(parents=True, exist_ok=True)\n\n    with TemporaryDirectory() as package_cache:\n        cache = Path(package_cache)\n        res = make_snapshots(cache, outdir, args.update_released)\n\n    update_python_metadata_bzl(res)\n\n    upload_snapshots(outdir)\n    print()\n    print(\n        \"Upload these files to the ew-snapshot-tests R2 bucket: \"\n        + \"https://dash.cloudflare.com/e415f1017791ced9d5f3eb0df2b31c9e/r2/default/buckets/ew-snapshot-tests\"\n    )\n    print(\"Updated python_metadata.bzl with:\")\n    for ver, kvs in res:\n        print(\"Version\", ver)\n        for key, val in kvs:\n            print(indent(f'\"{key}\": \"{val}\",', \" \" * 8))\n    return 0\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  },
  {
    "path": "src/pyodide/package.json",
    "content": "{\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "src/pyodide/pyodide_extra.capnp",
    "content": "@0x96c290dbf479ac0c;\nusing Cxx = import \"/capnp/c++.capnp\";\n$Cxx.namespace(\"workerd\");\n\nstruct PackageLock {\n  packageDate @0 :Text;\n  lock @1 :Text;\n}\nconst packageLocks :List(PackageLock) = [\n  %PACKAGE_LOCKS\n];\n\n\nstruct PythonSnapshotRelease @0x89c66fb883cb6975 {\n  # Used to indicate a specific Python release that introduces a change which likely breaks\n  # existing memory snapshots.\n  #\n  # The versions/dates specified here are used to generate a filename for the package memory\n  # snapshots created by the validator. They are also used to generate a filename of the Pyodide\n  # and package bundle that gets downloaded for Python Workers.\n  pyodide @0 :Text;\n  # The Pyodide version, for example \"0.26.0a2\".\n  pyodideRevision @1 :Text;\n  # A date identifying a revision of the above Pyodide version. A change in this field but not\n  # the `pyodide` version field may indicate that changes to the workerd Pyodide integration code\n  # were made with the Pyodide version remaining the same.\n  #\n  # For example \"2024-05-25\".\n  packages @2 :Text;\n  # A date identifying a revision of the Python package bundle.\n  #\n  # For example \"2024-02-18\".\n  backport @3 :Int64;\n  # A number that is incremented each time we need to backport a fix to an existing Python release.\n  baselineSnapshotHash @4 :Text;\n  # A sha256 checksum hash of the baseline/universal memory snapshot to use for Python Workers using\n  # this release.\n  fieldName @5 :Text;\n  # Name of the corresponding feature flag\n  flagName @6 :Text;\n  realPyodideVersion @7 :Text;\n}\n\nconst releases :List(PythonSnapshotRelease) = [\n  %PYTHON_RELEASES\n];\n"
  },
  {
    "path": "src/pyodide/python-entrypoint-helper.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n/* eslint-disable @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return  */\n// This file is a BUILTIN module that provides the actual implementation for the\n// python-entrypoint.js USER module.\n\nimport { patch_env_helper } from 'pyodide-internal:envHelpers';\nimport { enterJaegerSpan } from 'pyodide-internal:jaeger';\nimport { default as Limiter } from 'pyodide-internal:limiter';\nimport {\n  COMPATIBILITY_FLAGS,\n  IS_WORKERD,\n  LEGACY_GLOBAL_HANDLERS,\n  EXTERNAL_SDK,\n  LOCKFILE,\n  MAIN_MODULE_NAME,\n  SHOULD_SNAPSHOT_TO_DISK,\n  TRANSITIVE_REQUIREMENTS,\n  WORKERD_INDEX_URL,\n  WORKFLOWS_ENABLED,\n} from 'pyodide-internal:metadata';\nimport {\n  beforeRequest,\n  clearSignals,\n  loadPyodide,\n} from 'pyodide-internal:python';\nimport { patchLoadPackage } from 'pyodide-internal:setupPackages';\nimport {\n  LOADED_SNAPSHOT_TYPE,\n  maybeCollectDedicatedSnapshot,\n} from 'pyodide-internal:snapshot';\nimport {\n  PythonUserError,\n  PythonWorkersInternalError,\n  reportError,\n} from 'pyodide-internal:util';\nimport { PyodideVersion } from 'pyodide-internal:const';\nexport { createImportProxy } from 'pyodide-internal:serializeJsModule';\n\ntype PyFuture<T> = Promise<T> & { copy(): PyFuture<T>; destroy(): void };\n\nconst waitUntilPatched = new WeakSet();\n\nfunction patchWaitUntil(ctx: {\n  waitUntil: (p: Promise<void> | PyFuture<void>) => void;\n}): void {\n  let tag;\n  try {\n    tag = Object.prototype.toString.call(ctx);\n  } catch (_e) {}\n  if (tag !== '[object ExecutionContext]') {\n    return;\n  }\n  if (waitUntilPatched.has(ctx)) {\n    return;\n  }\n  const origWaitUntil: (p: Promise<void>) => void = ctx.waitUntil.bind(ctx);\n  function waitUntil(p: Promise<void> | PyFuture<void>): void {\n    origWaitUntil(\n      (async function (): Promise<void> {\n        if ('copy' in p) {\n          p = p.copy();\n        }\n        await p;\n        if ('destroy' in p) {\n          p.destroy();\n        }\n      })()\n    );\n  }\n  ctx.waitUntil = waitUntil;\n  waitUntilPatched.add(ctx);\n}\n\nexport type PyodideEntrypointHelper = {\n  doAnImport: (mod: string) => Promise<any>;\n  cloudflareWorkersModule: { env: any };\n  cloudflareSocketsModule: any;\n  workerEntrypoint: any;\n  patchWaitUntil: typeof patchWaitUntil;\n  patch_env_helper: (patch: unknown) => Generator<void>;\n};\n\n// Function to import JavaScript modules from Python\nlet _pyodide_entrypoint_helper: PyodideEntrypointHelper | null = null;\n\nfunction get_pyodide_entrypoint_helper(): PyodideEntrypointHelper {\n  if (!_pyodide_entrypoint_helper) {\n    throw new PythonWorkersInternalError(\n      'pyodide_entrypoint_helper is not initialized'\n    );\n  }\n  return _pyodide_entrypoint_helper;\n}\n\nexport async function setDoAnImport(\n  doAnImport: (mod: string) => Promise<any>,\n  workerEntrypoint: any\n): Promise<void> {\n  _pyodide_entrypoint_helper = {\n    doAnImport,\n    cloudflareWorkersModule: await doAnImport('cloudflare:workers'),\n    cloudflareSocketsModule: await doAnImport('cloudflare:sockets'),\n    workerEntrypoint,\n    patchWaitUntil,\n    patch_env_helper,\n  };\n}\n\nfunction handleSrcImport(pyodide: Pyodide, e: any): never {\n  // Users may be expecting to import local modules via the `src` directory, which for a default\n  // project structure will fail. This code will add some extra info to the error message to help\n  // them fix it.\n  if (e.name === 'PythonError' && e.type === 'ModuleNotFoundError') {\n    pyodide.runPython(`\n      try:\n        import sys\n        exc = sys.last_value\n        if exc.name == \"src\":\n          exc.add_note(\n            \"If your main module is inside the 'src' directory then your import \" +\n            \"statement shouldn't include a 'src.' prefix\")\n        raise exc\n      finally:\n        del exc\n    `);\n  }\n  throw e;\n}\n\nasync function pyimportMainModule(pyodide: Pyodide): Promise<PyModule> {\n  if (!MAIN_MODULE_NAME.endsWith('.py')) {\n    throw new PythonUserError(\n      'Main module needs to end with a .py file extension'\n    );\n  }\n  const mainModuleName = MAIN_MODULE_NAME.slice(0, -3);\n  if (pyodide.version === PyodideVersion.V0_26_0a2) {\n    return pyodide.pyimport(mainModuleName);\n  } else {\n    return await pyodide._module.API.pyodide_base.pyimport_impl.callPromising(\n      mainModuleName\n    );\n  }\n}\n\nlet pyodidePromise: Promise<Pyodide> | undefined;\nasync function getPyodide(): Promise<Pyodide> {\n  return await enterJaegerSpan('get_pyodide', () => {\n    if (pyodidePromise) {\n      return pyodidePromise;\n    }\n    pyodidePromise = (async function (): Promise<Pyodide> {\n      const pyodide = loadPyodide(IS_WORKERD, LOCKFILE, WORKERD_INDEX_URL, {\n        pyodide_entrypoint_helper: get_pyodide_entrypoint_helper(),\n        cloudflare_compat_flags: COMPATIBILITY_FLAGS,\n      });\n      await setupPatches(pyodide);\n      return pyodide;\n    })();\n    return pyodidePromise;\n  });\n}\n\n/**\n * Import the data from the data module es6 import called jsModName.py into a module called\n * pyModName.py. The site_packages directory is on the path.\n */\nasync function injectSitePackagesModule(\n  pyodide: Pyodide,\n  jsModName: string,\n  pyModName: string\n): Promise<void> {\n  const mod = await import(`pyodide-internal:${jsModName}.py`);\n  pyodide.FS.writeFile(\n    `${pyodide.FS.sitePackages}/${pyModName}.py`,\n    new Uint8Array(mod.default),\n    { canOwn: true }\n  );\n}\n\n/**\n * Put the patch into site_packages and import it.\n *\n * TODO: Ideally we should only import the patch lazily when the package that it patches is\n * imported. Or just apply the patch directly or upstream a fix.\n */\nasync function applyPatch(pyodide: Pyodide, patchName: string): Promise<void> {\n  await injectSitePackagesModule(\n    pyodide,\n    `patches/${patchName}`,\n    patchName + '_patch'\n  );\n  pyodide.pyimport(patchName + '_patch');\n}\n\nasync function injectWorkersApi(pyodide: Pyodide): Promise<void> {\n  if (EXTERNAL_SDK) {\n    pyodide.FS.mkdir(`${pyodide.FS.sitePackages}/workers`);\n    const template = [\n      `err = ModuleNotFoundError(\"No module named '$MODNAME'\", name=\"$MODNAME\")`,\n      `err.add_note(\"You need to update to workers-py >= 1.90 or to pass disable_python_external_sdk\")`,\n      `raise err`,\n    ].join('\\n');\n    pyodide.FS.writeFile(\n      `${pyodide.FS.sitePackages}/workers/__init__.py`,\n      template.replaceAll('$MODNAME', 'workers')\n    );\n    pyodide.FS.writeFile(\n      `${pyodide.FS.sitePackages}/asgi.py`,\n      template.replaceAll('$MODNAME', 'asgi')\n    );\n    return;\n  }\n\n  const sitePackages = pyodide.FS.sitePackages;\n  if (pyodide.version === PyodideVersion.V0_26_0a2) {\n    // Inject at cloudflare.workers for backwards compatibility\n    pyodide.FS.mkdirTree(`${sitePackages}/cloudflare/workers`);\n    await injectSitePackagesModule(\n      pyodide,\n      'workers-api/src/workers/__init__',\n      'cloudflare/workers/__init__'\n    );\n    await injectSitePackagesModule(\n      pyodide,\n      'workers-api/src/workers/_workers',\n      'cloudflare/workers/_workers'\n    );\n  }\n  // The SDK was moved from `cloudflare.workers` to just `workers`.\n  // Create workers package structure with workflows submodule\n  pyodide.FS.mkdir(`${sitePackages}/workers`);\n  await injectSitePackagesModule(\n    pyodide,\n    'workers-api/src/workers/__init__',\n    'workers/__init__'\n  );\n  await injectSitePackagesModule(\n    pyodide,\n    'workers-api/src/workers/_workers',\n    'workers/_workers'\n  );\n  await injectSitePackagesModule(\n    pyodide,\n    'workers-api/src/workers/workflows',\n    'workers/workflows'\n  );\n  await injectSitePackagesModule(pyodide, 'workers-api/src/asgi', 'asgi');\n}\n\nasync function setupPatches(pyodide: Pyodide): Promise<void> {\n  await enterJaegerSpan('setup_patches', async () => {\n    patchLoadPackage(pyodide);\n\n    // install any extra packages into the site-packages directory\n    // Expose the doAnImport function and global modules to Python globals\n    pyodide.registerJsModule(\n      '_pyodide_entrypoint_helper',\n      get_pyodide_entrypoint_helper()\n    );\n\n    pyodide.registerJsModule('_cloudflare_compat_flags', COMPATIBILITY_FLAGS);\n\n    // Inject modules that enable JS features to be used idiomatically from Python.\n    await injectWorkersApi(pyodide);\n\n    // Install patches as needed\n    if (TRANSITIVE_REQUIREMENTS.has('aiohttp')) {\n      await applyPatch(pyodide, 'aiohttp');\n    }\n    // Other than the oldest version of httpx, we apply the patch at the build step.\n    if (\n      pyodide._module.API.version === PyodideVersion.V0_26_0a2 &&\n      TRANSITIVE_REQUIREMENTS.has('httpx')\n    ) {\n      await applyPatch(pyodide, 'httpx');\n    }\n  });\n}\n\nlet mainModulePromise: Promise<PyModule> | undefined;\nfunction getMainModule(): Promise<PyModule> {\n  return enterJaegerSpan('get_main_module', async () => {\n    if (mainModulePromise) {\n      return mainModulePromise;\n    }\n    mainModulePromise = (async function (): Promise<PyModule> {\n      const pyodide = await getPyodide();\n      Limiter.beginStartup();\n      try {\n        return await enterJaegerSpan('pyimport_main_module', () =>\n          pyimportMainModule(pyodide)\n        );\n      } catch (e: any) {\n        handleSrcImport(pyodide, e);\n      } finally {\n        Limiter.finishStartup(LOADED_SNAPSHOT_TYPE);\n      }\n    })();\n    return mainModulePromise;\n  });\n}\n\nasync function preparePython(): Promise<PyModule> {\n  try {\n    const pyodide = await getPyodide();\n    const mainModule = await getMainModule();\n    beforeRequest(pyodide._module);\n    return mainModule;\n  } catch (e) {\n    // In edgeworker test suite, without this we get the file name and line number of the exception\n    // but no traceback. This gives us a full traceback.\n    reportError(e as Error);\n  }\n}\n\nasync function doPyCallHelper(\n  relaxed: boolean,\n  pyfunc: PyCallable,\n  args: any[]\n): Promise<any> {\n  const pyodide = await getPyodide();\n  clearSignals(pyodide._module);\n  try {\n    if (pyfunc.callWithOptions) {\n      return await pyfunc.callWithOptions(\n        { relaxed, promising: true },\n        ...args\n      );\n    }\n    if (relaxed) {\n      return await pyfunc.callRelaxed(...args);\n    }\n    return await pyfunc(...args);\n  } catch (e: any) {\n    const pyodide = await getPyodide();\n    handleSrcImport(pyodide, e);\n  }\n}\n\nfunction doPyCall(pyfunc: PyCallable, args: any[]): any {\n  return doPyCallHelper(false, pyfunc, args);\n}\n\nfunction doRelaxedPyCall(pyfunc: PyCallable, args: any[]): any {\n  return doPyCallHelper(true, pyfunc, args);\n}\n\nfunction makeHandler(pyHandlerName: string): Handler {\n  if (\n    pyHandlerName === 'test' &&\n    SHOULD_SNAPSHOT_TO_DISK &&\n    LEGACY_GLOBAL_HANDLERS\n  ) {\n    return async function () {\n      await getPyodide();\n      console.log('Stored snapshot to disk; quitting without running test');\n    };\n  }\n  return async function (...args: any[]) {\n    const mainModule = await enterJaegerSpan(\n      'prep_python',\n      async () => await preparePython()\n    );\n    const handler = mainModule[pyHandlerName];\n    if (!handler) {\n      throw new PythonUserError(\n        `Python entrypoint \"${MAIN_MODULE_NAME}\" does not export a handler named \"${pyHandlerName}\"`\n      );\n    }\n    const result = await enterJaegerSpan('python_code', () => {\n      return doRelaxedPyCall(handler, args);\n    });\n\n    // Support returning a pyodide.ffi.FetchResponse.\n    return result?.js_response ?? result;\n  };\n}\n\nasync function initPyInstance(\n  className: string,\n  args: any[]\n): Promise<PyModule> {\n  const mainModule = await preparePython();\n  const pyClassConstructor = mainModule[className];\n  if (typeof pyClassConstructor !== 'function') {\n    throw new TypeError(\n      `There is no '${className}' class defined in the Python Worker's main module`\n    );\n  }\n  const res = await doPyCall(pyClassConstructor, args);\n  return res as PyModule;\n}\n\n// https://developers.cloudflare.com/workers/runtime-apis/rpc/reserved-methods/\nconst SPECIAL_HANDLER_NAMES = ['fetch', 'connect'];\nconst SPECIAL_DO_HANDLER_NAMES = [\n  'alarm',\n  'webSocketMessage',\n  'webSocketClose',\n  'webSocketError',\n];\n\nfunction makeEntrypointProxyHandler(\n  pyInstancePromise: Promise<PyModule>,\n  className: string\n): ProxyHandler<any> {\n  return {\n    get(target, prop, receiver): any {\n      if (typeof prop !== 'string') {\n        return Reflect.get(target, prop, receiver);\n      }\n      const isDurableObject = className === 'DurableObject';\n      const isWorkflow = className === 'WorkflowEntrypoint';\n\n      // Proxy calls to `fetch` to methods named `on_fetch` (and the same for other handlers.)\n      const isKnownHandler = SPECIAL_HANDLER_NAMES.includes(prop);\n      const isKnownDoHandler =\n        isDurableObject && SPECIAL_DO_HANDLER_NAMES.includes(prop);\n      const isFetch = prop === 'fetch';\n      const isWorkflowHandler = isWorkflow && prop === 'run';\n      if ((isKnownHandler || isKnownDoHandler) && LEGACY_GLOBAL_HANDLERS) {\n        prop = 'on_' + prop;\n      }\n\n      if (\n        !LEGACY_GLOBAL_HANDLERS &&\n        prop === 'test' &&\n        SHOULD_SNAPSHOT_TO_DISK\n      ) {\n        return async function () {\n          await getPyodide();\n          console.log('Stored snapshot to disk; quitting without running test');\n        };\n      }\n\n      return async function (...args: any[]): Promise<any> {\n        // Check if the requested method exists and if so, call it.\n        const pyInstance = await pyInstancePromise;\n\n        if (typeof pyInstance[prop] !== 'function') {\n          throw new TypeError(`Method ${prop} does not exist`);\n        }\n\n        if ((isKnownHandler || isKnownDoHandler) && !isFetch) {\n          return await doPyCallHelper(\n            true,\n            pyInstance[prop] as PyCallable,\n            args\n          );\n        }\n\n        if (WORKFLOWS_ENABLED && isWorkflowHandler) {\n          // we're hiding this behind a compat flag for now\n          return await doPyCallHelper(\n            true,\n            pyInstance[prop] as PyCallable,\n            args\n          );\n        }\n\n        const introspectionMod = await getIntrospectionMod();\n\n        const isRelaxed = isFetch || prop === 'test';\n        return await doPyCall(introspectionMod.wrapper_func, [\n          isRelaxed,\n          pyInstance,\n          prop,\n          ...args,\n        ]);\n      };\n    },\n  };\n}\n\nfunction makeEntrypointClass(\n  className: string,\n  classKind: AnyClass,\n  methods: string[]\n): any {\n  const result = class EntrypointWrapper extends classKind {\n    constructor(...args: any[]) {\n      super(...args);\n      // Initialise a Python instance of the class.\n      const pyInstancePromise = initPyInstance(className, args);\n      // We do not know the methods that are defined on the RPC class, so we need a proxy to\n      // support any possible method name.\n      return new Proxy(\n        this,\n        makeEntrypointProxyHandler(pyInstancePromise, classKind.name)\n      );\n    }\n  };\n\n  // Add dummy functions to the class so that the validator can detect them. These will never get\n  // accessed because of the proxy at runtime.\n  for (let method of methods) {\n    if (\n      SUPPORTED_HANDLER_NAMES.includes(method.slice(3)) &&\n      LEGACY_GLOBAL_HANDLERS\n    ) {\n      // Remove the \"on_\" prefix.\n      method = method.slice(3);\n    }\n    result.prototype[method] = function (): void {};\n  }\n  return result;\n}\n\ntype IntrospectionMod = {\n  __dict__: PyDict;\n  collect_entrypoint_classes: (mod: PyModule) => PythonEntrypointClasses;\n  wrapper_func: PyCallable;\n};\n\nlet introspectionModPromise: Promise<IntrospectionMod> | null = null;\nasync function loadIntrospectionMod(\n  pyodide: Pyodide\n): Promise<IntrospectionMod> {\n  const introspectionSource = await import('pyodide-internal:introspection.py');\n  const introspectionMod = pyodide.runPython(\n    \"from types import ModuleType; ModuleType('introspection')\"\n  ) as IntrospectionMod;\n  const decoder = new TextDecoder();\n  pyodide.runPython(decoder.decode(introspectionSource.default), {\n    globals: introspectionMod.__dict__,\n    filename: 'introspection.py',\n  });\n\n  return introspectionMod;\n}\n\nasync function getIntrospectionMod(): Promise<IntrospectionMod> {\n  if (introspectionModPromise === null) {\n    introspectionModPromise = getPyodide().then(loadIntrospectionMod);\n  }\n\n  return introspectionModPromise;\n}\n\nconst SUPPORTED_HANDLER_NAMES = [\n  'fetch',\n  'alarm',\n  'scheduled',\n  'trace',\n  'queue',\n  'pubsub',\n];\n\ntype ExporterClassInfo = {\n  className: string;\n  methodNames: string[];\n};\n\ntype PythonEntrypointClasses = {\n  durableObjects: ExporterClassInfo[];\n  workerEntrypoints: ExporterClassInfo[];\n  workflowEntrypoints: ExporterClassInfo[];\n};\n\ntype PythonInitResult = {\n  handlers: { [handlerName: string]: Handler };\n  pythonEntrypointClasses: PythonEntrypointClasses;\n  makeEntrypointClass: typeof makeEntrypointClass;\n};\n\nfunction handleDefaultClass(\n  handlers: PythonInitResult['handlers'],\n  workerEntrypoints: ExporterClassInfo[]\n): void {\n  const index = workerEntrypoints.findIndex(\n    (cls) => cls.className === 'Default'\n  );\n  if (index === -1) {\n    return;\n  }\n  const cls = workerEntrypoints[index]!;\n\n  // Disallow defining a `Default` WorkerEntrypoint and other \"default\" top-level handlers.\n  if (Object.keys(handlers).length > 0) {\n    throw new TypeError('Cannot define multiple default entrypoints');\n  }\n\n  handlers['default'] = makeEntrypointClass(\n    'Default',\n    get_pyodide_entrypoint_helper().workerEntrypoint,\n    cls.methodNames\n  );\n  // Remove the default entrypoint from the list of workerEntrypoints to avoid duplication.\n  workerEntrypoints.splice(index, 1);\n}\n\nexport async function initPython(): Promise<PythonInitResult> {\n  const handlers: {\n    [handlerName: string]: Handler;\n  } = {};\n\n  let pythonEntrypointClasses: PythonEntrypointClasses = {\n    durableObjects: [],\n    workerEntrypoints: [],\n    workflowEntrypoints: [],\n  };\n\n  const mainModule = await getMainModule();\n\n  // In order to get the entrypoint classes exported by the worker, we use a Python module\n  // to introspect the user's main module. So we are effectively using Python to analyse the\n  // classes exported by the user worker here. The class names are then exported from here and\n  // used to create the equivalent JS classes via makeEntrypointClass.\n  const introspectionMod = await getIntrospectionMod();\n  pythonEntrypointClasses =\n    introspectionMod.collect_entrypoint_classes(mainModule);\n  handleDefaultClass(handlers, pythonEntrypointClasses.workerEntrypoints);\n\n  if (LEGACY_GLOBAL_HANDLERS) {\n    // We add all handlers when running in workerd, so that we can handle the case where the\n    // handler is not defined in our own code and throw a more helpful error. See\n    // undefined-handler.wd-test.\n    const addAllHandlers = IS_WORKERD && !handlers['default'];\n    for (const handlerName of SUPPORTED_HANDLER_NAMES) {\n      const pyHandlerName = 'on_' + handlerName;\n      if (addAllHandlers || typeof mainModule[pyHandlerName] === 'function') {\n        handlers[handlerName] = makeHandler(pyHandlerName);\n      }\n    }\n\n    if (typeof mainModule.test === 'function') {\n      handlers.test = makeHandler('test');\n    }\n  }\n\n  // Collect a dedicated snapshot at the very end.\n  const pyodide = await getPyodide();\n  const customSerializedObjects = {\n    pyodide_entrypoint_helper: get_pyodide_entrypoint_helper(),\n    cloudflare_compat_flags: COMPATIBILITY_FLAGS,\n  };\n  maybeCollectDedicatedSnapshot(pyodide._module, customSerializedObjects);\n\n  return { handlers, pythonEntrypointClasses, makeEntrypointClass };\n}\n"
  },
  {
    "path": "src/pyodide/python-entrypoint.js",
    "content": "// The entrypoint to a worker has to be an ES6 module (well, ignoring service\n// workers). For Python workers, we use this file as the ES6 entrypoint.\n//\n// This file is treated as part of the user bundle and cannot import internal\n// modules. Any imports from this file must be user-visible. To deal with this,\n// we delegate the implementation to `python-entrypoint-helper` which is a\n// BUILTIN module that can see our INTERNAL modules.\n\nimport {\n  DurableObject,\n  WorkerEntrypoint,\n  WorkflowEntrypoint,\n} from 'cloudflare:workers';\n\n// The creation of `pythonDurableObjects` et al. has to be done here because\n// python-entrypoint-helper is a BUILTIN and so cannot import `DurableObject` et al.\n// (which are also builtins). As a workaround we call `makeEntrypointClass` here and pass it the\n// appropriate class.\nimport {\n  setDoAnImport,\n  initPython,\n  createImportProxy,\n} from 'pyodide:python-entrypoint-helper';\n\n// Function to dynamically import JavaScript modules from Python\n// We need the import \"call\" to occur in this file since it is the only file that is part of the\n// user bundle and can see BUILTIN modules and not INTERNAL modules. If we put the import in any\n// other file, it would be possible to import INTERNAL modules (not good) and not possible to import\n// USER or BUILTIN modules.\nasync function doAnImport(name) {\n  const mod = await import(name);\n  return createImportProxy(name, mod);\n}\n\n// Pass the import function to the helper\nawait setDoAnImport(doAnImport, WorkerEntrypoint);\n\n// Initialise Python only after the import function has been set above.\nconst { handlers, pythonEntrypointClasses, makeEntrypointClass } =\n  await initPython();\n\nfunction makeEntrypointClassFromNames(classes, baseClass) {\n  return classes.map(({ className, methodNames }) => [\n    className,\n    makeEntrypointClass(className, baseClass, methodNames),\n  ]);\n}\n\nconst entrypoints = {\n  durableObjects: DurableObject,\n  workerEntrypoints: WorkerEntrypoint,\n  workflowEntrypoints: WorkflowEntrypoint,\n};\n\nconst pythonEntrypoints = Object.fromEntries(\n  Object.entries(entrypoints).flatMap(([key, baseClass]) => {\n    const classes = pythonEntrypointClasses[key];\n    return makeEntrypointClassFromNames(classes, baseClass);\n  })\n);\n\nexport { pythonEntrypoints };\nexport default 'default' in handlers ? handlers['default'] : handlers;\n"
  },
  {
    "path": "src/pyodide/tool_utils.py",
    "content": "import subprocess\nimport sys\nfrom base64 import b64encode\nfrom contextlib import contextmanager\nfrom hashlib import file_digest\nfrom pathlib import Path\nfrom time import time\nfrom typing import Any\n\n\ndef run(\n    cmd: list[str | Path],\n    cwd: Path | str | None = None,\n    env: dict[str, str] | None = None,\n) -> subprocess.CompletedProcess[Any]:\n    \"\"\"Execute a command and exit on failure with error output.\"\"\"\n    res = subprocess.run(\n        cmd, capture_output=True, text=True, check=False, cwd=cwd, env=env\n    )\n    if res.returncode:\n        print(\"Invocation failed:\")\n        print(res.stdout)\n        print(res.stderr)\n        sys.exit(res.returncode)\n    return res\n\n\ndef bytesdigest(p: Path | str) -> bytes:\n    \"\"\"Calculate SHA256 digest of a file as bytes.\"\"\"\n    return file_digest(Path(p).open(\"rb\"), \"sha256\").digest()\n\n\ndef hexdigest(p: Path | str) -> str:\n    \"\"\"Calculate SHA256 digest of a file as hex string.\"\"\"\n    digest = bytesdigest(p)\n    return \"\".join(hex(e)[2:].zfill(2) for e in digest)\n\n\ndef b64digest(p: Path | str):\n    return \"sha256-\" + b64encode(bytesdigest(p)).decode()\n\n\n@contextmanager\ndef timing(name: str):\n    \"\"\"Context manager to time operations and print elapsed time.\"\"\"\n    print(f\"Making {name}...\")\n    start = time()\n    yield\n    elapsed = time() - start\n    print(f\"  {elapsed:.2f}s\")\n"
  },
  {
    "path": "src/pyodide/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tools/base.tsconfig.json\",\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"paths\": {\n      \"pyodide:*\": [\"./*\"],\n      \"pyodide-internal:*\": [\"./internal/*\", \"./types/*\"],\n      // generated/emscriptenSetup is an esbuild-bundled version of pool/emscriptenSetup. They have\n      // the same exports, so tell TypeScript to redirect the import.\n      \"pyodide-internal:generated/emscriptenSetup\": [\n        \"./internal/pool/emscriptenSetup.ts\"\n      ],\n      \"internal:*\": [\"./types/*\"],\n      \"cloudflare-internal:*\": [\"./types/cloudflare-internal/*\"]\n    },\n    \"typeRoots\": [\"./types\"],\n    \"noEmit\": false,\n    \"noEmitOnError\": false,\n    \"allowArbitraryExtensions\": true\n  },\n  \"include\": [\"./**/*\"],\n  \"exclude\": [\"./**/generated/**/*\", \"node_modules\", \"**/*.mjs\"]\n}\n"
  },
  {
    "path": "src/pyodide/types/Error.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ninterface ErrorConstructor {\n  prepareStackTrace: ((_error: Error, stack: StackItem[]) => void) | undefined;\n  stackTraceLimit: number;\n}\n\ninterface StackItem {\n  getFunctionName: () => string;\n  getFileName: () => string;\n}\n"
  },
  {
    "path": "src/pyodide/types/Pyodide.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ndeclare type AnyClass<T = any> = new (...args: any[]) => T;\n\ndeclare type Handler = (...args: any[]) => any;\n\ndeclare type PyCallable = ((...args: any[]) => any) & {\n  call: (...args: any[]) => any;\n  callRelaxed: (...args: any[]) => any;\n  callPromising: (...args: any[]) => any;\n  callWithOptions?: (\n    options: { relaxed?: boolean; promising?: boolean; kwargs?: boolean },\n    ...args: any[]\n  ) => any;\n};\n\ndeclare type PyDict = object;\n\ninterface PyModule {\n  [handlerName: string]: PyCallable;\n}\n\ninterface Pyodide {\n  _module: Module;\n  runPython: (\n    code: string,\n    opts?: { globals?: PyDict; filename?: string }\n  ) => any;\n  registerJsModule: (handle: string, mod: object) => void;\n  pyimport: (moduleName: string) => PyModule;\n  FS: FS;\n  loadPackage: (names: string | string[], options: object) => Promise<any[]>;\n  setStdout: (options?: any) => void;\n  setStderr: (options?: any) => void;\n  version: API['version'];\n}\n"
  },
  {
    "path": "src/pyodide/types/artifacts.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ndeclare namespace ArtifactBundler {\n  type SnapshotType = 'baseline' | 'dedicated' | 'package';\n  type MemorySnapshotResult = {\n    snapshot: Uint8Array;\n    importedModulesList: string[];\n    snapshotType: SnapshotType;\n  };\n\n  const hasMemorySnapshot: () => boolean;\n  const isEwValidating: () => boolean;\n  const readMemorySnapshot: (\n    offset: number,\n    buf: Uint32Array | Uint8Array\n  ) => void;\n  const getMemorySnapshotSize: () => number;\n  const disposeMemorySnapshot: () => void;\n  const storeMemorySnapshot: (snap: MemorySnapshotResult) => void;\n  const getPackage: (path: string) => Reader | null;\n}\n\nexport default ArtifactBundler;\n"
  },
  {
    "path": "src/pyodide/types/cloudflare-internal/env.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nconst innerEnv: {\n  pythonPatchEnv(patch: unknown): {\n    [Symbol.dispose](): void;\n  };\n};\nexport default innerEnv;\n"
  },
  {
    "path": "src/pyodide/types/disk_cache.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ndeclare namespace DiskCache {\n  const get: (key: string) => ArrayBuffer | null;\n  const put: (key: string, val: ArrayBuffer | Uint8Array) => void;\n  const putSnapshot: (key: string, val: ArrayBuffer | Uint8Array) => void;\n}\n\nexport default DiskCache;\n"
  },
  {
    "path": "src/pyodide/types/emscripten.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ninterface ENV {\n  HOME: string;\n  [k: string]: string;\n}\n\ninterface PyodideConfig {\n  env: ENV;\n  jsglobals: typeof globalThis;\n  resolveLockFilePromise?: (lockfile: PackageLock) => void;\n  indexURL?: string;\n  _makeSnapshot?: boolean;\n  lockFileURL: '';\n  enableRunUntilComplete: boolean;\n}\n\ntype SerializedHiwireValue = { path: string[] } | { serialized: any } | null;\n\ntype SnapshotConfig = {\n  hiwireKeys: SerializedHiwireValue[];\n  immortalKeys: string[];\n};\n\ninterface API {\n  config: PyodideConfig;\n  finalizeBootstrap: (\n    fromSnapshot?: SnapshotConfig,\n    snapshotDeserializer?: (obj: any) => any\n  ) => void;\n  public_api: Pyodide;\n  rawRun: (code: string) => [status: number, err: string];\n  initializeStreams: (\n    stdin?: any,\n    stdout?: (a: string) => void,\n    stderr?: (a: string) => void\n  ) => void;\n  version: '0.26.0a2' | '0.28.2';\n  pyodide_base: {\n    pyimport_impl: PyCallable;\n  };\n  serializeHiwireState(serializer: (obj: any) => any): SnapshotConfig;\n  pyVersionTuple: [number, number, number];\n  scheduleCallback: (callback: () => void, timeout: number) => void;\n  // Callback invoked when Pyodide encounters a fatal error. Setting this allows\n  // the runtime to handle fatal errors (e.g., by condemning the isolate).\n  on_fatal?: (error: any) => void;\n}\n\ninterface LDSO {\n  loadedLibsByHandle: {\n    [handle: number]: DSO;\n  };\n  loadedLibsByName: {\n    [name: string]: DSO;\n  };\n}\n\ninterface DSO {\n  name: string;\n  refcount: number;\n  global: boolean;\n  exports: WebAssembly.Exports;\n}\n\n// https://github.com/emscripten-core/emscripten/blob/main/src/lib/libpath.js\ninterface PATH {\n  normalizeArray: (parts: string[], allowAboveRoot: boolean) => string[];\n}\n\ntype PreRunHook = (mod: Module) => void;\n\ninterface EmscriptenSettings {\n  preRun: PreRunHook[];\n  instantiateWasm: (\n    wasmImports: WebAssembly.Imports,\n    successCallback: (\n      inst: WebAssembly.Instance,\n      mod: WebAssembly.Module\n    ) => void\n  ) => WebAssembly.Exports;\n  reportUndefinedSymbolsNoOp: () => void;\n  noInitialRun?: boolean;\n  API: Pick<API, 'config'>;\n  readyPromise: Promise<Module>;\n  rejectReadyPromise: (e: any) => void;\n}\n\ninterface Module {\n  HEAP8: Uint8Array;\n  HEAPU32: Uint32Array;\n  _dump_traceback: () => void;\n  FS: FS;\n  API: API;\n  ENV: ENV;\n  LDSO: LDSO;\n  PATH: PATH;\n  newDSO: (path: string, opt: object | undefined, handle: string) => DSO;\n  _Py_Version: number;\n  _py_version_major?: () => number;\n  _py_version_minor: () => number;\n  _py_version_micro: () => number;\n  loadWebAssemblyModule: (\n    mod: WebAssembly.Module,\n    opt: object,\n    path: string,\n    localScope: object\n  ) => WebAssembly.Exports;\n  growMemory(newSize: number): void;\n  addRunDependency(x: string): void;\n  removeRunDependency(x: string): void;\n  noInitialRun: boolean;\n  setUnsafeEval(mod: typeof import('internal:unsafe-eval').default): void;\n  setGetRandomValues(\n    func: typeof import('pyodide-internal:topLevelEntropy/lib').getRandomValues\n  ): void;\n  setSetTimeout(\n    st: typeof setTimeout,\n    ct: typeof clearTimeout,\n    si: typeof setInterval,\n    ci: typeof clearInterval\n  ): void;\n  getMemory(size: number): number;\n  getMemoryPatched(\n    Module: Module,\n    libName: string,\n    handle: number,\n    size: number\n  ): number;\n  promise: Promise<void>;\n  reportUndefinedSymbols(): void;\n  wasmTable: WebAssembly.Table;\n  // Set snapshotDebug to true to print relocation information when loading dynamic libraries. If\n  // there are crashes involving snapshots this information can be compared between when creating\n  // and using the snapshot and any discrepancy explains the crash.\n  snapshotDebug?: boolean;\n  getEmptyTableSlot(): number;\n  freeTableIndexes: number[];\n  LD_LIBRARY_PATH: string;\n  Py_EmscriptenSignalBuffer: Uint8Array;\n  _Py_EMSCRIPTEN_SIGNAL_HANDLING: number;\n  ___memory_base: WebAssembly.Global<'i32'>;\n  compileModuleFromReadOnlyFS: (\n    Module: Module,\n    path: string\n  ) => WebAssembly.Module;\n  findLibraryFS: (libName: string, rpath: any) => string;\n}\n"
  },
  {
    "path": "src/pyodide/types/fatal-reporter.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// A typescript declaration file for the internal WorkerFatalReporter JSG class\n// see src/workerd/api/pyodide/pyodide.h (WorkerFatalReporter)\n\ninterface FatalReporter {\n  reportFatal: (error: string) => void;\n}\n\ndeclare const fatalReporter: FatalReporter;\n\nexport default fatalReporter;\n"
  },
  {
    "path": "src/pyodide/types/filesystem.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ninterface Reader {\n  read: (offset: number, dest: Uint8Array) => number;\n}\n\ninterface TarFSInfo {\n  children: Map<string, TarFSInfo> | undefined;\n  mode: number;\n  type: string;\n  modtime: number;\n  size: number;\n  path: string;\n  name: string;\n  parts: string[];\n  contentsOffset?: number;\n  reader: Reader | null;\n}\n\ndeclare type MetadataDirInfo = Map<string, MetadataFSInfo>;\ndeclare type MetadataFSInfo = MetadataDirInfo | number; // file infos are numbers and dir infos are maps\n\ninterface FSLookupResult<Info> {\n  node: FSNode<Info>;\n}\n\ninterface FS {\n  mkdir: (dirname: string) => void;\n  mkdirTree: (dirname: string) => void;\n  writeFile: (\n    fname: string,\n    contents: Uint8Array | string,\n    options?: { canOwn: boolean }\n  ) => void;\n  readFile: (fname: string, options?: { encoding?: string }) => Uint8Array;\n  mount(fs: object, options: { info?: any }, path: string): void;\n  createNode<Info>(\n    parent: FSNode<Info> | null,\n    name: string,\n    mode: number\n  ): FSNode<Info>;\n  lookupPath<Info>(path: string): FSLookupResult<Info>;\n  open<Info>(nodeOrPath: FSNode<Info> | string, flags?: number): FSStream<Info>;\n  read<Info>(\n    stream: FSStream<Info>,\n    buffer: Uint8Array,\n    offset: number,\n    length: number,\n    position: number\n  ): number;\n  close<Info>(stream: FSStream<Info>): void;\n  isFile: (mode: number) => boolean;\n  readdir: (path: string) => string[];\n  genericErrors: { 44: Error };\n  sitePackages: string;\n  sessionSitePackages: string;\n  ErrnoError: { new (errno: number): Error };\n}\n\ninterface FSOps<Info> {\n  getNodeMode: (\n    parent: FSNode<Info> | null,\n    name: string,\n    info: Info\n  ) => {\n    permissions: number;\n    isDir: boolean;\n  };\n  setNodeAttributes: (\n    node: FSNode<Info>,\n    info: Info | undefined,\n    isDir: boolean\n  ) => void;\n  readdir: (node: FSNode<Info>) => string[];\n  lookup: (parent: FSNode<Info>, name: string) => Info;\n  read: (\n    stream: FSStream<Info>,\n    position: number,\n    buffer: Uint8Array\n  ) => number;\n}\n\ninterface MountOpts<Info> {\n  opts: { info: Info };\n}\n\ninterface FSNodeOps<Info> {\n  getattr: (node: FSNode<Info>) => FSAttr;\n  readdir: (node: FSNode<Info>) => string[];\n  lookup: (parent: FSNode<Info>, name: string) => FSNode<Info>;\n}\n\ninterface FSStream<Info> {\n  node: FSNode<Info>;\n  position: number;\n}\n\ninterface FSStreamOps<Info> {\n  llseek: (stream: FSStream<Info>, offset: number, whence: number) => number;\n  read: (\n    stream: FSStream<Info>,\n    buffer: Uint8Array,\n    offset: number,\n    length: number,\n    position: number\n  ) => number;\n}\n\ninterface FSMount {\n  type: EmscriptenFS<any>;\n}\n\ninterface FSNode<Info> {\n  id: number;\n  usedBytes: number;\n  mode: number;\n  modtime: number;\n  node_ops: FSNodeOps<Info>;\n  stream_ops: FSStreamOps<Info>;\n  mount: FSMount;\n  info: Info;\n  contentsOffset?: number | undefined;\n  tree?: MetadataDirInfo;\n  index?: number;\n}\n\ninterface FSAttr {\n  dev: number;\n  ino: number;\n  mode: number;\n  nlink: number;\n  uid: number;\n  gid: number;\n  rdev: number;\n  size: number;\n  atime: Date;\n  mtime: Date;\n  ctime: Date;\n  blksize: number;\n  blocks: number;\n}\n\ninterface EmscriptenFS<Info> {\n  mount: (mount: MountOpts<Info>) => FSNode<Info>;\n  createNode: (\n    parent: FSNode<Info> | null,\n    name: string,\n    info: Info\n  ) => FSNode<Info>;\n  node_ops: FSNodeOps<Info>;\n  stream_ops: FSStreamOps<Info>;\n}\n"
  },
  {
    "path": "src/pyodide/types/internalJaeger.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ndeclare namespace internalJaeger {\n  const traceId: number | null,\n    enterSpan: <T>(name: string, callback: () => T) => T;\n}\n\nexport default internalJaeger;\n"
  },
  {
    "path": "src/pyodide/types/limiter.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// A typescript declaration file for the internal Limiter JSG class\n// see src/workerd/api/pyodide/pyodide.h (SimplePythonLimiter)\n\nimport ArtifactBundler from './artifacts.js';\n\ninterface Limiter {\n  beginStartup: () => void;\n  finishStartup: (\n    snapshotType: ArtifactBundler.SnapshotType | undefined\n  ) => void;\n}\n\ndeclare const limiter: Limiter;\n\nexport default limiter;\n"
  },
  {
    "path": "src/pyodide/types/modules.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ndeclare module 'pyodide-internal:introspection.py' {\n  const value: Uint8Array;\n  export default value;\n}\n\ndeclare module 'pyodideRuntime-internal:emscriptenSetup' {\n  function instantiateEmscriptenModule(\n    isWorkerd: boolean,\n    a: any,\n    b: any,\n    UnsafeEval: any\n  ): Promise<Module>;\n  export { instantiateEmscriptenModule };\n}\n\ndeclare module 'pyodideRuntime-internal:python_stdlib.zip' {\n  const value: Uint8Array;\n  export default value;\n}\n\ndeclare module 'pyodideRuntime-internal:pyodide.asm.wasm' {\n  const value: Uint8Array;\n  export default value;\n}\n"
  },
  {
    "path": "src/pyodide/types/packages_tar_reader.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ndeclare const TarReader: Reader;\n\nexport default TarReader;\n"
  },
  {
    "path": "src/pyodide/types/pyodide-lock.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ntype InstallDir = 'site' | 'stdlib' | 'dynlib';\ninterface PackageDeclaration {\n  depends: string[];\n  file_name: string;\n  imports: string[];\n  install_dir: InstallDir;\n  name: string;\n  package_type: string;\n  sha256: string;\n  shared_library: boolean;\n  unvendored_tests: boolean;\n  version: string;\n}\n\ninterface PackageLock {\n  packages: {\n    [id: string]: PackageDeclaration;\n  };\n}\n\ndeclare module 'pyodide-internal:generated/pyodide-lock.json' {\n  const lock: PackageLock;\n  export default lock;\n}\n"
  },
  {
    "path": "src/pyodide/types/pyodide.asm.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ndeclare module 'pyodide-internal:generated/pyodide.asm.wasm' {\n  const pyodideWasmModule: WebAssembly.Module;\n  export default pyodideWasmModule;\n}\n\ndeclare module 'pyodide-internal:generated/pyodide.asm' {\n  const _createPyodideModule: (es: EmscriptenSettings) => Promise<void>;\n}\n"
  },
  {
    "path": "src/pyodide/types/python_stdlib.zip.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ndeclare module 'pyodide-internal:generated/python_stdlib.zip' {\n  const stdlib: Uint8Array;\n  export default stdlib;\n}\n"
  },
  {
    "path": "src/pyodide/types/runtime-generated/metadata.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ndeclare namespace MetadataReader {\n  export interface CompatibilityFlags {\n    python_workflows?: boolean;\n    python_no_global_handlers?: boolean;\n    python_workers_force_new_vendor_path?: boolean;\n    python_dedicated_snapshot?: boolean;\n    enable_python_external_sdk?: boolean;\n    python_check_rng_state?: boolean;\n    python_workflows_implicit_dependencies?: boolean;\n  }\n\n  const isWorkerd: () => boolean;\n  const isTracing: () => boolean;\n  const shouldSnapshotToDisk: () => boolean;\n  const isCreatingBaselineSnapshot: () => boolean;\n  const getRequirements: () => string[];\n  const getMainModule: () => string;\n  const hasMemorySnapshot: () => boolean;\n  const getNames: () => string[];\n  const getPackageSnapshotImports: (version: string) => string[];\n  const getSizes: () => number[];\n  const readMemorySnapshot: (\n    offset: number,\n    buf: Uint32Array | Uint8Array\n  ) => void;\n  const getMemorySnapshotSize: () => number;\n  const disposeMemorySnapshot: () => void;\n  const getPyodideVersion: () => string;\n  const getPackagesVersion: () => string;\n  const getPackagesLock: () => string;\n  const read: (index: number, position: number, buffer: Uint8Array) => number;\n  const getTransitiveRequirements: () => Set<string>;\n  const getCompatibilityFlags: () => CompatibilityFlags;\n  const setCpuLimitNearlyExceededCallback: (\n    buf: Uint8Array,\n    sig_clock: number,\n    sig_flag: number\n  ) => void;\n  const constructor: {\n    getBaselineSnapshotImports(): string[];\n  };\n}\n\nexport default MetadataReader;\n"
  },
  {
    "path": "src/pyodide/types/setup-emscripten.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ndeclare namespace SetupEmscripten {\n  const getModule: () => Module;\n}\n\nexport default SetupEmscripten;\n"
  },
  {
    "path": "src/pyodide/types/unsafe-eval.d.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ndeclare namespace UnsafeEval {\n  const newWasmModule: (wasm: Uint8Array) => WebAssembly.Module;\n}\n\nexport default UnsafeEval;\n"
  },
  {
    "path": "src/pyodide/upload_bundles.py",
    "content": "import argparse\nimport json\nimport re\nimport subprocess\nimport sys\nfrom copy import deepcopy\nfrom dataclasses import dataclass\nfrom functools import cache\nfrom os import environ\nfrom pathlib import Path\n\nimport requests\nfrom tool_utils import b64digest\n\n\ndef cquery(rule):\n    res = subprocess.run(\n        [\n            \"bazel\",\n            \"cquery\",\n            rule,\n            \"--output=files\",\n        ],\n        capture_output=True,\n        text=True,\n        check=False,\n    )\n    if res.returncode:\n        print(res.stdout)\n        print(res.stderr)\n        sys.exit(res.returncode)\n    return res.stdout.strip()\n\n\ndef bazel_mod_tidy():\n    res = subprocess.run([\"bazel\", \"mod\", \"tidy\"])\n    if res.returncode:\n        print(res.stdout)\n        print(res.stderr)\n        sys.exit(res.returncode)\n\n\n@cache\ndef _bundle_version_info():\n    with Path(cquery(\"@workerd//src/pyodide:bundle_version_info\")).open() as f:\n        return json.load(f)\n\n\ndef bundle_version_info():\n    return deepcopy(_bundle_version_info())\n\n\ndef get_pyodide_bin_path(ver):\n    return cquery(f\"@workerd//src/pyodide:pyodide.capnp.bin@rule@{ver}\")\n\n\ndef bundle_key(*, pyodide_version, pyodide_date, backport, **_kwds):\n    return f\"pyodide_{pyodide_version}_{pyodide_date}_{backport}.capnp.bin\"\n\n\ndef bundle_url(**kwds):\n    return \"https://pyodide-capnp-bin.edgeworker.net/\" + bundle_key(**kwds)\n\n\ndef get_backport(ver):\n    info = bundle_version_info()[ver]\n    backport = int(info[\"backport\"])\n    for b in range(backport + 1, backport + 20):\n        info[\"backport\"] = b\n        url = bundle_url(**info)\n        res = requests.head(url)\n        if res.status_code == 404:\n            return b\n        res.raise_for_status()\n\n\ndef _get_replacer(backport, integrity):\n    def replace_values(match):\n        prefix = match.group(1)\n        backport_key = match.group(2)\n        middle = match.group(3)\n        integrity_key = match.group(4)\n        return (\n            f'{prefix}{backport_key}\"{backport}\",{middle}{integrity_key}\"{integrity}\",'\n        )\n\n    return replace_values\n\n\n@dataclass\nclass BundleInfo:\n    version: str\n    backport: int\n    integrity: str\n    path: Path\n\n\ndef update_python_metadata_bzl(bundles: list[BundleInfo]) -> None:\n    \"\"\"Update python_metadata.bzl file with new backport and integrity values.\"\"\"\n    metadata_path = (\n        Path(__file__).parent.parent.parent / \"build\" / \"python_metadata.bzl\"\n    )\n    content = metadata_path.read_text()\n\n    for info in bundles:\n        # Find the version block and update backport and integrity\n        version_pattern = rf'(\\s+{{\\s*\\n\\s*\"name\":\\s*\"{re.escape(info.version)}\",.*?)(\"backport\":\\s*)\"[^\"]*\",(.*?)(\"integrity\":\\s*)\"[^\"]*\",'\n\n        content = re.sub(\n            version_pattern,\n            _get_replacer(info.backport, info.integrity),\n            content,\n            flags=re.DOTALL,\n        )\n\n    metadata_path.write_text(content)\n    bazel_mod_tidy()\n\n\ndef print_info(info: BundleInfo) -> None:\n    print(f\"Uploading version {info.version} backport {info.backport}\")\n    i = \" \" * 8\n    print(\"Update python_metadata.bzl with:\\n\")\n    print(i + f'\"backport\": \"{info.backport}\",')\n    print(i + f'\"integrity\": \"{info.integrity}\",')\n    print()\n\n\ndef make_bundles(update_released: bool) -> list[BundleInfo]:\n    result = []\n    for ver, info in bundle_version_info().items():\n        if ver.startswith(\"dev\"):\n            continue\n        if not update_released and info.get(\"released\", False):\n            continue\n        path = Path(get_pyodide_bin_path(ver)).resolve()\n        b = get_backport(ver)\n        info[\"backport\"] = b\n        integrity = b64digest(path)\n        result.append(BundleInfo(ver, b, integrity, path))\n    return result\n\n\ndef upload_bundles(bundles: list[BundleInfo]):\n    from boto3 import client\n\n    s3 = client(\n        \"s3\",\n        endpoint_url=f\"https://{environ['R2_ACCOUNT_ID']}.r2.cloudflarestorage.com\",\n        aws_access_key_id=environ[\"R2_ACCESS_KEY_ID\"],\n        aws_secret_access_key=environ[\"R2_SECRET_ACCESS_KEY\"],\n        region_name=\"auto\",\n    )\n\n    for bundle in bundles:\n        ver = bundle.version\n        path = Path(get_pyodide_bin_path(ver)).resolve()\n        b = get_backport(ver)\n        info = bundle_version_info()[ver]\n        info[\"backport\"] = b\n        key = bundle_key(**info)\n        s3.upload_file(str(path), \"pyodide-capnp-bin\", key)\n\n\ndef main():\n    parser = argparse.ArgumentParser(\n        description=\"Upload Pyodide bundles and update metadata\"\n    )\n    parser.add_argument(\n        \"--update-released\",\n        action=\"store_true\",\n        help=\"Update already released versions?\",\n    )\n    args = parser.parse_args()\n    bundles = make_bundles(args.update_released)\n    for bundle in bundles:\n        print_info(bundle)\n    update_python_metadata_bzl(bundles)\n    upload_bundles(bundles)\n    return 0\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  },
  {
    "path": "src/rust/AGENTS.md",
    "content": "# src/rust/\n\n## OVERVIEW\n\n11 Rust library crates + 1 binary crate linked into workerd via CXX FFI. No Cargo workspace — entirely Bazel-driven (`wd_rust_crate.bzl`). Clippy pedantic+nursery enabled; `allow-unwrap-in-tests`.\n\n## CRATES\n\n| Crate                | Purpose                                                                                                |\n| -------------------- | ------------------------------------------------------------------------------------------------------ |\n| `jsg/`               | Rust JSG bindings: `Lock`, `Rc<T>`, `Resource`, `Struct`, `Type`, `Realm`, `FeatureFlags`, module registration |\n| `jsg-macros/`        | Proc macros: `#[jsg_struct]`, `#[jsg_method]`, `#[jsg_resource]`, `#[jsg_oneof]`, `#[jsg_static_constant]` |\n| `jsg-test/`          | Test harness (`Harness`) for JSG Rust bindings                                                         |\n| `api/`               | Rust-implemented Node.js APIs; registers modules via `register_nodejs_modules()`                       |\n| `dns/`               | DNS record parsing (CAA, NAPTR) via CXX bridge; legacy duplicate of `api/dns.rs`, pending removal      |\n| `net/`               | Single function: `canonicalize_ip()`                                                                   |\n| `kj/`                | Rust bindings for KJ library (`http`, `io`, `own` submodules); `Result<T>` = `Result<T, cxx::KjError>` |\n| `cxx-integration/`   | Tokio runtime init; called from C++ `main()` before anything else                                      |\n| `transpiler/`        | TS type stripping via SWC (`ts_strip()`, `StripOnly` mode)                                             |\n| `python-parser/`     | Python import extraction via `ruff_python_parser`; **namespace: `edgeworker::rust::`**                 |\n| `gen-compile-cache/` | Binary crate — V8 bytecode cache generator; calls C++ `compile()` via CXX                              |\n\n## CONVENTIONS\n\n- **CXX bridge**: `#[cxx::bridge(namespace = \"workerd::rust::<crate>\")]` with companion `ffi.c++`/`ffi.h` files\n- **Namespace**: always `workerd::rust::*` except `python-parser` → `edgeworker::rust::python_parser`\n- **Errors**: `thiserror` for library crates; `jsg::Error` with `ExceptionType` for JSG-facing crates\n- **JSG resources**: `#[jsg_resource]` on struct + impl block; `#[jsg_method]` auto-converts `snake_case` → `camelCase`; methods with `&self`/`&mut self` become instance methods, methods without a receiver become static methods; `#[jsg_static_constant]` on `const` items exposes read-only numeric constants on both constructor and prototype (name kept as-is, no camelCase); resources integrate with GC via the `GarbageCollected` trait (auto-derived for `Rc<T>`, `WeakRc<T>`, `Option<Rc<T>>`, and `Nullable<Rc<T>>` fields)\n- **Formatting**: `rustfmt.toml` — `group_imports = \"StdExternalCrate\"`, `imports_granularity = \"Item\"` (one `use` per import)\n- **Linting**: `just clippy <crate>` — pedantic+nursery; `allow-unwrap-in-tests`\n- **Tests**: inline `#[cfg(test)]` modules; JSG tests use `jsg_test::Harness::run_in_context()`\n- **FFI pointers**: functions receiving raw pointers must be `unsafe fn` (see `jsg/README.md`)\n- **Parameter ordering**: `&Lock` / `&mut Lock` must always be the first parameter in any function that takes a lock (matching the C++ convention where `jsg::Lock&` is always first). This applies to free functions, trait methods, and associated functions (excluding `&self`/`&mut self` receivers which come before `lock`).\n- **Feature flags**: `Lock::feature_flags()` returns a capnp `compatibility_flags::Reader` for the current worker. Use `lock.feature_flags().get_node_js_compat()`. Flags are parsed once and stored in the `Realm` at construction; C++ passes canonical capnp bytes to `realm_create()`. Schema: `src/workerd/io/compatibility-date.capnp`, generated Rust bindings: `compatibility_date_capnp` crate.\n"
  },
  {
    "path": "src/rust/BUILD.bazel",
    "content": "exports_files([\n    \"rustfmt.toml\",\n    \"clippy.toml\",\n])\n"
  },
  {
    "path": "src/rust/api/BUILD.bazel",
    "content": "load(\"//:build/wd_rust_crate.bzl\", \"wd_rust_crate\")\n\nwd_rust_crate(\n    name = \"api\",\n    cxx_bridge_deps = [\"//src/rust/jsg\"],\n    cxx_bridge_srcs = [\"lib.rs\"],\n    proc_macro_deps = [\n        \"//src/rust/jsg-macros\",\n    ],\n    test_deps = [\"//src/rust/jsg-test\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/rust/jsg\",\n        \"@crates_vendor//:thiserror\",\n    ],\n)\n"
  },
  {
    "path": "src/rust/api/dns.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nuse jsg_macros::jsg_method;\nuse jsg_macros::jsg_resource;\nuse jsg_macros::jsg_struct;\nuse thiserror::Error;\n\n#[derive(Debug, Error)]\npub enum DnsParserError {\n    #[error(\"Invalid hex string: {0}\")]\n    InvalidHexString(String),\n    #[error(\"ParseInt error: {0}\")]\n    ParseIntError(#[from] std::num::ParseIntError),\n    #[error(\"Invalid DNS response: {0}\")]\n    InvalidDnsResponse(String),\n    #[error(\"unknown dns parser error\")]\n    Unknown,\n}\n\nimpl From<DnsParserError> for jsg::Error {\n    fn from(val: DnsParserError) -> Self {\n        match val {\n            DnsParserError::InvalidHexString(msg) | DnsParserError::InvalidDnsResponse(msg) => {\n                Self::new_error(&msg)\n            }\n            DnsParserError::ParseIntError(msg) => Self::new_range_error(msg.to_string()),\n            DnsParserError::Unknown => Self::new_error(\"Unknown dns parser error\"),\n        }\n    }\n}\n\n/// CAA record representation\n#[jsg_struct]\n#[derive(Debug)]\npub struct CaaRecord {\n    pub critical: u8,\n    pub field: String,\n    pub value: String,\n}\n\n/// NAPTR record representation\n#[jsg_struct]\n#[derive(Debug)]\npub struct NaptrRecord {\n    pub flags: String,\n    pub service: String,\n    pub regexp: String,\n    pub replacement: String,\n    pub order: u32,\n    pub preference: u32,\n}\n\n/// Given a vector of strings, converts each slice to UTF-8 from HEX.\n///\n/// # Errors\n/// `DnsParserError::InvalidHexString`\n/// `DnsParserError::ParseIntError`\npub fn decode_hex(input: &[&str]) -> Result<Vec<String>, DnsParserError> {\n    let mut v = Vec::with_capacity(input.len());\n\n    for slice in input {\n        let num = u16::from_str_radix(slice, 16)?;\n        let ch = String::from_utf16(&[num])\n            .map_err(|_| DnsParserError::InvalidHexString(\"Invalid UTF-16 sequence\".to_owned()))?;\n        v.push(ch);\n    }\n\n    Ok(v)\n}\n\n/// Replacement values needs to be parsed accordingly.\n///\n/// It has a similar characteristic to CAA and NAPTR records whereas\n/// first character contains the length of the input, and the second character\n/// is the starting index of the substring. We need to continue parsing until there\n/// are no input left, and later join them using \".\"\n///\n/// It is important that the returning value doesn't end with dot (\".\") character.\n///\n/// # Errors\n/// `DnsParserError::InvalidHexString`\n/// `DnsParserError::ParseIntError`\npub fn parse_replacement(input: &[&str]) -> jsg::Result<String, DnsParserError> {\n    if input.is_empty() {\n        return Ok(String::new());\n    }\n\n    let mut output: Vec<String> = vec![];\n    let mut length_index = 0;\n    let mut offset_index = 1;\n\n    // Iterate through each character to parse different frames.\n    // Each frame starts with the length of the remaining frame.\n    while length_index < input.len() {\n        let length = usize::from_str_radix(input[length_index], 16)?;\n        if length + offset_index > input.len() {\n            return Err(DnsParserError::InvalidDnsResponse(\n                \"replacement data too short for declared frame length\".to_owned(),\n            ));\n        }\n        let subset = input[offset_index..length + offset_index].to_vec();\n        let decoded = decode_hex(&subset)?.join(\"\");\n\n        // We omit the trailing \".\" from replacements.\n        // Cloudflare DNS returns \"_sip._udp.sip2sip.info.\" whereas Node.js removes trailing dot\n        if !decoded.is_empty() {\n            output.push(decoded);\n        }\n\n        length_index += subset.len() + 1;\n        offset_index = length_index + 1;\n    }\n\n    Ok(output.join(\".\"))\n}\n\n#[jsg_resource]\npub struct DnsUtil;\n\n#[jsg_resource]\nimpl DnsUtil {\n    pub fn new() -> jsg::Rc<Self> {\n        jsg::Rc::new(Self {})\n    }\n\n    /// Parses an unknown RR format returned from Cloudflare DNS.\n    /// Specification is available at\n    /// `<https://datatracker.ietf.org/doc/html/rfc3597>`\n    ///\n    /// The format of the record is as follows:\n    ///   \\# <length-in-bytes> <bytes-in-hex>\n    ///   \\\\# 15 00 05 69 73 73 75 65 70 6b 69 2e 67 6f 6f 67\n    ///       |  |  |  |\n    ///       |  |  |  - Starting point of the actual data\n    ///       |  |  - Length of the field.\n    ///       |  - Number representation of \"`is_critical`\"\n    ///       - Length of the data\n    ///\n    /// Note: Field can be \"issuewild\", \"issue\" or \"iodef\".\n    ///\n    /// ```\n    /// let record = parse_caa_record(\"\\\\# 15 00 05 69 73 73 75 65 70 6b 69 2e 67 6f 6f 67\");\n    /// assert_eq!(record.critical, false);\n    /// assert_eq!(record.field, \"issue\")\n    /// assert_eq!(record.value, \"pki.goog\")\n    /// ```\n    /// # Errors\n    /// `DnsParserError::InvalidHexString`\n    /// `DnsParserError::ParseIntError`\n    #[jsg_method]\n    pub fn parse_caa_record(&self, record: String) -> Result<CaaRecord, DnsParserError> {\n        // Let's remove \"\\\\#\" and the length of data from the beginning of the record\n        let parts: Vec<_> = record.split_ascii_whitespace().collect();\n        if parts.len() < 3 {\n            return Err(DnsParserError::InvalidDnsResponse(\n                \"CAA record too short: expected at least 3 fields\".to_owned(),\n            ));\n        }\n        let data = parts[2..].to_vec();\n        if data.len() < 2 {\n            return Err(DnsParserError::InvalidDnsResponse(\n                \"CAA record data too short: expected critical and prefix length fields\".to_owned(),\n            ));\n        }\n        let critical = data[0].parse::<u8>()?;\n        let prefix_length = data[1].parse::<usize>()?;\n\n        if data.len() < 2 + prefix_length {\n            return Err(DnsParserError::InvalidDnsResponse(format!(\n                \"CAA record data too short for prefix_length {prefix_length}\"\n            )));\n        }\n        let field = decode_hex(&data[2..prefix_length + 2])?.join(\"\");\n        let value = decode_hex(&data[(prefix_length + 2)..])?.join(\"\");\n\n        // Field can be \"issuewild\", \"issue\" or \"iodef\"\n        if field != \"issuewild\" && field != \"issue\" && field != \"iodef\" {\n            return Err(DnsParserError::InvalidDnsResponse(format!(\n                \"Received unknown field '{field}'\"\n            )));\n        }\n\n        Ok(CaaRecord {\n            critical,\n            field,\n            value,\n        })\n    }\n\n    /// Parses an unknown RR format returned from Cloudflare DNS.\n    /// Specification is available at\n    /// `<https://datatracker.ietf.org/doc/html/rfc3597>`\n    ///\n    /// The format of the record is as follows:\n    /// \\# 37 15 b3 08 ae 01 73 0a 6d 79 2d 73 65 72 76 69 63 65 06 72 65 67 65 78 70 0b 72 65 70 6c 61 63 65 6d 65 6e 74 00\n    ///       |--|  |--|  |  |  |  |--------------------------|  |  |--------------|  |  |--------------------------------|\n    ///       |     |     |  |  |  |                             |  |                 |  - Replacement\n    ///       |     |     |  |  |  |                             |  |                 - Length of first part of the replacement\n    ///       |     |     |  |  |  |                             |  - Regexp\n    ///       |     |     |  |  |  |                             - Regexp length\n    ///       |     |     |  |  |  - Service\n    ///       |     |     |  |  - Length of service\n    ///       |     |     |  - Flag\n    ///       |     |     - Length of flags\n    ///       |     - Preference\n    ///       - Order\n    ///\n    /// ```\n    /// let record = parse_naptr_record(\"\\\\# 37 15 b3 08 ae 01 73 0a 6d 79 2d 73 65 72 76 69 63 65 06 72 65 67 65 78 70 0b 72 65 70 6c 61 63 65 6d 65 6e 74 00\");\n    /// assert_eq!(record.flags, \"s\");\n    /// assert_eq!(record.service, \"my-service\");\n    /// assert_eq!(record.regexp, \"regexp\");\n    /// assert_eq!(record.replacement, \"replacement\");\n    /// assert_eq!(record.order, 5555);\n    /// assert_eq!(record.preference, 2222);\n    /// ```\n    ///\n    /// # Errors\n    /// `DnsParserError::InvalidHexString`\n    /// `DnsParserError::ParseIntError`\n    #[jsg_method]\n    pub fn parse_naptr_record(&self, record: String) -> jsg::Result<NaptrRecord, DnsParserError> {\n        let parts: Vec<_> = record.split_ascii_whitespace().collect();\n        if parts.len() < 2 {\n            return Err(DnsParserError::InvalidDnsResponse(\n                \"NAPTR record too short\".to_owned(),\n            ));\n        }\n        let data = parts[1..].to_vec();\n\n        // Need at least: length(1) + order(2) + preference(2) + flag_length(1) = 6 fields\n        if data.len() < 6 {\n            return Err(DnsParserError::InvalidDnsResponse(\n                \"NAPTR record data too short: expected at least 6 fields\".to_owned(),\n            ));\n        }\n\n        let order_str = data[1..3].to_vec();\n        let order = u32::from_str_radix(&order_str.join(\"\"), 16)?;\n        let preference_str = data[3..5].to_vec();\n        let preference = u32::from_str_radix(&preference_str.join(\"\"), 16)?;\n\n        let flag_length = usize::from_str_radix(data[5], 16)?;\n        let flag_offset = 6;\n        if data.len() < flag_offset + flag_length + 1 {\n            return Err(DnsParserError::InvalidDnsResponse(\n                \"NAPTR record too short for flags field\".to_owned(),\n            ));\n        }\n        let flags = decode_hex(&data[flag_offset..flag_length + flag_offset])?.join(\"\");\n\n        let service_length = usize::from_str_radix(data[flag_offset + flag_length], 16)?;\n        let service_offset = flag_offset + flag_length + 1;\n        if data.len() < service_offset + service_length + 1 {\n            return Err(DnsParserError::InvalidDnsResponse(\n                \"NAPTR record too short for service field\".to_owned(),\n            ));\n        }\n        let service = decode_hex(&data[service_offset..service_length + service_offset])?.join(\"\");\n\n        let regexp_length = usize::from_str_radix(data[service_offset + service_length], 16)?;\n        let regexp_offset = service_offset + service_length + 1;\n        if data.len() < regexp_offset + regexp_length {\n            return Err(DnsParserError::InvalidDnsResponse(\n                \"NAPTR record too short for regexp field\".to_owned(),\n            ));\n        }\n        let regexp = decode_hex(&data[regexp_offset..regexp_length + regexp_offset])?.join(\"\");\n\n        let replacement = parse_replacement(&data[regexp_offset + regexp_length..])?;\n\n        Ok(NaptrRecord {\n            flags,\n            service,\n            regexp,\n            replacement,\n            order,\n            preference,\n        })\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_decode() {\n        let input = vec![\"69\", \"73\", \"73\", \"75\", \"65\"];\n        assert_eq!(decode_hex(&input).unwrap().join(\"\"), \"issue\");\n\n        let empty_input: Vec<&str> = vec![];\n        assert!(decode_hex(&empty_input).unwrap().is_empty());\n    }\n\n    #[test]\n    fn test_decode_hex_invalid() {\n        let input = vec![\"ZZ\"];\n        let result = decode_hex(&input);\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_parse_replacement_empty() {\n        let input: Vec<&str> = vec![];\n        assert_eq!(parse_replacement(&input).unwrap(), \"\");\n\n        let multiple_parts_input = vec![\"03\", \"73\", \"69\", \"70\", \"04\", \"74\", \"65\", \"73\", \"74\", \"00\"];\n        assert_eq!(\n            parse_replacement(&multiple_parts_input).unwrap(),\n            \"sip.test\"\n        );\n    }\n\n    #[test]\n    fn test_parse_caa_record_issue() {\n        let dns_util = DnsUtil {};\n        let record = dns_util\n            .parse_caa_record(\"\\\\# 15 00 05 69 73 73 75 65 70 6b 69 2e 67 6f 6f 67\".to_owned())\n            .unwrap();\n\n        assert_eq!(record.critical, 0);\n        assert_eq!(record.field, \"issue\");\n        assert_eq!(record.value, \"pki.goog\");\n    }\n\n    #[test]\n    fn test_parse_caa_record_issuewild() {\n        let dns_util = DnsUtil {};\n        let record = dns_util\n            .parse_caa_record(\n                \"\\\\# 21 00 09 69 73 73 75 65 77 69 6c 64 6c 65 74 73 65 6e 63 72 79 70 74\"\n                    .to_owned(),\n            )\n            .unwrap();\n\n        assert_eq!(record.critical, 0);\n        assert_eq!(record.field, \"issuewild\");\n        assert_eq!(record.value, \"letsencrypt\");\n    }\n\n    #[test]\n    fn test_parse_caa_record_invalid_field() {\n        let dns_util = DnsUtil {};\n        let result = dns_util.parse_caa_record(\n            \"\\\\# 15 00 05 69 6e 76 61 6c 69 64 70 6b 69 2e 67 6f 6f 67\".to_owned(),\n        );\n\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_parse_naptr_record() {\n        let dns_util = DnsUtil {};\n        let record = dns_util\n            .parse_naptr_record(\"\\\\# 37 15 b3 08 ae 01 73 0a 6d 79 2d 73 65 72 76 69 63 65 06 72 65 67 65 78 70 0b 72 65 70 6c 61 63 65 6d 65 6e 74 00\".to_owned())\n            .unwrap();\n\n        assert_eq!(record.flags, \"s\");\n        assert_eq!(record.service, \"my-service\");\n        assert_eq!(record.regexp, \"regexp\");\n        assert_eq!(record.replacement, \"replacement\");\n        assert_eq!(record.order, 5555);\n        assert_eq!(record.preference, 2222);\n    }\n\n    // =========================================================================\n    // Malformed input tests — these previously caused panics (index out of bounds)\n    // which would abort the process via CXX. They must return Err, not panic.\n    // =========================================================================\n\n    #[test]\n    fn test_parse_caa_record_empty_string() {\n        let dns_util = DnsUtil {};\n        assert!(dns_util.parse_caa_record(String::new()).is_err());\n    }\n\n    #[test]\n    fn test_parse_caa_record_single_token() {\n        let dns_util = DnsUtil {};\n        assert!(dns_util.parse_caa_record(\"\\\\#\".to_owned()).is_err());\n    }\n\n    #[test]\n    fn test_parse_caa_record_two_tokens() {\n        let dns_util = DnsUtil {};\n        assert!(dns_util.parse_caa_record(\"\\\\# 15\".to_owned()).is_err());\n    }\n\n    #[test]\n    fn test_parse_caa_record_data_too_short_for_prefix() {\n        let dns_util = DnsUtil {};\n        // critical=00, prefix_length=FF (255) but no data follows\n        assert!(\n            dns_util\n                .parse_caa_record(\"\\\\# 02 00 FF\".to_owned())\n                .is_err()\n        );\n    }\n\n    #[test]\n    fn test_parse_naptr_record_empty_string() {\n        let dns_util = DnsUtil {};\n        assert!(dns_util.parse_naptr_record(String::new()).is_err());\n    }\n\n    #[test]\n    fn test_parse_naptr_record_single_token() {\n        let dns_util = DnsUtil {};\n        assert!(dns_util.parse_naptr_record(\"\\\\#\".to_owned()).is_err());\n    }\n\n    #[test]\n    fn test_parse_naptr_record_too_few_fields() {\n        let dns_util = DnsUtil {};\n        assert!(\n            dns_util\n                .parse_naptr_record(\"\\\\# 37 15 b3\".to_owned())\n                .is_err()\n        );\n    }\n\n    #[test]\n    fn test_parse_replacement_length_exceeds_input() {\n        // First element says frame is FF (255) bytes but only 2 bytes follow\n        let input = vec![\"FF\", \"73\", \"69\"];\n        assert!(parse_replacement(&input).is_err());\n    }\n\n    #[test]\n    fn test_parse_naptr_record_truncated_at_flags() {\n        let dns_util = DnsUtil {};\n        // Has order+preference+flag_length but no flag data\n        assert!(\n            dns_util\n                .parse_naptr_record(\"\\\\# 06 15 b3 08 ae 05\".to_owned())\n                .is_err()\n        );\n    }\n}\n"
  },
  {
    "path": "src/rust/api/lib.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nuse std::pin::Pin;\n\nuse jsg::ToJS;\n\nuse crate::dns::DnsUtil;\n\npub mod dns;\n\n#[cxx::bridge(namespace = \"workerd::rust::api\")]\nmod ffi {\n    #[namespace = \"workerd::rust::jsg\"]\n    unsafe extern \"C++\" {\n        include!(\"workerd/rust/jsg/ffi.h\");\n\n        type ModuleRegistry = jsg::v8::ffi::ModuleRegistry;\n    }\n    extern \"Rust\" {\n        pub fn register_nodejs_modules(registry: Pin<&mut ModuleRegistry>);\n    }\n}\n\npub fn register_nodejs_modules(registry: Pin<&mut ffi::ModuleRegistry>) {\n    jsg::modules::add_builtin(\n        registry,\n        \"node-internal:dns\",\n        // SAFETY: isolate is valid and locked — called from C++ module registration.\n        |isolate| unsafe {\n            let mut lock = jsg::Lock::from_isolate_ptr(isolate);\n            let dns_util = DnsUtil::new();\n            dns_util.to_js(&mut lock).into_ffi()\n        },\n        jsg::modules::ModuleType::Internal,\n    );\n}\n\n#[cfg(test)]\nmod tests {\n    use jsg_test::Harness;\n\n    use super::*;\n\n    #[test]\n    fn test_wrap_resource_equality() {\n        let harness = Harness::new();\n        harness.run_in_context(|lock, _ctx| {\n            let dns_util = DnsUtil::new();\n\n            let lhs = dns_util.clone().to_js(lock);\n            let rhs = dns_util.to_js(lock);\n\n            assert_eq!(lhs, rhs);\n            Ok(())\n        });\n    }\n}\n"
  },
  {
    "path": "src/rust/clippy.toml",
    "content": "allow-unwrap-in-tests = true\n"
  },
  {
    "path": "src/rust/cxx-integration/BUILD.bazel",
    "content": "load(\"//:build/wd_rust_crate.bzl\", \"wd_rust_crate\")\n\nwd_rust_crate(\n    name = \"cxx-integration\",\n    cxx_bridge_deps = [\n        \"@capnp-cpp//src/kj\",\n    ],\n    cxx_bridge_src = \"lib.rs\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@crates_vendor//:tokio\",\n        \"@crates_vendor//:tracing\",\n    ],\n)\n"
  },
  {
    "path": "src/rust/cxx-integration/lib.rs",
    "content": "pub mod tokio;\n\n#[cxx::bridge(namespace = \"workerd::rust::cxx_integration\")]\nmod ffi {\n    extern \"Rust\" {\n        fn init();\n\n        fn trigger_panic(msg: &str);\n    }\n}\n\npub fn init() {\n    init_tokio(None);\n}\n\n/// Initialize tokio runtime.\n/// Should not be called directly but as a part of a downstream cxx-integration init.\npub fn init_tokio(worker_threads: Option<usize>) {\n    tokio::init(worker_threads);\n}\n\nfn trigger_panic(msg: &str) {\n    panic!(\"{}\", msg)\n}\n"
  },
  {
    "path": "src/rust/cxx-integration/tokio.rs",
    "content": "use std::future::Future;\nuse std::sync::OnceLock;\n\nuse tokio::task::JoinHandle;\nuse tracing::Instrument;\nuse tracing::info;\n\nstatic TOKIO_RUNTIME: OnceLock<tokio::runtime::Runtime> = OnceLock::new();\n\n/// Initialize tokio runtime.\n/// Must be called after all forking and sandbox setup is finished.\npub(crate) fn init(worker_threads: Option<usize>) {\n    assert!(TOKIO_RUNTIME.get().is_none());\n\n    let mut builder = tokio::runtime::Builder::new_multi_thread();\n\n    if let Some(worker_threads) = worker_threads {\n        builder.worker_threads(worker_threads);\n    }\n\n    let runtime = builder\n        .enable_time()\n        .enable_io()\n        .build()\n        .expect(\"failed to build tokio runtime\");\n\n    TOKIO_RUNTIME\n        .set(runtime)\n        .expect(\"failed to set tokio runtime\");\n    spawn(async {\n        info!(nosentry = true, \"tokio runtime is online\");\n    });\n}\n\n/// Obtain a handle to the shared tokio runtime.\n/// Requires calling [`init_tokio`] first.\n/// # Panics\n/// if tokio runtime is not available yet.\npub fn runtime_handle() -> tokio::runtime::Handle {\n    TOKIO_RUNTIME\n        .get()\n        .expect(\"tokio runtime is not initialized\")\n        .handle()\n        .clone()\n}\n\n/// This is helper to set the spawn and stuff duplicating the signature of\n/// `https://docs.rs/tokio/latest/tokio/task/fn.spawn.html`.\npub fn spawn<F>(future: F) -> JoinHandle<F::Output>\nwhere\n    F: Future + Send + 'static,\n    F::Output: Send + 'static,\n{\n    let handle = runtime_handle();\n    let _guard = handle.enter();\n\n    tokio::spawn(future.in_current_span())\n}\n\n/// Exposes `Runtime::block_on`\n/// `https://docs.rs/tokio/latest/tokio/runtime/struct.Runtime.html#method.block_on`.\npub fn block_on<F: Future>(f: F) -> F::Output {\n    runtime_handle().block_on(f)\n}\n\n#[cfg(test)]\nmod test {\n    use std::time::Duration;\n\n    use super::*;\n\n    #[test]\n    fn test_tokio_init() {\n        init(None);\n        let join = spawn(async move {\n            tokio::time::sleep(Duration::from_millis(1)).await;\n            42\n        });\n        let result = runtime_handle().block_on(join).unwrap();\n        assert_eq!(42, result);\n    }\n}\n"
  },
  {
    "path": "src/rust/cxx-integration-test/BUILD.bazel",
    "content": "load(\"//:build/kj_test.bzl\", \"kj_test\")\nload(\"//:build/wd_rust_crate.bzl\", \"wd_rust_crate\")\n\nwd_rust_crate(\n    name = \"cxx-integration-test\",\n    cxx_bridge_deps = [\n        \"@capnp-cpp//src/kj\",\n    ],\n    cxx_bridge_src = \"lib.rs\",\n    test_deps = [\n        \"@crates_vendor//:nix\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/rust/cxx-integration\",\n        \"@crates_vendor//:tokio\",\n        \"@crates_vendor//:tracing\",\n    ],\n)\n\nkj_test(\n    src = \"cxx-rust-integration-test.c++\",\n    deps = [\n        \":cxx-integration-test\",\n        \"//src/rust/cxx-integration\",\n        \"@capnp-cpp//src/kj:kj-async\",\n        \"@workerd-cxx//kj-rs\",\n    ],\n)\n"
  },
  {
    "path": "src/rust/cxx-integration-test/cxx-rust-integration-test.c++",
    "content": "#include <workerd/rust/cxx-integration-test/lib.rs.h>\n#include <workerd/rust/cxx-integration/lib.rs.h>\n\n#include <kj-rs/kj-rs.h>\n#include <rust/cxx.h>\n#include <signal.h>\n\n#include <kj/async.h>\n#include <kj/test.h>\n\nusing namespace kj_rs;\n\n// Test generic rust/c++ integration boundary.\n// See src/rust/cxx-integration-tests for rust backend.\n\nnamespace workerd::rust {\n\nKJ_TEST(\"init cxx_integration\") {\n  // this tests initializes cxx integration for the rest of the tests\n  rust::cxx_integration::init();\n}\n\nKJ_TEST(\"panic results in abort\") {\n  KJ_EXPECT_SIGNAL(SIGABRT, rust::cxx_integration::trigger_panic(\"foobar\"));\n}\n\nKJ_TEST(\"ok Result\") {\n  KJ_EXPECT(42 == rust::test::result_ok());\n}\n\nKJ_TEST(\"err Result\") {\n  // if fn returns an error, it is translated into kj::Exception exception.\n  try {\n    rust::test::result_error();\n    KJ_FAIL_REQUIRE(\"exception is expected\");\n  } catch (kj::Exception& e) {\n    // this is expected\n    KJ_EXPECT(e.getDescription() == \"test error\"_kj);\n  }\n}\n\nKJ_TEST(\"err Result with getCaughtExceptionAsKj\") {\n  // if fn returns an error, it is translated into kj::Exception exception that can be accessed\n  // using getCaughtExceptionAsKj as is very common in the source code.\n  try {\n    rust::test::result_error();\n    KJ_FAIL_REQUIRE(\"exception is expected\");\n  } catch (...) {\n    // this is expected\n    KJ_EXPECT(kj::getCaughtExceptionAsKj().getDescription() == \"test error\"_kj);\n  }\n}\n\nKJ_TEST(\"test callback\") {\n  rust::test::TestCallback callback = [](size_t a, size_t b) { return a + b; };\n  auto result = rust::test::call_callback(callback, 40, 2);\n  KJ_EXPECT(result == 42);\n}\n\nKJ_TEST(\"test crashing callback\") {\n  rust::test::TestCallback callback = [](size_t a, size_t b) -> size_t {\n    KJ_FAIL_REQUIRE(\"expected to crash\");\n  };\n  // std::terminate is called when c++ throws fatal except\n  KJ_EXPECT_SIGNAL(SIGABRT, rust::test::call_callback(callback, 40, 2));\n}\n\nKJ_TEST(\"test recoverable exception callback\") {\n  rust::test::TestCallback callback = [](size_t a, size_t b) -> size_t {\n    kj::throwRecoverableException(KJ_EXCEPTION(DISCONNECTED, \"Premature EOF.\"));\n    KJ_UNREACHABLE;\n  };\n  // std::terminate is called when c++ throws unhandled recoverable exception\n  KJ_EXPECT_SIGNAL(SIGABRT, rust::test::call_callback(callback, 40, 2));\n}\n\nKJ_TEST(\"shared structure\") {\n  {\n    // rust structure arguments are passed by-value in c++\n    const auto s = rust::test::SharedStruct{.a = 20, .b = 22};\n    KJ_EXPECT(42 == rust::test::pass_shared_struct(s));\n  }\n\n  {\n    // rust structure return values are return by-value in c++\n    const auto s = rust::test::return_shared_struct();\n    KJ_EXPECT(13 == s.a);\n    KJ_EXPECT(29 == s.b);\n  }\n\n  {\n    // rust reference looks like const reference to c++;\n    const auto s = rust::test::SharedStruct{.a = 20, .b = 22};\n    KJ_EXPECT(42 == rust::test::pass_shared_struct_as_ref(s));\n  }\n\n  {\n    // rust mutable reference looks like reference to c++\n    auto s = rust::test::SharedStruct{.a = 10, .b = 32};\n    rust::test::pass_shared_struct_as_mut_ref(s);\n    KJ_EXPECT(s.a == 42);\n    KJ_EXPECT(s.b == 0);\n  }\n\n  {\n    // rust const pointer looks like const pointer to c++\n    const auto s = rust::test::SharedStruct{.a = 20, .b = 22};\n    KJ_EXPECT(42 == rust::test::pass_shared_struct_as_const_ptr(&s));\n  }\n\n  {\n    // rust mut pointer looks like pointer to c++\n    auto s = rust::test::SharedStruct{.a = 10, .b = 32};\n    rust::test::pass_shared_struct_as_mut_ptr(&s);\n    KJ_EXPECT(s.a == 0);\n    KJ_EXPECT(s.b == 0);\n  }\n\n  {\n    // rust Box<T> type is represented as special ::rust::Box<T> c++ type\n    // there are many ways to create a Box\n\n    {\n      // box can be created by copying the value\n      auto box = ::rust::Box<rust::test::SharedStruct>(rust::test::SharedStruct{.a = 3, .b = 39});\n      // box is consumed by the call as expected\n      KJ_EXPECT(42 == rust::test::pass_shared_struct_as_box(kj::mv(box)));\n    }\n\n    {\n      // box can be created by moving the value\n      auto s = rust::test::SharedStruct{.a = 3, .b = 39};\n      auto box = ::rust::Box<rust::test::SharedStruct>(kj::mv(s));\n      KJ_EXPECT(42 == rust::test::pass_shared_struct_as_box(kj::mv(box)));\n    }\n\n    {\n      // box can be created from raw pointer.\n      // Memory needs to be allocated using malloc, since rust doesn't have\n      // access to delete.\n      auto mem = malloc(sizeof(rust::test::SharedStruct));\n      auto s = new (mem) rust::test::SharedStruct;\n      s->a = 4;\n      s->b = 38;\n      auto box = ::rust::Box<rust::test::SharedStruct>::from_raw(s);\n      KJ_EXPECT(42 == rust::test::pass_shared_struct_as_box(kj::mv(box)));\n    }\n  }\n\n  {\n    // box can be returned from rust to c++ as well\n    auto box = rust::test::return_shared_struct_as_box();\n    KJ_EXPECT(1 == box->a);\n    KJ_EXPECT(41 == box->b);\n  }\n}\n\nKJ_TEST(\"opaque rust type\") {\n  // &str is represented as a special ::rust::Str type\n  // it supports variety of implicit constructors.\n  auto s = rust::test::rust_struct_new_box(\"test_name\");\n  ::rust::Str name = s->get_name();\n\n  // ::rust::Str is _not_ null-terminated so kj::StringPtr can't be created from\n  // it. need to allocate to create c++-string (or use it as ArrayPtr).\n  auto strName = std::string(name);\n  KJ_EXPECT(\"test_name\"_kj == kj::StringPtr(strName.c_str()));\n\n  s->set_name(\"another_name\");\n  KJ_EXPECT(\"another_name\"_kjc == kj::arrayPtr(s->get_name().data(), s->get_name().size()));\n}\n\nKJ_TEST(\"rust::String test\") {\n  auto s = rust::test::get_string();\n  auto expected = \"rust_string\"_kj;\n  KJ_EXPECT(expected == kj::str(s));\n  KJ_EXPECT(expected == kj::toCharSequence(s));\n  KJ_EXPECT(kj::hashCode(expected) == kj::hashCode(s));\n}\n\nKJ_TEST(\"rust::str test\") {\n  auto s = rust::test::get_str();\n  auto expected = \"rust_str\"_kj;\n  KJ_EXPECT(expected == kj::str(s));\n  KJ_EXPECT(expected == kj::toCharSequence(s));\n  KJ_EXPECT(kj::hashCode(expected) == kj::hashCode(s));\n}\n\nKJ_TEST(\"test async immediate future\") {\n  kj::EventLoop loop;\n  kj::WaitScope waitScope(loop);\n\n  auto pair = kj::newPromiseAndCrossThreadFulfiller<size_t>();\n\n  rust::test::UsizeCallback callback = [&](size_t a) { pair.fulfiller->fulfill(kj::mv(a)); };\n  rust::test::async_immediate(callback);\n\n  auto result = pair.promise.wait(waitScope);\n  KJ_EXPECT(result == 42);\n}\n\nKJ_TEST(\"test async delay\") {\n  kj::EventLoop loop;\n  kj::WaitScope waitScope(loop);\n\n  auto pair = kj::newPromiseAndCrossThreadFulfiller<size_t>();\n\n  rust::test::UsizeCallback callback = [&](size_t a) { pair.fulfiller->fulfill(kj::mv(a)); };\n  rust::test::async_sleep(callback);\n\n  auto result = pair.promise.wait(waitScope);\n  KJ_EXPECT(result == 42);\n}\n\nKJ_TEST(\"array/slice convertions\") {\n  // const arrayPtr -> const slice\n  {\n    kj::ArrayPtr<const kj::byte> a = \"foo\"_kjb;\n    ::rust::Slice<const kj::byte> s = a.as<Rust>();\n    KJ_EXPECT(s.length() == a.size());\n  }\n\n  // mutable arrayPtr -> const slice\n  {\n    kj::Array<kj::byte> a = kj::heapArray<kj::byte>(20);\n    kj::ArrayPtr<kj::byte> p = a.asPtr();\n    ::rust::Slice<const kj::byte> s = p.as<Rust>();\n    KJ_EXPECT(s.length() == a.size());\n  }\n\n  // mutable arrayPtr -> mutable slice\n  {\n    kj::Array<kj::byte> a = kj::heapArray<kj::byte>(20);\n    ::rust::Slice<kj::byte> s = a.asPtr().as<RustMutable>();\n    KJ_EXPECT(s.length() == a.size());\n  }\n\n  // const array -> const slice\n  {\n    kj::Array<const kj::byte> a = kj::heapArray<kj::byte>(20);\n    ::rust::Slice<const kj::byte> s = a.as<Rust>();\n    KJ_EXPECT(s.length() == a.size());\n  }\n\n  // mutable array -> const slice\n  {\n    kj::Array<kj::byte> a = kj::heapArray<kj::byte>(20);\n    ::rust::Slice<const kj::byte> s = a.as<Rust>();\n    KJ_EXPECT(s.length() == a.size());\n  }\n\n  // mutable array -> mutable slice\n  {\n    kj::Array<kj::byte> a = kj::heapArray<kj::byte>(20);\n    ::rust::Slice<kj::byte> s = a.as<RustMutable>();\n    KJ_EXPECT(s.length() == a.size());\n  }\n}\n\n}  // namespace workerd::rust\n"
  },
  {
    "path": "src/rust/cxx-integration-test/cxx-rust-integration-test.h",
    "content": "#pragma once\n\n#include <kj/function.h>\n\n#define operatorCALL operator()\n\nnamespace workerd::rust::test {\n\nusing TestCallback = kj::Function<size_t(size_t, size_t)>;\n\nusing UsizeCallback = kj::Function<void(size_t)>;\n\n}  // namespace workerd::rust::test\n"
  },
  {
    "path": "src/rust/cxx-integration-test/lib.rs",
    "content": "//! Non-production crate to help test various aspects of rust/c++ integration.\n\nuse std::io::Error;\nuse std::pin::Pin;\nuse std::time::Duration;\n\nuse tracing::debug;\nuse tracing::error;\nuse tracing::info;\nuse tracing::trace;\nuse tracing::warn;\n\ntype Result<T> = std::io::Result<T>;\n\n#[cxx::bridge(namespace = \"workerd::rust::test\")]\nmod ffi {\n    unsafe extern \"C++\" {\n        // To use a C++ callback first define it as an opaque to Rust type.\n        type TestCallback;\n\n        // Then define a call function with a correct signature.\n        // cxx can't call operator() but can call an ordinary function.\n        // Use C++ preprocessor to alias operatorCALL to operator().\n        #[cxx_name = \"operatorCALL\"]\n        fn call(self: Pin<&mut TestCallback>, a: usize, b: usize) -> usize;\n\n        // Include the header with the actual callback type definition.\n        // This will be included into cxx generated files.\n        include!(\"workerd/rust/cxx-integration-test/cxx-rust-integration-test.h\");\n    }\n\n    // Structures defined without any extern specifier are visible both to Rust and c++.\n    struct SharedStruct {\n        a: i32,\n        b: i32,\n    }\n\n    extern \"Rust\" {\n        fn result_ok() -> Result<i32>;\n        fn result_error() -> Result<i32>;\n\n        fn log_every_level();\n\n        fn call_callback(callback: Pin<&mut TestCallback>, a: usize, b: usize) -> usize;\n    }\n\n    extern \"Rust\" {\n        // Shared structures can be passed with full ownership\n        fn pass_shared_struct(s: SharedStruct) -> i32;\n        fn return_shared_struct() -> SharedStruct;\n\n        // References can be safely passed from c++\n        fn pass_shared_struct_as_ref(s: &SharedStruct) -> i32;\n        fn pass_shared_struct_as_mut_ref(s: &mut SharedStruct);\n\n        // Structs can be passed as pointers, but functions need to be unsafe then\n        unsafe fn pass_shared_struct_as_const_ptr(s: *const SharedStruct) -> i32;\n        unsafe fn pass_shared_struct_as_mut_ptr(s: *mut SharedStruct);\n\n        // Box<T> is supported\n        fn pass_shared_struct_as_box(s: Box<SharedStruct>) -> i32;\n        fn return_shared_struct_as_box() -> Box<SharedStruct>;\n    }\n\n    extern \"Rust\" {\n        // rust-defined structures can be exposed to c++ as opaque type.\n        type RustStruct;\n\n        // rust needs to provide a way to access instances of the type.\n        fn rust_struct_new_box(name: &str) -> Box<RustStruct>;\n\n        // c++ can interact with opaque structures using conventional functions and methods\n        fn get_name(self: &RustStruct) -> &str;\n\n        // if there's only one type defined in extern block, then you can use self shorthand\n        fn set_name(&mut self, name: &str);\n    }\n\n    extern \"Rust\" {\n        fn get_string() -> String;\n        fn get_str() -> &'static str;\n    }\n\n    // test async\n    unsafe extern \"C++\" {\n        type UsizeCallback;\n        #[cxx_name = \"operatorCALL\"]\n        fn call(self: Pin<&mut UsizeCallback>, x: usize);\n    }\n    extern \"Rust\" {\n        fn async_immediate(callback: Pin<&'static mut UsizeCallback>);\n        fn async_sleep(callback: Pin<&'static mut UsizeCallback>);\n    }\n}\n\n#[expect(clippy::unnecessary_wraps)]\nfn result_ok() -> Result<i32> {\n    Ok(42)\n}\n\nfn result_error() -> Result<i32> {\n    Err(Error::other(\"test error\"))\n}\n\nfn log_every_level() {\n    trace!(\"rust_trace_message\");\n    debug!(\"rust_debug_message\");\n    info!(\"rust_info_message\");\n    warn!(\"rust_warn_message\");\n    error!(\"rust_error_message\");\n}\n\nfn call_callback(callback: Pin<&mut ffi::TestCallback>, a: usize, b: usize) -> usize {\n    callback.call(a, b)\n}\n\n#[expect(clippy::needless_pass_by_value)]\nfn pass_shared_struct(s: ffi::SharedStruct) -> i32 {\n    s.a + s.b\n}\n\nfn return_shared_struct() -> ffi::SharedStruct {\n    ffi::SharedStruct { a: 13, b: 29 }\n}\n\nfn pass_shared_struct_as_ref(s: &ffi::SharedStruct) -> i32 {\n    s.a + s.b\n}\n\nfn pass_shared_struct_as_mut_ref(s: &mut ffi::SharedStruct) {\n    s.a += s.b;\n    s.b = 0;\n}\n\nunsafe fn pass_shared_struct_as_const_ptr(s: *const ffi::SharedStruct) -> i32 {\n    assert!(!s.is_null());\n    // SAFETY: Null check above ensures s is valid; the SharedStruct is valid for reads.\n    unsafe { (*s).a + (*s).b }\n}\n\nunsafe fn pass_shared_struct_as_mut_ptr(s: *mut ffi::SharedStruct) {\n    // SAFETY: Caller guarantees s is a valid, non-null mutable pointer.\n    unsafe { (*s).a = 0 };\n    // SAFETY: Caller guarantees s is a valid, non-null mutable pointer.\n    unsafe { (*s).b = 0 };\n}\n\n#[expect(clippy::boxed_local)] // clippy is right, but we want to test it anyway\n#[expect(clippy::needless_pass_by_value)]\nfn pass_shared_struct_as_box(s: Box<ffi::SharedStruct>) -> i32 {\n    s.a + s.b\n}\n\nfn return_shared_struct_as_box() -> Box<ffi::SharedStruct> {\n    Box::new(ffi::SharedStruct { a: 1, b: 41 })\n}\n\nstruct RustStruct {\n    name: String,\n}\n\nfn rust_struct_new_box(name: &str) -> Box<RustStruct> {\n    Box::new(RustStruct {\n        name: name.to_owned(),\n    })\n}\n\nimpl RustStruct {\n    fn get_name(&self) -> &str {\n        &self.name\n    }\n\n    fn set_name(&mut self, name: &str) {\n        name.clone_into(&mut self.name);\n    }\n}\n\nfn get_string() -> String {\n    \"rust_string\".to_owned()\n}\n\nfn get_str() -> &'static str {\n    \"rust_str\"\n}\n\n// SAFETY: UsizeCallback is only accessed from the Tokio runtime thread after being moved there.\nunsafe impl Send for ffi::UsizeCallback {}\n// SAFETY: UsizeCallback is only accessed from one thread at a time via Pin<&mut>.\nunsafe impl Sync for ffi::UsizeCallback {}\n\nfn async_immediate(callback: Pin<&'static mut ffi::UsizeCallback>) {\n    cxx_integration::tokio::spawn(async move {\n        callback.call(42);\n    });\n}\n\nfn async_sleep(callback: Pin<&'static mut ffi::UsizeCallback>) {\n    cxx_integration::tokio::spawn(async move {\n        tokio::time::sleep(Duration::from_millis(1)).await;\n        callback.call(42);\n    });\n}\n\n#[cfg(all(test, feature = \"sanitizer_address\"))]\nmod tests {\n    use nix::sys::signal::Signal;\n    use safe_libc::expect_signal;\n\n    #[test]\n    fn asan_stack_buffer_overflow() {\n        expect_signal!(Signal::SIGABRT, {\n            let xs = [0, 1, 2, 3];\n            // SAFETY: Intentional out-of-bounds access to trigger ASAN detection.\n            let _y = unsafe { *xs.as_ptr().offset(4) };\n        });\n    }\n}\n"
  },
  {
    "path": "src/rust/encoding/BUILD.bazel",
    "content": "load(\"//:build/wd_rust_crate.bzl\", \"wd_rust_crate\")\n\nwd_rust_crate(\n    name = \"encoding\",\n    cxx_bridge_src = \"lib.rs\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/rust/cxx-integration\",\n        \"@crates_vendor//:encoding_rs\",\n    ],\n)\n"
  },
  {
    "path": "src/rust/encoding/lib.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n//! WHATWG Encoding Standard legacy decoders via `encoding_rs`.\n//!\n//! Exposes a streaming decoder to C++ via CXX bridge. All legacy encodings\n//! (CJK multi-byte, single-byte windows-1252, and x-user-defined) are handled\n//! by a single opaque `Decoder` type backed by `encoding_rs::Decoder`.\n//!\n//! The output buffer is owned by the `Decoder` and reused across calls to\n//! avoid repeated heap allocations. C++ reads the decoded UTF-16 data via\n//! the pointer and length returned in `DecodeResult`.\n\n#[cxx::bridge(namespace = \"workerd::rust::encoding\")]\n#[expect(clippy::elidable_lifetime_names)] // CXX bridge requires named lifetimes on shared structs\nmod ffi {\n    /// Legacy encoding types supported by the Rust decoder.\n    /// Shared between C++ and Rust.\n    #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n    #[repr(u16)]\n    enum Encoding {\n        Big5,\n        EucJp,\n        EucKr,\n        Gb18030,\n        Gbk,\n        Iso2022Jp,\n        ShiftJis,\n        Windows1252,\n        XUserDefined,\n    }\n\n    /// Result of a decode operation. The output slice borrows the\n    /// decoder's internal buffer and is valid until the next `decode` or\n    /// `reset` call.\n    struct DecodeResult<'a> {\n        /// UTF-16 code units decoded from the input, borrowing the\n        /// decoder's reusable output buffer.\n        output: &'a [u16],\n        /// True if a fatal decoding error was encountered. Only meaningful\n        /// when the caller requested fatal mode — in replacement mode errors\n        /// are silently replaced with U+FFFD and this flag is not set.\n        had_error: bool,\n    }\n\n    struct DecodeOptions {\n        flush: bool,\n        fatal: bool,\n    }\n\n    extern \"Rust\" {\n        type Decoder;\n\n        /// Create a new streaming decoder for the given encoding.\n        // CXX bridge requires Box for opaque types.\n        #[expect(clippy::unnecessary_box_returns)]\n        fn new_decoder(encoding: Encoding) -> Box<Decoder>;\n\n        /// Decode a chunk of bytes. The decoded UTF-16 output is stored in\n        /// the decoder's internal buffer; the returned `DecodeResult`\n        /// borrows that buffer. Set `flush` to true on the final chunk.\n        /// When `fatal` is true and an error is encountered, `had_error`\n        /// is set and the output may be incomplete.\n        unsafe fn decode<'a>(\n            decoder: &'a mut Decoder,\n            input: &[u8],\n            options: &DecodeOptions,\n        ) -> DecodeResult<'a>;\n\n        /// Reset the decoder to its initial state (for explicit reset calls).\n        fn reset(decoder: &mut Decoder);\n    }\n}\n\n/// Opaque decoder state exposed to C++ via `Box<Decoder>`.\npub struct Decoder {\n    encoding: &'static encoding_rs::Encoding,\n    inner: encoding_rs::Decoder,\n    /// Reusable output buffer — kept across calls to avoid allocation.\n    output: Vec<u16>,\n    /// Set after a flush decode; checked at the start of the next decode\n    /// to lazily reconstruct the inner decoder.\n    needs_reset: bool,\n}\n\n/// Map a CXX-shared `Encoding` variant to the corresponding\n/// `encoding_rs` static.\nfn to_encoding(enc: ffi::Encoding) -> &'static encoding_rs::Encoding {\n    match enc {\n        ffi::Encoding::Big5 => encoding_rs::BIG5,\n        ffi::Encoding::EucJp => encoding_rs::EUC_JP,\n        ffi::Encoding::EucKr => encoding_rs::EUC_KR,\n        ffi::Encoding::Gb18030 => encoding_rs::GB18030,\n        ffi::Encoding::Gbk => encoding_rs::GBK,\n        ffi::Encoding::Iso2022Jp => encoding_rs::ISO_2022_JP,\n        ffi::Encoding::ShiftJis => encoding_rs::SHIFT_JIS,\n        ffi::Encoding::Windows1252 => encoding_rs::WINDOWS_1252,\n        ffi::Encoding::XUserDefined => encoding_rs::X_USER_DEFINED,\n        _ => unreachable!(),\n    }\n}\n\npub fn new_decoder(encoding: ffi::Encoding) -> Box<Decoder> {\n    let encoding = to_encoding(encoding);\n    Box::new(Decoder {\n        inner: encoding.new_decoder_without_bom_handling(),\n        encoding,\n        output: Vec::new(),\n        needs_reset: false,\n    })\n}\n\npub fn decode<'a>(\n    state: &'a mut Decoder,\n    input: &[u8],\n    options: &ffi::DecodeOptions,\n) -> ffi::DecodeResult<'a> {\n    // Lazy reset: reconstruct the inner decoder only when a previous flush\n    // marked it as needed, avoiding the cost on one-shot decodes where the\n    // decoder is never reused.\n    if state.needs_reset {\n        state.inner = state.encoding.new_decoder_without_bom_handling();\n        state.needs_reset = false;\n    }\n\n    // Reuse the output buffer — clear length but keep the allocation.\n    state.output.clear();\n    let max_len = state\n        .inner\n        .max_utf16_buffer_length(input.len())\n        .unwrap_or(input.len() + 4);\n    state.output.resize(max_len, 0);\n\n    let mut total_read = 0usize;\n    let mut total_written = 0usize;\n\n    if options.fatal {\n        loop {\n            let (result, read, written) = state.inner.decode_to_utf16_without_replacement(\n                &input[total_read..],\n                &mut state.output[total_written..],\n                options.flush,\n            );\n            total_read += read;\n            total_written += written;\n\n            match result {\n                encoding_rs::DecoderResult::InputEmpty => break,\n                encoding_rs::DecoderResult::OutputFull => {\n                    state.output.resize(state.output.len() * 2, 0);\n                }\n                encoding_rs::DecoderResult::Malformed(_, _) => {\n                    // Reset immediately on fatal error so the decoder is\n                    // ready for a fresh sequence if reused.\n                    state.inner = state.encoding.new_decoder_without_bom_handling();\n                    state.output.truncate(total_written);\n                    return ffi::DecodeResult {\n                        output: &state.output,\n                        had_error: true,\n                    };\n                }\n            }\n        }\n    } else {\n        loop {\n            let (result, read, written, _had_errors) = state.inner.decode_to_utf16(\n                &input[total_read..],\n                &mut state.output[total_written..],\n                options.flush,\n            );\n            total_read += read;\n            total_written += written;\n\n            match result {\n                encoding_rs::CoderResult::InputEmpty => break,\n                encoding_rs::CoderResult::OutputFull => {\n                    state.output.resize(state.output.len() * 2, 0);\n                }\n            }\n        }\n    }\n\n    state.output.truncate(total_written);\n\n    if options.flush {\n        // Defer the actual reset to the next decode() call.\n        state.needs_reset = true;\n    }\n\n    ffi::DecodeResult {\n        output: &state.output,\n        had_error: false,\n    }\n}\n\npub fn reset(state: &mut Decoder) {\n    state.inner = state.encoding.new_decoder_without_bom_handling();\n    state.needs_reset = false;\n    // Intentionally keep state.output — preserves the allocation for reuse.\n    // The buffer can grow up to ~2× the largest input chunk (due to UTF-16\n    // expansion and the doubling strategy in decode()) and stays at that high-\n    // water mark. This is acceptable because the Decoder is owned by a JS\n    // TextDecoder object and is GC'd with it, so the buffer lifetime is\n    // bounded by the object's reachability.\n}\n"
  },
  {
    "path": "src/rust/gen-compile-cache/BUILD.bazel",
    "content": "load(\"@workerd//:build/wd_cc_library.bzl\", \"wd_cc_library\")\nload(\"@workerd//:build/wd_rust_binary.bzl\", \"wd_rust_binary\")\n\nwd_rust_binary(\n    name = \"gen-compile-cache\",\n    cxx_bridge_src = \"main.rs\",\n    test_size = \"large\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":cxx-bridge\",\n        \"@crates_vendor//:clap\",\n    ],\n)\n\nwd_cc_library(\n    name = \"cxx-bridge\",\n    srcs = [\n        \"cxx-bridge.c++\",\n    ],\n    hdrs = [\n        \"cxx-bridge.h\",\n    ],\n    linkstatic = select({\n        \"@platforms//os:windows\": True,\n        \"//conditions:default\": False,\n    }),\n    deps = [\n        \"//src/rust/cxx-integration\",\n        \"//src/workerd/jsg\",\n        \"//src/workerd/jsg:compile-cache\",\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n"
  },
  {
    "path": "src/rust/gen-compile-cache/cxx-bridge.c++",
    "content": "#include \"cxx-bridge.h\"\n\n#include <workerd/jsg/compile-cache.h>\n#include <workerd/jsg/setup.h>\n#include <workerd/jsg/type-wrapper.h>\n\n#include <kj-rs/kj-rs.h>\n\n#include <capnp/serialize.h>\n\nusing namespace kj_rs;\nnamespace workerd::rust::gen_compile_cache {\n\nnamespace {\nstruct CompilerCacheContext: public jsg::Object, public jsg::ContextGlobal {\n  JSG_RESOURCE_TYPE(CompilerCacheContext) {}\n};\n\nJSG_DECLARE_ISOLATE_TYPE(CompileCacheIsolate, CompilerCacheContext);\n\nconstexpr int resourceLineOffset = 0;\nconstexpr int resourceColumnOffset = 0;\nconstexpr bool resourceIsSharedCrossOrigin = false;\nconstexpr int scriptId = -1;\nconstexpr bool resourceIsOpaque = false;\nconstexpr bool isWasm = false;\nconstexpr bool isModule = true;\nconstexpr v8::ScriptCompiler::CompileOptions compileOptions = v8::ScriptCompiler::kNoCompileOptions;\n\n}  // namespace\n\n::rust::Vec<uint8_t> compile(::rust::Str path, ::rust::Str source) {\n  static jsg::V8System system{};\n  static v8::Isolate::CreateParams params{};\n  static CompileCacheIsolate ccIsolate(system, kj::heap<jsg::IsolateObserver>(), params);\n\n  auto data = ccIsolate.runInLockScope([&](CompileCacheIsolate::Lock& isolateLock) {\n    return JSG_WITHIN_CONTEXT_SCOPE(isolateLock,\n        isolateLock.newContext<CompilerCacheContext>().getHandle(isolateLock), [&](jsg::Lock& js) {\n      return js.tryCatch([&]() {\n        auto resourceName = jsg::newExternalOneByteString(js, kj::from<Rust>(path));\n        v8::ScriptOrigin origin(resourceName, resourceLineOffset, resourceColumnOffset,\n            resourceIsSharedCrossOrigin, scriptId, {}, resourceIsOpaque, isWasm, isModule);\n\n        auto contentStr = jsg::newExternalOneByteString(js, kj::from<Rust>(source));\n        auto source = v8::ScriptCompiler::Source(contentStr, origin, nullptr);\n        auto module =\n            jsg::check(v8::ScriptCompiler::CompileModule(js.v8Isolate, &source, compileOptions));\n\n        auto codeCache = v8::ScriptCompiler::CreateCodeCache(module->GetUnboundModuleScript());\n        auto data = kj::arrayPtr(codeCache->data, codeCache->length).as<RustCopy>();\n        delete codeCache;\n        return data;\n      }, [&](jsg::Value exception) -> ::rust::Vec<uint8_t> {\n        auto kjException = js.exceptionToKj(kj::mv(exception));\n        KJ_FAIL_REQUIRE(\"JavaScript compilation error\", path, kjException.getDescription());\n      });\n    });\n  });\n\n  return kj::mv(data);\n}\n\n}  // namespace workerd::rust::gen_compile_cache\n"
  },
  {
    "path": "src/rust/gen-compile-cache/cxx-bridge.h",
    "content": "#pragma once\n\n#include <rust/cxx.h>\n\nnamespace workerd::rust::gen_compile_cache {\n::rust::Vec<uint8_t> compile(::rust::Str path, ::rust::Str source);\n}\n"
  },
  {
    "path": "src/rust/gen-compile-cache/main.rs",
    "content": "use std::fs;\nuse std::path::Path;\nuse std::path::PathBuf;\n\nuse clap::Parser;\n\n/// Generate V8 compile caches\n#[derive(Parser, Debug)]\nstruct Args {\n    /// Contains `<input_path> <output_path>` lines\n    file_list: PathBuf,\n}\n\nfn main() {\n    let args = Args::parse();\n\n    let file_list = fs::read_to_string(&args.file_list).expect(\"can't read file list\");\n    for line in file_list.split('\\n').filter(|l| !l.is_empty()) {\n        let [input_path, output_path] = line\n            .split(' ')\n            .collect::<Vec<_>>()\n            .try_into()\n            .expect(\"incorrect input line\");\n\n        let input = fs::read_to_string(input_path).expect(\"error reading input file\");\n        let output = ffi::compile(input_path, &input);\n\n        fs::write(Path::new(output_path), output).expect(\"error writing output file\");\n    }\n}\n\n#[cxx::bridge(namespace = \"workerd::rust::gen_compile_cache\")]\nmod ffi {\n    unsafe extern \"C++\" {\n        include!(\"workerd/rust/gen-compile-cache/cxx-bridge.h\");\n\n        fn compile(path: &str, source_code: &str) -> Vec<u8>;\n    }\n}\n"
  },
  {
    "path": "src/rust/jsg/BUILD.bazel",
    "content": "load(\"//:build/wd_cc_library.bzl\", \"wd_cc_library\")\nload(\"//:build/wd_rust_crate.bzl\", \"wd_rust_crate\")\n\nwd_rust_crate(\n    name = \"jsg\",\n    cxx_bridge_deps = [\"//src/workerd/jsg:jsg-core\"],\n    cxx_bridge_features = [\"-parse_headers\"],\n    cxx_bridge_local_defines = [\"JSG_IMPLEMENTATION\"],\n    cxx_bridge_srcs = [\n        \"lib.rs\",\n        \"v8.rs\",\n    ],\n    cxx_bridge_tags = [\"no-clang-tidy\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":ffi\",\n        \"//src/workerd/io:compatibility-date_capnp_rust\",\n        \"@crates_vendor//:capnp\",\n    ],\n)\n\nwd_cc_library(\n    name = \"bridge\",\n    srcs = [],\n    hdrs = [\"jsg.h\"],\n    local_defines = [\"JSG_IMPLEMENTATION\"],\n    tags = [\"no-clang-tidy\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\":jsg\"],\n)\n\nwd_cc_library(\n    name = \"ffi\",\n    srcs = [\"ffi.c++\"],\n    hdrs = [\n        \"ffi.h\",\n    ],\n    local_defines = [\"JSG_IMPLEMENTATION\"],\n    tags = [\"no-clang-tidy\"],\n    textual_hdrs = [\n        \"ffi-inl.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":lib.rs@cxx\",\n        \":v8.rs@cxx\",\n        \"//src/workerd/jsg:jsg-core\",\n        \"@workerd-cxx//kj-rs\",\n        \"@workerd-v8//:v8\",\n    ],\n)\n"
  },
  {
    "path": "src/rust/jsg/README.md",
    "content": "# JSG (JavaScript Glue) Rust Bindings\n\nRust bindings for the JSG (JavaScript Glue) layer, enabling Rust code to integrate with workerd's JavaScript runtime.\n\n## Core Types\n\n### `Lock`\n\nProvides access to V8 operations within an isolate lock. Passed to resource methods and callbacks.\n\n### `Rc<R>`\n\nStrong reference to a Rust resource managed by GC. Derefs to `&R`. Cloning and dropping a `Rc` tracks strong references for the garbage collector.\n\n### `Weak<R>`\n\nWeak reference that doesn't prevent GC collection. Use `upgrade()` to get a `Rc<R>` if the resource is still alive.\n\n### `Realm`\n\nPer-isolate state for Rust resources exposed to JavaScript. Stores cached function templates.\n\n## Resources\n\nRust resources integrate with V8's garbage collector through the existing C++ `Wrappable` infrastructure — the same GC system that C++ `jsg::Rc<T>` and `jsg::Object` use.\n\n```rust\nuse jsg_macros::{jsg_resource, jsg_method};\nuse std::cell::Cell;\n\n#[jsg_resource]\nstruct MyResource {\n    name: String,\n\n    // jsg::Rc<T> fields — strong GC edges, automatically traced\n    child: jsg::Rc<OtherResource>,\n    maybe_child: Option<jsg::Rc<OtherResource>>,\n    nullable_child: jsg::Nullable<jsg::Rc<OtherResource>>,\n\n    // jsg::Weak<T> fields — weak reference, does not keep the target alive\n    observer: jsg::Weak<OtherResource>,\n\n    // jsg::v8::Global<T> fields — JS value traced with strong↔weak dual-mode switching.\n    // Allows GC to detect and collect back-reference cycles (e.g. a stored callback\n    // that closes over the resource's own JS wrapper).\n    // Must be wrapped in Cell<_> for interior mutability (trace takes &self).\n    callback: Cell<Option<jsg::v8::Global<jsg::v8::Value>>>,\n}\n\n#[jsg_resource]\nimpl MyResource {\n    #[jsg_method]\n    fn get_name(&self) -> Result<String, jsg::Error> {\n        Ok(self.name.clone())\n    }\n}\n```\n\n### Lifecycle\n\n```rust\n// Create a resource\nlet resource = jsg::Rc::new(MyResource { ... });\n\n// Convert to a JS object (uses cached FunctionTemplate)\nlet js_obj = resource.to_js(&mut lock);\n\n// Convert from JS back to a Ref\nlet r: jsg::Rc<MyResource> = jsg::Rc::from_js(&mut lock, js_val)?;\n```\n\n### GC Behavior\n\n- **No JS wrapper**: Dropping the last `Rc` immediately destroys the resource (no GC needed).\n- **With JS wrapper**: Dropping all `Rc`s makes the wrapper eligible for V8 GC. When collected, the resource is destroyed.\n- **Tracing**: The `#[jsg_resource]` macro auto-generates `GarbageCollected::trace` based on field types:\n\n| Field type | Traced? | Notes |\n|---|---|---|\n| `jsg::Rc<T>` | Yes — strong edge | Keeps target alive through GC |\n| `Option<jsg::Rc<T>>` | Yes — when `Some` | |\n| `jsg::Nullable<jsg::Rc<T>>` | Yes — when `Some` | |\n| `Cell<jsg::Rc<T>>` | Yes — strong edge | Use `Cell` when field needs interior mutability |\n| `Cell<Option<jsg::Rc<T>>>` | Yes — when `Some` | |\n| `Cell<jsg::Nullable<jsg::Rc<T>>>` | Yes — when `Some` | |\n| `jsg::v8::Global<T>` | Yes — dual strong/traced | Enables cycle collection; see below |\n| `Option<jsg::v8::Global<T>>` | Yes — when `Some` | |\n| `jsg::Nullable<jsg::v8::Global<T>>` | Yes — when `Some` | |\n| `Cell<jsg::v8::Global<T>>` | Yes — dual strong/traced | Required when set after construction |\n| `Cell<Option<jsg::v8::Global<T>>>` | Yes — when `Some` | |\n| `jsg::Weak<T>` | No | Doesn't keep target alive |\n| Anything else | No | Plain data fields are ignored |\n\n- **`Cell<T>` for interior mutability**: `GarbageCollected::trace` takes `&self`. Fields that need to be mutated after construction (e.g. a callback set in a method) must be wrapped in `Cell<T>`. Both `Cell<T>` and `std::cell::Cell<T>` are recognised.\n- **`jsg::v8::Global<T>` cycle collection**: Uses the same strong↔traced dual-mode as C++ `jsg::V8Ref<T>`. While the parent resource has strong Rust refs the JS handle stays strong. Once all Rust `Rc`s are dropped, `visit_global` downgrades the handle to a `v8::TracedReference` that cppgc can follow — allowing cycles (e.g. a resource holding a callback that captures its own wrapper) to be detected and collected.\n- **Circular references** through `jsg::Rc<T>` are **not** collected, matching C++ `jsg::Rc<T>` behavior.\n\n## V8 Handle Types\n\n### `Local<'a, T>`\n\nA stack-allocated handle to a V8 value. The lifetime `'a` is tied to the `HandleScope` that created it.\n\n```rust\nlet str_value = \"hello\".to_local(&mut lock);\nlet num_value = 42u32.to_local(&mut lock);\nlet global = local.to_global(&mut lock);\n```\n\n### `Global<T>`\n\nA persistent handle that outlives `HandleScope`s. Must be explicitly managed.\n\n`Global<T>` fields on `#[jsg_resource]` structs participate in GC tracing when visited via `GcVisitor::visit_global`. This enables the garbage collector to detect and collect back-reference cycles — for example, a resource that stores a JS callback which closes over the resource's own JS wrapper:\n\n```rust\n#[jsg_resource]\nstruct EventEmitter {\n    // Cell<Option<_>> for interior mutability: the callback is set after\n    // construction, and trace() receives &self.\n    on_event: Cell<Option<jsg::v8::Global<jsg::v8::Value>>>,\n}\n\n#[jsg_resource]\nimpl EventEmitter {\n    #[jsg_method]\n    fn set_callback(&self, lock: &mut jsg::Lock, cb: jsg::v8::Local<jsg::v8::Value>) {\n        self.on_event.set(Some(cb.to_global(lock)));\n    }\n}\n```\n\nWithout tracing, storing a `Global` back to the resource's own wrapper creates an unbreakable reference cycle that leaks until the worker is torn down. With `visit_global` tracing (generated automatically by `#[jsg_resource]`), the cycle is collected by the next full GC after all strong Rust `Rc`s are dropped.\n\n## Union Types\n\nTo accept JavaScript values that can be one of several types, define an enum with `#[jsg_oneof]`:\n\n```rust\nuse jsg_macros::jsg_oneof;\n\n#[jsg_oneof]\n#[derive(Debug, Clone)]\nenum StringOrNumber {\n    String(String),\n    Number(f64),\n}\n\n// In a jsg_method:\npub fn process(&self, value: StringOrNumber) -> Result<String, jsg::Error> {\n    match value {\n        StringOrNumber::String(s) => Ok(format!(\"string: {}\", s)),\n        StringOrNumber::Number(n) => Ok(format!(\"number: {}\", n)),\n    }\n}\n```\n\nThis is similar to `kj::OneOf<>` in C++ JSG.\n\n## Feature Flags (Compatibility Flags)\n\n`Lock::feature_flags()` provides Rust-native access to the worker's compatibility flags, backed by the Cap'n Proto Rust crate (`capnp`). The flags are deserialized from the `CompatibilityFlags` schema in `src/workerd/io/compatibility-date.capnp`.\n\n### Reading flags\n\n```rust\nif lock.feature_flags().get_node_js_compat() {\n    // Node.js compatibility behavior\n}\n```\n\n`feature_flags()` returns a capnp-generated `compatibility_flags::Reader` with a getter for each boolean flag (e.g., `get_node_js_compat()`, `get_url_standard()`, `get_fetch_refuses_unknown_protocols()`).\n\n### How it works\n\n1. During worker initialization, C++ canonicalizes the worker's `CompatibilityFlags` via `capnp::canonicalize()` and passes the bytes to `realm_create()`, which parses them once and stores the result in the per-context `Realm`.\n2. `lock.feature_flags()` reads the cached `FeatureFlags` and returns its capnp reader. No copies or re-parsing on access.\n\n### Key types and files\n\n| Item | Location |\n|------|----------|\n| `FeatureFlags` struct | `src/rust/jsg/feature_flags.rs` |\n| `Lock::feature_flags()` | `src/rust/jsg/lib.rs` |\n| `realm_create()` FFI | `src/rust/jsg/lib.rs` (CXX bridge) |\n| C++ call site | `src/workerd/io/worker.c++` (`initIsolate`) |\n| Cap'n Proto schema | `src/workerd/io/compatibility-date.capnp` |\n| Generated Rust bindings | `//src/workerd/io:compatibility-date_capnp_rust` (Bazel target) |\n\n\n## Constructors\n\nTo allow JavaScript to create instances of a resource via `new MyResource(args)`, mark a static method with `#[jsg_constructor]`:\n\n```rust\nuse jsg_macros::{jsg_resource, jsg_method, jsg_constructor};\n\n#[jsg_resource]\nstruct Greeting {\n    message: String,\n}\n\n#[jsg_resource]\nimpl Greeting {\n    #[jsg_constructor]\n    fn constructor(message: String) -> Self {\n        Self { message }\n    }\n\n    #[jsg_method]\n    fn get_message(&self) -> String {\n        self.message.clone()\n    }\n}\n// JS: let g = new Greeting(\"hello\"); g.getMessage() === \"hello\"\n```\n\n**Rules:**\n- The method must be static (no `self` receiver) and must return `Self`.\n- Only one `#[jsg_constructor]` is allowed per impl block.\n- The first parameter may be `&mut Lock` (or `&mut jsg::Lock`) if the constructor needs isolate access; it is not exposed as a JS argument.\n- If no `#[jsg_constructor]` is present, `new MyResource()` throws an `Illegal constructor` error, matching C++ JSG behavior.\n\n## Static Constants\n\nTo expose numeric constants on a resource class (equivalent to `JSG_STATIC_CONSTANT` in C++), use `#[jsg_static_constant]` on `const` items inside a `#[jsg_resource]` impl block:\n\n```rust\nuse jsg_macros::jsg_static_constant;\n\n#[jsg_resource]\nimpl WebSocket {\n    #[jsg_static_constant]\n    pub const CONNECTING: i32 = 0;\n\n    #[jsg_static_constant]\n    pub const OPEN: i32 = 1;\n}\n// JS: WebSocket.CONNECTING === 0, instance.OPEN === 1\n```\n\nConstants are set on both the constructor and prototype as read-only, non-configurable properties per Web IDL. The name is used as-is (no camelCase conversion). Only numeric types are supported (`i8`..`i64`, `u8`..`u64`, `f32`, `f64`).\n\n## FFI Functions with Raw Pointers\n\nFunctions exposed to C++ via FFI that receive raw pointers must be marked `unsafe fn`:\n\n```rust\npub unsafe fn realm_create(isolate: *mut v8::ffi::Isolate, feature_flags_data: &[u8]) -> Box<Realm> {\n    // ...\n}\n```\n"
  },
  {
    "path": "src/rust/jsg/feature_flags.rs",
    "content": "//! Rust-native access to workerd compatibility flags.\n//!\n//! ```ignore\n//! if lock.feature_flags().get_node_js_compat() {\n//!     // Node.js compatibility behavior\n//! }\n//! ```\n\nuse capnp::message::ReaderOptions;\npub use compatibility_date_capnp::compatibility_flags;\n\n/// Provides access to the current worker's compatibility flags.\n///\n/// Parsed once from canonical Cap'n Proto bytes during Realm construction\n/// and stored in the per-context [`Realm`](crate::Realm). Access via\n/// [`Lock::feature_flags()`](crate::Lock::feature_flags).\npub struct FeatureFlags {\n    message: capnp::message::Reader<Vec<Vec<u8>>>,\n}\n\nimpl FeatureFlags {\n    /// Create from canonical (single-segment, no segment table) Cap'n Proto bytes.\n    ///\n    /// On the C++ side, produce these via `capnp::canonicalize(reader)`.\n    ///\n    /// # Panics\n    ///\n    /// Panics if `data` is empty or not word-aligned.\n    pub(crate) fn from_bytes(data: &[u8]) -> Self {\n        assert!(!data.is_empty(), \"FeatureFlags data must not be empty\");\n        assert!(\n            data.len().is_multiple_of(8),\n            \"FeatureFlags data must be word-aligned (got {} bytes)\",\n            data.len()\n        );\n        let segments = vec![data.to_vec()];\n        let message = capnp::message::Reader::new(segments, ReaderOptions::new());\n        Self { message }\n    }\n\n    /// Returns the `CompatibilityFlags` reader.\n    ///\n    /// The reader has a getter for each flag defined in `compatibility-date.capnp`\n    /// (e.g., `get_node_js_compat()`).\n    ///\n    /// # Panics\n    ///\n    /// Panics if the stored message has an invalid capnp root (should never happen\n    /// when constructed via `from_bytes`).\n    pub fn reader(&self) -> compatibility_flags::Reader<'_> {\n        self.message\n            .get_root::<compatibility_flags::Reader<'_>>()\n            .expect(\"Invalid FeatureFlags capnp root\")\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    /// Helper: build a `CompatibilityFlags` capnp message with the given flag setter,\n    /// return the raw single-segment bytes (no wire-format header).\n    fn build_flags<F>(setter: F) -> Vec<u8>\n    where\n        F: FnOnce(compatibility_flags::Builder<'_>),\n    {\n        let mut message = capnp::message::Builder::new_default();\n        {\n            let flags = message.init_root::<compatibility_flags::Builder<'_>>();\n            setter(flags);\n        }\n        let output = message.get_segments_for_output();\n        output[0].to_vec()\n    }\n\n    #[test]\n    fn from_bytes_roundtrip() {\n        let bytes = build_flags(|mut f| {\n            f.set_node_js_compat(true);\n        });\n        let ff = FeatureFlags::from_bytes(&bytes);\n        assert!(ff.reader().get_node_js_compat());\n    }\n\n    #[test]\n    #[should_panic(expected = \"FeatureFlags data must not be empty\")]\n    fn from_bytes_empty_panics() {\n        FeatureFlags::from_bytes(&[]);\n    }\n\n    #[test]\n    fn default_flags_are_false() {\n        let bytes = build_flags(|_| {});\n        let ff = FeatureFlags::from_bytes(&bytes);\n        assert!(!ff.reader().get_node_js_compat());\n        assert!(!ff.reader().get_node_js_compat_v2());\n        assert!(!ff.reader().get_fetch_refuses_unknown_protocols());\n    }\n\n    #[test]\n    fn multiple_flags() {\n        let bytes = build_flags(|mut f| {\n            f.set_node_js_compat(true);\n            f.set_node_js_compat_v2(true);\n            f.set_fetch_refuses_unknown_protocols(false);\n        });\n        let ff = FeatureFlags::from_bytes(&bytes);\n        assert!(ff.reader().get_node_js_compat());\n        assert!(ff.reader().get_node_js_compat_v2());\n        assert!(!ff.reader().get_fetch_refuses_unknown_protocols());\n    }\n\n    #[test]\n    fn reader_called_multiple_times() {\n        let bytes = build_flags(|mut f| {\n            f.set_node_js_compat(true);\n        });\n        let ff = FeatureFlags::from_bytes(&bytes);\n        // Reader can be obtained multiple times from the same FeatureFlags.\n        assert!(ff.reader().get_node_js_compat());\n        assert!(ff.reader().get_node_js_compat());\n    }\n}\n"
  },
  {
    "path": "src/rust/jsg/ffi-inl.h",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n// This header contains template implementations that require complete type definitions.\n// It should be included after workerd/rust/jsg/v8.rs.h which defines the complete types.\n\n#include <workerd/rust/jsg/ffi.h>\n#include <workerd/rust/jsg/v8.rs.h>\n\nnamespace workerd::rust::jsg {\n\n// Local<T>\nstatic_assert(sizeof(v8::Local<v8::Value>) == 8, \"Size should match\");\nstatic_assert(alignof(v8::Local<v8::Value>) == 8, \"Alignment should match\");\n\ntemplate <typename T>\ninline Local to_ffi(v8::Local<T>&& value) {\n  size_t result;\n  auto ptr_void = reinterpret_cast<void*>(&result);\n  new (ptr_void) v8::Local<T>(kj::mv(value));\n  return Local{result};\n}\n\ntemplate <typename T>\ninline v8::Local<T> local_from_ffi(Local&& value) {\n  auto ptr_void = reinterpret_cast<void*>(&value.ptr);\n  return *reinterpret_cast<v8::Local<T>*>(ptr_void);\n}\n\ntemplate <typename T>\ninline const v8::Local<T>& local_as_ref_from_ffi(const Local& value) {\n  auto ptr_void = reinterpret_cast<const void*>(&value.ptr);\n  return *reinterpret_cast<const v8::Local<T>*>(ptr_void);\n}\n\n// Global<T>\n//\n// ffi::Global stores only the strong v8::Global<v8::Value> in `ptr`.\n// The traced v8::TracedReference<v8::Data> slot lives in Global<T>::traced_ptr\n// on the Rust side (as UnsafeCell<usize>), passed by raw pointer to\n// wrappable_visit_global and wrappable_global_reset.\nstatic_assert(sizeof(v8::Global<v8::Value>) == sizeof(size_t), \"Global must be pointer-sized\");\nstatic_assert(sizeof(v8::TracedReference<v8::Data>) == sizeof(size_t),\n    \"TracedReference must be pointer-sized\");\nstatic_assert(sizeof(Global) == sizeof(size_t), \"ffi::Global must hold exactly one pointer slot\");\nstatic_assert(alignof(v8::Global<v8::Value>) == alignof(Global), \"Alignment should match\");\n\ntemplate <typename T>\ninline Global to_ffi(v8::Global<T>&& value) {\n  size_t strong_slot;\n  auto ptr_void = reinterpret_cast<void*>(&strong_slot);\n  new (ptr_void) v8::Global<T>(kj::mv(value));\n  return Global{strong_slot};\n}\n\ntemplate <typename T>\ninline v8::Global<T> global_from_ffi(Global&& value) {\n  auto ptr_void = reinterpret_cast<void*>(&value.ptr);\n  return kj::mv(*reinterpret_cast<v8::Global<T>*>(ptr_void));\n}\n\ntemplate <typename T>\ninline const v8::Global<T>& global_as_ref_from_ffi(const Global& value) {\n  auto ptr_void = reinterpret_cast<const void*>(&value.ptr);\n  return *reinterpret_cast<const v8::Global<T>*>(ptr_void);\n}\n\ntemplate <typename T>\ninline v8::Global<T>* global_as_ref_from_ffi(Global& value) {\n  auto ptr_void = reinterpret_cast<void*>(&value.ptr);\n  return reinterpret_cast<v8::Global<T>*>(ptr_void);\n}\n\n// TracedReference\nstatic_assert(sizeof(v8::TracedReference<v8::Data>) == sizeof(TracedReference),\n    \"TracedReference size must match\");\nstatic_assert(alignof(v8::TracedReference<v8::Data>) == alignof(TracedReference),\n    \"TracedReference alignment must match\");\n\ninline v8::TracedReference<v8::Data>& traced_ref_from_ffi(TracedReference& value) {\n  return *reinterpret_cast<v8::TracedReference<v8::Data>*>(&value);\n}\n\n// GcVisitor - wraps a pointer to jsg::GcVisitor\nstatic_assert(sizeof(::workerd::jsg::GcVisitor*) == sizeof(GcVisitor), \"Size should match\");\nstatic_assert(alignof(::workerd::jsg::GcVisitor*) == alignof(GcVisitor), \"Alignment should match\");\n\ninline GcVisitor to_ffi(::workerd::jsg::GcVisitor* visitor) {\n  return GcVisitor{reinterpret_cast<size_t>(visitor)};\n}\n\ninline ::workerd::jsg::GcVisitor* gc_visitor_from_ffi(GcVisitor* value) {\n  return reinterpret_cast<::workerd::jsg::GcVisitor*>(value->ptr);\n}\n\n}  // namespace workerd::rust::jsg\n"
  },
  {
    "path": "src/rust/jsg/ffi.c++",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"ffi.h\"\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/util.h>\n#include <workerd/jsg/wrappable.h>\n#include <workerd/rust/jsg/ffi-inl.h>\n#include <workerd/rust/jsg/lib.rs.h>\n#include <workerd/rust/jsg/v8.rs.h>\n\n#include <kj-rs/convert.h>\n\n#include <kj/common.h>\n#include <kj/string.h>\n\n#include <memory>\n\nusing namespace kj_rs;\n\nnamespace workerd::rust::jsg {\n\n#define DEFINE_TYPED_ARRAY_NEW(name, v8_type, elem_type)                                           \\\n  Local local_new_##name(Isolate* isolate, const elem_type* data, size_t length) {                 \\\n    auto backingStore = v8::ArrayBuffer::NewBackingStore(isolate, length * sizeof(elem_type));     \\\n    memcpy(backingStore->Data(), data, length * sizeof(elem_type));                                \\\n    auto arrayBuffer = v8::ArrayBuffer::New(isolate, std::move(backingStore));                     \\\n    return to_ffi(v8::v8_type::New(arrayBuffer, 0, length));                                       \\\n  }\n\n#define DEFINE_TYPED_ARRAY_UNWRAP(name, v8_type, elem_type)                                        \\\n  ::rust::Vec<elem_type> unwrap_##name(Isolate* isolate, Local value) {                            \\\n    auto v8Val = local_from_ffi<v8::Value>(kj::mv(value));                                         \\\n    KJ_REQUIRE(v8Val->Is##v8_type());                                                              \\\n    auto typed = v8Val.As<v8::v8_type>();                                                          \\\n    ::rust::Vec<elem_type> result;                                                                 \\\n    result.reserve(typed->Length());                                                               \\\n    auto data = reinterpret_cast<elem_type*>(                                                      \\\n        static_cast<uint8_t*>(typed->Buffer()->Data()) + typed->ByteOffset());                     \\\n    for (size_t i = 0; i < typed->Length(); i++) {                                                 \\\n      result.push_back(data[i]);                                                                   \\\n    }                                                                                              \\\n    return result;                                                                                 \\\n  }\n\n#define DEFINE_TYPED_ARRAY_GET(name, v8_type, elem_type)                                           \\\n  elem_type local_##name##_get(Isolate* isolate, const Local& array, size_t index) {               \\\n    auto typed = local_as_ref_from_ffi<v8::v8_type>(array);                                        \\\n    KJ_REQUIRE(index < typed->Length(), \"index out of bounds\");                                    \\\n    auto data = reinterpret_cast<elem_type*>(                                                      \\\n        static_cast<uint8_t*>(typed->Buffer()->Data()) + typed->ByteOffset());                     \\\n    return data[index];                                                                            \\\n  }\n\n// =============================================================================\n\n// Wrappable implementation - calls into Rust via CXX bridge\nWrappable::~Wrappable() {\n  wrappable_invoke_drop(*this);\n}\n\nvoid Wrappable::jsgVisitForGc(::workerd::jsg::GcVisitor& visitor) {\n  auto ffi_visitor = to_ffi(&visitor);\n  wrappable_invoke_trace(*this, &ffi_visitor);\n}\n\nkj::StringPtr Wrappable::jsgGetMemoryName() const {\n  // memory_name() on the Rust side returns a &'static str backed by a\n  // compile-time c\"...\" literal, so the pointer is valid for the process\n  // lifetime. Construct kj::StringPtr directly from data+size — no copy,\n  // no allocation, no caching needed.\n  auto name = wrappable_invoke_get_name(*this);\n  return kj::StringPtr(name.data(), name.size());\n}\n\nsize_t Wrappable::jsgGetMemorySelfSize() const {\n  return sizeof(Wrappable);\n}\n\n// Local<T>\nvoid local_drop(Local value) {\n  // Convert from FFI representation and let v8::Local destructor handle cleanup\n  local_from_ffi<v8::Value>(kj::mv(value));\n}\n\nLocal local_clone(const Local& value) {\n  return Local{.ptr = value.ptr};\n}\n\nGlobal local_to_global(Isolate* isolate, Local value) {\n  v8::Global<v8::Value> global(isolate, local_from_ffi<v8::Value>(kj::mv(value)));\n  return to_ffi(kj::mv(global));\n}\n\nLocal local_new_number(Isolate* isolate, double value) {\n  v8::Local<v8::Number> val = v8::Number::New(isolate, value);\n  return to_ffi(kj::mv(val));\n}\n\nLocal local_new_string(Isolate* isolate, ::rust::Str value) {\n  auto val = ::workerd::jsg::check(\n      v8::String::NewFromUtf8(isolate, value.cbegin(), v8::NewStringType::kNormal, value.size()));\n  return to_ffi(kj::mv(val));\n}\n\nLocal local_new_boolean(Isolate* isolate, bool value) {\n  v8::Local<v8::Boolean> val = v8::Boolean::New(isolate, value);\n  return to_ffi(kj::mv(val));\n}\n\nLocal local_new_object(Isolate* isolate) {\n  v8::Local<v8::Object> object = v8::Object::New(isolate);\n  return to_ffi(kj::mv(object));\n}\n\nLocal local_new_null(Isolate* isolate) {\n  v8::Local<v8::Primitive> null = v8::Null(isolate);\n  return to_ffi(kj::mv(null));\n}\n\nLocal local_new_undefined(Isolate* isolate) {\n  v8::Local<v8::Primitive> undefined = v8::Undefined(isolate);\n  return to_ffi(kj::mv(undefined));\n}\n\nbool local_eq(const Local& lhs, const Local& rhs) {\n  return local_as_ref_from_ffi<v8::Value>(lhs) == local_as_ref_from_ffi<v8::Value>(rhs);\n}\n\nbool local_has_value(const Local& val) {\n  return *local_as_ref_from_ffi<v8::Value>(val) != nullptr;\n}\n\nbool local_is_string(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsString();\n}\n\nbool local_is_boolean(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsBoolean();\n}\n\nbool local_is_number(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsNumber();\n}\n\nbool local_is_null(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsNull();\n}\n\nbool local_is_undefined(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsUndefined();\n}\n\nbool local_is_null_or_undefined(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsNullOrUndefined();\n}\n\nbool local_is_object(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsObject();\n}\n\nbool local_is_native_error(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsNativeError();\n}\n\nbool local_is_array(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsArray();\n}\n\nbool local_is_uint8_array(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsUint8Array();\n}\n\nbool local_is_uint16_array(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsUint16Array();\n}\n\nbool local_is_uint32_array(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsUint32Array();\n}\n\nbool local_is_int8_array(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsInt8Array();\n}\n\nbool local_is_int16_array(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsInt16Array();\n}\n\nbool local_is_int32_array(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsInt32Array();\n}\n\nbool local_is_float32_array(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsFloat32Array();\n}\n\nbool local_is_float64_array(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsFloat64Array();\n}\n\nbool local_is_bigint64_array(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsBigInt64Array();\n}\n\nbool local_is_biguint64_array(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsBigUint64Array();\n}\n\nbool local_is_array_buffer(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsArrayBuffer();\n}\n\nbool local_is_array_buffer_view(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsArrayBufferView();\n}\n\nbool local_is_function(const Local& val) {\n  return local_as_ref_from_ffi<v8::Value>(val)->IsFunction();\n}\n\n::rust::String local_type_of(Isolate* isolate, const Local& val) {\n  auto v8Val = local_as_ref_from_ffi<v8::Value>(val);\n  v8::Local<v8::String> typeStr = v8Val->TypeOf(isolate);\n  v8::String::Utf8Value utf8(isolate, typeStr);\n  return ::rust::String(*utf8, utf8.length());\n}\n\n// Local<Function>\nLocal local_function_call(\n    Isolate* isolate, const Local& function, const Local& recv, ::rust::Slice<const Local> args) {\n  auto context = isolate->GetCurrentContext();\n  auto fn = local_as_ref_from_ffi<v8::Function>(function);\n  auto receiver = local_as_ref_from_ffi<v8::Value>(recv);\n\n  v8::LocalVector<v8::Value> v8Args(isolate, args.size());\n  for (size_t i = 0; i < args.size(); i++) {\n    v8Args[i] = local_as_ref_from_ffi<v8::Value>(args[i]);\n  }\n\n  return to_ffi(::workerd::jsg::check(fn->Call(context, receiver, v8Args.size(), v8Args.data())));\n}\n\n// Local<Object>\nvoid local_object_set_property(Isolate* isolate, Local& object, ::rust::Str key, Local value) {\n  auto v8_obj = local_as_ref_from_ffi<v8::Object>(object);\n  auto context = isolate->GetCurrentContext();\n  auto v8_key = ::workerd::jsg::check(\n      v8::String::NewFromUtf8(isolate, key.cbegin(), v8::NewStringType::kInternalized, key.size()));\n  ::workerd::jsg::check(v8_obj->Set(context, v8_key, local_from_ffi<v8::Value>(kj::mv(value))));\n}\n\nbool local_object_has_property(Isolate* isolate, const Local& object, ::rust::Str key) {\n  auto v8_obj = local_as_ref_from_ffi<v8::Object>(object);\n  auto context = isolate->GetCurrentContext();\n  auto v8_key = ::workerd::jsg::check(\n      v8::String::NewFromUtf8(isolate, key.cbegin(), v8::NewStringType::kInternalized, key.size()));\n  return v8_obj->Has(context, v8_key).FromJust();\n}\n\nkj::Maybe<Local> local_object_get_property(Isolate* isolate, const Local& object, ::rust::Str key) {\n  auto v8_obj = local_as_ref_from_ffi<v8::Object>(object);\n  auto context = isolate->GetCurrentContext();\n  auto v8_key = ::workerd::jsg::check(\n      v8::String::NewFromUtf8(isolate, key.cbegin(), v8::NewStringType::kInternalized, key.size()));\n  v8::Local<v8::Value> result;\n  if (!v8_obj->Get(context, v8_key).ToLocal(&result)) {\n    return kj::none;\n  }\n  return to_ffi(kj::mv(result));\n}\n\n// Local<Array>\nLocal local_new_array(Isolate* isolate, size_t length) {\n  return to_ffi(v8::Array::New(isolate, length));\n}\n\nuint32_t local_array_length(Isolate* isolate, const Local& array) {\n  return local_as_ref_from_ffi<v8::Array>(array)->Length();\n}\n\nLocal local_array_get(Isolate* isolate, const Local& array, uint32_t index) {\n  auto context = isolate->GetCurrentContext();\n  auto v8Array = local_as_ref_from_ffi<v8::Array>(array);\n  return to_ffi(::workerd::jsg::check(v8Array->Get(context, index)));\n}\n\nvoid local_array_set(Isolate* isolate, Local& array, uint32_t index, Local value) {\n  auto context = isolate->GetCurrentContext();\n  auto v8Array = local_as_ref_from_ffi<v8::Array>(array);\n  ::workerd::jsg::check(v8Array->Set(context, index, local_from_ffi<v8::Value>(kj::mv(value))));\n}\n\n// TypedArray creation functions\nDEFINE_TYPED_ARRAY_NEW(uint8_array, Uint8Array, uint8_t)\nDEFINE_TYPED_ARRAY_NEW(uint16_array, Uint16Array, uint16_t)\nDEFINE_TYPED_ARRAY_NEW(uint32_array, Uint32Array, uint32_t)\nDEFINE_TYPED_ARRAY_NEW(int8_array, Int8Array, int8_t)\nDEFINE_TYPED_ARRAY_NEW(int16_array, Int16Array, int16_t)\nDEFINE_TYPED_ARRAY_NEW(int32_array, Int32Array, int32_t)\nDEFINE_TYPED_ARRAY_NEW(float32_array, Float32Array, float)\nDEFINE_TYPED_ARRAY_NEW(float64_array, Float64Array, double)\nDEFINE_TYPED_ARRAY_NEW(bigint64_array, BigInt64Array, int64_t)\nDEFINE_TYPED_ARRAY_NEW(biguint64_array, BigUint64Array, uint64_t)\n\n// Wrappers\nLocal wrap_resource(Isolate* isolate, kj::Rc<Wrappable> wrappable, const Global& tmpl) {\n  // Check if already wrapped\n  KJ_IF_SOME(handle, wrappable->tryGetHandle(isolate)) {\n    return to_ffi(v8::Local<v8::Value>::Cast(handle));\n  }\n\n  auto& global_tmpl = global_as_ref_from_ffi<v8::FunctionTemplate>(tmpl);\n  auto local_tmpl = v8::Local<v8::FunctionTemplate>::New(isolate, global_tmpl);\n  v8::Local<v8::Object> object = ::workerd::jsg::check(\n      local_tmpl->InstanceTemplate()->NewInstance(isolate->GetCurrentContext()));\n\n  // attachWrapper sets up CppgcShim, TracedReference, internal fields, etc.\n  wrappable->attachWrapper(isolate, object, true);\n\n  // Override tag to identify as Rust object for unwrapping\n  auto tagAddress = const_cast<uint16_t*>(&::workerd::jsg::Wrappable::WORKERD_RUST_WRAPPABLE_TAG);\n  object->SetAlignedPointerInInternalField(::workerd::jsg::Wrappable::WRAPPABLE_TAG_FIELD_INDEX,\n      tagAddress,\n      static_cast<v8::EmbedderDataTypeTag>(::workerd::jsg::Wrappable::WRAPPABLE_TAG_FIELD_INDEX));\n\n  return to_ffi(v8::Local<v8::Value>::Cast(object));\n}\n\nvoid wrappable_attach_wrapper(kj::Rc<Wrappable> wrappable, FunctionCallbackInfo& args) {\n  auto* isolate = args.GetIsolate();\n  auto object = args.This();\n\n  // attachWrapper sets up CppgcShim, TracedReference, internal fields, etc.\n  wrappable->attachWrapper(isolate, object, true);\n\n  // Override tag to identify as Rust object for unwrapping\n  auto tagAddress = const_cast<uint16_t*>(&::workerd::jsg::Wrappable::WORKERD_RUST_WRAPPABLE_TAG);\n  object->SetAlignedPointerInInternalField(::workerd::jsg::Wrappable::WRAPPABLE_TAG_FIELD_INDEX,\n      tagAddress,\n      static_cast<v8::EmbedderDataTypeTag>(::workerd::jsg::Wrappable::WRAPPABLE_TAG_FIELD_INDEX));\n}\n\n// Unwrappers\n::rust::String unwrap_string(Isolate* isolate, Local value) {\n  v8::Local<v8::String> v8Str = ::workerd::jsg::check(\n      local_from_ffi<v8::Value>(kj::mv(value))->ToString(isolate->GetCurrentContext()));\n  v8::String::ValueView view(isolate, v8Str);\n  if (!view.is_one_byte()) {\n    return ::rust::String(reinterpret_cast<const char16_t*>(view.data16()), view.length());\n  }\n  return ::rust::String::latin1(reinterpret_cast<const char*>(view.data8()), view.length());\n}\n\nbool unwrap_boolean(Isolate* isolate, Local value) {\n  return local_from_ffi<v8::Value>(kj::mv(value))->ToBoolean(isolate)->Value();\n}\n\ndouble unwrap_number(Isolate* isolate, Local value) {\n  return ::workerd::jsg::check(\n      local_from_ffi<v8::Value>(kj::mv(value))->ToNumber(isolate->GetCurrentContext()))\n      ->Value();\n}\n\nkj::Rc<Wrappable> unwrap_resource(Isolate* isolate, Local value) {\n  auto v8_val = local_from_ffi<v8::Value>(kj::mv(value));\n  // Non-object values (numbers, strings, booleans, etc.) are never wrapped resources.\n  if (!v8_val->IsObject()) return nullptr;\n  auto v8_obj = v8_val.As<v8::Object>();\n  // Plain JS objects have no internal fields; check before reading to avoid V8 fatal error.\n  if (v8_obj->InternalFieldCount() < ::workerd::jsg::Wrappable::INTERNAL_FIELD_COUNT ||\n      v8_obj->GetAlignedPointerFromInternalField(\n          ::workerd::jsg::Wrappable::WRAPPABLE_TAG_FIELD_INDEX,\n          static_cast<v8::EmbedderDataTypeTag>(\n              ::workerd::jsg::Wrappable::WRAPPABLE_TAG_FIELD_INDEX)) !=\n          const_cast<uint16_t*>(&::workerd::jsg::Wrappable::WORKERD_RUST_WRAPPABLE_TAG)) {\n    return nullptr;\n  }\n  auto* ptr = static_cast<Wrappable*>(\n      reinterpret_cast<::workerd::jsg::Wrappable*>(v8_obj->GetAlignedPointerFromInternalField(\n          ::workerd::jsg::Wrappable::WRAPPED_OBJECT_FIELD_INDEX,\n          static_cast<v8::EmbedderDataTypeTag>(\n              ::workerd::jsg::Wrappable::WRAPPED_OBJECT_FIELD_INDEX))));\n  return ptr->toRc();\n}\n\n// TypedArray unwrap functions\nDEFINE_TYPED_ARRAY_UNWRAP(uint8_array, Uint8Array, uint8_t)\nDEFINE_TYPED_ARRAY_UNWRAP(uint16_array, Uint16Array, uint16_t)\nDEFINE_TYPED_ARRAY_UNWRAP(uint32_array, Uint32Array, uint32_t)\nDEFINE_TYPED_ARRAY_UNWRAP(int8_array, Int8Array, int8_t)\nDEFINE_TYPED_ARRAY_UNWRAP(int16_array, Int16Array, int16_t)\nDEFINE_TYPED_ARRAY_UNWRAP(int32_array, Int32Array, int32_t)\nDEFINE_TYPED_ARRAY_UNWRAP(float32_array, Float32Array, float)\nDEFINE_TYPED_ARRAY_UNWRAP(float64_array, Float64Array, double)\nDEFINE_TYPED_ARRAY_UNWRAP(bigint64_array, BigInt64Array, int64_t)\nDEFINE_TYPED_ARRAY_UNWRAP(biguint64_array, BigUint64Array, uint64_t)\n\n// Uses V8's Array::Iterate() which is faster than indexed access.\n// Returns Global handles because Local handles get reused during iteration.\n::rust::Vec<Global> local_array_iterate(Isolate* isolate, Local value) {\n  auto context = isolate->GetCurrentContext();\n  auto v8Val = local_from_ffi<v8::Value>(kj::mv(value));\n\n  KJ_REQUIRE(v8Val->IsArray(), \"Value must be an array\");\n  auto arr = v8Val.As<v8::Array>();\n\n  struct Data {\n    Isolate* isolate;\n    ::rust::Vec<Global>* result;\n  };\n\n  ::rust::Vec<Global> result;\n  Data data{isolate, &result};\n\n  auto iterateResult = arr->Iterate(context,\n      [](uint32_t index, v8::Local<v8::Value> element,\n          void* userData) -> v8::Array::CallbackResult {\n    auto* d = static_cast<Data*>(userData);\n    d->result->push_back(to_ffi(v8::Global<v8::Value>(d->isolate, element)));\n    return v8::Array::CallbackResult::kContinue;\n  },\n      &data);\n\n  KJ_REQUIRE(iterateResult.IsJust(), \"Iteration failed\");\n  return result;\n}\n\n// Local<TypedArray>\nsize_t local_typed_array_length(Isolate* isolate, const Local& array) {\n  return local_as_ref_from_ffi<v8::TypedArray>(array)->Length();\n}\n\n// TypedArray element getter functions\nDEFINE_TYPED_ARRAY_GET(uint8_array, Uint8Array, uint8_t)\nDEFINE_TYPED_ARRAY_GET(uint16_array, Uint16Array, uint16_t)\nDEFINE_TYPED_ARRAY_GET(uint32_array, Uint32Array, uint32_t)\nDEFINE_TYPED_ARRAY_GET(int8_array, Int8Array, int8_t)\nDEFINE_TYPED_ARRAY_GET(int16_array, Int16Array, int16_t)\nDEFINE_TYPED_ARRAY_GET(int32_array, Int32Array, int32_t)\nDEFINE_TYPED_ARRAY_GET(float32_array, Float32Array, float)\nDEFINE_TYPED_ARRAY_GET(float64_array, Float64Array, double)\nDEFINE_TYPED_ARRAY_GET(bigint64_array, BigInt64Array, int64_t)\nDEFINE_TYPED_ARRAY_GET(biguint64_array, BigUint64Array, uint64_t)\n\n// Global<T>\nvoid global_reset(Global& value) {\n  global_as_ref_from_ffi<v8::Value>(value)->Reset();\n}\n\nGlobal global_clone(Isolate* isolate, const Global& value) {\n  auto& original = global_as_ref_from_ffi<v8::Value>(value);\n  return to_ffi(v8::Global<v8::Value>(isolate, original));\n}\n\nLocal global_to_local(Isolate* isolate, const Global& value) {\n  auto& glbl = global_as_ref_from_ffi<v8::Value>(value);\n  v8::Local<v8::Value> local = v8::Local<v8::Value>::New(isolate, glbl);\n  return to_ffi(kj::mv(local));\n}\n\n// Wrappable - data access\nconst TraitObjectPtr& wrappable_get_trait_object(const Wrappable& wrappable) {\n  return wrappable.trait_object;\n}\n\nvoid wrappable_clear_trait_object(Wrappable& wrappable) {\n  wrappable.trait_object = {0, 0, 0, 0};\n}\n\nkj::uint wrappable_strong_refcount(const Wrappable& wrappable) {\n  return wrappable.getStrongRefcount();\n}\n\n// Wrappable lifecycle\nkj::Rc<Wrappable> wrappable_new(TraitObjectPtr ptr) {\n  auto rc = kj::rc<Wrappable>();\n  rc->trait_object = kj::mv(ptr);\n  rc->addStrongRef();\n  return kj::mv(rc);\n}\n\nkj::Rc<Wrappable> wrappable_to_rc(Wrappable& wrappable) {\n  return wrappable.toRc();\n}\n\nvoid wrappable_add_strong_ref(Wrappable& wrappable) {\n  wrappable.addStrongRef();\n}\n\nvoid wrappable_remove_strong_ref(Wrappable& wrappable, bool is_strong) {\n  // maybeDeferDestruction() requires a kj::Own<Wrappable> to take ownership of.\n  // We must temporarily increment the refcount via addRef() to create that handle.\n  //\n  // Refcount accounting:\n  //   addRef(wrappable)      → +1 (creates `own`)\n  //   maybeDeferDestruction  → internally stores `own` in RefToDelete\n  //   ~RefToDelete           → if is_strong: calls removeStrongRef(), then drops `own` → -1\n  // Net effect: 0 (the actual kj::Rc decrement happens later when Rust drops the KjRc).\n  //\n  // is_strong must match the Ref's current strong flag. If GC tracing already transitioned\n  // the ref to weak (strong=false), passing true here would double-decrement strongRefcount.\n  auto own = kj::addRef(wrappable);\n  wrappable.maybeDeferDestruction(is_strong, kj::mv(own), &wrappable);\n}\n\nvoid wrappable_visit_global(GcVisitor* visitor, uintptr_t* global, TracedReference& traced) {\n  auto* gcVisitor = gc_visitor_from_ffi(visitor);\n  auto& strongHandle = *reinterpret_cast<v8::Global<v8::Value>*>(global);\n  auto& tracedHandle = traced_ref_from_ffi(traced);\n  gcVisitor->visit(strongHandle, tracedHandle);\n}\n\nvoid traced_reference_reset(TracedReference& traced) {\n  traced_ref_from_ffi(traced).Reset();\n}\n\nvoid wrappable_visit_ref(\n    Wrappable& wrappable, uintptr_t* ref_parent, bool* ref_strong, GcVisitor* visitor) {\n  auto* gcVisitor = gc_visitor_from_ffi(visitor);\n\n  // Convert opaque uintptr_t to kj::Maybe<Wrappable&>\n  kj::Maybe<::workerd::jsg::Wrappable&> parentMaybe;\n  if (*ref_parent != 0) {\n    parentMaybe = *reinterpret_cast<::workerd::jsg::Wrappable*>(*ref_parent);\n  }\n\n  wrappable.visitRef(*gcVisitor, parentMaybe, *ref_strong);\n\n  // Write back\n  KJ_IF_SOME(p, parentMaybe) {\n    *ref_parent = reinterpret_cast<uintptr_t>(&p);\n  } else {\n    *ref_parent = 0;\n  }\n}\n\n// FunctionCallbackInfo\nIsolate* fci_get_isolate(FunctionCallbackInfo* args) {\n  return args->GetIsolate();\n}\n\nLocal fci_get_this(FunctionCallbackInfo* args) {\n  return to_ffi(args->This());\n}\n\nsize_t fci_get_length(FunctionCallbackInfo* args) {\n  return args->Length();\n}\n\nLocal fci_get_arg(FunctionCallbackInfo* args, size_t index) {\n  return to_ffi((*args)[index]);\n}\n\nvoid fci_set_return_value(FunctionCallbackInfo* args, Local value) {\n  args->GetReturnValue().Set(local_from_ffi<v8::Value>(kj::mv(value)));\n}\n\nGlobal create_resource_template(Isolate* isolate, const ResourceDescriptor& descriptor) {\n  // Construct lazily.\n  v8::EscapableHandleScope scope(isolate);\n\n  v8::Local<v8::FunctionTemplate> constructor;\n  KJ_IF_SOME(descriptor, descriptor.constructor) {\n    constructor = v8::FunctionTemplate::New(isolate,\n        reinterpret_cast<v8::FunctionCallback>(reinterpret_cast<void*>(descriptor.callback)));\n  } else {\n    constructor = v8::FunctionTemplate::New(isolate, &workerd::jsg::throwIllegalConstructor);\n  }\n\n  auto prototype = constructor->PrototypeTemplate();\n\n  // Signatures protect our methods from being invoked with the wrong `this`.\n  auto signature = v8::Signature::New(isolate, constructor);\n\n  auto instance = constructor->InstanceTemplate();\n\n  instance->SetInternalFieldCount(workerd::jsg::Wrappable::INTERNAL_FIELD_COUNT);\n\n  auto classname = ::workerd::jsg::check(v8::String::NewFromUtf8(\n      isolate, descriptor.name.data(), v8::NewStringType::kNormal, descriptor.name.size()));\n\n  if (workerd::jsg::getShouldSetToStringTag(isolate)) {\n    prototype->Set(v8::Symbol::GetToStringTag(isolate), classname, v8::PropertyAttribute::DontEnum);\n  }\n\n  auto internalMarker =\n      v8::Symbol::For(isolate, ::workerd::jsg::v8StrIntern(isolate, \"cloudflare:internal-class\"));\n  prototype->Set(internalMarker, internalMarker,\n      static_cast<v8::PropertyAttribute>(v8::PropertyAttribute::DontEnum |\n          v8::PropertyAttribute::DontDelete | v8::PropertyAttribute::ReadOnly));\n\n  constructor->SetClassName(classname);\n\n  for (const auto& method: descriptor.static_methods) {\n    auto functionTemplate = v8::FunctionTemplate::New(isolate,\n        reinterpret_cast<v8::FunctionCallback>(reinterpret_cast<void*>(method.callback)),\n        v8::Local<v8::Value>(), v8::Local<v8::Signature>(), 0, v8::ConstructorBehavior::kThrow);\n    functionTemplate->RemovePrototype();\n    auto name = ::workerd::jsg::check(v8::String::NewFromUtf8(\n        isolate, method.name.data(), v8::NewStringType::kInternalized, method.name.size()));\n    constructor->Set(name, functionTemplate);\n  }\n\n  for (const auto& method: descriptor.methods) {\n    auto functionTemplate = v8::FunctionTemplate::New(isolate,\n        reinterpret_cast<v8::FunctionCallback>(reinterpret_cast<void*>(method.callback)),\n        v8::Local<v8::Value>(), signature, 0, v8::ConstructorBehavior::kThrow);\n    auto name = ::workerd::jsg::check(v8::String::NewFromUtf8(\n        isolate, method.name.data(), v8::NewStringType::kInternalized, method.name.size()));\n    prototype->Set(name, functionTemplate);\n  }\n\n  for (const auto& constant: descriptor.static_constants) {\n    auto name = ::workerd::jsg::check(v8::String::NewFromUtf8(\n        isolate, constant.name.data(), v8::NewStringType::kInternalized, constant.name.size()));\n    auto value = v8::Number::New(isolate, constant.value);\n\n    // Per Web IDL, constants are {writable: false, enumerable: true, configurable: false}.\n    auto attrs = ::workerd::jsg::getSpecCompliantPropertyAttributes(isolate)\n        ? static_cast<v8::PropertyAttribute>(\n              v8::PropertyAttribute::ReadOnly | v8::PropertyAttribute::DontDelete)\n        : v8::PropertyAttribute::ReadOnly;\n    constructor->Set(name, value, attrs);\n    prototype->Set(name, value, attrs);\n  }\n\n  auto result = scope.Escape(constructor);\n  return to_ffi(v8::Global<v8::FunctionTemplate>(isolate, result));\n}\n\n// FunctionTemplate\nLocal function_template_get_function(Isolate* isolate, const Global& tmpl) {\n  auto& global_tmpl = global_as_ref_from_ffi<v8::FunctionTemplate>(tmpl);\n  auto local_tmpl = v8::Local<v8::FunctionTemplate>::New(isolate, global_tmpl);\n  auto function = ::workerd::jsg::check(local_tmpl->GetFunction(isolate->GetCurrentContext()));\n  return to_ffi(kj::mv(function));\n}\n\n// Realm\nRealm* realm_from_isolate(Isolate* isolate) {\n  auto* realm =\n      static_cast<Realm*>(isolate->GetData(::workerd::jsg::SetDataIndex::SET_DATA_RUST_REALM));\n  KJ_ASSERT(realm != nullptr, \"Rust Realm not set on isolate\");\n  return realm;\n}\n\n// Errors\nLocal exception_create(Isolate* isolate, ExceptionType exception_type, ::rust::Str description) {\n  auto message = ::workerd::jsg::check(v8::String::NewFromUtf8(\n      isolate, description.data(), v8::NewStringType::kInternalized, description.size()));\n  switch (exception_type) {\n    case ExceptionType::RangeError:\n      return to_ffi(v8::Exception::RangeError(message));\n    case ExceptionType::ReferenceError:\n      return to_ffi(v8::Exception::ReferenceError(message));\n    case ExceptionType::SyntaxError:\n      return to_ffi(v8::Exception::SyntaxError(message));\n    case ExceptionType::TypeError:\n      return to_ffi(v8::Exception::TypeError(message));\n    default:\n      // DOM-style exceptions (OperationError, DataError, etc.) and Error fall back to Error.\n      // TODO(soon): Use js.domException() to create proper DOMException objects.\n      return to_ffi(v8::Exception::Error(message));\n  }\n}\n\n// Isolate\nvoid isolate_throw_exception(Isolate* isolate, Local exception) {\n  isolate->ThrowException(local_from_ffi<v8::Value>(kj::mv(exception)));\n}\n\nvoid isolate_throw_error(Isolate* isolate, ::rust::Str description) {\n  auto message = ::workerd::jsg::check(v8::String::NewFromUtf8(\n      isolate, description.data(), v8::NewStringType::kInternalized, description.size()));\n  isolate->ThrowError(message);\n}\n\nvoid isolate_throw_internal_error(Isolate* isolate, ::rust::Str internalMessage) {\n  // Mirrors makeInternalError() from util.c++: generates a unique error ID,\n  // logs the internal message (with ID) to KJ_LOG(ERROR) for Sentry, and\n  // throws a generic \"internal error; reference = <id>\" JS Error to the caller.\n  // kj::heapString(data, size) copies exactly `size` bytes and appends a NUL,\n  // avoiding the out-of-bounds read that kj::StringPtr(data, size) would cause\n  // on non-NUL-terminated Rust &str data.\n  auto message = kj::heapString(internalMessage.data(), internalMessage.size());\n  isolate->ThrowException(::workerd::jsg::makeInternalError(isolate, message));\n}\n\nbool isolate_is_locked(Isolate* isolate) {\n  return v8::Locker::IsLocked(isolate);\n}\n\n}  // namespace workerd::rust::jsg\n"
  },
  {
    "path": "src/rust/jsg/ffi.h",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/jsg/modules.capnp.h>\n#include <workerd/jsg/wrappable.h>\n\n#include <kj-rs/kj-rs.h>\n#include <rust/cxx.h>\n#include <v8.h>\n\n#include <kj/function.h>\n#include <kj/memory.h>\n#include <kj/refcount.h>\n\n// Forward declarations needed by v8.rs.h\nnamespace workerd::rust::jsg {\nusing Isolate = v8::Isolate;\nusing FunctionCallbackInfo = v8::FunctionCallbackInfo<v8::Value>;\nstruct ModuleRegistry;\nstruct Local;\nstruct Global;\nstruct TracedReference;\nstruct Realm;\nstruct GcVisitor;\nenum class ExceptionType : ::std::uint8_t;\n// ModuleType enum is generated by CXX in v8.rs.h - forward declare it here\nenum class ModuleType : ::std::uint8_t;\nusing ModuleCallback = ::rust::Fn<Local(Isolate*)>;\n\n// Mirrors the CXX shared struct defined in v8.rs. Must be kept in sync.\n// The CXX-generated v8.rs.h provides the canonical definition; this is\n// only used when ffi.h is included before v8.rs.h (e.g. from ffi.c++\n// before the CXX bridge is compiled).\n#ifndef CXXBRIDGE1_STRUCT_workerd$rust$jsg$TraitObjectPtr\n#define CXXBRIDGE1_STRUCT_workerd$rust$jsg$TraitObjectPtr\nstruct TraitObjectPtr {\n  uintptr_t data_ptr = 0;\n  uintptr_t vtable_ptr = 0;\n  uintptr_t type_id_lo = 0;\n  uintptr_t type_id_hi = 0;\n};\n#endif\n\n// A Wrappable subclass for Rust objects.\n//\n// Lives on the KJ heap (via kj::Refcounted through Wrappable) and integrates\n// with cppgc via the CppgcShim bridge pattern that Wrappable provides.\n//\n// The data[] field stores a Rust fat pointer (data + vtable) to `dyn GarbageCollected`\n// from a leaked Box<dyn GarbageCollected>. This lets us call back into Rust for tracing\n// and destruction without knowing the concrete type at compile time.\nclass Wrappable: public ::workerd::jsg::Wrappable {\n public:\n  ~Wrappable();\n  void jsgVisitForGc(::workerd::jsg::GcVisitor& visitor) override;\n  kj::StringPtr jsgGetMemoryName() const override;\n  size_t jsgGetMemorySelfSize() const override;\n\n  // Returns a new kj::Rc reference to this Wrappable.\n  kj::Rc<Wrappable> toRc() {\n    return addRefToThis();\n  }\n\n  // Rust trait object pointer + TypeId, stored by Ref::new().\n  // Defined as a CXX shared struct in v8.rs; contains data_ptr, vtable_ptr,\n  // type_id_lo, type_id_hi.\n  TraitObjectPtr trait_object = {0, 0, 0, 0};\n};\n\nstruct ResourceDescriptor;\n\n// Wrappable lifecycle — returns KjRc<Wrappable> to Rust for reference-counted ownership.\n// Ref::clone calls wrappable_add_strong_ref; Ref::drop calls wrappable_remove_strong_ref.\n// wrappable_remove_strong_ref does addRef(+1) + maybeDeferDestruction(-1) = net 0 on kj::Rc;\n// the actual kj::Rc decrement happens when WrappableRc (KjRc<Wrappable>) is dropped.\nkj::Rc<Wrappable> wrappable_new(TraitObjectPtr ptr);\nkj::Rc<Wrappable> wrappable_to_rc(Wrappable& wrappable);\nvoid wrappable_add_strong_ref(Wrappable& wrappable);\nvoid wrappable_remove_strong_ref(Wrappable& wrappable, bool is_strong);\nvoid wrappable_visit_ref(\n    Wrappable& wrappable, uintptr_t* ref_parent, bool* ref_strong, GcVisitor* visitor);\n\n/// Visit a `v8::Global` field during GC tracing, implementing the same\n/// strong↔traced dual-mode switching as `jsg::Data` / `jsg::V8Ref<T>`.\n///\n/// `global` points to the `ptr` field of `ffi::Global` (the strong handle).\n/// `traced` is the `ffi::TracedReference` slot from `Global<T>` on the Rust side.\n/// Both are mutated in-place by this function.\nvoid wrappable_visit_global(GcVisitor* visitor, uintptr_t* global, TracedReference& traced);\n\n/// Resets a `v8::TracedReference`, releasing the weak GC handle.\n/// Must be called when a `Global<T>` is dropped in traced mode to avoid\n/// leaking a live `v8::TracedReference`.\nvoid traced_reference_reset(TracedReference& traced);\n\n// Function declarations\nvoid local_drop(Local value);\nLocal local_clone(const Local& value);\nGlobal local_to_global(Isolate* isolate, Local value);\nLocal local_new_number(Isolate* isolate, double value);\nLocal local_new_string(Isolate* isolate, ::rust::Str value);\nLocal local_new_boolean(Isolate* isolate, bool value);\nLocal local_new_object(Isolate* isolate);\nLocal local_new_null(Isolate* isolate);\nLocal local_new_undefined(Isolate* isolate);\nLocal local_new_array(Isolate* isolate, size_t length);\nLocal local_new_uint8_array(Isolate* isolate, const uint8_t* data, size_t length);\nLocal local_new_uint16_array(Isolate* isolate, const uint16_t* data, size_t length);\nLocal local_new_uint32_array(Isolate* isolate, const uint32_t* data, size_t length);\nLocal local_new_int8_array(Isolate* isolate, const int8_t* data, size_t length);\nLocal local_new_int16_array(Isolate* isolate, const int16_t* data, size_t length);\nLocal local_new_int32_array(Isolate* isolate, const int32_t* data, size_t length);\nLocal local_new_float32_array(Isolate* isolate, const float* data, size_t length);\nLocal local_new_float64_array(Isolate* isolate, const double* data, size_t length);\nLocal local_new_bigint64_array(Isolate* isolate, const int64_t* data, size_t length);\nLocal local_new_biguint64_array(Isolate* isolate, const uint64_t* data, size_t length);\nbool local_eq(const Local& lhs, const Local& rhs);\nbool local_has_value(const Local& val);\nbool local_is_string(const Local& val);\nbool local_is_boolean(const Local& val);\nbool local_is_number(const Local& val);\nbool local_is_null(const Local& val);\nbool local_is_undefined(const Local& val);\nbool local_is_null_or_undefined(const Local& val);\nbool local_is_object(const Local& val);\nbool local_is_native_error(const Local& val);\nbool local_is_array(const Local& val);\nbool local_is_uint8_array(const Local& val);\nbool local_is_uint16_array(const Local& val);\nbool local_is_uint32_array(const Local& val);\nbool local_is_int8_array(const Local& val);\nbool local_is_int16_array(const Local& val);\nbool local_is_int32_array(const Local& val);\nbool local_is_float32_array(const Local& val);\nbool local_is_float64_array(const Local& val);\nbool local_is_bigint64_array(const Local& val);\nbool local_is_biguint64_array(const Local& val);\nbool local_is_array_buffer(const Local& val);\nbool local_is_array_buffer_view(const Local& val);\nbool local_is_function(const Local& val);\n::rust::String local_type_of(Isolate* isolate, const Local& val);\n\n// Local<Function>\nLocal local_function_call(\n    Isolate* isolate, const Local& function, const Local& recv, ::rust::Slice<const Local> args);\n\n// Local<Object>\nvoid local_object_set_property(Isolate* isolate, Local& object, ::rust::Str key, Local value);\nbool local_object_has_property(Isolate* isolate, const Local& object, ::rust::Str key);\nkj::Maybe<Local> local_object_get_property(Isolate* isolate, const Local& object, ::rust::Str key);\n\n// Local<Array>\nuint32_t local_array_length(Isolate* isolate, const Local& array);\nLocal local_array_get(Isolate* isolate, const Local& array, uint32_t index);\nvoid local_array_set(Isolate* isolate, Local& array, uint32_t index, Local value);\n::rust::Vec<Global> local_array_iterate(Isolate* isolate, Local value);\n\n// Local<TypedArray>\nsize_t local_typed_array_length(Isolate* isolate, const Local& array);\nuint8_t local_uint8_array_get(Isolate* isolate, const Local& array, size_t index);\nuint16_t local_uint16_array_get(Isolate* isolate, const Local& array, size_t index);\nuint32_t local_uint32_array_get(Isolate* isolate, const Local& array, size_t index);\nint8_t local_int8_array_get(Isolate* isolate, const Local& array, size_t index);\nint16_t local_int16_array_get(Isolate* isolate, const Local& array, size_t index);\nint32_t local_int32_array_get(Isolate* isolate, const Local& array, size_t index);\nfloat local_float32_array_get(Isolate* isolate, const Local& array, size_t index);\ndouble local_float64_array_get(Isolate* isolate, const Local& array, size_t index);\nint64_t local_bigint64_array_get(Isolate* isolate, const Local& array, size_t index);\nuint64_t local_biguint64_array_get(Isolate* isolate, const Local& array, size_t index);\n\n// Global<T>\nvoid global_reset(Global& value);\nGlobal global_clone(Isolate* isolate, const Global& value);\nLocal global_to_local(Isolate* isolate, const Global& value);\n\n// Wrappable - data access\nconst TraitObjectPtr& wrappable_get_trait_object(const Wrappable& wrappable);\nvoid wrappable_clear_trait_object(Wrappable& wrappable);\nkj::uint wrappable_strong_refcount(const Wrappable& wrappable);\n\n// Wrappers\nLocal wrap_resource(Isolate* isolate, kj::Rc<Wrappable> wrappable, const Global& tmpl);\nvoid wrappable_attach_wrapper(kj::Rc<Wrappable> wrappable, FunctionCallbackInfo& args);\n\n// Unwrappers\n::rust::String unwrap_string(Isolate* isolate, Local value);\nbool unwrap_boolean(Isolate* isolate, Local value);\ndouble unwrap_number(Isolate* isolate, Local value);\nkj::Rc<Wrappable> unwrap_resource(Isolate* isolate, Local value);\n::rust::Vec<uint8_t> unwrap_uint8_array(Isolate* isolate, Local value);\n::rust::Vec<uint16_t> unwrap_uint16_array(Isolate* isolate, Local value);\n::rust::Vec<uint32_t> unwrap_uint32_array(Isolate* isolate, Local value);\n::rust::Vec<int8_t> unwrap_int8_array(Isolate* isolate, Local value);\n::rust::Vec<int16_t> unwrap_int16_array(Isolate* isolate, Local value);\n::rust::Vec<int32_t> unwrap_int32_array(Isolate* isolate, Local value);\n::rust::Vec<float> unwrap_float32_array(Isolate* isolate, Local value);\n::rust::Vec<double> unwrap_float64_array(Isolate* isolate, Local value);\n::rust::Vec<int64_t> unwrap_bigint64_array(Isolate* isolate, Local value);\n::rust::Vec<uint64_t> unwrap_biguint64_array(Isolate* isolate, Local value);\n\n// FunctionCallbackInfo\nIsolate* fci_get_isolate(FunctionCallbackInfo* args);\nLocal fci_get_this(FunctionCallbackInfo* args);\nsize_t fci_get_length(FunctionCallbackInfo* args);\nLocal fci_get_arg(FunctionCallbackInfo* args, size_t index);\nvoid fci_set_return_value(FunctionCallbackInfo* args, Local value);\n\nstruct ModuleRegistry {\n  virtual ~ModuleRegistry() = default;\n  virtual void addBuiltinModule(\n      ::rust::Str specifier, ModuleCallback moduleCallback, ModuleType moduleType) = 0;\n};\n\ninline void register_add_builtin_module(ModuleRegistry& registry,\n    ::rust::Str specifier,\n    ModuleCallback callback,\n    ModuleType moduleType) {\n  registry.addBuiltinModule(specifier, kj::mv(callback), moduleType);\n}\n\nGlobal create_resource_template(Isolate* isolate, const ResourceDescriptor& descriptor);\n\n// FunctionTemplate\nLocal function_template_get_function(Isolate* isolate, const Global& tmpl);\n\n// Realm\nRealm* realm_from_isolate(Isolate* isolate);\n\n// Errors\nLocal exception_create(Isolate* isolate, ExceptionType exception_type, ::rust::Str message);\n\n// Isolate\nvoid isolate_throw_exception(Isolate* isolate, Local exception);\nvoid isolate_throw_error(Isolate* isolate, ::rust::Str message);\nvoid isolate_throw_internal_error(Isolate* isolate, ::rust::Str internalMessage);\nbool isolate_is_locked(Isolate* isolate);\n\n}  // namespace workerd::rust::jsg\n"
  },
  {
    "path": "src/rust/jsg/jsg.h",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/modules-new.h>\n#include <workerd/jsg/modules.h>\n#include <workerd/rust/jsg/ffi-inl.h>\n#include <workerd/rust/jsg/ffi.h>\n#include <workerd/rust/jsg/v8.rs.h>\n\n#include <kj-rs/kj-rs.h>\n#include <rust/cxx.h>\n\n#include <kj/function.h>\n\nnamespace workerd::rust::jsg {\n\n// Adapter for registering Rust-implemented modules into the OLD module registry\n// (jsg::ModuleRegistryImpl). Translates Rust ModuleCallback into ModuleInfo.\ntemplate <typename Registry>\nstruct RustModuleRegistry: public ::workerd::rust::jsg::ModuleRegistry {\n  virtual ~RustModuleRegistry() = default;\n  RustModuleRegistry(Registry& registry): registry(registry) {}\n\n  void addBuiltinModule(\n      ::rust::Str specifier, ModuleCallback moduleCallback, ModuleType moduleType) override {\n    ::workerd::jsg::ModuleType jsgModuleType;\n    switch (moduleType) {\n      case ModuleType::Bundle:\n        jsgModuleType = ::workerd::jsg::ModuleType::BUNDLE;\n        break;\n      case ModuleType::Builtin:\n        jsgModuleType = ::workerd::jsg::ModuleType::BUILTIN;\n        break;\n      case ModuleType::Internal:\n        jsgModuleType = ::workerd::jsg::ModuleType::INTERNAL;\n        break;\n      default:\n        KJ_UNREACHABLE;\n    }\n\n    registry.addBuiltinModule(kj::str(specifier),\n        [kj_specifier = kj::str(specifier), callback = kj::mv(moduleCallback)](\n            ::workerd::jsg::Lock& js, ::workerd::jsg::ModuleRegistry::ResolveMethod,\n            kj::Maybe<const kj::Path&>&) mutable\n        -> kj::Maybe<::workerd::jsg::ModuleRegistry::ModuleInfo> {\n      auto localValue = callback(js.v8Isolate);\n      auto value = local_from_ffi<v8::Value>(kj::mv(localValue));\n      KJ_DASSERT(value->IsObject());\n\n      using ModuleInfo = ::workerd::jsg::ModuleRegistry::ModuleInfo;\n      using ObjectModuleInfo = ::workerd::jsg::ModuleRegistry::ObjectModuleInfo;\n      return kj::Maybe(\n          ModuleInfo(js, kj_specifier, kj::none, ObjectModuleInfo(js, value.As<v8::Object>())));\n    },\n        jsgModuleType);\n  }\n\n  Registry& registry;\n};\n\n// Adapter for registering Rust-implemented modules into the NEW module registry\n// (jsg::modules::ModuleBundle::BuiltinBuilder). Translates Rust ModuleCallback\n// into BuiltinBuilder::addSynthetic calls. Each Rust module callback receives a\n// v8::Isolate* and returns a v8::Local<v8::Value> (the module's default export).\n//\n// Usage:\n//   RustBuiltinModuleAdapter adapter(builder);\n//   ::workerd::rust::api::register_nodejs_modules(adapter);\nstruct RustBuiltinModuleAdapter: public ::workerd::rust::jsg::ModuleRegistry {\n  ::workerd::jsg::modules::ModuleBundle::BuiltinBuilder& builder;\n\n  explicit RustBuiltinModuleAdapter(::workerd::jsg::modules::ModuleBundle::BuiltinBuilder& builder)\n      : builder(builder) {}\n\n  void addBuiltinModule(\n      ::rust::Str specifier, ModuleCallback moduleCallback, ModuleType moduleType) override {\n    // Convert ::rust::Str to kj::ArrayPtr<const char> for URL parsing.\n    auto specifierPtr = kj::ArrayPtr<const char>(specifier.data(), specifier.size());\n    KJ_IF_SOME(url, ::workerd::jsg::Url::tryParse(specifierPtr)) {\n      builder.addSynthetic(url,\n          [callback = kj::mv(moduleCallback)](::workerd::jsg::Lock& js, const ::workerd::jsg::Url&,\n              const ::workerd::jsg::modules::Module::ModuleNamespace& ns,\n              const ::workerd::jsg::CompilationObserver&) mutable {\n        auto localValue = callback(js.v8Isolate);\n        auto value = local_from_ffi<v8::Value>(kj::mv(localValue));\n        KJ_DASSERT(value->IsObject());\n        ns.setDefault(js, ::workerd::jsg::JsValue(value));\n        return true;\n      });\n    } else {\n      KJ_LOG(WARNING, \"Ignoring Rust module with invalid specifier\", specifierPtr);\n    }\n  }\n};\n\n}  // namespace workerd::rust::jsg\n"
  },
  {
    "path": "src/rust/jsg/lib.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nuse std::future::Future;\nuse std::num::ParseIntError;\nuse std::ops::Deref;\n\npub mod feature_flags;\npub mod modules;\npub mod resource;\npub mod v8;\nmod wrappable;\n\npub use feature_flags::FeatureFlags;\npub use resource::Rc;\npub use resource::Resource;\npub use resource::Weak;\npub use v8::BigInt64Array;\npub use v8::BigUint64Array;\npub use v8::Float32Array;\npub use v8::Float64Array;\npub use v8::GcVisitor;\npub use v8::Int8Array;\npub use v8::Int16Array;\npub use v8::Int32Array;\npub use v8::IsolatePtr;\npub use v8::Uint8Array;\npub use v8::Uint16Array;\npub use v8::Uint32Array;\npub use v8::ffi::ExceptionType;\npub use wrappable::FromJS;\npub use wrappable::ToJS;\n\n#[cxx::bridge(namespace = \"workerd::rust::jsg\")]\nmod ffi {\n    extern \"Rust\" {\n        type Realm;\n\n        /// Create a fully-initialized Realm with feature flags.\n        /// `feature_flags_data` is canonical (single-segment, no segment table) Cap'n Proto\n        /// bytes produced by `capnp::canonicalize()` on the C++ side.\n        #[expect(clippy::unnecessary_box_returns)]\n        unsafe fn realm_create(isolate: *mut Isolate, feature_flags_data: &[u8]) -> Box<Realm>;\n    }\n\n    unsafe extern \"C++\" {\n        include!(\"workerd/rust/jsg/ffi.h\");\n\n        type Isolate = crate::v8::ffi::Isolate;\n\n        // Realm\n        pub unsafe fn realm_from_isolate(isolate: *mut Isolate) -> *mut Realm;\n    }\n}\n\npub type Result<T, E = Error> = std::result::Result<T, E>;\n\nimpl From<&str> for ExceptionType {\n    fn from(value: &str) -> Self {\n        match value {\n            \"OperationError\" => Self::OperationError,\n            \"DataError\" => Self::DataError,\n            \"DataCloneError\" => Self::DataCloneError,\n            \"InvalidAccessError\" => Self::InvalidAccessError,\n            \"InvalidStateError\" => Self::InvalidStateError,\n            \"InvalidCharacterError\" => Self::InvalidCharacterError,\n            \"NotSupportedError\" => Self::NotSupportedError,\n            \"SyntaxError\" => Self::SyntaxError,\n            \"TimeoutError\" => Self::TimeoutError,\n            \"TypeMismatchError\" => Self::TypeMismatchError,\n            \"AbortError\" => Self::AbortError,\n            \"NotFoundError\" => Self::NotFoundError,\n            \"TypeError\" => Self::TypeError,\n            \"RangeError\" => Self::RangeError,\n            \"ReferenceError\" => Self::ReferenceError,\n            _ => Self::Error,\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct Error {\n    pub name: ExceptionType,\n    pub message: String,\n}\n\nimpl std::fmt::Display for Error {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}: {}\", self.name, self.message)\n    }\n}\n\n/// Generates constructor methods for each `ExceptionType` variant.\n/// e.g., `new_type_error(\"message\")` creates an Error with `ExceptionType::TypeError`\nmacro_rules! impl_error_constructors {\n    ($($variant:ident => $fn_name:ident),* $(,)?) => {\n        impl Error {\n            $(\n                pub fn $fn_name(message: impl Into<String>) -> Self {\n                    Self {\n                        name: ExceptionType::$variant,\n                        message: message.into(),\n                    }\n                }\n            )*\n        }\n    };\n}\n\nimpl_error_constructors! {\n    OperationError => new_operation_error,\n    DataError => new_data_error,\n    DataCloneError => new_data_clone_error,\n    InvalidAccessError => new_invalid_access_error,\n    InvalidStateError => new_invalid_state_error,\n    InvalidCharacterError => new_invalid_character_error,\n    NotSupportedError => new_not_supported_error,\n    SyntaxError => new_syntax_error,\n    TimeoutError => new_timeout_error,\n    TypeMismatchError => new_type_mismatch_error,\n    AbortError => new_abort_error,\n    NotFoundError => new_not_found_error,\n    TypeError => new_type_error,\n    Error => new_error,\n    RangeError => new_range_error,\n    ReferenceError => new_reference_error,\n}\n\nimpl FromJS for Error {\n    type ResultType = Self;\n\n    /// Creates an Error from a V8 value (typically an exception).\n    ///\n    /// If the value is a native error, extracts the name and message properties.\n    /// Otherwise, converts the value to a string for the message.\n    fn from_js(lock: &mut Lock, value: v8::Local<v8::Value>) -> Result<Self::ResultType, Error> {\n        if value.is_native_error() {\n            let obj: v8::Local<v8::Object> = value.into();\n\n            let name = obj\n                .get(lock, \"name\")\n                .and_then(|v| String::from_js(lock, v).ok());\n\n            let message = obj\n                .get(lock, \"message\")\n                .and_then(|v| String::from_js(lock, v).ok())\n                .unwrap_or_else(|| \"Unknown error\".to_owned());\n\n            Ok(Self {\n                name: name.map_or(ExceptionType::Error, |n| ExceptionType::from(n.as_str())),\n                message,\n            })\n        } else {\n            Err(Self::new_type_error(\"Unknown error\"))\n        }\n    }\n}\n\nimpl Error {\n    pub fn new(name: &str, message: &str) -> Self {\n        Self {\n            name: ExceptionType::from(name),\n            message: message.to_owned(),\n        }\n    }\n\n    /// Creates a V8 exception from this error.\n    pub fn to_local<'a>(&self, isolate: v8::IsolatePtr) -> v8::Local<'a, v8::Value> {\n        // SAFETY: isolate is valid and locked (guaranteed by caller).\n        unsafe {\n            v8::Local::from_ffi(\n                isolate,\n                v8::ffi::exception_create(isolate.as_ffi(), self.name, &self.message),\n            )\n        }\n    }\n}\n\nimpl From<ParseIntError> for Error {\n    fn from(err: ParseIntError) -> Self {\n        Self::new_range_error(format!(\"Failed to parse integer: {err}\"))\n    }\n}\n\n/// A wrapper type that prevents automatic type coercion when unwrapping from JavaScript.\n///\n/// JavaScript automatically coerces types in certain contexts. For instance, when a JavaScript\n/// API expects a string, calling it with the value `null` will result in the null being coerced\n/// into the string value `\"null\"`.\n///\n/// `NonCoercible<T>` can be used to disable automatic type coercion in APIs. For instance,\n/// `NonCoercible<String>` can be used to accept a value only if the input is already a string.\n/// If the input is the value `null`, then an error is thrown rather than silently coercing to\n/// `\"null\"`.\n///\n/// # Supported Types\n///\n/// Any type implementing the [`Type`] trait can be used with `NonCoercible<T>`. Built-in\n/// implementations include:\n///\n/// - `NonCoercible<String>` - only accepts JavaScript strings\n/// - `NonCoercible<bool>` - only accepts JavaScript booleans\n/// - `NonCoercible<Number>` - only accepts JavaScript numbers\n///\n/// # Example\n///\n/// ```ignore\n/// use jsg::NonCoercible;\n///\n/// // This function will only accept actual strings, not values that can be coerced to strings\n/// #[jsg_method]\n/// pub fn process_string(&self, param: NonCoercible<String>) -> Result<(), Error> {\n///     let s: &String = param.as_ref();\n///     // or use Deref: let s: &str = &*param;\n///     // ...\n/// }\n/// ```\n///\n/// # Important Notes\n///\n/// Using `NonCoercible<T>` runs counter to Web IDL and general JavaScript API conventions.\n/// In nearly all cases, APIs should allow coercion to occur and should deal with the coerced\n/// input accordingly to avoid being a source of user confusion. Only use `NonCoercible` if\n/// you have a good reason to disable coercion.\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct NonCoercible<T> {\n    value: T,\n}\n\nimpl<T> NonCoercible<T> {\n    /// Creates a new `NonCoercible` wrapper around the given value.\n    pub fn new(value: T) -> Self {\n        Self { value }\n    }\n\n    /// Consumes the wrapper and returns the inner value.\n    pub fn into_inner(self) -> T {\n        self.value\n    }\n}\n\nimpl<T> From<T> for NonCoercible<T> {\n    fn from(value: T) -> Self {\n        Self::new(value)\n    }\n}\n\nimpl<T> AsRef<T> for NonCoercible<T> {\n    fn as_ref(&self) -> &T {\n        &self.value\n    }\n}\n\nimpl<T> Deref for NonCoercible<T> {\n    type Target = T;\n\n    fn deref(&self) -> &Self::Target {\n        &self.value\n    }\n}\n\n/// A wrapper type for JavaScript numbers (IEEE 754 double-precision floats).\n///\n/// `Number` represents JavaScript's `number` type, which is always a 64-bit\n/// floating-point value. This wrapper type is used instead of raw `f64` to\n/// distinguish between JavaScript numbers and Rust's `f64` type used for\n/// `Float64Array` elements.\n///\n/// # Usage\n///\n/// Use `Number` when you need to accept or return JavaScript numbers in your API:\n///\n/// ```ignore\n/// use jsg::Number;\n///\n/// #[jsg_method]\n/// pub fn add(&self, a: Number, b: Number) -> Number {\n///     Number::new(a.value() + b.value())\n/// }\n/// ```\n///\n/// # Type Mapping\n///\n/// | Rust Type | JavaScript Type |\n/// |-----------|-----------------|\n/// | `jsg::Number` | `number` |\n/// | `f64` | Used for `Float64Array` elements |\n/// | `Vec<f64>` | `Float64Array` |\n#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]\npub struct Number {\n    value: f64,\n}\n\nimpl Number {\n    /// The largest integer that can be represented exactly in JavaScript (2^53 - 1).\n    ///\n    /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)\n    pub const MAX_SAFE_INTEGER: f64 = 9_007_199_254_740_991.0; // 2^53 - 1\n\n    /// The smallest integer that can be represented exactly in JavaScript (-(2^53 - 1)).\n    ///\n    /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER)\n    pub const MIN_SAFE_INTEGER: f64 = -9_007_199_254_740_991.0; // -(2^53 - 1)\n\n    /// Creates a new `Number` from an `f64` value.\n    #[inline]\n    pub fn new(value: f64) -> Self {\n        Self { value }\n    }\n\n    /// Returns the underlying `f64` value.\n    #[inline]\n    pub fn value(&self) -> f64 {\n        self.value\n    }\n\n    /// Consumes the wrapper and returns the inner `f64` value.\n    #[inline]\n    pub fn into_inner(self) -> f64 {\n        self.value\n    }\n\n    /// Determines whether the value is a finite number.\n    ///\n    /// Returns `true` if the value is finite (not `Infinity`, `-Infinity`, or `NaN`).\n    ///\n    /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite)\n    #[inline]\n    pub fn is_finite(&self) -> bool {\n        self.value.is_finite()\n    }\n\n    /// Determines whether the value is an integer.\n    ///\n    /// Returns `true` if the value is finite and has no fractional part.\n    ///\n    /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger)\n    #[inline]\n    #[expect(clippy::float_cmp)] // Exact comparison is correct here - we want trunc(x) == x\n    pub fn is_integer(&self) -> bool {\n        self.value.is_finite() && self.value.trunc() == self.value\n    }\n\n    /// Determines whether the value is `NaN`.\n    ///\n    /// This is more robust than the global `isNaN()` because it doesn't coerce\n    /// the value to a number first.\n    ///\n    /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN)\n    #[inline]\n    pub fn is_nan(&self) -> bool {\n        self.value.is_nan()\n    }\n\n    /// Determines whether the value is a safe integer.\n    ///\n    /// A safe integer is an integer that:\n    /// - Can be exactly represented as an IEEE-754 double precision number\n    /// - Has an IEEE-754 representation that cannot be the result of rounding any other integer\n    ///\n    /// Safe integers range from -(2^53 - 1) to 2^53 - 1, inclusive.\n    ///\n    /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger)\n    #[inline]\n    pub fn is_safe_integer(&self) -> bool {\n        self.is_integer()\n            && self.value >= Self::MIN_SAFE_INTEGER\n            && self.value <= Self::MAX_SAFE_INTEGER\n    }\n}\n\nimpl From<f64> for Number {\n    fn from(value: f64) -> Self {\n        Self::new(value)\n    }\n}\n\nimpl From<Number> for f64 {\n    fn from(num: Number) -> Self {\n        num.value\n    }\n}\n\nimpl From<i32> for Number {\n    fn from(value: i32) -> Self {\n        Self::new(f64::from(value))\n    }\n}\n\nimpl From<u32> for Number {\n    fn from(value: u32) -> Self {\n        Self::new(f64::from(value))\n    }\n}\n\nimpl std::fmt::Display for Number {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self.value)\n    }\n}\n\n/// A wrapper type that accepts `null`, `undefined`, or a value of type `T`.\n///\n/// `Nullable<T>` is similar to `Option<T>` but also accepts `undefined` as a null-ish value.\n/// This is useful for JavaScript APIs where both `null` and `undefined` represent\n/// the absence of a value.\n///\n/// # Behavior\n///\n/// - `null` → `Nullable::Null`\n/// - `undefined` → `Nullable::Undefined`\n/// - `T` → `Nullable::Some(T)`\n///\n/// # Example\n///\n/// ```ignore\n/// use jsg::Nullable;\n///\n/// #[jsg_method]\n/// pub fn process(&self, value: Nullable<String>) -> Result<(), Error> {\n///     match value {\n///         Nullable::Some(s) => println!(\"Got value: {}\", s),\n///         Nullable::Null => println!(\"Got null\"),\n///         Nullable::Undefined => println!(\"Got undefined\"),\n///     }\n///     Ok(())\n/// }\n/// ```\n#[derive(Debug, Clone, PartialEq, Eq)]\npub enum Nullable<T> {\n    Some(T),\n    Null,\n    Undefined,\n}\n\nimpl<T> Nullable<T> {\n    /// Returns `true` if the nullable contains a value.\n    pub fn is_some(&self) -> bool {\n        matches!(self, Self::Some(_))\n    }\n\n    /// Returns `true` if the nullable is `Null`.\n    pub fn is_null(&self) -> bool {\n        matches!(self, Self::Null)\n    }\n\n    /// Returns `true` if the nullable is `Undefined`.\n    pub fn is_undefined(&self) -> bool {\n        matches!(self, Self::Undefined)\n    }\n\n    /// Returns `true` if the nullable is `Null` or `Undefined`.\n    pub fn is_null_or_undefined(&self) -> bool {\n        matches!(self, Self::Null | Self::Undefined)\n    }\n\n    /// Returns a reference to the contained value, or `None` if null or undefined.\n    pub fn as_ref(&self) -> Option<&T> {\n        match self {\n            Self::Some(v) => Some(v),\n            Self::Null | Self::Undefined => None,\n        }\n    }\n}\n\nimpl<T> From<Option<T>> for Nullable<T> {\n    fn from(opt: Option<T>) -> Self {\n        match opt {\n            Some(v) => Self::Some(v),\n            None => Self::Null,\n        }\n    }\n}\n\nimpl<T> From<Nullable<T>> for Option<T> {\n    fn from(nullable: Nullable<T>) -> Self {\n        match nullable {\n            Nullable::Some(v) => Some(v),\n            Nullable::Null | Nullable::Undefined => None,\n        }\n    }\n}\n\n/// Proof that the V8 isolate is valid and exclusively locked by the current thread.\n///\n/// A `Lock` instance can only be created when the C++ `jsg::Lock` (or equivalent)\n/// is held, meaning:\n/// - The `v8::Isolate` pointer is valid and has not been disposed.\n/// - The current thread holds the isolate lock (`v8::Locker` is active).\n/// - No other thread can enter the isolate concurrently.\n///\n/// `Lock` is passed to resource methods and callbacks to perform V8 operations\n/// like creating objects, wrapping values, and accessing the `Realm`. It is\n/// analogous to `jsg::Lock&` in C++ JSG.\n///\n/// `Lock` is neither `Send` nor `Sync` — it cannot escape the thread that\n/// created it.\npub struct Lock {\n    isolate: v8::IsolatePtr,\n}\n\nimpl Lock {\n    /// # Safety\n    /// The caller must ensure that `args` is a valid pointer to `FunctionCallbackInfo`.\n    pub unsafe fn from_args(args: *mut v8::ffi::FunctionCallbackInfo) -> Self {\n        // SAFETY: args is a valid FunctionCallbackInfo pointer (guaranteed by caller).\n        unsafe { Self::from_isolate_ptr(v8::ffi::fci_get_isolate(args)) }\n    }\n\n    /// Creates a Lock from a raw isolate pointer.\n    ///\n    /// # Safety\n    /// The caller must ensure that `isolate` is a valid pointer to an `Isolate`.\n    ///\n    /// # Panics\n    /// Panics if the isolate is not currently locked.\n    pub unsafe fn from_isolate_ptr(isolate: *mut v8::ffi::Isolate) -> Self {\n        // SAFETY: isolate pointer is valid (guaranteed by caller).\n        let isolate = unsafe { v8::IsolatePtr::from_ffi(isolate) };\n        // SAFETY: Lock guarantees the isolate is valid\n        assert!(unsafe { isolate.is_locked() });\n        Self { isolate }\n    }\n\n    /// Returns the isolate associated with this lock.\n    pub fn isolate(&self) -> v8::IsolatePtr {\n        self.isolate\n    }\n\n    pub fn new_object<'a>(&mut self) -> v8::Local<'a, v8::Object> {\n        // SAFETY: Lock guarantees the isolate is valid and locked\n        unsafe {\n            v8::Local::from_ffi(\n                self.isolate(),\n                v8::ffi::local_new_object(self.isolate().as_ffi()),\n            )\n        }\n    }\n\n    pub fn throw_error(&mut self, message: &str) {\n        // SAFETY: Lock guarantees the isolate is valid and locked\n        unsafe { v8::ffi::isolate_throw_error(self.isolate().as_ffi(), message) }\n    }\n\n    pub fn await_io<F, C, I, R>(self, _fut: F, _callback: C) -> Result<R>\n    where\n        F: Future<Output = I>,\n        C: FnOnce(Self, I) -> Result<R>,\n    {\n        unimplemented!(\"Lock::await_io is not yet implemented for Rust resources\")\n    }\n\n    pub(crate) fn realm(&mut self) -> &mut Realm {\n        // SAFETY: isolate is valid and locked (guaranteed by Lock); Realm pointer is valid.\n        unsafe { &mut *crate::ffi::realm_from_isolate(self.isolate().as_ffi()) }\n    }\n\n    /// Returns the current worker's compatibility flags reader.\n    ///\n    /// ```ignore\n    /// if lock.feature_flags().get_node_js_compat() {\n    ///     // Node.js compatibility behavior\n    /// }\n    /// ```\n    pub fn feature_flags(&mut self) -> compatibility_date_capnp::compatibility_flags::Reader<'_> {\n        self.realm().feature_flags.reader()\n    }\n\n    /// Throws an error as a V8 exception.\n    pub fn throw_exception(&mut self, err: &Error) {\n        // SAFETY: isolate is valid and locked (guaranteed by Lock).\n        unsafe {\n            v8::ffi::isolate_throw_exception(\n                self.isolate().as_ffi(),\n                err.to_local(self.isolate()).into_ffi(),\n            );\n        }\n    }\n\n    /// Throws an internal error as a V8 exception, matching `makeInternalError()` in C++.\n    ///\n    /// Generates a unique error reference ID, logs `internal_message` with the ID via\n    /// `KJ_LOG(ERROR)` (reaching Sentry), and schedules a generic\n    /// `\"Error: internal error; reference = <id>\"` JS exception on the isolate so the\n    /// internal message is never exposed to JavaScript.\n    pub fn throw_internal_error(&mut self, internal_message: &str) {\n        // SAFETY: isolate is valid and locked (guaranteed by Lock).\n        unsafe {\n            v8::ffi::isolate_throw_internal_error(self.isolate().as_ffi(), internal_message);\n        }\n    }\n}\n\n/// Provides metadata about Rust types exposed to JavaScript.\n///\n/// This trait provides type information used for error messages, memory tracking,\n/// and type validation (for `NonCoercible<T>`). The actual conversion logic is in\n/// `ToJS` (Rust → JS) and `FromJS` (JS → Rust).\n///\n/// TODO: Implement `memory_info(jsg::MemoryTracker)`\npub trait Type: Sized {\n    /// The JavaScript class name for this type (used in error messages).\n    fn class_name() -> &'static str;\n\n    /// Same as jsgGetMemorySelfSize\n    fn memory_self_size() -> usize {\n        std::mem::size_of::<Self>()\n    }\n\n    /// Returns true if the V8 value is exactly this type (no coercion).\n    /// Used by `NonCoercible<T>` to reject values that would require coercion.\n    fn is_exact(value: &v8::Local<v8::Value>) -> bool;\n}\n\n/// Represents a constant value that can be exposed to JavaScript.\npub enum ConstantValue {\n    Number(f64),\n}\n\nmacro_rules! impl_constant_value_from_lossless {\n    ($($t:ty),*) => {\n        $(\n            impl From<$t> for ConstantValue {\n                fn from(v: $t) -> Self {\n                    Self::Number(f64::from(v))\n                }\n            }\n        )*\n    };\n}\n\nimpl_constant_value_from_lossless!(i8, i16, i32, u8, u16, u32, f32, f64);\n\n// i64/u64 can lose precision in f64 (52-bit mantissa), but JavaScript numbers are\n// always f64 so this is inherent to the language boundary.\nimpl From<i64> for ConstantValue {\n    #[expect(clippy::cast_precision_loss)]\n    fn from(v: i64) -> Self {\n        Self::Number(v as f64)\n    }\n}\n\nimpl From<u64> for ConstantValue {\n    #[expect(clippy::cast_precision_loss)]\n    fn from(v: u64) -> Self {\n        Self::Number(v as f64)\n    }\n}\n\npub enum Member {\n    Constructor {\n        callback: unsafe extern \"C\" fn(*mut v8::ffi::FunctionCallbackInfo),\n    },\n    Method {\n        name: String,\n        callback: unsafe extern \"C\" fn(*mut v8::ffi::FunctionCallbackInfo),\n    },\n    Property {\n        name: String,\n        getter_callback: unsafe extern \"C\" fn(*mut v8::ffi::FunctionCallbackInfo),\n        setter_callback: unsafe extern \"C\" fn(*mut v8::ffi::FunctionCallbackInfo),\n    },\n    StaticMethod {\n        name: String,\n        callback: unsafe extern \"C\" fn(*mut v8::ffi::FunctionCallbackInfo),\n    },\n    StaticConstant {\n        name: String,\n        value: ConstantValue,\n    },\n}\n\n/// Trait for types that participate in V8 garbage collection tracing.\n///\n/// Implementors can report nested GC-visible references via [`GcVisitor`] and\n/// optionally provide a class name for heap snapshot tooling.\npub trait GarbageCollected {\n    /// Trace nested GC-visible references. Called during V8 GC marking.\n    fn trace(&self, _visitor: &mut GcVisitor);\n\n    /// Class name for heap snapshots / debugging.\n    ///\n    /// Returns a `&'static CStr` — always a compile-time literal. This lets the C++ side\n    /// construct a `kj::StringPtr` directly from the NUL-terminated pointer without any\n    /// allocation or caching. Implementations must not access `self`.\n    fn memory_name(&self) -> &'static std::ffi::CStr;\n}\n\n/// Rust types that are deep-copied into JavaScript as value types.\n///\n/// Unlike resource types, struct types are copied entirely into JavaScript objects with no\n/// further Rust involvement after wrapping. This is analogous to `JSG_STRUCT` in C++ JSG.\npub trait Struct: Type {}\n\n/// Per-isolate state for Rust resources exposed to JavaScript.\n///\n/// A Realm is created for each V8 isolate and stored in the isolate's data slot. It holds\n/// cached function templates and tracks resource instances. When all Rust `Ref` handles to a\n/// resource are dropped and no JS references remain, V8 GC collects the wrapper and the\n/// `CppgcShim` destruction triggers cleanup of the underlying `Wrappable`.\npub struct Realm {\n    isolate: v8::IsolatePtr,\n    pub(crate) resources: resource::Resources,\n    /// Parsed `CompatibilityFlags` capnp message, initialized at construction.\n    feature_flags: FeatureFlags,\n}\n\nimpl Realm {\n    /// Creates a new Realm with its feature flags.\n    pub fn new(isolate: v8::IsolatePtr, feature_flags: FeatureFlags) -> Self {\n        Self {\n            isolate,\n            resources: resource::Resources::default(),\n            feature_flags,\n        }\n    }\n\n    pub fn isolate(&self) -> v8::IsolatePtr {\n        self.isolate\n    }\n}\n\nimpl Drop for Realm {\n    fn drop(&mut self) {\n        debug_assert!(\n            // SAFETY: isolate pointer is valid (guaranteed by Realm construction).\n            unsafe { self.isolate.is_locked() },\n            \"Realm must be dropped while holding the isolate lock\"\n        );\n    }\n}\n\n#[expect(clippy::unnecessary_box_returns)]\nunsafe fn realm_create(isolate: *mut v8::ffi::Isolate, feature_flags_data: &[u8]) -> Box<Realm> {\n    let feature_flags = FeatureFlags::from_bytes(feature_flags_data);\n    // SAFETY: isolate pointer is valid (guaranteed by C++ caller).\n    unsafe { Box::new(Realm::new(v8::IsolatePtr::from_ffi(isolate), feature_flags)) }\n}\n\n/// Executes `f`, catching any panic and converting it to a JS internal error.\n///\n/// `extern \"C\"` V8 callbacks generated by `jsg-macros` call this so that a\n/// Rust panic is handled the same way as `KJ_ASSERT(false)` in C++ JSG\n/// handlers: the internal panic message is logged via `KJ_LOG(ERROR)` (reaching\n/// Sentry) and the JS caller receives a generic\n/// `\"Error: internal error; reference = <id>\"` exception with a unique\n/// reference ID, keeping the internal message out of JS-visible output.\n#[doc(hidden)]\npub fn catch_panic<F: FnOnce()>(lock: &mut Lock, f: F) {\n    // SAFETY: `f` is called from an `extern \"C\"` V8 callback generated by\n    // jsg-macros. Raw pointers captured by `f` are valid for the entire\n    // duration of the callback (V8 guarantees this), and V8 is\n    // single-threaded, so there is no aliasing hazard on unwind.\n    // `AssertUnwindSafe` is therefore correct here.\n    if let Err(payload) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {\n        let msg = if let Some(s) = payload.downcast_ref::<&str>() {\n            *s\n        } else if let Some(s) = payload.downcast_ref::<String>() {\n            s.as_str()\n        } else {\n            \"<non-string panic payload>\"\n        };\n        lock.throw_internal_error(msg);\n    }\n}\n"
  },
  {
    "path": "src/rust/jsg/modules.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nuse std::pin::Pin;\n\npub use ffi::ModuleType;\n\nuse crate::v8::ffi;\n\n/// Registers a builtin module with the given specifier and module type.\n///\n/// The callback is invoked when JavaScript imports the module, receiving the isolate pointer\n/// and returning a V8 Local handle to the module's exports object.\npub fn add_builtin(\n    registry: Pin<&mut ffi::ModuleRegistry>,\n    specifier: &str,\n    callback: fn(*mut ffi::Isolate) -> ffi::Local,\n    module_type: ModuleType,\n) {\n    // SAFETY: registry is a valid pinned ModuleRegistry; specifier and callback are valid.\n    unsafe {\n        ffi::register_add_builtin_module(registry, specifier, callback, module_type);\n    }\n}\n"
  },
  {
    "path": "src/rust/jsg/resource.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nuse std::any::TypeId;\nuse std::cell::Cell;\nuse std::collections::HashMap;\nuse std::fmt;\nuse std::ops::Deref;\nuse std::ptr::NonNull;\n\nuse kj_rs::KjMaybe;\n\nuse crate::ConstantValue;\nuse crate::Error;\nuse crate::FromJS;\nuse crate::GarbageCollected;\nuse crate::Lock;\nuse crate::Member;\nuse crate::ToJS;\nuse crate::Type;\nuse crate::v8;\nuse crate::v8::ffi::Wrappable;\n\n/// A reference-counted smart pointer to a Rust resource that integrates with\n/// V8's garbage collector.\n///\n/// Named `Rc` to mirror both [`std::rc::Rc`] (reference-counted shared ownership)\n/// and C++ `jsg::Ref` (the GC-integrated reference type in workerd's C++ JSG layer).\n/// Like `std::rc::Rc`, cloning is cheap (increments a refcount). Unlike `std::rc::Rc`,\n/// the destructor also notifies the GC so JavaScript wrappers can be collected.\n///\n/// Each `Rc<R>` holds:\n/// - An `std::rc::Rc<R>` for Rust-side shared ownership (enables `Weak`)\n/// - A [`v8::WrappableRc`] for `kj::Rc` refcounting and GC integration\n///\n/// **GC integration:**\n/// - On `Clone`, calls `wrappable_add_strong_ref` (C++ `addStrongRef`).\n/// - On `Drop`, calls `wrappable_remove_strong_ref` with the ref's current\n///   `strong` flag (C++ `maybeDeferDestruction`), then fields drop in\n///   declaration order.\npub struct Rc<R: Resource> {\n    /// Shared ownership of the Rust resource.\n    handle: std::rc::Rc<R>,\n    /// Owned, reference-counted handle to the C++ Wrappable.\n    wrappable: v8::WrappableRc,\n    /// Per-Rc GC state: pointer to the parent `Wrappable`.\n    /// `None` until first visited. Set by `visitRef` during GC tracing.\n    parent: Cell<Option<NonNull<Wrappable>>>,\n    /// Per-Rc GC state: whether this ref is currently strong.\n    /// Starts `true` (addStrongRef was called). Toggled by `visitRef` during GC tracing.\n    strong: Cell<bool>,\n}\n\nimpl<R: Resource> Rc<R> {\n    /// Creates a new `Rc<R>` that ties a Rust resource's lifetime to the\n    /// JavaScript virtual machine's garbage collector.\n    ///\n    /// This is the primary way to create a Rust resource that can be exposed\n    /// to JavaScript. The returned `Rc` can then be converted to a JS object\n    /// via [`ToJS::to_js`](crate::ToJS::to_js).\n    ///\n    /// The resource stays alive as long as at least one `Rc` exists **or** a\n    /// JavaScript wrapper object is reachable from the JS heap. When all Rust\n    /// `Rc`s are dropped and the JS wrapper becomes unreachable, V8's garbage\n    /// collector will destroy the resource.\n    pub fn new(resource: R) -> Self {\n        let handle = std::rc::Rc::new(resource);\n\n        // Leak an Rc clone as a fat pointer to dyn GarbageCollected and hand\n        // ownership to a new Wrappable on the KJ heap. Since R: GarbageCollected,\n        // the vtable dispatches trace/get_name directly to R's implementations.\n        let raw: *const R = std::rc::Rc::into_raw(std::rc::Rc::clone(&handle));\n        let fat: *mut dyn GarbageCollected = raw as *const dyn GarbageCollected as *mut _;\n        let trait_object = v8::ffi::TraitObjectPtr::from_raw(fat, std::any::TypeId::of::<R>());\n        let wrappable: v8::WrappableRc = trait_object.into();\n\n        Self {\n            handle,\n            wrappable,\n            parent: Cell::new(None),\n            strong: Cell::new(true),\n        }\n    }\n\n    /// Visits this Rc during GC tracing.\n    ///\n    /// Delegates to C++ `Wrappable::visitRef()` which handles strong/traced switching\n    /// and transitive tracing.\n    ///\n    /// Takes `&self` because `GarbageCollected::trace(&self)` receives a shared\n    /// reference to `R` inside the `Rc` allocation. The `parent` and `strong`\n    /// fields already use `Cell` for interior mutability.\n    /// `WrappableRc::visit_rc(&self)` produces `Pin<&mut Wrappable>` from\n    /// the raw pointer inside `KjRc` — the mutation target is the C++ heap\n    /// object, not the `WrappableRc` wrapper itself.\n    pub(crate) fn visit(&self, visitor: &mut v8::GcVisitor) {\n        self.wrappable.visit_rc(\n            self.parent.as_ptr().cast::<usize>(),\n            self.strong.as_ptr(),\n            visitor,\n        );\n    }\n\n    /// Creates a [`Weak`] reference to this resource.\n    ///\n    /// The weak reference does not prevent GC collection. Use\n    /// [`Weak::upgrade`] to obtain a strong `Rc<R>` if the resource is\n    /// still alive.\n    ///\n    /// Mirrors [`std::rc::Rc::downgrade`](std::rc::std::rc::Rc::downgrade).\n    pub fn downgrade(&self) -> Weak<R> {\n        Weak::from(self)\n    }\n\n    /// Attaches this resource to the `this` object in a V8 constructor callback.\n    ///\n    /// Called from `#[jsg_constructor]`-generated code. V8 has already created\n    /// the `this` object from the `FunctionTemplate`'s `InstanceTemplate`;\n    /// this method attaches the `Wrappable` to it so that instance methods\n    /// can resolve the resource via `resolve_resource`.\n    pub fn attach_to_this(&self, info: &mut v8::FunctionCallbackInfo) {\n        self.wrappable.attach_to_this(info);\n    }\n\n    /// Returns the C++ Wrappable's strong reference count.\n    #[cfg(debug_assertions)]\n    pub fn strong_refcount(&self) -> u32 {\n        self.wrappable.strong_refcount()\n    }\n}\n\nimpl<R: Resource> Deref for Rc<R> {\n    type Target = R;\n\n    #[inline]\n    fn deref(&self) -> &Self::Target {\n        &self.handle\n    }\n}\n\nimpl<R: Resource> Clone for Rc<R> {\n    fn clone(&self) -> Self {\n        let mut wrappable = self.wrappable.clone();\n        wrappable.add_strong_ref();\n        Self {\n            handle: std::rc::Rc::clone(&self.handle),\n            wrappable,\n            parent: Cell::new(None),\n            strong: Cell::new(true),\n        }\n    }\n}\n\nimpl<R: Resource> Drop for Rc<R> {\n    fn drop(&mut self) {\n        self.wrappable.remove_strong_ref(self.strong.get());\n    }\n}\n\nimpl<R: Resource + 'static> ToJS for Rc<R> {\n    fn to_js<'a, 'b>(self, lock: &'a mut Lock) -> v8::Local<'b, v8::Value>\n    where\n        'b: 'a,\n    {\n        wrap(lock, self)\n    }\n}\n\nimpl<R: Resource + 'static> FromJS for Rc<R> {\n    type ResultType = Self;\n\n    fn from_js(lock: &mut Lock, value: v8::Local<v8::Value>) -> Result<Self, Error> {\n        // Capture the JS type name before consuming the value, for error messages.\n        let type_name = value.type_of();\n\n        let mut wrappable = v8::WrappableRc::from_js(lock.isolate(), value).ok_or_else(|| {\n            Error::new_type_error(format!(\"expected {}, got {type_name}\", R::class_name()))\n        })?;\n\n        let resource_ptr = wrappable.resolve_resource::<R>().ok_or_else(|| {\n            Error::new_type_error(format!(\"expected {}, got {type_name}\", R::class_name()))\n        })?;\n\n        // SAFETY: The pointer came from std::rc::Rc::into_raw in Rc::new(), and the\n        // Wrappable being alive guarantees the Rc allocation is still valid.\n        // increment_strong_count bumps the count so from_raw can safely create\n        // a second Rc handle without double-freeing.\n        let handle = unsafe {\n            std::rc::Rc::increment_strong_count(resource_ptr.as_ptr());\n            std::rc::Rc::from_raw(resource_ptr.as_ptr())\n        };\n\n        wrappable.add_strong_ref();\n        Ok(Self {\n            handle,\n            wrappable,\n            parent: Cell::new(None),\n            strong: Cell::new(true),\n        })\n    }\n}\n\nimpl<R: Resource> fmt::Debug for Rc<R> {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"Rc\")\n            .field(\"type\", &std::any::type_name::<R>())\n            .field(\"rc_strong\", &std::rc::Rc::strong_count(&self.handle))\n            .finish_non_exhaustive()\n    }\n}\n\n/// A weak reference to a resource that doesn't prevent resource destruction.\n///\n/// Uses `Weak<R>` for liveness detection: when all `Rc`s drop and\n/// `~Wrappable` drops the `Rc` (via `std::rc::Rc::from_raw`), the `std::rc::Rc<R>` reaches\n/// 0 and all `Weak`s expire.\n///\n/// Does NOT hold a `WrappableRc` — does not keep the `kj::std::rc::Rc<Wrappable>` alive.\n/// Stores a non-owning pointer to the Wrappable so that `upgrade()` can\n/// reconstruct a `WrappableRc` via `wrappable_to_rc`. The pointer is only\n/// dereferenced after `Weak::upgrade` confirms the resource (and thus the\n/// Wrappable) is still alive.\npub struct Weak<R: Resource> {\n    weak: std::rc::Weak<R>,\n    /// Non-owning pointer to the C++ Wrappable. `None` for default-constructed\n    /// `Weak`s (which can never be upgraded). Valid as long as the resource\n    /// is alive (verified by `Weak::upgrade` before use).\n    wrappable: Option<NonNull<Wrappable>>,\n}\n\nimpl<R: Resource> Weak<R> {\n    /// Returns `true` if the resource is still alive.\n    #[inline]\n    pub fn is_alive(&self) -> bool {\n        self.weak.strong_count() > 0\n    }\n\n    /// Upgrades to a strong `Rc<R>` if the resource is still alive.\n    pub fn upgrade(&self) -> Option<Rc<R>> {\n        let handle = self.weak.upgrade()?;\n        let wrappable_ptr = self.wrappable?;\n        // Reconstruct a WrappableRc from the stored wrappable pointer.\n        // SAFETY: The resource is alive (Weak::upgrade succeeded), so the Wrappable\n        // is alive too (the Wrappable's Rc clone keeps the resource alive, which\n        // means the Wrappable must still exist — its destruction via std::rc::Rc::from_raw\n        // would have released the Rc and thus the resource).\n        let mut wrappable = unsafe { v8::WrappableRc::from_raw_wrappable(wrappable_ptr.as_ptr()) };\n        wrappable.add_strong_ref();\n        Some(Rc {\n            handle,\n            wrappable,\n            parent: Cell::new(None),\n            strong: Cell::new(true),\n        })\n    }\n}\n\nimpl<R: Resource> From<&Rc<R>> for Weak<R> {\n    fn from(r: &Rc<R>) -> Self {\n        Self {\n            weak: std::rc::Rc::downgrade(&r.handle),\n            wrappable: Some(r.wrappable.as_ptr()),\n        }\n    }\n}\n\nimpl<R: Resource> Clone for Weak<R> {\n    fn clone(&self) -> Self {\n        Self {\n            weak: std::rc::Weak::clone(&self.weak),\n            wrappable: self.wrappable,\n        }\n    }\n}\n\nimpl<R: Resource> GarbageCollected for Weak<R> {\n    /// No-op: weak references don't keep the target alive and have no GC edges to trace.\n    fn trace(&self, _visitor: &mut v8::GcVisitor) {}\n\n    fn memory_name(&self) -> &'static std::ffi::CStr {\n        // jsgGetMemoryName is only called on live Wrappables, never on Weak<R>.\n        // Delegate to the concrete R via a live upgrade. In the (unreachable)\n        // case where the referent is already gone, return an empty string.\n        self.weak.upgrade().as_deref().map_or(c\"\", R::memory_name)\n    }\n}\n\nimpl<R: Resource> Default for Weak<R> {\n    fn default() -> Self {\n        Self {\n            weak: std::rc::Weak::new(),\n            wrappable: None,\n        }\n    }\n}\n\n/// Wraps a `Rc<R>` as a JavaScript object backed by the resource's `FunctionTemplate`.\n///\n/// **Identity**: Multiple calls with `Rc`s that share the same underlying `Wrappable`\n/// (i.e. clones of the same `Rc`) return the **same** JS object — V8 caches the\n/// wrapper on the `Wrappable` via `CppgcShim`. This means:\n///\n/// ```text\n/// let a = resource.clone().to_js(lock);\n/// let b = resource.to_js(lock);\n/// // a === b in JavaScript (same object identity)\n/// ```\n///\n/// **Prototype**: The returned object's prototype is set up from the resource's\n/// [`FunctionTemplate`], which includes all `#[jsg_method]` instance methods and\n/// `#[jsg_static_constant]` values registered via [`Resource::members()`].\n///\n/// **Ownership**: The `Rc` is consumed. The JS wrapper keeps the `Wrappable`\n/// alive via `CppgcShim`. When the JS object is garbage collected and all Rust\n/// `Rc`s are dropped, the resource is destroyed.\n#[expect(clippy::needless_pass_by_value)]\npub fn wrap<'a, R: Resource + 'static>(\n    lock: &mut Lock,\n    resource: Rc<R>,\n) -> v8::Local<'a, v8::Value> {\n    let isolate = lock.isolate();\n    let constructor = lock.realm().resources.get_constructor::<R>(isolate);\n\n    resource.wrappable.to_js(isolate, constructor)\n}\n\n/// Returns the constructor function for a resource type as a `Local<Function>`.\n///\n/// Lazily creates and caches the `FunctionTemplate` in the `Realm` on first call.\n/// The returned function can be exposed as a global (e.g., `ctx.set_global(\"MyClass\", func)`).\npub fn function_template_of<'a, R: Resource + 'static>(\n    lock: &mut Lock,\n) -> v8::Local<'a, v8::Function> {\n    let isolate = lock.isolate();\n    let constructor = lock.realm().resources.get_constructor::<R>(isolate);\n    // SAFETY: `isolate` is valid and locked (Lock invariant). `constructor` is a valid\n    // Global<FunctionTemplate> cached in the Realm. Inlined from `as_local_function` to\n    // avoid re-borrowing `self` while `constructor` holds an immutable borrow through `realm()`.\n    unsafe {\n        v8::Local::from_ffi(\n            isolate,\n            v8::ffi::function_template_get_function(isolate.as_ffi(), constructor.as_ffi_ref()),\n        )\n    }\n}\n\n/// Builds a [`ResourceDescriptor`] from `R`'s [`Resource::members()`] list.\n/// The descriptor is passed to C++ `create_resource_template` to set up the\n/// V8 `FunctionTemplate` with the correct methods, constants, and constructor.\nfn get_resource_descriptor<R: Resource>() -> v8::ffi::ResourceDescriptor {\n    let mut descriptor = v8::ffi::ResourceDescriptor {\n        name: R::class_name().to_owned(),\n        constructor: KjMaybe::None,\n        methods: Vec::new(),\n        static_methods: Vec::new(),\n        static_constants: Vec::new(),\n    };\n\n    for m in R::members() {\n        match m {\n            Member::Constructor { callback } => {\n                descriptor.constructor = KjMaybe::Some(v8::ffi::ConstructorDescriptor {\n                    callback: callback as usize,\n                });\n            }\n            Member::Method { name, callback } => {\n                descriptor.methods.push(v8::ffi::MethodDescriptor {\n                    name,\n                    callback: callback as usize,\n                });\n            }\n            Member::Property {\n                name: _,\n                getter_callback: _,\n                setter_callback: _,\n            } => unimplemented!(\"#[jsg_property] is not yet supported on Rust resources\"),\n            Member::StaticMethod { name, callback } => {\n                descriptor.static_methods.push(v8::ffi::MethodDescriptor {\n                    name,\n                    callback: callback as usize,\n                });\n            }\n            Member::StaticConstant { name, value } => {\n                let ConstantValue::Number(number_value) = value;\n                descriptor\n                    .static_constants\n                    .push(v8::ffi::StaticConstantDescriptor {\n                        name,\n                        value: number_value,\n                    });\n            }\n        }\n    }\n\n    descriptor\n}\n\n#[derive(Default)]\npub struct Resources {\n    /// Cached V8 `FunctionTemplate`s keyed by resource `TypeId`.\n    templates: HashMap<TypeId, v8::Global<v8::FunctionTemplate>>,\n}\n\nimpl Resources {\n    /// Gets or creates the cached `FunctionTemplate` for a resource type.\n    pub fn get_constructor<R: Resource + 'static>(\n        &mut self,\n        isolate: v8::IsolatePtr,\n    ) -> &v8::Global<v8::FunctionTemplate> {\n        self.templates\n            .entry(TypeId::of::<R>())\n            .or_insert_with(|| Self::create_resource_constructor::<R>(isolate))\n    }\n\n    /// Creates a new V8 `FunctionTemplate` for resource type `R` via the C++ FFI.\n    fn create_resource_constructor<R: Resource>(\n        isolate: v8::IsolatePtr,\n    ) -> v8::Global<v8::FunctionTemplate> {\n        // SAFETY: Caller guarantees the isolate is valid and locked.\n        unsafe {\n            v8::ffi::create_resource_template(isolate.as_ffi(), &get_resource_descriptor::<R>())\n                .into()\n        }\n    }\n}\n\n/// Rust types exposed to JavaScript as resource types.\n///\n/// Resource types are passed by reference and call back into Rust when JavaScript accesses\n/// their members. This is analogous to `JSG_RESOURCE_TYPE` in C++ JSG. Resources must provide\n/// member declarations, a cleanup function for GC, and access to their V8 wrapper state.\npub trait Resource: Type + GarbageCollected + Sized + 'static {\n    /// Returns the list of methods, properties, and constructors exposed to JavaScript.\n    fn members() -> Vec<Member>\n    where\n        Self: Sized;\n}\n"
  },
  {
    "path": "src/rust/jsg/v8.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n//! V8 JavaScript engine bindings and garbage collector integration.\n//!\n//! This module provides Rust wrappers for V8 types and integration with the C++ `Wrappable`\n//! garbage collection system used by workerd.\n//!\n//! # Core Types\n//!\n//! - [`IsolatePtr`] - Non-null wrapper around `v8::Isolate*`; callers must still\n//!   ensure the isolate is alive and the current thread holds the isolate lock\n//! - [`Local<'a, T>`] - Stack-allocated handle to a V8 value, tied to a `HandleScope`\n//! - [`Global<T>`] - Persistent handle that outlives `HandleScope`s\n//!\n//! # Garbage Collection\n//!\n//! Rust resources integrate with V8's GC through the C++ `Wrappable` base class. Each Rust\n//! resource is wrapped in `Rc<R>` with a `Wrappable` on the KJ heap that bridges to cppgc\n//! via a `CppgcShim`. The Wrappable's `data[0..1]` stores a fat pointer to\n//! `dyn GarbageCollected` — the data part is the `Rc::into_raw` pointer (pointing to `R`\n//! inside the `Rc` allocation) and the vtable part carries `R`'s `GarbageCollected` impl.\n//! On destruction, `wrappable_invoke_drop` reconstructs the `Rc` via `Rc::from_raw` and\n//! drops it, which may drop the resource. The Rust `Ref<R>` smart pointer holds its own\n//! `Rc<R>` plus a `WrappableRc` (`KjRc<Wrappable>`) for reference-counted ownership. On\n//! drop, `wrappable_remove_strong_ref()` handles GC cleanup via `maybeDeferDestruction()`,\n//! then the `WrappableRc` drop decrements the `kj::Rc` refcount.\n\nuse std::cell::UnsafeCell;\nuse std::fmt::Display;\nuse std::marker::PhantomData;\nuse std::pin::Pin;\nuse std::ptr::NonNull;\nuse std::rc::Rc;\n\nuse crate::Error;\nuse crate::FromJS;\nuse crate::GarbageCollected;\nuse crate::Lock;\nuse crate::Number;\nuse crate::Resource;\n#[expect(clippy::missing_safety_doc)]\n#[cxx::bridge(namespace = \"workerd::rust::jsg\")]\npub mod ffi {\n    #[derive(Debug)]\n    struct Local {\n        ptr: usize,\n    }\n\n    #[derive(Debug)]\n    struct Global {\n        /// Strong `v8::Global<v8::Value>` handle. Always valid when non-zero.\n        ptr: usize,\n    }\n\n    #[derive(Debug)]\n    struct TracedReference {\n        /// Weak `v8::TracedReference<v8::Data>` handle used during GC tracing.\n        /// Zero/null when inactive (strong mode).\n        ptr: usize,\n    }\n\n    #[derive(Debug)]\n    struct GcVisitor {\n        ptr: usize,\n    }\n\n    #[derive(Debug, PartialEq, Eq, Copy, Clone)]\n    pub enum ExceptionType {\n        OperationError,\n        DataError,\n        DataCloneError,\n        InvalidAccessError,\n        InvalidStateError,\n        InvalidCharacterError,\n        NotSupportedError,\n        SyntaxError,\n        TimeoutError,\n        TypeMismatchError,\n        AbortError,\n        NotFoundError,\n        TypeError,\n        Error,\n        RangeError,\n        ReferenceError,\n    }\n\n    extern \"Rust\" {\n        /// Called from C++ Wrappable destructor to drop the Rust object.\n        /// Reconstructs the `Rc<dyn GarbageCollected>` from `data[0..1]` and drops it.\n        unsafe fn wrappable_invoke_drop(wrappable: Pin<&mut Wrappable>);\n\n        /// Called from C++ Wrappable::jsgVisitForGc to trace nested handles.\n        /// Reconstructs `&dyn GarbageCollected` from `data[0..1]` and calls `trace()`.\n        unsafe fn wrappable_invoke_trace(wrappable: &Wrappable, visitor: *mut GcVisitor);\n\n        /// Called from C++ `Wrappable::jsgGetMemoryName`.\n        /// Returns the NUL-terminated class name for heap snapshots as a `rust::Str` view\n        /// into a `'static` string literal. The C++ side constructs a `kj::StringPtr`\n        /// directly from `.data()` / `.size()` — no allocation needed.\n        unsafe fn wrappable_invoke_get_name(wrappable: &Wrappable) -> &'static str;\n    }\n\n    unsafe extern \"C++\" {\n        include!(\"workerd/rust/jsg/ffi.h\");\n\n        type Isolate;\n        type FunctionCallbackInfo;\n        type Wrappable;\n\n        // Local<T>\n        pub unsafe fn local_drop(value: Local);\n        pub unsafe fn local_clone(value: &Local) -> Local;\n        pub unsafe fn local_to_global(isolate: *mut Isolate, value: Local) -> Global;\n        pub unsafe fn local_new_number(isolate: *mut Isolate, value: f64) -> Local;\n        pub unsafe fn local_new_string(isolate: *mut Isolate, value: &str) -> Local;\n        pub unsafe fn local_new_boolean(isolate: *mut Isolate, value: bool) -> Local;\n        pub unsafe fn local_new_object(isolate: *mut Isolate) -> Local;\n        pub unsafe fn local_new_null(isolate: *mut Isolate) -> Local;\n        pub unsafe fn local_new_undefined(isolate: *mut Isolate) -> Local;\n        pub unsafe fn local_new_array(isolate: *mut Isolate, length: usize) -> Local;\n        pub unsafe fn local_new_uint8_array(\n            isolate: *mut Isolate,\n            data: *const u8,\n            length: usize,\n        ) -> Local;\n        pub unsafe fn local_new_uint16_array(\n            isolate: *mut Isolate,\n            data: *const u16,\n            length: usize,\n        ) -> Local;\n        pub unsafe fn local_new_uint32_array(\n            isolate: *mut Isolate,\n            data: *const u32,\n            length: usize,\n        ) -> Local;\n        pub unsafe fn local_new_int8_array(\n            isolate: *mut Isolate,\n            data: *const i8,\n            length: usize,\n        ) -> Local;\n        pub unsafe fn local_new_int16_array(\n            isolate: *mut Isolate,\n            data: *const i16,\n            length: usize,\n        ) -> Local;\n        pub unsafe fn local_new_int32_array(\n            isolate: *mut Isolate,\n            data: *const i32,\n            length: usize,\n        ) -> Local;\n        pub unsafe fn local_new_float32_array(\n            isolate: *mut Isolate,\n            data: *const f32,\n            length: usize,\n        ) -> Local;\n        pub unsafe fn local_new_float64_array(\n            isolate: *mut Isolate,\n            data: *const f64,\n            length: usize,\n        ) -> Local;\n        pub unsafe fn local_new_bigint64_array(\n            isolate: *mut Isolate,\n            data: *const i64,\n            length: usize,\n        ) -> Local;\n        pub unsafe fn local_new_biguint64_array(\n            isolate: *mut Isolate,\n            data: *const u64,\n            length: usize,\n        ) -> Local;\n        pub unsafe fn local_eq(lhs: &Local, rhs: &Local) -> bool;\n        pub unsafe fn local_has_value(value: &Local) -> bool;\n        pub unsafe fn local_is_string(value: &Local) -> bool;\n        pub unsafe fn local_is_boolean(value: &Local) -> bool;\n        pub unsafe fn local_is_number(value: &Local) -> bool;\n        pub unsafe fn local_is_null(value: &Local) -> bool;\n        pub unsafe fn local_is_undefined(value: &Local) -> bool;\n        pub unsafe fn local_is_null_or_undefined(value: &Local) -> bool;\n        pub unsafe fn local_is_object(value: &Local) -> bool;\n        pub unsafe fn local_is_native_error(value: &Local) -> bool;\n        pub unsafe fn local_is_array(value: &Local) -> bool;\n        pub unsafe fn local_is_uint8_array(value: &Local) -> bool;\n        pub unsafe fn local_is_uint16_array(value: &Local) -> bool;\n        pub unsafe fn local_is_uint32_array(value: &Local) -> bool;\n        pub unsafe fn local_is_int8_array(value: &Local) -> bool;\n        pub unsafe fn local_is_int16_array(value: &Local) -> bool;\n        pub unsafe fn local_is_int32_array(value: &Local) -> bool;\n        pub unsafe fn local_is_float32_array(value: &Local) -> bool;\n        pub unsafe fn local_is_float64_array(value: &Local) -> bool;\n        pub unsafe fn local_is_bigint64_array(value: &Local) -> bool;\n        pub unsafe fn local_is_biguint64_array(value: &Local) -> bool;\n        pub unsafe fn local_is_array_buffer(value: &Local) -> bool;\n        pub unsafe fn local_is_array_buffer_view(value: &Local) -> bool;\n        pub unsafe fn local_is_function(value: &Local) -> bool;\n        pub unsafe fn local_type_of(isolate: *mut Isolate, value: &Local) -> String;\n\n        // Local<Function>\n        pub unsafe fn local_function_call(\n            isolate: *mut Isolate,\n            function: &Local,\n            recv: &Local,\n            args: &[Local],\n        ) -> Local;\n\n        // Local<Object>\n        pub unsafe fn local_object_set_property(\n            isolate: *mut Isolate,\n            object: &mut Local,\n            key: &str,\n            value: Local,\n        );\n        pub unsafe fn local_object_has_property(\n            isolate: *mut Isolate,\n            object: &Local,\n            key: &str,\n        ) -> bool;\n        pub unsafe fn local_object_get_property(\n            isolate: *mut Isolate,\n            object: &Local,\n            key: &str,\n        ) -> KjMaybe<Local>;\n\n        // Local<Array>\n        pub unsafe fn local_array_length(isolate: *mut Isolate, array: &Local) -> u32;\n        pub unsafe fn local_array_get(isolate: *mut Isolate, array: &Local, index: u32) -> Local;\n        pub unsafe fn local_array_set(\n            isolate: *mut Isolate,\n            array: &mut Local,\n            index: u32,\n            value: Local,\n        );\n        pub unsafe fn local_array_iterate(isolate: *mut Isolate, value: Local) -> Vec<Global>;\n\n        // Local<TypedArray>\n        pub unsafe fn local_typed_array_length(isolate: *mut Isolate, array: &Local) -> usize;\n        pub unsafe fn local_uint8_array_get(\n            isolate: *mut Isolate,\n            array: &Local,\n            index: usize,\n        ) -> u8;\n        pub unsafe fn local_uint16_array_get(\n            isolate: *mut Isolate,\n            array: &Local,\n            index: usize,\n        ) -> u16;\n        pub unsafe fn local_uint32_array_get(\n            isolate: *mut Isolate,\n            array: &Local,\n            index: usize,\n        ) -> u32;\n        pub unsafe fn local_int8_array_get(\n            isolate: *mut Isolate,\n            array: &Local,\n            index: usize,\n        ) -> i8;\n        pub unsafe fn local_int16_array_get(\n            isolate: *mut Isolate,\n            array: &Local,\n            index: usize,\n        ) -> i16;\n        pub unsafe fn local_int32_array_get(\n            isolate: *mut Isolate,\n            array: &Local,\n            index: usize,\n        ) -> i32;\n        pub unsafe fn local_float32_array_get(\n            isolate: *mut Isolate,\n            array: &Local,\n            index: usize,\n        ) -> f32;\n        pub unsafe fn local_float64_array_get(\n            isolate: *mut Isolate,\n            array: &Local,\n            index: usize,\n        ) -> f64;\n        pub unsafe fn local_bigint64_array_get(\n            isolate: *mut Isolate,\n            array: &Local,\n            index: usize,\n        ) -> i64;\n        pub unsafe fn local_biguint64_array_get(\n            isolate: *mut Isolate,\n            array: &Local,\n            index: usize,\n        ) -> u64;\n\n        // Global<T>\n        pub unsafe fn global_reset(value: Pin<&mut Global>);\n        pub unsafe fn global_clone(isolate: *mut Isolate, value: &Global) -> Global;\n        pub unsafe fn global_to_local(isolate: *mut Isolate, value: &Global) -> Local;\n\n        // Wrappable - data access\n        #[expect(\n            clippy::needless_lifetimes,\n            reason = \"CXX bridge requires explicit lifetimes on return references\"\n        )]\n        pub unsafe fn wrappable_get_trait_object<'a>(\n            wrappable: &'a Wrappable,\n        ) -> &'a TraitObjectPtr;\n        pub unsafe fn wrappable_clear_trait_object(wrappable: Pin<&mut Wrappable>);\n        pub unsafe fn wrappable_strong_refcount(wrappable: &Wrappable) -> u32;\n\n        // Wrappable lifecycle — KjRc<Wrappable> for reference-counted ownership\n        pub unsafe fn wrappable_new(ptr: TraitObjectPtr) -> KjRc<Wrappable>;\n\n        pub unsafe fn wrappable_to_rc(wrappable: Pin<&mut Wrappable>) -> KjRc<Wrappable>;\n        pub unsafe fn wrappable_add_strong_ref(wrappable: Pin<&mut Wrappable>);\n        pub unsafe fn wrappable_remove_strong_ref(wrappable: Pin<&mut Wrappable>, is_strong: bool);\n        pub unsafe fn wrappable_visit_ref(\n            wrappable: Pin<&mut Wrappable>,\n            ref_parent: *mut usize,\n            ref_strong: *mut bool,\n            visitor: *mut GcVisitor,\n        );\n        /// Visit a `v8::Global` field during GC tracing, implementing the same\n        /// strong↔traced dual-mode switching that `jsg::Data` / `jsg::V8Ref<T>`\n        /// use in C++.\n        ///\n        /// `global` points to the `ptr` field of `ffi::Global` (the strong handle).\n        /// `traced` points to the `traced_ptr` field (the weak traced handle).\n        /// Both are mutated in-place to reflect the new handle state after the visit.\n        pub unsafe fn wrappable_visit_global(\n            visitor: *mut GcVisitor,\n            global: *mut usize,\n            traced: &mut TracedReference,\n        );\n        /// Resets a `v8::TracedReference`, releasing the weak GC handle.\n        /// Must be called when a `Global<T>` is dropped in traced mode to avoid\n        /// leaking a live `v8::TracedReference`.\n        pub unsafe fn traced_reference_reset(traced: &mut TracedReference);\n\n        // Unwrappers\n        pub unsafe fn unwrap_string(isolate: *mut Isolate, value: Local) -> String;\n        pub unsafe fn unwrap_boolean(isolate: *mut Isolate, value: Local) -> bool;\n        pub unsafe fn unwrap_number(isolate: *mut Isolate, value: Local) -> f64;\n        pub unsafe fn unwrap_uint8_array(isolate: *mut Isolate, value: Local) -> Vec<u8>;\n        pub unsafe fn unwrap_uint16_array(isolate: *mut Isolate, value: Local) -> Vec<u16>;\n        pub unsafe fn unwrap_uint32_array(isolate: *mut Isolate, value: Local) -> Vec<u32>;\n        pub unsafe fn unwrap_int8_array(isolate: *mut Isolate, value: Local) -> Vec<i8>;\n        pub unsafe fn unwrap_int16_array(isolate: *mut Isolate, value: Local) -> Vec<i16>;\n        pub unsafe fn unwrap_int32_array(isolate: *mut Isolate, value: Local) -> Vec<i32>;\n        pub unsafe fn unwrap_float32_array(isolate: *mut Isolate, value: Local) -> Vec<f32>;\n        pub unsafe fn unwrap_float64_array(isolate: *mut Isolate, value: Local) -> Vec<f64>;\n        pub unsafe fn unwrap_bigint64_array(isolate: *mut Isolate, value: Local) -> Vec<i64>;\n        pub unsafe fn unwrap_biguint64_array(isolate: *mut Isolate, value: Local) -> Vec<u64>;\n\n        // FunctionCallbackInfo\n        pub unsafe fn fci_get_isolate(args: *mut FunctionCallbackInfo) -> *mut Isolate;\n        pub unsafe fn fci_get_this(args: *mut FunctionCallbackInfo) -> Local;\n        pub unsafe fn fci_get_length(args: *mut FunctionCallbackInfo) -> usize;\n        pub unsafe fn fci_get_arg(args: *mut FunctionCallbackInfo, index: usize) -> Local;\n        pub unsafe fn fci_set_return_value(args: *mut FunctionCallbackInfo, value: Local);\n\n        // Errors\n        pub unsafe fn exception_create(\n            isolate: *mut Isolate,\n            exception_type: ExceptionType,\n            message: &str,\n        ) -> Local;\n\n        // Isolate\n        pub unsafe fn isolate_throw_exception(isolate: *mut Isolate, exception: Local);\n        pub unsafe fn isolate_throw_error(isolate: *mut Isolate, message: &str);\n        pub unsafe fn isolate_throw_internal_error(isolate: *mut Isolate, internal_message: &str);\n        pub unsafe fn isolate_is_locked(isolate: *mut Isolate) -> bool;\n    }\n\n    /// Fat pointer to a `dyn GarbageCollected` trait object plus its TypeId.\n    ///\n    /// Stored inside the C++ `Wrappable` struct. Contains everything needed to\n    /// reconstruct the Rust trait object and verify its type.\n    pub struct TraitObjectPtr {\n        /// `Rc::into_raw(*const R)` — data pointer to the resource.\n        pub data_ptr: usize,\n        /// Vtable pointer for `R as dyn GarbageCollected`.\n        pub vtable_ptr: usize,\n        /// `TypeId::of::<R>()` low 64 bits.\n        pub type_id_lo: usize,\n        /// `TypeId::of::<R>()` high 64 bits.\n        pub type_id_hi: usize,\n    }\n\n    pub struct ConstructorDescriptor {\n        callback: usize,\n    }\n\n    pub struct MethodDescriptor {\n        name: String,\n        callback: usize,\n    }\n\n    pub struct StaticConstantDescriptor {\n        pub name: String,\n        pub value: f64, /* number */\n    }\n\n    pub struct ResourceDescriptor {\n        pub name: String,\n        pub constructor: KjMaybe<ConstructorDescriptor>,\n        pub methods: Vec<MethodDescriptor>,\n        pub static_methods: Vec<MethodDescriptor>,\n        pub static_constants: Vec<StaticConstantDescriptor>,\n    }\n\n    // Resources\n    unsafe extern \"C++\" {\n        unsafe fn create_resource_template(\n            isolate: *mut Isolate,\n            descriptor: &ResourceDescriptor,\n        ) -> Global /* v8::Global<FunctionTemplate> */;\n\n        pub unsafe fn wrap_resource(\n            isolate: *mut Isolate,\n            wrappable: KjRc<Wrappable>,\n            constructor: &Global, /* v8::Global<FunctionTemplate> */\n        ) -> Local /* v8::Local<Value> */;\n\n        pub unsafe fn wrappable_attach_wrapper(\n            wrappable: KjRc<Wrappable>,\n            args: Pin<&mut FunctionCallbackInfo>,\n        );\n\n        pub unsafe fn unwrap_resource(\n            isolate: *mut Isolate,\n            value: Local, /* v8::LocalValue */\n        ) -> KjRc<Wrappable>;\n\n        pub unsafe fn function_template_get_function(\n            isolate: *mut Isolate,\n            constructor: &Global, /* v8::Global<FunctionTemplate> */\n        ) -> Local /* v8::Local<Function> */;\n    }\n\n    /// Module visibility level, mirroring workerd::jsg::ModuleType from modules.capnp.\n    ///\n    /// CXX shared enums cannot reference existing C++ enums, so we define matching values here.\n    /// The conversion to workerd::jsg::ModuleType happens in jsg.h's RustModuleRegistry.\n    enum ModuleType {\n        Bundle = 0,\n        Builtin = 1,\n        Internal = 2,\n    }\n\n    unsafe extern \"C++\" {\n        type ModuleRegistry;\n\n        pub unsafe fn register_add_builtin_module(\n            registry: Pin<&mut ModuleRegistry>,\n            specifier: &str,\n            callback: unsafe fn(*mut Isolate) -> Local,\n            module_type: ModuleType,\n        );\n    }\n}\n\nimpl std::fmt::Display for ffi::ExceptionType {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let name = match *self {\n            Self::OperationError => \"OperationError\",\n            Self::DataError => \"DataError\",\n            Self::DataCloneError => \"DataCloneError\",\n            Self::InvalidAccessError => \"InvalidAccessError\",\n            Self::InvalidStateError => \"InvalidStateError\",\n            Self::InvalidCharacterError => \"InvalidCharacterError\",\n            Self::NotSupportedError => \"NotSupportedError\",\n            Self::SyntaxError => \"SyntaxError\",\n            Self::TimeoutError => \"TimeoutError\",\n            Self::TypeMismatchError => \"TypeMismatchError\",\n            Self::AbortError => \"AbortError\",\n            Self::NotFoundError => \"NotFoundError\",\n            Self::TypeError => \"TypeError\",\n            Self::RangeError => \"RangeError\",\n            Self::ReferenceError => \"ReferenceError\",\n            _ => \"Error\",\n        };\n        write!(f, \"{name}\")\n    }\n}\n\n// Marker types for Local<T>\n#[derive(Debug)]\npub struct Value;\n\nimpl Display for Local<'_, Value> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        // SAFETY: isolate is valid and locked (guaranteed by the Local's invariant).\n        let mut lock = unsafe { Lock::from_isolate_ptr(self.isolate.as_ffi()) };\n        match String::from_js(&mut lock, self.clone()) {\n            Ok(value) => write!(f, \"{value}\"),\n            Err(e) => write!(f, \"{e:?}\"),\n        }\n    }\n}\n#[derive(Debug)]\npub struct Object;\npub struct Function;\npub struct FunctionTemplate;\npub struct Array;\npub struct TypedArray;\npub struct Uint8Array;\npub struct Uint16Array;\npub struct Uint32Array;\npub struct Int8Array;\npub struct Int16Array;\npub struct Int32Array;\npub struct Float32Array;\npub struct Float64Array;\npub struct BigInt64Array;\npub struct BigUint64Array;\n\n// Generic Local<'a, T> handle with lifetime\n#[derive(Debug)]\npub struct Local<'a, T> {\n    handle: ffi::Local,\n    isolate: IsolatePtr,\n    _marker: PhantomData<(&'a (), T)>,\n}\n\nimpl<T> Drop for Local<'_, T> {\n    fn drop(&mut self) {\n        if self.handle.ptr == 0 {\n            return;\n        }\n        let handle = std::mem::replace(&mut self.handle, ffi::Local { ptr: 0 });\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_drop(handle) };\n    }\n}\n\n// Common implementations for all Local<'a, T>\nimpl<'a, T> Local<'a, T> {\n    /// Creates a `Local` from an FFI handle.\n    ///\n    /// # Safety\n    /// The caller must ensure that `isolate` is valid and that `handle` points to a V8 value\n    /// that is still alive within the current `HandleScope`.\n    pub unsafe fn from_ffi(isolate: IsolatePtr, handle: ffi::Local) -> Self {\n        Local {\n            handle,\n            isolate,\n            _marker: PhantomData,\n        }\n    }\n\n    /// Consumes this `Local` and returns the underlying FFI handle.\n    ///\n    /// # Safety\n    /// The returned FFI handle must not outlive the `HandleScope` that owns the V8 value.\n    pub unsafe fn into_ffi(mut self) -> ffi::Local {\n        let handle = ffi::Local {\n            ptr: self.handle.ptr,\n        };\n        self.handle.ptr = 0;\n        handle\n    }\n\n    /// Returns a reference to the underlying FFI handle.\n    ///\n    /// # Safety\n    /// The caller must ensure the returned reference is not used after this `Local` is dropped.\n    pub unsafe fn as_ffi(&self) -> &ffi::Local {\n        &self.handle\n    }\n\n    /// Returns a mutable reference to the underlying FFI handle.\n    ///\n    /// # Safety\n    /// The caller must ensure the returned reference is not used after this `Local` is dropped.\n    pub unsafe fn as_ffi_mut(&mut self) -> &mut ffi::Local {\n        &mut self.handle\n    }\n\n    pub fn null(lock: &mut crate::Lock) -> Local<'a, Value> {\n        // SAFETY: isolate is valid and locked (guaranteed by Lock).\n        unsafe { Local::from_ffi(lock.isolate(), ffi::local_new_null(lock.isolate().as_ffi())) }\n    }\n\n    pub fn undefined(lock: &mut crate::Lock) -> Local<'a, Value> {\n        // SAFETY: isolate is valid and locked (guaranteed by Lock).\n        unsafe {\n            Local::from_ffi(\n                lock.isolate(),\n                ffi::local_new_undefined(lock.isolate().as_ffi()),\n            )\n        }\n    }\n\n    pub fn has_value(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_has_value(&self.handle) }\n    }\n\n    pub fn is_string(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_string(&self.handle) }\n    }\n\n    pub fn is_boolean(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_boolean(&self.handle) }\n    }\n\n    pub fn is_number(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_number(&self.handle) }\n    }\n\n    pub fn is_null(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_null(&self.handle) }\n    }\n\n    pub fn is_undefined(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_undefined(&self.handle) }\n    }\n\n    pub fn is_null_or_undefined(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_null_or_undefined(&self.handle) }\n    }\n\n    /// Returns true if the value is a JavaScript object.\n    ///\n    /// Note: Unlike JavaScript's `typeof` operator which returns \"object\" for `null`,\n    /// this method returns `false` for `null` values. Use `is_null_or_undefined()`\n    /// to check for nullish values separately.\n    pub fn is_object(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_object(&self.handle) }\n    }\n\n    /// Returns true if the value is a native JavaScript error.\n    pub fn is_native_error(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_native_error(&self.handle) }\n    }\n\n    /// Returns true if the value is a JavaScript array.\n    pub fn is_array(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_array(&self.handle) }\n    }\n\n    /// Returns true if the value is a `Uint8Array`.\n    pub fn is_uint8_array(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_uint8_array(&self.handle) }\n    }\n\n    /// Returns true if the value is a `Uint16Array`.\n    pub fn is_uint16_array(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_uint16_array(&self.handle) }\n    }\n\n    /// Returns true if the value is a `Uint32Array`.\n    pub fn is_uint32_array(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_uint32_array(&self.handle) }\n    }\n\n    /// Returns true if the value is an `Int8Array`.\n    pub fn is_int8_array(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_int8_array(&self.handle) }\n    }\n\n    /// Returns true if the value is an `Int16Array`.\n    pub fn is_int16_array(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_int16_array(&self.handle) }\n    }\n\n    /// Returns true if the value is an `Int32Array`.\n    pub fn is_int32_array(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_int32_array(&self.handle) }\n    }\n\n    /// Returns true if the value is a `Float32Array`.\n    pub fn is_float32_array(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_float32_array(&self.handle) }\n    }\n\n    /// Returns true if the value is a `Float64Array`.\n    pub fn is_float64_array(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_float64_array(&self.handle) }\n    }\n\n    /// Returns true if the value is a `BigInt64Array`.\n    pub fn is_bigint64_array(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_bigint64_array(&self.handle) }\n    }\n\n    /// Returns true if the value is a `BigUint64Array`.\n    pub fn is_biguint64_array(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_biguint64_array(&self.handle) }\n    }\n\n    /// Returns true if the value is an `ArrayBuffer`.\n    pub fn is_array_buffer(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_array_buffer(&self.handle) }\n    }\n\n    /// Returns true if the value is an `ArrayBufferView`.\n    pub fn is_array_buffer_view(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_array_buffer_view(&self.handle) }\n    }\n\n    /// Returns true if the value is a JavaScript function.\n    pub fn is_function(&self) -> bool {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_is_function(&self.handle) }\n    }\n\n    /// Returns the JavaScript type of the underlying value as a string.\n    ///\n    /// Uses V8's native `TypeOf` method which returns the same result as\n    /// JavaScript's `typeof` operator: \"undefined\", \"boolean\", \"number\",\n    /// \"bigint\", \"string\", \"symbol\", \"function\", or \"object\".\n    ///\n    /// Note: For `null`, this returns \"object\" (JavaScript's historical behavior).\n    pub fn type_of(&self) -> String {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_type_of(self.isolate.as_ffi(), &self.handle) }\n    }\n}\n\nimpl<T> Clone for Local<'_, T> {\n    fn clone(&self) -> Self {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { Self::from_ffi(self.isolate, ffi::local_clone(&self.handle)) }\n    }\n}\n\n/// Trait for safe checked casts between V8 `Local` handle types.\n///\n/// Use via the turbofish method on `Local<Value>`:\n/// ```ignore\n/// let func: Local<Function> = value.try_as::<Function>().unwrap();\n/// ```\npub trait As<T>: Sized {\n    type Output;\n\n    /// Attempts to cast this handle to the target type.\n    /// Returns `None` if the underlying value is not of the target type.\n    fn try_as(self) -> Option<Self::Output>;\n}\n\n/// Implements `As<$target>` for `Local<Value>` using the given type-check method.\nmacro_rules! impl_as {\n    ($target:ident, $check:ident) => {\n        impl<'a> As<$target> for Local<'a, Value> {\n            type Output = Local<'a, $target>;\n\n            fn try_as(self) -> Option<Local<'a, $target>> {\n                if self.$check() {\n                    // SAFETY: We verified the type above.\n                    Some(unsafe { Local::from_ffi(self.isolate, self.into_ffi()) })\n                } else {\n                    None\n                }\n            }\n        }\n    };\n}\n\nimpl_as!(Function, is_function);\nimpl_as!(Object, is_object);\nimpl_as!(Array, is_array);\n\n// Value-specific implementations\nimpl<'a> Local<'a, Value> {\n    pub fn to_global(self, lock: &'a mut Lock) -> Global<Value> {\n        // SAFETY: isolate is valid and locked (guaranteed by Lock); handle is valid.\n        unsafe { ffi::local_to_global(lock.isolate().as_ffi(), self.into_ffi()).into() }\n    }\n\n    /// Attempts a checked cast to a more specific `Local<T>` type.\n    ///\n    /// Returns `None` if the value is not of the target type.\n    pub fn try_as<T>(self) -> Option<<Self as As<T>>::Output>\n    where\n        Self: As<T>,\n    {\n        As::<T>::try_as(self)\n    }\n}\n\nimpl PartialEq for Local<'_, Value> {\n    fn eq(&self, other: &Self) -> bool {\n        // SAFETY: both handles are valid within the current HandleScope.\n        unsafe { ffi::local_eq(&self.handle, &other.handle) }\n    }\n}\n\nimpl Local<'_, Function> {\n    /// Calls this function and converts the result via [`FromJS`].\n    ///\n    /// `receiver` is the `this` value; pass `None` for `undefined`. Any `Local<T>`\n    /// that converts to `Local<Value>` (via `Into`) can be used directly.\n    ///\n    /// `args` is a slice of `Local<Value>` — use [`ToJS::to_js`] to convert Rust\n    /// values, or `.into()` for other `Local<T>` handles.\n    ///\n    /// # Example\n    ///\n    /// ```ignore\n    /// let result: Number = func.call(lock, None::<Local<Value>>, &[x.to_js(lock), y.to_js(lock)])?;\n    /// ```\n    pub fn call<'b, R: FromJS, Recv: Into<Local<'b, Value>>>(\n        &self,\n        lock: &mut Lock,\n        receiver: Option<Recv>,\n        args: &[Local<'_, Value>],\n    ) -> Result<R::ResultType, Error> {\n        let recv = receiver.map_or_else(|| Local::<Value>::undefined(lock), Into::into);\n\n        // Build a contiguous array of ffi::Local handles for the FFI call.\n        let ffi_args: Vec<ffi::Local> = args\n            .iter()\n            // SAFETY: each arg handle is valid within the current HandleScope.\n            .map(|a| unsafe { ffi::local_clone(a.as_ffi()) })\n            .collect();\n\n        // SAFETY: lock guarantees the isolate is locked and a HandleScope is active.\n        // self.handle is a valid Local<Function>. recv and ffi_args are valid Local handles.\n        let result = unsafe {\n            Local::from_ffi(\n                lock.isolate(),\n                ffi::local_function_call(\n                    lock.isolate().as_ffi(),\n                    &self.handle,\n                    recv.as_ffi(),\n                    &ffi_args,\n                ),\n            )\n        };\n        R::from_js(lock, result)\n    }\n}\n\n/// Implements bidirectional `From` conversions between `Local<$from>` and `Local<$to>`.\n///\n/// All V8 handle subtypes share the same pointer representation, so these casts\n/// are just reinterpretations of the handle. The `assert!` verifies the type\n/// invariant at runtime — redundant for upcasts (always true) but guards\n/// downcasts against misuse.\nmacro_rules! impl_local_cast {\n    ($from:ident -> $to:ident, $check:ident) => {\n        impl<'a> From<Local<'a, $from>> for Local<'a, $to> {\n            fn from(value: Local<'a, $from>) -> Self {\n                assert!(value.$check());\n                // SAFETY: V8 subtypes share handle representation; assert verifies the invariant.\n                unsafe { Self::from_ffi(value.isolate, value.into_ffi()) }\n            }\n        }\n        impl<'a> From<Local<'a, $to>> for Local<'a, $from> {\n            fn from(value: Local<'a, $to>) -> Self {\n                assert!(value.$check());\n                // SAFETY: V8 subtypes share handle representation; assert verifies the invariant.\n                unsafe { Self::from_ffi(value.isolate, value.into_ffi()) }\n            }\n        }\n    };\n}\n\n// Upcasts to Value\nimpl_local_cast!(Object -> Value, is_object);\nimpl_local_cast!(Function -> Value, is_function);\nimpl_local_cast!(Array -> Value, is_array);\nimpl_local_cast!(Uint8Array -> Value, is_uint8_array);\nimpl_local_cast!(Uint16Array -> Value, is_uint16_array);\nimpl_local_cast!(Uint32Array -> Value, is_uint32_array);\nimpl_local_cast!(Int8Array -> Value, is_int8_array);\nimpl_local_cast!(Int16Array -> Value, is_int16_array);\nimpl_local_cast!(Int32Array -> Value, is_int32_array);\nimpl_local_cast!(Float32Array -> Value, is_float32_array);\nimpl_local_cast!(Float64Array -> Value, is_float64_array);\nimpl_local_cast!(BigInt64Array -> Value, is_bigint64_array);\nimpl_local_cast!(BigUint64Array -> Value, is_biguint64_array);\n\n// TypedArray base type to Value. Uses `is_array_buffer_view` which also matches\n// `DataView`, but this is acceptable because `TypedArray` is only constructed from\n// concrete typed array types (Uint8Array, etc.) that are always ArrayBufferViews.\nimpl_local_cast!(TypedArray -> Value, is_array_buffer_view);\n\n// Concrete typed arrays to TypedArray base\nimpl_local_cast!(Uint8Array -> TypedArray, is_uint8_array);\nimpl_local_cast!(Uint16Array -> TypedArray, is_uint16_array);\nimpl_local_cast!(Uint32Array -> TypedArray, is_uint32_array);\nimpl_local_cast!(Int8Array -> TypedArray, is_int8_array);\nimpl_local_cast!(Int16Array -> TypedArray, is_int16_array);\nimpl_local_cast!(Int32Array -> TypedArray, is_int32_array);\nimpl_local_cast!(Float32Array -> TypedArray, is_float32_array);\nimpl_local_cast!(Float64Array -> TypedArray, is_float64_array);\nimpl_local_cast!(BigInt64Array -> TypedArray, is_bigint64_array);\nimpl_local_cast!(BigUint64Array -> TypedArray, is_biguint64_array);\n\n// Upcasts to Object (Function, Array, TypedArray are all Object subtypes in V8)\nimpl_local_cast!(Function -> Object, is_function);\nimpl_local_cast!(Array -> Object, is_array);\nimpl_local_cast!(TypedArray -> Object, is_array_buffer_view);\n\nimpl Local<'_, Array> {\n    /// Creates a new JavaScript array with the given length.\n    pub fn new<'a>(lock: &mut crate::Lock, len: usize) -> Local<'a, Array> {\n        let isolate = lock.isolate();\n        // SAFETY: isolate is valid and locked (guaranteed by Lock).\n        unsafe { Local::from_ffi(isolate, ffi::local_new_array(isolate.as_ffi(), len)) }\n    }\n\n    /// Returns the length of the array.\n    #[inline]\n    pub fn len(&self) -> usize {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_array_length(self.isolate.as_ffi(), &self.handle) as usize }\n    }\n\n    /// Returns true if the array is empty.\n    #[inline]\n    pub fn is_empty(&self) -> bool {\n        self.len() == 0\n    }\n\n    /// Sets an element at the given index.\n    pub fn set(&mut self, index: usize, value: Local<'_, Value>) {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe {\n            ffi::local_array_set(\n                self.isolate.as_ffi(),\n                &mut self.handle,\n                index as u32,\n                value.into_ffi(),\n            );\n        }\n    }\n\n    /// Iterates over array elements using V8's native `Array::Iterate()`.\n    /// Returns Global handles because Local handles get reused during iteration.\n    pub fn iterate(self) -> Vec<Global<Value>> {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_array_iterate(self.isolate.as_ffi(), self.into_ffi()) }\n            .into_iter()\n            // SAFETY: each Global handle was created by the C++ side and is valid.\n            .map(|g| unsafe { Global::from_ffi(g) })\n            .collect()\n    }\n}\n\n// `TypedArray`-specific implementations\nimpl Local<'_, TypedArray> {\n    /// Returns the number of elements in this `TypedArray`.\n    pub fn len(&self) -> usize {\n        // SAFETY: handle is valid within the current HandleScope.\n        unsafe { ffi::local_typed_array_length(self.isolate.as_ffi(), &self.handle) }\n    }\n\n    /// Returns true if the `TypedArray` is empty.\n    pub fn is_empty(&self) -> bool {\n        self.len() == 0\n    }\n}\n\n// =============================================================================\n// `TypedArray` Iterator Types\n// =============================================================================\n\n/// Iterator over `TypedArray` elements by reference.\n///\n/// Created by calling `iter()` on a `Local<'a, TypedArray>`.\n/// Does not consume the array, allowing multiple iterations.\npub struct TypedArrayIter<'a, 'b, T, E> {\n    array: &'b Local<'a, T>,\n    index: usize,\n    len: usize,\n    _marker: PhantomData<E>,\n}\n\n/// Owning iterator over `TypedArray` elements.\n///\n/// Created by calling `into_iter()` on a `Local<'a, TypedArray>`.\n/// Consumes the array handle.\npub struct TypedArrayIntoIter<'a, T, E> {\n    array: Local<'a, T>,\n    index: usize,\n    len: usize,\n    _marker: PhantomData<E>,\n}\n\n// =============================================================================\n// `TypedArray` Implementation Macro\n// =============================================================================\n\n/// Implements methods and traits for a specific `TypedArray` type.\n///\n/// For each `TypedArray` marker type (e.g., `Uint8Array`), this macro generates:\n/// - `len()`, `is_empty()`, `get(index)` methods\n/// - `iter()` method for borrowing iteration\n/// - `IntoIterator` for owned and borrowed iteration\n/// - `Iterator` implementations for both iterator types\nmacro_rules! impl_typed_array {\n    ($marker:ident, $elem:ty, $get_fn:ident) => {\n        impl<'a> Local<'a, $marker> {\n            /// Returns the number of elements in this `TypedArray`.\n            #[inline]\n            pub fn len(&self) -> usize {\n                // SAFETY: handle is valid within the current HandleScope.\n                unsafe { ffi::local_typed_array_length(self.isolate.as_ffi(), &self.handle) }\n            }\n\n            /// Returns `true` if the `TypedArray` contains no elements.\n            #[inline]\n            pub fn is_empty(&self) -> bool {\n                self.len() == 0\n            }\n\n            /// Returns the element at `index`.\n            ///\n            /// # Panics\n            ///\n            /// Panics if `index >= self.len()`.\n            #[inline]\n            pub fn get(&self, index: usize) -> $elem {\n                debug_assert!(index < self.len(), \"index out of bounds\");\n                // SAFETY: handle is valid within the current HandleScope.\n                unsafe { ffi::$get_fn(self.isolate.as_ffi(), &self.handle, index) }\n            }\n\n            /// Returns an iterator over the elements.\n            ///\n            /// The iterator yields elements by value (copied from V8 memory).\n            #[inline]\n            pub fn iter(&self) -> TypedArrayIter<'a, '_, $marker, $elem> {\n                TypedArrayIter {\n                    array: self,\n                    index: 0,\n                    len: self.len(),\n                    _marker: PhantomData,\n                }\n            }\n        }\n\n        // Owned iteration: `for x in array`\n        impl<'a> IntoIterator for Local<'a, $marker> {\n            type Item = $elem;\n            type IntoIter = TypedArrayIntoIter<'a, $marker, $elem>;\n\n            #[inline]\n            fn into_iter(self) -> Self::IntoIter {\n                let len = self.len();\n                TypedArrayIntoIter {\n                    array: self,\n                    index: 0,\n                    len,\n                    _marker: PhantomData,\n                }\n            }\n        }\n\n        // Borrowed iteration: `for x in &array`\n        impl<'a, 'b> IntoIterator for &'b Local<'a, $marker> {\n            type Item = $elem;\n            type IntoIter = TypedArrayIter<'a, 'b, $marker, $elem>;\n\n            #[inline]\n            fn into_iter(self) -> Self::IntoIter {\n                self.iter()\n            }\n        }\n\n        impl<'a, 'b> Iterator for TypedArrayIter<'a, 'b, $marker, $elem> {\n            type Item = $elem;\n\n            #[inline]\n            fn next(&mut self) -> Option<Self::Item> {\n                if self.index < self.len {\n                    // SAFETY: handle is valid within the current HandleScope; index < len.\n                    let value = unsafe {\n                        ffi::$get_fn(self.array.isolate.as_ffi(), &self.array.handle, self.index)\n                    };\n                    self.index += 1;\n                    Some(value)\n                } else {\n                    None\n                }\n            }\n\n            #[inline]\n            fn size_hint(&self) -> (usize, Option<usize>) {\n                let remaining = self.len - self.index;\n                (remaining, Some(remaining))\n            }\n        }\n\n        impl<'a, 'b> ExactSizeIterator for TypedArrayIter<'a, 'b, $marker, $elem> {}\n\n        impl<'a, 'b> DoubleEndedIterator for TypedArrayIter<'a, 'b, $marker, $elem> {\n            #[inline]\n            fn next_back(&mut self) -> Option<Self::Item> {\n                if self.index < self.len {\n                    self.len -= 1;\n                    // SAFETY: handle is valid within the current HandleScope; len is in bounds.\n                    let value = unsafe {\n                        ffi::$get_fn(self.array.isolate.as_ffi(), &self.array.handle, self.len)\n                    };\n                    Some(value)\n                } else {\n                    None\n                }\n            }\n        }\n\n        impl<'a, 'b> std::iter::FusedIterator for TypedArrayIter<'a, 'b, $marker, $elem> {}\n\n        impl<'a> Iterator for TypedArrayIntoIter<'a, $marker, $elem> {\n            type Item = $elem;\n\n            #[inline]\n            fn next(&mut self) -> Option<Self::Item> {\n                if self.index < self.len {\n                    // SAFETY: handle is valid within the current HandleScope; index < len.\n                    let value = unsafe {\n                        ffi::$get_fn(self.array.isolate.as_ffi(), &self.array.handle, self.index)\n                    };\n                    self.index += 1;\n                    Some(value)\n                } else {\n                    None\n                }\n            }\n\n            #[inline]\n            fn size_hint(&self) -> (usize, Option<usize>) {\n                let remaining = self.len - self.index;\n                (remaining, Some(remaining))\n            }\n        }\n\n        impl<'a> ExactSizeIterator for TypedArrayIntoIter<'a, $marker, $elem> {}\n\n        impl<'a> DoubleEndedIterator for TypedArrayIntoIter<'a, $marker, $elem> {\n            #[inline]\n            fn next_back(&mut self) -> Option<Self::Item> {\n                if self.index < self.len {\n                    self.len -= 1;\n                    // SAFETY: handle is valid within the current HandleScope; len is in bounds.\n                    let value = unsafe {\n                        ffi::$get_fn(self.array.isolate.as_ffi(), &self.array.handle, self.len)\n                    };\n                    Some(value)\n                } else {\n                    None\n                }\n            }\n        }\n\n        impl<'a> std::iter::FusedIterator for TypedArrayIntoIter<'a, $marker, $elem> {}\n    };\n}\n\nimpl_typed_array!(Uint8Array, u8, local_uint8_array_get);\nimpl_typed_array!(Uint16Array, u16, local_uint16_array_get);\nimpl_typed_array!(Uint32Array, u32, local_uint32_array_get);\nimpl_typed_array!(Int8Array, i8, local_int8_array_get);\nimpl_typed_array!(Int16Array, i16, local_int16_array_get);\nimpl_typed_array!(Int32Array, i32, local_int32_array_get);\nimpl_typed_array!(Float32Array, f32, local_float32_array_get);\nimpl_typed_array!(Float64Array, f64, local_float64_array_get);\nimpl_typed_array!(BigInt64Array, i64, local_bigint64_array_get);\nimpl_typed_array!(BigUint64Array, u64, local_biguint64_array_get);\n\n// Object-specific implementations\nimpl<'a> Local<'a, Object> {\n    pub fn set(&mut self, lock: &mut Lock, key: &str, value: Local<'a, Value>) {\n        // SAFETY: isolate is valid and locked (guaranteed by Lock); handle is valid.\n        unsafe {\n            ffi::local_object_set_property(\n                lock.isolate().as_ffi(),\n                &mut self.handle,\n                key,\n                value.into_ffi(),\n            );\n        }\n    }\n\n    pub fn has(&self, lock: &mut Lock, key: &str) -> bool {\n        // SAFETY: isolate is valid and locked (guaranteed by Lock); handle is valid.\n        unsafe { ffi::local_object_has_property(lock.isolate().as_ffi(), &self.handle, key) }\n    }\n\n    pub fn get(&self, lock: &mut Lock, key: &str) -> Option<Local<'a, Value>> {\n        if !self.has(lock, key) {\n            return None;\n        }\n\n        // SAFETY: isolate is valid and locked (guaranteed by Lock); handle is valid.\n        unsafe {\n            let maybe_local =\n                ffi::local_object_get_property(lock.isolate().as_ffi(), &self.handle, key);\n            let opt_local: Option<ffi::Local> = maybe_local.into();\n            opt_local.map(|local| Local::from_ffi(lock.isolate(), local))\n        }\n    }\n}\n\n// Generic Global<T> handle without lifetime\npub struct Global<T> {\n    handle: ffi::Global,\n    /// Weak `v8::TracedReference<v8::Data>` handle used during GC tracing.\n    ///\n    /// Empty (`ptr == 0`) when the strong handle is active; becomes non-empty\n    /// once the parent `Wrappable` is downgraded to traced mode (all strong Rust\n    /// `Rc`s dropped). Reset to empty when strong refs are re-acquired.\n    ///\n    /// `UnsafeCell` is required because `GcVisitor::visit_global` takes `&Global<T>`\n    /// (shared reference) but must mutate this field to install the traced handle.\n    /// This is sound because GC tracing is always single-threaded within a V8\n    /// isolate and `trace` is never re-entrant on the same object.\n    traced: UnsafeCell<ffi::TracedReference>,\n    _marker: PhantomData<T>,\n}\n\n// Common implementations for all Global<T>\nimpl<T> Global<T> {\n    /// Creates a `Global` from an FFI handle.\n    ///\n    /// # Safety\n    /// The caller must ensure that `handle` points to a valid V8 persistent handle.\n    pub unsafe fn from_ffi(handle: ffi::Global) -> Self {\n        Self {\n            handle,\n            traced: UnsafeCell::new(ffi::TracedReference { ptr: 0 }),\n            _marker: PhantomData,\n        }\n    }\n\n    /// Returns a reference to the underlying FFI handle.\n    ///\n    /// # Safety\n    /// The caller must ensure the returned reference is not used after this `Global` is dropped.\n    pub unsafe fn as_ffi_ref(&self) -> &ffi::Global {\n        &self.handle\n    }\n\n    /// Returns a mutable reference to the underlying FFI handle.\n    ///\n    /// # Safety\n    /// The caller must ensure the returned reference is not used after this `Global` is dropped.\n    pub unsafe fn as_ffi_mut(&mut self) -> *mut ffi::Global {\n        &raw mut self.handle\n    }\n\n    pub fn as_local<'a>(&self, lock: &mut Lock) -> Local<'a, T> {\n        // SAFETY: isolate is valid and locked (guaranteed by Lock); global handle is valid.\n        unsafe {\n            Local::from_ffi(\n                lock.isolate(),\n                ffi::global_to_local(lock.isolate().as_ffi(), &self.handle),\n            )\n        }\n    }\n\n    /// Resets this global handle, releasing both the strong and traced V8 handles.\n    ///\n    /// # Safety\n    /// The caller must ensure the global handle is valid.\n    pub unsafe fn reset(&mut self) {\n        // Reset the strong handle.\n        // SAFETY: global handle is valid; Pin is sound because ffi::Global is not moved.\n        unsafe {\n            ffi::global_reset(Pin::new_unchecked(&mut self.handle));\n        }\n        // Reset the TracedReference to avoid leaking a live V8 handle when this\n        // Global is dropped while in traced mode.\n        // SAFETY: traced is valid for the lifetime of self.\n        unsafe {\n            ffi::traced_reference_reset(self.traced.get_mut());\n        }\n    }\n}\n\nimpl<T> From<Local<'_, T>> for Global<T> {\n    fn from(local: Local<'_, T>) -> Self {\n        Self {\n            // SAFETY: isolate is valid (guaranteed by Local's invariant); handle is valid.\n            handle: unsafe { ffi::local_to_global(local.isolate.as_ffi(), local.into_ffi()) },\n            traced: UnsafeCell::new(ffi::TracedReference { ptr: 0 }),\n            _marker: PhantomData,\n        }\n    }\n}\n\nimpl Global<FunctionTemplate> {\n    /// Returns the constructor function for this function template.\n    ///\n    /// This is the V8 `Function` object that can be called as a constructor or\n    /// used to access static methods, analogous to a JavaScript class reference\n    /// (e.g., `URL`, `TextEncoder`).\n    pub fn as_local_function<'a>(&self, lock: &mut Lock) -> Local<'a, Function> {\n        // SAFETY: `lock` guarantees the isolate is locked and a HandleScope is active.\n        // `self.handle` is a valid `Global<FunctionTemplate>` created by `create_resource_template`.\n        // The returned `Local` is tied to the current HandleScope via the `'a` lifetime.\n        unsafe {\n            Local::from_ffi(\n                lock.isolate(),\n                ffi::function_template_get_function(lock.isolate().as_ffi(), &self.handle),\n            )\n        }\n    }\n}\n\n// Allow implicit conversion from ffi::Global\nimpl<T> From<ffi::Global> for Global<T> {\n    fn from(handle: ffi::Global) -> Self {\n        Self {\n            handle,\n            traced: UnsafeCell::new(ffi::TracedReference { ptr: 0 }),\n            _marker: PhantomData,\n        }\n    }\n}\n\nimpl<T> Drop for Global<T> {\n    fn drop(&mut self) {\n        // SAFETY: global handle is valid (guaranteed by construction).\n        unsafe { self.reset() };\n    }\n}\n\n// Note: Global<T> intentionally does NOT implement the std Clone trait.\n// Cloning a V8 persistent handle requires an isolate pointer to create an\n// independent handle via v8::Global::New(isolate, original). The Clone trait\n// cannot provide this. Use the `clone()` method directly instead.\nimpl<T> Global<T> {\n    /// Creates an independent copy of this persistent handle.\n    ///\n    /// This properly creates a new V8 persistent handle that references the same\n    /// JS object. Both the original and clone can be independently dropped.\n    ///\n    /// The returned clone always starts in **strong mode** (`traced_ptr = 0`),\n    /// regardless of whether `self` is currently in traced mode. If the clone\n    /// needs to be traced, `GcVisitor::visit_global` will transition it on the\n    /// next GC cycle.\n    #[must_use]\n    pub fn clone(&self, lock: &mut Lock) -> Self {\n        // SAFETY: isolate is valid and locked (guaranteed by Lock); global handle is valid.\n        unsafe { ffi::global_clone(lock.isolate().as_ffi(), &self.handle).into() }\n    }\n}\n\npub trait ToLocalValue {\n    fn to_local<'a>(&self, lock: &mut Lock) -> Local<'a, Value>;\n}\n\nmacro_rules! impl_to_local_value_integer {\n    ($($type:ty),*) => {\n        $(\n            impl ToLocalValue for $type {\n                fn to_local<'a>(&self, lock: &mut Lock) -> Local<'a, Value> {\n                    // SAFETY: isolate is valid and locked (guaranteed by Lock).\n                    unsafe {\n                        Local::from_ffi(\n                            lock.isolate(),\n                            ffi::local_new_number(lock.isolate().as_ffi(), f64::from(*self)),\n                        )\n                    }\n                }\n            }\n        )*\n    };\n}\n\nimpl_to_local_value_integer!(u8, u16, u32, i8, i16, i32);\n\nimpl ToLocalValue for String {\n    fn to_local<'a>(&self, lock: &mut Lock) -> Local<'a, Value> {\n        self.as_str().to_local(lock)\n    }\n}\n\nimpl ToLocalValue for &str {\n    fn to_local<'a>(&self, lock: &mut Lock) -> Local<'a, Value> {\n        // SAFETY: isolate is valid and locked (guaranteed by Lock).\n        unsafe {\n            Local::from_ffi(\n                lock.isolate(),\n                ffi::local_new_string(lock.isolate().as_ffi(), self),\n            )\n        }\n    }\n}\n\nimpl ToLocalValue for bool {\n    fn to_local<'a>(&self, lock: &mut Lock) -> Local<'a, Value> {\n        // SAFETY: isolate is valid and locked (guaranteed by Lock).\n        unsafe {\n            Local::from_ffi(\n                lock.isolate(),\n                ffi::local_new_boolean(lock.isolate().as_ffi(), *self),\n            )\n        }\n    }\n}\n\nimpl ToLocalValue for Number {\n    fn to_local<'a>(&self, lock: &mut Lock) -> Local<'a, Value> {\n        // SAFETY: isolate is valid and locked (guaranteed by Lock).\n        unsafe {\n            Local::from_ffi(\n                lock.isolate(),\n                ffi::local_new_number(lock.isolate().as_ffi(), self.value()),\n            )\n        }\n    }\n}\n\npub struct FunctionCallbackInfo<'a>(*mut ffi::FunctionCallbackInfo, PhantomData<&'a ()>);\n\nimpl<'a> FunctionCallbackInfo<'a> {\n    /// # Safety\n    /// The caller must ensure that `info` is a valid pointer to `FunctionCallbackInfo`.\n    pub unsafe fn from_ffi(info: *mut ffi::FunctionCallbackInfo) -> Self {\n        Self(info, PhantomData)\n    }\n\n    /// Returns the V8 isolate associated with this function callback.\n    pub fn isolate(&self) -> IsolatePtr {\n        // SAFETY: self.0 is a valid FunctionCallbackInfo pointer (guaranteed by constructor).\n        unsafe { IsolatePtr::from_ffi(ffi::fci_get_isolate(self.0)) }\n    }\n\n    pub fn this(&self) -> Local<'a, Value> {\n        // SAFETY: self.0 is a valid FunctionCallbackInfo pointer (guaranteed by constructor).\n        unsafe { Local::from_ffi(self.isolate(), ffi::fci_get_this(self.0)) }\n    }\n\n    pub fn len(&self) -> usize {\n        // SAFETY: self.0 is a valid FunctionCallbackInfo pointer (guaranteed by constructor).\n        unsafe { ffi::fci_get_length(self.0) }\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.len() == 0\n    }\n\n    pub fn get(&self, index: usize) -> Local<'a, Value> {\n        debug_assert!(index <= self.len(), \"index out of bounds\");\n        // SAFETY: self.0 is a valid FunctionCallbackInfo pointer (guaranteed by constructor).\n        unsafe { Local::from_ffi(self.isolate(), ffi::fci_get_arg(self.0, index)) }\n    }\n\n    pub fn set_return_value(&self, value: Local<Value>) {\n        // SAFETY: self.0 is a valid FunctionCallbackInfo pointer (guaranteed by constructor).\n        unsafe {\n            ffi::fci_set_return_value(self.0, value.into_ffi());\n        }\n    }\n}\n\n/// A fat pointer to a `dyn GarbageCollected` trait object, decomposed into its\n/// data pointer and vtable pointer halves.\n///\n/// Rust trait object pointers (`*mut dyn Trait`) are fat pointers with the\n/// layout `[data_ptr, vtable_ptr]`. We decompose them so the two halves can be\n/// stored in the C++ `Wrappable::data[0..1]` slots (`uintptr_t`).\n///\n/// # Safety of the transmute\n///\n/// Rust guarantees that `*mut dyn Trait` is two pointers wide (the \"fat\n/// pointer\" representation). We transmute to `[*mut (); 2]` to split and\n/// rejoin. While the *exact* field order (`[data, vtable]`) is not\n/// stabilised in a language RFC, it has been the layout since Rust 1.0 and\n/// is relied upon by miri, the compiler test suite, and the unstable\n/// `ptr_metadata` API. A layout change would break the ecosystem; we add a\n/// compile-time size assert as a safety net.\n///\n/// TODO(rust-lang/rust#81513): Replace the transmute with `std::ptr::metadata`\n/// / `std::ptr::from_raw_parts_mut` once the `ptr_metadata` feature is\n/// stabilised. That will make the `[data, vtable]` ordering an explicit API\n/// contract rather than an assumed layout.\n// Fat pointer must be exactly two pointers wide.\nconst _: () = assert!(\n    size_of::<*mut dyn GarbageCollected>() == 2 * size_of::<usize>(),\n    \"trait object pointer must be two pointers wide\",\n);\n\nimpl Clone for ffi::TraitObjectPtr {\n    fn clone(&self) -> Self {\n        Self {\n            data_ptr: self.data_ptr,\n            vtable_ptr: self.vtable_ptr,\n            type_id_lo: self.type_id_lo,\n            type_id_hi: self.type_id_hi,\n        }\n    }\n}\n\nimpl ffi::TraitObjectPtr {\n    /// Creates a `TraitObjectPtr` from a raw `*mut dyn GarbageCollected` fat pointer\n    /// and the concrete type's `TypeId`.\n    pub(crate) fn from_raw(ptr: *mut dyn GarbageCollected, type_id: std::any::TypeId) -> Self {\n        // SAFETY: a fat pointer is layout-equivalent to [*mut (); 2].\n        let [data, vtable]: [*mut (); 2] = unsafe { std::mem::transmute(ptr) };\n\n        // Casting a fat pointer to *mut () is guaranteed to yield the data pointer.\n        // If the transmuted layout ever diverges from [data, vtable], this catches it.\n        debug_assert_eq!(\n            data.cast::<()>(),\n            ptr.cast::<()>(),\n            \"fat pointer layout assumption violated: expected [data, vtable]\"\n        );\n\n        assert!(!data.is_null(), \"Rc::into_raw returned null\");\n\n        // SAFETY: TypeId is 128 bits (two usize on 64-bit), transmute to split halves for FFI storage.\n        let [type_id_lo, type_id_hi]: [usize; 2] = unsafe { std::mem::transmute(type_id) };\n\n        Self {\n            data_ptr: data as usize,\n            vtable_ptr: vtable as usize,\n            type_id_lo,\n            type_id_hi,\n        }\n    }\n\n    /// Returns `true` if this trait object has been cleared (data pointer is null).\n    pub(crate) fn is_cleared(&self) -> bool {\n        self.data_ptr == 0\n    }\n\n    /// Reads the trait object pointer from a Wrappable.\n    ///\n    /// Returns `None` if the trait object has been cleared (resource already dropped).\n    fn from_wrappable(wrappable: &ffi::Wrappable) -> Option<&Self> {\n        // SAFETY: wrappable is valid (guaranteed by KjRc lifetime).\n        let ptr = unsafe { ffi::wrappable_get_trait_object(wrappable) };\n        if ptr.is_cleared() { None } else { Some(ptr) }\n    }\n\n    /// Returns the stored `TypeId`.\n    pub(crate) fn type_id(&self) -> std::any::TypeId {\n        // SAFETY: Reconstructing TypeId from the same [lo, hi] halves stored in from_raw.\n        unsafe { std::mem::transmute([self.type_id_lo, self.type_id_hi]) }\n    }\n\n    /// Returns the data pointer as a `NonNull<R>`.\n    ///\n    /// # Safety\n    /// The caller must have verified the `TypeId` matches `R`.\n    unsafe fn data_as<R>(&self) -> NonNull<R> {\n        // SAFETY: data_ptr is non-null (checked in from_raw) and TypeId was verified by caller.\n        unsafe { NonNull::new_unchecked(self.data_ptr as *mut R) }\n    }\n\n    /// Reconstructs a shared reference to the `dyn GarbageCollected` object.\n    ///\n    /// # Safety\n    /// The original object must still be alive for lifetime `'a`.\n    #[expect(clippy::needless_lifetimes)]\n    unsafe fn as_gc_ref<'a>(&'a self) -> &'a dyn GarbageCollected {\n        // SAFETY: transmuting [data, vtable] back into a fat pointer.\n        unsafe {\n            let fat_ptr: *const dyn GarbageCollected =\n                std::mem::transmute([self.data_ptr as *const (), self.vtable_ptr as *const ()]);\n            &*fat_ptr\n        }\n    }\n\n    /// Reconstructs the `Rc<dyn GarbageCollected>` and drops it.\n    ///\n    /// # Safety\n    /// The data pointer must have originated from `Rc::into_raw`.\n    /// Must only be called once per allocation.\n    unsafe fn drop_rc(&self) {\n        // SAFETY: transmuting [data, vtable] back into a fat pointer; Rc::from_raw\n        // reclaims the allocation originally created by Rc::into_raw in from_raw.\n        unsafe {\n            let fat_ptr: *const dyn GarbageCollected =\n                std::mem::transmute([self.data_ptr as *const (), self.vtable_ptr as *const ()]);\n            drop(Rc::from_raw(fat_ptr));\n        }\n    }\n\n    /// Zeroes the trait object in a Wrappable.\n    ///\n    /// Called before `drop_rc` so that any re-entrant `wrappable_invoke_trace`\n    /// calls during destruction see null and no-op.\n    ///\n    /// # Safety\n    /// Must only be called while holding exclusive access (e.g. via `Pin<&mut Wrappable>`).\n    unsafe fn clear_wrappable(wrappable: Pin<&mut ffi::Wrappable>) {\n        // SAFETY: wrappable is valid and exclusively accessed (caller holds Pin<&mut>).\n        unsafe { ffi::wrappable_clear_trait_object(wrappable) };\n    }\n}\n\n/// `TraitObjectPtr` → `WrappableRc`: allocates a new Wrappable on the KJ heap,\n/// transferring ownership of the `Rc`-backed `dyn GarbageCollected` fat pointer.\n///\n/// # Safety\n/// The data pointer must have come from `Rc::into_raw` and must not be used to\n/// reconstruct the Rc after this call (the Wrappable now owns it).\nimpl From<ffi::TraitObjectPtr> for WrappableRc {\n    fn from(ptr: ffi::TraitObjectPtr) -> Self {\n        Self {\n            // SAFETY: ptr contains a valid Rc::into_raw data pointer (guaranteed by caller).\n            handle: unsafe { ffi::wrappable_new(ptr) },\n        }\n    }\n}\n\nunsafe fn wrappable_invoke_drop(wrappable: Pin<&mut ffi::Wrappable>) {\n    // SAFETY: wrappable is valid and exclusively accessed (Pin<&mut>).\n    // Clear data slots before drop to prevent re-entrant trace during destruction,\n    // then drop the Rc<dyn GarbageCollected> to release the resource.\n    unsafe {\n        let Some(trait_ptr) = ffi::TraitObjectPtr::from_wrappable(wrappable.as_ref().get_ref())\n        else {\n            return;\n        };\n        // Clone before clearing — clear_wrappable_data zeroes data_ptr.\n        let saved = trait_ptr.clone();\n        ffi::TraitObjectPtr::clear_wrappable(wrappable);\n        saved.drop_rc();\n    }\n}\n\nunsafe fn wrappable_invoke_trace(wrappable: &ffi::Wrappable, visitor: *mut ffi::GcVisitor) {\n    // SAFETY: wrappable is valid. visitor is a valid C++ GcVisitor pointer.\n    unsafe {\n        let Some(trait_ptr) = ffi::TraitObjectPtr::from_wrappable(wrappable) else {\n            return;\n        };\n        let mut gc_visitor = GcVisitor::from_ffi(visitor);\n        trait_ptr.as_gc_ref().trace(&mut gc_visitor);\n    }\n}\n\nunsafe fn wrappable_invoke_get_name(wrappable: &ffi::Wrappable) -> &'static str {\n    // SAFETY: wrappable is valid.\n    unsafe {\n        let Some(trait_ptr) = ffi::TraitObjectPtr::from_wrappable(wrappable) else {\n            return \"\";\n        };\n        // memory_name() returns a &'static CStr (a compile-time NUL-terminated\n        // literal). Convert to &str so CXX can pass it as rust::Str. The C++\n        // side uses .data()/.size() directly as a kj::StringPtr — no copy.\n        trait_ptr.as_gc_ref().memory_name().to_str().unwrap_or(\"\")\n    }\n}\n\n/// Visitor for garbage collection tracing.\n///\n/// `GcVisitor` wraps a C++ `jsg::GcVisitor` pointer. All GC visitation logic\n/// (strong/traced switching, parent tracking) is handled by the C++ side via\n/// `Wrappable::visitRef()`.\n#[derive(Debug)]\npub struct GcVisitor {\n    pub(crate) handle: ffi::GcVisitor,\n}\n\nimpl GcVisitor {\n    /// Creates a `GcVisitor` from a raw FFI pointer.\n    ///\n    /// # Safety\n    ///\n    /// `visitor` must be a valid, non-null pointer to a live `ffi::GcVisitor`.\n    pub(crate) unsafe fn from_ffi(visitor: *mut ffi::GcVisitor) -> Self {\n        Self {\n            handle: ffi::GcVisitor {\n                // SAFETY: visitor is a valid, non-null pointer (guaranteed by caller).\n                ptr: unsafe { (*visitor).ptr },\n            },\n        }\n    }\n\n    /// Visits a `jsg::Rc<R>` field during GC tracing.\n    ///\n    /// Delegates to the C++ `Wrappable::visitRef()` which handles all the\n    /// strong/traced switching logic and transitive tracing.\n    pub fn visit_rc<R: crate::Resource>(&mut self, r: &crate::Rc<R>) {\n        r.visit(self);\n    }\n\n    /// Visits a `v8::Global<T>` field during GC tracing.\n    ///\n    /// Implements the same strong↔traced dual-mode switching that `jsg::Data`\n    /// / `jsg::V8Ref<T>` use in C++. When the parent `Wrappable` has strong\n    /// Rust refs the handle stays strong; once all Rust refs are dropped and\n    /// only the JS wrapper keeps it alive, the handle is downgraded to a\n    /// `v8::TracedReference` that cppgc can follow — allowing GC to detect\n    /// and break reference cycles.\n    ///\n    /// Accepts `&Global<T>` even though it mutates the `traced` slot inside the\n    /// handle. This is safe because:\n    /// - GC tracing is always single-threaded within a V8 isolate.\n    /// - `trace` is never re-entrant on the same object during a GC cycle.\n    /// - The mutation only touches `traced` (the weak traced handle slot),\n    ///   never `handle` (the strong handle), so the value observed through any\n    ///   other `&Global<T>` reference remains valid.\n    pub fn visit_global<T>(&mut self, global: &Global<T>) {\n        // SAFETY: `global.traced` is an `UnsafeCell`; accessing it via `get()`\n        // is sound under the single-threaded, non-reentrant GC tracing contract\n        // documented on `Global<T>::traced`.\n        unsafe {\n            ffi::wrappable_visit_global(\n                &raw mut self.handle,\n                (&raw const global.handle.ptr).cast_mut(),\n                &mut *global.traced.get(),\n            );\n        }\n    }\n}\n\n/// A safe wrapper around a V8 isolate pointer.\n///\n/// `IsolatePtr` provides a type-safe abstraction over raw `v8::Isolate*` pointers,\n/// ensuring that the pointer is always non-null. This type is `Copy` and can be\n/// freely passed around without worrying about ownership.\n///\n/// # Thread Safety\n///\n/// V8 isolates are single-threaded. While `IsolatePtr` itself is `Send` and `Sync`\n/// (as it's just a pointer wrapper), V8 operations must only be performed on the\n/// thread that owns the isolate lock. Use `is_locked()` to verify the current\n/// thread holds the lock before performing V8 operations.\n///\n/// # Example\n///\n/// ```ignore\n/// // Create from raw pointer (unsafe)\n/// let isolate = unsafe { v8::IsolatePtr::from_ffi(raw_ptr) };\n///\n/// // Check if locked before V8 operations\n/// assert!(unsafe { isolate.is_locked() });\n///\n/// // Get raw pointer for FFI calls\n/// let ptr = isolate.as_ffi();\n/// ```\n#[derive(Clone, Copy, Debug)]\npub struct IsolatePtr {\n    handle: NonNull<ffi::Isolate>,\n}\n\nimpl IsolatePtr {\n    /// Creates an `IsolatePtr` from a raw pointer.\n    ///\n    /// # Safety\n    /// The pointer must be non-null and point to a valid V8 isolate.\n    pub unsafe fn from_ffi(handle: *mut ffi::Isolate) -> Self {\n        // SAFETY: isolate pointer is valid (guaranteed by caller).\n        debug_assert!(unsafe { ffi::isolate_is_locked(handle) });\n        Self {\n            // SAFETY: handle is non-null (guaranteed by caller).\n            handle: unsafe { NonNull::new_unchecked(handle) },\n        }\n    }\n\n    /// Creates an `IsolatePtr` from a `NonNull` pointer.\n    pub fn from_non_null(handle: NonNull<ffi::Isolate>) -> Self {\n        // SAFETY: handle is non-null (guaranteed by NonNull) and points to a valid isolate.\n        debug_assert!(unsafe { ffi::isolate_is_locked(handle.as_ptr()) });\n        Self { handle }\n    }\n\n    /// Returns whether this isolate is currently locked by the current thread.\n    ///\n    /// # Safety\n    ///\n    /// The caller must ensure the isolate is still valid and not deallocated.\n    pub unsafe fn is_locked(&self) -> bool {\n        // SAFETY: isolate pointer is valid (guaranteed by caller).\n        unsafe { ffi::isolate_is_locked(self.handle.as_ptr()) }\n    }\n\n    /// Returns the raw pointer to the V8 isolate.\n    pub fn as_ffi(&self) -> *mut ffi::Isolate {\n        self.handle.as_ptr()\n    }\n\n    /// Returns the `NonNull` pointer to the V8 isolate.\n    pub fn as_non_null(&self) -> NonNull<ffi::Isolate> {\n        self.handle\n    }\n}\n\n// =============================================================================\n// Wrappable — owned, reference-counted handle\n// =============================================================================\n\n/// Owned, reference-counted handle to a C++ `Wrappable` on the KJ heap.\n///\n/// Encapsulates `KjRc<ffi::Wrappable>` so that modules outside `v8` never\n/// reference the CXX-generated `ffi::Wrappable` type directly.\n///\n/// `Clone` / `Drop` only affect the `KjRc` refcount (`kj::Rc` reference counting).\n/// GC strong-ref tracking (`addStrongRef` / `removeStrongRef`) is handled by\n/// `Ref<R>`, not here.\n#[derive(Clone)]\npub struct WrappableRc {\n    handle: kj_rs::KjRc<ffi::Wrappable>,\n}\n\nimpl WrappableRc {\n    /// Unwraps a JavaScript value to get an owned Wrappable handle.\n    ///\n    /// Returns `None` if the value is not a Rust-tagged Wrappable\n    /// (e.g. a C++ JSG object, a plain JS object, or a primitive).\n    ///\n    /// The C++ `unwrap_resource` returns a `KjRc<Wrappable>` whose inner\n    /// pointer is null when the value doesn't contain a Rust Wrappable.\n    /// We check `get().is_null()` to distinguish that case.\n    #[doc(hidden)]\n    pub fn from_js(isolate: IsolatePtr, value: Local<Value>) -> Option<Self> {\n        // SAFETY: isolate is valid and locked; value handle is valid.\n        let handle = unsafe { ffi::unwrap_resource(isolate.as_ffi(), value.into_ffi()) };\n        if handle.get().is_null() {\n            return None;\n        }\n        Some(Self { handle })\n    }\n\n    /// Wraps this Wrappable as a JavaScript object using the given constructor template.\n    pub(crate) fn to_js<'a>(\n        &self,\n        isolate: IsolatePtr,\n        constructor: &Global<FunctionTemplate>,\n    ) -> Local<'a, Value> {\n        // SAFETY: isolate is valid and locked; constructor global handle is valid.\n        unsafe {\n            Local::from_ffi(\n                isolate,\n                ffi::wrap_resource(\n                    isolate.as_ffi(),\n                    self.handle.clone(),\n                    constructor.as_ffi_ref(),\n                ),\n            )\n        }\n    }\n\n    /// Attaches this Wrappable to the `this` object in a V8 constructor callback.\n    ///\n    /// V8 has already created the `this` object from the `FunctionTemplate`'s\n    /// `InstanceTemplate`; this method attaches the Wrappable to it via\n    /// `CppgcShim` so that instance methods can resolve the resource.\n    pub fn attach_to_this(&self, info: &mut FunctionCallbackInfo) {\n        // SAFETY: info is valid for the duration of the callback.\n        let pin = unsafe { std::pin::Pin::new_unchecked(&mut *info.0) };\n        // SAFETY: The Pin guarantees info is valid. wrap_constructor attaches\n        // the Wrappable to args.This() and sets the Rust tag.\n        unsafe { ffi::wrappable_attach_wrapper(self.handle.clone(), pin) };\n    }\n\n    /// Creates an owning `WrappableRc` from a raw `*const ffi::Wrappable` pointer.\n    ///\n    /// Increments the `kj::Rc` refcount via `addRefToThis()`.\n    ///\n    /// # Safety\n    /// The pointed-to Wrappable must still be alive.\n    pub(crate) unsafe fn from_raw_wrappable(ptr: *const ffi::Wrappable) -> Self {\n        // SAFETY: ptr is valid and alive (caller verified via Weak::upgrade).\n        // The const-to-mut cast is sound for the same reason as as_pin_mut().\n        // wrappable_to_rc calls addRefToThis() which uses interior mutability.\n        unsafe {\n            let wrappable = Pin::new_unchecked(&mut *ptr.cast_mut());\n            Self {\n                handle: ffi::wrappable_to_rc(wrappable),\n            }\n        }\n    }\n\n    /// Returns a `Pin<&mut ffi::Wrappable>` for C++ FFI calls.\n    ///\n    /// Takes `&self` because the mutation target is the C++ `Wrappable` on the\n    /// KJ heap (behind the raw pointer in `KjRc`), not the `WrappableRc` wrapper\n    /// itself. `KjRc::get()` returns `*const T`; the const-to-mut cast is\n    /// required because CXX maps non-const C++ references (`T&`) to\n    /// `Pin<&mut T>`. `KjRc::get_mut()` cannot be used because it returns\n    /// `None` when the refcount > 1 (the common case).\n    ///\n    /// # Safety\n    ///\n    /// The returned `Pin<&mut Wrappable>` must be used transiently — passed\n    /// directly into a C++ FFI call and never stored. This prevents aliased\n    /// `&mut` references from coexisting. The invariant is enforced by:\n    ///\n    /// 1. **`pub(crate)` visibility** — only code in this crate can call this.\n    /// 2. **Single-threaded V8 isolate** — all callers run on the isolate's\n    ///    thread, so no concurrent access is possible.\n    /// 3. **No same-object re-entrancy** — the C++ methods called through this\n    ///    pin (`addStrongRef`, `removeStrongRef`, `visitRef`) may re-enter Rust\n    ///    for *different* Wrappables during GC tracing (e.g. `visitRef` traces\n    ///    children, which calls `GarbageCollected::trace()` on them), but never\n    ///    create a second `Pin<&mut Wrappable>` for the *same* object.\n    #[expect(\n        clippy::mut_from_ref,\n        reason = \"Pin<&mut> comes from a raw pointer, not from &self\"\n    )]\n    unsafe fn as_pin_mut(&self) -> Pin<&mut ffi::Wrappable> {\n        // SAFETY: KjRc pointer is valid; const-to-mut cast is sound (see doc comment above).\n        unsafe { Pin::new_unchecked(&mut *self.handle.get().cast_mut()) }\n    }\n\n    /// Visits this Wrappable during GC tracing.\n    ///\n    /// Takes `&self` because this is called from `Ref::visit(&self)` which is\n    /// called from `GarbageCollected::trace(&self)`. The mutation target is\n    /// the C++ `Wrappable` on the KJ heap, not the `WrappableRc` wrapper.\n    pub(crate) fn visit_rc(&self, parent: *mut usize, strong: *mut bool, visitor: &mut GcVisitor) {\n        // SAFETY: wrappable, parent, strong, and visitor pointers are all valid (guaranteed by callers).\n        unsafe {\n            ffi::wrappable_visit_ref(\n                self.as_pin_mut(),\n                parent,\n                strong,\n                std::ptr::from_mut(&mut visitor.handle),\n            );\n        }\n    }\n\n    /// Returns a `NonNull` pointer to the underlying `ffi::Wrappable`.\n    ///\n    /// The `KjRc` always holds a valid, non-null pointer to the Wrappable on\n    /// the KJ heap.\n    pub(crate) fn as_ptr(&self) -> NonNull<ffi::Wrappable> {\n        // SAFETY: KjRc always holds a valid, non-null pointer.\n        unsafe { NonNull::new_unchecked(self.handle.get().cast_mut()) }\n    }\n\n    /// Increments the strong reference count on the underlying Wrappable.\n    ///\n    /// Called when a new `Ref<R>` is created (clone, unwrap) to inform the GC\n    /// that this Wrappable has an additional strong reference from Rust.\n    pub(crate) fn add_strong_ref(&mut self) {\n        // SAFETY: wrappable is valid (guaranteed by KjRc lifetime).\n        unsafe { ffi::wrappable_add_strong_ref(self.as_pin_mut()) };\n    }\n\n    /// Decrements the strong reference count and potentially defers destruction.\n    ///\n    /// Called when a `Ref<R>` is dropped. Calls `maybeDeferDestruction` on\n    /// the C++ side with the ref's current `strong` flag. If `is_strong` is\n    /// true, `~RefToDelete` will call `removeStrongRef()`; if false (the ref\n    /// was already weakened by GC tracing), it skips the decrement.\n    pub(crate) fn remove_strong_ref(&mut self, is_strong: bool) {\n        // SAFETY: wrappable is valid (guaranteed by KjRc lifetime).\n        unsafe { ffi::wrappable_remove_strong_ref(self.as_pin_mut(), is_strong) };\n    }\n\n    /// Returns the current strong reference count from the C++ Wrappable.\n    #[cfg(debug_assertions)]\n    pub(crate) fn strong_refcount(&self) -> u32 {\n        // SAFETY: wrappable pointer is valid (guaranteed by KjRc lifetime).\n        unsafe { ffi::wrappable_strong_refcount(&*self.handle.get()) }\n    }\n\n    /// Resolves the `Rc::into_raw` pointer stored in the Wrappable as a typed `NonNull<R>`.\n    ///\n    /// Returns `None` if the trait object has been cleared or the stored `TypeId`\n    /// does not match `R`, preventing type confusion in all builds.\n    #[doc(hidden)]\n    pub fn resolve_resource<R: Resource>(&self) -> Option<NonNull<R>> {\n        // SAFETY: wrappable pointer is valid (guaranteed by KjRc lifetime).\n        let trait_ptr = unsafe {\n            let wrappable = &*self.handle.get();\n            ffi::TraitObjectPtr::from_wrappable(wrappable)?\n        };\n\n        if trait_ptr.type_id() != std::any::TypeId::of::<R>() {\n            return None;\n        }\n\n        // SAFETY: TypeId matched, so data_ptr is a valid Rc::into_raw pointer to R.\n        Some(unsafe { trait_ptr.data_as::<R>() })\n    }\n}\n"
  },
  {
    "path": "src/rust/jsg/wrappable.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#![allow(clippy::allow_attributes)]\n\n//! Traits for converting between Rust and JavaScript values.\n//!\n//! # Supported Types\n//!\n//! ## Primitive Types\n//!\n//! | Rust Type | JavaScript Type |\n//! |-----------|-----------------|\n//! | `()` | `undefined` |\n//! | `String` | `string` |\n//! | `&str` | `string` |\n//! | `bool` | `boolean` |\n//! | `Number` | `number` |\n//! | `Option<T>` | `T` or `undefined` |\n//! | `Nullable<T>` | `T`, `null`, or `undefined` |\n//! | `Result<T, E>` | `T` or throws |\n//! | `NonCoercible<T>` | `T` (strict type checking) |\n//! | `T: Struct` | `object` |\n//! | `Vec<T>` | `Array<T>` |\n//! | `&[T]` | `Array<T>` (parameter only) |\n//!\n//! ## `TypedArray` Types\n//!\n//! These specialized `Vec` and slice types map directly to JavaScript `TypedArray`s\n//! for efficient binary data transfer:\n//!\n//! | Rust Type | JavaScript Type |\n//! |-----------|-----------------|\n//! | `Vec<u8>` | `Uint8Array` |\n//! | `Vec<u16>` | `Uint16Array` |\n//! | `Vec<u32>` | `Uint32Array` |\n//! | `Vec<i8>` | `Int8Array` |\n//! | `Vec<i16>` | `Int16Array` |\n//! | `Vec<i32>` | `Int32Array` |\n//! | `Vec<f32>` | `Float32Array` |\n//! | `Vec<f64>` | `Float64Array` |\n//! | `Vec<i64>` | `BigInt64Array` |\n//! | `Vec<u64>` | `BigUint64Array` |\n//! | `&[u8]` | `Uint8Array` (parameter only) |\n//! | `&[u16]` | `Uint16Array` (parameter only) |\n//! | `&[u32]` | `Uint32Array` (parameter only) |\n//! | `&[i8]` | `Int8Array` (parameter only) |\n//! | `&[i16]` | `Int16Array` (parameter only) |\n//! | `&[i32]` | `Int32Array` (parameter only) |\n//! | `&[f32]` | `Float32Array` (parameter only) |\n//! | `&[f64]` | `Float64Array` (parameter only) |\n//! | `&[i64]` | `BigInt64Array` (parameter only) |\n//! | `&[u64]` | `BigUint64Array` (parameter only) |\n//!\n//! ## Integer Parameter Types\n//!\n//! Integer types (`u8`, `u16`, `u32`, `i8`, `i16`, `i32`) can be used as method\n//! parameters. JavaScript numbers are converted via truncation:\n//!\n//! - Values are truncated toward zero (e.g., `3.7` → `3`, `-2.9` → `-2`)\n//! - Out-of-range values wrap (e.g., `256.0` → `0` for `u8`)\n//! - `NaN` becomes `0`\n//!\n//! For strict validation, wrap parameters in `NonCoercible<T>` or validate manually.\n\nuse crate::Error;\nuse crate::Lock;\nuse crate::NonCoercible;\nuse crate::Nullable;\nuse crate::Number;\nuse crate::Type;\nuse crate::v8;\nuse crate::v8::ToLocalValue;\n\n// =============================================================================\n// ToJS trait (Rust → JavaScript)\n// =============================================================================\n\n/// Trait for converting Rust values to JavaScript.\n///\n/// Provides Rust → JavaScript conversion.\npub trait ToJS: Sized {\n    /// Converts this Rust value into a JavaScript value.\n    fn to_js<'a, 'b>(self, lock: &'a mut Lock) -> v8::Local<'b, v8::Value>\n    where\n        'b: 'a;\n}\n\n// =============================================================================\n// FromJS trait (JavaScript → Rust)\n// =============================================================================\n\n/// Trait for converting JavaScript values to Rust.\n///\n/// Provides JS → Rust conversion. The `try_unwrap` method is used by macros\n/// to unwrap function parameters with proper error handling.\npub trait FromJS: Sized {\n    type ResultType;\n\n    /// Converts a JavaScript value into this Rust type.\n    fn from_js(lock: &mut Lock, value: v8::Local<v8::Value>) -> Result<Self::ResultType, Error>;\n\n    /// Tries to convert only if the JavaScript type matches exactly.\n    /// Returns `None` if the type doesn't match, `Some(result)` if conversion was attempted.\n    /// Used by `#[jsg_oneof]` macro to try each variant without coercion.\n    fn try_from_js_exact(\n        lock: &mut Lock,\n        value: &v8::Local<v8::Value>,\n    ) -> Option<Result<Self::ResultType, Error>>\n    where\n        Self: Type,\n    {\n        if Self::is_exact(value) {\n            Some(Self::from_js(lock, value.clone()))\n        } else {\n            None\n        }\n    }\n}\n\n// =============================================================================\n// Primitive type implementations\n// =============================================================================\n\n/// Implements `Type`, `ToJS`, and `FromJS` for primitive types.\nmacro_rules! impl_primitive {\n    { $type:ty, $class_name:literal, $is_exact:ident, $unwrap_fn:ident } => {\n        impl Type for $type {\n            fn class_name() -> &'static str {\n                $class_name\n            }\n\n            fn is_exact(value: &v8::Local<v8::Value>) -> bool {\n                value.$is_exact()\n            }\n        }\n\n        impl ToJS for $type {\n            fn to_js<'a, 'b>(self, lock: &'a mut Lock) -> v8::Local<'b, v8::Value>\n            where\n                'b: 'a,\n            {\n                self.to_local(lock)\n            }\n        }\n\n        impl FromJS for $type {\n            type ResultType = Self;\n\n            fn from_js(lock: &mut Lock, value: v8::Local<v8::Value>) -> Result<Self::ResultType, Error> {\n                // SAFETY: The isolate is locked and value is a valid V8 local handle.\n                Ok(unsafe { v8::ffi::$unwrap_fn(lock.isolate().as_ffi(), value.into_ffi()) })\n            }\n        }\n    };\n}\n\nimpl_primitive!(String, \"string\", is_string, unwrap_string);\nimpl_primitive!(bool, \"boolean\", is_boolean, unwrap_boolean);\n\n// Number implementation for JavaScript numbers\nimpl Type for Number {\n    fn class_name() -> &'static str {\n        \"number\"\n    }\n\n    fn is_exact(value: &v8::Local<v8::Value>) -> bool {\n        value.is_number()\n    }\n}\n\nimpl ToJS for Number {\n    fn to_js<'a, 'b>(self, lock: &'a mut Lock) -> v8::Local<'b, v8::Value>\n    where\n        'b: 'a,\n    {\n        self.to_local(lock)\n    }\n}\n\nimpl FromJS for Number {\n    type ResultType = Self;\n\n    fn from_js(lock: &mut Lock, value: v8::Local<v8::Value>) -> Result<Self::ResultType, Error> {\n        // SAFETY: The isolate is locked and value is a valid V8 local handle.\n        let f64_value =\n            unsafe { v8::ffi::unwrap_number(lock.isolate().as_ffi(), value.into_ffi()) };\n        Ok(Self::new(f64_value))\n    }\n}\n\n// Special implementation for &str - allows functions to accept &str parameters\n// by converting JavaScript strings to owned Strings, then borrowing.\n// The macro handles passing &arg instead of arg for reference types.\nimpl Type for &str {\n    fn class_name() -> &'static str {\n        \"string\"\n    }\n\n    fn is_exact(value: &v8::Local<v8::Value>) -> bool {\n        value.is_string()\n    }\n}\n\nimpl FromJS for &str {\n    type ResultType = String;\n\n    fn from_js(lock: &mut Lock, value: v8::Local<v8::Value>) -> Result<Self::ResultType, Error> {\n        // SAFETY: The isolate is locked and value is a valid V8 local handle.\n        Ok(unsafe { v8::ffi::unwrap_string(lock.isolate().as_ffi(), value.into_ffi()) })\n    }\n}\n\nimpl<T: FromJS<ResultType = T>> FromJS for &T {\n    type ResultType = T;\n\n    fn from_js(lock: &mut Lock, value: v8::Local<v8::Value>) -> Result<Self::ResultType, Error> {\n        T::from_js(lock, value)\n    }\n}\n\n// Slice type - allows functions to accept &[T] parameters.\n// JavaScript arrays are converted to Vec<T>, then borrowed as &[T] by the macro.\nimpl<T: Type> Type for &[T] {\n    fn class_name() -> &'static str {\n        \"Array\"\n    }\n\n    fn is_exact(value: &v8::Local<v8::Value>) -> bool {\n        value.is_array()\n    }\n}\n\nimpl<T: Type + FromJS<ResultType = T>> FromJS for &[T] {\n    type ResultType = Vec<T>;\n\n    fn from_js(lock: &mut Lock, value: v8::Local<v8::Value>) -> Result<Self::ResultType, Error> {\n        Vec::<T>::from_js(lock, value)\n    }\n}\n\n// Integer types - JavaScript numbers are IEEE 754 doubles (f64)\n//\n// Conversion behavior:\n// - Values are truncated toward zero (e.g., 3.7 → 3, -2.9 → -2)\n// - Values outside the target type's range wrap around (e.g., 256.0 → 0 for u8)\n// - NaN becomes 0\n// - Infinity wraps to 0 for unsigned types, or type MIN/MAX for signed types\n//\n// This matches JavaScript's behavior for TypedArray element assignment.\n// For strict validation, use `NonCoercible<T>` or validate in your method.\nmacro_rules! impl_integer_from_js {\n    ($($type:ty),*) => {\n        $(\n            impl FromJS for $type {\n                type ResultType = Self;\n\n                #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]\n                fn from_js(lock: &mut Lock, value: v8::Local<v8::Value>) -> Result<Self::ResultType, Error> {\n                    // SAFETY: The isolate is locked and value is a valid V8 local handle.\n                    let num = unsafe { v8::ffi::unwrap_number(lock.isolate().as_ffi(), value.into_ffi()) };\n                    Ok(num as $type)\n                }\n            }\n        )*\n    };\n}\n\nimpl_integer_from_js!(u8, u16, u32, i8, i16, i32);\n\n// =============================================================================\n// Wrapper type implementations\n// =============================================================================\n\nimpl ToJS for () {\n    fn to_js<'a, 'b>(self, lock: &'a mut Lock) -> v8::Local<'b, v8::Value>\n    where\n        'b: 'a,\n    {\n        v8::Local::<v8::Value>::undefined(lock)\n    }\n}\n\nimpl<T: ToJS> ToJS for Option<T> {\n    fn to_js<'a, 'b>(self, lock: &'a mut Lock) -> v8::Local<'b, v8::Value>\n    where\n        'b: 'a,\n    {\n        match self {\n            Some(value) => value.to_js(lock),\n            None => v8::Local::<v8::Value>::undefined(lock),\n        }\n    }\n}\n\nimpl<T: Type + ToJS> ToJS for NonCoercible<T> {\n    fn to_js<'a, 'b>(self, lock: &'a mut Lock) -> v8::Local<'b, v8::Value>\n    where\n        'b: 'a,\n    {\n        self.into_inner().to_js(lock)\n    }\n}\n\nimpl<T: ToJS> ToJS for Nullable<T> {\n    fn to_js<'a, 'b>(self, lock: &'a mut Lock) -> v8::Local<'b, v8::Value>\n    where\n        'b: 'a,\n    {\n        match self {\n            Self::Some(value) => value.to_js(lock),\n            Self::Null => v8::Local::<v8::Value>::null(lock),\n            Self::Undefined => v8::Local::<v8::Value>::undefined(lock),\n        }\n    }\n}\n\nimpl<T: Type + FromJS> FromJS for Option<T> {\n    type ResultType = Option<T::ResultType>;\n\n    fn from_js(lock: &mut Lock, value: v8::Local<v8::Value>) -> Result<Self::ResultType, Error> {\n        if value.is_null() {\n            let msg = format!(\"Expected {} or undefined but got null\", T::class_name());\n            Err(Error::new_type_error(msg))\n        } else if value.is_undefined() {\n            Ok(None)\n        } else {\n            Ok(Some(T::from_js(lock, value)?))\n        }\n    }\n}\n\nimpl<T: Type + FromJS> FromJS for NonCoercible<T> {\n    type ResultType = NonCoercible<T::ResultType>;\n\n    fn from_js(lock: &mut Lock, value: v8::Local<v8::Value>) -> Result<Self::ResultType, Error> {\n        if !T::is_exact(&value) {\n            let error_msg = format!(\n                \"Expected a {} value but got {}\",\n                T::class_name(),\n                value.type_of()\n            );\n            return Err(Error::new_type_error(error_msg));\n        }\n        Ok(<Self::ResultType>::new(T::from_js(lock, value)?))\n    }\n}\n\nimpl<T: Type + FromJS> FromJS for Nullable<T> {\n    type ResultType = Nullable<T::ResultType>;\n\n    fn from_js(lock: &mut Lock, value: v8::Local<v8::Value>) -> Result<Self::ResultType, Error> {\n        if value.is_null() {\n            Ok(Nullable::Null)\n        } else if value.is_undefined() {\n            Ok(Nullable::Undefined)\n        } else {\n            Ok(Nullable::Some(T::from_js(lock, value)?))\n        }\n    }\n}\n\n// =============================================================================\n// Array type implementations (Vec<T>)\n// =============================================================================\n\nimpl<T: Type> Type for Vec<T> {\n    fn class_name() -> &'static str {\n        \"Array\"\n    }\n\n    fn is_exact(value: &v8::Local<v8::Value>) -> bool {\n        value.is_array()\n    }\n}\n\nimpl<T: ToJS> ToJS for Vec<T> {\n    fn to_js<'a, 'b>(self, lock: &'a mut Lock) -> v8::Local<'b, v8::Value>\n    where\n        'b: 'a,\n    {\n        let mut array = v8::Local::<v8::Array>::new(lock, self.len());\n        for (i, item) in self.into_iter().enumerate() {\n            array.set(i, item.to_js(lock));\n        }\n        array.into()\n    }\n}\n\nimpl<T: Type + FromJS<ResultType = T>> FromJS for Vec<T> {\n    type ResultType = Self;\n\n    fn from_js(lock: &mut Lock, value: v8::Local<v8::Value>) -> Result<Self::ResultType, Error> {\n        let type_name = value.type_of();\n        let array = value\n            .try_as::<v8::Array>()\n            .ok_or_else(|| Error::new_type_error(format!(\"Expected Array but got {type_name}\")))?;\n\n        let globals = array.iterate();\n        let mut result = Self::with_capacity(globals.len());\n        for global in globals {\n            let local = global.as_local(lock);\n            result.push(T::from_js(lock, local)?);\n        }\n        Ok(result)\n    }\n}\n\n// =============================================================================\n// TypedArray implementations\n// =============================================================================\n//\n// This macro generates implementations for TypedArray types:\n// - `v8::*Array` marker types: `Type` trait for type-safe V8 handles\n// - `Vec<T>`: `Type`, `ToJS`, `FromJS` for bidirectional conversion\n// - `&[T]`: `Type`, `FromJS` for parameter-only conversion (delegates to Vec<T>)\n//\n// Supported mappings:\n// - `Vec<u8>` / `&[u8]`   <-> `Uint8Array`\n// - `Vec<u16>` / `&[u16]` <-> `Uint16Array`\n// - `Vec<u32>` / `&[u32]` <-> `Uint32Array`\n// - `Vec<i8>` / `&[i8]`   <-> `Int8Array`\n// - `Vec<i16>` / `&[i16]` <-> `Int16Array`\n// - `Vec<i32>` / `&[i32]` <-> `Int32Array`\n// - `Vec<f32>` / `&[f32]` <-> `Float32Array`\n// - `Vec<f64>` / `&[f64]` <-> `Float64Array`\n// - `Vec<i64>` / `&[i64]` <-> `BigInt64Array`\n// - `Vec<u64>` / `&[u64]` <-> `BigUint64Array`\n\nmacro_rules! impl_typed_array {\n    ($elem:ty, $marker:ident, $is_check:ident, $new_fn:ident, $unwrap_fn:ident) => {\n        impl Type for v8::$marker {\n            fn class_name() -> &'static str {\n                stringify!($marker)\n            }\n            fn is_exact(value: &v8::Local<v8::Value>) -> bool {\n                value.$is_check()\n            }\n        }\n\n        impl Type for Vec<$elem> {\n            fn class_name() -> &'static str {\n                stringify!($marker)\n            }\n            fn is_exact(value: &v8::Local<v8::Value>) -> bool {\n                value.$is_check()\n            }\n        }\n\n        impl Type for &[$elem] {\n            fn class_name() -> &'static str {\n                stringify!($marker)\n            }\n            fn is_exact(value: &v8::Local<v8::Value>) -> bool {\n                value.$is_check()\n            }\n        }\n\n        impl ToJS for Vec<$elem> {\n            fn to_js<'a, 'b>(self, lock: &'a mut Lock) -> v8::Local<'b, v8::Value>\n            where\n                'b: 'a,\n            {\n                let isolate = lock.isolate();\n                // SAFETY: Lock guarantees the isolate is locked and a HandleScope is active.\n                unsafe {\n                    v8::Local::from_ffi(\n                        isolate,\n                        v8::ffi::$new_fn(isolate.as_ffi(), self.as_ptr(), self.len()),\n                    )\n                }\n            }\n        }\n\n        impl FromJS for Vec<$elem> {\n            type ResultType = Self;\n\n            fn from_js(lock: &mut Lock, value: v8::Local<v8::Value>) -> Result<Self, Error> {\n                if !value.$is_check() {\n                    return Err(Error::new_type_error(format!(\n                        \"Expected {} but got {}\",\n                        stringify!($marker),\n                        value.type_of()\n                    )));\n                }\n                // SAFETY: The isolate is locked and value is a valid V8 local handle of the correct TypedArray type.\n                Ok(unsafe { v8::ffi::$unwrap_fn(lock.isolate().as_ffi(), value.into_ffi()) })\n            }\n        }\n\n        impl FromJS for &[$elem] {\n            type ResultType = Vec<$elem>;\n\n            fn from_js(lock: &mut Lock, value: v8::Local<v8::Value>) -> Result<Vec<$elem>, Error> {\n                Vec::<$elem>::from_js(lock, value)\n            }\n        }\n    };\n}\n\nimpl_typed_array!(\n    u8,\n    Uint8Array,\n    is_uint8_array,\n    local_new_uint8_array,\n    unwrap_uint8_array\n);\nimpl_typed_array!(\n    u16,\n    Uint16Array,\n    is_uint16_array,\n    local_new_uint16_array,\n    unwrap_uint16_array\n);\nimpl_typed_array!(\n    u32,\n    Uint32Array,\n    is_uint32_array,\n    local_new_uint32_array,\n    unwrap_uint32_array\n);\nimpl_typed_array!(\n    i8,\n    Int8Array,\n    is_int8_array,\n    local_new_int8_array,\n    unwrap_int8_array\n);\nimpl_typed_array!(\n    i16,\n    Int16Array,\n    is_int16_array,\n    local_new_int16_array,\n    unwrap_int16_array\n);\nimpl_typed_array!(\n    i32,\n    Int32Array,\n    is_int32_array,\n    local_new_int32_array,\n    unwrap_int32_array\n);\nimpl_typed_array!(\n    f32,\n    Float32Array,\n    is_float32_array,\n    local_new_float32_array,\n    unwrap_float32_array\n);\nimpl_typed_array!(\n    f64,\n    Float64Array,\n    is_float64_array,\n    local_new_float64_array,\n    unwrap_float64_array\n);\nimpl_typed_array!(\n    i64,\n    BigInt64Array,\n    is_bigint64_array,\n    local_new_bigint64_array,\n    unwrap_bigint64_array\n);\nimpl_typed_array!(\n    u64,\n    BigUint64Array,\n    is_biguint64_array,\n    local_new_biguint64_array,\n    unwrap_biguint64_array\n);\n"
  },
  {
    "path": "src/rust/jsg-macros/BUILD.bazel",
    "content": "load(\"//:build/wd_rust_proc_macro.bzl\", \"wd_rust_proc_macro\")\n\nwd_rust_proc_macro(\n    name = \"jsg-macros\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@crates_vendor//:quote\",\n        \"@crates_vendor//:syn\",\n    ],\n)\n"
  },
  {
    "path": "src/rust/jsg-macros/README.md",
    "content": "# JSG Macros\n\nProcedural macros for JSG (JavaScript Glue) Rust bindings. These macros reduce boilerplate when implementing the JSG type system.\n\n## `#[jsg_struct]`\n\nGenerates the `jsg::Struct` and `jsg::Type` implementations for data structures. Only public fields are exposed to JavaScript. Automatically implements `class_name()` using the struct name, or a custom name if provided via the `name` parameter.\n\n```rust\n#[jsg_struct]\npub struct CaaRecord {\n    pub critical: f64,\n    pub field: String,\n    pub value: String,\n}\n\n#[jsg_struct(name = \"CustomName\")]\npub struct MyRecord {\n    pub value: String,\n}\n```\n\n## `#[jsg_method]`\n\nGenerates FFI callback functions for JSG resource methods. The `name` parameter is optional and defaults to converting the method name from `snake_case` to `camelCase`.\n\nThe macro automatically detects whether a method is an instance method or a static method based on the presence of a receiver (`&self` or `&mut self`):\n\n- **Instance methods** (with `&self`/`&mut self`) are placed on the prototype, called on instances (e.g., `obj.getName()`).\n- **Static methods** (without a receiver) are placed on the constructor, called on the class itself (e.g., `MyClass.create()`).\n\nParameters and return values are handled via the `jsg::FromJS` and `jsg::ToJS` traits. Any type implementing these traits can be used as a parameter or return value:\n\n- `Option<T>` - accepts `T` or `undefined`, rejects `null`\n- `Nullable<T>` - accepts `T`, `null`, or `undefined`\n- `NonCoercible<T>` - rejects values that would require JavaScript coercion\n\n```rust\nimpl DnsUtil {\n    // Instance method: called as obj.parseCaaRecord(...)\n    #[jsg_method(name = \"parseCaaRecord\")]\n    pub fn parse_caa_record(&self, record: String) -> Result<CaaRecord, DnsParserError> {\n        // Errors are thrown as JavaScript exceptions\n    }\n\n    // Instance method: called as obj.getName()\n    #[jsg_method]\n    pub fn get_name(&self) -> String {\n        self.name.clone()\n    }\n\n    // Instance method: void methods return undefined in JavaScript\n    #[jsg_method]\n    pub fn reset(&self) {\n    }\n\n    // Static method: called as DnsUtil.create(...)\n    #[jsg_method]\n    pub fn create(name: String) -> Result<String, jsg::Error> {\n        Ok(name)\n    }\n}\n```\n\n## `#[jsg_resource]`\n\nGenerates boilerplate for JSG resources. Applied to both struct definitions and impl blocks. Automatically implements `jsg::Type::class_name()` using the struct name, or a custom name if provided via the `name` parameter.\n\n```rust\n#[jsg_resource]\npub struct DnsUtil {}\n\n#[jsg_resource(name = \"CustomUtil\")]\npub struct MyUtil {\n    pub value: u32,\n}\n\n#[jsg_resource]\nimpl DnsUtil {\n    #[jsg_method]\n    pub fn parse_caa_record(&self, record: String) -> Result<CaaRecord, DnsParserError> {\n        // Instance method on the prototype\n    }\n\n    #[jsg_method]\n    pub fn create(name: String) -> Result<String, jsg::Error> {\n        // Static method on the constructor (no &self)\n    }\n}\n```\n\nOn struct definitions, generates:\n- `jsg::Type` implementation\n- `jsg::GarbageCollected` implementation with automatic field tracing (see below)\n- Wrapper struct and `ResourceTemplate` implementations\n\nOn impl blocks, scans for `#[jsg_method]` and `#[jsg_static_constant]` attributes and generates the `Resource` trait implementation. Methods with a receiver (`&self`/`&mut self`) are registered as instance methods; methods without a receiver are registered as static methods.\n\n## `#[jsg_static_constant]`\n\nExposes a Rust `const` item as a read-only static constant on both the JavaScript constructor and prototype. This is the Rust equivalent of `JSG_STATIC_CONSTANT` in C++ JSG.\n\nThe constant name is used as-is for the JavaScript property name (no camelCase conversion), matching the convention that constants are `UPPER_SNAKE_CASE` in both Rust and JavaScript. Only numeric types are supported (`i8`..`i64`, `u8`..`u64`, `f32`, `f64`).\n\n```rust\n#[jsg_resource]\nimpl WebSocket {\n    #[jsg_static_constant]\n    pub const CONNECTING: i32 = 0;\n\n    #[jsg_static_constant]\n    pub const OPEN: i32 = 1;\n\n    #[jsg_static_constant]\n    pub const CLOSING: i32 = 2;\n\n    #[jsg_static_constant]\n    pub const CLOSED: i32 = 3;\n}\n// In JavaScript:\n//   WebSocket.CONNECTING === 0\n//   WebSocket.OPEN === 1\n//   new WebSocket(...).CLOSING === 2  (also on instances via prototype)\n```\n\nPer Web IDL, constants are `{writable: false, enumerable: true, configurable: false}`.\n\n## `#[jsg_constructor]`\n\nMarks a static method as the JavaScript constructor for a `#[jsg_resource]`. When JavaScript calls `new MyClass(args)`, V8 invokes this method, creates a `jsg::Rc<Self>`, and attaches it to the `this` object.\n\n```rust\n#[jsg_resource]\nimpl MyResource {\n    #[jsg_constructor]\n    fn constructor(name: String) -> Self {\n        Self { name }\n    }\n}\n// JS: let r = new MyResource(\"hello\");\n```\n\nThe method must be static (no `self` receiver) and must return `Self`. Only one `#[jsg_constructor]` is allowed per impl block. The first parameter may be `&mut Lock` if the constructor needs isolate access — it is not exposed as a JS argument.\n\nIf no `#[jsg_constructor]` is present, `new MyClass()` throws an `Illegal constructor` error.\n\n## `#[jsg_oneof]`\n\nGenerates `jsg::Type` and `jsg::FromJS` implementations for union types. Use this to accept parameters that can be one of several JavaScript types.\n\nEach enum variant should be a single-field tuple variant where the field type implements `jsg::Type` and `jsg::FromJS` (e.g., `String`, `f64`, `bool`).\n\n```rust\nuse jsg_macros::jsg_oneof;\n\n#[jsg_oneof]\n#[derive(Debug, Clone)]\nenum StringOrNumber {\n    String(String),\n    Number(f64),\n}\n\nimpl MyResource {\n    #[jsg_method]\n    pub fn process(&self, value: StringOrNumber) -> Result<String, jsg::Error> {\n        match value {\n            StringOrNumber::String(s) => Ok(format!(\"string: {}\", s)),\n            StringOrNumber::Number(n) => Ok(format!(\"number: {}\", n)),\n        }\n    }\n}\n```\n\nThe macro generates type-checking code that matches JavaScript values to enum variants without coercion. If no variant matches, a `TypeError` is thrown listing all expected types.\n\n### Garbage Collection\n\nResources are automatically integrated with V8's garbage collector through the C++ `Wrappable` base class. The macro generates a `GarbageCollected` implementation that traces fields based on their type:\n\n| Field type | Behaviour |\n|---|---|\n| `jsg::Rc<T>` | Strong GC edge — keeps target alive |\n| `jsg::Weak<T>` | Not traced — does not keep target alive |\n| `jsg::v8::Global<T>` | Dual strong/traced — enables back-reference cycle collection |\n| Anything else | Not traced — plain data, ignored by tracer |\n\n`Option<F>` and `jsg::Nullable<F>` wrappers are supported for all traced field types and are traced only when `Some`. Any traced field type may also be wrapped in `Cell<F>` (or `std::cell::Cell<F>`) for interior mutability — required when the field is set after construction, since `GarbageCollected::trace` receives `&self`.\n\n#### `jsg::v8::Global<T>` and cycle collection\n\n`jsg::v8::Global<T>` fields use the same strong↔traced dual-mode as C++ `jsg::V8Ref<T>`. While the parent resource has strong Rust `Rc` refs the handle stays strong; once all `Rc`s are dropped, `visit_global` downgrades the handle to a `v8::TracedReference` that cppgc can follow — allowing back-reference cycles (e.g. a resource holding a callback that captures its own JS wrapper) to be collected by the next full GC.\n\n```rust\nuse std::cell::Cell;\n\n#[jsg_resource]\npub struct MyResource {\n    // Strong GC edge — keeps child alive\n    child: jsg::Rc<ChildResource>,\n\n    // Conditionally traced\n    maybe_child: Option<jsg::Rc<ChildResource>>,\n\n    // Weak — does not keep target alive\n    observer: jsg::Weak<ChildResource>,\n\n    // JS value — traced with dual-mode switching; Cell needed because\n    // the callback is set after construction (trace takes &self)\n    callback: Cell<Option<jsg::v8::Global<jsg::v8::Value>>>,\n\n    // Plain data — not traced\n    name: String,\n}\n```\n\nFor complex cases or custom tracing logic, you can manually implement `GarbageCollected` without using the `jsg_resource` macro:\n\n```rust\npub struct CustomResource {\n    data: String,\n}\n\nimpl jsg::GarbageCollected for CustomResource {\n    fn trace(&self, visitor: &mut jsg::GcVisitor) {\n        // Custom tracing logic\n    }\n}\n```\n"
  },
  {
    "path": "src/rust/jsg-macros/lib.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nuse proc_macro::TokenStream;\nuse quote::ToTokens;\nuse quote::quote;\nuse syn::Data;\nuse syn::DeriveInput;\nuse syn::Fields;\nuse syn::FnArg;\nuse syn::ItemFn;\nuse syn::ItemImpl;\nuse syn::Type;\nuse syn::parse_macro_input;\n\n/// Generates `jsg::Struct` and `jsg::Type` implementations for data structures.\n///\n/// Only public fields are included in the generated JavaScript object.\n/// Use `name` parameter for custom JavaScript class name.\n#[proc_macro_attribute]\npub fn jsg_struct(attr: TokenStream, item: TokenStream) -> TokenStream {\n    let input = parse_macro_input!(item as DeriveInput);\n    let name = &input.ident;\n    let class_name = extract_name_attribute(attr).unwrap_or_else(|| name.to_string());\n\n    let named_fields = match extract_named_fields(&input, \"jsg_struct\") {\n        Ok(fields) => fields,\n        Err(err) => return err,\n    };\n\n    let mut field_assignments = Vec::new();\n    let mut field_extractions = Vec::new();\n    let mut field_names = Vec::new();\n\n    for field in &named_fields {\n        // Only public fields are projected into JavaScript objects.\n        if !matches!(field.vis, syn::Visibility::Public(_)) {\n            continue;\n        }\n        // Named fields always have an ident; guard is purely defensive.\n        let Some(field_name) = field.ident.as_ref() else {\n            continue;\n        };\n        let field_name_str = field_name.to_string();\n        let field_type = &field.ty;\n\n        field_assignments.push(quote! {\n            let #field_name = jsg::v8::ToLocalValue::to_local(&this.#field_name, lock);\n            obj.set(lock, #field_name_str, #field_name);\n        });\n        field_extractions.push(quote! {\n            let #field_name = {\n                let prop = obj.get(lock, #field_name_str)\n                    .ok_or_else(|| jsg::Error::new_type_error(\n                        format!(\"Missing property '{}'\", #field_name_str)\n                    ))?;\n                <#field_type as jsg::FromJS>::from_js(lock, prop)?\n            };\n        });\n        field_names.push(field_name);\n    }\n\n    quote! {\n        #input\n\n        impl jsg::Type for #name {\n            fn class_name() -> &'static str { #class_name }\n\n            fn is_exact(value: &jsg::v8::Local<jsg::v8::Value>) -> bool {\n                value.is_object()\n            }\n        }\n\n        impl jsg::ToJS for #name {\n            fn to_js<'a, 'b>(self, lock: &'a mut jsg::Lock) -> jsg::v8::Local<'b, jsg::v8::Value>\n            where\n                'b: 'a,\n            {\n                // TODO(soon): Use a precached ObjectTemplate instance to create the object,\n                // similar to how C++ JSG optimizes object creation. This would avoid recreating\n                // the object shape on every wrap() call and improve performance.\n                {\n                    let this = self;\n                    let mut obj = lock.new_object();\n                    #(#field_assignments)*\n                    obj.into()\n                }\n            }\n        }\n\n        impl jsg::FromJS for #name {\n            type ResultType = Self;\n\n            fn from_js(lock: &mut jsg::Lock, value: jsg::v8::Local<jsg::v8::Value>) -> Result<Self::ResultType, jsg::Error> {\n                if !value.is_object() {\n                    return Err(jsg::Error::new_type_error(\n                        format!(\"Expected object but got {}\", value.type_of())\n                    ));\n                }\n                let obj: jsg::v8::Local<'_, jsg::v8::Object> = value.into();\n                #(#field_extractions)*\n                Ok(Self { #(#field_names),* })\n            }\n        }\n\n        impl jsg::Struct for #name {}\n    }\n    .into()\n}\n\n/// Generates FFI callback for JSG methods.\n///\n/// Parameters and return values are handled via `jsg::FromJS`.\n/// See `jsg/wrappable.rs` for supported types.\n#[proc_macro_attribute]\npub fn jsg_method(_attr: TokenStream, item: TokenStream) -> TokenStream {\n    let input_fn = parse_macro_input!(item as ItemFn);\n    let fn_name = &input_fn.sig.ident;\n    let fn_vis = &input_fn.vis;\n    let fn_sig = &input_fn.sig;\n    let fn_block = &input_fn.block;\n    let callback_name = syn::Ident::new(&format!(\"{fn_name}_callback\"), fn_name.span());\n\n    // Methods with a receiver (&self, &mut self) become instance methods on the prototype.\n    // Methods without a receiver become static methods on the constructor.\n    let has_self = fn_sig\n        .inputs\n        .iter()\n        .any(|arg| matches!(arg, FnArg::Receiver(_)));\n\n    let params: Vec<_> = fn_sig\n        .inputs\n        .iter()\n        .filter_map(|arg| match arg {\n            FnArg::Typed(pat_type) => Some(&pat_type.ty),\n            FnArg::Receiver(_) => None,\n        })\n        .collect();\n\n    // Check if the first typed parameter is `&mut Lock` — if so, pass `&mut lock`\n    // directly instead of extracting it from JS args (like C++ jsg::Lock&).\n    let has_lock_param = params.first().is_some_and(|ty| is_lock_ref(ty));\n    let js_arg_offset = usize::from(has_lock_param);\n\n    let (unwraps, arg_exprs): (Vec<_>, Vec<_>) = params\n        .iter()\n        .enumerate()\n        .map(|(i, ty)| {\n            // First param is &mut Lock — pass the callback's lock directly.\n            if i == 0 && has_lock_param {\n                let unwrap = quote! {};\n                let arg_expr = quote! { &mut lock };\n                return (unwrap, arg_expr);\n            }\n\n            let js_index = i - js_arg_offset;\n            let arg = syn::Ident::new(&format!(\"arg{js_index}\"), fn_name.span());\n            let unwrap = quote! {\n                let #arg = match <#ty as jsg::FromJS>::from_js(&mut lock, args.get(#js_index)) {\n                    Ok(v) => v,\n                    Err(err) => {\n                        lock.throw_exception(&err);\n                        return;\n                    }\n                };\n            };\n            // For reference types (like &str), FromJS returns an owned type (String),\n            // so we need to borrow it when passing to the function.\n            let is_ref = matches!(ty.as_ref(), syn::Type::Reference(_));\n            let arg_expr = if is_ref {\n                quote! { &#arg }\n            } else {\n                quote! { #arg }\n            };\n            (unwrap, arg_expr)\n        })\n        .unzip();\n\n    // Check if return type is Result<T, E>\n    let is_result = matches!(&fn_sig.output, syn::ReturnType::Type(_, ty) if is_result_type(ty));\n\n    let result_handling = if is_result {\n        quote! {\n            match result {\n                Ok(value) => args.set_return_value(jsg::ToJS::to_js(value, &mut lock)),\n                Err(err) => lock.throw_exception(&err.into()),\n            }\n        }\n    } else {\n        quote! {\n            args.set_return_value(jsg::ToJS::to_js(result, &mut lock));\n        }\n    };\n\n    let invocation = if has_self {\n        quote! {\n            let this = args.this();\n            // SAFETY: `v8::Signature` (passed to `FunctionTemplate::New` in\n            // `create_resource_template`) enforces that V8 only dispatches this\n            // callback when `this` is an instance of the resource's own\n            // `FunctionTemplate`. If the caller destructures the method and calls\n            // it with a wrong receiver (e.g. `const {abort} = ac; abort()`), V8\n            // throws a `TypeError: Illegal invocation` *before* reaching this\n            // code. Given that guarantee, `from_js` / `resolve_resource` perform\n            // a belt-and-suspenders `TypeId` check; the `.expect` panics\n            // (aborting the isolate) rather than triggering UB on a mismatch.\n            // The `&mut` is sound because V8 is single-threaded and no other\n            // Rust code can alias the resource during the callback.\n            let self_: &mut Self = unsafe {\n                let wrappable = jsg::v8::WrappableRc::from_js(lock.isolate(), this)\n                    .expect(\"receiver is not a Rust-wrapped resource\");\n                &mut *wrappable.resolve_resource::<Self>()\n                    .expect(\"type mismatch in resource callback\")\n                    .as_ptr()\n            };\n            let result = self_.#fn_name(#(#arg_exprs),*);\n        }\n    } else {\n        quote! {\n            let result = Self::#fn_name(#(#arg_exprs),*);\n        }\n    };\n\n    quote! {\n        #fn_vis #fn_sig { #fn_block }\n\n        #[automatically_derived]\n        #[expect(clippy::undocumented_unsafe_blocks)]\n        extern \"C\" fn #callback_name(args: *mut jsg::v8::ffi::FunctionCallbackInfo) {\n            let mut lock = unsafe { jsg::Lock::from_args(args) };\n            jsg::catch_panic(&mut lock, || {\n                let mut lock = unsafe { jsg::Lock::from_args(args) };\n                let mut args = unsafe { jsg::v8::FunctionCallbackInfo::from_ffi(args) };\n                #(#unwraps)*\n                #invocation\n                #result_handling\n            });\n        }\n    }\n    .into()\n}\n\n/// Generates boilerplate for JSG resources.\n///\n/// On structs: generates `jsg::Type`, `jsg::ToJS`, `jsg::FromJS`, and `jsg::GarbageCollected`.\n/// On impl blocks: generates `Resource` trait with method registrations.\n///\n/// The generated `GarbageCollected` implementation automatically traces fields that\n/// need GC integration:\n/// - `Rc<T>` fields - traced as a strong GC edge\n/// - `Option<Rc<T>>` / `Nullable<Rc<T>>` - conditionally traced when `Some`\n/// - `Weak<T>` fields - not traced (weak references don't keep the target alive)\n#[proc_macro_attribute]\npub fn jsg_resource(attr: TokenStream, item: TokenStream) -> TokenStream {\n    if let Ok(impl_block) = syn::parse::<ItemImpl>(item.clone()) {\n        return generate_resource_impl(&impl_block);\n    }\n\n    let input = parse_macro_input!(item as DeriveInput);\n    let name: &syn::Ident = &input.ident;\n\n    let class_name = if attr.is_empty() {\n        name.to_string()\n    } else {\n        extract_name_attribute(attr).unwrap_or_else(|| name.to_string())\n    };\n\n    let fields = match extract_named_fields(&input, \"jsg_resource\") {\n        Ok(fields) => fields,\n        Err(err) => return err,\n    };\n\n    // Generate trace statements for traceable fields\n    let trace_statements = generate_trace_statements(&fields);\n    let name_str = name.to_string();\n    let gc_impl = quote! {\n        #[automatically_derived]\n        impl jsg::GarbageCollected for #name {\n            fn trace(&self, visitor: &mut jsg::GcVisitor) {\n                // Suppress unused warning when there are no traceable fields.\n                let _ = visitor;\n                #(#trace_statements)*\n            }\n\n            fn memory_name(&self) -> &'static ::std::ffi::CStr {\n                // from_bytes_with_nul on a concat!(name, \"\\0\") literal is a\n                // compile-time constant expression — the compiler folds the\n                // unwrap and emits a direct pointer into the read-only data\n                // segment. The C++ side constructs a kj::StringPtr directly\n                // from data()+size() with no allocation.\n                ::std::ffi::CStr::from_bytes_with_nul(concat!(#name_str, \"\\0\").as_bytes())\n                    .unwrap()\n            }\n        }\n    };\n\n    quote! {\n        #input\n\n        #[automatically_derived]\n        impl jsg::Type for #name {\n            fn class_name() -> &'static str { #class_name }\n\n            fn is_exact(value: &jsg::v8::Local<jsg::v8::Value>) -> bool {\n                value.is_object()\n            }\n        }\n\n        #[automatically_derived]\n        impl jsg::ToJS for #name {\n            fn to_js<'a, 'b>(self, lock: &'a mut jsg::Lock) -> jsg::v8::Local<'b, jsg::v8::Value>\n            where\n                'b: 'a,\n            {\n                let r = jsg::Rc::new(self);\n                r.to_js(lock)\n            }\n        }\n\n        #[automatically_derived]\n        impl jsg::FromJS for #name {\n            type ResultType = jsg::Rc<Self>;\n\n            fn from_js(\n                lock: &mut jsg::Lock,\n                value: jsg::v8::Local<jsg::v8::Value>,\n            ) -> Result<Self::ResultType, jsg::Error> {\n                <jsg::Rc<Self> as jsg::FromJS>::from_js(lock, value)\n            }\n        }\n\n        #gc_impl\n    }\n    .into()\n}\n\n#[derive(Debug, Clone, Copy, PartialEq)]\nenum TraceableType {\n    /// `jsg::Rc<T>` — strong GC edge; visited via `GcVisitor::visit_rc`.\n    Ref,\n    /// `jsg::Weak<T>` — weak reference, not traced (doesn't keep the target alive).\n    Weak,\n    /// `jsg::v8::Global<T>` — JS value strong/traced dual-mode handle;\n    /// visited via `GcVisitor::visit_global`.\n    Global,\n    /// Not a traceable type.\n    None,\n}\n\nenum OptionalKind {\n    Option,\n    Nullable,\n}\n\n/// Checks if a type path matches a known JSG traceable type.\n///\n/// Matches `jsg::Rc<T>`, `jsg::Weak<T>`, and `jsg::v8::Global<T>`.\n/// All must be fully qualified — this avoids confusion with same-named types\n/// from other crates.\nfn get_traceable_type(ty: &Type) -> TraceableType {\n    if let Type::Path(type_path) = ty {\n        let segments = &type_path.path.segments;\n\n        // `jsg::Rc<T>` or `jsg::Weak<T>` — exactly 2 segments.\n        if segments.len() == 2 && segments[0].ident == \"jsg\" {\n            match segments[1].ident.to_string().as_str() {\n                \"Rc\" => return TraceableType::Ref,\n                \"Weak\" => return TraceableType::Weak,\n                _ => {}\n            }\n        }\n\n        // `jsg::v8::Global<T>` — exactly 3 segments.\n        if segments.len() == 3\n            && segments[0].ident == \"jsg\"\n            && segments[1].ident == \"v8\"\n            && segments[2].ident == \"Global\"\n        {\n            return TraceableType::Global;\n        }\n    }\n    TraceableType::None\n}\n\n/// Extracts the inner type from `Option<T>` or `Nullable<T>` if present.\nfn extract_optional_inner(ty: &Type) -> Option<(OptionalKind, &Type)> {\n    if let Type::Path(type_path) = ty\n        && let Some(segment) = type_path.path.segments.last()\n        && let syn::PathArguments::AngleBracketed(args) = &segment.arguments\n        && let Some(syn::GenericArgument::Type(inner)) = args.args.first()\n    {\n        let kind = match segment.ident.to_string().as_str() {\n            \"Option\" => OptionalKind::Option,\n            \"Nullable\" => OptionalKind::Nullable,\n            _ => return None,\n        };\n        return Some((kind, inner));\n    }\n    None\n}\n\n/// Extracts the inner type `T` from `Cell<T>` or `std::cell::Cell<T>` if present.\n///\n/// Accepts both unqualified `Cell<T>` and fully-qualified `std::cell::Cell<T>`.\nfn extract_cell_inner(ty: &Type) -> Option<&Type> {\n    if let Type::Path(type_path) = ty {\n        let segments = &type_path.path.segments;\n\n        // `Cell<T>` — single segment.\n        let cell_seg = if segments.len() == 1 && segments[0].ident == \"Cell\" {\n            &segments[0]\n        // `std::cell::Cell<T>` — three segments.\n        } else if segments.len() == 3\n            && segments[0].ident == \"std\"\n            && segments[1].ident == \"cell\"\n            && segments[2].ident == \"Cell\"\n        {\n            &segments[2]\n        } else {\n            return None;\n        };\n\n        if let syn::PathArguments::AngleBracketed(args) = &cell_seg.arguments\n            && let Some(syn::GenericArgument::Type(inner)) = args.args.first()\n        {\n            return Some(inner);\n        }\n    }\n    None\n}\n\n/// Generates a trace statement for a field whose type is known to be a `Cell<T>`.\n///\n/// Because `GarbageCollected::trace` receives `&self`, `Cell<T>` fields cannot be\n/// accessed through normal Rust references (they require `&mut self` or `T: Copy`).\n/// We use `Cell::as_ptr` to obtain a raw pointer and dereference it for mutable\n/// access.  This is safe because:\n///\n/// - V8 GC tracing is always single-threaded within an isolate.\n/// - `trace` is never re-entrant on the same object during a single GC cycle.\nfn generate_cell_trace_statement(\n    field_name: &syn::Ident,\n    cell_inner_ty: &Type,\n) -> Option<quote::__private::TokenStream> {\n    match get_traceable_type(cell_inner_ty) {\n        // Cell<jsg::Rc<T>> — strong Rc reference inside a Cell, read-only visit.\n        TraceableType::Ref => {\n            return Some(quote! {\n                // SAFETY: trace() is single-threaded and never re-entrant.\n                // We only read through the pointer.\n                unsafe { visitor.visit_rc(&*self.#field_name.as_ptr()); }\n            });\n        }\n        // Cell<jsg::v8::Global<T>> — visit_global takes &Global (safe).\n        TraceableType::Global => {\n            return Some(quote! {\n                // SAFETY: Cell::as_ptr() dereference is sound because GC\n                // tracing is single-threaded and never re-entrant on the\n                // same object. visit_global itself is safe.\n                unsafe { visitor.visit_global(&*self.#field_name.as_ptr()); }\n            });\n        }\n        TraceableType::Weak | TraceableType::None => {}\n    }\n\n    // Cell<Option<jsg::Rc<T>>> or Cell<jsg::Nullable<jsg::Rc<T>>>.\n    if let Some((kind, inner_ty)) = extract_optional_inner(cell_inner_ty) {\n        let pattern = match kind {\n            OptionalKind::Option => quote! { Some(inner) },\n            OptionalKind::Nullable => quote! { jsg::Nullable::Some(inner) },\n        };\n        match get_traceable_type(inner_ty) {\n            TraceableType::Ref => {\n                return Some(quote! {\n                    // SAFETY: trace() is single-threaded and never re-entrant.\n                    if let #pattern = unsafe { &*self.#field_name.as_ptr() } {\n                        visitor.visit_rc(inner);\n                    }\n                });\n            }\n            TraceableType::Global => {\n                return Some(quote! {\n                    // SAFETY: Cell::as_ptr() dereference is sound because GC\n                    // tracing is single-threaded and never re-entrant on the\n                    // same object. visit_global itself is safe.\n                    if let #pattern = unsafe { &*self.#field_name.as_ptr() } {\n                        visitor.visit_global(inner);\n                    }\n                });\n            }\n            TraceableType::Weak | TraceableType::None => {}\n        }\n    }\n\n    None\n}\n\n/// Generates trace statements for all traceable fields in a struct.\nfn generate_trace_statements(\n    fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,\n) -> Vec<quote::__private::TokenStream> {\n    fields\n        .iter()\n        .filter_map(|field| {\n            let field_name = field.ident.as_ref()?;\n            let ty = &field.ty;\n\n            // Check if this field is wrapped in a `Cell<T>`.\n            if let Some(cell_inner_ty) = extract_cell_inner(ty) {\n                return generate_cell_trace_statement(field_name, cell_inner_ty);\n            }\n\n            // Check if it's Option<Traceable> or Nullable<Traceable>\n            if let Some((kind, inner_ty)) = extract_optional_inner(ty) {\n                let pattern = match kind {\n                    OptionalKind::Option => quote! { Some(ref inner) },\n                    OptionalKind::Nullable => quote! { jsg::Nullable::Some(ref inner) },\n                };\n                match get_traceable_type(inner_ty) {\n                    // Rc<T> is a strong reference — visit it so the GC knows the edge.\n                    TraceableType::Ref => {\n                        return Some(quote! {\n                            if let #pattern = self.#field_name {\n                                visitor.visit_rc(inner);\n                            }\n                        });\n                    }\n                    // Global<T> — visit_global takes &Global (safe).\n                    TraceableType::Global => {\n                        return Some(quote! {\n                            if let #pattern = self.#field_name {\n                                visitor.visit_global(inner);\n                            }\n                        });\n                    }\n                    // Weak<T> doesn't keep the target alive and has no GC edges to trace.\n                    TraceableType::Weak | TraceableType::None => {}\n                }\n            }\n\n            match get_traceable_type(ty) {\n                TraceableType::Ref => Some(quote! {\n                    visitor.visit_rc(&self.#field_name);\n                }),\n                // visit_global takes &Global and handles interior mutation safely.\n                TraceableType::Global => Some(quote! {\n                    visitor.visit_global(&self.#field_name);\n                }),\n                // Weak<T> doesn't keep the target alive and has no GC edges to trace.\n                TraceableType::Weak | TraceableType::None => None,\n            }\n        })\n        .collect()\n}\n\nfn generate_resource_impl(impl_block: &ItemImpl) -> TokenStream {\n    let self_ty = &impl_block.self_ty;\n\n    if !matches!(&**self_ty, syn::Type::Path(_)) {\n        return error(\n            self_ty,\n            \"#[jsg_resource] impl blocks must use a simple path type (e.g., `impl MyResource`)\",\n        );\n    }\n\n    let method_registrations: Vec<_> = impl_block\n        .items\n        .iter()\n        .filter_map(|item| {\n            // Skip non-function items (e.g. type aliases, consts).\n            let syn::ImplItem::Fn(method) = item else {\n                return None;\n            };\n\n            // Only methods annotated with #[jsg_method] are registered.\n            let attr = method.attrs.iter().find(|a| is_attr(a, \"jsg_method\"))?;\n\n            let rust_method_name = &method.sig.ident;\n            // Use explicit name from #[jsg_method(name = \"...\")] if provided,\n            // otherwise convert snake_case to camelCase.\n            let js_name = attr\n                .meta\n                .require_list()\n                .ok()\n                .map(|list| list.tokens.clone().into())\n                .and_then(extract_name_attribute)\n                .unwrap_or_else(|| snake_to_camel(&rust_method_name.to_string()));\n            let callback_name = syn::Ident::new(\n                &format!(\"{rust_method_name}_callback\"),\n                rust_method_name.span(),\n            );\n\n            // Methods with a receiver (&self, &mut self) become instance methods on the prototype.\n            // Methods without a receiver become static methods on the constructor.\n            let has_self = method\n                .sig\n                .inputs\n                .iter()\n                .any(|arg| matches!(arg, FnArg::Receiver(_)));\n\n            let member = if has_self {\n                quote! {\n                    jsg::Member::Method {\n                        name: #js_name.to_owned(),\n                        callback: Self::#callback_name,\n                    }\n                }\n            } else {\n                quote! {\n                    jsg::Member::StaticMethod {\n                        name: #js_name.to_owned(),\n                        callback: Self::#callback_name,\n                    }\n                }\n            };\n            Some(member)\n        })\n        .collect();\n\n    let constant_registrations: Vec<_> = impl_block\n        .items\n        .iter()\n        .filter_map(|item| {\n            let syn::ImplItem::Const(constant) = item else {\n                return None;\n            };\n            let attr = constant.attrs.iter().find(|a| {\n                a.path().is_ident(\"jsg_static_constant\")\n                    || a.path()\n                        .segments\n                        .last()\n                        .is_some_and(|s| s.ident == \"jsg_static_constant\")\n            })?;\n\n            let rust_name = &constant.ident;\n            let js_name = attr\n                .meta\n                .require_list()\n                .ok()\n                .map(|list| list.tokens.clone().into())\n                .and_then(extract_name_attribute)\n                .unwrap_or_else(|| rust_name.to_string());\n\n            Some(quote! {\n                jsg::Member::StaticConstant {\n                    name: #js_name.to_owned(),\n                    value: jsg::ConstantValue::from(Self::#rust_name),\n                }\n            })\n        })\n        .collect();\n\n    let constructor_registration = generate_constructor_registration(impl_block, self_ty);\n\n    let constructor_vec: Vec<_> = constructor_registration.into_iter().collect();\n\n    quote! {\n        #impl_block\n\n        #[automatically_derived]\n        impl jsg::Resource for #self_ty {\n            fn members() -> Vec<jsg::Member>\n            where\n                Self: Sized,\n            {\n                vec![\n                    #(#constructor_vec,)*\n                    #(#method_registrations,)*\n                    #(#constant_registrations,)*\n                ]\n            }\n        }\n    }\n    .into()\n}\n\n/// Scans an impl block for a `#[jsg_constructor]` attribute and generates the\n/// constructor callback registration. Returns `None` if no constructor is defined.\n/// Validates that a `#[jsg_constructor]` method has the right shape and returns\n/// a compile-error token stream if it doesn't.\nfn validate_constructor(method: &syn::ImplItemFn) -> Option<quote::__private::TokenStream> {\n    let has_self = method\n        .sig\n        .inputs\n        .iter()\n        .any(|arg| matches!(arg, FnArg::Receiver(_)));\n    if has_self {\n        return Some(quote! {\n            compile_error!(\"#[jsg_constructor] must be a static method (no self receiver)\");\n        });\n    }\n\n    let returns_self = matches!(&method.sig.output,\n        syn::ReturnType::Type(_, ty) if matches!(&**ty,\n            syn::Type::Path(p) if p.path.is_ident(\"Self\")\n        )\n    );\n    if !returns_self {\n        return Some(quote! {\n            compile_error!(\"#[jsg_constructor] must return Self\");\n        });\n    }\n\n    None\n}\n\n/// Extracts constructor argument unwrap statements and argument expressions.\nfn extract_constructor_params(\n    method: &syn::ImplItemFn,\n) -> (\n    bool,\n    Vec<quote::__private::TokenStream>,\n    Vec<quote::__private::TokenStream>,\n) {\n    let params: Vec<_> = method\n        .sig\n        .inputs\n        .iter()\n        .filter_map(|arg| {\n            if let FnArg::Typed(pat_type) = arg {\n                Some((*pat_type.ty).clone())\n            } else {\n                None\n            }\n        })\n        .collect();\n\n    let has_lock_param = params.first().is_some_and(is_lock_ref);\n    let js_arg_offset = usize::from(has_lock_param);\n\n    let (unwraps, arg_exprs) = params\n        .iter()\n        .enumerate()\n        .skip(js_arg_offset)\n        .map(|(i, ty)| {\n            let js_index = i - js_arg_offset;\n            let var = syn::Ident::new(&format!(\"arg{js_index}\"), method.sig.ident.span());\n            let unwrap = quote! {\n                let #var = match <#ty as jsg::FromJS>::from_js(&mut lock, args.get(#js_index)) {\n                    Ok(v) => v,\n                    Err(e) => {\n                        lock.throw_exception(&e);\n                        return;\n                    }\n                };\n            };\n            (unwrap, quote! { #var })\n        })\n        .unzip();\n\n    (has_lock_param, unwraps, arg_exprs)\n}\n\nfn generate_constructor_registration(\n    impl_block: &ItemImpl,\n    self_ty: &syn::Type,\n) -> Option<quote::__private::TokenStream> {\n    let constructors: Vec<_> = impl_block\n        .items\n        .iter()\n        .filter_map(|item| match item {\n            syn::ImplItem::Fn(m) if m.attrs.iter().any(|a| is_attr(a, \"jsg_constructor\")) => {\n                Some(m)\n            }\n            _ => None,\n        })\n        .collect();\n\n    if constructors.len() > 1 {\n        return Some(quote! {\n            compile_error!(\"only one #[jsg_constructor] is allowed per impl block\");\n        });\n    }\n\n    constructors\n        .into_iter()\n        .map(|method| {\n            if let Some(err) = validate_constructor(method) {\n                return err;\n            }\n\n            let rust_method_name = &method.sig.ident;\n            let callback_name = syn::Ident::new(\n                &format!(\"{rust_method_name}_constructor_callback\"),\n                rust_method_name.span(),\n            );\n\n            let (has_lock_param, unwraps, arg_exprs) = extract_constructor_params(method);\n            let lock_arg = if has_lock_param {\n                quote! { &mut lock, }\n            } else {\n                quote! {}\n            };\n\n            quote! {\n                jsg::Member::Constructor {\n                    callback: {\n                        unsafe extern \"C\" fn #callback_name(\n                            info: *mut jsg::v8::ffi::FunctionCallbackInfo,\n                        ) {\n                            let mut lock = unsafe { jsg::Lock::from_args(info) };\n                            jsg::catch_panic(&mut lock, || {\n                                // SAFETY: info is a valid V8 FunctionCallbackInfo from the constructor call.\n                                let mut args = unsafe { jsg::v8::FunctionCallbackInfo::from_ffi(info) };\n                                let mut lock = unsafe { jsg::Lock::from_args(info) };\n\n                                #(#unwraps)*\n\n                                let resource = #self_ty::#rust_method_name(#lock_arg #(#arg_exprs),*);\n                                let rc = jsg::Rc::new(resource);\n                                rc.attach_to_this(&mut args);\n                            });\n                        }\n                        #callback_name\n                    },\n                }\n            }\n        })\n        .next()\n}\n\n/// Extracts named fields from a struct, returning an empty list for unit structs.\n/// Returns `Err` with a compile error for tuple structs or non-struct data.\nfn extract_named_fields(\n    input: &DeriveInput,\n    macro_name: &str,\n) -> Result<syn::punctuated::Punctuated<syn::Field, syn::token::Comma>, TokenStream> {\n    match &input.data {\n        Data::Struct(data) => match &data.fields {\n            Fields::Named(fields) => Ok(fields.named.clone()),\n            Fields::Unit => Ok(syn::punctuated::Punctuated::new()),\n            Fields::Unnamed(_) => Err(error(\n                input,\n                &format!(\"#[{macro_name}] does not support tuple structs\"),\n            )),\n        },\n        _ => Err(error(\n            input,\n            &format!(\"#[{macro_name}] can only be applied to structs or impl blocks\"),\n        )),\n    }\n}\n\n/// Checks if an attribute matches a given name, handling both unqualified (`#[jsg_method]`)\n/// and qualified (`#[jsg_macros::jsg_method]`) paths.\nfn is_attr(attr: &syn::Attribute, name: &str) -> bool {\n    attr.path().is_ident(name) || attr.path().segments.last().is_some_and(|s| s.ident == name)\n}\n\nfn error(tokens: &impl ToTokens, msg: &str) -> TokenStream {\n    syn::Error::new_spanned(tokens, msg)\n        .to_compile_error()\n        .into()\n}\n\nfn extract_name_attribute(tokens: TokenStream) -> Option<String> {\n    let nv: syn::MetaNameValue = syn::parse(tokens).ok()?;\n    if !nv.path.is_ident(\"name\") {\n        return None;\n    }\n    if let syn::Expr::Lit(syn::ExprLit {\n        lit: syn::Lit::Str(s),\n        ..\n    }) = &nv.value\n    {\n        Some(s.value())\n    } else {\n        None\n    }\n}\n\nfn snake_to_camel(s: &str) -> String {\n    let mut result = String::new();\n    let mut cap_next = false;\n    for (i, c) in s.chars().enumerate() {\n        match c {\n            '_' => cap_next = true,\n            _ if i == 0 => result.push(c),\n            _ if cap_next => {\n                result.push(c.to_ascii_uppercase());\n                cap_next = false;\n            }\n            _ => result.push(c),\n        }\n    }\n    result\n}\n\n/// Checks if a type is `Result<T, E>`.\nfn is_result_type(ty: &syn::Type) -> bool {\n    if let syn::Type::Path(type_path) = ty\n        && let Some(segment) = type_path.path.segments.last()\n    {\n        return segment.ident == \"Result\";\n    }\n    false\n}\n\n/// Marks a `const` item inside a `#[jsg_resource]` impl block as a static constant\n/// exposed to JavaScript on both the constructor and prototype.\n///\n/// The constant name is used as-is for the JavaScript property name (no camelCase\n/// conversion), matching the convention that constants are `UPPER_SNAKE_CASE` in\n/// both Rust and JavaScript.\n///\n/// Only numeric types are supported (`i8`..`i64`, `u8`..`u64`, `f32`, `f64`).\n///\n/// # Example\n///\n/// ```ignore\n/// #[jsg_resource]\n/// impl MyResource {\n///     #[jsg_static_constant]\n///     pub const MAX_SIZE: u32 = 1024;\n///\n///     #[jsg_static_constant]\n///     pub const STATUS_OK: i32 = 0;\n/// }\n/// // In JavaScript: MyResource.MAX_SIZE === 1024\n/// ```\n#[proc_macro_attribute]\npub fn jsg_static_constant(_attr: TokenStream, item: TokenStream) -> TokenStream {\n    // Marker attribute — the actual registration is handled by #[jsg_resource] on the impl block.\n    item\n}\n\n/// Marks a static method as the JavaScript constructor for a `#[jsg_resource]`.\n///\n/// The method must be a static function (no `self` receiver) that returns `Self`.\n/// When JavaScript calls `new MyResource(args)`, V8 invokes this method,\n/// wraps the returned resource, and attaches it to the `this` object.\n///\n/// ```ignore\n/// #[jsg_resource]\n/// impl MyResource {\n///     #[jsg_constructor]\n///     fn constructor(name: String) -> Self {\n///         Self { name }\n///     }\n/// }\n/// // JS: let obj = new MyResource(\"hello\");\n/// ```\n///\n/// Only one `#[jsg_constructor]` is allowed per impl block.\n#[proc_macro_attribute]\npub fn jsg_constructor(_attr: TokenStream, item: TokenStream) -> TokenStream {\n    // Marker attribute — the actual registration is handled by #[jsg_resource] on the impl block.\n    item\n}\n\n/// Returns true if the type is `&mut Lock` or `&mut jsg::Lock`.\n///\n/// When a method's first typed parameter matches this pattern, the macro passes the\n/// callback's `lock` directly instead of extracting it from JavaScript arguments.\nfn is_lock_ref(ty: &syn::Type) -> bool {\n    let syn::Type::Reference(ref_type) = ty else {\n        return false;\n    };\n    if ref_type.mutability.is_none() {\n        return false;\n    }\n    let syn::Type::Path(type_path) = ref_type.elem.as_ref() else {\n        return false;\n    };\n    let segments: Vec<_> = type_path.path.segments.iter().collect();\n    match segments.len() {\n        // `&mut Lock` — bare import (assumes `use jsg::Lock;`)\n        1 => segments[0].ident == \"Lock\",\n        // `&mut jsg::Lock` — fully qualified path\n        2 => segments[0].ident == \"jsg\" && segments[1].ident == \"Lock\",\n        _ => false,\n    }\n}\n\n/// Generates `jsg::Type` and `jsg::FromJS` implementations for union types.\n///\n/// This macro automatically implements the traits needed for enums with\n/// single-field tuple variants to be used directly as `jsg_method` parameters.\n/// Each variant should contain a type that implements `jsg::Type` and `jsg::FromJS`.\n///\n/// # Example\n///\n/// ```ignore\n/// use jsg_macros::jsg_oneof;\n///\n/// #[jsg_oneof]\n/// #[derive(Debug, Clone)]\n/// enum StringOrNumber {\n///     String(String),\n///     Number(f64),\n/// }\n///\n/// // Use directly as a parameter type:\n/// #[jsg_method]\n/// fn process(&self, value: StringOrNumber) -> Result<String, jsg::Error> {\n///     match value {\n///         StringOrNumber::String(s) => Ok(format!(\"string: {}\", s)),\n///         StringOrNumber::Number(n) => Ok(format!(\"number: {}\", n)),\n///     }\n/// }\n/// ```\n#[proc_macro_attribute]\npub fn jsg_oneof(_attr: TokenStream, item: TokenStream) -> TokenStream {\n    let input = parse_macro_input!(item as DeriveInput);\n    let name = &input.ident;\n\n    let Data::Enum(data) = &input.data else {\n        return error(&input, \"#[jsg_oneof] can only be applied to enums\");\n    };\n\n    let mut variants = Vec::new();\n    for variant in &data.variants {\n        let variant_name = &variant.ident;\n        let Fields::Unnamed(fields) = &variant.fields else {\n            return error(\n                variant,\n                \"#[jsg_oneof] variants must be tuple variants (e.g., `Variant(Type)`)\",\n            );\n        };\n        if fields.unnamed.len() != 1 {\n            return error(variant, \"#[jsg_oneof] variants must have exactly one field\");\n        }\n        let inner_type = &fields.unnamed[0].ty;\n        variants.push((variant_name, inner_type));\n    }\n\n    if variants.is_empty() {\n        return error(&input, \"#[jsg_oneof] requires at least one variant\");\n    }\n\n    let type_checks: Vec<_> = variants\n        .iter()\n        .map(|(variant_name, inner_type)| {\n            quote! {\n                if let Some(result) = <#inner_type as jsg::FromJS>::try_from_js_exact(lock, &value) {\n                    return result.map(Self::#variant_name);\n                }\n            }\n        })\n        .collect();\n\n    let type_names: Vec<_> = variants\n        .iter()\n        .map(|(_, inner_type)| {\n            quote! { <#inner_type as jsg::Type>::class_name() }\n        })\n        .collect();\n\n    let is_exact_checks: Vec<_> = variants\n        .iter()\n        .map(|(_, inner_type)| {\n            quote! { <#inner_type as jsg::Type>::is_exact(value) }\n        })\n        .collect();\n\n    let error_msg = quote! {\n        let expected: Vec<&str> = vec![#(#type_names),*];\n        let msg = format!(\n            \"Expected one of [{}] but got {}\",\n            expected.join(\", \"),\n            value.type_of()\n        );\n        Err(jsg::Error::new_type_error(msg))\n    };\n\n    quote! {\n        #input\n\n        #[automatically_derived]\n        impl jsg::Type for #name {\n            fn class_name() -> &'static str {\n                stringify!(#name)\n            }\n\n            fn is_exact(value: &jsg::v8::Local<jsg::v8::Value>) -> bool {\n                #(#is_exact_checks)||*\n            }\n        }\n\n        #[automatically_derived]\n        impl jsg::FromJS for #name {\n            type ResultType = Self;\n\n            fn from_js(lock: &mut jsg::Lock, value: jsg::v8::Local<jsg::v8::Value>) -> Result<Self::ResultType, jsg::Error> {\n                #(#type_checks)*\n                #error_msg\n            }\n        }\n    }\n    .into()\n}\n"
  },
  {
    "path": "src/rust/jsg-test/BUILD.bazel",
    "content": "load(\"//:build/wd_cc_library.bzl\", \"wd_cc_library\")\nload(\"//:build/wd_rust_crate.bzl\", \"wd_rust_crate\")\n\nwd_cc_library(\n    name = \"ffi-hdrs\",\n    hdrs = [\n        \"ffi.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/rust/jsg:ffi\",\n        \"//src/workerd/jsg\",\n    ],\n)\n\nwd_cc_library(\n    name = \"ffi-impl\",\n    srcs = [\"ffi.c++\"],\n    implementation_deps = [\n        \":lib.rs@cxx\",\n        \"//src/workerd/io:compatibility-date_capnp\",\n        \"@capnp-cpp//src/capnp:capnp\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":ffi-hdrs\",\n    ],\n)\n\nwd_rust_crate(\n    name = \"jsg-test\",\n    cxx_bridge_deps = [\n        \"//src/workerd/jsg:jsg-core\",\n        \":ffi-hdrs\",\n    ],\n    cxx_bridge_srcs = [\n        \"lib.rs\",\n    ],\n    test_proc_macro_deps = [\"//src/rust/jsg-macros\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":ffi-impl\",\n        \"//src/rust/jsg\",\n    ],\n)\n"
  },
  {
    "path": "src/rust/jsg-test/ffi.c++",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"ffi.h\"\n\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/jsg/setup.h>\n#include <workerd/rust/jsg-test/lib.rs.h>\n#include <workerd/rust/jsg/ffi-inl.h>\n#include <workerd/rust/jsg/ffi.h>\n#include <workerd/rust/jsg/lib.rs.h>\n#include <workerd/rust/jsg/v8.rs.h>\n\n#include <cppgc/common.h>\n#include <v8-cppgc.h>\n#include <v8.h>\n\n#include <capnp/message.h>\n#include <kj/common.h>\n\nusing namespace kj_rs;\n\nnamespace {\n// Enable predictable mode so RequestGarbageCollectionForTesting actually triggers GC.\n// Without this, V8 may defer or skip the requested collection.\nbool initPredictableMode = (workerd::setPredictableModeForTest(), true);\n}  // namespace\n\nnamespace workerd {\n\nstruct TestContext: public jsg::Object, public jsg::ContextGlobal {\n  JSG_RESOURCE_TYPE(TestContext) {}\n};\nJSG_DECLARE_ISOLATE_TYPE(TestIsolate, TestContext);\n\nnamespace rust::jsg_test {\n\n// Lazy initialization of V8System to avoid static initialization order issues\nstatic ::workerd::jsg::V8System& getV8System() {\n  static ::workerd::jsg::V8System v8System;\n  return v8System;\n}\n\nTestHarness::TestHarness(::workerd::jsg::V8StackScope&)\n    : isolate(kj::heap<TestIsolate>(getV8System(), kj::heap<::workerd::jsg::IsolateObserver>())),\n      locker(isolate->getIsolate()),\n      isolateScope(isolate->getIsolate()),\n      realm([&] {\n        // Build default (all-false) feature flags for the test realm.\n        capnp::MallocMessageBuilder flagsMessage;\n        flagsMessage.initRoot<CompatibilityFlags>();\n        auto words = capnp::canonicalize(flagsMessage.getRoot<CompatibilityFlags>().asReader());\n        return ::workerd::rust::jsg::realm_create(\n            isolate->getIsolate(), words.asBytes().as<Rust>());\n      }()) {\n  isolate->getIsolate()->SetData(::workerd::jsg::SetDataIndex::SET_DATA_RUST_REALM, &*realm);\n}\n\nkj::Own<TestHarness> create_test_harness() {\n  return ::workerd::jsg::runInV8Stack(\n      [](::workerd::jsg::V8StackScope& stackScope) { return kj::heap<TestHarness>(stackScope); });\n}\n\nEvalContext::EvalContext(v8::Isolate* isolate, v8::Local<v8::Context> context)\n    : v8Isolate(isolate),\n      v8Context(isolate, context) {}\n\nvoid EvalContext::set_global(::rust::Str name, ::workerd::rust::jsg::Local value) const {\n  auto ctx = v8Context.Get(v8Isolate);\n  auto key = ::workerd::jsg::check(v8::String::NewFromUtf8(\n      v8Isolate, name.data(), v8::NewStringType::kNormal, static_cast<int>(name.size())));\n  auto v8Value = ::workerd::rust::jsg::local_from_ffi<v8::Value>(kj::mv(value));\n  ::workerd::jsg::check(ctx->Global()->Set(ctx, key, v8Value));\n}\n\nEvalResult EvalContext::eval(::rust::Str code) const {\n  EvalResult result;\n  result.success = false;\n\n  v8::Local<v8::Context> ctx = v8Context.Get(v8Isolate);\n\n  v8::Local<v8::String> source = ::workerd::jsg::check(v8::String::NewFromUtf8(\n      v8Isolate, code.data(), v8::NewStringType::kNormal, static_cast<int>(code.size())));\n\n  v8::Local<v8::Script> script;\n  KJ_ASSERT(v8::Script::Compile(ctx, source).ToLocal(&script), \"Failed to compile script\");\n  v8::TryCatch catcher(v8Isolate);\n\n  v8::Local<v8::Value> value;\n  if (script->Run(ctx).ToLocal(&value)) {\n    result.success = true;\n    result.value = ::workerd::rust::jsg::to_ffi(kj::mv(value));\n  } else if (catcher.HasCaught()) {\n    result.success = false;\n    auto exception = catcher.Exception();\n    result.value = ::workerd::rust::jsg::to_ffi(kj::mv(exception));\n  } else {\n    result.success = false;\n  }\n\n  return result;\n}\n\nvoid TestHarness::run_in_context(\n    size_t data, ::rust::Fn<void(size_t, Isolate*, EvalContext&)> callback) const {\n  isolate->runInLockScope([&](TestIsolate::Lock& lock) {\n    auto context = lock.newContext<TestContext>();\n    v8::Local<v8::Context> v8Context = context.getHandle(lock.v8Isolate);\n    v8::Context::Scope contextScope(v8Context);\n\n    EvalContext evalContext(lock.v8Isolate, v8Context);\n    callback(data, lock.v8Isolate, evalContext);\n  });\n}\n\nvoid request_gc(Isolate* isolate, GcType gc_type) {\n  switch (gc_type) {\n    case GcType::Full:\n      isolate->RequestGarbageCollectionForTesting(\n          v8::Isolate::GarbageCollectionType::kFullGarbageCollection);\n      if (auto* cppHeap = isolate->GetCppHeap()) {\n        cppHeap->CollectGarbageForTesting(cppgc::EmbedderStackState::kNoHeapPointers);\n      }\n      break;\n    case GcType::Minor:\n      isolate->RequestGarbageCollectionForTesting(\n          v8::Isolate::GarbageCollectionType::kMinorGarbageCollection);\n      // V8's minor GC (scavenge) only collects V8's young generation heap.\n      // CppgcShim objects live on the cppgc heap, so we must also trigger\n      // cppgc's young generation collection to actually reclaim them.\n      if (auto* cppHeap = isolate->GetCppHeap()) {\n        cppHeap->CollectGarbageInYoungGenerationForTesting(\n            cppgc::EmbedderStackState::kNoHeapPointers);\n      }\n      break;\n  }\n}\n\n::workerd::rust::jsg::Local create_cpp_tagged_object(Isolate* isolate) {\n  auto tmpl = v8::ObjectTemplate::New(isolate);\n  tmpl->SetInternalFieldCount(::workerd::jsg::Wrappable::INTERNAL_FIELD_COUNT);\n  auto obj = ::workerd::jsg::check(tmpl->NewInstance(isolate->GetCurrentContext()));\n\n  // Set the C++ wrappable tag (NOT the Rust tag) to simulate a C++ JSG object\n  auto tagAddress = const_cast<uint16_t*>(&::workerd::jsg::Wrappable::WORKERD_WRAPPABLE_TAG);\n  obj->SetAlignedPointerInInternalField(::workerd::jsg::Wrappable::WRAPPABLE_TAG_FIELD_INDEX,\n      tagAddress,\n      static_cast<v8::EmbedderDataTypeTag>(::workerd::jsg::Wrappable::WRAPPABLE_TAG_FIELD_INDEX));\n\n  return ::workerd::rust::jsg::to_ffi(v8::Local<v8::Value>::Cast(obj));\n}\n\n}  // namespace rust::jsg_test\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/rust/jsg-test/ffi.h",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/rust/jsg/ffi.h>\n#include <workerd/rust/jsg/v8.rs.h>\n\n#include <kj-rs/kj-rs.h>\n#include <rust/cxx.h>\n#include <v8.h>\n\n#include <kj/function.h>\n#include <kj/memory.h>\n\nnamespace workerd {\nclass TestIsolate;\n\nnamespace rust::jsg_test {\n\nusing Isolate = v8::Isolate;\n\nstruct EvalResult;\n\nclass EvalContext {\n public:\n  EvalContext(v8::Isolate* isolate, v8::Local<v8::Context> context);\n\n  EvalResult eval(::rust::Str code) const;\n  void set_global(::rust::Str name, ::workerd::rust::jsg::Local value) const;\n\n  v8::Isolate* v8Isolate;\n  v8::Global<v8::Context> v8Context;\n};\n\n// Testing harness that provides a simple V8 isolate for Rust JSG testing\nclass TestHarness {\n public:\n  // Use create_test_harness() instead - it ensures proper V8 stack scope\n  TestHarness(::workerd::jsg::V8StackScope& stackScope);\n\n  // Runs a callback within a proper V8 context and stack scope\n  // The callback receives the data pointer, isolate and a context\n  void run_in_context(size_t data, ::rust::Fn<void(size_t, Isolate*, EvalContext&)> callback) const;\n\n private:\n  mutable kj::Own<TestIsolate> isolate;\n  mutable v8::Locker locker;\n  mutable v8::Isolate::Scope isolateScope;\n  mutable ::rust::Box<::workerd::rust::jsg::Realm> realm;\n};\n\nkj::Own<TestHarness> create_test_harness();\n\n// Forward-declared; defined by the CXX-generated lib.rs.h header.\nenum class GcType : uint8_t;\n\n// Triggers garbage collection for testing purposes.\nvoid request_gc(Isolate* isolate, GcType gc_type);\n\n// Creates a V8 object with the C++ WORKERD_WRAPPABLE_TAG set in its internal fields.\n// Used to test that Rust unwrap correctly rejects non-Rust wrappable objects.\n::workerd::rust::jsg::Local create_cpp_tagged_object(Isolate* isolate);\n\n}  // namespace rust::jsg_test\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/rust/jsg-test/lib.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nuse std::pin::Pin;\n\nuse jsg::FromJS;\nuse jsg::v8;\nuse kj_rs::KjOwn;\n\n#[cfg(test)]\nmod tests;\n\n#[cxx::bridge(namespace = \"workerd::rust::jsg_test\")]\nmod ffi {\n    #[namespace = \"workerd::rust::jsg\"]\n    unsafe extern \"C++\" {\n        include!(\"workerd/rust/jsg/ffi.h\");\n        type Isolate = jsg::v8::ffi::Isolate;\n        type Local = jsg::v8::ffi::Local;\n    }\n\n    #[derive(Debug)]\n    struct EvalResult {\n        success: bool,\n        value: KjMaybe<Local>,\n    }\n\n    enum GcType {\n        /// Full (major) GC — collects both young and old generations, plus cppgc heap.\n        Full = 0,\n        /// Minor (scavenge) GC — collects only the young generation.\n        Minor = 1,\n    }\n\n    unsafe extern \"C++\" {\n        include!(\"workerd/rust/jsg-test/ffi.h\");\n        type TestHarness;\n        type EvalContext;\n\n        pub unsafe fn create_test_harness() -> KjOwn<TestHarness>;\n        pub unsafe fn run_in_context(\n            self: &TestHarness,\n            data: usize, /* callback */\n            callback: unsafe fn(usize /* callback */, *mut Isolate, Pin<&mut EvalContext>),\n        );\n\n        pub unsafe fn eval(self: &EvalContext, code: &str) -> EvalResult;\n        pub unsafe fn set_global(self: &EvalContext, name: &str, value: Local);\n\n        /// Triggers garbage collection for testing purposes.\n        /// Note: For GC to actually collect objects, they must not be reachable from the\n        /// current HandleScope.\n        #[expect(clippy::allow_attributes)] // Only used in tests, but #[expect(dead_code)] fails during test builds\n        #[allow(dead_code)]\n        pub unsafe fn request_gc(isolate: *mut Isolate, gc_type: GcType);\n\n        /// Creates a V8 object with the C++ `WORKERD_WRAPPABLE_TAG` in its internal fields.\n        /// Used to test that Rust unwrap correctly rejects non-Rust wrappable objects.\n        #[expect(clippy::allow_attributes)]\n        #[allow(dead_code)]\n        pub unsafe fn create_cpp_tagged_object(isolate: *mut Isolate) -> Local;\n\n    }\n}\n\npub struct Harness(KjOwn<ffi::TestHarness>);\n\npub struct EvalContext<'a> {\n    inner: &'a ffi::EvalContext,\n    isolate: v8::IsolatePtr,\n}\n\n#[derive(Debug)]\npub enum EvalError<'a> {\n    UncoercibleResult {\n        value: v8::Local<'a, v8::Value>,\n        message: String,\n    },\n    Exception(v8::Local<'a, v8::Value>),\n    EvalFailed,\n}\n\nimpl EvalError<'_> {\n    /// Extracts a `jsg::Error` from an `EvalError::Exception` variant.\n    ///\n    /// # Panics\n    ///\n    /// Panics if `self` is not `EvalError::Exception`, or if the value cannot be converted to a\n    /// `jsg::Error`.\n    pub fn unwrap_jsg_err(&self, lock: &mut jsg::Lock) -> jsg::Error {\n        match self {\n            EvalError::Exception(value) => jsg::Error::from_js(lock, value.clone())\n                .expect(\"Failed to convert exception to jsg::Error\"),\n            _ => panic!(\"Unexpected error\"),\n        }\n    }\n}\nimpl EvalContext<'_> {\n    pub fn eval<T>(&self, lock: &mut jsg::Lock, code: &str) -> Result<T, EvalError<'_>>\n    where\n        T: jsg::FromJS<ResultType = T>,\n    {\n        // SAFETY: self.inner is a valid EvalContext from C++; code is a valid str.\n        let result = unsafe { self.inner.eval(code) };\n        let opt_local: Option<v8::ffi::Local> = result.value.into();\n\n        if result.success {\n            match opt_local {\n                Some(local) => {\n                    // SAFETY: self.isolate is valid and local is from a successful eval result.\n                    let local = unsafe { v8::Local::from_ffi(self.isolate, local) };\n                    match T::from_js(lock, local.clone()) {\n                        Err(e) => Err(EvalError::UncoercibleResult {\n                            value: local,\n                            message: e.to_string(),\n                        }),\n                        Ok(value) => Ok(value),\n                    }\n                }\n                None => unreachable!(),\n            }\n        } else {\n            match opt_local {\n                Some(local) => {\n                    // SAFETY: self.isolate is valid and local is from an eval exception.\n                    let value = unsafe { v8::Local::from_ffi(self.isolate, local) };\n                    Err(EvalError::Exception(value))\n                }\n                None => Err(EvalError::EvalFailed),\n            }\n        }\n    }\n\n    /// Evaluates JavaScript code and returns the raw `Local<Value>` without conversion.\n    ///\n    /// Useful for obtaining handles (e.g. functions) that aren't `FromJS` types.\n    pub fn eval_raw(&self, code: &str) -> Result<v8::Local<'_, v8::Value>, EvalError<'_>> {\n        // SAFETY: self.inner is a valid EvalContext from C++; code is a valid str.\n        let result = unsafe { self.inner.eval(code) };\n        let opt_local: Option<v8::ffi::Local> = result.value.into();\n\n        if result.success {\n            match opt_local {\n                // SAFETY: self.isolate is valid and local is from a successful eval result.\n                Some(local) => Ok(unsafe { v8::Local::from_ffi(self.isolate, local) }),\n                None => unreachable!(),\n            }\n        } else {\n            match opt_local {\n                Some(local) => {\n                    // SAFETY: self.isolate is valid and local is from an eval exception.\n                    let value = unsafe { v8::Local::from_ffi(self.isolate, local) };\n                    Err(EvalError::Exception(value))\n                }\n                None => Err(EvalError::EvalFailed),\n            }\n        }\n    }\n\n    pub fn set_global(&self, name: &str, value: v8::Local<v8::Value>) {\n        // SAFETY: self.inner is a valid EvalContext and value is a valid Local handle.\n        unsafe { self.inner.set_global(name, value.into_ffi()) }\n    }\n}\n\nimpl Harness {\n    pub fn new() -> Self {\n        // SAFETY: create_test_harness initializes the V8 platform and returns a valid harness.\n        Self(unsafe { ffi::create_test_harness() })\n    }\n\n    /// Runs a callback within a V8 context.\n    ///\n    /// The callback is passed through C++ via a data pointer since CXX doesn't support\n    /// closures directly. The monomorphized trampoline function receives the pointer\n    /// and reconstructs the closure.\n    ///\n    /// The callback returns `Result<(), jsg::Error>` to allow use of the `?` operator.\n    /// If an error is returned, the test will panic.\n    pub fn run_in_context<F>(&self, callback: F)\n    where\n        F: FnOnce(&mut jsg::Lock, &mut EvalContext) -> Result<(), jsg::Error>,\n    {\n        #[expect(clippy::needless_pass_by_value)]\n        fn trampoline<F>(\n            data: usize,\n            isolate: *mut v8::ffi::Isolate,\n            context: Pin<&mut ffi::EvalContext>,\n        ) where\n            F: FnOnce(&mut jsg::Lock, &mut EvalContext) -> Result<(), jsg::Error>,\n        {\n            // SAFETY: data was cast from &raw mut Option<F> in run_in_context below.\n            let cb = unsafe { &mut *(data as *mut Option<F>) };\n            if let Some(callback) = cb.take() {\n                // SAFETY: isolate is a valid pointer provided by the C++ test harness.\n                let isolate_ptr = unsafe { v8::IsolatePtr::from_ffi(isolate) };\n                let mut eval_context = EvalContext {\n                    inner: &context,\n                    isolate: isolate_ptr,\n                };\n                // SAFETY: isolate is a valid pointer provided by the C++ test harness.\n                let mut lock = unsafe { jsg::Lock::from_isolate_ptr(isolate) };\n                if let Err(e) = callback(&mut lock, &mut eval_context) {\n                    panic!(\"Test failed: {}: {}\", e.name, e.message);\n                }\n            }\n        }\n\n        let mut callback = Some(callback);\n        // SAFETY: callback pointer is valid for the duration of run_in_context.\n        unsafe {\n            self.0\n                .run_in_context(&raw mut callback as usize, trampoline::<F>);\n        }\n    }\n\n    pub fn request_gc(lock: &mut jsg::Lock) {\n        // SAFETY: isolate is valid and locked (guaranteed by Lock).\n        unsafe { ffi::request_gc(lock.isolate().as_ffi(), ffi::GcType::Full) };\n    }\n\n    pub fn request_minor_gc(lock: &mut jsg::Lock) {\n        // SAFETY: isolate is valid and locked (guaranteed by Lock).\n        unsafe { ffi::request_gc(lock.isolate().as_ffi(), ffi::GcType::Minor) };\n    }\n\n    /// Creates a V8 object tagged with the C++ `WORKERD_WRAPPABLE_TAG`.\n    /// Used to test that Rust unwrap rejects non-Rust wrappable objects.\n    pub fn create_cpp_tagged_object<'a>(lock: &mut jsg::Lock) -> v8::Local<'a, v8::Value> {\n        // SAFETY: isolate is valid and locked (guaranteed by Lock).\n        unsafe {\n            let local = ffi::create_cpp_tagged_object(lock.isolate().as_ffi());\n            v8::Local::from_ffi(lock.isolate(), local)\n        }\n    }\n}\n\nimpl Default for Harness {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n"
  },
  {
    "path": "src/rust/jsg-test/tests/arrays.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nuse jsg::Number;\nuse jsg::ToJS;\nuse jsg_macros::jsg_method;\nuse jsg_macros::jsg_resource;\nuse jsg_macros::jsg_struct;\n\n#[jsg_struct]\nstruct Person {\n    pub name: String,\n    pub age: Number,\n}\n\n#[jsg_resource]\nstruct ArrayResource;\n\n#[jsg_resource]\nimpl ArrayResource {\n    #[jsg_method]\n    pub fn sum(&self, numbers: Vec<Number>) -> Number {\n        Number::new(numbers.iter().map(jsg::Number::value).sum())\n    }\n\n    #[jsg_method]\n    pub fn sum_slice(&self, numbers: &[Number]) -> Number {\n        Number::new(numbers.iter().map(jsg::Number::value).sum())\n    }\n\n    #[jsg_method]\n    pub fn join_strings(&self, strings: &[String]) -> String {\n        strings.join(\"-\")\n    }\n\n    #[jsg_method]\n    pub fn double(&self, numbers: Vec<Number>) -> Vec<Number> {\n        numbers\n            .into_iter()\n            .map(|n| Number::new(n.value() * 2.0))\n            .collect()\n    }\n\n    #[jsg_method]\n    pub fn concat_strings(&self, strings: Vec<String>) -> String {\n        strings.join(\", \")\n    }\n\n    #[jsg_method]\n    pub fn split_string(&self, s: &str) -> Vec<String> {\n        s.split(',').map(|s| s.trim().to_owned()).collect()\n    }\n\n    #[jsg_method]\n    pub fn filter_positive(&self, numbers: Vec<Number>) -> Vec<Number> {\n        numbers.into_iter().filter(|n| n.value() > 0.0).collect()\n    }\n\n    #[jsg_method]\n    pub fn reverse_bytes(&self, bytes: Vec<u8>) -> Vec<u8> {\n        bytes.into_iter().rev().collect()\n    }\n\n    #[jsg_method]\n    pub fn reverse_bytes_slice(&self, bytes: &[u8]) -> Vec<u8> {\n        bytes.iter().copied().rev().collect()\n    }\n\n    #[jsg_method]\n    pub fn sum_i32(&self, numbers: Vec<i32>) -> Number {\n        Number::new(numbers.iter().map(|&n| f64::from(n)).sum())\n    }\n\n    #[jsg_method]\n    pub fn sum_i32_slice(&self, numbers: &[i32]) -> Number {\n        Number::new(numbers.iter().map(|&n| f64::from(n)).sum())\n    }\n\n    #[jsg_method]\n    pub fn filter_adults(&self, people: Vec<Person>) -> Vec<Person> {\n        people\n            .into_iter()\n            .filter(|p| p.age.value() >= 18.0)\n            .collect()\n    }\n\n    // Float32Array methods\n    #[jsg_method]\n    pub fn sum_f32(&self, numbers: Vec<f32>) -> Number {\n        Number::new(numbers.iter().map(|&n| f64::from(n)).sum())\n    }\n\n    #[jsg_method]\n    pub fn sum_f32_slice(&self, numbers: &[f32]) -> Number {\n        Number::new(numbers.iter().map(|&n| f64::from(n)).sum())\n    }\n\n    #[jsg_method]\n    pub fn double_f32(&self, numbers: Vec<f32>) -> Vec<f32> {\n        numbers.into_iter().map(|n| n * 2.0).collect()\n    }\n\n    // Float64Array methods\n    #[jsg_method]\n    pub fn sum_f64(&self, numbers: Vec<f64>) -> Number {\n        Number::new(numbers.iter().sum())\n    }\n\n    #[jsg_method]\n    pub fn sum_f64_slice(&self, numbers: &[f64]) -> Number {\n        Number::new(numbers.iter().sum())\n    }\n\n    #[jsg_method]\n    pub fn double_f64(&self, numbers: Vec<f64>) -> Vec<f64> {\n        numbers.into_iter().map(|n| n * 2.0).collect()\n    }\n\n    // BigInt64Array methods\n    #[jsg_method]\n    pub fn sum_i64(&self, numbers: Vec<i64>) -> Number {\n        #[expect(clippy::cast_precision_loss)]\n        Number::new(numbers.iter().map(|&n| n as f64).sum())\n    }\n\n    #[jsg_method]\n    pub fn sum_i64_slice(&self, numbers: &[i64]) -> Number {\n        #[expect(clippy::cast_precision_loss)]\n        Number::new(numbers.iter().map(|&n| n as f64).sum())\n    }\n\n    #[jsg_method]\n    pub fn double_i64(&self, numbers: Vec<i64>) -> Vec<i64> {\n        numbers.into_iter().map(|n| n * 2).collect()\n    }\n\n    // BigUint64Array methods\n    #[jsg_method]\n    pub fn sum_u64(&self, numbers: Vec<u64>) -> Number {\n        #[expect(clippy::cast_precision_loss)]\n        Number::new(numbers.iter().map(|&n| n as f64).sum())\n    }\n\n    #[jsg_method]\n    pub fn sum_u64_slice(&self, numbers: &[u64]) -> Number {\n        #[expect(clippy::cast_precision_loss)]\n        Number::new(numbers.iter().map(|&n| n as f64).sum())\n    }\n\n    #[jsg_method]\n    pub fn double_u64(&self, numbers: Vec<u64>) -> Vec<u64> {\n        numbers.into_iter().map(|n| n * 2).collect()\n    }\n}\n\n#[test]\nfn resource_accepts_array_parameter() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        let result: Number = ctx.eval(lock, \"arr.sum([1, 2, 3, 4, 5])\").unwrap();\n        assert!((result.value() - 15.0).abs() < f64::EPSILON);\n\n        let result: String = ctx\n            .eval(lock, \"arr.concatStrings(['hello', 'world'])\")\n            .unwrap();\n        assert_eq!(result, \"hello, world\");\n        Ok(())\n    });\n}\n\n#[test]\nfn resource_accepts_slice_parameter() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        let result: Number = ctx.eval(lock, \"arr.sumSlice([1, 2, 3, 4, 5])\").unwrap();\n        assert!((result.value() - 15.0).abs() < f64::EPSILON);\n\n        let result: String = ctx.eval(lock, \"arr.joinStrings(['a', 'b', 'c'])\").unwrap();\n        assert_eq!(result, \"a-b-c\");\n\n        let result: Number = ctx.eval(lock, \"arr.sumSlice([])\").unwrap();\n        assert!((result.value() - 0.0).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n#[test]\nfn resource_returns_array() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        let result: Vec<Number> = ctx.eval(lock, \"arr.double([1, 2, 3])\").unwrap();\n        let values: Vec<f64> = result.iter().map(jsg::Number::value).collect();\n        assert_eq!(values, vec![2.0, 4.0, 6.0]);\n\n        let result: Vec<String> = ctx.eval(lock, \"arr.splitString('a, b, c')\").unwrap();\n        assert_eq!(result, vec![\"a\", \"b\", \"c\"]);\n\n        let result: Vec<Number> = ctx\n            .eval(lock, \"arr.filterPositive([-1, 2, -3, 4, 0])\")\n            .unwrap();\n        let values: Vec<f64> = result.iter().map(jsg::Number::value).collect();\n        assert_eq!(values, vec![2.0, 4.0]);\n        Ok(())\n    });\n}\n\n#[test]\nfn resource_accepts_typed_array_parameter() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        let result: Vec<u8> = ctx\n            .eval(lock, \"arr.reverseBytes(new Uint8Array([1, 2, 3]))\")\n            .unwrap();\n        assert_eq!(result, vec![3, 2, 1]);\n\n        let result: Number = ctx\n            .eval(lock, \"arr.sumI32(new Int32Array([-10, 20, -5]))\")\n            .unwrap();\n        assert!((result.value() - 5.0).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n#[test]\nfn resource_returns_typed_array() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        let is_u8: bool = ctx\n            .eval(\n                lock,\n                \"arr.reverseBytes(new Uint8Array([1, 2, 3])) instanceof Uint8Array\",\n            )\n            .unwrap();\n        assert!(is_u8);\n        Ok(())\n    });\n}\n\n#[test]\nfn resource_accepts_typed_array_slice_parameter() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        // Test &[u8] accepts Uint8Array\n        let result: Vec<u8> = ctx\n            .eval(lock, \"arr.reverseBytesSlice(new Uint8Array([1, 2, 3]))\")\n            .unwrap();\n        assert_eq!(result, vec![3, 2, 1]);\n\n        // Test &[i32] accepts Int32Array\n        let result: Number = ctx\n            .eval(lock, \"arr.sumI32Slice(new Int32Array([-10, 20, -5]))\")\n            .unwrap();\n        assert!((result.value() - 5.0).abs() < f64::EPSILON);\n\n        // Test empty TypedArray\n        let result: Vec<u8> = ctx\n            .eval(lock, \"arr.reverseBytesSlice(new Uint8Array([]))\")\n            .unwrap();\n        assert!(result.is_empty());\n\n        Ok(())\n    });\n}\n\n#[test]\nfn typed_array_slice_rejects_wrong_type() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        // &[u8] should reject Int8Array\n        let result: Result<Vec<u8>, _> =\n            ctx.eval(lock, \"arr.reverseBytesSlice(new Int8Array([1, 2, 3]))\");\n        assert!(result.is_err());\n\n        // &[u8] should reject regular Array\n        let result: Result<Vec<u8>, _> = ctx.eval(lock, \"arr.reverseBytesSlice([1, 2, 3])\");\n        assert!(result.is_err());\n\n        // &[i32] should reject Uint32Array\n        let result: Result<Number, _> =\n            ctx.eval(lock, \"arr.sumI32Slice(new Uint32Array([1, 2, 3]))\");\n        assert!(result.is_err());\n\n        // &[i32] should reject regular Array\n        let result: Result<Number, _> = ctx.eval(lock, \"arr.sumI32Slice([1, 2, 3])\");\n        assert!(result.is_err());\n\n        Ok(())\n    });\n}\n\n#[test]\nfn resource_accepts_and_returns_struct_array() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        let result: Vec<Person> = ctx\n            .eval(\n                lock,\n                \"arr.filterAdults([{name: 'Alice', age: 25}, {name: 'Bob', age: 15}, {name: 'Charlie', age: 30}])\",\n            )\n            .unwrap();\n        assert_eq!(result.len(), 2);\n        assert_eq!(result[0].name, \"Alice\");\n        assert_eq!(result[1].name, \"Charlie\");\n        Ok(())\n    });\n}\n\n#[test]\nfn vec_to_js_creates_array() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let vec = vec![\"hello\".to_owned(), \"world\".to_owned()];\n        let js_val = vec.to_js(lock);\n        ctx.set_global(\"arr\", js_val);\n\n        let is_array: bool = ctx.eval(lock, \"Array.isArray(arr)\").unwrap();\n        assert!(is_array);\n        let length: Number = ctx.eval(lock, \"arr.length\").unwrap();\n        assert!((length.value() - 2.0).abs() < f64::EPSILON);\n        let first: String = ctx.eval(lock, \"arr[0]\").unwrap();\n        assert_eq!(first, \"hello\");\n\n        let vec = vec![Number::new(1.5), Number::new(2.5), Number::new(3.5)];\n        let js_val = vec.to_js(lock);\n        ctx.set_global(\"nums\", js_val);\n\n        let sum: Number = ctx.eval(lock, \"nums[0] + nums[1] + nums[2]\").unwrap();\n        assert!((sum.value() - 7.5).abs() < f64::EPSILON);\n\n        let vec = vec![true, false, true];\n        let js_val = vec.to_js(lock);\n        ctx.set_global(\"bools\", js_val);\n\n        let result: Vec<bool> = ctx.eval(lock, \"bools\").unwrap();\n        assert_eq!(result, vec![true, false, true]);\n        Ok(())\n    });\n}\n\n#[test]\nfn vec_from_js_parses_array() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let strings: Vec<String> = ctx.eval(lock, \"['a', 'b', 'c']\").unwrap();\n        assert_eq!(strings, vec![\"a\", \"b\", \"c\"]);\n\n        let numbers: Vec<Number> = ctx.eval(lock, \"[1, 2, 3]\").unwrap();\n        let values: Vec<f64> = numbers.iter().map(jsg::Number::value).collect();\n        assert_eq!(values, vec![1.0, 2.0, 3.0]);\n        Ok(())\n    });\n}\n\n#[test]\nfn vec_empty_roundtrip() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let vec: Vec<String> = vec![];\n        let js_val = vec.to_js(lock);\n        ctx.set_global(\"arr\", js_val);\n\n        let length: Number = ctx.eval(lock, \"arr.length\").unwrap();\n        assert!(length.value().abs() < f64::EPSILON);\n\n        let result: Vec<String> = ctx.eval(lock, \"arr\").unwrap();\n        assert!(result.is_empty());\n        Ok(())\n    });\n}\n\n#[test]\nfn vec_nested_arrays() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let nested = vec![\n            vec![Number::new(1.0), Number::new(2.0)],\n            vec![Number::new(3.0), Number::new(4.0)],\n        ];\n        let js_val = nested.to_js(lock);\n        ctx.set_global(\"matrix\", js_val);\n\n        let first_row_sum: Number = ctx.eval(lock, \"matrix[0][0] + matrix[0][1]\").unwrap();\n        assert!((first_row_sum.value() - 3.0).abs() < f64::EPSILON);\n\n        let second_row_sum: Number = ctx.eval(lock, \"matrix[1][0] + matrix[1][1]\").unwrap();\n        assert!((second_row_sum.value() - 7.0).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n#[test]\nfn vec_from_non_array_returns_error() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let result: Result<Vec<String>, _> = ctx.eval(lock, \"'not an array'\");\n        assert!(result.is_err());\n        Ok(())\n    });\n}\n\n#[test]\nfn typed_array_to_js() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let vec: Vec<u8> = vec![1, 2, 255];\n        ctx.set_global(\"u8_arr\", vec.to_js(lock));\n        let check: bool = ctx.eval(lock, \"u8_arr instanceof Uint8Array\").unwrap();\n        assert!(check);\n        let val: Number = ctx.eval(lock, \"u8_arr[2]\").unwrap();\n        assert!((val.value() - 255.0).abs() < f64::EPSILON);\n\n        let vec: Vec<u16> = vec![1, 2, 65535];\n        ctx.set_global(\"u16_arr\", vec.to_js(lock));\n        let check: bool = ctx.eval(lock, \"u16_arr instanceof Uint16Array\").unwrap();\n        assert!(check);\n        let val: Number = ctx.eval(lock, \"u16_arr[2]\").unwrap();\n        assert!((val.value() - 65535.0).abs() < f64::EPSILON);\n\n        let vec: Vec<u32> = vec![1, 2, 4_294_967_295];\n        ctx.set_global(\"u32_arr\", vec.to_js(lock));\n        let check: bool = ctx.eval(lock, \"u32_arr instanceof Uint32Array\").unwrap();\n        assert!(check);\n        let val: Number = ctx.eval(lock, \"u32_arr[2]\").unwrap();\n        assert!((val.value() - 4_294_967_295.0).abs() < f64::EPSILON);\n\n        let vec: Vec<i8> = vec![-128, 0, 127];\n        ctx.set_global(\"i8_arr\", vec.to_js(lock));\n        let check: bool = ctx.eval(lock, \"i8_arr instanceof Int8Array\").unwrap();\n        assert!(check);\n        let val: Number = ctx.eval(lock, \"i8_arr[0]\").unwrap();\n        assert!((val.value() - (-128.0)).abs() < f64::EPSILON);\n\n        let vec: Vec<i16> = vec![-32768, 0, 32767];\n        ctx.set_global(\"i16_arr\", vec.to_js(lock));\n        let check: bool = ctx.eval(lock, \"i16_arr instanceof Int16Array\").unwrap();\n        assert!(check);\n        let val: Number = ctx.eval(lock, \"i16_arr[0]\").unwrap();\n        assert!((val.value() - (-32768.0)).abs() < f64::EPSILON);\n\n        let vec: Vec<i32> = vec![-2_147_483_648, 0, 2_147_483_647];\n        ctx.set_global(\"i32_arr\", vec.to_js(lock));\n        let check: bool = ctx.eval(lock, \"i32_arr instanceof Int32Array\").unwrap();\n        assert!(check);\n        let val: Number = ctx.eval(lock, \"i32_arr[0]\").unwrap();\n        assert!((val.value() - (-2_147_483_648.0)).abs() < f64::EPSILON);\n\n        let is_array: bool = ctx.eval(lock, \"Array.isArray(u8_arr)\").unwrap();\n        assert!(!is_array);\n        Ok(())\n    });\n}\n\n#[test]\nfn typed_array_from_js() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let u8_arr: Vec<u8> = ctx.eval(lock, \"new Uint8Array([10, 20, 255])\").unwrap();\n        assert_eq!(u8_arr, vec![10, 20, 255]);\n\n        let u16_arr: Vec<u16> = ctx\n            .eval(lock, \"new Uint16Array([100, 200, 65535])\")\n            .unwrap();\n        assert_eq!(u16_arr, vec![100, 200, 65535]);\n\n        let u32_arr: Vec<u32> = ctx\n            .eval(lock, \"new Uint32Array([1000, 2000, 4294967295])\")\n            .unwrap();\n        assert_eq!(u32_arr, vec![1000, 2000, 4_294_967_295]);\n\n        let i8_arr: Vec<i8> = ctx.eval(lock, \"new Int8Array([-128, 0, 127])\").unwrap();\n        assert_eq!(i8_arr, vec![-128, 0, 127]);\n\n        let i16_arr: Vec<i16> = ctx\n            .eval(lock, \"new Int16Array([-32768, 0, 32767])\")\n            .unwrap();\n        assert_eq!(i16_arr, vec![-32768, 0, 32767]);\n\n        let i32_arr: Vec<i32> = ctx\n            .eval(lock, \"new Int32Array([-2147483648, 0, 2147483647])\")\n            .unwrap();\n        assert_eq!(i32_arr, vec![-2_147_483_648, 0, 2_147_483_647]);\n        Ok(())\n    });\n}\n\n#[test]\nfn typed_array_empty_roundtrip() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let vec: Vec<u8> = vec![];\n        ctx.set_global(\"arr\", vec.to_js(lock));\n\n        let length: Number = ctx.eval(lock, \"arr.length\").unwrap();\n        assert!(length.value().abs() < f64::EPSILON);\n\n        let result: Vec<u8> = ctx.eval(lock, \"arr\").unwrap();\n        assert!(result.is_empty());\n        Ok(())\n    });\n}\n\n#[test]\nfn typed_array_type_mismatch_returns_error() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let result: Result<Vec<u8>, _> = ctx.eval(lock, \"new Int8Array([1, 2, 3])\");\n        assert!(result.is_err());\n\n        let result: Result<Vec<i32>, _> = ctx.eval(lock, \"new Uint32Array([1, 2, 3])\");\n        assert!(result.is_err());\n\n        let result: Result<Vec<u8>, _> = ctx.eval(lock, \"[1, 2, 3]\");\n        assert!(result.is_err());\n\n        let result: Result<Vec<u8>, _> = ctx.eval(lock, \"'hello'\");\n        assert!(result.is_err());\n        Ok(())\n    });\n}\n\n#[test]\nfn large_typed_array_roundtrip() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let vec: Vec<u8> = (0_u32..65536).map(|i| (i % 256) as u8).collect();\n        ctx.set_global(\"arr\", vec.clone().to_js(lock));\n\n        let length: Number = ctx.eval(lock, \"arr.length\").unwrap();\n        assert!((length.value() - 65536.0).abs() < f64::EPSILON);\n\n        let result: Vec<u8> = ctx.eval(lock, \"arr\").unwrap();\n        assert_eq!(result, vec);\n        Ok(())\n    });\n}\n\n#[test]\nfn typed_array_iter_uint8() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let data: Vec<u8> = vec![10, 20, 30];\n        let js_val = data.to_js(lock);\n        let typed: jsg::v8::Local<'_, jsg::v8::Uint8Array> =\n            // SAFETY: isolate is valid and locked, js_val is a valid Local.\n            unsafe { jsg::v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };\n\n        assert_eq!(typed.len(), 3);\n        assert!(!typed.is_empty());\n\n        assert_eq!(typed.get(0), 10);\n        assert_eq!(typed.get(1), 20);\n        assert_eq!(typed.get(2), 30);\n\n        let sum: u8 = typed.iter().fold(0u8, u8::wrapping_add);\n        assert_eq!(sum, 60);\n\n        let collected: Vec<u8> = typed.iter().collect();\n        assert_eq!(collected, vec![10, 20, 30]);\n\n        Ok(())\n    });\n}\n\n#[test]\nfn typed_array_into_iter_uint8() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let data: Vec<u8> = vec![1, 2, 3, 4, 5];\n        let js_val = data.to_js(lock);\n        let typed: jsg::v8::Local<'_, jsg::v8::Uint8Array> =\n            // SAFETY: isolate is valid and locked, js_val is a valid Local.\n            unsafe { jsg::v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };\n\n        let sum: u8 = typed.into_iter().sum();\n        assert_eq!(sum, 15);\n\n        Ok(())\n    });\n}\n\n#[test]\nfn typed_array_iter_int32() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let data: Vec<i32> = vec![-100, 0, 100];\n        let js_val = data.to_js(lock);\n        let typed: jsg::v8::Local<'_, jsg::v8::Int32Array> =\n            // SAFETY: isolate is valid and locked, js_val is a valid Local.\n            unsafe { jsg::v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };\n\n        assert_eq!(typed.len(), 3);\n        assert_eq!(typed.get(0), -100);\n        assert_eq!(typed.get(1), 0);\n        assert_eq!(typed.get(2), 100);\n\n        let sum: i32 = typed.iter().sum();\n        assert_eq!(sum, 0);\n\n        Ok(())\n    });\n}\n\n#[test]\nfn typed_array_iter_reverse() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let data: Vec<u8> = vec![1, 2, 3, 4];\n        let js_val = data.to_js(lock);\n        let typed: jsg::v8::Local<'_, jsg::v8::Uint8Array> =\n            // SAFETY: isolate is valid and locked, js_val is a valid Local.\n            unsafe { jsg::v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };\n\n        let reversed: Vec<u8> = typed.iter().rev().collect();\n        assert_eq!(reversed, vec![4, 3, 2, 1]);\n\n        Ok(())\n    });\n}\n\n#[test]\nfn typed_array_empty() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let data: Vec<u8> = vec![];\n        let js_val = data.to_js(lock);\n        let typed: jsg::v8::Local<'_, jsg::v8::Uint8Array> =\n            // SAFETY: isolate is valid and locked, js_val is a valid Local.\n            unsafe { jsg::v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };\n\n        assert_eq!(typed.len(), 0);\n        assert!(typed.is_empty());\n        assert_eq!(typed.iter().count(), 0);\n\n        Ok(())\n    });\n}\n\n// =============================================================================\n// Float32Array tests\n// =============================================================================\n\n#[test]\nfn float32_array_parameter_and_return() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        // Test Vec<f32> parameter\n        let result: Number = ctx\n            .eval(lock, \"arr.sumF32(new Float32Array([1.5, 2.5, 3.0]))\")\n            .unwrap();\n        assert!((result.value() - 7.0).abs() < f64::EPSILON);\n\n        // Test Vec<f32> return value\n        let is_f32: bool = ctx\n            .eval(\n                lock,\n                \"arr.doubleF32(new Float32Array([1.0, 2.0])) instanceof Float32Array\",\n            )\n            .unwrap();\n        assert!(is_f32);\n\n        let result: Vec<f32> = ctx\n            .eval(lock, \"arr.doubleF32(new Float32Array([1.0, 2.0, 3.0]))\")\n            .unwrap();\n        assert_eq!(result, vec![2.0, 4.0, 6.0]);\n\n        Ok(())\n    });\n}\n\n#[test]\nfn float32_array_slice_parameter() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        // Test &[f32] parameter\n        let result: Number = ctx\n            .eval(lock, \"arr.sumF32Slice(new Float32Array([1.5, 2.5, 3.0]))\")\n            .unwrap();\n        assert!((result.value() - 7.0).abs() < f64::EPSILON);\n\n        // Test empty Float32Array\n        let result: Number = ctx\n            .eval(lock, \"arr.sumF32Slice(new Float32Array([]))\")\n            .unwrap();\n        assert!((result.value() - 0.0).abs() < f64::EPSILON);\n\n        Ok(())\n    });\n}\n\n#[test]\nfn float32_array_rejects_wrong_type() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        // Float32Array should reject Float64Array\n        let result: Result<Number, _> =\n            ctx.eval(lock, \"arr.sumF32Slice(new Float64Array([1.0, 2.0]))\");\n        assert!(result.is_err());\n\n        // Float32Array should reject regular Array\n        let result: Result<Number, _> = ctx.eval(lock, \"arr.sumF32Slice([1.0, 2.0])\");\n        assert!(result.is_err());\n\n        Ok(())\n    });\n}\n\n#[test]\nfn float32_array_to_js_and_from_js() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        // Test ToJS: Vec<f32> -> Float32Array\n        let data: Vec<f32> = vec![1.5, 2.5, 3.5, 4.5];\n        let js_value = data.to_js(lock);\n        ctx.set_global(\"f32arr\", js_value);\n\n        let is_f32: bool = ctx.eval(lock, \"f32arr instanceof Float32Array\").unwrap();\n        assert!(is_f32);\n\n        let len: Number = ctx.eval(lock, \"f32arr.length\").unwrap();\n        #[expect(clippy::cast_sign_loss)]\n        let len_usize = len.value() as usize;\n        assert_eq!(len_usize, 4);\n\n        // Test FromJS: Float32Array -> Vec<f32>\n        let result: Vec<f32> = ctx.eval(lock, \"f32arr\").unwrap();\n        assert_eq!(result.len(), 4);\n        assert!((result[0] - 1.5).abs() < f32::EPSILON);\n        assert!((result[1] - 2.5).abs() < f32::EPSILON);\n        assert!((result[2] - 3.5).abs() < f32::EPSILON);\n        assert!((result[3] - 4.5).abs() < f32::EPSILON);\n\n        // Test roundtrip with special values\n        let special: Vec<f32> = vec![f32::MIN, f32::MAX, 0.0, -0.0];\n        let js_special = special.to_js(lock);\n        ctx.set_global(\"special\", js_special);\n        let roundtrip: Vec<f32> = ctx.eval(lock, \"special\").unwrap();\n        #[expect(clippy::float_cmp)]\n        {\n            assert_eq!(roundtrip[0], f32::MIN);\n            assert_eq!(roundtrip[1], f32::MAX);\n        }\n\n        Ok(())\n    });\n}\n\n// =============================================================================\n// Float64Array tests\n// =============================================================================\n\n#[test]\nfn float64_array_parameter_and_return() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        // Test Vec<f64> parameter\n        let result: Number = ctx\n            .eval(lock, \"arr.sumF64(new Float64Array([1.5, 2.5, 3.0]))\")\n            .unwrap();\n        assert!((result.value() - 7.0).abs() < f64::EPSILON);\n\n        // Test Vec<f64> return value\n        let is_f64: bool = ctx\n            .eval(\n                lock,\n                \"arr.doubleF64(new Float64Array([1.0, 2.0])) instanceof Float64Array\",\n            )\n            .unwrap();\n        assert!(is_f64);\n\n        let result: Vec<f64> = ctx\n            .eval(lock, \"arr.doubleF64(new Float64Array([1.0, 2.0, 3.0]))\")\n            .unwrap();\n        assert_eq!(result, vec![2.0, 4.0, 6.0]);\n\n        Ok(())\n    });\n}\n\n#[test]\nfn float64_array_slice_parameter() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        // Test &[f64] parameter\n        let result: Number = ctx\n            .eval(lock, \"arr.sumF64Slice(new Float64Array([1.5, 2.5, 3.0]))\")\n            .unwrap();\n        assert!((result.value() - 7.0).abs() < f64::EPSILON);\n\n        // Test empty Float64Array\n        let result: Number = ctx\n            .eval(lock, \"arr.sumF64Slice(new Float64Array([]))\")\n            .unwrap();\n        assert!((result.value() - 0.0).abs() < f64::EPSILON);\n\n        Ok(())\n    });\n}\n\n#[test]\nfn float64_array_rejects_wrong_type() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        // Float64Array should reject Float32Array\n        let result: Result<Number, _> =\n            ctx.eval(lock, \"arr.sumF64Slice(new Float32Array([1.0, 2.0]))\");\n        assert!(result.is_err());\n\n        // Float64Array should reject regular Array\n        let result: Result<Number, _> = ctx.eval(lock, \"arr.sumF64Slice([1.0, 2.0])\");\n        assert!(result.is_err());\n\n        Ok(())\n    });\n}\n\n#[test]\nfn float64_array_to_js_and_from_js() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        // Test Vec<f64> to JavaScript\n        let vec: Vec<f64> = vec![1.5, 2.5, -3.5];\n        ctx.set_global(\"f64_arr\", vec.to_js(lock));\n\n        let is_f64: bool = ctx.eval(lock, \"f64_arr instanceof Float64Array\").unwrap();\n        assert!(is_f64);\n\n        let val: Number = ctx.eval(lock, \"f64_arr[0]\").unwrap();\n        assert!((val.value() - 1.5).abs() < f64::EPSILON);\n\n        let val: Number = ctx.eval(lock, \"f64_arr[2]\").unwrap();\n        assert!((val.value() - (-3.5)).abs() < f64::EPSILON);\n\n        // Test Float64Array from JavaScript\n        let result: Vec<f64> = ctx.eval(lock, \"new Float64Array([1.1, 2.2, 3.3])\").unwrap();\n        assert!((result[0] - 1.1).abs() < f64::EPSILON);\n        assert!((result[1] - 2.2).abs() < f64::EPSILON);\n        assert!((result[2] - 3.3).abs() < f64::EPSILON);\n\n        Ok(())\n    });\n}\n\n// =============================================================================\n// BigInt64Array tests\n// =============================================================================\n\n#[test]\nfn bigint64_array_parameter_and_return() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        // Test Vec<i64> parameter\n        let result: Number = ctx\n            .eval(lock, \"arr.sumI64(new BigInt64Array([1n, 2n, 3n]))\")\n            .unwrap();\n        assert!((result.value() - 6.0).abs() < f64::EPSILON);\n\n        // Test Vec<i64> return value\n        let is_i64: bool = ctx\n            .eval(\n                lock,\n                \"arr.doubleI64(new BigInt64Array([1n, 2n])) instanceof BigInt64Array\",\n            )\n            .unwrap();\n        assert!(is_i64);\n\n        let result: Vec<i64> = ctx\n            .eval(lock, \"arr.doubleI64(new BigInt64Array([1n, 2n, 3n]))\")\n            .unwrap();\n        assert_eq!(result, vec![2, 4, 6]);\n\n        // Test with negative values\n        let result: Number = ctx\n            .eval(lock, \"arr.sumI64(new BigInt64Array([-10n, 20n, -5n]))\")\n            .unwrap();\n        assert!((result.value() - 5.0).abs() < f64::EPSILON);\n\n        Ok(())\n    });\n}\n\n#[test]\nfn bigint64_array_slice_parameter() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        // Test &[i64] parameter\n        let result: Number = ctx\n            .eval(lock, \"arr.sumI64Slice(new BigInt64Array([10n, 20n, 30n]))\")\n            .unwrap();\n        assert!((result.value() - 60.0).abs() < f64::EPSILON);\n\n        // Test empty BigInt64Array\n        let result: Number = ctx\n            .eval(lock, \"arr.sumI64Slice(new BigInt64Array([]))\")\n            .unwrap();\n        assert!((result.value() - 0.0).abs() < f64::EPSILON);\n\n        Ok(())\n    });\n}\n\n#[test]\nfn bigint64_array_rejects_wrong_type() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        // BigInt64Array should reject BigUint64Array\n        let result: Result<Number, _> =\n            ctx.eval(lock, \"arr.sumI64Slice(new BigUint64Array([1n, 2n]))\");\n        assert!(result.is_err());\n\n        // BigInt64Array should reject Int32Array\n        let result: Result<Number, _> = ctx.eval(lock, \"arr.sumI64Slice(new Int32Array([1, 2]))\");\n        assert!(result.is_err());\n\n        Ok(())\n    });\n}\n\n#[test]\nfn bigint64_array_to_js_and_from_js() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        // Test Vec<i64> to JavaScript\n        let vec: Vec<i64> = vec![100, -200, 300];\n        ctx.set_global(\"i64_arr\", vec.to_js(lock));\n\n        let is_i64: bool = ctx.eval(lock, \"i64_arr instanceof BigInt64Array\").unwrap();\n        assert!(is_i64);\n\n        // BigInt64Array returns BigInt values, verify via string\n        let val: String = ctx.eval(lock, \"String(i64_arr[0])\").unwrap();\n        assert_eq!(val, \"100\");\n\n        let val: String = ctx.eval(lock, \"String(i64_arr[1])\").unwrap();\n        assert_eq!(val, \"-200\");\n\n        // Test BigInt64Array from JavaScript\n        let result: Vec<i64> = ctx.eval(lock, \"new BigInt64Array([1n, -2n, 3n])\").unwrap();\n        assert_eq!(result, vec![1, -2, 3]);\n\n        Ok(())\n    });\n}\n\n// =============================================================================\n// BigUint64Array tests\n// =============================================================================\n\n#[test]\nfn biguint64_array_parameter_and_return() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        // Test Vec<u64> parameter\n        let result: Number = ctx\n            .eval(lock, \"arr.sumU64(new BigUint64Array([1n, 2n, 3n]))\")\n            .unwrap();\n        assert!((result.value() - 6.0).abs() < f64::EPSILON);\n\n        // Test Vec<u64> return value\n        let is_u64: bool = ctx\n            .eval(\n                lock,\n                \"arr.doubleU64(new BigUint64Array([1n, 2n])) instanceof BigUint64Array\",\n            )\n            .unwrap();\n        assert!(is_u64);\n\n        let result: Vec<u64> = ctx\n            .eval(lock, \"arr.doubleU64(new BigUint64Array([1n, 2n, 3n]))\")\n            .unwrap();\n        assert_eq!(result, vec![2, 4, 6]);\n\n        Ok(())\n    });\n}\n\n#[test]\nfn biguint64_array_slice_parameter() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        // Test &[u64] parameter\n        let result: Number = ctx\n            .eval(lock, \"arr.sumU64Slice(new BigUint64Array([10n, 20n, 30n]))\")\n            .unwrap();\n        assert!((result.value() - 60.0).abs() < f64::EPSILON);\n\n        // Test empty BigUint64Array\n        let result: Number = ctx\n            .eval(lock, \"arr.sumU64Slice(new BigUint64Array([]))\")\n            .unwrap();\n        assert!((result.value() - 0.0).abs() < f64::EPSILON);\n\n        Ok(())\n    });\n}\n\n#[test]\nfn biguint64_array_rejects_wrong_type() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ArrayResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"arr\", wrapped);\n\n        // BigUint64Array should reject BigInt64Array\n        let result: Result<Number, _> =\n            ctx.eval(lock, \"arr.sumU64Slice(new BigInt64Array([1n, 2n]))\");\n        assert!(result.is_err());\n\n        // BigUint64Array should reject Uint32Array\n        let result: Result<Number, _> = ctx.eval(lock, \"arr.sumU64Slice(new Uint32Array([1, 2]))\");\n        assert!(result.is_err());\n\n        Ok(())\n    });\n}\n\n#[test]\nfn biguint64_array_to_js_and_from_js() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        // Test Vec<u64> to JavaScript\n        let vec: Vec<u64> = vec![100, 200, 300];\n        ctx.set_global(\"u64_arr\", vec.to_js(lock));\n\n        let is_u64: bool = ctx.eval(lock, \"u64_arr instanceof BigUint64Array\").unwrap();\n        assert!(is_u64);\n\n        // BigUint64Array returns BigInt values, verify via string\n        let val: String = ctx.eval(lock, \"String(u64_arr[0])\").unwrap();\n        assert_eq!(val, \"100\");\n\n        let val: String = ctx.eval(lock, \"String(u64_arr[2])\").unwrap();\n        assert_eq!(val, \"300\");\n\n        // Test BigUint64Array from JavaScript\n        let result: Vec<u64> = ctx.eval(lock, \"new BigUint64Array([1n, 2n, 3n])\").unwrap();\n        assert_eq!(result, vec![1, 2, 3]);\n\n        Ok(())\n    });\n}\n"
  },
  {
    "path": "src/rust/jsg-test/tests/eval.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nuse jsg::Number;\n\nuse crate::EvalError;\n\n#[test]\nfn eval_returns_correct_type() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let result: String = ctx.eval(lock, \"'Hello, World!'\").unwrap();\n        assert_eq!(result, \"Hello, World!\");\n        Ok(())\n    });\n}\n\n#[test]\nfn eval_string_concatenation() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let result: String = ctx.eval(lock, \"'Hello' + ', ' + 'World!'\").unwrap();\n        assert_eq!(result, \"Hello, World!\");\n        Ok(())\n    });\n}\n\n#[test]\nfn eval_number_returns_number_type() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let result: Number = ctx.eval(lock, \"42\").unwrap();\n        assert!((result.value() - 42.0).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n#[test]\nfn eval_arithmetic_expression() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let result: Number = ctx.eval(lock, \"1 + 2 + 3\").unwrap();\n        assert!((result.value() - 6.0).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n#[test]\nfn eval_boolean_true() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let result: bool = ctx.eval(lock, \"true\").unwrap();\n        assert!(result);\n        Ok(())\n    });\n}\n\n#[test]\nfn eval_boolean_false() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let result: bool = ctx.eval(lock, \"false\").unwrap();\n        assert!(!result);\n        Ok(())\n    });\n}\n\n#[test]\nfn eval_comparison_expression() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let result: bool = ctx.eval(lock, \"5 > 3\").unwrap();\n        assert!(result);\n        Ok(())\n    });\n}\n\n#[test]\nfn eval_null() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let result: jsg::Nullable<bool> = ctx.eval(lock, \"null\").unwrap();\n        assert!(result.is_null());\n        Ok(())\n    });\n}\n\n#[test]\nfn eval_throws_on_error() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let result = ctx.eval::<bool>(lock, \"throw new Error('test error')\");\n        match result.unwrap_err() {\n            EvalError::Exception(value) => {\n                assert_eq!(value.to_string(), \"Error: test error\");\n            }\n            _ => panic!(\"Unexpected error type\"),\n        }\n        Ok(())\n    });\n}\n\n#[test]\nfn eval_throws_string_preserves_message() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let result = ctx.eval::<bool>(lock, \"throw 'custom string error'\");\n        match result.unwrap_err() {\n            EvalError::Exception(value) => {\n                assert_eq!(value.to_string(), \"custom string error\");\n            }\n            _ => panic!(\"Unexpected error type\"),\n        }\n        Ok(())\n    });\n}\n\n#[test]\nfn eval_function_call() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let result: String = ctx\n            .eval(lock, \"(function() { return 'from function'; })()\")\n            .unwrap();\n        assert_eq!(result, \"from function\");\n        Ok(())\n    });\n}\n\n#[test]\nfn eval_typeof_string() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let result: String = ctx.eval(lock, \"typeof 'hello'\").unwrap();\n        assert_eq!(result, \"string\");\n        Ok(())\n    });\n}\n\n#[test]\nfn eval_typeof_number() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let result: String = ctx.eval(lock, \"typeof 42\").unwrap();\n        assert_eq!(result, \"number\");\n        Ok(())\n    });\n}\n\n#[test]\nfn eval_typeof_boolean() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let result: String = ctx.eval(lock, \"typeof true\").unwrap();\n        assert_eq!(result, \"boolean\");\n        Ok(())\n    });\n}\n\n#[test]\nfn eval_unicode_string() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let result: String = ctx.eval(lock, \"'こんにちは'\").unwrap();\n        assert_eq!(result, \"こんにちは\");\n        Ok(())\n    });\n}\n\n#[test]\nfn eval_emoji_string() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let result: String = ctx.eval(lock, \"'😀🎉'\").unwrap();\n        assert_eq!(result, \"😀🎉\");\n        Ok(())\n    });\n}\n"
  },
  {
    "path": "src/rust/jsg-test/tests/function.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n//! Tests for `Local<Function>::call()`, `As<T>` trait, and `impl_local_cast!` conversions.\n\nuse jsg::Number;\nuse jsg::ToJS;\nuse jsg::v8;\n\n/// `try_as::<Function>()` succeeds for a JS function value.\n#[test]\nfn try_as_function_succeeds() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|_lock, ctx| {\n        let value = ctx.eval_raw(\"(x => x * 2)\").unwrap();\n        assert!(value.try_as::<v8::Function>().is_some());\n        Ok(())\n    });\n}\n\n/// `try_as::<Function>()` returns `None` for a non-function value.\n#[test]\nfn try_as_function_fails_for_number() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|_lock, ctx| {\n        let value = ctx.eval_raw(\"42\").unwrap();\n        assert!(value.try_as::<v8::Function>().is_none());\n        Ok(())\n    });\n}\n\n/// `try_as::<Object>()` succeeds for a plain object.\n#[test]\nfn try_as_object_succeeds() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|_lock, ctx| {\n        let value = ctx.eval_raw(\"({a: 1})\").unwrap();\n        assert!(value.try_as::<v8::Object>().is_some());\n        Ok(())\n    });\n}\n\n/// `try_as::<Object>()` returns `None` for a primitive.\n#[test]\nfn try_as_object_fails_for_string() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|_lock, ctx| {\n        let value = ctx.eval_raw(\"'hello'\").unwrap();\n        assert!(value.try_as::<v8::Object>().is_none());\n        Ok(())\n    });\n}\n\n/// `try_as::<Array>()` succeeds for an array.\n#[test]\nfn try_as_array_succeeds() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|_lock, ctx| {\n        let value = ctx.eval_raw(\"[1, 2, 3]\").unwrap();\n        assert!(value.try_as::<v8::Array>().is_some());\n        Ok(())\n    });\n}\n\n/// `try_as::<Array>()` returns `None` for a non-array object.\n#[test]\nfn try_as_array_fails_for_object() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|_lock, ctx| {\n        let value = ctx.eval_raw(\"({length: 3})\").unwrap();\n        assert!(value.try_as::<v8::Array>().is_none());\n        Ok(())\n    });\n}\n\n/// `Local<Function>::call` with zero args returns the correct value via `FromJS`.\n#[test]\nfn call_zero_args() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let value = ctx.eval_raw(\"(() => 42)\").unwrap();\n        let func = value.try_as::<v8::Function>().unwrap();\n        let result = func.call::<Number, _>(lock, None::<v8::Local<v8::Value>>, &[])?;\n        assert!((result.value() - 42.0).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n/// `Local<Function>::call` with arguments.\n#[test]\nfn call_with_args() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let value = ctx.eval_raw(\"((a, b) => a + b)\").unwrap();\n        let func = value.try_as::<v8::Function>().unwrap();\n\n        let a = Number::new(10.0).to_js(lock);\n        let b = Number::new(32.0).to_js(lock);\n        let result = func.call::<Number, _>(lock, None::<v8::Local<v8::Value>>, &[a, b])?;\n        assert!((result.value() - 42.0).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n/// `Local<Function>::call` returning a string via `FromJS`.\n#[test]\nfn call_returns_string() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let value = ctx.eval_raw(\"(s => s.toUpperCase())\").unwrap();\n        let func = value.try_as::<v8::Function>().unwrap();\n\n        let arg = String::from(\"hello\").to_js(lock);\n        let result = func.call::<String, _>(lock, None::<v8::Local<v8::Value>>, &[arg])?;\n        assert_eq!(result, \"HELLO\");\n        Ok(())\n    });\n}\n\n/// `Local<Function>::call` with an object receiver (`this`).\n#[test]\nfn call_with_receiver() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        // Create an object with a property and a function that reads `this.x`\n        let obj = ctx.eval_raw(\"({x: 100})\").unwrap();\n        let func_val = ctx.eval_raw(\"(function() { return this.x; })\").unwrap();\n        let func = func_val.try_as::<v8::Function>().unwrap();\n\n        let result = func.call::<Number, _>(lock, Some(obj), &[])?;\n        assert!((result.value() - 100.0).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n/// `Local<Function>::call` with a `Local<Object>` receiver via `.into()`.\n#[test]\nfn call_with_object_receiver_via_into() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let obj_val = ctx.eval_raw(\"({name: 'world'})\").unwrap();\n        let obj = obj_val.try_as::<v8::Object>().unwrap();\n\n        let func_val = ctx\n            .eval_raw(\"(function() { return 'hello ' + this.name; })\")\n            .unwrap();\n        let func = func_val.try_as::<v8::Function>().unwrap();\n\n        // Pass Local<Object> as receiver — the Into<Local<Value>> bound handles conversion.\n        let result = func.call::<String, v8::Local<v8::Object>>(lock, Some(obj), &[])?;\n        assert_eq!(result, \"hello world\");\n        Ok(())\n    });\n}\n\n/// `impl_local_cast!` conversions: `Local<Object>` → `Local<Value>` via `Into`.\n#[test]\nfn local_object_into_value() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|_lock, ctx| {\n        let obj_val = ctx.eval_raw(\"({})\").unwrap();\n        let obj = obj_val.try_as::<v8::Object>().unwrap();\n        // Into<Local<Value>> should compile and not panic in debug.\n        let value: v8::Local<v8::Value> = obj.into();\n        assert!(value.is_object());\n        Ok(())\n    });\n}\n\n/// `impl_local_cast!` conversions: `Local<Function>` → `Local<Value>` via `Into`.\n#[test]\nfn local_function_into_value() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|_lock, ctx| {\n        let func_val = ctx.eval_raw(\"(() => {})\").unwrap();\n        let func = func_val.try_as::<v8::Function>().unwrap();\n        let value: v8::Local<v8::Value> = func.into();\n        assert!(value.is_object());\n        Ok(())\n    });\n}\n\n/// `impl_local_cast!` conversions: `Local<Array>` → `Local<Value>` via `Into`.\n#[test]\nfn local_array_into_value() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|_lock, ctx| {\n        let arr_val = ctx.eval_raw(\"[1, 2]\").unwrap();\n        let arr = arr_val.try_as::<v8::Array>().unwrap();\n        let value: v8::Local<v8::Value> = arr.into();\n        assert!(value.is_array());\n        Ok(())\n    });\n}\n"
  },
  {
    "path": "src/rust/jsg-test/tests/gc.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n//! GC (garbage collection) tests for Rust resources.\n//!\n//! These tests verify that Rust resources are properly cleaned up when:\n//! 1. All Rust `Ref` handles are dropped and no JavaScript wrapper exists (immediate cleanup)\n//! 2. All Rust `Ref` handles are dropped and V8 garbage collects the JS wrapper\n//!\n//! Note: Circular references through `Ref<T>` are NOT collected, matching the behavior\n//! of C++ `jsg::Rc<T>` which uses `kj::Own<T>` cross-references.\n\nuse std::cell::Cell;\nuse std::sync::atomic::AtomicUsize;\nuse std::sync::atomic::Ordering;\n\nuse jsg::ToJS;\nuse jsg_macros::jsg_method;\nuse jsg_macros::jsg_resource;\n\n/// Counter to track how many `SimpleResource` instances have been dropped.\nstatic SIMPLE_RESOURCE_DROPS: AtomicUsize = AtomicUsize::new(0);\n\n#[jsg_resource]\nstruct SimpleResource {\n    pub name: String,\n}\n\nimpl Drop for SimpleResource {\n    fn drop(&mut self) {\n        SIMPLE_RESOURCE_DROPS.fetch_add(1, Ordering::SeqCst);\n    }\n}\n\n#[jsg_resource]\n#[expect(clippy::unnecessary_wraps)]\nimpl SimpleResource {\n    #[jsg_method]\n    fn get_name(&self) -> Result<String, jsg::Error> {\n        Ok(self.name.clone())\n    }\n}\n\n/// Counter to track how many `ParentResource` instances have been dropped.\nstatic PARENT_RESOURCE_DROPS: AtomicUsize = AtomicUsize::new(0);\n\n#[jsg_resource]\nstruct ParentResource {\n    pub child: jsg::Rc<SimpleResource>,\n    pub optional_child: Option<jsg::Rc<SimpleResource>>,\n}\n\nimpl Drop for ParentResource {\n    fn drop(&mut self) {\n        PARENT_RESOURCE_DROPS.fetch_add(1, Ordering::SeqCst);\n    }\n}\n\n#[jsg_resource]\nimpl ParentResource {}\n\n/// Tests that resources are dropped immediately when all Rust Refs are dropped\n/// and no JS wrapper exists.\n///\n/// In the Wrappable model, dropping the last Ref decrements the kj refcount to 0,\n/// which immediately destroys the Wrappable (and thus the Rust resource).\n/// No GC is needed.\n#[test]\nfn supports_gc_via_ref_drop() {\n    SIMPLE_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|_lock, _ctx| {\n        let resource = jsg::Rc::new(SimpleResource {\n            name: \"test\".to_owned(),\n        });\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n        std::mem::drop(resource);\n        // In the Wrappable model, no wrapper means immediate cleanup when refcount hits 0\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n/// Tests that resources are dropped via V8 GC when JS wrapper is collected.\n///\n/// When a resource is wrapped for JavaScript:\n/// 1. Dropping all Rust `Ref` handles calls `removeStrongRef()` but the `CppgcShim`\n///    still holds a `kj::Own` keeping the object alive\n/// 2. V8 GC collects the wrapper, `CppgcShim` is destroyed, `kj::Own` is dropped\n/// 3. kj refcount reaches 0, Wrappable is destroyed\n#[test]\nfn supports_gc_via_weak_callback() {\n    SIMPLE_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let resource = jsg::Rc::new(SimpleResource {\n            name: \"test\".to_owned(),\n        });\n        let _wrapped = resource.clone().to_js(lock);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n        std::mem::drop(resource);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n        Ok(())\n    });\n\n    harness.run_in_context(|lock, _ctx| {\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n        crate::Harness::request_gc(lock);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n#[test]\nfn resource_with_traced_ref_field() {\n    SIMPLE_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n    PARENT_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let child = jsg::Rc::new(SimpleResource {\n            name: \"child\".to_owned(),\n        });\n        let optional_child = jsg::Rc::new(SimpleResource {\n            name: \"optional_child\".to_owned(),\n        });\n\n        let parent = jsg::Rc::new(ParentResource {\n            child: child.clone(),\n            optional_child: Some(optional_child.clone()),\n        });\n\n        let _wrapped = parent.clone().to_js(lock);\n\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n        assert_eq!(PARENT_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n\n        std::mem::drop(child);\n        std::mem::drop(optional_child);\n        std::mem::drop(parent);\n        Ok(())\n    });\n\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        assert_eq!(PARENT_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 2);\n        Ok(())\n    });\n}\n\n#[test]\nfn child_traced_ref_kept_alive_by_parent() {\n    SIMPLE_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n    PARENT_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let child = jsg::Rc::new(SimpleResource {\n            name: \"child\".to_owned(),\n        });\n\n        let parent = jsg::Rc::new(ParentResource {\n            child: child.clone(),\n            optional_child: None,\n        });\n\n        // Wrap the parent so it has a JS object, then let the Local go out of scope\n        // by not storing it. The wrapper is now only held weakly by cppgc.\n        let _ = parent.clone().to_js(lock);\n\n        // Child not collected because parent still holds a Ref (which holds a kj::Own)\n        std::mem::drop(child);\n        crate::Harness::request_gc(lock);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n\n        // Drop the Rust strong ref. The parent still has a wrapper, but with no\n        // strong refs and no JS references, the wrapper is eligible for GC.\n        std::mem::drop(parent);\n        Ok(())\n    });\n\n    // GC in a separate context so the Local handle from wrap() is gone\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        assert_eq!(PARENT_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n#[test]\nfn weak_ref_upgrade() {\n    SIMPLE_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|_lock, _ctx| {\n        let strong = jsg::Rc::new(SimpleResource {\n            name: \"test\".to_owned(),\n        });\n        let weak = strong.downgrade();\n\n        assert!(weak.is_alive());\n        let upgraded = weak.upgrade();\n        assert!(upgraded.is_some());\n        assert!(weak.is_alive());\n\n        std::mem::drop(upgraded);\n        assert!(weak.is_alive());\n\n        std::mem::drop(strong);\n        // No wrapper, so resource is destroyed immediately when last strong ref drops\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n        assert!(!weak.is_alive());\n        assert!(weak.upgrade().is_none());\n        Ok(())\n    });\n}\n\n/// Tests that a wrapped resource stays alive as long as JS wrapper exists.\n#[test]\nfn wrapped_resource_kept_alive_by_js() {\n    SIMPLE_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let strong = jsg::Rc::new(SimpleResource {\n            name: \"test\".to_owned(),\n        });\n        let _wrapped = strong.clone().to_js(lock);\n        std::mem::drop(strong);\n        // CppgcShim still holds kj::Own, keeping it alive\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n        Ok(())\n    });\n\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n/// Tests weak ref behavior with wrapped resources.\n#[test]\nfn weak_ref_with_wrapped_resource() {\n    SIMPLE_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let strong = jsg::Rc::new(SimpleResource {\n            name: \"test\".to_owned(),\n        });\n        let _wrapped = strong.clone().to_js(lock);\n        let weak = strong.downgrade();\n\n        assert!(weak.upgrade().is_some());\n        std::mem::drop(strong);\n        // CppgcShim keeps it alive even after dropping the strong ref\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n        Ok(())\n    });\n\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n/// Tests parent-child GC with traced refs.\n#[test]\nfn traced_ref_in_gc() {\n    SIMPLE_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n    PARENT_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let child = jsg::Rc::new(SimpleResource {\n            name: \"child\".to_owned(),\n        });\n        let _child_wrapped = child.clone().to_js(lock);\n\n        let parent = jsg::Rc::new(ParentResource {\n            child: child.clone(),\n            optional_child: None,\n        });\n        let _parent_wrapped = parent.clone().to_js(lock);\n\n        std::mem::drop(child);\n        std::mem::drop(parent);\n\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n        assert_eq!(PARENT_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n        Ok(())\n    });\n\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        assert_eq!(PARENT_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n        crate::Harness::request_gc(lock);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n// =============================================================================\n// WeakRef tests\n// =============================================================================\n\n/// Tests that `WeakRef::get()` returns the resource data while the resource is alive.\n#[test]\nfn weak_ref_get_returns_resource_data() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|_lock, _ctx| {\n        let strong = jsg::Rc::new(SimpleResource {\n            name: \"hello\".to_owned(),\n        });\n        let weak = strong.downgrade();\n\n        // get() should return a reference to the resource\n        let resource = weak.upgrade().expect(\"weak ref should be alive\");\n        assert_eq!(resource.name, \"hello\");\n        Ok(())\n    });\n}\n\n/// Tests that `WeakRef::get()` returns `None` after the resource is dropped.\n#[test]\nfn weak_ref_get_returns_none_after_drop() {\n    SIMPLE_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|_lock, _ctx| {\n        let strong = jsg::Rc::new(SimpleResource {\n            name: \"ephemeral\".to_owned(),\n        });\n        let weak = strong.downgrade();\n        assert!(weak.upgrade().is_some());\n\n        std::mem::drop(strong);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n\n        // get() must return None without touching freed memory\n        assert!(weak.upgrade().is_none());\n        Ok(())\n    });\n}\n\n/// Tests that `WeakRef::default()` creates a dead weak reference.\n///\n/// A default `WeakRef` has `wrappable: None` and an expired `Weak<R>`.\n/// All operations must be safe: `get()` -> `None`, `upgrade()` -> `None`, `is_alive()` -> `false`.\n#[test]\nfn weak_ref_default_is_dead() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|_lock, _ctx| {\n        let weak: jsg::Weak<SimpleResource> = jsg::Weak::default();\n\n        assert!(!weak.is_alive());\n        assert!(weak.upgrade().is_none());\n        assert!(weak.upgrade().is_none());\n        Ok(())\n    });\n}\n\n/// Tests that cloning a `WeakRef` shares the alive marker.\n///\n/// When the resource dies, ALL clones must see `is_alive() == false` simultaneously.\n#[test]\nfn weak_ref_clone_shares_alive_marker() {\n    SIMPLE_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|_lock, _ctx| {\n        let strong = jsg::Rc::new(SimpleResource {\n            name: \"shared\".to_owned(),\n        });\n\n        let weak1 = strong.downgrade();\n        let weak2 = weak1.clone();\n        let weak3 = weak2.clone();\n\n        assert!(weak1.is_alive());\n        assert!(weak2.is_alive());\n        assert!(weak3.is_alive());\n\n        std::mem::drop(strong);\n\n        // All clones see death simultaneously\n        assert!(!weak1.is_alive());\n        assert!(!weak2.is_alive());\n        assert!(!weak3.is_alive());\n        assert!(weak1.upgrade().is_none());\n        assert!(weak2.upgrade().is_none());\n        Ok(())\n    });\n}\n\n/// Tests that `WeakRef::upgrade()` with a wrapped resource creates a strong ref\n/// that prevents GC collection.\n#[test]\nfn weak_ref_upgrade_with_wrapped_resource_prevents_gc() {\n    SIMPLE_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let strong = jsg::Rc::new(SimpleResource {\n            name: \"persistent\".to_owned(),\n        });\n        let _wrapped = strong.clone().to_js(lock);\n        let weak = strong.downgrade();\n\n        // Drop the original strong ref, but upgrade from weak creates a new one\n        std::mem::drop(strong);\n        let upgraded = weak.upgrade().expect(\"should be alive via wrapper\");\n        assert_eq!(upgraded.name, \"persistent\");\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n\n        // The upgraded Ref keeps the resource alive\n        std::mem::drop(upgraded);\n        Ok(())\n    });\n\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n/// Tests that `WeakRef::trace()` is a no-op and doesn't prevent GC collection.\n///\n/// A resource holding a `WeakRef` to another resource should not keep it alive through tracing.\n#[test]\nfn weak_ref_trace_does_not_prevent_gc() {\n    static HOLDER_DROPS: AtomicUsize = AtomicUsize::new(0);\n\n    #[jsg_resource]\n    struct WeakRefHolder {\n        weak: jsg::Weak<SimpleResource>,\n    }\n\n    impl Drop for WeakRefHolder {\n        fn drop(&mut self) {\n            HOLDER_DROPS.fetch_add(1, Ordering::SeqCst);\n        }\n    }\n\n    #[jsg_resource]\n    impl WeakRefHolder {}\n\n    SIMPLE_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n    HOLDER_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let target = jsg::Rc::new(SimpleResource {\n            name: \"target\".to_owned(),\n        });\n        let _target_wrapped = target.clone().to_js(lock);\n\n        let holder = jsg::Rc::new(WeakRefHolder {\n            weak: target.downgrade(),\n        });\n        let _holder_wrapped = holder.clone().to_js(lock);\n\n        // WeakRef should be upgradable while the target is alive\n        assert!(holder.weak.upgrade().is_some());\n\n        // Drop all Rust refs — both are only held by JS wrappers\n        std::mem::drop(target);\n        std::mem::drop(holder);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n        Ok(())\n    });\n\n    // GC should collect the target — the WeakRef in holder doesn't keep it alive\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        // Both should be collected (holder's WeakRef doesn't prevent target's collection)\n        assert_eq!(HOLDER_DROPS.load(Ordering::SeqCst), 1);\n        crate::Harness::request_gc(lock);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n// =============================================================================\n// Ref tests\n// =============================================================================\n\n/// Tests that `Ref::clone()` keeps the resource alive after the original is dropped.\n#[test]\nfn ref_clone_keeps_resource_alive() {\n    SIMPLE_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|_lock, _ctx| {\n        let original = jsg::Rc::new(SimpleResource {\n            name: \"cloned\".to_owned(),\n        });\n        let clone1 = original.clone();\n        let clone2 = original.clone();\n\n        std::mem::drop(original);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n        assert_eq!(clone1.name, \"cloned\");\n\n        std::mem::drop(clone1);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n        assert_eq!(clone2.name, \"cloned\");\n\n        std::mem::drop(clone2);\n        // Only now all refs are gone — resource is destroyed\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n/// Tests that dropping multiple Refs only triggers destruction on the last one.\n#[test]\nfn multiple_ref_drops_only_last_triggers_destruction() {\n    SIMPLE_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|_lock, _ctx| {\n        let original = jsg::Rc::new(SimpleResource {\n            name: \"multi\".to_owned(),\n        });\n\n        // Create several clones\n        let clones: Vec<_> = (0..5).map(|_| original.clone()).collect();\n        std::mem::drop(original);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n\n        // Drop one by one\n        for (i, c) in clones.into_iter().enumerate() {\n            std::mem::drop(c);\n            if i < 4 {\n                assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n            }\n        }\n        // Last clone dropped — resource is destroyed\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n/// Tests that `Ref::deref()` returns the correct resource data.\n#[test]\nfn ref_deref_returns_correct_resource_data() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|_lock, _ctx| {\n        let r = jsg::Rc::new(SimpleResource {\n            name: \"deref-test\".to_owned(),\n        });\n\n        // Deref should return the resource with correct data\n        assert_eq!(r.name, \"deref-test\");\n\n        // Clone should also deref to the same data\n        #[expect(clippy::redundant_clone)]\n        let clone = r.clone();\n        assert_eq!(clone.name, \"deref-test\");\n        Ok(())\n    });\n}\n\n// =============================================================================\n// unwrap / FromJS tests\n// =============================================================================\n\n/// Tests that `FromJS for Ref<R>` creates a strong reference from a JS wrapper.\n///\n/// The returned `Ref<R>` must keep the resource alive even after dropping the original.\n#[test]\nfn from_js_creates_strong_reference_from_js_wrapper() {\n    SIMPLE_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(SimpleResource {\n            name: \"unwrap-me\".to_owned(),\n        });\n        let wrapped = resource.clone().to_js(lock);\n        ctx.set_global(\"obj\", wrapped);\n\n        // Get the JS value back and use FromJS to create a new strong Ref\n        let js_val = ctx.eval_raw(\"obj\").unwrap();\n        let new_ref: jsg::Rc<SimpleResource> =\n            <jsg::Rc<SimpleResource> as jsg::FromJS>::from_js(lock, js_val)\n                .expect(\"FromJS should succeed for a wrapped resource\");\n        assert_eq!(new_ref.name, \"unwrap-me\");\n\n        // Drop the original Ref — the unwrapped ref should keep it alive\n        std::mem::drop(resource);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n\n        // The unwrapped ref is still valid\n        assert_eq!(new_ref.name, \"unwrap-me\");\n\n        // Dropping the unwrapped ref (with wrapper still alive) should not destroy\n        std::mem::drop(new_ref);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n        Ok(())\n    });\n\n    // GC the JS wrapper — now the resource can be collected\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n/// Tests that `FromJS` returns `Err` for a plain JS object.\n///\n/// A plain `{}` object has no internal fields and no wrappable tag.\n/// `FromJS::from_js` must return `Err` without crashing.\n#[test]\nfn from_js_returns_err_for_plain_js_object() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let plain_obj = ctx.eval_raw(\"({})\").unwrap();\n        let result = <jsg::Rc<SimpleResource> as jsg::FromJS>::from_js(lock, plain_obj);\n        assert!(\n            result.is_err(),\n            \"FromJS should return Err for a plain JS object\"\n        );\n        Ok(())\n    });\n}\n\n// =============================================================================\n// Wrap/template caching tests\n// =============================================================================\n\n/// Tests that wrapping multiple instances of the same type uses the same template.\n///\n/// Verifies that `Realm::get_constructor()` caches the template on first use\n/// and returns the same one on subsequent calls.\n#[test]\nfn wrap_multiple_instances_of_same_type() {\n    SIMPLE_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let r1 = jsg::Rc::new(SimpleResource {\n            name: \"first\".to_owned(),\n        });\n        let r2 = jsg::Rc::new(SimpleResource {\n            name: \"second\".to_owned(),\n        });\n\n        let w1 = r1.to_js(lock);\n        let w2 = r2.to_js(lock);\n        ctx.set_global(\"r1\", w1);\n        ctx.set_global(\"r2\", w2);\n\n        // Both should be instances of the same constructor (same prototype chain)\n        let same_proto: bool = ctx\n            .eval(\n                lock,\n                \"Object.getPrototypeOf(r1) === Object.getPrototypeOf(r2)\",\n            )\n            .unwrap();\n        assert!(same_proto);\n\n        // Both should work independently\n        let n1: String = ctx.eval(lock, \"r1.getName()\").unwrap();\n        let n2: String = ctx.eval(lock, \"r2.getName()\").unwrap();\n        assert_eq!(n1, \"first\");\n        assert_eq!(n2, \"second\");\n        Ok(())\n    });\n}\n\n/// Tests that wrapping the same resource twice returns the same JS object.\n///\n/// `Wrappable::tryGetHandle()` should detect an existing wrapper and return it.\n#[test]\nfn wrap_same_resource_twice_returns_same_object() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(SimpleResource {\n            name: \"singleton\".to_owned(),\n        });\n\n        let w1 = resource.clone().to_js(lock);\n        ctx.set_global(\"w1\", w1);\n        let w2 = resource.to_js(lock);\n        ctx.set_global(\"w2\", w2);\n\n        // Both wraps should return the exact same JS object\n        let same: bool = ctx.eval(lock, \"w1 === w2\").unwrap();\n        assert!(same);\n        Ok(())\n    });\n}\n\n// =============================================================================\n// Resource data integrity tests\n// =============================================================================\n\n/// Tests that resource data survives a GC cycle when held by a JS global.\n#[test]\nfn resource_data_survives_gc_via_js_global() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(SimpleResource {\n            name: \"survivor\".to_owned(),\n        });\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"obj\", wrapped);\n\n        // Force GC — the global reference should keep it alive\n        crate::Harness::request_gc(lock);\n\n        // Data should still be correct\n        let name: String = ctx.eval(lock, \"obj.getName()\").unwrap();\n        assert_eq!(name, \"survivor\");\n        Ok(())\n    });\n}\n\n/// Tests parent-child relationships where parent is only held by JS.\n///\n/// When the parent is wrapped and held by a JS global, its `Ref<SimpleResource>` child\n/// should be traced during GC and kept alive even without any Rust strong refs.\n#[test]\nfn parent_ref_keeps_child_alive_through_gc() {\n    SIMPLE_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n    PARENT_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let child = jsg::Rc::new(SimpleResource {\n            name: \"child\".to_owned(),\n        });\n        let parent = jsg::Rc::new(ParentResource {\n            child: child.clone(),\n            optional_child: None,\n        });\n        let wrapped = parent.clone().to_js(lock);\n        ctx.set_global(\"parent\", wrapped);\n\n        // Drop all Rust refs\n        std::mem::drop(child);\n        std::mem::drop(parent);\n\n        // GC should NOT collect the parent (held by global) or child (held by parent's Ref)\n        crate::Harness::request_gc(lock);\n        assert_eq!(PARENT_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n        Ok(())\n    });\n\n    // New context — global is gone\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        assert_eq!(PARENT_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n/// Tests that dropping all strong refs correctly invalidates ALL weak refs.\n///\n/// Creates multiple `WeakRef`s from different `Ref`s, verifies they all see death at the same time.\n#[test]\nfn instance_drop_invalidates_all_weak_refs() {\n    SIMPLE_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|_lock, _ctx| {\n        let original = jsg::Rc::new(SimpleResource {\n            name: \"shared-target\".to_owned(),\n        });\n        let clone1 = original.clone();\n        let clone2 = original.clone();\n\n        // Create weak refs from different strong refs\n        let weak_from_original = original.downgrade();\n        let weak_from_clone1 = clone1.downgrade();\n        let weak_from_clone2 = clone2.downgrade();\n\n        // All alive\n        assert!(weak_from_original.is_alive());\n        assert!(weak_from_clone1.is_alive());\n        assert!(weak_from_clone2.is_alive());\n\n        // Drop all strong refs — resource is destroyed\n        std::mem::drop(original);\n        std::mem::drop(clone1);\n        std::mem::drop(clone2);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n\n        // ALL weak refs see death\n        assert!(!weak_from_original.is_alive());\n        assert!(!weak_from_clone1.is_alive());\n        assert!(!weak_from_clone2.is_alive());\n        assert!(weak_from_original.upgrade().is_none());\n        assert!(weak_from_clone1.upgrade().is_none());\n        assert!(weak_from_clone2.upgrade().is_none());\n        Ok(())\n    });\n}\n\n// =============================================================================\n// Nullable<Ref<T>> tracing tests\n// =============================================================================\n\n/// Counter to track how many `NullableParent` instances have been dropped.\nstatic NULLABLE_PARENT_DROPS: AtomicUsize = AtomicUsize::new(0);\n\n#[jsg_resource]\nstruct NullableParent {\n    pub child: jsg::Nullable<jsg::Rc<SimpleResource>>,\n}\n\nimpl Drop for NullableParent {\n    fn drop(&mut self) {\n        NULLABLE_PARENT_DROPS.fetch_add(1, Ordering::SeqCst);\n    }\n}\n\n#[jsg_resource]\nimpl NullableParent {}\n\n/// Tests that `Nullable<Ref<T>>` with `Nullable::Some` keeps the child alive through GC tracing.\n#[test]\nfn nullable_ref_some_keeps_child_alive_through_gc() {\n    SIMPLE_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n    NULLABLE_PARENT_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let child = jsg::Rc::new(SimpleResource {\n            name: \"nullable_child\".to_owned(),\n        });\n        let parent = jsg::Rc::new(NullableParent {\n            child: jsg::Nullable::Some(child.clone()),\n        });\n        let wrapped = parent.clone().to_js(lock);\n        ctx.set_global(\"parent\", wrapped);\n\n        // Drop all Rust refs\n        std::mem::drop(child);\n        std::mem::drop(parent);\n\n        // GC should NOT collect the parent (held by global) or child (traced via Nullable::Some)\n        crate::Harness::request_gc(lock);\n        assert_eq!(NULLABLE_PARENT_DROPS.load(Ordering::SeqCst), 0);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n        Ok(())\n    });\n\n    // New context — global is gone\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        assert_eq!(NULLABLE_PARENT_DROPS.load(Ordering::SeqCst), 1);\n        assert_eq!(SIMPLE_RESOURCE_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n/// Tests that `Nullable<Ref<T>>` with `Nullable::Null` doesn't cause issues during GC.\n#[test]\nfn nullable_ref_null_does_not_crash_during_gc() {\n    NULLABLE_PARENT_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let parent = jsg::Rc::new(NullableParent {\n            child: jsg::Nullable::Null,\n        });\n        let wrapped = parent.clone().to_js(lock);\n        ctx.set_global(\"parent\", wrapped);\n\n        std::mem::drop(parent);\n\n        // GC with Nullable::Null should not crash\n        crate::Harness::request_gc(lock);\n        assert_eq!(NULLABLE_PARENT_DROPS.load(Ordering::SeqCst), 0);\n        Ok(())\n    });\n\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        assert_eq!(NULLABLE_PARENT_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n/// Tests that `Nullable<Ref<T>>` with `Nullable::Undefined` doesn't cause issues during GC.\n#[test]\nfn nullable_ref_undefined_does_not_crash_during_gc() {\n    NULLABLE_PARENT_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let parent = jsg::Rc::new(NullableParent {\n            child: jsg::Nullable::Undefined,\n        });\n        let wrapped = parent.clone().to_js(lock);\n        ctx.set_global(\"parent\", wrapped);\n\n        std::mem::drop(parent);\n\n        // GC with Nullable::Undefined should not crash\n        crate::Harness::request_gc(lock);\n        assert_eq!(NULLABLE_PARENT_DROPS.load(Ordering::SeqCst), 0);\n        Ok(())\n    });\n\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        assert_eq!(NULLABLE_PARENT_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n// ---------------------------------------------------------------------------\n// Rc<NativeState> shared ownership — native object outlives Ref but is\n// dropped when V8 GC collects the JS wrapper.\n// ---------------------------------------------------------------------------\n\nstatic NATIVE_STATE_DROPS: AtomicUsize = AtomicUsize::new(0);\n\nstruct NativeState {\n    value: u64,\n}\n\nimpl Drop for NativeState {\n    fn drop(&mut self) {\n        NATIVE_STATE_DROPS.fetch_add(1, Ordering::SeqCst);\n    }\n}\n\n#[jsg_resource]\nstruct NativeOwnerResource {\n    state: std::rc::Rc<NativeState>,\n}\n\n#[jsg_resource]\nimpl NativeOwnerResource {\n    #[jsg_method]\n    fn get_value(&self) -> jsg::Number {\n        #[expect(clippy::cast_precision_loss)]\n        jsg::Number::new(self.state.value as f64)\n    }\n}\n\n/// Regression test for the `strong` flag bug in `wrappable_remove_strong_ref`.\n///\n/// When a parent resource (with a JS wrapper) holds a `Ref<Child>`, GC tracing\n/// transitions the child's ref from strong→weak via `visitRef`, which calls\n/// `removeStrongRef()` once.\n///\n/// **Bug**: `wrappable_remove_strong_ref` always passed `strong=true` to\n/// `maybeDeferDestruction`, so when the parent was later collected and the\n/// child `Ref` dropped, `~RefToDelete` called `removeStrongRef()` a second\n/// time — underflowing the strong refcount.\n///\n/// This test keeps a direct Rust ref to the child alive so we can observe\n/// the strong refcount after the parent's traced ref is dropped by GC.\n/// With the bug: `strong_refcount()` returns 0 instead of 1.\n#[test]\n#[cfg(debug_assertions)]\nfn traced_ref_drop_respects_strong_flag() {\n    let harness = crate::Harness::new();\n    let mut child_holder: Option<jsg::Rc<SimpleResource>> = None;\n\n    harness.run_in_context(|lock, _ctx| {\n        let child = jsg::Rc::new(SimpleResource {\n            name: \"traced_child\".to_owned(),\n        });\n\n        // strongRefcount = 1 (our ref).\n        assert_eq!(child.strong_refcount(), 1);\n\n        let parent = jsg::Rc::new(ParentResource {\n            child: child.clone(),\n            optional_child: None,\n        });\n\n        // strongRefcount = 2 (our ref + parent's ref).\n        assert_eq!(child.strong_refcount(), 2);\n\n        // Wrap the parent — child is reachable only through the parent's Ref.\n        let _wrapped = parent.clone().to_js(lock);\n\n        // GC traces parent wrapper → visitRef on child → parent's ref\n        // transitions strong→weak, removeStrongRef() called once.\n        // strongRefcount = 1 (only our direct ref remains strong).\n        crate::Harness::request_gc(lock);\n        assert_eq!(child.strong_refcount(), 1);\n\n        // Drop parent Rust ref. JS wrapper keeps parent alive.\n        std::mem::drop(parent);\n\n        // Move child out so we can observe it after GC collects the parent.\n        child_holder = Some(child);\n        Ok(())\n    });\n\n    let child = child_holder.expect(\"child not set\");\n\n    // Second GC: parent's JS wrapper is unreachable → parent collected →\n    // parent's child Ref drops → wrappable_remove_strong_ref called.\n    //\n    // With the bug: parent's ref has strong=false but remove_strong_ref\n    // always passes true → removeStrongRef() called → strongRefcount\n    // goes from 1 to 0, but our ref is still strong → should be 1!\n    //\n    // With the fix: strong=false is threaded through → no removeStrongRef\n    // call → strongRefcount stays at 1 (correct).\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        assert_eq!(\n            child.strong_refcount(),\n            1,\n            \"strong refcount underflowed — \\\n            wrappable_remove_strong_ref passed strong=true for a traced (weak) ref\"\n        );\n        Ok(())\n    });\n}\n\n/// A Rust resource holds an `Rc<NativeState>`. After wrapping for JS and\n/// dropping the Rust `Ref`, the native state is kept alive by the `CppgcShim`.\n/// Minor (young-generation) GC must collect the wrapper and drop the native\n/// state — proving the `ResetRoot` / `detachLater` path works for Rust resources.\n#[test]\nfn rc_native_object_dropped_on_minor_gc() {\n    NATIVE_STATE_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    let state = std::rc::Rc::new(NativeState { value: 99 });\n    let state_clone = state.clone();\n\n    harness.run_in_context(|lock, _ctx| {\n        let resource = jsg::Rc::new(NativeOwnerResource { state: state_clone });\n\n        // Wrap for JS so CppgcShim takes ownership of the Wrappable.\n        let _wrapped = resource.clone().to_js(lock);\n\n        // Drop the Rust Ref. CppgcShim still keeps the resource alive.\n        std::mem::drop(resource);\n        assert_eq!(NATIVE_STATE_DROPS.load(Ordering::SeqCst), 0);\n        Ok(())\n    });\n\n    // Drop the external Rc clone. The resource's Rc inside the Wrappable\n    // is the last one, but the Wrappable is alive (held by CppgcShim).\n    std::mem::drop(state);\n    assert_eq!(NATIVE_STATE_DROPS.load(Ordering::SeqCst), 0);\n\n    // Minor GC collects the young-generation wrapper via the ResetRoot path,\n    // which detaches the CppgcShim → drops kj::Own<Wrappable> → drops the\n    // resource → drops Rc<NativeState> → NativeState is dropped.\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_minor_gc(lock);\n        assert_eq!(NATIVE_STATE_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n// =============================================================================\n// Global<Value> back-reference cycle — collected via visit_global tracing\n// =============================================================================\n//\n// `jsg::v8::Global<T>` fields on Rust resources participate in GC tracing via\n// `GcVisitor::visit_global`, which implements the same strong↔traced dual-mode\n// switching as `jsg::Data` / `jsg::V8Ref<T>` in C++.\n//\n// When the parent Wrappable has strong Rust refs the handle stays strong.\n// Once all Rust refs are dropped and only the JS wrapper keeps it alive,\n// `visit_global` downgrades the handle to a `v8::TracedReference` that cppgc\n// can follow — allowing the GC to detect and collect the cycle.\n\nstatic CYCLIC_RESOURCE_DROPS: AtomicUsize = AtomicUsize::new(0);\n\n/// A resource that stores a `Global<Value>` via `Cell` so the back-reference\n/// to the resource's own JS wrapper can be installed after wrapping.\n///\n/// `Cell<Option<…>>` provides interior mutability through `&self`, matching\n/// the access pattern of `GarbageCollected::trace(&self)`.\n#[jsg_resource]\nstruct CyclicResource {\n    /// The stored \"callback\". Uses `Cell` so it can be set after `to_js()`\n    /// returns the wrapper `Local`, without requiring `&mut self`.\n    callback: std::cell::Cell<Option<jsg::v8::Global<jsg::v8::Value>>>,\n}\n\nimpl Drop for CyclicResource {\n    fn drop(&mut self) {\n        CYCLIC_RESOURCE_DROPS.fetch_add(1, Ordering::SeqCst);\n    }\n}\n\n#[jsg_resource]\nimpl CyclicResource {}\n\n/// Verifies that a `Global<Value>` back-reference cycle is collected by GC.\n///\n/// The cycle:\n///   CyclicResource.callback (Global<Value>) → JS wrapper\n///   JS wrapper → `CppgcShim` → `Wrappable` → `CyclicResource` (same object)\n///\n/// `visit_global` downgrades the `Global` to a `v8::TracedReference` once all\n/// strong Rust refs are dropped, making the cycle visible to cppgc so it can\n/// be collected on the next full GC.\n#[test]\nfn global_value_back_ref_is_collected_by_gc() {\n    CYCLIC_RESOURCE_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let resource = jsg::Rc::new(CyclicResource {\n            callback: std::cell::Cell::new(None),\n        });\n\n        // Wrap to produce the JS object, then immediately promote to a\n        // Global so we can store it back into the resource's own field.\n        // `to_js` consumes the Rc clone but the original `resource` still\n        // holds a strong ref, so the resource stays alive.\n        let wrapper_local = resource.clone().to_js(lock);\n        let wrapper_global = wrapper_local.to_global(lock);\n\n        // Install the back-reference: resource now holds a Global pointing\n        // to its own JS wrapper, closing the cycle.\n        resource.callback.set(Some(wrapper_global));\n\n        // Drop the only Rust Rc. The cycle is now closed but the Global\n        // will be downgraded to a TracedReference by visit_global, making\n        // it visible to cppgc.\n        std::mem::drop(resource);\n        assert_eq!(CYCLIC_RESOURCE_DROPS.load(Ordering::SeqCst), 0);\n        Ok(())\n    });\n\n    // Full GC can now detect and collect the cycle because visit_global\n    // downgrades the Global to a TracedReference during tracing.\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        assert_eq!(\n            CYCLIC_RESOURCE_DROPS.load(Ordering::SeqCst),\n            1,\n            \"full GC should collect the cyclic resource via visit_global tracing\"\n        );\n        Ok(())\n    });\n}\n\n// =============================================================================\n// Cell<T> tracing tests\n// =============================================================================\n\nstatic CELL_CHILD_DROPS: AtomicUsize = AtomicUsize::new(0);\n\n#[jsg_resource]\nstruct CellChild;\n\nimpl Drop for CellChild {\n    fn drop(&mut self) {\n        CELL_CHILD_DROPS.fetch_add(1, Ordering::SeqCst);\n    }\n}\n\n#[jsg_resource]\nimpl CellChild {}\n\nstatic CELL_PARENT_DROPS: AtomicUsize = AtomicUsize::new(0);\n\n/// Resource with a `Cell<jsg::Rc<T>>` field.\n#[jsg_resource]\nstruct CellParent {\n    pub child: Cell<jsg::Rc<CellChild>>,\n}\n\nimpl Drop for CellParent {\n    fn drop(&mut self) {\n        CELL_PARENT_DROPS.fetch_add(1, Ordering::SeqCst);\n    }\n}\n\n#[jsg_resource]\nimpl CellParent {}\n\n/// Resource with a `Cell<Option<jsg::Rc<T>>>` field.\n#[jsg_resource]\nstruct CellOptionParent {\n    pub child: Cell<Option<jsg::Rc<CellChild>>>,\n}\n\nimpl Drop for CellOptionParent {\n    fn drop(&mut self) {\n        CELL_PARENT_DROPS.fetch_add(1, Ordering::SeqCst);\n    }\n}\n\n#[jsg_resource]\nimpl CellOptionParent {}\n\n/// Resource with a `Cell<jsg::Nullable<jsg::Rc<T>>>` field.\n#[jsg_resource]\nstruct CellNullableParent {\n    pub child: Cell<jsg::Nullable<jsg::Rc<CellChild>>>,\n}\n\nimpl Drop for CellNullableParent {\n    fn drop(&mut self) {\n        CELL_PARENT_DROPS.fetch_add(1, Ordering::SeqCst);\n    }\n}\n\n#[jsg_resource]\nimpl CellNullableParent {}\n\n/// Resource using the fully-qualified `std::cell::Cell<jsg::Rc<T>>` syntax.\n#[jsg_resource]\nstruct StdCellParent {\n    pub child: std::cell::Cell<jsg::Rc<CellChild>>,\n}\n\nimpl Drop for StdCellParent {\n    fn drop(&mut self) {\n        CELL_PARENT_DROPS.fetch_add(1, Ordering::SeqCst);\n    }\n}\n\n#[jsg_resource]\nimpl StdCellParent {}\n\n/// `Cell<jsg::Rc<T>>` keeps the child alive through GC.\n#[test]\nfn cell_ref_keeps_child_alive_through_gc() {\n    CELL_CHILD_DROPS.store(0, Ordering::SeqCst);\n    CELL_PARENT_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let child = jsg::Rc::new(CellChild);\n        let parent = jsg::Rc::new(CellParent {\n            child: Cell::new(child.clone()),\n        });\n        let wrapped = parent.clone().to_js(lock);\n        ctx.set_global(\"parent\", wrapped);\n\n        // Drop all Rust refs.\n        std::mem::drop(child);\n        std::mem::drop(parent);\n\n        // Parent is JS-global-held; child is kept alive via Cell<Rc<T>> tracing.\n        crate::Harness::request_gc(lock);\n        assert_eq!(CELL_PARENT_DROPS.load(Ordering::SeqCst), 0);\n        assert_eq!(CELL_CHILD_DROPS.load(Ordering::SeqCst), 0);\n        Ok(())\n    });\n\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        assert_eq!(CELL_PARENT_DROPS.load(Ordering::SeqCst), 1);\n        assert_eq!(CELL_CHILD_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n/// `Cell<Option<jsg::Rc<T>>>` with `Some` keeps the child alive through GC.\n#[test]\nfn cell_option_ref_some_keeps_child_alive_through_gc() {\n    CELL_CHILD_DROPS.store(0, Ordering::SeqCst);\n    CELL_PARENT_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let child = jsg::Rc::new(CellChild);\n        let parent = jsg::Rc::new(CellOptionParent {\n            child: Cell::new(Some(child.clone())),\n        });\n        let wrapped = parent.clone().to_js(lock);\n        ctx.set_global(\"parent\", wrapped);\n\n        std::mem::drop(child);\n        std::mem::drop(parent);\n\n        crate::Harness::request_gc(lock);\n        assert_eq!(CELL_PARENT_DROPS.load(Ordering::SeqCst), 0);\n        assert_eq!(CELL_CHILD_DROPS.load(Ordering::SeqCst), 0);\n        Ok(())\n    });\n\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        assert_eq!(CELL_PARENT_DROPS.load(Ordering::SeqCst), 1);\n        assert_eq!(CELL_CHILD_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n/// `Cell<Option<jsg::Rc<T>>>` with `None` does not crash during GC.\n#[test]\nfn cell_option_ref_none_does_not_crash_during_gc() {\n    CELL_PARENT_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let parent = jsg::Rc::new(CellOptionParent {\n            child: Cell::new(None),\n        });\n        let wrapped = parent.clone().to_js(lock);\n        ctx.set_global(\"parent\", wrapped);\n\n        std::mem::drop(parent);\n\n        crate::Harness::request_gc(lock);\n        assert_eq!(CELL_PARENT_DROPS.load(Ordering::SeqCst), 0);\n        Ok(())\n    });\n\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        assert_eq!(CELL_PARENT_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n/// `Cell<jsg::Nullable<jsg::Rc<T>>>` with `Nullable::Some` keeps child alive.\n#[test]\nfn cell_nullable_ref_some_keeps_child_alive_through_gc() {\n    CELL_CHILD_DROPS.store(0, Ordering::SeqCst);\n    CELL_PARENT_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let child = jsg::Rc::new(CellChild);\n        let parent = jsg::Rc::new(CellNullableParent {\n            child: Cell::new(jsg::Nullable::Some(child.clone())),\n        });\n        let wrapped = parent.clone().to_js(lock);\n        ctx.set_global(\"parent\", wrapped);\n\n        std::mem::drop(child);\n        std::mem::drop(parent);\n\n        crate::Harness::request_gc(lock);\n        assert_eq!(CELL_PARENT_DROPS.load(Ordering::SeqCst), 0);\n        assert_eq!(CELL_CHILD_DROPS.load(Ordering::SeqCst), 0);\n        Ok(())\n    });\n\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        assert_eq!(CELL_PARENT_DROPS.load(Ordering::SeqCst), 1);\n        assert_eq!(CELL_CHILD_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n/// `Cell<jsg::Nullable<jsg::Rc<T>>>` with `Nullable::Null` does not crash during GC.\n#[test]\nfn cell_nullable_ref_null_does_not_crash_during_gc() {\n    CELL_PARENT_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let parent = jsg::Rc::new(CellNullableParent {\n            child: Cell::new(jsg::Nullable::Null),\n        });\n        let wrapped = parent.clone().to_js(lock);\n        ctx.set_global(\"parent\", wrapped);\n\n        std::mem::drop(parent);\n\n        crate::Harness::request_gc(lock);\n        assert_eq!(CELL_PARENT_DROPS.load(Ordering::SeqCst), 0);\n        Ok(())\n    });\n\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        assert_eq!(CELL_PARENT_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n\n/// `std::cell::Cell<jsg::Rc<T>>` (fully-qualified path) keeps child alive through GC.\n#[test]\nfn std_cell_ref_keeps_child_alive_through_gc() {\n    CELL_CHILD_DROPS.store(0, Ordering::SeqCst);\n    CELL_PARENT_DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let child = jsg::Rc::new(CellChild);\n        let parent = jsg::Rc::new(StdCellParent {\n            child: std::cell::Cell::new(child.clone()),\n        });\n        let wrapped = parent.clone().to_js(lock);\n        ctx.set_global(\"parent\", wrapped);\n\n        std::mem::drop(child);\n        std::mem::drop(parent);\n\n        crate::Harness::request_gc(lock);\n        assert_eq!(CELL_PARENT_DROPS.load(Ordering::SeqCst), 0);\n        assert_eq!(CELL_CHILD_DROPS.load(Ordering::SeqCst), 0);\n        Ok(())\n    });\n\n    harness.run_in_context(|lock, _ctx| {\n        crate::Harness::request_gc(lock);\n        assert_eq!(CELL_PARENT_DROPS.load(Ordering::SeqCst), 1);\n        assert_eq!(CELL_CHILD_DROPS.load(Ordering::SeqCst), 1);\n        Ok(())\n    });\n}\n"
  },
  {
    "path": "src/rust/jsg-test/tests/jsg_oneof.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nuse jsg::ExceptionType;\nuse jsg::Number;\nuse jsg::ToJS;\nuse jsg_macros::jsg_method;\nuse jsg_macros::jsg_oneof;\nuse jsg_macros::jsg_resource;\n\n#[jsg_oneof]\n#[derive(Debug, Clone, PartialEq)]\nenum StringOrNumber {\n    String(String),\n    Number(Number),\n}\n\n#[jsg_oneof]\n#[derive(Debug, Clone, PartialEq)]\nenum NumberOrString {\n    Number(Number),\n    String(String),\n}\n\n#[jsg_oneof]\n#[derive(Debug, Clone, PartialEq)]\nenum StringOrBool {\n    String(String),\n    Bool(bool),\n}\n\n#[jsg_oneof]\n#[derive(Debug, Clone, PartialEq)]\nenum ThreeTypes {\n    String(String),\n    Number(Number),\n    Bool(bool),\n}\n\n#[jsg_resource]\nstruct EnumTestResource;\n\n#[jsg_resource]\n#[expect(clippy::unnecessary_wraps)]\nimpl EnumTestResource {\n    #[jsg_method]\n    pub fn string_or_number(&self, value: StringOrNumber) -> Result<String, jsg::Error> {\n        match value {\n            StringOrNumber::String(s) => Ok(format!(\"string:{s}\")),\n            StringOrNumber::Number(n) => Ok(format!(\"number:{n}\")),\n        }\n    }\n\n    #[jsg_method]\n    pub fn string_or_number_ref(&self, value: &StringOrNumber) -> Result<String, jsg::Error> {\n        match value {\n            StringOrNumber::String(s) => Ok(format!(\"ref_string:{s}\")),\n            StringOrNumber::Number(n) => Ok(format!(\"ref_number:{n}\")),\n        }\n    }\n\n    #[jsg_method]\n    pub fn string_or_bool(&self, value: StringOrBool) -> Result<String, jsg::Error> {\n        match value {\n            StringOrBool::String(s) => Ok(format!(\"string:{s}\")),\n            StringOrBool::Bool(b) => Ok(format!(\"bool:{b}\")),\n        }\n    }\n\n    #[jsg_method]\n    pub fn three_types(&self, value: ThreeTypes) -> Result<String, jsg::Error> {\n        match value {\n            ThreeTypes::String(s) => Ok(format!(\"string:{s}\")),\n            ThreeTypes::Number(n) => Ok(format!(\"number:{n}\")),\n            ThreeTypes::Bool(b) => Ok(format!(\"bool:{b}\")),\n        }\n    }\n\n    #[jsg_method]\n    pub fn number_or_string(&self, value: NumberOrString) -> Result<String, jsg::Error> {\n        match value {\n            NumberOrString::Number(n) => Ok(format!(\"number:{n}\")),\n            NumberOrString::String(s) => Ok(format!(\"string:{s}\")),\n        }\n    }\n}\n\n#[test]\nfn jsg_oneof_derives_debug_and_clone() {\n    let s = StringOrNumber::String(\"test\".to_owned());\n    let cloned = s.clone();\n    assert_eq!(s, cloned);\n\n    let debug_str = format!(\"{s:?}\");\n    assert!(debug_str.contains(\"String\"));\n    assert!(debug_str.contains(\"test\"));\n}\n\n#[test]\nfn jsg_oneof_string_or_number_accepts_string() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(EnumTestResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"resource\", wrapped);\n\n        let result: String = ctx.eval(lock, \"resource.stringOrNumber('hello')\").unwrap();\n        assert_eq!(result, \"string:hello\");\n        Ok(())\n    });\n}\n\n#[test]\nfn jsg_oneof_string_or_number_accepts_number() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(EnumTestResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"resource\", wrapped);\n\n        let result: String = ctx.eval(lock, \"resource.stringOrNumber(42)\").unwrap();\n        assert_eq!(result, \"number:42\");\n        Ok(())\n    });\n}\n\n#[test]\nfn jsg_oneof_string_or_number_rejects_boolean() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(EnumTestResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"resource\", wrapped);\n\n        let err = ctx\n            .eval::<String>(lock, \"resource.stringOrNumber(true)\")\n            .unwrap_err()\n            .unwrap_jsg_err(lock);\n        assert_eq!(err.name, ExceptionType::TypeError);\n        assert!(err.message.contains(\"string\"));\n        assert!(err.message.contains(\"number\"));\n        Ok(())\n    });\n}\n\n#[test]\nfn jsg_oneof_string_or_bool_accepts_both() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(EnumTestResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"resource\", wrapped);\n\n        let result: String = ctx.eval(lock, \"resource.stringOrBool('test')\").unwrap();\n        assert_eq!(result, \"string:test\");\n\n        let result: String = ctx.eval(lock, \"resource.stringOrBool(true)\").unwrap();\n        assert_eq!(result, \"bool:true\");\n        Ok(())\n    });\n}\n\n#[test]\nfn jsg_oneof_three_types_accepts_all() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(EnumTestResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"resource\", wrapped);\n\n        let result: String = ctx.eval(lock, \"resource.threeTypes('hello')\").unwrap();\n        assert_eq!(result, \"string:hello\");\n\n        let result: String = ctx.eval(lock, \"resource.threeTypes(123.5)\").unwrap();\n        assert_eq!(result, \"number:123.5\");\n\n        let result: String = ctx.eval(lock, \"resource.threeTypes(false)\").unwrap();\n        assert_eq!(result, \"bool:false\");\n        Ok(())\n    });\n}\n\n#[test]\nfn jsg_oneof_three_types_rejects_null_and_undefined() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(EnumTestResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"resource\", wrapped);\n\n        let err = ctx\n            .eval::<String>(lock, \"resource.threeTypes(null)\")\n            .unwrap_err()\n            .unwrap_jsg_err(lock);\n        assert_eq!(err.name, ExceptionType::TypeError);\n\n        let err = ctx\n            .eval::<String>(lock, \"resource.threeTypes(undefined)\")\n            .unwrap_err()\n            .unwrap_jsg_err(lock);\n        assert_eq!(err.name, ExceptionType::TypeError);\n        Ok(())\n    });\n}\n\n#[test]\nfn jsg_oneof_variant_order_matches_declaration() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(EnumTestResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"resource\", wrapped);\n\n        // StringOrNumber has String first, Number second\n        let result: String = ctx.eval(lock, \"resource.stringOrNumber('42')\").unwrap();\n        assert_eq!(result, \"string:42\");\n\n        let result: String = ctx.eval(lock, \"resource.stringOrNumber(42)\").unwrap();\n        assert_eq!(result, \"number:42\");\n\n        // NumberOrString has Number first, String second\n        let result: String = ctx.eval(lock, \"resource.numberOrString(42)\").unwrap();\n        assert_eq!(result, \"number:42\");\n\n        let result: String = ctx.eval(lock, \"resource.numberOrString('42')\").unwrap();\n        assert_eq!(result, \"string:42\");\n        Ok(())\n    });\n}\n\n#[test]\nfn jsg_oneof_reference_parameter() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(EnumTestResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"resource\", wrapped);\n\n        let result: String = ctx\n            .eval(lock, \"resource.stringOrNumberRef('hello')\")\n            .unwrap();\n        assert_eq!(result, \"ref_string:hello\");\n\n        let result: String = ctx.eval(lock, \"resource.stringOrNumberRef(42)\").unwrap();\n        assert_eq!(result, \"ref_number:42\");\n        Ok(())\n    });\n}\n"
  },
  {
    "path": "src/rust/jsg-test/tests/jsg_struct.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nuse jsg::Error;\nuse jsg::ExceptionType;\nuse jsg::ToJS;\nuse jsg::v8;\nuse jsg::v8::ToLocalValue;\nuse jsg_macros::jsg_struct;\n\n#[jsg_struct]\nstruct TestStruct {\n    pub str: String,\n}\n\n#[jsg_struct]\nstruct MultiPropertyStruct {\n    pub name: String,\n    pub age: u32,\n    pub active: String,\n}\n\n#[jsg_struct]\nstruct NestedStruct {\n    pub inner: String,\n}\n\n#[test]\nfn objects_can_be_wrapped_and_unwrapped() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let instance = TestStruct {\n            str: \"test\".to_owned(),\n        };\n        let wrapped = instance.to_js(lock);\n        let mut obj: v8::Local<'_, v8::Object> = wrapped.into();\n        assert!(obj.has(lock, \"str\"));\n        let str_value = obj.get(lock, \"str\");\n        assert!(str_value.unwrap().is_string());\n        assert!(!obj.has(lock, \"test\"));\n        let value = \"value\".to_local(lock);\n        assert!(value.is_string());\n        obj.set(lock, \"test\", value);\n        assert!(obj.has(lock, \"test\"));\n        Ok(())\n    });\n}\n\n#[test]\nfn struct_with_multiple_properties() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let instance = MultiPropertyStruct {\n            name: \"Alice\".to_owned(),\n            age: 30,\n            active: \"true\".to_owned(),\n        };\n        let wrapped = instance.to_js(lock);\n        let obj: v8::Local<'_, v8::Object> = wrapped.into();\n\n        assert!(obj.has(lock, \"name\"));\n        assert!(obj.has(lock, \"age\"));\n        assert!(obj.has(lock, \"active\"));\n\n        let name_value = obj.get(lock, \"name\");\n        assert!(name_value.is_some());\n        assert!(name_value.unwrap().is_string());\n\n        let age_value = obj.get(lock, \"age\");\n        assert!(age_value.is_some());\n\n        let active_value = obj.get(lock, \"active\");\n        assert!(active_value.is_some());\n        assert!(active_value.unwrap().is_string());\n        Ok(())\n    });\n}\n\n#[test]\nfn number_type_conversions() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let byte_val: u8 = 42;\n        let byte_local = byte_val.to_local(lock);\n        assert!(byte_local.has_value());\n\n        let int_val: u32 = 12345;\n        let int_local = int_val.to_local(lock);\n        assert!(int_local.has_value());\n        Ok(())\n    });\n}\n\n#[test]\nfn empty_object_and_property_setting() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let mut obj = lock.new_object();\n\n        assert!(!obj.has(lock, \"nonexistent\"));\n        assert!(obj.get(lock, \"nonexistent\").is_none());\n\n        let str_value = \"hello\".to_local(lock);\n        obj.set(lock, \"key1\", str_value);\n        assert!(obj.has(lock, \"key1\"));\n\n        let num_value = 100u32.to_local(lock);\n        obj.set(lock, \"key2\", num_value);\n        assert!(obj.has(lock, \"key2\"));\n\n        let val1 = obj.get(lock, \"key1\");\n        assert!(val1.is_some());\n        assert!(val1.unwrap().is_string());\n\n        let val2 = obj.get(lock, \"key2\");\n        assert!(val2.is_some());\n        Ok(())\n    });\n}\n\n#[test]\nfn global_handle_conversion() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let local_str = \"global test\".to_local(lock);\n        assert!(local_str.has_value());\n\n        let global_str = local_str.to_global(lock);\n        let local_again = global_str.as_local(lock);\n        assert!(local_again.has_value());\n        Ok(())\n    });\n}\n\n#[test]\nfn nested_object_properties() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let mut outer = lock.new_object();\n\n        let inner_instance = NestedStruct {\n            inner: \"nested value\".to_owned(),\n        };\n        let inner_wrapped = inner_instance.to_js(lock);\n        outer.set(lock, \"nested\", inner_wrapped);\n\n        assert!(outer.has(lock, \"nested\"));\n        let nested_val = outer.get(lock, \"nested\");\n        assert!(nested_val.is_some());\n\n        let nested_obj: v8::Local<'_, v8::Object> = nested_val.unwrap().into();\n        assert!(nested_obj.has(lock, \"inner\"));\n\n        let inner_val = nested_obj.get(lock, \"inner\");\n        assert!(inner_val.is_some());\n        assert!(inner_val.unwrap().is_string());\n        Ok(())\n    });\n}\n\n#[test]\nfn error_creation_and_display() {\n    let type_error = Error::new_type_error(\"Invalid type\");\n    assert_eq!(type_error.name, ExceptionType::TypeError);\n    assert_eq!(type_error.message, \"Invalid type\");\n\n    // Test Display for all exception types\n    assert_eq!(format!(\"{}\", ExceptionType::RangeError), \"RangeError\");\n    assert_eq!(\n        format!(\"{}\", ExceptionType::ReferenceError),\n        \"ReferenceError\"\n    );\n    assert_eq!(format!(\"{}\", ExceptionType::SyntaxError), \"SyntaxError\");\n}\n\n#[test]\nfn error_from_parse_int_error() {\n    let parse_result: Result<i32, _> = \"not_a_number\".parse();\n    let error: Error = parse_result.unwrap_err().into();\n    assert_eq!(error.name, ExceptionType::RangeError);\n    assert!(error.message.contains(\"Failed to parse integer\"));\n}\n\n#[test]\nfn type_of_returns_correct_js_types() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let str_val = \"hello\".to_local(lock);\n        assert_eq!(str_val.type_of(), \"string\");\n        assert!(str_val.is_string());\n        assert!(!str_val.is_null_or_undefined());\n\n        let num_val = 42u32.to_local(lock);\n        assert_eq!(num_val.type_of(), \"number\");\n        assert!(num_val.is_number());\n        assert!(!num_val.is_null_or_undefined());\n\n        let bool_val = true.to_local(lock);\n        assert_eq!(bool_val.type_of(), \"boolean\");\n        assert!(bool_val.is_boolean());\n        assert!(!bool_val.is_null_or_undefined());\n\n        let obj_val = lock.new_object();\n        assert_eq!(obj_val.type_of(), \"object\");\n        assert!(!obj_val.is_null_or_undefined());\n        Ok(())\n    });\n}\n"
  },
  {
    "path": "src/rust/jsg-test/tests/local_cast.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n//! Tests for `impl_local_cast!` conversions added for Float/BigInt typed arrays,\n//! and upcasts to Object (Function -> Object, Array -> Object, `TypedArray` -> Object).\n\nuse jsg::ToJS;\nuse jsg::v8;\n\n// =============================================================================\n// Float/BigInt typed arrays -> Value\n// =============================================================================\n\n/// `Local<Float32Array>` → `Local<Value>` via `Into`, preserving the type tag.\n#[test]\nfn float32_array_into_value() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let data: Vec<f32> = vec![1.0, 2.0, 3.0];\n        let js_val = data.to_js(lock);\n        // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.\n        let typed: v8::Local<'_, v8::Float32Array> =\n            unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };\n        assert_eq!(typed.len(), 3);\n        let value: v8::Local<v8::Value> = typed.into();\n        assert!(value.is_float32_array());\n        assert!(value.is_object());\n        assert!(!value.is_float64_array());\n        Ok(())\n    });\n}\n\n/// `Local<Float64Array>` → `Local<Value>` via `Into`, preserving the type tag.\n#[test]\nfn float64_array_into_value() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let data: Vec<f64> = vec![1.0, 2.0, 3.0];\n        let js_val = data.to_js(lock);\n        // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.\n        let typed: v8::Local<'_, v8::Float64Array> =\n            unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };\n        assert_eq!(typed.len(), 3);\n        let value: v8::Local<v8::Value> = typed.into();\n        assert!(value.is_float64_array());\n        assert!(value.is_object());\n        assert!(!value.is_float32_array());\n        Ok(())\n    });\n}\n\n/// `Local<BigInt64Array>` → `Local<Value>` via `Into`, preserving the type tag.\n#[test]\nfn bigint64_array_into_value() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let data: Vec<i64> = vec![1, 2, 3];\n        let js_val = data.to_js(lock);\n        // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.\n        let typed: v8::Local<'_, v8::BigInt64Array> =\n            unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };\n        assert_eq!(typed.len(), 3);\n        let value: v8::Local<v8::Value> = typed.into();\n        assert!(value.is_bigint64_array());\n        assert!(value.is_object());\n        assert!(!value.is_biguint64_array());\n        Ok(())\n    });\n}\n\n/// `Local<BigUint64Array>` → `Local<Value>` via `Into`, preserving the type tag.\n#[test]\nfn biguint64_array_into_value() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let data: Vec<u64> = vec![1, 2, 3];\n        let js_val = data.to_js(lock);\n        // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.\n        let typed: v8::Local<'_, v8::BigUint64Array> =\n            unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };\n        assert_eq!(typed.len(), 3);\n        let value: v8::Local<v8::Value> = typed.into();\n        assert!(value.is_biguint64_array());\n        assert!(value.is_object());\n        assert!(!value.is_bigint64_array());\n        Ok(())\n    });\n}\n\n// =============================================================================\n// Float/BigInt typed arrays -> TypedArray\n// =============================================================================\n\n/// `Local<Float32Array>` → `Local<TypedArray>` preserves length.\n#[test]\nfn float32_array_into_typed_array() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let data: Vec<f32> = vec![1.5, 2.5];\n        let js_val = data.to_js(lock);\n        // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.\n        let typed: v8::Local<'_, v8::Float32Array> =\n            unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };\n        assert_eq!(typed.len(), 2);\n        let ta: v8::Local<v8::TypedArray> = typed.into();\n        assert_eq!(ta.len(), 2);\n        assert!(!ta.is_empty());\n        // Round-trip back and verify elements\n        let back: v8::Local<v8::Float32Array> = ta.into();\n        assert!((back.get(0) - 1.5).abs() < f32::EPSILON);\n        assert!((back.get(1) - 2.5).abs() < f32::EPSILON);\n        Ok(())\n    });\n}\n\n/// `Local<Float64Array>` → `Local<TypedArray>` preserves length.\n#[test]\nfn float64_array_into_typed_array() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let data: Vec<f64> = vec![1.5, 2.5];\n        let js_val = data.to_js(lock);\n        // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.\n        let typed: v8::Local<'_, v8::Float64Array> =\n            unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };\n        assert_eq!(typed.len(), 2);\n        let ta: v8::Local<v8::TypedArray> = typed.into();\n        assert_eq!(ta.len(), 2);\n        assert!(!ta.is_empty());\n        // Round-trip back and verify elements\n        let back: v8::Local<v8::Float64Array> = ta.into();\n        assert!((back.get(0) - 1.5).abs() < f64::EPSILON);\n        assert!((back.get(1) - 2.5).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n/// `Local<BigInt64Array>` → `Local<TypedArray>` preserves length.\n#[test]\nfn bigint64_array_into_typed_array() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let data: Vec<i64> = vec![10, -20];\n        let js_val = data.to_js(lock);\n        // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.\n        let typed: v8::Local<'_, v8::BigInt64Array> =\n            unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };\n        assert_eq!(typed.len(), 2);\n        let ta: v8::Local<v8::TypedArray> = typed.into();\n        assert_eq!(ta.len(), 2);\n        assert!(!ta.is_empty());\n        // Round-trip back and verify elements\n        let back: v8::Local<v8::BigInt64Array> = ta.into();\n        assert_eq!(back.get(0), 10);\n        assert_eq!(back.get(1), -20);\n        Ok(())\n    });\n}\n\n/// `Local<BigUint64Array>` → `Local<TypedArray>` preserves length.\n#[test]\nfn biguint64_array_into_typed_array() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let data: Vec<u64> = vec![10, 20];\n        let js_val = data.to_js(lock);\n        // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.\n        let typed: v8::Local<'_, v8::BigUint64Array> =\n            unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };\n        assert_eq!(typed.len(), 2);\n        let ta: v8::Local<v8::TypedArray> = typed.into();\n        assert_eq!(ta.len(), 2);\n        assert!(!ta.is_empty());\n        // Round-trip back and verify elements\n        let back: v8::Local<v8::BigUint64Array> = ta.into();\n        assert_eq!(back.get(0), 10);\n        assert_eq!(back.get(1), 20);\n        Ok(())\n    });\n}\n\n// =============================================================================\n// Upcasts to Object\n// =============================================================================\n\n/// `Local<Function>` → `Local<Object>` via `Into`; verify `has()` sees `name` property.\n#[test]\nfn function_into_object() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let func_val = ctx.eval_raw(\"(function myFunc() {})\").unwrap();\n        assert!(func_val.is_function());\n        let func = func_val.try_as::<v8::Function>().unwrap();\n        let obj: v8::Local<v8::Object> = func.into();\n        // Functions are objects — the `name` property should be accessible.\n        assert!(obj.has(lock, \"name\"));\n        let prop = obj.get(lock, \"name\").unwrap();\n        let name: String = <String as jsg::FromJS>::from_js(lock, prop)?;\n        assert_eq!(name, \"myFunc\");\n        Ok(())\n    });\n}\n\n/// `Local<Array>` → `Local<Object>` via `Into`; verify `length` property preserved.\n#[test]\nfn array_into_object() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let arr_val = ctx.eval_raw(\"[1, 2, 3]\").unwrap();\n        assert!(arr_val.is_array());\n        let arr = arr_val.try_as::<v8::Array>().unwrap();\n        assert_eq!(arr.len(), 3);\n        let obj: v8::Local<v8::Object> = arr.into();\n        assert!(obj.has(lock, \"length\"));\n        let prop = obj.get(lock, \"length\").unwrap();\n        let len: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, prop)?;\n        assert!((len.value() - 3.0).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n/// `Local<TypedArray>` → `Local<Object>` via `Into`; verify `byteLength` property.\n#[test]\nfn typed_array_into_object() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let data: Vec<u8> = vec![1, 2, 3];\n        let js_val = data.to_js(lock);\n        // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.\n        let typed: v8::Local<'_, v8::Uint8Array> =\n            unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };\n        assert_eq!(typed.len(), 3);\n        // Uint8Array -> TypedArray -> Object\n        let ta: v8::Local<v8::TypedArray> = typed.into();\n        assert_eq!(ta.len(), 3);\n        let obj: v8::Local<v8::Object> = ta.into();\n        assert!(obj.has(lock, \"byteLength\"));\n        let prop = obj.get(lock, \"byteLength\").unwrap();\n        let byte_len: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, prop)?;\n        assert!((byte_len.value() - 3.0).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n// =============================================================================\n// Property access on Object obtained from upcast\n// =============================================================================\n\n/// Upcast `Function` to `Object`, then read a custom property via `.get()`.\n/// Functions are objects in JS, so custom properties can be set on them.\n#[test]\nfn function_as_object_property_access() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let func_val = ctx\n            .eval_raw(\"var f = () => {}; f.custom = 'hello'; f\")\n            .unwrap();\n        let func = func_val.try_as::<v8::Function>().unwrap();\n        let obj: v8::Local<v8::Object> = func.into();\n\n        assert!(obj.has(lock, \"custom\"));\n        let prop = obj.get(lock, \"custom\").unwrap();\n        let val: String = <String as jsg::FromJS>::from_js(lock, prop)?;\n        assert_eq!(val, \"hello\");\n        Ok(())\n    });\n}\n\n/// Upcast `Array` to `Object`, then read `length` via `.get()`.\n#[test]\nfn array_as_object_property_access() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let arr_val = ctx.eval_raw(\"[10, 20, 30]\").unwrap();\n        let arr = arr_val.try_as::<v8::Array>().unwrap();\n        let obj: v8::Local<v8::Object> = arr.into();\n\n        assert!(obj.has(lock, \"length\"));\n        let prop = obj.get(lock, \"length\").unwrap();\n        let val: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, prop)?;\n        assert!((val.value() - 3.0).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n/// Upcast `TypedArray` (via `Uint8Array`) to `Object`, then read `byteLength`.\n#[test]\nfn typed_array_as_object_property_access() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let data: Vec<u8> = vec![1, 2, 3, 4, 5];\n        let js_val = data.to_js(lock);\n        // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.\n        let typed: v8::Local<'_, v8::Uint8Array> =\n            unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };\n        let ta: v8::Local<v8::TypedArray> = typed.into();\n        let obj: v8::Local<v8::Object> = ta.into();\n\n        assert!(obj.has(lock, \"byteLength\"));\n        assert!(obj.has(lock, \"length\"));\n        let prop = obj.get(lock, \"byteLength\").unwrap();\n        let byte_len: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, prop)?;\n        assert!((byte_len.value() - 5.0).abs() < f64::EPSILON);\n        let prop = obj.get(lock, \"length\").unwrap();\n        let len: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, prop)?;\n        assert!((len.value() - 5.0).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n// =============================================================================\n// Bidirectional casts (downcast from Value back to concrete type)\n// =============================================================================\n\n/// Round-trip: `Float32Array` → `Value` → `Float32Array`; verify elements preserved.\n#[test]\nfn float32_array_value_roundtrip() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let data: Vec<f32> = vec![1.5, 2.5, 3.5];\n        let js_val = data.to_js(lock);\n        // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.\n        let typed: v8::Local<'_, v8::Float32Array> =\n            unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };\n        assert_eq!(typed.len(), 3);\n        let value: v8::Local<v8::Value> = typed.into();\n        assert!(value.is_float32_array());\n        let back: v8::Local<v8::Float32Array> = value.into();\n        assert_eq!(back.len(), 3);\n        assert!((back.get(0) - 1.5).abs() < f32::EPSILON);\n        assert!((back.get(1) - 2.5).abs() < f32::EPSILON);\n        assert!((back.get(2) - 3.5).abs() < f32::EPSILON);\n        Ok(())\n    });\n}\n\n/// Round-trip: `Float64Array` → `Value` → `Float64Array`; verify elements preserved.\n#[test]\nfn float64_array_value_roundtrip() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let data: Vec<f64> = vec![1.1, 2.2, 3.3];\n        let js_val = data.to_js(lock);\n        // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.\n        let typed: v8::Local<'_, v8::Float64Array> =\n            unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };\n        assert_eq!(typed.len(), 3);\n        let value: v8::Local<v8::Value> = typed.into();\n        assert!(value.is_float64_array());\n        let back: v8::Local<v8::Float64Array> = value.into();\n        assert_eq!(back.len(), 3);\n        assert!((back.get(0) - 1.1).abs() < f64::EPSILON);\n        assert!((back.get(1) - 2.2).abs() < f64::EPSILON);\n        assert!((back.get(2) - 3.3).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n/// Round-trip: `BigInt64Array` → `Value` → `BigInt64Array`; verify elements preserved.\n#[test]\nfn bigint64_array_value_roundtrip() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let data: Vec<i64> = vec![-100, 0, 100];\n        let js_val = data.to_js(lock);\n        // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.\n        let typed: v8::Local<'_, v8::BigInt64Array> =\n            unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };\n        assert_eq!(typed.len(), 3);\n        let value: v8::Local<v8::Value> = typed.into();\n        assert!(value.is_bigint64_array());\n        let back: v8::Local<v8::BigInt64Array> = value.into();\n        assert_eq!(back.len(), 3);\n        assert_eq!(back.get(0), -100);\n        assert_eq!(back.get(1), 0);\n        assert_eq!(back.get(2), 100);\n        Ok(())\n    });\n}\n\n/// Round-trip: `BigUint64Array` → `Value` → `BigUint64Array`; verify elements preserved.\n#[test]\nfn biguint64_array_value_roundtrip() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let data: Vec<u64> = vec![0, 42, 999];\n        let js_val = data.to_js(lock);\n        // SAFETY: The isolate is locked and the FFI handle is from a valid eval result.\n        let typed: v8::Local<'_, v8::BigUint64Array> =\n            unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) };\n        assert_eq!(typed.len(), 3);\n        let value: v8::Local<v8::Value> = typed.into();\n        assert!(value.is_biguint64_array());\n        let back: v8::Local<v8::BigUint64Array> = value.into();\n        assert_eq!(back.len(), 3);\n        assert_eq!(back.get(0), 0);\n        assert_eq!(back.get(1), 42);\n        assert_eq!(back.get(2), 999);\n        Ok(())\n    });\n}\n\n/// Round-trip: `Function` → `Object` → `Function`; verify callable after roundtrip.\n#[test]\nfn function_object_roundtrip() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let func_val = ctx.eval_raw(\"(function namedFn() { return 42; })\").unwrap();\n        assert!(func_val.is_function());\n        let func = func_val.try_as::<v8::Function>().unwrap();\n        let obj: v8::Local<v8::Object> = func.into();\n        // Verify function-specific property is readable as Object\n        assert!(obj.has(lock, \"name\"));\n        let prop = obj.get(lock, \"name\").unwrap();\n        let name: String = <String as jsg::FromJS>::from_js(lock, prop)?;\n        assert_eq!(name, \"namedFn\");\n        // Roundtrip back and verify it's still callable\n        let back: v8::Local<v8::Function> = obj.into();\n        let result = back.call::<jsg::Number, v8::Local<v8::Value>>(lock, None, &[])?;\n        assert!((result.value() - 42.0).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n/// Round-trip: `Array` → `Object` → `Array`; verify length preserved.\n#[test]\nfn array_object_roundtrip() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let arr_val = ctx.eval_raw(\"[10, 20, 30]\").unwrap();\n        assert!(arr_val.is_array());\n        let arr = arr_val.try_as::<v8::Array>().unwrap();\n        assert_eq!(arr.len(), 3);\n        let obj: v8::Local<v8::Object> = arr.into();\n        // Verify length via Object property access\n        let prop = obj.get(lock, \"length\").unwrap();\n        let len: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, prop)?;\n        assert!((len.value() - 3.0).abs() < f64::EPSILON);\n        // Roundtrip back and verify Array-specific methods work\n        let back: v8::Local<v8::Array> = obj.into();\n        assert_eq!(back.len(), 3);\n        assert!(!back.is_empty());\n        Ok(())\n    });\n}\n"
  },
  {
    "path": "src/rust/jsg-test/tests/mod.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nmod arrays;\nmod eval;\nmod function;\nmod gc;\nmod jsg_oneof;\nmod jsg_struct;\nmod local_cast;\nmod non_coercible;\nmod resource_callback;\nmod resource_conversion;\nmod unwrap;\n"
  },
  {
    "path": "src/rust/jsg-test/tests/non_coercible.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nuse jsg::ExceptionType;\nuse jsg::NonCoercible;\nuse jsg::Number;\nuse jsg::ToJS;\nuse jsg_macros::jsg_method;\nuse jsg_macros::jsg_resource;\n\n#[jsg_resource]\nstruct MyResource;\n\n#[jsg_resource]\n#[expect(clippy::unnecessary_wraps)]\nimpl MyResource {\n    #[jsg_method]\n    pub fn string(&self, nc: NonCoercible<String>) -> Result<String, jsg::Error> {\n        Ok(nc.as_ref().clone())\n    }\n\n    #[jsg_method]\n    pub fn boolean(&self, nc: NonCoercible<bool>) -> Result<bool, jsg::Error> {\n        Ok(*nc.as_ref())\n    }\n\n    #[jsg_method]\n    pub fn number(&self, nc: NonCoercible<Number>) -> Result<Number, jsg::Error> {\n        Ok(*nc.as_ref())\n    }\n}\n\n#[test]\nfn non_coercible_string_new_and_as_ref() {\n    let nc: NonCoercible<String> = NonCoercible::new(\"hello\".to_owned());\n    assert_eq!(nc.as_ref(), \"hello\");\n}\n\n#[test]\nfn non_coercible_bool_new_and_as_ref() {\n    let nc_true: NonCoercible<bool> = NonCoercible::new(true);\n    let nc_false: NonCoercible<bool> = NonCoercible::new(false);\n    assert!(*nc_true.as_ref());\n    assert!(!*nc_false.as_ref());\n}\n\n#[test]\nfn non_coercible_number_new_and_as_ref() {\n    let nc: NonCoercible<Number> = NonCoercible::new(Number::new(2.5));\n    assert!((nc.as_ref().value() - 2.5).abs() < f64::EPSILON);\n}\n\n#[test]\nfn non_coercible_from_trait() {\n    let nc_string: NonCoercible<String> = \"test\".to_owned().into();\n    assert_eq!(nc_string.as_ref(), \"test\");\n\n    let nc_bool: NonCoercible<bool> = true.into();\n    assert!(*nc_bool.as_ref());\n\n    let nc_num: NonCoercible<Number> = Number::new(42.0).into();\n    assert!((nc_num.as_ref().value() - 42.0).abs() < f64::EPSILON);\n}\n\n#[test]\nfn non_coercible_as_ref_trait() {\n    let nc: NonCoercible<String> = NonCoercible::new(\"as_ref test\".to_owned());\n    let s: &String = nc.as_ref();\n    assert_eq!(s, \"as_ref test\");\n\n    let nc_bool: NonCoercible<bool> = NonCoercible::new(true);\n    let b: &bool = nc_bool.as_ref();\n    assert!(*b);\n}\n\n#[test]\nfn non_coercible_deref_trait() {\n    let nc: NonCoercible<String> = NonCoercible::new(\"deref test\".to_owned());\n    // Deref allows us to call String methods directly\n    assert_eq!(nc.len(), 10);\n    assert!(nc.starts_with(\"deref\"));\n\n    let nc_bool: NonCoercible<bool> = NonCoercible::new(true);\n    // Deref to bool\n    assert!(*nc_bool);\n\n    let nc_num: NonCoercible<Number> = NonCoercible::new(Number::new(2.5));\n    // Deref to Number, then get value\n    assert!(nc_num.value() > 2.0);\n}\n\n#[test]\nfn non_coercible_clone() {\n    let nc1: NonCoercible<String> = NonCoercible::new(\"clone test\".to_owned());\n    let nc2 = nc1.clone();\n    assert_eq!(nc1.as_ref(), nc2.as_ref());\n    assert_eq!(*nc1, *nc2);\n}\n\n#[test]\nfn non_coercible_partial_eq() {\n    let nc1: NonCoercible<String> = NonCoercible::new(\"equal\".to_owned());\n    let nc2: NonCoercible<String> = NonCoercible::new(\"equal\".to_owned());\n    let nc3: NonCoercible<String> = NonCoercible::new(\"different\".to_owned());\n\n    assert_eq!(nc1, nc2);\n    assert_ne!(nc1, nc3);\n\n    let nc_bool1: NonCoercible<bool> = NonCoercible::new(true);\n    let nc_bool2: NonCoercible<bool> = NonCoercible::new(true);\n    let nc_bool3: NonCoercible<bool> = NonCoercible::new(false);\n\n    assert_eq!(nc_bool1, nc_bool2);\n    assert_ne!(nc_bool1, nc_bool3);\n}\n\n#[test]\nfn non_coercible_debug() {\n    let nc: NonCoercible<String> = NonCoercible::new(\"debug test\".to_owned());\n    let debug_str = format!(\"{nc:?}\");\n    assert!(debug_str.contains(\"NonCoercible\"));\n    assert!(debug_str.contains(\"debug test\"));\n}\n\n#[test]\nfn non_coercible_methods_accept_correct_types_and_reject_incorrect_types() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(MyResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"resource\", wrapped);\n\n        // String method accepts string\n        let result: String = ctx.eval(lock, \"resource.string('hello')\").unwrap();\n        assert_eq!(result, \"hello\");\n\n        // String method rejects number\n        let err = ctx\n            .eval::<String>(lock, \"resource.string(123)\")\n            .unwrap_err()\n            .unwrap_jsg_err(lock);\n        assert_eq!(err.name, ExceptionType::TypeError);\n        assert!(err.message.contains(\"string\"));\n\n        // Boolean method accepts boolean\n        let result: bool = ctx.eval(lock, \"resource.boolean(true)\").unwrap();\n        assert!(result);\n\n        // Boolean method rejects string\n        let err = ctx\n            .eval::<bool>(lock, \"resource.boolean('true')\")\n            .unwrap_err()\n            .unwrap_jsg_err(lock);\n        assert_eq!(err.name, ExceptionType::TypeError);\n        assert!(err.message.contains(\"bool\"));\n\n        // Number method accepts number\n        let result: Number = ctx.eval(lock, \"resource.number(42.5)\").unwrap();\n        assert!((result.value() - 42.5).abs() < f64::EPSILON);\n\n        // Number method rejects string\n        let err = ctx\n            .eval::<Number>(lock, \"resource.number('42')\")\n            .unwrap_err()\n            .unwrap_jsg_err(lock);\n        assert_eq!(err.name, ExceptionType::TypeError);\n        assert!(err.message.contains(\"number\"));\n        Ok(())\n    });\n}\n"
  },
  {
    "path": "src/rust/jsg-test/tests/resource_callback.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n//! Tests for resource method callbacks.\n//!\n//! These tests ensure that resource methods can be called from JavaScript and that\n//! the resource pointer is correctly unwrapped. This specifically validates that\n//! V8's internal field embedder data type tags are used correctly when getting\n//! aligned pointers from internal fields.\n\nuse std::cell::Cell;\nuse std::rc::Rc;\n\nuse jsg::ExceptionType;\nuse jsg::Number;\nuse jsg::ToJS;\nuse jsg_macros::jsg_constructor;\nuse jsg_macros::jsg_method;\nuse jsg_macros::jsg_resource;\nuse jsg_macros::jsg_static_constant;\n\n#[jsg_resource]\nstruct EchoResource {\n    prefix: String,\n}\n\n#[jsg_resource]\n#[expect(clippy::unnecessary_wraps)]\nimpl EchoResource {\n    #[jsg_method]\n    pub fn echo(&self, message: String) -> Result<String, jsg::Error> {\n        Ok(format!(\"{}{}\", self.prefix, message))\n    }\n\n    #[jsg_method]\n    pub fn greet(&self, name: &str) -> String {\n        format!(\"{}{}!\", self.prefix, name)\n    }\n}\n\n#[jsg_resource]\nstruct DirectReturnResource {\n    name: String,\n    counter: Rc<Cell<u32>>,\n}\n\n#[jsg_resource]\nimpl DirectReturnResource {\n    #[jsg_method]\n    pub fn get_name(&self) -> String {\n        self.name.clone()\n    }\n\n    #[jsg_method]\n    pub fn is_valid(&self) -> bool {\n        !self.name.is_empty()\n    }\n\n    #[jsg_method]\n    pub fn get_counter(&self) -> jsg::Number {\n        jsg::Number::from(self.counter.get())\n    }\n\n    #[jsg_method]\n    pub fn increment_counter(&self) {\n        self.counter.set(self.counter.get() + 1);\n    }\n\n    #[jsg_method]\n    pub fn maybe_name(&self) -> Option<String> {\n        Some(self.name.clone()).filter(|s| !s.is_empty())\n    }\n}\n\n/// Validates that resource methods can be called from JavaScript.\n/// This test ensures the embedder data type tag is correctly used when\n/// unwrapping resource pointers from V8 internal fields.\n#[test]\nfn resource_method_callback_receives_correct_self() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(EchoResource {\n            prefix: \"Hello, \".to_owned(),\n        });\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"echoResource\", wrapped);\n\n        // Call the method from JavaScript\n        let result: String = ctx.eval(lock, \"echoResource.echo('World!')\").unwrap();\n        assert_eq!(result, \"Hello, World!\");\n        Ok(())\n    });\n}\n\n/// Validates that multiple method calls work correctly on the same resource.\n#[test]\nfn resource_method_can_be_called_multiple_times() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(EchoResource {\n            prefix: \">> \".to_owned(),\n        });\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"echo\", wrapped);\n\n        // First call\n        let result: String = ctx.eval(lock, \"echo.echo('first')\").unwrap();\n        assert_eq!(result, \">> first\");\n\n        // Second call\n        let result: String = ctx.eval(lock, \"echo.echo('second')\").unwrap();\n        assert_eq!(result, \">> second\");\n        Ok(())\n    });\n}\n\n/// Validates that methods can accept &str parameters.\n#[test]\nfn resource_method_accepts_str_ref_parameter() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(EchoResource {\n            prefix: \"Hello, \".to_owned(),\n        });\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"echo\", wrapped);\n\n        let result: String = ctx.eval(lock, \"echo.greet('World')\").unwrap();\n        assert_eq!(result, \"Hello, World!\");\n        Ok(())\n    });\n}\n\n/// Validates that methods can return values directly without Result wrapper.\n#[test]\nfn resource_method_returns_non_result_values() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let counter = Rc::new(Cell::new(42));\n        let resource = jsg::Rc::new(DirectReturnResource {\n            name: \"TestResource\".to_owned(),\n            counter: counter.clone(),\n        });\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"resource\", wrapped);\n\n        // Test getString returns string\n        let result: String = ctx.eval(lock, \"resource.getName()\").unwrap();\n        assert_eq!(result, \"TestResource\");\n\n        // Test isValid returns boolean\n        let result: bool = ctx.eval(lock, \"resource.isValid()\").unwrap();\n        assert!(result);\n\n        // Test getCounter returns number\n        let result: Number = ctx.eval(lock, \"resource.getCounter()\").unwrap();\n        assert!((result.value() - 42.0).abs() < f64::EPSILON);\n\n        // Test incrementCounter returns undefined (we just check it doesn't error)\n        let _: Option<bool> = ctx.eval(lock, \"resource.incrementCounter()\").unwrap();\n        assert_eq!(counter.get(), 43);\n\n        // Test maybeName returns string for Some\n        let result: String = ctx.eval(lock, \"resource.maybeName()\").unwrap();\n        assert_eq!(result, \"TestResource\");\n        Ok(())\n    });\n}\n\n#[jsg_resource]\nstruct MathResource;\n\n#[jsg_resource]\nimpl MathResource {\n    #[jsg_method]\n    pub fn add(a: Number, b: Number) -> Number {\n        Number::new(a.value() + b.value())\n    }\n\n    #[jsg_method]\n    pub fn greet(name: String) -> String {\n        format!(\"Hello, {name}!\")\n    }\n\n    #[jsg_method]\n    pub fn divide(a: Number, b: Number) -> Result<Number, jsg::Error> {\n        if b.value() == 0.0 {\n            return Err(jsg::Error::new_range_error(\"Division by zero\"));\n        }\n        Ok(Number::new(a.value() / b.value()))\n    }\n\n    #[jsg_method]\n    pub fn get_prefix(&self) -> String {\n        \"math\".to_owned()\n    }\n}\n\n/// Validates that methods without &self are registered as static methods on the class.\n#[test]\nfn static_method_callable_on_class() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let constructor = jsg::resource::function_template_of::<MathResource>(lock);\n        ctx.set_global(\"MathResource\", constructor.into());\n\n        let result: Number = ctx.eval(lock, \"MathResource.add(2, 3)\").unwrap();\n        assert!((result.value() - 5.0).abs() < f64::EPSILON);\n\n        let result: String = ctx.eval(lock, \"MathResource.greet('World')\").unwrap();\n        assert_eq!(result, \"Hello, World!\");\n        Ok(())\n    });\n}\n\n/// Validates that instance methods still work when static methods are present.\n#[test]\nfn instance_and_static_methods_coexist() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        // Expose the class constructor as a global\n        let constructor = jsg::resource::function_template_of::<MathResource>(lock);\n        ctx.set_global(\"MathResource\", constructor.into());\n\n        // Allocate and expose an instance as a global\n        let resource = jsg::Rc::new(MathResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"math\", wrapped);\n\n        // Instance method works on the object\n        let result: String = ctx.eval(lock, \"math.getPrefix()\").unwrap();\n        assert_eq!(result, \"math\");\n\n        // Static method works on the class\n        let result: Number = ctx.eval(lock, \"MathResource.add(10, 20)\").unwrap();\n        assert!((result.value() - 30.0).abs() < f64::EPSILON);\n\n        // Static methods are NOT on the instance\n        let is_undefined: bool = ctx.eval(lock, \"typeof math.add === 'undefined'\").unwrap();\n        assert!(is_undefined);\n        Ok(())\n    });\n}\n\n/// Validates that static methods with Result return type work on the success path.\n#[test]\nfn static_method_result_return_type() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let constructor = jsg::resource::function_template_of::<MathResource>(lock);\n        ctx.set_global(\"MathResource\", constructor.into());\n\n        let result: String = ctx.eval(lock, \"MathResource.greet('Rust')\").unwrap();\n        assert_eq!(result, \"Hello, Rust!\");\n        Ok(())\n    });\n}\n\n/// Validates that static methods propagate JS exceptions from `Result::Err`.\n#[test]\nfn static_method_throws_exception() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let constructor = jsg::resource::function_template_of::<MathResource>(lock);\n        ctx.set_global(\"MathResource\", constructor.into());\n\n        // Valid call succeeds\n        let result: Number = ctx.eval(lock, \"MathResource.divide(10, 2)\").unwrap();\n        assert!((result.value() - 5.0).abs() < f64::EPSILON);\n\n        // Division by zero throws a RangeError\n        let err = ctx\n            .eval::<Number>(lock, \"MathResource.divide(1, 0)\")\n            .unwrap_err()\n            .unwrap_jsg_err(lock);\n        assert_eq!(err.name, ExceptionType::RangeError);\n        assert!(err.message.contains(\"Division by zero\"));\n        Ok(())\n    });\n}\n\n/// Validates that Option<T> returns null for None.\n#[test]\nfn resource_method_returns_null_for_none() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(DirectReturnResource {\n            name: String::new(),\n            counter: Rc::new(Cell::new(0)),\n        });\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"resource\", wrapped);\n\n        let result: Option<String> = ctx.eval(lock, \"resource.maybeName()\").unwrap();\n        assert!(result.is_none());\n        Ok(())\n    });\n}\n\n// =============================================================================\n// Static constant tests\n// =============================================================================\n\n#[jsg_resource]\nstruct ConstantResource;\n\n#[jsg_resource]\nimpl ConstantResource {\n    #[jsg_static_constant]\n    pub const MAX_SIZE: u32 = 1024;\n\n    #[jsg_static_constant]\n    pub const STATUS_OK: i32 = 0;\n\n    #[jsg_static_constant]\n    pub const STATUS_ERROR: i32 = -1;\n\n    #[jsg_static_constant]\n    pub const SCALE_FACTOR: f64 = 2.5;\n\n    #[jsg_method]\n    pub fn get_name(&self) -> String {\n        \"constant_resource\".to_owned()\n    }\n}\n\n/// Validates that static constants are accessible on the constructor.\n#[test]\nfn static_constant_accessible_on_constructor() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let constructor = jsg::resource::function_template_of::<ConstantResource>(lock);\n        ctx.set_global(\"ConstantResource\", constructor.into());\n\n        let result: Number = ctx.eval(lock, \"ConstantResource.MAX_SIZE\").unwrap();\n        assert!((result.value() - 1024.0).abs() < f64::EPSILON);\n\n        let result: Number = ctx.eval(lock, \"ConstantResource.STATUS_OK\").unwrap();\n        assert!((result.value() - 0.0).abs() < f64::EPSILON);\n\n        let result: Number = ctx.eval(lock, \"ConstantResource.STATUS_ERROR\").unwrap();\n        assert!((result.value() - (-1.0)).abs() < f64::EPSILON);\n\n        let result: Number = ctx.eval(lock, \"ConstantResource.SCALE_FACTOR\").unwrap();\n        assert!((result.value() - 2.5).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n/// Validates that static constants are also accessible on instances (via prototype).\n#[test]\nfn static_constant_accessible_on_instance() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ConstantResource {});\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"obj\", wrapped);\n\n        let result: Number = ctx.eval(lock, \"obj.MAX_SIZE\").unwrap();\n        assert!((result.value() - 1024.0).abs() < f64::EPSILON);\n\n        let result: Number = ctx.eval(lock, \"obj.STATUS_OK\").unwrap();\n        assert!((result.value() - 0.0).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n/// Validates that static constants are read-only (not writable).\n#[test]\nfn static_constant_is_read_only() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let constructor = jsg::resource::function_template_of::<ConstantResource>(lock);\n        ctx.set_global(\"ConstantResource\", constructor.into());\n\n        // Attempt to overwrite should silently fail (strict mode would throw).\n        // The value should remain unchanged.\n        let result: Number = ctx\n            .eval(\n                lock,\n                \"ConstantResource.MAX_SIZE = 9999; ConstantResource.MAX_SIZE\",\n            )\n            .unwrap();\n        assert!((result.value() - 1024.0).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n/// Validates that static constants coexist with methods.\n#[test]\nfn static_constant_coexists_with_methods() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(ConstantResource {});\n\n        let constructor = jsg::resource::function_template_of::<ConstantResource>(lock);\n        ctx.set_global(\"ConstantResource\", constructor.into());\n\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"obj\", wrapped);\n\n        // Instance method works\n        let result: String = ctx.eval(lock, \"obj.getName()\").unwrap();\n        assert_eq!(result, \"constant_resource\");\n\n        // Static constant works on constructor\n        let result: Number = ctx.eval(lock, \"ConstantResource.MAX_SIZE\").unwrap();\n        assert!((result.value() - 1024.0).abs() < f64::EPSILON);\n\n        // Static constant works on instance\n        let result: Number = ctx.eval(lock, \"obj.MAX_SIZE\").unwrap();\n        assert!((result.value() - 1024.0).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n// =============================================================================\n// catch_panic tests\n// =============================================================================\n\n/// A resource whose `panic_now` method unconditionally panics.\n/// Used to verify that a panic inside a `#[jsg_method]` callback is caught and\n/// converted to a JavaScript exception rather than aborting the process.\n#[jsg_resource]\nstruct PanicResource;\n\n#[jsg_resource]\nimpl PanicResource {\n    #[jsg_method]\n    pub fn panic_now(&self) {\n        panic!(\"intentional panic in jsg_method\");\n    }\n}\n\n/// Verifies that a panic inside a `#[jsg_method]` callback is converted to a\n/// JavaScript internal error (matching the behavior of `KJ_ASSERT(false)` in\n/// C++ JSG handlers): the internal panic message is hidden from JS and the\n/// caller sees a generic `\"internal error; reference = <id>\"` message.\n#[test]\nfn method_panic_becomes_js_internal_error() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(PanicResource);\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"obj\", wrapped);\n\n        // The panic should surface as a JS Error, not abort the process.\n        let is_error: Result<bool, _> = ctx.eval(\n            lock,\n            \"try { obj.panicNow(); false } catch(e) { e instanceof Error }\",\n        );\n        assert!(is_error.unwrap(), \"panic should become a JS Error\");\n\n        // The JS error message must contain \"internal error\" — the internal\n        // panic message must not be exposed to JavaScript.\n        let msg: Result<String, _> =\n            ctx.eval(lock, \"try { obj.panicNow(); '' } catch(e) { e.message }\");\n        let msg = msg.unwrap();\n        assert!(\n            msg.contains(\"internal error\"),\n            \"JS error message should say \\\"internal error\\\", got: {msg:?}\"\n        );\n        assert!(\n            !msg.contains(\"intentional panic in jsg_method\"),\n            \"internal panic message must not be exposed to JS, got: {msg:?}\"\n        );\n        Ok(())\n    });\n}\n\n// =============================================================================\n// Receiver guard tests\n// =============================================================================\n\n/// Validates that destructuring an instance method and calling it without a\n/// receiver throws a `TypeError: Illegal invocation` rather than reaching Rust.\n///\n/// This tests the `v8::Signature` guard described in the SAFETY comment of the\n/// `#[jsg_method]` callback generated by `jsg-macros`.\n#[test]\nfn instance_method_throws_on_missing_receiver() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(EchoResource {\n            prefix: \"Hello, \".to_owned(),\n        });\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"obj\", wrapped);\n\n        // Destructure the method and call it with no receiver (`this` === global\n        // / undefined in strict mode). V8's Signature check must fire before the\n        // Rust callback is invoked.\n        let err = ctx\n            .eval::<String>(lock, \"const { echo } = obj; echo('world')\")\n            .unwrap_err()\n            .unwrap_jsg_err(lock);\n        assert_eq!(err.name, ExceptionType::TypeError);\n        assert!(\n            err.message.to_lowercase().contains(\"illegal invocation\")\n                || err.message.to_lowercase().contains(\"illegal\"),\n            \"unexpected error message: {}\",\n            err.message\n        );\n        Ok(())\n    });\n}\n\n/// Validates that `Reflect.apply` with a plain-object receiver also throws\n/// `TypeError: Illegal invocation`.\n#[test]\nfn instance_method_throws_on_wrong_receiver_via_reflect_apply() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(EchoResource {\n            prefix: \"Hello, \".to_owned(),\n        });\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"obj\", wrapped);\n\n        let err = ctx\n            .eval::<String>(lock, \"Reflect.apply(obj.echo, {}, ['world'])\")\n            .unwrap_err()\n            .unwrap_jsg_err(lock);\n        assert_eq!(err.name, ExceptionType::TypeError);\n        assert!(\n            err.message.to_lowercase().contains(\"illegal invocation\")\n                || err.message.to_lowercase().contains(\"illegal\"),\n            \"unexpected error message: {}\",\n            err.message\n        );\n        Ok(())\n    });\n}\n\n/// Validates that a method called on the correct receiver still works after\n/// the above receiver-guard checks, confirming normal dispatch is unaffected.\n#[test]\nfn instance_method_works_on_correct_receiver() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let resource = jsg::Rc::new(EchoResource {\n            prefix: \"Hi, \".to_owned(),\n        });\n        let wrapped = resource.to_js(lock);\n        ctx.set_global(\"obj\", wrapped);\n\n        // `Function.prototype.call` with the correct `this` must succeed.\n        let result: String = ctx.eval(lock, \"obj.echo.call(obj, 'world')\").unwrap();\n        assert_eq!(result, \"Hi, world\");\n        Ok(())\n    });\n}\n\n// =============================================================================\n// Constructor tests\n// =============================================================================\n\n#[jsg_resource]\nstruct Greeting {\n    message: String,\n}\n\n#[jsg_resource]\nimpl Greeting {\n    #[jsg_constructor]\n    fn constructor(message: String) -> Self {\n        Self { message }\n    }\n\n    #[jsg_method]\n    fn get_message(&self) -> String {\n        self.message.clone()\n    }\n}\n\n/// Resources without `#[jsg_constructor]` should throw when called with `new`.\n#[test]\nfn resource_without_constructor_throws() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let constructor = jsg::resource::function_template_of::<EchoResource>(lock);\n        ctx.set_global(\"EchoResource\", constructor.into());\n\n        let result: Result<Number, _> = ctx.eval(lock, \"new EchoResource('hi')\");\n        assert!(result.is_err(), \"should throw illegal constructor\");\n        Ok(())\n    });\n}\n\n/// A `#[jsg_constructor]` method is callable from JavaScript via `new`.\n#[test]\nfn constructor_creates_instance() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let constructor = jsg::resource::function_template_of::<Greeting>(lock);\n        ctx.set_global(\"Greeting\", constructor.into());\n\n        let result: String = ctx\n            .eval(lock, \"new Greeting('hello').getMessage()\")\n            .unwrap();\n        assert_eq!(result, \"hello\");\n        Ok(())\n    });\n}\n\n/// Constructor arguments are converted from JS types via `FromJS`.\n#[test]\nfn constructor_converts_arguments() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let constructor = jsg::resource::function_template_of::<Greeting>(lock);\n        ctx.set_global(\"Greeting\", constructor.into());\n\n        // Number is coerced to string by V8\n        let result: String = ctx\n            .eval(lock, \"new Greeting(String(42)).getMessage()\")\n            .unwrap();\n        assert_eq!(result, \"42\");\n        Ok(())\n    });\n}\n\n/// Multiple `new` calls create distinct JS objects.\n#[test]\nfn constructor_creates_distinct_objects() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let constructor = jsg::resource::function_template_of::<Greeting>(lock);\n        ctx.set_global(\"Greeting\", constructor.into());\n\n        let result: String = ctx\n            .eval(\n                lock,\n                \"let a = new Greeting('one'); let b = new Greeting('two'); \\\n                 a.getMessage() + ',' + b.getMessage()\",\n            )\n            .unwrap();\n        assert_eq!(result, \"one,two\");\n        Ok(())\n    });\n}\n\n/// `instanceof` works correctly for constructor-created instances.\n#[test]\nfn constructor_instanceof_works() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let constructor = jsg::resource::function_template_of::<Greeting>(lock);\n        ctx.set_global(\"Greeting\", constructor.into());\n\n        let result: String = ctx\n            .eval(\n                lock,\n                \"let g = new Greeting('test'); \\\n                 String(g instanceof Greeting)\",\n            )\n            .unwrap();\n        assert_eq!(result, \"true\");\n        Ok(())\n    });\n}\n\n// Constructor with Lock parameter\n\n#[jsg_resource]\nstruct Counter {\n    value: Number,\n}\n\n#[jsg_resource]\nimpl Counter {\n    #[jsg_constructor]\n    fn constructor(_lock: &mut jsg::Lock, value: Number) -> Self {\n        Self { value }\n    }\n\n    #[jsg_method]\n    fn get_value(&self) -> Number {\n        self.value\n    }\n}\n\n/// `#[jsg_constructor]` with a `Lock` parameter works.\n#[test]\nfn constructor_with_lock_parameter() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let constructor = jsg::resource::function_template_of::<Counter>(lock);\n        ctx.set_global(\"Counter\", constructor.into());\n\n        let result: Number = ctx.eval(lock, \"new Counter(99).getValue()\").unwrap();\n        assert!((result.value() - 99.0).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n"
  },
  {
    "path": "src/rust/jsg-test/tests/resource_conversion.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n//! Tests for resource `ToJS` and `FromJS` macro-generated implementations.\n//!\n//! These tests verify the automatically derived `ToJS` and `FromJS` implementations\n//! that the `#[jsg_resource]` macro generates for resource types:\n//!\n//! - `ToJS`: allocates a `Ref<R>` and wraps it as a JS object (alloc + wrap)\n//! - `FromJS`: unwraps a JS object back to a `Ref<R>`, returning a `TypeError`\n//!   for non-matching values\n\nuse jsg::FromJS;\nuse jsg::Number;\nuse jsg::ToJS;\nuse jsg_macros::jsg_method;\nuse jsg_macros::jsg_resource;\n\n#[jsg_resource]\nstruct Greeter {\n    greeting: String,\n}\n\n#[jsg_resource]\nimpl Greeter {\n    #[jsg_method]\n    pub fn greet(&self, name: &str) -> String {\n        format!(\"{}, {}!\", self.greeting, name)\n    }\n}\n\n#[jsg_resource]\nstruct Counter {\n    value: f64,\n}\n\n#[jsg_resource]\nimpl Counter {\n    #[jsg_method]\n    pub fn get_value(&self) -> Number {\n        Number::new(self.value)\n    }\n}\n\n// =============================================================================\n// ToJS tests\n// =============================================================================\n\n/// `ToJS` creates a JS object that has the resource's methods.\n#[test]\nfn to_js_creates_object_with_methods() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let greeter = Greeter {\n            greeting: \"Hello\".to_owned(),\n        };\n        let js_val = greeter.to_js(lock);\n        ctx.set_global(\"greeter\", js_val);\n\n        let result: String = ctx.eval(lock, \"greeter.greet('World')\").unwrap();\n        assert_eq!(result, \"Hello, World!\");\n        Ok(())\n    });\n}\n\n/// `ToJS` produces a valid object for different resource types.\n#[test]\nfn to_js_works_for_different_resource_types() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let counter = Counter { value: 42.0 };\n        let js_val = counter.to_js(lock);\n        ctx.set_global(\"counter\", js_val);\n\n        let result: Number = ctx.eval(lock, \"counter.getValue()\").unwrap();\n        assert!((result.value() - 42.0).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n/// `ToJS` called twice on different values creates distinct JS objects.\n#[test]\nfn to_js_creates_distinct_objects() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let g1 = Greeter {\n            greeting: \"Hi\".to_owned(),\n        };\n        let g2 = Greeter {\n            greeting: \"Hey\".to_owned(),\n        };\n        ctx.set_global(\"g1\", g1.to_js(lock));\n        ctx.set_global(\"g2\", g2.to_js(lock));\n\n        let same: bool = ctx.eval(lock, \"g1 === g2\").unwrap();\n        assert!(\n            !same,\n            \"distinct resources should produce distinct JS objects\"\n        );\n\n        let r1: String = ctx.eval(lock, \"g1.greet('A')\").unwrap();\n        let r2: String = ctx.eval(lock, \"g2.greet('A')\").unwrap();\n        assert_eq!(r1, \"Hi, A!\");\n        assert_eq!(r2, \"Hey, A!\");\n        Ok(())\n    });\n}\n\n// =============================================================================\n// FromJS tests\n// =============================================================================\n\n/// `FromJS` unwraps a JS-wrapped resource back to a `Ref<R>`.\n#[test]\nfn from_js_unwraps_resource() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let greeter = Greeter {\n            greeting: \"Howdy\".to_owned(),\n        };\n        ctx.set_global(\"greeter\", greeter.to_js(lock));\n\n        // Get the JS value back and use FromJS to unwrap it.\n        let js_val = ctx.eval_raw(\"greeter\").unwrap();\n        let r = Greeter::from_js(lock, js_val).expect(\"FromJS should succeed\");\n        assert_eq!(r.greeting, \"Howdy\");\n        Ok(())\n    });\n}\n\n/// `FromJS` returns a `Ref<R>` that keeps the resource alive.\n#[test]\nfn from_js_ref_keeps_resource_alive() {\n    use std::sync::atomic::AtomicUsize;\n    use std::sync::atomic::Ordering;\n\n    static DROPS: AtomicUsize = AtomicUsize::new(0);\n\n    #[jsg_resource]\n    struct Tracked {\n        tag: String,\n    }\n\n    impl Drop for Tracked {\n        fn drop(&mut self) {\n            DROPS.fetch_add(1, Ordering::SeqCst);\n        }\n    }\n\n    #[jsg_resource]\n    impl Tracked {}\n\n    DROPS.store(0, Ordering::SeqCst);\n\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let tracked = Tracked {\n            tag: \"alive\".to_owned(),\n        };\n        ctx.set_global(\"obj\", tracked.to_js(lock));\n\n        let js_val = ctx.eval_raw(\"obj\").unwrap();\n        let r = Tracked::from_js(lock, js_val).expect(\"FromJS should succeed\");\n        assert_eq!(r.tag, \"alive\");\n\n        // The Ref from FromJS keeps the resource alive even after GC.\n        crate::Harness::request_gc(lock);\n        assert_eq!(DROPS.load(Ordering::SeqCst), 0);\n        assert_eq!(r.tag, \"alive\");\n\n        std::mem::drop(r);\n        Ok(())\n    });\n}\n\n/// `FromJS` returns a `TypeError` for a plain JS object.\n#[test]\nfn from_js_rejects_plain_object() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let js_val = ctx.eval_raw(\"({})\").unwrap();\n        let err = Greeter::from_js(lock, js_val).unwrap_err();\n        assert_eq!(err.name, jsg::ExceptionType::TypeError);\n        assert_eq!(err.message, \"expected Greeter, got object\");\n        Ok(())\n    });\n}\n\n/// `FromJS` returns a `TypeError` for a string.\n#[test]\nfn from_js_rejects_string() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let js_val = ctx.eval_raw(\"'not a resource'\").unwrap();\n        let err = Greeter::from_js(lock, js_val).unwrap_err();\n        assert_eq!(err.name, jsg::ExceptionType::TypeError);\n        assert_eq!(err.message, \"expected Greeter, got string\");\n        Ok(())\n    });\n}\n\n/// `FromJS` returns a `TypeError` for a number.\n#[test]\nfn from_js_rejects_number() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let js_val = ctx.eval_raw(\"42\").unwrap();\n        let err = Greeter::from_js(lock, js_val).unwrap_err();\n        assert_eq!(err.name, jsg::ExceptionType::TypeError);\n        assert_eq!(err.message, \"expected Greeter, got number\");\n        Ok(())\n    });\n}\n\n/// `FromJS` returns a `TypeError` for null.\n#[test]\nfn from_js_rejects_null() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let js_val = ctx.eval_raw(\"null\").unwrap();\n        let err = Greeter::from_js(lock, js_val).unwrap_err();\n        assert_eq!(err.name, jsg::ExceptionType::TypeError);\n        // typeof null === \"object\" in JavaScript\n        assert_eq!(err.message, \"expected Greeter, got object\");\n        Ok(())\n    });\n}\n\n/// `FromJS` returns a `TypeError` for undefined.\n#[test]\nfn from_js_rejects_undefined() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let js_val = ctx.eval_raw(\"undefined\").unwrap();\n        let err = Greeter::from_js(lock, js_val).unwrap_err();\n        assert_eq!(err.name, jsg::ExceptionType::TypeError);\n        assert_eq!(err.message, \"expected Greeter, got undefined\");\n        Ok(())\n    });\n}\n\n// =============================================================================\n// Round-trip tests\n// =============================================================================\n\n/// `ToJS` followed by `FromJS` preserves the resource data.\n#[test]\nfn round_trip_preserves_data() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let original = Greeter {\n            greeting: \"Bonjour\".to_owned(),\n        };\n        ctx.set_global(\"obj\", original.to_js(lock));\n\n        let js_val = ctx.eval_raw(\"obj\").unwrap();\n        let unwrapped = Greeter::from_js(lock, js_val).expect(\"round-trip should succeed\");\n        assert_eq!(unwrapped.greeting, \"Bonjour\");\n        Ok(())\n    });\n}\n\n/// Round-trip through JavaScript — resource passes through JS code and comes back.\n#[test]\nfn round_trip_through_js_identity_function() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let greeter = Greeter {\n            greeting: \"Hola\".to_owned(),\n        };\n        ctx.set_global(\"greeter\", greeter.to_js(lock));\n\n        // Pass through a JS identity function.\n        let js_val = ctx.eval_raw(\"(x => x)(greeter)\").unwrap();\n        let unwrapped = Greeter::from_js(lock, js_val).expect(\"identity round-trip should work\");\n        assert_eq!(unwrapped.greeting, \"Hola\");\n        Ok(())\n    });\n}\n\n/// Round-trip preserves object identity — same wrapper object.\n#[test]\nfn round_trip_preserves_js_identity() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let greeter = Greeter {\n            greeting: \"Ciao\".to_owned(),\n        };\n        ctx.set_global(\"g\", greeter.to_js(lock));\n\n        let same: bool = ctx.eval(lock, \"g === (x => x)(g)\").unwrap();\n        assert!(\n            same,\n            \"passing a resource through JS should preserve identity\"\n        );\n        Ok(())\n    });\n}\n\n// =============================================================================\n// FromJS via eval_raw (integration with test harness)\n// =============================================================================\n\n/// Evaluating a global that holds a resource and calling `FromJS` returns a `Ref<R>`.\n#[test]\nfn eval_raw_returns_ref_via_from_js() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let greeter = Greeter {\n            greeting: \"Salut\".to_owned(),\n        };\n        ctx.set_global(\"g\", greeter.to_js(lock));\n\n        let js_val = ctx.eval_raw(\"g\").unwrap();\n        let r = Greeter::from_js(lock, js_val).expect(\"FromJS should succeed\");\n        assert_eq!(r.greeting, \"Salut\");\n        Ok(())\n    });\n}\n\n/// `FromJS` on a non-resource value returns a `TypeError`.\n#[test]\nfn eval_raw_with_wrong_type_gives_type_error() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let js_val = ctx.eval_raw(\"42\").unwrap();\n        let err = Greeter::from_js(lock, js_val).unwrap_err();\n        assert_eq!(err.name, jsg::ExceptionType::TypeError);\n        assert_eq!(err.message, \"expected Greeter, got number\");\n        Ok(())\n    });\n}\n\n/// Resources stored in a JS array can be retrieved individually via `FromJS`.\n#[test]\nfn resource_stored_in_js_array_and_retrieved() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let g1 = Greeter {\n            greeting: \"One\".to_owned(),\n        };\n        let g2 = Greeter {\n            greeting: \"Two\".to_owned(),\n        };\n        ctx.set_global(\"g1\", g1.to_js(lock));\n        ctx.set_global(\"g2\", g2.to_js(lock));\n\n        let js_val = ctx.eval_raw(\"([g1, g2])[0]\").unwrap();\n        let r = Greeter::from_js(lock, js_val).expect(\"FromJS should succeed for index 0\");\n        assert_eq!(r.greeting, \"One\");\n\n        let js_val = ctx.eval_raw(\"([g1, g2])[1]\").unwrap();\n        let r = Greeter::from_js(lock, js_val).expect(\"FromJS should succeed for index 1\");\n        assert_eq!(r.greeting, \"Two\");\n        Ok(())\n    });\n}\n"
  },
  {
    "path": "src/rust/jsg-test/tests/unwrap.rs",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nuse jsg::Number;\nuse jsg::v8::ToLocalValue;\n\n#[test]\nfn v8_is_string_returns_true_for_strings() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let str_val = \"hello\".to_local(lock);\n        assert!(str_val.is_string());\n        Ok(())\n    });\n}\n\n#[test]\nfn v8_is_boolean_returns_true_for_booleans() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let bool_true = true.to_local(lock);\n        assert!(bool_true.is_boolean());\n\n        let bool_false = false.to_local(lock);\n        assert!(bool_false.is_boolean());\n        Ok(())\n    });\n}\n\n#[test]\nfn v8_is_number_returns_true_for_numbers() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let float_val = Number::new(2.5).to_local(lock);\n        assert!(float_val.is_number());\n\n        let int_val = 42u32.to_local(lock);\n        assert!(int_val.is_number());\n\n        let num_u8 = 255u8.to_local(lock);\n        assert!(num_u8.is_number());\n        Ok(())\n    });\n}\n\n#[test]\nfn v8_type_checks_are_mutually_exclusive() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        // String is only string\n        let s = \"test\".to_local(lock);\n        assert!(s.is_string() && !s.is_boolean() && !s.is_number());\n\n        // Boolean is only boolean\n        let b = true.to_local(lock);\n        assert!(!b.is_string() && b.is_boolean() && !b.is_number());\n\n        // Number is only number\n        let n = Number::new(42.0).to_local(lock);\n        assert!(!n.is_string() && !n.is_boolean() && n.is_number());\n        Ok(())\n    });\n}\n\n#[test]\nfn v8_unwrap_boolean_returns_correct_values() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let bool_true = true.to_local(lock);\n        // SAFETY: The isolate is locked and the value is a valid V8 local handle.\n        let unwrapped_true =\n            unsafe { jsg::v8::ffi::unwrap_boolean(lock.isolate().as_ffi(), bool_true.into_ffi()) };\n        assert!(unwrapped_true);\n\n        let bool_false = false.to_local(lock);\n        // SAFETY: The isolate is locked and the value is a valid V8 local handle.\n        let unwrapped_false =\n            unsafe { jsg::v8::ffi::unwrap_boolean(lock.isolate().as_ffi(), bool_false.into_ffi()) };\n        assert!(!unwrapped_false);\n        Ok(())\n    });\n}\n\n#[test]\nfn v8_unwrap_number_returns_correct_values() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let num = Number::new(2.5).to_local(lock);\n        // SAFETY: The isolate is locked and the value is a valid V8 local handle.\n        let unwrapped =\n            unsafe { jsg::v8::ffi::unwrap_number(lock.isolate().as_ffi(), num.into_ffi()) };\n        assert!((unwrapped - 2.5).abs() < f64::EPSILON);\n\n        let zero = Number::new(0.0).to_local(lock);\n        // SAFETY: The isolate is locked and the value is a valid V8 local handle.\n        let unwrapped_zero =\n            unsafe { jsg::v8::ffi::unwrap_number(lock.isolate().as_ffi(), zero.into_ffi()) };\n        assert!(unwrapped_zero.abs() < f64::EPSILON);\n\n        let negative = Number::new(-42.5).to_local(lock);\n        // SAFETY: The isolate is locked and the value is a valid V8 local handle.\n        let unwrapped_neg =\n            unsafe { jsg::v8::ffi::unwrap_number(lock.isolate().as_ffi(), negative.into_ffi()) };\n        assert!((unwrapped_neg - (-42.5)).abs() < f64::EPSILON);\n        Ok(())\n    });\n}\n\n#[test]\nfn v8_unwrap_string_returns_correct_values() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let s = \"hello world\".to_local(lock);\n        // SAFETY: The isolate is locked and the value is a valid V8 local handle.\n        let unwrapped =\n            unsafe { jsg::v8::ffi::unwrap_string(lock.isolate().as_ffi(), s.into_ffi()) };\n        assert_eq!(unwrapped.as_str(), \"hello world\");\n\n        let empty = \"\".to_local(lock);\n        // SAFETY: The isolate is locked and the value is a valid V8 local handle.\n        let unwrapped_empty =\n            unsafe { jsg::v8::ffi::unwrap_string(lock.isolate().as_ffi(), empty.into_ffi()) };\n        assert_eq!(unwrapped_empty.as_str(), \"\");\n\n        let unicode = \"こんにちは\".to_local(lock);\n        // SAFETY: The isolate is locked and the value is a valid V8 local handle.\n        let unwrapped_unicode =\n            unsafe { jsg::v8::ffi::unwrap_string(lock.isolate().as_ffi(), unicode.into_ffi()) };\n        assert_eq!(unwrapped_unicode.as_str(), \"こんにちは\");\n        Ok(())\n    });\n}\n\n/// Tests that `unwrap_resource` rejects a C++ JSG object (tagged with `WORKERD_WRAPPABLE_TAG`).\n///\n/// Rust wrappables use `WORKERD_RUST_WRAPPABLE_TAG` (0xeb05), while C++ JSG objects use\n/// `WORKERD_WRAPPABLE_TAG` (0xeb04). Attempting to unwrap a C++ object through the Rust path\n/// must return nullptr to prevent reading garbage from non-existent `data[2]` fields.\n#[test]\nfn unwrap_resource_rejects_cpp_tagged_object() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, _ctx| {\n        let cpp_obj = crate::Harness::create_cpp_tagged_object(lock);\n\n        // unwrap_resource returns nullptr because the object has the C++ tag, not the Rust tag.\n        let result =\n            // SAFETY: isolate is valid and locked, value is a valid Local.\n            unsafe { jsg::v8::ffi::unwrap_resource(lock.isolate().as_ffi(), cpp_obj.into_ffi()) };\n        assert!(\n            result.get().is_null(),\n            \"unwrap_resource should return null for a C++ tagged object\"\n        );\n\n        Ok(())\n    });\n}\n\n/// Tests that `unwrap_resource` rejects a plain JS object with no internal fields.\n///\n/// A plain `{}` object has no wrappable tag at all. This verifies we don't crash on\n/// objects that were never wrapped by either the C++ or Rust JSG layer.\n#[test]\nfn unwrap_resource_rejects_plain_js_object() {\n    let harness = crate::Harness::new();\n    harness.run_in_context(|lock, ctx| {\n        let plain_obj = ctx.eval_raw(\"({})\").unwrap();\n\n        let result =\n            // SAFETY: isolate is valid and locked, value is a valid Local.\n            unsafe { jsg::v8::ffi::unwrap_resource(lock.isolate().as_ffi(), plain_obj.into_ffi()) };\n        assert!(\n            result.get().is_null(),\n            \"unwrap_resource should return null for a plain JS object\"\n        );\n\n        Ok(())\n    });\n}\n"
  },
  {
    "path": "src/rust/kj/BUILD.bazel",
    "content": "load(\"//:build/wd_cc_library.bzl\", \"wd_cc_library\")\nload(\"//:build/wd_rust_crate.bzl\", \"wd_rust_crate\")\n\nwd_rust_crate(\n    name = \"kj\",\n    cxx_bridge_deps = [\n        \"@capnp-cpp//src/kj/compat:kj-http\",\n    ],\n    cxx_bridge_srcs = [\n        \"http.rs\",\n        \"io.rs\",\n    ],\n    proc_macro_deps = [\n        \"@crates_vendor//:async-trait\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":ffi\",\n        \"@crates_vendor//:futures\",\n        \"@crates_vendor//:static_assertions\",\n    ],\n)\n\nwd_cc_library(\n    name = \"ffi\",\n    srcs = [\n        \"ffi.c++\",\n    ],\n    hdrs = [\n        \"ffi.h\",\n    ],\n    linkstatic = select({\n        \"@platforms//os:windows\": True,\n        \"//conditions:default\": False,\n    }),\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":http.rs@cxx\",\n        \"//src/rust/cxx-integration\",\n        \"@capnp-cpp//src/kj\",\n        \"@capnp-cpp//src/kj/compat:kj-http\",\n    ],\n)\n"
  },
  {
    "path": "src/rust/kj/ffi.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"ffi.h\"\n\n#include <workerd/rust/kj/http.rs.h>\n\n#include <kj/compat/http.h>\n\nstatic_assert(sizeof(kj::rust::HttpConnectSettings) == 16, \"HttpConnectSettings size mismatch\");\nstatic_assert(alignof(kj::rust::HttpConnectSettings) == alignof(uint64_t),\n    \"HttpConnectSettings alignment mismatch\");\n\nnamespace kj::rust {\nkj::Promise<void> connect(HttpService& service,\n    ::rust::Slice<const kj::byte> host,\n    const HttpHeaders& headers,\n    AsyncIoStream& connection,\n    ConnectResponse& response,\n    HttpConnectSettings settings) {\n  auto strHost = kj::str(kj::from<kj_rs::Rust>(host).asChars());\n  return service.connect(strHost, headers, connection, response,\n      {\n        .useTls = settings.use_tls,\n        .tlsStarter = settings.tls_starter,\n      });\n}\n\n}  // namespace kj::rust\n"
  },
  {
    "path": "src/rust/kj/ffi.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj-rs/convert.h>\n#include <rust/cxx.h>\n\n#include <kj/compat/http.h>\n\nnamespace kj::rust {\n\nstruct HttpConnectSettings;\n\n// --- Async IO\n\nusing AsyncInputStream = kj::AsyncInputStream;\nusing AsyncOutputStream = kj::AsyncOutputStream;\nusing AsyncIoStream = kj::AsyncIoStream;\n\n// --- kj::HttpHeaders ffi\n\nusing BuiltinIndicesEnum = kj::HttpHeaders::BuiltinIndicesEnum;\nusing HttpHeaders = kj::HttpHeaders;\nusing HttpHeaderId = kj::HttpHeaderId;\n\ninline kj::Own<kj::HttpHeaders> clone_shallow(const HttpHeaders& headers) {\n  // there is no c++ stack frame to hold the new instance,\n  // so sadly we have to heap allocate it.\n  return kj::heap(headers.cloneShallow());\n}\n\ninline kj::HttpHeaderId toHeaderId(BuiltinIndicesEnum id) {\n  switch (id) {\n    case kj::HttpHeaders::BuiltinIndicesEnum::CONNECTION:\n      return kj::HttpHeaderId::CONNECTION;\n    case kj::HttpHeaders::BuiltinIndicesEnum::KEEP_ALIVE:\n      return kj::HttpHeaderId::KEEP_ALIVE;\n    case kj::HttpHeaders::BuiltinIndicesEnum::TE:\n      return kj::HttpHeaderId::TE;\n    case kj::HttpHeaders::BuiltinIndicesEnum::TRAILER:\n      return kj::HttpHeaderId::TRAILER;\n    case kj::HttpHeaders::BuiltinIndicesEnum::UPGRADE:\n      return kj::HttpHeaderId::UPGRADE;\n    case kj::HttpHeaders::BuiltinIndicesEnum::CONTENT_LENGTH:\n      return kj::HttpHeaderId::CONTENT_LENGTH;\n    case kj::HttpHeaders::BuiltinIndicesEnum::TRANSFER_ENCODING:\n      return kj::HttpHeaderId::TRANSFER_ENCODING;\n    case kj::HttpHeaders::BuiltinIndicesEnum::SEC_WEBSOCKET_KEY:\n      return kj::HttpHeaderId::SEC_WEBSOCKET_KEY;\n    case kj::HttpHeaders::BuiltinIndicesEnum::SEC_WEBSOCKET_VERSION:\n      return kj::HttpHeaderId::SEC_WEBSOCKET_VERSION;\n    case kj::HttpHeaders::BuiltinIndicesEnum::SEC_WEBSOCKET_ACCEPT:\n      return kj::HttpHeaderId::SEC_WEBSOCKET_ACCEPT;\n    case kj::HttpHeaders::BuiltinIndicesEnum::SEC_WEBSOCKET_EXTENSIONS:\n      return kj::HttpHeaderId::SEC_WEBSOCKET_EXTENSIONS;\n    case kj::HttpHeaders::BuiltinIndicesEnum::HOST:\n      return kj::HttpHeaderId::HOST;\n    case kj::HttpHeaders::BuiltinIndicesEnum::DATE:\n      return kj::HttpHeaderId::DATE;\n    case kj::HttpHeaders::BuiltinIndicesEnum::LOCATION:\n      return kj::HttpHeaderId::LOCATION;\n    case kj::HttpHeaders::BuiltinIndicesEnum::CONTENT_TYPE:\n      return kj::HttpHeaderId::CONTENT_TYPE;\n    case kj::HttpHeaders::BuiltinIndicesEnum::RANGE:\n      return kj::HttpHeaderId::RANGE;\n    case kj::HttpHeaders::BuiltinIndicesEnum::CONTENT_RANGE:\n      return kj::HttpHeaderId::CONTENT_RANGE;\n      break;\n  }\n}\n\ninline void set_header(HttpHeaders& headers, BuiltinIndicesEnum id, ::rust::Str value) {\n  headers.set(toHeaderId(id), kj::str(value));\n}\n\ninline kj::Maybe<::rust::Slice<const kj::byte>> get_header(\n    const HttpHeaders& headers, BuiltinIndicesEnum id) {\n  auto header = headers.get(toHeaderId(id));\n  return header.map([](auto header) { return header.asBytes().template as<kj_rs::Rust>(); });\n}\n\ninline kj::Maybe<::rust::Slice<const kj::byte>> get_header_by_id(\n    const HttpHeaders& headers, const HttpHeaderId& id) {\n  auto header = headers.get(id);\n  return header.map([](auto header) { return header.asBytes().template as<kj_rs::Rust>(); });\n}\n\n// --- kj::HttpService ffi\nusing AsyncInputStream = kj::AsyncInputStream;\nusing AsyncIoStream = kj::AsyncIoStream;\nusing ConnectResponse = kj::HttpService::ConnectResponse;\nusing HttpMethod = kj::HttpMethod;\nusing HttpService = kj::HttpService;\nusing HttpServiceResponse = kj::HttpService::Response;\nusing TlsStarterCallback = kj::TlsStarterCallback;\n\ninline kj::Promise<void> request(HttpService& service,\n    HttpMethod method,\n    ::rust::Slice<const kj::byte> url,\n    const HttpHeaders& headers,\n    AsyncInputStream& request_body,\n    HttpServiceResponse& response) {\n  auto strUrl = kj::str(kj::from<kj_rs::Rust>(url).asChars());\n  co_await service.request(method, strUrl, headers, request_body, response);\n}\n\nkj::Promise<void> connect(HttpService& service,\n    ::rust::Slice<const kj::byte> host,\n    const HttpHeaders& headers,\n    AsyncIoStream& connection,\n    ConnectResponse& response,\n    HttpConnectSettings settings);\n\n}  // namespace kj::rust\n"
  },
  {
    "path": "src/rust/kj/http.rs",
    "content": "use std::marker::PhantomData;\nuse std::pin::Pin;\n\nuse futures::TryFutureExt;\nuse kj_rs::KjOwn;\nuse static_assertions::assert_eq_align;\nuse static_assertions::assert_eq_size;\n\nuse crate::OwnOrRef;\nuse crate::Result;\nuse crate::io::AsyncInputStream;\nuse crate::io::AsyncIoStream;\n\n#[cxx::bridge(namespace = \"kj::rust\")]\n#[expect(clippy::missing_panics_doc)]\n#[expect(clippy::missing_safety_doc)]\n#[expect(clippy::elidable_lifetime_names)]\npub mod ffi {\n    unsafe extern \"C++\" {\n        include!(\"workerd/rust/kj/ffi.h\");\n    }\n\n    /// Corresponds to `kj::HttpMethod`.\n    /// Values are automatically assigned by `cxx` because of extern declaration below.\n    #[derive(Debug, PartialEq, Eq, Copy, Clone)]\n    #[repr(u32)]\n    enum HttpMethod {\n        GET,\n        HEAD,\n        POST,\n        PUT,\n        DELETE,\n        PATCH,\n        PURGE,\n        OPTIONS,\n        TRACE,\n        COPY,\n        LOCK,\n        MKCOL,\n        MOVE,\n        PROPFIND,\n        PROPPATCH,\n        SEARCH,\n        UNLOCK,\n        ACL,\n        REPORT,\n        MKACTIVITY,\n        CHECKOUT,\n        MERGE,\n        MSEARCH,\n        NOTIFY,\n        SUBSCRIBE,\n        UNSUBSCRIBE,\n        QUERY,\n        BAN,\n    }\n    unsafe extern \"C++\" {\n        type HttpMethod;\n    }\n\n    // --- HttpHeaderId\n    // Opaque handle to a kj::HttpHeaderId, which identifies a header by numeric index in an\n    // HttpHeaderTable. This supports both builtin headers and custom headers registered via\n    // HttpHeaderTable::Builder::add(). Pass these by reference from C++ to Rust and back.\n\n    unsafe extern \"C++\" {\n        type HttpHeaderId;\n    }\n\n    // --- HttpHeaders\n    // TODO(when needed): support HttpHeaderId creation from rust.\n\n    /// Corresponds to `kj::HttpHeaders::BuiltinIndicesEnum`.\n    /// Values are automatically assigned by `cxx` because of extern declaration below.\n    #[derive(Debug, PartialEq, Eq, Copy, Clone)]\n    #[repr(u32)]\n    pub enum BuiltinIndicesEnum {\n        CONNECTION,\n        KEEP_ALIVE,\n        TE,\n        TRAILER,\n        UPGRADE,\n        CONTENT_LENGTH,\n        TRANSFER_ENCODING,\n        SEC_WEBSOCKET_KEY,\n        SEC_WEBSOCKET_VERSION,\n        SEC_WEBSOCKET_ACCEPT,\n        SEC_WEBSOCKET_EXTENSIONS,\n        HOST,\n        DATE,\n        LOCATION,\n        CONTENT_TYPE,\n        RANGE,\n        CONTENT_RANGE,\n    }\n\n    unsafe extern \"C++\" {\n        type BuiltinIndicesEnum;\n        type HttpHeaders;\n        fn clone_shallow(this_: &HttpHeaders) -> KjOwn<HttpHeaders>;\n        fn set_header(this_: Pin<&mut HttpHeaders>, id: BuiltinIndicesEnum, value: &str);\n        unsafe fn get_header<'a>(\n            this_: &'a HttpHeaders,\n            id: BuiltinIndicesEnum,\n        ) -> KjMaybe<&'a [u8]>;\n        unsafe fn get_header_by_id<'a>(\n            this_: &'a HttpHeaders,\n            id: &HttpHeaderId,\n        ) -> KjMaybe<&'a [u8]>;\n    }\n\n    // --- kj::HttpService ffi\n\n    unsafe extern \"C++\" {\n        type TlsStarterCallback;\n    }\n\n    /// Corresponds to `kj::HttpConnectSettings`.\n    struct HttpConnectSettings<'a> {\n        use_tls: bool,\n        tls_starter: KjMaybe<Pin<&'a mut TlsStarterCallback>>,\n    }\n\n    unsafe extern \"C++\" {\n        type AsyncInputStream = crate::io::AsyncInputStream;\n        type AsyncIoStream = crate::io::AsyncIoStream;\n        type ConnectResponse;\n        type HttpServiceResponse;\n        type HttpService;\n\n        /// Corresponds to `kj::HttpService::request`.\n        async fn request(\n            this_: Pin<&mut HttpService>,\n            method: HttpMethod,\n            url: &[u8],\n            headers: &HttpHeaders,\n            request_body: Pin<&mut AsyncInputStream>,\n            response: Pin<&mut HttpServiceResponse>,\n        ) -> Result<()>;\n\n        /// Corresponds to `kj::HttpService::connect`.\n        async fn connect(\n            this_: Pin<&mut HttpService>,\n            host: &[u8],\n            headers: &HttpHeaders,\n            connection: Pin<&mut AsyncIoStream>,\n            response: Pin<&mut ConnectResponse>,\n            settings: HttpConnectSettings<'_>,\n        ) -> Result<()>;\n    }\n\n    // DynHttpService\n    extern \"Rust\" {\n        type DynHttpService;\n\n        async unsafe fn request<'a>(\n            self: &'a mut DynHttpService,\n            method: HttpMethod,\n            url: &'a [u8],\n            headers: &'a HttpHeaders,\n            request_body: Pin<&'a mut AsyncInputStream>,\n            response: Pin<&'a mut HttpServiceResponse>,\n        ) -> Result<()>;\n\n        async unsafe fn connect<'a>(\n            self: &'a mut DynHttpService,\n            host: &'a [u8],\n            headers: &'a HttpHeaders,\n            connection: Pin<&'a mut AsyncIoStream>,\n            response: Pin<&'a mut ConnectResponse>,\n            settings: HttpConnectSettings<'a>,\n        ) -> Result<()>;\n    }\n\n    impl Box<DynHttpService> {}\n}\n\nassert_eq_size!(ffi::HttpConnectSettings, [u8; 16]);\nassert_eq_align!(ffi::HttpConnectSettings, u64);\n\npub type HeaderId = ffi::BuiltinIndicesEnum;\npub type CustomHttpHeader = ffi::HttpHeaderId;\n// TODO(tewaro) soon: replace by enum HeaderId\n\n/// Non-owning reference to a `kj::HttpHeaderId`.\n///\n/// `CustomHttpHeader` is an opaque CXX type representing `HttpHeaderId` and can only be passed by\n/// reference across the FFI boundary. This wrapper makes the borrow lifetime explicit and provides\n/// a safe Rust handle.\n///\n/// `repr(transparent)` guarantees the same layout as `&ffi::HttpHeaderId` (i.e. a single\n/// pointer), which allows safe reinterpretation of `&[*const HttpHeaderId]` slices received\n/// from C++ into `&[CustomHttpHeaderId]` via [`CustomHttpHeaderId::from_ptr_slice`].\n#[derive(Clone, Copy)]\n#[repr(transparent)]\npub struct CustomHttpHeaderId<'a>(&'a CustomHttpHeader);\n\nimpl<'a> From<&'a CustomHttpHeader> for CustomHttpHeaderId<'a> {\n    fn from(value: &'a CustomHttpHeader) -> Self {\n        CustomHttpHeaderId(value)\n    }\n}\n\nimpl<'a> CustomHttpHeaderId<'a> {\n    /// Reinterpret a slice of `*const CustomHttpHeader` pointers (as received from C++ via CXX) into\n    /// a slice of `HttpHeader`.\n    ///\n    /// This is the canonical way to receive a `kj::ArrayPtr<const kj::HttpHeaderId>` from C++:\n    /// the C++ side converts the array into a `rust::Slice<const HttpHeaderId* const>` and the\n    /// Rust side calls this function to get a safe `&[HttpHeaderIdRef]`.\n    ///\n    /// # Safety\n    /// `CustomHttpHeaderId` is `#[repr(transparent)]` over `&CustomHttpHeader`, which has\n    /// the same layout as `*const kj::HttpHeaderId`. The caller guarantees all pointers are valid.\n    pub unsafe fn from_ptr_slice(ptrs: &'a [*const CustomHttpHeader]) -> &'a [Self] {\n        let ptr = std::ptr::from_ref::<[*const CustomHttpHeader]>(ptrs) as *const [Self];\n        // SAFETY: CustomHttpHeaderId is #[repr(transparent)] over &CustomHttpHeader; caller guarantees all pointers are valid.\n        unsafe { &*ptr }\n    }\n}\n\n/// Non-owning constant reference to `kj::HttpHeaders`\npub struct HttpHeadersRef<'a>(&'a ffi::HttpHeaders);\n\nimpl HttpHeadersRef<'_> {\n    pub fn get(&self, id: HeaderId) -> Option<&[u8]> {\n        // SAFETY: self.0 is a valid HttpHeaders reference and id is a valid builtin header enum.\n        unsafe { ffi::get_header(self.0, id).into() }\n    }\n\n    /// Look up a header by its `kj::HttpHeaderId`. This works for both builtin headers and custom\n    /// headers registered via `HttpHeaderTable::Builder::add()`.\n    pub fn get_by_id(&self, id: CustomHttpHeaderId<'_>) -> Option<&[u8]> {\n        // SAFETY: self.0 is a valid HttpHeaders reference and id.0 is a valid HttpHeaderId.\n        unsafe { ffi::get_header_by_id(self.0, id.0).into() }\n    }\n\n    #[must_use]\n    pub fn clone_shallow(&self) -> HttpHeaders<'_> {\n        HttpHeaders {\n            own: ffi::clone_shallow(self.0),\n            _marker: PhantomData,\n        }\n    }\n}\n\nimpl<'a> From<&'a ffi::HttpHeaders> for HttpHeadersRef<'a> {\n    fn from(value: &'a ffi::HttpHeaders) -> Self {\n        HttpHeadersRef(value)\n    }\n}\n\n/// `HttpHeaders` that `kj::Own` the underlying C++ header object.\n///\n/// Notice, that despite the fact that headers are fully owned, because of a `shallowClone`\n/// method, data might not be owned: hence the lifetime parameter.\npub struct HttpHeaders<'a> {\n    own: KjOwn<ffi::HttpHeaders>,\n    _marker: PhantomData<&'a ffi::HttpHeaders>,\n}\n\nimpl<'a> HttpHeaders<'a> {\n    pub fn set(&mut self, id: HeaderId, value: &str) {\n        ffi::set_header(self.own.as_mut(), id, value);\n    }\n\n    pub fn as_ref(&'a self) -> HttpHeadersRef<'a> {\n        HttpHeadersRef(self.own.as_ref())\n    }\n}\n\npub type HttpMethod = ffi::HttpMethod;\npub type HttpServiceResponse = ffi::HttpServiceResponse;\npub type ConnectResponse = ffi::ConnectResponse;\npub type HttpConnectSettings<'a> = ffi::HttpConnectSettings<'a>;\n\n#[async_trait::async_trait(?Send)]\npub trait HttpService {\n    /// Make an HTTP request.\n    async fn request<'a>(\n        &'a mut self,\n        method: HttpMethod,\n        url: &'a [u8],\n        headers: HttpHeadersRef<'a>,\n        request_body: Pin<&'a mut AsyncInputStream>,\n        response: Pin<&'a mut HttpServiceResponse>,\n    ) -> Result<()>;\n\n    /// Make a CONNECT request\n    ///\n    /// WARNING: as c++ implementation does, this method has an outbound _immediate_\n    /// parameter inside `settings` (`tls_starter`).\n    ///\n    /// This method should be implement without using `async` method, since its body is called only\n    /// when it is first polled, but by manually creating a future using async block.\n    async fn connect<'a>(\n        &'a mut self,\n        host: &'a [u8],\n        headers: HttpHeadersRef<'a>,\n        connection: Pin<&'a mut AsyncIoStream>,\n        response: Pin<&'a mut ConnectResponse>,\n        settings: HttpConnectSettings<'a>,\n    ) -> Result<()>;\n\n    /// Convert `self` into the structure suitable for passing to C++ through FFI layer\n    /// To obtain `kj::HttpService` on C++ side finish wrapping with `fromRust()`\n    fn into_ffi(self) -> Box<DynHttpService>\n    where\n        Self: Sized + 'static,\n    {\n        Box::new(DynHttpService(Box::new(self)))\n    }\n}\n\npub struct CxxHttpService<'a>(OwnOrRef<'a, ffi::HttpService>);\n\n#[async_trait::async_trait(?Send)]\nimpl HttpService for CxxHttpService<'_> {\n    async fn request<'a>(\n        &'a mut self,\n        method: HttpMethod,\n        url: &'a [u8],\n        headers: HttpHeadersRef<'a>,\n        request_body: Pin<&'a mut AsyncInputStream>,\n        response: Pin<&'a mut HttpServiceResponse>,\n    ) -> Result<()> {\n        // SAFETY: self.0 is a valid owned-or-borrowed HttpService.\n        let service = unsafe { self.0.as_mut() };\n        ffi::request(service, method, url, headers.0, request_body, response).await?;\n        Ok(())\n    }\n\n    fn connect<'a, 'b>(\n        &'a mut self,\n        host: &'a [u8],\n        headers: HttpHeadersRef<'a>,\n        connection: Pin<&'a mut AsyncIoStream>,\n        response: Pin<&'a mut ConnectResponse>,\n        settings: HttpConnectSettings<'a>,\n    ) -> ::core::pin::Pin<Box<dyn ::core::future::Future<Output = Result<()>> + 'b>>\n    where\n        'a: 'b,\n        Self: 'b,\n    {\n        // SAFETY: self.0 is a valid owned-or-borrowed HttpService.\n        let service = unsafe { self.0.as_mut() };\n        Box::pin(\n            ffi::connect(service, host, headers.0, connection, response, settings)\n                .map_err(Into::into),\n        )\n    }\n}\n\nimpl From<KjOwn<ffi::HttpService>> for CxxHttpService<'_> {\n    fn from(value: KjOwn<ffi::HttpService>) -> Self {\n        CxxHttpService(value.into())\n    }\n}\n\npub struct DynHttpService(Box<dyn HttpService>);\n\nimpl DynHttpService {\n    async fn request<'a>(\n        &'a mut self,\n        method: HttpMethod,\n        url: &'a [u8],\n        headers: &'a ffi::HttpHeaders,\n        request_body: Pin<&'a mut AsyncInputStream>,\n        response: Pin<&'a mut HttpServiceResponse>,\n    ) -> Result<()> {\n        self.0\n            .request(method, url, HttpHeadersRef(headers), request_body, response)\n            .await?;\n        Ok(())\n    }\n\n    fn connect<'a>(\n        &'a mut self,\n        host: &'a [u8],\n        headers: &'a ffi::HttpHeaders,\n        connection: Pin<&'a mut AsyncIoStream>,\n        response: Pin<&'a mut ConnectResponse>,\n        settings: HttpConnectSettings<'a>,\n    ) -> impl Future<Output = Result<()>> {\n        self.0.connect(\n            host,\n            HttpHeadersRef(headers),\n            connection,\n            response,\n            settings,\n        )\n    }\n}\n"
  },
  {
    "path": "src/rust/kj/io.rs",
    "content": "#[cxx::bridge(namespace = \"kj::rust\")]\npub mod ffi {\n    unsafe extern \"C++\" {\n        include!(\"workerd/rust/kj/ffi.h\");\n\n        type AsyncInputStream;\n        type AsyncIoStream;\n        type AsyncOutputStream;\n    }\n}\n\npub type AsyncInputStream = ffi::AsyncInputStream;\npub type AsyncIoStream = ffi::AsyncIoStream;\npub type AsyncOutputStream = ffi::AsyncOutputStream;\n"
  },
  {
    "path": "src/rust/kj/lib.rs",
    "content": "//! KJ ffi crate.\n//!\n//! This crate provides bindings to common KJ classes and functions that do not require special\n//! C++ bridge support.\n\npub mod http;\npub mod io;\n\nmod own;\npub use own::*;\n\npub type Result<T> = std::result::Result<T, cxx::KjError>;\n"
  },
  {
    "path": "src/rust/kj/own.rs",
    "content": "use std::ops::Deref;\nuse std::pin::Pin;\n\nuse kj_rs::KjOwn;\n\n/// Wrapper for C++ objects.\n///\n/// C++ objects are either passed by references or are owned by rust using `kj::Own`.\n/// `OwnOrRef` gets uniform access to all cases.\n///\n/// Instances of this class are not expected to be exposed to the user directly, but as a building\n/// block for ffi wrappers.\n///\n/// Usage Guidelines:\n///\n/// To achieve maximum compatibility with Rust's type system, C++ objects should be represented\n/// on the Rust side as some `struct Wrapper(OwnOrRef<T>)` with inaccessible tuple value.\n///\n/// C++ can pass an object to Rust three ways:\n///\n/// - as `kj::Own<T>` - these should be represented as `Wrapper` objects passed by-value on Rust\n///   side: `kj::Own<Foo> createFoo()` on C++ should be translated to `createFoo() -> FooWrapper`\n///   on Rust.\n///\n/// - as `const T&` - these should be represented as non-mutable `&Wrapper` references on Rust\n///   side: `bar(const Foo& foo)` on C++ side should be translated to `bar(foo: &FooWrapper)` on\n///   Rust.\n///\n/// - as `T&` - these should be represented as mutable `&Wrapper` references on Rust side:\n///   `bar(T& foo)` on C++ side should be translated to `bar(foo: &mut FooWrapper)` on Rust.\n///\n/// When passing object from rust to C++ do similarly:\n///\n/// - if C++ side accepts `kj::Own<T>` - accept `Wrapper` objects by-value\n///\n/// - if C++ side accepts `const T&` - accept `&Wrapper` references\n///\n/// - if C++ side accepts `T&` - accept `&mut Wrapper` references\npub enum OwnOrRef<'a, T> {\n    Own(KjOwn<T>),\n    Ref(&'a T),\n    MutRef(Pin<&'a mut T>),\n}\n\nimpl<T> AsRef<T> for OwnOrRef<'_, T> {\n    fn as_ref(&self) -> &T {\n        match self {\n            OwnOrRef::Own(own) => own.as_ref(),\n            OwnOrRef::Ref(ref_) => ref_,\n            OwnOrRef::MutRef(ref_) => ref_,\n        }\n    }\n}\n\nimpl<T> OwnOrRef<'_, T> {\n    /// Obtain mut reference to the underlying object.\n    ///\n    /// # Safety\n    ///\n    /// - self should not be `Ref` variant.\n    ///\n    /// C++ mutable references are represented by `Pin<&mut T>`, otherwise we'd implement `AsMut`.\n    pub unsafe fn as_mut(&mut self) -> Pin<&mut T> {\n        match self {\n            OwnOrRef::Own(own) => own.as_mut(),\n            OwnOrRef::Ref(_) => unreachable!(\"mut reference to borrowed object\"),\n            OwnOrRef::MutRef(ref_) => ref_.as_mut(),\n        }\n    }\n}\n\nimpl<T> Deref for OwnOrRef<'_, T> {\n    type Target = T;\n\n    fn deref(&self) -> &Self::Target {\n        self.as_ref()\n    }\n}\n\nimpl<'a, T> From<&'a T> for OwnOrRef<'a, T> {\n    fn from(value: &'a T) -> Self {\n        Self::Ref(value)\n    }\n}\n\nimpl<T> From<KjOwn<T>> for OwnOrRef<'_, T> {\n    fn from(value: KjOwn<T>) -> Self {\n        Self::Own(value)\n    }\n}\n\nimpl<'a, T> From<Pin<&'a mut T>> for OwnOrRef<'a, T> {\n    fn from(value: Pin<&'a mut T>) -> Self {\n        Self::MutRef(value)\n    }\n}\n"
  },
  {
    "path": "src/rust/kj/tests/BUILD.bazel",
    "content": "load(\"//:build/kj_test.bzl\", \"kj_test\")\nload(\"//:build/wd_rust_crate.bzl\", \"wd_rust_crate\")\n\nwd_rust_crate(\n    name = \"tests\",\n    cxx_bridge_deps = [\n        \"//src/rust/kj:ffi\",\n    ],\n    cxx_bridge_src = \"lib.rs\",\n    proc_macro_deps = [\n        \"@crates_vendor//:async-trait\",\n    ],\n    deps = [\n        \"//src/rust/kj\",\n    ],\n)\n\nkj_test(\n    src = \"ffi-test.c++\",\n    deps = [\n        \":tests\",\n        \"//src/rust/kj\",\n    ],\n)\n"
  },
  {
    "path": "src/rust/kj/tests/ffi-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/rust/kj/http.rs.h>\n#include <workerd/rust/kj/tests/lib.rs.h>\n\n#include <kj-rs/kj-rs.h>\n\n#include <kj/test.h>\n\n#include <cmath>\n\nusing namespace kj_rs;\n\nstatic kj::StringPtr tlsHost;\n\nkj::Promise<void> startTls(kj::StringPtr hostName) {\n  tlsHost = hostName;\n  return kj::READY_NOW;\n}\n\nclass MockHttpService: public kj::HttpService {\n public:\n  virtual ~MockHttpService() {}\n  kj::Promise<void> request(kj::HttpMethod method,\n      kj::StringPtr url,\n      const kj::HttpHeaders& headers,\n      kj::AsyncInputStream& requestBody,\n      kj::HttpService::Response& response) override {\n    KJ_UNIMPLEMENTED(\"not exercised by test\");\n  }\n\n  kj::Promise<void> connect(kj::StringPtr host,\n      const kj::HttpHeaders& headers,\n      kj::AsyncIoStream& connection,\n      ConnectResponse& response,\n      kj::HttpConnectSettings settings) override {\n    if (settings.useTls) {\n      return kj::READY_NOW;\n    }\n    KJ_IF_SOME(tlsStarter, settings.tlsStarter) {\n      tlsStarter = startTls;\n    }\n    return kj::READY_NOW;\n  }\n};\n\nclass TestConnectResponse: public kj::HttpService::ConnectResponse {\n public:\n  void accept(uint statusCode, kj::StringPtr statusText, const kj::HttpHeaders& headers) override {\n    KJ_UNIMPLEMENTED(\"not exercised by test\");\n  }\n  kj::Own<kj::AsyncOutputStream> reject(uint statusCode,\n      kj::StringPtr statusText,\n      const kj::HttpHeaders& headers,\n      kj::Maybe<uint64_t> expectedBodySize = kj::none) override {\n    KJ_UNIMPLEMENTED(\"not exercised by test\");\n  }\n};\n\nKJ_TEST(\"get header by id round-trip through Rust\") {\n  // Register a custom header and a second one we'll leave unset.\n  kj::HttpHeaderTable::Builder builder;\n  auto customId = builder.add(\"X-Custom-Header\");\n  auto absentId = builder.add(\"X-Absent-Header\");\n  auto table = builder.build();\n\n  kj::HttpHeaders headers(*table);\n  headers.setPtr(customId, \"hello-from-cpp\");\n\n  // Also set a builtin header to test that path.\n  headers.setPtr(kj::HttpHeaderId::HOST, \"example.com\");\n\n  // Round-trip: C++ -> Rust (get_header_value_via_id) -> C++ (get_header_by_id shim) -> value.\n  {\n    auto maybe = kj::rust::tests::get_header_value_via_id(headers, customId);\n    KJ_IF_SOME(value, maybe) {\n      auto strValue = kj::StringPtr(reinterpret_cast<const char*>(value.data()), value.size());\n      KJ_EXPECT(strValue == \"hello-from-cpp\", strValue);\n    } else {\n      KJ_FAIL_EXPECT(\"expected Some for custom header, got None\");\n    }\n  }\n\n  // Absent header should return None.\n  {\n    auto maybe = kj::rust::tests::get_header_value_via_id(headers, absentId);\n    KJ_EXPECT(maybe == kj::none, \"expected None for absent header\");\n  }\n\n  // Builtin header via HttpHeaderId.\n  {\n    auto maybe = kj::rust::tests::get_header_value_via_id(headers, kj::HttpHeaderId::HOST);\n    KJ_IF_SOME(value, maybe) {\n      auto strValue = kj::StringPtr(reinterpret_cast<const char*>(value.data()), value.size());\n      KJ_EXPECT(strValue == \"example.com\", strValue);\n    } else {\n      KJ_FAIL_EXPECT(\"expected Some for HOST header, got None\");\n    }\n  }\n}\n\nKJ_TEST(\"assert header ids present via ArrayPtr round-trip through Rust\") {\n  kj::HttpHeaderTable::Builder builder;\n  auto custom1 = builder.add(\"X-First\");\n  auto custom2 = builder.add(\"X-Second\");\n  auto table = builder.build();\n\n  kj::HttpHeaders headers(*table);\n  headers.setPtr(custom1, \"value1\");\n  headers.setPtr(custom2, \"value2\");\n  headers.setPtr(kj::HttpHeaderId::HOST, \"example.com\");\n\n  // Build an array of pointers from our HttpHeaderIds, simulating what you'd get from\n  // kj::ArrayPtr<const kj::HttpHeaderId> — CXX can't pass opaque types in slices directly,\n  // so we pass pointers instead.\n  const kj::HttpHeaderId* const idPtrs[] = {&custom1, &custom2, &kj::HttpHeaderId::HOST};\n  rust::Slice<const kj::HttpHeaderId* const> idSlice(idPtrs, 3);\n\n  // The Rust side wraps each pointer in HttpHeaderIdRef and asserts all are present.\n  kj::rust::tests::assert_header_ids_present(headers, idSlice);\n}\n\nKJ_TEST(\"http connect settings\") {\n  kj::EventLoop loop;\n  kj::WaitScope waitScope(loop);\n\n  auto mock = kj::heap<MockHttpService>();\n  auto mockRef = *mock;\n\n  auto proxy = kj::rust::tests::new_proxy_http_service(kj::mv(mock));\n\n  kj::StringPtr host = \"example.com\";\n  kj::HttpHeaderTable headerTable;\n  kj::HttpHeaders headers(headerTable);\n\n  auto pipe = kj::newTwoWayPipe();\n  kj::AsyncIoStream& connection = *pipe.ends[0];\n\n  TestConnectResponse tunnel;\n\n  kj::Own<kj::TlsStarterCallback> tlsStarter = kj::heap<kj::TlsStarterCallback>();\n  ::kj::rust::HttpConnectSettings settings = {.use_tls = false, .tls_starter = kj::none};\n  settings.tls_starter = tlsStarter;\n\n  auto promise = proxy->connect(host.asBytes().as<Rust>(), headers, connection, tunnel, settings);\n  KJ_ASSERT_NONNULL (*tlsStarter)(host).wait(waitScope);\n  KJ_EXPECT(tlsHost == host);\n}\n"
  },
  {
    "path": "src/rust/kj/tests/lib.rs",
    "content": "use std::pin::Pin;\n\nuse kj::Result;\nuse kj::http::ConnectResponse;\nuse kj::http::CustomHttpHeaderId;\nuse kj::http::CxxHttpService;\nuse kj::http::DynHttpService;\nuse kj::http::HttpConnectSettings;\nuse kj::http::HttpHeadersRef;\nuse kj::http::HttpMethod;\nuse kj::http::HttpService;\nuse kj::http::HttpServiceResponse;\nuse kj::io::AsyncInputStream;\nuse kj::io::AsyncIoStream;\nuse kj_rs::KjMaybe;\nuse kj_rs::KjOwn;\n\n#[cxx::bridge(namespace = \"kj::rust::tests\")]\npub mod ffi {\n    #[namespace = \"kj::rust\"]\n    unsafe extern \"C++\" {\n        include!(\"workerd/rust/kj/ffi.h\");\n        type HttpService = kj::http::ffi::HttpService;\n        type HttpHeaders = kj::http::ffi::HttpHeaders;\n        type HttpHeaderId = kj::http::ffi::HttpHeaderId;\n    }\n\n    #[namespace = \"kj::rust\"]\n    extern \"Rust\" {\n        type DynHttpService = kj::http::DynHttpService;\n    }\n\n    extern \"Rust\" {\n        type ProxyHttpService;\n\n        #[expect(clippy::unnecessary_box_returns)]\n        fn new_proxy_http_service(service: KjOwn<HttpService>) -> Box<DynHttpService>;\n\n        /// Look up a header value by HttpHeaderId, returning the value if present.\n        /// This exercises the C++ -> Rust -> C++ round-trip for HttpHeaderId.\n        unsafe fn get_header_value_via_id<'a>(\n            headers: &'a HttpHeaders,\n            id: &HttpHeaderId,\n        ) -> KjMaybe<&'a [u8]>;\n\n        /// Receive an array of HttpHeaderIdpointers, convert to &[HttpHeaderIdRef] via\n        /// from_ptr_slice, look up each header, and assert all are present.\n        /// This exercises passing a kj::ArrayPtr<const kj::HttpHeaderId> into Rust.\n        unsafe fn assert_header_ids_present(headers: &HttpHeaders, ids: &[*const HttpHeaderId]);\n    }\n}\n\nstruct ProxyHttpService {\n    target: CxxHttpService<'static>,\n}\n\n#[async_trait::async_trait(?Send)]\nimpl HttpService for ProxyHttpService {\n    async fn request<'a>(\n        &'a mut self,\n        method: HttpMethod,\n        url: &'a [u8],\n        headers: HttpHeadersRef<'a>,\n        request_body: Pin<&'a mut AsyncInputStream>,\n        response: Pin<&'a mut HttpServiceResponse>,\n    ) -> Result<()> {\n        self.target\n            .request(method, url, headers, request_body, response)\n            .await?;\n        Ok(())\n    }\n\n    fn connect<'a, 'b>(\n        &'a mut self,\n        host: &'a [u8],\n        headers: HttpHeadersRef<'a>,\n        connection: Pin<&'a mut AsyncIoStream>,\n        response: Pin<&'a mut ConnectResponse>,\n        settings: HttpConnectSettings<'a>,\n    ) -> ::core::pin::Pin<Box<dyn ::core::future::Future<Output = Result<()>> + 'b>>\n    where\n        'a: 'b,\n        Self: 'b,\n    {\n        Box::pin(\n            self.target\n                .connect(host, headers, connection, response, settings),\n        )\n    }\n}\n\n#[expect(clippy::unnecessary_box_returns)]\nfn new_proxy_http_service(service: KjOwn<ffi::HttpService>) -> Box<DynHttpService> {\n    ProxyHttpService {\n        target: service.into(),\n    }\n    .into_ffi()\n}\n\nfn get_header_value_via_id<'a>(\n    headers: &'a ffi::HttpHeaders,\n    id: &ffi::HttpHeaderId,\n) -> KjMaybe<&'a [u8]> {\n    // SAFETY: headers is a valid HttpHeaders ref and id is a valid HttpHeaderId from C++.\n    unsafe { kj::http::ffi::get_header_by_id(headers, id) }\n}\n\n/// # Safety\n///\n/// Each pointer in `ids` must be non-null and point to a valid, live `HttpHeaderId`.\nunsafe fn assert_header_ids_present(headers: &ffi::HttpHeaders, ids: &[*const ffi::HttpHeaderId]) {\n    let headers_ref = HttpHeadersRef::from(headers);\n    // SAFETY: All pointers in ids are valid, as guaranteed by the unsafe fn contract.\n    let id_refs = unsafe { CustomHttpHeaderId::from_ptr_slice(ids) };\n    for (i, &id_ref) in id_refs.iter().enumerate() {\n        let value = headers_ref.get_by_id(id_ref);\n        assert!(\n            value.is_some(),\n            \"expected header at index {i} to be present, but got None\"\n        );\n    }\n}\n"
  },
  {
    "path": "src/rust/net/BUILD.bazel",
    "content": "load(\"//:build/wd_rust_crate.bzl\", \"wd_rust_crate\")\n\nwd_rust_crate(\n    name = \"net\",\n    cxx_bridge_src = \"lib.rs\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/rust/cxx-integration\",\n    ],\n)\n"
  },
  {
    "path": "src/rust/net/lib.rs",
    "content": "use std::net::IpAddr;\nuse std::str::FromStr;\n\n#[cxx::bridge(namespace = \"workerd::rust::net\")]\nmod ffi {\n    extern \"Rust\" {\n        fn canonicalize_ip(input: &str) -> String;\n    }\n}\n\n#[must_use]\npub fn canonicalize_ip(input: &str) -> String {\n    IpAddr::from_str(input)\n        .map(|ip| ip.to_string())\n        .unwrap_or(String::new())\n}\n"
  },
  {
    "path": "src/rust/python-parser/BUILD",
    "content": "load(\"//:build/kj_test.bzl\", \"kj_test\")\nload(\"//:build/wd_rust_crate.bzl\", \"wd_rust_crate\")\n\nwd_rust_crate(\n    name = \"python-parser\",\n    cxx_bridge_src = \"lib.rs\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@crates_vendor//:ruff_python_ast\",\n        \"@crates_vendor//:ruff_python_parser\",\n    ],\n)\n\nkj_test(\n    src = \"import_parsing.c++\",\n    deps = [\n        \":python-parser\",\n        \"//src/rust/cxx-integration\",\n    ],\n)\n"
  },
  {
    "path": "src/rust/python-parser/import_parsing.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/rust/python-parser/lib.rs.h>\n\n#include <kj-rs/kj-rs.h>\n\n#include <kj/test.h>\n\nusing namespace kj_rs;\n\nusing ::edgeworker::rust::python_parser::get_imports;\n\nkj::Array<kj::String> parseImports(kj::ArrayPtr<kj::StringPtr> cpp_modules) {\n  auto rust_modules = kj::heapArrayBuilder<::rust::Str const>(cpp_modules.size());\n  for (auto& entry: cpp_modules) {\n    rust_modules.add(entry.cStr());\n  }\n  ::rust::Slice<::rust::Str const> rust_slice(rust_modules.begin(), rust_modules.size());\n  auto rust_result = get_imports(rust_slice);\n  return kj::from<RustCopy>(rust_result);\n}\n\nnamespace workerd::api {\nnamespace {\n\nKJ_TEST(\"basic `import` tests\") {\n  auto result = parseImports(kj::arr(\"import a\\nimport z\"_kj, \"import b\"_kj));\n  KJ_REQUIRE(result.size() == 3);\n  KJ_REQUIRE(result[0] == \"a\");\n  KJ_REQUIRE(result[1] == \"b\");\n  KJ_REQUIRE(result[2] == \"z\");\n}\n\nKJ_TEST(\"supports whitespace\") {\n  auto result = parseImports(kj::arr(\"import      a\\nimport   \\\\\\n\\tz\"_kj));\n  KJ_REQUIRE(result.size() == 2);\n  KJ_REQUIRE(result[0] == \"a\");\n  KJ_REQUIRE(result[1] == \"z\");\n}\n\nKJ_TEST(\"supports windows newlines\") {\n  auto result = parseImports(kj::arr(\"import      a\\r\\nimport    \\\\\\r\\n\\tz\"_kj));\n  KJ_REQUIRE(result.size() == 2);\n  KJ_REQUIRE(result[0] == \"a\");\n  KJ_REQUIRE(result[1] == \"z\");\n}\n\nKJ_TEST(\"basic `from` test\") {\n  auto result = parseImports(kj::arr(\"from x import a,b\\nfrom z import y\"_kj));\n  KJ_REQUIRE(result.size() == 2);\n  KJ_REQUIRE(result[0] == \"x\");\n  KJ_REQUIRE(result[1] == \"z\");\n}\n\nKJ_TEST(\"ignores indented blocks\") {\n  auto result = parseImports(kj::arr(\"import a\\nif True:\\n  import x\\nimport y\"_kj));\n  KJ_REQUIRE(result.size() == 2);\n  KJ_REQUIRE(result[0] == \"a\");\n  KJ_REQUIRE(result[1] == \"y\");\n}\n\nKJ_TEST(\"supports nested imports\") {\n  auto result = parseImports(kj::arr(\"import a.b\\nimport z.x.y.i\"_kj));\n  KJ_REQUIRE(result.size() == 2);\n  KJ_REQUIRE(result[0] == \"a.b\");\n  KJ_REQUIRE(result[1] == \"z.x.y.i\");\n}\n\nKJ_TEST(\"nested `from` test\") {\n  auto result = parseImports(kj::arr(\"from x.y.z import a,b\\nfrom z import y\"_kj));\n  KJ_REQUIRE(result.size() == 2);\n  KJ_REQUIRE(result[0] == \"x.y.z\");\n  KJ_REQUIRE(result[1] == \"z\");\n}\n\nKJ_TEST(\"ignores trailing period\") {\n  auto result = parseImports(kj::arr(\"import a.b.\\nimport z.x.y.i.\"_kj));\n  KJ_REQUIRE(result.size() == 0);\n}\n\nKJ_TEST(\"ignores relative import\") {\n  // This is where we diverge from the old AST-based approach. It would have returned `y` in the\n  // input below.\n  auto result = parseImports(kj::arr(\"import .a.b\\nimport ..z.x\\nfrom .y import x\"_kj));\n  KJ_REQUIRE(result.size() == 0);\n}\n\nKJ_TEST(\"supports commas\") {\n  auto result = parseImports(kj::arr(\"import a,b\"_kj));\n  KJ_REQUIRE(result.size() == 2);\n  KJ_REQUIRE(result[0] == \"a\");\n  KJ_REQUIRE(result[1] == \"b\");\n}\n\nKJ_TEST(\"supports backslash\") {\n  // clang-format off\n  auto result = parseImports(kj::arr(\n    \"import a\\\\\\n,b\"_kj,\n    \"import\\\\\\n q,w\"_kj,\n    \"from \\\\\\nx import y\"_kj,\n    \"from \\\\\\n   c import y\"_kj\n  ));\n  // clang-format on\n  KJ_REQUIRE(result.size() == 6);\n  KJ_REQUIRE(result[0] == \"a\");\n  KJ_REQUIRE(result[1] == \"b\");\n  KJ_REQUIRE(result[2] == \"c\");\n  KJ_REQUIRE(result[3] == \"q\");\n  KJ_REQUIRE(result[4] == \"w\");\n  KJ_REQUIRE(result[5] == \"x\");\n}\n\nKJ_TEST(\"multiline-strings ignored\") {\n  // clang-format off\n  auto files = kj::arr(R\"SCRIPT(\nFOO=\"\"\"\nimport x\nfrom y import z\n\"\"\"\n)SCRIPT\"_kj,\nR\"SCRIPT(\nFOO='''\nimport f\nfrom g import z\n'''\n)SCRIPT\"_kj,\nR\"SCRIPT(FOO = \"\\\nimport b \\\n\")SCRIPT\"_kj,\n\"FOO=\\\"\\\"\\\"  \\n\"_kj,\nR\"SCRIPT(import x\nfrom y import z\n\"\"\")SCRIPT\"_kj);\n  // clang-format on\n  auto result = parseImports(files);\n  KJ_REQUIRE(result.size() == 0);\n}\n\nKJ_TEST(\"multiline-strings with imports in-between\") {\n  auto files = kj::arr(\n      R\"SCRIPT(FOO=\"\"\"\nimport x\nfrom y import z\n\"\"\"\nimport q\nimport w\nBAR=\"\"\"\nimport e\n\"\"\"\nfrom t import u)SCRIPT\"_kj);\n  auto result = parseImports(files);\n  KJ_REQUIRE(result.size() == 3);\n  KJ_REQUIRE(result[0] == \"q\");\n  KJ_REQUIRE(result[1] == \"t\");\n  KJ_REQUIRE(result[2] == \"w\");\n}\n\nKJ_TEST(\"import after string literal\") {\n  auto files = kj::arr(R\"SCRIPT(import a\n\"import b\")SCRIPT\"_kj);\n  auto result = parseImports(files);\n  KJ_REQUIRE(result.size() == 1);\n  KJ_REQUIRE(result[0] == \"a\");\n}\n\nKJ_TEST(\"langchain import\") {\n  auto files = kj::arr(R\"SCRIPT(from js import Response, console, URL\nfrom langchain.chat_models import ChatOpenAI\nimport openai)SCRIPT\"_kj);\n  auto result = parseImports(files);\n  KJ_REQUIRE(result.size() == 3);\n  KJ_REQUIRE(result[0] == \"js\");\n  KJ_REQUIRE(result[1] == \"langchain.chat_models\");\n  KJ_REQUIRE(result[2] == \"openai\");\n}\n\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/rust/python-parser/lib.rs",
    "content": "use std::collections::HashSet;\n\nuse ruff_python_ast::Stmt;\nuse ruff_python_ast::StmtImportFrom;\nuse ruff_python_parser::parse_module;\n\n#[cxx::bridge(namespace = \"edgeworker::rust::python_parser\")]\nmod ffi {\n\n    extern \"Rust\" {\n        fn get_imports(sources: &[&str]) -> Vec<String>;\n    }\n}\n\n#[must_use]\npub fn get_imports(sources: &[&str]) -> Vec<String> {\n    let mut names: HashSet<String> = HashSet::new();\n    for src in sources {\n        // Just skip it if it doesn't parse.\n        let Ok(module) = parse_module(src) else {\n            continue;\n        };\n        for stmt in &module.syntax().body {\n            match stmt {\n                Stmt::Import(s) => {\n                    names.extend(s.names.iter().map(|x| x.name.id.as_str().to_owned()));\n                }\n                Stmt::ImportFrom(StmtImportFrom {\n                    module: Some(module),\n                    level: 0,\n                    ..\n                }) => {\n                    names.insert(module.id.as_str().to_owned());\n                }\n                _ => {}\n            }\n        }\n    }\n    let mut result: Vec<_> = names.drain().collect();\n    result.sort();\n    result\n}\n"
  },
  {
    "path": "src/rust/rustfmt.toml",
    "content": "group_imports = \"StdExternalCrate\"\nimports_granularity = \"Item\"\n\n"
  },
  {
    "path": "src/rust/transpiler/BUILD",
    "content": "load(\"//:build/wd_rust_crate.bzl\", \"wd_rust_crate\")\n\nwd_rust_crate(\n    name = \"transpiler\",\n    cxx_bridge_src = \"lib.rs\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/rust/cxx-integration\",\n        \"@crates_vendor//:swc_common\",\n        \"@crates_vendor//:swc_ts_fast_strip\",\n        \"@crates_vendor//:thiserror\",\n    ],\n)\n"
  },
  {
    "path": "src/rust/transpiler/lib.rs",
    "content": "use std::cell::RefCell;\nuse std::rc::Rc;\n\nuse swc_common::SourceMap;\nuse swc_common::errors::DiagnosticBuilder;\nuse swc_common::errors::Emitter;\nuse swc_common::errors::HANDLER;\nuse swc_common::errors::Handler;\nuse swc_common::errors::Level;\n\nuse crate::ffi::Output;\n\n#[cxx::bridge(namespace = \"workerd::rust::transpiler\")]\nmod ffi {\n    #[derive(Debug)]\n    struct Output {\n        success: bool,\n        // empty when error\n        code: String,\n        error: String,\n        diagnostics: Vec<Message>,\n    }\n\n    #[derive(Debug, Clone, PartialEq, Eq)]\n    enum Level {\n        Error,\n        Warning,\n    }\n\n    #[derive(Debug, Clone, PartialEq, Eq)]\n    struct Message {\n        level: Level,\n        message: String,\n    }\n    extern \"Rust\" {\n\n        /// Strip typescript types from the source code.\n        /// Other typescript constructs line enum will result in error `Output`.\n        fn ts_strip(filename: &str, src: &[u8]) -> Output;\n    }\n}\n\nfn ts_strip(filename: &str, src: &[u8]) -> Output {\n    tr_strip_string(filename, String::from_utf8_lossy(src).to_string())\n}\n\nfn tr_strip_string(filename: &str, src: String) -> Output {\n    let cm: Rc<SourceMap> = Rc::default();\n    let errors = Box::new(MessagesCollector::default());\n    let messages = errors.messages.clone();\n    let handler = Handler::with_emitter(false, false, errors);\n\n    let output = HANDLER.set(&handler, || {\n        swc_ts_fast_strip::operate(\n            &cm,\n            &handler,\n            src,\n            swc_ts_fast_strip::Options {\n                filename: Some(filename.to_owned()),\n                mode: swc_ts_fast_strip::Mode::StripOnly,\n                ..Default::default()\n            },\n        )\n    });\n\n    match output {\n        Ok(output) => Output {\n            success: true,\n            code: output.code,\n            error: String::new(),\n            diagnostics: messages.borrow().clone(),\n        },\n        Err(err) => Output {\n            success: false,\n            code: String::new(),\n            error: err.message,\n            diagnostics: messages.borrow().clone(),\n        },\n    }\n}\n\n/// Collects all swc emitted error message.\n#[derive(Default)]\nstruct MessagesCollector {\n    messages: Rc<RefCell<Vec<ffi::Message>>>,\n}\n\nimpl Emitter for MessagesCollector {\n    fn emit(&mut self, db: &mut DiagnosticBuilder<'_>) {\n        if db.is_error() {\n            self.messages.borrow_mut().push(ffi::Message {\n                level: ffi::Level::Error,\n                message: db.message(),\n            });\n        } else if db.level == Level::Warning {\n            self.messages.borrow_mut().push(ffi::Message {\n                level: ffi::Level::Warning,\n                message: db.message(),\n            });\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::ffi::Output;\n    use crate::ffi::{self};\n    use crate::tr_strip_string;\n\n    fn tr(src: &str) -> Output {\n        tr_strip_string(\"foo.ts\", src.to_owned())\n    }\n\n    #[test]\n    fn js() {\n        let out = tr(\"let x = 42;\");\n        assert!(out.success);\n        assert_eq!(\"let x = 42;\", out.code);\n        assert!(out.diagnostics.is_empty());\n    }\n\n    #[test]\n    fn ts() {\n        let out = tr(\"let x: Number = 42;\");\n        assert!(out.success);\n        assert_eq!(\"let x         = 42;\", out.code);\n        assert!(out.diagnostics.is_empty());\n    }\n\n    #[test]\n    fn worker() {\n        assert_eq!(\n            r\"\nexport default {\n    async fetch(request, env, ctx)                    {\n        return new Response('Hello World from Typescript!');\n    },\n}                               ;\",\n            tr(r\"\nexport default {\n    async fetch(request, env, ctx): Promise<Response> {\n        return new Response('Hello World from Typescript!');\n    },\n} satisfies ExportedHandler<Env>;\")\n            .code\n        );\n    }\n\n    #[test]\n    fn erase_enum() {\n        // only types are stripped, unsupported typescript construct are reported as errors\n        let out = tr(r\"enum Foo { A,B,C }\");\n        assert!(!out.success);\n        assert_eq!(\"\", out.code);\n        assert_eq!(\"Unsupported syntax\", out.error);\n        assert_eq!(\n            vec![ffi::Message {\n                level: ffi::Level::Error,\n                message: \"TypeScript enum is not supported in strip-only mode\".to_owned()\n            }],\n            out.diagnostics\n        );\n    }\n}\n"
  },
  {
    "path": "src/workerd/README.md",
    "content": "The subdirectories are organized as follows:\n\n* **util:** Contains random unrelated utilities that don't depend on each other or the rest of the system.\n* **jsg:** Contains magic template library for auto-generating FFI glue between C++ and V8 JavaScript.\n* **io:** Generally contains code that handles the I/O layer which allows APIs to talk to the rest of the world. Also includes basic Worker lifecycle and event delivery.\n* **api:** Contains implementations of publicly documented application-visible JavaScript APIs.\n* **server:** Contains the high-level server implementation.\n* **tools:** Contains additional meta-programs, notably a script for exporting API types. "
  },
  {
    "path": "src/workerd/api/AGENTS.md",
    "content": "# src/workerd/api/\n\nAll JavaScript APIs exposed to Workers: HTTP, crypto, streams, WebSocket, Cache, KV, R2, SQL, encoding, events, timers, scheduled/alarm handlers.\n\n## STRUCTURE\n\n```\n*.h + *.c++          # Top-level API impls (http, headers, cache, blob, url, events, etc.)\ncrypto/              # WebCrypto + Node crypto: AES, RSA, EC, DH, HKDF, PBKDF2, X.509, JWK\nstreams/             # ReadableStream/WritableStream/TransformStream (internal + standard); has README.md\nnode/                # Node.js compat C++ layer; register new modules in NODEJS_MODULES macro in node.h\npyodide/             # Python Workers Pyodide/Emscripten bridge (7 files)\ntests/               # JS integration tests (238 entries); each test = .js + .wd-test pair\n```\n\n## WHERE TO LOOK\n\n| Task                           | Files                                                 |\n| ------------------------------ | ----------------------------------------------------- |\n| fetch / Request / Response     | `http.h`, `http.c++`                                  |\n| Headers                        | `headers.h`, `headers.c++`                            |\n| WebSocket                      | `web-socket.h`, `web-socket.c++`                      |\n| Hibernatable WS (DO)           | `hibernatable-web-socket.h`                           |\n| GlobalScope / addEventListener | `global-scope.h`, `global-scope.c++`, `basics.h`      |\n| JS RPC / Fetcher               | `worker-rpc.h`, `worker-rpc.c++`                      |\n| Durable Object state           | `actor-state.h`, `actor.h`                            |\n| Streams internals              | `streams/` (see `streams/README.md` for architecture) |\n| Crypto (WebCrypto)             | `crypto/crypto.h`, `crypto/impl.h`, `crypto/keys.h`   |\n| Node.js compat (C++)           | `node/node.h` (NODEJS_MODULES macro), per-module .h   |\n| URL (legacy vs standard)       | `url.h` (legacy), `url-standard.h` (spec-compliant)   |\n| Encoding (TextEncoder/Dec)     | `encoding.h`; stream variants in `streams/encoding.h` |\n| Scheduled / Queue / Alarm      | `scheduled.h`, `queue.h`, `actor-state.h`             |\n| HTMLRewriter                   | `html-rewriter.h`                                     |\n| R2 storage                     | `r2-bucket.h`, `r2-admin.h`, `r2-multipart.h`         |\n| KV / SyncKV                    | `kv.h`, `sync-kv.h`                                   |\n| SQL (DO)                       | `sql.h`                                               |\n| Body mixin (shared Req/Res)    | `http.h` — `Body` class, `Body::Initializer` OneOf    |\n\n## CONVENTIONS\n\n- `global-scope.h` forward-declares most API classes; `ServiceWorkerGlobalScope` registers all nested types\n- Node.js compat: add C++ class + register in `NODEJS_MODULES(V)` macro in `node/node.h`; experimental modules go in `NODEJS_MODULES_EXPERIMENTAL(V)`\n- URL has dual impl: legacy (`url.h`) vs standard (`url-standard.h`); compat flag selects which\n- Streams has dual impl: internal (`streams/internal.h`) vs standard (`streams/standard.h`)\n- Test naming: `tests/<feature>-test.js` + `tests/<feature>-test.wd-test`; C++ unit tests: `<feature>-test.c++`\n"
  },
  {
    "path": "src/workerd/api/BUILD.bazel",
    "content": "load(\"//:build/kj_test.bzl\", \"kj_test\")\nload(\"//:build/wd_capnp_library.bzl\", \"wd_capnp_library\")\nload(\"//:build/wd_cc_library.bzl\", \"wd_cc_library\")\nload(\"//:build/wd_test.bzl\", \"wd_test\")\n\nfilegroup(\n    name = \"srcs\",\n    # Only add files here when needed: Where possible, new src/header files should be defined in\n    # their own targets, modularizing bazel targets makes incremental rebuilds faster.\n    srcs = [\n        \"actor.c++\",\n        \"actor-state.c++\",\n        \"basics.c++\",\n        \"blob.c++\",\n        \"cache.c++\",\n        \"cf-property.c++\",\n        \"container.c++\",\n        \"events.c++\",\n        \"eventsource.c++\",\n        \"export-loopback.c++\",\n        \"filesystem.c++\",\n        \"form-data.c++\",\n        \"global-scope.c++\",\n        \"headers.c++\",\n        \"hibernatable-web-socket.c++\",\n        \"http.c++\",\n        \"messagechannel.c++\",\n        \"performance.c++\",\n        \"queue.c++\",\n        \"scheduled.c++\",\n        \"sockets.c++\",\n        \"sql.c++\",\n        \"sync-kv.c++\",\n        \"system-streams.c++\",\n        \"trace.c++\",\n        \"unsafe.c++\",\n        \"url-standard.c++\",\n        \"web-socket.c++\",\n        \"worker-rpc.c++\",\n    ] + glob(\n        [\n            \"crypto/*.c++\",\n            \"streams/*.c++\",\n        ],\n        exclude = [\n            \"**/*test*.c++\",\n            \"crypto/crc-impl.c++\",\n            \"streams/compression.c++\",\n            \"streams/encoding.c++\",\n        ],\n    ),\n    visibility = [\"//visibility:public\"],\n)\n\nfilegroup(\n    name = \"hdrs\",\n    srcs = [\n        \"actor.h\",\n        \"actor-state.h\",\n        \"basics.h\",\n        \"blob.h\",\n        \"cache.h\",\n        \"cf-property.h\",\n        \"container.h\",\n        \"events.h\",\n        \"eventsource.h\",\n        \"export-loopback.h\",\n        \"filesystem.h\",\n        \"form-data.h\",\n        \"global-scope.h\",\n        \"headers.h\",\n        \"hibernatable-web-socket.h\",\n        \"http.h\",\n        \"messagechannel.h\",\n        \"performance.h\",\n        \"queue.h\",\n        \"scheduled.h\",\n        \"sockets.h\",\n        \"sql.h\",\n        \"sync-kv.h\",\n        \"system-streams.h\",\n        \"trace.h\",\n        \"unsafe.h\",\n        \"url-standard.h\",\n        \"web-socket.h\",\n        \"worker-rpc.h\",\n    ] + glob(\n        [\n            \"crypto/*.h\",\n            \"streams/*.h\",\n        ],\n        exclude = [\n            \"crypto/crc-impl.h\",\n            \"streams/compression.h\",\n            \"streams/encoding.h\",\n        ],\n    ),\n    visibility = [\"//visibility:public\"],\n)\n\nwd_cc_library(\n    name = \"analytics-engine\",\n    srcs = [\n        \"analytics-engine.c++\",\n    ],\n    hdrs = [\n        \"analytics-engine.h\",\n        \"analytics-engine-impl.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/io\",\n    ],\n)\n\nwd_cc_library(\n    name = \"capnp\",\n    srcs = [\n        \"capnp.c++\",\n    ],\n    hdrs = [\n        \"capnp.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/io\",\n    ],\n)\n\nwd_cc_library(\n    name = \"worker-loader\",\n    srcs = [\n        \"worker-loader.c++\",\n    ],\n    hdrs = [\n        \"worker-loader.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/io\",\n    ],\n)\n\nwd_cc_library(\n    name = \"workers-module\",\n    srcs = [\n        \"workers-module.c++\",\n    ],\n    hdrs = [\n        \"workers-module.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/io\",\n    ],\n)\n\nwd_cc_library(\n    name = \"base64\",\n    srcs = [\n        \"base64.c++\",\n    ],\n    hdrs = [\n        \"base64.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/jsg\",\n        \"@simdutf\",\n    ],\n)\n\nwd_cc_library(\n    name = \"commonjs\",\n    srcs = [\n        \"commonjs.c++\",\n    ],\n    hdrs = [\n        \"commonjs.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/io\",\n    ],\n)\n\nwd_cc_library(\n    name = \"crypto-crc-impl\",\n    srcs = [\n        \"crypto/crc-impl.c++\",\n    ],\n    hdrs = [\n        \"crypto/crc-impl.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n)\n\nwd_cc_library(\n    name = \"fuzzilli\",\n    srcs = [\n        \"fuzzilli.c++\",\n    ],\n    hdrs = [\n        \"fuzzilli.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/api:util\",\n        \"//src/workerd/util:immediate-crash\",\n    ],\n)\n\nwd_cc_library(\n    name = \"kv\",\n    srcs = [\n        \"kv.c++\",\n    ],\n    hdrs = [\n        \"kv.h\",\n    ],\n    implementation_deps = [\n        \"//src/workerd/util:mimetype\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/io\",\n    ],\n)\n\n# for using EW_STREAMS_ISOLATE_TYPES with workerd-api etc. To use the different streams APIs,\n# include the headers you need directly.\nwd_cc_library(\n    name = \"streams\",\n    hdrs = [\"streams.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":streams-compression\",\n        \":streams-encoding\",\n        \"//src/workerd/io\",\n    ],\n)\n\nwd_cc_library(\n    name = \"streams-compression\",\n    srcs = [\"streams/compression.c++\"],\n    hdrs = [\"streams/compression.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/io\",\n        \"//src/workerd/util:state-machine\",\n        \"@nbytes\",\n    ],\n)\n\nwd_cc_library(\n    name = \"streams-encoding\",\n    srcs = [\"streams/encoding.c++\"],\n    hdrs = [\"streams/encoding.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/io\",\n    ],\n)\n\nwd_cc_library(\n    name = \"hibernation-event-params\",\n    hdrs = [\"hibernation-event-params.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nwd_cc_library(\n    name = \"r2\",\n    srcs = glob([\"r2*.c++\"]),\n    hdrs = glob([\"r2*.h\"]),\n    implementation_deps = [\n        \":r2-api_capnp\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/io\",\n    ],\n)\n\nwd_cc_library(\n    name = \"memory-cache\",\n    srcs = [\"memory-cache.c++\"],\n    hdrs = [\"memory-cache.h\"],\n    implementation_deps = [\n        \"//src/workerd/io\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/io:compatibility-date_capnp\",\n        \"//src/workerd/jsg\",\n        \"//src/workerd/util:checked-queue\",\n        \"//src/workerd/util:uuid\",\n    ],\n)\n\nwd_cc_library(\n    name = \"tracing-module\",\n    srcs = [\"tracing-module.c++\"],\n    hdrs = [\"tracing-module.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/io\",\n        \"//src/workerd/jsg\",\n    ],\n)\n\nwd_cc_library(\n    name = \"rtti\",\n    srcs = [\n        \"modules.c++\",\n        \"rtti.c++\",\n    ],\n    hdrs = [\n        \"modules.h\",\n        \"rtti.h\",\n    ],\n    implementation_deps = [\n        \":analytics-engine\",\n        \":kv\",\n        \":pyodide\",\n        \":streams\",\n        \":streams-compression\",\n        \":streams-encoding\",\n        \":urlpattern\",\n        \":urlpattern-standard\",\n        \":worker-loader\",\n        \"//src/workerd/jsg:rtti\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":base64\",\n        \":html-rewriter\",\n        \":hyperdrive\",\n        \":memory-cache\",\n        \":r2\",\n        \":tracing-module\",\n        \":workers-module\",\n        \"//src/cloudflare\",\n        \"//src/node\",\n        \"//src/pyodide\",\n        \"//src/workerd/api/node\",\n        \"//src/workerd/io\",\n    ],\n)\n\nwd_cc_library(\n    name = \"html-rewriter\",\n    srcs = [\"html-rewriter.c++\"],\n    hdrs = [\"html-rewriter.h\"],\n    implementation_deps = [\n        \"@com_cloudflare_lol_html//:lolhtml\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/io\",\n    ],\n)\n\nwd_cc_library(\n    name = \"hyperdrive\",\n    srcs = [\"hyperdrive.c++\"],\n    hdrs = [\"hyperdrive.h\"],\n    implementation_deps = [\n        \"//src/workerd/util:entropy\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/io\",\n    ],\n)\n\nwd_cc_library(\n    name = \"pyodide\",\n    srcs = [\n        \"pyodide/pyodide.c++\",\n        \"pyodide/requirements.c++\",\n        \"pyodide/setup-emscripten.c++\",\n    ],\n    hdrs = [\n        \"pyodide/pyodide.h\",\n        \"pyodide/requirements.h\",\n        \"pyodide/setup-emscripten.h\",\n        \"//src/pyodide:generated/pyodide_extra.capnp.h\",\n        \"//src/pyodide:pyodide_static.capnp.h\",\n    ],\n    implementation_deps = [\n        \"//src/workerd/io\",\n        \"//src/workerd/util:strings\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/pyodide:pyodide_extra_capnp\",\n        \"//src/pyodide:pyodide_static\",\n        \"//src/workerd/io:compatibility-date_capnp\",\n        \"//src/workerd/jsg\",\n        \"@capnp-cpp//src/kj:kj-async\",\n        \"@capnp-cpp//src/kj/compat:kj-gzip\",\n        \"@capnp-cpp//src/kj/compat:kj-http\",\n        \"@capnp-cpp//src/kj/compat:kj-tls\",\n    ],\n)\n\nkj_test(\n    src = \"pyodide/pyodide-test.c++\",\n    deps = [\":pyodide\"],\n)\n\nwd_cc_library(\n    name = \"util\",\n    srcs = [\"util.c++\"],\n    hdrs = [\"util.h\"],\n    implementation_deps = [\n        \"//src/workerd/util:mimetype\",\n        \"//src/workerd/util:strings\",\n        \"@simdutf\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/jsg\",\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nwd_cc_library(\n    name = \"encoding\",\n    srcs = [\n        \"encoding.c++\",\n        \"encoding-legacy.c++\",\n    ],\n    hdrs = [\n        \"encoding.h\",\n        \"encoding-legacy.h\",\n        \"encoding-shared.h\",\n    ],\n    implementation_deps = [\n        \"//src/workerd/util:strings\",\n        \"@simdutf\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":util\",\n        \"//src/rust/encoding\",\n        \"//src/workerd/io:compatibility-date_capnp\",\n        \"//src/workerd/io:features\",\n        \"//src/workerd/jsg\",\n        \"@capnp-cpp//src/kj\",\n        \"@simdutf\",\n        \"@workerd-cxx//kj-rs\",\n    ],\n)\n\nwd_cc_library(\n    name = \"data-url\",\n    srcs = [\"data-url.c++\"],\n    hdrs = [\"data-url.h\"],\n    implementation_deps = [\n        \":encoding\",\n        \"//src/workerd/util:strings\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/jsg:url\",\n        \"//src/workerd/util:mimetype\",\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nwd_cc_library(\n    name = \"deferred-proxy\",\n    hdrs = [\"deferred-proxy.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@capnp-cpp//src/kj\",\n        \"@capnp-cpp//src/kj:kj-async\",\n    ],\n)\n\nwd_capnp_library(src = \"r2-api.capnp\")\n\nwd_capnp_library(src = \"analytics-engine.capnp\")\n\nwd_cc_library(\n    name = \"url\",\n    srcs = [\"url.c++\"],\n    hdrs = [\"url.h\"],\n    implementation_deps = [\":util\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/io:compatibility-date_capnp\",\n        \"//src/workerd/jsg\",\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nwd_cc_library(\n    name = \"urlpattern\",\n    srcs = [\"urlpattern.c++\"],\n    hdrs = [\"urlpattern.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":url\",\n        \"//src/workerd/jsg\",\n        \"//src/workerd/util:own-util\",\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nwd_cc_library(\n    name = \"urlpattern-standard\",\n    srcs = [\"urlpattern-standard.c++\"],\n    hdrs = [\"urlpattern-standard.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/jsg\",\n        \"@ada-url\",\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\n####################################################################################################\n## Tests\n\n[\n    kj_test(\n        src = f,\n        deps = [\n            \"//src/workerd/io\",\n            \"//src/workerd/io:promise-wrapper\",\n        ],\n    )\n    for f in [\n        \"actor-state-test.c++\",\n        \"basics-test.c++\",\n        \"crypto/aes-test.c++\",\n        \"crypto/impl-test.c++\",\n    ]\n]\n\n[\n    kj_test(\n        src = f,\n        deps = [\n            \"//src/workerd/io\",\n            \"//src/workerd/io:promise-wrapper\",\n            \"//src/workerd/tests:test-fixture\",\n        ],\n    )\n    for f in [\n        \"streams/queue-test.c++\",\n        \"streams/standard-test.c++\",\n    ]\n]\n\nkj_test(\n    src = \"data-url-test.c++\",\n    deps = [\n        \":data-url\",\n        \"//src/workerd/io\",\n    ],\n)\n\nkj_test(\n    src = \"util-test.c++\",\n    deps = [\n        \":util\",\n    ],\n)\n\nkj_test(\n    src = \"deferred-proxy-test.c++\",\n    deps = [\n        \":deferred-proxy\",\n    ],\n)\n\nkj_test(\n    src = \"streams/internal-test.c++\",\n    deps = [\n        \"//src/workerd/io\",\n        \"//src/workerd/tests:test-fixture\",\n    ],\n)\n\nkj_test(\n    src = \"streams-test.c++\",\n    deps = [\n        \"//src/workerd/io\",\n        \"//src/workerd/tests:test-fixture\",\n    ],\n)\n\nkj_test(\n    src = \"system-streams-test.c++\",\n    deps = [\n        \"//src/workerd/io\",\n        \"//src/workerd/tests:test-fixture\",\n    ],\n)\n\nkj_test(\n    src = \"actor-state-iocontext-test.c++\",\n    deps = [\n        \"//src/workerd/io\",\n        \"//src/workerd/tests:test-fixture\",\n    ],\n)\n\nkj_test(\n    src = \"api-rtti-test.c++\",\n    deps = [\n        \":encoding\",\n        \":html-rewriter\",\n        \":streams\",\n        \":urlpattern\",\n        \":urlpattern-standard\",\n        \"//src/workerd/io\",\n        \"//src/workerd/jsg:rtti\",\n    ],\n)\n\nkj_test(\n    src = \"encoding-test.c++\",\n    deps = [\n        \":encoding\",\n        \"//src/workerd/io\",\n    ],\n)\n\nkj_test(\n    src = \"base64-test.c++\",\n    deps = [\"//src/workerd/tests:test-fixture\"],\n)\n\nkj_test(\n    src = \"ser-errors-test.c++\",\n    deps = [\n        \"//src/workerd/jsg\",\n        \"//src/workerd/tests:test-fixture\",\n    ],\n)\n\nkj_test(\n    src = \"cf-property-test.c++\",\n    deps = [\"//src/workerd/tests:test-fixture\"],\n)\n\nkj_test(\n    src = \"streams/writable-sink-test.c++\",\n    deps = [\n        \"//src/workerd/io\",\n        \"//src/workerd/jsg\",\n        \"//src/workerd/tests:test-fixture\",\n    ],\n)\n\nkj_test(\n    src = \"streams/readable-source-test.c++\",\n    deps = [\n        \"//src/workerd/io\",\n        \"//src/workerd/jsg\",\n        \"//src/workerd/tests:test-fixture\",\n    ],\n)\n\nkj_test(\n    src = \"streams/writable-sink-adapter-test.c++\",\n    deps = [\n        \"//src/workerd/io\",\n        \"//src/workerd/jsg\",\n        \"//src/workerd/tests:test-fixture\",\n    ],\n)\n\nkj_test(\n    src = \"streams/readable-source-adapter-test.c++\",\n    deps = [\n        \"//src/workerd/io\",\n        \"//src/workerd/jsg\",\n        \"//src/workerd/tests:test-fixture\",\n    ],\n)\n\nwd_test(\n    src = \"streams/identitytransformstream-backpressure-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"streams/identitytransformstream-backpressure-test.js\"],\n)\n\nwd_test(\n    src = \"streams/identitytransformstream-byob-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"streams/identitytransformstream-byob-test.js\"],\n)\n\nwd_test(\n    src = \"streams/streams-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"streams/streams-test.js\"],\n)\n"
  },
  {
    "path": "src/workerd/api/actor-state-iocontext-test.c++",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/api/actor-state.h>\n#include <workerd/io/actor-id.h>\n#include <workerd/tests/test-fixture.h>\n\n#include <kj/encoding.h>\n#include <kj/test.h>\n\n#include <algorithm>\n\nnamespace workerd::api {\nnamespace {\n\nusing workerd::TestFixture;\n\nbool contains(kj::StringPtr haystack, kj::StringPtr needle) {\n  return std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end()) !=\n      haystack.end();\n}\n\nclass MockActorId: public ActorIdFactory::ActorId {\n public:\n  MockActorId(kj::String id): id(kj::mv(id)) {}\n  kj::String toString() const override {\n    return kj::str(\"MockActorId<\", id, \">\");\n  }\n\n  kj::Maybe<kj::StringPtr> getName() const override {\n    return kj::none;\n  }\n\n  kj::Maybe<kj::StringPtr> getJurisdiction() const override {\n    return kj::none;\n  }\n\n  bool equals(const ActorId& other) const override {\n    return false;\n  }\n\n  kj::Own<ActorId> clone() const override {\n    return kj::heap<MockActorId>(kj::heapString(id));\n  }\n\n  virtual ~MockActorId() {};\n\n private:\n  kj::String id;\n};\n\nvoid runBadDeserialization(jsg::Lock& lock, kj::StringPtr expectedId) {\n  // FF = kVersion token, 0E = version 15, 06 = an unknown tag value\n  kj::StringPtr invalidV8Hex = \"FF0E06\"_kj;\n  auto invalidV8Value = kj::decodeHex(invalidV8Hex.asArray());\n  try {\n    deserializeV8Value(lock, \"some-key\"_kj, invalidV8Value);\n    KJ_FAIL_ASSERT(\"deserializeV8Value should have failed.\");\n  } catch (kj::Exception& ex) {\n    if (ex.getDescription().startsWith(\"actor storage deserialization failed\")) {\n      KJ_ASSERT(contains(ex.getDescription(), expectedId));\n    } else {\n      throw;\n    }\n  }\n}\n\nvoid runBadDeserializationInIoContext(TestFixture& fixture, kj::StringPtr expectedId) {\n  fixture.runInIoContext(\n      [expectedId](const workerd::TestFixture::Environment& env) -> kj::Promise<void> {\n    runBadDeserialization(env.lock, expectedId);\n    return kj::READY_NOW;\n  });\n}\n\n// TODO(maybe) It would be nice to have a test that tests the case when there's no IoContext,\n// but that's a royal pain to set up in this test file we'd basically only test that we don't\n// crash, which the actor-state-test.c++ does for us.\n\nKJ_TEST(\"no actor specified\") {\n  TestFixture fixture;\n  runBadDeserializationInIoContext(fixture, \"actorId = ;\"_kj);\n}\n\nKJ_TEST(\"actor specified with string id\") {\n  Worker::Actor::Id id = kj::str(\"testActorId\");\n  TestFixture fixture(TestFixture::SetupParams{.actorId = kj::mv(id)});\n  runBadDeserializationInIoContext(fixture, \"actorId = testActorId;\"_kj);\n}\n\nKJ_TEST(\"actor specified with ActorId object\") {\n  kj::Own<ActorIdFactory::ActorId> mockActorId = kj::heap<MockActorId>(kj::str(\"testActorId\"));\n  Worker::Actor::Id id = kj::mv(mockActorId);\n  TestFixture fixture(TestFixture::SetupParams{\n    .actorId = kj::mv(id),\n  });\n  runBadDeserializationInIoContext(fixture, \"actorId = MockActorId<testActorId>;\"_kj);\n}\n\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/actor-state-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/api/actor-state.h>\n#include <workerd/api/util.h>\n#include <workerd/jsg/jsg-test.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/ser.h>\n#include <workerd/jsg/setup.h>\n\n#include <capnp/message.h>\n#include <capnp/rpc-twoparty.h>\n#include <capnp/rpc.h>\n#include <kj/encoding.h>\n#include <kj/test.h>\n\n#include <fstream>\n#include <iostream>\n\nnamespace workerd::api {\nnamespace {\n\njsg::V8System v8System;\n\nstruct ActorStateContext: public jsg::Object, public jsg::ContextGlobal {\n  JSG_RESOURCE_TYPE(ActorStateContext) {}\n};\nJSG_DECLARE_ISOLATE_TYPE(ActorStateIsolate, ActorStateContext);\n\nKJ_TEST(\"v8 serialization version tag hasn't changed\") {\n  jsg::test::Evaluator<ActorStateContext, ActorStateIsolate> e(v8System);\n  e.getIsolate().runInLockScope([&](ActorStateIsolate::Lock& isolateLock) {\n    JSG_WITHIN_CONTEXT_SCOPE(isolateLock,\n        isolateLock.newContext<ActorStateContext>().getHandle(isolateLock), [&](jsg::Lock& js) {\n      auto buf = serializeV8Value(isolateLock, isolateLock.boolean(true));\n\n      // Confirm that a version header is appropriately written and that it contains the expected\n      // current version. When the version increases, we need to write a v8 patch that allows it\n      // to continue writing data at the old version so that we can do a rolling upgrade without\n      // any bugs caused by old processes failing to read data written by new ones.\n      KJ_EXPECT(buf[0] == 0xFF);\n      KJ_EXPECT(buf[1] == 0x0F);  // v8 serializer version\n\n      // And this just confirms that the deserializer agrees on the version.\n      v8::ValueDeserializer deserializer(isolateLock.v8Isolate, buf.begin(), buf.size());\n      auto maybeHeader = deserializer.ReadHeader(isolateLock.v8Context());\n      KJ_EXPECT(jsg::check(maybeHeader));\n      KJ_EXPECT(deserializer.GetWireFormatVersion() == 15);\n\n      // Just for kicks, make sure it deserializes properly too.\n      KJ_EXPECT(deserializeV8Value(isolateLock, \"some-key\"_kj, buf).isTrue());\n    });\n  });\n}\n\nKJ_TEST(\"we support deserializing up to v15\") {\n  jsg::test::Evaluator<ActorStateContext, ActorStateIsolate> e(v8System);\n  e.getIsolate().runInLockScope([&](ActorStateIsolate::Lock& isolateLock) {\n    JSG_WITHIN_CONTEXT_SCOPE(isolateLock,\n        isolateLock.newContext<ActorStateContext>().getHandle(isolateLock), [&](jsg::Lock& js) {\n      kj::Vector<kj::StringPtr> testCases;\n      testCases.add(\"54\");\n      testCases.add(\"FF0D54\");\n      testCases.add(\"FF0E54\");\n      testCases.add(\"FF0F54\");\n\n      for (const auto& hexStr: testCases) {\n        auto dataIn = kj::decodeHex(hexStr.asArray());\n        KJ_EXPECT(deserializeV8Value(isolateLock, \"some-key\"_kj, dataIn).isTrue());\n      }\n    });\n  });\n}\n\n// This is hacky, but we want to compare the old deserialization logic that's been in prod from when\n// actors went live through March 2022 to the new version of the deserialization logic and make sure\n// it works the same.\n// TODO(soon): Remove this. Ideally we can just fix the test below that attempts to read serialized\n// data and round-trip it back to storage to deal with the problem that it likes to read in \"sparse\"\n// JS arrays and write them back out as \"dense\" JS arrays, which breaks the equality check after\n// round-tripping a value.\njsg::JsValue oldDeserializeV8Value(jsg::Lock& js, kj::ArrayPtr<const kj::byte> buf) {\n  jsg::Deserializer des(js, buf);\n  return des.readValue(js);\n}\n\nKJ_TEST(\"wire format version does not change deserialization behavior on real data\") {\n  // This test checks for the presence of a specially named file in the current working directory\n  // that contains lines of hex-encoded v8-serialized data. It processes one line at time,\n  // hex-decoding it and then testing deserializing/re-serializing it.\n\n  std::ifstream file;\n  file.open(\"serialization-test-data.txt\");\n  if (!file) {\n    KJ_LOG(WARNING, \"skipping serialization test due to missing data file\");\n    return;\n  }\n\n  jsg::test::Evaluator<ActorStateContext, ActorStateIsolate> e(v8System);\n  e.getIsolate().runInLockScope([&](ActorStateIsolate::Lock& isolateLock) {\n    JSG_WITHIN_CONTEXT_SCOPE(isolateLock,\n        isolateLock.newContext<ActorStateContext>().getHandle(isolateLock), [&](jsg::Lock& js) {\n      // Read in data line by line and verify that it round trips (serializes and\n      // then deserializes) back to the exact same data as the input.\n      std::string hexStr;\n      const auto key = \"some-key\"_kj;\n      while (std::getline(file, hexStr)) {\n        auto dataIn = kj::decodeHex(kj::ArrayPtr(hexStr.c_str(), hexStr.size()));\n        KJ_EXPECT(!dataIn.hadErrors, kj::str(hexStr.c_str()));\n\n        auto oldVal = oldDeserializeV8Value(isolateLock, dataIn);\n        auto oldOutput = serializeV8Value(isolateLock, oldVal);\n\n        auto newVal = deserializeV8Value(isolateLock, key, dataIn);\n        auto newOutput = serializeV8Value(isolateLock, newVal);\n        KJ_EXPECT(oldOutput == newOutput, kj::str(hexStr.c_str()));\n      }\n    });\n  });\n}\n\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/actor-state.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"actor-state.h\"\n\n#include \"actor.h\"\n#include \"export-loopback.h\"\n#include \"sql.h\"\n#include \"sync-kv.h\"\n#include \"util.h\"\n\n#include <workerd/api/web-socket.h>\n#include <workerd/io/actor-cache.h>\n#include <workerd/io/actor-id.h>\n#include <workerd/io/actor-sqlite.h>\n#include <workerd/io/features.h>\n#include <workerd/io/hibernation-manager.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/ser.h>\n#include <workerd/jsg/util.h>\n\n#include <v8.h>\n\nnamespace workerd::api {\n\nnamespace {\n\nconstexpr size_t BILLING_UNIT = 4096;\n\nenum class BillAtLeastOne { NO, YES };\n\nuint32_t billingUnits(size_t bytes, BillAtLeastOne billAtLeastOne = BillAtLeastOne::YES) {\n  if (billAtLeastOne == BillAtLeastOne::YES && bytes == 0) {\n    return 1;  // always bill for at least 1 billing unit\n  }\n  return bytes / BILLING_UNIT + (bytes % BILLING_UNIT != 0);\n}\n\njsg::JsValue deserializeMaybeV8Value(\n    jsg::Lock& js, kj::ArrayPtr<const char> key, kj::Maybe<kj::ArrayPtr<const kj::byte>> buf) {\n  KJ_IF_SOME(b, buf) {\n    return deserializeV8Value(js, key, b);\n  } else {\n    return js.undefined();\n  }\n}\n\ntemplate <typename T, typename Options, typename Func>\nauto transformCacheResult(jsg::Lock& js,\n    kj::OneOf<T, kj::Promise<T>> input,\n    const Options& options,\n    Func&& func) -> jsg::Promise<decltype(func(js, kj::instance<T>()))> {\n  KJ_SWITCH_ONEOF(input) {\n    KJ_CASE_ONEOF(value, T) {\n      return js.resolvedPromise(func(js, kj::mv(value)));\n    }\n    KJ_CASE_ONEOF(promise, kj::Promise<T>) {\n      auto& context = IoContext::current();\n      if (options.allowConcurrency.orDefault(false)) {\n        return context.awaitIo(\n            js, kj::mv(promise), [func = kj::fwd<Func>(func)](jsg::Lock& js, T&& value) mutable {\n          return func(js, kj::mv(value));\n        });\n      } else {\n        return context.awaitIoWithInputLock(\n            js, kj::mv(promise), [func = kj::fwd<Func>(func)](jsg::Lock& js, T&& value) mutable {\n          return func(js, kj::mv(value));\n        });\n      }\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\ntemplate <typename T, typename Options, typename Func>\nauto transformCacheResultWithCacheStatus(jsg::Lock& js,\n    kj::OneOf<T, kj::Promise<T>> input,\n    const Options& options,\n    Func&& func) -> jsg::Promise<decltype(func(js, kj::instance<T>(), kj::instance<bool>()))> {\n  KJ_SWITCH_ONEOF(input) {\n    KJ_CASE_ONEOF(value, T) {\n      return js.resolvedPromise(func(js, kj::mv(value), true));\n    }\n    KJ_CASE_ONEOF(promise, kj::Promise<T>) {\n      auto& context = IoContext::current();\n      if (options.allowConcurrency.orDefault(false)) {\n        return context.awaitIo(\n            js, kj::mv(promise), [func = kj::fwd<Func>(func)](jsg::Lock& js, T&& value) mutable {\n          return func(js, kj::mv(value), false);\n        });\n      } else {\n        return context.awaitIoWithInputLock(\n            js, kj::mv(promise), [func = kj::fwd<Func>(func)](jsg::Lock& js, T&& value) mutable {\n          return func(js, kj::mv(value), false);\n        });\n      }\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\ntemplate <typename Options>\njsg::Promise<void> transformMaybeBackpressure(\n    jsg::Lock& js, const Options& options, kj::Maybe<kj::Promise<void>> maybeBackpressure) {\n  KJ_IF_SOME(backpressure, maybeBackpressure) {\n    // Note: In practice `allowConcurrency` will have no effect on a backpressure promise since\n    //   backpressure blocks everything anyway, but we pass the option through for consistency in\n    //   case of future changes.\n    auto& context = IoContext::current();\n    if (options.allowConcurrency.orDefault(false)) {\n      return context.awaitIo(js, kj::mv(backpressure));\n    } else {\n      return context.awaitIoWithInputLock(js, kj::mv(backpressure), [](jsg::Lock&) {});\n    }\n  } else {\n    return js.resolvedPromise();\n  }\n}\n\nActorObserver& currentActorMetrics() {\n  return IoContext::current().getActorOrThrow().getMetrics();\n}\n\njsg::JsRef<jsg::JsValue> listResultsToMap(\n    jsg::Lock& js, ActorCacheOps::GetResultList value, bool completelyCached) {\n  return js\n      .withinHandleScope([&] {\n    auto map = js.map();\n    size_t cachedReadBytes = 0;\n    size_t uncachedReadBytes = 0;\n    for (const auto& entry: value) {\n      auto& bytesRef =\n          entry.status == ActorCacheOps::CacheStatus::CACHED ? cachedReadBytes : uncachedReadBytes;\n      bytesRef += entry.key.size() + entry.value.size();\n      map.set(js, entry.key, deserializeV8Value(js, entry.key, entry.value));\n    }\n    auto& actorMetrics = currentActorMetrics();\n    if (cachedReadBytes || uncachedReadBytes) {\n      size_t totalReadBytes = cachedReadBytes + uncachedReadBytes;\n      uint32_t totalUnits = billingUnits(totalReadBytes);\n\n      // If we went to disk, we want to ensure we bill at least 1 uncached unit.\n      // Otherwise, we disable this behavior, to ensure a fully cached list will have\n      // uncachedUnits == 0.\n      auto billAtLeastOne = completelyCached ? BillAtLeastOne::NO : BillAtLeastOne::YES;\n      uint32_t uncachedUnits = billingUnits(uncachedReadBytes, billAtLeastOne);\n      uint32_t cachedUnits = totalUnits - uncachedUnits;\n\n      actorMetrics.addUncachedStorageReadUnits(uncachedUnits);\n      actorMetrics.addCachedStorageReadUnits(cachedUnits);\n    } else {\n      // We bill 1 uncached read unit if there was no results from the list.\n      actorMetrics.addUncachedStorageReadUnits(1);\n    }\n\n    return jsg::JsValue(map);\n  }).addRef(js);\n}\n\nkj::Function<jsg::JsRef<jsg::JsValue>(jsg::Lock&, ActorCacheOps::GetResultList)>\ngetMultipleResultsToMap(size_t numInputKeys) {\n  return [numInputKeys](jsg::Lock& js, ActorCacheOps::GetResultList value) mutable {\n    return js\n        .withinHandleScope([&] {\n      auto map = js.map();\n      uint32_t cachedUnits = 0;\n      uint32_t uncachedUnits = 0;\n      for (const auto& entry: value) {\n        auto& unitsRef =\n            entry.status == ActorCacheOps::CacheStatus::CACHED ? cachedUnits : uncachedUnits;\n        unitsRef += billingUnits(entry.key.size() + entry.value.size());\n        map.set(js, entry.key, deserializeV8Value(js, entry.key, entry.value));\n      }\n      auto& actorMetrics = currentActorMetrics();\n      actorMetrics.addCachedStorageReadUnits(cachedUnits);\n\n      size_t leftoverKeys = 0;\n      if (numInputKeys >= value.size()) {\n        leftoverKeys = numInputKeys - value.size();\n      } else {\n        KJ_LOG(ERROR, \"More returned pairs than provided input keys in getMultipleResultsToMap\",\n            numInputKeys, value.size());\n      }\n\n      // leftover keys weren't in the result set, but potentially still\n      // had to be queried for existence.\n      //\n      // TODO(someday): This isn't quite accurate -- we do cache negative entries.\n      // Billing will still be correct today, but if we do ever start billing\n      // only for uncached reads, we'll need to address this.\n      actorMetrics.addUncachedStorageReadUnits(leftoverKeys + uncachedUnits);\n\n      return jsg::JsValue(map);\n    }).addRef(js);\n  };\n}\n\nkj::Promise<void> updateStorageWriteUnit(\n    IoContext& context, ActorObserver& metrics, uint32_t units) {\n  // The ActorObserver& reference here is guaranteed to outlive this task, so\n  // accessing it after the co_await here is safe.\n  co_await context.waitForOutputLocks();\n  metrics.addStorageWriteUnits(units);\n}\n\nkj::Promise<void> updateStorageDeletes(\n    IoContext& context, ActorObserver& metrics, kj::Promise<uint> promise) {\n  // The ActorObserver& reference here is guaranteed to outlive this task, so\n  // accessing it after the co_await here is safe.\n  auto deleted = co_await promise;\n  if (deleted == 0) deleted = 1;\n  metrics.addStorageDeletes(deleted);\n};\n\n// Return the id of the current actor (or the empty string if there is no current actor).\nkj::Maybe<kj::String> getCurrentActorId() {\n  KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n    KJ_IF_SOME(actor, ioContext.getActor()) {\n      KJ_SWITCH_ONEOF(actor.getId()) {\n        KJ_CASE_ONEOF(s, kj::String) {\n          return kj::heapString(s);\n        }\n        KJ_CASE_ONEOF(actorId, kj::Own<ActorIdFactory::ActorId>) {\n          return actorId->toString();\n        }\n      }\n      KJ_UNREACHABLE;\n    }\n  }\n  return kj::none;\n}\n\n}  // namespace\n\nDurableObjectStorage::DurableObjectStorage(jsg::Lock& js,\n    IoPtr<ActorCacheInterface> cache,\n    bool enableSql,\n    kj::Own<IoChannelFactory::ActorChannel> primaryActorChannel,\n    kj::Own<ActorIdFactory::ActorId> primaryActorId)\n    : cache(kj::mv(cache)),\n      enableSql(enableSql) {\n\n  auto replicaFactory = kj::heap<ReplicaActorOutgoingFactory>(\n      kj::mv(primaryActorChannel), primaryActorId->toString());\n  auto outgoingFactory =\n      IoContext::current().addObject<Fetcher::OutgoingFactory>(kj::mv(replicaFactory));\n  auto requiresHost = FeatureFlags::get(IoContext::current().getCurrentLock())\n                          .getDurableObjectFetchRequiresSchemeAuthority()\n      ? Fetcher::RequiresHostAndProtocol::YES\n      : Fetcher::RequiresHostAndProtocol::NO;\n\n  this->maybePrimary = js.alloc<DurableObject>(\n      js.alloc<DurableObjectId>(kj::mv(primaryActorId)), kj::mv(outgoingFactory), requiresHost);\n}\n\njsg::Promise<jsg::JsRef<jsg::JsValue>> DurableObjectStorageOperations::get(jsg::Lock& js,\n    kj::OneOf<kj::String, kj::Array<kj::String>> keys,\n    jsg::Optional<GetOptions> maybeOptions) {\n  auto& context = IoContext::current();\n  auto traceContext = context.makeUserTraceSpan(\"durable_object_storage_get\"_kjc);\n  auto options = configureOptions(kj::mv(maybeOptions).orDefault(GetOptions{}));\n  KJ_SWITCH_ONEOF(keys) {\n    KJ_CASE_ONEOF(s, kj::String) {\n      return context.attachSpans(js, getOne(js, kj::mv(s), options), kj::mv(traceContext));\n    }\n    KJ_CASE_ONEOF(a, kj::Array<kj::String>) {\n      return context.attachSpans(js, getMultiple(js, kj::mv(a), options), kj::mv(traceContext));\n    }\n  }\n  KJ_UNREACHABLE\n}\n\njsg::Promise<jsg::JsRef<jsg::JsValue>> DurableObjectStorageOperations::getOne(\n    jsg::Lock& js, kj::String key, const GetOptions& options) {\n  auto result = getCache(OP_GET).get(kj::str(key), options);\n  return transformCacheResultWithCacheStatus(js, kj::mv(result), options,\n      [key = kj::mv(key)](jsg::Lock& js, kj::Maybe<ActorCacheOps::Value> value, bool cached) {\n    uint32_t units = 1;\n    KJ_IF_SOME(v, value) {\n      units = billingUnits(v.size());\n    }\n    auto& actorMetrics = currentActorMetrics();\n    if (cached) {\n      actorMetrics.addCachedStorageReadUnits(units);\n    } else {\n      actorMetrics.addUncachedStorageReadUnits(units);\n    }\n    return deserializeMaybeV8Value(js, key, value).addRef(js);\n  });\n}\n\njsg::Promise<kj::Maybe<double>> DurableObjectStorageOperations::getAlarm(\n    jsg::Lock& js, jsg::Optional<GetAlarmOptions> maybeOptions) {\n  auto& context = IoContext::current();\n  auto traceContext = context.makeUserTraceSpan(\"durable_object_storage_getAlarm\"_kjc);\n  // Even if we do not have an alarm handler, we might once have had one. It's fine to return\n  // whatever a previous alarm setting or a falsy result.\n  auto options = configureOptions(maybeOptions\n                                      .map([](auto& o) {\n    return GetOptions{.allowConcurrency = o.allowConcurrency, .noCache = false};\n  }).orDefault(GetOptions{}));\n  auto result = getCache(OP_GET_ALARM).getAlarm(options);\n\n  return context.attachSpans(js,\n      transformCacheResult(js, kj::mv(result), options,\n          [](jsg::Lock&, kj::Maybe<kj::Date> date) {\n    return date.map(\n        [](auto& date) { return static_cast<double>((date - kj::UNIX_EPOCH) / kj::MILLISECONDS); });\n  }),\n      kj::mv(traceContext));\n}\n\nkj::Maybe<DurableObjectStorageOperations::CompiledListOptions> DurableObjectStorageOperations::\n    compileListOptions(kj::Maybe<ListOptions>& maybeOptions) {\n  kj::String start;\n  kj::Maybe<kj::String> end;\n  bool reverse = false;\n  kj::Maybe<uint> limit;\n\n  KJ_IF_SOME(o, maybeOptions) {\n    KJ_IF_SOME(s, o.start) {\n      if (o.startAfter != kj::none) {\n        KJ_FAIL_REQUIRE(\n            \"jsg.TypeError: list() cannot be called with both start and startAfter values.\");\n      }\n      start = kj::mv(s);\n    }\n    KJ_IF_SOME(sks, o.startAfter) {\n      // Convert an exclusive startAfter into an inclusive start key here so that the implementation\n      // doesn't need to handle both. This can be done simply by adding two NULL bytes. One to the end of\n      // the startAfter and another to set the start key after startAfter.\n      auto startAfterKey = kj::heapArray<char>(sks.size() + 2);\n\n      // Copy over the original string.\n      memcpy(startAfterKey.begin(), sks.begin(), sks.size());\n      // Add one additional null byte to set the new start as the key immediately\n      // after startAfter. This looks a little sketchy to be doing with strings rather\n      // than arrays, but kj::String explicitly allows for NULL bytes inside of strings.\n      startAfterKey[startAfterKey.size() - 2] = '\\0';\n      // kj::String automatically reads the last NULL as string termination, so we need to add it twice\n      // to make it stick in the final string.\n      startAfterKey[startAfterKey.size() - 1] = '\\0';\n      start = kj::String(kj::mv(startAfterKey));\n    }\n    KJ_IF_SOME(e, o.end) {\n      end = kj::mv(e);\n    }\n    KJ_IF_SOME(r, o.reverse) {\n      reverse = r;\n    }\n    KJ_IF_SOME(l, o.limit) {\n      JSG_REQUIRE(l > 0, TypeError, \"List limit must be positive.\");\n      limit = l;\n    }\n    KJ_IF_SOME(prefix, o.prefix) {\n      // Let's clamp `start` and `end` to include only keys with the given prefix.\n      if (prefix.size() > 0) {\n        if (start < prefix) {\n          // `start` is before `prefix`, so listing should actually start at `prefix`.\n          start = kj::str(prefix);\n        } else if (start.startsWith(prefix)) {\n          // `start` is within the prefix, so need not be modified.\n        } else {\n          // `start` comes after the last value with the prefix, so there's no overlap.\n          return kj::none;\n        }\n\n        // Calculate the first key that sorts after all keys with the given prefix.\n        kj::Vector<char> keyAfterPrefix(prefix.size());\n        keyAfterPrefix.addAll(prefix);\n        while (!keyAfterPrefix.empty() && static_cast<byte>(keyAfterPrefix.back()) == 0xff) {\n          keyAfterPrefix.removeLast();\n        }\n        if (keyAfterPrefix.empty()) {\n          // The prefix is a string of some number of 0xff bytes, so includes the entire key space\n          // up through the last possible key. Hence, there is no end. (But if an end was specified\n          // earlier, that's still valid.)\n        } else {\n          keyAfterPrefix.back()++;\n          keyAfterPrefix.add('\\0');\n          auto keyAfterPrefixStr = kj::String(keyAfterPrefix.releaseAsArray());\n\n          KJ_IF_SOME(e, end) {\n            if (e <= prefix) {\n              // No keys could possibly match both the end and the prefix.\n              return kj::none;\n            } else if (e.startsWith(prefix)) {\n              // `end` is within the prefix, so need not be modified.\n            } else {\n              // `end` comes after all keys with the prefix, so we should stop at the end of the\n              // prefix.\n              end = kj::mv(keyAfterPrefixStr);\n            }\n          } else {\n            // We didn't have any end set, so use the end of the prefix range.\n            end = kj::mv(keyAfterPrefixStr);\n          }\n        }\n      }\n    }\n  }\n\n  KJ_IF_SOME(e, end) {\n    if (e <= start) {\n      // Key range is empty.\n      return kj::none;\n    }\n  }\n\n  return CompiledListOptions{\n    .start = kj::mv(start),\n    .end = kj::mv(end),\n    .reverse = reverse,\n    .limit = limit,\n  };\n}\n\njsg::Promise<jsg::JsRef<jsg::JsValue>> DurableObjectStorageOperations::list(\n    jsg::Lock& js, jsg::Optional<ListOptions> maybeOptions) {\n  auto& context = IoContext::current();\n  auto traceContext = context.makeUserTraceSpan(\"durable_object_storage_list\"_kjc);\n  auto [start, end, reverse, limit] = KJ_UNWRAP_OR(compileListOptions(maybeOptions),\n      { return js.resolvedPromise(jsg::JsValue(js.map()).addRef(js)); });\n\n  auto options = configureOptions(kj::mv(maybeOptions).orDefault(ListOptions{}));\n  ActorCacheOps::ReadOptions readOptions = options;\n\n  auto result = reverse\n      ? getCache(OP_LIST).listReverse(kj::mv(start), kj::mv(end), limit, readOptions)\n      : getCache(OP_LIST).list(kj::mv(start), kj::mv(end), limit, readOptions);\n  return context.attachSpans(js,\n      transformCacheResultWithCacheStatus(js, kj::mv(result), options, &listResultsToMap),\n      kj::mv(traceContext));\n}\n\njsg::Promise<void> DurableObjectStorageOperations::put(jsg::Lock& js,\n    kj::OneOf<kj::String, jsg::Dict<jsg::JsValue>> keyOrEntries,\n    jsg::Optional<jsg::JsValue> value,\n    jsg::Optional<PutOptions> maybeOptions,\n    const jsg::TypeHandler<PutOptions>& optionsTypeHandler) {\n  auto& context = IoContext::current();\n  auto traceContext = context.makeUserTraceSpan(\"durable_object_storage_put\"_kjc);\n  // TODO(soon): Add tests of data generated at current versions to ensure we'll\n  // know before releasing any backwards-incompatible serializer changes,\n  // potentially checking the header in addition to the value.\n  auto options = configureOptions(kj::mv(maybeOptions).orDefault(PutOptions{}));\n  KJ_SWITCH_ONEOF(keyOrEntries) {\n    KJ_CASE_ONEOF(k, kj::String) {\n      KJ_IF_SOME(v, value) {\n        return context.attachSpans(js, putOne(js, kj::mv(k), v, options), kj::mv(traceContext));\n      } else {\n        JSG_FAIL_REQUIRE(TypeError, \"put() called with undefined value.\");\n      }\n    }\n    KJ_CASE_ONEOF(o, jsg::Dict<jsg::JsValue>) {\n      KJ_IF_SOME(v, value) {\n        KJ_IF_SOME(opt, optionsTypeHandler.tryUnwrap(js, v)) {\n          // return putMultiple(js, kj::mv(o), configureOptions(kj::mv(opt)));\n          return context.attachSpans(\n              js, putMultiple(js, kj::mv(o), configureOptions(kj::mv(opt))), kj::mv(traceContext));\n        } else {\n          JSG_FAIL_REQUIRE(TypeError,\n              \"put() may only be called with a single key-value pair and optional options as put(key, value, options) or with multiple key-value pairs and optional options as put(entries, options)\");\n        }\n      } else {\n        // return putMultiple(js, kj::mv(o), options);\n        return context.attachSpans(js, putMultiple(js, kj::mv(o), options), kj::mv(traceContext));\n      }\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\njsg::Promise<void> DurableObjectStorageOperations::setAlarm(\n    jsg::Lock& js, kj::Date scheduledTime, jsg::Optional<SetAlarmOptions> maybeOptions) {\n  JSG_REQUIRE(scheduledTime > kj::origin<kj::Date>(), TypeError,\n      \"setAlarm() cannot be called with an alarm time <= 0\");\n\n  auto& context = IoContext::current();\n  auto traceContext = context.makeUserTraceSpan(\"durable_object_storage_setAlarm\"_kjc);\n  // This doesn't check if we have an alarm handler per say. It checks if we have an initialized\n  // (post-ctor) JS durable object with an alarm handler. Notably, this means this won't throw if\n  // `setAlarm` is invoked in the DO ctor even if the DO class does not have an alarm handler. This\n  // is better than throwing even if we do have an alarm handler.\n  context.getActorOrThrow().assertCanSetAlarm();\n\n  auto options = configureOptions(maybeOptions\n                                      .map([](auto& o) {\n    return PutOptions{.allowConcurrency = o.allowConcurrency,\n      .allowUnconfirmed = o.allowUnconfirmed,\n      .noCache = false};\n  }).orDefault(PutOptions{}));\n\n  // We fudge times set in the past to Date.now() to ensure that any one user can't DDOS the alarm\n  // polling system by putting dates far in the past and therefore getting sorted earlier by the index.\n  // This also ensures uniqueness of alarm times (which is required for correctness),\n  // in the situation where customers use a constant date in the past to indicate\n  // they want immediate execution.\n  kj::Date dateNowKjDate = static_cast<int64_t>(dateNow()) * kj::MILLISECONDS + kj::UNIX_EPOCH;\n\n  auto maybeBackpressure = transformMaybeBackpressure(js, options,\n      getCache(OP_PUT_ALARM)\n          .setAlarm(kj::max(scheduledTime, dateNowKjDate), options, context.getCurrentTraceSpan()));\n\n  // setAlarm() is billed as a single write unit.\n  context.addTask(updateStorageWriteUnit(context, currentActorMetrics(), 1));\n\n  return context.attachSpans(js, kj::mv(maybeBackpressure), kj::mv(traceContext));\n}\n\njsg::Promise<void> DurableObjectStorageOperations::putOne(\n    jsg::Lock& js, kj::String key, jsg::JsValue value, const PutOptions& options) {\n\n  kj::Array<byte> buffer = serializeV8Value(js, value);\n\n  auto units = billingUnits(key.size() + buffer.size());\n\n  auto& context = IoContext::current();\n\n  jsg::Promise<void> maybeBackpressure = transformMaybeBackpressure(js, options,\n      getCache(OP_PUT).put(kj::mv(key), kj::mv(buffer), options, context.getCurrentTraceSpan()));\n\n  context.addTask(updateStorageWriteUnit(context, currentActorMetrics(), units));\n  return maybeBackpressure;\n}\n\nkj::OneOf<jsg::Promise<bool>, jsg::Promise<int>> DurableObjectStorageOperations::delete_(\n    jsg::Lock& js,\n    kj::OneOf<kj::String, kj::Array<kj::String>> keys,\n    jsg::Optional<PutOptions> maybeOptions) {\n  auto& context = IoContext::current();\n  auto traceContext = context.makeUserTraceSpan(\"durable_object_storage_delete\"_kjc);\n  auto options = configureOptions(kj::mv(maybeOptions).orDefault(PutOptions{}));\n  KJ_SWITCH_ONEOF(keys) {\n    KJ_CASE_ONEOF(s, kj::String) {\n      return context.attachSpans(js, deleteOne(js, kj::mv(s), options), kj::mv(traceContext));\n    }\n    KJ_CASE_ONEOF(a, kj::Array<kj::String>) {\n      return context.attachSpans(js, deleteMultiple(js, kj::mv(a), options), kj::mv(traceContext));\n    }\n  }\n  KJ_UNREACHABLE\n}\n\njsg::Promise<void> DurableObjectStorageOperations::deleteAlarm(\n    jsg::Lock& js, jsg::Optional<SetAlarmOptions> maybeOptions) {\n  auto& context = IoContext::current();\n  auto traceContext = context.makeUserTraceSpan(\"durable_object_storage_deleteAlarm\"_kjc);\n  // Even if we do not have an alarm handler, we might once have had one. It's fine to remove that\n  // alarm or noop on the absence of one.\n  auto options = configureOptions(maybeOptions\n                                      .map([](auto& o) {\n    return PutOptions{.allowConcurrency = o.allowConcurrency,\n      .allowUnconfirmed = o.allowUnconfirmed,\n      .noCache = false};\n  }).orDefault(PutOptions{}));\n\n  return context.attachSpans(js,\n      transformMaybeBackpressure(js, options,\n          getCache(OP_DELETE_ALARM).setAlarm(kj::none, options, context.getCurrentTraceSpan())),\n      kj::mv(traceContext));\n}\n\njsg::Promise<void> DurableObjectStorage::deleteAll(\n    jsg::Lock& js, jsg::Optional<PutOptions> maybeOptions) {\n  auto& context = IoContext::current();\n  auto traceContext = context.makeUserTraceSpan(\"durable_object_storage_deleteAll\"_kjc);\n  auto options = configureOptions(kj::mv(maybeOptions).orDefault(PutOptions{}));\n\n  DeleteAllOptions deleteAllOptions{\n    .deleteAlarm = FeatureFlags::get(js).getDeleteAllDeletesAlarm(),\n  };\n  auto deleteAll = cache->deleteAll(options, context.getCurrentTraceSpan(), deleteAllOptions);\n\n  context.addTask(updateStorageDeletes(context, currentActorMetrics(), kj::mv(deleteAll.count)));\n\n  return context.attachSpans(js,\n      transformMaybeBackpressure(js, options, kj::mv(deleteAll.backpressure)),\n      kj::mv(traceContext));\n}\n\nvoid DurableObjectTransaction::deleteAll() {\n  JSG_FAIL_REQUIRE(Error, \"Cannot call deleteAll() within a transaction\");\n}\n\njsg::Promise<bool> DurableObjectStorageOperations::deleteOne(\n    jsg::Lock& js, kj::String key, const PutOptions& options) {\n  auto& context = IoContext::current();\n\n  return transformCacheResult(js,\n      getCache(OP_DELETE).delete_(kj::mv(key), options, context.getCurrentTraceSpan()), options,\n      [](jsg::Lock&, bool value) {\n    currentActorMetrics().addStorageDeletes(1);\n    return value;\n  });\n}\n\njsg::Promise<jsg::JsRef<jsg::JsValue>> DurableObjectStorageOperations::getMultiple(\n    jsg::Lock& js, kj::Array<kj::String> keys, const GetOptions& options) {\n  auto numKeys = keys.size();\n\n  return transformCacheResult(\n      js, getCache(OP_GET).get(kj::mv(keys), options), options, getMultipleResultsToMap(numKeys));\n}\n\njsg::Promise<void> DurableObjectStorageOperations::putMultiple(\n    jsg::Lock& js, jsg::Dict<jsg::JsValue> entries, const PutOptions& options) {\n  kj::Vector<ActorCacheOps::KeyValuePair> kvs(entries.fields.size());\n\n  uint32_t units = 0;\n  for (auto& field: entries.fields) {\n    if (field.value.isUndefined()) continue;\n    // We silently drop fields with value=undefined in putMultiple. There aren't many good options here, as\n    // deleting an undefined field is confusing, throwing could break otherwise working code, and\n    // a stray undefined here or there is probably closer to what the user desires.\n\n    kj::Array<byte> buffer = serializeV8Value(js, field.value);\n\n    units += billingUnits(field.name.size() + buffer.size());\n\n    kvs.add(ActorCacheOps::KeyValuePair{kj::mv(field.name), kj::mv(buffer)});\n  }\n\n  auto& context = IoContext::current();\n\n  jsg::Promise<void> maybeBackpressure = transformMaybeBackpressure(js, options,\n      getCache(OP_PUT).put(kvs.releaseAsArray(), options, context.getCurrentTraceSpan()));\n\n  context.addTask(updateStorageWriteUnit(context, currentActorMetrics(), units));\n\n  return maybeBackpressure;\n}\n\njsg::Promise<int> DurableObjectStorageOperations::deleteMultiple(\n    jsg::Lock& js, kj::Array<kj::String> keys, const PutOptions& options) {\n  auto numKeys = keys.size();\n\n  auto& context = IoContext::current();\n\n  return transformCacheResult(js,\n      getCache(OP_DELETE).delete_(kj::mv(keys), options, context.getCurrentTraceSpan()), options,\n      [numKeys](jsg::Lock&, uint count) -> int {\n    currentActorMetrics().addStorageDeletes(numKeys);\n    return count;\n  });\n}\n\nActorCacheOps& DurableObjectStorage::getCache(OpName op) {\n  return *cache;\n}\n\njsg::Promise<jsg::JsRef<jsg::JsValue>> DurableObjectStorage::transaction(jsg::Lock& js,\n    jsg::Function<jsg::Promise<jsg::JsRef<jsg::JsValue>>(jsg::Ref<DurableObjectTransaction>)>\n        callback,\n    jsg::Optional<TransactionOptions> options) {\n  auto& context = IoContext::current();\n  auto traceContext = context.makeUserTraceSpan(\"durable_object_storage_transaction\"_kjc);\n\n  struct TxnResult {\n    jsg::JsRef<jsg::JsValue> value;\n    bool isError;\n  };\n\n  return context.attachSpans(js,\n      context\n          .blockConcurrencyWhile(js,\n              [callback = kj::mv(callback), &context, &cache = *cache](\n                  jsg::Lock& js) mutable -> jsg::Promise<TxnResult> {\n    // Note that the call to `startTransaction()` is when the SQLite-backed implementation will\n    // actually invoke `BEGIN TRANSACTION`, so it's important that we're inside the\n    // blockConcurrencyWhile block before that point so we don't accidentally catch some other\n    // asynchronous event in our transaction.\n    //\n    // For the ActorCache-based implementation, it doesn't matter when we call `startTransaction()`\n    // as the method merely allocates an object and returns it with no side effects.\n    auto txn = js.alloc<DurableObjectTransaction>(context.addObject(cache.startTransaction()));\n\n    return js.resolvedPromise(txn.addRef())\n        .then(js, kj::mv(callback))\n        .then(js, [txn = txn.addRef()](jsg::Lock& js, jsg::JsRef<jsg::JsValue> value) mutable {\n      // In correct usage, `context` should not have changed here, particularly because we're in\n      // a critical section so it should have been impossible for any other context to receive\n      // control. However, depending on all that is a bit precarious. jsg::Promise::then() itself\n      // does NOT guarantee it runs in the same context (the application could have returned a\n      // custom Promise and then resolved in from some other context). So let's be safe and grab\n      // IoContext::current() again here, rather than capture it in the lambda.\n      auto& context = IoContext::current();\n      return context.awaitIoWithInputLock(js, txn->maybeCommit(),\n          [value = kj::mv(value)](jsg::Lock&) mutable { return TxnResult{kj::mv(value), false}; });\n    }, [txn = txn.addRef()](jsg::Lock& js, jsg::Value exception) mutable {\n      // The transaction callback threw an exception. We don't actually want to reset the object,\n      // we only want to roll back the transaction and propagate the exception. So, we carefully\n      // pack the exception away into a value.\n      txn->maybeRollback();\n      return js.resolvedPromise(TxnResult{\n        // TODO(cleanup): Simplify this once exception is passed using jsg::JsRef instead\n        // of jsg::V8Ref\n        jsg::JsValue(exception.getHandle(js)).addRef(js), true});\n    });\n  })\n          .then(js,\n              [](jsg::Lock& js, TxnResult result) -> jsg::JsRef<jsg::JsValue> {\n    if (result.isError) {\n      js.throwException(result.value.getHandle(js));\n    } else {\n      return kj::mv(result.value);\n    }\n  }),\n      kj::mv(traceContext));\n}\n\njsg::JsRef<jsg::JsValue> DurableObjectStorage::transactionSync(\n    jsg::Lock& js, jsg::Function<jsg::JsRef<jsg::JsValue>()> callback) {\n  KJ_IF_SOME(sqlite, cache->getSqliteDatabase()) {\n    // SAVEPOINT is a readonly statement, but we need to trigger an outer TRANSACTION\n    sqlite.notifyWrite();\n\n    uint depth = transactionSyncDepth++;\n    KJ_DEFER(--transactionSyncDepth);\n\n    // TODO(perf): SQLite actually allows multiple savepoints with the same name. The name refers\n    //   to the most-recent of these savepoints. This means we don't actually have to append the\n    //   depth to each savepoint name like I originally thought. We should refactor this -- and use\n    //   prepared statements.\n\n    sqlite.run(\n        {.regulator = SqliteDatabase::TRUSTED}, kj::str(\"SAVEPOINT _cf_sync_savepoint_\", depth));\n    return js.tryCatch([&]() {\n      auto result = callback(js);\n\n      // If a critical error forced an automatic rollback, we throw an exception to convey failure\n      // to the caller of transactionSync(), even if the callback did not throw.\n      JSG_REQUIRE(!sqlite.observedCriticalError(), Error,\n          \"Cannot commit transaction due to an earlier SQL critical error\");\n\n      sqlite.run(\n          {.regulator = SqliteDatabase::TRUSTED}, kj::str(\"RELEASE _cf_sync_savepoint_\", depth));\n      return kj::mv(result);\n    }, [&](jsg::Value exception) -> jsg::JsRef<jsg::JsValue> {\n      // If a critical error forced an automatic rollback, we skip the rollback and release\n      // attempt, because savepoints should already be released.\n      if (!sqlite.observedCriticalError()) {\n        sqlite.run({.regulator = SqliteDatabase::TRUSTED},\n            kj::str(\"ROLLBACK TO _cf_sync_savepoint_\", depth));\n        sqlite.run(\n            {.regulator = SqliteDatabase::TRUSTED}, kj::str(\"RELEASE _cf_sync_savepoint_\", depth));\n      }\n      js.throwException(kj::mv(exception));\n    });\n  } else {\n    JSG_FAIL_REQUIRE(Error, \"Durable Object is not backed by SQL.\");\n  }\n}\n\njsg::Promise<void> DurableObjectStorage::sync(jsg::Lock& js) {\n  auto& context = IoContext::current();\n  auto traceContext = context.makeUserTraceSpan(\"durable_object_storage_sync\"_kjc);\n  KJ_IF_SOME(p, cache->onNoPendingFlush(traceContext.getInternalSpanParent())) {\n    // Note that we're not actually flushing since that will happen anyway once we go async. We're\n    // merely checking if we have any pending or in-flight operations, and providing a promise that\n    // resolves when they succeed. This promise only covers operations that were scheduled before\n    // this method was invoked. If the cache has to flush again later from future operations, this\n    // promise will resolve before they complete. If this promise were to reject, then the actor's\n    // output gate will be broken first and the isolate will not resume synchronous execution.\n    return context.attachSpans(js, context.awaitIo(js, kj::mv(p)), kj::mv(traceContext));\n  } else {\n    return js.resolvedPromise();\n  }\n}\n\nSqliteDatabase& DurableObjectStorage::getSqliteDb(jsg::Lock& js) {\n  KJ_IF_SOME(db, cache->getSqliteDatabase()) {\n    // Actor is SQLite-backed but let's make sure SQL is configured to be enabled.\n    if (enableSql) {\n      return db;\n    } else if (FeatureFlags::get(js).getWorkerdExperimental()) {\n      // For backwards-compatibility, if the `experimental` compat flag is on, enable SQL. This is\n      // deprecated, though, so warn in this case.\n\n      // TODO(soon): Uncomment this warning after the D1 simulator has been updated to use\n      //   `enableSql`. Otherwise, people doing local dev against D1 may see the warning\n      //   spuriously.\n\n      // IoContext::current().logWarningOnce(\n      //     \"Enabling SQL API based on the 'experimental' flag, but this will stop working soon. \"\n      //     \"Instead, please set `enableSql = true` in your workerd config for the DO namespace. \"\n      //     \"If using wrangler, under `[[migrations]]` in wrangler.toml, change `new_classes` to \"\n      //     \"`new_sqlite_classes`.\");\n\n      return db;\n    } else {\n      // We're presumably running local workerd, which always uses SQLite for DO storage, but we're\n      // trying to simulate a non-SQLite DO namespace for testing purposes.\n      JSG_FAIL_REQUIRE(Error,\n          \"SQL is not enabled for this Durable Object class. To enable it, change \"\n          \"`new_classes` to `new_sqlite_classes` within the 'migrations' field in \"\n          \"your wrangler.jsonc or wrangler.toml file. If using workerd directly,\"\n          \"set `enableSql = true` in your workerd config for the class. Note \"\n          \"that this change cannot be made after the class is \"\n          \"already deployed to production.\");\n    }\n  } else {\n    // We're in production (not local workerd) and this DO namespace is not backed by SQLite.\n    JSG_FAIL_REQUIRE(Error,\n        \"This Durable Object is not backed by SQLite storage, so the SQL API is not available. \"\n        \"SQL can be enabled on a new Durable Object class by using the `new_sqlite_classes` \"\n        \"instead of `new_classes` under `[[migrations]]` in your wrangler.toml, but an \"\n        \"already-deployed class cannot be converted to SQLite (except by deleting the existing \"\n        \"data).\");\n  }\n}\n\nSqliteKv& DurableObjectStorage::getSqliteKv(jsg::Lock& js) {\n  KJ_IF_SOME(kv, cache->getSqliteKv()) {\n    // Actor is SQLite-backed but let's make sure SQL is configured to be enabled.\n    if (enableSql) {\n      return kv;\n    } else {\n      // We're presumably running local workerd, which always uses SQLite for DO storage, but we're\n      // trying to simulate a non-SQLite DO namespace for testing purposes.\n      JSG_FAIL_REQUIRE(Error,\n          \"The storage.kv (synchronous KV) API is only available for SQLite-backed Durable \"\n          \"Objects, but this object's namespace is not declared to use SQLite. You can use \"\n          \"the older, asyncronous interface via methods of `storage` itself (e.g. \"\n          \"`storage.get()`). Alternatively, to enable SQLite, change `new_classes` to \"\n          \"`new_sqlite_classes` within the 'migrations' field in your wrangler.jsonc or \"\n          \"wrangler.toml file. If using workerd directly, set `enableSql = true` in your workerd \"\n          \"config for the class. Note that this change cannot be made after the class is \"\n          \"already deployed to production.\");\n    }\n  } else {\n    // We're in production (not local workerd) and this DO namespace is not backed by SQLite.\n    JSG_FAIL_REQUIRE(Error,\n        \"The storage.kv (synchronous KV) API is only available for SQLite-backed Durable \"\n        \"Objects, but this object's namespace is not declared to use SQLite. You can use \"\n        \"the older, asyncronous interface via methods of `storage` itself (e.g. \"\n        \"`storage.get()`). SQLite can be enabled on a new Durable Object class by using the \"\n        \"`new_sqlite_classes` instead of `new_classes` under `migrations` in your \"\n        \"wrangler.jsonc or wrangler.toml, but an already-deployed class cannot be converted \"\n        \"to SQLite (except by deleting the existing data).\");\n  }\n}\n\njsg::Ref<SqlStorage> DurableObjectStorage::getSql(jsg::Lock& js) {\n  return js.alloc<SqlStorage>(JSG_THIS);\n}\n\njsg::Ref<SyncKvStorage> DurableObjectStorage::getKv(jsg::Lock& js) {\n  return js.alloc<SyncKvStorage>(JSG_THIS);\n}\n\nkj::Promise<kj::String> DurableObjectStorage::getCurrentBookmark() {\n  auto& context = IoContext::current();\n  auto traceContext = context.makeUserTraceSpan(\"durable_object_storage_getCurrentBookmark\"_kjc);\n\n  return cache->getCurrentBookmark(traceContext.getInternalSpanParent())\n      .attach(kj::mv(traceContext));\n}\n\nkj::Promise<kj::String> DurableObjectStorage::getBookmarkForTime(kj::Date timestamp) {\n  return cache->getBookmarkForTime(timestamp);\n}\n\nkj::Promise<kj::String> DurableObjectStorage::onNextSessionRestoreBookmark(kj::String bookmark) {\n  return cache->onNextSessionRestoreBookmark(bookmark);\n}\n\nkj::Promise<void> DurableObjectStorage::waitForBookmark(kj::String bookmark) {\n  auto& context = IoContext::current();\n  auto traceContext = context.makeUserTraceSpan(\"durable_object_storage_waitForBookmark\"_kjc);\n\n  return cache->waitForBookmark(bookmark, traceContext.getInternalSpanParent())\n      .attach(kj::mv(traceContext));\n}\n\nvoid DurableObjectStorage::ensureReplicas() {\n  if (maybePrimary != kj::none) {\n    KJ_FAIL_ASSERT(\"replica Durable Objects cannot call ensureReplicas().\");\n  }\n  return cache->ensureReplicas();\n}\n\nvoid DurableObjectStorage::disableReplicas() {\n  if (maybePrimary != kj::none) {\n    KJ_FAIL_ASSERT(\"replica Durable Objects cannot call disableReplicas().\");\n  }\n  return cache->disableReplicas();\n}\n\njsg::Optional<jsg::Ref<DurableObject>> DurableObjectStorage::getPrimary(jsg::Lock& js) {\n  KJ_IF_SOME(primary, maybePrimary) {\n    return primary.addRef();\n  }\n  return kj::none;\n}\n\nActorCacheOps& DurableObjectTransaction::getCache(OpName op) {\n  JSG_REQUIRE(!rolledBack, Error, kj::str(\"Cannot \", op, \" on rolled back transaction\"));\n  auto& result = *JSG_REQUIRE_NONNULL(cacheTxn, Error,\n      kj::str(\"Cannot call \", op,\n          \" on transaction that has already committed: did you move `txn` outside of the closure?\"));\n  return result;\n}\n\nvoid DurableObjectTransaction::rollback() {\n  if (rolledBack) return;  // allow multiple calls to rollback()\n  getCache(OP_ROLLBACK);   // just for the checks\n  KJ_IF_SOME(t, cacheTxn) {\n    auto prom = t->rollback();\n    IoContext::current().addWaitUntil(kj::mv(prom).attach(kj::mv(cacheTxn)));\n    cacheTxn = kj::none;\n  }\n  rolledBack = true;\n}\n\nkj::Promise<void> DurableObjectTransaction::maybeCommit() {\n  // cacheTxn is null if rollback() was called, in which case we don't want to commit anything.\n  KJ_IF_SOME(t, cacheTxn) {\n    auto maybePromise = t->commit();\n    cacheTxn = kj::none;\n    KJ_IF_SOME(promise, maybePromise) {\n      return kj::mv(promise);\n    }\n  }\n  return kj::READY_NOW;\n}\n\nvoid DurableObjectTransaction::maybeRollback() {\n  cacheTxn = kj::none;\n  rolledBack = true;\n}\n\nnamespace {\n\n// Maximum length of a facet name, in characters.\nconstexpr size_t MAX_FACET_NAME_LENGTH = 256;\n\n// Maximum depth of the facet tree, including the root Durable Object. Root is at depth 0, so\n// the deepest allowed facet is at depth MAX_FACET_TREE_DEPTH - 1.\nconstexpr uint MAX_FACET_TREE_DEPTH = 4;\n\ninline void requireValidFacetName(kj::StringPtr name) {\n  JSG_REQUIRE(name.size() <= MAX_FACET_NAME_LENGTH, TypeError, \"Facet name is too long (max \",\n      MAX_FACET_NAME_LENGTH, \" characters).\");\n}\n\n}  // namespace\n\nclass FacetOutgoingFactory final: public Fetcher::OutgoingFactory {\n public:\n  FacetOutgoingFactory(Worker::Actor::FacetManager& facetManager,\n      kj::String name,\n      kj::Function<kj::Promise<Worker::Actor::FacetManager::StartInfo>()> getStartInfo)\n      : facetManager(facetManager),\n        name(kj::mv(name)),\n        getStartInfo(kj::mv(getStartInfo)) {}\n\n  kj::Own<WorkerInterface> newSingleUseClient(kj::Maybe<kj::String> cfStr) override {\n    auto& context = IoContext::current();\n\n    return context.getMetrics().wrapActorSubrequestClient(context.getSubrequest(\n        [&](TraceContext& tracing, IoChannelFactory& ioChannelFactory) {\n      tracing.setTag(\"facet_name\"_kjc, name.asPtr());\n\n      // Lazily initialize actorChannel\n      if (actorChannel == kj::none) {\n        actorChannel = facetManager.getFacet(name, kj::mv(getStartInfo));\n      }\n\n      return KJ_REQUIRE_NONNULL(actorChannel)\n          ->startRequest(\n              {.cfBlobJson = kj::mv(cfStr), .parentSpan = tracing.getInternalSpanParent()});\n    },\n        {.inHouse = true,\n          .wrapMetrics = true,\n          .operationName = kj::ConstString(\"facet_subrequest\"_kjc)}));\n  }\n\n private:\n  Worker::Actor::FacetManager& facetManager;\n  kj::String name;\n\n  // This is moved away when `actorChannel` is initialized.\n  kj::Function<kj::Promise<Worker::Actor::FacetManager::StartInfo>()> getStartInfo;\n\n  kj::Maybe<kj::Own<IoChannelFactory::ActorChannel>> actorChannel;\n};\n\njsg::Ref<Fetcher> DurableObjectFacets::get(jsg::Lock& js,\n    kj::String name,\n    jsg::Function<jsg::Promise<StartupOptions>()> getStartupOptions) {\n  requireValidFacetName(name);\n\n  auto& fm = getFacetManager();\n\n  JSG_REQUIRE(fm.getDepth() + 1 < MAX_FACET_TREE_DEPTH, Error,\n      \"Facet nesting depth limit exceeded. The maximum depth including the root Durable Object is \",\n      MAX_FACET_TREE_DEPTH, \".\");\n\n  auto& ioCtx = IoContext::current();\n\n  kj::Function<kj::Promise<Worker::Actor::FacetManager::StartInfo>()> getStartInfo =\n      ioCtx.makeReentryCallback(\n          [&ioCtx, getStartupOptions = kj::mv(getStartupOptions)](jsg::Lock& js) mutable {\n    return getStartupOptions(js).then(js, [&ioCtx](jsg::Lock& js, StartupOptions options) {\n      Worker::Actor::Id id;\n      KJ_IF_SOME(i, options.id) {\n        KJ_SWITCH_ONEOF(i) {\n          KJ_CASE_ONEOF(doId, jsg::Ref<DurableObjectId>) {\n            id = doId->getInner().clone();\n          }\n          KJ_CASE_ONEOF(strId, kj::String) {\n            id = kj::mv(strId);\n          }\n        }\n      } else {\n        // Child inherits parent ID.\n        id = ioCtx.getActorOrThrow().cloneId();\n      }\n\n      DurableObjectClass& actorClass = [&]() -> DurableObjectClass& {\n        KJ_SWITCH_ONEOF(options.$class) {\n          KJ_CASE_ONEOF(bare, jsg::Ref<DurableObjectClass>) {\n            return *bare.get();\n          }\n          KJ_CASE_ONEOF(loopback, jsg::Ref<LoopbackDurableObjectNamespace>) {\n            return loopback->getClass();\n          }\n          KJ_CASE_ONEOF(loopback, jsg::Ref<LoopbackColoLocalActorNamespace>) {\n            return loopback->getClass();\n          }\n        }\n        KJ_UNREACHABLE;\n      }();\n\n      return Worker::Actor::FacetManager::StartInfo{\n        .actorClass = actorClass.getChannel(ioCtx),\n        .id = kj::mv(id),\n      };\n    });\n  });\n\n  kj::Own<Fetcher::OutgoingFactory> factory =\n      kj::heap<FacetOutgoingFactory>(fm, kj::mv(name), kj::mv(getStartInfo));\n\n  auto requiresHost = FeatureFlags::get(js).getDurableObjectFetchRequiresSchemeAuthority()\n      ? Fetcher::RequiresHostAndProtocol::YES\n      : Fetcher::RequiresHostAndProtocol::NO;\n\n  // We return a plain Fetcher, not a DurableObject, because we don't want the stub to have\n  // `name` or `id` properties.\n  return js.alloc<Fetcher>(ioCtx.addObject(kj::mv(factory)), requiresHost, true /* isInHouse */);\n}\n\nvoid DurableObjectFacets::abort(jsg::Lock& js, kj::String name, jsg::JsValue reason) {\n  requireValidFacetName(name);\n  getFacetManager().abortFacet(name, js.exceptionToKj(reason));\n}\n\nvoid DurableObjectFacets::delete_(jsg::Lock& js, kj::String name) {\n  requireValidFacetName(name);\n  getFacetManager().deleteFacet(name);\n}\n\nActorState::ActorState(Worker::Actor::Id actorId,\n    kj::Maybe<jsg::JsRef<jsg::JsValue>> transient,\n    kj::Maybe<jsg::Ref<DurableObjectStorage>> persistent)\n    : id(kj::mv(actorId)),\n      transient(kj::mv(transient)),\n      persistent(kj::mv(persistent)) {}\n\nkj::OneOf<jsg::Ref<DurableObjectId>, kj::StringPtr> ActorState::getId(jsg::Lock& js) {\n  KJ_SWITCH_ONEOF(id) {\n    KJ_CASE_ONEOF(coloLocalId, kj::String) {\n      return coloLocalId.asPtr();\n    }\n    KJ_CASE_ONEOF(globalId, kj::Own<ActorIdFactory::ActorId>) {\n      return js.alloc<DurableObjectId>(globalId->clone());\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nDurableObjectState::DurableObjectState(jsg::Lock& js,\n    Worker::Actor::Id actorId,\n    jsg::JsValue exports,\n    jsg::JsValue props,\n    kj::Maybe<jsg::Ref<DurableObjectStorage>> storage,\n    kj::Maybe<rpc::Container::Client> container,\n    bool containerRunning,\n    kj::Maybe<Worker::Actor::FacetManager&> facetManager,\n    kj::Maybe<ActorVersion> version)\n    : id(kj::mv(actorId)),\n      exports(js, exports),\n      props(js, props),\n      storage(kj::mv(storage)),\n      container(container.map([&](rpc::Container::Client& cap) {\n        return js.alloc<Container>(kj::mv(cap), containerRunning);\n      })),\n      facetManager(facetManager.map(\n          [](Worker::Actor::FacetManager& ref) { return IoContext::current().addObject(ref); })),\n      version(kj::mv(version)) {}\n\nvoid DurableObjectState::waitUntil(kj::Promise<void> promise) {\n  IoContext::current().addWaitUntil(kj::mv(promise));\n}\n\nkj::OneOf<jsg::Ref<DurableObjectId>, kj::StringPtr> DurableObjectState::getId(jsg::Lock& js) {\n  KJ_SWITCH_ONEOF(id) {\n    KJ_CASE_ONEOF(coloLocalId, kj::String) {\n      return coloLocalId.asPtr();\n    }\n    KJ_CASE_ONEOF(globalId, kj::Own<ActorIdFactory::ActorId>) {\n      return js.alloc<DurableObjectId>(globalId->clone());\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\njsg::Promise<jsg::JsRef<jsg::JsValue>> DurableObjectState::blockConcurrencyWhile(\n    jsg::Lock& js, jsg::Function<jsg::Promise<jsg::JsRef<jsg::JsValue>>()> callback) {\n  return IoContext::current().blockConcurrencyWhile(js, kj::mv(callback));\n}\n\nvoid DurableObjectState::abort(jsg::Lock& js, jsg::Optional<kj::String> reason) {\n  kj::String description = kj::mv(reason)\n                               .map([](kj::String&& text) {\n    return kj::str(\"broken.outputGateBroken; jsg.Error: \", text);\n  }).orDefault([]() {\n    return kj::str(\"broken.outputGateBroken; jsg.Error: Application called abort() to reset \"\n                   \"Durable Object.\");\n  });\n\n  kj::Exception error(kj::Exception::Type::FAILED, __FILE__, __LINE__, kj::mv(description));\n\n  KJ_IF_SOME(s, storage) {\n    // Make sure we _synchronously_ break storage so that there's no chance our promise fulfilling\n    // will race against the output gate, possibly allowing writes to complete before being\n    // canceled.\n    s.get()->getActorCacheInterface().shutdown(error);\n  }\n\n  IoContext::current().abort(kj::mv(error));\n  js.terminateExecutionNow();\n}\n\nWorker::Actor::HibernationManager& DurableObjectState::maybeInitHibernationManager(\n    Worker::Actor& actor) {\n  if (actor.getHibernationManager() == kj::none) {\n    // If there's no hibernation manager created yet, we should create one.\n    actor.setHibernationManager(kj::refcounted<HibernationManagerImpl>(\n        actor.getLoopback(), KJ_REQUIRE_NONNULL(actor.getHibernationEventType())));\n  }\n  return KJ_REQUIRE_NONNULL(actor.getHibernationManager());\n}\n\nvoid DurableObjectState::acceptWebSocket(\n    jsg::Ref<WebSocket> ws, jsg::Optional<kj::Array<kj::String>> tags) {\n  JSG_ASSERT(!ws->isAccepted(), Error,\n      \"Cannot call `acceptWebSocket()` if the WebSocket was already accepted via `accept()`\");\n  JSG_ASSERT(ws->peerIsAwaitingCoupling(), Error,\n      \"Cannot call `acceptWebSocket()` on this WebSocket because its pair has already been \"\n      \"accepted or used in a Response.\");\n\n  // We need to get a HibernationManager to give the websocket to.\n  auto& a = KJ_REQUIRE_NONNULL(IoContext::current().getActor());\n  // HibernationManager's acceptWebSocket() will throw if the websocket is in an incompatible state.\n  // Note that not providing a tag is equivalent to providing an empty tag array.\n  // Any duplicate tags will be ignored.\n  kj::Array<kj::String> distinctTags = [&]() -> kj::Array<kj::String> {\n    KJ_IF_SOME(t, tags) {\n      kj::HashSet<kj::String> seen;\n      size_t distinctTagCount = 0;\n      for (auto tag = t.begin(); tag < t.end(); tag++) {\n        JSG_REQUIRE(distinctTagCount < MAX_TAGS_PER_CONNECTION, Error,\n            \"a Hibernatable WebSocket cannot have more than \", MAX_TAGS_PER_CONNECTION, \" tags\");\n        JSG_REQUIRE(tag->size() <= MAX_TAG_LENGTH, Error, \"\\\"\", *tag, \"\\\" \",\n            \"is longer than the max tag length (\", MAX_TAG_LENGTH, \" characters).\");\n        if (!seen.contains(*tag)) {\n          seen.insert(kj::mv(*tag));\n          distinctTagCount++;\n        }\n      }\n\n      return KJ_MAP(tag, seen) { return kj::mv(tag); };\n    }\n    return kj::Array<kj::String>();\n  }();\n  maybeInitHibernationManager(a).acceptWebSocket(kj::mv(ws), distinctTags);\n}\n\nkj::Array<jsg::Ref<api::WebSocket>> DurableObjectState::getWebSockets(\n    jsg::Lock& js, jsg::Optional<kj::String> tag) {\n  auto& a = KJ_REQUIRE_NONNULL(IoContext::current().getActor());\n  KJ_IF_SOME(manager, a.getHibernationManager()) {\n    return manager.getWebSockets(js, tag.map([](kj::StringPtr t) { return t; })).releaseAsArray();\n  }\n  return kj::Array<jsg::Ref<api::WebSocket>>();\n}\n\nvoid DurableObjectState::setWebSocketAutoResponse(\n    jsg::Optional<jsg::Ref<WebSocketRequestResponsePair>> maybeReqResp) {\n  auto& a = KJ_REQUIRE_NONNULL(IoContext::current().getActor());\n\n  if (maybeReqResp == kj::none) {\n    // If there's no request/response pair, we unset any current set auto response configuration.\n    KJ_IF_SOME(manager, a.getHibernationManager()) {\n      // If there's no hibernation manager created yet, there's nothing to do here.\n      manager.setWebSocketAutoResponse(kj::none, kj::none);\n    }\n    return;\n  }\n\n  auto reqResp = KJ_REQUIRE_NONNULL(kj::mv(maybeReqResp));\n  auto maxRequestOrResponseSize = 2048;\n\n  JSG_REQUIRE(reqResp->getRequest().size() <= maxRequestOrResponseSize, RangeError,\n      kj::str(\"Request cannot be larger than \", maxRequestOrResponseSize, \" bytes. \",\n          \"A request of size \", reqResp->getRequest().size(), \" was provided.\"));\n\n  JSG_REQUIRE(reqResp->getResponse().size() <= maxRequestOrResponseSize, RangeError,\n      kj::str(\"Response cannot be larger than \", maxRequestOrResponseSize, \" bytes. \",\n          \"A response of size \", reqResp->getResponse().size(), \" was provided.\"));\n\n  maybeInitHibernationManager(a).setWebSocketAutoResponse(\n      reqResp->getRequest(), reqResp->getResponse());\n}\n\nkj::Maybe<jsg::Ref<api::WebSocketRequestResponsePair>> DurableObjectState::getWebSocketAutoResponse(\n    jsg::Lock& js) {\n  auto& a = KJ_REQUIRE_NONNULL(IoContext::current().getActor());\n  KJ_IF_SOME(manager, a.getHibernationManager()) {\n    // If there's no hibernation manager created yet, there's nothing to do here.\n    return manager.getWebSocketAutoResponse(js);\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::Date> DurableObjectState::getWebSocketAutoResponseTimestamp(jsg::Ref<WebSocket> ws) {\n  return ws->getAutoResponseTimestamp();\n}\n\nvoid DurableObjectState::setHibernatableWebSocketEventTimeout(jsg::Optional<uint32_t> timeoutMs) {\n  auto& a = KJ_REQUIRE_NONNULL(IoContext::current().getActor());\n\n  // Setting a timeout = 0ms or an empty value will unset any currently set event timeout.\n  // If there's no hibernation manager instantiated, we can skip the event timeout unsetting.\n  if (timeoutMs == kj::none || KJ_REQUIRE_NONNULL(timeoutMs) == 0) {\n    KJ_IF_SOME(hibernationManager, a.getHibernationManager()) {\n      hibernationManager.setEventTimeout(kj::none);\n    }\n    return;\n  }\n\n  auto t = timeoutMs.orDefault(static_cast<uint32_t>(0));\n\n  // We want to limit the duration of an event to a maximum of 7 days (604800 * 1000 millis).\n  JSG_REQUIRE(t <= 604800 * 1000, Error, \"Event timeout should not exceed 604800000 ms.\");\n\n  maybeInitHibernationManager(a).setEventTimeout(t);\n}\n\nkj::Maybe<uint32_t> DurableObjectState::getHibernatableWebSocketEventTimeout() {\n  KJ_IF_SOME(a, IoContext::current().getActor()) {\n    KJ_IF_SOME(manager, a.getHibernationManager()) {\n      return manager.getEventTimeout();\n    }\n  }\n  return kj::none;\n}\n\nkj::Array<kj::StringPtr> DurableObjectState::getTags(jsg::Lock& js, jsg::Ref<api::WebSocket> ws) {\n  return ws->getHibernatableTags();\n}\n\nkj::Array<kj::byte> serializeV8Value(jsg::Lock& js, const jsg::JsValue& value) {\n  jsg::Serializer serializer(js,\n      jsg::Serializer::Options{\n        .version = 15,\n        .omitHeader = false,\n      });\n  serializer.write(js, value);\n  auto released = serializer.release();\n  return kj::mv(released.data);\n}\n\njsg::JsValue deserializeV8Value(\n    jsg::Lock& js, kj::ArrayPtr<const char> key, kj::ArrayPtr<const kj::byte> buf) {\n\n  KJ_ASSERT(buf.size() > 0, \"unexpectedly empty value buffer\", key);\n  try {\n    // The js.tryCatch will handle the normal exception path. We wrap this in an\n    // additional try/catch in case the js.tryCatch hits an exception that is\n    // terminal for the isolate, causing exception to be rethrown, in which case\n    // we throw a kj::Exception wrapping a jsg.Error.\n    return js.tryCatch([&]() -> jsg::JsValue {\n      jsg::Deserializer::Options options{};\n      if (buf[0] != 0xFF) {\n        // When Durable Objects was first released, it did not properly write headers when serializing\n        // to storage. If we find that the header is missing (as indicated by the first byte not being\n        // 0xFF), it's safe to assume that the data was written at the only serialization version we\n        // used during that early time period, so we explicitly set that version here.\n        options.version = 13;\n        options.readHeader = false;\n      }\n\n      jsg::Deserializer deserializer(js, buf, kj::none, kj::none, options);\n\n      return deserializer.readValue(js);\n    }, [&](jsg::Value&& exception) mutable -> jsg::JsValue {\n      // If we do hit a deserialization error, we log information that will be helpful in\n      // understanding the problem but that won't leak too much about the customer's data. We\n      // include the key (to help find the data in the database if it hasn't been deleted), the\n      // length of the value, and the first three bytes of the value (which is just the v8-internal\n      // version header and the tag that indicates the type of the value, but not its contents).\n      kj::String actorId = getCurrentActorId().orDefault([]() { return kj::String(); });\n      KJ_FAIL_ASSERT(\"actor storage deserialization failed\", \"failed to deserialize stored value\",\n          actorId, exception.getHandle(js), key, buf.size(),\n          buf.first(std::min(static_cast<size_t>(3), buf.size())));\n    });\n  } catch (jsg::JsExceptionThrown&) {\n    // We can occasionally hit an isolate termination here -- we prefix the error with jsg to avoid\n    // counting it against our internal storage error metrics but also throw a KJ exception rather\n    // than a jsExceptionThrown error to avoid confusing the normal termination handling code.\n    // We don't expect users to ever actually see this error.\n    JSG_FAIL_REQUIRE(Error,\n        \"isolate terminated while deserializing value from Durable Object \"\n        \"storage; contact us if you're wondering why you're seeing this\");\n  }\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/actor-state.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// APIs that an Actor (Durable Object) uses to access its own state.\n//\n// See actor.h for APIs used by other Workers to talk to Actors.\n\n#include <workerd/api/actor.h>\n#include <workerd/api/container.h>\n#include <workerd/io/actor-cache.h>\n#include <workerd/io/actor-id.h>\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/io/io-own.h>\n#include <workerd/io/worker.h>\n#include <workerd/jsg/jsg.h>\n\n#include <kj/async.h>\n\nnamespace workerd::api {\nclass SqlStorage;\nclass SyncKvStorage;\n\n// Forward-declared to avoid dependency cycle (actor.h -> http.h -> basics.h -> actor-state.h)\nclass DurableObject;\nclass DurableObjectId;\nclass WebSocket;\nclass DurableObjectClass;\nclass LoopbackDurableObjectNamespace;\nclass LoopbackColoLocalActorNamespace;\n\nkj::Array<kj::byte> serializeV8Value(jsg::Lock& js, const jsg::JsValue& value);\n\njsg::JsValue deserializeV8Value(\n    jsg::Lock& js, kj::ArrayPtr<const char> key, kj::ArrayPtr<const kj::byte> buf);\n\n// Common implementation of DurableObjectStorage and DurableObjectTransaction. This class is\n// designed to be used as a mixin.\nclass DurableObjectStorageOperations {\n public:\n  struct GetOptions {\n    jsg::Optional<bool> allowConcurrency;\n    jsg::Optional<bool> noCache;\n\n    inline operator ActorCacheOps::ReadOptions() const {\n      return {.noCache = noCache.orDefault(false)};\n    }\n\n    JSG_STRUCT(allowConcurrency, noCache);\n    JSG_STRUCT_TS_OVERRIDE(DurableObjectGetOptions);  // Rename from DurableObjectStorageOperationsGetOptions\n  };\n\n  jsg::Promise<jsg::JsRef<jsg::JsValue>> get(jsg::Lock& js,\n      kj::OneOf<kj::String, kj::Array<kj::String>> keys,\n      jsg::Optional<GetOptions> options);\n\n  struct GetAlarmOptions {\n    jsg::Optional<bool> allowConcurrency;\n\n    JSG_STRUCT(allowConcurrency);\n    JSG_STRUCT_TS_OVERRIDE(DurableObjectGetAlarmOptions);  // Rename from DurableObjectStorageOperationsGetAlarmOptions\n  };\n\n  jsg::Promise<kj::Maybe<double>> getAlarm(jsg::Lock& js, jsg::Optional<GetAlarmOptions> options);\n\n  struct ListOptions {\n    jsg::Optional<kj::String> start;\n    jsg::Optional<kj::String> startAfter;\n    jsg::Optional<kj::String> end;\n    jsg::Optional<kj::String> prefix;\n    jsg::Optional<bool> reverse;\n    jsg::Optional<int> limit;\n\n    jsg::Optional<bool> allowConcurrency;\n    jsg::Optional<bool> noCache;\n\n    inline operator ActorCacheOps::ReadOptions() const {\n      return {.noCache = noCache.orDefault(false)};\n    }\n\n    JSG_STRUCT(start, startAfter, end, prefix, reverse, limit, allowConcurrency, noCache);\n    JSG_STRUCT_TS_OVERRIDE(DurableObjectListOptions);  // Rename from DurableObjectStorageOperationsListOptions\n  };\n\n  // A more convenient form of `ListOptions` for actually implementing the operation -- but less\n  // convenient for specifying it.\n  struct CompiledListOptions {\n    kj::String start;\n    kj::Maybe<kj::String> end;\n    bool reverse;\n    kj::Maybe<uint> limit;\n  };\n\n  // Compile `ListOptions` into `CompiledListOptions`. Returns null if the list operation would\n  // provably return no results (e.g. the end key is before the start key). This may (or may not)\n  // move some of the strings from the input to the output.\n  //\n  // This is public so that SyncKvStorage can reuse it.\n  static kj::Maybe<CompiledListOptions> compileListOptions(kj::Maybe<ListOptions>& maybeOptions);\n\n  jsg::Promise<jsg::JsRef<jsg::JsValue>> list(jsg::Lock& js, jsg::Optional<ListOptions> options);\n\n  struct PutOptions {\n    jsg::Optional<bool> allowConcurrency;\n    jsg::Optional<bool> allowUnconfirmed;\n    jsg::Optional<bool> noCache;\n\n    inline operator ActorCacheOps::WriteOptions() const {\n      return {\n        .allowUnconfirmed = allowUnconfirmed.orDefault(false), .noCache = noCache.orDefault(false)};\n    }\n\n    JSG_STRUCT(allowConcurrency, allowUnconfirmed, noCache);\n    JSG_STRUCT_TS_OVERRIDE(DurableObjectPutOptions);  // Rename from DurableObjectStorageOperationsPutOptions\n  };\n\n  jsg::Promise<void> put(jsg::Lock& js,\n      kj::OneOf<kj::String, jsg::Dict<jsg::JsValue>> keyOrEntries,\n      jsg::Optional<jsg::JsValue> value,\n      jsg::Optional<PutOptions> options,\n      const jsg::TypeHandler<PutOptions>& optionsTypeHandler);\n\n  kj::OneOf<jsg::Promise<bool>, jsg::Promise<int>> delete_(jsg::Lock& js,\n      kj::OneOf<kj::String, kj::Array<kj::String>> keys,\n      jsg::Optional<PutOptions> options);\n\n  struct SetAlarmOptions {\n    jsg::Optional<bool> allowConcurrency;\n    jsg::Optional<bool> allowUnconfirmed;\n    // We don't allow noCache for alarm puts.\n\n    inline operator ActorCacheOps::WriteOptions() const {\n      return {\n        .allowUnconfirmed = allowUnconfirmed.orDefault(false),\n      };\n    }\n\n    JSG_STRUCT(allowConcurrency, allowUnconfirmed);\n    JSG_STRUCT_TS_OVERRIDE(DurableObjectSetAlarmOptions);  // Rename from DurableObjectStorageOperationsSetAlarmOptions\n  };\n\n  jsg::Promise<void> setAlarm(\n      jsg::Lock& js, kj::Date scheduledTime, jsg::Optional<SetAlarmOptions> options);\n  jsg::Promise<void> deleteAlarm(jsg::Lock& js, jsg::Optional<SetAlarmOptions> options);\n\n protected:\n  using OpName = kj::StringPtr;\n  static constexpr OpName OP_GET = \"get()\"_kj;\n  static constexpr OpName OP_GET_ALARM = \"getAlarm()\"_kj;\n  static constexpr OpName OP_LIST = \"list()\"_kj;\n  static constexpr OpName OP_PUT = \"put()\"_kj;\n  static constexpr OpName OP_PUT_ALARM = \"setAlarm()\"_kj;\n  static constexpr OpName OP_DELETE = \"delete()\"_kj;\n  static constexpr OpName OP_DELETE_ALARM = \"deleteAlarm()\"_kj;\n  static constexpr OpName OP_RENAME = \"rename()\"_kj;\n  static constexpr OpName OP_ROLLBACK = \"rollback()\"_kj;\n\n  static bool readOnlyOp(OpName op) {\n    return op == OP_GET || op == OP_LIST || op == OP_ROLLBACK;\n  }\n\n  virtual ActorCacheOps& getCache(OpName op) = 0;\n\n  // Whether to skip caching and allow concurrency on all operations.\n  virtual bool useDirectIo() = 0;\n\n  // Method that should be called at the start of each storage operation to override any of the\n  // options as appropriate.\n  template <typename T>\n  T configureOptions(T&& options) {\n    if (useDirectIo()) {\n      options.allowConcurrency = true;\n      options.noCache = true;\n    }\n    return kj::mv(options);\n  }\n\n private:\n  jsg::Promise<jsg::JsRef<jsg::JsValue>> getOne(\n      jsg::Lock& js, kj::String key, const GetOptions& options);\n  jsg::Promise<jsg::JsRef<jsg::JsValue>> getMultiple(\n      jsg::Lock& js, kj::Array<kj::String> keys, const GetOptions& options);\n\n  jsg::Promise<void> putOne(\n      jsg::Lock& js, kj::String key, jsg::JsValue value, const PutOptions& options);\n  jsg::Promise<void> putMultiple(\n      jsg::Lock& js, jsg::Dict<jsg::JsValue> entries, const PutOptions& options);\n\n  jsg::Promise<bool> deleteOne(jsg::Lock& js, kj::String key, const PutOptions& options);\n  jsg::Promise<int> deleteMultiple(\n      jsg::Lock& js, kj::Array<kj::String> keys, const PutOptions& options);\n};\n\nclass DurableObjectTransaction;\n\nclass DurableObjectStorage: public jsg::Object, public DurableObjectStorageOperations {\n public:\n  DurableObjectStorage(jsg::Lock&, IoPtr<ActorCacheInterface> cache, bool enableSql)\n      : cache(kj::mv(cache)),\n        enableSql(enableSql) {}\n\n  // This constructor is only used when we're setting up the `DurableObjectStorage` for a replica\n  // Durable Object instance. Replicas need to retain a reference to their primary so they can\n  // forward write requests, and since we already have a reference to the primary prior to\n  // constructing the `DurableObjectStorage`, we can just pass in the information we need to build\n  // a stub. The stub is then stored in `maybePrimary`.\n  DurableObjectStorage(jsg::Lock& js,\n      IoPtr<ActorCacheInterface> cache,\n      bool enableSql,\n      kj::Own<IoChannelFactory::ActorChannel> primaryActorChannel,\n      kj::Own<ActorIdFactory::ActorId> primaryActorId);\n\n  ActorCacheInterface& getActorCacheInterface() {\n    return *cache;\n  }\n\n  // Throws if not SQLite-backed.\n  SqliteDatabase& getSqliteDb(jsg::Lock& js);\n  SqliteKv& getSqliteKv(jsg::Lock& js);\n\n  struct TransactionOptions {\n    jsg::Optional<kj::Date> asOfTime;\n    jsg::Optional<bool> lowPriority;\n\n    JSG_STRUCT(asOfTime, lowPriority);\n    JSG_STRUCT_TS_OVERRIDE(type TransactionOptions = never);\n    // Omit from definitions\n  };\n\n  jsg::Promise<jsg::JsRef<jsg::JsValue>> transaction(jsg::Lock& js,\n      jsg::Function<jsg::Promise<jsg::JsRef<jsg::JsValue>>(jsg::Ref<DurableObjectTransaction>)>\n          closure,\n      jsg::Optional<TransactionOptions> options);\n\n  jsg::JsRef<jsg::JsValue> transactionSync(\n      jsg::Lock& js, jsg::Function<jsg::JsRef<jsg::JsValue>()> callback);\n\n  jsg::Promise<void> deleteAll(jsg::Lock& js, jsg::Optional<PutOptions> options);\n\n  jsg::Promise<void> sync(jsg::Lock& js);\n\n  jsg::Ref<SqlStorage> getSql(jsg::Lock& js);\n\n  jsg::Ref<SyncKvStorage> getKv(jsg::Lock& js);\n\n  // Get a bookmark for the current state of the database. Note that since this is async, the\n  // bookmark will include any writes in the current atomic batch, including writes that are\n  // performed after this call begins. It could also include concurrent writes that haven't happened\n  // yet, unless blockConcurrencyWhile() is used to prevent them.\n  kj::Promise<kj::String> getCurrentBookmark();\n\n  // Get a bookmark representing approximately the given timestamp, which is a time up to 30 days\n  // in the past (or whatever the backup retention period is).\n  kj::Promise<kj::String> getBookmarkForTime(kj::Date timestamp);\n\n  // Arrange that the next time the Durable Object restarts, the database will be restored to\n  // the state represented by the given bookmark. This returns a bookmark string which represents\n  // the state immediately before the restoration takes place, and thus can be used to undo the\n  // restore. (This bookmark technically refers to a *future* state -- it specifies the state the\n  // object will have at the end of the current session.)\n  //\n  // It is up to the caller to force a restart in order to complete the restoration, for instance\n  // by calling state.abort() or by throwing from a blockConcurrencyWhile() callback.\n  kj::Promise<kj::String> onNextSessionRestoreBookmark(kj::String bookmark);\n\n  // Wait until the database has been updated to the state represented by `bookmark`.\n  //\n  // `waitForBookmark` is useful synchronizing requests across replicas of the same database.  On\n  // primary databases, `waitForBookmark` will resolve immediately.  On replica databases,\n  // `waitForBookmark` will resolve when the replica has been updated to a point at or after\n  // `bookmark`.\n  kj::Promise<void> waitForBookmark(kj::String bookmark);\n\n  // Arrange to create replicas for this Durable Object.\n  //\n  // Once a Durable Object instance calls `ensureReplicas`, all subsequent calls will be no-ops,\n  // making it idempotent, unless `disableReplicas` has been called between `ensureReplicas` calls.\n  void ensureReplicas();\n\n  // Arrange to disable replicas for this Durable Object.\n  //\n  // If replicas have never been created, this is a no-op. Similar to `ensureReplicas`, repeated\n  // calls are no-ops unless `ensureReplicas` re-enabled the replicas.\n  void disableReplicas();\n\n  jsg::Optional<jsg::Ref<DurableObject>> getPrimary(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(DurableObjectStorage, CompatibilityFlags::Reader flags) {\n    JSG_METHOD(get);\n    JSG_METHOD(list);\n    JSG_METHOD(put);\n    JSG_METHOD_NAMED(delete, delete_);\n    JSG_METHOD(deleteAll);\n    JSG_METHOD(transaction);\n    JSG_METHOD(getAlarm);\n    JSG_METHOD(setAlarm);\n    JSG_METHOD(deleteAlarm);\n    JSG_METHOD(sync);\n\n    JSG_LAZY_INSTANCE_PROPERTY(sql, getSql);\n    JSG_LAZY_INSTANCE_PROPERTY(kv, getKv);\n    JSG_METHOD(transactionSync);\n\n    JSG_METHOD(getCurrentBookmark);\n    JSG_METHOD(getBookmarkForTime);\n    JSG_METHOD(onNextSessionRestoreBookmark);\n\n    if (flags.getWorkerdExperimental()) {\n      JSG_METHOD(waitForBookmark);\n      JSG_READONLY_INSTANCE_PROPERTY(primary, getPrimary);\n    }\n\n    if (flags.getReplicaRouting()) {\n      JSG_METHOD(ensureReplicas);\n      JSG_METHOD(disableReplicas);\n    }\n\n    JSG_TS_OVERRIDE({\n      get<T = unknown>(key: string, options?: DurableObjectGetOptions): Promise<T | undefined>;\n      get<T = unknown>(keys: string[], options?: DurableObjectGetOptions): Promise<Map<string, T>>;\n\n      list<T = unknown>(options?: DurableObjectListOptions): Promise<Map<string, T>>;\n\n      put<T>(key: string, value: T, options?: DurableObjectPutOptions): Promise<void>;\n      put<T>(entries: Record<string, T>, options?: DurableObjectPutOptions): Promise<void>;\n\n      delete(key: string, options?: DurableObjectPutOptions): Promise<boolean>;\n      delete(keys: string[], options?: DurableObjectPutOptions): Promise<number>;\n\n      transaction<T>(closure: (txn: DurableObjectTransaction) => Promise<T>): Promise<T>;\n      transactionSync<T>(closure: () => T): T;\n    });\n  }\n\n protected:\n  ActorCacheOps& getCache(kj::StringPtr op) override;\n\n  bool useDirectIo() override {\n    return false;\n  }\n\n private:\n  IoPtr<ActorCacheInterface> cache;\n  bool enableSql;\n  uint transactionSyncDepth = 0;\n\n  // Set if this is a replica Durable Object.\n  kj::Maybe<jsg::Ref<DurableObject>> maybePrimary;\n};\n\nclass DurableObjectTransaction final: public jsg::Object, public DurableObjectStorageOperations {\n public:\n  DurableObjectTransaction(IoOwn<ActorCacheInterface::Transaction> cacheTxn)\n      : cacheTxn(kj::mv(cacheTxn)) {}\n\n  // Called from C++, not JS, after the transaction callback has completed (successfully or not).\n  // These methods do nothing if the transaction is already committed / rolled back.\n  kj::Promise<void> maybeCommit();\n\n  // Called from C++, not JS, after the transaction callback has completed (successfully or not).\n  // These methods do nothing if the transaction is already committed / rolled back.\n  void maybeRollback();\n\n  void rollback();  // called from JS\n\n  // Just throws an exception saying this isn't supported.\n  void deleteAll();\n\n  JSG_RESOURCE_TYPE(DurableObjectTransaction) {\n    JSG_METHOD(get);\n    JSG_METHOD(list);\n    JSG_METHOD(put);\n    JSG_METHOD_NAMED(delete, delete_);\n    JSG_METHOD(deleteAll);\n    JSG_METHOD(rollback);\n    JSG_METHOD(getAlarm);\n    JSG_METHOD(setAlarm);\n    JSG_METHOD(deleteAlarm);\n\n    JSG_TS_OVERRIDE({\n      get<T = unknown>(key: string, options?: DurableObjectGetOptions): Promise<T | undefined>;\n      get<T = unknown>(keys: string[], options?: DurableObjectGetOptions): Promise<Map<string, T>>;\n\n      list<T = unknown>(options?: DurableObjectListOptions): Promise<Map<string, T>>;\n\n      put<T>(key: string, value: T, options?: DurableObjectPutOptions): Promise<void>;\n      put<T>(entries: Record<string, T>, options?: DurableObjectPutOptions): Promise<void>;\n\n      delete(key: string, options?: DurableObjectPutOptions): Promise<boolean>;\n      delete(keys: string[], options?: DurableObjectPutOptions): Promise<number>;\n\n      deleteAll: never;\n    });\n  }\n\n protected:\n  ActorCacheOps& getCache(kj::StringPtr op) override;\n\n  bool useDirectIo() override {\n    return false;\n  }\n\n private:\n  // Becomes null when committed or rolled back.\n  kj::Maybe<IoOwn<ActorCacheInterface::Transaction>> cacheTxn;\n\n  bool rolledBack = false;\n\n  friend DurableObjectStorage;\n};\n\nclass DurableObjectFacets: public jsg::Object {\n public:\n  DurableObjectFacets(kj::Maybe<IoPtr<Worker::Actor::FacetManager>> facetManager)\n      : facetManager(kj::mv(facetManager)) {}\n\n  // Describes how to run a facet. The app provides this when first accessing a facet that isn't\n  // already running.\n  struct StartupOptions {\n    // The actor class to use to implement the facet.\n    //\n    // Note that the $ is needed only because `class` is a keyword in C++. JSG removes the $ from\n    // the name in the JS API. C++ does not officially recognize the existence of a $ symbol but\n    // all major compilers support using it as if it were a letter.\n    kj::OneOf<jsg::Ref<DurableObjectClass>,\n        jsg::Ref<LoopbackDurableObjectNamespace>,\n        jsg::Ref<LoopbackColoLocalActorNamespace>>\n        $class;\n\n    // Value to expose as `ctx.id` in the facet.\n    jsg::Optional<kj::OneOf<jsg::Ref<DurableObjectId>, kj::String>> id;\n\n    JSG_STRUCT($class, id);\n\n    JSG_STRUCT_TS_OVERRIDE(FacetStartupOptions<\n        T extends Rpc.DurableObjectBranded | undefined = undefined> {\n      class: DurableObjectClass<T>;\n      id?: DurableObjectId | string;\n\n      $class: never;  // work around generate-types bug\n    });\n  };\n\n  // Get a facet by name, starting it if it isn't already running. `getStartupOptions` is invoked\n  // only if the facet wasn't already running, to get information needed to start the facet.\n  //\n  // Returns a `Fetcher` instead of a `DurableObject` becasue the returend stub does not have the\n  // `id` or `name` methods that a DO stub normally has.\n  jsg::Ref<Fetcher> get(jsg::Lock& js,\n      kj::String name,\n      jsg::Function<jsg::Promise<StartupOptions>()> getStartupOptions);\n\n  void abort(jsg::Lock& js, kj::String name, jsg::JsValue reason);\n  void delete_(jsg::Lock& js, kj::String name);\n\n  JSG_RESOURCE_TYPE(DurableObjectFacets) {\n    JSG_METHOD(get);\n    JSG_METHOD(abort);\n    JSG_METHOD_NAMED(delete, delete_);\n\n    JSG_TS_OVERRIDE({\n      get<T extends Rpc.DurableObjectBranded | undefined = undefined>(\n          name: string,\n          getStartupOptions: () => FacetStartupOptions<T> | Promise<FacetStartupOptions<T>>)\n          : Fetcher<T>;\n    });\n  }\n\n private:\n  kj::Maybe<IoPtr<Worker::Actor::FacetManager>> facetManager;\n\n  Worker::Actor::FacetManager& getFacetManager() {\n    return *JSG_REQUIRE_NONNULL(\n        facetManager, Error, \"This Durable Object does not support creating facets.\");\n  }\n};\n\n// The type placed in event.actorState (pre-modules API).\n// NOTE: It hasn't been renamed under the assumption that it will only be\n// used for colo-local namespaces.\nclass ActorState: public jsg::Object {\n  // TODO(cleanup): Remove getPersistent method that isn't supported for colo-local actors anymore.\n public:\n  ActorState(Worker::Actor::Id actorId,\n      kj::Maybe<jsg::JsRef<jsg::JsValue>> transient,\n      kj::Maybe<jsg::Ref<DurableObjectStorage>> persistent);\n\n  kj::OneOf<jsg::Ref<DurableObjectId>, kj::StringPtr> getId(jsg::Lock& js);\n\n  jsg::Optional<jsg::JsValue> getTransient(jsg::Lock& js) {\n    return transient.map([&](jsg::JsRef<jsg::JsValue>& v) { return v.getHandle(js); });\n  }\n\n  jsg::Optional<jsg::Ref<DurableObjectStorage>> getPersistent() {\n    return persistent.map([&](jsg::Ref<DurableObjectStorage>& p) { return p.addRef(); });\n  }\n\n  JSG_RESOURCE_TYPE(ActorState) {\n    JSG_READONLY_INSTANCE_PROPERTY(id, getId);\n    JSG_READONLY_INSTANCE_PROPERTY(transient, getTransient);\n    JSG_READONLY_INSTANCE_PROPERTY(persistent, getPersistent);\n\n    JSG_TS_OVERRIDE(type ActorState = never);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    KJ_SWITCH_ONEOF(id) {\n      KJ_CASE_ONEOF(str, kj::String) {\n        tracker.trackField(\"id\", str);\n      }\n      KJ_CASE_ONEOF(id, kj::Own<ActorIdFactory::ActorId>) {\n        // TODO(later): This only yields the shallow size of the ActorId and not the\n        // size of the actual value. Should probably make ActorID a MemoryRetainer.\n        tracker.trackFieldWithSize(\"id\", sizeof(ActorIdFactory::ActorId));\n      }\n    }\n    tracker.trackField(\"transient\", transient);\n    tracker.trackField(\"persistent\", persistent);\n  }\n\n private:\n  Worker::Actor::Id id;\n  kj::Maybe<jsg::JsRef<jsg::JsValue>> transient;\n  kj::Maybe<jsg::Ref<DurableObjectStorage>> persistent;\n};\n\nclass WebSocketRequestResponsePair: public jsg::Object {\n public:\n  WebSocketRequestResponsePair(kj::String request, kj::String response)\n      : request(kj::mv(request)),\n        response(kj::mv(response)) {};\n\n  static jsg::Ref<WebSocketRequestResponsePair> constructor(\n      jsg::Lock& js, kj::String request, kj::String response) {\n    return js.alloc<WebSocketRequestResponsePair>(kj::mv(request), kj::mv(response));\n  };\n\n  kj::StringPtr getRequest() {\n    return request.asPtr();\n  }\n  kj::StringPtr getResponse() {\n    return response.asPtr();\n  }\n\n  JSG_RESOURCE_TYPE(WebSocketRequestResponsePair) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(request, getRequest);\n    JSG_READONLY_PROTOTYPE_PROPERTY(response, getResponse);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"request\", request);\n    tracker.trackField(\"response\", response);\n  }\n\n private:\n  kj::String request;\n  kj::String response;\n};\n\n// The type passed as the first parameter to durable object class's constructor.\nclass DurableObjectState: public jsg::Object {\n public:\n  DurableObjectState(jsg::Lock& js,\n      Worker::Actor::Id actorId,\n      jsg::JsValue exports,\n      jsg::JsValue props,\n      kj::Maybe<jsg::Ref<DurableObjectStorage>> storage,\n      kj::Maybe<rpc::Container::Client> container,\n      bool containerRunning,\n      kj::Maybe<Worker::Actor::FacetManager&> facetManager,\n      kj::Maybe<ActorVersion> version = kj::none);\n\n  void waitUntil(kj::Promise<void> promise);\n\n  jsg::JsValue getExports(jsg::Lock& js) {\n    return exports.getHandle(js);\n  }\n\n  jsg::JsValue getProps(jsg::Lock& js) {\n    return props.getHandle(js);\n  }\n\n  kj::OneOf<jsg::Ref<DurableObjectId>, kj::StringPtr> getId(jsg::Lock& js);\n\n  jsg::Optional<jsg::Ref<DurableObjectStorage>> getStorage() {\n    return storage.map([&](jsg::Ref<DurableObjectStorage>& p) { return p.addRef(); });\n  }\n\n  struct Version {\n    jsg::Optional<kj::StringPtr> cohort;\n    JSG_STRUCT(cohort);\n  };\n  jsg::Optional<Version> getVersion() {\n    return version.map([](ActorVersion& v) -> Version {\n      return Version{.cohort = v.cohort.map([](kj::String& s) -> kj::StringPtr { return s; })};\n    });\n  }\n  jsg::Optional<jsg::Ref<Container>> getContainer() {\n    return container.map([](jsg::Ref<Container>& c) { return c.addRef(); });\n  }\n\n  jsg::Ref<DurableObjectFacets> getFacets(jsg::Lock& js) {\n    return js.alloc<DurableObjectFacets>(facetManager);\n  }\n\n  jsg::Promise<jsg::JsRef<jsg::JsValue>> blockConcurrencyWhile(\n      jsg::Lock& js, jsg::Function<jsg::Promise<jsg::JsRef<jsg::JsValue>>()> callback);\n\n  // Reset the object, including breaking the output gate and canceling any writes that haven't\n  // been committed yet.\n  void abort(jsg::Lock& js, jsg::Optional<kj::String> reason);\n\n  // Sets and returns a new hibernation manager in an actor if there's none or returns the existing.\n  Worker::Actor::HibernationManager& maybeInitHibernationManager(Worker::Actor& actor);\n\n  // Adds a WebSocket to the set attached to this object.\n  // `ws.accept()` must NOT have been called separately.\n  // Once called, any incoming messages will be delivered\n  // by calling the Durable Object's webSocketMessage()\n  // handler, and webSocketClose() will be invoked upon\n  // disconnect.\n  //\n  // After calling this, the WebSocket is accepted, so\n  // its send() and close() methods can be used to send\n  // messages. It should be noted that calling addEventListener()\n  // on the websocket does nothing, since inbound events will\n  // automatically be delivered to one of the webSocketMessage()/\n  // webSocketClose()/webSocketError() handlers. No inbound events\n  // to a WebSocket accepted via acceptWebSocket() will ever be\n  // delivered to addEventListener(), so there is no reason to call it.\n  //\n  // `tags` are string tags which can be used to look up\n  // the WebSocket with getWebSockets().\n  void acceptWebSocket(jsg::Ref<WebSocket> ws, jsg::Optional<kj::Array<kj::String>> tags);\n\n  // Gets an array of accepted WebSockets matching the given tag.\n  // If no tag is provided, an array of all accepted WebSockets is returned.\n  // Disconnected WebSockets are automatically removed from the list.\n  kj::Array<jsg::Ref<api::WebSocket>> getWebSockets(jsg::Lock& js, jsg::Optional<kj::String> tag);\n\n  // Sets an object-wide websocket auto response message for a specific\n  // request string. All websockets belonging to the same object must\n  // reply to the request with the matching response, then store the timestamp at which\n  // the request was received.\n  // If maybeReqResp is not set, we consider it as unset and remove any set request response pair.\n  void setWebSocketAutoResponse(\n      jsg::Optional<jsg::Ref<api::WebSocketRequestResponsePair>> maybeReqResp);\n\n  // Gets the currently set object-wide websocket auto response.\n  kj::Maybe<jsg::Ref<api::WebSocketRequestResponsePair>> getWebSocketAutoResponse(jsg::Lock& js);\n\n  // Get the last auto response timestamp or null\n  kj::Maybe<kj::Date> getWebSocketAutoResponseTimestamp(jsg::Ref<WebSocket> ws);\n\n  // Sets or unsets the timeout for hibernatable websocket events, preventing the execution of\n  // the event from taking longer than the specified timeout, if set.\n  void setHibernatableWebSocketEventTimeout(jsg::Optional<uint32_t> timeoutMs);\n\n  // Get the currently set hibernatable websocket event timeout if set, or kj::none if not.\n  kj::Maybe<uint32_t> getHibernatableWebSocketEventTimeout();\n\n  // Gets an array of tags that this websocket was accepted with. If the given websocket is not\n  // hibernatable, we'll throw an error because regular websockets do not have tags.\n  kj::Array<kj::StringPtr> getTags(jsg::Lock& js, jsg::Ref<api::WebSocket> ws);\n\n  JSG_RESOURCE_TYPE(DurableObjectState, CompatibilityFlags::Reader flags) {\n    JSG_METHOD(waitUntil);\n    if (flags.getEnableCtxExports()) {\n      JSG_LAZY_INSTANCE_PROPERTY(exports, getExports);\n    }\n    JSG_LAZY_INSTANCE_PROPERTY(props, getProps);\n    JSG_LAZY_INSTANCE_PROPERTY(id, getId);\n    JSG_LAZY_INSTANCE_PROPERTY(storage, getStorage);\n    JSG_LAZY_INSTANCE_PROPERTY(container, getContainer);\n    if (flags.getWorkerdExperimental()) {\n      // Experimental new API, details may change!\n      JSG_LAZY_INSTANCE_PROPERTY(facets, getFacets);\n    }\n    if (flags.getEnableVersionApi()) {\n      JSG_LAZY_INSTANCE_PROPERTY(version, getVersion);\n    }\n    JSG_METHOD(blockConcurrencyWhile);\n    JSG_METHOD(acceptWebSocket);\n    JSG_METHOD(getWebSockets);\n    JSG_METHOD(setWebSocketAutoResponse);\n    JSG_METHOD(getWebSocketAutoResponse);\n    JSG_METHOD(getWebSocketAutoResponseTimestamp);\n    JSG_METHOD(setHibernatableWebSocketEventTimeout);\n    JSG_METHOD(getHibernatableWebSocketEventTimeout);\n    JSG_METHOD(getTags);\n\n    JSG_METHOD(abort);\n\n    JSG_TS_ROOT();\n\n    // Type overrides:\n    // * Define Props/Exports type parameters.\n    // * Make `storage` non-optional\n    // * Make `id` strictly `DurableObjectId` (it's only a string for colo-local actors which are\n    //   not available publicly).\n    if (flags.getEnableCtxExports()) {\n      JSG_TS_OVERRIDE(<Props = unknown> {\n        readonly props: Props;\n        readonly exports: Cloudflare.Exports;\n        readonly id: DurableObjectId;\n        readonly storage: DurableObjectStorage;\n        blockConcurrencyWhile<T>(callback: () => Promise<T>): Promise<T>;\n      });\n    } else {\n      // No ctx.exports yet.\n      JSG_TS_OVERRIDE(<Props = unknown> {\n        readonly props: Props;\n        readonly id: DurableObjectId;\n        readonly storage: DurableObjectStorage;\n        blockConcurrencyWhile<T>(callback: () => Promise<T>): Promise<T>;\n      });\n    }\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    KJ_SWITCH_ONEOF(id) {\n      KJ_CASE_ONEOF(str, kj::String) {\n        tracker.trackField(\"id\", str);\n      }\n      KJ_CASE_ONEOF(id, kj::Own<ActorIdFactory::ActorId>) {\n        // TODO(later): This only yields the shallow size of the ActorId and not the\n        // size of the actual value. Should probably make ActorID a MemoryRetainer.\n        tracker.trackFieldWithSize(\"id\", sizeof(ActorIdFactory::ActorId));\n      }\n    }\n    tracker.trackField(\"storage\", storage);\n  }\n\n private:\n  Worker::Actor::Id id;\n  jsg::JsRef<jsg::JsValue> exports;\n  jsg::JsRef<jsg::JsValue> props;\n  kj::Maybe<jsg::Ref<DurableObjectStorage>> storage;\n  kj::Maybe<jsg::Ref<Container>> container;\n  kj::Maybe<IoPtr<Worker::Actor::FacetManager>> facetManager;\n  kj::Maybe<ActorVersion> version;\n\n  // Limits for Hibernatable WebSocket tags.\n\n  const size_t MAX_TAGS_PER_CONNECTION = 10;\n  const size_t MAX_TAG_LENGTH = 256;\n};\n\n#define EW_ACTOR_STATE_ISOLATE_TYPES                                                               \\\n  api::ActorState, api::DurableObjectState, api::DurableObjectTransaction,                         \\\n      api::DurableObjectStorage, api::DurableObjectStorage::TransactionOptions,                    \\\n      api::DurableObjectStorageOperations::ListOptions,                                            \\\n      api::DurableObjectStorageOperations::GetOptions,                                             \\\n      api::DurableObjectStorageOperations::GetAlarmOptions,                                        \\\n      api::DurableObjectStorageOperations::PutOptions,                                             \\\n      api::DurableObjectStorageOperations::SetAlarmOptions, api::WebSocketRequestResponsePair,     \\\n      api::DurableObjectFacets, api::DurableObjectFacets::StartupOptions,                          \\\n      api::DurableObjectState::Version\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/actor.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"actor.h\"\n\n#include <workerd/io/features.h>\n\n#include <capnp/compat/byte-stream.h>\n#include <capnp/compat/http-over-capnp.h>\n#include <capnp/message.h>\n#include <capnp/schema.h>\n#include <kj/compat/http.h>\n#include <kj/encoding.h>\n\nnamespace workerd::api {\n\nkj::Own<WorkerInterface> LocalActorOutgoingFactory::newSingleUseClient(\n    kj::Maybe<kj::String> cfStr) {\n  auto& context = IoContext::current();\n\n  return context.getMetrics().wrapActorSubrequestClient(context.getSubrequest(\n      [&](TraceContext& tracing, IoChannelFactory& ioChannelFactory) {\n    tracing.setTag(\"objectId\"_kjc, actorId.asPtr());\n\n    // Lazily initialize actorChannel\n    if (actorChannel == kj::none) {\n      actorChannel =\n          context.getColoLocalActorChannel(channelId, actorId, tracing.getInternalSpanParent());\n    }\n\n    return KJ_REQUIRE_NONNULL(actorChannel)\n        ->startRequest(\n            {.cfBlobJson = kj::mv(cfStr), .parentSpan = tracing.getInternalSpanParent()});\n  },\n      {.inHouse = true,\n        .wrapMetrics = true,\n        .operationName = kj::ConstString(\"durable_object_subrequest\"_kjc)}));\n}\n\nkj::Own<WorkerInterface> GlobalActorOutgoingFactory::newSingleUseClient(\n    kj::Maybe<kj::String> cfStr) {\n  auto& context = IoContext::current();\n\n  return context.getMetrics().wrapActorSubrequestClient(context.getSubrequest(\n      [&](TraceContext& tracing, IoChannelFactory& ioChannelFactory) {\n    tracing.setTag(\"objectId\"_kjc, id->toString());\n\n    // Lazily initialize actorChannel\n    if (actorChannel == kj::none) {\n      KJ_SWITCH_ONEOF(channelIdOrFactory) {\n        KJ_CASE_ONEOF(channelId, uint) {\n          actorChannel = context.getGlobalActorChannel(channelId, id->getInner(),\n              kj::mv(locationHint), mode, enableReplicaRouting, routingMode,\n              tracing.getInternalSpanParent(), kj::mv(version));\n        }\n        KJ_CASE_ONEOF(factory, kj::Own<DurableObjectNamespace::ActorChannelFactory>) {\n          actorChannel = factory->getGlobalActor(id->getInner(), kj::mv(locationHint), mode,\n              enableReplicaRouting, routingMode, tracing.getInternalSpanParent(), kj::mv(version));\n        }\n      }\n    }\n\n    return KJ_REQUIRE_NONNULL(actorChannel)\n        ->startRequest(\n            {.cfBlobJson = kj::mv(cfStr), .parentSpan = tracing.getInternalSpanParent()});\n  },\n      {.inHouse = true,\n        .wrapMetrics = true,\n        .operationName = kj::ConstString(\"durable_object_subrequest\"_kjc)}));\n}\n\nkj::Own<WorkerInterface> ReplicaActorOutgoingFactory::newSingleUseClient(\n    kj::Maybe<kj::String> cfStr) {\n  auto& context = IoContext::current();\n\n  return context.getMetrics().wrapActorSubrequestClient(context.getSubrequest(\n      [&](TraceContext& tracing, IoChannelFactory& ioChannelFactory) {\n    tracing.setTag(\"objectId\"_kjc, actorId.asPtr());\n\n    // Unlike in `GlobalActorOutgoingFactory`, we do not create this lazily, since our channel was\n    // already open prior to this DO starting up.\n    return actorChannel->startRequest(\n        {.cfBlobJson = kj::mv(cfStr), .parentSpan = tracing.getInternalSpanParent()});\n  },\n      {.inHouse = true,\n        .wrapMetrics = true,\n        .operationName = kj::ConstString(\"durable_object_subrequest\"_kjc)}));\n}\n\njsg::Ref<Fetcher> ColoLocalActorNamespace::get(jsg::Lock& js, kj::String actorId) {\n  JSG_REQUIRE(actorId.size() > 0 && actorId.size() <= 2048, TypeError,\n      \"Actor ID length must be in the range [1, 2048].\");\n\n  auto& context = IoContext::current();\n\n  kj::Own<api::Fetcher::OutgoingFactory> factory =\n      kj::heap<LocalActorOutgoingFactory>(channel, kj::mv(actorId));\n  auto outgoingFactory = context.addObject(kj::mv(factory));\n\n  bool isInHouse = true;\n  return js.alloc<Fetcher>(\n      kj::mv(outgoingFactory), Fetcher::RequiresHostAndProtocol::YES, isInHouse);\n}\n\n// =======================================================================================\n\nkj::String DurableObjectId::toString() {\n  return id->toString();\n}\n\njsg::Ref<DurableObjectId> DurableObjectNamespace::newUniqueId(\n    jsg::Lock& js, jsg::Optional<NewUniqueIdOptions> options) {\n  return js.alloc<DurableObjectId>(\n      idFactory->newUniqueId(options.orDefault({}).jurisdiction.orDefault(kj::none)));\n}\n\njsg::Ref<DurableObjectId> DurableObjectNamespace::idFromName(jsg::Lock& js, kj::String name) {\n  return js.alloc<DurableObjectId>(idFactory->idFromName(kj::mv(name)));\n}\n\njsg::Ref<DurableObjectId> DurableObjectNamespace::idFromString(jsg::Lock& js, kj::String id) {\n  return js.alloc<DurableObjectId>(idFactory->idFromString(kj::mv(id)));\n}\n\njsg::Ref<DurableObject> DurableObjectNamespace::getByName(\n    jsg::Lock& js, kj::String name, jsg::Optional<GetDurableObjectOptions> options) {\n  auto id = js.alloc<DurableObjectId>(idFactory->idFromName(kj::mv(name)));\n  return getImpl(js, ActorGetMode::GET_OR_CREATE, kj::mv(id), kj::mv(options));\n}\n\njsg::Ref<DurableObject> DurableObjectNamespace::get(\n    jsg::Lock& js, jsg::Ref<DurableObjectId> id, jsg::Optional<GetDurableObjectOptions> options) {\n  return getImpl(js, ActorGetMode::GET_OR_CREATE, kj::mv(id), kj::mv(options));\n}\n\njsg::Ref<DurableObject> DurableObjectNamespace::getExisting(\n    jsg::Lock& js, jsg::Ref<DurableObjectId> id, jsg::Optional<GetDurableObjectOptions> options) {\n  return getImpl(js, ActorGetMode::GET_EXISTING, kj::mv(id), kj::mv(options));\n}\n\njsg::Ref<DurableObject> DurableObjectNamespace::getImpl(jsg::Lock& js,\n    ActorGetMode mode,\n    jsg::Ref<DurableObjectId> id,\n    jsg::Optional<GetDurableObjectOptions> options) {\n  JSG_REQUIRE(idFactory->matchesJurisdiction(id->getInner()), TypeError,\n      \"get called on jurisdictional subnamespace with an ID from a different jurisdiction\");\n  ActorRoutingMode routingMode = ActorRoutingMode::DEFAULT;\n  KJ_IF_SOME(o, options) {\n    KJ_IF_SOME(rm, o.routingMode) {\n      JSG_REQUIRE(rm == \"primary-only\", RangeError, \"unknown routingMode: \", rm);\n      routingMode = ActorRoutingMode::PRIMARY_ONLY;\n    }\n  }\n\n  auto& context = IoContext::current();\n  kj::Maybe<kj::String> locationHint;\n  kj::Maybe<ActorVersion> version;\n  KJ_IF_SOME(o, options) {\n    locationHint = kj::mv(o.locationHint);\n    if (FeatureFlags::get(js).getEnableVersionApi()) {\n      KJ_IF_SOME(v, o.version) {\n        version = ActorVersion{.cohort = kj::mv(v.cohort)};\n      }\n    }\n  }\n\n  bool enableReplicaRouting = FeatureFlags::get(js).getReplicaRouting();\n\n  kj::Own<Fetcher::OutgoingFactory> outgoingFactory;\n  KJ_SWITCH_ONEOF(channel) {\n    KJ_CASE_ONEOF(channelId, uint) {\n      outgoingFactory = kj::heap<GlobalActorOutgoingFactory>(channelId, id.addRef(),\n          kj::mv(locationHint), mode, enableReplicaRouting, routingMode, kj::mv(version));\n    }\n    KJ_CASE_ONEOF(channelFactory, IoOwn<ActorChannelFactory>) {\n      outgoingFactory =\n          kj::heap<GlobalActorOutgoingFactory>(kj::addRef(*channelFactory), id.addRef(),\n              kj::mv(locationHint), mode, enableReplicaRouting, routingMode, kj::mv(version));\n    }\n  }\n\n  auto requiresHost = FeatureFlags::get(js).getDurableObjectFetchRequiresSchemeAuthority()\n      ? Fetcher::RequiresHostAndProtocol::YES\n      : Fetcher::RequiresHostAndProtocol::NO;\n  return js.alloc<DurableObject>(\n      kj::mv(id), context.addObject(kj::mv(outgoingFactory)), requiresHost);\n}\n\njsg::Ref<DurableObjectNamespace> DurableObjectNamespace::jurisdiction(\n    jsg::Lock& js, jsg::Optional<kj::Maybe<kj::String>> maybeJurisdiction) {\n  auto newIdFactory = idFactory->cloneWithJurisdiction(maybeJurisdiction.orDefault(kj::none));\n\n  KJ_SWITCH_ONEOF(channel) {\n    KJ_CASE_ONEOF(channelId, uint) {\n      return js.alloc<api::DurableObjectNamespace>(channelId, kj::mv(newIdFactory));\n    }\n    KJ_CASE_ONEOF(channelFactory, IoOwn<ActorChannelFactory>) {\n      return js.alloc<api::DurableObjectNamespace>(\n          IoContext::current().addObject(kj::addRef(*channelFactory)), kj::mv(newIdFactory));\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\nkj::Own<IoChannelFactory::ActorClassChannel> DurableObjectClass::getChannel(IoContext& ioctx) {\n  KJ_SWITCH_ONEOF(channel) {\n    KJ_CASE_ONEOF(number, uint) {\n      return ioctx.getIoChannelFactory().getActorClass(number);\n    }\n    KJ_CASE_ONEOF(object, IoOwn<IoChannelFactory::ActorClassChannel>) {\n      return kj::addRef(*object);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid DurableObjectClass::serialize(jsg::Lock& js, jsg::Serializer& serializer) {\n  auto channel = getChannel(IoContext::current());\n  channel->requireAllowsTransfer();\n\n  KJ_IF_SOME(handler, serializer.getExternalHandler()) {\n    KJ_IF_SOME(frankenvalueHandler, kj::tryDowncast<Frankenvalue::CapTableBuilder>(handler)) {\n      // Encoding a Frankenvalue (e.g. for dynamic loopback props or dynamic isolate env).\n      serializer.writeRawUint32(frankenvalueHandler.add(kj::mv(channel)));\n      return;\n    } else KJ_IF_SOME(rpcHandler, kj::tryDowncast<RpcSerializerExternalHandler>(handler)) {\n      JSG_REQUIRE(FeatureFlags::get(js).getWorkerdExperimental(), DOMDataCloneError,\n          \"DurableObjectClass serialization requires the 'experimental' compat flag.\");\n\n      auto token = channel->getToken(IoChannelFactory::ChannelTokenUsage::RPC);\n      rpcHandler.write([token = kj::mv(token)](rpc::JsValue::External::Builder builder) {\n        builder.setActorClassChannelToken(token);\n      });\n      return;\n    }\n    // TODO(someday): structuredClone() should have special handling that just reproduces the same\n    //   local object. At present we have no way to recognize structuredClone() here though.\n  }\n\n  // The allow_irrevocable_stub_storage flag allows us to just embed the token inline. This format\n  // is temporary, anyone using this will lose their data later.\n  JSG_REQUIRE(FeatureFlags::get(js).getAllowIrrevocableStubStorage(), DOMDataCloneError,\n      \"DurableObjectClass cannot be serialized in this context.\");\n  serializer.writeLengthDelimited(channel->getToken(IoChannelFactory::ChannelTokenUsage::STORAGE));\n}\n\njsg::Ref<DurableObjectClass> DurableObjectClass::deserialize(\n    jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer) {\n  KJ_IF_SOME(handler, deserializer.getExternalHandler()) {\n    KJ_IF_SOME(frankenvalueHandler, kj::tryDowncast<Frankenvalue::CapTableReader>(handler)) {\n      // Decoding a Frankenvalue (e.g. for dynamic loopback props or dynamic isolate env).\n      auto& cap = KJ_REQUIRE_NONNULL(frankenvalueHandler.get(deserializer.readRawUint32()),\n          \"serialized DurableObjectClass had invalid cap table index\");\n\n      KJ_IF_SOME(channel, kj::tryDowncast<IoChannelFactory::ActorClassChannel>(cap)) {\n        // Probably decoding dynamic ctx.props.\n        return js.alloc<DurableObjectClass>(IoContext::current().addObject(kj::addRef(channel)));\n      } else KJ_IF_SOME(channel, kj::tryDowncast<IoChannelCapTableEntry>(cap)) {\n        // Probably decoding dynamic isolate env.\n        return js.alloc<DurableObjectClass>(\n            channel.getChannelNumber(IoChannelCapTableEntry::Type::ACTOR_CLASS));\n      } else {\n        KJ_FAIL_REQUIRE(\n            \"DurableObjectClass capability in Frankenvalue is not a ActorClassChannel?\");\n      }\n    } else KJ_IF_SOME(rpcHandler, kj::tryDowncast<RpcDeserializerExternalHandler>(handler)) {\n      JSG_REQUIRE(FeatureFlags::get(js).getWorkerdExperimental(), DOMDataCloneError,\n          \"DurableObjectClass serialization requires the 'experimental' compat flag.\");\n\n      auto external = rpcHandler.read();\n      KJ_REQUIRE(external.isActorClassChannelToken());\n      auto& ioctx = IoContext::current();\n      auto channel = ioctx.getIoChannelFactory().actorClassFromToken(\n          IoChannelFactory::ChannelTokenUsage::RPC, external.getActorClassChannelToken());\n      return js.alloc<DurableObjectClass>(ioctx.addObject(kj::mv(channel)));\n    }\n  }\n\n  // The allow_irrevocable_stub_storage flag allows us to just embed the token inline. This format\n  // is temporary, anyone using this will lose their data later.\n  JSG_REQUIRE(FeatureFlags::get(js).getAllowIrrevocableStubStorage(), DOMDataCloneError,\n      \"DOMDataCloneError cannot be deserialized in this context.\");\n  auto& ioctx = IoContext::current();\n  auto channel = ioctx.getIoChannelFactory().actorClassFromToken(\n      IoChannelFactory::ChannelTokenUsage::STORAGE, deserializer.readLengthDelimitedBytes());\n  return js.alloc<DurableObjectClass>(ioctx.addObject(kj::mv(channel)));\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/actor.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// \"Actors\" are the internal name for Durable Objects, because they implement a sort of actor\n// model. We ended up not calling the product \"Actors\" publicly because we found that people who\n// were familiar with actor-model programming were more confused than helped by it -- they tended\n// to expect something that looked more specifically like Erlang, whereas our actors are much more\n// abstractly related.\n\n#include <workerd/api/http.h>\n#include <workerd/io/actor-id.h>\n#include <workerd/io/worker-interface.capnp.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd {\ntemplate <typename T>\nclass IoOwn;\n}\n\nnamespace workerd::api {\n\n// A capability to an ephemeral Actor namespace.\nclass ColoLocalActorNamespace: public jsg::Object {\n public:\n  ColoLocalActorNamespace(uint channel): channel(channel) {}\n\n  jsg::Ref<Fetcher> get(jsg::Lock& js, kj::String actorId);\n\n  JSG_RESOURCE_TYPE(ColoLocalActorNamespace) {\n    JSG_METHOD(get);\n  }\n\n private:\n  uint channel;\n};\n\nclass DurableObjectNamespace;\n\n// DurableObjectId type seen by JavaScript.\nclass DurableObjectId: public jsg::Object {\n public:\n  DurableObjectId(kj::Own<ActorIdFactory::ActorId> id): id(kj::mv(id)) {}\n\n  const ActorIdFactory::ActorId& getInner() {\n    return *id;\n  }\n\n  // ---------------------------------------------------------------------------\n  // JS API\n\n  // Converts to a string which can be passed back to the constructor to reproduce the same ID.\n  kj::String toString();\n\n  inline bool equals(DurableObjectId& other) {\n    return id->equals(*other.id);\n  }\n\n  // Get the name, if known.\n  inline jsg::Optional<kj::StringPtr> getName() {\n    return id->getName();\n  }\n\n  jsg::Optional<kj::StringPtr> getJurisdiction() {\n    return id->getJurisdiction();\n  }\n\n  JSG_RESOURCE_TYPE(DurableObjectId) {\n    JSG_METHOD(toString);\n    JSG_METHOD(equals);\n    JSG_READONLY_INSTANCE_PROPERTY(name, getName);\n    JSG_READONLY_INSTANCE_PROPERTY(jurisdiction, getJurisdiction);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackFieldWithSize(\"id\", sizeof(ActorIdFactory::ActorId));\n  }\n\n private:\n  kj::Own<ActorIdFactory::ActorId> id;\n\n  friend class DurableObjectNamespace;\n};\n\n// Stub object used to send messages to a remote durable object.\nclass DurableObject final: public Fetcher {\n public:\n  DurableObject(jsg::Ref<DurableObjectId> id,\n      IoOwn<OutgoingFactory> outgoingFactory,\n      RequiresHostAndProtocol requiresHost)\n      : Fetcher(kj::mv(outgoingFactory), requiresHost, true /* isInHouse */),\n        id(kj::mv(id)) {}\n\n  jsg::Ref<DurableObjectId> getId() {\n    return id.addRef();\n  }\n\n  jsg::Optional<kj::StringPtr> getName() {\n    return id->getName();\n  }\n\n  JSG_RESOURCE_TYPE(DurableObject) {\n    JSG_INHERIT(Fetcher);\n\n    JSG_READONLY_INSTANCE_PROPERTY(id, getId);\n    JSG_READONLY_INSTANCE_PROPERTY(name, getName);\n\n    JSG_TS_DEFINE(interface DurableObject {\n      fetch(request: Request): Response | Promise<Response>;\n      alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise<void>;\n      webSocketMessage?(ws: WebSocket, message: string | ArrayBuffer): void | Promise<void>;\n      webSocketClose?(ws: WebSocket, code: number, reason: string, wasClean: boolean): void | Promise<void>;\n      webSocketError?(ws: WebSocket, error: unknown): void | Promise<void>;\n    });\n    JSG_TS_OVERRIDE(\n      type DurableObjectStub<T extends Rpc.DurableObjectBranded | undefined = undefined> =\n        Fetcher<T, \"alarm\" | \"webSocketMessage\" | \"webSocketClose\" | \"webSocketError\">\n        & {\n          readonly id: DurableObjectId;\n          readonly name?: string;\n        }\n    );\n    // Rename this resource type to DurableObjectStub, and make DurableObject\n    // the interface implemented by users' Durable Object classes.\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"id\", id);\n  }\n\n private:\n  jsg::Ref<DurableObjectId> id;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(id);\n  }\n};\n\n// Global durable object class binding type.\nclass DurableObjectNamespace: public jsg::Object {\n public:\n  // Instead of providing a channel ID, the caller can pass a factory object. This is used in cases\n  // where a DurableObjectNamespace is constructed dynamically within an execution context, rather\n  // than being a long-lived binding.\n  class ActorChannelFactory: public kj::Refcounted {\n   public:\n    virtual kj::Own<IoChannelFactory::ActorChannel> getGlobalActor(\n        const ActorIdFactory::ActorId& id,\n        kj::Maybe<kj::String> locationHint,\n        ActorGetMode mode,\n        bool enableReplicaRouting,\n        ActorRoutingMode routingMode,\n        SpanParent parentSpan,\n        kj::Maybe<ActorVersion> version) = 0;\n  };\n\n  DurableObjectNamespace(uint channel, kj::Own<ActorIdFactory> idFactory)\n      : channel(channel),\n        idFactory(kj::mv(idFactory)) {}\n  DurableObjectNamespace(IoOwn<ActorChannelFactory> factory, kj::Own<ActorIdFactory> idFactory)\n      : channel(kj::mv(factory)),\n        idFactory(kj::mv(idFactory)) {}\n\n  struct NewUniqueIdOptions {\n    // Restricts the new unique ID to a set of colos within a jurisdiction.\n    jsg::Optional<kj::Maybe<kj::String>> jurisdiction;\n\n    JSG_STRUCT(jurisdiction);\n\n    JSG_STRUCT_TS_DEFINE(type DurableObjectJurisdiction = \"eu\" | \"fedramp\" | \"fedramp-high\");\n    // Possible values from https://developers.cloudflare.com/workers/runtime-apis/durable-objects/#restricting-objects-to-a-jurisdiction\n    JSG_STRUCT_TS_OVERRIDE({\n      jurisdiction?: DurableObjectJurisdiction;\n    });\n  };\n\n  // Create a new unique ID for a durable object that will be allocated nearby the calling colo.\n  jsg::Ref<DurableObjectId> newUniqueId(jsg::Lock& js, jsg::Optional<NewUniqueIdOptions> options);\n\n  // Create a name-derived ID. Passing in the same `name` (to the same class) will always\n  // produce the same ID.\n  jsg::Ref<DurableObjectId> idFromName(jsg::Lock& js, kj::String name);\n\n  // Create a DurableObjectId from the stringified form of the ID (as produced by calling\n  // `toString()` on a durable object ID). Throws if the ID is not a 64-digit hex number, or if the\n  // ID was not originally created for this class.\n  //\n  // The ID may be one that was originally created using either `newUniqueId()` or `idFromName()`.\n  jsg::Ref<DurableObjectId> idFromString(jsg::Lock& js, kj::String id);\n\n  struct GetDurableObjectOptions {\n    jsg::Optional<kj::String> locationHint;\n    // `routingMode` may be be of interest to applications using Durable Objects replicas. It can be\n    // one of the following options:\n    //    - none: the default, indicates we will pick for the application.\n    //    - \"primary-only\": guarantees we route directly to the primary (skip any replicas).\n    jsg::Optional<kj::String> routingMode;\n\n    struct VersionOptions {\n      jsg::Optional<kj::String> cohort;\n      JSG_STRUCT(cohort);\n      JSG_STRUCT_TS_OVERRIDE_DYNAMIC(CompatibilityFlags::Reader flags) {\n        if (!flags.getWorkerdExperimental()) {\n          JSG_TS_OVERRIDE(type VersionOptions = never);\n        }\n      }\n    };\n    jsg::Optional<VersionOptions> version;\n\n    JSG_STRUCT(locationHint, routingMode, version);\n\n    // DurableObjectLocationHint values from https://developers.cloudflare.com/workers/runtime-apis/durable-objects/#providing-a-location-hint\n    JSG_STRUCT_TS_DEFINE(\n      type DurableObjectLocationHint = \"wnam\" | \"enam\" | \"sam\" | \"weur\" | \"eeur\" | \"apac\" | \"oc\" | \"afr\" | \"me\";\n      type DurableObjectRoutingMode = \"primary-only\");\n\n    JSG_STRUCT_TS_OVERRIDE_DYNAMIC(CompatibilityFlags::Reader flags) {\n      if (flags.getWorkerdExperimental()) {\n        JSG_TS_OVERRIDE({\n          locationHint?: DurableObjectLocationHint;\n          routingMode?: DurableObjectRoutingMode;\n          version?: { cohort?: string };\n        });\n      } else {\n        JSG_TS_OVERRIDE({\n          locationHint?: DurableObjectLocationHint;\n          routingMode?: DurableObjectRoutingMode;\n          version: never;\n        });\n      }\n    }\n  };\n\n  // Gets a durable object by ID or creates it if it doesn't already exist.\n  jsg::Ref<DurableObject> get(\n      jsg::Lock& js, jsg::Ref<DurableObjectId> id, jsg::Optional<GetDurableObjectOptions> options);\n\n  // Gets a durable object by name or creates it if it doesn't already exist.\n  //\n  // Short for `idFromName()` followed by `get()`.\n  jsg::Ref<DurableObject> getByName(\n      jsg::Lock& js, kj::String name, jsg::Optional<GetDurableObjectOptions> options);\n\n  // Experimental. Gets a durable object by ID if it already exists. Currently, gated for use\n  // by cloudflare only.\n  jsg::Ref<DurableObject> getExisting(\n      jsg::Lock& js, jsg::Ref<DurableObjectId> id, jsg::Optional<GetDurableObjectOptions> options);\n\n  // Creates a subnamespace with the jurisdiction hardcoded.\n  jsg::Ref<DurableObjectNamespace> jurisdiction(\n      jsg::Lock& js, jsg::Optional<kj::Maybe<kj::String>> maybeJurisdiction);\n\n  JSG_RESOURCE_TYPE(DurableObjectNamespace, CompatibilityFlags::Reader flags) {\n    JSG_METHOD(newUniqueId);\n    JSG_METHOD(idFromName);\n    JSG_METHOD(idFromString);\n    JSG_METHOD(get);\n    JSG_METHOD(getByName);\n    if (flags.getDurableObjectGetExisting()) {\n      JSG_METHOD(getExisting);\n    }\n    JSG_METHOD(jurisdiction);\n\n    JSG_TS_ROOT();\n    if (flags.getDurableObjectGetExisting()) {\n      JSG_TS_OVERRIDE(<T extends Rpc.DurableObjectBranded | undefined = undefined> {\n        get(id: DurableObjectId, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub<T>;\n        getByName(name: string, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub<T>;\n        getExisting(id: DurableObjectId, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub<T>;\n        jurisdiction(jurisdiction: DurableObjectJurisdiction): DurableObjectNamespace<T>;\n      });\n    } else {\n      JSG_TS_OVERRIDE(<T extends Rpc.DurableObjectBranded | undefined = undefined> {\n        get(id: DurableObjectId, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub<T>;\n        getByName(name: string, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub<T>;\n        jurisdiction(jurisdiction: DurableObjectJurisdiction): DurableObjectNamespace<T>;\n      });\n    }\n  }\n\n private:\n  kj::OneOf<uint, IoOwn<ActorChannelFactory>> channel;\n  kj::Own<ActorIdFactory> idFactory;\n\n  jsg::Ref<DurableObject> getImpl(jsg::Lock& js,\n      ActorGetMode mode,\n      jsg::Ref<DurableObjectId> id,\n      jsg::Optional<GetDurableObjectOptions> options);\n};\n\nclass GlobalActorOutgoingFactory final: public Fetcher::OutgoingFactory {\n public:\n  using ChannelIdOrFactory = kj::OneOf<uint, kj::Own<DurableObjectNamespace::ActorChannelFactory>>;\n\n  GlobalActorOutgoingFactory(ChannelIdOrFactory channelIdOrFactory,\n      jsg::Ref<DurableObjectId> id,\n      kj::Maybe<kj::String> locationHint,\n      ActorGetMode mode,\n      bool enableReplicaRouting,\n      ActorRoutingMode routingMode,\n      kj::Maybe<ActorVersion> version)\n      : channelIdOrFactory(kj::mv(channelIdOrFactory)),\n        id(kj::mv(id)),\n        locationHint(kj::mv(locationHint)),\n        mode(mode),\n        enableReplicaRouting(enableReplicaRouting),\n        routingMode(routingMode),\n        version(kj::mv(version)) {}\n\n  kj::Own<WorkerInterface> newSingleUseClient(kj::Maybe<kj::String> cfStr) override;\n\n private:\n  ChannelIdOrFactory channelIdOrFactory;\n  jsg::Ref<DurableObjectId> id;\n  kj::Maybe<kj::String> locationHint;\n  ActorGetMode mode;\n  bool enableReplicaRouting;\n  ActorRoutingMode routingMode;\n  kj::Maybe<ActorVersion> version;\n  kj::Maybe<kj::Own<IoChannelFactory::ActorChannel>> actorChannel;\n};\n\n// Like `GlobalActorOutgoingFactory`, but for colo-local actors\nclass LocalActorOutgoingFactory final: public Fetcher::OutgoingFactory {\n public:\n  LocalActorOutgoingFactory(uint channelId, kj::String actorId)\n      : channelId(channelId),\n        actorId(kj::mv(actorId)) {}\n\n  kj::Own<WorkerInterface> newSingleUseClient(kj::Maybe<kj::String> cfStr) override;\n\n private:\n  uint channelId;\n  kj::String actorId;\n  kj::Maybe<kj::Own<IoChannelFactory::ActorChannel>> actorChannel;\n};\n\n// Like `GlobalActorOutgoingFactory`, but only used for creating a stub to the primary DO so the\n// stub can be given to a replica.\n//\n// The main distinction here is we already have the capability to the primary, so we don't need to\n// make an outgoing request to set things up.\nclass ReplicaActorOutgoingFactory final: public Fetcher::OutgoingFactory {\n public:\n  ReplicaActorOutgoingFactory(kj::Own<IoChannelFactory::ActorChannel> channel, kj::String actorId)\n      : actorChannel(kj::mv(channel)),\n        actorId(kj::mv(actorId)) {}\n\n  kj::Own<WorkerInterface> newSingleUseClient(kj::Maybe<kj::String> cfStr) override;\n\n private:\n  kj::Own<IoChannelFactory::ActorChannel> actorChannel;\n  kj::String actorId;\n};\n\n// DurableObjectClass represents a binding to a Durable Object class that can be used\n// as a facet. The only use of this type is to pass to `ctx.facets.get()`.\nclass DurableObjectClass: public jsg::Object {\n public:\n  DurableObjectClass(uint channel): channel(channel) {}\n  DurableObjectClass(IoOwn<IoChannelFactory::ActorClassChannel> channel)\n      : channel(kj::mv(channel)) {}\n\n  kj::Own<IoChannelFactory::ActorClassChannel> getChannel(IoContext& ioctx);\n\n  JSG_RESOURCE_TYPE(DurableObjectClass) {\n    // No methods - this is just a handle that gets passed to ctx.facets.get()\n\n    JSG_TS_OVERRIDE(\n      interface DurableObjectClass<\n        _T extends Rpc.DurableObjectBranded | undefined = undefined\n      > {}\n    );\n  }\n\n  void serialize(jsg::Lock& js, jsg::Serializer& serializer);\n  static jsg::Ref<DurableObjectClass> deserialize(\n      jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer);\n\n  JSG_SERIALIZABLE(rpc::SerializationTag::ACTOR_CLASS);\n\n private:\n  kj::OneOf<uint, IoOwn<IoChannelFactory::ActorClassChannel>> channel;\n};\n\n#define EW_ACTOR_ISOLATE_TYPES                                                                     \\\n  api::ColoLocalActorNamespace, api::DurableObject, api::DurableObjectId,                          \\\n      api::DurableObjectNamespace, api::DurableObjectNamespace::NewUniqueIdOptions,                \\\n      api::DurableObjectNamespace::GetDurableObjectOptions, api::DurableObjectClass,               \\\n      api::DurableObjectNamespace::GetDurableObjectOptions::VersionOptions\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/analytics-engine-impl.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api {\n\nconstexpr uint MAX_INDEXES_LENGTH = 1;\nconstexpr size_t MAX_INDEX_SIZE_IN_BYTES = 96;\nconstexpr uint MAX_ARRAY_MEMBERS = 20;\nconstexpr size_t MAX_CUMULATIVE_BYTES_IN_BLOBS = 800 * MAX_ARRAY_MEMBERS;\n\ntemplate <typename Message>\nvoid setDoubles(Message msg, kj::ArrayPtr<double> arr, kj::StringPtr errorPrefix) {\n  JSG_REQUIRE(arr.size() <= MAX_ARRAY_MEMBERS, TypeError, errorPrefix, \"Maximum of \",\n      MAX_ARRAY_MEMBERS, \" doubles supported.\");\n\n  uint index = 1;\n  for (auto& item: arr) {\n    switch (index) {\n      case 1:\n        msg.setDouble1(item);\n        break;\n      case 2:\n        msg.setDouble2(item);\n        break;\n      case 3:\n        msg.setDouble3(item);\n        break;\n      case 4:\n        msg.setDouble4(item);\n        break;\n      case 5:\n        msg.setDouble5(item);\n        break;\n      case 6:\n        msg.setDouble6(item);\n        break;\n      case 7:\n        msg.setDouble7(item);\n        break;\n      case 8:\n        msg.setDouble8(item);\n        break;\n      case 9:\n        msg.setDouble9(item);\n        break;\n      case 10:\n        msg.setDouble10(item);\n        break;\n      case 11:\n        msg.setDouble11(item);\n        break;\n      case 12:\n        msg.setDouble12(item);\n        break;\n      case 13:\n        msg.setDouble13(item);\n        break;\n      case 14:\n        msg.setDouble14(item);\n        break;\n      case 15:\n        msg.setDouble15(item);\n        break;\n      case 16:\n        msg.setDouble16(item);\n        break;\n      case 17:\n        msg.setDouble17(item);\n        break;\n      case 18:\n        msg.setDouble18(item);\n        break;\n      case 19:\n        msg.setDouble19(item);\n        break;\n      case 20:\n        msg.setDouble20(item);\n        break;\n    }\n    index++;\n  }\n}\n\ntemplate <typename Message>\nvoid setBlobs(Message msg,\n    kj::ArrayPtr<kj::Maybe<kj::OneOf<kj::Array<kj::byte>, kj::String>>> arr,\n    kj::StringPtr errorPrefix) {\n  JSG_REQUIRE(arr.size() <= MAX_ARRAY_MEMBERS, TypeError, errorPrefix, \"Maximum of \",\n      MAX_ARRAY_MEMBERS, \" blobs supported.\");\n  kj::ArrayPtr<kj::byte> value;\n  uint index = 1;\n  size_t sizeSum = 0;\n  for (auto& item: arr) {\n    KJ_IF_SOME(i, item) {\n      KJ_SWITCH_ONEOF(i) {\n        KJ_CASE_ONEOF(val, kj::Array<kj::byte>) {\n          value = val.asBytes();\n        }\n        KJ_CASE_ONEOF(val, kj::String) {\n          value = val.asBytes();\n        }\n      }\n      sizeSum += value.size();\n      JSG_REQUIRE(sizeSum <= MAX_CUMULATIVE_BYTES_IN_BLOBS, TypeError, errorPrefix,\n          \"Cumulative size of blobs exceeds \", MAX_CUMULATIVE_BYTES_IN_BLOBS, \" bytes).\");\n      switch (index) {\n        case 1:\n          msg.setBlob1(value);\n          break;\n        case 2:\n          msg.setBlob2(value);\n          break;\n        case 3:\n          msg.setBlob3(value);\n          break;\n        case 4:\n          msg.setBlob4(value);\n          break;\n        case 5:\n          msg.setBlob5(value);\n          break;\n        case 6:\n          msg.setBlob6(value);\n          break;\n        case 7:\n          msg.setBlob7(value);\n          break;\n        case 8:\n          msg.setBlob8(value);\n          break;\n        case 9:\n          msg.setBlob9(value);\n          break;\n        case 10:\n          msg.setBlob10(value);\n          break;\n        case 11:\n          msg.setBlob11(value);\n          break;\n        case 12:\n          msg.setBlob12(value);\n          break;\n        case 13:\n          msg.setBlob13(value);\n          break;\n        case 14:\n          msg.setBlob14(value);\n          break;\n        case 15:\n          msg.setBlob15(value);\n          break;\n        case 16:\n          msg.setBlob16(value);\n          break;\n        case 17:\n          msg.setBlob17(value);\n          break;\n        case 18:\n          msg.setBlob18(value);\n          break;\n        case 19:\n          msg.setBlob19(value);\n          break;\n        case 20:\n          msg.setBlob20(value);\n          break;\n      }\n    }\n    index++;\n  }\n}\n\ntemplate <typename Message>\nvoid setIndexes(Message msg,\n    kj::ArrayPtr<kj::Maybe<kj::OneOf<kj::Array<kj::byte>, kj::String>>> arr,\n    kj::StringPtr errorPrefix) {\n  JSG_REQUIRE(arr.size() <= MAX_INDEXES_LENGTH, TypeError, errorPrefix, \"Maximum of \",\n      MAX_INDEXES_LENGTH, \" indexes supported.\");\n  if (arr.size() == 0) {\n    return;\n  }\n  auto item = kj::mv(arr[0]);\n  kj::ArrayPtr<kj::byte> value;\n  KJ_IF_SOME(i, item) {\n    KJ_SWITCH_ONEOF(i) {\n      KJ_CASE_ONEOF(val, kj::Array<kj::byte>) {\n        value = val.asBytes();\n      }\n      KJ_CASE_ONEOF(val, kj::String) {\n        value = val.asBytes();\n      }\n    }\n    JSG_REQUIRE(value.size() <= MAX_INDEX_SIZE_IN_BYTES, TypeError, errorPrefix,\n        \"Size of indexes[0] exceeds \", MAX_INDEX_SIZE_IN_BYTES, \" bytes).\");\n    msg.setIndex1(value);\n  }\n}\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/analytics-engine.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"analytics-engine.h\"\n\n#include <workerd/api/analytics-engine-impl.h>\n#include <workerd/api/analytics-engine.capnp.h>\n#include <workerd/io/io-context.h>\n\nnamespace workerd::api {\n\nvoid AnalyticsEngine::writeDataPoint(\n    jsg::Lock& js, jsg::Optional<api::AnalyticsEngine::AnalyticsEngineEvent> event) {\n  auto& context = IoContext::current();\n\n  context.getLimitEnforcer().newAnalyticsEngineRequest();\n\n  // Optimization: For non-actors, which never have output locks, avoid the overhead of\n  // awaitIo() and such by not going back to the event loop at all.\n  KJ_IF_SOME(promise, context.waitForOutputLocksIfNecessary()) {\n    context.awaitIo(js, kj::mv(promise), [this, event = kj::mv(event)](jsg::Lock& js) mutable {\n      writeDataPointNoOutputLock(js, kj::mv(event));\n    });\n  } else {\n    writeDataPointNoOutputLock(js, kj::mv(event));\n  }\n}\n\nvoid AnalyticsEngine::writeDataPointNoOutputLock(\n    jsg::Lock& js, jsg::Optional<api::AnalyticsEngine::AnalyticsEngineEvent>&& event) {\n  auto& context = IoContext::current();\n\n  context.writeLogfwdr(logfwdrChannel, [&](capnp::AnyPointer::Builder ptr) {\n    api::AnalyticsEngineEvent::Builder aeEvent = ptr.initAs<api::AnalyticsEngineEvent>();\n\n    aeEvent.setAccountId(static_cast<int64_t>(ownerId));\n    aeEvent.setTimestamp(now());\n    aeEvent.setDataset(dataset.asBytes());\n    aeEvent.setSchemaVersion(version);\n    // `index1` should default to the empty string (`\"\"`).\n    // The optional call to `setIndexes()`below assumes defaults, if any.\n    aeEvent.setIndex1(\"\"_kj.asBytes());\n\n    kj::StringPtr errorPrefix = \"writeDataPoint(): \"_kj;\n    KJ_IF_SOME(ev, event) {\n      KJ_IF_SOME(indexes, ev.indexes) {\n        setIndexes<api::AnalyticsEngineEvent::Builder>(aeEvent, indexes, errorPrefix);\n      }\n      KJ_IF_SOME(blobs, ev.blobs) {\n        setBlobs<api::AnalyticsEngineEvent::Builder>(aeEvent, blobs, errorPrefix);\n      }\n      KJ_IF_SOME(doubles, ev.doubles) {\n        setDoubles<api::AnalyticsEngineEvent::Builder>(aeEvent, doubles, errorPrefix);\n      }\n    }\n  });\n}\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/analytics-engine.capnp",
    "content": "# Copyright (c) 2017-2022 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n@0x8fe697b0d6269a23;\n\nusing Cxx = import \"/capnp/c++.capnp\";\n$Cxx.namespace(\"workerd::api\");\n$Cxx.allowCancellation;\n\n# ========================================================================================\n# DO NOT MODIFY BELOW THIS COMMENT -- except if copying from the authoritative version.\n#\n# This protocol belongs to the Data team. Any modifications must be made in the data/schemas\n# repo, and then copied here *after* it has been merged there. Any annotations relating to\n# languages other than C++ should be removed after copying.\n# ========================================================================================\n\nstruct AnalyticsEngineEvent {\n  # Canonical struct for sending events to Analytics Engine\n\n  # Cloudflare numeric account ID.\n  accountId @0 :Int64;\n\n  # Name of the dataset this belongs to.\n  # For instance, weather measurements or website visits.\n  dataset @1 :Data;\n\n  # For tracking breaking changes to the schema\n  schemaVersion @2 :Int64;\n\n  # Secondary piece of data by which to sample and index events.\n  # For example, Cloudflare zone ID or Waiting Room ID.\n  # Cannot be longer than 64 bytes.\n  # We anticipate allowing users to configure multiple data dimensions\n  # in future, requiring an `index2` and maybe `index3`, hence the name\n  # ending with `1`.\n  index1 @3 :Data;\n\n  # Timestamp in nanoseconds\n  timestamp @4 :Int64;\n\n  double1 @5 :Float64;\n  double2 @6 :Float64;\n  double3 @7 :Float64;\n  double4 @8 :Float64;\n  double5 @9 :Float64;\n  double6 @10 :Float64;\n  double7 @11 :Float64;\n  double8 @12 :Float64;\n  double9 @13 :Float64;\n  double10 @14 :Float64;\n  double11 @15 :Float64;\n  double12 @16 :Float64;\n  double13 @17 :Float64;\n  double14 @18 :Float64;\n  double15 @19 :Float64;\n  double16 @20 :Float64;\n  double17 @21 :Float64;\n  double18 @22 :Float64;\n  double19 @23 :Float64;\n  double20 @24 :Float64;\n\n  # The total length of all blobs cannot exceed 256*20 = 5120 bytes.\n  blob1 @25 :Data;\n  blob2 @26 :Data;\n  blob3 @27 :Data;\n  blob4 @28 :Data;\n  blob5 @29 :Data;\n  blob6 @30 :Data;\n  blob7 @31 :Data;\n  blob8 @32 :Data;\n  blob9 @33 :Data;\n  blob10 @34 :Data;\n  blob11 @35 :Data;\n  blob12 @36 :Data;\n  blob13 @37 :Data;\n  blob14 @38 :Data;\n  blob15 @39 :Data;\n  blob16 @40 :Data;\n  blob17 @41 :Data;\n  blob18 @42 :Data;\n  blob19 @43 :Data;\n  blob20 @44 :Data;\n}\n"
  },
  {
    "path": "src/workerd/api/analytics-engine.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/io/io-util.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api {\n\n// Analytics Engine is a tool for customers to get telemetry about anything\n// using Workers. The data points gathered from the edge are stored into\n// ClickHouse and can be queried through the Analytics Engine's SQL API.\n//\n// The generated data points are encoded through the\n// analytics_engine_event.capnp format and sent to logfwdr for them to enter\n// the Data Pipeline. Each data point consists of an array of index values, 20\n// numeric fields (doubles) and 20 text fields (blobs), alongside some\n// metadata. Aside from ordinality and maximum length, the semantics of the\n// `blobs` and `doubles` fields are left up to applications submitting\n// messages.\n//\n// https://blog.cloudflare.com/workers-analytics-engine/\nclass AnalyticsEngine: public jsg::Object {\n public:\n  explicit AnalyticsEngine(\n      uint logfwdrChannel, kj::String dataset, int64_t version, uint32_t ownerId)\n      : logfwdrChannel(logfwdrChannel),\n        dataset(kj::mv(dataset)),\n        version(version),\n        ownerId(ownerId) {}\n  struct AnalyticsEngineEvent {\n    // An array of values for the user-defined indexes, that provide a way for\n    // users to improve the efficiency of common queries. In addition, by\n    // default, the sampling key includes all the indexes in the list. This\n    // gives users some control over the way data is sampled.\n    jsg::Optional<kj::Array<kj::Maybe<kj::OneOf<kj::Array<byte>, kj::String>>>> indexes;\n    jsg::Optional<kj::Array<double>> doubles;\n    jsg::Optional<kj::Array<kj::Maybe<kj::OneOf<kj::Array<byte>, kj::String>>>> blobs;\n\n    // The ordering of the elements within the `doubles` and `blobs` fields matters, insofar as the\n    // elements within each array will be unrolled, and based on the element's ordinality, the\n    // `.set{Blob,Data}{1..20}(element)` method is invoked. Because of this, these arrays can each\n    // have a maximum of 20 elements.\n\n    JSG_STRUCT(indexes, doubles, blobs);\n    JSG_STRUCT_TS_OVERRIDE(AnalyticsEngineDataPoint);\n  };\n\n  // Send an Analytics Engine-compatible event to the configured logfwdr socket.\n  // Like logfwdr itself, `writeDataPoint` makes no delivery guarantees.\n  void writeDataPoint(\n      jsg::Lock& js, jsg::Optional<api::AnalyticsEngine::AnalyticsEngineEvent> event);\n\n  JSG_RESOURCE_TYPE(AnalyticsEngine) {\n    JSG_METHOD(writeDataPoint);\n    JSG_TS_ROOT();\n    JSG_TS_OVERRIDE(AnalyticsEngineDataset);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"dataset\", dataset);\n  }\n\n private:\n  double millisToNanos(double m) {\n    return m * 1000000;\n  }\n\n  // Called within writeDataPoint after waiting for output locks\n  void writeDataPointNoOutputLock(\n      jsg::Lock& js, jsg::Optional<api::AnalyticsEngine::AnalyticsEngineEvent>&& event);\n\n  uint logfwdrChannel;\n  kj::String dataset;\n  int64_t version;\n  uint32_t ownerId;\n\n  uint64_t now() {\n    return millisToNanos(dateNow());\n  }\n};\n#define EW_ANALYTICS_ENGINE_ISOLATE_TYPES                                                          \\\n  ::workerd::api::AnalyticsEngine, ::workerd::api::AnalyticsEngine::AnalyticsEngineEvent\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/api-rtti-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/api/actor-state.h>\n#include <workerd/api/actor.h>\n#include <workerd/api/cache.h>\n#include <workerd/api/crypto/crypto.h>\n#include <workerd/api/encoding.h>\n#include <workerd/api/events.h>\n#include <workerd/api/eventsource.h>\n#include <workerd/api/global-scope.h>\n#include <workerd/api/html-rewriter.h>\n#include <workerd/api/queue.h>\n#include <workerd/api/scheduled.h>\n#include <workerd/api/sockets.h>\n#include <workerd/api/sql.h>\n#include <workerd/api/streams.h>\n#include <workerd/api/streams/standard.h>\n#include <workerd/api/sync-kv.h>\n#include <workerd/api/trace.h>\n#include <workerd/api/url-standard.h>\n#include <workerd/api/urlpattern-standard.h>\n#include <workerd/api/urlpattern.h>\n#include <workerd/io/compatibility-date.h>\n#include <workerd/jsg/rtti.h>\n\n#include <kj/test.h>\n\n// Test building rtti for various APIs.\n\nnamespace workerd::api {\nnamespace {\n\nKJ_TEST(\"WorkerGlobalScope\") {\n  jsg::rtti::Builder builder((CompatibilityFlags::Reader()));\n  builder.structure<WorkerGlobalScope>();\n  KJ_EXPECT(builder.structure(\"workerd::api::Event\"_kj) != kj::none);\n  KJ_EXPECT(builder.structure(\"workerd::api::ObviouslyWrongName\"_kj) == kj::none);\n}\n\nKJ_TEST(\"ServiceWorkerGlobalScope\") {\n  jsg::rtti::Builder builder((CompatibilityFlags::Reader()));\n  builder.structure<ServiceWorkerGlobalScope>();\n  KJ_EXPECT(builder.structure(\"workerd::api::DurableObjectId\"_kj) != kj::none);\n}\n\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/base64-test.c++",
    "content": "#include \"base64.h\"\n#include \"workerd/tests/test-fixture.h\"\n\n#include <kj/encoding.h>\n#include <kj/test.h>\n\nnamespace workerd::api {\nnamespace {\n\nKJ_TEST(\"base64 encode\") {\n  TestFixture t;\n\n  t.runInIoContext([](const workerd::TestFixture::Environment& env) {\n    auto b = Base64Module();\n    auto backing = jsg::BackingStore::alloc(env.js, 1);\n    backing.asArrayPtr().begin()[0] = 'A';\n    auto ret = b.encodeArray(env.js, jsg::BufferSource(env.js, kj::mv(backing)));\n    KJ_ASSERT(ret.asArrayPtr() == \"QQ==\"_kjb);\n  });\n}\n\nKJ_TEST(\"base64 valid decode\") {\n  TestFixture t;\n\n  t.runInIoContext([](const workerd::TestFixture::Environment& env) {\n    auto b = Base64Module();\n    auto backing = jsg::BackingStore::alloc(env.js, 4);\n    backing.asArrayPtr().copyFrom(\"QQ==\"_kjb);\n    auto ret = b.decodeArray(env.js, jsg::BufferSource(env.js, kj::mv(backing)));\n    KJ_ASSERT(ret.asArrayPtr() == \"A\"_kjb);\n  });\n}\n\nKJ_TEST(\"base64 invalid decode\") {\n  TestFixture t;\n\n  t.runInIoContext([](const workerd::TestFixture::Environment& env) {\n    auto b = Base64Module();\n    auto backing = jsg::BackingStore::alloc(env.js, 14);\n    auto ptr = backing.asArrayPtr();\n    ptr.copyFrom(\"INVALID BASE64\"_kjb);\n    try {\n      auto ret = b.decodeArray(env.js, jsg::BufferSource(env.js, kj::mv(backing)));\n      KJ_UNREACHABLE;\n    } catch (kj::Exception& e) {\n      KJ_EXPECT(e.getDescription().contains(\"jsg.DOMException(SyntaxError): Invalid base64\"_kj));\n    }\n  });\n}\n\nKJ_TEST(\"base64 decode as string\") {\n  TestFixture t;\n\n  t.runInIoContext([](const workerd::TestFixture::Environment& env) {\n    auto b = Base64Module();\n    auto backing = jsg::BackingStore::alloc(env.js, 1);\n    backing.asArrayPtr().begin()[0] = 'A';\n    KJ_ASSERT(b.encodeArrayToString(env.lock, jsg::BufferSource(env.js, kj::mv(backing))) ==\n        env.js.str(\"QQ==\"_kj));\n  });\n}\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/base64.c++",
    "content": "#include \"base64.h\"\n\n#include \"simdutf.h\"\n\n#include <kj/debug.h>\n\nnamespace workerd::api {\n\njsg::BufferSource Base64Module::decodeArray(jsg::Lock& js, jsg::BufferSource input) {\n  auto ptr = input.asArrayPtr();\n  auto size = simdutf::maximal_binary_length_from_base64(\n      reinterpret_cast<const char*>(ptr.begin()), ptr.size());\n  auto buf = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, size);\n  auto result = simdutf::base64_to_binary(ptr.asChars().begin(), input.size(),\n      buf.asArrayPtr().asChars().begin(), simdutf::base64_default);\n  JSG_REQUIRE(result.error == simdutf::SUCCESS, DOMSyntaxError,\n      kj::str(\"Invalid base64 at position \", result.count, \": \",\n          simdutf::error_to_string(result.error).data()));\n  KJ_ASSERT(result.count <= size);\n  buf.trim(buf.size() - result.count);\n  return jsg::BufferSource(js, kj::mv(buf));\n}\n\njsg::BufferSource Base64Module::encodeArray(jsg::Lock& js, jsg::BufferSource input) {\n  auto size = simdutf::base64_length_from_binary(input.size());\n  auto buf = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, size);\n  auto out_size = simdutf::binary_to_base64(input.asArrayPtr().asChars().begin(), input.size(),\n      buf.asArrayPtr().asChars().begin(), simdutf::base64_default);\n  KJ_ASSERT(out_size <= size);\n  buf.limit(out_size);\n  return jsg::BufferSource(js, kj::mv(buf));\n}\n\njsg::JsString Base64Module::encodeArrayToString(jsg::Lock& js, jsg::BufferSource input) {\n  KJ_ASSERT(input.size() < 256 * 1024 * 1024);\n  auto size = simdutf::base64_length_from_binary(input.size());\n  auto buf = jsg::BackingStore::alloc(js, size);\n  auto out_size = simdutf::binary_to_base64(input.asArrayPtr().asChars().begin(), input.size(),\n      buf.asArrayPtr().asChars().begin(), simdutf::base64_default);\n  KJ_ASSERT(out_size <= size);\n  return js.str(buf.asArrayPtr().first(out_size));\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/base64.h",
    "content": "#pragma once\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/modules-new.h>\n#include <workerd/jsg/url.h>\n\nnamespace workerd::api {\n\nclass Base64Module final: public jsg::Object {\n public:\n  Base64Module() = default;\n  Base64Module(jsg::Lock&, const jsg::Url&) {}\n\n  jsg::BufferSource decodeArray(jsg::Lock& js, jsg::BufferSource source);\n  jsg::BufferSource encodeArray(jsg::Lock& js, jsg::BufferSource source);\n  jsg::JsString encodeArrayToString(jsg::Lock&, jsg::BufferSource source);\n\n  JSG_RESOURCE_TYPE(Base64Module) {\n    JSG_METHOD(encodeArray);\n    JSG_METHOD(decodeArray);\n    JSG_METHOD(encodeArrayToString);\n    JSG_TS_OVERRIDE({\n      decodeArray(source: ArrayBuffer | ArrayBufferView): ArrayBuffer;\n      encodeArray(source: ArrayBuffer | ArrayBufferView): ArrayBuffer;\n      encodeArrayToString(source: ArrayBuffer | ArrayBufferView): string;\n    });\n  }\n};\n\ntemplate <class Registry>\nvoid registerBase64Module(Registry& registry, auto featureFlags) {\n  registry.template addBuiltinModule<Base64Module>(\n      \"cloudflare-internal:base64\", workerd::jsg::ModuleRegistry::Type::INTERNAL);\n}\n\ntemplate <typename TypeWrapper>\nkj::Own<jsg::modules::ModuleBundle> getInternalBase64ModuleBundle(auto featureFlags) {\n  jsg::modules::ModuleBundle::BuiltinBuilder builder(\n      jsg::modules::ModuleBundle::BuiltinBuilder::Type::BUILTIN_ONLY);\n  static const auto kSpecifier = \"cloudflare-internal:base64\"_url;\n  builder.addObject<Base64Module, TypeWrapper>(kSpecifier);\n  return builder.finish();\n}\n\n#define EW_BASE64_ISOLATE_TYPES api::Base64Module\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/basics-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Filter some stuff from the JSG_RESOURCE_TYPE blocks so that we can actually compile this\n// test without pulling in the world.\n#define WORKERD_API_BASICS_TEST 1\n\n#include \"actor-state.h\"\n#include \"actor.h\"\n#include \"basics.h\"\n#include \"util.h\"\n\n#include <workerd/io/promise-wrapper.h>\n#include <workerd/jsg/jsg-test.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api {\nnamespace {\n\njsg::V8System v8System;\n\nstruct BasicsContext: public jsg::Object, public jsg::ContextGlobal {\n\n  bool testNativeListenersWork(jsg::Lock& js) {\n    auto target = js.alloc<api::EventTarget>();\n\n    int called = 0;\n    bool onceCalled = false;\n\n    // Should be invoked multiple times.\n    auto handler = target->newNativeHandler(js, kj::str(\"foo\"),\n        [&called](jsg::Lock& js, jsg::Ref<api::Event> event) { called++; }, false);\n\n    // Should only be invoked once.\n    auto handlerOnce = target->newNativeHandler(\n        js, kj::str(\"foo\"), [&](jsg::Lock& js, jsg::Ref<api::Event> event) {\n      KJ_ASSERT(!onceCalled);\n      onceCalled = true;\n      // Recursively dispatching the event here should not cause this handler to\n      // be invoked again.\n      target->dispatchEventImpl(js, js.alloc<api::Event>(kj::str(\"foo\")));\n    }, true);\n\n    KJ_ASSERT(target->dispatchEventImpl(js, js.alloc<api::Event>(kj::str(\"foo\"))));\n    KJ_ASSERT(target->dispatchEventImpl(js, js.alloc<api::Event>(kj::str(\"foo\"))));\n    KJ_ASSERT(onceCalled);\n    return called == 3;\n  }\n\n  bool testCanAddHandlersInHandlers(jsg::Lock& js) {\n    // Exercises a use case that triggered asan failures in earlier implementations.\n    auto target = js.alloc<api::EventTarget>();\n    int toplevelCalls = 0;\n    int otherCalls = 0;\n    kj::Vector<kj::Own<void>> handlers;\n\n    handlers.add(target->newNativeHandler(\n        js, kj::str(\"foo\"), [&](jsg::Lock& js, jsg::Ref<api::Event> event) {\n      toplevelCalls++;\n\n      for (int i = 0; i < 16; ++i) {\n        handlers.add(target->newNativeHandler(js, kj::str(\"foo\", i),\n            [&](jsg::Lock& js, jsg::Ref<api::Event> event) { otherCalls++; }, false));\n      }\n    }, false));\n\n    handlers.add(target->newNativeHandler(js, kj::str(\"foo\"),\n        [&](jsg::Lock& js, jsg::Ref<api::Event> event) { toplevelCalls++; }, false));\n\n    KJ_ASSERT(target->dispatchEventImpl(js, js.alloc<api::Event>(kj::str(\"foo\"))));\n\n    KJ_ASSERT(toplevelCalls == 2);\n    KJ_ASSERT(otherCalls == 0);\n    return true;\n  }\n\n  JSG_RESOURCE_TYPE(BasicsContext) {\n    JSG_METHOD(testNativeListenersWork);\n    JSG_METHOD(testCanAddHandlersInHandlers);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(BasicsIsolate,\n    BasicsContext,\n    EW_BASICS_ISOLATE_TYPES,\n    jsg::TypeWrapperExtension<PromiseWrapper>);\n\nKJ_TEST(\"EventTarget native listeners work\") {\n  jsg::test::Evaluator<BasicsContext, BasicsIsolate, CompatibilityFlags::Reader> e(v8System);\n  e.expectEval(\"testNativeListenersWork()\", \"boolean\", \"true\");\n}\n\nKJ_TEST(\"EventTarget can add handlers in handlers\") {\n  jsg::test::Evaluator<BasicsContext, BasicsIsolate, CompatibilityFlags::Reader> e(v8System);\n  e.expectEval(\"testCanAddHandlersInHandlers()\", \"boolean\", \"true\");\n}\n\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/basics.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"basics.h\"\n\n#include \"actor-state.h\"\n#include \"global-scope.h\"\n\n#include <workerd/io/features.h>\n#include <workerd/io/io-context.h>\n\n#include <capnp/message.h>\n#include <kj/async.h>\n#include <kj/vector.h>\n\nnamespace workerd::api {\n\nnamespace {\n// \"Special\" events are the global addEventListener(...) events that the runtime itself\n// will emit for various things (e.g. the \"fetch\" event). When using module syntax, these\n// are not emitted as events and instead should be registered as functions on the exported\n// handler. To help make that clearer, if user code calls addEventListener() using one of\n// these special types (only when using module syntax), a warning will be logged to the\n// console.\n// It's important to keep this list in sync with any other top level events that are emitted\n// when in worker syntax but called as exports in module syntax.\nconstexpr bool isSpecialEventType(kj::StringPtr type) {\n  // TODO(someday): How should we cover custom events here? Since it's just for a warning I'm\n  //   leaving them out for now.\n  return type == \"fetch\" || type == \"scheduled\" || type == \"tail\" || type == \"trace\" ||\n      type == \"alarm\";\n}\n}  // namespace\n\nEventTarget::NativeHandler::NativeHandler(\n    jsg::Lock& js, EventTarget& target, kj::String type, jsg::Function<Signature> func, bool once)\n    : type(kj::mv(type)),\n      state(State{\n        .target = target,\n        .func = kj::mv(func),\n      }),\n      once(once) {\n  target.addNativeListener(js, *this);\n}\n\nEventTarget::NativeHandler::~NativeHandler() noexcept(false) {\n  detach();\n}\n\nvoid EventTarget::NativeHandler::operator()(jsg::Lock& js, jsg::Ref<Event> event) {\n  KJ_IF_SOME(s, state) {\n    if (once) {\n      auto fn = kj::mv(s.func);\n      detach();\n      fn(js, kj::mv(event));\n      // Note that the function may have detached itself and caused the NativeHandler\n      // to be destroyed. Let's be careful not to touch it after this point.\n    } else {\n      s.func(js, kj::mv(event));\n    }\n    return;\n  }\n}\n\nuint EventTarget::NativeHandler::hashCode() const {\n  return kj::hashCode(this);\n}\n\nvoid EventTarget::NativeHandler::visitForGc(jsg::GcVisitor& visitor) {\n  KJ_IF_SOME(s, state) {\n    visitor.visit(s.func);\n  }\n}\n\nvoid EventTarget::NativeHandler::detach() {\n  KJ_IF_SOME(s, state) {\n    s.target.removeNativeListener(*this);\n    state = kj::none;\n  }\n}\n\nkj::Own<void> EventTarget::newNativeHandler(\n    jsg::Lock& js, kj::String type, jsg::Function<void(jsg::Ref<Event>)> func, bool once) {\n  return kj::heap<EventTarget::NativeHandler>(js, *this, kj::mv(type), kj::mv(func), once);\n}\n\nconst EventTarget::EventHandler::Handler& EventTarget::EventHandlerHashCallbacks::keyForRow(\n    const kj::Own<EventHandler>& row) const {\n  // The key for each EventHandler struct is the handler, which is a kj::OneOf\n  // of either a JavaScriptHandler or NativeHandler.\n  return row->handler;\n}\n\nbool EventTarget::EventHandlerHashCallbacks::matches(\n    const kj::Own<EventHandler>& a, const jsg::HashableV8Ref<v8::Object>& b) const {\n  KJ_IF_SOME(jsA, a->handler.tryGet<EventHandler::JavaScriptHandler>()) {\n    return jsA.identity == b;\n  }\n  return false;\n}\n\nbool EventTarget::EventHandlerHashCallbacks::matches(\n    const kj::Own<EventHandler>& a, const NativeHandler& b) const {\n  KJ_IF_SOME(ref, a->handler.tryGet<EventHandler::NativeHandlerRef>()) {\n    return &ref.handler == &b;\n  }\n  return false;\n}\n\nbool EventTarget::EventHandlerHashCallbacks::matches(\n    const kj::Own<EventHandler>& a, const EventHandler::NativeHandlerRef& b) const {\n  return matches(a, b.handler);\n}\n\nbool EventTarget::EventHandlerHashCallbacks::matches(\n    const kj::Own<EventHandler>& a, const EventHandler::Handler& b) const {\n  KJ_SWITCH_ONEOF(b) {\n    KJ_CASE_ONEOF(jsB, EventHandler::JavaScriptHandler) {\n      return matches(a, jsB.identity);\n    }\n    KJ_CASE_ONEOF(nativeB, EventHandler::NativeHandlerRef) {\n      return matches(a, nativeB);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nuint EventTarget::EventHandlerHashCallbacks::hashCode(\n    const jsg::HashableV8Ref<v8::Object>& obj) const {\n  return obj.hashCode();\n}\n\nuint EventTarget::EventHandlerHashCallbacks::hashCode(const NativeHandler& handler) const {\n  return handler.hashCode();\n}\n\nuint EventTarget::EventHandlerHashCallbacks::hashCode(\n    const EventHandler::NativeHandlerRef& handler) const {\n  return hashCode(handler.handler);\n}\n\nuint EventTarget::EventHandlerHashCallbacks::hashCode(\n    const EventHandler::JavaScriptHandler& handler) const {\n  return hashCode(handler.identity);\n}\n\nuint EventTarget::EventHandlerHashCallbacks::hashCode(const EventHandler::Handler& handler) const {\n  KJ_SWITCH_ONEOF(handler) {\n    KJ_CASE_ONEOF(js, EventHandler::JavaScriptHandler) {\n      return hashCode(js);\n    }\n    KJ_CASE_ONEOF(native, EventHandler::NativeHandlerRef) {\n      return hashCode(native);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\njsg::Ref<Event> Event::constructor(jsg::Lock& js, kj::String type, jsg::Optional<Init> init) {\n  static const Init defaultInit;\n  return js.alloc<Event>(kj::mv(type), init.orDefault(defaultInit), false /* not trusted */);\n}\n\nkj::StringPtr Event::getType() {\n  return type;\n}\n\nkj::Maybe<jsg::Ref<EventTarget>> Event::getCurrentTarget() {\n  if (flags.isBeingDispatched) {\n    return getTarget();\n  }\n  return kj::none;\n}\n\njsg::Optional<jsg::Ref<EventTarget>> Event::getTarget() {\n  return target.map([&](jsg::Ref<EventTarget>& t) { return t.addRef(); });\n}\n\nkj::Array<jsg::Ref<EventTarget>> Event::composedPath() {\n  if (flags.isBeingDispatched) {\n    // When isBeingDispatched is true, target should always be non-null.\n    // If it's not, there's a bug that we need to know about.\n    return kj::arr(KJ_ASSERT_NONNULL(target).addRef());\n  }\n  return kj::Array<jsg::Ref<EventTarget>>();\n}\n\nvoid Event::beginDispatch(jsg::Ref<EventTarget> target) {\n  JSG_REQUIRE(\n      !flags.isBeingDispatched, DOMInvalidStateError, \"The event is already being dispatched.\");\n  flags.isBeingDispatched = true;\n  this->target = kj::mv(target);\n}\n\njsg::Ref<EventTarget> EventTarget::constructor(jsg::Lock& js) {\n  return js.alloc<EventTarget>();\n}\n\nEventTarget::~EventTarget() noexcept(false) {\n  for (auto& entry: typeMap) {\n    for (auto& handler: entry.value.handlers) {\n      KJ_IF_SOME(native, handler->handler.tryGet<EventHandler::NativeHandlerRef>()) {\n        // Note: Can't call `detach()` here because it would loop back and call\n        // `removeNativeListener()` on us, invalidating the `typeMap` iterator. We'll directly\n        // null out the state.\n        native.handler.state = kj::none;\n      }\n    }\n  }\n}\n\nsize_t EventTarget::getHandlerCount(kj::StringPtr type) const {\n  KJ_IF_SOME(handlerSet, typeMap.find(type)) {\n    return handlerSet.handlers.size();\n  } else {\n    return 0;\n  }\n}\n\nkj::Array<kj::StringPtr> EventTarget::getHandlerNames() const {\n  return KJ_MAP(entry, typeMap) { return entry.key.asPtr(); };\n}\n\nvoid EventTarget::addEventListener(jsg::Lock& js,\n    kj::String type,\n    kj::Maybe<jsg::Identified<Handler>> maybeHandler,\n    jsg::Optional<AddEventListenerOpts> maybeOptions,\n    const jsg::TypeHandler<jsg::Ref<EventTarget>>& eventTargetHandler) {\n  if (flags.warnOnSpecialEvents && isSpecialEventType(type)) {\n    js.logWarning(kj::str(\"When using module syntax, the '\", type,\n        \"' event handler should be \"\n        \"declared as an exported function on the root module as opposed to using \"\n        \"the global addEventListener().\"));\n  }\n\n  KJ_IF_SOME(handler, maybeHandler) {\n    js.withinHandleScope([&] {\n      // Per the spec, the handler can be either a Function, or an object with a\n      // handleEvent member function.\n      HandlerFunction handlerFn = ([&]() {\n        KJ_SWITCH_ONEOF(handler.unwrapped) {\n          KJ_CASE_ONEOF(fn, HandlerFunction) {\n            if (FeatureFlags::get(js).getSetEventTargetThis()) {\n              fn.setReceiver(js.v8Ref(eventTargetHandler.wrap(js, JSG_THIS)));\n            }\n            return kj::mv(fn);\n          }\n          KJ_CASE_ONEOF(obj, HandlerObject) {\n            if (FeatureFlags::get(js).getSetEventTargetThis()) {\n              obj.handleEvent.setReceiver(obj.self.asValue(js));\n            }\n            return kj::mv(obj.handleEvent);\n          }\n        }\n        KJ_UNREACHABLE;\n      })();\n\n      bool once = false;\n      kj::Maybe<jsg::Ref<AbortSignal>> maybeSignal;\n      kj::Maybe<jsg::Ref<AbortSignal>> maybeFollowingSignal;\n      KJ_IF_SOME(value, maybeOptions) {\n        KJ_SWITCH_ONEOF(value) {\n          KJ_CASE_ONEOF(b, bool) {\n            JSG_REQUIRE(!b, TypeError, \"addEventListener(): useCapture must be false.\");\n          }\n          KJ_CASE_ONEOF(opts, AddEventListenerOptions) {\n            JSG_REQUIRE(!opts.capture.orDefault(false), TypeError,\n                \"addEventListener(): options.capture must be false.\");\n            JSG_REQUIRE(!opts.passive.orDefault(false), TypeError,\n                \"addEventListener(): options.passive must be false.\");\n            once = opts.once.orDefault(false);\n            maybeSignal = kj::mv(opts.signal);\n            maybeFollowingSignal = kj::mv(opts.followingSignal);\n          }\n        }\n      }\n      KJ_IF_SOME(signal, maybeSignal) {\n        // If the AbortSignal has already been triggered, then we need to stop here.\n        // Return without adding the event listener.\n        if (signal->getAborted(js)) {\n          return;\n        }\n      }\n\n      auto& set = getOrCreate(type);\n\n      auto maybeAbortHandler = maybeSignal.map([&](jsg::Ref<AbortSignal>& signal) {\n        // The returned native handler captures a bare reference to signal and\n        // will be held by this EventTarget. The signal is the only thing that\n        // triggers it. If signal is gc'd the native handler created here could\n        // still be alive which means *technically* it will be holding a bare\n        // reference for something that is already destroyed. However, there's\n        // nothing else that would trigger it so it's generally safe-ish. That\n        // said, it's still a potential UAF so let's guard against it by attaching\n        // a strong reference to the signal to the event handler. This will mean\n        // likely keeping the signal in memory longer if it can otherwise be\n        // gc'd but that's ok, the impact should be minimal.\n        auto func =\n            JSG_VISITABLE_LAMBDA((this, type = kj::mv(type), handler = handler.identity.addRef(js),\n                                     signal = signal.addRef()),\n                (handler, signal), (jsg::Lock& js, jsg::Ref<Event>) {\n                  removeEventListener(js, kj::mv(type), kj::mv(handler), kj::none);\n                });\n\n        return signal->newNativeHandler(js, kj::str(\"abort\"), kj::mv(func), true);\n      });\n\n      auto eventHandler = kj::heap<EventHandler>(\n          EventHandler::JavaScriptHandler{\n            .identity = kj::mv(handler.identity),\n            .callback = kj::mv(handlerFn),\n            .abortHandler = kj::mv(maybeAbortHandler),\n          },\n          once);\n\n      // If maybeFollowingSignal is set, we need to attach it to the event handler\n      // in order to keep it alive. This is used only for AbortSignal.any() where\n      // the followed signal (this) is being followed by another signal. We need\n      // to make sure the following signal stays alive until either the followed\n      // signal is triggered or destroyed.\n      KJ_IF_SOME(following, maybeFollowingSignal) {\n        eventHandler = eventHandler.attach(kj::mv(following));\n      }\n\n      set.handlers.upsert(kj::mv(eventHandler), [&](auto&&...) {});\n    });\n  }\n}\n\nvoid EventTarget::removeEventListener(jsg::Lock& js,\n    kj::String type,\n    kj::Maybe<jsg::HashableV8Ref<v8::Object>> maybeHandler,\n    jsg::Optional<EventListenerOpts> maybeOptions) {\n  KJ_IF_SOME(value, maybeOptions) {\n    KJ_SWITCH_ONEOF(value) {\n      KJ_CASE_ONEOF(b, bool) {\n        JSG_REQUIRE(!b, TypeError, \"removeEventListener(): useCapture must be false.\");\n      }\n      KJ_CASE_ONEOF(opts, EventListenerOptions) {\n        JSG_REQUIRE(!opts.capture.orDefault(false), TypeError,\n            \"removeEventListener(): options.capture must be false.\");\n      }\n    }\n  }\n\n  KJ_IF_SOME(handler, maybeHandler) {\n    js.withinHandleScope([&] {\n      KJ_IF_SOME(handlerSet, typeMap.find(type)) {\n        handlerSet.handlers.eraseMatch(handler);\n      }\n    });\n  }\n}\n\nvoid EventTarget::addNativeListener(jsg::Lock& js, NativeHandler& handler) {\n  auto& set = getOrCreate(handler.type);\n\n  auto eventHandler = kj::heap<EventHandler>(\n      EventHandler::NativeHandlerRef{\n        .handler = handler,\n      },\n      handler.once);\n\n  set.handlers.upsert(kj::mv(eventHandler), [&](auto&&...) {});\n}\n\nbool EventTarget::removeNativeListener(EventTarget::NativeHandler& handler) {\n  KJ_IF_SOME(handlerSet, typeMap.find(handler.type)) {\n    return handlerSet.handlers.eraseMatch(handler);\n  }\n  return false;\n}\n\nEventTarget::EventHandlerSet& EventTarget::getOrCreate(kj::StringPtr type) {\n  return typeMap.upsert(kj::str(type), EventHandlerSet(), [&](auto&&...) {}).value;\n}\n\nbool EventTarget::dispatchEventImpl(jsg::Lock& js, jsg::Ref<Event> event) {\n  event->beginDispatch(JSG_THIS);\n  KJ_DEFER(event->endDispatch());\n\n  event->clearPreventDefault();\n\n  // First, gather all the function handles that we plan to call. This is important to ensure that\n  // the callback can add or remove listeners without affecting the current event's processing.\n\n  return js.withinHandleScope([&] {\n    struct Callback {\n      EventHandler::Handler handler;\n      bool once = false;\n      bool oldStyle = false;\n    };\n\n    kj::Vector<Callback> callbacks;\n\n    // Check if there is an `on<event>` property on this object. If so, we treat that as an event\n    // handler, in addition to the ones registered with addEventListener().\n    KJ_IF_SOME(onProp, onEvents.get(js, kj::str(\"on\", event->getType()))) {\n      // If the on-event is not a function, we silently ignore it rather than raise an error.\n      KJ_IF_SOME(cb, onProp.tryGet<HandlerFunction>()) {\n        callbacks.add(Callback{\n          .handler =\n              EventHandler::JavaScriptHandler{\n                .identity = nullptr,  // won't be used below if oldStyle is true and once is false\n                .callback = kj::mv(cb),\n              },\n          .oldStyle = true,\n        });\n      }\n    }\n\n    KJ_IF_SOME(handlerSet, typeMap.find(event->getType())) {\n      callbacks.reserve(handlerSet.handlers.size());\n      for (auto& handler: handlerSet.handlers.ordered<kj::InsertionOrderIndex>()) {\n        KJ_SWITCH_ONEOF(handler->handler) {\n          KJ_CASE_ONEOF(jsh, EventHandler::JavaScriptHandler) {\n            callbacks.add(Callback{\n              .handler = EventHandler::JavaScriptHandler{.identity = jsh.identity.addRef(js),\n                .callback = jsh.callback.addRef(js)},\n              .once = handler->once,\n            });\n          }\n          KJ_CASE_ONEOF(native, EventHandler::NativeHandlerRef) {\n            callbacks.add(Callback{\n              .handler =\n                  EventHandler::NativeHandlerRef{\n                    .handler = native.handler,\n                  },\n              .once = handler->once,\n            });\n          }\n        }\n      }\n    }\n\n    const auto isRemoved = [&](auto& handler) {\n      // This is not the most efficient way to do this but it's what works right now.\n      // Instead of capturing direct references to the handler structs, we copy those\n      // into the Callbacks vector, which means we need to look up the actual handler\n      // again to see if it still exists in the list. The entire way the storage of the\n      // handlers is done here can be improved to make this more efficient.\n\n      KJ_IF_SOME(handlerSet, typeMap.find(event->getType())) {\n        KJ_SWITCH_ONEOF(handler) {\n          KJ_CASE_ONEOF(js, EventHandler::JavaScriptHandler) {\n            return handlerSet.handlers.find(js.identity) == kj::none;\n          }\n          KJ_CASE_ONEOF(native, EventHandler::NativeHandlerRef) {\n            return handlerSet.handlers.find(native.handler) == kj::none;\n          }\n        }\n      } else {\n        return true;\n      }\n      KJ_UNREACHABLE;\n    };\n\n    for (auto& callback: callbacks) {\n      if (event->isStopped()) {\n        // stopImmediatePropagation() was called; don't call any further listeners\n        break;\n      }\n\n      // If the handler gets removed by an earlier run handler, then we need to\n      // make sure we don't run it. Skip over and continue.\n      if (!callback.oldStyle && isRemoved(callback.handler)) {\n        continue;\n      }\n\n      if (callback.once) {\n        KJ_SWITCH_ONEOF(callback.handler) {\n          KJ_CASE_ONEOF(jsh, EventHandler::JavaScriptHandler) {\n            removeEventListener(js, kj::str(event->getType()), jsh.identity.addRef(js), kj::none);\n          }\n          KJ_CASE_ONEOF(native, EventHandler::NativeHandlerRef) {\n            // The native handler will handle detaching itself when invoked\n          }\n        }\n      }\n\n      KJ_SWITCH_ONEOF(callback.handler) {\n        KJ_CASE_ONEOF(jsh, EventHandler::JavaScriptHandler) {\n          // Per the standard, the event listener is not supposed to return any value, and if it\n          // does, that value is ignored. That can be somewhat problematic if the user passes an\n          // async function as the event handler. Doing so counts as undefined behavior and can\n          // introduce subtle and difficult to diagnose bugs. Here, if the handler does return a\n          // value, we're going to emit a warning but otherwise ignore it. The warning will only\n          // be emitted at most once per EventEmitter instance.\n          auto ret = jsh.callback(js, event.addRef());\n          // Note: We used to run each handler in its own v8::TryCatch. However, due to a\n          //   misunderstanding of the V8 API, we incorrectly believed that TryCatch mishandled\n          //   termination (or maybe it actually did at the time), so we changed things such that\n          //   we don't catch exceptions so the first handler to throw an exception terminates the\n          //   loop, and the exception flows out of dispatchEvent(). In theory if multiple\n          //   handlers were registered then maybe we ought to be running all of them even if one\n          //   fails. This isn't entirely clear, though: in the case of 'fetch' handlers, in\n          //   fail-closed mode, an exception from any handler should make the whole request fail,\n          //   but then who cares if the remaining handlers run? Meanwhile, in fail-open mode, for\n          //   consistency, we should probably trigger fallback behavior if any handler throws, so\n          //   again it doesn't matter. For other types of handlers, e.g. WebSocket 'message', it's\n          //   not clear why one would ever register multiple handlers.\n          KJ_IF_SOME(r, ret) {\n            auto handle = r.getHandle(js);\n            // Returning true is the same as calling preventDefault() on the event.\n            if (handle->IsTrue()) {\n              event->preventDefault();\n            }\n            if (flags.warnOnHandlerReturn && !handle->IsBoolean()) {\n              flags.warnOnHandlerReturn = false;\n              // To help make debugging easier, let's tailor the warning a bit if it was a promise.\n              if (handle->IsPromise()) {\n                js.logWarning(kj::str(\n                    \"An event handler returned a promise that will be ignored. Event handlers \"\n                    \"should not have a return value and should not be async functions.\"));\n              } else {\n                js.logWarning(kj::str(\"An event handler returned a value of type \\\"\",\n                    handle->TypeOf(js.v8Isolate),\n                    \"\\\" that will be ignored. Event handlers should not have a return value.\"));\n              }\n            }\n          }\n        }\n        KJ_CASE_ONEOF(native, EventHandler::NativeHandlerRef) {\n          native.handler(js, event.addRef());\n        }\n      }\n    }\n\n    return !event->isPreventDefault();\n  });\n}\n\nbool EventTarget::dispatchEvent(jsg::Lock& js, jsg::Ref<Event> event) {\n  return dispatchEventImpl(js, kj::mv(event));\n}\n\n// A wrapper for the AbortTrigger jsrpc client, that automatically sends a release() message once\n// the client is destroyed, informing the server that an abort will not be triggered in the future.\nclass AbortTriggerRpcClient final {\n public:\n  AbortTriggerRpcClient(rpc::AbortTrigger::Client&& client): client(kj::mv(client)) {}\n\n  kj::Promise<void> abort(kj::ArrayPtr<kj::byte> reason) {\n    auto req = client.abortRequest(capnp::MessageSize{reason.size() / sizeof(capnp::word) + 8, 0});\n    auto field = req.initReason();\n    field.setV8Serialized(reason);\n    return req.sendIgnoringResult();\n  }\n\n  ~AbortTriggerRpcClient() noexcept(false) {\n    if (skipReleaseForTest) {\n      return;\n    }\n\n    auto req = client.releaseRequest(capnp::MessageSize{4, 0});\n    // We call detach() on the resulting promise so that we can perform RPC in a destructor\n    req.sendIgnoringResult().detach([](kj::Exception exc) {\n      if (exc.getType() == kj::Exception::Type::DISCONNECTED) {\n        // It's possible we can't send the release message because we're already disconnected.\n        return;\n      };\n\n      // Other exceptions could be more interesting\n      LOG_EXCEPTION(\"abortTriggerReleaseRpc\", exc);\n    });\n  }\n\n  bool skipReleaseForTest = false;\n\n private:\n  rpc::AbortTrigger::Client client;\n};\n\nnamespace {\n// The jsrpc handler that receives aborts from the remote and triggers them locally\n//\n// TODO(cleanup): This class has been copied to external-pusher.c++. The copy here can be\n//   deleted as soon as we've switched from StreamSink to ExternalPusher and can delete all the\n//   StreamSink-related code. For now I'm not trying to avoid duplication.\nclass AbortTriggerRpcServer final: public rpc::AbortTrigger::Server {\n public:\n  AbortTriggerRpcServer(kj::Own<kj::PromiseFulfiller<void>> fulfiller,\n      kj::Own<AbortSignal::PendingReason>&& pendingReason)\n      : fulfiller(kj::mv(fulfiller)),\n        pendingReason(kj::mv(pendingReason)) {}\n\n  kj::Promise<void> abort(AbortContext abortCtx) override {\n    auto params = abortCtx.getParams();\n    auto reason = params.getReason().getV8Serialized();\n\n    pendingReason->getWrapped() = kj::heapArray(reason.asBytes());\n    fulfiller->fulfill();\n    return kj::READY_NOW;\n  }\n\n  kj::Promise<void> release(ReleaseContext releaseCtx) override {\n    released = true;\n    return kj::READY_NOW;\n  }\n\n  ~AbortTriggerRpcServer() noexcept(false) {\n    if (pendingReason->getWrapped() != nullptr) {\n      // Already triggered\n      return;\n    }\n\n    if (!released) {\n      pendingReason->getWrapped() = JSG_KJ_EXCEPTION(FAILED, DOMAbortError,\n          \"An AbortSignal received over RPC was implicitly aborted because the connection back to \"\n          \"its trigger was lost.\");\n    }\n\n    // Always fulfill the promise in case the AbortSignal was waiting\n    fulfiller->fulfill();\n  }\n\n private:\n  kj::Own<kj::PromiseFulfiller<void>> fulfiller;\n  kj::Own<AbortSignal::PendingReason> pendingReason;\n  bool released = false;\n};\n}  // namespace\n\nAbortSignal::AbortSignal(kj::Maybe<kj::Exception> exception,\n    jsg::Optional<jsg::JsRef<jsg::JsValue>> maybeReason,\n    Flag flag)\n    : canceler(\n          IoContext::current().addObject(kj::refcounted<RefcountedCanceler>(kj::cp(exception)))),\n      flag(flag),\n      reason(kj::mv(maybeReason)) {}\n\nkj::Maybe<jsg::JsValue> AbortSignal::getOnAbort(jsg::Lock& js) {\n  return onAbortHandler.map(\n      [&](jsg::JsRef<jsg::JsValue>& ref) -> jsg::JsValue { return ref.getHandle(js); });\n}\n\nvoid AbortSignal::setOnAbort(jsg::Lock& js, jsg::Optional<jsg::JsValue> handler) {\n  // We only want to accept the handler if it's a valid handler... For anything\n  // else, set it to null.\n  KJ_IF_SOME(h, handler) {\n    if (h.isFunction() || h.isObject()) {\n      onAbortHandler = jsg::JsRef(js, h);\n      subscribeToRpcAbort(js);\n      return;\n    }\n  }\n  onAbortHandler = kj::none;\n}\n\nvoid AbortSignal::addEventListener(jsg::Lock& js,\n    kj::String type,\n    jsg::Identified<Handler> handler,\n    jsg::Optional<AddEventListenerOpts> maybeOptions,\n    const jsg::TypeHandler<jsg::Ref<EventTarget>>& eventTargetHandler) {\n  EventTarget::addEventListener(\n      js, kj::mv(type), kj::mv(handler), kj::mv(maybeOptions), eventTargetHandler);\n  subscribeToRpcAbort(js);\n}\n\nbool AbortSignal::getAborted(jsg::Lock& js) {\n  return canceler->isCanceled() || hasPendingReason();\n}\n\njsg::JsValue AbortSignal::getReason(jsg::Lock& js) {\n  KJ_IF_SOME(r, reason) {\n    return r.getHandle(js);\n  }\n\n  KJ_IF_SOME(r, deserializePendingReason(js)) {\n    return r;\n  }\n\n  return js.undefined();\n}\n\nkj::Exception AbortSignal::abortException(\n    jsg::Lock& js, jsg::Optional<kj::OneOf<kj::Exception, jsg::JsValue>> maybeReason) {\n  KJ_IF_SOME(reason, maybeReason) {\n    KJ_SWITCH_ONEOF(reason) {\n      KJ_CASE_ONEOF(reason, jsg::JsValue) {\n        return js.exceptionToKj(reason);\n      }\n      KJ_CASE_ONEOF(reason, kj::Exception) {\n        return kj::cp(reason);\n      }\n    }\n  }\n\n  return JSG_KJ_EXCEPTION(DISCONNECTED, DOMAbortError, \"The operation was aborted\");\n}\n\njsg::Ref<AbortSignal> AbortSignal::abort(jsg::Lock& js, jsg::Optional<jsg::JsValue> maybeReason) {\n  auto exception = abortException(js, maybeReason);\n  KJ_IF_SOME(reason, maybeReason) {\n    return js.alloc<AbortSignal>(kj::mv(exception), reason.addRef(js));\n  }\n  return js.alloc<AbortSignal>(kj::cp(exception), js.exceptionToJsValue(kj::mv(exception)));\n}\n\nvoid AbortSignal::throwIfAborted(jsg::Lock& js) {\n  if (canceler->isCanceled()) {\n    KJ_IF_SOME(r, reason) {\n      js.throwException(r.getHandle(js));\n    } else {\n      js.throwException(abortException(js, kj::none));\n    }\n  }\n\n  KJ_IF_SOME(r, deserializePendingReason(js)) {\n    js.throwException(r);\n  }\n}\n\njsg::Ref<AbortSignal> AbortSignal::timeout(jsg::Lock& js, double delay) {\n  auto signal = js.alloc<AbortSignal>();\n\n  auto context = js.v8Context();\n\n  auto& global =\n      jsg::extractInternalPointer<ServiceWorkerGlobalScope, true>(context, context->Global());\n\n  // It's worth noting that the setTimeout holds a strong pointer to the AbortSignal,\n  // keeping it from being garbage collected before the timer fires or until the request\n  // completes, whichever comes first.\n\n  global.setTimeoutInternal([signal = signal.addRef()](jsg::Lock& js) mutable {\n    signal->triggerAbort(js,\n        JSG_KJ_EXCEPTION(\n            DISCONNECTED, DOMTimeoutError, \"The operation was aborted due to timeout\"));\n  }, delay);\n\n  return kj::mv(signal);\n}\n\njsg::Ref<AbortSignal> AbortSignal::any(jsg::Lock& js,\n    kj::Array<jsg::Ref<AbortSignal>> signals,\n    const jsg::TypeHandler<EventTarget::HandlerFunction>& handler,\n    const jsg::TypeHandler<jsg::Ref<EventTarget>>& eventTargetHandler) {\n  // If nothing was passed in, we can just return a signal that never aborts.\n  if (signals.size() == 0) {\n    return js.alloc<AbortSignal>(kj::none, kj::none, AbortSignal::Flag::NEVER_ABORTS);\n  }\n\n  // Let's check to see if any of the signals are already aborted. If it is, we can\n  // optimize here by skipping the event handler registration.\n  for (auto& sig: signals) {\n    if (sig->getAborted(js)) {\n      return AbortSignal::abort(js, sig->getReason(js));\n    }\n  }\n\n  // Otherwise we need to create a new signal and register event handlers on all\n  // of the signals that were passed in.\n  auto signal = js.alloc<AbortSignal>();\n  for (auto& sig: signals) {\n    // This is a bit of a hack. We want to call addEventListener, but that requires a\n    // jsg::Identified<EventTarget::Handler>, which we can't create directly yet.\n    // So we create a jsg::Function, wrap that in a v8::Function, then convert that into\n    // the jsg::Identified<EventTarget::Handler>, and voila, we have what we need.\n    auto fn = js.wrapSimpleFunction(\n        js.v8Context(), [&signal = *signal, &self = *sig](jsg::Lock& js, auto&) {\n      // Note that we are not capturing any strong references here to either signal\n      // or sig. This is because we are capturing a strong reference to the signal\n      // when we add the event below. This ensures that we do not have an unbreakable\n      // circular reference. The returned signal will not have any strong references\n      // to any of the signals that are passed in, but each of the signals passed in\n      // will have a strong reference to the new signal.\n      signal.triggerAbort(js, self.getReason(js));\n    });\n    jsg::Identified<EventTarget::Handler> identified = {.identity = {js.v8Isolate, fn},\n      .unwrapped = JSG_REQUIRE_NONNULL(handler.tryUnwrap(js, fn.As<v8::Value>()), TypeError,\n          \"Unable to create AbortSignal.any handler\")};\n\n    sig->addEventListener(js, kj::str(\"abort\"), kj::mv(identified),\n        AddEventListenerOptions{// Once the abort is triggered, this handler should remove itself.\n          .once = true,\n          // Each of the followed signals will maintain a strong reference to this new\n          // one that's been created.\n          .followingSignal = signal.addRef()},\n        eventTargetHandler);\n  }\n  return signal;\n}\n\nvoid AbortSignal::visitForGc(jsg::GcVisitor& visitor) {\n  visitor.visit(reason, onAbortHandler);\n}\n\nRefcountedCanceler& AbortSignal::getCanceler() {\n  return *canceler;\n}\n\nvoid AbortSignal::triggerAbort(\n    jsg::Lock& js, jsg::Optional<kj::OneOf<kj::Exception, jsg::JsValue>> maybeReason) {\n  KJ_ASSERT(flag != Flag::NEVER_ABORTS);\n  if (canceler->isCanceled()) {\n    return;\n  }\n  auto exception = AbortSignal::abortException(js, maybeReason);\n  KJ_IF_SOME(r, maybeReason) {\n    KJ_SWITCH_ONEOF(r) {\n      KJ_CASE_ONEOF(value, jsg::JsValue) {\n        reason = value.addRef(js);\n      }\n      KJ_CASE_ONEOF(ex, kj::Exception) {\n        reason = js.exceptionToJsValue(kj::mv(ex));\n      }\n    }\n  } else {\n    reason = js.exceptionToJsValue(kj::cp(exception));\n  }\n\n  canceler->cancel(kj::mv(exception));\n\n  // 1. Dispatch to RPC clients\n  if (!rpcClients.empty()) {\n    IoContext& ioContext = IoContext::current();\n    jsg::Serializer ser(js);\n    KJ_IF_SOME(r, reason) {\n      ser.write(js, r.getHandle(js));\n    }\n\n    auto released = ser.release();\n    ioContext.addTask(sendToRpc(kj::mv(released.data)));\n  }\n\n  // 2. Dispatch to local listeners\n\n  // This is questionable only because it goes against the spec but it does help prevent\n  // memory leaks. Once the abort signal has been triggered, there's really nothing else\n  // the AbortSignal can be used for and no other events make sense. The user code could\n  // add more, and could even emit their own events on the signal by using it as an\n  // EventTarget directly but that would be rather silly, so stepping outside the lines\n  // of the spec here should be just fine.\n  KJ_DEFER(removeAllHandlers());\n\n  dispatchEventImpl(js, js.alloc<Event>(kj::str(\"abort\")));\n}\n\nvoid AbortSignal::serialize(jsg::Lock& js, jsg::Serializer& serializer) {\n  JSG_REQUIRE(FeatureFlags::get(js).getAbortSignalRpc(), DOMDataCloneError,\n      \"AbortSignal serialization is not enabled.\");\n\n  auto& handler = JSG_REQUIRE_NONNULL(serializer.getExternalHandler(), DOMDataCloneError,\n      \"AbortSignal can only be serialized for RPC.\");\n\n  auto externalHandler = dynamic_cast<RpcSerializerExternalHandler*>(&handler);\n  JSG_REQUIRE(\n      externalHandler != nullptr, DOMDataCloneError, \"AbortSignal can only be serialized for RPC.\");\n\n  serializer.writeRawUint32(static_cast<uint>(canceler->isCanceled()));\n  serializer.writeRawUint32(static_cast<uint>(flag));\n  KJ_IF_SOME(r, reason) {\n    serializer.write(js, r.getHandle(js));\n  } else {\n    serializer.write(js, js.undefined());\n  }\n\n  if (getAborted(js) || getNeverAborts()) {\n    // This AbortSignal cannot be triggered in the future. No stream is needed.\n    return;\n  }\n\n  auto triggerCap = [&]() -> rpc::AbortTrigger::Client {\n    KJ_IF_SOME(pusher, externalHandler->getExternalPusher()) {\n      auto pipeline = pusher.pushAbortSignalRequest(capnp::MessageSize{2, 0}).sendForPipeline();\n\n      externalHandler->write(\n          [signal = pipeline.getSignal()](rpc::JsValue::External::Builder builder) mutable {\n        builder.setAbortSignal(kj::mv(signal));\n      });\n\n      return pipeline.getTrigger();\n    } else {\n      return externalHandler\n          ->writeStream([&](rpc::JsValue::External::Builder builder) mutable {\n        builder.setAbortTrigger();\n      }).castAs<rpc::AbortTrigger>();\n    }\n  }();\n\n  auto& ioContext = IoContext::current();\n  // Keep track of every AbortSignal cloned from this one.\n  // If this->triggerAbort(...) is called, each rpcClient will be informed.\n  rpcClients.add(ioContext.addObject(kj::heap<AbortTriggerRpcClient>(kj::mv(triggerCap))));\n}\n\njsg::Ref<AbortSignal> AbortSignal::deserialize(\n    jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer) {\n  auto& handler = KJ_REQUIRE_NONNULL(\n      deserializer.getExternalHandler(), \"got AbortSignal on non-RPC serialized object?\");\n  auto externalHandler = dynamic_cast<RpcDeserializerExternalHandler*>(&handler);\n  KJ_REQUIRE(externalHandler != nullptr, \"got AbortSignal on non-RPC serialized object?\");\n\n  auto isCanceled = static_cast<bool>(deserializer.readRawUint32());\n  auto flag = static_cast<Flag>(deserializer.readRawUint32());\n  auto reason = deserializer.readValue(js);\n\n  if (isCanceled) {\n    // The signal is already aborted and cannot be triggered again. We don't need to set up RPC.\n    return abort(js, reason);\n  }\n\n  if (flag == Flag::NEVER_ABORTS) {\n    // The signal can't be aborted. We don't need to setup RPC\n    return js.alloc<AbortSignal>(/* exception */ kj::none, /* maybeReason */ kj::none, flag);\n  }\n\n  // The AbortSignalImpl will receive any remote triggerAbort requests and fulfill the promise with the reason for abort\n\n  auto signal = js.alloc<AbortSignal>(/* exception */ kj::none, /* maybeReason */ kj::none, flag);\n\n  auto& ioctx = IoContext::current();\n\n  auto reader = externalHandler->read();\n  if (reader.isAbortTrigger()) {\n    // Old-style StreamSink.\n    // TODO(cleanup): Remove this once the ExternalPusher autogate has rolled out.\n    auto paf = kj::newPromiseAndFulfiller<void>();\n    auto pendingReason = ioctx.addObject(kj::refcounted<PendingReason>());\n\n    externalHandler->setLastStream(\n        kj::heap<AbortTriggerRpcServer>(kj::mv(paf.fulfiller), kj::addRef(*pendingReason)));\n    signal->rpcAbortPromise = ioctx.addObject(kj::heap(kj::mv(paf.promise)));\n    signal->pendingReason = kj::mv(pendingReason);\n  } else {\n    KJ_REQUIRE(reader.isAbortSignal(), \"external table slot type does't match serialization tag\");\n\n    auto resolvedSignal = ioctx.getExternalPusher()->unwrapAbortSignal(reader.getAbortSignal());\n\n    signal->rpcAbortPromise = ioctx.addObject(kj::heap(kj::mv(resolvedSignal.signal)));\n    signal->pendingReason = ioctx.addObject(kj::mv(resolvedSignal.reason));\n  }\n\n  return signal;\n}\n\nvoid AbortSignal::skipReleaseForTest() {\n  for (auto& cap: rpcClients) {\n    cap->skipReleaseForTest = true;\n  }\n\n  rpcClients.clear();\n}\n\nkj::Promise<void> AbortSignal::sendToRpc(kj::Array<kj::byte>&& reason) {\n  auto& ioContext = IoContext::current();\n\n  KJ_IF_SOME(outputLocks, ioContext.waitForOutputLocksIfNecessary()) {\n    co_await outputLocks;\n  }\n\n  kj::Vector<kj::Promise<void>> promises;\n  for (auto& cap: rpcClients) {\n    promises.add(cap->abort(reason));\n  }\n\n  co_await kj::joinPromises(promises.releaseAsArray());\n}\n\nbool AbortSignal::hasPendingReason() {\n  KJ_IF_SOME(pr, pendingReason) {\n    return pr->getWrapped() != nullptr;\n  }\n\n  return false;\n}\n\nkj::Maybe<jsg::JsValue> AbortSignal::deserializePendingReason(jsg::Lock& js) {\n  KJ_IF_SOME(pr, pendingReason) {\n    if (pr->getWrapped() == nullptr) {\n      // pendingReason not initialized. This means abort wasn't yet triggered\n      return kj::none;\n    }\n\n    KJ_SWITCH_ONEOF(pr->getWrapped()) {\n      KJ_CASE_ONEOF(v8Serialized, kj::Array<kj::byte>) {\n        jsg::Deserializer des(js, v8Serialized);\n        return kj::some(des.readValue(js));\n      }\n\n      KJ_CASE_ONEOF(exception, kj::Exception) {\n        return kj::some(js.exceptionToJsValue(kj::cp(exception)).getHandle(js));\n      }\n    }\n  }\n\n  return kj::none;\n}\n\nvoid AbortSignal::subscribeToRpcAbort(jsg::Lock& js) {\n  // For an AbortSignal received over RPC, the first time someone registers an event on the signal,\n  // we want to arrange to awaitIo() for the underlying RPC signal. If no one is actually listening,\n  // though, we don't want to awaitIo() since it blocks hibernation in actors.\n\n  KJ_IF_SOME(promise, rpcAbortPromise) {\n    IoContext::current().awaitIo(js, kj::mv(*promise), [this](jsg::Lock& js) {\n      KJ_IF_SOME(r, deserializePendingReason(js)) {\n        triggerAbort(js, r);\n      }\n    });\n\n    rpcAbortPromise = kj::none;\n  }\n}\n\nbool AbortSignal::isIgnoredForSubrequests(jsg::Lock& js) const {\n  // True if this is a signal on the request of an incoming fetch. When the compat flag\n  // `requestSignalPassthrough` is set, this flag has no effect. But to ensure backwards\n  // compatibility, when this flag is not set, this signal will not be passed through to\n  // subrequests derived from the incoming request.\n\n  return !FeatureFlags::get(js).getRequestSignalPassthrough() &&\n      flag == Flag::IGNORE_FOR_SUBREQUESTS;\n}\n\nvoid AbortController::abort(jsg::Lock& js, jsg::Optional<jsg::JsValue> maybeReason) {\n  signal->triggerAbort(js, maybeReason);\n}\n\nvoid EventTarget::visitForGc(jsg::GcVisitor& visitor) {\n  for (auto& entry: typeMap) {\n    for (auto& handler: entry.value.handlers) {\n      KJ_SWITCH_ONEOF(handler->handler) {\n        KJ_CASE_ONEOF(js, EventHandler::JavaScriptHandler) {\n          visitor.visit(js);\n        }\n        KJ_CASE_ONEOF(native, EventHandler::NativeHandlerRef) {\n          // Note that even though `native.handler` is a non-owned reference, we still need to\n          // visit it. This is because we are the ones that will invoke the handles contained\n          // in the native handler if it ever fires. The actual owner of the C++ NativeHandler\n          // object doesn't ever access the JS objects it contains; the ownership relationship\n          // exists only for RAII reasons, so that the NativeHandler is automatically unregistered\n          // if the owner is destroyed.\n          //\n          // You might say: \"Well, it's fine if the owner is responsible for visiting it, because\n          // if the owner is no longer reachable then it will be destroyed and it will unregister\n          // itself from here!\" That doesn't quite work: V8's GC doesn't necessarily destroy\n          // objects immediately when they become unreachable. However, it is no longer safe to\n          // access an object once it is unreachable. Therefore, if we left it to the\n          // NativeHandler's owner to visit the object, it's possible that the object becomes\n          // poison some time before it is actually unregistered.\n          //\n          // Put another way, this is a very weird case where the C++ ownership and the JavaScript\n          // ownership are different. We need GC visitation to follow the JavaScript ownership\n          // graph.\n          visitor.visit(native.handler);\n        }\n      }\n    }\n  }\n}\n\nkj::Promise<void> Scheduler::wait(\n    jsg::Lock& js, double delay, jsg::Optional<WaitOptions> maybeOptions) {\n  KJ_IF_SOME(options, maybeOptions) {\n    KJ_IF_SOME(s, options.signal) {\n      if (s->getAborted(js)) {\n        return js.exceptionToKj(s->getReason(js));\n      }\n    }\n  }\n\n  // TODO(cleanup): Use jsg promise and resolver to avoid an unlock/relock. However, we need\n  //   the abort signal to support wrapping jsg promises.\n  auto paf = kj::newPromiseAndFulfiller<void>();\n\n  auto context = js.v8Context();\n\n  auto& global =\n      jsg::extractInternalPointer<ServiceWorkerGlobalScope, true>(context, context->Global());\n  auto timeoutId = global.setTimeoutInternal(\n      [fulfiller = IoContext::current().addObject(kj::mv(paf.fulfiller))](jsg::Lock& lock) mutable {\n    fulfiller->fulfill();\n  }, delay);\n\n  auto promise = kj::mv(paf.promise);\n\n  KJ_IF_SOME(options, maybeOptions) {\n    KJ_IF_SOME(s, options.signal) {\n      promise = s->wrap(js, kj::mv(promise));\n      // When the signal aborts and the canceler drops the promise, clear the underlying\n      // timeout to free the quota slot. clearTimeoutImpl is a no-op if the timeout has\n      // already fired, so this is safe on the normal completion path as well.\n      promise = promise.attach(kj::defer([timeoutId, &ioContext = IoContext::current()]() {\n        ioContext.clearTimeoutImpl(TimeoutId::fromNumber(timeoutId));\n      }));\n    }\n  }\n\n  return kj::mv(promise);\n}\n\nvoid ExtendableEvent::waitUntil(kj::Promise<void> promise) {\n  JSG_REQUIRE(\n      getIsTrusted(), DOMInvalidStateError, \"waitUntil() can only be called on trusted event.\");\n  IoContext::current().addWaitUntil(kj::mv(promise));\n}\n\njsg::Optional<jsg::Ref<ActorState>> ExtendableEvent::getActorState(jsg::Lock& js) {\n  IoContext& context = IoContext::current();\n  return context.getActor().map([&](Worker::Actor& actor) {\n    auto& lock = context.getCurrentLock();\n    auto persistent = actor.makeStorageForSwSyntax(lock);\n    return js.alloc<api::ActorState>(actor.cloneId(), actor.getTransient(lock), kj::mv(persistent));\n  });\n}\n\nCustomEvent::CustomEvent(kj::String ownType, CustomEventInit init)\n    : Event(kj::mv(ownType), Event::Init(init)),\n      detail(kj::mv(init.detail)) {}\n\njsg::Ref<CustomEvent> CustomEvent::constructor(\n    jsg::Lock& js, kj::String type, jsg::Optional<CustomEventInit> init) {\n  return js.alloc<CustomEvent>(kj::mv(type), kj::mv(init).orDefault({}));\n}\n\njsg::Optional<jsg::JsValue> CustomEvent::getDetail(jsg::Lock& js) {\n  return detail.map([&](jsg::JsRef<jsg::JsValue>& val) { return val.getHandle(js); });\n}\n\nCustomEvent::CustomEventInit::operator Event::Init() {\n  return {\n    .bubbles = this->bubbles.map([](auto& val) { return val; }),\n    .cancelable = this->cancelable.map([](auto& val) { return val; }),\n    .composed = this->composed.map([](auto& val) { return val; }),\n  };\n}\n\nsize_t EventTarget::EventHandler::JavaScriptHandler::jsgGetMemorySelfSize() const {\n  return sizeof(JavaScriptHandler);\n}\n\nvoid EventTarget::EventHandler::JavaScriptHandler::jsgGetMemoryInfo(\n    jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"identity\", identity);\n  tracker.trackField(\"callback\", callback);\n  if (abortHandler != kj::none) {\n    tracker.trackFieldWithSize(\n        \"abortHandler\", sizeof(kj::Own<NativeHandler>) + sizeof(NativeHandler));\n  }\n}\n\nsize_t EventTarget::EventHandler::jsgGetMemorySelfSize() const {\n  return sizeof(EventHandler);\n}\n\nvoid EventTarget::EventHandler::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n  KJ_SWITCH_ONEOF(handler) {\n    KJ_CASE_ONEOF(js, JavaScriptHandler) {\n      tracker.trackField(\"js\", js);\n    }\n    KJ_CASE_ONEOF(native, NativeHandlerRef) {\n      tracker.trackFieldWithSize(\"native\", sizeof(NativeHandlerRef));\n    }\n  }\n}\n\nsize_t EventTarget::EventHandlerSet::jsgGetMemorySelfSize() const {\n  return sizeof(EventHandlerSet);\n}\n\nvoid EventTarget::EventHandlerSet::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n  for (const auto& handler: handlers) {\n    tracker.trackField(\"handler\", handler);\n  }\n}\n\nvoid EventTarget::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"typeMap\", typeMap);\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/basics.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// This file defines Event- and EventTarget-related APIs.\n//\n// TODO(cleanup): Rename to events.h?\n\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/io/external-pusher.h>\n#include <workerd/io/io-own.h>\n#include <workerd/io/worker-interface.capnp.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/util/canceler.h>\n\n#include <kj/function.h>\n#include <kj/map.h>\n\nnamespace workerd::api {\n\nclass EventTarget;\nclass AbortSignal;\nclass AbortController;\nclass ActorState;\n\n// An implementation of the Web Platform Standard Event API\nclass Event: public jsg::Object {\n public:\n  struct Init final {\n    jsg::Optional<bool> bubbles;\n    jsg::Optional<bool> cancelable;\n    jsg::Optional<bool> composed;\n    JSG_STRUCT(bubbles, cancelable, composed);\n  };\n\n  inline explicit Event(kj::String ownType, Init init = {}, bool trusted = true)\n      : ownType(kj::mv(ownType)),\n        type(this->ownType) {\n    flags.trusted = trusted;\n    flags.bubbles = init.bubbles.orDefault(false);\n    flags.cancelable = init.cancelable.orDefault(false);\n    flags.composed = init.composed.orDefault(false);\n  }\n\n  inline explicit Event(kj::StringPtr type, Init init = {}, bool trusted = true): type(type) {\n    flags.trusted = trusted;\n    flags.bubbles = init.bubbles.orDefault(false);\n    flags.cancelable = init.cancelable.orDefault(false);\n    flags.composed = init.composed.orDefault(false);\n  }\n\n  inline bool isPreventDefault() const {\n    return flags.preventedDefault;\n  }\n  inline void clearPreventDefault() {\n    flags.preventedDefault = false;\n  }\n\n  void beginDispatch(jsg::Ref<EventTarget> target);\n  inline void endDispatch() {\n    flags.isBeingDispatched = false;\n  }\n\n  inline bool isStopped() const {\n    return flags.stopped;\n  }\n\n  static jsg::Ref<Event> constructor(jsg::Lock& js, kj::String type, jsg::Optional<Init> init);\n  kj::StringPtr getType();\n\n  inline void stopImmediatePropagation() {\n    flags.stopped = true;\n  }\n  inline void preventDefault() {\n    flags.preventedDefault = true;\n  }\n\n  // The only phases we actually use are NONE and AT_TARGET but we provide\n  // all of them to meet spec compliance.\n  enum Phase {\n    NONE,\n    CAPTURING_PHASE,\n    AT_TARGET,\n    BUBBLING_PHASE,\n  };\n\n  inline int getEventPhase() const {\n    return flags.isBeingDispatched ? AT_TARGET : NONE;\n  }\n\n  // Much of the following is not used in our implementation of Event\n  // simply because we do not support the notion of bubbled events\n  // (events propagated up through a hierarchy of objects). They are\n  // provided to fill-out Event spec compliance.\n\n  inline bool getCancelBubble() const {\n    return flags.propagationStopped;\n  }\n  inline void setCancelBubble(bool stopped) {\n    flags.propagationStopped = stopped;\n  }\n  inline void stopPropagation() {\n    flags.propagationStopped = true;\n  }\n  inline bool getComposed() const {\n    return flags.composed;\n  }\n  inline bool getBubbles() const {\n    return flags.bubbles;\n  }\n  inline bool getCancelable() const {\n    return flags.cancelable;\n  }\n  inline bool getDefaultPrevented() const {\n    return getCancelable() && flags.preventedDefault;\n  }\n  inline bool getReturnValue() const {\n    return !getDefaultPrevented();\n  }\n\n  // We provide the timeStamp property for spec compliance but we force\n  // the value to 0.0 always because we really don't want users to rely\n  // on this property for timing details.\n  inline double getTimestamp() const {\n    return 0.0;\n  }\n\n  // What makes an Event trusted? It's pretty simple... any Event created\n  // by EW internally is Trusted, any Event created using new Event() in JS\n  // is not trusted.\n  inline bool getIsTrusted() const {\n    return flags.trusted;\n  }\n\n  // The currentTarget is the EventTarget on which the Event is being\n  // dispatched. This will be set every time dispatchEvent() is called\n  // successfully and will be null after dispatchEvent returns.\n  kj::Maybe<jsg::Ref<EventTarget>> getCurrentTarget();\n\n  // Because we don't support hierarchical EventTargets, this function\n  // will always return the same value as getCurrentTarget().\n  jsg::Optional<jsg::Ref<EventTarget>> getTarget();\n\n  // For our implementation, since we do not support hierarchical EventTargets,\n  // the composedPath is always either an empty array if the Event is currently\n  // not being dispatched, or an array containing only the currentTarget if\n  // it is being dispatched.\n  kj::Array<jsg::Ref<EventTarget>> composedPath();\n\n  JSG_RESOURCE_TYPE(Event, CompatibilityFlags::Reader flags) {\n    // Previously, we were setting all properties as instance properties,\n    // which broke the ability to subclass the Event object. With the\n    // compatibility flag set, we instead attach the properties to the\n    // prototype.\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(type, getType);\n      JSG_READONLY_PROTOTYPE_PROPERTY(eventPhase, getEventPhase);\n      JSG_READONLY_PROTOTYPE_PROPERTY(composed, getComposed);\n      JSG_READONLY_PROTOTYPE_PROPERTY(bubbles, getBubbles);\n      JSG_READONLY_PROTOTYPE_PROPERTY(cancelable, getCancelable);\n      JSG_READONLY_PROTOTYPE_PROPERTY(defaultPrevented, getDefaultPrevented);\n      JSG_READONLY_PROTOTYPE_PROPERTY(returnValue, getReturnValue);\n      if (flags.getPedanticWpt()) {\n        JSG_READONLY_PROTOTYPE_PROPERTY(currentTarget, getCurrentTarget);\n      } else {\n        // The original implementation had getTarget simply deferring to\n        // getCurrentTarget, the new impl moves the original impl into\n        // getTarget here so having currentTarget point to getTarget\n        // preserves the original behavior.\n        JSG_READONLY_PROTOTYPE_PROPERTY(currentTarget, getTarget);\n      }\n      JSG_READONLY_PROTOTYPE_PROPERTY(target, getTarget);\n      JSG_READONLY_PROTOTYPE_PROPERTY(srcElement, getTarget);\n      JSG_READONLY_PROTOTYPE_PROPERTY(timeStamp, getTimestamp);\n      if (flags.getPedanticWpt()) {\n        JSG_READONLY_INSTANCE_PROPERTY(isTrusted, getIsTrusted);\n      } else {\n        JSG_READONLY_PROTOTYPE_PROPERTY(isTrusted, getIsTrusted);\n      }\n\n      JSG_PROTOTYPE_PROPERTY(cancelBubble, getCancelBubble, setCancelBubble);\n    } else {\n      JSG_READONLY_INSTANCE_PROPERTY(type, getType);\n      JSG_READONLY_INSTANCE_PROPERTY(eventPhase, getEventPhase);\n      JSG_READONLY_INSTANCE_PROPERTY(composed, getComposed);\n      JSG_READONLY_INSTANCE_PROPERTY(bubbles, getBubbles);\n      JSG_READONLY_INSTANCE_PROPERTY(cancelable, getCancelable);\n      JSG_READONLY_INSTANCE_PROPERTY(defaultPrevented, getDefaultPrevented);\n      JSG_READONLY_INSTANCE_PROPERTY(returnValue, getReturnValue);\n      if (flags.getPedanticWpt()) {\n        JSG_READONLY_INSTANCE_PROPERTY(currentTarget, getCurrentTarget);\n      } else {\n        JSG_READONLY_INSTANCE_PROPERTY(currentTarget, getTarget);\n      }\n      JSG_READONLY_INSTANCE_PROPERTY(target, getTarget);\n      JSG_READONLY_INSTANCE_PROPERTY(srcElement, getCurrentTarget);\n      JSG_READONLY_INSTANCE_PROPERTY(timeStamp, getTimestamp);\n      JSG_READONLY_INSTANCE_PROPERTY(isTrusted, getIsTrusted);\n\n      JSG_INSTANCE_PROPERTY(cancelBubble, getCancelBubble, setCancelBubble);\n    }\n\n    JSG_METHOD(stopImmediatePropagation);\n    JSG_METHOD(preventDefault);\n    JSG_METHOD(stopPropagation);\n    JSG_METHOD(composedPath);\n\n    JSG_STATIC_CONSTANT(NONE);\n    JSG_STATIC_CONSTANT(CAPTURING_PHASE);\n    JSG_STATIC_CONSTANT(AT_TARGET);\n    JSG_STATIC_CONSTANT(BUBBLING_PHASE);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"type\", ownType);\n    tracker.trackField(\"target\", target);\n  }\n\n private:\n  // listing ownType first so type can be initialized with it in constructor\n  kj::String ownType;\n  kj::StringPtr type;\n  kj::Maybe<jsg::Ref<EventTarget>> target;\n\n  struct Flags {\n    uint8_t trusted : 1 = 1;\n    uint8_t stopped : 1 = 0;\n    uint8_t preventedDefault : 1 = 0;\n    uint8_t isBeingDispatched : 1 = 0;\n    uint8_t propagationStopped : 1 = 0;\n    uint8_t composed : 1 = 0;\n    uint8_t bubbles : 1 = 0;\n    uint8_t cancelable : 1 = 0;\n  };\n  Flags flags{};\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(target);\n  }\n};\n\nclass ExtendableEvent: public Event {\n public:\n  using Event::Event;\n\n  // While ExtendableEvent is defined by the spec to be constructable, there's really not a\n  // lot of reason currently to do so, especially with the restriction that waitUntil can\n  // only be called on trusted events (which have to originate from within the system).\n  static jsg::Ref<ExtendableEvent> constructor(kj::String type) = delete;\n\n  void waitUntil(kj::Promise<void> promise);\n\n  jsg::Optional<jsg::Ref<ActorState>> getActorState(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(ExtendableEvent) {\n    JSG_INHERIT(Event);\n    JSG_METHOD(waitUntil);\n\n#if !WORKERD_API_BASICS_TEST\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(actorState, getActorState);\n#endif\n\n    JSG_TS_OVERRIDE({ actorState: never });\n    // Omit `actorState` from definitions\n  }\n};\n\n// An implementation of the Web Platform Standard CustomEvent API\nclass CustomEvent: public Event {\n public:\n  struct CustomEventInit final {\n    jsg::Optional<bool> bubbles;\n    jsg::Optional<bool> cancelable;\n    jsg::Optional<bool> composed;\n    jsg::Optional<jsg::JsRef<jsg::JsValue>> detail;\n    JSG_STRUCT(bubbles, cancelable, composed, detail);\n\n    operator Event::Init();\n  };\n\n  explicit CustomEvent(kj::String ownType, CustomEventInit init = CustomEventInit());\n\n  static jsg::Ref<CustomEvent> constructor(\n      jsg::Lock& js, kj::String type, jsg::Optional<CustomEventInit> init);\n\n  jsg::Optional<jsg::JsValue> getDetail(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(CustomEvent) {\n    JSG_INHERIT(Event);\n    JSG_READONLY_PROTOTYPE_PROPERTY(detail, getDetail);\n    JSG_TS_OVERRIDE(<T = any> {\n      get detail(): T;\n    });\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"detail\", detail);\n  }\n\n private:\n  jsg::Optional<jsg::JsRef<jsg::JsValue>> detail;\n};\n\n// An implementation of the Web Platform Standard EventTarget API\nclass EventTarget: public jsg::Object {\n public:\n  ~EventTarget() noexcept(false);\n\n  size_t getHandlerCount(kj::StringPtr type) const;\n\n  kj::Array<kj::StringPtr> getHandlerNames() const;\n\n  bool dispatchEventImpl(jsg::Lock& js, jsg::Ref<Event> event);\n\n  inline void removeAllHandlers() {\n    typeMap.clear();\n  }\n\n  inline void enableWarningOnSpecialEvents() {\n    flags.warnOnSpecialEvents = true;\n  }\n\n  // The EventListenerCallback, if given, is called whenever addEventListener\n  // or removeEventListener is invoked to report the number of registered\n  // handlers for the event.\n  using EventListenerCallback = jsg::Function<void(kj::StringPtr, size_t)>;\n\n  // ---------------------------------------------------------------------------\n  // JS API\n\n  struct EventListenerOptions {\n    jsg::Optional<bool> capture;\n\n    JSG_STRUCT(capture);\n  };\n\n  struct AddEventListenerOptions {\n    jsg::Optional<bool> capture;\n    jsg::Optional<bool> passive;\n    jsg::Optional<bool> once;\n    jsg::Optional<jsg::Ref<AbortSignal>> signal;\n\n    JSG_STRUCT(capture, passive, once, signal);\n\n    // A following signal is used when the EventTarget is an AbortSignal\n    // that is being followed by another AbortSignal via the AbortSignal.any.\n    // This is used to keep the following signal alive until either the\n    // signal is triggered or this AbortSignal is destroyed.\n    jsg::Optional<jsg::Ref<AbortSignal>> followingSignal;\n  };\n\n  using AddEventListenerOpts = kj::OneOf<AddEventListenerOptions, bool>;\n  using EventListenerOpts = kj::OneOf<EventListenerOptions, bool>;\n\n  using HandlerFunction = jsg::Function<jsg::Optional<jsg::Value>(jsg::Ref<Event>)>;\n\n  struct HandlerObject {\n    HandlerFunction handleEvent;\n    jsg::SelfRef self;\n    JSG_STRUCT(handleEvent, self);\n\n    // TODO(cleanup): Get rid of this override and parse the type directly in param-extractor.rs\n    JSG_STRUCT_TS_OVERRIDE({\n      handleEvent: (event: Event) => any | undefined;\n    });\n  };\n  using Handler = kj::OneOf<HandlerFunction, HandlerObject>;\n\n  void addEventListener(jsg::Lock& js,\n      kj::String type,\n      kj::Maybe<jsg::Identified<Handler>> maybeHandler,\n      jsg::Optional<AddEventListenerOpts> maybeOptions,\n      const jsg::TypeHandler<jsg::Ref<EventTarget>>& eventTargetHandler);\n  void removeEventListener(jsg::Lock& js,\n      kj::String type,\n      kj::Maybe<jsg::HashableV8Ref<v8::Object>> maybeHandler,\n      jsg::Optional<EventListenerOpts> options);\n  bool dispatchEvent(jsg::Lock& js, jsg::Ref<Event> event);\n\n  JSG_RESOURCE_TYPE(EventTarget) {\n    JSG_METHOD(addEventListener);\n    JSG_METHOD(removeEventListener);\n    JSG_METHOD(dispatchEvent);\n\n    JSG_TS_DEFINE(\n      type EventListener<EventType extends Event = Event> = (event: EventType) => void;\n      interface EventListenerObject<EventType extends Event = Event> {\n        handleEvent(event: EventType): void;\n      }\n      type EventListenerOrEventListenerObject<EventType extends Event = Event> = EventListener<EventType> | EventListenerObject<EventType>;\n    );\n    JSG_TS_OVERRIDE(<EventMap extends Record<string, Event> = Record<string, Event>> {\n      addEventListener<Type extends keyof EventMap>(type: Type, handler: EventListenerOrEventListenerObject<EventMap[Type]>, options?: EventTargetAddEventListenerOptions | boolean): void;\n      removeEventListener<Type extends keyof EventMap>(type: Type, handler: EventListenerOrEventListenerObject<EventMap[Type]>, options?: EventTargetEventListenerOptions | boolean): void;\n      dispatchEvent(event: EventMap[keyof EventMap]): boolean;\n    });\n  }\n  JSG_REFLECTION(onEvents);\n\n  static jsg::Ref<EventTarget> constructor(jsg::Lock& js);\n\n  // Registers a lambda that will be called when the given event type is emitted.\n  // The handler will be registered for as long as the returned kj::Own<void>\n  // handle is held. If the EventTarget is destroyed while the native handler handle\n  // is held, it will be automatically detached.\n  //\n  // The caller must not do anything with the returned Own<void> except drop it. This is why it\n  // is Own<void> and not Own<NativeHandler>.\n  kj::Own<void> newNativeHandler(\n      jsg::Lock& js, kj::String type, jsg::Function<void(jsg::Ref<Event>)> func, bool once = false);\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n protected:\n  void setEventListenerCallback(EventListenerCallback&& callback) {\n    maybeListenerCallback = kj::mv(callback);\n  }\n\n private:\n  // RAII-style listener that can be attached to an EventTarget.\n  class NativeHandler {\n   public:\n    using Signature = void(jsg::Ref<Event>);\n    NativeHandler(jsg::Lock& js,\n        EventTarget& target,\n        kj::String type,\n        jsg::Function<Signature> func,\n        bool once = false);\n    ~NativeHandler() noexcept(false);\n    KJ_DISALLOW_COPY_AND_MOVE(NativeHandler);\n\n    void operator()(jsg::Lock& js, jsg::Ref<Event> event);\n\n    uint hashCode() const;\n\n    void visitForGc(jsg::GcVisitor& visitor);\n\n   private:\n    void detach();\n\n    kj::String type;\n    struct State {\n      // target's destructor will null out `state`, so this is OK to be a bare reference.\n      EventTarget& target;\n\n      jsg::Function<Signature> func;\n    };\n\n    kj::Maybe<State> state;\n    bool once;\n\n    friend class EventTarget;\n  };\n\n  void addNativeListener(jsg::Lock& js, NativeHandler& handler);\n  bool removeNativeListener(NativeHandler& handler);\n\n  struct EventHandler {\n    struct JavaScriptHandler {\n      jsg::HashableV8Ref<v8::Object> identity;\n      HandlerFunction callback;\n\n      // If the event handler is registered with an AbortSignal, then the abortHandler points\n      // at the NativeHandler representing that registration, so that if this object is GC'ed before\n      // the AbortSignal is signalled, we unregister ourselves from listening on it. Note that\n      // this is Own<void> for the same reason newNativeHandler() returns Own<void>: We are not\n      // supposed to do anything with this except drop it.\n      kj::Maybe<kj::Own<void>> abortHandler;\n\n      void visitForGc(jsg::GcVisitor& visitor) {\n        visitor.visit(identity, callback);\n\n        // Note that we intentionally do NOT visit `abortHandler`. This is because the JS handles\n        // held by `abortHandler` are not ever accessed by this path. Instead, they are accessed\n        // by the AbortSignal, if and when it fires. So it is the AbortSignal's responsibility to\n        // visit the NativeHandler's content.\n      }\n\n      kj::StringPtr jsgGetMemoryName() const {\n        return \"JavaScriptHandler\"_kjc;\n      }\n      size_t jsgGetMemorySelfSize() const;\n      void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const;\n    };\n\n    struct NativeHandlerRef {\n      NativeHandler& handler;\n    };\n\n    // An EventHandler can be backed by either a JavaScript Handler (which is either a\n    // function or an object) or a native handler. The insertion order matters here so\n    // we maintain a single table.\n    using Handler = kj::OneOf<JavaScriptHandler, NativeHandlerRef>;\n\n    Handler handler;\n\n    // When once is true, the handler will be removed after it is invoked one time.\n    bool once = false;\n\n    EventHandler(Handler handler, bool once): handler(kj::mv(handler)), once(once) {}\n    KJ_DISALLOW_COPY_AND_MOVE(EventHandler);\n\n    kj::StringPtr jsgGetMemoryName() const {\n      return \"EventHandler\"_kjc;\n    }\n    size_t jsgGetMemorySelfSize() const;\n    void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const;\n  };\n\n  struct EventHandlerHashCallbacks {\n    const EventHandler::Handler& keyForRow(const kj::Own<EventHandler>& row) const;\n    bool matches(const kj::Own<EventHandler>& a, const jsg::HashableV8Ref<v8::Object>& b) const;\n    bool matches(const kj::Own<EventHandler>& a, const NativeHandler& b) const;\n    bool matches(const kj::Own<EventHandler>& a, const EventHandler::NativeHandlerRef& b) const;\n    bool matches(const kj::Own<EventHandler>& a, const EventHandler::Handler& b) const;\n    uint hashCode(const jsg::HashableV8Ref<v8::Object>& obj) const;\n    uint hashCode(const NativeHandler& handler) const;\n    uint hashCode(const EventHandler::NativeHandlerRef& handler) const;\n    uint hashCode(const EventHandler::JavaScriptHandler& handler) const;\n    uint hashCode(const EventHandler::Handler& handler) const;\n  };\n\n  struct EventHandlerSet {\n    kj::Table<kj::Own<EventHandler>,\n        kj::HashIndex<EventHandlerHashCallbacks>,\n        kj::InsertionOrderIndex>\n        handlers;\n\n    EventHandlerSet(): handlers(EventHandlerHashCallbacks(), {}) {}\n\n    kj::StringPtr jsgGetMemoryName() const {\n      return \"EventHandlerSet\"_kjc;\n    }\n    size_t jsgGetMemorySelfSize() const;\n    void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const;\n  };\n\n  EventHandlerSet& getOrCreate(kj::StringPtr str) KJ_LIFETIMEBOUND;\n\n  jsg::PropertyReflection<kj::OneOf<HandlerFunction, jsg::Value>> onEvents;\n\n  kj::HashMap<kj::String, EventHandlerSet> typeMap;\n\n  kj::Maybe<EventListenerCallback> maybeListenerCallback;\n\n  struct Flags {\n    // When using module syntax, the \"fetch\", \"scheduled\", \"trace\", etc.\n    // events are handled by exports rather than events. When warnOnSpecialEvents is true,\n    // when using module syntax, attempts to register event handlers for these special\n    // types of events will result in a warning being emitted.\n    uint8_t warnOnSpecialEvents : 1 = 0;\n    // Event handlers are not supposed to return values. The first time one does, we'll\n    // emit a warning to help users debug things but we'll otherwise ignore it.\n    uint8_t warnOnHandlerReturn : 1 = 1;\n  };\n  Flags flags;\n\n  void visitForGc(jsg::GcVisitor& visitor);\n\n  friend class NativeHandler;\n};\n\n// An implementation of the Web Platform Standard AbortSignal API\nclass AbortTriggerRpcClient;\n\nclass AbortSignal final: public EventTarget {\n public:\n  enum class Flag { NONE, NEVER_ABORTS, IGNORE_FOR_SUBREQUESTS };\n\n  AbortSignal(kj::Maybe<kj::Exception> exception = kj::none,\n      jsg::Optional<jsg::JsRef<jsg::JsValue>> maybeReason = kj::none,\n      Flag flag = Flag::NONE);\n\n  using PendingReason = ExternalPusherImpl::PendingAbortReason;\n\n  // The AbortSignal explicitly does not expose a constructor(). It is\n  // illegal for user code to create an AbortSignal directly.\n  static jsg::Ref<AbortSignal> constructor() = delete;\n\n  bool getAborted(jsg::Lock& js);\n\n  jsg::JsValue getReason(jsg::Lock& js);\n\n  // Will synchronously throw an error if the abort signal has been triggered.\n  void throwIfAborted(jsg::Lock& js);\n\n  inline bool getNeverAborts() const {\n    return flag == Flag::NEVER_ABORTS;\n  }\n\n  // The static abort() function here returns an AbortSignal that\n  // has been pre-emptively aborted. It's useful when it might still\n  // be desirable to kick off an async process while communicating\n  // that it shouldn't continue.\n  static jsg::Ref<AbortSignal> abort(jsg::Lock& js, jsg::Optional<jsg::JsValue> reason);\n\n  // Returns an AbortSignal that is triggered after delay milliseconds.\n  static jsg::Ref<AbortSignal> timeout(jsg::Lock& js, double delay);\n\n  void triggerAbort(\n      jsg::Lock& js, jsg::Optional<kj::OneOf<kj::Exception, jsg::JsValue>> maybeReason);\n\n  static jsg::Ref<AbortSignal> any(jsg::Lock& js,\n      kj::Array<jsg::Ref<AbortSignal>> signals,\n      const jsg::TypeHandler<EventTarget::HandlerFunction>& handler,\n      const jsg::TypeHandler<jsg::Ref<EventTarget>>& eventTargetHandler);\n\n  // While AbortSignal extends EventTarget, and our EventTarget implementation will\n  // automatically support onabort being set as an own property, the spec defines\n  // onabort as a prototype property on the AbortSignal prototype. Therefore, we\n  // need to explicitly set it as a prototype property here.\n  kj::Maybe<jsg::JsValue> getOnAbort(jsg::Lock& js);\n  void setOnAbort(jsg::Lock& js, jsg::Optional<jsg::JsValue> handler);\n\n  void addEventListener(jsg::Lock& js,\n      kj::String type,\n      jsg::Identified<Handler> handler,\n      jsg::Optional<AddEventListenerOpts> maybeOptions,\n      const jsg::TypeHandler<jsg::Ref<EventTarget>>& eventTargetHandler);\n\n  JSG_RESOURCE_TYPE(AbortSignal, CompatibilityFlags::Reader flags) {\n    JSG_INHERIT(EventTarget);\n    JSG_STATIC_METHOD(abort);\n    JSG_STATIC_METHOD(timeout);\n    JSG_STATIC_METHOD(any);\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(aborted, getAborted);\n      JSG_READONLY_PROTOTYPE_PROPERTY(reason, getReason);\n    } else {\n      JSG_READONLY_INSTANCE_PROPERTY(aborted, getAborted);\n      JSG_READONLY_INSTANCE_PROPERTY(reason, getReason);\n    }\n    JSG_PROTOTYPE_PROPERTY(onabort, getOnAbort, setOnAbort);\n    JSG_METHOD(throwIfAborted);\n\n    if (flags.getWorkerdExperimental()) {\n      JSG_METHOD(skipReleaseForTest);\n      JSG_TS_OVERRIDE({ skipReleaseForTest: never });\n    }\n  }\n\n  // Allows this AbortSignal to also serve as a kj::Canceler\n  template <typename T>\n  kj::Promise<T> wrap(jsg::Lock& js, kj::Promise<T> promise) {\n    subscribeToRpcAbort(js);\n\n    JSG_REQUIRE(!canceler->isCanceled(), TypeError, \"The AbortSignal has already been triggered\");\n    return canceler->wrap(kj::mv(promise));\n  }\n\n  template <typename T>\n  static kj::Promise<T> maybeCancelWrap(\n      jsg::Lock& js, kj::Maybe<jsg::Ref<AbortSignal>>& signal, kj::Promise<T> promise) {\n    KJ_IF_SOME(s, signal) {\n      return s->wrap(js, kj::mv(promise));\n    } else {\n      return kj::mv(promise);\n    }\n  }\n\n  RefcountedCanceler& getCanceler();\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    EventTarget::visitForMemoryInfo(tracker);\n    tracker.trackInlineFieldWithSize(\n        \"IoOwn<RefcountedCanceler>\", sizeof(IoOwn<RefcountedCanceler>));\n    tracker.trackField(\"reason\", reason);\n  }\n\n  void serialize(jsg::Lock& js, jsg::Serializer& serializer);\n\n  // To test what happens if a capability is dropped before invoking release on the cloned abort\n  // signal, this method will tell every rpcClient to skip this step before destruction.\n  void skipReleaseForTest();\n\n  static jsg::Ref<AbortSignal> deserialize(\n      jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer);\n\n  JSG_SERIALIZABLE(rpc::SerializationTag::ABORT_SIGNAL);\n\n  // True if this is a signal on the request of an incoming fetch. When the compat flag\n  // `requestSignalPassthrough` is set, this flag has no effect. But to ensure backwards\n  // compatibility, when this flag is not set, this signal will not be passed through to\n  // subrequests derived from the incoming request.\n  bool isIgnoredForSubrequests(jsg::Lock& js) const;\n\n private:\n  IoOwn<RefcountedCanceler> canceler;\n  Flag flag;\n\n  kj::Maybe<jsg::JsRef<jsg::JsValue>> reason;\n  kj::Maybe<jsg::JsRef<jsg::JsValue>> onAbortHandler;\n\n  static kj::Exception abortException(\n      jsg::Lock& js, jsg::Optional<kj::OneOf<kj::Exception, jsg::JsValue>> reason);\n\n  void visitForGc(jsg::GcVisitor& visitor);\n\n  friend class AbortController;\n\n  // -------------------------------------------------------------\n  // RPC client functionality. Used if this signal was serialized.\n\n  // A collection of rpcClients, which will be notified if this signal is triggered and when this\n  // signal is destroyed.\n  kj::Vector<IoOwn<AbortTriggerRpcClient>> rpcClients;\n\n  // Trigger an abort on all associated clients\n  kj::Promise<void> sendToRpc(kj::Array<kj::byte>&& reason);\n\n  // ---------------------------------------------------------------\n  // RPC server functionality. Used if this signal was deserialized.\n\n  // A promise that is fulfilled if an abort() message is received over RPC.\n  kj::Maybe<IoOwn<kj::Promise<void>>> rpcAbortPromise;\n\n  // A refcounted object used to receive a serialized abort reason\n  // The abort reason is required in asynchronous event handlers as well as synchronous methods\n  // like getReason(). As a result, we can't pass the abort reason in the above promise, and both\n  // sync and async methods will need to check this value.\n  kj::Maybe<IoOwn<PendingReason>> pendingReason;\n\n  // Synchronously check if an abort reason was sent over RPC\n  bool hasPendingReason();\n  kj::Maybe<jsg::JsValue> deserializePendingReason(jsg::Lock& js);\n\n  // Wait for abort over RPC.\n  // We invoke this once at least one event handler is attached to the AbortSignal\n  void subscribeToRpcAbort(jsg::Lock& js);\n};\n\n// An implementation of the Web Platform Standard AbortController API\nclass AbortController final: public jsg::Object {\n public:\n  explicit AbortController(\n      jsg::Lock& js, AbortSignal::Flag abortSignalFlag = AbortSignal::Flag::NONE)\n      : signal(js.alloc<AbortSignal>(\n            kj::none /* exception */, kj::none /* maybeReason */, abortSignalFlag)) {}\n\n  static jsg::Ref<AbortController> constructor(jsg::Lock& js) {\n    return js.alloc<AbortController>(js);\n  }\n\n  jsg::Ref<AbortSignal> getSignal() {\n    return signal.addRef();\n  }\n\n  void abort(jsg::Lock& js, jsg::Optional<jsg::JsValue> reason);\n\n  JSG_RESOURCE_TYPE(AbortController, CompatibilityFlags::Reader flags) {\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(signal, getSignal);\n    } else {\n      JSG_READONLY_INSTANCE_PROPERTY(signal, getSignal);\n    }\n    JSG_METHOD(abort);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"signal\", signal);\n  }\n\n private:\n  jsg::Ref<AbortSignal> signal;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    // We have to be careful with GC here. The event listeners added to the AbortSignal\n    // could hold a circular reference to the AbortController.\n    visitor.visit(signal);\n  }\n};\n\n// The scheduler class is an emerging web platform standard API that is meant\n// to be global and provides task scheduling APIs. We currently only implement\n// a subset of the API that is being defined.\nclass Scheduler final: public jsg::Object {\n public:\n  struct WaitOptions {\n    jsg::Optional<jsg::Ref<AbortSignal>> signal;\n    JSG_STRUCT(signal);\n  };\n\n  // Returns a promise that resolves after the `delay` milliseconds.\n  // Essentially an awaitable alternative to setTimeout(). The wait\n  // can be canceled using an AbortSignal.\n  kj::Promise<void> wait(jsg::Lock& js, double delay, jsg::Optional<WaitOptions> maybeOptions);\n\n  JSG_RESOURCE_TYPE(Scheduler) {\n    JSG_METHOD(wait);\n  }\n\n private:\n};\n\n#define EW_BASICS_ISOLATE_TYPES                                                                    \\\n  api::Event, api::Event::Init, api::EventTarget, api::EventTarget::EventListenerOptions,          \\\n      api::EventTarget::AddEventListenerOptions, api::EventTarget::HandlerObject,                  \\\n      api::AbortController, api::AbortSignal, api::Scheduler, api::Scheduler::WaitOptions,         \\\n      api::ExtendableEvent, api::CustomEvent, api::CustomEvent::CustomEventInit\n// The list of basics.h types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/blob.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"blob.h\"\n\n#include <workerd/api/streams/readable-source.h>\n#include <workerd/api/streams/readable.h>\n#include <workerd/io/observer.h>\n#include <workerd/util/mimetype.h>\n#include <workerd/util/stream-utils.h>\n\nnamespace workerd::api {\n\nnamespace {\n// Concatenate an array of segments (parameter to Blob constructor).\njsg::BufferSource concat(jsg::Lock& js, jsg::Optional<Blob::Bits> maybeBits) {\n  // TODO(perf): Make it so that a Blob can keep references to the input data rather than copy it.\n  //   Note that we can't keep references to ArrayBuffers since they are mutable, but we can\n  //   reference other Blobs in the input.\n\n  auto bits = kj::mv(maybeBits).orDefault(nullptr);\n\n  auto maxBlobSize = Worker::Isolate::from(js).getLimitEnforcer().getBlobSizeLimit();\n  size_t size = 0;\n  for (auto& part: bits) {\n    size_t partSize = 0;\n    KJ_SWITCH_ONEOF(part) {\n      KJ_CASE_ONEOF(bytes, kj::Array<const byte>) {\n        partSize = bytes.size();\n      }\n      KJ_CASE_ONEOF(text, kj::String) {\n        partSize = text.size();\n      }\n      KJ_CASE_ONEOF(blob, jsg::Ref<Blob>) {\n        partSize = blob->getData().size();\n      }\n    }\n\n    // We can skip the remaining checks if the part is empty.\n    if (partSize == 0) continue;\n\n    // While overflow is *extremely* unlikely to ever be a problem here, let's\n    // be extra cautious and check for it anyway.\n    static constexpr size_t kOverflowLimit = kj::maxValue;\n    // Upper limit the max number of bytes we can add to size to avoid overflow.\n    // partSize must be less than or equal to upperLimit. Practically speaking,\n    // however, it is practically impossible to reach this limit in any real-world\n    // scenario given the size limit check below.\n    size_t upperLimit = kOverflowLimit - size;\n    JSG_REQUIRE(\n        partSize <= upperLimit, RangeError, kj::str(\"Blob part too large: \", partSize, \" bytes\"));\n\n    // Checks for oversize\n    JSG_REQUIRE(size + partSize <= maxBlobSize, RangeError,\n        kj::str(\"Blob size \", size + partSize, \" exceeds limit \", maxBlobSize));\n    size += partSize;\n  }\n\n  auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, size);\n  auto result = jsg::BufferSource(js, kj::mv(backing));\n\n  if (size == 0) return kj::mv(result);\n\n  auto view = result.asArrayPtr();\n\n  for (auto& part: bits) {\n    KJ_SWITCH_ONEOF(part) {\n      KJ_CASE_ONEOF(bytes, kj::Array<const byte>) {\n        if (bytes.size() == 0) continue;\n        KJ_ASSERT(view.size() >= bytes.size());\n        view.first(bytes.size()).copyFrom(bytes);\n        view = view.slice(bytes.size());\n      }\n      KJ_CASE_ONEOF(text, kj::String) {\n        auto byteLength = text.asBytes().size();\n        if (byteLength == 0) continue;\n        KJ_ASSERT(view.size() >= byteLength);\n        view.first(byteLength).copyFrom(text.asBytes());\n        view = view.slice(byteLength);\n      }\n      KJ_CASE_ONEOF(blob, jsg::Ref<Blob>) {\n        auto data = blob->getData();\n        if (data.size() == 0) continue;\n        KJ_ASSERT(view.size() >= data.size());\n        view.first(data.size()).copyFrom(data);\n        view = view.slice(data.size());\n      }\n    }\n  }\n\n  KJ_ASSERT(view == nullptr);\n\n  return kj::mv(result);\n}\n\nkj::String normalizeType(kj::String type) {\n  // This does not properly parse mime types. We have the new workerd::MimeType impl\n  // but that handles mime types a bit more strictly than this. Ideally we'd be able to\n  // switch over to it but there's a non-zero risk of breaking running code. We might need\n  // a compat flag to switch at some point but for now we'll keep this as it is.\n\n  // https://www.w3.org/TR/FileAPI/#constructorBlob step 3 inexplicably insists that if the\n  // type contains non-printable-ASCII characters we should discard it, and otherwise we should\n  // lower-case it.\n  for (char& c: type) {\n    if (static_cast<signed char>(c) < 0x20) {\n      // Throw it away.\n      return nullptr;\n    } else if ('A' <= c && c <= 'Z') {\n      c = c - 'A' + 'a';\n    }\n  }\n\n  return kj::mv(type);\n}\n\n}  // namespace\n\nBlob::Blob(kj::Array<byte> data, kj::String type)\n    : ownData(kj::mv(data)),\n      data(ownData.get<kj::Array<kj::byte>>()),\n      type(kj::mv(type)) {}\n\nBlob::Blob(jsg::Lock& js, jsg::BufferSource data, kj::String type)\n    : ownData(kj::mv(data)),\n      data(ownData.get<jsg::BufferSource>().asArrayPtr()),\n      type(kj::mv(type)) {}\n\nBlob::Blob(jsg::Ref<Blob> parent, kj::ArrayPtr<const byte> data, kj::String type)\n    : ownData(kj::mv(parent)),\n      data(data),\n      type(kj::mv(type)) {}\n\njsg::Ref<Blob> Blob::constructor(\n    jsg::Lock& js, jsg::Optional<Bits> bits, jsg::Optional<Options> options) {\n  kj::String type;  // note: default value is intentionally empty string\n  KJ_IF_SOME(o, options) {\n    KJ_IF_SOME(t, o.type) {\n      type = normalizeType(kj::mv(t));\n    }\n  }\n\n  return js.alloc<Blob>(js, concat(js, kj::mv(bits)), kj::mv(type));\n}\n\nkj::ArrayPtr<const byte> Blob::getData() const {\n  FeatureObserver::maybeRecordUse(FeatureObserver::Feature::BLOB_GET_DATA);\n  return data;\n}\n\njsg::Ref<Blob> Blob::slice(jsg::Lock& js,\n    jsg::Optional<int> maybeStart,\n    jsg::Optional<int> maybeEnd,\n    jsg::Optional<kj::String> type) {\n  int start = maybeStart.orDefault(0);\n  int end = maybeEnd.orDefault(data.size());\n\n  if (start < 0) {\n    // Negative value interpreted as offset from end.\n    start += data.size();\n  }\n  // Clamp start to range.\n  if (start < 0) {\n    start = 0;\n  } else if (start > data.size()) {\n    start = data.size();\n  }\n\n  if (end < 0) {\n    // Negative value interpreted as offset from end.\n    end += data.size();\n  }\n  // Clamp end to range.\n  if (end < start) {\n    end = start;\n  } else if (end > data.size()) {\n    end = data.size();\n  }\n\n  return js.alloc<Blob>(\n      JSG_THIS, data.slice(start, end), normalizeType(kj::mv(type).orDefault(nullptr)));\n}\n\njsg::Promise<jsg::BufferSource> Blob::arrayBuffer(jsg::Lock& js) {\n  FeatureObserver::maybeRecordUse(FeatureObserver::Feature::BLOB_AS_ARRAY_BUFFER);\n  // We use BufferSource here instead of kj::Array<kj::byte> to ensure that the\n  // resulting backing store is associated with the isolate, which is necessary\n  // for when we start making use of v8 sandboxing.\n  auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, data.size());\n  backing.asArrayPtr().copyFrom(data);\n  return js.resolvedPromise(jsg::BufferSource(js, kj::mv(backing)));\n}\n\njsg::Promise<jsg::BufferSource> Blob::bytes(jsg::Lock& js) {\n  // We use BufferSource here instead of kj::Array<kj::byte> to ensure that the\n  // resulting backing store is associated with the isolate, which is necessary\n  // for when we start making use of v8 sandboxing.\n  auto backing = jsg::BackingStore::alloc<v8::Uint8Array>(js, data.size());\n  backing.asArrayPtr().copyFrom(data);\n  return js.resolvedPromise(jsg::BufferSource(js, kj::mv(backing)));\n}\n\njsg::Promise<kj::String> Blob::text(jsg::Lock& js) {\n  FeatureObserver::maybeRecordUse(FeatureObserver::Feature::BLOB_AS_TEXT);\n  return js.resolvedPromise(kj::str(data.asChars()));\n}\n\njsg::Ref<ReadableStream> Blob::stream(jsg::Lock& js) {\n  FeatureObserver::maybeRecordUse(FeatureObserver::Feature::BLOB_AS_STREAM);\n  return js.alloc<ReadableStream>(\n      IoContext::current(), streams::newMemorySource(data, kj::heap(JSG_THIS)));\n}\n\n// =======================================================================================\n\nFile::File(kj::Array<byte> data, kj::String name, kj::String type, double lastModified)\n    : Blob(kj::mv(data), kj::mv(type)),\n      name(kj::mv(name)),\n      lastModified(lastModified) {}\n\nFile::File(\n    jsg::Lock& js, jsg::BufferSource data, kj::String name, kj::String type, double lastModified)\n    : Blob(js, kj::mv(data), kj::mv(type)),\n      name(kj::mv(name)),\n      lastModified(lastModified) {}\n\nFile::File(jsg::Ref<Blob> parent,\n    kj::ArrayPtr<const byte> data,\n    kj::String name,\n    kj::String type,\n    double lastModified)\n    : Blob(kj::mv(parent), data, kj::mv(type)),\n      name(kj::mv(name)),\n      lastModified(lastModified) {}\n\njsg::Ref<File> File::constructor(\n    jsg::Lock& js, jsg::Optional<Bits> bits, kj::String name, jsg::Optional<Options> options) {\n  kj::String type;  // note: default value is intentionally empty string\n  kj::Maybe<double> maybeLastModified;\n  KJ_IF_SOME(o, options) {\n    KJ_IF_SOME(t, o.type) {\n      type = normalizeType(kj::mv(t));\n    }\n    maybeLastModified = o.lastModified;\n  }\n\n  double lastModified;\n  KJ_IF_SOME(m, maybeLastModified) {\n    lastModified = kj::isNaN(m) ? 0 : m;\n  } else {\n    lastModified = dateNow();\n  }\n\n  return js.alloc<File>(js, concat(js, kj::mv(bits)), kj::mv(name), kj::mv(type), lastModified);\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/blob.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api {\n\nclass ReadableStream;\nclass File;\n\n// An implementation of the Web Platform Standard Blob API\nclass Blob: public jsg::Object {\n public:\n  Blob(jsg::Lock& js, jsg::BufferSource data, kj::String type);\n  Blob(jsg::Ref<Blob> parent, kj::ArrayPtr<const byte> data, kj::String type);\n\n  kj::ArrayPtr<const byte> getData() const KJ_LIFETIMEBOUND;\n\n  // ---------------------------------------------------------------------------\n  // JS API\n\n  struct Options {\n    jsg::Optional<kj::String> type;\n    jsg::Unimplemented endings;\n\n    JSG_STRUCT(type, endings);\n  };\n\n  using Bits = kj::Array<kj::OneOf<kj::Array<const byte>, kj::String, jsg::Ref<Blob>>>;\n\n  static jsg::Ref<Blob> constructor(\n      jsg::Lock& js, jsg::Optional<Bits> bits, jsg::Optional<Options> options);\n\n  int getSize() const {\n    return data.size();\n  }\n  kj::StringPtr getType() const {\n    return type;\n  }\n\n  jsg::Ref<Blob> slice(jsg::Lock& js,\n      jsg::Optional<int> start,\n      jsg::Optional<int> end,\n      jsg::Optional<kj::String> type);\n\n  jsg::Promise<jsg::BufferSource> arrayBuffer(jsg::Lock& js);\n  jsg::Promise<jsg::BufferSource> bytes(jsg::Lock& js);\n  jsg::Promise<kj::String> text(jsg::Lock& js);\n  jsg::Ref<ReadableStream> stream(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(Blob, CompatibilityFlags::Reader flags) {\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(size, getSize);\n      JSG_READONLY_PROTOTYPE_PROPERTY(type, getType);\n    } else {\n      JSG_READONLY_INSTANCE_PROPERTY(size, getSize);\n      JSG_READONLY_INSTANCE_PROPERTY(type, getType);\n    }\n\n    JSG_METHOD(slice);\n    JSG_METHOD(arrayBuffer);\n    JSG_METHOD(bytes);\n    JSG_METHOD(text);\n    JSG_METHOD(stream);\n\n    JSG_TS_OVERRIDE({\n      bytes(): Promise<Uint8Array>;\n      arrayBuffer(): Promise<ArrayBuffer>;\n    });\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    KJ_SWITCH_ONEOF(ownData) {\n      KJ_CASE_ONEOF(data, jsg::BufferSource) {\n        tracker.trackField(\"ownData\", data);\n      }\n      KJ_CASE_ONEOF(data, jsg::Ref<Blob>) {\n        tracker.trackField(\"ownData\", data);\n      }\n      KJ_CASE_ONEOF(data, kj::Array<kj::byte>) {\n        tracker.trackField(\"ownData\", data);\n      }\n    }\n    tracker.trackField(\"type\", type);\n  }\n\n private:\n  Blob(kj::Array<byte> data, kj::String type);\n\n  // Using a jsg::BufferSource to store the ownData allows the associated isolate\n  // to track the external data allocation correctly.\n  // The Variation that uses kj::Array<kj::byte> only is used only in very\n  // specific cases (i.e. the internal fiddle service) where we parse FormData\n  // outside of the isolate lock.\n  kj::OneOf<jsg::BufferSource, kj::Array<kj::byte>, jsg::Ref<Blob>> ownData;\n  kj::ArrayPtr<const byte> data;\n  kj::String type;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    KJ_SWITCH_ONEOF(ownData) {\n      KJ_CASE_ONEOF(b, jsg::BufferSource) {\n        visitor.visit(b);\n      }\n      KJ_CASE_ONEOF(b, jsg::Ref<Blob>) {\n        visitor.visit(b);\n      }\n      KJ_CASE_ONEOF(b, kj::Array<kj::byte>) {}\n    }\n  }\n\n  // this could just be \"friend File;\", but clang-cl wants to see the qualified name here.\n  friend class ::workerd::api::File;\n};\n\n// An implementation of the Web Platform Standard File API\nclass File: public Blob {\n public:\n  // This constructor variation is used when a File is created outside of the isolate\n  // lock. This is currently only the case when parsing FormData outside of running\n  // JavaScript (such as in the internal fiddle service).\n  File(kj::Array<byte> data, kj::String name, kj::String type, double lastModified);\n  File(\n      jsg::Lock& js, jsg::BufferSource data, kj::String name, kj::String type, double lastModified);\n  File(jsg::Ref<Blob> parent,\n      kj::ArrayPtr<const byte> data,\n      kj::String name,\n      kj::String type,\n      double lastModified);\n\n  struct Options {\n    jsg::Optional<kj::String> type;\n    jsg::Optional<double> lastModified;\n    jsg::Unimplemented endings;\n\n    JSG_STRUCT(type, lastModified, endings);\n  };\n\n  static jsg::Ref<File> constructor(\n      jsg::Lock& js, jsg::Optional<Bits> bits, kj::String name, jsg::Optional<Options> options);\n\n  kj::StringPtr getName() {\n    return name;\n  }\n  double getLastModified() {\n    return lastModified;\n  }\n\n  JSG_RESOURCE_TYPE(File, CompatibilityFlags::Reader flags) {\n    JSG_INHERIT(Blob);\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(name, getName);\n      JSG_READONLY_PROTOTYPE_PROPERTY(lastModified, getLastModified);\n    } else {\n      JSG_READONLY_INSTANCE_PROPERTY(name, getName);\n      JSG_READONLY_INSTANCE_PROPERTY(lastModified, getLastModified);\n    }\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"name\", name);\n  }\n\n private:\n  kj::String name;\n  double lastModified;\n};\n\n#define EW_BLOB_ISOLATE_TYPES api::Blob, api::Blob::Options, api::File, api::File::Options\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/cache.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"cache.h\"\n\n#include \"util.h\"\n\n#include <workerd/io/io-context.h>\n#include <workerd/util/own-util.h>\n\n#include <kj/encoding.h>\n\nnamespace workerd::api {\n\n// =======================================================================================\n// Cache\n\nnamespace {\n\n// TODO(someday): Implement Cache API in preview.\nconstexpr auto CACHE_API_PREVIEW_WARNING =\n    \"The Service Workers Cache API is currently unimplemented in the Cloudflare Workers Preview. \"\n    \"Cache API operations which would function normally in production will not throw any errors, \"\n    \"but will have no effect. Notably, Cache.match() will always return undefined, and \"\n    \"Cache.delete() will always return false. When you deploy your script to production, its \"\n    \"caching behavior will function as expected.\"_kj;\n\n#define LOG_CACHE_ERROR_ONCE(TEXT, RESPONSE)\n// TODO(someday): Fix Cache API bugs. We logged them for two years as a reminder, but... they\n//   never got fixed. The logging is making it hard to see other problems. So we're ending it.\n//   If someone decides to take this on again, you can restore this macro's implementation as\n//   follows:\n//\n// #define LOG_CACHE_ERROR_ONCE(TEXT, RESPONSE) ({ \\\n//   static bool seen = false; \\\n//   if (!seen) { \\\n//     seen = true; \\\n//     KJ_LOG(ERROR, TEXT, RESPONSE.statusCode, RESPONSE.statusText); \\\n//   } \\\n// })\n\n// Throw an application-visible exception if the URL won't be parsed correctly at a lower\n// layer. If the URL is valid then just return it. The purpose of this function is to avoid\n// throwing an \"internal error\".\nkj::StringPtr validateUrl(kj::StringPtr url) {\n  // TODO(bug): We should parse and process URLs the same way we would URLs passed to fetch().\n  //   But, that might mean e.g. discarding fragments (\"hashes\", stuff after a '#'), which would\n  //   be a change in behavior that could subtly affect production workers...\n\n  static constexpr auto urlOptions = kj::Url::Options{\n    .percentDecode = false,\n    .allowEmpty = true,\n  };\n\n  JSG_REQUIRE(kj::Url::tryParse(url, kj::Url::HTTP_PROXY_REQUEST, urlOptions) != kj::none,\n      TypeError, \"Invalid URL. Cache API keys must be fully-qualified, valid URLs.\");\n\n  return url;\n}\n\n}  // namespace\n\nCache::Cache(kj::Maybe<kj::String> cacheName): cacheName(kj::mv(cacheName)) {}\n\njsg::Unimplemented Cache::add(Request::Info request) {\n  return {};\n}\n\njsg::Unimplemented Cache::addAll(kj::Array<Request::Info> requests) {\n  return {};\n}\n\njsg::Promise<jsg::Optional<jsg::Ref<Response>>> Cache::match(jsg::Lock& js,\n    Request::Info requestOrUrl,\n    jsg::Optional<CacheQueryOptions> options,\n    CompatibilityFlags::Reader flags) {\n  // TODO(someday): Implement Cache API in preview.\n  auto& context = IoContext::current();\n  TraceContext traceContext = context.makeUserTraceSpan(\"cache_match\"_kjc);\n\n  KJ_IF_SOME(o, options) {\n    KJ_IF_SOME(ignoreMethod, o.ignoreMethod) {\n      traceContext.setTag(\"cache.request.ignore_method\"_kjc, ignoreMethod);\n    }\n  }\n\n  if (context.isFiddle()) {\n    context.logWarningOnce(CACHE_API_PREVIEW_WARNING);\n    return js.resolvedPromise(jsg::Optional<jsg::Ref<Response>>());\n  }\n\n  // This use of evalNow() is obsoleted by the capture_async_api_throws compatibility flag, but\n  // we need to keep it here for people who don't have that flag set.\n  return js.evalNow([&]() -> jsg::Promise<jsg::Optional<jsg::Ref<Response>>> {\n    auto jsRequest = Request::coerce(js, kj::mv(requestOrUrl), kj::none);\n\n    traceContext.setTag(\"cache.request.url\"_kjc, jsRequest->getUrl());\n    traceContext.setTag(\"cache.request.method\"_kjc, kj::str(jsRequest->getMethodEnum()));\n\n    if (!options.orDefault({}).ignoreMethod.orDefault(false) &&\n        jsRequest->getMethodEnum() != kj::HttpMethod::GET) {\n      return js.resolvedPromise(jsg::Optional<jsg::Ref<Response>>());\n    }\n\n    auto httpClient = getHttpClient(\n        context, jsRequest->serializeCfBlobJson(js), traceContext, flags.getCacheApiCompatFlags());\n    auto requestHeaders = kj::HttpHeaders(context.getHeaderTable());\n    jsRequest->shallowCopyHeadersTo(requestHeaders);\n\n    auto headerIds = context.getHeaderIds();\n    // parse each of the request headers to add info to span\n    KJ_IF_SOME(range, requestHeaders.get(headerIds.range)) {\n      traceContext.setTag(\"cache.request.header.range\"_kjc, range);\n    }\n    KJ_IF_SOME(ifModifiedSince, requestHeaders.get(headerIds.ifModifiedSince)) {\n      traceContext.setTag(\"cache.request.header.if_modified_since\"_kjc, ifModifiedSince);\n    }\n    KJ_IF_SOME(ifNoneMatch, requestHeaders.get(headerIds.ifNoneMatch)) {\n      traceContext.setTag(\"cache.request.header.if_none_match\"_kjc, ifNoneMatch);\n    }\n\n    requestHeaders.setPtr(context.getHeaderIds().cacheControl, \"only-if-cached\");\n    auto nativeRequest = httpClient->request(kj::HttpMethod::GET, validateUrl(jsRequest->getUrl()),\n        requestHeaders, static_cast<uint64_t>(0));\n\n    return context.awaitIo(js, kj::mv(nativeRequest.response),\n        [httpClient = kj::mv(httpClient), &context, traceContext = kj::mv(traceContext)](\n            jsg::Lock& js,\n            kj::HttpClient::Response&& response) mutable -> jsg::Optional<jsg::Ref<Response>> {\n      response.body = response.body.attach(kj::mv(httpClient));\n\n      traceContext.setTag(\n          \"cache.response.status_code\"_kjc, static_cast<int64_t>(response.statusCode));\n      KJ_IF_SOME(length, response.body->tryGetLength()) {\n        traceContext.setTag(\"cache.response.body.size\"_kjc, static_cast<int64_t>(length));\n      }\n\n      kj::StringPtr cacheStatus;\n      KJ_IF_SOME(cs, response.headers->get(context.getHeaderIds().cfCacheStatus)) {\n        cacheStatus = cs;\n        traceContext.setTag(\"cache.response.cache_status\"_kjc, cacheStatus);\n      } else {\n        // This is an internal error representing a violation of the contract between us and\n        // the cache. Since it is always conformant to return undefined from Cache::match()\n        // (because we are allowed to evict any asset at any time), we don't really need to make the\n        // script fail. However, it might be indicative of a larger problem, and should be\n        // investigated.\n        LOG_CACHE_ERROR_ONCE(\"Response to Cache API GET has no CF-Cache-Status: \", response);\n        traceContext.setTag(\"cache.response.success\"_kjc, false);\n        return kj::none;\n      }\n\n      // The status code should be a 504 on cache miss, but we need to rely on CF-Cache-Status\n      // because someone might cache a 504.\n      // See https://httpwg.org/specs/rfc7234.html#cache-request-directive.only-if-cached\n      //\n      // TODO(cleanup): CACHE-5949 We should never receive EXPIRED or UPDATING responses, but we do.\n      //   We treat them the same as a MISS mostly to keep from blowing up our Sentry reports.\n      // TODO(someday): If the cache status is EXPIRED and we return undefined here, does a PURGE on\n      //   this URL result in a 200, causing us to return true from Cache::delete_()? If so, that's\n      //   a small inconsistency: we shouldn't have a match failure but a delete success.\n      if (cacheStatus == \"MISS\" || cacheStatus == \"EXPIRED\" || cacheStatus == \"UPDATING\") {\n        traceContext.setTag(\"cache.response.success\"_kjc, false);\n        return kj::none;\n      } else if (cacheStatus != \"HIT\") {\n        // Another internal error. See above comment where we retrieve the CF-Cache-Status header.\n        LOG_CACHE_ERROR_ONCE(\"Response to Cache API GET has invalid CF-Cache-Status: \", response);\n        traceContext.setTag(\"cache.response.success\"_kjc, false);\n        return kj::none;\n      }\n\n      traceContext.setTag(\"cache.response.success\"_kjc, true);\n      return makeHttpResponse(js, kj::HttpMethod::GET, {}, response.statusCode, response.statusText,\n          *response.headers, kj::mv(response.body), kj::none);\n    });\n  });\n}\n\n// Send a PUT request to the cache whose URL is the original request URL and whose body is the\n// HTTP response we'd like to cache for that request.\n//\n// The HTTP response in the PUT request body (the \"PUT payload\") must itself be an HTTP message,\n// except that it MUST NOT have chunked encoding applied to it, even if it has a\n// Transfer-Encoding: chunked header. To be clear, the PUT request itself may be chunked, but it\n// must not have any nested chunked encoding.\n//\n// In order to extract the response's data to serialize it, we'll need to call\n// `jsResponse->send()`, which will properly encode the response's body if a Content-Encoding\n// header is present. This means we'll need to create an instance of kj::HttpService::Response.\njsg::Promise<void> Cache::put(jsg::Lock& js,\n    Request::Info requestOrUrl,\n    jsg::Ref<Response> jsResponse,\n    CompatibilityFlags::Reader flags) {\n\n  JSG_REQUIRE(\n      jsResponse->getType() != \"error\"_kj, TypeError, \"Cache is unable to store an error response\");\n\n  // Fake kj::HttpService::Response implementation that allows us to reuse jsResponse->send() to\n  // serialize the response (headers + body) in the format needed to serve as the payload of\n  // our cache PUT request.\n  class ResponseSerializer final: public kj::HttpService::Response {\n   public:\n    struct Payload {\n      // The serialized form of the response to be cached. This stream itself contains a full\n      // HTTP response, with headers and body, representing the content of jsResponse to be written\n      // to the cache.\n      kj::Own<kj::AsyncInputStream> stream;\n\n      // A promise which resolves once the payload's headers have been written. Normally, this\n      // couldn't possibly resolve until the body has been written, and jsRepsonse->send() won't\n      // complete until then -- except if the body is empty, in which case jsResponse->send() may\n      // return immediately.\n      kj::Promise<void> writeHeadersPromise;\n    };\n\n    Payload getPayload() {\n      return KJ_ASSERT_NONNULL(kj::mv(payload));\n    }\n\n   private:\n    kj::Own<kj::AsyncOutputStream> send(uint statusCode,\n        kj::StringPtr statusText,\n        const kj::HttpHeaders& headers,\n        kj::Maybe<uint64_t> expectedBodySize) override {\n      kj::String contentLength;\n\n      kj::StringPtr connectionHeaders[kj::HttpHeaders::CONNECTION_HEADERS_COUNT];\n      KJ_IF_SOME(ebs, expectedBodySize) {\n        contentLength = kj::str(ebs);\n        connectionHeaders[kj::HttpHeaders::BuiltinIndices::CONTENT_LENGTH] = contentLength;\n      } else {\n        connectionHeaders[kj::HttpHeaders::BuiltinIndices::TRANSFER_ENCODING] = \"chunked\";\n      }\n\n      auto serializedHeaders = headers.serializeResponse(statusCode, statusText, connectionHeaders);\n\n      auto expectedPayloadSize =\n          expectedBodySize.map([&](uint64_t size) { return size + serializedHeaders.size(); });\n\n      // We want to create an AsyncInputStream that represents the payload, including both headers\n      // and body. To do this, we'll create a one-way pipe, using the input end of the pipe as\n      // said stream. This means we have to write the headers, followed by the body, to the output\n      // end of the pipe.\n      //\n      // send() needs to return a stream to which the caller can write the body. Since we need to\n      // make sure the headers are written first, we'll return a kj::newPromisedStream(), using a\n      // promise that resolves to the pipe output as soon as the headers are written.\n      //\n      // There's a catch: Unfortunately, if the caller doesn't intend to write any body, then they\n      // will probably drop the return stream immediately. This could prematurely cancel our header\n      // write. To avoid that, we split the promise and keep a branch in `writeHeadersPromise`,\n      // which will have to be awaited separately.\n      auto payloadPipe = kj::newOneWayPipe(expectedPayloadSize);\n\n      static auto constexpr handleHeaders = [](kj::Own<kj::AsyncOutputStream> out,\n                                                kj::String serializedHeaders)\n          -> kj::Promise<kj::Tuple<kj::Own<kj::AsyncOutputStream>, bool>> {\n        co_await out->write(serializedHeaders.asBytes());\n        co_return kj::tuple(kj::mv(out), false);\n      };\n\n      auto headersPromises =\n          handleHeaders(kj::mv(payloadPipe.out), kj::mv(serializedHeaders)).split();\n\n      payload = Payload{.stream = kj::mv(payloadPipe.in),\n        .writeHeadersPromise = kj::get<1>(headersPromises).ignoreResult()};\n\n      return kj::newPromisedStream(kj::mv(kj::get<0>(headersPromises)));\n    }\n\n    kj::Own<kj::WebSocket> acceptWebSocket(const kj::HttpHeaders&) override {\n      JSG_FAIL_REQUIRE(TypeError, \"Cannot cache WebSocket upgrade response.\");\n    }\n\n    kj::Maybe<Payload> payload;\n  };\n\n  // This use of evalNow() is obsoleted by the capture_async_api_throws compatibility flag, but\n  // we need to keep it here for people who don't have that flag set.\n  return js.evalNow([&] {\n    auto jsRequest = Request::coerce(js, kj::mv(requestOrUrl), kj::none);\n\n    auto& context = IoContext::current();\n    TraceContext traceContext = context.makeUserTraceSpan(\"cache_put\"_kjc);\n\n    traceContext.setTag(\"cache.request.url\"_kjc, jsRequest->getUrl());\n    traceContext.setTag(\"cache.request.method\"_kjc, kj::str(jsRequest->getMethodEnum()));\n    traceContext.setTag(\n        \"cache.request.payload.status_code\"_kjc, static_cast<int64_t>(jsResponse->getStatus()));\n\n    // TODO(conform): Require that jsRequest's url has an http or https scheme. This is only\n    //   important if api::Request is changed to parse its URL eagerly (as required by spec), rather\n    //   than at fetch()-time.\n\n    JSG_REQUIRE(jsRequest->getMethodEnum() == kj::HttpMethod::GET, TypeError,\n        \"Cannot cache response to non-GET request.\");\n\n    JSG_REQUIRE(jsResponse->getStatus() != 206, TypeError,\n        \"Cannot cache response to a range request (206 Partial Content).\");\n\n    auto responseHeadersRef = jsResponse->getHeaders(js);\n    auto cacheControl = responseHeadersRef->getCommon(js, capnp::CommonHeaderName::CACHE_CONTROL);\n\n    KJ_IF_SOME(vary, responseHeadersRef->getCommon(js, capnp::CommonHeaderName::VARY)) {\n      JSG_REQUIRE(vary.findFirst('*') == kj::none, TypeError,\n          \"Cannot cache response with 'Vary: *' header.\");\n    }\n\n    KJ_IF_SOME(cacheControl,\n        responseHeadersRef->getCommon(js, capnp::CommonHeaderName::CACHE_CONTROL)) {\n      traceContext.setTag(\"cache.request.payload.header.cache_control\"_kjc, cacheControl.asPtr());\n    }\n    KJ_IF_SOME(cacheTag, responseHeadersRef->getPtr(js, \"cache-tag\"_kj)) {\n      traceContext.setTag(\"cache.request.payload.header.cache_tag\"_kjc, cacheTag.asPtr());\n    }\n    KJ_IF_SOME(etag, responseHeadersRef->getCommon(js, capnp::CommonHeaderName::ETAG)) {\n      traceContext.setTag(\"cache.request.payload.header.etag\"_kjc, etag.asPtr());\n    }\n    KJ_IF_SOME(expires, responseHeadersRef->getCommon(js, capnp::CommonHeaderName::EXPIRES)) {\n      traceContext.setTag(\"cache.request.payload.header.expires\"_kjc, expires.asPtr());\n    }\n    KJ_IF_SOME(lastModified,\n        responseHeadersRef->getCommon(js, capnp::CommonHeaderName::LAST_MODIFIED)) {\n      traceContext.setTag(\"cache.request.payload.header.last_modified\"_kjc, lastModified.asPtr());\n    }\n\n    if (jsResponse->getStatus() == 304) {\n      // Silently discard 304 status responses to conditional requests. Caching 304s could be a\n      // source of bugs in a worker, since a worker which blindly stuffs responses from `fetch()`\n      // into cache could end up caching one, then later respond to non-conditional requests with\n      // the cached 304.\n      //\n      // Unlike the 206 response status check above, we don't throw here because we used to allow\n      // this behavior. Silently discarding 304s maintains backwards compatibility and is actually\n      // still spec-conformant.\n\n      if (context.hasWarningHandler()) {\n        context.logWarning(\n            \"Ignoring attempt to Cache.put() a 304 status response. 304 responses \"\n            \"are not meaningful to cache, and a potential source of bugs. Consider validating that \"\n            \"the response status is meaningful to cache before calling Cache.put().\");\n      }\n\n      return js.resolvedPromise();\n    }\n\n    ResponseSerializer serializer;\n    // We need to send the response to our serializer immediately in order to fulfill Cache.put()'s\n    // contract: the caller should be able to observe that the response body is disturbed as soon\n    // as put() returns.\n    auto serializePromise = jsResponse->send(js, serializer, {}, kj::none);\n    auto payload = serializer.getPayload();\n\n    KJ_IF_SOME(length, payload.stream->tryGetLength()) {\n      traceContext.setTag(\"cache.request.payload.size\"_kjc, static_cast<int64_t>(length));\n    }\n\n    // TODO(someday): Implement Cache API in preview. This bail-out lives all the way down here,\n    //   after all KJ_REQUIRE checks and the start of response serialization, so that Cache.put()\n    //   fulfills its contract, even in the preview. This prevents buggy code from working in the\n    //   preview, but failing in production.\n    if (context.isFiddle()) {\n      context.logWarningOnce(CACHE_API_PREVIEW_WARNING);\n      return js.resolvedPromise();\n    }\n\n    // Wait for output locks and cache put quota, trying to avoid returning to the KJ event loop\n    // in the common case where no waits are needed.\n    // TODO(later): With Cache streams no longer having a size limit enforced by the runtime,\n    // explore if we can clean up stream serialization too.\n    jsg::Promise<IoOwn<kj::AsyncInputStream>> startStreamPromise = nullptr;\n    auto makeCachePutStream = [&context, stream = kj::mv(payload.stream)](jsg::Lock& js) mutable {\n      return context.makeCachePutStream(js, kj::mv(stream));\n    };\n    KJ_IF_SOME(p, context.waitForOutputLocksIfNecessary()) {\n      startStreamPromise = context.awaitIo(js, kj::mv(p), kj::mv(makeCachePutStream));\n    } else {\n      startStreamPromise = makeCachePutStream(js);\n    }\n\n    return startStreamPromise.then(js,\n        context.addFunctor(\n            [this, &context, jsRequest = kj::mv(jsRequest), cacheControl = kj::mv(cacheControl),\n                serializePromise = kj::mv(serializePromise),\n                writePayloadHeadersPromise = kj::mv(payload.writeHeadersPromise),\n                enableCompatFlags = flags.getCacheApiCompatFlags(),\n                traceContext = kj::mv(traceContext)](jsg::Lock& js,\n                IoOwn<kj::AsyncInputStream> payloadStream) mutable -> jsg::Promise<void> {\n      // Make the PUT request to cache.\n      auto httpClient = getHttpClient(\n          context, jsRequest->serializeCfBlobJson(js), traceContext, enableCompatFlags);\n      auto requestHeaders = kj::HttpHeaders(context.getHeaderTable());\n      jsRequest->shallowCopyHeadersTo(requestHeaders);\n      auto nativeRequest = httpClient->request(kj::HttpMethod::PUT,\n          validateUrl(jsRequest->getUrl()), requestHeaders, payloadStream->tryGetLength());\n\n      auto pumpRequestBodyPromise = payloadStream->pumpTo(*nativeRequest.body).ignoreResult();\n      // NOTE: We don't attach nativeRequest.body here because we want to control its\n      //   destruction timing in the event of an error; see below.\n\n      // The next step is a bit complicated as it occurs in two separate async flows.\n      // First, we await the serialization promise, then enter \"deferred proxying\" by issuing\n      // `KJ_CO_MAGIC BEGIN_DEFERRED_PROXYING` from our coroutine. Everything after that\n      // `KJ_CO_MAGIC` constitutes the second async flow that actually handles the request and\n      // response.\n      //\n      // Weird: It's important that these objects be torn down in the right order and that the\n      // DeferredProxy promise is handled separately from the inner promise.\n      //\n      // Moreover, there is an interesting property: In the event that `httpClient` is destroyed\n      // immediately after `bodyStream` (i.e. without returning to the KJ event loop in between),\n      // and the body is chunked, then the connection will be closed before the terminating chunk\n      // can be written. This is actually convenient as it allows us to make sure that when we\n      // bail out due to an error, the cache is able to see that the request was incomplete and\n      // should therefore not commit the cache entry.\n      //\n      // This is a bit of an accident. It would be much better if KJ's AsyncOutputStream had an\n      // explicit `end()` method to indicate all data had been written successfully, rather than\n      // just assume so in the destructor. But, that's a major refactor, and it's immediately\n      // important to us that we don't write incomplete cache entries, so we rely on this hack for\n      // now. See EW-812 for the broader problem.\n      //\n      // A little funky: The process of \"serializing\" the cache entry payload means reading all the\n      // data from the payload body stream and writing it to cache. But, the payload body might\n      // originate from the app's own JavaScript, rather than being the response to some remote\n      // request. If the stream is JS-backed, then we want to be careful to track \"pending events\".\n      // Specifically, if the stream hasn't reported EOF yet, but JavaScript stops executing and\n      // there is no external I/O that we're waiting for, then we know that the stream will never\n      // end, and we want to cancel out the IoContext proactively.\n      //\n      // If we were to use `context.awaitIo(serializePromise)` here, we'd lose this property,\n      // because the context would believe that waiting for the stream itself constituted I/O, even\n      // if the stream is backed by JS.\n      //\n      // On the other hand, once the serialization step completes, we need to wait for the cache\n      // backend to respond. At that point, we *are* awaiting I/O, and want to record that\n      // correctly.\n      //\n      // So basically, we have an asynchronous promise we need to wait for, and for the first part\n      // of that wait, we don't want to count it as pending I/O, but for the second part, we do.\n      // How do we accomplish this?\n      //\n      // Well, it just so happens that `serializePromise` is a special kind of promise that might\n      // help us -- it's kj::Promise<DeferredProxy<void>>, a deferred proxy stream promise. This\n      // is a promise-for-a-promise, with an interesting property: the outer promise is used to\n      // wait for JavaScript-backed stream events, while the inner promise represents pure external\n      // I/O. The method context.awaitDeferredProxy() awaits this special kind of promise, and it\n      // already only counts the inner promise as being external pending I/O.\n      //\n      // However, we have some additional work we want to do *after* serializePromise (both parts)\n      // completes -- additional work that is also external I/O. So how do we handle that? Well...\n      // we can actually append it to `serializePromise`'s inner promise! Then awaitDeferredProxy()\n      // will properly treat it as pending I/O, but only *after* the outer promise completes. This\n      // gets us everything we want.\n      //\n      // Hence, what you see here: we first await the serializePromise, then enter deferred proxying\n      // with our magic `KJ_CO_MAGIC`, then perform all our additional work. Then we\n      // `awaitDeferredProxy()` the whole thing.\n\n      // Here we handle the promise for the DeferredProxy itself.\n      static auto constexpr handleSerialize =\n          [](kj::Promise<DeferredProxy<void>> serialize, kj::Own<kj::HttpClient> httpClient,\n              kj::Promise<kj::HttpClient::Response> responsePromise,\n              kj::Own<kj::AsyncOutputStream> bodyStream, kj::Promise<void> pumpRequestBodyPromise,\n              kj::Promise<void> writePayloadHeadersPromise,\n              kj::Own<kj::AsyncInputStream> payloadStream,\n              TraceContext traceContext) -> kj::Promise<DeferredProxy<void>> {\n        // This is extremely odd and a bit annoying but we have to make sure\n        // these are destroyed in a particular order due to cross-dependencies\n        // for each. If the kj::Promise returned by handleSerialize is dropped\n        // before the co_await serialize completes, then these won't ever be\n        // moved away into the handleResponse method (which ensures proper\n        // cleanup order). In such a case, we explicitly layout a cleanup order\n        // here to make it clear.\n        // Note: we could do this by ordering the arguments in a particular way,\n        // or by doing what a previous iteration of this code did and put everything\n        // into a struct in a particular order but pulling things out like this makes\n        // what is going on here much more intentional and explicit.\n        //\n        // If these are not cleaned up in the right order, there can be subtle\n        // use-after-free issues reported by asan and certain flows can end up\n        // hanging.\n        KJ_DEFER({\n          pumpRequestBodyPromise = nullptr;\n          payloadStream = nullptr;\n          bodyStream = nullptr;\n          responsePromise = nullptr;\n          writePayloadHeadersPromise = nullptr;\n          httpClient = nullptr;\n        });\n        try {\n          auto deferred = co_await serialize;\n\n          // With our `serialize` promise having resolved to a DeferredProxy, we can now enter\n          // deferred proxying ourselves.\n          KJ_CO_MAGIC BEGIN_DEFERRED_PROXYING;\n\n          co_await deferred.proxyTask;\n          // Make sure headers get written even if the body was empty -- see comments earlier.\n          co_await writePayloadHeadersPromise;\n          // Make sure the request body is done being pumped and had no errors. If serialization\n          // completed successfully, then this should also complete immediately thereafter.\n          co_await pumpRequestBodyPromise;\n          // It is important to destroy the bodyStream before actually waiting on the\n          // responsePromise to ensure that the terminal chunk is written since the bodyStream\n          // may only write the terminal chunk in the streams destructor.\n          bodyStream = nullptr;\n          payloadStream = nullptr;\n          auto response = co_await responsePromise;\n          // We expect to see either 204 (success) or 413 (failure). Any other status code is a\n          // violation of the contract between us and the cache, and is an internal\n          // error, which we log. However, there's no need to throw, since the Cache API is an\n          // ephemeral K/V store, and we never guaranteed the script we'd actually cache anything.\n          if (response.statusCode != 204 && response.statusCode != 413) {\n            LOG_CACHE_ERROR_ONCE(\"Response to Cache API PUT was neither 204 nor 413: \", response);\n          } else if (response.statusCode == 204) {\n            traceContext.setTag(\"cache.response.success\"_kjc, true);\n          } else if (response.statusCode == 413) {\n            traceContext.setTag(\"cache.response.success\"_kjc, false);\n          }\n        } catch (...) {\n          traceContext.setTag(\"cache.response.success\"_kjc, false);\n          auto exception = kj::getCaughtExceptionAsKj();\n          if (exception.getType() != kj::Exception::Type::DISCONNECTED) {\n            kj::throwFatalException(kj::mv(exception));\n          }\n          // If the origin or the cache disconnected, we don't treat this as an error, as put()\n          // doesn't guarantee that it stores anything anyway.\n          //\n          // TODO(someday): I (Kenton) don't understand why we'd explicitly want to hide this\n          //   error, even though hiding it is technically not a violation of the contract. To me\n          //   this seems undesirable, especially when it was the origin that failed. The caller\n          //   can always choose to ignore errors if they want (and many do, by passing to\n          //   waitUntil()). However, there is at least one test which depends on this behavior,\n          //   and probably production Workers in the wild, so I'm not changing it for now.\n        }\n      };\n\n      return context.awaitDeferredProxy(js,\n          handleSerialize(kj::mv(serializePromise), kj::mv(httpClient),\n              kj::mv(nativeRequest.response), kj::mv(nativeRequest.body),\n              kj::mv(pumpRequestBodyPromise), kj::mv(writePayloadHeadersPromise),\n              kj::mv(payloadStream), kj::mv(traceContext)));\n    }));\n  });\n}\n\njsg::Promise<bool> Cache::delete_(jsg::Lock& js,\n    Request::Info requestOrUrl,\n    jsg::Optional<CacheQueryOptions> options,\n    CompatibilityFlags::Reader flags) {\n  // TODO(someday): Implement Cache API in preview.\n  auto& context = IoContext::current();\n  TraceContext traceContext = context.makeUserTraceSpan(\"cache_delete\"_kjc);\n\n  KJ_IF_SOME(o, options) {\n    KJ_IF_SOME(ignoreMethod, o.ignoreMethod) {\n      traceContext.setTag(\"cache.request.ignore_method\"_kjc, ignoreMethod);\n    }\n  }\n\n  if (context.isFiddle()) {\n    context.logWarningOnce(CACHE_API_PREVIEW_WARNING);\n    return js.resolvedPromise(false);\n  }\n\n  // This use of evalNow() is obsoleted by the capture_async_api_throws compatibility flag, but\n  // we need to keep it here for people who don't have that flag set.\n  return js.evalNow([&]() -> jsg::Promise<bool> {\n    auto jsRequest = Request::coerce(js, kj::mv(requestOrUrl), kj::none);\n\n    traceContext.setTag(\"cache.request.url\"_kjc, jsRequest->getUrl());\n    traceContext.setTag(\"cache.request.method\"_kjc, kj::str(jsRequest->getMethodEnum()));\n    if (!options.orDefault({}).ignoreMethod.orDefault(false) &&\n        jsRequest->getMethodEnum() != kj::HttpMethod::GET) {\n      return js.resolvedPromise(false);\n    }\n\n    // Make the PURGE request to cache.\n\n    auto httpClient = getHttpClient(\n        context, jsRequest->serializeCfBlobJson(js), traceContext, flags.getCacheApiCompatFlags());\n    auto requestHeaders = kj::HttpHeaders(context.getHeaderTable());\n    jsRequest->shallowCopyHeadersTo(requestHeaders);\n    // HACK: The cache doesn't permit PURGE requests from the outside world. It does this by\n    //   filtering on X-Real-IP, which can't be set from the outside world. X-Real-IP can, however,\n    //   be set by a Worker when making requests to its own origin, as \"spoofing\" client IPs to\n    //   your own origin isn't a security flaw. Also, a Worker sending PURGE requests to its own\n    //   origin's cache is not a security flaw (that's what this very API is implementing after\n    //   all) so it all lines up nicely.\n    requestHeaders.addPtrPtr(\"X-Real-IP\"_kj, \"127.0.0.1\"_kj);\n    auto nativeRequest = httpClient->request(kj::HttpMethod::PURGE,\n        validateUrl(jsRequest->getUrl()), requestHeaders, static_cast<uint64_t>(0));\n\n    return context.awaitIo(js, kj::mv(nativeRequest.response),\n        [httpClient = kj::mv(httpClient), traceContext = kj::mv(traceContext)](\n            jsg::Lock&, kj::HttpClient::Response&& response) mutable -> bool {\n      traceContext.setTag(\n          \"cache.response.status_code\"_kjc, static_cast<int64_t>(response.statusCode));\n      if (response.statusCode == 200) {\n        traceContext.setTag(\"cache.response.success\"_kjc, true);\n        return true;\n      } else if (response.statusCode == 404) {\n        traceContext.setTag(\"cache.response.success\"_kjc, false);\n        return false;\n      } else if (response.statusCode == 429) {\n        traceContext.setTag(\"cache.response.success\"_kjc, false);\n        // Throw, but do not log the response to Sentry, as rate-limited subrequests are normal\n        JSG_FAIL_REQUIRE(\n            Error, \"Unable to delete cached response. Subrequests are being rate-limited.\");\n      }\n      LOG_CACHE_ERROR_ONCE(\"Response to Cache API PURGE was neither 200 nor 404: \", response);\n      JSG_FAIL_REQUIRE(Error, \"Unable to delete cached response.\");\n    });\n  });\n}\n\nkj::Own<kj::HttpClient> Cache::getHttpClient(IoContext& context,\n    kj::Maybe<kj::String> cfBlobJson,\n    TraceContext& traceContext,\n    bool enableCompatFlags) {\n  auto cacheClient = context.getCacheClient();\n  auto metadata = CacheClient::SubrequestMetadata{\n    .cfBlobJson = kj::mv(cfBlobJson),\n    .parentSpan = traceContext.getInternalSpanParent(),\n    .featureFlagsForFl = kj::none,\n  };\n  if (enableCompatFlags) {\n    metadata.featureFlagsForFl =\n        mapCopyString(context.getWorker().getIsolate().getFeatureFlagsForFl());\n  }\n  auto httpClient =\n      cacheName.map([&](kj::String& n) {\n    return cacheClient->getNamespace(n, kj::mv(metadata));\n  }).orDefault([&]() { return cacheClient->getDefault(kj::mv(metadata)); });\n  return httpClient.attach(kj::mv(cacheClient));\n}\n\n// =======================================================================================\n// CacheStorage\n\nCacheStorage::CacheStorage(jsg::Lock& js): default_(js.alloc<Cache>(kj::none)) {}\n\njsg::Promise<jsg::Ref<Cache>> CacheStorage::open(jsg::Lock& js, kj::String cacheName) {\n  // Set some reasonable limit to prevent scripts from blowing up our control header size.\n  static constexpr auto MAX_CACHE_NAME_LENGTH = 1024;\n  JSG_REQUIRE(cacheName.size() < MAX_CACHE_NAME_LENGTH, TypeError,\n      \"Cache name is too long.\");  // Mah spoon is toooo big.\n\n  // TODO(someday): Implement Cache API in preview.\n\n  // It is possible here that open() will be called in the global scope in fiddle\n  // mode in which case the warning will not be emitted. But that's OK? The warning\n  // is not critical by any stretch.\n  KJ_IF_SOME(context, IoContext::tryCurrent()) {\n    if (context.isFiddle()) {\n      context.logWarningOnce(CACHE_API_PREVIEW_WARNING);\n    }\n  }\n\n  return js.resolvedPromise(js.alloc<Cache>(kj::mv(cacheName)));\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/cache.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"http.h\"\n\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api {\n\n// =======================================================================================\n// Cache\n\nstruct CacheQueryOptions {\n  // By default, Cache.match() and Cache.delete() will return undefined/false if passed a non-GET\n  // request. Setting `ignoreMethod` to true disables this behavior; Cache.match() and\n  // Cache.delete() will treat any request as a GET request.\n  jsg::Optional<bool> ignoreMethod;\n\n  // Our cache does not support matching without query parameters at match time. Users can still\n  // remove query parameters before put()ing the Request/Response pair, if they wish.\n  jsg::WontImplement ignoreSearch;\n\n  // Historically, Cloudflare has not supported the Vary header because it's easy to blow up your\n  // cache keys. Customers can now implement this with workers by modifying cache keys as they see\n  // fit based on any arbitrary parameter (User-Agent, Content-Encoding, etc.).\n  jsg::WontImplement ignoreVary;\n\n  // Only used in CacheStorage::match(), which we won't implement.\n  jsg::WontImplement cacheName;\n\n  JSG_STRUCT(ignoreMethod, ignoreSearch, ignoreVary, cacheName);\n};\n\nclass Cache: public jsg::Object {\n public:\n  explicit Cache(kj::Maybe<kj::String> cacheName);\n\n  jsg::Unimplemented add(Request::Info request);\n  jsg::Unimplemented addAll(kj::Array<Request::Info> requests);\n\n  jsg::Promise<jsg::Optional<jsg::Ref<Response>>> match(jsg::Lock& js,\n      Request::Info request,\n      jsg::Optional<CacheQueryOptions> options,\n      CompatibilityFlags::Reader flags);\n\n  jsg::Promise<void> put(jsg::Lock& js,\n      Request::Info request,\n      jsg::Ref<Response> response,\n      CompatibilityFlags::Reader flags);\n\n  jsg::Promise<bool> delete_(jsg::Lock& js,\n      Request::Info request,\n      jsg::Optional<CacheQueryOptions> options,\n      CompatibilityFlags::Reader flags);\n\n  // Our cache does not support one-to-many matching, so this is not possible to implement.\n  jsg::WontImplement matchAll(jsg::Optional<Request::Info>, jsg::Optional<CacheQueryOptions>) {\n    return {};\n  }\n\n  // Our cache does not support cache item enumeration, so this is not possible to implement.\n  jsg::WontImplement keys(jsg::Optional<Request::Info>, jsg::Optional<CacheQueryOptions>) {\n    return {};\n  }\n\n  JSG_RESOURCE_TYPE(Cache) {\n    JSG_METHOD(add);\n    JSG_METHOD(addAll);\n    JSG_METHOD_NAMED(delete, delete_);\n    JSG_METHOD(match);\n    JSG_METHOD(put);\n    JSG_METHOD(matchAll);\n    JSG_METHOD(keys);\n\n    JSG_TS_OVERRIDE({\n      delete(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<boolean>;\n      match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise<Response | undefined>;\n      put(request: RequestInfo | URL, response: Response): Promise<void>;\n    });\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"cacheName\", cacheName);\n  }\n\n private:\n  kj::Maybe<kj::String> cacheName;\n\n  kj::Own<kj::HttpClient> getHttpClient(IoContext& context,\n      kj::Maybe<kj::String> cfBlobJson,\n      TraceContext& traceContext,\n      bool enableCompatFlags);\n};\n\n// =======================================================================================\n// CacheStorage\n\nclass CacheStorage: public jsg::Object {\n public:\n  CacheStorage(jsg::Lock& js);\n\n  jsg::Promise<jsg::Ref<Cache>> open(jsg::Lock& js, kj::String cacheName);\n\n  jsg::Ref<Cache> getDefault() {\n    return default_.addRef();\n  }\n\n  // Our cache does not support namespace enumeration, so none of these are possible to implement.\n\n  jsg::WontImplement match(Request::Info, jsg::Optional<CacheQueryOptions>) {\n    return {};\n  }\n  jsg::WontImplement has(kj::String) {\n    return {};\n  }\n  jsg::WontImplement delete_(kj::String) {\n    return {};\n  }\n  jsg::WontImplement keys() {\n    return {};\n  }\n\n  JSG_RESOURCE_TYPE(CacheStorage) {\n    JSG_METHOD(open);\n    JSG_METHOD_NAMED(delete, delete_);\n    JSG_METHOD(match);\n    JSG_METHOD(has);\n    JSG_METHOD(keys);\n\n    JSG_READONLY_INSTANCE_PROPERTY(default, getDefault);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"default\", default_);\n  }\n\n private:\n  jsg::Ref<Cache> default_;\n};\n\n#define EW_CACHE_ISOLATE_TYPES api::CacheStorage, api::Cache, api::CacheQueryOptions\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/capnp.c++",
    "content": "#include \"capnp.h\"\n\nnamespace workerd::api {\n\n// =======================================================================================\n// Some code here is derived from node-capnp.\n// Copyright (c) 2014-2021 Kenton Varda, Sandstorm Development Group, Inc., and contributors\n// Licensed under the MIT License\n\n#define STACK_STR(js, name, handle, sizeHint)                                                      \\\n  /* Read a JavaScript string, allocating it on the stack if it's small enough. */                 \\\n  char name##_buf[sizeHint]{};                                                                     \\\n  kj::Array<char> name##_heap;                                                                     \\\n  kj::StringPtr name;                                                                              \\\n  {                                                                                                \\\n    v8::Local<v8::String> v8str = jsg::check(handle->ToString(js.v8Context()));                    \\\n    char* ptr;                                                                                     \\\n    size_t len = v8str->Utf8LengthV2(js.v8Isolate);                                                \\\n    if (len < sizeHint) {                                                                          \\\n      ptr = name##_buf;                                                                            \\\n    } else {                                                                                       \\\n      name##_heap = kj::heapArray<char>(len + 1);                                                  \\\n      ptr = name##_heap.begin();                                                                   \\\n    }                                                                                              \\\n    v8str->WriteUtf8V2(js.v8Isolate, ptr, len);                                                    \\\n    name = kj::StringPtr(ptr, len);                                                                \\\n  }\n\n// Convert JS values to/from capnp.\nstruct JsCapnpConverter {\n  kj::Maybe<CapnpTypeWrapperBase&> wrapper;\n\n  capnp::Orphan<capnp::DynamicValue> orphanFromJs(jsg::Lock& js,\n      kj::Maybe<capnp::StructSchema::Field> field,\n      capnp::Orphanage orphanage,\n      capnp::Type type,\n      v8::Local<v8::Value> jsValue) {\n    return js.withinHandleScope([&]() -> capnp::Orphan<capnp::DynamicValue> {\n      switch (type.which()) {\n        case capnp::schema::Type::VOID:\n          if (jsValue->IsNull()) {\n            return capnp::VOID;\n          }\n          break;\n        case capnp::schema::Type::BOOL:\n          return jsValue->BooleanValue(js.v8Isolate);\n        case capnp::schema::Type::INT8:\n          return jsg::check(jsValue->Int32Value(js.v8Context()));\n        case capnp::schema::Type::INT16:\n          return jsg::check(jsValue->Int32Value(js.v8Context()));\n        case capnp::schema::Type::INT32:\n          return jsg::check(jsValue->Int32Value(js.v8Context()));\n        case capnp::schema::Type::UINT8:\n          return jsg::check(jsValue->Uint32Value(js.v8Context()));\n        case capnp::schema::Type::UINT16:\n          return jsg::check(jsValue->Uint32Value(js.v8Context()));\n        case capnp::schema::Type::UINT32:\n          return jsg::check(jsValue->Uint32Value(js.v8Context()));\n        case capnp::schema::Type::FLOAT32:\n          return jsg::check(jsValue->NumberValue(js.v8Context()));\n        case capnp::schema::Type::FLOAT64:\n          return jsg::check(jsValue->NumberValue(js.v8Context()));\n        case capnp::schema::Type::UINT64: {\n          if (jsValue->IsNumber()) {\n            // js->ToBigInt() doesn't work with Numbers. V8 bug?\n            double value = jsg::check(jsValue->NumberValue(js.v8Context()));\n\n            // Casting a double to an integer when the double is out-of-range is UB. `0x1p64` is a\n            // C++17 hex double literal with value 2^64. We cannot use UINT64_MAX here because it is\n            // not exactly representable as a double, so casting it to double will actually change\n            // the value (rounding it up to 2^64). The compiler will rightly produce a warning about\n            // this.\n            if (value >= 0 && value < 0x1p64 && value == static_cast<uint64_t>(value)) {\n              return static_cast<uint64_t>(value);\n            }\n          } else {\n            // Let V8 decide what types can be implicitly cast to BigInt.\n            auto bi = jsg::check(jsValue->ToBigInt(js.v8Context()));\n            bool lossless;\n            uint64_t value = bi->Uint64Value(&lossless);\n            if (lossless) {\n              return value;\n            }\n          }\n          break;\n        }\n        case capnp::schema::Type::INT64: {\n          // (See comments above for UInt64 case.)\n          if (jsValue->IsNumber()) {\n            double value = jsg::check(jsValue->NumberValue(js.v8Context()));\n            if (value >= -0x1p63 && value < 0x1p63 && value == static_cast<uint64_t>(value)) {\n              return static_cast<uint64_t>(value);\n            }\n          } else {\n            auto bi = jsg::check(jsValue->ToBigInt(js.v8Context()));\n            bool lossless;\n            int64_t value = bi->Int64Value(&lossless);\n            if (lossless) {\n              return value;\n            }\n          }\n          break;\n        }\n        case capnp::schema::Type::TEXT: {\n          auto str = jsg::check(jsValue->ToString(js.v8Context()));\n          capnp::Orphan<capnp::Text> orphan =\n              orphanage.newOrphan<capnp::Text>(str->Utf8LengthV2(js.v8Isolate));\n          str->WriteUtf8V2(js.v8Isolate, orphan.get().begin(), orphan.get().size());\n          return kj::mv(orphan);\n        }\n        case capnp::schema::Type::DATA:\n          if (jsValue->IsArrayBuffer()) {\n            auto backing = jsValue.As<v8::ArrayBuffer>()->GetBackingStore();\n            return orphanage.newOrphanCopy(capnp::Data::Reader(kj::arrayPtr(\n                reinterpret_cast<const kj::byte*>(backing->Data()), backing->ByteLength())));\n          } else if (jsValue->IsArrayBufferView()) {\n            auto arrayBufferView = jsValue.As<v8::ArrayBufferView>();\n            auto backing = arrayBufferView->Buffer()->GetBackingStore();\n            kj::ArrayPtr buffer(static_cast<kj::byte*>(backing->Data()), backing->ByteLength());\n            auto sliceStart = arrayBufferView->ByteOffset();\n            auto sliceEnd = sliceStart + arrayBufferView->ByteLength();\n            KJ_ASSERT(buffer.size() >= sliceEnd);\n            return orphanage.newOrphanCopy(capnp::Data::Reader(buffer.slice(sliceStart, sliceEnd)));\n          }\n          break;\n        case capnp::schema::Type::LIST: {\n          if (jsValue->IsArray()) {\n            auto jsArray = jsValue.As<v8::Array>();\n            auto schema = type.asList();\n            auto elementType = schema.getElementType();\n            auto orphan = orphanage.newOrphan(schema, jsArray->Length());\n            auto builder = orphan.get();\n            if (elementType.isStruct()) {\n              // Struct lists can't adopt.\n              bool error = false;\n              for (uint i: kj::indices(builder)) {\n                auto element = jsg::check(jsArray->Get(js.v8Context(), i));\n                if (element->IsObject()) {\n                  structFromJs(js, builder[i].as<capnp::DynamicStruct>(), element.As<v8::Object>());\n                } else {\n                  error = true;\n                  break;\n                }\n              }\n              if (error) break;\n            } else {\n              bool isPointerList =\n                  builder.as<capnp::AnyList>().getElementSize() == capnp::ElementSize::POINTER;\n              for (uint i: kj::indices(builder)) {\n                auto jsElement = jsg::check(jsArray->Get(js.v8Context(), i));\n                if (isPointerList && (jsElement->IsNull() || jsElement->IsUndefined())) {\n                  // Skip null element.\n                } else {\n                  builder.adopt(i, orphanFromJs(js, field, orphanage, elementType, jsElement));\n                }\n              }\n            }\n            return kj::mv(orphan);\n          }\n          break;\n        }\n        case capnp::schema::Type::ENUM: {\n          auto schema = type.asEnum();\n          if (jsValue->IsUint32()) {\n            return capnp::DynamicEnum(schema, jsg::check(jsValue->Uint32Value(js.v8Context())));\n          }\n\n          STACK_STR(js, name, jsValue, 32);\n          KJ_IF_SOME(enumerant, schema.findEnumerantByName(name)) {\n            return capnp::DynamicEnum(enumerant);\n          }\n          break;\n        }\n        case capnp::schema::Type::STRUCT: {\n          if (jsValue->IsObject()) {\n            auto schema = type.asStruct();\n            auto orphan = orphanage.newOrphan(schema);\n            structFromJs(js, orphan.get(), jsValue.As<v8::Object>());\n            return kj::mv(orphan);\n          }\n          break;\n        }\n        case capnp::schema::Type::INTERFACE: {\n          KJ_IF_SOME(wrapper, this->wrapper) {\n            auto schema = type.asInterface();\n            if (jsValue->IsNull()) {\n              auto cap =\n                  capnp::Capability::Client(nullptr).castAs<capnp::DynamicCapability>(schema);\n              return orphanage.newOrphanCopy(cap);\n            } else KJ_IF_SOME(cap, wrapper.tryUnwrapCap(js, js.v8Context(), jsValue)) {\n              // We were given a capability type obtained from elsewhere.\n              if (cap.getSchema().extends(schema)) {\n                return orphanage.newOrphanCopy(cap);\n              }\n            } else if (jsValue->IsObject()) {\n              // We were given a raw object, which we will treat as a server implementation.\n              auto cap = IoContext::current().getLocalCapSet().add(\n                  kj::heap<CapnpServer>(js, schema, js.v8Ref(jsValue.As<v8::Object>()), wrapper));\n              return orphanage.newOrphanCopy(kj::mv(cap));\n            }\n          }\n          break;\n        }\n        case capnp::schema::Type::ANY_POINTER:\n          // TODO(someday): Support this somehow?\n          break;\n      }\n\n      KJ_IF_SOME(ff, field) {\n        JSG_FAIL_REQUIRE(\n            TypeError, \"Incorrect type for Cap'n Proto field: \", ff.getProto().getName());\n      } else {\n        JSG_FAIL_REQUIRE(TypeError, \"Incorrect type for Cap'n Proto value.\");\n      }\n    });\n  }\n\n  void fieldFromJs(jsg::Lock& js,\n      capnp::DynamicStruct::Builder builder,\n      capnp::StructSchema::Field field,\n      v8::Local<v8::Value> jsValue) {\n    if (jsValue->IsUndefined()) {\n      // Ignore.\n      return;\n    }\n    auto proto = field.getProto();\n    switch (proto.which()) {\n      case capnp::schema::Field::SLOT: {\n        builder.adopt(field,\n            orphanFromJs(js, field, capnp::Orphanage::getForMessageContaining(builder),\n                field.getType(), jsValue));\n        return;\n      }\n\n      case capnp::schema::Field::GROUP:\n        if (jsValue->IsObject()) {\n          structFromJs(\n              js, builder.init(field).as<capnp::DynamicStruct>(), jsValue.As<v8::Object>());\n        } else {\n          JSG_FAIL_REQUIRE(TypeError, \"Incorrect type for Cap'n Proto field: \", proto.getName());\n        }\n        return;\n    }\n\n    KJ_FAIL_ASSERT(\"Unimplemented field type (not slot or group).\");\n  }\n\n  void structFromJs(\n      jsg::Lock& js, capnp::DynamicStruct::Builder builder, v8::Local<v8::Object> jsValue) {\n    js.withinHandleScope([&] {\n      auto schema = builder.getSchema();\n      v8::Local<v8::Array> fieldNames = jsg::check(jsValue->GetOwnPropertyNames(js.v8Context()));\n      for (uint i: kj::zeroTo(fieldNames->Length())) {\n        auto jsName = jsg::check(fieldNames->Get(js.v8Context(), i));\n        STACK_STR(js, fieldName, jsName, 32);\n        KJ_IF_SOME(field, schema.findFieldByName(fieldName)) {\n          fieldFromJs(js, builder, field, jsg::check(jsValue->Get(js.v8Context(), jsName)));\n        } else {\n          JSG_FAIL_REQUIRE(TypeError, \"No such field in Cap'n Proto struct: \", fieldName);\n        }\n      }\n    });\n  }\n\n  void rpcResultsFromJs(jsg::Lock& js,\n      capnp::CallContext<capnp::DynamicStruct, capnp::DynamicStruct>& rpcContext,\n      v8::Local<v8::Value> jsValue) {\n    if (jsValue->IsObject()) {\n      structFromJs(js, rpcContext.getResults(), jsValue.As<v8::Object>());\n    } else if (jsValue->IsUndefined()) {\n      // assume default return\n    } else {\n      JSG_FAIL_REQUIRE(TypeError, \"RPC method server implementation returned a non-object.\");\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n  // handle pipelines (as in promise pipelining)\n  //\n  // In C++, a capnp::RemotePromise<T> represents a combination of a Promise<T::Reader> and a\n  // T::Pipeline. The latter is a special object that allows immediately initiating pipeline calls\n  // on any capabilities that the response is expected to contain.\n  //\n  // In JavaScript, we will accomplish something similar by returning a Promise that has been\n  // extended with properties representing the pipelined capabilities.\n\n  struct PipelinedCap;\n  using PipelinedCapMap = kj::HashMap<capnp::StructSchema::Field, PipelinedCap>;\n\n  // We return a set of pipelined capabilities on the Promise returned by an RPC call. Later on,\n  // that Promise resolves to a response object likely containing the same capabilities again.\n  // We don't want the application to have to call `.close()` on both the pipelined version and\n  // the final version in order to actually close a capability. So, we need to make sure the final\n  // response uses the same CapnpCapability objects that were returned as part of the pipeline.\n  // To facilitate this, when we extend the Promise with pipeline properties, we also return a\n  // PipelineCapMap which contains all the objects that need to be injected into the final\n  // response.\n  struct PipelinedCap {\n    kj::OneOf<jsg::Ref<CapnpCapability>, PipelinedCapMap> content;\n  };\n\n  v8::Local<v8::Object> pipelineStructFieldToJs(jsg::Lock& js,\n      capnp::DynamicStruct::Pipeline& pipeline,\n      capnp::StructSchema::Field field,\n      PipelinedCapMap& capMap) {\n    v8::Local<v8::Object> fieldValue = v8::Object::New(js.v8Isolate);\n    auto subMap =\n        pipelineToJs(js, pipeline.get(field).releaseAs<capnp::DynamicStruct>(), fieldValue);\n    if (subMap.size() > 0) {\n      // Some capabilities were found in this sub-message, so add it to the map.\n      capMap.insert(field, PipelinedCap{kj::mv(subMap)});\n    }\n    return fieldValue;\n  }\n\n  // This function is only useful in the context of RPC, where this->wrapper will always be\n  // available.\n  PipelinedCapMap pipelineToJs(\n      jsg::Lock& js, capnp::DynamicStruct::Pipeline&& pipeline, v8::Local<v8::Object> jsValue) {\n    CapnpTypeWrapperBase& wrapper = KJ_REQUIRE_NONNULL(this->wrapper);\n\n    return js.withinHandleScope([&]() -> PipelinedCapMap {\n      capnp::StructSchema schema = pipeline.getSchema();\n\n      PipelinedCapMap capMap;\n\n      for (capnp::StructSchema::Field field: schema.getNonUnionFields()) {\n        auto proto = field.getProto();\n        v8::Local<v8::Value> fieldValue;\n\n        switch (proto.which()) {\n          case capnp::schema::Field::SLOT: {\n            auto type = field.getType();\n            switch (type.which()) {\n              case capnp::schema::Type::STRUCT:\n                fieldValue = pipelineStructFieldToJs(js, pipeline, field, capMap);\n                break;\n              case capnp::schema::Type::ANY_POINTER:\n                if (type.whichAnyPointerKind() !=\n                    capnp::schema::Type::AnyPointer::Unconstrained::CAPABILITY) {\n                  continue;\n                }\n                [[fallthrough]];\n              case capnp::schema::Type::INTERFACE: {\n                jsg::Ref<CapnpCapability> ref = nullptr;\n                fieldValue = wrapper.wrapCap(js, js.v8Context(),\n                    pipeline.get(field).releaseAs<capnp::DynamicCapability>(), &ref);\n                capMap.insert(field, PipelinedCap{kj::mv(ref)});\n                break;\n              }\n              default:\n                continue;\n            }\n            break;\n          }\n\n          case capnp::schema::Field::GROUP:\n            fieldValue = pipelineStructFieldToJs(js, pipeline, field, capMap);\n            break;\n\n          default:\n            continue;\n        }\n\n        KJ_ASSERT(!fieldValue.IsEmpty());\n        jsg::check(jsValue->Set(\n            js.v8Context(), jsg::v8StrIntern(js.v8Isolate, proto.getName()), fieldValue));\n      }\n\n      return capMap;\n    });\n  }\n\n  // ---------------------------------------------------------------------------\n  // convert capnp values to JS\n\n  v8::Local<v8::Value> valueToJs(jsg::Lock& js,\n      capnp::DynamicValue::Reader value,\n      capnp::Type type,\n      kj::Maybe<PipelinedCap&> pipelinedCap) {\n    // TODO(later): support deserialization outside of RPC, i.e., not requiring a wrapper.\n    CapnpTypeWrapperBase& wrapper = KJ_REQUIRE_NONNULL(this->wrapper);\n\n    return js.withinHandleScope([&]() -> v8::Local<v8::Value> {\n      switch (value.getType()) {\n        case capnp::DynamicValue::UNKNOWN:\n          return js.undefined();\n        case capnp::DynamicValue::VOID:\n          return js.null();\n        case capnp::DynamicValue::BOOL:\n          return js.boolean(value.as<bool>());\n        case capnp::DynamicValue::INT: {\n          if (type.which() == capnp::schema::Type::INT64 ||\n              type.which() == capnp::schema::Type::UINT64) {\n            return v8::BigInt::New(js.v8Isolate, value.as<int64_t>());\n          } else {\n            return v8::Integer::New(js.v8Isolate, value.as<int32_t>());\n          }\n        }\n        case capnp::DynamicValue::UINT: {\n          if (type.which() == capnp::schema::Type::INT64 ||\n              type.which() == capnp::schema::Type::UINT64) {\n            return v8::BigInt::NewFromUnsigned(js.v8Isolate, value.as<uint64_t>());\n          } else {\n            return v8::Integer::NewFromUnsigned(js.v8Isolate, value.as<uint32_t>());\n          }\n        }\n        case capnp::DynamicValue::FLOAT:\n          return v8::Number::New(js.v8Isolate, value.as<double>());\n        case capnp::DynamicValue::TEXT:\n          return jsg::v8Str(js.v8Isolate, value.as<capnp::Text>());\n        case capnp::DynamicValue::DATA: {\n          capnp::Data::Reader data = value.as<capnp::Data>();\n\n          // In theory we could avoid a copy if we kept the response message in memory, but we\n          // probably don't want to do that.\n          auto result = jsg::check(v8::ArrayBuffer::MaybeNew(js.v8Isolate, data.size()));\n          memcpy(result->GetBackingStore()->Data(), data.begin(), data.size());\n\n          return result;\n        }\n        case capnp::DynamicValue::LIST: {\n          capnp::DynamicList::Reader list = value.as<capnp::DynamicList>();\n          auto elementType = list.getSchema().getElementType();\n          auto indices = kj::indices(list);\n          KJ_STACK_ARRAY(v8::Local<v8::Value>, items, indices.size(), 100, 100);\n          for (uint i: indices) {\n            items[i] = valueToJs(js, list[i], elementType, kj::none);\n          }\n          return v8::Array::New(js.v8Isolate, items.begin(), items.size());\n        }\n        case capnp::DynamicValue::ENUM: {\n          auto enumValue = value.as<capnp::DynamicEnum>();\n          KJ_IF_SOME(enumerant, enumValue.getEnumerant()) {\n            return jsg::v8StrIntern(js.v8Isolate, enumerant.getProto().getName());\n          } else {\n            return v8::Integer::NewFromUnsigned(js.v8Isolate, enumValue.getRaw());\n          }\n        }\n        case capnp::DynamicValue::STRUCT: {\n          auto capMap = pipelinedCap.map([](PipelinedCap& pc) -> PipelinedCapMap& {\n            // If we had a PipelinedCap for a struct field, it must be a PipelinedCapMap.\n            return pc.content.get<PipelinedCapMap>();\n          });\n\n          capnp::DynamicStruct::Reader reader = value.as<capnp::DynamicStruct>();\n          auto object = v8::Object::New(js.v8Isolate);\n          KJ_IF_SOME(field, reader.which()) {\n            fieldToJs(js, object, reader, field, capMap);\n          }\n\n          for (auto field: reader.getSchema().getNonUnionFields()) {\n            if (reader.has(field)) {\n              fieldToJs(js, object, reader, field, capMap);\n            }\n          }\n          return object;\n        }\n        case capnp::DynamicValue::CAPABILITY:\n          KJ_IF_SOME(p, pipelinedCap) {\n            // Use the same CapnpCapability object that we returned earlier for promise pipelining.\n            // Note: We know the JS wrapper exists because CapnpCapability objects are always created\n            //   by CapnpTypeWrapper::wrap() and immediately have a wrapper added.\n            return KJ_ASSERT_NONNULL(p.content.get<jsg::Ref<CapnpCapability>>().tryGetHandle(js));\n          } else {\n            return wrapper.wrapCap(js, js.v8Context(), value.as<capnp::DynamicCapability>());\n          }\n        case capnp::DynamicValue::ANY_POINTER:\n          return js.null();\n      }\n\n      KJ_FAIL_ASSERT(\"Unimplemented DynamicValue type.\");\n    });\n  }\n\n  void fieldToJs(jsg::Lock& js,\n      v8::Local<v8::Object> object,\n      capnp::DynamicStruct::Reader reader,\n      capnp::StructSchema::Field field,\n      kj::Maybe<PipelinedCapMap&> capMap) {\n    js.withinHandleScope([&] {\n      kj::Maybe<PipelinedCap&> pipelinedCap;\n      KJ_IF_SOME(m, capMap) {\n        pipelinedCap = m.find(field);\n      }\n\n      auto proto = field.getProto();\n      v8::Local<v8::Value> fieldValue;\n      switch (proto.which()) {\n        case capnp::schema::Field::SLOT:\n          fieldValue = valueToJs(js, reader.get(field), field.getType(), pipelinedCap);\n          break;\n        case capnp::schema::Field::GROUP:\n          fieldValue = valueToJs(js, reader.get(field), field.getType(), pipelinedCap);\n          break;\n      }\n\n      JSG_REQUIRE(\n          !fieldValue.IsEmpty(), TypeError, \"Unimplemented field type (not slot or group).\");\n\n      jsg::check(\n          object->Set(js.v8Context(), jsg::v8StrIntern(js.v8Isolate, proto.getName()), fieldValue));\n    });\n  }\n};\n\n// =======================================================================================\n\nvoid fillCapnpFieldFromJs(jsg::Lock& js,\n    capnp::DynamicStruct::Builder builder,\n    capnp::StructSchema::Field field,\n    v8::Local<v8::Value> jsValue) {\n  JsCapnpConverter converter;\n  converter.fieldFromJs(js, builder, field, jsValue);\n}\n\ncapnp::Orphan<capnp::DynamicValue> capnpValueFromJs(\n    jsg::Lock& js, capnp::Orphanage orphanage, capnp::Type type, v8::Local<v8::Value> jsValue) {\n  JsCapnpConverter converter;\n  return converter.orphanFromJs(js, kj::none, orphanage, type, jsValue);\n}\n\n// =======================================================================================\n\nCapnpServer::CapnpServer(jsg::Lock& js,\n    capnp::InterfaceSchema schema,\n    jsg::V8Ref<v8::Object> objectParam,\n    CapnpTypeWrapperBase& wrapper)\n    : capnp::DynamicCapability::Server(schema),\n      ioContext(IoContext::current().getWeakRef()),\n      object(kj::mv(objectParam)),\n      closeMethod(getCloseMethod(js)),\n      wrapper(wrapper) {}\n\nkj::Maybe<jsg::V8Ref<v8::Function>> CapnpServer::getCloseMethod(jsg::Lock& js) {\n  auto handle = object.getHandle(js);\n  auto methodHandle =\n      jsg::check(handle->Get(js.v8Context(), jsg::v8StrIntern(js.v8Isolate, \"close\")));\n  if (methodHandle->IsFunction()) {\n    return js.v8Ref(methodHandle.As<v8::Function>());\n  } else {\n    return kj::none;\n  }\n}\n\nCapnpServer::~CapnpServer() noexcept(false) {\n  KJ_IF_SOME(c, closeMethod) {\n    ioContext->runIfAlive([&](IoContext& rc) {\n      rc.addTask(\n          rc.run([object = kj::mv(object), closeMethod = kj::mv(c)](Worker::Lock& lock) mutable {\n        auto handle = object.getHandle(lock);\n        auto methodHandle = closeMethod.getHandle(lock);\n        if (methodHandle->IsFunction()) {\n          jsg::check(methodHandle.As<v8::Function>()->Call(lock.getContext(), handle, 0, nullptr));\n        }\n      }));\n    });\n  }\n}\n\nkj::Promise<void> CapnpServer::call(capnp::InterfaceSchema::Method method,\n    capnp::CallContext<capnp::DynamicStruct, capnp::DynamicStruct> rpcContext) {\n  kj::Promise<void> result = nullptr;\n\n  bool live = ioContext->runIfAlive([&](IoContext& rc) {\n    result =\n        rc.run([this, method, rpcContext, &rc](Worker::Lock& lock) mutable -> kj::Promise<void> {\n      jsg::Lock& js = lock;\n      auto handle = object.getHandle(js);\n      auto methodName = method.getProto().getName();\n      auto methodHandle =\n          jsg::check(handle->Get(lock.getContext(), jsg::v8StrIntern(js.v8Isolate, methodName)));\n\n      if (!methodHandle->IsFunction()) {\n        KJ_UNIMPLEMENTED(kj::str(\"jsg.Error: RPC method not implemented: \", methodName));\n      }\n\n      JsCapnpConverter converter{wrapper};\n      auto params = rpcContext.getParams();\n      auto jsParams = converter.valueToJs(js, params, params.getSchema(), kj::none);\n      rpcContext.releaseParams();\n\n      auto result = jsg::check(\n          methodHandle.As<v8::Function>()->Call(lock.getContext(), handle, 1, &jsParams));\n      KJ_IF_SOME(promise, wrapper.tryUnwrapPromise(lock, lock.getContext(), result)) {\n        return rc.awaitJs(js,\n            promise.then(\n                js, rc.addFunctor([this, rpcContext](jsg::Lock& js, jsg::Value result) mutable {\n          JsCapnpConverter converter{wrapper};\n          converter.rpcResultsFromJs(js, rpcContext, result.getHandle(js));\n        })));\n\n      } else {\n        converter.rpcResultsFromJs(js, rpcContext, result);\n        return kj::READY_NOW;\n      }\n    });\n  });\n\n  if (live) {\n    return result;\n  } else {\n    return KJ_EXCEPTION(DISCONNECTED, \"jsg.Error: Called to event context that is no longer live.\");\n  }\n}\n\n// =======================================================================================\n\nCapnpCapability::CapnpCapability(capnp::DynamicCapability::Client client)\n    : schema(client.getSchema()),\n      client(IoContext::current().addObject(kj::heap(kj::mv(client)))) {}\n\nCapnpCapability::~CapnpCapability() noexcept(false) {\n  KJ_IF_SOME(c, client) {\n    // The client was not explicitly close()ed and instead waited for GC. There are two problems\n    // with this:\n    // 1. It's rude to force the remote peer to wait until the lazy garbage collector gets around\n    //    to collecting the object before we let the peer know that it can clean up its end. Our\n    //    GC is sociopathic, it decides when to collect based purely on its own memory pressure\n    //    and has no idea what memory pressure the peer might be feeling, so likely won't make\n    //    empathetic choices about when to collect.\n    // 2. We generally do not want to allow an application to observe its own garbage collection\n    //    behavior, as this may reveal side channels. The capability could be a loopback into\n    //    this very isolate, in which case closing it now would immediately call back into the\n    //    server's close() method, notifying the application of its own GC. We need to prevent that.\n\n    // To solve #2, we defer destruction of the object until the end of the IoContext.\n    kj::mv(c).deferGcToContext();\n\n    // In preview, let's try to warn the developer about the problem.\n    //\n    // TODO(cleanup): Instead of logging this warning at GC time, it would be better if we logged\n    //   it at the time that the client is destroyed, i.e. when the IoContext is torn down,\n    //   which is usually sooner (and more deterministic). But logging a warning during\n    //   IoContext tear-down is problematic since logWarningOnce() is a method on\n    //   IoContext...\n    KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n      ioContext.logWarningOnce(\n          kj::str(\"A Cap'n Proto capability of type \", schema.getShortDisplayName(),\n              \" was not closed properly. You must call close() on all capabilities in order to \"\n              \"let the other side know that you are no longer using them. You cannot rely on \"\n              \"the garbage collector for this because it may take arbitrarily long before actually \"\n              \"collecting unreachable objects.\"));\n    }\n  }\n}\n\nv8::Local<v8::Value> CapnpCapability::call(jsg::Lock& js,\n    capnp::InterfaceSchema::Method method,\n    v8::Local<v8::Value> params,\n    CapnpTypeWrapperBase& wrapper) {\n  auto& ioContext = IoContext::current();\n  auto req = getClient(js, wrapper).newRequest(method);\n  JsCapnpConverter converter{wrapper};\n  if (params->IsObject()) {\n    converter.structFromJs(js, req, params.As<v8::Object>());\n  } else if (params->IsUndefined()) {\n    // leave params all-default\n  } else {\n    JSG_FAIL_REQUIRE(TypeError, \"Argument to a capnp RPC call must be an object.\");\n  }\n  if (method.isStreaming()) {\n    // Note: We know the JS wrapper exists for JSG_THIS because CapnpCapability objects are always\n    //   created by CapnpTypeWrapper::wrap() and immediately have a wrapper added.\n    return wrapper.wrapPromise(js, js.v8Context(), KJ_ASSERT_NONNULL(JSG_THIS.tryGetHandle(js)),\n        ioContext.awaitIo(\n            js, req.sendStreaming(), [](jsg::Lock& js) { return js.v8Ref(js.v8Undefined()); }));\n  } else {\n    // The RPC promise is actually both a promise and a pipeline.\n    auto rpcPromise = req.send();\n\n    auto pipelinedCapHolder = kj::heap<JsCapnpConverter::PipelinedCap>();\n    auto& pipelinedCapRef = *pipelinedCapHolder;\n\n    // We'll consume the promise itself to handle converting the response.\n    // Note: We know the JS wrapper exists for JSG_THIS because CapnpCapability objects are always\n    //   created by CapnpTypeWrapper::wrap() and immediately have a wrapper added.\n    auto responsePromise =\n        kj::Promise<capnp::Response<capnp::DynamicStruct>>(kj::mv(rpcPromise))\n            .catch_([](kj::Exception&& ex) -> kj::Promise<capnp::Response<capnp::DynamicStruct>> {\n      auto errorType = jsg::tunneledErrorType(ex.getDescription());\n      if (!errorType.isJsgError) {\n        // Wrap any non-JS exceptions as JS errors\n        auto newDescription =\n            kj::str(\"remote.\" JSG_EXCEPTION(Error) \": capnp RPC exception: \"_kj, errorType.message);\n        ex.setDescription(kj::mv(newDescription));\n      }\n      return kj::mv(ex);\n    });\n    auto result =\n        wrapper.wrapPromise(js, js.v8Context(), KJ_ASSERT_NONNULL(JSG_THIS.tryGetHandle(js)),\n            ioContext.awaitIo(js, kj::mv(responsePromise),\n                [&wrapper, pipelinedCapHolder = kj::mv(pipelinedCapHolder)](\n                    jsg::Lock& js, capnp::Response<capnp::DynamicStruct> resp) mutable {\n      JsCapnpConverter converter{wrapper};\n      return js.v8Ref(converter.valueToJs(js, resp, resp.getSchema(), *pipelinedCapHolder));\n    }));\n\n    // Now we take the pipeline part of `rpcPromise` and merge it into the V8 promise object, by\n    // adding fields representing the pipelined struct.\n    KJ_ASSERT(result->IsPromise());\n    pipelinedCapRef.content =\n        converter.pipelineToJs(js, kj::mv(rpcPromise), result.As<v8::Promise>());\n\n    return result;\n  }\n}\n\nvoid CapnpCapability::close() {\n  KJ_IF_SOME(c, client) {\n    // Verify we're in the correct IoContext. This will throw otherwise.\n    *c;\n  }\n  client = kj::none;\n}\n\njsg::Promise<kj::Maybe<jsg::V8Ref<v8::Object>>> CapnpCapability::unwrap(jsg::Lock& js) {\n  // We need to allocate a heap copy of the `Client` so that if this capability is closed while\n  // the promise is still outstanding, the client isn't destroyed, which would otherwise cause\n  // UAF in the getLocalServer() implementation.\n  auto capHolder = kj::heap(*JSG_REQUIRE_NONNULL(client, Error, \"Capability has been closed.\"));\n  auto& ioContext = IoContext::current();\n  auto promise = ioContext.getLocalCapSet().getLocalServer(*capHolder);\n\n  return ioContext.awaitIo(js, kj::mv(promise),\n      [capHolder = kj::mv(capHolder)](\n          jsg::Lock& js, kj::Maybe<capnp::DynamicCapability::Server&> server) {\n    return server.map([&](capnp::DynamicCapability::Server& s) {\n      return kj::downcast<CapnpServer>(s).object.addRef(js);\n    });\n  });\n}\n\ncapnp::DynamicCapability::Client CapnpCapability::getClient(\n    jsg::Lock&, CapnpTypeWrapperBase& wrapper) {\n  return *JSG_REQUIRE_NONNULL(client, Error, \"Capability has been closed.\");\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/capnp.h",
    "content": "#pragma once\n\n#include <workerd/io/io-context.h>\n#include <workerd/jsg/jsg.h>\n\n#include <capnp/dynamic.h>\n#include <kj/map.h>\n\nnamespace workerd::api {\n\ntemplate <typename TypeWrapper>\nclass CapnpTypeWrapper;\nclass CapnpCapability;\nclass CapnpTypeWrapperBase;\n\nvoid fillCapnpFieldFromJs(capnp::DynamicStruct::Builder builder,\n    capnp::StructSchema::Field field,\n    v8::Local<v8::Context> context,\n    v8::Local<v8::Value> jsValue);\n\ncapnp::Orphan<capnp::DynamicValue> capnpValueFromJs(\n    jsg::Lock& js, capnp::Orphanage orphanage, capnp::Type type, v8::Local<v8::Value> jsValue);\n\nclass CapnpServer final: public capnp::DynamicCapability::Server {\n public:\n  CapnpServer(jsg::Lock& js,\n      capnp::InterfaceSchema schema,\n      jsg::V8Ref<v8::Object> object,\n      CapnpTypeWrapperBase& wrapper);\n  ~CapnpServer() noexcept(false);\n\n  kj::Promise<void> call(capnp::InterfaceSchema::Method method,\n      capnp::CallContext<capnp::DynamicStruct, capnp::DynamicStruct> context) override;\n\n private:\n  kj::Own<IoContext::WeakRef> ioContext;\n  jsg::V8Ref<v8::Object> object;\n  kj::Maybe<jsg::V8Ref<v8::Function>> closeMethod;\n  CapnpTypeWrapperBase& wrapper;  // only valid if isolate is locked!\n\n  kj::Maybe<jsg::V8Ref<v8::Function>> getCloseMethod(jsg::Lock& js);\n\n  friend class CapnpCapability;\n};\n\nclass CapnpCapability: public jsg::Object {\n public:\n  CapnpCapability(capnp::DynamicCapability::Client client);\n  ~CapnpCapability() noexcept(false);\n\n  v8::Local<v8::Value> call(jsg::Lock& js,\n      capnp::InterfaceSchema::Method method,\n      v8::Local<v8::Value> params,\n      CapnpTypeWrapperBase& wrapper);\n\n  void close();\n  jsg::Promise<kj::Maybe<jsg::V8Ref<v8::Object>>> unwrap(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(CapnpCapability) {\n    JSG_METHOD(close);\n    JSG_METHOD(unwrap);\n  }\n\n  capnp::DynamicCapability::Client getClient(jsg::Lock& js, CapnpTypeWrapperBase& wrapper);\n\n private:\n  // Used for error messages.\n  capnp::InterfaceSchema schema;\n\n  // null if closed\n  kj::Maybe<IoOwn<capnp::DynamicCapability::Client>> client;\n\n  template <typename TypeWrapper>\n  friend class CapnpTypeWrapper;\n};\n\nclass CapnpTypeWrapperBase {\n public:\n  virtual v8::Local<v8::Object> wrapCap(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      capnp::DynamicCapability::Client value,\n      kj::Maybe<jsg::Ref<CapnpCapability>&> refToInitialize = kj::none) = 0;\n  virtual kj::Maybe<capnp::DynamicCapability::Client> tryUnwrapCap(\n      jsg::Lock& js, v8::Local<v8::Context> context, v8::Local<v8::Value> value) = 0;\n\n  virtual v8::Local<v8::Promise> wrapPromise(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      jsg::Promise<jsg::Value> value) = 0;\n  virtual kj::Maybe<jsg::Promise<jsg::Value>> tryUnwrapPromise(\n      jsg::Lock& js, v8::Local<v8::Context> context, v8::Local<v8::Value> value) = 0;\n};\n\ntemplate <typename TypeWrapper>\nclass CapnpTypeWrapper: private CapnpTypeWrapperBase {\n public:\n  static constexpr const char* getName(capnp::DynamicCapability::Client*) {\n    return \"Capability\";\n  }\n\n  v8::Local<v8::Function> wrap(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      capnp::Schema schema) {\n    auto tmpl = getCapnpTemplate(js, schema);\n    return jsg::check(tmpl->GetFunction(context));\n  }\n\n  v8::Local<v8::Object> wrap(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      capnp::DynamicCapability::Client client,\n      kj::Maybe<jsg::Ref<CapnpCapability>&> refToInitialize = kj::none) {\n    auto tmpl = getCapnpTemplate(js, client.getSchema());\n    auto obj = jsg::check(tmpl->InstanceTemplate()->NewInstance(context));\n    auto ref = js.alloc<CapnpCapability>(kj::mv(client));\n    ref.attachWrapper(js.v8Isolate, obj);\n    KJ_IF_SOME(r, refToInitialize) {\n      r = kj::mv(ref);\n    }\n    return obj;\n  }\n\n  // Wrap a specific compiled-in interface. This lets you use MyType::Client as a return type\n  // in a JSG method.\n  template <typename Client, typename = capnp::FromClient<Client>>\n  v8::Local<v8::Object> wrap(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      Client client) {\n    return wrap(js, context, creator, capnp::toDynamic(kj::mv(client)));\n  }\n\n  kj::Maybe<capnp::DynamicCapability::Client> tryUnwrap(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      capnp::DynamicCapability::Client*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    auto& wrapper = static_cast<TypeWrapper&>(*this);\n    KJ_IF_SOME(obj,\n        wrapper.tryUnwrap(js, context, handle, (CapnpCapability*)nullptr, parentObject)) {\n      return obj.getClient(js, *this);\n    } else {\n      // Since we don't know the schema, we cannot accept an arbitrary object.\n      return kj::none;\n    }\n  }\n\n  // Unwrap a specific compiled-in interface. This lets you use MyType::Client as a parameter\n  // in a JSG method.\n  template <typename Client, typename Interface = capnp::FromClient<Client>>\n  kj::Maybe<Client> tryUnwrap(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      Client*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    auto expectedSchema = capnp::Schema::from<Interface>();\n\n    auto& wrapper = static_cast<TypeWrapper&>(*this);\n    KJ_IF_SOME(obj,\n        wrapper.tryUnwrap(js, context, handle, (CapnpCapability*)nullptr, parentObject)) {\n      capnp::DynamicCapability::Client dynamic = obj.getClient(js.v8Isolate, *this);\n      if (dynamic.getSchema().extends(expectedSchema)) {\n        return dynamic.as<Interface>();\n      } else {\n        // Incompatible interfaces.\n        return kj::none;\n      }\n    } else if (handle->IsObject()) {\n      // Treat object as a server implementation.\n      auto isolate = js.v8Isolate;\n      CapnpTypeWrapperBase& wrapper = TypeWrapper::from(isolate);\n      capnp::DynamicCapability::Client dynamic = IoContext::current().getLocalCapSet().add(\n          kj::heap<CapnpServer>(expectedSchema, handle.As<v8::Object>(), wrapper, isolate));\n      return dynamic.as<Interface>();\n    } else {\n      return kj::none;\n    }\n  }\n\n  // Not relevant for us but we must define a method with this name to satisfy TypeWrapperExtension.\n  void newContext() = delete;\n\n  template <bool isContext = false, typename Client, typename Interface = capnp::FromClient<Client>>\n  v8::Local<v8::FunctionTemplate> getTemplate(jsg::Lock& js, Client*) {\n    static_assert(!isContext);\n    return getCapnpTemplate(js, capnp::Schema::from<Interface>());\n  }\n\n  v8::Local<v8::FunctionTemplate> getCapnpTemplate(jsg::Lock& js, capnp::Schema schema) {\n    using Ret = decltype(typeConstructors)::Entry;\n    return typeConstructors\n        .findOrCreate(schema, [&]() -> Ret {\n      return js.withinHandleScope([&]() -> Ret {\n        auto handle = makeConstructor(js, schema);\n        return {schema, {js.v8Isolate, handle}};\n      });\n    }).Get(js.v8Isolate);\n  }\n\n private:\n  kj::HashMap<capnp::Schema, v8::Global<v8::FunctionTemplate>> typeConstructors;\n\n  // Each method callback we create needs to pack the method schema into a v8::External. But\n  // v8::External can only store a pointer, and InterfaceSchema::Method is larger than a pointer.\n  // So we need to allocate copies of all the `Method` objects somewhere where they'll live until\n  // the isolate shuts down.\n  kj::HashMap<capnp::InterfaceSchema, kj::Array<capnp::InterfaceSchema::Method>> methodSchemas;\n\n  v8::Local<v8::FunctionTemplate> makeConstructor(jsg::Lock& js, capnp::Schema schema) {\n    return js.withinHandleScope([&]() -> v8::Local<v8::FunctionTemplate> {\n      // HACK: We happen to know that `Schema` is just a pointer internally, and is\n      //   trivially copyable and destructible. So, we can safely stuff it directly into a\n      //   v8::External by value, avoiding extra allocations.\n      static_assert(sizeof(schema) == sizeof(void*));\n      static_assert(__is_trivially_copyable(capnp::Schema));\n      static_assert(__is_trivially_destructible(capnp::Schema));\n      void* schemaAsPtr;\n      memcpy(&schemaAsPtr, &schema, sizeof(schema));\n\n      auto constructor = v8::FunctionTemplate::New(js.v8Isolate, &constructorCallback,\n          v8::External::New(js.v8Isolate, schemaAsPtr, v8::kExternalPointerTypeTagDefault));\n\n      auto prototype = constructor->PrototypeTemplate();\n      auto signature = v8::Signature::New(js.v8Isolate, constructor);\n\n      auto instance = constructor->InstanceTemplate();\n\n      constructor->SetClassName(jsg::v8StrIntern(js.v8Isolate, schema.getShortDisplayName()));\n\n      auto& wrapper = static_cast<TypeWrapper&>(*this);\n\n      auto proto = schema.getProto();\n      switch (proto.which()) {\n        case capnp::schema::Node::FILE:\n        case capnp::schema::Node::STRUCT:\n        case capnp::schema::Node::ENUM:\n        case capnp::schema::Node::CONST:\n        case capnp::schema::Node::ANNOTATION:\n          // TODO(someday): Support non-interface types.\n          break;\n\n        case capnp::schema::Node::INTERFACE: {\n          // As explained in ResourceWrapper, we must have 2 internal fields, where the first one is\n          // the GC visitation callback.\n          instance->SetInternalFieldCount(jsg::Wrappable::INTERNAL_FIELD_COUNT);\n\n          constructor->Inherit(\n              wrapper.getTemplate(js.v8Isolate, static_cast<CapnpCapability*>(nullptr)));\n          kj::HashSet<uint64_t> seen;\n          addAllMethods(js, prototype, signature, schema.asInterface(), seen);\n          break;\n        }\n      }\n\n      for (auto nested: proto.getNestedNodes()) {\n        KJ_IF_SOME(child,\n            js.getCapnpSchemaLoader<api::ServiceWorkerGlobalScope>().tryGet(nested.getId())) {\n          switch (child.getProto().which()) {\n            case capnp::schema::Node::FILE:\n            case capnp::schema::Node::STRUCT:\n            case capnp::schema::Node::INTERFACE:\n              constructor->Set(\n                  jsg::v8StrIntern(js.v8Isolate, nested.getName()), makeConstructor(js, child));\n              break;\n\n            case capnp::schema::Node::ENUM:\n            case capnp::schema::Node::CONST:\n            case capnp::schema::Node::ANNOTATION:\n              // These kinds are not implemented and cannot contain further nested scopes, so don't\n              // generate anything at all for now.\n              break;\n          }\n        }\n      }\n\n      return constructor;\n    });\n  }\n\n  // Add all methods to the capability prototype. Since JavaScript doesn't support multiple\n  // inheritance, we need to flatten all inherited methods into each interface.\n  //\n  // `seen` is a set of type IDs that we've visited already, so that diamond inheritance doesn't\n  // lead to us double-registering methods.\n  void addAllMethods(jsg::Lock& js,\n      v8::Local<v8::ObjectTemplate> prototype,\n      v8::Local<v8::Signature> signature,\n      capnp::InterfaceSchema schema,\n      kj::HashSet<uint64_t>& seen) {\n\n    JSG_REQUIRE(seen.size() < 64, TypeError,\n        \"Interface inherits too many types: \", schema.getProto().getDisplayName());\n\n    // Reverse-iterate so that in case of duplicate method names, the method from the first class\n    // in the list takes precedence.\n    auto supers = schema.getSuperclasses();\n    for (auto i = supers.size(); i > 0; i--) {\n      auto super = supers[i - 1];\n\n      // Check if this superclass is in the `seen` set. As a slight optimization we only check this\n      // before visiting a superclass, so that for a regular interface that doesn't inherit\n      // anything, we never allocate the `seen` set. This assumes that inheritance is not cyclic.\n      // Technically it's possible to declare cyclic inheritance (maliciously, perhaps), but in\n      // that case we'll just redundantly create the methods for one type, which is not a big deal.\n      uint64_t id = super.getProto().getId();\n      bool isNew = false;\n      seen.findOrCreate(id, [&]() {\n        isNew = true;\n        return id;\n      });\n      if (isNew) {\n        addAllMethods(js, prototype, signature, super, seen);\n      }\n    }\n\n    kj::ArrayPtr<capnp::InterfaceSchema::Method> methods =\n        methodSchemas.findOrCreate(schema, [&]() -> decltype(methodSchemas)::Entry {\n      return {schema, KJ_MAP(m, schema.getMethods()) { return m; }};\n    });\n\n    for (auto& method: methods) {\n      auto name = jsg::v8StrIntern(js.v8Isolate, method.getProto().getName());\n      prototype->Set(name,\n          v8::FunctionTemplate::New(js.v8Isolate, &methodCallback,\n              v8::External::New(js.v8Isolate, &method, v8::kExternalPointerTypeTagDefault),\n              signature, 0, v8::ConstructorBehavior::kThrow));\n    }\n  }\n\n  static void constructorCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {\n    jsg::liftKj(args, [&]() {\n      auto data = args.Data();\n      KJ_ASSERT(data->IsExternal());\n      void* schemaAsPtr = data.As<v8::External>()->Value(v8::kExternalPointerTypeTagDefault);\n      capnp::Schema schema;\n      memcpy(&schema, &schemaAsPtr, sizeof(schema));\n\n      JSG_REQUIRE(args.IsConstructCall(), TypeError, \"Failed to construct '\",\n          schema.getShortDisplayName(),\n          \"': Please use the \"\n          \"'new' operator, this object constructor cannot be called as a function.\");\n\n      auto& js = jsg::Lock::from(args.GetIsolate());\n      auto obj = args.This();\n      KJ_ASSERT(obj->InternalFieldCount() == jsg::Wrappable::INTERNAL_FIELD_COUNT);\n\n      auto arg = args[0];\n      JSG_REQUIRE(arg->IsObject(), TypeError, \"Constructor argument for '\",\n          schema.getShortDisplayName(),\n          \"' must be an object \"\n          \"implementing the interface.\");\n\n      CapnpTypeWrapperBase& wrapper = TypeWrapper::from(js.v8Isolate);\n      capnp::DynamicCapability::Client client =\n          IoContext::current().getLocalCapSet().add(kj::heap<CapnpServer>(js, schema.asInterface(),\n              jsg::V8Ref<v8::Object>(js.v8Isolate, arg.As<v8::Object>()), wrapper));\n      auto ptr = js.alloc<CapnpCapability>(kj::mv(client));\n\n      ptr.attachWrapper(js.v8Isolate, obj);\n    });\n  }\n\n  static void methodCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {\n    jsg::liftKj(args, [&]() {\n      auto data = args.Data();\n      KJ_ASSERT(data->IsExternal());\n      auto& method = *reinterpret_cast<capnp::InterfaceSchema::Method*>(\n          data.As<v8::External>()->Value(v8::kExternalPointerTypeTagDefault));\n\n      auto& js = jsg::Lock::from(args.GetIsolate());\n      auto obj = args.This();\n      auto& wrapper = TypeWrapper::from(js.v8Isolate);\n      auto& self = jsg::extractInternalPointer<CapnpCapability, false>(js.v8Context(), obj);\n\n      return wrapper.wrap(js, js.v8Context(), obj, self.call(js, method, args[0], wrapper));\n    });\n  }\n\n  v8::Local<v8::Object> wrapCap(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      capnp::DynamicCapability::Client value,\n      kj::Maybe<jsg::Ref<CapnpCapability>&> refToInitialize) override {\n    return wrap(js, context, kj::none, kj::mv(value), refToInitialize);\n  }\n  kj::Maybe<capnp::DynamicCapability::Client> tryUnwrapCap(\n      jsg::Lock& js, v8::Local<v8::Context> context, v8::Local<v8::Value> value) override {\n    return tryUnwrap(\n        js, context, value, static_cast<capnp::DynamicCapability::Client*>(nullptr), kj::none);\n  }\n\n  v8::Local<v8::Promise> wrapPromise(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      jsg::Promise<jsg::Value> value) override {\n    return static_cast<TypeWrapper&>(*this).wrap(js, context, creator, kj::mv(value));\n  }\n  kj::Maybe<jsg::Promise<jsg::Value>> tryUnwrapPromise(\n      jsg::Lock& js, v8::Local<v8::Context> context, v8::Local<v8::Value> value) override {\n    return static_cast<TypeWrapper&>(*this).tryUnwrap(\n        js, context, value, static_cast<jsg::Promise<jsg::Value>*>(nullptr), kj::none);\n  }\n};\n\n#define EW_CAPNP_TYPES                                                                             \\\n  ::workerd::api::CapnpCapability,                                                                 \\\n      ::workerd::jsg::TypeWrapperExtension<::workerd::api::CapnpTypeWrapper>\n// The list of capnp.h types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/cf-property-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"cf-property.h\"\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/tests/test-fixture.h>\n\nnamespace workerd::api {\nnamespace {\n\nKJ_TEST(\"Test that CfProperty is frozen by default\") {\n  TestFixture fixture({.mainModuleSource = R\"SCRIPT(\n      export default {\n        async fetch(request) {\n          request.cf.foo = 100;\n          return new Response(`OK`);\n        },\n      };\n    )SCRIPT\"_kj});\n\n  try {\n    auto result = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"TEST\"_kj);\n    KJ_FAIL_REQUIRE(\"exception expected\");\n  } catch (kj::Exception& e) {\n    KJ_EXPECT(e.getDescription() ==\n        \"jsg.TypeError: Cannot add property foo, object is not extensible\"_kj);\n  }\n}\n\nKJ_TEST(\"Test that CfProperty::deepClone returns editable object\") {\n  TestFixture fixture({.mainModuleSource = R\"SCRIPT(\n      export default {\n        async fetch(request) {\n          const req = new Request(request);\n          req.cf.foo = 100;\n          return new Response(`OK`);\n        },\n      };\n    )SCRIPT\"_kj});\n  auto result = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"TEST\"_kj);\n  KJ_EXPECT(result.statusCode == 200);\n}\n\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/cf-property.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"cf-property.h\"\n\n#include <workerd/io/features.h>\n#include <workerd/util/own-util.h>\n\nnamespace workerd::api {\n\nstatic constexpr kj::StringPtr kDefaultBotManagementValue = R\"DATA({\n  \"corporateProxy\": false,\n  \"verifiedBot\": false,\n  \"jsDetection\": { \"passed\": false },\n  \"staticResource\": false,\n  \"detectionIds\": {},\n  \"score\": 99\n})DATA\"_kjc;\n\n// When the cfBotManagementNoOp compatibility flag is set, we'll check the\n// request cf blob to see if it contains a botManagement field. If it does\n// *not* we will add it using the following default fields.\n// Note that if the botManagement team changes any of the fields they provide,\n// this default value may need to be changed also.\nstatic void handleDefaultBotManagement(jsg::Lock& js, jsg::JsObject handle) {\n  auto name = \"botManagement\"_kjc;\n  if (!handle.has(js, name)) {\n    // For performance reasons, we only want to construct the default values\n    // once per isolate so we cache the constructed value using an internal\n    // private field on the global scope. Whenever we need to use it again we\n    // pull the exact same value.\n    auto bm = js.global().getPrivate(js, name);\n    if (bm.isUndefined()) {\n      bm = jsg::JsValue::fromJson(js, kDefaultBotManagementValue);\n      KJ_DASSERT(bm.isObject());\n      js.global().setPrivate(js, name, bm);\n    }\n    handle.set(js, name, bm);\n  }\n}\n\nCfProperty::CfProperty(kj::Maybe<kj::StringPtr> unparsed) {\n  value = mapCopyString(unparsed);\n}\n\nCfProperty::CfProperty(jsg::Lock& js, const jsg::JsObject& object)\n    : CfProperty(kj::Maybe(jsg::JsRef(js, object))) {}\n\nCfProperty::CfProperty(kj::Maybe<jsg::JsRef<jsg::JsObject>>&& parsed) {\n  KJ_IF_SOME(v, parsed) {\n    value = kj::mv(v);\n  }\n}\n\njsg::Optional<jsg::JsObject> CfProperty::get(jsg::Lock& js) {\n  return getRef(js).map(\n      [&js](jsg::JsRef<jsg::JsObject>&& ref) mutable { return ref.getHandle(js); });\n}\n\njsg::Optional<jsg::JsRef<jsg::JsObject>> CfProperty::getRef(jsg::Lock& js) {\n  KJ_IF_SOME(cf, value) {\n    KJ_SWITCH_ONEOF(cf) {\n      KJ_CASE_ONEOF(parsed, jsg::JsRef<jsg::JsObject>) {\n        return parsed.addRef(js);\n      }\n      KJ_CASE_ONEOF(unparsed, kj::String) {\n        auto parsed = jsg::JsValue::fromJson(js, unparsed);\n        auto object = KJ_ASSERT_NONNULL(parsed.tryCast<jsg::JsObject>());\n\n        if (!FeatureFlags::get(js).getNoCfBotManagementDefault()) {\n          handleDefaultBotManagement(js, object);\n        }\n\n        object.recursivelyFreeze(js);\n\n        // replace unparsed string with a parsed v8 object\n        this->value = object.addRef(js);\n        return jsg::JsRef(js, object);\n      }\n    }\n  }\n\n  return kj::none;\n}\n\nkj::Maybe<kj::String> CfProperty::serialize(jsg::Lock& js) {\n  KJ_IF_SOME(cf, value) {\n    KJ_SWITCH_ONEOF(cf) {\n      KJ_CASE_ONEOF(parsed, jsg::JsRef<jsg::JsObject>) {\n        return jsg::JsValue(parsed.getHandle(js)).toJson(js);\n      }\n      KJ_CASE_ONEOF(unparsed, kj::String) {\n        if (!FeatureFlags::get(js).getNoCfBotManagementDefault()) {\n          // we mess up with the value on this code path,\n          // need to parse it, fix it and serialize back\n          jsg::JsValue handle = KJ_ASSERT_NONNULL(getRef(js)).getHandle(js);\n          return handle.toJson(js);\n        }\n\n        return kj::str(unparsed);\n      }\n    }\n  }\n\n  return kj::none;\n}\n\nCfProperty CfProperty::deepClone(jsg::Lock& js) {\n  // By default, when CfProperty is lazily parsed, the resulting JS object\n  // will be recursively frozen, preventing edits. However, when the CfProperty\n  // is cloned and the clone is lazily parsed, the resulting JS object must not\n  // be frozen! So, to ensure that, we'll force the parse to occur here if it\n  // hasn't been parsed already, this will ensure that the clone receives the\n  // parsed object via json cloning below rather than the raw string.\n  // TODO(cleanup): With a bit of refactoring we can preserve the lazy parsing\n  // optimization through the clone. But for now, let's just do the easy thing.\n  getRef(js);\n  KJ_IF_SOME(cf, value) {\n    KJ_SWITCH_ONEOF(cf) {\n      KJ_CASE_ONEOF(parsed, jsg::JsRef<jsg::JsObject>) {\n        return CfProperty(jsg::JsRef(js, parsed.getHandle(js).jsonClone(js)));\n      }\n      KJ_CASE_ONEOF(unparsed, kj::String) {\n        KJ_FAIL_REQUIRE(\"The cf property should have been lazily parsed!\");\n      }\n    }\n  }\n\n  return nullptr;\n}\n\nvoid CfProperty::visitForGc(jsg::GcVisitor& visitor) {\n  KJ_IF_SOME(cf, value) {\n    KJ_SWITCH_ONEOF(cf) {\n      KJ_CASE_ONEOF(parsed, jsg::JsRef<jsg::JsObject>) {\n        visitor.visit(parsed);\n      }\n      KJ_CASE_ONEOF_DEFAULT {}\n    }\n  }\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/cf-property.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n// Common functionality to manage cf headers and properties.\n\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api {\n\n// A holder for Cf header property value.\n// The string header is parsed on demand and the parsed value cached.\nclass CfProperty {\n\n public:\n  KJ_DISALLOW_COPY(CfProperty);\n\n  explicit CfProperty() {}\n  CfProperty(decltype(nullptr)) {}\n  CfProperty(CfProperty&&) = default;\n  CfProperty& operator=(CfProperty&&) = default;\n\n  explicit CfProperty(kj::Maybe<kj::StringPtr> unparsed);\n\n  explicit CfProperty(jsg::Lock& js, const jsg::JsObject& object);\n\n  explicit CfProperty(kj::Maybe<jsg::JsRef<jsg::JsObject>>&& parsed);\n\n  // Get parsed value\n  jsg::Optional<jsg::JsObject> get(jsg::Lock& js);\n\n  // Get parsed value as a global ref\n  jsg::Optional<jsg::JsRef<jsg::JsObject>> getRef(jsg::Lock& js);\n\n  // Serialize to string\n  kj::Maybe<kj::String> serialize(jsg::Lock& js);\n\n  // Clone by deep cloning parsed v8 object (if any).\n  CfProperty deepClone(jsg::Lock& js);\n\n  void visitForGc(jsg::GcVisitor& visitor);\n\n  JSG_MEMORY_INFO(CfProperty) {\n    KJ_IF_SOME(v, value) {\n      KJ_SWITCH_ONEOF(v) {\n        KJ_CASE_ONEOF(str, kj::String) {\n          tracker.trackField(\"value\", str);\n        }\n        KJ_CASE_ONEOF(obj, jsg::JsRef<jsg::JsObject>) {\n          tracker.trackField(\"value\", obj);\n        }\n      }\n    }\n  }\n\n private:\n  kj::Maybe<kj::OneOf<kj::String, jsg::JsRef<jsg::JsObject>>> value;\n};\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/commonjs.c++",
    "content": "#include \"commonjs.h\"\n\n#include <workerd/io/features.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/modules-new.h>\n#include <workerd/jsg/resource.h>\n\nnamespace workerd::api {\n\nCommonJsModuleContext::CommonJsModuleContext(jsg::Lock& js, kj::Path path)\n    : module(js.alloc<CommonJsModuleObject>(js, path.toString(true))),\n      pathOrSpecifier(kj::mv(path)),\n      exports(js, module->getExports(js)) {}\n\nCommonJsModuleContext::CommonJsModuleContext(jsg::Lock& js, const jsg::Url& specifier)\n    : module(js.alloc<CommonJsModuleObject>(js, kj::str(specifier.getHref()))),\n      pathOrSpecifier(specifier.clone()),\n      exports(js, module->getExports(js)) {}\n\njsg::JsValue CommonJsModuleContext::require(jsg::Lock& js, kj::String specifier) {\n  if (isNodeJsCompatEnabled(js)) {\n    KJ_IF_SOME(nodeSpec, jsg::checkNodeSpecifier(specifier)) {\n      specifier = kj::mv(nodeSpec);\n    }\n  }\n\n  if (FeatureFlags::get(js).getNewModuleRegistry()) {\n    return jsg::modules::ModuleRegistry::resolve(js, specifier, \"default\"_kj,\n        jsg::modules::ResolveContext::Type::BUNDLE, jsg::modules::ResolveContext::Source::REQUIRE,\n        KJ_ASSERT_NONNULL(pathOrSpecifier.tryGet<jsg::Url>()));\n  }\n\n  auto& path = KJ_ASSERT_NONNULL(pathOrSpecifier.tryGet<kj::Path>());\n\n  auto modulesForResolveCallback = jsg::getModulesForResolveCallback(js.v8Isolate);\n  KJ_REQUIRE(modulesForResolveCallback != nullptr, \"didn't expect resolveCallback() now\");\n\n  kj::Path targetPath = ([&] {\n    // If the specifier begins with one of our known prefixes, let's not resolve\n    // it against the referrer.\n    if (specifier.startsWith(\"node:\") || specifier.startsWith(\"cloudflare:\") ||\n        specifier.startsWith(\"workerd:\")) {\n      return kj::Path::parse(specifier);\n    }\n    return path.parent().eval(specifier);\n  })();\n\n  // require() is only exposed to worker bundle modules so the resolve here is only\n  // permitted to require worker bundle or built-in modules. Internal modules are\n  // excluded.\n  auto& info =\n      JSG_REQUIRE_NONNULL(modulesForResolveCallback->resolve(js, targetPath, path,\n                              jsg::ModuleRegistry::ResolveOption::DEFAULT,\n                              jsg::ModuleRegistry::ResolveMethod::REQUIRE, specifier.asPtr()),\n          Error, \"No such module \\\"\", targetPath.toString(), \"\\\".\");\n  // Adding imported from suffix here not necessary like it is for resolveCallback, since we have a\n  // js stack that will include the parent module's name and location of the failed require().\n\n  auto options = jsg::ModuleRegistry::RequireImplOptions::DEFAULT;\n  if (FeatureFlags::get(js).getExportCommonJsDefaultNamespace()) {\n    options = jsg::ModuleRegistry::RequireImplOptions::EXPORT_DEFAULT;\n  }\n\n  return jsg::ModuleRegistry::requireImpl(js, info, options);\n}\n\nvoid CommonJsModuleContext::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"exports\", exports);\n  KJ_SWITCH_ONEOF(pathOrSpecifier) {\n    KJ_CASE_ONEOF(path, kj::Path) {\n      tracker.trackFieldWithSize(\"path\", path.size());\n    }\n    KJ_CASE_ONEOF(specifier, jsg::Url) {\n      tracker.trackField(\"specifier\", specifier);\n    }\n  }\n}\n\nkj::String CommonJsModuleContext::getFilename() const {\n  KJ_SWITCH_ONEOF(pathOrSpecifier) {\n    KJ_CASE_ONEOF(path, kj::Path) {\n      return path.toString(true);\n    }\n    KJ_CASE_ONEOF(specifier, jsg::Url) {\n      // The specifier is a URL. We want to parse it as a path and\n      // return just the filename portion.\n      // TODO(soon): kj::Path::parse() requires a kj::StringPtr but\n      // the path name here is a kj::ArrayPtr<const char>. We can\n      // avoid an extraneous copy here by updating kj::Path::parse\n      // to also accept a kj::ArrayPtr<const char>.\n      auto path = kj::str(specifier.getPathname().slice(1));\n      auto filename = kj::Path::parse(path).basename();\n      return filename.toString(false);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::String CommonJsModuleContext::getDirname() const {\n  KJ_SWITCH_ONEOF(pathOrSpecifier) {\n    KJ_CASE_ONEOF(path, kj::Path) {\n      return path.parent().toString(true);\n    }\n    KJ_CASE_ONEOF(specifier, jsg::Url) {\n      // The specifier is a URL. We want to parse it as a path and\n      // return just the directory portion.\n      auto path = kj::str(specifier.getPathname().slice(1));\n      auto pathObj = kj::Path::parse(path);\n      return pathObj.parent().toString(true);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\njsg::Ref<CommonJsModuleObject> CommonJsModuleContext::getModule(jsg::Lock& js) {\n  return module.addRef();\n}\n\njsg::JsValue CommonJsModuleContext::getExports(jsg::Lock& js) const {\n  return exports.getHandle(js);\n}\nvoid CommonJsModuleContext::setExports(jsg::Lock& js, jsg::JsValue value) {\n  exports = jsg::JsRef(js, value);\n}\n\nCommonJsModuleObject::CommonJsModuleObject(jsg::Lock& js, kj::String path)\n    : exports(js, js.obj()),\n      path(kj::mv(path)) {}\n\njsg::JsValue CommonJsModuleObject::getExports(jsg::Lock& js) const {\n  return exports.getHandle(js);\n}\nvoid CommonJsModuleObject::setExports(jsg::Lock& js, jsg::JsValue value) {\n  exports = jsg::JsRef(js, value);\n}\n\nkj::StringPtr CommonJsModuleObject::getPath() const {\n  return path;\n}\n\nvoid CommonJsModuleObject::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"exports\", exports);\n  tracker.trackField(\"path\", path);\n}\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/commonjs.h",
    "content": "#pragma once\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/url.h>\n\n#include <kj/filesystem.h>\n\nnamespace workerd::api {\n\nclass CommonJsModuleObject final: public jsg::Object {\n public:\n  CommonJsModuleObject(jsg::Lock& js, kj::String path);\n\n  jsg::JsValue getExports(jsg::Lock& js) const;\n  void setExports(jsg::Lock& js, jsg::JsValue value);\n  kj::StringPtr getPath() const;\n\n  JSG_RESOURCE_TYPE(CommonJsModuleObject) {\n    JSG_INSTANCE_PROPERTY(exports, getExports, setExports);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(path, getPath);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  jsg::JsRef<jsg::JsValue> exports;\n  kj::String path;\n};\n\nclass CommonJsModuleContext final: public jsg::Object {\n public:\n  CommonJsModuleContext(jsg::Lock& js, kj::Path path);\n  CommonJsModuleContext(jsg::Lock& js, const jsg::Url& url);\n\n  jsg::JsValue require(jsg::Lock& js, kj::String specifier);\n\n  jsg::Ref<CommonJsModuleObject> getModule(jsg::Lock& js);\n\n  jsg::JsValue getExports(jsg::Lock& js) const;\n  void setExports(jsg::Lock& js, jsg::JsValue value);\n\n  kj::String getFilename() const;\n  kj::String getDirname() const;\n\n  jsg::JsValue getModuleExports(jsg::Lock& js) {\n    return getModule(js)->getExports(js);\n  }\n\n  JSG_RESOURCE_TYPE(CommonJsModuleContext) {\n    JSG_METHOD(require);\n    JSG_READONLY_INSTANCE_PROPERTY(module, getModule);\n    JSG_INSTANCE_PROPERTY(exports, getExports, setExports);\n    JSG_LAZY_INSTANCE_PROPERTY(__filename, getFilename);\n    JSG_LAZY_INSTANCE_PROPERTY(__dirname, getDirname);\n  }\n\n  jsg::Ref<CommonJsModuleObject> module;\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  // If pathOrSpecifier is a path, then we're using the old module registry\n  // implementation. If it is a jsg::Url, then we are using the new module\n  // registry implementation.\n  kj::OneOf<kj::Path, jsg::Url> pathOrSpecifier;\n  jsg::JsRef<jsg::JsValue> exports;\n};\n\n// Used with the original module registry implementation.\ntemplate <typename LockType>\nstruct CommonJsImpl: public jsg::ModuleRegistry::CommonJsModuleInfo::CommonJsModuleProvider {\n  jsg::Ref<api::CommonJsModuleContext> context;\n  CommonJsImpl(jsg::Lock& js, kj::Path path)\n      : context(js.alloc<api::CommonJsModuleContext>(js, kj::mv(path))) {}\n  KJ_DISALLOW_COPY_AND_MOVE(CommonJsImpl);\n  jsg::JsObject getContext(jsg::Lock& js) override {\n    auto& lock = kj::downcast<LockType>(js);\n    return jsg::JsObject(lock.wrap(js.v8Context(), context.addRef()));\n  }\n  jsg::JsValue getExports(jsg::Lock& js) override {\n    return jsg::JsValue(context->getModule(js)->getExports(js));\n  }\n};\n\n#define EW_CJS_ISOLATE_TYPES api::CommonJsModuleObject, api::CommonJsModuleContext\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/container.c++",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"container.h\"\n\n#include <workerd/api/http.h>\n#include <workerd/io/features.h>\n#include <workerd/io/io-context.h>\n\nnamespace workerd::api {\n\n// =======================================================================================\n// Basic lifecycle methods\n\nContainer::Container(rpc::Container::Client rpcClient, bool running)\n    : rpcClient(IoContext::current().addObject(kj::heap(kj::mv(rpcClient)))),\n      running(running) {}\n\nvoid Container::start(jsg::Lock& js, jsg::Optional<StartupOptions> maybeOptions) {\n  auto flags = FeatureFlags::get(js);\n  JSG_REQUIRE(!running, Error, \"start() cannot be called on a container that is already running.\");\n\n  StartupOptions options = kj::mv(maybeOptions).orDefault({});\n\n  auto req = rpcClient->startRequest();\n  KJ_IF_SOME(entrypoint, options.entrypoint) {\n    auto list = req.initEntrypoint(entrypoint.size());\n    for (auto i: kj::indices(entrypoint)) {\n      list.set(i, entrypoint[i]);\n    }\n  }\n  req.setEnableInternet(options.enableInternet);\n\n  KJ_IF_SOME(env, options.env) {\n    auto list = req.initEnvironmentVariables(env.fields.size());\n    for (auto i: kj::indices(env.fields)) {\n      auto field = &env.fields[i];\n      JSG_REQUIRE(field->name.findFirst('=') == kj::none, Error,\n          \"Environment variable names cannot contain '=': \", field->name);\n\n      JSG_REQUIRE(field->name.findFirst('\\0') == kj::none, Error,\n          \"Environment variable names cannot contain '\\\\0': \", field->name);\n\n      JSG_REQUIRE(field->value.findFirst('\\0') == kj::none, Error,\n          \"Environment variable values cannot contain '\\\\0': \", field->name);\n\n      list.set(i, str(field->name, \"=\", field->value));\n    }\n  }\n\n  if (flags.getWorkerdExperimental()) {\n    KJ_IF_SOME(hardTimeoutMs, options.hardTimeout) {\n      JSG_REQUIRE(hardTimeoutMs > 0, RangeError, \"Hard timeout must be greater than 0\");\n      req.setHardTimeoutMs(hardTimeoutMs);\n    }\n  }\n\n  KJ_IF_SOME(labels, options.labels) {\n    auto list = req.initLabels(labels.fields.size());\n    for (auto i: kj::indices(labels.fields)) {\n      auto& field = labels.fields[i];\n      JSG_REQUIRE(field.name.size() > 0, Error, \"Label names cannot be empty\");\n      for (auto c: field.name) {\n        JSG_REQUIRE(static_cast<kj::byte>(c) >= 0x20, Error,\n            \"Label names cannot contain control characters (index \", i, \")\");\n      }\n      for (auto c: field.value) {\n        JSG_REQUIRE(static_cast<kj::byte>(c) >= 0x20, Error,\n            \"Label values cannot contain control characters (index \", i, \")\");\n      }\n      list[i].setName(field.name);\n      list[i].setValue(field.value);\n    }\n  }\n\n  req.setCompatibilityFlags(flags);\n\n  IoContext::current().addTask(req.sendIgnoringResult());\n\n  running = true;\n}\n\njsg::Promise<void> Container::setInactivityTimeout(jsg::Lock& js, int64_t durationMs) {\n  JSG_REQUIRE(\n      durationMs > 0, TypeError, \"setInactivityTimeout() cannot be called with a durationMs <= 0\");\n\n  auto req = rpcClient->setInactivityTimeoutRequest();\n\n  req.setDurationMs(durationMs);\n  return IoContext::current().awaitIo(js, req.sendIgnoringResult());\n}\n\njsg::Promise<void> Container::interceptOutboundHttp(\n    jsg::Lock& js, kj::String addr, jsg::Ref<Fetcher> binding) {\n  auto& ioctx = IoContext::current();\n  auto channel = binding->getSubrequestChannel(ioctx);\n\n  // Get a channel token for RPC usage, the container runtime can use this\n  // token later to redeem a Fetcher.\n  auto token = channel->getToken(IoChannelFactory::ChannelTokenUsage::RPC);\n\n  auto req = rpcClient->setEgressHttpRequest();\n  req.setHostPort(addr);\n  req.setChannelToken(token);\n  return ioctx.awaitIo(js, req.sendIgnoringResult());\n}\n\njsg::Promise<void> Container::interceptAllOutboundHttp(jsg::Lock& js, jsg::Ref<Fetcher> binding) {\n  auto& ioctx = IoContext::current();\n  auto channel = binding->getSubrequestChannel(ioctx);\n  auto token = channel->getToken(IoChannelFactory::ChannelTokenUsage::RPC);\n\n  // Register for all IPv4 and IPv6 addresses (on port 80)\n  auto reqV4 = rpcClient->setEgressHttpRequest();\n  reqV4.setHostPort(\"0.0.0.0/0\"_kj);\n  reqV4.setChannelToken(token);\n\n  auto reqV6 = rpcClient->setEgressHttpRequest();\n  reqV6.setHostPort(\"::/0\"_kj);\n  reqV6.setChannelToken(token);\n\n  return ioctx.awaitIo(js,\n      kj::joinPromisesFailFast(kj::arr(reqV4.sendIgnoringResult(), reqV6.sendIgnoringResult())));\n}\n\njsg::Promise<void> Container::monitor(jsg::Lock& js) {\n  JSG_REQUIRE(running, Error, \"monitor() cannot be called on a container that is not running.\");\n\n  return IoContext::current()\n      .awaitIo(js, rpcClient->monitorRequest(capnp::MessageSize{4, 0}).send())\n      .then(js, [this](jsg::Lock& js, capnp::Response<rpc::Container::MonitorResults> results) {\n    running = false;\n    auto exitCode = results.getExitCode();\n    KJ_IF_SOME(d, destroyReason) {\n      jsg::Value error = kj::mv(d);\n      destroyReason = kj::none;\n      js.throwException(kj::mv(error));\n    }\n\n    if (exitCode != 0) {\n      auto err = js.error(kj::str(\"Container exited with unexpected exit code: \", exitCode));\n      KJ_ASSERT_NONNULL(err.tryCast<jsg::JsObject>()).set(js, \"exitCode\", js.num(exitCode));\n      js.throwException(err);\n    }\n  }, [this](jsg::Lock& js, jsg::Value&& error) {\n    running = false;\n    destroyReason = kj::none;\n    js.throwException(kj::mv(error));\n  });\n}\n\njsg::Promise<void> Container::destroy(jsg::Lock& js, jsg::Optional<jsg::Value> error) {\n  if (!running) return js.resolvedPromise();\n\n  if (destroyReason == kj::none) {\n    destroyReason = kj::mv(error);\n  }\n\n  return IoContext::current().awaitIo(\n      js, rpcClient->destroyRequest(capnp::MessageSize{4, 0}).sendIgnoringResult());\n}\n\nvoid Container::signal(jsg::Lock& js, int signo) {\n  JSG_REQUIRE(signo > 0 && signo <= 64, RangeError, \"Invalid signal number.\");\n  JSG_REQUIRE(running, Error, \"signal() cannot be called on a container that is not running.\");\n\n  auto req = rpcClient->signalRequest(capnp::MessageSize{4, 0});\n  req.setSigno(signo);\n  IoContext::current().addTask(req.sendIgnoringResult());\n}\n\n// =======================================================================================\n// getTcpPort()\n\n// `getTcpPort()` returns a `Fetcher`, on which `fetch()` and `connect()` can be called. `Fetcher`\n// is a JavaScript wrapper around `WorkerInterface`, so we need to implement that.\nclass Container::TcpPortWorkerInterface final: public WorkerInterface {\n public:\n  TcpPortWorkerInterface(capnp::ByteStreamFactory& byteStreamFactory,\n      kj::EntropySource& entropySource,\n      const kj::HttpHeaderTable& headerTable,\n      rpc::Container::Port::Client port)\n      : byteStreamFactory(byteStreamFactory),\n        entropySource(entropySource),\n        headerTable(headerTable),\n        port(kj::mv(port)) {}\n\n  // Implements fetch(), i.e., HTTP requests. We form a TCP connection, then run HTTP over it\n  // (as opposed to, say, speaking http-over-capnp to the container service).\n  kj::Promise<void> request(kj::HttpMethod method,\n      kj::StringPtr url,\n      const kj::HttpHeaders& headers,\n      kj::AsyncInputStream& requestBody,\n      kj::HttpService::Response& response) override {\n    // URLs should have been validated earlier in the stack, so parsing the URL should succeed.\n    auto parsedUrl = KJ_REQUIRE_NONNULL(kj::Url::tryParse(url, kj::Url::Context::HTTP_PROXY_REQUEST,\n                                            {.percentDecode = false, .allowEmpty = true}),\n        \"invalid url?\", url);\n\n    // We don't support TLS.\n    JSG_REQUIRE(parsedUrl.scheme != \"https\", Error,\n        \"Connecting to a container using HTTPS is not currently supported; use HTTP instead. \"\n        \"TLS is unnecessary anyway, as the connection is already secure by default.\");\n\n    // Schemes other than http: and https: should have been rejected earlier, but let's verify.\n    KJ_REQUIRE(parsedUrl.scheme == \"http\");\n\n    // We need to convert the URL from proxy format (full URL in request line) to host format\n    // (path in request line, hostname in Host header).\n    auto newHeaders = headers.cloneShallow();\n    newHeaders.setPtr(kj::HttpHeaderId::HOST, parsedUrl.host);\n    auto noHostUrl = parsedUrl.toString(kj::Url::Context::HTTP_REQUEST);\n\n    // Make a TCP connection...\n    auto pipe = kj::newTwoWayPipe();\n    kj::Maybe<kj::Exception> connectionException = kj::none;\n\n    auto connectionPromise = connectImpl(*pipe.ends[1]);\n\n    // ... and then stack an HttpClient on it ...\n    auto client = kj::newHttpClient(headerTable, *pipe.ends[0], {.entropySource = entropySource});\n\n    // ... and then adapt that to an HttpService ...\n    auto service = kj::newHttpService(*client);\n\n    // ... fork connection promises so we can keep the original exception around ...\n    auto connectionPromiseForked = connectionPromise.fork();\n    auto connectionPromiseBranch = connectionPromiseForked.addBranch();\n    auto connectionPromiseToKeepException = connectionPromiseForked.addBranch();\n\n    // ... and now we can just forward our call to that ...\n    try {\n      co_await service->request(method, noHostUrl, newHeaders, requestBody, response)\n          .exclusiveJoin(\n              // never done as we do not want a Connection RPC exiting successfully\n              // affecting the request\n              connectionPromiseBranch.then([]() -> kj::Promise<void> { return kj::NEVER_DONE; }));\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      connectionException = kj::some(kj::mv(exception));\n    }\n\n    // ... and last but not least, if the connect() call succeeded but the connection\n    // was broken, we throw that exception.\n    KJ_IF_SOME(exception, connectionException) {\n      co_await connectionPromiseToKeepException;\n      kj::throwFatalException(kj::mv(exception));\n    }\n  }\n\n  // Implements connect(), i.e., forms a raw socket.\n  kj::Promise<void> connect(kj::StringPtr host,\n      const kj::HttpHeaders& headers,\n      kj::AsyncIoStream& connection,\n      ConnectResponse& response,\n      kj::HttpConnectSettings settings) override {\n    JSG_REQUIRE(!settings.useTls, Error,\n        \"Connencting to a container using TLS is not currently supported. It is unnecessary \"\n        \"anyway, as the connection is already secure by default.\");\n\n    auto promise = connectImpl(connection);\n\n    kj::HttpHeaders responseHeaders(headerTable);\n    response.accept(200, \"OK\", responseHeaders);\n\n    return promise;\n  }\n\n  // The only `CustomEvent` that can happen through `Fetcher` is a JSRPC call. Maybe we will\n  // support this someday? But not today.\n  kj::Promise<CustomEvent::Result> customEvent(kj::Own<CustomEvent> event) override {\n    return event->notSupported();\n  }\n\n  // There's no way to invoke the remaining event types via `Fetcher`.\n  kj::Promise<void> prewarm(kj::StringPtr url) override {\n    KJ_UNREACHABLE;\n  }\n  kj::Promise<ScheduledResult> runScheduled(kj::Date scheduledTime, kj::StringPtr cron) override {\n    KJ_UNREACHABLE;\n  }\n  kj::Promise<AlarmResult> runAlarm(kj::Date scheduledTime, uint32_t retryCount) override {\n    KJ_UNREACHABLE;\n  }\n\n private:\n  capnp::ByteStreamFactory& byteStreamFactory;\n  kj::EntropySource& entropySource;\n  const kj::HttpHeaderTable& headerTable;\n  rpc::Container::Port::Client port;\n\n  // Connect to the port and pump bytes to/from `connection`. Used by both request() and\n  // connect().\n  kj::Promise<void> connectImpl(kj::AsyncIoStream& connection) {\n    // A lot of the following is copied from\n    // capnp::HttpOverCapnpFactory::KjToCapnpHttpServiceAdapter::connect().\n    auto req = port.connectRequest(capnp::MessageSize{4, 1});\n    auto downPipe = kj::newOneWayPipe();\n    req.setDown(byteStreamFactory.kjToCapnp(kj::mv(downPipe.out)));\n    auto pipeline = req.send();\n\n    // Make sure the request message isn't pinned into memory through the co_await below.\n    { auto drop = kj::mv(req); }\n\n    auto downPumpTask =\n        downPipe.in->pumpTo(connection)\n            .then([&connection, down = kj::mv(downPipe.in)](uint64_t) -> kj::Promise<void> {\n      connection.shutdownWrite();\n      return kj::NEVER_DONE;\n    });\n    auto up = pipeline.getUp();\n\n    auto upStream = byteStreamFactory.capnpToKjExplicitEnd(up);\n    auto upPumpTask = connection.pumpTo(*upStream)\n                          .then([&upStream = *upStream](uint64_t) mutable {\n      return upStream.end();\n    }).then([up = kj::mv(up), upStream = kj::mv(upStream)]() mutable -> kj::Promise<void> {\n      return kj::NEVER_DONE;\n    });\n\n    co_await pipeline.ignoreResult();\n    co_await kj::joinPromisesFailFast(kj::arr(kj::mv(upPumpTask), kj::mv(downPumpTask)));\n  }\n};\n\n// `Fetcher` actually wants us to give it a factory that creates a new `WorkerInterface` for each\n// request, so this is that.\nclass Container::TcpPortOutgoingFactory final: public Fetcher::OutgoingFactory {\n public:\n  TcpPortOutgoingFactory(capnp::ByteStreamFactory& byteStreamFactory,\n      kj::EntropySource& entropySource,\n      const kj::HttpHeaderTable& headerTable,\n      rpc::Container::Port::Client port)\n      : byteStreamFactory(byteStreamFactory),\n        entropySource(entropySource),\n        headerTable(headerTable),\n        port(kj::mv(port)) {}\n\n  kj::Own<WorkerInterface> newSingleUseClient(kj::Maybe<kj::String> cfStr) override {\n    // At present we have no use for `cfStr`.\n    return kj::heap<TcpPortWorkerInterface>(byteStreamFactory, entropySource, headerTable, port);\n  }\n\n private:\n  capnp::ByteStreamFactory& byteStreamFactory;\n  kj::EntropySource& entropySource;\n  const kj::HttpHeaderTable& headerTable;\n  rpc::Container::Port::Client port;\n};\n\njsg::Ref<Fetcher> Container::getTcpPort(jsg::Lock& js, int port) {\n  JSG_REQUIRE(port > 0 && port < 65536, TypeError, \"Invalid port number: \", port);\n\n  auto req = rpcClient->getTcpPortRequest(capnp::MessageSize{4, 0});\n  req.setPort(port);\n\n  auto& ioctx = IoContext::current();\n\n  kj::Own<Fetcher::OutgoingFactory> factory =\n      kj::heap<TcpPortOutgoingFactory>(ioctx.getByteStreamFactory(), ioctx.getEntropySource(),\n          ioctx.getHeaderTable(), req.send().getPort());\n\n  return js.alloc<Fetcher>(\n      ioctx.addObject(kj::mv(factory)), Fetcher::RequiresHostAndProtocol::YES, true);\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/container.h",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// APIs that an Actor (Durable Object) uses to access its own state.\n//\n// See actor.h for APIs used by other Workers to talk to Actors.\n//\n#include <workerd/io/compatibility-date.h>\n#include <workerd/io/container.capnp.h>\n#include <workerd/io/io-own.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api {\n\nclass Fetcher;\n\n// Implements the `ctx.container` API for durable-object-attached containers. This API allows\n// the DO to supervise the attached container (lightweight virtual machine), including starting,\n// stopping, monitoring, making requests to the container, intercepting outgoing network requests,\n// etc.\nclass Container: public jsg::Object {\n public:\n  Container(rpc::Container::Client rpcClient, bool running);\n\n  struct StartupOptions {\n    jsg::Optional<kj::Array<kj::String>> entrypoint;\n    bool enableInternet = false;\n    jsg::Optional<jsg::Dict<kj::String>> env;\n    jsg::Optional<int64_t> hardTimeout;\n    jsg::Optional<jsg::Dict<kj::String>> labels;\n\n    // TODO(containers): Allow intercepting stdin/stdout/stderr by specifying streams here.\n\n    JSG_STRUCT(entrypoint, enableInternet, env, hardTimeout, labels);\n    JSG_STRUCT_TS_OVERRIDE_DYNAMIC(CompatibilityFlags::Reader flags) {\n      if (flags.getWorkerdExperimental()) {\n        JSG_TS_OVERRIDE(ContainerStartupOptions {\n          entrypoint?: string[];\n          enableInternet: boolean;\n          env?: Record<string, string>;\n          hardTimeout?: number | bigint;\n          labels?: Record<string, string>;\n        });\n      } else {\n        JSG_TS_OVERRIDE(ContainerStartupOptions {\n          entrypoint?: string[];\n          enableInternet: boolean;\n          env?: Record<string, string>;\n          labels?: Record<string, string>;\n        });\n      }\n    }\n  };\n\n  bool getRunning() {\n    return running;\n  }\n\n  // Methods correspond closely to the RPC interface in `container.capnp`.\n  void start(jsg::Lock& js, jsg::Optional<StartupOptions> options);\n  jsg::Promise<void> monitor(jsg::Lock& js);\n  jsg::Promise<void> destroy(jsg::Lock& js, jsg::Optional<jsg::Value> error);\n  void signal(jsg::Lock& js, int signo);\n  jsg::Ref<Fetcher> getTcpPort(jsg::Lock& js, int port);\n  jsg::Promise<void> setInactivityTimeout(jsg::Lock& js, int64_t durationMs);\n  jsg::Promise<void> interceptOutboundHttp(\n      jsg::Lock& js, kj::String addr, jsg::Ref<Fetcher> binding);\n  jsg::Promise<void> interceptAllOutboundHttp(jsg::Lock& js, jsg::Ref<Fetcher> binding);\n\n  // TODO(containers): listenTcp()\n\n  JSG_RESOURCE_TYPE(Container, CompatibilityFlags::Reader flags) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(running, getRunning);\n    JSG_METHOD(start);\n    JSG_METHOD(monitor);\n    JSG_METHOD(destroy);\n    JSG_METHOD(signal);\n    JSG_METHOD(getTcpPort);\n    JSG_METHOD(setInactivityTimeout);\n\n    JSG_METHOD(interceptOutboundHttp);\n    JSG_METHOD(interceptAllOutboundHttp);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"destroyReason\", destroyReason);\n  }\n\n private:\n  IoOwn<rpc::Container::Client> rpcClient;\n  bool running;\n\n  kj::Maybe<jsg::Value> destroyReason;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(destroyReason);\n  }\n\n  class TcpPortWorkerInterface;\n  class TcpPortOutgoingFactory;\n};\n\n#define EW_CONTAINER_ISOLATE_TYPES api::Container, api::Container::StartupOptions\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/AGENTS.md",
    "content": "# src/workerd/api/crypto/\n\n## OVERVIEW\n\nWebCrypto API + Node.js crypto C++ implementations over BoringSSL. `crypto.h` defines public JSG types (`CryptoKey`, `SubtleCrypto`, `CryptoKeyUsageSet`). `impl.h` defines internal `CryptoKey::Impl` base class with per-algorithm static `ImportFunc`/`GenerateFunc` dispatch. Algorithm files implement `Impl` subclasses. `OSSLCALL()` macro wraps all BoringSSL calls with error translation.\n\n## FILE MAP\n\n| File                                             | Contents                                                                                              |\n| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |\n| `crypto.h/c++`                                   | Public API: `SubtleCrypto`, `CryptoKey`, `CryptoKeyPair`, `DigestStream`, `CryptoKeyUsageSet` bitmask |\n| `impl.h/c++`                                     | Internal base: `CryptoKey::Impl`, `interpretAlgorithmParam()`, OpenSSL error helpers, `OSSLCALL`      |\n| `keys.h/c++`                                     | `AsymmetricKeyCryptoKeyImpl` base; `KeyEncoding`/`KeyFormat`/`KeyType` enums; generic import/export   |\n| `aes.c++`                                        | AES-CBC, AES-CTR, AES-GCM, AES-KW                                                                     |\n| `rsa.h/c++`                                      | RSA-OAEP, RSA-PSS, RSASSA-PKCS1-v1_5                                                                  |\n| `ec.h/c++`                                       | ECDSA, ECDH, EdDSA (Ed25519/X25519)                                                                   |\n| `dh.h/c++`                                       | Diffie-Hellman key exchange (Node.js compat)                                                          |\n| `digest.h/c++`                                   | Hash/digest: SHA-\\*, MD5, Blake2b512/2s256                                                            |\n| `kdf.h` + `hkdf.c++`, `pbkdf2.c++`, `scrypt.c++` | KDF declarations in header; each algo in own .c++                                                     |\n| `jwk.h/c++`                                      | JWK import/export helpers                                                                             |\n| `x509.h/c++`                                     | X.509 certificate parsing (Node.js compat)                                                            |\n| `spkac.h/c++`                                    | SPKAC/Netscape SPKI (Node.js compat)                                                                  |\n| `prime.h/c++`                                    | Prime generation/checking (Node.js compat)                                                            |\n| `crc-impl.h/c++`                                 | CRC32/CRC32C (non-crypto checksum)                                                                    |\n| `endianness.h/c++`                               | Byte-swap utilities                                                                                   |\n| `impl-test.c++`, `aes-test.c++`                  | C++ unit tests                                                                                        |\n"
  },
  {
    "path": "src/workerd/api/crypto/aes-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"crypto.h\"\n#include \"impl.h\"\n\n#include <workerd/api/util.h>\n#include <workerd/jsg/jsg-test.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/setup.h>\n\n#include <kj/test.h>\n\n#include <array>\n\nnamespace workerd::api {\nnamespace {\n\njsg::V8System v8System;\n\nstruct CryptoContext: public jsg::Object, public jsg::ContextGlobal {\n  JSG_RESOURCE_TYPE(CryptoContext) {}\n};\nJSG_DECLARE_ISOLATE_TYPE(CryptoIsolate, CryptoContext);\n\nKJ_TEST(\"AES-KW key wrap\") {\n  // Basic test that I wrote when I was seeing heap corruption. Found it easier to iterate on with\n  // ASAN/valgrind than using our conformance tests with test-runner.\n  jsg::test::Evaluator<CryptoContext, CryptoIsolate> e(v8System);\n  e.getIsolate().runInLockScope([&](CryptoIsolate::Lock& isolateLock) {\n    auto rawWrappingKeys = std::array<kj::Array<kj::byte>, 3>({\n      kj::heapArray<kj::byte>({0xe6, 0x95, 0xea, 0xe3, 0xa8, 0xc0, 0x30, 0xf1, 0x76, 0xe3, 0x0e,\n        0x8e, 0x36, 0xf8, 0xf4, 0x31}),\n      // AES-KW 128\n      kj::heapArray<kj::byte>({0x20, 0xa7, 0x98, 0xd1, 0x82, 0x8c, 0x18, 0x67, 0xfd, 0xda, 0x16,\n        0x03, 0x57, 0xc6, 0x32, 0x4f, 0xcc, 0xe8, 0x08, 0x6d, 0x21, 0xe9, 0x3c, 0x60}),\n      // AES-KW 192\n      kj::heapArray<kj::byte>({0x52, 0x4b, 0x67, 0x25, 0xe3, 0x56, 0xaa, 0xce, 0x7e, 0x76, 0x9b,\n        0x48, 0x92, 0x55, 0x49, 0x06, 0x12, 0x5e, 0xf5, 0xae, 0xce, 0x39, 0xde, 0xc2, 0x5b, 0x27,\n        0x33, 0x4e, 0x6e, 0x52, 0x32, 0x4e}),\n      // AES-KW 256\n    });\n\n    auto aesKeys = KJ_MAP(rawKey, kj::mv(rawWrappingKeys)) {\n      SubtleCrypto::ImportKeyAlgorithm algorithm = {\n        .name = kj::str(\"AES-KW\"),\n      };\n      bool extractable = false;\n\n      return CryptoKey::Impl::importAes(isolateLock, \"AES-KW\", \"raw\", kj::mv(rawKey),\n          kj::mv(algorithm), extractable, {kj::str(\"wrapKey\"), kj::str(\"unwrapKey\")});\n    };\n\n    auto keyMaterial = kj::heapArray<const kj::byte>(\n        {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24});\n\n    JSG_WITHIN_CONTEXT_SCOPE(isolateLock,\n        isolateLock.newContext<CryptoContext>().getHandle(isolateLock), [&](jsg::Lock& js) {\n      for (const auto& aesKey: aesKeys) {\n        SubtleCrypto::EncryptAlgorithm params;\n        params.name = kj::str(\"AES-KW\");\n\n        auto wrapped = aesKey->wrapKey(js, kj::mv(params), keyMaterial.asPtr());\n\n        params = {};\n        params.name = kj::str(\"AES-KW\");\n\n        auto unwrapped = aesKey->unwrapKey(js, kj::mv(params), wrapped.asArrayPtr().asConst());\n\n        KJ_EXPECT(unwrapped.asArrayPtr() == keyMaterial);\n\n        // Corruption of wrapped key material should throw.\n        params = {};\n        params.name = kj::str(\"AES-KW\");\n        wrapped.asArrayPtr()[5] += 1;\n        KJ_EXPECT_THROW_MESSAGE(\n            \"[24 == -1]\", aesKey->unwrapKey(js, kj::mv(params), wrapped.asArrayPtr().asConst()));\n      }\n    });\n  });\n}\n\n// Disable null pointer checks (a subset of UBSan) here due to the null reference being passed for\n// jwkHandler. Using attribute push as annotating just the test itself didn't seem to work.\n#if __clang__ && __has_feature(undefined_behavior_sanitizer)\n#pragma clang attribute push(__attribute__((no_sanitize(\"null\"))), apply_to = function)\n#endif\nKJ_TEST(\"AES-CTR key wrap\") {\n  // Basic test that let me repro an issue where using an AES key that's not AES-KW would fail to\n  // wrap if it didn't have \"encrypt\" in its usages when created.\n\n  const jsg::TypeHandler<SubtleCrypto::JsonWebKey>* jwkHandler = nullptr;\n  // Not testing JWK here, so valid value isn't needed.\n\n  static constexpr kj::byte RAW_KEY_DATA[] = {0x52, 0x4b, 0x67, 0x25, 0xe3, 0x56, 0xaa, 0xce, 0x7e,\n    0x76, 0x9b, 0x48, 0x92, 0x55, 0x49, 0x06, 0x12, 0x5e, 0xf5, 0xae, 0xce, 0x39, 0xde, 0xc2, 0x5b,\n    0x27, 0x33, 0x4e, 0x6e, 0x52, 0x32, 0x4e};\n\n  static constexpr kj::ArrayPtr<const kj::byte> KEY_DATA(RAW_KEY_DATA, 32);\n\n  SubtleCrypto subtle;\n\n  static constexpr auto getWrappingKey = [](jsg::Lock& js, SubtleCrypto& subtle) {\n    return subtle.importKeySync(js, \"raw\", kj::heapArray<kj::byte>(KEY_DATA),\n        SubtleCrypto::ImportKeyAlgorithm{.name = kj::str(\"AES-CTR\")}, false /* extractable */,\n        {kj::str(\"wrapKey\"), kj::str(\"unwrapKey\")});\n  };\n\n  static constexpr auto getEnc = [](jsg::Lock& js) {\n    static constexpr auto kRaw =\n        \"\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0A\\x0B\\x0C\\x0D\\x0E\\x0F\\x10\"_kjb;\n    auto counter = jsg::JsUint8Array::create(js, kRaw);\n\n    return SubtleCrypto::EncryptAlgorithm{\n      .name = kj::str(\"AES-CTR\"),\n      .counter = jsg::JsBufferSource(counter).addRef(js),\n      .length = 5,\n    };\n  };\n\n  static constexpr auto getImportKeyAlg = [] {\n    return SubtleCrypto::ImportKeyAlgorithm{\n      .name = kj::str(\"AES-CBC\"),\n      .length = 256,\n    };\n  };\n\n  jsg::test::Evaluator<CryptoContext, CryptoIsolate> e(v8System);\n  bool completed = false;\n\n  e.getIsolate().runInLockScope([&](CryptoIsolate::Lock& isolateLock) {\n    JSG_WITHIN_CONTEXT_SCOPE(isolateLock,\n        isolateLock.newContext<CryptoContext>().getHandle(isolateLock), [&](jsg::Lock& js) {\n      auto wrappingKey = getWrappingKey(js, subtle);\n      subtle\n          .importKey(js, kj::str(\"raw\"), kj::heapArray(KEY_DATA), getImportKeyAlg(), true,\n              kj::arr(kj::str(\"decrypt\")))\n          .then(js,\n              [&](jsg::Lock&, jsg::Ref<CryptoKey> toWrap) {\n        return subtle.wrapKey(js, kj::str(\"raw\"), *toWrap, *wrappingKey, getEnc(js), *jwkHandler);\n      })\n          .then(js,\n              [&](jsg::Lock& js, jsg::JsRef<jsg::JsArrayBuffer> wrapped) {\n        auto data = wrapped.getHandle(js).copy();\n        return subtle.unwrapKey(js, kj::str(\"raw\"), kj::mv(data), *wrappingKey, getEnc(js),\n            getImportKeyAlg(), true, kj::arr(kj::str(\"encrypt\")), *jwkHandler);\n      })\n          .then(js, [&](jsg::Lock& js, jsg::Ref<CryptoKey> unwrapped) {\n        return subtle.exportKey(js, kj::str(\"raw\"), *unwrapped);\n      }).then(js, [&](jsg::Lock& js, api::SubtleCrypto::ExportKeyData roundTrippedKeyMaterial) {\n        auto& buf = roundTrippedKeyMaterial.get<jsg::JsRef<jsg::JsArrayBuffer>>();\n        KJ_ASSERT(buf.getHandle(js).asArrayPtr() == KEY_DATA);\n        completed = true;\n      });\n\n      js.runMicrotasks();\n    });\n  });\n\n  KJ_ASSERT(completed, \"Microtasks did not run fully.\");\n}\n#if __clang__ && __has_feature(undefined_behavior_sanitizer)\n#pragma clang attribute pop  // __attribute__((no_sanitize(\"null\"))\n#endif\n\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/aes.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"impl.h\"\n#include \"util.h\"\n\n#include <workerd/io/io-context.h>\n#include <workerd/jsg/jsvalue.h>\n\n#include <openssl/aes.h>\n#include <openssl/base.h>\n#include <openssl/bn.h>\n#include <openssl/cipher.h>\n#include <openssl/mem.h>\n\n#include <algorithm>\n#include <cstdint>\n\nnamespace workerd::api {\nnamespace {\nauto lookupAesCbcType(uint bitLength) {\n  switch (bitLength) {\n    case 128:\n      return EVP_aes_128_cbc();\n    case 192:\n      return EVP_aes_192_cbc();\n    case 256:\n      return EVP_aes_256_cbc();\n    default:\n      KJ_FAIL_ASSERT(\"CryptoKey has invalid data length\", bitLength);\n      // Assert because the data length must have come from a key we created!\n  }\n}\n\nauto lookupAesGcmType(uint bitLength) {\n  switch (bitLength) {\n    case 128:\n      return EVP_aes_128_gcm();\n    case 192:\n      return EVP_aes_192_gcm();\n    case 256:\n      return EVP_aes_256_gcm();\n    default:\n      KJ_FAIL_ASSERT(\"CryptoKey has invalid data length\", bitLength);\n      // Assert because the data length must have come from a key we created!\n  }\n}\n\n// Ensure the tagLength passed to the AES-GCM algorithm is one of the allowed bit lengths.\nvoid validateAesGcmTagLength(int tagLength) {\n  switch (tagLength) {\n    case 32:\n    case 64:\n    case 96:\n    case 104:\n    case 112:\n    case 120:\n    case 128:\n      break;\n    default:\n      JSG_FAIL_REQUIRE(DOMOperationError, \"Invalid AES-GCM tag length \", tagLength, \".\");\n  }\n}\n\nint decryptFinalHelper(kj::StringPtr algorithm,\n    size_t inputLength,\n    size_t outputLength,\n    EVP_CIPHER_CTX* cipherCtx,\n    kj::byte* out) {\n  // EVP_DecryptFinal_ex() failures can mean a mundane decryption failure, so we have to be careful\n  // with error handling when calling it. We can't use our usual OSSLCALL() macro, because that\n  // throws an unhelpful opaque OperationError.\n\n  // Clear the error queue; who knows what kind of junk is in there.\n  ClearErrorOnReturn clearErrorOnReturn;\n\n  int finalPlainSize = 0;\n  if (EVP_DecryptFinal_ex(cipherCtx, out, &finalPlainSize)) {\n    return finalPlainSize;\n  }\n\n  // Decryption failure! Let's figure out what exception to throw.\n\n  auto ec = clearErrorOnReturn.peekError();\n\n  // If the error code is anything other than zero or BAD_DECRYPT, just throw an opaque\n  // OperationError for consistency with our OSSLCALL() macro. Notably, AES-GCM tag authentication\n  // failures don't produce any error code, though they should probably be BAD_DECRYPT.\n  JSG_REQUIRE(ec == 0 || ec == ERR_PACK(ERR_LIB_CIPHER, CIPHER_R_BAD_DECRYPT) ||\n          ec == ERR_PACK(ERR_LIB_CIPHER, CIPHER_R_WRONG_FINAL_BLOCK_LENGTH),\n      InternalDOMOperationError, \"Unexpected issue decrypting\", internalDescribeOpensslErrors());\n\n  // Consume the error since it's one we were expecting.\n  clearErrorOnReturn.consumeError();\n\n  // Otherwise, tell the script author they gave us garbage.\n  JSG_FAIL_REQUIRE(DOMOperationError,\n      \"Decryption failed. This could be due \"\n      \"to a ciphertext authentication failure, bad padding, incorrect CryptoKey, or another \"\n      \"algorithm-specific reason. Input length was \",\n      inputLength, \", output length expected to be \", outputLength, \" for \", algorithm);\n}\n\n// NOTE: The OpenSSL calls to implement AES-GCM and AES-CBC are quite similar. If you update one\n//   algorithm's encrypt() or decrypt() implementation, it'd be worth reviewing the other\n//   algorithm's implementation as well.\n\n// The base key is used to avoid repeating the JWK export logic. It also happens to simplify the\n// concrete implementations to only define encrypt/decrypt.\nclass AesKeyBase: public CryptoKey::Impl {\n public:\n  explicit AesKeyBase(kj::Array<kj::byte> keyData,\n      CryptoKey::AesKeyAlgorithm keyAlgorithm,\n      bool extractable,\n      CryptoKeyUsageSet usages)\n      : CryptoKey::Impl(extractable, usages),\n        keyData(kj::mv(keyData)),\n        keyAlgorithm(kj::mv(keyAlgorithm)) {}\n\n protected:\n  kj::StringPtr getAlgorithmName() const override final {\n    // AesKeyAlgorithm is constructed from normalizedName which points into the static constant\n    // defined in crypto.c++ for lookup.\n    return keyAlgorithm.name;\n  }\n\n  bool equals(const CryptoKey::Impl& other) const override final {\n    return this == &other || (other.getType() == \"secret\"_kj && other.equals(keyData));\n  }\n\n  bool equals(const kj::Array<kj::byte>& other) const override final {\n    return keyData.size() == other.size() &&\n        CRYPTO_memcmp(keyData.begin(), other.begin(), keyData.size()) == 0;\n  }\n\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"AesKeyBase\"_kjc;\n  }\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(AesKeyBase);\n  }\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {\n    tracker.trackFieldWithSize(\"keyData\", keyData.size());\n    tracker.trackField(\"keyAlgorithm\", keyAlgorithm);\n  }\n\n private:\n  CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override final {\n    return keyAlgorithm;\n  }\n\n  SubtleCrypto::ExportKeyData exportKey(jsg::Lock& js, kj::StringPtr format) const override final {\n    JSG_REQUIRE(format == \"raw\" || format == \"jwk\", DOMNotSupportedError, getAlgorithmName(),\n        \" key only supports exporting \\\"raw\\\" & \\\"jwk\\\", not \\\"\", format, \"\\\".\");\n\n    if (format == \"jwk\") {\n      auto lengthInBytes = keyData.size();\n      KJ_ASSERT(lengthInBytes == 16 || lengthInBytes == 24 || lengthInBytes == 32);\n\n      auto aesMode = keyAlgorithm.name.slice(4);\n\n#ifdef KJ_DEBUG\n      static constexpr auto expectedModes = {\"GCM\", \"KW\", \"CTR\", \"CBC\"};\n      KJ_DASSERT(\n          expectedModes.end() != std::find(expectedModes.begin(), expectedModes.end(), aesMode));\n#endif\n\n      SubtleCrypto::JsonWebKey jwk;\n      jwk.kty = kj::str(\"oct\");\n      jwk.k = fastEncodeBase64Url(keyData);\n      jwk.alg = kj::str(\"A\", lengthInBytes * 8, aesMode);\n      jwk.key_ops = getUsages().map([](auto usage) { return kj::str(usage.name()); });\n      // I don't know why the spec says:\n      //   Set the ext attribute of jwk to equal the [[extractable]] internal slot of key.\n      // Earlier in the normative part of the spec it says:\n      //   6. If the [[extractable]] internal slot of key is false, then throw an InvalidAccessError.\n      //   7. Let result be the result of performing the export key operation specified by the\n      //      [[algorithm]] internal slot of key using key and format.\n      // So there's not really any other value that `ext` can have here since this code is the\n      // implementation of step 7 (see SubtleCrypto::exportKey where you can confirm it is\n      // enforcing step 6).\n      jwk.ext = true;\n\n      return jwk;\n    }\n\n    // Every export should be a separate copy.\n    return jsg::JsArrayBuffer::create(js, keyData).addRef(js);\n  }\n\n protected:\n  ZeroOnFree keyData;\n  CryptoKey::AesKeyAlgorithm keyAlgorithm;\n};\n\nclass AesGcmKey final: public AesKeyBase {\n public:\n  explicit AesGcmKey(kj::Array<kj::byte> keyData,\n      CryptoKey::AesKeyAlgorithm keyAlgorithm,\n      bool extractable,\n      CryptoKeyUsageSet usages)\n      : AesKeyBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable, usages) {}\n\n private:\n  jsg::JsArrayBuffer encrypt(jsg::Lock& js,\n      SubtleCrypto::EncryptAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> plainText) const override {\n    auto iv = JSG_REQUIRE_NONNULL(algorithm.iv, TypeError, \"Missing field \\\"iv\\\" in \\\"algorithm\\\".\")\n                  .getHandle(js);\n    JSG_REQUIRE(iv.size() != 0, DOMOperationError, \"AES-GCM IV must not be empty.\");\n\n    kj::ArrayPtr<kj::byte> empty = nullptr;\n    auto additionalData = ([&] {\n      KJ_IF_SOME(sourceRef, algorithm.additionalData) {\n        auto source = sourceRef.getHandle(js);\n        return source.asArrayPtr();\n      } else {\n        return empty;\n      }\n    })();\n\n    // The magic number below came from here:\n    // https://w3c.github.io/webcrypto/Overview.html#aes-gcm-operations\n    JSG_REQUIRE(plainText.size() <= ((UINT64_C(1) << 39) - 256), DOMOperationError,\n        \"AES-GCM can only encrypt up to 2^39 - 256 bytes of plaintext at a time, but requested \",\n        plainText.size(), \" bytes.\");\n\n    int tagLength = algorithm.tagLength.orDefault(128);\n    validateAesGcmTagLength(tagLength);\n\n    auto cipherCtx = kj::disposeWith<EVP_CIPHER_CTX_free>(EVP_CIPHER_CTX_new());\n    KJ_ASSERT(cipherCtx.get() != nullptr);\n\n    auto type = lookupAesGcmType(keyData.size() * 8);\n\n    // Set up the cipher context with the initialization vector. We pass nullptrs for the key data\n    // and initialization vector because we may need to override the default IV length.\n    OSSLCALL(EVP_EncryptInit_ex(cipherCtx.get(), type, nullptr, nullptr, nullptr));\n    OSSLCALL(EVP_CIPHER_CTX_ctrl(cipherCtx.get(), EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr));\n    OSSLCALL(EVP_EncryptInit_ex(\n        cipherCtx.get(), nullptr, nullptr, keyData.begin(), iv.asArrayPtr().begin()));\n\n    if (additionalData.size() > 0) {\n      // Run the engine with the additional data, which will presumably be transmitted alongside the\n      // cipher text in plain text. I noticed that if I call EncryptUpdate with 0-length AAD here,\n      // the subsequent call to EncryptUpdate will fail, thus the if-check.\n      int dummy;\n      OSSLCALL(EVP_EncryptUpdate(\n          cipherCtx.get(), nullptr, &dummy, additionalData.begin(), additionalData.size()));\n    }\n\n    // We make two cipher calls: EVP_EncryptUpdate() and EVP_EncryptFinal_ex(). AES-GCM behaves like\n    // a stream cipher in that it does not add padding and can process partial blocks, meaning that\n    // we know the exact ciphertext size in advance.\n    auto tagByteSize = tagLength / 8;\n    auto cipherText = jsg::JsArrayBuffer::create(js, plainText.size() + tagByteSize);\n\n    // Perform the actual encryption.\n\n    int cipherSize = 0;\n    OSSLCALL(EVP_EncryptUpdate(cipherCtx.get(), cipherText.asArrayPtr().begin(), &cipherSize,\n        plainText.begin(), plainText.size()));\n    KJ_ASSERT(cipherSize == plainText.size(), \"EVP_EncryptUpdate should encrypt all at once\");\n\n    int finalCipherSize = 0;\n    OSSLCALL(EVP_EncryptFinal_ex(\n        cipherCtx.get(), cipherText.asArrayPtr().begin() + cipherSize, &finalCipherSize));\n    KJ_ASSERT(finalCipherSize == 0, \"EVP_EncryptFinal_ex should not output any data\");\n\n    // Concatenate the tag onto the cipher text.\n    KJ_ASSERT(cipherSize + tagByteSize == cipherText.size(), \"imminent buffer overrun\");\n    OSSLCALL(EVP_CIPHER_CTX_ctrl(cipherCtx.get(), EVP_CTRL_GCM_GET_TAG, tagByteSize,\n        cipherText.asArrayPtr().begin() + cipherSize));\n    cipherSize += tagByteSize;\n    KJ_ASSERT(cipherSize == cipherText.size(), \"buffer overrun\");\n\n    return cipherText;\n  }\n\n  jsg::JsArrayBuffer decrypt(jsg::Lock& js,\n      SubtleCrypto::EncryptAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> cipherText) const override {\n    auto iv = JSG_REQUIRE_NONNULL(algorithm.iv, TypeError, \"Missing field \\\"iv\\\" in \\\"algorithm\\\".\")\n                  .getHandle(js);\n    JSG_REQUIRE(iv.size() != 0, DOMOperationError, \"AES-GCM IV must not be empty.\");\n\n    int tagLength = algorithm.tagLength.orDefault(128);\n    validateAesGcmTagLength(tagLength);\n\n    JSG_REQUIRE(cipherText.size() >= tagLength / 8, DOMOperationError, \"Ciphertext length of \",\n        cipherText.size() * 8,\n        \" bits must be greater than or equal to \"\n        \"the size of the AES-GCM tag length of \",\n        tagLength, \" bits.\");\n\n    kj::ArrayPtr<kj::byte> empty = nullptr;\n    auto additionalData = ([&] {\n      KJ_IF_SOME(sourceRef, algorithm.additionalData) {\n        auto source = sourceRef.getHandle(js);\n        return source.asArrayPtr();\n      }\n      return empty;\n    })();\n\n    auto cipherCtx = kj::disposeWith<EVP_CIPHER_CTX_free>(EVP_CIPHER_CTX_new());\n    KJ_ASSERT(cipherCtx.get() != nullptr);\n\n    auto type = lookupAesGcmType(keyData.size() * 8);\n\n    OSSLCALL(EVP_DecryptInit_ex(cipherCtx.get(), type, nullptr, nullptr, nullptr));\n    OSSLCALL(EVP_CIPHER_CTX_ctrl(cipherCtx.get(), EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr));\n    OSSLCALL(EVP_DecryptInit_ex(\n        cipherCtx.get(), nullptr, nullptr, keyData.begin(), iv.asArrayPtr().begin()));\n\n    int plainSize = 0;\n\n    if (additionalData.size() > 0) {\n      OSSLCALL(EVP_DecryptUpdate(\n          cipherCtx.get(), nullptr, &plainSize, additionalData.begin(), additionalData.size()));\n      plainSize = 0;\n    }\n\n    auto actualCipherText = cipherText.first(cipherText.size() - tagLength / 8);\n    auto tagText = cipherText.slice(actualCipherText.size(), cipherText.size());\n\n    auto plainText = jsg::JsArrayBuffer::create(js, actualCipherText.size());\n\n    // Perform the actual decryption.\n    OSSLCALL(EVP_DecryptUpdate(cipherCtx.get(), plainText.asArrayPtr().begin(), &plainSize,\n        actualCipherText.begin(), actualCipherText.size()));\n    KJ_ASSERT(plainSize == plainText.size());\n\n    // NOTE: We const_cast tagText here. EVP_CIPHER_CTX_ctrl() is used to set various\n    //   cipher-specific parameters, not just the GCM tag. Because of this, it takes its pointer\n    //   parameter as a void*, thus the const_cast. This is safe because tagText points to a\n    //   BufferSource allocated on V8's heap, and we know that OpenSSL does not modify the tag. (If\n    //   it did, the W3C crypto tests would fail.)\n    //\n    //   This little hack seems like a lesser evil than accepting the plaintext as mutable in every\n    //   decrypt implementation function interface.\n    OSSLCALL(EVP_CIPHER_CTX_ctrl(cipherCtx.get(), EVP_CTRL_GCM_SET_TAG, tagLength / 8,\n        const_cast<kj::byte*>(tagText.begin())));\n\n    plainSize += decryptFinalHelper(getAlgorithmName(), actualCipherText.size(), plainSize,\n        cipherCtx.get(), plainText.asArrayPtr().begin() + plainSize);\n    KJ_ASSERT(plainSize == plainText.size());\n\n    return plainText;\n  }\n};\n\nclass AesCbcKey final: public AesKeyBase {\n public:\n  explicit AesCbcKey(kj::Array<kj::byte> keyData,\n      CryptoKey::AesKeyAlgorithm keyAlgorithm,\n      bool extractable,\n      CryptoKeyUsageSet usages)\n      : AesKeyBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable, usages) {}\n\n private:\n  jsg::JsArrayBuffer encrypt(jsg::Lock& js,\n      SubtleCrypto::EncryptAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> plainText) const override {\n    auto iv = JSG_REQUIRE_NONNULL(algorithm.iv, TypeError, \"Missing field \\\"iv\\\" in \\\"algorithm\\\".\")\n                  .getHandle(js);\n\n    JSG_REQUIRE(iv.size() == 16, DOMOperationError, \"AES-CBC IV must be 16 bytes long (provided \",\n        iv.size(), \" bytes).\");\n\n    auto cipherCtx = kj::disposeWith<EVP_CIPHER_CTX_free>(EVP_CIPHER_CTX_new());\n    KJ_ASSERT(cipherCtx.get() != nullptr);\n    auto type = lookupAesCbcType(keyData.size() * 8);\n\n    // Set up the cipher context with the initialization vector.\n    OSSLCALL(EVP_EncryptInit_ex(\n        cipherCtx.get(), type, nullptr, keyData.begin(), iv.asArrayPtr().begin()));\n\n    auto blockSize = EVP_CIPHER_CTX_block_size(cipherCtx.get());\n    size_t paddingSize = blockSize - (plainText.size() % blockSize);\n    auto cipherText = jsg::JsArrayBuffer::create(js, plainText.size() + paddingSize);\n\n    // Perform the actual encryption.\n    //\n    // Note: We don't worry about PKCS padding (see RFC2315 section 10.3 step 2) because BoringSSL\n    //   takes care of it for us by default in EVP_EncryptFinal_ex().\n\n    int cipherSize = 0;\n    OSSLCALL(EVP_EncryptUpdate(cipherCtx.get(), cipherText.asArrayPtr().begin(), &cipherSize,\n        plainText.begin(), plainText.size()));\n    KJ_ASSERT(cipherSize <= cipherText.size(), \"buffer overrun\");\n\n    KJ_ASSERT(cipherSize + blockSize <= cipherText.size(), \"imminent buffer overrun\");\n    int finalCipherSize = 0;\n    OSSLCALL(EVP_EncryptFinal_ex(\n        cipherCtx.get(), cipherText.asArrayPtr().begin() + cipherSize, &finalCipherSize));\n    cipherSize += finalCipherSize;\n    KJ_ASSERT(cipherSize == cipherText.size(), \"buffer overrun\");\n\n    return cipherText;\n  }\n\n  jsg::JsArrayBuffer decrypt(jsg::Lock& js,\n      SubtleCrypto::EncryptAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> cipherText) const override {\n    auto iv = JSG_REQUIRE_NONNULL(algorithm.iv, TypeError, \"Missing field \\\"iv\\\" in \\\"algorithm\\\".\")\n                  .getHandle(js);\n\n    JSG_REQUIRE(iv.size() == 16, DOMOperationError, \"AES-CBC IV must be 16 bytes long (provided \",\n        iv.size(), \").\");\n\n    auto cipherCtx = kj::disposeWith<EVP_CIPHER_CTX_free>(EVP_CIPHER_CTX_new());\n    KJ_ASSERT(cipherCtx.get() != nullptr);\n\n    auto type = lookupAesCbcType(keyData.size() * 8);\n\n    // Set up the cipher context with the initialization vector.\n    OSSLCALL(EVP_DecryptInit_ex(\n        cipherCtx.get(), type, nullptr, keyData.begin(), iv.asArrayPtr().begin()));\n\n    int plainSize = 0;\n    auto blockSize = EVP_CIPHER_CTX_block_size(cipherCtx.get());\n\n    KJ_STACK_ARRAY(\n        kj::byte, plainText, cipherText.size() + ((blockSize > 1) ? blockSize : 0), 1024, 4096);\n\n    // Perform the actual decryption.\n    OSSLCALL(EVP_DecryptUpdate(\n        cipherCtx.get(), plainText.begin(), &plainSize, cipherText.begin(), cipherText.size()));\n    KJ_ASSERT(plainSize + ((blockSize > 1) ? blockSize : 0) <= plainText.size());\n\n    plainSize += decryptFinalHelper(getAlgorithmName(), cipherText.size(), plainSize,\n        cipherCtx.get(), plainText.begin() + plainSize);\n    KJ_ASSERT(plainSize <= plainText.size());\n\n    // Copy is necessary to support v8:Sandbox where all ArrayBuffers have to be\n    // allocated from within the sandbox.\n    return jsg::JsArrayBuffer::create(js, plainText.first(plainSize));\n  }\n};\n\nclass AesCtrKey final: public AesKeyBase {\n  static constexpr size_t expectedCounterByteSize = 16;\n\n public:\n  explicit AesCtrKey(kj::Array<kj::byte> keyData,\n      CryptoKey::AesKeyAlgorithm keyAlgorithm,\n      bool extractable,\n      CryptoKeyUsageSet usages)\n      : AesKeyBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable, usages) {}\n\n  jsg::JsArrayBuffer encrypt(jsg::Lock& js,\n      SubtleCrypto::EncryptAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> plainText) const override {\n    return encryptOrDecrypt(js, kj::mv(algorithm), plainText);\n  }\n\n  jsg::JsArrayBuffer decrypt(jsg::Lock& js,\n      SubtleCrypto::EncryptAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> cipherText) const override {\n    return encryptOrDecrypt(js, kj::mv(algorithm), cipherText);\n  }\n\n protected:\n  static const EVP_CIPHER& lookupAesType(size_t keyLengthBytes) {\n    switch (keyLengthBytes) {\n      case 16:\n        return *EVP_aes_128_ctr();\n      // NOTE: FWIW Chrome intentionally doesn't support 192 (http://crbug.com/533699) & at one\n      //   point in removal of the 192 variant was scheduled for removal from BoringSSL. However, we\n      //   do support it for completeness (as does Firefox).\n      case 24:\n        return *EVP_aes_192_ctr();\n      case 32:\n        return *EVP_aes_256_ctr();\n    }\n    KJ_FAIL_ASSERT(\"CryptoKey has invalid data length\");\n  }\n\n  jsg::JsArrayBuffer encryptOrDecrypt(jsg::Lock& js,\n      SubtleCrypto::EncryptAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> data) const {\n    auto counter = JSG_REQUIRE_NONNULL(\n        algorithm.counter, TypeError, \"Missing \\\"counter\\\" member in \\\"algorithm\\\".\")\n                       .getHandle(js);\n    JSG_REQUIRE(counter.size() == expectedCounterByteSize, DOMOperationError,\n        \"Counter must have length of 16 bytes (provided \", counter.size(), \").\");\n\n    auto& counterBitLength = JSG_REQUIRE_NONNULL(\n        algorithm.length, TypeError, \"Missing \\\"length\\\" member in \\\"algorithm\\\".\");\n\n    // Web IDL defines an octet as [0, 255] which explains why the spec here only calls out != 0 and\n    // <= 128, which implies the intended range must be [1, 128] which is what we enforce here.\n    // However, we check > 0 instead of != 0 because we don't enforce the bounds of an octet when\n    // converting from JS. If we were to ever add support for annotations into JSG (specifically\n    // EnforceRange), then we'd have enforcement way before this that counterBitLength is in the\n    // [0, 255] range:\n    //   * https://heycam.github.io/webidl/#EnforceRange,\n    //   * https://heycam.github.io/webidl/#es-octet\n    //   * https://heycam.github.io/webidl/#abstract-opdef-converttoint\n    JSG_REQUIRE(counterBitLength > 0 && counterBitLength <= 128, DOMOperationError,\n        \"Invalid counter of \", counterBitLength, \" bits length provided.\");\n\n    const auto& cipher = lookupAesType(keyData.size());\n\n    // The output of AES-CTR is the same size as the input.\n    auto result = jsg::JsArrayBuffer::create(js, data.size());\n\n    auto numCounterValues = newBignum();\n    JSG_REQUIRE(BN_lshift(numCounterValues.get(), BN_value_one(), counterBitLength),\n        InternalDOMOperationError, \"Error doing \", getAlgorithmName(), \" encrypt/decrypt\",\n        internalDescribeOpensslErrors());\n\n    auto currentCounter = getCounter(counter.asArrayPtr(), counterBitLength);\n\n    // Now figure out how many AES blocks we'll process/how many times to increment the counter.\n    auto numOutputBlocks = newBignum();\n    JSG_REQUIRE(BN_set_word(numOutputBlocks.get(),\n                    integerCeilDivision(result.size(), static_cast<size_t>(AES_BLOCK_SIZE))),\n        InternalDOMOperationError, \"Error doing \", getAlgorithmName(), \" encrypt/decrypt\",\n        internalDescribeOpensslErrors());\n\n    JSG_REQUIRE(BN_cmp(numOutputBlocks.get(), numCounterValues.get()) <= 0, DOMOperationError,\n        \"Counter block values will repeat\", tryDescribeOpensslErrors());\n\n    auto numBlocksUntilReset = newBignum();\n    // The number of blocks that can be encrypted without overflowing the counter. Subsequent\n    // blocks will need to reset the counter back to 0. BN_sub's signature is (result, a, b) and\n    // evaluates result = a - b. BN_sub documentation says an error happens on allocation failure\n    // but I can't find any evidence there's any such allocation & the errors seem to be a result\n    // of internal errors.\n    JSG_REQUIRE(BN_sub(numBlocksUntilReset.get(), numCounterValues.get(), currentCounter.get()),\n        InternalDOMOperationError, \"Error doing \", getAlgorithmName(), \" encrypt/decrypt\",\n        internalDescribeOpensslErrors());\n\n    if (BN_cmp(numBlocksUntilReset.get(), numOutputBlocks.get()) >= 0) {\n      // If the counter doesn't need any wrapping, can evaluate this as a single call.\n      process(&cipher, data, counter.asArrayPtr(), result.asArrayPtr());\n      return result;\n    }\n\n    // Need this to be done in 2 parts using the current counter block and then resetting the\n    // counter portion of the block back to zero.\n    auto inputSizePart1 = BN_get_word(numBlocksUntilReset.get()) * AES_BLOCK_SIZE;\n\n    process(&cipher, data.first(inputSizePart1), counter.asArrayPtr(), result.asArrayPtr());\n\n    // Zero the counter bits of the block in a copy of the input counter.\n    kj::Array<kj::byte> zeroed_counter = counter.copy();\n    {\n      KJ_DASSERT(counterBitLength / 8 <= expectedCounterByteSize);\n\n      auto remainder = counterBitLength % 8;\n      auto idx = expectedCounterByteSize - counterBitLength / 8;\n      zeroed_counter.slice(idx).fill(0);\n      if (remainder) {\n        zeroed_counter[idx - 1] &= 0xFF << remainder;\n      }\n    }\n\n    process(&cipher, data.slice(inputSizePart1, data.size()), zeroed_counter,\n        result.asArrayPtr().slice(inputSizePart1, result.size()));\n\n    return result;\n  }\n\n private:\n  kj::Own<BIGNUM> getCounter(\n      kj::ArrayPtr<kj::byte> counterBlock, const unsigned counterBitLength) const {\n    // See GetCounter from https://chromium.googlesource.com/chromium/src/+/refs/tags/91.0.4458.2/components/webcrypto/algorithms/aes_ctr.cc#86\n    // The counter is the rightmost \"counterBitLength\" of the block as a big-endian number.\n    KJ_DASSERT(counterBlock.size() == expectedCounterByteSize);\n\n    auto remainderBits = counterBitLength % 8;\n    if (remainderBits == 0) {\n      // Multiple of 8 bits, then can pass the remainder to BN_bin2bn (binary to bignum).\n      auto byteLength = counterBitLength / 8;\n      auto remainingCounter =\n          counterBlock.slice(expectedCounterByteSize - byteLength, counterBlock.size());\n\n      return JSG_REQUIRE_NONNULL(toBignum(remainingCounter.asBytes()), InternalDOMOperationError,\n          \"Error doing \", getAlgorithmName(), \" encrypt/decrypt\", internalDescribeOpensslErrors());\n    }\n\n    // Convert the counter but zero out the topmost bits so that we can convert to bignum from a\n    // byte stream. Chromium creates a copy here but that's because they only have a const only view\n    // of the data but in our WebCrypto implementation we have a non-const view of the underlying\n    // counter buffer.\n    auto byteLength = integerCeilDivision(counterBitLength, 8u);\n    KJ_DASSERT(byteLength > 0, counterBitLength, remainderBits);\n    KJ_DASSERT(byteLength <= expectedCounterByteSize, counterBitLength, counterBlock.size());\n\n    auto counterToProcess =\n        counterBlock.slice(expectedCounterByteSize - byteLength, counterBlock.size());\n    auto previous = counterToProcess[0];\n    counterToProcess[0] &= ~(0xFF << remainderBits);\n    KJ_DEFER(counterToProcess[0] = previous);\n    // We temporarily modified the counter to construct the BIGNUM, this undoes it to restore the\n    // input counter.\n\n    return JSG_REQUIRE_NONNULL(toBignum(counterToProcess), InternalDOMOperationError,\n        \"Error doing \", getAlgorithmName(), \" encrypt/decrypt\", internalDescribeOpensslErrors());\n  }\n\n  void process(const EVP_CIPHER* cipher,\n      kj::ArrayPtr<const kj::byte> input,\n      kj::ArrayPtr<kj::byte> counter,\n      kj::ArrayPtr<kj::byte> output) const {\n    // Workers are limited to 128MB so this isn't actually a realistic concern, but sanity check.\n    JSG_REQUIRE(input.size() < INT_MAX, DOMOperationError, \"Input is too large to encrypt.\");\n\n    auto cipherContext = kj::disposeWith<EVP_CIPHER_CTX_free>(EVP_CIPHER_CTX_new());\n    KJ_ASSERT(cipherContext.get() != nullptr);\n\n    // For CTR, it really does not matter whether we are encrypting or decrypting, so set enc to 0.\n    JSG_REQUIRE(EVP_CipherInit_ex(cipherContext.get(), cipher, nullptr,\n                    keyData.asPtr().asBytes().begin(), counter.asBytes().begin(), 0),\n        InternalDOMOperationError, \"Error doing \", getAlgorithmName(), \" encrypt/decrypt\",\n        internalDescribeOpensslErrors());\n\n    int outputLength = 0;\n    JSG_REQUIRE(EVP_CipherUpdate(cipherContext.get(), output.begin(), &outputLength,\n                    input.asBytes().begin(), input.size()),\n        InternalDOMOperationError, \"Error doing \", getAlgorithmName(), \" encrypt/decrypt\",\n        internalDescribeOpensslErrors());\n\n    KJ_ASSERT(outputLength >= 0 && outputLength <= output.size(), outputLength, output.size());\n\n    int finalOutputChunkLength = 0;\n    auto finalizationBuffer = output.slice(outputLength, output.size()).asBytes().begin();\n    JSG_REQUIRE(\n        EVP_CipherFinal_ex(cipherContext.get(), finalizationBuffer, &finalOutputChunkLength),\n        InternalDOMOperationError, \"Error doing \", getAlgorithmName(), \" encrypt/decrypt\",\n        internalDescribeOpensslErrors());\n\n    KJ_ASSERT(finalOutputChunkLength >= 0 && finalOutputChunkLength <= output.size(),\n        finalOutputChunkLength, output.size());\n\n    JSG_REQUIRE(static_cast<size_t>(outputLength) + static_cast<size_t>(finalOutputChunkLength) ==\n            input.size(),\n        InternalDOMOperationError, \"Error doing \", getAlgorithmName(), \" encrypt/decrypt.\");\n  }\n};\n\nclass AesKwKey final: public AesKeyBase {\n public:\n  explicit AesKwKey(kj::Array<kj::byte> keyData,\n      CryptoKey::AesKeyAlgorithm keyAlgorithm,\n      bool extractable,\n      CryptoKeyUsageSet usages)\n      : AesKeyBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable, usages) {}\n\n  jsg::JsArrayBuffer wrapKey(jsg::Lock& js,\n      SubtleCrypto::EncryptAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> unwrappedKey) const override {\n    // Resources used to implement this:\n    // https://www.ietf.org/rfc/rfc3394.txt\n    // https://chromium.googlesource.com/chromium/src/+/refs/tags/91.0.4458.2/components/webcrypto/algorithms/aes_kw.cc\n\n    JSG_REQUIRE((unwrappedKey.size() % 8) == 0, DOMOperationError,\n        \"Unwrapped key bit length must be a multiple of 64 bits but unwrapped key has a length of \",\n        unwrappedKey.size() * 8, \" bits.\");\n\n    JSG_REQUIRE(unwrappedKey.size() >= 16 && unwrappedKey.size() <= SIZE_MAX - 8, DOMOperationError,\n        \"Unwrapped key has length \", unwrappedKey.size(),\n        \" bytes but it should be greater than or \"\n        \"equal to 16 and less than or equal to \",\n        SIZE_MAX - 8);\n\n    auto wrapped = jsg::JsArrayBuffer::create(js, unwrappedKey.size() + 8);\n    // Wrapping adds 8 bytes of overhead for storing the IV which we check on decryption.\n\n    AES_KEY aesKey;\n    JSG_REQUIRE(0 == AES_set_encrypt_key(keyData.begin(), keyData.size() * 8, &aesKey),\n        InternalDOMOperationError, \"Error doing \", getAlgorithmName(), \" key wrapping\",\n        internalDescribeOpensslErrors());\n\n    JSG_REQUIRE(wrapped.size() ==\n            AES_wrap_key(&aesKey, nullptr, wrapped.asArrayPtr().begin(), unwrappedKey.begin(),\n                unwrappedKey.size()),\n        DOMOperationError, getAlgorithmName(), \" key wrapping failed\", tryDescribeOpensslErrors());\n\n    return wrapped;\n  }\n\n  jsg::JsArrayBuffer unwrapKey(jsg::Lock& js,\n      SubtleCrypto::EncryptAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> wrappedKey) const override {\n    // Resources used to implement this:\n    // https://www.ietf.org/rfc/rfc3394.txt\n    // https://chromium.googlesource.com/chromium/src/+/refs/tags/91.0.4458.2/components/webcrypto/algorithms/aes_kw.cc\n\n    JSG_REQUIRE((wrappedKey.size() % 8) == 0, DOMOperationError,\n        \"Provided a wrapped key to unwrap that is \", wrappedKey.size() * 8,\n        \" bits which isn't a multiple of 64 bits.\");\n\n    JSG_REQUIRE(wrappedKey.size() >= 24, DOMOperationError,\n        \"Provided a wrapped key to unwrap this is \", wrappedKey.size() * 8,\n        \" bits that is less than the minimal length of 192 bits.\");\n\n    auto unwrapped = jsg::JsArrayBuffer::create(js, wrappedKey.size() - 8);\n\n    AES_KEY aesKey;\n    JSG_REQUIRE(0 == AES_set_decrypt_key(keyData.begin(), keyData.size() * 8, &aesKey),\n        InternalDOMOperationError, \"Error doing \", getAlgorithmName(), \" key unwrapping\",\n        internalDescribeOpensslErrors());\n\n    // null for the IV value here will tell OpenSSL to validate using the default IV from RFC3394.\n    // https://github.com/openssl/openssl/blob/13a574d8bb2523181f8150de49bc041c9841f59d/crypto/modes/wrap128.c\n    JSG_REQUIRE(unwrapped.size() ==\n            AES_unwrap_key(&aesKey, nullptr, unwrapped.asArrayPtr().begin(), wrappedKey.begin(),\n                wrappedKey.size()),\n        DOMOperationError, getAlgorithmName(), \" key unwrapping failed\",\n        tryDescribeOpensslErrors());\n\n    return unwrapped;\n  }\n};\n\nCryptoKeyUsageSet validateAesUsages(CryptoKeyUsageSet::Context ctx,\n    kj::StringPtr normalizedName,\n    kj::ArrayPtr<const kj::String> keyUsages) {\n  // AES-CTR, AES-CBC, AES-GCM, and AES-KW all share the same logic for operations, with the only\n  // difference being the valid usages.\n  CryptoKeyUsageSet validUsages = CryptoKeyUsageSet::wrapKey() | CryptoKeyUsageSet::unwrapKey();\n  if (normalizedName != \"AES-KW\") {\n    validUsages |= CryptoKeyUsageSet::encrypt() | CryptoKeyUsageSet::decrypt();\n  }\n  return CryptoKeyUsageSet::validate(normalizedName, ctx, keyUsages, validUsages);\n}\n\n}  // namespace\n\nkj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> CryptoKey::Impl::generateAes(jsg::Lock& js,\n    kj::StringPtr normalizedName,\n    SubtleCrypto::GenerateKeyAlgorithm&& algorithm,\n    bool extractable,\n    kj::ArrayPtr<const kj::String> keyUsages) {\n  CryptoKeyUsageSet usages =\n      validateAesUsages(CryptoKeyUsageSet::Context::generate, normalizedName, keyUsages);\n\n  auto length = JSG_REQUIRE_NONNULL(\n      algorithm.length, TypeError, \"Missing field \\\"length\\\" in \\\"algorithm\\\".\");\n\n  switch (length) {\n    case 128:\n    case 192:\n    case 256:\n      break;\n    default:\n      JSG_FAIL_REQUIRE(DOMOperationError,\n          \"Generated AES key length must be 128, 192, or 256 bits but requested \", length, \".\");\n  }\n\n  auto keyDataArray = kj::heapArray<kj::byte>(length / 8);\n  IoContext::current().getEntropySource().generate(keyDataArray);\n\n  auto keyAlgorithm = CryptoKey::AesKeyAlgorithm{normalizedName, static_cast<uint16_t>(length)};\n\n  kj::Own<CryptoKey::Impl> keyImpl;\n\n  if (normalizedName == \"AES-GCM\") {\n    keyImpl = kj::heap<AesGcmKey>(kj::mv(keyDataArray), kj::mv(keyAlgorithm), extractable, usages);\n  } else if (normalizedName == \"AES-CBC\") {\n    keyImpl = kj::heap<AesCbcKey>(kj::mv(keyDataArray), kj::mv(keyAlgorithm), extractable, usages);\n  } else if (normalizedName == \"AES-CTR\") {\n    keyImpl = kj::heap<AesCtrKey>(kj::mv(keyDataArray), kj::mv(keyAlgorithm), extractable, usages);\n  } else if (normalizedName == \"AES-KW\") {\n    keyImpl = kj::heap<AesKwKey>(kj::mv(keyDataArray), kj::mv(keyAlgorithm), extractable, usages);\n  } else {\n    JSG_FAIL_REQUIRE(DOMNotSupportedError, normalizedName, \" key generation not supported.\");\n  }\n\n  return js.alloc<CryptoKey>(kj::mv(keyImpl));\n}\n\nkj::Own<CryptoKey::Impl> CryptoKey::Impl::importAes(jsg::Lock& js,\n    kj::StringPtr normalizedName,\n    kj::StringPtr format,\n    SubtleCrypto::ImportKeyData keyData,\n    SubtleCrypto::ImportKeyAlgorithm&& algorithm,\n    bool extractable,\n    kj::ArrayPtr<const kj::String> keyUsages) {\n  CryptoKeyUsageSet usages =\n      validateAesUsages(CryptoKeyUsageSet::Context::importSecret, normalizedName, keyUsages);\n\n  kj::Array<kj::byte> keyDataArray;\n\n  if (format == \"raw\") {\n    // NOTE: Checked in SubtleCrypto::importKey().\n    keyDataArray = kj::mv(keyData.get<kj::Array<kj::byte>>());\n    switch (keyDataArray.size() * 8) {\n      case 128:\n      case 192:\n      case 256:\n        break;\n      default:\n        JSG_FAIL_REQUIRE(DOMDataError,\n            \"Imported AES key length must be 128, 192, or 256 bits but provided \",\n            keyDataArray.size() * 8, \".\");\n    }\n  } else if (format == \"jwk\") {\n    auto aesMode = normalizedName.slice(4);\n\n    auto& keyDataJwk = keyData.get<SubtleCrypto::JsonWebKey>();\n    JSG_REQUIRE(keyDataJwk.kty == \"oct\", DOMDataError,\n        \"Symmetric \\\"jwk\\\" key import requires a JSON Web Key with Key Type parameter \"\n        \"\\\"kty\\\" equal to \\\"oct\\\" (encountered \\\"\",\n        keyDataJwk.kty, \"\\\").\");\n    // https://www.rfc-editor.org/rfc/rfc7518.txt Section 6.1\n    keyDataArray = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.k), DOMDataError,\n        \"Symmetric \\\"jwk\\\" key import requires a base64Url encoding of the key.\");\n\n    switch (keyDataArray.size() * 8) {\n      case 128:\n      case 192:\n      case 256:\n        KJ_IF_SOME(alg, keyDataJwk.alg) {\n          auto expectedAlg = kj::str(\"A\", keyDataArray.size() * 8, aesMode);\n          JSG_REQUIRE(alg == expectedAlg, DOMDataError,\n              \"Symmetric \\\"jwk\\\" key contains invalid \\\"alg\\\" value \\\"\", alg, \"\\\", expected \\\"\",\n              expectedAlg, \"\\\".\");\n        }\n        break;\n      default:\n        JSG_FAIL_REQUIRE(DOMDataError,\n            \"Imported AES key length must be 128, 192, or 256 bits but provided \",\n            keyDataArray.size() * 8, \".\");\n    }\n\n    if (keyUsages.size() != 0) {\n      KJ_IF_SOME(u, keyDataJwk.use) {\n        JSG_REQUIRE(u == \"enc\", DOMDataError,\n            \"Symmetric \\\"jwk\\\" key must have a \\\"use\\\" of \\\"enc\\\", not \\\"\", u, \"\\\".\");\n      }\n    }\n\n    KJ_IF_SOME(ops, keyDataJwk.key_ops) {\n      std::sort(ops.begin(), ops.end());\n      // Don't want to use the trick above to use a red-black tree because that constructs the set\n      // once ever for the process, but this path is dependent on user input. Could write things\n      // without the sort but it makes the enforcement from Section 4.2 below a 1-liner.\n\n      auto duplicate = std::adjacent_find(ops.begin(), ops.end());\n      JSG_REQUIRE(duplicate == ops.end(), DOMDataError,\n          \"Symmetric \\\"jwk\\\" key contains duplicate value \\\"\", *duplicate, \"\\\", in \\\"key_op\\\".\");\n      // https://tools.ietf.org/html/rfc7517#section-4.2 - no duplicate values in key_ops.\n\n      for (const auto& usage: keyUsages) {\n        JSG_REQUIRE(std::binary_search(ops.begin(), ops.end(), usage), DOMDataError,\n            \"\\\"jwk\\\" key missing usage \\\"\", usage, \"\\\", in \\\"key_ops\\\".\");\n      }\n    }\n\n    // TODO(conform/review): How should this from the standard:\n    //     > The \"use\" and \"key_ops\" JWK members SHOULD NOT be used together;\n    //     > however, if both are used, the information they convey MUST be\n    //     > consistent\n    //   be interpreted? What constitutes \"inconsistency\"? Is that implicit in enforcing that \"enc\"\n    //   must be the value for `use'? Or is there something else?\n\n    KJ_IF_SOME(e, keyDataJwk.ext) {\n      JSG_REQUIRE(e || !extractable, DOMDataError, \"\\\"jwk\\\" key has value \\\"\", e ? \"true\" : \"false\",\n          \"\\\", for \\\"ext\\\" that is incompatible \"\n          \"with import extractability value \\\"\",\n          extractable ? \"true\" : \"false\", \"\\\".\");\n    }\n  } else {\n    JSG_FAIL_REQUIRE(DOMNotSupportedError, \"Unrecognized key import format \\\"\", format, \"\\\".\");\n  }\n\n  auto keySize = keyDataArray.size() * 8;\n  KJ_ASSERT(keySize == 128 || keySize == 192 || keySize == 256);\n\n  auto keyAlgorithm = CryptoKey::AesKeyAlgorithm{normalizedName, static_cast<uint16_t>(keySize)};\n\n  if (normalizedName == \"AES-GCM\") {\n    return kj::heap<AesGcmKey>(kj::mv(keyDataArray), kj::mv(keyAlgorithm), extractable, usages);\n  } else if (normalizedName == \"AES-CBC\") {\n    return kj::heap<AesCbcKey>(kj::mv(keyDataArray), kj::mv(keyAlgorithm), extractable, usages);\n  } else if (normalizedName == \"AES-CTR\") {\n    return kj::heap<AesCtrKey>(kj::mv(keyDataArray), kj::mv(keyAlgorithm), extractable, usages);\n  } else if (normalizedName == \"AES-KW\") {\n    return kj::heap<AesKwKey>(kj::mv(keyDataArray), kj::mv(keyAlgorithm), extractable, usages);\n  }\n\n  JSG_FAIL_REQUIRE(\n      DOMNotSupportedError, \"Unsupported algorithm \\\"\", normalizedName, \"\\\" to import.\");\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/crc-impl.c++",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"crc-impl.h\"\n\n#include <array>\n#include <type_traits>\n\nnamespace {\nconstexpr auto crcTableSize = 256;\n\ntemplate <typename T>\nconcept Uint64OrUint32 = std::unsigned_integral<T> && (sizeof(T) == 8 || sizeof(T) == 4);\n\ntemplate <Uint64OrUint32 T>\nconstexpr T reverse(T value) {\n  if constexpr (sizeof(T) == 4) {\n    value = ((value & 0xaaaaaaaa) >> 1) | ((value & 0x55555555) << 1);\n    value = ((value & 0xcccccccc) >> 2) | ((value & 0x33333333) << 2);\n    value = ((value & 0xf0f0f0f0) >> 4) | ((value & 0x0f0f0f0f) << 4);\n    value = ((value & 0xff00ff00) >> 8) | ((value & 0x00ff00ff) << 8);\n    value = (value >> 16) | (value << 16);\n    return value;\n  } else {\n    value = ((value & 0xaaaaaaaaaaaaaaaa) >> 1) | ((value & 0x5555555555555555) << 1);\n    value = ((value & 0xcccccccccccccccc) >> 2) | ((value & 0x3333333333333333) << 2);\n    value = ((value & 0xf0f0f0f0f0f0f0f0) >> 4) | ((value & 0x0f0f0f0f0f0f0f0f) << 4);\n    value = ((value & 0xff00ff00ff00ff00) >> 8) | ((value & 0x00ff00ff00ff00ff) << 8);\n    value = ((value & 0xffff0000ffff0000) >> 16) | ((value & 0x0000ffff0000ffff) << 16);\n    value = (value >> 32) | (value << 32);\n    return value;\n  }\n}\n\ntemplate <Uint64OrUint32 T>\n// NOLINTNEXTLINE(edgeworker-multiple-bool-args)\nconstexpr std::array<T, crcTableSize> gen_crc_table(T polynomial, bool reflectIn, bool reflectOut) {\n  constexpr auto numIterations = sizeof(polynomial) * 8;  // number of bits in polynomial\n  auto crcTable = std::array<T, crcTableSize>{};\n\n  for (T byte = 0u; byte < crcTableSize; ++byte) {\n    T crc = (reflectIn ? (reverse(T(byte)) >> (numIterations - 8)) : byte);\n\n    for (int i = 0; i < numIterations; ++i) {\n      if (crc & (static_cast<T>(1) << (numIterations - 1))) {\n        crc = (crc << 1) ^ polynomial;\n      } else {\n        crc <<= 1;\n      }\n    }\n\n    crcTable[byte] = (reflectOut ? reverse(crc) : crc);\n  }\n\n  return crcTable;\n}\n\n#if !(__CRC32__ || __ARM_FEATURE_CRC32)\n// https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32-iscsi\nconstexpr auto crc32c_table = gen_crc_table(static_cast<uint32_t>(0x1edc6f41), true, true);\n#endif\n// https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-64-nvme\nconstexpr auto crc64nvme_table =\n    gen_crc_table(static_cast<uint64_t>(0xad93d23594c93659), true, true);\n}  // namespace\n\nuint32_t crc32c(uint32_t crc, const uint8_t *data, unsigned int length) {\n  if (data == nullptr) {\n    return 0;\n  }\n  crc ^= 0xffffffff;\n#if __CRC32__ || __ARM_FEATURE_CRC32\n  // Using hardware acceleration, process data in 8-byte chunks. Any remaining bytes are processed\n  // one-by-one in the main loop.\n  while (length >= 8) {\n    // 8-byte unaligned read\n    uint64_t val = *reinterpret_cast<const uint64_t *>(data);\n#if __ARM_FEATURE_CRC32\n    crc = __builtin_arm_crc32cd(crc, val);\n#else\n    crc = __builtin_ia32_crc32di(crc, val);\n#endif\n    length -= 8;\n    data += 8;\n  }\n#endif\n\n  while (length--) {\n#if __ARM_FEATURE_CRC32\n    crc = __builtin_arm_crc32cb(crc, *data++);\n#elif __CRC32__\n    crc = __builtin_ia32_crc32qi(crc, *data++);\n#else\n    crc = crc32c_table[(crc ^ *data++) & 0xffL] ^ (crc >> 8);\n#endif\n  }\n  return crc ^ 0xffffffff;\n}\n\nuint64_t crc64nvme(uint64_t crc, const uint8_t *data, unsigned int length) {\n  if (data == nullptr) {\n    return 0;\n  }\n  crc ^= 0xffffffffffffffff;\n  while (length--) {\n    crc = crc64nvme_table[(crc ^ *data++) & 0xffL] ^ (crc >> 8);\n  }\n  return crc ^ 0xffffffffffffffff;\n}\n"
  },
  {
    "path": "src/workerd/api/crypto/crc-impl.h",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n#include <stdint.h>\n\n// crc32-c implementation according to the spec:\n// https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32-iscsi\nuint32_t crc32c(uint32_t crc, const uint8_t *data, unsigned int length);\n\n// crc64-nvme implementation according to the spec:\n// https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-64-nvme\nuint64_t crc64nvme(uint64_t crc, const uint8_t *data, unsigned int length);\n"
  },
  {
    "path": "src/workerd/api/crypto/crypto.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"crypto.h\"\n\n#include \"impl.h\"\n\n#include <workerd/api/crypto/crc-impl.h>\n#include <workerd/api/crypto/endianness.h>\n#include <workerd/api/streams/standard.h>\n#include <workerd/api/util.h>\n#include <workerd/io/io-context.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/util/uuid.h>\n\n#include <openssl/digest.h>\n#include <openssl/mem.h>\n#include <zlib.h>\n\n#include <algorithm>\n#include <array>\n#include <limits>\n#include <set>\n#include <typeinfo>\n\nnamespace workerd::api {\n\nkj::StringPtr CryptoKeyUsageSet::name() const {\n  if (*this == encrypt()) return \"encrypt\";\n  if (*this == decrypt()) return \"decrypt\";\n  if (*this == sign()) return \"sign\";\n  if (*this == verify()) return \"verify\";\n  if (*this == deriveKey()) return \"deriveKey\";\n  if (*this == deriveBits()) return \"deriveBits\";\n  if (*this == wrapKey()) return \"wrapKey\";\n  if (*this == unwrapKey()) return \"unwrapKey\";\n  KJ_FAIL_REQUIRE(\"CryptoKeyUsageSet does not contain exactly one key usage\");\n}\n\nCryptoKeyUsageSet CryptoKeyUsageSet::byName(kj::StringPtr name) {\n  for (auto& usage: singletons()) {\n    if (name == usage.name()) return usage;\n  }\n  return {};\n}\n\nkj::ArrayPtr<const CryptoKeyUsageSet> CryptoKeyUsageSet::singletons() {\n  static const workerd::api::CryptoKeyUsageSet singletons[] = {\n    encrypt(), decrypt(), sign(), verify(), deriveKey(), deriveBits(), wrapKey(), unwrapKey()};\n  return singletons;\n}\n\nCryptoKeyUsageSet CryptoKeyUsageSet::validate(kj::StringPtr normalizedName,\n    Context ctx,\n    kj::ArrayPtr<const kj::String> actual,\n    CryptoKeyUsageSet mask) {\n  const auto op = (ctx == Context::generate) ? \"generate\"\n      : (ctx == Context::importSecret)       ? \"import secret\"\n      : (ctx == Context::importPublic)       ? \"import public\"\n                                             : \"import private\";\n  CryptoKeyUsageSet usages;\n  for (const auto& usage: actual) {\n    CryptoKeyUsageSet match = byName(usage);\n    JSG_REQUIRE(match.isSingleton() && match <= mask, DOMSyntaxError, \"Attempt to \", op, \" \",\n        normalizedName, \" key with invalid usage \\\"\", usage, \"\\\".\");\n    usages |= match;\n  }\n  return usages;\n}\n\nnamespace {\n\n// IMPLEMENTATION STRATEGY\n//\n// Each SubtleCrypto method is polymorphic, with different implementations selected based on the\n// `name` property of the Algorithm dictionary passed (or KeyAlgorithm dictionary of the CryptoKey\n// passed, in the case of subtle.exportKey()).\n//\n// This polymorphism is implemented in CryptoKey::Impl. All of the key-based crypto algorithm\n// operations (encrypt, decrypt, sign, verify, deriveBits, wrapKey, unwrapKey) are virtual functions\n// on CryptoKey::Impl -- SubtleCrypto forwards to CryptoKey which forwards to Impl.\n//\n// TODO(cleanup): We validate crypto algorithm/operation/key sanity in a preamble in the functions\n//   defined in the SubtleCrypto interface. This is because this whole thing was originally\n//   implemented differently and I haven't completed refactoring it. We should put this validation\n//   somewhere in CryptoKey, perhaps implicitly in the default implementations of the\n//   encrypt/decrypt/sign/verify/etc. functions.\n//\n// Note that SubtleCrypto.digest() is special. It is not a key-based operation and we only support\n// one hash family, SHA, so its implementation is non-virtual.\n//\n// NOTE(perf): The SubtleCrypto interface is asynchronous, but all of our implementations perform\n//   the crypto synchronously before returning. In theory, we could be performing bulk crypto in a\n//   separate thread, maybe improving performance. However, it's unclear what real use case would\n//   benefit from this. It's also unclear that we would want a single request to be able to use\n//   multiple cores -- certainly it would greatly complicate our implementation of request CPU\n//   limits. So, we probably shouldn't implement true asynchronous crypto.\n//\n//   Additionally, performing the crypto synchronously actually has a performance benefit: we can\n//   safely avoid copying input BufferSources -- most of our functions can take\n//   kj::ArrayPtr<const kj::byte>s, rather than kj::Array<kj::byte>s.\n\n// =======================================================================================\n// Registered algorithms\n\nstatic kj::Maybe<const CryptoAlgorithm&> lookupAlgorithm(kj::StringPtr name) {\n  static const std::set<CryptoAlgorithm> ALGORITHMS = {\n    {\"AES-CTR\"_kj, &CryptoKey::Impl::importAes, &CryptoKey::Impl::generateAes},\n    {\"AES-CBC\"_kj, &CryptoKey::Impl::importAes, &CryptoKey::Impl::generateAes},\n    {\"AES-GCM\"_kj, &CryptoKey::Impl::importAes, &CryptoKey::Impl::generateAes},\n    {\"AES-KW\"_kj, &CryptoKey::Impl::importAes, &CryptoKey::Impl::generateAes},\n    {\"HMAC\"_kj, &CryptoKey::Impl::importHmac, &CryptoKey::Impl::generateHmac},\n    {\"PBKDF2\"_kj, &CryptoKey::Impl::importPbkdf2},\n    {\"HKDF\"_kj, &CryptoKey::Impl::importHkdf},\n    {\"RSASSA-PKCS1-v1_5\"_kj, &CryptoKey::Impl::importRsa, &CryptoKey::Impl::generateRsa},\n    {\"RSA-PSS\"_kj, &CryptoKey::Impl::importRsa, &CryptoKey::Impl::generateRsa},\n    {\"RSA-OAEP\"_kj, &CryptoKey::Impl::importRsa, &CryptoKey::Impl::generateRsa},\n    {\"ECDSA\"_kj, &CryptoKey::Impl::importEcdsa, &CryptoKey::Impl::generateEcdsa},\n    {\"ECDH\"_kj, &CryptoKey::Impl::importEcdh, &CryptoKey::Impl::generateEcdh},\n    {\"NODE-ED25519\"_kj, &CryptoKey::Impl::importEddsa, &CryptoKey::Impl::generateEddsa},\n    {\"Ed25519\"_kj, &CryptoKey::Impl::importEddsa, &CryptoKey::Impl::generateEddsa},\n    {\"X25519\"_kj, &CryptoKey::Impl::importEddsa, &CryptoKey::Impl::generateEddsa},\n    {\"RSA-RAW\"_kj, &CryptoKey::Impl::importRsaRaw},\n  };\n\n  auto iter = ALGORITHMS.find(CryptoAlgorithm{name});\n  if (iter == ALGORITHMS.end()) {\n    // No such built-in algorithm, so fall back to checking if the Api has a custom\n    // algorithm registered.\n    return Worker::Api::current().getCryptoAlgorithm(name);\n  } else {\n    return *iter;\n  }\n}\n\n// =======================================================================================\n// Helper functions\n\n// Throws InvalidAccessError if the key is incompatible with the given normalized algorithm name,\n// or if it doesn't support the given usage.\nvoid validateOperation(const CryptoKey& key, kj::StringPtr requestedName, CryptoKeyUsageSet usage) {\n  // TODO(someday): Throw a NotSupportedError? The Web Crypto API spec says InvalidAccessError, but\n  //   Web IDL says that's deprecated.\n  //\n  // TODO(cleanup): Make this function go away. Maybe this can be rolled into the default\n  //   implementations of the CryptoKey::Impl::<crypto operation>() functions.\n\n  JSG_REQUIRE(strcasecmp(requestedName.cStr(), key.getAlgorithmName().cStr()) == 0,\n      DOMInvalidAccessError, \"Requested algorithm \\\"\", requestedName,\n      \"\\\" does not match this CryptoKey's algorithm \\\"\", key.getAlgorithmName(), \"\\\".\");\n  JSG_REQUIRE(usage <= key.getUsageSet(), DOMInvalidAccessError, \"Requested key usage \\\"\",\n      usage.name(), \"\\\" does not match any usage listed in this CryptoKey.\");\n}\n\n// Helper for `deriveKey()`. This private crypto operation is actually defined by the spec as\n// the \"get key length\" operation.\nkj::Maybe<uint32_t> getKeyLength(const SubtleCrypto::ImportKeyAlgorithm& derivedKeyAlgorithm) {\n\n  kj::StringPtr algName = derivedKeyAlgorithm.name;\n\n  // TODO(cleanup): This should be a method of CryptoKey::Impl so it can be abstracted. Currently\n  //   we ad-hoc match various algorithms below, so the set of supported algorithms must be\n  //   hard-coded.\n  static const std::set<kj::StringPtr, CiLess> registeredAlgorithms{\n    {\"AES-CTR\"},\n    {\"AES-CBC\"},\n    {\"AES-GCM\"},\n    {\"AES-KW\"},\n    {\"HMAC\"},\n    {\"HKDF\"},\n    {\"PBKDF2\"},\n  };\n  auto algIter = registeredAlgorithms.find(algName);\n  JSG_REQUIRE(algIter != registeredAlgorithms.end(), DOMNotSupportedError,\n      \"Unrecognized derived key type \\\"\", algName, \"\\\" requested.\");\n\n  // We could implement getKeyLength() with the same map-of-strings-to-implementation-functions\n  // strategy as the rest of the crypto operations, but this function is so simple that it hardly\n  // seems worth the bother. The spec only identifies three cases: the AES family, HMAC, and the KDF\n  // algorithms.\n  if (algIter->startsWith(\"AES-\")) {\n    int length = JSG_REQUIRE_NONNULL(\n        derivedKeyAlgorithm.length, TypeError, \"Missing field \\\"length\\\" in \\\"derivedKeyParams\\\".\");\n    switch (length) {\n      case 128:\n        [[fallthrough]];\n      case 192:\n        [[fallthrough]];\n      case 256:\n        break;\n      default:\n        JSG_FAIL_REQUIRE(DOMOperationError,\n            \"Derived AES key must be 128, 192, or 256 bits in length but provided \", length, \".\");\n    }\n    return length;\n  } else if (*algIter == \"HMAC\") {\n    KJ_IF_SOME(length, derivedKeyAlgorithm.length) {\n      // If the user requested a specific HMAC key length, honor it.\n      if (length > 0) {\n        return length;\n      }\n      JSG_FAIL_REQUIRE(TypeError, \"HMAC key length must be a non-zero unsigned long integer.\");\n    }\n    // Otherwise, assume the user wants the default HMAC key size.\n    auto digestAlg = getAlgorithmName(JSG_REQUIRE_NONNULL(\n        derivedKeyAlgorithm.hash, TypeError, \"Missing field \\\"hash\\\" in \\\"derivedKeyParams\\\".\"));\n    return EVP_MD_block_size(lookupDigestAlgorithm(digestAlg).second) * 8;\n  } else {\n    // HKDF or PBKDF2. I'm not not sure what it means to derive a HKDF/PBKDF2 key from a base key\n    // (are you deriving a password from a password?) but based on my reading of the spec, this code\n    // path will become meaningful once we support ECDH, which handles null-length deriveBits()\n    // operations. This is the entire reason getKeyLength() returns a Maybe<uint32_t> rather than a\n    // uint32_t (and also why we do not throw an OperationError here but rather later on in\n    // deriveBitsPbkdf2Impl()).\n    return kj::none;\n  }\n}\n\nauto webCryptoOperationBegin(\n    const char* operation, kj::StringPtr algorithm, kj::Maybe<kj::StringPtr> context = kj::none) {\n  // This clears all OpenSSL errors & errno at the start & returns a deferred evaluation to make\n  // sure that, when the WebCrypto entrypoint completes, there are no errors hanging around.\n  // Context is used for adding contextual information (e.g. the algorithm name of the key being\n  // wrapped, the import/export format being processed etc).\n  ERR_clear_error();\n  ERR_clear_system_error();\n\n  // Ok to capture pointers by value because this will only be used for the duration of the parent's\n  // scope which is passing in these arguments.\n  return kj::defer([=] {\n    if (ERR_peek_error() != 0) {\n      auto allErrors = KJ_MAP(e, consumeAllOpensslErrors()) {\n        KJ_SWITCH_ONEOF(e) {\n          KJ_CASE_ONEOF(friendly, kj::StringPtr) {\n            return kj::str(friendly);\n          }\n          KJ_CASE_ONEOF(raw, OpensslUntranslatedError) {\n            return kj::str(raw.library, \"::\", raw.reasonName);\n          }\n        }\n\n        KJ_UNREACHABLE;\n      };\n\n      kj::String stringifiedOperation;\n      KJ_IF_SOME(c, context) {\n        stringifiedOperation = kj::str(operation, \"(\", c, \")\");\n      } else {\n        stringifiedOperation = kj::str(operation);\n      }\n      KJ_LOG(WARNING, \"WebCrypto didn't handle all BoringSSL errors\", stringifiedOperation,\n          algorithm, allErrors);\n    }\n  });\n}\n\nauto webCryptoOperationBegin(\n    const char* operation, kj::StringPtr algorithm, const kj::String& context) {\n  return webCryptoOperationBegin(operation, algorithm, context.slice(0));\n}\n\ntemplate <typename T,\n    typename = kj::EnableIf<kj::isSameType<kj::String, decltype(kj::instance<T>().name)>()>>\n[[gnu::always_inline]] auto webCryptoOperationBegin(\n    const char* operation, const T& algorithm, kj::Maybe<kj::StringPtr> context = kj::none) {\n  return kj::defer([operation, algorithm = kj::str(algorithm.name), context] {\n    // We need a copy of the algorithm name as this defer runs after the EncryptAlgorithm struct\n    // is destroyed.\n    (void)webCryptoOperationBegin(operation, algorithm, context);\n  });\n}\n\n}  // namespace\n\n// =======================================================================================\n// CryptoKey / SubtleCrypto implementations\n\nCryptoKey::CryptoKey(kj::Own<Impl> impl): impl(kj::mv(impl)) {}\nCryptoKey::~CryptoKey() noexcept(false) {}\nkj::StringPtr CryptoKey::getAlgorithmName() const {\n  return impl->getAlgorithmName();\n}\nCryptoKey::AlgorithmVariant CryptoKey::getAlgorithm(jsg::Lock& js) const {\n  return impl->getAlgorithm(js);\n}\nkj::StringPtr CryptoKey::getType() const {\n  return impl->getType();\n}\nbool CryptoKey::getExtractable() const {\n  return impl->isExtractable();\n}\nkj::Array<kj::StringPtr> CryptoKey::getUsages() const {\n  return getUsageSet().map([](auto singleton) { return singleton.name(); });\n}\nCryptoKeyUsageSet CryptoKey::getUsageSet() const {\n  return impl->getUsages();\n}\n\nbool CryptoKey::operator==(const CryptoKey& other) const {\n  // We check this first because we don't want any comparison to happen if\n  // either key is not extractable, even if they are the same object.\n  if (!getExtractable() || !other.getExtractable()) {\n    return false;\n  }\n  return this == &other || (getType() == other.getType() && impl->equals(*other.impl));\n}\n\nCryptoKey::AsymmetricKeyDetails CryptoKey::getAsymmetricKeyDetails(jsg::Lock& js) const {\n  return impl->getAsymmetricKeyDetail(js);\n}\n\nbool CryptoKey::verifyX509Public(const X509* cert) const {\n  if (this->getType() != \"public\"_kj) return false;\n  return impl->verifyX509Public(cert);\n}\n\nbool CryptoKey::verifyX509Private(const X509* cert) const {\n  if (this->getType() != \"private\"_kj) return false;\n  return impl->verifyX509Private(cert);\n}\n\nvoid CryptoKey::visitForGc(jsg::GcVisitor& visitor) {\n  if (impl.get() == nullptr) return;\n  impl->visitForGc(visitor);\n}\n\njsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> SubtleCrypto::encrypt(jsg::Lock& js,\n    kj::OneOf<kj::String, EncryptAlgorithm> algorithmParam,\n    const CryptoKey& key,\n    kj::Array<const kj::byte> plainText) {\n  auto algorithm = interpretAlgorithmParam(kj::mv(algorithmParam));\n\n  auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);\n\n  return js.evalNow([&] {\n    validateOperation(key, algorithm.name, CryptoKeyUsageSet::encrypt());\n    return key.impl->encrypt(js, kj::mv(algorithm), plainText).addRef(js);\n  });\n}\n\njsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> SubtleCrypto::decrypt(jsg::Lock& js,\n    kj::OneOf<kj::String, EncryptAlgorithm> algorithmParam,\n    const CryptoKey& key,\n    kj::Array<const kj::byte> cipherText) {\n  auto algorithm = interpretAlgorithmParam(kj::mv(algorithmParam));\n\n  auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);\n\n  return js.evalNow([&] {\n    validateOperation(key, algorithm.name, CryptoKeyUsageSet::decrypt());\n    return key.impl->decrypt(js, kj::mv(algorithm), cipherText).addRef(js);\n  });\n}\n\njsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> SubtleCrypto::sign(jsg::Lock& js,\n    kj::OneOf<kj::String, SignAlgorithm> algorithmParam,\n    const CryptoKey& key,\n    kj::Array<const kj::byte> data) {\n  auto algorithm = interpretAlgorithmParam(kj::mv(algorithmParam));\n\n  auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);\n\n  return js.evalNow([&] {\n    validateOperation(key, algorithm.name, CryptoKeyUsageSet::sign());\n    return key.impl->sign(js, kj::mv(algorithm), data).addRef(js);\n  });\n}\n\njsg::Promise<bool> SubtleCrypto::verify(jsg::Lock& js,\n    kj::OneOf<kj::String, SignAlgorithm> algorithmParam,\n    const CryptoKey& key,\n    kj::Array<const kj::byte> signature,\n    kj::Array<const kj::byte> data) {\n  auto algorithm = interpretAlgorithmParam(kj::mv(algorithmParam));\n\n  auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);\n\n  return js.evalNow([&] {\n    validateOperation(key, algorithm.name, CryptoKeyUsageSet::verify());\n    return key.impl->verify(js, kj::mv(algorithm), signature, data);\n  });\n}\n\njsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> SubtleCrypto::digest(jsg::Lock& js,\n    kj::OneOf<kj::String, HashAlgorithm> algorithmParam,\n    kj::Array<const kj::byte> data) {\n  auto algorithm = interpretAlgorithmParam(kj::mv(algorithmParam));\n\n  auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);\n\n  return js.evalNow([&] {\n    auto type = lookupDigestAlgorithm(algorithm.name).second;\n\n    auto digestCtx = kj::disposeWith<EVP_MD_CTX_free>(EVP_MD_CTX_new());\n    KJ_ASSERT(digestCtx.get() != nullptr);\n\n    OSSLCALL(EVP_DigestInit_ex(digestCtx.get(), type, nullptr));\n    OSSLCALL(EVP_DigestUpdate(digestCtx.get(), data.begin(), data.size()));\n\n    auto buf = jsg::JsArrayBuffer::create(js, EVP_MD_CTX_size(digestCtx.get()));\n    uint messageDigestSize = 0;\n    OSSLCALL(EVP_DigestFinal_ex(digestCtx.get(), buf.asArrayPtr().begin(), &messageDigestSize));\n\n    KJ_ASSERT(messageDigestSize == buf.size());\n    return buf.addRef(js);\n  });\n}\n\njsg::Promise<kj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair>> SubtleCrypto::generateKey(jsg::Lock& js,\n    kj::OneOf<kj::String, GenerateKeyAlgorithm> algorithmParam,\n    bool extractable,\n    kj::Array<kj::String> keyUsages) {\n\n  auto algorithm = interpretAlgorithmParam(kj::mv(algorithmParam));\n\n  auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);\n\n  return js.evalNow([&] {\n    CryptoAlgorithm algoImpl = lookupAlgorithm(algorithm.name).orDefault({});\n    JSG_REQUIRE(algoImpl.generateFunc != nullptr, DOMNotSupportedError,\n        \"Unrecognized key generation algorithm \\\"\", algorithm.name, \"\\\" requested.\");\n\n    auto cryptoKeyOrPair =\n        algoImpl.generateFunc(js, algoImpl.name, kj::mv(algorithm), extractable, keyUsages);\n    KJ_SWITCH_ONEOF(cryptoKeyOrPair) {\n      KJ_CASE_ONEOF(cryptoKey, jsg::Ref<CryptoKey>) {\n        if (keyUsages.size() == 0) {\n          auto type = cryptoKey->getType();\n          JSG_REQUIRE(type != \"secret\" && type != \"private\", DOMSyntaxError,\n              \"Secret/private CryptoKeys must have at least one usage.\");\n        }\n      }\n      KJ_CASE_ONEOF(keyPair, CryptoKeyPair) {\n        JSG_REQUIRE(keyPair.privateKey->getUsageSet().size() != 0, DOMSyntaxError,\n            \"Attempt to generate asymmetric keys with no valid private key usages.\");\n      }\n    }\n    return cryptoKeyOrPair;\n  });\n}\n\njsg::Promise<jsg::Ref<CryptoKey>> SubtleCrypto::deriveKey(jsg::Lock& js,\n    kj::OneOf<kj::String, DeriveKeyAlgorithm> algorithmParam,\n    const CryptoKey& baseKey,\n    kj::OneOf<kj::String, ImportKeyAlgorithm> derivedKeyAlgorithmParam,\n    bool extractable,\n    kj::Array<kj::String> keyUsages) {\n  auto algorithm = interpretAlgorithmParam(kj::mv(algorithmParam));\n  auto derivedKeyAlgorithm = interpretAlgorithmParam(kj::mv(derivedKeyAlgorithmParam));\n\n  auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);\n\n  return js.evalNow([&] {\n    validateOperation(baseKey, algorithm.name, CryptoKeyUsageSet::deriveKey());\n\n    auto length = getKeyLength(derivedKeyAlgorithm);\n\n    auto secret = baseKey.impl->deriveBits(js, kj::mv(algorithm), length);\n\n    // TODO(perf): For conformance, importKey() makes a copy of `secret`. In this case we really\n    //   don't need to, but rather we ought to call the appropriate CryptoKey::Impl::import*()\n    //   function directly.\n    return importKeySync(\n        js, \"raw\", secret.copy(), kj::mv(derivedKeyAlgorithm), extractable, kj::mv(keyUsages));\n  });\n}\n\njsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> SubtleCrypto::deriveBits(jsg::Lock& js,\n    kj::OneOf<kj::String, DeriveKeyAlgorithm> algorithmParam,\n    const CryptoKey& baseKey,\n    jsg::Optional<kj::Maybe<int>> lengthParam) {\n  auto algorithm = interpretAlgorithmParam(kj::mv(algorithmParam));\n\n  auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);\n\n  kj::Maybe<uint32_t> length = kj::none;\n  KJ_IF_SOME(maybeLength, lengthParam) {\n    KJ_IF_SOME(l, maybeLength) {\n      JSG_REQUIRE(l >= 0, TypeError, \"deriveBits length must be an unsigned long integer.\");\n      length = static_cast<uint32_t>(l);\n    }\n  }\n\n  return js.evalNow([&] {\n    validateOperation(baseKey, algorithm.name, CryptoKeyUsageSet::deriveBits());\n    return baseKey.impl->deriveBits(js, kj::mv(algorithm), length).addRef(js);\n  });\n}\n\njsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> SubtleCrypto::wrapKey(jsg::Lock& js,\n    kj::String format,\n    const CryptoKey& key,\n    const CryptoKey& wrappingKey,\n    kj::OneOf<kj::String, EncryptAlgorithm> wrapAlgorithm,\n    const jsg::TypeHandler<JsonWebKey>& jwkHandler) {\n  auto checkErrorsOnFinish =\n      webCryptoOperationBegin(__func__, wrappingKey.getAlgorithmName(), key.getAlgorithmName());\n\n  return js.evalNow([&] {\n    auto algorithm = interpretAlgorithmParam(kj::mv(wrapAlgorithm));\n\n    validateOperation(wrappingKey, algorithm.name, CryptoKeyUsageSet::wrapKey());\n\n    struct PaddingDetails {\n      kj::byte byteAlignment;\n      size_t minimumLength;\n    };\n\n    JSG_REQUIRE(key.getExtractable(), DOMInvalidAccessError, \"Attempt to export non-extractable \",\n        key.getAlgorithmName(), \" key.\");\n\n    auto exportedKey = key.impl->exportKey(js, kj::mv(format));\n\n    KJ_SWITCH_ONEOF(exportedKey) {\n      KJ_CASE_ONEOF(k, jsg::JsRef<jsg::JsArrayBuffer>) {\n        auto handle = k.getHandle(js);\n        return wrappingKey.impl->wrapKey(js, kj::mv(algorithm), handle.asArrayPtr().asConst())\n            .addRef(js);\n      }\n      KJ_CASE_ONEOF(jwk, JsonWebKey) {\n        auto stringified = js.serializeJson(jwkHandler.wrap(js, kj::mv(jwk)));\n        return wrappingKey.impl->wrapKey(js, kj::mv(algorithm), stringified.asBytes().asConst())\n            .addRef(js);\n      }\n    }\n\n    KJ_UNREACHABLE;\n  });\n}\n\njsg::Promise<jsg::Ref<CryptoKey>> SubtleCrypto::unwrapKey(jsg::Lock& js,\n    kj::String format,\n    kj::Array<const kj::byte> wrappedKey,\n    const CryptoKey& unwrappingKey,\n    kj::OneOf<kj::String, EncryptAlgorithm> unwrapAlgorithm,\n    kj::OneOf<kj::String, ImportKeyAlgorithm> unwrappedKeyAlgorithm,\n    bool extractable,\n    kj::Array<kj::String> keyUsages,\n    const jsg::TypeHandler<JsonWebKey>& jwkHandler) {\n  auto operation = __func__;\n  return js.evalNow([&]() -> jsg::Ref<CryptoKey> {\n    auto normalizedAlgorithm = interpretAlgorithmParam(kj::mv(unwrapAlgorithm));\n    auto normalizedUnwrapAlgorithm = interpretAlgorithmParam(kj::mv(unwrappedKeyAlgorithm));\n\n    // Need a copy of the algorithm name to live in this scope, because we later kj::mv() it out.\n    auto context = kj::str(normalizedUnwrapAlgorithm.name);\n    auto checkErrorsOnFinish =\n        webCryptoOperationBegin(operation, unwrappingKey.getAlgorithmName(), context);\n\n    validateOperation(unwrappingKey, normalizedAlgorithm.name, CryptoKeyUsageSet::unwrapKey());\n\n    auto bytes = unwrappingKey.impl->unwrapKey(js, kj::mv(normalizedAlgorithm), wrappedKey);\n\n    ImportKeyData importData;\n\n    if (format == \"jwk\") {\n      auto jwkDict = js.parseJson(bytes.asArrayPtr().asChars());\n\n      importData = JSG_REQUIRE_NONNULL(jwkHandler.tryUnwrap(js, jwkDict.getHandle(js)),\n          DOMDataError, \"Missing \\\"kty\\\" field or corrupt JSON unwrapping key?\");\n    } else {\n      importData = bytes.copy();\n    }\n\n    auto imported = importKeySync(js, format, kj::mv(importData), kj::mv(normalizedUnwrapAlgorithm),\n        extractable, keyUsages.asPtr());\n\n    if (imported->getType() == \"secret\" || imported->getType() == \"private\") {\n      JSG_REQUIRE(imported->getUsageSet().size() != 0, DOMSyntaxError,\n          \"Secret/private CryptoKeys must have at least one usage.\");\n    }\n\n    return imported;\n  });\n}\n\njsg::Promise<jsg::Ref<CryptoKey>> SubtleCrypto::importKey(jsg::Lock& js,\n    kj::String format,\n    ImportKeyData keyData,\n    kj::OneOf<kj::String, ImportKeyAlgorithm> algorithmParam,\n    bool extractable,\n    kj::Array<kj::String> keyUsages) {\n  auto algorithm = interpretAlgorithmParam(kj::mv(algorithmParam));\n\n  auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm, format.asPtr());\n\n  return js.evalNow([&] {\n    return importKeySync(js, format, kj::mv(keyData), kj::mv(algorithm), extractable, keyUsages);\n  });\n}\n\njsg::Ref<CryptoKey> SubtleCrypto::importKeySync(jsg::Lock& js,\n    kj::StringPtr format,\n    ImportKeyData keyData,\n    ImportKeyAlgorithm algorithm,\n    bool extractable,\n    kj::ArrayPtr<const kj::String> keyUsages) {\n  if (format == \"raw\" || format == \"pkcs8\" || format == \"spki\") {\n    auto& key = JSG_REQUIRE_NONNULL(keyData.tryGet<kj::Array<kj::byte>>(), TypeError,\n        \"Import data provided for \\\"raw\\\", \\\"pkcs8\\\", or \\\"spki\\\" import formats must be a buffer \"\n        \"source.\");\n\n    // Make a copy of the key import data.\n    keyData = kj::heapArray(key.asPtr());\n  } else if (format == \"jwk\") {\n    JSG_REQUIRE(keyData.is<JsonWebKey>(), TypeError,\n        \"Import data provided for \\\"jwk\\\" import format must be a JsonWebKey.\");\n    KJ_IF_SOME(ext, keyData.get<JsonWebKey>().ext) {\n      JSG_REQUIRE(ext || !extractable, DOMDataError, \"JWK ext field for \\\"\", algorithm.name,\n          \"\\\" is set to false but \"\n          \"extractable is true\");\n    }\n  } else {\n    // Not prescribed by the spec here, but we might as well bail out here by return. Otherwise,\n    // the import function implementations will eventually result in this error.\n    JSG_FAIL_REQUIRE(DOMNotSupportedError, \"Unrecognized key import format \\\"\", format, \"\\\".\");\n  }\n\n  CryptoAlgorithm algoImpl = lookupAlgorithm(algorithm.name).orDefault({});\n  JSG_REQUIRE(algoImpl.importFunc != nullptr, DOMNotSupportedError,\n      \"Unrecognized key import algorithm \\\"\", algorithm.name, \"\\\" requested.\");\n\n  // Note: we pass in the algorithm name (algoImpl.name) because we know it is uppercase, which\n  //   the `name` member of the `algorithm` value itself is not required to be. The individual\n  //   implementation functions don't necessarily know the name of the algorithm whose key they're\n  //   importing (importKeyAesImpl handles AES-CTR, -CBC, and -GCM, for instance), so they should\n  //   rely on this value to set the imported CryptoKey's name.\n  auto cryptoKey = js.alloc<CryptoKey>(algoImpl.importFunc(\n      js, algoImpl.name, format, kj::mv(keyData), kj::mv(algorithm), extractable, keyUsages));\n\n  if (cryptoKey->getUsageSet().size() == 0) {\n    auto type = cryptoKey->getType();\n    JSG_REQUIRE(type != \"secret\" && type != \"private\", DOMSyntaxError,\n        \"Secret/private CryptoKeys must have at least one usage.\");\n  }\n\n  return cryptoKey;\n}\n\njsg::Promise<SubtleCrypto::ExportKeyData> SubtleCrypto::exportKey(\n    jsg::Lock& js, kj::String format, const CryptoKey& key) {\n  auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, key.getAlgorithmName());\n\n  return js.evalNow([&] {\n    // TODO(someday): Throw a NotSupportedError? The Web Crypto API spec says InvalidAccessError,\n    //   but Web IDL says that's deprecated.\n    JSG_REQUIRE(key.getExtractable(), DOMInvalidAccessError, \"Attempt to export non-extractable \",\n        key.getAlgorithmName(), \" key.\");\n\n    return key.impl->exportKey(js, format);\n  });\n}\n\nbool SubtleCrypto::timingSafeEqual(jsg::JsBufferSource a, jsg::JsBufferSource b) {\n  JSG_REQUIRE(a.size() == b.size(), TypeError, \"Input buffers must have the same byte length.\");\n\n  // The implementation here depends entirely on the characteristics of the CRYPTO_memcmp\n  // implementation. We do not perform any additional verification that the operation is\n  // actually timing safe other than checking the input types and lengths.\n\n  return CRYPTO_memcmp(a.asArrayPtr().begin(), b.asArrayPtr().begin(), a.size()) == 0;\n}\n\n// =======================================================================================\n// Crypto implementation\n\njsg::JsArrayBufferView Crypto::getRandomValues(jsg::JsArrayBufferView buffer) {\n  // NOTE: TypeMismatchError is deprecated (obviated by TypeError), but the spec and W3C tests still\n  //   expect a TypeMismatchError here.\n  JSG_REQUIRE(buffer.isIntegerType(), DOMTypeMismatchError,\n      \"ArrayBufferView argument to getRandomValues() must be an integer-typed view.\");\n  JSG_REQUIRE(buffer.size() <= 0x10000, DOMQuotaExceededError,\n      \"getRandomValues() only accepts buffers of size <= 64K but provided \", buffer.size(),\n      \" bytes.\");\n  IoContext::current().getEntropySource().generate(buffer.asArrayPtr());\n  return buffer;\n}\n\nkj::String Crypto::randomUUID() {\n  return ::workerd::randomUUID(IoContext::current().getEntropySource());\n}\n\n// =======================================================================================\n// Crypto Streams implementation\n\nclass CRC32DigestContext final: public DigestContext {\n public:\n  CRC32DigestContext(): value(crc32(0, Z_NULL, 0)) {}\n  ~CRC32DigestContext() noexcept override = default;\n\n  void write(kj::ArrayPtr<kj::byte> buffer) override {\n    value = crc32(value, buffer.begin(), buffer.size());\n  }\n\n  jsg::JsArrayBuffer close(jsg::Lock& js) override {\n    auto beValue = htobe32(value);\n    static_assert(sizeof(value) == sizeof(beValue), \"CRC32 digest is not 32 bits?\");\n    kj::ArrayPtr<kj::byte> be(reinterpret_cast<kj::byte*>(&beValue), sizeof(beValue));\n    return jsg::JsArrayBuffer::create(js, be);\n  }\n\n private:\n  uint32_t value;\n};\n\nclass CRC32CDigestContext final: public DigestContext {\n public:\n  CRC32CDigestContext(): value(crc32c(0, nullptr, 0)) {}\n  ~CRC32CDigestContext() noexcept override = default;\n\n  void write(kj::ArrayPtr<kj::byte> buffer) override {\n    value = crc32c(value, buffer.begin(), buffer.size());\n  }\n\n  jsg::JsArrayBuffer close(jsg::Lock& js) override {\n    auto beValue = htobe32(value);\n    static_assert(sizeof(value) == sizeof(beValue), \"CRC32 digest is not 32 bits?\");\n    kj::ArrayPtr<kj::byte> be(reinterpret_cast<kj::byte*>(&beValue), sizeof(beValue));\n    return jsg::JsArrayBuffer::create(js, be);\n  }\n\n private:\n  uint32_t value;\n};\n\nclass CRC64NVMEDigestContext final: public DigestContext {\n public:\n  CRC64NVMEDigestContext(): value(crc64nvme(0, nullptr, 0)) {}\n  ~CRC64NVMEDigestContext() noexcept override = default;\n\n  void write(kj::ArrayPtr<kj::byte> buffer) override {\n    value = crc64nvme(value, buffer.begin(), buffer.size());\n  }\n\n  jsg::JsArrayBuffer close(jsg::Lock& js) override {\n    auto beValue = htobe64(value);\n    static_assert(sizeof(value) == sizeof(beValue), \"CRC64 digest is not 64 bits?\");\n    kj::ArrayPtr<kj::byte> be(reinterpret_cast<kj::byte*>(&beValue), sizeof(beValue));\n    return jsg::JsArrayBuffer::create(js, be);\n  }\n\n private:\n  uint64_t value;\n};\n\nclass OpenSSLDigestContext final: public DigestContext {\n public:\n  OpenSSLDigestContext(kj::StringPtr algorithm): algorithm(kj::str(algorithm)) {\n    auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);\n    auto type = lookupDigestAlgorithm(algorithm).second;\n    auto opensslContext = kj::disposeWith<EVP_MD_CTX_free>(EVP_MD_CTX_new());\n    KJ_ASSERT(opensslContext.get() != nullptr);\n    OSSLCALL(EVP_DigestInit_ex(opensslContext.get(), type, nullptr));\n    context = kj::mv(opensslContext);\n  }\n  ~OpenSSLDigestContext() noexcept override = default;\n\n  void write(kj::ArrayPtr<kj::byte> buffer) override {\n    auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);\n    OSSLCALL(EVP_DigestUpdate(context.get(), buffer.begin(), buffer.size()));\n  }\n\n  jsg::JsArrayBuffer close(jsg::Lock& js) override {\n    auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);\n    uint size = 0;\n    auto buf = jsg::JsArrayBuffer::create(js, EVP_MD_CTX_size(context.get()));\n    OSSLCALL(EVP_DigestFinal_ex(context.get(), buf.asArrayPtr().begin(), &size));\n    KJ_ASSERT(size == buf.size());\n    return buf;\n  }\n\n private:\n  kj::String algorithm;\n  kj::Own<EVP_MD_CTX> context;\n};\n\nDigestStream::DigestContextPtr DigestStream::initContext(SubtleCrypto::HashAlgorithm& algorithm) {\n  if (algorithm.name == \"crc32\") {\n    return kj::heap<CRC32DigestContext>();\n  } else if (algorithm.name == \"crc32c\") {\n    return kj::heap<CRC32CDigestContext>();\n  } else if (algorithm.name == \"crc64nvme\") {\n    return kj::heap<CRC64NVMEDigestContext>();\n  } else {\n    return kj::heap<OpenSSLDigestContext>(algorithm.name);\n  }\n}\n\nDigestStream::DigestStream(kj::Own<WritableStreamController> controller,\n    SubtleCrypto::HashAlgorithm algorithm,\n    jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>>::Resolver resolver,\n    jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> promise)\n    : WritableStream(kj::mv(controller)),\n      promise(kj::mv(promise)),\n      state(Ready(kj::mv(algorithm), kj::mv(resolver))) {}\n\nvoid DigestStream::dispose(jsg::Lock& js) {\n  JSG_TRY(js) {\n    KJ_IF_SOME(ready, state.tryGet<Ready>()) {\n      auto reason = js.typeError(\"The DigestStream was disposed.\");\n      ready.resolver.reject(js, reason);\n      state.init<StreamStates::Errored>(js.v8Ref<v8::Value>(reason));\n    }\n  }\n  JSG_CATCH(exception) {\n    js.throwException(kj::mv(exception));\n  }\n}\n\nvoid DigestStream::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"promise\", promise);\n  KJ_IF_SOME(ready, state.tryGet<Ready>()) {\n    tracker.trackField(\"resolver\", ready.resolver);\n  }\n}\n\nvoid DigestStream::visitForGc(jsg::GcVisitor& visitor) {\n  visitor.visit(promise);\n  KJ_IF_SOME(ready, state.tryGet<Ready>()) {\n    visitor.visit(ready.resolver);\n  }\n}\n\nkj::Maybe<StreamStates::Errored> DigestStream::write(jsg::Lock& js, kj::ArrayPtr<kj::byte> buffer) {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      return kj::none;\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return errored.addRef(js);\n    }\n    KJ_CASE_ONEOF(ready, Ready) {\n      ready.context->write(buffer);\n      return kj::none;\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::Maybe<StreamStates::Errored> DigestStream::close(jsg::Lock& js) {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      return kj::none;\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return errored.addRef(js);\n    }\n    KJ_CASE_ONEOF(ready, Ready) {\n      ready.resolver.resolve(js, ready.context->close(js).addRef(js));\n      state.init<StreamStates::Closed>();\n      return kj::none;\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid DigestStream::abort(jsg::Lock& js, jsg::JsValue reason) {\n  // If the state is already closed or errored, then this is a non-op\n  KJ_IF_SOME(ready, state.tryGet<Ready>()) {\n    ready.resolver.reject(js, reason);\n    state.init<StreamStates::Errored>(js.v8Ref<v8::Value>(reason));\n  }\n}\n\njsg::Ref<DigestStream> DigestStream::constructor(jsg::Lock& js, Algorithm algorithm) {\n  auto paf = js.newPromiseAndResolver<jsg::JsRef<jsg::JsArrayBuffer>>();\n\n  auto stream = js.alloc<DigestStream>(newWritableStreamJsController(),\n      interpretAlgorithmParam(kj::mv(algorithm)), kj::mv(paf.resolver), kj::mv(paf.promise));\n\n  // clang-format off\n  stream->getController().setup(js, UnderlyingSink{\n    .write = [&stream = *stream](jsg::Lock& js, v8::Local<v8::Value> chunk, auto c) mutable {\n      return js.tryCatch([&] {\n        // Make sure what we got can be interpreted as bytes...\n        if (chunk->IsArrayBuffer() || chunk->IsArrayBufferView()) {\n          jsg::JsBufferSource source(chunk);\n          if (source.size() == 0) return js.resolvedPromise();\n\n          KJ_IF_SOME(error, stream.write(js, source.asArrayPtr())) {\n            return js.rejectedPromise<void>(kj::mv(error));\n          } else {\n          }  // Here to silence a compiler warning\n          stream.bytesWritten += source.size();\n          return js.resolvedPromise();\n        } else if (chunk->IsString()) {\n          // If we receive a string, we'll convert that to UTF-8 bytes and digest that.\n          auto str = js.toString(chunk);\n          if (str.size() == 0) return js.resolvedPromise();\n          KJ_IF_SOME(error, stream.write(js, str.asBytes())) {\n            return js.rejectedPromise<void>(kj::mv(error));\n          }\n          stream.bytesWritten += str.size();\n          return js.resolvedPromise();\n        }\n        return js.rejectedPromise<void>(\n            js.typeError(\"DigestStream is a byte stream but received an object of \"\n                        \"non-ArrayBuffer/ArrayBufferView/string type on its writable side.\"));\n      }, [&](jsg::Value exception) { return js.rejectedPromise<void>(kj::mv(exception)); });\n    },\n    .abort = [&stream = *stream](jsg::Lock& js, auto reason) mutable {\n      return js.tryCatch([&] {\n        stream.abort(js, jsg::JsValue(reason));\n        return js.resolvedPromise();\n      }, [&](jsg::Value exception) { return js.rejectedPromise<void>(kj::mv(exception)); });\n    },\n    .close = [&stream = *stream](jsg::Lock& js) mutable {\n      return js.tryCatch([&] {\n        // If sink.close returns a non kj::none value, that means the sink was errored\n        // and we return a rejected promise here. Otherwise, we return resolved.\n        KJ_IF_SOME(error, stream.close(js)) {\n          return js.rejectedPromise<void>(kj::mv(error));\n        } else {\n        }  // Here to silence a compiler warning\n        return js.resolvedPromise();\n      }, [&](jsg::Value exception) { return js.rejectedPromise<void>(kj::mv(exception)); });\n    }\n  }, kj::none);\n  // clang-format on\n\n  return kj::mv(stream);\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/crypto.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// WebCrypto API\n\n#include <workerd/api/streams/writable.h>\n#include <workerd/io/features.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/jsvalue.h>\n\n#include <openssl/base.h>  // for EVP_MD_CTX, X509\n\n#include <bit>\n\nnamespace workerd::api {\nnamespace node {\nclass CryptoImpl;\n}\nnamespace {\nclass EdDsaKey;\nclass EllipticKey;\n}  // namespace\n\n// Subset of recognized key usage values.\n//\n// https://w3c.github.io/webcrypto/#dfn-RecognizedKeyUsage\nclass CryptoKeyUsageSet {\n public:\n  static constexpr CryptoKeyUsageSet encrypt() {\n    return 1 << 0;\n  }\n  static constexpr CryptoKeyUsageSet decrypt() {\n    return 1 << 1;\n  }\n  static constexpr CryptoKeyUsageSet sign() {\n    return 1 << 2;\n  }\n  static constexpr CryptoKeyUsageSet verify() {\n    return 1 << 3;\n  }\n  static constexpr CryptoKeyUsageSet deriveKey() {\n    return 1 << 4;\n  }\n  static constexpr CryptoKeyUsageSet deriveBits() {\n    return 1 << 5;\n  }\n  static constexpr CryptoKeyUsageSet wrapKey() {\n    return 1 << 6;\n  }\n  static constexpr CryptoKeyUsageSet unwrapKey() {\n    return 1 << 7;\n  }\n\n  static constexpr CryptoKeyUsageSet publicKeyMask() {\n    return encrypt() | verify() | wrapKey();\n  }\n\n  static constexpr CryptoKeyUsageSet privateKeyMask() {\n    return decrypt() | sign() | unwrapKey() | deriveKey() | deriveBits();\n  }\n\n  static constexpr CryptoKeyUsageSet derivationKeyMask() {\n    return deriveKey() | deriveBits();\n  }\n\n  CryptoKeyUsageSet(): set(0) {}\n\n  CryptoKeyUsageSet operator&(CryptoKeyUsageSet other) const {\n    return set & other.set;\n  }\n  CryptoKeyUsageSet operator|(CryptoKeyUsageSet other) const {\n    return set | other.set;\n  }\n\n  CryptoKeyUsageSet& operator&=(CryptoKeyUsageSet other) {\n    set &= other.set;\n    return *this;\n  }\n\n  CryptoKeyUsageSet& operator|=(CryptoKeyUsageSet other) {\n    set |= other.set;\n    return *this;\n  }\n\n  // True if and only if this is a subset of the given set.\n  inline bool operator<=(CryptoKeyUsageSet superset) const {\n    return (superset & *this) == *this;\n  }\n\n  inline bool operator==(CryptoKeyUsageSet other) const {\n    return set == other.set;\n  }\n\n  unsigned int size() const {\n    return std::popcount(set);\n  }\n  bool isSingleton() const {\n    return size() == 1;\n  }\n\n  // The recognized name. This must be a singleton.\n  kj::StringPtr name() const;\n\n  // A singleton with the given name.\n  static CryptoKeyUsageSet byName(kj::StringPtr name);\n\n  // All singletons, in the order defined by the spec (encrypt, decrypt, sign, verify, ...).\n  static kj::ArrayPtr<const CryptoKeyUsageSet> singletons();\n\n  enum class Context { generate, importSecret, importPublic, importPrivate };\n\n  // Parses a list of key usage strings. Throws if any are not recognized or not in mask.\n  static CryptoKeyUsageSet validate(kj::StringPtr normalizedName,\n      Context ctx,\n      kj::ArrayPtr<const kj::String> actual,\n      CryptoKeyUsageSet mask);\n\n  template <typename Func>\n  auto map(Func f) const -> kj::Array<decltype(f(*this))> {\n    auto strings = kj::heapArrayBuilder<decltype(f(*this))>(size());\n    for (auto& singleton: singletons()) {\n      if (singleton <= *this) strings.add(f(singleton));\n    }\n    return strings.finish();\n  }\n\n private:\n  constexpr CryptoKeyUsageSet(uint8_t set): set(set) {}\n  uint8_t set;\n};\n\n// =======================================================================================\n// SubtleCrypto and CryptoKey\n\n// Represents keying material. Users get an object of this type by calling SubtleCrypto's\n// `importKey()`, `generateKey()`, or `deriveKey()` methods. The user can then use the object by\n// passing it as a parameter to other SubtleCrypto methods.\nclass CryptoKey: public jsg::Object {\n public:\n  // KeyAlgorithm dictionaries\n  //\n  // These dictionaries implement CryptoKey's `algorithm` property. They allow user code to inspect\n  // which algorithm a particular CryptoKey is used for, and what algorithm-specific parameters it\n  // might have. These are similar to the Algorithm-derived dictionaries used as parameters to\n  // SubtleCrypto's interface (see the SubtleCrypto class below), but they are specific to\n  // CryptoKey. Like Algorithm, all of these dictionaries notionally derive from a KeyAlgorithm base\n  // class.\n  //\n  // One difference between CryptoKey::KeyAlgorithm dictionaries and SubtleCrypto::Algorithm\n  // dictionaries is that KeyAlgorithms use a kj::StringPtr to store their algorithm names, because\n  // we know that they will only ever point to internal static strings of normalized algorithm\n  // names.\n\n  struct KeyAlgorithm {\n    kj::StringPtr name;\n    JSG_STRUCT(name);\n    JSG_MEMORY_INFO(KeyAlgorithm) {}\n  };\n\n  struct AesKeyAlgorithm {\n    // \"AES-CTR\", \"AES-GCM\", \"AES-CBC\", \"AES-KW\"\n    kj::StringPtr name;\n\n    // Length in bits of the key.\n    uint16_t length;\n\n    JSG_STRUCT(name, length);\n\n    JSG_MEMORY_INFO(AesKeyAlgorithm) {}\n  };\n\n  struct HmacKeyAlgorithm {\n    // \"HMAC\"\n    kj::StringPtr name;\n\n    // The inner hash function to use.\n    KeyAlgorithm hash;\n\n    // Length in bits of the key. The spec wants this to be an unsigned long, but whatever.\n    // TODO(someday): Reexamine use of uint16_t in these algorithm structures.\n    // We picked uint16_t to work around ambiguous bindings for uint32_t in\n    // jsg::PrimitiveWrapper::wrap().  HMAC, at least, allows very long keys.\n    uint16_t length;\n\n    JSG_STRUCT(name, hash, length);\n    JSG_MEMORY_INFO(HmacKeyAlgorithm) {}\n  };\n\n  struct RsaKeyAlgorithm {\n    // \"RSASSA-PKCS1-v1_5\", \"RSA-PSS\", \"RSA-OAEP\"\n    kj::StringPtr name;\n\n    // The length, in bits, of the RSA modulus. The spec would have this be an unsigned long.\n    uint16_t modulusLength;\n\n    // The RSA public exponent (in unsigned big-endian form)\n    jsg::JsRef<jsg::JsBufferSource> publicExponent;\n\n    // The hash algorithm that is used with this key.\n    jsg::Optional<KeyAlgorithm> hash;\n\n    RsaKeyAlgorithm clone(jsg::Lock& js) const {\n      auto pe = publicExponent.getHandle(js);\n      auto data = pe.asArrayPtr();\n      // Should only happen if the flag is enabled and an algorithm field is cloned twice.\n      if (FeatureFlags::get(js).getCryptoPreservePublicExponent()) {\n        auto exp = jsg::JsUint8Array::create(js, data);\n        return {name, modulusLength, jsg::JsBufferSource(exp).addRef(js), hash};\n      } else {\n        auto exp = jsg::JsArrayBuffer::create(js, data);\n        return {name, modulusLength, jsg::JsBufferSource(exp).addRef(js), hash};\n      }\n    }\n\n    JSG_STRUCT(name, modulusLength, publicExponent, hash);\n\n    JSG_MEMORY_INFO(RsaKeyAlgorithm) {}\n  };\n\n  struct EllipticKeyAlgorithm {\n    // \"ECDSA\" or \"ECDH\"\n    kj::StringPtr name;\n\n    // \"P-256\", \"P-384\", or \"P-521\"\n    kj::StringPtr namedCurve;\n\n    JSG_STRUCT(name, namedCurve);\n\n    JSG_MEMORY_INFO(EllipticKeyAlgorithm) {}\n  };\n\n  // Catch-all that can be used for extension algorithms. Combines fields of several known types.\n  struct ArbitraryKeyAlgorithm {\n    // TODO(cleanup): Should we just replace AlgorithmVariant with this? Note we'd have to add\n    //   `publicExponent` which is currently a problem because it makes the type non-copyable...\n    //   Alternatively, should we create some better way to abstract this?\n\n    kj::StringPtr name;\n    jsg::Optional<KeyAlgorithm> hash;\n    jsg::Optional<kj::StringPtr> namedCurve;\n    jsg::Optional<uint16_t> length;\n\n    JSG_STRUCT(name, hash, namedCurve, length);\n  };\n\n  // Used as part of the Node.js crypto implementation of KeyObject.\n  // Defined here instead of api/node/crypto.h because it it is needed\n  // by CryptoKey::Impl to provide the actual implementation.\n  struct AsymmetricKeyDetails {\n    jsg::Optional<uint32_t> modulusLength;\n    jsg::Optional<jsg::JsRef<jsg::JsArrayBuffer>> publicExponent;\n    // TODO(later): BoringSSL does not currently support getting the RSA-PSS\n    // details for an RSA key. Once it does, we can update our impl and add\n    // these fields.\n    // jsg::Optional<kj::String> hashAlgorithm;\n    // jsg::Optional<kj::String> mgf1HashAlgorithm;\n    // jsg::Optional<uint32_t> saltLength;\n    jsg::Optional<uint32_t> divisorLength;\n    jsg::Optional<kj::String> namedCurve;\n    JSG_STRUCT(modulusLength,\n        publicExponent,\n        // hashAlgorithm,\n        // mgf1HashAlgorithm,\n        // saltLength,\n        divisorLength,\n        namedCurve);\n  };\n  AsymmetricKeyDetails getAsymmetricKeyDetails(jsg::Lock& js) const;\n\n  ~CryptoKey() noexcept(false);\n\n  // Returns the name of this CryptoKey's algorithm in a normalized, statically-allocated string.\n  kj::StringPtr getAlgorithmName() const;\n\n  // JS API\n\n  using AlgorithmVariant = kj::OneOf<KeyAlgorithm,\n      AesKeyAlgorithm,\n      HmacKeyAlgorithm,\n      RsaKeyAlgorithm,\n      EllipticKeyAlgorithm,\n      ArbitraryKeyAlgorithm>;\n\n  AlgorithmVariant getAlgorithm(jsg::Lock& js) const;\n  kj::StringPtr getType() const;\n  bool getExtractable() const;\n  kj::Array<kj::StringPtr> getUsages() const;\n  CryptoKeyUsageSet getUsageSet() const;\n\n  JSG_RESOURCE_TYPE(CryptoKey) {\n    JSG_READONLY_INSTANCE_PROPERTY(type, getType);\n    JSG_READONLY_INSTANCE_PROPERTY(extractable, getExtractable);\n    JSG_READONLY_INSTANCE_PROPERTY(algorithm, getAlgorithm);\n    JSG_READONLY_INSTANCE_PROPERTY(usages, getUsages);\n  }\n\n  // HACK: Needs to be public so derived classes can inherit from it.\n  class Impl;\n\n  // Treat as private -- needs to be public for js.alloc<T>()...\n  explicit CryptoKey(kj::Own<Impl> impl);\n\n  // Compare the contents of this key with the other. Will return false if\n  // either key is not extractable or if the keys are a different type.\n  // For secret keys, we will compare only the actual key material and not\n  // the algorithm parameters or the algorithm name. We will also ensure\n  // that a timing-safe comparison is used for the key material.\n  bool operator==(const CryptoKey& other) const;\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n  bool verifyX509Public(const X509* x509) const;\n  bool verifyX509Private(const X509* x509) const;\n\n private:\n  kj::Own<Impl> impl;\n\n  void visitForGc(jsg::GcVisitor& visitor);\n\n  friend class SubtleCrypto;\n  friend class EllipticKey;\n  friend class EdDsaKey;\n  friend class node::CryptoImpl;\n};\n\nstruct CryptoKeyPair {\n  jsg::Ref<CryptoKey> publicKey;\n  jsg::Ref<CryptoKey> privateKey;\n\n  JSG_STRUCT(publicKey, privateKey);\n};\n\nclass SubtleCrypto: public jsg::Object {\n public:\n  // Algorithm dictionaries\n  //\n  // Every method of SubtleCrypto except `exportKey()` takes an `algorithm` parameter, usually as the\n  // first argument. This can usually be a raw string algorithm name, or an object with a `name`\n  // field and other fields. The other fields differ based on which algorithm is named and which\n  // function is being called. We achieve polymorphism here by making all the fields except `name`\n  // be `jsg::Optional`... ugly, but it works.\n\n  // Type of the `algorithm` parameter passed to `digest()`. Also used as the type of the `hash`\n  // parameter of many other algorithm structs.\n  struct HashAlgorithm {\n    kj::String name;\n\n    JSG_STRUCT(name);\n  };\n\n  // Type of the `algorithm` parameter passed to `encrypt()` and `decrypt()`. Different\n  // algorithms call for different fields.\n  struct EncryptAlgorithm {\n    // E.g. \"AES-GCM\"\n    kj::String name;\n\n    // For AES: The initialization vector use. May be up to 2^64-1 bytes long.\n    jsg::Optional<jsg::JsRef<jsg::JsBufferSource>> iv;\n\n    // The additional authentication data to include.\n    jsg::Optional<jsg::JsRef<jsg::JsBufferSource>> additionalData;\n\n    // The desired length of the authentication tag. May be 0 - 128.\n    // Note: the spec specifies this as a Web IDL byte (== signed char in C++), not an int, but JS\n    //   has no such 8-bit integer animal.\n    jsg::Optional<int> tagLength;\n\n    // The initial value of the counter block for AES-CTR.\n    // https://www.w3.org/TR/WebCryptoAPI/#aes-ctr-params\n    jsg::Optional<jsg::JsRef<jsg::JsBufferSource>> counter;\n\n    // The length, in bits, of the rightmost part of the counter block that is incremented.\n    // See above why we use int instead of int8_t.\n    // https://www.w3.org/TR/WebCryptoAPI/#aes-ctr-params\n    jsg::Optional<int> length;\n\n    // The optional label/application data to associate with the message (for RSA-OAEP)\n    jsg::Optional<jsg::JsRef<jsg::JsBufferSource>> label;\n\n    JSG_STRUCT(name, iv, additionalData, tagLength, counter, length, label);\n  };\n\n  // Type of the `algorithm` parameter passed to `sign()` and `verify()`. Different\n  // algorithms call for different fields.\n  struct SignAlgorithm {\n    // E.g. \"RSASSA-PKCS1-v1_5\", \"ECDSA\"\n    kj::String name;\n\n    // ECDSA wants the hash to be specified at call time rather than import\n    // time.\n    jsg::Optional<kj::OneOf<kj::String, HashAlgorithm>> hash;\n\n    // Not part of the WebCrypto spec. Used by an extension.\n    jsg::Optional<int> dataLength;\n\n    // Used for RSA-PSS\n    jsg::Optional<int> saltLength;\n\n    JSG_STRUCT(name, hash, dataLength, saltLength);\n  };\n\n  // Type of the `algorithm` parameter passed to `generateKey()`. Different algorithms call for\n  // different fields.\n  struct GenerateKeyAlgorithm {\n    // E.g. \"HMAC\", \"RSASSA-PKCS1-v1_5\", \"ECDSA\", ...\n    kj::String name;\n\n    // For signing algorithms where the hash is specified at import time, identifies the hash\n    // function to use, e.g. \"SHA-256\".\n    jsg::Optional<kj::OneOf<kj::String, HashAlgorithm>> hash;\n\n    // For RSA algorithms: The length in bits of the RSA modulus.\n    jsg::Optional<int> modulusLength;\n\n    // For RSA algorithms\n    jsg::Optional<jsg::JsRef<jsg::JsBufferSource>> publicExponent;\n\n    // For AES algorithms or when name == \"HMAC\": The length in bits of the key.\n    jsg::Optional<int> length;\n\n    // When name == \"ECDSA\": \"P-256\", \"P-384\", or \"P-521\"\n    jsg::Optional<kj::String> namedCurve;\n\n    JSG_STRUCT(name, hash, modulusLength, publicExponent, length, namedCurve);\n  };\n\n  // Type of the `algorithm` parameter passed to `importKey()`, as well as the\n  // `derivedKeyAlgorithm` parameter to `deriveKey()`. Different algorithms call for different\n  // fields.\n  struct ImportKeyAlgorithm {\n    // E.g. \"HMAC\", \"RSASSA-PKCS1-v1_5\", \"ECDSA\", ...\n    kj::String name;\n\n    // For signing algorithms where the hash is specified at import time, identifies the hash\n    // function to use, e.g. \"SHA-256\".\n    jsg::Optional<kj::OneOf<kj::String, HashAlgorithm>> hash;\n\n    // When name == \"HMAC\": The length in bits of the key.\n    jsg::Optional<int> length;\n\n    // When name == \"ECDSA\": \"P-256\", \"P-384\", or \"P-521\"\n    jsg::Optional<kj::String> namedCurve;\n\n    // Not part of the WebCrypto spec. Used by an extension to indicate that curve points are in\n    // compressed format. (The standard algorithms do not recognize this option.)\n    jsg::Optional<bool> compressed;\n\n    JSG_STRUCT(name, hash, length, namedCurve, compressed);\n  };\n\n  // Type of the `algorithm` parameter passed to `deriveKey()`. Different algorithms call for\n  // different fields.\n  struct DeriveKeyAlgorithm {\n    // e.g. \"PBKDF2\", \"ECDH\", etc\n    kj::String name;\n\n    // PBKDF2 parameters\n    jsg::Optional<jsg::JsRef<jsg::JsBufferSource>> salt;\n    jsg::Optional<int> iterations;\n    jsg::Optional<kj::OneOf<kj::String, HashAlgorithm>> hash;\n\n    // ECDH parameters\n    jsg::Optional<jsg::Ref<CryptoKey>> $public;\n\n    // HKDF parameters (some shared with PBKDF2)\n\n    // Bit string that corresponds to the context and application specific context for the derived\n    // keying material\n    jsg::Optional<jsg::JsRef<jsg::JsBufferSource>> info;\n\n    JSG_STRUCT(name, salt, iterations, hash, $public, info);\n  };\n\n  // https://www.w3.org/TR/WebCryptoAPI/#JsonWebKey-dictionary\n  struct JsonWebKey {\n\n    struct RsaOtherPrimesInfo {\n      // The following fields are defined in Section 6.3.2.7 of JSON Web Algorithms\n      jsg::Optional<kj::String> r;\n      jsg::Optional<kj::String> d;\n      jsg::Optional<kj::String> t;\n\n      JSG_STRUCT(r, d, t);\n      JSG_STRUCT_TS_OVERRIDE(RsaOtherPrimesInfo);  // Rename from SubtleCryptoJsonWebKeyRsaOtherPrimesInfo\n    };\n\n    // The following fields are defined in Section 3.1 of JSON Web Key (RFC 7517).\n    // NOTE: The Web Crypto spec's IDL for JsonWebKey considers `kty` optional, yet the RFC lists it\n    //   as required.\n    kj::String kty;\n    jsg::Optional<kj::String> use;\n    jsg::Optional<kj::Array<kj::String>> key_ops;\n    jsg::Optional<kj::String> alg;\n\n    // The following fields are defined in JSON Web Key Parameters Registration\n    jsg::Optional<bool> ext;\n\n    // The following fields are defined in Section 6 of JSON Web Algorithms\n    jsg::Optional<kj::String> crv;\n    jsg::Optional<kj::String> x;\n    jsg::Optional<kj::String> y;\n    jsg::Optional<kj::String> d;\n    jsg::Optional<kj::String> n;\n    jsg::Optional<kj::String> e;\n    jsg::Optional<kj::String> p;\n    jsg::Optional<kj::String> q;\n    jsg::Optional<kj::String> dp;\n    jsg::Optional<kj::String> dq;\n    jsg::Optional<kj::String> qi;\n    jsg::Optional<kj::Array<RsaOtherPrimesInfo>> oth;\n    // TODO(conform): Support multiprime RSA keys. This used to be jsg::Unimplemented but needs to\n    //   be properly defined for exporting JWK of other keys. On the other hand, are we even going\n    //   to bother adding support for multiprime RSA keys? Chromium doesn't AFAICT...\n    jsg::Optional<kj::String> k;\n\n    JSG_STRUCT(kty, use, key_ops, alg, ext, crv, x, y, d, n, e, p, q, dp, dq, qi, oth, k);\n    JSG_STRUCT_TS_OVERRIDE(JsonWebKey);  // Rename from SubtleCryptoJsonWebKey\n  };\n\n  using ImportKeyData = kj::OneOf<kj::Array<kj::byte>, JsonWebKey>;\n  using ExportKeyData = kj::OneOf<jsg::JsRef<jsg::JsArrayBuffer>, JsonWebKey>;\n\n  jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> encrypt(jsg::Lock& js,\n      kj::OneOf<kj::String, EncryptAlgorithm> algorithm,\n      const CryptoKey& key,\n      kj::Array<const kj::byte> plainText);\n  jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> decrypt(jsg::Lock& js,\n      kj::OneOf<kj::String, EncryptAlgorithm> algorithm,\n      const CryptoKey& key,\n      kj::Array<const kj::byte> cipherText);\n\n  jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> sign(jsg::Lock& js,\n      kj::OneOf<kj::String, SignAlgorithm> algorithm,\n      const CryptoKey& key,\n      kj::Array<const kj::byte> data);\n  jsg::Promise<bool> verify(jsg::Lock& js,\n      kj::OneOf<kj::String, SignAlgorithm> algorithm,\n      const CryptoKey& key,\n      kj::Array<const kj::byte> signature,\n      kj::Array<const kj::byte> data);\n\n  jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> digest(jsg::Lock& js,\n      kj::OneOf<kj::String, HashAlgorithm> algorithm,\n      kj::Array<const kj::byte> data);\n\n  jsg::Promise<kj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair>> generateKey(jsg::Lock& js,\n      kj::OneOf<kj::String, GenerateKeyAlgorithm> algorithm,\n      bool extractable,\n      kj::Array<kj::String> keyUsages);\n\n  jsg::Promise<jsg::Ref<CryptoKey>> deriveKey(jsg::Lock& js,\n      kj::OneOf<kj::String, DeriveKeyAlgorithm> algorithm,\n      const CryptoKey& baseKey,\n      kj::OneOf<kj::String, ImportKeyAlgorithm> derivedKeyAlgorithm,\n      bool extractable,\n      kj::Array<kj::String> keyUsages);\n  jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> deriveBits(jsg::Lock& js,\n      kj::OneOf<kj::String, DeriveKeyAlgorithm> algorithm,\n      const CryptoKey& baseKey,\n      // The operation needs to be able to take both undefined and null\n      // and handle them equivalently... if we just used jsg::Optional<int>\n      // here, null would be coerced to 0. If we just used kj::Maybe<int>\n      // here, undefined would be an error. So we need to use an optional\n      // maybe int in order to treat undefined and null as being equivalent.\n      jsg::Optional<kj::Maybe<int>> length);\n\n  jsg::Promise<jsg::Ref<CryptoKey>> importKey(jsg::Lock& js,\n      kj::String format,\n      ImportKeyData keyData,\n      kj::OneOf<kj::String, ImportKeyAlgorithm> algorithm,\n      bool extractable,\n      kj::Array<kj::String> keyUsages);\n\n  // NOT VISIBLE TO JS: like importKey() but return the key, not a promise.\n  jsg::Ref<CryptoKey> importKeySync(jsg::Lock& js,\n      kj::StringPtr format,\n      ImportKeyData keyData,\n      ImportKeyAlgorithm algorithm,\n      bool extractable,\n      kj::ArrayPtr<const kj::String> keyUsages);\n\n  jsg::Promise<ExportKeyData> exportKey(jsg::Lock& js, kj::String format, const CryptoKey& key);\n\n  jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> wrapKey(jsg::Lock& js,\n      kj::String format,\n      const CryptoKey& key,\n      const CryptoKey& wrappingKey,\n      kj::OneOf<kj::String, EncryptAlgorithm> wrapAlgorithm,\n      const jsg::TypeHandler<JsonWebKey>& jwkHandler);\n  jsg::Promise<jsg::Ref<CryptoKey>> unwrapKey(jsg::Lock& js,\n      kj::String format,\n      kj::Array<const kj::byte> wrappedKey,\n      const CryptoKey& unwrappingKey,\n      kj::OneOf<kj::String, EncryptAlgorithm> unwrapAlgorithm,\n      kj::OneOf<kj::String, ImportKeyAlgorithm> unwrappedKeyAlgorithm,\n      bool extractable,\n      kj::Array<kj::String> keyUsages,\n      const jsg::TypeHandler<JsonWebKey>& jwkHandler);\n\n  // This is a non-standard extension based off Node.js' implementation of crypto.timingSafeEqual.\n  bool timingSafeEqual(jsg::JsBufferSource a, jsg::JsBufferSource b);\n\n  JSG_RESOURCE_TYPE(SubtleCrypto) {\n    JSG_METHOD(encrypt);\n    JSG_METHOD(decrypt);\n    JSG_METHOD(sign);\n    JSG_METHOD(verify);\n    JSG_METHOD(digest);\n    JSG_METHOD(generateKey);\n    JSG_METHOD(deriveKey);\n    JSG_METHOD(deriveBits);\n    JSG_METHOD(importKey);\n    JSG_METHOD(exportKey);\n    JSG_METHOD(wrapKey);\n    JSG_METHOD(unwrapKey);\n    JSG_METHOD(timingSafeEqual);\n\n    JSG_TS_OVERRIDE({\n      wrapKey(format: string,\n              key: CryptoKey,\n              wrappingKey: CryptoKey,\n              wrapAlgorithm: string | SubtleCryptoEncryptAlgorithm)\n              : Promise<ArrayBuffer>;\n      deriveBits(algorithm: string | SubtleCryptoDeriveKeyAlgorithm,\n                 baseKey : CryptoKey,\n                 length? : number | null)\n                 : Promise<ArrayBuffer>;\n      digest(algorithm: string | SubtleCryptoHashAlgorithm,\n             data: ArrayBuffer | ArrayBufferView)\n          : Promise<ArrayBuffer>;\n      sign(algorithm: string | SubtleCryptoSignAlgorithm,\n           key: CryptoKey,\n           data: ArrayBuffer | ArrayBufferView)\n           : Promise<ArrayBuffer>;\n      decrypt(algorithm: string | SubtleCryptoEncryptAlgorithm,\n              key: CryptoKey,\n              cipherText: ArrayBuffer | ArrayBufferView)\n              : Promise<ArrayBuffer>;\n      encrypt(algorithm: string | SubtleCryptoEncryptAlgorithm,\n              key: CryptoKey,\n              plainText: ArrayBuffer | ArrayBufferView)\n              : Promise<ArrayBuffer>;\n      exportKey(format: string, key: CryptoKey) : Promise<ArrayBuffer | JsonWebKey>;\n    });\n  }\n};\n\n// =======================================================================================\n// DigestStream is a non-standard extension that provides a way of generating\n// a hash digest from streaming data. It combines Web Crypto concepts into a\n// WritableStream and is compatible with both APIs.\nclass DigestContext {\n public:\n  virtual ~DigestContext() noexcept = default;\n  virtual void write(kj::ArrayPtr<kj::byte> buffer) = 0;\n  virtual jsg::JsArrayBuffer close(jsg::Lock& js) = 0;\n};\n\nclass DigestStream: public WritableStream {\n public:\n  using DigestContextPtr = kj::Own<DigestContext>;\n  using Algorithm = kj::OneOf<kj::String, SubtleCrypto::HashAlgorithm>;\n\n  explicit DigestStream(kj::Own<WritableStreamController> controller,\n      SubtleCrypto::HashAlgorithm algorithm,\n      jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>>::Resolver resolver,\n      jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> promise);\n\n  static jsg::Ref<DigestStream> constructor(jsg::Lock& js, Algorithm algorithm);\n\n  jsg::MemoizedIdentity<jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>>>& getDigest() {\n    return promise;\n  }\n  void dispose(jsg::Lock& js);\n  uint64_t getBytesWritten() const {\n    return bytesWritten;\n  }\n\n  JSG_RESOURCE_TYPE(DigestStream, CompatibilityFlags::Reader flags) {\n    JSG_INHERIT(WritableStream);\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(digest, getDigest);\n    } else {\n      JSG_READONLY_INSTANCE_PROPERTY(digest, getDigest);\n    }\n    JSG_READONLY_PROTOTYPE_PROPERTY(bytesWritten, getBytesWritten);\n    JSG_DISPOSE(dispose);\n\n    JSG_TS_OVERRIDE(extends WritableStream<ArrayBuffer | ArrayBufferView> {\n      readonly digest: Promise<ArrayBuffer>;\n    });\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  static DigestContextPtr initContext(SubtleCrypto::HashAlgorithm& algorithm);\n\n  struct Ready {\n    SubtleCrypto::HashAlgorithm algorithm;\n    jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>>::Resolver resolver;\n    DigestContextPtr context;\n    Ready(SubtleCrypto::HashAlgorithm algorithm,\n        jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>>::Resolver resolver)\n        : algorithm(kj::mv(algorithm)),\n          resolver(kj::mv(resolver)),\n          context(initContext(this->algorithm)) {}\n  };\n  jsg::MemoizedIdentity<jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>>> promise;\n  kj::OneOf<Ready, StreamStates::Closed, StreamStates::Errored> state;\n  uint64_t bytesWritten = 0;\n\n  kj::Maybe<StreamStates::Errored> write(jsg::Lock& js, kj::ArrayPtr<kj::byte> buffer);\n  kj::Maybe<StreamStates::Errored> close(jsg::Lock& js);\n  void abort(jsg::Lock& js, jsg::JsValue reason);\n\n  void visitForGc(jsg::GcVisitor& visitor);\n};\n\n// =======================================================================================\n// Crypto\n\n// Implements the Crypto interface as prescribed by:\n// https://www.w3.org/TR/WebCryptoAPI/#crypto-interface\nclass Crypto: public jsg::Object {\n public:\n  Crypto(jsg::Lock& js): subtle(js.alloc<SubtleCrypto>()) {}\n\n  jsg::JsArrayBufferView getRandomValues(jsg::JsArrayBufferView buffer);\n\n  kj::String randomUUID();\n\n  jsg::Ref<SubtleCrypto> getSubtle() {\n    return subtle.addRef();\n  }\n\n  JSG_RESOURCE_TYPE(Crypto, CompatibilityFlags::Reader flags) {\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(subtle, getSubtle);\n    } else {\n      JSG_READONLY_INSTANCE_PROPERTY(subtle, getSubtle);\n    }\n    JSG_METHOD(getRandomValues);\n    JSG_METHOD(randomUUID);\n\n    JSG_NESTED_TYPE(DigestStream);\n\n    JSG_TS_OVERRIDE({\n      getRandomValues<\n        T extends\n          | Int8Array\n          | Uint8Array\n          | Int16Array\n          | Uint16Array\n          | Int32Array\n          | Uint32Array\n          | BigInt64Array\n          | BigUint64Array\n      >(buffer: T): T;\n    });\n  }\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(subtle);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"subtle\", subtle);\n  }\n\n private:\n  jsg::Ref<SubtleCrypto> subtle;\n};\n\n#define EW_CRYPTO_ISOLATE_TYPES                                                                    \\\n  api::Crypto, api::SubtleCrypto, api::CryptoKey, api::CryptoKeyPair,                              \\\n      api::SubtleCrypto::JsonWebKey, api::SubtleCrypto::JsonWebKey::RsaOtherPrimesInfo,            \\\n      api::SubtleCrypto::DeriveKeyAlgorithm, api::SubtleCrypto::EncryptAlgorithm,                  \\\n      api::SubtleCrypto::GenerateKeyAlgorithm, api::SubtleCrypto::HashAlgorithm,                   \\\n      api::SubtleCrypto::ImportKeyAlgorithm, api::SubtleCrypto::SignAlgorithm,                     \\\n      api::CryptoKey::KeyAlgorithm, api::CryptoKey::AesKeyAlgorithm,                               \\\n      api::CryptoKey::HmacKeyAlgorithm, api::CryptoKey::RsaKeyAlgorithm,                           \\\n      api::CryptoKey::EllipticKeyAlgorithm, api::CryptoKey::ArbitraryKeyAlgorithm,                 \\\n      api::CryptoKey::AsymmetricKeyDetails, api::DigestStream\n\n}  // namespace workerd::api\n\nKJ_DECLARE_NON_POLYMORPHIC(EVP_MD_CTX)\nKJ_DECLARE_NON_POLYMORPHIC(BIO);\n"
  },
  {
    "path": "src/workerd/api/crypto/dh.c++",
    "content": "#include \"dh.h\"\n\n#include \"impl.h\"\n\n#include <workerd/io/io-context.h>\n\n#include <ncrypto.h>\n#include <openssl/bn.h>\n#include <openssl/dh.h>\n\n#include <kj/one-of.h>\n#include <kj/string.h>\n\nnamespace workerd::api {\n\nnamespace {\n\n// Maximum DH prime size, adapted from BoringSSL. This is already defined in more recent versions.\n#ifndef OPENSSL_DH_MAX_MODULUS_BITS\n#define OPENSSL_DH_MAX_MODULUS_BITS 10000\n#endif\n\n// Returns a function that can be used to create an instance of a standardized\n// Diffie-Hellman group.\nBIGNUM* (*findDiffieHellmanGroup(const char* name))(BIGNUM*) {\n#if _WIN32\n#define V(n, p)                                                                                    \\\n  if (_strnicmp(name, n, 7) == 0) {                                                                \\\n    return p;                                                                                      \\\n  }\n#else\n#define V(n, p)                                                                                    \\\n  if (strncasecmp(name, n, 7) == 0) {                                                              \\\n    return p;                                                                                      \\\n  }\n#endif\n  // Only the following primes are supported based on security concerns about the smaller prime\n  // groups (https://www.rfc-editor.org/rfc/rfc8247#section-2.4).\n  V(\"modp14\", BN_get_rfc3526_prime_2048);\n  V(\"modp15\", BN_get_rfc3526_prime_3072);\n  V(\"modp16\", BN_get_rfc3526_prime_4096);\n  V(\"modp17\", BN_get_rfc3526_prime_6144);\n  V(\"modp18\", BN_get_rfc3526_prime_8192);\n#undef V\n\n  return nullptr;\n}\n\nkj::Own<DH> initDhGroup(kj::StringPtr name) {\n  auto group = findDiffieHellmanGroup(name.begin());\n  JSG_REQUIRE(group != nullptr, Error,\n      \"Failed to init DiffieHellmanGroup: invalid group. Only \"\n      \"groups {modp14, modp15, modp16, modp17, modp18} are supported.\");\n  auto groupKey = group(nullptr);\n  KJ_ASSERT(groupKey != nullptr);\n\n  const int kStandardizedGenerator = 2;\n  auto dh = OSSL_NEW(DH);\n\n  // Note: We're deliberately not using kj::Own/OSSL_NEW() here as DH_set0_pqg() takes ownership\n  // of the key, so there is no need to free it if the operation succeeds.\n  UniqueBignum bn_g(BN_new(), &BN_clear_free);\n  if (!BN_set_word(bn_g.get(), kStandardizedGenerator) ||\n      !DH_set0_pqg(dh, groupKey, nullptr, bn_g.get())) {\n    JSG_FAIL_REQUIRE(Error, \"DiffieHellmanGroup init failed: could not set keys\");\n  }\n  bn_g.release();\n  return kj::mv(dh);\n}\n\nkj::Own<DH> initDh(kj::OneOf<kj::Array<kj::byte>, int>& sizeOrKey,\n    kj::OneOf<kj::Array<kj::byte>, int>& generator) {\n  KJ_SWITCH_ONEOF(sizeOrKey) {\n    KJ_CASE_ONEOF(size, int) {\n      KJ_SWITCH_ONEOF(generator) {\n        KJ_CASE_ONEOF(gen, int) {\n          // Generating a DH key with a reasonable size can be expensive.\n          // We will only allow it if there is an active IoContext so that\n          // we can enforce a timeout associate with the limit enforcer.\n          auto& ioContext = JSG_REQUIRE_NONNULL(IoContext::tryCurrent(), Error,\n              \"DiffieHellman key generation requires an active request\");\n\n          struct Status {\n            IoContext& context;\n            kj::Maybe<EventOutcome> status;\n          } status{.context = ioContext};\n\n          auto dh = OSSL_NEW(DH);\n          BN_GENCB cb;\n          cb.arg = &status;\n          // This callback is called many times during the key generation process.\n          // We use it because key generation is expensive and may run over the\n          // CPU limits for the request. As this method can itself contribute to\n          // running over the CPU limit, it is important to do as little as possible.\n          cb.callback = [](int a, int b, BN_GENCB* cb) -> int {\n            Status& status = *static_cast<Status*>(cb->arg);\n            KJ_IF_SOME(outcome, status.context.getLimitEnforcer().getLimitsExceeded()) {\n              status.status = outcome;\n              return 0;\n            }\n            return 1;\n          };\n          // Operations on an \"egregiously large\" prime will throw with recent BoringSSL.\n          JSG_REQUIRE(size <= OPENSSL_DH_MAX_MODULUS_BITS, RangeError,\n              \"DiffieHellman init failed: requested prime size too large\");\n          if (!DH_generate_parameters_ex(dh.get(), size, gen, &cb)) {\n            KJ_IF_SOME(outcome, status.status) {\n              if (outcome == EventOutcome::EXCEEDED_CPU) {\n                JSG_FAIL_REQUIRE(\n                    Error, \"DiffieHellman init failed: key generation exceeded CPU limit\");\n              } else if (outcome == EventOutcome::EXCEEDED_MEMORY) {\n                JSG_FAIL_REQUIRE(\n                    Error, \"DiffieHellman init failed: key generation exceeded memory limit\");\n              }\n            }\n            JSG_FAIL_REQUIRE(Error, \"DiffieHellman init failed: could not generate parameters\");\n          }\n          // Boringssl throws on DH with g >= p or p | 2 since g can't be an element of p's\n          // multiplicative group in that case.\n          JSG_REQUIRE(BN_is_odd(DH_get0_p(dh)) && BN_ucmp(DH_get0_g(dh), DH_get0_p(dh)) < 0, Error,\n              \"DiffieHellman init failed: Invalid DH prime generated\");\n          return kj::mv(dh);\n        }\n        KJ_CASE_ONEOF(gen, kj::Array<kj::byte>) {\n          // Node.js does not support generating Diffie-Hellman keys from an int prime\n          // and byte-array generator. This could change in the future.\n          JSG_FAIL_REQUIRE(Error, \"DiffieHellman init failed: invalid parameters\");\n        }\n      }\n    }\n    KJ_CASE_ONEOF(key, kj::Array<kj::byte>) {\n      // Operations on an \"egregiously large\" prime will throw with BoringSSL.\n      JSG_REQUIRE(key.size() <= OPENSSL_DH_MAX_MODULUS_BITS / CHAR_BIT, RangeError,\n          \"DiffieHellman init failed: key is too large\");\n      JSG_REQUIRE(key.size() > 0, Error, \"DiffieHellman init failed: invalid key\");\n      auto dh = OSSL_NEW(DH);\n\n      // We use a std::unique_ptr here instead of a kj::Own because DH_set0_pqg takes ownership\n      // and we need to be able to release ownership if the operation succeeds but want the\n      // BIGNUMs to be appropriately freed if the operations fail.\n      using UniqueBignum = std::unique_ptr<BIGNUM, void (*)(BIGNUM*)>;\n      UniqueBignum bn_g(nullptr, &BN_clear_free);\n\n      KJ_SWITCH_ONEOF(generator) {\n        KJ_CASE_ONEOF(gen, int) {\n          JSG_REQUIRE(gen >= 2, RangeError, \"DiffieHellman init failed: generator too small\");\n          bn_g.reset(BN_new());\n          if (!BN_set_word(bn_g.get(), gen)) {\n            JSG_FAIL_REQUIRE(Error, \"DiffieHellman init failed: could not set keys\");\n          }\n        }\n        KJ_CASE_ONEOF(gen, kj::Array<kj::byte>) {\n          JSG_REQUIRE(gen.size() <= OPENSSL_DH_MAX_MODULUS_BITS / CHAR_BIT, RangeError,\n              \"DiffieHellman init failed: generator is too large\");\n          JSG_REQUIRE(gen.size() > 0, Error, \"DiffieHellman init failed: invalid generator\");\n\n          bn_g.reset(toBignumUnowned(gen));\n          if (BN_is_zero(bn_g.get()) || BN_is_one(bn_g.get())) {\n            JSG_FAIL_REQUIRE(Error, \"DiffieHellman init failed: invalid generator\");\n          }\n        }\n      }\n      UniqueBignum bn_p(toBignumUnowned(key), &BN_clear_free);\n      JSG_REQUIRE(bn_p != nullptr, Error,\n          \"DiffieHellman init failed: could not convert key representation\");\n      // Boringssl throws on DH with g >= p or p | 2 since g can't be an element of p's\n      // multiplicative group in that case.\n      JSG_REQUIRE(BN_is_odd(bn_p.get()) && BN_ucmp(bn_g.get(), bn_p.get()) < 0, Error,\n          \"DiffieHellman init failed: Invalid DH prime generated\");\n      JSG_REQUIRE(DH_set0_pqg(dh, bn_p.get(), nullptr, bn_g.get()), Error,\n          \"DiffieHellman init failed: could not set keys\");\n      bn_g.release();\n      bn_p.release();\n      return kj::mv(dh);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid zeroPadDiffieHellmanSecret(size_t remainder_size, unsigned char* data, size_t prime_size) {\n  // DH_size returns number of bytes in a prime number.\n  // DH_compute_key returns number of bytes in a remainder of exponent, which\n  // may have less bytes than a prime number. Therefore add 0-padding to the\n  // allocated buffer.\n  if (remainder_size != prime_size) {\n    KJ_ASSERT(remainder_size < prime_size);\n    const size_t padding = prime_size - remainder_size;\n    memmove(data + padding, data, remainder_size);\n    kj::arrayPtr(data, padding).fill(0);\n  }\n}\n}  // namespace\n\nDiffieHellman::DiffieHellman(kj::StringPtr group): dh(initDhGroup(group)) {}\n\nDiffieHellman::DiffieHellman(\n    kj::OneOf<kj::Array<kj::byte>, int>& sizeOrKey, kj::OneOf<kj::Array<kj::byte>, int>& generator)\n    : dh(initDh(sizeOrKey, generator)) {}\n\nkj::Maybe<int> DiffieHellman::check() {\n  ClearErrorOnReturn clearErrorOnReturn;\n  int codes;\n  if (!DH_check(dh.get(), &codes)) {\n    return kj::none;\n  }\n  return codes;\n}\n\nvoid DiffieHellman::setPrivateKey(kj::ArrayPtr<kj::byte> key) {\n  auto bn = toBignumOwned(key);\n  OSSLCALL(DH_set0_key(dh, nullptr, bn.get()));\n  bn.release();\n}\n\nvoid DiffieHellman::setPublicKey(kj::ArrayPtr<kj::byte> key) {\n  auto bn = toBignumOwned(key);\n\n  int checkResult;\n  JSG_REQUIRE(DH_check_pub_key(dh, bn.get(), &checkResult), Error,\n      \"DiffieHellman setPublicKey() failed: could not validate public key\");\n  if (checkResult) {\n    JSG_REQUIRE(!(checkResult & DH_CHECK_PUBKEY_TOO_SMALL), RangeError,\n        \"DiffieHellman setPublicKey() failed: key is too small\");\n    JSG_REQUIRE(!(checkResult & DH_CHECK_PUBKEY_TOO_LARGE), RangeError,\n        \"DiffieHellman setPublicKey() failed: key is too large\");\n    JSG_FAIL_REQUIRE(Error, \"DiffieHellman setPublicKey() failed: invalid public key\");\n  }\n\n  OSSLCALL(DH_set0_key(dh, bn.get(), nullptr));\n  bn.release();\n}\n\njsg::JsUint8Array DiffieHellman::getPublicKey(jsg::Lock& js) {\n  const BIGNUM* pub_key = DH_get0_pub_key(dh);\n  JSG_REQUIRE(pub_key != nullptr, Error, \"No public key\");\n  return JSG_REQUIRE_NONNULL(\n      bignumToArrayPadded(js, *pub_key), Error, \"Error while retrieving DiffieHellman public key\");\n}\n\njsg::JsUint8Array DiffieHellman::getPrivateKey(jsg::Lock& js) {\n  const BIGNUM* priv_key = DH_get0_priv_key(dh);\n  JSG_REQUIRE(priv_key != nullptr, Error, \"No private key\");\n  return JSG_REQUIRE_NONNULL(bignumToArrayPadded(js, *priv_key), Error,\n      \"Error while retrieving DiffieHellman private key\");\n}\n\njsg::JsUint8Array DiffieHellman::getGenerator(jsg::Lock& js) {\n  const BIGNUM* g = DH_get0_g(dh);\n  JSG_REQUIRE(g != nullptr, Error, \"No DiffieHellman generator\");\n  return JSG_REQUIRE_NONNULL(\n      bignumToArrayPadded(js, *g), Error, \"Error while retrieving DiffieHellman generator\");\n}\n\njsg::JsUint8Array DiffieHellman::getPrime(jsg::Lock& js) {\n  const BIGNUM* p = DH_get0_p(dh);\n  JSG_REQUIRE(p != nullptr, Error, \"No DiffieHellman prime\");\n  return JSG_REQUIRE_NONNULL(\n      bignumToArrayPadded(js, *p), Error, \"Error while retrieving DiffieHellman prime\");\n}\n\njsg::JsUint8Array DiffieHellman::computeSecret(jsg::Lock& js, kj::ArrayPtr<kj::byte> key) {\n  JSG_REQUIRE(key.size() <= INT32_MAX, RangeError,\n      \"DiffieHellman computeSecret() failed: key is too large\");\n  JSG_REQUIRE(key.size() > 0, Error, \"DiffieHellman computeSecret() failed: invalid key\");\n\n  ClearErrorOnReturn clear_error_on_return;\n  auto k = JSG_REQUIRE_NONNULL(\n      toBignum(key), Error, \"Error getting key while computing DiffieHellman secret\");\n\n  // Validate the peer's public key before computing the shared secret.\n  int checkResult;\n  JSG_REQUIRE(DH_check_pub_key(dh, k, &checkResult), Error,\n      \"DiffieHellman computeSecret() failed: could not validate peer public key\");\n  if (checkResult) {\n    JSG_REQUIRE(!(checkResult & DH_CHECK_PUBKEY_TOO_SMALL), RangeError,\n        \"DiffieHellman computeSecret() failed: Supplied key is too small\");\n    JSG_REQUIRE(!(checkResult & DH_CHECK_PUBKEY_TOO_LARGE), RangeError,\n        \"DiffieHellman computeSecret() failed: Supplied key is too large\");\n    JSG_FAIL_REQUIRE(Error, \"DiffieHellman computeSecret() failed: invalid peer public key\");\n  }\n\n  size_t prime_size = DH_size(dh);\n  auto buf = jsg::JsUint8Array::create(js, prime_size);\n\n  int size = DH_compute_key(buf.asArrayPtr().begin(), k.get(), dh);\n  JSG_REQUIRE(\n      size != -1, Error, \"DiffieHellman computeSecret() failed: error computing shared secret\");\n\n  KJ_ASSERT(size >= 0);\n  zeroPadDiffieHellmanSecret(size, buf.asArrayPtr().begin(), prime_size);\n  return buf;\n}\n\njsg::JsUint8Array DiffieHellman::generateKeys(jsg::Lock& js) {\n  ClearErrorOnReturn clear_error_on_return;\n  OSSLCALL(DH_generate_key(dh));\n  const BIGNUM* pub_key = DH_get0_pub_key(dh);\n  return JSG_REQUIRE_NONNULL(\n      bignumToArrayPadded(js, *pub_key), Error, \"Error while generating DiffieHellman keys\");\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/dh.h",
    "content": "#pragma once\n\n#include <workerd/jsg/jsg.h>\n\n#include <openssl/base.h>\n\n#include <kj/common.h>\n\nnamespace workerd::api {\n\nclass DiffieHellman final {\n public:\n  DiffieHellman(kj::StringPtr group);\n  DiffieHellman(kj::OneOf<kj::Array<kj::byte>, int>& sizeOrKey,\n      kj::OneOf<kj::Array<kj::byte>, int>& generator);\n  DiffieHellman(DiffieHellman&&) = default;\n  DiffieHellman& operator=(DiffieHellman&&) = default;\n  KJ_DISALLOW_COPY(DiffieHellman);\n\n  void setPrivateKey(kj::ArrayPtr<kj::byte> key);\n  void setPublicKey(kj::ArrayPtr<kj::byte> key);\n\n  jsg::JsUint8Array getPublicKey(jsg::Lock& js) KJ_WARN_UNUSED_RESULT;\n  jsg::JsUint8Array getPrivateKey(jsg::Lock& js) KJ_WARN_UNUSED_RESULT;\n  jsg::JsUint8Array getGenerator(jsg::Lock& js) KJ_WARN_UNUSED_RESULT;\n  jsg::JsUint8Array getPrime(jsg::Lock& js) KJ_WARN_UNUSED_RESULT;\n  jsg::JsUint8Array computeSecret(jsg::Lock& js, kj::ArrayPtr<kj::byte> key) KJ_WARN_UNUSED_RESULT;\n  jsg::JsUint8Array generateKeys(jsg::Lock& js) KJ_WARN_UNUSED_RESULT;\n\n  kj::Maybe<int> check() KJ_WARN_UNUSED_RESULT;\n\n private:\n  kj::Own<DH> dh;\n};\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/digest.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"digest.h\"\n\n#include \"impl.h\"\n#include \"util.h\"\n\n#include <workerd/api/crypto/crypto.h>\n#include <workerd/io/io-context.h>\n\n#include <openssl/hmac.h>\n#include <openssl/mem.h>\n\nnamespace workerd::api {\nnamespace {\n\nclass HmacKey final: public CryptoKey::Impl {\n public:\n  explicit HmacKey(kj::Array<kj::byte> keyData,\n      CryptoKey::HmacKeyAlgorithm keyAlgorithm,\n      bool extractable,\n      CryptoKeyUsageSet usages)\n      : CryptoKey::Impl(extractable, usages),\n        keyData(kj::mv(keyData)),\n        keyAlgorithm(kj::mv(keyAlgorithm)) {}\n\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"HmacKey\";\n  }\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(HmacKey);\n  }\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {\n    tracker.trackFieldWithSize(\"keyData\", keyData.size());\n    tracker.trackField(\"keyAlgorithm\", keyAlgorithm);\n  }\n\n private:\n  jsg::JsArrayBuffer sign(jsg::Lock& js,\n      SubtleCrypto::SignAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> data) const override {\n    return computeHmac(js, kj::mv(algorithm), data);\n  }\n\n  bool verify(jsg::Lock& js,\n      SubtleCrypto::SignAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> signature,\n      kj::ArrayPtr<const kj::byte> data) const override {\n    auto messageDigest = computeHmac(js, kj::mv(algorithm), data);\n    return messageDigest.size() == signature.size() &&\n        CRYPTO_memcmp(messageDigest.asArrayPtr().begin(), signature.begin(), signature.size()) == 0;\n  }\n\n  jsg::JsArrayBuffer computeHmac(jsg::Lock& js,\n      SubtleCrypto::SignAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> data) const {\n    // For HMAC, the hash is specified when creating the key, not at call time.\n    auto type = lookupDigestAlgorithm(keyAlgorithm.hash.name).second;\n    auto buf = jsg::JsArrayBuffer::create(js, EVP_MD_size(type));\n\n    uint messageDigestSize = 0;\n    auto ptr = HMAC(type, keyData.begin(), keyData.size(), data.begin(), data.size(),\n        buf.asArrayPtr().begin(), &messageDigestSize);\n    JSG_REQUIRE(ptr != nullptr, DOMOperationError, \"HMAC computation failed.\");\n\n    KJ_ASSERT(messageDigestSize == buf.size());\n    return buf;\n  }\n\n  SubtleCrypto::ExportKeyData exportKey(jsg::Lock& js, kj::StringPtr format) const override {\n    JSG_REQUIRE(format == \"raw\" || format == \"jwk\", DOMNotSupportedError,\n        \"Unimplemented key export format \\\"\", format, \"\\\".\");\n\n    if (format == \"jwk\") {\n      // This assert enforces that the slice logic to fill in `.alg` below is safe.\n      JSG_REQUIRE(keyAlgorithm.hash.name.first(4) == \"SHA-\"_kj, DOMNotSupportedError,\n          \"Unimplemented JWK key export format for key algorithm \\\"\", keyAlgorithm.hash.name,\n          \"\\\".\");\n\n      SubtleCrypto::JsonWebKey jwk;\n      jwk.kty = kj::str(\"oct\");\n      jwk.k = fastEncodeBase64Url(keyData);\n      jwk.alg = kj::str(\"HS\", keyAlgorithm.hash.name.slice(4));\n      jwk.key_ops = getUsages().map([](auto usage) { return kj::str(usage.name()); });\n      // I don't know why the spec says:\n      //   Set the ext attribute of jwk to equal the [[extractable]] internal slot of key.\n      // Earlier in the normative part of the spec it says:\n      //   6. If the [[extractable]] internal slot of key is false, then throw an InvalidAccessError.\n      //   7. Let result be the result of performing the export key operation specified by the\n      //      [[algorithm]] internal slot of key using key and format.\n      // So there's not really any other value that `ext` can have here since this code is the\n      // implementation of step 7 (see SubtleCrypto::exportKey where you can confirm it is\n      // enforcing step 6).\n      jwk.ext = true;\n\n      return jwk;\n    }\n\n    return jsg::JsArrayBuffer::create(js, keyData).addRef(js);\n  }\n\n  kj::StringPtr getAlgorithmName() const override {\n    return \"HMAC\";\n  }\n  CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {\n    return keyAlgorithm;\n  }\n\n  bool equals(const CryptoKey::Impl& other) const override final {\n    return this == &other || (other.getType() == \"secret\"_kj && other.equals(keyData));\n  }\n\n  bool equals(const kj::Array<kj::byte>& other) const override final {\n    return keyData.size() == other.size() &&\n        CRYPTO_memcmp(keyData.begin(), other.begin(), keyData.size()) == 0;\n  }\n\n  ZeroOnFree keyData;\n  CryptoKey::HmacKeyAlgorithm keyAlgorithm;\n};\n\nvoid zeroOutTrailingKeyBits(kj::Array<kj::byte>& keyDataArray, int keyBitLength) {\n  // We zero out the least-significant bits of the last byte, matching Chrome's\n  // big-endian behavior when generating keys.\n  int arrayBitLength = keyDataArray.size() * 8;\n  KJ_REQUIRE(arrayBitLength >= keyBitLength);\n  KJ_REQUIRE(arrayBitLength - 8 < keyBitLength);\n\n  if (auto difference = keyBitLength - (arrayBitLength - 8); difference > 0) {\n    keyDataArray.back() &= 0xff00 >> difference;\n  }\n}\n\nkj::Own<HMAC_CTX> initHmacContext(\n    jsg::Lock& js, kj::StringPtr algorithm, HmacContext::KeyData& key) {\n  static constexpr auto handle = [](kj::StringPtr algorithm, kj::ArrayPtr<kj::byte> key) {\n    ClearErrorOnReturn clearErrorOnReturn;\n    JSG_REQUIRE(key.size() <= INT_MAX, RangeError, \"key is too long\");\n    const EVP_MD* md = EVP_get_digestbyname(algorithm.begin());\n    JSG_REQUIRE(md != nullptr, Error, \"Digest method not supported\");\n    static constexpr auto mt = \"\"_kjc;\n    auto hmac_ctx = OSSL_NEW(HMAC_CTX);\n    JSG_REQUIRE(HMAC_Init_ex(hmac_ctx.get(), key.size() ? key.asChars().begin() : mt.begin(),\n                    key.size(), md, nullptr),\n        Error, \"Failed to initalize HMAC\");\n    return kj::mv(hmac_ctx);\n  };\n\n  KJ_SWITCH_ONEOF(key) {\n    KJ_CASE_ONEOF(buf, kj::ArrayPtr<kj::byte>) {\n      return handle(algorithm, buf);\n    }\n    KJ_CASE_ONEOF(key2, CryptoKey::Impl*) {\n      // We already checked that the key is a secret key, so the following should succeed.\n      SubtleCrypto::ExportKeyData keyData = key2->exportKey(js, \"raw\"_kj);\n\n      KJ_SWITCH_ONEOF(keyData) {\n        KJ_CASE_ONEOF(key_data, jsg::JsRef<jsg::JsArrayBuffer>) {\n          auto buf = key_data.getHandle(js);\n          return handle(algorithm, buf.asArrayPtr());\n        }\n        KJ_CASE_ONEOF(jwk, SubtleCrypto::JsonWebKey) {\n          KJ_UNREACHABLE;\n        }\n      }\n    }\n  }\n  KJ_UNREACHABLE;\n}\n}  // namespace\n\nHmacContext::HmacContext(jsg::Lock& js, kj::StringPtr algorithm, KeyData key)\n    : state(initHmacContext(js, algorithm, key)) {}\n\nvoid HmacContext::update(kj::ArrayPtr<kj::byte> data) {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(ctx, kj::Own<HMAC_CTX>) {\n      JSG_REQUIRE(data.size() <= INT_MAX, RangeError, \"data is too long\");\n      KJ_ASSERT(HMAC_Update(ctx.get(), data.begin(), data.size()) == 1);\n    }\n    KJ_CASE_ONEOF(digest, jsg::JsRef<jsg::JsUint8Array>) {\n      JSG_FAIL_REQUIRE(DOMOperationError, \"HMAC context has already been finalized.\");\n    }\n  }\n}\n\njsg::JsUint8Array HmacContext::digest(jsg::Lock& js) {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(ctx, kj::Own<HMAC_CTX>) {\n      auto theCtx = kj::mv(ctx);\n      unsigned len;\n      auto buf = jsg::JsUint8Array::create(js, HMAC_size(theCtx.get()));\n      JSG_REQUIRE(HMAC_Final(theCtx.get(), buf.asArrayPtr().begin(), &len), Error,\n          \"Failed to finalize HMAC\");\n      KJ_ASSERT(len == buf.size());\n      state = buf.addRef(js);\n      return buf;\n    }\n    KJ_CASE_ONEOF(digest, jsg::JsRef<jsg::JsUint8Array>) {\n      auto cached = digest.getHandle(js);\n      return jsg::JsUint8Array::create(js, cached.asArrayPtr());\n    }\n    KJ_UNREACHABLE;\n  }\n  return jsg::JsUint8Array::create(js, 0);\n}\n\nsize_t HmacContext::size() const {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(ctx, kj::Own<HMAC_CTX>) {\n      return 0;\n    }\n    KJ_CASE_ONEOF(digest, jsg::JsRef<jsg::JsUint8Array>) {\n      // JsRef doesn't expose size() without a lock. Return 0 for memory tracking;\n      // the JsRef itself is tracked separately by the GC visitor.\n      return 0;\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> CryptoKey::Impl::generateHmac(jsg::Lock& js,\n    kj::StringPtr normalizedName,\n    SubtleCrypto::GenerateKeyAlgorithm&& algorithm,\n    bool extractable,\n    kj::ArrayPtr<const kj::String> keyUsages) {\n  KJ_REQUIRE(normalizedName == \"HMAC\");\n  kj::StringPtr hash = api::getAlgorithmName(\n      JSG_REQUIRE_NONNULL(algorithm.hash, TypeError, \"Missing field \\\"hash\\\" in \\\"algorithm\\\".\"));\n\n  auto [normalizedHashName, hashEvpMd] = lookupDigestAlgorithm(hash);\n  auto usages = CryptoKeyUsageSet::validate(normalizedName, CryptoKeyUsageSet::Context::generate,\n      keyUsages, CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify());\n\n  // If the user requested a specific HMAC key length, honor it.\n  auto length = algorithm.length.orDefault(EVP_MD_block_size(hashEvpMd) * 8);\n  JSG_REQUIRE(length > 0, DOMOperationError,\n      \"HMAC key length must be a non-zero unsigned long integer (requested \", length, \").\");\n\n  auto keyDataArray = kj::heapArray<kj::byte>(\n      integerCeilDivision<std::make_unsigned_t<decltype(length)>>(length, 8u));\n  IoContext::current().getEntropySource().generate(keyDataArray);\n  zeroOutTrailingKeyBits(keyDataArray, length);\n\n  auto keyAlgorithm = CryptoKey::HmacKeyAlgorithm{\n    normalizedName, {normalizedHashName}, static_cast<uint16_t>(length)};\n\n  return js.alloc<CryptoKey>(\n      kj::heap<HmacKey>(kj::mv(keyDataArray), kj::mv(keyAlgorithm), extractable, usages));\n}\n\nkj::Own<CryptoKey::Impl> CryptoKey::Impl::importHmac(jsg::Lock& js,\n    kj::StringPtr normalizedName,\n    kj::StringPtr format,\n    SubtleCrypto::ImportKeyData keyData,\n    SubtleCrypto::ImportKeyAlgorithm&& algorithm,\n    bool extractable,\n    kj::ArrayPtr<const kj::String> keyUsages) {\n  auto usages =\n      CryptoKeyUsageSet::validate(normalizedName, CryptoKeyUsageSet::Context::importSecret,\n          keyUsages, CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify());\n\n  kj::Array<kj::byte> keyDataArray;\n  kj::StringPtr hash = api::getAlgorithmName(\n      JSG_REQUIRE_NONNULL(algorithm.hash, TypeError, \"Missing field \\\"hash\\\" in \\\"algorithm\\\".\"));\n\n  if (format == \"raw\") {\n    // NOTE: Checked in SubtleCrypto::importKey().\n    keyDataArray = kj::mv(keyData.get<kj::Array<kj::byte>>());\n  } else if (format == \"jwk\") {\n    auto& keyDataJwk = keyData.get<SubtleCrypto::JsonWebKey>();\n    JSG_REQUIRE(keyDataJwk.kty == \"oct\", DOMDataError,\n        \"HMAC \\\"jwk\\\" key import requires a JSON Web Key with Key Type parameter \"\n        \"(\\\"kty\\\") equal to \\\"oct\\\" (encountered \\\"\",\n        keyDataJwk.kty, \"\\\").\");\n    // https://www.rfc-editor.org/rfc/rfc7518.txt Section 6.1\n    keyDataArray = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.k), DOMDataError,\n        \"HMAC \\\"jwk\\\" key import requires a base64Url encoding of the key\");\n\n    KJ_IF_SOME(alg, keyDataJwk.alg) {\n      if (hash.startsWith(\"SHA-\")) {\n        auto expectedAlg = kj::str(\"HS\", hash.slice(4));\n        JSG_REQUIRE(alg == expectedAlg, DOMDataError,\n            \"HMAC \\\"jwk\\\" key import specifies \\\"alg\\\" that is incompatible with the hash name \"\n            \"(encountered \\\"\",\n            alg, \"\\\", expected \\\"\", expectedAlg, \"\\\").\");\n      } else {\n        // TODO(conform): Spec says this for non-SHA hashes:\n        //     > Perform any key import steps defined by other applicable specifications, passing\n        //     > format, jwk and hash and obtaining hash.\n        //   What other hashes should be supported (if any)? For example, technically we support MD5\n        //   below in `lookupDigestAlgorithm` for \"raw\" keys...\n        JSG_FAIL_REQUIRE(\n            DOMNotSupportedError, \"Unrecognized or unimplemented hash algorithm requested\", alg);\n      }\n    }\n  } else {\n    JSG_FAIL_REQUIRE(DOMNotSupportedError, \"Unrecognized key import format \\\"\", format, \"\\\".\");\n  }\n\n  // The spec claims the length of an HMAC key can be up to 7 bits less than the bit length of the\n  // raw key data passed in to `importKey()`. Since the raw key data comes in bytes, that means that\n  // HMAC keys can have non-multiple-of-8 bit lengths. I dutifully implemented this check, but it\n  // seems rather pointless: the OpenSSL HMAC interface only supports key lengths in bytes ...\n  auto keySize = keyDataArray.size() * 8;\n  auto length = algorithm.length.orDefault(keySize);\n  if (length == 0 || length > keySize || length <= keySize - 8) {\n    JSG_FAIL_REQUIRE(DOMDataError, \"Imported HMAC key length (\", length,\n        \") must be a non-zero value up to 7 bits less than, \"\n        \"and no greater than, the bit length of the raw key data (\",\n        keySize, \").\");\n  }\n\n  // Not required by the spec, but zeroing out the unused bits makes me feel better.\n  zeroOutTrailingKeyBits(keyDataArray, length);\n\n  auto normalizedHashName = lookupDigestAlgorithm(hash).first;\n  auto keyAlgorithm = CryptoKey::HmacKeyAlgorithm{\n    normalizedName, {normalizedHashName}, static_cast<uint16_t>(length)};\n  return kj::heap<HmacKey>(kj::mv(keyDataArray), kj::mv(keyAlgorithm), extractable, usages);\n}\n\n// ======================================================================================\n\nnamespace {\nkj::Own<EVP_MD_CTX> initDigestCtx(kj::StringPtr algorithm) {\n  const EVP_MD* md = EVP_get_digestbyname(algorithm.begin());\n  JSG_REQUIRE(md != nullptr, Error, \"Digest method not supported\");\n  auto ctx = OSSL_NEW(EVP_MD_CTX);\n  OSSLCALL(EVP_DigestInit(ctx.get(), md));\n  return kj::mv(ctx);\n}\n\nvoid checkXofLen(EVP_MD_CTX* ctx, kj::Maybe<uint32_t>& maybeXof) {\n  KJ_IF_SOME(xof, maybeXof) {\n    auto md = EVP_MD_CTX_md(ctx);\n    if (xof != EVP_MD_size(md)) {\n      JSG_REQUIRE((EVP_MD_flags(md) & EVP_MD_FLAG_XOF) != 0, Error, \"invalid digest size\");\n    }\n  }\n}\n}  // namespace\n\nHashContext::HashContext(kj::OneOf<kj::Own<EVP_MD_CTX>, jsg::JsRef<jsg::JsUint8Array>> state,\n    kj::Maybe<uint32_t> maybeXof)\n    : state(kj::mv(state)),\n      maybeXof(kj::mv(maybeXof)) {\n  checkXofLen(this->state.get<kj::Own<EVP_MD_CTX>>().get(), this->maybeXof);\n}\n\nHashContext::HashContext(kj::StringPtr algorithm, kj::Maybe<uint32_t> maybeXof)\n    : HashContext(initDigestCtx(algorithm), kj::mv(maybeXof)) {}\n\nvoid HashContext::update(kj::ArrayPtr<kj::byte> data) {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(ctx, kj::Own<EVP_MD_CTX>) {\n      JSG_REQUIRE(data.size() <= INT_MAX, RangeError, \"data is too long\");\n      OSSLCALL(EVP_DigestUpdate(ctx.get(), data.begin(), data.size()));\n    }\n    KJ_CASE_ONEOF(digest, jsg::JsRef<jsg::JsUint8Array>) {\n      JSG_FAIL_REQUIRE(DOMOperationError, \"Hash context has already been finalized.\");\n    }\n  }\n}\n\njsg::JsUint8Array HashContext::digest(jsg::Lock& js) {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(ctx, kj::Own<EVP_MD_CTX>) {\n      auto theCtx = kj::mv(ctx);\n      uint32_t len = EVP_MD_size(EVP_MD_CTX_md(theCtx.get()));\n      KJ_IF_SOME(xof, maybeXof) {\n        if (xof == len) {\n          auto buf = jsg::JsUint8Array::create(js, len);\n          JSG_REQUIRE(EVP_DigestFinal_ex(theCtx.get(), buf.asArrayPtr().begin(), &len) == 1, Error,\n              \"Failed to compute hash digest\");\n          KJ_ASSERT(len == buf.size());\n          state = buf.addRef(js);\n          return buf;\n        }\n\n        auto buf = jsg::JsUint8Array::create(js, xof);\n        JSG_REQUIRE(EVP_DigestFinalXOF(theCtx.get(), buf.asArrayPtr().begin(), xof) == 1, Error,\n            \"Failed to compute XOF hash digest\");\n        state = buf.addRef(js);\n        return buf;\n      }\n\n      auto buf = jsg::JsUint8Array::create(js, len);\n      JSG_REQUIRE(EVP_DigestFinal_ex(theCtx.get(), buf.asArrayPtr().begin(), &len) == 1, Error,\n          \"Failed to compute hash digest\");\n      KJ_ASSERT(len == buf.size());\n      state = buf.addRef(js);\n      return buf;\n    }\n    KJ_CASE_ONEOF(digest, jsg::JsRef<jsg::JsUint8Array>) {\n      auto cached = digest.getHandle(js);\n      return jsg::JsUint8Array::create(js, cached.asArrayPtr());\n    }\n    KJ_UNREACHABLE\n  }\n\n  return jsg::JsUint8Array::create(js, 0);\n}\n\nHashContext HashContext::clone(jsg::Lock& js, kj::Maybe<uint32_t> xofLen) {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(ctx, kj::Own<EVP_MD_CTX>) {\n      auto newCtx = OSSL_NEW(EVP_MD_CTX);\n      OSSLCALL(EVP_MD_CTX_copy_ex(newCtx, ctx.get()));\n      return HashContext(kj::mv(newCtx), kj::mv(xofLen));\n    }\n    KJ_CASE_ONEOF(digest, jsg::JsRef<jsg::JsUint8Array>) {\n      JSG_FAIL_REQUIRE(DOMOperationError, \"Hash context has already been finalized.\");\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nsize_t HashContext::size() const {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(ctx, kj::Own<EVP_MD_CTX>) {\n      return 0;\n    }\n    KJ_CASE_ONEOF(digest, jsg::JsRef<jsg::JsUint8Array>) {\n      // JsRef doesn't expose size() without a lock. Return 0 for memory tracking;\n      // the JsRef itself is tracked separately by the GC visitor.\n      return 0;\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/digest.h",
    "content": "#pragma once\n\n#include \"impl.h\"\n\n#include <workerd/api/crypto/crypto.h>\n\nKJ_DECLARE_NON_POLYMORPHIC(HMAC_CTX)\n\nnamespace workerd::api {\nclass HmacContext final {\n public:\n  using KeyData = kj::OneOf<kj::ArrayPtr<kj::byte>, CryptoKey::Impl*>;\n\n  HmacContext(jsg::Lock& js, kj::StringPtr algorithm, KeyData key);\n  HmacContext(HmacContext&&) = default;\n  HmacContext& operator=(HmacContext&&) = default;\n  KJ_DISALLOW_COPY(HmacContext);\n\n  void update(kj::ArrayPtr<kj::byte> data);\n  jsg::JsUint8Array digest(jsg::Lock& js);\n\n  size_t size() const;\n\n private:\n  // Will be kj::Own<HMAC_CTX> while the HMAC data is being updated,\n  // and jsg::JsRef<jsg::JsUint8Array> after the digest() has been called.\n  kj::OneOf<kj::Own<HMAC_CTX>, jsg::JsRef<jsg::JsUint8Array>> state;\n};\n\nclass HashContext final {\n public:\n  HashContext(kj::StringPtr algorithm, kj::Maybe<uint32_t> maybeXof);\n  HashContext(HashContext&&) = default;\n  HashContext& operator=(HashContext&&) = default;\n  KJ_DISALLOW_COPY(HashContext);\n\n  void update(kj::ArrayPtr<kj::byte> data);\n  jsg::JsUint8Array digest(jsg::Lock& js);\n  HashContext clone(jsg::Lock& js, kj::Maybe<uint32_t> xofLen);\n\n  size_t size() const;\n\n private:\n  HashContext(\n      kj::OneOf<kj::Own<EVP_MD_CTX>, jsg::JsRef<jsg::JsUint8Array>>, kj::Maybe<uint32_t> maybeXof);\n\n  // Will be kj::Own<EVP_MD_CTX> while the hash data is being updated,\n  // and jsg::JsRef<jsg::JsUint8Array> after the digest() has been called.\n  kj::OneOf<kj::Own<EVP_MD_CTX>, jsg::JsRef<jsg::JsUint8Array>> state;\n  kj::Maybe<uint32_t> maybeXof;\n};\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/ec.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"ec.h\"\n\n#include \"impl.h\"\n#include \"keys.h\"\n\n#include <workerd/api/util.h>\n#include <workerd/io/features.h>\n\n#include <openssl/bn.h>\n#include <openssl/crypto.h>\n#include <openssl/curve25519.h>\n#include <openssl/ec_key.h>\n#include <openssl/x509.h>\n\n#include <kj/function.h>\n\n#include <map>\n#include <type_traits>\n\nnamespace workerd::api {\n\nEc::Ec(EC_KEY* key): key(key), x(OSSL_NEW(BIGNUM)), y(OSSL_NEW(BIGNUM)) {\n  KJ_ASSERT(key != nullptr);\n  group = EC_KEY_get0_group(key);\n  JSG_REQUIRE(\n      1 == EC_POINT_get_affine_coordinates(group, getPublicKey(), x.get(), y.get(), nullptr),\n      InternalDOMOperationError, \"Error getting affine coordinates for export\",\n      internalDescribeOpensslErrors());\n}\n\nint Ec::getCurveName() const {\n  return EC_GROUP_get_curve_name(group);\n}\n\nuint32_t Ec::getDegree() const {\n  return EC_GROUP_get_degree(getGroup());\n}\n\nconst EC_POINT* Ec::getPublicKey() const {\n  return EC_KEY_get0_public_key(key);\n}\n\nconst BIGNUM* Ec::getPrivateKey() const {\n  return EC_KEY_get0_private_key(key);\n}\n\nSubtleCrypto::JsonWebKey Ec::toJwk(KeyType keyType, kj::StringPtr curveName) const {\n  JSG_REQUIRE(group != nullptr, DOMOperationError, \"No elliptic curve group in this key\",\n      tryDescribeOpensslErrors());\n  JSG_REQUIRE(getPublicKey() != nullptr, DOMOperationError,\n      \"No public elliptic curve key data in this key\", tryDescribeOpensslErrors());\n\n  auto groupDegreeInBytes = integerCeilDivision(getDegree(), 8u);\n  // EC_GROUP_get_degree returns number of bits. We need this because x, y, & d need to match the\n  // group degree according to JWK.\n\n  SubtleCrypto::JsonWebKey jwk;\n  jwk.kty = kj::str(\"EC\");\n  jwk.crv = kj::str(curveName);\n\n  static constexpr auto handleBn = [](const BIGNUM& bn, size_t size) {\n    return JSG_REQUIRE_NONNULL(bignumToArrayPadded(bn, size), InternalDOMOperationError,\n        \"Error converting EC affine co-ordinates to padded array\", internalDescribeOpensslErrors());\n  };\n\n  auto xa = handleBn(*x, groupDegreeInBytes);\n  auto ya = handleBn(*y, groupDegreeInBytes);\n\n  jwk.x = fastEncodeBase64Url(xa);\n  jwk.y = fastEncodeBase64Url(ya);\n\n  if (keyType == KeyType::PRIVATE) {\n    const auto privateKey = getPrivateKey();\n    JSG_REQUIRE(privateKey != nullptr, InternalDOMOperationError,\n        \"Error getting private key material for JSON Web Key export\",\n        internalDescribeOpensslErrors());\n    auto pk = handleBn(*privateKey, groupDegreeInBytes);\n    jwk.d = fastEncodeBase64Url(pk);\n  }\n  return jwk;\n}\n\njsg::JsArrayBuffer Ec::getRawPublicKey(jsg::Lock& js) const {\n  JSG_REQUIRE_NONNULL(group, InternalDOMOperationError, \"No elliptic curve group in this key\",\n      tryDescribeOpensslErrors());\n  auto publicKey = getPublicKey();\n  JSG_REQUIRE(publicKey != nullptr, InternalDOMOperationError,\n      \"No public elliptic curve key data in this key\", tryDescribeOpensslErrors());\n\n  // Serialize the public key as an uncompressed point in X9.62 form.\n  uint8_t* raw;\n  // The caller takes ownership of the buffer and, unless the buffer was fixed with CBB_init_fixed,\n  // must call OPENSSL_free when done.\n  // https://commondatastorage.googleapis.com/chromium-boringssl-docs/bytestring.h.html#CBB_finish\n  KJ_DEFER(if (raw != nullptr) { OPENSSL_free(raw); });\n  size_t raw_len;\n  CBB cbb;\n\n  JSG_REQUIRE(1 == CBB_init(&cbb, 0), InternalDOMOperationError, \"Failed to init CBB\",\n      internalDescribeOpensslErrors());\n  KJ_DEFER(CBB_cleanup(&cbb));\n\n  JSG_REQUIRE(\n      1 == EC_POINT_point2cbb(&cbb, group, publicKey, POINT_CONVERSION_UNCOMPRESSED, nullptr),\n      InternalDOMOperationError, \"Failed to convert to serialize EC key\",\n      internalDescribeOpensslErrors());\n\n  JSG_REQUIRE(1 == CBB_finish(&cbb, &raw, &raw_len), InternalDOMOperationError,\n      \"Failed to finish CBB\", internalDescribeOpensslErrors());\n\n  return jsg::JsArrayBuffer::create(js, kj::arrayPtr(raw, raw_len));\n}\n\nCryptoKey::AsymmetricKeyDetails Ec::getAsymmetricKeyDetail(jsg::Lock& js) const {\n  // Adapted from Node.js' GetEcKeyDetail\n  return CryptoKey::AsymmetricKeyDetails{\n    .namedCurve = kj::str(OBJ_nid2sn(EC_GROUP_get_curve_name(group)))};\n}\n\nkj::Maybe<Ec> Ec::tryGetEc(const EVP_PKEY* key) {\n  int type = EVP_PKEY_id(key);\n  if (type != EVP_PKEY_EC) return kj::none;\n  auto ec = EVP_PKEY_get0_EC_KEY(key);\n  if (ec == nullptr) return kj::none;\n  return Ec(ec);\n}\n\n// =====================================================================================\n// ECDSA & ECDH\n\nnamespace {\n\nclass EllipticKey final: public AsymmetricKeyCryptoKeyImpl {\n public:\n  explicit EllipticKey(AsymmetricKeyData keyData,\n      CryptoKey::EllipticKeyAlgorithm keyAlgorithm,\n      uint rsSize,\n      bool extractable)\n      : AsymmetricKeyCryptoKeyImpl(kj::mv(keyData), extractable),\n        keyAlgorithm(kj::mv(keyAlgorithm)),\n        rsSize(rsSize) {}\n\n  CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {\n    return keyAlgorithm;\n  }\n  kj::StringPtr getAlgorithmName() const override {\n    return keyAlgorithm.name;\n  }\n\n  void requireSigningAbility() const {\n    // This assert is internal to our WebCrypto implementation because we share the AsymmetricKey\n    // implementation between ECDH & ECDSA (the former only supports deriveBits/deriveKey, not\n    // signing which is the usage for this function).\n    JSG_REQUIRE(keyAlgorithm.name == \"ECDSA\", DOMNotSupportedError,\n        \"The sign and verify operations are not implemented for \\\"\", keyAlgorithm.name, \"\\\".\");\n  }\n\n  kj::StringPtr chooseHash(\n      const kj::Maybe<kj::OneOf<kj::String, SubtleCrypto::HashAlgorithm>>& callTimeHash)\n      const override {\n    requireSigningAbility();\n\n    // ECDSA infamously expects the hash to be specified at call time.\n    // See: https://github.com/w3c/webcrypto/issues/111\n    return api::getAlgorithmName(JSG_REQUIRE_NONNULL(callTimeHash, TypeError,\n        \"Missing \\\"hash\\\" in AlgorithmIdentifier. (ECDSA requires that the hash algorithm be \"\n        \"specified at call time rather than on the key. This differs from other WebCrypto \"\n        \"algorithms for historical reasons.)\"));\n  }\n\n  jsg::JsArrayBuffer deriveBits(jsg::Lock& js,\n      SubtleCrypto::DeriveKeyAlgorithm&& algorithm,\n      kj::Maybe<uint32_t> resultBitLength) const override final {\n    JSG_REQUIRE(keyAlgorithm.name == \"ECDH\", DOMNotSupportedError,\n        \"\"\n        \"The deriveBits operation is not implemented for \\\"\",\n        keyAlgorithm.name, \"\\\".\");\n\n    JSG_REQUIRE(getTypeEnum() == KeyType::PRIVATE, DOMInvalidAccessError,\n        \"\"\n        \"The deriveBits operation is only valid for a private key, not \\\"\",\n        getType(), \"\\\".\");\n\n    auto& publicKey = JSG_REQUIRE_NONNULL(\n        algorithm.$public, TypeError, \"Missing field \\\"public\\\" in \\\"derivedKeyParams\\\".\");\n\n    JSG_REQUIRE(publicKey->getType() == \"public\"_kj, DOMInvalidAccessError,\n        \"\"\n        \"The provided key has type \\\"\",\n        publicKey->getType(), \"\\\", not \\\"public\\\"\");\n\n    JSG_REQUIRE(getAlgorithm(js).which() == publicKey->getAlgorithm(js).which(),\n        DOMInvalidAccessError, \"Base \", getAlgorithmName(),\n        \" private key cannot be used to derive\"\n        \" a key from a peer \",\n        publicKey->getAlgorithmName(), \" public key\");\n\n    JSG_REQUIRE(getAlgorithmName() == publicKey->getAlgorithmName(), DOMInvalidAccessError,\n        \"Private key for derivation is using \\\"\", getAlgorithmName(),\n        \"\\\" while public key is using \\\"\", publicKey->getAlgorithmName(), \"\\\".\");\n\n    auto publicCurve =\n        publicKey->getAlgorithm(js).get<CryptoKey::EllipticKeyAlgorithm>().namedCurve;\n    JSG_REQUIRE(keyAlgorithm.namedCurve == publicCurve, DOMInvalidAccessError,\n        \"Private key for derivation is using curve \\\"\", keyAlgorithm.namedCurve,\n        \"\\\" while public key is using \\\"\", publicCurve, \"\\\".\");\n\n    // The check above for the algorithm `which` equality ensures that the impl can be downcast to\n    // EllipticKey (assuming we don't accidentally create a class that doesn't inherit this one that\n    // for some reason returns an EllipticKey).\n    auto& publicKeyImpl = kj::downcast<EllipticKey>(*publicKey->impl);\n\n    // Adapted from https://wiki.openssl.org/index.php/Elliptic_Curve_Diffie_Hellman:\n    auto privateEcKey = JSG_REQUIRE_NONNULL(Ec::tryGetEc(getEvpPkey()), InternalDOMOperationError,\n        \"No elliptic curve data backing key\", tryDescribeOpensslErrors());\n    auto publicEcKey =\n        JSG_REQUIRE_NONNULL(Ec::tryGetEc(publicKeyImpl.getEvpPkey()), InternalDOMOperationError,\n            \"No elliptic curve data backing key\", tryDescribeOpensslErrors());\n    JSG_REQUIRE(publicEcKey.getPublicKey() != nullptr, DOMOperationError,\n        \"No public elliptic curve key data in this key\", tryDescribeOpensslErrors());\n    auto fieldSize = privateEcKey.getDegree();\n\n    // Assuming that `fieldSize` will always be a sane value since it's related to the keys we\n    // construct in C++ (i.e. not untrusted user input).\n\n    kj::Vector<kj::byte> sharedSecret;\n    sharedSecret.resize(\n        integerCeilDivision<std::make_unsigned_t<decltype(fieldSize)>>(fieldSize, 8u));\n    auto written = ECDH_compute_key(sharedSecret.begin(), sharedSecret.capacity(),\n        publicEcKey.getPublicKey(), privateEcKey.getKey(), nullptr);\n    JSG_REQUIRE(written > 0, DOMOperationError, \"Failed to generate shared ECDH secret\",\n        tryDescribeOpensslErrors());\n\n    sharedSecret.resize(written);\n\n    auto outputBitLength = resultBitLength.orDefault(sharedSecret.size() * 8);\n    JSG_REQUIRE(outputBitLength <= sharedSecret.size() * 8, DOMOperationError,\n        \"Derived key length (\", outputBitLength, \" bits) is too long (should be at most \",\n        sharedSecret.size() * 8, \" bits).\");\n\n    // Round up since outputBitLength may not be a perfect multiple of 8.\n    // However, the last byte may now have bits that have leaked which we handle below.\n    auto resultByteLength = integerCeilDivision(outputBitLength, 8u);\n    sharedSecret.truncate(resultByteLength);\n\n    // We have to remember to mask off the bits that weren't requested (if a non multiple of 8 was\n    // passed in). NOTE: The conformance tests DO NOT appear to test for this. This is my reading of\n    // the spec, combining:\n    //   * ECDH: Return an octet string containing the first length bits of secret.\n    //   * octet string: b is the octet string obtained by first appending zero or more bits of\n    //                   value zero to b such that the length of the resulting bit string is minimal\n    //                   and an integer multiple of 8.\n    auto numBitsToMaskOff = resultByteLength * 8 - outputBitLength;\n    KJ_DASSERT(numBitsToMaskOff < 8, numBitsToMaskOff);\n\n    // The mask should have `numBitsToMaskOff` bits set to 0 from least significant to most.\n    // 0 = 1 1 1 1 1 1 1 1 (0xFF)\n    // 1 = 1 1 1 1 1 1 1 0 (0xFE)\n    // 2 = 1 1 1 1 1 1 0 0 (0xFD)\n    // 3 = 1 1 1 1 1 0 0 0 (0xFC)\n    // Let's rewrite this to have the lower bits set to 1 since that's typically the easier form to\n    // generate with bit twiddling.\n    // 0 = 0 0 0 0 0 0 0 0 (0)\n    // 1 = 0 0 0 0 0 0 0 1 (1)\n    // 2 = 0 0 0 0 0 0 1 1 (3)\n    // 3 = 0 0 0 0 0 1 1 1 (7)\n    // The pattern seems pretty clearly ~(2^n - 1) where n is the number of bits to mask off. Let's\n    // check the last one though (8 is not a possible boundary condition).\n    // (2^7 - 1) = 0x7f => ~0x7f = 0x80 (when truncated to a byte)\n    if (numBitsToMaskOff) {\n      uint8_t mask = ~((1 << numBitsToMaskOff) - 1);\n      sharedSecret.back() &= mask;\n    }\n\n    return jsg::JsArrayBuffer::create(js, sharedSecret.asPtr());\n  }\n\n  jsg::JsArrayBuffer signatureSslToWebCrypto(\n      jsg::Lock& js, kj::ArrayPtr<kj::byte> signature) const override {\n    // An EC signature is two big integers \"r\" and \"s\". WebCrypto wants us to just concatenate both\n    // integers, using a constant size of each that depends on the curve size. OpenSSL wants to\n    // encode them in some ASN.1 wrapper with variable-width sizes. Ugh.\n\n    requireSigningAbility();\n\n    // Manually decode ASN.1 BER.\n    KJ_ASSERT(signature.size() >= 6);\n    KJ_ASSERT(signature[0] == 0x30);\n    kj::ArrayPtr<const kj::byte> rest;\n    if (signature[1] < 128) {\n      KJ_ASSERT(signature[1] == signature.size() - 2);\n      rest = signature.slice(2, signature.size());\n    } else {\n      // Size of message did not fit in 7 bits, so the first byte encodes the size-of-size, but it\n      // will always fit in 8 bits so the size-of-size will always be 1 (plus 128 because top bit\n      // is set).\n      KJ_ASSERT(signature[1] == 129);\n      KJ_ASSERT(signature[2] == signature.size() - 3);\n      rest = signature.slice(3, signature.size());\n    }\n\n    KJ_ASSERT(rest.size() >= 2);\n    KJ_ASSERT(rest[0] == 0x02);\n    size_t rSize = rest[1];\n    KJ_ASSERT(rest.size() >= 2 + rSize);\n    auto r = rest.slice(2, 2 + rSize);\n\n    rest = rest.slice(2 + rSize, rest.size());\n\n    KJ_ASSERT(rest.size() >= 2);\n    KJ_ASSERT(rest[0] == 0x02);\n    size_t sSize = rest[1];\n    KJ_ASSERT(rest.size() == 2 + sSize);\n    auto s = rest.slice(2, 2 + sSize);\n\n    // If the top bit is set, BER encoding will add an extra 0-byte prefix to disambiguate from a\n    // negative number. Uggghhh.\n    while (r.size() > rsSize && r[0] == 0) r = r.slice(1, r.size());\n    while (s.size() > rsSize && s[0] == 0) s = s.slice(1, s.size());\n    KJ_ASSERT(r.size() <= rsSize);\n    KJ_ASSERT(s.size() <= rsSize);\n\n    // Construct WebCrypto format.\n    auto out = jsg::JsArrayBuffer::create(js, rsSize * 2);\n    auto outPtr = out.asArrayPtr();\n\n    // We're dealing with big-endian, so we have to align the copy to the right. This is exactly\n    // why big-endian is the wrong endian.\n    outPtr.slice(rsSize - r.size(), rsSize).copyFrom(r);\n    outPtr.slice(rsSize * 2 - s.size(), rsSize * 2).copyFrom(s);\n    return out;\n  }\n\n  jsg::JsArrayBuffer signatureWebCryptoToSsl(\n      jsg::Lock& js, kj::ArrayPtr<const kj::byte> signature) const override {\n    requireSigningAbility();\n\n    if (signature.size() != rsSize * 2) {\n      // The signature is the wrong size. Return an empty signature, which will be judged invalid.\n      return jsg::JsArrayBuffer::create(js, 0);\n    }\n\n    auto r = signature.first(rsSize);\n    auto s = signature.slice(rsSize, signature.size());\n\n    // Trim leading zeros.\n    while (r.size() > 1 && r[0] == 0) r = r.slice(1, r.size());\n    while (s.size() > 1 && s[0] == 0) s = s.slice(1, s.size());\n\n    // If the most significant bit is set, we have to add a zero, ugh.\n    bool padR = r[0] >= 128;\n    bool padS = s[0] >= 128;\n\n    size_t bodySize = 4 + padR + padS + r.size() + s.size();\n    size_t resultSize = 2 + bodySize + (bodySize >= 128);\n    auto result = jsg::JsArrayBuffer::create(js, resultSize);\n\n    kj::byte* pos = result.asArrayPtr().begin();\n    *pos++ = 0x30;\n    if (bodySize < 128) {\n      *pos++ = bodySize;\n    } else {\n      *pos++ = 129;\n      *pos++ = bodySize;\n    }\n\n    *pos++ = 0x02;\n    *pos++ = r.size() + padR;\n    if (padR) *pos++ = 0;\n    memcpy(pos, r.begin(), r.size());\n    pos += r.size();\n\n    *pos++ = 0x02;\n    *pos++ = s.size() + padS;\n    if (padS) *pos++ = 0;\n    memcpy(pos, s.begin(), s.size());\n    pos += s.size();\n\n    KJ_ASSERT(pos == result.asArrayPtr().end());\n\n    return result;\n  }\n\n  static kj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> generateElliptic(jsg::Lock& js,\n      kj::StringPtr normalizedName,\n      SubtleCrypto::GenerateKeyAlgorithm&& algorithm,\n      bool extractable,\n      CryptoKeyUsageSet privateKeyUsages,\n      CryptoKeyUsageSet publicKeyUsages);\n\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"EllipticKey\";\n  }\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(EllipticKey);\n  }\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {\n    AsymmetricKeyCryptoKeyImpl::jsgGetMemoryInfo(tracker);\n    tracker.trackField(\"keyAlgorithm\", keyAlgorithm);\n  }\n\n private:\n  SubtleCrypto::JsonWebKey exportJwk() const override final {\n    auto ec = JSG_REQUIRE_NONNULL(Ec::tryGetEc(getEvpPkey()), DOMOperationError,\n        \"No elliptic curve data backing key\", tryDescribeOpensslErrors());\n    return ec.toJwk(getTypeEnum(), kj::str(keyAlgorithm.namedCurve));\n  }\n\n  jsg::JsArrayBuffer exportRaw(jsg::Lock& js) const override final {\n    JSG_REQUIRE(getTypeEnum() == KeyType::PUBLIC, DOMInvalidAccessError,\n        \"Raw export of elliptic curve keys is only allowed for public keys.\");\n    return JSG_REQUIRE_NONNULL(Ec::tryGetEc(getEvpPkey()), InternalDOMOperationError,\n        \"No elliptic curve data backing key\", tryDescribeOpensslErrors())\n        .getRawPublicKey(js);\n  }\n\n  CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail(jsg::Lock& js) const override {\n    // Adapted from Node.js' GetEcKeyDetail\n    return KJ_ASSERT_NONNULL(Ec::tryGetEc(getEvpPkey())).getAsymmetricKeyDetail(js);\n  }\n\n  CryptoKey::EllipticKeyAlgorithm keyAlgorithm;\n  uint rsSize;\n};\n\nstruct EllipticCurveInfo {\n  kj::StringPtr normalizedName;\n  int opensslCurveId;\n  uint rsSize;  // size of \"r\" and \"s\" in the signature\n};\n\nEllipticCurveInfo lookupEllipticCurve(kj::StringPtr curveName) {\n  static const std::map<kj::StringPtr, EllipticCurveInfo, CiLess> registeredCurves{\n    {\"P-256\", {\"P-256\", NID_X9_62_prime256v1, 32}},\n    {\"P-384\", {\"P-384\", NID_secp384r1, 48}},\n    {\"P-521\", {\"P-521\", NID_secp521r1, 66}},\n  };\n\n  auto iter = registeredCurves.find(curveName);\n  JSG_REQUIRE(iter != registeredCurves.end(), DOMNotSupportedError,\n      \"Unrecognized or unimplemented EC curve \\\"\", curveName, \"\\\" requested.\");\n  return iter->second;\n}\n\nkj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> EllipticKey::generateElliptic(jsg::Lock& js,\n    kj::StringPtr normalizedName,\n    SubtleCrypto::GenerateKeyAlgorithm&& algorithm,\n    bool extractable,\n    CryptoKeyUsageSet privateKeyUsages,\n    CryptoKeyUsageSet publicKeyUsages) {\n  kj::StringPtr namedCurve = JSG_REQUIRE_NONNULL(\n      algorithm.namedCurve, TypeError, \"Missing field \\\"namedCurve\\\" in \\\"algorithm\\\".\");\n\n  auto [normalizedNamedCurve, curveId, rsSize] = lookupEllipticCurve(namedCurve);\n\n  auto keyAlgorithm = CryptoKey::EllipticKeyAlgorithm{\n    normalizedName,\n    normalizedNamedCurve,\n  };\n\n  // Used OpenBSD man pages starting with https://man.openbsd.org/ECDSA_SIG_new.3 for functions and\n  // CryptoKey::Impl::generateRsa as a template.\n  // https://stackoverflow.com/questions/18155559/how-does-one-access-the-raw-ecdh-public-key-private-key-and-params-inside-opens\n  // for the reference on how to deserialize the public/private key.\n\n  auto ecPrivateKey =\n      OSSLCALL_OWN(EC_KEY, EC_KEY_new_by_curve_name(curveId), InternalDOMOperationError,\n          \"Error generating EC \\\"\", namedCurve, \"\\\" key\", internalDescribeOpensslErrors());\n  OSSLCALL(EC_KEY_generate_key(ecPrivateKey));\n\n  auto privateEvpPKey = OSSL_NEW(EVP_PKEY);\n  OSSLCALL(EVP_PKEY_set1_EC_KEY(privateEvpPKey.get(), ecPrivateKey.get()));\n\n  auto ecPublicKey =\n      OSSLCALL_OWN(EC_KEY, EC_KEY_new_by_curve_name(curveId), InternalDOMOperationError,\n          \"Error generating EC \\\"\", namedCurve, \"\\\" key\", internalDescribeOpensslErrors());\n  OSSLCALL(EC_KEY_set_public_key(ecPublicKey, EC_KEY_get0_public_key(ecPrivateKey)));\n  auto publicEvpPKey = OSSL_NEW(EVP_PKEY);\n  OSSLCALL(EVP_PKEY_set1_EC_KEY(publicEvpPKey.get(), ecPublicKey.get()));\n\n  AsymmetricKeyData privateKeyData{\n    .evpPkey = kj::mv(privateEvpPKey),\n    .keyType = KeyType::PRIVATE,\n    .usages = privateKeyUsages,\n  };\n  AsymmetricKeyData publicKeyData{\n    .evpPkey = kj::mv(publicEvpPKey),\n    .keyType = KeyType::PUBLIC,\n    .usages = publicKeyUsages,\n  };\n\n  auto privateKey = js.alloc<CryptoKey>(\n      kj::heap<EllipticKey>(kj::mv(privateKeyData), keyAlgorithm, rsSize, extractable));\n  auto publicKey =\n      js.alloc<CryptoKey>(kj::heap<EllipticKey>(kj::mv(publicKeyData), keyAlgorithm, rsSize, true));\n\n  return CryptoKeyPair{.publicKey = kj::mv(publicKey), .privateKey = kj::mv(privateKey)};\n}\n\nAsymmetricKeyData importEllipticRaw(SubtleCrypto::ImportKeyData keyData,\n    int curveId,\n    kj::StringPtr normalizedName,\n    kj::ArrayPtr<const kj::String> keyUsages,\n    CryptoKeyUsageSet allowedUsages) {\n  // Import an elliptic key represented by raw data, only public keys are supported.\n  JSG_REQUIRE(keyData.is<kj::Array<kj::byte>>(), DOMDataError,\n      \"Expected raw EC key but instead got a Json Web Key.\");\n\n  const auto& raw = keyData.get<kj::Array<kj::byte>>();\n\n  auto usages = CryptoKeyUsageSet::validate(\n      normalizedName, CryptoKeyUsageSet::Context::importPublic, keyUsages, allowedUsages);\n\n  if (curveId == NID_ED25519 || curveId == NID_X25519) {\n    auto evpId = curveId == NID_X25519 ? EVP_PKEY_X25519 : EVP_PKEY_ED25519;\n    auto curveName = curveId == NID_X25519 ? \"X25519\" : \"Ed25519\";\n\n    JSG_REQUIRE(raw.size() == 32, DOMDataError, curveName,\n        \" raw keys must be exactly 32-bytes \"\n        \"(provided \",\n        raw.size(), \").\");\n\n    return {\n      OSSLCALL_OWN(EVP_PKEY, EVP_PKEY_new_raw_public_key(evpId, nullptr, raw.begin(), raw.size()),\n          InternalDOMOperationError, \"Failed to import raw public EDDSA\", raw.size(),\n          internalDescribeOpensslErrors()),\n      KeyType::PUBLIC, usages};\n  }\n\n  auto ecKey = OSSLCALL_OWN(EC_KEY, EC_KEY_new_by_curve_name(curveId), DOMOperationError,\n      \"Error importing EC key\", tryDescribeOpensslErrors());\n  auto ecGroup = EC_KEY_get0_group(ecKey.get());\n\n  auto point = OSSL_NEW(EC_POINT, ecGroup);\n  JSG_REQUIRE(1 == EC_POINT_oct2point(ecGroup, point.get(), raw.begin(), raw.size(), nullptr),\n      DOMDataError, \"Failed to import raw EC key data\", tryDescribeOpensslErrors());\n  JSG_REQUIRE(1 == EC_KEY_set_public_key(ecKey.get(), point.get()), InternalDOMOperationError,\n      \"Failed to set EC raw public key\", internalDescribeOpensslErrors());\n  JSG_REQUIRE(1 == EC_KEY_check_key(ecKey.get()), DOMDataError, \"Invalid raw EC key provided\",\n      tryDescribeOpensslErrors());\n\n  auto evpPkey = OSSL_NEW(EVP_PKEY);\n  OSSLCALL(EVP_PKEY_set1_EC_KEY(evpPkey.get(), ecKey.get()));\n\n  return AsymmetricKeyData{kj::mv(evpPkey), KeyType::PUBLIC, usages};\n}\n\nkj::Own<EVP_PKEY> ellipticJwkReader(\n    int curveId, SubtleCrypto::JsonWebKey&& keyDataJwk, kj::StringPtr normalizedName) {\n  if (curveId == NID_ED25519 || curveId == NID_X25519) {\n    auto evpId = curveId == NID_X25519 ? EVP_PKEY_X25519 : EVP_PKEY_ED25519;\n    auto curveName = curveId == NID_X25519 ? \"X25519\" : \"Ed25519\";\n\n    JSG_REQUIRE(keyDataJwk.kty == \"OKP\", DOMDataError, curveName,\n        \" \\\"jwk\\\" key imports requires a JSON Web Key with Key Type parameter \"\n        \"\\\"kty\\\" (\\\"\",\n        keyDataJwk.kty, \"\\\") equal to \\\"OKP\\\".\");\n    auto& crv = JSG_REQUIRE_NONNULL(\n        keyDataJwk.crv, DOMDataError, \"Missing field \\\"crv\\\" for \", curveName, \" key.\");\n    JSG_REQUIRE(crv == curveName, DOMNotSupportedError, \"Only \", curveName, \" is supported but \\\"\",\n        crv, \"\\\" was requested.\");\n    KJ_IF_SOME(alg, keyDataJwk.alg) {\n      // If this JWK specifies an algorithm, make sure it jives with the hash we were passed via\n      // importKey().\n      if (curveId == NID_ED25519) {\n        JSG_REQUIRE(alg == \"EdDSA\", DOMDataError, \"JSON Web Key Algorithm parameter \\\"alg\\\" (\\\"\",\n            alg,\n            \"\\\") does not match requested \"\n            \"Ed25519 curve.\");\n      }\n    }\n\n    auto x = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.x), DOMDataError, \"Invalid \", crv,\n        \" key in JSON WebKey; missing or invalid public key component (\\\"x\\\").\");\n    JSG_REQUIRE(x.size() == 32, DOMDataError, \"Invalid length \", x.size(), \" for public key\");\n\n    if (keyDataJwk.d == kj::none) {\n      // This is a public key.\n      return OSSLCALL_OWN(EVP_PKEY,\n          EVP_PKEY_new_raw_public_key(evpId, nullptr, x.begin(), x.size()),\n          InternalDOMOperationError, \"Failed to construct \", crv, \" public key\",\n          internalDescribeOpensslErrors());\n    }\n\n    // This is a private key. The Section 2 of the RFC says...\n    // >  The parameter \"x\" MUST be present and contain the public key encoded using the base64url\n    // >  [RFC4648] encoding.\n    // https://tools.ietf.org/html/draft-ietf-jose-cfrg-curves-06\n    // ... but there's nothing really to do beside enforce that it's set? The NodeJS implementation\n    // seems to throw it away when a private key is provided.\n\n    auto d = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.d), DOMDataError, \"Invalid \", curveName,\n        \" key in JSON Web Key; missing or invalid private key component (\\\"d\\\").\");\n    JSG_REQUIRE(d.size() == 32, DOMDataError, \"Invalid length \", d.size(), \" for private key\");\n\n    return OSSLCALL_OWN(EVP_PKEY, EVP_PKEY_new_raw_private_key(evpId, nullptr, d.begin(), d.size()),\n        InternalDOMOperationError, \"Failed to construct \", crv, \" private key\",\n        internalDescribeOpensslErrors());\n  }\n\n  JSG_REQUIRE(keyDataJwk.kty == \"EC\", DOMDataError,\n      \"Elliptic curve \\\"jwk\\\" key import requires a JSON Web Key with Key Type parameter \"\n      \"\\\"kty\\\" (\\\"\",\n      keyDataJwk.kty, \"\\\") equal to \\\"EC\\\".\");\n\n  if (normalizedName == \"ECDSA\") {\n    KJ_IF_SOME(alg, keyDataJwk.alg) {\n      // If this JWK specifies an algorithm, make sure it jives with the hash we were passed via\n      // importKey().\n      static const std::map<kj::StringPtr, int> ecdsaAlgorithms{\n        {\"ES256\", NID_X9_62_prime256v1},\n        {\"ES384\", NID_secp384r1},\n        {\"ES512\", NID_secp521r1},\n      };\n\n      auto iter = ecdsaAlgorithms.find(alg);\n      JSG_REQUIRE(iter != ecdsaAlgorithms.end(), DOMNotSupportedError,\n          \"Unrecognized or unimplemented algorithm \\\"\", alg,\n          \"\\\" listed in JSON Web Key Algorithm parameter.\");\n\n      JSG_REQUIRE(iter->second == curveId, DOMDataError,\n          \"JSON Web Key Algorithm parameter \\\"alg\\\" (\\\"\", alg,\n          \"\\\") does not match requested curve.\");\n    }\n  }\n\n  auto ecKey = OSSLCALL_OWN(EC_KEY, EC_KEY_new_by_curve_name(curveId), DOMOperationError,\n      \"Error importing EC key\", tryDescribeOpensslErrors());\n\n  auto x = UNWRAP_JWK_BIGNUM(\n      kj::mv(keyDataJwk.x), DOMDataError, \"Invalid EC key in JSON Web Key; missing \\\"x\\\".\");\n  auto y = UNWRAP_JWK_BIGNUM(\n      kj::mv(keyDataJwk.y), DOMDataError, \"Invalid EC key in JSON Web Key; missing \\\"y\\\".\");\n\n  auto group = EC_KEY_get0_group(ecKey);\n\n  auto bigX = JSG_REQUIRE_NONNULL(toBignum(x), InternalDOMOperationError, \"Error importing EC key\",\n      internalDescribeOpensslErrors());\n  auto bigY = JSG_REQUIRE_NONNULL(toBignum(y), InternalDOMOperationError, \"Error importing EC key\",\n      internalDescribeOpensslErrors());\n\n  auto point = OSSL_NEW(EC_POINT, group);\n  JSG_REQUIRE(1 == EC_POINT_set_affine_coordinates_GFp(group, point, bigX, bigY, nullptr),\n      DOMOperationError, \"Invalid EC key; public key coordinates \\\"x\\\" and \\\"y\\\" are invalid\",\n      tryDescribeOpensslErrors());\n  JSG_REQUIRE(1 == EC_KEY_set_public_key(ecKey, point), DOMOperationError,\n      \"Invalid EC key; public key coordinates \\\"x\\\" and \\\"y\\\" are invalid\",\n      tryDescribeOpensslErrors());\n\n  if (keyDataJwk.d != kj::none) {\n    // This is a private key.\n\n    auto d = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.d), DOMDataError,\n        \"Invalid EC key in JSON Web Key; missing or invalid private key component (\\\"d\\\").\");\n\n    auto bigD = JSG_REQUIRE_NONNULL(toBignum(d), InternalDOMOperationError,\n        \"Error importing EC key\", internalDescribeOpensslErrors());\n\n    JSG_REQUIRE(1 == EC_KEY_set_private_key(ecKey, bigD), DOMOperationError,\n        \"Invalid EC key; \"\n        \"private key component \\\"d\\\" is invalid\",\n        tryDescribeOpensslErrors());\n  }\n\n  JSG_REQUIRE(1 == EC_KEY_check_key(ecKey.get()), DOMDataError, \"Invalid EC key in JSON Web Key\",\n      tryDescribeOpensslErrors());\n\n  auto evpPkey = OSSL_NEW(EVP_PKEY);\n  JSG_REQUIRE(1 == EVP_PKEY_set1_EC_KEY(evpPkey.get(), ecKey.get()), DOMOperationError,\n      \"Error importing EC key\", tryDescribeOpensslErrors());\n  return evpPkey;\n}\n}  // namespace\n\nkj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> CryptoKey::Impl::generateEcdsa(jsg::Lock& js,\n    kj::StringPtr normalizedName,\n    SubtleCrypto::GenerateKeyAlgorithm&& algorithm,\n    bool extractable,\n    kj::ArrayPtr<const kj::String> keyUsages) {\n  auto usages = CryptoKeyUsageSet::validate(normalizedName, CryptoKeyUsageSet::Context::generate,\n      keyUsages, CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify());\n  auto privateKeyUsages = usages & CryptoKeyUsageSet::privateKeyMask();\n  auto publicKeyUsages = usages & CryptoKeyUsageSet::publicKeyMask();\n\n  return EllipticKey::generateElliptic(\n      js, normalizedName, kj::mv(algorithm), extractable, privateKeyUsages, publicKeyUsages);\n}\n\nkj::Own<CryptoKey::Impl> CryptoKey::Impl::importEcdsa(jsg::Lock& js,\n    kj::StringPtr normalizedName,\n    kj::StringPtr format,\n    SubtleCrypto::ImportKeyData keyData,\n    SubtleCrypto::ImportKeyAlgorithm&& algorithm,\n    bool extractable,\n    kj::ArrayPtr<const kj::String> keyUsages) {\n  kj::StringPtr namedCurve = JSG_REQUIRE_NONNULL(\n      algorithm.namedCurve, TypeError, \"Missing field \\\"namedCurve\\\" in \\\"algorithm\\\".\");\n\n  auto [normalizedNamedCurve, curveId, rsSize] = lookupEllipticCurve(namedCurve);\n\n  auto importedKey = [&, curveId = curveId] {\n    if (format != \"raw\") {\n      return importAsymmetricForWebCrypto(js, format, kj::mv(keyData), normalizedName, extractable,\n          keyUsages,\n          // Verbose lambda capture needed because: https://bugs.llvm.org/show_bug.cgi?id=35984\n          [curveId = curveId, normalizedName = kj::str(normalizedName)](\n              SubtleCrypto::JsonWebKey keyDataJwk) -> kj::Own<EVP_PKEY> {\n        return ellipticJwkReader(curveId, kj::mv(keyDataJwk), normalizedName);\n      },\n          CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify());\n    } else {\n      return importEllipticRaw(\n          kj::mv(keyData), curveId, normalizedName, keyUsages, CryptoKeyUsageSet::verify());\n    }\n  }();\n\n  // get0 avoids adding a refcount...\n  auto ecKey = JSG_REQUIRE_NONNULL(Ec::tryGetEc(importedKey.evpPkey.get()), DOMDataError,\n      \"Input was not an EC key\", tryDescribeOpensslErrors());\n\n  // Verify namedCurve matches what was specified in the key data.\n  JSG_REQUIRE(ecKey.getGroup() != nullptr && ecKey.getCurveName() == curveId, DOMDataError,\n      \"\\\"algorithm.namedCurve\\\" \\\"\", namedCurve,\n      \"\\\" does not match the curve specified by the \"\n      \"input key data\",\n      tryDescribeOpensslErrors());\n\n  auto keyAlgorithm = CryptoKey::EllipticKeyAlgorithm{\n    normalizedName,\n    normalizedNamedCurve,\n  };\n\n  return kj::heap<EllipticKey>(kj::mv(importedKey), kj::mv(keyAlgorithm), rsSize, extractable);\n}\n\nkj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> CryptoKey::Impl::generateEcdh(jsg::Lock& js,\n    kj::StringPtr normalizedName,\n    SubtleCrypto::GenerateKeyAlgorithm&& algorithm,\n    bool extractable,\n    kj::ArrayPtr<const kj::String> keyUsages) {\n  auto usages = CryptoKeyUsageSet::validate(normalizedName, CryptoKeyUsageSet::Context::generate,\n      keyUsages, CryptoKeyUsageSet::derivationKeyMask());\n  return EllipticKey::generateElliptic(\n      js, normalizedName, kj::mv(algorithm), extractable, usages, {});\n}\n\nkj::Own<CryptoKey::Impl> CryptoKey::Impl::importEcdh(jsg::Lock& js,\n    kj::StringPtr normalizedName,\n    kj::StringPtr format,\n    SubtleCrypto::ImportKeyData keyData,\n    SubtleCrypto::ImportKeyAlgorithm&& algorithm,\n    bool extractable,\n    kj::ArrayPtr<const kj::String> keyUsages) {\n  kj::StringPtr namedCurve = JSG_REQUIRE_NONNULL(\n      algorithm.namedCurve, TypeError, \"Missing field \\\"namedCurve\\\" in \\\"algorithm\\\".\");\n\n  auto [normalizedNamedCurve, curveId, rsSize] = lookupEllipticCurve(namedCurve);\n\n  auto importedKey = [&, curveId = curveId] {\n    auto strictCrypto = FeatureFlags::get(js).getStrictCrypto();\n    auto usageSet = strictCrypto ? CryptoKeyUsageSet() : CryptoKeyUsageSet::derivationKeyMask();\n\n    if (format != \"raw\") {\n      return importAsymmetricForWebCrypto(js, format, kj::mv(keyData), normalizedName, extractable,\n          keyUsages,\n          // Verbose lambda capture needed because: https://bugs.llvm.org/show_bug.cgi?id=35984\n          [curveId = curveId, normalizedName = kj::str(normalizedName)](\n              SubtleCrypto::JsonWebKey keyDataJwk) -> kj::Own<EVP_PKEY> {\n        return ellipticJwkReader(curveId, kj::mv(keyDataJwk), normalizedName);\n      },\n          CryptoKeyUsageSet::derivationKeyMask());\n    } else {\n      // The usage set is required to be empty for public ECDH keys, including raw keys.\n      return importEllipticRaw(kj::mv(keyData), curveId, normalizedName, keyUsages, usageSet);\n    }\n  }();\n\n  auto ecKey = JSG_REQUIRE_NONNULL(Ec::tryGetEc(importedKey.evpPkey.get()), DOMDataError,\n      \"Input was not an EC public key nor a DH key\", tryDescribeOpensslErrors());\n\n  // We ignore id-ecDH because BoringSSL doesn't implement this.\n  // https://bugs.chromium.org/p/chromium/issues/detail?id=532728\n  // https://bugs.chromium.org/p/chromium/issues/detail?id=389400\n\n  // Verify namedCurve matches what was specified in the key data.\n  JSG_REQUIRE(ecKey.getGroup() != nullptr && ecKey.getCurveName() == curveId, DOMDataError,\n      \"\\\"algorithm.namedCurve\\\" \\\"\", namedCurve,\n      \"\\\", does not match the curve \"\n      \"specified by the input key data\",\n      tryDescribeOpensslErrors());\n\n  auto keyAlgorithm = CryptoKey::EllipticKeyAlgorithm{\n    normalizedName,\n    normalizedNamedCurve,\n  };\n\n  return kj::heap<EllipticKey>(kj::mv(importedKey), kj::mv(keyAlgorithm), rsSize, extractable);\n}\n\n// =====================================================================================\n// EDDSA & EDDH\n\nnamespace {\n\n// Abstract base class for EDDSA and EDDH. The legacy NODE-ED25519 identifier for EDDSA has a\n// namedCurve field whereas the algorithms in the Secure Curves spec do not. We handle this by\n// keeping track of the algorithm identifier and returning an algorithm struct based on that.\nclass EdDsaKey final: public AsymmetricKeyCryptoKeyImpl {\n public:\n  explicit EdDsaKey(AsymmetricKeyData keyData, kj::StringPtr keyAlgorithm, bool extractable)\n      : AsymmetricKeyCryptoKeyImpl(kj::mv(keyData), extractable),\n        keyAlgorithm(kj::mv(keyAlgorithm)) {}\n\n  static kj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> generateKey(jsg::Lock& js,\n      kj::StringPtr normalizedName,\n      int nid,\n      CryptoKeyUsageSet privateKeyUsages,\n      CryptoKeyUsageSet publicKeyUsages,\n      bool extractablePrivateKey);\n\n  CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {\n    // For legacy node-based keys with NODE-ED25519, algorithm contains a namedCurve field.\n    if (keyAlgorithm == \"NODE-ED25519\") {\n      return CryptoKey::EllipticKeyAlgorithm{\n        keyAlgorithm,\n        keyAlgorithm,\n      };\n    } else {\n      return CryptoKey::KeyAlgorithm{keyAlgorithm};\n    }\n  }\n\n  kj::StringPtr getAlgorithmName() const override {\n    return keyAlgorithm;\n  }\n\n  kj::StringPtr chooseHash(\n      const kj::Maybe<kj::OneOf<kj::String, SubtleCrypto::HashAlgorithm>>& callTimeHash)\n      const override {\n    KJ_UNIMPLEMENTED();\n  }\n\n  jsg::JsArrayBuffer sign(jsg::Lock& js,\n      SubtleCrypto::SignAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> data) const override {\n    JSG_REQUIRE(getTypeEnum() == KeyType::PRIVATE, DOMInvalidAccessError,\n        \"Asymmetric signing requires a private key.\");\n\n    JSG_REQUIRE(getAlgorithmName() == \"Ed25519\" || getAlgorithmName() == \"NODE-ED25519\",\n        DOMOperationError, \"Not implemented for algorithm \\\"\", getAlgorithmName(), \"\\\".\");\n    // Why NODE-ED25519? NodeJS uses NODE-ED25519/NODE-448 as algorithm names but that feels\n    // inconsistent with the broader WebCrypto standard. Filed an issue with the standard for\n    // clarification: https://github.com/tQsW/webcrypto-curve25519/issues/7\n\n    auto signature = jsg::JsArrayBuffer::create(js, ED25519_SIGNATURE_LEN);\n    size_t signatureLength = signature.size();\n\n    // NOTE: Even though there's a ED25519_sign/ED25519_verify methods, they don't actually seem to\n    // work or are intended for some other use-case. I tried adding the verify immediately after\n    // signing here & the verification failed.\n    auto digestCtx = OSSL_NEW(EVP_MD_CTX);\n\n    JSG_REQUIRE(1 == EVP_DigestSignInit(digestCtx.get(), nullptr, nullptr, nullptr, getEvpPkey()),\n        DOMOperationError, \"Failed to initialize Ed25519 signing digest\",\n        tryDescribeOpensslErrors());\n    JSG_REQUIRE(1 ==\n            EVP_DigestSign(digestCtx.get(), signature.asArrayPtr().begin(), &signatureLength,\n                data.begin(), data.size()),\n        DOMOperationError, \"Failed to sign with Ed25119 key\", tryDescribeOpensslErrors());\n\n    JSG_REQUIRE(signatureLength == signature.size(), InternalDOMOperationError,\n        \"Unexpected change in size signing Ed25519\", signatureLength);\n\n    return signature;\n  }\n\n  bool verify(jsg::Lock& js,\n      SubtleCrypto::SignAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> signature,\n      kj::ArrayPtr<const kj::byte> data) const override {\n    ClearErrorOnReturn clearErrorOnReturn;\n\n    JSG_REQUIRE(getTypeEnum() == KeyType::PUBLIC, DOMInvalidAccessError,\n        \"Asymmetric verification requires a public key.\");\n\n    JSG_REQUIRE(getAlgorithmName() == \"Ed25519\" || getAlgorithmName() == \"NODE-ED25519\",\n        DOMOperationError, \"Not implemented for this algorithm\", getAlgorithmName());\n\n    JSG_REQUIRE(signature.size() == ED25519_SIGNATURE_LEN, DOMOperationError, \"Invalid \",\n        getAlgorithmName(), \" signature length \", signature.size());\n\n    auto digestCtx = OSSL_NEW(EVP_MD_CTX);\n    JSG_REQUIRE(1 == EVP_DigestSignInit(digestCtx.get(), nullptr, nullptr, nullptr, getEvpPkey()),\n        DOMOperationError, \"Failed to initialize Ed25519 verification digest\",\n        tryDescribeOpensslErrors());\n\n    auto result = EVP_DigestVerify(\n        digestCtx.get(), signature.begin(), signature.size(), data.begin(), data.size());\n\n    JSG_REQUIRE(result == 0 || result == 1, InternalDOMOperationError, \"Unexpected return code\",\n        result, internalDescribeOpensslErrors());\n\n    return !!result;\n  }\n\n  jsg::JsArrayBuffer deriveBits(jsg::Lock& js,\n      SubtleCrypto::DeriveKeyAlgorithm&& algorithm,\n      kj::Maybe<uint32_t> resultBitLength) const override final {\n    JSG_REQUIRE(getAlgorithmName() == \"X25519\", DOMNotSupportedError,\n        \"\"\n        \"The deriveBits operation is not implemented for \\\"\",\n        getAlgorithmName(), \"\\\".\");\n\n    JSG_REQUIRE(getTypeEnum() == KeyType::PRIVATE, DOMInvalidAccessError,\n        \"\"\n        \"The deriveBits operation is only valid for a private key, not \\\"\",\n        getType(), \"\\\".\");\n\n    auto& publicKey = JSG_REQUIRE_NONNULL(\n        algorithm.$public, TypeError, \"Missing field \\\"public\\\" in \\\"derivedKeyParams\\\".\");\n\n    JSG_REQUIRE(publicKey->getType() == \"public\"_kj, DOMInvalidAccessError,\n        \"\"\n        \"The provided key has type \\\"\",\n        publicKey->getType(), \"\\\", not \\\"public\\\"\");\n\n    JSG_REQUIRE(getAlgorithm(js).which() == publicKey->getAlgorithm(js).which(),\n        DOMInvalidAccessError, \"Base \", getAlgorithmName(),\n        \" private key cannot be used to derive\"\n        \" a key from a peer \",\n        publicKey->getAlgorithmName(), \" public key\");\n\n    JSG_REQUIRE(getAlgorithmName() == publicKey->getAlgorithmName(), DOMInvalidAccessError,\n        \"Private key for derivation is using \\\"\", getAlgorithmName(),\n        \"\\\" while public key is using \\\"\", publicKey->getAlgorithmName(), \"\\\".\");\n\n    auto outputBitLength = resultBitLength.orDefault(X25519_SHARED_KEY_LEN * 8);\n    JSG_REQUIRE(outputBitLength <= X25519_SHARED_KEY_LEN * 8, DOMOperationError,\n        \"Derived key length (\", outputBitLength, \" bits) is too long (should be at most \",\n        X25519_SHARED_KEY_LEN * 8, \" bits).\");\n\n    // The check above for the algorithm `which` equality ensures that the impl can be downcast to\n    // EdDsaKey (assuming we don't accidentally create a class that doesn't inherit this one that\n    // for some reason returns an EdDsaKey).\n    auto& publicKeyImpl = kj::downcast<EdDsaKey>(*publicKey->impl);\n\n    // EDDH code derived from https://www.openssl.org/docs/manmaster/man3/EVP_PKEY_derive.html\n    auto ctx = OSSL_NEW(EVP_PKEY_CTX, getEvpPkey(), nullptr);\n    JSG_REQUIRE(1 == EVP_PKEY_derive_init(ctx), InternalDOMOperationError,\n        \"Failed to init EDDH key derivation\", internalDescribeOpensslErrors());\n    JSG_REQUIRE(1 == EVP_PKEY_derive_set_peer(ctx, publicKeyImpl.getEvpPkey()),\n        InternalDOMOperationError, \"Failed to set EDDH peer\", internalDescribeOpensslErrors());\n\n    kj::Vector<kj::byte> sharedSecret;\n    sharedSecret.resize(X25519_SHARED_KEY_LEN);\n    size_t skeylen = X25519_SHARED_KEY_LEN;\n    JSG_REQUIRE(1 == EVP_PKEY_derive(ctx, sharedSecret.begin(), &skeylen), DOMOperationError,\n        \"Failed to derive EDDH key\", internalDescribeOpensslErrors());\n    KJ_ASSERT(skeylen == X25519_SHARED_KEY_LEN);\n\n    // Check for all-zero value as mandated by spec\n    kj::byte isNonZeroSecret = 0;\n    for (kj::byte b: sharedSecret) {\n      isNonZeroSecret |= b;\n    }\n    JSG_REQUIRE(isNonZeroSecret, DOMOperationError,\n        \"Detected small order secure curve points, aborting EDDH derivation\");\n\n    // mask off bits like in ECDH's deriveBits()\n    auto resultByteLength = integerCeilDivision(outputBitLength, 8u);\n    sharedSecret.truncate(resultByteLength);\n    auto numBitsToMaskOff = resultByteLength * 8 - outputBitLength;\n    KJ_DASSERT(numBitsToMaskOff < 8, numBitsToMaskOff);\n\n    if (numBitsToMaskOff) {\n      uint8_t mask = ~((1 << numBitsToMaskOff) - 1);\n      sharedSecret.back() &= mask;\n    }\n\n    return jsg::JsArrayBuffer::create(js, sharedSecret.asPtr());\n  }\n\n  CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail(jsg::Lock& js) const override {\n    // Node.js implementation for EdDsa keys currently does not provide any detail\n    return CryptoKey::AsymmetricKeyDetails{};\n  }\n\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"EdDsaKey\";\n  }\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(EdDsaKey);\n  }\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {\n    AsymmetricKeyCryptoKeyImpl::jsgGetMemoryInfo(tracker);\n  }\n\n private:\n  kj::StringPtr keyAlgorithm;\n\n  SubtleCrypto::JsonWebKey exportJwk() const override final {\n    KJ_ASSERT(getAlgorithmName() == \"X25519\"_kj || getAlgorithmName() == \"Ed25519\"_kj ||\n        getAlgorithmName() == \"NODE-ED25519\"_kj);\n\n    uint8_t rawPublicKey[ED25519_PUBLIC_KEY_LEN]{};\n    size_t publicKeyLen = sizeof(rawPublicKey);\n    JSG_REQUIRE(1 == EVP_PKEY_get_raw_public_key(getEvpPkey(), rawPublicKey, &publicKeyLen),\n        InternalDOMOperationError, \"Failed to retrieve public key\",\n        internalDescribeOpensslErrors());\n\n    KJ_ASSERT(publicKeyLen == 32, publicKeyLen);\n\n    SubtleCrypto::JsonWebKey jwk;\n    jwk.kty = kj::str(\"OKP\");\n    jwk.crv = kj::str(getAlgorithmName() == \"X25519\"_kj ? \"X25519\"_kj : \"Ed25519\"_kj);\n    jwk.x = fastEncodeBase64Url(kj::arrayPtr(rawPublicKey, publicKeyLen));\n    if (getAlgorithmName() == \"Ed25519\"_kj) {\n      jwk.alg = kj::str(\"EdDSA\");\n    }\n\n    if (getTypeEnum() == KeyType::PRIVATE) {\n      // Deliberately use ED25519_PUBLIC_KEY_LEN here.\n      // BoringSSL defines ED25519_PRIVATE_KEY_LEN as 64B since it stores the private key together\n      // with public key data in some functions, but in the EVP interface only the 32B private key\n      // itself is returned.\n      uint8_t rawPrivateKey[ED25519_PUBLIC_KEY_LEN]{};\n      size_t privateKeyLen = ED25519_PUBLIC_KEY_LEN;\n      JSG_REQUIRE(1 == EVP_PKEY_get_raw_private_key(getEvpPkey(), rawPrivateKey, &privateKeyLen),\n          InternalDOMOperationError, \"Failed to retrieve private key\",\n          internalDescribeOpensslErrors());\n\n      KJ_ASSERT(privateKeyLen == 32, privateKeyLen);\n\n      jwk.d = fastEncodeBase64Url(kj::arrayPtr(rawPrivateKey, privateKeyLen));\n      OPENSSL_cleanse(rawPrivateKey, sizeof(rawPrivateKey));\n    }\n\n    return jwk;\n  }\n\n  jsg::JsArrayBuffer exportRaw(jsg::Lock& js) const override final {\n    JSG_REQUIRE(getTypeEnum() == KeyType::PUBLIC, DOMInvalidAccessError, \"Raw export of \",\n        getAlgorithmName(), \" keys is only allowed for public keys.\");\n\n    auto raw = jsg::JsArrayBuffer::create(js, ED25519_PUBLIC_KEY_LEN);\n    size_t exportedLength = raw.size();\n\n    JSG_REQUIRE(\n        1 == EVP_PKEY_get_raw_public_key(getEvpPkey(), raw.asArrayPtr().begin(), &exportedLength),\n        InternalDOMOperationError, \"Failed to retrieve public key\",\n        internalDescribeOpensslErrors());\n\n    JSG_REQUIRE(exportedLength == raw.size(), InternalDOMOperationError,\n        \"Unexpected change in size\", raw.size(), exportedLength);\n\n    return raw;\n  }\n};\n\ntemplate <size_t keySize, void (*KeypairInit)(uint8_t[keySize], uint8_t[keySize * 2])>\nCryptoKeyPair generateKeyImpl(jsg::Lock& js,\n    kj::StringPtr normalizedName,\n    int nid,\n    CryptoKeyUsageSet privateKeyUsages,\n    CryptoKeyUsageSet publicKeyUsages,\n    bool extractablePrivateKey,\n    kj::StringPtr curveName) {\n  uint8_t rawPublicKey[keySize] = {0};\n  uint8_t rawPrivateKey[keySize * 2] = {0};\n  KeypairInit(rawPublicKey, rawPrivateKey);\n  KJ_DEFER(OPENSSL_cleanse(rawPrivateKey, sizeof(rawPrivateKey)));\n\n  // The private key technically also contains the public key. Why does the keypair function bother\n  // writing out the public key to a separate buffer?\n\n  auto privateEvpPKey = OSSLCALL_OWN(EVP_PKEY,\n      EVP_PKEY_new_raw_private_key(nid, nullptr, rawPrivateKey, keySize), InternalDOMOperationError,\n      \"Error constructing \", curveName, \" private key\", internalDescribeOpensslErrors());\n\n  auto publicEvpPKey = OSSLCALL_OWN(EVP_PKEY,\n      EVP_PKEY_new_raw_public_key(nid, nullptr, rawPublicKey, keySize), InternalDOMOperationError,\n      \"Internal error construct \", curveName, \"public key\", internalDescribeOpensslErrors());\n\n  AsymmetricKeyData privateKeyData{\n    .evpPkey = kj::mv(privateEvpPKey),\n    .keyType = KeyType::PRIVATE,\n    .usages = privateKeyUsages,\n  };\n  AsymmetricKeyData publicKeyData{\n    .evpPkey = kj::mv(publicEvpPKey),\n    .keyType = KeyType::PUBLIC,\n    .usages = publicKeyUsages,\n  };\n\n  auto privateKey = js.alloc<CryptoKey>(\n      kj::heap<EdDsaKey>(kj::mv(privateKeyData), normalizedName, extractablePrivateKey));\n  auto publicKey =\n      js.alloc<CryptoKey>(kj::heap<EdDsaKey>(kj::mv(publicKeyData), normalizedName, true));\n\n  return CryptoKeyPair{.publicKey = kj::mv(publicKey), .privateKey = kj::mv(privateKey)};\n}\n\nkj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> EdDsaKey::generateKey(jsg::Lock& js,\n    kj::StringPtr normalizedName,\n    int nid,\n    CryptoKeyUsageSet privateKeyUsages,\n    CryptoKeyUsageSet publicKeyUsages,\n    bool extractablePrivateKey) {\n  switch (nid) {\n    // BoringSSL doesn't support ED448/X448.\n    case NID_ED25519:\n      return generateKeyImpl<ED25519_PUBLIC_KEY_LEN, ED25519_keypair>(js, normalizedName, nid,\n          privateKeyUsages, publicKeyUsages, extractablePrivateKey, \"Ed25519\"_kj);\n    case NID_X25519:\n      return generateKeyImpl<X25519_PUBLIC_VALUE_LEN, X25519_keypair>(js, normalizedName, nid,\n          privateKeyUsages, publicKeyUsages, extractablePrivateKey, \"X25519\"_kj);\n  }\n\n  KJ_FAIL_REQUIRE(\"ED \", normalizedName, \" unimplemented\", nid);\n}\n\n}  // namespace\n\nkj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> CryptoKey::Impl::generateEddsa(jsg::Lock& js,\n    kj::StringPtr normalizedName,\n    SubtleCrypto::GenerateKeyAlgorithm&& algorithm,\n    bool extractable,\n    kj::ArrayPtr<const kj::String> keyUsages) {\n  auto usages =\n      CryptoKeyUsageSet::validate(normalizedName, CryptoKeyUsageSet::Context::generate, keyUsages,\n          normalizedName == \"X25519\" ? CryptoKeyUsageSet::derivationKeyMask()\n                                     : CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify());\n  auto privateKeyUsages = usages & CryptoKeyUsageSet::privateKeyMask();\n  auto publicKeyUsages = usages & CryptoKeyUsageSet::publicKeyMask();\n\n  if (normalizedName == \"NODE-ED25519\") {\n    kj::StringPtr namedCurve = JSG_REQUIRE_NONNULL(\n        algorithm.namedCurve, TypeError, \"Missing field \\\"namedCurve\\\" in \\\"algorithm\\\".\");\n    JSG_REQUIRE(namedCurve == \"NODE-ED25519\", DOMNotSupportedError, \"EDDSA curve \\\"\", namedCurve,\n        \"\\\" isn't supported.\");\n  }\n\n  return EdDsaKey::generateKey(js, normalizedName,\n      normalizedName == \"X25519\" ? NID_X25519 : NID_ED25519, privateKeyUsages, publicKeyUsages,\n      extractable);\n}\n\nkj::Own<CryptoKey::Impl> CryptoKey::Impl::importEddsa(jsg::Lock& js,\n    kj::StringPtr normalizedName,\n    kj::StringPtr format,\n    SubtleCrypto::ImportKeyData keyData,\n    SubtleCrypto::ImportKeyAlgorithm&& algorithm,\n    bool extractable,\n    kj::ArrayPtr<const kj::String> keyUsages) {\n\n  // BoringSSL doesn't support ED448.\n  if (normalizedName == \"NODE-ED25519\") {\n    // TODO: I prefer this style (declaring variables within the scope where they are needed) –\n    // does KJ style want this to be done differently?\n    kj::StringPtr namedCurve = JSG_REQUIRE_NONNULL(\n        algorithm.namedCurve, TypeError, \"Missing field \\\"namedCurve\\\" in \\\"algorithm\\\".\");\n    JSG_REQUIRE(namedCurve == \"NODE-ED25519\", DOMNotSupportedError, \"EDDSA curve \\\"\", namedCurve,\n        \"\\\" isn't supported.\");\n  }\n\n  auto importedKey = [&] {\n    auto nid = normalizedName == \"X25519\" ? NID_X25519 : NID_ED25519;\n    if (format != \"raw\") {\n      return importAsymmetricForWebCrypto(js, format, kj::mv(keyData), normalizedName, extractable,\n          keyUsages,\n          [nid, normalizedName = kj::str(normalizedName)](\n              SubtleCrypto::JsonWebKey keyDataJwk) -> kj::Own<EVP_PKEY> {\n        return ellipticJwkReader(nid, kj::mv(keyDataJwk), normalizedName);\n      },\n          normalizedName == \"X25519\" ? CryptoKeyUsageSet::derivationKeyMask()\n                                     : CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify());\n    } else {\n      return importEllipticRaw(kj::mv(keyData), nid, normalizedName, keyUsages,\n          normalizedName == \"X25519\" ? CryptoKeyUsageSet() : CryptoKeyUsageSet::verify());\n    }\n  }();\n\n  // In X25519 we ignore the id-X25519 identifier, as with id-ecDH above.\n  return kj::heap<EdDsaKey>(kj::mv(importedKey), normalizedName, extractable);\n}\n\nkj::Own<CryptoKey::Impl> fromEcKey(kj::Own<EVP_PKEY> key) {\n  auto nid = EVP_PKEY_id(key.get());\n  if (nid == NID_X25519 || nid == NID_ED25519) {\n    return fromEd25519Key(kj::mv(key));\n  }\n\n  auto curveName = OBJ_nid2sn(nid);\n  if (curveName == nullptr) {\n    curveName = \"unknown\";\n  }\n\n  auto [normalizedNamedCurve, curveId, rsSize] = lookupEllipticCurve(curveName);\n\n  return kj::heap<EllipticKey>(\n      AsymmetricKeyData{\n        .evpPkey = kj::mv(key),\n        .keyType = KeyType::PUBLIC,\n        .usages = CryptoKeyUsageSet::verify(),\n      },\n      CryptoKey::EllipticKeyAlgorithm{.name = \"ECDSA\"_kj, .namedCurve = normalizedNamedCurve},\n      rsSize, true);\n}\n\nkj::Own<CryptoKey::Impl> fromEd25519Key(kj::Own<EVP_PKEY> key) {\n  return kj::heap<EdDsaKey>(\n      AsymmetricKeyData{\n        .evpPkey = kj::mv(key),\n        .keyType = KeyType::PUBLIC,\n        .usages = CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify(),\n      },\n      \"Ed25519\"_kj, true);\n}\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/ec.h",
    "content": "#pragma once\n\n#include \"impl.h\"\n#include \"keys.h\"\n\n#include <openssl/base.h>\n\n#include <kj/common.h>\n\nnamespace workerd::api {\n\nclass Ec final {\n public:\n  static kj::Maybe<Ec> tryGetEc(const EVP_PKEY* key);\n  Ec(EC_KEY* key);\n\n  inline const EC_KEY* getKey() {\n    return key;\n  }\n  inline const EC_GROUP* getGroup() const {\n    return group;\n  }\n  int getCurveName() const;\n\n  const EC_POINT* getPublicKey() const;\n  const BIGNUM* getPrivateKey() const;\n  uint32_t getDegree() const;\n\n  inline const BIGNUM& getX() const {\n    return *x;\n  }\n  inline const BIGNUM& getY() const {\n    return *y;\n  }\n\n  SubtleCrypto::JsonWebKey toJwk(\n      KeyType keyType, kj::StringPtr curveName) const KJ_WARN_UNUSED_RESULT;\n\n  jsg::JsArrayBuffer getRawPublicKey(jsg::Lock& js) const KJ_WARN_UNUSED_RESULT;\n\n  CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail(jsg::Lock& js) const;\n\n private:\n  EC_KEY* key;\n  const EC_GROUP* group = nullptr;\n  kj::Own<BIGNUM> x;\n  kj::Own<BIGNUM> y;\n};\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/endianness.c++",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"endianness.h\"\n\n#if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64)) && !defined(__WINDOWS__)\n\n#define __WINDOWS__\n\n#endif\n\n#if defined(__linux__) || defined(__CYGWIN__)\n\n#include <endian.h>\n\n#elifdef __APPLE__\n\n#include <libkern/OSByteOrder.h>\n\nuint16_t htobe16(uint16_t x) {\n  return OSSwapHostToBigInt16(x);\n}\nuint16_t htole16(uint16_t x) {\n  return OSSwapHostToLittleInt16(x);\n}\nuint16_t be16toh(uint16_t x) {\n  return OSSwapBigToHostInt16(x);\n}\nuint16_t le16toh(uint16_t x) {\n  return OSSwapLittleToHostInt16(x);\n}\n\nuint32_t htobe32(uint32_t x) {\n  return OSSwapHostToBigInt32(x);\n}\nuint32_t htole32(uint32_t x) {\n  return OSSwapHostToLittleInt32(x);\n}\nuint32_t be32toh(uint32_t x) {\n  return OSSwapBigToHostInt32(x);\n}\nuint32_t le32toh(uint32_t x) {\n  return OSSwapLittleToHostInt32(x);\n}\n\nuint64_t htobe64(uint64_t x) {\n  return OSSwapHostToBigInt64(x);\n}\nuint64_t htole64(uint64_t x) {\n  return OSSwapHostToLittleInt64(x);\n}\nuint64_t be64toh(uint64_t x) {\n  return OSSwapBigToHostInt64(x);\n}\nuint64_t le64toh(uint64_t x) {\n  return OSSwapLittleToHostInt64(x);\n}\n\n#elifdef __OpenBSD__\n\n#include <sys/endian.h>\n\n#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__)\n\n#include <sys/endian.h>\n\nuint16_t be16toh(uint16_t x) {\n  return betoh16(x);\n}\nuint16_t le16toh(uint16_t x) {\n  return letoh16(x);\n}\n\nuint32_t be32toh(uint32_t x) {\n  return betoh32(x);\n}\nuint32_t le32toh(uint32_t x) {\n  return letoh32(x);\n}\n\nuint64_t be64toh(uint64_t x) {\n  return betoh64(x);\n}\nuint64_t le64toh(uint64_t x) {\n  return letoh64(x);\n}\n\n#elifdef __WINDOWS__\n\n#include <winsock2.h>\n\n#if BYTE_ORDER == LITTLE_ENDIAN\n\nuint16_t htobe16(uint16_t x) {\n  return htons(x);\n}\nuint16_t htole16(uint16_t x) {\n  return x;\n}\nuint16_t be16toh(uint16_t x) {\n  return ntohs(x);\n}\nuint16_t le16toh(uint16_t x) {\n  return x;\n}\n\nuint32_t htobe32(uint32_t x) {\n  return htonl(x);\n}\nuint32_t htole32(uint32_t x) {\n  return x;\n}\nuint32_t be32toh(uint32_t x) {\n  return ntohl(x);\n}\nuint32_t le32toh(uint32_t x) {\n  return x;\n}\n\nuint64_t htobe64(uint64_t x) {\n  return htonll(x);\n}\nuint64_t htole64(uint64_t x) {\n  return x;\n}\nuint64_t be64toh(uint64_t x) {\n  return ntohll(x);\n}\nuint64_t le64toh(uint64_t x) {\n  return x;\n}\n\n#elif BYTE_ORDER == BIG_ENDIAN\n\n/* that would be xbox 360 */\nuint16_t htobe16(uint16_t x) {\n  return x;\n}\nuint16_t htole16(uint16_t x) {\n  return __builtin_bswap16(x);\n}\nuint16_t be16toh(uint16_t x) {\n  return x;\n}\nuint16_t le16toh(uint16_t x) {\n  return __builtin_bswap16(x);\n}\n\nuint32_t htobe32(uint32_t x) {\n  return x;\n}\nuint32_t htole32(uint32_t x) {\n  return __builtin_bswap32(x);\n}\nuint32_t be32toh(uint32_t x) {\n  return x;\n}\nuint32_t le32toh(uint32_t x) {\n  return __builtin_bswap32(x);\n}\n\nuint64_t htobe64(uint64_t x) {\n  return x;\n}\nuint64_t htole64(uint64_t x) {\n  return __builtin_bswap64(x);\n}\nuint64_t be64toh(uint64_t x) {\n  return x;\n}\nuint64_t le64toh(uint64_t x) {\n  return __builtin_bswap64(x);\n}\n\n#else\n\n#error byte order not supported\n\n#endif\n\n#else\n\n#error platform not supported\n\n#endif\n"
  },
  {
    "path": "src/workerd/api/crypto/endianness.h",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n#include <stdint.h>\n\n// This file provides cross platform support for endianness conversions.\n// It is intended to hide away the polluting includes of system headers to provide the functions\n// without polluting the global namespace.\n\nuint16_t htobe16(uint16_t x);\nuint16_t htole16(uint16_t x);\nuint16_t be16toh(uint16_t x);\nuint16_t le16toh(uint16_t x);\n\nuint32_t htobe32(uint32_t x);\nuint32_t htole32(uint32_t x);\nuint32_t be32toh(uint32_t x);\nuint32_t le32toh(uint32_t x);\n\nuint64_t htobe64(uint64_t x);\nuint64_t htole64(uint64_t x);\nuint64_t be64toh(uint64_t x);\nuint64_t le64toh(uint64_t x);\n"
  },
  {
    "path": "src/workerd/api/crypto/hkdf.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"impl.h\"\n#include \"kdf.h\"\n\n#include <workerd/jsg/jsvalue.h>\n\n#include <ncrypto.h>\n\nnamespace workerd::api {\nnamespace {\n\n// The underlying implementation of HKDF for WebCrypto.\n// The CryptoKey::Impl here is used only for web crypto uses.\nclass HkdfKey final: public CryptoKey::Impl {\n public:\n  explicit HkdfKey(kj::Array<kj::byte> keyData,\n      CryptoKey::KeyAlgorithm keyAlgorithm,\n      bool extractable,\n      CryptoKeyUsageSet usages)\n      : CryptoKey::Impl(extractable, usages),\n        keyData(kj::mv(keyData)),\n        keyAlgorithm(kj::mv(keyAlgorithm)) {}\n\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"HkdfKey\";\n  }\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(HkdfKey);\n  }\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {\n    tracker.trackFieldWithSize(\"keyData\", keyData.size());\n    tracker.trackField(\"keyAlgorithm\", keyAlgorithm);\n  }\n\n private:\n  jsg::JsArrayBuffer deriveBits(jsg::Lock& js,\n      SubtleCrypto::DeriveKeyAlgorithm&& algorithm,\n      kj::Maybe<uint32_t> maybeLength) const override {\n    kj::StringPtr hashName = api::getAlgorithmName(\n        JSG_REQUIRE_NONNULL(algorithm.hash, TypeError, \"Missing field \\\"hash\\\" in \\\"algorithm\\\".\"));\n    const EVP_MD* hashType = lookupDigestAlgorithm(hashName).second;\n\n    auto saltHandle =\n        JSG_REQUIRE_NONNULL(algorithm.salt, TypeError, \"Missing field \\\"salt\\\" in \\\"algorithm\\\".\")\n            .getHandle(js);\n    const auto& salt = saltHandle.asArrayPtr();\n    auto infoHandle =\n        JSG_REQUIRE_NONNULL(algorithm.info, TypeError, \"Missing field \\\"info\\\" in \\\"algorithm\\\".\")\n            .getHandle(js);\n    const auto& info = infoHandle.asArrayPtr();\n\n    uint32_t length = JSG_REQUIRE_NONNULL(\n        maybeLength, DOMOperationError, \"HKDF cannot derive a key with null length.\");\n\n    JSG_REQUIRE(length % 8 == 0, DOMOperationError,\n        \"HKDF requires a derived key length that is a multiple of eight (requested \", length, \").\");\n\n    auto derivedLengthBytes = length / 8;\n\n    return JSG_REQUIRE_NONNULL(hkdf(js, derivedLengthBytes, hashType, keyData, salt, info),\n        DOMOperationError, \"HKDF deriveBits failed.\");\n  }\n\n  kj::StringPtr getAlgorithmName() const override {\n    return \"HKDF\";\n  }\n  CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {\n    return keyAlgorithm;\n  }\n\n  bool equals(const CryptoKey::Impl& other) const override final {\n    return this == &other || (other.getType() == \"secret\"_kj && other.equals(keyData));\n  }\n\n  bool equals(const kj::Array<kj::byte>& other) const override final {\n    return keyData.size() == other.size() &&\n        CRYPTO_memcmp(keyData.begin(), other.begin(), keyData.size()) == 0;\n  }\n\n  ZeroOnFree keyData;\n  CryptoKey::KeyAlgorithm keyAlgorithm;\n};\n}  // namespace\n\nkj::Maybe<jsg::JsArrayBuffer> hkdf(jsg::Lock& js,\n    size_t length,\n    const EVP_MD* digest,\n    kj::ArrayPtr<const kj::byte> key,\n    kj::ArrayPtr<const kj::byte> salt,\n    kj::ArrayPtr<const kj::byte> info) {\n  // Because we want to be using the v8 sandbox, we need to allocate the result\n  // buffer in the v8 isolate heap then generate the HKDF result into that.\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n  auto buf = jsg::JsArrayBuffer::create(js, length);\n  auto ncBuf = ToNcryptoBuffer(buf.asArrayPtr());\n  if (ncrypto::hkdfInfo(digest, ToNcryptoBuffer(key), ToNcryptoBuffer(info), ToNcryptoBuffer(salt),\n          length, &ncBuf)) {\n    return kj::mv(buf);\n  }\n\n  return kj::none;\n}\n\nkj::Own<CryptoKey::Impl> CryptoKey::Impl::importHkdf(jsg::Lock& js,\n    kj::StringPtr normalizedName,\n    kj::StringPtr format,\n    SubtleCrypto::ImportKeyData keyData,\n    SubtleCrypto::ImportKeyAlgorithm&& algorithm,\n    bool extractable,\n    kj::ArrayPtr<const kj::String> keyUsages) {\n  auto usages = CryptoKeyUsageSet::validate(normalizedName,\n      CryptoKeyUsageSet::Context::importSecret, keyUsages, CryptoKeyUsageSet::derivationKeyMask());\n\n  JSG_REQUIRE(!extractable, DOMSyntaxError, \"HKDF key cannot be extractable.\");\n  JSG_REQUIRE(format == \"raw\", DOMNotSupportedError,\n      \"HKDF key must be imported \"\n      \"in \\\"raw\\\" format (requested \\\"\",\n      format, \"\\\")\");\n\n  // NOTE: Checked in SubtleCrypto::importKey().\n  auto keyDataArray = kj::mv(keyData.get<kj::Array<kj::byte>>());\n\n  auto keyAlgorithm = CryptoKey::KeyAlgorithm{normalizedName};\n  return kj::heap<HkdfKey>(kj::mv(keyDataArray), kj::mv(keyAlgorithm), extractable, usages);\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/impl-test.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"impl.h\"\n\n#include <openssl/ec.h>\n#include <openssl/err.h>\n#include <openssl/rsa.h>\n\n#include <kj/test.h>\n\nnamespace workerd::api {\nnamespace {\n\nKJ_TEST(\"Crypto error conversion\") {\n  ClearErrorOnReturn clearErrorOnReturn;\n\n  // Intentionally provide an error type not handled in throwOpensslError()\n  // (RSA_R_CANNOT_RECOVER_MULTI_PRIME_KEY) that overlaps with an EC error that we do handle\n  // (EC_R_INVALID_ENCODING). This test will fail if we do not check the library code of the error.\n  // This test needs to be updated (e.g. with a different error code) if\n  // RSA_R_CANNOT_RECOVER_MULTI_PRIME_KEY is added to the error types provided to users in\n  // throwOpensslError().\n\n  OPENSSL_PUT_ERROR(RSA, RSA_R_CANNOT_RECOVER_MULTI_PRIME_KEY);\n  // Throw an exception based on BoringSSL error queue, expecting to get an internal error instead\n  // of a DOMException\n  KJ_EXPECT_THROW_MESSAGE(\"OpenSSL call failed\", OSSLCALL(0));\n\n  // EC_R_INVALID_ENCODING is one of the errors converted to user errors, test that it is converted\n  // to a DOMException.\n  OPENSSL_PUT_ERROR(EC, EC_R_INVALID_ENCODING);\n  KJ_EXPECT_THROW_MESSAGE(\"jsg.DOMException(OperationError): Invalid point encoding.\", OSSLCALL(0));\n}\n\nKJ_TEST(\"RSA_R_KEY_SIZE_TOO_SMALL is a user-facing error\") {\n  ClearErrorOnReturn clearErrorOnReturn;\n\n  // RSA_R_KEY_SIZE_TOO_SMALL should be converted to a DOMException(OperationError) rather than\n  // an internal error (which would generate Sentry noise).\n  OPENSSL_PUT_ERROR(RSA, RSA_R_KEY_SIZE_TOO_SMALL);\n  KJ_EXPECT_THROW_MESSAGE(\n      \"jsg.DOMException(OperationError): RSA key size is too small.\", OSSLCALL(0));\n}\n\nKJ_TEST(\"RSA_R_INTERNAL_ERROR is a user-facing error\") {\n  ClearErrorOnReturn clearErrorOnReturn;\n\n  // RSA_R_INTERNAL_ERROR should be converted to a DOMException(OperationError) rather than\n  // an internal error. This error occurs during RSA signing when the private key computation\n  // or post-sign verification fails (e.g. due to corrupted key material).\n  OPENSSL_PUT_ERROR(RSA, RSA_R_INTERNAL_ERROR);\n  KJ_EXPECT_THROW_MESSAGE(\"jsg.DOMException(OperationError): RSA operation failed.\", OSSLCALL(0));\n}\n\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/impl.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"impl.h\"\n\n#include \"simdutf.h\"\n\n#include <workerd/api/util.h>\n#include <workerd/jsg/jsvalue.h>\n#include <workerd/jsg/memory.h>\n\n#include <openssl/bn.h>\n#include <openssl/crypto.h>\n#include <openssl/ec.h>\n#include <openssl/err.h>\n#include <openssl/evp.h>\n#include <openssl/rand.h>\n#include <openssl/rsa.h>\n\n#include <algorithm>\n#include <map>\n\nnamespace workerd::api {\nnamespace {\nkj::String errorsToString(\n    kj::Array<kj::OneOf<kj::StringPtr, OpensslUntranslatedError>> accumulatedErrors,\n    kj::StringPtr defaultIfNoError) {\n  if (accumulatedErrors.size() == 0) {\n    return kj::str(defaultIfNoError);\n  }\n\n  if (accumulatedErrors.size() == 1) {\n    kj::String heap;\n    kj::StringPtr description;\n    KJ_SWITCH_ONEOF(accumulatedErrors[0]) {\n      KJ_CASE_ONEOF(e, kj::StringPtr) {\n        description = e;\n      }\n      KJ_CASE_ONEOF(e, OpensslUntranslatedError) {\n        heap = kj::str(e.library, \" \", e.reasonName);\n        description = heap;\n      }\n    }\n    return kj::str(\": \", description, \".\");\n  }\n\n  return kj::str(\": \",\n      kj::strArray(\n          KJ_MAP(e, accumulatedErrors) {\n    KJ_SWITCH_ONEOF(accumulatedErrors[0]) {\n      KJ_CASE_ONEOF(e, kj::StringPtr) {\n        return e;\n      }\n      KJ_CASE_ONEOF(e, OpensslUntranslatedError) {\n        return e.reasonName;\n      }\n    }\n    KJ_UNREACHABLE;\n  }, \" \"),\n      \".\");\n}\n}  // namespace\n\nconst SslArrayDisposer SslArrayDisposer::INSTANCE;\n\nvoid SslArrayDisposer::disposeImpl(void* firstElement,\n    size_t elementSize,\n    size_t elementCount,\n    size_t capacity,\n    void (*destroyElement)(void*)) const {\n  OPENSSL_free(firstElement);\n}\n\n// Call when an OpenSSL function returns an error code to convert that into an exception and\n// throw it.\nvoid throwOpensslError(const char* file, int line, kj::StringPtr code) {\n  // Some error codes that we know are the application's fault are converted to app errors.\n  // We only attempt to convert the most-recent error in the queue this way, because other errors\n  // in the queue might have been accidentally left there by previous, unrelated operations.\n  // Unfortunately BoringSSL's ERR_error_string() and friends produce unfriendly strings that\n  // mostly just tell you the error constant name, which isn't what we want to throw at users.\n  switch (ERR_GET_LIB(ERR_peek_last_error())) {\n    // The error code defines overlap between the different BoringSSL libraries (for example, we\n    // have EC_R_INVALID_ENCODING == RSA_R_CANNOT_RECOVER_MULTI_PRIME_KEY), so we must check the\n    // library code.\n    case ERR_LIB_EC:\n      switch (ERR_GET_REASON(ERR_peek_last_error())) {\n#define MAP_ERROR(CODE, TEXT)                                                                      \\\n  case CODE: {                                                                                     \\\n    ClearErrorOnReturn clearErrorOnReturn;                                                         \\\n    kj::throwFatalException(kj::Exception(kj::Exception::Type::FAILED, file, line,                 \\\n        kj::str(JSG_EXCEPTION(DOMOperationError) \": \", TEXT)));                                    \\\n  }\n\n        MAP_ERROR(EC_R_INVALID_ENCODING, \"Invalid point encoding.\")\n        MAP_ERROR(EC_R_INVALID_COMPRESSED_POINT, \"Invalid compressed point.\")\n        MAP_ERROR(EC_R_POINT_IS_NOT_ON_CURVE, \"Point is not on curve.\")\n        default:\n          break;\n      };\n      break;\n    case ERR_LIB_RSA:\n      switch (ERR_GET_REASON(ERR_peek_last_error())) {\n        MAP_ERROR(RSA_R_DATA_LEN_NOT_EQUAL_TO_MOD_LEN, \"Invalid RSA signature.\");\n        MAP_ERROR(RSA_R_KEY_SIZE_TOO_SMALL, \"RSA key size is too small.\");\n        MAP_ERROR(RSA_R_INTERNAL_ERROR, \"RSA operation failed.\");\n#undef MAP_ERROR\n\n        default:\n          break;\n      };\n      break;\n    default:\n      // not an error code to be converted to app error, move on\n      break;\n  };\n\n  // We don't recognize the error as one that is the app's fault, so assume it is an internal\n  // error. Here we'll accept BoringSSL's ugly error strings as hopefully it's at least something\n  // we can decipher.\n  kj::Vector<kj::String> lines;\n  while (unsigned long long error = ERR_get_error()) {\n    char message[1024]{};\n    ERR_error_string_n(error, message, sizeof(message));\n    lines.add(kj::heapString(message));\n  }\n  kj::throwFatalException(kj::Exception(kj::Exception::Type::FAILED, file, line,\n      kj::str(\"OpenSSL call failed: \", code, \"; \",\n          lines.empty() ? \"but ERR_get_error() returned 0\"_kj : kj::strArray(lines, \"; \"))));\n}\n\nkj::Vector<kj::OneOf<kj::StringPtr, OpensslUntranslatedError>> consumeAllOpensslErrors() {\n  kj::Vector<kj::OneOf<kj::StringPtr, OpensslUntranslatedError>> accumulatedErrors;\n\n  while (auto error = ERR_get_error()) {\n    accumulatedErrors.add([error]() -> kj::OneOf<kj::StringPtr, OpensslUntranslatedError> {\n      switch (ERR_GET_LIB(error)) {\n        case ERR_LIB_RSA:\n          switch (ERR_GET_REASON(error)) {\n            case RSA_R_DATA_LEN_NOT_EQUAL_TO_MOD_LEN:\n              return \"Invalid RSA signature.\"_kj;\n            case RSA_R_KEY_SIZE_TOO_SMALL:\n              return \"RSA key size is too small.\"_kj;\n            case RSA_R_INTERNAL_ERROR:\n              return \"RSA operation failed.\"_kj;\n          }\n          break;\n        case ERR_LIB_EC:\n          switch (ERR_GET_REASON(error)) {\n            case EC_R_INVALID_ENCODING:\n              return \"Invalid point encoding.\"_kj;\n            case EC_R_INVALID_COMPRESSED_POINT:\n              return \"Invalid compressed point.\"_kj;\n            case EC_R_POINT_IS_NOT_ON_CURVE:\n              return \"Point is not on curve.\"_kj;\n            case EC_R_UNKNOWN_GROUP:\n              return \"Unsupported elliptic curve group.\"_kj;\n          }\n          break;\n      }\n\n      return OpensslUntranslatedError{\n        .library = ERR_lib_error_string(error),\n        .reasonName = ERR_reason_error_string(error),\n      };\n    }());\n  }\n\n  return accumulatedErrors;\n}\n\nkj::String tryDescribeOpensslErrors(kj::StringPtr defaultIfNoError) {\n  if (defaultIfNoError.size() == 0) {\n    defaultIfNoError = \".\"_kj;\n  }\n\n  auto accumulatedErrors = consumeAllOpensslErrors();\n\n  // For now we only allow errors we explicitly map to friendly strings to be displayed to end\n  // users. #if 1 is convenient as it makes it easy to #if 0 to see the error codes printed when\n  // debugging issues.\n#if 1\n  auto removeBegin = std::remove_if(accumulatedErrors.begin(), accumulatedErrors.end(),\n      [](const auto& error) { return error.template is<OpensslUntranslatedError>(); });\n\n  accumulatedErrors.resize(removeBegin - accumulatedErrors.begin());\n#endif\n\n  return errorsToString(accumulatedErrors.releaseAsArray(), defaultIfNoError);\n}\n\nkj::String internalDescribeOpensslErrors() {\n  return errorsToString(consumeAllOpensslErrors().releaseAsArray(), \".\"_kj);\n}\n\nstd::pair<kj::StringPtr, const EVP_MD*> lookupDigestAlgorithm(kj::StringPtr algorithm) {\n  static const std::map<kj::StringPtr, const EVP_MD*, CiLess> registeredAlgorithms{\n    {\"SHA-1\", EVP_sha1()},\n    {\"SHA-256\", EVP_sha256()},\n    {\"SHA-384\", EVP_sha384()},\n    {\"SHA-512\", EVP_sha512()},\n\n    // MD5 is not supported by WebCrypto, presumably because the designers didn't want to\n    // support broken crypto. However, the reality is that people still use MD5 for things, and if\n    // we don't give them a native implementation, they're going to use a pure-JS implementation,\n    // leaving everyone worse-off.\n    {\"MD5\", EVP_md5()},\n  };\n\n  auto algIter = registeredAlgorithms.find(algorithm);\n  JSG_REQUIRE(algIter != registeredAlgorithms.end(), DOMNotSupportedError,\n      \"Unrecognized or unimplemented digest algorithm requested.\");\n  return *algIter;\n}\n\nkj::EncodingResult<kj::Array<kj::byte>> decodeBase64Url(kj::String text) {\n  // TODO(cleanup): Make a non-mutating version of this and put in kj-encoding. Or add a\n  //   \"bool urlEncoded = false\" parameter to kj::decodeBase64()?\n\n  std::replace(text.begin(), text.end(), '-', '+');\n  std::replace(text.begin(), text.end(), '_', '/');\n  return kj::decodeBase64(text);\n}\n\nbool CryptoKey::Impl::equals(const kj::Array<kj::byte>& other) const {\n  KJ_FAIL_REQUIRE(\"Unable to compare raw key material for this key\");\n}\n\nkj::Own<CryptoKey::Impl> CryptoKey::Impl::from(jsg::Lock& js, kj::Own<EVP_PKEY> key) {\n  switch (EVP_PKEY_id(key.get())) {\n    case EVP_PKEY_RSA:\n      return fromRsaKey(js, kj::mv(key));\n    case EVP_PKEY_EC:\n      return fromEcKey(kj::mv(key));\n    case EVP_PKEY_ED25519:\n      return fromEd25519Key(kj::mv(key));\n    default:\n      JSG_FAIL_REQUIRE(TypeError, \"Unsupported key type\");\n  }\n  KJ_UNREACHABLE;\n}\n\nZeroOnFree::~ZeroOnFree() noexcept(false) {\n  OPENSSL_cleanse(inner.begin(), inner.size());\n}\n\nvoid checkPbkdfLimits(jsg::Lock& js, size_t iterations) {\n  auto& limits = Worker::Isolate::from(js).getLimitEnforcer();\n  KJ_IF_SOME(max, limits.checkPbkdfIterations(js, iterations)) {\n    JSG_FAIL_REQUIRE(DOMNotSupportedError,\n        kj::str(\"Pbkdf2 failed: iteration counts above \", max, \" are not supported (requested \",\n            iterations, \").\"));\n  }\n}\n\nkj::Maybe<kj::Own<BIGNUM>> toBignum(kj::ArrayPtr<const kj::byte> data) {\n  BIGNUM* result = BN_bin2bn(data.begin(), data.size(), nullptr);\n  if (result == nullptr) return kj::none;\n  return kj::Own<BIGNUM>(result, workerd::api::SslDisposer<BIGNUM, &BIGNUM_free>::INSTANCE);\n}\n\nBIGNUM* toBignumUnowned(kj::ArrayPtr<const kj::byte> data) {\n  auto result = BN_bin2bn(data.begin(), data.size(), nullptr);\n  JSG_REQUIRE(result != nullptr, DOMOperationError, \"Error importing BIGNUM\");\n  return result;\n}\n\nUniqueBignum toBignumOwned(kj::ArrayPtr<const kj::byte> data) {\n  return UniqueBignum(toBignumUnowned(data), &BN_clear_free);\n}\n\nkj::Maybe<kj::Array<kj::byte>> bignumToArray(const BIGNUM& n) {\n  auto result = kj::heapArray<kj::byte>(BN_num_bytes(&n));\n  if (BN_bn2bin(&n, result.begin()) != result.size()) return kj::none;\n  return kj::mv(result);\n}\n\nkj::Maybe<kj::Array<kj::byte>> bignumToArrayPadded(const BIGNUM& n) {\n  auto result = kj::heapArray<kj::byte>(BN_num_bytes(&n));\n  if (BN_bn2binpad(&n, result.begin(), result.size()) != result.size()) return kj::none;\n  return kj::mv(result);\n}\n\nkj::Maybe<kj::Array<kj::byte>> bignumToArrayPadded(const BIGNUM& n, size_t paddedLength) {\n  auto result = kj::heapArray<kj::byte>(paddedLength);\n  if (BN_bn2bin_padded(result.begin(), paddedLength, &n) == 0) {\n    return kj::none;\n  }\n  return kj::mv(result);\n}\n\nkj::Maybe<jsg::JsUint8Array> bignumToArray(jsg::Lock& js, const BIGNUM& n) {\n  auto buf = jsg::JsUint8Array::create(js, BN_num_bytes(&n));\n  if (BN_bn2bin(&n, buf.asArrayPtr().begin()) != buf.asArrayPtr().size()) return kj::none;\n  return buf;\n}\n\nkj::Maybe<jsg::JsUint8Array> bignumToArrayPadded(jsg::Lock& js, const BIGNUM& n) {\n  auto buf = jsg::JsUint8Array::create(js, BN_num_bytes(&n));\n  if (BN_bn2binpad(&n, buf.asArrayPtr().begin(), buf.asArrayPtr().size()) !=\n      buf.asArrayPtr().size()) {\n    return kj::none;\n  }\n  return buf;\n}\n\nkj::Maybe<jsg::JsUint8Array> bignumToArrayPadded(\n    jsg::Lock& js, const BIGNUM& n, size_t paddedLength) {\n  auto buf = jsg::JsUint8Array::create(js, paddedLength);\n  if (BN_bn2bin_padded(buf.asArrayPtr().begin(), paddedLength, &n) == 0) {\n    return kj::none;\n  }\n  return buf;\n}\n\nkj::Own<BIGNUM> newBignum() {\n  return kj::Own<BIGNUM>(BN_new(), workerd::api::SslDisposer<BIGNUM, &BIGNUM_free>::INSTANCE);\n}\n\nvoid CryptoKey::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"impl\", impl);\n}\n\nbool CSPRNG(kj::ArrayPtr<kj::byte> buffer) {\n  do {\n    if (1 == RAND_status())\n      if (1 == RAND_bytes(buffer.begin(), buffer.size())) return true;\n#if OPENSSL_VERSION_MAJOR >= 3\n    const auto code = ERR_peek_last_error();\n    // A misconfigured OpenSSL 3 installation may report 1 from RAND_poll()\n    // and RAND_status() but fail in RAND_bytes() if it cannot look up\n    // a matching algorithm for the CSPRNG.\n    if (ERR_GET_LIB(code) == ERR_LIB_RAND) {\n      const auto reason = ERR_GET_REASON(code);\n      if (reason == RAND_R_ERROR_INSTANTIATING_DRBG || reason == RAND_R_UNABLE_TO_FETCH_DRBG ||\n          reason == RAND_R_UNABLE_TO_CREATE_DRBG) {\n        return false;\n      }\n    }\n#endif\n  } while (1 == RAND_poll());\n\n  return false;\n}\n\nkj::Maybe<kj::ArrayPtr<const kj::byte>> tryGetAsn1Sequence(kj::ArrayPtr<const kj::byte> data) {\n  if (data.size() < 2 || data[0] != 0x30) return kj::none;\n\n  if (data[1] & 0x80) {\n    // Long form.\n    size_t n_bytes = data[1] & ~0x80;\n    if (n_bytes + 2 > data.size() || n_bytes > sizeof(size_t)) return kj::none;\n    size_t length = 0;\n    for (size_t i = 0; i < n_bytes; i++) length = (length << 8) | data[i + 2];\n    auto start = 2 + n_bytes;\n    auto end = start + kj::min(data.size() - 2 - n_bytes, length);\n    return data.slice(start, end);\n  }\n\n  // Short form.\n  auto start = 2;\n  auto end = start + kj::min(data.size() - 2, data[1]);\n  return data.slice(start, end);\n}\n\nkj::Maybe<kj::Array<kj::byte>> simdutfBase64UrlDecode(kj::StringPtr input) {\n  auto size = simdutf::maximal_binary_length_from_base64(input.begin(), input.size());\n  auto buf = kj::heapArray<kj::byte>(size);\n  auto result = simdutf::base64_to_binary(\n      input.begin(), input.size(), buf.asChars().begin(), simdutf::base64_url);\n  if (result.error != simdutf::SUCCESS) return kj::none;\n  KJ_ASSERT(result.count <= size);\n  return buf.slice(0, result.count).attach(kj::mv(buf));\n}\n\nkj::Maybe<jsg::JsUint8Array> simdutfBase64UrlDecode(jsg::Lock& js, kj::StringPtr input) {\n  auto size = simdutf::maximal_binary_length_from_base64(input.begin(), input.size());\n  KJ_STACK_ARRAY(kj::byte, buf, size, 1024, 4096);\n  auto result = simdutf::base64_to_binary(\n      input.begin(), input.size(), buf.asChars().begin(), simdutf::base64_url);\n  if (result.error != simdutf::SUCCESS) return kj::none;\n  KJ_ASSERT(result.count <= size);\n\n  return jsg::JsUint8Array::create(js, buf.first(result.count));\n}\n\njsg::JsUint8Array simdutfBase64UrlDecodeChecked(\n    jsg::Lock& js, kj::StringPtr input, kj::StringPtr error) {\n  return JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(js, input), Error, error);\n}\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/impl.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// INTERNAL CRYPTO IMPLEMENTATION FILE\n//\n// Don't include this file unless your name is \"crypto*.c++\".\n\n#include \"crypto.h\"\n\n#include <workerd/api/util.h>\n#include <workerd/jsg/jsvalue.h>\n\n#include <ncrypto.h>\n#include <openssl/base.h>\n#include <openssl/bn.h>\n#include <openssl/err.h>\n\n#include <kj/encoding.h>\n\nusing BIGNUM = struct bignum_st;\n\n// Wrap calls to OpenSSL's EVP_* interface (and similar APIs) in this macro to\n// deal with errors.\n#define OSSLCALL(...)                                                                              \\\n  if ((__VA_ARGS__) != 1) ::workerd::api::throwOpensslError(__FILE__, __LINE__, #__VA_ARGS__)\n\n#define UNWRAP_JWK_BIGNUM(value, ...)                                                              \\\n  JSG_REQUIRE_NONNULL(decodeBase64Url(JSG_REQUIRE_NONNULL((value), __VA_ARGS__)), __VA_ARGS__)\n\nnamespace workerd::api {\n\nstruct OpensslUntranslatedError {\n  kj::StringPtr library;\n  kj::StringPtr reasonName;\n};\n\n// Call to throw an exception based on the OpenSSL error code. Usually, you should wrap your call\n// in OSSLCALL() to have this invoked automatically.\n//\n// Some error codes are translated into application-visible errors of type\n// `DOMException(OperationError)`, but most errors are considered internal errors.\nKJ_NORETURN(void throwOpensslError(const char* file, int line, kj::StringPtr code));\n\n// Consumes the entire OpenSSL error queue & converts it either into friendly names or the raw\n// (unfriendly) name that OpenSSL gives the error code.\nkj::Vector<kj::OneOf<kj::StringPtr, OpensslUntranslatedError>> consumeAllOpensslErrors();\n\n// Returns a description of the OpenSSL errors (starting with \": \") in the stack & clears them if\n// there are any. The expected usage is something like:\n//   JSG_REQUIRE(<some OpenSSL call succeeds>, OperationError, \"This thing went wrong\",\n//       tryDescribeOpensslErrors());\n// This way if there are any OpenSSL errors to describe it will get rendered as:\n//   \"jsg.DOMException(OperationError): This thing went wrong: <description>.\"\n// and if there aren't, then this will get rendered as:\n//   \"jsg.DOMException(OperationError): This thing went wrong.\"\nkj::String tryDescribeOpensslErrors(kj::StringPtr defaultIfNoError = nullptr);\n\n// Like tryDescribeOpensslErrors but dumps all OpenSSL errors even if not user-facing. This is for\n// use with `Internal` errors passed to JSG which automagically strip all contextual information so\n// that these errors only end up in Sentry.\nkj::String internalDescribeOpensslErrors();\n\n// Helper for implementing `sign()`, `digest()` and `importKey()`. Returns a pair containing a\n// StringPtr to the normalized name of the given algorithm and the EVP_MD type to use with\n// OpenSSL's EVP interface.\n//\n// Throws if the given algorithm isn't supported.\nstd::pair<kj::StringPtr, const EVP_MD*> lookupDigestAlgorithm(kj::StringPtr algorithm);\n\n// kj::decodeBase64 doesn't know how to parse URL-encoded variants.\n// https://en.wikipedia.org/wiki/Base64#URL_applications\n// Due to this, the input string is modified prior to passing to kj::decodeBase64. The mutation\n// isn't actually required & it's possible that a non-mutating variant could be written. That's more\n// complex to implement outside of kj::decodeBase64 though and the mutating variant is easier to\n// implement as a wrapper. Could be sufficient to just add a \"urlEncoded\" boolean so that\n// kj::decodeBase64 can do this in-situ for both cases.\nkj::EncodingResult<kj::Array<kj::byte>> decodeBase64Url(kj::String text);\n\n// WebCrypto likes to allow algorithms to be specified as a simple string name, or as a struct\n// containing a `name` field and possibly other fields. This helper collapses that.\ntemplate <typename T>\nT interpretAlgorithmParam(kj::OneOf<kj::String, T>&& param) {\n  if (param.template is<kj::String>()) {\n    T result;\n    result.name = kj::mv(param.template get<kj::String>());\n    return result;\n  } else {\n    return kj::mv(param.template get<T>());\n  }\n}\n\n// Like `interpretAlgorithmParam` but just get the algorithm name. Works with const input.\ntemplate <typename T>\nkj::StringPtr getAlgorithmName(const kj::OneOf<kj::String, T>& param) {\n  if (param.template is<kj::String>()) {\n    return param.template get<kj::String>();\n  } else {\n    return param.template get<T>().name;\n  }\n}\n\nclass CryptoKey::Impl {\n public:\n  // C++ API\n\n  using ImportFunc = kj::Own<Impl>(jsg::Lock& js,\n      kj::StringPtr normalizedName,\n      kj::StringPtr format,\n      SubtleCrypto::ImportKeyData keyData,\n      SubtleCrypto::ImportKeyAlgorithm&& algorithm,\n      bool extractable,\n      kj::ArrayPtr<const kj::String> keyUsages);\n\n  static ImportFunc importAes;\n  static ImportFunc importHmac;\n  static ImportFunc importPbkdf2;\n  static ImportFunc importHkdf;\n  static ImportFunc importRsa;\n  static ImportFunc importEcdsa;\n  static ImportFunc importEcdh;\n  static ImportFunc importEddsa;\n  static ImportFunc importRsaRaw;\n\n  using GenerateFunc = kj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair>(jsg::Lock& js,\n      kj::StringPtr normalizedName,\n      SubtleCrypto::GenerateKeyAlgorithm&& algorithm,\n      bool extractable,\n      kj::ArrayPtr<const kj::String> keyUsages);\n\n  static GenerateFunc generateAes;\n  static GenerateFunc generateHmac;\n  static GenerateFunc generateRsa;\n  static GenerateFunc generateEcdsa;\n  static GenerateFunc generateEcdh;\n  static GenerateFunc generateEddsa;\n\n  Impl(bool extractable, CryptoKeyUsageSet usages): extractable(extractable), usages(usages) {}\n\n  static kj::Own<CryptoKey::Impl> from(jsg::Lock& js, kj::Own<EVP_PKEY> key);\n\n  bool isExtractable() const {\n    return extractable;\n  }\n  CryptoKeyUsageSet getUsages() const {\n    return usages;\n  }\n\n  virtual jsg::JsArrayBuffer encrypt(jsg::Lock& js,\n      SubtleCrypto::EncryptAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> plainText) const {\n    JSG_FAIL_REQUIRE(DOMNotSupportedError, \"The encrypt operation is not implemented for \\\"\",\n        getAlgorithmName(), \"\\\".\");\n  }\n  virtual jsg::JsArrayBuffer decrypt(jsg::Lock& js,\n      SubtleCrypto::EncryptAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> cipherText) const {\n    JSG_FAIL_REQUIRE(DOMNotSupportedError, \"The decrypt operation is not implemented for \\\"\",\n        getAlgorithmName(), \"\\\".\");\n  }\n\n  virtual jsg::JsArrayBuffer sign(jsg::Lock& js,\n      SubtleCrypto::SignAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> data) const {\n    JSG_FAIL_REQUIRE(DOMNotSupportedError, \"The sign operation is not implemented for \\\"\",\n        getAlgorithmName(), \"\\\".\");\n  }\n  virtual bool verify(jsg::Lock& js,\n      SubtleCrypto::SignAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> signature,\n      kj::ArrayPtr<const kj::byte> data) const {\n    JSG_FAIL_REQUIRE(DOMNotSupportedError, \"The verify operation is not implemented for \\\"\",\n        getAlgorithmName(), \"\\\".\");\n  }\n\n  virtual jsg::JsArrayBuffer deriveBits(jsg::Lock& js,\n      SubtleCrypto::DeriveKeyAlgorithm&& algorithm,\n      kj::Maybe<uint32_t> length) const {\n    JSG_FAIL_REQUIRE(DOMNotSupportedError,\n        \"The deriveKey and deriveBits operations are not implemented for \\\"\", getAlgorithmName(),\n        \"\\\".\");\n  }\n\n  virtual jsg::JsArrayBuffer wrapKey(jsg::Lock& js,\n      SubtleCrypto::EncryptAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> unwrappedKey) const {\n    // For many algorithms, wrapKey() is the same as encrypt(), so as a convenience the default\n    // implementation just forwards to it.\n    return encrypt(js, kj::mv(algorithm), unwrappedKey);\n  }\n\n  virtual jsg::JsArrayBuffer unwrapKey(jsg::Lock& js,\n      SubtleCrypto::EncryptAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> wrappedKey) const {\n    // For many algorithms, unwrapKey() is the same as decrypt(), so as a convenience the default\n    // implementation just forwards to it.\n    return decrypt(js, kj::mv(algorithm), wrappedKey);\n  }\n\n  virtual SubtleCrypto::ExportKeyData exportKey(jsg::Lock& js, kj::StringPtr format) const {\n    JSG_FAIL_REQUIRE(DOMNotSupportedError, \"Unrecognized or unsupported export of \\\"\",\n        getAlgorithmName(), \"\\\" requested.\");\n  }\n\n  // The exportKeyExt variant is used by the Node.js crypto module. It allows the caller to\n  // specify a broader range of export formats and types that are not supported by Web\n  // Crypto. For instance, Web Crypto limits the export of public keys to only the spki or\n  // jwk formats, while Node.js allows pkcs1 or spki formatted as either pem, der, or jwk.\n  // For private keys, Node.js allows optionally encrypting the private key using a given\n  // cipher and passphrase.\n  // Rather than modify the existing exportKey API, we add this new variant to support the\n  // Node.js implementation without risking breaking the Web Crypto impl.\n  virtual jsg::JsUint8Array exportKeyExt(jsg::Lock& js,\n      kj::StringPtr format,\n      kj::StringPtr type,\n      jsg::Optional<kj::String> cipher = kj::none,\n      jsg::Optional<kj::Array<kj::byte>> passphrase = kj::none) const {\n    JSG_FAIL_REQUIRE(DOMNotSupportedError, \"Unrecognized or unsupported export of \\\"\",\n        getAlgorithmName(), \"\\\" requested.\");\n  }\n\n  virtual kj::StringPtr getAlgorithmName() const = 0;\n\n  virtual CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail(jsg::Lock& js) const {\n    JSG_FAIL_REQUIRE(DOMNotSupportedError,\n        \"The getAsymmetricKeyDetail operation is not implemented for \\\"\", getAlgorithmName(),\n        \"\\\".\");\n  }\n\n  // JS API implementation\n\n  virtual AlgorithmVariant getAlgorithm(jsg::Lock& js) const = 0;\n  virtual kj::StringPtr getType() const {\n    return \"secret\"_kj;\n  }\n\n  virtual bool equals(const Impl& other) const = 0;\n  virtual bool equals(const kj::Array<kj::byte>& other) const;\n\n  virtual kj::StringPtr jsgGetMemoryName() const {\n    return \"CryptoKey::Impl\";\n  }\n  virtual size_t jsgGetMemorySelfSize() const {\n    return sizeof(Impl);\n  }\n  virtual void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {}\n\n  virtual bool verifyX509Public(const X509* cert) const {\n    return false;\n  }\n  virtual bool verifyX509Private(const X509* cert) const {\n    return false;\n  }\n\n  virtual void visitForGc(jsg::GcVisitor& visitor) {\n    // By default, nothing to visit.\n  }\n\n private:\n  const bool extractable;\n  const CryptoKeyUsageSet usages;\n};\n\nstruct CryptoAlgorithm {\n  // Name, in canonical (all-uppercase) format.\n  kj::StringPtr name;\n\n  // Functions to import / generate keys for this algorithm. If nullptr, the respective\n  // operation isn't allowed.\n  CryptoKey::Impl::ImportFunc* importFunc = nullptr;\n\n  // Functions to import / generate keys for this algorithm. If nullptr, the respective\n  // operation isn't allowed.\n  CryptoKey::Impl::GenerateFunc* generateFunc = nullptr;\n  // TODO(cleanup): I have these as pointers instead of maybe-references because the references\n  //   would have to be const in order to enable const-copying, but it turns out you cannot specify\n  //   `const` on a reference-to-function (the compiler ignores it as \"redundant\", but then\n  //   template metaprogramming cannot recognize it as const). Maybe we can fix this in KJ, by\n  //   making `RemoveConstOrDisable` recognize function references are inherently const.\n\n  // Allow comparison by name, case-insensitive. This is a convenience for placing in an std::set.\n  inline bool operator==(const CryptoAlgorithm& other) const {\n    return strcasecmp(name.cStr(), other.name.cStr()) == 0;\n  }\n  // Allow comparison by name, case-insensitive. This is a convenience for placing in an std::set.\n  inline bool operator<(const CryptoAlgorithm& other) const {\n    return strcasecmp(name.cStr(), other.name.cStr()) < 0;\n  }\n  // TODO(cleanup): I'd rather use kj::Table with HashIndex but we need a case-insensitive hash\n  //   function, which seemed slightly too annoying to implement now.\n};\n\nclass SslArrayDisposer: public kj::ArrayDisposer {\n public:\n  static const SslArrayDisposer INSTANCE;\n\n  void disposeImpl(void* firstElement,\n      size_t elementSize,\n      size_t elementCount,\n      size_t capacity,\n      void (*destroyElement)(void*)) const override;\n};\n\ntemplate <typename T, void (*sslFree)(T*)>\nclass SslDisposer: public kj::Disposer {\n public:\n  static const SslDisposer INSTANCE;\n\n protected:\n  void disposeImpl(void* pointer) const override {\n    sslFree(reinterpret_cast<T*>(pointer));\n  }\n};\n\ntemplate <typename T, void (*sslFree)(T*)>\nconst SslDisposer<T, sslFree> SslDisposer<T, sslFree>::INSTANCE;\n\n#define OSSLCALL_OWN(T, code, ...)                                                                 \\\n  ({                                                                                               \\\n    T* result = code;                                                                              \\\n    JSG_REQUIRE(result != nullptr, ##__VA_ARGS__);                                                 \\\n    kj::Own<T>(result, workerd::api::SslDisposer<T, &T##_free>::INSTANCE);                         \\\n  })\n\n#define OSSL_NEW(T, ...)                                                                           \\\n  OSSLCALL_OWN(T, T##_new(__VA_ARGS__), InternalDOMOperationError, \"Error allocating crypto\")\n\n#define BIGNUM_new BN_new\n#define BIGNUM_free BN_clear_free\n// BIGNUM obnoxiously doesn't follow the naming convention...\n// Using BN_clear_free here ensures that any potentially sensitive information in the\n// BIGNUM is also cleansed when it is freed.\n\nusing UniqueBignum = std::unique_ptr<BIGNUM, void (*)(BIGNUM*)>;\nkj::Maybe<kj::Own<BIGNUM>> toBignum(kj::ArrayPtr<const kj::byte> data);\nBIGNUM* toBignumUnowned(kj::ArrayPtr<const kj::byte> data);\n// Like toBignumUnowned but returns a UniqueBignum for RAII. Use .release() to transfer\n// ownership to RSA_set0_key etc.\nUniqueBignum toBignumOwned(kj::ArrayPtr<const kj::byte> data);\nkj::Maybe<kj::Array<kj::byte>> bignumToArray(const BIGNUM& bignum);\nkj::Maybe<kj::Array<kj::byte>> bignumToArrayPadded(const BIGNUM& bignum);\nkj::Maybe<kj::Array<kj::byte>> bignumToArrayPadded(const BIGNUM& bignum, size_t paddedLength);\nkj::Maybe<jsg::JsUint8Array> bignumToArray(jsg::Lock& js, const BIGNUM& bignum);\nkj::Maybe<jsg::JsUint8Array> bignumToArrayPadded(jsg::Lock& js, const BIGNUM& bignum);\nkj::Maybe<jsg::JsUint8Array> bignumToArrayPadded(\n    jsg::Lock& js, const BIGNUM& bignum, size_t paddedLength);\nkj::Own<BIGNUM> newBignum();\n\n#define OSSL_BIO_MEM()                                                                             \\\n  ({                                                                                               \\\n    BIO* result = BIO_new(BIO_s_mem());                                                            \\\n    JSG_REQUIRE(result != nullptr, InternalDOMOperationError, \"Error allocating crypto\");          \\\n    kj::Own<BIO>(result, workerd::api::SslDisposer<BIO, &BIO_free_all>::INSTANCE);                 \\\n  })\n\n// Adopted from Node.js' crypto implementation. the MarkPopErrorOnReturn\n// and ClearErrorOnReturn mechanisms make working with the openssl error\n// stack a bit easier...\nstruct MarkPopErrorOnReturn {\n  MarkPopErrorOnReturn() {\n    ERR_set_mark();\n  }\n  ~MarkPopErrorOnReturn() {\n    ERR_pop_to_mark();\n  }\n  KJ_DISALLOW_COPY_AND_MOVE(MarkPopErrorOnReturn);\n};\n\nstruct ClearErrorOnReturn {\n  ClearErrorOnReturn() {\n    ERR_clear_error();\n  }\n  ~ClearErrorOnReturn() {\n    ERR_clear_error();\n  }\n  KJ_DISALLOW_COPY_AND_MOVE(ClearErrorOnReturn);\n\n  uint32_t peekError() {\n    return ERR_peek_error();\n  }\n  uint32_t consumeError() {\n    return ERR_get_error();\n  }\n};\n\n// Returns ceil(a / b) for integers (std::ceil always returns a floating point result).\ntemplate <typename T>\nstatic inline T integerCeilDivision(T a, T b) {\n  static_assert(std::is_unsigned_v<T>);\n  return a == 0 ? 0 : 1 + (a - 1) / b;\n}\n\n// A wrapper for kj::Array<kj::byte> that will ensure the memory is overwritten\n// with zeroes when destroyed.\nclass ZeroOnFree {\n public:\n  inline ZeroOnFree(kj::Array<kj::byte>&& inner): inner(kj::mv(inner)) {}\n  ~ZeroOnFree() noexcept(false);\n\n  inline size_t size() const {\n    return inner.size();\n  }\n  inline const kj::byte* begin() const {\n    return inner.begin();\n  }\n  inline operator kj::ArrayPtr<const kj::byte>() const {\n    return inner.asPtr();\n  }\n  inline operator const kj::Array<kj::byte>&() const {\n    return inner;\n  }\n  inline kj::ArrayPtr<kj::byte> asPtr() {\n    return inner.asPtr();\n  }\n  inline kj::ArrayPtr<const kj::byte> asPtr() const {\n    return inner.asPtr();\n  }\n\n private:\n  kj::Array<kj::byte> inner;\n};\n\n// Check that the requested number of iterations for a key-derivation function\n// is acceptable. If the requested iterations is not acceptable, a JS error will\n// be thrown. Otherwise the method will return normally.\nvoid checkPbkdfLimits(jsg::Lock& js, size_t iterations);\n\n// Either succeeds with exactly |length| bytes of cryptographically\n// strong pseudo-random data, or fails. This function may block.\n// Don't assume anything about the contents of |buffer| on error.\n// As a special case, |length == 0| can be used to check if the CSPRNG\n// is properly seeded without consuming entropy.\nbool CSPRNG(kj::ArrayPtr<kj::byte> buffer);\n\nkj::Own<CryptoKey::Impl> fromRsaKey(jsg::Lock& js, kj::Own<EVP_PKEY> key);\nkj::Own<CryptoKey::Impl> fromEcKey(kj::Own<EVP_PKEY> key);\nkj::Own<CryptoKey::Impl> fromEd25519Key(kj::Own<EVP_PKEY> key);\n\n// If the input bytes are a valid ASN.1 sequence, return them minus the prefix.\nkj::Maybe<kj::ArrayPtr<const kj::byte>> tryGetAsn1Sequence(kj::ArrayPtr<const kj::byte> data);\n\ntemplate <typename T = const kj::byte>\nncrypto::Buffer<T> ToNcryptoBuffer(kj::ArrayPtr<T> array) {\n  return ncrypto::Buffer<T>(array.begin(), array.size());\n}\n\nkj::Maybe<kj::Array<kj::byte>> simdutfBase64UrlDecode(kj::StringPtr input);\nkj::Maybe<jsg::JsUint8Array> simdutfBase64UrlDecode(jsg::Lock& js, kj::StringPtr input);\njsg::JsUint8Array simdutfBase64UrlDecodeChecked(\n    jsg::Lock& js, kj::StringPtr input, kj::StringPtr error);\n\n}  // namespace workerd::api\n\nKJ_DECLARE_NON_POLYMORPHIC(DH);\nKJ_DECLARE_NON_POLYMORPHIC(EC_KEY);\nKJ_DECLARE_NON_POLYMORPHIC(EC_POINT);\nKJ_DECLARE_NON_POLYMORPHIC(EC_GROUP);\nKJ_DECLARE_NON_POLYMORPHIC(BN_CTX);\nKJ_DECLARE_NON_POLYMORPHIC(EVP_PKEY);\nKJ_DECLARE_NON_POLYMORPHIC(EVP_PKEY_CTX);\nKJ_DECLARE_NON_POLYMORPHIC(RSA);\n// Tell KJ that these OpenSSL types are non-polymorphic so that they can be wrapped in kj::Own.\n"
  },
  {
    "path": "src/workerd/api/crypto/jwk.c++",
    "content": "#include \"jwk.h\"\n\n#include <ncrypto.h>\n#include <openssl/curve25519.h>\n\nnamespace workerd::api {\n\nusing JsonWebKey = SubtleCrypto::JsonWebKey;\nusing ncrypto::BignumPointer;\nusing ncrypto::ECKeyPointer;\nusing ncrypto::EVPKeyPointer;\nusing ncrypto::RSAPointer;\n\nnamespace {\n\nkj::String getCurveName(int nid) {\n  switch (nid) {\n    case NID_X9_62_prime256v1:\n      return kj::str(\"P-256\");\n    case NID_secp256k1:\n      return kj::str(\"secp256k1\");\n    case NID_secp384r1:\n      return kj::str(\"P-384\");\n    case NID_secp521r1:\n      return kj::str(\"P-521\");\n    default:\n      return kj::String();\n  }\n}\n\nint getCurveFromName(kj::StringPtr name) {\n  int nid = EC_curve_nist2nid(name.begin());\n  if (nid == NID_undef) nid = OBJ_sn2nid(name.begin());\n  return nid;\n}\n\nint getOKPCurveFromName(kj::StringPtr name) {\n  int nid;\n  if (name == \"Ed25519\") {\n    nid = EVP_PKEY_ED25519;\n  } else if (name == \"X25519\") {\n    nid = EVP_PKEY_X25519;\n  } else {\n    // 448 keys are not supported by boringssl\n    nid = NID_undef;\n  }\n  return nid;\n}\n\nJsonWebKey jwkFromEdKey(const EVPKeyPointer& key, KeyType keyType) {\n  KJ_REQUIRE(key, \"Key must not be null\");\n  KJ_ASSERT(key.id() == EVP_PKEY_ED25519 || key.id() == EVP_PKEY_X25519,\n      \"Key must be an Ed25519 or X25519 key\");\n\n  auto pkey = key.rawPublicKey();\n  JSG_REQUIRE(pkey, InternalDOMOperationError, \"Failed to retrieve public key\",\n      internalDescribeOpensslErrors());\n  KJ_ASSERT(pkey.size() == 32);\n  kj::ArrayPtr<const kj::byte> rawPublicKey(static_cast<const kj::byte*>(pkey.get()), pkey.size());\n\n  JsonWebKey jwk;\n  jwk.kty = kj::str(\"OKP\");\n  jwk.crv = key.id() == EVP_PKEY_X25519 ? kj::str(\"X25519\") : kj::str(\"Ed25519\");\n  jwk.x = fastEncodeBase64Url(rawPublicKey);\n  if (key.id() == EVP_PKEY_ED25519) {\n    jwk.alg = kj::str(\"EdDSA\");\n  }\n\n  if (keyType == KeyType::PRIVATE) {\n    // Deliberately use ED25519_PUBLIC_KEY_LEN here.\n    // BoringSSL defines ED25519_PRIVATE_KEY_LEN as 64B since it stores the private key together\n    // with public key data in some functions, but in the EVP interface only the 32B private key\n    // itself is returned.\n    uint8_t rawPrivateKey[ED25519_PUBLIC_KEY_LEN]{};\n    size_t privateKeyLen = ED25519_PUBLIC_KEY_LEN;\n    JSG_REQUIRE(1 == EVP_PKEY_get_raw_private_key(key.get(), rawPrivateKey, &privateKeyLen),\n        InternalDOMOperationError, \"Failed to retrieve private key\",\n        internalDescribeOpensslErrors());\n    KJ_ASSERT(privateKeyLen == 32, privateKeyLen);\n    jwk.d = fastEncodeBase64Url(kj::arrayPtr(rawPrivateKey, privateKeyLen));\n  }\n\n  return jwk;\n}\n\nJsonWebKey jwkFromEcKey(const EVPKeyPointer& key, KeyType keyType) {\n  KJ_REQUIRE(key, \"Key must not be null\");\n  KJ_ASSERT(key.id() == EVP_PKEY_EC, \"Key must be an EC key\");\n\n  ncrypto::Ec ec = key;\n\n  JSG_REQUIRE(ec.getX() && ec.getY(), InternalDOMOperationError,\n      \"Error getting affine coordinates for export\", internalDescribeOpensslErrors());\n\n  JSG_REQUIRE(ec.getGroup() != nullptr, DOMOperationError, \"No elliptic curve group in this key\",\n      tryDescribeOpensslErrors());\n  JSG_REQUIRE(ec.getPublicKey(), DOMOperationError, \"No public elliptic curve key data in this key\",\n      tryDescribeOpensslErrors());\n\n  auto groupDegreeInBytes = integerCeilDivision(ec.getDegree(), 8U);\n  // getDegree() returns number of bits. We need this because x, y, & d need\n  // to match the group degree according to JWK.\n\n  SubtleCrypto::JsonWebKey jwk;\n  jwk.kty = kj::str(\"EC\");\n  jwk.crv = getCurveName(ec.getCurve());\n\n  static constexpr auto handleBn = [](const BIGNUM& bn, size_t size) {\n    return JSG_REQUIRE_NONNULL(bignumToArrayPadded(bn, size), InternalDOMOperationError,\n        \"Error converting EC affine co-ordinates to padded array\", internalDescribeOpensslErrors());\n  };\n\n  // We check that getX and getY return good values above.\n  auto xa = handleBn(*ec.getX().get(), groupDegreeInBytes);\n  jwk.x = fastEncodeBase64Url(xa);\n\n  auto ya = handleBn(*ec.getY().get(), groupDegreeInBytes);\n  jwk.y = fastEncodeBase64Url(ya);\n\n  if (keyType == KeyType::PRIVATE) {\n    auto privateKey = ec.getPrivateKey();\n    JSG_REQUIRE(privateKey, InternalDOMOperationError,\n        \"Error getting private key material for JSON Web Key export\",\n        internalDescribeOpensslErrors());\n    auto pk = handleBn(*privateKey, groupDegreeInBytes);\n    jwk.d = fastEncodeBase64Url(pk);\n  }\n  return jwk;\n}\n\nJsonWebKey jwkFromRsaKey(const EVPKeyPointer& key, KeyType keyType) {\n  SubtleCrypto::JsonWebKey jwk;\n  jwk.kty = kj::str(\"RSA\");\n\n  ncrypto::Rsa rsa = key;\n  auto publicKey = rsa.getPublicKey();\n\n  if (publicKey.n != nullptr) {\n    jwk.n = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(*publicKey.n)));\n  }\n  if (publicKey.e != nullptr) {\n    jwk.e = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(*publicKey.e)));\n  }\n\n  if (keyType == KeyType::PRIVATE) {\n    auto privateKey = rsa.getPrivateKey();\n    if (publicKey.d != nullptr) {\n      jwk.d = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(*publicKey.d)));\n    }\n    if (privateKey.p != nullptr) {\n      jwk.p = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(*privateKey.p)));\n    }\n    if (privateKey.q != nullptr) {\n      jwk.q = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(*privateKey.q)));\n    }\n    if (privateKey.dp != nullptr) {\n      jwk.dp = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(*privateKey.dp)));\n    }\n    if (privateKey.dq != nullptr) {\n      jwk.dq = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(*privateKey.dq)));\n    }\n    if (privateKey.qi != nullptr) {\n      jwk.qi = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(*privateKey.qi)));\n    }\n  }\n\n  return jwk;\n}\n\nEVPKeyPointer rsaKeyFromJwk(const JsonWebKey& jwk, KeyType keyType) {\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n\n  RSAPointer rsa(RSA_new());\n  if (!rsa) return {};\n  ncrypto::Rsa rsa_view(rsa.get());\n\n  auto& n = JSG_REQUIRE_NONNULL(jwk.n, Error, \"RSA JWK missing n parameter\");\n  auto& e = JSG_REQUIRE_NONNULL(jwk.e, Error, \"RSA JWK missing e parameter\");\n  auto N = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(n), Error, \"RSA JWK invalid n parameter\");\n  auto E = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(e), Error, \"RSA JWK invalid e parameter\");\n\n  BignumPointer n_bn(N.begin(), N.size());\n  BignumPointer e_bn(E.begin(), E.size());\n\n  JSG_REQUIRE(\n      rsa_view.setPublicKey(kj::mv(n_bn), kj::mv(e_bn)), Error, \"RSA JWK invalid public key\");\n\n  if (keyType == KeyType::PRIVATE) {\n    auto& d = JSG_REQUIRE_NONNULL(jwk.d, Error, \"RSA JWK missing d parameter\");\n    auto& p = JSG_REQUIRE_NONNULL(jwk.p, Error, \"RSA JWK missing p parameter\");\n    auto& q = JSG_REQUIRE_NONNULL(jwk.q, Error, \"RSA JWK missing q parameter\");\n    auto& dp = JSG_REQUIRE_NONNULL(jwk.dp, Error, \"RSA JWK missing dp parameter\");\n    auto& dq = JSG_REQUIRE_NONNULL(jwk.dq, Error, \"RSA JWK missing dq parameter\");\n    auto& qi = JSG_REQUIRE_NONNULL(jwk.qi, Error, \"RSA JWK missing qi parameter\");\n\n    auto D = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(d), Error, \"RSA JWK invalid d parameter\");\n    auto P = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(p), Error, \"RSA JWK invalid p parameter\");\n    auto Q = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(q), Error, \"RSA JWK invalid q parameter\");\n    auto DP =\n        JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(dp), Error, \"RSA JWK invalid dp parameter\");\n    auto DQ =\n        JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(dq), Error, \"RSA JWK invalid dq parameter\");\n    auto QI =\n        JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(qi), Error, \"RSA JWK invalid qi parameter\");\n\n    BignumPointer d_bn(D.begin(), D.size());\n    BignumPointer p_bn(P.begin(), P.size());\n    BignumPointer q_bn(Q.begin(), Q.size());\n    BignumPointer dp_bn(DP.begin(), DP.size());\n    BignumPointer dq_bn(DQ.begin(), DQ.size());\n    BignumPointer qi_bn(QI.begin(), QI.size());\n\n    JSG_REQUIRE(rsa_view.setPrivateKey(kj::mv(d_bn), kj::mv(q_bn), kj::mv(p_bn), kj::mv(dp_bn),\n                    kj::mv(dq_bn), kj::mv(qi_bn)),\n        Error, \"RSA JWK invalid private key\");\n  }\n\n  return EVPKeyPointer::NewRSA(std::move(rsa));\n}\n\nEVPKeyPointer ecKeyFromJwk(const JsonWebKey& jwk, KeyType keyType) {\n  int nid = getCurveFromName(JSG_REQUIRE_NONNULL(jwk.crv, Error, \"EC JWK missing crv parameter\"));\n  JSG_REQUIRE(nid != NID_undef, Error, \"EC JWK unsupported crv parameter\");\n\n  auto ec = ECKeyPointer::NewByCurveName(nid);\n  JSG_REQUIRE(ec, Error, \"EC JWK unsupported curve\");\n\n  auto& x = JSG_REQUIRE_NONNULL(jwk.x, Error, \"EC JWK missing x parameter\");\n  auto& y = JSG_REQUIRE_NONNULL(jwk.y, Error, \"EC JWK missing y parameter\");\n\n  auto X = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(x), Error, \"EC JWK invalid x parameter\");\n  auto Y = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(y), Error, \"EC JWK invalid y parameter\");\n\n  BignumPointer x_bn(X.begin(), X.size());\n  BignumPointer y_bn(Y.begin(), Y.size());\n\n  JSG_REQUIRE(ec.setPublicKeyRaw(kj::mv(x_bn), kj::mv(y_bn)), Error, \"EC JWK invalid public key\");\n\n  if (keyType == KeyType::PRIVATE) {\n    auto& d = JSG_REQUIRE_NONNULL(jwk.d, Error, \"EC JWK missing d parameter\");\n    auto D = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(d), Error, \"EC JWK invalid d parameter\");\n\n    BignumPointer d_bn(D.begin(), D.size());\n    JSG_REQUIRE(ec.setPrivateKey(kj::mv(d_bn)), Error, \"EW JWK invalid private key\");\n  }\n\n  auto pkey = EVPKeyPointer::New();\n  if (!pkey || !pkey.set(ec)) return {};\n  return pkey;\n}\n\nEVPKeyPointer edKeyFromJwk(const JsonWebKey& jwk, KeyType keyType) {\n  int nid =\n      getOKPCurveFromName(JSG_REQUIRE_NONNULL(jwk.crv, Error, \"OKP JWK missing crv parameter\"));\n  JSG_REQUIRE(nid != NID_undef, Error, \"OKP JWK unsupported crv parameter\");\n\n  if (keyType == KeyType::PRIVATE) {\n    auto& d = JSG_REQUIRE_NONNULL(jwk.d, Error, \"OKP JWK missing d parameter\");\n    auto D = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(d), Error, \"OKP JWK invalid d parameter\");\n    return EVPKeyPointer::NewRawPrivate(nid, ToNcryptoBuffer(D.asPtr().asConst()));\n  }\n\n  auto& x = JSG_REQUIRE_NONNULL(jwk.x, Error, \"OKP JWK missing x parameter\");\n  auto X = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(x), Error, \"OKP JWK invalid x parameter\");\n  return EVPKeyPointer::NewRawPrivate(nid, ToNcryptoBuffer(X.asPtr().asConst()));\n}\n}  // namespace\n\nJsonWebKey toJwk(const EVPKeyPointer& key, KeyType keyType) {\n  if (key) {\n    switch (key.id()) {\n      case EVP_PKEY_ED25519:\n        return jwkFromEdKey(key, keyType);\n      case EVP_PKEY_X25519:\n        return jwkFromEdKey(key, keyType);\n      case EVP_PKEY_EC:\n        return jwkFromEcKey(key, keyType);\n      case EVP_PKEY_RSA:\n        return jwkFromRsaKey(key, keyType);\n      case EVP_PKEY_RSA2:\n        return jwkFromRsaKey(key, keyType);\n      case EVP_PKEY_RSA_PSS:\n        return jwkFromRsaKey(key, keyType);\n      case EVP_PKEY_DSA: {\n        // DSA keys are not supported for JWK export.\n        break;\n      }\n    }\n  }\n\n  return JsonWebKey{\n    .kty = kj::str(\"INVALID\"),\n  };\n}\n\nEVPKeyPointer fromJwk(const JsonWebKey& jwk, KeyType keyType) {\n  if (jwk.kty == \"OKP\") return edKeyFromJwk(jwk, keyType);\n  if (jwk.kty == \"EC\") return ecKeyFromJwk(jwk, keyType);\n  if (jwk.kty == \"RSA\") return rsaKeyFromJwk(jwk, keyType);\n  return {};\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/jwk.h",
    "content": "#pragma once\n\n#include \"keys.h\"\n\nnamespace workerd::api {\n\n// TODO(soon): All of the JWK conversion logic will be moved in here\n// soon. Currently this only covers conversion with ncrypto keys not\n// kj::Own held keys.\n\nSubtleCrypto::JsonWebKey toJwk(\n    const ncrypto::EVPKeyPointer& key, KeyType keyType = KeyType::PUBLIC);\n\nncrypto::EVPKeyPointer fromJwk(\n    const SubtleCrypto::JsonWebKey& jwk, KeyType keyType = KeyType::PUBLIC);\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/kdf.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/jsvalue.h>\n\n#include <kj/common.h>\n\n#include <cstdint>\n\nusing EVP_MD = struct env_md_st;\n\nnamespace workerd::api {\n\n// Perform HKDF key derivation.\nkj::Maybe<jsg::JsArrayBuffer> hkdf(jsg::Lock& js,\n    size_t length,\n    const EVP_MD* digest,\n    kj::ArrayPtr<const kj::byte> key,\n    kj::ArrayPtr<const kj::byte> salt,\n    kj::ArrayPtr<const kj::byte> info);\n\n// Perform PBKDF2 key derivation.\nkj::Maybe<jsg::JsArrayBuffer> pbkdf2(jsg::Lock& js,\n    size_t length,\n    size_t iterations,\n    const EVP_MD* digest,\n    kj::ArrayPtr<const kj::byte> password,\n    kj::ArrayPtr<const kj::byte> salt);\n\n// Perform Scrypt key derivation.\nkj::Maybe<jsg::JsArrayBuffer> scrypt(jsg::Lock& js,\n    size_t length,\n    uint32_t N,\n    uint32_t r,\n    uint32_t p,\n    uint32_t maxmem,\n    kj::ArrayPtr<const kj::byte> pass,\n    kj::ArrayPtr<const kj::byte> salt);\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/keys.c++",
    "content": "#include \"keys.h\"\n\n#include <openssl/crypto.h>\n#include <openssl/ec_key.h>\n#include <openssl/pem.h>\n#include <openssl/x509.h>\n\nnamespace workerd::api {\n\nnamespace {\nstatic const char EMPTY_PASSPHRASE[] = \"\";\n}  // namespace\n\nkj::StringPtr toStringPtr(KeyType type) {\n  switch (type) {\n    case KeyType::SECRET:\n      return \"secret\"_kj;\n    case KeyType::PUBLIC:\n      return \"public\"_kj;\n    case KeyType::PRIVATE:\n      return \"private\"_kj;\n  }\n  KJ_UNREACHABLE;\n}\n\nAsymmetricKeyCryptoKeyImpl::AsymmetricKeyCryptoKeyImpl(AsymmetricKeyData&& key, bool extractable)\n    : CryptoKey::Impl(extractable, key.usages),\n      keyData(kj::mv(key.evpPkey)),\n      keyType(key.keyType) {\n  KJ_DASSERT(keyType != KeyType::SECRET);\n}\n\njsg::JsArrayBuffer AsymmetricKeyCryptoKeyImpl::signatureSslToWebCrypto(\n    jsg::Lock& js, kj::ArrayPtr<kj::byte> signature) const {\n  return jsg::JsArrayBuffer::create(js, signature);\n}\n\njsg::JsArrayBuffer AsymmetricKeyCryptoKeyImpl::signatureWebCryptoToSsl(\n    jsg::Lock& js, kj::ArrayPtr<const kj::byte> signature) const {\n  return jsg::JsArrayBuffer::create(js, signature);\n}\n\nSubtleCrypto::ExportKeyData AsymmetricKeyCryptoKeyImpl::exportKey(\n    jsg::Lock& js, kj::StringPtr format) const {\n  // EVP_marshal_{public,private}_key() functions are BoringSSL\n  // extensions which export asymmetric keys in DER format.\n  // DER is the binary format which *should* work to export any EVP_PKEY.\n\n  uint8_t* der = nullptr;\n  // The caller takes ownership of the buffer and, unless the buffer was fixed with CBB_init_fixed,\n  // must call OPENSSL_free when done.\n  // https://commondatastorage.googleapis.com/chromium-boringssl-docs/bytestring.h.html#CBB_finish\n  KJ_DEFER(if (der != nullptr) { OPENSSL_free(der); });\n  size_t derLen;\n  bssl::ScopedCBB cbb;\n  if (format == \"pkcs8\"_kj) {\n    JSG_REQUIRE(keyType == KeyType::PRIVATE, DOMInvalidAccessError,\n        \"Asymmetric pkcs8 export requires private key (not \\\"\", toStringPtr(keyType), \"\\\").\");\n    if (!CBB_init(cbb.get(), 0) || !EVP_marshal_private_key(cbb.get(), keyData.get()) ||\n        !CBB_finish(cbb.get(), &der, &derLen)) {\n      JSG_FAIL_REQUIRE(DOMOperationError, \"Private key export failed.\");\n    }\n  } else if (format == \"spki\"_kj) {\n    JSG_REQUIRE(keyType == KeyType::PUBLIC, DOMInvalidAccessError,\n        \"Asymmetric spki export requires public key (not \\\"\", toStringPtr(keyType), \"\\\").\");\n    if (!CBB_init(cbb.get(), 0) || !EVP_marshal_public_key(cbb.get(), keyData.get()) ||\n        !CBB_finish(cbb.get(), &der, &derLen)) {\n      JSG_FAIL_REQUIRE(DOMOperationError, \"Public key export failed.\");\n    }\n  } else if (format == \"jwk\"_kj) {\n    auto jwk = exportJwk();\n    // Implicitly extractable since the normative part of the implementation validates that\n    // already.\n    jwk.ext = true;\n    jwk.key_ops = getUsages().map([](auto usage) { return kj::str(usage.name()); });\n    return jwk;\n  } else if (format == \"raw\"_kj) {\n    return exportRaw(js).addRef(js);\n  } else {\n    JSG_FAIL_REQUIRE(DOMInvalidAccessError, \"Cannot export \\\"\", getAlgorithmName(), \"\\\" in \\\"\",\n        format, \"\\\" format.\");\n  }\n\n  return jsg::JsArrayBuffer::create(js, kj::arrayPtr(der, derLen)).addRef(js);\n}\n\njsg::JsUint8Array AsymmetricKeyCryptoKeyImpl::exportKeyExt(jsg::Lock& js,\n    kj::StringPtr format,\n    kj::StringPtr type,\n    jsg::Optional<kj::String> cipher,\n    jsg::Optional<kj::Array<kj::byte>> passphrase) const {\n  KJ_REQUIRE(isExtractable(), \"Key is not extractable.\");\n  MarkPopErrorOnReturn mark_pop_error_on_return;\n  KJ_REQUIRE(format != \"jwk\", \"jwk export not supported for exportKeyExt\");\n  auto pkey = getEvpPkey();\n  auto bio = OSSL_BIO_MEM();\n\n  struct EncDetail {\n    // const_cast is acceptable, pass will be reassigned before it is written to.\n    char* pass = const_cast<char*>(&EMPTY_PASSPHRASE[0]);\n    size_t pass_len = 0;\n    const EVP_CIPHER* cipher = nullptr;\n  };\n\n  const auto getEncDetail = [&] {\n    EncDetail detail;\n    KJ_IF_SOME(pw, passphrase) {\n      detail.pass = reinterpret_cast<char*>(pw.begin());\n      detail.pass_len = pw.size();\n    }\n    KJ_IF_SOME(ciph, cipher) {\n      detail.cipher = EVP_get_cipherbyname(ciph.cStr());\n      JSG_REQUIRE(detail.cipher != nullptr, TypeError, \"Unknown cipher \", ciph);\n      KJ_REQUIRE(detail.pass != nullptr);\n    }\n    return detail;\n  };\n\n  const auto fromBio = [&](kj::StringPtr format) {\n    BUF_MEM* bptr;\n    BIO_get_mem_ptr(bio.get(), &bptr);\n    auto src = kj::arrayPtr(bptr->data, bptr->length);\n    return jsg::JsUint8Array::create(js, src.asBytes());\n  };\n\n  if (getType() == \"public\"_kj) {\n    // Here we only care about the format and the type.\n    if (type == \"pkcs1\"_kj) {\n      // PKCS#1 is only for RSA keys.\n      JSG_REQUIRE(EVP_PKEY_id(pkey) == EVP_PKEY_RSA, TypeError,\n          \"The pkcs1 type is only valid for RSA keys.\");\n      auto rsa = EVP_PKEY_get1_RSA(pkey);\n      KJ_DEFER(RSA_free(rsa));\n      if (format == \"pem\"_kj) {\n        if (PEM_write_bio_RSAPublicKey(bio.get(), rsa) == 1) {\n          return fromBio(format);\n        }\n      } else if (format == \"der\"_kj) {\n        if (i2d_RSAPublicKey_bio(bio.get(), rsa) == 1) {\n          return fromBio(format);\n        }\n      }\n    } else if (type == \"spki\"_kj) {\n      if (format == \"pem\"_kj) {\n        if (PEM_write_bio_PUBKEY(bio.get(), pkey) == 1) {\n          return fromBio(format);\n        }\n      } else if (format == \"der\"_kj) {\n        if (i2d_PUBKEY_bio(bio.get(), pkey) == 1) {\n          return fromBio(format);\n        }\n      }\n    }\n    JSG_FAIL_REQUIRE(TypeError, \"Failed to encode public key\");\n  }\n\n  // Otherwise it's a private key.\n  KJ_REQUIRE(getType() == \"private\"_kj);\n\n  if (type == \"pkcs1\"_kj) {\n    // PKCS#1 is only for RSA keys.\n    JSG_REQUIRE(\n        EVP_PKEY_id(pkey) == EVP_PKEY_RSA, TypeError, \"The pkcs1 type is only valid for RSA keys.\");\n    auto rsa = EVP_PKEY_get1_RSA(pkey);\n    KJ_DEFER(RSA_free(rsa));\n    if (format == \"pem\"_kj) {\n      auto enc = getEncDetail();\n      if (PEM_write_bio_RSAPrivateKey(bio.get(), rsa, enc.cipher,\n              reinterpret_cast<unsigned char*>(enc.pass), enc.pass_len, nullptr, nullptr) == 1) {\n        return fromBio(format);\n      }\n    } else if (format == \"der\"_kj) {\n      // The cipher and passphrase are ignored for DER with PKCS#1.\n      if (i2d_RSAPrivateKey_bio(bio.get(), rsa) == 1) {\n        return fromBio(format);\n      }\n    }\n  } else if (type == \"pkcs8\"_kj) {\n    auto enc = getEncDetail();\n    if (format == \"pem\"_kj) {\n      if (PEM_write_bio_PKCS8PrivateKey(\n              bio.get(), pkey, enc.cipher, enc.pass, enc.pass_len, nullptr, nullptr) == 1) {\n        return fromBio(format);\n      }\n    } else if (format == \"der\"_kj) {\n      if (i2d_PKCS8PrivateKey_bio(\n              bio.get(), pkey, enc.cipher, enc.pass, enc.pass_len, nullptr, nullptr) == 1) {\n        return fromBio(format);\n      }\n    }\n  } else if (type == \"sec1\"_kj) {\n    // SEC1 is only used for EC keys.\n    JSG_REQUIRE(\n        EVP_PKEY_id(pkey) == EVP_PKEY_EC, TypeError, \"The sec1 type is only valid for EC keys.\");\n    auto ec = EVP_PKEY_get1_EC_KEY(pkey);\n    KJ_DEFER(EC_KEY_free(ec));\n    if (format == \"pem\"_kj) {\n      auto enc = getEncDetail();\n      if (PEM_write_bio_ECPrivateKey(bio.get(), ec, enc.cipher,\n              reinterpret_cast<unsigned char*>(enc.pass), enc.pass_len, nullptr, nullptr) == 1) {\n        return fromBio(format);\n      }\n    } else if (format == \"der\"_kj) {\n      // The cipher and passphrase are ignored for DER with SEC1\n      if (i2d_ECPrivateKey_bio(bio.get(), ec) == 1) {\n        return fromBio(format);\n      }\n    }\n  }\n\n  JSG_FAIL_REQUIRE(TypeError, \"Failed to encode private key\");\n}\n\njsg::JsArrayBuffer AsymmetricKeyCryptoKeyImpl::sign(jsg::Lock& js,\n    SubtleCrypto::SignAlgorithm&& algorithm,\n    kj::ArrayPtr<const kj::byte> data) const {\n  JSG_REQUIRE(keyType == KeyType::PRIVATE, DOMInvalidAccessError,\n      \"Asymmetric signing requires a private key.\");\n\n  auto type = lookupDigestAlgorithm(chooseHash(algorithm.hash)).second;\n  if (getAlgorithmName() == \"RSASSA-PKCS1-v1_5\") {\n    // RSASSA-PKCS1-v1_5 requires the RSA key to be at least as big as the digest size\n    // plus a 15 to 19 byte digest-specific prefix (see BoringSSL's RSA_add_pkcs1_prefix) plus 11\n    // bytes for padding (see RSA_PKCS1_PADDING_SIZE). For simplicity, require the key to be at\n    // least 32 bytes larger than the hash digest.\n    // Similar checks could also be adopted for more detailed error handling in verify(), but the\n    // current approach should be sufficient to avoid internal errors.\n    RSA& rsa = JSG_REQUIRE_NONNULL(EVP_PKEY_get0_RSA(getEvpPkey()), DOMDataError, \"Missing RSA key\",\n        tryDescribeOpensslErrors());\n\n    JSG_REQUIRE(EVP_MD_size(type) + 32 <= RSA_size(&rsa), DOMOperationError,\n        \"key too small for signing with given digest, need at least \", 8 * (EVP_MD_size(type) + 32),\n        \"bits.\");\n  } else if (getAlgorithmName() == \"RSA-PSS\") {\n    // Similarly, RSA-PSS requires keys to be at least the size of the digest and salt plus 2\n    // bytes, see https://developer.mozilla.org/en-US/docs/Web/API/RsaPssParams for details.\n    RSA& rsa = JSG_REQUIRE_NONNULL(EVP_PKEY_get0_RSA(getEvpPkey()), DOMDataError, \"Missing RSA key\",\n        tryDescribeOpensslErrors());\n    auto salt = JSG_REQUIRE_NONNULL(algorithm.saltLength, DOMDataError,\n        \"Failed to provide salt for RSA-PSS key operation which requires a salt\");\n    JSG_REQUIRE(salt >= 0, DOMDataError, \"SaltLength for RSA-PSS must be non-negative \",\n        \"(provided \", salt, \").\");\n    JSG_REQUIRE(EVP_MD_size(type) + 2 <= RSA_size(&rsa), DOMOperationError,\n        \"key too small for signing with given digest\");\n    JSG_REQUIRE(salt <= RSA_size(&rsa) - EVP_MD_size(type) - 2, DOMOperationError,\n        \"key too small for signing with given digest and salt length\");\n  }\n\n  auto digestCtx = OSSL_NEW(EVP_MD_CTX);\n\n  OSSLCALL(EVP_DigestSignInit(digestCtx.get(), nullptr, type, nullptr, keyData.get()));\n  addSalt(digestCtx->pctx, algorithm);\n  // No-op call unless CryptoKey is RsaPss\n  OSSLCALL(EVP_DigestSignUpdate(digestCtx.get(), data.begin(), data.size()));\n  size_t signatureSize = 0;\n  OSSLCALL(EVP_DigestSignFinal(digestCtx.get(), nullptr, &signatureSize));\n\n  KJ_STACK_ARRAY(kj::byte, signature, signatureSize, 256, 256);\n  OSSLCALL(EVP_DigestSignFinal(digestCtx.get(), signature.begin(), &signatureSize));\n\n  KJ_ASSERT(signatureSize <= signature.size());\n  if (signatureSize < signature.size()) {\n    return signatureSslToWebCrypto(js, signature.first(signatureSize));\n  }\n\n  return signatureSslToWebCrypto(js, signature);\n}\n\nbool AsymmetricKeyCryptoKeyImpl::verify(jsg::Lock& js,\n    SubtleCrypto::SignAlgorithm&& algorithm,\n    kj::ArrayPtr<const kj::byte> signature,\n    kj::ArrayPtr<const kj::byte> data) const {\n  ClearErrorOnReturn clearErrorOnReturn;\n\n  JSG_REQUIRE(keyType == KeyType::PUBLIC, DOMInvalidAccessError,\n      \"Asymmetric verification requires a public key.\");\n\n  auto sslSignature = signatureWebCryptoToSsl(js, signature);\n\n  auto type = lookupDigestAlgorithm(chooseHash(algorithm.hash)).second;\n\n  auto digestCtx = OSSL_NEW(EVP_MD_CTX);\n\n  OSSLCALL(EVP_DigestVerifyInit(digestCtx.get(), nullptr, type, nullptr, keyData.get()));\n  addSalt(digestCtx->pctx, algorithm);\n  // No-op call unless CryptoKey is RsaPss\n  OSSLCALL(EVP_DigestVerifyUpdate(digestCtx.get(), data.begin(), data.size()));\n  // EVP_DigestVerifyFinal() returns 1 on success, 0 on invalid signature, and any other value\n  // indicates \"a more serious error\".\n  auto result = EVP_DigestVerifyFinal(\n      digestCtx.get(), sslSignature.asArrayPtr().begin(), sslSignature.size());\n  JSG_REQUIRE(result == 0 || result == 1, InternalDOMOperationError,\n      \"Unexpected return code from digest verify\", getAlgorithmName());\n  return !!result;\n}\n\nbool AsymmetricKeyCryptoKeyImpl::equals(const CryptoKey::Impl& other) const {\n  if (this == &other) return true;\n  KJ_IF_SOME(otherImpl, kj::dynamicDowncastIfAvailable<const AsymmetricKeyCryptoKeyImpl>(other)) {\n    // EVP_PKEY_cmp will return 1 if the inputs match, 0 if they don't match,\n    // -1 if the key types are different, and -2 if the operation is not supported.\n    // We only really care about the first two cases.\n    return EVP_PKEY_cmp(keyData.get(), otherImpl.keyData.get()) == 1;\n  }\n  return false;\n}\n\nkj::StringPtr AsymmetricKeyCryptoKeyImpl::getType() const {\n  return toStringPtr(keyType);\n}\n\nbool AsymmetricKeyCryptoKeyImpl::verifyX509Public(const X509* cert) const {\n  ClearErrorOnReturn clearErrorOnReturn;\n  return X509_verify(const_cast<X509*>(cert), getEvpPkey()) > 0;\n}\n\nbool AsymmetricKeyCryptoKeyImpl::verifyX509Private(const X509* cert) const {\n  ClearErrorOnReturn clearErrorOnReturn;\n  return X509_check_private_key(const_cast<X509*>(cert), getEvpPkey()) == 1;\n}\n\n// ======================================================================================\n\nAsymmetricKeyData importAsymmetricForWebCrypto(jsg::Lock& js,\n    kj::StringPtr format,\n    SubtleCrypto::ImportKeyData keyData,\n    kj::StringPtr normalizedName,\n    bool extractable,\n    kj::ArrayPtr<const kj::String> keyUsages,\n    kj::FunctionParam<kj::Own<EVP_PKEY>(SubtleCrypto::JsonWebKey)> readJwk,\n    CryptoKeyUsageSet allowedUsages) {\n  CryptoKeyUsageSet usages;\n  if (format == \"jwk\") {\n    // I found jww's SO answer immeasurably helpful while writing this:\n    // https://stackoverflow.com/questions/24093272/how-to-load-a-private-key-from-a-jwk-into-openssl\n\n    auto& keyDataJwk = JSG_REQUIRE_NONNULL(keyData.tryGet<SubtleCrypto::JsonWebKey>(), DOMDataError,\n        \"JSON Web Key import requires a JSON Web Key object.\");\n\n    KeyType keyType = KeyType::PRIVATE;\n    if (keyDataJwk.d != kj::none) {\n      // Private key (`d` is the private exponent, per RFC 7518).\n      keyType = KeyType::PRIVATE;\n      usages =\n          CryptoKeyUsageSet::validate(normalizedName, CryptoKeyUsageSet::Context::importPrivate,\n              keyUsages, allowedUsages & CryptoKeyUsageSet::privateKeyMask());\n\n      // https://tools.ietf.org/html/rfc7518#section-6.3.2.7\n      // We don't support keys with > 2 primes, so error out.\n      JSG_REQUIRE(keyDataJwk.oth == kj::none, DOMNotSupportedError,\n          \"Multi-prime private keys not supported.\");\n    } else {\n      // Public key.\n      keyType = KeyType::PUBLIC;\n      auto strictCrypto = FeatureFlags::get(js).getStrictCrypto();\n      // restrict key usages to public key usages. In the case of ECDH, usages must be empty, but\n      // if the strict crypto compat flag is not enabled allow the same usages as with private ECDH\n      // keys, i.e. derivationKeyMask().\n      usages = CryptoKeyUsageSet::validate(normalizedName, CryptoKeyUsageSet::Context::importPublic,\n          keyUsages,\n          allowedUsages &\n              (normalizedName == \"ECDH\"\n                      ? strictCrypto ? CryptoKeyUsageSet() : CryptoKeyUsageSet::derivationKeyMask()\n                      : CryptoKeyUsageSet::publicKeyMask()));\n    }\n\n    auto [expectedUse, op0, op1] = [&, normalizedName] {\n      if (normalizedName == \"RSA-OAEP\") {\n        return std::make_tuple(\"enc\", \"encrypt\", \"wrapKey\");\n      }\n      if (normalizedName == \"ECDH\" || normalizedName == \"X25519\") {\n        return std::make_tuple(\"enc\", \"unused\", \"unused\");\n      }\n      return std::make_tuple(\"sig\", \"sign\", \"verify\");\n    }();\n\n    if (keyUsages.size() > 0) {\n      KJ_IF_SOME(use, keyDataJwk.use) {\n        JSG_REQUIRE(use == expectedUse, DOMDataError,\n            \"Asymmetric \\\"jwk\\\" key import with usages requires a JSON Web Key with \"\n            \"Public Key Use parameter \\\"use\\\" (\\\"\",\n            use, \"\\\") equal to \\\"sig\\\".\");\n      }\n    }\n\n    KJ_IF_SOME(ops, keyDataJwk.key_ops) {\n      // TODO(cleanup): When we implement other JWK import functions, factor this part out into a\n      //   JWK validation function.\n\n      // \"The key operation values are case-sensitive strings.  Duplicate key operation values MUST\n      // NOT be present in the array.\" -- RFC 7517, section 4.3\n      std::sort(ops.begin(), ops.end());\n      JSG_REQUIRE(std::adjacent_find(ops.begin(), ops.end()) == ops.end(), DOMDataError,\n          \"A JSON Web Key's Key Operations parameter (\\\"key_ops\\\") \"\n          \"must not contain duplicates.\");\n\n      KJ_IF_SOME(use, keyDataJwk.use) {\n        // \"The \"use\" and \"key_ops\" JWK members SHOULD NOT be used together; however, if both are\n        // used, the information they convey MUST be consistent.\" -- RFC 7517, section 4.3.\n\n        JSG_REQUIRE(use == expectedUse, DOMDataError,\n            \"Asymmetric \\\"jwk\\\" import requires a JSON \"\n            \"Web Key with Public Key Use \\\"use\\\" (\\\"\",\n            use, \"\\\") equal to \\\"\", expectedUse, \"\\\".\");\n\n        for (const auto& op: ops) {\n          JSG_REQUIRE(normalizedName != \"ECDH\" && normalizedName != \"X25519\", DOMDataError,\n              \"A JSON Web Key should have either a Public Key Use parameter (\\\"use\\\") or a Key \"\n              \"Operations parameter (\\\"key_ops\\\"); otherwise, the parameters must be consistent \"\n              \"with each other. For public \",\n              normalizedName,\n              \" keys, there are no valid usages,\"\n              \"so keys with a non-empty \\\"key_ops\\\" parameter are not allowed.\");\n\n          // TODO(conform): Can a JWK private key actually be used to verify? Not\n          //   using the Web Crypto API...\n          JSG_REQUIRE(op == op0 || op == op1, DOMDataError,\n              \"A JSON Web Key should have either a Public Key Use parameter (\\\"use\\\") or a Key \"\n              \"Operations parameter (\\\"key_ops\\\"); otherwise, the parameters must be consistent \"\n              \"with each other. A Public Key Use for \",\n              normalizedName,\n              \" would allow a Key \"\n              \"Operations array with only \\\"\",\n              op0, \"\\\" and/or \\\"\", op1, \"\\\" values (not \\\"\", op, \"\\\").\");\n        }\n      }\n\n      // We're supposed to verify that `ops` contains all the values listed in `keyUsages`. For any\n      // of the supported algorithms, a key may have at most two distinct usages ('sig' type keys\n      // have at most one valid usage, but there may be two for e.g. ECDH). Test the first usage\n      // and the next usages. Test the first usage and the first usage distinct from the first, if\n      // present (i.e. the second allowed usage, even if there are duplicates).\n      if (keyUsages.size() > 0) {\n        JSG_REQUIRE(std::find(ops.begin(), ops.end(), keyUsages.front()) != ops.end(), DOMDataError,\n            \"All specified key usages must be present in the JSON \"\n            \"Web Key's Key Operations parameter (\\\"key_ops\\\").\");\n        auto secondUsage = std::find_end(keyUsages.begin(), keyUsages.end(), keyUsages.begin(),\n                               keyUsages.begin() + 1) +\n            1;\n        if (secondUsage != keyUsages.end()) {\n          JSG_REQUIRE(std::find(ops.begin(), ops.end(), *secondUsage) != ops.end(), DOMDataError,\n              \"All specified key usages must be present in the JSON \"\n              \"Web Key's Key Operations parameter (\\\"key_ops\\\").\");\n        }\n      }\n    }\n\n    KJ_IF_SOME(ext, keyDataJwk.ext) {\n      // If the user requested this key to be extractable, make sure the JWK does not disallow it.\n      JSG_REQUIRE(!extractable || ext, DOMDataError,\n          \"Cannot create an extractable CryptoKey from an unextractable JSON Web Key.\");\n    }\n\n    return {readJwk(kj::mv(keyDataJwk)), keyType, usages};\n  } else if (format == \"spki\") {\n    kj::ArrayPtr<const kj::byte> keyBytes =\n        JSG_REQUIRE_NONNULL(keyData.tryGet<kj::Array<kj::byte>>(), DOMDataError,\n            \"SPKI import requires an ArrayBuffer.\");\n    const kj::byte* ptr = keyBytes.begin();\n    auto evpPkey = OSSLCALL_OWN(\n        EVP_PKEY, d2i_PUBKEY(nullptr, &ptr, keyBytes.size()), DOMDataError, \"Invalid SPKI input.\");\n    if (ptr != keyBytes.end()) {\n      JSG_FAIL_REQUIRE(\n          DOMDataError, \"Invalid \", keyBytes.end() - ptr, \" trailing bytes after SPKI input.\");\n    }\n\n    // usages must be empty for ECDH public keys, so use CryptoKeyUsageSet() when validating the\n    // usage set.\n    usages = CryptoKeyUsageSet::validate(normalizedName, CryptoKeyUsageSet::Context::importPublic,\n        keyUsages,\n        allowedUsages &\n            (normalizedName == \"ECDH\" ? CryptoKeyUsageSet() : CryptoKeyUsageSet::publicKeyMask()));\n    return {kj::mv(evpPkey), KeyType::PUBLIC, usages};\n  } else if (format == \"pkcs8\") {\n    kj::ArrayPtr<const kj::byte> keyBytes =\n        JSG_REQUIRE_NONNULL(keyData.tryGet<kj::Array<kj::byte>>(), DOMDataError,\n            \"PKCS8 import requires an ArrayBuffer.\");\n    const kj::byte* ptr = keyBytes.begin();\n    auto evpPkey = OSSLCALL_OWN(EVP_PKEY, d2i_AutoPrivateKey(nullptr, &ptr, keyBytes.size()),\n        DOMDataError, \"Invalid PKCS8 input.\");\n    if (ptr != keyBytes.end()) {\n      JSG_FAIL_REQUIRE(\n          DOMDataError, \"Invalid \", keyBytes.end() - ptr, \" trailing bytes after PKCS8 input.\");\n    }\n    usages = CryptoKeyUsageSet::validate(normalizedName, CryptoKeyUsageSet::Context::importPrivate,\n        keyUsages, allowedUsages & CryptoKeyUsageSet::privateKeyMask());\n    return {kj::mv(evpPkey), KeyType::PRIVATE, usages};\n  } else {\n    JSG_FAIL_REQUIRE(DOMNotSupportedError, \"Unrecognized key import format \\\"\", format, \"\\\".\");\n  }\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/keys.h",
    "content": "#pragma once\n\n#include \"impl.h\"\n\nnamespace workerd::api {\n\nenum class KeyEncoding {\n  PKCS1,\n  PKCS8,\n  SPKI,\n  SEC1,\n};\n\ninline kj::StringPtr KJ_STRINGIFY(KeyEncoding encoding) {\n  switch (encoding) {\n    case KeyEncoding::PKCS1:\n      return \"pkcs1\";\n    case KeyEncoding::PKCS8:\n      return \"pkcs8\";\n    case KeyEncoding::SPKI:\n      return \"spki\";\n    case KeyEncoding::SEC1:\n      return \"sec1\";\n  }\n  KJ_UNREACHABLE;\n}\n\nenum class KeyFormat {\n  PEM,\n  DER,\n  JWK,\n};\n\nenum class KeyType {\n  SECRET,\n  PUBLIC,\n  PRIVATE,\n};\n\nkj::StringPtr toStringPtr(KeyType type);\n\nstruct AsymmetricKeyData {\n  kj::Own<EVP_PKEY> evpPkey;\n  KeyType keyType;\n  CryptoKeyUsageSet usages;\n};\n\nclass AsymmetricKeyCryptoKeyImpl: public CryptoKey::Impl {\n public:\n  explicit AsymmetricKeyCryptoKeyImpl(AsymmetricKeyData&& key, bool extractable);\n\n  // ---------------------------------------------------------------------------\n  // Subclasses must implement these\n\n  // virtual CryptoKey::AlgorithmVariant getAlgorithm() = 0;\n  // kj::StringPtr getAlgorithmName() const = 0;\n  // (inherited from CryptoKey::Impl, needs to be implemented by subclass)\n\n  // Determine the hash function to use. Some algorithms choose this at key import time while\n  // others choose it at sign() or verify() time. `callTimeHash` is the hash name passed to the\n  // call.\n  virtual kj::StringPtr chooseHash(\n      const kj::Maybe<kj::OneOf<kj::String, SubtleCrypto::HashAlgorithm>>& callTimeHash) const = 0;\n\n  // Convert OpenSSL-format signature to WebCrypto-format signature, if different.\n  virtual jsg::JsArrayBuffer signatureSslToWebCrypto(\n      jsg::Lock& js, kj::ArrayPtr<kj::byte> signature) const;\n\n  // Convert WebCrypto-format signature to OpenSSL-format signature, if different.\n  virtual jsg::JsArrayBuffer signatureWebCryptoToSsl(\n      jsg::Lock& js, kj::ArrayPtr<const kj::byte> signature) const;\n\n  // Add salt to digest context in order to generate or verify salted signature.\n  // Currently only used for RSA-PSS sign and verify operations.\n  virtual void addSalt(\n      EVP_PKEY_CTX* digestCtx, const SubtleCrypto::SignAlgorithm& algorithm) const {}\n\n  // ---------------------------------------------------------------------------\n  // Implementation of CryptoKey\n\n  SubtleCrypto::ExportKeyData exportKey(jsg::Lock& js, kj::StringPtr format) const override final;\n\n  virtual jsg::JsUint8Array exportKeyExt(jsg::Lock& js,\n      kj::StringPtr format,\n      kj::StringPtr type,\n      jsg::Optional<kj::String> cipher = kj::none,\n      jsg::Optional<kj::Array<kj::byte>> passphrase = kj::none) const override final;\n\n  jsg::JsArrayBuffer sign(jsg::Lock& js,\n      SubtleCrypto::SignAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> data) const override;\n\n  bool verify(jsg::Lock& js,\n      SubtleCrypto::SignAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> signature,\n      kj::ArrayPtr<const kj::byte> data) const override;\n\n  kj::StringPtr getType() const override;\n  KeyType getTypeEnum() const {\n    return keyType;\n  }\n\n  inline EVP_PKEY* getEvpPkey() const {\n    return keyData.get();\n  }\n\n  bool equals(const CryptoKey::Impl& other) const override final;\n\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"AsymmetricKey\";\n  }\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(AsymmetricKeyCryptoKeyImpl);\n  }\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {}\n\n  bool verifyX509Public(const X509* cert) const override;\n  bool verifyX509Private(const X509* cert) const override;\n\n private:\n  virtual SubtleCrypto::JsonWebKey exportJwk() const = 0;\n  virtual jsg::JsArrayBuffer exportRaw(jsg::Lock& js) const = 0;\n\n  mutable kj::Own<EVP_PKEY> keyData;\n  // mutable because OpenSSL wants non-const pointers even when the object won't be modified...\n  KeyType keyType;\n};\n\n// Performs asymmetric key import per the Web Crypto spec.\nAsymmetricKeyData importAsymmetricForWebCrypto(jsg::Lock& js,\n    kj::StringPtr format,\n    SubtleCrypto::ImportKeyData keyData,\n    kj::StringPtr normalizedName,\n    bool extractable,\n    kj::ArrayPtr<const kj::String> keyUsages,\n    kj::FunctionParam<kj::Own<EVP_PKEY>(SubtleCrypto::JsonWebKey)> readJwk,\n    CryptoKeyUsageSet allowedUsages);\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/pbkdf2.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"impl.h\"\n#include \"kdf.h\"\n\n#include <ncrypto.h>\n\nnamespace workerd::api {\nnamespace {\n\n// The underlying implementation of PBKDF2 for WebCrypto.\n// The CryptoKey::Impl here is used only for web crypto uses.\nclass Pbkdf2Key final: public CryptoKey::Impl {\n public:\n  explicit Pbkdf2Key(kj::Array<kj::byte> keyData,\n      CryptoKey::KeyAlgorithm keyAlgorithm,\n      bool extractable,\n      CryptoKeyUsageSet usages)\n      : CryptoKey::Impl(extractable, usages),\n        keyData(kj::mv(keyData)),\n        keyAlgorithm(kj::mv(keyAlgorithm)) {}\n\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"Pbkdf2Key\";\n  }\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(Pbkdf2Key);\n  }\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {\n    tracker.trackFieldWithSize(\"keyData\", keyData.size());\n    tracker.trackField(\"keyAlgorithm\", keyAlgorithm);\n  }\n\n private:\n  jsg::JsArrayBuffer deriveBits(jsg::Lock& js,\n      SubtleCrypto::DeriveKeyAlgorithm&& algorithm,\n      kj::Maybe<uint32_t> maybeLength) const override {\n    kj::StringPtr hashName = api::getAlgorithmName(\n        JSG_REQUIRE_NONNULL(algorithm.hash, TypeError, \"Missing field \\\"hash\\\" in \\\"algorithm\\\".\"));\n    auto hashType = lookupDigestAlgorithm(hashName).second;\n    auto saltHandle =\n        JSG_REQUIRE_NONNULL(algorithm.salt, TypeError, \"Missing field \\\"salt\\\" in \\\"algorithm\\\".\")\n            .getHandle(js);\n    kj::ArrayPtr<kj::byte> salt = saltHandle.asArrayPtr();\n    int iterations = JSG_REQUIRE_NONNULL(\n        algorithm.iterations, TypeError, \"Missing field \\\"iterations\\\" in \\\"algorithm\\\".\");\n\n    uint32_t length = JSG_REQUIRE_NONNULL(\n        maybeLength, DOMOperationError, \"PBKDF2 cannot derive a key with null length.\");\n\n    JSG_REQUIRE(length % 8 == 0, DOMOperationError,\n        \"PBKDF2 requires a derived key length that is a multiple of eight (requested \", length,\n        \").\");\n\n    JSG_REQUIRE(iterations > 0, DOMOperationError,\n        \"PBKDF2 requires a positive iteration count (requested \", iterations, \").\");\n\n    // Note: The user could DoS us by selecting a very high iteration count. Our dead man's switch\n    // would kick in, resulting in a process restart. We guard against this by limiting the\n    // maximum iteration count a user can select -- this is an intentional non-conformity.\n    // Another approach might be to fork OpenSSL's PKCS5_PBKDF2_HMAC() function and insert a\n    // check for v8::Isolate::IsExecutionTerminating() in the loop, but for now a hard cap seems\n    // wisest.\n    checkPbkdfLimits(js, iterations);\n\n    return JSG_REQUIRE_NONNULL(pbkdf2(js, length / 8, iterations, hashType, keyData, salt), Error,\n        \"PBKDF2 deriveBits failed.\");\n  }\n\n  // TODO(bug): Possibly by mistake, PBKDF2 was historically not on the allow list of\n  //   algorithms in exportKey(). Later, the allow list was removed, instead assuming that any\n  //   algorithm which implemented this method must be allowed. To maintain exactly the\n  //   preexisting behavior, then, this implementation had to be commented out. If disallowing this\n  //   was a mistake, we can un-comment this method, but we would need to make sure to add tests\n  //   when we do.\n  //   SubtleCrypto::ExportKeyData exportKey(kj::StringPtr format) const override {\n  //     JSG_REQUIRE(format == \"raw\", DOMNotSupportedError,\n  //         \"Unimplemented key export format \\\"\", format, \"\\\".\");\n  //     return kj::heapArray(keyData.asPtr());\n  //   }\n\n  kj::StringPtr getAlgorithmName() const override {\n    return \"PBKDF2\";\n  }\n  CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {\n    return keyAlgorithm;\n  }\n\n  bool equals(const CryptoKey::Impl& other) const override final {\n    return this == &other || (other.getType() == \"secret\"_kj && other.equals(keyData));\n  }\n\n  bool equals(const kj::Array<kj::byte>& other) const override final {\n    return keyData.size() == other.size() &&\n        CRYPTO_memcmp(keyData.begin(), other.begin(), keyData.size()) == 0;\n  }\n\n  ZeroOnFree keyData;\n  CryptoKey::KeyAlgorithm keyAlgorithm;\n};\n}  // namespace\n\nkj::Maybe<jsg::JsArrayBuffer> pbkdf2(jsg::Lock& js,\n    size_t length,\n    size_t iterations,\n    const EVP_MD* digest,\n    kj::ArrayPtr<const kj::byte> password,\n    kj::ArrayPtr<const kj::byte> salt) {\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n  auto buf = jsg::JsArrayBuffer::create(js, length);\n  auto ncBuf = ToNcryptoBuffer(buf.asArrayPtr());\n  if (ncrypto::pbkdf2Into(digest, ToNcryptoBuffer(password.asChars()), ToNcryptoBuffer(salt),\n          iterations, length, &ncBuf)) {\n    return kj::mv(buf);\n  }\n  return kj::none;\n}\n\nkj::Own<CryptoKey::Impl> CryptoKey::Impl::importPbkdf2(jsg::Lock& js,\n    kj::StringPtr normalizedName,\n    kj::StringPtr format,\n    SubtleCrypto::ImportKeyData keyData,\n    SubtleCrypto::ImportKeyAlgorithm&& algorithm,\n    bool extractable,\n    kj::ArrayPtr<const kj::String> keyUsages) {\n  auto usages = CryptoKeyUsageSet::validate(normalizedName,\n      CryptoKeyUsageSet::Context::importSecret, keyUsages, CryptoKeyUsageSet::derivationKeyMask());\n\n  JSG_REQUIRE(!extractable, DOMSyntaxError, \"PBKDF2 key cannot be extractable.\");\n  JSG_REQUIRE(format == \"raw\", DOMNotSupportedError,\n      \"PBKDF2 key must be imported in \\\"raw\\\" format (requested \\\"\", format, \"\\\").\");\n\n  // NOTE: Checked in SubtleCrypto::importKey().\n  auto keyDataArray = kj::mv(keyData.get<kj::Array<kj::byte>>());\n\n  auto keyAlgorithm = CryptoKey::KeyAlgorithm{normalizedName};\n  return kj::heap<Pbkdf2Key>(kj::mv(keyDataArray), kj::mv(keyAlgorithm), extractable, usages);\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/prime.c++",
    "content": "#include \"prime.h\"\n\n#include \"impl.h\"\n\n#include <workerd/jsg/jsg.h>\n\n#include <ncrypto.h>\n\nnamespace workerd::api {\n\njsg::JsArrayBuffer randomPrime(jsg::Lock& js,\n    uint32_t size,\n    bool safe,\n    kj::Maybe<kj::ArrayPtr<kj::byte>> add_buf,\n    kj::Maybe<kj::ArrayPtr<kj::byte>> rem_buf) {\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n\n  // Use mapping to have kj::Own work with optional buffer\n  static const auto toBignum =\n      [](kj::Maybe<kj::ArrayPtr<kj::byte>>& maybeBignum) -> ncrypto::BignumPointer {\n    KJ_IF_SOME(a, maybeBignum) {\n      if (auto bn = ncrypto::BignumPointer(a.begin(), a.size())) {\n        return bn;\n      }\n      JSG_FAIL_REQUIRE(\n          RangeError, \"Error importing add parameter\", internalDescribeOpensslErrors());\n    };\n    return {};\n  };\n\n  auto add = toBignum(add_buf);\n  auto rem = toBignum(rem_buf);\n  // The JS interface already ensures that the (positive) size fits into an int.\n  int bits = static_cast<int>(size);\n\n  if (add) {\n    // Currently, we only allow certain values for add and rem due to a bug in\n    // the BN_generate_prime_ex that allows invalid values to enter an infinite\n    // loop. This diverges from the Node.js implementation a bit but that's OK.\n    // The key use case for this function is generating DH parameters and those\n    // have pretty specific values for various generators anyway.\n    // Specifically, we limit the values of add and rem to match the specific\n    // pairings: add: 12, rem 11, add 24, rem 23, and add 60, rem 59.\n    // If users complain about this, we can always remove this check and try\n    // to get the infinite loop bug fixed.\n\n    auto addCheck = ncrypto::BignumPointer::New();\n    auto remCheck = ncrypto::BignumPointer::New();\n    const auto checkAddRem = [&](auto anum, auto bnum) {\n      addCheck.setWord(anum);\n      remCheck.setWord(bnum);\n      return BN_cmp(add.get(), addCheck.get()) == 0 && BN_cmp(rem.get(), remCheck.get()) == 0;\n    };\n\n    JSG_REQUIRE(rem && (checkAddRem(12, 11) || checkAddRem(24, 23) || checkAddRem(60, 59)),\n        RangeError, \"Invalid values for add and rem\");\n\n    // This would definitely lead to an infinite loop if allowed since\n    // OpenSSL does not check this condition.\n    JSG_REQUIRE(add > rem, RangeError, \"options.rem must be smaller than options.add\");\n\n    // If we allowed this, the best case would be returning a static prime\n    // that wasn't generated randomly. The worst case would be an infinite\n    // loop within OpenSSL, blocking the main thread or one of the threads\n    // in the thread pool.\n    JSG_REQUIRE(add.bitLength() <= bits, RangeError,\n        \"options.add must not be bigger than size of the requested prime\");\n  }\n\n  // Generating random primes uses the PRNG internally.\n  // Make sure the CSPRNG is properly seeded.\n  JSG_REQUIRE(\n      workerd::api::CSPRNG(nullptr), Error, \"Error while generating prime (bad random state)\");\n\n  if (auto prime = ncrypto::BignumPointer::NewPrime({\n        .bits = bits,\n        .safe = safe,\n        .add = kj::mv(add),\n        .rem = kj::mv(rem),\n      })) {\n    auto buf = JSG_REQUIRE_NONNULL(\n        bignumToArrayPadded(js, *prime.get()), Error, \"Error while generating prime\");\n    return jsg::JsArrayBuffer::create(js, buf.asArrayPtr());\n  }\n\n  JSG_FAIL_REQUIRE(Error, \"Error while generating prime\");\n}\n\nbool checkPrime(kj::ArrayPtr<kj::byte> bufferView, uint32_t num_checks) {\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n  static constexpr int32_t kMaxChecks = kj::maxValue;\n  // Strictly upper bound the number of checks. If this proves to be too expensive\n  // then we may need to consider lowering this limit further.\n  JSG_REQUIRE(num_checks <= kMaxChecks, RangeError, \"Invalid number of checks\");\n\n  auto candidate = ncrypto::BignumPointer(bufferView.begin(), bufferView.size());\n  JSG_REQUIRE(candidate, Error, \"Error while checking prime\");\n  return candidate.isPrime(num_checks);\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/prime.h",
    "content": "#pragma once\n\n#include <kj/common.h>\n\n#include <cstdint>\n\nnamespace workerd::jsg {\nclass Lock;\nclass JsArrayBuffer;\n}  // namespace workerd::jsg\n\nnamespace workerd::api {\n\n// Generate a random prime number\njsg::JsArrayBuffer randomPrime(jsg::Lock& js,\n    uint32_t size,\n    bool safe,\n    kj::Maybe<kj::ArrayPtr<kj::byte>> add_buf,\n    kj::Maybe<kj::ArrayPtr<kj::byte>> rem_buf);\n\n// Checks if the given buffer represents a prime.\nbool checkPrime(kj::ArrayPtr<kj::byte> buffer, uint32_t num_checks);\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/rsa.c++",
    "content": "#include \"rsa.h\"\n\n#include \"impl.h\"\n#include \"keys.h\"\n#include \"util.h\"\n\n#include <openssl/bn.h>\n#include <openssl/crypto.h>\n#include <openssl/evp.h>\n#include <openssl/pem.h>\n\n#include <kj/array.h>\n#include <kj/common.h>\n\n#include <map>\n\nnamespace workerd::api {\n\nnamespace {\ntemplate <typename T>\nkj::Maybe<T> fromBignum(kj::ArrayPtr<kj::byte> value) {\n  static_assert(std::is_unsigned_v<T>, \"This can only be invoked when the return type is unsigned\");\n\n  T asUnsigned = 0;\n  for (size_t i = 0; i < value.size(); ++i) {\n    size_t bitShift = value.size() - i - 1;\n    if (bitShift >= sizeof(T) && value[i]) {\n      // Too large for desired type.\n      return kj::none;\n    }\n\n    asUnsigned |= value[i] << 8 * bitShift;\n  }\n\n  return asUnsigned;\n}\n\njsg::JsArrayBuffer bioToArray(jsg::Lock& js, BIO* bio) {\n  BUF_MEM* bptr;\n  BIO_get_mem_ptr(bio, &bptr);\n  return jsg::JsArrayBuffer::create(js, kj::asBytes(bptr->data, bptr->length));\n}\n}  // namespace\n\nkj::Maybe<Rsa> Rsa::tryGetRsa(const EVP_PKEY* key) {\n  int type = EVP_PKEY_id(key);\n  if (type != EVP_PKEY_RSA && type != EVP_PKEY_RSA_PSS) return kj::none;\n  auto rsa = EVP_PKEY_get0_RSA(key);\n  if (rsa == nullptr) return kj::none;\n  return Rsa(rsa);\n}\n\nRsa::Rsa(RSA* rsa): rsa(rsa) {\n  RSA_get0_key(rsa, &n, &e, &d);\n}\n\nsize_t Rsa::getModulusBits() const {\n  return getModulusSize() * 8;\n}\n\nsize_t Rsa::getModulusSize() const {\n  return RSA_size(rsa);\n}\n\njsg::JsUint8Array Rsa::getPublicExponent(jsg::Lock& js) {\n  return KJ_REQUIRE_NONNULL(bignumToArray(js, *e));\n}\n\nCryptoKey::AsymmetricKeyDetails Rsa::getAsymmetricKeyDetail(jsg::Lock& js) const {\n  CryptoKey::AsymmetricKeyDetails details;\n\n  details.modulusLength = BN_num_bits(n);\n  auto pubExp =\n      JSG_REQUIRE_NONNULL(bignumToArrayPadded(js, *e), Error, \"Failed to extract public exponent\");\n  auto ab = jsg::JsArrayBuffer::create(js, pubExp.asArrayPtr());\n  details.publicExponent = ab.addRef(js);\n\n  // TODO(soon): Does BoringSSL not support retrieving RSA_PSS params?\n  // if (type == EVP_PKEY_RSA_PSS) {\n  //   // Due to the way ASN.1 encoding works, default values are omitted when\n  //   // encoding the data structure. However, there are also RSA-PSS keys for\n  //   // which no parameters are set. In that case, the ASN.1 RSASSA-PSS-params\n  //   // sequence will be missing entirely and RSA_get0_pss_params will return\n  //   // nullptr. If parameters are present but all parameters are set to their\n  //   // default values, an empty sequence will be stored in the ASN.1 structure.\n  //   // In that case, RSA_get0_pss_params does not return nullptr but all fields\n  //   // of the returned RSA_PSS_PARAMS will be set to nullptr.\n\n  //   const RSA_PSS_PARAMS* params = RSA_get0_pss_params(rsa);\n  //   if (params != nullptr) {\n  //     int hash_nid = NID_sha1;\n  //     int mgf_nid = NID_mgf1;\n  //     int mgf1_hash_nid = NID_sha1;\n  //     int64_t salt_length = 20;\n\n  //     if (params->hashAlgorithm != nullptr) {\n  //       hash_nid = OBJ_obj2nid(params->hashAlgorithm->algorithm);\n  //     }\n  //     details.hashAlgorithm = kj::str(OBJ_nid2ln(hash_nid));\n\n  //     if (params->maskGenAlgorithm != nullptr) {\n  //       mgf_nid = OBJ_obj2nid(params->maskGenAlgorithm->algorithm);\n  //       if (mgf_nid == NID_mgf1) {\n  //         mgf1_hash_nid = OBJ_obj2nid(params->maskHash->algorithm);\n  //       }\n  //     }\n\n  //     // If, for some reason, the MGF is not MGF1, then the MGF1 hash function\n  //     // is intentionally not added to the object.\n  //     if (mgf_nid == NID_mgf1) {\n  //       details.mgf1HashAlgorithm = kj::str(OBJ_nid2ln(mgf1_hash_nid));\n  //     }\n\n  //     if (params->saltLength != nullptr) {\n  //       JSG_REQUIRE(ASN1_INTEGER_get_int64(&salt_length, params->saltLength) == 1,\n  //                   Error, \"Unable to get salt length from RSA-PSS parameters\");\n  //     }\n  //     details.saltLength = static_cast<double>(salt_length);\n  //   }\n  // }\n\n  return kj::mv(details);\n}\n\njsg::JsArrayBuffer Rsa::sign(jsg::Lock& js, const kj::ArrayPtr<const kj::byte> data) const {\n  size_t size = getModulusSize();\n\n  // RSA encryption/decryption requires the key value to be strictly larger than the value to be\n  // signed. Ideally we would enforce this by checking that the key size is larger than the input\n  // size – having both the same size makes it highly likely that some values are higher than the\n  // key value – but there are scripts and test cases that depend on signing data with keys of\n  // the same size.\n  JSG_REQUIRE(data.size() <= size, DOMDataError, \"Blind Signing requires presigned data (\",\n      data.size(),\n      \" bytes) to be smaller than \"\n      \"the key (\",\n      size, \" bytes).\");\n  if (data.size() == size) {\n    auto dataVal = JSG_REQUIRE_NONNULL(toBignum(data), InternalDOMOperationError,\n        \"Error converting presigned data\", internalDescribeOpensslErrors());\n    JSG_REQUIRE(BN_ucmp(dataVal, getN()) < 0, DOMDataError,\n        \"Blind Signing requires presigned data value to be strictly smaller than RSA key\"\n        \"modulus, consider using a larger key size.\");\n  }\n\n  KJ_STACK_ARRAY(kj::byte, signature, size, 256, 256);\n  size_t signatureSize = 0;\n  OSSLCALL(RSA_decrypt(rsa, &signatureSize, signature.begin(), signature.size(), data.begin(),\n      data.size(), RSA_NO_PADDING));\n  KJ_ASSERT(signatureSize <= signature.size());\n\n  return jsg::JsArrayBuffer::create(js, signature.first(signatureSize));\n}\n\njsg::JsArrayBuffer Rsa::cipher(jsg::Lock& js,\n    EVP_PKEY_CTX* ctx,\n    SubtleCrypto::EncryptAlgorithm&& algorithm,\n    kj::ArrayPtr<const kj::byte> data,\n    EncryptDecryptFunction encryptDecrypt,\n    const EVP_MD* digest) const {\n\n  JSG_REQUIRE(1 == EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING),\n      InternalDOMOperationError, \"Error doing RSA OAEP encrypt/decrypt (\", \"padding\", \")\",\n      internalDescribeOpensslErrors());\n  JSG_REQUIRE(1 == EVP_PKEY_CTX_set_rsa_oaep_md(ctx, digest), InternalDOMOperationError,\n      \"Error doing RSA OAEP encrypt/decrypt (\", \"message digest\", \")\",\n      internalDescribeOpensslErrors());\n  JSG_REQUIRE(1 == EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, digest), InternalDOMOperationError,\n      \"Error doing RSA OAEP encrypt/decrypt (\", \"MGF1 digest\", \")\",\n      internalDescribeOpensslErrors());\n\n  KJ_IF_SOME(lRef, algorithm.label) {\n    auto l = lRef.getHandle(js);\n    auto labelCopy = reinterpret_cast<uint8_t*>(OPENSSL_malloc(l.size()));\n    KJ_DEFER(OPENSSL_free(labelCopy));\n    // If setting the label fails we need to remember to destroy the buffer. In practice it can't\n    // actually happen since we set RSA_PKCS1_OAEP_PADDING above & that appears to be the only way\n    // this API call can fail.\n\n    JSG_REQUIRE(labelCopy != nullptr, DOMOperationError,\n        \"Failed to allocate space for RSA-OAEP label copy\", tryDescribeOpensslErrors());\n    kj::arrayPtr(labelCopy, l.size()).copyFrom(l.asArrayPtr());\n\n    // EVP_PKEY_CTX_set0_rsa_oaep_label below takes ownership of the buffer passed in (must have\n    // been OPENSSL_malloc-allocated).\n    JSG_REQUIRE(1 == EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, labelCopy, l.size()), DOMOperationError,\n        \"Failed to set RSA-OAEP label\", tryDescribeOpensslErrors());\n\n    // Ownership has now been transferred. The chromium WebCrypto code technically has a potential\n    // memory leak here in that they check the error for EVP_PKEY_CTX_set0_rsa_oaep_label after\n    // releasing. It's not actually possible though because the padding mode is set unconditionally\n    // to RSA_PKCS1_OAEP_PADDING which seems to be the only way setting the label will fail.\n    labelCopy = nullptr;\n  }\n\n  size_t maxResultLength = 0;\n  // First compute an upper bound on the amount of space we need to store the encrypted/decrypted\n  // result. Then we actually apply the encryption & finally resize to the actual correct length.\n  JSG_REQUIRE(1 == encryptDecrypt(ctx, nullptr, &maxResultLength, data.begin(), data.size()),\n      DOMOperationError, \"Failed to compute length of RSA-OAEP result\", tryDescribeOpensslErrors());\n\n  kj::Vector<kj::byte> result(maxResultLength);\n  auto err = encryptDecrypt(ctx, result.begin(), &maxResultLength, data.begin(), data.size());\n  JSG_REQUIRE(\n      1 == err, DOMOperationError, \"RSA-OAEP failed encrypt/decrypt\", tryDescribeOpensslErrors());\n  result.resize(maxResultLength);\n\n  return jsg::JsArrayBuffer::create(js, result.asPtr());\n}\n\nSubtleCrypto::JsonWebKey Rsa::toJwk(\n    KeyType keyType, kj::Maybe<kj::String> maybeHashAlgorithm) const {\n  SubtleCrypto::JsonWebKey jwk;\n  jwk.kty = kj::str(\"RSA\");\n  KJ_IF_SOME(name, maybeHashAlgorithm) {\n    jwk.alg = kj::mv(name);\n  }\n\n  jwk.n = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(KJ_REQUIRE_NONNULL(n))));\n  jwk.e = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(KJ_REQUIRE_NONNULL(e))));\n\n  if (keyType == KeyType::PRIVATE) {\n    jwk.d = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(KJ_REQUIRE_NONNULL(d))));\n    jwk.p =\n        fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(KJ_REQUIRE_NONNULL(RSA_get0_p(rsa)))));\n    jwk.q =\n        fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(KJ_REQUIRE_NONNULL(RSA_get0_q(rsa)))));\n    jwk.dp = fastEncodeBase64Url(\n        KJ_REQUIRE_NONNULL(bignumToArray(KJ_REQUIRE_NONNULL(RSA_get0_dmp1(rsa)))));\n    jwk.dq = fastEncodeBase64Url(\n        KJ_REQUIRE_NONNULL(bignumToArray(KJ_REQUIRE_NONNULL(RSA_get0_dmq1(rsa)))));\n    jwk.qi = fastEncodeBase64Url(\n        KJ_REQUIRE_NONNULL(bignumToArray(KJ_REQUIRE_NONNULL(RSA_get0_iqmp(rsa)))));\n  }\n\n  return jwk;\n}\n\nkj::Maybe<AsymmetricKeyData> Rsa::fromJwk(\n    jsg::Lock& js, KeyType keyType, const SubtleCrypto::JsonWebKey& jwk) {\n  ClearErrorOnReturn clearErrorOnReturn;\n\n  if (jwk.kty != \"RSA\"_kj) return kj::none;\n  auto n = JSG_REQUIRE_NONNULL(jwk.n.map([](auto& str) { return str.asPtr(); }), Error,\n      \"Invalid RSA key in JSON Web Key; missing or invalid \"\n      \"Modulus parameter (\\\"n\\\").\");\n  auto e = JSG_REQUIRE_NONNULL(jwk.e.map([](auto& str) { return str.asPtr(); }), Error,\n      \"Invalid RSA key in JSON Web Key; missing or invalid \"\n      \"Exponent parameter (\\\"e\\\").\");\n\n  auto rsa = OSSL_NEW(RSA);\n\n  static constexpr auto kInvalidBase64Error = \"Invalid RSA key in JSON Web Key; invalid base64.\"_kj;\n\n  auto nBuf = simdutfBase64UrlDecodeChecked(js, n, kInvalidBase64Error);\n  auto nDecoded = toBignumOwned(nBuf.asArrayPtr());\n  auto eBuf = simdutfBase64UrlDecodeChecked(js, e, kInvalidBase64Error);\n  auto eDecoded = toBignumOwned(eBuf.asArrayPtr());\n  JSG_REQUIRE(RSA_set0_key(rsa.get(), nDecoded.get(), eDecoded.get(), nullptr) == 1, Error,\n      \"Invalid RSA key in JSON Web Key; failed to set key parameters\");\n  nDecoded.release();\n  eDecoded.release();\n\n  if (keyType == KeyType::PRIVATE) {\n    auto d = JSG_REQUIRE_NONNULL(jwk.d.map([](auto& str) { return str.asPtr(); }), Error,\n        \"Invalid RSA key in JSON Web Key; missing or invalid \"\n        \"Private Exponent parameter (\\\"d\\\").\");\n    auto p = JSG_REQUIRE_NONNULL(jwk.p.map([](auto& str) { return str.asPtr(); }), Error,\n        \"Invalid RSA key in JSON Web Key; missing or invalid \"\n        \"First Prime Factor parameter (\\\"p\\\").\");\n    auto q = JSG_REQUIRE_NONNULL(jwk.q.map([](auto& str) { return str.asPtr(); }), Error,\n        \"Invalid RSA key in JSON Web Key; missing or invalid \"\n        \"Second Prime Factor parameter (\\\"q\\\").\");\n    auto dp = JSG_REQUIRE_NONNULL(jwk.dp.map([](auto& str) { return str.asPtr(); }), Error,\n        \"Invalid RSA key in JSON Web Key; missing or invalid \"\n        \"First Factor CRT Exponent parameter (\\\"dp\\\").\");\n    auto dq = JSG_REQUIRE_NONNULL(jwk.dq.map([](auto& str) { return str.asPtr(); }), Error,\n        \"Invalid RSA key in JSON Web Key; missing or invalid \"\n        \"Second Factor CRT Exponent parameter (\\\"dq\\\").\");\n    auto qi = JSG_REQUIRE_NONNULL(jwk.qi.map([](auto& str) { return str.asPtr(); }), Error,\n        \"Invalid RSA key in JSON Web Key; missing or invalid \"\n        \"First CRT Coefficient parameter (\\\"qi\\\").\");\n    auto dBuf = simdutfBase64UrlDecodeChecked(js, d, \"Invalid RSA key in JSON Web Key\"_kj);\n    auto dDecoded = toBignumOwned(dBuf.asArrayPtr());\n    auto pBuf = simdutfBase64UrlDecodeChecked(js, p, kInvalidBase64Error);\n    auto pDecoded = toBignumOwned(pBuf.asArrayPtr());\n    auto qBuf = simdutfBase64UrlDecodeChecked(js, q, kInvalidBase64Error);\n    auto qDecoded = toBignumOwned(qBuf.asArrayPtr());\n    auto dpBuf = simdutfBase64UrlDecodeChecked(js, dp, kInvalidBase64Error);\n    auto dpDecoded = toBignumOwned(dpBuf.asArrayPtr());\n    auto dqBuf = simdutfBase64UrlDecodeChecked(js, dq, kInvalidBase64Error);\n    auto dqDecoded = toBignumOwned(dqBuf.asArrayPtr());\n    auto qiBuf = simdutfBase64UrlDecodeChecked(js, qi, kInvalidBase64Error);\n    auto qiDecoded = toBignumOwned(qiBuf.asArrayPtr());\n\n    // .release() transfers BIGNUM ownership to the RSA key. UniqueBignum ensures\n    // cleanup if any earlier allocation or decode throws.\n    JSG_REQUIRE(RSA_set0_key(rsa.get(), nullptr, nullptr, dDecoded.get()) == 1, Error,\n        \"Invalid RSA key in JSON Web Key; failed to set private exponent\");\n    dDecoded.release();\n    JSG_REQUIRE(RSA_set0_factors(rsa.get(), pDecoded.get(), qDecoded.get()) == 1, Error,\n        \"Invalid RSA key in JSON Web Key; failed to set prime factors\");\n    pDecoded.release();\n    qDecoded.release();\n    JSG_REQUIRE(\n        RSA_set0_crt_params(rsa.get(), dpDecoded.get(), dqDecoded.get(), qiDecoded.get()) == 1,\n        Error, \"Invalid RSA key in JSON Web Key; failed to set CRT parameters\");\n    dpDecoded.release();\n    dqDecoded.release();\n    qiDecoded.release();\n  }\n\n  auto evpPkey = OSSL_NEW(EVP_PKEY);\n  OSSLCALL(EVP_PKEY_set1_RSA(evpPkey.get(), rsa.get()));\n\n  auto usages = keyType == KeyType::PRIVATE ? CryptoKeyUsageSet::privateKeyMask()\n                                            : CryptoKeyUsageSet::publicKeyMask();\n  return AsymmetricKeyData{kj::mv(evpPkey), keyType, usages};\n}\n\nkj::String Rsa::toPem(\n    jsg::Lock& js, KeyEncoding encoding, KeyType keyType, kj::Maybe<CipherOptions> options) const {\n  ClearErrorOnReturn clearErrorOnReturn;\n  auto bio = OSSL_BIO_MEM();\n  switch (keyType) {\n    case KeyType::PUBLIC: {\n      switch (encoding) {\n        case KeyEncoding::PKCS1: {\n          JSG_REQUIRE(PEM_write_bio_RSAPublicKey(bio.get(), rsa) == 1, Error,\n              \"Failed to write RSA public key to PEM\", tryDescribeOpensslErrors());\n          break;\n        }\n        case workerd::api::KeyEncoding::SPKI: {\n          JSG_REQUIRE(PEM_write_bio_RSA_PUBKEY(bio.get(), rsa) == 1, Error,\n              \"Failed to write RSA public key to PEM\", tryDescribeOpensslErrors());\n          break;\n        }\n        default: {\n          JSG_FAIL_REQUIRE(Error, \"Unsupported RSA public key encoding: \", encoding);\n        }\n      }\n      break;\n    }\n    case KeyType::PRIVATE: {\n      kj::byte* passphrase = nullptr;\n      size_t passLen = 0;\n      const EVP_CIPHER* cipher = nullptr;\n      KJ_IF_SOME(opts, options) {\n        passphrase = const_cast<kj::byte*>(opts.passphrase.begin());\n        passLen = opts.passphrase.size();\n        cipher = opts.cipher;\n      }\n      switch (encoding) {\n        case KeyEncoding::PKCS1: {\n          JSG_REQUIRE(PEM_write_bio_RSAPrivateKey(\n                          bio.get(), rsa, cipher, passphrase, passLen, nullptr, nullptr) == 1,\n              Error, \"Failed to write RSA private key to PEM\", tryDescribeOpensslErrors());\n          break;\n        }\n        case KeyEncoding::PKCS8: {\n          auto evpPkey = OSSL_NEW(EVP_PKEY);\n          OSSLCALL(EVP_PKEY_set1_RSA(evpPkey.get(), rsa));\n          JSG_REQUIRE(PEM_write_bio_PKCS8PrivateKey(bio.get(), evpPkey.get(), cipher,\n                          reinterpret_cast<char*>(passphrase), passLen, nullptr, nullptr) == 1,\n              Error, \"Failed to write RSA private key to PKCS8 PEM\", tryDescribeOpensslErrors());\n          break;\n        }\n        default: {\n          JSG_FAIL_REQUIRE(Error, \"Unsupported RSA private key encoding: \", encoding);\n        }\n      }\n      break;\n    }\n    default:\n      KJ_UNREACHABLE;\n  }\n  return kj::str(bioToArray(js, bio.get()).asArrayPtr().asChars());\n}\n\njsg::JsArrayBuffer Rsa::toDer(\n    jsg::Lock& js, KeyEncoding encoding, KeyType keyType, kj::Maybe<CipherOptions> options) const {\n  ClearErrorOnReturn clearErrorOnReturn;\n  auto bio = OSSL_BIO_MEM();\n  switch (keyType) {\n    case KeyType::PUBLIC: {\n      switch (encoding) {\n        case KeyEncoding::PKCS1: {\n          JSG_REQUIRE(i2d_RSAPublicKey_bio(bio.get(), rsa) == 1, Error,\n              \"Failed to write RSA public key to DER\", tryDescribeOpensslErrors());\n          break;\n        }\n        case workerd::api::KeyEncoding::SPKI: {\n          auto evpPkey = OSSL_NEW(EVP_PKEY);\n          OSSLCALL(EVP_PKEY_set1_RSA(evpPkey.get(), rsa));\n          JSG_REQUIRE(i2d_PUBKEY_bio(bio.get(), evpPkey.get()) == 1, Error,\n              \"Failed to write RSA public key to SPKI\", tryDescribeOpensslErrors());\n          break;\n        }\n        default: {\n          JSG_FAIL_REQUIRE(Error, \"Unsupported RSA public key encoding: \", encoding);\n        }\n      }\n      break;\n    }\n    case KeyType::PRIVATE: {\n      kj::byte* passphrase = nullptr;\n      size_t passLen = 0;\n      const EVP_CIPHER* cipher = nullptr;\n      KJ_IF_SOME(opts, options) {\n        passphrase = const_cast<kj::byte*>(opts.passphrase.begin());\n        passLen = opts.passphrase.size();\n        cipher = opts.cipher;\n      }\n      switch (encoding) {\n        case KeyEncoding::PKCS1: {\n          // Does not permit encryption\n          JSG_REQUIRE(i2d_RSAPrivateKey_bio(bio.get(), rsa), Error,\n              \"Failed to write RSA private key to PEM\", tryDescribeOpensslErrors());\n          break;\n        }\n        case KeyEncoding::PKCS8: {\n          auto evpPkey = OSSL_NEW(EVP_PKEY);\n          OSSLCALL(EVP_PKEY_set1_RSA(evpPkey.get(), rsa));\n          JSG_REQUIRE(i2d_PKCS8PrivateKey_bio(bio.get(), evpPkey.get(), cipher,\n                          reinterpret_cast<char*>(passphrase), passLen, nullptr, nullptr) == 1,\n              Error, \"Failed to write RSA private key to PKCS8 PEM\", tryDescribeOpensslErrors());\n          break;\n        }\n        default: {\n          JSG_FAIL_REQUIRE(Error, \"Unsupported RSA private key encoding: \", encoding);\n        }\n      }\n      break;\n    }\n    default:\n      KJ_UNREACHABLE;\n  }\n  return bioToArray(js, bio.get());\n}\n\nvoid Rsa::validateRsaParams(\n    jsg::Lock& js, size_t modulusLength, kj::ArrayPtr<kj::byte> publicExponent, bool isImport) {\n  KJ_ASSERT(modulusLength <= ~uint16_t(0));\n  // Use Chromium's limits for RSA keygen to avoid infinite loops:\n  // * Key sizes a multiple of 8 bits.\n  // * Key sizes must be in [256, 16k] bits.\n  auto strictCrypto = FeatureFlags::get(js).getStrictCrypto();\n  JSG_REQUIRE(!(strictCrypto || !isImport) ||\n          (modulusLength % 8 == 0 && modulusLength >= 256 && modulusLength <= 16384),\n      DOMOperationError,\n      \"The modulus length must be a multiple of 8 and \"\n      \"between 256 and 16k, but \",\n      modulusLength, \" was requested.\");\n\n  // Now check the public exponent for allow-listed values.\n  // First see if we can convert the public exponent to an unsigned number. Unfortunately OpenSSL\n  // doesn't have convenient APIs to do this (since these are bignums) so we have to do it by hand.\n  // Since the problematic BIGNUMs are within the range of an unsigned int (& technically an\n  // unsigned short) we can treat an out-of-range issue as valid input.\n  KJ_IF_SOME(v, fromBignum<unsigned>(publicExponent)) {\n    if (!isImport) {\n      JSG_REQUIRE(v == 3 || v == 65537, DOMOperationError,\n          \"The \\\"publicExponent\\\" must be either 3 or 65537, but got \", v, \".\");\n    } else if (strictCrypto) {\n      // While we have long required the exponent to be 3 or 65537 when generating keys, handle\n      // imported keys more permissively and allow additional exponents that are considered safe\n      // and commonly used.\n      JSG_REQUIRE(v == 3 || v == 17 || v == 37 || v == 65537, DOMOperationError,\n          \"Imported RSA key has invalid publicExponent \", v, \".\");\n    }\n  } else {\n    JSG_FAIL_REQUIRE(DOMOperationError,\n        \"The \\\"publicExponent\\\" must be either 3 or 65537, but \"\n        \"got a number larger than 2^32.\");\n  }\n}\n\nbool Rsa::isRSAPrivateKey(kj::ArrayPtr<const kj::byte> keyData) {\n  KJ_IF_SOME(rem, tryGetAsn1Sequence(keyData)) {\n    return rem.size() >= 3 && rem[0] == 2 && rem[1] == 1 && !(rem[2] & 0xfe);\n  }\n  return false;\n}\n\n// ======================================================================================\n// Web Crypto Impl: RSASSA-PKCS1-V1_5, RSA-PSS, RSA-OEAP, RSA-RAW\n\nnamespace {\nclass RsaBase: public AsymmetricKeyCryptoKeyImpl {\n public:\n  explicit RsaBase(\n      AsymmetricKeyData keyData, CryptoKey::RsaKeyAlgorithm keyAlgorithm, bool extractable)\n      : AsymmetricKeyCryptoKeyImpl(kj::mv(keyData), extractable),\n        keyAlgorithm(kj::mv(keyAlgorithm)) {}\n\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"AsymmetricKey\";\n  }\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(AsymmetricKeyCryptoKeyImpl);\n  }\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {\n    AsymmetricKeyCryptoKeyImpl::jsgGetMemoryInfo(tracker);\n    tracker.trackField(\"keyAlgorithm\", keyAlgorithm);\n  }\n\n protected:\n  CryptoKey::RsaKeyAlgorithm keyAlgorithm;\n\n private:\n  SubtleCrypto::JsonWebKey exportJwk() const override final {\n    auto rsa = JSG_REQUIRE_NONNULL(Rsa::tryGetRsa(getEvpPkey()), DOMDataError,\n        \"No RSA data backing key\", tryDescribeOpensslErrors());\n    return rsa.toJwk(getTypeEnum(), jwkHashAlgorithmName());\n  }\n\n  jsg::JsArrayBuffer exportRaw(jsg::Lock& js) const override final {\n    JSG_FAIL_REQUIRE(\n        DOMInvalidAccessError, \"Cannot export \\\"\", getAlgorithmName(), \"\\\" in \\\"raw\\\" format.\");\n  }\n\n  CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail(jsg::Lock& js) const override {\n    return KJ_ASSERT_NONNULL(Rsa::tryGetRsa(getEvpPkey())).getAsymmetricKeyDetail(js);\n  }\n\n  virtual kj::String jwkHashAlgorithmName() const = 0;\n};\n\nclass RsassaPkcs1V15Key final: public RsaBase {\n public:\n  explicit RsassaPkcs1V15Key(\n      AsymmetricKeyData keyData, CryptoKey::RsaKeyAlgorithm keyAlgorithm, bool extractable)\n      : RsaBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable) {}\n\n  CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {\n    return keyAlgorithm.clone(js);\n  }\n  kj::StringPtr getAlgorithmName() const override {\n    return \"RSASSA-PKCS1-v1_5\";\n  }\n\n  kj::StringPtr chooseHash(\n      const kj::Maybe<kj::OneOf<kj::String, SubtleCrypto::HashAlgorithm>>& callTimeHash)\n      const override {\n    // RSASSA-PKCS1-v1_5 attaches the hash to the key, ignoring whatever is specified at call time.\n    return KJ_REQUIRE_NONNULL(keyAlgorithm.hash).name;\n  }\n\n private:\n  kj::String jwkHashAlgorithmName() const override {\n    const auto& hashName = KJ_REQUIRE_NONNULL(keyAlgorithm.hash).name;\n    JSG_REQUIRE(hashName.startsWith(\"SHA\"), DOMNotSupportedError,\n        \"JWK export not supported for hash algorithm \\\"\", hashName, \"\\\".\");\n    return kj::str(\"RS\", hashName.slice(4, hashName.size()));\n  }\n};\n\nclass RsaPssKey final: public RsaBase {\n public:\n  explicit RsaPssKey(\n      AsymmetricKeyData keyData, CryptoKey::RsaKeyAlgorithm keyAlgorithm, bool extractable)\n      : RsaBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable) {}\n\n  CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {\n    return keyAlgorithm.clone(js);\n  }\n  kj::StringPtr getAlgorithmName() const override {\n    return keyAlgorithm.name;\n  }\n\n  kj::StringPtr chooseHash(\n      const kj::Maybe<kj::OneOf<kj::String, SubtleCrypto::HashAlgorithm>>& callTimeHash)\n      const override {\n    // RSA-PSS attaches the hash to the key, ignoring whatever is specified at call time.\n    return KJ_REQUIRE_NONNULL(keyAlgorithm.hash).name;\n  }\n\n  void addSalt(EVP_PKEY_CTX* pctx, const SubtleCrypto::SignAlgorithm& algorithm) const override {\n    auto salt = JSG_REQUIRE_NONNULL(algorithm.saltLength, TypeError,\n        \"Failed to provide salt for RSA-PSS key operation which requires a salt\");\n    JSG_REQUIRE(salt >= 0, DOMDataError, \"SaltLength for RSA-PSS must be non-negative (provided \",\n        salt, \").\");\n    OSSLCALL(EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING));\n    OSSLCALL(EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, salt));\n  }\n\n private:\n  kj::String jwkHashAlgorithmName() const override {\n    const auto& hashName = KJ_REQUIRE_NONNULL(keyAlgorithm.hash).name;\n    JSG_REQUIRE(hashName.startsWith(\"SHA\"), DOMNotSupportedError,\n        \"JWK export not supported for hash algorithm \\\"\", hashName, \"\\\".\");\n    return kj::str(\"PS\", hashName.slice(4, hashName.size()));\n  }\n};\n\nclass RsaOaepKey final: public RsaBase {\n  using InitFunction = decltype(EVP_PKEY_encrypt_init);\n  using EncryptDecryptFunction = decltype(EVP_PKEY_encrypt);\n\n public:\n  explicit RsaOaepKey(\n      AsymmetricKeyData keyData, CryptoKey::RsaKeyAlgorithm keyAlgorithm, bool extractable)\n      : RsaBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable) {}\n\n  CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {\n    return keyAlgorithm.clone(js);\n  }\n  kj::StringPtr getAlgorithmName() const override {\n    return keyAlgorithm.name;\n  }\n\n  kj::StringPtr chooseHash(\n      const kj::Maybe<kj::OneOf<kj::String, SubtleCrypto::HashAlgorithm>>& callTimeHash)\n      const override {\n    // RSA-OAEP is for encryption/decryption, not signing, but this method is called by the\n    // parent class when performing sign() or verify().\n    JSG_FAIL_REQUIRE(DOMNotSupportedError,\n        \"The sign and verify operations are not implemented for \\\"\", keyAlgorithm.name, \"\\\".\");\n  }\n\n  jsg::JsArrayBuffer encrypt(jsg::Lock& js,\n      SubtleCrypto::EncryptAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> plainText) const override {\n    JSG_REQUIRE(getTypeEnum() == KeyType::PUBLIC, DOMInvalidAccessError,\n        \"Encryption/key wrapping only works with public keys, not \\\"\", getType(), \"\\\".\");\n    return commonEncryptDecrypt(\n        js, kj::mv(algorithm), plainText, EVP_PKEY_encrypt_init, EVP_PKEY_encrypt);\n  }\n\n  jsg::JsArrayBuffer decrypt(jsg::Lock& js,\n      SubtleCrypto::EncryptAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> cipherText) const override {\n    JSG_REQUIRE(getTypeEnum() == KeyType::PRIVATE, DOMInvalidAccessError,\n        \"Decryption/key unwrapping only works with private keys, not \\\"\", getType(), \"\\\".\");\n    return commonEncryptDecrypt(\n        js, kj::mv(algorithm), cipherText, EVP_PKEY_decrypt_init, EVP_PKEY_decrypt);\n  }\n\n private:\n  jsg::JsArrayBuffer commonEncryptDecrypt(jsg::Lock& js,\n      SubtleCrypto::EncryptAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> data,\n      InitFunction init,\n      EncryptDecryptFunction encryptDecrypt) const {\n    auto pkey = getEvpPkey();\n    auto digest = lookupDigestAlgorithm(KJ_REQUIRE_NONNULL(keyAlgorithm.hash).name).second;\n    auto ctx = OSSL_NEW(EVP_PKEY_CTX, pkey, nullptr);\n    JSG_REQUIRE(1 == init(ctx.get()), DOMOperationError, \"RSA-OAEP failed to initialize\",\n        tryDescribeOpensslErrors());\n    return KJ_ASSERT_NONNULL(Rsa::tryGetRsa(pkey))\n        .cipher(js, ctx, kj::mv(algorithm), data, encryptDecrypt, digest);\n  }\n\n  kj::String jwkHashAlgorithmName() const override {\n    const auto& hashName = KJ_REQUIRE_NONNULL(keyAlgorithm.hash).name;\n    JSG_REQUIRE(hashName.startsWith(\"SHA\"), DOMNotSupportedError,\n        \"JWK export not supported for hash algorithm \\\"\", hashName, \"\\\".\");\n    if (hashName == \"SHA-1\") {\n      return kj::str(\"RSA-OAEP\");\n    }\n    return kj::str(\"RSA-OAEP-\", hashName.slice(4, hashName.size()));\n  }\n};\n\nclass RsaRawKey final: public RsaBase {\n public:\n  explicit RsaRawKey(\n      AsymmetricKeyData keyData, CryptoKey::RsaKeyAlgorithm keyAlgorithm, bool extractable)\n      : RsaBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable) {}\n\n  jsg::JsArrayBuffer sign(jsg::Lock& js,\n      SubtleCrypto::SignAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> data) const override {\n    auto rsa = JSG_REQUIRE_NONNULL(Rsa::tryGetRsa(getEvpPkey()), DOMDataError, \"Missing RSA key\");\n    return rsa.sign(js, data);\n  }\n\n  bool verify(jsg::Lock& js,\n      SubtleCrypto::SignAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> signature,\n      kj::ArrayPtr<const kj::byte> data) const override {\n    KJ_UNIMPLEMENTED(\"RawRsa Verification currently unsupported\");\n  }\n\n  CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {\n    return keyAlgorithm.clone(js);\n  }\n\n  kj::StringPtr getAlgorithmName() const override {\n    return keyAlgorithm.name;\n  }\n\n  kj::StringPtr chooseHash(\n      const kj::Maybe<kj::OneOf<kj::String, SubtleCrypto::HashAlgorithm>>& callTimeHash)\n      const override {\n    KJ_UNIMPLEMENTED(\"this should not be called since we overrode sign() and verify()\");\n  }\n\n private:\n  kj::String jwkHashAlgorithmName() const override {\n    const auto& hashName = KJ_REQUIRE_NONNULL(keyAlgorithm.hash).name;\n    JSG_REQUIRE(hashName.startsWith(\"SHA\"), DOMNotSupportedError,\n        \"JWK export not supported for hash algorithm \\\"\", hashName, \"\\\".\");\n    return kj::str(\"RS\", hashName.slice(4, hashName.size()));\n  }\n};\n\nCryptoKeyPair generateRsaPair(jsg::Lock& js,\n    kj::StringPtr normalizedName,\n    kj::Own<EVP_PKEY> privateEvpPKey,\n    kj::Own<EVP_PKEY> publicEvpPKey,\n    CryptoKey::RsaKeyAlgorithm&& keyAlgorithm,\n    bool privateKeyExtractable,\n    CryptoKeyUsageSet usages) {\n  auto privateKeyAlgorithm = keyAlgorithm.clone(js);\n\n  AsymmetricKeyData publicKeyData{\n    .evpPkey = kj::mv(publicEvpPKey),\n    .keyType = KeyType::PUBLIC,\n    .usages = usages & CryptoKeyUsageSet::publicKeyMask(),\n  };\n  AsymmetricKeyData privateKeyData{\n    .evpPkey = kj::mv(privateEvpPKey),\n    .keyType = KeyType::PRIVATE,\n    .usages = usages & CryptoKeyUsageSet::privateKeyMask(),\n  };\n\n  static constexpr auto createPair = [](jsg::Lock& js, kj::Own<CryptoKey::Impl> publicKey,\n                                         kj::Own<CryptoKey::Impl> privateKey) {\n    return CryptoKeyPair{.publicKey = js.alloc<CryptoKey>(kj::mv(publicKey)),\n      .privateKey = js.alloc<CryptoKey>(kj::mv(privateKey))};\n  };\n\n  if (normalizedName == \"RSASSA-PKCS1-v1_5\") {\n    return createPair(js,\n        kj::heap<RsassaPkcs1V15Key>(kj::mv(publicKeyData), kj::mv(keyAlgorithm), true),\n        kj::heap<RsassaPkcs1V15Key>(\n            kj::mv(privateKeyData), kj::mv(privateKeyAlgorithm), privateKeyExtractable));\n  } else if (normalizedName == \"RSA-PSS\") {\n    return createPair(js, kj::heap<RsaPssKey>(kj::mv(publicKeyData), kj::mv(keyAlgorithm), true),\n        kj::heap<RsaPssKey>(\n            kj::mv(privateKeyData), kj::mv(privateKeyAlgorithm), privateKeyExtractable));\n  } else if (normalizedName == \"RSA-OAEP\") {\n    return createPair(js, kj::heap<RsaOaepKey>(kj::mv(publicKeyData), kj::mv(keyAlgorithm), true),\n        kj::heap<RsaOaepKey>(\n            kj::mv(privateKeyData), kj::mv(privateKeyAlgorithm), privateKeyExtractable));\n  }\n  JSG_FAIL_REQUIRE(DOMNotSupportedError, \"Unimplemented RSA generation \\\"\", normalizedName, \"\\\".\");\n}\n\nkj::Own<EVP_PKEY> rsaJwkReader(SubtleCrypto::JsonWebKey&& keyDataJwk) {\n  auto rsaKey = OSSL_NEW(RSA);\n\n  auto modulus = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.n), DOMDataError,\n      \"Invalid RSA key in JSON Web Key; missing or invalid Modulus \"\n      \"parameter (\\\"n\\\").\");\n  auto publicExponent = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.e), DOMDataError,\n      \"Invalid RSA key in JSON Web Key; missing or invalid \"\n      \"Exponent parameter (\\\"e\\\").\");\n\n  auto nBignum = toBignumOwned(modulus);\n  auto eBignum = toBignumOwned(publicExponent);\n  OSSLCALL(RSA_set0_key(rsaKey.get(), nBignum.get(), eBignum.get(), nullptr));\n  nBignum.release();\n  eBignum.release();\n\n  if (keyDataJwk.d != kj::none) {\n    // This is a private key.\n\n    auto privateExponent = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.d), DOMDataError,\n        \"Invalid RSA key in JSON Web Key; missing or invalid \"\n        \"Private Exponent parameter (\\\"d\\\").\");\n\n    auto dBignum = toBignumOwned(privateExponent);\n    OSSLCALL(RSA_set0_key(rsaKey.get(), nullptr, nullptr, dBignum.get()));\n    dBignum.release();\n\n    auto presence = (keyDataJwk.p != kj::none) + (keyDataJwk.q != kj::none) +\n        (keyDataJwk.dp != kj::none) + (keyDataJwk.dq != kj::none) + (keyDataJwk.qi != kj::none);\n\n    if (presence == 5) {\n      auto firstPrimeFactor = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.p), DOMDataError,\n          \"Invalid RSA key in JSON Web Key; invalid First Prime \"\n          \"Factor parameter (\\\"p\\\").\");\n      auto secondPrimeFactor = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.q), DOMDataError,\n          \"Invalid RSA key in JSON Web Key; invalid Second Prime \"\n          \"Factor parameter (\\\"q\\\").\");\n      auto firstFactorCrtExponent = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.dp), DOMDataError,\n          \"Invalid RSA key in JSON Web Key; invalid First Factor \"\n          \"CRT Exponent parameter (\\\"dp\\\").\");\n      auto secondFactorCrtExponent = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.dq), DOMDataError,\n          \"Invalid RSA key in JSON Web Key; invalid Second Factor \"\n          \"CRT Exponent parameter (\\\"dq\\\").\");\n      auto firstCrtCoefficient = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.qi), DOMDataError,\n          \"Invalid RSA key in JSON Web Key; invalid First CRT \"\n          \"Coefficient parameter (\\\"qi\\\").\");\n\n      auto pBn = toBignumOwned(firstPrimeFactor);\n      auto qBn = toBignumOwned(secondPrimeFactor);\n      auto dpBn = toBignumOwned(firstFactorCrtExponent);\n      auto dqBn = toBignumOwned(secondFactorCrtExponent);\n      auto qiBn = toBignumOwned(firstCrtCoefficient);\n\n      OSSLCALL(RSA_set0_factors(rsaKey.get(), pBn.get(), qBn.get()));\n      pBn.release();\n      qBn.release();\n      OSSLCALL(RSA_set0_crt_params(rsaKey.get(), dpBn.get(), dqBn.get(), qiBn.get()));\n      dpBn.release();\n      dqBn.release();\n      qiBn.release();\n    } else {\n      JSG_REQUIRE(presence == 0, DOMDataError,\n          \"Invalid RSA private key in JSON Web Key; if one Prime \"\n          \"Factor or CRT Exponent/Coefficient parameter is present, then they must all be \"\n          \"present (\\\"p\\\", \\\"q\\\", \\\"dp\\\", \\\"dq\\\", \\\"qi\\\").\");\n    }\n  }\n\n  auto evpPkey = OSSL_NEW(EVP_PKEY);\n  OSSLCALL(EVP_PKEY_set1_RSA(evpPkey.get(), rsaKey.get()));\n  return evpPkey;\n}\n}  // namespace\n\nkj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> CryptoKey::Impl::generateRsa(jsg::Lock& js,\n    kj::StringPtr normalizedName,\n    SubtleCrypto::GenerateKeyAlgorithm&& algorithm,\n    bool extractable,\n    kj::ArrayPtr<const kj::String> keyUsages) {\n\n  KJ_ASSERT(normalizedName == \"RSASSA-PKCS1-v1_5\" || normalizedName == \"RSA-PSS\" ||\n          normalizedName == \"RSA-OAEP\",\n      \"generateRsa called on non-RSA cryptoKey\", normalizedName);\n\n  auto publicExponent = JSG_REQUIRE_NONNULL(kj::mv(algorithm.publicExponent), TypeError,\n      \"Missing field \\\"publicExponent\\\" in \\\"algorithm\\\".\")\n                            .getHandle(js);\n  kj::StringPtr hash = api::getAlgorithmName(\n      JSG_REQUIRE_NONNULL(algorithm.hash, TypeError, \"Missing field \\\"hash\\\" in \\\"algorithm\\\".\"));\n  int modulusLength = JSG_REQUIRE_NONNULL(\n      algorithm.modulusLength, TypeError, \"Missing field \\\"modulusLength\\\" in \\\"algorithm\\\".\");\n  JSG_REQUIRE(modulusLength > 0, DOMOperationError,\n      \"modulusLength must be greater than zero \"\n      \"(requested \",\n      modulusLength, \").\");\n  auto [normalizedHashName, hashEvpMd] = lookupDigestAlgorithm(hash);\n\n  CryptoKeyUsageSet validUsages = (normalizedName == \"RSA-OAEP\")\n      ? (CryptoKeyUsageSet::encrypt() | CryptoKeyUsageSet::decrypt() |\n            CryptoKeyUsageSet::wrapKey() | CryptoKeyUsageSet::unwrapKey())\n      : (CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify());\n  auto usages = CryptoKeyUsageSet::validate(\n      normalizedName, CryptoKeyUsageSet::Context::generate, keyUsages, validUsages);\n\n  Rsa::validateRsaParams(js, modulusLength, publicExponent.asArrayPtr());\n  // BoringSSL silently uses (modulusLength & ~127) for the key size, i.e. it rounds down to the\n  // closest multiple of 128 bits. This can easily cause confusion when non-standard key sizes are\n  // requested.\n  // The `modulusLength` field of the resulting CryptoKey will be incorrect when the compat flag\n  // is disabled and the key size is rounded down, but since it is not currently used this is\n  // acceptable.\n  JSG_REQUIRE(!(FeatureFlags::get(js).getStrictCrypto() && (modulusLength & 127)),\n      DOMOperationError, \"Can't generate key: RSA key size is required to be a multiple of 128\");\n\n  auto bnExponent = JSG_REQUIRE_NONNULL(toBignum(publicExponent.asArrayPtr()),\n      InternalDOMOperationError, \"Error setting up RSA keygen.\");\n\n  auto rsaPrivateKey = OSSL_NEW(RSA);\n  OSSLCALL(RSA_generate_key_ex(rsaPrivateKey, modulusLength, bnExponent.get(), nullptr));\n  auto privateEvpPKey = OSSL_NEW(EVP_PKEY);\n  OSSLCALL(EVP_PKEY_set1_RSA(privateEvpPKey.get(), rsaPrivateKey.get()));\n  kj::Own<RSA> rsaPublicKey = OSSLCALL_OWN(RSA, RSAPublicKey_dup(rsaPrivateKey.get()),\n      InternalDOMOperationError, \"Error finalizing RSA keygen\", internalDescribeOpensslErrors());\n  auto publicEvpPKey = OSSL_NEW(EVP_PKEY);\n  OSSLCALL(EVP_PKEY_set1_RSA(publicEvpPKey.get(), rsaPublicKey));\n\n  // Create a JsUint8Array copy of the public exponent for the key algorithm struct.\n  auto expCopy = jsg::JsUint8Array::create(js, publicExponent.asArrayPtr());\n  auto keyAlgorithm = CryptoKey::RsaKeyAlgorithm{.name = normalizedName,\n    .modulusLength = static_cast<uint16_t>(modulusLength),\n    .publicExponent = jsg::JsBufferSource(expCopy).addRef(js),\n    .hash = KeyAlgorithm{normalizedHashName}};\n\n  return generateRsaPair(js, normalizedName, kj::mv(privateEvpPKey), kj::mv(publicEvpPKey),\n      kj::mv(keyAlgorithm), extractable, usages);\n}\n\nkj::Own<CryptoKey::Impl> CryptoKey::Impl::importRsa(jsg::Lock& js,\n    kj::StringPtr normalizedName,\n    kj::StringPtr format,\n    SubtleCrypto::ImportKeyData keyData,\n    SubtleCrypto::ImportKeyAlgorithm&& algorithm,\n    bool extractable,\n    kj::ArrayPtr<const kj::String> keyUsages) {\n  kj::StringPtr hash = api::getAlgorithmName(\n      JSG_REQUIRE_NONNULL(algorithm.hash, TypeError, \"Missing field \\\"hash\\\" in \\\"algorithm\\\".\"));\n\n  CryptoKeyUsageSet allowedUsages = (normalizedName == \"RSA-OAEP\")\n      ? (CryptoKeyUsageSet::encrypt() | CryptoKeyUsageSet::decrypt() |\n            CryptoKeyUsageSet::wrapKey() | CryptoKeyUsageSet::unwrapKey())\n      : (CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify());\n\n  auto [normalizedHashName, hashEvpMd] = lookupDigestAlgorithm(hash);\n\n  auto importedKey = importAsymmetricForWebCrypto(js, kj::mv(format), kj::mv(keyData),\n      normalizedName, extractable, keyUsages,\n      // Verbose lambda capture needed because: https://bugs.llvm.org/show_bug.cgi?id=35984\n      [hashEvpMd = hashEvpMd, &algorithm](\n          SubtleCrypto::JsonWebKey keyDataJwk) -> kj::Own<EVP_PKEY> {\n    JSG_REQUIRE(keyDataJwk.kty == \"RSA\", DOMDataError,\n        \"RSASSA-PKCS1-v1_5 \\\"jwk\\\" key import requires a JSON Web Key with Key Type parameter \"\n        \"\\\"kty\\\" (\\\"\",\n        keyDataJwk.kty, \"\\\") equal to \\\"RSA\\\".\");\n\n    KJ_IF_SOME(alg, keyDataJwk.alg) {\n      // If this JWK specifies an algorithm, make sure it jives with the hash we were passed via\n      // importKey().\n      static const std::map<kj::StringPtr, const EVP_MD*> knownRsaAlgorithms{\n        {\"RS1\", EVP_sha1()},\n        {\"RS256\", EVP_sha256()},\n        {\"RS384\", EVP_sha384()},\n        {\"RS512\", EVP_sha512()},\n        {\"PS1\", EVP_sha1()},\n        {\"PS256\", EVP_sha256()},\n        {\"PS384\", EVP_sha384()},\n        {\"PS512\", EVP_sha512()},\n        {\"RSA-OAEP\", EVP_sha1()},\n        {\"RSA-OAEP-256\", EVP_sha256()},\n        {\"RSA-OAEP-384\", EVP_sha384()},\n        {\"RSA-OAEP-512\", EVP_sha512()},\n      };\n      const auto tryFindAlgorithm = [&](kj::StringPtr alg) -> kj::Maybe<const EVP_MD*> {\n        if (algorithm.name == \"RSASSA-PKCS1-v1_5\" || algorithm.name == \"RSA-PSS\" ||\n            algorithm.name == \"RSA-OAEP\") {\n          auto ret = knownRsaAlgorithms.find(alg);\n          if (ret != knownRsaAlgorithms.end()) {\n            return ret->second;\n          }\n          return kj::none;\n        } else {\n          JSG_FAIL_REQUIRE(\n              DOMNotSupportedError, \"Unrecognized RSA variant \\\"\", algorithm.name, \"\\\".\");\n        }\n      };\n      auto jwkHash = JSG_REQUIRE_NONNULL(tryFindAlgorithm(alg), DOMNotSupportedError,\n          \"Unrecognized or unimplemented algorithm \\\"\", alg,\n          \"\\\" listed in JSON Web Key Algorithm \"\n          \"parameter.\");\n\n      JSG_REQUIRE(jwkHash == hashEvpMd, DOMDataError,\n          \"JSON Web Key Algorithm parameter \\\"alg\\\" (\\\"\", alg,\n          \"\\\") does not match requested hash \"\n          \"algorithm \\\"\",\n          alg, \"\\\".\");\n    }\n\n    return rsaJwkReader(kj::mv(keyDataJwk));\n  },\n      allowedUsages);\n\n  // get0 avoids adding a refcount...\n  auto rsa = JSG_REQUIRE_NONNULL(Rsa::tryGetRsa(importedKey.evpPkey.get()), DOMDataError,\n      \"Input was not an RSA key\", tryDescribeOpensslErrors());\n\n  // TODO(conform): We're supposed to check if PKCS8/SPKI input specified a hash and, if so,\n  //   compare it against the hash requested in `algorithm`. But, I can't find the OpenSSL\n  //   interface to extract the hash from the ASN.1. Oh well...\n\n  size_t modulusLength = rsa.getModulusBits();\n  auto publicExponent = rsa.getPublicExponent(js);\n\n  // Validate modulus and exponent, reject imported RSA keys that may be unsafe.\n  Rsa::validateRsaParams(js, modulusLength, publicExponent.asArrayPtr(), true);\n\n  auto keyAlgorithm = CryptoKey::RsaKeyAlgorithm{.name = normalizedName,\n    .modulusLength = static_cast<uint16_t>(modulusLength),\n    .publicExponent = jsg::JsBufferSource(publicExponent).addRef(js),\n    .hash = KeyAlgorithm{normalizedHashName}};\n  if (normalizedName == \"RSASSA-PKCS1-v1_5\") {\n    return kj::heap<RsassaPkcs1V15Key>(kj::mv(importedKey), kj::mv(keyAlgorithm), extractable);\n  } else if (normalizedName == \"RSA-PSS\") {\n    return kj::heap<RsaPssKey>(kj::mv(importedKey), kj::mv(keyAlgorithm), extractable);\n  } else if (normalizedName == \"RSA-OAEP\") {\n    return kj::heap<RsaOaepKey>(kj::mv(importedKey), kj::mv(keyAlgorithm), extractable);\n  } else {\n    JSG_FAIL_REQUIRE(DOMNotSupportedError, \"Unrecognized RSA variant \\\"\", normalizedName, \"\\\".\");\n  }\n}\n\nkj::Own<CryptoKey::Impl> CryptoKey::Impl::importRsaRaw(jsg::Lock& js,\n    kj::StringPtr normalizedName,\n    kj::StringPtr format,\n    SubtleCrypto::ImportKeyData keyData,\n    SubtleCrypto::ImportKeyAlgorithm&& algorithm,\n    bool extractable,\n    kj::ArrayPtr<const kj::String> keyUsages) {\n  // Note that in this context raw refers to the RSA-RAW algorithm, not to keys represented by raw\n  // data. Importing raw keys is currently not supported for this algorithm.\n  CryptoKeyUsageSet allowedUsages = CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify();\n  auto importedKey = importAsymmetricForWebCrypto(js, kj::mv(format), kj::mv(keyData),\n      normalizedName, extractable, keyUsages,\n      // Verbose lambda capture needed because: https://bugs.llvm.org/show_bug.cgi?id=35984\n      [](SubtleCrypto::JsonWebKey keyDataJwk) -> kj::Own<EVP_PKEY> {\n    JSG_REQUIRE(keyDataJwk.kty == \"RSA\", DOMDataError,\n        \"RSA-RAW \\\"jwk\\\" key import requires a JSON Web Key with Key Type parameter \"\n        \"\\\"kty\\\" (\\\"\",\n        keyDataJwk.kty, \"\\\") equal to \\\"RSA\\\".\");\n\n    KJ_IF_SOME(alg, keyDataJwk.alg) {\n      // If this JWK specifies an algorithm, make sure it jives with the hash we were passed via\n      // importKey().\n      static const std::map<kj::StringPtr, const EVP_MD*> rsaAlgorithms{\n        {\"RS1\", EVP_sha1()},\n        {\"RS256\", EVP_sha256()},\n        {\"RS384\", EVP_sha384()},\n        {\"RS512\", EVP_sha512()},\n      };\n      auto jwkHash = rsaAlgorithms.find(alg);\n      JSG_REQUIRE(jwkHash != rsaAlgorithms.end(), DOMNotSupportedError,\n          \"Unrecognized or unimplemented algorithm \\\"\", alg,\n          \"\\\" listed in JSON Web Key Algorithm parameter.\");\n    }\n    return rsaJwkReader(kj::mv(keyDataJwk));\n  }, allowedUsages);\n\n  JSG_REQUIRE(importedKey.keyType == KeyType::PRIVATE, DOMDataError,\n      \"RSA-RAW only supports private keys but requested \\\"\", toStringPtr(importedKey.keyType),\n      \"\\\".\");\n\n  // get0 avoids adding a refcount...\n  auto rsa = JSG_REQUIRE_NONNULL(Rsa::tryGetRsa(importedKey.evpPkey.get()), DOMDataError,\n      \"Input was not an RSA key\", tryDescribeOpensslErrors());\n\n  size_t modulusLength = rsa.getModulusBits();\n  auto publicExponent = KJ_REQUIRE_NONNULL(bignumToArray(js, *rsa.getE()));\n\n  // Validate modulus and exponent, reject imported RSA keys that may be unsafe.\n  Rsa::validateRsaParams(js, modulusLength, publicExponent.asArrayPtr(), true);\n\n  auto keyAlgorithm = CryptoKey::RsaKeyAlgorithm{.name = \"RSA-RAW\"_kj,\n    .modulusLength = static_cast<uint16_t>(modulusLength),\n    .publicExponent = jsg::JsBufferSource(publicExponent).addRef(js)};\n\n  return kj::heap<RsaRawKey>(kj::mv(importedKey), kj::mv(keyAlgorithm), extractable);\n}\n\nkj::Own<CryptoKey::Impl> fromRsaKey(jsg::Lock& js, kj::Own<EVP_PKEY> key) {\n  auto rsa =\n      JSG_REQUIRE_NONNULL(Rsa::tryGetRsa(key.get()), DOMDataError, \"Input was not an RSA key\");\n\n  auto publicExponent = KJ_REQUIRE_NONNULL(bignumToArray(js, *rsa.getE()));\n  return kj::heap<RsassaPkcs1V15Key>(AsymmetricKeyData{.evpPkey = kj::mv(key),\n                                       .keyType = KeyType::PUBLIC,\n                                       .usages = CryptoKeyUsageSet::decrypt() |\n                                           CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify()},\n      CryptoKey::RsaKeyAlgorithm{\n        .name = \"RSA\"_kj,\n        .publicExponent = jsg::JsBufferSource(publicExponent).addRef(js),\n      },\n      true);\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/rsa.h",
    "content": "#pragma once\n\n#include \"crypto.h\"\n#include \"keys.h\"\n\n#include <workerd/jsg/jsvalue.h>\n\n#include <openssl/base.h>\n#include <openssl/evp.h>\n\n#include <kj/common.h>\n\nnamespace workerd::api {\n\nclass Rsa final {\n public:\n  static kj::Maybe<Rsa> tryGetRsa(const EVP_PKEY* key);\n  Rsa(RSA* rsa);\n\n  size_t getModulusBits() const;\n  size_t getModulusSize() const;\n\n  inline const BIGNUM* getN() const {\n    return n;\n  }\n  inline const BIGNUM* getE() const {\n    return e;\n  }\n  inline const BIGNUM* getD() const {\n    return d;\n  }\n\n  jsg::JsUint8Array getPublicExponent(jsg::Lock& js) KJ_WARN_UNUSED_RESULT;\n\n  CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail(jsg::Lock& js) const KJ_WARN_UNUSED_RESULT;\n\n  jsg::JsArrayBuffer sign(\n      jsg::Lock& js, kj::ArrayPtr<const kj::byte> data) const KJ_WARN_UNUSED_RESULT;\n\n  static kj::Maybe<AsymmetricKeyData> fromJwk(\n      jsg::Lock& js, KeyType keyType, const SubtleCrypto::JsonWebKey& jwk) KJ_WARN_UNUSED_RESULT;\n\n  SubtleCrypto::JsonWebKey toJwk(\n      KeyType keytype, kj::Maybe<kj::String> maybeHashAlgorithm) const KJ_WARN_UNUSED_RESULT;\n\n  struct CipherOptions {\n    const EVP_CIPHER* cipher;\n    kj::ArrayPtr<const kj::byte> passphrase;\n  };\n\n  kj::String toPem(jsg::Lock& js,\n      KeyEncoding encoding,\n      KeyType keyType,\n      kj::Maybe<CipherOptions> options = kj::none) const KJ_WARN_UNUSED_RESULT;\n\n  jsg::JsArrayBuffer toDer(jsg::Lock& js,\n      KeyEncoding encoding,\n      KeyType keyType,\n      kj::Maybe<CipherOptions> options = kj::none) const KJ_WARN_UNUSED_RESULT;\n\n  using EncryptDecryptFunction = decltype(EVP_PKEY_encrypt);\n  jsg::JsArrayBuffer cipher(jsg::Lock& js,\n      EVP_PKEY_CTX* ctx,\n      SubtleCrypto::EncryptAlgorithm&& algorithm,\n      kj::ArrayPtr<const kj::byte> data,\n      EncryptDecryptFunction encryptDecrypt,\n      const EVP_MD* cipher) const KJ_WARN_UNUSED_RESULT;\n\n  // The W3C standard itself doesn't describe any parameter validation but the conformance tests\n  // do test \"bad\" exponents, likely because everyone uses OpenSSL that suffers from poor behavior\n  // with these bad exponents (e.g. if an exponent < 3 or 65535 generates an infinite loop, a\n  // library might be expected to handle such cases on its own, no?).\n  static void validateRsaParams(jsg::Lock& js,\n      size_t modulusLength,\n      kj::ArrayPtr<kj::byte> publicExponent,\n      bool isImport = false);\n\n  static bool isRSAPrivateKey(kj::ArrayPtr<const kj::byte> keyData) KJ_WARN_UNUSED_RESULT;\n\n private:\n  RSA* rsa;\n  const BIGNUM* n = nullptr;\n  const BIGNUM* e = nullptr;\n  const BIGNUM* d = nullptr;\n};\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/scrypt.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"impl.h\"\n#include \"kdf.h\"\n\n#include <ncrypto.h>\n\nnamespace workerd::api {\n\nkj::Maybe<jsg::JsArrayBuffer> scrypt(jsg::Lock& js,\n    size_t length,\n    uint32_t N,\n    uint32_t r,\n    uint32_t p,\n    uint32_t maxmem,\n    kj::ArrayPtr<const kj::byte> pass,\n    kj::ArrayPtr<const kj::byte> salt) {\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n  auto buf = jsg::JsArrayBuffer::create(js, length);\n  auto ncBuf = ToNcryptoBuffer(buf.asArrayPtr());\n  if (ncrypto::scryptInto(ToNcryptoBuffer(pass.asChars()), ToNcryptoBuffer(salt), N, r, p, maxmem,\n          length, &ncBuf)) {\n    return kj::mv(buf);\n  }\n  return kj::none;\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/spkac.c++",
    "content": "#include \"spkac.h\"\n\n#include \"impl.h\"\n\n#include <workerd/io/io-context.h>\n#include <workerd/jsg/jsg.h>\n\n#include <ncrypto.h>\n\nnamespace workerd::api {\n\nbool verifySpkac(kj::ArrayPtr<const kj::byte> input) {\n  // So, this is fun. SPKAC uses MD5 as the digest algorithm. This is a problem because\n  // using MD5 for signature verification is not allowed in FIPS mode, which means that\n  // although we have a working implementation here, the result of this call is always\n  // going to be false even if the input signature is correct. So this is a bit of a dead\n  // end that isn't going to be super useful. Fortunately tho the exportPublicKey and\n  // exportChallenge functions both work correctly and are useful. Unfortunately, this\n  // likely means users would need to implement their own verification, which sucks.\n  //\n  // Alternatively we could choose to implement our own version of the validation that\n  // bypasses BoringSSL's FIPS configuration. For now tho, this does end up matching\n  // Node.js' behavior when FIPS is enabled so I guess that's something.\n  KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n    ioContext.logWarningOnce(\n        \"The verifySpkac function is currently of limited value in workers because \"\n        \"the SPKAC signature verification uses MD5, which is not supported in FIPS mode. \"\n        \"All workers run in FIPS mode. Accordingly, this method will currently always \"\n        \"return false even if the SPKAC signature is valid. This is a known limitation.\");\n  }\n\n  // Works around a bug in ncrypto...\n  auto pos = std::string_view(input.asChars().begin(), input.size()).find_last_not_of(\" \\n\\r\\t\");\n  if (pos == std::string_view::npos) {\n    return false;\n  }\n\n  return ncrypto::VerifySpkac(ToNcryptoBuffer(input.asChars()));\n}\n\nkj::Maybe<jsg::JsUint8Array> exportPublicKey(jsg::Lock& js, kj::ArrayPtr<const kj::byte> input) {\n  // Works around a bug in ncrypto...\n  auto pos = std::string_view(input.asChars().begin(), input.size()).find_last_not_of(\" \\n\\r\\t\");\n  if (pos == std::string_view::npos) {\n    return kj::none;\n  }\n\n  if (auto bio = ncrypto::ExportPublicKey(ToNcryptoBuffer(input.asChars()))) {\n    BUF_MEM* bptr = bio;\n    auto buf = jsg::JsUint8Array::create(js, bptr->length);\n    auto aptr = kj::arrayPtr(bptr->data, bptr->length);\n    buf.asArrayPtr<char>().copyFrom(aptr);\n    return buf;\n  }\n  return kj::none;\n}\n\nkj::Maybe<jsg::JsUint8Array> exportChallenge(jsg::Lock& js, kj::ArrayPtr<const kj::byte> input) {\n\n  // Works around a bug in ncrypto...\n  auto pos = std::string_view(input.asChars().begin(), input.size()).find_last_not_of(\" \\n\\r\\t\");\n  if (pos == std::string_view::npos) {\n    return kj::none;\n  }\n\n  if (auto dp = ncrypto::ExportChallenge(ToNcryptoBuffer(input.asChars()))) {\n    auto src = kj::arrayPtr(dp.get<kj::byte>(), dp.size());\n    return jsg::JsUint8Array::create(js, src);\n  }\n  return kj::none;\n}\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/spkac.h",
    "content": "#pragma once\n\n#include <workerd/jsg/jsg.h>\n\n#include <kj/common.h>\n\nnamespace workerd::api {\n\nbool verifySpkac(kj::ArrayPtr<const kj::byte> input);\n\nkj::Maybe<jsg::JsUint8Array> exportPublicKey(jsg::Lock& js, kj::ArrayPtr<const kj::byte> input);\n\nkj::Maybe<jsg::JsUint8Array> exportChallenge(jsg::Lock& js, kj::ArrayPtr<const kj::byte> input);\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/x509.c++",
    "content": "#include \"x509.h\"\n\n#include \"impl.h\"\n\n#include <openssl/bio.h>\n#include <openssl/pem.h>\n#include <openssl/x509v3.h>\n\nKJ_DECLARE_NON_POLYMORPHIC(STACK_OF(ASN1_OBJECT));\n\nnamespace workerd::api {\n\nnamespace {\n\nstatic constexpr int kX509NameFlagsMultiline = ASN1_STRFLGS_ESC_2253 | ASN1_STRFLGS_ESC_CTRL |\n    ASN1_STRFLGS_UTF8_CONVERT | XN_FLAG_SEP_MULTILINE | XN_FLAG_FN_SN;\n\nstatic constexpr int kX509NameFlagsRFC2253WithinUtf8JSON =\n    XN_FLAG_RFC2253 & ~ASN1_STRFLGS_ESC_MSB & ~ASN1_STRFLGS_ESC_CTRL;\n\nkj::Maybe<kj::Own<BIO>> newBio() {\n  auto ptr = BIO_new(BIO_s_mem());\n  if (ptr == nullptr) return kj::none;\n  return kj::disposeWith<BIO_free_all>(ptr);\n}\n\nkj::Maybe<kj::Own<BIO>> loadBio(kj::ArrayPtr<const kj::byte> raw) {\n  static constexpr int32_t kMaxSize = kj::maxValue;\n  if (raw.size() > kMaxSize) return kj::none;\n  KJ_IF_SOME(bio, newBio()) {\n    int written = BIO_write(bio.get(), raw.begin(), raw.size());\n    if (written != raw.size()) return kj::none;\n    return kj::mv(bio);\n  }\n  return kj::none;\n}\n\nint NoPasswordCallback(char* buf, int size, int rwflag, void* u) {\n  return 0;\n}\n\nkj::String toString(BIO* bio) {\n  BUF_MEM* mem;\n  BIO_get_mem_ptr(bio, &mem);\n  auto result = kj::heapArray<char>(mem->length + 1);\n  kj::ArrayPtr<char> data(mem->data, mem->length);\n  result.first(data.size()).copyFrom(data);\n  result[result.size() - 1] = '\\0';  // NUL-terminate.\n  return kj::String(kj::mv(result));\n}\n\nbool isSafeAltName(const char* name, size_t length, bool utf8) {\n  for (size_t i = 0; i < length; i++) {\n    char c = name[i];\n    switch (c) {\n      case '\"':\n      case '\\\\':\n        // These mess with encoding rules.\n        // Fall through.\n      case ',':\n        // Commas make it impossible to split the list of subject alternative\n        // names unambiguously, which is why we have to escape.\n        // Fall through.\n      case '\\'':\n        // Single quotes are unlikely to appear in any legitimate values, but they\n        // could be used to make a value look like it was escaped (i.e., enclosed\n        // in single/double quotes).\n        return false;\n      default:\n        if (utf8) {\n          // In UTF8 strings, we require escaping for any ASCII control character,\n          // but NOT for non-ASCII characters. Note that all bytes of any code\n          // point that consists of more than a single byte have their MSB set.\n          if (static_cast<unsigned char>(c) < ' ' || c == '\\x7f') {\n            return false;\n          }\n        } else {\n          // Check if the char is a control character or non-ASCII character. Note\n          // that char may or may not be a signed type. Regardless, non-ASCII\n          // values will always be outside of this range.\n          if (c < ' ' || c > '~') {\n            return false;\n          }\n        }\n    }\n  }\n  return true;\n}\n\nvoid printAltName(BIO* out, const char* name, size_t length, bool utf8, const char* safe_prefix) {\n  if (isSafeAltName(name, length, utf8)) {\n    // For backward-compatibility, append \"safe\" names without any\n    // modifications.\n    if (safe_prefix != nullptr) {\n      BIO_printf(out, \"%s:\", safe_prefix);\n    }\n    BIO_write(out, name, length);\n  } else {\n    // If a name is not \"safe\", we cannot embed it without special\n    // encoding. This does not usually happen, but we don't want to hide\n    // it from the user either. We use JSON compatible escaping here.\n    BIO_write(out, \"\\\"\", 1);\n    if (safe_prefix != nullptr) {\n      BIO_printf(out, \"%s:\", safe_prefix);\n    }\n    for (size_t j = 0; j < length; j++) {\n      char c = static_cast<char>(name[j]);\n      if (c == '\\\\') {\n        BIO_write(out, \"\\\\\\\\\", 2);\n      } else if (c == '\"') {\n        BIO_write(out, \"\\\\\\\"\", 2);\n      } else if ((c >= ' ' && c != ',' && c <= '~') || (utf8 && (c & 0x80))) {\n        // Note that the above condition explicitly excludes commas, which means\n        // that those are encoded as Unicode escape sequences in the \"else\"\n        // block. That is not strictly necessary, and Node.js itself would parse\n        // it correctly either way. We only do this to account for third-party\n        // code that might be splitting the string at commas (as Node.js itself\n        // used to do).\n        BIO_write(out, &c, 1);\n      } else {\n        // Control character or non-ASCII character. We treat everything as\n        // Latin-1, which corresponds to the first 255 Unicode code points.\n        const char hex[] = \"0123456789abcdef\";\n        char u[] = {'\\\\', 'u', '0', '0', hex[(c & 0xf0) >> 4], hex[c & 0x0f]};\n        BIO_write(out, u, sizeof(u));\n      }\n    }\n    BIO_write(out, \"\\\"\", 1);\n  }\n}\n\nvoid printLatin1AltName(BIO* out, const ASN1_IA5STRING* name, const char* safe_prefix = nullptr) {\n  printAltName(out, reinterpret_cast<const char*>(name->data), name->length, false, safe_prefix);\n}\n\nvoid printUtf8AltName(BIO* out, const ASN1_UTF8STRING* name, const char* safe_prefix = nullptr) {\n  printAltName(out, reinterpret_cast<const char*>(name->data), name->length, true, safe_prefix);\n}\n\nbool printGeneralName(BIO* out, const GENERAL_NAME* gen) {\n  if (gen->type == GEN_DNS) {\n    ASN1_IA5STRING* name = gen->d.dNSName;\n    BIO_write(out, \"DNS:\", 4);\n    // Note that the preferred name syntax (see RFCs 5280 and 1034) with\n    // wildcards is a subset of what we consider \"safe\", so spec-compliant DNS\n    // names will never need to be escaped.\n    printLatin1AltName(out, name);\n  } else if (gen->type == GEN_EMAIL) {\n    ASN1_IA5STRING* name = gen->d.rfc822Name;\n    BIO_write(out, \"email:\", 6);\n    printLatin1AltName(out, name);\n  } else if (gen->type == GEN_URI) {\n    ASN1_IA5STRING* name = gen->d.uniformResourceIdentifier;\n    BIO_write(out, \"URI:\", 4);\n    // The set of \"safe\" names was designed to include just about any URI,\n    // with a few exceptions, most notably URIs that contains commas (see\n    // RFC 2396). In other words, most legitimate URIs will not require\n    // escaping.\n    printLatin1AltName(out, name);\n  } else if (gen->type == GEN_DIRNAME) {\n    // Earlier versions of Node.js used X509_NAME_oneline to print the X509_NAME\n    // object. The format was non standard and should be avoided. The use of\n    // X509_NAME_oneline is discouraged by OpenSSL but was required for backward\n    // compatibility. Conveniently, X509_NAME_oneline produced ASCII and the\n    // output was unlikely to contains commas or other characters that would\n    // require escaping. However, it SHOULD NOT produce ASCII output since an\n    // RFC5280 AttributeValue may be a UTF8String.\n    // Newer versions of Node.js have since switched to X509_NAME_print_ex to\n    // produce a better format at the cost of backward compatibility. The new\n    // format may contain Unicode characters and it is likely to contain commas,\n    // which require escaping. Fortunately, the recently safeguarded function\n    // PrintAltName handles all of that safely.\n    BIO_printf(out, \"DirName:\");\n    auto tmp = KJ_ASSERT_NONNULL(newBio());\n    if (X509_NAME_print_ex(tmp.get(), gen->d.dirn, 0, kX509NameFlagsRFC2253WithinUtf8JSON) < 0) {\n      return false;\n    }\n    char* oline = nullptr;\n    long n_bytes = BIO_get_mem_data(tmp.get(), &oline);  // NOLINT(runtime/int)\n    KJ_REQUIRE(n_bytes >= 0);\n    if (n_bytes > 0) {\n      KJ_REQUIRE(oline != nullptr);\n    }\n\n    printAltName(out, oline, static_cast<size_t>(n_bytes), true, nullptr);\n  } else if (gen->type == GEN_IPADD) {\n    BIO_printf(out, \"IP Address:\");\n    const ASN1_OCTET_STRING* ip = gen->d.ip;\n    const unsigned char* b = ip->data;\n    if (ip->length == 4) {\n      BIO_printf(out, \"%d.%d.%d.%d\", b[0], b[1], b[2], b[3]);\n    } else if (ip->length == 16) {\n      for (unsigned int j = 0; j < 8; j++) {\n        uint16_t pair = (b[2 * j] << 8) | b[2 * j + 1];\n        BIO_printf(out, (j == 0) ? \"%X\" : \":%X\", pair);\n      }\n    } else {\n      BIO_printf(out, \"<invalid>\");\n    }\n  } else if (gen->type == GEN_RID) {\n    // Unlike OpenSSL's default implementation, never print the OID as text and\n    // instead always print its numeric representation.\n    char oline[256] = {0};\n    OBJ_obj2txt(oline, sizeof(oline), gen->d.rid, true);\n    BIO_printf(out, \"Registered ID:%s\", oline);\n  } else if (gen->type == GEN_OTHERNAME) {\n    // The format that is used here is based on OpenSSL's implementation of\n    // GENERAL_NAME_print (as of OpenSSL 3.0.1). Earlier versions of Node.js\n    // instead produced the same format as i2v_GENERAL_NAME, which was somewhat\n    // awkward, especially when passed to translatePeerCertificate.\n    bool unicode = true;\n    const char* prefix = nullptr;\n    // OpenSSL 1.1.1 does not support othername in GENERAL_NAME_print and may\n    // not define these NIDs.\n#if OPENSSL_VERSION_MAJOR >= 3\n    int nid = OBJ_obj2nid(gen->d.otherName->type_id);\n    switch (nid) {\n      case NID_id_on_SmtpUTF8Mailbox:\n        prefix = \"SmtpUTF8Mailbox\";\n        break;\n      case NID_XmppAddr:\n        prefix = \"XmppAddr\";\n        break;\n      case NID_SRVName:\n        prefix = \"SRVName\";\n        unicode = false;\n        break;\n      case NID_ms_upn:\n        prefix = \"UPN\";\n        break;\n      case NID_NAIRealm:\n        prefix = \"NAIRealm\";\n        break;\n    }\n#endif  // OPENSSL_VERSION_MAJOR >= 3\n    int val_type = gen->d.otherName->value->type;\n    if (prefix == nullptr || (unicode && val_type != V_ASN1_UTF8STRING) ||\n        (!unicode && val_type != V_ASN1_IA5STRING)) {\n      BIO_printf(out, \"othername:<unsupported>\");\n    } else {\n      BIO_printf(out, \"othername:\");\n      if (unicode) {\n        printUtf8AltName(out, gen->d.otherName->value->value.utf8string, prefix);\n      } else {\n        printLatin1AltName(out, gen->d.otherName->value->value.ia5string, prefix);\n      }\n    }\n  } else if (gen->type == GEN_X400) {\n    // TODO(tniessen): this is what OpenSSL does, implement properly instead\n    BIO_printf(out, \"X400Name:<unsupported>\");\n  } else if (gen->type == GEN_EDIPARTY) {\n    // TODO(tniessen): this is what OpenSSL does, implement properly instead\n    BIO_printf(out, \"EdiPartyName:<unsupported>\");\n  } else {\n    // This is safe because X509V3_EXT_d2i would have returned nullptr in this\n    // case already.\n    KJ_UNREACHABLE;\n  }\n\n  return true;\n}\n\nbool safeX509SubjectAltNamePrint(BIO* out, X509_EXTENSION* ext) {\n  KJ_REQUIRE(OBJ_obj2nid(X509_EXTENSION_get_object(ext)) == NID_subject_alt_name);\n\n  GENERAL_NAMES* names = static_cast<GENERAL_NAMES*>(X509V3_EXT_d2i(ext));\n  if (names == nullptr) return false;\n\n  bool ok = true;\n\n  for (int i = 0; i < sk_GENERAL_NAME_num(names); i++) {\n    GENERAL_NAME* gen = sk_GENERAL_NAME_value(names, i);\n\n    if (i != 0) BIO_write(out, \", \", 2);\n\n    if (!(ok = printGeneralName(out, gen))) {\n      break;\n    }\n  }\n  sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);\n\n  return ok;\n}\n\nbool safeX509InfoAccessPrint(BIO* out, X509_EXTENSION* ext) {\n  KJ_REQUIRE(OBJ_obj2nid(X509_EXTENSION_get_object(ext)) == NID_info_access);\n\n  AUTHORITY_INFO_ACCESS* descs = static_cast<AUTHORITY_INFO_ACCESS*>(X509V3_EXT_d2i(ext));\n  if (descs == nullptr) return false;\n\n  bool ok = true;\n\n  for (int i = 0; i < sk_ACCESS_DESCRIPTION_num(descs); i++) {\n    ACCESS_DESCRIPTION* desc = sk_ACCESS_DESCRIPTION_value(descs, i);\n\n    if (i != 0) BIO_write(out, \"\\n\", 1);\n\n    char objtmp[80] = {0};\n    i2t_ASN1_OBJECT(objtmp, sizeof(objtmp), desc->method);\n    BIO_printf(out, \"%s - \", objtmp);\n    if (!(ok = printGeneralName(out, desc->location))) {\n      break;\n    }\n  }\n  sk_ACCESS_DESCRIPTION_pop_free(descs, ACCESS_DESCRIPTION_free);\n\n#if OPENSSL_VERSION_MAJOR < 3\n  BIO_write(out, \"\\n\", 1);\n#endif\n\n  return ok;\n}\n\nvoid addFingerprintDigest(\n    const unsigned char* md, unsigned int md_size, char fingerprint[3 * EVP_MAX_MD_SIZE]) {\n  unsigned int i;\n  const char hex[] = \"0123456789ABCDEF\";\n\n  for (i = 0; i < md_size; i++) {\n    fingerprint[3 * i] = hex[(md[i] & 0xf0) >> 4];\n    fingerprint[(3 * i) + 1] = hex[(md[i] & 0x0f)];\n    fingerprint[(3 * i) + 2] = ':';\n  }\n  fingerprint[(3 * (md_size - 1)) + 2] = '\\0';\n}\n\nkj::Maybe<kj::String> getFingerprintDigest(const EVP_MD* method, X509* cert) {\n  unsigned char md[EVP_MAX_MD_SIZE]{};\n  unsigned int md_size;\n  auto fingerprint = kj::heapArray<char>(EVP_MD_size(method) * 3);\n  if (X509_digest(cert, method, md, &md_size)) {\n    addFingerprintDigest(md, md_size, fingerprint.begin());\n    return kj::String(kj::mv(fingerprint));\n  }\n  return kj::none;\n}\n\nint optionsToFlags(jsg::Optional<X509Certificate::CheckOptions>& options) {\n  X509Certificate::CheckOptions opts = kj::mv(options).orDefault({});\n  int flags = 0;\n  if (!opts.wildcards.orDefault(true)) {\n    flags |= X509_CHECK_FLAG_NO_WILDCARDS;\n  }\n  if (!opts.partialWildcards.orDefault(true)) {\n    flags |= X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS;\n  }\n  if (opts.multiLabelWildcards.orDefault(false)) {\n    flags |= X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS;\n  }\n  if (opts.singleLabelSubdomains.orDefault(false)) {\n    flags |= X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS;\n  }\n  KJ_IF_SOME(subject, opts.subject) {\n    if (subject == \"default\"_kj) {\n      // nothing to do\n    } else if (subject == \"always\"_kj) {\n      flags |= X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT;\n    } else if (subject == \"never\"_kj) {\n      flags |= X509_CHECK_FLAG_NEVER_CHECK_SUBJECT;\n    } else {\n      JSG_FAIL_REQUIRE(Error, \"Invalid subject option\");\n    }\n  }\n  return flags;\n}\n\nkj::Maybe<kj::Own<EVP_PKEY>> getInnerPublicKey(X509* cert) {\n  EVP_PKEY* pkey = X509_get_pubkey(cert);\n  if (pkey == nullptr) {\n    return kj::none;\n  }\n  return kj::disposeWith<EVP_PKEY_free>(pkey);\n}\n\nkj::String getModulusString(BIO* bio, const BIGNUM* n) {\n  BIO_reset(bio);\n  BN_print(bio, n);\n  return toString(bio);\n}\nkj::String getExponentString(BIO* bio, const BIGNUM* e) {\n  BIO_reset(bio);\n  uint64_t exponent_word = static_cast<uint64_t>(BN_get_word(e));\n  BIO_printf(bio, \"0x%\" PRIx64, exponent_word);\n  return toString(bio);\n}\n\njsg::JsUint8Array getRsaPubKey(jsg::Lock& js, RSA* rsa) {\n  int size = i2d_RSA_PUBKEY(rsa, nullptr);\n  KJ_ASSERT(size >= 0);\n\n  auto buf = jsg::JsUint8Array::create(js, size);\n  auto data = buf.asArrayPtr().begin();\n  KJ_ASSERT(i2d_RSA_PUBKEY(rsa, &data) >= 0);\n\n  return buf;\n}\n\nkj::Maybe<int32_t> getECGroupBits(const EC_GROUP* group) {\n  if (group == nullptr) return kj::none;\n\n  int32_t bits = EC_GROUP_order_bits(group);\n  if (bits <= 0) return kj::none;\n\n  return bits;\n}\n\nkj::Maybe<jsg::JsUint8Array> eCPointToBuffer(\n    jsg::Lock& js, const EC_GROUP* group, const EC_POINT* point, point_conversion_form_t form) {\n  size_t len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr);\n  if (len == 0) {\n    return kj::none;\n  }\n\n  auto buffer = jsg::JsUint8Array::create(js, len);\n\n  len = EC_POINT_point2oct(group, point, form, buffer.asArrayPtr().begin(), buffer.size(), nullptr);\n  if (len == 0) {\n    return kj::none;\n  }\n\n  return buffer;\n}\n\ntemplate <const char* (*nid2string)(int nid)>\nkj::Maybe<kj::String> getCurveName(const int nid) {\n  const char* name = nid2string(nid);\n  if (name == nullptr) {\n    return kj::none;\n  }\n  return kj::str(name);\n}\n\nkj::Maybe<jsg::JsUint8Array> getECPubKey(jsg::Lock& js, const EC_GROUP* group, EC_KEY* ec) {\n  const EC_POINT* pubkey = EC_KEY_get0_public_key(ec);\n  if (pubkey == nullptr) return kj::none;\n\n  return eCPointToBuffer(js, group, pubkey, EC_KEY_get_conv_form(ec));\n}\n\ntemplate <X509_NAME* get_name(const X509*)>\nkj::Maybe<jsg::JsObject> getX509NameObject(jsg::Lock& js, X509* cert) {\n  auto obj = js.obj();\n  X509_NAME* name = get_name(cert);\n  KJ_ASSERT(name != nullptr);\n\n  int cnt = X509_NAME_entry_count(name);\n  KJ_ASSERT(cnt >= 0);\n\n  for (int i = 0; i < cnt; i++) {\n    X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, i);\n    KJ_ASSERT(entry != nullptr);\n\n    // We intentionally ignore the value of X509_NAME_ENTRY_set because the\n    // representation as an object does not allow grouping entries into sets\n    // anyway, and multi-value RDNs are rare, i.e., the vast majority of\n    // Relative Distinguished Names contains a single type-value pair only.\n    const ASN1_OBJECT* type = X509_NAME_ENTRY_get_object(entry);\n    ASN1_STRING* value = X509_NAME_ENTRY_get_data(entry);\n\n    // If OpenSSL knows the type, use the short name of the type as the key, and\n    // the numeric representation of the type's OID otherwise.\n    int type_nid = OBJ_obj2nid(type);\n    char type_buf[80] = {0};\n    const char* type_str;\n    if (type_nid != NID_undef) {\n      type_str = OBJ_nid2sn(type_nid);\n      KJ_ASSERT(type_str != nullptr);\n    } else {\n      OBJ_obj2txt(type_buf, sizeof(type_buf), type, true);\n      type_str = type_buf;\n    }\n\n    auto name = js.str(kj::StringPtr(type_str));\n\n    // The previous implementation used X509_NAME_print_ex, which escapes some\n    // characters in the value. The old implementation did not decode/unescape\n    // values correctly though, leading to ambiguous and incorrect\n    // representations. The new implementation only converts to Unicode and does\n    // not escape anything.\n    unsigned char* value_str;\n    int value_str_size = ASN1_STRING_to_UTF8(&value_str, value);\n    if (value_str_size < 0) return kj::none;\n    auto v8_value = js.str(kj::StringPtr(reinterpret_cast<char*>(value_str), value_str_size));\n    OPENSSL_free(value_str);\n\n    // For backward compatibility, we only create arrays if multiple values\n    // exist for the same key. That is not great but there is not much we can\n    // change here without breaking things. Note that this creates nested data\n    // structures, yet still does not allow representing Distinguished Names\n    // accurately.\n    if (obj.has(js, name)) {\n      auto existing = obj.get(js, name);\n      KJ_IF_SOME(a, existing.tryCast<jsg::JsArray>()) {\n        a.add(js, v8_value);\n      } else {\n        obj.set(js, name, js.arr(existing, v8_value));\n      }\n    } else {\n      obj.set(js, name, v8_value);\n    }\n  }\n\n  return obj;\n}\n\nstruct StackOfXASN1Disposer: public kj::Disposer {\n  void disposeImpl(void* p) const override {\n    auto ptr = static_cast<STACK_OF(ASN1_OBJECT)*>(p);\n    sk_ASN1_OBJECT_pop_free(ptr, ASN1_OBJECT_free);\n  }\n};\nconstexpr StackOfXASN1Disposer stackOfXASN1Disposer;\n}  // namespace\n\nkj::Maybe<jsg::Ref<X509Certificate>> X509Certificate::parse(\n    jsg::Lock& js, kj::Array<const kj::byte> raw) {\n  ClearErrorOnReturn clearErrorOnReturn;\n  KJ_IF_SOME(bio, loadBio(raw)) {\n    auto ptr = PEM_read_bio_X509_AUX(bio.get(), nullptr, NoPasswordCallback, nullptr);\n    if (ptr == nullptr) {\n      MarkPopErrorOnReturn mark_here;\n      auto data = raw.begin();\n      ptr = d2i_X509(nullptr, &data, raw.size());\n      if (ptr == nullptr) {\n        // Invalid certificate data is a user input error, not an internal error.\n        // Return kj::none and let the JS layer throw a user-facing error.\n        return kj::none;\n      }\n    }\n    return js.alloc<X509Certificate>(ptr);\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::String> X509Certificate::getSubject() {\n  ClearErrorOnReturn clearErrorOnReturn;\n  KJ_IF_SOME(bio, newBio()) {\n    if (X509_NAME_print_ex(\n            bio.get(), X509_get_subject_name(cert_.get()), 0, kX509NameFlagsMultiline) > 0) {\n      return toString(bio.get());\n    }\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::String> X509Certificate::getSubjectAltName() {\n  ClearErrorOnReturn clearErrorOnReturn;\n  KJ_IF_SOME(bio, newBio()) {\n    int index = X509_get_ext_by_NID(cert_.get(), NID_subject_alt_name, -1);\n    if (index < 0) return kj::none;\n\n    X509_EXTENSION* ext = X509_get_ext(cert_.get(), index);\n    KJ_ASSERT(ext != nullptr);\n\n    if (!safeX509SubjectAltNamePrint(bio, ext)) {\n      return kj::none;\n    }\n\n    return toString(bio.get());\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::String> X509Certificate::getInfoAccess() {\n  ClearErrorOnReturn clearErrorOnReturn;\n  KJ_IF_SOME(bio, newBio()) {\n    int index = X509_get_ext_by_NID(cert_.get(), NID_info_access, -1);\n    if (index < 0) return kj::none;\n\n    X509_EXTENSION* ext = X509_get_ext(cert_.get(), index);\n    KJ_REQUIRE(ext != nullptr);\n\n    if (!safeX509InfoAccessPrint(bio, ext)) {\n      return kj::none;\n    }\n\n    return toString(bio.get());\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::String> X509Certificate::getIssuer() {\n  ClearErrorOnReturn clearErrorOnReturn;\n  KJ_IF_SOME(bio, newBio()) {\n    if (X509_NAME_print_ex(\n            bio.get(), X509_get_issuer_name(cert_.get()), 0, kX509NameFlagsMultiline) > 0) {\n      return toString(bio.get());\n    }\n  }\n  return kj::none;\n}\n\nkj::Maybe<jsg::Ref<X509Certificate>> X509Certificate::getIssuerCert() {\n  ClearErrorOnReturn clearErrorOnReturn;\n  return issuerCert_.map([](jsg::Ref<X509Certificate>& cert) mutable -> jsg::Ref<X509Certificate> {\n    return cert.addRef();\n  });\n}\n\nkj::Maybe<kj::String> X509Certificate::getValidFrom() {\n  ClearErrorOnReturn clearErrorOnReturn;\n  KJ_IF_SOME(bio, newBio()) {\n    ASN1_TIME_print(bio.get(), X509_get0_notBefore(cert_.get()));\n    return toString(bio.get());\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::String> X509Certificate::getValidTo() {\n  ClearErrorOnReturn clearErrorOnReturn;\n  KJ_IF_SOME(bio, newBio()) {\n    ASN1_TIME_print(bio.get(), X509_get0_notAfter(cert_.get()));\n    return toString(bio.get());\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::Array<kj::String>> X509Certificate::getKeyUsage() {\n  ClearErrorOnReturn clearErrorOnReturn;\n  auto ptr = static_cast<STACK_OF(ASN1_OBJECT)*>(\n      X509_get_ext_d2i(cert_.get(), NID_ext_key_usage, nullptr, nullptr));\n  if (ptr == nullptr) return kj::none;\n  auto eku = kj::Own<STACK_OF(ASN1_OBJECT)>(ptr, stackOfXASN1Disposer);\n  const int count = sk_ASN1_OBJECT_num(eku.get());\n  kj::Vector<kj::String> ext_key_usage(count);\n  char buf[256]{};\n\n  int j = 0;\n  for (int i = 0; i < count; i++) {\n    if (OBJ_obj2txt(buf, sizeof(buf), sk_ASN1_OBJECT_value(eku.get(), i), 1) >= 0) {\n      ext_key_usage[j++] = kj::str(buf);\n    }\n  }\n\n  return ext_key_usage.releaseAsArray();\n}\n\nkj::Maybe<kj::Array<const char>> X509Certificate::getSerialNumber() {\n  ClearErrorOnReturn clearErrorOnReturn;\n  if (ASN1_INTEGER* serial_number = X509_get_serialNumber(cert_.get())) {\n    BIGNUM* bn = ASN1_INTEGER_to_BN(serial_number, nullptr);\n    if (bn != nullptr) {\n      KJ_DEFER(BN_clear_free(bn));\n      char* data = BN_bn2hex(bn);\n      return kj::arrayPtr<const char>(data, strlen(data))\n          .attach(kj::defer([data, len = strlen(data)] { OPENSSL_clear_free(data, len); }));\n    }\n  }\n\n  return kj::none;\n}\n\njsg::JsUint8Array X509Certificate::getRaw(jsg::Lock& js) {\n  ClearErrorOnReturn clearErrorOnReturn;\n  int size = i2d_X509(cert_.get(), nullptr);\n  auto buf = jsg::JsUint8Array::create(js, size);\n  auto data = buf.asArrayPtr().begin();\n  KJ_REQUIRE(i2d_X509(cert_.get(), &data) >= 0);\n  return buf;\n}\n\nkj::Maybe<jsg::Ref<CryptoKey>> X509Certificate::getPublicKey(jsg::Lock& js) {\n  ClearErrorOnReturn clear_error_on_return;\n  auto ptr = X509_get_pubkey(cert_.get());\n  if (ptr == nullptr) return kj::none;\n  auto pkey = kj::disposeWith<EVP_PKEY_free>(ptr);\n  return js.alloc<CryptoKey>(CryptoKey::Impl::from(js, kj::mv(pkey)));\n}\n\nkj::Maybe<kj::String> X509Certificate::getPem() {\n  ClearErrorOnReturn clearErrorOnReturn;\n  KJ_IF_SOME(bio, newBio()) {\n    if (PEM_write_bio_X509(bio.get(), cert_.get())) {\n      return toString(bio.get());\n    }\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::String> X509Certificate::getFingerprint() {\n  ClearErrorOnReturn clearErrorOnReturn;\n  return getFingerprintDigest(EVP_sha1(), cert_.get());\n}\n\nkj::Maybe<kj::String> X509Certificate::getFingerprint256() {\n  ClearErrorOnReturn clearErrorOnReturn;\n  return getFingerprintDigest(EVP_sha256(), cert_.get());\n}\n\nkj::Maybe<kj::String> X509Certificate::getFingerprint512() {\n  ClearErrorOnReturn clearErrorOnReturn;\n  return getFingerprintDigest(EVP_sha512(), cert_.get());\n}\n\nbool X509Certificate::getIsCA() {\n  ClearErrorOnReturn clearErrorOnReturn;\n  return X509_check_ca(cert_.get()) == 1;\n}\n\nkj::Maybe<kj::String> X509Certificate::checkHost(\n    kj::String name, jsg::Optional<CheckOptions> options) {\n  ClearErrorOnReturn clearErrorOnReturn;\n  char* peername = nullptr;\n  switch (\n      X509_check_host(cert_.get(), name.begin(), name.size(), optionsToFlags(options), &peername)) {\n    case 1: {  // Match!\n      if (peername != nullptr) {\n        KJ_DEFER(OPENSSL_free(peername));\n        return kj::str(peername);\n      }\n      return kj::mv(name);\n    }\n    case 0:             // No Match!\n      return kj::none;  // No return value is set\n    case -2:            // Error!\n      JSG_FAIL_REQUIRE(Error, \"Invalid name\");\n    default:  // Error!\n      JSG_FAIL_REQUIRE(Error, \"Operation failed\");\n  }\n\n  KJ_UNREACHABLE;\n}\n\nkj::Maybe<kj::String> X509Certificate::checkEmail(\n    kj::String email, jsg::Optional<CheckOptions> options) {\n  ClearErrorOnReturn clearErrorOnReturn;\n  switch (X509_check_email(cert_.get(), email.begin(), email.size(), optionsToFlags(options))) {\n    case 1:  // Match!\n      return kj::mv(email);\n    case 0:             // No Match!\n      return kj::none;  // No return value is set\n    case -2:            // Error!\n      JSG_FAIL_REQUIRE(Error, \"Invalid name\");\n    default:  // Error!\n      JSG_FAIL_REQUIRE(Error, \"Operation failed\");\n  }\n\n  KJ_UNREACHABLE;\n}\n\nkj::Maybe<kj::String> X509Certificate::checkIp(kj::String ip, jsg::Optional<CheckOptions> options) {\n  ClearErrorOnReturn clearErrorOnReturn;\n  switch (X509_check_ip_asc(cert_.get(), ip.begin(), optionsToFlags(options))) {\n    case 1:  // Match!\n      return kj::mv(ip);\n    case 0:             // No Match!\n      return kj::none;  // No return value is set\n    case -2:            // Error!\n      JSG_FAIL_REQUIRE(Error, \"Invalid IP\");\n    default:  // Error!\n      JSG_FAIL_REQUIRE(Error, \"Operation failed\");\n  }\n\n  KJ_UNREACHABLE;\n}\n\nbool X509Certificate::checkIssued(jsg::Ref<X509Certificate> other) {\n  ClearErrorOnReturn clearErrorOnReturn;\n  return X509_check_issued(other->cert_.get(), cert_.get()) == X509_V_OK;\n}\n\nbool X509Certificate::checkPrivateKey(jsg::Ref<CryptoKey> privateKey) {\n  JSG_REQUIRE(privateKey->getType() == \"private\"_kj, Error, \"Invalid key type\");\n  return privateKey->verifyX509Private(cert_.get());\n}\n\nbool X509Certificate::verify(jsg::Ref<CryptoKey> publicKey) {\n  JSG_REQUIRE(publicKey->getType() == \"public\"_kj, Error, \"Invalid key type\");\n  return publicKey->verifyX509Public(cert_.get());\n}\n\njsg::JsObject X509Certificate::toLegacyObject(jsg::Lock& js) {\n  ClearErrorOnReturn clearErrorOnReturn;\n  auto obj = js.obj();\n  KJ_IF_SOME(subject, getX509NameObject<X509_get_subject_name>(js, cert_.get())) {\n    obj.set(js, \"subject\", subject);\n  }\n  KJ_IF_SOME(issuer, getX509NameObject<X509_get_issuer_name>(js, cert_.get())) {\n    obj.set(js, \"issuer\", issuer);\n  }\n  obj.set(js, \"subjectAltName\", js.str(getSubjectAltName().orDefault(kj::String())));\n  obj.set(js, \"infoAccess\", js.str(getInfoAccess().orDefault(kj::String())));\n  obj.set(js, \"ca\", js.boolean(getIsCA()));\n\n  KJ_IF_SOME(key, getInnerPublicKey(cert_.get())) {\n    auto bio = KJ_ASSERT_NONNULL(newBio());\n    switch (EVP_PKEY_id(key.get())) {\n      case EVP_PKEY_RSA: {\n        RSA* rsa = EVP_PKEY_get0_RSA(key.get());\n        KJ_ASSERT(rsa != nullptr);\n        obj.set(js, \"modulus\", js.str(getModulusString(bio.get(), RSA_get0_n(rsa))));\n        obj.set(js, \"bits\", js.num(RSA_bits(rsa)));\n        obj.set(js, \"exponent\", js.str(getExponentString(bio.get(), RSA_get0_e(rsa))));\n        obj.set(js, \"pubkey\", getRsaPubKey(js, rsa));\n        break;\n      }\n      case EVP_PKEY_EC: {\n        EC_KEY* ec = EVP_PKEY_get0_EC_KEY(key.get());\n        const EC_GROUP* group = EC_KEY_get0_group(ec);\n        KJ_ASSERT(ec != nullptr);\n        KJ_ASSERT(group != nullptr);\n        KJ_IF_SOME(bits, getECGroupBits(group)) {\n          obj.set(js, \"bits\", js.num(bits));\n        }\n        KJ_IF_SOME(pubkey, getECPubKey(js, group, ec)) {\n          obj.set(js, \"pubkey\", pubkey);\n        }\n\n        const int nid = EC_GROUP_get_curve_name(group);\n        if (nid != 0) {\n          // Curve is well-known, get its OID and NIST nick-name (if it has one).\n\n          KJ_IF_SOME(name, getCurveName<OBJ_nid2sn>(nid)) {\n            obj.set(js, \"asn1Curve\", js.str(name));\n          }\n          KJ_IF_SOME(name, getCurveName<EC_curve_nid2nist>(nid)) {\n            obj.set(js, \"nistCurve\", js.str(name));\n          }\n        } else {\n          // Unnamed curves can be described by their mathematical properties,\n          // but aren't used much (at all?) with X.509/TLS. Support later if needed.\n        }\n        break;\n      }\n    }\n  }\n\n  KJ_IF_SOME(from, getValidFrom()) {\n    obj.set(js, \"valid_from\", js.str(from));\n  }\n  KJ_IF_SOME(to, getValidTo()) {\n    obj.set(js, \"valid_to\", js.str(to));\n  }\n\n  KJ_IF_SOME(fingerprint, getFingerprint()) {\n    obj.set(js, \"fingerprint\", js.str(fingerprint));\n  }\n  KJ_IF_SOME(fingerprint256, getFingerprint256()) {\n    obj.set(js, \"fingerprint256\", js.str(fingerprint256));\n  }\n  KJ_IF_SOME(fingerprint512, getFingerprint512()) {\n    obj.set(js, \"fingerprint512\", js.str(fingerprint512));\n  }\n  KJ_IF_SOME(keyUsage, getKeyUsage()) {\n    obj.set(js, \"ext_key_usage\",\n        js.arr(keyUsage.asPtr(), [](jsg::Lock& js, const kj::String& val) { return js.str(val); }));\n  }\n  KJ_IF_SOME(serialNumber, getSerialNumber()) {\n    obj.set(js, \"serialNumber\", js.str(serialNumber));\n  }\n  obj.set(js, \"raw\", getRaw(js));\n\n  return obj;\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/crypto/x509.h",
    "content": "#pragma once\n\n#include <workerd/jsg/jsg.h>\n\n#include <openssl/x509.h>\n\nnamespace workerd::api {\n\nclass CryptoKey;\n\nclass X509Certificate: public jsg::Object {\n public:\n  X509Certificate(X509* cert): cert_(kj::disposeWith<X509_free>(cert)) {}\n\n  static kj::Maybe<jsg::Ref<X509Certificate>> parse(jsg::Lock& js, kj::Array<const kj::byte> raw);\n\n  kj::Maybe<kj::String> getSubject();\n  kj::Maybe<kj::String> getSubjectAltName();\n  kj::Maybe<kj::String> getInfoAccess();\n  kj::Maybe<kj::String> getIssuer();\n  kj::Maybe<jsg::Ref<X509Certificate>> getIssuerCert();\n  kj::Maybe<kj::String> getValidFrom();\n  kj::Maybe<kj::String> getValidTo();\n  kj::Maybe<kj::Array<kj::String>> getKeyUsage();\n  kj::Maybe<kj::Array<const char>> getSerialNumber();\n  jsg::JsUint8Array getRaw(jsg::Lock& js);\n  kj::Maybe<jsg::Ref<CryptoKey>> getPublicKey(jsg::Lock& js);\n  kj::Maybe<kj::String> getPem();\n  kj::Maybe<kj::String> getFingerprint();\n  kj::Maybe<kj::String> getFingerprint256();\n  kj::Maybe<kj::String> getFingerprint512();\n  bool getIsCA();\n\n  struct CheckOptions {\n    jsg::Optional<kj::String> subject;\n    jsg::Optional<bool> wildcards;\n    jsg::Optional<bool> partialWildcards;\n    jsg::Optional<bool> multiLabelWildcards;\n    jsg::Optional<bool> singleLabelSubdomains;\n    JSG_STRUCT(subject, wildcards, partialWildcards, multiLabelWildcards, singleLabelSubdomains);\n  };\n  kj::Maybe<kj::String> checkHost(kj::String name, jsg::Optional<CheckOptions> options);\n\n  kj::Maybe<kj::String> checkEmail(kj::String email, jsg::Optional<CheckOptions> options);\n\n  kj::Maybe<kj::String> checkIp(kj::String ip, jsg::Optional<CheckOptions> options);\n\n  bool checkIssued(jsg::Ref<X509Certificate> other);\n\n  bool checkPrivateKey(jsg::Ref<CryptoKey> privateKey);\n\n  bool verify(jsg::Ref<CryptoKey> publicKey);\n\n  jsg::JsObject toLegacyObject(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(X509Certificate) {\n    JSG_STATIC_METHOD(parse);\n    JSG_READONLY_PROTOTYPE_PROPERTY(subject, getSubject);\n    JSG_READONLY_PROTOTYPE_PROPERTY(subjectAltName, getSubjectAltName);\n    JSG_READONLY_PROTOTYPE_PROPERTY(infoAccess, getInfoAccess);\n    JSG_READONLY_PROTOTYPE_PROPERTY(issuer, getIssuer);\n    JSG_READONLY_PROTOTYPE_PROPERTY(issuerCert, getIssuerCert);\n    JSG_READONLY_PROTOTYPE_PROPERTY(validFrom, getValidFrom);\n    JSG_READONLY_PROTOTYPE_PROPERTY(validTo, getValidTo);\n    JSG_READONLY_PROTOTYPE_PROPERTY(fingerprint, getFingerprint);\n    JSG_READONLY_PROTOTYPE_PROPERTY(fingerprint256, getFingerprint256);\n    JSG_READONLY_PROTOTYPE_PROPERTY(fingerprint512, getFingerprint512);\n    JSG_READONLY_PROTOTYPE_PROPERTY(keyUsage, getKeyUsage);\n    JSG_READONLY_PROTOTYPE_PROPERTY(serialNumber, getSerialNumber);\n    JSG_READONLY_PROTOTYPE_PROPERTY(pem, getPem);\n    JSG_READONLY_PROTOTYPE_PROPERTY(raw, getRaw);\n    JSG_READONLY_PROTOTYPE_PROPERTY(publicKey, getPublicKey);\n    JSG_READONLY_PROTOTYPE_PROPERTY(isCA, getIsCA);\n    JSG_METHOD(checkHost);\n    JSG_METHOD(checkEmail);\n    JSG_METHOD(checkIp);\n    JSG_METHOD(checkIssued);\n    JSG_METHOD(checkPrivateKey);\n    JSG_METHOD(verify);\n    JSG_METHOD(toLegacyObject);\n  }\n\n private:\n  kj::Own<X509> cert_;\n  kj::Maybe<jsg::Ref<X509Certificate>> issuerCert_;\n};\n\n}  // namespace workerd::api\n\nKJ_DECLARE_NON_POLYMORPHIC(X509);\n\n#define EW_CRYPTO_X509_ISOLATE_TYPES api::X509Certificate, api::X509Certificate::CheckOptions\n"
  },
  {
    "path": "src/workerd/api/data-url-test.c++",
    "content": "#include \"data-url.h\"\n\n#include <workerd/jsg/url.h>\n\n#include <kj/test.h>\n\nnamespace workerd::api {\nnamespace {\n\nKJ_TEST(\"DataUrl Basics\") {\n  auto dataUrl =\n      KJ_ASSERT_NONNULL(DataUrl::tryParse(\"data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==\"_kj));\n  KJ_ASSERT(dataUrl.getMimeType() == MimeType::PLAINTEXT);\n  KJ_ASSERT(dataUrl.getData().asChars() == \"Hello, World!\"_kj);\n}\n\nKJ_TEST(\"DataUrl Leading/Trailing Whitespace\") {\n  auto dataUrl = KJ_ASSERT_NONNULL(\n      DataUrl::tryParse(\"    data: \\t text/plain \\t;base64\\t\\t ,SGVsbG8sIFdvcmxkIQ==    \"_kj));\n  KJ_ASSERT(dataUrl.getMimeType() == MimeType::PLAINTEXT);\n  KJ_ASSERT(dataUrl.getData().asChars() == \"Hello, World!\"_kj);\n}\n\nKJ_TEST(\"DataUrl base64 case-insensitive\") {\n  auto dataUrl = KJ_ASSERT_NONNULL(\n      DataUrl::tryParse(\"    data: \\t text/plain \\t;BasE64\\t\\t ,SGVsbG8sIFdvcmxkIQ==    \"_kj));\n  KJ_ASSERT(dataUrl.getMimeType() == MimeType::PLAINTEXT);\n  KJ_ASSERT(dataUrl.getData().asChars() == \"Hello, World!\"_kj);\n}\n\nKJ_TEST(\"DataUrl no-base64\") {\n  auto dataUrl = KJ_ASSERT_NONNULL(\n      DataUrl::tryParse(\"    data: \\t text/plain \\t;a=b\\t\\t ,SGVsbG8sIFdvcmxkIQ==    \"_kj));\n  KJ_ASSERT(dataUrl.getMimeType() == MimeType::PLAINTEXT);\n\n  auto& val = KJ_REQUIRE_NONNULL(dataUrl.getMimeType().params().find(\"a\"_kj));\n  KJ_ASSERT(val == \"b\"_kj);\n\n  KJ_ASSERT(dataUrl.getData().asChars() == \"SGVsbG8sIFdvcmxkIQ==\"_kj);\n}\n\nKJ_TEST(\"DataUrl default mime type\") {\n  auto dataUrl = KJ_ASSERT_NONNULL(DataUrl::tryParse(\"data:,Hello, World!\"_kj));\n  KJ_ASSERT(dataUrl.getMimeType() == MimeType::PLAINTEXT);\n  KJ_ASSERT(dataUrl.getData().asChars() == \"Hello, World!\"_kj);\n}\n\nKJ_TEST(\"DataUrl default mime type\") {\n  auto dataUrl = KJ_ASSERT_NONNULL(DataUrl::tryParse(\"data:;,Hello, World!\"_kj));\n  KJ_ASSERT(dataUrl.getMimeType() == MimeType::PLAINTEXT);\n  KJ_ASSERT(dataUrl.getData().asChars() == \"Hello, World!\"_kj);\n}\n\nKJ_TEST(\"DataUrl default mime type\") {\n  auto dataUrl = KJ_ASSERT_NONNULL(DataUrl::tryParse(\"data:;charset=UTF-8,Hello, World!\"_kj));\n  KJ_ASSERT(dataUrl.getMimeType() == MimeType::PLAINTEXT);\n  KJ_ASSERT(dataUrl.getData().asChars() == \"Hello, World!\"_kj);\n\n  auto& val = KJ_REQUIRE_NONNULL(dataUrl.getMimeType().params().find(\"charset\"_kj));\n  KJ_ASSERT(val == \"UTF-8\"_kj);\n}\n\nstruct Test {\n  kj::StringPtr input;\n  kj::StringPtr mimeType;\n  kj::Array<const kj::byte> data;\n};\n\nKJ_TEST(\"DataUrl Web Platform Tests\") {\n\n  Test tests[] = {{\n                    \"data://test/,X\"_kj,\n                    \"text/plain;charset=US-ASCII\"_kj,\n                    kj::heapArray<kj::byte>({88}),\n                  },\n    {\n      \"data://test:test/,X\"_kj,\n      nullptr,\n      kj::heapArray<kj::byte>(0),\n    },\n    {\n      \"data:,X\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:\"_kj,\n      nullptr,\n      kj::heapArray<kj::byte>(0),\n    },\n    {\n      \"data:text/html\"_kj,\n      nullptr,\n      kj::heapArray<kj::byte>(0),\n    },\n    {\n      \"data:text/html    ;charset=x   \"_kj,\n      nullptr,\n      kj::heapArray<kj::byte>(0),\n    },\n    {\n      \"data:,\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>(0),\n    },\n    {\n      \"data:,X#X\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:,%FF\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({255}),\n    },\n    {\n      \"data:text/plain,X\"_kj,\n      \"text/plain\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:text/plain ,X\"_kj,\n      \"text/plain\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:text/plain%20,X\"_kj,\n      \"text/plain%20\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:text/plain\\f,X\"_kj,\n      \"text/plain%0c\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:text/plain%0C,X\"_kj,\n      \"text/plain%0c\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:text/plain;,X\"_kj,\n      \"text/plain\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:;x=x;charset=x,X\"_kj,\n      \"text/plain;x=x;charset=x\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:;x=x,X\"_kj,\n      \"text/plain;x=x\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:text/plain;charset=windows-1252,%C2%B1\"_kj,\n      \"text/plain;charset=windows-1252\"_kj,\n      kj::heapArray<kj::byte>({194, 177}),\n    },\n    {\n      \"data:text/plain;Charset=UTF-8,%C2%B1\"_kj,\n      \"text/plain;charset=UTF-8\"_kj,\n      kj::heapArray<kj::byte>({194, 177}),\n    },\n    {\n      \"data:text/plain;charset=windows-1252,áñçə💩\"_kj,\n      \"text/plain;charset=windows-1252\"_kj,\n      kj::heapArray<kj::byte>({195, 161, 195, 177, 195, 167, 201, 153, 240, 159, 146, 169}),\n    },\n    {\n      \"data:text/plain;charset=UTF-8,áñçə💩\"_kj,\n      \"text/plain;charset=UTF-8\"_kj,\n      kj::heapArray<kj::byte>({195, 161, 195, 177, 195, 167, 201, 153, 240, 159, 146, 169}),\n    },\n    {\n      \"data:image/gif,%C2%B1\"_kj,\n      \"image/gif\"_kj,\n      kj::heapArray<kj::byte>({194, 177}),\n    },\n    {\n      \"data:IMAGE/gif,%C2%B1\"_kj,\n      \"image/gif\"_kj,\n      kj::heapArray<kj::byte>({194, 177}),\n    },\n    {\n      \"data:IMAGE/gif;hi=x,%C2%B1\"_kj,\n      \"image/gif;hi=x\"_kj,\n      kj::heapArray<kj::byte>({194, 177}),\n    },\n    {\n      \"data:IMAGE/gif;CHARSET=x,%C2%B1\"_kj,\n      \"image/gif;charset=x\"_kj,\n      kj::heapArray<kj::byte>({194, 177}),\n    },\n    {\n      \"data: ,%FF\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({255}),\n    },\n    {\n      \"data:%20,%FF\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({255}),\n    },\n    {\n      \"data:\\f,%FF\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({255}),\n    },\n    {\n      \"data:%1F,%FF\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({255}),\n    },\n    {\n      \"data:\\u0000,%FF\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({255}),\n    },\n    {\n      \"data:%00,%FF\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({255}),\n    },\n    {\n      \"data:text/html  ,X\"_kj,\n      \"text/html\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:text / html,X\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:†,X\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:†/†,X\"_kj,\n      \"%e2%80%a0/%e2%80%a0\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:X,X\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:image/png,X X\"_kj,\n      \"image/png\"_kj,\n      kj::heapArray<kj::byte>({88, 32, 88}),\n    },\n    {\n      \"data:application/javascript,X X\"_kj,\n      \"application/javascript\"_kj,\n      kj::heapArray<kj::byte>({88, 32, 88}),\n    },\n    {\n      \"data:application/xml,X X\"_kj,\n      \"application/xml\"_kj,\n      kj::heapArray<kj::byte>({88, 32, 88}),\n    },\n    {\n      \"data:text/javascript,X X\"_kj,\n      \"text/javascript\"_kj,\n      kj::heapArray<kj::byte>({88, 32, 88}),\n    },\n    {\n      \"data:text/plain,X X\"_kj,\n      \"text/plain\"_kj,\n      kj::heapArray<kj::byte>({88, 32, 88}),\n    },\n    {\n      \"data:unknown/unknown,X X\"_kj,\n      \"unknown/unknown\"_kj,\n      kj::heapArray<kj::byte>({88, 32, 88}),\n    },\n    {\n      \"data:text/plain;a=\\\",\\\",X\"_kj,\n      \"text/plain;a=\\\"\\\"\"_kj,\n      kj::heapArray<kj::byte>({34, 44, 88}),\n    },\n    {\n      \"data:text/plain;a=%2C,X\"_kj,\n      \"text/plain;a=%2C\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:;base64;base64,WA\"_kj,\n      \"text/plain\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:x/x;base64;base64,WA\"_kj,\n      \"x/x\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:x/x;base64;charset=x,WA\"_kj,\n      \"x/x;charset=x\"_kj,\n      kj::heapArray<kj::byte>({87, 65}),\n    },\n    {\n      \"data:x/x;base64;charset=x;base64,WA\"_kj,\n      \"x/x;charset=x\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:x/x;base64;base64x,WA\"_kj,\n      \"x/x\"_kj,\n      kj::heapArray<kj::byte>({87, 65}),\n    },\n    {\n      \"data:;base64,W%20A\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:;base64,W%0CA\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:x;base64x,WA\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({87, 65}),\n    },\n    {\n      \"data:x;base64;x,WA\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({87, 65}),\n    },\n    {\n      \"data:x;base64=x,WA\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({87, 65}),\n    },\n    {\n      \"data:; base64,WA\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:;  base64,WA\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:  ;charset=x   ;  base64,WA\"_kj,\n      \"text/plain;charset=x\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:;base64;,WA\"_kj,\n      \"text/plain\"_kj,\n      kj::heapArray<kj::byte>({87, 65}),\n    },\n    {\n      \"data:;base64 ,WA\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:;base64   ,WA\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:;base 64,WA\"_kj,\n      \"text/plain\"_kj,\n      kj::heapArray<kj::byte>({87, 65}),\n    },\n    {\n      \"data:;BASe64,WA\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:;%62ase64,WA\"_kj,\n      \"text/plain\"_kj,\n      kj::heapArray<kj::byte>({87, 65}),\n    },\n    {\n      \"data:%3Bbase64,WA\"_kj,\n      \"text/plain;charset=US-ASCII\"_kj,\n      kj::heapArray<kj::byte>({87, 65}),\n    },\n    {\n      \"data:;charset=x,X\"_kj,\n      \"text/plain;charset=x\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:; charset=x,X\"_kj,\n      \"text/plain;charset=x\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:;charset =x,X\"_kj,\n      \"text/plain\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:;charset= x,X\"_kj,\n      \"text/plain;charset=\\\" x\\\"\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:;charset=,X\"_kj,\n      \"text/plain\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:;charset,X\"_kj,\n      \"text/plain\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:;charset=\\\"x\\\",X\"_kj,\n      \"text/plain;charset=x\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    },\n    {\n      \"data:;CHARSET=\\\"X\\\",X\"_kj,\n      \"text/plain;charset=X\"_kj,\n      kj::heapArray<kj::byte>({88}),\n    }};\n\n  auto testPtr = kj::arrayPtr<Test>(tests, 72);\n\n  for (auto& test: testPtr) {\n    if (test.mimeType == nullptr) {\n      KJ_ASSERT(DataUrl::tryParse(test.input) == kj::none);\n    } else {\n      auto parsed = KJ_ASSERT_NONNULL(DataUrl::tryParse(test.input));\n      KJ_ASSERT(parsed.getMimeType().toString() == test.mimeType);\n      KJ_ASSERT(parsed.getData() == test.data);\n    }\n  }\n}\n\nstruct Base64Test {\n  kj::StringPtr input;\n  kj::Array<kj::byte> expected;\n};\n\nKJ_TEST(\"DataUrl base64\") {\n  // Our base64 decoder is not very strict and way more forgiving than the\n  // web platform's forgiving base64 decoder. That's just fine for us.\n  // These cases were extracted from the Web Platform Tests for data urls\n  // See: https://github.com/web-platform-tests/wpt/blob/master/fetch/data-urls/resources/\n  Base64Test tests[] = {{\"\"_kj, nullptr}, {\"abcd\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\" abcd\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"abcd \"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\" abcd===\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"abcd=== \"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"abcd ===\"_kj, kj::heapArray<kj::byte>({105, 183, 29})}, {\"a\"_kj, nullptr},\n    {\"ab\"_kj, kj::heapArray<kj::byte>({105})}, {\"abc\"_kj, kj::heapArray<kj::byte>({105, 183})},\n    {\"abcde\"_kj, kj::heapArray<kj::byte>({105, 183, 29})}, {\"𐀀\"_kj, nullptr}, {\"=\"_kj, nullptr},\n    {\"==\"_kj, nullptr}, {\"===\"_kj, nullptr}, {\"====\"_kj, nullptr}, {\"=====\"_kj, nullptr},\n    {\"a=\"_kj, nullptr}, {\"a==\"_kj, nullptr}, {\"a===\"_kj, nullptr}, {\"a====\"_kj, nullptr},\n    {\"a=====\"_kj, nullptr}, {\"ab=\"_kj, kj::heapArray<kj::byte>({105})},\n    {\"ab==\"_kj, kj::heapArray<kj::byte>({105})}, {\"ab===\"_kj, kj::heapArray<kj::byte>({105})},\n    {\"ab====\"_kj, kj::heapArray<kj::byte>({105})}, {\"ab=====\"_kj, kj::heapArray<kj::byte>({105})},\n    {\"abc=\"_kj, kj::heapArray<kj::byte>({105, 183})},\n    {\"abc==\"_kj, kj::heapArray<kj::byte>({105, 183})},\n    {\"abc===\"_kj, kj::heapArray<kj::byte>({105, 183})},\n    {\"abc====\"_kj, kj::heapArray<kj::byte>({105, 183})},\n    {\"abc=====\"_kj, kj::heapArray<kj::byte>({105, 183})},\n    {\"abcd=\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"abcd==\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"abcd===\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"abcd====\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"abcd=====\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"abcde=\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"abcde==\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"abcde===\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"abcde====\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"abcde=====\"_kj, kj::heapArray<kj::byte>({105, 183, 29})}, {\"=a\"_kj, nullptr},\n    {\"=a=\"_kj, nullptr}, {\"a=b\"_kj, kj::heapArray<kj::byte>({105})},\n    {\"a=b=\"_kj, kj::heapArray<kj::byte>({105})}, {\"ab=c\"_kj, kj::heapArray<kj::byte>({105, 183})},\n    {\"ab=c=\"_kj, kj::heapArray<kj::byte>({105, 183})},\n    {\"abc=d\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"abc=d=\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"ab\\u000Bcd\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"ab\\u3000cd\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"ab\\u3001cd\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"ab\\tcd\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"ab\\ncd\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"ab\\fcd\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"ab\\rcd\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"ab cd\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"ab\\u00a0cd\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"ab\\t\\n\\f\\r cd\"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\" \\t\\n\\f\\r ab\\t\\n\\f\\r cd\\t\\n\\f\\r \"_kj, kj::heapArray<kj::byte>({105, 183, 29})},\n    {\"ab\\t\\n\\f\\r =\\t\\n\\f\\r =\\t\\n\\f\\r \"_kj, kj::heapArray<kj::byte>({105})}, {\"A\"_kj, nullptr},\n    {\"/A\"_kj, kj::heapArray<kj::byte>({252})}, {\"//A\"_kj, kj::heapArray<kj::byte>({255, 240})},\n    {\"///A\"_kj, kj::heapArray<kj::byte>({255, 255, 192})},\n    {\"////A\"_kj, kj::heapArray<kj::byte>({255, 255, 255})}, {\"/\"_kj, nullptr},\n    {\"A/\"_kj, kj::heapArray<kj::byte>({3})}, {\"AA/\"_kj, kj::heapArray<kj::byte>({0, 15})},\n    {\"AAAA/\"_kj, kj::heapArray<kj::byte>({0, 0, 0})},\n    {\"AAA/\"_kj, kj::heapArray<kj::byte>({0, 0, 63})},\n    {\"\\u0000nonsense\"_kj, kj::heapArray<kj::byte>({158, 137, 236, 122, 123, 30})},\n    {\"abcd\\u0000nonsense\"_kj, kj::heapArray<kj::byte>({105, 183, 29, 158, 137, 236, 122, 123, 30})},\n    {\"YQ\"_kj, kj::heapArray<kj::byte>({97})}, {\"YR\"_kj, kj::heapArray<kj::byte>({97})},\n    {\"~~\"_kj, nullptr}, {\"..\"_kj, nullptr}, {\"--\"_kj, nullptr}, {\"__\"_kj, nullptr}};\n\n  auto testPtr = kj::arrayPtr<Base64Test>(tests, 80);\n\n  for (auto& test: testPtr) {\n    auto input = kj::str(\"data:;base64,\", test.input);\n\n    auto url = KJ_ASSERT_NONNULL(DataUrl::tryParse(input));\n\n    KJ_ASSERT(url.getData() == test.expected, test.input);\n  }\n}\n\nKJ_TEST(\"Large Data URL\") {\n  auto str = kj::str(kj::repeat('a', 6000));\n  auto url = kj::str(\"data:,\", str);\n  auto parsed = KJ_ASSERT_NONNULL(DataUrl::tryParse(url));\n  KJ_ASSERT(parsed.getData().asChars() == str);\n}\n\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/data-url.c++",
    "content": "#include \"data-url.h\"\n\n#include <workerd/api/encoding.h>\n#include <workerd/util/strings.h>\n\n#include <kj/encoding.h>\n\nnamespace workerd::api {\n\nkj::Maybe<DataUrl> DataUrl::tryParse(kj::StringPtr url) {\n  KJ_IF_SOME(url, jsg::Url::tryParse(url)) {\n    return from(url);\n  }\n  return kj::none;\n}\n\nkj::Maybe<DataUrl> DataUrl::from(const jsg::Url& url) {\n  if (url.getProtocol() != \"data:\"_kj) return kj::none;\n  auto clone = url.clone(jsg::Url::EquivalenceOption::IGNORE_FRAGMENTS);\n\n  // Remove the \"data:\" prefix.\n  auto href = clone.getHref().slice(5);\n\n  // We scan for the first comma, which separates the MIME type from the data.\n  // Per the fetch spec, it doesn't matter if the comma is within a quoted\n  // string value in the MIME type... which is fun.\n\n  static const auto isBase64 = [](kj::ArrayPtr<const char> label) -> bool {\n    KJ_IF_SOME(pos, label.findLast(';')) {\n      auto res = trimLeadingAndTrailingWhitespace(label.slice(pos + 1));\n      return res.size() == 6 && (res[0] | 0x20) == 'b' && (res[1] | 0x20) == 'a' &&\n          (res[2] | 0x20) == 's' && (res[3] | 0x20) == 'e' && (res[4] == '6') && (res[5] == '4');\n    }\n    return false;\n  };\n\n  static const auto create = [](auto input, auto decoded) {\n    KJ_IF_SOME(parsed, MimeType::tryParse(input)) {\n      return DataUrl(kj::mv(parsed), kj::mv(decoded));\n    } else {\n      return DataUrl(MimeType::PLAINTEXT_ASCII.clone(), kj::mv(decoded));\n    }\n  };\n\n  KJ_IF_SOME(pos, href.findFirst(',')) {\n    auto unparsed = href.first(pos);\n    auto data = href.slice(pos + 1);\n\n    // We need to trim leading and trailing whitespace from the mimetype\n    unparsed = trimLeadingAndTrailingWhitespace(unparsed);\n\n    // Determine if the data is base64 encoded\n    kj::Array<kj::byte> decoded = nullptr;\n    if (isBase64(unparsed)) {\n      unparsed = unparsed.first(KJ_ASSERT_NONNULL(unparsed.findLast(';')));\n      decoded =\n          kj::decodeBase64(stripInnerWhitespace(jsg::Url::percentDecode(data.asBytes())).asChars());\n    } else {\n      decoded = jsg::Url::percentDecode(data.asBytes());\n    }\n\n    if (unparsed.startsWith(\";\"_kj)) {\n      // If the mime type starts with ;, then the spec tells us to\n      // prepend \"text/plain\" to the mime type.\n      auto fixed = kj::str(\"text/plain\", unparsed);\n      return create(fixed.asPtr(), kj::mv(decoded));\n    }\n\n    return create(unparsed, kj::mv(decoded));\n  }\n\n  // If there are no commas, the data: url is invalid.\n  return kj::none;\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/data-url.h",
    "content": "#pragma once\n\n#include <workerd/jsg/url.h>\n#include <workerd/util/mimetype.h>\n\n#include <kj/array.h>\n\nnamespace workerd::api {\n\nclass DataUrl final {\n public:\n  static kj::Maybe<DataUrl> tryParse(kj::StringPtr url);\n  static kj::Maybe<DataUrl> from(const jsg::Url& url);\n\n  DataUrl(DataUrl&&) = default;\n  DataUrl& operator=(DataUrl&&) = default;\n  KJ_DISALLOW_COPY(DataUrl);\n\n  const MimeType& getMimeType() const {\n    return mimeType;\n  }\n  kj::ArrayPtr<const kj::byte> getData() const {\n    return data.asPtr();\n  }\n\n  kj::Array<kj::byte> releaseData() {\n    return data.releaseAsBytes();\n  }\n\n private:\n  DataUrl(MimeType mimeType, kj::Array<kj::byte> data)\n      : mimeType(kj::mv(mimeType)),\n        data(kj::mv(data)) {}\n\n  MimeType mimeType;\n  kj::Array<kj::byte> data;\n};\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/deferred-proxy-test.c++",
    "content": "#include \"deferred-proxy.h\"\n\n#include <kj/test.h>\n\nnamespace workerd::api {\nnamespace {\n\nKJ_TEST(\"kj::Promise<DeferredProxy<T>>: early co_return implicitly fulfills outer promise\") {\n  kj::EventLoop loop;\n  kj::WaitScope waitScope(loop);\n\n  {\n    // Implicit void co_return.\n    auto coro = []() -> kj::Promise<DeferredProxy<void>> {\n      co_await kj::Promise<void>(kj::READY_NOW);\n    };\n    auto promise = coro();\n    KJ_EXPECT(promise.poll(waitScope));\n    auto proxyTask = promise.wait(waitScope).proxyTask;\n    KJ_EXPECT(proxyTask.poll(waitScope));\n    proxyTask.wait(waitScope);\n  }\n  {\n    // Explicit void co_return.\n    auto coro = []() -> kj::Promise<DeferredProxy<void>> { co_return; };\n    auto promise = coro();\n    KJ_EXPECT(promise.poll(waitScope));\n    auto proxyTask = promise.wait(waitScope).proxyTask;\n    KJ_EXPECT(proxyTask.poll(waitScope));\n    proxyTask.wait(waitScope);\n  }\n  {\n    // Valueful co_return.\n    auto coro = []() -> kj::Promise<DeferredProxy<int>> { co_return 123; };\n    auto promise = coro();\n    KJ_EXPECT(promise.poll(waitScope));\n    auto proxyTask = promise.wait(waitScope).proxyTask;\n    KJ_EXPECT(proxyTask.poll(waitScope));\n    KJ_EXPECT(proxyTask.wait(waitScope) == 123);\n  }\n}\n\nKJ_TEST(\"kj::Promise<DeferredProxy<T>>: `KJ_CO_MAGIC BEGIN_DEFERRED_PROXYING` fulfills outer \"\n        \"promise\") {\n  kj::EventLoop loop;\n  kj::WaitScope waitScope(loop);\n\n  auto paf1 = kj::newPromiseAndFulfiller<void>();\n  auto paf2 = kj::newPromiseAndFulfiller<int>();\n\n  auto coro = [&]() -> kj::Promise<DeferredProxy<int>> {\n    co_await paf1.promise;\n    KJ_CO_MAGIC BEGIN_DEFERRED_PROXYING;\n    co_return co_await paf2.promise;\n  };\n\n  auto promise = coro();\n\n  // paf1 unfulfilled, so we don't have a DeferredProxy<T> yet.\n  KJ_EXPECT(!promise.poll(waitScope));\n\n  paf1.fulfiller->fulfill();\n\n  KJ_EXPECT(promise.poll(waitScope));\n  auto proxyTask = promise.wait(waitScope).proxyTask;\n\n  // paf2 unfulfilled, so we don't have a T yet.\n  KJ_EXPECT(!proxyTask.poll(waitScope));\n\n  paf2.fulfiller->fulfill(123);\n\n  KJ_EXPECT(proxyTask.poll(waitScope));\n  KJ_EXPECT(proxyTask.wait(waitScope) == 123);\n}\n\nKJ_TEST(\"kj::Promise<DeferredProxy<T>>: unhandled exception before \"\n        \"`KJ_CO_MAGIC BEGIN_DEFERRED_PROXYING`\") {\n  kj::EventLoop loop;\n  kj::WaitScope waitScope(loop);\n\n  auto paf = kj::newPromiseAndFulfiller<void>();\n\n  auto coro = [&]() -> kj::Promise<DeferredProxy<int>> {\n    co_await paf.promise;\n    KJ_FAIL_ASSERT(\"promise should have been rejected\");\n  };\n\n  auto promise = coro();\n\n  // paf unfulfilled, so we don't have a DeferredProxy<T> yet.\n  KJ_EXPECT(!promise.poll(waitScope));\n\n  paf.fulfiller->reject(KJ_EXCEPTION(FAILED, \"test error\"));\n\n  KJ_EXPECT(promise.poll(waitScope));\n  KJ_EXPECT_THROW_MESSAGE(\"test error\", promise.wait(waitScope));\n}\n\nKJ_TEST(\"kj::Promise<DeferredProxy<T>>: unhandled exception after \"\n        \"`KJ_CO_MAGIC BEGIN_DEFERRED_PROXYING`\") {\n  kj::EventLoop loop;\n  kj::WaitScope waitScope(loop);\n\n  auto paf1 = kj::newPromiseAndFulfiller<void>();\n  auto paf2 = kj::newPromiseAndFulfiller<int>();\n\n  auto coro = [&]() -> kj::Promise<DeferredProxy<int>> {\n    co_await paf1.promise;\n    KJ_CO_MAGIC BEGIN_DEFERRED_PROXYING;\n    co_return co_await paf2.promise;\n  };\n\n  auto promise = coro();\n\n  // paf1 unfulfilled, so we don't have a DeferredProxy<T> yet.\n  KJ_EXPECT(!promise.poll(waitScope));\n\n  paf1.fulfiller->fulfill();\n\n  KJ_EXPECT(promise.poll(waitScope));\n  auto proxyTask = promise.wait(waitScope).proxyTask;\n\n  // paf2 unfulfilled, so we don't have a T yet.\n  KJ_EXPECT(!proxyTask.poll(waitScope));\n\n  paf2.fulfiller->reject(KJ_EXCEPTION(FAILED, \"test error\"));\n\n  KJ_EXPECT(proxyTask.poll(waitScope));\n  KJ_EXPECT_THROW_MESSAGE(\"test error\", proxyTask.wait(waitScope));\n}\n\nKJ_TEST(\"kj::Promise<DeferredProxy<T>>: can be `co_await`ed from another coroutine\") {\n  kj::EventLoop loop;\n  kj::WaitScope waitScope(loop);\n\n  auto paf1 = kj::newPromiseAndFulfiller<void>();\n  auto paf2 = kj::newPromiseAndFulfiller<int>();\n\n  auto nestedCoro = [&]() -> kj::Promise<DeferredProxy<int>> {\n    co_await paf1.promise;\n    KJ_CO_MAGIC BEGIN_DEFERRED_PROXYING;\n    co_return co_await paf2.promise;\n  };\n\n  auto coro = [&]() -> kj::Promise<DeferredProxy<int>> {\n    auto deferred = co_await nestedCoro();\n    KJ_CO_MAGIC BEGIN_DEFERRED_PROXYING;\n    co_return co_await deferred.proxyTask;\n  };\n\n  auto promise = coro();\n\n  // paf1 unfulfilled, so we don't have a DeferredProxy<T> yet.\n  KJ_EXPECT(!promise.poll(waitScope));\n\n  paf1.fulfiller->fulfill();\n\n  KJ_EXPECT(promise.poll(waitScope));\n  auto proxyTask = promise.wait(waitScope).proxyTask;\n\n  // paf2 unfulfilled, so we don't have a T yet.\n  KJ_EXPECT(!proxyTask.poll(waitScope));\n\n  paf2.fulfiller->fulfill(123);\n\n  KJ_EXPECT(proxyTask.poll(waitScope));\n  KJ_EXPECT(proxyTask.wait(waitScope) == 123);\n}\n\nstruct Counter {\n  size_t& wind;\n  size_t& unwind;\n  Counter(size_t& wind, size_t& unwind): wind(wind), unwind(unwind) {\n    ++wind;\n  }\n  ~Counter() {\n    ++unwind;\n  }\n  KJ_DISALLOW_COPY_AND_MOVE(Counter);\n};\n\nkj::Promise<DeferredProxy<void>> cancellationTester(kj::Promise<void> preDeferredProxying,\n    kj::Promise<void> postDeferredProxying,\n    size_t& wind,\n    size_t& unwind) {\n  Counter preCounter(wind, unwind);\n  co_await preDeferredProxying;\n  KJ_CO_MAGIC BEGIN_DEFERRED_PROXYING;\n  Counter postCounter(wind, unwind);\n  co_await postDeferredProxying;\n};\n\nKJ_TEST(\"kj::Promise<DeferredProxy<T>>: can be canceled while suspended before deferred proxying\") {\n  kj::EventLoop loop;\n  kj::WaitScope waitScope(loop);\n\n  size_t wind = 0, unwind = 0;\n\n  {\n    auto neverDone1 = kj::Promise<void>(kj::NEVER_DONE);\n    auto neverDone2 = kj::Promise<void>(kj::NEVER_DONE);\n    neverDone1 = neverDone1.attach(kj::heap<Counter>(wind, unwind));\n    neverDone2 = neverDone2.attach(kj::heap<Counter>(wind, unwind));\n    auto promise = cancellationTester(kj::mv(neverDone1), kj::mv(neverDone2), wind, unwind);\n    KJ_EXPECT(!promise.poll(waitScope));\n  }\n\n  KJ_EXPECT(wind == 3);\n  KJ_EXPECT(unwind == 3);\n}\n\nKJ_TEST(\"kj::Promise<DeferredProxy<T>>: can be canceled while suspended after deferred proxying\") {\n  kj::EventLoop loop;\n  kj::WaitScope waitScope(loop);\n\n  size_t wind = 0, unwind = 0;\n\n  {\n    auto readyNow = kj::Promise<void>(kj::READY_NOW);\n    auto neverDone = kj::Promise<void>(kj::NEVER_DONE);\n    readyNow = readyNow.attach(kj::heap<Counter>(wind, unwind));\n    neverDone = neverDone.attach(kj::heap<Counter>(wind, unwind));\n    auto promise = cancellationTester(kj::mv(readyNow), kj::mv(neverDone), wind, unwind);\n    auto proxyTask = promise.wait(waitScope).proxyTask;\n    KJ_EXPECT(!proxyTask.poll(waitScope));\n  }\n\n  KJ_EXPECT(wind == 4);\n  KJ_EXPECT(unwind == 4);\n}\n\nKJ_TEST(\"kj::Promise<DeferredProxy<T>>: destroying inner PromiseNode before outer does not \"\n        \"segfault\") {\n  // Destroy the inner promise before the outer promise to test our safeguard against incorrect\n  // destruction order causing segfaults.\n\n  kj::EventLoop loop;\n  kj::WaitScope waitScope(loop);\n\n  auto coro = []() -> kj::Promise<DeferredProxy<void>> {\n    KJ_CO_MAGIC BEGIN_DEFERRED_PROXYING;\n    co_await kj::Promise<void>(kj::NEVER_DONE);\n  };\n\n  auto outer = coro();\n\n  // We could call `get()` on the outer node immediately, even before it reports it is ready, but\n  // we call `poll()` for good measure, in case the DeferredProxyCoroutine implementation ever\n  // changes to disallow `get()`-before-ready. We cannot use `wait()` for this purpose, because\n  // `wait()` would avoid the segfault by (correctly) destroying the outer PromiseNode before\n  // returning the result to us.\n  KJ_EXPECT(outer.poll(waitScope));\n\n  auto outerNode = kj::_::PromiseNode::from(kj::mv(outer));\n\n  // `poll()`, unlike `wait()`, does not call `setSelfPointer()` on the outer PromiseNode, which\n  // would cause an assertion failure inside the outer PromiseNode's `get()` implementation, so we\n  // have to do it ourselves.\n  outerNode->setSelfPointer(&outerNode);\n\n  kj::_::ExceptionOr<DeferredProxy<void>> result;\n  outerNode->get(result);\n\n  {\n    // Destroy the inner promise.\n    auto inner = kj::mv(KJ_ASSERT_NONNULL(result.value).proxyTask);\n  }\n\n  // Destroy the outer promise. At one time, this caused a segfault ... or at least it produced\n  // invalid accesses under Valgrind. :/\n  outerNode = nullptr;\n}\n\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/deferred-proxy.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/async.h>\n#include <kj/debug.h>\n\nnamespace workerd::api {\n\n// =======================================================================================\n\n// Some API methods return Promise<DeferredProxy<T>> when the task can be separated into two\n// parts: some work that must be done with the IoContext still live, and some part that\n// can occur after the IoContext completes, but which should still be performed before\n// the overall task is \"done\".\n//\n// In particular, when an HTTP event ends up proxying the response body stream (or WebSocket\n// stream) directly to/from origin, then that streaming can take place without pinning the\n// isolate in memory, and without holding the IoContext open. So,\n// `ServiceWorkerGlobalScope::request()` returns `Promise<DeferredProxy<void>>`. The outer\n// Promise waits for the JavaScript work to be done, and the inner DeferredProxy<void> represents\n// the proxying step.\n//\n// Note that if you're performing a task that resolves to DeferredProxy but JavaScript is\n// actually waiting for the result of the task, then it's your responsibility to call\n// IoContext::current().registerPendingEvent() and attach it to `proxyTask`, otherwise\n// the request might be canceled as the proxy task won't be recognized as something that the\n// request is waiting on.\ntemplate <typename T>\nstruct DeferredProxy {\n  // TODO(cleanup): Now that we have jsg::Promise, it might make sense for deferred proxying to\n  //    be represented as `jsg::Promise<api::DeferredProxy<T>>`, since the outer promise is\n  //    intended to represent activity that happens in JavaScript while the inner one represents\n  //    pure I/O. This will require some refactoring, though.\n\n  kj::Promise<T> proxyTask;\n};\n\ninline DeferredProxy<void> newNoopDeferredProxy() {\n  return DeferredProxy<void>{kj::READY_NOW};\n}\n\ntemplate <typename T>\ninline DeferredProxy<T> newNoopDeferredProxy(T&& value) {\n  return DeferredProxy<T>{kj::mv(value)};\n}\n\n// Helper method to use when you need to return `Promise<DeferredProxy<T>>` but no part of the\n// operation you are returning is eligible to be deferred past the IoContext lifetime.\ntemplate <typename T>\ninline kj::Promise<DeferredProxy<T>> addNoopDeferredProxy(kj::Promise<T> promise) {\n  co_return newNoopDeferredProxy(co_await promise);\n}\ninline kj::Promise<DeferredProxy<void>> addNoopDeferredProxy(kj::Promise<void> promise) {\n  co_await promise;\n  co_return newNoopDeferredProxy();\n}\n\n// ---------------------------------------------------------\n// Deferred proxy coroutine integration\n\n// If a coroutine returns a kj::Promise<DeferredProxy<T>>, the coroutine implementation gains the\n// following features:\n//\n// - `KJ_CO_MAGIC BEGIN_DEFERRED_PROXYING` fulfills the outer kj::Promise<DeferredProxy<T>>. The\n//   resulting DeferredProxy<T> object contains a `proxyTask` Promise which owns the coroutine.\n//\n// - `co_return` implicitly fulfills the outer Promise for the DeferredProxy<T> (if it has not\n//   already been fulfilled by the magic `KJ_CO_MAGIC` described above), then fulfills the inner\n//   `proxyTask`.\n//\n// - Unhandled exceptions reject the outer kj::Promise<DeferredProxy<T>> (if it has not already\n//   been fulfilled by the magic `KJ_CO_MAGIC` described above), then reject the inner `proxyTask`.\n//\n// It is not possible to write a \"regular\" coroutine which returns kj::Promise<DeferredProxy<T>>;\n// that is, `co_return DeferredProxy<T> { ... }` is a compile error. You must initiate deferred\n// proxying using `KJ_CO_MAGIC BEGIN_DEFERRED_PROXYING`.\n\n// The coroutine adapter class, required for the compiler to know how to create coroutines\n// returning kj::Promise<DeferredProxy<T>>. We declare it here so we can name it in our\n// `coroutine_traits` specialization below.\ntemplate <typename T, typename... Args>\nclass DeferredProxyCoroutine;\n}  // namespace workerd::api\n\nnamespace KJ_COROUTINE_STD_NAMESPACE {\n// Enter the `std` or `std::experimental` namespace, depending on whether we're using C++20\n// coroutines or the Coroutines TS.\n\ntemplate <class T, class... Args>\nstruct coroutine_traits<kj::Promise<workerd::api::DeferredProxy<T>>, Args...> {\n  using promise_type = workerd::api::DeferredProxyCoroutine<T, Args...>;\n};\n\n}  // namespace KJ_COROUTINE_STD_NAMESPACE\n\nnamespace workerd::api {\n\nclass BeginDeferredProxyingConstant final {};\n// A magic constant which a DeferredProxyPromise<T> coroutine can `KJ_CO_MAGIC` to indicate that the\n// deferred proxying phase of its operation has begun.\nconstexpr BeginDeferredProxyingConstant BEGIN_DEFERRED_PROXYING{};\n\n// A concept which is true if C is a coroutine adapter which supports the `co_yield` operator for\n// type T. We could also check that the expression results in an awaitable, but that is already a\n// compile error in other ways.\ntemplate <typename T, typename C>\nconcept CoroutineYieldValue = requires(T&& v, C coroutineAdapter) {\n  { coroutineAdapter.yield_value(kj::fwd<T>(v)) };\n};\n\n// The coroutine adapter type for DeferredProxyPromise<T>. Most of the work is forwarded to the\n// regular kj::Promise<T> coroutine adapter.\ntemplate <typename T, typename... Args>\nclass DeferredProxyCoroutine: public kj::_::PromiseNode,\n                              public kj::_::CoroutineMixin<DeferredProxyCoroutine<T, Args...>, T> {\n  using InnerCoroutineAdapter =\n      kj::_::stdcoro::coroutine_traits<kj::Promise<T>, Args...>::promise_type;\n\n public:\n  using Handle = kj::_::stdcoro::coroutine_handle<DeferredProxyCoroutine>;\n\n  DeferredProxyCoroutine(kj::SourceLocation location = {})\n      : inner(Handle::from_promise(*this), location) {}\n\n  kj::Promise<DeferredProxy<T>> get_return_object() {\n    // We need to return a RAII object which will destroy this (as in, `this`) coroutine adapter.\n    // The logic which calls `coroutine_handle<>::destroy()` is tucked away in our inner coroutine\n    // adapter, however, leading to the weird situation where the `inner.get_return_object()`\n    // Promise owns `this`. And `this` owns `inner.get_return_object()` transitively via `result`!\n    //\n    // Fortunately, DeferredProxyCoroutine implements the PromiseNode interface, meaning when our\n    // returned Promise is eventually dropped, our `PromiseNode::destroy()` implementation will be\n    // called. This gives us the opportunity (that is, in `destroy()`) to destroy our\n    // `inner.get_return_object()` Promise, breaking the ownership cycle and destroying `this`.\n\n    result.value = DeferredProxy<T>{inner.get_return_object()};\n    return kj::_::PromiseNode::to<kj::Promise<DeferredProxy<T>>>(kj::_::OwnPromiseNode(this));\n  }\n\n  auto initial_suspend() {\n    return inner.initial_suspend();\n  }\n  auto final_suspend() noexcept {\n    return inner.final_suspend();\n  }\n  // Just trivially forward these.\n\n  void unhandled_exception() {\n    // Reject our outer promise if it hasn't yet been fulfilled, or forward to the inner\n    // implementation.\n\n    if (!deferredProxyingHasBegun) {\n      result.addException(kj::getCaughtExceptionAsKj());\n      onReadyEvent.arm();\n      deferredProxyingHasBegun = true;\n    } else {\n      inner.unhandled_exception();\n    }\n  }\n\n  kj::_::stdcoro::suspend_never yield_value(decltype(BEGIN_DEFERRED_PROXYING)) {\n    // This allows us to write `KJ_CO_MAGIC` within a DeferredProxyPromise<T> coroutine to fulfill\n    // the coroutine's outer promise with a DeferredProxy<T>.\n    //\n    // This could alternatively be implemented as an await_transform() with a magic parameter type.\n\n    fulfillOuterPromise();\n    return {};\n  }\n\n  template <CoroutineYieldValue<InnerCoroutineAdapter> U>\n  auto yield_value(U&& value) {\n    // Forward all other `co_yield`s to the inner coroutine, if it has a `yield_value()`\n    // implementation -- it might implement some magic, too.\n    return inner.yield_value(kj::fwd<U>(value));\n  }\n\n  void fulfill(kj::_::FixVoid<T>&& value) {\n    // Required by CoroutineMixin implementation to implement `co_return`.\n\n    fulfillOuterPromise();\n    inner.fulfill(kj::mv(value));\n  }\n\n  template <typename U>\n  decltype(auto) await_transform(U&& awaitable) {\n    // Trivially forward everything, so we can await anything a kj::Promise<T> can.\n    return inner.await_transform(kj::fwd<U>(awaitable));\n  }\n\n  operator kj::_::CoroutineBase&() {\n    return inner;\n  }\n  // Required by Awaiter<T>::await_suspend() to support awaiting Promises.\n\n private:\n  void fulfillOuterPromise() {\n    // Fulfill the outer promise if it hasn't already settled.\n\n    if (!deferredProxyingHasBegun) {\n      // Our `result` is put in place already by `get_return_object()`, so all we have to do is arm\n      // the event.\n      onReadyEvent.arm();\n      deferredProxyingHasBegun = true;\n    }\n  }\n\n  // PromiseNode implementation\n\n  void setSelfPointer(kj::_::OwnPromiseNode* selfPtr) noexcept override {\n    this->selfPtr = selfPtr;\n  }\n\n  void destroy() override {\n    // The promise returned by `inner.get_return_object()` is what actually owns this coroutine\n    // frame. We temporarily store that in `result` until our outer promise is fulfilled. So, to\n    // destroy ourselves, we must manually drop `result`.\n    //\n    // On the other hand, if our outer promise has already been fulfilled, then `result` has already\n    // been delivered to wherever it is going, and someone else directly owns the coroutine now, not\n    // us. In this case, this `destroy()` override will have already been called (and it will have\n    // been a no-op), because our own OwnPromiseNode will have already been dropped in `get()`.\n\n    auto drop = kj::mv(result);\n  }\n\n  void onReady(kj::_::Event* event) noexcept override {\n    onReadyEvent.init(event);\n  }\n\n  void get(kj::_::ExceptionOrValue& output) noexcept override {\n    // Make sure that the outer PromiseNode (`this` one) is destroyed before the inner PromiseNode.\n    // kj-async should already provide us this guarantee, but since incorrect destruction order\n    // would cause invalid memory access, we provide a stronger guarantee. Also see the comment for\n    // the `result` data member.\n    KJ_ASSERT(selfPtr != nullptr);\n    KJ_DEFER(*selfPtr = nullptr);\n\n    static_cast<decltype(result)&>(output) = kj::mv(result);\n  }\n\n  void tracePromise(kj::_::TraceBuilder& builder, bool stopAtNextEvent) override {\n    // The PromiseNode we're waiting on is whatever the coroutine is waiting on.\n    static_cast<kj::_::PromiseNode&>(inner).tracePromise(builder, stopAtNextEvent);\n\n    // Maybe returning the address of get() will give us a function name with meaningful type\n    // information.\n    builder.add(getMethodStartAddress(implicitCast<PromiseNode&>(*this), &PromiseNode::get));\n  }\n\n  // We defer the majority of the implementation to the regular kj::Promise<T> coroutine adapter.\n  InnerCoroutineAdapter inner;\n\n  // Helper to arm the event which fires when the outer promise (that is, `this` PromiseNode) for\n  // the DeferredProxy<T> is ready.\n  OnReadyEvent onReadyEvent;\n\n  // Stores the result for the outer promise.\n  //\n  // WARNING: This object owns `this` PromiseNode! If `result` is ever moved away, as is done in\n  // `get()`, we must arrange to make sure that no one ever tries to use `this` PromiseNode again.\n  // Stated another way, we must guarantee that the outer PromiseNode (for `DeferredProxy<T>`) is\n  // always destroyed before the inner PromiseNode (for `T`). kj-async always does this anyway, but\n  // we implement an additional safeguard by immediately destroying our own `OwnPromiseNode` (which\n  // we have access to via `setSelfPointer()`) when we move `result` away in `get()`.\n  kj::_::ExceptionOr<DeferredProxy<T>> result;\n\n  // Used to drop ourselves in `get()` -- see comment for `result`.\n  kj::_::OwnPromiseNode* selfPtr = nullptr;\n\n  // Set to true when deferred proxying has begun -- that is, when the outer DeferredProxy<T>\n  // promise is fulfilled by calling `onReadyEvent.arm()`.\n  bool deferredProxyingHasBegun = false;\n};\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/encoding-legacy.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"encoding-legacy.h\"\n\n#include <kj-rs/convert.h>\n#include <rust/cxx.h>\n\n#include <kj/common.h>\n\nnamespace workerd::api {\n\nnamespace {\n\n// Map workerd::api::Encoding to the Rust-side RustEncoding enum.\n::workerd::rust::encoding::Encoding toRustEncoding(Encoding encoding) {\n  using RE = ::workerd::rust::encoding::Encoding;\n  switch (encoding) {\n    case Encoding::Big5:\n      return RE::Big5;\n    case Encoding::Euc_Jp:\n      return RE::EucJp;\n    case Encoding::Euc_Kr:\n      return RE::EucKr;\n    case Encoding::Gb18030:\n      return RE::Gb18030;\n    case Encoding::Gbk:\n      return RE::Gbk;\n    case Encoding::Iso2022_Jp:\n      return RE::Iso2022Jp;\n    case Encoding::Shift_Jis:\n      return RE::ShiftJis;\n    case Encoding::Windows_1252:\n      return RE::Windows1252;\n    case Encoding::X_User_Defined:\n      return RE::XUserDefined;\n    default:\n      KJ_UNREACHABLE;\n  }\n}\n\n}  // namespace\n\nLegacyDecoder::LegacyDecoder(Encoding encoding, DecoderFatal fatal)\n    : encoding(encoding),\n      fatal(fatal),\n      state(::workerd::rust::encoding::new_decoder(toRustEncoding(encoding))) {}\n\nvoid LegacyDecoder::reset() {\n  ::workerd::rust::encoding::reset(*state);\n}\n\nkj::Maybe<jsg::JsString> LegacyDecoder::decode(\n    jsg::Lock& js, kj::ArrayPtr<const kj::byte> buffer, bool flush) {\n  if (!flush && buffer.size() == 0) {\n    // Avoid encoding_rs empty-chunk streaming bug:\n    // https://github.com/hsivonen/encoding_rs/issues/126#issuecomment-3677642122\n    return js.str();\n  }\n\n  ::workerd::rust::encoding::DecodeOptions options{.flush = flush, .fatal = fatal.toBool()};\n  // Decode into the Rust-side reusable buffer. The Rust decoder handles\n  // lazy reset internally when a previous call used flush=true.\n  auto result =\n      ::workerd::rust::encoding::decode(*state, buffer.as<kj_rs::RustMutable>(), kj::mv(options));\n\n  if (fatal.toBool() && result.had_error) {\n    return kj::none;\n  }\n\n  // Zero-copy view of the UTF-16 output slice from the Rust-owned buffer.\n  return js.str(kj::from<kj_rs::Rust>(result.output));\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/encoding-legacy.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// WHATWG-compliant legacy decoders (CJK multi-byte, windows-1252,\n// x-user-defined) implemented via the encoding_rs Rust crate through\n// a CXX bridge. A single LegacyDecoder class wraps an opaque Rust-side\n// decoder that handles all the encoding-specific state machines.\n\n#pragma once\n\n#include \"encoding-shared.h\"\n\n#include <workerd/rust/encoding/lib.rs.h>\n\n#include <rust/cxx.h>\n\n#include <kj/common.h>\n\nnamespace workerd::api {\n\n// Unified legacy decoder using encoding_rs via Rust CXX bridge.\n// encoding_rs implements the full WHATWG decoder algorithms for all\n// legacy encodings, including streaming, error recovery, and ASCII\n// byte pushback.\n//\n// According to WHATWG spec, any encoding except UTF-8 and UTF-16 is considered legacy.\nclass LegacyDecoder final: public Decoder {\n public:\n  LegacyDecoder(Encoding encoding, DecoderFatal fatal);\n  ~LegacyDecoder() noexcept = default;\n  LegacyDecoder(LegacyDecoder&&) noexcept = default;\n  LegacyDecoder& operator=(LegacyDecoder&&) noexcept = default;\n  KJ_DISALLOW_COPY(LegacyDecoder);\n\n  Encoding getEncoding() override {\n    return encoding;\n  }\n\n  kj::Maybe<jsg::JsString> decode(\n      jsg::Lock& js, kj::ArrayPtr<const kj::byte> buffer, bool flush = false) override;\n\n  void reset() override;\n\n private:\n  Encoding encoding;\n  DecoderFatal fatal;\n  ::rust::Box<::workerd::rust::encoding::Decoder> state;\n};\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/encoding-shared.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Shared types used by encoding.h and encoding-legacy.h.\n// Extracted to break circular dependencies between the two headers.\n\n#pragma once\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/util/strong-bool.h>\n\nnamespace workerd::api {\n\nWD_STRONG_BOOL(DecoderFatal);\nWD_STRONG_BOOL(DecoderIgnoreBom);\n\n// The encodings listed here are defined as required by the Encoding spec.\n// The first label is enum we use to identify the encoding in code, while\n// the second label is the public identifier.\n#define EW_ENCODINGS(V)                                                                            \\\n  V(Utf8, \"utf-8\")                                                                                 \\\n  V(Ibm866, \"ibm866\")                                                                              \\\n  V(Iso8859_2, \"iso-8859-2\")                                                                       \\\n  V(Iso8859_3, \"iso-8859-3\")                                                                       \\\n  V(Iso8859_4, \"iso-8859-4\")                                                                       \\\n  V(Iso8859_5, \"iso-8859-5\")                                                                       \\\n  V(Iso8859_6, \"iso-8859-6\")                                                                       \\\n  V(Iso8859_7, \"iso-8859-7\")                                                                       \\\n  V(Iso8859_8, \"iso-8859-8\")                                                                       \\\n  V(Iso8859_8i, \"iso-8859-8-i\")                                                                    \\\n  V(Iso8859_10, \"iso-8859-10\")                                                                     \\\n  V(Iso8859_13, \"iso-8859-13\")                                                                     \\\n  V(Iso8859_14, \"iso-8859-14\")                                                                     \\\n  V(Iso8859_15, \"iso-8859-15\")                                                                     \\\n  V(Iso8859_16, \"iso-8859-16\")                                                                     \\\n  V(Ko18_r, \"koi8-r\")                                                                              \\\n  V(Koi8_u, \"koi8-u\")                                                                              \\\n  V(Macintosh, \"macintosh\")                                                                        \\\n  V(Windows_874, \"windows-874\")                                                                    \\\n  V(Windows_1250, \"windows-1250\")                                                                  \\\n  V(Windows_1251, \"windows-1251\")                                                                  \\\n  V(Windows_1252, \"windows-1252\")                                                                  \\\n  V(Windows_1253, \"windows-1253\")                                                                  \\\n  V(Windows_1254, \"windows-1254\")                                                                  \\\n  V(Windows_1255, \"windows-1255\")                                                                  \\\n  V(Windows_1256, \"windows-1256\")                                                                  \\\n  V(Windows_1257, \"windows-1257\")                                                                  \\\n  V(Windows_1258, \"windows-1258\")                                                                  \\\n  V(X_Mac_Cyrillic, \"x-mac-cyrillic\")                                                              \\\n  V(Gbk, \"gbk\")                                                                                    \\\n  V(Gb18030, \"gb18030\")                                                                            \\\n  V(Big5, \"big5\")                                                                                  \\\n  V(Euc_Jp, \"euc-jp\")                                                                              \\\n  V(Iso2022_Jp, \"iso-2022-jp\")                                                                     \\\n  V(Shift_Jis, \"shift_jis\")                                                                        \\\n  V(Euc_Kr, \"euc-kr\")                                                                              \\\n  V(Replacement, \"replacement\")                                                                    \\\n  V(Utf16be, \"utf-16be\")                                                                           \\\n  V(Utf16le, \"utf-16le\")                                                                           \\\n  V(X_User_Defined, \"x-user-defined\")\n\nenum class Encoding {\n  INVALID,\n#define V(name, _) name,\n  EW_ENCODINGS(V)\n#undef V\n};\n\n// A Decoder provides the underlying implementation of a TextDecoder.\nclass Decoder {\n public:\n  virtual ~Decoder() noexcept(true) {}\n  virtual Encoding getEncoding() = 0;\n  virtual kj::Maybe<jsg::JsString> decode(\n      jsg::Lock& js, kj::ArrayPtr<const kj::byte> buffer, bool flush = false) = 0;\n\n  virtual void reset() {}\n};\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/encoding-test.c++",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"encoding.h\"\n\n#include <kj/test.h>\n\nnamespace workerd::api {\nnamespace test {\n\n// These tests verify the findBestFit() function used by TextEncoder.encodeInto().\n//\n// bestFit(input, bufferSize) returns the number of input code units that can be\n// fully converted to UTF-8 and fit within the given output buffer size in bytes.\n//\n// Different characters expand to different UTF-8 byte lengths:\n//   - ASCII (U+0000-U+007F): 1 byte per code unit\n//   - Latin-1 extended (U+0080-U+00FF): 2 bytes per code unit\n//   - BMP characters (U+0100-U+FFFF): 2-3 bytes per code unit\n//   - Supplementary characters (U+10000+): 4 bytes, encoded as surrogate pairs in UTF-16\n//\n// The function must never split a surrogate pair, so if there's only room for part of\n// a multi-byte character, it stops before that character.\nKJ_TEST(\"BestFitASCII\") {\n  // If there's zero input or output space, the answer is zero.\n  KJ_ASSERT(bestFit(\"\", 0) == 0);\n  KJ_ASSERT(bestFit(\"a\", 0) == 0);\n  KJ_ASSERT(bestFit(\"aa\", 0) == 0);\n  KJ_ASSERT(bestFit(\"aaa\", 0) == 0);\n  KJ_ASSERT(bestFit(\"aaaa\", 0) == 0);\n  KJ_ASSERT(bestFit(\"aaaaa\", 0) == 0);\n  KJ_ASSERT(bestFit(\"\", 0) == 0);\n  KJ_ASSERT(bestFit(\"\", 1) == 0);\n  KJ_ASSERT(bestFit(\"\", 2) == 0);\n  KJ_ASSERT(bestFit(\"\", 3) == 0);\n  KJ_ASSERT(bestFit(\"\", 4) == 0);\n  KJ_ASSERT(bestFit(\"\", 5) == 0);\n  // Zero cases with two-byte strings.\n  KJ_ASSERT(bestFit(u\"\", 0) == 0);\n  KJ_ASSERT(bestFit(u\"€\", 0) == 0);\n  KJ_ASSERT(bestFit(u\"€€\", 0) == 0);\n  KJ_ASSERT(bestFit(u\"€€€\", 0) == 0);\n  KJ_ASSERT(bestFit(u\"€€€€\", 0) == 0);\n  KJ_ASSERT(bestFit(u\"€€€€€\", 0) == 0);\n  KJ_ASSERT(bestFit(u\"\", 0) == 0);\n  KJ_ASSERT(bestFit(u\"\", 1) == 0);\n  KJ_ASSERT(bestFit(u\"\", 2) == 0);\n  KJ_ASSERT(bestFit(u\"\", 3) == 0);\n  KJ_ASSERT(bestFit(u\"\", 4) == 0);\n  KJ_ASSERT(bestFit(u\"\", 5) == 0);\n  // Small buffers that only just fit.\n  KJ_ASSERT(bestFit(u\"a\", 1) == 1);\n  KJ_ASSERT(bestFit(u\"å\", 2) == 1);\n  KJ_ASSERT(bestFit(u\"€\", 3) == 1);\n  KJ_ASSERT(bestFit(u\"😹\", 4) == 2);\n  // Small buffers that don't fit.\n  KJ_ASSERT(bestFit(u\"å\", 1) == 0);\n  KJ_ASSERT(bestFit(u\"€\", 2) == 0);\n  KJ_ASSERT(bestFit(u\"😹\", 3) == 0);\n  // Don't chop a surrogate pair.\n  KJ_ASSERT(bestFit(u\"1😹\", 4) == 1);\n  KJ_ASSERT(bestFit(u\"12😹\", 5) == 2);\n  KJ_ASSERT(bestFit(u\"123😹\", 6) == 3);\n  KJ_ASSERT(bestFit(u\"1234😹\", 7) == 4);\n  KJ_ASSERT(bestFit(u\"12345😹\", 8) == 5);\n  // Some bigger ones just for fun.\n  KJ_ASSERT(bestFit(u\"😹😹😹😹😹😹\", 0) == 0);\n  KJ_ASSERT(bestFit(u\"😹😹😹😹😹😹\", 1) == 0);\n  KJ_ASSERT(bestFit(u\"😹😹😹😹😹😹\", 2) == 0);\n  KJ_ASSERT(bestFit(u\"😹😹😹😹😹😹\", 3) == 0);\n  KJ_ASSERT(bestFit(u\"😹😹😹😹😹😹\", 4) == 2);\n  KJ_ASSERT(bestFit(u\"😹😹😹😹😹😹\", 5) == 2);\n  KJ_ASSERT(bestFit(u\"😹😹😹😹😹😹\", 6) == 2);\n  KJ_ASSERT(bestFit(u\"😹😹😹😹😹😹\", 7) == 2);\n  KJ_ASSERT(bestFit(u\"😹😹😹😹😹😹\", 8) == 4);\n  KJ_ASSERT(bestFit(u\"😹😹😹😹😹😹\", 9) == 4);\n  KJ_ASSERT(bestFit(u\"0😹😹😹😹😹😹\", 9) == 5);          // 0😹😹 is 5 and takes 9.\n  KJ_ASSERT(bestFit(u\"01😹😹😹😹😹😹\", 9) == 4);         // 01😹 is 4 and takes 6.\n  KJ_ASSERT(bestFit(u\"012😹😹😹😹😹😹\", 9) == 5);        // 012😹 is 5 and takes 7.\n  KJ_ASSERT(bestFit(u\"0123😹😹😹😹😹😹\", 9) == 6);       // 0123😹 is 6 and takes 8.\n  KJ_ASSERT(bestFit(u\"01234😹😹😹😹😹😹\", 9) == 7);      // 01234😹 is 7 and takes 9.\n  KJ_ASSERT(bestFit(u\"012345😹😹😹😹😹😹\", 9) == 6);     // 012345 is 6 and takes 6.\n  KJ_ASSERT(bestFit(u\"0123456😹😹😹😹😹😹\", 9) == 7);    // 0123456 is 7 and takes 7.\n  KJ_ASSERT(bestFit(u\"01234567😹😹😹😹😹😹\", 9) == 8);   // 0123456 is 8 and takes 8.\n  KJ_ASSERT(bestFit(u\"012345678😹😹😹😹😹😹\", 9) == 9);  // 0123456 is 9 and takes 9.\n}\n\n}  // namespace test\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/encoding.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"encoding.h\"\n\n#include \"simdutf.h\"\n#include \"util.h\"\n\n#include <workerd/io/features.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/util/autogate.h>\n#include <workerd/util/strings.h>\n\n#include <unicode/ucnv.h>\n#include <unicode/utf8.h>\n#include <v8.h>\n\n#include <kj/array.h>\n#include <kj/string.h>\n\nnamespace workerd::api {\n\n// =======================================================================================\n// TextDecoder implementation\n\nnamespace {\n#define EW_ENCODING_LABELS(V)                                                                      \\\n  V(\"unicode-1-1-utf-8\", Utf8)                                                                     \\\n  V(\"unicode11utf8\", Utf8)                                                                         \\\n  V(\"unicode20utf8\", Utf8)                                                                         \\\n  V(\"utf-8\", Utf8)                                                                                 \\\n  V(\"utf8\", Utf8)                                                                                  \\\n  V(\"x-unicode20utf8\", Utf8)                                                                       \\\n  V(\"866\", Ibm866)                                                                                 \\\n  V(\"cp866\", Ibm866)                                                                               \\\n  V(\"csibm866\", Ibm866)                                                                            \\\n  V(\"ibm866\", Ibm866)                                                                              \\\n  V(\"csisolatin2\", Iso8859_2)                                                                      \\\n  V(\"iso-8859-2\", Iso8859_2)                                                                       \\\n  V(\"iso-ir-101\", Iso8859_2)                                                                       \\\n  V(\"iso8859-2\", Iso8859_2)                                                                        \\\n  V(\"iso88592\", Iso8859_2)                                                                         \\\n  V(\"iso_8859-2\", Iso8859_2)                                                                       \\\n  V(\"iso_8859-2:1987\", Iso8859_2)                                                                  \\\n  V(\"l2\", Iso8859_2)                                                                               \\\n  V(\"latin2\", Iso8859_2)                                                                           \\\n  V(\"csisolatin3\", Iso8859_3)                                                                      \\\n  V(\"iso-8859-3\", Iso8859_3)                                                                       \\\n  V(\"iso-ir-109\", Iso8859_3)                                                                       \\\n  V(\"iso8859-3\", Iso8859_3)                                                                        \\\n  V(\"iso88593\", Iso8859_3)                                                                         \\\n  V(\"iso_8859-3\", Iso8859_3)                                                                       \\\n  V(\"iso_8859-3:1988\", Iso8859_3)                                                                  \\\n  V(\"l3\", Iso8859_3)                                                                               \\\n  V(\"latin3\", Iso8859_3)                                                                           \\\n  V(\"csisolatin4\", Iso8859_4)                                                                      \\\n  V(\"iso-8859-4\", Iso8859_4)                                                                       \\\n  V(\"iso-ir-110\", Iso8859_4)                                                                       \\\n  V(\"iso8859-4\", Iso8859_4)                                                                        \\\n  V(\"iso88594\", Iso8859_4)                                                                         \\\n  V(\"iso_8859-4\", Iso8859_4)                                                                       \\\n  V(\"iso_8859-4:1988\", Iso8859_4)                                                                  \\\n  V(\"l4\", Iso8859_4)                                                                               \\\n  V(\"latin4\", Iso8859_4)                                                                           \\\n  V(\"csisolatincyrillic\", Iso8859_5)                                                               \\\n  V(\"cyrillic\", Iso8859_5)                                                                         \\\n  V(\"iso-8859-5\", Iso8859_5)                                                                       \\\n  V(\"iso-ir-144\", Iso8859_5)                                                                       \\\n  V(\"iso8859-5\", Iso8859_5)                                                                        \\\n  V(\"iso88595\", Iso8859_5)                                                                         \\\n  V(\"iso_8859-5\", Iso8859_5)                                                                       \\\n  V(\"iso_8859-5:1988\", Iso8859_5)                                                                  \\\n  V(\"arabic\", Iso8859_6)                                                                           \\\n  V(\"asmo-708\", Iso8859_6)                                                                         \\\n  V(\"csiso88596e\", Iso8859_6)                                                                      \\\n  V(\"csiso88596i\", Iso8859_6)                                                                      \\\n  V(\"csisolatinarabic\", Iso8859_6)                                                                 \\\n  V(\"ecma-114\", Iso8859_6)                                                                         \\\n  V(\"iso-8859-6\", Iso8859_6)                                                                       \\\n  V(\"iso-8859-6-e\", Iso8859_6)                                                                     \\\n  V(\"iso-8859-6-i\", Iso8859_6)                                                                     \\\n  V(\"iso-ir-127\", Iso8859_6)                                                                       \\\n  V(\"iso8859-6\", Iso8859_6)                                                                        \\\n  V(\"iso88596\", Iso8859_6)                                                                         \\\n  V(\"iso_8859-6\", Iso8859_6)                                                                       \\\n  V(\"iso_8859-6:1987\", Iso8859_6)                                                                  \\\n  V(\"csisolatingreek\", Iso8859_7)                                                                  \\\n  V(\"ecma-118\", Iso8859_7)                                                                         \\\n  V(\"elot_928\", Iso8859_7)                                                                         \\\n  V(\"greek\", Iso8859_7)                                                                            \\\n  V(\"greek8\", Iso8859_7)                                                                           \\\n  V(\"iso-8859-7\", Iso8859_7)                                                                       \\\n  V(\"iso-ir-126\", Iso8859_7)                                                                       \\\n  V(\"iso8859-7\", Iso8859_7)                                                                        \\\n  V(\"iso88597\", Iso8859_7)                                                                         \\\n  V(\"iso_8859-7\", Iso8859_7)                                                                       \\\n  V(\"iso_8859-7:1987\", Iso8859_7)                                                                  \\\n  V(\"sun_eu_greek\", Iso8859_7)                                                                     \\\n  V(\"csiso88598e\", Iso8859_8)                                                                      \\\n  V(\"csisolatinhebrew\", Iso8859_8)                                                                 \\\n  V(\"hebrew\", Iso8859_8)                                                                           \\\n  V(\"iso-8859-8\", Iso8859_8)                                                                       \\\n  V(\"iso-8859-8-e\", Iso8859_8)                                                                     \\\n  V(\"iso-ir-138\", Iso8859_8)                                                                       \\\n  V(\"iso8859-8\", Iso8859_8)                                                                        \\\n  V(\"iso88598\", Iso8859_8)                                                                         \\\n  V(\"iso_8859-8\", Iso8859_8)                                                                       \\\n  V(\"iso_8859-8:1988\", Iso8859_8)                                                                  \\\n  V(\"visual\", Iso8859_8)                                                                           \\\n  V(\"csiso88598i\", Iso8859_8i)                                                                     \\\n  V(\"iso-8859-8-i\", Iso8859_8i)                                                                    \\\n  V(\"logical\", Iso8859_8i)                                                                         \\\n  V(\"csisolatin6\", Iso8859_10)                                                                     \\\n  V(\"iso-8859-10\", Iso8859_10)                                                                     \\\n  V(\"iso-ir-157\", Iso8859_10)                                                                      \\\n  V(\"iso8859-10\", Iso8859_10)                                                                      \\\n  V(\"iso885910\", Iso8859_10)                                                                       \\\n  V(\"l6\", Iso8859_10)                                                                              \\\n  V(\"latin6\", Iso8859_10)                                                                          \\\n  V(\"iso-8859-13\", Iso8859_13)                                                                     \\\n  V(\"iso8859-13\", Iso8859_13)                                                                      \\\n  V(\"iso885913\", Iso8859_13)                                                                       \\\n  V(\"iso-8859-14\", Iso8859_14)                                                                     \\\n  V(\"iso8859-14\", Iso8859_14)                                                                      \\\n  V(\"iso885914\", Iso8859_14)                                                                       \\\n  V(\"csisolatin9\", Iso8859_15)                                                                     \\\n  V(\"iso-8859-15\", Iso8859_15)                                                                     \\\n  V(\"iso8859-15\", Iso8859_15)                                                                      \\\n  V(\"iso885915\", Iso8859_15)                                                                       \\\n  V(\"iso_8859-15\", Iso8859_15)                                                                     \\\n  V(\"l9\", Iso8859_15)                                                                              \\\n  V(\"iso-8859-16\", Iso8859_16)                                                                     \\\n  V(\"cskoi8r\", Ko18_r)                                                                             \\\n  V(\"koi\", Ko18_r)                                                                                 \\\n  V(\"koi8\", Ko18_r)                                                                                \\\n  V(\"koi8-r\", Ko18_r)                                                                              \\\n  V(\"koi8_r\", Ko18_r)                                                                              \\\n  V(\"koi8-ru\", Koi8_u)                                                                             \\\n  V(\"koi8-u\", Koi8_u)                                                                              \\\n  V(\"csmacintosh\", Macintosh)                                                                      \\\n  V(\"mac\", Macintosh)                                                                              \\\n  V(\"macintosh\", Macintosh)                                                                        \\\n  V(\"x-mac-roman\", Macintosh)                                                                      \\\n  V(\"dos-874\", Windows_874)                                                                        \\\n  V(\"iso-8859-11\", Windows_874)                                                                    \\\n  V(\"iso8859-11\", Windows_874)                                                                     \\\n  V(\"iso885911\", Windows_874)                                                                      \\\n  V(\"tis-620\", Windows_874)                                                                        \\\n  V(\"windows-874\", Windows_874)                                                                    \\\n  V(\"cp1250\", Windows_1250)                                                                        \\\n  V(\"windows-1250\", Windows_1250)                                                                  \\\n  V(\"x-cp1250\", Windows_1250)                                                                      \\\n  V(\"cp1251\", Windows_1251)                                                                        \\\n  V(\"windows-1251\", Windows_1251)                                                                  \\\n  V(\"x-cp1251\", Windows_1251)                                                                      \\\n  V(\"ansi_x3.4-1968\", Windows_1252)                                                                \\\n  V(\"ascii\", Windows_1252)                                                                         \\\n  V(\"cp1252\", Windows_1252)                                                                        \\\n  V(\"cp819\", Windows_1252)                                                                         \\\n  V(\"csisolatin1\", Windows_1252)                                                                   \\\n  V(\"ibm819\", Windows_1252)                                                                        \\\n  V(\"iso-8859-1\", Windows_1252)                                                                    \\\n  V(\"iso-ir-100\", Windows_1252)                                                                    \\\n  V(\"iso8859-1\", Windows_1252)                                                                     \\\n  V(\"iso88591\", Windows_1252)                                                                      \\\n  V(\"iso_8859-1\", Windows_1252)                                                                    \\\n  V(\"iso_8859-1:1987\", Windows_1252)                                                               \\\n  V(\"l1\", Windows_1252)                                                                            \\\n  V(\"latin1\", Windows_1252)                                                                        \\\n  V(\"us-ascii\", Windows_1252)                                                                      \\\n  V(\"windows-1252\", Windows_1252)                                                                  \\\n  V(\"x-cp1252\", Windows_1252)                                                                      \\\n  V(\"cp1253\", Windows_1253)                                                                        \\\n  V(\"windows-1253\", Windows_1253)                                                                  \\\n  V(\"x-cp1253\", Windows_1253)                                                                      \\\n  V(\"cp1254\", Windows_1254)                                                                        \\\n  V(\"csisolatin5\", Windows_1254)                                                                   \\\n  V(\"iso-8859-9\", Windows_1254)                                                                    \\\n  V(\"iso-ir-148\", Windows_1254)                                                                    \\\n  V(\"iso8859-9\", Windows_1254)                                                                     \\\n  V(\"iso88599\", Windows_1254)                                                                      \\\n  V(\"iso_8859-9\", Windows_1254)                                                                    \\\n  V(\"iso_8859-9:1989\", Windows_1254)                                                               \\\n  V(\"l5\", Windows_1254)                                                                            \\\n  V(\"latin5\", Windows_1254)                                                                        \\\n  V(\"windows-1254\", Windows_1254)                                                                  \\\n  V(\"x-cp1254\", Windows_1254)                                                                      \\\n  V(\"cp1255\", Windows_1255)                                                                        \\\n  V(\"windows-1255\", Windows_1255)                                                                  \\\n  V(\"x-cp1255\", Windows_1255)                                                                      \\\n  V(\"cp1256\", Windows_1256)                                                                        \\\n  V(\"windows-1256\", Windows_1256)                                                                  \\\n  V(\"x-cp1256\", Windows_1256)                                                                      \\\n  V(\"cp1257\", Windows_1257)                                                                        \\\n  V(\"windows-1257\", Windows_1257)                                                                  \\\n  V(\"x-cp1257\", Windows_1257)                                                                      \\\n  V(\"cp1258\", Windows_1258)                                                                        \\\n  V(\"windows-1258\", Windows_1258)                                                                  \\\n  V(\"x-cp1258\", Windows_1258)                                                                      \\\n  V(\"x-mac-cyrillic\", X_Mac_Cyrillic)                                                              \\\n  V(\"x-mac-ukrainian\", X_Mac_Cyrillic)                                                             \\\n  V(\"chinese\", Gbk)                                                                                \\\n  V(\"csgb2312\", Gbk)                                                                               \\\n  V(\"csiso58gb231280\", Gbk)                                                                        \\\n  V(\"gb2312\", Gbk)                                                                                 \\\n  V(\"gb_2312\", Gbk)                                                                                \\\n  V(\"gb_2312-80\", Gbk)                                                                             \\\n  V(\"gbk\", Gbk)                                                                                    \\\n  V(\"iso-ir-58\", Gbk)                                                                              \\\n  V(\"x-gbk\", Gbk)                                                                                  \\\n  V(\"gb18030\", Gb18030)                                                                            \\\n  V(\"big5\", Big5)                                                                                  \\\n  V(\"big5-hkscs\", Big5)                                                                            \\\n  V(\"cn-big5\", Big5)                                                                               \\\n  V(\"csbig5\", Big5)                                                                                \\\n  V(\"x-x-big5\", Big5)                                                                              \\\n  V(\"cseucpkdfmtjapanese\", Euc_Jp)                                                                 \\\n  V(\"euc-jp\", Euc_Jp)                                                                              \\\n  V(\"x-euc-jp\", Euc_Jp)                                                                            \\\n  V(\"csiso2022jp\", Iso2022_Jp)                                                                     \\\n  V(\"iso-2022-jp\", Iso2022_Jp)                                                                     \\\n  V(\"csshiftjis\", Shift_Jis)                                                                       \\\n  V(\"ms932\", Shift_Jis)                                                                            \\\n  V(\"ms_kanji\", Shift_Jis)                                                                         \\\n  V(\"shift-jis\", Shift_Jis)                                                                        \\\n  V(\"shift_jis\", Shift_Jis)                                                                        \\\n  V(\"sjis\", Shift_Jis)                                                                             \\\n  V(\"windows-31j\", Shift_Jis)                                                                      \\\n  V(\"x-sjis\", Shift_Jis)                                                                           \\\n  V(\"cseuckr\", Euc_Kr)                                                                             \\\n  V(\"csksc56011987\", Euc_Kr)                                                                       \\\n  V(\"euc-kr\", Euc_Kr)                                                                              \\\n  V(\"iso-ir-149\", Euc_Kr)                                                                          \\\n  V(\"korean\", Euc_Kr)                                                                              \\\n  V(\"ks_c_5601-1987\", Euc_Kr)                                                                      \\\n  V(\"ks_c_5601-1989\", Euc_Kr)                                                                      \\\n  V(\"ksc5601\", Euc_Kr)                                                                             \\\n  V(\"ksc_5601\", Euc_Kr)                                                                            \\\n  V(\"windows-949\", Euc_Kr)                                                                         \\\n  V(\"csiso2022kr\", Replacement)                                                                    \\\n  V(\"hz-gb-2312\", Replacement)                                                                     \\\n  V(\"iso-2022-cn\", Replacement)                                                                    \\\n  V(\"iso-2022-cn-ext\", Replacement)                                                                \\\n  V(\"iso-2022-kr\", Replacement)                                                                    \\\n  V(\"replacement\", Replacement)                                                                    \\\n  V(\"unicodefffe\", Utf16be)                                                                        \\\n  V(\"utf-16be\", Utf16be)                                                                           \\\n  V(\"csunicode\", Utf16le)                                                                          \\\n  V(\"iso-10646-ucs-2\", Utf16le)                                                                    \\\n  V(\"ucs-2\", Utf16le)                                                                              \\\n  V(\"unicode\", Utf16le)                                                                            \\\n  V(\"unicodefeff\", Utf16le)                                                                        \\\n  V(\"utf-16\", Utf16le)                                                                             \\\n  V(\"utf-16le\", Utf16le)                                                                           \\\n  V(\"x-user-defined\", X_User_Defined)\n\nkj::StringPtr getEncodingId(Encoding encoding) {\n  switch (encoding) {\n    case Encoding::INVALID:\n      return \"invalid\"_kj;\n#define V(name, id)                                                                                \\\n  case Encoding::name:                                                                             \\\n    return id##_kj;\n      EW_ENCODINGS(V)\n#undef V\n  }\n  KJ_UNREACHABLE;\n}\n\nEncoding getEncodingForLabel(kj::StringPtr label) {\n  auto lower = toLower(label);\n  auto trimmed = trimLeadingAndTrailingWhitespace(lower);\n#define V(label, key)                                                                              \\\n  if (trimmed == label##_kjb) return Encoding::key;\n  EW_ENCODING_LABELS(V)\n#undef V\n  return Encoding::INVALID;\n}\n\nconstexpr int MAX_SIZE_FOR_STACK_ALLOC = 4096;\n\n}  // namespace\n\nconst kj::Array<const kj::byte> TextDecoder::EMPTY =\n    kj::Array<const kj::byte>(&DUMMY, 0, kj::NullArrayDisposer::instance);\nconst TextDecoder::DecodeOptions TextDecoder::DEFAULT_OPTIONS = TextDecoder::DecodeOptions();\n\nkj::Maybe<IcuDecoder> IcuDecoder::create(Encoding encoding, bool fatal, bool ignoreBom) {\n  UErrorCode status = U_ZERO_ERROR;\n  // Per the WHATWG encoding spec (section 10.1.1), GBK's decoder is gb18030's decoder.\n  // https://encoding.spec.whatwg.org/#gbk-decoder\n  // We can't change getEncodingId() itself because it is also used for the TextDecoder.encoding\n  // getter, which must still return \"gbk\" for GBK.\n  auto icuEncoding =\n      encoding == Encoding::Gbk ? getEncodingId(Encoding::Gb18030) : getEncodingId(encoding);\n  UConverter* inner = ucnv_open(icuEncoding.cStr(), &status);\n  JSG_REQUIRE(U_SUCCESS(status), RangeError, \"Invalid or unsupported encoding\");\n\n  if (fatal) {\n    status = U_ZERO_ERROR;\n    ucnv_setToUCallBack(inner, UCNV_TO_U_CALLBACK_STOP, nullptr, nullptr, nullptr, &status);\n    if (U_FAILURE(status)) return kj::none;\n  }\n\n  return IcuDecoder(encoding, inner, fatal, ignoreBom);\n}\n\nkj::Maybe<jsg::JsString> IcuDecoder::decode(\n    jsg::Lock& js, kj::ArrayPtr<const kj::byte> buffer, bool flush) {\n  UErrorCode status = U_ZERO_ERROR;\n  const auto maxCharSize = [this]() { return ucnv_getMaxCharSize(inner.get()); };\n\n  const auto isUnicode = [this]() {\n    switch (ucnv_getType(inner.get())) {\n      case UCNV_UTF8:\n      case UCNV_UTF16:\n      case UCNV_UTF16_BigEndian:\n      case UCNV_UTF16_LittleEndian:\n        return true;\n      default:\n        return false;\n    }\n    KJ_UNREACHABLE;\n  };\n\n  KJ_DEFER({\n    if (flush) reset();\n  });\n\n  // Evaluate fast-path options. These provide shortcuts for common cases with the caveat\n  // that error handling for invalid sequences might be a bit different (because the\n  // conversions are being handled by v8 directly rather than by the ICU converter).\n  if (buffer.size() > 0 && ucnv_toUCountPending(inner.get(), &status) == 0) {\n    KJ_ASSERT(U_SUCCESS(status));\n    if (encoding == Encoding::Utf8 &&\n        simdutf::validate_ascii(buffer.asChars().begin(), buffer.size())) {\n      // This is a fast-path option for UTF-8 that can be taken when there\n      // are no buffered inputs and the non-empty input buffer contains only\n      // codepoints <= 0x7f. This path is safe because with ASCII range codepoints\n      // we know we won't accidentally split a multi-byte encoding. We also don't\n      // have to worry about the BOM here since the BOM bytes are > 0x7f.\n      // Note also that in this case we'll interpret as Latin1 since UTF-8 bytes\n      // within this range are identical to Latin1 and v8 allocates these more\n      // efficiently.\n      return js.str(buffer);\n    }\n\n    if (encoding == Encoding::Utf16le && buffer.size() % sizeof(char16_t) == 0) {\n      // This is a fast-path option for UTF-16le that can be taken when:\n      // there are no buffered inputs, the non-empty input buffer length is an\n      // even multiple of 2, and either flush is true or the last code unit\n      // is not a Unicode lead surrogate. This is safe because when flush\n      // is true the converter state will be cleared, and if the last code\n      // unit is not a lead surrogate, we won't have to worry about possibly\n      // splitting a valid surrogate pair.\n\n      // The input buffer may be at an odd byte offset (e.g. a Uint8Array view\n      // at offset 3 into an ArrayBuffer), which makes reinterpret_cast to\n      // char16_t* undefined behavior due to alignment violation. Copy into an\n      // aligned buffer to avoid this.\n      auto bufSize = buffer.size() / 2;\n      kj::SmallArray<char16_t, 256> aligned(bufSize);\n      aligned.asBytes().copyFrom(buffer.first(bufSize * 2));\n      auto data = aligned.asPtr();\n\n      if (flush || !U_IS_SURROGATE_LEAD(data[data.size() - 1])) {\n        bool omitInitialBom = false;\n        if (!ignoreBom && !bomSeen) {\n          omitInitialBom = data[0] == 0xfeff;\n          bomSeen = true;\n        }\n\n        auto slice = data.slice(omitInitialBom ? 1 : 0, data.size());\n\n        // If textDecoderReplaceSurrogates flag is enabled, then we follow the spec\n        // and fix invalid surrogates on the UTF-16 input.\n        if (slice.size() == 0 || !FeatureFlags::get(js).getTextDecoderReplaceSurrogates()) {\n          return js.str(slice);\n        }\n\n        if (simdutf::validate_utf16(slice.begin(), slice.size())) {\n          return js.str(slice);\n        }\n\n        if (fatal) {\n          // In fatal mode, return error for invalid surrogates\n          return kj::none;\n        }\n\n        // In non-fatal mode, replace invalid surrogates with U+FFFD.\n        // Output size equals input size because each invalid surrogate (1 code unit)\n        // is replaced with U+FFFD (also 1 code unit).\n        // Use stack allocation for small strings (up to 256 code units) to avoid\n        // heap allocation overhead.\n        kj::SmallArray<char16_t, 256> fixed(slice.size());\n        simdutf::to_well_formed_utf16(slice.begin(), slice.size(), fixed.begin());\n        return js.str(fixed.asPtr());\n      }\n    }\n  }\n\n  status = U_ZERO_ERROR;\n  auto limit = 2 * maxCharSize() *\n      (!flush ? buffer.size()\n              : kj::max(buffer.size(),\n                    static_cast<size_t>(ucnv_toUCountPending(inner.get(), &status))));\n\n  KJ_STACK_ARRAY(UChar, result, limit, 512, 4096);\n\n  auto dest = result.begin();\n  auto source = reinterpret_cast<const char*>(buffer.begin());\n\n  ucnv_toUnicode(\n      inner.get(), &dest, dest + limit, &source, source + buffer.size(), nullptr, flush, &status);\n\n  if (U_FAILURE(status)) return kj::none;\n\n  auto omitInitialBom = false;\n  auto length = std::distance(result.begin(), dest);\n  if (length > 0 && isUnicode() && !ignoreBom && !bomSeen) {\n    omitInitialBom = result[0] == 0xfeff;\n    bomSeen = true;\n  }\n\n  return js.str(result.slice(omitInitialBom ? 1 : 0, length));\n}\n\nvoid IcuDecoder::reset() {\n  bomSeen = false;\n  return ucnv_reset(inner.get());\n}\n\nDecoder& TextDecoder::getImpl() {\n  KJ_SWITCH_ONEOF(decoder) {\n    KJ_CASE_ONEOF(dec, LegacyDecoder) {\n      return dec;\n    }\n    KJ_CASE_ONEOF(dec, IcuDecoder) {\n      return dec;\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\njsg::Ref<TextDecoder> TextDecoder::constructor(jsg::Lock& js,\n    jsg::Optional<kj::String> maybeLabel,\n    jsg::Optional<ConstructorOptions> maybeOptions) {\n  static constexpr ConstructorOptions DEFAULT_OPTIONS;\n  auto options = maybeOptions.orDefault(DEFAULT_OPTIONS);\n  auto encoding = Encoding::Utf8;\n\n  const auto errorMessage = [](kj::StringPtr label) {\n    return kj::str(\"\\\"\", label, \"\\\" is not a valid encoding.\");\n  };\n\n  KJ_IF_SOME(label, maybeLabel) {\n    encoding = getEncodingForLabel(label);\n    JSG_REQUIRE(encoding != Encoding::Replacement && encoding != Encoding::INVALID, RangeError,\n        errorMessage(label));\n  }\n\n  switch (encoding) {\n    case Encoding::Big5:\n    case Encoding::Euc_Jp:\n    case Encoding::Euc_Kr:\n    case Encoding::Gb18030:\n    case Encoding::Gbk:\n    case Encoding::Iso2022_Jp:\n    case Encoding::Shift_Jis: {\n      // If the feature flag is disabled, we use the ICU decoder.\n      if (!FeatureFlags::get(js).getTextDecoderCjkDecoder()) {\n        break;\n      }\n\n      // We fallthrough to LegacyDecoder in order to avoid breaking changes.\n      [[fallthrough]];\n    }\n    case Encoding::X_User_Defined:\n    case Encoding::Windows_1252:\n      return js.alloc<TextDecoder>(LegacyDecoder(encoding, DecoderFatal(options.fatal)), options);\n    default:\n      break;\n  }\n\n  return js.alloc<TextDecoder>(\n      JSG_REQUIRE_NONNULL(IcuDecoder::create(encoding, options.fatal, options.ignoreBOM),\n          RangeError, errorMessage(getEncodingId(encoding))),\n      options);\n}\n\nkj::StringPtr TextDecoder::getEncoding() {\n  return getEncodingId(getImpl().getEncoding());\n}\n\njsg::JsString TextDecoder::decode(jsg::Lock& js,\n    jsg::Optional<kj::Array<const kj::byte>> maybeInput,\n    jsg::Optional<DecodeOptions> maybeOptions) {\n  auto options = maybeOptions.orDefault(DEFAULT_OPTIONS);\n  auto& input = maybeInput.orDefault(EMPTY);\n  return JSG_REQUIRE_NONNULL(\n      getImpl().decode(js, input, !options.stream), TypeError, \"Failed to decode input.\");\n}\n\nkj::Maybe<jsg::JsString> TextDecoder::decodePtr(\n    jsg::Lock& js, kj::ArrayPtr<const kj::byte> buffer, bool flush) {\n  KJ_SWITCH_ONEOF(decoder) {\n    KJ_CASE_ONEOF(dec, LegacyDecoder) {\n      return dec.decode(js, buffer, flush);\n    }\n    KJ_CASE_ONEOF(dec, IcuDecoder) {\n      return dec.decode(js, buffer, flush);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\n// =======================================================================================\n// TextEncoder implementation\n\njsg::Ref<TextEncoder> TextEncoder::constructor(jsg::Lock& js) {\n  return js.alloc<TextEncoder>();\n}\n\njsg::JsUint8Array TextEncoder::encode(jsg::Lock& js, jsg::Optional<jsg::JsString> input) {\n  if (!workerd::util::Autogate::isEnabled(workerd::util::AutogateKey::ENABLE_FAST_TEXTENCODER)) {\n    auto str = input.orDefault(js.str());\n    auto view = JSG_REQUIRE_NONNULL(jsg::BufferSource::tryAlloc(js, str.utf8Length(js)), RangeError,\n        \"Cannot allocate space for TextEncoder.encode\");\n    [[maybe_unused]] auto result = str.writeInto(\n        js, view.asArrayPtr().asChars(), jsg::JsString::WriteFlags::REPLACE_INVALID_UTF8);\n    KJ_DASSERT(result.written == view.size());\n    return jsg::JsUint8Array(view.getHandle(js).As<v8::Uint8Array>());\n  }\n\n  jsg::JsString str = input.orDefault(js.str());\n\n  size_t utf8_length = 0;\n  auto length = str.length(js);\n\n#ifdef KJ_DEBUG\n  bool wasAlreadyFlat = str.isFlat();\n  KJ_DEFER({ KJ_ASSERT(wasAlreadyFlat || !str.isFlat()); });\n#endif\n\n  // Note: writeInto() doesn't flatten the string - it calls writeTo() which chains through\n  // Write2 -> WriteV2 -> WriteHelperV2 -> String::WriteToFlat.\n  // This means we may read from multiple string segments, but that's fine for our use case.\n\n  if (str.isOneByte(js)) {\n    // Use off-heap allocation for intermediate Latin-1 buffer to avoid wasting V8 heap space\n    // and potentially triggering GC. Stack allocation for small strings, heap for large.\n    kj::SmallArray<kj::byte, MAX_SIZE_FOR_STACK_ALLOC> latin1Buffer(length);\n\n    [[maybe_unused]] auto writeResult = str.writeInto(js, latin1Buffer.asPtr());\n    KJ_DASSERT(\n        writeResult.written == length, \"writeInto must completely overwrite the backing buffer\");\n\n    utf8_length = simdutf::utf8_length_from_latin1(\n        reinterpret_cast<const char*>(latin1Buffer.begin()), length);\n\n    auto backingStore = js.allocBackingStore(utf8_length, jsg::Lock::AllocOption::UNINITIALIZED);\n    if (utf8_length == length) {\n      // ASCII fast path: no conversion needed, Latin-1 is same as UTF-8 for ASCII\n      kj::arrayPtr(static_cast<kj::byte*>(backingStore->Data()), length).copyFrom(latin1Buffer);\n    } else {\n      [[maybe_unused]] auto written =\n          simdutf::convert_latin1_to_utf8(reinterpret_cast<const char*>(latin1Buffer.begin()),\n              length, reinterpret_cast<char*>(backingStore->Data()));\n      KJ_DASSERT(utf8_length == written);\n    }\n    return jsg::JsUint8Array::create(js, kj::mv(backingStore), 0, utf8_length);\n  }\n\n  // Use off-heap allocation for intermediate UTF-16 buffer to avoid wasting V8 heap space\n  // and potentially triggering GC. Stack allocation for small strings, heap for large.\n  // Stack allocation for small strings, heap for large.\n  kj::SmallArray<uint16_t, MAX_SIZE_FOR_STACK_ALLOC> utf16Buffer(length);\n\n  [[maybe_unused]] auto writeResult = str.writeInto(js, utf16Buffer.asPtr());\n  KJ_DASSERT(\n      writeResult.written == length, \"writeInto must completely overwrite the backing buffer\");\n\n  auto data = reinterpret_cast<char16_t*>(utf16Buffer.begin());\n  auto lengthResult = simdutf::utf8_length_from_utf16_with_replacement(data, length);\n  utf8_length = lengthResult.count;\n\n  if (lengthResult.error == simdutf::SURROGATE) {\n    // If there are surrogates there may be unpaired surrogates. Fix them.\n    simdutf::to_well_formed_utf16(data, length, data);\n  } else {\n    KJ_DASSERT(lengthResult.error == simdutf::SUCCESS);\n  }\n\n  auto backingStore = js.allocBackingStore(utf8_length, jsg::Lock::AllocOption::UNINITIALIZED);\n  [[maybe_unused]] auto written =\n      simdutf::convert_utf16_to_utf8(data, length, reinterpret_cast<char*>(backingStore->Data()));\n  KJ_DASSERT(written == utf8_length, \"Conversion yielded wrong number of UTF-8 bytes\");\n\n  return jsg::JsUint8Array::create(js, kj::mv(backingStore), 0, utf8_length);\n}\n\nnamespace {\n\nconstexpr bool isSurrogatePair(uint16_t lead, uint16_t trail) {\n  // We would like to use simdutf::trim_partial_utf16, but it's not guaranteed\n  // to work right on invalid UTF-16. Hence, we need this method to check for\n  // surrogate pairs and correctly trim utf16 chunks.\n  return (lead & 0xfc00) == 0xd800 && (trail & 0xfc00) == 0xdc00;\n}\n\n// Ignores surrogates conservatively.\nconstexpr size_t simpleUtfEncodingLength(uint16_t c) {\n  return 1 + (c >= 0x80) + (c >= 0x400);\n}\n\n// Find how many UTF-16 or Latin1 code units fit when converted to UTF-8.\n// May conservatively underestimate the largest number of code units we can fit\n// because of undetected surrogate pairs on boundaries.\n// Works even on malformed UTF-16.\ntemplate <typename Char>\nsize_t findBestFit(const Char* data, size_t length, size_t bufferSize) {\n  size_t pos = 0;\n  size_t utf8Accumulated = 0;\n  // The SIMD is more efficient with a size that's a little over a multiple of 16.\n  constexpr size_t CHUNK = 257;\n  // The max number of UTF-8 output bytes per input code unit.\n  constexpr bool UTF16 = sizeof(Char) == 2;\n  constexpr size_t MAX_FACTOR = UTF16 ? 3 : 2;\n\n  // Our initial guess at how much the number of elements expands in the\n  // conversion to UTF-8.\n  double expansion = 1.15;\n\n  while (pos < length && utf8Accumulated < bufferSize) {\n    size_t remainingInput = length - pos;\n    size_t spaceRemaining = bufferSize - utf8Accumulated;\n    KJ_DASSERT(expansion >= 1.15);\n\n    // We estimate how many characters are likely to fit in the buffer, but\n    // only try for CHUNK characters at a time to minimize the worst case\n    // waste of time if we guessed too high.\n    size_t guaranteedToFit = spaceRemaining / MAX_FACTOR;\n    if (guaranteedToFit >= remainingInput) {\n      // Don't even bother checking any more, it's all going to fit.  Hitting\n      // this halfway through is also a good reason to limit the CHUNK size.\n      return length;\n    }\n    size_t likelyToFit = kj::min(static_cast<size_t>(spaceRemaining / expansion), CHUNK);\n    size_t fitEstimate = kj::max(1, kj::max(guaranteedToFit, likelyToFit));\n    size_t chunkSize = kj::min(remainingInput, fitEstimate);\n    if (chunkSize == 1) break;  // Not worth running this complicated stuff one char at a time.\n    // No div-by-zero because remainingInput and fitEstimate are at least 1.\n    KJ_DASSERT(chunkSize >= 1);\n\n    size_t chunkUtf8Len;\n    if constexpr (UTF16) {\n      chunkUtf8Len = simdutf::utf8_length_from_utf16_with_replacement(data + pos, chunkSize).count;\n    } else {\n      chunkUtf8Len = simdutf::utf8_length_from_latin1(data + pos, chunkSize);\n    }\n\n    if (utf8Accumulated + chunkUtf8Len > bufferSize) {\n      // Our chosen chunk didn't fit in the rest of the output buffer.\n      KJ_DASSERT(chunkSize > guaranteedToFit);\n      // Since it didn't fit we adjust our expansion guess upwards.\n      expansion = kj::max(expansion * 1.1, (chunkUtf8Len * 1.1) / chunkSize);\n    } else {\n      // Use successful length calculation to adjust our expansion estimate.\n      expansion = kj::max(1.15, (chunkUtf8Len * 1.1) / chunkSize);\n      pos += chunkSize;\n      utf8Accumulated += chunkUtf8Len;\n    }\n  }\n  // Do the last few code units in a simpler way.\n  while (pos < length && utf8Accumulated < bufferSize) {\n    size_t extra = simpleUtfEncodingLength(data[pos]);\n    if (utf8Accumulated + extra > bufferSize) break;\n    pos++;\n    utf8Accumulated += extra;\n  }\n  if (UTF16 && pos != 0 && pos != length && isSurrogatePair(data[pos - 1], data[pos])) {\n    // We ended on a leading surrogate which has a matching trailing surrogate in the next\n    // position.  In order to make progress when the bufferSize is tiny we try to include it.\n    if (utf8Accumulated < bufferSize) {\n      pos++;  // We had one more byte, so we can include the pair, UTF-8 encoding 3->4.\n    } else {\n      pos--;  // Don't chop the pair in half.\n    }\n  }\n  return pos;\n}\n\n}  // namespace\n\n// Test helpers used by encoding-test.c++ to verify findBestFit behavior.\nnamespace test {\n\nsize_t bestFit(const char* str, size_t bufferSize) {\n  return findBestFit(str, strlen(str), bufferSize);\n}\n\nsize_t bestFit(const char16_t* str, size_t bufferSize) {\n  size_t length = 0;\n  while (str[length] != 0) length++;\n  return findBestFit(str, length, bufferSize);\n}\n\n}  // namespace test\n\nTextEncoder::EncodeIntoResult TextEncoder::encodeInto(\n    jsg::Lock& js, jsg::JsString input, jsg::JsUint8Array buffer) {\n  if (!workerd::util::Autogate::isEnabled(workerd::util::AutogateKey::ENABLE_FAST_TEXTENCODER)) {\n    auto result = input.writeInto(\n        js, buffer.asArrayPtr<char>(), jsg::JsString::WriteFlags::REPLACE_INVALID_UTF8);\n    return TextEncoder::EncodeIntoResult{\n      .read = static_cast<int>(result.read),\n      .written = static_cast<int>(result.written),\n    };\n  }\n\n  auto outputBuf = buffer.asArrayPtr<char>();\n  size_t bufferSize = outputBuf.size();\n\n  size_t read = 0;\n  size_t written = 0;\n  {\n    // Scope for the view - we can't do anything that might cause a V8 GC!\n    v8::String::ValueView view(js.v8Isolate, input);\n    size_t length = view.length();\n\n    if (view.is_one_byte()) {\n      auto data = reinterpret_cast<const char*>(view.data8());\n      simdutf::result result =\n          simdutf::validate_ascii_with_errors(data, kj::min(length, bufferSize));\n      written = read = result.count;\n      auto outAddr = outputBuf.begin();\n      kj::arrayPtr(outAddr, read).copyFrom(kj::arrayPtr(data, read));\n      outAddr += read;\n      data += read;\n      length -= read;\n      bufferSize -= read;\n      if (length != 0 && bufferSize != 0) {\n        size_t rest = findBestFit(data, length, bufferSize);\n        if (rest != 0) {\n          KJ_DASSERT(simdutf::utf8_length_from_latin1(data, rest) <= bufferSize);\n          written += simdutf::convert_latin1_to_utf8(data, rest, outAddr);\n          read += rest;\n        }\n      }\n    } else {\n      auto data = reinterpret_cast<const char16_t*>(view.data16());\n      read = findBestFit(data, length, bufferSize);\n      if (read != 0) {\n        KJ_DASSERT(\n            simdutf::utf8_length_from_utf16_with_replacement(data, read).count <= bufferSize);\n        simdutf::result result =\n            simdutf::convert_utf16_to_utf8_with_errors(data, read, outputBuf.begin());\n        if (result.error == simdutf::SUCCESS) {\n          written = result.count;\n        } else {\n          // Oh, no, there are unpaired surrogates.  This is hopefully rare.\n          kj::SmallArray<char16_t, MAX_SIZE_FOR_STACK_ALLOC> conversionBuffer(read);\n          simdutf::to_well_formed_utf16(data, read, conversionBuffer.begin());\n          written =\n              simdutf::convert_utf16_to_utf8(conversionBuffer.begin(), read, outputBuf.begin());\n        }\n      }\n    }\n  }\n  KJ_DASSERT(written <= outputBuf.size());\n  // V8's String::kMaxLenth is a lot less than a maximal int so this is fine.\n  using RInt = decltype(TextEncoder::EncodeIntoResult::read);\n  using WInt = decltype(TextEncoder::EncodeIntoResult::written);\n  KJ_DASSERT(0 <= read && read <= std::numeric_limits<RInt>::max());\n  KJ_DASSERT(0 <= written && written <= std::numeric_limits<WInt>::max());\n  return TextEncoder::EncodeIntoResult{\n    .read = static_cast<RInt>(read),\n    .written = static_cast<WInt>(written),\n  };\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/encoding.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"encoding-legacy.h\"\n#include \"encoding-shared.h\"\n\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/jsg/jsg.h>\n\n#include <unicode/ucnv.h>\n\nnamespace workerd::api {\n\n// Decoder implementation that uses ICU's built-in conversion APIs.\n// ICU's decoder is fairly comprehensive, covering the full range\n// of encodings required by the Encoding specification.\nclass IcuDecoder final: public Decoder {\n public:\n  IcuDecoder(Encoding encoding, UConverter* converter, bool fatal, bool ignoreBom)\n      : encoding(encoding),\n        inner(converter),\n        fatal(fatal),\n        ignoreBom(ignoreBom),\n        bomSeen(false) {}\n  IcuDecoder(IcuDecoder&&) = default;\n  IcuDecoder& operator=(IcuDecoder&&) = default;\n\n  static kj::Maybe<IcuDecoder> create(Encoding encoding, bool fatal, bool ignoreBom);\n\n  Encoding getEncoding() override {\n    return encoding;\n  }\n\n  kj::Maybe<jsg::JsString> decode(\n      jsg::Lock& js, kj::ArrayPtr<const kj::byte> buffer, bool flush = false) override;\n\n  void reset() override;\n\n private:\n  struct ConverterDeleter {\n    void operator()(UConverter* pointer) const {\n      ucnv_close(pointer);\n    }\n  };\n\n  Encoding encoding;\n  std::unique_ptr<UConverter, ConverterDeleter> inner;\n\n  bool fatal;\n  bool ignoreBom;\n  bool bomSeen;\n};\n\n// Implements the TextDecoder interface as prescribed by:\n// https://encoding.spec.whatwg.org/#interface-textdecoder\nclass TextDecoder final: public jsg::Object {\n public:\n  using DecoderImpl = kj::OneOf<LegacyDecoder, IcuDecoder>;\n\n  struct ConstructorOptions {\n    bool fatal = false;\n    bool ignoreBOM = false;\n\n    JSG_STRUCT(fatal, ignoreBOM);\n  };\n\n  struct DecodeOptions {\n    bool stream = false;\n\n    JSG_STRUCT(stream);\n  };\n\n  static jsg::Ref<TextDecoder> constructor(\n      jsg::Lock& js, jsg::Optional<kj::String> label, jsg::Optional<ConstructorOptions> options);\n\n  jsg::JsString decode(jsg::Lock& js,\n      jsg::Optional<kj::Array<const kj::byte>> input,\n      jsg::Optional<DecodeOptions> options);\n\n  kj::StringPtr getEncoding();\n\n  bool getFatal() {\n    return ctorOptions.fatal;\n  }\n  bool getIgnoreBom() {\n    return ctorOptions.ignoreBOM;\n  }\n\n  JSG_RESOURCE_TYPE(TextDecoder, CompatibilityFlags::Reader flags) {\n    JSG_METHOD(decode);\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(encoding, getEncoding);\n      JSG_READONLY_PROTOTYPE_PROPERTY(fatal, getFatal);\n      JSG_READONLY_PROTOTYPE_PROPERTY(ignoreBOM, getIgnoreBom);\n    } else {\n      JSG_READONLY_INSTANCE_PROPERTY(encoding, getEncoding);\n      JSG_READONLY_INSTANCE_PROPERTY(fatal, getFatal);\n      JSG_READONLY_INSTANCE_PROPERTY(ignoreBOM, getIgnoreBom);\n    }\n    // TODO(soon): Defining the constructor override here *should not* be\n    // necessary but for some reason the type generation is creating an\n    // invalid result without it.\n    JSG_TS_OVERRIDE({\n      constructor(label?: string, options?: TextDecoderConstructorOptions);\n    });\n  }\n\n  explicit TextDecoder(DecoderImpl decoder): decoder(kj::mv(decoder)) {}\n\n  explicit TextDecoder(DecoderImpl decoder, const ConstructorOptions& options)\n      : decoder(kj::mv(decoder)),\n        ctorOptions(options) {}\n\n  kj::Maybe<jsg::JsString> decodePtr(\n      jsg::Lock& js, kj::ArrayPtr<const kj::byte> buffer, bool flush);\n\n private:\n  Decoder& getImpl();\n\n  DecoderImpl decoder;\n  ConstructorOptions ctorOptions;\n\n  static const DecodeOptions DEFAULT_OPTIONS;\n  static constexpr kj::byte DUMMY = 0;\n  static const kj::Array<const kj::byte> EMPTY;\n};\n\n// Implements the TextEncoder interface as prescribed by:\n// https://encoding.spec.whatwg.org/#interface-textencoder\nclass TextEncoder final: public jsg::Object {\n public:\n  struct EncodeIntoResult {\n    int read;\n    int written;\n    // TODO(conform): Perhaps use unsigned long long.\n\n    JSG_STRUCT(read, written);\n  };\n\n  static jsg::Ref<TextEncoder> constructor(jsg::Lock& js);\n\n  jsg::JsUint8Array encode(jsg::Lock& js, jsg::Optional<jsg::JsString> input);\n\n  EncodeIntoResult encodeInto(jsg::Lock& js, jsg::JsString input, jsg::JsUint8Array buffer);\n\n  // UTF-8 is the only encoding type supported by the WHATWG spec.\n  kj::StringPtr getEncoding() {\n    return \"utf-8\";\n  }\n\n  JSG_RESOURCE_TYPE(TextEncoder, CompatibilityFlags::Reader flags) {\n    JSG_METHOD(encode);\n    JSG_METHOD(encodeInto);\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(encoding, getEncoding);\n    } else {\n      JSG_READONLY_INSTANCE_PROPERTY(encoding, getEncoding);\n    }\n\n    JSG_TS_OVERRIDE({\n      encodeInto(input: string, buffer: Uint8Array): TextEncoderEncodeIntoResult;\n    });\n  }\n};\n\n#define EW_ENCODING_ISOLATE_TYPES                                                                  \\\n  api::TextDecoder, api::TextEncoder, api::TextDecoder::ConstructorOptions,                        \\\n      api::TextDecoder::DecodeOptions, api::TextEncoder::EncodeIntoResult\n\nnamespace test {\n\nsize_t bestFit(const char* str, size_t bufferSize);\nsize_t bestFit(const char16_t* str, size_t bufferSize);\n\n}  // namespace test\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/events.c++",
    "content": "#include \"events.h\"\n\n#include \"blob.h\"\n#include \"messagechannel.h\"\n\nnamespace workerd::api {\n\nMessageEvent::MessageEvent(jsg::Lock& js,\n    const jsg::JsValue& data,\n    kj::String lastEventId,\n    kj::Maybe<jsg::Ref<MessagePort>> source,\n    kj::Maybe<jsg::Url&> urlForOrigin)\n    : Event(\"message\"),\n      data(jsg::JsRef(js, data)),\n      lastEventId(kj::mv(lastEventId)),\n      maybeSource(kj::mv(source)),\n      maybeOrigin(urlForOrigin.map([](auto& url) { return url.getOrigin(); })) {}\nMessageEvent::MessageEvent(jsg::Lock& js,\n    jsg::JsRef<jsg::JsValue> data,\n    kj::String lastEventId,\n    kj::Maybe<jsg::Ref<MessagePort>> source,\n    kj::Maybe<jsg::Url&> urlForOrigin)\n    : Event(\"message\"),\n      data(kj::mv(data)),\n      lastEventId(kj::mv(lastEventId)),\n      maybeSource(kj::mv(source)),\n      maybeOrigin(urlForOrigin.map([](auto& url) { return url.getOrigin(); })) {}\nMessageEvent::MessageEvent(jsg::Lock& js,\n    kj::String type,\n    const jsg::JsValue& data,\n    kj::String lastEventId,\n    kj::Maybe<jsg::Ref<MessagePort>> source,\n    kj::Maybe<jsg::Url&> urlForOrigin)\n    : Event(kj::mv(type)),\n      data(jsg::JsRef(js, kj::mv(data))),\n      lastEventId(kj::mv(lastEventId)),\n      maybeSource(kj::mv(source)),\n      maybeOrigin(urlForOrigin.map([](auto& url) { return url.getOrigin(); })) {}\nMessageEvent::MessageEvent(jsg::Lock& js,\n    kj::String type,\n    kj::OneOf<jsg::JsRef<jsg::JsValue>, jsg::Ref<Blob>> data,\n    kj::String lastEventId,\n    kj::Maybe<jsg::Ref<MessagePort>> source,\n    kj::Maybe<jsg::Url&> urlForOrigin)\n    : Event(kj::mv(type)),\n      data(kj::mv(data)),\n      lastEventId(kj::mv(lastEventId)),\n      maybeSource(kj::mv(source)),\n      maybeOrigin(urlForOrigin.map([](auto& url) { return url.getOrigin(); })) {}\n\njsg::Ref<MessageEvent> MessageEvent::constructor(\n    jsg::Lock& js, kj::String type, Initializer initializer) {\n  return js.alloc<MessageEvent>(js, kj::mv(type), kj::mv(initializer.data));\n}\n\nkj::OneOf<jsg::JsValue, jsg::Ref<Blob>> MessageEvent::getData(jsg::Lock& js) {\n  KJ_SWITCH_ONEOF(data) {\n    KJ_CASE_ONEOF(jsValue, jsg::JsRef<jsg::JsValue>) {\n      return jsValue.getHandle(js);\n    }\n    KJ_CASE_ONEOF(blob, jsg::Ref<Blob>) {\n      return blob.addRef();\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::Maybe<kj::ArrayPtr<const char>> MessageEvent::getOrigin() {\n  return maybeOrigin.map([](auto& a) -> kj::ArrayPtr<const char> { return a.asPtr(); });\n}\n\nkj::StringPtr MessageEvent::getLastEventId() {\n  return lastEventId;\n}\n\n// Per the spec, the source of a MessageEvent is one of a MessagePort,\n// ServiceWorker, WindowProxy, etc. The only one of these we actually\n// support is MessagePort, return that if its set or null if not.\nkj::Maybe<jsg::Ref<MessagePort>> MessageEvent::getSource() {\n  return maybeSource.map([](auto& port) mutable -> jsg::Ref<MessagePort> { return port.addRef(); });\n}\nkj::ArrayPtr<jsg::Ref<MessagePort>> MessageEvent::getPorts() {\n  // We don't support transferring MessagePorts in MessageEvent\n  // for now, so we return an empty array. Later we might support\n  // this.\n  return nullptr;\n}\n\nvoid MessageEvent::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  KJ_SWITCH_ONEOF(data) {\n    KJ_CASE_ONEOF(jsValue, jsg::JsRef<jsg::JsValue>) {\n      tracker.trackField(\"data\", jsValue);\n    }\n    KJ_CASE_ONEOF(blob, jsg::Ref<Blob>) {\n      tracker.trackField(\"data\", blob);\n    }\n  }\n  tracker.trackField(\"source\", maybeSource);\n}\n\nvoid MessageEvent::visitForGc(jsg::GcVisitor& visitor) {\n  KJ_SWITCH_ONEOF(data) {\n    KJ_CASE_ONEOF(jsValue, jsg::JsRef<jsg::JsValue>) {\n      visitor.visit(jsValue);\n    }\n    KJ_CASE_ONEOF(blob, jsg::Ref<Blob>) {\n      visitor.visit(blob);\n    }\n  }\n  visitor.visit(maybeSource);\n}\n\n// ======================================================================================\nnamespace {\nconst kj::StringPtr kDefaultErrorEventName = \"error\"_kj;\n}  // namespace\n\nErrorEvent::ErrorEvent(ErrorEventInit init): Event(kDefaultErrorEventName), init(kj::mv(init)) {}\n\nErrorEvent::ErrorEvent(kj::String type, ErrorEventInit init)\n    : Event(kj::mv(type)),\n      init(kj::mv(init)) {}\n\nErrorEvent::ErrorEvent(jsg::Lock& js, jsg::JsValue error)\n    : ErrorEvent(ErrorEventInit{.error = jsg::JsRef(js, error)}) {}\n\njsg::Ref<ErrorEvent> ErrorEvent::constructor(\n    jsg::Lock& js, kj::String type, jsg::Optional<ErrorEventInit> init) {\n  return js.alloc<ErrorEvent>(kj::mv(type), kj::mv(init).orDefault({}));\n}\n\nkj::StringPtr ErrorEvent::getFilename() {\n  return init.filename.orDefault(nullptr);\n}\n\nkj::StringPtr ErrorEvent::getMessage() {\n  return init.message.orDefault(nullptr);\n}\n\nint ErrorEvent::getLineno() {\n  return init.lineno.orDefault(0);\n}\n\nint ErrorEvent::getColno() {\n  return init.colno.orDefault(0);\n}\n\njsg::JsValue ErrorEvent::getError(jsg::Lock& js) {\n  KJ_IF_SOME(error, init.error) {\n    return error.getHandle(js);\n  } else {\n    return js.undefined();\n  }\n}\n\nvoid ErrorEvent::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"message\", init.message);\n  tracker.trackField(\"filename\", init.filename);\n  tracker.trackField(\"error\", init.error);\n}\n\nvoid ErrorEvent::visitForGc(jsg::GcVisitor& visitor) {\n  visitor.visit(init.error);\n}\n\n// ======================================================================================\nnamespace {\nconstexpr kj::StringPtr kUnhandledRejectionEventName = \"unhandledrejection\"_kj;\nconstexpr kj::StringPtr kRejectionHandledEventName = \"rejectionhandled\"_kj;\n\nconstexpr kj::StringPtr getPromiseRejectionEventName(v8::PromiseRejectEvent type) {\n  switch (type) {\n    case v8::PromiseRejectEvent::kPromiseRejectWithNoHandler:\n      return kUnhandledRejectionEventName;\n    case v8::PromiseRejectEvent::kPromiseHandlerAddedAfterReject:\n      return kRejectionHandledEventName;\n    default:\n      // Events are not emitted for the other reject types.\n      KJ_UNREACHABLE;\n  }\n}\n\n}  // namespace\n\nPromiseRejectionEvent::PromiseRejectionEvent(\n    v8::PromiseRejectEvent type, jsg::V8Ref<v8::Promise> promise, jsg::Value reason)\n    : Event(getPromiseRejectionEventName(type)),\n      promise(kj::mv(promise)),\n      reason(kj::mv(reason)) {}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/events.h",
    "content": "#pragma once\n\n#include \"basics.h\"\n\n#include <workerd/api/messagechannel.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/url.h>\n\nnamespace workerd::api {\n\nclass Blob;\n\nclass MessageEvent final: public Event {\n public:\n  MessageEvent(jsg::Lock& js,\n      const jsg::JsValue& data,\n      kj::String lastEventId = kj::String(),\n      kj::Maybe<jsg::Ref<MessagePort>> source = kj::none,\n      kj::Maybe<jsg::Url&> urlForOrigin = kj::none);\n\n  MessageEvent(jsg::Lock& js,\n      jsg::JsRef<jsg::JsValue> data,\n      kj::String lastEventId = kj::String(),\n      kj::Maybe<jsg::Ref<MessagePort>> source = kj::none,\n      kj::Maybe<jsg::Url&> urlForOrigin = kj::none);\n\n  MessageEvent(jsg::Lock& js,\n      kj::String type,\n      const jsg::JsValue& data,\n      kj::String lastEventId = kj::String(),\n      kj::Maybe<jsg::Ref<MessagePort>> source = kj::none,\n      kj::Maybe<jsg::Url&> urlForOrigin = kj::none);\n\n  MessageEvent(jsg::Lock& js,\n      kj::String type,\n      kj::OneOf<jsg::JsRef<jsg::JsValue>, jsg::Ref<Blob>> data,\n      kj::String lastEventId = kj::String(),\n      kj::Maybe<jsg::Ref<MessagePort>> source = kj::none,\n      kj::Maybe<jsg::Url&> urlForOrigin = kj::none);\n\n  struct Initializer {\n    jsg::JsRef<jsg::JsValue> data;\n\n    JSG_STRUCT(data);\n    JSG_STRUCT_TS_OVERRIDE(MessageEventInit {\n      data: ArrayBuffer | string;\n    });\n  };\n  static jsg::Ref<MessageEvent> constructor(\n      jsg::Lock& js, kj::String type, Initializer initializer);\n\n  kj::OneOf<jsg::JsValue, jsg::Ref<Blob>> getData(jsg::Lock& js);\n\n  kj::Maybe<kj::ArrayPtr<const char>> getOrigin();\n\n  kj::StringPtr getLastEventId();\n\n  // Per the spec, the source of a MessageEvent is one of a MessagePort,\n  // ServiceWorker, WindowProxy, etc. The only one of these we actually\n  // support is MessagePort, return that if its set or null if not.\n  kj::Maybe<jsg::Ref<MessagePort>> getSource();\n\n  kj::ArrayPtr<jsg::Ref<MessagePort>> getPorts();\n\n  JSG_RESOURCE_TYPE(MessageEvent) {\n    JSG_INHERIT(Event);\n\n    JSG_READONLY_INSTANCE_PROPERTY(data, getData);\n    JSG_READONLY_INSTANCE_PROPERTY(origin, getOrigin);\n    JSG_READONLY_INSTANCE_PROPERTY(lastEventId, getLastEventId);\n    JSG_READONLY_INSTANCE_PROPERTY(source, getSource);\n    JSG_READONLY_INSTANCE_PROPERTY(ports, getPorts);\n\n    JSG_TS_ROOT();\n    JSG_TS_OVERRIDE({ readonly data: any; });\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  // Blob is used only by web-socket.h/c++\n  kj::OneOf<jsg::JsRef<jsg::JsValue>, jsg::Ref<Blob>> data;\n  kj::String lastEventId;\n  kj::Maybe<jsg::Ref<MessagePort>> maybeSource;\n  kj::Maybe<kj::Array<const char>> maybeOrigin;\n\n  void visitForGc(jsg::GcVisitor& visitor);\n};\n\nclass OpenEvent final: public Event {\n public:\n  OpenEvent(): Event(\"open\"_kjc) {}\n  static jsg::Ref<OpenEvent> constructor() = delete;\n  JSG_RESOURCE_TYPE(OpenEvent) {\n    JSG_INHERIT(Event);\n  }\n};\n\nclass ErrorEvent: public Event {\n public:\n  struct ErrorEventInit {\n    jsg::Optional<kj::String> message;\n    jsg::Optional<kj::String> filename;\n    jsg::Optional<int32_t> lineno;\n    jsg::Optional<int32_t> colno;\n    jsg::Optional<jsg::JsRef<jsg::JsValue>> error;\n    JSG_STRUCT(message, filename, lineno, colno, error);\n  };\n\n  ErrorEvent(ErrorEventInit init);\n  ErrorEvent(kj::String type, ErrorEventInit init);\n  ErrorEvent(jsg::Lock& js, jsg::JsValue error);\n\n  static jsg::Ref<ErrorEvent> constructor(\n      jsg::Lock& js, kj::String type, jsg::Optional<ErrorEventInit> init);\n\n  kj::StringPtr getFilename();\n  kj::StringPtr getMessage();\n  int getLineno();\n  int getColno();\n  jsg::JsValue getError(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(ErrorEvent) {\n    JSG_INHERIT(Event);\n\n    JSG_READONLY_PROTOTYPE_PROPERTY(filename, getFilename);\n    JSG_READONLY_PROTOTYPE_PROPERTY(message, getMessage);\n    JSG_READONLY_PROTOTYPE_PROPERTY(lineno, getLineno);\n    JSG_READONLY_PROTOTYPE_PROPERTY(colno, getColno);\n    JSG_READONLY_PROTOTYPE_PROPERTY(error, getError);\n\n    JSG_TS_ROOT();\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  ErrorEventInit init;\n\n  void visitForGc(jsg::GcVisitor& visitor);\n};\n\n// ======================================================================================\nclass PromiseRejectionEvent: public Event {\n public:\n  PromiseRejectionEvent(\n      v8::PromiseRejectEvent type, jsg::V8Ref<v8::Promise> promise, jsg::Value reason);\n\n  static jsg::Ref<PromiseRejectionEvent> constructor(kj::String type) = delete;\n\n  jsg::V8Ref<v8::Promise> getPromise(jsg::Lock& js) {\n    return promise.addRef(js);\n  }\n  jsg::Value getReason(jsg::Lock& js) {\n    return reason.addRef(js);\n  }\n\n  JSG_RESOURCE_TYPE(PromiseRejectionEvent) {\n    JSG_INHERIT(Event);\n    JSG_READONLY_INSTANCE_PROPERTY(promise, getPromise);\n    JSG_READONLY_INSTANCE_PROPERTY(reason, getReason);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"promise\", promise);\n    tracker.trackField(\"reason\", reason);\n  }\n\n private:\n  jsg::V8Ref<v8::Promise> promise;\n  jsg::Value reason;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(promise, reason);\n  }\n};\n\n#define EW_EVENTS_ISOLATE_TYPES                                                                    \\\n  api::ErrorEvent, api::ErrorEvent::ErrorEventInit, api::MessageEvent,                             \\\n      api::MessageEvent::Initializer, api::PromiseRejectionEvent, api::OpenEvent\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/eventsource.c++",
    "content": "// Copyright (c) 2017-2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"eventsource.h\"\n\n#include \"http.h\"\n#include \"messagechannel.h\"\n#include \"streams/common.h\"\n\n#include <workerd/io/features.h>\n#include <workerd/jsg/exception.h>\n#include <workerd/util/mimetype.h>\n\nnamespace workerd::api {\n\nnamespace {\nclass EventSourceSink final: public WritableStreamSink {\n public:\n  EventSourceSink(EventSource& eventSource): eventSource(eventSource) {}\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::byte> buffer) override {\n    // The event stream is a new-line delimited format where each line represents an event.\n    // We need to scan the buffer for end-of-line characters. When we find one, everything before\n    // it is pushed into the event queue and we keep scanning. If we do not find an end-of-line\n    // sequence in the remaining input, we buffer it and wait for the next write to continue\n    // scanning, or until the stream is ended or aborted.\n\n    if (eventSource == kj::none) {\n      // Write was received after end() or abort() was called.\n      // We'll just ignore the write.\n      return kj::READY_NOW;\n    }\n\n    auto input = buffer.asChars();\n\n    // The stream may or may not begin with the UTF-8 BOM (%xFEFF). If this is the\n    // first write, we need to check for it and skip it if it is present.\n    // The BOM is a 3-byte sequence (0xEF, 0xBB, 0xBF) that encodes the Unicode\n    // codepoint U+FEFF. We only want to check for this once.\n    if (!bomChecked) {\n      bomChecked = true;\n      if (input.size() >= 3 && input[0] == '\\xEF' && input[1] == '\\xBB' && input[2] == '\\xBF') {\n        input = input.slice(3);\n      }\n    }\n\n    while (input != nullptr) {\n      KJ_IF_SOME(found, findEndOfLine(input)) {\n        auto prefix = kept.releaseAsArray();\n        // Feed the line into the processor.\n        feed(kj::str(prefix, input.first(found.pos)));\n        input = found.remaining;\n        // If we've reached the end of the input, input will == nullptr here.\n      } else {\n        // No end-of-line found, buffer the input.\n        kept.addAll(input.begin(), input.end());\n        input = nullptr;\n      }\n    }\n\n    // Release any buffered events to the EventSource\n    release();\n\n    return kj::READY_NOW;\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const kj::byte>> pieces) override {\n    for (auto& piece: pieces) {\n      co_await write(piece);\n    }\n    co_return;\n  }\n\n  kj::Promise<void> end() override {\n    // The stream has finished. There's really nothing left to do here. Any partially\n    // filled data will be dropped on the floor.\n    clear();\n    return kj::READY_NOW;\n  }\n\n  void abort(kj::Exception reason) override {\n    // There's really nothing to do here.\n    clear();\n  }\n\n private:\n  kj::Maybe<EventSource&> eventSource;\n\n  // Retained bytes to be processed in the next write.\n  kj::Vector<char> kept;\n\n  // The collected messages that are pending to be dispatched as events\n  kj::Vector<EventSource::PendingMessage> pendingMessages;\n\n  // The message that is currently being processed.\n  kj::Maybe<EventSource::PendingMessage> currentPendingMessage;\n\n  // Set to true once the byte-order-mark has been checked\n  bool bomChecked = false;\n\n  EventSource::PendingMessage& getPendingMessage() {\n    KJ_IF_SOME(pending, currentPendingMessage) {\n      return pending;\n    }\n    return currentPendingMessage.emplace();\n  }\n\n  void feed(kj::String line) {\n    // Parse line according to the event stream format and dispatch the event.\n\n    // stream        = [ bom ] *event\n    // event         = *( comment / field ) end-of-line\n    // comment       = colon *any-char end-of-line\n    // field         = 1*name-char [ colon [ space ] *any-char ] end-of-line\n    // end-of-line   = ( cr lf / cr / lf )\n\n    // ; characters\n    // lf            = %x000A ; U+000A LINE FEED (LF)\n    // cr            = %x000D ; U+000D CARRIAGE RETURN (CR)\n    // space         = %x0020 ; U+0020 SPACE\n    // colon         = %x003A ; U+003A COLON (:)\n    // bom           = %xFEFF ; U+FEFF BYTE ORDER MARK\n    // name-char     = %x0000-0009 / %x000B-000C / %x000E-0039 / %x003B-10FFFF\n    //                 ; a scalar value other than U+000A LINE FEED (LF), U+000D CARRIAGE RETURN\n    //                   (CR), or U+003A COLON (:)\n    // any-char      = %x0000-0009 / %x000B-000C / %x000E-10FFFF\n    //                 ; a scalar value other than U+000A LINE FEED (LF) or U+000D CARRIAGE\n    //                   RETURN (CR)\n\n    // Note that the BOM (if present) is filtered out in the write() method.\n\n    if (line.size() == 0) {\n      // Dispatch the current pending message and clear it. If there is no\n      // pending message, we'll just ignore the line.\n      KJ_IF_SOME(pending, currentPendingMessage) {\n        // This message is done and ready to be dispatched. Add it to the\n        // pendingMessages list. The next time release() is called, it will\n        // be passed off to the EventSource.\n        pending.id = kj::str(KJ_ASSERT_NONNULL(eventSource).getLastEventId());\n        pendingMessages.add(kj::mv(pending));\n        currentPendingMessage = kj::none;\n      }\n    } else if (line[0] == ':') {\n      // Ignore the line.\n    } else {\n      static constexpr auto handle = [](auto& self, kj::ArrayPtr<const char> field,\n                                         kj::ArrayPtr<const char> value) {\n        auto& pending = self.getPendingMessage();\n        auto& ev = KJ_ASSERT_NONNULL(self.eventSource);\n        // Per the spec, only one space after the colon is optional and trimmed.\n        // Any other whitespace, or additional spaces aren't accounted for so would\n        // be part of the value.\n        if (value.size() > 0 && value[0] == ' ') {\n          value = value.slice(1);\n        }\n        if (field == \"data\"_kjc) {\n          pending.data.add(kj::str(value));\n        } else if (field == \"event\"_kjc) {\n          pending.event = kj::str(value);\n        } else if (field == \"id\"_kjc) {\n          ev.setLastEventId(kj::str(value));\n        } else if (field == \"retry\"_kjc) {\n          KJ_IF_SOME(time, kj::str(value).tryParseAs<uint32_t>()) {\n            KJ_ASSERT_NONNULL(self.eventSource).setReconnectionTime(time);\n          }\n          // Ignore the line if it cannot be successfully parsed as a uint32_t\n        }\n      };\n\n      KJ_IF_SOME(pos, line.findFirst(':')) {\n        handle(*this, line.first(pos), line.slice(pos + 1));\n      } else {\n        handle(*this, line, \"\"_kjc);\n      }\n    }\n  }\n\n  void release() {\n    if (pendingMessages.empty()) return;\n    auto pending = pendingMessages.releaseAsArray();\n    // If the event source is gone, just drop the messages on the floor.\n    KJ_IF_SOME(es, eventSource) {\n      es.enqueueMessages(kj::mv(pending));\n    }\n  }\n\n  void clear() {\n    eventSource = kj::none;\n    kept.clear();\n    pendingMessages.clear();\n    currentPendingMessage = kj::none;\n  }\n\n  struct EndOfLine {\n    size_t pos;\n    kj::ArrayPtr<const char> remaining;\n  };\n  kj::Maybe<EndOfLine> findEndOfLine(kj::ArrayPtr<const char> input) {\n    // The end-of-line marker is either \\n, \\r, or \\r\\n\n    size_t pos = 0;\n    while (pos < input.size()) {\n      if (input[pos] == '\\n') {\n        return EndOfLine{pos, input.slice(pos + 1)};\n      } else if (input[pos] == '\\r') {\n        if (pos + 1 < input.size() && input[pos + 1] == '\\n') {\n          return EndOfLine{pos, input.slice(pos + 2)};\n        }\n        return EndOfLine{pos, input.slice(pos + 1)};\n      }\n      pos++;\n    }\n    return kj::none;\n  }\n};\n\nkj::Promise<void> processBody(IoContext& context, kj::Promise<DeferredProxy<void>> promise) {\n  try {\n    co_await context.waitForDeferredProxy(kj::mv(promise));\n  } catch (...) {\n    auto ex = kj::getCaughtExceptionAsKj();\n    // We would see a disconnection exception if the eventstream is closed for\n    // multiple kinds of reasons. If it's a network error and we know we can\n    // reconnect, we should try to reconnect.\n    if (ex.getType() == kj::Exception::Type::DISCONNECTED) {\n      co_return;\n    }\n    // Propagate the exception up.\n    kj::throwFatalException(kj::mv(ex));\n  }\n}\n}  // namespace\n\njsg::Ref<EventSource> EventSource::constructor(\n    jsg::Lock& js, kj::String url, jsg::Optional<EventSourceInit> init) {\n  JSG_REQUIRE(IoContext::hasCurrent(), DOMNotSupportedError,\n      \"An EventSource can only be created within the context of a worker request.\");\n\n  KJ_IF_SOME(i, init) {\n    KJ_IF_SOME(withCredentials, i.withCredentials) {\n      JSG_REQUIRE(!withCredentials, DOMNotSupportedError,\n          \"The init.withCredentials option is not supported. It must be false or undefined.\");\n    }\n  }\n\n  auto eventsource = js.alloc<EventSource>(js,\n      JSG_REQUIRE_NONNULL(jsg::Url::tryParse(url.asPtr()), DOMSyntaxError,\n          kj::str(\"Cannot open an EventSource to '\", url, \"'. The URL is invalid.\")),\n      kj::mv(init));\n  eventsource->start(js);\n  return kj::mv(eventsource);\n}\n\njsg::Ref<EventSource> EventSource::from(jsg::Lock& js, jsg::Ref<ReadableStream> readable) {\n  JSG_REQUIRE(IoContext::hasCurrent(), DOMNotSupportedError,\n      \"An EventSource can only be created within the context of a worker request.\");\n  JSG_REQUIRE(!readable->isLocked(), TypeError, \"This ReadableStream is locked.\");\n  JSG_REQUIRE(\n      !readable->isDisturbed(), TypeError, \"This ReadableStream has already been read from.\");\n  auto eventsource = js.alloc<EventSource>(js);\n  eventsource->run(js, kj::mv(readable), false /* No reconnection attempts */);\n  return kj::mv(eventsource);\n}\n\nEventSource::EventSource(jsg::Lock& js, jsg::Url url, kj::Maybe<EventSourceInit> init)\n    : context(IoContext::current()),\n      impl({\n        .url = kj::mv(url),\n        .options = kj::mv(init).orDefault({}),\n      }),\n      abortController(js.alloc<AbortController>(js)),\n      readyState(State::CONNECTING) {}\n\nEventSource::EventSource(jsg::Lock& js)\n    : context(IoContext::current()),\n      abortController(js.alloc<AbortController>(js)),\n      readyState(State::CONNECTING) {}\n\nvoid EventSource::notifyError(jsg::Lock& js, const jsg::JsValue& error, bool reconnecting) {\n  if (readyState == State::CLOSED) return;\n\n  // Abort the connection if it hasn't already been. This will be a non-op if the\n  // controller has already been aborted.\n  abortController->abort(js, error);\n\n  if (!reconnecting)\n    readyState = State::CLOSED;\n  else\n    readyState = State::CONNECTING;\n\n  // Dispatch the error event.\n  dispatchEventImpl(js, js.alloc<ErrorEvent>(js, error));\n\n  // Log the error as an uncaught exception for debugging purposes.\n  IoContext::current().logUncaughtException(UncaughtExceptionSource::ASYNC_TASK, error);\n}\n\nvoid EventSource::notifyOpen(jsg::Lock& js) {\n  if (readyState == State::CLOSED) return;\n  readyState = State::OPEN;\n  dispatchEventImpl(js, js.alloc<OpenEvent>());\n}\n\nvoid EventSource::notifyMessages(jsg::Lock& js, kj::Array<PendingMessage> messages) {\n  if (readyState == State::CLOSED) return;\n  js.tryCatch([&] {\n    for (auto& message: messages) {\n      auto data = kj::str(kj::delimited(kj::mv(message.data), \"\\n\"_kjc));\n      if (data.size() == 0) continue;\n      kj::String type = kj::mv(message.event).orDefault([]() { return kj::str(\"message\"); });\n      dispatchEventImpl(js,\n          js.alloc<MessageEvent>(js, kj::mv(type), js.str(data), kj::mv(message.id),\n              kj::none /** source **/, impl.map([](FetchImpl& i) -> jsg::Url& { return i.url; })));\n    }\n  }, [&](jsg::Value exception) {\n    // If we end up with an exception being thrown in one of the event handlers, we will\n    // stop trying to process the messages and instead just error the EventSource.\n    notifyError(js, jsg::JsValue(exception.getHandle(js)));\n  });\n}\n\nvoid EventSource::reconnect(jsg::Lock& js) {\n  KJ_ASSERT(impl != kj::none);\n  readyState = State::CONNECTING;\n  abortController = js.alloc<AbortController>(js);\n  auto signal = abortController->getSignal();\n  context.awaitIo(js, signal->wrap(js, context.afterLimitTimeout(reconnectionTime)))\n      .then(js,\n          JSG_VISITABLE_LAMBDA(\n              (self = JSG_THIS), (self), (jsg::Lock & js) mutable { self->start(js); }),\n          JSG_VISITABLE_LAMBDA((self = JSG_THIS), (self), (jsg::Lock& js, jsg::Value exception) {\n            // In this case, it is most likely the EventSource was closed by the user or\n            // there was some other failure. We should not continue trying to reconnect.\n            self->notifyError(js, jsg::JsValue(exception.getHandle(js)));\n          }));\n}\n\nvoid EventSource::start(jsg::Lock& js) {\n  auto& i = KJ_ASSERT_NONNULL(impl);\n  if (readyState == State::CLOSED) return;\n\n  auto fetcher = i.options.fetcher.map([](jsg::Ref<Fetcher>& f) { return f.addRef(); });\n\n  static constexpr auto handleError = [](auto& js, auto& self, kj::String message) {\n    auto ex = js.domException(kj::str(\"AbortError\"), kj::mv(message));\n    auto handle = KJ_ASSERT_NONNULL(ex.tryGetHandle(js));\n    self->notifyError(js, jsg::JsValue(handle));\n    return js.resolvedPromise();\n  };\n\n  auto onSuccess = JSG_VISITABLE_LAMBDA(\n      (self = JSG_THIS, fetcher = fetcher.map([](jsg::Ref<Fetcher>& f) -> jsg::Ref<Fetcher> {\n    return f.addRef();\n  })),\n      (self, fetcher), (jsg::Lock& js, jsg::Ref<Response> response) {\n        if (self->readyState == State::CLOSED) return js.resolvedPromise();\n        auto& impl = KJ_ASSERT_NONNULL(self->impl);\n        if (!response->getOk()) {\n        // Response status code is not 2xx, so we fail.\n        // No reconnection attempt should be made.\n        return handleError(\n            js, self, kj::str(\"The response status code was \", response->getStatus(), \".\"));\n        }\n\n        KJ_IF_SOME(contentType,\n            response->getHeaders(js)->getCommon(js, capnp::CommonHeaderName::CONTENT_TYPE)) {\n        bool invalid = false;\n        KJ_IF_SOME(parsed, MimeType::tryParse(contentType)) {\n        invalid = parsed != MimeType::EVENT_STREAM;\n        } else {\n        invalid = true;\n        }\n        if (invalid) {\n        // No reconnection attempt should be made.\n        return handleError(js, self, kj::str(\"The content type '\", contentType, \"' is invalid.\"));\n        }\n        } else {\n        // No reconnection attempt should be made.\n        return handleError(\n            js, self, kj::str(\"No content type header was present in the response.\"));\n        }\n\n        // If the request was redirected, update the URL to the new location.\n        if (response->getRedirected()) {\n        KJ_IF_SOME(newUrl, jsg::Url::tryParse(response->getUrl())) {\n        impl.url = kj::mv(newUrl);\n        } else {\n        }  // Extra else block to squash compiler warning\n        }\n\n        KJ_IF_SOME(body, response->getBody()) {\n        // Well, ok! We're ready to start trying to process the stream! We do so by\n        // pumping the body into an EventSourceSink until the body is closed, canceled,\n        // or errored.\n        self->run(js, kj::mv(body), true, response.addRef(), kj::mv(fetcher));\n        return js.resolvedPromise();\n        } else {\n        auto& i = KJ_ASSERT_NONNULL(self->impl);\n        // If there is no body, there's nothing to do. We'll treat this as if\n        // the server disconnected. If it only happens once, we'll try to reconnect.\n        // If it happens again, we'll fail the connection as it is likely indicative\n        // of a bug in the server or along the path to the server.\n        if (i.previousNoBody) {\n        self->notifyError(js, js.error(\"The server provided no content.\"));\n        } else {\n        i.previousNoBody = true;\n        self->notifyError(js, js.error(\"The server provided no content. Will try reconnecting.\"),\n            true /* reconnecting */);\n        self->reconnect(js);\n        }\n        return js.resolvedPromise();\n        }\n      });\n\n  auto onFailed =\n      JSG_VISITABLE_LAMBDA((self = JSG_THIS), (self), (jsg::Lock& js, jsg::Value exception) {\n        self->notifyError(js, jsg::JsValue(exception.getHandle(js)));\n        return js.resolvedPromise();\n      });\n\n  auto headers = js.alloc<Headers>();\n  headers->setCommon(capnp::CommonHeaderName::ACCEPT, MimeType::EVENT_STREAM.essence());\n  headers->setCommon(capnp::CommonHeaderName::CACHE_CONTROL, kj::str(\"no-cache\"));\n  if (lastEventId != \"\"_kjc) {\n    headers->setUnguarded(js, kj::str(\"last-event-id\"), kj::str(lastEventId));\n  }\n\n  fetchImpl(js, kj::mv(fetcher), kj::str(i.url),\n      RequestInitializerDict{\n        .headers = kj::mv(headers),\n        .signal = abortController->getSignal(),\n      })\n      .then(js, kj::mv(onSuccess), kj::mv(onFailed));\n}\n\nnamespace {\ntemplate <typename T>\nkj::Maybe<jsg::Ref<T>> addRef(kj::Maybe<jsg::Ref<T>>& ref) {\n  return ref.map([](jsg::Ref<T>& r) { return r.addRef(); });\n}\n}  // namespace\n\nvoid EventSource::run(jsg::Lock& js,\n    jsg::Ref<ReadableStream> readable,\n    bool withReconnection,\n    kj::Maybe<jsg::Ref<Response>> response,\n    kj::Maybe<jsg::Ref<Fetcher>> fetcher) {\n  notifyOpen(js);\n\n  KJ_IF_SOME(resp, response) {\n    JSG_REQUIRE(resp->getType() != \"error\"_kj, TypeError,\n        \"Error responses are unsupported with EventSource\");\n  }\n\n  auto onSuccess =\n      JSG_VISITABLE_LAMBDA((self = JSG_THIS, readable = readable.addRef(), withReconnection,\n                               response = addRef(response), fetcher = addRef(fetcher)),\n          (self, readable, response, fetcher), (jsg::Lock& js) {\n            // The pump finished. Did the server disconnect? If so, try reconnecting if we can.\n            self->notifyError(js, js.error(\"The server disconnected.\"), withReconnection);\n            if (withReconnection) self->reconnect(js);\n          });\n\n  auto onFailed = JSG_VISITABLE_LAMBDA(\n      (self = JSG_THIS, response = addRef(response), fetcher = addRef(fetcher)),\n      (self, response, fetcher), (jsg::Lock& js, jsg::Value exception) {\n        // If the pump fails, catch the error and convert it into an error event.\n        // If we got here, it likely isn't just a DISCONNECT event. Let's not\n        // try to reconnect at this point.\n        self->notifyError(js, jsg::JsValue(exception.getHandle(js)));\n      });\n\n  // Well, ok! We're ready to start trying to process the stream! We do so by\n  // pumping the body into an EventSourceSink until the body is closed, canceled,\n  // or errored.\n  context\n      .awaitIo(\n          js, processBody(context, readable->pumpTo(js, kj::heap<EventSourceSink>(*this), true)))\n      .then(js, kj::mv(onSuccess), kj::mv(onFailed));\n}\n\nvoid EventSource::close(jsg::Lock& js) {\n  if (closeCalled) return;\n  closeCalled = true;\n  abortController->abort(js, kj::none);\n  readyState = State::CLOSED;\n}\n\nvoid EventSource::enqueueMessages(kj::Array<PendingMessage> messages) {\n  context.addTask(context.run([this, messages = kj::mv(messages)](\n                                  auto& lock) mutable { notifyMessages(lock, kj::mv(messages)); }));\n}\n\nvoid EventSource::setReconnectionTime(uint32_t time) {\n  // We enforce both a min and max reconnection time. The minimum is 1 second,\n  // and the maximum is 10 seconds.\n  reconnectionTime =\n      kj::max(kj::min(time, MAX_RECONNECTION_TIME), MIN_RECONNECTION_TIME) * kj::MILLISECONDS;\n}\n\nkj::StringPtr EventSource::getLastEventId() {\n  return lastEventId;\n}\n\nvoid EventSource::setLastEventId(kj::String id) {\n  lastEventId = kj::mv(id);\n}\n\nvoid EventSource::visitForGc(jsg::GcVisitor& visitor) {\n  KJ_IF_SOME(i, impl) {\n    visitor.visit(i.options.fetcher);\n  }\n  visitor.visit(abortController);\n}\n\nvoid EventSource::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  KJ_IF_SOME(i, impl) {\n    tracker.trackField(\"fetcher\", i.options.fetcher);\n    tracker.trackField(\"url\", i.url);\n  }\n  tracker.trackField(\"abortController\", abortController);\n  tracker.trackField(\"lastEventId\", lastEventId);\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/eventsource.h",
    "content": "// Copyright (c) 2017-2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n#include \"basics.h\"\n#include \"events.h\"\n#include \"http.h\"\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/url.h>\n\nnamespace workerd::api {\n\nusing kj::uint;\nclass Fetcher;\nclass ReadableStream;\nclass Response;\n\n// Implements the web standard EventSource API\n// https://developer.mozilla.org/en-US/docs/Web/API/EventSource\nclass EventSource: public EventTarget {\n public:\n  struct EventSourceInit {\n    // We don't actually make use of the standard withCredentials option. If this is set to\n    // any truthy value, we'll throw.\n    jsg::Optional<bool> withCredentials;\n\n    // This is a non-standard workers-specific extension that allows the EventSource to\n    // use a custom Fetcher instance.\n    jsg::Optional<jsg::Ref<Fetcher>> fetcher;\n    JSG_STRUCT(withCredentials, fetcher);\n  };\n\n  enum class State {\n    CONNECTING = 0,\n    OPEN = 1,\n    CLOSED = 2,\n  };\n\n  EventSource(jsg::Lock& js, jsg::Url url, kj::Maybe<EventSourceInit> init = kj::none);\n\n  EventSource(jsg::Lock& js);\n\n  static jsg::Ref<EventSource> constructor(\n      jsg::Lock& js, kj::String url, jsg::Optional<EventSourceInit> init);\n\n  kj::ArrayPtr<const char> getUrl() const {\n    KJ_IF_SOME(i, impl) {\n      return i.url.getHref();\n    }\n    return nullptr;\n  }\n  bool getWithCredentials() const {\n    return false;\n  }\n  uint getReadyState() const {\n    return static_cast<uint>(readyState);\n  }\n\n  void close(jsg::Lock& js);\n\n  // A non-standard extension that creates an EventSource instance around a ReadableStream\n  // instance. In this instance, automatic reconnection is disabled since there is no URL\n  // or underlying fetch used. The ReadableStream instance must produce bytes. It will be\n  // locked and disturbed, and will be read until it either ends or errors. Calling close()\n  // will cause the stream to be canceled.\n  static jsg::Ref<EventSource> from(jsg::Lock& js, jsg::Ref<ReadableStream> stream);\n\n  kj::Maybe<jsg::JsValue> getOnOpen(jsg::Lock& js) {\n    return onopenValue.map(\n        [&](jsg::JsRef<jsg::JsValue>& ref) -> jsg::JsValue { return ref.getHandle(js); });\n  }\n  void setOnOpen(jsg::Lock& js, jsg::JsValue value) {\n    if (!value.isObject() && !value.isFunction()) {\n      onopenValue = kj::none;\n    } else {\n      onopenValue = jsg::JsRef<jsg::JsValue>(js, value);\n    }\n  }\n  kj::Maybe<jsg::JsValue> getOnMessage(jsg::Lock& js) {\n    return onmessageValue.map(\n        [&](jsg::JsRef<jsg::JsValue>& ref) -> jsg::JsValue { return ref.getHandle(js); });\n  }\n  void setOnMessage(jsg::Lock& js, jsg::JsValue value) {\n    if (!value.isObject() && !value.isFunction()) {\n      onmessageValue = kj::none;\n    } else {\n      onmessageValue = jsg::JsRef<jsg::JsValue>(js, value);\n    }\n  }\n  kj::Maybe<jsg::JsValue> getOnError(jsg::Lock& js) {\n    return onerrorValue.map(\n        [&](jsg::JsRef<jsg::JsValue>& ref) -> jsg::JsValue { return ref.getHandle(js); });\n  }\n  void setOnError(jsg::Lock& js, jsg::JsValue value) {\n    if (!value.isObject() && !value.isFunction()) {\n      onerrorValue = kj::none;\n    } else {\n      onerrorValue = jsg::JsRef<jsg::JsValue>(js, value);\n    }\n  }\n\n  JSG_RESOURCE_TYPE(EventSource) {\n    JSG_INHERIT(EventTarget);\n    JSG_METHOD(close);\n    JSG_READONLY_PROTOTYPE_PROPERTY(url, getUrl);\n    JSG_READONLY_PROTOTYPE_PROPERTY(withCredentials, getWithCredentials);\n    JSG_READONLY_PROTOTYPE_PROPERTY(readyState, getReadyState);\n    JSG_PROTOTYPE_PROPERTY(onopen, getOnOpen, setOnOpen);\n    JSG_PROTOTYPE_PROPERTY(onmessage, getOnMessage, setOnMessage);\n    JSG_PROTOTYPE_PROPERTY(onerror, getOnError, setOnError);\n    JSG_STATIC_CONSTANT_NAMED(CONNECTING, static_cast<uint>(State::CONNECTING));\n    JSG_STATIC_CONSTANT_NAMED(OPEN, static_cast<uint>(State::OPEN));\n    JSG_STATIC_CONSTANT_NAMED(CLOSED, static_cast<uint>(State::CLOSED));\n    JSG_STATIC_METHOD(from);\n\n    // EventSource is not defined by the spec as being disposable using ERM, but\n    // it makes sense to do so. The dispose operation simply defers to close().\n    // This will enable `using eventsource = new EventSource(...)`\n    JSG_DISPOSE(close);\n  }\n\n  struct PendingMessage {\n    kj::Vector<kj::String> data;\n    kj::Maybe<kj::String> event;\n    kj::String id;\n  };\n\n  // Called by the internal implementation to notify the EventSource about messages\n  // received from the server.\n  void enqueueMessages(kj::Array<PendingMessage> messages);\n\n  // Called by the internal implementation to notify the EventSource that the server\n  // has provided a new reconnection time.\n  void setReconnectionTime(uint32_t time);\n\n  // Called by the internal implementation to retrieve the last event id that was\n  // specified by the server.\n  kj::StringPtr getLastEventId();\n\n  // Called by the internal implementation to set the last event id that was specified\n  // by the server.\n  void setLastEventId(kj::String id);\n\n  void visitForGc(jsg::GcVisitor& visitor);\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  IoContext& context;\n  struct FetchImpl {\n    jsg::Url url;\n    EventSourceInit options;\n    // Indicates that the server previously responded with no content after a\n    // successful connection. This is likely indicative of a bug on the server.\n    // If this happens once, we'll try to reconnect. If it happens again, we'll\n    // fail the connection.\n    bool previousNoBody = false;\n  };\n  // Used when the EventSource is created using the constructor. This\n  // is the normal mode of operation, when the EventSource uses fetch\n  // under the covers to connect, and reconnect, to the server. This\n  // will be kj::none when the EventSource is created using the from()\n  // method.\n  kj::Maybe<FetchImpl> impl;\n  jsg::Ref<AbortController> abortController;\n  State readyState;\n  kj::String lastEventId;\n\n  // Indicates that the close method has been previously called.\n  bool closeCalled = false;\n\n  // The EventSource spec defines onopen, onmessage, and onerror as prototype\n  // properties on the class.\n  kj::Maybe<jsg::JsRef<jsg::JsValue>> onopenValue;\n  kj::Maybe<jsg::JsRef<jsg::JsValue>> onmessageValue;\n  kj::Maybe<jsg::JsRef<jsg::JsValue>> onerrorValue;\n\n  // The default reconnection wait time. This is fairly arbitrary and is left\n  // entirely up to the implementation. The event stream can provide a new value.\n  static constexpr auto DEFAULT_RECONNECTION_TIME = 2 * kj::SECONDS;\n  static constexpr uint32_t MIN_RECONNECTION_TIME = 1000;\n  static constexpr uint32_t MAX_RECONNECTION_TIME = 10 * 1000;\n\n  kj::Duration reconnectionTime = DEFAULT_RECONNECTION_TIME;\n\n  void notifyOpen(jsg::Lock& js);\n  void notifyError(jsg::Lock& js, const jsg::JsValue& error, bool reconnecting = false);\n  void notifyMessages(jsg::Lock& js, kj::Array<PendingMessage> messages);\n\n  // The run() method handles the actual processing of the stream.\n  void run(jsg::Lock& js,\n      jsg::Ref<ReadableStream> stream,\n      bool withReconnection = true,\n      kj::Maybe<jsg::Ref<Response>> response = kj::none,\n      kj::Maybe<jsg::Ref<Fetcher>> fetcher = kj::none);\n  // The start() method initializes the fetch and the processing of the\n  // stream by calling run.\n  void start(jsg::Lock& js);\n  void reconnect(jsg::Lock& js);\n};\n\n}  // namespace workerd::api\n\n#define EW_EVENTSOURCE_ISOLATE_TYPES api::EventSource, api::EventSource::EventSourceInit\n"
  },
  {
    "path": "src/workerd/api/export-loopback.c++",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"export-loopback.h\"\n\n#include <workerd/io/frankenvalue.h>\n\nnamespace workerd::api {\n\njsg::Ref<Fetcher> LoopbackServiceStub::callImpl(jsg::Lock& js,\n    jsg::Optional<jsg::JsRef<jsg::JsObject>> propsMaybe,\n    jsg::Optional<OptionsWithVersion::Version> versionMaybe) {\n  Frankenvalue props;\n  KJ_IF_SOME(p, propsMaybe) {\n    props = Frankenvalue::fromJs(js, p.getHandle(js));\n  }\n  kj::Maybe<IoChannelFactory::VersionRequest> versionRequest;\n  KJ_IF_SOME(version, kj::mv(versionMaybe)) {\n    versionRequest = IoChannelFactory::VersionRequest{\n      .cohort = kj::mv(version.cohort).orDefault(kj::none),\n    };\n  }\n\n  IoContext& ioctx = IoContext::current();\n  auto channelObj = ioctx.getIoChannelFactory().getSubrequestChannel(\n      channel, kj::mv(props), kj::mv(versionRequest));\n  return js.alloc<Fetcher>(ioctx.addObject(kj::mv(channelObj)));\n}\n\njsg::Ref<DurableObjectClass> LoopbackDurableObjectClass::call(jsg::Lock& js, Options options) {\n  Frankenvalue props;\n  KJ_IF_SOME(p, options.props) {\n    props = Frankenvalue::fromJs(js, p.getHandle(js));\n  }\n\n  IoContext& ioctx = IoContext::current();\n  auto channelObj = ioctx.getIoChannelFactory().getActorClass(channel, kj::mv(props));\n  return js.alloc<DurableObjectClass>(ioctx.addObject(kj::mv(channelObj)));\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/export-loopback.h",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"actor.h\"\n#include \"http.h\"\n\n#include <workerd/io/io-channels.h>\n\nnamespace workerd::api {\n\n// LoopbackServiceStub is the type of a property of `ctx.exports` which points back at a stateless\n// (non-actor) entrypoint of this Worker. It can be used as a regular Fetcher to make calls to that\n// entrypoint with empty props. It can also be invoked as a function in order to specialize it with\n// props and make it available for RPC.\nclass LoopbackServiceStub: public Fetcher {\n public:\n  // Loopback services are always represented by numbered subrequest channels.\n  explicit LoopbackServiceStub(uint channel)\n      : Fetcher(channel, RequiresHostAndProtocol::YES, /*isInHouse=*/true),\n        channel(channel) {}\n\n  struct Options {\n    jsg::Optional<jsg::JsRef<jsg::JsObject>> props;\n\n    JSG_STRUCT(props);\n  };\n\n  struct OptionsWithVersion {\n    struct Version {\n      jsg::Optional<kj::Maybe<kj::String>> cohort;\n\n      JSG_STRUCT(cohort);\n    };\n\n    jsg::Optional<jsg::JsRef<jsg::JsObject>> props;\n    jsg::Optional<Version> version;\n\n    JSG_STRUCT(props, version);\n  };\n\n  // Create a specialized Fetcher which can be passed over RPC.\n  jsg::Ref<Fetcher> callImpl(jsg::Lock& js,\n      jsg::Optional<jsg::JsRef<jsg::JsObject>> propsMaybe,\n      jsg::Optional<OptionsWithVersion::Version> versionMaybe);\n\n  jsg::Ref<Fetcher> call(jsg::Lock& js, Options options) {\n    return callImpl(js, kj::mv(options.props), kj::none);\n  }\n\n  jsg::Ref<Fetcher> callWithVersion(jsg::Lock& js, OptionsWithVersion options) {\n    return callImpl(js, kj::mv(options.props), kj::mv(options.version));\n  }\n\n  // Note that `LoopbackServiceStub` is intentionally NOT serializable, unlike its parent class\n  // Fetcher. We want people to explicitly specialize the entrypoint with props before sending\n  // it off to other services.\n\n  JSG_RESOURCE_TYPE(LoopbackServiceStub, CompatibilityFlags::Reader flags) {\n    JSG_INHERIT(Fetcher);\n\n    if (flags.getEnableVersionApi()) {\n      JSG_CALLABLE(callWithVersion);\n    } else {\n      JSG_CALLABLE(call);\n    }\n\n    JSG_TS_ROOT();\n\n    if (flags.getEnableVersionApi()) {\n      JSG_TS_OVERRIDE(\n        type LoopbackServiceStub<\n          T extends Rpc.WorkerEntrypointBranded | undefined = undefined\n        > = Fetcher<T> &\n          ( T extends CloudflareWorkersModule.WorkerEntrypoint<any, infer Props>\n          ? (opts: {props?: Props, version?: { cohort?: string | null }}) => Fetcher<T>\n          : (opts: {props?: any, version?: { cohort?: string | null }}) => Fetcher<T>);\n      );\n    } else {\n      JSG_TS_OVERRIDE(\n        type LoopbackServiceStub<\n          T extends Rpc.WorkerEntrypointBranded | undefined = undefined\n        > = Fetcher<T> &\n          ( T extends CloudflareWorkersModule.WorkerEntrypoint<any, infer Props>\n          ? (opts: {props?: Props}) => Fetcher<T>\n          : (opts: {props?: any}) => Fetcher<T>);\n      );\n    }\n\n    // LoopbackForExport takes the type of an exported value and evaluates to the appropriate\n    // loopback stub for that export.\n    JSG_TS_DEFINE(\n      type LoopbackForExport<\n        T extends\n          | (new (...args: any[]) => Rpc.EntrypointBranded)\n          | ExportedHandler<any, any, any>\n          | undefined = undefined\n      > = T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded ? LoopbackServiceStub<InstanceType<T>>\n        : T extends new (...args: any[]) => Rpc.DurableObjectBranded ? LoopbackDurableObjectClass<InstanceType<T>>\n        : T extends ExportedHandler<any, any, any> ? LoopbackServiceStub<undefined>\n        : undefined;\n    );\n  }\n\n private:\n  uint channel;\n};\n\n// Similar to LoopbackServiceStub, but for actor classes.\n//\n// Specifically, this is used for actor classes that do *not* have any storage configured. If you\n// simply export a class extending `DurableObject` but you don't configure storage for it, it shows\n// up in `ctx.exports` as this type. This can be used to create a Durable Object facet.\nclass LoopbackDurableObjectClass: public DurableObjectClass {\n public:\n  LoopbackDurableObjectClass(uint channel): DurableObjectClass(channel), channel(channel) {}\n\n  struct Options {\n    jsg::Optional<jsg::JsRef<jsg::JsObject>> props;\n\n    JSG_STRUCT(props);\n  };\n\n  // Create a specialized DurableObjectClass which can be passed over RPC.\n  jsg::Ref<DurableObjectClass> call(jsg::Lock& js, Options options);\n\n  JSG_RESOURCE_TYPE(LoopbackDurableObjectClass) {\n    JSG_INHERIT(DurableObjectClass);\n    JSG_CALLABLE(call);\n\n    JSG_TS_ROOT();\n    JSG_TS_OVERRIDE(\n      type LoopbackDurableObjectClass<\n        T extends\n          | Rpc.DurableObjectBranded\n          | undefined = undefined\n      > = DurableObjectClass<T> &\n        ( T extends CloudflareWorkersModule.DurableObject<any, infer Props>\n        ? (opts: {props?: Props}) => DurableObjectClass<T>\n        : (opts: {props?: any}) => DurableObjectClass<T>);\n    );\n  }\n\n private:\n  uint channel;\n};\n\n// LoopbackDurableObjectNamespace is similar to LoopbackDurableObjectClass, but used when the\n// class has storage configured. In this case, we want a binding that behaves *both* like a\n// LoopbackDurableObjectClass *and* like a DurableObjectNamespace binding. Easy enough, we'll\n// inherit DurableObjectNamespace, but also make the binding invokable as a function like\n// LoopbackDurableObjectClass.\nclass LoopbackDurableObjectNamespace: public DurableObjectNamespace {\n public:\n  LoopbackDurableObjectNamespace(uint nsChannel,\n      kj::Own<ActorIdFactory> idFactory,\n      jsg::Ref<LoopbackDurableObjectClass> loopbackClass)\n      : DurableObjectNamespace(nsChannel, kj::mv(idFactory)),\n        loopbackClass(kj::mv(loopbackClass)) {}\n\n  // getClass() accessor for use from C++ only.\n  LoopbackDurableObjectClass& getClass() {\n    return *loopbackClass.get();\n  }\n\n  // Invoking the binding creates a specialization of the class -- not the namespace.\n  jsg::Ref<DurableObjectClass> call(jsg::Lock& js, LoopbackDurableObjectClass::Options options) {\n    return loopbackClass->call(js, kj::mv(options));\n  }\n\n  // If `DurableObjectNamespace` ever becomes serializable, we actually don't want to block\n  // serialization here, the way we want to for `LoopbackDurableObjectClass`, because actually\n  // serializing the loopback namespace would mean serializing the namespace stub, *not* the\n  // class stub. They are different things, and you might want to serialize either one.\n\n  JSG_RESOURCE_TYPE(LoopbackDurableObjectNamespace) {\n    JSG_INHERIT(DurableObjectNamespace);\n    JSG_CALLABLE(call);\n  }\n\n private:\n  jsg::Ref<LoopbackDurableObjectClass> loopbackClass;\n};\n\n// Like LoopbackDurableObjectNamespace, but for colo-local (ephemeral) actor namespaces.\nclass LoopbackColoLocalActorNamespace: public ColoLocalActorNamespace {\n public:\n  LoopbackColoLocalActorNamespace(\n      uint nsChannel, jsg::Ref<LoopbackDurableObjectClass> loopbackClass)\n      : ColoLocalActorNamespace(nsChannel),\n        loopbackClass(kj::mv(loopbackClass)) {}\n\n  // getClass() accessor for use from C++ only.\n  LoopbackDurableObjectClass& getClass() {\n    return *loopbackClass.get();\n  }\n\n  // Invoking the binding creates a specialization of the class -- not the namespace.\n  jsg::Ref<DurableObjectClass> call(jsg::Lock& js, LoopbackDurableObjectClass::Options options) {\n    return loopbackClass->call(js, kj::mv(options));\n  }\n\n  JSG_RESOURCE_TYPE(LoopbackColoLocalActorNamespace) {\n    JSG_INHERIT(ColoLocalActorNamespace);\n    JSG_CALLABLE(call);\n  }\n\n private:\n  jsg::Ref<LoopbackDurableObjectClass> loopbackClass;\n};\n\n#define EW_EXPORT_LOOPBACK_ISOLATE_TYPES                                                           \\\n  api::LoopbackServiceStub, api::LoopbackServiceStub::Options,                                     \\\n      api::LoopbackServiceStub::OptionsWithVersion,                                                \\\n      api::LoopbackServiceStub::OptionsWithVersion::Version, api::LoopbackDurableObjectClass,      \\\n      api::LoopbackDurableObjectClass::Options, api::LoopbackDurableObjectNamespace,               \\\n      api::LoopbackColoLocalActorNamespace\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/filesystem.c++",
    "content": "#include \"filesystem.h\"\n\n#include \"blob.h\"\n#include \"url-standard.h\"\n#include \"url.h\"\n\n#include <workerd/api/node/exceptions.h>\n#include <workerd/api/streams/standard.h>\n#include <workerd/io/features.h>\n#include <workerd/jsg/setup.h>\n\nnamespace workerd::api {\n\n// =======================================================================================\n// Implementation of cloudflare-internal:filesystem in support of node:fs\n\nnamespace {\nstatic constexpr uint32_t kMax = kj::maxValue;\nconstexpr kj::StringPtr nameForFsType(FsType type) {\n  switch (type) {\n    case FsType::FILE:\n      return \"file\"_kj;\n    case FsType::DIRECTORY:\n      return \"directory\"_kj;\n    case FsType::SYMLINK:\n      return \"symlink\"_kj;\n  }\n  KJ_UNREACHABLE;\n}\n\n// A file path is passed to the C++ layer as a URL object. However, we have two different\n// implementations of URL in the system. This class wraps and abstracts over both of them.\nstruct NormalizedFilePath {\n  const jsg::Url url;\n\n  static jsg::Url normalize(FileSystemModule::FilePath path) {\n    KJ_SWITCH_ONEOF(path) {\n      KJ_CASE_ONEOF(legacy, jsg::Ref<URL>) {\n        auto parsed = JSG_REQUIRE_NONNULL(\n            jsg::Url::tryParse(legacy->getHref(), \"file:///\"_kj), Error, \"Invalid URL\"_kj);\n        // The cloning here is necessary to de-percent-encode characters in the\n        // path that don't need to be percent-encoded, allowing us to treat equivalent\n        // encodings of the same path as equal. For instance, '/foo' and '/%66oo' should\n        // be considered the same path since 'f' and '%66' are equivalent. Importantly,\n        // this retains percent-encoding on characters that do need to be percent-encoded\n        // to be valid in URLs, such as non-ASCII characters.\n        return parsed.clone(jsg::Url::EquivalenceOption::NORMALIZE_PATH);\n      }\n      KJ_CASE_ONEOF(standard, jsg::Ref<url::URL>) {\n        jsg::Url url = *standard;\n        return url.clone(jsg::Url::EquivalenceOption::NORMALIZE_PATH);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  NormalizedFilePath(FileSystemModule::FilePath path): url(normalize(kj::mv(path))) {\n    validate();\n  }\n\n  void validate() {\n    JSG_REQUIRE(url.getProtocol() == \"file:\"_kj, TypeError, \"File path must be a file: URL\");\n    JSG_REQUIRE(url.getHost().size() == 0, Error, \"File path must not have a host\");\n\n    // Let's count the number of path segments in the URL. We want to make sure that\n    // it does not have more than 48 segments. Why 48? Great question! It's a completely\n    // arbitrary limit that we set to prevent excessively long paths that could lead to\n    // performance issues.\n    // We also limit the maximum total length of the path to 4096 characters.\n    auto pathname = url.getPathname();\n    JSG_REQUIRE(pathname.size() <= 4096, Error, \"File path is too long\"_kj);\n\n    uint32_t segmentCount = 0;\n    for (auto i: kj::indices(pathname)) {\n      if (pathname[i] == '/') {\n        segmentCount++;\n      }\n    }\n    JSG_REQUIRE(segmentCount <= 48, Error, \"File path has too many segments\"_kj);\n  }\n\n  operator const jsg::Url&() const {\n    return url;\n  }\n\n  operator kj::Path() const {\n    auto path = kj::str(url.getPathname().slice(1));\n    kj::Path root{};\n    return root.eval(path);\n  }\n};\n\n[[noreturn]] void throwFsError(\n    jsg::Lock& js, workerd::FsError error, kj::StringPtr syscall, kj::StringPtr path = nullptr) {\n  switch (error) {\n    case workerd::FsError::NOT_DIRECTORY: {\n      node::THROW_ERR_UV_ENOTDIR(js, syscall, nullptr, path);\n    }\n    case workerd::FsError::NOT_EMPTY: {\n      node::THROW_ERR_UV_ENOTEMPTY(js, syscall, nullptr, path);\n    }\n    case workerd::FsError::READ_ONLY: {\n      node::THROW_ERR_UV_EPERM(js, syscall, nullptr, path);\n    }\n    case workerd::FsError::TOO_MANY_OPEN_FILES: {\n      node::THROW_ERR_UV_EMFILE(js, syscall, nullptr, path);\n    }\n    case workerd::FsError::ALREADY_EXISTS: {\n      node::THROW_ERR_UV_EEXIST(js, syscall, nullptr, path);\n    }\n    case workerd::FsError::NOT_SUPPORTED: {\n      node::THROW_ERR_UV_ENOSYS(js, syscall, nullptr, path);\n    }\n    case workerd::FsError::NOT_PERMITTED: {\n      node::THROW_ERR_UV_EPERM(js, syscall, nullptr, path);\n    }\n    case workerd::FsError::NOT_PERMITTED_ON_DIRECTORY: {\n      node::THROW_ERR_UV_EISDIR(js, syscall, nullptr, path);\n    }\n    case workerd::FsError::FAILED: {\n      node::THROW_ERR_UV_EIO(js, syscall, nullptr, path);\n    }\n    case workerd::FsError::INVALID_PATH: {\n      node::THROW_ERR_UV_EINVAL(js, syscall, \"Invalid path\"_kj, path);\n    }\n    case workerd::FsError::FILE_SIZE_LIMIT_EXCEEDED: {\n      node::THROW_ERR_UV_EPERM(js, syscall, \"File size limit exceeded\"_kj, path);\n    }\n    case workerd::FsError::SYMLINK_DEPTH_EXCEEDED: {\n      node::THROW_ERR_UV_ELOOP(js, syscall, \"symlink depth exceeded\"_kj, path);\n    }\n    default: {\n      node::THROW_ERR_UV_EPERM(js, syscall, nullptr, path);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n}  // namespace\n\nStat::Stat(const workerd::Stat& stat)\n    : type(nameForFsType(stat.type)),\n      size(stat.size),\n      lastModified((stat.lastModified - kj::UNIX_EPOCH) / kj::NANOSECONDS),\n      created((stat.created - kj::UNIX_EPOCH) / kj::NANOSECONDS),\n      writable(stat.writable),\n      device(stat.device) {}\n\nkj::Maybe<Stat> FileSystemModule::stat(\n    jsg::Lock& js, kj::OneOf<int, FilePath> pathOrFd, StatOptions options) {\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n  KJ_SWITCH_ONEOF(pathOrFd) {\n    KJ_CASE_ONEOF(path, FilePath) {\n      NormalizedFilePath normalizedPath(kj::mv(path));\n      KJ_IF_SOME(node,\n          vfs.resolve(\n              js, normalizedPath, {.followLinks = options.followSymlinks.orDefault(true)})) {\n        KJ_SWITCH_ONEOF(node) {\n          KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n            return Stat(file->stat(js));\n          }\n          KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n            return Stat(dir->stat(js));\n          }\n          KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n            // If a symbolic link is returned here then the options.followSymLinks\n            // must have been set to false.\n            return Stat(link->stat(js));\n          }\n          KJ_CASE_ONEOF(err, workerd::FsError) {\n            // If we got here, then the path was not found.\n            throwFsError(js, err, \"stat\"_kj);\n          }\n        }\n        KJ_UNREACHABLE;\n      }\n    }\n    KJ_CASE_ONEOF(fd, int) {\n      KJ_IF_SOME(opened, vfs.tryGetFd(js, fd)) {\n        KJ_SWITCH_ONEOF(opened->node) {\n          KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n            return Stat(file->stat(js));\n          }\n          KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n            return Stat(dir->stat(js));\n          }\n          KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n            return Stat(link->stat(js));\n          }\n        }\n        KJ_UNREACHABLE;\n      } else {\n        node::THROW_ERR_UV_EBADF(js, \"fstat\"_kj);\n      }\n    }\n  }\n  return kj::none;\n}\n\nvoid FileSystemModule::setLastModified(\n    jsg::Lock& js, kj::OneOf<int, FilePath> pathOrFd, kj::Date lastModified, StatOptions options) {\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n  KJ_SWITCH_ONEOF(pathOrFd) {\n    KJ_CASE_ONEOF(path, FilePath) {\n      NormalizedFilePath normalizedPath(kj::mv(path));\n      KJ_IF_SOME(node,\n          vfs.resolve(\n              js, normalizedPath, {.followLinks = options.followSymlinks.orDefault(true)})) {\n        KJ_SWITCH_ONEOF(node) {\n          KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n            KJ_IF_SOME(err, file->setLastModified(js, lastModified)) {\n              // If we got here, then the file is read-only.\n              throwFsError(js, err, \"futimes\"_kj);\n            }\n            return;\n          }\n          KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n            // Do nothing\n            return;\n          }\n          KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n            // If we got here, then followSymLinks was set to false. We cannot\n            // change the last modified time of a symbolic link in our vfs so\n            // we do nothing.\n            return;\n          }\n          KJ_CASE_ONEOF(err, workerd::FsError) {\n            // If we got here, then the path was not found.\n            throwFsError(js, err, \"futimes\"_kj);\n          }\n        }\n      } else {\n        node::THROW_ERR_UV_ENOENT(\n            js, \"utimes\"_kj, nullptr, kj::str(normalizedPath.url.getPathname()));\n      }\n    }\n    KJ_CASE_ONEOF(fd, int) {\n      KJ_IF_SOME(opened, vfs.tryGetFd(js, fd)) {\n        KJ_SWITCH_ONEOF(opened->node) {\n          KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n            KJ_IF_SOME(err, file->setLastModified(js, lastModified)) {\n              throwFsError(js, err, \"futimes\"_kj);\n            }\n            return;\n          }\n          KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n            // Do nothing\n            return;\n          }\n          KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n            // Do nothing\n            return;\n          }\n        }\n        KJ_UNREACHABLE;\n      } else {\n        node::THROW_ERR_UV_EBADF(js, \"futimes\"_kj);\n      }\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid FileSystemModule::truncate(jsg::Lock& js, kj::OneOf<int, FilePath> pathOrFd, uint32_t size) {\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n  KJ_SWITCH_ONEOF(pathOrFd) {\n    KJ_CASE_ONEOF(path, FilePath) {\n      NormalizedFilePath normalizedPath(kj::mv(path));\n      KJ_IF_SOME(node, vfs.resolve(js, normalizedPath)) {\n        KJ_SWITCH_ONEOF(node) {\n          KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n            KJ_IF_SOME(err, file->resize(js, size)) {\n              throwFsError(js, err, \"ftruncate\"_kj);\n            }\n            return;\n          }\n          KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n            node::THROW_ERR_UV_EISDIR(js, \"ftruncate\"_kj);\n          }\n          KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n            // If we got here, then followSymLinks was set to false. We cannot\n            // truncate a symbolic link.\n            node::THROW_ERR_UV_EINVAL(js, \"ftruncate\"_kj);\n          }\n          KJ_CASE_ONEOF(err, workerd::FsError) {\n            // If we got here, then the path was not found.\n            throwFsError(js, err, \"ftruncate\"_kj);\n          }\n        }\n      } else {\n        node::THROW_ERR_UV_ENOENT(\n            js, \"ftruncate\"_kj, nullptr, kj::str(normalizedPath.url.getPathname()));\n      }\n    }\n    KJ_CASE_ONEOF(fd, int) {\n      KJ_IF_SOME(opened, vfs.tryGetFd(js, fd)) {\n        KJ_SWITCH_ONEOF(opened->node) {\n          KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n            KJ_IF_SOME(err, file->resize(js, size)) {\n              throwFsError(js, err, \"ftruncate\"_kj);\n            }\n            return;\n          }\n          KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n            node::THROW_ERR_UV_EISDIR(js, \"ftruncate\"_kj);\n          }\n          KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n            node::THROW_ERR_UV_EINVAL(js, \"ftruncate\"_kj);\n          }\n        }\n        KJ_UNREACHABLE;\n      } else {\n        node::THROW_ERR_UV_EBADF(js, \"ftruncate\"_kj);\n      }\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::String FileSystemModule::readLink(jsg::Lock& js, FilePath path, ReadLinkOptions options) {\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n  NormalizedFilePath normalizedPath(kj::mv(path));\n  KJ_IF_SOME(node, vfs.resolve(js, normalizedPath, {.followLinks = false})) {\n    KJ_SWITCH_ONEOF(node) {\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        if (options.failIfNotSymlink) {\n          node::THROW_ERR_UV_EINVAL(js, \"readlink\"_kj);\n        }\n        kj::Path path = normalizedPath;\n        return path.toString(true);\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        if (options.failIfNotSymlink) {\n          node::THROW_ERR_UV_EINVAL(js, \"readlink\"_kj);\n        }\n        kj::Path path = normalizedPath;\n        return path.toString(true);\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        return link->getTargetPath().toString(true);\n      }\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        // If we got here, then the path was not found.\n        throwFsError(js, err, \"readlink\"_kj, kj::str(normalizedPath.url.getPathname()));\n      }\n    }\n    KJ_UNREACHABLE;\n  } else {\n    node::THROW_ERR_UV_ENOENT(\n        js, \"readlink\"_kj, nullptr, kj::str(normalizedPath.url.getPathname()));\n  }\n}\n\nvoid FileSystemModule::link(jsg::Lock& js, FilePath from, FilePath to, LinkOptions options) {\n  // The from argument is where we are creating the link, while the to is the target.\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n  NormalizedFilePath normalizedFrom(kj::mv(from));\n  NormalizedFilePath normalizedTo(kj::mv(to));\n\n  // First, let's make sure the destination (from) does not already exist.\n  const jsg::Url& fromUrl = normalizedFrom;\n  const jsg::Url& toUrl = normalizedTo;\n\n  KJ_IF_SOME(maybeNode, vfs.resolve(js, fromUrl)) {\n    KJ_IF_SOME(err, maybeNode.tryGet<workerd::FsError>()) {\n      throwFsError(js, err, \"link\"_kj);\n    }\n    // If we got here, then the destination already exists.\n    node::THROW_ERR_UV_EEXIST(js, \"link\"_kj, \"File already exists\"_kj);\n  }\n\n  // Now, let's split the fromUrl into a base directory URL and a file name so\n  // that we can make sure the destination directory exists.\n  jsg::Url::Relative fromRelative = fromUrl.getRelative();\n\n  if (fromRelative.name.size() == 0) {\n    node::THROW_ERR_UV_EINVAL(js, \"link\"_kj, \"Invalid filename\"_kj);\n  }\n\n  KJ_IF_SOME(parent, vfs.resolve(js, fromRelative.base)) {\n    KJ_IF_SOME(dir, parent.tryGet<kj::Rc<workerd::Directory>>()) {\n      // Dir is where the new link will go. fromRelative.name is the name of\n      // the new link in this directory.\n\n      // If we are creating a symbolic link, we do not need to check if the target exists.\n      if (options.symbolic) {\n        KJ_IF_SOME(err, dir->add(js, fromRelative.name, vfs.newSymbolicLink(js, toUrl))) {\n          throwFsError(js, err, \"link\"_kj);\n        }\n        return;\n      }\n\n      // If we are creating a hard link, however, the target must exist.\n      KJ_IF_SOME(target, vfs.resolve(js, toUrl, {.followLinks = false})) {\n        KJ_SWITCH_ONEOF(target) {\n          KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n            KJ_IF_SOME(err, dir->add(js, fromRelative.name, file.addRef())) {\n              throwFsError(js, err, \"link\"_kj);\n            }\n          }\n          KJ_CASE_ONEOF(tdir, kj::Rc<workerd::Directory>) {\n            // It is not permitted to hardlink to a directory.\n            node::THROW_ERR_UV_EPERM(js, \"link\"_kj, \"Cannot hardlink to a directory\"_kj);\n          }\n          KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n            KJ_IF_SOME(err, dir->add(js, fromRelative.name, link.addRef())) {\n              throwFsError(js, err, \"link\"_kj);\n            }\n          }\n          KJ_CASE_ONEOF(err, workerd::FsError) {\n            // If we got here, then the target path was not found.\n            throwFsError(js, err, \"link\"_kj);\n          }\n        }\n      } else {\n        node::THROW_ERR_UV_ENOENT(js, \"link\"_kj, \"File not found\"_kj, kj::str(toUrl.getPathname()));\n      }\n    } else {\n      node::THROW_ERR_UV_EINVAL(js, \"link\"_kj, \"Not a directory\"_kj);\n    }\n  } else {\n    node::THROW_ERR_UV_ENOENT(\n        js, \"link\"_kj, \"Directory does not exist\"_kj, kj::str(toUrl.getPathname()));\n  }\n}\n\nvoid FileSystemModule::unlink(jsg::Lock& js, FilePath path) {\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n  NormalizedFilePath normalizedPath(kj::mv(path));\n  const jsg::Url& url = normalizedPath;\n  auto relative = url.getRelative();\n\n  KJ_IF_SOME(parent, vfs.resolve(js, relative.base)) {\n    KJ_IF_SOME(dir, parent.tryGet<kj::Rc<workerd::Directory>>()) {\n      kj::Path fpath(relative.name);\n      KJ_IF_SOME(stat, dir->stat(js, fpath)) {\n        KJ_SWITCH_ONEOF(stat) {\n          KJ_CASE_ONEOF(stat, workerd::Stat) {\n            if (stat.type == FsType::DIRECTORY) {\n              node::THROW_ERR_UV_EISDIR(js, \"unlink\"_kj, \"Cannot unlink a directory\"_kj);\n            }\n          }\n          KJ_CASE_ONEOF(err, workerd::FsError) {\n            throwFsError(js, err, \"unlink\"_kj);\n          }\n        }\n      } else {\n        node::THROW_ERR_UV_ENOENT(js, \"unlink\"_kj, \"File not found\"_kj, relative.name);\n      }\n\n      KJ_SWITCH_ONEOF(dir->remove(js, fpath)) {\n        KJ_CASE_ONEOF(res, bool) {\n          // Ignore the return.\n        }\n        KJ_CASE_ONEOF(err, workerd::FsError) {\n          throwFsError(js, err, \"unlink\"_kj);\n        }\n      }\n    } else {\n      node::THROW_ERR_UV_ENOTDIR(js, \"unlink\"_kj, \"Parent path is not a directory\"_kj);\n    }\n  } else {\n    node::THROW_ERR_UV_ENOENT(js, \"unlink\"_kj, \"File not found\"_kj, relative.name);\n  }\n}\n\nint FileSystemModule::open(jsg::Lock& js, FilePath path, OpenOptions options) {\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n  NormalizedFilePath normalizedPath(kj::mv(path));\n  KJ_SWITCH_ONEOF(vfs.openFd(js, normalizedPath,\n                      workerd::VirtualFileSystem::OpenOptions{\n                        .read = options.read,\n                        .write = options.write,\n                        .append = options.append,\n                        .exclusive = options.exclusive,\n                        .followLinks = options.followSymlinks,\n                      })) {\n    KJ_CASE_ONEOF(opened, kj::Rc<workerd::VirtualFileSystem::OpenedFile>) {\n      return opened->fd;\n    }\n    KJ_CASE_ONEOF(err, workerd::FsError) {\n      throwFsError(js, err, \"open\"_kj);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid FileSystemModule::close(jsg::Lock& js, int fd) {\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n  vfs.closeFd(js, fd);\n}\n\nuint32_t FileSystemModule::write(\n    jsg::Lock& js, int fd, kj::Array<jsg::BufferSource> data, WriteOptions options) {\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n\n  KJ_IF_SOME(opened, vfs.tryGetFd(js, fd)) {\n    static const auto getPosition = [](jsg::Lock& js, auto opened, auto file,\n                                        const WriteOptions& options) -> uint32_t {\n      if (opened->append) {\n        // If the file descriptor is opened in append mode, we ignore the position\n        // option and always append to the end of the file.\n        auto stat = file->stat(js);\n        return stat.size;\n      }\n      auto pos = options.position.orDefault(opened->position);\n      if (pos > kMax) {\n        node::THROW_ERR_UV_EINVAL(js, \"write\"_kj, \"position out of range\"_kj);\n      }\n      return static_cast<uint32_t>(pos);\n    };\n    KJ_SWITCH_ONEOF(opened->node) {\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        auto pos = getPosition(js, opened.addRef(), file.addRef(), options);\n        uint32_t total = 0;\n        for (auto& buffer: data) {\n          KJ_SWITCH_ONEOF(file->write(js, pos, buffer)) {\n            KJ_CASE_ONEOF(written, uint32_t) {\n              pos += written;\n              total += written;\n            }\n            KJ_CASE_ONEOF(err, workerd::FsError) {\n              throwFsError(js, err, \"write\"_kj);\n            }\n          }\n        }\n        // We only update the position if the options.position is not set and\n        // the file descriptor is not opened in append mode.\n        if (options.position == kj::none && !opened->append) {\n          opened->position += total;\n        }\n        return total;\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        node::THROW_ERR_UV_EISDIR(js, \"write\"_kj);\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        // If we get here, then followSymLinks was set to false when open was called.\n        // We can't write to a symbolic link.\n        node::THROW_ERR_UV_EINVAL(js, \"write\"_kj);\n      }\n    }\n    KJ_UNREACHABLE;\n  } else {\n    node::THROW_ERR_UV_EBADF(js, \"write\"_kj);\n  }\n}\n\nuint32_t FileSystemModule::read(\n    jsg::Lock& js, int fd, kj::Array<jsg::BufferSource> data, WriteOptions options) {\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n  KJ_IF_SOME(opened, vfs.tryGetFd(js, fd)) {\n    if (!opened->read) {\n      node::THROW_ERR_UV_EBADF(js, \"read\"_kj);\n    }\n\n    KJ_SWITCH_ONEOF(opened->node) {\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        auto pos = options.position.orDefault(opened->position);\n        if (pos > kMax) {\n          node::THROW_ERR_UV_EINVAL(js, \"read\"_kj, \"position out of range\"_kj);\n        }\n        uint32_t total = 0;\n        for (auto& buffer: data) {\n          auto read = file->read(js, pos, buffer);\n          // if read is less than the size of the buffer, we are at EOF.\n          pos += read;\n          total += read;\n          if (read < buffer.size()) break;\n        }\n        // We only update the position if the options.position is not set.\n        if (options.position == kj::none) {\n          opened->position += total;\n        }\n        return total;\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        node::THROW_ERR_UV_EISDIR(js, \"read\"_kj);\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        // If we get here, then followSymLinks was set to false when open was called.\n        // We can't read from a symbolic link.\n        node::THROW_ERR_UV_EINVAL(js, \"read\"_kj);\n      }\n    }\n    KJ_UNREACHABLE;\n  } else {\n    node::THROW_ERR_UV_EBADF(js, \"read\"_kj);\n  }\n}\n\njsg::BufferSource FileSystemModule::readAll(jsg::Lock& js, kj::OneOf<int, FilePath> pathOrFd) {\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n  KJ_SWITCH_ONEOF(pathOrFd) {\n    KJ_CASE_ONEOF(path, FilePath) {\n      NormalizedFilePath normalized(kj::mv(path));\n      KJ_IF_SOME(node, vfs.resolve(js, normalized)) {\n        KJ_SWITCH_ONEOF(node) {\n          KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n            KJ_SWITCH_ONEOF(file->readAllBytes(js)) {\n              KJ_CASE_ONEOF(data, jsg::BufferSource) {\n                return kj::mv(data);\n              }\n              KJ_CASE_ONEOF(err, workerd::FsError) {\n                throwFsError(js, err, \"readAll\"_kj);\n              }\n            }\n            KJ_UNREACHABLE;\n          }\n          KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n            node::THROW_ERR_UV_EISDIR(js, \"readAll\"_kj);\n          }\n          KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n            // We shouldn't be able to get here since we are following symlinks.\n            KJ_UNREACHABLE;\n          }\n          KJ_CASE_ONEOF(err, workerd::FsError) {\n            throwFsError(js, err, \"readAll\"_kj);\n          }\n        }\n      } else {\n        node::THROW_ERR_UV_ENOENT(js, \"readAll\"_kj, nullptr, kj::str(normalized.url.getPathname()));\n      }\n    }\n    KJ_CASE_ONEOF(fd, int) {\n      KJ_IF_SOME(opened, vfs.tryGetFd(js, fd)) {\n        if (!opened->read) {\n          node::THROW_ERR_UV_EBADF(js, \"fread\"_kj);\n        }\n\n        KJ_IF_SOME(file, opened->node.tryGet<kj::Rc<workerd::File>>()) {\n          // Move the opened.position to the end of the file.\n          KJ_DEFER({\n            auto stat = file->stat(js);\n            opened->position = stat.size;\n          });\n\n          KJ_SWITCH_ONEOF(file->readAllBytes(js)) {\n            KJ_CASE_ONEOF(data, jsg::BufferSource) {\n              return kj::mv(data);\n            }\n            KJ_CASE_ONEOF(err, workerd::FsError) {\n              throwFsError(js, err, \"freadAll\"_kj);\n            }\n          }\n          KJ_UNREACHABLE;\n        } else {\n          node::THROW_ERR_UV_EBADF(js, \"fread\"_kj);\n        }\n      } else {\n        node::THROW_ERR_UV_EBADF(js, \"fread\"_kj);\n      }\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nuint32_t FileSystemModule::writeAll(jsg::Lock& js,\n    kj::OneOf<int, FilePath> pathOrFd,\n    jsg::BufferSource data,\n    WriteAllOptions options) {\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n\n  if (data.size() > kMax) {\n    node::THROW_ERR_UV_EFBIG(js, \"writeAll\"_kj);\n  }\n\n  KJ_SWITCH_ONEOF(pathOrFd) {\n    KJ_CASE_ONEOF(path, FilePath) {\n      NormalizedFilePath normalized(kj::mv(path));\n      KJ_IF_SOME(node, vfs.resolve(js, normalized)) {\n        // If the exclusive option is set, the file must not already exist.\n        if (options.exclusive) {\n          node::THROW_ERR_UV_EEXIST(js, \"writeAll\"_kj, \"file already exists\"_kj);\n        }\n        // The file exists, we can write to it.\n        KJ_SWITCH_ONEOF(node) {\n          KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n            // First let's check that the file is writable.\n            auto stat = file->stat(js);\n            if (!stat.writable) {\n              node::THROW_ERR_UV_EPERM(js, \"writeAll\"_kj);\n            }\n\n            // If the append option is set, we will write to the end of the file\n            // instead of overwriting it.\n            if (options.append) {\n              KJ_SWITCH_ONEOF(file->write(js, stat.size, data)) {\n                KJ_CASE_ONEOF(written, uint32_t) {\n                  return written;\n                }\n                KJ_CASE_ONEOF(err, workerd::FsError) {\n                  throwFsError(js, err, \"writeAll\"_kj);\n                }\n              }\n              KJ_UNREACHABLE;\n            }\n\n            // Otherwise, we overwrite the entire file.\n            KJ_SWITCH_ONEOF(file->writeAll(js, data)) {\n              KJ_CASE_ONEOF(written, uint32_t) {\n                return written;\n              }\n              KJ_CASE_ONEOF(err, workerd::FsError) {\n                throwFsError(js, err, \"writeAll\"_kj);\n              }\n            }\n            KJ_UNREACHABLE;\n          }\n          KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n            node::THROW_ERR_UV_EISDIR(js, \"writeAll\"_kj);\n          }\n          KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n            // If we get here, then followSymLinks was set to false when open was called.\n            // We can't write to a symbolic link.\n            node::THROW_ERR_UV_EINVAL(js, \"writeAll\"_kj);\n          }\n          KJ_CASE_ONEOF(err, workerd::FsError) {\n            throwFsError(js, err, \"writeAll\"_kj);\n          }\n        }\n        KJ_UNREACHABLE;\n      }\n      // The file does not exist. We first need to create it, then write to it.\n      // Let's make sure the parent directory exists.\n      const jsg::Url& url = normalized;\n      jsg::Url::Relative relative = url.getRelative();\n\n      KJ_IF_SOME(parent, vfs.resolve(js, relative.base)) {\n        // Let's make sure the parent is a directory.\n        KJ_SWITCH_ONEOF(parent) {\n          KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n            node::THROW_ERR_UV_ENOTDIR(js, \"writeAll\"_kj);\n          }\n          KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n            auto stat = dir->stat(js);\n            if (!stat.writable) {\n              node::THROW_ERR_UV_EPERM(js, \"writeAll\"_kj);\n            }\n            auto file = workerd::File::newWritable(js, static_cast<uint32_t>(data.size()));\n            KJ_SWITCH_ONEOF(file->writeAll(js, data)) {\n              KJ_CASE_ONEOF(written, uint32_t) {\n                KJ_IF_SOME(err, dir->add(js, relative.name, kj::mv(file))) {\n                  throwFsError(js, err, \"writeAll\"_kj);\n                }\n                return written;\n              }\n              KJ_CASE_ONEOF(err, workerd::FsError) {\n                throwFsError(js, err, \"writeAll\"_kj);\n              }\n            }\n          }\n          KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n            // If we get here, then followSymLinks was set to false when open was called.\n            // We can't write to a symbolic link.\n            node::THROW_ERR_UV_EINVAL(js, \"writeAll\"_kj);\n          }\n          KJ_CASE_ONEOF(err, workerd::FsError) {\n            // If we got here, then the parent path was not found.\n            throwFsError(js, err, \"writeAll\"_kj);\n          }\n        }\n        KJ_UNREACHABLE;\n      } else {\n        node::THROW_ERR_UV_ENOENT(\n            js, \"writeAll\"_kj, nullptr, kj::str(normalized.url.getPathname()));\n      }\n    }\n    KJ_CASE_ONEOF(fd, int) {\n      KJ_IF_SOME(opened, vfs.tryGetFd(js, fd)) {\n        // Otherwise, we'll overwrite the file...\n        if (!opened->write) {\n          node::THROW_ERR_UV_EBADF(js, \"fwrite\"_kj);\n        }\n\n        KJ_IF_SOME(file, opened->node.tryGet<kj::Rc<workerd::File>>()) {\n          auto stat = file->stat(js);\n\n          if (!stat.writable) {\n            node::THROW_ERR_UV_EPERM(js, \"fwrite\"_kj);\n          }\n\n          KJ_DEFER({\n            // In either case, we need to update the position of the file descriptor.\n            stat = file->stat(js);\n            opened->position = stat.size;\n          });\n\n          // If the file descriptor was opened in append mode, or if the append option\n          // is set, then we'll use write instead to append to the end of the file.\n          if (opened->append || options.append) {\n            return write(js, fd, kj::arr(kj::mv(data)),\n                {\n                  .position = stat.size,\n                });\n          }\n\n          // Otherwise, we overwrite the entire file.\n          KJ_SWITCH_ONEOF(file->writeAll(js, data)) {\n            KJ_CASE_ONEOF(written, uint32_t) {\n              return written;\n            }\n            KJ_CASE_ONEOF(err, workerd::FsError) {\n              throwFsError(js, err, \"fwriteAll\"_kj);\n            }\n          }\n          KJ_UNREACHABLE;\n        } else {\n          node::THROW_ERR_UV_EBADF(js, \"fwrite\"_kj);\n        }\n      } else {\n        node::THROW_ERR_UV_EBADF(js, \"fwrite\"_kj);\n      }\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\nvoid FileSystemModule::renameOrCopy(\n    jsg::Lock& js, FilePath src, FilePath dest, RenameOrCopyOptions options) {\n  // The source must exist, the destination must not.\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n  NormalizedFilePath normalizedSrc(kj::mv(src));\n  NormalizedFilePath normalizedDest(kj::mv(dest));\n\n  const jsg::Url& destUrl = normalizedDest;\n  const jsg::Url& srcUrl = normalizedSrc;\n\n  auto opName = options.copy ? \"copy\"_kj : \"rename\"_kj;\n\n  KJ_IF_SOME(maybeDestNode, vfs.resolve(js, destUrl)) {\n    KJ_IF_SOME(err, maybeDestNode.tryGet<workerd::FsError>()) {\n      throwFsError(js, err, \"rename\"_kj);\n    }\n    node::THROW_ERR_UV_EEXIST(js, opName);\n  }\n\n  jsg::Url::Relative relative = destUrl.getRelative();\n  // The destination parent must exist.\n  KJ_IF_SOME(parent, vfs.resolve(js, relative.base)) {\n    KJ_SWITCH_ONEOF(parent) {\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        node::THROW_ERR_UV_ENOTDIR(js, opName);\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        kj::Maybe<kj::Rc<Directory>> srcParent;\n        if (!options.copy) {\n          // If we are not copying, let's make sure that the source directory is writable\n          // before we actually try moving it.\n          auto relative = srcUrl.getRelative();\n          KJ_IF_SOME(parent, vfs.resolve(js, relative.base)) {\n            KJ_SWITCH_ONEOF(parent) {\n              KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n                node::THROW_ERR_UV_ENOTDIR(js, opName);\n              }\n              KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n                // We can only rename a file or directory if the parent is writable.\n                // If the parent is not writable, we throw an error.\n                auto stat = dir->stat(js);\n                if (!stat.writable) {\n                  node::THROW_ERR_UV_EPERM(js, opName);\n                }\n                srcParent = dir.addRef();\n              }\n              KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n                node::THROW_ERR_UV_ENOTDIR(js, opName);\n              }\n              KJ_CASE_ONEOF(err, workerd::FsError) {\n                // If we got here, then the parent path was not found.\n                throwFsError(js, err, opName);\n              }\n            }\n          } else {\n            node::THROW_ERR_UV_ENOENT(js, opName, nullptr, relative.name);\n          }\n        }\n\n        KJ_IF_SOME(srcNode, vfs.resolve(js, normalizedSrc)) {\n          // The next part is easy. We either clone or add ref the original node and add it to the\n          // destination directory.\n          KJ_SWITCH_ONEOF(srcNode) {\n            KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n              kj::OneOf<workerd::FsError, kj::Rc<workerd::File>> errOrFile =\n                  options.copy ? file->clone(js) : file.addRef();\n              KJ_SWITCH_ONEOF(errOrFile) {\n                KJ_CASE_ONEOF(err, workerd::FsError) {\n                  throwFsError(js, err, \"cp\"_kj);\n                }\n                KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n                  KJ_IF_SOME(err, dir->add(js, relative.name, kj::mv(file))) {\n                    throwFsError(js, err, opName);\n                  }\n                }\n              }\n            }\n            KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n              if (options.copy) {\n                node::THROW_ERR_UV_EISDIR(js, opName);\n              }\n              KJ_IF_SOME(err, dir->add(js, relative.name, dir.addRef())) {\n                throwFsError(js, err, opName);\n              }\n            }\n            KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n              KJ_IF_SOME(err, dir->add(js, relative.name, link.addRef())) {\n                throwFsError(js, err, opName);\n              }\n            }\n            KJ_CASE_ONEOF(err, workerd::FsError) {\n              throwFsError(js, err, opName);\n            }\n          }\n\n          KJ_IF_SOME(dir, srcParent) {\n            auto relative = srcUrl.getRelative();\n            KJ_SWITCH_ONEOF(dir->remove(js, kj::Path({relative.name}), {.recursive = true})) {\n              KJ_CASE_ONEOF(_, bool) {\n                // ignore the specific return value.\n                return;\n              }\n              KJ_CASE_ONEOF(err, workerd::FsError) {\n                throwFsError(js, err, \"rename\"_kj);\n              }\n            }\n            KJ_UNREACHABLE;\n          }\n        } else {\n          node::THROW_ERR_UV_ENOENT(js, opName, nullptr, kj::str(normalizedSrc.url.getPathname()));\n        }\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        node::THROW_ERR_UV_ENOTDIR(js, opName);\n      }\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        // If we got here, then the parent path was not found.\n        throwFsError(js, err, opName);\n      }\n    }\n  } else {\n    node::THROW_ERR_UV_ENOENT(js, opName, nullptr, relative.name);\n  }\n}\n\njsg::Optional<kj::String> FileSystemModule::mkdir(\n    jsg::Lock& js, FilePath path, MkdirOptions options) {\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n  NormalizedFilePath normalizedPath(kj::mv(path));\n  const jsg::Url& url = normalizedPath;\n\n  // The path must not already exist. However, if the path is a directory, we\n  // will just return rather than throwing an error.\n  KJ_IF_SOME(node, vfs.resolve(js, url, {.followLinks = false})) {\n    KJ_SWITCH_ONEOF(node) {\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        node::THROW_ERR_UV_EEXIST(js, \"mkdir\"_kj);\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        // The directory already exists. We will just return.\n        return kj::none;\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        node::THROW_ERR_UV_EEXIST(js, \"mkdir\"_kj);\n      }\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        throwFsError(js, err, \"mkdir\"_kj);\n      }\n    }\n    KJ_UNREACHABLE;\n  };\n\n  if (options.recursive) {\n    KJ_ASSERT(!options.tmp);\n    // If the recursive option is set, we will create all the directories in the\n    // path that do not exist, returning the path to the first one that was created.\n    const kj::Path kjPath = normalizedPath;\n    const auto parentPath = kjPath.parent();\n    const auto name = kjPath.basename();\n    kj::Maybe<kj::String> createdPath;\n\n    // We'll start from the root and work our way down.\n    auto current = vfs.getRoot(js);\n    kj::Path currentPath{};\n    for (const auto& part: parentPath) {\n      currentPath = currentPath.append(part);\n      bool moveToNext = false;\n      // Try opening the next part of the path. Note that we are not using the\n      // createAs option here because we don't necessarily want to implicitly\n      // create the directory if it doesn't exist. We want to create it explicitly\n      // so that we can return the path to the first directory that was created\n      // and tryOpen does not us if the directory already existed or was created.\n      KJ_IF_SOME(node, current->tryOpen(js, kj::Path({part}))) {\n        // Let's make sure the node is a directory.\n        KJ_SWITCH_ONEOF(node) {\n          KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n            node::THROW_ERR_UV_ENOTDIR(js, \"mkdir\"_kj);\n          }\n          KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n            node::THROW_ERR_UV_ENOTDIR(js, \"mkdir\"_kj);\n          }\n          KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n            // The node is a directory, we can continue.\n            current = kj::mv(dir);\n            moveToNext = true;\n          }\n          KJ_CASE_ONEOF(err, workerd::FsError) {\n            throwFsError(js, err, \"mkdir\"_kj);\n          }\n        }\n      }\n      if (moveToNext) continue;\n\n      // The node does not exist, let's create it so long as the current\n      // directory is writable.\n      auto stat = current->stat(js);\n      if (!stat.writable) {\n        node::THROW_ERR_UV_EPERM(js, \"mkdir\"_kj);\n      }\n      auto dir = workerd::Directory::newWritable(js);\n      KJ_IF_SOME(err, current->add(js, part, dir.addRef())) {\n        throwFsError(js, err, \"mkdir\"_kj);\n      }\n      current = kj::mv(dir);\n      if (createdPath == kj::none) {\n        createdPath = currentPath.toString(true);\n      }\n    }\n\n    // Now that we have the parent directory, let's try creating the new directory.\n    auto newDir = workerd::Directory::newWritable(js);\n    KJ_IF_SOME(err, current->add(js, name.toString(false), kj::mv(newDir))) {\n      throwFsError(js, err, \"mkdir\"_kj);\n    }\n\n    return kj::mv(createdPath);\n  }\n\n  KJ_DASSERT(!options.recursive);\n  // If the recursive option is not set, we will create the directory only if\n  // the parent directory exists. If the parent directory does not exist, we\n  // will return an error.\n  jsg::Url::Relative relative = url.getRelative();\n  KJ_IF_SOME(parent, vfs.resolve(js, relative.base)) {\n    KJ_SWITCH_ONEOF(parent) {\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        node::THROW_ERR_UV_ENOTDIR(js, \"mkdir\"_kj);\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        auto stat = dir->stat(js);\n        if (!stat.writable) {\n          node::THROW_ERR_UV_EPERM(js, \"mkdir\"_kj);\n        }\n        auto newDir = workerd::Directory::newWritable(js);\n        if (options.tmp) {\n          if (tmpFileCounter >= kMax) {\n            node::THROW_ERR_UV_EPERM(js, \"mkdir\"_kj, \"Too many temporary directories created\"_kj);\n          }\n          auto name = kj::str(relative.name, tmpFileCounter++);\n          KJ_IF_SOME(err, dir->add(js, name, kj::mv(newDir))) {\n            throwFsError(js, err, \"mkdir\"_kj);\n          }\n          KJ_IF_SOME(newUrl, relative.base.resolve(name)) {\n            // If we are creating a temporary directory, we return the URL of the\n            // new directory.\n            return kj::str(newUrl.getPathname());\n          } else {\n            node::THROW_ERR_UV_EINVAL(js, \"mkdir\"_kj, \"Invalid name for temporary directory\"_kj);\n          }\n        }\n\n        KJ_IF_SOME(err, dir->add(js, relative.name, kj::mv(newDir))) {\n          throwFsError(js, err, \"mkdir\"_kj);\n        }\n\n        return kj::none;\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        node::THROW_ERR_UV_ENOTDIR(js, \"mkdir\"_kj);\n      }\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        throwFsError(js, err, \"mkdir\"_kj, relative.name);\n      }\n    }\n    KJ_UNREACHABLE;\n  } else {\n    node::THROW_ERR_UV_ENOENT(js, \"mkdir\"_kj, nullptr, relative.name);\n  }\n}\n\nvoid FileSystemModule::rm(jsg::Lock& js, FilePath path, RmOptions options) {\n  // TODO(node-fs): Implement the force option.\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n  NormalizedFilePath normalizedPath(kj::mv(path));\n  const jsg::Url& url = normalizedPath;\n  jsg::Url::Relative relative = url.getRelative();\n\n  KJ_IF_SOME(parent, vfs.resolve(js, relative.base)) {\n    KJ_IF_SOME(dir, parent.tryGet<kj::Rc<workerd::Directory>>()) {\n      auto stat = dir->stat(js);\n      if (!stat.writable) {\n        node::THROW_ERR_UV_EPERM(js, \"rm\"_kj);\n      }\n\n      kj::Path name(relative.name);\n\n      if (options.dironly) {\n        // If the dironly option is set, we will only remove the entry if it is a directory.\n        KJ_IF_SOME(stat, dir->stat(js, name)) {\n          KJ_SWITCH_ONEOF(stat) {\n            KJ_CASE_ONEOF(stat, workerd::Stat) {\n              if (stat.type != workerd::FsType::DIRECTORY) {\n                node::THROW_ERR_UV_ENOTDIR(js, \"rm\"_kj);\n              }\n            }\n            KJ_CASE_ONEOF(err, workerd::FsError) {\n              throwFsError(js, err, \"rm\"_kj);\n            }\n          }\n        } else {\n          node::THROW_ERR_UV_ENOENT(js, \"rm\"_kj, nullptr, relative.name);\n        }\n      }\n\n      KJ_SWITCH_ONEOF(dir->remove(js, name, {.recursive = options.recursive})) {\n        KJ_CASE_ONEOF(res, bool) {\n          // Ignore the return.\n        }\n        KJ_CASE_ONEOF(err, workerd::FsError) {\n          throwFsError(js, err, \"rm\"_kj, relative.name);\n        }\n      }\n    } else {\n      node::THROW_ERR_UV_ENOTDIR(js, \"rm\"_kj, nullptr, relative.name);\n    }\n  } else {\n    node::THROW_ERR_UV_ENOENT(js, \"rm\"_kj, nullptr, relative.name);\n  }\n}\n\nnamespace {\nstatic constexpr int UV_DIRENT_FILE = 1;\nstatic constexpr int UV_DIRENT_DIR = 2;\nstatic constexpr int UV_DIRENT_LINK = 3;\nstatic constexpr int UV_DIRENT_CHAR = 6;\nvoid readdirImpl(jsg::Lock& js,\n    const workerd::VirtualFileSystem& vfs,\n    kj::Rc<workerd::Directory> dir,\n    const kj::Path& path,\n    const FileSystemModule::ReadDirOptions& options,\n    kj::Vector<FileSystemModule::DirEntHandle>& entries) {\n  for (auto& entry: *dir.get()) {\n    auto name = options.recursive ? path.append(entry.key).toString(false) : kj::str(entry.key);\n    KJ_SWITCH_ONEOF(entry.value) {\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        auto stat = file->stat(js);\n        entries.add(FileSystemModule::DirEntHandle{\n          .name = kj::mv(name),\n          .parentPath = path.toString(true),\n          .type = stat.device ? UV_DIRENT_CHAR : UV_DIRENT_FILE,\n        });\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        entries.add(FileSystemModule::DirEntHandle{\n          .name = kj::mv(name),\n          .parentPath = path.toString(true),\n          .type = UV_DIRENT_DIR,\n        });\n\n        if (options.recursive) {\n          readdirImpl(js, vfs, dir.addRef(), path.append(entry.key), options, entries);\n        }\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        entries.add(FileSystemModule::DirEntHandle{\n          .name = kj::mv(name),\n          .parentPath = path.toString(true),\n          .type = UV_DIRENT_LINK,\n        });\n\n        if (options.recursive) {\n          workerd::SymbolicLinkRecursionGuardScope guard;\n          KJ_IF_SOME(err, guard.checkSeen(link.get())) {\n            throwFsError(js, err, \"readdir\"_kj);\n          }\n          KJ_IF_SOME(target, link->resolve(js)) {\n            KJ_SWITCH_ONEOF(target) {\n              KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n                // Do nothing\n              }\n              KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n                readdirImpl(js, vfs, dir.addRef(), path.append(entry.key), options, entries);\n              }\n              KJ_CASE_ONEOF(err, workerd::FsError) {\n                throwFsError(js, err, \"readdir\"_kj);\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}\n}  // namespace\n\nkj::Array<FileSystemModule::DirEntHandle> FileSystemModule::readdir(\n    jsg::Lock& js, FilePath path, ReadDirOptions options) {\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n  NormalizedFilePath normalizedPath(kj::mv(path));\n\n  KJ_IF_SOME(node, vfs.resolve(js, normalizedPath, {.followLinks = false})) {\n    KJ_SWITCH_ONEOF(node) {\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        kj::Vector<DirEntHandle> entries;\n        readdirImpl(js, vfs, kj::mv(dir), normalizedPath, options, entries);\n        return entries.releaseAsArray();\n      }\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        node::THROW_ERR_UV_ENOTDIR(\n            js, \"readdir\"_kj, nullptr, kj::str(normalizedPath.url.getPathname()));\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        node::THROW_ERR_UV_EINVAL(\n            js, \"readdir\"_kj, nullptr, kj::str(normalizedPath.url.getPathname()));\n      }\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        throwFsError(js, err, \"readdir\"_kj, kj::str(normalizedPath.url.getPathname()));\n      }\n    }\n    KJ_UNREACHABLE;\n  } else {\n    node::THROW_ERR_UV_ENOENT(js, \"readdir\"_kj, nullptr, kj::str(normalizedPath.url.getPathname()));\n  }\n}\n\nnamespace {\n\nusing MaybeFsNode = kj::Maybe<\n    kj::OneOf<kj::Rc<workerd::Directory>, kj::Rc<workerd::File>, kj::Rc<workerd::SymbolicLink>>>;\n\nMaybeFsNode getNodeOrError(jsg::Lock& js,\n    const workerd::VirtualFileSystem& vfs,\n    const jsg::Url& url,\n    const FileSystemModule::CpOptions& options) {\n  KJ_IF_SOME(node, vfs.resolve(js, url, {.followLinks = options.deferenceSymlinks})) {\n    KJ_SWITCH_ONEOF(node) {\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        return MaybeFsNode(kj::mv(file));\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        return MaybeFsNode(kj::mv(dir));\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        return MaybeFsNode(kj::mv(link));\n      }\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        throwFsError(js, err, \"cp\"_kj);\n      }\n    }\n  }\n  return kj::none;\n}\n\n// Copy the src symbolic link to the destination URL location.\n// We've already checked that the destination either does not exist\n// or we are want to overwrite it. We need to next determine if the\n// destination is writable. If it is not, this will throw an error.\n// If it is, we will either create a new symbolic link at the destination\n// or overwrite the existing file or link if it exists.\nvoid handleCpLink(jsg::Lock& js,\n    const workerd::VirtualFileSystem& vfs,\n    kj::Rc<workerd::SymbolicLink> srcLink,\n    const jsg::Url& destUrl) {\n  // Here, we are going to essentially create a hard link (new refcount)\n  // of srcLink and destUrl, if we are allowed to do so.\n  // We need to check if the destination exists and is writable.\n  auto relative = destUrl.getRelative();\n  // relative.base is the parent directory path.\n  // relative.name is the name of the link we are creating in the parent.\n\n  auto basePath = kj::str(relative.base.getPathname().slice(1));\n  kj::Path root{};\n  auto base = root.eval(basePath);\n\n  // We need to grab the parent directory, creating it if it does not exist\n  // and we are permitted to do so.\n  KJ_IF_SOME(destDir,\n      vfs.getRoot(js)->tryOpen(js, base,\n          {\n            .createAs = workerd::FsType::DIRECTORY,\n            .followLinks = true,\n          })) {\n    // Awesome, either the destination directory existed already or we\n    // successfully created it, or an error was reported.\n    KJ_SWITCH_ONEOF(destDir) {\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        // We cannot copy into a file, so we throw an error.\n        node::THROW_ERR_UV_ENOTDIR(js, \"cp\"_kj);\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        kj::Path path({relative.name});\n        // This is the case we're looking for!\n        // First, let's check to see if the target name already exists.\n        // If it does, we'll remove it.\n        KJ_SWITCH_ONEOF(dir->remove(js, path, {.recursive = false})) {\n          KJ_CASE_ONEOF(err, workerd::FsError) {\n            throwFsError(js, err, \"cp\"_kj);\n          }\n          KJ_CASE_ONEOF(b, bool) {\n            // Ignore the return value, we don't actually care if the thing existed or not.\n          }\n        }\n        // Now, we can add the symbolic link to the directory.\n        KJ_IF_SOME(err, dir->add(js, relative.name, kj::mv(srcLink))) {\n          // If we got here, an error was reported\n          throwFsError(js, err, \"cp\"_kj);\n        }\n        // If we got here, success!\n        return;\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        // This shouldn't be possible since we told tryOpen to follow links.\n        // But, let's just throw an error.\n        node::THROW_ERR_UV_EINVAL(js, \"cp\"_kj);\n      }\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        throwFsError(js, err, \"cp\"_kj);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  // In this case, the destDir could not be opened, treat as an error.\n  node::THROW_ERR_UV_EINVAL(js, \"cp\"_kj);\n}\n\nvoid handleCpFile(jsg::Lock& js,\n    const workerd::VirtualFileSystem& vfs,\n    kj::Rc<workerd::File> file,\n    const jsg::Url& destUrl) {\n  // Here, we are going to clone the file into a new file at the destination\n  // if we are allowed to do so.\n  // We need to check if the destination exists and is writable.\n  auto relative = destUrl.getRelative();\n  // relative.base is the parent directory path.\n  // relative.name is the name of the link we are creating in the parent.\n\n  auto basePath = kj::str(relative.base.getPathname().slice(1));\n  kj::Path root{};\n  auto base = root.eval(basePath);\n\n  // We need to grab the parent directory, creating it if it does not exist\n  // and we are permitted to do so.\n  KJ_IF_SOME(destDir,\n      vfs.getRoot(js)->tryOpen(js, base,\n          {\n            .createAs = workerd::FsType::DIRECTORY,\n            .followLinks = true,\n          })) {\n    // Awesome, either the destination directory existed already or we\n    // successfully created it, or an error was reported.\n    KJ_SWITCH_ONEOF(destDir) {\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        // We cannot copy into a file, so we throw an error.\n        node::THROW_ERR_UV_ENOTDIR(js, \"cp\"_kj);\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        kj::Path path({relative.name});\n        // This is the case we're looking for!\n        // First, let's check to see if the target name already exists.\n        // If it does, we'll remove it.\n        KJ_SWITCH_ONEOF(dir->remove(js, path, {.recursive = false})) {\n          KJ_CASE_ONEOF(err, workerd::FsError) {\n            throwFsError(js, err, \"cp\"_kj);\n          }\n          KJ_CASE_ONEOF(b, bool) {\n            // Ignore the return value, we don't actually care if the thing existed or not.\n          }\n        }\n        // Now, we can add the file the directory.\n        KJ_SWITCH_ONEOF(file->clone(js)) {\n          KJ_CASE_ONEOF(err, workerd::FsError) {\n            throwFsError(js, err, \"cp\"_kj);\n          }\n          KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n            KJ_IF_SOME(err, dir->add(js, relative.name, kj::mv(file))) {\n              // If we got here, an error was reported\n              throwFsError(js, err, \"cp\"_kj);\n            }\n          }\n        }\n        // If we got here, success!\n        return;\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        // This shouldn't be possible since we told tryOpen to follow links.\n        // But, let's just throw an error.\n        node::THROW_ERR_UV_EINVAL(js, \"cp\"_kj);\n      }\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        throwFsError(js, err, \"cp\"_kj);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  // In this case, the destDir could not be opened, treat as an error.\n  node::THROW_ERR_UV_EINVAL(js, \"cp\"_kj);\n}\n\nvoid handleCpDir(jsg::Lock& js,\n    const workerd::VirtualFileSystem& vfs,\n    kj::Rc<workerd::Directory> src,\n    kj::Rc<workerd::Directory> dest,\n    const FileSystemModule::CpOptions& options) {\n  auto stat = dest->stat(js);\n  if (!stat.writable) {\n    node::THROW_ERR_UV_EPERM(js, \"cp\"_kj, \"Destination directory is not writable\"_kj);\n  }\n  if (src.get() == dest.get()) {\n    node::THROW_ERR_UV_EINVAL(js, \"cp\"_kj, \"Source and destination directories are the same\"_kj);\n  }\n\n  // Here, we iterate through each of the entries in the source directory,\n  // recursively copying them to the destination directory.\n  for (auto& entry: *src.get()) {\n    kj::StringPtr name = entry.key;\n    KJ_SWITCH_ONEOF(entry.value) {\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        // We have a file, we will copy it to the destination directory\n        // unless errorOnExist is true, force is false, and the destination already exists.\n\n        KJ_IF_SOME(existing,\n            dest->tryOpen(js, kj::Path({name}),\n                {\n                  .followLinks = options.deferenceSymlinks,\n                })) {\n          // The destination path already exists. Check to see if we can overwrite it.\n          KJ_SWITCH_ONEOF(existing) {\n            KJ_CASE_ONEOF(existingFile, kj::Rc<workerd::File>) {\n              if (existingFile.get() == file.get()) {\n                // Do nothing\n              } else if (options.force) {\n                KJ_SWITCH_ONEOF(dest->remove(js, kj::Path({name}), {.recursive = false})) {\n                  KJ_CASE_ONEOF(err, workerd::FsError) {\n                    throwFsError(js, err, \"cp\"_kj);\n                  }\n                  KJ_CASE_ONEOF(b, bool) {\n                    // Ignore the return value.\n                  }\n                }\n                KJ_SWITCH_ONEOF(file->clone(js)) {\n                  KJ_CASE_ONEOF(err, workerd::FsError) {\n                    throwFsError(js, err, \"cp\"_kj);\n                  }\n                  KJ_CASE_ONEOF(cloned, kj::Rc<workerd::File>) {\n                    KJ_IF_SOME(err, dest->add(js, name, kj::mv(cloned))) {\n                      // If we got here, an error was reported\n                      throwFsError(js, err, \"cp\"_kj);\n                    }\n                  }\n                }\n              } else if (options.errorOnExist) {\n                node::THROW_ERR_UV_EEXIST(\n                    js, \"cp\"_kj, kj::str(\"Destination already exists: \", name));\n              }\n              // If we got here, we are not overwriting the file, so we just ignore it.\n            }\n            KJ_CASE_ONEOF(existingDir, kj::Rc<workerd::Directory>) {\n              // We cannot overwrite a directory with a file, so we throw an error.\n              node::THROW_ERR_UV_EISDIR(\n                  js, \"cp\"_kj, kj::str(\"Cannot copy file to directory: \", name));\n            }\n            KJ_CASE_ONEOF(_, kj::Rc<workerd::SymbolicLink>) {\n              // We're going to replace the existing link with the file.\n              if (options.force) {\n                KJ_SWITCH_ONEOF(dest->remove(js, kj::Path({name}), {.recursive = false})) {\n                  KJ_CASE_ONEOF(err, workerd::FsError) {\n                    throwFsError(js, err, \"cp\"_kj);\n                  }\n                  KJ_CASE_ONEOF(b, bool) {\n                    // Ignore the return value.\n                  }\n                }\n                KJ_SWITCH_ONEOF(file->clone(js)) {\n                  KJ_CASE_ONEOF(err, workerd::FsError) {\n                    throwFsError(js, err, \"cp\"_kj);\n                  }\n                  KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n                    KJ_IF_SOME(err, dest->add(js, name, kj::mv(file))) {\n                      // If we got here, an error was reported\n                      throwFsError(js, err, \"cp\"_kj);\n                    }\n                  }\n                }\n              } else if (options.errorOnExist) {\n                node::THROW_ERR_UV_EEXIST(\n                    js, \"cp\"_kj, kj::str(\"Destination already exists: \", name));\n              }\n              // If we got here, we are not overwriting the file, so we just ignore it.\n            }\n            KJ_CASE_ONEOF(err, workerd::FsError) {\n              throwFsError(js, err, \"cp\"_kj);\n            }\n          }\n        } else {\n          KJ_SWITCH_ONEOF(file->clone(js)) {\n            KJ_CASE_ONEOF(err, workerd::FsError) {\n              throwFsError(js, err, \"cp\"_kj);\n            }\n            KJ_CASE_ONEOF(cloned, kj::Rc<workerd::File>) {\n              KJ_IF_SOME(err, dest->add(js, name, kj::mv(cloned))) {\n                // If we got here, an error was reported\n                throwFsError(js, err, \"cp\"_kj);\n              }\n            }\n          }\n        }\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        // We have a directory, we will copy it to the destination directory\n        // recursively.\n\n        // First, we need to check if the destination directory already exists.\n        KJ_IF_SOME(existing,\n            dest->tryOpen(js, kj::Path({name}),\n                {\n                  .followLinks = options.deferenceSymlinks,\n                })) {\n          // The destination exists. Check to see if we can overwrite it.\n          KJ_SWITCH_ONEOF(existing) {\n            KJ_CASE_ONEOF(existingFile, kj::Rc<workerd::File>) {\n              // The destination is a file, we cannot overwrite it with a directory.\n              node::THROW_ERR_UV_ENOTDIR(\n                  js, \"cp\"_kj, kj::str(\"Cannot copy directory to file: \", name));\n            }\n            KJ_CASE_ONEOF(existingDir, kj::Rc<workerd::Directory>) {\n              handleCpDir(js, vfs, kj::mv(dir), kj::mv(existingDir), options);\n            }\n            KJ_CASE_ONEOF(existingLink, kj::Rc<workerd::SymbolicLink>) {\n              // The destination is a symbolic link, we can overwrite it with a directory.\n              node::THROW_ERR_UV_EISDIR(\n                  js, \"cp\"_kj, kj::str(\"Cannot copy directory to symbolic link: \", name));\n            }\n            KJ_CASE_ONEOF(err, workerd::FsError) {\n              throwFsError(js, err, \"cp\"_kj);\n            }\n          }\n        } else {\n          // The destination does not exist, we'll need to create a new directory.\n          // then recursively copy into it.\n          auto newDir = workerd::Directory::newWritable(js);\n          KJ_IF_SOME(err, dest->add(js, name, newDir.addRef())) {\n            // If we got here, an error was reported\n            throwFsError(js, err, \"cp\"_kj);\n          }\n          // Now we can recursively copy the contents of the source directory into the new one.\n          handleCpDir(js, vfs, kj::mv(dir), kj::mv(newDir), options);\n        }\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        KJ_IF_SOME(existing,\n            dest->tryOpen(js, kj::Path({name}),\n                {\n                  .followLinks = options.deferenceSymlinks,\n                })) {\n          // The destination path already exists. Check to see if we can overwrite it.\n          KJ_SWITCH_ONEOF(existing) {\n            KJ_CASE_ONEOF(_, kj::Rc<workerd::File>) {\n              if (options.force) {\n                KJ_SWITCH_ONEOF(dest->remove(js, kj::Path({name}), {.recursive = false})) {\n                  KJ_CASE_ONEOF(err, workerd::FsError) {\n                    throwFsError(js, err, \"cp\"_kj);\n                  }\n                  KJ_CASE_ONEOF(b, bool) {\n                    // Ignore the return value.\n                  }\n                }\n                KJ_IF_SOME(err, dest->add(js, name, link.addRef())) {\n                  // If we got here, an error was reported\n                  throwFsError(js, err, \"cp\"_kj);\n                }\n              } else if (options.errorOnExist) {\n                node::THROW_ERR_UV_EEXIST(\n                    js, \"cp\"_kj, kj::str(\"Destination already exists: \", name));\n              }\n              // If we got here, we are not overwriting the file, so we just ignore it.\n            }\n            KJ_CASE_ONEOF(existingDir, kj::Rc<workerd::Directory>) {\n              // We cannot overwrite a directory with a file, so we throw an error.\n              node::THROW_ERR_UV_EISDIR(\n                  js, \"cp\"_kj, kj::str(\"Cannot copy link to directory: \", name));\n            }\n            KJ_CASE_ONEOF(existingLink, kj::Rc<workerd::SymbolicLink>) {\n              if (existingLink.get() == link.get()) {\n                // Do nothing\n              } else if (options.force) {\n                KJ_SWITCH_ONEOF(dest->remove(js, kj::Path({name}), {.recursive = false})) {\n                  KJ_CASE_ONEOF(err, workerd::FsError) {\n                    throwFsError(js, err, \"cp\"_kj);\n                  }\n                  KJ_CASE_ONEOF(b, bool) {\n                    // Ignore the return value.\n                  }\n                }\n                KJ_IF_SOME(err, dest->add(js, name, link.addRef())) {\n                  // If we got here, an error was reported\n                  throwFsError(js, err, \"cp\"_kj);\n                }\n              } else if (options.errorOnExist) {\n                node::THROW_ERR_UV_EEXIST(\n                    js, \"cp\"_kj, kj::str(\"Destination already exists: \", name));\n              }\n              // If we got here, we are not overwriting the file, so we just ignore it.\n            }\n            KJ_CASE_ONEOF(err, workerd::FsError) {\n              throwFsError(js, err, \"cp\"_kj);\n            }\n          }\n        } else KJ_IF_SOME(err, dest->add(js, name, link.addRef())) {\n          // If we got here, an error was reported\n          throwFsError(js, err, \"cp\"_kj);\n        }\n      }\n    }\n  }\n}\n\nvoid handleCpDir(jsg::Lock& js,\n    const workerd::VirtualFileSystem& vfs,\n    kj::Rc<workerd::Directory> src,\n    const jsg::Url& dest,\n    const FileSystemModule::CpOptions& options) {\n  // For this variation of handleCpDir, the dest needs to be created as a directory.\n  // The assumption here is that the destination does not yet exist. Let's create it.\n\n  auto basePath = kj::str(dest.getPathname().slice(1));\n  kj::Path root{};\n  auto path = root.eval(basePath);\n\n  KJ_IF_SOME(destDir,\n      vfs.getRoot(js)->tryOpen(js, path,\n          {\n            .createAs = workerd::FsType::DIRECTORY,\n            .followLinks = true,\n          })) {\n    KJ_SWITCH_ONEOF(destDir) {\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        // We cannot copy into a file, so we throw an error.\n        node::THROW_ERR_UV_ENOTDIR(js, \"cp\"_kj);\n      }\n      KJ_CASE_ONEOF(destination, kj::Rc<workerd::Directory>) {\n        // Nice... we have our destination directory. Continue to copy the contents.\n        return handleCpDir(js, vfs, kj::mv(src), kj::mv(destination), options);\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        // This shouldn't be possible since we told tryOpen to follow links.\n        // But, let's just throw an error.\n        node::THROW_ERR_UV_EINVAL(js, \"cp\"_kj);\n      }\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        throwFsError(js, err, \"cp\"_kj);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  // If we got here, then for some reason we could not open/create the destination\n  // directory. Since we passed createAs, we shouldn't really be able to get here.\n  node::THROW_ERR_UV_EINVAL(js, \"cp\"_kj);\n}\n\nvoid cpImpl(jsg::Lock& js,\n    const workerd::VirtualFileSystem& vfs,\n    const jsg::Url& src,\n    const jsg::Url& dest,\n    const FileSystemModule::CpOptions& options) {\n\n  // Cannot copy a file to itself.\n  JSG_REQUIRE(!src.equal(dest,\n                  jsg::Url::EquivalenceOption::IGNORE_FRAGMENTS |\n                      jsg::Url::EquivalenceOption::IGNORE_SEARCH |\n                      jsg::Url::EquivalenceOption::NORMALIZE_PATH),\n      Error, \"Source and destination paths must not be the same\"_kj);\n\n  // Cannot copy a directory to a subdirectory of itself. The pathnames are\n  // already normalized by jsg::Url (no .., //, or trailing slashes except root).\n  {\n    auto srcPath = src.getPathname();\n    auto destPath = dest.getPathname();\n    KJ_ASSERT(srcPath.size() > 0, \"normalized URL pathname must not be empty\");\n    auto srcPrefix = srcPath.back() == '/' ? kj::str(srcPath) : kj::str(srcPath, \"/\");\n    auto destStr = kj::StringPtr(destPath.begin(), destPath.size());\n    if (destStr.size() > srcPrefix.size() && destStr.startsWith(srcPrefix)) {\n      node::THROW_ERR_FS_CP_EINVAL(\n          js, kj::str(\"Cannot copy '\", srcPath, \"' to a subdirectory of itself, '\", destPath, \"'\"));\n    }\n  }\n\n  // Step 1: If deferenceSymlinks is true, the we will be following symbolic links. If\n  // it is false, we won't be.\n\n  auto maybeSrcNode = getNodeOrError(js, vfs, src, options);\n  auto maybeDestNode = getNodeOrError(js, vfs, dest, options);\n\n  KJ_IF_SOME(sourceNode, maybeSrcNode) {\n    KJ_SWITCH_ONEOF(sourceNode) {\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        KJ_IF_SOME(node, maybeDestNode) {\n          KJ_SWITCH_ONEOF(node) {\n            KJ_CASE_ONEOF(_, kj::Rc<workerd::File>) {\n              // If options.force is true, we will overwrite the destination file.\n              if (options.force) {\n                return handleCpFile(js, vfs, kj::mv(file), dest);\n              }\n              // Otherwise, if options.errorOnExist is true, we will throw an error.\n              if (options.errorOnExist) {\n                node::THROW_ERR_FS_CP_EEXIST(js);\n              }\n              // Otherwise, we skip this file and do nothing.\n              return;\n            }\n            KJ_CASE_ONEOF(_, kj::Rc<workerd::Directory>) {\n              // Simple case: user is trying to copy a file over a directory\n              // which is not allowed.\n              node::THROW_ERR_FS_CP_NON_DIR_TO_DIR(js);\n            }\n            KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n              if (options.deferenceSymlinks) {\n                KJ_IF_SOME(target, link->resolve(js)) {\n                  KJ_SWITCH_ONEOF(target) {\n                    KJ_CASE_ONEOF(targetFile, kj::Rc<workerd::File>) {\n                      KJ_SWITCH_ONEOF(file->clone(js)) {\n                        KJ_CASE_ONEOF(err, workerd::FsError) {\n                          throwFsError(js, err, \"cp\"_kj);\n                        }\n                        KJ_CASE_ONEOF(clonedFile, kj::Rc<workerd::File>) {\n                          KJ_IF_SOME(err, targetFile->replace(js, kj::mv(clonedFile))) {\n                            throwFsError(js, err, \"cp\"_kj);\n                          }\n                        }\n                      }\n                      return;\n                    }\n                    KJ_CASE_ONEOF(_, kj::Rc<workerd::Directory>) {\n                      node::THROW_ERR_UV_EISDIR(js, \"cp\"_kj);\n                    }\n                    KJ_CASE_ONEOF(err, workerd::FsError) {\n                      throwFsError(js, err, \"cp\"_kj);\n                    }\n                  }\n                } else {\n                  node::THROW_ERR_UV_ENOENT(\n                      js, \"cp\"_kj, nullptr, kj::str(link->getTargetUrl().getPathname()));\n                }\n              }\n\n              // We would only get here if deferenceSymlinks is false.\n              // In this case, if errorOnExist is true and force is false,\n              // we will throw an error.\n              if (options.force) {\n                // Copy the file contents to the destination, replacing\n                // the symbolic link with a copy of the file.\n                return handleCpFile(js, vfs, kj::mv(file), dest);\n              }\n              if (options.errorOnExist) {\n                node::THROW_ERR_FS_CP_EEXIST(js);\n              }\n\n              // Otherwise, we skip this file and do nothing.\n              return;\n            }\n          }\n          KJ_UNREACHABLE;\n        }\n\n        // Yay! we can just copy the file contents to the destination.\n        // If the path to the destination does not exist, we will create it\n        // if possible.\n        return handleCpFile(js, vfs, kj::mv(file), dest);\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        // The source is a directory. The options.recursive option must be set\n        // to true or we will fail.\n        if (!options.recursive) {\n          node::THROW_ERR_FS_EISDIR(js);\n        }\n\n        KJ_IF_SOME(dest, maybeDestNode) {\n          KJ_SWITCH_ONEOF(dest) {\n            KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n              // Simple case: user is trying to copy a directory over a file\n              // which is not allowed.\n              node::THROW_ERR_FS_CP_DIR_TO_NON_DIR(js);\n            }\n            KJ_CASE_ONEOF(destDir, kj::Rc<workerd::Directory>) {\n              // So Node.js has a bit of an inconsistency here when copying directories.\n              // When copying a file over a file, we will check the errorOnExist and force\n              // options, failing if the destination file exists and errorOnExist is true,\n              // unless the force option is set. If both are false, we skip the copy.\n              // However, the same logic is not applied to copying a directory. If the\n              // destination directory exists, we will still proceed to copy the source\n              // directory into the destination directory, only applying the force and\n              // errorOnExist options to individual files within the directories.\n              // See: https://github.com/nodejs/node/issues/58947\n              return handleCpDir(js, vfs, kj::mv(dir), kj::mv(destDir), options);\n            }\n            KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n              // Also a simple case, user is trying to copy a directory over\n              // an existing symbolic link, which we do not allow.\n              node::THROW_ERR_UV_ENOTDIR(js, \"cp\"_kj);\n            }\n          }\n          KJ_UNREACHABLE;\n        }\n\n        // Yay! we can just copy the file contents to the destination.\n        // If the path to the destination does not exist, we will create it\n        // if possible.\n        return handleCpDir(js, vfs, kj::mv(dir), dest, options);\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        // If we got here, then the source is itself a symbolic link.\n        // The destination, if we do copy it, will also be a symbolic link to\n        // the same target. The options.errorOnExist and options.force still\n        // apply here, but we will not follow the symbolic link at all.\n        KJ_IF_SOME(node, maybeDestNode) {\n          KJ_SWITCH_ONEOF(node) {\n            KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n              if (options.force) {\n                return handleCpLink(js, vfs, kj::mv(link), dest);\n              }\n\n              if (options.errorOnExist) {\n                node::THROW_ERR_FS_CP_EEXIST(js);\n              }\n\n              // Otherwise we skip this file and do nothing.\n              return;\n            }\n            KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n              // Simple case: user is trying to copy a symbolic link over a directory\n              // which is not allowed.\n              node::THROW_ERR_UV_ENOTDIR(js, \"cp\"_kj);\n            }\n            KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n              if (options.force) {\n                return handleCpLink(js, vfs, kj::mv(link), dest);\n              }\n\n              if (options.errorOnExist) {\n                node::THROW_ERR_FS_CP_EEXIST(js);\n              }\n\n              // Otherwise we skip this file and do nothing.\n              return;\n            }\n          }\n          KJ_UNREACHABLE;\n        }\n\n        // Yay! we can just copy fhe symbolic link to the destination.\n        // If the path to the destination does not exist, we will create it\n        // if possible.\n        return handleCpLink(js, vfs, kj::mv(link), dest);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  // If we got here, the sourceNode does not exist.\n  node::THROW_ERR_UV_ENOENT(js, \"cp\"_kj, nullptr, kj::str(src.getPathname()));\n}\n}  // namespace\n\nvoid FileSystemModule::cp(jsg::Lock& js, FilePath src, FilePath dest, CpOptions options) {\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n  NormalizedFilePath normalizedSrc(kj::mv(src));\n  NormalizedFilePath normalizedDest(kj::mv(dest));\n  // TODO(node-fs): Support the preserveTimestamps option.\n  cpImpl(js, vfs, normalizedSrc, normalizedDest, options);\n}\n\njsg::Ref<Blob> FileSystemModule::openAsBlob(\n    jsg::Lock& js, FilePath path, OpenAsBlobOptions options) {\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n  NormalizedFilePath normalizedSrc(kj::mv(path));\n  KJ_IF_SOME(item, vfs.resolve(js, normalizedSrc, {})) {\n    KJ_SWITCH_ONEOF(item) {\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        node::THROW_ERR_UV_ENOENT(js, \"open\"_kj, nullptr, kj::str(normalizedSrc.url.getPathname()));\n      }\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        KJ_SWITCH_ONEOF(file->readAllBytes(js)) {\n          KJ_CASE_ONEOF(bytes, jsg::BufferSource) {\n            return js.alloc<Blob>(js, kj::mv(bytes), kj::mv(options.type).orDefault(kj::String()));\n          }\n          KJ_CASE_ONEOF(err, workerd::FsError) {\n            throwFsError(js, err, \"open\"_kj);\n          }\n        }\n        KJ_UNREACHABLE;\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        node::THROW_ERR_UV_EISDIR(js, \"open\"_kj);\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        node::THROW_ERR_UV_EINVAL(js, \"open\"_kj);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  node::THROW_ERR_UV_ENOENT(js, \"open\"_kj, nullptr, kj::str(normalizedSrc.url.getPathname()));\n}\n\n// =======================================================================================\n\njsg::Ref<FileFdHandle> FileFdHandle::constructor(jsg::Lock& js, int fd) {\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n  return js.alloc<FileFdHandle>(js, vfs, fd);\n}\n\nFileFdHandle::FileFdHandle(jsg::Lock& js, const workerd::VirtualFileSystem& vfs, int fd)\n    : fdHandle(vfs.wrapFd(js, fd)),\n      isolate(js.v8Isolate) {}\n\nvoid FileFdHandle::close(jsg::Lock& js) {\n  fdHandle = kj::none;\n}\n\nFileFdHandle::~FileFdHandle() noexcept {\n  // In Node.js, closing the file descriptor on destruction is an\n  // error (it was a deprecated behavior for a long time and\n  // was recently upgraded to a catchable error in Node.js. However,\n  // throwing an error in our implementation is of questionable value\n  // since it's not clear exactly what the user is supposed to do about\n  // it beyond making sure to explicitly close the file descriptor\n  // before the object is destroyed, which we can't guarantee.\n  KJ_IF_SOME(handle, fdHandle) {\n    if (isolate != nullptr) {\n      if (v8::Locker::IsLocked(isolate)) {\n        // If the isolate is locked, we can safely close the fdHandle.\n        // This is because closing the handle requires decrementing the\n        // refcount on the underlying node object, which can only be done\n        // safely while the isolate is locked.\n        fdHandle = kj::none;\n      } else {\n        // If the isolate is not locked, we cannot safely close the fdHandle\n        // yet. We need to make sure the isolate is locked before we close it.\n        // So instead, we will defer the actual destruction to when we next\n        // hold the isolate lock.\n        jsg::IsolateBase::from(isolate).destroyUnderLock(kj::mv(handle));\n        fdHandle = kj::none;\n      }\n    }\n\n    // If we have an active IoContext, then we'll go ahead and log a warning.\n    KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n      ioContext.logWarning(\"A FileHandle was destroyed without being closed. This is \"\n                           \"not recommended and may lead to file descriptors being held \"\n                           \"far longer than necessary. Please make sure to explicitly close \"\n                           \"the FileHandle object explicitly before it is destroyed.\"_kj);\n    }\n  }\n}\n\n// =======================================================================================\n// Implementation of the Web File System API\n\nnamespace {\nconstexpr bool isValidFileName(kj::StringPtr name) {\n  return name.size() > 0 && name != \".\"_kj && name != \"..\"_kj && name.find(\"/\"_kj) == kj::none &&\n      name.find(\"\\\\\"_kj) == kj::none;\n}\n\njsg::Ref<jsg::DOMException> fsErrorToDomException(jsg::Lock& js, workerd::FsError error) {\n  switch (error) {\n    case workerd::FsError::NOT_DIRECTORY: {\n      return js.domException(kj::str(\"NotSupportedError\"), kj::str(\"Not a directory\"));\n    }\n    case workerd::FsError::NOT_EMPTY: {\n      return js.domException(kj::str(\"InvalidModificationError\"), kj::str(\"Directory not empty\"));\n    }\n    case workerd::FsError::READ_ONLY: {\n      return js.domException(kj::str(\"InvalidStateError\"), kj::str(\"Read-only file system\"));\n    }\n    case workerd::FsError::NOT_PERMITTED: {\n      return js.domException(kj::str(\"NotAllowedError\"), kj::str(\"Operation not permitted\"));\n    }\n    case workerd::FsError::NOT_PERMITTED_ON_DIRECTORY: {\n      return js.domException(\n          kj::str(\"NotAllowedError\"), kj::str(\"Operation not permitted on a directory\"));\n    }\n    case workerd::FsError::ALREADY_EXISTS: {\n      return js.domException(kj::str(\"InvalidStateError\"), kj::str(\"File already exists\"));\n    }\n    case workerd::FsError::TOO_MANY_OPEN_FILES: {\n      return js.domException(kj::str(\"QuotaExceededError\"),\n          kj::str(\"Too many open files, please close some files and try again\"));\n    }\n    case workerd::FsError::FAILED: {\n      return js.domException(kj::str(\"UnknownError\"), kj::str(\"File system operation failed\"));\n    }\n    case workerd::FsError::NOT_SUPPORTED: {\n      return js.domException(kj::str(\"NotSupportedError\"), kj::str(\"Operation not supported\"));\n    }\n    case workerd::FsError::INVALID_PATH: {\n      return js.domException(kj::str(\"TypeMismatchError\"), kj::str(\"Invalid file path\"));\n    }\n    case workerd::FsError::FILE_SIZE_LIMIT_EXCEEDED: {\n      return js.domException(kj::str(\"QuotaExceededError\"),\n          kj::str(\"File size limit exceeded, please reduce the file size and try again\"));\n    }\n    case workerd::FsError::SYMLINK_DEPTH_EXCEEDED: {\n      return js.domException(kj::str(\"InvalidStateError\"),\n          kj::str(\"Symbolic link depth exceeded, please check the symbolic links\"));\n    }\n    default: {\n      return js.domException(\n          kj::str(\"UnknownError\"), kj::str(\"Unknown file system error: \", static_cast<int>(error)));\n    }\n  }\n  KJ_UNREACHABLE;\n}\n}  // namespace\n\nFileSystemHandle::FileSystemHandle(\n    const workerd::VirtualFileSystem& vfs, jsg::Url&& locator, jsg::USVString name)\n    : vfs(vfs),\n      locator(kj::mv(locator)),\n      name(kj::mv(name)) {}\n\njsg::Promise<kj::StringPtr> FileSystemHandle::getUniqueId(\n    jsg::Lock& js, const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& deHandler) {\n  KJ_IF_SOME(item, vfs.resolve(js, getLocator(), {})) {\n    KJ_SWITCH_ONEOF(item) {\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        return js.resolvedPromise(file->getUniqueId(js));\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        return js.resolvedPromise(dir->getUniqueId(js));\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        return js.resolvedPromise(link->getUniqueId(js));\n      }\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        return js.rejectedPromise<kj::StringPtr>(\n            deHandler.wrap(js, fsErrorToDomException(js, err)));\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n  auto ex = js.domException(kj::str(\"NotFoundError\"), kj::str(\"The entry was not found.\"));\n  return js.rejectedPromise<kj::StringPtr>(deHandler.wrap(js, kj::mv(ex)));\n}\n\njsg::Promise<bool> FileSystemHandle::isSameEntry(jsg::Lock& js, jsg::Ref<FileSystemHandle> other) {\n  // Per the spec, two handles are the same if they refer to the same entry (that is,\n  // have the same locator). It does not matter if they are different actual entries.\n  return getKind(js) == other->getKind(js) &&\n          locator.equal(other->getLocator(),\n              jsg::Url::EquivalenceOption::IGNORE_FRAGMENTS |\n                  jsg::Url::EquivalenceOption::IGNORE_SEARCH |\n                  jsg::Url::EquivalenceOption::NORMALIZE_PATH)\n      ? js.resolvedPromise(true)\n      : js.resolvedPromise(false);\n}\n\njsg::Promise<void> FileSystemHandle::remove(jsg::Lock& js,\n    jsg::Optional<RemoveOptions> options,\n    const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& deHandler) {\n\n  if (!canBeModifiedCurrently(js)) {\n    auto ex = js.domException(kj::str(\"NoModificationAllowedError\"),\n        kj::str(\"Cannot remove a handle that is not writable or not a directory.\"));\n    return js.rejectedPromise<void>(deHandler.wrap(js, kj::mv(ex)));\n  }\n\n  auto relative = getLocator().getRelative(jsg::Url::RelativeOption::STRIP_TAILING_SLASHES);\n  auto opts = options.orDefault(RemoveOptions{});\n  auto recursive = opts.recursive.orDefault(false);\n  KJ_IF_SOME(parent, vfs.resolve(js, relative.base, workerd::VirtualFileSystem::ResolveOptions{})) {\n    KJ_SWITCH_ONEOF(parent) {\n      KJ_CASE_ONEOF(parentDir, kj::Rc<workerd::Directory>) {\n        // Webfs requires that the entry exists before we try to remove it.\n        kj::Path path({name});\n        if (parentDir->stat(js, path) == kj::none) {\n          auto ex = js.domException(kj::str(\"NotFoundError\"), kj::str(\"The entry was not found.\"));\n          return js.rejectedPromise<void>(deHandler.wrap(js, kj::mv(ex)));\n        }\n\n        KJ_SWITCH_ONEOF(parentDir->remove(js, path, {.recursive = recursive})) {\n          KJ_CASE_ONEOF(err, workerd::FsError) {\n            return js.rejectedPromise<void>(deHandler.wrap(js, fsErrorToDomException(js, err)));\n          }\n          KJ_CASE_ONEOF(removed, bool) {\n            if (!removed) {\n              auto ex =\n                  js.domException(kj::str(\"NotFoundError\"), kj::str(\"The entry was not found.\"));\n              return js.rejectedPromise<void>(deHandler.wrap(js, kj::mv(ex)));\n            }\n            return js.resolvedPromise();\n          }\n        }\n      }\n      KJ_CASE_ONEOF(_, kj::Rc<workerd::File>) {\n        return js.rejectedPromise<void>(\n            deHandler.wrap(js, fsErrorToDomException(js, workerd::FsError::NOT_DIRECTORY)));\n      }\n      KJ_CASE_ONEOF(_, kj::Rc<workerd::SymbolicLink>) {\n        return js.rejectedPromise<void>(\n            deHandler.wrap(js, fsErrorToDomException(js, workerd::FsError::NOT_DIRECTORY)));\n      }\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        return js.rejectedPromise<void>(deHandler.wrap(js, fsErrorToDomException(js, err)));\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n  auto ex = js.domException(kj::str(\"NotFoundError\"), kj::str(\"The entry was not found.\"));\n  return js.rejectedPromise<void>(deHandler.wrap(js, kj::mv(ex)));\n}\n\nbool FileSystemHandle::canBeModifiedCurrently(jsg::Lock& js) const {\n  auto pathname = getLocator().getPathname();\n  if (pathname.endsWith(\"/\"_kj)) {\n    auto cloned = getLocator().clone();\n    cloned.setPathname(pathname.slice(0, pathname.size() - 1));\n    return !getVfs().isLocked(js, cloned);\n  }\n  return !getVfs().isLocked(js, getLocator());\n}\n\njsg::Promise<jsg::Ref<FileSystemDirectoryHandle>> StorageManager::getDirectory(\n    jsg::Lock& js, const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& exception) {\n  auto& vfs = workerd::VirtualFileSystem::current(js);\n  return js.resolvedPromise(js.alloc<FileSystemDirectoryHandle>(\n      vfs, KJ_ASSERT_NONNULL(jsg::Url::tryParse(\"file:///\"_kj)), jsg::USVString()));\n}\n\nFileSystemDirectoryHandle::FileSystemDirectoryHandle(\n    const workerd::VirtualFileSystem& vfs, jsg::Url locator, jsg::USVString name)\n    : FileSystemHandle(vfs, kj::mv(locator), kj::mv(name)) {}\n\njsg::Promise<jsg::Ref<FileSystemFileHandle>> FileSystemDirectoryHandle::getFileHandle(jsg::Lock& js,\n    jsg::USVString name,\n    jsg::Optional<FileSystemGetFileOptions> options,\n    const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& exception) {\n  if (!isValidFileName(name)) {\n    return js.rejectedPromise<jsg::Ref<FileSystemFileHandle>>(js.typeError(\"Invalid file name\"));\n  }\n  kj::Maybe<FsType> createAs;\n  KJ_IF_SOME(opts, options) {\n    if (opts.create) createAs = FsType::FILE;\n  }\n\n  KJ_IF_SOME(existing, getVfs().resolve(js, getLocator(), {})) {\n    KJ_SWITCH_ONEOF(existing) {\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        return js.rejectedPromise<jsg::Ref<FileSystemFileHandle>>(\n            exception.wrap(js, fsErrorToDomException(js, err)));\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        auto locator = KJ_ASSERT_NONNULL(getLocator().tryResolve(name));\n        auto relative = locator.getRelative();\n        KJ_IF_SOME(node,\n            dir->tryOpen(js, kj::Path({relative.name}),\n                Directory::OpenOptions{\n                  .createAs = createAs,\n                })) {\n          KJ_SWITCH_ONEOF(node) {\n            KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n              return js.resolvedPromise(\n                  js.alloc<FileSystemFileHandle>(getVfs(), kj::mv(locator), kj::mv(name)));\n            }\n            KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n              auto ex = js.domException(\n                  kj::str(\"TypeMismatchError\"), kj::str(\"File name is a directory\"));\n              return js.rejectedPromise<jsg::Ref<FileSystemFileHandle>>(\n                  exception.wrap(js, kj::mv(ex)));\n            }\n            KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n              auto ex = js.domException(kj::str(\"TypeMismatchError\"), kj::str(\"Not a file\"));\n              return js.rejectedPromise<jsg::Ref<FileSystemFileHandle>>(\n                  exception.wrap(js, kj::mv(ex)));\n            }\n            KJ_CASE_ONEOF(err, workerd::FsError) {\n              return js.rejectedPromise<jsg::Ref<FileSystemFileHandle>>(\n                  exception.wrap(js, fsErrorToDomException(js, err)));\n            }\n          }\n          KJ_UNREACHABLE;\n        }\n\n        auto ex = js.domException(kj::str(\"NotFoundError\"), kj::str(\"Not found\"));\n        return js.rejectedPromise<jsg::Ref<FileSystemFileHandle>>(exception.wrap(js, kj::mv(ex)));\n      }\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        auto ex = js.domException(kj::str(\"TypeMismatchError\"), kj::str(\"Not a directory\"));\n        return js.rejectedPromise<jsg::Ref<FileSystemFileHandle>>(exception.wrap(js, kj::mv(ex)));\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        auto ex = js.domException(kj::str(\"TypeMismatchError\"), kj::str(\"Not a directory\"));\n        return js.rejectedPromise<jsg::Ref<FileSystemFileHandle>>(exception.wrap(js, kj::mv(ex)));\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  auto ex = js.domException(kj::str(\"NotFoundError\"), kj::str(\"Directory not found\"));\n  return js.rejectedPromise<jsg::Ref<FileSystemFileHandle>>(exception.wrap(js, kj::mv(ex)));\n}\n\njsg::Promise<jsg::Ref<FileSystemDirectoryHandle>> FileSystemDirectoryHandle::getDirectoryHandle(\n    jsg::Lock& js,\n    jsg::USVString name,\n    jsg::Optional<FileSystemGetDirectoryOptions> options,\n    const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& exception) {\n  if (!isValidFileName(name)) {\n    return js.rejectedPromise<jsg::Ref<FileSystemDirectoryHandle>>(\n        js.typeError(\"Invalid directory name\"));\n  }\n\n  kj::Maybe<FsType> createAs;\n  KJ_IF_SOME(opts, options) {\n    if (opts.create) createAs = FsType::DIRECTORY;\n  }\n\n  KJ_IF_SOME(existing, getVfs().resolve(js, getLocator(), {})) {\n    KJ_SWITCH_ONEOF(existing) {\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        return js.rejectedPromise<jsg::Ref<FileSystemDirectoryHandle>>(\n            exception.wrap(js, fsErrorToDomException(js, err)));\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        auto locator = KJ_ASSERT_NONNULL(getLocator().tryResolve(name));\n        auto relative = locator.getRelative();\n        KJ_IF_SOME(node,\n            dir->tryOpen(js, kj::Path({relative.name}),\n                Directory::OpenOptions{\n                  .createAs = createAs,\n                })) {\n          KJ_SWITCH_ONEOF(node) {\n            KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n              return js.resolvedPromise(js.alloc<FileSystemDirectoryHandle>(getVfs(),\n                  KJ_ASSERT_NONNULL(locator.resolve(kj::str(locator.getPathname(), \"/\"))),\n                  kj::mv(name)));\n            }\n            KJ_CASE_ONEOF(dir, kj::Rc<workerd::File>) {\n              auto ex =\n                  js.domException(kj::str(\"TypeMismatchError\"), kj::str(\"File name is a file\"));\n              return js.rejectedPromise<jsg::Ref<FileSystemDirectoryHandle>>(\n                  exception.wrap(js, kj::mv(ex)));\n            }\n            KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n              auto ex = js.domException(kj::str(\"TypeMismatchError\"), kj::str(\"Not a directory\"));\n              return js.rejectedPromise<jsg::Ref<FileSystemDirectoryHandle>>(\n                  exception.wrap(js, kj::mv(ex)));\n            }\n            KJ_CASE_ONEOF(err, workerd::FsError) {\n              return js.rejectedPromise<jsg::Ref<FileSystemDirectoryHandle>>(\n                  exception.wrap(js, fsErrorToDomException(js, err)));\n            }\n          }\n          KJ_UNREACHABLE;\n        }\n        // Could not open or create the directory.\n        auto ex =\n            js.domException(kj::str(\"NotFoundError\"), kj::str(\"Directory not opened or created\"));\n        return js.rejectedPromise<jsg::Ref<FileSystemDirectoryHandle>>(\n            exception.wrap(js, kj::mv(ex)));\n      }\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        auto ex = js.domException(kj::str(\"TypeMismatchError\"), kj::str(\"Not a directory\"));\n        return js.rejectedPromise<jsg::Ref<FileSystemDirectoryHandle>>(\n            exception.wrap(js, kj::mv(ex)));\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        auto ex = js.domException(kj::str(\"TypeMismatchError\"), kj::str(\"Not a directory\"));\n        return js.rejectedPromise<jsg::Ref<FileSystemDirectoryHandle>>(\n            exception.wrap(js, kj::mv(ex)));\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  auto ex = js.domException(kj::str(\"NotFoundError\"), kj::str(\"Directory not found\"));\n  return js.rejectedPromise<jsg::Ref<FileSystemDirectoryHandle>>(exception.wrap(js, kj::mv(ex)));\n}\n\njsg::Promise<void> FileSystemDirectoryHandle::removeEntry(jsg::Lock& js,\n    jsg::USVString name,\n    jsg::Optional<FileSystemRemoveOptions> options,\n    const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& exception) {\n  if (!isValidFileName(name)) {\n    return js.rejectedPromise<void>(js.typeError(\"Invalid name\"));\n  }\n  auto opts = options.orDefault(FileSystemRemoveOptions{});\n\n  KJ_IF_SOME(existing, getVfs().resolve(js, getLocator(), {})) {\n    KJ_SWITCH_ONEOF(existing) {\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        return js.rejectedPromise<void>(exception.wrap(js, fsErrorToDomException(js, err)));\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        kj::Path item({name});\n        auto fileLocator = KJ_ASSERT_NONNULL(getLocator().tryResolve(name));\n        if (getVfs().isLocked(js, fileLocator)) {\n          // If the file is locked, we cannot remove it.\n          auto ex = js.domException(kj::str(\"NoModificationAllowedError\"),\n              kj::str(\"Cannot remove an entry that is currently locked.\"));\n          return js.rejectedPromise<void>(exception.wrap(js, kj::mv(ex)));\n        }\n\n        KJ_SWITCH_ONEOF(dir->remove(js, item,\n                            workerd::Directory::RemoveOptions{\n                              .recursive = opts.recursive,\n                            })) {\n          KJ_CASE_ONEOF(res, bool) {\n            if (res) {\n              return js.resolvedPromise();\n            }\n            // If the entry was not found, we throw a NotFoundError.\n            auto ex = js.domException(kj::str(\"NotFoundError\"), kj::str(\"File not found\"));\n            return js.rejectedPromise<void>(exception.wrap(js, kj::mv(ex)));\n          }\n          KJ_CASE_ONEOF(error, workerd::FsError) {\n            return js.rejectedPromise<void>(exception.wrap(js, fsErrorToDomException(js, error)));\n          }\n        }\n        KJ_UNREACHABLE;\n      }\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        auto ex = js.domException(kj::str(\"TypeMismatchError\"), kj::str(\"Not a directory\"));\n        return js.rejectedPromise<void>(exception.wrap(js, kj::mv(ex)));\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        auto ex = js.domException(kj::str(\"TypeMismatchError\"), kj::str(\"Not a directory\"));\n        return js.rejectedPromise<void>(exception.wrap(js, kj::mv(ex)));\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  auto ex = js.domException(kj::str(\"NotFoundError\"), kj::str(\"Not found\"));\n  return js.rejectedPromise<void>(exception.wrap(js, kj::mv(ex)));\n}\n\njsg::Promise<kj::Array<jsg::USVString>> FileSystemDirectoryHandle::resolve(\n    jsg::Lock& js, jsg::Ref<FileSystemHandle> possibleDescendant) {\n  JSG_FAIL_REQUIRE(Error, \"Not implemented\");\n}\n\nnamespace {\nkj::Array<jsg::Ref<FileSystemHandle>> collectEntries(const workerd::VirtualFileSystem& vfs,\n    jsg::Lock& js,\n    kj::Rc<workerd::Directory> inner,\n    const jsg::Url& parentLocator) {\n  kj::Vector<jsg::Ref<FileSystemHandle>> entries;\n  for (auto& entry: *inner.get()) {\n    KJ_SWITCH_ONEOF(entry.value) {\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        auto locator = KJ_ASSERT_NONNULL(parentLocator.tryResolve(entry.key));\n        entries.add(js.alloc<FileSystemFileHandle>(\n            vfs, kj::mv(locator), jsg::USVString(kj::str(entry.key))));\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        auto locator = KJ_ASSERT_NONNULL(parentLocator.tryResolve(kj::str(entry.key, \"/\")));\n        entries.add(js.alloc<FileSystemDirectoryHandle>(\n            vfs, kj::mv(locator), jsg::USVString(kj::str(entry.key))));\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        SymbolicLinkRecursionGuardScope guardScope;\n        KJ_IF_SOME(_, guardScope.checkSeen(link.get())) {\n          // Throw a DOMException indicating that the symbolic link is recursive.\n          JSG_FAIL_REQUIRE(DOMOperationError, \"Symbolic link recursion detected\"_kj);\n        }\n        KJ_IF_SOME(res, link->resolve(js)) {\n          KJ_SWITCH_ONEOF(res) {\n            KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n              auto locator = KJ_ASSERT_NONNULL(parentLocator.tryResolve(entry.key));\n              entries.add(js.alloc<FileSystemFileHandle>(\n                  vfs, kj::mv(locator), jsg::USVString(kj::str(entry.key))));\n            }\n            KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n              auto locator = KJ_ASSERT_NONNULL(parentLocator.tryResolve(kj::str(entry.key, \"/\")));\n              entries.add(js.alloc<FileSystemDirectoryHandle>(\n                  vfs, kj::mv(locator), jsg::USVString(kj::str(entry.key))));\n            }\n            KJ_CASE_ONEOF(_, workerd::FsError) {\n              JSG_FAIL_REQUIRE(DOMOperationError, \"Symbolic link recursion detected\"_kj);\n            }\n          }\n        }\n      }\n    }\n  }\n  return entries.releaseAsArray();\n}\n\nauto resolveDirectoryHandle(jsg::Lock& js, const VirtualFileSystem& vfs, const jsg::Url& locator) {\n  auto pathname = locator.getPathname();\n  if (pathname.endsWith(\"/\"_kj)) {\n    pathname = pathname.first(pathname.size() - 1);\n    auto cloned = locator.clone();\n    cloned.setPathname(pathname);\n    return vfs.resolve(js, cloned, {});\n  }\n  // Otherwise fall-back to the original locator.\n  return vfs.resolve(js, locator, {});\n}\n}  // namespace\n\njsg::Ref<FileSystemDirectoryHandle::EntryIterator> FileSystemDirectoryHandle::entries(\n    jsg::Lock& js) {\n  KJ_IF_SOME(existing, resolveDirectoryHandle(js, getVfs(), getLocator())) {\n    KJ_SWITCH_ONEOF(existing) {\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        JSG_FAIL_REQUIRE(DOMOperationError, \"Failed to read directory: \", static_cast<int>(err));\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        return js.alloc<EntryIterator>(\n            IteratorState(JSG_THIS, collectEntries(getVfs(), js, kj::mv(dir), getLocator())));\n      }\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        JSG_FAIL_REQUIRE(DOMTypeMismatchError, \"Not a directory\");\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        JSG_FAIL_REQUIRE(DOMTypeMismatchError, \"Not a directory\");\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  // The directory was not found. However, for some weird reason the spec requires that\n  // we still return an iterator here but it needs to throw a NotFoundError when next\n  // is actually called.\n  auto ex = js.domException(kj::str(\"NotFoundError\"), kj::str(\"Not found\"));\n  auto handle = jsg::JsValue(KJ_ASSERT_NONNULL(ex.tryGetHandle(js)));\n  return js.alloc<EntryIterator>(IteratorState(jsg::JsRef(js, handle)));\n}\n\njsg::Ref<FileSystemDirectoryHandle::KeyIterator> FileSystemDirectoryHandle::keys(jsg::Lock& js) {\n  KJ_IF_SOME(existing, resolveDirectoryHandle(js, getVfs(), getLocator())) {\n    KJ_SWITCH_ONEOF(existing) {\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        JSG_FAIL_REQUIRE(DOMOperationError, \"Failed to read directory: \", static_cast<int>(err));\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        return js.alloc<KeyIterator>(\n            IteratorState(JSG_THIS, collectEntries(getVfs(), js, kj::mv(dir), getLocator())));\n      }\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        JSG_FAIL_REQUIRE(DOMTypeMismatchError, \"Not a directory\");\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        JSG_FAIL_REQUIRE(DOMTypeMismatchError, \"Not a directory\");\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  // The directory was not found. However, for some weird reason the spec requires that\n  // we still return an iterator here but it needs to throw a NotFoundError when next\n  // is actually called.\n  auto ex = js.domException(kj::str(\"NotFoundError\"), kj::str(\"Not found\"));\n  auto handle = jsg::JsValue(KJ_ASSERT_NONNULL(ex.tryGetHandle(js)));\n  return js.alloc<KeyIterator>(IteratorState(jsg::JsRef(js, handle)));\n}\n\njsg::Ref<FileSystemDirectoryHandle::ValueIterator> FileSystemDirectoryHandle::values(\n    jsg::Lock& js) {\n  KJ_IF_SOME(existing, resolveDirectoryHandle(js, getVfs(), getLocator())) {\n    KJ_SWITCH_ONEOF(existing) {\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        JSG_FAIL_REQUIRE(DOMOperationError, \"Failed to read directory: \", static_cast<int>(err));\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        return js.alloc<ValueIterator>(\n            IteratorState(JSG_THIS, collectEntries(getVfs(), js, kj::mv(dir), getLocator())));\n      }\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        JSG_FAIL_REQUIRE(DOMTypeMismatchError, \"Not a directory\");\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        JSG_FAIL_REQUIRE(DOMTypeMismatchError, \"Not a directory\");\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  // The directory was not found. However, for some weird reason the spec requires that\n  // we still return an iterator here but it needs to throw a NotFoundError when next\n  // is actually called.\n  auto ex = js.domException(kj::str(\"NotFoundError\"), kj::str(\"Not found\"));\n  auto handle = jsg::JsValue(KJ_ASSERT_NONNULL(ex.tryGetHandle(js)));\n  return js.alloc<ValueIterator>(IteratorState(jsg::JsRef(js, handle)));\n}\n\nvoid FileSystemDirectoryHandle::forEach(jsg::Lock& js,\n    jsg::Function<void(\n        jsg::USVString, jsg::Ref<FileSystemHandle>, jsg::Ref<FileSystemDirectoryHandle>)> callback,\n    jsg::Optional<jsg::Value> thisArg,\n    const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& exception) {\n\n  KJ_IF_SOME(existing, resolveDirectoryHandle(js, getVfs(), getLocator())) {\n    KJ_SWITCH_ONEOF(existing) {\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        js.throwException(js.v8Ref(exception.wrap(js, fsErrorToDomException(js, err))));\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        auto receiver = js.v8Undefined();\n        KJ_IF_SOME(arg, thisArg) {\n          auto handle = arg.getHandle(js);\n          if (!handle->IsNullOrUndefined()) {\n            receiver = handle;\n          }\n        }\n        callback.setReceiver(js.v8Ref(receiver));\n\n        for (auto& entry: collectEntries(getVfs(), js, kj::mv(dir), getLocator())) {\n          callback(js, jsg::USVString(kj::str(entry->getName(js))), entry.addRef(), JSG_THIS);\n        }\n        return;\n      }\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        JSG_FAIL_REQUIRE(DOMTypeMismatchError, \"Not a directory\");\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        JSG_FAIL_REQUIRE(DOMTypeMismatchError, \"Not a directory\");\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  JSG_FAIL_REQUIRE(DOMNotFoundError, \"Not found\");\n}\n\nFileSystemFileHandle::FileSystemFileHandle(\n    const workerd::VirtualFileSystem& vfs, jsg::Url locator, jsg::USVString name)\n    : FileSystemHandle(vfs, kj::mv(locator), kj::mv(name)) {}\n\njsg::Promise<jsg::Ref<File>> FileSystemFileHandle::getFile(\n    jsg::Lock& js, const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& deHandler) {\n  // TODO(node-fs): Currently this copies the file data into the new File object.\n  // Alternatively, File/Blob can be modified to allow it to be backed by a\n  // workerd::File such that it does not need to create a separate in-memory\n  // copy of the data. We can make that optimization as a follow-up, however.\n\n  // First, let's use the locator and vfs to see if the file actually exists.\n  KJ_IF_SOME(item, getVfs().resolve(js, getLocator(), {})) {\n    KJ_SWITCH_ONEOF(item) {\n      KJ_CASE_ONEOF(err, workerd::FsError) {\n        return js.rejectedPromise<jsg::Ref<File>>(\n            deHandler.wrap(js, fsErrorToDomException(js, err)));\n      }\n      KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n        auto stat = file->stat(js);\n        KJ_SWITCH_ONEOF(file->readAllBytes(js)) {\n          KJ_CASE_ONEOF(bytes, jsg::BufferSource) {\n            return js.resolvedPromise(\n                js.alloc<File>(js, kj::mv(bytes), jsg::USVString(kj::str(getName(js))),\n                    kj::String(), (stat.lastModified - kj::UNIX_EPOCH) / kj::MILLISECONDS));\n          }\n          KJ_CASE_ONEOF(err, workerd::FsError) {\n            return js.rejectedPromise<jsg::Ref<File>>(\n                deHandler.wrap(js, fsErrorToDomException(js, err)));\n          }\n        }\n        KJ_UNREACHABLE;\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n        auto ex = js.domException(kj::str(\"TypeMismatchError\"), kj::str(\"Is a directory\"));\n        return js.rejectedPromise<jsg::Ref<File>>(deHandler.wrap(js, kj::mv(ex)));\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n        auto ex = js.domException(kj::str(\"TypeMismatchError\"), kj::str(\"Is a symbolic link\"));\n        return js.rejectedPromise<jsg::Ref<File>>(deHandler.wrap(js, kj::mv(ex)));\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  // If the file does not exist, we reject the promise with a NotFoundError.\n  auto ex = js.domException(kj::str(\"NotFoundError\"), kj::str(\"Not found\"));\n  return js.rejectedPromise<jsg::Ref<File>>(deHandler.wrap(js, kj::mv(ex)));\n}\n\njsg::Promise<jsg::Ref<FileSystemWritableFileStream>> FileSystemFileHandle::createWritable(\n    jsg::Lock& js,\n    jsg::Optional<FileSystemCreateWritableOptions> options,\n    const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& deHandler,\n    const jsg::TypeHandler<FileSystemWritableData>& dataHandler) {\n\n  // Per the spec, the writable stream we create here is expected to write into\n  // a temporary space until the stream is closed. When closed, the original file\n  // contents are replaced with the new contents. If the stream is aborted or\n  // errored, the temporary file data is discarded.\n  auto opts = options.orDefault(FileSystemCreateWritableOptions{});\n\n  // If keepExistingData is true, the temporary file is created with a copy of\n  // the original file data. Otherwise, the temporary file is created empty,\n  // which means that if we create a writable stream and close it without writing\n  // anything, the original file data is lost.\n  bool keepExistingData = opts.keepExistingData.orDefault(false);\n\n  kj::Maybe<kj::Rc<workerd::File>> fileData;\n  KJ_IF_SOME(existing, getVfs().resolve(js, getLocator(), {})) {\n    if (keepExistingData) {\n      KJ_SWITCH_ONEOF(existing) {\n        KJ_CASE_ONEOF(err, workerd::FsError) {\n          return js.rejectedPromise<jsg::Ref<FileSystemWritableFileStream>>(\n              deHandler.wrap(js, fsErrorToDomException(js, err)));\n        }\n        KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n          KJ_SWITCH_ONEOF(file->clone(js)) {\n            KJ_CASE_ONEOF(err, workerd::FsError) {\n              return js.rejectedPromise<jsg::Ref<FileSystemWritableFileStream>>(\n                  deHandler.wrap(js, fsErrorToDomException(js, err)));\n            }\n            KJ_CASE_ONEOF(cloned, kj::Rc<workerd::File>) {\n              fileData = kj::mv(cloned);\n            }\n          }\n        }\n        KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n          auto ex = js.domException(kj::str(\"TypeMismatchError\"), kj::str(\"Is a directory\"));\n          return js.rejectedPromise<jsg::Ref<FileSystemWritableFileStream>>(\n              deHandler.wrap(js, kj::mv(ex)));\n        }\n        KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n          auto ex = js.domException(kj::str(\"TypeMismatchError\"), kj::str(\"Is a symbolic link\"));\n          return js.rejectedPromise<jsg::Ref<FileSystemWritableFileStream>>(\n              deHandler.wrap(js, kj::mv(ex)));\n        }\n      }\n    } else {\n      fileData = workerd::File::newWritable(js);\n    }\n  } else {\n    auto ex = js.domException(kj::str(\"NotFoundError\"), kj::str(\"File not found\"));\n    return js.rejectedPromise<jsg::Ref<FileSystemWritableFileStream>>(\n        deHandler.wrap(js, kj::mv(ex)));\n  }\n\n  auto sharedState = kj::rc<FileSystemWritableFileStream::State>(\n      js, getVfs(), JSG_THIS, KJ_ASSERT_NONNULL(kj::mv(fileData)));\n  auto stream =\n      js.alloc<FileSystemWritableFileStream>(newWritableStreamJsController(), sharedState.addRef());\n\n  UnderlyingSink sink;\n  // Per the WHATWG spec, the type property for WritableStream's underlying sink must be undefined.\n  // The \"bytes\" type is only valid for ReadableStream. When pedantic_wpt is not set, we preserve\n  // the legacy behavior of setting the type to \"bytes\".\n  if (!FeatureFlags::get(js).getPedanticWpt()) {\n    sink.type = kj::str(\"bytes\");\n  }\n  sink.write = [state = sharedState.addRef(), &deHandler, &dataHandler](\n                   jsg::Lock& js, v8::Local<v8::Value> chunk, auto c) mutable {\n    return js.tryCatch([&] {\n      KJ_IF_SOME(unwrapped, dataHandler.tryUnwrap(js, chunk)) {\n        return FileSystemWritableFileStream::writeImpl(\n            js, kj::mv(unwrapped), *state.get(), deHandler);\n      }\n      return js.rejectedPromise<void>(\n          js.typeError(\"WritableStream received a value that is not writable\"));\n    }, [&](jsg::Value exception) { return js.rejectedPromise<void>(kj::mv(exception)); });\n  };\n  sink.abort = [state = sharedState.addRef()](jsg::Lock& js, auto reason) mutable {\n    // When aborted, we just drop any of the written data on the floor.\n    state->clear();\n    return js.resolvedPromise();\n  };\n  sink.close = [state = sharedState.addRef(), &deHandler](jsg::Lock& js) mutable {\n    KJ_DEFER(state->clear());\n    return js.tryCatch([&] {\n      KJ_IF_SOME(temp, state->temp) {\n        auto basePath = kj::str(state->file->getLocator().getPathname().slice(1));\n        kj::Path root{};\n        auto base = root.eval(basePath);\n\n        KJ_IF_SOME(existing,\n            state->vfs.getRoot(js)->tryOpen(js, base,\n                {\n                  .createAs = workerd::FsType::FILE,\n                })) {\n          KJ_SWITCH_ONEOF(existing) {\n            KJ_CASE_ONEOF(err, workerd::FsError) {\n              return js.rejectedPromise<void>(deHandler.wrap(js, fsErrorToDomException(js, err)));\n            }\n            KJ_CASE_ONEOF(dir, kj::Rc<workerd::Directory>) {\n              auto ex = js.domException(kj::str(\"TypeMismatchError\"), kj::str(\"Is a directory\"));\n              return js.rejectedPromise<void>(deHandler.wrap(js, kj::mv(ex)));\n            }\n            KJ_CASE_ONEOF(file, kj::Rc<workerd::File>) {\n              KJ_IF_SOME(err, file->replace(js, temp.addRef())) {\n                return js.rejectedPromise<void>(deHandler.wrap(js, fsErrorToDomException(js, err)));\n              }\n              return js.resolvedPromise();\n            }\n            KJ_CASE_ONEOF(link, kj::Rc<workerd::SymbolicLink>) {\n              auto ex =\n                  js.domException(kj::str(\"TypeMismatchError\"), kj::str(\"Is a symbolic link\"));\n              return js.rejectedPromise<void>(deHandler.wrap(js, kj::mv(ex)));\n            }\n          }\n          KJ_UNREACHABLE;\n        }\n        auto ex =\n            js.domException(kj::str(\"InvalidStateError\"), kj::str(\"Failed to open or create file\"));\n        return js.rejectedPromise<void>(deHandler.wrap(js, kj::mv(ex)));\n      }\n      return js.resolvedPromise();\n    }, [&](jsg::Value exception) { return js.rejectedPromise<void>(kj::mv(exception)); });\n  };\n  stream->getController().setup(js, kj::mv(sink), kj::none);\n\n  return js.resolvedPromise(kj::mv(stream));\n}\n\nFileSystemWritableFileStream::FileSystemWritableFileStream(\n    kj::Own<WritableStreamController> controller, kj::Rc<State> sharedState)\n    : WritableStream(kj::mv(controller)),\n      sharedState(kj::mv(sharedState)) {}\n\njsg::Promise<void> FileSystemWritableFileStream::write(jsg::Lock& js,\n    kj::OneOf<jsg::Ref<Blob>, jsg::BufferSource, kj::String, WriteParams> data,\n    const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& deHandler) {\n  JSG_REQUIRE(!getController().isLockedToWriter(), TypeError,\n      \"Cannot write to a stream that is locked to a reader\");\n  auto writer = getWriter(js);\n  KJ_DEFER(writer->releaseLock(js));\n  return writeImpl(js, kj::mv(data), *sharedState.get(), deHandler);\n}\n\njsg::Promise<void> FileSystemWritableFileStream::writeImpl(jsg::Lock& js,\n    FileSystemWritableData data,\n    State& state,\n    const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& deHandler) {\n  KJ_IF_SOME(inner, state.temp) {\n    return js.tryCatch([&] {\n      KJ_SWITCH_ONEOF(data) {\n        KJ_CASE_ONEOF(blob, jsg::Ref<Blob>) {\n          KJ_SWITCH_ONEOF(inner->write(js, state.position, blob->getData())) {\n            KJ_CASE_ONEOF(written, uint32_t) {\n              state.position += written;\n            }\n            KJ_CASE_ONEOF(err, workerd::FsError) {\n              return js.rejectedPromise<void>(deHandler.wrap(js, fsErrorToDomException(js, err)));\n            }\n          }\n        }\n        KJ_CASE_ONEOF(buffer, jsg::BufferSource) {\n          KJ_SWITCH_ONEOF(inner->write(js, state.position, buffer)) {\n            KJ_CASE_ONEOF(written, uint32_t) {\n              state.position += written;\n            }\n            KJ_CASE_ONEOF(err, workerd::FsError) {\n              return js.rejectedPromise<void>(deHandler.wrap(js, fsErrorToDomException(js, err)));\n            }\n          }\n        }\n        KJ_CASE_ONEOF(str, kj::String) {\n          KJ_SWITCH_ONEOF(inner->write(js, state.position, str)) {\n            KJ_CASE_ONEOF(written, uint32_t) {\n              state.position += written;\n            }\n            KJ_CASE_ONEOF(err, workerd::FsError) {\n              return js.rejectedPromise<void>(deHandler.wrap(js, fsErrorToDomException(js, err)));\n            }\n          }\n        }\n        KJ_CASE_ONEOF(params, WriteParams) {\n          uint32_t offset = state.position;\n          KJ_IF_SOME(pos, params.position) {\n            auto stat = inner->stat(js);\n            if (pos > stat.size) {\n              KJ_IF_SOME(err, inner->resize(js, offset)) {\n                return js.rejectedPromise<void>(deHandler.wrap(js, fsErrorToDomException(js, err)));\n              }\n            }\n            offset = pos;\n          }\n\n          if (params.type == \"write\"_kj) {\n            KJ_IF_SOME(maybeData, params.data) {\n              KJ_IF_SOME(data, maybeData) {\n                KJ_SWITCH_ONEOF(data) {\n                  KJ_CASE_ONEOF(blob, jsg::Ref<Blob>) {\n                    KJ_SWITCH_ONEOF(inner->write(js, offset, blob->getData())) {\n                      KJ_CASE_ONEOF(written, uint32_t) {\n                        state.position = offset + written;\n                        return js.resolvedPromise();\n                      }\n                      KJ_CASE_ONEOF(err, workerd::FsError) {\n                        return js.rejectedPromise<void>(\n                            deHandler.wrap(js, fsErrorToDomException(js, err)));\n                      }\n                    }\n                    KJ_UNREACHABLE;\n                  }\n                  KJ_CASE_ONEOF(buffer, jsg::BufferSource) {\n                    KJ_SWITCH_ONEOF(inner->write(js, offset, buffer)) {\n                      KJ_CASE_ONEOF(written, uint32_t) {\n                        state.position = offset + written;\n                        return js.resolvedPromise();\n                      }\n                      KJ_CASE_ONEOF(err, workerd::FsError) {\n                        return js.rejectedPromise<void>(\n                            deHandler.wrap(js, fsErrorToDomException(js, err)));\n                      }\n                    }\n                    KJ_UNREACHABLE;\n                  }\n                  KJ_CASE_ONEOF(str, kj::String) {\n                    KJ_SWITCH_ONEOF(inner->write(js, offset, str)) {\n                      KJ_CASE_ONEOF(written, uint32_t) {\n                        state.position = offset + written;\n                        return js.resolvedPromise();\n                      }\n                      KJ_CASE_ONEOF(err, workerd::FsError) {\n                        return js.rejectedPromise<void>(\n                            deHandler.wrap(js, fsErrorToDomException(js, err)));\n                      }\n                    }\n                  }\n                  KJ_UNREACHABLE;\n                }\n              } else {\n                return js.rejectedPromise<void>(\n                    js.typeError(\"write() requires a non-null data parameter\"));\n              }\n            }\n\n            return js.rejectedPromise<void>(deHandler.wrap(js,\n                js.domException(kj::str(\"SyntaxError\"),\n                    kj::str(\"write() requires a non-null data parameter\"))));\n\n          } else if (params.type == \"seek\"_kj) {\n            uint32_t pos;\n            KJ_IF_SOME(s, params.position) {\n              pos = s;\n            } else {\n              return js.rejectedPromise<void>(deHandler.wrap(js,\n                  js.domException(\n                      kj::str(\"SyntaxError\"), kj::str(\"seek() requires a position parameter\"))));\n            }\n            state.position = pos;\n            auto stat = inner->stat(js);\n            if (state.position > stat.size) {\n              KJ_IF_SOME(err, inner->resize(js, state.position)) {\n                return js.rejectedPromise<void>(deHandler.wrap(js, fsErrorToDomException(js, err)));\n              }\n            }\n          } else if (params.type == \"truncate\"_kj) {\n            uint32_t size = 0;\n            KJ_IF_SOME(s, params.size) {\n              size = s;\n            } else {\n              return js.rejectedPromise<void>(deHandler.wrap(js,\n                  js.domException(\n                      kj::str(\"SyntaxError\"), kj::str(\"truncate() requires a size parameter\"))));\n            }\n            KJ_IF_SOME(err, inner->resize(js, size)) {\n              return js.rejectedPromise<void>(deHandler.wrap(js, fsErrorToDomException(js, err)));\n            }\n            auto stat = inner->stat(js);\n            if (state.position > stat.size) {\n              state.position = stat.size;\n            }\n          } else {\n            return js.rejectedPromise<void>(\n                js.typeError(kj::str(\"Invalid write type: \", params.type)));\n          }\n        }\n      }\n\n      return js.resolvedPromise();\n    }, [&](jsg::Value exception) { return js.rejectedPromise<void>(kj::mv(exception)); });\n  }\n\n  return js.rejectedPromise<void>(js.typeError(\"write() after closed\"));\n}\n\njsg::Promise<void> FileSystemWritableFileStream::seek(jsg::Lock& js,\n    uint32_t position,\n    const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& deHandler) {\n  KJ_IF_SOME(inner, sharedState->temp) {\n    auto stat = inner->stat(js);\n    if (position > stat.size) {\n      KJ_IF_SOME(err, inner->resize(js, position)) {\n        return js.rejectedPromise<void>(deHandler.wrap(js, fsErrorToDomException(js, err)));\n      }\n    }\n    sharedState->position = position;\n    return js.resolvedPromise();\n  }\n\n  return js.rejectedPromise<void>(js.typeError(\"seek() after closed\"));\n}\n\njsg::Promise<void> FileSystemWritableFileStream::truncate(\n    jsg::Lock& js, uint32_t size, const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& deHandler) {\n  KJ_IF_SOME(inner, sharedState->temp) {\n    KJ_IF_SOME(err, inner->resize(js, size)) {\n      return js.rejectedPromise<void>(deHandler.wrap(js, fsErrorToDomException(js, err)));\n    }\n    auto stat = inner->stat(js);\n    if (sharedState->position > stat.size) {\n      sharedState->position = stat.size;\n    }\n    return js.resolvedPromise();\n  }\n\n  return js.rejectedPromise<void>(js.typeError(\"seek() after closed\"));\n}\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/filesystem.h",
    "content": "#pragma once\n\n#include <workerd/api/streams/writable.h>\n#include <workerd/io/worker-fs.h>\n#include <workerd/jsg/iterator.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api {\n\nclass Blob;\nclass File;\n\nclass URL;  // Legacy URL class impl\nnamespace url {\nclass URL;\n}  // namespace url\n\n// Metadata about a file, directory, or link.\nstruct Stat {\n  kj::StringPtr type;  // One of either \"file\", \"directory\", or \"symlink\"\n  uint32_t size;\n  uint64_t lastModified;\n  uint64_t created;\n  bool writable;\n  bool device;\n  JSG_STRUCT(type, size, lastModified, created, writable, device);\n\n  Stat(const workerd::Stat& stat);\n};\n\n// A simple RAII handle for a file descriptor. When the instance is\n// destroyed, the file descriptor is closed if it hasn't already\n// been closed.\nclass FileFdHandle final: public jsg::Object {\n public:\n  FileFdHandle(jsg::Lock& js, const workerd::VirtualFileSystem& vfs, int fd);\n  ~FileFdHandle() noexcept;\n\n  static jsg::Ref<FileFdHandle> constructor(jsg::Lock& js, int fd);\n\n  void close(jsg::Lock&);\n\n  JSG_RESOURCE_TYPE(FileFdHandle) {\n    JSG_METHOD(close);\n  }\n\n private:\n  kj::Maybe<kj::Own<void>> fdHandle;\n  v8::Isolate* isolate;\n};\n\nclass FileSystemModule final: public jsg::Object {\n public:\n  using FilePath = kj::OneOf<jsg::Ref<URL>, jsg::Ref<url::URL>>;\n\n  struct StatOptions {\n    jsg::Optional<bool> followSymlinks;\n    JSG_STRUCT(followSymlinks);\n  };\n\n  kj::Maybe<Stat> stat(jsg::Lock& js, kj::OneOf<int, FilePath> pathOrFd, StatOptions options);\n  void setLastModified(\n      jsg::Lock& js, kj::OneOf<int, FilePath> pathOrFd, kj::Date lastModified, StatOptions options);\n  void truncate(jsg::Lock& js, kj::OneOf<int, FilePath> pathOrFd, uint32_t size);\n\n  struct ReadLinkOptions {\n    // The readLink method is used for both realpath and readlink. The readlink\n    // API will throw an error if the path is not a symlink. The realpath API\n    // will return the resolved path either way.\n    bool failIfNotSymlink = false;\n    JSG_STRUCT(failIfNotSymlink);\n  };\n  kj::String readLink(jsg::Lock& js, FilePath path, ReadLinkOptions options);\n\n  struct LinkOptions {\n    bool symbolic;\n    JSG_STRUCT(symbolic);\n  };\n\n  void link(jsg::Lock& js, FilePath from, FilePath to, LinkOptions options);\n\n  void unlink(jsg::Lock& js, FilePath path);\n\n  struct OpenOptions {\n    // File is opened to supprt reads.\n    bool read;\n    // File is opened to support writes.\n    bool write;\n    // File is opened in append mode. Ignored if write is false.\n    bool append;\n    // If the exclusive option is set, throw if the file already exists.\n    bool exclusive;\n    // If the followSymlinks option is set, follow symbolic links.\n    bool followSymlinks = true;\n    JSG_STRUCT(read, write, append, exclusive, followSymlinks);\n  };\n\n  int open(jsg::Lock& js, FilePath path, OpenOptions options);\n  void close(jsg::Lock& js, int fd);\n\n  struct WriteOptions {\n    kj::Maybe<uint64_t> position;\n    JSG_STRUCT(position);\n  };\n\n  uint32_t write(jsg::Lock& js, int fd, kj::Array<jsg::BufferSource> data, WriteOptions options);\n  uint32_t read(jsg::Lock& js, int fd, kj::Array<jsg::BufferSource> data, WriteOptions options);\n\n  jsg::BufferSource readAll(jsg::Lock& js, kj::OneOf<int, FilePath> pathOrFd);\n\n  struct WriteAllOptions {\n    bool exclusive;\n    bool append;\n    JSG_STRUCT(exclusive, append);\n  };\n\n  uint32_t writeAll(jsg::Lock& js,\n      kj::OneOf<int, FilePath> pathOrFd,\n      jsg::BufferSource data,\n      WriteAllOptions options);\n\n  struct RenameOrCopyOptions {\n    bool copy;\n    JSG_STRUCT(copy);\n  };\n\n  void renameOrCopy(jsg::Lock& js, FilePath src, FilePath dest, RenameOrCopyOptions options);\n\n  struct MkdirOptions {\n    bool recursive = false;\n    // When temp is true, the directory name will be augmented with an\n    // additional random string to make it unique and the directory name\n    // will be returned.\n    bool tmp = false;\n    JSG_STRUCT(recursive, tmp);\n  };\n  jsg::Optional<kj::String> mkdir(jsg::Lock& js, FilePath path, MkdirOptions options);\n\n  struct RmOptions {\n    bool recursive = false;\n    bool force = false;\n    bool dironly = false;\n    JSG_STRUCT(recursive, force, dironly);\n  };\n  void rm(jsg::Lock& js, FilePath path, RmOptions options);\n\n  struct DirEntHandle {\n    kj::String name;\n    kj::String parentPath;\n    int type;\n    JSG_STRUCT(name, parentPath, type);\n  };\n  struct ReadDirOptions {\n    bool recursive = false;\n    JSG_STRUCT(recursive);\n  };\n  kj::Array<DirEntHandle> readdir(jsg::Lock& js, FilePath path, ReadDirOptions options);\n\n  FileSystemModule() = default;\n  FileSystemModule(jsg::Lock&, const jsg::Url&) {}\n\n  jsg::Ref<FileFdHandle> getFdHandle(jsg::Lock& js, int fd) {\n    return FileFdHandle::constructor(js, fd);\n  }\n\n  struct CpOptions {\n    bool deferenceSymlinks;\n    bool recursive;\n    bool force;\n    bool errorOnExist;\n    JSG_STRUCT(deferenceSymlinks, recursive, force, errorOnExist);\n  };\n\n  void cp(jsg::Lock& js, FilePath src, FilePath dest, CpOptions options);\n\n  struct OpenAsBlobOptions {\n    jsg::Optional<kj::String> type;\n    JSG_STRUCT(type);\n  };\n  jsg::Ref<Blob> openAsBlob(jsg::Lock& js, FilePath path, OpenAsBlobOptions options);\n\n  JSG_RESOURCE_TYPE(FileSystemModule) {\n    JSG_METHOD(stat);\n    JSG_METHOD(setLastModified);\n    JSG_METHOD(truncate);\n    JSG_METHOD(readLink);\n    JSG_METHOD(link);\n    JSG_METHOD(unlink);\n    JSG_METHOD(open);\n    JSG_METHOD(close);\n    JSG_METHOD(write);\n    JSG_METHOD(read);\n    JSG_METHOD(readAll);\n    JSG_METHOD(writeAll);\n    JSG_METHOD(renameOrCopy);\n    JSG_METHOD(mkdir);\n    JSG_METHOD(rm);\n    JSG_METHOD(readdir);\n    JSG_METHOD(getFdHandle);\n    JSG_METHOD(cp);\n    JSG_METHOD(openAsBlob);\n  }\n\n private:\n  // Rather than relying on random generation of temp directory names we\n  // use a simple counter. Once the counter reaches the max value we will\n  // refuse to create any more temp directories. This is a bit of a hack\n  // to allow temp dirs to be created outside of an IoContext.\n  uint32_t tmpFileCounter = 0;\n};\n\n// ======================================================================================\n// An implementation of the WHATWG Web File System API (https://fs.spec.whatwg.org/)\n// All of the classes in this part of the impl are defined by the spec.\n\nclass FileSystemWritableFileStream;\n\n// This provides access to a file or directory in the virutual file system via a \"standardized\"\n// API. We use quotes around standardized because the API is still very much a work in progress\n// as a Web standard and is not fully baked yet, at least not within a single specification\n// document. There are bits and pieces of the API defined in various places, including the WHATWG\n// spec, WICG proposals, Web Platform Tests, etc. The Web Platform Tests include tests for parts of\n// the API that are not yet fully defined with the tests not actually marked as tentative, etc. We\n// implement what is defined so far using the Web Platform Tests and the specific characteristics\n// of the underlying virtual file system in workers as a guide.\n//\n// There are two kinds of handles, FileSystemFileHandle and FileSystemDirectoryHandle. Both of these\n// extend from FileSystemHandle. A FileSystemHandle holds the locator, which in our implementation\n// is a file URL pointing to a location in the worker's virtual file system. When the\n// FileSystemHandle is created, we validate that the locator points to a valid location in the\n// VFS, however the file may be modified, deleted, etc after the handle is created. This means\n// that the handle may become invalid after creation but then may become valid again. The actual\n// underlying file or directory is checked each time an operation is performed on the handle.\n//\n// Two handles are considered the same if they point to the same location and have the same type.\nclass FileSystemHandle: public jsg::Object {\n  // TODO(node-fs): The spec defines FileSystemHandle objects as being\n  // serializable, meaning that they should work with structured cloning.\n  // In workers, this would suggest also that they should work with jsrpc.\n  // We don't yet implement any form of serialization for these objects.\n  // Since each worker has its own file system, it might be a bit weird\n  // as the received file on the other side would not actually be in that\n  // destination worker's file system... that is, it would be an orphaned\n  // file handle. It would still likely be useful tho. As a follow up\n  // step, we will implement serialization/deserialization of these objects.\n public:\n  FileSystemHandle(const workerd::VirtualFileSystem& vfs, jsg::Url&& locator, jsg::USVString name);\n\n  const jsg::USVString& getName(jsg::Lock& js) {\n    return name;\n  }\n\n  virtual kj::StringPtr getKind(jsg::Lock& js) {\n    // Implemented by subclasses.\n    KJ_UNIMPLEMENTED(\"getKind() not implemented\");\n  }\n\n  jsg::Promise<bool> isSameEntry(jsg::Lock& js, jsg::Ref<FileSystemHandle> other);\n\n  struct RemoveOptions {\n    jsg::Optional<bool> recursive;\n    JSG_STRUCT(recursive);\n  };\n  jsg::Promise<void> remove(jsg::Lock& js,\n      jsg::Optional<RemoveOptions> options,\n      const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& deHandler);\n\n  jsg::Promise<kj::StringPtr> getUniqueId(\n      jsg::Lock& js, const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& deHandler);\n\n  JSG_RESOURCE_TYPE(FileSystemHandle) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(kind, getKind);\n    JSG_READONLY_PROTOTYPE_PROPERTY(name, getName);\n    JSG_METHOD(isSameEntry);\n    JSG_METHOD(getUniqueId);\n    JSG_METHOD(remove);\n  }\n\n protected:\n  const jsg::Url& getLocator() const {\n    return locator;\n  }\n  const workerd::VirtualFileSystem& getVfs() const {\n    return vfs;\n  }\n\n  bool canBeModifiedCurrently(jsg::Lock&) const;\n\n private:\n  const workerd::VirtualFileSystem& vfs;\n  const jsg::Url locator;\n  jsg::USVString name;\n};\n\nstruct FileSystemFileWriteParams {\n  kj::String type;  // one of: write, seek, truncate\n  jsg::Optional<uint32_t> size;\n  jsg::Optional<uint32_t> position;\n  // Yes, wrapping the kj::Maybe with a jsg::Optional is intentional here. We need to\n  // be able to accept null or undefined values and handle them per the spec.\n  jsg::Optional<kj::Maybe<kj::OneOf<jsg::Ref<Blob>, jsg::BufferSource, kj::String>>> data;\n  JSG_STRUCT(type, size, position, data);\n};\n\nusing FileSystemWritableData =\n    kj::OneOf<jsg::Ref<Blob>, jsg::BufferSource, kj::String, FileSystemFileWriteParams>;\n\nclass FileSystemFileHandle final: public FileSystemHandle {\n public:\n  FileSystemFileHandle(\n      const workerd::VirtualFileSystem& vfs, jsg::Url locator, jsg::USVString name);\n\n  kj::StringPtr getKind(jsg::Lock& js) override {\n    return \"file\"_kj;\n  }\n\n  struct FileSystemCreateWritableOptions {\n    jsg::Optional<bool> keepExistingData;\n    JSG_STRUCT(keepExistingData);\n  };\n\n  jsg::Promise<jsg::Ref<File>> getFile(\n      jsg::Lock& js, const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& deHandler);\n\n  jsg::Promise<jsg::Ref<FileSystemWritableFileStream>> createWritable(jsg::Lock& js,\n      jsg::Optional<FileSystemCreateWritableOptions> options,\n      const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& deHandler,\n      const jsg::TypeHandler<FileSystemWritableData>& dataHandler);\n\n  JSG_RESOURCE_TYPE(FileSystemFileHandle) {\n    JSG_INHERIT(FileSystemHandle);\n    JSG_METHOD(getFile);\n    JSG_METHOD(createWritable);\n  }\n\n private:\n  mutable size_t writableCount = 0;\n  friend class FileSystemWritableFileStream;\n};\n\nclass FileSystemDirectoryHandle final: public FileSystemHandle {\n private:\n  // The actual entry type is an array of two elements, the first being the key,\n  // the second being the value.\n  using EntryType = kj::OneOf<jsg::JsRef<jsg::JsString>, jsg::Ref<FileSystemHandle>>;\n  using EntryIteratorType = kj::Array<EntryType>;\n  using KeyIteratorType = jsg::USVString;\n  using ValueIteratorType = jsg::Ref<FileSystemHandle>;\n\n  struct IteratorState final {\n    struct Listing {\n      jsg::Ref<FileSystemDirectoryHandle> parent;\n      kj::Array<jsg::Ref<FileSystemHandle>> entries;\n      uint index = 0;\n    };\n    using Errored = jsg::JsRef<jsg::JsValue>;\n    kj::OneOf<Listing, Errored> state;\n\n    IteratorState(\n        jsg::Ref<FileSystemDirectoryHandle> parent, kj::Array<jsg::Ref<FileSystemHandle>> entries)\n        : state(Listing{\n            .parent = kj::mv(parent),\n            .entries = kj::mv(entries),\n          }) {}\n    IteratorState(jsg::JsRef<jsg::JsValue> exception): state(kj::mv(exception)) {}\n\n    void visitForGc(jsg::GcVisitor& visitor) {\n      KJ_SWITCH_ONEOF(state) {\n        KJ_CASE_ONEOF(listing, Listing) {\n          visitor.visit(listing.parent);\n          visitor.visitAll(listing.entries);\n        }\n        KJ_CASE_ONEOF(exception, jsg::JsRef<jsg::JsValue>) {\n          visitor.visit(exception);\n        }\n      }\n    }\n\n    JSG_MEMORY_INFO(IteratorState) {\n      KJ_SWITCH_ONEOF(state) {\n        KJ_CASE_ONEOF(listing, Listing) {\n          tracker.trackField(\"parent\", listing.parent);\n          for (auto& entry: listing.entries) {\n            tracker.trackField(\"entry\", entry);\n          }\n        }\n        KJ_CASE_ONEOF(exception, jsg::JsRef<jsg::JsValue>) {\n          tracker.trackField(\"exception\", exception);\n        }\n      }\n    }\n  };\n\n public:\n  FileSystemDirectoryHandle(\n      const workerd::VirtualFileSystem& vfs, jsg::Url locator, jsg::USVString name);\n\n  kj::StringPtr getKind(jsg::Lock& js) override {\n    return \"directory\"_kj;\n  }\n\n  struct FileSystemGetFileOptions {\n    bool create = false;\n    JSG_STRUCT(create);\n  };\n\n  struct FileSystemGetDirectoryOptions {\n    bool create = false;\n    JSG_STRUCT(create);\n  };\n\n  struct FileSystemRemoveOptions {\n    bool recursive = false;\n    JSG_STRUCT(recursive);\n  };\n\n  jsg::Promise<jsg::Ref<FileSystemFileHandle>> getFileHandle(jsg::Lock& js,\n      jsg::USVString name,\n      jsg::Optional<FileSystemGetFileOptions> options,\n      const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& exception);\n\n  jsg::Promise<jsg::Ref<FileSystemDirectoryHandle>> getDirectoryHandle(jsg::Lock& js,\n      jsg::USVString name,\n      jsg::Optional<FileSystemGetDirectoryOptions> options,\n      const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& exception);\n\n  jsg::Promise<void> removeEntry(jsg::Lock& js,\n      jsg::USVString name,\n      jsg::Optional<FileSystemRemoveOptions> options,\n      const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& exception);\n\n  // TODO(node-fs): We are not currently implementing the resolve() method\n  // as defined in the spec.\n  jsg::Promise<kj::Array<jsg::USVString>> resolve(\n      jsg::Lock& js, jsg::Ref<FileSystemHandle> possibleDescendant);\n\n  JSG_ASYNC_ITERATOR(EntryIterator,\n      entries,\n      EntryIteratorType,\n      IteratorState,\n      iteratorNext<EntryIteratorType>,\n      iteratorReturn<EntryIteratorType>);\n  JSG_ASYNC_ITERATOR(KeyIterator,\n      keys,\n      KeyIteratorType,\n      IteratorState,\n      iteratorNext<KeyIteratorType>,\n      iteratorReturn<KeyIteratorType>);\n  JSG_ASYNC_ITERATOR(ValueIterator,\n      values,\n      ValueIteratorType,\n      IteratorState,\n      iteratorNext<ValueIteratorType>,\n      iteratorReturn<ValueIteratorType>);\n\n  void forEach(jsg::Lock& js,\n      jsg::Function<void(\n          jsg::USVString, jsg::Ref<FileSystemHandle>, jsg::Ref<FileSystemDirectoryHandle>)>\n          callback,\n      jsg::Optional<jsg::Value> thisArg,\n      const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& exception);\n\n  JSG_RESOURCE_TYPE(FileSystemDirectoryHandle) {\n    JSG_INHERIT(FileSystemHandle);\n    JSG_METHOD(getFileHandle);\n    JSG_METHOD(getDirectoryHandle);\n    JSG_METHOD(removeEntry);\n    JSG_METHOD(resolve);\n    JSG_METHOD(entries);\n    JSG_METHOD(keys);\n    JSG_METHOD(values);\n    JSG_METHOD(forEach);\n    JSG_ASYNC_ITERABLE(entries);\n\n    JSG_TS_OVERRIDE({\n      entries(): AsyncIterableIterator<[string, FileSystemHandle]>;\n      [Symbol.asyncIterator]() : AsyncIterableIterator<[string, FileSystemHandle]>;\n    });\n  }\n\n private:\n  template <typename Type>\n  static jsg::Promise<kj::Maybe<Type>> iteratorNext(jsg::Lock& js, IteratorState& state) {\n    KJ_SWITCH_ONEOF(state.state) {\n      KJ_CASE_ONEOF(listing, IteratorState::Listing) {\n        if (listing.index >= listing.entries.size()) {\n          return js.resolvedPromise<kj::Maybe<Type>>(kj::none);\n        }\n\n        auto& entry = listing.entries[listing.index++];\n\n        if constexpr (kj::isSameType<Type, EntryIteratorType>()) {\n          EntryIteratorType result = kj::arr(\n              EntryType(jsg::JsRef(js, js.str(entry->getName(js)))), EntryType(entry.addRef()));\n          return js.resolvedPromise<kj::Maybe<Type>>(kj::mv(result));\n        } else if constexpr (kj::isSameType<Type, KeyIteratorType>()) {\n          return js.resolvedPromise<kj::Maybe<Type>>(jsg::USVString(kj::str(entry->getName(js))));\n        } else if constexpr (kj::isSameType<Type, ValueIteratorType>()) {\n          return js.resolvedPromise<kj::Maybe<Type>>(entry.addRef());\n        } else {\n          static_assert(false, \"invalid iterator type\");\n        }\n      }\n      KJ_CASE_ONEOF(exception, jsg::JsRef<jsg::JsValue>) {\n        return js.rejectedPromise<kj::Maybe<Type>>(exception.getHandle(js));\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  template <typename Type>\n  static jsg::Promise<void> iteratorReturn(\n      jsg::Lock& js, IteratorState& state, jsg::Optional<Type>& value) {\n    return js.resolvedPromise();\n  }\n};\n\nclass FileSystemWritableFileStream final: public WritableStream {\n public:\n  struct State final: public kj::Refcounted {\n    const workerd::VirtualFileSystem& vfs;\n\n    // The FileSystemWritableFileStream is a bit transactional in nature.\n    // All writes are done to a temporary file. When the stream is closed\n    // without an error, the original file data is replaced with the data\n    // in the temporary buffer. If the stream is aborted, the temporary file\n    // is deleted and the original contents are left intact.\n    // The temporary file is not visible to the user and is not included in\n    // any directory. It is only used to hold the data until the stream\n    // is closed.\n    kj::Maybe<kj::Rc<workerd::File>> temp;\n\n    // The file handle we are writing to\n    jsg::Ref<FileSystemFileHandle> file;\n\n    kj::Maybe<kj::Own<void>> lock;\n\n    // A note on file position and sizes. In the spec, file sizes and positions\n    // are defined in terms of unsigned long long, or 64-bits. We are using\n    // unsigned long (uint32_t) for these instead. This is largely because\n    // the underlying implementation is holding the file in memory and 4 GB\n    // is WAY more than our isolate heap limit. A uint32_t is more than enough\n    // for our purposes.\n    uint32_t position = 0;\n    State(jsg::Lock& js,\n        const workerd::VirtualFileSystem& vfs,\n        jsg::Ref<FileSystemFileHandle> file,\n        kj::Rc<workerd::File> temp)\n        : vfs(vfs),\n          temp(kj::mv(temp)),\n          file(kj::mv(file)),\n          lock(vfs.lock(js, this->file->getLocator())) {}\n\n    void clear() {\n      temp = kj::none;\n      position = 0;\n      lock = kj::none;\n    }\n  };\n\n  FileSystemWritableFileStream(\n      kj::Own<WritableStreamController> controller, kj::Rc<State> sharedState);\n\n  static jsg::Ref<FileSystemWritableFileStream> constructor() = delete;\n\n  using WriteParams = FileSystemFileWriteParams;\n\n  jsg::Promise<void> write(jsg::Lock& js,\n      FileSystemWritableData data,\n      const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& deHandler);\n  jsg::Promise<void> seek(jsg::Lock& js,\n      uint32_t position,\n      const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& deHandler);\n  jsg::Promise<void> truncate(\n      jsg::Lock& js, uint32_t size, const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& deHandler);\n\n  JSG_RESOURCE_TYPE(FileSystemWritableFileStream) {\n    JSG_INHERIT(WritableStream);\n    JSG_METHOD(write);\n    JSG_METHOD(seek);\n    JSG_METHOD(truncate);\n  }\n\n  static jsg::Promise<void> writeImpl(jsg::Lock& js,\n      FileSystemWritableData data, State& state,\n      const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& deHandler);\n\n private:\n  kj::Rc<State> sharedState;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(sharedState->file);\n  }\n};\n\nclass StorageManager final: public jsg::Object {\n public:\n  jsg::Promise<jsg::Ref<FileSystemDirectoryHandle>> getDirectory(\n      jsg::Lock& js, const jsg::TypeHandler<jsg::Ref<jsg::DOMException>>& exception);\n\n  JSG_RESOURCE_TYPE(StorageManager) {\n    JSG_METHOD(getDirectory);\n  }\n};\n\n#define EW_WEB_FILESYSTEM_ISOLATE_TYPE                                                             \\\n  workerd::api::FileSystemHandle, workerd::api::FileSystemFileHandle,                              \\\n      workerd::api::FileSystemDirectoryHandle, workerd::api::FileSystemWritableFileStream,         \\\n      workerd::api::StorageManager,                                                                \\\n      workerd::api::FileSystemFileHandle::FileSystemCreateWritableOptions,                         \\\n      workerd::api::FileSystemDirectoryHandle::FileSystemGetFileOptions,                           \\\n      workerd::api::FileSystemDirectoryHandle::FileSystemGetDirectoryOptions,                      \\\n      workerd::api::FileSystemDirectoryHandle::FileSystemRemoveOptions,                            \\\n      workerd::api::FileSystemWritableFileStream::WriteParams,                                     \\\n      workerd::api::FileSystemDirectoryHandle::EntryIterator,                                      \\\n      workerd::api::FileSystemDirectoryHandle::KeyIterator,                                        \\\n      workerd::api::FileSystemDirectoryHandle::ValueIterator,                                      \\\n      workerd::api::FileSystemDirectoryHandle::EntryIterator::Next,                                \\\n      workerd::api::FileSystemDirectoryHandle::KeyIterator::Next,                                  \\\n      workerd::api::FileSystemDirectoryHandle::ValueIterator::Next,                                \\\n      workerd::api::FileSystemHandle::RemoveOptions\n\n#define EW_FILESYSTEM_ISOLATE_TYPES                                                                \\\n  workerd::api::FileSystemModule, workerd::api::Stat, workerd::api::FileSystemModule::StatOptions, \\\n      workerd::api::FileSystemModule::ReadLinkOptions,                                             \\\n      workerd::api::FileSystemModule::LinkOptions, workerd::api::FileSystemModule::OpenOptions,    \\\n      workerd::api::FileSystemModule::WriteOptions,                                                \\\n      workerd::api::FileSystemModule::WriteAllOptions,                                             \\\n      workerd::api::FileSystemModule::RenameOrCopyOptions,                                         \\\n      workerd::api::FileSystemModule::MkdirOptions, workerd::api::FileSystemModule::RmOptions,     \\\n      workerd::api::FileSystemModule::DirEntHandle,                                                \\\n      workerd::api::FileSystemModule::ReadDirOptions, workerd::api::FileFdHandle,                  \\\n      workerd::api::FileSystemModule::CpOptions,                                                   \\\n      workerd::api::FileSystemModule::OpenAsBlobOptions\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/form-data.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"form-data.h\"\n\n#include \"util.h\"\n\n#include <workerd/io/io-util.h>\n#include <workerd/util/mimetype.h>\n#include <workerd/util/own-util.h>\n\n#include <kj/compat/http.h>\n#include <kj/parse/char.h>\n#include <kj/vector.h>\n\n#include <algorithm>\n#include <regex>\n\n#if !_MSC_VER\n#include <strings.h>\n#endif\n\nnamespace workerd::api {\n\nnamespace {\n// Like split() in kj/compat/url.c++, but splits at a substring rather than a character.\nkj::ArrayPtr<const char> splitAtSubString(kj::ArrayPtr<const char>& text, kj::StringPtr subString) {\n  // TODO(perf): Use a Boyer-Moore search?\n  auto iter = std::search(text.begin(), text.end(), subString.begin(), subString.end());\n  auto result = kj::arrayPtr(text.begin(), iter - text.begin());\n  text =\n      text.slice(kj::min(text.size(), result.end() - text.begin() + subString.size()), text.size());\n  return result;\n}\n\nstruct FormDataHeaderTable {\n  kj::HttpHeaderId contentDispositionId;\n  kj::Own<kj::HttpHeaderTable> table;\n\n  FormDataHeaderTable(kj::HttpHeaderTable::Builder builder)\n      : contentDispositionId(builder.add(\"Content-Disposition\")),\n        table(builder.build()) {}\n};\n\nconst FormDataHeaderTable& getFormDataHeaderTable() {\n  static const FormDataHeaderTable table({});\n  return table;\n}\n\nnamespace p = kj::parse;\nconstexpr auto httpIdentifier = p::oneOrMore(p::nameChar.orChar('-'));\nconstexpr auto quotedChar = p::oneOf(p::anyOfChars(\"\\\"\\n\\\\\").invert(),\n    // Chrome interprets \"\\<c>\" as reducing to <c> for any character <c>, including double quote.\n    // (So \"\\n\" = \"n\", etc.)\n    p::sequence(p::exactChar<'\\\\'>(), p::anyOfChars(\"\\n\").invert()));\nconstexpr auto contentDispositionParam = p::sequence(p::exactChar<';'>(),\n    p::discardWhitespace,\n    httpIdentifier,\n    p::discardWhitespace,\n    p::exactChar<'='>(),\n    p::discardWhitespace,\n    p::exactChar<'\"'>(),\n    p::oneOrMore(quotedChar),\n    p::exactChar<'\"'>(),\n    p::discardWhitespace);\nconstexpr auto contentDisposition = p::sequence(\n    p::discardWhitespace, httpIdentifier, p::discardWhitespace, p::many(contentDispositionParam));\n\nkj::OneOf<jsg::Ref<File>, kj::String> blobToFile(jsg::Lock& js,\n    kj::StringPtr name,\n    kj::OneOf<jsg::Ref<File>, jsg::Ref<Blob>, kj::String> value,\n    jsg::Optional<kj::String> filename) {\n  auto fromBlob = [&](jsg::Ref<Blob> blob) {\n    kj::String fn;\n    KJ_IF_SOME(f, filename) {\n      fn = kj::mv(f);\n    } else {\n      fn = kj::str(name);\n    }\n    // The file is created with the same data as the blob (essentially as just\n    // a view of the same blob) to avoid copying the data.\n    return js.alloc<File>(\n        blob.addRef(), blob->getData(), kj::mv(fn), kj::str(blob->getType()), dateNow());\n  };\n\n  KJ_SWITCH_ONEOF(value) {\n    KJ_CASE_ONEOF(file, jsg::Ref<File>) {\n      if (filename == kj::none) {\n        return kj::mv(file);\n      } else {\n        // Need to substitute filename.\n        return fromBlob(kj::mv(file));\n      }\n    }\n    KJ_CASE_ONEOF(blob, jsg::Ref<Blob>) {\n      return fromBlob(kj::mv(blob));\n    }\n    KJ_CASE_ONEOF(string, kj::String) {\n      return kj::mv(string);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\n// Add the chars from `value` into `builder` escaping the characters '\"' and '\\n' using %\n// encoding, exactly as Chrome does for Content-Disposition values.\nvoid addEscapingQuotes(kj::Vector<char>& builder, kj::StringPtr value) {\n  // Chrome throws \"Failed to fetch\" if the name ends with a backslash. Otherwise it worries that\n  // the backslash may be interpreted as escaping the final quote.\n  JSG_REQUIRE(!value.endsWith(\"\\\\\"), TypeError, \"Name or filename can't end with backslash\");\n\n  for (char c: value) {\n    switch (c) {\n      case '\\\"':\n        // Firefox supposedly escapes this as '\\\"', but Chrome chooses to use percent escapes,\n        // probably for fear of a buggy receiver who interprets the '\"' as being the end of the\n        // string. There is no standard.\n        builder.addAll(\"%22\"_kj);\n        break;\n      case '\\n':\n        builder.addAll(\"%0A\"_kj);\n        break;\n      case '\\\\':\n        // Chrome doesn't escape '\\', but this awkwardly means that the '\\' will be evaluated as\n        // an escape sequence on the other end. That seems like a bug. Let's not copy bugs.\n        builder.addAll(\"\\\\\\\\\"_kj);\n        break;\n      default:\n        builder.add(c);\n        break;\n    }\n  }\n}\n\nvoid assertUtf8(const auto& params) {\n  KJ_IF_SOME(charset, params.find(\"charset\"_kj)) {\n    JSG_REQUIRE(strcasecmp(charset.cStr(), \"utf-8\") == 0 ||\n            strcasecmp(charset.cStr(), \"utf8\") == 0 ||\n            strcasecmp(charset.cStr(), \"unicode-1-1-utf-8\") == 0,\n        TypeError, \"Non-utf-8 application/x-www-form-urlencoded body.\");\n  }\n}\n\n}  // namespace\n\n// =======================================================================================\n// FormData implementation\n\nvoid FormData::parseFormDataImpl(\n    kj::ArrayPtr<const char> rawText, kj::StringPtr boundary, ParseCallback callback) {\n  // multipart/form-data messages are delimited by <CRLF>--<boundary>. We want to be able to handle\n  // omitted carriage returns, though, so our delimiter only matches against a preceding line feed.\n  const auto delimiter = kj::str(\"\\n--\", boundary);\n\n  // We want to slice off the delimiter's preceding newline for the initial search, because the very\n  // first instance does not require one. In every subsequent multipart message, the preceding\n  // newline is required.\n  auto message = splitAtSubString(rawText, delimiter.slice(1));\n\n  JSG_REQUIRE(rawText.size() > 0, TypeError,\n      \"No initial boundary string (or you have a truncated message).\");\n\n  static const auto done = [](kj::ArrayPtr<const char>& body) {\n    // Consume any (CR)LF characters that trailed the boundary and indicate continuation, or consume\n    // the terminal \"--\" characters and indicate termination, or throw an error.\n    if (body.startsWith(\"\\n\"_kj)) {\n      body = body.slice(1, body.size());\n    } else if (body.startsWith(\"\\r\\n\"_kj)) {\n      body = body.slice(2, body.size());\n    } else if (body.startsWith(\"--\"_kj)) {\n      // We're done!\n      return true;\n    } else {\n      JSG_FAIL_REQUIRE(TypeError, \"Boundary string was not succeeded by CRLF, LF, or '--'.\");\n    }\n    return false;\n  };\n\n  constexpr auto staticRegexFlags =\n      std::regex_constants::ECMAScript | std::regex_constants::optimize;\n\n  static const auto headerTerminationRegex = std::regex(\"\\r?\\n\\r?\\n\", staticRegexFlags);\n\n  std::cmatch match;\n\n  auto& formDataHeaderTable = getFormDataHeaderTable();\n\n  while (!done(rawText)) {\n    JSG_REQUIRE(std::regex_search(rawText.begin(), rawText.end(), match, headerTerminationRegex),\n        TypeError, \"No multipart message header termination found.\");\n\n    // TODO(cleanup): Use kj-http to parse multipart headers. Right now that API isn't public, so\n    //   I'm just using a regex. For reference, multipart/form-data supports the following three\n    //   headers (https://tools.ietf.org/html/rfc7578#section-4.8):\n    //\n    //   Content-Disposition        (required)\n    //   Content-Type               (optional, recommended for files)\n    //   Content-Transfer-Encoding  (for 7-bit encoding, deprecated in HTTP contexts)\n    //\n    // TODO(soon): Read the Content-Type to support files.\n\n    auto headersText = kj::str(rawText.first(match[0].second - rawText.begin()));\n    rawText = rawText.slice(match[0].second - rawText.begin(), rawText.size());\n\n    kj::HttpHeaders headers(*formDataHeaderTable.table);\n    JSG_REQUIRE(headers.tryParse(headersText), TypeError, \"FormData part had invalid headers.\");\n\n    kj::StringPtr disposition =\n        JSG_REQUIRE_NONNULL(headers.get(formDataHeaderTable.contentDispositionId), TypeError,\n            \"No valid Content-Disposition header found in FormData part.\");\n\n    kj::Maybe<kj::String> maybeName;\n    kj::Maybe<kj::String> filename;\n    {\n      p::IteratorInput<char, const char*> input(disposition.begin(), disposition.end());\n      auto result = JSG_REQUIRE_NONNULL(contentDisposition(input), TypeError,\n          \"Invalid Content-Disposition header found in FormData part.\");\n      JSG_REQUIRE(kj::get<0>(result) == \"form-data\"_kj.asArray(), TypeError,\n          \"Content-Disposition header for FormData part must have the value \\\"form-data\\\", \"\n          \"possibly followed by parameters. Got: \\\"\",\n          kj::get<0>(result), \"\\\"\");\n\n      for (auto& param: kj::get<1>(result)) {\n        if (kj::get<0>(param) == \"name\"_kj.asArray()) {\n          maybeName = kj::str(kj::get<1>(param));\n        } else if (kj::get<0>(param) == \"filename\"_kj.asArray()) {\n          filename = kj::str(kj::get<1>(param));\n        }\n      }\n    }\n\n    kj::String name = JSG_REQUIRE_NONNULL(kj::mv(maybeName), TypeError,\n        \"Content-Disposition header in FormData part is missing a name.\");\n\n    kj::Maybe<kj::StringPtr> type = headers.get(kj::HttpHeaderId::CONTENT_TYPE);\n\n    message = splitAtSubString(rawText, delimiter);\n    JSG_REQUIRE(\n        rawText.size() > 0, TypeError, \"No subsequent boundary string after multipart message.\");\n\n    if (message.size() > 0) {\n      // If we skipped a CR, we must avoid including it in the message data.\n      message = message.first(message.size() - static_cast<uint>(message.back() == '\\r'));\n    }\n\n    callback(name, filename.map([](auto& str) { return str.asPtr(); }), type, message.asBytes());\n  }\n}\n\nkj::Array<FormData::EntryWithoutLock> FormData::parseWithoutLock(\n    kj::ArrayPtr<const char> rawText, kj::StringPtr contentType) {\n  kj::Vector<FormData::EntryWithoutLock> data;\n\n  KJ_IF_SOME(parsed, MimeType::tryParse(contentType)) {\n    auto& params = parsed.params();\n    if (MimeType::FORM_DATA == parsed) {\n      auto& boundary = JSG_REQUIRE_NONNULL(params.find(\"boundary\"_kj), TypeError,\n          \"No boundary string in Content-Type header. The multipart/form-data MIME \"\n          \"type requires a boundary parameter, e.g. 'Content-Type: multipart/form-data; \"\n          \"boundary=\\\"abcd\\\"'. See RFC 7578, section 4.\");\n\n      parseFormDataImpl(rawText, boundary,\n          [&](kj::StringPtr name, kj::Maybe<kj::StringPtr> maybeFilename,\n              kj::Maybe<kj::StringPtr> maybeType, kj::ArrayPtr<const kj::byte> message) mutable {\n        KJ_IF_SOME(filename, maybeFilename) {\n          data.add(FormData::EntryWithoutLock{\n            .name = kj::str(name),\n            .filename = kj::str(filename),\n            .type = mapCopyString(maybeType),\n            .value = kj::heapArray<kj::byte>(message),\n          });\n        } else {\n          data.add(FormData::EntryWithoutLock{\n            .name = kj::str(name),\n            .value = kj::heapArray<kj::byte>(message),\n          });\n        }\n      });\n      return data.releaseAsArray();\n    } else if (MimeType::FORM_URLENCODED == parsed) {\n      // Let's read the charset so we can barf if the body isn't UTF-8.\n      //\n      // TODO(conform): Transcode to UTF-8, like the spec tells us to.\n      assertUtf8(params);\n      kj::Vector<kj::Url::QueryParam> query;\n      parseQueryString(query, kj::mv(rawText));\n      data.reserve(query.size());\n      for (auto& param: query) {\n        data.add(EntryWithoutLock{\n          .name = kj::mv(param.name),\n          .value = kj::mv(param.value),\n        });\n      }\n      return data.releaseAsArray();\n    }\n  }\n  JSG_FAIL_REQUIRE(TypeError,\n      \"Unrecognized Content-Type header value. FormData can only \"\n      \"parse the following MIME types: \",\n      MimeType::FORM_DATA.toString(), \", \", MimeType::FORM_URLENCODED.toString());\n}\n\nvoid FormData::parse(jsg::Lock& js,\n    kj::ArrayPtr<const char> rawText,\n    kj::StringPtr contentType,\n    bool convertFilesToStrings) {\n  KJ_IF_SOME(parsed, MimeType::tryParse(contentType)) {\n    auto& params = parsed.params();\n    if (MimeType::FORM_DATA == parsed) {\n      auto& boundary = JSG_REQUIRE_NONNULL(params.find(\"boundary\"_kj), TypeError,\n          \"No boundary string in Content-Type header. The multipart/form-data MIME \"\n          \"type requires a boundary parameter, e.g. 'Content-Type: multipart/form-data; \"\n          \"boundary=\\\"abcd\\\"'. See RFC 7578, section 4.\");\n\n      parseFormDataImpl(rawText, boundary,\n          [&](kj::StringPtr name, kj::Maybe<kj::StringPtr> maybeFilename,\n              kj::Maybe<kj::StringPtr> maybeType, kj::ArrayPtr<const kj::byte> message) {\n        KJ_IF_SOME(filename, maybeFilename) {\n          if (convertFilesToStrings) {\n            auto messageData = kj::heapArray<char>(message.asChars());\n            data.add(FormData::Entry{\n              .name = kj::str(name),\n              .value = kj::str(kj::mv(messageData)),\n            });\n          } else {\n            auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, message.size());\n            jsg::BufferSource bytes(js, kj::mv(backing));\n            bytes.asArrayPtr().copyFrom(message);\n            data.add(FormData::Entry{.name = kj::str(name),\n              .value = js.alloc<File>(js, kj::mv(bytes), kj::str(filename),\n                  kj::str(maybeType.orDefault(nullptr)), dateNow())});\n          }\n        } else {\n          auto messageData = kj::heapArray<char>(message.asChars());\n          data.add(FormData::Entry{\n            .name = kj::str(name),\n            .value = kj::str(kj::mv(messageData)),\n          });\n        }\n      });\n      return;\n    } else if (MimeType::FORM_URLENCODED == parsed) {\n      // Let's read the charset so we can barf if the body isn't UTF-8.\n      //\n      // TODO(conform): Transcode to UTF-8, like the spec tells us to.\n      assertUtf8(params);\n      kj::Vector<kj::Url::QueryParam> query;\n      parseQueryString(query, kj::mv(rawText));\n      data.reserve(query.size());\n      for (auto& param: query) {\n        data.add(Entry{\n          .name = kj::str(param.name),\n          .value = kj::str(param.value),\n        });\n      }\n      return;\n    }\n  }\n  JSG_FAIL_REQUIRE(TypeError,\n      \"Unrecognized Content-Type header value. FormData can only \"\n      \"parse the following MIME types: \",\n      MimeType::FORM_DATA.toString(), \", \", MimeType::FORM_URLENCODED.toString());\n}\n\nkj::Array<kj::byte> FormData::serialize(kj::ArrayPtr<const char> boundary) {\n  // Boundary string requirement per RFC7578\n  JSG_REQUIRE(boundary.size() > 0 && boundary.size() <= 70, TypeError,\n      \"Length of multipart/form-data boundary string must be in the range [1, 70].\");\n\n  // TODO(perf): We should be able to trivially calculate the length of the serialized form data\n  //   beforehand. I tried, but apparently my math REALLY sucks and I hate memory overruns, so ...\n  auto builder = kj::Vector<char>{};\n\n  for (auto& kv: data) {\n    builder.addAll(\"--\"_kj);\n    builder.addAll(boundary);\n    builder.addAll(\"\\r\\n\"_kj);\n    builder.addAll(\"Content-Disposition: form-data; name=\\\"\"_kj);\n    addEscapingQuotes(builder, kv.name);\n    KJ_SWITCH_ONEOF(kv.value) {\n      KJ_CASE_ONEOF(text, kj::String) {\n        builder.addAll(\"\\\"\\r\\n\\r\\n\"_kj);\n        builder.addAll(text);\n      }\n      KJ_CASE_ONEOF(file, jsg::Ref<File>) {\n        builder.addAll(\"\\\"; filename=\\\"\"_kj);\n        addEscapingQuotes(builder, file->getName());\n        builder.addAll(\"\\\"\\r\\nContent-Type: \"_kj);\n        auto type = file->getType();\n        if (type == nullptr) {\n          builder.addAll(MimeType::OCTET_STREAM.toString());\n        } else {\n          builder.addAll(type);\n        }\n        builder.addAll(\"\\r\\n\\r\\n\"_kj);\n        builder.addAll(file->getData().asChars());\n      }\n    }\n    builder.addAll(\"\\r\\n\"_kj);\n  }\n  builder.addAll(\"--\"_kj);\n  builder.addAll(boundary);\n  builder.addAll(\"--\"_kj);\n\n  return builder.releaseAsArray().releaseAsBytes();\n}\n\nFormData::EntryType FormData::clone(jsg::Lock& js, FormData::EntryType& value) {\n  KJ_SWITCH_ONEOF(value) {\n    KJ_CASE_ONEOF(file, jsg::Ref<File>) {\n      return file.addRef();\n    }\n    KJ_CASE_ONEOF(string, kj::String) {\n      auto data = kj::heapArray<char>(string);\n      return kj::str(kj::mv(data));\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\njsg::Ref<FormData> FormData::constructor(jsg::Lock& js) {\n  return js.alloc<FormData>();\n}\n\nvoid FormData::append(jsg::Lock& js,\n    kj::String name,\n    kj::OneOf<jsg::Ref<File>, jsg::Ref<Blob>, kj::String> value,\n    jsg::Optional<kj::String> filename) {\n  auto filifiedValue = blobToFile(js, name, kj::mv(value), kj::mv(filename));\n  data.add(Entry{kj::mv(name), kj::mv(filifiedValue)});\n}\n\nvoid FormData::delete_(kj::String name) {\n  auto pivot =\n      std::remove_if(data.begin(), data.end(), [&name](const auto& kv) { return kv.name == name; });\n  data.truncate(pivot - data.begin());\n}\n\nkj::Maybe<kj::OneOf<jsg::Ref<File>, kj::String>> FormData::get(jsg::Lock& js, kj::String name) {\n  for (auto& [k, v]: data) {\n    if (k == name) {\n      return clone(js, v);\n    }\n  }\n  return kj::none;\n}\n\nkj::Array<kj::OneOf<jsg::Ref<File>, kj::String>> FormData::getAll(jsg::Lock& js, kj::String name) {\n  kj::Vector<kj::OneOf<jsg::Ref<File>, kj::String>> result;\n  for (auto& [k, v]: data) {\n    if (k == name) {\n      result.add(clone(js, v));\n    }\n  }\n  return result.releaseAsArray();\n}\n\nbool FormData::has(kj::String name) {\n  for (auto& [k, v]: data) {\n    if (k == name) {\n      return true;\n    }\n  }\n  return false;\n}\n\n// Set the first element named `name` to `value`, then remove all the rest matching that name.\nvoid FormData::set(jsg::Lock& js,\n    kj::String name,\n    kj::OneOf<jsg::Ref<File>, jsg::Ref<Blob>, kj::String> value,\n    jsg::Optional<kj::String> filename) {\n  const auto predicate = [name = name.slice(0)](const auto& kv) { return kv.name == name; };\n  auto firstFound = std::find_if(data.begin(), data.end(), predicate);\n  if (firstFound != data.end()) {\n    firstFound->value = blobToFile(js, name, kj::mv(value), kj::mv(filename));\n    auto pivot = std::remove_if(++firstFound, data.end(), predicate);\n    data.truncate(pivot - data.begin());\n  } else {\n    append(js, kj::mv(name), kj::mv(value), kj::mv(filename));\n  }\n}\n\njsg::Ref<FormData::EntryIterator> FormData::entries(jsg::Lock& js) {\n  return js.alloc<EntryIterator>(IteratorState{JSG_THIS});\n}\n\njsg::Ref<FormData::KeyIterator> FormData::keys(jsg::Lock& js) {\n  return js.alloc<KeyIterator>(IteratorState{JSG_THIS});\n}\n\njsg::Ref<FormData::ValueIterator> FormData::values(jsg::Lock& js) {\n  return js.alloc<ValueIterator>(IteratorState{JSG_THIS});\n}\n\nvoid FormData::forEach(jsg::Lock& js,\n    jsg::Function<void(EntryType, kj::StringPtr, jsg::Ref<FormData>)> callback,\n    jsg::Optional<jsg::Value> thisArg) {\n  // Here, if the thisArg is not passed, or is passed explicitly as a null or\n  // undefined, then undefined is used as the thisArg.\n  auto receiver = js.v8Undefined();\n  KJ_IF_SOME(arg, thisArg) {\n    auto handle = arg.getHandle(js);\n    if (!handle->IsNullOrUndefined()) {\n      receiver = handle;\n    }\n  }\n  callback.setReceiver(js.v8Ref(receiver));\n\n  // On each iteration of the for loop, a JavaScript callback is invoked. If a new\n  // item is appended to the URLSearchParams within that function, the loop must pick\n  // it up. Using the classic for (;;) syntax here allows for that. However, this does\n  // mean that it's possible for a user to trigger an infinite loop here if new items\n  // are added to the search params unconditionally on each iteration.\n  // Silence clang-tidy warning, using an iterator would not work correctly if callback\n  // increases the array size.\n  // NOLINTNEXTLINE(modernize-loop-convert)\n  for (size_t i = 0; i < this->data.size(); i++) {\n    auto& [key, value] = this->data[i];\n    callback(js, clone(js, value), key, JSG_THIS);\n  }\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/form-data.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"blob.h\"\n#include <kj/vector.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/io/compatibility-date.capnp.h>\n\nnamespace workerd::api {\n\n// Implements the FormData interface as prescribed by:\n// https://xhr.spec.whatwg.org/#interface-formdata\n//\n// NOTE: This class is actually reused by some internal code implementing the fiddle service, for\n//   lack of any other C++ form data parser implementation. In that usage, there is no isolate.\n//   It uses `parse()` and `getData()`. This relies on the ability to construct `File` objects\n//   without an isolate.\nclass FormData: public jsg::Object {\nprivate:\n  using EntryType = kj::OneOf<jsg::Ref<File>, kj::String>;\n  using EntryIteratorType = kj::Array<EntryType>;\n  using KeyIteratorType = kj::String;\n  using ValueIteratorType = EntryType;\n\n  struct IteratorState final {\n    jsg::Ref<FormData> parent;\n    uint index = 0;\n\n    void visitForGc(jsg::GcVisitor& visitor) {\n      visitor.visit(parent);\n    }\n\n    JSG_MEMORY_INFO(IteratorState) {\n      tracker.trackField(\"parent\", parent);\n    }\n  };\n\npublic:\n\n  using ParseCallback = kj::FunctionParam<void(kj::StringPtr name,\n                                               kj::Maybe<kj::StringPtr> filename,\n                                               kj::Maybe<kj::StringPtr> type,\n                                               kj::ArrayPtr<const kj::byte> data)>;\n\n  static void parseFormDataImpl(kj::ArrayPtr<const char> rawText,\n                                kj::StringPtr boundary,\n                                ParseCallback callback);\n  struct EntryWithoutLock {\n    kj::String name;\n    kj::Maybe<kj::String> filename;\n    kj::Maybe<kj::String> type;\n    kj::OneOf<kj::Array<kj::byte>, kj::String> value;\n  };\n\n  // Provided for cases where parsing FormData outside of any direct JS\n  // API usage (such as in fiddle internally).\n  static kj::Array<EntryWithoutLock> parseWithoutLock(kj::ArrayPtr<const char> rawText,\n                                                      kj::StringPtr contentType);\n\n  // Parse `rawText`, storing the results in this FormData object. `contentType` must be either\n  // multipart/form-data or application/x-www-form-urlencoded.\n  //\n  // `convertFilesToStrings` is for backwards-compatibility. The first implementation of this\n  // class in Workers incorrectly represented files as strings (of their content). Changing this\n  // could break deployed code, so this has to be controlled by a compatibility flag.\n  //\n  // Parsing may or may not pass a jsg::Lock. If a lock is passed, any File objects created will\n  // track their internal allocated memory in the associated isolate. If a lock is not passed,\n  // the internal allocated memory will not be tracked.\n  void parse(jsg::Lock& js,\n             kj::ArrayPtr<const char> rawText,\n             kj::StringPtr contentType,\n             bool convertFilesToStrings);\n\n  // Given a delimiter string `boundary`, serialize all fields in this form data to an array of\n  // bytes suitable for use as an HTTP message body.\n  kj::Array<kj::byte> serialize(kj::ArrayPtr<const char> boundary);\n\n  struct Entry {\n    kj::String name;\n    kj::OneOf<jsg::Ref<File>, kj::String> value;\n\n    JSG_MEMORY_INFO(Entry) {\n      tracker.trackField(\"name\", name);\n      KJ_SWITCH_ONEOF(value) {\n        KJ_CASE_ONEOF(file, jsg::Ref<File>) {\n          tracker.trackField(\"value\", file);\n        }\n        KJ_CASE_ONEOF(str, kj::String) {\n          tracker.trackField(\"value\", str);\n        }\n      }\n    }\n  };\n\n  kj::ArrayPtr<const Entry> getData() { return data; }\n\n  // JS API\n\n  // The spec allows a FormData to be constructed from a <form> HTML element. We don't support that,\n  // for obvious reasons, so this constructor doesn't take any parameters. If someone tries to use\n  // FormData to represent a <form> element we probably don't have to worry about making the error\n  // message they receive too pretty: they won't get farther than `document.getElementById()`.\n  static jsg::Ref<FormData> constructor(jsg::Lock& js);\n\n  void append(jsg::Lock& js, kj::String name,\n              kj::OneOf<jsg::Ref<File>, jsg::Ref<Blob>, kj::String> value,\n              jsg::Optional<kj::String> filename);\n\n  void delete_(kj::String name);\n\n  kj::Maybe<kj::OneOf<jsg::Ref<File>, kj::String>> get(jsg::Lock& js, kj::String name);\n\n  kj::Array<kj::OneOf<jsg::Ref<File>, kj::String>> getAll(jsg::Lock& js, kj::String name);\n\n  bool has(kj::String name);\n\n  void set(jsg::Lock& js, kj::String name,\n           kj::OneOf<jsg::Ref<File>, jsg::Ref<Blob>, kj::String> value,\n           jsg::Optional<kj::String> filename);\n\n  JSG_ITERATOR(EntryIterator, entries,\n                EntryIteratorType,\n                IteratorState,\n                iteratorNext<EntryIteratorType>);\n  JSG_ITERATOR(KeyIterator, keys,\n                KeyIteratorType,\n                IteratorState,\n                iteratorNext<KeyIteratorType>);\n  JSG_ITERATOR(ValueIterator,\n                values,\n                ValueIteratorType,\n                IteratorState,\n                iteratorNext<ValueIteratorType>);\n\n  void forEach(\n      jsg::Lock& js,\n      jsg::Function<void(EntryType, kj::StringPtr, jsg::Ref<FormData>)> callback,\n      jsg::Optional<jsg::Value> thisArg);\n\n  JSG_RESOURCE_TYPE(FormData, CompatibilityFlags::Reader flags) {\n    JSG_METHOD(append);\n    JSG_METHOD_NAMED(delete, delete_);\n    JSG_METHOD(get);\n    JSG_METHOD(getAll);\n    JSG_METHOD(has);\n    JSG_METHOD(set);\n    JSG_METHOD(entries);\n    JSG_METHOD(keys);\n    JSG_METHOD(values);\n\n    JSG_METHOD(forEach);\n    JSG_ITERABLE(entries);\n\n    if (flags.getFormDataParserSupportsFiles()) {\n      JSG_TS_OVERRIDE({\n        append(name: string, value: string | Blob): void;\n        append(name: string, value: string): void;\n        append(name: string, value: Blob, filename?: string): void;\n\n        set(name: string, value: string | Blob): void;\n        set(name: string, value: string): void;\n        set(name: string, value: Blob, filename?: string): void;\n\n        entries(): IterableIterator<[key: string, value: File | string]>;\n        [Symbol.iterator](): IterableIterator<[key: string, value: File | string]>;\n\n        forEach<This = unknown>(callback: (this: This, value: File | string, key: string, parent: FormData) => void, thisArg?: This): void;\n      });\n    } else {\n      JSG_TS_OVERRIDE({\n        get(name: string): string | null;\n        getAll(name: string): string[];\n\n        append(name: string, value: string | Blob): void;\n        append(name: string, value: string): void;\n        append(name: string, value: Blob, filename?: string): void;\n\n        set(name: string, value: string | Blob): void;\n        set(name: string, value: string): void;\n        set(name: string, value: Blob, filename?: string): void;\n\n        entries(): IterableIterator<[key: string, value: string]>;\n        [Symbol.iterator](): IterableIterator<[key: string, value: string]>;\n\n        forEach<This = unknown>(callback: (this: This, value: string, key: string, parent: FormData) => void, thisArg?: This): void;\n      });\n    }\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"data\", data.asPtr());\n  }\n\nprivate:\n  kj::Vector<Entry> data;\n\n  static EntryType clone(jsg::Lock& js, EntryType& value);\n\n  template <typename Type>\n  static kj::Maybe<Type> iteratorNext(jsg::Lock& js, IteratorState& state) {\n    if (state.index >= state.parent->data.size()) {\n      return kj::none;\n    }\n    auto& [key, value] = state.parent->data[state.index++];\n    if constexpr (kj::isSameType<Type, EntryIteratorType>()) {\n      return kj::arr<EntryType>(kj::str(key), clone(js, value));\n    } else if constexpr (kj::isSameType<Type, KeyIteratorType>()) {\n      return kj::str(key);\n    } else if constexpr (kj::isSameType<Type, ValueIteratorType>()) {\n      return clone(js, value);\n    } else {\n      KJ_UNREACHABLE;\n    }\n  }\n};\n\n#define EW_FORMDATA_ISOLATE_TYPES     \\\n  api::FormData,                      \\\n  api::FormData::EntryIterator,       \\\n  api::FormData::EntryIterator::Next, \\\n  api::FormData::KeyIterator,         \\\n  api::FormData::KeyIterator::Next,   \\\n  api::FormData::ValueIterator,       \\\n  api::FormData::ValueIterator::Next\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/fuzzilli.c++",
    "content": "#if defined(__linux__) && defined(WORKERD_FUZZILLI)\n#include \"fuzzilli.h\"\n\n#include <workerd/api/util.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/util/immediate-crash.h>\n\n#include <errno.h>\n#include <string.h>\n\n#include <kj/common.h>\n#include <kj/debug.h>\n#include <kj/exception.h>\n\n// Declare global structures used for coverage info in Fuzzilli\n// as Fuzzilli is coverage guided it requires trace pc guard\n// NOLINTBEGIN(edgeworker-mutable-globals)\nstruct shmem_data* __shmem;\nuint32_t* __edges_start;\nuint32_t* __edges_stop;\n// NOLINTEND(edgeworker-mutable-globals)\n\nvoid perform_wild_write() {\n  // Access an invalid address.\n  // We want to use an \"interesting\" address for the access (instead of\n  // e.g. nullptr). In the (unlikely) case that the address is actually\n  // mapped, simply increment the pointer until it crashes.\n  // The cast ensures that this works correctly on both 32-bit and 64-bit.\n  uintptr_t addr = static_cast<uintptr_t>(0x414141414141ull);\n  char* ptr = reinterpret_cast<char*>(addr);\n  for (int i = 0; i < 1024; i++) {\n    *ptr = 'A';\n    ptr += 1 * 1024 * 1024;\n  }\n}\n\nvoid __sanitizer_cov_reset_edgeguards() {\n  uint64_t N = 0;\n  for (uint32_t* x = __edges_start; x < __edges_stop && N < MAX_EDGES; x++) *x = ++N;\n}\n\n// setup trace pc guard to let fuzzilli get some coverage info\nextern \"C\" void __sanitizer_cov_trace_pc_guard_init(uint32_t* start, uint32_t* stop) {\n  // Avoid duplicate initialization\n  if (start == stop || *start) return;\n\n  if (__edges_start != NULL || __edges_stop != NULL) {\n    KJ_LOG(ERROR, \"Coverage instrumentation is only supported for a single module\\n\");\n    _exit(-1);\n  }\n\n  __edges_start = start;\n  __edges_stop = stop;\n\n  // Map the shared memory region\n  const char* shm_key = getenv(\"SHM_ID\");\n  if (!shm_key) {\n    KJ_LOG(INFO, \"[COV] no shared memory bitmap available, skipping\");\n    __shmem = (struct shmem_data*)malloc(SHM_SIZE);\n  } else {\n    int fd = shm_open(shm_key, O_RDWR, S_IREAD | S_IWRITE);\n    if (fd <= -1) {\n      KJ_LOG(ERROR, \"Failed to open shared memory region: %s\\n\", strerror(errno));\n      _exit(-1);\n    }\n\n    __shmem = (struct shmem_data*)mmap(0, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);\n    if (__shmem == MAP_FAILED) {\n      KJ_LOG(ERROR, \"Failed to mmap shared memory region\\n\");\n      _exit(-1);\n    }\n  }\n\n  __sanitizer_cov_reset_edgeguards();\n  __shmem->num_edges = stop - start;\n}\n\nextern \"C\" void __sanitizer_cov_trace_pc_guard(uint32_t* guard) {\n  // There's a small race condition here: if this function executes in two threads for the same\n  // edge at the same time, the first thread might disable the edge (by setting the guard to zero)\n  // before the second thread fetches the guard value (and thus the index). However, our\n  // instrumentation ignores the first edge (see libcoverage.c) and so the race is unproblematic.\n  uint32_t index = *guard;\n  // If this function is called before coverage instrumentation is properly initialized we want to return early.\n  if (!index) return;\n  __shmem->edges[index / 8] |= 1 << (index % 8);\n  *guard = 0;\n}\n\nvoid fuzzilli_handler(workerd::jsg::Lock& js, workerd::jsg::Arguments<workerd::jsg::Value>& args) {\n  if (args.size() == 0) {\n    // No arguments provided, just return\n    return;\n  }\n\n  v8::Isolate* isolate = v8::Isolate::GetCurrent();\n  v8::Local<v8::Value> value = v8::Local<v8::Value>::Cast(args[0].getHandle(isolate));\n  v8::Local<v8::String> str = workerd::jsg::check(value->ToDetailString(js.v8Context()));\n  v8::String::Utf8Value operation(js.v8Isolate, str);\n  if (*operation == nullptr) {\n    return;\n  }\n\n  if (strcmp(*operation, \"FUZZILLI_CRASH\") == 0) {\n    auto maybeArg =\n        v8::Local<v8::Int32>::Cast(args[1].getHandle(isolate))->Int32Value(js.v8Context());\n    if (!maybeArg.IsJust()) {\n      KJ_LOG(ERROR, \"Maybe arg is empty...\\n\");\n      fflush(stdout);\n      return;\n    }\n    int32_t arg = maybeArg.FromJust();\n    switch (arg) {\n      case 0:\n        IMMEDIATE_CRASH();\n        break;\n      case 1:\n        assert(0);\n        //CHECK(false);\n        break;\n      case 2:\n        assert(0);\n        //DCHECK(false);\n        break;\n      case 3: {\n        perform_wild_write();\n        break;\n      }\n      case 4: {\n        // Use-after-free, should be caught by ASan (if active).\n        auto* vec = new std::vector<int>(4);\n        delete vec;\n        USE(vec->at(0));\n#ifndef V8_USE_ADDRESS_SANITIZER\n        // The testcase must also crash on non-asan builds.\n        perform_wild_write();\n#endif  // !V8_USE_ADDRESS_SANITIZER\n        break;\n      }\n      case 5: {\n        // Out-of-bounds access (1), likely only crashes in ASan or\n        // \"hardened\"/\"safe\" libc++ builds.\n        std::vector<int> vec(5);\n        USE(vec[5]);\n        break;\n      }\n      case 6: {\n        // Out-of-bounds access (2), likely only crashes in ASan builds.\n        std::vector<int> vec(6);\n        //linter complains about this...\n        // NOLINTNEXTLINE(edgeworker-ban-memset)\n        memset(vec.data(), 42, 0x100);\n        break;\n      }\n      default:\n        break;\n    }\n  } else if (strcmp(*operation, \"FUZZILLI_PRINT\") == 0) {\n    static FILE* fzliout = nullptr;\n    if (!fzliout) {\n      fzliout = fdopen(REPRL_DWFD, \"w\");\n      if (!fzliout) {\n        KJ_LOG(ERROR, \"Fuzzer output channel not available, printing to stdout instead\\n\");\n        fzliout = stdout;\n      }\n    }\n\n    value = v8::Local<v8::Value>::Cast(args[1].getHandle(isolate));\n    str = workerd::jsg::check(value->ToDetailString(js.v8Context()));\n    v8::String::Utf8Value string(js.v8Isolate, str);\n    if (*string == nullptr) {\n      return;\n    }\n    fprintf(fzliout, \"%s\\n\", *string);\n    fflush(fzliout);\n  }\n}\n\n#endif\n"
  },
  {
    "path": "src/workerd/api/fuzzilli.h",
    "content": "#pragma once\n\n#ifdef __linux__\n#include <workerd/jsg/jsg.h>\n\n#include <assert.h>\n#include <fcntl.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <sys/wait.h>\n#include <unistd.h>\n\n#include <cstdint>\n\n#define SHM_SIZE 0x200000\n#define MAX_EDGES ((SHM_SIZE - 4) * 8)\n\nstruct shmem_data {\n  uint32_t num_edges;\n  unsigned char edges[];\n};\n\n// NOLINTBEGIN(edgeworker-mutable-globals)\nextern struct shmem_data* __shmem;\nextern uint32_t* __edges_start;\nextern uint32_t* __edges_stop;\n// NOLINTEND(edgeworker-mutable-globals)\n\nvoid __sanitizer_cov_reset_edgeguards();\n\n//Fuzzilli sockets for communication\n#define REPRL_CRFD 100\n#define REPRL_CWFD 101\n#define REPRL_DRFD 102\n#define REPRL_DWFD 103\n#define USE(...)                                                                                   \\\n  do {                                                                                             \\\n    (void)(__VA_ARGS__);                                                                           \\\n  } while (false)\n\n#define CHECK(condition)                                                                           \\\n  do {                                                                                             \\\n    if (!(condition)) {                                                                            \\\n      fprintf(stderr, \"Error: %s:%d: condition failed: %s\\n\", __FILE__, __LINE__, #condition);     \\\n      exit(EXIT_FAILURE);                                                                          \\\n    }                                                                                              \\\n  } while (0)\n\nvoid perform_wild_write();\nvoid sanitizer_cov_reset_edgeguards();\nuint32_t sanitizer_cov_count_discovered_edges();\nvoid sanitizer_cov_prepare_for_hardware_sandbox();\nvoid cov_init_builtins_edges(uint32_t num_edges);\n\nvoid fuzzilli_handler(workerd::jsg::Lock& js, workerd::jsg::Arguments<workerd::jsg::Value>& args);\n\n// TODO: this would only work with the profiler like in d8\n// void cov_update_builtins_basic_block_coverage(const std::vector<bool>& cov_map);\n// https://github.com/v8/v8/blob/a63b49495eac716c1a4ccbef57e2e1c7e98f27e4/src/d8/d8.cc#L7284-L7286\n//\n#endif\n"
  },
  {
    "path": "src/workerd/api/global-scope.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"global-scope.h\"\n\n#include \"simdutf.h\"\n\n#include <workerd/api/cache.h>\n#include <workerd/api/crypto/crypto.h>\n#include <workerd/api/events.h>\n#include <workerd/api/eventsource.h>\n#ifdef WORKERD_FUZZILLI\n#include <workerd/api/fuzzilli.h>\n#endif\n#include <workerd/api/hibernatable-web-socket.h>\n#include <workerd/api/scheduled.h>\n#include <workerd/api/system-streams.h>\n#include <workerd/api/trace.h>\n#include <workerd/api/util.h>\n#include <workerd/io/compatibility-date.h>\n#include <workerd/io/features.h>\n#include <workerd/io/io-context.h>\n#include <workerd/io/tracer.h>\n#include <workerd/jsg/async-context.h>\n#include <workerd/jsg/ser.h>\n#include <workerd/jsg/util.h>\n#include <workerd/util/sentry.h>\n#include <workerd/util/stream-utils.h>\n#include <workerd/util/thread-scopes.h>\n#include <workerd/util/uncaught-exception-source.h>\n#include <workerd/util/use-perfetto-categories.h>\n\n#include <kj/encoding.h>\n\nnamespace workerd::api {\n\nnamespace {\n\nenum class NeuterReason { SENT_RESPONSE, THREW_EXCEPTION, CLIENT_DISCONNECTED };\n\nkj::Exception makeNeuterException(NeuterReason reason) {\n  switch (reason) {\n    case NeuterReason::SENT_RESPONSE:\n      return JSG_KJ_EXCEPTION(\n          FAILED, TypeError, \"Can't read from request stream after response has been sent.\");\n    case NeuterReason::THREW_EXCEPTION:\n      return JSG_KJ_EXCEPTION(\n          FAILED, TypeError, \"Can't read from request stream after responding with an exception.\");\n    case NeuterReason::CLIENT_DISCONNECTED:\n      return JSG_KJ_EXCEPTION(\n          DISCONNECTED, TypeError, \"Can't read from request stream because client disconnected.\");\n  }\n  KJ_UNREACHABLE;\n}\n\n}  // namespace\n\nvoid ExecutionContext::waitUntil(kj::Promise<void> promise) {\n  IoContext::current().addWaitUntil(kj::mv(promise));\n}\n\nvoid ExecutionContext::passThroughOnException() {\n  IoContext::current().setFailOpen();\n}\n\nvoid ExecutionContext::abort(jsg::Lock& js, jsg::Optional<jsg::Value> reason) {\n  KJ_IF_SOME(r, reason) {\n    IoContext::current().abort(js.exceptionToKj(kj::mv(r)));\n  } else {\n    auto e =\n        JSG_KJ_EXCEPTION(FAILED, Error, \"Worker execution was aborted due to call to ctx.abort().\");\n    IoContext::current().abort(kj::mv(e));\n  }\n\n  js.terminateExecutionNow();\n}\n\nnamespace {\ntemplate <typename T>\njsg::LenientOptional<T> mapAddRef(jsg::Lock& js, jsg::LenientOptional<T>& function) {\n  return function.map([&](T& a) { return a.addRef(js); });\n}\n}  // namespace\n\nExportedHandler ExportedHandler::clone(jsg::Lock& js) {\n  return ExportedHandler{\n    .fetch{mapAddRef(js, fetch)},\n    .tail{mapAddRef(js, tail)},\n    .trace{mapAddRef(js, trace)},\n    .tailStream{mapAddRef(js, tailStream)},\n    .scheduled{mapAddRef(js, scheduled)},\n    .alarm{mapAddRef(js, alarm)},\n    .test{mapAddRef(js, test)},\n    .webSocketMessage{mapAddRef(js, webSocketMessage)},\n    .webSocketClose{mapAddRef(js, webSocketClose)},\n    .webSocketError{mapAddRef(js, webSocketError)},\n    .self{js.v8Isolate, self.getHandle(js.v8Isolate)},\n    .env{env.addRef(js)},\n    .ctx{getCtx()},\n    .missingSuperclass = missingSuperclass,\n  };\n}\n\nServiceWorkerGlobalScope::ServiceWorkerGlobalScope()\n    : unhandledRejections([this](jsg::Lock& js,\n                              v8::PromiseRejectEvent event,\n                              jsg::V8Ref<v8::Promise> promise,\n                              jsg::Value value) {\n        // If async context tracking is enabled, then we need to ensure that we enter the frame\n        // associated with the promise before we invoke the unhandled rejection callback handling.\n        auto ev = js.alloc<PromiseRejectionEvent>(event, kj::mv(promise), kj::mv(value));\n        dispatchEventImpl(js, kj::mv(ev));\n      }) {}\n\nvoid ServiceWorkerGlobalScope::clear() {\n  removeAllHandlers();\n  unhandledRejections.clear();\n}\n\nkj::Promise<DeferredProxy<void>> ServiceWorkerGlobalScope::request(kj::HttpMethod method,\n    kj::StringPtr url,\n    const kj::HttpHeaders& headers,\n    kj::AsyncInputStream& requestBody,\n    kj::HttpService::Response& response,\n    kj::Maybe<kj::StringPtr> cfBlobJson,\n    Worker::Lock& lock,\n    kj::Maybe<ExportedHandler&> exportedHandler,\n    kj::Maybe<jsg::Ref<AbortSignal>> abortSignal) {\n  TRACE_EVENT(\"workerd\", \"ServiceWorkerGlobalScope::request()\");\n  // To construct a ReadableStream object, we're supposed to pass in an Own<AsyncInputStream>, so\n  // that it can drop the reference whenever it gets GC'ed. But in this case the stream's lifetime\n  // is not under our control -- it's attached to the request. So, we wrap it in a\n  // NeuterableInputStream which allows us to disconnect the stream before it becomes invalid.\n  auto ownRequestBody = newNeuterableInputStream(requestBody);\n  auto deferredNeuter = kj::defer([ownRequestBody = kj::addRef(*ownRequestBody)]() mutable {\n    // Make sure to cancel the request body stream since the native stream is no longer valid once\n    // the returned promise completes. Note that the KJ HTTP library deals with the fact that we\n    // haven't consumed the entire request body.\n    ownRequestBody->neuter(makeNeuterException(NeuterReason::CLIENT_DISCONNECTED));\n  });\n  KJ_ON_SCOPE_FAILURE(ownRequestBody->neuter(makeNeuterException(NeuterReason::THREW_EXCEPTION)));\n\n  auto& ioContext = IoContext::current();\n  jsg::Lock& js = lock;\n\n  CfProperty cf(cfBlobJson);\n\n  // We only create the body stream if there is a body to read.\n  kj::Maybe<jsg::Ref<ReadableStream>> maybeJsStream = kj::none;\n\n  // If the request has \"no body\", we want `request.body` to be null. But, this is not the same\n  // thing as the request having a body that happens to be empty. Unfortunately, KJ HTTP gives us\n  // a zero-length AsyncInputStream either way, so we can't just check the stream length.\n  //\n  // The HTTP spec says: \"The presence of a message body in a request is signaled by a\n  // Content-Length or Transfer-Encoding header field.\" RFC 7230, section 3.3.\n  // https://tools.ietf.org/html/rfc7230#section-3.3\n  //\n  // But, the request was not necessarily received over HTTP! It could be from another worker in\n  // a pipeline, or it could have been received over RPC. In either case, the headers don't\n  // necessarily mean anything; the calling worker can fill them in however it wants.\n  //\n  // So, we decide if the body is null if both headers are missing AND the stream is known to have\n  // zero length. And on the sending end (fetchImpl() in http.c++), if we're sending a request with\n  // a non-null body that is known to be empty, we explicitly set Content-Length: 0. This should\n  // mean that in all worker-to-worker interactions, if the sender provided a non-null body, the\n  // receiver will receive a non-null body, independent of anything else.\n  //\n  // TODO(cleanup): Should KJ HTTP interfaces explicitly communicate the difference between a\n  //   missing body and an empty one?\n  kj::Maybe<Body::ExtractedBody> body;\n  if (headers.get(kj::HttpHeaderId::CONTENT_LENGTH) != kj::none ||\n      headers.get(kj::HttpHeaderId::TRANSFER_ENCODING) != kj::none ||\n      requestBody.tryGetLength().orDefault(1) > 0) {\n    // We do not automatically decode gzipped request bodies because the fetch() standard doesn't\n    // specify any automatic encoding of requests. https://github.com/whatwg/fetch/issues/589\n    auto b = newSystemStream(kj::addRef(*ownRequestBody), StreamEncoding::IDENTITY);\n    auto jsStream = js.alloc<ReadableStream>(ioContext, kj::mv(b));\n    body = Body::ExtractedBody(jsStream.addRef());\n    maybeJsStream = kj::mv(jsStream);\n  }\n\n  auto jsHeaders = js.alloc<Headers>(js, headers, Headers::Guard::REQUEST);\n\n  // If the request doesn't specify \"Content-Length\" or \"Transfer-Encoding\", set \"Content-Length\"\n  // to the body length if it's known. This ensures handlers for worker-to-worker requests can\n  // access known body lengths if they're set, without buffering bodies.\n  if (body != kj::none && !jsHeaders->hasCommon(capnp::CommonHeaderName::CONTENT_LENGTH) &&\n      !jsHeaders->hasCommon(capnp::CommonHeaderName::TRANSFER_ENCODING)) {\n    KJ_IF_SOME(l, requestBody.tryGetLength()) {\n      jsHeaders->setCommon(capnp::CommonHeaderName::CONTENT_LENGTH, kj::str(l));\n    } else {\n      jsHeaders->setCommon(capnp::CommonHeaderName::TRANSFER_ENCODING, kj::str(\"chunked\"));\n    }\n  }\n\n  if (defaultFetcher == kj::none) {\n    // The default fetcher can be shared across requests since it does not persist any\n    // request-specific state. We can create one lazily here for the first request\n    // handled by this global scope and avoid the extra allocations on subsequence requests.\n    // We could also consider creating it lazily when first accessed, but Fetcher is very\n    // lightweight so this seems sufficient for now.\n    defaultFetcher =\n        js.alloc<Fetcher>(IoContext::NEXT_CLIENT_CHANNEL, Fetcher::RequiresHostAndProtocol::YES);\n  }\n\n  auto jsRequest = js.alloc<Request>(js, method, url, Request::Redirect::MANUAL, kj::mv(jsHeaders),\n      KJ_ASSERT_NONNULL(defaultFetcher).addRef(),\n      /* signal */ kj::mv(abortSignal), kj::mv(cf), kj::mv(body),\n      /* thisSignal */ kj::none, Request::CacheMode::NONE);\n\n  // signal vs thisSignal\n  // --------------------\n  // The fetch spec definition of Request has a distinction between the\n  // \"signal\" (which is an optional AbortSignal passed in with the options), and \"this' signal\",\n  // which is an AbortSignal that is always available via the request.signal accessor.\n  //\n  // redirect\n  // --------\n  // I set the redirect mode to manual here, so that by default scripts that just pass requests\n  // through to a fetch() call will behave the same as scripts which don't call .respondWith(): if\n  // the request results in a redirect, the visitor will see that redirect.\n\n  auto event = js.alloc<FetchEvent>(kj::mv(jsRequest));\n\n  uint tasksBefore = ioContext.taskCount();\n\n  // We'll drop our span once the promise (fetch handler result) resolves.\n  kj::Maybe<SpanBuilder> span = ioContext.makeTraceSpan(\"fetch_handler\"_kjc);\n  bool useDefaultHandling;\n  KJ_IF_SOME(h, exportedHandler) {\n    KJ_IF_SOME(f, h.fetch) {\n      auto promise = f(lock, event->getRequest(), h.env.addRef(js), h.getCtx());\n      event->respondWith(lock, kj::mv(promise));\n      useDefaultHandling = false;\n    } else {\n      // In modules mode we don't have a concept of \"default handling\".\n      lock.logWarningOnce(\"Received a FetchEvent but we lack a handler for FetchEvents. \"\n                          \"Did you remember to export a fetch() function?\");\n      JSG_FAIL_REQUIRE(Error, \"Handler does not export a fetch() function.\");\n    }\n  } else {\n    // Fire off the handlers.\n    useDefaultHandling = dispatchEventImpl(lock, event.addRef());\n  }\n\n  if (useDefaultHandling) {\n    // No one called respondWith() or preventDefault(). Go directly to subrequest.\n\n    if (ioContext.taskCount() > tasksBefore) {\n      lock.logWarning(\n          \"FetchEvent handler did not call respondWith() before returning, but initiated some \"\n          \"asynchronous task. That task will be canceled and default handling will occur -- the \"\n          \"request will be sent unmodified to your origin. Remember that you must call \"\n          \"respondWith() *before* the event handler returns, if you don't want default handling. \"\n          \"You cannot call it asynchronously later on. If you need to wait for I/O (e.g. a \"\n          \"subrequest) before generating a Response, then call respondWith() with a Promise (for \"\n          \"the eventual Response) as the argument.\");\n    }\n\n    KJ_IF_SOME(jsStream, maybeJsStream) {\n      if (jsStream->isDisturbed()) {\n        lock.logUncaughtException(\n            \"Script consumed request body but didn't call respondWith(). Can't forward request.\");\n        return addNoopDeferredProxy(\n            response.sendError(500, \"Internal Server Error\", ioContext.getHeaderTable()));\n      }\n      maybeJsStream = kj::none;  // Release our reference; we don't need it anymore.\n    }\n\n    auto client = ioContext.getHttpClient(\n        IoContext::NEXT_CLIENT_CHANNEL, false, mapCopyString(cfBlobJson), \"fetch_default\"_kjc);\n    auto adapter = kj::newHttpService(*client);\n    auto promise = adapter->request(method, url, headers, requestBody, response);\n    // Default handling doesn't rely on the IoContext at all so we can return it as a\n    // deferred proxy task.\n    return DeferredProxy<void>{promise.attach(kj::mv(adapter), kj::mv(client))};\n  } else KJ_IF_SOME(promise, event->getResponsePromise(lock)) {\n    maybeJsStream = kj::none;  // Release reference to request body stream. We don't need it.\n    auto body2 = kj::addRef(*ownRequestBody);\n\n    // HACK: If the client disconnects, the `response` reference is no longer valid. But our\n    //   promise resolves in JavaScript space, so won't be canceled. So we need to track\n    //   cancellation separately. We use a weird refcounted boolean.\n    // TODO(cleanup): Is there something less ugly we can do here?\n    auto canceled = kj::refcounted<kj::RefcountedWrapper<bool>>(false);\n\n    return ioContext\n        .awaitJs(lock,\n            promise.then(kj::implicitCast<jsg::Lock&>(lock),\n                ioContext.addFunctor(\n                    [&response, allowWebSocket = headers.isWebSocket(),\n                        canceled = canceled->addWrappedRef(), &headers, span = kj::mv(span)](\n                        jsg::Lock& js, jsg::Ref<Response> innerResponse) mutable\n                    -> IoOwn<kj::Promise<DeferredProxy<void>>> {\n      JSG_REQUIRE(innerResponse->getType() != \"error\"_kj, TypeError,\n          \"Return value from serve handler must not be an error response (like Response.error())\");\n\n      auto& context = IoContext::current();\n      // Drop our fetch_handler span now that the promise has resolved.\n      span = kj::none;\n      if (*canceled) {\n        // Oops, the client disconnected before the response was ready to send. `response` is\n        // a dangling reference, let's not use it.\n        return context.addObject(kj::heap(addNoopDeferredProxy(kj::READY_NOW)));\n      } else {\n        return context.addObject(kj::heap(\n            innerResponse->send(js, response, {.allowWebSocket = allowWebSocket}, headers)));\n      }\n    })))\n        .attach(\n            kj::defer([canceled = kj::mv(canceled)]() mutable { canceled->getWrapped() = true; }))\n        .then(\n            [ownRequestBody = kj::mv(ownRequestBody), deferredNeuter = kj::mv(deferredNeuter)](\n                DeferredProxy<void> deferredProxy) mutable {\n      // In the case of bidirectional streaming, the request body stream needs to remain valid\n      // while proxying the response. So, arrange for neutering to happen only after the proxy\n      // task finishes.\n      deferredProxy.proxyTask = deferredProxy.proxyTask\n                                    .then([body = kj::addRef(*ownRequestBody)]() mutable {\n        body->neuter(makeNeuterException(NeuterReason::SENT_RESPONSE));\n      }, [body = kj::addRef(*ownRequestBody)](kj::Exception&& e) mutable {\n        body->neuter(makeNeuterException(NeuterReason::THREW_EXCEPTION));\n        kj::throwFatalException(kj::mv(e));\n      }).attach(kj::mv(deferredNeuter));\n\n      return deferredProxy;\n    },\n            [body = kj::mv(body2)](kj::Exception&& e) mutable -> DeferredProxy<void> {\n      // HACK: We depend on the fact that the success-case lambda above hasn't been destroyed yet\n      //   so `deferredNeuter` hasn't been destroyed yet.\n      body->neuter(makeNeuterException(NeuterReason::THREW_EXCEPTION));\n      kj::throwFatalException(kj::mv(e));\n    });\n  } else {\n    // The service worker API says that if default handling is prevented and respondWith() wasn't\n    // called, the request should result in \"a network error\".\n    return KJ_EXCEPTION(DISCONNECTED, \"preventDefault() called but respondWith() not called\");\n  }\n}\n\nvoid ServiceWorkerGlobalScope::sendTraces(kj::ArrayPtr<kj::Own<Trace>> traces,\n    Worker::Lock& lock,\n    kj::Maybe<ExportedHandler&> exportedHandler) {\n  auto isolate = lock.getIsolate();\n  jsg::Lock& js = lock;\n\n  KJ_IF_SOME(h, exportedHandler) {\n    KJ_IF_SOME(f, h.tail) {\n      auto tailEvent = js.alloc<TailEvent>(lock, \"tail\"_kjc, traces);\n      auto promise = f(lock, tailEvent->getEvents(), h.env.addRef(isolate), h.getCtx());\n      tailEvent->waitUntil(kj::mv(promise));\n    } else KJ_IF_SOME(f, h.trace) {\n      auto traceEvent = js.alloc<TailEvent>(lock, \"trace\"_kjc, traces);\n      auto promise = f(lock, traceEvent->getEvents(), h.env.addRef(isolate), h.getCtx());\n      traceEvent->waitUntil(kj::mv(promise));\n    } else {\n      lock.logWarningOnce(\"Attempted to send events but we lack a handler, \"\n                          \"did you remember to export a tail() function?\");\n      JSG_FAIL_REQUIRE(Error, \"Handler does not export a tail() function.\");\n    }\n  } else {\n    // Fire off the handlers.\n    // We only create both events here.\n    auto tailEvent = js.alloc<TailEvent>(lock, \"tail\"_kjc, traces);\n    auto traceEvent = js.alloc<TailEvent>(lock, \"trace\"_kjc, traces);\n    dispatchEventImpl(lock, tailEvent.addRef());\n    dispatchEventImpl(lock, traceEvent.addRef());\n\n    // We assume no action is necessary for \"default\" trace handling.\n  }\n}\n\nvoid ServiceWorkerGlobalScope::startScheduled(kj::Date scheduledTime,\n    kj::StringPtr cron,\n    Worker::Lock& lock,\n    kj::Maybe<ExportedHandler&> exportedHandler) {\n  auto& context = IoContext::current();\n  jsg::Lock& js = lock;\n\n  double eventTime = (scheduledTime - kj::UNIX_EPOCH) / kj::MILLISECONDS;\n\n  auto event = js.alloc<ScheduledEvent>(eventTime, cron);\n\n  auto isolate = lock.getIsolate();\n\n  KJ_IF_SOME(h, exportedHandler) {\n    KJ_IF_SOME(f, h.scheduled) {\n      auto promise =\n          f(lock, js.alloc<ScheduledController>(event.addRef()), h.env.addRef(isolate), h.getCtx())\n              .then([&context]() {\n        KJ_IF_SOME(t, context.getWorkerTracer()) {\n          t.setReturn(context.now());\n        }\n      });\n      event->waitUntil(kj::mv(promise));\n    } else {\n      lock.logWarningOnce(\n          \"Received a ScheduledEvent but we lack a handler for ScheduledEvents \"\n          \"(a.k.a. Cron Triggers). Did you remember to export a scheduled() function?\");\n      context.setNoRetryScheduled();\n      JSG_FAIL_REQUIRE(Error, \"Handler does not export a scheduled() function\");\n    }\n  } else {\n    // Fire off the handlers after confirming there is at least one.\n    if (getHandlerCount(\"scheduled\") == 0) {\n      lock.logWarningOnce(\n          \"Received a ScheduledEvent but we lack an event listener for scheduled events \"\n          \"(a.k.a. Cron Triggers). Did you remember to call addEventListener(\\\"scheduled\\\", ...)?\");\n      context.setNoRetryScheduled();\n      JSG_FAIL_REQUIRE(Error, \"No event listener registered for scheduled events.\");\n    }\n    dispatchEventImpl(lock, event.addRef());\n  }\n}\n\nkj::Promise<WorkerInterface::AlarmResult> ServiceWorkerGlobalScope::runAlarm(kj::Date scheduledTime,\n    kj::Duration timeout,\n    uint32_t retryCount,\n    Worker::Lock& lock,\n    kj::Maybe<ExportedHandler&> exportedHandler) {\n\n  auto& context = IoContext::current();\n  auto& actor = KJ_ASSERT_NONNULL(context.getActor());\n  auto& persistent = KJ_ASSERT_NONNULL(actor.getPersistent());\n\n  kj::String actorId;\n  KJ_SWITCH_ONEOF(actor.getId()) {\n    KJ_CASE_ONEOF(f, kj::Own<ActorIdFactory::ActorId>) {\n      actorId = f->toString();\n    }\n    KJ_CASE_ONEOF(s, kj::String) {\n      actorId = kj::str(s);\n    }\n  }\n\n  auto currentTime = context.now();\n  KJ_SWITCH_ONEOF(persistent.armAlarmHandler(\n                      scheduledTime, context.getCurrentTraceSpan(), currentTime, false, actorId)) {\n    KJ_CASE_ONEOF(armResult, ActorCacheInterface::RunAlarmHandler) {\n      auto& handler = KJ_REQUIRE_NONNULL(exportedHandler);\n      if (handler.alarm == kj::none) {\n\n        lock.logWarningOnce(\"Attempted to run a scheduled alarm without a handler, \"\n                            \"did you remember to export an alarm() function?\");\n        return WorkerInterface::AlarmResult{\n          .retry = false, .outcome = EventOutcome::SCRIPT_NOT_FOUND};\n      }\n\n      auto& alarm = KJ_ASSERT_NONNULL(handler.alarm);\n\n      return context\n          .run([exportedHandler, &context, timeout, retryCount, &alarm,\n                   maybeAsyncContext = jsg::AsyncContextFrame::currentRef(lock)](\n                   Worker::Lock& lock) mutable -> kj::Promise<WorkerInterface::AlarmResult> {\n        jsg::AsyncContextFrame::Scope asyncScope(lock, maybeAsyncContext);\n        // We want to limit alarm handler walltime to 15 minutes at most. If the timeout promise\n        // completes we want to cancel the alarm handler. If the alarm handler promise completes\n        // first timeout will be canceled.\n        jsg::Lock& js = lock;\n        auto timeoutPromise = context.afterLimitTimeout(timeout).then(\n            [&context]() -> kj::Promise<WorkerInterface::AlarmResult> {\n          // We don't want to delete the alarm since we have not successfully completed the alarm\n          // execution.\n          auto& actor = KJ_ASSERT_NONNULL(context.getActor());\n          auto& persistent = KJ_ASSERT_NONNULL(actor.getPersistent());\n          persistent.cancelDeferredAlarmDeletion();\n\n          LOG_NOSENTRY(WARNING, \"Alarm exceeded its allowed execution time\");\n          // Report alarm handler failure and log it.\n          auto e = KJ_EXCEPTION(OVERLOADED,\n              \"broken.dropped; worker_do_not_log; jsg.Error: Alarm exceeded its allowed execution time\");\n          context.getMetrics().reportFailure(e);\n\n          // We don't want the handler to keep running after timeout.\n          context.abort(kj::mv(e));\n          // We want timed out alarms to be treated as user errors. As such, we'll mark them as\n          // retriable, and we'll count the retries against the alarm retries limit. This will ensure\n          // that the handler will attempt to run for a number of times before giving up and deleting\n          // the alarm.\n          return WorkerInterface::AlarmResult{\n            .retry = true, .retryCountsAgainstLimit = true, .outcome = EventOutcome::EXCEEDED_CPU};\n        });\n\n        return alarm(lock, js.alloc<AlarmInvocationInfo>(retryCount))\n            .then([]() -> kj::Promise<WorkerInterface::AlarmResult> {\n          return WorkerInterface::AlarmResult{.retry = false, .outcome = EventOutcome::OK};\n        }).exclusiveJoin(kj::mv(timeoutPromise));\n      })\n          .catch_([&context, deferredDelete = kj::mv(armResult.deferredDelete)](\n                      kj::Exception&& e) mutable {\n        auto& actor = KJ_ASSERT_NONNULL(context.getActor());\n        auto& persistent = KJ_ASSERT_NONNULL(actor.getPersistent());\n        persistent.cancelDeferredAlarmDeletion();\n\n        context.getMetrics().reportFailure(e);\n\n        auto description = kj::str(e.getDescription());  // because e is moved before this is used\n        auto log = !jsg::isTunneledException(description) && !jsg::isDoNotLogException(description);\n        auto isUserError = e.getDetail(jsg::EXCEPTION_IS_USER_ERROR) != kj::none;\n\n        // This will include the error in inspector/tracers and log to syslog if internal.\n        context.logUncaughtExceptionAsync(UncaughtExceptionSource::ALARM_HANDLER, kj::mv(e));\n\n        EventOutcome outcome = EventOutcome::EXCEPTION;\n        KJ_IF_SOME(status, context.getLimitEnforcer().getLimitsExceeded()) {\n          outcome = status;\n        }\n\n        kj::String actorId;\n        KJ_SWITCH_ONEOF(actor.getId()) {\n          KJ_CASE_ONEOF(f, kj::Own<ActorIdFactory::ActorId>) {\n            actorId = f->toString();\n          }\n          KJ_CASE_ONEOF(s, kj::String) {\n            actorId = kj::str(s);\n          }\n        }\n\n        // We only want to retry against limits if it's a user error. By default let's check if the\n        // output gate is broken.\n        auto shouldRetryCountsAgainstLimits = !context.isOutputGateBroken();\n\n        // We want to alert if we aren't going to count this alarm retry against limits\n        if (log && context.isOutputGateBroken()) {\n          LOG_NOSENTRY(ERROR, \"output lock broke during alarm execution\", actorId, description);\n        } else if (context.isOutputGateBroken()) {\n          if (isUserError) {\n            // The handler failed because the user overloaded the object. It's their fault, we'll not\n            // retry forever.\n            shouldRetryCountsAgainstLimits = true;\n          }\n\n          // We don't usually log these messages, but it's useful to know the real reason we failed\n          // to correctly investigate stuck alarms.\n          LOG_NOSENTRY(ERROR,\n              \"output lock broke during alarm execution without an interesting error description\",\n              actorId, description, shouldRetryCountsAgainstLimits);\n        }\n        return WorkerInterface::AlarmResult{.retry = true,\n          .retryCountsAgainstLimit = shouldRetryCountsAgainstLimits,\n          .outcome = outcome};\n      })\n          .then([&context](WorkerInterface::AlarmResult result)\n                    -> kj::Promise<WorkerInterface::AlarmResult> {\n        return context.waitForOutputLocks().then(\n            [result]() { return kj::mv(result); }, [&context](kj::Exception&& e) {\n          auto& actor = KJ_ASSERT_NONNULL(context.getActor());\n          kj::String actorId;\n          KJ_SWITCH_ONEOF(actor.getId()) {\n            KJ_CASE_ONEOF(f, kj::Own<ActorIdFactory::ActorId>) {\n              actorId = f->toString();\n            }\n            KJ_CASE_ONEOF(s, kj::String) {\n              actorId = kj::str(s);\n            }\n          }\n          // We only want to retry against limits if it's a user error. By default let's assume it's our\n          // fault.\n          auto shouldRetryCountsAgainstLimits = false;\n          if (auto desc = e.getDescription();\n              !jsg::isTunneledException(desc) && !jsg::isDoNotLogException(desc)) {\n            if (isInterestingException(e)) {\n              LOG_EXCEPTION(\"alarmOutputLock\"_kj, e);\n            } else {\n              LOG_NOSENTRY(ERROR, \"output lock broke after executing alarm\", actorId, e);\n            }\n          } else {\n            if (e.getDetail(jsg::EXCEPTION_IS_USER_ERROR) != kj::none) {\n              // The handler failed because the user overloaded the object. It's their fault, we'll not\n              // retry forever.\n              shouldRetryCountsAgainstLimits = true;\n            }\n          }\n          return WorkerInterface::AlarmResult{.retry = true,\n            .retryCountsAgainstLimit = shouldRetryCountsAgainstLimits,\n            .outcome = EventOutcome::EXCEPTION};\n        });\n      });\n    }\n    KJ_CASE_ONEOF(armResult, ActorCacheInterface::CancelAlarmHandler) {\n      return armResult.waitBeforeCancel.then([]() {\n        return WorkerInterface::AlarmResult{.retry = false, .outcome = EventOutcome::CANCELED};\n      });\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\njsg::Promise<void> ServiceWorkerGlobalScope::test(\n    Worker::Lock& lock, kj::Maybe<ExportedHandler&> exportedHandler) {\n  // TODO(someday): For Service Workers syntax, do we want addEventListener(\"test\")? Not supporting\n  //   it for now.\n  ExportedHandler& eh = JSG_REQUIRE_NONNULL(\n      exportedHandler, Error, \"Tests are not currently supported with Service Workers syntax.\");\n\n  auto& testHandler =\n      JSG_REQUIRE_NONNULL(eh.test, Error, \"Entrypoint does not export a test() function.\");\n\n  jsg::Lock& js = lock;\n  return testHandler(lock, js.alloc<TestController>(), eh.env.addRef(lock), eh.getCtx());\n}\n\n// This promise is used to set the timeout for hibernatable websocket events. It's expected to be\n// dropped in most cases, as long as the hibernatable websocket event promise completes before it.\nkj::Promise<void> ServiceWorkerGlobalScope::eventTimeoutPromise(uint32_t timeoutMs) {\n  auto& actor = KJ_ASSERT_NONNULL(IoContext::current().getActor());\n  co_await IoContext::current().afterLimitTimeout(timeoutMs * kj::MILLISECONDS);\n  // This is the ActorFlushReason for eviction in Cloudflare's internal implementation.\n  auto evictionCode = 2;\n  actor.shutdown(evictionCode,\n      KJ_EXCEPTION(DISCONNECTED,\n          \"broken.dropped; jsg.Error: Actor exceeded event execution time and was disconnected.\"));\n}\n\nkj::Promise<void> ServiceWorkerGlobalScope::setHibernatableEventTimeout(\n    kj::Promise<void> event, kj::Maybe<uint32_t> eventTimeoutMs) {\n  // If we have a maximum event duration timeout set, we should prevent the actor from running\n  // for more than the user selected duration.\n  auto timeoutMs = eventTimeoutMs.orDefault(static_cast<uint32_t>(0));\n  if (timeoutMs > 0) {\n    return event.exclusiveJoin(eventTimeoutPromise(timeoutMs));\n  }\n  return event;\n}\n\n// TODO(cleanup): the hibernatable websocket handler functions here are largely identical – consider\n// folding them.\n//\n// Note: The hibernatable WebSocket message path passes kj::OneOf<kj::String, kj::Array<byte>>\n// directly to the webSocketMessage() handler, so binary data is always delivered as ArrayBuffer.\n// The WebSocket binaryType property (and the websocket_standard_binary_type compat flag) has no\n// effect here — this is by design for the Durable Object handler API, which bypasses the\n// normal WebSocket read loop and its Blob/ArrayBuffer dispatch logic.\nvoid ServiceWorkerGlobalScope::sendHibernatableWebSocketMessage(IoContext& context,\n    kj::OneOf<kj::String, kj::Array<byte>> message,\n    kj::Maybe<uint32_t> eventTimeoutMs,\n    kj::String websocketId,\n    Worker::Lock& lock,\n    kj::Maybe<ExportedHandler&> exportedHandler) {\n  jsg::Lock& js = lock;\n  auto event = js.alloc<HibernatableWebSocketEvent>();\n  // Even if no handler is exported, we need to claim the websocket so it's removed from the map.\n  auto websocket = event->claimWebSocket(lock, websocketId);\n\n  KJ_IF_SOME(h, exportedHandler) {\n    KJ_IF_SOME(handler, h.webSocketMessage) {\n      event->waitUntil(setHibernatableEventTimeout(\n          handler(lock, kj::mv(websocket), kj::mv(message)), eventTimeoutMs)\n                           .then([&context]() {\n        KJ_IF_SOME(t, context.getWorkerTracer()) {\n          t.setReturn(context.now());\n        }\n      }));\n    }\n    // We want to deliver a message, but if no webSocketMessage handler is exported, we shouldn't fail\n  }\n}\n\nvoid ServiceWorkerGlobalScope::sendHibernatableWebSocketClose(IoContext& context,\n    HibernatableSocketParams::Close close,\n    kj::Maybe<uint32_t> eventTimeoutMs,\n    kj::String websocketId,\n    Worker::Lock& lock,\n    kj::Maybe<ExportedHandler&> exportedHandler) {\n  jsg::Lock& js = lock;\n  auto event = js.alloc<HibernatableWebSocketEvent>();\n\n  // Even if no handler is exported, we need to claim the websocket so it's removed from the map.\n  //\n  // We won't be dispatching any further events because we've received a close, so we return the\n  // owned websocket back to the api::WebSocket.\n  auto releasePackage = event->prepareForRelease(lock, websocketId);\n  auto websocket = kj::mv(releasePackage.webSocketRef);\n  websocket->initiateHibernatableRelease(lock, kj::mv(releasePackage.ownedWebSocket),\n      kj::mv(releasePackage.tags), api::WebSocket::HibernatableReleaseState::CLOSE);\n  KJ_IF_SOME(h, exportedHandler) {\n    KJ_IF_SOME(handler, h.webSocketClose) {\n      event->waitUntil(setHibernatableEventTimeout(\n          handler(lock, kj::mv(websocket), close.code, kj::mv(close.reason), close.wasClean),\n          eventTimeoutMs)\n                           .then([&context]() {\n        KJ_IF_SOME(t, context.getWorkerTracer()) {\n          t.setReturn(context.now());\n        }\n      }));\n    }\n    // We want to deliver close, but if no webSocketClose handler is exported, we shouldn't fail\n  }\n}\n\nvoid ServiceWorkerGlobalScope::sendHibernatableWebSocketError(IoContext& context,\n    kj::Exception e,\n    kj::Maybe<uint32_t> eventTimeoutMs,\n    kj::String websocketId,\n    Worker::Lock& lock,\n    kj::Maybe<ExportedHandler&> exportedHandler) {\n  jsg::Lock& js = lock;\n  auto event = js.alloc<HibernatableWebSocketEvent>();\n\n  // Even if no handler is exported, we need to claim the websocket so it's removed from the map.\n  //\n  // We won't be dispatching any further events because we've encountered an error, so we return\n  // the owned websocket back to the api::WebSocket.\n  auto releasePackage = event->prepareForRelease(lock, websocketId);\n  auto& websocket = releasePackage.webSocketRef;\n  websocket->initiateHibernatableRelease(lock, kj::mv(releasePackage.ownedWebSocket),\n      kj::mv(releasePackage.tags), WebSocket::HibernatableReleaseState::ERROR);\n\n  KJ_IF_SOME(h, exportedHandler) {\n    KJ_IF_SOME(handler, h.webSocketError) {\n      event->waitUntil(setHibernatableEventTimeout(\n          handler(js, kj::mv(websocket), js.exceptionToJs(kj::mv(e))), eventTimeoutMs)\n                           .then([&context]() {\n        KJ_IF_SOME(t, context.getWorkerTracer()) {\n          t.setReturn(context.now());\n        }\n      }));\n    }\n    // We want to deliver an error, but if no webSocketError handler is exported, we shouldn't fail\n  }\n}\n\nvoid ServiceWorkerGlobalScope::emitPromiseRejection(jsg::Lock& js,\n    v8::PromiseRejectEvent event,\n    jsg::V8Ref<v8::Promise> promise,\n    jsg::Value value) {\n\n  const auto hasHandlers = [this] {\n    return getHandlerCount(\"unhandledrejection\"_kj) + getHandlerCount(\"rejectionhandled\"_kj);\n  };\n\n  const auto hasInspector = [] {\n    KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n      return ioContext.isInspectorEnabled();\n    } else {\n      return false;\n    }\n  };\n\n  if (hasHandlers() || hasInspector()) {\n    unhandledRejections.setUseMicrotasksCompletedCallback(\n        FeatureFlags::get(js).getUnhandledRejectionAfterMicrotaskCheckpoint());\n    unhandledRejections.report(js, event, kj::mv(promise), kj::mv(value));\n  }\n}\n\nvoid ServiceWorkerGlobalScope::setConnectOverride(kj::String networkAddress, ConnectFn connectFn) {\n  connectOverrides.upsert(kj::mv(networkAddress), kj::mv(connectFn));\n}\n\nkj::Maybe<ServiceWorkerGlobalScope::ConnectFn&> ServiceWorkerGlobalScope::getConnectOverride(\n    kj::StringPtr networkAddress) {\n  return connectOverrides.find(networkAddress);\n}\n\njsg::JsString ServiceWorkerGlobalScope::btoa(jsg::Lock& js, jsg::JsString str) {\n  // We could implement btoa() by accepting a kj::String, but then we'd have to check that it\n  // doesn't have any multibyte code points. Easier to perform that test using v8::String's\n  // ContainsOnlyOneByte() function.\n  JSG_REQUIRE(str.containsOnlyOneByte(), DOMInvalidCharacterError,\n      \"btoa() can only operate on characters in the Latin1 (ISO/IEC 8859-1) range.\");\n  auto strArray = str.toArray<kj::byte>(js);\n  auto expected_length = simdutf::base64_length_from_binary(strArray.size());\n  KJ_STACK_ARRAY(kj::byte, result, expected_length, 1024, 1024);\n  auto written = simdutf::binary_to_base64(\n      strArray.asChars().begin(), strArray.size(), result.asChars().begin());\n  return js.str(result.first(written));\n}\n\n#ifdef WORKERD_FUZZILLI\nvoid ServiceWorkerGlobalScope::fuzzilli(jsg::Lock& js, jsg::Arguments<jsg::Value> args) {\n  // Delegate to the fuzzilli handler in fuzzilli.c++\n  fuzzilli_handler(js, args);\n}\n#endif\n\njsg::JsString ServiceWorkerGlobalScope::atob(jsg::Lock& js, kj::String data) {\n  auto decoded = kj::decodeBase64(data.asArray());\n\n  JSG_REQUIRE(!decoded.hadErrors, DOMInvalidCharacterError,\n      \"atob() called with invalid base64-encoded data. (Only whitespace, '+', '/', alphanumeric \"\n      \"ASCII, and up to two terminal '=' signs when the input data length is divisible by 4 are \"\n      \"allowed.)\");\n\n  // Similar to btoa() taking a v8::Value, we return a v8::String directly, as this allows us to\n  // construct a string from the non-nul-terminated array returned from decodeBase64(). This avoids\n  // making a copy purely to append a nul byte.\n  return js.str(decoded.asBytes());\n}\n\nvoid ServiceWorkerGlobalScope::queueMicrotask(jsg::Lock& js, jsg::Function<void()> task) {\n  auto fn = js.wrapSimpleFunction(js.v8Context(),\n      JSG_VISITABLE_LAMBDA((this, fn = kj::mv(task)), (fn),\n          (jsg::Lock& js, const v8::FunctionCallbackInfo<v8::Value>& args) {\n            js.tryCatch([&] {\n              // The function won't be called with any arguments, so we can\n              // safely ignore anything passed in to args.\n              fn(js);\n            }, [&](jsg::Value exception) {\n              // The reportError call itself can potentially throw errors. Let's catch\n              // and report them as well.\n              js.tryCatch([&] { reportError(js, jsg::JsValue(exception.getHandle(js))); },\n                  [&](jsg::Value exception) {\n                // An error was thrown by the 'error' event handler. That's unfortunate.\n                // Let's log the error and just continue. It won't be possible to actually\n                // catch or handle this error so logging is really the only way to notify\n                // folks about it.\n                auto val = jsg::JsValue(exception.getHandle(js));\n                // If the value is an object that has a stack property, log that so we get\n                // the stack trace if it is an exception.\n                KJ_IF_SOME(obj, val.tryCast<jsg::JsObject>()) {\n                auto stack = obj.get(js, \"stack\"_kj);\n                if (!stack.isUndefined()) {\n                js.reportError(stack);\n                return;\n                }\n                } else {\n                }  // Here to avoid a compile warning\n                // Otherwise just log the stringified value generically.\n                js.reportError(val);\n              });\n            });\n          }));\n\n  js.v8Isolate->EnqueueMicrotask(fn);\n}\n\njsg::JsValue ServiceWorkerGlobalScope::structuredClone(\n    jsg::Lock& js, jsg::JsValue value, jsg::Optional<StructuredCloneOptions> maybeOptions) {\n  KJ_IF_SOME(options, maybeOptions) {\n    KJ_IF_SOME(transfer, options.transfer) {\n      auto transfers = KJ_MAP(i, transfer) { return i.getHandle(js); };\n      return value.structuredClone(js, kj::mv(transfers));\n    }\n  }\n  return value.structuredClone(js);\n}\n\nTimeoutId::NumberType ServiceWorkerGlobalScope::setTimeoutInternal(\n    jsg::Function<void()> function, double msDelay) {\n  auto timeoutId = IoContext::current().setTimeoutImpl(timeoutIdGenerator,\n      /* repeat */ false, kj::mv(function), msDelay);\n  return timeoutId.toNumber();\n}\n\nTimeoutId::NumberType ServiceWorkerGlobalScope::setTimeout(jsg::Lock& js,\n    jsg::Function<void(jsg::Arguments<jsg::Value>)> function,\n    jsg::Optional<double> msDelay,\n    jsg::Arguments<jsg::Value> args) {\n  function.setReceiver(js.v8Ref<v8::Value>(js.v8Context()->Global()));\n  auto fn = [function = kj::mv(function), args = kj::mv(args),\n                context = jsg::AsyncContextFrame::currentRef(js)](jsg::Lock& js) mutable {\n    jsg::AsyncContextFrame::Scope scope(js, context);\n    function(js, kj::mv(args));\n  };\n  auto timeoutId = IoContext::current().setTimeoutImpl(timeoutIdGenerator,\n      /* repeat */ false, [function = kj::mv(fn)](jsg::Lock& js) mutable { function(js); },\n      msDelay.orDefault(0));\n  return timeoutId.toNumber();\n}\n\nvoid ServiceWorkerGlobalScope::clearTimeout(jsg::Lock& js, kj::Maybe<jsg::JsNumber> timeoutId) {\n  KJ_IF_SOME(rawId, timeoutId) {\n    // Browsers does not throw an error when \"unsafe\" integers are passed to the clearTimeout method.\n    // Let's make sure we ignore those values, just like browsers and other runtimes.\n    KJ_IF_SOME(id, rawId.toSafeInteger(js)) {\n      IoContext::current().clearTimeoutImpl(TimeoutId::fromNumber(id));\n    }\n  }\n}\n\nTimeoutId::NumberType ServiceWorkerGlobalScope::setInterval(jsg::Lock& js,\n    jsg::Function<void(jsg::Arguments<jsg::Value>)> function,\n    jsg::Optional<double> msDelay,\n    jsg::Arguments<jsg::Value> args) {\n  function.setReceiver(js.v8Ref<v8::Value>(js.v8Context()->Global()));\n  auto fn = [function = kj::mv(function), args = kj::mv(args),\n                context = jsg::AsyncContextFrame::currentRef(js)](jsg::Lock& js) mutable {\n    jsg::AsyncContextFrame::Scope scope(js, context);\n    // Because the fn is called multiple times, we will clone the args on each call.\n    auto argv = KJ_MAP(i, args) { return i.addRef(js); };\n    function(js, jsg::Arguments(kj::mv(argv)));\n  };\n  auto timeoutId = IoContext::current().setTimeoutImpl(timeoutIdGenerator,\n      /* repeat */ true, [function = kj::mv(fn)](jsg::Lock& js) mutable { function(js); },\n      msDelay.orDefault(0));\n  return timeoutId.toNumber();\n}\n\nvoid ServiceWorkerGlobalScope::clearInterval(jsg::Lock& js, kj::Maybe<jsg::JsNumber> timeoutId) {\n  clearTimeout(js, kj::mv(timeoutId));\n}\n\njsg::Ref<Crypto> ServiceWorkerGlobalScope::getCrypto(jsg::Lock& js) {\n  return js.alloc<Crypto>(js);\n}\n\njsg::Ref<CacheStorage> ServiceWorkerGlobalScope::getCaches(jsg::Lock& js) {\n  return js.alloc<CacheStorage>(js);\n}\n\njsg::Promise<jsg::Ref<Response>> ServiceWorkerGlobalScope::fetch(jsg::Lock& js,\n    kj::OneOf<jsg::Ref<Request>, kj::String> requestOrUrl,\n    jsg::Optional<Request::Initializer> requestInit) {\n  return fetchImpl(js, kj::none, kj::mv(requestOrUrl), kj::mv(requestInit));\n}\n\nvoid ServiceWorkerGlobalScope::reportError(jsg::Lock& js, jsg::JsValue error) {\n  // Per the spec, we are going to first emit an error event on the global object.\n  // If that event is not prevented, we will log the error to the console. Note\n  // that we do not throw the error at all.\n  auto message = v8::Exception::CreateMessage(js.v8Isolate, error);\n  auto event = js.alloc<ErrorEvent>(ErrorEvent::ErrorEventInit{.message = kj::str(message->Get()),\n    .filename = kj::str(message->GetScriptResourceName()),\n    .lineno = jsg::check(message->GetLineNumber(js.v8Context())),\n    .colno = jsg::check(message->GetStartColumn(js.v8Context())),\n    .error = jsg::JsRef(js, error)});\n  if (dispatchEventImpl(js, kj::mv(event))) {\n    // If the value is an object that has a stack property, log that so we get\n    // the stack trace if it is an exception.\n    KJ_IF_SOME(obj, error.tryCast<jsg::JsObject>()) {\n      auto stack = obj.get(js, \"stack\"_kj);\n      if (!stack.isUndefined()) {\n        js.reportError(stack);\n        return;\n      }\n    }\n    // Otherwise just log the stringified value generically.\n    js.reportError(error);\n  }\n}\n\njsg::JsValue ServiceWorkerGlobalScope::getBuffer(jsg::Lock& js) {\n  KJ_IF_SOME(p, bufferValue) {\n    return p.getHandle(js);\n  }\n  constexpr auto kSpecifier = \"node:buffer\"_kj;\n  KJ_IF_SOME(module, js.resolveModule(kSpecifier)) {\n    KJ_IF_SOME(p, bufferValue) {\n      // There's a chance that resolving the module caused side-effects\n      // that set the bufferValue, we let's check again.\n      return p.getHandle(js);\n    }\n    // When requireReturnsDefaultExport flag is enabled, resolveModule returns the\n    // default export directly. Otherwise it returns the module namespace.\n    auto obj = module.has(js, \"default\"_kj)\n        ? KJ_ASSERT_NONNULL(module.get(js, \"default\"_kj).tryCast<jsg::JsObject>())\n        : module;\n    auto buffer = obj.get(js, \"Buffer\"_kj);\n    JSG_REQUIRE(buffer.isFunction(), TypeError, \"Invalid node:buffer implementation\");\n    bufferValue = jsg::JsRef(js, buffer);\n    return buffer;\n  } else {\n    // If we are unable to resolve the node:buffer module here, it likely\n    // means that we don't actually have a module registry installed. Just\n    // return undefined in this case.\n    bufferValue = jsg::JsRef(js, js.undefined());\n    return js.undefined();\n  }\n}\n\nvoid ServiceWorkerGlobalScope::setProcess(jsg::Lock& js, jsg::JsValue newProcess) {\n  processValue = jsg::JsRef(js, newProcess);\n}\n\nvoid ServiceWorkerGlobalScope::setBuffer(jsg::Lock& js, jsg::JsValue newBuffer) {\n  bufferValue = jsg::JsRef(js, newBuffer);\n}\n\njsg::JsValue ServiceWorkerGlobalScope::getProcess(jsg::Lock& js) {\n  KJ_IF_SOME(p, processValue) {\n    return p.getHandle(js);\n  }\n  // Handle process module redirection based on enable_nodejs_process_v2 flag\n  auto specifier = ([&]() -> kj::StringPtr {\n    if (FeatureFlags::get(js).getEnableNodeJsProcessV2()) {\n      return \"node-internal:public_process\"_kj;\n    } else {\n      return \"node-internal:legacy_process\"_kj;\n    }\n  })();\n\n  KJ_IF_SOME(module, js.resolveInternalModule(specifier)) {\n    KJ_IF_SOME(p, processValue) {\n      // There's a chance that resolving the module caused side-effects\n      // that set the processValue, we let's check again.\n      return p.getHandle(js);\n    }\n    // When requireReturnsDefaultExport flag is enabled, resolveInternalModule returns the\n    // default export directly. Otherwise it returns the module namespace.\n    auto def = module.has(js, \"default\"_kj) ? module.get(js, \"default\"_kj) : jsg::JsValue(module);\n    JSG_REQUIRE(def.isObject(), TypeError, \"Invalid node:process implementation\");\n    processValue = jsg::JsRef(js, def);\n    return def;\n  } else {\n    // If we are unable to resolve the internal module here, it likely\n    // means that we don't actually have a module registry installed. Just\n    // return undefined in this case.\n    processValue = jsg::JsRef(js, js.undefined());\n    return js.undefined();\n  }\n}\n\njsg::Ref<StorageManager> Navigator::getStorage(jsg::Lock& js) {\n  return js.alloc<StorageManager>();\n}\n\nbool Navigator::sendBeacon(jsg::Lock& js, kj::String url, jsg::Optional<Body::Initializer> body) {\n  KJ_IF_SOME(context, IoContext::tryCurrent()) {\n    auto v8Context = js.v8Context();\n    auto& global =\n        jsg::extractInternalPointer<ServiceWorkerGlobalScope, true>(v8Context, v8Context->Global());\n    auto promise = global.fetch(js, kj::mv(url),\n        Request::InitializerDict{\n          .method = kj::str(\"POST\"),\n          .body = kj::mv(body),\n        });\n\n    context.addWaitUntil(context.awaitJs(js, kj::mv(promise)).ignoreResult());\n    return true;\n  }\n\n  // We cannot schedule a beacon to be sent outside of a request context.\n  return false;\n}\n\n// ======================================================================================\n\nImmediate::Immediate(IoContext& context, TimeoutId timeoutId)\n    : ioContext(context.addObject(context)),\n      timeoutId(timeoutId) {}\n\nvoid Immediate::dispose() {\n  // IoPtr will throw if the IoContext is no longer valid, which is fine - accessing the Immediate\n  // from the wrong IoContext should throw an error, just as accessing the Immediate when it's\n  // owning IoContext is gone.\n  ioContext->clearTimeoutImpl(timeoutId);\n}\n\njsg::Ref<Immediate> ServiceWorkerGlobalScope::setImmediate(jsg::Lock& js,\n    jsg::Function<void(jsg::Arguments<jsg::Value>)> function,\n    jsg::Arguments<jsg::Value> args) {\n\n  // This is an approximation of the Node.js setImmediate global API.\n  // We implement it in terms of setting a 0 ms timeout. This is not\n  // how Node.js does it so there will be some edge cases where the\n  // timing of the callback will differ relative to the equivalent\n  // operations in Node.js. For the vast majority of cases, users\n  // really shouldn't be able to tell a difference. It would likely\n  // only be somewhat pathological edge cases that could be affected\n  // by the differences. Unfortunately, changing this later to match\n  // Node.js would likely be a breaking change for some users that\n  // would require a compat flag... but that's OK for now?\n\n  auto& context = IoContext::current();\n  auto fn = [function = kj::mv(function), args = kj::mv(args),\n                context = jsg::AsyncContextFrame::currentRef(js)](jsg::Lock& js) mutable {\n    jsg::AsyncContextFrame::Scope scope(js, context);\n    function(js, kj::mv(args));\n  };\n  auto timeoutId = context.setTimeoutImpl(timeoutIdGenerator,\n      /* repeat */ false, [function = kj::mv(fn)](jsg::Lock& js) mutable { function(js); }, 0);\n  return js.alloc<Immediate>(context, timeoutId);\n}\n\nvoid ServiceWorkerGlobalScope::clearImmediate(kj::Maybe<jsg::Ref<Immediate>> maybeImmediate) {\n  KJ_IF_SOME(immediate, maybeImmediate) {\n    immediate->dispose();\n  }\n}\n\njsg::JsObject Cloudflare::getCompatibilityFlags(jsg::Lock& js) {\n  auto flags = FeatureFlags::get(js);\n  auto obj = js.objNoProto();\n  auto dynamic = capnp::toDynamic(flags);\n  auto schema = dynamic.getSchema();\n\n  bool skipExperimental = !flags.getWorkerdExperimental();\n\n  for (auto field: schema.getFields()) {\n    // If this is an experimental flag, we expose it only if the experimental mode\n    // is enabled.\n    auto annotations = field.getProto().getAnnotations();\n    bool skip = false;\n    if (skipExperimental) {\n      for (auto annotation: annotations) {\n        if (annotation.getId() == EXPERIMENTAl_ANNOTATION_ID) {\n          skip = true;\n          break;\n        }\n      }\n    }\n    if (skip) continue;\n\n    // Note that disable flags are not exposed.\n    for (auto annotation: annotations) {\n      if (annotation.getId() == COMPAT_ENABLE_FLAG_ANNOTATION_ID) {\n        obj.setReadOnly(\n            js, annotation.getValue().getText(), js.boolean(dynamic.get(field).as<bool>()));\n      }\n    }\n  }\n\n  obj.seal(js);\n  return obj;\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/global-scope.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"basics.h\"\n#include \"filesystem.h\"\n#include \"http.h\"\n#include \"messagechannel.h\"\n#include \"performance.h\"\n\n#include <workerd/api/hibernation-event-params.h>\n#ifdef WORKERD_FUZZILLI\n#include \"unsafe.h\"\n\n#include <workerd/api/fuzzilli.h>\n#endif\n\n#include <workerd/io/features.h>\n#include <workerd/io/io-timers.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::jsg {\nclass DOMException;\n}  // namespace workerd::jsg\n\nnamespace workerd::api {\n\nclass TailEvent;\nclass Cache;\nclass CacheStorage;\nclass Crypto;\nclass CryptoKey;\nclass ErrorEvent;\nclass EventSource;\nclass FixedLengthStream;\nclass SubtleCrypto;\nclass TextDecoder;\nclass TextEncoder;\nclass HTMLRewriter;\nclass IdentityTransformStream;\nclass Response;\nclass TraceItem;\nclass TransformStream;\nclass ScheduledController;\nclass ScheduledEvent;\nclass ReadableStream;\nclass ReadableStreamDefaultReader;\nclass ReadableStreamBYOBReader;\nclass ReadableStreamBYOBRequest;\nclass WritableStream;\nclass WritableStreamDefaultWriter;\nclass ReadableStreamBYOBRequest;\nclass ReadableStreamDefaultController;\nclass ReadableByteStreamController;\nclass WritableStreamDefaultController;\nclass CompressionStream;\nclass DecompressionStream;\nclass TextEncoderStream;\nclass TextDecoderStream;\nclass Blob;\nclass File;\nclass FormData;\n\nclass URLPattern;\nnamespace urlpattern {\nclass URLPattern;\n}  // namespace urlpattern\n\nclass URL;\nclass URLSearchParams;\nnamespace url {\nclass URL;\nclass URLSearchParams;\n}  // namespace url\n\n// We need access to DOMException within this namespace so JSG_NESTED_TYPE can name it correctly.\nusing DOMException = jsg::DOMException;\n\n// A subset of the standard Navigator API.\nclass Navigator: public jsg::Object {\n public:\n  kj::StringPtr getUserAgent() {\n    return \"Cloudflare-Workers\"_kj;\n  }\n\n  bool sendBeacon(jsg::Lock& js, kj::String url, jsg::Optional<Body::Initializer> body);\n\n  kj::uint getHardwareConcurrency() {\n    // Workers does not expose hardware concurrency to users.\n    // From the user code perspective there's only one core.\n    return 1;\n  }\n\n  kj::StringPtr getLanguage() {\n    // Some packages depend on navigator.language being set to a specific value.\n    return \"en\"_kj;\n  }\n\n  kj::Array<kj::StringPtr> getLanguages() {\n    auto builder = kj::heapArrayBuilder<kj::StringPtr>(1);\n    builder.add(\"en\"_kjc);\n    return builder.finish();\n  }\n\n  jsg::Ref<StorageManager> getStorage(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(Navigator, CompatibilityFlags::Reader reader) {\n    JSG_METHOD(sendBeacon);\n    JSG_READONLY_INSTANCE_PROPERTY(userAgent, getUserAgent);\n    JSG_READONLY_INSTANCE_PROPERTY(hardwareConcurrency, getHardwareConcurrency);\n\n    if (reader.getEnableNavigatorLanguage()) {\n      JSG_READONLY_INSTANCE_PROPERTY(language, getLanguage);\n      JSG_READONLY_INSTANCE_PROPERTY(languages, getLanguages);\n    }\n\n    if (reader.getWebFileSystem()) {\n      JSG_LAZY_READONLY_INSTANCE_PROPERTY(storage, getStorage);\n    }\n\n    JSG_TS_OVERRIDE({\n      sendBeacon(url: string, body?: BodyInit): boolean;\n    });\n  }\n};\n\n// Exposed as a global to provide access to certain Cloudflare-specific\n// configuration details. This is not a standard API and great care should\n// be taken when deciding to expose new properties or methods here.\nclass Cloudflare: public jsg::Object {\n public:\n  // Return an object containing the state of all compatibility flags known to the runtime.\n  jsg::JsObject getCompatibilityFlags(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(Cloudflare) {\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(compatibilityFlags, getCompatibilityFlags);\n\n    JSG_TS_OVERRIDE({ readonly compatibilityFlags: Record<string, boolean>;\n    });\n  }\n};\n\nclass WorkerGlobalScope: public EventTarget, public jsg::ContextGlobal {\n public:\n  jsg::Unimplemented importScripts(kj::String s) {\n    return {};\n  };\n\n  JSG_RESOURCE_TYPE(WorkerGlobalScope, CompatibilityFlags::Reader flags) {\n    JSG_INHERIT(EventTarget);\n\n    // *** WARNING ***: *Every* new export here must be treated as a potentially\n    // breaking change. It doesn't matter if it's a new method, a new property,\n    // or a new nested type. Adding anything to the global scope risks breaking\n    // existing user code that is feature-sniffing or monkeypatching the global.\n    // *Always* add new exports behind a new compatibility flag! And when in\n    // doubt, don't add the new export on globalThis at all. The only things\n    // that should be exported on globalThis should be standardized web APIs or\n    // Node.js compat mode globals.\n\n    JSG_NESTED_TYPE(EventTarget);\n\n    if (!flags.getNoImportScripts()) {\n      JSG_METHOD(importScripts);\n    }\n\n    JSG_TS_DEFINE(type WorkerGlobalScopeEventMap = {\n      fetch: FetchEvent;\n      scheduled: ScheduledEvent;\n      queue: QueueEvent;\n      unhandledrejection: PromiseRejectionEvent;\n      rejectionhandled: PromiseRejectionEvent;\n    });\n    JSG_TS_OVERRIDE(extends EventTarget<WorkerGlobalScopeEventMap>);\n  }\n\n  // Because EventTarget has a constructor(), we have to explicitly delete\n  // the constructor() here or we'll end up with compilation errors\n  // (EventTarget's constructor confuses the hasConstructorMethod in resource.h)\n  static jsg::Ref<WorkerGlobalScope> constructor() = delete;\n};\n\n// Controller type for test handler.\n//\n// At present, this has no methods. It is defined for consistency with other handlers and on the\n// assumption that we'll probably want to put something here someday.\nclass TestController: public jsg::Object {\n public:\n  JSG_RESOURCE_TYPE(TestController) {}\n};\n\nclass ExecutionContext: public jsg::Object {\n public:\n  ExecutionContext(jsg::Lock& js, jsg::JsValue exports)\n      : ExecutionContext(js, kj::mv(exports), /*props=*/js.obj(), /*versionInfo=*/kj::none) {}\n\n  ExecutionContext(jsg::Lock& js,\n      jsg::JsValue exports,\n      jsg::JsValue props,\n      kj::Maybe<Worker::VersionInfo> versionInfo)\n      : exports(js, exports),\n        props(js, props) {\n    auto featureFlags = FeatureFlags::get(js);\n    if (featureFlags.getEnableVersionApi()) {\n      version = kj::mv(versionInfo).map([&js, &featureFlags](auto v) {\n        return jsg::JsRef{js,\n          v.toJs(js,\n              featureFlags.getEnableCtxVersionMetadata() ? PopulateVersionInfoMetadata::YES\n                                                         : PopulateVersionInfoMetadata::NO)};\n      });\n    }\n  }\n\n  void waitUntil(kj::Promise<void> promise);\n  void passThroughOnException();\n\n  // Cancels the current execution context with the given exception, causing all execution to stop\n  // and throwing an error at the client.\n  void abort(jsg::Lock& js, jsg::Optional<jsg::Value> reason);\n\n  jsg::JsValue getExports(jsg::Lock& js) {\n    return exports.getHandle(js);\n  }\n\n  jsg::JsValue getProps(jsg::Lock& js) {\n    return props.getHandle(js);\n  }\n\n  jsg::JsValue getVersion(jsg::Lock& js) {\n    // TODO(soon): We should be able to assert for `version != kj::none` in the constructor when the\n    //   `enable_version_api` compat flag is enabled, but currently dynamic workers and \"reusable\n    //   `ctx` object instantiation\" do not pass information to populate `ctx.version` with,\n    //   `ctx.version` is currently optional so we can return undefined for now.\n    KJ_IF_SOME(someVersion, version) {\n      return someVersion.getHandle(js);\n    }\n    return js.undefined();\n  }\n\n  JSG_RESOURCE_TYPE(ExecutionContext, CompatibilityFlags::Reader flags) {\n    JSG_METHOD(waitUntil);\n    JSG_METHOD(passThroughOnException);\n    if (flags.getEnableCtxExports()) {\n      JSG_LAZY_INSTANCE_PROPERTY(exports, getExports);\n    }\n    JSG_LAZY_INSTANCE_PROPERTY(props, getProps);\n    if (flags.getEnableVersionApi()) {\n      JSG_LAZY_INSTANCE_PROPERTY(version, getVersion);\n    }\n\n    if (flags.getWorkerdExperimental()) {\n      // TODO(soon): Before making this generally available we need to:\n      // * Consider whether to use TerminateExecution() instead of throwing.\n      // * Make sure it's really not possible for more code to run in the context after abort().\n      //   Currently, abort() triggers in a partially async way so there's an opportunity for some\n      //   other event in the event queue to squeeze in.\n      // * Try to ensure that the provided error is actually the one that propagates out of event\n      //   handlers. Currently this is not consistently true.\n      // * Make sure all event handlers actually honor onAbort().\n      // * Enable the Durable Object version at the same time -- and make sure they're suitably\n      //   consistent with each other.\n      JSG_METHOD(abort);\n    }\n\n    // TODO(soon): This is getting unwieldy.\n    if (flags.getEnableCtxExports()) {\n      if (flags.getEnableVersionApi()) {\n        JSG_TS_OVERRIDE(<Props = unknown> {\n          readonly props: Props;\n          readonly exports: Cloudflare.Exports;\n          readonly version?: {\n            readonly metadata?: { readonly id: string; };\n            readonly cohort?: string;\n            readonly key?: string;\n            readonly override?: string;\n          };\n        });\n      } else {\n        JSG_TS_OVERRIDE(<Props = unknown> {\n          readonly props: Props;\n          readonly exports: Cloudflare.Exports;\n        });\n      }\n    } else {\n      if (flags.getEnableVersionApi()) {\n        JSG_TS_OVERRIDE(<Props = unknown> {\n          readonly props: Props;\n          readonly version?: {\n            readonly metadata?: { readonly id: string; };\n            readonly cohort?: string;\n            readonly key?: string;\n            readonly override?: string;\n          };\n        });\n      } else {\n        JSG_TS_OVERRIDE(<Props = unknown> {\n          readonly props: Props;\n        });\n      }\n    }\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"props\", props);\n    tracker.trackField(\"version\", version);\n  }\n\n private:\n  jsg::JsRef<jsg::JsValue> exports;\n  jsg::JsRef<jsg::JsValue> props;\n  kj::Maybe<jsg::JsRef<jsg::JsValue>> version;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(props);\n    visitor.visit(version);\n  }\n};\n\n// AlarmEventInfo is a jsg::Object used to pass alarm invocation info to an alarm handler.\nclass AlarmInvocationInfo: public jsg::Object {\n public:\n  AlarmInvocationInfo(uint32_t retry): retryCount(retry) {}\n\n  bool getIsRetry() {\n    return retryCount > 0;\n  }\n  uint32_t getRetryCount() {\n    return retryCount;\n  }\n\n  JSG_RESOURCE_TYPE(AlarmInvocationInfo) {\n    JSG_READONLY_INSTANCE_PROPERTY(isRetry, getIsRetry);\n    JSG_READONLY_INSTANCE_PROPERTY(retryCount, getRetryCount);\n  }\n\n private:\n  uint32_t retryCount = 0;\n};\n\n// Type signature for handlers exported from the root module.\n//\n// We define each handler method as a LenientOptional rather than as a plain Optional in order to\n// treat incorrect types as if the field is undefined. Without this, Durable Object class\n// constructors that set a field with one of these names would cause confusing type errors.\nstruct ExportedHandler {\n  using FetchHandler = jsg::Promise<jsg::Ref<api::Response>>(jsg::Ref<api::Request> request,\n      jsg::Value env,\n      jsg::Optional<jsg::Ref<ExecutionContext>> ctx);\n  jsg::LenientOptional<jsg::Function<FetchHandler>> fetch;\n\n  using TailHandler = kj::Promise<void>(kj::Array<jsg::Ref<TraceItem>> events,\n      jsg::Value env,\n      jsg::Optional<jsg::Ref<ExecutionContext>> ctx);\n  jsg::LenientOptional<jsg::Function<TailHandler>> tail;\n  jsg::LenientOptional<jsg::Function<TailHandler>> trace;\n\n  using TailStreamHandler = kj::Promise<void>(\n      jsg::JsObject obj, jsg::Value env, jsg::Optional<jsg::Ref<ExecutionContext>> ctx);\n  jsg::LenientOptional<jsg::Function<TailStreamHandler>> tailStream;\n\n  using ScheduledHandler = kj::Promise<void>(jsg::Ref<ScheduledController> controller,\n      jsg::Value env,\n      jsg::Optional<jsg::Ref<ExecutionContext>> ctx);\n  jsg::LenientOptional<jsg::Function<ScheduledHandler>> scheduled;\n\n  using AlarmHandler = kj::Promise<void>(jsg::Ref<AlarmInvocationInfo> alarmInfo);\n  // Alarms are only exported on DOs, which receive env bindings from the constructor\n  jsg::LenientOptional<jsg::Function<AlarmHandler>> alarm;\n\n  using TestHandler = jsg::Promise<void>(jsg::Ref<TestController> controller,\n      jsg::Value env,\n      jsg::Optional<jsg::Ref<ExecutionContext>> ctx);\n  jsg::LenientOptional<jsg::Function<TestHandler>> test;\n\n  using HibernatableWebSocketMessageHandler = kj::Promise<void>(\n      jsg::Ref<WebSocket>, kj::OneOf<kj::String, kj::Array<byte>> message);\n  jsg::LenientOptional<jsg::Function<HibernatableWebSocketMessageHandler>> webSocketMessage;\n\n  using HibernatableWebSocketCloseHandler = kj::Promise<void>(\n      jsg::Ref<WebSocket>, int code, kj::String reason, bool wasClean);\n  jsg::LenientOptional<jsg::Function<HibernatableWebSocketCloseHandler>> webSocketClose;\n\n  using HibernatableWebSocketErrorHandler = kj::Promise<void>(jsg::Ref<WebSocket>, jsg::Value);\n  jsg::LenientOptional<jsg::Function<HibernatableWebSocketErrorHandler>> webSocketError;\n\n  // Self-ref potentially allows extracting other custom handlers from the object.\n  jsg::SelfRef self;\n\n  JSG_STRUCT(fetch,\n      tail,\n      trace,\n      tailStream,\n      scheduled,\n      alarm,\n      test,\n      webSocketMessage,\n      webSocketClose,\n      webSocketError,\n      self);\n\n  JSG_STRUCT_TS_ROOT();\n  // ExportedHandler isn't included in the global scope, but we still want to\n  // include it in type definitions.\n\n  JSG_STRUCT_TS_DEFINE(\n    type ExportedHandlerFetchHandler<Env = unknown, CfHostMetadata = unknown, Props = unknown> = (request: Request<CfHostMetadata, IncomingRequestCfProperties<CfHostMetadata>>, env: Env, ctx: ExecutionContext<Props>) => Response | Promise<Response>;\n    type ExportedHandlerTailHandler<Env = unknown, Props = unknown> = (events: TraceItem[], env: Env, ctx: ExecutionContext<Props>) => void | Promise<void>;\n    type ExportedHandlerTraceHandler<Env = unknown, Props = unknown> = (traces: TraceItem[], env: Env, ctx: ExecutionContext<Props>) => void | Promise<void>;\n    type ExportedHandlerTailStreamHandler<Env = unknown, Props = unknown> = (event : TailStream.TailEvent<TailStream.Onset>, env: Env, ctx: ExecutionContext<Props>) => TailStream.TailEventHandlerType | Promise<TailStream.TailEventHandlerType>;\n    type ExportedHandlerScheduledHandler<Env = unknown, Props = unknown> = (controller: ScheduledController, env: Env, ctx: ExecutionContext<Props>) => void | Promise<void>;\n    type ExportedHandlerQueueHandler<Env = unknown, Message = unknown, Props = unknown> = (batch: MessageBatch<Message>, env: Env, ctx: ExecutionContext<Props>) => void | Promise<void>;\n    type ExportedHandlerTestHandler<Env = unknown, Props = unknown> = (controller: TestController, env: Env, ctx: ExecutionContext<Props>) => void | Promise<void>;\n  );\n  JSG_STRUCT_TS_OVERRIDE(<Env = unknown, QueueHandlerMessage = unknown, CfHostMetadata = unknown, Props = unknown> {\n    email?: EmailExportedHandler<Env, Props>;\n    fetch?: ExportedHandlerFetchHandler<Env, CfHostMetadata, Props>;\n    tail?: ExportedHandlerTailHandler<Env, Props>;\n    trace?: ExportedHandlerTraceHandler<Env, Props>;\n    tailStream?: ExportedHandlerTailStreamHandler<Env, Props>;\n    scheduled?: ExportedHandlerScheduledHandler<Env, Props>;\n    alarm: never;\n    webSocketMessage: never;\n    webSocketClose: never;\n    webSocketError: never;\n    queue?: ExportedHandlerQueueHandler<Env, QueueHandlerMessage, Props>;\n    test?: ExportedHandlerTestHandler<Env, Props>;\n  });\n  // Make `env` parameter generic\n\n  // Values to pass for `env` and `ctx` when calling handlers. Note these have to be the last members\n  // so that they don't interfere with `JSG_STRUCT`'s machinations.\n\n  // env and ctx values that need to be passed to the handler function. If the ExportedHandler\n  // represents a class instance (e.g. Durable Object instance), then `env` is is the JS value\n  // `undefined` and `ctx` is `kj::none`.\n  // TODO(cleanup): Why isn't `env` a `jsg::Optional` too? Or maybe the pair should be wrapped in\n  //   a struct that is `Maybe`?\n  jsg::Value env = nullptr;\n  jsg::Optional<jsg::Ref<ExecutionContext>> ctx = kj::none;\n  // TODO(cleanup): These are shoved here as a bit of a hack. At present, this is convenient and\n  //   works for all use cases. If we have bindings or things on ctx that vary on a per-request basis,\n  //   this won't work as well, I guess, but we can cross that bridge when we come to it.\n\n  // If true, this is a Durable Object class that failed to extend `DurableObject`. We will not\n  // permit RPC to this class.\n  bool missingSuperclass = false;\n\n  jsg::Optional<jsg::Ref<ExecutionContext>> getCtx() {\n    return ctx.map([&](jsg::Ref<ExecutionContext>& p) { return p.addRef(); });\n  }\n\n  ExportedHandler clone(jsg::Lock& js);\n};\n\n// An approximation of Node.js setImmediate `Immediate` object.\n// This is used only when the `nodejs_compat_v2` compatibility flag is enabled.\nclass Immediate final: public jsg::Object {\n public:\n  Immediate(IoContext& context, TimeoutId timeoutId);\n\n  // In Node.js, the \"ref\" mechanism refers to whether or not an i/o object\n  // will keep the libuv event loop alive (and therefore keep the process alive).\n  // We do not implement a similar mechanism in workerd. These are here only to\n  // satisfy the API contract for the `Immediate` object but are never expected\n  // to actually do anything.\n  bool hasRef() {\n    return false;\n  }\n  void ref() { /* non-op */ }\n  void unref() { /* non-op */ }\n\n  void dispose();\n\n  JSG_RESOURCE_TYPE(Immediate) {\n    JSG_METHOD(ref);\n    JSG_METHOD(unref);\n    JSG_METHOD(hasRef);\n    JSG_DISPOSE(dispose);\n  }\n\n private:\n  // Note: We cannot use IoContext::WeakRef here because it's not thread-safe (it's only intended\n  // to be held from KJ I/O objects, but this is a JSG object which can be accessed by V8's GC\n  // on different threads). Instead, we use IoPtr<IoContext> which is safe to hold from JSG objects.\n  IoPtr<IoContext> ioContext;\n  TimeoutId timeoutId;\n};\n\n// Global object API exposed to JavaScript.\nclass ServiceWorkerGlobalScope: public WorkerGlobalScope {\n public:\n  ServiceWorkerGlobalScope();\n\n  // Drop all references to JavaScript objects so that the context can be garbage-collected. Call\n  // this when the context will never be used again and should be disposed.\n  void clear();\n  // TODO(someday): We should instead implement V8's GC visitor interface so that we don't have\n  //   to hold persistent references.\n\n  // Received request (called from C++, not JS).\n  //\n  // If `exportedHandler` is provided, the request will be delivered to it rather than to event\n  // listeners.\n  kj::Promise<DeferredProxy<void>> request(kj::HttpMethod method,\n      kj::StringPtr url,\n      const kj::HttpHeaders& headers,\n      kj::AsyncInputStream& requestBody,\n      kj::HttpService::Response& response,\n      kj::Maybe<kj::StringPtr> cfBlobJson,\n      Worker::Lock& lock,\n      kj::Maybe<ExportedHandler&> exportedHandler,\n      kj::Maybe<jsg::Ref<AbortSignal>> abortSignal);\n  // TODO(cleanup): Factor out the shared code used between old-style event listeners vs. module\n  //   exports and move that code somewhere more appropriate.\n\n  // Received sendTraces (called from C++, not JS).\n  void sendTraces(kj::ArrayPtr<kj::Own<Trace>> traces,\n      Worker::Lock& lock,\n      kj::Maybe<ExportedHandler&> exportedHandler);\n\n  // Start a scheduled event (called from C++, not JS). It is the caller's responsibility to wait\n  // for waitUntil()s in order to construct the final ScheduledResult.\n  void startScheduled(kj::Date scheduledTime,\n      kj::StringPtr cron,\n      Worker::Lock& lock,\n      kj::Maybe<ExportedHandler&> exportedHandler);\n\n  // Received runAlarm (called from C++, not JS).\n  kj::Promise<WorkerInterface::AlarmResult> runAlarm(kj::Date scheduledTime,\n      kj::Duration timeout,\n      uint32_t retryCount,\n      Worker::Lock& lock,\n      kj::Maybe<ExportedHandler&> exportedHandler);\n\n  // Received test() (called from C++, not JS). See WorkerInterface::test(). This version returns\n  // a jsg::Promise<void>; it fails if an exception is thrown. WorkerEntrypoint will catch these\n  // and report them.\n  jsg::Promise<void> test(Worker::Lock& lock, kj::Maybe<ExportedHandler&> exportedHandler);\n\n  kj::Promise<void> eventTimeoutPromise(uint32_t timeoutMs);\n  kj::Promise<void> setHibernatableEventTimeout(\n      kj::Promise<void> event, kj::Maybe<uint32_t> eventTimeoutMs);\n\n  void sendHibernatableWebSocketMessage(IoContext& context,\n      kj::OneOf<kj::String, kj::Array<byte>> message,\n      kj::Maybe<uint32_t> eventTimeoutMs,\n      kj::String websocketId,\n      Worker::Lock& lock,\n      kj::Maybe<ExportedHandler&> exportedHandler);\n\n  void sendHibernatableWebSocketClose(IoContext& context,\n      HibernatableSocketParams::Close close,\n      kj::Maybe<uint32_t> eventTimeoutMs,\n      kj::String websocketId,\n      Worker::Lock& lock,\n      kj::Maybe<ExportedHandler&> exportedHandler);\n\n  void sendHibernatableWebSocketError(IoContext& context,\n      kj::Exception e,\n      kj::Maybe<uint32_t> eventTimeoutMs,\n      kj::String websocketId,\n      Worker::Lock& lock,\n      kj::Maybe<ExportedHandler&> exportedHandler);\n\n  void emitPromiseRejection(jsg::Lock& js,\n      v8::PromiseRejectEvent event,\n      jsg::V8Ref<v8::Promise> promise,\n      jsg::Value value);\n\n  // Track a set of address->callback overrides for which the connect(address) behavior should be\n  // overridden via callbacks rather than using the default Socket connect() logic.\n  // This is useful for allowing generic client libraries to connect to private local services using\n  // just a provided address (rather than requiring them to support being passed a binding to call\n  // binding.connect() on).\n  using ConnectFn = kj::Function<jsg::Ref<api::Socket>(jsg::Lock&)>;\n  void setConnectOverride(kj::String networkAddress, ConnectFn connectFn);\n  kj::Maybe<ConnectFn&> getConnectOverride(kj::StringPtr networkAddress);\n\n  // ---------------------------------------------------------------------------\n  // JS API\n\n  jsg::JsString btoa(jsg::Lock& js, jsg::JsString data);\n  jsg::JsString atob(jsg::Lock& js, kj::String data);\n\n  void queueMicrotask(jsg::Lock& js, jsg::Function<void()> task);\n\n#ifdef WORKERD_FUZZILLI\n  void fuzzilli(jsg::Lock& js, jsg::Arguments<jsg::Value> args);\n#endif\n\n  struct StructuredCloneOptions {\n    jsg::Optional<kj::Array<jsg::JsRef<jsg::JsValue>>> transfer;\n    JSG_STRUCT(transfer);\n    JSG_STRUCT_TS_OVERRIDE(StructuredSerializeOptions);\n  };\n\n  jsg::JsValue structuredClone(\n      jsg::Lock& js, jsg::JsValue value, jsg::Optional<StructuredCloneOptions> options);\n\n  TimeoutId::NumberType setTimeout(jsg::Lock& js,\n      jsg::Function<void(jsg::Arguments<jsg::Value>)> function,\n      jsg::Optional<double> msDelay,\n      jsg::Arguments<jsg::Value> args);\n  void clearTimeout(jsg::Lock& js, kj::Maybe<jsg::JsNumber> timeoutId);\n\n  TimeoutId::NumberType setTimeoutInternal(jsg::Function<void()> function, double msDelay);\n\n  TimeoutId::NumberType setInterval(jsg::Lock& js,\n      jsg::Function<void(jsg::Arguments<jsg::Value>)> function,\n      jsg::Optional<double> msDelay,\n      jsg::Arguments<jsg::Value> args);\n  void clearInterval(jsg::Lock& js, kj::Maybe<jsg::JsNumber> timeoutId);\n\n  jsg::Promise<jsg::Ref<Response>> fetch(jsg::Lock& js,\n      kj::OneOf<jsg::Ref<Request>, kj::String> request,\n      jsg::Optional<Request::Initializer> requestInitr);\n\n  jsg::Ref<ServiceWorkerGlobalScope> getSelf() {\n    return JSG_THIS;\n  }\n\n  // Implemented in global-scope.c++ to avoid including crypto.h\n  jsg::Ref<Crypto> getCrypto(jsg::Lock& js);\n\n  jsg::Ref<Scheduler> getScheduler(jsg::Lock& js) {\n    return js.alloc<Scheduler>();\n  }\n\n  jsg::Ref<Navigator> getNavigator(jsg::Lock& js) {\n    return js.alloc<Navigator>();\n  }\n\n  jsg::Ref<Performance> getPerformance(jsg::Lock& js) {\n    return js.alloc<Performance>(Worker::Isolate::from(js).getLimitEnforcer());\n  }\n\n  jsg::Ref<Cloudflare> getCloudflare(jsg::Lock& js) {\n    return js.alloc<Cloudflare>();\n  }\n\n  // The origin is unknown, return \"null\" as described in\n  // https://html.spec.whatwg.org/multipage/browsers.html#concept-origin-opaque.\n  kj::StringPtr getOrigin() {\n    return \"null\";\n  }\n\n  jsg::Ref<CacheStorage> getCaches(jsg::Lock& js);\n\n  void reportError(jsg::Lock& js, jsg::JsValue error);\n\n  // When the nodejs_compat_v2 compatibility flag is enabled, we expose the Node.js\n  // compat Buffer and process at the global scope in all modules as lazy instance\n  // properties.\n  jsg::JsValue getBuffer(jsg::Lock& js);\n  void setBuffer(jsg::Lock& js, jsg::JsValue newBuffer);\n  jsg::JsValue getProcess(jsg::Lock& js);\n  void setProcess(jsg::Lock& js, jsg::JsValue newProcess);\n  jsg::Ref<Immediate> setImmediate(jsg::Lock& js,\n      jsg::Function<void(jsg::Arguments<jsg::Value>)> function,\n      jsg::Arguments<jsg::Value> args);\n  void clearImmediate(kj::Maybe<jsg::Ref<Immediate>> immediate);\n\n  JSG_RESOURCE_TYPE(ServiceWorkerGlobalScope, CompatibilityFlags::Reader flags) {\n    JSG_INHERIT(WorkerGlobalScope);\n\n    // *** WARNING ***: *Every* new export here must be treated as a potentially\n    // breaking change. It doesn't matter if it's a new method, a new property,\n    // or a new nested type. Adding anything to the global scope risks breaking\n    // existing user code that is feature-sniffing or monkeypatching the global.\n    // *Always* add new exports behind a new compatibility flag! And when in\n    // doubt, don't add the new export on globalThis at all. The only things\n    // that should be exported on globalThis should be standardized web APIs or\n    // Node.js compat mode globals.\n\n    JSG_NESTED_TYPE(DOMException);\n    JSG_NESTED_TYPE(WorkerGlobalScope);\n    if (flags.getSpecCompliantPropertyAttributes()) {\n      // EventTarget is also declared on WorkerGlobalScope, but V8's\n      // FunctionTemplate::Inherit() does not propagate instance-template\n      // properties.  Redeclare here so it becomes an own property of globalThis.\n      JSG_NESTED_TYPE(EventTarget);\n    }\n\n    JSG_METHOD(btoa);\n    JSG_METHOD(atob);\n\n    JSG_METHOD(setTimeout);\n    JSG_METHOD(clearTimeout);\n    JSG_METHOD(setInterval);\n    JSG_METHOD(clearInterval);\n    JSG_METHOD(queueMicrotask);\n    JSG_METHOD(structuredClone);\n    JSG_METHOD(reportError);\n\n    JSG_METHOD(fetch);\n\n    // Unlike regular interface attributes, which Web IDL requires us to\n    // implement as prototype properties, the global scope is special --\n    // interface attributes defined on the global scope must be implemented as\n    // instance properties. As an additional wrinkle, many of these properties\n    // are supposed to be readonly, but in practice most browsers do not fully\n    // honor that part of the spec, and allow user scripts to override many of\n    // the properties.\n    //\n    // Using JSG_LAZY_INSTANCE_PROPERTY here to expose new global properties\n    // ensures that any new global property we expose can be monkeypatched by\n    // user code without us having to handle any of the storage. The\n    // first time the properties are accessed, the getter will be invoked\n    // if the user has not already set the value for the property themselves.\n    // This should be the default choice for all new global properties that\n    // are not methods or nested types.\n    //\n    // We make an exception for origin, and define it as a readonly instance\n    // property, because we currently do not provide any implementation for it.\n\n    JSG_LAZY_INSTANCE_PROPERTY(self, getSelf);\n    JSG_LAZY_INSTANCE_PROPERTY(crypto, getCrypto);\n    JSG_LAZY_INSTANCE_PROPERTY(caches, getCaches);\n    JSG_LAZY_INSTANCE_PROPERTY(scheduler, getScheduler);\n    JSG_LAZY_INSTANCE_PROPERTY(performance, getPerformance);\n    JSG_LAZY_INSTANCE_PROPERTY(Cloudflare, getCloudflare);\n    JSG_READONLY_INSTANCE_PROPERTY(origin, getOrigin);\n\n#ifdef WORKERD_FUZZILLI\n    if (flags.getWorkerdExperimental()) {\n      JSG_METHOD(fuzzilli);\n    }\n#endif\n\n    JSG_NESTED_TYPE(Event);\n    JSG_NESTED_TYPE(ExtendableEvent);\n    JSG_NESTED_TYPE(CustomEvent);\n    JSG_NESTED_TYPE(PromiseRejectionEvent);\n    JSG_NESTED_TYPE(FetchEvent);\n    JSG_NESTED_TYPE(TailEvent);\n    JSG_NESTED_TYPE_NAMED(TailEvent, TraceEvent);\n    JSG_NESTED_TYPE(ScheduledEvent);\n    JSG_NESTED_TYPE(MessageEvent);\n    JSG_NESTED_TYPE(CloseEvent);\n    JSG_NESTED_TYPE(ReadableStreamDefaultReader);\n    JSG_NESTED_TYPE(ReadableStreamBYOBReader);\n    JSG_NESTED_TYPE(ReadableStream);\n    JSG_NESTED_TYPE(WritableStream);\n    JSG_NESTED_TYPE(WritableStreamDefaultWriter);\n    JSG_NESTED_TYPE(TransformStream);\n    JSG_NESTED_TYPE(ByteLengthQueuingStrategy);\n    JSG_NESTED_TYPE(CountQueuingStrategy);\n    JSG_NESTED_TYPE(ErrorEvent);\n\n    if (flags.getExposeGlobalMessageChannel()) {\n      JSG_NESTED_TYPE(MessageChannel);\n      JSG_NESTED_TYPE(MessagePort);\n    }\n\n    if (flags.getWebFileSystem()) {\n      JSG_NESTED_TYPE(FileSystemHandle);\n      JSG_NESTED_TYPE(FileSystemFileHandle);\n      JSG_NESTED_TYPE(FileSystemDirectoryHandle);\n      JSG_NESTED_TYPE(FileSystemWritableFileStream);\n      JSG_NESTED_TYPE(StorageManager);\n    }\n\n    JSG_NESTED_TYPE(EventSource);\n\n    if (flags.getStreamsJavaScriptControllers()) {\n      JSG_NESTED_TYPE(ReadableStreamBYOBRequest);\n      JSG_NESTED_TYPE(ReadableStreamDefaultController);\n      JSG_NESTED_TYPE(ReadableByteStreamController);\n      JSG_NESTED_TYPE(WritableStreamDefaultController);\n      JSG_NESTED_TYPE(TransformStreamDefaultController);\n    }\n\n    if (flags.getNodeJsCompatV2()) {\n      JSG_INSTANCE_PROPERTY(Buffer, getBuffer, setBuffer);\n      JSG_INSTANCE_PROPERTY(process, getProcess, setProcess);\n      JSG_LAZY_INSTANCE_PROPERTY(global, getSelf);\n      JSG_METHOD(setImmediate);\n      JSG_METHOD(clearImmediate);\n    }\n\n    JSG_NESTED_TYPE(CompressionStream);\n    JSG_NESTED_TYPE(DecompressionStream);\n    JSG_NESTED_TYPE(TextEncoderStream);\n    JSG_NESTED_TYPE(TextDecoderStream);\n\n    JSG_NESTED_TYPE(Headers);\n    JSG_NESTED_TYPE(Body);\n    JSG_NESTED_TYPE(Request);\n    JSG_NESTED_TYPE(Response);\n    JSG_NESTED_TYPE(WebSocket);\n    JSG_NESTED_TYPE(WebSocketPair);\n    JSG_NESTED_TYPE(WebSocketRequestResponsePair);\n\n    JSG_NESTED_TYPE(AbortController);\n    JSG_NESTED_TYPE(AbortSignal);\n\n    JSG_NESTED_TYPE(TextDecoder);\n    JSG_NESTED_TYPE(TextEncoder);\n\n    if (flags.getGlobalNavigator()) {\n      JSG_LAZY_INSTANCE_PROPERTY(navigator, getNavigator);\n      JSG_NESTED_TYPE(Navigator);\n    }\n\n    if (flags.getSpecCompliantUrl()) {\n      JSG_NESTED_TYPE_NAMED(url::URL, URL);\n      JSG_NESTED_TYPE_NAMED(url::URLSearchParams, URLSearchParams);\n    } else {\n      JSG_NESTED_TYPE(URL);\n      JSG_NESTED_TYPE(URLSearchParams);\n    }\n\n    if (flags.getSpecCompliantUrlpattern()) {\n      JSG_NESTED_TYPE_NAMED(urlpattern::URLPattern, URLPattern);\n    } else {\n      JSG_NESTED_TYPE(URLPattern);\n    }\n\n    JSG_NESTED_TYPE(Blob);\n    JSG_NESTED_TYPE(File);\n    JSG_NESTED_TYPE(FormData);\n\n    JSG_NESTED_TYPE(Crypto);\n    JSG_NESTED_TYPE(SubtleCrypto);\n    JSG_NESTED_TYPE(CryptoKey);\n\n    JSG_NESTED_TYPE(CacheStorage);\n    JSG_NESTED_TYPE(Cache);\n\n    // Off-spec extensions.\n    JSG_NESTED_TYPE(FixedLengthStream);\n    JSG_NESTED_TYPE(IdentityTransformStream);\n    JSG_NESTED_TYPE(HTMLRewriter);\n\n    // Performance API\n    if (flags.getEnableGlobalPerformanceClasses() || flags.getEnableNodeJsPerfHooksModule()) {\n      JSG_NESTED_TYPE(Performance);\n      JSG_NESTED_TYPE(PerformanceEntry);\n      JSG_NESTED_TYPE(PerformanceMark);\n      JSG_NESTED_TYPE(PerformanceMeasure);\n      JSG_NESTED_TYPE(PerformanceResourceTiming);\n      JSG_NESTED_TYPE(PerformanceObserver);\n      JSG_NESTED_TYPE(PerformanceObserverEntryList);\n    }\n\n    JSG_TS_ROOT();\n    JSG_TS_DEFINE(\n      interface Console {\n        \"assert\"(condition?: boolean, ...data: any[]): void;\n        clear(): void;\n        count(label?: string): void;\n        countReset(label?: string): void;\n        debug(...data: any[]): void;\n        dir(item?: any, options?: any): void;\n        dirxml(...data: any[]): void;\n        error(...data: any[]): void;\n        group(...data: any[]): void;\n        groupCollapsed(...data: any[]): void;\n        groupEnd(): void;\n        info(...data: any[]): void;\n        log(...data: any[]): void;\n        table(tabularData?: any, properties?: string[]): void;\n        time(label?: string): void;\n        timeEnd(label?: string): void;\n        timeLog(label?: string, ...data: any[]): void;\n        timeStamp(label?: string): void;\n        trace(...data: any[]): void;\n        warn(...data: any[]): void;\n      }\n      const console: Console;\n\n      type BufferSource = ArrayBufferView | ArrayBuffer;\n      type TypedArray =\n        | Int8Array\n        | Uint8Array\n        | Uint8ClampedArray\n        | Int16Array\n        | Uint16Array\n        | Int32Array\n        | Uint32Array\n        | Float32Array\n        | Float64Array\n        | BigInt64Array\n        | BigUint64Array;\n\n      namespace WebAssembly {\n        class CompileError extends Error {\n          constructor(message?: string);\n        }\n        class RuntimeError extends Error {\n          constructor(message?: string);\n        }\n\n        type ValueType = \"anyfunc\" | \"externref\" | \"f32\" | \"f64\" | \"i32\" | \"i64\" | \"v128\";\n        interface GlobalDescriptor {\n          value: ValueType;\n          mutable?: boolean;\n        }\n        class Global {\n          constructor(descriptor: GlobalDescriptor, value?: any);\n          value: any;\n          valueOf(): any;\n        }\n\n        type ImportValue = ExportValue | number;\n        type ModuleImports = Record<string, ImportValue>;\n        type Imports = Record<string, ModuleImports>;\n        type ExportValue = Function | Global | Memory | Table;\n        type Exports = Record<string, ExportValue>;\n        class Instance {\n          constructor(module: Module, imports?: Imports);\n          readonly exports: Exports;\n        }\n\n        interface MemoryDescriptor {\n          initial: number;\n          maximum?: number;\n          shared?: boolean;\n        }\n        class Memory {\n          constructor(descriptor: MemoryDescriptor);\n          readonly buffer: ArrayBuffer;\n          grow(delta: number): number;\n        }\n\n        type ImportExportKind = \"function\" | \"global\" | \"memory\" | \"table\";\n        interface ModuleExportDescriptor {\n          kind: ImportExportKind;\n          name: string;\n        }\n        interface ModuleImportDescriptor {\n          kind: ImportExportKind;\n          module: string;\n          name: string;\n        }\n        abstract class Module {\n          static customSections(module: Module, sectionName: string): ArrayBuffer[];\n          static exports(module: Module): ModuleExportDescriptor[];\n          static imports(module: Module): ModuleImportDescriptor[];\n        }\n\n        type TableKind = \"anyfunc\" | \"externref\";\n        interface TableDescriptor {\n          element: TableKind;\n          initial: number;\n          maximum?: number;\n        }\n        class Table {\n          constructor(descriptor: TableDescriptor, value?: any);\n          readonly length: number;\n          get(index: number): any;\n          grow(delta: number, value?: any): number;\n          set(index: number, value?: any): void;\n        }\n\n        function instantiate(module: Module, imports?: Imports): Promise<Instance>;\n        function validate(bytes: BufferSource): boolean;\n      }\n    );\n    // workerd disables dynamic WebAssembly compilation, so `compile()`, `compileStreaming()`, the\n    // `instantiate()` override taking a `BufferSource` and `instantiateStreaming()` are omitted.\n    // `Module` is also declared `abstract` to disable its `BufferSource` constructor.\n\n    JSG_TS_OVERRIDE({\n      setTimeout(callback: (...args: any[]) => void, msDelay?: number): number;\n      setTimeout<Args extends any[]>(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number;\n\n      setInterval(callback: (...args: any[]) => void, msDelay?: number): number;\n      setInterval<Args extends any[]>(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number;\n\n      structuredClone<T>(value: T, options?: StructuredSerializeOptions): T;\n      queueMicrotask(task: Function): void;\n\n      fetch(input: RequestInfo | URL, init?: RequestInit<RequestInitCfProperties>): Promise<Response>;\n    });\n  }\n\n  TimeoutId::Generator timeoutIdGenerator;\n  // The generator for all timeout IDs associated with this scope.\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"unhandledRejections\", unhandledRejections);\n  }\n\n private:\n  jsg::UnhandledRejectionHandler unhandledRejections;\n  kj::Maybe<jsg::JsRef<jsg::JsValue>> processValue;\n  kj::Maybe<jsg::JsRef<jsg::JsValue>> bufferValue;\n  kj::Maybe<jsg::Ref<Fetcher>> defaultFetcher;\n  kj::HashMap<kj::String, ConnectFn> connectOverrides;\n\n  // Global properties such as scheduler, crypto, caches, self, and origin should\n  // be monkeypatchable / mutable at the global scope.\n};\n\n#define EW_GLOBAL_SCOPE_ISOLATE_TYPES                                                              \\\n  api::WorkerGlobalScope, api::ServiceWorkerGlobalScope, api::TestController,                      \\\n      api::ExecutionContext, api::ExportedHandler,                                                 \\\n      api::ServiceWorkerGlobalScope::StructuredCloneOptions, api::Navigator,                       \\\n      api::AlarmInvocationInfo, api::Immediate, api::Cloudflare\n// The list of global-scope.h types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/headers.c++",
    "content": "#include \"headers.h\"\n\n#include \"simdutf.h\"\n\n#include <workerd/io/features.h>\n#include <workerd/io/io-context.h>\n#include <workerd/util/header-validation.h>\n#include <workerd/util/strings.h>\n\nnamespace workerd::api {\n\nnamespace {\n\n#define COMMON_HEADERS(V)                                                                          \\\n  V(ACCEPT_CHARSET, \"accept-charset\")                                                              \\\n  V(ACCEPT_ENCODING, \"accept-encoding\")                                                            \\\n  V(ACCEPT_LANGUAGE, \"accept-language\")                                                            \\\n  V(ACCEPT_RANGES, \"accept-ranges\")                                                                \\\n  V(ACCEPT, \"accept\")                                                                              \\\n  V(ACCESS_CONTROL_ALLOW_ORIGIN, \"access-control-allow-origin\")                                    \\\n  V(AGE, \"age\")                                                                                    \\\n  V(ALLOW, \"allow\")                                                                                \\\n  V(AUTHORIZATION, \"authorization\")                                                                \\\n  V(CACHE_CONTROL, \"cache-control\")                                                                \\\n  V(CONTENT_DISPOSITION, \"content-disposition\")                                                    \\\n  V(CONTENT_ENCODING, \"content-encoding\")                                                          \\\n  V(CONTENT_LANGUAGE, \"content-language\")                                                          \\\n  V(CONTENT_LENGTH, \"content-length\")                                                              \\\n  V(CONTENT_LOCATION, \"content-location\")                                                          \\\n  V(CONTENT_RANGE, \"content-range\")                                                                \\\n  V(CONTENT_TYPE, \"content-type\")                                                                  \\\n  V(COOKIE, \"cookie\")                                                                              \\\n  V(DATE, \"date\")                                                                                  \\\n  V(ETAG, \"etag\")                                                                                  \\\n  V(EXPECT, \"expect\")                                                                              \\\n  V(EXPIRES, \"expires\")                                                                            \\\n  V(FROM, \"from\")                                                                                  \\\n  V(HOST, \"host\")                                                                                  \\\n  V(IF_MATCH, \"if-match\")                                                                          \\\n  V(IF_MODIFIED_SINCE, \"if-modified-since\")                                                        \\\n  V(IF_NONE_MATCH, \"if-none-match\")                                                                \\\n  V(IF_RANGE, \"if-range\")                                                                          \\\n  V(IF_UNMODIFIED_SINCE, \"if-unmodified-since\")                                                    \\\n  V(LAST_MODIFIED, \"last-modified\")                                                                \\\n  V(LINK, \"link\")                                                                                  \\\n  V(LOCATION, \"location\")                                                                          \\\n  V(MAX_FORWARDS, \"max-forwards\")                                                                  \\\n  V(PROXY_AUTHENTICATE, \"proxy-authenticate\")                                                      \\\n  V(PROXY_AUTHORIZATION, \"proxy-authorization\")                                                    \\\n  V(RANGE, \"range\")                                                                                \\\n  V(REFERER, \"referer\")                                                                            \\\n  V(REFRESH, \"refresh\")                                                                            \\\n  V(RETRY_AFTER, \"retry-after\")                                                                    \\\n  V(SERVER, \"server\")                                                                              \\\n  V(SET_COOKIE, \"set-cookie\")                                                                      \\\n  V(STRICT_TRANSPORT_SECURITY, \"strict-transport-security\")                                        \\\n  V(TRANSFER_ENCODING, \"transfer-encoding\")                                                        \\\n  V(USER_AGENT, \"user-agent\")                                                                      \\\n  V(VARY, \"vary\")                                                                                  \\\n  V(VIA, \"via\")                                                                                    \\\n  V(WWW_AUTHENTICATE, \"www-authenticate\")\n\n// Constexpr array of lowercase common header names (must match CommonHeaderName enum order\n// and must be kept in sync with the ordinal values defined in http-over-capnp.capnp). Since\n// it is extremely unlikely that those will change often, we hardcode them here for runtime\n// efficiency.\nconstexpr kj::StringPtr COMMON_HEADER_NAMES[] = {nullptr,  // 0: invalid\n#define V(_, str) str##_kj,\n  COMMON_HEADERS(V)\n#undef V\n};\n\n// Static asserts for all of the common header names to ensure they are correct and in sync\n// with the enum values.\nstatic_assert(std::size(COMMON_HEADER_NAMES) == (Headers::MAX_COMMON_HEADER_ID + 1),\n    \"COMMON_HEADER_NAMES must have an entry for each CommonHeaderName enum value\");\n#define V(id, str)                                                                                 \\\n  static_assert(COMMON_HEADER_NAMES[static_cast<uint>(capnp::CommonHeaderName::id)] == str##_kj);\nCOMMON_HEADERS(V)\n#undef V\n\ninline constexpr kj::StringPtr getCommonHeaderName(uint id) {\n  KJ_ASSERT(id > 0 && id <= Headers::MAX_COMMON_HEADER_ID, \"Invalid common header ID\");\n  return COMMON_HEADER_NAMES[id];\n}\n\n// The CASE_CONVERSION_TABLE maps ASCII characters to their case conversion offset. For example,\n// 'A' (0x41) maps to 32, which is the offset to convert it to 'a' (0x61). This allows us to perform\n// branch-free case-insensitive comparisons and hashing by adding the offset to each character.\n// For non-ASCII characters, the offset is 0, which means they are not modified. This table is\n// used in the strcaseeq and caseInsensitiveHash functions below.\n// clang-format off\nconstexpr kj::byte CASE_CONVERSION_TABLE[] = {\n   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,\n   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,\n   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,\n   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,\n   0, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,\n  32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,  0,  0,  0,  0,  0,\n   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,\n   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,\n   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,\n   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,\n   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,\n   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,\n   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,\n   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,\n   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,\n   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,\n};\n// clang-format on\n\n// Bit paranoid but just to be safe...\nstatic_assert(std::numeric_limits<kj::byte>::max() < kj::size(CASE_CONVERSION_TABLE));\n\nconstexpr bool strcaseeq(kj::StringPtr a, kj::StringPtr b) {\n  if (a.size() != b.size()) return false;\n\n  size_t i = 0;\n  while (i + sizeof(uint64_t) <= a.size()) {\n    // We assume that a is always lower-case to start.\n    uint64_t wa = (static_cast<uint64_t>(static_cast<uint8_t>(a[i + 0])) << 0) |\n        (static_cast<uint64_t>(static_cast<uint8_t>(a[i + 1])) << 8) |\n        (static_cast<uint64_t>(static_cast<uint8_t>(a[i + 2])) << 16) |\n        (static_cast<uint64_t>(static_cast<uint8_t>(a[i + 3])) << 24) |\n        (static_cast<uint64_t>(static_cast<uint8_t>(a[i + 4])) << 32) |\n        (static_cast<uint64_t>(static_cast<uint8_t>(a[i + 5])) << 40) |\n        (static_cast<uint64_t>(static_cast<uint8_t>(a[i + 6])) << 48) |\n        (static_cast<uint64_t>(static_cast<uint8_t>(a[i + 7])) << 56);\n\n    uint8_t b0 = b[i + 0];\n    uint8_t b1 = b[i + 1];\n    uint8_t b2 = b[i + 2];\n    uint8_t b3 = b[i + 3];\n    uint8_t b4 = b[i + 4];\n    uint8_t b5 = b[i + 5];\n    uint8_t b6 = b[i + 6];\n    uint8_t b7 = b[i + 7];\n\n    uint64_t normalized_b = (static_cast<uint64_t>(b0 + CASE_CONVERSION_TABLE[b0]) << 0) |\n        (static_cast<uint64_t>(b1 + CASE_CONVERSION_TABLE[b1]) << 8) |\n        (static_cast<uint64_t>(b2 + CASE_CONVERSION_TABLE[b2]) << 16) |\n        (static_cast<uint64_t>(b3 + CASE_CONVERSION_TABLE[b3]) << 24) |\n        (static_cast<uint64_t>(b4 + CASE_CONVERSION_TABLE[b4]) << 32) |\n        (static_cast<uint64_t>(b5 + CASE_CONVERSION_TABLE[b5]) << 40) |\n        (static_cast<uint64_t>(b6 + CASE_CONVERSION_TABLE[b6]) << 48) |\n        (static_cast<uint64_t>(b7 + CASE_CONVERSION_TABLE[b7]) << 56);\n\n    if (wa != normalized_b) return false;\n    i += sizeof(uint64_t);\n  }\n\n  for (; i < a.size(); ++i) {\n    char cb = b[i] + CASE_CONVERSION_TABLE[static_cast<kj::byte>(b[i])];\n    if (a[i] != cb) return false;\n  }\n  return true;\n}\n\n// A FNV hash function that is case-insensitive.\n// See: https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function\nconstexpr uint caseInsensitiveHash(kj::StringPtr name) {\n  static constexpr uint32_t MULTIPLIER = 16777619u;\n  uint hash = 2166136261u;\n  size_t i = 0;\n\n  while (i + sizeof(uint64_t) <= name.size()) {\n    uint8_t b0 = name[i + 0];\n    uint8_t b1 = name[i + 1];\n    uint8_t b2 = name[i + 2];\n    uint8_t b3 = name[i + 3];\n    uint8_t b4 = name[i + 4];\n    uint8_t b5 = name[i + 5];\n    uint8_t b6 = name[i + 6];\n    uint8_t b7 = name[i + 7];\n\n    hash ^= b0 + CASE_CONVERSION_TABLE[b0];\n    hash *= MULTIPLIER;\n    hash ^= b1 + CASE_CONVERSION_TABLE[b1];\n    hash *= MULTIPLIER;\n    hash ^= b2 + CASE_CONVERSION_TABLE[b2];\n    hash *= MULTIPLIER;\n    hash ^= b3 + CASE_CONVERSION_TABLE[b3];\n    hash *= MULTIPLIER;\n    hash ^= b4 + CASE_CONVERSION_TABLE[b4];\n    hash *= MULTIPLIER;\n    hash ^= b5 + CASE_CONVERSION_TABLE[b5];\n    hash *= MULTIPLIER;\n    hash ^= b6 + CASE_CONVERSION_TABLE[b6];\n    hash *= MULTIPLIER;\n    hash ^= b7 + CASE_CONVERSION_TABLE[b7];\n    hash *= MULTIPLIER;\n\n    i += sizeof(uint64_t);\n  }\n\n  // Handle remaining bytes\n  for (; i < name.size(); ++i) {\n    uint8_t b = name[i];\n    hash ^= b + CASE_CONVERSION_TABLE[b];\n    hash *= MULTIPLIER;\n  }\n\n  hash = (hash >> 16) ^ hash;\n  return hash;\n}\n\nconstexpr size_t HEADER_MAP_SIZE = 512;\n\nstruct HeaderHashTable final {\n  struct Entry {\n    kj::StringPtr name;\n    uint id = 0;\n  };\n\n  Entry entries[HEADER_MAP_SIZE] = {};\n\n  // These are both calculated at compile time by the constexpr constructor.\n  size_t kMaxEntrySize = 0;\n  size_t kMinEntrySize = kj::maxValue;\n\n  constexpr HeaderHashTable() {\n    for (uint i = 1; i <= Headers::MAX_COMMON_HEADER_ID; ++i) {\n      auto name = COMMON_HEADER_NAMES[i];\n      size_t slot = caseInsensitiveHash(name) % HEADER_MAP_SIZE;\n      entries[slot] = {name, i};\n      kMaxEntrySize = std::max(kMaxEntrySize, name.size());\n      kMinEntrySize = std::min(kMinEntrySize, name.size());\n    }\n  }\n\n  constexpr uint find(kj::StringPtr name) const {\n    if (name.size() < kMinEntrySize || name.size() > kMaxEntrySize) {\n      return 0;\n    }\n    size_t slot = caseInsensitiveHash(name) % HEADER_MAP_SIZE;\n    const auto& entry = entries[slot];\n    if (entry.id != 0 && strcaseeq(entry.name, name)) {\n      return entry.id;\n    }\n    return 0;  // Not found\n  }\n\n  // The isPerfectTest and isLowerTest are constexpr static assertions used\n  // only the verify correctness of the hash table at compile time.\n  constexpr bool isPerfectTest() const {\n    for (uint i = 1; i <= Headers::MAX_COMMON_HEADER_ID; ++i) {\n      auto name = COMMON_HEADER_NAMES[i];\n      size_t slot = caseInsensitiveHash(name) % HEADER_MAP_SIZE;\n      if (entries[slot].id != i) return false;\n      if (!strcaseeq(entries[slot].name, name)) return false;\n    }\n    return true;\n  }\n\n  constexpr bool isLowerTest() const {\n    for (uint i = 1; i <= Headers::MAX_COMMON_HEADER_ID; ++i) {\n      auto name = COMMON_HEADER_NAMES[i];\n      for (char c: name) {\n        if ('A' <= c && c <= 'Z') return false;\n      }\n    }\n    return true;\n  }\n};\n\nconstexpr HeaderHashTable HEADER_HASH_TABLE;\nstatic_assert(HEADER_HASH_TABLE.kMaxEntrySize == 27);\nstatic_assert(HEADER_HASH_TABLE.kMinEntrySize == 3);\nstatic_assert(HEADER_HASH_TABLE.isPerfectTest());\nstatic_assert(HEADER_HASH_TABLE.isLowerTest());\nstatic_assert(HEADER_HASH_TABLE.find(\"accept-charset\"_kj) == 1);\nstatic_assert(HEADER_HASH_TABLE.find(\"AcCePt-ChArSeT\"_kj) == 1);\nstatic_assert(std::size(COMMON_HEADER_NAMES) == (Headers::MAX_COMMON_HEADER_ID + 1));\n\nvoid maybeWarnIfBadHeaderString(kj::StringPtr name, kj::StringPtr str) {\n  KJ_IF_SOME(context, IoContext::tryCurrent()) {\n    if (context.hasWarningHandler()) {\n      if (!simdutf::validate_ascii(str.begin(), str.size())) {\n        // The string contains non-ASCII characters. While any 8-bit value is technically valid\n        // in HTTP headers, we encode header strings as UTF-8, so we want to warn the user that\n        // their header name/value may not be what they may expect based on what browsers do.\n        auto utf8Hex = kj::strArray(\n            KJ_MAP(b, str) { return kj::str(\"\\\\x\", kj::hex(static_cast<kj::byte>(b))); }, \"\");\n        context.logWarning(kj::str(\"A header value for \\\"\", name,\n            \"\\\" contains non-ASCII \"\n            \"characters: \\\"\",\n            str, \"\\\" (raw bytes: \\\"\", utf8Hex,\n            \"\\\"). As a quirk to support Unicode, we are encoding \"\n            \"values as UTF-8 in the header, but in a browser this would likely result in a \"\n            \"TypeError exception. Consider encoding this string in ASCII for compatibility with \"\n            \"browser implementations of the Fetch specification.\"));\n      }\n    }\n  }\n}\n\n// Left- and right-trim HTTP whitespace from `value`.\nkj::String normalizeHeaderValue(kj::StringPtr name, kj::String value) {\n  // Fast path: if empty, return as-is\n  if (value.size() == 0) {\n    return kj::mv(value);\n  }\n\n  char* begin = value.begin();\n  char* end = value.end();\n\n  while (begin < end && util::isHttpWhitespace(*begin)) {\n    ++begin;\n  }\n  while (begin < end && util::isHttpWhitespace(*(end - 1))) {\n    --end;\n  }\n\n  size_t newSize = end - begin;\n  if (newSize == value.size()) {\n    JSG_REQUIRE(workerd::util::isValidHeaderValue(value), TypeError, \"Invalid header value.\");\n    maybeWarnIfBadHeaderString(name, value);\n    return kj::mv(value);\n  }\n\n  auto trimmed = kj::ArrayPtr(begin, newSize);\n  JSG_REQUIRE(workerd::util::isValidHeaderValue(trimmed), TypeError, \"Invalid header value.\");\n  maybeWarnIfBadHeaderString(name, value);\n  // By attaching the original array to the trimmed view, we keep the original allocation alive\n  // and prevent an unnecessary copy.\n  return kj::str(trimmed.attach(value.releaseArray()));\n}\n\nHeaders::HeaderKey getHeaderKeyFor(kj::StringPtr name) {\n  if (uint commonId = HEADER_HASH_TABLE.find(name)) {\n    KJ_DASSERT(commonId > 0 && commonId <= Headers::MAX_COMMON_HEADER_ID);\n    return commonId;\n  }\n\n  for (char c: name) {\n    JSG_REQUIRE(util::isHttpTokenChar(c), TypeError, \"Invalid header name.\");\n  }\n\n  // Not a common header, so allocate lowercase copy for uncommon header\n  return toLower(name);\n}\n}  // namespace\n\nHeaders::Headers(jsg::Lock& js, jsg::Dict<kj::String, kj::String> dict): guard(Guard::NONE) {\n  // Because the headers might end up in either of our two tables,\n  // we can't really reserve space for them up front.\n  for (auto& field: dict.fields) {\n    append(js, kj::mv(field.name), kj::mv(field.value));\n  }\n}\n\nHeaders::Headers(jsg::Lock& js, const Headers& other): guard(Guard::NONE) {\n  for (kj::uint i = 1; i < other.commonHeaders.size(); i++) {\n    commonHeaders[i] =\n        other.commonHeaders[i].map([](const kj::Own<Header>& h) { return h->clone(); });\n  }\n  uncommonHeaders.reserve(other.uncommonHeaders.size());\n  for (auto& [key, header]: other.uncommonHeaders) {\n    // It should not be possible to have duplicate keys here.\n    uncommonHeaders.insert(kj::str(key), header->clone());\n  }\n}\n\nHeaders::Headers(jsg::Lock& js, const kj::HttpHeaders& other, Guard guard): guard(guard) {\n  // TODO(perf): Once kj::HttpHeaders supports an API for getting the CommonHeaderName directly\n  // from the headers, we can optimize this to avoid looking up the common header IDs again,\n  // making this constructor more efficient when copying common headers from kj::HttpHeaders.\n  other.forEach([this, &js](auto name, auto value) {\n    // We have to copy the strings here but we can avoid normalizing and validating since\n    // they presumably already went through that process when they were added to the\n    // kj::HttpHeader instance.\n    appendUnguarded(js, kj::str(name), kj::str(value));\n  });\n}\n\nkj::Maybe<Headers::Header&> Headers::tryGetHeader(const HeaderKey& key) {\n  KJ_SWITCH_ONEOF(key) {\n    KJ_CASE_ONEOF(idx, kj::uint) {\n      return commonHeaders[idx].map([](kj::Own<Header>& header) -> Header& { return *header; });\n    }\n    KJ_CASE_ONEOF(name, kj::String) {\n      return uncommonHeaders.find(name).map(\n          [](kj::Own<Header>& header) -> Header& { return *header; });\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\njsg::Ref<Headers> Headers::clone(jsg::Lock& js) const {\n  auto result = js.alloc<Headers>(js, *this);\n  result->guard = guard;\n  return kj::mv(result);\n}\n\n// Fill in the given HttpHeaders with these headers. Note that strings are inserted by\n// reference, so the output must be consumed immediately.\nvoid Headers::shallowCopyTo(kj::HttpHeaders& out) {\n  for (kj::uint i = 1; i < commonHeaders.size(); i++) {\n    KJ_IF_SOME(header, commonHeaders[i]) {\n      KJ_IF_SOME(name, header->name) {\n        for (auto& value: header->values) {\n          out.addPtrPtr(name, value);\n        }\n      } else {\n        auto name = getCommonHeaderName(i);\n        for (auto& value: header->values) {\n          out.addPtrPtr(name, value);\n        }\n      }\n    }\n  }\n\n  // This is unfortunate... but we have to sort the uncommon headers by name\n  // before passing them off to kj::HttpHeaders...While the actual header order\n  // (for different header names) is not meaningful in HTTP, we have tests that\n  // expect a certain order for reproducibility.\n  struct Entry {\n    kj::StringPtr name;\n    kj::StringPtr value;\n  };\n  kj::Vector<Entry> entries(uncommonHeaders.size());\n  for (auto& header: uncommonHeaders) {\n    KJ_IF_SOME(name, header.value->name) {\n      for (auto& value: header.value->values) {\n        entries.add(Entry{.name = name, .value = value});\n      }\n    } else {\n      for (auto& value: header.value->values) {\n        entries.add(Entry{.name = header.key, .value = value});\n      }\n    }\n  }\n  std::stable_sort(entries.begin(), entries.end(),\n      [](const Entry& a, const Entry& b) { return a.name < b.name; });\n\n  for (const auto& entry: entries) {\n    out.addPtrPtr(entry.name, entry.value);\n  }\n}\n\nkj::Array<Headers::DisplayedHeader> Headers::getDisplayedHeaders(jsg::Lock& js) {\n  auto getSetCookie = FeatureFlags::get(js).getHttpHeadersGetSetCookie();\n\n  size_t reserved = 0;\n\n  for (kj::uint i = 1; i < commonHeaders.size(); i++) {\n    KJ_IF_SOME(header, commonHeaders[i]) {\n      if (getSetCookie && i == static_cast<uint>(capnp::CommonHeaderName::SET_COOKIE)) {\n        reserved += header->values.size();\n      } else {\n        reserved += 1;\n      }\n    }\n  }\n  for (auto& header: uncommonHeaders) {\n    reserved += header.value->values.size();\n  }\n  kj::Vector<Headers::DisplayedHeader> vec(reserved);\n\n  for (kj::uint i = 1; i < commonHeaders.size(); i++) {\n    auto name = getCommonHeaderName(i);\n    KJ_IF_SOME(header, commonHeaders[i]) {\n      if (getSetCookie && i == static_cast<uint>(capnp::CommonHeaderName::SET_COOKIE)) {\n        for (auto& value: header->values) {\n          vec.add(Headers::DisplayedHeader{\n            .key = kj::str(name),\n            .value = kj::str(value),\n          });\n        }\n      } else {\n        vec.add(Headers::DisplayedHeader{\n          .key = kj::str(name),\n          .value = kj::strArray(header->values, \", \"),\n        });\n      }\n    }\n  }\n\n  for (auto& header: uncommonHeaders) {\n    vec.add(Headers::DisplayedHeader{\n      .key = kj::str(header.key),\n      .value = kj::strArray(header.value->values, \", \"),\n    });\n  }\n\n  auto ret = vec.releaseAsArray();\n  std::sort(ret.begin(), ret.end(), [](const auto& a, const auto& b) { return a.key < b.key; });\n  return kj::mv(ret);\n}\n\njsg::Ref<Headers> Headers::constructor(jsg::Lock& js, jsg::Optional<Initializer> init) {\n  using StringDict = jsg::Dict<kj::String, kj::String>;\n\n  KJ_IF_SOME(i, init) {\n    KJ_SWITCH_ONEOF(kj::mv(i)) {\n      KJ_CASE_ONEOF(dict, StringDict) {\n        return js.alloc<Headers>(js, kj::mv(dict));\n      }\n      KJ_CASE_ONEOF(headers, jsg::Ref<Headers>) {\n        return js.alloc<Headers>(js, *headers);\n        // It's important to note here that we are treating the Headers object\n        // as a special case here. Per the fetch spec, we *should* be grabbing\n        // the Symbol.iterator off the Headers object and interpreting it as\n        // a Sequence<Sequence<kj::String>> (as in the StringPairs case\n        // below). However, special casing Headers like we do here is more\n        // performant and has other side effects such as preserving the casing\n        // of header names that have been received.\n        //\n        // This does mean that we fail one of the more pathological (and kind\n        // of weird) Web Platform Tests for this API:\n        //\n        //   const h = new Headers();\n        //   h[Symbol.iterator] = function * () { yield [\"test\", \"test\"]; };\n        //   const headers = new Headers(h);\n        //   console.log(headers.has(\"test\"));\n        //\n        // The spec would say headers.has(\"test\") here should be true. With our\n        // implementation here, however, we are ignoring the Symbol.iterator so\n        // the test fails.\n      }\n      KJ_CASE_ONEOF(pairs, StringPairs) {\n        auto dict = KJ_MAP(entry, pairs) {\n          JSG_REQUIRE(entry.size() == 2, TypeError,\n              \"To initialize a Headers object from a sequence, each inner sequence \"\n              \"must have exactly two elements.\");\n          return StringDict::Field{kj::mv(entry[0]), kj::mv(entry[1])};\n        };\n        return js.alloc<Headers>(js, StringDict{kj::mv(dict)});\n      }\n    }\n  }\n\n  return js.alloc<Headers>();\n}\n\nkj::Maybe<kj::String> Headers::get(jsg::Lock& js, kj::String name) {\n  return getPtr(js, name);\n}\n\nkj::Maybe<kj::String> Headers::getPtr(jsg::Lock& js, kj::StringPtr name) {\n  return tryGetHeader(getHeaderKeyFor(name)).map([](Header& header) {\n    return kj::strArray(header.values, \", \");\n  });\n}\n\nkj::Maybe<kj::String> Headers::getCommon(jsg::Lock& js, capnp::CommonHeaderName idx) {\n  kj::uint index = static_cast<kj::uint>(idx);\n  KJ_DASSERT(index <= Headers::MAX_COMMON_HEADER_ID);\n  return commonHeaders[index].map([](auto& header) { return kj::strArray(header->values, \", \"); });\n}\n\nkj::Array<kj::StringPtr> Headers::getSetCookie() {\n  auto& header = commonHeaders[static_cast<kj::uint>(capnp::CommonHeaderName::SET_COOKIE)];\n  KJ_IF_SOME(h, header) {\n    return KJ_MAP(value, h->values) { return value.asPtr(); };\n  }\n  return nullptr;\n}\n\nkj::Array<kj::StringPtr> Headers::getAll(kj::String name) {\n  if (!strcaseeq(\"set-cookie\"_kj, name)) {\n    JSG_FAIL_REQUIRE(TypeError, \"getAll() can only be used with the header name \\\"Set-Cookie\\\".\");\n  }\n\n  // getSetCookie() is the standard API here. getAll(...) is our legacy non-standard extension\n  // for the same use case. We continue to support getAll for backwards compatibility but moving\n  // forward users really should be using getSetCookie.\n  return getSetCookie();\n}\n\nbool Headers::has(kj::String name) {\n  return tryGetHeader(getHeaderKeyFor(name)) != kj::none;\n}\n\nbool Headers::hasCommon(capnp::CommonHeaderName idx) {\n  kj::uint index = static_cast<kj::uint>(idx);\n  KJ_DASSERT(index <= Headers::MAX_COMMON_HEADER_ID);\n  return commonHeaders[index] != kj::none;\n}\n\nvoid Headers::set(jsg::Lock& js, kj::String name, kj::String value) {\n  checkGuard();\n  value = normalizeHeaderValue(name, kj::mv(value));\n  setUnguarded(js, kj::mv(name), kj::mv(value));\n}\n\nvoid Headers::setUnguarded(jsg::Lock& js, kj::String name, kj::String value) {\n  KJ_SWITCH_ONEOF(getHeaderKeyFor(name)) {\n    KJ_CASE_ONEOF(id, kj::uint) {\n      KJ_IF_SOME(existing, commonHeaders[id]) {\n        existing->values.resize(1);\n        existing->values[0] = kj::mv(value);\n      } else {\n        auto& created = commonHeaders[id].emplace(kj::heap(Header()));\n        if (name != getCommonHeaderName(id)) {\n          created->name = kj::mv(name);\n        }\n        created->values.resize(1);\n        created->values[0] = kj::mv(value);\n      }\n      return;\n    }\n    KJ_CASE_ONEOF(n, kj::String) {\n      using Ret = decltype(uncommonHeaders)::Entry;\n      auto& header = uncommonHeaders.findOrCreate(n, [&] -> Ret {\n        kj::Maybe<kj::String> maybeName;\n        if (name != n) {\n          maybeName = kj::mv(name);\n        }\n        return Ret{\n          .key = kj::mv(n),\n          .value = kj::heap(Header(kj::mv(maybeName))),\n        };\n      });\n      header->values.resize(1);\n      header->values[0] = kj::mv(value);\n      return;\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid Headers::setCommon(capnp::CommonHeaderName idx, kj::String value) {\n  kj::uint index = static_cast<kj::uint>(idx);\n  KJ_DASSERT(index <= Headers::MAX_COMMON_HEADER_ID);\n  KJ_IF_SOME(existing, commonHeaders[index]) {\n    existing->values.resize(1);\n    existing->values[0] = kj::mv(value);\n  } else {\n    auto& created = commonHeaders[index].emplace(kj::heap(Header()));\n    created->values.resize(1);\n    created->values[0] = kj::mv(value);\n  }\n}\n\nvoid Headers::append(jsg::Lock& js, kj::String name, kj::String value) {\n  checkGuard();\n  value = normalizeHeaderValue(name, kj::mv(value));\n  appendUnguarded(js, kj::mv(name), kj::mv(value));\n}\n\nvoid Headers::appendUnguarded(jsg::Lock& js, kj::String name, kj::String value) {\n  KJ_SWITCH_ONEOF(getHeaderKeyFor(name)) {\n    KJ_CASE_ONEOF(id, kj::uint) {\n      KJ_IF_SOME(existing, commonHeaders[id]) {\n        existing->values.add(kj::mv(value));\n      } else {\n        auto& created = commonHeaders[id].emplace(kj::heap(Header()));\n        if (name != getCommonHeaderName(id)) {\n          created->name = kj::mv(name);\n        }\n        created->values.resize(1);\n        created->values[0] = kj::mv(value);\n      }\n      return;\n    }\n    KJ_CASE_ONEOF(n, kj::String) {\n      KJ_IF_SOME(existing, uncommonHeaders.find(n)) {\n        existing->values.add(kj::mv(value));\n      } else {\n        using Ret = decltype(uncommonHeaders)::Entry;\n        auto& header = uncommonHeaders.findOrCreate(n, [&] -> Ret {\n          kj::Maybe<kj::String> maybeName;\n          if (name != n) {\n            maybeName = kj::mv(name);\n          }\n          return Ret{\n            .key = kj::mv(n),\n            .value = kj::heap(Header(kj::mv(maybeName))),\n          };\n        });\n        header->values.add(kj::mv(value));\n      }\n      return;\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid Headers::delete_(kj::String name) {\n  checkGuard();\n  KJ_SWITCH_ONEOF(getHeaderKeyFor(name)) {\n    KJ_CASE_ONEOF(id, kj::uint) {\n      commonHeaders[id] = kj::none;\n      return;\n    }\n    KJ_CASE_ONEOF(n, kj::String) {\n      uncommonHeaders.erase(n);\n      return;\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid Headers::deleteCommon(capnp::CommonHeaderName idx) {\n  kj::uint index = static_cast<kj::uint>(idx);\n  KJ_DASSERT(index <= Headers::MAX_COMMON_HEADER_ID);\n  commonHeaders[index] = kj::none;\n}\n\n// There are a couple implementation details of the Headers iterators worth calling out.\n//\n// 1. Each iterator gets its own copy of the keys and/or values of the headers. While nauseating\n//    from a performance perspective, this solves both the iterator -> iterable lifetime dependence\n//    and the iterator invalidation issue: i.e., it's impossible for a user to unsafely modify the\n//    Headers data structure while iterating over it, because they are simply two separate data\n//    structures. By empirical testing, this seems to be how Chrome implements Headers iteration.\n//\n//    Other alternatives bring their own pitfalls. We could store a Ref of the parent Headers\n//    object, solving the lifetime issue. To solve the iterator invalidation issue, we could store a\n//    copy of the currently-iterated-over key and use std::upper_bound() to find the next entry\n//    every time we want to increment the iterator (making the increment operation O(lg n) rather\n//    than O(1)); or we could make each Header entry in the map store a set of back-pointers to all\n//    live iterators pointing to it, with delete_() incrementing all iterators in the set whenever\n//    it deletes a header entry. Neither hack appealed to me.\n//\n// 2. Notice that the next() member function of the iterator classes moves the string(s) they\n//    contain, rather than making a copy of them as in the FormData iterators. This is safe to do\n//    because, unlike FormData, these iterators have their own copies of the strings, and since they\n//    are forward-only iterators, we know we won't need the strings again.\n//\n// TODO(perf): On point 1, perhaps we could avoid most copies by using a copy-on-write strategy\n//   applied to the header map elements? We'd still copy the whole data structure to avoid iterator\n//   invalidation, but the elements would be cheaper to copy.\n\njsg::Ref<Headers::EntryIterator> Headers::entries(jsg::Lock& js) {\n  return js.alloc<EntryIterator>(IteratorState<DisplayedHeader>{getDisplayedHeaders(js)});\n}\njsg::Ref<Headers::KeyIterator> Headers::keys(jsg::Lock& js) {\n  auto headers = getDisplayedHeaders(js);\n  kj::Vector<kj::String> keys(headers.size());\n  for (auto& header: headers) {\n    keys.add(kj::mv(header.key));\n  };\n  return js.alloc<KeyIterator>(IteratorState<kj::String>(keys.releaseAsArray()));\n}\njsg::Ref<Headers::ValueIterator> Headers::values(jsg::Lock& js) {\n  // Annoyingly, the spec requires that the values iterator still be sorted by key.\n  // To make this easiest, let's grab the displayed headers and then extract the values.\n  // the getDisplayedHeaders() function does the sorting for us at the cost of an extra\n  // copy of the names. Fortunately, enumerating by value is likely way less common than\n  // other forms of iteration so the cost should be acceptable.\n  auto headers = getDisplayedHeaders(js);\n  kj::Vector<kj::String> values(headers.size());\n  for (auto& header: headers) {\n    values.add(kj::mv(header.value));\n  };\n  return js.alloc<ValueIterator>(IteratorState<kj::String>(values.releaseAsArray()));\n}\n\nvoid Headers::forEach(jsg::Lock& js,\n    jsg::Function<void(kj::StringPtr, kj::StringPtr, jsg::Ref<Headers>)> callback,\n    jsg::Optional<jsg::Value> thisArg) {\n  auto receiver = js.v8Undefined();\n  KJ_IF_SOME(arg, thisArg) {\n    auto handle = arg.getHandle(js);\n    if (!handle->IsNullOrUndefined()) {\n      receiver = handle;\n    }\n  }\n  callback.setReceiver(js.v8Ref(receiver));\n\n  for (auto& entry: getDisplayedHeaders(js)) {\n    callback(js, entry.value, entry.key, JSG_THIS);\n  }\n}\n\nbool Headers::inspectImmutable() {\n  return guard != Guard::NONE;\n}\n\nvoid Headers::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  for (const auto& header: commonHeaders) {\n    tracker.trackField(\"header\", header);\n  }\n  for (const auto& header: uncommonHeaders) {\n    tracker.trackField(nullptr, header.value);\n  }\n}\n\n// -----------------------------------------------------------------------------\n// serialization of headers\n//\n// http-over-capnp.capnp has a nice list of common header names, taken from the HTTP/2 standard.\n// We'll use it as an optimization.\n//\n// Note that using numeric IDs for headers implies we lose the original capitalization. However,\n// the JS Headers API doesn't actually give the application any way to observe the capitalization\n// of header names -- it only becomes relevant when serializing over HTTP/1.1. And at that point,\n// we are actually free to change the capitalization anyway, and we commonly do (KJ itself will\n// normalize capitalization of all registered headers, and http-over-capnp also loses\n// capitalization). So, it's certainly not worth it to try to keep the original capitalization\n// across serialization.\n\nvoid Headers::serialize(jsg::Lock& js, jsg::Serializer& serializer) {\n  // We serialize as a series of key-value pairs. Each value is a length-delimited string. Each key\n  // is a common header ID, or the value zero to indicate an uncommon header, which is then\n  // followed by a length-delimited name.\n\n  serializer.writeRawUint32(static_cast<uint>(guard));\n\n  // Write the count of headers.\n  uint count = 0;\n  for (auto& header: commonHeaders) {\n    KJ_IF_SOME(h, header) {\n      count += h->values.size();\n    }\n  }\n  for (auto& header: uncommonHeaders) {\n    count += header.value->values.size();\n  }\n  serializer.writeRawUint32(count);\n\n  // Now write key/values.\n  for (kj::uint i = 1; i < commonHeaders.size(); i++) {\n    KJ_IF_SOME(header, commonHeaders[i]) {\n      for (auto& value: header->values) {\n        serializer.writeRawUint32(i);\n        serializer.writeLengthDelimited(value);\n      }\n    }\n  }\n  for (auto& header: uncommonHeaders) {\n    auto name = ([&] -> kj::StringPtr {\n      KJ_IF_SOME(name, header.value->name) {\n        return name;\n      } else {\n        return header.key;\n      }\n    })();\n    for (auto& value: header.value->values) {\n      serializer.writeRawUint32(0);\n      serializer.writeLengthDelimited(name);\n      serializer.writeLengthDelimited(value);\n    }\n  }\n}\n\njsg::Ref<Headers> Headers::deserialize(\n    jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer) {\n  auto result = js.alloc<Headers>();\n  uint guard = deserializer.readRawUint32();\n  KJ_REQUIRE(guard <= static_cast<uint>(Guard::NONE), \"unknown guard value\");\n\n  uint count = deserializer.readRawUint32();\n\n  for (auto i KJ_UNUSED: kj::zeroTo(count)) {\n    uint commonId = deserializer.readRawUint32();\n    kj::String name;\n    if (commonId == 0) {\n      name = deserializer.readLengthDelimitedString();\n    } else {\n      KJ_ASSERT(commonId <= Headers::MAX_COMMON_HEADER_ID);\n      name = kj::str(getCommonHeaderName(commonId));\n    }\n\n    auto value = deserializer.readLengthDelimitedString();\n\n    // TODO(performance): We can avoid some copies here by constructing the\n    // the Header entry directly using information from the deserializer\n    // directly without relying on append.\n    result->appendUnguarded(js, kj::mv(name), kj::mv(value));\n  }\n\n  // Don't actually set the guard until here because it may block the ability to call `append()`.\n  result->guard = static_cast<Guard>(guard);\n\n  return result;\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/headers.h",
    "content": "#pragma once\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/memory.h>\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/io/worker-interface.capnp.h>\n#include <kj/compat/http.h>\n\nnamespace workerd::api {\n\nclass Headers final: public jsg::Object {\nprivate:\n  template <typename T>\n  struct IteratorState {\n    kj::Array<T> copy;\n    decltype(copy.begin()) cursor = copy.begin();\n  };\n\npublic:\n  static constexpr kj::uint MAX_COMMON_HEADER_ID =\n    static_cast<kj::uint>(capnp::CommonHeaderName::WWW_AUTHENTICATE);\n\n  enum class Guard {\n    // WARNING: This type is serialized, do not change the numeric values.\n    IMMUTABLE = 0,\n    REQUEST = 1,\n    // REQUEST_NO_CORS,  // CORS not relevant on server side\n    RESPONSE = 2,\n    NONE = 3\n  };\n\n  struct DisplayedHeader {\n    kj::String key;   // lower-cased name\n    kj::String value; // comma-concatenation of all values seen\n  };\n\n  Headers(): guard(Guard::NONE) {}\n  explicit Headers(jsg::Lock& js, jsg::Dict<kj::String, kj::String> dict);\n  explicit Headers(jsg::Lock& js, const Headers& other);\n  explicit Headers(jsg::Lock& js, const kj::HttpHeaders& other, Guard guard);\n  KJ_DISALLOW_COPY_AND_MOVE(Headers);\n\n  // Make a copy of this Headers object, and preserve the guard.\n  jsg::Ref<Headers> clone(jsg::Lock& js) const;\n\n  // Fill in the given HttpHeaders with these headers. Note that strings are inserted by\n  // reference, so the output must be consumed immediately.\n  void shallowCopyTo(kj::HttpHeaders& out);\n\n  // Returns headers with lower-case name and comma-concatenated duplicates.\n  kj::Array<DisplayedHeader> getDisplayedHeaders(jsg::Lock& js);\n\n  using StringPair = jsg::Sequence<kj::String>;\n  using StringPairs = jsg::Sequence<StringPair>;\n\n  // Per the fetch specification, it is possible to initialize a Headers object\n  // from any other object that has a Symbol.iterator implementation. Those are\n  // handled in this Initializer definition using the StringPairs definition\n  // that aliases jsg::Sequence<jsg::Sequence<kj::String>>. Technically,\n  // the Headers object itself falls under that definition as well. However, treating\n  // a Headers object as a jsg::Sequence<jsg::Sequence<T>> is nowhere near as\n  // performant and has the side effect of forcing all header names to be lower-cased\n  // rather than case-preserved. Instead of following the spec exactly here, we\n  // choose to special case creating a Header object from another Header object.\n  // This is an intentional departure from the spec.\n  using Initializer = kj::OneOf<jsg::Ref<Headers>,\n                                StringPairs,\n                                jsg::Dict<kj::String, kj::String>>;\n\n  static jsg::Ref<Headers> constructor(jsg::Lock& js, jsg::Optional<Initializer> init);\n  kj::Maybe<kj::String> get(jsg::Lock& js, kj::String name);\n\n  // getAll is a legacy non-standard extension API that we introduced before\n  // getSetCookie() was defined. We continue to support it for backwards\n  // compatibility but users really ought to be using getSetCookie() now.\n  kj::Array<kj::StringPtr> getAll(kj::String name);\n\n  // The Set-Cookie header is special in that it is the only HTTP header that\n  // is not permitted to be combined into a single instance.\n  kj::Array<kj::StringPtr> getSetCookie();\n\n  bool has(kj::String name);\n\n  void set(jsg::Lock& js, kj::String name, kj::String value);\n  void append(jsg::Lock& js, kj::String name, kj::String value);\n  void delete_(kj::String name);\n\n  // The *Unguarded variations of set/append are used for internal use when we want to\n  // bypass certain checks, such as the guard check. These are not intended for public use and should be used with caution.\n  kj::Maybe<kj::String> getPtr(jsg::Lock& js, kj::StringPtr name);\n  void setUnguarded(jsg::Lock& js, kj::String name, kj::String value);\n  void appendUnguarded(jsg::Lock& js, kj::String name, kj::String value);\n\n  // The *Common variations of get/has/set/delete are used for internal use when we want to access\n  // common headers by their common enum ID. These are not intended for public use and should be\n  // used with caution. These also avoid guard checks.\n  kj::Maybe<kj::String> getCommon(jsg::Lock& js, capnp::CommonHeaderName idx);\n  bool hasCommon(capnp::CommonHeaderName idx);\n  void setCommon(capnp::CommonHeaderName idx, kj::String value);\n  void deleteCommon(capnp::CommonHeaderName idx);\n\n  void forEach(jsg::Lock& js,\n               jsg::Function<void(kj::StringPtr, kj::StringPtr, jsg::Ref<Headers>)>,\n               jsg::Optional<jsg::Value>);\n\n  bool inspectImmutable();\n\n  JSG_ITERATOR(EntryIterator, entries,\n               kj::Array<kj::String>,\n               IteratorState<DisplayedHeader>,\n               entryIteratorNext)\n  JSG_ITERATOR(KeyIterator, keys,\n               kj::String,\n               IteratorState<kj::String>,\n               keyOrValueIteratorNext)\n  JSG_ITERATOR(ValueIterator, values,\n               kj::String,\n               IteratorState<kj::String>,\n               keyOrValueIteratorNext)\n\n  // JavaScript API.\n\n  JSG_RESOURCE_TYPE(Headers, CompatibilityFlags::Reader flags) {\n    JSG_METHOD(get);\n    JSG_METHOD(getAll);\n    if (flags.getHttpHeadersGetSetCookie()) {\n      JSG_METHOD(getSetCookie);\n    }\n    JSG_METHOD(has);\n    JSG_METHOD(set);\n    JSG_METHOD(append);\n    JSG_METHOD_NAMED(delete, delete_);\n    JSG_METHOD(forEach);\n    JSG_METHOD(entries);\n    JSG_METHOD(keys);\n    JSG_METHOD(values);\n\n    JSG_INSPECT_PROPERTY(immutable, inspectImmutable);\n\n    JSG_ITERABLE(entries);\n\n    JSG_TS_DEFINE(type HeadersInit = Headers | Iterable<Iterable<string>> | Record<string, string>);\n    // All type aliases get inlined when exporting RTTI, but this type alias is included by\n    // the official TypeScript types, so users might be depending on it.\n\n    JSG_TS_OVERRIDE({\n      constructor(init?: HeadersInit);\n\n      entries(): IterableIterator<[key: string, value: string]>;\n      [Symbol.iterator](): IterableIterator<[key: string, value: string]>;\n\n      forEach<This = unknown>(callback: (this: This, value: string, key: string, parent: Headers) => void, thisArg?: This): void;\n    });\n  }\n\n  void serialize(jsg::Lock& js, jsg::Serializer& serializer);\n  static jsg::Ref<Headers> deserialize(\n      jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer);\n\n  JSG_SERIALIZABLE(rpc::SerializationTag::HEADERS);\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n  // A header is identified by either a common header ID or an uncommon header name.\n  // The header key name is always identifed in lower-case form, while the original\n  // casing is preserved in the actual Header struct to support case-preserving display.\n  // TODO(perf): We can likely optimize this further by interning uncommon header names\n  // so that we avoid repeated allocations of the same uncommon header name. Unless\n  // it proves to be a performance problem, however, we can leave that for future work.\n  using HeaderKey = kj::OneOf<uint, kj::String>;\n\nprivate:\n  struct Header final {\n    // The name is only set when the casing of the name differs from the lower-cased key.\n    kj::Maybe<kj::String> name;\n    kj::Vector<kj::String> values;\n    Header() = default;\n    explicit Header(kj::Maybe<kj::String> name): name(kj::mv(name)) {\n      values.reserve(1);\n    }\n\n    kj::Own<Header> clone() const {\n      Header header;\n      header.name = name.map([](const kj::String& s) { return kj::str(s); });\n      header.values = KJ_MAP(v, values) { return kj::str(v); };\n      return kj::heap(kj::mv(header));\n    }\n\n    JSG_MEMORY_INFO(Header) {\n      tracker.trackField(\"name\", name);\n      for (const auto& value : values) {\n        tracker.trackField(\"value\", value);\n      }\n    }\n  };\n\n  // This wastes one slot, but it is a fixed array for fast access.\n  kj::FixedArray<kj::Maybe<kj::Own<Header>>, MAX_COMMON_HEADER_ID + 1> commonHeaders;\n\n  // The key is always lower-case.\n  kj::HashMap<kj::String, kj::Own<Header>> uncommonHeaders;\n\n  Guard guard;\n\n  kj::Maybe<Header&> tryGetHeader(const HeaderKey& key);\n\n  void checkGuard() {\n    JSG_REQUIRE(guard == Guard::NONE, TypeError, \"Can't modify immutable headers.\");\n  }\n\n  static kj::Maybe<kj::Array<kj::String>> entryIteratorNext(jsg::Lock& js, auto& state) {\n    if (state.cursor == state.copy.end()) {\n      return kj::none;\n    }\n    auto& ret = *state.cursor++;\n    return kj::arr(kj::mv(ret.key), kj::mv(ret.value));\n  }\n\n  static kj::Maybe<kj::String> keyOrValueIteratorNext(jsg::Lock& js, auto& state) {\n    if (state.cursor == state.copy.end()) {\n      return kj::none;\n    }\n    auto& ret = *state.cursor++;\n    return kj::mv(ret);\n  }\n};\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/hibernatable-web-socket.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"hibernatable-web-socket.h\"\n\n#include <workerd/api/global-scope.h>\n#include <workerd/io/hibernation-manager.h>\n#include <workerd/io/tracer.h>\n#include <workerd/jsg/ser.h>\n\nnamespace workerd::api {\n\nHibernatableWebSocketEvent::HibernatableWebSocketEvent(): ExtendableEvent(\"webSocketMessage\") {};\n\nWorker::Actor::HibernationManager& HibernatableWebSocketEvent::getHibernationManager(\n    jsg::Lock& lock) {\n  auto& actor = KJ_REQUIRE_NONNULL(IoContext::current().getActor());\n  return KJ_REQUIRE_NONNULL(actor.getHibernationManager());\n}\n\nHibernatableWebSocketEvent::ItemsForRelease HibernatableWebSocketEvent::prepareForRelease(\n    jsg::Lock& lock, kj::StringPtr websocketId) {\n  auto& manager = kj::downcast<HibernationManagerImpl>(getHibernationManager(lock));\n  auto& hibernatableWebSocket =\n      KJ_REQUIRE_NONNULL(manager.webSocketsForEventHandler.findEntry(websocketId));\n\n  // Note that we don't call `claimWebSocket()` to get this, since we would lose our reference to\n  // the HibernatableWebSocket (it removes it from `webSocketsForEventHandler`).\n  auto websocketRef = hibernatableWebSocket.value->getActiveOrUnhibernate(lock);\n  auto ownedWebSocket = kj::mv(KJ_REQUIRE_NONNULL(hibernatableWebSocket.value->ws));\n  auto tags = hibernatableWebSocket.value->cloneTags();\n\n  // Now that we've obtained the websocket for the event, let's free up the slots we had allocated.\n  manager.webSocketsForEventHandler.erase(hibernatableWebSocket);\n\n  return ItemsForRelease(kj::mv(websocketRef), kj::mv(ownedWebSocket), kj::mv(tags));\n}\n\njsg::Ref<WebSocket> HibernatableWebSocketEvent::claimWebSocket(\n    jsg::Lock& lock, kj::StringPtr websocketId) {\n  // Should only be called once per event since it removes the HibernatableWebSocket from the\n  // webSocketsForEventHandler collection.\n  auto& manager = kj::downcast<HibernationManagerImpl>(getHibernationManager(lock));\n\n  // Grab it from our collection.\n  auto& hibernatableWebSocket =\n      KJ_REQUIRE_NONNULL(manager.webSocketsForEventHandler.findEntry(websocketId));\n\n  // Get the reference.\n  auto websocket = hibernatableWebSocket.value->getActiveOrUnhibernate(lock);\n\n  // Now that we've obtained the websocket, we need to remove the entry from the map and make the\n  // key available again.\n  manager.webSocketsForEventHandler.erase(hibernatableWebSocket);\n\n  return kj::mv(websocket);\n}\n\nkj::Promise<WorkerInterface::CustomEvent::Result> HibernatableWebSocketCustomEvent::run(\n    kj::Own<IoContext_IncomingRequest> incomingRequest,\n    kj::Maybe<kj::StringPtr> entrypointName,\n    kj::Maybe<Worker::VersionInfo> versionInfo,\n    Frankenvalue props,\n    kj::TaskSet& waitUntilTasks) {\n  // Mark the request as delivered because we're about to run some JS.\n  auto& context = incomingRequest->getContext();\n  incomingRequest->delivered();\n\n  KJ_DEFER({ waitUntilTasks.add(incomingRequest->drain().attach(kj::mv(incomingRequest))); });\n\n  EventOutcome outcome = EventOutcome::OK;\n\n  // We definitely have an actor by this point. Let's set the hibernation manager on the actor\n  // before we start running any events that might need to access it.\n  auto& a = KJ_REQUIRE_NONNULL(context.getActor());\n  if (a.getHibernationManager() == kj::none) {\n    a.setHibernationManager(kj::addRef(KJ_REQUIRE_NONNULL(manager)));\n  }\n\n  auto eventParameters = consumeParams();\n\n  try {\n    co_await context.run(\n        [entrypointName = entrypointName, &context, eventParameters = kj::mv(eventParameters),\n            versionInfo = kj::mv(versionInfo), props = kj::mv(props)](Worker::Lock& lock) mutable {\n      KJ_SWITCH_ONEOF(eventParameters.eventType) {\n        KJ_CASE_ONEOF(text, HibernatableSocketParams::Text) {\n          return lock.getGlobalScope().sendHibernatableWebSocketMessage(context,\n              kj::mv(text.message), eventParameters.eventTimeoutMs,\n              kj::mv(eventParameters.websocketId), lock,\n              lock.getExportedHandler(\n                  entrypointName, kj::mv(versionInfo), kj::mv(props), context.getActor()));\n        }\n        KJ_CASE_ONEOF(data, HibernatableSocketParams::Data) {\n          return lock.getGlobalScope().sendHibernatableWebSocketMessage(context,\n              kj::mv(data.message), eventParameters.eventTimeoutMs,\n              kj::mv(eventParameters.websocketId), lock,\n              lock.getExportedHandler(\n                  entrypointName, kj::mv(versionInfo), kj::mv(props), context.getActor()));\n        }\n        KJ_CASE_ONEOF(close, HibernatableSocketParams::Close) {\n          return lock.getGlobalScope().sendHibernatableWebSocketClose(context, kj::mv(close),\n              eventParameters.eventTimeoutMs, kj::mv(eventParameters.websocketId), lock,\n              lock.getExportedHandler(\n                  entrypointName, kj::mv(versionInfo), kj::mv(props), context.getActor()));\n        }\n        KJ_CASE_ONEOF(e, HibernatableSocketParams::Error) {\n          return lock.getGlobalScope().sendHibernatableWebSocketError(context, kj::mv(e.error),\n              eventParameters.eventTimeoutMs, kj::mv(eventParameters.websocketId), lock,\n              lock.getExportedHandler(\n                  entrypointName, kj::mv(versionInfo), kj::mv(props), context.getActor()));\n        }\n        KJ_UNREACHABLE;\n      }\n    });\n  } catch (kj::Exception& e) {\n    if (auto desc = e.getDescription();\n        !jsg::isTunneledException(desc) && !jsg::isDoNotLogException(desc)) {\n      LOG_EXCEPTION(\"HibernatableWebSocketCustomEvent\"_kj, e);\n    }\n    outcome = EventOutcome::EXCEPTION;\n  }\n\n  co_return Result{\n    .outcome = outcome,\n  };\n}\n\nkj::Promise<WorkerInterface::CustomEvent::Result> HibernatableWebSocketCustomEvent::sendRpc(\n    capnp::HttpOverCapnpFactory& httpOverCapnpFactory,\n    capnp::ByteStreamFactory& byteStreamFactory,\n    rpc::EventDispatcher::Client dispatcher) {\n  auto req = dispatcher.castAs<rpc::HibernatableWebSocketEventDispatcher>()\n                 .hibernatableWebSocketEventRequest();\n\n  KJ_IF_SOME(rpcParameters, params.tryGet<kj::Own<HibernationReader>>()) {\n    req.setMessage(rpcParameters->getMessage());\n  } else {\n    auto message = req.initMessage();\n    auto payload = message.initPayload();\n    auto& eventParameters = KJ_REQUIRE_NONNULL(params.tryGet<HibernatableSocketParams>());\n    KJ_SWITCH_ONEOF(eventParameters.eventType) {\n      KJ_CASE_ONEOF(text, HibernatableSocketParams::Text) {\n        payload.setText(kj::mv(text.message));\n      }\n      KJ_CASE_ONEOF(data, HibernatableSocketParams::Data) {\n        payload.setData(kj::mv(data.message));\n      }\n      KJ_CASE_ONEOF(close, HibernatableSocketParams::Close) {\n        auto closeBuilder = payload.initClose();\n        closeBuilder.setCode(close.code);\n        closeBuilder.setReason(kj::mv(close.reason));\n        closeBuilder.setWasClean(close.wasClean);\n      }\n      KJ_CASE_ONEOF(e, HibernatableSocketParams::Error) {\n        payload.setError(e.error.getDescription());\n      }\n      KJ_UNREACHABLE;\n    }\n    message.setWebsocketId(kj::mv(eventParameters.websocketId));\n    KJ_IF_SOME(t, eventParameters.eventTimeoutMs) {\n      message.setEventTimeoutMs(t);\n    }\n  }\n\n  return req.send().then([](auto resp) {\n    auto respResult = resp.getResult();\n    return WorkerInterface::CustomEvent::Result{\n      .outcome = respResult.getOutcome(),\n    };\n  });\n}\n\nHibernatableWebSocketEvent::ItemsForRelease::ItemsForRelease(\n    jsg::Ref<WebSocket> ref, kj::Own<kj::WebSocket> owned, kj::Array<kj::String> tags)\n    : webSocketRef(kj::mv(ref)),\n      ownedWebSocket(kj::mv(owned)),\n      tags(kj::mv(tags)) {}\n\nHibernatableWebSocketCustomEvent::HibernatableWebSocketCustomEvent(uint16_t typeId,\n    kj::Own<HibernationReader> params,\n    kj::Maybe<Worker::Actor::HibernationManager&> manager)\n    : typeId(typeId),\n      params(kj::mv(params)) {}\nHibernatableWebSocketCustomEvent::HibernatableWebSocketCustomEvent(\n    uint16_t typeId, HibernatableSocketParams params, Worker::Actor::HibernationManager& manager)\n    : typeId(typeId),\n      params(kj::mv(params)),\n      manager(manager) {}\n\n// Try to extract event type from params if available\ntracing::HibernatableWebSocketEventInfo::Type HibernatableWebSocketCustomEvent::getEventType()\n    const {\n  KJ_SWITCH_ONEOF(params) {\n    KJ_CASE_ONEOF(socketParams, HibernatableSocketParams) {\n      KJ_SWITCH_ONEOF(socketParams.eventType) {\n        KJ_CASE_ONEOF(_, HibernatableSocketParams::Text) {\n          return tracing::HibernatableWebSocketEventInfo::Message{};\n        }\n        KJ_CASE_ONEOF(_, HibernatableSocketParams::Data) {\n          return tracing::HibernatableWebSocketEventInfo::Message{};\n        }\n        KJ_CASE_ONEOF(close, HibernatableSocketParams::Close) {\n          return tracing::HibernatableWebSocketEventInfo::Close{close.code, close.wasClean};\n        }\n        KJ_CASE_ONEOF(_, HibernatableSocketParams::Error) {\n          return tracing::HibernatableWebSocketEventInfo::Error{};\n        }\n      }\n    }\n    KJ_CASE_ONEOF(reader, kj::Own<HibernationReader>) {\n      // Parse the HibernationReader to determine the actual event type\n      auto payload = reader->getMessage().getPayload();\n      switch (payload.which()) {\n        case rpc::HibernatableWebSocketEventMessage::Payload::TEXT:\n        case rpc::HibernatableWebSocketEventMessage::Payload::DATA:\n          return tracing::HibernatableWebSocketEventInfo::Message{};\n        case rpc::HibernatableWebSocketEventMessage::Payload::CLOSE: {\n          auto close = payload.getClose();\n          return tracing::HibernatableWebSocketEventInfo::Close{\n            close.getCode(), close.getWasClean()};\n        }\n        case rpc::HibernatableWebSocketEventMessage::Payload::ERROR:\n          return tracing::HibernatableWebSocketEventInfo::Error{};\n      }\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\ntracing::EventInfo HibernatableWebSocketCustomEvent::getEventInfo() const {\n  return tracing::HibernatableWebSocketEventInfo(getEventType());\n}\n\nHibernatableSocketParams HibernatableWebSocketCustomEvent::consumeParams() {\n  KJ_IF_SOME(p, params.tryGet<kj::Own<HibernationReader>>()) {\n    kj::Maybe<HibernatableSocketParams> eventParameters;\n    auto websocketId = kj::str(p->getMessage().getWebsocketId());\n    auto payload = p->getMessage().getPayload();\n    switch (payload.which()) {\n      case rpc::HibernatableWebSocketEventMessage::Payload::TEXT: {\n        eventParameters.emplace(kj::str(payload.getText()), kj::mv(websocketId));\n        break;\n      }\n      case rpc::HibernatableWebSocketEventMessage::Payload::DATA: {\n        kj::Array<byte> b = kj::heapArray(payload.getData().asBytes());\n        eventParameters.emplace(kj::mv(b), kj::mv(websocketId));\n        break;\n      }\n      case rpc::HibernatableWebSocketEventMessage::Payload::CLOSE: {\n        auto close = payload.getClose();\n        eventParameters.emplace(\n            close.getCode(), kj::str(close.getReason()), close.getWasClean(), kj::mv(websocketId));\n        break;\n      }\n      case rpc::HibernatableWebSocketEventMessage::Payload::ERROR: {\n        eventParameters.emplace(\n            KJ_EXCEPTION(FAILED, kj::str(payload.getError())), kj::mv(websocketId));\n        break;\n      }\n    }\n    KJ_REQUIRE_NONNULL(eventParameters).setTimeout(p->getMessage().getEventTimeoutMs());\n    return kj::mv(KJ_REQUIRE_NONNULL(eventParameters));\n  }\n  return kj::mv(KJ_REQUIRE_NONNULL(params.tryGet<HibernatableSocketParams>()));\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/hibernatable-web-socket.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/api/basics.h>\n#include <workerd/api/hibernation-event-params.h>\n#include <workerd/api/web-socket.h>\n#include <workerd/io/trace.h>\n#include <workerd/io/worker-interface.capnp.h>\n#include <workerd/io/worker-interface.h>\n#include <workerd/io/worker.h>\n\n#include <kj/debug.h>\n\nnamespace workerd::api {\n\nusing HibernationReader =\n    rpc::HibernatableWebSocketEventDispatcher::HibernatableWebSocketEventParams::Reader;\nclass HibernatableWebSocketEvent final: public ExtendableEvent {\n public:\n  explicit HibernatableWebSocketEvent();\n\n  static jsg::Ref<HibernatableWebSocketEvent> constructor(kj::String type) = delete;\n\n  // When we call a close or error event, we need to move the owned websocket and the tags back into\n  // the api::WebSocket to extend their lifetimes. This is because the HibernatableWebSocket, which\n  // has owned these things for the entire duration of the connection, is free to go away after we\n  // dispatch the final event. JS may still want to access the underlying kj::WebSocket or the tags,\n  // so we have to transfer ownership to JS-land.\n  struct ItemsForRelease {\n    jsg::Ref<WebSocket> webSocketRef;\n    kj::Own<kj::WebSocket> ownedWebSocket;\n    kj::Array<kj::String> tags;\n\n    explicit ItemsForRelease(\n        jsg::Ref<WebSocket> ref, kj::Own<kj::WebSocket> owned, kj::Array<kj::String> tags);\n  };\n\n  // Call this when transferring ownership of the kj::WebSocket and tags to the api::WebSocket.\n  //\n  // Gets a reference to the api::WebSocket, and moves the owned kj::WebSocket out of the\n  // HibernatableWebSocket whose event we are currently delivering.\n  ItemsForRelease prepareForRelease(jsg::Lock& lock, kj::StringPtr websocketId);\n\n  // Should only be called once per event, see definition for details.\n  jsg::Ref<WebSocket> claimWebSocket(jsg::Lock& lock, kj::StringPtr websocketId);\n\n  JSG_RESOURCE_TYPE(HibernatableWebSocketEvent) {\n    JSG_INHERIT(ExtendableEvent);\n  }\n\n private:\n  Worker::Actor::HibernationManager& getHibernationManager(jsg::Lock& lock);\n};\n\nclass HibernatableWebSocketCustomEvent final: public WorkerInterface::CustomEvent,\n                                              public kj::Refcounted {\n public:\n  HibernatableWebSocketCustomEvent(uint16_t typeId,\n      kj::Own<HibernationReader> params,\n      kj::Maybe<Worker::Actor::HibernationManager&> manager = kj::none);\n  HibernatableWebSocketCustomEvent(\n      uint16_t typeId, HibernatableSocketParams params, Worker::Actor::HibernationManager& manager);\n\n  kj::Promise<Result> run(kj::Own<IoContext_IncomingRequest> incomingRequest,\n      kj::Maybe<kj::StringPtr> entrypointName,\n      kj::Maybe<Worker::VersionInfo> versionInfo,\n      Frankenvalue props,\n      kj::TaskSet& waitUntilTasks) override;\n\n  kj::Promise<Result> sendRpc(capnp::HttpOverCapnpFactory& httpOverCapnpFactory,\n      capnp::ByteStreamFactory& byteStreamFactory,\n      rpc::EventDispatcher::Client dispatcher) override;\n\n  uint16_t getType() override {\n    return typeId;\n  }\n\n  tracing::EventInfo getEventInfo() const override;\n\n  kj::Promise<Result> notSupported() override {\n    KJ_UNIMPLEMENTED(\"hibernatable web socket event not supported\");\n  }\n\n private:\n  // Returns `params`, but if we have a HibernationReader we convert it to a\n  // HibernatableSocketParams first.\n  HibernatableSocketParams consumeParams();\n\n  // Peeks at params to extract the event type for tracing, without consuming them.\n  tracing::HibernatableWebSocketEventInfo::Type getEventType() const;\n\n  uint16_t typeId;\n  kj::OneOf<HibernatableSocketParams, kj::Own<HibernationReader>> params;\n  kj::Maybe<uint32_t> timeoutMs;\n  kj::Maybe<Worker::Actor::HibernationManager&> manager;\n};\n\n#define EW_WEB_SOCKET_MESSAGE_ISOLATE_TYPES                                                        \\\n  api::HibernatableWebSocketEvent, api::HibernatableWebSocketExportedHandler\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/hibernation-event-params.h",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/common.h>\n#include <kj/exception.h>\n#include <kj/one-of.h>\n#include <kj/string.h>\n\n#include <cstdint>\n\nnamespace workerd::api {\n// Event types and their corresponding parameters.\nstruct HibernatableSocketParams {\n  struct Text {\n    kj::String message;\n  };\n\n  struct Data {\n    kj::Array<kj::byte> message;\n  };\n\n  struct Close {\n    uint16_t code;\n    kj::String reason;\n    bool wasClean;\n  };\n\n  struct Error {\n    kj::Exception error;\n  };\n\n  kj::OneOf<Text, Data, Close, Error> eventType;\n  kj::String websocketId;\n  kj::Maybe<uint32_t> eventTimeoutMs;\n\n  explicit HibernatableSocketParams(kj::String message, kj::String id)\n      : eventType(Text{kj::mv(message)}),\n        websocketId(kj::mv(id)) {}\n  explicit HibernatableSocketParams(kj::Array<kj::byte> message, kj::String id)\n      : eventType(Data{kj::mv(message)}),\n        websocketId(kj::mv(id)) {}\n  explicit HibernatableSocketParams(uint16_t code, kj::String reason, bool wasClean, kj::String id)\n      : eventType(Close{code, kj::mv(reason), wasClean}),\n        websocketId(kj::mv(id)) {}\n  explicit HibernatableSocketParams(kj::Exception e, kj::String id)\n      : eventType(Error{kj::mv(e)}),\n        websocketId(kj::mv(id)) {}\n\n  HibernatableSocketParams(HibernatableSocketParams&& other) = default;\n\n  bool isCloseEvent() {\n    return eventType.is<Close>();\n  }\n\n  void setTimeout(kj::Maybe<uint32_t> timeoutMs) {\n    eventTimeoutMs = kj::mv(timeoutMs);\n  }\n};\n};  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/html-rewriter.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"html-rewriter.h\"\n\n#include \"util.h\"\n\n#include <workerd/api/streams/common.h>\n#include <workerd/api/streams/identity-transform-stream.h>\n#include <workerd/io/features.h>\n#include <workerd/io/io-context.h>\n\n#include <lol_html.h>\n\nstruct lol_html_HtmlRewriter {};\nstruct lol_html_HtmlRewriterBuilder {};\nstruct lol_html_AttributesIterator {};\nstruct lol_html_Selector {};\n// TODO(cleanup): These are defined internally in lol-html, but kj::Own<T> needs to check whether\n//   or not T is polymorphic, so here's a dummy definition.\n\nnamespace workerd::api {\n\nnamespace {\n\n// =======================================================================================\n// RAII helpers for lol-html\n\n// RAII helper for lol-html types which are managed by pointers and have straightforward _free()\n// functions.\ntemplate <typename T, void (*lolhtmlFree)(T*)>\nclass LolHtmlDisposer: public kj::Disposer {\n public:\n  static const LolHtmlDisposer INSTANCE;\n\n protected:\n  void disposeImpl(void* pointer) const override {\n    lolhtmlFree(reinterpret_cast<T*>(pointer));\n  }\n};\n\ntemplate <typename T, void (*lolhtmlFree)(T*)>\nconst LolHtmlDisposer<T, lolhtmlFree> LolHtmlDisposer<T, lolhtmlFree>::INSTANCE;\n\n#define LOL_HTML_OWN(name, ...)                                                                    \\\n  ({                                                                                               \\\n    using T = lol_html_##name##_t;                                                                 \\\n    constexpr auto* lolhtmlFree = lol_html_##name##_free;                                          \\\n    kj::Own<T>(&check(__VA_ARGS__), LolHtmlDisposer<T, lolhtmlFree>::INSTANCE);                    \\\n  })\n\n// RAII helper for lol_html_str_t.\n//\n// We cannot use a kj::Own<T> because lol_html_str_t is a struct, not a pointer, so instead we\n// have this LolString RAII wrapper.\n//\n// Use `kj::str(LolString.asChars())` to allocate your own copy of a LolString.\nclass LolString {\n public:\n  explicit LolString(lol_html_str_t s): chars(s.data, s.len) {}\n  ~LolString() noexcept(false) {\n    lol_html_str_free({chars.begin(), chars.size()});\n  }\n  KJ_DISALLOW_COPY(LolString);\n\n  kj::ArrayPtr<const char> asChars() const {\n    return chars;\n  }\n\n  kj::Maybe<kj::String> asKjString() {\n    if (chars.begin() != nullptr) {\n      return kj::str(chars);\n    } else {\n      return kj::none;\n    }\n  }\n\n private:\n  kj::ArrayPtr<const char> chars;\n};\n\n// =======================================================================================\n// Error checking for lol-html\n\nkj::Maybe<kj::Exception> tryGetLastError() {\n  auto maybeErrorString = lol_html_take_last_error();\n  if (maybeErrorString.data == nullptr) {\n    return kj::none;\n  }\n  auto errorString = LolString(maybeErrorString);\n  return kj::Exception(kj::Exception::Type::FAILED, __FILE__, __LINE__,\n      kj::str(JSG_EXCEPTION(TypeError) \": Parser error: \", errorString.asChars()));\n}\n\nvoid discardLastError() {\n  auto drop = LolString(lol_html_take_last_error());\n}\n\nkj::Exception getLastError() {\n  return KJ_REQUIRE_NONNULL(tryGetLastError(),\n      \"lol-html reported error through return value, but lol_html_take_last_error() is null\");\n}\n\nint check(int rc) {\n  if (rc == -1) {\n    kj::throwFatalException(getLastError());\n  }\n  return rc;\n}\n\ntemplate <typename T>\n[[nodiscard]] T& check(T* ptr) {\n  // Nodiscard, because this function typically checks references that must later be freed.\n  if (ptr == nullptr) {\n    kj::throwFatalException(getLastError());\n  }\n  return *ptr;\n}\n\n// Helper function to determine if a content token is still valid. Each content token has an\n// implementation object inside a Maybe -- when HTMLRewriter::TokenScope (defined below)\n// gets destroyed, that Maybe gets nullified, and the content token becomes a dead, useless,\n// JavaScript object occupying space, waiting to get garbage collected.\n//\n// In other words, if you try to access a content token (Element, Text, etc.) outside of a\n// content handler, you're gonna get this exception.\ntemplate <typename T>\ndecltype(auto) checkToken(kj::Maybe<T>& impl) {\n  return JSG_REQUIRE_NONNULL(impl, TypeError,\n      \"This content token is no longer valid. Content tokens are only valid \"\n      \"during the execution of the relevant content handler.\");\n}\n\n}  // namespace\n\n// =======================================================================================\n// HTMLRewriter::TokenScope\n\nclass HTMLRewriter::TokenScope {\n public:\n  template <typename T>\n  explicit TokenScope(jsg::Ref<T>& value): contentToken(value.addRef()) {}\n  ~TokenScope() noexcept(false) {\n    KJ_IF_SOME(token, contentToken) {\n      token->htmlContentScopeEnd();\n    }\n  }\n  TokenScope(TokenScope&& o): contentToken(kj::mv(o.contentToken)) {\n    o.contentToken = kj::none;\n  }\n  KJ_DISALLOW_COPY(TokenScope);\n\n private:\n  kj::Maybe<jsg::Ref<HTMLRewriter::Token>> contentToken;\n};\n\nnamespace {\n\n// =======================================================================================\n// Rewriter\nusing ElementCallbackFunction = HTMLRewriter::ElementCallbackFunction;\n\nstruct UnregisteredElementHandlers {\n  kj::Own<lol_html_Selector> selector;\n\n  // The actual handler functions. We store them as jsg::Values for compatibility with GcVisitor.\n\n  jsg::Optional<ElementCallbackFunction> element;\n  jsg::Optional<ElementCallbackFunction> comments;\n  jsg::Optional<ElementCallbackFunction> text;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(element, comments, text);\n  }\n\n  JSG_MEMORY_INFO(UnregisteredElementHandlers) {\n    tracker.trackField(\"element\", element);\n    tracker.trackField(\"comments\", comments);\n    tracker.trackField(\"text\", text);\n  }\n};\n\nstruct UnregisteredDocumentHandlers {\n\n  // The actual handler functions. We store them as jsg::Values for compatibility with GcVisitor.\n\n  jsg::Optional<ElementCallbackFunction> doctype;\n  jsg::Optional<ElementCallbackFunction> comments;\n  jsg::Optional<ElementCallbackFunction> text;\n  jsg::Optional<ElementCallbackFunction> end;\n\n  // The `this` object used to call the handler functions.\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(doctype, comments, text, end);\n  }\n\n  JSG_MEMORY_INFO(UnregisteredDocumentHandlers) {\n    tracker.trackField(\"doctype\", doctype);\n    tracker.trackField(\"comments\", comments);\n    tracker.trackField(\"text\", text);\n    tracker.trackField(\"end\", end);\n  }\n};\n\nusing UnregisteredElementOrDocumentHandlers =\n    kj::OneOf<UnregisteredElementHandlers, UnregisteredDocumentHandlers>;\n\n}  // namespace\n\n// Wrapper around an actual rewriter (streaming parser).\nclass Rewriter final: public WritableStreamSink {\n public:\n  explicit Rewriter(jsg::Lock& js,\n      kj::ArrayPtr<UnregisteredElementOrDocumentHandlers> unregisteredHandlers,\n      kj::ArrayPtr<const char> encoding,\n      kj::Own<WritableStreamSink> inner);\n  KJ_DISALLOW_COPY_AND_MOVE(Rewriter);\n\n  // WritableStreamSink implementation. The input body pumpTo() operation calls these.\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override;\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override;\n  kj::Promise<void> end() override;\n  void abort(kj::Exception reason) override;\n\n  // Implementation for `Element::onEndTag` to avoid exposing private details of Rewriter.\n  void onEndTag(lol_html_element_t* element, ElementCallbackFunction&& callback);\n\n  lol_html_streaming_handler_t registerReplacer(jsg::Ref<ReadableStream> content, bool isHtml);\n\n  ~Rewriter() {\n    KJ_ASSERT(registeredReplacers.size() == 0, \"Some replacers were leaked by lol-html\");\n  }\n\n private:\n  // Wait for the write promise (if any) produced by our `output()` callback, then, if there is a\n  // stored exception, abort the wrapped WritableStreamSink with it, then return the exception.\n  // Otherwise, just return.\n  kj::Promise<void> finishWrite();\n\n  kj::Promise<void> flushWrite();\n\n  static kj::Own<lol_html_HtmlRewriter> buildRewriter(jsg::Lock& js,\n      kj::ArrayPtr<UnregisteredElementOrDocumentHandlers> unregisteredHandlers,\n      kj::ArrayPtr<const char> encoding,\n      Rewriter& rewriterWrapper);\n\n  static void output(const char* buffer, size_t size, void* userdata);\n  void outputImpl(kj::ArrayPtr<const byte> buffer);\n\n  void tryHandleCancellation(int rc) {\n    if (canceled) {\n      canceled = false;\n\n      // We canceled this, which means we used LOL_HTML_STOP. That means we have an error sitting\n      // in the error buffer in the lol-html C API. Let's make sure our return code is -1 and get\n      // rid of that error value to make sure nobody picks it up later on accident and thinks an\n      // error occurred.\n      KJ_ASSERT(rc == -1);\n      discardLastError();\n\n      throw kj::CanceledException{};\n    }\n  }\n\n  friend class ::workerd::api::HTMLRewriter;\n\n  // Keeps track of streams currently being used as replacement content for tokens.\n  // lol-html will invoke replacerThunk with a pointer to the RegisteredReplacer to be used.\n  class RegisteredReplacer {\n   public:\n    Rewriter& rewriter;\n    bool isHtml;\n    jsg::Ref<ReadableStream> stream;\n  };\n\n  struct RegisteredHandler {\n    // A back-reference to the rewriter which owns this particular registered handler.\n    Rewriter& rewriter;\n\n    ElementCallbackFunction callback;\n  };\n\n  kj::Vector<kj::Own<RegisteredHandler>> registeredHandlers;\n  // TODO(perf): Don't store Owns. We need to pass stable pointers as the userdata parameter to\n  //   lol_html_rewriter_builder_add_*_content_handlers(), but don't have a really easy way to\n  //   know precisely how many handlers we're going to register beforehand, so we need a vector. But\n  //   vectors can grow, moving their objects around, invalidating pointers into their storage.\n\n  // This is separate from `registeredHandlers` so we can delete them more eagerly when EndTags are\n  // destroyed, and not have to look through all other handlers.\n  kj::Vector<kj::Own<RegisteredHandler>> registeredEndTagHandlers;\n  // TODO(perf) Don't store Owns, same as `registeredHandlers` above.\n\n  template <typename T, typename CType = T::CType>\n  static lol_html_rewriter_directive_t thunk(CType* content, void* userdata);\n  template <typename T, typename CType = T::CType>\n  lol_html_rewriter_directive_t thunkImpl(CType* content, RegisteredHandler& registration);\n  template <typename T, typename CType = T::CType>\n  kj::Promise<void> thunkPromise(CType* content, RegisteredHandler& registration);\n\n  // Eagerly free this handler. Should only be called if we're confident the handler will never be\n  // used again.\n  void removeEndTagHandler(RegisteredHandler& registration);\n\n  // Field stores a list of readable streams that are being used by lol-html for replacements\n  kj::HashMap<void*, kj::Own<RegisteredReplacer>> registeredReplacers;\n\n  static int replacerThunk(lol_html_streaming_sink_t* sink, void* userData);\n  kj::Promise<void> replacerThunkPromise(\n      lol_html_streaming_sink_t* sink, RegisteredReplacer& registration);\n  int replacerThunkImpl(lol_html_streaming_sink_t* sink, RegisteredReplacer& registration);\n  static void removeRegisteredReplacer(void* userData);\n\n  // Must be constructed AFTER the registered handler vector, since the function which constructs\n  // this (buildRewriter()) modifies that vector.\n  kj::Own<lol_html_HtmlRewriter> rewriter;\n\n  // Stores data written by lol-html, which will be periodically flushed to inner.\n  kj::Vector<kj::byte> outputBuffer;\n\n  // Used to ensure memory usage is reported to V8 and cannot grow arbritrarily\n  jsg::ExternalMemoryAdjustment externalMemoryAdjustment;\n\n  // True if we are currently flushing from outputBuffer to inner (in flushWrite)\n  bool flushing = false;\n\n  // The destination for the output from lol-html\n  kj::Own<WritableStreamSink> inner;\n\n  kj::Maybe<kj::Exception> maybeException;\n\n  IoContext& ioContext;\n\n  kj::Maybe<kj::WaitScope&> maybeWaitScope;\n\n  bool canceled = false;\n\n  kj::Maybe<jsg::Ref<jsg::AsyncContextFrame>> maybeAsyncContext;\n\n  bool isPoisoned() {\n    // If a call to `lol-html` returned an error or propagated a user error from a handler\n    // (LOL_HTML_STOP for instance); we consider its instance as poisoned. Future calls to\n    // `lol_html_rewriter_write` and `lol_html_rewriter_end` will probably throw.\n    return maybeException != kj::none;\n  }\n\n  void maybePoison(kj::Exception exception) {\n    // Ignore this error if maybeException is already populated -- this error is probably just a\n    // secondary effect.\n    if (maybeException == kj::none) {\n      maybeException = kj::mv(exception);\n    }\n  }\n};\n\nkj::Own<lol_html_HtmlRewriter> Rewriter::buildRewriter(jsg::Lock& js,\n    kj::ArrayPtr<UnregisteredElementOrDocumentHandlers> unregisteredHandlers,\n    kj::ArrayPtr<const char> encoding,\n    Rewriter& rewriter) {\n  auto builder = LOL_HTML_OWN(rewriter_builder, lol_html_rewriter_builder_new());\n\n  auto registerCallback = [&](ElementCallbackFunction& callback) {\n    auto registeredHandler = RegisteredHandler{rewriter, callback.addRef(js)};\n    return rewriter.registeredHandlers.add(kj::heap(kj::mv(registeredHandler))).get();\n  };\n\n  for (auto& handlers: unregisteredHandlers) {\n    KJ_SWITCH_ONEOF(handlers) {\n      KJ_CASE_ONEOF(elementHandlers, UnregisteredElementHandlers) {\n        auto element = elementHandlers.element.map(registerCallback);\n        auto comments = elementHandlers.comments.map(registerCallback);\n        auto text = elementHandlers.text.map(registerCallback);\n\n        check(lol_html_rewriter_builder_add_element_content_handlers(builder,\n            elementHandlers.selector, element == kj::none ? nullptr : &Rewriter::thunk<Element>,\n            element.orDefault(nullptr), comments == kj::none ? nullptr : &Rewriter::thunk<Comment>,\n            comments.orDefault(nullptr), text == kj::none ? nullptr : &Rewriter::thunk<Text>,\n            text.orDefault(nullptr)));\n      }\n      KJ_CASE_ONEOF(documentHandlers, UnregisteredDocumentHandlers) {\n        auto doctype = documentHandlers.doctype.map(registerCallback);\n        auto comments = documentHandlers.comments.map(registerCallback);\n        auto text = documentHandlers.text.map(registerCallback);\n        auto end = documentHandlers.end.map(registerCallback);\n\n        // Adding document content handlers cannot fail, so no need for check().\n        lol_html_rewriter_builder_add_document_content_handlers(builder,\n            doctype == kj::none ? nullptr : &Rewriter::thunk<Doctype>, doctype.orDefault(nullptr),\n            comments == kj::none ? nullptr : &Rewriter::thunk<Comment>, comments.orDefault(nullptr),\n            text == kj::none ? nullptr : &Rewriter::thunk<Text>, text.orDefault(nullptr),\n            end == kj::none ? nullptr : &Rewriter::thunk<DocumentEnd>, end.orDefault(nullptr));\n      }\n    }\n  }\n\n  // `strict` mode will bail out from tokenization process in cases when\n  // there is no way to determine correct parsing context. Recommended\n  // setting for safety reasons.\n  bool isStrict = true;\n\n  // Configure a maximum memory limit that `lol-html` is allowed to use and\n  // preallocate some memory for its internal buffer.\n  lol_html_memory_settings_t memorySettings = {\n    .preallocated_parsing_buffer_size = 1024, .max_allowed_memory_usage = 3 * 1024 * 1024};\n\n  if (FeatureFlags::get(js).getEsiIncludeIsVoidTag()) {\n    return LOL_HTML_OWN(rewriter,\n        unstable_lol_html_rewriter_build_with_esi_tags(builder, encoding.begin(), encoding.size(),\n            memorySettings, &Rewriter::output, &rewriter, isStrict));\n\n  } else {\n    return LOL_HTML_OWN(rewriter,\n        lol_html_rewriter_build(builder, encoding.begin(), encoding.size(), memorySettings,\n            &Rewriter::output, &rewriter, isStrict));\n  }\n}\n\nRewriter::Rewriter(jsg::Lock& js,\n    kj::ArrayPtr<UnregisteredElementOrDocumentHandlers> unregisteredHandlers,\n    kj::ArrayPtr<const char> encoding,\n    kj::Own<WritableStreamSink> inner)\n    : rewriter(buildRewriter(js, unregisteredHandlers, encoding, *this)),\n      externalMemoryAdjustment(js.getExternalMemoryAdjustment()),\n      inner(kj::mv(inner)),\n      ioContext(IoContext::current()),\n      maybeAsyncContext(jsg::AsyncContextFrame::currentRef(js)) {}\n\nnamespace {\n\n// The stack size floor enforced by kj. We could go lower,\n// but it'd always be increased to this anyway.\nconst size_t FIBER_STACK_SIZE = 1024 * 64;\n\nconst kj::FiberPool& getFiberPool() {\n  const static kj::FiberPool FIBER_POOL(FIBER_STACK_SIZE);\n  return FIBER_POOL;\n}\n\n}  // namespace\n\nkj::Promise<void> Rewriter::write(kj::ArrayPtr<const byte> buffer) {\n  KJ_ASSERT(maybeWaitScope == kj::none);\n  // Defer fiber creation until the event loop runs. If this promise is dropped synchronously\n  // (e.g. by Canceler::cancel() during PumpToReader destruction), no fiber is created, avoiding\n  // a KJ assertion failure when destroying an unfired fiber. Once the event loop processes this,\n  // the fiber is created and immediately fires (armDepthFirst), so cancellation works normally.\n  return kj::evalLater([this, buffer]() {\n    return getFiberPool().startFiber([this, buffer](kj::WaitScope& scope) {\n      maybeWaitScope = scope;\n      if (!isPoisoned()) {\n        // Cannot use `check()` because `finishWrite()` implements the error path.\n        auto rc = lol_html_rewriter_write(rewriter, buffer.asChars().begin(), buffer.size());\n        tryHandleCancellation(rc);\n        if (rc == -1) {\n          maybePoison(getLastError());\n        }\n      }\n      return finishWrite();\n    });\n  });\n}\n\nkj::Promise<void> Rewriter::write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) {\n  KJ_ASSERT(maybeWaitScope == kj::none);\n  return kj::evalLater([this, pieces]() {\n    return getFiberPool().startFiber([this, pieces](kj::WaitScope& scope) {\n      maybeWaitScope = scope;\n      if (!isPoisoned()) {\n        for (auto bytes: pieces) {\n          auto chars = bytes.asChars();\n          // Cannot use `check()` because `finishWrite()` implements the error path.\n          auto rc = lol_html_rewriter_write(rewriter, chars.begin(), chars.size());\n          tryHandleCancellation(rc);\n          if (rc == -1) {\n            maybePoison(getLastError());\n            // A handler threw an exception; stop calling `lol_html_rewriter_write()`.\n            break;\n          }\n        }\n      }\n      return finishWrite();\n    });\n  });\n}\n\nkj::Promise<void> Rewriter::end() {\n  KJ_ASSERT(maybeWaitScope == kj::none);\n  return kj::evalLater([this]() {\n    return getFiberPool().startFiber([this](kj::WaitScope& scope) {\n      maybeWaitScope = scope;\n      if (!isPoisoned()) {\n        // Cannot use `check()` because `finishWrite()` implements the error path.\n        auto rc = lol_html_rewriter_end(rewriter);\n        tryHandleCancellation(rc);\n        if (rc == -1) {\n          maybePoison(getLastError());\n        }\n      }\n      return finishWrite().then([this]() { return inner->end(); });\n    });\n  });\n}\n\nvoid Rewriter::abort(kj::Exception reason) {\n  // End the rewriter and forward the error to the wrapped output stream.\n  maybeException = kj::cp(reason);\n\n  inner->abort(kj::mv(reason));\n}\n\nkj::Promise<void> Rewriter::finishWrite() {\n  maybeWaitScope = kj::none;\n  return flushWrite();\n}\n\nkj::Promise<void> Rewriter::flushWrite() {\n  KJ_ASSERT(!flushing);\n\n  if (!outputBuffer.empty()) {\n    KJ_DEFER({\n      externalMemoryAdjustment.set(0);\n      outputBuffer.clear();\n      flushing = false;\n    });\n\n    flushing = true;\n    co_await inner->write(outputBuffer);\n  }\n\n  KJ_IF_SOME(exception, maybeException) {\n    inner->abort(kj::cp(exception));\n    kj::throwFatalException(kj::cp(exception));\n  }\n}\ntemplate <typename T, typename CType>\nlol_html_rewriter_directive_t Rewriter::thunk(CType* content, void* userdata) {\n  auto& registration = *reinterpret_cast<RegisteredHandler*>(userdata);\n  return registration.rewriter.thunkImpl<T>(content, registration);\n}\n\ntemplate <typename T, typename CType>\nlol_html_rewriter_directive_t Rewriter::thunkImpl(\n    CType* content, RegisteredHandler& registeredHandler) {\n  if (isPoisoned()) {\n    // Handlers disabled due to exception.\n    KJ_LOG(ERROR, \"poisoned rewriter should not be able to call handlers\");\n    return LOL_HTML_STOP;\n  }\n\n  try {\n    KJ_IF_SOME(exception, kj::runCatchingExceptions([&] {\n      // V8 has a thread local pointer that points to where the stack limit is on this thread which\n      // is tested for overflows when we enter any JS code. However since we're running in a fiber\n      // here, we're in an entirely different stack that V8 doesn't know about, so it gets confused\n      // and may think we've overflowed our stack. evalLater will run thunkPromise on the main stack\n      // to keep V8 from getting confused.\n      auto promise = kj::evalLater([&]() { return thunkPromise<T>(content, registeredHandler); });\n      promise.wait(KJ_ASSERT_NONNULL(maybeWaitScope));\n      flushWrite().wait(KJ_ASSERT_NONNULL(maybeWaitScope));\n    })) {\n      // Exception in handler. We need to abort the streaming parser, but can't do so just yet: we\n      // need to unwind the stack because we're probably still inside a cool_thing_rewriter_write().\n      // We can't unwind with an exception across the Rust/C++ boundary, so instead we'll keep this\n      // exception around and disable all later handlers.\n      maybePoison(kj::mv(exception));\n      return LOL_HTML_STOP;\n    }\n  } catch (kj::CanceledException) {\n    // The fiber is being canceled. Same as runCatchingExceptions, we need to abort the parser,\n    // but can't since we're still inside cool_thing_rewriter_write(). This isn't handled by\n    // runCatchingExceptions since CanceledException isn't a kj exception, and we wouldn't want\n    // runCatchingExceptions to handle it anyway. We set canceled to true and once we leave Rust,\n    // we rethrow it to properly cancel the fiber.\n    canceled = true;\n    return LOL_HTML_STOP;\n  }\n  return LOL_HTML_CONTINUE;\n}\n\nvoid Rewriter::removeEndTagHandler(RegisteredHandler& handler) {\n  auto size = registeredEndTagHandlers.size();\n  for (auto counter = size; counter != 0; --counter) {\n    auto idx = counter - 1;\n    if (registeredEndTagHandlers[idx].get() == &handler) {\n      // equivalent of `Vec::swap_remove` in Rust\n      if (counter != size) {\n        registeredEndTagHandlers[idx] = kj::mv(registeredEndTagHandlers[size - 1]);\n      }\n      registeredEndTagHandlers.removeLast();\n      break;\n    }\n  }\n}\n\ntemplate <typename T, typename CType>\nkj::Promise<void> Rewriter::thunkPromise(CType* content, RegisteredHandler& registeredHandler) {\n  return ioContext.run(\n      [this, content, &registeredHandler](Worker::Lock& lock) -> kj::Promise<void> {\n    // We enter the AsyncContextFrame that was current when the Rewriter was created\n    // (when transform() was called). If someone wants, instead, to use the context\n    // that was current when on(...) is called, the ElementHandler can use AsyncResource\n    // (or eventually the standard AsyncContext once that lands).\n    jsg::Lock& js = lock;\n    jsg::AsyncContextFrame::Scope asyncContextScope(js, maybeAsyncContext);\n    auto jsContent = js.alloc<T>(*content, *this);\n    auto scope = HTMLRewriter::TokenScope(jsContent);\n    auto value = registeredHandler.callback(js, kj::mv(jsContent));\n\n    if constexpr (kj::isSameType<T, EndTag>()) {\n      // TODO(someday): We can't unconditionally pop the top of `registeredEndTagHandlers`,\n      //   because that depends on https://github.com/cloudflare/lol-html/issues/110\n      //   being resolved. For now we let handles to end tag handlers tags live for the duration of\n      //   the response transformation, but eagerly release ones that we can.\n      //   In particular, note that `thunkPromise` is never called for implied end tags.\n      removeEndTagHandler(registeredHandler);\n    }\n\n    return value.attach(kj::mv(scope));\n  });\n}\n\nlol_html_streaming_handler_t Rewriter::registerReplacer(\n    jsg::Ref<ReadableStream> content, bool isHtml) {\n  auto replacer = kj::heap<RegisteredReplacer>(*this, isHtml, kj::mv(content));\n  auto userData = replacer.get();\n  registeredReplacers.insert(userData, kj::mv(replacer));\n\n  return {\n    .user_data = userData,\n    .write_all_callback = Rewriter::replacerThunk,\n    .drop_callback = Rewriter::removeRegisteredReplacer,\n  };\n}\n\n// Adapter that allows pumping a ReadableStream to a pre-established lol_html\n// streaming sink, named `sink`. Writes of arbitrary bytes and sizes are allowed,\n// but the content must be valid UTF-8 or lol_html will reject it.\nclass ReplacerStreamSink final: public WritableStreamSink {\n public:\n  ReplacerStreamSink(lol_html_streaming_sink_t* sink, bool isHtml): sink(sink), isHtml(isHtml) {}\n\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override KJ_WARN_UNUSED_RESULT {\n    auto err = lol_html_streaming_sink_write_utf8_chunk(\n        sink, buffer.asChars().begin(), buffer.size(), isHtml);\n    if (err != 0) {\n      return getLastError();\n    }\n\n    return kj::READY_NOW;\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {\n    for (auto bytes: pieces) {\n      auto err = lol_html_streaming_sink_write_utf8_chunk(\n          sink, bytes.asChars().begin(), bytes.size(), isHtml);\n      if (err != 0) {\n        return getLastError();\n      }\n    }\n\n    return kj::READY_NOW;\n  }\n\n  kj::Promise<void> end() override KJ_WARN_UNUSED_RESULT {\n    // Nothing specific needs to be done to tell lol_html we're done writing the stream\n    return kj::READY_NOW;\n  }\n\n  void abort(kj::Exception reason) override {\n    // Nothing specific needs to be done. The rewriter will be poisoned in replacerThunkImpl,\n    // and lol_html will bubble up the error through to write.\n  }\n\n private:\n  lol_html_streaming_sink_t* sink;\n  bool isHtml;\n};\n\nint Rewriter::replacerThunk(lol_html_streaming_sink_t* sink, void* userData) {\n  auto& registration = *reinterpret_cast<RegisteredReplacer*>(userData);\n  return registration.rewriter.replacerThunkImpl(sink, registration);\n}\n\nint Rewriter::replacerThunkImpl(\n    lol_html_streaming_sink_t* sink, RegisteredReplacer& registeredHandler) {\n  if (isPoisoned()) {\n    // Handlers disabled due to exception.\n    KJ_LOG(ERROR, \"poisoned rewriter should not be able to call handlers\");\n    return -1;\n  }\n\n  try {\n    KJ_IF_SOME(exception, kj::runCatchingExceptions([&] {\n      // V8 has a thread local pointer that points to where the stack limit is on this thread which\n      // is tested for overflows when we enter any JS code. However since we're running in a fiber\n      // here, we're in an entirely different stack that V8 doesn't know about, so it gets confused\n      // and may think we've overflowed our stack. evalLater will run thunkPromise on the main stack\n      // to keep V8 from getting confused.\n      auto promise = kj::evalLater([&]() { return replacerThunkPromise(sink, registeredHandler); });\n      promise.wait(KJ_ASSERT_NONNULL(maybeWaitScope));\n    })) {\n      // Exception in handler. We need to abort the streaming parser, but can't do so just yet: we\n      // need to unwind the stack because we're probably still inside a cool_thing_rewriter_write().\n      // We can't unwind with an exception across the Rust/C++ boundary, so instead we'll keep this\n      // exception around and disable all later handlers\n      maybePoison(kj::mv(exception));\n      return -1;\n    }\n  } catch (kj::CanceledException) {\n    // The fiber is being canceled. Same as runCatchingExceptions, we need to abort the parser,\n    // but can't since we're still inside cool_thing_rewriter_write(). This isn't handled by\n    // runCatchingExceptions since CanceledException isn't a kj exception, and we wouldn't want\n    // runCatchingExceptions to handle it anyway. We set canceled to true and once we leave Rust,\n    // we rethrow it to properly cancel the fiber.\n    canceled = true;\n    return -1;\n  }\n  return 0;\n}\n\nkj::Promise<void> Rewriter::replacerThunkPromise(\n    lol_html_streaming_sink_t* sink, RegisteredReplacer& registration) {\n  return ioContext.run([this, sink, &registration](Worker::Lock& lock) -> kj::Promise<void> {\n    jsg::AsyncContextFrame::Scope asyncContextScope(lock, maybeAsyncContext);\n\n    auto streamSink = kj::heap<ReplacerStreamSink>(sink, registration.isHtml);\n    return ioContext.waitForDeferredProxy(\n        registration.stream->pumpTo(lock, kj::mv(streamSink), true));\n  });\n}\n\nvoid Rewriter::removeRegisteredReplacer(void* userData) {\n  auto& registration = *reinterpret_cast<RegisteredReplacer*>(userData);\n  KJ_REQUIRE(registration.rewriter.registeredReplacers.erase(userData),\n      \"Tried to remove replacer that was not registered\");\n}\n\nvoid Rewriter::onEndTag(lol_html_element_t* element, ElementCallbackFunction&& callback) {\n  auto registeredHandler = Rewriter::RegisteredHandler{*this, kj::mv(callback)};\n  // NOTE: this gets freed in `thunkPromise` above.\n  // TODO(someday): this uses more memory than necessary for implied end tags, which lol-html\n  // doesn't actually call `thunk` on.  LOL HTML drops the handler after it finishes transforming\n  // the current element, but this code will keep it around until the entire HTML document is\n  // transformed. It would be nice to free it directly after the handler is used; unfortunately,\n  // this isn't trivial to do since we have no idea whether there's an end tag or not. The fix for\n  // this probably needs to happen in lol-html; see #110.\n  // WARNING: if we ever start reusing the same Rewriter for multiple documents,\n  // this will cause a memory leak!\n  auto& registeredHandlerPtr = registeredEndTagHandlers.add(kj::heap(kj::mv(registeredHandler)));\n  lol_html_element_clear_end_tag_handlers(element);\n  check(lol_html_element_add_end_tag_handler(\n      element, Rewriter::thunk<EndTag>, registeredHandlerPtr.get()));\n}\n\nvoid Rewriter::output(const char* buffer, size_t size, void* userdata) {\n  auto& rewriter = *reinterpret_cast<Rewriter*>(userdata);\n  rewriter.outputImpl(kj::asBytes(buffer, size));\n}\n\nvoid Rewriter::outputImpl(kj::ArrayPtr<const byte> buffer) {\n  if (isPoisoned()) {\n    // Handlers disabled due to exception or running in a destructor.\n    return;\n  }\n\n  KJ_ASSERT(!flushing);\n  externalMemoryAdjustment.adjust(buffer.size());\n  outputBuffer.addAll(buffer);\n}\n\n// =======================================================================================\n// HTMLRewriter::Token::ImplBase<CType>\n\ntemplate <typename CType>\nHTMLRewriter::Token::ImplBase<CType>::ImplBase(CType& element, Rewriter& rewriter)\n    : element(element),\n      rewriter(rewriter) {}\ntemplate <typename CType>\nHTMLRewriter::Token::ImplBase<CType>::~ImplBase() noexcept(false) {}\ntemplate <typename CType>\ntemplate <auto Func, auto StreamingFunc>\nvoid HTMLRewriter::Token::ImplBase<CType>::rewriteContentGeneric(\n    Content content, jsg::Optional<ContentOptions> options) {\n  auto isHtml = options.orDefault({}).html.orDefault(false);\n\n  KJ_SWITCH_ONEOF(content) {\n    KJ_CASE_ONEOF(stringContent, kj::String) {\n      check(Func(&element, stringContent.cStr(), stringContent.size(), isHtml));\n    }\n\n    KJ_CASE_ONEOF(streamContent, jsg::Ref<ReadableStream>) {\n      auto handler = rewriter.registerReplacer(kj::mv(streamContent), isHtml);\n      check(StreamingFunc(&element, &handler));\n    }\n\n    KJ_CASE_ONEOF(responseContent, jsg::Ref<Response>) {\n      KJ_IF_SOME(body, responseContent->getBody()) {\n        auto handler = rewriter.registerReplacer(kj::mv(body), isHtml);\n        check(StreamingFunc(&element, &handler));\n      }\n      // Otherwise if no body, there is no replacement to make\n    }\n  }\n}\n// =======================================================================================\n// Element\n\nElement::Element(CType& element, Rewriter& rewriter) {\n  impl.emplace(element, rewriter);\n}\n\nkj::String Element::getTagName() {\n  auto tagName = LolString(lol_html_element_tag_name_get(&checkToken(impl).element));\n  return kj::str(tagName.asChars());\n}\n\nvoid Element::setTagName(kj::String name) {\n  check(lol_html_element_tag_name_set(&checkToken(impl).element, name.cStr(), name.size()));\n}\n\nbool Element::getRemoved() {\n  return lol_html_element_is_removed(&checkToken(impl).element);\n}\n\nkj::StringPtr Element::getNamespaceURI() {\n  // lol-html returns a static C string, no need to handle its lifetime.\n  return lol_html_element_namespace_uri_get(&checkToken(impl).element);\n}\n\njsg::Ref<Element::AttributesIterator> Element::getAttributes(jsg::Lock& js) {\n  auto& implRef = checkToken(impl);\n\n  auto iter = LOL_HTML_OWN(attributes_iterator, lol_html_attributes_iterator_get(&implRef.element));\n\n  auto jsIter = js.alloc<Element::AttributesIterator>(kj::mv(iter));\n  implRef.attributesIterators.add(jsIter.addRef());\n  return kj::mv(jsIter);\n}\n\nkj::Maybe<kj::String> Element::getAttribute(kj::String name) {\n  // NOTE: lol_html_element_get_attribute() returns NULL for both nonexistent attributes and for\n  //   errors, so we can't use check() here.\n  LolString attr(\n      lol_html_element_get_attribute(&checkToken(impl).element, name.cStr(), name.size()));\n  // TODO(perf): We could construct a v8::String directly here, saving a copy.\n  kj::Maybe kjAttr = attr.asKjString();\n  if (kjAttr != kj::none) {\n    return kj::mv(kjAttr);\n  }\n\n  KJ_IF_SOME(exception, tryGetLastError()) {\n    kj::throwFatalException(kj::mv(exception));\n  }\n\n  // No error, just doesn't exist.\n  return kj::none;\n}\n\nbool Element::hasAttribute(kj::String name) {\n  return !!check(\n      lol_html_element_has_attribute(&checkToken(impl).element, name.cStr(), name.size()));\n}\n\njsg::Ref<Element> Element::setAttribute(kj::String name, kj::String value) {\n  auto& implRef = checkToken(impl);\n  check(lol_html_element_set_attribute(\n      &implRef.element, name.cStr(), name.size(), value.cStr(), value.size()));\n\n  // Mutating attributes may cause lol-html's internal Vec to reallocate, invalidating\n  // any live iterators' pointers. We must invalidate all outstanding iterators.\n  for (auto& iter: implRef.attributesIterators) {\n    iter->invalidate();\n  }\n\n  return JSG_THIS;\n}\n\njsg::Ref<Element> Element::removeAttribute(kj::String name) {\n  auto& implRef = checkToken(impl);\n  check(lol_html_element_remove_attribute(&implRef.element, name.cStr(), name.size()));\n\n  // Removing attributes may shift elements in lol-html's internal Vec (via retain()),\n  // invalidating any live iterators' pointers.\n  for (auto& iter: implRef.attributesIterators) {\n    iter->invalidate();\n  }\n\n  return JSG_THIS;\n}\n\nnamespace {\nkj::String unwrapContent(Content content) {\n  return kj::mv(JSG_REQUIRE_NONNULL(content.tryGet<kj::String>(), TypeError,\n      \"Replacing content in HTML comments using a ReadableStream or Response object is not \"\n      \"implemented. You must provide a string.\"));\n}\n}  // namespace\n\njsg::Ref<Element> Element::before(Content content, jsg::Optional<ContentOptions> options) {\n  checkToken(impl)\n      .rewriteContentGeneric<lol_html_element_before, lol_html_element_streaming_before>(\n          kj::mv(content), options);\n  return JSG_THIS;\n}\n\njsg::Ref<Element> Element::after(Content content, jsg::Optional<ContentOptions> options) {\n  checkToken(impl).rewriteContentGeneric<lol_html_element_after, lol_html_element_streaming_after>(\n      kj::mv(content), options);\n  return JSG_THIS;\n}\n\njsg::Ref<Element> Element::prepend(Content content, jsg::Optional<ContentOptions> options) {\n  checkToken(impl)\n      .rewriteContentGeneric<lol_html_element_prepend, lol_html_element_streaming_prepend>(\n          kj::mv(content), options);\n  return JSG_THIS;\n}\n\njsg::Ref<Element> Element::append(Content content, jsg::Optional<ContentOptions> options) {\n  checkToken(impl)\n      .rewriteContentGeneric<lol_html_element_append, lol_html_element_streaming_append>(\n          kj::mv(content), options);\n  return JSG_THIS;\n}\n\njsg::Ref<Element> Element::replace(Content content, jsg::Optional<ContentOptions> options) {\n  checkToken(impl)\n      .rewriteContentGeneric<lol_html_element_replace, lol_html_element_streaming_replace>(\n          kj::mv(content), options);\n  return JSG_THIS;\n}\n\njsg::Ref<Element> Element::setInnerContent(Content content, jsg::Optional<ContentOptions> options) {\n  checkToken(impl)\n      .rewriteContentGeneric<lol_html_element_set_inner_content,\n          lol_html_element_streaming_set_inner_content>(kj::mv(content), options);\n  return JSG_THIS;\n}\n\njsg::Ref<Element> Element::remove() {\n  lol_html_element_remove(&checkToken(impl).element);\n  return JSG_THIS;\n}\n\njsg::Ref<Element> Element::removeAndKeepContent() {\n  lol_html_element_remove_and_keep_content(&checkToken(impl).element);\n  return JSG_THIS;\n}\n\nvoid Element::onEndTag(ElementCallbackFunction&& callback) {\n  auto& knownImpl = checkToken(impl);\n  knownImpl.rewriter.onEndTag(&knownImpl.element, kj::mv(callback));\n}\n\nEndTag::EndTag(CType& endTag, Rewriter& rewriter) {\n  impl.emplace(endTag, rewriter);\n}\n\nvoid EndTag::htmlContentScopeEnd() {\n  impl = kj::none;\n}\n\nkj::String EndTag::getName() {\n  auto text = LolString(lol_html_end_tag_name_get(&checkToken(impl).element));\n  return kj::str(text.asChars());\n}\n\nvoid EndTag::setName(kj::String text) {\n  check(lol_html_end_tag_name_set(&checkToken(impl).element, text.cStr(), text.size()));\n}\n\njsg::Ref<EndTag> EndTag::before(Content content, jsg::Optional<ContentOptions> options) {\n  checkToken(impl)\n      .rewriteContentGeneric<lol_html_end_tag_before, lol_html_end_tag_streaming_before>(\n          kj::mv(content), kj::mv(options));\n  return JSG_THIS;\n}\n\njsg::Ref<EndTag> EndTag::after(Content content, jsg::Optional<ContentOptions> options) {\n  checkToken(impl).rewriteContentGeneric<lol_html_end_tag_after, lol_html_end_tag_streaming_after>(\n      kj::mv(content), kj::mv(options));\n  return JSG_THIS;\n}\n\njsg::Ref<EndTag> EndTag::remove() {\n  lol_html_end_tag_remove(&checkToken(impl).element);\n  return JSG_THIS;\n}\n\nvoid Element::htmlContentScopeEnd() {\n  impl = kj::none;\n}\n\nElement::Impl::~Impl() noexcept(false) {\n  for (auto& jsIter: attributesIterators) {\n    static_cast<HTMLRewriter::Token&>(*jsIter).htmlContentScopeEnd();\n  }\n}\n\n// =======================================================================================\n// Element::AttributesIterator\n\nElement::AttributesIterator::AttributesIterator(kj::Own<CType> iter): impl(kj::mv(iter)) {}\n\njsg::Ref<Element::AttributesIterator> Element::AttributesIterator::self() {\n  return JSG_THIS;\n}\n\nElement::AttributesIterator::Next Element::AttributesIterator::next() {\n  // If the element's attributes were modified (via setAttribute/removeAttribute) while this\n  // iterator was live, the underlying lol-html iterator holds stale pointers into a potentially\n  // reallocated Vec. Continuing to iterate would be a use-after-free.\n  JSG_REQUIRE(!mutatedDuringIteration, Error,\n      \"The attributes of this element have been modified during iteration. \"\n      \"You must create a new iterator after modifying attributes.\");\n\n  // NOTE: lol_html_attribute_t doesn't need to be freed.\n  auto* attribute = lol_html_attributes_iterator_next(checkToken(impl));\n  if (attribute == nullptr) {\n    // End of iteration.\n    // TODO(someday): Eagerly deallocate. Can't seem to nullify the Own without also nullifying the\n    //   enclosing Maybe, however.\n    return {true, kj::none};\n  }\n\n  auto name = LolString(lol_html_attribute_name_get(attribute));\n  auto value = LolString(lol_html_attribute_value_get(attribute));\n\n  return {false, kj::arr(kj::str(name.asChars()), kj::str(value.asChars()))};\n}\n\nvoid Element::AttributesIterator::invalidate() {\n  mutatedDuringIteration = true;\n  // Also release the underlying lol-html iterator since it's no longer safe to use.\n  impl = kj::none;\n}\n\nvoid Element::AttributesIterator::htmlContentScopeEnd() {\n  // Clear the mutation flag so that after scope end, the \"content token is no longer valid\"\n  // error (from checkToken) takes precedence over the mutation error.\n  mutatedDuringIteration = false;\n  impl = kj::none;\n}\n\n// =======================================================================================\n// Comment\n\nComment::Comment(CType& comment, Rewriter&): impl(comment) {}\n\nkj::String Comment::getText() {\n  auto text = LolString(lol_html_comment_text_get(&checkToken(impl)));\n  return kj::str(text.asChars());\n}\n\nvoid Comment::setText(kj::String text) {\n  check(lol_html_comment_text_set(&checkToken(impl), text.cStr(), text.size()));\n}\n\nbool Comment::getRemoved() {\n  // NOTE: No error checking seems required by this function -- it returns a bool directly.\n  return lol_html_comment_is_removed(&checkToken(impl));\n}\n\njsg::Ref<Comment> Comment::before(Content content, jsg::Optional<ContentOptions> options) {\n  // TODO(someday): If lol-html adds support for streaming replacements for comments, this\n  // function will need to be updated.\n  auto stringContent = unwrapContent(kj::mv(content));\n  check(lol_html_comment_before(&checkToken(impl), stringContent.cStr(), stringContent.size(),\n      options.orDefault({}).html.orDefault(false)));\n\n  return JSG_THIS;\n}\n\njsg::Ref<Comment> Comment::after(Content content, jsg::Optional<ContentOptions> options) {\n  // TODO(someday): If lol-html adds support for streaming replacements for comments, this\n  // function will need to be updated.\n  auto stringContent = unwrapContent(kj::mv(content));\n  check(lol_html_comment_after(&checkToken(impl), stringContent.cStr(), stringContent.size(),\n      options.orDefault({}).html.orDefault(false)));\n\n  return JSG_THIS;\n}\n\njsg::Ref<Comment> Comment::replace(Content content, jsg::Optional<ContentOptions> options) {\n  // TODO(someday): If lol-html adds support for streaming replacements for comments, this\n  // function will need to be updated.\n  auto stringContent = unwrapContent(kj::mv(content));\n  check(lol_html_comment_replace(&checkToken(impl), stringContent.cStr(), stringContent.size(),\n      options.orDefault({}).html.orDefault(false)));\n\n  return JSG_THIS;\n}\n\njsg::Ref<Comment> Comment::remove() {\n  lol_html_comment_remove(&checkToken(impl));\n\n  return JSG_THIS;\n}\n\nvoid Comment::htmlContentScopeEnd() {\n  impl = kj::none;\n}\n\n// =======================================================================================\n// Text\n\nText::Text(CType& text, Rewriter& rewriter) {\n  impl.emplace(text, rewriter);\n}\n\nkj::String Text::getText() {\n  auto content = lol_html_text_chunk_content_get(&checkToken(impl).element);\n  return kj::heapString(content.data, content.len);\n}\n\nbool Text::getLastInTextNode() {\n  // NOTE: No error checking seems required by this function -- it returns a bool directly.\n  return lol_html_text_chunk_is_last_in_text_node(&checkToken(impl).element);\n}\n\nbool Text::getRemoved() {\n  // NOTE: No error checking seems required by this function -- it returns a bool directly.\n  return lol_html_text_chunk_is_removed(&checkToken(impl).element);\n}\n\njsg::Ref<Text> Text::before(Content content, jsg::Optional<ContentOptions> options) {\n  checkToken(impl)\n      .rewriteContentGeneric<lol_html_text_chunk_before, lol_html_text_chunk_streaming_before>(\n          kj::mv(content), kj::mv(options));\n  return JSG_THIS;\n}\n\njsg::Ref<Text> Text::after(Content content, jsg::Optional<ContentOptions> options) {\n  checkToken(impl)\n      .rewriteContentGeneric<lol_html_text_chunk_after, lol_html_text_chunk_streaming_after>(\n          kj::mv(content), kj::mv(options));\n  return JSG_THIS;\n}\n\njsg::Ref<Text> Text::replace(Content content, jsg::Optional<ContentOptions> options) {\n  checkToken(impl)\n      .rewriteContentGeneric<lol_html_text_chunk_replace, lol_html_text_chunk_streaming_replace>(\n          kj::mv(content), kj::mv(options));\n  return JSG_THIS;\n}\n\njsg::Ref<Text> Text::remove() {\n  lol_html_text_chunk_remove(&checkToken(impl).element);\n\n  return JSG_THIS;\n}\n\nvoid Text::htmlContentScopeEnd() {\n  impl = kj::none;\n}\n\n// =======================================================================================\n// Doctype\n\nDoctype::Doctype(CType& doctype, Rewriter&): impl(doctype) {}\n\nkj::Maybe<kj::String> Doctype::getName() {\n  LolString name(lol_html_doctype_name_get(&checkToken(impl)));\n  return name.asKjString();\n}\n\nkj::Maybe<kj::String> Doctype::getPublicId() {\n  LolString publicId(lol_html_doctype_public_id_get(&checkToken(impl)));\n  return publicId.asKjString();\n}\n\nkj::Maybe<kj::String> Doctype::getSystemId() {\n  LolString systemId(lol_html_doctype_system_id_get(&checkToken(impl)));\n  return systemId.asKjString();\n}\n\nvoid Doctype::htmlContentScopeEnd() {\n  impl = kj::none;\n}\n\n// =======================================================================================\n// DocumentEnd\n\nDocumentEnd::DocumentEnd(CType& documentEnd, Rewriter&): impl(documentEnd) {}\n\njsg::Ref<DocumentEnd> DocumentEnd::append(Content content, jsg::Optional<ContentOptions> options) {\n  // TODO(someday): If lol-html adds support for streaming replacements for the document end,\n  // this function will need to be updated.\n  auto stringContent = unwrapContent(kj::mv(content));\n  check(lol_html_doc_end_append(&checkToken(impl), stringContent.cStr(), stringContent.size(),\n      options.orDefault({}).html.orDefault(false)));\n\n  return JSG_THIS;\n}\n\nvoid DocumentEnd::htmlContentScopeEnd() {\n  impl = kj::none;\n}\n\n// =======================================================================================\n// HTMLRewriter\n\nstruct HTMLRewriter::Impl {\n  // The list of handlers added to this builder.\n  kj::Vector<UnregisteredElementOrDocumentHandlers> unregisteredHandlers;\n  // TODO(perf): It'd be nice to eagerly register handlers on the native builder object. However,\n  //   currently lol-html rewriters are inextricably linked to the builders which created them,\n  //   and this has concurrency and reentrancy ramifications: two rewriters built from the same\n  //   builder require synchronization to access safely, and their callbacks must not use the\n  //   builder which created them, lest the process deadlock.\n  //\n  //   In the meantime, we keep this list of handlers around and \"replay\" their registration, in\n  //   order, on the builder object that we create inside of .transform().\n\n  JSG_MEMORY_INFO(HTMLRewriter::Impl) {\n    for (const auto& handlers: unregisteredHandlers) {\n      KJ_SWITCH_ONEOF(handlers) {\n        KJ_CASE_ONEOF(h, UnregisteredElementHandlers) {\n          tracker.trackField(nullptr, h);\n        }\n        KJ_CASE_ONEOF(h, UnregisteredDocumentHandlers) {\n          tracker.trackField(nullptr, h);\n        }\n      }\n    }\n  }\n};\n\nHTMLRewriter::HTMLRewriter(): impl(kj::heap<Impl>()) {}\nHTMLRewriter::~HTMLRewriter() noexcept(false) {}\n\nvoid HTMLRewriter::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"impl\", impl);\n}\n\njsg::Ref<HTMLRewriter> HTMLRewriter::constructor(jsg::Lock& js) {\n  return js.alloc<HTMLRewriter>();\n}\n\njsg::Ref<HTMLRewriter> HTMLRewriter::on(\n    kj::String stringSelector, ElementContentHandlers&& handlers) {\n  kj::Own<lol_html_Selector> selector =\n      LOL_HTML_OWN(selector, lol_html_selector_parse(stringSelector.cStr(), stringSelector.size()));\n\n  impl->unregisteredHandlers.add(UnregisteredElementHandlers{\n    kj::mv(selector), kj::mv(handlers.element), kj::mv(handlers.comments), kj::mv(handlers.text)});\n\n  return JSG_THIS;\n}\n\njsg::Ref<HTMLRewriter> HTMLRewriter::onDocument(DocumentContentHandlers&& handlers) {\n  impl->unregisteredHandlers.add(UnregisteredDocumentHandlers{kj::mv(handlers.doctype),\n    kj::mv(handlers.comments), kj::mv(handlers.text), kj::mv(handlers.end)});\n\n  return JSG_THIS;\n}\n\njsg::Ref<Response> HTMLRewriter::transform(jsg::Lock& js, jsg::Ref<Response> response) {\n\n  JSG_REQUIRE(response->getType() != \"error\"_kj, TypeError,\n      \"HTMLRewriter cannot transform an error response\");\n\n  auto maybeInput = response->getBody();\n\n  if (maybeInput == kj::none) {\n    // That was easy!\n    return kj::mv(response);\n  }\n\n  auto& ioContext = IoContext::current();\n\n  auto pipe = newIdentityPipe();\n  response = Response::constructor(\n      js, kj::Maybe(js.alloc<ReadableStream>(ioContext, kj::mv(pipe.in))), kj::mv(response));\n\n  kj::String ownContentType;\n  kj::String encoding = kj::str(\"utf-8\");\n  KJ_IF_SOME(contentType,\n      response->getHeaders(js)->getCommon(js, capnp::CommonHeaderName::CONTENT_TYPE)) {\n    // TODO(cleanup): readContentTypeParameter can be replaced with using\n    // workerd/util/mimetype.h directly.\n    KJ_IF_SOME(charset, readContentTypeParameter(contentType, \"charset\")) {\n      ownContentType = kj::mv(contentType);\n      encoding = kj::mv(charset);\n    }\n  }\n\n  auto rewriter = kj::heap<Rewriter>(js, impl->unregisteredHandlers, encoding, kj::mv(pipe.out));\n\n  // NOTE: Avoid throwing any exceptions after initiating the pump below. This makes\n  //   the input response object disturbed (response.bodyUsed === true), which should only happen\n  //   after we know that nothing else (like invalid encoding) could cause an exception.\n\n  // Drive and flush the parser asynchronously.\n  ioContext.addTask(\n      ioContext\n          .waitForDeferredProxy(KJ_ASSERT_NONNULL(maybeInput)->pumpTo(js, kj::mv(rewriter), true))\n          .catch_([](kj::Exception&& e) {\n    // Errors in pumpTo() are already propagated to the destination stream. We don't want to\n    // throw them from here since it'll cause an uncaught exception to be reported via taskFailed(),\n    // which would poison the IoContext even though the application may have handled the error.\n  }));\n\n  // TODO(soon): EW-2025 Make Rewriter a proper wrapper object and put it in hidden property on the\n  //   response so the GC can find the handlers which Rewriter co-owns.\n  return kj::mv(response);\n}\n\nvoid HTMLRewriter::visitForGc(jsg::GcVisitor& visitor) {\n  for (auto& handlers: impl->unregisteredHandlers) {\n    KJ_SWITCH_ONEOF(handlers) {\n      KJ_CASE_ONEOF(elementHandlers, UnregisteredElementHandlers) {\n        visitor.visit(elementHandlers);\n      }\n      KJ_CASE_ONEOF(documentHandlers, UnregisteredDocumentHandlers) {\n        visitor.visit(documentHandlers);\n      }\n    }\n  }\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/html-rewriter.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/api/http.h>\n#include <workerd/jsg/jsg.h>\n\n#include <v8.h>\n\nstruct lol_html_HtmlRewriterBuilder;\nstruct lol_html_HtmlRewriter;\nstruct lol_html_Doctype;\nstruct lol_html_DocumentEnd;\nstruct lol_html_Comment;\nstruct lol_html_TextChunk;\nstruct lol_html_Element;\nstruct lol_html_EndTag;\nstruct lol_html_AttributesIterator;\nstruct lol_html_Attribute;\n// Defined in lol_html.h, forward declarations mirrored here so we don't need the header.\n\nKJ_DECLARE_NON_POLYMORPHIC(lol_html_AttributesIterator);\n\nnamespace workerd::api {\n\nclass Element;\nclass EndTag;\nclass Comment;\nclass Text;\nclass Doctype;\nclass DocumentEnd;\n\n// =======================================================================================\n// HTMLRewriter\n\nclass HTMLRewriter: public jsg::Object {\n public:\n  class Token;\n  class TokenScope;\n\n  explicit HTMLRewriter();\n  ~HTMLRewriter() noexcept(false);\n  KJ_DISALLOW_COPY_AND_MOVE(HTMLRewriter);\n\n  static jsg::Ref<HTMLRewriter> constructor(jsg::Lock& js);\n\n  using ElementCallback = kj::Promise<void>(jsg::Ref<jsg::Object> element);\n  using ElementCallbackFunction = jsg::Function<ElementCallback>;\n\n  // A struct-like wrapper around element content handlers. I say struct-like, because we only use\n  // this wrapper as a convenience to help us access the three function properties that we expect\n  // to find. In reality, this is more like a \"callback interface\" in Web IDL terms, since we hang\n  // onto the original object so that we can use it as the `this` argument.\n  struct ElementContentHandlers {\n    jsg::Optional<ElementCallbackFunction> element;\n    jsg::Optional<ElementCallbackFunction> comments;\n    jsg::Optional<ElementCallbackFunction> text;\n\n    JSG_STRUCT(element, comments, text);\n\n    JSG_STRUCT_TS_OVERRIDE({\n      element?(element: Element): void | Promise<void>;\n      comments?(comment: Comment): void | Promise<void>;\n      text?(element: Text): void | Promise<void>;\n    });\n    // Specify parameter types for callback functions\n  };\n\n  // A struct-like wrapper around document content handlers. See the doc comment on\n  // ElementContentHandlers for more information on its idiosyncrasies.\n  struct DocumentContentHandlers {\n    jsg::Optional<ElementCallbackFunction> doctype;\n    jsg::Optional<ElementCallbackFunction> comments;\n    jsg::Optional<ElementCallbackFunction> text;\n    jsg::Optional<ElementCallbackFunction> end;\n\n    JSG_STRUCT(doctype, comments, text, end);\n\n    JSG_STRUCT_TS_OVERRIDE({\n      doctype?(doctype: Doctype): void | Promise<void>;\n      comments?(comment: Comment): void | Promise<void>;\n      text?(text: Text): void | Promise<void>;\n      end?(end: DocumentEnd): void | Promise<void>;\n    });\n    // Specify parameter types for callback functions\n  };\n\n  jsg::Ref<HTMLRewriter> on(kj::String selector, ElementContentHandlers&& handlers);\n  jsg::Ref<HTMLRewriter> onDocument(DocumentContentHandlers&& handlers);\n\n  // Register element or doctype content handlers. `handlers` must be unwrappable into a\n  // ElementContentHandlers or DocumentContentHandlers struct, respectively. We take it as a\n  // v8::Object so that we can use it as the `this` argument for the function calls.\n\n  // Create a new Response object that is identical to the input response except that its body is\n  // the result of running the original body through this HTMLRewriter's rewriter. This\n  // function does not run the parser itself -- to drive the parser, you must read the transformed\n  // response body.\n  //\n  // Pre-condition: the input response body is not disturbed.\n  // Post-condition: the input response body is disturbed.\n  jsg::Ref<Response> transform(jsg::Lock& js, jsg::Ref<Response> response);\n\n  JSG_RESOURCE_TYPE(HTMLRewriter) {\n    JSG_METHOD(on);\n    JSG_METHOD(onDocument);\n    JSG_METHOD(transform);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  void visitForGc(jsg::GcVisitor& visitor);\n\n  struct Impl;\n  kj::Own<Impl> impl;\n};\n\n// A chunk of text or HTML which can be passed to content token mutation functions.\nusing Content = kj::OneOf<kj::String, jsg::Ref<ReadableStream>, jsg::Ref<Response>>;\n// TODO(soon): Support ReadableStream/Response types. Requires fibers or lol-html saveable state.\n\n// Options bag which can be passed to content token mutation functions.\nstruct ContentOptions {\n  // True if the Content being passed to the mutation function is HTML. If false, the content will\n  // be escaped (HTML entity-encoded).\n  jsg::Optional<bool> html;\n\n  JSG_STRUCT(html);\n};\n\nclass Rewriter;\n\n// =======================================================================================\n// HTML Content Tokens\n//\n// HTML content tokens represent individual chunks of HTML that scripts can manipulate. There are\n// four types: Element, Comment, Text, and Doctype. Each one wraps a corresponding lower-level\n// handle that lol-html passes to our callbacks. These lower-level handles are only valid during\n// the execution (scope) of the callback, which the JS wrapper objects can obviously outlive. To\n// cope with that, we have some machinery (HTMLRewriter::TokenScope/Token) which enforces\n// in-scope access.\n//\n// The Element content token also exposes an AttributesIterator type. This is not a content token\n// per se, but follows the same scoping rule.\n//\n// Note, when generating TypeScript types, definitions to include are collected before overrides are\n// applied. Because ElementCallbackFunction's parameter is always jsg::Ref<jsg::Object> and not the\n// token type, we would not include token types by default as these are only defined in overrides.\n// Therefore, we manually define each token type as a JSG_TS_ROOT(), so it gets visited when\n// collecting definitions.\n\nclass HTMLRewriter::Token: public jsg::Object {\n public:\n  virtual void htmlContentScopeEnd() = 0;\n\n  // Each Token subclass has an inner ImplBase subclass which holds a reference\n  // to the rewriter, and the actual underlying lol-html C API handle for the token.\n  template <typename CType>\n  struct ImplBase {\n    ImplBase(CType& element, Rewriter& rewriter);\n    KJ_DISALLOW_COPY_AND_MOVE(ImplBase);\n    ~ImplBase() noexcept(false);\n\n    // Dispatches calls to the underlying lol_html methods for each event (e.g. before, after, replace).\n    // Handles replacements of each supported type (string, ReadableStream, Body).\n    template <auto Func, auto StreamingFunc>\n    void rewriteContentGeneric(Content content, jsg::Optional<ContentOptions> options);\n\n    CType& element;\n    Rewriter& rewriter;\n  };\n};\n\nclass Element final: public HTMLRewriter::Token {\n public:\n  using CType = lol_html_Element;\n\n  explicit Element(CType& element, Rewriter& wrapper);\n\n  kj::String getTagName();\n  void setTagName(kj::String tagName);\n\n  class AttributesIterator;\n  jsg::Ref<AttributesIterator> getAttributes(jsg::Lock& js);\n\n  bool getRemoved();\n\n  kj::StringPtr getNamespaceURI();\n\n  kj::Maybe<kj::String> getAttribute(kj::String name);\n  bool hasAttribute(kj::String name);\n  jsg::Ref<Element> setAttribute(kj::String name, kj::String value);\n  jsg::Ref<Element> removeAttribute(kj::String name);\n\n  jsg::Ref<Element> before(Content content, jsg::Optional<ContentOptions> options);\n  jsg::Ref<Element> after(Content content, jsg::Optional<ContentOptions> options);\n\n  jsg::Ref<Element> prepend(Content content, jsg::Optional<ContentOptions> options);\n  jsg::Ref<Element> append(Content content, jsg::Optional<ContentOptions> options);\n\n  jsg::Ref<Element> replace(Content content, jsg::Optional<ContentOptions> options);\n  jsg::Ref<Element> setInnerContent(Content content, jsg::Optional<ContentOptions> options);\n\n  jsg::Ref<Element> remove();\n  jsg::Ref<Element> removeAndKeepContent();\n\n  void onEndTag(HTMLRewriter::ElementCallbackFunction&& callback);\n\n  JSG_RESOURCE_TYPE(Element) {\n    JSG_INSTANCE_PROPERTY(tagName, getTagName, setTagName);\n    JSG_READONLY_INSTANCE_PROPERTY(attributes, getAttributes);\n    JSG_READONLY_INSTANCE_PROPERTY(removed, getRemoved);\n    JSG_READONLY_INSTANCE_PROPERTY(namespaceURI, getNamespaceURI);\n\n    JSG_METHOD(getAttribute);\n    JSG_METHOD(hasAttribute);\n    JSG_METHOD(setAttribute);\n    JSG_METHOD(removeAttribute);\n    JSG_METHOD(before);\n    JSG_METHOD(after);\n    JSG_METHOD(prepend);\n    JSG_METHOD(append);\n    JSG_METHOD(replace);\n    JSG_METHOD(remove);\n    JSG_METHOD(removeAndKeepContent);\n    JSG_METHOD(setInnerContent);\n    JSG_METHOD(onEndTag);\n\n    JSG_TS_ROOT();\n    JSG_TS_OVERRIDE({\n         onEndTag(handler: (tag: EndTag) => void | Promise<void>): void;\n    });\n    // Specify parameter type for onEndTag callback function\n  }\n\n private:\n  struct Impl: public HTMLRewriter::Token::ImplBase<CType> {\n    using HTMLRewriter::Token::ImplBase<CType>::ImplBase;\n    ~Impl() noexcept(false);\n    kj::Vector<jsg::Ref<AttributesIterator>> attributesIterators;\n  };\n\n  kj::Maybe<Impl> impl;\n\n  void htmlContentScopeEnd() override;\n};\n\nclass Element::AttributesIterator final: public HTMLRewriter::Token {\n public:\n  using CType = lol_html_AttributesIterator;\n\n  // lol_html_AttributesIterator has the distinction of being valid only during a content handler\n  // execution scope AND also requiring manual deallocation, so this takes an Own<T> rather than T&.\n  explicit AttributesIterator(kj::Own<CType> iter);\n\n  struct Next {\n    bool done;\n    jsg::Optional<kj::Array<kj::String>> value;\n\n    JSG_STRUCT(done, value);\n  };\n\n  Next next();\n\n  // Called by Element when its attributes are mutated (setAttribute/removeAttribute),\n  // which invalidates the underlying lol-html iterator's pointers.\n  void invalidate();\n\n  jsg::Ref<AttributesIterator> self();\n\n  JSG_RESOURCE_TYPE(AttributesIterator) {\n    JSG_INHERIT_INTRINSIC(v8::kIteratorPrototype);\n    JSG_METHOD(next);\n    JSG_ITERABLE(self);\n  }\n\n private:\n  kj::Maybe<kj::Own<CType>> impl;\n  bool mutatedDuringIteration = false;\n\n  void htmlContentScopeEnd() override;\n};\n\nclass EndTag final: public HTMLRewriter::Token {\n public:\n  using CType = lol_html_EndTag;\n\n  explicit EndTag(CType& tag, Rewriter& rewriter);\n\n  kj::String getName();\n  void setName(kj::String);\n\n  jsg::Ref<EndTag> before(Content content, jsg::Optional<ContentOptions> options);\n  jsg::Ref<EndTag> after(Content content, jsg::Optional<ContentOptions> options);\n  jsg::Ref<EndTag> remove();\n\n  JSG_RESOURCE_TYPE(EndTag) {\n    JSG_INSTANCE_PROPERTY(name, getName, setName);\n\n    JSG_METHOD(before);\n    JSG_METHOD(after);\n    JSG_METHOD(remove);\n\n    JSG_TS_ROOT();\n  }\n\n private:\n  kj::Maybe<HTMLRewriter::Token::ImplBase<CType>> impl;\n\n  void htmlContentScopeEnd() override;\n};\n\nclass Comment final: public HTMLRewriter::Token {\n public:\n  using CType = lol_html_Comment;\n\n  explicit Comment(CType& comment, Rewriter&);\n\n  kj::String getText();\n  void setText(kj::String);\n\n  bool getRemoved();\n\n  jsg::Ref<Comment> before(Content content, jsg::Optional<ContentOptions> options);\n  jsg::Ref<Comment> after(Content content, jsg::Optional<ContentOptions> options);\n  jsg::Ref<Comment> replace(Content content, jsg::Optional<ContentOptions> options);\n  jsg::Ref<Comment> remove();\n\n  JSG_RESOURCE_TYPE(Comment) {\n    JSG_INSTANCE_PROPERTY(text, getText, setText);\n    JSG_READONLY_INSTANCE_PROPERTY(removed, getRemoved);\n\n    JSG_METHOD(before);\n    JSG_METHOD(after);\n    JSG_METHOD(replace);\n    JSG_METHOD(remove);\n\n    JSG_TS_ROOT();\n    JSG_TS_OVERRIDE({\n      before(content: string, options?: ContentOptions): Comment;\n      after(content: string, options?: ContentOptions): Comment;\n      replace(content: string, options?: ContentOptions): Comment;\n    });\n    // Require content to be a string\n  }\n\n private:\n  kj::Maybe<CType&> impl;\n\n  void htmlContentScopeEnd() override;\n};\n\nclass Text final: public HTMLRewriter::Token {\n public:\n  using CType = lol_html_TextChunk;\n\n  explicit Text(CType& text, Rewriter& rewriter);\n\n  kj::String getText();\n\n  bool getLastInTextNode();\n\n  bool getRemoved();\n\n  jsg::Ref<Text> before(Content content, jsg::Optional<ContentOptions> options);\n  jsg::Ref<Text> after(Content content, jsg::Optional<ContentOptions> options);\n  jsg::Ref<Text> replace(Content content, jsg::Optional<ContentOptions> options);\n  jsg::Ref<Text> remove();\n\n  JSG_RESOURCE_TYPE(Text) {\n    JSG_READONLY_INSTANCE_PROPERTY(text, getText);\n    JSG_READONLY_INSTANCE_PROPERTY(lastInTextNode, getLastInTextNode);\n    JSG_READONLY_INSTANCE_PROPERTY(removed, getRemoved);\n\n    JSG_METHOD(before);\n    JSG_METHOD(after);\n    JSG_METHOD(replace);\n    JSG_METHOD(remove);\n\n    JSG_TS_ROOT();\n  }\n\n private:\n  kj::Maybe<HTMLRewriter::Token::ImplBase<CType>> impl;\n\n  void htmlContentScopeEnd() override;\n};\n\nclass Doctype final: public HTMLRewriter::Token {\n public:\n  using CType = lol_html_Doctype;\n\n  explicit Doctype(CType& doctype, Rewriter&);\n\n  kj::Maybe<kj::String> getName();\n  kj::Maybe<kj::String> getPublicId();\n  kj::Maybe<kj::String> getSystemId();\n\n  JSG_RESOURCE_TYPE(Doctype) {\n    JSG_READONLY_INSTANCE_PROPERTY(name, getName);\n    JSG_READONLY_INSTANCE_PROPERTY(publicId, getPublicId);\n    JSG_READONLY_INSTANCE_PROPERTY(systemId, getSystemId);\n\n    JSG_TS_ROOT();\n  }\n\n private:\n  kj::Maybe<CType&> impl;\n\n  void htmlContentScopeEnd() override;\n};\n\nclass DocumentEnd final: public HTMLRewriter::Token {\n public:\n  using CType = lol_html_DocumentEnd;\n\n  explicit DocumentEnd(CType& documentEnd, Rewriter&);\n\n  jsg::Ref<DocumentEnd> append(Content content, jsg::Optional<ContentOptions> options);\n\n  JSG_RESOURCE_TYPE(DocumentEnd) {\n    JSG_METHOD(append);\n\n    JSG_TS_ROOT();\n    JSG_TS_OVERRIDE({\n      append(content: string, options?: ContentOptions): DocumentEnd;\n    });\n    // Require content to be a string\n  }\n\n private:\n  kj::Maybe<CType&> impl;\n\n  void htmlContentScopeEnd() override;\n};\n\n#define EW_HTML_REWRITER_ISOLATE_TYPES                                                             \\\n  api::ContentOptions, api::HTMLRewriter, api::HTMLRewriter::ElementContentHandlers,               \\\n      api::HTMLRewriter::DocumentContentHandlers, api::Doctype, api::Element, api::EndTag,         \\\n      api::Comment, api::Text, api::DocumentEnd, api::Element::AttributesIterator,                 \\\n      api::Element::AttributesIterator::Next\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/http.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"http.h\"\n\n#include \"data-url.h\"\n#include \"headers.h\"\n#include \"queue.h\"\n#include \"sockets.h\"\n#include \"streams/readable-source.h\"\n#include \"system-streams.h\"\n#include \"util.h\"\n#include \"worker-rpc.h\"\n#include \"workerd/jsg/jsvalue.h\"\n\n#include <workerd/io/features.h>\n#include <workerd/io/io-context.h>\n#include <workerd/jsg/ser.h>\n#include <workerd/jsg/url.h>\n#include <workerd/util/abortable.h>\n#include <workerd/util/entropy.h>\n#include <workerd/util/http-util.h>\n#include <workerd/util/mimetype.h>\n#include <workerd/util/own-util.h>\n#include <workerd/util/stream-utils.h>\n#include <workerd/util/strings.h>\n#include <workerd/util/thread-scopes.h>\n\n#include <capnp/compat/http-over-capnp.capnp.h>\n#include <kj/compat/url.h>\n#include <kj/encoding.h>\n#include <kj/memory.h>\n#include <kj/parse/char.h>\n\nnamespace workerd::api {\n\nnamespace {\nRequest::CacheMode getCacheModeFromName(kj::StringPtr value) {\n  if (value == \"no-store\") return Request::CacheMode::NOSTORE;\n  if (value == \"no-cache\") return Request::CacheMode::NOCACHE;\n  if (value == \"reload\") return Request::CacheMode::RELOAD;\n  JSG_FAIL_REQUIRE(TypeError, kj::str(\"Unsupported cache mode: \", value));\n}\n\njsg::Optional<kj::StringPtr> getCacheModeName(Request::CacheMode mode) {\n  switch (mode) {\n    case (Request::CacheMode::NONE):\n      return kj::none;\n    case (Request::CacheMode::NOCACHE):\n      return \"no-cache\"_kj;\n    case (Request::CacheMode::NOSTORE):\n      return \"no-store\"_kj;\n    case (Request::CacheMode::RELOAD):\n      return \"reload\"_kj;\n  }\n  KJ_UNREACHABLE;\n}\n\n}  // namespace\n\n// -----------------------------------------------------------------------------\n// serialization of headers\n//\n// http-over-capnp.capnp has a nice list of common header names, taken from the HTTP/2 standard.\n// We'll use it as an optimization.\n//\n// Note that using numeric IDs for headers implies we lose the original capitalization. However,\n// the JS Headers API doesn't actually give the application any way to observe the capitalization\n// of header names -- it only becomes relevant when serializing over HTTP/1.1. And at that point,\n// we are actually free to change the capitalization anyway, and we commonly do (KJ itself will\n// normalize capitalization of all registered headers, and http-over-capnp also loses\n// capitalization). So, it's certainly not worth it to try to keep the original capitalization\n// across serialization.\n\nBody::Buffer Body::Buffer::clone(jsg::Lock& js) {\n  Buffer result;\n  result.view = view;\n  KJ_SWITCH_ONEOF(ownBytes) {\n    KJ_CASE_ONEOF(refcounted, kj::Own<RefcountedBytes>) {\n      result.ownBytes = kj::addRef(*refcounted);\n    }\n    KJ_CASE_ONEOF(blob, jsg::Ref<Blob>) {\n      result.ownBytes = blob.addRef();\n    }\n  }\n  return result;\n}\n\nBody::ExtractedBody::ExtractedBody(\n    jsg::Ref<ReadableStream> stream, kj::Maybe<Buffer> buffer, kj::Maybe<kj::String> contentType)\n    : impl{kj::mv(stream), kj::mv(buffer)},\n      contentType(kj::mv(contentType)) {\n  // This check is in the constructor rather than `extractBody()`, because we often construct\n  // ExtractedBodys from ReadableStreams directly.\n  JSG_REQUIRE(!impl.stream->isDisturbed(), TypeError,\n      \"This ReadableStream is disturbed (has already been read from), and cannot \"\n      \"be used as a body.\");\n}\n\nBody::ExtractedBody Body::extractBody(jsg::Lock& js, Initializer init) {\n  Buffer buffer;\n  kj::Maybe<kj::String> contentType;\n\n  KJ_SWITCH_ONEOF(init) {\n    KJ_CASE_ONEOF(stream, jsg::Ref<ReadableStream>) {\n      return kj::mv(stream);\n    }\n    KJ_CASE_ONEOF(gen, jsg::AsyncGeneratorIgnoringStrings<jsg::Value>) {\n      return ReadableStream::from(js, gen.release());\n    }\n    KJ_CASE_ONEOF(text, kj::String) {\n      contentType = kj::str(MimeType::PLAINTEXT_STRING);\n      buffer = kj::mv(text);\n    }\n    KJ_CASE_ONEOF(bytes, kj::Array<byte>) {\n      // NOTE: The spec would have us create a copy of the input buffer here, but that would be a\n      //   sad waste of CPU and memory. This is technically a non-conformity that would allow a user\n      //   to construct a Body from a BufferSource and then later modify the BufferSource. However,\n      //   redirects cause body streams to be reconstructed from the original, possibly mutated,\n      //   buffer anyway, so this is unlikely to be a problem in practice.\n      buffer = kj::mv(bytes);\n    }\n    KJ_CASE_ONEOF(blob, jsg::Ref<Blob>) {\n      // Blobs always have a type, but it defaults to an empty string. We should NOT set\n      // Content-Type when the blob type is empty.\n      kj::StringPtr blobType = blob->getType();\n      if (blobType != nullptr) {\n        contentType = kj::str(blobType);\n      }\n      buffer = kj::mv(blob);\n    }\n    KJ_CASE_ONEOF(formData, jsg::Ref<FormData>) {\n      // Make an array of characters containing random hexadecimal digits.\n      //\n      // Note: Rather than use random hex digits, we could generate the hex digits by hashing the\n      //   form-data content itself! This would give us pleasing assurance that our boundary string\n      //   is not present in the content being divided. The downside is CPU usage if, say, a user\n      //   uploads an enormous file.\n      kj::FixedArray<kj::byte, 16> boundaryBuffer;\n      workerd::getEntropy(boundaryBuffer);\n      auto boundary = kj::encodeHex(boundaryBuffer);\n      contentType = MimeType::formDataWithBoundary(boundary);\n      buffer = formData->serialize(boundary);\n    }\n    KJ_CASE_ONEOF(searchParams, jsg::Ref<URLSearchParams>) {\n      contentType = MimeType::formUrlEncodedWithCharset(\"UTF-8\"_kj);\n      buffer = searchParams->toString();\n    }\n    KJ_CASE_ONEOF(searchParams, jsg::Ref<url::URLSearchParams>) {\n      contentType = MimeType::formUrlEncodedWithCharset(\"UTF-8\"_kj);\n      buffer = searchParams->toString();\n    }\n  }\n\n  auto buf = buffer.clone(js);\n\n  // We use streams::newMemorySource() here rather than newSystemStream() wrapping a\n  // newMemoryInputStream() because we do NOT want deferred proxying for bodies with\n  // V8 heap provenance. Specifically, the bufferCopy.view here, while being a kj::ArrayPtr,\n  // will typically be wrapping a v8::BackingStore, and we must ensure that is is consumed\n  // and destroyed while under the isolate lock, which means deferred proxying is not allowed.\n  auto rs = streams::newMemorySource(buf.view, kj::heap(kj::mv(buf.ownBytes)));\n\n  return {js.alloc<ReadableStream>(IoContext::current(), kj::mv(rs)), kj::mv(buffer),\n    kj::mv(contentType)};\n}\n\nBody::Body(jsg::Lock& js, kj::Maybe<ExtractedBody> init, Headers& headers)\n    : impl(kj::mv(init).map([&headers](auto i) -> Impl {\n        KJ_IF_SOME(ct, i.contentType) {\n          if (!headers.hasCommon(capnp::CommonHeaderName::CONTENT_TYPE)) {\n            // The spec allows the user to override the Content-Type, if they wish, so we only set\n            // the Content-Type if it doesn't already exist.\n            headers.setCommon(capnp::CommonHeaderName::CONTENT_TYPE, kj::mv(ct));\n          } else KJ_IF_SOME(parsed, MimeType::tryParse(ct)) {\n            if (MimeType::FORM_DATA == parsed) {\n              // Custom content-type request/responses with FormData are broken since they require a\n              // boundary parameter only the FormData serializer can provide. Let's warn if a dev does this.\n              IoContext::current().logWarning(\n                  \"A FormData body was provided with a custom Content-Type header when constructing \"\n                  \"a Request or Response object. This will prevent the recipient of the Request or \"\n                  \"Response from being able to parse the body. Consider omitting the custom \"\n                  \"Content-Type header.\");\n            }\n          }\n        }\n        return kj::mv(i.impl);\n      })),\n      headersRef(headers) {}\n\nkj::Maybe<Body::Buffer> Body::getBodyBuffer(jsg::Lock& js) {\n  KJ_IF_SOME(i, impl) {\n    KJ_IF_SOME(b, i.buffer) {\n      return b.clone(js);\n    }\n  }\n  return kj::none;\n}\n\nbool Body::canRewindBody() {\n  KJ_IF_SOME(i, impl) {\n    // We can only rewind buffer-backed bodies.\n    return i.buffer != kj::none;\n  }\n  // Null bodies are trivially \"rewindable\".\n  return true;\n}\n\nvoid Body::rewindBody(jsg::Lock& js) {\n  KJ_DASSERT(canRewindBody());\n\n  KJ_IF_SOME(i, impl) {\n    auto bufferCopy = KJ_ASSERT_NONNULL(i.buffer).clone(js);\n\n    // We use streams::newMemorySource() here rather than newSystemStream() wrapping a\n    // newMemoryInputStream() because we do NOT want deferred proxying for bodies with\n    // V8 heap provenance. Specifically, the bufferCopy.view here, while being a kj::ArrayPtr,\n    // will typically be wrapping a v8::BackingStore, and we must ensure that is is consumed\n    // and destroyed while under the isolate lock, which means deferred proxying is not allowed.\n    auto rs = streams::newMemorySource(bufferCopy.view, kj::heap(kj::mv(bufferCopy.ownBytes)));\n    i.stream = js.alloc<ReadableStream>(IoContext::current(), kj::mv(rs));\n  }\n}\n\nvoid Body::nullifyBody() {\n  impl = kj::none;\n}\n\nkj::Maybe<jsg::Ref<ReadableStream>> Body::getBody() {\n  KJ_IF_SOME(i, impl) {\n    return i.stream.addRef();\n  }\n  return kj::none;\n}\nbool Body::getBodyUsed() {\n  KJ_IF_SOME(i, impl) {\n    return i.stream->isDisturbed();\n  }\n  return false;\n}\njsg::Promise<jsg::BufferSource> Body::arrayBuffer(jsg::Lock& js) {\n  KJ_IF_SOME(i, impl) {\n    return js.evalNow([&] {\n      JSG_REQUIRE(!i.stream->isDisturbed(), TypeError,\n          \"Body has already been used. \"\n          \"It can only be used once. Use tee() first if you need to read it twice.\");\n      return i.stream->getController().readAllBytes(\n          js, IoContext::current().getLimitEnforcer().getBufferingLimit());\n    });\n  }\n\n  // If there's no body, we just return an empty array.\n  // See https://fetch.spec.whatwg.org/#concept-body-consume-body\n  auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, 0);\n  return js.resolvedPromise(jsg::BufferSource(js, kj::mv(backing)));\n}\n\njsg::Promise<jsg::BufferSource> Body::bytes(jsg::Lock& js) {\n  return arrayBuffer(js).then(js,\n      [](jsg::Lock& js, jsg::BufferSource data) { return data.getTypedView<v8::Uint8Array>(js); });\n}\n\njsg::Promise<kj::String> Body::text(jsg::Lock& js) {\n  KJ_IF_SOME(i, impl) {\n    return js.evalNow([&] {\n      JSG_REQUIRE(!i.stream->isDisturbed(), TypeError,\n          \"Body has already been used. \"\n          \"It can only be used once. Use tee() first if you need to read it twice.\");\n\n      // A common mistake is to call .text() on non-text content, e.g. because you're implementing a\n      // search-and-replace across your whole site and you forgot that it'll apply to images too.\n      // When running in the fiddle, let's warn the developer if they do this.\n      auto& context = IoContext::current();\n      if (context.hasWarningHandler()) {\n        KJ_IF_SOME(type, headersRef.getCommon(js, capnp::CommonHeaderName::CONTENT_TYPE)) {\n          maybeWarnIfNotText(js, type);\n        }\n      }\n\n      return i.stream->getController().readAllText(\n          js, context.getLimitEnforcer().getBufferingLimit());\n    });\n  }\n\n  // If there's no body, we just return an empty string.\n  // See https://fetch.spec.whatwg.org/#concept-body-consume-body\n  return js.resolvedPromise(kj::String());\n}\n\njsg::Promise<jsg::Ref<FormData>> Body::formData(jsg::Lock& js) {\n  auto formData = js.alloc<FormData>();\n\n  return js.evalNow([&] {\n    JSG_REQUIRE(!getBodyUsed(), TypeError,\n        \"Body has already been used. \"\n        \"It can only be used once. Use tee() first if you need to read it twice.\");\n\n    auto contentType =\n        JSG_REQUIRE_NONNULL(headersRef.getCommon(js, capnp::CommonHeaderName::CONTENT_TYPE),\n            TypeError, \"Parsing a Body as FormData requires a Content-Type header.\");\n\n    KJ_IF_SOME(i, impl) {\n      KJ_ASSERT(!i.stream->isDisturbed());\n      auto& context = IoContext::current();\n      return i.stream->getController()\n          .readAllText(js, context.getLimitEnforcer().getBufferingLimit())\n          .then(js,\n              [contentType = kj::mv(contentType), formData = kj::mv(formData)](\n                  auto& js, kj::String rawText) mutable {\n        formData->parse(js, kj::mv(rawText), contentType,\n            !FeatureFlags::get(js).getFormDataParserSupportsFiles());\n        return kj::mv(formData);\n      });\n    }\n\n    // Theoretically, we already know if this will throw: the empty string is a valid\n    // application/x-www-form-urlencoded body, but not multipart/form-data. However, best to let\n    // FormData::parse() make the decision, to keep the logic in one place.\n    formData->parse(\n        js, kj::String(), contentType, !FeatureFlags::get(js).getFormDataParserSupportsFiles());\n    return js.resolvedPromise(kj::mv(formData));\n  });\n}\n\njsg::Promise<jsg::Value> Body::json(jsg::Lock& js) {\n  return text(js).then(js, [](jsg::Lock& js, kj::String text) { return js.parseJson(text); });\n}\n\njsg::Promise<jsg::Ref<Blob>> Body::blob(jsg::Lock& js) {\n  return arrayBuffer(js).then(js, [this](jsg::Lock& js, jsg::BufferSource buffer) {\n    kj::String contentType = headersRef.getCommon(js, capnp::CommonHeaderName::CONTENT_TYPE)\n                                 .map([](auto&& b) -> kj::String {\n      return kj::mv(b);\n    }).orDefault(nullptr);\n\n    if (FeatureFlags::get(js).getBlobStandardMimeType()) {\n      contentType = MimeType::extract(contentType)\n                        .map([](MimeType&& mt) -> kj::String {\n        return mt.toString();\n      }).orDefault(nullptr);\n    }\n\n    return js.alloc<Blob>(js, kj::mv(buffer), kj::mv(contentType));\n  });\n}\n\nkj::Maybe<Body::ExtractedBody> Body::clone(jsg::Lock& js) {\n  KJ_IF_SOME(i, impl) {\n    auto branches = i.stream->tee(js);\n\n    i.stream = kj::mv(branches[0]);\n\n    return ExtractedBody{kj::mv(branches[1]), i.buffer.map([&](Buffer& b) { return b.clone(js); })};\n  }\n\n  return kj::none;\n}\n\n// =======================================================================================\n\njsg::Ref<Request> Request::coerce(\n    jsg::Lock& js, Request::Info input, jsg::Optional<Request::Initializer> init) {\n  return input.is<jsg::Ref<Request>>() && init == kj::none\n      ? kj::mv(input.get<jsg::Ref<Request>>())\n      : Request::constructor(js, kj::mv(input), kj::mv(init));\n}\n\njsg::Optional<kj::StringPtr> Request::getCache(jsg::Lock& js) {\n  return getCacheModeName(cacheMode);\n}\nRequest::CacheMode Request::getCacheMode() {\n  return cacheMode;\n}\n\njsg::Ref<Request> Request::constructor(\n    jsg::Lock& js, Request::Info input, jsg::Optional<Request::Initializer> init) {\n  kj::String url;\n  kj::HttpMethod method = kj::HttpMethod::GET;\n  kj::Maybe<jsg::Ref<Headers>> headers;\n  kj::Maybe<jsg::Ref<Fetcher>> fetcher;\n  kj::Maybe<jsg::Ref<AbortSignal>> signal;\n  CfProperty cf;\n  kj::Maybe<Body::ExtractedBody> body;\n  Redirect redirect = Redirect::FOLLOW;\n  CacheMode cacheMode = CacheMode::NONE;\n  Response_BodyEncoding responseBodyEncoding = Response_BodyEncoding::AUTO;\n\n  KJ_SWITCH_ONEOF(input) {\n    KJ_CASE_ONEOF(u, kj::String) {\n      url = kj::mv(u);\n\n      // TODO(later): This is rather unfortunate. The original implementation of\n      // this used non-standard URL parsing in violation of the spec. Unfortunately\n      // some users have come to depend on the non-standard behavior so we have to\n      // gate the standard behavior with a compat flag. Ideally we'd just be able to\n      // use the standard parsed URL throughout all of the code but in order to\n      // minimize the number of changes, we're going to ultimately end up double\n      // parsing (and serializing) the URL... here we parse it with the standard\n      // parser, reserialize it back into a string for the sake of not modifying\n      // the rest of the implementation. Fortunately the standard parser is fast\n      // but it would eventually be nice to eliminate the double parsing.\n      if (FeatureFlags::get(js).getFetchStandardUrl()) {\n        auto parsed = JSG_REQUIRE_NONNULL(\n            jsg::Url::tryParse(url.asPtr()), TypeError, kj::str(\"Invalid URL: \", url));\n        url = kj::str(parsed.getHref());\n      }\n    }\n    KJ_CASE_ONEOF(r, jsg::Ref<Request>) {\n      // Check to see if we're getting a new body from `init`. If so, we want to ignore `input`'s\n      // body. Note that this is technically non-conformant behavior, but the spec is broken:\n      // https://github.com/whatwg/fetch/issues/674\n      //\n      // TODO(cleanup): The body extraction logic is getting difficult to follow with the current\n      //   2-pass initialization we perform (first `input`, then `init`). It'd be nice to defer\n      //   checks like the one we're avoiding here until the very end, so the `init` pass has a\n      //   chance to override `input`'s members *before* we check if the body we're extracting is\n      //   disturbed.\n      bool ignoreInputBody = false;\n      KJ_IF_SOME(i, init) {\n        KJ_SWITCH_ONEOF(i) {\n          KJ_CASE_ONEOF(initDict, InitializerDict) {\n            if (initDict.body != kj::none) {\n              ignoreInputBody = true;\n            }\n          }\n          KJ_CASE_ONEOF(otherRequest, jsg::Ref<Request>) {\n            // If our initializer dictionary is another Request object, it will always have a `body`\n            // property. Even if it's null, we should treat it as an explicit body rewrite.\n            ignoreInputBody = true;\n          }\n        }\n      }\n\n      jsg::Ref<Request> oldRequest = kj::mv(r);\n      url = kj::str(oldRequest->getUrl());\n      method = oldRequest->method;\n      headers = js.alloc<Headers>(js, *oldRequest->headers);\n      cf = oldRequest->cf.deepClone(js);\n      if (!ignoreInputBody) {\n        JSG_REQUIRE(!oldRequest->getBodyUsed(), TypeError,\n            \"Cannot reconstruct a Request with a used body.\");\n        KJ_IF_SOME(oldJsBody, oldRequest->getBody()) {\n          // The stream spec says to \"create a proxy\" for the passed in readable, which it\n          // defines generically as creating a TransformStream and using pipeThrough to pass\n          // the input stream through, giving the TransformStream's readable to the extracted\n          // body below. We don't need to do that. Instead, we just create a new ReadableStream\n          // that takes over ownership of the internals of the given stream. The given stream\n          // is left in a locked/disturbed mode so that it can no longer be used.\n          body = Body::ExtractedBody((oldJsBody)->detach(js), oldRequest->getBodyBuffer(js));\n        }\n      }\n      cacheMode = oldRequest->getCacheMode();\n      redirect = oldRequest->getRedirectEnum();\n      fetcher = oldRequest->getFetcher();\n      signal = oldRequest->getSignal();\n    }\n  }\n\n  KJ_IF_SOME(i, init) {\n    KJ_SWITCH_ONEOF(i) {\n      KJ_CASE_ONEOF(initDict, InitializerDict) {\n        KJ_IF_SOME(integrity, initDict.integrity) {\n          JSG_REQUIRE(integrity.size() == 0, TypeError,\n              \"Subrequest integrity checking is not implemented. \"\n              \"The integrity option must be either undefined or an empty string.\");\n        }\n\n        KJ_IF_SOME(m, initDict.method) {\n          KJ_IF_SOME(code, tryParseHttpMethod(m)) {\n            method = code;\n          } else KJ_IF_SOME(code, kj::tryParseHttpMethod(toUpper(m))) {\n            method = code;\n            if (!FeatureFlags::get(js).getUpperCaseAllHttpMethods()) {\n              // This is actually the spec defined behavior. We're expected to only\n              // upper case get, post, put, delete, head, and options per the spec.\n              // Other methods, even if they would be recognized if they were uppercased,\n              // are supposed to be rejected.\n              // Refs: https://fetch.spec.whatwg.org/#methods\n              switch (method) {\n                case kj::HttpMethod::GET:\n                case kj::HttpMethod::POST:\n                case kj::HttpMethod::PUT:\n                case kj::HttpMethod::DELETE:\n                case kj::HttpMethod::HEAD:\n                case kj::HttpMethod::OPTIONS:\n                  break;\n                default:\n                  JSG_FAIL_REQUIRE(TypeError, kj::str(\"Invalid HTTP method string: \", m));\n              }\n            }\n          } else {\n            JSG_FAIL_REQUIRE(TypeError, kj::str(\"Invalid HTTP method string: \", m));\n          }\n        }\n\n        KJ_IF_SOME(h, initDict.headers) {\n          headers = Headers::constructor(js, kj::mv(h));\n        }\n\n        KJ_IF_SOME(p, initDict.fetcher) {\n          fetcher = kj::mv(p);\n        }\n\n        KJ_IF_SOME(s, initDict.signal) {\n          // Note that since this is an optional-maybe, `s` is type Maybe<AbortSignal>. It could\n          // be null. But that seems like what we want. If someone doesn't specify `signal` at all,\n          // they want to inherit the `signal` property from the original request. But if they\n          // explicitly say `signal: null`, they must want to drop the signal that was on the\n          // original request.\n          signal = kj::mv(s);\n          initDict.signal = kj::none;\n        }\n\n        KJ_IF_SOME(newCf, initDict.cf) {\n          // TODO(cleanup): When initDict.cf is updated to use jsg::JsRef instead\n          // of jsg::V8Ref, we can clean this up a bit further.\n          auto cloned = newCf.deepClone(js);\n          cf = CfProperty(js, jsg::JsObject(cloned.getHandle(js)));\n        }\n\n        KJ_IF_SOME(b, kj::mv(initDict.body).orDefault(kj::none)) {\n          body = Body::extractBody(js, kj::mv(b));\n          JSG_REQUIRE(method != kj::HttpMethod::GET && method != kj::HttpMethod::HEAD, TypeError,\n              \"Request with a GET or HEAD method cannot have a body.\");\n        }\n\n        KJ_IF_SOME(r, initDict.redirect) {\n          redirect = JSG_REQUIRE_NONNULL(Request::tryParseRedirect(r), TypeError,\n              \"Invalid redirect value, must be one of \\\"follow\\\" or \\\"manual\\\" (\\\"error\\\" won't be \"\n              \"implemented since it does not make sense at the edge; use \\\"manual\\\" and check the \"\n              \"response status code).\");\n        }\n\n        KJ_IF_SOME(c, initDict.cache) {\n          cacheMode = getCacheModeFromName(c);\n        }\n\n        KJ_IF_SOME(e, initDict.encodeResponseBody) {\n          if (e == \"manual\"_kj) {\n            responseBodyEncoding = Response_BodyEncoding::MANUAL;\n          } else if (e == \"automatic\"_kj) {\n            responseBodyEncoding = Response_BodyEncoding::AUTO;\n          } else {\n            JSG_FAIL_REQUIRE(TypeError, kj::str(\"encodeResponseBody: unexpected value: \", e));\n          }\n        }\n\n        if (initDict.method != kj::none || initDict.body != kj::none) {\n          // We modified at least one of the method or the body. In this case, we enforce the\n          // spec rule that GET/HEAD requests cannot have bodies. (On the other hand, if neither\n          // of these fields was modified, but the original Request object that we're rewriting\n          // already represented a GET/HEAD method with a body, we allow that to pass through.\n          // We support proxying such requests and rewriting their URL/headers/etc.)\n          JSG_REQUIRE(\n              (method != kj::HttpMethod::GET && method != kj::HttpMethod::HEAD) || body == kj::none,\n              TypeError, \"Request with a GET or HEAD method cannot have a body.\");\n        }\n      }\n      KJ_CASE_ONEOF(otherRequest, jsg::Ref<Request>) {\n        method = otherRequest->method;\n        redirect = otherRequest->redirect;\n        cacheMode = otherRequest->cacheMode;\n        responseBodyEncoding = otherRequest->responseBodyEncoding;\n        fetcher = otherRequest->getFetcher();\n        signal = otherRequest->getSignal();\n        headers = js.alloc<Headers>(js, *otherRequest->headers);\n        cf = otherRequest->cf.deepClone(js);\n        KJ_IF_SOME(b, otherRequest->getBody()) {\n          // Note that unlike when `input` (Request ctor's 1st parameter) is a Request object, here\n          // we're NOT stealing the other request's body, because we're supposed to pretend that the\n          // other request is just a dictionary.\n          body = Body::ExtractedBody(kj::mv(b));\n        }\n      }\n    }\n  }\n\n  if (headers == kj::none) {\n    headers = js.alloc<Headers>();\n  }\n\n  // TODO(conform): If `init` has a keepalive flag, pass it to the Body constructor.\n  return js.alloc<Request>(js, method, url, redirect, KJ_ASSERT_NONNULL(kj::mv(headers)),\n      kj::mv(fetcher), kj::mv(signal), kj::mv(cf), kj::mv(body), /* thisSignal */ kj::none,\n      cacheMode, responseBodyEncoding);\n}\n\njsg::Ref<Request> Request::clone(jsg::Lock& js) {\n  auto headersClone = headers->clone(js);\n\n  auto cfClone = cf.deepClone(js);\n  auto bodyClone = Body::clone(js);\n\n  return js.alloc<Request>(js, method, url, redirect, kj::mv(headersClone), getFetcher(),\n      /* signal */ getSignal(), kj::mv(cfClone), kj::mv(bodyClone), /* thisSignal */ kj::none,\n      cacheMode, responseBodyEncoding);\n}\n\nkj::StringPtr Request::getMethod() {\n  return kj::toCharSequence(method);\n}\nkj::StringPtr Request::getUrl() {\n  return url;\n}\njsg::Ref<Headers> Request::getHeaders(jsg::Lock& js) {\n  return headers.addRef();\n}\nkj::StringPtr Request::getRedirect() {\n  // TODO(cleanup): Web IDL enum <-> JS string conversion boilerplate is a common need and could be\n  //   factored out.\n\n  switch (redirect) {\n    case Redirect::FOLLOW:\n      return \"follow\";\n    case Redirect::MANUAL:\n      return \"manual\";\n  }\n\n  KJ_UNREACHABLE;\n}\nkj::Maybe<jsg::Ref<Fetcher>> Request::getFetcher() {\n  return fetcher.map([](jsg::Ref<Fetcher>& f) { return f.addRef(); });\n}\nkj::Maybe<jsg::Ref<AbortSignal>> Request::getSignal() {\n  return signal.map([](jsg::Ref<AbortSignal>& s) { return s.addRef(); });\n}\n\njsg::Optional<jsg::JsObject> Request::getCf(jsg::Lock& js) {\n  return cf.get(js);\n}\n\n// If signal is given, getThisSignal returns a reference to it.\n// Otherwise, we lazily create a new never-aborts AbortSignal that will not\n// be used for anything because the spec wills it so.\n// Note: To be pedantic, the spec actually calls for us to create a\n// second AbortSignal in addition to the one being passed in, but\n// that's a bit silly and unnecessary.\n// The name \"thisSignal\" is derived from the fetch spec, which draws a\n// distinction between the \"signal\" and \"this' signal\".\njsg::Ref<AbortSignal> Request::getThisSignal(jsg::Lock& js) {\n  KJ_IF_SOME(s, signal) {\n    return s.addRef();\n  }\n  KJ_IF_SOME(s, thisSignal) {\n    return s.addRef();\n  }\n  auto newSignal = js.alloc<AbortSignal>(kj::none, kj::none, AbortSignal::Flag::NEVER_ABORTS);\n  thisSignal = newSignal.addRef();\n  return newSignal;\n}\n\nvoid Request::clearSignalIfIgnoredForSubrequest(jsg::Lock& js) {\n  KJ_IF_SOME(s, signal) {\n    if (s->isIgnoredForSubrequests(js)) {\n      signal = kj::none;\n    }\n  }\n}\n\nkj::Maybe<Request::Redirect> Request::tryParseRedirect(kj::StringPtr redirect) {\n  if (strcasecmp(redirect.cStr(), \"follow\") == 0) {\n    return Redirect::FOLLOW;\n  }\n  if (strcasecmp(redirect.cStr(), \"manual\") == 0) {\n    return Redirect::MANUAL;\n  }\n  return kj::none;\n}\n\nvoid Request::shallowCopyHeadersTo(kj::HttpHeaders& out) {\n  headers->shallowCopyTo(out);\n}\n\nkj::Maybe<kj::String> Request::serializeCfBlobJson(jsg::Lock& js) {\n  if (cacheMode == CacheMode::NONE) {\n    return cf.serialize(js);\n  }\n\n  CfProperty clone;\n  KJ_IF_SOME(obj, cf.get(js)) {\n    (void)obj;\n    clone = cf.deepClone(js);\n  } else {\n    clone = CfProperty(js, js.obj());\n  }\n  auto obj = KJ_ASSERT_NONNULL(clone.get(js));\n\n  constexpr int NOCACHE_TTL = -1;\n  switch (cacheMode) {\n    case CacheMode::NOSTORE:\n      if (obj.has(js, \"cacheTtl\")) {\n        jsg::JsValue oldTtl = obj.get(js, \"cacheTtl\");\n        JSG_REQUIRE(oldTtl.strictEquals(js.num(NOCACHE_TTL)), TypeError,\n            kj::str(\"CacheTtl: \", oldTtl, \", is not compatible with cache: \",\n                getCacheModeName(cacheMode).orDefault(\"none\"_kj), \" header.\"));\n      } else {\n        obj.set(js, \"cacheTtl\", js.num(NOCACHE_TTL));\n      }\n      KJ_FALLTHROUGH;\n    case CacheMode::RELOAD:\n      obj.set(js, \"cacheLevel\", js.str(\"bypass\"_kjc));\n      break;\n    case CacheMode::NOCACHE:\n      obj.set(js, \"cacheForceRevalidate\", js.boolean(true));\n      break;\n    case CacheMode::NONE:\n      KJ_UNREACHABLE;\n  }\n\n  return clone.serialize(js);\n}\n\nvoid RequestInitializerDict::validate(jsg::Lock& js) {\n  KJ_IF_SOME(c, cache) {\n    // Check compatibility flag\n    JSG_REQUIRE(FeatureFlags::get(js).getCacheOptionEnabled(), Error,\n        kj::str(\"The 'cache' field on 'RequestInitializerDict' is not implemented.\"));\n\n    // Validate that the cache type is valid\n    auto cacheMode = getCacheModeFromName(c);\n\n    bool invalidNoCache =\n        !FeatureFlags::get(js).getCacheNoCache() && (cacheMode == Request::CacheMode::NOCACHE);\n    bool invalidReload =\n        !FeatureFlags::get(js).getCacheReload() && (cacheMode == Request::CacheMode::RELOAD);\n    JSG_REQUIRE(\n        !invalidNoCache && !invalidReload, TypeError, kj::str(\"Unsupported cache mode: \", c));\n  }\n\n  KJ_IF_SOME(e, encodeResponseBody) {\n    JSG_REQUIRE(e == \"manual\"_kj || e == \"automatic\"_kj, TypeError,\n        kj::str(\"encodeResponseBody: unexpected value: \", e));\n  }\n}\n\nvoid Request::serialize(jsg::Lock& js,\n    jsg::Serializer& serializer,\n    const jsg::TypeHandler<RequestInitializerDict>& initDictHandler) {\n  serializer.writeLengthDelimited(url);\n\n  // Our strategy is to construct an initializer dict object and serialize that as a JS object.\n  // This makes the deserialization end really simple (just call the constructor), and it also\n  // gives us extensibility: we can add new fields without having to bump the serialization tag.\n  // clang-format off\n  serializer.write(js, jsg::JsValue(initDictHandler.wrap(js, RequestInitializerDict{\n    // GET is the default, so only serialize the method if it's something else.\n    .method = method == kj::HttpMethod::GET ? jsg::Optional<kj::String>() : kj::str(method),\n\n    .headers = headers.addRef(),\n\n    .body = getBody().map([](jsg::Ref<ReadableStream> stream) -> Body::Initializer {\n      // jsg::Ref<ReadableStream> is one of the possible variants of Body::Initializer.\n      return kj::mv(stream);\n    }),\n\n    // \"manual\" is the default for `redirect`, so only encode if it's not that.\n    .redirect = redirect == Redirect::MANUAL ? kj::str(getRedirect())\n                                              : kj::Maybe<kj::String>(kj::none),\n\n    // We have to ignore .fetcher for serialization. We can't simply fail if a fetcher is present\n    // because requests received by the top-level fetch handler actually have .fetcher set to\n    // the hidden \"next\" binding, which historically could be different from null (although in\n    // practice these days it is always the same). We obviously want to be able to serialize\n    // requests received by the top-level fetch handler so... we have to ignore this. This\n    // property should probably go away in any case.\n\n    .cf = cf.getRef(js),\n\n    .cache = getCacheModeName(cacheMode).map(\n        [](kj::StringPtr name) -> kj::String { return kj::str(name); }),\n\n    // .mode is unimplemented\n    // .credentials is unimplemented\n    // .referrer is unimplemented\n    // .referrerPolicy is unimplemented\n    // .integrity is required to be empty\n\n    // If an AbortSignal is present, we'll try to serialize it. As of this writing, AbortSignal\n    // is not serializable, but we could add support for sending it over RPC in the future.\n    //\n    // Note we have to double-Maybe this, so that if no signal is present, the property is absent\n    // instead of `null`.\n    .signal =\n        signal.map([&js](jsg::Ref<AbortSignal>& s) -> kj::Maybe<jsg::Ref<AbortSignal>> {\n      if (s->isIgnoredForSubrequests(js)) {\n        return kj::none;\n      }\n\n      return s.addRef();\n    }),\n\n    // Only serialize responseBodyEncoding if it's not the default AUTO\n    .encodeResponseBody = responseBodyEncoding == Response_BodyEncoding::AUTO\n        ? jsg::Optional<kj::String>()\n        : kj::str(\"manual\")\n  })));\n}\n\njsg::Ref<Request> Request::deserialize(jsg::Lock& js,\n    rpc::SerializationTag tag,\n    jsg::Deserializer& deserializer,\n    const jsg::TypeHandler<RequestInitializerDict>& initDictHandler) {\n  auto url = deserializer.readLengthDelimitedString();\n  auto init = KJ_ASSERT_NONNULL(initDictHandler.tryUnwrap(js, deserializer.readValue(js)));\n  return Request::constructor(js, kj::mv(url), kj::mv(init));\n}\n\n// =======================================================================================\n\nnamespace {\nconstexpr kj::StringPtr defaultStatusText(uint statusCode) {\n  // RFC 7231 recommendations, unless otherwise specified.\n  // https://tools.ietf.org/html/rfc7231#section-6.1\n#define STATUS(code, text) case code: return text##_kj\n  switch (statusCode) {\n    // Status code 0 is used exclusively with error responses\n    // created using Response.error()\n    STATUS(0, \"\");\n    STATUS(100, \"Continue\");\n    STATUS(101, \"Switching Protocols\");\n    STATUS(102, \"Processing\");   // RFC 2518, WebDAV\n    STATUS(103, \"Early Hints\");  // RFC 8297\n    STATUS(200, \"OK\");\n    STATUS(201, \"Created\");\n    STATUS(202, \"Accepted\");\n    STATUS(203, \"Non-Authoritative Information\");\n    STATUS(204, \"No Content\");\n    STATUS(205, \"Reset Content\");\n    STATUS(206, \"Partial Content\");\n    STATUS(207, \"Multi-Status\");      // RFC 4918, WebDAV\n    STATUS(208, \"Already Reported\");  // RFC 5842, WebDAV\n    STATUS(226, \"IM Used\");           // RFC 3229\n    STATUS(300, \"Multiple Choices\");\n    STATUS(301, \"Moved Permanently\");\n    STATUS(302, \"Found\");\n    STATUS(303, \"See Other\");\n    STATUS(304, \"Not Modified\");\n    STATUS(305, \"Use Proxy\");\n\n    STATUS(307, \"Temporary Redirect\");\n    STATUS(308, \"Permanent Redirect\");  // RFC 7538\n    STATUS(400, \"Bad Request\");\n    STATUS(401, \"Unauthorized\");\n    STATUS(402, \"Payment Required\");\n    STATUS(403, \"Forbidden\");\n    STATUS(404, \"Not Found\");\n    STATUS(405, \"Method Not Allowed\");\n    STATUS(406, \"Not Acceptable\");\n    STATUS(407, \"Proxy Authentication Required\");\n    STATUS(408, \"Request Timeout\");\n    STATUS(409, \"Conflict\");\n    STATUS(410, \"Gone\");\n    STATUS(411, \"Length Required\");\n    STATUS(412, \"Precondition Failed\");\n    STATUS(413, \"Payload Too Large\");\n    STATUS(414, \"URI Too Long\");\n    STATUS(415, \"Unsupported Media Type\");\n    STATUS(416, \"Range Not Satisfiable\");\n    STATUS(417, \"Expectation Failed\");\n    STATUS(418, \"I'm a teapot\");          // RFC 2324\n    STATUS(421, \"Misdirected Request\");   // RFC 7540\n    STATUS(422, \"Unprocessable Entity\");  // RFC 4918, WebDAV\n    STATUS(423, \"Locked\");                // RFC 4918, WebDAV\n    STATUS(424, \"Failed Dependency\");     // RFC 4918, WebDAV\n    STATUS(426, \"Upgrade Required\");\n    STATUS(428, \"Precondition Required\");            // RFC 6585\n    STATUS(429, \"Too Many Requests\");                // RFC 6585\n    STATUS(431, \"Request Header Fields Too Large\");  // RFC 6585\n    STATUS(451, \"Unavailable For Legal Reasons\");    // RFC 7725\n    STATUS(500, \"Internal Server Error\");\n    STATUS(501, \"Not Implemented\");\n    STATUS(502, \"Bad Gateway\");\n    STATUS(503, \"Service Unavailable\");\n    STATUS(504, \"Gateway Timeout\");\n    STATUS(505, \"HTTP Version Not Supported\");\n    STATUS(506, \"Variant Also Negotiates\");          // RFC 2295\n    STATUS(507, \"Insufficient Storage\");             // RFC 4918, WebDAV\n    STATUS(508, \"Loop Detected\");                    // RFC 5842, WebDAV\n    STATUS(510, \"Not Extended\");                     // RFC 2774\n    STATUS(511, \"Network Authentication Required\");  // RFC 6585\n    default:\n      // If we don't recognize the status code, check which range it falls into and use the status\n      // code class defined by RFC 7231, section 6, as the status text.\n      if (statusCode >= 200 && statusCode < 300) {\n        return \"Successful\"_kj;\n      } else if (statusCode >= 300 && statusCode < 400) {\n        return \"Redirection\"_kj;\n      } else if (statusCode >= 400 && statusCode < 500) {\n        return \"Client Error\"_kj;\n      } else if (statusCode >= 500 && statusCode < 600) {\n        return \"Server Error\"_kj;\n      } else {\n        return \"\"_kj;\n      }\n  }\n#undef STATUS\n}\n\nconstexpr bool isNullBodyStatusCode(uint statusCode) {\n  switch (statusCode) {\n    // Fetch spec section 2.2.3 defines these status codes as null body statuses:\n    // https://fetch.spec.whatwg.org/#null-body-status\n    case 101:\n    case 204:\n    case 205:\n    case 304:\n      return true;\n    default:\n      return false;\n  }\n}\n\nconstexpr bool isRedirectStatusCode(uint statusCode) {\n  switch (statusCode) {\n    // Fetch spec section 2.2.3 defines these status codes as redirect statuses:\n    // https://fetch.spec.whatwg.org/#redirect-status\n    case 301:\n    case 302:\n    case 303:\n    case 307:\n    case 308:\n      return true;\n    default:\n      return false;\n  }\n}\n}  // namespace\n\nResponse::Response(jsg::Lock& js,\n    int statusCode,\n    kj::Maybe<kj::String> statusText,\n    jsg::Ref<Headers> headers,\n    CfProperty&& cf,\n    kj::Maybe<Body::ExtractedBody> body,\n    kj::Array<kj::String> urlList,\n    kj::Maybe<jsg::Ref<WebSocket>> webSocket,\n    Response::BodyEncoding bodyEncoding)\n    : Body(js, kj::mv(body), *headers),\n      statusCode(statusCode),\n      statusText(kj::mv(statusText)),\n      headers(kj::mv(headers)),\n      cf(kj::mv(cf)),\n      urlList(kj::mv(urlList)),\n      webSocket(kj::mv(webSocket)),\n      bodyEncoding(bodyEncoding),\n      asyncContext(jsg::AsyncContextFrame::currentRef(js)) {}\n\njsg::Ref<Response> Response::error(jsg::Lock& js) {\n  return js.alloc<Response>(js, 0, kj::none, js.alloc<Headers>(), CfProperty(), kj::none);\n};\n\njsg::Ref<Response> Response::constructor(jsg::Lock& js,\n    jsg::Optional<kj::Maybe<Body::Initializer>> optionalBodyInit,\n    jsg::Optional<Initializer> maybeInit) {\n  auto bodyInit = kj::mv(optionalBodyInit).orDefault(kj::none);\n  Initializer init = kj::mv(maybeInit).orDefault(InitializerDict());\n\n  int statusCode = 200;\n  BodyEncoding bodyEncoding = Response::BodyEncoding::AUTO;\n\n  kj::Maybe<kj::String> statusText;\n  kj::Maybe<Body::ExtractedBody> body = kj::none;\n  jsg::Ref<Headers> headers = nullptr;\n  CfProperty cf;\n  kj::Maybe<jsg::Ref<WebSocket>> webSocket = kj::none;\n\n  KJ_SWITCH_ONEOF(init) {\n    KJ_CASE_ONEOF(initDict, InitializerDict) {\n      KJ_IF_SOME(status, initDict.status) {\n        statusCode = status;\n      }\n      KJ_IF_SOME(t, initDict.statusText) {\n        statusText = kj::mv(t);\n      }\n      KJ_IF_SOME(v, initDict.encodeBody) {\n        if (v == \"manual\"_kj) {\n          bodyEncoding = Response::BodyEncoding::MANUAL;\n        } else if (v == \"automatic\"_kj) {\n          bodyEncoding = Response::BodyEncoding::AUTO;\n        } else {\n          JSG_FAIL_REQUIRE(TypeError, kj::str(\"encodeBody: unexpected value: \", v));\n        }\n      }\n\n      KJ_IF_SOME(initHeaders, initDict.headers) {\n        headers = Headers::constructor(js, kj::mv(initHeaders));\n      } else {\n        headers = js.alloc<Headers>();\n      }\n\n      KJ_IF_SOME(newCf, initDict.cf) {\n        // TODO(cleanup): When initDict.cf is updated to use jsg::JsRef instead\n        // of jsg::V8Ref, we can clean this up a bit further.\n        auto cloned = newCf.deepClone(js);\n        cf = CfProperty(js, jsg::JsObject(cloned.getHandle(js)));\n      }\n\n      KJ_IF_SOME(ws, initDict.webSocket) {\n        KJ_IF_SOME(ws2, ws) {\n          webSocket = ws2.addRef();\n        }\n      }\n    }\n    KJ_CASE_ONEOF(otherResponse, jsg::Ref<Response>) {\n      // Note that in a true Fetch-conformant implementation, this entire case is enabled by Web IDL\n      // treating objects as dictionaries. However, some of our Response class's properties are\n      // jsg::WontImplement, which prevent us from relying on that Web IDL behavior ourselves.\n\n      statusCode = otherResponse->statusCode;\n      bodyEncoding = otherResponse->bodyEncoding;\n      kj::StringPtr otherStatusText = otherResponse->getStatusText();\n      if (otherStatusText != defaultStatusText(statusCode)) {\n        statusText = kj::str(otherStatusText);\n      }\n      headers = js.alloc<Headers>(js, *otherResponse->headers);\n      cf = otherResponse->cf.deepClone(js);\n      KJ_IF_SOME(otherWs, otherResponse->webSocket) {\n        webSocket = otherWs.addRef();\n      }\n    }\n  }\n\n  if (webSocket == kj::none) {\n    JSG_REQUIRE(statusCode >= 200 && statusCode <= 599, RangeError,\n        \"Responses may only be constructed with status codes in the range 200 to 599, inclusive.\");\n  } else {\n    JSG_REQUIRE(\n        statusCode == 101, RangeError, \"Responses with a WebSocket must have status code 101.\");\n  }\n\n  KJ_IF_SOME(s, statusText) {\n    // Disallow control characters (especially \\r and \\n) in statusText since it could allow\n    // header injection.\n    //\n    // TODO(cleanup): Once this is deployed, update open-source KJ HTTP to do this automatically.\n    for (char c: s) {\n      if (static_cast<byte>(c) < 0x20u) {\n        JSG_FAIL_REQUIRE(TypeError, \"Invalid statusText\");\n      }\n    }\n  }\n\n  KJ_IF_SOME(bi, bodyInit) {\n    body = Body::extractBody(js, kj::mv(bi));\n    if (isNullBodyStatusCode(statusCode)) {\n      // TODO(conform): We *should* fail unconditionally here, but during the Workers beta we\n      //   allowed Responses to have null body statuses with non-null, zero-length bodies. In order\n      //   not to break anything in production, for now we allow the author to construct a Response\n      //   with a zero-length buffer, but we give them a console warning. If we can ever verify that\n      //   no one relies on this behavior, we should remove this non-conformity.\n\n      // Fail if the body is not backed by a buffer (i.e., it's an opaque ReadableStream).\n      auto& buffer = JSG_REQUIRE_NONNULL(KJ_ASSERT_NONNULL(body).impl.buffer, TypeError,\n          \"Response with null body status (101, 204, 205, or 304) cannot have a body.\");\n\n      // Fail if the body is backed by a non-zero-length buffer.\n      JSG_REQUIRE(buffer.view.size() == 0, TypeError,\n          \"Response with null body status (101, 204, 205, or 304) cannot have a body.\");\n\n      auto& context = IoContext::current();\n      if (context.hasWarningHandler()) {\n        context.logWarning(kj::str(\"Constructing a Response with a null body status (\", statusCode,\n            \") and a non-null, \"\n            \"zero-length body. This is technically incorrect, and we recommend you update your \"\n            \"code to explicitly pass in a `null` body, e.g. `new Response(null, { status: \",\n            statusCode,\n            \", ... })`. (We continue to allow the zero-length body behavior because it \"\n            \"was previously the only way to construct a Response with a null body status. This \"\n            \"behavior may change in the future.)\"));\n      }\n\n      // Treat the zero-length body as a null body.\n      body = kj::none;\n    }\n  }\n\n  return js.alloc<Response>(js, statusCode, kj::mv(statusText), kj::mv(headers),\n      kj::mv(cf), kj::mv(body), nullptr, kj::mv(webSocket), bodyEncoding);\n}\n\njsg::Ref<Response> Response::redirect(jsg::Lock& js, kj::String url, jsg::Optional<int> status) {\n  auto statusCode = status.orDefault(302);\n  if (!isRedirectStatusCode(statusCode)) {\n    JSG_FAIL_REQUIRE(RangeError,\n        kj::str(statusCode,\n            \" is not a redirect status code. \"\n            \"It must be one of: 301, 302, 303, 307, or 308.\"));\n  }\n\n  // TODO(conform): The URL is supposed to be parsed relative to the \"current setting's object's API\n  //   base URL\".\n  kj::String parsedUrl = nullptr;\n  if (FeatureFlags::get(js).getSpecCompliantResponseRedirect()) {\n    auto parsed = JSG_REQUIRE_NONNULL(\n        jsg::Url::tryParse(url.asPtr()), TypeError, \"Unable to parse URL: \", url);\n    parsedUrl = kj::str(parsed.getHref());\n  } else {\n    auto urlOptions = kj::Url::Options{.percentDecode = false, .allowEmpty = true};\n    auto maybeParsedUrl = kj::Url::tryParse(url.asPtr(), kj::Url::REMOTE_HREF, urlOptions);\n    if (maybeParsedUrl == kj::none) {\n      JSG_FAIL_REQUIRE(TypeError, kj::str(\"Unable to parse URL: \", url));\n    }\n    parsedUrl = KJ_ASSERT_NONNULL(kj::mv(maybeParsedUrl)).toString();\n  }\n\n  if (!kj::HttpHeaders::isValidHeaderValue(parsedUrl)) {\n    JSG_FAIL_REQUIRE(\n        TypeError, kj::str(\"Redirect URL cannot contain '\\\\r', '\\\\n', or '\\\\0': \", url));\n  }\n\n  // Build our headers object with `Location` set to the parsed URL.\n  kj::HttpHeaders kjHeaders(IoContext::current().getHeaderTable());\n  kjHeaders.set(kj::HttpHeaderId::LOCATION, kj::mv(parsedUrl));\n  auto headers = js.alloc<Headers>(js, kjHeaders, Headers::Guard::IMMUTABLE);\n\n  return js.alloc<Response>(js, statusCode, kj::none, kj::mv(headers), nullptr, kj::none);\n}\n\njsg::Ref<Response> Response::json_(\n    jsg::Lock& js, jsg::JsValue any, jsg::Optional<Initializer> maybeInit) {\n\n  const auto maybeSetContentType = [](jsg::Lock& js, auto headers) {\n    if (!headers->hasCommon(capnp::CommonHeaderName::CONTENT_TYPE)) {\n      headers->setCommon(capnp::CommonHeaderName::CONTENT_TYPE, MimeType::JSON.toString());\n    }\n    return kj::mv(headers);\n  };\n\n  // While this all looks a bit complicated, all the following is doing is checking\n  // to see if maybeInit contains a content-type header. If it does, the existing\n  // value is left alone. If it does not, then we set the value of content-type\n  // to the default content type for JSON payloads. The reason this all looks a bit\n  // complicated is that maybeInit is an optional kj::OneOf that might be either\n  // a dict or a jsg::Ref<Response>. If it is a dict, then the optional headers\n  // field is also an optional kj::OneOf that can be either a dict or a jsg::Ref<Headers>.\n  // We have to deal with all of the various possibilities here to set the content-type\n  // appropriately.\n  KJ_IF_SOME(init, maybeInit) {\n    KJ_SWITCH_ONEOF(init) {\n      KJ_CASE_ONEOF(dict, InitializerDict) {\n        KJ_IF_SOME(headers, dict.headers) {\n          dict.headers = maybeSetContentType(js, Headers::constructor(js, kj::mv(headers)));\n        } else {\n          dict.headers = maybeSetContentType(js, js.alloc<Headers>());\n        }\n      }\n      KJ_CASE_ONEOF(res, jsg::Ref<Response>) {\n        auto otherStatusText = res->getStatusText();\n        auto newInit = InitializerDict{\n          .status = res->statusCode,\n          .statusText = otherStatusText == nullptr ||\n                        otherStatusText == defaultStatusText(res->statusCode)\n                        ? jsg::Optional<kj::String>() : kj::str(otherStatusText),\n          .headers = maybeSetContentType(js, Headers::constructor(js, res->headers.addRef())),\n          .cf = res->cf.getRef(js),\n          .encodeBody =\n              kj::str(res->bodyEncoding == Response::BodyEncoding::MANUAL ? \"manual\" : \"automatic\"),\n        };\n\n        KJ_IF_SOME(otherWs, res->webSocket) {\n          newInit.webSocket = otherWs.addRef();\n        }\n\n        maybeInit = kj::mv(newInit);\n      }\n    }\n  } else {\n    maybeInit = InitializerDict{\n      .headers = maybeSetContentType(js, js.alloc<Headers>()),\n    };\n  }\n\n  return constructor(js, kj::Maybe(any.toJson(js)), kj::mv(maybeInit));\n}\n\njsg::Ref<Response> Response::clone(jsg::Lock& js) {\n  JSG_REQUIRE(\n      webSocket == kj::none, TypeError, \"Cannot clone a response to a WebSocket handshake.\");\n\n  auto headersClone = headers->clone(js);\n  auto cfClone = cf.deepClone(js);\n\n  auto bodyClone = Body::clone(js);\n\n  auto urlListClone = KJ_MAP(url, urlList) { return kj::str(url); };\n\n  return js.alloc<Response>(js, statusCode,\n      mapCopyString(statusText),\n      kj::mv(headersClone), kj::mv(cfClone), kj::mv(bodyClone), kj::mv(urlListClone));\n}\n\nkj::Promise<DeferredProxy<void>> Response::send(jsg::Lock& js,\n    kj::HttpService::Response& outer,\n    SendOptions options,\n    kj::Maybe<const kj::HttpHeaders&> maybeReqHeaders) {\n  JSG_REQUIRE(!getBodyUsed(), TypeError,\n      \"Body has already been used. \"\n      \"It can only be used once. Use tee() first if you need to read it twice.\");\n\n  // Careful: Keep in mind that the promise we return could be canceled in which case `outer` will\n  // be destroyed. Additionally, the response body stream we get from calling send() must itself\n  // be destroyed before `outer` is destroyed. So, it's important to make sure that only the\n  // promise we return encapsulates any task that might write to the response body. We can't, for\n  // example, put the response body into a JS heap object. That should all be fine as long as we\n  // use a pumpTo() that can be canceled.\n\n  auto& context = IoContext::current();\n  kj::HttpHeaders outHeaders(context.getHeaderTable());\n  headers->shallowCopyTo(outHeaders);\n\n  KJ_IF_SOME(ws, webSocket) {\n    // `Response::acceptWebSocket()` can throw if we did not ask for a WebSocket. This\n    // would promote a js client error into an uncatchable server error. Thus, we throw early here\n    // if we do not expect a WebSocket. This could also be a 426 status code response, but we think\n    // that the majority of our users expect us to throw on a client-side fetch error instead of\n    // returning a 4xx status code. A 426 status code error _might_ be more appropriate if the\n    // request headers originate from outside the worker developer's control (e.g. a client\n    // application by some other party).\n    JSG_REQUIRE(options.allowWebSocket, TypeError,\n        \"Worker tried to return a WebSocket in a response to a request \"\n        \"which did not contain the header \\\"Upgrade: websocket\\\".\");\n\n    const bool hasEnabledWebSocketCompression = FeatureFlags::get(js).getWebSocketCompression();\n\n    if (hasEnabledWebSocketCompression &&\n        outHeaders.get(kj::HttpHeaderId::SEC_WEBSOCKET_EXTENSIONS) == kj::none) {\n      // Since workerd uses `MANUAL_COMPRESSION` mode for websocket compression, we need to\n      // pass the headers we want to support to `acceptWebSocket()`.\n      KJ_IF_SOME(config, ws->getPreferredExtensions(kj::WebSocket::ExtensionsContext::RESPONSE)) {\n        // We try to get extensions for use in a response (i.e. for a server side websocket).\n        // This allows us to `optimizedPumpTo()` `webSocket`.\n        outHeaders.set(kj::HttpHeaderId::SEC_WEBSOCKET_EXTENSIONS, kj::mv(config));\n      } else {\n        // `webSocket` is not a WebSocketImpl, we want to support whatever valid config the client\n        // requested, so we'll just use the client's requested headers.\n        KJ_IF_SOME(reqHeaders, maybeReqHeaders) {\n          KJ_IF_SOME(value, reqHeaders.get(kj::HttpHeaderId::SEC_WEBSOCKET_EXTENSIONS)) {\n            outHeaders.setPtr(kj::HttpHeaderId::SEC_WEBSOCKET_EXTENSIONS, value);\n          }\n        }\n      }\n    } else if (!hasEnabledWebSocketCompression) {\n      // While we guard against an origin server including `Sec-WebSocket-Extensions` in a Response\n      // (we don't send the extension in an offer, and if the server includes it in a response we\n      // will reject the connection), a Worker could still explicitly add the header to a Response.\n      outHeaders.unset(kj::HttpHeaderId::SEC_WEBSOCKET_EXTENSIONS);\n    }\n\n    auto clientSocket = outer.acceptWebSocket(outHeaders);\n    auto wsPromise = ws->couple(kj::mv(clientSocket), context.getMetrics());\n\n    KJ_IF_SOME(a, context.getActor()) {\n      KJ_IF_SOME(hib, a.getHibernationManager()) {\n        // We attach a reference to the deferred proxy task so the HibernationManager lives at least\n        // as long as the websocket connection.\n        // The actor still retains its reference to the manager, so any subsequent requests prior\n        // to hibernation will not need to re-obtain a reference.\n        wsPromise = wsPromise.attach(kj::addRef(hib));\n      }\n    }\n    return wsPromise;\n  } else KJ_IF_SOME(jsBody, getBody()) {\n    auto encoding = getContentEncoding(context, outHeaders, bodyEncoding, FeatureFlags::get(js));\n    auto maybeLength = jsBody->tryGetLength(encoding);\n    auto stream =\n        newSystemStream(outer.send(statusCode, getStatusText(), outHeaders, maybeLength), encoding);\n    // We need to enter the AsyncContextFrame that was captured when the\n    // Response was created before starting the loop.\n    jsg::AsyncContextFrame::Scope scope(js, asyncContext);\n    return jsBody->pumpTo(js, kj::mv(stream), true);\n  } else {\n    outer.send(statusCode, getStatusText(), outHeaders, static_cast<uint64_t>(0));\n    return addNoopDeferredProxy(kj::READY_NOW);\n  }\n}\n\nint Response::getStatus() {\n  return statusCode;\n}\nkj::StringPtr Response::getStatusText() {\n  KJ_IF_SOME(text, statusText) {\n    return text;\n  }\n  return defaultStatusText(statusCode);\n}\njsg::Ref<Headers> Response::getHeaders(jsg::Lock& js) {\n  return headers.addRef();\n}\n\nbool Response::getOk() {\n  return statusCode >= 200 && statusCode < 300;\n}\nbool Response::getRedirected() {\n  return urlList.size() > 1;\n}\nkj::StringPtr Response::getUrl() {\n  if (urlList.size() > 0) {\n    // We're supposed to drop any fragment from the URL. Instead of doing it here, we rely on the\n    // code that calls the Response constructor (e.g. makeHttpResponse()) to drop the fragments\n    // before giving the stringified URL to us.\n    return urlList.back();\n  } else {\n    // Per spec, if the URL list is empty, we return an empty string. I dunno, man.\n    return \"\";\n  }\n}\n\nkj::Maybe<jsg::Ref<WebSocket>> Response::getWebSocket(jsg::Lock& js) {\n  return webSocket.map([&](jsg::Ref<WebSocket>& ptr) { return ptr.addRef(); });\n}\n\njsg::Optional<jsg::JsObject> Response::getCf(jsg::Lock& js) {\n  return cf.get(js);\n}\n\nvoid Response::serialize(jsg::Lock& js,\n    jsg::Serializer& serializer,\n    const jsg::TypeHandler<InitializerDict>& initDictHandler,\n    const jsg::TypeHandler<kj::Maybe<jsg::Ref<ReadableStream>>>& streamHandler) {\n  serializer.write(js, jsg::JsValue(streamHandler.wrap(js, getBody())));\n\n  // As with Request, we serialize the initializer dict as a JS object.\n  serializer.write(js,\n      jsg::JsValue(initDictHandler.wrap(js,\n          InitializerDict{\n            .status = statusCode == 200 ? jsg::Optional<int>() : statusCode,\n            .statusText = statusText.map([](auto& txt) { return kj::str(txt); }),\n            .headers = headers.addRef(),\n            .cf = cf.getRef(js),\n\n            // If a WebSocket is present, we'll try to serialize it. As of this writing, WebSocket\n            // is not serializable, but we could add support for sending it over RPC in the future.\n            //\n            // Note we have to double-Maybe this, so that if no signal is present, the property is absent\n            // instead of `null`.\n            .webSocket =\n                webSocket.map([](jsg::Ref<WebSocket>& s) -> kj::Maybe<jsg::Ref<WebSocket>> {\n    return s.addRef();\n  }),\n\n            .encodeBody = bodyEncoding == BodyEncoding::AUTO ? jsg::Optional<kj::String>()\n                                                             : kj::str(\"manual\"),\n          })));\n}\n\njsg::Ref<Response> Response::deserialize(jsg::Lock& js,\n    rpc::SerializationTag tag,\n    jsg::Deserializer& deserializer,\n    const jsg::TypeHandler<InitializerDict>& initDictHandler,\n    const jsg::TypeHandler<kj::Maybe<jsg::Ref<ReadableStream>>>& streamHandler) {\n  auto body = KJ_ASSERT_NONNULL(streamHandler.tryUnwrap(js, deserializer.readValue(js)));\n  auto init = KJ_ASSERT_NONNULL(initDictHandler.tryUnwrap(js, deserializer.readValue(js)));\n\n  // If the status code is zero, then it was an error response. We cannot\n  // use the Response::constructor.\n  KJ_IF_SOME(status, init.status) {\n    if (status == 0) {\n      return Response::error(js);\n    }\n  }\n\n  return Response::constructor(js, kj::mv(body), kj::mv(init));\n}\n\n// =======================================================================================\n\njsg::Ref<Request> FetchEvent::getRequest() {\n  return request.addRef();\n}\n\nkj::Maybe<jsg::Promise<jsg::Ref<Response>>> FetchEvent::getResponsePromise(jsg::Lock& js) {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(_, AwaitingRespondWith) {\n      state = ResponseSent();\n      return kj::none;\n    }\n    KJ_CASE_ONEOF(called, RespondWithCalled) {\n      auto result = kj::mv(called.promise);\n      state = ResponseSent();\n      return kj::mv(result);\n    }\n    KJ_CASE_ONEOF(_, ResponseSent) {\n      KJ_FAIL_REQUIRE(\"can only call getResponsePromise() once\");\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid FetchEvent::respondWith(jsg::Lock& js, jsg::Promise<jsg::Ref<Response>> promise) {\n  preventDefault();\n\n  if (IoContext::current().hasOutputGate()) {\n    // Once a Response is returned, we need to apply the output lock.\n    promise = promise.then(js, [](jsg::Lock& js, jsg::Ref<Response>&& response) {\n      auto& context = IoContext::current();\n      return context.awaitIo(js, context.waitForOutputLocks(),\n          [response = kj::mv(response)](jsg::Lock&) mutable { return kj::mv(response); });\n    });\n  }\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(_, AwaitingRespondWith) {\n      state = RespondWithCalled{kj::mv(promise)};\n    }\n    KJ_CASE_ONEOF(called, RespondWithCalled) {\n      JSG_FAIL_REQUIRE(DOMInvalidStateError,\n          \"FetchEvent.respondWith() has already been called; it can only be called once.\");\n    }\n    KJ_CASE_ONEOF(_, ResponseSent) {\n      JSG_FAIL_REQUIRE(DOMInvalidStateError,\n          \"Too late to call FetchEvent.respondWith(). It must be called synchronously in the \"\n          \"event handler.\");\n    }\n  }\n\n  stopImmediatePropagation();\n}\n\nvoid FetchEvent::passThroughOnException() {\n  IoContext::current().setFailOpen();\n}\n\n// =======================================================================================\n\nnamespace {\n\n// Fetch spec requires (suggests?) 20: https://fetch.spec.whatwg.org/#http-redirect-fetch\nconstexpr auto MAX_REDIRECT_COUNT = 20;\n\njsg::Promise<jsg::Ref<Response>> handleHttpResponse(jsg::Lock& js,\n    jsg::Ref<Fetcher> fetcher,\n    jsg::Ref<Request> jsRequest,\n    kj::Vector<kj::Url> urlList,\n    kj::HttpClient::Response&& response);\njsg::Promise<jsg::Ref<Response>> handleHttpRedirectResponse(jsg::Lock& js,\n    jsg::Ref<Fetcher> fetcher,\n    jsg::Ref<Request> jsRequest,\n    kj::Vector<kj::Url> urlList,\n    uint status,\n    kj::StringPtr location);\n\njsg::Promise<jsg::Ref<Response>> fetchImplNoOutputLock(jsg::Lock& js,\n    jsg::Ref<Fetcher> fetcher,\n    jsg::Ref<Request> jsRequest,\n    kj::Vector<kj::Url> urlList) {\n  KJ_ASSERT(!urlList.empty());\n\n  auto& ioContext = IoContext::current();\n\n  auto signal = jsRequest->getSignal();\n  KJ_IF_SOME(s, signal) {\n    // If the AbortSignal has already been triggered, then we need to stop here.\n    if (s->getAborted(js)) {\n      return js.rejectedPromise<jsg::Ref<Response>>(s->getReason(js));\n    }\n  }\n\n  // Get client and trace context (if needed) in one clean call\n  auto clientWithTracing = fetcher->getClientWithTracing(ioContext, jsRequest->serializeCfBlobJson(js), \"fetch\"_kjc);\n  auto traceContext = kj::mv(clientWithTracing.traceContext);\n\n  // TODO(cleanup): Don't convert to HttpClient. Use the HttpService interface instead. This\n  //   requires a significant rewrite of the code below. It'll probably get simpler, though?\n  kj::Own<kj::HttpClient> client = asHttpClient(kj::mv(clientWithTracing.client));\n\n  kj::HttpHeaders headers(ioContext.getHeaderTable());\n  jsRequest->shallowCopyHeadersTo(headers);\n\n  // If the jsRequest has a CacheMode, we need to handle that here.\n  // Currently, the only cache mode we support is undefined and no-store, no-cache, and reload\n  auto headerIds = ioContext.getHeaderIds();\n  const auto cacheMode = jsRequest->getCacheMode();\n  switch (cacheMode) {\n    case Request::CacheMode::RELOAD:\n      KJ_FALLTHROUGH;\n    case Request::CacheMode::NOSTORE:\n      KJ_FALLTHROUGH;\n    case Request::CacheMode::NOCACHE:\n      if (headers.get(headerIds.cacheControl) == kj::none) {\n        headers.setPtr(headerIds.cacheControl, \"no-cache\");\n      }\n      if (headers.get(headerIds.pragma) == kj::none) {\n        headers.setPtr(headerIds.pragma, \"no-cache\");\n      }\n      KJ_FALLTHROUGH;\n    case Request::CacheMode::NONE:\n      break;\n    default:\n      KJ_UNREACHABLE;\n  }\n\n  KJ_IF_SOME(ctx, traceContext) {\n    ctx.setTag(\"network.protocol.name\"_kjc, \"http\"_kjc);\n    ctx.setTag(\"network.protocol.version\"_kjc, \"HTTP/1.1\"_kjc);\n    ctx.setTag(\"http.request.method\"_kjc, kj::str(jsRequest->getMethodEnum()));\n    ctx.setTag(\"url.full\"_kjc, jsRequest->getUrl());\n\n    KJ_IF_SOME(userAgent, headers.get(headerIds.userAgent)) {\n      ctx.setTag(\"user_agent.original\"_kjc, userAgent);\n    }\n\n    KJ_IF_SOME(contentType, headers.get(headerIds.contentType)) {\n      ctx.setTag(\"http.request.header.content-type\"_kjc, contentType);\n    }\n\n    KJ_IF_SOME(contentLength, headers.get(headerIds.contentLength)) {\n      ctx.setTag(\"http.request.header.content-length\"_kjc, contentLength);\n    }\n\n    KJ_IF_SOME(accept, headers.get(headerIds.accept)) {\n      ctx.setTag(\"http.request.header.accept\"_kjc, accept);\n    }\n\n    KJ_IF_SOME(acceptEncoding, headers.get(headerIds.acceptEncoding)) {\n      ctx.setTag(\"http.request.header.accept-encoding\"_kjc, acceptEncoding);\n    }\n  }\n\n  kj::String url =\n      uriEncodeControlChars(urlList.back().toString(kj::Url::HTTP_PROXY_REQUEST).asBytes());\n\n  if (headers.isWebSocket()) {\n    if (!FeatureFlags::get(js).getWebSocketCompression()) {\n      // If we haven't enabled the websocket compression compatibility flag, strip the header from the\n      // subrequest.\n      headers.unset(kj::HttpHeaderId::SEC_WEBSOCKET_EXTENSIONS);\n    }\n    auto webSocketResponse = client->openWebSocket(url, headers);\n    return ioContext.awaitIo(js,\n        AbortSignal::maybeCancelWrap(js, signal, kj::mv(webSocketResponse)),\n        [fetcher = kj::mv(fetcher), jsRequest = kj::mv(jsRequest), urlList = kj::mv(urlList),\n            client = kj::mv(client), signal = kj::mv(signal)](\n            jsg::Lock& js, kj::HttpClient::WebSocketResponse&& response) mutable\n        -> jsg::Promise<jsg::Ref<Response>> {\n      KJ_SWITCH_ONEOF(response.webSocketOrBody) {\n        KJ_CASE_ONEOF(body, kj::Own<kj::AsyncInputStream>) {\n          body = body.attach(kj::mv(client));\n          return handleHttpResponse(js, kj::mv(fetcher), kj::mv(jsRequest), kj::mv(urlList),\n              {response.statusCode, response.statusText, response.headers, kj::mv(body)});\n        }\n        KJ_CASE_ONEOF(webSocket, kj::Own<kj::WebSocket>) {\n          KJ_ASSERT(response.statusCode == 101);\n          webSocket = webSocket.attach(kj::mv(client));\n          KJ_IF_SOME(s, signal) {\n            // If the AbortSignal has already been triggered, then we need to stop here.\n            if (s->getAborted(js)) {\n              return js.rejectedPromise<jsg::Ref<Response>>(s->getReason(js));\n            }\n            webSocket = kj::refcounted<AbortableWebSocket>(kj::mv(webSocket), s->getCanceler());\n          }\n          return js.resolvedPromise(makeHttpResponse(js, jsRequest->getMethodEnum(),\n              kj::mv(urlList), response.statusCode, response.statusText, *response.headers,\n              newNullInputStream(), js.alloc<WebSocket>(js, kj::mv(webSocket)),\n              jsRequest->getResponseBodyEncoding(), kj::mv(signal)));\n        }\n      }\n      KJ_UNREACHABLE;\n    });\n  } else {\n    kj::Maybe<kj::HttpClient::Request> nativeRequest;\n    KJ_IF_SOME(jsBody, jsRequest->getBody()) {\n      // Note that for requests, we do not automatically handle Content-Encoding, because the fetch()\n      // standard does not say that we should. Hence, we always use StreamEncoding::IDENTITY.\n      // https://github.com/whatwg/fetch/issues/589\n      auto maybeLength = jsBody->tryGetLength(StreamEncoding::IDENTITY);\n      KJ_IF_SOME(ctx, traceContext) {\n        KJ_IF_SOME(length, maybeLength) {\n          ctx.setTag(\"http.request.body.size\"_kjc, static_cast<int64_t>(length));\n        }\n      }\n\n      if (maybeLength.orDefault(1) == 0 &&\n          headers.get(kj::HttpHeaderId::CONTENT_LENGTH) == kj::none &&\n          headers.get(kj::HttpHeaderId::TRANSFER_ENCODING) == kj::none) {\n        // Request has a non-null but explicitly empty body, and has neither a Content-Length nor\n        // a Transfer-Encoding header. If we don't set one of those two, and the receiving end is\n        // another worker (especially within a pipeline or reached via RPC, not real HTTP), then\n        // the code in global-scope.c++ on the receiving end will decide the body should be null.\n        // We'd like to avoid this weird discontinuity, so let's set Content-Length explicitly to\n        // 0.\n        headers.setPtr(kj::HttpHeaderId::CONTENT_LENGTH, \"0\"_kj);\n      }\n\n      KJ_IF_SOME(ctx, traceContext) {\n        KJ_IF_SOME(cfRay, headers.get(headerIds.cfRay)) {\n          ctx.setTag(\"cloudflare.ray_id\"_kjc, cfRay);\n        }\n      }\n\n      nativeRequest = client->request(jsRequest->getMethodEnum(), url, headers, maybeLength);\n      auto& nr = KJ_ASSERT_NONNULL(nativeRequest);\n      auto stream = newSystemStream(kj::mv(nr.body), StreamEncoding::IDENTITY);\n\n      // We want to support bidirectional streaming, so we actually don't want to wait for the\n      // request to finish before we deliver the response to the app.\n\n      // jsBody is not used directly within the function but is passed in so that\n      // the coroutine frame keeps it alive.\n      static constexpr auto handleCancelablePump = [](kj::Promise<void> promise,\n                                                       auto jsBody) -> kj::Promise<void> {\n        try {\n          co_await promise;\n        } catch (...) {\n          auto exception = kj::getCaughtExceptionAsKj();\n          if (exception.getType() != kj::Exception::Type::DISCONNECTED) {\n            kj::throwFatalException(kj::mv(exception));\n          }\n          // Ignore DISCONNECTED exceptions thrown by the writePromise, so that we always\n          // return the server's response, which should identify if any issue occurred with\n          // the body stream anyway.\n        }\n      };\n\n      // TODO(someday): Allow deferred proxying for bidirectional streaming.\n      ioContext.addWaitUntil(handleCancelablePump(\n          AbortSignal::maybeCancelWrap(\n              js, signal, ioContext.waitForDeferredProxy(jsBody->pumpTo(js, kj::mv(stream), true))),\n          jsBody.addRef()));\n    } else {\n      nativeRequest = client->request(jsRequest->getMethodEnum(), url, headers, static_cast<uint64_t>(0));\n    }\n    return ioContext.awaitIo(js,\n        AbortSignal::maybeCancelWrap(js, signal, kj::mv(KJ_ASSERT_NONNULL(nativeRequest).response))\n            .catch_([](kj::Exception&& exception) -> kj::Promise<kj::HttpClient::Response> {\n      if (exception.getDescription().startsWith(\"invalid Content-Length header value\")) {\n        return JSG_KJ_EXCEPTION(FAILED, Error, exception.getDescription());\n      } else if (exception.getDescription().contains(\"NOSENTRY script not found\")) {\n        return JSG_KJ_EXCEPTION(FAILED, Error, \"Worker not found.\");\n      }\n      return kj::mv(exception);\n    }),\n        [fetcher = kj::mv(fetcher), jsRequest = kj::mv(jsRequest), urlList = kj::mv(urlList),\n            client = kj::mv(client), traceContext = kj::mv(traceContext)](jsg::Lock& js,\n            kj::HttpClient::Response&& response) mutable -> jsg::Promise<jsg::Ref<Response>> {\n      response.body = response.body.attach(kj::mv(client));\n      KJ_IF_SOME(ctx, traceContext) {\n        ctx.setTag(\"http.response.status_code\"_kjc, static_cast<int64_t>(response.statusCode));\n        KJ_IF_SOME(length, response.body->tryGetLength()) {\n          ctx.setTag(\"http.response.body.size\"_kjc, static_cast<int64_t>(length));\n        }\n      }\n      return handleHttpResponse(\n          js, kj::mv(fetcher), kj::mv(jsRequest), kj::mv(urlList), kj::mv(response));\n    });\n  }\n}\n\njsg::Promise<jsg::Ref<Response>> fetchImpl(jsg::Lock& js,\n    jsg::Ref<Fetcher> fetcher,\n    jsg::Ref<Request> jsRequest,\n    kj::Vector<kj::Url> urlList) {\n  auto& context = IoContext::current();\n  // Optimization: For non-actors, which never have output locks, avoid the overhead of\n  // awaitIo() and such by not going back to the event loop at all.\n  KJ_IF_SOME(promise, context.waitForOutputLocksIfNecessary()) {\n    return context.awaitIo(js, kj::mv(promise),\n        [fetcher = kj::mv(fetcher), jsRequest = kj::mv(jsRequest), urlList = kj::mv(urlList)](\n            jsg::Lock& js) mutable {\n      return fetchImplNoOutputLock(js, kj::mv(fetcher), kj::mv(jsRequest), kj::mv(urlList));\n    });\n  } else {\n    return fetchImplNoOutputLock(js, kj::mv(fetcher), kj::mv(jsRequest), kj::mv(urlList));\n  }\n}\n\njsg::Promise<jsg::Ref<Response>> handleHttpResponse(jsg::Lock& js,\n    jsg::Ref<Fetcher> fetcher,\n    jsg::Ref<Request> jsRequest,\n    kj::Vector<kj::Url> urlList,\n    kj::HttpClient::Response&& response) {\n  auto signal = jsRequest->getSignal();\n\n  KJ_IF_SOME(s, signal) {\n    // If the AbortSignal has already been triggered, then we need to stop here.\n    if (s->getAborted(js)) {\n      return js.rejectedPromise<jsg::Ref<Response>>(s->getReason(js));\n    }\n    response.body = kj::refcounted<AbortableInputStream>(kj::mv(response.body), s->getCanceler());\n  }\n\n  if (isRedirectStatusCode(response.statusCode) &&\n      jsRequest->getRedirectEnum() == Request::Redirect::FOLLOW) {\n    KJ_IF_SOME(l, response.headers->get(kj::HttpHeaderId::LOCATION)) {\n\n      // Pump the response body to a singleton null stream before following the redirect.\n      auto& ioContext = IoContext::current();\n      return ioContext.awaitIo(js,\n          response.body->pumpTo(getGlobalNullOutputStream()).ignoreResult().attach(kj::mv(response.body)),\n          [fetcher = kj::mv(fetcher), jsRequest = kj::mv(jsRequest), urlList = kj::mv(urlList),\n           status = response.statusCode, location = kj::str(l)](jsg::Lock& js) mutable {\n        return handleHttpRedirectResponse(\n            js, kj::mv(fetcher), kj::mv(jsRequest), kj::mv(urlList), status, kj::mv(location));\n      });\n    } else {\n      // No Location header. That's OK, we just return the response as is.\n      // See https://fetch.spec.whatwg.org/#http-redirect-fetch step 2.\n    }\n  }\n\n  auto result = makeHttpResponse(js, jsRequest->getMethodEnum(), kj::mv(urlList),\n      response.statusCode, response.statusText, *response.headers, kj::mv(response.body), kj::none,\n      jsRequest->getResponseBodyEncoding(), kj::mv(signal));\n\n  return js.resolvedPromise(kj::mv(result));\n}\n\njsg::Promise<jsg::Ref<Response>> handleHttpRedirectResponse(jsg::Lock& js,\n    jsg::Ref<Fetcher> fetcher,\n    jsg::Ref<Request> jsRequest,\n    kj::Vector<kj::Url> urlList,\n    uint status,\n    kj::StringPtr location) {\n  // Reconstruct the request body stream for retransmission in the face of a redirect. Before\n  // reconstructing the stream, however, this function:\n  //\n  //   - Throws if `status` is non-303 and this request doesn't have a \"rewindable\" body.\n  //   - Translates POST requests that hit 301, 302, or 303 into GET requests with null bodies.\n  //   - Translates HEAD requests that hit 303 into HEAD requests with null bodies.\n  //   - Translates all other requests that hit 303 into GET requests with null bodies.\n\n  auto redirectedLocation = ([&]() -> kj::Maybe<kj::Url> {\n    // TODO(later): This is a bit unfortunate. Per the fetch spec, we're supposed to be\n    // using standard WHATWG URL parsing to resolve the redirect URL. However, changing it\n    // now requires a compat flag. In order to minimize changes to the rest of the impl\n    // we end up double parsing the URL here, once with the standard parser to produce the\n    // correct result, and again with kj::Url in order to produce something that works with\n    // the existing code. Fortunately the standard parser is fast but it would be nice to\n    // be able to avoid the double parse at some point.\n    if (FeatureFlags::get(js).getFetchStandardUrl()) {\n      auto base = urlList.back().toString();\n      KJ_IF_SOME(parsed, jsg::Url::tryParse(location, base.asPtr())) {\n        auto str = kj::str(parsed.getHref());\n        return kj::Url::tryParse(str.asPtr(), kj::Url::Context::REMOTE_HREF,\n            kj::Url::Options{\n              .percentDecode = false,\n              .allowEmpty = true,\n            });\n      } else {\n        return kj::none;\n      }\n    } else {\n      return urlList.back().tryParseRelative(location);\n    }\n  })();\n\n  if (redirectedLocation == kj::none) {\n    auto exception =\n        JSG_KJ_EXCEPTION(FAILED, TypeError, \"Invalid Location header; unable to follow redirect.\");\n    return js.rejectedPromise<jsg::Ref<Response>>(kj::mv(exception));\n  }\n\n  // Note: RFC7231 says we should propagate fragments from the current request URL to the\n  //   redirected URL. The Fetch spec seems to take the position that that's the navigator's\n  //   job -- i.e., that you should be using redirect manual mode and deciding what to do with\n  //   fragments in Location headers yourself. We follow the spec, and don't do any explicit\n  //   fragment propagation.\n\n  if (urlList.size() - 1 >= MAX_REDIRECT_COUNT) {\n    auto exception = JSG_KJ_EXCEPTION(FAILED, TypeError, \"Too many redirects.\", urlList);\n    return js.rejectedPromise<jsg::Ref<Response>>(kj::mv(exception));\n  }\n\n  if (FeatureFlags::get(js).getStripAuthorizationOnCrossOriginRedirect()) {\n    auto base = urlList.back().toString();\n\n    auto currentUrl = KJ_UNWRAP_OR(jsg::Url::tryParse(base.asPtr()), {\n      auto exception =\n        JSG_KJ_EXCEPTION(FAILED, TypeError, \"Invalid current URL; unable to follow redirect.\");\n      return js.rejectedPromise<jsg::Ref<Response>>(kj::mv(exception));\n    });\n\n    auto locationUrl = KJ_UNWRAP_OR(jsg::Url::tryParse(location, base.asPtr()), {\n      auto exception =\n        JSG_KJ_EXCEPTION(FAILED, TypeError, \"Invalid Location header; unable to follow redirect.\");\n      return js.rejectedPromise<jsg::Ref<Response>>(kj::mv(exception));\n    });\n\n    if (currentUrl.getOrigin() != locationUrl.getOrigin()) {\n      // If request’s current URL’s origin is not same origin with locationURL’s origin, then\n      // for each headerName of CORS non-wildcard request-header name, delete headerName from\n      // request’s header list.\n      // -- Fetch spec s. 4.4.13\n      // <https://fetch.spec.whatwg.org/#http-redirect-fetch>\n      //  (NB: \"CORS non-wildcard request-header name\" consists solely of \"Authorization\")\n\n      jsRequest->getHeaders(js)->deleteCommon(capnp::CommonHeaderName::AUTHORIZATION);\n    }\n  }\n\n  urlList.add(kj::mv(KJ_ASSERT_NONNULL(redirectedLocation)));\n\n  // \"If actualResponse’s status is not 303, request’s body is non-null, and\n  //   request’s body’s source [buffer] is null, then return a network error.\"\n  //   https://fetch.spec.whatwg.org/#http-redirect-fetch step 9.\n  //\n  // TODO(conform): this check pedantically enforces the spec, even if a POST hits a 301 or\n  //   302. In that case, we're going to null out the body, anyway, so it feels strange to\n  //   report an error. If we widen fetch()'s contract to allow POSTs with non-buffer-backed\n  //   bodies to survive 301/302 redirects, our logic would get simpler here.\n  //\n  //   Follow up with the spec authors about this.\n  if (status != 303 && !jsRequest->canRewindBody()) {\n    auto exception = JSG_KJ_EXCEPTION(FAILED, TypeError,\n        \"A request with a one-time-use body (it was initialized from a stream, not a buffer) \"\n        \"encountered a redirect requiring the body to be retransmitted. To avoid this error \"\n        \"in the future, construct this request from a buffer-like body initializer.\");\n    return js.rejectedPromise<jsg::Ref<Response>>(kj::mv(exception));\n  }\n\n  auto method = jsRequest->getMethodEnum();\n\n  // \"If either actualResponse’s status is 301 or 302 and request’s method is\n  //   `POST`, or actualResponse’s status is 303 and request's method is not `HEAD`, set request’s\n  //   method to `GET` and request’s body to null.\"\n  //   https://fetch.spec.whatwg.org/#http-redirect-fetch step 11.\n  if (((status == 301 || status == 302) && method == kj::HttpMethod::POST) ||\n      (status == 303 && method != kj::HttpMethod::HEAD)) {\n    // TODO(conform): When translating a request with a body to a GET request, should we\n    //   explicitly remove Content-* headers? See https://github.com/whatwg/fetch/issues/609\n    jsRequest->setMethodEnum(kj::HttpMethod::GET);\n    jsRequest->nullifyBody();\n  } else {\n    // Reconstruct the stream from our buffer. The spec does not specify that we should cancel the\n    // current body transmission in HTTP/1.1, so I'm not neutering the stream. (For HTTP/2 it asks\n    // us to send a RST_STREAM frame if possible.)\n    //\n    // We know `buffer` is non-null here because we checked `buffer`'s nullness when non-303, and\n    // nulled out `impl` when 303. Combined, they guarantee that we have a backing buffer.\n    jsRequest->rewindBody(js);\n  }\n\n  // No need to wait for output locks again when following a redirect, because we didn't interact\n  // with the app state in any way.\n  return fetchImplNoOutputLock(js, kj::mv(fetcher), kj::mv(jsRequest), kj::mv(urlList));\n}\n\n}  // namespace\n\njsg::Ref<Response> makeHttpResponse(jsg::Lock& js,\n    kj::HttpMethod method,\n    kj::Vector<kj::Url> urlListParam,\n    uint statusCode,\n    kj::StringPtr statusText,\n    const kj::HttpHeaders& headers,\n    kj::Own<kj::AsyncInputStream> body,\n    kj::Maybe<jsg::Ref<WebSocket>> webSocket,\n    Response::BodyEncoding bodyEncoding,\n    kj::Maybe<jsg::Ref<AbortSignal>> signal) {\n  auto responseHeaders = js.alloc<Headers>(js, headers, Headers::Guard::RESPONSE);\n  auto& context = IoContext::current();\n\n  // The Fetch spec defines responses to HEAD or CONNECT requests, or responses with null body\n  // statuses, as having null bodies.\n  // See https://fetch.spec.whatwg.org/#main-fetch step 21.\n  //\n  // Note that we don't handle the CONNECT case here because kj-http handles CONNECT specially,\n  // and the Fetch spec doesn't allow users to create Requests with CONNECT methods.\n  kj::Maybe<Body::ExtractedBody> responseBody = kj::none;\n  if (method != kj::HttpMethod::HEAD && !isNullBodyStatusCode(statusCode)) {\n    responseBody = Body::ExtractedBody(js.alloc<ReadableStream>(context,\n        newSystemStream(kj::mv(body),\n            getContentEncoding(context, headers, bodyEncoding, FeatureFlags::get(js)))));\n  }\n\n  // The Fetch spec defines \"response URLs\" as having no fragments. Since the last URL in the list\n  // is the one reported by Response::getUrl(), we nullify its fragment before serialization.\n  kj::Array<kj::String> urlList;\n  if (!urlListParam.empty()) {\n    urlListParam.back().fragment = kj::none;\n    urlList = KJ_MAP(url, urlListParam) { return url.toString(); };\n  }\n\n  // TODO(someday): Fill response CF blob from somewhere?\n  kj::Maybe<kj::String> maybeStatusText = statusText == defaultStatusText(statusCode)\n      ? kj::Maybe<kj::String>()\n      : kj::str(statusText);\n  return js.alloc<Response>(js, statusCode, kj::mv(maybeStatusText), kj::mv(responseHeaders),\n      nullptr, kj::mv(responseBody), kj::mv(urlList), kj::mv(webSocket), bodyEncoding);\n}\n\nnamespace {\n\njsg::Promise<jsg::Ref<Response>> fetchImplNoOutputLock(jsg::Lock& js,\n    kj::Maybe<jsg::Ref<Fetcher>> fetcher,\n    Request::Info requestOrUrl,\n    jsg::Optional<Request::Initializer> requestInit) {\n  // This use of evalNow() is obsoleted by the capture_async_api_throws compatibility flag, but\n  // we need to keep it here for people who don't have that flag set.\n  return js.evalNow([&] {\n    // The spec requires us to call Request's constructor here, so we do. This is unfortunate, but\n    // important for a few reasons:\n    //\n    // 1. If Request's constructor would throw, we must throw here, too.\n    // 2. If `requestOrUrl` is a Request object, we must disturb its body immediately and leave it\n    //    disturbed. The typical fetch() call will do this naturally, except those which encounter\n    //    303 redirects: they become GET requests with null bodies, which could then be reused.\n    // 3. Following from the previous point, we must not allow the original request's method to\n    //    mutate.\n    //\n    // We could emulate these behaviors with various hacks, but just reconstructing the request up\n    // front is robust, and won't add significant overhead compared to the rest of fetch().\n    auto jsRequest = Request::constructor(js, kj::mv(requestOrUrl), kj::mv(requestInit));\n\n    // Clear the request's signal if the 'ignoreForSubrequests' flag is set. This happens when\n    // a request from an incoming fetch is passed-through to another fetch. We want to avoid\n    // aborting the subrequest in that case.\n    jsRequest->clearSignalIfIgnoredForSubrequest(js);\n\n    // This URL list keeps track of redirections and becomes a source for Response's URL list. The\n    // first URL in the list is the Request's URL (visible to JS via Request::getUrl()). The last URL\n    // in the list is the Request's \"current\" URL (eventually visible to JS via Response::getUrl()).\n    auto urlList = kj::Vector<kj::Url>(1 + MAX_REDIRECT_COUNT);\n\n    jsg::Ref<Fetcher> actualFetcher = nullptr;\n    KJ_IF_SOME(f, fetcher) {\n      actualFetcher = kj::mv(f);\n    } else KJ_IF_SOME(f, jsRequest->getFetcher()) {\n      actualFetcher = kj::mv(f);\n    } else {\n      actualFetcher =\n          js.alloc<Fetcher>(IoContext::NULL_CLIENT_CHANNEL, Fetcher::RequiresHostAndProtocol::YES);\n    }\n\n    KJ_IF_SOME(dataUrl, DataUrl::tryParse(jsRequest->getUrl())) {\n      // If the URL is a data URL, we need to handle it specially.\n      kj::Maybe<kj::Array<kj::byte>> maybeResponseBody;\n\n      // The Fetch spec defines responses to HEAD or CONNECT requests, or responses with null body\n      // statuses, as having null bodies.\n      // See https://fetch.spec.whatwg.org/#main-fetch step 21.\n      //\n      // Note that we don't handle the CONNECT case here because kj-http handles CONNECT specially,\n      // and the Fetch spec doesn't allow users to create Requests with CONNECT methods.\n      if (jsRequest->getMethodEnum() == kj::HttpMethod::GET) {\n        maybeResponseBody.emplace(dataUrl.releaseData());\n      }\n\n      auto headers = js.alloc<Headers>();\n      headers->setCommon(capnp::CommonHeaderName::CONTENT_TYPE, dataUrl.getMimeType().toString());\n      return js.resolvedPromise(Response::constructor(js, kj::mv(maybeResponseBody),\n          Response::InitializerDict{\n            .status = 200,\n            .headers = kj::mv(headers),\n          }));\n    }\n\n    urlList.add(actualFetcher->parseUrl(js, jsRequest->getUrl()));\n    return fetchImplNoOutputLock(js, kj::mv(actualFetcher), kj::mv(jsRequest), kj::mv(urlList));\n  });\n}\n\n}  // namespace\n\njsg::Promise<jsg::Ref<Response>> fetchImpl(jsg::Lock& js,\n    kj::Maybe<jsg::Ref<Fetcher>> fetcher,\n    Request::Info requestOrUrl,\n    jsg::Optional<Request::Initializer> requestInit) {\n  auto& context = IoContext::current();\n  // Optimization: For non-actors, which never have output locks, avoid the overhead of\n  // awaitIo() and such by not going back to the event loop at all.\n  KJ_IF_SOME(promise, context.waitForOutputLocksIfNecessary()) {\n    return context.awaitIo(js, kj::mv(promise),\n        [fetcher = kj::mv(fetcher), requestOrUrl = kj::mv(requestOrUrl),\n            requestInit = kj::mv(requestInit)](jsg::Lock& js) mutable {\n      return fetchImplNoOutputLock(js, kj::mv(fetcher), kj::mv(requestOrUrl), kj::mv(requestInit));\n    });\n  } else {\n    return fetchImplNoOutputLock(js, kj::mv(fetcher), kj::mv(requestOrUrl), kj::mv(requestInit));\n  }\n}\n\njsg::Ref<Socket> Fetcher::connect(\n    jsg::Lock& js, AnySocketAddress address, jsg::Optional<SocketOptions> options) {\n  return connectImpl(js, JSG_THIS, kj::mv(address), kj::mv(options));\n}\n\njsg::Promise<jsg::Ref<Response>> Fetcher::fetch(jsg::Lock& js,\n    kj::OneOf<jsg::Ref<Request>, kj::String> requestOrUrl,\n    jsg::Optional<kj::OneOf<RequestInitializerDict, jsg::Ref<Request>>> requestInit) {\n  return fetchImpl(js, JSG_THIS, kj::mv(requestOrUrl), kj::mv(requestInit));\n}\n\nkj::Maybe<jsg::Ref<JsRpcProperty>> Fetcher::getRpcMethod(jsg::Lock& js, kj::String name) {\n  // This is like JsRpcStub::getRpcMethod(), but we also initiate a whole new JS RPC session\n  // each time the method is called (handled by `getClientForOneCall()`, below).\n\n  auto flags = FeatureFlags::get(js);\n  if (!flags.getFetcherRpc() && !flags.getWorkerdExperimental()) {\n    // We need to pretend that we haven't implemented a wildcard property, as unfortunately it\n    // breaks some workers in the wild. We would, however, like to warn users who are trying to use\n    // RPC so they understand why it isn't working.\n\n    if (name == \"idFromName\") {\n      // HACK specifically for itty-durable: We will not write any warning here, since itty-durable\n      // automatically checks for this property on all bindings in an effort to discover Durable\n      // Object namespaces. The warning would be confusing.\n      //\n      // Reported here: https://github.com/kwhitley/itty-durable/issues/48\n    } else {\n      IoContext::current().logWarningOnce(kj::str(\"WARNING: Tried to access method or property '\",\n          name,\n          \"' on a Service Binding or \"\n          \"Durable Object stub. Are you trying to use RPC? If so, please enable the 'rpc' compat \"\n          \"flag or update your compat date to 2024-04-03 or later (see \"\n          \"https://developers.cloudflare.com/workers/configuration/compatibility-dates/ ). If you \"\n          \"are not trying to use RPC, please note that in the future, this property (and all other \"\n          \"property names) will appear to be present as an RPC method.\"));\n    }\n\n    return kj::none;\n  }\n\n  return getRpcMethodInternal(js, kj::mv(name));\n}\n\nkj::Maybe<jsg::Ref<JsRpcProperty>> Fetcher::getRpcMethodInternal(jsg::Lock& js, kj::String name) {\n  // Same as getRpcMethod, but skips compatibility check to allow RPC to be used from bindings\n  // attached to workers without rpc flag.\n\n  // Do not return a method for `then`, otherwise JavaScript decides this is a thenable, i.e. a\n  // custom Promise, which will mean a Promise that resolves to this object will attempt to chain\n  // with it, which is not what you want!\n  if (name == \"then\"_kj) return kj::none;\n\n  return js.alloc<JsRpcProperty>(JSG_THIS, kj::mv(name));\n}\n\nrpc::JsRpcTarget::Client Fetcher::getClientForOneCall(\n    jsg::Lock& js, kj::Vector<kj::StringPtr>& path) {\n  auto& ioContext = IoContext::current();\n  auto worker = getClient(ioContext, kj::none, \"jsRpcSession\"_kjc);\n  auto event = kj::heap<api::JsRpcSessionCustomEvent>(\n      JsRpcSessionCustomEvent::WORKER_RPC_EVENT_TYPE);\n\n  auto result = event->getCap();\n\n  // Arrange to cancel the CustomEvent if our I/O context is destroyed. But otherwise, we don't\n  // actually care about the result of the event. If it throws, the membrane will already have\n  // propagated the exception to any RPC calls that we're waiting on, so we even ignore errors\n  // here -- otherwise they'll end up logged as \"uncaught exceptions\" even if they were, in fact,\n  // caught elsewhere.\n  ioContext.addTask(worker->customEvent(kj::mv(event)).attach(kj::mv(worker)).then([](auto&&) {\n  }, [](kj::Exception&&) {}));\n\n  // (Don't extend `path` because we're the root.)\n\n  return result;\n}\n\nvoid Fetcher::serialize(jsg::Lock& js, jsg::Serializer& serializer) {\n  auto channel = getSubrequestChannel(IoContext::current());\n  channel->requireAllowsTransfer();\n\n  KJ_IF_SOME(handler, serializer.getExternalHandler()) {\n    KJ_IF_SOME(frankenvalueHandler, kj::tryDowncast<Frankenvalue::CapTableBuilder>(handler)) {\n      // Encoding a Frankenvalue (e.g. for dynamic loopback props or dynamic isolate env).\n      serializer.writeRawUint32(frankenvalueHandler.add(kj::mv(channel)));\n      return;\n    } else KJ_IF_SOME(rpcHandler, kj::tryDowncast<RpcSerializerExternalHandler>(handler)) {\n      JSG_REQUIRE(FeatureFlags::get(js).getWorkerdExperimental(), DOMDataCloneError,\n          \"ServiceStub serialization requires the 'experimental' compat flag.\");\n\n      auto token = channel->getToken(IoChannelFactory::ChannelTokenUsage::RPC);\n      rpcHandler.write([token = kj::mv(token)](rpc::JsValue::External::Builder builder) {\n        builder.setSubrequestChannelToken(token);\n      });\n      return;\n    }\n    // TODO(someday): structuredClone() should have special handling that just reproduces the same\n    //   local object. At present we have no way to recognize structuredClone() here though.\n  }\n\n  // The allow_irrevocable_stub_storage flag allows us to just embed the token inline. This format\n  // is temporary, anyone using this will lose their data later.\n  JSG_REQUIRE(FeatureFlags::get(js).getAllowIrrevocableStubStorage(), DOMDataCloneError,\n      \"ServiceStub cannot be serialized in this context.\");\n  serializer.writeLengthDelimited(channel->getToken(IoChannelFactory::ChannelTokenUsage::STORAGE));\n}\n\njsg::Ref<Fetcher> Fetcher::deserialize(jsg::Lock& js,\n    rpc::SerializationTag tag, jsg::Deserializer& deserializer) {\n  KJ_IF_SOME(handler, deserializer.getExternalHandler()) {\n    KJ_IF_SOME(frankenvalueHandler, kj::tryDowncast<Frankenvalue::CapTableReader>(handler)) {\n      // Decoding a Frankenvalue (e.g. for dynamic loopback props or dynamic isolate env).\n      auto& cap = KJ_REQUIRE_NONNULL(frankenvalueHandler.get(deserializer.readRawUint32()),\n          \"serialized ServiceStub had invalid cap table index\");\n\n      KJ_IF_SOME(channel, kj::tryDowncast<IoChannelFactory::SubrequestChannel>(cap)) {\n        // Probably decoding dynamic ctx.props.\n        return js.alloc<Fetcher>(IoContext::current().addObject(kj::addRef(channel)));\n      } else KJ_IF_SOME(channel, kj::tryDowncast<IoChannelCapTableEntry>(cap)) {\n        // Probably decoding dynamic isolate env.\n        return js.alloc<Fetcher>(\n            channel.getChannelNumber(IoChannelCapTableEntry::Type::SUBREQUEST),\n            RequiresHostAndProtocol::YES, /*isInHouse=*/false);\n      } else {\n        KJ_FAIL_REQUIRE(\"ServiceStub capability in Frankenvalue is not a SubrequestChannel?\");\n      }\n    } else KJ_IF_SOME(rpcHandler, kj::tryDowncast<RpcDeserializerExternalHandler>(handler)) {\n      JSG_REQUIRE(FeatureFlags::get(js).getWorkerdExperimental(), DOMDataCloneError,\n          \"ServiceStub serialization requires the 'experimental' compat flag.\");\n\n      auto external = rpcHandler.read();\n      KJ_REQUIRE(external.isSubrequestChannelToken());\n      auto& ioctx = IoContext::current();\n      auto channel = ioctx.getIoChannelFactory().subrequestChannelFromToken(\n          IoChannelFactory::ChannelTokenUsage::RPC,\n          external.getSubrequestChannelToken());\n      return js.alloc<Fetcher>(ioctx.addObject(kj::mv(channel)));\n    }\n  }\n\n  // The allow_irrevocable_stub_storage flag allows us to just embed the token inline. This format\n  // is temporary, anyone using this will lose their data later.\n  JSG_REQUIRE(FeatureFlags::get(js).getAllowIrrevocableStubStorage(), DOMDataCloneError,\n      \"ServiceStub cannot be deserialized in this context.\");\n  auto& ioctx = IoContext::current();\n  auto channel = ioctx.getIoChannelFactory().subrequestChannelFromToken(\n      IoChannelFactory::ChannelTokenUsage::STORAGE, deserializer.readLengthDelimitedBytes());\n  return js.alloc<Fetcher>(ioctx.addObject(kj::mv(channel)));\n}\n\nstatic jsg::Promise<void> throwOnError(\n    jsg::Lock& js, kj::StringPtr method, jsg::Promise<jsg::Ref<Response>> promise) {\n  return promise.then(js, [method](jsg::Lock&, jsg::Ref<Response> response) {\n    uint status = response->getStatus();\n    // TODO(someday): Would be nice to attach the response to the JavaScript error, maybe? Or\n    //   should people really use fetch() if they want to inspect error responses?\n    JSG_REQUIRE(status >= 200 && status < 300, Error,\n        kj::str(\"HTTP \", method, \" request failed: \", response->getStatus(), \" \",\n            response->getStatusText()));\n  });\n}\n\nstatic jsg::Promise<Fetcher::GetResult> parseResponse(\n    jsg::Lock& js, jsg::Ref<Response> response, jsg::Optional<kj::String> type) {\n  auto typeName =\n      type.map([](const kj::String& s) -> kj::StringPtr { return s; }).orDefault(\"text\");\n  if (typeName == \"stream\") {\n    KJ_IF_SOME(body, response->getBody()) {\n      return js.resolvedPromise(Fetcher::GetResult(kj::mv(body)));\n    } else {\n      // Empty body.\n      return js.resolvedPromise(Fetcher::GetResult(js.alloc<ReadableStream>(\n          IoContext::current(), newSystemStream(newNullInputStream(), StreamEncoding::IDENTITY))));\n    }\n  }\n\n  if (typeName == \"text\") {\n    return response->text(js).then(js, [response = kj::mv(response)](jsg::Lock&, auto x) {\n      return Fetcher::GetResult(kj::mv(x));\n    });\n  } else if (typeName == \"arrayBuffer\") {\n    return response->arrayBuffer(js).then(js, [response = kj::mv(response)](jsg::Lock&, auto x) {\n      return Fetcher::GetResult(kj::mv(x));\n    });\n  } else if (typeName == \"json\") {\n    return response->json(js).then(js, [response = kj::mv(response)](jsg::Lock&, auto x) {\n      return Fetcher::GetResult(kj::mv(x));\n    });\n  } else {\n    JSG_FAIL_REQUIRE(TypeError,\n        \"Unknown response type. Possible types are \\\"text\\\", \\\"arrayBuffer\\\", \"\n        \"\\\"json\\\", and \\\"stream\\\".\");\n  }\n}\n\njsg::Promise<Fetcher::GetResult> Fetcher::get(\n    jsg::Lock& js, kj::String url, jsg::Optional<kj::String> type) {\n  RequestInitializerDict subInit;\n  subInit.method = kj::str(\"GET\");\n\n  return fetchImpl(js, JSG_THIS, kj::mv(url), kj::mv(subInit))\n      .then(js,\n          [type = kj::mv(type)](\n              jsg::Lock& js, jsg::Ref<Response> response) mutable -> jsg::Promise<GetResult> {\n    uint status = response->getStatus();\n    if (status == 404 || status == 410) {\n      return js.resolvedPromise(GetResult(js.v8Ref(js.v8Null())));\n    } else if (!response->getOk()) {\n      // Manually construct exception so that we can incorporate method and status into the text\n      // that JavaScript sees.\n      // TODO(someday): Would be nice to attach the response to the JavaScript error, maybe? Or\n      //   should people really use fetch() if they want to inspect error responses?\n      JSG_FAIL_REQUIRE(Error,\n          kj::str(\n              \"HTTP GET request failed: \", response->getStatus(), \" \", response->getStatusText()));\n    } else {\n      return parseResponse(js, kj::mv(response), kj::mv(type));\n    }\n  });\n}\n\njsg::Promise<void> Fetcher::put(jsg::Lock& js,\n    kj::String url,\n    Body::Initializer body,\n    jsg::Optional<Fetcher::PutOptions> options) {\n  // Note that this borrows liberally from fetchImpl(fetcher, request, init, isolate).\n  // This use of evalNow() is obsoleted by the capture_async_api_throws compatibility flag, but\n  // we need to keep it here for people who don't have that flag set.\n  return throwOnError(js, \"PUT\", js.evalNow([&] {\n    RequestInitializerDict subInit;\n    subInit.method = kj::str(\"PUT\");\n    subInit.body = kj::mv(body);\n    auto jsRequest = Request::constructor(js, kj::mv(url), kj::mv(subInit));\n    auto urlList = kj::Vector<kj::Url>(1 + MAX_REDIRECT_COUNT);\n\n    kj::Url parsedUrl = this->parseUrl(js, jsRequest->getUrl());\n\n    // If any optional parameters were specified by the client, append them to\n    // the URL's query parameters.\n    KJ_IF_SOME(o, options) {\n      KJ_IF_SOME(expiration, o.expiration) {\n        parsedUrl.query.add(kj::Url::QueryParam{kj::str(\"expiration\"), kj::str(expiration)});\n      }\n      KJ_IF_SOME(expirationTtl, o.expirationTtl) {\n        parsedUrl.query.add(kj::Url::QueryParam{kj::str(\"expiration_ttl\"), kj::str(expirationTtl)});\n      }\n    }\n\n    urlList.add(kj::mv(parsedUrl));\n    return fetchImpl(js, JSG_THIS, kj::mv(jsRequest), kj::mv(urlList));\n  }));\n}\n\njsg::Promise<void> Fetcher::delete_(jsg::Lock& js, kj::String url) {\n  RequestInitializerDict subInit;\n  subInit.method = kj::str(\"DELETE\");\n  return throwOnError(js, \"DELETE\", fetchImpl(js, JSG_THIS, kj::mv(url), kj::mv(subInit)));\n}\n\njsg::Promise<Fetcher::QueueResult> Fetcher::queue(\n    jsg::Lock& js, kj::String queueName, kj::Array<ServiceBindingQueueMessage> messages) {\n  auto& ioContext = IoContext::current();\n\n  auto encodedMessages = kj::heapArrayBuilder<IncomingQueueMessage>(messages.size());\n  for (auto& msg: messages) {\n    KJ_IF_SOME(b, msg.body) {\n      JSG_REQUIRE(msg.serializedBody == kj::none, TypeError,\n          \"Expected one of body or serializedBody for each message\");\n      jsg::Serializer serializer(js,\n          jsg::Serializer::Options{\n            .version = 15,\n            .omitHeader = false,\n          });\n      serializer.write(js, jsg::JsValue(b.getHandle(js)));\n      encodedMessages.add(IncomingQueueMessage{.id = kj::mv(msg.id),\n        .timestamp = msg.timestamp,\n        .body = serializer.release().data,\n        .attempts = msg.attempts});\n    } else KJ_IF_SOME(b, msg.serializedBody) {\n      encodedMessages.add(IncomingQueueMessage{.id = kj::mv(msg.id),\n        .timestamp = msg.timestamp,\n        .body = kj::mv(b),\n        .attempts = msg.attempts});\n    } else {\n      JSG_FAIL_REQUIRE(TypeError, \"Expected one of body or serializedBody for each message\");\n    }\n  }\n\n  // Only create worker interface after the error checks above to reduce overhead in case of errors.\n  auto worker = getClient(ioContext, kj::none, \"queue\"_kjc);\n  auto event = kj::refcounted<api::QueueCustomEvent>(QueueEvent::Params{\n    .queueName = kj::mv(queueName),\n    .messages = encodedMessages.finish(),\n  });\n\n  auto eventRef =\n      kj::addRef(*event);  // attempt to work around windows-specific null pointer deref.\n  return ioContext.awaitIo(js, worker->customEvent(kj::mv(eventRef)).attach(kj::mv(worker)),\n      [event = kj::mv(event)](jsg::Lock& js, WorkerInterface::CustomEvent::Result result) {\n    return Fetcher::QueueResult{\n      .outcome = kj::str(result.outcome),\n      .ackAll = event->getAckAll(),\n      .retryBatch = event->getRetryBatch(),\n      .explicitAcks = event->getExplicitAcks(),\n      .retryMessages = event->getRetryMessages(),\n    };\n  });\n}\n\njsg::Promise<Fetcher::ScheduledResult> Fetcher::scheduled(\n    jsg::Lock& js, jsg::Optional<ScheduledOptions> options) {\n  auto& ioContext = IoContext::current();\n  auto worker = getClient(ioContext, kj::none, \"scheduled\"_kjc);\n\n  auto scheduledTime = ioContext.now();\n  auto cron = kj::String();\n  KJ_IF_SOME(o, options) {\n    KJ_IF_SOME(t, o.scheduledTime) {\n      scheduledTime = t;\n    }\n    KJ_IF_SOME(c, o.cron) {\n      cron = kj::mv(c);\n    }\n  }\n\n  return ioContext.awaitIo(js,\n      worker->runScheduled(scheduledTime, cron).attach(kj::mv(worker), kj::mv(cron)),\n      [](jsg::Lock& js, WorkerInterface::ScheduledResult result) {\n    return Fetcher::ScheduledResult{\n      .outcome = kj::str(result.outcome),\n      .noRetry = !result.retry,\n    };\n  });\n}\n\nkj::Own<WorkerInterface> Fetcher::getClient(\n    IoContext& ioContext, kj::Maybe<kj::String> cfStr, kj::ConstString operationName) {\n  auto clientWithTracing = getClientWithTracing(ioContext, kj::mv(cfStr), kj::mv(operationName));\n  return clientWithTracing.client.attach(kj::mv(clientWithTracing.traceContext));\n}\n\nFetcher::ClientWithTracing Fetcher::getClientWithTracing(\n    IoContext& ioContext, kj::Maybe<kj::String> cfStr, kj::ConstString operationName) {\n  KJ_SWITCH_ONEOF(channelOrClientFactory) {\n    KJ_CASE_ONEOF(channel, uint) {\n      // For channels, create trace context\n      auto traceContext = ioContext.makeUserTraceSpan(kj::mv(operationName));\n      auto client = ioContext.getSubrequestChannel(channel, isInHouse, kj::mv(cfStr), traceContext);\n      return ClientWithTracing{kj::mv(client), kj::mv(traceContext)};\n    }\n    KJ_CASE_ONEOF(channel, IoOwn<IoChannelFactory::SubrequestChannel>) {\n      auto traceContext = ioContext.makeUserTraceSpan(kj::mv(operationName));\n      auto client = ioContext.getSubrequest(\n          [&](TraceContext& tracing, IoChannelFactory& ioChannelFactory) {\n        return channel->startRequest({.cfBlobJson = kj::mv(cfStr), .parentSpan = tracing.getInternalSpanParent()});\n      }, {\n        .inHouse = isInHouse,\n        .wrapMetrics = !isInHouse,\n        .existingTraceContext = traceContext,\n      });\n      return ClientWithTracing{kj::mv(client), kj::mv(traceContext)};\n    }\n    KJ_CASE_ONEOF(outgoingFactory, IoOwn<OutgoingFactory>) {\n      // For outgoing factories, no trace context needed\n      auto client = outgoingFactory->newSingleUseClient(kj::mv(cfStr));\n      return ClientWithTracing{kj::mv(client), kj::none};\n    }\n    KJ_CASE_ONEOF(outgoingFactory, kj::Own<CrossContextOutgoingFactory>) {\n      // For cross-context outgoing factories, no trace context needed\n      auto client = outgoingFactory->newSingleUseClient(ioContext, kj::mv(cfStr));\n      return ClientWithTracing{kj::mv(client), kj::none};\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::Own<IoChannelFactory::SubrequestChannel> Fetcher::getSubrequestChannel(IoContext& ioContext) {\n  KJ_SWITCH_ONEOF(channelOrClientFactory) {\n    KJ_CASE_ONEOF(channel, uint) {\n      return ioContext.getIoChannelFactory().getSubrequestChannel(channel);\n    }\n    KJ_CASE_ONEOF(channel, IoOwn<IoChannelFactory::SubrequestChannel>) {\n      return kj::addRef(*channel);\n    }\n    KJ_CASE_ONEOF(outgoingFactory, IoOwn<OutgoingFactory>) {\n      return outgoingFactory->getSubrequestChannel();\n    }\n    KJ_CASE_ONEOF(outgoingFactory, kj::Own<CrossContextOutgoingFactory>) {\n      return outgoingFactory->getSubrequestChannel(ioContext);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::Url Fetcher::parseUrl(jsg::Lock& js, kj::StringPtr url) {\n  // We need to prep the request's URL for transmission over HTTP. fetch() accepts URLs that have\n  // \".\" and \"..\" components as well as fragments (stuff after '#'), all of which needs to be\n  // removed/collapsed before the URL is HTTP-ready. Luckily our URL parser does all this if we\n  // tell it the context is REMOTE_HREF.\n  constexpr auto urlOptions = kj::Url::Options{.percentDecode = false, .allowEmpty = true};\n  kj::Maybe<kj::Url> maybeParsed;\n  if (this->requiresHost == RequiresHostAndProtocol::YES) {\n    maybeParsed = kj::Url::tryParse(url, kj::Url::REMOTE_HREF, urlOptions);\n  } else {\n    // We don't require a protocol nor hostname, but we accept them. The easiest way to implement\n    // this is to parse relative to a dummy URL.\n    static const kj::Url FAKE =\n        kj::Url::parse(\"https://fake-host/\", kj::Url::REMOTE_HREF, urlOptions);\n    maybeParsed = FAKE.tryParseRelative(url);\n  }\n\n  KJ_IF_SOME(p, maybeParsed) {\n    if (p.scheme != \"http\" && p.scheme != \"https\") {\n      // A non-HTTP scheme was requested. We should probably throw an exception, but historically\n      // we actually went ahead and passed `X-Forwarded-Proto: whatever` to FL, which it happily\n      // ignored if the protocol specified was not \"https\". Whoops. Unfortunately, some workers\n      // in production have grown dependent on the bug. We'll have to use a runtime versioning flag\n      // to fix this.\n\n      if (FeatureFlags::get(js).getFetchRefusesUnknownProtocols()) {\n        // Backwards-compatibility flag not enabled, so just fail.\n        JSG_FAIL_REQUIRE(TypeError, kj::str(\"Fetch API cannot load: \", url));\n      }\n\n      if (p.scheme != nullptr && '0' <= p.scheme[0] && p.scheme[0] <= '9') {\n        // First character of the scheme is a digit. This is a weird case: Normally the KJ URL\n        // parser would treat a scheme starting with a digit as invalid. But, due to a bug,\n        // `tryParseRelative()` does NOT treat it as invalid. So, we know we took the branch above\n        // that used `tryParseRelative()` above. In any case, later stages of the runtime will\n        // definitely try to parse this URL again and will reject it at that time, producing an\n        // internal error. We might as well throw a transparent error here instead so that we don't\n        // log a garbage sentry alert.\n        JSG_FAIL_REQUIRE(TypeError, kj::str(\"Fetch API cannot load: \", url));\n      }\n\n      // In preview, log a warning in hopes that people fix this.\n      kj::StringPtr more = nullptr;\n      if (p.scheme == \"ws\" || p.scheme == \"wss\") {\n        // Include some extra text for ws:// and wss:// specifically, since this is the most common\n        // mistake.\n        more = \" Note that fetch() treats WebSockets as a special kind of HTTP request, \"\n               \"therefore WebSockets should use 'http:'/'https:', not 'ws:'/'wss:'.\";\n      } else if (p.scheme == \"ftp\") {\n        // Include some extra text for ftp://, since we see this sometimes.\n        more = \" fetch() does not support the FTP protocol.\";\n      }\n      IoContext::current().logWarning(\n          kj::str(\"Worker passed an invalid URL to fetch(). URLs passed to fetch() must begin with \"\n                  \"either 'http:' or 'https:', not '\",\n              p.scheme,\n              \":'. Due to a historical bug, any \"\n              \"other protocol used here will be treated the same as 'http:'. We plan to correct \"\n              \"this bug in the future, so please update your Worker to use 'http:' or 'https:' for \"\n              \"all fetch() URLs.\",\n              more));\n    }\n\n    return kj::mv(p);\n  } else {\n    JSG_FAIL_REQUIRE(TypeError, kj::str(\"Fetch API cannot load: \", url));\n  }\n}\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/http.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"basics.h\"\n#include \"blob.h\"\n#include \"cf-property.h\"\n#include \"form-data.h\"\n#include \"headers.h\"\n#include \"queue.h\"\n#include \"web-socket.h\"\n#include \"worker-rpc.h\"\n\n#include <workerd/api/streams/readable.h>\n#include <workerd/api/url-standard.h>\n#include <workerd/api/url.h>\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/jsg/async-context.h>\n#include <workerd/jsg/jsg.h>\n\n#include <kj/compat/http.h>\n\nnamespace workerd::api {\n\n// Base class for Request and Response. In JavaScript, this class is a mixin, meaning no one will\n// be instantiating objects of this type -- it exists solely to house body-related functionality\n// common to both Requests and Responses.\nclass Body: public jsg::Object {\n public:\n  // The types of objects from which a Body can be created.\n  //\n  // If the object is a ReadableStream, Body will adopt it directly; otherwise the object is some\n  // sort of buffer-like source. In this case, Body will store its own ReadableStream that wraps the\n  // source, and it keeps a reference to the source object around. This allows Requests and\n  // Responses created from Strings, ArrayBuffers, FormDatas, Blobs, or URLSearchParams to be\n  // retransmitted.\n  //\n  // For an example of where this is important, consider a POST Request in redirect-follow mode and\n  // containing a body: if passed to a fetch() call that results in a 307 or 308 response, fetch()\n  // will re-POST to the new URL. If the body was constructed from a ReadableStream, this re-POST\n  // will fail, because there is no body source left. On the other hand, if the body was constructed\n  // from any of the other source types, Body can create a new ReadableStream from the source, and\n  // the POST will successfully retransmit.\n  using Initializer = kj::OneOf<jsg::Ref<ReadableStream>,\n      kj::String,\n      kj::Array<byte>,\n      jsg::Ref<Blob>,\n      jsg::Ref<FormData>,\n      jsg::Ref<URLSearchParams>,\n      jsg::Ref<url::URLSearchParams>,\n      jsg::AsyncGeneratorIgnoringStrings<jsg::Value>>;\n\n  struct RefcountedBytes final: public kj::Refcounted {\n    kj::Array<kj::byte> bytes;\n    RefcountedBytes(kj::Array<kj::byte>&& bytes): bytes(kj::mv(bytes)) {}\n    JSG_MEMORY_INFO(RefcountedBytes) {\n      tracker.trackFieldWithSize(\"bytes\", bytes.size());\n    }\n  };\n\n  // The Fetch spec calls this type the body's \"source\", even though it really is a buffer. I end\n  // talking about things like \"a buffer-backed body\", whereas in standardese I should say\n  // \"a body with a non-null source\".\n  //\n  // I find that confusing, so let's just call it what it is: a Body::Buffer.\n  struct Buffer {\n    // In order to reconstruct buffer-backed ReadableStreams without gratuitous array copying, we\n    // need to be able to tie the lifetime of the source buffer to the lifetime of the\n    // ReadableStream's native stream, AND the lifetime of the Body itself. Thus we need\n    // refcounting.\n    //\n    // NOTE: ownBytes may contain a v8::Global reference, hence instances of `Buffer` must exist\n    //   only within the V8 heap space.\n    kj::OneOf<kj::Own<RefcountedBytes>, jsg::Ref<Blob>> ownBytes;\n    // TODO(cleanup): When we integrate with V8's garbage collection APIs, we need to account for\n    //   that here.\n\n    // Bodies constructed from buffers rather than ReadableStreams can be retransmitted if necessary\n    // (e.g. for redirects, authentication). In these cases, we need to keep an ArrayPtr view onto\n    // the Array source itself, because the source may be a string, and thus have a trailing nul\n    // byte.\n    kj::ArrayPtr<const kj::byte> view;\n\n    Buffer() = default;\n    Buffer(kj::Array<kj::byte> array)\n        : ownBytes(kj::refcounted<RefcountedBytes>(kj::mv(array))),\n          view(ownBytes.get<kj::Own<RefcountedBytes>>()->bytes) {}\n    Buffer(kj::String string)\n        : ownBytes(kj::refcounted<RefcountedBytes>(string.releaseArray().releaseAsBytes())),\n          view([this] {\n            auto bytesIncludingNull = ownBytes.get<kj::Own<RefcountedBytes>>()->bytes.asPtr();\n            return bytesIncludingNull.first(bytesIncludingNull.size() - 1);\n          }()) {}\n    Buffer(jsg::Ref<Blob> blob)\n        : ownBytes(kj::mv(blob)),\n          view(ownBytes.get<jsg::Ref<Blob>>()->getData()) {}\n\n    Buffer clone(jsg::Lock& js);\n\n    JSG_MEMORY_INFO(Buffer) {\n      KJ_SWITCH_ONEOF(ownBytes) {\n        KJ_CASE_ONEOF(bytes, kj::Own<RefcountedBytes>) {\n          tracker.trackField(\"bytes\", bytes);\n        }\n        KJ_CASE_ONEOF(blob, jsg::Ref<Blob>) {\n          tracker.trackField(\"blob\", blob);\n        }\n      }\n    }\n  };\n\n  struct Impl {\n    jsg::Ref<ReadableStream> stream;\n    kj::Maybe<Buffer> buffer;\n    JSG_MEMORY_INFO(Impl) {\n      tracker.trackField(\"stream\", stream);\n      tracker.trackField(\"buffer\", buffer);\n    }\n  };\n\n  struct ExtractedBody {\n    ExtractedBody(jsg::Ref<ReadableStream> stream,\n        kj::Maybe<Buffer> source = kj::none,\n        kj::Maybe<kj::String> contentType = kj::none);\n\n    Impl impl;\n    kj::Maybe<kj::String> contentType;\n  };\n\n  // Implements the \"extract a body\" algorithm from the Fetch spec.\n  // https://fetch.spec.whatwg.org/#concept-bodyinit-extract\n  static ExtractedBody extractBody(jsg::Lock& js, Initializer init);\n\n  explicit Body(jsg::Lock& js, kj::Maybe<ExtractedBody> init, Headers& headers);\n\n  kj::Maybe<Buffer> getBodyBuffer(jsg::Lock& js);\n\n  // The following body rewind/nullification functions are helpers for implementing fetch() redirect\n  // handling.\n\n  // True if this body is null or buffer-backed, false if this body is a ReadableStream.\n  bool canRewindBody();\n\n  // Reconstruct this body from its backing buffer. Precondition: `canRewindBody() == true`.\n  void rewindBody(jsg::Lock& js);\n\n  // Convert this body into a null body.\n  void nullifyBody();\n\n  // ---------------------------------------------------------------------------\n  // JS API\n\n  kj::Maybe<jsg::Ref<ReadableStream>> getBody();\n  bool getBodyUsed();\n  jsg::Promise<jsg::BufferSource> arrayBuffer(jsg::Lock& js);\n  jsg::Promise<jsg::BufferSource> bytes(jsg::Lock& js);\n  jsg::Promise<kj::String> text(jsg::Lock& js);\n  jsg::Promise<jsg::Ref<FormData>> formData(jsg::Lock& js);\n  jsg::Promise<jsg::Value> json(jsg::Lock& js);\n  jsg::Promise<jsg::Ref<Blob>> blob(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(Body, CompatibilityFlags::Reader flags) {\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(body, getBody);\n      JSG_READONLY_PROTOTYPE_PROPERTY(bodyUsed, getBodyUsed);\n    } else {\n      JSG_READONLY_INSTANCE_PROPERTY(body, getBody);\n      JSG_READONLY_INSTANCE_PROPERTY(bodyUsed, getBodyUsed);\n    }\n    JSG_METHOD(arrayBuffer);\n    JSG_METHOD(bytes);\n    JSG_METHOD(text);\n    JSG_METHOD(json);\n    JSG_METHOD(formData);\n    JSG_METHOD(blob);\n\n    if (flags.getFetchIterableTypeSupport()) {\n      JSG_TS_DEFINE(type BodyInit = ReadableStream<Uint8Array> | string | ArrayBuffer | ArrayBufferView | Blob | URLSearchParams | FormData | Iterable<ArrayBuffer|ArrayBufferView> | AsyncIterable<ArrayBuffer|ArrayBufferView>);\n    } else {\n      JSG_TS_DEFINE(type BodyInit = ReadableStream<Uint8Array> | string | ArrayBuffer | ArrayBufferView | Blob | URLSearchParams | FormData);\n    }\n    // All type aliases get inlined when exporting RTTI, but this type alias is included by\n    // the official TypeScript types, so users might be depending on it.\n    JSG_TS_OVERRIDE({\n      json<T>(): Promise<T>;\n      bytes(): Promise<Uint8Array>;\n      arrayBuffer(): Promise<ArrayBuffer>;\n    });\n    // Allow JSON body type to be specified\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"impl\", impl);\n  }\n\n protected:\n  // Helper to implement Request/Response::clone().\n  kj::Maybe<ExtractedBody> clone(jsg::Lock& js);\n\n private:\n  kj::Maybe<Impl> impl;\n\n  // HACK: This `headersRef` variable refers to a Headers object in the Request/Response subclass.\n  //   As such, it will briefly dangle during object destruction. While unlikely to be an issue,\n  //   it's worth being aware of.\n  Headers& headersRef;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    KJ_IF_SOME(i, impl) {\n      visitor.visit(i.stream);\n    }\n  }\n};\n\n// Controls how response bodies are encoded/decoded according to Content-Encoding headers\nenum class Response_BodyEncoding {\n  AUTO,   // Automatically encode/decode based on Content-Encoding headers\n  MANUAL  // Treat Content-Encoding headers as opaque (no automatic encoding/decoding)\n};\n\nclass Request;\nclass Response;\nstruct RequestInitializerDict;\n\nclass Socket;\nstruct SocketOptions;\nstruct SocketAddress;\nusing AnySocketAddress = kj::OneOf<SocketAddress, kj::String>;\n\n// Represents a client to a remote \"web service\".\n//\n// Originally, this meant an HTTP service, and `Fetcher` had just one method, `fetch()`, hence the\n// name. However, `Fetcher` is really the JavaScript type for a `WorkerInterface`, and is used in\n// particular to represent service bindings as well as Durable Object stubs. As such, as Workers\n// have grown new ways to talk to other Workers, `Fetcher` has added methods other than `fetch()`.\n//\n// TODO(cleanup): This probably doesn't belong in `http.h` anymore. And perhaps it should be\n//   renamed, though I haven't heard any great suggestions for what the name should be.\nclass Fetcher: public JsRpcClientProvider {\n public:\n  // Should we use a fake https base url if we lack a scheme+authority?\n  enum class RequiresHostAndProtocol { YES, NO };\n\n  // `channel` is what to pass to IoContext::getSubrequestChannel() to get a WorkerInterface\n  // representing this Fetcher. Note that different requests potentially have different client\n  // objects because a WorkerInterface is a KJ I/O object and therefore tied to a thread.\n  // Abstractly, within a worker instance, the same channel always refers to the same Fetcher, even\n  // though the WorkerInterface object changes from request to request.\n  //\n  // If `requiresHost` is false, then requests using this Fetcher are allowed to specify a\n  // URL that has no protocol or host.\n  //\n  // See pipeline.capnp or request-context.h for an explanation of `isInHouse`.\n  explicit Fetcher(uint channel, RequiresHostAndProtocol requiresHost, bool isInHouse = false)\n      : channelOrClientFactory(channel),\n        requiresHost(requiresHost),\n        isInHouse(isInHouse) {}\n\n  // Create a Fetcher bound to an IoChannelFactory::SubrequestChannel object rather than a numeric\n  // channel. This Fetcher will inherently be bound to the current I/O context.\n  explicit Fetcher(IoOwn<IoChannelFactory::SubrequestChannel> subrequestChannel,\n      RequiresHostAndProtocol requiresHost = RequiresHostAndProtocol::YES,\n      bool isInHouse = false)\n      : channelOrClientFactory(kj::mv(subrequestChannel)),\n        requiresHost(requiresHost),\n        isInHouse(isInHouse) {}\n\n  // Used by Fetchers that use ad-hoc, single-use WorkerInterface instances, such as ones\n  // created for Actors.\n  //\n  // TODO(cleanup): Consider removing this in favor of `IoChannelFactory::SubrequestChannel`, which\n  //   is almost the same thing.\n  class OutgoingFactory {\n   public:\n    virtual kj::Own<WorkerInterface> newSingleUseClient(kj::Maybe<kj::String> cfStr) = 0;\n\n    // Get a `SubrequestChannel` representing this Fetcher. This is used especially when the\n    // Fetcher is being passed to another isolate.\n    virtual kj::Own<IoChannelFactory::SubrequestChannel> getSubrequestChannel() {\n      // TODO(soon): Update all implementations and remove this default implementation.\n      KJ_UNIMPLEMENTED(\"this Fetcher doesn't yet implement getSubrequestChannel()\");\n    }\n  };\n\n  // Used by Fetchers that obtain their HttpClient in a custom way, but which aren't tied\n  // to a specific I/O context. The factory object moves with the isolate across threads and\n  // contexts, and must work from any context.\n  class CrossContextOutgoingFactory {\n   public:\n    virtual kj::Own<WorkerInterface> newSingleUseClient(\n        IoContext& context, kj::Maybe<kj::String> cfStr) = 0;\n\n    virtual kj::Own<IoChannelFactory::SubrequestChannel> getSubrequestChannel(IoContext& context) {\n      // TODO(soon): Update all implementations and remove this default implementation.\n      KJ_UNIMPLEMENTED(\"this Fetcher doesn't yet implement getSubrequestChannel()\");\n    }\n  };\n\n  // `outgoingFactory` is used for Fetchers that use ad-hoc WorkerInterface instances, such as ones\n  // created for Actors.\n  Fetcher(IoOwn<OutgoingFactory> outgoingFactory,\n      RequiresHostAndProtocol requiresHost,\n      bool isInHouse = false)\n      : channelOrClientFactory(kj::mv(outgoingFactory)),\n        requiresHost(requiresHost),\n        isInHouse(isInHouse) {}\n\n  // `outgoingFactory` is used for Fetchers that use ad-hoc WorkerInterface instances, but doesn't\n  // require an IoContext\n  Fetcher(kj::Own<CrossContextOutgoingFactory> outgoingFactory,\n      RequiresHostAndProtocol requiresHost,\n      bool isInHouse = false)\n      : channelOrClientFactory(kj::mv(outgoingFactory)),\n        requiresHost(requiresHost),\n        isInHouse(isInHouse) {}\n\n  // Returns an `WorkerInterface` that is only valid for the lifetime of the current\n  // `IoContext`.\n  kj::Own<WorkerInterface> getClient(\n      IoContext& ioContext, kj::Maybe<kj::String> cfStr, kj::ConstString operationName);\n\n  // Result of getClient call that includes optional trace context\n  struct ClientWithTracing {\n    kj::Own<WorkerInterface> client;\n    kj::Maybe<TraceContext> traceContext;\n  };\n\n  // Get client and optionally create trace context, all in one call\n  ClientWithTracing getClientWithTracing(\n      IoContext& ioContext, kj::Maybe<kj::String> cfStr, kj::ConstString operationName);\n\n  // Get a SubrequestChannel representing this Fetcher.\n  kj::Own<IoChannelFactory::SubrequestChannel> getSubrequestChannel(IoContext& ioContext);\n\n  // Wraps kj::Url::parse to take into account whether the Fetcher requires a host to be\n  // specified on URLs, Fetcher-specific URL decoding options, and error handling.\n  kj::Url parseUrl(jsg::Lock& js, kj::StringPtr url);\n\n  jsg::Ref<Socket> connect(\n      jsg::Lock& js, AnySocketAddress address, jsg::Optional<SocketOptions> options);\n\n  jsg::Promise<jsg::Ref<Response>> fetch(jsg::Lock& js,\n      kj::OneOf<jsg::Ref<Request>, kj::String> requestOrUrl,\n      jsg::Optional<kj::OneOf<RequestInitializerDict, jsg::Ref<Request>>> requestInit);\n\n  using GetResult = kj::OneOf<jsg::Ref<ReadableStream>, jsg::BufferSource, kj::String, jsg::Value>;\n\n  jsg::Promise<GetResult> get(jsg::Lock& js, kj::String url, jsg::Optional<kj::String> type);\n\n  // Optional parameter for passing options into a Fetcher::put. Initially\n  // intended for supporting expiration times in KV bindings.\n  struct PutOptions {\n    jsg::Optional<int> expiration;\n    jsg::Optional<int> expirationTtl;\n\n    JSG_STRUCT(expiration, expirationTtl);\n  };\n\n  jsg::Promise<void> put(\n      jsg::Lock& js, kj::String url, Body::Initializer body, jsg::Optional<PutOptions> options);\n\n  jsg::Promise<void> delete_(jsg::Lock& js, kj::String url);\n\n  // Representation of a queue message for use when invoking the queue() event handler on another\n  // worker via a service binding.\n  struct ServiceBindingQueueMessage {\n    kj::String id;\n    kj::Date timestamp;\n    jsg::Optional<jsg::Value> body;\n    jsg::Optional<kj::Array<kj::byte>> serializedBody;\n    uint16_t attempts;\n\n    JSG_STRUCT(id, timestamp, body, serializedBody, attempts);\n    JSG_STRUCT_TS_OVERRIDE(type ServiceBindingQueueMessage<Body = unknown> = {\n      id: string;\n      timestamp: Date;\n      attempts: number;\n    } & (\n      | { body: Body }\n      | { serializedBody: ArrayBuffer | ArrayBufferView }\n    ));\n  };\n\n  struct QueueResult {\n    kj::String outcome;\n    bool ackAll;\n    QueueRetryBatch retryBatch;\n    kj::Array<kj::String> explicitAcks;\n    kj::Array<QueueRetryMessage> retryMessages;\n    JSG_STRUCT(outcome, ackAll, retryBatch, explicitAcks, retryMessages);\n  };\n\n  jsg::Promise<QueueResult> queue(\n      jsg::Lock& js, kj::String queueName, kj::Array<ServiceBindingQueueMessage> messages);\n\n  struct ScheduledOptions {\n    jsg::Optional<kj::Date> scheduledTime;\n    jsg::Optional<kj::String> cron;\n\n    JSG_STRUCT(scheduledTime, cron);\n  };\n\n  struct ScheduledResult {\n    kj::String outcome;\n    bool noRetry;\n\n    JSG_STRUCT(outcome, noRetry);\n  };\n\n  jsg::Promise<ScheduledResult> scheduled(jsg::Lock& js, jsg::Optional<ScheduledOptions> options);\n\n  kj::Maybe<jsg::Ref<JsRpcProperty>> getRpcMethod(jsg::Lock& js, kj::String name);\n  // Internal method for use from bindings code. It skips compatibility flags checks.\n  kj::Maybe<jsg::Ref<JsRpcProperty>> getRpcMethodInternal(jsg::Lock& js, kj::String name);\n  kj::Maybe<jsg::Ref<JsRpcProperty>> getRpcMethodForTestOnly(jsg::Lock& js, kj::String name) {\n    return getRpcMethod(js, kj::mv(name));\n  }\n\n  rpc::JsRpcTarget::Client getClientForOneCall(\n      jsg::Lock& js, kj::Vector<kj::StringPtr>& path) override;\n\n  JSG_RESOURCE_TYPE(Fetcher, CompatibilityFlags::Reader flags) {\n    // WARNING: New JSG_METHODs on Fetcher must be gated via compatibility flag to prevent\n    // conflicts with JS RPC methods (implemented via the wildcard property). Ideally, we do not\n    // add any new methods here, and instead rely on RPC for all future needs.\n    //\n    // Similarly, subclasses of `Fetcher` (notably, `DurableObject`) must follow the same rule,\n    // as any methods added to them will shadow RPC methods of the same name.\n\n    JSG_METHOD(fetch);\n    JSG_METHOD(connect);\n\n    if (flags.getServiceBindingExtraHandlers()) {\n      JSG_METHOD(queue);\n      JSG_METHOD(scheduled);\n\n      JSG_TS_OVERRIDE(type Fetcher<\n        T extends Rpc.EntrypointBranded | undefined = undefined,\n        Reserved extends string = never\n      > = (\n        T extends Rpc.EntrypointBranded\n          ? Rpc.Provider<T, Reserved | \"fetch\" | \"connect\" | \"queue\" | \"scheduled\">\n          : unknown\n      ) & {\n        fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;\n        connect(address: SocketAddress | string, options?: SocketOptions): Socket;\n        queue(queueName: string, messages: ServiceBindingQueueMessage[]): Promise<FetcherQueueResult>;\n        scheduled(options?: FetcherScheduledOptions): Promise<FetcherScheduledResult>;\n      });\n    } else {\n      JSG_TS_OVERRIDE(type Fetcher<\n        T extends Rpc.EntrypointBranded | undefined = undefined,\n        Reserved extends string = never\n      > = (\n        T extends Rpc.EntrypointBranded\n          ? Rpc.Provider<T, Reserved | \"fetch\" | \"connect\">\n          : unknown\n      ) & {\n        fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;\n        connect(address: SocketAddress | string, options?: SocketOptions): Socket;\n      });\n    }\n    JSG_TS_DEFINE(\n      type Service<\n        T extends\n          | (new (...args: any[]) => Rpc.WorkerEntrypointBranded)\n          | Rpc.WorkerEntrypointBranded\n          | ExportedHandler<any, any, any>\n          | undefined = undefined,\n      > = T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded ? Fetcher<InstanceType<T>>\n        : T extends Rpc.WorkerEntrypointBranded ? Fetcher<T>\n        : T extends Exclude<Rpc.EntrypointBranded, Rpc.WorkerEntrypointBranded> ? never\n        : Fetcher<undefined>\n    );\n\n    if (!flags.getFetcherNoGetPutDelete()) {\n      // These helpers just map to `fetch()` with the corresponding HTTP method. They were never\n      // documented and probably never should have been defined. We are removing them to make room\n      // for RPC.\n      JSG_METHOD(get);\n      JSG_METHOD(put);\n      JSG_METHOD_NAMED(delete, delete_);\n    }\n\n    JSG_WILDCARD_PROPERTY(getRpcMethod);\n\n    if (flags.getWorkerdExperimental()) {\n      // We export a copy of getRpcMethod for use in tests only which allows the caller to provide\n      // an arbitrary string as the method name. This allows invoking methods that would normally\n      // be shadowed by non-wildcard methods.\n      JSG_METHOD(getRpcMethodForTestOnly);\n    }\n  }\n\n  void serialize(jsg::Lock& js, jsg::Serializer& serializer);\n  static jsg::Ref<Fetcher> deserialize(\n      jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer);\n\n  JSG_SERIALIZABLE(rpc::SerializationTag::SERVICE_STUB);\n\n private:\n  kj::OneOf<uint,\n      IoOwn<IoChannelFactory::SubrequestChannel>,\n      kj::Own<CrossContextOutgoingFactory>,\n      IoOwn<OutgoingFactory>>\n      channelOrClientFactory;\n  RequiresHostAndProtocol requiresHost;\n  bool isInHouse;\n};\n\n// Type of the second parameter to Request's constructor. Also the type of the second parameter\n// to fetch().\n//\n// When adding new properties to this struct, don't forget to update Request::serialize().\nstruct RequestInitializerDict {\n  jsg::Optional<kj::String> method;\n  jsg::Optional<Headers::Initializer> headers;\n\n  // The script author may specify an empty body either implicitly, by allowing this property to\n  // be undefined, or explicitly, by setting this property to null. To support both cases, this\n  // body initializer must be Optional<Maybe<Body::Initializer>>.\n  jsg::Optional<kj::Maybe<Body::Initializer>> body;\n\n  // follow, error, manual (default follow)\n  jsg::Optional<kj::String> redirect;\n\n  jsg::Optional<kj::Maybe<jsg::Ref<Fetcher>>> fetcher;\n\n  // Cloudflare-specific feature flags.\n  jsg::Optional<jsg::V8Ref<v8::Object>> cf;\n  // TODO(someday): We should generalize this concept to sending control information to\n  //   downstream workers in the pipeline. That is, when multiple workers apply to the same\n  //   request (with the first worker's subrequests being passed to the next worker), then\n  //   first worker should be able to set flags on the request that the second worker can see.\n  //   Perhaps we should say that any field you set on a Request object will be JSON-serialized\n  //   and passed on to the next worker? Then `cf` is just one such field: it's not special,\n  //   it's only named `cf` because the consumer is Cloudflare code.\n\n  // The fetch standard defines additional properties that are really only relevant in browser\n  // implementations that implement CORS. The WinterTC has determined that for non-browser\n  // environments, these should be silently ignoredif the runtime has no use for them.\n  //  * mode\n  //  * credentials\n  //  * referrer\n  //  * referrerPolicy\n  //  * keepalive\n  //  * window\n\n  // In browsers this controls the local browser cache. For Cloudflare Workers it could control the\n  // Cloudflare edge cache. While the standard defines a number of values for this property, our\n  // implementation supports only three: undefined (identifying the default caching behavior that\n  // has been implemented by the runtime), \"no-store\", and \"no-cache\".\n  jsg::Optional<kj::String> cache;\n\n  // Subresource integrity (check response against a given hash).\n  // We do not implement integrity checking, however, we will accept either an undefined\n  // or empty string value for the property. If any other value is given we will throw.\n  jsg::Optional<kj::String> integrity;\n\n  // The spec declares this optional, but is unclear on whether it is nullable. The spec is also\n  // unclear on whether the `Request.signal` property is nullable. If `Request.signal` is nullable,\n  // then we definitely have to accept `null` as an input here, otherwise\n  // `new Request(url, {...request})` will fail when `request.signal` is null. However, it's also\n  // possible that neither property should be nullable. Indeed, it appears that Chrome always\n  // constructs a dummy signal even if none was provided, and uses that. But Chrome is also happy\n  // to accept `null` as an input, so if we're doing what Chrome does, then we should accept\n  // `null`.\n  jsg::Optional<kj::Maybe<jsg::Ref<AbortSignal>>> signal;\n\n  // Controls whether the response body is automatically decoded according to Content-Encoding\n  // headers. Default behavior is \"automatic\" which means bodies are decoded. Setting this to\n  // \"manual\" means the raw compressed bytes are returned.\n  jsg::Optional<kj::String> encodeResponseBody;\n\n  // The duplex option controls whether or not a fetch is expected to send the entire request\n  // before processing the response. The default value (\"half\"), which is currently the only\n  // option supported by the standard, dictates that the request is fully sent before handling\n  // the response. There are currently a proposal to add a \"full\" option which is the model\n  // we support. Once \"full\" is added, we need to update this to accept either undefined or\n  // \"full\", and possibly decide if we want to support the \"half\" option. For now we'll just\n  // ignore this option. Enabling this option later might require a compatibility flag.\n  // jsg::Optional<kj::String> duplex;\n  // TODO(conform): Might support later?\n\n  // Specifies the relative priority of the request. We currently do not make use of this\n  // information. Per the spec, the only values acceptable for the priority option are\n  // \"high\", \"low\", and \"auto\", with \"auto\" being considered the default. For now we'll just\n  // ignore this option. Enabling this option later might require a compatibility flag.\n  // jsg::Optional<kj::String> priority;\n  // TODO(conform): Might support later?\n\n  JSG_STRUCT(\n      method, headers, body, redirect, fetcher, cf, cache, integrity, signal, encodeResponseBody);\n  JSG_STRUCT_TS_OVERRIDE_DYNAMIC(CompatibilityFlags::Reader flags) {\n    if (flags.getCacheOptionEnabled()) {\n      if (flags.getCacheReload()) {\n        JSG_TS_OVERRIDE(RequestInit<Cf = CfProperties> {\n          headers?: HeadersInit;\n          body?: BodyInit | null;\n          cache?: 'no-store' | 'no-cache' | 'reload';\n          cf?: Cf;\n          encodeResponseBody?: \"automatic\" | \"manual\";\n        });\n\n      } else if (flags.getCacheNoCache()) {\n        JSG_TS_OVERRIDE(RequestInit<Cf = CfProperties> {\n          headers?: HeadersInit;\n          body?: BodyInit | null;\n          cache?: 'no-store' | 'no-cache';\n          cf?: Cf;\n          encodeResponseBody?: \"automatic\" | \"manual\";\n        });\n      } else {\n        JSG_TS_OVERRIDE(RequestInit<Cf = CfProperties> {\n          headers?: HeadersInit;\n          body?: BodyInit | null;\n          cache?: 'no-store';\n          cf?: Cf;\n          encodeResponseBody?: \"automatic\" | \"manual\";\n        });\n      }\n    } else {\n      JSG_TS_OVERRIDE(RequestInit<Cf = CfProperties> {\n        headers?: HeadersInit;\n        body?: BodyInit | null;\n        cache?: never;\n        cf?: Cf;\n        encodeResponseBody?: \"automatic\" | \"manual\";\n      });\n    }\n  }\n\n  // This method is called within tryUnwrap() when the type is unpacked from v8.\n  // See jsg Readme for more details.\n  void validate(jsg::Lock&);\n};\n\nclass Request final: public Body {\n public:\n  enum class Redirect {\n    FOLLOW,\n    MANUAL\n    // Note: error mode doesn't make sense for us.\n  };\n  static kj::Maybe<Redirect> tryParseRedirect(kj::StringPtr redirect);\n\n  enum class CacheMode {\n    // CacheMode::NONE is set when cache is undefined. It represents the default cache\n    // mode that workers has supported.\n    NONE,\n    NOSTORE,\n    NOCACHE,\n    RELOAD,\n  };\n\n  Request(jsg::Lock& js,\n      kj::HttpMethod method,\n      kj::StringPtr url,\n      Redirect redirect,\n      jsg::Ref<Headers> headers,\n      kj::Maybe<jsg::Ref<Fetcher>> fetcher,\n      kj::Maybe<jsg::Ref<AbortSignal>> signal,\n      CfProperty&& cf,\n      kj::Maybe<Body::ExtractedBody> body,\n      kj::Maybe<jsg::Ref<AbortSignal>> thisSignal,\n      CacheMode cacheMode = CacheMode::NONE,\n      Response_BodyEncoding responseBodyEncoding = Response_BodyEncoding::AUTO)\n      : Body(js, kj::mv(body), *headers),\n        method(method),\n        url(kj::str(url)),\n        redirect(redirect),\n        headers(kj::mv(headers)),\n        fetcher(kj::mv(fetcher)),\n        cacheMode(cacheMode),\n        cf(kj::mv(cf)),\n        responseBodyEncoding(responseBodyEncoding) {\n    KJ_IF_SOME(s, signal) {\n      // If the AbortSignal will never abort, assigning it to thisSignal instead ensures\n      // that the cancel machinery is not used but the request.signal accessor will still\n      // do the right thing.\n      if (s->getNeverAborts()) {\n        this->thisSignal = s.addRef();\n      } else {\n        this->signal = s.addRef();\n      }\n    }\n  }\n  // TODO(conform): Technically, the request's URL should be parsed immediately upon Request\n  //   construction, and any errors encountered should be thrown. Instead, we defer parsing until\n  //   fetch()-time. This sidesteps an awkward issue: The request URL should be parsed relative to\n  //   the service worker script's URL (e.g. https://capnproto.org/sw.js), but edge worker scripts\n  //   don't have a script URL, so we have no choice but to parse it as an absolute URL. This means\n  //   constructs like `new Request(\"\")` should actually throw TypeError, but constructing Requests\n  //   with empty URLs is useful in testing.\n\n  kj::HttpMethod getMethodEnum() {\n    return method;\n  }\n  void setMethodEnum(kj::HttpMethod newMethod) {\n    method = newMethod;\n  }\n  Redirect getRedirectEnum() {\n    return redirect;\n  }\n  void shallowCopyHeadersTo(kj::HttpHeaders& out);\n  kj::Maybe<kj::String> serializeCfBlobJson(jsg::Lock& js);\n\n  // ---------------------------------------------------------------------------\n  // JS API\n\n  using InitializerDict = RequestInitializerDict;\n\n  using Info = kj::OneOf<jsg::Ref<Request>, kj::String>;\n  using Initializer = kj::OneOf<InitializerDict, jsg::Ref<Request>>;\n\n  // Wrapper around Request::constructor that calls it only if necessary, and returns a\n  // jsg::Ref<Request>.\n  //\n  // C++ API, but declared down here because we need the InitializerDict type.\n  static jsg::Ref<Request> coerce(\n      jsg::Lock& js, Request::Info input, jsg::Optional<Request::Initializer> init);\n\n  static jsg::Ref<Request> constructor(\n      jsg::Lock& js, Request::Info input, jsg::Optional<Request::Initializer> init);\n\n  jsg::Ref<Request> clone(jsg::Lock& js);\n\n  kj::StringPtr getMethod();\n  kj::StringPtr getUrl();\n  jsg::Ref<Headers> getHeaders(jsg::Lock& js);\n  kj::StringPtr getRedirect();\n  kj::Maybe<jsg::Ref<Fetcher>> getFetcher();\n\n  // getSignal() is the one that we used internally to determine if there's actually\n  // an AbortSignal that can be triggered to cancel things. The getThisSignal() is\n  // used only on the JavaScript side to conform to the spec, which requires\n  // request.signal to always return an AbortSignal even if one is not actively\n  // used on this request.\n  kj::Maybe<jsg::Ref<AbortSignal>> getSignal();\n  jsg::Ref<AbortSignal> getThisSignal(jsg::Lock& js);\n\n  // Clear the request's signal if the 'ignoreForSubrequests' flag is set. This happens when\n  // a request from an incoming fetch is passed-through to another fetch. We want to avoid\n  // aborting the subrequest in that case.\n  void clearSignalIfIgnoredForSubrequest(jsg::Lock& js);\n\n  // Returns the `cf` field containing Cloudflare feature flags.\n  jsg::Optional<jsg::JsObject> getCf(jsg::Lock& js);\n\n  // The duplex option controls whether or not a fetch is expected to send the entire request\n  // before processing the response. The default value (\"half\"), which is currently the only\n  // option supported by the standard, dictates that the request is fully sent before handling\n  // the response. There are currently a proposal to add a \"full\" option which is the model\n  // we support. Once \"full\" is added, we need to update this to accept either undefined or\n  // \"full\", and possibly decide if we want to support the \"half\" option.\n  // jsg::JsValue getDuplex(jsg::Lock& js) { return js.undefined(); }\n  // TODO(conform): Might implement?\n\n  // These relate to CORS support, which we do not implement. WinterTC has determined that\n  // non-browser implementations that do not implement CORS support should ignore these\n  // entirely as if they were not defined.\n  //  * destination\n  //  * mode\n  //  * credentials\n  //  * referrer\n  //  * referrerPolicy\n  //  * isReloadNavigation\n  //  * isHistoryNavigation\n  //  * keepalive (see below)\n\n  // We do not implement support for the keepalive option but we do want to at least provide\n  // the standard property, hard-coded to always be false. WinterTC actually recommends that\n  // this one just be left undefined but we already had this returning false always and it\n  // would require a compat flag to remove. Just keep it as it's harmless.\n  bool getKeepalive() {\n    return false;\n  }\n\n  // The cache mode determines how HTTP cache is used with the request.\n  jsg::Optional<kj::StringPtr> getCache(jsg::Lock& js);\n  CacheMode getCacheMode();\n\n  // We do not implement integrity checking at all. However, the spec says that\n  // the default value should be an empty string. When the Request object is\n  // created we verify that the given value is undefined or empty.\n  kj::String getIntegrity() {\n    return kj::String();\n  }\n\n  // Get the response body encoding setting for this request\n  Response_BodyEncoding getResponseBodyEncoding() {\n    return responseBodyEncoding;\n  }\n\n  JSG_RESOURCE_TYPE(Request, CompatibilityFlags::Reader flags) {\n    JSG_INHERIT(Body);\n\n    JSG_METHOD(clone);\n\n    JSG_TS_DEFINE(type RequestInfo<CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>> = Request<CfHostMetadata, Cf> | string);\n    // All type aliases get inlined when exporting RTTI, but this type alias is included by\n    // the official TypeScript types, so users might be depending on it.\n\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(method, getMethod);\n      JSG_READONLY_PROTOTYPE_PROPERTY(url, getUrl);\n      JSG_READONLY_PROTOTYPE_PROPERTY(headers, getHeaders);\n      JSG_READONLY_PROTOTYPE_PROPERTY(redirect, getRedirect);\n      JSG_READONLY_PROTOTYPE_PROPERTY(fetcher, getFetcher);\n      JSG_READONLY_PROTOTYPE_PROPERTY(signal, getThisSignal);\n      JSG_READONLY_PROTOTYPE_PROPERTY(cf, getCf);\n\n      // TODO(conform): These are standard properties that we do not implement (see descriptions\n      // above).\n      // JSG_READONLY_PROTOTYPE_PROPERTY(duplex, getDuplex);\n      JSG_READONLY_PROTOTYPE_PROPERTY(integrity, getIntegrity);\n      JSG_READONLY_PROTOTYPE_PROPERTY(keepalive, getKeepalive);\n      if (flags.getCacheOptionEnabled()) {\n        JSG_READONLY_PROTOTYPE_PROPERTY(cache, getCache);\n        if (flags.getCacheReload()) {\n          JSG_TS_OVERRIDE(<CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>> {\n            constructor(input: RequestInfo<CfProperties> | URL, init?: RequestInit<Cf>);\n            clone(): Request<CfHostMetadata, Cf>;\n            cache?: \"no-store\" | \"no-cache\" | \"reload\";\n            cf?: Cf;\n          });\n        } else if (flags.getCacheNoCache()) {\n          JSG_TS_OVERRIDE(<CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>> {\n            constructor(input: RequestInfo<CfProperties> | URL, init?: RequestInit<Cf>);\n            clone(): Request<CfHostMetadata, Cf>;\n            cache?: \"no-store\" | \"no-cache\";\n            cf?: Cf;\n          });\n        } else {\n          JSG_TS_OVERRIDE(<CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>> {\n            constructor(input: RequestInfo<CfProperties> | URL, init?: RequestInit<Cf>);\n            clone(): Request<CfHostMetadata, Cf>;\n            cache?: \"no-store\";\n            cf?: Cf;\n          });\n        }\n      } else {\n        JSG_TS_OVERRIDE(<CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>> {\n          constructor(input: RequestInfo<CfProperties> | URL, init?: RequestInit<Cf>);\n          clone(): Request<CfHostMetadata, Cf>;\n          cf?: Cf;\n        });\n      }\n\n      // Use `RequestInfo` and `RequestInit` type aliases in constructor instead of inlining.\n      // `CfProperties` is defined in `/types/defines/cf.d.ts`. We only really need a single `Cf`\n      // type parameter here, but it would be a breaking type change to remove `CfHostMetadata`.\n    } else {\n      JSG_READONLY_INSTANCE_PROPERTY(method, getMethod);\n      JSG_READONLY_INSTANCE_PROPERTY(url, getUrl);\n      JSG_READONLY_INSTANCE_PROPERTY(headers, getHeaders);\n      JSG_READONLY_INSTANCE_PROPERTY(redirect, getRedirect);\n      JSG_READONLY_INSTANCE_PROPERTY(fetcher, getFetcher);\n      JSG_READONLY_INSTANCE_PROPERTY(signal, getThisSignal);\n      JSG_READONLY_INSTANCE_PROPERTY(cf, getCf);\n\n      // TODO(conform): These are standard properties that we do not implement (see descriptions\n      // above).\n      // JSG_READONLY_INSTANCE_PROPERTY(duplex, getDuplex);\n      JSG_READONLY_INSTANCE_PROPERTY(integrity, getIntegrity);\n      JSG_READONLY_INSTANCE_PROPERTY(keepalive, getKeepalive);\n\n      JSG_TS_OVERRIDE(<CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>> {\n        constructor(input: RequestInfo<CfProperties> | URL, init?: RequestInit<Cf>);\n        clone(): Request<CfHostMetadata, Cf>;\n        readonly cf?: Cf;\n      });\n    }\n  }\n\n  void serialize(jsg::Lock& js,\n      jsg::Serializer& serializer,\n      const jsg::TypeHandler<RequestInitializerDict>& initDictHandler);\n  static jsg::Ref<Request> deserialize(jsg::Lock& js,\n      rpc::SerializationTag tag,\n      jsg::Deserializer& deserializer,\n      const jsg::TypeHandler<RequestInitializerDict>& initDictHandler);\n\n  JSG_SERIALIZABLE(rpc::SerializationTag::REQUEST);\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"url\", url);\n    tracker.trackField(\"headers\", headers);\n    tracker.trackField(\"fetcher\", fetcher);\n    tracker.trackField(\"signal\", signal);\n    tracker.trackField(\"thisSignal\", thisSignal);\n    tracker.trackField(\"cf\", cf);\n  }\n\n private:\n  kj::HttpMethod method;\n  kj::String url;\n  Redirect redirect;\n  jsg::Ref<Headers> headers;\n  kj::Maybe<jsg::Ref<Fetcher>> fetcher;\n  kj::Maybe<jsg::Ref<AbortSignal>> signal;\n\n  CacheMode cacheMode = CacheMode::NONE;\n\n  // The fetch spec definition of Request has a distinction between the \"signal\" (which is\n  // an optional AbortSignal passed in with the options), and \"this' signal\", which is an\n  // AbortSignal that is always available via the request.signal accessor. When signal is\n  // used explicitly, thisSignal will not be.\n  kj::Maybe<jsg::Ref<AbortSignal>> thisSignal;\n\n  CfProperty cf;\n\n  // Controls how to handle Content-Encoding headers in the response\n  Response_BodyEncoding responseBodyEncoding = Response_BodyEncoding::AUTO;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(headers, fetcher, signal, thisSignal, cf);\n  }\n};\n\nclass Response final: public Body {\n public:\n  // Alias to the global Response_BodyEncoding enum for backward compatibility\n  using BodyEncoding = Response_BodyEncoding;\n\n  Response(jsg::Lock& js,\n      int statusCode,\n      kj::Maybe<kj::String> statusText,\n      jsg::Ref<Headers> headers,\n      CfProperty&& cf,\n      kj::Maybe<Body::ExtractedBody> body,\n      kj::Array<kj::String> urlList = {},\n      kj::Maybe<jsg::Ref<WebSocket>> webSocket = kj::none,\n      BodyEncoding bodyEncoding = BodyEncoding::AUTO);\n\n  // ---------------------------------------------------------------------------\n  // JS API\n\n  struct InitializerDict {\n    jsg::Optional<int> status;\n    jsg::Optional<kj::String> statusText;\n    jsg::Optional<Headers::Initializer> headers;\n\n    // Cloudflare-specific feature flags.\n    jsg::Optional<jsg::V8Ref<v8::Object>> cf;\n\n    jsg::Optional<kj::Maybe<jsg::Ref<WebSocket>>> webSocket;\n\n    jsg::Optional<kj::String> encodeBody;\n\n    JSG_STRUCT(status, statusText, headers, cf, webSocket, encodeBody);\n    JSG_STRUCT_TS_OVERRIDE(ResponseInit {\n      headers?: HeadersInit;\n      encodeBody?: \"automatic\" | \"manual\";\n    });\n  };\n\n  using Initializer = kj::OneOf<InitializerDict, jsg::Ref<Response>>;\n\n  // Response's constructor has two arguments: an optional, nullable body that defaults to null, and\n  // an optional initializer property bag. Tragically, the only way to express the \"optional,\n  // nullable body that defaults to null\" is with an Optional<Maybe<Body::Initializer>>. The reason\n  // for this is because:\n  //\n  //   - We need to be able to call `new Response()`, meaning the body initializer MUST be Optional.\n  //   - We need to be able to call `new Response(null)`, but `null` cannot implicitly convert to\n  //     an Optional, so we need an inner Maybe to inhibit string coercion to Body::Initializer.\n  static jsg::Ref<Response> constructor(jsg::Lock& js,\n      jsg::Optional<kj::Maybe<Body::Initializer>> bodyInit,\n      jsg::Optional<Initializer> maybeInit);\n\n  // Constructs a redirection response. `status` must be a redirect status if given, otherwise it\n  // defaults to 302 (technically a non-conformity, but both Chrome and Firefox use this default).\n  //\n  // It's worth noting a couple property quirks of Responses constructed using this method:\n  //   1. `url` will be the empty string, because the response didn't actually come from any\n  //      particular URL.\n  //   2. `redirected` will equal false, for the same reason as (1).\n  //   3. `body` will be empty -- we don't even provide a default courtesy body. If you need one,\n  //      you'll need to use the regular constructor, which is more flexible.\n  //\n  // These behaviors surprised me, but they match both the spec and Chrome/Firefox behavior.\n  static jsg::Ref<Response> redirect(jsg::Lock& js, kj::String url, jsg::Optional<int> status);\n\n  // Constructs a `network error` response.\n  //\n  // A network error is a response whose status is always 0, status message is always the empty\n  // byte sequence, header list is always empty, body is always null, and trailer is always empty.\n  static jsg::Ref<Response> error(jsg::Lock& js);\n\n  jsg::Ref<Response> clone(jsg::Lock& js);\n\n  static jsg::Ref<Response> json_(\n      jsg::Lock& js, jsg::JsValue any, jsg::Optional<Initializer> maybeInit);\n\n  struct SendOptions {\n    bool allowWebSocket = false;\n  };\n\n  // Helper not exposed to JavaScript.\n  kj::Promise<DeferredProxy<void>> send(jsg::Lock& js,\n      kj::HttpService::Response& outer,\n      SendOptions options,\n      kj::Maybe<const kj::HttpHeaders&> maybeReqHeaders);\n\n  int getStatus();\n  kj::StringPtr getStatusText();\n  jsg::Ref<Headers> getHeaders(jsg::Lock& js);\n\n  bool getOk();\n  bool getRedirected();\n  kj::StringPtr getUrl();\n\n  kj::Maybe<jsg::Ref<WebSocket>> getWebSocket(jsg::Lock& js);\n\n  // Returns the `cf` field containing Cloudflare feature flags.\n  jsg::Optional<jsg::JsObject> getCf(jsg::Lock& js);\n\n  // This relates to CORS, which doesn't apply on the edge -- see Request::Initializer::mode.\n  // In discussing with other runtime implementations that do not implement CORS, it was\n  // determined that only the `'default'` and `'error'` properties should be implemented.\n  kj::StringPtr getType() {\n    if (statusCode == 0) return \"error\"_kj;\n    return \"default\"_kj;\n  }\n\n  JSG_RESOURCE_TYPE(Response, CompatibilityFlags::Reader flags) {\n    JSG_INHERIT(Body);\n\n    JSG_STATIC_METHOD(error);\n    JSG_STATIC_METHOD(redirect);\n    JSG_STATIC_METHOD_NAMED(json, json_);\n    JSG_METHOD(clone);\n\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(status, getStatus);\n      JSG_READONLY_PROTOTYPE_PROPERTY(statusText, getStatusText);\n      JSG_READONLY_PROTOTYPE_PROPERTY(headers, getHeaders);\n\n      JSG_READONLY_PROTOTYPE_PROPERTY(ok, getOk);\n      JSG_READONLY_PROTOTYPE_PROPERTY(redirected, getRedirected);\n      JSG_READONLY_PROTOTYPE_PROPERTY(url, getUrl);\n\n      JSG_READONLY_PROTOTYPE_PROPERTY(webSocket, getWebSocket);\n\n      JSG_READONLY_PROTOTYPE_PROPERTY(cf, getCf);\n\n      JSG_READONLY_PROTOTYPE_PROPERTY(type, getType);\n    } else {\n      JSG_READONLY_INSTANCE_PROPERTY(status, getStatus);\n      JSG_READONLY_INSTANCE_PROPERTY(statusText, getStatusText);\n      JSG_READONLY_INSTANCE_PROPERTY(headers, getHeaders);\n\n      JSG_READONLY_INSTANCE_PROPERTY(ok, getOk);\n      JSG_READONLY_INSTANCE_PROPERTY(redirected, getRedirected);\n      JSG_READONLY_INSTANCE_PROPERTY(url, getUrl);\n\n      JSG_READONLY_INSTANCE_PROPERTY(webSocket, getWebSocket);\n\n      JSG_READONLY_INSTANCE_PROPERTY(cf, getCf);\n\n      JSG_READONLY_INSTANCE_PROPERTY(type, getType);\n    }\n\n    JSG_TS_OVERRIDE({\n      constructor(body?: BodyInit | null, init?: ResponseInit);\n      type: 'default' | 'error';\n    });\n    // Use `BodyInit` and `ResponseInit` type aliases in constructor instead of inlining\n  }\n\n  void serialize(jsg::Lock& js,\n      jsg::Serializer& serializer,\n      const jsg::TypeHandler<InitializerDict>& initDictHandler,\n      const jsg::TypeHandler<kj::Maybe<jsg::Ref<ReadableStream>>>& streamHandler);\n  static jsg::Ref<Response> deserialize(jsg::Lock& js,\n      rpc::SerializationTag tag,\n      jsg::Deserializer& deserializer,\n      const jsg::TypeHandler<InitializerDict>& initDictHandler,\n      const jsg::TypeHandler<kj::Maybe<jsg::Ref<ReadableStream>>>& streamHandler);\n\n  JSG_SERIALIZABLE(rpc::SerializationTag::RESPONSE);\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"statusText\", statusText);\n    tracker.trackField(\"headers\", headers);\n    tracker.trackField(\"webSocket\", webSocket);\n    tracker.trackField(\"cf\", cf);\n    for (const auto& url: urlList) {\n      tracker.trackField(\"urlList\", url);\n    }\n    tracker.trackField(\"asyncContext\", asyncContext);\n  }\n\n private:\n  int statusCode;\n  // If the statusText is empty, we will derive it from the statusCode. If there's no\n  // match, it will be empty.\n  kj::Maybe<kj::String> statusText;\n  jsg::Ref<Headers> headers;\n  CfProperty cf;\n\n  // The URL list, per the Fetch spec. Only Responses actually created by fetch() have a non-empty\n  // URL list; for responses created from JavaScript this is empty. The list is filled in with the\n  // sequence of URLs that fetch() requested. In redirect manual mode, this will be one element,\n  // and just be a copy of the corresponding request's URL; in redirect follow mode the length of\n  // the list will be one plus the number of redirects followed.\n  //\n  // The last URL is typically the only one that the user will care about, and is the one exposed\n  // by getUrl().\n  kj::Array<kj::String> urlList;\n\n  // If this response represents a successful WebSocket handshake, this is the socket, and the body\n  // is empty.\n  kj::Maybe<jsg::Ref<WebSocket>> webSocket;\n\n  // If this response is already encoded and the user don't want to encode the\n  // body twice, they can specify encodeBody: \"manual\".\n  Response::BodyEncoding bodyEncoding;\n\n  // Capturing the AsyncContextFrame when the Response is created is necessary because there's\n  // a natural separation that occurs between the moment the Response is created and when we\n  // actually start consuming it. If a JS-backed ReadableStream is used, we end up losing the\n  // appropriate async context in the promise read loop since that is kicked off later.\n  kj::Maybe<jsg::Ref<jsg::AsyncContextFrame>> asyncContext;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(headers, webSocket, cf, asyncContext);\n  }\n};\n\nclass FetchEvent final: public ExtendableEvent {\n public:\n  FetchEvent(jsg::Ref<Request> request)\n      : ExtendableEvent(\"fetch\"),\n        request(kj::mv(request)),\n        state(AwaitingRespondWith()) {}\n\n  kj::Maybe<jsg::Promise<jsg::Ref<Response>>> getResponsePromise(jsg::Lock& js);\n\n  // TODO(soon): constructor\n  static jsg::Ref<FetchEvent> constructor(kj::String type) = delete;\n\n  jsg::Ref<Request> getRequest();\n  void respondWith(jsg::Lock& js, jsg::Promise<jsg::Ref<Response>> promise);\n\n  void passThroughOnException();\n\n  // TODO(someday): Do any other FetchEvent members make sense on the edge?\n\n  JSG_RESOURCE_TYPE(FetchEvent) {\n    JSG_INHERIT(ExtendableEvent);\n\n    JSG_READONLY_INSTANCE_PROPERTY(request, getRequest);\n    JSG_METHOD(respondWith);\n    JSG_METHOD(passThroughOnException);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"request\", request);\n    KJ_IF_SOME(respondWithCalled, state.tryGet<RespondWithCalled>()) {\n      tracker.trackField(\"promise\", respondWithCalled.promise);\n    }\n  }\n\n private:\n  jsg::Ref<Request> request;\n\n  struct AwaitingRespondWith {};\n  struct RespondWithCalled {\n    jsg::Promise<jsg::Ref<Response>> promise;\n  };\n  struct ResponseSent {};\n\n  kj::OneOf<AwaitingRespondWith, RespondWithCalled, ResponseSent> state;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(request);\n    KJ_IF_SOME(respondWithCalled, state.tryGet<RespondWithCalled>()) {\n      visitor.visit(respondWithCalled.promise);\n    }\n  }\n};\n\njsg::Promise<jsg::Ref<Response>> fetchImpl(jsg::Lock& js,\n    kj::Maybe<jsg::Ref<Fetcher>> fetcher,  // if null, use fetcher from request object\n    Request::Info requestOrUrl,\n    jsg::Optional<Request::Initializer> requestInit);\n\njsg::Ref<Response> makeHttpResponse(jsg::Lock& js,\n    kj::HttpMethod method,\n    kj::Vector<kj::Url> urlList,\n    uint statusCode,\n    kj::StringPtr statusText,\n    const kj::HttpHeaders& headers,\n    kj::Own<kj::AsyncInputStream> body,\n    kj::Maybe<jsg::Ref<WebSocket>> webSocket,\n    Response::BodyEncoding bodyEncoding = Response::BodyEncoding::AUTO,\n    kj::Maybe<jsg::Ref<AbortSignal>> signal = kj::none);\n\n#define EW_HTTP_ISOLATE_TYPES                                                                      \\\n  api::FetchEvent, api::Headers, api::Headers::EntryIterator, api::Headers::EntryIterator::Next,   \\\n      api::Headers::KeyIterator, api::Headers::KeyIterator::Next, api::Headers::ValueIterator,     \\\n      api::Headers::ValueIterator::Next, api::Body, api::Response, api::Response::InitializerDict, \\\n      api::Request, api::Request::InitializerDict, api::Fetcher, api::Fetcher::PutOptions,         \\\n      api::Fetcher::ScheduledOptions, api::Fetcher::ScheduledResult, api::Fetcher::QueueResult,    \\\n      api::Fetcher::ServiceBindingQueueMessage\n\n// The list of http.h types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/hyperdrive.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"hyperdrive.h\"\n\n#include \"sockets.h\"\n\n#include <workerd/api/global-scope.h>\n#include <workerd/util/entropy.h>\n\n#include <kj/compat/http.h>\n#include <kj/encoding.h>\n#include <kj/string.h>\n\nnamespace workerd::api {\nHyperdrive::Hyperdrive(\n    uint clientIndex, kj::String database, kj::String user, kj::String password, kj::String scheme)\n    : clientIndex(clientIndex),\n      database(kj::mv(database)),\n      user(kj::mv(user)),\n      password(kj::mv(password)),\n      scheme(kj::mv(scheme)) {\n  kj::FixedArray<kj::byte, 16> randomBytes;\n  getEntropy(randomBytes.asPtr());\n  randomHost = kj::str(kj::encodeHex(randomBytes), \".hyperdrive.local\");\n}\n\njsg::Ref<Socket> Hyperdrive::connect(jsg::Lock& js) {\n  auto connPromise = connectToDb();\n\n  auto paf = kj::newPromiseAndFulfiller<kj::Maybe<kj::Exception>>();\n  auto conn =\n      kj::newPromisedStream(connPromise\n                                .then([&f = *paf.fulfiller](kj::Own<kj::AsyncIoStream> stream) {\n    f.fulfill(kj::none);\n    return kj::mv(stream);\n  }, [&f = *paf.fulfiller](kj::Exception e) {\n    KJ_LOG(WARNING, \"failed to connect to local database\", e);\n    f.fulfill(kj::cp(e));\n    return kj::mv(e);\n  }).attach(kj::mv(paf.fulfiller)));\n\n  // TODO(someday): Support TLS? It's not at all necessary since we're connecting locally, but\n  // some users may want it anyway.\n  auto nullTlsStarter = kj::heap<kj::TlsStarterCallback>();\n  auto sock = setupSocket(js, kj::mv(conn), kj::str(getHost(), \":\", getPort()), kj::none,\n      kj::mv(nullTlsStarter), SecureTransportKind::OFF, kj::str(this->randomHost), false,\n      kj::none /* maybeOpenedPrPair */);\n  sock->handleProxyStatus(js, kj::mv(paf.promise));\n  return sock;\n}\n\nkj::StringPtr Hyperdrive::getDatabase() {\n  return this->database;\n}\n\nkj::StringPtr Hyperdrive::getUser() {\n  return this->user;\n}\nkj::StringPtr Hyperdrive::getPassword() {\n  return this->password;\n}\n\nkj::StringPtr Hyperdrive::getScheme() {\n  return this->scheme;\n}\n\nkj::StringPtr Hyperdrive::getHost() {\n  if (!registeredConnectOverride) {\n    // Returns the random hostname and ensures the connect override is registered on the\n    // ServiceWorkerGlobalScope for the Worker. This getter has a side effect: it registers (or\n    // re-registers) an entry in the ServiceWorkerGlobalScope's connectOverrides HashMap so that\n    // cloudflare:sockets's connect() will route connections to this magic hostname through Hyperdrive.\n    auto& globalScope = IoContext::current().getCurrentLock().getGlobalScope();\n    globalScope.setConnectOverride(kj::str(randomHost, \":\", getPort()),\n        [self = JSG_THIS](jsg::Lock& js) mutable { return self->connect(js); });\n    registeredConnectOverride = true;\n  }\n  return randomHost;\n}\n\n// We currently only support Postgres and MySQL\nuint16_t Hyperdrive::getPort() {\n  if (scheme == \"mysql\") {\n    return 3306;\n  }\n\n  // We default to postgres if the scheme is not mysql\n  return 5432;\n}\n\nkj::String Hyperdrive::getConnectionString() {\n  // MySQL: `?ssl-mode=disabled`\n  // PostgreSQL: `?sslmode=disable`\n  auto sslParameter = scheme == \"mysql\" ? \"?ssl-mode=disabled\" : \"?sslmode=disable\";\n  return kj::str(getScheme(), \"://\", getUser(), \":\", getPassword(), \"@\", getHost(), \":\", getPort(),\n      \"/\", getDatabase(), sslParameter);\n}\n\nkj::Promise<kj::Own<kj::AsyncIoStream>> Hyperdrive::connectToDb() {\n  auto& context = IoContext::current();\n  auto service = context.getSubrequestChannel(\n      this->clientIndex, true, kj::none, kj::ConstString(\"hyperdrive_connect\"_kjc));\n\n  kj::HttpHeaderTable headerTable;\n  kj::HttpHeaders headers(headerTable);\n\n  auto connectReq = kj::newHttpClient(*service)->connect(\n      kj::str(getHost(), \":\", getPort()), headers, kj::HttpConnectSettings{});\n\n  auto status = co_await connectReq.status;\n\n  if (status.statusCode >= 200 && status.statusCode < 300) {\n    co_return kj::mv(connectReq.connection);\n  }\n\n  KJ_IF_SOME(e, status.errorBody) {\n    try {\n      auto errorBody = co_await e->readAllText();\n      kj::throwFatalException(\n          KJ_EXCEPTION(FAILED, kj::str(\"unexpected error connecting to database: \", errorBody)));\n    } catch (const kj::Exception& e) {\n      kj::throwFatalException(KJ_EXCEPTION(FAILED,\n          kj::str(\"unexpected error connecting to database \"\n                  \"and couldn't read error details: \",\n              e)));\n    }\n  } else {\n    kj::throwFatalException(KJ_EXCEPTION(\n        FAILED, kj::str(\"unexpected error connecting to database: \", status.statusText)));\n  }\n}\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/hyperdrive.h",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/jsg/jsg.h>\n\nnamespace kj {\nclass AsyncIoStream;\n}\n\nnamespace workerd::api {\n\nclass Socket;\n\n// A Hyperdrive resource for development integrations.\n//\n// Provides the same interface as Hyperdrive while sending connection\n// traffic directly to postgres\nclass Hyperdrive: public jsg::Object {\n public:\n  // `clientIndex` is what to pass to IoContext::getHttpClient() to get an HttpClient\n  // representing this namespace.\n  explicit Hyperdrive(uint clientIndex,\n      kj::String database,\n      kj::String user,\n      kj::String password,\n      kj::String scheme);\n  jsg::Ref<Socket> connect(jsg::Lock& js);\n  kj::StringPtr getDatabase();\n  kj::StringPtr getUser();\n  kj::StringPtr getPassword();\n  kj::StringPtr getScheme();\n\n  kj::StringPtr getHost();\n  uint16_t getPort();\n\n  kj::String getConnectionString();\n\n  JSG_RESOURCE_TYPE(Hyperdrive) {\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(database, getDatabase);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(user, getUser);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(password, getPassword);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(host, getHost);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(port, getPort);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(connectionString, getConnectionString);\n\n    JSG_METHOD(connect);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"randomHost\", randomHost);\n    tracker.trackField(\"database\", database);\n    tracker.trackField(\"user\", user);\n    tracker.trackField(\"password\", password);\n    tracker.trackField(\"scheme\", scheme);\n  }\n\n private:\n  uint clientIndex;\n  kj::String randomHost;\n  kj::String database;\n  kj::String user;\n  kj::String password;\n  kj::String scheme;\n  bool registeredConnectOverride = false;\n\n  kj::Promise<kj::Own<kj::AsyncIoStream>> connectToDb();\n};\n#define EW_HYPERDRIVE_ISOLATE_TYPES api::Hyperdrive\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/kv.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"kv.h\"\n\n#include \"system-streams.h\"\n#include \"util.h\"\n\n#include <workerd/io/features.h>\n#include <workerd/io/io-context.h>\n#include <workerd/io/limit-enforcer.h>\n#include <workerd/util/http-util.h>\n#include <workerd/util/mimetype.h>\n\n#include <kj/compat/http.h>\n#include <kj/encoding.h>\n\nnamespace workerd::api {\n\n// As documented in Cloudflare's Worker KV limits.\nstatic constexpr size_t kMaxKeyLength = 512;\n\nstatic void checkForErrorStatus(kj::StringPtr method, const kj::HttpClient::Response& response) {\n  if (response.statusCode < 200 || response.statusCode >= 300) {\n    // Manually construct exception so that we can incorporate method and status into the text\n    // that JavaScript sees.\n    kj::throwFatalException(kj::Exception(kj::Exception::Type::FAILED, __FILE__, __LINE__,\n        kj::str(JSG_EXCEPTION(Error) \": KV \", method, \" failed: \", response.statusCode, ' ',\n            response.statusText)));\n  }\n}\n\nstatic void validateKeyName(kj::StringPtr method, kj::StringPtr name) {\n  JSG_REQUIRE(name != \"\", TypeError, \"Key name cannot be empty.\");\n  JSG_REQUIRE(name != \".\", TypeError, \"\\\".\\\" is not allowed as a key name.\");\n  JSG_REQUIRE(name != \"..\", TypeError, \"\\\"..\\\" is not allowed as a key name.\");\n  JSG_REQUIRE(name.size() <= kMaxKeyLength, Error, \"KV \", method, \" failed: \", 414,\n      \" UTF-8 encoded length of \", name.size(), \" exceeds key length limit of \", kMaxKeyLength,\n      \".\");\n}\n\nstatic void parseListMetadata(TraceContext& traceContext,\n    jsg::Lock& js,\n    jsg::JsValue listResponse,\n    kj::Maybe<jsg::JsValue> cacheStatus) {\n  static constexpr auto METADATA = \"metadata\"_kjc;\n  static constexpr auto KEYS = \"keys\"_kjc;\n  static constexpr auto CURSOR = \"cursor\"_kjc;\n  static constexpr auto LIST_COMPLETE = \"list_complete\"_kjc;\n  static constexpr auto EXPIRATION = \"expiration\"_kjc;\n\n  js.withinHandleScope([&] {\n    auto obj = KJ_ASSERT_NONNULL(listResponse.tryCast<jsg::JsObject>());\n\n    KJ_IF_SOME(boolVal, obj.get(js, LIST_COMPLETE).tryCast<jsg::JsBoolean>()) {\n      traceContext.setTag(\"cloudflare.kv.response.list_complete\"_kjc, boolVal.value(js));\n    }\n\n    KJ_IF_SOME(cursor, obj.get(js, CURSOR).tryCast<jsg::JsString>()) {\n      traceContext.setTag(\"cloudflare.kv.response.cursor\"_kjc, kj::str(cursor));\n    }\n\n    KJ_IF_SOME(expiration, obj.get(js, EXPIRATION).tryCast<jsg::JsNumber>()) {\n      KJ_IF_SOME(value, expiration.value(js)) {\n        traceContext.setTag(\"cloudflare.kv.response.expiration\"_kjc, static_cast<int64_t>(value));\n      }\n    }\n\n    KJ_IF_SOME(keysArr, obj.get(js, KEYS).tryCast<jsg::JsArray>()) {\n      auto length = keysArr.size();\n      traceContext.setTag(\"cloudflare.kv.response.returned_rows\"_kjc, static_cast<int64_t>(length));\n      for (int i = 0; i < length; i++) {\n        js.withinHandleScope([&] {\n          KJ_IF_SOME(key, keysArr.get(js, i).tryCast<jsg::JsObject>()) {\n            KJ_IF_SOME(str, key.get(js, METADATA).tryCast<jsg::JsString>()) {\n              key.set(js, METADATA, jsg::JsValue::fromJson(js, str));\n            }\n          }\n        });\n      }\n    }\n\n    obj.set(js, \"cacheStatus\"_kjc, cacheStatus.orDefault(js.null()));\n  });\n}\n\nconstexpr auto FLPROD_405_HEADER = \"CF-KV-FLPROD-405\"_kj;\n\nkj::Own<kj::HttpClient> KvNamespace::getHttpClient(IoContext& context,\n    kj::HttpHeaders& headers,\n    kj::OneOf<LimitEnforcer::KvOpType, kj::LiteralStringConst> opTypeOrName,\n    kj::StringPtr urlStr,\n    TraceContext& traceContext) {\n\n  KJ_SWITCH_ONEOF(opTypeOrName) {\n    KJ_CASE_ONEOF(name, kj::LiteralStringConst) {}\n    KJ_CASE_ONEOF(opType, LimitEnforcer::KvOpType) {\n      // Check if we've hit KV usage limits. (This will throw if we have.)\n      context.getLimitEnforcer().newKvRequest(opType);\n    }\n  }\n\n  auto client = context.getHttpClient(subrequestChannel, true, kj::none, traceContext);\n\n  headers.addPtrPtr(FLPROD_405_HEADER, urlStr);\n  for (const auto& header: additionalHeaders) {\n    headers.addPtrPtr(header.name.asPtr(), header.value.asPtr());\n  }\n\n  return client;\n}\n\njsg::Promise<KvNamespace::GetResult> KvNamespace::getSingle(jsg::Lock& js,\n    IoContext& context,\n    TraceContext& traceContext,\n    kj::String name,\n    jsg::Optional<kj::OneOf<kj::String, GetOptions>> options) {\n  return js.evalNow([&] {\n    auto resp = getWithMetadataImpl(\n        js, context, traceContext, kj::mv(name), kj::mv(options), LimitEnforcer::KvOpType::GET);\n    return resp.then(js,\n        [](jsg::Lock&, KvNamespace::GetWithMetadataResult result) { return kj::mv(result.value); });\n  });\n}\n\njsg::Promise<jsg::JsRef<jsg::JsMap>> KvNamespace::getBulk(jsg::Lock& js,\n    IoContext& context,\n    TraceContext& traceContext,\n    kj::Array<kj::String> name,\n    jsg::Optional<kj::OneOf<kj::String, GetOptions>> options,\n    bool withMetadata) {\n  return js.evalNow([&] {\n    kj::Url url;\n    url.scheme = kj::str(\"https\");\n    url.host = kj::str(\"fake-host\");\n    url.path.add(kj::str(\"bulk\"));\n    url.path.add(kj::str(\"get\"));\n\n    kj::String body = formBulkBodyString(js, name, withMetadata, options);\n    kj::Maybe<uint64_t> expectedBodySize = static_cast<uint64_t>(body.size());\n    auto headers = kj::HttpHeaders(context.getHeaderTable());\n    headers.set(kj::HttpHeaderId::CONTENT_TYPE, MimeType::JSON.toString());\n\n    auto urlStr = url.toString(kj::Url::Context::HTTP_PROXY_REQUEST);\n\n    // This could be quite large, so let's limit the string length to 512 characters\n    auto keysStr = kj::strArray(name, \", \");\n    if (keysStr.size() > 512) {\n      keysStr = kj::str(keysStr.slice(0, 509), \"...\");\n    }\n    traceContext.setTag(\"cloudflare.kv.query.keys\"_kjc, kj::mv(keysStr));\n    traceContext.setTag(\"cloudflare.kv.query.keys.count\"_kjc, static_cast<int64_t>(name.size()));\n\n    KJ_IF_SOME(_options, options) {\n      KJ_SWITCH_ONEOF(_options) {\n        KJ_CASE_ONEOF(type, kj::String) {\n          traceContext.setTag(\"cloudflare.kv.query.type\"_kjc, kj::mv(type));\n        }\n        KJ_CASE_ONEOF(o, GetOptions) {\n          KJ_IF_SOME(type, o.type) {\n            traceContext.setTag(\"cloudflare.kv.query.type\"_kjc, kj::mv(type));\n          }\n          KJ_IF_SOME(cacheTtl, o.cacheTtl) {\n            traceContext.setTag(\n                \"cloudflare.kv.query.cache_ttl\"_kjc, static_cast<int64_t>(cacheTtl));\n          }\n        }\n      }\n    }\n\n    auto client =\n        getHttpClient(context, headers, LimitEnforcer::KvOpType::GET_BULK, urlStr, traceContext);\n\n    auto promise = context.waitForOutputLocks().then(\n        [client = kj::mv(client), urlStr = kj::mv(urlStr), headers = kj::mv(headers),\n            expectedBodySize, supportedBody = kj::mv(body)]() mutable {\n      auto innerReq = client->request(kj::HttpMethod::POST, urlStr, headers, expectedBodySize);\n      auto req = attachToRequest(kj::mv(innerReq), kj::refcountedWrapper(kj::mv(client)));\n\n      kj::Promise<void> writePromise = nullptr;\n      writePromise = req.body->write(supportedBody.asBytes()).attach(kj::mv(supportedBody));\n\n      return writePromise.attach(kj::mv(req.body)).then([resp = kj::mv(req.response)]() mutable {\n        return resp.then([](kj::HttpClient::Response&& response) mutable {\n          checkForErrorStatus(\"GET_BULK\", response);\n          return response.body->readAllText().attach(kj::mv(response.body));\n        });\n      });\n    });\n\n    return context.awaitIo(js, kj::mv(promise),\n        [&, traceContext = kj::mv(traceContext)](jsg::Lock& js, kj::String text) mutable {\n      traceContext.setTag(\"cloudflare.kv.response.size\"_kjc, static_cast<int64_t>(text.size()));\n      auto result = jsg::JsValue::fromJson(js, text);\n      auto map = js.map();\n      KJ_IF_SOME(obj, result.tryCast<jsg::JsObject>()) {\n        auto values = obj.getPropertyNames(js, jsg::KeyCollectionFilter::OWN_ONLY,\n            jsg::PropertyFilter::SKIP_SYMBOLS, jsg::IndexFilter::SKIP_INDICES);\n        for (int i = 0; i < values.size(); i++) {\n          auto key = values.get(js, i);\n          map.set(js, kj::mv(key), obj.get(js, key));\n        }\n        traceContext.setTag(\n            \"cloudflare.kv.response.returned_rows\"_kjc, static_cast<int64_t>(values.size()));\n      }\n      return jsg::JsRef(js, map);\n    });\n  });\n}\n\nkj::String KvNamespace::formBulkBodyString(jsg::Lock& js,\n    kj::Array<kj::String>& names,\n    bool withMetadata,\n    jsg::Optional<kj::OneOf<kj::String, GetOptions>>& options) {\n\n  kj::String type = kj::str(\"\");\n  kj::String cacheTtlStr = kj::str(\"\");\n  KJ_IF_SOME(oneOfOptions, options) {\n    KJ_SWITCH_ONEOF(oneOfOptions) {\n      KJ_CASE_ONEOF(t, kj::String) {\n        type = kj::str(t);\n      }\n      KJ_CASE_ONEOF(options, GetOptions) {\n        KJ_IF_SOME(t, options.type) {\n          type = kj::str(t);\n        }\n        KJ_IF_SOME(cacheTtl, options.cacheTtl) {\n          cacheTtlStr = kj::str(cacheTtl);\n        }\n      }\n    }\n  }\n  auto object = js.obj();\n\n  auto keysArray =\n      js.arr(names.asPtr(), [](jsg::Lock& js, const kj::String& val) { return js.str(val); });\n  object.set(js, \"keys\", keysArray);\n\n  if (type != kj::str(\"\")) {\n    object.set(js, \"type\", js.str(type));\n  }\n  if (withMetadata) {\n    object.set(js, \"withMetadata\", js.boolean(true));\n  }\n  if (cacheTtlStr != kj::str(\"\")) {\n    object.set(js, \"cacheTtl\", js.str(cacheTtlStr));\n  }\n  return jsg::JsValue(object).toJson(js);\n}\n\nkj::OneOf<jsg::Promise<KvNamespace::GetResult>, jsg::Promise<jsg::JsRef<jsg::JsMap>>> KvNamespace::\n    get(jsg::Lock& js,\n        kj::OneOf<kj::String, kj::Array<kj::String>> name,\n        jsg::Optional<kj::OneOf<kj::String, GetOptions>> options) {\n  auto& context = IoContext::current();\n  TraceContext traceContext = context.makeUserTraceSpan(\"kv_get\"_kjc);\n  traceContext.setTag(\"db.system.name\"_kjc, \"cloudflare-kv\"_kjc);\n  traceContext.setTag(\"db.operation.name\"_kjc, \"get\"_kjc);\n  traceContext.setTag(\"cloudflare.binding.name\"_kjc, bindingName.asPtr());\n  traceContext.setTag(\"cloudflare.binding.type\"_kjc, \"KV\"_kjc);\n\n  KJ_SWITCH_ONEOF(name) {\n    KJ_CASE_ONEOF(arr, kj::Array<kj::String>) {\n      return context.attachSpans(js,\n          getBulk(js, context, traceContext, kj::mv(arr), kj::mv(options), false),\n          kj::mv(traceContext));\n    }\n    KJ_CASE_ONEOF(str, kj::String) {\n      return context.attachSpans(js,\n          getSingle(js, context, traceContext, kj::mv(str), kj::mv(options)), kj::mv(traceContext));\n    }\n  }\n  KJ_UNREACHABLE;\n};\n\njsg::Promise<KvNamespace::GetWithMetadataResult> KvNamespace::getWithMetadataSingle(jsg::Lock& js,\n    IoContext& context,\n    TraceContext& traceContext,\n    kj::String name,\n    jsg::Optional<kj::OneOf<kj::String, GetOptions>> options) {\n  return getWithMetadataImpl(\n      js, context, traceContext, kj::mv(name), kj::mv(options), LimitEnforcer::KvOpType::GET_WITH);\n}\n\nkj::OneOf<jsg::Promise<KvNamespace::GetWithMetadataResult>, jsg::Promise<jsg::JsRef<jsg::JsMap>>>\nKvNamespace::getWithMetadata(jsg::Lock& js,\n    kj::OneOf<kj::Array<kj::String>, kj::String> name,\n    jsg::Optional<kj::OneOf<kj::String, GetOptions>> options) {\n\n  auto& context = IoContext::current();\n  TraceContext traceContext = context.makeUserTraceSpan(\"kv_getWithMetadata\"_kjc);\n  traceContext.setTag(\"db.system.name\"_kjc, \"cloudflare-kv\"_kjc);\n  traceContext.setTag(\"db.operation.name\"_kjc, \"get\"_kjc);\n  traceContext.setTag(\"cloudflare.binding.name\"_kjc, bindingName.asPtr());\n  traceContext.setTag(\"cloudflare.binding.type\"_kjc, \"KV\"_kjc);\n  KJ_SWITCH_ONEOF(name) {\n    KJ_CASE_ONEOF(arr, kj::Array<kj::String>) {\n      return context.attachSpans(js,\n          getBulk(js, context, traceContext, kj::mv(arr), kj::mv(options), true),\n          kj::mv(traceContext));\n    }\n    KJ_CASE_ONEOF(str, kj::String) {\n      return context.attachSpans(js,\n          getWithMetadataSingle(js, context, traceContext, kj::mv(str), kj::mv(options)),\n          kj::mv(traceContext));\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\njsg::Promise<KvNamespace::GetWithMetadataResult> KvNamespace::getWithMetadataImpl(jsg::Lock& js,\n    IoContext& context,\n    TraceContext& traceContext,\n    kj::String name,\n    jsg::Optional<kj::OneOf<kj::String, GetOptions>> options,\n    LimitEnforcer::KvOpType op) {\n  validateKeyName(\"GET\", name);\n\n  traceContext.setTag(\"cloudflare.kv.query.keys\"_kjc, name.asPtr());\n  traceContext.setTag(\"cloudflare.kv.query.keys.count\"_kjc, static_cast<int64_t>(1));\n\n  kj::Url url;\n  url.scheme = kj::str(\"https\");\n  url.host = kj::str(\"fake-host\");\n  url.path.add(kj::mv(name));\n  url.query.add(kj::Url::QueryParam{kj::str(\"urlencoded\"), kj::str(\"true\")});\n\n  kj::Maybe<kj::String> type;\n  KJ_IF_SOME(oneOfOptions, options) {\n    KJ_SWITCH_ONEOF(oneOfOptions) {\n      KJ_CASE_ONEOF(t, kj::String) {\n        type = kj::str(t);\n        traceContext.setTag(\"cloudflare.kv.query.type\"_kjc, kj::mv(t));\n      }\n      KJ_CASE_ONEOF(options, GetOptions) {\n        KJ_IF_SOME(t, options.type) {\n          type = kj::str(t);\n          traceContext.setTag(\"cloudflare.kv.query.type\"_kjc, kj::mv(t));\n        }\n        KJ_IF_SOME(cacheTtl, options.cacheTtl) {\n          url.query.add(kj::Url::QueryParam{kj::str(\"cache_ttl\"), kj::str(cacheTtl)});\n          traceContext.setTag(\"cloudflare.kv.query.cache_ttl\"_kjc, static_cast<int64_t>(cacheTtl));\n        }\n      }\n    }\n  }\n\n  auto urlStr = url.toString(kj::Url::Context::HTTP_PROXY_REQUEST);\n\n  auto headers = kj::HttpHeaders(context.getHeaderTable());\n  auto client = getHttpClient(context, headers, op, urlStr, traceContext);\n\n  auto request = client->request(kj::HttpMethod::GET, urlStr, headers);\n  return context.awaitIo(js, kj::mv(request.response),\n      [type = kj::mv(type), &context, client = kj::mv(client), traceContext = kj::mv(traceContext)](\n          jsg::Lock& js, kj::HttpClient::Response&& response) mutable\n      -> jsg::Promise<KvNamespace::GetWithMetadataResult> {\n    auto cacheStatus =\n        response.headers->get(context.getHeaderIds().cfCacheStatus).map([&](kj::StringPtr cs) {\n      traceContext.setTag(\"cloudflare.kv.response.cache_status\"_kjc, cs);\n      return jsg::JsRef<jsg::JsValue>(js, js.strIntern(cs));\n    });\n\n    if (response.statusCode == 404 || response.statusCode == 410) {\n      return js.resolvedPromise(KvNamespace::GetWithMetadataResult{\n        .value = kj::none,\n        .metadata = kj::none,\n        .cacheStatus = kj::mv(cacheStatus),\n      });\n    }\n\n    checkForErrorStatus(\"GET\", response);\n\n    auto metaheader = response.headers->get(context.getHeaderIds().cfKvMetadata);\n    kj::Maybe<kj::String> maybeMeta;\n    KJ_IF_SOME(m, metaheader) {\n      traceContext.setTag(\"cloudflare.kv.response.metadata\"_kjc, true);\n      maybeMeta = kj::str(m);\n    }\n\n    auto typeName =\n        type.map([](const kj::String& s) -> kj::StringPtr { return s; }).orDefault(\"text\");\n\n    auto& context = IoContext::current();\n    auto stream = newSystemStream(response.body.attach(kj::mv(client)),\n        getContentEncoding(\n            context, *response.headers, Response::BodyEncoding::AUTO, FeatureFlags::get(js)));\n\n    jsg::Promise<KvNamespace::GetResult> result = nullptr;\n\n    KJ_IF_SOME(size, stream->tryGetLength(StreamEncoding::IDENTITY)) {\n      traceContext.setTag(\"cloudflare.kv.response.size\"_kjc, static_cast<int64_t>(size));\n    }\n    // This method always returns a single result, but this attribute should be consistent with getBulk\n    traceContext.setTag(\"cloudflare.kv.response.returned_rows\"_kjc, static_cast<int64_t>(1));\n\n    if (typeName == \"stream\") {\n      result = js.resolvedPromise(\n          KvNamespace::GetResult(js.alloc<ReadableStream>(context, kj::mv(stream))));\n    } else if (typeName == \"text\") {\n      // NOTE: In theory we should be using awaitIoLegacy() here since ReadableStreamSource is\n      //   supposed to handle pending events on its own, but we also know that the HTTP client\n      //   backing a KV namespace is never implemented in local JavaScript, so whatever.\n      result = context.awaitIo(js,\n          stream->readAllText(context.getLimitEnforcer().getBufferingLimit())\n              .attach(kj::mv(stream)),\n          [](jsg::Lock&, kj::String text) { return KvNamespace::GetResult(kj::mv(text)); });\n    } else if (typeName == \"arrayBuffer\") {\n      result = context.awaitIo(js,\n          stream->readAllBytes(context.getLimitEnforcer().getBufferingLimit())\n              .attach(kj::mv(stream)),\n          [](jsg::Lock&, kj::Array<byte> text) { return KvNamespace::GetResult(kj::mv(text)); });\n    } else if (typeName == \"json\") {\n      result = context.awaitIo(js,\n          stream->readAllText(context.getLimitEnforcer().getBufferingLimit())\n              .attach(kj::mv(stream)),\n          [](jsg::Lock& js, kj::String text) {\n        auto ref = jsg::JsRef(js, jsg::JsValue::fromJson(js, text));\n        return KvNamespace::GetResult(kj::mv(ref));\n      });\n    } else {\n      JSG_FAIL_REQUIRE(TypeError,\n          \"Unknown response type. Possible types are \\\"text\\\", \\\"arrayBuffer\\\", \"\n          \"\\\"json\\\", and \\\"stream\\\".\");\n    }\n    return result.then(js,\n        [maybeMeta = kj::mv(maybeMeta), cacheStatus = kj::mv(cacheStatus)](jsg::Lock& js,\n            KvNamespace::GetResult result) mutable -> KvNamespace::GetWithMetadataResult {\n      kj::Maybe<jsg::JsRef<jsg::JsValue>> meta;\n      KJ_IF_SOME(metaStr, maybeMeta) {\n        meta = jsg::JsRef(js, jsg::JsValue::fromJson(js, metaStr));\n      }\n      return KvNamespace::GetWithMetadataResult{\n        kj::mv(result),\n        kj::mv(meta),\n        kj::mv(cacheStatus),\n      };\n    });\n  });\n}\n\njsg::Promise<jsg::JsRef<jsg::JsValue>> KvNamespace::list(\n    jsg::Lock& js, jsg::Optional<ListOptions> options) {\n  return js.evalNow([&] {\n    auto& context = IoContext::current();\n    TraceContext traceContext = context.makeUserTraceSpan(\"kv_list\"_kjc);\n\n    traceContext.setTag(\"db.system.name\"_kjc, \"cloudflare-kv\"_kjc);\n    traceContext.setTag(\"db.operation.name\"_kjc, \"list\"_kjc);\n    traceContext.setTag(\"cloudflare.binding.name\"_kjc, bindingName.asPtr());\n    traceContext.setTag(\"cloudflare.binding.type\"_kjc, \"KV\"_kjc);\n\n    kj::Url url;\n    url.scheme = kj::str(\"https\");\n    url.host = kj::str(\"fake-host\");\n    KJ_IF_SOME(o, options) {\n      KJ_IF_SOME(limit, o.limit) {\n        traceContext.setTag(\"cloudflare.kv.query.limit\"_kjc, static_cast<int64_t>(limit));\n        if (limit > 0) {\n          url.query.add(kj::Url::QueryParam{kj::str(\"key_count_limit\"), kj::str(limit)});\n        }\n      }\n      KJ_IF_SOME(maybePrefix, o.prefix) {\n        KJ_IF_SOME(prefix, maybePrefix) {\n          traceContext.setTag(\"cloudflare.kv.query.prefix\"_kjc, prefix.asPtr());\n          url.query.add(kj::Url::QueryParam{kj::str(\"prefix\"), kj::str(prefix)});\n        }\n      }\n      KJ_IF_SOME(maybeCursor, o.cursor) {\n        KJ_IF_SOME(cursor, maybeCursor) {\n          traceContext.setTag(\"cloudflare.kv.query.cursor\"_kjc, cursor.asPtr());\n          url.query.add(kj::Url::QueryParam{kj::str(\"cursor\"), kj::str(cursor)});\n        }\n      }\n    }\n\n    auto urlStr = url.toString(kj::Url::Context::HTTP_PROXY_REQUEST);\n\n    auto headers = kj::HttpHeaders(context.getHeaderTable());\n    auto client =\n        getHttpClient(context, headers, LimitEnforcer::KvOpType::LIST, urlStr, traceContext);\n\n    auto request = client->request(kj::HttpMethod::GET, urlStr, headers);\n    return context.attachSpans(js,\n        context.awaitIo(js, kj::mv(request.response),\n            [&context, client = kj::mv(client), traceContext = kj::mv(traceContext)](\n                jsg::Lock& js, kj::HttpClient::Response&& response) mutable\n            -> jsg::Promise<jsg::JsRef<jsg::JsValue>> {\n      checkForErrorStatus(\"GET\", response);\n\n      kj::Maybe<jsg::JsRef<jsg::JsValue>> cacheStatus =\n          [&]() -> kj::Maybe<jsg::JsRef<jsg::JsValue>> {\n        KJ_IF_SOME(cs, response.headers->get(context.getHeaderIds().cfCacheStatus)) {\n          traceContext.setTag(\"cloudflare.kv.response.cache_status\"_kjc, cs);\n          return jsg::JsRef<jsg::JsValue>(js, js.strIntern(cs));\n        }\n        return kj::none;\n      }();\n\n      auto stream = newSystemStream(response.body.attach(kj::mv(client)),\n          getContentEncoding(\n              context, *response.headers, Response::BodyEncoding::AUTO, FeatureFlags::get(js)));\n\n      KJ_IF_SOME(size, stream->tryGetLength(StreamEncoding::IDENTITY)) {\n        traceContext.setTag(\"cloudflare.kv.response.size\"_kjc, static_cast<int64_t>(size));\n      }\n\n      return context.awaitIo(js,\n          stream->readAllText(context.getLimitEnforcer().getBufferingLimit())\n              .attach(kj::mv(stream)),\n          [cacheStatus = kj::mv(cacheStatus), traceContext = kj::mv(traceContext)](\n              jsg::Lock& js, kj::String text) mutable {\n        auto result = jsg::JsValue::fromJson(js, text);\n        parseListMetadata(traceContext, js, result,\n            cacheStatus.map(\n                [&](jsg::JsRef<jsg::JsValue>& cs) -> jsg::JsValue { return cs.getHandle(js); }));\n        return jsg::JsRef(js, result);\n      });\n    }),\n        kj::mv(traceContext));\n  });\n}\n\njsg::Promise<void> KvNamespace::put(jsg::Lock& js,\n    kj::String name,\n    KvNamespace::PutBody body,\n    jsg::Optional<PutOptions> options,\n    const jsg::TypeHandler<KvNamespace::PutSupportedTypes>& putTypeHandler) {\n  return js.evalNow([&] {\n    validateKeyName(\"PUT\", name);\n\n    auto& context = IoContext::current();\n    TraceContext traceContext = context.makeUserTraceSpan(\"kv_put\"_kjc);\n\n    traceContext.setTag(\"db.system.name\"_kjc, \"cloudflare-kv\"_kjc);\n    traceContext.setTag(\"db.operation.name\"_kjc, \"put\"_kjc);\n    traceContext.setTag(\"cloudflare.binding.name\"_kjc, bindingName.asPtr());\n    traceContext.setTag(\"cloudflare.binding.type\"_kjc, \"KV\"_kjc);\n    traceContext.setTag(\"cloudflare.kv.query.keys\"_kjc, name.asPtr());\n    traceContext.setTag(\"cloudflare.kv.query.keys.count\"_kjc, static_cast<int64_t>(1));\n\n    kj::Url url;\n    url.scheme = kj::str(\"https\");\n    url.host = kj::str(\"fake-host\");\n    url.path.add(kj::mv(name));\n    url.query.add(kj::Url::QueryParam{kj::str(\"urlencoded\"), kj::str(\"true\")});\n\n    kj::HttpHeaders headers(context.getHeaderTable());\n\n    // If any optional parameters were specified by the client, append them to\n    // the URL's query parameters.\n    KJ_IF_SOME(o, options) {\n      KJ_IF_SOME(expiration, o.expiration) {\n        traceContext.setTag(\"cloudflare.kv.query.expiration\"_kjc, static_cast<int64_t>(expiration));\n        url.query.add(kj::Url::QueryParam{kj::str(\"expiration\"), kj::str(expiration)});\n      }\n      KJ_IF_SOME(expirationTtl, o.expirationTtl) {\n        traceContext.setTag(\n            \"cloudflare.kv.query.expiration_ttl\"_kjc, static_cast<int64_t>(expirationTtl));\n        url.query.add(kj::Url::QueryParam{kj::str(\"expiration_ttl\"), kj::str(expirationTtl)});\n      }\n      KJ_IF_SOME(maybeMetadata, o.metadata) {\n        KJ_IF_SOME(metadata, maybeMetadata) {\n          kj::String json = metadata.getHandle(js).toJson(js);\n          headers.set(context.getHeaderIds().cfKvMetadata, kj::mv(json));\n          traceContext.setTag(\"cloudflare.kv.query.metadata\"_kjc, true);\n        }\n      }\n    }\n\n    PutSupportedTypes supportedBody;\n\n    KJ_SWITCH_ONEOF(body) {\n      KJ_CASE_ONEOF(text, kj::String) {\n        supportedBody = kj::mv(text);\n      }\n      KJ_CASE_ONEOF(object, jsg::JsObject) {\n        supportedBody = JSG_REQUIRE_NONNULL(putTypeHandler.tryUnwrap(js, object), TypeError,\n            \"KV put() accepts only strings, ArrayBuffers, ArrayBufferViews, and \"\n            \"ReadableStreams as values.\");\n        JSG_REQUIRE(!supportedBody.is<kj::String>(), TypeError,\n            \"KV put() accepts only strings, ArrayBuffers, ArrayBufferViews, and \"\n            \"ReadableStreams as values.\");\n        // TODO(someday): replace this with logic to do something smarter with Objects\n      }\n    }\n\n    kj::Maybe<uint64_t> expectedBodySize;\n\n    KJ_SWITCH_ONEOF(supportedBody) {\n      KJ_CASE_ONEOF(text, kj::String) {\n        headers.setPtr(kj::HttpHeaderId::CONTENT_TYPE, MimeType::PLAINTEXT_STRING);\n        expectedBodySize = static_cast<uint64_t>(text.size());\n        traceContext.setTag(\"cloudflare.kv.query.value_type\"_kjc, \"text\"_kjc);\n      }\n      KJ_CASE_ONEOF(data, kj::Array<byte>) {\n        expectedBodySize = static_cast<uint64_t>(data.size());\n        traceContext.setTag(\"cloudflare.kv.query.value_type\"_kjc, \"ArrayBuffer\"_kjc);\n      }\n      KJ_CASE_ONEOF(stream, jsg::Ref<ReadableStream>) {\n        expectedBodySize = stream->tryGetLength(StreamEncoding::IDENTITY);\n        traceContext.setTag(\"cloudflare.kv.query.value_type\"_kjc, \"ReadableStream\"_kjc);\n      }\n    }\n\n    KJ_IF_SOME(bodySize, expectedBodySize) {\n      traceContext.setTag(\"cloudflare.kv.query.payload.size\"_kjc, static_cast<int64_t>(bodySize));\n    }\n\n    auto urlStr = url.toString(kj::Url::Context::HTTP_PROXY_REQUEST);\n\n    auto client =\n        getHttpClient(context, headers, LimitEnforcer::KvOpType::PUT, urlStr, traceContext);\n\n    auto promise = context.waitForOutputLocks().then(\n        [&context, client = kj::mv(client), urlStr = kj::mv(urlStr), headers = kj::mv(headers),\n            expectedBodySize, supportedBody = kj::mv(supportedBody)]() mutable {\n      auto innerReq = client->request(kj::HttpMethod::PUT, urlStr, headers, expectedBodySize);\n      // TODO(perf): More efficient to explicitly attach rcClient below?\n      auto req = attachToRequest(kj::mv(innerReq), kj::refcountedWrapper(kj::mv(client)));\n\n      kj::Promise<void> writePromise = nullptr;\n      KJ_SWITCH_ONEOF(supportedBody) {\n        KJ_CASE_ONEOF(text, kj::String) {\n          writePromise = req.body->write(text.asBytes()).attach(kj::mv(text));\n        }\n        KJ_CASE_ONEOF(data, kj::Array<byte>) {\n          writePromise = req.body->write(data).attach(kj::mv(data));\n        }\n        KJ_CASE_ONEOF(stream, jsg::Ref<ReadableStream>) {\n          writePromise = context.run(\n              [dest = newSystemStream(kj::mv(req.body), StreamEncoding::IDENTITY, context),\n                  stream = kj::mv(stream)](jsg::Lock& js) mutable {\n            return IoContext::current().waitForDeferredProxy(\n                stream->pumpTo(js, kj::mv(dest), true));\n          });\n        }\n      }\n\n      return writePromise.attach(kj::mv(req.body)).then([resp = kj::mv(req.response)]() mutable {\n        return resp.then([](kj::HttpClient::Response&& response) mutable {\n          checkForErrorStatus(\"PUT\", response);\n\n          // Read and discard response body, otherwise we might burn the HTTP connection.\n          return response.body->readAllBytes().attach(kj::mv(response.body)).ignoreResult();\n        });\n      });\n    });\n\n    return context.attachSpans(js, context.awaitIo(js, kj::mv(promise)), kj::mv(traceContext));\n  });\n}\n\njsg::Promise<void> KvNamespace::delete_(jsg::Lock& js, kj::String name) {\n  return js.evalNow([&] {\n    validateKeyName(\"DELETE\", name);\n\n    auto& context = IoContext::current();\n    TraceContext traceContext = context.makeUserTraceSpan(\"kv_delete\"_kjc);\n\n    traceContext.setTag(\"db.system.name\"_kjc, \"cloudflare-kv\"_kjc);\n    traceContext.setTag(\"db.operation.name\"_kjc, \"delete\"_kjc);\n    traceContext.setTag(\"cloudflare.binding.name\"_kjc, bindingName.asPtr());\n    traceContext.setTag(\"cloudflare.binding.type\"_kjc, \"KV\"_kjc);\n    traceContext.setTag(\"cloudflare.kv.query.keys\"_kjc, name.asPtr());\n    traceContext.setTag(\"cloudflare.kv.query.keys.count\"_kjc, static_cast<int64_t>(1));\n\n    auto urlStr = kj::str(\"https://fake-host/\", kj::encodeUriComponent(name), \"?urlencoded=true\");\n\n    kj::HttpHeaders headers(context.getHeaderTable());\n\n    auto client =\n        getHttpClient(context, headers, LimitEnforcer::KvOpType::DELETE, urlStr, traceContext);\n\n    auto promise = context.waitForOutputLocks().then(\n        [headers = kj::mv(headers), client = kj::mv(client), urlStr = kj::mv(urlStr)]() mutable {\n      return client->request(kj::HttpMethod::DELETE, urlStr, headers, static_cast<uint64_t>(0))\n          .response\n          .then([](kj::HttpClient::Response&& response) mutable {\n        checkForErrorStatus(\"DELETE\", response);\n      }).attach(kj::mv(client));\n    });\n\n    return context.attachSpans(js, context.awaitIo(js, kj::mv(promise)), kj::mv(traceContext));\n  });\n}\n\njsg::Ref<JsRpcPromise> KvNamespace::deleteBulk(const v8::FunctionCallbackInfo<v8::Value>& args) {\n  jsg::Lock& js = jsg::Lock::from(args.GetIsolate());\n  auto fetcher = js.alloc<Fetcher>(subrequestChannel, Fetcher::RequiresHostAndProtocol::NO, true);\n  auto method = JSG_REQUIRE_NONNULL(\n      fetcher->getRpcMethodInternal(js, kj::str(\"delete\"_kj)), Error, \"missing delete method\");\n  return method->call(args);\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/kv.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/api/streams/readable.h>\n#include <workerd/api/worker-rpc.h>\n#include <workerd/io/limit-enforcer.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace kj {\nclass HttpClient;\nclass HttpHeaders;\n}  // namespace kj\nnamespace workerd {\nclass IoContext;\n}\nnamespace workerd::api {\n\n// A capability to a KV namespace.\nclass KvNamespace: public jsg::Object {\n public:\n  struct AdditionalHeader {\n    kj::String name;\n    kj::String value;\n\n    JSG_MEMORY_INFO(AdditionalHeader) {\n      tracker.trackField(\"name\", name);\n      tracker.trackField(\"value\", value);\n    }\n  };\n\n  // `subrequestChannel` is what to pass to IoContext::getHttpClient() to get an HttpClient\n  // representing this namespace. It is also used to construct fetcher for JSRPC methods.\n  // `additionalHeaders` is what gets appended to every outbound request.\n  explicit KvNamespace(\n      kj::String bindingName, kj::Array<AdditionalHeader> additionalHeaders, uint subrequestChannel)\n      : additionalHeaders(kj::mv(additionalHeaders)),\n        subrequestChannel(subrequestChannel),\n        bindingName(kj::mv(bindingName)) {}\n\n  struct GetOptions {\n    jsg::Optional<kj::String> type;\n    jsg::Optional<int> cacheTtl;\n\n    JSG_STRUCT(type, cacheTtl);\n    JSG_STRUCT_TS_OVERRIDE(KVNamespaceGetOptions<Type> {\n      type: Type;\n    });\n  };\n\n  using GetResult = kj::Maybe<\n      kj::OneOf<jsg::Ref<ReadableStream>, kj::Array<byte>, kj::String, jsg::JsRef<jsg::JsValue>>>;\n\n  jsg::Promise<KvNamespace::GetResult> getSingle(jsg::Lock& js,\n      IoContext& context,\n      TraceContext& traceContext,\n      kj::String name,\n      jsg::Optional<kj::OneOf<kj::String, GetOptions>> options);\n\n  jsg::Promise<jsg::JsRef<jsg::JsMap>> getBulk(jsg::Lock& js,\n      IoContext& context,\n      TraceContext& traceContext,\n      kj::Array<kj::String> name,\n      jsg::Optional<kj::OneOf<kj::String, GetOptions>> options,\n      bool withMetadata);\n\n  kj::String formBulkBodyString(jsg::Lock& js,\n      kj::Array<kj::String>& names,\n      bool withMetadata,\n      jsg::Optional<kj::OneOf<kj::String, GetOptions>>& options);\n\n  kj::OneOf<jsg::Promise<KvNamespace::GetResult>, jsg::Promise<jsg::JsRef<jsg::JsMap>>> get(\n      jsg::Lock& js,\n      kj::OneOf<kj::String, kj::Array<kj::String>> name,\n      jsg::Optional<kj::OneOf<kj::String, GetOptions>> options);\n\n  struct GetWithMetadataResult {\n    GetResult value;\n    kj::Maybe<jsg::JsRef<jsg::JsValue>> metadata;\n    kj::Maybe<jsg::JsRef<jsg::JsValue>> cacheStatus;\n\n    JSG_STRUCT(value, metadata, cacheStatus);\n    JSG_STRUCT_TS_OVERRIDE(KVNamespaceGetWithMetadataResult<Value, Metadata> {\n      value: Value | null;\n      metadata: Metadata | null;\n      cacheStatus: string | null;\n    });\n  };\n\n  jsg::Promise<GetWithMetadataResult> getWithMetadataImpl(jsg::Lock& js,\n      IoContext& context,\n      TraceContext& traceContext,\n      kj::String name,\n      jsg::Optional<kj::OneOf<kj::String, GetOptions>> options,\n      LimitEnforcer::KvOpType op);\n\n  jsg::Promise<KvNamespace::GetWithMetadataResult> getWithMetadataSingle(jsg::Lock& js,\n      IoContext& context,\n      TraceContext& traceContext,\n      kj::String name,\n      jsg::Optional<kj::OneOf<kj::String, GetOptions>> options);\n\n  kj::OneOf<jsg::Promise<KvNamespace::GetWithMetadataResult>, jsg::Promise<jsg::JsRef<jsg::JsMap>>>\n  getWithMetadata(jsg::Lock& js,\n      kj::OneOf<kj::Array<kj::String>, kj::String> name,\n      jsg::Optional<kj::OneOf<kj::String, GetOptions>> options);\n  struct ListOptions {\n    jsg::Optional<int> limit;\n    jsg::Optional<kj::Maybe<kj::String>> prefix;\n    jsg::Optional<kj::Maybe<kj::String>> cursor;\n\n    JSG_STRUCT(limit, prefix, cursor);\n    JSG_STRUCT_TS_OVERRIDE(KVNamespaceListOptions);\n  };\n\n  jsg::Promise<jsg::JsRef<jsg::JsValue>> list(jsg::Lock& js, jsg::Optional<ListOptions> options);\n\n  // Optional parameter for passing options into a Fetcher::put. Initially\n  // intended for supporting expiration times in KV bindings.\n  struct PutOptions {\n    jsg::Optional<int> expiration;\n    jsg::Optional<int> expirationTtl;\n    jsg::Optional<kj::Maybe<jsg::JsRef<jsg::JsValue>>> metadata;\n\n    JSG_STRUCT(expiration, expirationTtl, metadata);\n    JSG_STRUCT_TS_OVERRIDE(KVNamespacePutOptions);\n  };\n\n  // We can't just list the supported types in this OneOf because if we did then arbitrary objects\n  // would get coerced into meaningless strings like \"[object Object]\". Instead we first use this\n  // OneOf to differentiate between primitives and objects, and check the object for the types that\n  // we specifically support later.\n  using PutBody = kj::OneOf<kj::String, jsg::JsObject>;\n\n  using PutSupportedTypes = kj::OneOf<kj::String, kj::Array<byte>, jsg::Ref<ReadableStream>>;\n\n  jsg::Promise<void> put(jsg::Lock& js,\n      kj::String name,\n      PutBody body,\n      jsg::Optional<PutOptions> options,\n      const jsg::TypeHandler<PutSupportedTypes>& putTypeHandler);\n\n  jsg::Promise<void> delete_(jsg::Lock& js, kj::String name);\n  jsg::Ref<JsRpcPromise> deleteBulk(const v8::FunctionCallbackInfo<v8::Value>& args);\n\n  JSG_RESOURCE_TYPE(KvNamespace, CompatibilityFlags::Reader flags) {\n    JSG_METHOD(get);\n    JSG_METHOD(list);\n    JSG_METHOD(put);\n    JSG_METHOD(getWithMetadata);\n    JSG_METHOD_NAMED(delete, delete_);\n    if (flags.getWorkerdExperimental()) {\n      // Temporary method for tests\n      JSG_METHOD(deleteBulk);\n    }\n\n    JSG_TS_ROOT();\n\n    JSG_TS_DEFINE(\n      interface KVNamespaceListKey<Metadata, Key extends string = string> {\n        name: Key;\n        expiration?: number;\n        metadata?: Metadata;\n      }\n      type KVNamespaceListResult<Metadata, Key extends string = string> =\n        | { list_complete: false; keys: KVNamespaceListKey<Metadata, Key>[]; cursor: string; cacheStatus: string | null; }\n        | { list_complete: true; keys: KVNamespaceListKey<Metadata, Key>[]; cacheStatus: string | null; };\n    );\n    // `Metadata` before `Key` type parameter for backwards-compatibility with `workers-types@3`.\n    // `Key` is also an optional type parameter, which must come after required parameters.\n\n    if (flags.getWorkerdExperimental()) {\n      JSG_TS_OVERRIDE(KVNamespace<Key extends string = string> {\n        get(key: Key, options?: Partial<KVNamespaceGetOptions<undefined>>): Promise<string | null>;\n        get(key: Key, type: \"text\"): Promise<string | null>;\n        get<ExpectedValue = unknown>(key: Key, type: \"json\"): Promise<ExpectedValue | null>;\n        get(key: Key, type: \"arrayBuffer\"): Promise<ArrayBuffer | null>;\n        get(key: Key, type: \"stream\"): Promise<ReadableStream | null>;\n        get(key: Key, options?: KVNamespaceGetOptions<\"text\">): Promise<string | null>;\n        get<ExpectedValue = unknown>(key: Key, options?: KVNamespaceGetOptions<\"json\">): Promise<ExpectedValue | null>;\n        get(key: Key, options?: KVNamespaceGetOptions<\"arrayBuffer\">): Promise<ArrayBuffer | null>;\n        get(key: Key, options?: KVNamespaceGetOptions<\"stream\">): Promise<ReadableStream | null>;\n\n        get(key: Array<Key>, type: \"text\"): Promise<Map<string, string | null>>;\n        get<ExpectedValue = unknown>(key: Array<Key>, type: \"json\"): Promise<Map<string, ExpectedValue | null>>;\n        get(key: Array<Key>, options?: Partial<KVNamespaceGetOptions<undefined>>): Promise<Map<string, string | null>>;\n        get(key: Array<Key>, options?: KVNamespaceGetOptions<\"text\">): Promise<Map<string, string | null>>;\n        get<ExpectedValue = unknown>(key: Array<Key>, options?: KVNamespaceGetOptions<\"json\">): Promise<Map<string, ExpectedValue | null>>;\n\n        list<Metadata = unknown>(options?: KVNamespaceListOptions): Promise<KVNamespaceListResult<Metadata, Key>>;\n\n        put(key: Key, value: string | ArrayBuffer | ArrayBufferView | ReadableStream, options?: KVNamespacePutOptions): Promise<void>;\n\n        getWithMetadata<Metadata = unknown>(key: Key, options?: Partial<KVNamespaceGetOptions<undefined>>): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n        getWithMetadata<Metadata = unknown>(key: Key, type: \"text\"): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n        getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(key: Key, type: \"json\"): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;\n        getWithMetadata<Metadata = unknown>(key: Key, type: \"arrayBuffer\"): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>;\n        getWithMetadata<Metadata = unknown>(key: Key, type: \"stream\"): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>;\n        getWithMetadata<Metadata = unknown>(key: Key, options: KVNamespaceGetOptions<\"text\">): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n        getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(key: Key, options: KVNamespaceGetOptions<\"json\">): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;\n        getWithMetadata<Metadata = unknown>(key: Key, options: KVNamespaceGetOptions<\"arrayBuffer\">): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>;\n        getWithMetadata<Metadata = unknown>(key: Key, options: KVNamespaceGetOptions<\"stream\">): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>;\n\n        getWithMetadata<Metadata = unknown>(key: Array<Key>, type: \"text\"): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>;\n        getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(key: Array<Key>, type: \"json\"): Promise<Map<string, KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;\n        getWithMetadata<Metadata = unknown>(key: Array<Key>, options?: Partial<KVNamespaceGetOptions<undefined>>): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>;\n        getWithMetadata<Metadata = unknown>(key: Array<Key>, options?: KVNamespaceGetOptions<\"text\">): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>;\n        getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(key: Array<Key>, options?: KVNamespaceGetOptions<\"json\">): Promise<Map<string, KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;\n        delete(key: Key): Promise<void>;\n        deleteBulk(keys: Key | Key[]): Promise<void>;\n      });\n    } else {\n      JSG_TS_OVERRIDE(KVNamespace<Key extends string = string> {\n        get(key: Key, options?: Partial<KVNamespaceGetOptions<undefined>>): Promise<string | null>;\n        get(key: Key, type: \"text\"): Promise<string | null>;\n        get<ExpectedValue = unknown>(key: Key, type: \"json\"): Promise<ExpectedValue | null>;\n        get(key: Key, type: \"arrayBuffer\"): Promise<ArrayBuffer | null>;\n        get(key: Key, type: \"stream\"): Promise<ReadableStream | null>;\n        get(key: Key, options?: KVNamespaceGetOptions<\"text\">): Promise<string | null>;\n        get<ExpectedValue = unknown>(key: Key, options?: KVNamespaceGetOptions<\"json\">): Promise<ExpectedValue | null>;\n        get(key: Key, options?: KVNamespaceGetOptions<\"arrayBuffer\">): Promise<ArrayBuffer | null>;\n        get(key: Key, options?: KVNamespaceGetOptions<\"stream\">): Promise<ReadableStream | null>;\n\n        get(key: Array<Key>, type: \"text\"): Promise<Map<string, string | null>>;\n        get<ExpectedValue = unknown>(key: Array<Key>, type: \"json\"): Promise<Map<string, ExpectedValue | null>>;\n        get(key: Array<Key>, options?: Partial<KVNamespaceGetOptions<undefined>>): Promise<Map<string, string | null>>;\n        get(key: Array<Key>, options?: KVNamespaceGetOptions<\"text\">): Promise<Map<string, string | null>>;\n        get<ExpectedValue = unknown>(key: Array<Key>, options?: KVNamespaceGetOptions<\"json\">): Promise<Map<string, ExpectedValue | null>>;\n\n        list<Metadata = unknown>(options?: KVNamespaceListOptions): Promise<KVNamespaceListResult<Metadata, Key>>;\n\n        put(key: Key, value: string | ArrayBuffer | ArrayBufferView | ReadableStream, options?: KVNamespacePutOptions): Promise<void>;\n\n        getWithMetadata<Metadata = unknown>(key: Key, options?: Partial<KVNamespaceGetOptions<undefined>>): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n        getWithMetadata<Metadata = unknown>(key: Key, type: \"text\"): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n        getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(key: Key, type: \"json\"): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;\n        getWithMetadata<Metadata = unknown>(key: Key, type: \"arrayBuffer\"): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>;\n        getWithMetadata<Metadata = unknown>(key: Key, type: \"stream\"): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>;\n        getWithMetadata<Metadata = unknown>(key: Key, options: KVNamespaceGetOptions<\"text\">): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n        getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(key: Key, options: KVNamespaceGetOptions<\"json\">): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;\n        getWithMetadata<Metadata = unknown>(key: Key, options: KVNamespaceGetOptions<\"arrayBuffer\">): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>;\n        getWithMetadata<Metadata = unknown>(key: Key, options: KVNamespaceGetOptions<\"stream\">): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>;\n\n        getWithMetadata<Metadata = unknown>(key: Array<Key>, type: \"text\"): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>;\n        getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(key: Array<Key>, type: \"json\"): Promise<Map<string, KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;\n        getWithMetadata<Metadata = unknown>(key: Array<Key>, options?: Partial<KVNamespaceGetOptions<undefined>>): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>;\n        getWithMetadata<Metadata = unknown>(key: Array<Key>, options?: KVNamespaceGetOptions<\"text\">): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>;\n        getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(key: Array<Key>, options?: KVNamespaceGetOptions<\"json\">): Promise<Map<string, KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;\n        delete(key: Key): Promise<void>;\n      });\n    }\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"additionalHeaders\", additionalHeaders.asPtr());\n  }\n\n protected:\n  // Do the boilerplate work of constructing an HTTP client to KV. Setting a KvOptType causes\n  // the limiter for that op type to be checked. If a string is used, there isn't any limiter\n  // enforcement.\n  // NOTE: The urlStr is added to the headers as a non-owning reference and thus must outlive\n  // the usage of the headers.\n  kj::Own<kj::HttpClient> getHttpClient(IoContext& context,\n      kj::HttpHeaders& headers,\n      kj::OneOf<LimitEnforcer::KvOpType, kj::LiteralStringConst> opTypeOrName,\n      kj::StringPtr urlStr,\n      TraceContext& traceContext);\n\n private:\n  kj::Array<AdditionalHeader> additionalHeaders;\n  uint subrequestChannel;\n  kj::String bindingName;\n};\n\n#define EW_KV_ISOLATE_TYPES                                                                        \\\n  api::KvNamespace, api::KvNamespace::ListOptions, api::KvNamespace::GetOptions,                   \\\n      api::KvNamespace::PutOptions, api::KvNamespace::GetWithMetadataResult\n// The list of kv.h types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/memory-cache.c++",
    "content": "#include \"memory-cache.h\"\n\n#include <workerd/api/util.h>\n#include <workerd/io/io-context.h>\n#include <workerd/io/io-util.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/ser.h>\n#include <workerd/util/weak-refs.h>\n\nnamespace workerd::api {\n\nstatic constexpr size_t MAX_KEY_SIZE = 2 * 1024;\n\n// Returns the current calendar time as a double, just like Date.now() would,\n// except without the safeguards that exist within an I/O context. This\n// function is used only when a worker is being created or destroyed.\nstatic double getCurrentTimeOutsideIoContext() {\n  KJ_ASSERT(!IoContext::hasCurrent());\n  auto now = kj::systemCoarseCalendarClock().now();\n  return (now - kj::UNIX_EPOCH) / kj::MILLISECONDS;\n}\n\n// Returns true if the given expiration time exists and has passed. If this is\n// called in an I/O context, the I/O context's timer is used. Otherwise,\n// if allowOutsideIoContext is true, the system clock is used (see above).\n// Lastly, if this function is called from outside of an I/O context and if\n// allowOutsideIoContext is false, this function returns false regardless\n// of whether the expiration time has passed.\nstatic bool hasExpired(const kj::Maybe<double>& expiration, bool allowOutsideIoContext = false) {\n  KJ_IF_SOME(e, expiration) {\n    double now = (allowOutsideIoContext && !IoContext::hasCurrent())\n        ? getCurrentTimeOutsideIoContext()\n        : dateNow();\n    return e < now;\n  }\n  return false;\n}\n\nSharedMemoryCache::SharedMemoryCache(kj::Maybe<const MemoryCacheProvider&> provider,\n    kj::StringPtr id,\n    kj::Maybe<AdditionalResizeMemoryLimitHandler&> additionalResizeMemoryLimitHandler,\n    const kj::MonotonicClock& timer)\n    : provider(provider),\n      id(kj::str(id)),\n      additionalResizeMemoryLimitHandler(additionalResizeMemoryLimitHandler),\n      timer(timer) {}\n\nSharedMemoryCache::~SharedMemoryCache() noexcept(false) {\n  KJ_IF_SOME(p, provider) {\n    p.removeInstance(*this);\n  }\n}\n\nvoid SharedMemoryCache::suggest(const Limits& limits) const {\n  auto data = this->data.lockExclusive();\n  bool isKnownLimit = data->suggestedLimits.contains(limits);\n  data->suggestedLimits.insert(limits);\n  if (!isKnownLimit) {\n    resize(*data);\n  }\n}\n\nvoid SharedMemoryCache::unsuggest(const Limits& limits) const {\n  auto data = this->data.lockExclusive();\n  auto loc = data->suggestedLimits.find(limits);\n  KJ_ASSERT(loc != data->suggestedLimits.end());\n  data->suggestedLimits.erase(loc);\n  resize(*data);\n}\n\nvoid SharedMemoryCache::resize(ThreadUnsafeData& data) const {\n  data.effectiveLimits = Limits::min();\n  for (const auto& limits: data.suggestedLimits) {\n    data.effectiveLimits = Limits::max(data.effectiveLimits, limits.normalize());\n  }\n\n  KJ_IF_SOME(handler, additionalResizeMemoryLimitHandler) {\n    // Allow the embedder to adjust the effective limits.\n    handler(data);\n  }\n\n  // Fast path for clearing the cache.\n  if (data.effectiveLimits.maxKeys == 0) {\n    data.totalValueSize = 0;\n    data.cache.clear();\n    return;\n  }\n\n  // First, remove any values that might be too large.\n  while (data.cache.size() != 0) {\n    MemoryCacheEntry& largestEntry = *data.cache.ordered<2>().begin();\n    if (largestEntry.size() <= data.effectiveLimits.maxValueSize) {\n      break;\n    }\n    data.totalValueSize -= largestEntry.size();\n    data.cache.erase(largestEntry);\n  }\n\n  // Now just keep keep evicting until we are within limits.\n  while (data.totalValueSize > data.effectiveLimits.maxTotalValueSize ||\n      data.cache.size() > data.effectiveLimits.maxKeys) {\n    evictNextWhileLocked(data, true);\n  }\n}\n\nkj::Maybe<kj::Own<CacheValue>> SharedMemoryCache::getWhileLocked(\n    ThreadUnsafeData& data, const kj::String& key) const {\n  KJ_IF_SOME(existingCacheEntry, data.cache.find(key)) {\n    if (hasExpired(existingCacheEntry.expiration)) {\n      // The cache entry has an associated expiration time and that time has\n      // passed (according to the calling IoContext's timer).\n      data.totalValueSize -= existingCacheEntry.size();\n      data.cache.erase(existingCacheEntry);\n      return kj::none;\n    }\n\n    // Obtain a reference to the cache value before we kj::mv the cache entry.\n    auto cacheValue = kj::atomicAddRef(*existingCacheEntry.value);\n\n    // Update the liveliness.\n    MemoryCacheEntry entry = data.cache.release(existingCacheEntry);\n    entry.liveliness = data.stepLiveliness();\n    data.cache.insert(kj::mv(entry));\n\n    return kj::mv(cacheValue);\n  } else {\n    return kj::none;\n  }\n}\n\nvoid SharedMemoryCache::putWhileLocked(ThreadUnsafeData& data,\n    const kj::String& key,\n    kj::Own<CacheValue>&& value,\n    kj::Maybe<double> expiration) const {\n  size_t valueSize = value->bytes.size();\n\n  auto writeSpan = IoContext::current().makeTraceSpan(\"memory_cache_write\"_kjc);\n  writeSpan.setTag(\"key\"_kjc, key.asPtr());\n  writeSpan.setTag(\"value_size\"_kjc, static_cast<double>(valueSize));\n  writeSpan.setTag(\"has_expiration\"_kjc, expiration != kj::none);\n\n  if (valueSize > data.effectiveLimits.maxValueSize) {\n    // Silently drop the value. For consistency, also drop the previous value,\n    // if one exists, such that a subsequent read() will not return an outdated\n    // value. Note that removeIfExistsWhileLocked(key) will update the\n    // totalValueSize if necessary, so we don't need to do that here.\n    writeSpan.setTag(\"write_rejected\"_kjc, true);\n    writeSpan.setTag(\"rejection_reason\"_kjc, \"value_too_large\"_kjc);\n    writeSpan.setTag(\"max_value_size\"_kjc, static_cast<double>(data.effectiveLimits.maxValueSize));\n    removeIfExistsWhileLocked(data, key);\n    return;\n  }\n\n  if (hasExpired(expiration)) {\n    writeSpan.setTag(\"write_rejected\"_kjc, true);\n    writeSpan.setTag(\"rejection_reason\"_kjc, \"already_expired\"_kjc);\n    removeIfExistsWhileLocked(data, key);\n    return;\n  }\n\n  kj::Maybe<MemoryCacheEntry&> existingEntry = data.cache.find(key.asPtr());\n  bool isUpdate = existingEntry != kj::none;\n  size_t evictionCount = 0;\n\n  KJ_IF_SOME(entry, existingEntry) {\n    size_t oldValueSize = entry.size();\n    KJ_ASSERT(data.totalValueSize >= oldValueSize);\n    MemoryCacheEntry updatedEntry = data.cache.release(entry);\n    data.totalValueSize -= oldValueSize;\n    while (data.totalValueSize + valueSize > data.effectiveLimits.maxTotalValueSize) {\n      // We have already released the existing entry for our key, so there is no\n      // risk of evicting it.\n      evictNextWhileLocked(data);\n      evictionCount++;\n    }\n    updatedEntry.liveliness = data.stepLiveliness();\n    updatedEntry.value = kj::mv(value);\n    updatedEntry.expiration = expiration;\n    data.cache.insert(kj::mv(updatedEntry));\n    data.totalValueSize += valueSize;\n  } else {\n    // Ensure that adding a new key won't push us over the limit.\n    if (data.cache.size() >= data.effectiveLimits.maxKeys) {\n      evictNextWhileLocked(data);\n      evictionCount++;\n    }\n    // Ensure that the size of the new value won't push us over the limit.\n    while (data.totalValueSize + valueSize > data.effectiveLimits.maxTotalValueSize) {\n      evictNextWhileLocked(data);\n      evictionCount++;\n    }\n    MemoryCacheEntry newEntry = {\n      kj::str(key),\n      data.stepLiveliness(),\n      kj::mv(value),\n      expiration,\n    };\n    data.cache.insert(kj::mv(newEntry));\n    data.totalValueSize += valueSize;\n  }\n\n  writeSpan.setTag(\"write_success\"_kjc, true);\n  writeSpan.setTag(\"is_update\"_kjc, isUpdate);\n  writeSpan.setTag(\"evictions_triggered\"_kjc, static_cast<double>(evictionCount));\n  writeSpan.setTag(\"cache_total_size_after\"_kjc, static_cast<double>(data.totalValueSize));\n  writeSpan.setTag(\"cache_entry_count_after\"_kjc, static_cast<double>(data.cache.size()));\n}\n\nvoid SharedMemoryCache::evictNextWhileLocked(\n    ThreadUnsafeData& data, bool allowOutsideIoContext) const {\n  // The caller is responsible for ensuring that the cache is not empty already.\n  KJ_REQUIRE(data.cache.size() > 0);\n\n  // Create eviction span - only called from IO context\n  auto evictionSpan = IoContext::current().makeTraceSpan(\"memory_cache_eviction\"_kjc);\n\n  // If there is an entry that has expired already, evict that one.\n  MemoryCacheEntry& maybeExpired = *data.cache.ordered<3>().begin();\n  KJ_ASSERT(data.totalValueSize >= maybeExpired.size());\n  if (hasExpired(maybeExpired.expiration, allowOutsideIoContext)) {\n    evictionSpan.setTag(\"eviction_reason\"_kjc, \"expiration\"_kjc);\n    evictionSpan.setTag(\"evicted_key\"_kjc, maybeExpired.key.asPtr());\n    evictionSpan.setTag(\"evicted_size\"_kjc, static_cast<double>(maybeExpired.size()));\n    evictionSpan.setTag(\"cache_size_before\"_kjc, static_cast<double>(data.totalValueSize));\n    evictionSpan.setTag(\"cache_entries_before\"_kjc, static_cast<double>(data.cache.size()));\n    data.totalValueSize -= maybeExpired.size();\n    data.cache.erase(maybeExpired);\n    return;\n  }\n\n  // Otherwise, if no entry has expired, evict the least recently used entry.\n  MemoryCacheEntry& leastRecentlyUsed = *data.cache.ordered<1>().begin();\n  evictionSpan.setTag(\"eviction_reason\"_kjc, \"lru\"_kjc);\n  evictionSpan.setTag(\"evicted_key\"_kjc, leastRecentlyUsed.key.asPtr());\n  evictionSpan.setTag(\"evicted_size\"_kjc, static_cast<double>(leastRecentlyUsed.size()));\n  evictionSpan.setTag(\"cache_size_before\"_kjc, static_cast<double>(data.totalValueSize));\n  evictionSpan.setTag(\"cache_entries_before\"_kjc, static_cast<double>(data.cache.size()));\n  KJ_ASSERT(data.totalValueSize >= leastRecentlyUsed.size());\n  data.totalValueSize -= leastRecentlyUsed.size();\n  data.cache.erase(leastRecentlyUsed);\n}\n\nvoid SharedMemoryCache::removeIfExistsWhileLocked(\n    ThreadUnsafeData& data, const kj::String& key) const {\n  KJ_IF_SOME(entry, data.cache.find(key)) {\n    // This DOES NOT count as an eviction because it might happen while\n    // replacing the existing cache entry with a new one, when the new one is\n    // being evicted immediately. It is up to the caller to count that.\n    size_t valueSize = entry.size();\n    KJ_ASSERT(valueSize <= data.totalValueSize);\n    data.totalValueSize -= valueSize;\n    data.cache.erase(entry);\n  }\n}\n\nkj::Own<const SharedMemoryCache> SharedMemoryCache::create(\n    kj::Maybe<const MemoryCacheProvider&> provider,\n    kj::StringPtr id,\n    kj::Maybe<AdditionalResizeMemoryLimitHandler&> handler,\n    const kj::MonotonicClock& timer) {\n  return kj::atomicRefcounted<const SharedMemoryCache>(provider, id, handler, timer);\n}\n\nSharedMemoryCache::Use::Use(kj::Own<const SharedMemoryCache> cache, const Limits& limits)\n    : cache(kj::mv(cache)),\n      limits(limits) {\n  this->cache->suggest(limits);\n}\n\nSharedMemoryCache::Use::Use(Use&& other): cache(kj::mv(other.cache)), limits(other.limits) {\n  this->cache->suggest(limits);\n}\n\nSharedMemoryCache::Use::~Use() noexcept(false) {\n  if (cache.get() != nullptr) {\n    cache->unsuggest(limits);\n  }\n}\n\nkj::Maybe<kj::Own<CacheValue>> SharedMemoryCache::Use::getWithoutFallback(\n    const kj::String& key, SpanBuilder& readSpan) const {\n  kj::Locked<ThreadUnsafeData> data = [&] {\n    auto memoryCacheLockRecord =\n        ScopedDurationTagger(readSpan, memoryCachekLockWaitTimeTag, cache->timer);\n    return cache->data.lockExclusive();\n  }();\n  auto result = cache->getWhileLocked(*data, key);\n\n  // Track cache hit/miss\n  readSpan.setTag(\"cache_hit\"_kjc, result != kj::none);\n  KJ_IF_SOME(value, result) {\n    readSpan.setTag(\"entry_size\"_kjc, static_cast<double>(value->bytes.size()));\n  }\n  readSpan.setTag(\"cache_total_size\"_kjc, static_cast<double>(data->totalValueSize));\n  readSpan.setTag(\"cache_entry_count\"_kjc, static_cast<double>(data->cache.size()));\n\n  return result;\n}\n\nkj::OneOf<kj::Own<CacheValue>, kj::Promise<SharedMemoryCache::Use::GetWithFallbackOutcome>>\nSharedMemoryCache::Use::getWithFallback(const kj::String& key, SpanBuilder& readSpan) const {\n  kj::Locked<ThreadUnsafeData> data = [&] {\n    auto memoryCacheLockRecord =\n        ScopedDurationTagger(readSpan, memoryCachekLockWaitTimeTag, cache->timer);\n    return cache->data.lockExclusive();\n  }();\n  KJ_IF_SOME(existingValue, cache->getWhileLocked(*data, key)) {\n    // Cache hit\n    readSpan.setTag(\"cache_hit\"_kjc, true);\n    readSpan.setTag(\"entry_size\"_kjc, static_cast<double>(existingValue->bytes.size()));\n    readSpan.setTag(\"cache_total_size\"_kjc, static_cast<double>(data->totalValueSize));\n    readSpan.setTag(\"cache_entry_count\"_kjc, static_cast<double>(data->cache.size()));\n    return kj::mv(existingValue);\n  } else KJ_IF_SOME(existingInProgress, data->inProgress.find(key)) {\n    // Cache miss - but another request is already fetching this key\n    readSpan.setTag(\"cache_hit\"_kjc, false);\n    readSpan.setTag(\"coalesced_request\"_kjc, true);\n    readSpan.setTag(\"waiting_on_inflight\"_kjc, true);\n    readSpan.setTag(\n        \"inflight_waiters_count\"_kjc, static_cast<double>(existingInProgress->waiting.size() + 1));\n\n    // Create a span to track how long we wait for the inflight request\n    auto waitSpan = readSpan.newChild(\"memory_cache_coalesce_wait\"_kjc);\n    waitSpan.setTag(\"key\"_kjc, key.asPtr());\n    waitSpan.setTag(\"waiters_ahead\"_kjc, static_cast<double>(existingInProgress->waiting.size()));\n\n    // We return a Promise, but we keep the fulfiller. We might fulfill it\n    // from a different thread, so we need a cross-thread fulfiller here.\n    auto pair = kj::newPromiseAndCrossThreadFulfiller<GetWithFallbackOutcome>();\n    existingInProgress->waiting.emplace(kj::mv(pair.fulfiller));\n    // We have to register a pending event with the I/O context so that the\n    // runtime does not detect a hanging promise. Another fallback is in\n    // progress and once it settles, we will fulfill the promise that we return\n    // here, either with the produced value or with another fallback task.\n    return pair.promise.attach(IoContext::current().registerPendingEvent(), kj::mv(waitSpan));\n  } else {\n    // Cache miss - this request will fetch from upstream\n    readSpan.setTag(\"cache_hit\"_kjc, false);\n    readSpan.setTag(\"coalesced_request\"_kjc, false);\n    readSpan.setTag(\"initiating_fallback\"_kjc, true);\n    readSpan.setTag(\"cache_total_size\"_kjc, static_cast<double>(data->totalValueSize));\n    readSpan.setTag(\"cache_entry_count\"_kjc, static_cast<double>(data->cache.size()));\n\n    auto& newEntry = data->inProgress.insert(kj::heap<InProgress>(kj::str(key)));\n    auto inProgress = newEntry.get();\n    return kj::Promise<GetWithFallbackOutcome>(prepareFallback(*inProgress));\n  }\n}\n\nSharedMemoryCache::Use::FallbackDoneCallback SharedMemoryCache::Use::prepareFallback(\n    InProgress& inProgress) const {\n  // We need to detect if the Promise that we are about to create ever settles,\n  // as opposed to being destroyed without either being resolved or rejecting.\n  struct FallbackStatus {\n    bool hasSettled = false;\n  };\n  auto status = kj::heap<FallbackStatus>();\n  auto& statusRef = *status;\n\n  auto deferredCancel = kj::defer([this, status = kj::mv(status), &inProgress]() {\n    // If the callback was destroyed without having run (for example, because\n    // it was added to an I/O context that has since been canceled), we treat\n    // it as if the promise had failed.\n    if (!status->hasSettled) {\n      handleFallbackFailure(inProgress);\n    }\n  });\n\n  return [this, &inProgress, &status = statusRef, deferredCancel = kj::mv(deferredCancel)](\n             kj::Maybe<FallbackResult> maybeResult, SpanBuilder& fallbackSpan) mutable {\n    KJ_IF_SOME(result, maybeResult) {\n      // The fallback succeeded. Store the value in the cache and propagate it to\n      // all waiting requests, even if it has expired already.\n      status.hasSettled = true;\n\n      auto data = cache->data.lockExclusive();\n      size_t waiterCount = inProgress.waiting.size();\n\n      cache->putWhileLocked(\n          *data, kj::str(inProgress.key), kj::atomicAddRef(*result.value), result.expiration);\n\n      inProgress.waiting.drainTo(\n          [&](auto&& waiter) { waiter.fulfiller->fulfill(kj::atomicAddRef(*result.value)); });\n      data->inProgress.eraseMatch(inProgress.key);\n\n      // Track the completion of fallback and distribution to waiters\n      fallbackSpan.setTag(\"waiters_notified\"_kjc, static_cast<double>(waiterCount));\n    } else {\n      // The fallback failed for some reason. We do not care much about why it\n      // failed. If there are other queued fallbacks, handelFallbackFailure will\n      // schedule the next one.\n      status.hasSettled = true;\n      handleFallbackFailure(inProgress);\n    }\n  };\n}\n\nvoid SharedMemoryCache::Use::handleFallbackFailure(InProgress& inProgress) const {\n  kj::Own<kj::CrossThreadPromiseFulfiller<GetWithFallbackOutcome>> nextFulfiller;\n\n  // If there is another queued fallback, retrieve it and remove it from the\n  // queue. Otherwise, just delete the queue entirely.\n  {\n    auto data = cache->data.lockExclusive();\n\n    KJ_IF_SOME(next, inProgress.waiting.pop()) {\n      nextFulfiller = kj::mv(next.fulfiller);\n    } else {\n      data->inProgress.eraseMatch(inProgress.key);\n    }\n  }\n\n  // fulfill() might destroy the Promise returned by prepareFallback(). In\n  // particular, that will happen if the I/O context that the fulfiller was\n  // created for has been canceled or destroyed, in which case the promise\n  // associated with the fulfiller has been destroyed. When the promise returned\n  // by prepareFallback() is destroyed without having settled, it will recover\n  // from that, but it will lock the cache while doing so. That is why it is\n  // important that the cache is not already locked when we call fulfill().\n  if (nextFulfiller) {\n    nextFulfiller->fulfill(prepareFallback(inProgress));\n  }\n}\n\nvoid SharedMemoryCache::Use::delete_(const kj::String& key) const {\n  auto data = cache->data.lockExclusive();\n  cache->removeIfExistsWhileLocked(*data, key);\n}\n\n// Attempts to serialize a JavaScript value. If that fails, this function throws\n// a tunneled exception, see jsg::createTunneledException().\nstatic kj::Own<CacheValue> hackySerialize(jsg::Lock& js, jsg::JsRef<jsg::JsValue>& value) {\n  JSG_TRY(js) {\n    jsg::Serializer serializer(js);\n    serializer.write(js, value.getHandle(js));\n    return kj::atomicRefcounted<CacheValue>(serializer.release().data);\n  }\n  JSG_CATCH(exception) {\n    // We run into big problems with tunneled exceptions here. When\n    // the toString() function of the JavaScript error is not marked\n    // as side effect free, tunneling the exception fails entirely\n    // because kj::str() returns an empty string for the error. As a\n    // workaround, we drop the error object in that case and return\n    // a generic error that only includes the type of the value.\n    // TODO(later): remove this workaround\n    if (kj::str(exception.getHandle(js)).size() == 0) {\n      throw JSG_KJ_EXCEPTION(\n          FAILED, DOMDataCloneError, \"failed to serialize \", value.getHandle(js).typeOf(js));\n    }\n\n    // This is still pretty bad. We lose the original error stack.\n    // TODO(later): remove string-based error tunneling\n    throw js.exceptionToKj(kj::mv(exception));\n  }\n}\n\njsg::Promise<jsg::JsRef<jsg::JsValue>> MemoryCache::read(jsg::Lock& js,\n    jsg::NonCoercible<kj::String> key,\n    jsg::Optional<FallbackFunction> optionalFallback) {\n  if (key.value.size() > MAX_KEY_SIZE) {\n    return js.rejectedPromise<jsg::JsRef<jsg::JsValue>>(js.rangeError(\"Key too large.\"_kj));\n  }\n\n  auto readSpan = IoContext::current().makeTraceSpan(\"memory_cache_read\"_kjc);\n  auto userReadSpan = IoContext::current().makeUserTraceSpan(\"memory_cache_read\"_kjc);\n\n  KJ_IF_SOME(fallback, optionalFallback) {\n    KJ_SWITCH_ONEOF(cacheUse.getWithFallback(key.value, readSpan)) {\n      KJ_CASE_ONEOF(result, kj::Own<CacheValue>) {\n        // Optimization: Don't even release the isolate lock if the value is already in cache.\n        jsg::Deserializer deserializer(js, result->bytes.asPtr());\n        auto value = jsg::JsRef(js, deserializer.readValue(js));\n\n        return js.resolvedPromise(kj::mv(value));\n      }\n      KJ_CASE_ONEOF(promise, kj::Promise<SharedMemoryCache::Use::GetWithFallbackOutcome>) {\n        return IoContext::current().awaitIo(js, kj::mv(promise),\n            [fallback = kj::mv(fallback), key = kj::str(key.value), readSpan = kj::mv(readSpan),\n                userSpan = kj::mv(userReadSpan), self = JSG_THIS](\n                jsg::Lock& js, SharedMemoryCache::Use::GetWithFallbackOutcome cacheResult) mutable\n            -> jsg::Promise<jsg::JsRef<jsg::JsValue>> {\n          KJ_SWITCH_ONEOF(cacheResult) {\n            KJ_CASE_ONEOF(serialized, kj::Own<CacheValue>) {\n              readSpan.setTag(\"fallback_cache_hit\"_kjc, true);\n              readSpan.setTag(\"entry_size\"_kjc, static_cast<double>(serialized->bytes.size()));\n\n              jsg::Deserializer deserializer(js, serialized->bytes.asPtr());\n              return js.resolvedPromise(jsg::JsRef(js, deserializer.readValue(js)));\n            }\n            KJ_CASE_ONEOF(callback, SharedMemoryCache::Use::FallbackDoneCallback) {\n              auto& context = IoContext::current();\n              auto heapCallback = kj::heap(kj::mv(callback));\n\n              // Create a span for the fallback execution\n              auto fallbackSpan = readSpan.newChild(\"memory_cache_fallback\"_kjc);\n              fallbackSpan.setTag(\"key\"_kjc, key.asPtr());\n\n              // Wrap the spans in RefcountedWrapper so they can be shared between then/catch\n              auto fallbackSpanRc = kj::refcountedWrapper<SpanBuilder>(kj::mv(fallbackSpan));\n              auto readSpanRc = kj::refcountedWrapper<SpanBuilder>(kj::mv(readSpan));\n\n              return js.evalNow([&]() { return fallback(js, kj::mv(key)); })\n                  .then(js,\n                      [callback = context.addObject(*heapCallback),\n                          fallbackSpan = fallbackSpanRc->addWrappedRef(),\n                          readSpan = readSpanRc->addWrappedRef()](jsg::Lock& js,\n                          CacheValueProduceResult result) mutable -> jsg::JsRef<jsg::JsValue> {\n                // NOTE: `callback` is IoPtr, not IoOwn. The catch block gets the IoOwn, which\n                //   ensures the object still exists at this point.\n                fallbackSpan->setTag(\"fallback_success\"_kjc, true);\n\n                auto serialized = hackySerialize(js, result.value);\n                fallbackSpan->setTag(\n                    \"fallback_result_size\"_kjc, static_cast<double>(serialized->bytes.size()));\n\n                KJ_IF_SOME(expiration, result.expiration) {\n                  JSG_REQUIRE(\n                      !kj::isNaN(expiration), TypeError, \"Expiration time must not be NaN.\");\n                  fallbackSpan->setTag(\"has_expiration\"_kjc, true);\n                } else {\n                  fallbackSpan->setTag(\"has_expiration\"_kjc, false);\n                }\n                (*callback)(\n                    SharedMemoryCache::Use::FallbackResult{kj::mv(serialized), result.expiration},\n                    *fallbackSpan);\n                return kj::mv(result.value);\n              })\n                  .catch_(js,\n                  JSG_VISITABLE_LAMBDA(\n                      (self = kj::mv(self), callback = context.addObject(kj::mv(heapCallback)),\n                          fallbackSpan = fallbackSpanRc->addWrappedRef(),\n                          readSpan = readSpanRc->addWrappedRef()),\n                      (self),\n                      (jsg::Lock & js, jsg::Value&& exception) mutable->jsg::JsRef<jsg::JsValue> {\n                        fallbackSpan->setTag(\"fallback_success\"_kjc, false);\n                        fallbackSpan->setTag(\n                            \"fallback_error\"_kjc, kj::str(exception.getHandle(js)));\n                        (*callback)(kj::none, *fallbackSpan);\n                        js.throwException(kj::mv(exception));\n                      }));\n            }\n          }\n          KJ_UNREACHABLE;\n        });\n      }\n    }\n    KJ_UNREACHABLE;\n  } else {\n    KJ_IF_SOME(cacheValue, cacheUse.getWithoutFallback(key.value, readSpan)) {\n      jsg::Deserializer deserializer(js, cacheValue->bytes.asPtr());\n      return js.resolvedPromise(jsg::JsRef(js, deserializer.readValue(js)));\n    }\n    readSpan.setTag(\"cache_hit\"_kjc, false);\n    return js.resolvedPromise(jsg::JsRef(js, js.undefined()));\n  }\n}\n\nvoid MemoryCache::delete_(jsg::Lock& js, jsg::NonCoercible<kj::String> key) {\n  // Ignore operations on keys exceeding key max size.\n  if (key.value.size() > MAX_KEY_SIZE) {\n    js.throwException(js.rangeError(\"Key too large.\"_kj));\n    return;\n  }\n\n  auto deleteSpan = IoContext::current().makeTraceSpan(\"memory_cache_delete\"_kjc);\n  deleteSpan.setTag(\"key\"_kjc, key.value.asPtr());\n\n  cacheUse.delete_(key.value);\n\n  deleteSpan.setTag(\"delete_completed\"_kjc, true);\n}\n\n// ======================================================================================\n\nMemoryCacheProvider::MemoryCacheProvider(const kj::MonotonicClock& timer,\n    kj::Maybe<SharedMemoryCache::AdditionalResizeMemoryLimitHandler>\n        additionalResizeMemoryLimitHandler)\n    : additionalResizeMemoryLimitHandler(kj::mv(additionalResizeMemoryLimitHandler)),\n      timer(timer) {}\n\nMemoryCacheProvider::~MemoryCacheProvider() noexcept(false) {\n  // TODO(cleanup): Later, assuming progress is made on kj::Ptr<T>, we ought to be able\n  // to remove this. For now we just need to make sure that the MemoryCacheProvider instance\n  // outlives any SharedMemoryCache instances that are referencing it.\n  KJ_REQUIRE(caches.lockShared()->size() == 0,\n      \"There are still active SharedMemoryCache instances. Use-after-free errors are likely.\");\n}\n\nkj::Own<const SharedMemoryCache> MemoryCacheProvider::getInstance(\n    kj::Maybe<kj::StringPtr> cacheId) const {\n\n  const auto makeCache = [this](kj::Maybe<const MemoryCacheProvider&> provider, kj::StringPtr id) {\n    // The cache doesn't exist in the map. Let's create it.\n    auto handler = additionalResizeMemoryLimitHandler.map(\n        [](const SharedMemoryCache::AdditionalResizeMemoryLimitHandler& handler)\n            -> SharedMemoryCache::AdditionalResizeMemoryLimitHandler& {\n      return const_cast<SharedMemoryCache::AdditionalResizeMemoryLimitHandler&>(handler);\n    });\n    return SharedMemoryCache::create(provider, id, handler, timer);\n  };\n\n  KJ_IF_SOME(cid, cacheId) {\n    auto lock = caches.lockExclusive();\n\n    // First, let's see if the cache already exists. If it does, we'll just return\n    // a strong reference to it.\n    KJ_IF_SOME(found, lock->find(cid)) {\n      KJ_IF_SOME(ref, kj::atomicAddRefWeak(*found)) {\n        return kj::mv(ref);\n      } else {\n        // We found an entry in the map, but atomicAddRefWeak failed. Doh. We have\n        // to replace the map entry with a new cache instance.\n        auto cache = makeCache(kj::Maybe<const MemoryCacheProvider&>(*this), cid);\n        lock->upsert(kj::str(cid), cache.get());\n        return kj::mv(cache);\n      }\n    }\n\n    // The cache doesn't exist, let's create it and add it to the map\n    auto cache = makeCache(kj::Maybe<const MemoryCacheProvider&>(*this), cid);\n    lock->insert(kj::str(cid), cache.get());\n    return kj::mv(cache);\n  }\n\n  // Since we don't have a cache id, we'll just create a new cache and return it.\n  return makeCache(kj::none, nullptr);\n}\n\nvoid MemoryCacheProvider::removeInstance(const SharedMemoryCache& instance) const {\n  // This is fun. We have to make sure that the instance to be removed is actually\n  // what we expect it to be.\n  auto lock = caches.lockExclusive();\n  KJ_IF_SOME(found, lock->findEntry(instance.getId())) {\n    if (found.value != &instance) {\n      // Not the instance we expected it to be. Cache instance was likely replaced\n      // by a new instance with the same id. Do nothing.\n      return;\n    }\n    lock->erase(found);\n  }\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/memory-cache.h",
    "content": "#pragma once\n\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/util/checked-queue.h>\n\n#include <kj/hash.h>\n#include <kj/map.h>\n#include <kj/mutex.h>\n#include <kj/table.h>\n#include <kj/time.h>\n\n#include <set>\n\nnamespace workerd {\nclass SpanBuilder;\n}\n\nnamespace workerd::api {\n\n// The MemoryCache mechanism is an in-process, memory-resident data cache that\n// can be configured for workers. A single cache instance can be unique to an\n// individual worker or shared across multiple workers / isolates.\n//\n// Instances are configured as bindings on the worker (set up in the workers\n// configuration) and accessible via the environment bindings passed into the\n// worker handler functions:\n//\n//  async fetch(req, env) {\n//    await env.MY_CACHE.read('key', () => {\n//      // Called if the 'key' does not exist in the cache\n//      return 'new value';\n//    });\n//  }\n//\n// The cache is only capable of storing values that are v8 serializable (so\n// JS primitives other than Symbol, ordinary JavaScript objects but not class\n// instances, etc). Objects that represent i/o (like streams or promises are\n// explicitly not supported.\n\nstruct CacheValue: kj::AtomicRefcounted {\n  CacheValue(kj::Array<kj::byte>&& bytes): bytes(kj::mv(bytes)) {}\n\n  kj::Array<kj::byte> bytes;\n};\n\nstruct MemoryCacheEntry {\n  // The key that this entry is associated with.\n  kj::String key;\n\n  // Whenever an entry is created, updated, or retrieved, its liveliness is\n  // set to the value of a monotonically increasing counter.\n  uint64_t liveliness;\n  // TODO(cleanup): The liveliness index accomplishes the same thing as\n  //   kj::InsertionOrderIndex.\n  //\n  // TODO(perf): Updating a cache entry's liveliness requires a re-insertion,\n  //   which means that cache reads require an exclusive lock. This may be\n  //   suboptimal for a read-heavy workload. WorkerSet avoids this by atomically\n  //   updating a `lastUsed` timestamp. The tradeoff is that LRU-eviction\n  //   becomes O(n) instead of O(1), since we can no longer use kj::Table's\n  //   index to find the LRU entry.\n\n  // The stored JavaScript value, serialized by V8. It is atomicRefcounted to\n  // allow threads to deserialize the value without having to lock the cache,\n  // so the value can even be deserialized while the cache entry is being\n  // evicted.\n  kj::Own<CacheValue> value;\n\n  inline size_t size() const {\n    return value->bytes.size();\n  }\n\n  // The expiration timestamp of this cache entry, usually the time at which the\n  // entry was created plus some TTL. This is measured in milliseconds and\n  // stored as a double so that it is compatible with api::dateNow() and\n  // EdgeWorkerPlatform::CurrentClockTimeMillis().\n  kj::Maybe<double> expiration;\n};\n\nstruct CacheValueProduceResult {\n  jsg::JsRef<jsg::JsValue> value;\n  jsg::Optional<double> expiration;\n  JSG_STRUCT(value, expiration);\n};\n\nclass MemoryCacheProvider;\n\n// An in-memory cache that can be accessed by any number of workers/isolates\n// within the same process.\n// TODO(soon): We plan to explore replacing this implementation with a memcached-based\n// implementation in the near future. The memcached-based impl would likely be\n// fairly different from this implementation so quite a few of the details here\n// are expected to change.\nclass SharedMemoryCache: public kj::AtomicRefcounted {\n private:\n  struct InProgress;\n\n public:\n  struct ThreadUnsafeData;\n\n  struct Limits {\n    // The maximum number of keys that may exist within the cache at the same\n    // time. The cache size grows at least linearly in the number of entries.\n    uint32_t maxKeys;\n\n    // The maximum size of each individual value, when serialized.\n    uint32_t maxValueSize;\n\n    // The maximum sum of all stored values. This is essentially the cache size,\n    // except that it only includes the sizes of the values and does not account\n    // for keys and the overhead of the data structures themselves.\n    uint64_t maxTotalValueSize;\n\n    bool operator<(const Limits& b) const {\n      if (maxTotalValueSize != b.maxTotalValueSize) {\n        return maxTotalValueSize < b.maxTotalValueSize;\n      }\n      if (maxKeys != b.maxKeys) {\n        return maxKeys < b.maxKeys;\n      }\n      return maxTotalValueSize < b.maxTotalValueSize;\n    }\n\n    Limits normalize() const KJ_WARN_UNUSED_RESULT {\n      // Avoid surprises due to misconfigured bindings that set one or more limits to 0.\n      if (maxKeys == 0 || maxValueSize == 0 || maxTotalValueSize == 0) {\n        return min();\n      }\n\n      // If a binding specifies a maxValueSize that exceeds the maxTotalValueSize, remedy\n      // that by reducing the maxValueSize.\n      return Limits{\n        .maxKeys = maxKeys,\n        .maxValueSize = static_cast<uint32_t>(kj::min(maxValueSize, maxTotalValueSize)),\n        .maxTotalValueSize = maxTotalValueSize,\n      };\n    }\n\n    static constexpr Limits min() {\n      return {0, 0, 0};\n    }\n\n    static Limits max(const Limits& a, const Limits& b) {\n      return Limits{\n        std::max(a.maxKeys, b.maxKeys),\n        std::max(a.maxValueSize, b.maxValueSize),\n        std::max(a.maxTotalValueSize, b.maxTotalValueSize),\n      };\n    }\n  };\n\n  KJ_DISALLOW_COPY_AND_MOVE(SharedMemoryCache);\n\n  using AdditionalResizeMemoryLimitHandler = kj::Function<void(ThreadUnsafeData&)>;\n\n  SharedMemoryCache(kj::Maybe<const MemoryCacheProvider&> provider,\n      kj::StringPtr id,\n      kj::Maybe<AdditionalResizeMemoryLimitHandler&> additionalResizeMemoryLimitHandler,\n      const kj::MonotonicClock& timer);\n\n  ~SharedMemoryCache() noexcept(false);\n\n  kj::StringPtr getId() const {\n    return id;\n  }\n\n  static kj::Own<const SharedMemoryCache> create(kj::Maybe<const MemoryCacheProvider&> provider,\n      kj::StringPtr id,\n      kj::Maybe<AdditionalResizeMemoryLimitHandler&> additionalResizeMemoryLimitHandler,\n      const kj::MonotonicClock& timer);\n\n  // RAII class that attaches itself to a cache, suggests cache limits to the\n  // cache it is attached to, and allows interacting with the cache.\n  class Use {\n   public:\n    KJ_DISALLOW_COPY(Use);\n\n    Use(kj::Own<const SharedMemoryCache> cache, const Limits& limits);\n    Use(Use&& other);\n    ~Use() noexcept(false);\n\n    // Returns a cached value for the given key if one exists (and has not\n    // expired). If no such value exists, nothing is returned, regardless of any\n    // in-progress fallbacks trying to produce such a value.\n    kj::Maybe<kj::Own<CacheValue>> getWithoutFallback(\n        const kj::String& key, SpanBuilder& readSpan) const;\n\n    struct FallbackResult {\n      kj::Own<CacheValue> value;\n      kj::Maybe<double> expiration;\n    };\n    using FallbackDoneCallback = kj::Function<void(kj::Maybe<FallbackResult>, SpanBuilder&)>;\n    using GetWithFallbackOutcome = kj::OneOf<kj::Own<CacheValue>, FallbackDoneCallback>;\n\n    // Returns either:\n    // 1. The immediate value, if already in cache.\n    // 2. A Promise that will eventually resolve either to the cached value\n    //    or to a FallbackDoneCallback. In the latter case, the caller should\n    //    invoke the fallback function.\n    kj::OneOf<kj::Own<CacheValue>, kj::Promise<GetWithFallbackOutcome>> getWithFallback(\n        const kj::String& key, SpanBuilder& readSpan) const;\n\n    void delete_(const kj::String& key) const;\n\n   private:\n    // Creates a new FallbackDoneCallback associated with the given\n    // InProgress struct. This is called whenever getWithFallback() wants to\n    // invoke a fallback but it does not call the fallback directly. The caller\n    // is responsible for passing the returned task and fulfiller to the\n    // respective I/O context in which the fallback will run.\n    FallbackDoneCallback prepareFallback(InProgress& inProgress) const;\n\n    // Called whenever a fallback has failed. The fallback might have thrown an\n    // error or it might have returned a Promise that rejected, or the I/O\n    // context in which the fallback should have been invoked has already been\n    // destroyed. If other concurrent read operations have queued fallbacks,\n    // this schedules the next fallback. Otherwise, the InProgress struct is\n    // erased.\n    void handleFallbackFailure(InProgress& inProgress) const;\n\n    kj::Own<const SharedMemoryCache> cache;\n    static constexpr auto memoryCachekLockWaitTimeTag = \"memory_cache_lock_wait_time_ns\"_kjc;\n    Limits limits;\n  };\n\n private:\n  struct InProgress {\n    const kj::String key;\n\n    struct Waiter {\n      kj::Own<kj::CrossThreadPromiseFulfiller<Use::GetWithFallbackOutcome>> fulfiller;\n    };\n    workerd::util::Queue<Waiter> waiting;\n\n    InProgress(kj::String&& key): key(kj::mv(key)) {}\n\n    // Callbacks for a HashIndex that allow locating an InProgress struct\n    // based on the cache key.\n    class KeyCallbacks {\n     public:\n      inline const kj::String& keyForRow(const kj::Own<InProgress>& entry) const {\n        return entry->key;\n      }\n\n      template <typename KeyLike>\n      inline bool matches(const kj::Own<InProgress>& e, KeyLike&& key) const {\n        return e->key == key;\n      }\n\n      template <typename KeyLike>\n      inline auto hashCode(KeyLike&& key) const {\n        return kj::hashCode(key);\n      }\n    };\n  };\n\n  // Called when initializing globals (i.e., bindings) for an isolate. Each\n  // cache binding holds one SharedMemoryCache::Use, which automatically calls\n  // this function when created. This call will never reduce the effective cache\n  // limits, but might increase them.\n  void suggest(const Limits& limits) const;\n\n  // Called when a cache global and its associated SharedMemoryCache::Use is\n  // destroyed. This call might reduce the effective cache limits. If all uses\n  // have been destroyed, the effective limits will be reset to Limits::min(),\n  // effectively clearing the cache.\n  void unsuggest(const Limits& limits) const;\n\n  // Used internally by suggest() and unsuggest() to dynamically resize the\n  // cache as appropriate. This function also recomputed the effective cache\n  // limits and thus must be called even when the cache size is increased (which\n  // does not change the cache contents).\n  void resize(ThreadUnsafeData& data) const;\n\n  // Returns a cached value while the cache's data is already locked by the\n  // calling thread. If such a cache entry exists, it will be marked as the\n  // most recently used entry.\n  kj::Maybe<kj::Own<CacheValue>> getWhileLocked(\n      ThreadUnsafeData& data, const kj::String& key) const;\n\n  // Stores a value in the cache, with an optional expiration timestamp. It is\n  // marked as the most recently used entry.\n  void putWhileLocked(ThreadUnsafeData& data,\n      const kj::String& key,\n      kj::Own<CacheValue>&& value,\n      kj::Maybe<double> expiration) const;\n\n  // Evicts at least one cache entry. The cache's data must already be locked by\n  // the calling thread, and the cache must not be empty. Expiration timestamps\n  // are only considered if called from within an I/O context or if\n  // allowOutsideIoContext is true.\n  void evictNextWhileLocked(ThreadUnsafeData& data, bool allowOutsideIoContext = false) const;\n\n  // Removes the cache entry with the given key, if it exists.\n  void removeIfExistsWhileLocked(ThreadUnsafeData& data, const kj::String& key) const;\n\n  // Callbacks for a HashIndex that allow locating cache entries based on the\n  // cache key, which is a string. This is used for all key-based cache\n  // operations.\n  class KeyCallbacks {\n   public:\n    inline const kj::String& keyForRow(const MemoryCacheEntry& entry) const {\n      return entry.key;\n    }\n\n    template <typename KeyLike>\n    inline bool matches(const MemoryCacheEntry& e, KeyLike&& key) const {\n      return e.key == key;\n    }\n\n    template <typename KeyLike>\n    inline auto hashCode(KeyLike&& key) const {\n      return kj::hashCode(key);\n    }\n  };\n\n  // Callbacks for a TreeIndex that allow sorting cache entries by their\n  // liveliness. This is used to evict the least recently used entry.\n  class LivelinessCallbacks {\n   public:\n    inline const uint64_t& keyForRow(const MemoryCacheEntry& entry) const {\n      return entry.liveliness;\n    }\n\n    template <typename KeyLike>\n    inline bool matches(const MemoryCacheEntry& e, KeyLike&& key) const {\n      return e.liveliness == key;\n    }\n\n    template <typename KeyLike>\n    inline bool isBefore(const MemoryCacheEntry& e, KeyLike&& key) const {\n      return e.liveliness < key;\n    }\n  };\n\n  // Callbacks for a TreeIndex that allow sorting cache entries by the sizes\n  // of the serialized values. The entries are sorted in reverse order, i.e.,\n  // the first entry contains the largest value. This is used to quickly evict\n  // the largest cache values when the maximum value size is reduced, e.g.,\n  // when a new version of a worker is deployed.\n  class ValueSizeCallbacks {\n   public:\n    inline const MemoryCacheEntry& keyForRow(const MemoryCacheEntry& entry KJ_LIFETIMEBOUND) const {\n      return entry;\n    }\n\n    template <typename KeyLike>\n    inline bool matches(const MemoryCacheEntry& e, KeyLike&& key) const {\n      return e.size() == key.size() && e.key == key.key;\n    }\n\n    template <typename KeyLike>\n    inline bool isBefore(const MemoryCacheEntry& e, KeyLike&& key) const {\n      size_t szl = e.size(), szr = key.size();\n      if (szl != szr) return szl > szr;\n      return e.key < key.key;\n    }\n  };\n\n  // Callbacks for a TreeIndex that allow sorting cache entries by their\n  // expiration times. This is used to quickly evict expired entries even when\n  // they are not least recently used. Values with no expiration timestamp are\n  // at the very end, ordered by their cache keys.\n  class ExpirationCallbacks {\n   public:\n    inline const MemoryCacheEntry& keyForRow(const MemoryCacheEntry& entry KJ_LIFETIMEBOUND) const {\n      return entry;\n    }\n\n    template <typename KeyLike>\n    inline bool matches(const MemoryCacheEntry& e, KeyLike&& key) const {\n      return e.expiration == key.expiration && e.key == key.key;\n    }\n\n    template <typename KeyLike>\n    inline bool isBefore(const MemoryCacheEntry& e, KeyLike&& key) const {\n      const kj::Maybe<double>&expl = e.expiration, expr = key.expiration;\n      if (expl != expr) return isBefore(expl, expr);\n      return e.key < key.key;\n    }\n\n   private:\n    inline bool isBefore(const kj::Maybe<double>& a, const kj::Maybe<double>& b) const {\n      KJ_IF_SOME(da, a) {\n        KJ_IF_SOME(db, b) {\n          return da < db;\n        } else {\n          return false;\n        }\n      } else {\n        KJ_ASSERT(b != kj::none);\n        return true;\n      }\n    }\n  };\n\n public:\n  struct ThreadUnsafeData {\n    KJ_DISALLOW_COPY_AND_MOVE(ThreadUnsafeData);\n\n    ThreadUnsafeData() {}\n\n    // All limits that have been suggested by isolates that are currently using\n    // this cache.\n    std::multiset<Limits> suggestedLimits;\n\n    // The computed effective limits. These are updated whenever new isolates\n    // are attached to this cache.\n    Limits effectiveLimits = Limits::min();\n\n    // Returns the next liveliness and increments it so that the next call to\n    // this function will return a different value.\n    inline uint64_t stepLiveliness() {\n      return nextLiveliness++;\n    }\n\n    // We do not handle integer overflow, but a 64-bit counter should never wrap\n    // around, at least not in the foreseeable future. (Even at a billion cache\n    // operations per second, it would take almost 600 years.)\n    uint64_t nextLiveliness = 0;\n\n    // The sum of the sizes of all values that are currently stored in the cache.\n    // This is technically redundant information, but more efficient than\n    // iterating over all cache entries every time we need this information.\n    size_t totalValueSize = 0;\n\n    // The actual cache contents.\n    kj::Table<MemoryCacheEntry,              // row type\n        kj::HashIndex<KeyCallbacks>,         // index over keys\n        kj::TreeIndex<LivelinessCallbacks>,  // index over liveliness\n        kj::TreeIndex<ValueSizeCallbacks>,   // index over value sizes\n        kj::TreeIndex<ExpirationCallbacks>   // index over expiration\n        >\n        cache;\n\n    // Whenever a fallback is active for a particular key, this table will\n    // contain one corresponding row. Other concurrent read operations can add\n    // themselves to the InProgress struct to be notified once the fallback\n    // completes. When a fallback succeeds, this immediately notifies all\n    // waiting read operations, but when it fails, this behaves like a queue and\n    // and invokes the next available fallback only.\n    kj::Table<kj::Own<InProgress>, kj::HashIndex<InProgress::KeyCallbacks>> inProgress;\n  };\n\n private:\n  // To ensure thread-safety, all mutable data is guarded by a mutex. Each cache\n  // operation requires an exclusive lock. Even read-only operations need to\n  // update the liveliness of cache entries, which currently requires a lock.\n  kj::MutexGuarded<ThreadUnsafeData> data;\n\n  // The MemoryCacheProvider instance needs to be guaranteed to outlive the SharedMemoryCache\n  // instance. When the SharedMemoryCache is destroyed, it will remove itself from the provider.\n  // TODO(cleanup): Eventually, assuming/once the kj::Ptr<T> work progresses, it would be safer\n  // to replace this with a kj::Ptr<MemoryCacheProvider>\n  kj::Maybe<const MemoryCacheProvider&> provider;\n\n  // It's a bit unfortunate that we need to keep a copy of the id here as well as in the map\n  // in the MemoryCacheProvider, however, it's entirely possible (at least theoretically) that\n  // the map entry in the MemoryCacheProvider could be removed before the SharedMemoryCache is\n  // fully destroyed, leaving a dangling reference. This be safe and keep a copy.\n  kj::String id;\n\n  // Same as above, the MemoryCacheProvider owns the actual handler here. Since that is guaranteed\n  // to outlive this SharedMemoryCache instance, so is the handler.\n  kj::Maybe<AdditionalResizeMemoryLimitHandler&> additionalResizeMemoryLimitHandler;\n\n  const kj::MonotonicClock& timer;\n};\n\n// JavaScript class that allows accessing an in-memory cache.\n// Each instance of this class holds a SharedMemoryCache::Use object and\n// all calls from JavaScript are essentially forwarded to that object, which\n// manages interaction with the shared cache in a thread-safe manner.\nclass MemoryCache: public jsg::Object {\n public:\n  MemoryCache(SharedMemoryCache::Use&& use): cacheUse(kj::mv(use)) {}\n\n  using FallbackFunction = jsg::Function<jsg::Promise<CacheValueProduceResult>(kj::String)>;\n\n  // Reads a value from the cache or invokes a fallback function to obtain the\n  // value, if a fallback function was given.\n  jsg::Promise<jsg::JsRef<jsg::JsValue>> read(jsg::Lock& js,\n      jsg::NonCoercible<kj::String> key,\n      jsg::Optional<FallbackFunction> optionalFallback);\n\n  // Delete a value from the cache.\n  void delete_(jsg::Lock& js, jsg::NonCoercible<kj::String> key);\n\n  JSG_RESOURCE_TYPE(MemoryCache, CompatibilityFlags::Reader flags) {\n    JSG_METHOD(read);\n    if (flags.getMemoryCacheDelete()) {\n      JSG_METHOD_NAMED(delete, delete_);\n    }\n  }\n\n private:\n  SharedMemoryCache::Use cacheUse;\n};\n\n// The MemoryCacheProvider provides the internal implementation of the MemoryCache mechanism.\n// It is responsible for owning the SharedMemoryCache instances and providing them to the\n// bindings as needed. The default implementation (created and returned by createDefault())\n// uses a simple in-memory map to store the SharedMemoryCache instances.\n// TODO(later): It may be worth considering some kind of metrics observer for the provider\n// that can be passed along to the individual cache instances so we can monitor just how much\n// the in memory cache is being used.\nclass MemoryCacheProvider {\n public:\n  MemoryCacheProvider(const kj::MonotonicClock& timer,\n      kj::Maybe<SharedMemoryCache::AdditionalResizeMemoryLimitHandler>\n          additionalResizeMemoryLimitHandler = kj::none);\n  KJ_DISALLOW_COPY_AND_MOVE(MemoryCacheProvider);\n  ~MemoryCacheProvider() noexcept(false);\n\n  kj::Own<const SharedMemoryCache> getInstance(kj::Maybe<kj::StringPtr> cacheId = kj::none) const;\n\n  void removeInstance(const SharedMemoryCache& instance) const;\n\n private:\n  kj::Maybe<SharedMemoryCache::AdditionalResizeMemoryLimitHandler>\n      additionalResizeMemoryLimitHandler;\n\n  // All existing in-memory *shared* caches. This table will not include caches created\n  // that do not have an id (and therefore cannot be shared).\n  // TODO(cleanup): Later, assuming progress is made on kj::Ptr<T>, it would be nice\n  // to avoid the use of the bare pointer to SharedMemoryCache* here. When the SharedMemoryCache\n  // is destroyed, it will remove itself from this cache by calling removeInstance.\n  kj::MutexGuarded<kj::HashMap<kj::String, const SharedMemoryCache*>> caches;\n\n  const kj::MonotonicClock& timer;\n};\n\n// clang-format off\n#define EW_MEMORY_CACHE_ISOLATE_TYPES                                                   \\\n  api::MemoryCache,                                                                     \\\n  api::CacheValueProduceResult\n// clang-format on\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/messagechannel.c++",
    "content": "#include \"messagechannel.h\"\n\n#include \"events.h\"\n\n#include <workerd/io/worker.h>\n#include <workerd/jsg/ser.h>\n#include <workerd/util/weak-refs.h>\n\nnamespace workerd::api {\nMessagePort::MessagePort()\n    : weakThis(kj::refcounted<WeakRef<MessagePort>>(kj::Badge<MessagePort>{}, *this)),\n      state(Pending()) {\n  // We set a callback on the underlying EventTarget to be notified when\n  // a listener for the message event is added or removed. When there\n  // are no listeners, we move back to the Pending state, otherwise we\n  // will switch to the Started state if necessary.\n  setEventListenerCallback([&](jsg::Lock& js, kj::StringPtr name, size_t count) {\n    if (name == \"message\"_kj) {\n      KJ_SWITCH_ONEOF(state) {\n        KJ_CASE_ONEOF(pending, Pending) {\n          // If we are in the pending state, start the port if we have listeners.\n          // This is technically not spec compliant, but it is what Node.js\n          // supports. Specifically, adding a new message listener using the\n          // addEventListener method is *technically* not supposed to start\n          // the port but we're going to do what Node.js does.\n          if (count > 0 || onmessageValue != kj::none) {\n            start(js);\n          }\n        }\n        KJ_CASE_ONEOF(started, Started) {\n          // If we are in the started state, stop the port if there are no listeners.\n          if (count == 0 && onmessageValue == kj::none) {\n            state = Pending();\n          }\n        }\n        KJ_CASE_ONEOF(_, Closed) {\n          // Nothing to do. We're already closed so we don't care.\n        }\n      }\n    }\n  });\n}\n\nvoid MessagePort::dispatchMessage(jsg::Lock& js, const jsg::JsValue& value) {\n  JSG_TRY(js) {\n    auto message = js.alloc<MessageEvent>(js, kj::str(\"message\"), value, kj::String(), JSG_THIS);\n    dispatchEventImpl(js, kj::mv(message));\n  }\n  JSG_CATCH(exception) {\n    // There was an error dispatching the message event.\n    // We will dispatch a messageerror event instead.\n    auto message = js.alloc<MessageEvent>(\n        js, kj::str(\"message\"), jsg::JsValue(exception.getHandle(js)), kj::String(), JSG_THIS);\n    dispatchEventImpl(js, kj::mv(message));\n    // Now, if this dispatchEventImpl throws, we just blow up. Don't try to catch it.\n  }\n}\n\n// Deliver the message to this port, buffering if necessary if the port\n// has not been started. Buffered messages will be delivered when the\n// port is started later.\nvoid MessagePort::deliver(jsg::Lock& js, const jsg::JsValue& value) {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(pending, Pending) {\n      // We have not yet started the port so buffer the message.\n      // It will be delivered when the port is started.\n      // We don't know how many messages will be buffered, if any,\n      // so we avoid reserving space in the array.\n      pending.add(jsg::JsRef(js, value));\n    }\n    KJ_CASE_ONEOF(started, Started) {\n      js.resolvedPromise().then(\n          js, [self = JSG_THIS, value = jsg::JsRef(js, value)](jsg::Lock& js) mutable {\n        self->dispatchMessage(js, value.getHandle(js));\n      });\n    }\n    KJ_CASE_ONEOF(_, Closed) {\n      // Nothing to do in this case. Drop the message on the floor.\n    }\n  }\n}\n\n// Binds two ports to each other such that messages posted to one\n// are delivered on the other.\nvoid MessagePort::entangle(MessagePort& port1, MessagePort& port2) {\n  port1.other = port2.addWeakRef();\n  port2.other = port1.addWeakRef();\n}\n\n// Post a message to the entangled port.\nvoid MessagePort::postMessage(jsg::Lock& js,\n    jsg::Optional<jsg::JsRef<jsg::JsValue>> data,\n    jsg::Optional<TransferListOrOptions> options) {\n\n  // We don't currently support transfer lists, even for local\n  // same-isolate delivery.\n  // TODO(conform): Implement transfer later?\n  bool hasTransfer = false;\n  KJ_SWITCH_ONEOF(kj::mv(options).orDefault(PostMessageOptions{})) {\n    KJ_CASE_ONEOF(list, TransferList) {\n      hasTransfer = list.size() > 0;\n    }\n    KJ_CASE_ONEOF(opts, PostMessageOptions) {\n      KJ_IF_SOME(list, opts.transfer) {\n        hasTransfer = list.size() > 0;\n      }\n    }\n  }\n  JSG_REQUIRE(!hasTransfer, Error, \"Transfer list is not supported\");\n\n  // If the port is closed, other will be kj::none and we will just drop the message.\n  other->runIfAlive([&](MessagePort& o) {\n    jsg::Serializer ser(js);\n\n    KJ_IF_SOME(d, data) {\n      ser.write(js, d.getHandle(js));\n    } else {\n      ser.write(js, js.undefined());\n    }\n\n    auto released = ser.release();\n    JSG_REQUIRE(released.sharedArrayBuffers.size() == 0, TypeError,\n        \"SharedArrayBuffer is unsupported with MessagePort\");\n\n    // Now, deserialize the message into a JsValue\n    jsg::Deserializer deserializer(js, released);\n    auto clonedData = deserializer.readValue(js);\n    o.deliver(js, clonedData);\n  });\n}\n\nvoid MessagePort::closeImpl() {\n  // Any pending messages will be dropped on the floor, except for those that were\n  // already scheduled for delivery in the `start()` or `deliver()` methods.\n  if (state.is<Closed>()) return;\n  state = Closed{};\n  weakThis->invalidate();\n  other->runIfAlive([&](MessagePort& o) { o.closeImpl(); });\n}\n\nvoid MessagePort::close(jsg::Lock& js) {\n  if (state.is<Closed>()) return;\n  state = Closed{};\n  weakThis->invalidate();\n  other->runIfAlive([&](MessagePort& o) { o.close(js); });\n  auto closeEvent = js.alloc<Event>(kj::str(\"close\"), Event::Init{}, true);\n  dispatchEventImpl(js, kj::mv(closeEvent));\n}\n\n// Start delivering messages on this port. Any messages that are\n// buffered will be drained immediately.\nvoid MessagePort::start(jsg::Lock& js) {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(pending, Pending) {\n      auto list = kj::mv(pending);\n      state = Started{};\n      // We're going to dispatch the messages using a microtask so that the actual\n      // delivery is deferred to match Node.js' behavior as close as possible.\n      js.resolvedPromise().then(js, [list = kj::mv(list), self = JSG_THIS](jsg::Lock& js) mutable {\n        for (auto& item: list) {\n          self->dispatchMessage(js, item.getHandle(js));\n        }\n      });\n    }\n    KJ_CASE_ONEOF(_, Started) {\n      // Nothing to do in this case. We are already started!\n    }\n    KJ_CASE_ONEOF(_, Closed) {\n      // Nothing to do in this case. Can't start after closing.\n    }\n  }\n}\n\nkj::Maybe<jsg::JsValue> MessagePort::getOnMessage(jsg::Lock& js) {\n  return onmessageValue.map(\n      [&](jsg::JsRef<jsg::JsValue>& ref) -> jsg::JsValue { return ref.getHandle(js); });\n}\n\nvoid MessagePort::setOnMessage(jsg::Lock& js, jsg::JsValue value) {\n  if (!value.isObject() && !value.isFunction()) {\n    onmessageValue = kj::none;\n    // If we have no handlers and no onmessage ...\n    if (getHandlerCount(\"message\"_kj) == 0 && onmessageValue == kj::none) {\n      // ...Put the port back into a pending state where messages\n      // will be enqueued until another listener is attached.\n      state = Pending();\n    }\n  } else {\n    onmessageValue = jsg::JsRef<jsg::JsValue>(js, value);\n    start(js);\n  }\n}\n\njsg::Ref<MessageChannel> MessageChannel::constructor(jsg::Lock& js) {\n  auto port1 = js.alloc<MessagePort>();\n  auto port2 = js.alloc<MessagePort>();\n  MessagePort::entangle(*port1, *port2);\n  return js.alloc<MessageChannel>(kj::mv(port1), kj::mv(port2));\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/messagechannel.h",
    "content": "#pragma once\n\n#include <workerd/api/basics.h>\n#include <workerd/io/io-context.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/modules-new.h>\n#include <workerd/jsg/ser.h>\n#include <workerd/jsg/url.h>\n#include <workerd/util/weak-refs.h>\n\nnamespace workerd::api {\n\n// A closely approximate implementation of the Web platform standard MessagePort.\n// MessagePorts always come in pairs. When a message is posted to\n// one it is delivered to the other, and vice versa. When one port\n// is closed both ports are closed.\n//\n// This intentionally does not implement the full MessagePort spec and we know\n// that it varies from the standard definition in a number of ways:\n//\n// - It does not support transfer lists. We do not implement the transfer\n//   list semantics, but we do validate the transfer list input to an extent.\n// - It does not support serialization/deserialization. It's not possible to\n//   send a MessagePort anywhere currently.\n// - The `messageerror` event is only partially implemented. Currently, if a\n//   message data cannot be serialized/deserialized it will throw an error\n//   synchronously when posted rather than dispatching the `messageerror` event\n//   on the receiving port, this is just easiest to implement for now and makes\n//   the most sense for our current use case since the MessagePort only ever\n//   passes messages around within the same isolate (that is, we're not sending\n//   the serialized data off anywhere, we're just cloning it and dispatching it.)\n// - We intentionally do not implement the \"port message queue\" semantics exactly\n//   as they are described in the spec. When a MessagePort has an onmessage listener,\n//   the message delivery is flowing, when there is no onmessage listener, the\n//   messages are queued up until the port is started. Because we are storing\n//   these as JS values, we don't worry about extra memory accounting for the queue.\n// - We do not emit the close event on entangled ports when one of them is GC'd.\n// - We do not check to see if a MessagePort is entangled with another when we\n//   call entangle because there's only one way to entangle them currently and\n//   it's impossible for them to be already entangled.\n// - We do not implement disentangle steps other than to invalidate the weak\n//   ref to the other port when one of them is closed.\n// - We do not prevent a MessagePort from being garbage collected while it has\n//   messages queued up. Eventually when we implement ser/deser this might change.\n// - Unlike the implementation in Node.js, not closing a MessagePort does not\n//   prevent anything from exiting. It's best to close MessagePorts manually\n//   but the current implementation does not require it.\n//\n// Because of these differences we do not currently run the full suite of web\n// platform tests against our implementation -- we know most of them will fail\n// since most of them depend on the ability to transfer MessagePorts or depend\n// on the mechanisms we do not implement. And yes, we know that this means that\n// if we need stricter compliance with the spec in the future we will likely\n// need to introduce a compat flag.\nclass MessagePort final: public EventTarget {\n public:\n  // While we do not support transfer lists in the implementation\n  // currently, we do want to validate those inputs.\n  using TransferList = kj::Array<jsg::JsRef<jsg::JsValue>>;\n  struct PostMessageOptions {\n    jsg::Optional<TransferList> transfer;\n    JSG_STRUCT(transfer);\n  };\n  using TransferListOrOptions = kj::OneOf<TransferList, PostMessageOptions>;\n\n  MessagePort();\n  ~MessagePort() noexcept(false) {\n    closeImpl();\n  }\n\n  // MessagePort instances cannot be created directly.\n  // Use `new MessageChannel()`\n  static jsg::Ref<MessagePort> constructor() = delete;\n\n  void postMessage(jsg::Lock& js,\n      jsg::Optional<jsg::JsRef<jsg::JsValue>> data = kj::none,\n      jsg::Optional<TransferListOrOptions> options = kj::none);\n  void closeImpl();\n  void close(jsg::Lock& js);\n  void start(jsg::Lock& js);\n\n  // Support the onmessage getter and setter. Per the spec, when\n  // onmessage is set, the MessagePort is automatically started,\n  // but when addEventListener is set, start must be called\n  // separately. That's a kind of a weird rule but ok. To support\n  // that we need to define an onmessage getter/setter pair.\n  kj::Maybe<jsg::JsValue> getOnMessage(jsg::Lock& js);\n  void setOnMessage(jsg::Lock& js, jsg::JsValue value);\n\n  JSG_RESOURCE_TYPE(MessagePort) {\n    JSG_INHERIT(EventTarget);\n    JSG_METHOD(postMessage);\n    JSG_METHOD(close);\n    JSG_METHOD(start);\n    JSG_PROTOTYPE_PROPERTY(onmessage, getOnMessage, setOnMessage);\n  }\n\n  jsg::Ref<MessagePort> addRef() {\n    return JSG_THIS;\n  }\n  bool isClosed() const {\n    return state.is<Closed>();\n  }\n\n  void deliver(jsg::Lock& js, const jsg::JsValue& data);\n\n  // Bind two message ports together such that messages posted to\n  // one are delivered to the other.\n  static void entangle(MessagePort& port1, MessagePort& port2);\n\n  kj::Maybe<MessagePort&> getOther() {\n    return other->tryGet().map([](MessagePort& o) -> MessagePort& { return o; });\n  }\n\n  // TODO(soon): Support serialization/deserialization to use MessagePort\n  // with JSRPC. We'll need to implement a rpc mechanism for passing the\n  // messages across the rpc boundary.\n\n private:\n  // When the MessagePort is in the pending state, messages posted to it\n  // will be buffered until the port is started. When the port is started,\n  // the buffered messages will be delivered immediately.\n  using Pending = kj::Vector<jsg::JsRef<jsg::JsValue>>;\n  struct Started {};\n  struct Closed {};\n\n  void dispatchMessage(jsg::Lock& js, const jsg::JsValue& value);\n\n  kj::Own<WeakRef<MessagePort>> addWeakRef() {\n    KJ_ASSERT(weakThis->isValid());\n    return kj::addRef(*weakThis);\n  }\n\n  kj::Own<WeakRef<MessagePort>> weakThis;\n  kj::OneOf<Pending, Started, Closed> state;\n\n  // Two ports are entangled when they weakly reference each other.\n  // Keep in mind that this is a weak reference! So if one of the\n  // ports gets GC'd the other will will also end up being closed.\n  // To keep them both alive, maintain strong references to both\n  // ports!\n  kj::Own<WeakRef<MessagePort>> other;\n  kj::Maybe<jsg::JsRef<jsg::JsValue>> onmessageValue;\n};\n\n// MessageChannel is simple enough... create a couple of MessagePorts\n// and entangle those so that they will exchange messages with each\n// other.\nclass MessageChannel final: public jsg::Object {\n public:\n  MessageChannel(jsg::Ref<MessagePort> port1, jsg::Ref<MessagePort> port2)\n      : port1(kj::mv(port1)),\n        port2(kj::mv(port2)) {}\n\n  static jsg::Ref<MessageChannel> constructor(jsg::Lock& js);\n\n  jsg::Ref<MessagePort> getPort1() {\n    return port1.addRef();\n  }\n  jsg::Ref<MessagePort> getPort2() {\n    return port2.addRef();\n  }\n\n  JSG_RESOURCE_TYPE(MessageChannel) {\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(port1, getPort1);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(port2, getPort2);\n  }\n\n private:\n  jsg::Ref<MessagePort> port1;\n  jsg::Ref<MessagePort> port2;\n};\n\n// Module that exposes MessageChannel and MessagePort for internal use by\n// built-in modules like node:worker_threads without requiring the global\n// expose_global_message_channel compat flag.\nclass MessageChannelModule final: public jsg::Object {\n public:\n  MessageChannelModule() = default;\n  MessageChannelModule(jsg::Lock&, const jsg::Url&) {}\n\n  JSG_RESOURCE_TYPE(MessageChannelModule) {\n    JSG_NESTED_TYPE(MessageChannel);\n    JSG_NESTED_TYPE(MessagePort);\n  }\n};\n\ntemplate <class Registry>\nvoid registerMessageChannelModule(Registry& registry, auto featureFlags) {\n  registry.template addBuiltinModule<MessageChannelModule>(\n      \"cloudflare-internal:messagechannel\", workerd::jsg::ModuleRegistry::Type::INTERNAL);\n}\n\ntemplate <typename TypeWrapper>\nkj::Own<jsg::modules::ModuleBundle> getInternalMessageChannelModuleBundle(auto featureFlags) {\n  jsg::modules::ModuleBundle::BuiltinBuilder builder(\n      jsg::modules::ModuleBundle::BuiltinBuilder::Type::BUILTIN_ONLY);\n  static const auto kSpecifier = \"cloudflare-internal:messagechannel\"_url;\n  builder.addObject<MessageChannelModule, TypeWrapper>(kSpecifier);\n  return builder.finish();\n}\n\n}  // namespace workerd::api\n\n#define EW_MESSAGECHANNEL_ISOLATE_TYPES                                                            \\\n  api::MessagePort, api::MessageChannel, api::MessagePort::PostMessageOptions,                     \\\n      api::MessageChannelModule\n"
  },
  {
    "path": "src/workerd/api/modules.c++",
    "content": "#include \"modules.h\"\n\n#include <workerd/io/features.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/setup.h>\n\nnamespace workerd::api {\n\nkj::Maybe<jsg::JsObject> EnvModule::getCurrentEnv(jsg::Lock& js) {\n  auto& key = jsg::IsolateBase::from(js.v8Isolate).getEnvAsyncContextKey();\n  // Check async context first - withEnv() overrides take precedence over the disable flag.\n  KJ_IF_SOME(frame, jsg::AsyncContextFrame::current(js)) {\n    KJ_IF_SOME(value, frame.get(key)) {\n      auto handle = value.getHandle(js);\n      if (handle->IsObject()) {\n        return jsg::JsObject(handle.As<v8::Object>());\n      }\n      if (FeatureFlags::get(js).getEnvModuleNullableSupport() && handle->IsNullOrUndefined()) {\n        return kj::none;\n      }\n    }\n  }\n  if (FeatureFlags::get(js).getDisableImportableEnv()) return kj::none;\n\n  return js.getWorkerEnv().map([&](const jsg::V8Ref<v8::Object>& val) -> jsg::JsObject {\n    return jsg::JsObject(val.getHandle(js));\n  });\n}\n\njsg::JsRef<jsg::JsValue> EnvModule::withEnv(\n    jsg::Lock& js, jsg::Value newEnv, jsg::Function<jsg::JsRef<jsg::JsValue>()> fn) {\n  auto& key = jsg::IsolateBase::from(js.v8Isolate).getEnvAsyncContextKey();\n  jsg::AsyncContextFrame::StorageScope storage(js, key, kj::mv(newEnv));\n  return js.tryCatch([&]() mutable -> jsg::JsRef<jsg::JsValue> { return fn(js); },\n      [&](jsg::Value&& exception) mutable -> jsg::JsRef<jsg::JsValue> {\n    js.throwException(kj::mv(exception));\n  });\n}\n\njsg::Ref<PythonPatchedEnv> EnvModule::pythonPatchEnv(jsg::Lock& js, jsg::Value newEnv) {\n  auto& key = jsg::IsolateBase::from(js.v8Isolate).getEnvAsyncContextKey();\n  return jsg::alloc<PythonPatchedEnv>(js, key, kj::mv(newEnv));\n}\n\nkj::Maybe<jsg::JsObject> EnvModule::getCurrentExports(jsg::Lock& js) {\n  auto& key = jsg::IsolateBase::from(js.v8Isolate).getExportsAsyncContextKey();\n  // Check async context first - withExports() overrides take precedence over the disable flags.\n  KJ_IF_SOME(frame, jsg::AsyncContextFrame::current(js)) {\n    KJ_IF_SOME(value, frame.get(key)) {\n      auto handle = value.getHandle(js);\n      if (handle->IsObject()) {\n        return jsg::JsObject(handle.As<v8::Object>());\n      }\n      if (FeatureFlags::get(js).getEnvModuleNullableSupport() && handle->IsNullOrUndefined()) {\n        return kj::none;\n      }\n    }\n  }\n  if (FeatureFlags::get(js).getDisableImportableEnv()) {\n    return kj::none;\n  }\n\n  return js.getWorkerExports().map([&](const jsg::V8Ref<v8::Object>& val) -> jsg::JsObject {\n    return jsg::JsObject(val.getHandle(js));\n  });\n}\n\njsg::JsRef<jsg::JsValue> EnvModule::withExports(\n    jsg::Lock& js, jsg::Value newExports, jsg::Function<jsg::JsRef<jsg::JsValue>()> fn) {\n  auto& key = jsg::IsolateBase::from(js.v8Isolate).getExportsAsyncContextKey();\n  jsg::AsyncContextFrame::StorageScope storage(js, key, kj::mv(newExports));\n  return js.tryCatch([&]() mutable -> jsg::JsRef<jsg::JsValue> { return fn(js); },\n      [&](jsg::Value&& exception) mutable -> jsg::JsRef<jsg::JsValue> {\n    js.throwException(kj::mv(exception));\n  });\n}\n\njsg::JsRef<jsg::JsValue> EnvModule::withEnvAndExports(jsg::Lock& js,\n    jsg::Value newEnv,\n    jsg::Value newExports,\n    jsg::Function<jsg::JsRef<jsg::JsValue>()> fn) {\n  auto& envKey = jsg::IsolateBase::from(js.v8Isolate).getEnvAsyncContextKey();\n  auto& exportsKey = jsg::IsolateBase::from(js.v8Isolate).getExportsAsyncContextKey();\n  jsg::AsyncContextFrame::StorageScope envStorage(js, envKey, kj::mv(newEnv));\n  jsg::AsyncContextFrame::StorageScope exportsStorage(js, exportsKey, kj::mv(newExports));\n  return js.tryCatch([&]() mutable -> jsg::JsRef<jsg::JsValue> { return fn(js); },\n      [&](jsg::Value&& exception) mutable -> jsg::JsRef<jsg::JsValue> {\n    js.throwException(kj::mv(exception));\n  });\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/modules.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/api/base64.h>\n#include <workerd/api/filesystem.h>\n#include <workerd/api/messagechannel.h>\n#include <workerd/api/node/node.h>\n#include <workerd/api/pyodide/pyodide.h>\n#include <workerd/api/rtti.h>\n#include <workerd/api/sockets.h>\n#include <workerd/api/tracing-module.h>\n#include <workerd/api/unsafe.h>\n#include <workerd/api/workers-module.h>\n#include <workerd/jsg/modules-new.h>\n\n#include <cloudflare/cloudflare.capnp.h>\n\nnamespace workerd::api {\n\n// An object with a [Symbol.dispose]() method to remove patch to environment. Not exposed\n// publically, just used to implement Python's `patch_env()` context manager.\n// See src/pyodide/internal/envHelpers.ts\nclass PythonPatchedEnv: public jsg::Object {\n public:\n  PythonPatchedEnv(jsg::Lock& js, jsg::AsyncContextFrame::StorageKey& key, jsg::Value store) {\n    scope.emplace(js, key, kj::mv(store));\n  }\n\n  void dispose() {\n    scope = kj::none;\n  }\n\n  JSG_RESOURCE_TYPE(PythonPatchedEnv) {\n    JSG_DISPOSE(dispose);\n  }\n\n private:\n  kj::Maybe<jsg::AsyncContextFrame::StorageScope> scope;\n};\n\nclass EnvModule final: public jsg::Object {\n public:\n  EnvModule() = default;\n  EnvModule(jsg::Lock&, const jsg::Url&) {}\n\n  kj::Maybe<jsg::JsObject> getCurrentEnv(jsg::Lock& js);\n  kj::Maybe<jsg::JsObject> getCurrentExports(jsg::Lock& js);\n\n  // Arranges to propagate the given newEnv in the async context.\n  jsg::JsRef<jsg::JsValue> withEnv(\n      jsg::Lock& js, jsg::Value newEnv, jsg::Function<jsg::JsRef<jsg::JsValue>()> fn);\n\n  jsg::JsRef<jsg::JsValue> withExports(\n      jsg::Lock& js, jsg::Value newExports, jsg::Function<jsg::JsRef<jsg::JsValue>()> fn);\n\n  jsg::JsRef<jsg::JsValue> withEnvAndExports(jsg::Lock& js,\n      jsg::Value newEnv,\n      jsg::Value newExports,\n      jsg::Function<jsg::JsRef<jsg::JsValue>()> fn);\n\n  // Patch environment and return an object with a [Symbol.dispose]() method to restore it.\n  // Not exposed publically, just used to implement Python's `patch_env()` context manager.\n  // See src/pyodide/internal/envHelpers.ts\n  jsg::Ref<PythonPatchedEnv> pythonPatchEnv(jsg::Lock& js, jsg::Value newEnv);\n\n  JSG_RESOURCE_TYPE(EnvModule) {\n    JSG_METHOD(getCurrentEnv);\n    JSG_METHOD(getCurrentExports);\n    JSG_METHOD(withEnv);\n    JSG_METHOD(withExports);\n    JSG_METHOD(withEnvAndExports);\n    JSG_METHOD(pythonPatchEnv);\n  }\n};\n\ntemplate <class Registry>\nvoid registerModules(Registry& registry, auto featureFlags) {\n  node::registerNodeJsCompatModules(registry, featureFlags);\n  registerUnsafeModules(registry, featureFlags);\n  if (featureFlags.getRttiApi()) {\n    registerRTTIModule(registry);\n  }\n  if (featureFlags.getUnsafeModule()) {\n    registerUnsafeModule(registry);\n  }\n  registerSocketsModule(registry, featureFlags);\n  registerBase64Module(registry, featureFlags);\n  registerMessageChannelModule(registry, featureFlags);\n  registry.addBuiltinBundle(CLOUDFLARE_BUNDLE);\n  registerWorkersModule(registry, featureFlags);\n  registerTracingModule(registry, featureFlags);\n  registry.template addBuiltinModule<EnvModule>(\n      \"cloudflare-internal:env\", workerd::jsg::ModuleRegistry::Type::INTERNAL);\n  registry.template addBuiltinModule<FileSystemModule>(\n      \"cloudflare-internal:filesystem\", workerd::jsg::ModuleRegistry::Type::INTERNAL);\n}\n\ntemplate <class TypeWrapper>\nvoid registerBuiltinModules(jsg::modules::ModuleRegistry::Builder& builder, auto featureFlags) {\n  builder.add(node::getInternalNodeJsCompatModuleBundle<TypeWrapper>(featureFlags));\n  builder.add(node::getExternalNodeJsCompatModuleBundle(featureFlags));\n  builder.add(getInternalSocketModuleBundle<TypeWrapper>(featureFlags));\n  builder.add(getInternalBase64ModuleBundle<TypeWrapper>(featureFlags));\n  builder.add(getInternalMessageChannelModuleBundle<TypeWrapper>(featureFlags));\n  builder.add(getInternalRpcModuleBundle<TypeWrapper>(featureFlags));\n\n  builder.add(getInternalUnsafeModuleBundle<TypeWrapper>(featureFlags));\n  builder.add(getInternalTracingModuleBundle<TypeWrapper>(featureFlags));\n  if (featureFlags.getUnsafeModule()) {\n    builder.add(getExternalUnsafeModuleBundle<TypeWrapper>(featureFlags));\n  }\n\n  if (featureFlags.getRttiApi()) {\n    builder.add(getExternalRttiModuleBundle<TypeWrapper>(featureFlags));\n  }\n\n  {\n    jsg::modules::ModuleBundle::BuiltinBuilder builtinsBuilder(\n        jsg::modules::ModuleBundle::BuiltinBuilder::Type::BUILTIN);\n    jsg::modules::ModuleBundle::getBuiltInBundleFromCapnp(builtinsBuilder, CLOUDFLARE_BUNDLE);\n    builder.add(builtinsBuilder.finish());\n  }\n\n  {\n    jsg::modules::ModuleBundle::BuiltinBuilder builtinsBuilder(\n        jsg::modules::ModuleBundle::BuiltinBuilder::Type::BUILTIN_ONLY);\n    builtinsBuilder.addObject<EnvModule, TypeWrapper>(\"cloudflare-internal:env\"_url);\n    builtinsBuilder.addObject<FileSystemModule, TypeWrapper>(\"cloudflare-internal:filesystem\"_url);\n    jsg::modules::ModuleBundle::getBuiltInBundleFromCapnp(builtinsBuilder, CLOUDFLARE_BUNDLE);\n    builder.add(builtinsBuilder.finish());\n  }\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/node/AGENTS.md",
    "content": "# src/workerd/api/node/\n\nC++ implementations of Node.js built-in modules. Each module = JSG-bound class registered via `NODEJS_MODULES(V)` macro in `node.h`. TypeScript counterpart lives in `src/node/`.\n\n## BUILD TARGETS\n\n- **`node-core`**: buffer, dns, i18n, sqlite, url. **No `//src/workerd/io` dep** — do not add one. Depends on Rust crates (`cxx-integration`, `dns`, `net`), `ada-url`, `nbytes`, `simdutf`.\n- **`node`**: async-hooks, crypto, diagnostics-channel, module, process, timers, util, zlib-util. Depends on `io`, `ncrypto`, `zstd`, `kj-brotli`. Depends on `node-core`.\n- **`exceptions`**: Standalone Node.js exception types; dep of `node-core`.\n\n## MODULE REGISTRATION\n\n1. Add `JSG_RESOURCE_TYPE` class in `<module>.h` + `<module>.c++`\n2. Define `EW_NODE_<MODULE>_ISOLATE_TYPES` macro in header\n3. Add `V(ClassName, \"node-internal:<name>\")` to `NODEJS_MODULES(V)` in `node.h` (or `NODEJS_MODULES_EXPERIMENTAL(V)` for staging)\n4. Append to `EW_NODE_ISOLATE_TYPES` at bottom of `node.h`\n5. Per-module compat flags gated in `registerNodeJsCompatModules()` — add `isNode*Module()` + `featureFlags.getEnable*()` check\n6. DnsUtil special-cased: C++ fallback when `RUST_BACKED_NODE_DNS` autogate disabled\n\n## TESTING\n\nTests in `tests/`. Naming: `<module>-test.js` + `<module>-test.wd-test`; `-nodejs-` infix when needing compat flags. All tests set `compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\"]`. Network tests (net, tls, http) use sidecar `js_binary` targets. `fixtures/` has 46 PEM files for crypto. `process-stdio` tests use `sh_test` with `.expected_stdout`/`.expected_stderr`. C++ unit test: `buffer-test.c++` via `kj_test`.\n"
  },
  {
    "path": "src/workerd/api/node/BUILD.bazel",
    "content": "load(\"//:build/kj_test.bzl\", \"kj_test\")\nload(\"//:build/wd_cc_library.bzl\", \"wd_cc_library\")\n\nwd_cc_library(\n    name = \"exceptions\",\n    srcs = [\n        \"exceptions.c++\",\n    ],\n    hdrs = [\n        \"exceptions.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/jsg\",\n    ],\n)\n\nwd_cc_library(\n    name = \"node\",\n    srcs = [\n        \"async-hooks.c++\",\n        \"crypto.c++\",\n        \"crypto-keys.c++\",\n        \"diagnostics-channel.c++\",\n        \"module.c++\",\n        \"process.c++\",\n        \"timers.c++\",\n        \"util.c++\",\n        \"zlib-util.c++\",\n    ],\n    hdrs = [\n        \"async-hooks.h\",\n        \"crypto.h\",\n        \"diagnostics-channel.h\",\n        \"module.h\",\n        \"node.h\",\n        \"process.h\",\n        \"timers.h\",\n        \"util.h\",\n        \"zlib-util.h\",\n    ],\n    implementation_deps = [\n        \"@capnp-cpp//src/kj/compat:kj-gzip\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":node-core\",\n        \"//src/node\",\n        \"//src/rust/api\",\n        \"//src/workerd/api:streams-compression\",\n        \"//src/workerd/io\",\n        \"//src/workerd/util:mimetype\",\n        \"@capnp-cpp//src/kj/compat:kj-brotli\",\n        \"@ncrypto\",\n        \"@zstd\",\n    ],\n)\n\n# node source files that don't depend on io.\nwd_cc_library(\n    name = \"node-core\",\n    srcs = [\n        \"buffer.c++\",\n        \"i18n.c++\",\n        \"sqlite.c++\",\n        \"url.c++\",\n    ],\n    hdrs = [\n        \"buffer.h\",\n        \"buffer-string-search.h\",\n        \"i18n.h\",\n        \"node-version.h\",\n        \"sqlite.h\",\n        \"url.h\",\n    ],\n    implementation_deps = [\n        \"//src/rust/cxx-integration\",\n        \"//src/rust/net\",\n        \"@ada-url\",\n        \"@nbytes\",\n        \"@simdutf\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        # Please do not add //src/workerd/io here – this target is intended for code that does not\n        # depend on it, move files to the main node target if needed. That way, the files here don't\n        # need to be rebuilt if there are changes to io.\n        \"//src/workerd/io:compatibility-date_capnp\",\n        \"//src/workerd/jsg\",\n        \":exceptions\",\n    ],\n)\n\nkj_test(\n    src = \"buffer-test.c++\",\n    deps = [\"//src/workerd/tests:test-fixture\"],\n)\n"
  },
  {
    "path": "src/workerd/api/node/async-hooks.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#include \"async-hooks.h\"\n\n#include <workerd/io/features.h>\n#include <workerd/io/io-context.h>\n\nnamespace workerd::api::node {\n\nnamespace {\n// If there is a current IoContext, then it is possible/likely that the\n// current AsyncContextFrame is storing values that are bound to that\n// IoContext. In that case, we want to protect against the case where\n// the returned snapshot function is called from a different IoContext.\n// To do this we will capture a weak reference to the current IoContext\n// and check it against the current IoContext where the snapshot function\n// is invoked.\njsg::Function<void()> getValidator(jsg::Lock& js) {\n  kj::Maybe<kj::Own<IoContext::WeakRef>> maybeIoContext;\n  if (FeatureFlags::get(js).getBindAsyncLocalStorageSnapshot() && IoContext::hasCurrent()) {\n    // We use a weak reference to the IoContext because the current IoContext\n    // may be destroyed before the snapshot function is called.\n    maybeIoContext = IoContext::current().getWeakRef();\n  }\n\n  static constexpr auto kErrorMessage =\n      \"Cannot call this AsyncLocalStorage bound function outside of the \"\n      \"request in which it was created.\"_kj;\n\n  return [maybeIoContext = kj::mv(maybeIoContext)](jsg::Lock&) {\n    KJ_IF_SOME(originIoContext, maybeIoContext) {\n      // We had an IoContext when we created the snapshot function.\n      // If it is not the current IoContext, or if there is no current\n      // IoContext, or if the captured IoContext has been destroyed,\n      // we throw an error.\n      JSG_REQUIRE(IoContext::hasCurrent() && originIoContext->isValid(), Error, kErrorMessage);\n      originIoContext->runIfAlive([&](IoContext& otherContext) {\n        JSG_REQUIRE(&otherContext == &IoContext::current(), Error, kErrorMessage);\n      });\n    }\n  };\n}\n\n}  // namespace\n\njsg::Ref<AsyncLocalStorage> AsyncLocalStorage::constructor(\n    jsg::Lock& js, jsg::Optional<AsyncLocalStorage::AsyncLocalStorageOptions> options) {\n  return js.alloc<AsyncLocalStorage>(kj::mv(options));\n}\n\nv8::Local<v8::Value> AsyncLocalStorage::run(jsg::Lock& js,\n    v8::Local<v8::Value> store,\n    jsg::Function<v8::Local<v8::Value>(jsg::Arguments<jsg::Value>)> callback,\n    jsg::Arguments<jsg::Value> args) {\n  callback.setReceiver(js.v8Ref<v8::Value>(js.v8Context()->Global()));\n  jsg::AsyncContextFrame::StorageScope scope(js, *key, js.v8Ref(store));\n  return callback(js, kj::mv(args));\n}\n\nv8::Local<v8::Value> AsyncLocalStorage::exit(jsg::Lock& js,\n    jsg::Function<v8::Local<v8::Value>(jsg::Arguments<jsg::Value>)> callback,\n    jsg::Arguments<jsg::Value> args) {\n  // Node.js defines exit as running \"a function synchronously outside of a context\".\n  // It goes on to say that the store is not accessible within the callback or the\n  // asynchronous operations created within the callback. Any getStore() call done\n  // within the callback function will always return undefined... except if run() is\n  // called which implicitly enables the context again within that scope.\n  //\n  // We do not have to emulate Node.js enable/disable behavior since we are not\n  // implementing the enterWith/disable methods. We can emulate the correct\n  // behavior simply by calling run with the store value set to undefined, which\n  // will propagate correctly.\n  return run(js, js.undefined(), kj::mv(callback), kj::mv(args));\n}\n\nv8::Local<v8::Value> AsyncLocalStorage::getStore(jsg::Lock& js) {\n  KJ_IF_SOME(context, jsg::AsyncContextFrame::current(js)) {\n    KJ_IF_SOME(value, context.get(*key)) {\n      return value.getHandle(js);\n    }\n  }\n  KJ_IF_SOME(value, defaultValue) {\n    return value.getHandle(js);\n  }\n  return js.undefined();\n}\n\nkj::StringPtr AsyncLocalStorage::getName() {\n  KJ_IF_SOME(n, name) {\n    return n.asPtr();\n  }\n  return nullptr;\n}\n\nv8::Local<v8::Function> AsyncLocalStorage::bind(jsg::Lock& js, v8::Local<v8::Function> fn) {\n  KJ_IF_SOME(frame, jsg::AsyncContextFrame::current(js)) {\n    return frame.wrap(js, fn, getValidator(js));\n  } else {\n    return jsg::AsyncContextFrame::wrapRoot(js, fn);\n  }\n}\n\nv8::Local<v8::Function> AsyncLocalStorage::snapshot(jsg::Lock& js) {\n  return jsg::AsyncContextFrame::wrapSnapshot(js, getValidator(js));\n}\n\nnamespace {\nkj::Maybe<jsg::Ref<jsg::AsyncContextFrame>> tryGetFrameRef(jsg::Lock& js) {\n  return jsg::AsyncContextFrame::current(js).map(\n      [](jsg::AsyncContextFrame& frame) { return frame.addRef(); });\n}\n}  // namespace\n\nAsyncResource::AsyncResource(jsg::Lock& js): frame(tryGetFrameRef(js)) {}\n\njsg::Ref<AsyncResource> AsyncResource::constructor(\n    jsg::Lock& js, jsg::Optional<kj::String> type, jsg::Optional<Options> options) {\n  // The type and options are required as part of the Node.js API compatibility\n  // but our implementation does not currently make use of them at all. It is OK\n  // for us to silently ignore both here.\n  return js.alloc<AsyncResource>(js);\n}\n\nv8::Local<v8::Function> AsyncResource::staticBind(jsg::Lock& js,\n    v8::Local<v8::Function> fn,\n    jsg::Optional<kj::String> type,\n    jsg::Optional<v8::Local<v8::Value>> thisArg,\n    const jsg::TypeHandler<jsg::Ref<AsyncResource>>& handler) {\n  return AsyncResource::constructor(js, kj::mv(type).orDefault([] {\n    return kj::str(\"AsyncResource\");\n  }))->bind(js, fn, thisArg, handler);\n}\n\nkj::Maybe<jsg::AsyncContextFrame&> AsyncResource::getFrame() {\n  return frame.map([](jsg::Ref<jsg::AsyncContextFrame>& frame) -> jsg::AsyncContextFrame& {\n    return *(frame.get());\n  });\n}\n\nv8::Local<v8::Function> AsyncResource::bind(jsg::Lock& js,\n    v8::Local<v8::Function> fn,\n    jsg::Optional<v8::Local<v8::Value>> thisArg,\n    const jsg::TypeHandler<jsg::Ref<AsyncResource>>& handler) {\n  v8::Local<v8::Function> bound;\n  KJ_IF_SOME(frame, getFrame()) {\n    bound = frame.wrap(js, fn, getValidator(js), thisArg);\n  } else {\n    bound = jsg::AsyncContextFrame::wrapRoot(js, fn, thisArg);\n  }\n\n  // Per Node.js documentation (https://nodejs.org/dist/latest-v19.x/docs/api/async_context.html#asyncresourcebindfn-thisarg), the returned function \"will have an\n  // asyncResource property referencing the AsyncResource to which the function\n  // is bound\".\n  js.v8Set(bound, \"asyncResource\"_kj, handler.wrap(js, JSG_THIS));\n  return bound;\n}\n\nv8::Local<v8::Value> AsyncResource::runInAsyncScope(jsg::Lock& js,\n    jsg::Function<v8::Local<v8::Value>(jsg::Arguments<jsg::Value>)> fn,\n    jsg::Optional<v8::Local<v8::Value>> thisArg,\n    jsg::Arguments<jsg::Value> args) {\n  v8::Local<v8::Value> receiver = js.v8Context()->Global();\n  KJ_IF_SOME(arg, thisArg) {\n    receiver = arg;\n  }\n  fn.setReceiver(js.v8Ref<v8::Value>(receiver));\n  jsg::AsyncContextFrame::Scope scope(js, getFrame());\n  return fn(js, kj::mv(args));\n}\n\nkj::Own<jsg::AsyncContextFrame::StorageKey> AsyncLocalStorage::getKey() {\n  return kj::addRef(*key);\n}\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/async-hooks.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <workerd/jsg/async-context.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api::node {\n\n// Implements a subset of the Node.js AsyncLocalStorage API.\n//\n// Example:\n//\n//   import * as async_hooks from 'node:async_hooks';\n//   const als = new async_hooks.AsyncLocalStorage();\n//\n//   async function doSomethingAsync() {\n//     await scheduler.wait(100);\n//     console.log(als.getStore()); // 1\n//   }\n//\n//   als.run(1, async () => {\n//     console.log(als.getStore());  // 1\n//     await doSomethingAsync();\n//     console.log(als.getStore());  // 1\n//   });\n//   console.log(als.getStore());  // undefined\nclass AsyncLocalStorage final: public jsg::Object {\n public:\n  struct AsyncLocalStorageOptions {\n    jsg::Optional<jsg::JsRef<jsg::JsValue>> defaultValue;\n    jsg::Optional<kj::String> name;\n    JSG_STRUCT(defaultValue, name)\n  };\n\n  AsyncLocalStorage(jsg::Optional<AsyncLocalStorageOptions> options = kj::none)\n      : key(kj::refcounted<jsg::AsyncContextFrame::StorageKey>()) {\n    KJ_IF_SOME(opt, options) {\n      defaultValue = kj::mv(opt.defaultValue);\n      name = kj::mv(opt.name);\n    }\n  }\n\n  ~AsyncLocalStorage() noexcept(false) {\n    key->reset();\n  }\n\n  static jsg::Ref<AsyncLocalStorage> constructor(\n      jsg::Lock& js, jsg::Optional<AsyncLocalStorageOptions> options);\n\n  v8::Local<v8::Value> run(jsg::Lock& js,\n      v8::Local<v8::Value> store,\n      jsg::Function<v8::Local<v8::Value>(jsg::Arguments<jsg::Value>)> callback,\n      jsg::Arguments<jsg::Value> args);\n\n  v8::Local<v8::Value> exit(jsg::Lock& js,\n      jsg::Function<v8::Local<v8::Value>(jsg::Arguments<jsg::Value>)> callback,\n      jsg::Arguments<jsg::Value> args);\n\n  v8::Local<v8::Value> getStore(jsg::Lock& js);\n\n  // Binds the given function to the current async context frame such that\n  // whenever the function is called, the bound frame is entered.\n  static v8::Local<v8::Function> bind(jsg::Lock& js, v8::Local<v8::Function> fn);\n\n  // Returns a function bound to the current async context frame that calls\n  // the function passed to it as the only argument within that frame.\n  // Equivalent to AsyncLocalStorage.bind((cb, ...args) => cb(...args)).\n  static v8::Local<v8::Function> snapshot(jsg::Lock& js);\n\n  inline void enterWith(jsg::Lock&, v8::Local<v8::Value>) {\n    JSG_FAIL_REQUIRE(Error, \"asyncLocalStorage.enterWith() is not implemented\");\n  }\n\n  inline void disable(jsg::Lock&) {\n    JSG_FAIL_REQUIRE(Error, \"asyncLocalStorage.disable() is not implemented\");\n  }\n\n  kj::StringPtr getName();\n\n  JSG_RESOURCE_TYPE(AsyncLocalStorage) {\n    JSG_METHOD(run);\n    JSG_METHOD(exit);\n    JSG_METHOD(getStore);\n    JSG_METHOD(enterWith);\n    JSG_METHOD(disable);\n    JSG_STATIC_METHOD(bind);\n    JSG_STATIC_METHOD(snapshot);\n    JSG_READONLY_PROTOTYPE_PROPERTY(name, getName);\n\n    JSG_TS_OVERRIDE(AsyncLocalStorage<T> {\n      constructor(options?: AsyncLocalStorageAsyncLocalStorageOptions);\n      readonly name: string;\n      getStore(): T | undefined;\n      run<R, TArgs extends any[]>(store: T, callback: (...args: TArgs) => R, ...args: TArgs): R;\n      exit<R, TArgs extends any[]>(callback: (...args: TArgs) => R, ...args: TArgs): R;\n      enterWith(store: T): never;\n      disable(): never;\n      static bind<Func extends (...args: any[]) => any>(fn: Func): Func;\n      static snapshot<R, TArgs extends any[]>(): (fn: (...args: TArgs) => R, ...args: TArgs) => R;\n    });\n  }\n\n  kj::Own<jsg::AsyncContextFrame::StorageKey> getKey();\n\n private:\n  kj::Own<jsg::AsyncContextFrame::StorageKey> key;\n  kj::Maybe<jsg::JsRef<jsg::JsValue>> defaultValue;\n  kj::Maybe<kj::String> name;\n};\n\n// Note: The AsyncResource class is provided for Node.js backwards compatibility.\n// The class can be replaced entirely for async context tracking using the\n// AsyncLocalStorage.bind() and AsyncLocalStorage.snapshot() APIs.\n//\n// The AsyncResource class is an object that user code can use to define its own\n// async resources for the purpose of storage context propagation. For instance,\n// let's imagine that we have an EventTarget and we want to register two event listeners\n// on it that will share the same AsyncLocalStorage context. We can use AsyncResource\n// to easily define the context and bind multiple event handler functions to it:\n//\n//   const als = new AsyncLocalStorage();\n//   const context = als.run(123, () => new AsyncResource('foo'));\n//   const target = new EventTarget();\n//   target.addEventListener('abc', context.bind(() => console.log(als.getStore())));\n//   target.addEventListener('xyz', context.bind(() => console.log(als.getStore())));\n//   target.addEventListener('bar', () => console.log(als.getStore()));\n//\n// When the 'abc' and 'xyz' events are emitted, their event handlers will print 123\n// to the console. When the 'bar' event is emitted, undefined will be printed.\n//\n// Alternatively, we can use EventTarget's object event handler:\n//\n//   const als = new AsyncLocalStorage();\n//\n//   class MyHandler extends AsyncResource {\n//     constructor() { super('foo'); }\n//     void handleEvent() {\n//       this.runInAsyncScope(() => console.log(als.getStore()));\n//     }\n//   }\n//\n//   const handler = als.run(123, () => new MyHandler());\n//   const target = new EventTarget();\n//   target.addEventListener('abc', handler);\n//   target.addEventListener('xyz', handler);\nclass AsyncResource final: public jsg::Object {\n public:\n  struct Options {\n    // Node.js' API allows user code to create AsyncResource instances within an\n    // explicitly specified parent execution context (what we call an \"Async Context\n    // Frame\") that is specified by a numeric ID. We do not track our context frames\n    // by ID and always create new AsyncResource instances within the current Async\n    // Context Frame. To prevent subtle bugs, we'll throw explicitly if user code\n    // tries to set the triggerAsyncId option.\n    //\n    // Node.js also has an additional `requireManualDestroy` boolean option\n    // that we do not implement. We can simply omit it here. There's no risk of\n    // bugs or unexpected behavior by doing so.\n    jsg::WontImplement triggerAsyncId;\n\n    JSG_STRUCT(triggerAsyncId);\n  };\n\n  AsyncResource(jsg::Lock& js);\n\n  // While Node.js' API expects the first argument passed to the `new AsyncResource(...)`\n  // constructor to be a string specifying the resource type, we do not actually use it\n  // for anything. We'll just ignore the value and not store it, but we at least need to\n  // accept the argument and validate that it is a string.\n  static jsg::Ref<AsyncResource> constructor(\n      jsg::Lock& js, jsg::Optional<kj::String> type, jsg::Optional<Options> options = kj::none);\n\n  static v8::Local<v8::Function> staticBind(jsg::Lock& js,\n      v8::Local<v8::Function> fn,\n      jsg::Optional<kj::String> type,\n      jsg::Optional<v8::Local<v8::Value>> thisArg,\n      const jsg::TypeHandler<jsg::Ref<AsyncResource>>& handler);\n\n  // Binds the given function to this async context.\n  v8::Local<v8::Function> bind(jsg::Lock& js,\n      v8::Local<v8::Function> fn,\n      jsg::Optional<v8::Local<v8::Value>> thisArg,\n      const jsg::TypeHandler<jsg::Ref<AsyncResource>>& handler);\n\n  // Calls the given function within this async context.\n  v8::Local<v8::Value> runInAsyncScope(jsg::Lock& js,\n      jsg::Function<v8::Local<v8::Value>(jsg::Arguments<jsg::Value>)> fn,\n      jsg::Optional<v8::Local<v8::Value>> thisArg,\n      jsg::Arguments<jsg::Value>);\n\n  // The Node.js API uses numeric identifiers for all async resources. We do not\n  // implement that part of their API. Always returns 0.\n  inline int asyncId() {\n    return 0;\n  }\n\n  // The Node.js API uses numeric identifiers for all async resources. We do not\n  // implement that part of their API. Always returns 0.\n  inline int triggerAsyncId() {\n    return 0;\n  }\n\n  // No-op. We do not track resource lifetimes. This is provided only for API compatibility.\n  void emitDestroy(jsg::Lock&) {};\n\n  JSG_RESOURCE_TYPE(AsyncResource) {\n    JSG_STATIC_METHOD_NAMED(bind, staticBind);\n    JSG_METHOD(asyncId);\n    JSG_METHOD(triggerAsyncId);\n    JSG_METHOD(bind);\n    JSG_METHOD(runInAsyncScope);\n    JSG_METHOD(emitDestroy);\n\n    JSG_TS_OVERRIDE(AsyncResource {\n      constructor(type: string, options?: AsyncResourceOptions);\n      static bind<Func extends (this: ThisArg, ...args: any[]) => any, ThisArg>(fn: Func, type?: string, thisArg?: ThisArg): Func;\n      bind<Func extends (...args: any[]) => any>(fn: Func): Func;\n      runInAsyncScope<This, Result>(fn: (this: This, ...args: any[]) => Result, thisArg?: This, ...args: any[]): Result;\n      asyncId(): number;\n      triggerAsyncId(): number;\n      emitDestroy(): void;\n    });\n  }\n\n  // Returns the jsg::AsyncContextFrame captured when the AsyncResource was created, if any.\n  kj::Maybe<jsg::AsyncContextFrame&> getFrame();\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"frame\", frame);\n  }\n\n private:\n  kj::Maybe<jsg::Ref<jsg::AsyncContextFrame>> frame;\n\n  inline void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(frame);\n  }\n};\n\n// We have no intention of fully-implementing the Node.js async_hooks module.\n// We provide this because AsyncLocalStorage is exposed via async_hooks in\n// Node.js.\nclass AsyncHooksModule final: public jsg::Object {\n public:\n  AsyncHooksModule() = default;\n  AsyncHooksModule(jsg::Lock&, const jsg::Url&) {}\n\n  JSG_RESOURCE_TYPE(AsyncHooksModule) {\n    JSG_NESTED_TYPE(AsyncLocalStorage);\n    JSG_NESTED_TYPE(AsyncResource);\n  }\n};\n\n#define EW_NODE_ASYNCHOOKS_ISOLATE_TYPES                                                           \\\n  api::node::AsyncHooksModule, api::node::AsyncResource, api::node::AsyncResource::Options,        \\\n      api::node::AsyncLocalStorage, api::node::AsyncLocalStorage::AsyncLocalStorageOptions\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/buffer-string-search.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n// Copyright 2011 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n#pragma once\n\n#include <kj/common.h>\n\n#include <cstdint>\n\nusing kj::uint;\n\nnamespace workerd::api::node {\nnamespace stringsearch {\n\ntemplate <typename T>\nclass Vector {\n public:\n  Vector(T* data, size_t length, bool isForward)\n      : start_(data),\n        length_(length),\n        is_forward_(isForward) {}\n\n  // Returns the start of the memory range.\n  // For vector v this is NOT necessarily &v[0], see forward().\n  const T* start() const {\n    return start_;\n  }\n\n  // Returns the length of the vector, in characters.\n  size_t length() const {\n    return length_;\n  }\n\n  // Returns true if the Vector is front-to-back, false if back-to-front.\n  // In the latter case, v[0] corresponds to the *end* of the memory range.\n  bool forward() const {\n    return is_forward_;\n  }\n\n  // Access individual vector elements - checks bounds in debug mode.\n  T& operator[](size_t index) const {\n    return start_[is_forward_ ? index : (length_ - index - 1)];\n  }\n\n private:\n  T* start_;\n  size_t length_;\n  bool is_forward_;\n};\n\n//---------------------------------------------------------------------\n// String Search object.\n//---------------------------------------------------------------------\n\n// Class holding constants and methods that apply to all string search variants,\n// independently of subject and pattern char size.\nclass StringSearchBase {\n protected:\n  // Cap on the maximal shift in the Boyer-Moore implementation. By setting a\n  // limit, we can fix the size of tables. For a needle longer than this limit,\n  // search will not be optimal, since we only build tables for a suffix\n  // of the string, but it is a safe approximation.\n  static const int kBMMaxShift = 250;\n\n  // Reduce alphabet to this size.\n  // One of the tables used by Boyer-Moore and Boyer-Moore-Horspool has size\n  // proportional to the input alphabet. We reduce the alphabet size by\n  // equating input characters modulo a smaller alphabet size. This gives\n  // a potentially less efficient searching, but is a safe approximation.\n  // For needles using only characters in the same Unicode 256-code point page,\n  // there is no search speed degradation.\n  static const int kLatin1AlphabetSize = 256;\n  static const int kUC16AlphabetSize = 256;\n\n  // Bad-char shift table stored in the state. It's length is the alphabet size.\n  // For patterns below this length, the skip length of Boyer-Moore is too short\n  // to compensate for the algorithmic overhead compared to simple brute force.\n  static const int kBMMinPatternLength = 8;\n\n  // Store for the BoyerMoore(Horspool) bad char shift table.\n  int bad_char_shift_table_[kUC16AlphabetSize];\n  // Store for the BoyerMoore good suffix shift table.\n  int good_suffix_shift_table_[kBMMaxShift + 1];\n  // Table used temporarily while building the BoyerMoore good suffix\n  // shift table.\n  int suffix_table_[kBMMaxShift + 1];\n};\n\ntemplate <typename Char>\nclass StringSearch: private StringSearchBase {\n public:\n  using Vector = stringsearch::Vector<const Char>;\n\n  explicit StringSearch(Vector pattern): pattern_(pattern), start_(0) {\n    if (pattern.length() >= kBMMaxShift) {\n      start_ = pattern.length() - kBMMaxShift;\n    }\n\n    size_t pattern_length = pattern_.length();\n    if (pattern_length < kBMMinPatternLength) {\n      if (pattern_length == 1) {\n        strategy_ = SearchStrategy::kSingleChar;\n        return;\n      }\n      strategy_ = SearchStrategy::kLinear;\n      return;\n    }\n    strategy_ = SearchStrategy::kInitial;\n  }\n\n  size_t Search(Vector subject, size_t index) {\n    switch (strategy_) {\n      case kBoyerMooreHorspool:\n        return BoyerMooreHorspoolSearch(subject, index);\n      case kBoyerMoore:\n        return BoyerMooreSearch(subject, index);\n      case kInitial:\n        return InitialSearch(subject, index);\n      case kLinear:\n        return LinearSearch(subject, index);\n      case kSingleChar:\n        return SingleCharSearch(subject, index);\n    }\n    __builtin_unreachable();\n  }\n\n  static inline int AlphabetSize() {\n    if (sizeof(Char) == 1) {\n      // Latin1 needle.\n      return kLatin1AlphabetSize;\n    } else {\n      // UC16 needle.\n      return kUC16AlphabetSize;\n    }\n\n    static_assert(sizeof(Char) == sizeof(uint8_t) || sizeof(Char) == sizeof(uint16_t),\n        \"sizeof(Char) == sizeof(uint16_t) || sizeof(uint8_t)\");\n  }\n\n private:\n  using SearchFunction = size_t (StringSearch::*)(Vector, size_t);\n  size_t SingleCharSearch(Vector subject, size_t start_index);\n  size_t LinearSearch(Vector subject, size_t start_index);\n  size_t InitialSearch(Vector subject, size_t start_index);\n  size_t BoyerMooreHorspoolSearch(Vector subject, size_t start_index);\n  size_t BoyerMooreSearch(Vector subject, size_t start_index);\n\n  void PopulateBoyerMooreHorspoolTable();\n\n  void PopulateBoyerMooreTable();\n\n  static inline int CharOccurrence(int* bad_char_occurrence, Char char_code) {\n    if (sizeof(Char) == 1) {\n      return bad_char_occurrence[static_cast<uint>(char_code)];\n    }\n    // Both pattern and subject are UC16. Reduce character to equivalence class.\n    uint equiv_class = char_code % kUC16AlphabetSize;\n    return bad_char_occurrence[equiv_class];\n  }\n\n  enum SearchStrategy {\n    kBoyerMooreHorspool,\n    kBoyerMoore,\n    kInitial,\n    kLinear,\n    kSingleChar,\n  };\n\n  // The pattern to search for.\n  Vector pattern_;\n  SearchStrategy strategy_;\n  // Cache value of Max(0, pattern_length() - kBMMaxShift)\n  size_t start_;\n};\n\ntemplate <typename T, typename U>\ninline T AlignDown(T value, U alignment) {\n  return reinterpret_cast<T>((reinterpret_cast<uintptr_t>(value) & ~(alignment - 1)));\n}\n\ninline uint8_t GetHighestValueByte(uint16_t character) {\n  return kj::max(static_cast<uint8_t>(character & 0xFF), static_cast<uint8_t>(character >> 8));\n}\n\ninline uint8_t GetHighestValueByte(uint8_t character) {\n  return character;\n}\n\n// Searches for a byte value in a memory buffer, back to front.\n// Uses memrchr(3) on systems which support it, for speed.\n// Falls back to a vanilla for loop on non-GNU systems such as Windows.\ninline const void* MemrchrFill(const void* haystack, uint8_t needle, size_t haystack_len) {\n#ifdef _GNU_SOURCE\n  return memrchr(haystack, needle, haystack_len);\n#else\n  const uint8_t* haystack8 = static_cast<const uint8_t*>(haystack);\n  for (size_t i = haystack_len - 1; i != static_cast<size_t>(-1); i--) {\n    if (haystack8[i] == needle) {\n      return haystack8 + i;\n    }\n  }\n  return nullptr;\n#endif\n}\n\n// Finds the first occurrence of *two-byte* character pattern[0] in the string\n// `subject`. Does not check that the whole pattern matches.\ntemplate <typename Char>\ninline size_t FindFirstCharacter(\n    Vector<const Char> pattern, Vector<const Char> subject, size_t index) {\n  const Char pattern_first_char = pattern[0];\n  const size_t max_n = (subject.length() - pattern.length() + 1);\n\n  // For speed, search for the more `rare` of the two bytes in pattern[0]\n  // using memchr / memrchr (which are much faster than a simple for loop).\n  const uint8_t search_byte = GetHighestValueByte(pattern_first_char);\n  size_t pos = index;\n  do {\n    const size_t bytes_to_search = (max_n - pos) * sizeof(Char);\n    const void* void_pos;\n    if (subject.forward()) {\n      // Assert that bytes_to_search won't overflow\n      void_pos = memchr(subject.start() + pos, search_byte, bytes_to_search);\n    } else {\n      void_pos = MemrchrFill(subject.start() + pattern.length() - 1, search_byte, bytes_to_search);\n    }\n    const Char* char_pos = static_cast<const Char*>(void_pos);\n    if (char_pos == nullptr) return subject.length();\n\n    // Then, for each match, verify that the full two bytes match pattern[0].\n    char_pos = AlignDown(char_pos, sizeof(Char));\n    size_t raw_pos = static_cast<size_t>(char_pos - subject.start());\n    pos = subject.forward() ? raw_pos : (subject.length() - raw_pos - 1);\n    if (subject[pos] == pattern_first_char) {\n      // Match found, hooray.\n      return pos;\n    }\n    // Search byte matched, but the other byte of pattern[0] didn't. Keep going.\n  } while (++pos < max_n);\n\n  return subject.length();\n}\n\n// Finds the first occurrence of the byte pattern[0] in string `subject`.\n// Does not verify that the whole pattern matches.\ntemplate <>\ninline size_t FindFirstCharacter(\n    Vector<const uint8_t> pattern, Vector<const uint8_t> subject, size_t index) {\n  const uint8_t pattern_first_char = pattern[0];\n  const size_t subj_len = subject.length();\n  const size_t max_n = (subject.length() - pattern.length() + 1);\n\n  const void* pos;\n  if (subject.forward()) {\n    pos = memchr(subject.start() + index, pattern_first_char, max_n - index);\n  } else {\n    pos = MemrchrFill(subject.start() + pattern.length() - 1, pattern_first_char, max_n - index);\n  }\n  const uint8_t* char_pos = static_cast<const uint8_t*>(pos);\n  if (char_pos == nullptr) {\n    return subj_len;\n  }\n\n  size_t raw_pos = static_cast<size_t>(char_pos - subject.start());\n  return subject.forward() ? raw_pos : (subj_len - raw_pos - 1);\n}\n\n//---------------------------------------------------------------------\n// Single Character Pattern Search Strategy\n//---------------------------------------------------------------------\n\ntemplate <typename Char>\nsize_t StringSearch<Char>::SingleCharSearch(Vector subject, size_t index) {\n  return FindFirstCharacter(pattern_, subject, index);\n}\n\n//---------------------------------------------------------------------\n// Linear Search Strategy\n//---------------------------------------------------------------------\n\n// Simple linear search for short patterns. Never bails out.\ntemplate <typename Char>\nsize_t StringSearch<Char>::LinearSearch(Vector subject, size_t index) {\n  const size_t n = subject.length() - pattern_.length();\n  for (size_t i = index; i <= n; i++) {\n    i = FindFirstCharacter(pattern_, subject, i);\n    if (i == subject.length()) return subject.length();\n\n    bool matches = true;\n    for (size_t j = 1; j < pattern_.length(); j++) {\n      if (pattern_[j] != subject[i + j]) {\n        matches = false;\n        break;\n      }\n    }\n    if (matches) {\n      return i;\n    }\n  }\n  return subject.length();\n}\n\n//---------------------------------------------------------------------\n// Boyer-Moore string search\n//---------------------------------------------------------------------\n\ntemplate <typename Char>\nsize_t StringSearch<Char>::BoyerMooreSearch(Vector subject, size_t start_index) {\n  const size_t subject_length = subject.length();\n  const size_t pattern_length = pattern_.length();\n  // Only preprocess at most kBMMaxShift last characters of pattern.\n  size_t start = start_;\n\n  int* bad_char_occurrence = bad_char_shift_table_;\n  // Explicitly cast good_suffix_shift_table_ to int* here to avoid a benign UBSan warning.\n  // good_suffix_shift may point outside of the array, but only indices within the array will\n  // actually be accessed based on the checks below.\n  int* good_suffix_shift = static_cast<int*>(good_suffix_shift_table_) - start_;\n\n  Char last_char = pattern_[pattern_length - 1];\n  size_t index = start_index;\n  // Continue search from i.\n  while (index <= subject_length - pattern_length) {\n    size_t j = pattern_length - 1;\n    int c;\n    while (last_char != (c = subject[index + j])) {\n      int shift = j - CharOccurrence(bad_char_occurrence, c);\n      index += shift;\n      if (index > subject_length - pattern_length) {\n        return subject.length();\n      }\n    }\n    while (pattern_[j] == (c = subject[index + j])) {\n      if (j == 0) {\n        return index;\n      }\n      j--;\n    }\n    if (j < start) {\n      // we have matched more than our tables allow us to be smart about.\n      // Fall back on BMH shift.\n      index += pattern_length - 1 - CharOccurrence(bad_char_occurrence, last_char);\n    } else {\n      int gs_shift = good_suffix_shift[j + 1];\n      int bc_occ = CharOccurrence(bad_char_occurrence, c);\n      int shift = j - bc_occ;\n      if (gs_shift > shift) {\n        shift = gs_shift;\n      }\n      index += shift;\n    }\n  }\n\n  return subject.length();\n}\n\ntemplate <typename Char>\nvoid StringSearch<Char>::PopulateBoyerMooreTable() {\n  const size_t pattern_length = pattern_.length();\n  // Only look at the last kBMMaxShift characters of pattern (from start_\n  // to pattern_length).\n  const size_t start = start_;\n  const size_t length = pattern_length - start;\n\n  // Biased tables so that we can use pattern indices as table indices,\n  // even if we only cover the part of the pattern from offset start.\n  // Use explicit int* casts to avoid benign OOB UBsan warnings.\n  int* shift_table = static_cast<int*>(good_suffix_shift_table_) - start_;\n  int* suffix_table = static_cast<int*>(suffix_table_) - start_;\n\n  // Initialize table.\n  for (size_t i = start; i < pattern_length; i++) {\n    shift_table[i] = length;\n  }\n  shift_table[pattern_length] = 1;\n  suffix_table[pattern_length] = pattern_length + 1;\n\n  if (pattern_length <= start) {\n    return;\n  }\n\n  // Find suffixes.\n  Char last_char = pattern_[pattern_length - 1];\n  size_t suffix = pattern_length + 1;\n  {\n    size_t i = pattern_length;\n    while (i > start) {\n      Char c = pattern_[i - 1];\n      while (suffix <= pattern_length && c != pattern_[suffix - 1]) {\n        if (static_cast<size_t>(shift_table[suffix]) == length) {\n          shift_table[suffix] = suffix - i;\n        }\n        suffix = suffix_table[suffix];\n      }\n      suffix_table[--i] = --suffix;\n      if (suffix == pattern_length) {\n        // No suffix to extend, so we check against last_char only.\n        while ((i > start) && (pattern_[i - 1] != last_char)) {\n          if (static_cast<size_t>(shift_table[pattern_length]) == length) {\n            shift_table[pattern_length] = pattern_length - i;\n          }\n          suffix_table[--i] = pattern_length;\n        }\n        if (i > start) {\n          suffix_table[--i] = --suffix;\n        }\n      }\n    }\n  }\n  // Build shift table using suffixes.\n  if (suffix < pattern_length) {\n    for (size_t i = start; i <= pattern_length; i++) {\n      if (static_cast<size_t>(shift_table[i]) == length) {\n        shift_table[i] = suffix - start;\n      }\n      if (i == suffix) {\n        suffix = suffix_table[suffix];\n      }\n    }\n  }\n}\n\n//---------------------------------------------------------------------\n// Boyer-Moore-Horspool string search.\n//---------------------------------------------------------------------\n\ntemplate <typename Char>\nsize_t StringSearch<Char>::BoyerMooreHorspoolSearch(Vector subject, size_t start_index) {\n  const size_t subject_length = subject.length();\n  const size_t pattern_length = pattern_.length();\n  int* char_occurrences = bad_char_shift_table_;\n  int64_t badness = -static_cast<int64_t>(pattern_length);\n\n  // How bad we are doing without a good-suffix table.\n  Char last_char = pattern_[pattern_length - 1];\n  int last_char_shift = pattern_length - 1 - CharOccurrence(char_occurrences, last_char);\n\n  // Perform search\n  size_t index = start_index;  // No matches found prior to this index.\n  while (index <= subject_length - pattern_length) {\n    size_t j = pattern_length - 1;\n    int subject_char;\n    while (last_char != (subject_char = subject[index + j])) {\n      int bc_occ = CharOccurrence(char_occurrences, subject_char);\n      int shift = j - bc_occ;\n      index += shift;\n      badness += 1 - shift;  // at most zero, so badness cannot increase.\n      if (index > subject_length - pattern_length) {\n        return subject_length;\n      }\n    }\n    j--;\n    while (pattern_[j] == (subject[index + j])) {\n      if (j == 0) {\n        return index;\n      }\n      j--;\n    }\n    index += last_char_shift;\n    // Badness increases by the number of characters we have\n    // checked, and decreases by the number of characters we\n    // can skip by shifting. It's a measure of how we are doing\n    // compared to reading each character exactly once.\n    badness += (pattern_length - j) - last_char_shift;\n    if (badness > 0) {\n      PopulateBoyerMooreTable();\n      strategy_ = SearchStrategy::kBoyerMoore;\n      return BoyerMooreSearch(subject, index);\n    }\n  }\n  return subject.length();\n}\n\ntemplate <typename Char>\nvoid StringSearch<Char>::PopulateBoyerMooreHorspoolTable() {\n  const size_t pattern_length = pattern_.length();\n\n  int* bad_char_occurrence = bad_char_shift_table_;\n\n  // Only preprocess at most kBMMaxShift last characters of pattern.\n  const size_t start = start_;\n  // Run forwards to populate bad_char_table, so that *last* instance\n  // of character equivalence class is the one registered.\n  // Notice: Doesn't include the last character.\n  const size_t table_size = AlphabetSize();\n  if (start == 0) {\n    // All patterns less than kBMMaxShift in length.\n    kj::arrayPtr(bad_char_occurrence, table_size).fill(-1);\n  } else {\n    for (size_t i = 0; i < table_size; i++) {\n      bad_char_occurrence[i] = start - 1;\n    }\n  }\n  for (size_t i = start; i < pattern_length - 1; i++) {\n    Char c = pattern_[i];\n    uint bucket = (sizeof(Char) == 1) ? c : c % AlphabetSize();\n    bad_char_occurrence[bucket] = i;\n  }\n}\n\n//---------------------------------------------------------------------\n// Linear string search with bailout to BMH.\n//---------------------------------------------------------------------\n\n// Simple linear search for short patterns, which bails out if the string\n// isn't found very early in the subject. Upgrades to BoyerMooreHorspool.\ntemplate <typename Char>\nsize_t StringSearch<Char>::InitialSearch(Vector subject, size_t index) {\n  const size_t pattern_length = pattern_.length();\n  // Badness is a count of how much work we have done.  When we have\n  // done enough work we decide it's probably worth switching to a better\n  // algorithm.\n  int64_t badness = -10 - (pattern_length << 2);\n\n  // We know our pattern is at least 2 characters, we cache the first so\n  // the common case of the first character not matching is faster.\n  for (size_t i = index, n = subject.length() - pattern_length; i <= n; i++) {\n    badness++;\n    if (badness <= 0) {\n      i = FindFirstCharacter(pattern_, subject, i);\n      if (i == subject.length()) return subject.length();\n      size_t j = 1;\n      do {\n        if (pattern_[j] != subject[i + j]) {\n          break;\n        }\n        j++;\n      } while (j < pattern_length);\n      if (j == pattern_length) {\n        return i;\n      }\n      badness += j;\n    } else {\n      PopulateBoyerMooreHorspoolTable();\n      strategy_ = SearchStrategy::kBoyerMooreHorspool;\n      return BoyerMooreHorspoolSearch(subject, i);\n    }\n  }\n  return subject.length();\n}\n\n// Perform a single stand-alone search.\n// If searching multiple times for the same pattern, a search\n// object should be constructed once and the Search function then called\n// for each search.\ntemplate <typename Char>\nsize_t SearchString(Vector<const Char> subject, Vector<const Char> pattern, size_t start_index) {\n  StringSearch<Char> search(pattern);\n  return search.Search(subject, start_index);\n}\n}  // namespace stringsearch\n}  // namespace workerd::api::node\n\nnamespace workerd::api::node {\n\ntemplate <typename Char>\nsize_t SearchString(const Char* haystack,\n    size_t haystack_length,\n    const Char* needle,\n    size_t needle_length,\n    size_t start_index,\n    bool is_forward) {\n  if (haystack_length < needle_length) return haystack_length;\n  // To do a reverse search (lastIndexOf instead of indexOf) without redundant\n  // code, create two vectors that are reversed views into the input strings.\n  // For example, v_needle[0] would return the *last* character of the needle.\n  // So we're searching for the first instance of rev(needle) in rev(haystack)\n  stringsearch::Vector<const Char> v_needle(needle, needle_length, is_forward);\n  stringsearch::Vector<const Char> v_haystack(haystack, haystack_length, is_forward);\n  size_t diff = haystack_length - needle_length;\n  size_t relative_start_index;\n  if (is_forward) {\n    relative_start_index = start_index;\n  } else if (diff < start_index) {\n    relative_start_index = 0;\n  } else {\n    relative_start_index = diff - start_index;\n  }\n  size_t pos = node::stringsearch::SearchString(v_haystack, v_needle, relative_start_index);\n  if (pos == haystack_length) {\n    // not found\n    return pos;\n  }\n  return is_forward ? pos : (haystack_length - needle_length - pos);\n}\n\ntemplate <size_t N>\nsize_t SearchString(const char* haystack, size_t haystack_length, const char (&needle)[N]) {\n  return SearchString(reinterpret_cast<const uint8_t*>(haystack), haystack_length,\n      reinterpret_cast<const uint8_t*>(needle), N - 1, 0, true);\n}\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/buffer-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/tests/test-fixture.h>\n\n#include <kj/test.h>\n\nnamespace workerd::api {\nnamespace {\n\nKJ_TEST(\"node:buffer import without capability\") {\n  KJ_EXPECT_LOG(ERROR, \"script startup threw exception\");\n\n  try {\n    TestFixture fixture({.mainModuleSource = R\"SCRIPT(\n        import { Buffer } from 'node:buffer';\n\n        export default {\n          fetch(request) {\n            return new Response(new Buffer(\"test\").toString());\n          },\n        };\n      )SCRIPT\"_kj});\n\n    KJ_UNREACHABLE;\n  } catch (kj::Exception& e) {\n    KJ_EXPECT(e.getDescription() == \"script startup threw exception\"_kj);\n  }\n}\n\nKJ_TEST(\"Verify maximum Buffer size\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setNodeJsCompat(true);\n  flags.setWorkerdExperimental(true);\n\n  TestFixture fixture({.featureFlags = flags.asReader(), .mainModuleSource = R\"SCRIPT(\n      import { Buffer, kMaxLength } from 'node:buffer';\n\n      try {\n        Buffer.alloc(kMaxLength + 1);\n        throw new Error('alloc should have failed');\n      } catch (err) {\n        if (!err.message.startsWith(\"The value of \\\"size\\\" is out of range\"))\n          throw err;\n      }\n\n      export default {\n        fetch(request) {\n          return new Response(\"test\");\n        },\n      };\n    )SCRIPT\"_kj});\n\n  auto response = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"\"_kj);\n\n  KJ_EXPECT(response.statusCode == 200);\n  KJ_EXPECT(response.body == \"test\");\n}\n\nKJ_TEST(\"Create 0-length buffers\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setNodeJsCompat(true);\n  flags.setWorkerdExperimental(true);\n\n  TestFixture fixture({.featureFlags = flags.asReader(), .mainModuleSource = R\"SCRIPT(\n      import { Buffer } from 'node:buffer';\n      export default {\n        fetch(request) {\n          Buffer.from('');\n          Buffer.from('', 'ascii');\n          Buffer.from('', 'latin1');\n          Buffer.alloc(0);\n          Buffer.allocUnsafe(0);\n          new Buffer('');\n          new Buffer('', 'ascii');\n          new Buffer('', 'latin1');\n          new Buffer('', 'binary');\n          Buffer(0);\n          return new Response(\"test\");\n        },\n      };\n    )SCRIPT\"_kj});\n\n  auto response = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"\"_kj);\n\n  KJ_EXPECT(response.statusCode == 200);\n  KJ_EXPECT(response.body == \"test\");\n}\n\nKJ_TEST(\"new Buffer(string)\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setNodeJsCompat(true);\n  flags.setWorkerdExperimental(true);\n\n  TestFixture fixture({.featureFlags = flags.asReader(), .mainModuleSource = R\"SCRIPT(\n      import { Buffer } from 'node:buffer';\n      export default {\n        fetch(request) {\n          const b = new Buffer(\"test\");\n          return new Response(b.toString(\"utf8\"));\n        },\n      };\n    )SCRIPT\"_kj});\n\n  auto response = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"\"_kj);\n\n  KJ_EXPECT(response.statusCode == 200);\n  KJ_EXPECT(response.body == \"test\");\n}\n\nKJ_TEST(\"Buffer.allocUnsafe(), Buffer.alloc(), Buffer.allocUnsafeSlow()\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setNodeJsCompat(true);\n  flags.setWorkerdExperimental(true);\n\n  TestFixture fixture({.featureFlags = flags.asReader(), .mainModuleSource = R\"SCRIPT(\n      import { Buffer } from 'node:buffer';\n\n      export default {\n        fetch(request) {\n          [\n            'alloc',\n            'allocUnsafe',\n            'allocUnsafeSlow'\n          ].forEach((alloc) => {\n            const b = Buffer[alloc](1024);\n            if (b.length !== 1024) {\n              throw new Error(`Incorrect buffer length [${b.length}]`);\n            }\n\n            // In Node.js' implementation, the Buffer is sliced off a larger pool.\n            // We don't do that, so the underlying ArrayBuffer length and offsets\n            // should be what we expect.\n            if (b.length !== b.buffer.byteLength) {\n              throw new Error('b.buffer.byteLength does not match');\n            }\n            if (b.byteOffset !== 0) {\n              throw new Error(`Incorrect b.byteOffset [${b.byteOffset}]`);\n            }\n\n            // In Node.js' implementation of allocUnsafe, and allocUnsafeSlow(),\n            // the Buffer is filled with uninitialized memory. We don't do that,\n            // so everything should be zeroes.\n            for (const i of b) {\n              if (i !== 0) {\n                throw new Error(`Index should be zeroed out [${i}]`);\n              }\n            }\n\n            b[0] = -1;\n            if (b[0] !== 255) {\n              throw new Error(`Incorrect index value [${b[0]}]`);\n            }\n          });\n\n          return new Response(\"test\");\n        },\n      };\n    )SCRIPT\"_kj});\n\n  auto response = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"\"_kj);\n\n  KJ_EXPECT(response.statusCode == 200);\n  KJ_EXPECT(response.body == \"test\");\n}\n\nKJ_TEST(\"Buffer.from(string)\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setNodeJsCompat(true);\n  flags.setWorkerdExperimental(true);\n\n  TestFixture fixture({.featureFlags = flags.asReader(), .mainModuleSource = R\"SCRIPT(\n      import { Buffer } from 'node:buffer';\n\n      export default {\n        fetch(request) {\n          return new Response(Buffer.from(\"test\").toString());\n        },\n      };\n    )SCRIPT\"_kj});\n\n  auto response = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"\"_kj);\n\n  KJ_EXPECT(response.statusCode == 200);\n  KJ_EXPECT(response.body == \"test\");\n}\n\nKJ_TEST(\"Buffer.from(string, 'utf8')\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setNodeJsCompat(true);\n  flags.setWorkerdExperimental(true);\n\n  TestFixture fixture({.featureFlags = flags.asReader(), .mainModuleSource = R\"SCRIPT(\n      import { Buffer } from 'node:buffer';\n\n      export default {\n        fetch(request) {\n          return new Response(Buffer.from(\"test\", 'utf8').toString('utf8'));\n        },\n      };\n    )SCRIPT\"_kj});\n\n  auto response = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"\"_kj);\n\n  KJ_EXPECT(response.statusCode == 200);\n  KJ_EXPECT(response.body == \"test\");\n}\n\nKJ_TEST(\"Buffer.from(string, 'ucs2')\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setNodeJsCompat(true);\n  flags.setWorkerdExperimental(true);\n\n  TestFixture fixture({.featureFlags = flags.asReader(), .mainModuleSource = R\"SCRIPT(\n      import { Buffer } from 'node:buffer';\n\n      export default {\n        fetch(request) {\n          return new Response(Buffer.from(\"test\", 'ucs2').toString('ucs2'));\n        },\n      };\n    )SCRIPT\"_kj});\n\n  auto response = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"\"_kj);\n\n  KJ_EXPECT(response.statusCode == 200);\n  KJ_EXPECT(response.body == \"test\");\n}\n\nKJ_TEST(\"Buffer.from(string, 'hex')\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setNodeJsCompat(true);\n  flags.setWorkerdExperimental(true);\n\n  TestFixture fixture({.featureFlags = flags.asReader(), .mainModuleSource = R\"SCRIPT(\n      import { Buffer } from 'node:buffer';\n\n      export default {\n        fetch(request) {\n          // Only the valid hex in the input will be decoded. Anything after\n          // the first invalid hex pair will be ignored.\n          const buf = Buffer.from(\"74657374 invalid from here\", 'hex');\n          if (buf.length !== 4) {\n            throw new Error(`invalid buffer length [${buf.length}]`);\n          }\n          return new Response(Buffer.from(\"74657374 invalid from here\", 'hex').toString());\n        },\n      };\n    )SCRIPT\"_kj});\n\n  auto response = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"\"_kj);\n\n  KJ_EXPECT(response.statusCode == 200);\n  KJ_EXPECT(response.body == \"test\");\n}\n\nKJ_TEST(\"Buffer.from(string, 'base64')\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setNodeJsCompat(true);\n  flags.setWorkerdExperimental(true);\n\n  TestFixture fixture({.featureFlags = flags.asReader(), .mainModuleSource = R\"SCRIPT(\n      import { Buffer } from 'node:buffer';\n\n      export default {\n        fetch(request) {\n          // Invalid characters within the encoding are ignored...\n          return new Response(Buffer.from(\"dGV^^^^^^zdA==\", 'base64').toString());\n        },\n      };\n    )SCRIPT\"_kj});\n\n  auto response = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"\"_kj);\n\n  KJ_EXPECT(response.statusCode == 200);\n  KJ_EXPECT(response.body == \"test\");\n}\n\nKJ_TEST(\"new Buffer(string, 'base64')\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setNodeJsCompat(true);\n  flags.setWorkerdExperimental(true);\n\n  TestFixture fixture({.featureFlags = flags.asReader(), .mainModuleSource = R\"SCRIPT(\n      import { Buffer } from 'node:buffer';\n\n      export default {\n        fetch(request) {\n          return new Response(new Buffer(\"dGVzdA==\", 'base64').toString());\n        },\n      };\n    )SCRIPT\"_kj});\n\n  auto response = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"\"_kj);\n\n  KJ_EXPECT(response.statusCode == 200);\n  KJ_EXPECT(response.body == \"test\");\n}\n\nKJ_TEST(\"Buffer.from(string, 'base64url')\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setNodeJsCompat(true);\n  flags.setWorkerdExperimental(true);\n\n  TestFixture fixture({.featureFlags = flags.asReader(), .mainModuleSource = R\"SCRIPT(\n      import { Buffer } from 'node:buffer';\n\n      export default {\n        fetch(request) {\n          return new Response(Buffer.from(\"dGVzdA\", 'base64').toString());\n        },\n      };\n    )SCRIPT\"_kj});\n\n  auto response = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"\"_kj);\n\n  KJ_EXPECT(response.statusCode == 200);\n  KJ_EXPECT(response.body == \"test\");\n}\n\nKJ_TEST(\"Buffer.from(Uint8Array)\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setNodeJsCompat(true);\n  flags.setWorkerdExperimental(true);\n\n  TestFixture fixture({.featureFlags = flags.asReader(), .mainModuleSource = R\"SCRIPT(\n      import { Buffer } from 'node:buffer';\n\n      export default {\n        fetch(request) {\n          const u8 = new Uint8Array([74, 65, 73, 74]);\n          const buffer = Buffer.from(u8);\n          if (buffer.length !== 4) {\n            throw new Error(`Unexpected buffer length [${buffer.length}]`);\n          }\n          if (buffer[0] !== 74) {\n            throw new Error(`Unexpected buffer value [${buffer[0]}]`);\n          }\n          u8.fill(0);\n          return new Response(buffer);\n        },\n      };\n    )SCRIPT\"_kj});\n\n  auto response = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"\"_kj);\n\n  KJ_EXPECT(response.statusCode == 200);\n  KJ_EXPECT(response.body == \"JAIJ\");\n}\n\nKJ_TEST(\"new Buffer(Uint8Array)\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setNodeJsCompat(true);\n  flags.setWorkerdExperimental(true);\n\n  TestFixture fixture({.featureFlags = flags.asReader(), .mainModuleSource = R\"SCRIPT(\n      import { Buffer } from 'node:buffer';\n\n      export default {\n        fetch(request) {\n          const u8 = new Uint8Array([74, 65, 73, 74]);\n          const buffer = new Buffer(u8);\n          if (buffer.length !== 4) {\n            throw new Error(`Unexpected buffer length [${buffer.length}]`);\n          }\n          if (buffer[0] !== 74) {\n            throw new Error(`Unexpected buffer value [${buffer[0]}]`);\n          }\n          u8.fill(0);\n          return new Response(buffer);\n        },\n      };\n    )SCRIPT\"_kj});\n\n  auto response = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"\"_kj);\n\n  KJ_EXPECT(response.statusCode == 200);\n  KJ_EXPECT(response.body == \"JAIJ\");\n}\n\nKJ_TEST(\"Buffer.from(Uint32Array)\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setNodeJsCompat(true);\n  flags.setWorkerdExperimental(true);\n\n  TestFixture fixture({.featureFlags = flags.asReader(), .mainModuleSource = R\"SCRIPT(\n      import { Buffer } from 'node:buffer';\n\n      export default {\n        fetch(request) {\n          const u32 = new Uint32Array([1953719668]);\n          const buffer = Buffer.from(u32);\n          if (buffer.length !== 1) {\n            throw new Error(`Unexpected buffer length [${buffer.length}]`);\n          }\n          if (buffer[0] !== 116) {\n            throw new Error(`Unexpected buffer value [${buffer[0]}]`);\n          }\n          u32.fill(0);\n          return new Response(buffer);\n        },\n      };\n    )SCRIPT\"_kj});\n\n  auto response = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"\"_kj);\n\n  KJ_EXPECT(response.statusCode == 200);\n  KJ_EXPECT(response.body == \"t\");\n}\n\nKJ_TEST(\"Buffer.from(ArrayBuffer)\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setNodeJsCompat(true);\n  flags.setWorkerdExperimental(true);\n\n  TestFixture fixture({.featureFlags = flags.asReader(), .mainModuleSource = R\"SCRIPT(\n      import { Buffer } from 'node:buffer';\n\n      export default {\n        fetch(request) {\n          const u32 = new Uint32Array([1953719668]);\n          const buffer = Buffer.from(u32.buffer);\n          if (buffer.length !== 4) {\n            throw new Error(`Unexpected buffer length [${buffer.length}]`);\n          }\n          if (buffer[0] !== 116) {\n            throw new Error(`Unexpected buffer value [${buffer[0]}]`);\n          }\n          return new Response(buffer);\n        },\n      };\n    )SCRIPT\"_kj});\n\n  auto response = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"\"_kj);\n\n  KJ_EXPECT(response.statusCode == 200);\n  KJ_EXPECT(response.body == \"test\");\n}\n\nKJ_TEST(\"new Buffer(ArrayBuffer)\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setNodeJsCompat(true);\n  flags.setWorkerdExperimental(true);\n\n  TestFixture fixture({.featureFlags = flags.asReader(), .mainModuleSource = R\"SCRIPT(\n      import { Buffer } from 'node:buffer';\n\n      export default {\n        fetch(request) {\n          const u32 = new Uint32Array([1953719668]);\n          const buffer = new Buffer(u32.buffer);\n          if (buffer.length !== 4) {\n            throw new Error(`Unexpected buffer length [${buffer.length}]`);\n          }\n          if (buffer[0] !== 116) {\n            throw new Error(`Unexpected buffer value [${buffer[0]}]`);\n          }\n          return new Response(buffer);\n        },\n      };\n    )SCRIPT\"_kj});\n\n  auto response = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"\"_kj);\n\n  KJ_EXPECT(response.statusCode == 200);\n  KJ_EXPECT(response.body == \"test\");\n}\n\nKJ_TEST(\"Buffer.prototype.indexOf/lastIndexOf\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setNodeJsCompat(true);\n  flags.setWorkerdExperimental(true);\n\n  TestFixture fixture({.featureFlags = flags.asReader(), .mainModuleSource = R\"SCRIPT(\n      import { Buffer } from 'node:buffer';\n\n      export default {\n        fetch(request) {\n\n          const b = Buffer.from('helloabcabcthere');\n\n          if (b.indexOf('abc') !== 5) {\n            throw new Error('Incorrect index');\n          }\n\n          if (b.indexOf('abc', -8) !== 8) {\n            throw new Error('Incorrect index');\n          }\n\n          if (b.indexOf('abc', 6) !== 8) {\n            throw new Error('Incorrect index');\n          }\n\n          if (b.lastIndexOf('abc') !== 8) {\n            throw new Error('Incorrect last index');\n          }\n\n          if (b.indexOf(Buffer.from('abc')) !== 5) {\n            throw new Error('Incorrect index');\n          }\n\n          return new Response('test');\n        },\n      };\n    )SCRIPT\"_kj});\n\n  auto response = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"\"_kj);\n\n  KJ_EXPECT(response.statusCode == 200);\n  KJ_EXPECT(response.body == \"test\");\n}\n\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/node/buffer.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\n#include \"buffer.h\"\n\n#include \"buffer-string-search.h\"\n#include \"nbytes.h\"\n#include \"simdutf.h\"\n\n#include <workerd/jsg/jsg.h>\n\n#include <kj/array.h>\n#include <kj/encoding.h>\n\n#include <algorithm>\n\nnamespace workerd::api::node {\n\nnamespace {\n\nkj::Maybe<uint> tryFromHexDigit(char c) {\n  if ('0' <= c && c <= '9') {\n    return c - '0';\n  } else if ('a' <= c && c <= 'f') {\n    return c - ('a' - 10);\n  } else if ('A' <= c && c <= 'F') {\n    return c - ('A' - 10);\n  }\n  return kj::none;\n}\n\njsg::JsUint8Array decodeHexTruncated(\n    jsg::Lock& js, kj::ArrayPtr<kj::byte> text, bool strict = false) {\n  // We do not use kj::decodeHex because we need to match Node.js'\n  // behavior of truncating the response at the first invalid hex\n  // pair as opposed to just marking that an error happened and\n  // trying to continue with the decode.\n  if (text.size() % 2 != 0) {\n    JSG_REQUIRE(!strict, TypeError, \"The text is not valid hex\");\n    text = text.first(text.size() - 1);\n  }\n  auto vec = jsg::JsUint8Array::create(js, text.size() / 2);\n  auto ptr = vec.asArrayPtr();\n  size_t len = 0;\n\n  for (size_t i = 0; i < text.size(); i += 2) {\n    kj::byte b = 0;\n    KJ_IF_SOME(d1, tryFromHexDigit(text[i])) {\n      b = d1 << 4;\n    } else {\n      JSG_REQUIRE(!strict, TypeError, \"The text is not valid hex\");\n      break;\n    }\n    KJ_IF_SOME(d2, tryFromHexDigit(text[i + 1])) {\n      b |= d2;\n    } else {\n      JSG_REQUIRE(!strict, TypeError, \"The text is not valid hex\");\n      break;\n    }\n    ptr[len++] = b;\n  }\n\n  if (len == vec.size()) {\n    return vec;\n  }\n\n  return vec.slice(js, len);\n}\n\nuint32_t writeInto(jsg::Lock& js,\n    kj::ArrayPtr<kj::byte> buffer,\n    jsg::JsString string,\n    uint32_t offset,\n    uint32_t length,\n    Encoding encoding) {\n  KJ_ASSERT(offset <= buffer.size());\n  KJ_ASSERT(length <= buffer.size() - offset);\n  auto dest = buffer.slice(offset, kj::min(offset + length, buffer.size()));\n  if (dest.size() == 0 || string.length(js) == 0) {\n    return 0;\n  }\n\n  static constexpr jsg::JsString::WriteFlags flags =\n      jsg::JsString::WriteFlags::REPLACE_INVALID_UTF8;\n\n  switch (encoding) {\n    case Encoding::ASCII:\n      // Fall-through\n    case Encoding::LATIN1: {\n      auto result = string.writeInto(js, dest, flags);\n      return result.written;\n    }\n    case Encoding::UTF8: {\n      auto result = string.writeInto(js, dest.asChars(), flags);\n      return result.written;\n    }\n    case Encoding::UTF16LE: {\n#if __has_feature(undefined_behavior_sanitizer)\n      // UBSan warns about unaligned writes, but this can be hard to avoid if dest is unaligned.\n      // Use temp variable to perform aligned write instead.\n      kj::Array<uint16_t> tmpBuf = kj::heapArray<uint16_t>(dest.size() / sizeof(uint16_t));\n      auto result = string.writeInto(js, tmpBuf, flags);\n      kj::ArrayPtr<uint16_t> buf(reinterpret_cast<uint16_t*>(dest.begin()), result.written);\n      buf.copyFrom(tmpBuf.first(result.written));\n#else\n      kj::ArrayPtr<uint16_t> buf(\n          reinterpret_cast<uint16_t*>(dest.begin()), dest.size() / sizeof(uint16_t));\n      auto result = string.writeInto(js, buf, flags);\n#endif\n      return result.written * sizeof(uint16_t);\n    }\n    case Encoding::BASE64:\n      // Fall-through\n    case Encoding::BASE64URL: {\n      auto str = string.toString(js);\n      return nbytes::Base64Decode(dest.asChars().begin(), dest.size(), str.begin(), str.size());\n    }\n    case Encoding::HEX: {\n      KJ_STACK_ARRAY(kj::byte, buf, string.length(js), 1024, 536870888);\n      string.writeInto(js, buf, flags);\n      auto backing = decodeHexTruncated(js, buf, false);\n      auto bytes = backing.asArrayPtr();\n      auto amountToCopy = kj::min(bytes.size(), dest.size());\n      dest.first(amountToCopy).copyFrom(bytes.first(amountToCopy));\n      return amountToCopy;\n    }\n    default:\n      KJ_UNREACHABLE;\n  }\n}\n\njsg::JsUint8Array decodeStringImpl(\n    jsg::Lock& js, const jsg::JsString& string, Encoding encoding, bool strict = false) {\n  auto length = string.length(js);\n  if (length == 0) {\n    return jsg::JsUint8Array::create(js, 0);\n  }\n\n  static constexpr jsg::JsString::WriteFlags options = jsg::JsString::REPLACE_INVALID_UTF8;\n\n  switch (encoding) {\n    case Encoding::ASCII:\n      // Fall-through\n    case Encoding::LATIN1: {\n      auto dest = jsg::JsUint8Array::create(js, length);\n      writeInto(js, dest.asArrayPtr(), string, 0, dest.size(), Encoding::LATIN1);\n      return kj::mv(dest);\n    }\n    case Encoding::UTF8: {\n      auto dest = jsg::JsUint8Array::create(js, string.utf8Length(js));\n      writeInto(js, dest.asArrayPtr(), string, 0, dest.size(), Encoding::UTF8);\n      return kj::mv(dest);\n    }\n    case Encoding::UTF16LE: {\n      auto dest = jsg::JsUint8Array::create(js, length * sizeof(uint16_t));\n      writeInto(js, dest.asArrayPtr(), string, 0, dest.size(), Encoding::UTF16LE);\n      return kj::mv(dest);\n    }\n    case Encoding::BASE64:\n      // Fall-through\n    case Encoding::BASE64URL: {\n      // TODO(soon): Use simdutf for faster decoding for BASE64 and BASE64URL.\n      // We do not use the kj::String conversion here because inline null-characters\n      // need to be ignored.\n      KJ_STACK_ARRAY(kj::byte, buf, length, 1024, 536870888);\n      auto result = string.writeInto(js, buf, options);\n      auto len = result.written;\n      auto dest = jsg::JsUint8Array::create(js, nbytes::Base64DecodedSize(buf.begin(), len));\n      len = nbytes::Base64Decode(\n          dest.asArrayPtr<char>().begin(), dest.size(), buf.begin(), buf.size());\n      if (len < dest.size()) {\n        return dest.slice(js, len);\n      }\n      return dest;\n    }\n    case Encoding::HEX: {\n      KJ_STACK_ARRAY(kj::byte, buf, length, 1024, 536870888);\n      string.writeInto(js, buf, options);\n      return decodeHexTruncated(js, buf, strict);\n    }\n    default:\n      KJ_UNREACHABLE;\n  }\n}\n}  // namespace\n\nuint32_t BufferUtil::byteLength(jsg::Lock& js, jsg::JsString str) {\n  return str.utf8Length(js);\n}\n\nint BufferUtil::compare(jsg::Lock& js,\n    jsg::JsUint8Array one,\n    jsg::JsUint8Array two,\n    jsg::Optional<CompareOptions> maybeOptions) {\n  kj::ArrayPtr<kj::byte> ptrOne = one.asArrayPtr();\n  kj::ArrayPtr<kj::byte> ptrTwo = two.asArrayPtr();\n\n  // The options allow comparing subranges within the two inputs.\n  KJ_IF_SOME(options, maybeOptions) {\n    auto end = options.aEnd.orDefault(ptrOne.size());\n    end = kj::min(end, ptrOne.size());\n    auto start = kj::min(end, options.aStart.orDefault(0));\n    ptrOne = ptrOne.slice(start, end);\n    end = options.bEnd.orDefault(ptrTwo.size());\n    end = kj::min(end, ptrTwo.size());\n    start = kj::min(end, options.bStart.orDefault(0));\n    ptrTwo = ptrTwo.slice(start, end);\n  }\n\n  size_t toCompare = kj::min(ptrOne.size(), ptrTwo.size());\n  auto result = toCompare > 0 ? memcmp(ptrOne.begin(), ptrTwo.begin(), toCompare) : 0;\n\n  if (result == 0) {\n    if (ptrOne.size() > ptrTwo.size())\n      return 1;\n    else if (ptrOne.size() < ptrTwo.size())\n      return -1;\n    else\n      return 0;\n  }\n\n  return result > 0 ? 1 : -1;\n}\n\njsg::JsUint8Array BufferUtil::concat(\n    jsg::Lock& js, kj::Array<jsg::JsUint8Array> list, uint32_t length) {\n  // The Node.js Buffer.concat is interesting in that it doesn't just append\n  // the buffers together as is. The length parameter is used to determine the\n  // length of the result which can be lesser or greater than the actual\n  // combined lengths of the inputs. If the length is lesser, the result will\n  // be a truncated version of the combined buffers. If the length is greater,\n  // the result will be the combined buffers with the remaining space filled\n  // with zeroes.\n\n  JSG_REQUIRE(length <= v8::ArrayBuffer::kMaxByteLength, RangeError, \"The length is too large\");\n\n  auto dest = jsg::JsUint8Array::create(js, length);\n  if (length > 0) {\n    auto view = dest.asArrayPtr();\n\n    for (auto& src: list) {\n      // JsUint8Array is a view directly on the v8::Uint8Array, so we don't\n      // really need to worry about whether the underlying ArrayBuffer is\n      // detached or resized, etc. The length is not cached.\n      auto ptr = src.asArrayPtr();\n      if (ptr.size() == 0) continue;\n      // The amount to copy is the lesser of the remaining space in the destination or\n      // the size of the chunk we're copying.\n      auto amountToCopy = kj::min(ptr.size(), view.size());\n      view.first(amountToCopy).copyFrom(ptr.first(amountToCopy));\n      view = view.slice(amountToCopy);\n      // If there's no more space in the destination, we're done.\n      if (view == nullptr) {\n        break;\n      }\n    }\n  }\n\n  return dest;\n}\n\njsg::JsUint8Array BufferUtil::decodeString(\n    jsg::Lock& js, jsg::JsString string, EncodingValue encoding) {\n  return decodeStringImpl(js, string, static_cast<Encoding>(encoding));\n}\n\nvoid BufferUtil::fillImpl(jsg::Lock& js,\n    jsg::JsUint8Array buffer,\n    kj::OneOf<jsg::JsString, jsg::JsUint8Array> value,\n    uint32_t start,\n    uint32_t end,\n    jsg::Optional<EncodingValue> encoding) {\n  end = kj::min(end, buffer.size());\n  if (end <= start) return;\n\n  auto ptr = buffer.asArrayPtr().slice(start, end);\n  KJ_SWITCH_ONEOF(value) {\n    KJ_CASE_ONEOF(string, jsg::JsString) {\n      auto enc = encoding.orDefault(Encoding::UTF8);\n      auto decoded = decodeStringImpl(js, string, static_cast<Encoding>(enc), true /* strict */);\n      if (decoded.size() == 0) {\n        ptr.fill(0);\n        return;\n      }\n      ptr.fill(decoded.asArrayPtr());\n    }\n    KJ_CASE_ONEOF(source, jsg::JsUint8Array) {\n      if (source.size() == 0) {\n        ptr.fill(0);\n        return;\n      }\n      ptr.fill(source.asArrayPtr());\n    }\n  }\n}\n\nnamespace {\n\n// Computes the offset for starting an indexOf or lastIndexOf search.\n// Returns either a valid offset in [0...<length - 1>], ie inside the Buffer,\n// or -1 to signal that there is no possible match.\nint32_t indexOfOffset(size_t length, int32_t offset, int32_t needle_length, bool isForward) {\n  int32_t len = static_cast<int32_t>(length);\n  if (offset < 0) {\n    if (offset + len >= 0) {\n      // Negative offsets count backwards from the end of the buffer.\n      return len + offset;\n    } else if (isForward || needle_length == 0) {\n      // indexOf from before the start of the buffer: search the whole buffer.\n      return 0;\n    } else {\n      // lastIndexOf from before the start of the buffer: no match.\n      return -1;\n    }\n  } else {\n    // cast to int64_t to avoid overflow.\n    if (static_cast<int64_t>(offset) + needle_length <= len) {\n      // Valid positive offset.\n      return offset;\n    } else if (needle_length == 0) {\n      // Out of buffer bounds, but empty needle: point to end of buffer.\n      return len;\n    } else if (isForward) {\n      // indexOf from past the end of the buffer: no match.\n      return -1;\n    } else {\n      // lastIndexOf from past the end of the buffer: search the whole buffer.\n      return len - 1;\n    }\n  }\n}\n\njsg::Optional<uint32_t> indexOfBuffer(jsg::Lock& js,\n    kj::ArrayPtr<kj::byte> hayStack,\n    jsg::JsUint8Array needle,\n    int32_t byteOffset,\n    EncodingValue encoding,\n    bool isForward) {\n  auto enc = static_cast<Encoding>(encoding);\n  // Round down to the nearest multiple of 2 in case of UCS2.\n  auto hayStackLength = enc == Encoding::UTF16LE ? hayStack.size() & ~1 : hayStack.size();\n  auto optOffset = indexOfOffset(hayStackLength, byteOffset, needle.size(), isForward);\n\n  if (needle.size() == 0) return optOffset;\n  if (hayStackLength == 0 || optOffset <= -1 ||\n      (isForward && needle.size() + optOffset > hayStackLength) || needle.size() > hayStackLength) {\n    return kj::none;\n  }\n  auto result = hayStackLength;\n  if (enc == Encoding::UTF16LE) {\n    if (hayStackLength < 2 || needle.size() < 2) {\n      return kj::none;\n    }\n    // Copy haystack and needle to aligned buffers to avoid undefined behavior\n    // from unaligned uint16_t access (the data pointer may have an odd byte offset).\n    auto hayStackU16Len = hayStackLength / 2;\n    kj::SmallArray<uint16_t, 1024> alignedHayStack(hayStackU16Len);\n    alignedHayStack.asBytes().copyFrom(hayStack.first(hayStackLength));\n\n    auto needleLen = needle.size() & ~1;\n    auto needleU16Len = needleLen / 2;\n    kj::SmallArray<uint16_t, 1024> alignedNeedle(needleU16Len);\n    alignedNeedle.asBytes().copyFrom(needle.asArrayPtr().first(needleLen));\n\n    result = SearchString(alignedHayStack.begin(), hayStackU16Len, alignedNeedle.begin(),\n        needleU16Len, optOffset / 2, isForward);\n    result *= 2;\n  } else {\n    result = SearchString(hayStack.asBytes().begin(), hayStack.size(),\n        needle.asArrayPtr().asBytes().begin(), needle.size(), optOffset, isForward);\n  }\n\n  if (result == hayStackLength) return kj::none;\n\n  return result;\n}\n\njsg::Optional<uint32_t> indexOfString(jsg::Lock& js,\n    kj::ArrayPtr<kj::byte> hayStack,\n    const jsg::JsString& needle,\n    int32_t byteOffset,\n    EncodingValue encoding,\n    bool isForward) {\n\n  auto enc = static_cast<Encoding>(encoding);\n  auto decodedNeedle = decodeStringImpl(js, needle, enc);\n\n  // Round down to the nearest multiple of 2 in case of UCS2\n  auto hayStackLength = enc == Encoding::UTF16LE ? hayStack.size() & ~1 : hayStack.size();\n  auto optOffset = indexOfOffset(hayStackLength, byteOffset, decodedNeedle.size(), isForward);\n\n  if (decodedNeedle.size() == 0) {\n    return optOffset;\n  }\n\n  if (hayStackLength == 0 || optOffset <= -1 ||\n      (isForward && decodedNeedle.size() + optOffset > hayStackLength) ||\n      decodedNeedle.size() > hayStackLength) {\n    return kj::none;\n  }\n\n  auto result = hayStackLength;\n\n  if (enc == Encoding::UTF16LE) {\n    if (hayStackLength < 2 || decodedNeedle.size() < 2) {\n      return kj::none;\n    }\n    // Copy haystack to an aligned buffer to avoid undefined behavior from\n    // unaligned uint16_t access (the data pointer may have an odd byte offset).\n    auto hayStackU16Len = hayStackLength / 2;\n    kj::SmallArray<uint16_t, 1024> alignedHayStack(hayStackU16Len);\n    alignedHayStack.asBytes().copyFrom(hayStack.first(hayStackLength));\n\n    result = SearchString(alignedHayStack.begin(), hayStackU16Len,\n        reinterpret_cast<const uint16_t*>(decodedNeedle.asArrayPtr<char>().begin()),\n        decodedNeedle.size() / 2, optOffset / 2, isForward);\n    result *= 2;\n  } else {\n    result = SearchString(hayStack.asBytes().begin(), hayStack.size(),\n        decodedNeedle.asArrayPtr().begin(), decodedNeedle.size(), optOffset, isForward);\n  }\n\n  if (result == hayStackLength) return kj::none;\n\n  return result;\n}\n\njsg::JsString toStringImpl(\n    jsg::Lock& js, kj::ArrayPtr<kj::byte> bytes, uint32_t start, uint32_t end, Encoding encoding) {\n  KJ_ASSERT(end <= bytes.size());\n  if (end < start) end = start;\n  auto slice = bytes.slice(start, end);\n  if (slice.size() == 0) return js.str();\n  switch (encoding) {\n    case Encoding::ASCII: {\n      // TODO(perf): We can look at making this more performant later.\n      // Essentially we have to modify the buffer such that every byte\n      // has the highest bit turned off. Whee! Node.js has a faster\n      // algorithm that it implements so we can likely adopt that.\n      kj::Array<kj::byte> copy = KJ_MAP(b, slice) -> kj::byte { return b & 0x7f; };\n      return js.str(copy);\n    }\n    case Encoding::LATIN1: {\n      return js.str(slice);\n    }\n    case Encoding::UTF8: {\n      return js.str(slice.asChars());\n    }\n    case Encoding::UTF16LE: {\n      // Why are we copying here? Good question! There's really not much of a\n      // good reason why we should be copying here except that V8 doesn't seem\n      // to like it! We end up with an asan buffer over-read error if we pass\n      // in the slice directly... looks to have something to do with alignment\n      // issues. Copying here sidesteps the problem and avoids the asan issue.\n      kj::ArrayPtr<uint16_t> view(reinterpret_cast<uint16_t*>(slice.begin()), slice.size() / 2);\n      KJ_STACK_ARRAY(uint16_t, data, view.size(), 1024, 4096);\n      data.copyFrom(view);\n      return js.str(data);\n    }\n    case Encoding::BASE64: {\n      size_t length = simdutf::base64_length_from_binary(slice.size());\n      KJ_STACK_ARRAY(kj::byte, out, length, 1024, 4096);\n      simdutf::binary_to_base64(reinterpret_cast<const char*>(slice.begin()), slice.size(),\n          reinterpret_cast<char*>(out.begin()));\n      return js.str(out);\n    }\n    case Encoding::BASE64URL: {\n      auto options = simdutf::base64_url;\n      size_t length = simdutf::base64_length_from_binary(slice.size(), options);\n      KJ_STACK_ARRAY(kj::byte, out, length, 1024, 4096);\n      simdutf::binary_to_base64(reinterpret_cast<const char*>(slice.begin()), slice.size(),\n          reinterpret_cast<char*>(out.begin()), options);\n      return js.str(out);\n    }\n    case Encoding::HEX: {\n      return js.str(kj::encodeHex(slice));\n    }\n    default:\n      KJ_UNREACHABLE;\n  }\n}\n\n}  // namespace\n\njsg::Optional<uint32_t> BufferUtil::indexOf(jsg::Lock& js,\n    jsg::JsUint8Array buffer,\n    kj::OneOf<jsg::JsString, jsg::JsUint8Array> value,\n    int32_t byteOffset,\n    EncodingValue encoding,\n    bool isForward) {\n\n  KJ_SWITCH_ONEOF(value) {\n    KJ_CASE_ONEOF(string, jsg::JsString) {\n      return indexOfString(js, buffer.asArrayPtr(), string, byteOffset, encoding, isForward);\n    }\n    KJ_CASE_ONEOF(source, jsg::JsUint8Array) {\n      return indexOfBuffer(\n          js, buffer.asArrayPtr(), kj::mv(source), byteOffset, encoding, isForward);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid BufferUtil::swap(jsg::Lock& js, jsg::JsUint8Array buffer, int size) {\n  if (buffer.size() <= 1) return;\n  switch (size) {\n    case 16: {\n      JSG_REQUIRE(nbytes::SwapBytes16(buffer.asArrayPtr().asChars().begin(), buffer.size()), Error,\n          \"Swap bytes failed\");\n      break;\n    }\n    case 32: {\n      JSG_REQUIRE(nbytes::SwapBytes32(buffer.asArrayPtr().asChars().begin(), buffer.size()), Error,\n          \"Swap bytes failed\");\n      break;\n    }\n    case 64: {\n      JSG_REQUIRE(nbytes::SwapBytes64(buffer.asArrayPtr().asChars().begin(), buffer.size()), Error,\n          \"Swap bytes failed\");\n      break;\n    }\n    default:\n      JSG_FAIL_REQUIRE(Error, \"Unreachable\");\n  }\n}\n\njsg::JsString BufferUtil::toString(\n    jsg::Lock& js, jsg::JsUint8Array bytes, uint32_t start, uint32_t end, EncodingValue encoding) {\n  end = kj::min(bytes.size(), end);\n  if (end <= start) return js.str();\n  return toStringImpl(js, bytes.asArrayPtr(), start, end, static_cast<Encoding>(encoding));\n}\n\nuint32_t BufferUtil::write(jsg::Lock& js,\n    jsg::JsUint8Array buffer,\n    jsg::JsString string,\n    uint32_t offset,\n    uint32_t length,\n    EncodingValue encoding) {\n  length = kj::min(length, buffer.size() - offset);\n  if (length == 0) return 0;\n  return writeInto(\n      js, buffer.asArrayPtr(), string, offset, length, static_cast<Encoding>(encoding));\n}\n\n// ======================================================================================\n// StringDecoder\n//\n// It's helpful to review a bit about how the implementation works here.\n//\n// StringDecoder is a streaming decoder that ensures that multi-byte characters are correctly\n// handled. So, for instance, let's suppose I have the utf8 bytes for a euro symbol (0xe2, 0x82,\n// 0xac), but I only get those one at a time... StringDecoder will ensure that those are correctly\n// handled over multiple calls to write(...)...\n//\n//   const sd = new StringDecoder();\n//   let results = '';\n//   results += sd.write(new Uint8Array([0xe2]));  // results.length === 0\n//   results += sd.write(new Uint8Array([0x82]));  // results.length === 0\n//   results += sd.write(new Uint8Array([0xac]));  // results.length === 1\n//   results += sd.end();\n//\n// Internally, the decoder allocates a small 7 byte buffer (the state) argument below.\n//\n// The first four bytes of the state are used to hold partial bytes received on the previous\n// write. The fifth byte in state is a count of the number of missing bytes we need to complete\n// the character. The sixth byte in state is the number of bytes that have been encoded into the\n// first four. The seventh byte in state identifies the Encoding and matches the values of the\n// Encoding enum.\n//\n// So, in our example above, initially the first six bytes of the state are [0x00, 0x00, 0x00,\n// 0x00, 0x00, 0x00]\n//\n// After the first call to write above, the state is updated to: [0xe2, 0x00, 0x00, 0x00, 0x02,\n// 0x01]\n//\n// After the second call to write, the state is updated to: [0xe2, 0x82, 0x00, 0x00, 0x01, 0x02]\n//\n// After the third call to write, the pending multibyte character is completed, the state becomes:\n// [0xe2, 0x82, 0xac, 0x00, 0x00, 0x00] ... while the bytes are still in state, the buffered bytes\n// and bytes needed are zeroed out. Since the character is completed on that third write, it is\n// included in the returned string.\n//\n// The implementation here is taken nearly verbatim from Node.js with a few adaptations. The code\n// from Node.js has remained largely unchanged for years and is well-proven.\n\nnamespace {\ninline kj::byte getMissingBytes(kj::ArrayPtr<kj::byte> state) {\n  JSG_REQUIRE(state[BufferUtil::kMissingBytes] <= BufferUtil::kIncompleteCharactersEnd, Error,\n      \"Missing bytes cannot exceed 4\");\n  return state[BufferUtil::kMissingBytes];\n}\n\ninline kj::byte getBufferedBytes(kj::ArrayPtr<kj::byte> state) {\n  JSG_REQUIRE(state[BufferUtil::kBufferedBytes] <= BufferUtil::kIncompleteCharactersEnd, Error,\n      \"Buffered bytes cannot exceed 4\");\n  return state[BufferUtil::kBufferedBytes];\n}\n\ninline kj::byte* getIncompleteCharacterBuffer(kj::ArrayPtr<kj::byte> state) {\n  return state.begin() + BufferUtil::kIncompleteCharactersStart;\n}\n\ninline Encoding getEncoding(kj::ArrayPtr<kj::byte> state) {\n  JSG_REQUIRE(state[BufferUtil::kEncoding] <= static_cast<kj::byte>(Encoding::HEX), Error,\n      \"Invalid StringDecoder state\");\n  return static_cast<Encoding>(state[BufferUtil::kEncoding]);\n}\n\njsg::JsString getBufferedString(jsg::Lock& js, kj::ArrayPtr<kj::byte> state) {\n  JSG_REQUIRE(getBufferedBytes(state) <= BufferUtil::kIncompleteCharactersEnd, Error,\n      \"Invalid StringDecoder state\");\n  auto ret = toStringImpl(js, state, BufferUtil::kIncompleteCharactersStart,\n      BufferUtil::kIncompleteCharactersStart + getBufferedBytes(state), getEncoding(state));\n  state[BufferUtil::kBufferedBytes] = 0;\n  return ret;\n}\n}  // namespace\n\njsg::JsString BufferUtil::decode(jsg::Lock& js, jsg::JsUint8Array bytes, jsg::JsUint8Array state) {\n  JSG_REQUIRE(state.size() == BufferUtil::kSize, TypeError, \"Invalid StringDecoder\");\n  auto bytesPtr = bytes.asArrayPtr();\n  auto statePtr = state.asArrayPtr();\n  auto enc = getEncoding(statePtr);\n  if (enc == Encoding::ASCII || enc == Encoding::LATIN1 || enc == Encoding::HEX) {\n    // For ascii, latin1, and hex, we can just use the regular\n    // toString option since there will never be a case where\n    // these have left-over characters.\n    return toStringImpl(js, bytesPtr, 0, bytesPtr.size(), enc);\n  }\n\n  jsg::JsString prepend = js.str();\n  jsg::JsString body = js.str();\n  auto nread = bytesPtr.size();\n\n  // If bytes is empty there's nothing to decode.\n  if (bytesPtr.size() == 0) return js.str();\n\n  auto data = bytesPtr.begin();\n\n  if (getMissingBytes(statePtr) > 0) {\n    JSG_REQUIRE(getMissingBytes(statePtr) + getBufferedBytes(statePtr) <=\n            BufferUtil::kIncompleteCharactersEnd,\n        Error, \"Invalid StringDecoder state\");\n    if (enc == Encoding::UTF8) {\n      // For UTF-8, we need special treatment to align with the V8 decoder:\n      // If an incomplete character is found at a chunk boundary, we use\n      // its remainder and pass it to V8 as-is.\n      for (size_t i = 0; i < nread && i < getMissingBytes(statePtr); ++i) {\n        if ((data[i] & 0xC0) != 0x80) {\n          // This byte is not a continuation byte even though it should have\n          // been one. We stop decoding of the incomplete character at this\n          // point (but still use the rest of the incomplete bytes from this\n          // chunk) and assume that the new, unexpected byte starts a new one.\n          statePtr[kMissingBytes] = 0;\n          memcpy(getIncompleteCharacterBuffer(statePtr) + getBufferedBytes(statePtr), data, i);\n          statePtr[kBufferedBytes] += i;\n          data += i;\n          nread -= i;\n          break;\n        }\n      }\n    }\n\n    size_t found_bytes = std::min(nread, static_cast<size_t>(getMissingBytes(statePtr)));\n    KJ_ASSERT(data != nullptr);\n    memcpy(getIncompleteCharacterBuffer(statePtr) + getBufferedBytes(statePtr), data, found_bytes);\n    // Adjust the two buffers.\n    data += found_bytes;\n    nread -= found_bytes;\n\n    statePtr[kMissingBytes] -= found_bytes;\n    statePtr[kBufferedBytes] += found_bytes;\n\n    if (getMissingBytes(statePtr) == 0) {\n      // If no more bytes are missing, create a small string that we will later prepend.\n      prepend = getBufferedString(js, statePtr);\n    }\n  }\n\n  if (nread == 0) {\n    body = prepend.length(js) ? prepend : js.str();\n    prepend = js.str();\n  } else {\n    JSG_REQUIRE(getMissingBytes(statePtr) == 0, Error, \"Invalid StringDecoder state\");\n    JSG_REQUIRE(getBufferedBytes(statePtr) == 0, Error, \"Invalid StringDecoder state\");\n\n    // See whether there is a character that we may have to cut off and\n    // finish when receiving the next chunk.\n    if (enc == Encoding::UTF8 && data[nread - 1] & 0x80) {\n      // This is UTF-8 encoded data and we ended on a non-ASCII UTF-8 byte.\n      // This means we'll need to figure out where the character to which\n      // the byte belongs begins.\n      for (size_t i = nread - 1;; --i) {\n        JSG_REQUIRE(i < nread, Error, \"Invalid StringDecoder state\");\n        statePtr[kBufferedBytes]++;\n        if ((data[i] & 0xC0) == 0x80) {\n          // This byte does not start a character (a \"trailing\" byte).\n          if (statePtr[kBufferedBytes] >= 4 || i == 0) {\n            // We either have more then 4 trailing bytes (which means\n            // the current character would not be inside the range for\n            // valid Unicode, and in particular cannot be represented\n            // through JavaScript's UTF-16-based approach to strings), or the\n            // current buffer does not contain the start of an UTF-8 character\n            // at all. Either way, this is invalid UTF8 and we can just\n            // let the engine's decoder handle it.\n            statePtr[kBufferedBytes] = 0;\n            break;\n          }\n        } else {\n          // Found the first byte of a UTF-8 character. By looking at the\n          // upper bits we can tell how long the character *should* be.\n          if ((data[i] & 0xE0) == 0xC0) {\n            statePtr[kMissingBytes] = 2;\n          } else if ((data[i] & 0xF0) == 0xE0) {\n            statePtr[kMissingBytes] = 3;\n          } else if ((data[i] & 0xF8) == 0xF0) {\n            statePtr[kMissingBytes] = 4;\n          } else {\n            // This lead byte would indicate a character outside of the\n            // representable range.\n            statePtr[kBufferedBytes] = 0;\n            break;\n          }\n\n          if (getBufferedBytes(statePtr) >= getMissingBytes(statePtr)) {\n            // Received more or exactly as many trailing bytes than the lead\n            // character would indicate. In the \"==\" case, we have valid\n            // data and don't need to slice anything off;\n            // in the \">\" case, this is invalid UTF-8 anyway.\n            statePtr[kMissingBytes] = 0;\n            statePtr[kBufferedBytes] = 0;\n          }\n\n          statePtr[kMissingBytes] -= statePtr[kBufferedBytes];\n          break;\n        }\n      }\n    } else if (enc == Encoding::UTF16LE) {\n      if ((nread % 2) == 1) {\n        // WePtr got half a codepoint, and need the second byte of it.\n        statePtr[kBufferedBytes] = 1;\n        statePtr[kMissingBytes] = 1;\n      } else if ((data[nread - 1] & 0xFC) == 0xD8) {\n        // Half a split UTF-16 character.\n        statePtr[kBufferedBytes] = 2;\n        statePtr[kMissingBytes] = 2;\n      }\n    } else if (enc == Encoding::BASE64 || enc == Encoding::BASE64URL) {\n      statePtr[kBufferedBytes] = nread % 3;\n      if (statePtr[kBufferedBytes] > 0) statePtr[kMissingBytes] = 3 - getBufferedBytes(statePtr);\n    }\n\n    if (getBufferedBytes(statePtr) > 0) {\n      // Copy the requested number of buffered bytes from the end of the\n      // input into the incomplete character buffer.\n      nread -= getBufferedBytes(statePtr);\n      memcpy(getIncompleteCharacterBuffer(statePtr), data + nread, getBufferedBytes(statePtr));\n    }\n\n    if (nread > 0) {\n      body = toStringImpl(js, kj::ArrayPtr<kj::byte>(data, data + nread), 0, nread, enc);\n    } else {\n      body = js.str();\n    }\n  }\n\n  if (prepend.length(js) == 0) {\n    return body;\n  } else {\n    return jsg::JsString::concat(js, prepend, body);\n  }\n\n  return js.str();\n}\n\njsg::JsString BufferUtil::flush(jsg::Lock& js, jsg::JsUint8Array state) {\n  JSG_REQUIRE(state.size() == BufferUtil::kSize, TypeError, \"Invalid StringDecoder\");\n  auto statePtr = state.asArrayPtr();\n  auto enc = getEncoding(statePtr);\n  if (enc == Encoding::ASCII || enc == Encoding::HEX || enc == Encoding::LATIN1) {\n    JSG_REQUIRE(getMissingBytes(statePtr) == 0, Error, \"Invalid StringDecoder state\");\n    JSG_REQUIRE(getBufferedBytes(statePtr) == 0, Error, \"Invalid StringDecoder state\");\n  }\n\n  if (enc == Encoding::UTF16LE && getBufferedBytes(statePtr) % 2 == 1) {\n    // Ignore a single trailing byte, like the JS decoder does.\n    statePtr[kMissingBytes]--;\n    statePtr[kBufferedBytes]--;\n  }\n\n  if (getBufferedBytes(statePtr) == 0) {\n    return js.str();\n  }\n\n  auto ret = getBufferedString(js, statePtr);\n  statePtr[kMissingBytes] = 0;\n\n  return ret;\n}\n\nbool BufferUtil::isAscii(jsg::JsUint8Array buffer) {\n  if (buffer.size() == 0) return true;\n  return simdutf::validate_ascii(buffer.asArrayPtr().asChars().begin(), buffer.size());\n}\n\nbool BufferUtil::isUtf8(jsg::JsUint8Array buffer) {\n  if (buffer.size() == 0) return true;\n  return simdutf::validate_utf8(buffer.asArrayPtr().asChars().begin(), buffer.size());\n}\n\njsg::JsUint8Array BufferUtil::transcode(jsg::Lock& js,\n    jsg::JsUint8Array source,\n    EncodingValue rawFromEncoding,\n    EncodingValue rawToEncoding) {\n  auto fromEncoding = static_cast<Encoding>(rawFromEncoding);\n  auto toEncoding = static_cast<Encoding>(rawToEncoding);\n\n  JSG_REQUIRE(i18n::canBeTranscoded(fromEncoding) && i18n::canBeTranscoded(toEncoding), Error,\n      \"Unable to transcode buffer due to unsupported encoding\");\n\n  return i18n::transcode(js, source.asArrayPtr(), fromEncoding, toEncoding);\n}\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/buffer.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include \"i18n.h\"\n\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api::node {\n\n// Implements utilities in support of the Node.js Buffer\nclass BufferUtil final: public jsg::Object {\n public:\n  BufferUtil() = default;\n  BufferUtil(jsg::Lock&, const jsg::Url&) {}\n\n  uint32_t byteLength(jsg::Lock& js, jsg::JsString str);\n\n  struct CompareOptions {\n    jsg::Optional<uint32_t> aStart;\n    jsg::Optional<uint32_t> aEnd;\n    jsg::Optional<uint32_t> bStart;\n    jsg::Optional<uint32_t> bEnd;\n\n    JSG_STRUCT(aStart, aEnd, bStart, bEnd);\n  };\n\n  int compare(jsg::Lock& js,\n      jsg::JsUint8Array one,\n      jsg::JsUint8Array two,\n      jsg::Optional<CompareOptions> maybeOptions);\n\n  jsg::JsUint8Array concat(jsg::Lock& js, kj::Array<jsg::JsUint8Array> list, uint32_t length);\n\n  jsg::JsUint8Array decodeString(jsg::Lock& js, jsg::JsString string, EncodingValue encoding);\n\n  void fillImpl(jsg::Lock& js,\n      jsg::JsUint8Array buffer,\n      kj::OneOf<jsg::JsString, jsg::JsUint8Array> value,\n      uint32_t start,\n      uint32_t end,\n      jsg::Optional<EncodingValue> encoding);\n\n  jsg::Optional<uint32_t> indexOf(jsg::Lock& js,\n      jsg::JsUint8Array buffer,\n      kj::OneOf<jsg::JsString, jsg::JsUint8Array> value,\n      int32_t byteOffset,\n      EncodingValue encoding,\n      bool isForward);\n\n  void swap(jsg::Lock& js, jsg::JsUint8Array buffer, int size);\n\n  jsg::JsString toString(\n      jsg::Lock& js, jsg::JsUint8Array bytes, uint32_t start, uint32_t end, EncodingValue encoding);\n\n  uint32_t write(jsg::Lock& js,\n      jsg::JsUint8Array buffer,\n      jsg::JsString string,\n      uint32_t offset,\n      uint32_t length,\n      EncodingValue encoding);\n\n  enum NativeDecoderFields {\n    kIncompleteCharactersStart = 0,\n    kIncompleteCharactersEnd = 4,\n    kMissingBytes = 4,\n    kBufferedBytes = 5,\n    kEncoding = 6,\n    kSize = 7,\n  };\n\n  jsg::JsString decode(jsg::Lock& js, jsg::JsUint8Array bytes, jsg::JsUint8Array state);\n  jsg::JsString flush(jsg::Lock& js, jsg::JsUint8Array state);\n  bool isAscii(jsg::JsUint8Array bytes);\n  bool isUtf8(jsg::JsUint8Array bytes);\n  jsg::JsUint8Array transcode(jsg::Lock& js,\n      jsg::JsUint8Array source,\n      EncodingValue rawFromEncoding,\n      EncodingValue rawToEncoding);\n\n  JSG_RESOURCE_TYPE(BufferUtil) {\n    JSG_METHOD(byteLength);\n    JSG_METHOD(compare);\n    JSG_METHOD(concat);\n    JSG_METHOD(decodeString);\n    JSG_METHOD(fillImpl);\n    JSG_METHOD(indexOf);\n    JSG_METHOD(swap);\n    JSG_METHOD(toString);\n    JSG_METHOD(write);\n    JSG_METHOD(isAscii);\n    JSG_METHOD(isUtf8);\n    JSG_METHOD(transcode);\n\n    // For StringDecoder\n    JSG_METHOD(decode);\n    JSG_METHOD(flush);\n\n    JSG_STATIC_CONSTANT_NAMED(ASCII, static_cast<EncodingValue>(Encoding::ASCII));\n    JSG_STATIC_CONSTANT_NAMED(LATIN1, static_cast<EncodingValue>(Encoding::LATIN1));\n    JSG_STATIC_CONSTANT_NAMED(UTF8, static_cast<EncodingValue>(Encoding::UTF8));\n    JSG_STATIC_CONSTANT_NAMED(UTF16LE, static_cast<EncodingValue>(Encoding::UTF16LE));\n    JSG_STATIC_CONSTANT_NAMED(BASE64, static_cast<EncodingValue>(Encoding::BASE64));\n    JSG_STATIC_CONSTANT_NAMED(BASE64URL, static_cast<EncodingValue>(Encoding::BASE64URL));\n    JSG_STATIC_CONSTANT_NAMED(HEX, static_cast<EncodingValue>(Encoding::HEX));\n  }\n};\n\n#define EW_NODE_BUFFER_ISOLATE_TYPES api::node::BufferUtil, api::node::BufferUtil::CompareOptions\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/crypto-keys.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#include \"crypto.h\"\n#include \"util.h\"\n\n#include <workerd/api/crypto/impl.h>\n#include <workerd/api/crypto/jwk.h>\n\n#include <ncrypto.h>\n#include <openssl/crypto.h>\n\n#include <map>\n\n// TODO(soon): This implements most of node:crypto key import, export, and\n// generation with a number of notable exceptions.\n//\n// 1. While it is possible to import DSA keys, it is currently not possible\n//    to generate a new DSA key pair. This is due entirely to limitations\n//    currently in boringssl+fips that we use in production.\n// 2. It is currently not possible to generate or import diffie-hellman\n//    keys or use the stateless diffie-hellman API. The older DH apis are\n//    still functional, but the stateless DH and DH keys currently rely on\n//    the EVP DH APIs that are not implementing by boringssl+fips. An\n//    alternative approach is possible but requires a bit more effort.\n// 3.\nnamespace workerd::api::node {\n\nnamespace {\n// An algorithm-independent secret key. Used as the underlying\n// implementation of Node.js SecretKey objects. Unlike Web Crypto,\n// a Node.js secret key is not algorithm specific. For instance, a\n// single secret key can be used for both AES and HMAC, where as\n// Web Crypto requires a separate key for each algorithm.\nclass SecretKey final: public CryptoKey::Impl {\n public:\n  explicit SecretKey(kj::Array<kj::byte> keyData)\n      : Impl(true, CryptoKeyUsageSet::privateKeyMask() | CryptoKeyUsageSet::publicKeyMask()),\n        keyData(kj::mv(keyData)) {}\n  ~SecretKey() noexcept(false) {\n    OPENSSL_cleanse(keyData.begin(), keyData.size());\n  }\n\n  kj::StringPtr getAlgorithmName() const override {\n    return \"secret\"_kj;\n  }\n  CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {\n    return CryptoKey::ArbitraryKeyAlgorithm{\n      .name = getAlgorithmName(),\n      .length = keyData.size(),\n    };\n  }\n\n  bool equals(const CryptoKey::Impl& other) const override final {\n    if (this == &other) return true;\n    if (other.getType() != \"secret\"_kj) return false;\n    KJ_IF_SOME(o, kj::dynamicDowncastIfAvailable<const SecretKey>(other)) {\n      return equalsImpl(o.rawKeyData());\n    }\n    return false;\n  }\n\n  bool equalsImpl(kj::ArrayPtr<const kj::byte> other) const {\n    return keyData.size() == other.size() &&\n        CRYPTO_memcmp(keyData.begin(), other.begin(), keyData.size()) == 0;\n  }\n\n  bool equals(const kj::Array<kj::byte>& other) const override final {\n    return equalsImpl(other.asPtr());\n  }\n\n  SubtleCrypto::ExportKeyData exportKey(jsg::Lock& js, kj::StringPtr format) const override final {\n    JSG_REQUIRE(format == \"raw\" || format == \"jwk\", DOMNotSupportedError, getAlgorithmName(),\n        \" key only supports exporting \\\"raw\\\" & \\\"jwk\\\", not \\\"\", format, \"\\\".\");\n\n    if (format == \"jwk\") {\n      SubtleCrypto::JsonWebKey jwk;\n      jwk.kty = kj::str(\"oct\");\n      jwk.k = fastEncodeBase64Url(keyData.asPtr());\n      jwk.ext = true;\n      return jwk;\n    }\n\n    return jsg::JsArrayBuffer::create(js, keyData.asPtr()).addRef(js);\n  }\n\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"SecretKey\";\n  }\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(SecretKey);\n  }\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {\n    tracker.trackFieldWithSize(\"keyData\", keyData.size());\n  }\n\n  void visitForGc(jsg::GcVisitor& visitor) override {}\n\n  const kj::ArrayPtr<const kj::byte> rawKeyData() const {\n    return keyData.asPtr();\n  }\n\n private:\n  kj::Array<kj::byte> keyData;\n};\n\nCryptoKey::AsymmetricKeyDetails getRsaKeyDetails(jsg::Lock& js, const ncrypto::EVPKeyPointer& key) {\n  ncrypto::Rsa rsa = key;\n\n  // BoringSSL does not currently support the id-RSASSA-PSS key encoding and\n  // does not support getting the PSS param details using RSA_get0_pss_params.\n  // Therefore there's nothing else to do here currently.\n  // TODO(later): If/When BoringSSL supports getting the pss params, we will\n  // need to update this.\n  KJ_ASSERT(!rsa.getPssParams().has_value());\n\n  auto pubExp = JSG_REQUIRE_NONNULL(\n      bignumToArrayPadded(js, *rsa.getPublicKey().e), Error, \"Failed to extract public exponent\");\n  auto ab = jsg::JsArrayBuffer::create(js, pubExp.asArrayPtr());\n  return CryptoKey::AsymmetricKeyDetails{\n    .modulusLength = key.bits(),\n    .publicExponent = ab.addRef(js),\n  };\n}\n\nCryptoKey::AsymmetricKeyDetails getDsaKeyDetails(const ncrypto::EVPKeyPointer& key) {\n  ncrypto::Dsa dsa = key;\n\n  return CryptoKey::AsymmetricKeyDetails{\n    .modulusLength = static_cast<uint32_t>(dsa.getModulusLength()),\n    .divisorLength = static_cast<uint32_t>(dsa.getDivisorLength()),\n  };\n}\n\nCryptoKey::AsymmetricKeyDetails getEcKeyDetails(const ncrypto::EVPKeyPointer& key) {\n  ncrypto::Ec ec = key;\n\n  return CryptoKey::AsymmetricKeyDetails{\n    .namedCurve = kj::str(OBJ_nid2sn(EC_GROUP_get_curve_name(ec.getGroup()))),\n  };\n}\n\nkj::Maybe<ncrypto::EVPKeyPointer::PKFormatType> trySelectKeyFormat(kj::StringPtr format) {\n  if (format == \"pem\"_kj) return ncrypto::EVPKeyPointer::PKFormatType::PEM;\n  if (format == \"der\"_kj) return ncrypto::EVPKeyPointer::PKFormatType::DER;\n  if (format == \"jwk\"_kj) return ncrypto::EVPKeyPointer::PKFormatType::JWK;\n  return kj::none;\n}\n\nkj::Maybe<ncrypto::EVPKeyPointer::PKEncodingType> trySelectKeyEncoding(kj::StringPtr enc) {\n  if (enc == \"pkcs1\"_kj) return ncrypto::EVPKeyPointer::PKEncodingType::PKCS1;\n  if (enc == \"pkcs8\"_kj) return ncrypto::EVPKeyPointer::PKEncodingType::PKCS8;\n  if (enc == \"sec1\"_kj) return ncrypto::EVPKeyPointer::PKEncodingType::SEC1;\n  if (enc == \"spki\"_kj) return ncrypto::EVPKeyPointer::PKEncodingType::SPKI;\n  return kj::none;\n}\n\nclass AsymmetricKey final: public CryptoKey::Impl {\n public:\n  static kj::Own<AsymmetricKey> NewPrivate(ncrypto::EVPKeyPointer&& key) {\n    return kj::heap<AsymmetricKey>(kj::mv(key), true);\n  }\n\n  static kj::Own<AsymmetricKey> NewPublic(ncrypto::EVPKeyPointer&& key) {\n    return kj::heap<AsymmetricKey>(kj::mv(key), false);\n  }\n\n  AsymmetricKey(ncrypto::EVPKeyPointer&& key, bool isPrivate)\n      : CryptoKey::Impl(true, CryptoKeyUsageSet::privateKeyMask()),\n        key(kj::mv(key)),\n        isPrivate(isPrivate) {}\n\n  kj::StringPtr getAlgorithmName() const override {\n    if (!key) return nullptr;\n    switch (key.id()) {\n      case EVP_PKEY_RSA:\n        return \"rsa\"_kj;\n      case EVP_PKEY_RSA2:\n        return \"rsa\"_kj;\n      case EVP_PKEY_RSA_PSS:\n        return \"rsa\"_kj;\n      case EVP_PKEY_EC:\n        return \"ec\"_kj;\n      case EVP_PKEY_ED25519:\n        return \"ed25519\"_kj;\n      case EVP_PKEY_ED448:\n        return \"ed448\"_kj;\n      case EVP_PKEY_X25519:\n        return \"x25519\"_kj;\n      case EVP_PKEY_DSA:\n        return \"dsa\"_kj;\n      case EVP_PKEY_DH:\n        return \"dh\"_kj;\n#ifndef NCRYPTO_NO_KDF_H\n      case EVP_PKEY_HKDF:\n        return \"hkdf\"_kj;\n#endif\n      default:\n        return nullptr;\n    }\n    KJ_UNREACHABLE;\n  }\n\n  CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {\n    CryptoKey::ArbitraryKeyAlgorithm alg;\n    if (key) [[likely]] {\n      switch (key.id()) {\n        case EVP_PKEY_RSA:\n          alg.name = \"RSASSA-PKCS1-v1_5\"_kj;\n          break;\n        case EVP_PKEY_RSA2:\n          alg.name = \"RSASSA-PKCS1-v1_5\"_kj;\n          break;\n        case EVP_PKEY_RSA_PSS:\n          alg.name = \"RSA-PSS\"_kj;\n          break;\n        case EVP_PKEY_EC:\n          alg.name = \"ECDSA\"_kj;\n          break;\n        case EVP_PKEY_ED25519:\n          alg.name = \"Ed25519\"_kj;\n          break;\n        case EVP_PKEY_ED448:\n          alg.name = \"Ed448\"_kj;\n          break;\n        case EVP_PKEY_X25519:\n          alg.name = \"X25519\"_kj;\n          break;\n        case EVP_PKEY_DSA:\n          alg.name = \"NODE-DSA\"_kj;\n          break;\n        case EVP_PKEY_DH:\n          alg.name = \"NODE-DH\"_kj;\n          break;\n#ifndef NCRYPTO_NO_KDF_H\n        case EVP_PKEY_HKDF:\n          alg.name = \"NODE-HKDF\"_kj;\n          break;\n#endif\n      }\n    }\n    return alg;\n  }\n\n  CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail(jsg::Lock& js) const override {\n    if (!key) [[unlikely]]\n      return {};\n\n    if (key.isRsaVariant()) {\n      return getRsaKeyDetails(js, key);\n    }\n\n    if (key.id() == EVP_PKEY_DSA) {\n      return getDsaKeyDetails(key);\n    }\n\n    if (key.id() == EVP_PKEY_EC) {\n      return getEcKeyDetails(key);\n    }\n\n    return {};\n  }\n\n  jsg::JsUint8Array exportKeyExt(jsg::Lock& js,\n      kj::StringPtr format,\n      kj::StringPtr type,\n      jsg::Optional<kj::String> cipher = kj::none,\n      jsg::Optional<kj::Array<kj::byte>> passphrase = kj::none) const override {\n    if (!key) {\n      return jsg::JsUint8Array::create(js, 0);\n    }\n\n    auto formatType = JSG_REQUIRE_NONNULL(trySelectKeyFormat(format), Error, \"Invalid key format\");\n    auto encType = JSG_REQUIRE_NONNULL(trySelectKeyEncoding(type), Error, \"Invalid key encoding\");\n\n    if (!key.isRsaVariant()) {\n      JSG_REQUIRE(encType != ncrypto::EVPKeyPointer::PKEncodingType::PKCS1, Error,\n          \"PKCS1 can only be used for RSA keys\");\n    }\n\n    if (encType == ncrypto::EVPKeyPointer::PKEncodingType::SEC1) {\n      JSG_REQUIRE(key.id() == EVP_PKEY_EC, Error, \"SEC1 can only be used for EC keys\");\n    }\n\n    // This branch should never be taken for JWK\n    KJ_ASSERT(formatType != ncrypto::EVPKeyPointer::PKFormatType::JWK);\n\n    auto maybeBio = ([&] {\n      if (isPrivate) {\n        return key.writePrivateKey(\n            ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig(false, formatType, encType));\n      }\n      return key.writePublicKey(\n          ncrypto::EVPKeyPointer::PublicKeyEncodingConfig(false, formatType, encType));\n    })();\n    if (maybeBio.has_value) {\n      BUF_MEM* mem = maybeBio.value;\n      kj::ArrayPtr<kj::byte> source(reinterpret_cast<kj::byte*>(mem->data), mem->length);\n      if (source.size() > 0) {\n        return jsg::JsUint8Array::create(js, source);\n      } else {\n        return jsg::JsUint8Array::create(js, 0);\n      }\n    }\n\n    JSG_FAIL_REQUIRE(Error, \"Failed to export key\");\n  }\n\n  SubtleCrypto::ExportKeyData exportKey(jsg::Lock& js, kj::StringPtr format) const override final {\n    if (format == \"jwk\") {\n      auto res = toJwk(key, isPrivate ? KeyType::PRIVATE : KeyType::PUBLIC);\n      JSG_REQUIRE(res.kty != \"INVALID\"_kj, Error, \"Key type is invalid for JWK export\");\n      return kj::mv(res);\n    }\n\n    // exportKeyExt returns JsUint8Array. We need to wrap it in a JsRef<JsArrayBuffer>\n    // since ExportKeyData is OneOf<JsRef<JsArrayBuffer>, JsonWebKey>.\n    return jsg::JsArrayBuffer::create(js, exportKeyExt(js, format, \"pkcs8\"_kj).asArrayPtr())\n        .addRef(js);\n  }\n\n  bool equals(const CryptoKey::Impl& other) const override final {\n    KJ_IF_SOME(o, kj::dynamicDowncastIfAvailable<const AsymmetricKey>(other)) {\n      return EVP_PKEY_cmp(key.get(), o.key.get());\n    }\n    // TODO(later): Currently, this only compares keys using the ncrypto::EVPKeyPointer.\n    // If the \"other\" impl happens to be from the web crypto impl that does not use\n    // this AsymmetricKey impl then the comparison will be false. We can support both\n    // cases but for now, skip it.\n    return false;\n  }\n\n  kj::StringPtr getType() const override {\n    return isPrivate ? \"private\"_kj : \"public\"_kj;\n  }\n\n  kj::Own<AsymmetricKey> cloneAsPublicKey() {\n    if (!key) return kj::Own<AsymmetricKey>();\n    auto cloned = key.clone();\n    if (!cloned) return kj::Own<AsymmetricKey>();\n    return NewPublic(kj::mv(cloned));\n  }\n\n  operator const ncrypto::EVPKeyPointer&() const {\n    return key;\n  }\n\n private:\n  ncrypto::EVPKeyPointer key;\n  bool isPrivate;\n};\n\nint getCurveFromName(kj::StringPtr name) {\n  int nid = EC_curve_nist2nid(name.begin());\n  if (nid == NID_undef) nid = OBJ_sn2nid(name.begin());\n  return nid;\n}\n}  // namespace\n\nkj::OneOf<kj::String, jsg::JsArrayBuffer, SubtleCrypto::JsonWebKey> CryptoImpl::exportKey(\n    jsg::Lock& js, jsg::Ref<CryptoKey> key, jsg::Optional<KeyExportOptions> options) {\n  JSG_REQUIRE(key->getExtractable(), TypeError, \"Unable to export non-extractable key\");\n  auto& opts = JSG_REQUIRE_NONNULL(options, TypeError, \"Options must be an object\");\n\n  kj::StringPtr format = JSG_REQUIRE_NONNULL(opts.format, TypeError, \"Missing format option\");\n\n  auto convertExportKeyData = [&](SubtleCrypto::ExportKeyData&& exportData)\n      -> kj::OneOf<kj::String, jsg::JsArrayBuffer, SubtleCrypto::JsonWebKey> {\n    KJ_SWITCH_ONEOF(exportData) {\n      KJ_CASE_ONEOF(buf, jsg::JsRef<jsg::JsArrayBuffer>) {\n        return buf.getHandle(js);\n      }\n      KJ_CASE_ONEOF(jwk, SubtleCrypto::JsonWebKey) {\n        return kj::mv(jwk);\n      }\n    }\n    KJ_UNREACHABLE;\n  };\n\n  if (format == \"jwk\"_kj) {\n    // When format is jwk, all other options are ignored.\n    return convertExportKeyData(key->impl->exportKey(js, format));\n  }\n\n  if (key->getType() == \"secret\"_kj) {\n    // For secret keys, we only pay attention to the format option, which will be\n    // one of either \"buffer\" or \"jwk\". The \"buffer\" option correlates to the \"raw\"\n    // format in Web Crypto. The \"jwk\" option is handled above.\n    JSG_REQUIRE(format == \"buffer\"_kj, TypeError, \"Invalid format for secret key export: \", format);\n    return convertExportKeyData(key->impl->exportKey(js, \"raw\"_kj));\n  }\n\n  kj::StringPtr type = JSG_REQUIRE_NONNULL(opts.type, TypeError, \"Missing type option\");\n  auto data =\n      key->impl->exportKeyExt(js, format, type, kj::mv(opts.cipher), kj::mv(opts.passphrase));\n  if (format == \"pem\"_kj) {\n    // TODO(perf): As a later performance optimization, change this so that it doesn't copy.\n    return kj::str(data.asArrayPtr().asChars());\n  }\n  // exportKeyExt returns JsUint8Array; copy to ArrayBuffer for the Node.js TS layer.\n  return jsg::JsArrayBuffer::create(js, data.asArrayPtr());\n}\n\nbool CryptoImpl::equals(jsg::Lock& js, jsg::Ref<CryptoKey> key, jsg::Ref<CryptoKey> otherKey) {\n  return *key == *otherKey;\n}\n\nCryptoKey::AsymmetricKeyDetails CryptoImpl::getAsymmetricKeyDetail(\n    jsg::Lock& js, jsg::Ref<CryptoKey> key) {\n  JSG_REQUIRE(key->getType() != \"secret\"_kj, Error, \"Secret keys do not have asymmetric details\");\n  return key->getAsymmetricKeyDetails(js);\n}\n\nkj::StringPtr CryptoImpl::getAsymmetricKeyType(jsg::Lock& js, jsg::Ref<CryptoKey> key) {\n  static const std::map<kj::StringPtr, kj::StringPtr> mapping{\n    {\"RSASSA-PKCS1-v1_5\", \"rsa\"},\n    {\"RSA-PSS\", \"rsa\"},\n    {\"RSA-OAEP\", \"rsa\"},\n    {\"ECDSA\", \"ec\"},\n    {\"Ed25519\", \"ed25519\"},\n    {\"NODE-ED25519\", \"ed25519\"},\n    {\"ECDH\", \"ecdh\"},\n    {\"X25519\", \"x25519\"},\n  };\n  JSG_REQUIRE(\n      key->getType() != \"secret\"_kj, TypeError, \"Secret key does not have an asymmetric type\");\n  auto name = key->getAlgorithmName();\n  auto found = mapping.find(name);\n  return found != mapping.end() ? found->second : name;\n}\n\njsg::Ref<CryptoKey> CryptoImpl::createSecretKey(jsg::Lock& js, jsg::JsBufferSource keyData) {\n  // The keyData we receive here should be an exclusive copy of the key data.\n  // It will have been copied on the JS side before being passed to this function.\n  // We copy the raw bytes into a kj::Array for persistent storage.\n  return js.alloc<CryptoKey>(kj::heap<SecretKey>(keyData.copy()));\n}\n\nnamespace {\nstd::optional<ncrypto::EVPKeyPointer> tryParsingPrivate(jsg::Lock& js,\n    const CryptoImpl::CreateAsymmetricKeyOptions& options,\n    kj::ArrayPtr<const kj::byte> buffer) {\n  // As a private key the format can be either 'pem' or 'der',\n  // while type can be one of `pkcs1`, `pkcs8`, or `sec1`.\n  // The type is only required when format is 'der'.\n\n  auto format =\n      trySelectKeyFormat(options.format).orDefault(ncrypto::EVPKeyPointer::PKFormatType::PEM);\n\n  auto enc = ncrypto::EVPKeyPointer::PKEncodingType::PKCS8;\n  KJ_IF_SOME(type, options.type) {\n    enc = trySelectKeyEncoding(type).orDefault(enc);\n  }\n\n  ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config(false, format, enc);\n\n  KJ_IF_SOME(passphrase, options.passphrase) {\n    // TODO(later): Avoid using DataPointer for passphrase... so we\n    // can avoid the copy...\n    auto passphrasePtr = passphrase.getHandle(js).asArrayPtr();\n    auto dp = ncrypto::DataPointer::Alloc(passphrasePtr.size());\n    kj::ArrayPtr<kj::byte> ptr(dp.get<kj::byte>(), dp.size());\n    ptr.copyFrom(passphrasePtr);\n    config.passphrase = kj::mv(dp);\n  }\n\n  auto result = ncrypto::EVPKeyPointer::TryParsePrivateKey(config, ToNcryptoBuffer(buffer));\n\n  if (result.has_value) return kj::mv(result.value);\n  return std::nullopt;\n}\n}  // namespace\n\njsg::Ref<CryptoKey> CryptoImpl::createPrivateKey(\n    jsg::Lock& js, CreateAsymmetricKeyOptions options) {\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n\n  // Unlike with Web Crypto, where the CryptoKey being created is always\n  // algorithm specific, here we will create a generic private key impl\n  // that can be used for multiple kinds of operations.\n\n  KJ_SWITCH_ONEOF(options.key) {\n    KJ_CASE_ONEOF(bs, jsg::JsRef<jsg::JsBufferSource>) {\n      JSG_REQUIRE(options.format == \"pem\"_kj || options.format == \"der\"_kj, TypeError,\n          \"Invalid format for private key creation\");\n\n      auto bufferPtr = bs.getHandle(js).asArrayPtr();\n      if (auto maybePrivate = tryParsingPrivate(js, options, bufferPtr)) {\n        return js.alloc<CryptoKey>(AsymmetricKey::NewPrivate(kj::mv(maybePrivate.value())));\n      }\n\n      JSG_FAIL_REQUIRE(Error, \"Failed to parse private key\");\n    }\n    KJ_CASE_ONEOF(jwk, SubtleCrypto::JsonWebKey) {\n      JSG_REQUIRE(options.format == \"jwk\"_kj, TypeError, \"Invalid format for JWK key creation\");\n\n      if (auto key = fromJwk(jwk, KeyType::PRIVATE)) {\n        return js.alloc<CryptoKey>(AsymmetricKey::NewPrivate(kj::mv(key)));\n      }\n\n      JSG_FAIL_REQUIRE(Error, \"JWK private key import is not implemented for this key type\");\n    }\n    KJ_CASE_ONEOF(key, jsg::Ref<api::CryptoKey>) {\n      // This path shouldn't be reachable.\n      JSG_FAIL_REQUIRE(TypeError, \"Invalid key data\");\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\njsg::Ref<CryptoKey> CryptoImpl::createPublicKey(jsg::Lock& js, CreateAsymmetricKeyOptions options) {\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n\n  KJ_SWITCH_ONEOF(options.key) {\n    KJ_CASE_ONEOF(bs, jsg::JsRef<jsg::JsBufferSource>) {\n      JSG_REQUIRE(options.format == \"pem\"_kj || options.format == \"der\"_kj, TypeError,\n          \"Invalid format for public key creation\");\n\n      auto bufferPtr = bs.getHandle(js).asArrayPtr();\n\n      // As a public key the format can be either 'pem' or 'der',\n      // while type can be one of either `pkcs1` or `spki`\n\n      {\n        // It is necessary to pop the error on return before we attempt\n        // to try parsing as a private key if the public key parsing fails.\n        ncrypto::MarkPopErrorOnReturn markPopErrorOnReturn;\n\n        auto format =\n            trySelectKeyFormat(options.format).orDefault(ncrypto::EVPKeyPointer::PKFormatType::PEM);\n\n        auto enc = ncrypto::EVPKeyPointer::PKEncodingType::PKCS1;\n        KJ_IF_SOME(type, options.type) {\n          enc = trySelectKeyEncoding(type).orDefault(enc);\n        }\n\n        ncrypto::EVPKeyPointer::PublicKeyEncodingConfig config(true, format, enc);\n\n        auto result =\n            ncrypto::EVPKeyPointer::TryParsePublicKey(config, ToNcryptoBuffer(bufferPtr.asConst()));\n\n        if (result.has_value) {\n          return js.alloc<CryptoKey>(AsymmetricKey::NewPublic(kj::mv(result.value)));\n        }\n      }\n\n      // Otherwise, let's try parsing as a private key...\n      if (auto maybePrivate = tryParsingPrivate(js, options, bufferPtr)) {\n        return js.alloc<CryptoKey>(AsymmetricKey::NewPublic(kj::mv(maybePrivate.value())));\n      }\n\n      JSG_FAIL_REQUIRE(Error, \"Failed to parse public key\");\n    }\n    KJ_CASE_ONEOF(jwk, SubtleCrypto::JsonWebKey) {\n      JSG_REQUIRE(options.format == \"jwk\"_kj, TypeError, \"Invalid format for JWK key creation\");\n\n      if (auto key = fromJwk(jwk, KeyType::PUBLIC)) {\n        return js.alloc<CryptoKey>(AsymmetricKey::NewPublic(kj::mv(key)));\n      }\n\n      JSG_FAIL_REQUIRE(Error, \"JWK public key import is not implemented for this key type\");\n    }\n    KJ_CASE_ONEOF(key, jsg::Ref<api::CryptoKey>) {\n      JSG_REQUIRE(key->getType() == \"private\"_kj, TypeError,\n          \"Cannot create public key from secret or public key\");\n\n      // TODO(later): For now, this only works with crypto keys that are created using\n      // AsymmetricKey above. Web crypto private keys won't work here.\n      KJ_IF_SOME(impl, kj::dynamicDowncastIfAvailable<AsymmetricKey>(*key->impl.get())) {\n        return js.alloc<CryptoKey>(impl.cloneAsPublicKey());\n      }\n\n      JSG_FAIL_REQUIRE(Error, \"Failed to derive public key from private key\");\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\nCryptoKeyPair CryptoImpl::generateRsaKeyPair(jsg::Lock& js, RsaKeyPairOptions options) {\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n\n  auto ctx = ncrypto::EVPKeyCtxPointer::NewFromID(\n      options.type == \"rsa-pss\" ? EVP_PKEY_RSA_PSS : EVP_PKEY_RSA);\n\n  JSG_REQUIRE(ctx, Error, \"Failed to create keygen context\");\n  JSG_REQUIRE(ctx.initForKeygen(), Error, \"Failed to initialize keygen context\");\n  JSG_REQUIRE(ctx.setRsaKeygenBits(options.modulusLength), Error, \"Failed to set modulus length\");\n\n  if (options.publicExponent != ncrypto::EVPKeyCtxPointer::kDefaultRsaExponent) {\n    auto bn = ncrypto::BignumPointer::New();\n    JSG_REQUIRE(bn, Error, \"Failed to initialize public exponent\");\n    JSG_REQUIRE(bn.setWord(options.publicExponent) && ctx.setRsaKeygenPubExp(kj::mv(bn)), Error,\n        \"Failed to set public exponent\");\n  }\n\n  // TODO(later): BoringSSL does not support generating RSA-PSS this\n  // way... later see if there's an alternative approach.\n  // if (options.type == \"rsa-pss\") {\n\n  //   KJ_IF_SOME(hash, options.hashAlgorithm) {\n  //     std::string_view hashName(hash.begin(), hash.size());\n  //     auto nid = ncrypto::getDigestByName(hashName);\n  //     JSG_REQUIRE(nid != nullptr, Error, \"Unsupported hash algorithm\");\n  //     JSG_REQUIRE(ctx.setRsaPssKeygenMd(nid), Error, \"Failed to set hash algorithm\");\n  //   }\n\n  //   KJ_IF_SOME(hash, options.mgf1HashAlgorithm) {\n  //     std::string_view mgf1hashName(hash.begin(), hash.size());\n  //     auto mgf1_nid = ncrypto::getDigestByName(mgf1hashName);\n  //     if (mgf1_nid == nullptr) {\n  //       KJ_IF_SOME(hash, options.hashAlgorithm) {\n  //         std::string_view hashName(hash.begin(), hash.size());\n  //         mgf1_nid = ncrypto::getDigestByName(hashName);\n  //       }\n  //     }\n  //     if (mgf1_nid != nullptr) {\n  //       JSG_REQUIRE(ctx.setRsaPssKeygenMgf1Md(mgf1_nid), Error,\n  //                   \"Failed to set MGF1 hash algorithm\");\n  //     }\n  //   }\n\n  //   KJ_IF_SOME(len, options.saltLength) {\n  //     JSG_REQUIRE(ctx.setRsaPssSaltlen(len), Error, \"Failed to set salt length\");\n  //   }\n  // }\n\n  // Generate the key\n  EVP_PKEY* pkey = nullptr;\n  JSG_REQUIRE(EVP_PKEY_keygen(ctx.get(), &pkey), Error, \"Failed to generate key\");\n\n  auto generated = ncrypto::EVPKeyPointer(pkey);\n\n  auto publicKey = AsymmetricKey::NewPublic(generated.clone());\n  JSG_REQUIRE(publicKey, Error, \"Failed to create public key\");\n  auto privateKey = AsymmetricKey::NewPrivate(kj::mv(generated));\n  JSG_REQUIRE(privateKey, Error, \"Failed to create private key\");\n\n  return CryptoKeyPair{\n    .publicKey = js.alloc<CryptoKey>(kj::mv(publicKey)),\n    .privateKey = js.alloc<CryptoKey>(kj::mv(privateKey)),\n  };\n}\n\nCryptoKeyPair CryptoImpl::generateDsaKeyPair(jsg::Lock& js, DsaKeyPairOptions options) {\n  // TODO(later): BoringSSL does not implement DSA key generation using\n  // EVP_PKEY_keygen. We would need to implement this using the DSA-specific\n  // APIs which get a bit complicated when it comes to using a user-provided\n  // modulus length and divisor length. For now, leave this un-implemented.\n\n  // auto ctx = ncrypto::EVPKeyCtxPointer::NewFromID(EVP_PKEY_DSA);\n\n  // JSG_REQUIRE(ctx, Error, \"Failed to create keygen context\");\n  // JSG_REQUIRE(ctx.initForKeygen(), Error, \"Failed to initialize keygen context\");\n\n  // uint32_t bits = options.modulusLength;\n  // std::optional<uint32_t> q_bits = std::nullopt;\n  // KJ_IF_SOME(d, options.divisorLength) {\n  //   q_bits = d;\n  // }\n\n  // JSG_REQUIRE(ctx.setDsaParameters(bits, q_bits), Error, \"Failed to set DSA parameters\");\n\n  // // Generate the key\n  // EVP_PKEY* pkey = nullptr;\n  // JSG_REQUIRE(EVP_PKEY_keygen(ctx.get(), &pkey), Error, \"Failed to generate key\");\n\n  // auto generated = ncrypto::EVPKeyPointer(pkey);\n\n  // auto publicKey = AsymmetricKey::NewPublic(generated.clone());\n  // JSG_REQUIRE(publicKey, Error, \"Failed to create public key\");\n  // auto privateKey = AsymmetricKey::NewPrivate(kj::mv(generated));\n  // JSG_REQUIRE(privateKey, Error, \"Failed to create private key\");\n\n  // return CryptoKeyPair {\n  //   .publicKey = js.alloc<CryptoKey>(kj::mv(publicKey)),\n  //   .privateKey = js.alloc<CryptoKey>(kj::mv(privateKey)),\n  // };\n\n  JSG_FAIL_REQUIRE(Error, \"Not yet implemented\");\n}\n\nCryptoKeyPair CryptoImpl::generateEcKeyPair(jsg::Lock& js, EcKeyPairOptions options) {\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n\n  auto nid = getCurveFromName(options.namedCurve);\n  JSG_REQUIRE(nid != NID_undef, Error, \"Invalid or unsupported curve\");\n\n  auto paramEncoding =\n      options.paramEncoding == \"named\"_kj ? OPENSSL_EC_NAMED_CURVE : OPENSSL_EC_EXPLICIT_CURVE;\n\n  auto ecPrivateKey = ncrypto::ECKeyPointer::NewByCurveName(nid);\n  JSG_REQUIRE(ecPrivateKey, Error, \"Failed to initialize key\");\n  JSG_REQUIRE(ecPrivateKey.generate(), Error, \"Failed to generate private key\");\n\n  EC_KEY_set_enc_flags(ecPrivateKey, paramEncoding);\n\n  auto ecPublicKey = ncrypto::ECKeyPointer::NewByCurveName(nid);\n  JSG_REQUIRE(EC_KEY_set_public_key(ecPublicKey, EC_KEY_get0_public_key(ecPrivateKey)), Error,\n      \"Failed to derive public key\");\n\n  auto privateKey = ncrypto::EVPKeyPointer::New();\n  JSG_REQUIRE(privateKey.assign(ecPrivateKey), Error, \"Failed to assign private key\");\n\n  auto publicKey = ncrypto::EVPKeyPointer::New();\n  JSG_REQUIRE(publicKey.assign(ecPublicKey), Error, \"Failed to assign public key\");\n\n  ecPrivateKey.release();\n  ecPublicKey.release();\n\n  auto pubKey = AsymmetricKey::NewPublic(kj::mv(publicKey));\n  JSG_REQUIRE(pubKey, Error, \"Failed to create public key\");\n  auto pvtKey = AsymmetricKey::NewPrivate(kj::mv(privateKey));\n  JSG_REQUIRE(pvtKey, Error, \"Failed to create private key\");\n\n  return CryptoKeyPair{\n    .publicKey = js.alloc<CryptoKey>(kj::mv(pubKey)),\n    .privateKey = js.alloc<CryptoKey>(kj::mv(pvtKey)),\n  };\n}\n\nCryptoKeyPair CryptoImpl::generateEdKeyPair(jsg::Lock& js, EdKeyPairOptions options) {\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n\n  auto nid = ([&] {\n    if (options.type == \"ed25519\") {\n      return EVP_PKEY_ED25519;\n    }\n    if (options.type == \"x25519\") {\n      return EVP_PKEY_X25519;\n    }\n    return NID_undef;\n  })();\n  JSG_REQUIRE(nid != NID_undef, Error, \"Invalid or unsupported curve\");\n\n  auto ctx = ncrypto::EVPKeyCtxPointer::NewFromID(nid);\n  JSG_REQUIRE(ctx, Error, \"Failed to create keygen context\");\n  JSG_REQUIRE(ctx.initForKeygen(), Error, \"Failed to initialize keygen\");\n\n  // Generate the key\n  EVP_PKEY* pkey = nullptr;\n  JSG_REQUIRE(EVP_PKEY_keygen(ctx.get(), &pkey), Error, \"Failed to generate key\");\n\n  auto generated = ncrypto::EVPKeyPointer(pkey);\n\n  auto publicKey = AsymmetricKey::NewPublic(generated.clone());\n  JSG_REQUIRE(publicKey, Error, \"Failed to create public key\");\n  auto privateKey = AsymmetricKey::NewPrivate(kj::mv(generated));\n  JSG_REQUIRE(privateKey, Error, \"Failed to create private key\");\n\n  return CryptoKeyPair{\n    .publicKey = js.alloc<CryptoKey>(kj::mv(publicKey)),\n    .privateKey = js.alloc<CryptoKey>(kj::mv(privateKey)),\n  };\n}\n\nCryptoKeyPair CryptoImpl::generateDhKeyPair(jsg::Lock& js, DhKeyPairOptions options) {\n\n  // TODO(soon): Older versions of boringssl+fips do not support EVP with\n  // DH key pairs that are required to make the following work. A compile\n  // flag is used to disable the mechanism in ncrypto, causing the calls\n  // to `ncrypto::EVPKeyPointer::NewDH to return an empty EVPKeyPointer.\n  // While the ideal situation would be for us to adopt a newer version\n  // of boringssl+fips that *does* support EVP+DH, we can possibly work\n  // around the issue by implementing an alternative that uses the older\n  // DH_* specific APIs like the rest of our DH implementation does.\n\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n\n  static constexpr uint32_t kStandardizedGenerator = 2;\n\n  ncrypto::EVPKeyPointer key_params;\n  auto generator = options.generator.orDefault(kStandardizedGenerator);\n\n  KJ_SWITCH_ONEOF(options.primeOrGroup) {\n    KJ_CASE_ONEOF(group, kj::String) {\n      std::string_view group_name(group.begin(), group.size());\n      auto found = ncrypto::DHPointer::FindGroup(group_name);\n      JSG_REQUIRE(found, Error, \"Invalid or unsupported group\");\n\n      auto bn_g = ncrypto::BignumPointer::New();\n      JSG_REQUIRE(bn_g && bn_g.setWord(generator), Error, \"Failed to set generator\");\n\n      auto dh = ncrypto::DHPointer::New(kj::mv(found), kj::mv(bn_g));\n      JSG_REQUIRE(dh, Error, \"Failed to create DH key\");\n\n      key_params = ncrypto::EVPKeyPointer::NewDH(kj::mv(dh));\n    }\n    KJ_CASE_ONEOF(prime, jsg::JsRef<jsg::JsBufferSource>) {\n      auto primePtr = prime.getHandle(js).asArrayPtr();\n      ncrypto::BignumPointer bn(primePtr.begin(), primePtr.size());\n\n      auto bn_g = ncrypto::BignumPointer::New();\n      JSG_REQUIRE(bn_g && bn_g.setWord(generator), Error, \"Failed to set generator\");\n\n      auto dh = ncrypto::DHPointer::New(kj::mv(bn), kj::mv(bn_g));\n      JSG_REQUIRE(dh, Error, \"Failed to create DH key\");\n\n      key_params = ncrypto::EVPKeyPointer::NewDH(kj::mv(dh));\n    }\n    KJ_CASE_ONEOF(length, uint32_t) {\n      // TODO(later): BoringSSL appears to not implement DH key generation\n      // from a prime length the same way Node.js does. For now, defer this\n      // and come back to implement later.\n      JSG_FAIL_REQUIRE(Error, \"Generating DH keys from a prime length is not yet implemented\");\n    }\n  }\n\n  JSG_REQUIRE(key_params, Error, \"Failed to create keygen context\");\n  auto ctx = key_params.newCtx();\n  JSG_REQUIRE(ctx, Error, \"Failed to create keygen context\");\n  JSG_REQUIRE(ctx.initForKeygen(), Error, \"Failed to initialize keygen context\");\n\n  // Generate the key\n  EVP_PKEY* pkey = nullptr;\n  JSG_REQUIRE(EVP_PKEY_keygen(ctx.get(), &pkey), Error, \"Failed to generate key\");\n\n  auto generated = ncrypto::EVPKeyPointer(pkey);\n\n  auto publicKey = AsymmetricKey::NewPublic(generated.clone());\n  JSG_REQUIRE(publicKey, Error, \"Failed to create public key\");\n  auto privateKey = AsymmetricKey::NewPrivate(kj::mv(generated));\n  JSG_REQUIRE(privateKey, Error, \"Failed to create private key\");\n\n  return CryptoKeyPair{\n    .publicKey = js.alloc<CryptoKey>(kj::mv(publicKey)),\n    .privateKey = js.alloc<CryptoKey>(kj::mv(privateKey)),\n  };\n}\n\njsg::JsUint8Array CryptoImpl::statelessDH(\n    jsg::Lock& js, jsg::Ref<CryptoKey> privateKey, jsg::Ref<CryptoKey> publicKey) {\n  auto privateKeyAlg = privateKey->getAlgorithmName();\n  auto publicKeyAlg = publicKey->getAlgorithmName();\n  KJ_ASSERT(privateKeyAlg == \"dh\"_kj || privateKeyAlg == \"ec\"_kj || privateKeyAlg == \"x25519\"_kj,\n      \"Invalid private key algorithm\");\n  KJ_ASSERT(publicKeyAlg == \"dh\"_kj || publicKeyAlg == \"ec\"_kj || publicKeyAlg == \"x25519\"_kj,\n      \"Invalid public key algorithm\");\n  KJ_ASSERT(privateKeyAlg == publicKeyAlg, \"Mismatched public and private key types\");\n  KJ_IF_SOME(pubKey, kj::dynamicDowncastIfAvailable<AsymmetricKey>(*publicKey->impl)) {\n    KJ_IF_SOME(pvtKey, kj::dynamicDowncastIfAvailable<AsymmetricKey>(*privateKey->impl)) {\n      auto data = ncrypto::DHPointer::stateless(pubKey, pvtKey);\n      JSG_REQUIRE(data, Error, \"Failed to derive shared diffie-hellman secret\");\n      kj::ArrayPtr<const kj::byte> ptr(static_cast<const kj::byte*>(data.get()), data.size());\n      return jsg::JsUint8Array::create(js, ptr);\n    }\n  }\n  JSG_FAIL_REQUIRE(Error, \"Unsupported keys for stateless diffie-hellman\");\n}\n\nkj::Maybe<const ncrypto::EVPKeyPointer&> CryptoImpl::tryGetKey(jsg::Ref<CryptoKey>& key) {\n  KJ_IF_SOME(key, kj::dynamicDowncastIfAvailable<AsymmetricKey>(*key->impl)) {\n    const ncrypto::EVPKeyPointer& evp = key;\n    return evp;\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::ArrayPtr<const kj::byte>> CryptoImpl::tryGetSecretKeyData(jsg::Ref<CryptoKey>& key) {\n  KJ_IF_SOME(secret, kj::dynamicDowncastIfAvailable<SecretKey>(*key->impl)) {\n    return secret.rawKeyData();\n  }\n  return kj::none;\n}\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/crypto.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#include \"crypto.h\"\n\n#include <workerd/api/crypto/digest.h>\n#include <workerd/api/crypto/impl.h>\n#include <workerd/api/crypto/kdf.h>\n#include <workerd/api/crypto/prime.h>\n#include <workerd/api/crypto/spkac.h>\n#include <workerd/jsg/jsg.h>\n\n#include <ncrypto.h>\n\n#include <string_view>\n\nusing namespace std::string_view_literals;\n\nnamespace workerd::api::node {\n\n// ======================================================================================\n#pragma region KDF\n\njsg::JsArrayBuffer CryptoImpl::getHkdf(jsg::Lock& js,\n    kj::String hash,\n    kj::Array<const kj::byte> key,\n    kj::Array<const kj::byte> salt,\n    kj::Array<const kj::byte> info,\n    uint32_t length) {\n  // The Node.js version of the HKDF is a bit different from the Web Crypto API\n  // version. For one, the length here specifies the number of bytes, whereas\n  // in Web Crypto the length is expressed in the number of bits. Second, the\n  // Node.js implementation allows for a broader range of possible digest\n  // algorithms whereas the Web Crypto API only allows for a few specific ones.\n  // Third, the Node.js implementation enforces max size limits on the key,\n  // salt, and info parameters. Fourth, the Web Crypto API relies on the key\n  // being a CryptoKey object, whereas the Node.js implementation here takes a\n  // raw byte array.\n  auto digest = ncrypto::getDigestByName(hash.begin());\n\n  JSG_REQUIRE_NONNULL(digest, TypeError, \"Invalid Hkdf digest: \", hash);\n  JSG_REQUIRE(info.size() <= INT32_MAX, RangeError, \"Hkdf failed: info is too large\");\n  JSG_REQUIRE(salt.size() <= INT32_MAX, RangeError, \"Hkdf failed: salt is too large\");\n  JSG_REQUIRE(key.size() <= INT32_MAX, RangeError, \"Hkdf failed: key is too large\");\n  JSG_REQUIRE(ncrypto::checkHkdfLength(digest, length), RangeError, \"Invalid Hkdf key length\");\n\n  return JSG_REQUIRE_NONNULL(api::hkdf(js, length, digest, key, salt, info), Error, \"Hkdf failed\");\n}\n\njsg::JsArrayBuffer CryptoImpl::getPbkdf(jsg::Lock& js,\n    kj::Array<const kj::byte> password,\n    kj::Array<const kj::byte> salt,\n    uint32_t num_iterations,\n    uint32_t keylen,\n    kj::String name) {\n  // The Node.js version of the PBKDF2 is a bit different from the Web Crypto API.\n  // For one, the Node.js implementation allows for a broader range of possible\n  // digest algorithms whereas the Web Crypto API only allows for a few specific ones.\n  // Second, the Node.js implementation enforces max size limits on the password and\n  // salt parameters.\n  auto digest = ncrypto::getDigestByName(name.begin());\n\n  JSG_REQUIRE_NONNULL(\n      digest, TypeError, \"Invalid Pbkdf2 digest: \", name, internalDescribeOpensslErrors());\n  JSG_REQUIRE(password.size() <= INT32_MAX, RangeError, \"Pbkdf2 failed: password is too large\");\n  JSG_REQUIRE(salt.size() <= INT32_MAX, RangeError, \"Pbkdf2 failed: salt is too large\");\n  // Note: The user could DoS us by selecting a very high iteration count. As with the Web Crypto\n  // API, intentionally limit the maximum iteration count.\n  checkPbkdfLimits(js, num_iterations);\n\n  return JSG_REQUIRE_NONNULL(\n      api::pbkdf2(js, keylen, num_iterations, digest, password, salt), Error, \"Pbkdf2 failed\");\n}\n\njsg::JsArrayBuffer CryptoImpl::getScrypt(jsg::Lock& js,\n    kj::Array<const kj::byte> password,\n    kj::Array<const kj::byte> salt,\n    uint32_t N,\n    uint32_t r,\n    uint32_t p,\n    uint32_t maxmem,\n    uint32_t keylen) {\n  JSG_REQUIRE(password.size() <= INT32_MAX, RangeError, \"Scrypt failed: password is too large\");\n  JSG_REQUIRE(salt.size() <= INT32_MAX, RangeError, \"Scrypt failed: salt is too large\");\n\n  return JSG_REQUIRE_NONNULL(\n      api::scrypt(js, keylen, N, r, p, maxmem, password, salt), Error, \"Scrypt failed\");\n}\n#pragma endregion  // KDF\n\n// ======================================================================================\n#pragma region SPKAC\n\nbool CryptoImpl::verifySpkac(kj::Array<const kj::byte> input) {\n  return workerd::api::verifySpkac(input);\n}\n\nkj::Maybe<jsg::JsUint8Array> CryptoImpl::exportPublicKey(\n    jsg::Lock& js, kj::Array<const kj::byte> input) {\n  return workerd::api::exportPublicKey(js, input);\n}\n\nkj::Maybe<jsg::JsUint8Array> CryptoImpl::exportChallenge(\n    jsg::Lock& js, kj::Array<const kj::byte> input) {\n  return workerd::api::exportChallenge(js, input);\n}\n#pragma endregion  // SPKAC\n\n// ======================================================================================\n#pragma region Primes\n\njsg::JsArrayBuffer CryptoImpl::randomPrime(jsg::Lock& js,\n    uint32_t size,\n    bool safe,\n    jsg::Optional<kj::Array<kj::byte>> add_buf,\n    jsg::Optional<kj::Array<kj::byte>> rem_buf) {\n  return workerd::api::randomPrime(js, size, safe,\n      add_buf.map([](kj::Array<kj::byte>& buf) { return buf.asPtr(); }),\n      rem_buf.map([](kj::Array<kj::byte>& buf) { return buf.asPtr(); }));\n}\n\nbool CryptoImpl::checkPrimeSync(kj::Array<kj::byte> bufferView, uint32_t num_checks) {\n  return workerd::api::checkPrime(bufferView.asPtr(), num_checks);\n}\n#pragma endregion  // Primes\n\n// ======================================================================================\n#pragma region Hmac\njsg::Ref<CryptoImpl::HmacHandle> CryptoImpl::HmacHandle::constructor(\n    jsg::Lock& js, kj::String algorithm, kj::OneOf<kj::Array<kj::byte>, jsg::Ref<CryptoKey>> key) {\n  KJ_SWITCH_ONEOF(key) {\n    KJ_CASE_ONEOF(key_data, kj::Array<kj::byte>) {\n      return js.alloc<HmacHandle>(HmacContext(js, algorithm, key_data.asPtr()));\n    }\n    KJ_CASE_ONEOF(key, jsg::Ref<CryptoKey>) {\n      return js.alloc<HmacHandle>(HmacContext(js, algorithm, key->impl.get()));\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nint CryptoImpl::HmacHandle::update(kj::Array<kj::byte> data) {\n  ctx.update(data);\n  return 1;  // This just always returns 1 no matter what.\n}\n\njsg::JsUint8Array CryptoImpl::HmacHandle::digest(jsg::Lock& js) {\n  return ctx.digest(js);\n}\n\njsg::JsUint8Array CryptoImpl::HmacHandle::oneshot(jsg::Lock& js,\n    kj::String algorithm,\n    CryptoImpl::HmacHandle::KeyParam key,\n    kj::Array<kj::byte> data) {\n  KJ_SWITCH_ONEOF(key) {\n    KJ_CASE_ONEOF(key_data, kj::Array<kj::byte>) {\n      HmacContext ctx(js, algorithm, key_data.asPtr());\n      ctx.update(data);\n      return ctx.digest(js);\n    }\n    KJ_CASE_ONEOF(key, jsg::Ref<CryptoKey>) {\n      HmacContext ctx(js, algorithm, key->impl.get());\n      ctx.update(data);\n      return ctx.digest(js);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid CryptoImpl::HmacHandle::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackFieldWithSize(\"digest\", ctx.size());\n}\n#pragma endregion  // Hmac\n\n// ======================================================================================\n#pragma region Hash\njsg::Ref<CryptoImpl::HashHandle> CryptoImpl::HashHandle::constructor(\n    jsg::Lock& js, kj::String algorithm, kj::Maybe<uint32_t> xofLen) {\n  return js.alloc<HashHandle>(HashContext(algorithm, xofLen));\n}\n\nint CryptoImpl::HashHandle::update(kj::Array<kj::byte> data) {\n  ctx.update(data);\n  return 1;\n}\n\njsg::JsUint8Array CryptoImpl::HashHandle::digest(jsg::Lock& js) {\n  return ctx.digest(js);\n}\n\njsg::Ref<CryptoImpl::HashHandle> CryptoImpl::HashHandle::copy(\n    jsg::Lock& js, kj::Maybe<uint32_t> xofLen) {\n  return js.alloc<HashHandle>(ctx.clone(js, kj::mv(xofLen)));\n}\n\nvoid CryptoImpl::HashHandle::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackFieldWithSize(\"digest\", ctx.size());\n}\n\njsg::JsUint8Array CryptoImpl::HashHandle::oneshot(\n    jsg::Lock& js, kj::String algorithm, kj::Array<kj::byte> data, kj::Maybe<uint32_t> xofLen) {\n  HashContext ctx(algorithm, xofLen);\n  ctx.update(data);\n  return ctx.digest(js);\n}\n#pragma endregion Hash\n\n// ======================================================================================\n#pragma region DiffieHellman\n\njsg::Ref<CryptoImpl::DiffieHellmanHandle> CryptoImpl::DiffieHellmanGroupHandle(\n    jsg::Lock& js, kj::String name) {\n  return js.alloc<DiffieHellmanHandle>(DiffieHellman(name));\n}\n\njsg::Ref<CryptoImpl::DiffieHellmanHandle> CryptoImpl::DiffieHellmanHandle::constructor(\n    jsg::Lock& js,\n    kj::OneOf<kj::Array<kj::byte>, int> sizeOrKey,\n    kj::OneOf<kj::Array<kj::byte>, int> generator) {\n  return js.alloc<DiffieHellmanHandle>(DiffieHellman(sizeOrKey, generator));\n}\n\nCryptoImpl::DiffieHellmanHandle::DiffieHellmanHandle(DiffieHellman dh): dh(kj::mv(dh)) {\n  verifyError = JSG_REQUIRE_NONNULL(this->dh.check(), Error, \"DiffieHellman init failed\");\n};\n\nvoid CryptoImpl::DiffieHellmanHandle::setPrivateKey(kj::Array<kj::byte> key) {\n  dh.setPrivateKey(key);\n}\n\nvoid CryptoImpl::DiffieHellmanHandle::setPublicKey(kj::Array<kj::byte> key) {\n  dh.setPublicKey(key);\n}\n\njsg::JsUint8Array CryptoImpl::DiffieHellmanHandle::getPublicKey(jsg::Lock& js) {\n  return dh.getPublicKey(js);\n}\n\njsg::JsUint8Array CryptoImpl::DiffieHellmanHandle::getPrivateKey(jsg::Lock& js) {\n  return dh.getPrivateKey(js);\n}\n\njsg::JsUint8Array CryptoImpl::DiffieHellmanHandle::getGenerator(jsg::Lock& js) {\n  return dh.getGenerator(js);\n}\n\njsg::JsUint8Array CryptoImpl::DiffieHellmanHandle::getPrime(jsg::Lock& js) {\n  return dh.getPrime(js);\n}\n\njsg::JsUint8Array CryptoImpl::DiffieHellmanHandle::computeSecret(\n    jsg::Lock& js, kj::Array<kj::byte> key) {\n  return dh.computeSecret(js, key);\n}\n\njsg::JsUint8Array CryptoImpl::DiffieHellmanHandle::generateKeys(jsg::Lock& js) {\n  return dh.generateKeys(js);\n}\n\nint CryptoImpl::DiffieHellmanHandle::getVerifyError() {\n  return verifyError;\n}\n#pragma endregion  // DiffieHellman\n\n// ======================================================================================\n#pragma region SignVerify\n\nnamespace {\njsg::JsUint8Array signFinal(jsg::Lock& js,\n    ncrypto::EVPMDCtxPointer&& mdctx,\n    const ncrypto::EVPKeyPointer& pkey,\n    int padding,\n    jsg::Optional<int> pss_salt_len) {\n\n  // The version of BoringSSL we use does not support DSA keys with EVP\n  // When signing/verification. This may change in the future.\n  JSG_REQUIRE(pkey.id() != EVP_PKEY_DSA, Error, \"Signing with DSA keys is not currently supported\");\n\n  auto data = mdctx.digestFinal(mdctx.getExpectedSize());\n  JSG_REQUIRE(data, Error, \"Failed to generate digest\");\n\n  auto sig = jsg::JsUint8Array::create(js, pkey.size());\n  ncrypto::Buffer<kj::byte> sig_buf{\n    .data = sig.asArrayPtr().begin(),\n    .len = sig.size(),\n  };\n\n  ncrypto::EVPKeyCtxPointer pkctx = pkey.newCtx();\n  JSG_REQUIRE(pkctx.initForSign(), Error, \"Failed to initialize signing context\");\n\n  if (pkey.isRsaVariant()) {\n    std::optional<int> maybeSaltLen = std::nullopt;\n    KJ_IF_SOME(len, pss_salt_len) {\n      maybeSaltLen = len;\n    }\n    JSG_REQUIRE(ncrypto::EVPKeyCtxPointer::setRsaPadding(pkctx.get(), padding, maybeSaltLen), Error,\n        \"Failed to set RSA parameters for signature\");\n  }\n\n  JSG_REQUIRE(pkctx.setSignatureMd(mdctx), Error, \"Failed to set signature digest\");\n  JSG_REQUIRE(pkctx.signInto(data, &sig_buf), Error, \"Failed to generate signature\");\n\n  if (sig_buf.len < sig.size()) {\n    return sig.slice(js, sig_buf.len);\n  }\n\n  return sig;\n}\n\nbool verifyFinal(jsg::Lock& js,\n    ncrypto::EVPMDCtxPointer&& mdctx,\n    const ncrypto::EVPKeyPointer& pkey,\n    jsg::JsBufferSource& signature,\n    int padding,\n    jsg::Optional<int> pss_salt_len) {\n\n  // The version of BoringSSL we use does not support DSA keys with EVP\n  // When signing/verification. This may change in the future.\n  JSG_REQUIRE(\n      pkey.id() != EVP_PKEY_DSA, Error, \"Verifying with DSA keys is not currently supported\");\n\n  auto data = mdctx.digestFinal(mdctx.getExpectedSize());\n  JSG_REQUIRE(data, Error, \"Failed to finalize signature verification\");\n\n  ncrypto::EVPKeyCtxPointer pkctx = pkey.newCtx();\n  JSG_REQUIRE(pkctx, Error, \"Failed to initialize key for verification\");\n\n  const int init_ret = pkctx.initForVerify();\n  JSG_REQUIRE(init_ret != -2, Error, \"Failed to initialize key for verification\");\n\n  if (pkey.isRsaVariant()) {\n    std::optional<int> maybeSaltLen = std::nullopt;\n    KJ_IF_SOME(len, pss_salt_len) {\n      maybeSaltLen = len;\n    }\n    JSG_REQUIRE(ncrypto::EVPKeyCtxPointer::setRsaPadding(pkctx.get(), padding, maybeSaltLen), Error,\n        \"Failed to set RSA parameters for signature\");\n  }\n\n  JSG_REQUIRE(pkctx.setSignatureMd(mdctx), Error,\n      \"Failed to set digest context for signature verification\");\n\n  ncrypto::Buffer<const kj::byte> sig{\n    .data = signature.asArrayPtr().begin(),\n    .len = signature.size(),\n  };\n\n  return pkctx.verify(sig, data);\n}\n\njsg::JsUint8Array convertSignatureToP1363(\n    jsg::Lock& js, const ncrypto::EVPKeyPointer& pkey, jsg::JsUint8Array&& signature) {\n  auto maybeRs = pkey.getBytesOfRS();\n  if (!maybeRs.has_value()) return kj::mv(signature);\n  unsigned int n = maybeRs.value();\n\n  auto ret = jsg::JsUint8Array::create(js, 2 * n);\n\n  ncrypto::Buffer<const unsigned char> sig_buffer{\n    .data = signature.asArrayPtr().begin(),\n    .len = signature.size(),\n  };\n\n  if (!ncrypto::extractP1363(sig_buffer, ret.asArrayPtr().begin(), n)) {\n    return kj::mv(signature);\n  }\n\n  return ret;\n}\n\njsg::JsUint8Array convertSignatureToDER(\n    jsg::Lock& js, const ncrypto::EVPKeyPointer& pkey, jsg::JsUint8Array&& signature) {\n  auto maybeRs = pkey.getBytesOfRS();\n  if (!maybeRs.has_value()) return kj::mv(signature);\n  unsigned int n = maybeRs.value();\n\n  if (signature.size() != 2 * n) {\n    return jsg::JsUint8Array::create(js, 0);\n  }\n\n  const kj::byte* sig_data = signature.asArrayPtr().begin();\n\n  auto asn1_sig = ncrypto::ECDSASigPointer::New();\n  JSG_REQUIRE(asn1_sig, Error, \"Internal error generating signature\");\n  ncrypto::BignumPointer r(sig_data, n);\n  JSG_REQUIRE(r, Error, \"Internal error generating signature\");\n  ncrypto::BignumPointer s(sig_data + n, n);\n  JSG_REQUIRE(s, Error, \"Internal error generating signature\");\n  JSG_REQUIRE(asn1_sig.setParams(kj::mv(r), kj::mv(s)), Error,\n      \"Internal error setting signature parameters\");\n\n  auto buf = asn1_sig.encode();\n  if (buf.len <= 0) [[unlikely]] {\n    return jsg::JsUint8Array::create(js, 0);\n  }\n\n  return jsg::JsUint8Array::create(js, kj::ArrayPtr<kj::byte>(buf.data, buf.len));\n}\n\nconst EVP_MD* maybeGetDigest(jsg::Optional<kj::String>& maybeAlgorithm) {\n  KJ_IF_SOME(alg, maybeAlgorithm) {\n    auto md = ncrypto::getDigestByName(alg.cStr());\n    JSG_REQUIRE(md != nullptr, Error, kj::str(\"Unknown digest: \", alg));\n    return md;\n  }\n  return nullptr;\n}\n}  // namespace\n\nCryptoImpl::SignHandle::SignHandle(ncrypto::EVPMDCtxPointer ctx)\n    : ctx(ncrypto::EVPMDCtxPointer(ctx.release())) {}\n\njsg::Ref<CryptoImpl::SignHandle> CryptoImpl::SignHandle::constructor(\n    jsg::Lock& js, kj::String algorithm) {\n  ncrypto::ClearErrorOnReturn clear_error_on_return;\n  auto md = ncrypto::getDigestByName(algorithm.cStr());\n  JSG_REQUIRE(md != nullptr, Error, kj::str(\"Unknown digest: \", algorithm));\n\n  auto mdctx = ncrypto::EVPMDCtxPointer::New();\n  JSG_REQUIRE(mdctx, Error, \"Failed to create signing context\");\n  JSG_REQUIRE(mdctx.digestInit(md), Error, \"Failed to initialize signing context\");\n  return js.alloc<SignHandle>(kj::mv(mdctx));\n}\n\nvoid CryptoImpl::SignHandle::update(jsg::Lock& js, jsg::JsBufferSource data) {\n  ncrypto::ClearErrorOnReturn clear_error_on_return;\n  JSG_REQUIRE(ctx, Error, \"Signing context has already been finalized\");\n  auto ptr = data.asArrayPtr();\n  ncrypto::Buffer<const void> buf{\n    .data = ptr.begin(),\n    .len = ptr.size(),\n  };\n  JSG_REQUIRE(ctx.digestUpdate(buf), Error, \"Failed to update signing context\");\n}\n\njsg::JsUint8Array CryptoImpl::SignHandle::sign(jsg::Lock& js,\n    jsg::Ref<CryptoKey> key,\n    jsg::Optional<int> rsaPadding,\n    jsg::Optional<int> pssSaltLength,\n    jsg::Optional<int> dsaSigEnc) {\n  ncrypto::ClearErrorOnReturn clear_error_on_return;\n  JSG_REQUIRE(ctx, Error, \"Signing context has already been finalized\");\n\n  const auto& pkey = JSG_REQUIRE_NONNULL(tryGetKey(key), Error, \"Invalid key for sign operation\");\n  JSG_REQUIRE(pkey.validateDsaParameters(), Error, \"Invalid DSA parameters\");\n\n  // There's a bug in ncrypto that doesn't clear the EVPMDCtxPointer when\n  // moved with kj::mv so instead we release and wrap again.\n  auto sig = signFinal(js, ncrypto::EVPMDCtxPointer(ctx.release()), pkey,\n      rsaPadding.orDefault(pkey.getDefaultSignPadding()), pssSaltLength);\n\n  KJ_IF_SOME(enc, dsaSigEnc) {\n    static constexpr unsigned kP1363 = 1;\n    JSG_REQUIRE(enc <= kP1363 && enc >= 0, Error, \"Invalid DSA signature encoding\");\n    if (enc == kP1363) {\n      sig = convertSignatureToP1363(js, pkey, kj::mv(sig));\n    }\n  }\n\n  return sig;\n}\n\nCryptoImpl::VerifyHandle::VerifyHandle(ncrypto::EVPMDCtxPointer ctx)\n    : ctx(ncrypto::EVPMDCtxPointer(ctx.release())) {}\n\njsg::Ref<CryptoImpl::VerifyHandle> CryptoImpl::VerifyHandle::constructor(\n    jsg::Lock& js, kj::String algorithm) {\n  ncrypto::ClearErrorOnReturn clear_error_on_return;\n  auto md = ncrypto::getDigestByName(algorithm.cStr());\n  JSG_REQUIRE(md != nullptr, Error, kj::str(\"Unknown digest: \", algorithm));\n\n  auto mdctx = ncrypto::EVPMDCtxPointer::New();\n  JSG_REQUIRE(mdctx, Error, \"Failed to create verification context\");\n  JSG_REQUIRE(mdctx.digestInit(md), Error, \"Failed to initialize verification context\");\n\n  return js.alloc<VerifyHandle>(kj::mv(mdctx));\n}\n\nvoid CryptoImpl::VerifyHandle::update(jsg::Lock& js, jsg::JsBufferSource data) {\n  ncrypto::ClearErrorOnReturn clear_error_on_return;\n  JSG_REQUIRE(ctx, Error, \"Verification context has already been finalized\");\n  auto ptr = data.asArrayPtr();\n  ncrypto::Buffer<const void> buf{\n    .data = ptr.begin(),\n    .len = ptr.size(),\n  };\n  JSG_REQUIRE(ctx.digestUpdate(buf), Error, \"Failed to update verification context\");\n}\n\nbool CryptoImpl::VerifyHandle::verify(jsg::Lock& js,\n    jsg::Ref<CryptoKey> key,\n    jsg::JsBufferSource signature,\n    jsg::Optional<int> rsaPadding,\n    jsg::Optional<int> maybeSaltLen,\n    jsg::Optional<int> dsaSigEnc) {\n  ncrypto::ClearErrorOnReturn clear_error_on_return;\n\n  JSG_REQUIRE(ctx, Error, \"Verification context has already been finalized\");\n\n  const auto& pkey = JSG_REQUIRE_NONNULL(tryGetKey(key), Error, \"Invalid key for verify operation\");\n\n  JSG_REQUIRE(!pkey.isOneShotVariant(), Error, \"Unsupported operation for this key\");\n\n  auto sigCopy = jsg::JsUint8Array::create(js, signature.asArrayPtr());\n\n  KJ_IF_SOME(enc, dsaSigEnc) {\n    static constexpr unsigned kP1363 = 1;\n    JSG_REQUIRE(enc <= kP1363 && enc >= 0, Error, \"Invalid DSA signature encoding\");\n    if (enc == kP1363) {\n      sigCopy = convertSignatureToDER(js, pkey, kj::mv(sigCopy));\n    }\n  }\n\n  auto sigSource = jsg::JsBufferSource(sigCopy);\n\n  return verifyFinal(js, ncrypto::EVPMDCtxPointer(ctx.release()), pkey, sigSource,\n      rsaPadding.orDefault(pkey.getDefaultSignPadding()), maybeSaltLen);\n}\n\njsg::JsUint8Array CryptoImpl::signOneShot(jsg::Lock& js,\n    jsg::Ref<CryptoKey> key,\n    jsg::Optional<kj::String> algorithm,\n    jsg::JsBufferSource data,\n    jsg::Optional<int> rsaPadding,\n    jsg::Optional<int> pssSaltLength,\n    jsg::Optional<int> dsaSigEnc) {\n  ncrypto::ClearErrorOnReturn clear_error_on_return;\n\n  auto mdctx = ncrypto::EVPMDCtxPointer::New();\n  JSG_REQUIRE(mdctx, Error, \"Failed to create signing context\");\n\n  const auto& pkey = JSG_REQUIRE_NONNULL(tryGetKey(key), Error, \"Invalid key for sign operation\");\n\n  // The version of BoringSSL we use does not support DSA keys with EVP\n  // When signing/verification. This may change in the future.\n  JSG_REQUIRE(pkey.id() != EVP_PKEY_DSA, Error, \"Signing with DSA keys is not currently supported\");\n  // TODO(later): When DSA keys are supported, uncomment to validate DSA params.\n  // JSG_REQUIRE(pkey.validateDsaParameters(), Error, \"Invalid DSA parameters\");\n\n  auto md = maybeGetDigest(algorithm);\n\n  JSG_REQUIRE(mdctx.signInit(pkey, md).has_value(), Error, \"Failed to initialize signing context\");\n\n  ncrypto::Buffer<const kj::byte> buf{\n    .data = data.asArrayPtr().begin(),\n    .len = data.size(),\n  };\n\n  ncrypto::DataPointer sig = mdctx.signOneShot(buf);\n  auto sigBuf =\n      jsg::JsUint8Array::create(js, kj::ArrayPtr<const kj::byte>(sig.get<kj::byte>(), sig.size()));\n\n  KJ_IF_SOME(enc, dsaSigEnc) {\n    static constexpr unsigned kP1363 = 1;\n    JSG_REQUIRE(enc <= kP1363 && enc >= 0, Error, \"Invalid DSA signature encoding\");\n    if (enc == kP1363) {\n      sigBuf = convertSignatureToP1363(js, pkey, kj::mv(sigBuf));\n    }\n  }\n\n  return sigBuf;\n}\n\nbool CryptoImpl::verifyOneShot(jsg::Lock& js,\n    jsg::Ref<CryptoKey> key,\n    jsg::Optional<kj::String> algorithm,\n    jsg::JsBufferSource data,\n    jsg::JsBufferSource signature,\n    jsg::Optional<int> rsaPadding,\n    jsg::Optional<int> pssSaltLength,\n    jsg::Optional<int> dsaSigEnc) {\n  ncrypto::ClearErrorOnReturn clear_error_on_return;\n\n  auto mdctx = ncrypto::EVPMDCtxPointer::New();\n  JSG_REQUIRE(mdctx, Error, \"Failed to create verification context\");\n\n  const auto& pkey =\n      JSG_REQUIRE_NONNULL(tryGetKey(key), Error, \"Invalid key for verification operation\");\n\n  // The version of BoringSSL we use does not support DSA keys with EVP\n  // When signing/verification. This may change in the future.\n  JSG_REQUIRE(\n      pkey.id() != EVP_PKEY_DSA, Error, \"Verifying with DSA keys is not currently supported\");\n  // TODO(later): When DSA keys are supported, uncomment to validate DSA params.\n  // JSG_REQUIRE(pkey.validateDsaParameters(), Error, \"Invalid DSA parameters\");\n\n  auto md = maybeGetDigest(algorithm);\n\n  JSG_REQUIRE(\n      mdctx.verifyInit(pkey, md).has_value(), Error, \"Failed to initialize verification context\");\n\n  auto sigCopy = jsg::JsUint8Array::create(js, signature.asArrayPtr());\n\n  KJ_IF_SOME(enc, dsaSigEnc) {\n    static constexpr unsigned kP1363 = 1;\n    JSG_REQUIRE(enc <= kP1363 && enc >= 0, Error, \"Invalid DSA signature encoding\");\n    if (enc == kP1363) {\n      sigCopy = convertSignatureToDER(js, pkey, kj::mv(sigCopy));\n    }\n  }\n\n  ncrypto::Buffer<const kj::byte> buf{\n    .data = data.asArrayPtr().begin(),\n    .len = data.size(),\n  };\n\n  ncrypto::Buffer<const kj::byte> sig{\n    .data = sigCopy.asArrayPtr().begin(),\n    .len = sigCopy.size(),\n  };\n\n  return mdctx.verify(buf, sig);\n}\n\n#pragma endregion  // SignVerify\n\n// ======================================================================================\n#pragma region Cipher/Decipher\n\nnamespace {\nconstexpr unsigned kNoAuthTagLength = static_cast<unsigned>(-1);\n\nCryptoImpl::CipherHandle::AuthenticatedInfo initAuthenticated(ncrypto::CipherCtxPointer& ctx,\n    bool encrypt,\n    kj::StringPtr cipher_type,\n    int iv_len,\n    unsigned int auth_tag_len) {\n  ncrypto::MarkPopErrorOnReturn mark_pop_error_on_return;\n\n  JSG_REQUIRE(ctx.setIvLength(iv_len), Error, \"Invalid initialization vector\");\n\n  CryptoImpl::CipherHandle::AuthenticatedInfo info;\n  info.auth_tag_len = auth_tag_len;\n\n  const int mode = ctx.getMode();\n  if (mode == EVP_CIPH_GCM_MODE) {\n    if (info.auth_tag_len != kNoAuthTagLength) {\n      JSG_REQUIRE(ncrypto::Cipher::IsValidGCMTagLength(auth_tag_len), Error,\n          \"Invalid authentication tag length\");\n    }\n  } else {\n    if (auth_tag_len == kNoAuthTagLength) {\n      // We treat ChaCha20-Poly1305 specially. Like GCM, the authentication tag\n      // length defaults to 16 bytes when encrypting. Unlike GCM, the\n      // authentication tag length also defaults to 16 bytes when decrypting,\n      // whereas GCM would accept any valid authentication tag length.\n      if (ctx.getNid() == NID_chacha20_poly1305) {\n        info.auth_tag_len = 16;\n      } else {\n        JSG_FAIL_REQUIRE(\n            Error, kj::str(\"The auth tag length is required for cipher \", cipher_type));\n      }\n    }\n\n    if (mode == EVP_CIPH_CCM_MODE && !encrypt && FIPS_mode()) {\n      JSG_FAIL_REQUIRE(Error, \"CCM encryption not supported in FIPS mode\");\n    }\n\n    JSG_REQUIRE(\n        ctx.setAeadTagLength(info.auth_tag_len), Error, \"Invalid authentication tag length\");\n\n    if (mode == EVP_CIPH_CCM_MODE) {\n      // See https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38c.pdf\n      // A.1 Length Requirements\n\n      JSG_REQUIRE(iv_len >= 7 && iv_len <= 13, Error, \"Invalid authentication tag length\");\n      if (iv_len == 12) info.max_message_size = 16777215;\n      if (iv_len == 13) info.max_message_size = 65535;\n    }\n  }\n\n  return info;\n}\n\nbool isAuthenticatedMode(const ncrypto::CipherCtxPointer& ctx) {\n  return ncrypto::Cipher::FromCtx(ctx).isSupportedAuthenticatedMode();\n}\n\nbool passAuthTagToOpenSSL(ncrypto::CipherCtxPointer& ctx, kj::ArrayPtr<const kj::byte> authTag) {\n  ncrypto::Buffer<const char> buffer{\n    .data = reinterpret_cast<const char*>(authTag.begin()),\n    .len = authTag.size(),\n  };\n  return ctx.setAeadTag(buffer);\n}\n}  // namespace\n\nCryptoImpl::CipherHandle::CipherHandle(jsg::Lock& js,\n    CipherMode mode,\n    ncrypto::CipherCtxPointer ctx,\n    jsg::Ref<CryptoKey> key,\n    jsg::JsBufferSource iv,\n    kj::Maybe<AuthenticatedInfo> maybeAuthInfo)\n    : mode(mode),\n      ctx(kj::mv(ctx)),\n      key(kj::mv(key)),\n      iv(iv.addRef(js)),\n      maybeAuthInfo(kj::mv(maybeAuthInfo)) {}\n\njsg::Ref<CryptoImpl::CipherHandle> CryptoImpl::CipherHandle::construct(jsg::Lock& js,\n    CipherMode mode,\n    kj::StringPtr algorithm,\n    ncrypto::Cipher cipher,\n    jsg::Ref<CryptoKey> key,\n    jsg::JsBufferSource iv,\n    jsg::Optional<uint32_t> maybeAuthTagLength) {\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n\n  JSG_REQUIRE(key->getType() == \"secret\"_kj, TypeError, \"Invalid key type for cipher\");\n\n  auto keyData =\n      JSG_REQUIRE_NONNULL(tryGetSecretKeyData(key), Error, \"Failed to get raw secret key data\");\n\n  int expectedIvLength = cipher.getIvLength();\n\n  if ((expectedIvLength && !iv.size()) ||\n      (!cipher.isSupportedAuthenticatedMode() && iv.size() &&\n          static_cast<int>(iv.size()) != expectedIvLength)) {\n    JSG_FAIL_REQUIRE(Error, \"Invalid initialization vector\");\n  }\n\n  if (cipher.getNid() == NID_chacha20_poly1305) {\n    JSG_REQUIRE(iv.size(), Error, \"ChaCha20-Poly1305 requires an initialization vector\");\n    JSG_REQUIRE(iv.size() <= 12, Error, \"Invalid initialization vector\");\n  }\n\n  auto ctx = ncrypto::CipherCtxPointer::New();\n  JSG_REQUIRE(ctx, Error, \"Failed to create cipher/decipher context\");\n\n  if (cipher.getMode() == EVP_CIPH_WRAP_MODE) {\n    ctx.setAllowWrap();\n  }\n\n  bool encrypt = mode == CipherMode::CIPHER;\n\n  JSG_REQUIRE(ctx.init(cipher, encrypt), Error, \"Failed to initialize cipher/decipher context\");\n\n  kj::Maybe<CryptoImpl::CipherHandle::AuthenticatedInfo> maybeAuthInfo = kj::none;\n  if (cipher.isSupportedAuthenticatedMode()) {\n    maybeAuthInfo = initAuthenticated(\n        ctx, encrypt, algorithm, iv.size(), maybeAuthTagLength.orDefault(kNoAuthTagLength));\n  }\n\n  JSG_REQUIRE(ctx.setKeyLength(keyData.size()), Error, \"Invalid key length\");\n\n  JSG_REQUIRE(ctx.init(ncrypto::Cipher(), encrypt, keyData.begin(), iv.asArrayPtr().begin()), Error,\n      \"Failed to initialize cipher/cipher context\");\n\n  return js.alloc<CipherHandle>(\n      js, mode, kj::mv(ctx), kj::mv(key), kj::mv(iv), kj::mv(maybeAuthInfo));\n}\n\njsg::JsUint8Array CryptoImpl::CipherHandle::update(jsg::Lock& js, jsg::JsBufferSource data) {\n\n  JSG_REQUIRE(ctx, Error, \"Cipher/decipher context has already been finalized\");\n  JSG_REQUIRE(data.size() <= INT_MAX, Error, \"Data too large\");\n\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n\n  const int ctxMode = ctx.getMode();\n\n  if (ctxMode == EVP_CIPH_CCM_MODE) {\n    auto max = KJ_ASSERT_NONNULL(maybeAuthInfo).max_message_size;\n    JSG_REQUIRE(data.size() <= max, Error, \"Invalid message length\");\n  }\n\n  if (mode == CipherMode::DECIPHER && isAuthenticatedMode(ctx) && !authTagPassed) {\n    authTagPassed = true;\n    auto& tagRef = JSG_REQUIRE_NONNULL(maybeAuthTag, Error, \"No auth tag provided\");\n    auto tag = tagRef.getHandle(js);\n    JSG_REQUIRE(\n        passAuthTagToOpenSSL(ctx, tag.asArrayPtr().asConst()), Error, \"Failed to set auth tag\");\n  }\n\n  const int block_size = ctx.getBlockSize();\n  KJ_ASSERT(block_size > 0);\n  JSG_REQUIRE(data.size() + block_size <= INT_MAX, Error, \"Data too large\");\n  int buf_len = data.size() + block_size;\n\n  ncrypto::Buffer<const unsigned char> buffer = {\n    .data = data.asArrayPtr().begin(),\n    .len = data.size(),\n  };\n  if (mode == CipherMode::CIPHER && ctxMode == EVP_CIPH_WRAP_MODE &&\n      !ctx.update(buffer, nullptr, &buf_len)) {\n    JSG_FAIL_REQUIRE(Error, \"Failed to process data\");\n  }\n\n  auto buf = jsg::JsUint8Array::create(js, buf_len);\n  buffer.data = data.asArrayPtr().begin();\n  buffer.len = data.size();\n  bool r = ctx.update(buffer, buf.asArrayPtr().begin(), &buf_len);\n\n  if (buf_len != buf.size()) {\n    JSG_REQUIRE(buf_len < buf.size(), Error, \"Invalid buffer length\");\n    auto newBuf = jsg::JsUint8Array::create(js, buf_len);\n    if (buf_len > 0) {\n      newBuf.asArrayPtr().copyFrom(buf.asArrayPtr().first(buf_len));\n    }\n    buf = kj::mv(newBuf);\n  }\n\n  // When in CCM mode, EVP_CipherUpdate will fail if the authentication tag is\n  // invalid. In that case, remember the error and throw in final().\n  if (!r && mode == CipherMode::DECIPHER && ctxMode == EVP_CIPH_CCM_MODE) {\n    pendingAuthFailed = true;\n  }\n\n  return buf;\n}\n\njsg::JsUint8Array CryptoImpl::CipherHandle::final(jsg::Lock& js) {\n  JSG_REQUIRE(ctx, Error, \"Cipher/decipher context has already been finalized\");\n\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n\n  int ctxMode = ctx.getMode();\n\n  auto buf = jsg::JsUint8Array::create(js, ctx.getBlockSize());\n\n  if (mode == CipherMode::DECIPHER && isAuthenticatedMode(ctx) && !authTagPassed) {\n    authTagPassed = true;\n    auto& tagRef = JSG_REQUIRE_NONNULL(maybeAuthTag, Error, \"No auth tag provided\");\n    auto tag = tagRef.getHandle(js);\n    JSG_REQUIRE(\n        passAuthTagToOpenSSL(ctx, tag.asArrayPtr().asConst()), Error, \"Failed to set auth tag\");\n  }\n\n  if (ctx.getNid() == NID_chacha20_poly1305 && mode == CipherMode::DECIPHER) {\n    JSG_REQUIRE(authTagPassed, Error, \"An auth tag is required\");\n  }\n\n  // In CCM mode, final() only checks whether authentication failed in update().\n  // EVP_CipherFinal_ex must not be called and will fail.\n  bool ok;\n  if (mode == CipherMode::DECIPHER && ctxMode == EVP_CIPH_CCM_MODE) {\n    ok = !pendingAuthFailed;\n    buf = jsg::JsUint8Array::create(js, 0);\n  } else {\n    int out_len = buf.size();\n    ok = ctx.update({}, buf.asArrayPtr().begin(), &out_len, true);\n\n    if (out_len != buf.size()) {\n      JSG_REQUIRE(out_len < buf.size(), Error, \"Invalid buffer length\");\n      auto newBuf = jsg::JsUint8Array::create(js, out_len);\n      if (out_len > 0) {\n        newBuf.asArrayPtr().copyFrom(buf.asArrayPtr().first(out_len));\n      }\n      buf = kj::mv(newBuf);\n    }\n\n    if (ok && mode == CipherMode::CIPHER && isAuthenticatedMode(ctx)) {\n      auto& info = JSG_REQUIRE_NONNULL(maybeAuthInfo, Error, \"Missing required auth info\");\n      // In GCM mode, the authentication tag length can be specified in advance,\n      // but defaults to 16 bytes when encrypting. In CCM and OCB mode, it must\n      // always be given by the user.\n      if (info.auth_tag_len == kNoAuthTagLength) {\n        info.auth_tag_len = 16;\n      }\n      auto tag = jsg::JsUint8Array::create(js, info.auth_tag_len);\n      ok = ctx.getAeadTag(info.auth_tag_len, tag.asArrayPtr().begin());\n      maybeAuthTag = jsg::JsBufferSource(tag).addRef(js);\n    }\n  }\n\n  JSG_REQUIRE(ok, Error, \"Authentication failed\");\n\n  ctx.reset();\n  return buf;\n}\n\nvoid CryptoImpl::CipherHandle::setAAD(\n    jsg::Lock& js, jsg::JsBufferSource aad, jsg::Optional<uint32_t> maybePlaintextLength) {\n\n  JSG_REQUIRE(ctx, Error, \"Cipher/decipher context has already been finalized\");\n  JSG_REQUIRE(isAuthenticatedMode(ctx), Error, \"Cipher does not support authenticated mode\");\n\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n\n  int outlen;\n  const int ctxMode = ctx.getMode();\n\n  // When in CCM mode, we need to set the authentication tag and the plaintext\n  // length in advance.\n  if (ctxMode == EVP_CIPH_CCM_MODE) {\n    auto plaintextLength = JSG_REQUIRE_NONNULL(\n        maybePlaintextLength, Error, \"options.plaintextLength is required for CCM mode with AAD\");\n\n    auto& info = JSG_REQUIRE_NONNULL(maybeAuthInfo, Error, \"Required auth info is not available\");\n\n    JSG_REQUIRE(plaintextLength <= info.max_message_size, Error, \"Data too large\");\n\n    if (mode == CipherMode::DECIPHER && isAuthenticatedMode(ctx) && !authTagPassed) {\n      authTagPassed = true;\n      auto& tagRef = JSG_REQUIRE_NONNULL(maybeAuthTag, Error, \"No auth tag provided\");\n      auto tag = tagRef.getHandle(js);\n      JSG_REQUIRE(\n          passAuthTagToOpenSSL(ctx, tag.asArrayPtr().asConst()), Error, \"Failed to set auth tag\");\n    }\n\n    ncrypto::Buffer<const unsigned char> buffer{\n      .data = nullptr,\n      .len = plaintextLength,\n    };\n    // Specify the plaintext length.\n    JSG_REQUIRE(ctx.update(buffer, nullptr, &outlen), Error, \"Failed to set plaintext length\");\n  }\n\n  ncrypto::Buffer<const unsigned char> buffer{\n    .data = aad.asArrayPtr().begin(),\n    .len = aad.size(),\n  };\n  JSG_REQUIRE(ctx.update(buffer, nullptr, &outlen), Error, \"Failed to set AAD\");\n}\n\nvoid CryptoImpl::CipherHandle::setAutoPadding(jsg::Lock& js, bool autoPadding) {\n  JSG_REQUIRE(ctx, Error, \"Cipher/decipher context has already been finalized\");\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n  JSG_REQUIRE(ctx.setPadding(autoPadding), Error, \"Failed to set autopadding\");\n}\n\nvoid CryptoImpl::CipherHandle::setAuthTag(jsg::Lock& js, jsg::JsBufferSource authTag) {\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n  JSG_REQUIRE(ctx, Error, \"Cipher/decipher context has already been finalized\");\n  JSG_REQUIRE(isAuthenticatedMode(ctx), Error, \"Cipher does not support authenticated mode\");\n  JSG_REQUIRE(\n      mode == CipherMode::DECIPHER, Error, \"Setting auth tag only support in decipher mode\");\n  JSG_REQUIRE(maybeAuthTag == kj::none, Error, \"Auth tag is already set\");\n  JSG_REQUIRE(authTag.size() <= INT_MAX, Error, \"Auth tag is too big\");\n\n  int ctxMode = ctx.getMode();\n  bool is_valid = false;\n\n  auto& info = JSG_REQUIRE_NONNULL(maybeAuthInfo, Error, \"Required auth info is not available\");\n\n  if (ctxMode == EVP_CIPH_GCM_MODE) {\n    // Restrict GCM tag lengths according to NIST 800-38d, page 9.\n    is_valid = (info.auth_tag_len == kNoAuthTagLength || info.auth_tag_len == authTag.size()) &&\n        ncrypto::Cipher::IsValidGCMTagLength(authTag.size());\n  } else {\n    is_valid = info.auth_tag_len == authTag.size();\n  }\n\n  JSG_REQUIRE(is_valid, Error, \"Invalid authentication tag length\");\n\n  info.auth_tag_len = authTag.size();\n\n  // We defensively copy the auth tag here to prevent modification.\n  auto tagCopy = jsg::JsUint8Array::create(js, authTag.asArrayPtr());\n  maybeAuthTag = jsg::JsBufferSource(static_cast<v8::Local<v8::Value>>(tagCopy)).addRef(js);\n}\n\njsg::JsUint8Array CryptoImpl::CipherHandle::getAuthTag(jsg::Lock& js) {\n  JSG_REQUIRE(!ctx, Error, \"Auth tag is only available once cipher context has been finalized\");\n  JSG_REQUIRE(mode == CipherMode::CIPHER, Error, \"Getting the auth tag is only support for cipher\");\n\n  KJ_IF_SOME(ref, maybeAuthTag) {\n    KJ_DEFER(maybeAuthTag = kj::none);\n    auto tag = ref.getHandle(js);\n    return jsg::JsUint8Array::create(js, tag.asArrayPtr());\n  }\n\n  return jsg::JsUint8Array::create(js, 0);\n}\n\nnamespace {\nCryptoImpl::AeadHandle::AuthenticatedInfo initAuthenticated(ncrypto::Aead& aead,\n    ncrypto::AeadCtxPointer& ctx,\n    bool encrypt,\n    kj::StringPtr cipher_type,\n    int iv_len,\n    unsigned int auth_tag_len) {\n  ncrypto::MarkPopErrorOnReturn mark_pop_error_on_return;\n\n  CryptoImpl::AeadHandle::AuthenticatedInfo info;\n  info.auth_tag_len = auth_tag_len;\n\n  const int mode = aead.getMode();\n  if (mode == EVP_CIPH_GCM_MODE) {\n    if (info.auth_tag_len != kNoAuthTagLength) {\n      JSG_REQUIRE(ncrypto::Cipher::IsValidGCMTagLength(auth_tag_len), Error,\n          \"Invalid authentication tag length\");\n    }\n  } else {\n    if (auth_tag_len == kNoAuthTagLength) {\n      // We treat ChaCha20-Poly1305 specially. Like GCM, the authentication tag\n      // length defaults to 16 bytes when encrypting. Unlike GCM, the\n      // authentication tag length also defaults to 16 bytes when decrypting,\n      // whereas GCM would accept any valid authentication tag length.\n      if (aead.getName() == \"chacha20-poly1305\"sv) {\n        info.auth_tag_len = 16;\n      } else {\n        JSG_FAIL_REQUIRE(\n            Error, kj::str(\"The auth tag length is required for cipher \", cipher_type));\n      }\n    }\n\n    if (mode == EVP_CIPH_CCM_MODE && !encrypt && FIPS_mode()) {\n      JSG_FAIL_REQUIRE(Error, \"CCM encryption not supported in FIPS mode\");\n    }\n\n    if (mode == EVP_CIPH_CCM_MODE) {\n      // See https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38c.pdf\n      // A.1 Length Requirements\n      JSG_REQUIRE(iv_len >= 7 && iv_len <= 13, Error, \"Invalid authentication tag length\");\n      if (iv_len == 12) info.max_message_size = 16777215;\n      if (iv_len == 13) info.max_message_size = 65535;\n    }\n  }\n\n  return info;\n}\n}  // namespace\n\nCryptoImpl::AeadHandle::AeadHandle(jsg::Lock& js,\n    CipherMode mode,\n    ncrypto::Aead aead,\n    ncrypto::AeadCtxPointer ctx,\n    jsg::Ref<CryptoKey> key,\n    jsg::JsBufferSource iv,\n    kj::Maybe<AuthenticatedInfo> maybeAuthInfo)\n    : mode(mode),\n      aead(aead),\n      ctx(kj::mv(ctx)),\n      key(kj::mv(key)),\n      iv(iv.addRef(js)),\n      maybeAuthInfo(kj::mv(maybeAuthInfo)) {}\n\njsg::Ref<CryptoImpl::AeadHandle> CryptoImpl::AeadHandle::construct(jsg::Lock& js,\n    CipherMode mode,\n    kj::StringPtr algorithm,\n    ncrypto::Aead aead,\n    jsg::Ref<CryptoKey> key,\n    jsg::JsBufferSource iv,\n    jsg::Optional<uint32_t> maybeAuthTagLength) {\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n\n  JSG_REQUIRE(key->getType() == \"secret\"_kj, TypeError, \"Invalid key type for cipher\");\n\n  auto keyData =\n      JSG_REQUIRE_NONNULL(tryGetSecretKeyData(key), Error, \"Failed to get raw secret key data\");\n\n  int expectedIvLength = aead.getNonceLength();\n  JSG_REQUIRE(iv.size() == expectedIvLength, Error, \"Invalid initialization vector\");\n\n  if (aead.getName() == \"chacha20-poly1305\"sv) {\n    JSG_REQUIRE(iv.size(), Error, \"ChaCha20-Poly1305 requires an initialization vector\");\n    JSG_REQUIRE(iv.size() <= 12, Error, \"Invalid initialization vector\");\n  }\n\n  bool encrypt = mode == CipherMode::CIPHER;\n\n  // Note: kNoAuthTagLength is -1, and is used within the implementation of the node:crypto API,\n  // while EVP_AEAD_DEFAULT_TAG_LENGTH is 0 and is used when communicating with BoringSSL\n\n  auto ctx = ncrypto::AeadCtxPointer::New(aead, encrypt, keyData.begin(), keyData.size(),\n      maybeAuthTagLength.orDefault(EVP_AEAD_DEFAULT_TAG_LENGTH));\n  JSG_REQUIRE(ctx, Error, \"Failed to initialize AEAD cipher/decipher context\");\n\n  kj::Maybe<CryptoImpl::AeadHandle::AuthenticatedInfo> maybeAuthInfo = kj::none;\n  maybeAuthInfo = initAuthenticated(\n      aead, ctx, encrypt, algorithm, iv.size(), maybeAuthTagLength.orDefault(kNoAuthTagLength));\n\n  return js.alloc<AeadHandle>(\n      js, mode, aead, kj::mv(ctx), kj::mv(key), kj::mv(iv), kj::mv(maybeAuthInfo));\n}\n\njsg::JsUint8Array CryptoImpl::AeadHandle::update(jsg::Lock& js, jsg::JsBufferSource data) {\n  JSG_REQUIRE(!updated, Error, \"update() can only be invoked once on an AEAD\");\n  JSG_REQUIRE(ctx, Error, \"Cipher/decipher context has already been finalized\");\n  JSG_REQUIRE(data.size() <= INT_MAX, Error, \"Data too large\");\n\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n\n  const int aeadMode = aead.getMode();\n\n  if (aeadMode == EVP_CIPH_CCM_MODE) {\n    auto max = KJ_ASSERT_NONNULL(maybeAuthInfo).max_message_size;\n    JSG_REQUIRE(data.size() <= max, Error, \"Invalid message length\");\n  }\n\n  const int block_size = aead.getBlockSize();\n  KJ_ASSERT(block_size > 0);\n  JSG_REQUIRE(data.size() + block_size <= INT_MAX, Error, \"Data too large\");\n\n  ncrypto::Buffer<const unsigned char> buffer = {\n    .data = data.asArrayPtr().begin(),\n    .len = data.size(),\n  };\n\n  auto buf = jsg::JsUint8Array::create(js, data.size());\n\n  ncrypto::Buffer<unsigned char> outBuf = {.data = buf.asArrayPtr().begin(), .len = data.size()};\n  auto ivHandle = iv.getHandle(js);\n  ncrypto::Buffer<const unsigned char> ivBuf = {\n    .data = ivHandle.asArrayPtr().begin(), .len = ivHandle.size()};\n  ncrypto::Buffer<const unsigned char> aadBuf;\n\n  KJ_IF_SOME(aadRef, maybeAad) {\n    auto aad = aadRef.getHandle(js);\n    aadBuf = {.data = aad.asArrayPtr().begin(), .len = aad.size()};\n  }\n\n  bool r;\n  if (mode == CipherMode::CIPHER) {\n    auto& info = JSG_REQUIRE_NONNULL(maybeAuthInfo, Error, \"Missing required auth info\");\n    // In GCM mode, the authentication tag length can be specified in advance,\n    // but defaults to 16 bytes when encrypting. In CCM and OCB mode, it must\n    // always be given by the user.\n\n    if (info.auth_tag_len == kNoAuthTagLength) {\n      info.auth_tag_len = 16;\n    }\n\n    auto tag = jsg::JsUint8Array::create(js, info.auth_tag_len);\n\n    ncrypto::Buffer<unsigned char> tagBuf = {\n      .data = tag.asArrayPtr().begin(), .len = info.auth_tag_len};\n\n    r = ctx.encrypt(buffer, outBuf, tagBuf, ivBuf, aadBuf);\n    maybeAuthTag = jsg::JsBufferSource(tag).addRef(js);\n  } else {\n    auto tag = JSG_REQUIRE_NONNULL(maybeAuthTag, Error, \"No auth tag provided\").getHandle(js);\n\n    ncrypto::Buffer<const unsigned char> tagBuf = {\n      .data = tag.asArrayPtr().begin(), .len = tag.size()};\n\n    r = ctx.decrypt(buffer, outBuf, tagBuf, ivBuf, aadBuf);\n    ERR_print_errors_fp(stderr);\n  }\n\n  JSG_REQUIRE(r, Error, \"Authentication failed\");\n  // EVP_AEAD operations always return an output of the same size as the input\n  KJ_REQUIRE(outBuf.len == buf.size(), \"Invalid output length for AEAD operation\");\n  updated = true;\n  return buf;\n}\n\njsg::JsUint8Array CryptoImpl::AeadHandle::final(jsg::Lock& js) {\n  // There is no finalization operation in the EVP_AEAD API.\n  // Just return an empty value and clean up.\n  JSG_REQUIRE(ctx, Error, \"Cipher/decipher context has already been finalized\");\n\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n  ctx.reset();\n  return jsg::JsUint8Array::create(js, 0);\n}\n\nvoid CryptoImpl::AeadHandle::setAAD(\n    jsg::Lock& js, jsg::JsBufferSource aad, jsg::Optional<uint32_t> maybePlaintextLength) {\n  // In EVP_AEAD, the AAD is handled at the same time as the update.\n  // Just save the value until update() is called.\n\n  JSG_REQUIRE(ctx, Error, \"Cipher/decipher context has already been finalized\");\n\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n\n  const int aeadMode = aead.getMode();\n\n  // When in CCM mode, we need to set the authentication tag and the plaintext\n  // length in advance.\n  if (aeadMode == EVP_CIPH_CCM_MODE) {\n    auto plaintextLength = JSG_REQUIRE_NONNULL(\n        maybePlaintextLength, Error, \"options.plaintextLength is required for CCM mode with AAD\");\n\n    auto& info = JSG_REQUIRE_NONNULL(maybeAuthInfo, Error, \"Required auth info is not available\");\n\n    JSG_REQUIRE(plaintextLength <= info.max_message_size, Error, \"Data too large\");\n\n    if (mode == CipherMode::DECIPHER) {\n      JSG_REQUIRE_NONNULL(maybeAuthTag, Error, \"No auth tag provided\");\n    }\n  }\n\n  // Defensively copy the AAD data.\n  auto aadCopy = jsg::JsUint8Array::create(js, aad.asArrayPtr());\n  maybeAad = jsg::JsBufferSource(static_cast<v8::Local<v8::Value>>(aadCopy)).addRef(js);\n}\n\nvoid CryptoImpl::AeadHandle::setAutoPadding(jsg::Lock&, bool) {\n  JSG_REQUIRE(ctx, Error, \"Cipher/decipher context has already been finalized\");\n  JSG_FAIL_REQUIRE(Error, \"Setting autopadding is not supported on AEADs\");\n}\n\nvoid CryptoImpl::AeadHandle::setAuthTag(jsg::Lock& js, jsg::JsBufferSource authTag) {\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n  JSG_REQUIRE(ctx, Error, \"Cipher/decipher context has already been finalized\");\n  JSG_REQUIRE(\n      mode == CipherMode::DECIPHER, Error, \"Setting auth tag only support in decipher mode\");\n  JSG_REQUIRE(maybeAuthTag == kj::none, Error, \"Auth tag is already set\");\n  JSG_REQUIRE(authTag.size() <= INT_MAX, Error, \"Auth tag is too big\");\n\n  int aeadMode = aead.getMode();\n  bool is_valid = false;\n\n  auto& info = JSG_REQUIRE_NONNULL(maybeAuthInfo, Error, \"Required auth info is not available\");\n\n  if (aeadMode == EVP_CIPH_GCM_MODE) {\n    // Restrict GCM tag lengths according to NIST 800-38d, page 9.\n    is_valid = (info.auth_tag_len == kNoAuthTagLength || info.auth_tag_len == authTag.size()) &&\n        ncrypto::Cipher::IsValidGCMTagLength(authTag.size());\n  } else {\n    is_valid = info.auth_tag_len == authTag.size();\n  }\n\n  JSG_REQUIRE(is_valid, Error, \"Invalid authentication tag length\");\n\n  info.auth_tag_len = authTag.size();\n\n  // We defensively copy the auth tag here to prevent modification.\n  auto tagCopy = jsg::JsUint8Array::create(js, authTag.asArrayPtr());\n  maybeAuthTag = jsg::JsBufferSource(static_cast<v8::Local<v8::Value>>(tagCopy)).addRef(js);\n}\n\njsg::JsUint8Array CryptoImpl::AeadHandle::getAuthTag(jsg::Lock& js) {\n  JSG_REQUIRE(!ctx, Error, \"Auth tag is only available once cipher context has been finalized\");\n  JSG_REQUIRE(mode == CipherMode::CIPHER, Error, \"Getting the auth tag is only support for cipher\");\n\n  KJ_IF_SOME(ref, maybeAuthTag) {\n    KJ_DEFER(maybeAuthTag = kj::none);\n    auto tag = ref.getHandle(js);\n    return jsg::JsUint8Array::create(js, tag.asArrayPtr());\n  }\n\n  return jsg::JsUint8Array::create(js, 0);\n}\n\nkj::OneOf<jsg::Ref<CryptoImpl::CipherHandle>, jsg::Ref<CryptoImpl::AeadHandle>> CryptoImpl::\n    newHandle(jsg::Lock& js,\n        kj::uint mode,\n        kj::String algorithm,\n        jsg::Ref<CryptoKey> key,\n        jsg::JsBufferSource iv,\n        jsg::Optional<uint32_t> maybeAuthTagLength) {\n  CipherMode cipherMode = static_cast<CipherMode>(mode);\n\n  if (auto cipher = ncrypto::Cipher::FromName(algorithm.cStr())) {\n    return CipherHandle::construct(\n        js, cipherMode, algorithm, cipher, kj::mv(key), kj::mv(iv), kj::mv(maybeAuthTagLength));\n  } else if (auto aead =\n                 ncrypto::Aead::FromName(std::string_view(algorithm.begin(), algorithm.size()))) {\n    return AeadHandle::construct(\n        js, cipherMode, algorithm, aead, kj::mv(key), kj::mv(iv), kj::mv(maybeAuthTagLength));\n  }\n\n  JSG_FAIL_REQUIRE(Error, kj::str(\"Unknown or unsupported cipher: \", algorithm));\n}\n\nnamespace {\n\n// TODO(soon): For some reason the ncrypto implementation of these is not\n// working for us but they do work in Node.js. Will need to figure out why.\n// For now, it's easy enough to implement ourselves here.\nusing EVP_PKEY_cipher_t = int(\n    EVP_PKEY_CTX* ctx, unsigned char* out, size_t* outlen, const unsigned char* in, size_t inlen);\n\ntemplate <EVP_PKEY_cipher_t cipher>\njsg::JsUint8Array Cipher(jsg::Lock& js,\n    ncrypto::EVPKeyCtxPointer&& ctx,\n    jsg::JsBufferSource& buffer,\n    const CryptoImpl::PublicPrivateCipherOptions& options) {\n\n  ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n\n  const EVP_MD* digest = nullptr;\n  if (options.oaepHash.size() > 0) {\n    digest = ncrypto::getDigestByName(options.oaepHash.cStr());\n    JSG_REQUIRE(digest != nullptr, Error, \"Unsupported hash digest\");\n  }\n\n  JSG_REQUIRE(\n      EVP_PKEY_CTX_set_rsa_padding(ctx.get(), options.padding), Error, \"Failed to set the padding\");\n\n  if (digest != nullptr && options.padding == RSA_PKCS1_OAEP_PADDING) {\n    JSG_REQUIRE(\n        EVP_PKEY_CTX_set_rsa_oaep_md(ctx.get(), digest) == 1, Error, \"Failed to set the digest\");\n    JSG_REQUIRE(EVP_PKEY_CTX_set_rsa_mgf1_md(ctx.get(), digest) == 1, Error,\n        \"Failed to set the mgf1 digest\");\n  }\n\n  KJ_IF_SOME(labelRef, options.oaepLabel) {\n    auto label = labelRef.getHandle(js);\n    // The ctx takes ownership of the data buffer so we have to copy.\n    auto data = ncrypto::DataPointer::Alloc(label.size());\n    kj::ArrayPtr<kj::byte> dataPtr(data.get<kj::byte>(), data.size());\n    dataPtr.copyFrom(label.asArrayPtr());\n    auto released = data.release();\n    JSG_REQUIRE(EVP_PKEY_CTX_set0_rsa_oaep_label(\n                    ctx.get(), static_cast<uint8_t*>(released.data), released.len) == 1,\n        Error, \"Failed to set the OAEP label\");\n  }\n\n  size_t len;\n  JSG_REQUIRE(cipher(ctx.get(), nullptr, &len, buffer.asArrayPtr().begin(), buffer.size()) == 1,\n      Error, \"Failed to determine output size\");\n\n  if (len == 0) {\n    return jsg::JsUint8Array::create(js, 0);\n  }\n\n  auto buf = jsg::JsUint8Array::create(js, len);\n  JSG_REQUIRE(cipher(ctx.get(), buf.asArrayPtr().begin(), &len, buffer.asArrayPtr().begin(),\n                  buffer.size()) == 1,\n      Error, \"Failed to cipher/decipher\");\n\n  if (len < buf.size()) {\n    auto newBuf = jsg::JsUint8Array::create(js, len);\n    newBuf.asArrayPtr().copyFrom(buf.asArrayPtr().first(len));\n    buf = kj::mv(newBuf);\n  }\n\n  return buf;\n}\n}  // namespace\n\njsg::JsUint8Array CryptoImpl::publicEncrypt(jsg::Lock& js,\n    jsg::Ref<CryptoKey> key,\n    jsg::JsBufferSource buffer,\n    CryptoImpl::PublicPrivateCipherOptions options) {\n  auto& pkey = JSG_REQUIRE_NONNULL(tryGetKey(key), Error, \"No key provided\");\n  JSG_REQUIRE(pkey.isRsaVariant(), Error, \"publicEncrypt() currently only supports RSA keys\");\n  auto ctx = pkey.newCtx();\n  JSG_REQUIRE(ctx.initForEncrypt(), Error, \"Failed to init for encryption\");\n  return Cipher<EVP_PKEY_encrypt>(js, kj::mv(ctx), buffer, options);\n}\n\njsg::JsUint8Array CryptoImpl::privateDecrypt(jsg::Lock& js,\n    jsg::Ref<CryptoKey> key,\n    jsg::JsBufferSource buffer,\n    CryptoImpl::PublicPrivateCipherOptions options) {\n  auto& pkey = JSG_REQUIRE_NONNULL(tryGetKey(key), Error, \"No key provided\");\n  JSG_REQUIRE(pkey.isRsaVariant(), Error, \"publicEncrypt() currently only supports RSA keys\");\n  auto ctx = pkey.newCtx();\n  JSG_REQUIRE(ctx.initForDecrypt(), Error, \"Failed to init for decryption\");\n  return Cipher<EVP_PKEY_decrypt>(js, kj::mv(ctx), buffer, options);\n}\n\njsg::JsUint8Array CryptoImpl::publicDecrypt(jsg::Lock& js,\n    jsg::Ref<CryptoKey> key,\n    jsg::JsBufferSource buffer,\n    CryptoImpl::PublicPrivateCipherOptions options) {\n  auto& pkey = JSG_REQUIRE_NONNULL(tryGetKey(key), Error, \"No key provided\");\n  JSG_REQUIRE(pkey.isRsaVariant(), Error, \"publicEncrypt() currently only supports RSA keys\");\n  auto ctx = pkey.newCtx();\n  JSG_REQUIRE(EVP_PKEY_verify_recover_init(ctx.get()) == 1, Error, \"Failed to init for decryption\");\n  return Cipher<EVP_PKEY_verify_recover>(js, kj::mv(ctx), buffer,\n      {\n        .padding = options.padding,\n        .oaepHash = kj::String(),\n      });\n}\njsg::JsUint8Array CryptoImpl::privateEncrypt(jsg::Lock& js,\n    jsg::Ref<CryptoKey> key,\n    jsg::JsBufferSource buffer,\n    CryptoImpl::PublicPrivateCipherOptions options) {\n  auto& pkey = JSG_REQUIRE_NONNULL(tryGetKey(key), Error, \"No key provided\");\n  JSG_REQUIRE(pkey.isRsaVariant(), Error, \"publicEncrypt() currently only supports RSA keys\");\n  auto ctx = pkey.newCtx();\n  JSG_REQUIRE(EVP_PKEY_sign_init(ctx.get()) == 1, Error, \"Failed to init for encryption\");\n  return Cipher<EVP_PKEY_sign>(js, kj::mv(ctx), buffer,\n      {\n        .padding = options.padding,\n        .oaepHash = kj::String(),\n      });\n}\n\nnamespace {\nncrypto::Cipher getCipher(kj::OneOf<kj::String, int>& nameOrNid) {\n  KJ_SWITCH_ONEOF(nameOrNid) {\n    KJ_CASE_ONEOF(nid, int) {\n      return ncrypto::Cipher::FromNid(nid);\n    }\n    KJ_CASE_ONEOF(name, kj::String) {\n      return ncrypto::Cipher::FromName(name.cStr());\n    }\n  }\n  return {};\n}\n}  // namespace\n\njsg::Optional<CryptoImpl::CipherInfo> CryptoImpl::getCipherInfo(\n    kj::OneOf<kj::String, int> nameOrNid, CryptoImpl::GetCipherInfoOptions options) {\n\n  if (auto cipher = getCipher(nameOrNid)) {\n\n    int keyLength = cipher.getKeyLength();\n    int ivLength = cipher.getIvLength();\n\n    if (options.ivLength != kj::none || options.keyLength != kj::none) {\n      auto ctx = ncrypto::CipherCtxPointer::New();\n      if (!ctx.init(cipher, true)) return kj::none;\n      KJ_IF_SOME(len, options.keyLength) {\n        if (!ctx.setKeyLength(len)) return kj::none;\n        keyLength = len;\n      }\n      KJ_IF_SOME(len, options.ivLength) {\n        // For CCM modes, the IV may be between 7 and 13 bytes.\n        // For GCM and OCB modes, we'll check by attempting to\n        // set the value. For everything else, just check that\n        // check_len == iv_length.\n        switch (cipher.getMode()) {\n          case EVP_CIPH_CCM_MODE: {\n            if (len < 7 || len > 13) return kj::none;\n            break;\n          }\n          case EVP_CIPH_GCM_MODE: {\n            if (!ctx.setIvLength(len)) return kj::none;\n            break;\n          }\n          case EVP_CIPH_OCB_MODE: {\n            if (!ctx.setIvLength(len)) return kj::none;\n            break;\n          }\n          default:\n            if (len != ivLength) return kj::none;\n            break;\n        }\n        ivLength = len;\n      }\n    }\n\n    auto nameCstr = cipher.getName();\n    auto modeView = cipher.getModeLabel();\n    kj::String name = kj::heapString(nameCstr);\n    kj::String mode = kj::str(kj::heapArray<char>(modeView.data(), modeView.size()));\n\n    return CipherInfo{\n      .name = kj::mv(name),\n      .nid = cipher.getNid(),\n      .blockSize = cipher.getBlockSize(),\n      .ivLength = ivLength,\n      .keyLength = keyLength,\n      .mode = kj::mv(mode),\n    };\n  }\n\n  // If the cipher can't be found it might be an AEAD, which is handled using a different BoringSSL\n  // interface\n  KJ_SWITCH_ONEOF(nameOrNid) {\n    KJ_CASE_ONEOF(nid, int) {\n      // Can't safely find an AEAD by nid in boringssl\n      return kj::none;\n    }\n\n    KJ_CASE_ONEOF(name, kj::String) {\n      if (auto aead = ncrypto::Aead::FromName(std::string_view(name.begin(), name.size()))) {\n        auto modeView = aead.getModeLabel();\n        // The copy is strictly necessary\n        kj::String mode = kj::str(kj::heapArray<char>(modeView.data(), modeView.size()));\n\n        return CipherInfo{.name = kj::mv(name),\n          .nid = aead.getNid(),\n          .blockSize = aead.getBlockSize(),\n          .ivLength = aead.getNonceLength(),\n          .keyLength = aead.getKeyLength(),\n          .mode = kj::mv(mode)};\n      }\n    }\n  }\n\n  return kj::none;\n}\n\nkj::ArrayPtr<kj::StringPtr> CryptoImpl::getCiphers() {\n  // Cipher names are stored as string literals either within boringssl or ncrypto, so we can\n  // safely return pointers to them.\n  static kj::Array<kj::StringPtr> allCiphers = []() {\n    kj::Vector<kj::StringPtr> allCiphers;\n    ncrypto::Cipher::ForEach([&](const auto& name) { allCiphers.add(kj::StringPtr(name)); });\n\n    ncrypto::Aead::ForEach(\n        [&](const auto& name) { allCiphers.add(kj::StringPtr(name.data(), name.size())); });\n    return allCiphers.releaseAsArray();\n  }();\n\n  return allCiphers;\n}\n\n#pragma endregion  // Cipher/Decipher\n\n// =============================================================================\n#pragma region ECDH\n\nnamespace {\nncrypto::ECPointPointer bufferToPoint(const EC_GROUP* group, jsg::JsBufferSource& buf) {\n  JSG_REQUIRE(buf.size() <= INT32_MAX, Error, \"buffer is too big\");\n\n  auto pub = ncrypto::ECPointPointer::New(group);\n  JSG_REQUIRE(pub, Error, \"Failed to allocate EC_POINT for a public key\");\n\n  ncrypto::Buffer<const unsigned char> buffer{\n    .data = buf.asArrayPtr().begin(),\n    .len = buf.size(),\n  };\n\n  JSG_REQUIRE(pub.setFromBuffer(buffer, group), Error, \"Failed to set point\");\n  return pub;\n}\n\npoint_conversion_form_t getFormat(kj::StringPtr format) {\n  if (format == \"compressed\"_kj) return POINT_CONVERSION_COMPRESSED;\n  if (format == \"uncompressed\"_kj) return POINT_CONVERSION_UNCOMPRESSED;\n  if (format == \"hybrid\"_kj) return POINT_CONVERSION_HYBRID;\n  JSG_FAIL_REQUIRE(Error, \"Invalid ECDH public key format\");\n}\n\njsg::JsUint8Array ecPointToBuffer(\n    jsg::Lock& js, const EC_GROUP* group, const EC_POINT* point, point_conversion_form_t form) {\n  size_t len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr);\n  JSG_REQUIRE(len != 0, Error, \"Failed to get public key length\");\n\n  auto buf = jsg::JsUint8Array::create(js, len);\n\n  len = EC_POINT_point2oct(group, point, form, buf.asArrayPtr().begin(), buf.size(), nullptr);\n  JSG_REQUIRE(len != 0, Error, \"Failed to get public key\");\n\n  return buf;\n}\n\nbool isKeyValidForCurve(const EC_GROUP* group, const ncrypto::BignumPointer& private_key) {\n  // Private keys must be in the range [1, n-1].\n  // Ref: Section 3.2.1 - http://www.secg.org/sec1-v2.pdf\n  if (private_key < ncrypto::BignumPointer::One()) {\n    return false;\n  }\n  auto order = ncrypto::BignumPointer::New();\n  JSG_REQUIRE(order, Error, \"Internal failure when checking ECDH key\");\n  return EC_GROUP_get_order(group, order.get(), nullptr) && private_key < order;\n}\n}  // namespace\n\nCryptoImpl::ECDHHandle::ECDHHandle(ncrypto::ECKeyPointer key)\n    : key_(kj::mv(key)),\n      group_(key_.getGroup()) {}\n\njsg::Ref<CryptoImpl::ECDHHandle> CryptoImpl::ECDHHandle::constructor(\n    jsg::Lock& js, kj::String curveName) {\n\n  int nid = OBJ_sn2nid(curveName.begin());\n  JSG_REQUIRE(nid != NID_undef, Error, \"Invalid curve\");\n\n  auto key = ncrypto::ECKeyPointer::NewByCurveName(nid);\n  JSG_REQUIRE(key, Error, \"Failed to create key using named curve\");\n\n  return js.alloc<CryptoImpl::ECDHHandle>(kj::mv(key));\n}\n\njsg::JsUint8Array CryptoImpl::ECDHHandle::computeSecret(\n    jsg::Lock& js, jsg::JsBufferSource otherPublicKey) {\n\n  ncrypto::ClearErrorOnReturn clear_error_on_return;\n\n  JSG_REQUIRE(key_.checkKey(), Error, \"Invalid keypair\");\n\n  auto pub = bufferToPoint(group_, otherPublicKey);\n  JSG_REQUIRE(pub, Error, \"Invalid to set ECDH public key\");\n\n  int field_size = EC_GROUP_get_degree(group_);\n  size_t out_len = (field_size + 7) / 8;\n\n  auto buf = jsg::JsUint8Array::create(js, out_len);\n\n  JSG_REQUIRE(ECDH_compute_key(buf.asArrayPtr().begin(), out_len, pub, key_.get(), nullptr), Error,\n      \"Failed to compute ECDH key\");\n\n  return buf;\n}\n\nvoid CryptoImpl::ECDHHandle::generateKeys() {\n  ncrypto::ClearErrorOnReturn clear_error_on_return;\n  JSG_REQUIRE(key_.generate(), Error, \"Failed to generate keys\");\n}\n\njsg::JsUint8Array CryptoImpl::ECDHHandle::getPrivateKey(jsg::Lock& js) {\n  auto b = key_.getPrivateKey();\n  JSG_REQUIRE(b != nullptr, Error, \"Failed to get ECDH private key\");\n  auto buf = jsg::JsUint8Array::create(js, ncrypto::BignumPointer::GetByteCount(b));\n  JSG_REQUIRE(buf.size() ==\n          ncrypto::BignumPointer::EncodePaddedInto(b, buf.asArrayPtr().begin(), buf.size()),\n      Error, \"Failed to encode the private key\");\n  return buf;\n}\n\njsg::JsUint8Array CryptoImpl::ECDHHandle::getPublicKey(jsg::Lock& js, kj::String format) {\n  const auto group = key_.getGroup();\n  const auto pub = key_.getPublicKey();\n  JSG_REQUIRE(pub != nullptr, Error, \"Failed to get ECDH public key\");\n  point_conversion_form_t form = getFormat(format);\n  return ecPointToBuffer(js, group, pub, form);\n}\n\nvoid CryptoImpl::ECDHHandle::setPrivateKey(jsg::Lock& js, jsg::JsBufferSource key) {\n\n  JSG_REQUIRE(key.size() <= INT32_MAX, Error, \"key is too big\");\n\n  ncrypto::BignumPointer priv(key.asArrayPtr().begin(), key.size());\n  JSG_REQUIRE(priv, Error, \"Failed to convert buffer to BN\");\n\n  JSG_REQUIRE(\n      isKeyValidForCurve(group_, priv), Error, \"Private key is not valid for specified curve.\");\n\n  auto new_key = key_.clone();\n  JSG_REQUIRE(new_key, Error, \"Internal error when setting private key\");\n\n  bool result = new_key.setPrivateKey(priv);\n  priv.reset();\n\n  JSG_REQUIRE(result, Error, \"Failed to convert BN to a private key\");\n\n  ncrypto::ClearErrorOnReturn clear_error_on_return;\n\n  auto priv_key = new_key.getPrivateKey();\n  JSG_REQUIRE(priv_key, Error, \"Failed to get ECDH private key\");\n\n  auto pub = ncrypto::ECPointPointer::New(group_);\n  JSG_REQUIRE(pub, Error, \"Internal error when initializing new EC point\");\n\n  JSG_REQUIRE(pub.mul(group_, priv_key), Error, \"Failed to generate ECDH public key\");\n\n  JSG_REQUIRE(new_key.setPublicKey(pub), Error, \"Failed to set generated public key\");\n\n  key_ = std::move(new_key);\n  group_ = key_.getGroup();\n}\n\njsg::JsUint8Array CryptoImpl::ECDHHandle::convertKey(\n    jsg::Lock& js, jsg::JsBufferSource key, kj::String curveName, kj::String format) {\n  ncrypto::ClearErrorOnReturn clear_error_on_return;\n\n  JSG_REQUIRE(key.size() <= INT32_MAX, Error, \"key is too big\");\n  if (key.size() == 0) {\n    return jsg::JsUint8Array::create(js, 0);\n  }\n\n  int nid = OBJ_sn2nid(curveName.begin());\n  JSG_REQUIRE(nid != NID_undef, Error, \"Invalid curve\");\n\n  auto group = ncrypto::ECGroupPointer::NewByCurveName(nid);\n\n  auto pub = bufferToPoint(group, key);\n  JSG_REQUIRE(pub, Error, \"Failed to convert buffer to EC_POINT\");\n\n  point_conversion_form_t form = getFormat(format);\n\n  return ecPointToBuffer(js, group, pub, form);\n}\n\n#pragma endregion  // ECDH\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/crypto.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <workerd/api/crypto/crypto.h>\n#include <workerd/api/crypto/dh.h>\n#include <workerd/api/crypto/digest.h>\n#include <workerd/api/crypto/x509.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/jsvalue.h>\n\n#include <ncrypto/aead.h>\n\nnamespace workerd::api::node {\n\nclass CryptoImpl final: public jsg::Object {\n public:\n  // DH\n  class DiffieHellmanHandle final: public jsg::Object {\n   public:\n    DiffieHellmanHandle(DiffieHellman dh);\n\n    static jsg::Ref<DiffieHellmanHandle> constructor(jsg::Lock& js,\n        kj::OneOf<kj::Array<kj::byte>, int> sizeOrKey,\n        kj::OneOf<kj::Array<kj::byte>, int> generator);\n\n    void setPrivateKey(kj::Array<kj::byte> key);\n    void setPublicKey(kj::Array<kj::byte> key);\n    jsg::JsUint8Array getPublicKey(jsg::Lock& js);\n    jsg::JsUint8Array getPrivateKey(jsg::Lock& js);\n    jsg::JsUint8Array getGenerator(jsg::Lock& js);\n    jsg::JsUint8Array getPrime(jsg::Lock& js);\n    jsg::JsUint8Array computeSecret(jsg::Lock& js, kj::Array<kj::byte> key);\n    jsg::JsUint8Array generateKeys(jsg::Lock& js);\n    int getVerifyError();\n\n    JSG_RESOURCE_TYPE(DiffieHellmanHandle) {\n      JSG_METHOD(setPublicKey);\n      JSG_METHOD(setPrivateKey);\n      JSG_METHOD(getPublicKey);\n      JSG_METHOD(getPrivateKey);\n      JSG_METHOD(getGenerator);\n      JSG_METHOD(getPrime);\n      JSG_METHOD(computeSecret);\n      JSG_METHOD(generateKeys);\n      JSG_METHOD(getVerifyError);\n    };\n\n   private:\n    DiffieHellman dh;\n    int verifyError;\n  };\n\n  jsg::Ref<DiffieHellmanHandle> DiffieHellmanGroupHandle(jsg::Lock& js, kj::String name);\n\n  jsg::JsUint8Array statelessDH(\n      jsg::Lock& js, jsg::Ref<CryptoKey> privateKey, jsg::Ref<CryptoKey> publicKey);\n\n  // Primes\n  jsg::JsArrayBuffer randomPrime(jsg::Lock& js,\n      uint32_t size,\n      bool safe,\n      jsg::Optional<kj::Array<kj::byte>> add,\n      jsg::Optional<kj::Array<kj::byte>> rem);\n  bool checkPrimeSync(kj::Array<kj::byte> bufferView, uint32_t num_checks);\n\n  // Hash\n  class HashHandle final: public jsg::Object {\n   public:\n    HashHandle(HashContext ctx): ctx(kj::mv(ctx)) {}\n\n    static jsg::Ref<HashHandle> constructor(\n        jsg::Lock& js, kj::String algorithm, kj::Maybe<uint32_t> xofLen);\n    static jsg::JsUint8Array oneshot(\n        jsg::Lock&, kj::String algorithm, kj::Array<kj::byte> data, kj::Maybe<uint32_t> xofLen);\n\n    jsg::Ref<HashHandle> copy(jsg::Lock& js, kj::Maybe<uint32_t> xofLen);\n    int update(kj::Array<kj::byte> data);\n    jsg::JsUint8Array digest(jsg::Lock& js);\n\n    JSG_RESOURCE_TYPE(HashHandle) {\n      JSG_METHOD(update);\n      JSG_METHOD(digest);\n      JSG_METHOD(copy);\n      JSG_STATIC_METHOD(oneshot);\n    };\n\n    void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n   private:\n    HashContext ctx;\n  };\n\n  // Hmac\n  class HmacHandle final: public jsg::Object {\n   public:\n    using KeyParam = kj::OneOf<kj::Array<kj::byte>, jsg::Ref<CryptoKey>>;\n\n    HmacHandle(HmacContext ctx): ctx(kj::mv(ctx)) {};\n\n    static jsg::Ref<HmacHandle> constructor(jsg::Lock& js, kj::String algorithm, KeyParam key);\n\n    // Efficiently implement one-shot HMAC that avoids multiple calls\n    // across the C++/JS boundary.\n    static jsg::JsUint8Array oneshot(\n        jsg::Lock& js, kj::String algorithm, KeyParam key, kj::Array<kj::byte> data);\n\n    int update(kj::Array<kj::byte> data);\n    jsg::JsUint8Array digest(jsg::Lock& js);\n\n    JSG_RESOURCE_TYPE(HmacHandle) {\n      JSG_METHOD(update);\n      JSG_METHOD(digest);\n      JSG_STATIC_METHOD(oneshot);\n    };\n\n    void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n   private:\n    HmacContext ctx;\n  };\n\n  // Hkdf\n  jsg::JsArrayBuffer getHkdf(jsg::Lock& js,\n      kj::String hash,\n      kj::Array<const kj::byte> key,\n      kj::Array<const kj::byte> salt,\n      kj::Array<const kj::byte> info,\n      uint32_t length);\n\n  // Pbkdf2\n  jsg::JsArrayBuffer getPbkdf(jsg::Lock& js,\n      kj::Array<const kj::byte> password,\n      kj::Array<const kj::byte> salt,\n      uint32_t num_iterations,\n      uint32_t keylen,\n      kj::String name);\n\n  // Scrypt\n  jsg::JsArrayBuffer getScrypt(jsg::Lock& js,\n      kj::Array<const kj::byte> password,\n      kj::Array<const kj::byte> salt,\n      uint32_t N,\n      uint32_t r,\n      uint32_t p,\n      uint32_t maxmem,\n      uint32_t keylen);\n\n  // Keys\n  struct KeyExportOptions {\n    jsg::Optional<kj::String> type;\n    jsg::Optional<kj::String> format;\n    jsg::Optional<kj::String> cipher;\n    jsg::Optional<kj::Array<kj::byte>> passphrase;\n    JSG_STRUCT(type, format, cipher, passphrase);\n  };\n\n  struct GenerateKeyPairOptions {\n    jsg::Optional<uint32_t> modulusLength;\n    jsg::Optional<uint64_t> publicExponent;\n    jsg::Optional<kj::String> hashAlgorithm;\n    jsg::Optional<kj::String> mgf1HashAlgorithm;\n    jsg::Optional<uint32_t> saltLength;\n    jsg::Optional<uint32_t> divisorLength;\n    jsg::Optional<kj::String> namedCurve;\n    jsg::Optional<kj::Array<kj::byte>> prime;\n    jsg::Optional<uint32_t> primeLength;\n    jsg::Optional<uint32_t> generator;\n    jsg::Optional<kj::String> groupName;\n    jsg::Optional<kj::String> paramEncoding;  // one of either 'named' or 'explicit'\n    jsg::Optional<KeyExportOptions> publicKeyEncoding;\n    jsg::Optional<KeyExportOptions> privateKeyEncoding;\n\n    JSG_STRUCT(modulusLength,\n        publicExponent,\n        hashAlgorithm,\n        mgf1HashAlgorithm,\n        saltLength,\n        divisorLength,\n        namedCurve,\n        prime,\n        primeLength,\n        generator,\n        groupName,\n        paramEncoding,\n        publicKeyEncoding,\n        privateKeyEncoding);\n  };\n\n  struct CreateAsymmetricKeyOptions {\n    kj::OneOf<jsg::JsRef<jsg::JsBufferSource>, SubtleCrypto::JsonWebKey, jsg::Ref<api::CryptoKey>>\n        key;\n    kj::String format;\n    jsg::Optional<kj::String> type;\n    jsg::Optional<jsg::JsRef<jsg::JsBufferSource>> passphrase;\n    // The passphrase is only used for private keys. The format, type, and passphrase\n    // options are only used if the key is a kj::Array<kj::byte>.\n    JSG_STRUCT(key, format, type, passphrase);\n  };\n\n  CryptoImpl() = default;\n  CryptoImpl(jsg::Lock&, const jsg::Url&) {}\n\n  kj::OneOf<kj::String, jsg::JsArrayBuffer, SubtleCrypto::JsonWebKey> exportKey(\n      jsg::Lock& js, jsg::Ref<CryptoKey> key, jsg::Optional<KeyExportOptions> options);\n\n  bool equals(jsg::Lock& js, jsg::Ref<CryptoKey> key, jsg::Ref<CryptoKey> otherKey);\n\n  CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail(jsg::Lock& js, jsg::Ref<CryptoKey> key);\n  kj::StringPtr getAsymmetricKeyType(jsg::Lock& js, jsg::Ref<CryptoKey> key);\n\n  jsg::Ref<CryptoKey> createSecretKey(jsg::Lock& js, jsg::JsBufferSource keyData);\n  jsg::Ref<CryptoKey> createPrivateKey(jsg::Lock& js, CreateAsymmetricKeyOptions options);\n  jsg::Ref<CryptoKey> createPublicKey(jsg::Lock& js, CreateAsymmetricKeyOptions options);\n  static kj::Maybe<const ncrypto::EVPKeyPointer&> tryGetKey(jsg::Ref<CryptoKey>& key);\n  static kj::Maybe<kj::ArrayPtr<const kj::byte>> tryGetSecretKeyData(jsg::Ref<CryptoKey>& key);\n\n  struct RsaKeyPairOptions {\n    kj::String type;\n    uint32_t modulusLength;\n    uint32_t publicExponent;\n    jsg::Optional<uint32_t> saltLength;\n    jsg::Optional<kj::String> hashAlgorithm;\n    jsg::Optional<kj::String> mgf1HashAlgorithm;\n    JSG_STRUCT(type, modulusLength, publicExponent, saltLength, hashAlgorithm, mgf1HashAlgorithm);\n  };\n\n  struct DsaKeyPairOptions {\n    uint32_t modulusLength;\n    jsg::Optional<uint32_t> divisorLength;\n    JSG_STRUCT(modulusLength, divisorLength);\n  };\n\n  struct EcKeyPairOptions {\n    kj::String namedCurve;\n    kj::String paramEncoding;\n    JSG_STRUCT(namedCurve, paramEncoding);\n  };\n\n  struct EdKeyPairOptions {\n    kj::String type;\n    JSG_STRUCT(type);\n  };\n\n  struct DhKeyPairOptions {\n    kj::OneOf<jsg::JsRef<jsg::JsBufferSource>, uint32_t, kj::String> primeOrGroup;\n    jsg::Optional<uint32_t> generator;\n    JSG_STRUCT(primeOrGroup, generator);\n  };\n\n  CryptoKeyPair generateRsaKeyPair(jsg::Lock& js, RsaKeyPairOptions options);\n  CryptoKeyPair generateDsaKeyPair(jsg::Lock& js, DsaKeyPairOptions options);\n  CryptoKeyPair generateEcKeyPair(jsg::Lock& js, EcKeyPairOptions options);\n  CryptoKeyPair generateEdKeyPair(jsg::Lock& js, EdKeyPairOptions options);\n  CryptoKeyPair generateDhKeyPair(jsg::Lock& js, DhKeyPairOptions options);\n\n  // Sign/Verify\n  class SignHandle final: public jsg::Object {\n   public:\n    SignHandle(ncrypto::EVPMDCtxPointer ctx);\n    static jsg::Ref<SignHandle> constructor(jsg::Lock& js, kj::String algorithm);\n\n    void update(jsg::Lock& js, jsg::JsBufferSource data);\n    jsg::JsUint8Array sign(jsg::Lock& js,\n        jsg::Ref<CryptoKey> key,\n        jsg::Optional<int> rsaPadding,\n        jsg::Optional<int> pssSaltLength,\n        jsg::Optional<int> dsaSigEnc);\n\n    JSG_RESOURCE_TYPE(SignHandle) {\n      JSG_METHOD(update);\n      JSG_METHOD(sign);\n    }\n\n   private:\n    ncrypto::EVPMDCtxPointer ctx;\n  };\n  class VerifyHandle final: public jsg::Object {\n   public:\n    VerifyHandle(ncrypto::EVPMDCtxPointer ctx);\n    static jsg::Ref<VerifyHandle> constructor(jsg::Lock& js, kj::String algorithm);\n\n    void update(jsg::Lock& js, jsg::JsBufferSource data);\n    bool verify(jsg::Lock& js,\n        jsg::Ref<CryptoKey> key,\n        jsg::JsBufferSource signature,\n        jsg::Optional<int> rsaPadding,\n        jsg::Optional<int> pssSaltLength,\n        jsg::Optional<int> dsaSigEnc);\n\n    JSG_RESOURCE_TYPE(VerifyHandle) {\n      JSG_METHOD(update);\n      JSG_METHOD(verify);\n    }\n\n   private:\n    ncrypto::EVPMDCtxPointer ctx;\n  };\n\n  jsg::JsUint8Array signOneShot(jsg::Lock& js,\n      jsg::Ref<CryptoKey> key,\n      jsg::Optional<kj::String> algorithm,\n      jsg::JsBufferSource data,\n      jsg::Optional<int> rsaPadding,\n      jsg::Optional<int> pssSaltLength,\n      jsg::Optional<int> dsaSigEnc);\n  bool verifyOneShot(jsg::Lock& js,\n      jsg::Ref<CryptoKey> key,\n      jsg::Optional<kj::String> algorithm,\n      jsg::JsBufferSource data,\n      jsg::JsBufferSource signature,\n      jsg::Optional<int> rsaPadding,\n      jsg::Optional<int> pssSaltLength,\n      jsg::Optional<int> dsaSigEnc);\n\n  // Cipher/Decipher\n  enum class CipherMode { CIPHER, DECIPHER };\n  class CipherHandle final: public jsg::Object {\n   public:\n    struct AuthenticatedInfo {\n      unsigned int auth_tag_len = 0;\n      unsigned int max_message_size = INT_MAX;\n    };\n\n    CipherHandle(jsg::Lock& js,\n        CipherMode mode,\n        ncrypto::CipherCtxPointer ctx,\n        jsg::Ref<CryptoKey> key,\n        jsg::JsBufferSource iv,\n        kj::Maybe<AuthenticatedInfo> maybeAuthInfo);\n\n    static jsg::Ref<CipherHandle> construct(jsg::Lock& js,\n        CipherMode mode,\n        kj::StringPtr algorithm,\n        ncrypto::Cipher cipher,\n        jsg::Ref<CryptoKey> key,\n        jsg::JsBufferSource iv,\n        jsg::Optional<uint32_t> maybeAuthTagLength);\n\n    jsg::JsUint8Array update(jsg::Lock& js, jsg::JsBufferSource data);\n    jsg::JsUint8Array final(jsg::Lock& js);\n    void setAAD(\n        jsg::Lock& js, jsg::JsBufferSource aad, jsg::Optional<uint32_t> maybePlaintextLength);\n    void setAutoPadding(jsg::Lock& js, bool autoPadding);\n    void setAuthTag(jsg::Lock& js, jsg::JsBufferSource authTag);\n    jsg::JsUint8Array getAuthTag(jsg::Lock& js);\n\n    JSG_RESOURCE_TYPE(CipherHandle) {\n      JSG_METHOD(update);\n      JSG_METHOD(final);\n      JSG_METHOD(setAAD);\n      JSG_METHOD(setAutoPadding);\n      JSG_METHOD(setAuthTag);\n      JSG_METHOD(getAuthTag);\n    };\n\n   private:\n    CipherMode mode;\n    ncrypto::CipherCtxPointer ctx;\n    jsg::Ref<CryptoKey> key;\n    jsg::JsRef<jsg::JsBufferSource> iv;\n    kj::Maybe<jsg::JsRef<jsg::JsBufferSource>> maybeAuthTag;\n    kj::Maybe<AuthenticatedInfo> maybeAuthInfo;\n    bool authTagPassed = false;\n    bool pendingAuthFailed = false;\n  };\n\n  /*\n  * AeadHandle implements a public interface matching CipherHandle, based on the BoringSSL-specific\n  * EVP_AEAD API instead of the EVP_CIPHER API.\n  *\n  * It's essential to note that BoringSSL's EVP_AEAD API is *one-shot*, and will encrypt or decrypt\n  * the entire ciphertext at once, and doesn't provide support for streaming operations. This is\n  * for good reason, as it prevents a dangerous mistake from being made. Consider a decryption\n  * operation: While it's technically possible to begin streaming chunks of decrypted data, it\n  * is not safe to act on any of the data until the entire message is decrypted, validated and\n  * released. If any part of the message is invalid, all of that decrypted data must be discarded.\n  *\n  * As a result, it's only possible to call update() once when using an AEAD algorithm, followed\n  * by final(). If using the streaming interface, it is permitted to either call write() once\n  * followed by end() without data; or to call end() once with a chunk of data.\n  *\n  * This restriction applies for certain algorithms even in the original NodeJS implementation\n  * backed by OpenSSL. For example, see the note in the original documentation about CCM modes\n  * of operation: <https://nodejs.org/api/crypto.html#ccm-mode>\n  *\n  * However, in our implementation, this restriction applies for *all* AEAD algorithms, which\n  * differs from the behaviour of NodeJS.\n  *\n  * In principle, it's possible to allow multiple update() calls if required for a particular use\n  * case, by buffering all the data supplied in memory, then invoking BoringSSL when final() is\n  * called. This might not be as troubling as it sounds, as AEADs are usually used to protect\n  * reasonably-sized messages used by an application, and aren't used to handle large quantities\n  * of data. However, this is not yet implemented, until we see a convincing use case.\n  */\n  class AeadHandle final: public jsg::Object {\n   public:\n    struct AuthenticatedInfo {\n      unsigned int auth_tag_len = 0;\n      unsigned int max_message_size = INT_MAX;\n    };\n\n    AeadHandle(jsg::Lock& js,\n        CipherMode mode,\n        ncrypto::Aead aead,\n        ncrypto::AeadCtxPointer ctx,\n        jsg::Ref<CryptoKey> key,\n        jsg::JsBufferSource iv,\n        kj::Maybe<AuthenticatedInfo> maybeAuthInfo);\n\n    static jsg::Ref<AeadHandle> construct(jsg::Lock& js,\n        CipherMode mode,\n        kj::StringPtr algorithm,\n        ncrypto::Aead aead,\n        jsg::Ref<CryptoKey> key,\n        jsg::JsBufferSource iv,\n        jsg::Optional<uint32_t> maybeAuthTagLength);\n\n    jsg::JsUint8Array update(jsg::Lock& js, jsg::JsBufferSource data);\n    jsg::JsUint8Array final(jsg::Lock& js);\n    void setAAD(\n        jsg::Lock& js, jsg::JsBufferSource aad, jsg::Optional<uint32_t> maybePlaintextLength);\n    void setAutoPadding(jsg::Lock&, bool);\n    void setAuthTag(jsg::Lock& js, jsg::JsBufferSource authTag);\n    jsg::JsUint8Array getAuthTag(jsg::Lock& js);\n\n    JSG_RESOURCE_TYPE(AeadHandle) {\n      JSG_METHOD(update);\n      JSG_METHOD(final);\n      JSG_METHOD(setAAD);\n      JSG_METHOD(setAutoPadding);\n      JSG_METHOD(setAuthTag);\n      JSG_METHOD(getAuthTag);\n    };\n\n   private:\n    CipherMode mode;\n    ncrypto::Aead aead;\n    ncrypto::AeadCtxPointer ctx;\n    jsg::Ref<CryptoKey> key;\n    jsg::JsRef<jsg::JsBufferSource> iv;\n    kj::Maybe<jsg::JsRef<jsg::JsBufferSource>> maybeAuthTag;\n    kj::Maybe<AuthenticatedInfo> maybeAuthInfo;\n    kj::Maybe<jsg::JsRef<jsg::JsBufferSource>> maybeAad;\n    bool updated = false;\n  };\n\n  kj::OneOf<jsg::Ref<CipherHandle>, jsg::Ref<AeadHandle>> newHandle(jsg::Lock& js,\n      kj::uint mode,\n      kj::String algorithm,\n      jsg::Ref<CryptoKey> key,\n      jsg::JsBufferSource iv,\n      jsg::Optional<uint32_t> maybeAuthTagLength);\n\n  struct PublicPrivateCipherOptions {\n    int padding;\n    kj::String oaepHash;\n    jsg::Optional<jsg::JsRef<jsg::JsBufferSource>> oaepLabel;\n    JSG_STRUCT(padding, oaepHash, oaepLabel);\n  };\n\n  jsg::JsUint8Array publicEncrypt(jsg::Lock& js,\n      jsg::Ref<CryptoKey> key,\n      jsg::JsBufferSource buffer,\n      PublicPrivateCipherOptions options);\n  jsg::JsUint8Array publicDecrypt(jsg::Lock& js,\n      jsg::Ref<CryptoKey> key,\n      jsg::JsBufferSource buffer,\n      PublicPrivateCipherOptions options);\n  jsg::JsUint8Array privateEncrypt(jsg::Lock& js,\n      jsg::Ref<CryptoKey> key,\n      jsg::JsBufferSource buffer,\n      PublicPrivateCipherOptions options);\n  jsg::JsUint8Array privateDecrypt(jsg::Lock& js,\n      jsg::Ref<CryptoKey> key,\n      jsg::JsBufferSource buffer,\n      PublicPrivateCipherOptions options);\n\n  struct CipherInfo {\n    kj::String name;\n    int nid;\n    jsg::Optional<int> blockSize;\n    jsg::Optional<int> ivLength;\n    int keyLength;\n    kj::String mode;  // 'cbc', 'ccm', 'cfb', 'ctr', 'ecb', 'gcm', 'ocb',\n                      // 'ofb', 'stream', 'wrap', 'xts'\n    JSG_STRUCT(name, nid, blockSize, ivLength, keyLength, mode)\n  };\n\n  struct GetCipherInfoOptions {\n    jsg::Optional<int> keyLength;\n    jsg::Optional<int> ivLength;\n    JSG_STRUCT(keyLength, ivLength);\n  };\n\n  jsg::Optional<CipherInfo> getCipherInfo(\n      kj::OneOf<kj::String, int> nameOrNid, GetCipherInfoOptions options);\n\n  kj::ArrayPtr<kj::StringPtr> getCiphers();\n\n  // SPKAC\n  bool verifySpkac(kj::Array<const kj::byte> input);\n  kj::Maybe<jsg::JsUint8Array> exportPublicKey(jsg::Lock& js, kj::Array<const kj::byte> input);\n  kj::Maybe<jsg::JsUint8Array> exportChallenge(jsg::Lock& js, kj::Array<const kj::byte> input);\n\n  // ECDH\n  class ECDHHandle final: public jsg::Object {\n   public:\n    ECDHHandle(ncrypto::ECKeyPointer key);\n    static jsg::Ref<ECDHHandle> constructor(jsg::Lock& js, kj::String curveName);\n\n    static jsg::JsUint8Array convertKey(\n        jsg::Lock& js, jsg::JsBufferSource key, kj::String curveName, kj::String format);\n\n    jsg::JsUint8Array computeSecret(jsg::Lock& js, jsg::JsBufferSource otherPublicKey);\n    void generateKeys();\n    jsg::JsUint8Array getPrivateKey(jsg::Lock& js);\n    jsg::JsUint8Array getPublicKey(jsg::Lock& js, kj::String format);\n    void setPrivateKey(jsg::Lock& js, jsg::JsBufferSource key);\n\n    JSG_RESOURCE_TYPE(ECDHHandle) {\n      JSG_STATIC_METHOD(convertKey);\n      JSG_METHOD(computeSecret);\n      JSG_METHOD(generateKeys);\n      JSG_METHOD(getPrivateKey);\n      JSG_METHOD(getPublicKey);\n      JSG_METHOD(setPrivateKey);\n    }\n\n   private:\n    ncrypto::ECKeyPointer key_;\n    const EC_GROUP* group_;\n  };\n\n  JSG_RESOURCE_TYPE(CryptoImpl) {\n    // DH\n    JSG_NESTED_TYPE(DiffieHellmanHandle);\n    JSG_METHOD(DiffieHellmanGroupHandle);\n    JSG_METHOD(statelessDH);\n    // ECDH\n    JSG_NESTED_TYPE(ECDHHandle);\n    // Primes\n    JSG_METHOD(randomPrime);\n    JSG_METHOD(checkPrimeSync);\n    // Hash and Hmac\n    JSG_NESTED_TYPE(HashHandle);\n    JSG_NESTED_TYPE(HmacHandle);\n    // Hkdf\n    JSG_METHOD(getHkdf);\n    // Pbkdf2\n    JSG_METHOD(getPbkdf);\n    // Scrypt\n    JSG_METHOD(getScrypt);\n    // Keys\n    JSG_METHOD(exportKey);\n    JSG_METHOD(equals);\n    JSG_METHOD(getAsymmetricKeyDetail);\n    JSG_METHOD(getAsymmetricKeyType);\n    JSG_METHOD(createSecretKey);\n    JSG_METHOD(createPrivateKey);\n    JSG_METHOD(createPublicKey);\n    // Spkac\n    JSG_METHOD(verifySpkac);\n    JSG_METHOD(exportPublicKey);\n    JSG_METHOD(exportChallenge);\n    // X509\n    JSG_NESTED_TYPE(X509Certificate);\n    // Key generation\n    JSG_METHOD(generateRsaKeyPair);\n    JSG_METHOD(generateDsaKeyPair);\n    JSG_METHOD(generateEcKeyPair);\n    JSG_METHOD(generateEdKeyPair);\n    JSG_METHOD(generateDhKeyPair);\n    // Sign/Verify\n    JSG_NESTED_TYPE(SignHandle);\n    JSG_NESTED_TYPE(VerifyHandle);\n    JSG_METHOD(signOneShot);\n    JSG_METHOD(verifyOneShot);\n    // Cipher/Decipher\n    JSG_NESTED_TYPE(CipherHandle);\n    JSG_NESTED_TYPE(AeadHandle);\n    JSG_METHOD(newHandle);\n    JSG_METHOD(publicEncrypt);\n    JSG_METHOD(publicDecrypt);\n    JSG_METHOD(privateEncrypt);\n    JSG_METHOD(privateDecrypt);\n    JSG_METHOD(getCipherInfo);\n    JSG_METHOD(getCiphers);\n  }\n};\n\n#define EW_NODE_CRYPTO_ISOLATE_TYPES                                                               \\\n  api::node::CryptoImpl, api::node::CryptoImpl::DiffieHellmanHandle,                               \\\n      api::node::CryptoImpl::HashHandle, api::node::CryptoImpl::HmacHandle,                        \\\n      api::node::CryptoImpl::KeyExportOptions, api::node::CryptoImpl::GenerateKeyPairOptions,      \\\n      api::node::CryptoImpl::CreateAsymmetricKeyOptions, api::node::CryptoImpl::RsaKeyPairOptions, \\\n      api::node::CryptoImpl::DsaKeyPairOptions, api::node::CryptoImpl::EcKeyPairOptions,           \\\n      api::node::CryptoImpl::EdKeyPairOptions, api::node::CryptoImpl::DhKeyPairOptions,            \\\n      api::node::CryptoImpl::SignHandle, api::node::CryptoImpl::VerifyHandle,                      \\\n      api::node::CryptoImpl::CipherHandle, api::node::CryptoImpl::PublicPrivateCipherOptions,      \\\n      api::node::CryptoImpl::CipherInfo, api::node::CryptoImpl::GetCipherInfoOptions,              \\\n      api::node::CryptoImpl::ECDHHandle, api::node::CryptoImpl::AeadHandle,                        \\\n      EW_CRYPTO_X509_ISOLATE_TYPES\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/diagnostics-channel.c++",
    "content": "#include \"diagnostics-channel.h\"\n\n#include <workerd/io/io-context.h>\n#include <workerd/io/trace.h>\n#include <workerd/io/tracer.h>\n#include <workerd/jsg/ser.h>\n\nnamespace workerd::api::node {\n\njsg::Value Channel::identityTransform(jsg::Lock& js, jsg::Value value) {\n  return value.addRef(js);\n}\n\nChannel::Channel(jsg::Name name): name(kj::mv(name)) {}\n\nconst jsg::Name& Channel::getName() const {\n  return name;\n}\n\nbool Channel::hasSubscribers() {\n  return subscribers.size() != 0;\n}\n\nvoid Channel::publish(jsg::Lock& js, jsg::Value message) {\n  auto snapshot = KJ_MAP(sub, subscribers) -> MessageCallback { return sub.value.addRef(js); };\n  for (auto& cb: snapshot) {\n    cb(js, message.addRef(js), name.clone(js));\n  }\n\n  auto& context = IoContext::current();\n  KJ_IF_SOME(tracer, context.getWorkerTracer()) {\n    js.tryCatch([&]() {\n      jsg::Serializer ser(js,\n          jsg::Serializer::Options{\n            .omitHeader = false,\n          });\n      ser.write(js, jsg::JsValue(message.getHandle(js)));\n      auto tmp = ser.release();\n      JSG_REQUIRE(tmp.sharedArrayBuffers.size() == 0 && tmp.transferredArrayBuffers.size() == 0,\n          Error,\n          \"Diagnostic events cannot be published with SharedArrayBuffer or \"\n          \"transferred ArrayBuffer instances\");\n      tracer.addDiagnosticChannelEvent(\n          context.getInvocationSpanContext(), context.now(), name.toString(js), kj::mv(tmp.data));\n    }, [&](jsg::Value&& exception) {\n      jsg::JsValue jsException(exception.getHandle(js));\n      tracer.addException(context.getInvocationSpanContext(), context.now(), kj::str(\"Error\"),\n          kj::str(\"Failed to publish diagnostics channel message: \", jsException), kj::none);\n    });\n  }\n}\n\nvoid Channel::subscribe(jsg::Lock& js, jsg::Identified<MessageCallback> callback) {\n  subscribers.upsert(kj::mv(callback.identity), kj::mv(callback.unwrapped), [&](auto&, auto&&) {});\n}\n\nvoid Channel::unsubscribe(jsg::Lock& js, jsg::Identified<MessageCallback> callback) {\n  subscribers.erase(callback.identity);\n}\n\nvoid Channel::bindStore(jsg::Lock& js,\n    jsg::Ref<AsyncLocalStorage> als,\n    jsg::Optional<TransformCallback> maybeTransform) {\n  auto key = als->getKey();\n  KJ_IF_SOME(entry, stores.find(*key)) {\n    KJ_IF_SOME(transform, maybeTransform) {\n      entry.transform = kj::mv(transform);\n    } else {\n      entry.transform = [](jsg::Lock& js, jsg::Value value) {\n        return identityTransform(js, kj::mv(value));\n      };\n    }\n    return;\n  }\n\n  KJ_IF_SOME(transform, maybeTransform) {\n    stores.insert({.key = kj::mv(key), .transform = kj::mv(transform)});\n  } else {\n    stores.insert({.key = kj::mv(key), .transform = [](jsg::Lock& js, jsg::Value value) {\n      return identityTransform(js, kj::mv(value));\n    }});\n  }\n}\n\nvoid Channel::unbindStore(jsg::Lock& js, jsg::Ref<AsyncLocalStorage> als) {\n  auto key = als->getKey();\n  stores.eraseMatch(*key);\n}\n\nv8::Local<v8::Value> Channel::runStores(jsg::Lock& js,\n    jsg::Value message,\n    jsg::Function<v8::Local<v8::Value>(jsg::Arguments<jsg::Value>)> callback,\n    jsg::Optional<v8::Local<v8::Value>> maybeReceiver,\n    jsg::Arguments<jsg::Value> args) {\n  struct StoreSnapshot {\n    kj::Own<jsg::AsyncContextFrame::StorageKey> key;\n    TransformCallback transform;\n  };\n  auto snapshot = KJ_MAP(store, stores) -> StoreSnapshot {\n    return {kj::addRef(*store.key), store.transform.addRef(js)};\n  };\n  kj::Vector<kj::Own<jsg::AsyncContextFrame::StorageScope>> storageScopes;\n  for (auto& entry: snapshot) {\n    storageScopes.add(kj::heap<jsg::AsyncContextFrame::StorageScope>(\n        js, *entry.key, entry.transform(js, message.addRef(js))));\n  }\n\n  publish(js, message.addRef(js));\n\n  v8::Local<v8::Value> receiver = js.v8Context()->Global();\n  KJ_IF_SOME(val, maybeReceiver) {\n    receiver = val;\n  }\n  callback.setReceiver(js.v8Ref(receiver));\n  return callback(js, kj::mv(args));\n}\n\nvoid Channel::visitForGc(jsg::GcVisitor& visitor) {\n  for (auto& sub: subscribers) {\n    visitor.visit(sub.key, sub.value);\n  }\n  for (auto& store: stores) {\n    visitor.visit(store.transform);\n  }\n}\n\nbool DiagnosticsChannelModule::hasSubscribers(jsg::Lock& js, jsg::Name name) {\n  return tryGetChannel(js, name).map([&](Channel& channel) {\n    return channel.hasSubscribers();\n  }).orDefault(false);\n}\n\nvoid DiagnosticsChannelModule::subscribe(\n    jsg::Lock& js, jsg::Name name, jsg::Identified<Channel::MessageCallback> callback) {\n  channel(js, kj::mv(name))->subscribe(js, kj::mv(callback));\n}\n\nvoid DiagnosticsChannelModule::unsubscribe(\n    jsg::Lock& js, jsg::Name name, jsg::Identified<Channel::MessageCallback> callback) {\n  KJ_IF_SOME(channel, tryGetChannel(js, name)) {\n    channel.unsubscribe(js, kj::mv(callback));\n  }\n}\n\njsg::Ref<Channel> DiagnosticsChannelModule::channel(jsg::Lock& js, jsg::Name channel) {\n  kj::String name = channel.toString(js);\n  return channels\n      .findOrCreate(name,\n          [&, channel = kj::mv(channel)]() mutable\n          -> kj::HashMap<kj::String, jsg::Ref<Channel>>::Entry {\n    return {kj::mv(name), js.alloc<Channel>(kj::mv(channel))};\n  }).addRef();\n}\n\nkj::Maybe<Channel&> DiagnosticsChannelModule::tryGetChannel(jsg::Lock& js, jsg::Name& name) {\n  return channels.find(name.toString(js)).map([](jsg::Ref<Channel>& channel) -> Channel& {\n    return *channel;\n  });\n}\n\nvoid DiagnosticsChannelModule::visitForGc(jsg::GcVisitor& visitor) {\n  for (auto& channel: channels) {\n    visitor.visit(channel.value);\n  }\n}\n\nvoid Channel::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"name\", name);\n  for (auto& sub: subscribers) {\n    tracker.trackField(\"subscribers\", sub.key);\n    tracker.trackField(\"subscribers\", sub.value);\n  }\n  for (auto& store: stores) {\n    tracker.trackField(\"stores\", store);\n  }\n}\n\nvoid DiagnosticsChannelModule::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  for (auto& channel: channels) {\n    tracker.trackField(nullptr, channel.value);\n  }\n}\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/diagnostics-channel.h",
    "content": "#pragma once\n\n#include <workerd/api/node/async-hooks.h>\n#include <workerd/jsg/jsg.h>\n\n#include <kj/map.h>\n#include <kj/table.h>\n\nnamespace workerd::api::node {\n\nclass Channel: public jsg::Object {\n public:\n  using MessageCallback = jsg::Function<void(jsg::Value, jsg::Name)>;\n  using TransformCallback = jsg::Function<jsg::Value(jsg::Value)>;\n\n  static jsg::Value identityTransform(jsg::Lock& js, jsg::Value value);\n\n  Channel(jsg::Name name);\n\n  bool hasSubscribers();\n  void publish(jsg::Lock& js, jsg::Value message);\n  void subscribe(jsg::Lock& js, jsg::Identified<MessageCallback> callback);\n  void unsubscribe(jsg::Lock& js, jsg::Identified<MessageCallback> callback);\n  void bindStore(jsg::Lock& js,\n      jsg::Ref<AsyncLocalStorage> als,\n      jsg::Optional<TransformCallback> maybeTransform);\n  void unbindStore(jsg::Lock& js, jsg::Ref<AsyncLocalStorage> als);\n  v8::Local<v8::Value> runStores(jsg::Lock& js,\n      jsg::Value message,\n      jsg::Function<v8::Local<v8::Value>(jsg::Arguments<jsg::Value>)> callback,\n      jsg::Optional<v8::Local<v8::Value>> maybeReceiver,\n      jsg::Arguments<jsg::Value> args);\n\n  JSG_RESOURCE_TYPE(Channel) {\n    JSG_METHOD(hasSubscribers);\n    JSG_METHOD(publish);\n    JSG_METHOD(subscribe);\n    JSG_METHOD(unsubscribe);\n    JSG_METHOD(bindStore);\n    JSG_METHOD(unbindStore);\n    JSG_METHOD(runStores);\n  }\n\n  const jsg::Name& getName() const;\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  struct StoreEntry {\n    kj::Own<jsg::AsyncContextFrame::StorageKey> key;\n    TransformCallback transform;\n    JSG_MEMORY_INFO(StoreEntry) {\n      tracker.trackField(\"transform\", transform);\n    }\n  };\n\n  struct StoreCallbacks {\n    auto& keyForRow(StoreEntry& row) const {\n      return *row.key;\n    }\n\n    bool matches(const StoreEntry& a, jsg::AsyncContextFrame::StorageKey& key) const {\n      return a.key.get() == &key;\n    }\n\n    uint hashCode(jsg::AsyncContextFrame::StorageKey& key) const {\n      return key.hashCode();\n    }\n  };\n\n  jsg::Name name;\n  kj::HashMap<jsg::HashableV8Ref<v8::Object>, MessageCallback> subscribers;\n  kj::Table<StoreEntry, kj::HashIndex<StoreCallbacks>> stores;\n\n  void visitForGc(jsg::GcVisitor& visitor);\n};\n\nclass DiagnosticsChannelModule: public jsg::Object {\n public:\n  DiagnosticsChannelModule() = default;\n  DiagnosticsChannelModule(jsg::Lock&, const jsg::Url&) {}\n\n  bool hasSubscribers(jsg::Lock& js, jsg::Name name);\n  jsg::Ref<Channel> channel(jsg::Lock& js, jsg::Name name);\n  void subscribe(jsg::Lock& js, jsg::Name name, jsg::Identified<Channel::MessageCallback> callback);\n  void unsubscribe(\n      jsg::Lock& js, jsg::Name name, jsg::Identified<Channel::MessageCallback> callback);\n  // TODO: Support tracing channels\n\n  JSG_RESOURCE_TYPE(DiagnosticsChannelModule) {\n    JSG_METHOD(hasSubscribers);\n    JSG_METHOD(channel);\n    JSG_METHOD(subscribe);\n    JSG_METHOD(unsubscribe);\n    JSG_NESTED_TYPE(Channel);\n  }\n\n  kj::Maybe<Channel&> tryGetChannel(jsg::Lock& js, jsg::Name& name);\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  kj::HashMap<kj::String, jsg::Ref<Channel>> channels;\n\n  void visitForGc(jsg::GcVisitor& visitor);\n};\n\n#define EW_NODE_DIAGNOSTICCHANNEL_ISOLATE_TYPES                                                    \\\n  api::node::Channel, api::node::DiagnosticsChannelModule\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/exceptions.c++",
    "content": "#include \"exceptions.h\"\n\nnamespace workerd::api::node {\n\nnamespace {\njsg::JsObject createJsError(jsg::Lock& js, JsErrorType type, kj::StringPtr message) {\n  switch (type) {\n    case JsErrorType::TypeError: {\n      return KJ_ASSERT_NONNULL(js.typeError(message).tryCast<jsg::JsObject>());\n    }\n    case JsErrorType::RangeError: {\n      return KJ_ASSERT_NONNULL(js.rangeError(message).tryCast<jsg::JsObject>());\n    }\n    case JsErrorType::Error: {\n      KJ_FALLTHROUGH;\n    }\n    default: {\n      return KJ_ASSERT_NONNULL(js.error(message).tryCast<jsg::JsObject>());\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nconstexpr kj::StringPtr getMessage(NodeExceptionCode code, kj::StringPtr message) {\n  if (message != nullptr) return message;\n  switch (code) {\n    case NodeExceptionCode::ERR_FS_CP_EEXIST:\n      return \"File already exists\";\n    case NodeExceptionCode::ERR_FS_CP_DIR_TO_NON_DIR:\n      return \"Cannot copy directory to non-directory\";\n    case NodeExceptionCode::ERR_FS_CP_EINVAL:\n      return \"Invalid cp operation\";\n    case NodeExceptionCode::ERR_FS_CP_NON_DIR_TO_DIR:\n      return \"Cannot copy non-directory to directory\";\n    case NodeExceptionCode::ERR_FS_EISDIR:\n      return \"Expected a file but found a directory\";\n    default:\n      return \"Unknown Node.js exception\";\n  }\n}\n\nconstexpr kj::StringPtr getCode(NodeExceptionCode code) {\n#define V(name)                                                                                    \\\n  case NodeExceptionCode::name:                                                                    \\\n    return #name;\n  switch (code) {\n    NODE_EXCEPTION_CODE_LIST(V)\n    default:\n      return \"UNKNOWN\";\n  }\n#undef V\n}\n}  // namespace\n\njsg::JsValue createNodeException(\n    jsg::Lock& js, NodeExceptionCode code, JsErrorType type, kj::StringPtr message) {\n  auto err = createJsError(js, type, getMessage(code, message));\n  err.set(js, \"code\"_kj, js.str(getCode(code)));\n  return err;\n}\n\nnamespace {\n// Generates the error name.\n#define UV_ERR_NAME_GEN(name, _)                                                                   \\\n  case UV_##name:                                                                                  \\\n    return js.str(#name##_kj);\njsg::JsValue uv_err_name(jsg::Lock& js, int err) {\n  switch (err) { UV_ERRNO_MAP(UV_ERR_NAME_GEN) }\n  return js.str(\"UNKNOWN\"_kj);\n}\n#undef UV_ERR_NAME_GEN\n}  // namespace\n\njsg::JsValue createUVException(jsg::Lock& js,\n    int errorno,\n    kj::StringPtr syscall,\n    kj::StringPtr message,\n    kj::StringPtr path,\n    kj::StringPtr dest) {\n  KJ_DASSERT(syscall != nullptr, \"syscall must not be null\");\n\n  // Format the message to match Node.js format: \"ENOENT: no such file or directory, open 'path'\"\n  kj::String formattedMessage;\n  if (message == nullptr) {\n    // Get the default error message\n    kj::String defaultMsg;\n    switch (errorno) {\n#define UV_MSG_GEN(name, msg)                                                                      \\\n  case UV_##name:                                                                                  \\\n    defaultMsg = kj::str(msg);                                                                     \\\n    break;\n      UV_ERRNO_MAP(UV_MSG_GEN)\n#undef UV_MSG_GEN\n      default:\n        defaultMsg = kj::str(\"unknown error: \", errorno);\n        break;\n    }\n\n    if (path != nullptr) {\n      formattedMessage = kj::str(defaultMsg, \", \", syscall, \" '\", path, \"'\");\n    } else {\n      formattedMessage = kj::mv(defaultMsg);\n    }\n  } else {\n    formattedMessage = kj::str(message);\n  }\n\n  jsg::JsObject obj = KJ_ASSERT_NONNULL(js.error(formattedMessage).tryCast<jsg::JsObject>());\n  obj.set(js, \"syscall\"_kj, js.str(syscall));\n  obj.set(js, \"code\"_kj, uv_err_name(js, errorno));\n\n  if (path != nullptr) {\n    obj.set(js, \"path\"_kj, js.str(path));\n  }\n  if (dest != nullptr) {\n    obj.set(js, \"dest\"_kj, js.str(dest));\n  }\n\n  return obj;\n}\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/exceptions.h",
    "content": "#pragma once\n\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api::node {\n\n// Utilities for creating Node.js-style exceptions.\n\n// Most Node.js exceptions are represented as either Error,\n// TypeError, or RangeError.\nenum class JsErrorType {\n  Error,\n  TypeError,\n  RangeError,\n};\n\n// Node.js Exception Codes\n#define NODE_EXCEPTION_CODE_LIST(V)                                                                \\\n  V(ERR_FS_CP_EEXIST)                                                                              \\\n  V(ERR_FS_CP_DIR_TO_NON_DIR)                                                                      \\\n  V(ERR_FS_CP_EINVAL)                                                                              \\\n  V(ERR_FS_CP_NON_DIR_TO_DIR)                                                                      \\\n  V(ERR_FS_EISDIR)\n\nenum class NodeExceptionCode {\n#define V(name) name,\n  NODE_EXCEPTION_CODE_LIST(V)\n#undef V\n};\n\n// A Node.js style exception is just a JS error with a string \"code\" property.\njsg::JsValue createNodeException(\n    jsg::Lock& js, NodeExceptionCode code, JsErrorType type, kj::StringPtr message = nullptr);\n\n[[noreturn]] inline void throwNodeException(jsg::Lock& js,\n    NodeExceptionCode code,\n    JsErrorType type = JsErrorType::Error,\n    kj::StringPtr message = nullptr) {\n  js.throwException(createNodeException(js, code, type, message));\n}\n\n#define V(name)                                                                                    \\\n  [[noreturn]] inline void THROW_##name(jsg::Lock& js, kj::StringPtr message = nullptr) {          \\\n    throwNodeException(js, NodeExceptionCode::name, JsErrorType::Error, message);                  \\\n  }\n\nNODE_EXCEPTION_CODE_LIST(V)\n#undef V\n\n// Create a Node.js-style \"UVException\". The UVException is an\n// ordinary Error object with additional properties like code,\n// syscall, path, and destination properties. It is primarily\n// used to represent file system API errors.\njsg::JsValue createUVException(jsg::Lock& js,\n    int errorno,\n    kj::StringPtr syscall,\n    kj::StringPtr message = nullptr,\n    kj::StringPtr path = nullptr,\n    kj::StringPtr dest = nullptr);\n\n// Throw a Node.js-style \"UVException\". The UVException is an\n// ordinary Error object with additional properties like code,\n// syscall, path, and destination properties. It is primarily\n// used to represent file system API errors.\n[[noreturn]] inline void throwUVException(jsg::Lock& js,\n    int errorno,\n    kj::StringPtr syscall,\n    kj::StringPtr message,\n    kj::StringPtr path,\n    kj::StringPtr dest) {\n  js.throwException(createUVException(js, errorno, syscall, message, path, dest));\n}\n\n// This is an intentionally truncated list of the error codes that Node.js/libuv\n// uses. We won't need all of the error codes that libuv uses.\n#define UV_ERRNO_MAP(V)                                                                            \\\n  V(EACCES, \"permission denied\")                                                                   \\\n  V(EBADF, \"bad file descriptor\")                                                                  \\\n  V(EEXIST, \"file already exists\")                                                                 \\\n  V(EFBIG, \"file too large\")                                                                       \\\n  V(EINVAL, \"invalid argument\")                                                                    \\\n  V(EISDIR, \"illegal operation on a directory\")                                                    \\\n  V(ELOOP, \"too many symbolic links encountered\")                                                  \\\n  V(EMFILE, \"too many open files\")                                                                 \\\n  V(ENAMETOOLONG, \"name too long\")                                                                 \\\n  V(ENFILE, \"file table overflow\")                                                                 \\\n  V(ENOBUFS, \"no buffer space available\")                                                          \\\n  V(ENODEV, \"no such device\")                                                                      \\\n  V(ENOENT, \"no such file or directory\")                                                           \\\n  V(ENOMEM, \"not enough memory\")                                                                   \\\n  V(ENOSPC, \"no space left on device\")                                                             \\\n  V(ENOSYS, \"function not implemented\")                                                            \\\n  V(ENOTDIR, \"not a directory\")                                                                    \\\n  V(ENOTEMPTY, \"directory not empty\")                                                              \\\n  V(EPERM, \"operation not permitted\")                                                              \\\n  V(EMLINK, \"too many links\")                                                                      \\\n  V(EIO, \"input/output error\")\n\n#define V(code, _) constexpr int UV_##code = -code;\nUV_ERRNO_MAP(V)\n#undef V\n\n#define V(code, _)                                                                                 \\\n  [[noreturn]] inline void THROW_ERR_UV_##code(jsg::Lock& js, kj::StringPtr syscall,               \\\n      kj::StringPtr message = nullptr, kj::StringPtr path = nullptr,                               \\\n      kj::StringPtr dest = nullptr) {                                                              \\\n    throwUVException(js, UV_##code, syscall, message, path, dest);                                 \\\n  }\nUV_ERRNO_MAP(V)\n#undef V\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/i18n.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\n#include \"i18n.h\"\n\n#include \"simdutf.h\"\n\n#include <workerd/jsg/exception.h>\n\n#include <unicode/ucnv.h>\n#include <unicode/uidna.h>\n#include <unicode/urename.h>\n#include <unicode/utypes.h>\n#include <unicode/uvernum.h>\n#include <unicode/uversion.h>\n\nnamespace workerd::api::node {\n\nnamespace i18n {\n\nnamespace {\n\n// An isolate has a 128mb memory limit.\nconstexpr int ISOLATE_LIMIT = 134217728;\n\nconstexpr const char* getEncodingName(Encoding input) {\n  switch (input) {\n    case Encoding::ASCII:\n      return \"us-ascii\";\n    case Encoding::LATIN1:\n      return \"iso8859-1\";\n    case Encoding::UTF16LE:\n      return \"utf16le\";\n    case Encoding::UTF8:\n      return \"utf-8\";\n    default:\n      KJ_UNREACHABLE;\n  }\n}\n\nusing TranscodeImpl = kj::Function<kj::Maybe<jsg::JsUint8Array>(\n    jsg::Lock& js, kj::ArrayPtr<kj::byte> source, Encoding fromEncoding, Encoding toEncoding)>;\n\nkj::Maybe<jsg::JsUint8Array> TranscodeDefault(\n    jsg::Lock& js, kj::ArrayPtr<kj::byte> source, Encoding fromEncoding, Encoding toEncoding) {\n  Converter to(toEncoding);\n  auto substitute = kj::str(kj::repeat('?', to.minCharSize()));\n  to.setSubstituteChars(substitute);\n  Converter from(fromEncoding);\n\n  size_t limit = source.size() * to.maxCharSize();\n  if (limit == 0) {\n    return jsg::JsUint8Array::create(js, 0);\n  }\n  // Workers are limited to 128MB so this isn't actually a realistic concern, but sanity check.\n  JSG_REQUIRE(limit <= ISOLATE_LIMIT, Error, \"Source buffer is too large to transcode\");\n\n  auto out = jsg::JsUint8Array::create(js, limit);\n  auto outPtr = out.asArrayPtr().asChars();\n  char* target = outPtr.begin();\n  const char* source_ = source.asChars().begin();\n  UErrorCode status{};\n  ucnv_convertEx(to.conv(), from.conv(), &target, target + limit, &source_, source_ + source.size(),\n      nullptr, nullptr, nullptr, nullptr, true, true, &status);\n  if (U_SUCCESS(status)) {\n    auto written = target - outPtr.begin();\n    if (written < out.size()) {\n      return out.slice(js, written);\n    }\n    return out;\n  }\n\n  return kj::none;\n}\n\nkj::Maybe<jsg::JsUint8Array> TranscodeLatin1ToUTF16(\n    jsg::Lock& js, kj::ArrayPtr<kj::byte> source, Encoding fromEncoding, Encoding toEncoding) {\n  auto length_in_chars = source.size() * sizeof(char16_t);\n  // Workers are limited to 128MB so this isn't actually a realistic concern, but sanity check.\n  JSG_REQUIRE(length_in_chars <= ISOLATE_LIMIT, Error, \"Source buffer is too large to transcode\");\n\n  if (length_in_chars == 0) {\n    return jsg::JsUint8Array::create(js, 0);\n  }\n\n  Converter from(fromEncoding);\n  auto destBuf = jsg::JsUint8Array::create(js, length_in_chars);\n  auto destPtr = destBuf.asArrayPtr<char16_t>();\n  auto actual_length =\n      simdutf::convert_latin1_to_utf16(source.asChars().begin(), source.size(), destPtr.begin());\n\n  // simdutf returns 0 for invalid value.\n  if (actual_length == 0) {\n    return kj::none;\n  }\n\n  auto written = actual_length * sizeof(char16_t);\n  if (written < destBuf.size()) {\n    return destBuf.slice(js, written);\n  }\n  return destBuf;\n}\n\nkj::Maybe<jsg::JsUint8Array> TranscodeFromUTF16(\n    jsg::Lock& js, kj::ArrayPtr<kj::byte> source, Encoding fromEncoding, Encoding toEncoding) {\n  Converter to(toEncoding);\n  auto substitute = kj::str(kj::repeat('?', to.minCharSize()));\n  to.setSubstituteChars(substitute);\n\n  JSG_REQUIRE(\n      source.size() % sizeof(char16_t) == 0, Error, \"UTF-16le input size should be multiple of 2\");\n  auto utf16_input = kj::arrayPtr<char16_t>(\n      reinterpret_cast<char16_t*>(source.begin()), source.size() / sizeof(char16_t));\n\n  const auto limit = utf16_input.size() * to.maxCharSize();\n\n  // Workers are limited to 128MB so this isn't actually a realistic concern, but sanity check.\n  JSG_REQUIRE(limit <= ISOLATE_LIMIT, Error, \"Buffer is too large to transcode\");\n\n  if (limit == 0) {\n    return jsg::JsUint8Array::create(js, 0);\n  }\n\n  auto destBuf = jsg::JsUint8Array::create(js, limit);\n  auto destPtr = destBuf.asArrayPtr().asChars();\n  UErrorCode status{};\n  auto len = ucnv_fromUChars(\n      to.conv(), destPtr.begin(), destPtr.size(), utf16_input.begin(), utf16_input.size(), &status);\n\n  if (U_SUCCESS(status)) {\n    if (len < destBuf.size()) {\n      destBuf = destBuf.slice(js, len);\n    }\n    return destBuf;\n  }\n\n  return kj::none;\n}\n\nkj::Maybe<jsg::JsUint8Array> TranscodeUTF16FromUTF8(\n    jsg::Lock& js, kj::ArrayPtr<kj::byte> source, Encoding fromEncoding, Encoding toEncoding) {\n  size_t expected_utf16_length =\n      simdutf::utf16_length_from_utf8(source.asChars().begin(), source.size());\n  // Workers are limited to 128MB so this isn't actually a realistic concern, but sanity check.\n  JSG_REQUIRE(expected_utf16_length <= ISOLATE_LIMIT, Error,\n      \"Expected UTF-16le length is too large to transcode\");\n\n  auto length_in_chars = expected_utf16_length * sizeof(char16_t);\n  if (length_in_chars == 0) {\n    return jsg::JsUint8Array::create(js, 0);\n  }\n\n  auto destBuf = jsg::JsUint8Array::create(js, length_in_chars);\n  auto destPtr = destBuf.asArrayPtr<char16_t>();\n\n  size_t actual_length =\n      simdutf::convert_utf8_to_utf16le(source.asChars().begin(), source.size(), destPtr.begin());\n\n  // simdutf returns 0 for invalid UTF-8 value.\n  if (actual_length == 0) {\n    return kj::none;\n  }\n\n  JSG_REQUIRE(actual_length == expected_utf16_length, Error, \"Expected UTF16 length mismatch\");\n\n  return destBuf;\n}\n\nkj::Maybe<jsg::JsUint8Array> TranscodeUTF8FromUTF16(\n    jsg::Lock& js, kj::ArrayPtr<kj::byte> source, Encoding fromEncoding, Encoding toEncoding) {\n  JSG_REQUIRE(source.size() % 2 == 0, Error, \"UTF-16le input size should be multiple of 2\");\n  auto utf16_input =\n      kj::arrayPtr<char16_t>(reinterpret_cast<char16_t*>(source.begin()), source.size() / 2);\n  size_t expected_utf8_length =\n      simdutf::utf8_length_from_utf16le(utf16_input.begin(), utf16_input.size());\n\n  // Workers are limited to 128MB so this isn't actually a realistic concern, but sanity check.\n  JSG_REQUIRE(expected_utf8_length <= ISOLATE_LIMIT, Error,\n      \"Expected UTF-8 length is too large to transcode\");\n\n  if (expected_utf8_length == 0) {\n    return jsg::JsUint8Array::create(js, 0);\n  }\n\n  auto destBuf = jsg::JsUint8Array::create(js, expected_utf8_length);\n  auto destPtr = destBuf.asArrayPtr().asChars();\n\n  size_t actual_length =\n      simdutf::convert_utf16le_to_utf8(utf16_input.begin(), utf16_input.size(), destPtr.begin());\n  JSG_REQUIRE(actual_length == expected_utf8_length, Error, \"Expected UTF8 length mismatch\");\n\n  // simdutf returns 0 for invalid UTF-8 value.\n  if (actual_length == 0) {\n    return kj::none;\n  }\n\n  return destBuf;\n}\n\n}  // namespace\n\nConverter::Converter(Encoding encoding, kj::StringPtr substitute) {\n  UErrorCode status = U_ZERO_ERROR;\n  auto name = getEncodingName(encoding);\n  auto conv = ucnv_open(name, &status);\n  JSG_REQUIRE(U_SUCCESS(status), Error, \"Failed to initialize converter\");\n  conv_ = kj::disposeWith<ucnv_close>(conv);\n  setSubstituteChars(substitute);\n}\n\nUConverter* Converter::conv() const {\n  return const_cast<UConverter*>(conv_.get());\n}\n\nsize_t Converter::maxCharSize() const {\n  KJ_ASSERT_NONNULL(conv_.get());\n  return ucnv_getMaxCharSize(conv_.get());\n}\n\nsize_t Converter::minCharSize() const {\n  KJ_ASSERT_NONNULL(conv_.get());\n  return ucnv_getMinCharSize(conv_.get());\n}\n\nvoid Converter::reset() {\n  KJ_ASSERT_NONNULL(conv_.get());\n  ucnv_reset(conv_.get());\n}\n\nvoid Converter::setSubstituteChars(kj::StringPtr sub) {\n  KJ_ASSERT_NONNULL(conv_.get());\n  UErrorCode status = U_ZERO_ERROR;\n  if (sub.size() > 0) {\n    ucnv_setSubstChars(conv_.get(), sub.begin(), sub.size(), &status);\n    JSG_REQUIRE(U_SUCCESS(status), Error, \"Setting ICU substitute characters failed\");\n  }\n}\n\njsg::JsUint8Array transcode(\n    jsg::Lock& js, kj::ArrayPtr<kj::byte> source, Encoding fromEncoding, Encoding toEncoding) {\n  TranscodeImpl transcode_function = &TranscodeDefault;\n  switch (fromEncoding) {\n    case Encoding::ASCII:\n    case Encoding::LATIN1:\n      if (toEncoding == Encoding::UTF16LE) {\n        transcode_function = &TranscodeLatin1ToUTF16;\n      }\n      break;\n    case Encoding::UTF8:\n      if (toEncoding == Encoding::UTF16LE) {\n        transcode_function = &TranscodeUTF16FromUTF8;\n      }\n      break;\n    case Encoding::UTF16LE:\n      switch (toEncoding) {\n        case Encoding::UTF16LE:\n          transcode_function = &TranscodeDefault;\n          break;\n        case Encoding::UTF8:\n          transcode_function = &TranscodeUTF8FromUTF16;\n          break;\n        default:\n          transcode_function = &TranscodeFromUTF16;\n      }\n      break;\n    default:\n      JSG_FAIL_REQUIRE(Error, \"Invalid encoding passed to transcode\");\n  }\n\n  return JSG_REQUIRE_NONNULL(transcode_function(js, source, fromEncoding, toEncoding), Error,\n      \"Unable to transcode buffer\");\n}\n\n}  // namespace i18n\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/i18n.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <workerd/jsg/jsg.h>\n\n#include <kj/common.h>\n#include <kj/string.h>\n\n#include <cstdint>\n\nstruct UConverter;\n\nnamespace workerd::api::node {\n\nusing EncodingValue = uint8_t;\n\nenum Encoding : EncodingValue {\n  ASCII = 0,\n  LATIN1 = 1,\n  UTF8 = 2,\n  UTF16LE = 3,\n  BASE64 = 4,\n  BASE64URL = 5,\n  HEX = 6,\n};\n\nnamespace i18n {\n\n// Used by BufferUtil::transcode.\nconstexpr bool canBeTranscoded(Encoding encoding) noexcept {\n  switch (encoding) {\n    case Encoding::ASCII:\n    case Encoding::LATIN1:\n    case Encoding::UTF16LE:\n    case Encoding::UTF8:\n      return true;\n    default:\n      return false;\n  }\n}\n\nclass Converter final {\n public:\n  explicit Converter(Encoding encoding, kj::StringPtr substitude = \"\"_kj);\n  KJ_DISALLOW_COPY_AND_MOVE(Converter);\n\n  UConverter* conv() const;\n  size_t maxCharSize() const;\n  size_t minCharSize() const;\n  void reset();\n  void setSubstituteChars(kj::StringPtr sub);\n\n private:\n  kj::Own<UConverter> conv_;\n};\n\njsg::JsUint8Array transcode(\n    jsg::Lock& js, kj::ArrayPtr<kj::byte> source, Encoding fromEncoding, Encoding toEncoding);\n\n}  // namespace i18n\n\n}  // namespace workerd::api::node\n\nKJ_DECLARE_NON_POLYMORPHIC(UConverter)\n"
  },
  {
    "path": "src/workerd/api/node/module.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#include \"module.h\"\n\n#include <workerd/io/features.h>\n#include <workerd/jsg/modules-new.h>\n#include <workerd/jsg/url.h>\n\nnamespace workerd::api::node {\n\nbool ModuleUtil::isBuiltin(kj::String specifier) {\n  return jsg::checkNodeSpecifier(specifier) != kj::none;\n}\n\njsg::JsValue ModuleUtil::createRequire(jsg::Lock& js, kj::String path) {\n  // Node.js requires that the specifier path is a File URL or an absolute\n  // file path string. To be compliant, we will convert whatever specifier\n  // is into a File URL if possible, then take the path as the actual\n  // specifier to use.\n  auto parsed = JSG_REQUIRE_NONNULL(jsg::Url::tryParse(path.asPtr(), \"file:///\"_kj), TypeError,\n      \"The argument must be a file URL object, \"\n      \"a file URL string, or an absolute path string.\");\n\n  if (FeatureFlags::get(js).getNewModuleRegistry()) {\n    return jsg::JsValue(js.wrapReturningFunction(js.v8Context(),\n        [referrer = parsed.clone()](jsg::Lock& js,\n            const v8::FunctionCallbackInfo<v8::Value>& args) -> v8::Local<v8::Value> {\n      auto specifier = kj::str(args[0]);\n      if (jsg::isNodeJsCompatEnabled(js)) {\n        KJ_IF_SOME(nodeSpec, jsg::checkNodeSpecifier(specifier)) {\n          specifier = kj::mv(nodeSpec);\n        }\n      }\n      return jsg::modules::ModuleRegistry::resolve(js, specifier, \"default\"_kj,\n          jsg::modules::ResolveContext::Type::BUNDLE, jsg::modules::ResolveContext::Source::REQUIRE,\n          referrer);\n    }));\n  }\n\n  // We do not currently handle specifiers as URLs, so let's treat any\n  // input that has query string params or hash fragments as errors.\n  if (parsed.getSearch().size() > 0 || parsed.getHash().size() > 0) {\n    JSG_FAIL_REQUIRE(\n        Error, \"The specifier must not have query string parameters or hash fragments.\");\n  }\n\n  // The specifier must be a file: URL\n  JSG_REQUIRE(parsed.getProtocol() == \"file:\"_kj, TypeError, \"The specifier must be a file: URL.\");\n\n  return jsg::JsValue(js.wrapReturningFunction(js.v8Context(),\n      [referrer = kj::str(parsed.getPathname())](\n          jsg::Lock& js, const v8::FunctionCallbackInfo<v8::Value>& args) -> v8::Local<v8::Value> {\n    auto registry = jsg::ModuleRegistry::from(js);\n\n    // TODO(soon): This will need to be updated to support the new module registry\n    // when that is fully implemented.\n    JSG_REQUIRE(registry != nullptr, Error, \"Module registry not available.\");\n\n    auto ref = ([&] {\n      try {\n        return kj::Path::parse(referrer.slice(1));\n      } catch (kj::Exception& e) {\n        JSG_FAIL_REQUIRE(Error, kj::str(\"Invalid referrer path: \", referrer.slice(1)));\n      }\n    })();\n\n    auto spec = kj::str(args[0]);\n\n    if (jsg::isNodeJsCompatEnabled(js)) {\n      KJ_IF_SOME(nodeSpec, jsg::checkNodeSpecifier(spec)) {\n        spec = kj::mv(nodeSpec);\n      }\n    }\n\n    static const kj::Path kRoot = kj::Path::parse(\"\");\n\n    kj::Path targetPath = ([&] {\n      // If the specifier begins with one of our known prefixes, let's not resolve\n      // it against the referrer.\n      try {\n        if (spec.startsWith(\"node:\") || spec.startsWith(\"cloudflare:\") ||\n            spec.startsWith(\"workerd:\")) {\n          return kj::Path::parse(spec);\n        }\n\n        return ref == kRoot ? kj::Path::parse(spec) : ref.parent().eval(spec);\n      } catch (kj::Exception&) {\n        JSG_FAIL_REQUIRE(Error, kj::str(\"Invalid specifier path: \", spec));\n      }\n    })();\n\n    // require() is only exposed to worker bundle modules so the resolve here is only\n    // permitted to require worker bundle or built-in modules. Internal modules are\n    // excluded.\n    auto& info = JSG_REQUIRE_NONNULL(\n        registry->resolve(js, targetPath, ref, jsg::ModuleRegistry::ResolveOption::DEFAULT,\n            jsg::ModuleRegistry::ResolveMethod::REQUIRE, spec.asPtr()),\n        Error, \"No such module \\\"\", targetPath.toString(), \"\\\".\");\n\n    jsg::ModuleRegistry::RequireImplOptions options =\n        jsg::ModuleRegistry::RequireImplOptions::DEFAULT;\n    if (info.maybeSynthetic != kj::none) {\n      options = jsg::ModuleRegistry::RequireImplOptions::EXPORT_DEFAULT;\n    }\n\n    return jsg::ModuleRegistry::requireImpl(js, info, options);\n  }));\n}\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/module.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api::node {\n\nclass ModuleUtil final: public jsg::Object {\n public:\n  ModuleUtil() = default;\n  ModuleUtil(jsg::Lock&, const jsg::Url&) {}\n\n  jsg::JsValue createRequire(jsg::Lock& js, kj::String specifier);\n\n  // Returns true if the specifier is a known node.js built-in module specifier.\n  // Ignores whether or not the module actually exists (use process.getBuiltinModule()\n  // for that purpose).\n  bool isBuiltin(kj::String specifier);\n\n  JSG_RESOURCE_TYPE(ModuleUtil) {\n    JSG_METHOD(createRequire);\n    JSG_METHOD(isBuiltin);\n  }\n};\n\n#define EW_NODE_MODULE_ISOLATE_TYPES api::node::ModuleUtil\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/node-version.h",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <kj/string.h>\n\nnamespace workerd::api::node {\n\n// Node.js version reported by the platform, for Node.js compatibility.\n// We track the current LTS Node.js version by updating this file automatically\n// from the script in tools/update_node_version.py or via\n// `just update-reported-node-version`.\n// This is not a guarantee for compatibility, and APIs will still be incomplete,\n// but it at least can be used to indicate what Node.js version target is being\n// supported for Node.js platform code.\nstatic constexpr kj::StringPtr nodeVersion = \"22.19.0\"_kj;\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/node.h",
    "content": "#pragma once\n\n#include \"crypto.h\"\n#include \"diagnostics-channel.h\"\n#include \"zlib-util.h\"\n\n#include <workerd/api/node/async-hooks.h>\n#include <workerd/api/node/buffer.h>\n#include <workerd/api/node/module.h>\n#include <workerd/api/node/process.h>\n#include <workerd/api/node/sqlite.h>\n#include <workerd/api/node/timers.h>\n#include <workerd/api/node/url.h>\n#include <workerd/api/node/util.h>\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/modules-new.h>\n#include <workerd/jsg/url.h>\n#include <workerd/rust/api/lib.rs.h>\n#include <workerd/rust/jsg/jsg.h>\n\n#include <node/node.capnp.h>\n\n#include <capnp/dynamic.h>\n\nnamespace workerd::api::node {\n\n#define NODEJS_MODULES(V)                                                                          \\\n  V(AsyncHooksModule, \"node-internal:async_hooks\")                                                 \\\n  V(BufferUtil, \"node-internal:buffer\")                                                            \\\n  V(CryptoImpl, \"node-internal:crypto\")                                                            \\\n  V(ModuleUtil, \"node-internal:module\")                                                            \\\n  V(ProcessModule, \"node-internal:process\")                                                        \\\n  V(UtilModule, \"node-internal:util\")                                                              \\\n  V(DiagnosticsChannelModule, \"node-internal:diagnostics_channel\")                                 \\\n  V(ZlibUtil, \"node-internal:zlib\")                                                                \\\n  V(UrlUtil, \"node-internal:url\")                                                                  \\\n  V(TimersUtil, \"node-internal:timers\")                                                            \\\n  V(SqliteUtil, \"node-internal:sqlite\")\n\n// Add to the NODEJS_MODULES_EXPERIMENTAL list any currently in-development\n// node.js compat C++ modules that should be guarded by the experimental compat\n// flag. Once they are ready to ship, move them up to the NODEJS_MODULES list.\n#define NODEJS_MODULES_EXPERIMENTAL(V)\n\nbool isNodeJsCompatEnabled(auto featureFlags) {\n  return featureFlags.getNodeJsCompat() || featureFlags.getNodeJsCompatV2();\n}\n\nconstexpr bool isNodeJsCompatFsModule(kj::StringPtr name) {\n  return name == \"node:fs\"_kj;\n}\n\nconstexpr bool isNodeHttpModule(kj::StringPtr name) {\n  return name == \"node:http\"_kj || name == \"node:_http_common\"_kj ||\n      name == \"node:_http_outgoing\"_kj || name == \"node:_http_client\"_kj ||\n      name == \"node:_http_incoming\"_kj || name == \"node:_http_agent\"_kj || name == \"node:https\"_kj;\n}\n\nconstexpr bool isNodeHttpServerModule(kj::StringPtr name) {\n  return name == \"node:_http_server\"_kj;\n}\n\nconstexpr bool isNodeOsModule(kj::StringPtr name) {\n  return name == \"node:os\"_kj;\n}\n\nconstexpr bool isNodeHttp2Module(kj::StringPtr name) {\n  return name == \"node:http2\"_kj;\n}\n\nconstexpr bool isNodeConsoleModule(kj::StringPtr name) {\n  return name == \"node:console\"_kj;\n}\n\ntemplate <class Registry>\nvoid registerNodeJsCompatModules(Registry& registry, auto featureFlags) {\n#define V(T, N)                                                                                    \\\n  registry.template addBuiltinModule<T>(N, workerd::jsg::ModuleRegistry::Type::INTERNAL);\n\n  NODEJS_MODULES(V)\n\n  if (featureFlags.getWorkerdExperimental()) {\n    NODEJS_MODULES_EXPERIMENTAL(V)\n  }\n\n#undef V\n\n  bool nodeJsCompatEnabled = isNodeJsCompatEnabled(featureFlags);\n\n  registry.addBuiltinBundleFiltered(NODE_BUNDLE, [&](jsg::Module::Reader module) {\n    if (!nodeJsCompatEnabled) {\n      // If the `nodejs_compat` flag isn't enabled, only register internal modules.\n      // We need these for `console.log()`ing when running `workerd` locally.\n      return module.getType() == jsg::ModuleType::INTERNAL;\n    }\n\n    if (isNodeJsCompatFsModule(module.getName())) {\n      return featureFlags.getEnableNodeJsFsModule();\n    }\n\n    // We put node:http and node:https modules behind a compat flag\n    // for securing backward compatibility.\n    if (isNodeHttpModule(module.getName())) {\n      return featureFlags.getEnableNodejsHttpModules();\n    }\n\n    // We put node:_http_server and related features behind a compat flag\n    // for securing backward compatibility.\n    if (isNodeHttpServerModule(module.getName())) {\n      return featureFlags.getEnableNodejsHttpServerModules();\n    }\n\n    if (isNodeOsModule(module.getName())) {\n      return featureFlags.getEnableNodeJsOsModule();\n    }\n\n    if (isNodeHttp2Module(module.getName())) {\n      return featureFlags.getEnableNodeJsHttp2Module();\n    }\n\n    if (isNodeConsoleModule(module.getName())) {\n      return featureFlags.getEnableNodeJsConsoleModule();\n    }\n\n    if (module.getName() == \"node:vm\"_kj) {\n      return featureFlags.getEnableNodeJsVmModule();\n    }\n\n    if (module.getName() == \"node:perf_hooks\"_kj) {\n      return featureFlags.getEnableNodeJsPerfHooksModule();\n    }\n\n    if (module.getName() == \"node:domain\"_kj) {\n      return featureFlags.getEnableNodeJsDomainModule();\n    }\n\n    if (module.getName() == \"node:child_process\"_kj) {\n      return featureFlags.getEnableNodeJsChildProcessModule();\n    }\n\n    if (module.getName() == \"node:v8\"_kj) {\n      return featureFlags.getEnableNodeJsV8Module();\n    }\n\n    if (module.getName() == \"node:tty\"_kj) {\n      return featureFlags.getEnableNodeJsTtyModule();\n    }\n\n    if (module.getName() == \"node:punycode\"_kj) {\n      return featureFlags.getEnableNodeJsPunycodeModule();\n    }\n\n    if (module.getName() == \"node:cluster\"_kj) {\n      return featureFlags.getEnableNodeJsClusterModule();\n    }\n\n    if (module.getName() == \"node:worker_threads\"_kj) {\n      return featureFlags.getEnableNodeJsWorkerThreadsModule();\n    }\n\n    if (module.getName() == \"node:_stream_wrap\"_kj) {\n      return featureFlags.getEnableNodeJsStreamWrapModule();\n    }\n\n    if (module.getName() == \"node:wasi\"_kj) {\n      return featureFlags.getEnableNodeJsWasiModule();\n    }\n\n    if (module.getName() == \"node:dgram\"_kj) {\n      return featureFlags.getEnableNodeJsDgramModule();\n    }\n\n    if (module.getName() == \"node:inspector\"_kj ||\n        module.getName() == \"node:inspector/promises\"_kj) {\n      return featureFlags.getEnableNodeJsInspectorModule();\n    }\n\n    if (module.getName() == \"node:trace_events\"_kj) {\n      return featureFlags.getEnableNodeJsTraceEventsModule();\n    }\n\n    if (module.getName() == \"node:readline\"_kj || module.getName() == \"node:readline/promises\"_kj) {\n      return featureFlags.getEnableNodeJsReadlineModule();\n    }\n\n    if (module.getName() == \"node:repl\"_kj) {\n      return featureFlags.getEnableNodeJsReplModule();\n    }\n\n    if (module.getName() == \"node:sqlite\"_kj) {\n      return featureFlags.getEnableNodeJsSqliteModule();\n    }\n\n    return true;\n  });\n\n  // If the `nodejs_compat` flag is off, but the `nodejs_als` flag is on, we\n  // need to register the `node:async_hooks` module from the bundle.\n  if (!nodeJsCompatEnabled && featureFlags.getNodeJsAls()) {\n    jsg::Bundle::Reader reader = NODE_BUNDLE;\n    for (auto module: reader.getModules()) {\n      auto specifier = module.getName();\n      if (specifier == \"node:async_hooks\") {\n        KJ_DASSERT(module.getType() == jsg::ModuleType::BUILTIN);\n        registry.addBuiltinModule(module);\n      }\n    }\n  }\n\n  ::workerd::rust::jsg::RustModuleRegistry r(registry);\n  ::workerd::rust::api::register_nodejs_modules(r);\n}\n\ntemplate <class TypeWrapper>\nkj::Own<jsg::modules::ModuleBundle> getInternalNodeJsCompatModuleBundle(auto featureFlags) {\n  jsg::modules::ModuleBundle::BuiltinBuilder builder(\n      jsg::modules::ModuleBundle::BuiltinBuilder::Type::BUILTIN_ONLY);\n#define V(M, N)                                                                                    \\\n  static const auto k##M##Specifier = N##_url;                                                     \\\n  builder.addObject<M, TypeWrapper>(k##M##Specifier);\n  NODEJS_MODULES(V)\n  if (featureFlags.getWorkerdExperimental()) {\n    NODEJS_MODULES_EXPERIMENTAL(V)\n  }\n#undef V\n  jsg::modules::ModuleBundle::getBuiltInBundleFromCapnp(builder, NODE_BUNDLE);\n\n  // Register Rust-implemented Node.js modules using the reusable adapter\n  // that bridges Rust ModuleCallback into BuiltinBuilder::addSynthetic.\n  {\n    ::workerd::rust::jsg::RustBuiltinModuleAdapter adapter(builder);\n    ::workerd::rust::api::register_nodejs_modules(adapter);\n  }\n\n  return builder.finish();\n}\n\nkj::Own<jsg::modules::ModuleBundle> getExternalNodeJsCompatModuleBundle(auto featureFlags) {\n  jsg::modules::ModuleBundle::BuiltinBuilder builder(\n      jsg::modules::ModuleBundle::BuiltinBuilder::Type::BUILTIN);\n  if (isNodeJsCompatEnabled(featureFlags)) {\n    jsg::modules::ModuleBundle::getBuiltInBundleFromCapnp(\n        builder, NODE_BUNDLE, [&](jsg::Module::Reader module) -> bool {\n      if (isNodeJsCompatFsModule(module.getName())) {\n        return featureFlags.getEnableNodeJsFsModule();\n      }\n      if (isNodeHttpModule(module.getName())) {\n        return featureFlags.getEnableNodejsHttpModules();\n      }\n      if (isNodeHttpServerModule(module.getName())) {\n        return featureFlags.getEnableNodejsHttpServerModules();\n      }\n      if (isNodeOsModule(module.getName())) {\n        return featureFlags.getEnableNodeJsOsModule();\n      }\n      if (isNodeHttp2Module(module.getName())) {\n        return featureFlags.getEnableNodeJsHttp2Module();\n      }\n      if (isNodeConsoleModule(module.getName())) {\n        return featureFlags.getEnableNodeJsConsoleModule();\n      }\n      if (module.getName() == \"node:vm\") {\n        return featureFlags.getEnableNodeJsVmModule();\n      }\n      if (module.getName() == \"node:perf_hooks\") {\n        return featureFlags.getEnableNodeJsPerfHooksModule();\n      }\n      if (module.getName() == \"node:domain\") {\n        return featureFlags.getEnableNodeJsDomainModule();\n      }\n      if (module.getName() == \"node:child_process\") {\n        return featureFlags.getEnableNodeJsChildProcessModule();\n      }\n      if (module.getName() == \"node:v8\") {\n        return featureFlags.getEnableNodeJsV8Module();\n      }\n      if (module.getName() == \"node:tty\") {\n        return featureFlags.getEnableNodeJsTtyModule();\n      }\n      if (module.getName() == \"node:punycode\") {\n        return featureFlags.getEnableNodeJsPunycodeModule();\n      }\n      if (module.getName() == \"node:cluster\") {\n        return featureFlags.getEnableNodeJsClusterModule();\n      }\n      if (module.getName() == \"node:worker_threads\") {\n        return featureFlags.getEnableNodeJsWorkerThreadsModule();\n      }\n      if (module.getName() == \"node:_stream_wrap\") {\n        return featureFlags.getEnableNodeJsStreamWrapModule();\n      }\n      if (module.getName() == \"node:wasi\") {\n        return featureFlags.getEnableNodeJsWasiModule();\n      }\n      if (module.getName() == \"node:dgram\") {\n        return featureFlags.getEnableNodeJsDgramModule();\n      }\n      if (module.getName() == \"node:inspector\" || module.getName() == \"node:inspector/promises\") {\n        return featureFlags.getEnableNodeJsInspectorModule();\n      }\n      if (module.getName() == \"node:trace_events\") {\n        return featureFlags.getEnableNodeJsTraceEventsModule();\n      }\n      if (module.getName() == \"node:readline\" || module.getName() == \"node:readline/promises\") {\n        return featureFlags.getEnableNodeJsReadlineModule();\n      }\n      if (module.getName() == \"node:repl\") {\n        return featureFlags.getEnableNodeJsReplModule();\n      }\n      if (module.getName() == \"node:sqlite\") {\n        return featureFlags.getEnableNodeJsSqliteModule();\n      }\n      return true;\n    });\n  } else if (featureFlags.getNodeJsAls()) {\n    // The AsyncLocalStorage API can be enabled independently of the rest\n    // of the nodejs_compat layer.\n    jsg::Bundle::Reader reader = NODE_BUNDLE;\n    for (auto module: reader.getModules()) {\n      auto specifier = module.getName();\n      if (specifier == \"node:async_hooks\") {\n        KJ_DASSERT(module.getType() == jsg::ModuleType::BUILTIN);\n        KJ_DASSERT(module.which() == workerd::jsg::Module::SRC);\n        auto specifier = KJ_ASSERT_NONNULL(jsg::Url::tryParse(module.getName()));\n        builder.addEsm(specifier, module.getSrc().asChars());\n      }\n    }\n  }\n  return builder.finish();\n}\n\n#undef NODEJS_MODULES\n}  // namespace workerd::api::node\n\n#define EW_NODE_ISOLATE_TYPES                                                                      \\\n  EW_NODE_BUFFER_ISOLATE_TYPES, EW_NODE_CRYPTO_ISOLATE_TYPES,                                      \\\n      EW_NODE_DIAGNOSTICCHANNEL_ISOLATE_TYPES, EW_NODE_ASYNCHOOKS_ISOLATE_TYPES,                   \\\n      EW_NODE_UTIL_ISOLATE_TYPES, EW_NODE_PROCESS_ISOLATE_TYPES, EW_NODE_ZLIB_ISOLATE_TYPES,       \\\n      EW_NODE_URL_ISOLATE_TYPES, EW_NODE_MODULE_ISOLATE_TYPES, EW_NODE_TIMERS_ISOLATE_TYPES,       \\\n      EW_NODE_SQLITE_ISOLATE_TYPES\n"
  },
  {
    "path": "src/workerd/api/node/process.c++",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#include \"process.h\"\n\n#include <workerd/api/filesystem.h>\n#include <workerd/api/node/exceptions.h>\n#include <workerd/io/features.h>\n#include <workerd/io/io-context.h>\n#include <workerd/io/tracer.h>\n#include <workerd/io/worker-fs.h>\n#include <workerd/jsg/jsg.h>\n\n#include <kj/vector.h>\n\nnamespace workerd::api::node {\n\njsg::JsValue ProcessModule::getBuiltinModule(jsg::Lock& js, kj::String specifier) {\n  auto rawSpecifier = kj::str(specifier);\n  bool isNode = false;\n  KJ_IF_SOME(spec, jsg::checkNodeSpecifier(specifier)) {\n    isNode = true;\n    specifier = kj::mv(spec);\n  }\n\n  if (FeatureFlags::get(js).getNewModuleRegistry()) {\n    KJ_IF_SOME(mod, js.resolvePublicBuiltinModule(specifier)) {\n      return mod;\n    }\n    return js.undefined();\n  }\n\n  auto registry = jsg::ModuleRegistry::from(js);\n  if (registry == nullptr) return js.undefined();\n\n  // getBuiltinModule is a user-facing API that must only return modules that\n  // are normally importable by user code. Default to BUILTIN_ONLY which only\n  // resolves user-importable built-ins, not internal-only modules.\n  auto resolveOption = jsg::ModuleRegistry::ResolveOption::BUILTIN_ONLY;\n\n  // Handle process module redirection based on enable_nodejs_process_v2 flag.\n  // This is a special case where we need to redirect to an internal module,\n  // so we escalate to INTERNAL_ONLY only for this specific redirect.\n  if (isNode && specifier == \"node:process\") {\n    auto featureFlags = FeatureFlags::get(js);\n    if (featureFlags.getEnableNodeJsProcessV2()) {\n      specifier = kj::str(\"node-internal:public_process\");\n    } else {\n      specifier = kj::str(\"node-internal:legacy_process\");\n    }\n    resolveOption = jsg::ModuleRegistry::ResolveOption::INTERNAL_ONLY;\n  }\n\n  auto path = kj::Path::parse(specifier);\n\n  KJ_IF_SOME(info,\n      registry->resolve(js, path, kj::none, resolveOption,\n          jsg::ModuleRegistry::ResolveMethod::IMPORT, rawSpecifier.asPtr())) {\n    auto module = info.module.getHandle(js);\n    jsg::instantiateModule(js, module);\n\n    // For Node.js modules, we want to grab the default export and return that.\n    // For other built-ins, we'll return the module namespace instead. Can be\n    // a bit confusing but it's a side effect of Node.js modules originally\n    // being commonjs and the official getBuiltinModule returning what is\n    // expected to be the default export, while the behavior of other built-ins\n    // is not really defined by Node.js' implementation.\n    if (isNode) {\n      return jsg::JsValue(js.v8Get(module->GetModuleNamespace().As<v8::Object>(), \"default\"_kj));\n    } else {\n      return jsg::JsValue(module->GetModuleNamespace());\n    }\n  }\n\n  return js.undefined();\n}\n\njsg::JsObject ProcessModule::getEnvObject(jsg::Lock& js) {\n  if (FeatureFlags::get(js).getPopulateProcessEnv()) {\n    KJ_IF_SOME(env, js.getWorkerEnv()) {\n      return jsg::JsObject(env.getHandle(js));\n    }\n  }\n\n  // Default to empty object.\n  return js.obj();\n}\n\nnamespace {\n[[noreturn]] void handleProcessExit(jsg::Lock& js, int code) {\n  // There are a few things happening here. First, we abort the current IoContext\n  // in order to shut down this specific request....\n  auto message =\n      kj::str(\"The Node.js process.exit(\", code, \") API was called. Canceling the request.\");\n  auto& ioContext = IoContext::current();\n  // If we have a tail worker, let's report the error.\n  KJ_IF_SOME(tracer, ioContext.getWorkerTracer()) {\n    // Why create the error like this in tracing? Because we're adding the exception\n    // to the trace and ideally we'd have the JS stack attached to it. Just using\n    // JSG_KJ_EXCEPTION would not give us that, and we only want to incur the cost\n    // of creating and capturing the stack when we actually need it.\n    auto ex = KJ_ASSERT_NONNULL(js.error(message).tryCast<jsg::JsObject>());\n    tracer.addException(ioContext.getInvocationSpanContext(), ioContext.now(),\n        ex.get(js, \"name\"_kj).toString(js), ex.get(js, \"message\"_kj).toString(js),\n        ex.get(js, \"stack\"_kj).toString(js));\n    ioContext.abort(js.exceptionToKj(ex));\n  } else {\n    ioContext.abort(JSG_KJ_EXCEPTION(FAILED, Error, kj::mv(message)));\n  }\n  // ...then we tell the isolate to terminate the current JavaScript execution.\n  js.terminateExecutionNow();\n}\n}  // namespace\n\njsg::JsObject ProcessModule::getVersions(jsg::Lock& js) const {\n  // Node.js version - represents the most current Node.js version supported\n  // by the platform, as defined in node-version.h\n  static const kj::StringPtr key = \"node\"_kj;\n  jsg::JsValue value = js.str(nodeVersion);\n  return js.obj(kj::arrayPtr(key), kj::arrayPtr(value));\n}\n\nkj::StringPtr ProcessModule::getPlatform(jsg::Lock& js) const {\n  auto flags = FeatureFlags::get(js);\n  if (flags.getUnsupportedProcessActualPlatform()) {\n    return platform;\n  }\n  // Always return \"linux\" for production compatibility\n  return \"linux\"_kj;\n}\n\nvoid ProcessModule::exitImpl(jsg::Lock& js, int code) {\n  if (IoContext::hasCurrent()) {\n    handleProcessExit(js, code);\n  }\n\n  // Create an error object so we can easily capture the stack where the\n  // process.exit call was made.\n  auto err = KJ_ASSERT_NONNULL(\n      js.error(\"process.exit(...) called without a current request context. Ignoring.\")\n          .tryCast<jsg::JsObject>());\n  err.set(js, \"name\"_kj, js.str());\n  js.logWarning(kj::str(err.get(js, \"stack\"_kj)));\n}\n\nkj::String ProcessModule::getCwd(jsg::Lock& js) {\n  KJ_IF_SOME(cwd, getCurrentWorkingDirectory()) {\n    return cwd.toString(true);\n  }\n  return kj::str(\"/\");\n}\n\nvoid ProcessModule::setCwd(jsg::Lock& js, kj::String path) {\n  static constexpr size_t MAX_PATH_LENGTH = 4096;\n  if (path.size() > MAX_PATH_LENGTH) {\n    node::THROW_ERR_UV_ENAMETOOLONG(js, \"chdir\"_kj, nullptr, path);\n  }\n\n  if (path.size() == 0) {\n    node::THROW_ERR_UV_ENOENT(js, \"chdir\"_kj, nullptr, path);\n  }\n\n  auto& vfs = VirtualFileSystem::current(js);\n\n  // Resolve the path against current working directory if it's relative\n  kj::Path resolvedPath = [&]() {\n    if (path.startsWith(\"/\")) {\n      // Absolute path - parse without leading slash\n      return kj::Path::parse(path.slice(1));\n    } else {\n      // Relative path - resolve against current working directory\n      KJ_IF_SOME(cwd, getCurrentWorkingDirectory()) {\n        return cwd.eval(path);\n      }\n      return kj::Path({\"/\"}).eval(path);\n    }\n  }();\n\n  KJ_IF_SOME(stat, vfs.getRoot(js)->stat(js, resolvedPath)) {\n    KJ_SWITCH_ONEOF(stat) {\n      KJ_CASE_ONEOF(fsError, FsError) {\n        node::THROW_ERR_UV_ENOENT(js, \"chdir\"_kj, nullptr, kj::str(resolvedPath));\n      }\n      KJ_CASE_ONEOF(statInfo, workerd::Stat) {\n        if (statInfo.type != FsType::DIRECTORY) {\n          node::THROW_ERR_UV_ENOTDIR(js, \"chdir\"_kj, nullptr, kj::str(resolvedPath));\n        }\n        if (!setCurrentWorkingDirectory(resolvedPath.clone())) {\n          node::THROW_ERR_UV_EPERM(js, \"chdir\"_kj, nullptr, kj::str(resolvedPath));\n        }\n      }\n    }\n  } else {\n    node::THROW_ERR_UV_ENOENT(js, \"chdir\"_kj, nullptr, kj::str(resolvedPath));\n  }\n}\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/process.h",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <workerd/api/node/i18n.h>\n#include <workerd/api/node/node-version.h>\n#include <workerd/io/worker-fs.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api::node {\n\nclass ProcessModule final: public jsg::Object {\n public:\n  ProcessModule() = default;\n  ProcessModule(jsg::Lock&, const jsg::Url&) {}\n\n  jsg::JsValue getBuiltinModule(jsg::Lock& js, kj::String specifier);\n\n// This process.platform implementation is always gated behind the\n// 'enable_deprecated_process_actual_platform' compatibility flag\n// which is ONLY used in testing environments, and requires 'experimental'.\n#ifdef _WIN32\n  static constexpr kj::StringPtr platform = \"win32\"_kj;\n#elifdef __linux__\n  static constexpr kj::StringPtr platform = \"linux\"_kj;\n#elifdef __APPLE__\n  static constexpr kj::StringPtr platform = \"darwin\"_kj;\n#else\n  static constexpr kj::StringPtr platform = \"unsupported-platform\"_kj;\n#endif\n\n  // This is used in the implementation of process.exit(...). Contrary\n  // to what the name suggests, it does not actually exit the process.\n  // Instead, it will cause the IoContext, if any, and will stop javascript\n  // from further executing in that request. If there is no active IoContext,\n  // then it becomes a non-op.\n  void exitImpl(jsg::Lock& js, int code);\n\n  // IMPORTANT: This function will always return \"linux\" on production unless\n  // the unsupported_process_actual_platform compat flag is enabled.\n  // This is only added for Node.js compatibility and running OS specific tests\n  kj::StringPtr getPlatform(jsg::Lock& js) const;\n\n  jsg::JsObject getEnvObject(jsg::Lock& js);\n\n  jsg::JsObject getVersions(jsg::Lock& js) const;\n\n  kj::String getCwd(jsg::Lock& js);\n\n  void setCwd(jsg::Lock& js, kj::String path);\n\n  JSG_RESOURCE_TYPE(ProcessModule) {\n    JSG_METHOD(getEnvObject);\n    JSG_METHOD(getBuiltinModule);\n    JSG_METHOD(exitImpl);\n    JSG_METHOD(getCwd);\n    JSG_METHOD(setCwd);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(versions, getVersions);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(platform, getPlatform);\n  }\n};\n\n#define EW_NODE_PROCESS_ISOLATE_TYPES api::node::ProcessModule\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/sqlite.c++",
    "content": "#include \"sqlite.h\"\n\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api::node {\n\nvoid SqliteUtil::backup(jsg::Lock& js) {\n  JSG_FAIL_REQUIRE(Error, \"backup is not implemented\"_kj);\n}\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/sqlite.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <workerd/jsg/jsg.h>\n\n#include <kj/common.h>\n\nnamespace workerd::api {\n\nclass Immediate;\n\nnamespace node {\nclass SqliteUtil final: public jsg::Object {\n public:\n  SqliteUtil() = default;\n  SqliteUtil(jsg::Lock&, const jsg::Url&) {}\n\n  class DatabaseSync final: public jsg::Object {\n   public:\n    DatabaseSync() = default;\n    DatabaseSync(jsg::Lock&, const jsg::Url&) {}\n\n    // We intentionally do not implement the full API surface yet.\n    // TODO(soon): Add the full implementation of DatabaseSync.\n    static jsg::Ref<DatabaseSync> constructor(jsg::Lock& js) = delete;\n\n    JSG_RESOURCE_TYPE(DatabaseSync) {}\n  };\n\n  class StatementSync final: public jsg::Object {\n   public:\n    StatementSync() = default;\n    StatementSync(jsg::Lock&, const jsg::Url&) {}\n\n    // We intentionally do not implement the full API surface yet.\n    // TODO(soon): Add the full implementation of StatementSync.\n    static jsg::Ref<StatementSync> constructor(jsg::Lock& js) = delete;\n\n    JSG_RESOURCE_TYPE(StatementSync) {}\n  };\n\n  void backup(jsg::Lock& js);\n\n  static constexpr uint8_t SQLITE_CHANGESET_OMIT = 0;\n  static constexpr uint8_t SQLITE_CHANGESET_REPLACE = 1;\n  static constexpr uint8_t SQLITE_CHANGESET_ABORT = 2;\n  static constexpr uint8_t SQLITE_CHANGESET_DATA = 1;\n  static constexpr uint8_t SQLITE_CHANGESET_NOTFOUND = 2;\n  static constexpr uint8_t SQLITE_CHANGESET_CONFLICT = 3;\n  static constexpr uint8_t SQLITE_CHANGESET_CONSTRAINT = 4;\n  static constexpr uint8_t SQLITE_CHANGESET_FOREIGN_KEY = 5;\n\n  JSG_RESOURCE_TYPE(SqliteUtil) {\n    JSG_NESTED_TYPE(DatabaseSync);\n    JSG_NESTED_TYPE(StatementSync);\n    JSG_METHOD(backup);\n\n    JSG_STATIC_CONSTANT(SQLITE_CHANGESET_OMIT);\n    JSG_STATIC_CONSTANT(SQLITE_CHANGESET_REPLACE);\n    JSG_STATIC_CONSTANT(SQLITE_CHANGESET_ABORT);\n    JSG_STATIC_CONSTANT(SQLITE_CHANGESET_DATA);\n    JSG_STATIC_CONSTANT(SQLITE_CHANGESET_NOTFOUND);\n    JSG_STATIC_CONSTANT(SQLITE_CHANGESET_CONFLICT);\n    JSG_STATIC_CONSTANT(SQLITE_CHANGESET_CONSTRAINT);\n    JSG_STATIC_CONSTANT(SQLITE_CHANGESET_FOREIGN_KEY);\n  }\n};\n\n#define EW_NODE_SQLITE_ISOLATE_TYPES                                                               \\\n  api::node::SqliteUtil, api::node::SqliteUtil::DatabaseSync, api::node::SqliteUtil::StatementSync\n}  // namespace node\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/node/tests/BUILD.bazel",
    "content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_binary\")\nload(\"@rules_shell//shell:sh_test.bzl\", \"sh_test\")\nload(\"//:build/wd_test.bzl\", \"wd_test\")\n\nwd_test(\n    src = \"assert-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"assert-test.js\"],\n)\n\nwd_test(\n    size = \"large\",\n    src = \"buffer-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"buffer-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"cluster-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"cluster-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"child_process-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"child_process-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"console-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"console-nodejs-test.js\"],\n)\n\nwd_test(\n    size = \"enormous\",\n    src = \"crypto_dh-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"crypto_dh-test.js\"],\n)\n\nwd_test(\n    src = \"crypto_hash-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"crypto_hash-test.js\"],\n)\n\nwd_test(\n    src = \"crypto_hkdf-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"crypto_hkdf-test.js\"],\n)\n\nwd_test(\n    src = \"crypto_hmac-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"crypto_hmac-test.js\"],\n)\n\nwd_test(\n    src = \"crypto_keys-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"crypto_keys-test.js\",\n        \"fixtures/agent1-cert.pem\",\n        \"fixtures/dh_private.pem\",\n        \"fixtures/dh_public.pem\",\n        \"fixtures/dsa_private.pem\",\n        \"fixtures/dsa_private_1025.pem\",\n        \"fixtures/dsa_private_encrypted.pem\",\n        \"fixtures/dsa_private_encrypted_1025.pem\",\n        \"fixtures/dsa_private_pkcs8.pem\",\n        \"fixtures/dsa_public.pem\",\n        \"fixtures/dsa_public_1025.pem\",\n        \"fixtures/ec_p256_private.pem\",\n        \"fixtures/ec_p256_public.pem\",\n        \"fixtures/ec_p384_private.pem\",\n        \"fixtures/ec_p384_public.pem\",\n        \"fixtures/ec_p521_private.pem\",\n        \"fixtures/ec_p521_public.pem\",\n        \"fixtures/ec_secp256k1_private.pem\",\n        \"fixtures/ec_secp256k1_public.pem\",\n        \"fixtures/ed25519_private.pem\",\n        \"fixtures/ed25519_public.pem\",\n        \"fixtures/ed448_private.pem\",\n        \"fixtures/ed448_public.pem\",\n        \"fixtures/rsa_private.pem\",\n        \"fixtures/rsa_private_2048.pem\",\n        \"fixtures/rsa_private_4096.pem\",\n        \"fixtures/rsa_private_b.pem\",\n        \"fixtures/rsa_private_encrypted.pem\",\n        \"fixtures/rsa_private_pkcs8.pem\",\n        \"fixtures/rsa_private_pkcs8_bad.pem\",\n        \"fixtures/rsa_pss_private_2048.pem\",\n        \"fixtures/rsa_pss_private_2048_sha1_sha1_20.pem\",\n        \"fixtures/rsa_pss_private_2048_sha256_sha256_16.pem\",\n        \"fixtures/rsa_pss_private_2048_sha512_sha256_20.pem\",\n        \"fixtures/rsa_pss_public_2048.pem\",\n        \"fixtures/rsa_pss_public_2048_sha1_sha1_20.pem\",\n        \"fixtures/rsa_pss_public_2048_sha256_sha256_16.pem\",\n        \"fixtures/rsa_pss_public_2048_sha512_sha256_20.pem\",\n        \"fixtures/rsa_public.pem\",\n        \"fixtures/rsa_public_2048.pem\",\n        \"fixtures/rsa_public_4096.pem\",\n        \"fixtures/rsa_public_b.pem\",\n        \"fixtures/x25519_private.pem\",\n        \"fixtures/x25519_public.pem\",\n        \"fixtures/x448_private.pem\",\n        \"fixtures/x448_public.pem\",\n    ],\n)\n\nwd_test(\n    src = \"crypto_pbkdf2-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"crypto_pbkdf2-test.js\"],\n)\n\nwd_test(\n    src = \"crypto_random-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"crypto_random-test.js\"],\n)\n\nwd_test(\n    size = \"large\",\n    src = \"crypto_scrypt-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"crypto_scrypt-test.js\"],\n)\n\nwd_test(\n    src = \"crypto_spkac-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"crypto_spkac-test.js\"],\n)\n\nwd_test(\n    src = \"crypto_x509-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"crypto_x509-test.js\"],\n)\n\nwd_test(\n    src = \"diagnostics-channel-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"diagnostics-channel-test.js\"],\n)\n\nwd_test(\n    src = \"mimetype-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"mimetype-test.js\"],\n)\n\nwd_test(\n    src = \"node-compat-v2-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"node-compat-v2-test.js\"],\n)\n\nwd_test(\n    src = \"perf-hooks-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"perf-hooks-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"path-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"path-test.js\"],\n)\n\nwd_test(\n    src = \"punycode-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"punycode-nodejs-test.js\"],\n)\n\nwd_test(\n    size = \"large\",\n    src = \"streams-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"streams-test.js\"],\n)\n\nwd_test(\n    src = \"string-decoder-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"string-decoder-test.js\"],\n)\n\nwd_test(\n    src = \"url-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"url-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"util-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"util-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"sys-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"sys-nodejs-test.js\"],\n)\n\nwd_test(\n    size = \"large\",\n    src = \"zlib-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"zlib-nodejs-test.js\"],\n)\n\nwd_test(\n    size = \"large\",\n    src = \"zlib-zstd-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"zlib-zstd-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"module-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"module-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"module-create-require-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"module-create-require-test.js\"],\n)\n\nwd_test(\n    src = \"module-require-mutable-exports-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"module-require-mutable-exports-test.js\"],\n)\n\nwd_test(\n    src = \"process-exit-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"process-exit-test.js\"],\n)\n\nwd_test(\n    src = \"process-nodejs-test.wd-test\",\n    data = [\"process-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"process-getbuiltin-newmodreg-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"process-getbuiltin-newmodreg-test.js\"],\n)\n\nwd_test(\n    src = \"process-legacy-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"process-legacy-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"process-stdio-nodejs-test.wd-test\",\n    data = [\"process-stdio-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"process-stdio-fs-nodejs-test.wd-test\",\n    data = [\"process-stdio-fs-nodejs-test.js\"],\n)\n\nsh_test(\n    name = \"process-stdio-sh-test\",\n    srcs = [\"test_process_stdio.sh\"],\n    args = [\n        \"$(location //src/workerd/server:workerd_cross)\",\n        \"$(location process-stdio-nodejs-test.wd-test)\",\n        \"$(location process-stdio-nodejs-test.expected_stdout)\",\n        \"$(location process-stdio-nodejs-test.expected_stderr)\",\n    ],\n    data = [\n        \"process-stdio-nodejs-test.expected_stderr\",\n        \"process-stdio-nodejs-test.expected_stdout\",\n        \"process-stdio-nodejs-test.js\",\n        \"process-stdio-nodejs-test.wd-test\",\n        \"//src/workerd/server:workerd_cross\",\n    ],\n)\n\nsh_test(\n    name = \"process-stdio-fs-sh-test\",\n    srcs = [\"test_process_stdio.sh\"],\n    args = [\n        \"$(location //src/workerd/server:workerd_cross)\",\n        \"$(location process-stdio-fs-nodejs-test.wd-test)\",\n        \"$(location process-stdio-fs-nodejs-test.expected_stdout)\",\n        \"$(location process-stdio-fs-nodejs-test.expected_stderr)\",\n    ],\n    data = [\n        \"process-stdio-fs-nodejs-test.expected_stderr\",\n        \"process-stdio-fs-nodejs-test.expected_stdout\",\n        \"process-stdio-fs-nodejs-test.js\",\n        \"process-stdio-fs-nodejs-test.wd-test\",\n        \"//src/workerd/server:workerd_cross\",\n    ],\n)\n\nwd_test(\n    src = \"querystring-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"querystring-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"readline-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"readline-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"repl-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"repl-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"sqlite-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"sqlite-nodejs-test.js\"],\n)\n\nconfig_setting(\n    name = \"debug_build\",\n    values = {\"compilation_mode\": \"dbg\"},\n)\n\nwd_test(\n    size = \"large\",\n    src = \"legacy_url-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"legacy_url-nodejs-test.js\"],\n    target_compatible_with = select({\n        \":debug_build\": [\"@platforms//:incompatible\"],\n        \"//conditions:default\": [],\n    }),\n)\n\nwd_test(\n    src = \"dns-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"dns-nodejs-test.js\"],\n    tags = [\"requires-network\"],\n)\n\njs_binary(\n    name = \"net-nodejs-tcp-server\",\n    entry_point = \"net-nodejs-tcp-server.js\",\n)\n\nwd_test(\n    size = \"large\",\n    src = \"net-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"net-nodejs-test.js\"],\n    sidecar = \"net-nodejs-tcp-server\",\n    sidecar_port_bindings = [\n        \"SERVER_PORT\",\n        \"ECHO_SERVER_PORT\",\n        \"TIMEOUT_SERVER_PORT\",\n        \"END_SERVER_PORT\",\n        \"SERVER_THAT_DIES_PORT\",\n        \"RECONNECT_SERVER_PORT\",\n    ],\n)\n\nwd_test(\n    src = \"timers-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"timers-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"timers-global-override-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"timers-global-override-test.js\"],\n)\n\nwd_test(\n    src = \"async_hooks-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"async_hooks-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"crypto_sign-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"crypto_sign-test.js\",\n        \"fixtures/dsa_private.pem\",\n        \"fixtures/dsa_public.pem\",\n        \"fixtures/ed25519_private.pem\",\n        \"fixtures/ed25519_public.pem\",\n        \"fixtures/rsa_private.pem\",\n        \"fixtures/rsa_public.pem\",\n    ],\n)\n\nwd_test(\n    src = \"crypto_cipher-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"crypto_cipher-test.js\",\n        \"fixtures/ed25519_private.pem\",\n        \"fixtures/ed25519_public.pem\",\n        \"fixtures/rsa_private.pem\",\n        \"fixtures/rsa_public.pem\",\n    ],\n)\n\njs_binary(\n    name = \"tls-nodejs-tcp-server\",\n    entry_point = \"tls-nodejs-tcp-server.js\",\n)\n\nwd_test(\n    size = \"large\",\n    src = \"tls-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"fixtures/tls-nodejs-tcp-server.pem\",\n        \"tls-nodejs-test.js\",\n    ],\n    sidecar = \"tls-nodejs-tcp-server\",\n    sidecar_port_bindings = [\n        \"ECHO_SERVER_PORT\",\n        \"HELLO_SERVER_PORT\",\n        \"JS_STREAM_SERVER_PORT\",\n        \"STREAM_WRAP_SERVER_PORT\",\n    ],\n    sidecar_randomize_ip = False,\n)\n\nwd_test(\n    src = \"streams-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"streams-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"streams-v24-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"streams-v24-nodejs-test.js\"],\n)\n\njs_binary(\n    name = \"http-nodejs-server\",\n    entry_point = \"http-nodejs-server.js\",\n)\n\nwd_test(\n    src = \"http-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"http-nodejs-test.js\"],\n    sidecar = \"http-nodejs-server\",\n    sidecar_port_bindings = [\n        \"PONG_SERVER_PORT\",\n        \"ASD_SERVER_PORT\",\n        \"TIMEOUT_SERVER_PORT\",\n        \"HELLO_WORLD_SERVER_PORT\",\n        \"HEADER_VALIDATION_SERVER_PORT\",\n    ],\n    tags = [\"requires-network\"],\n)\n\njs_binary(\n    name = \"http-outgoing-nodejs-server\",\n    entry_point = \"http-outgoing-nodejs-server.js\",\n)\n\nwd_test(\n    src = \"http-outgoing-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"http-outgoing-nodejs-test.js\"],\n    sidecar = \"http-outgoing-nodejs-server\",\n    sidecar_port_bindings = [\n        \"FINISH_WRITABLE_PORT\",\n        \"WRITABLE_FINISHED_PORT\",\n        \"PROPERTIES_PORT\",\n    ],\n)\n\nwd_test(\n    src = \"http-incoming-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"http-incoming-nodejs-test.js\"],\n)\n\njs_binary(\n    name = \"http-client-nodejs-server\",\n    entry_point = \"http-client-nodejs-server.js\",\n)\n\nwd_test(\n    src = \"http-client-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"http-client-nodejs-test.js\"],\n    sidecar = \"http-client-nodejs-server\",\n    sidecar_port_bindings = [\n        \"PONG_SERVER_PORT\",\n        \"ASD_SERVER_PORT\",\n        \"DEFAULT_HEADERS_EXIST_PORT\",\n        \"REQUEST_ARGUMENTS_PORT\",\n        \"HELLO_WORLD_SERVER_PORT\",\n        \"GZIP_SERVER_PORT\",\n    ],\n)\n\njs_binary(\n    name = \"http-server-nodejs-server\",\n    entry_point = \"http-server-nodejs-server.js\",\n)\n\nwd_test(\n    src = \"http-server-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"http-server-nodejs-test.js\"],\n    sidecar = \"http-server-nodejs-server\",\n    sidecar_port_bindings = [\n        \"PONG_SERVER_PORT\",\n    ],\n)\n\njs_binary(\n    name = \"http-agent-nodejs-server\",\n    entry_point = \"http-agent-nodejs-server.js\",\n)\n\nwd_test(\n    src = \"http-agent-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"http-agent-nodejs-test.js\"],\n    sidecar = \"http-agent-nodejs-server\",\n    sidecar_port_bindings = [\n        \"PONG_SERVER_PORT\",\n    ],\n)\n\nwd_test(\n    src = \"bound-als-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"bound-als-test.js\"],\n)\n\nwd_test(\n    src = \"fs-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"fs-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"fs-access-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"fs-access-test.js\"],\n)\n\nwd_test(\n    src = \"fs-chown-chmod-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"fs-chown-chmod-test.js\"],\n)\n\nwd_test(\n    src = \"fs-utimes-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"fs-utimes-test.js\"],\n)\n\nwd_test(\n    src = \"fs-stat-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"fs-stat-test.js\"],\n)\n\nwd_test(\n    src = \"fs-misc-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"fs-misc-test.js\"],\n)\n\nwd_test(\n    src = \"fs-link-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"fs-link-test.js\"],\n)\n\nwd_test(\n    src = \"fs-dir-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"fs-dir-test.js\"],\n)\n\nwd_test(\n    src = \"fs-filehandle-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"fs-filehandle-test.js\"],\n)\n\nwd_test(\n    src = \"fs-glob-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"fs-glob-test.js\"],\n)\n\nwd_test(\n    src = \"fs-cp-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"fs-cp-test.js\"],\n)\n\nwd_test(\n    src = \"fs-cp-into-subdirectory-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"fs-cp-into-subdirectory-test.js\"],\n)\n\nwd_test(\n    src = \"fs-readstream-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"fs-readstream-test.js\"],\n)\n\nwd_test(\n    src = \"fs-writestream-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"fs-writestream-test.js\"],\n)\n\nwd_test(\n    src = \"os-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"os-test.js\"],\n)\n\nwd_test(\n    src = \"http-server-nodejs-global-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"http-server-nodejs-global-test.js\"],\n)\n\nwd_test(\n    src = \"constants-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"constants-test.js\"],\n)\n\nwd_test(\n    src = \"http2-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"http2-test.js\"],\n)\n\nwd_test(\n    src = \"inspector-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"inspector-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"vm-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"vm-test.js\"],\n)\n\nwd_test(\n    src = \"domain-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"domain-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"v8-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"v8-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"tty-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"tty-nodejs-test.js\"],\n)\n\njs_binary(\n    name = \"sidecar-supervisor\",\n    entry_point = \"sidecar-supervisor.mjs\",\n    visibility = [\"//visibility:public\"],\n)\n\nwd_test(\n    src = \"worker_threads-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"worker_threads-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"wasi-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"wasi-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"dgram-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"dgram-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"trace-events-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"trace-events-nodejs-test.js\"],\n)\n\nwd_test(\n    src = \"buffer-effective-size-concat-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"buffer-effective-size-concat-test.js\"],\n)\n\nwd_test(\n    src = \"als-gc-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"als-gc-test.js\"],\n)\n"
  },
  {
    "path": "src/workerd/api/node/tests/als-gc-test.js",
    "content": "import { AsyncLocalStorage } from 'node:async_hooks';\n\nconst store = new AsyncLocalStorage();\n\nexport const test = {\n  test() {\n    const outerValue = crypto.randomUUID();\n    ({ [outerValue]: `value doesn't matter here` });\n    store.run(outerValue, () => {\n      for (let i = 0; i < 1_000; i++) {\n        const storeValue = store.getStore();\n        if (!storeValue) {\n          throw new Error(`Failed on attempt ${i}.`);\n        }\n        for (let j = 0; j < 1_000; j++) {\n          (() => Math.random())();\n        }\n      }\n    });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/als-gc-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"als-gc-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"als-gc-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/assert-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT ORs\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport {\n  deepEqual,\n  deepStrictEqual,\n  doesNotMatch,\n  doesNotReject,\n  doesNotThrow,\n  equal,\n  fail,\n  ifError,\n  match,\n  notDeepEqual,\n  notDeepStrictEqual,\n  notEqual,\n  notStrictEqual,\n  ok,\n  partialDeepStrictEqual,\n  rejects,\n  strictEqual,\n  throws,\n  AssertionError,\n} from 'node:assert';\n\nimport { mock } from 'node:test';\nimport util from 'node:util';\n\nimport { default as assert } from 'node:assert';\n\nconst strictEqualMessageStart = 'Expected values to be strictly equal:\\n';\nconst start = 'Expected values to be strictly deep-equal:';\nconst actExp = '+ actual - expected';\n\nfunction thrower(errorConstructor) {\n  throw new errorConstructor({});\n}\n\nexport const test_ok = {\n  test(ctrl, env, ctx) {\n    // truthy values just work\n    [true, 1, [], {}, 'hello'].forEach(ok);\n    [true, 1, [], {}, 'hello'].forEach(assert);\n    [true, 1, [], {}, 'hello'].forEach((val) => {\n      doesNotThrow(() => ok(val));\n    });\n\n    // falsy values throw\n    [false, 0, '', undefined, NaN, null].forEach((val) => {\n      throws(() => ok(val), { name: 'AssertionError' });\n    });\n\n    // falsy values throw\n    [false, 0, '', undefined, NaN, null].forEach((val) => {\n      throws(() => ok(val, 'message'), { message: 'message' });\n    });\n  },\n};\n\nexport const test_equal = {\n  test(ctrl, env, ctx) {\n    [\n      { a: 1, b: 1 },\n      { a: 1, b: '1', fails: true },\n      { a: 1, b: '1', fails: true, message: 'boom' },\n      { a: 'a', b: 'a' },\n      { a: ctx, b: ctx },\n    ].forEach(({ a, b, fails, message }) => {\n      if (!fails) {\n        equal(a, b);\n        strictEqual(a, b);\n        if (message) {\n          throws(() => notEqual(a, b, message), { message });\n          throws(() => notStrictEqual(a, b, message), { message });\n        } else {\n          throws(() => notEqual(a, b), { name: 'AssertionError' });\n          throws(() => notStrictEqual(a, b), { name: 'AssertionError' });\n        }\n      } else {\n        notEqual(a, b);\n        notStrictEqual(a, b);\n        if (message) {\n          throws(() => equal(a, b, message), { message });\n          throws(() => strictEqual(a, b, message), { message });\n        } else {\n          throws(() => equal(a, b), { name: 'AssertionError' });\n          throws(() => strictEqual(a, b), { name: 'AssertionError' });\n        }\n      }\n    });\n  },\n};\n\nexport const test_fail = {\n  test(ctrl, env, ctx) {\n    throws(() => fail('boom'), { message: 'boom' });\n    throws(() => ifError('boom'));\n    throws(() => ifError(false));\n    doesNotThrow(() => ifError(null));\n    doesNotThrow(() => ifError(undefined));\n  },\n};\n\nexport const test_rejects = {\n  async test(ctrl, env, ctx) {\n    await rejects(Promise.reject(new Error('boom')), { message: 'boom' });\n    await doesNotReject(Promise.resolve(1));\n  },\n};\n\nexport const test_matching = {\n  test(ctrl, env, ctx) {\n    match('hello', /hello/);\n    throws(() => match('hello', /not/), { name: 'AssertionError' });\n    doesNotMatch('hello', /not/);\n    throws(() => doesNotMatch('hello', /hello/), { name: 'AssertionError' });\n  },\n};\n\nexport const test_deep_equal = {\n  test(ctrl, env, ctx) {\n    const a = {\n      b: [\n        {\n          c: new Uint8Array([1, 2, 3]),\n          d: false,\n          e: 'hello',\n        },\n      ],\n    };\n    const b = {\n      b: [\n        {\n          c: new Uint8Array([1, 2, 3]),\n          d: false,\n          e: 'hello',\n        },\n      ],\n    };\n    deepEqual(a, b);\n    deepStrictEqual(a, b);\n    b.b[0].c[0] = 4;\n    notDeepEqual(a, b);\n    notDeepStrictEqual(a, b);\n  },\n};\n\nexport const test_deep_equal_errors = {\n  test(ctrl, env, ctx) {\n    const a = {\n      b: [\n        {\n          c: new Uint8Array([1, 2, 3]),\n          d: false,\n          e: 'hello',\n        },\n      ],\n    };\n    const b = {\n      b: [\n        {\n          c: new Uint8Array([1, 2, 4]),\n          d: true,\n          e: 'hello',\n        },\n      ],\n    };\n\n    {\n      const expectedError = [\n        `Expected values to be strictly deep-equal:`,\n        `+ actual - expected`,\n        `+ { b: [ { c: Uint8Array(3) [ 1, 2, 3 ], d: false, e: 'hello' } ] }`,\n        `- { b: [ { c: Uint8Array(3) [ 1, 2, 4 ], d: true, e: 'hello' } ] }`,\n      ];\n      throws(\n        () => deepEqual(a, b),\n        (err) => {\n          assert(err instanceof AssertionError);\n          deepEqual(trimMessage(err.message), expectedError);\n          return true;\n        }\n      );\n      throws(\n        () => deepStrictEqual(a, b),\n        (err) => {\n          assert(err instanceof AssertionError);\n          deepEqual(trimMessage(err.message), expectedError);\n          return true;\n        }\n      );\n    }\n\n    // Fix one value, message updates\n    b.b[0].c[2] = 3;\n\n    {\n      const expectedError = [\n        `Expected values to be strictly deep-equal:`,\n        `+ actual - expected`,\n        `+ { b: [ { c: Uint8Array(3) [ 1, 2, 3 ], d: false, e: 'hello' } ] }`,\n        `- { b: [ { c: Uint8Array(3) [ 1, 2, 3 ], d: true, e: 'hello' } ] }`,\n      ];\n      throws(\n        () => deepEqual(a, b),\n        (err) => {\n          assert(err instanceof AssertionError);\n          deepEqual(trimMessage(err.message), expectedError);\n          return true;\n        }\n      );\n      throws(\n        () => deepStrictEqual(a, b),\n        (err) => {\n          assert(err instanceof AssertionError);\n          deepEqual(trimMessage(err.message), expectedError);\n          return true;\n        }\n      );\n    }\n  },\n};\n\nexport const test_check_proxies = {\n  test() {\n    const arrProxy = new Proxy([1, 2], {});\n    deepStrictEqual(arrProxy, [1, 2]);\n    const defaultMsgStartFull = `${start}\\n${actExp}`;\n    const tmp = util.inspect.defaultOptions;\n    util.inspect.defaultOptions = { showProxy: true };\n    throws(() => deepStrictEqual(arrProxy, [1, 2, 3]), {\n      message:\n        `${defaultMsgStartFull}\\n\\n` + '+ Proxy([ 1, 2 ])\\n' + '- [ 1, 2, 3 ]',\n    });\n    util.inspect.defaultOptions = tmp;\n\n    const invalidTrap = new Proxy([1, 2, 3], {\n      ownKeys() {\n        return [];\n      },\n    });\n    throws(() => deepStrictEqual(invalidTrap, [1, 2, 3]), {\n      name: 'TypeError',\n      message: \"'ownKeys' on proxy: trap result did not include 'length'\",\n    });\n  },\n};\n\nexport const test_partial_deep_strict_equal = {\n  test(ctrl, env, ctx) {\n    // Test basic object partial equality\n    const actual = { a: 1, b: 2, c: 3 };\n    const expected = { a: 1, b: 2 };\n    partialDeepStrictEqual(actual, expected);\n\n    // Test nested object partial equality\n    const actualNested = {\n      x: { y: { z: 1 } },\n      a: 'hello',\n      extra: 'should be ignored',\n    };\n    const expectedNested = { x: { y: { z: 1 } } };\n    partialDeepStrictEqual(actualNested, expectedNested);\n\n    // Test array partial equality\n    const actualArray = [1, 2, 3, 4, 5];\n    const expectedArray = [1, 2, 3];\n    partialDeepStrictEqual(actualArray, expectedArray);\n\n    // Test Set partial equality\n    const actualSet = new Set([1, 2, 3, 4]);\n    const expectedSet = new Set([1, 2]);\n    partialDeepStrictEqual(actualSet, expectedSet);\n\n    // Test Map partial equality\n    const actualMap = new Map([\n      ['a', 1],\n      ['b', 2],\n      ['c', 3],\n    ]);\n    const expectedMap = new Map([\n      ['a', 1],\n      ['b', 2],\n    ]);\n    partialDeepStrictEqual(actualMap, expectedMap);\n\n    // Test Date objects\n    const date1 = new Date('2023-01-01');\n    const date2 = new Date('2023-01-01');\n    partialDeepStrictEqual(date1, date2);\n\n    // Test RegExp objects\n    const regex1 = /test/gi;\n    const regex2 = /test/gi;\n    partialDeepStrictEqual(regex1, regex2);\n\n    // Test Error objects\n    const error1 = new Error('Test error');\n    const error2 = new Error('Test error');\n    partialDeepStrictEqual(error1, error2);\n\n    // Test circular references - simple self-reference\n    const circularObj1 = { a: 1, b: 2 };\n    circularObj1.self = circularObj1;\n    const circularObj2 = { a: 1 };\n    circularObj2.self = circularObj2;\n    partialDeepStrictEqual(circularObj1, circularObj2);\n\n    // Test circular references - mutual references\n    const objA = { name: 'A', value: 1 };\n    const objB = { name: 'B', ref: objA };\n    objA.ref = objB;\n    const objC = { name: 'A' };\n    const objD = { name: 'B', ref: objC };\n    objC.ref = objD;\n    partialDeepStrictEqual(objA, objC);\n\n    // Test mixed circular/non-circular (should still work)\n    const mixedCircular = { a: 1, b: { c: 2 } };\n    mixedCircular.self = mixedCircular;\n    const mixedExpected = { a: 1, b: { c: 2 } };\n    partialDeepStrictEqual(mixedCircular, mixedExpected);\n\n    // Test primitive equality\n    partialDeepStrictEqual(42, 42);\n    partialDeepStrictEqual('hello', 'hello');\n    partialDeepStrictEqual(null, null);\n    partialDeepStrictEqual(undefined, undefined);\n\n    // Test failure cases\n    throws(() => partialDeepStrictEqual({ a: 1 }, { a: 2 }), AssertionError);\n    throws(\n      () => partialDeepStrictEqual({ a: 1 }, { a: 1, b: 2 }),\n      AssertionError\n    );\n    throws(() => partialDeepStrictEqual([1, 2], [1, 2, 3]), AssertionError);\n    throws(\n      () => partialDeepStrictEqual(new Set([1, 2]), new Set([1, 2, 3])),\n      AssertionError\n    );\n    throws(\n      () =>\n        partialDeepStrictEqual(new Date('2023-01-01'), new Date('2023-01-02')),\n      AssertionError\n    );\n\n    // Test complex nested case\n    const complexActual = {\n      users: [\n        { id: 1, name: 'Alice', age: 30, email: 'alice@example.com' },\n        { id: 2, name: 'Bob', age: 25, email: 'bob@example.com' },\n      ],\n      config: {\n        theme: 'dark',\n        notifications: true,\n        advanced: { debug: false, verbose: true },\n      },\n      metadata: { version: '1.0.0', timestamp: Date.now() },\n    };\n\n    const complexExpected = {\n      users: [\n        { id: 1, name: 'Alice' },\n        { id: 2, name: 'Bob' },\n      ],\n      config: {\n        theme: 'dark',\n        advanced: { debug: false },\n      },\n    };\n\n    partialDeepStrictEqual(complexActual, complexExpected);\n  },\n};\n\nfunction trimMessage(msg) {\n  return msg\n    .trim()\n    .split('\\n')\n    .map((line) => line.trim())\n    .filter(Boolean);\n}\n\nexport const test_mocks = {\n  test() {\n    const fn = mock.fn(() => {});\n    fn(1, 2, 3);\n    strictEqual(fn.mock.callCount(), 1);\n    strictEqual(fn.mock.calls[0].arguments.length, 3);\n    deepStrictEqual(fn.mock.calls[0].arguments, [1, 2, 3]);\n\n    fn.mock.mockImplementation(() => 42);\n    strictEqual(fn(), 42);\n    strictEqual(fn.mock.callCount(), 2);\n\n    fn.mock.resetCalls();\n    strictEqual(fn.mock.callCount(), 0);\n  },\n};\n\nexport const spiesOnFunction = {\n  test() {\n    const sum = mock.fn((arg1, arg2) => {\n      return arg1 + arg2;\n    });\n\n    strictEqual(sum.mock.calls.length, 0);\n    strictEqual(sum(3, 4), 7);\n    strictEqual(sum.call(1000, 9, 1), 10);\n    strictEqual(sum.mock.calls.length, 2);\n\n    let call = sum.mock.calls[0];\n    deepStrictEqual(call.arguments, [3, 4]);\n    strictEqual(call.error, undefined);\n    strictEqual(call.result, 7);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, undefined);\n\n    call = sum.mock.calls[1];\n    deepStrictEqual(call.arguments, [9, 1]);\n    strictEqual(call.error, undefined);\n    strictEqual(call.result, 10);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, 1000);\n  },\n};\n\nexport const speiesOnBoundFunction = {\n  test() {\n    const bound = function (arg1, arg2) {\n      return this + arg1 + arg2;\n    }.bind(50);\n    const sum = mock.fn(bound);\n\n    strictEqual(sum.mock.calls.length, 0);\n    strictEqual(sum(3, 4), 57);\n    strictEqual(sum(9, 1), 60);\n    strictEqual(sum.mock.calls.length, 2);\n\n    let call = sum.mock.calls[0];\n    deepStrictEqual(call.arguments, [3, 4]);\n    strictEqual(call.result, 57);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, undefined);\n\n    call = sum.mock.calls[1];\n    deepStrictEqual(call.arguments, [9, 1]);\n    strictEqual(call.result, 60);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, undefined);\n  },\n};\n\nexport const spiesOnConstructor = {\n  test() {\n    class ParentClazz {\n      constructor(c) {\n        this.c = c;\n      }\n    }\n\n    class Clazz extends ParentClazz {\n      #privateValue;\n\n      constructor(a, b) {\n        super(a + b);\n        this.a = a;\n        this.#privateValue = b;\n      }\n\n      getPrivateValue() {\n        return this.#privateValue;\n      }\n    }\n\n    const ctor = mock.fn(Clazz);\n    const instance = new ctor(42, 85);\n\n    ok(instance instanceof Clazz);\n    ok(instance instanceof ParentClazz);\n    strictEqual(instance.a, 42);\n    strictEqual(instance.getPrivateValue(), 85);\n    strictEqual(instance.c, 127);\n    strictEqual(ctor.mock.calls.length, 1);\n\n    const call = ctor.mock.calls[0];\n\n    deepStrictEqual(call.arguments, [42, 85]);\n    strictEqual(call.error, undefined);\n    strictEqual(call.result, instance);\n    strictEqual(call.target, Clazz);\n    strictEqual(call.this, instance);\n  },\n};\n\nexport const noopSpyCreatedByDefault = {\n  test() {\n    const fn = mock.fn();\n\n    strictEqual(fn.mock.calls.length, 0);\n    strictEqual(fn(3, 4), undefined);\n    strictEqual(fn.mock.calls.length, 1);\n\n    const call = fn.mock.calls[0];\n    deepStrictEqual(call.arguments, [3, 4]);\n    strictEqual(call.result, undefined);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, undefined);\n  },\n};\n\nexport const internalNoOpFunctionCanBeReused = {\n  test() {\n    const fn1 = mock.fn();\n    fn1.prop = true;\n    const fn2 = mock.fn();\n\n    fn1(1);\n    fn2(2);\n    fn1(3);\n\n    notStrictEqual(fn1.mock, fn2.mock);\n    strictEqual(fn1.mock.calls.length, 2);\n    strictEqual(fn2.mock.calls.length, 1);\n    strictEqual(fn1.prop, true);\n    strictEqual(fn2.prop, undefined);\n  },\n};\n\nexport const functionsCanBeMockedMultipleTimesAtOnce = {\n  test() {\n    function sum(a, b) {\n      return a + b;\n    }\n\n    function difference(a, b) {\n      return a - b;\n    }\n\n    function product(a, b) {\n      return a * b;\n    }\n\n    const fn1 = mock.fn(sum, difference);\n    const fn2 = mock.fn(sum, product);\n\n    strictEqual(fn1(5, 3), 2);\n    strictEqual(fn2(5, 3), 15);\n    strictEqual(fn2(4, 2), 8);\n    ok(!('mock' in sum));\n    ok(!('mock' in difference));\n    ok(!('mock' in product));\n    notStrictEqual(fn1.mock, fn2.mock);\n    strictEqual(fn1.mock.calls.length, 1);\n    strictEqual(fn2.mock.calls.length, 2);\n  },\n};\n\nexport const internalNoopFunctionCanBeReusedAsMethods = {\n  test() {\n    const obj = {\n      _foo: 5,\n      _bar: 9,\n      foo() {\n        return this._foo;\n      },\n      bar() {\n        return this._bar;\n      },\n    };\n\n    mock.method(obj, 'foo');\n    obj.foo.prop = true;\n    mock.method(obj, 'bar');\n    strictEqual(obj.foo(), 5);\n    strictEqual(obj.bar(), 9);\n    strictEqual(obj.bar(), 9);\n    notStrictEqual(obj.foo.mock, obj.bar.mock);\n    strictEqual(obj.foo.mock.calls.length, 1);\n    strictEqual(obj.bar.mock.calls.length, 2);\n    strictEqual(obj.foo.prop, true);\n    strictEqual(obj.bar.prop, undefined);\n  },\n};\n\nexport const methodsCanBeMockedMultipleTimesButNotAtTheSameTime = {\n  test() {\n    const obj = {\n      offset: 3,\n      sum(a, b) {\n        return this.offset + a + b;\n      },\n    };\n\n    function difference(a, b) {\n      return this.offset + (a - b);\n    }\n\n    function product(a, b) {\n      return this.offset + a * b;\n    }\n\n    const originalSum = obj.sum;\n    const fn1 = mock.method(obj, 'sum', difference);\n\n    strictEqual(obj.sum(5, 3), 5);\n    strictEqual(obj.sum(5, 1), 7);\n    strictEqual(obj.sum, fn1);\n    notStrictEqual(fn1.mock, undefined);\n    strictEqual(originalSum.mock, undefined);\n    strictEqual(difference.mock, undefined);\n    strictEqual(product.mock, undefined);\n    strictEqual(fn1.mock.calls.length, 2);\n\n    const fn2 = mock.method(obj, 'sum', product);\n\n    strictEqual(obj.sum(5, 3), 18);\n    strictEqual(obj.sum, fn2);\n    notStrictEqual(fn1, fn2);\n    strictEqual(fn2.mock.calls.length, 1);\n\n    obj.sum.mock.restore();\n    strictEqual(obj.sum, fn1);\n    obj.sum.mock.restore();\n    strictEqual(obj.sum, originalSum);\n    strictEqual(obj.sum.mock, undefined);\n  },\n};\n\nexport const spiesOnObjectMethod = {\n  test() {\n    const obj = {\n      prop: 5,\n      method(a, b) {\n        return a + b + this.prop;\n      },\n    };\n\n    strictEqual(obj.method(1, 3), 9);\n    mock.method(obj, 'method');\n    strictEqual(obj.method.mock.calls.length, 0);\n    strictEqual(obj.method(1, 3), 9);\n\n    const call = obj.method.mock.calls[0];\n\n    deepStrictEqual(call.arguments, [1, 3]);\n    strictEqual(call.result, 9);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, obj);\n\n    strictEqual(obj.method.mock.restore(), undefined);\n    strictEqual(obj.method(1, 3), 9);\n    strictEqual(obj.method.mock, undefined);\n  },\n};\n\nexport const spiesOnGetter = {\n  test() {\n    const obj = {\n      prop: 5,\n      get method() {\n        return this.prop;\n      },\n    };\n\n    strictEqual(obj.method, 5);\n\n    const getter = mock.method(obj, 'method', { getter: true });\n\n    strictEqual(getter.mock.calls.length, 0);\n    strictEqual(obj.method, 5);\n\n    const call = getter.mock.calls[0];\n\n    deepStrictEqual(call.arguments, []);\n    strictEqual(call.result, 5);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, obj);\n\n    strictEqual(getter.mock.restore(), undefined);\n    strictEqual(obj.method, 5);\n  },\n};\n\nexport const spiesOnSetter = {\n  test() {\n    const obj = {\n      prop: 100,\n      // eslint-disable-next-line accessor-pairs\n      set method(val) {\n        this.prop = val;\n      },\n    };\n\n    strictEqual(obj.prop, 100);\n    obj.method = 88;\n    strictEqual(obj.prop, 88);\n\n    const setter = mock.method(obj, 'method', { setter: true });\n\n    strictEqual(setter.mock.calls.length, 0);\n    obj.method = 77;\n    strictEqual(obj.prop, 77);\n    strictEqual(setter.mock.calls.length, 1);\n\n    const call = setter.mock.calls[0];\n\n    deepStrictEqual(call.arguments, [77]);\n    strictEqual(call.result, undefined);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, obj);\n\n    strictEqual(setter.mock.restore(), undefined);\n    strictEqual(obj.prop, 77);\n    obj.method = 65;\n    strictEqual(obj.prop, 65);\n  },\n};\n\nexport const spyFunctionsCanBeBound = {\n  test() {\n    const sum = mock.fn(function (arg1, arg2) {\n      return this + arg1 + arg2;\n    });\n    const bound = sum.bind(1000);\n\n    strictEqual(bound(9, 1), 1010);\n    strictEqual(sum.mock.calls.length, 1);\n\n    const call = sum.mock.calls[0];\n    deepStrictEqual(call.arguments, [9, 1]);\n    strictEqual(call.result, 1010);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, 1000);\n\n    strictEqual(sum.mock.restore(), undefined);\n    strictEqual(sum.bind(0)(2, 11), 13);\n  },\n};\n\nexport const mocksPrototypeMethodsOnAnInstance = {\n  async test() {\n    class Runner {\n      async someTask(msg) {\n        return Promise.resolve(msg);\n      }\n\n      async method(msg) {\n        await this.someTask(msg);\n        return msg;\n      }\n    }\n    const msg = 'ok';\n    const obj = new Runner();\n    strictEqual(await obj.method(msg), msg);\n\n    mock.method(obj, obj.someTask.name);\n    strictEqual(obj.someTask.mock.calls.length, 0);\n\n    strictEqual(await obj.method(msg), msg);\n\n    const call = obj.someTask.mock.calls[0];\n\n    deepStrictEqual(call.arguments, [msg]);\n    strictEqual(await call.result, msg);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, obj);\n\n    const obj2 = new Runner();\n    // Ensure that a brand new instance is not mocked\n    strictEqual(obj2.someTask.mock, undefined);\n\n    strictEqual(obj.someTask.mock.restore(), undefined);\n    strictEqual(await obj.method(msg), msg);\n    strictEqual(obj.someTask.mock, undefined);\n    strictEqual(Runner.prototype.someTask.mock, undefined);\n  },\n};\n\nexport const spiesOnAsyncStaticClassMethods = {\n  async test() {\n    class Runner {\n      static async someTask(msg) {\n        return Promise.resolve(msg);\n      }\n\n      static async method(msg) {\n        await this.someTask(msg);\n        return msg;\n      }\n    }\n    const msg = 'ok';\n    strictEqual(await Runner.method(msg), msg);\n\n    mock.method(Runner, Runner.someTask.name);\n    strictEqual(Runner.someTask.mock.calls.length, 0);\n\n    strictEqual(await Runner.method(msg), msg);\n\n    const call = Runner.someTask.mock.calls[0];\n\n    deepStrictEqual(call.arguments, [msg]);\n    strictEqual(await call.result, msg);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, Runner);\n\n    strictEqual(Runner.someTask.mock.restore(), undefined);\n    strictEqual(await Runner.method(msg), msg);\n    strictEqual(Runner.someTask.mock, undefined);\n    strictEqual(Runner.prototype.someTask, undefined);\n  },\n};\n\nexport const givenNullToAMockMethodItThrowsAInvalidArgumentError = {\n  test() {\n    throws(() => mock.method(null, {}), { code: 'ERR_INVALID_ARG_TYPE' });\n  },\n};\n\nexport const itShouldThrowGivenAnInexistentPropertyOnAObjectInstance = {\n  test() {\n    throws(() => mock.method({ abc: 0 }, 'non-existent'), {\n      code: 'ERR_INVALID_ARG_VALUE',\n    });\n  },\n};\n\nexport const spyFunctionsCanBeUsedOnClassesInheritance = {\n  test() {\n    // Makes sure that having a null-prototype doesn't throw our system off\n    class A extends null {\n      static someTask(msg) {\n        return msg;\n      }\n      static method(msg) {\n        return this.someTask(msg);\n      }\n    }\n    class B extends A {}\n    class C extends B {}\n\n    const msg = 'ok';\n    strictEqual(C.method(msg), msg);\n\n    mock.method(C, C.someTask.name);\n    strictEqual(C.someTask.mock.calls.length, 0);\n\n    strictEqual(C.method(msg), msg);\n\n    const call = C.someTask.mock.calls[0];\n\n    deepStrictEqual(call.arguments, [msg]);\n    strictEqual(call.result, msg);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, C);\n\n    strictEqual(C.someTask.mock.restore(), undefined);\n    strictEqual(C.method(msg), msg);\n    strictEqual(C.someTask.mock, undefined);\n  },\n};\n\nexport const spyFunctionsDontAffectThePrototypeChain = {\n  test() {\n    class A {\n      static someTask(msg) {\n        return msg;\n      }\n    }\n    class B extends A {}\n    class C extends B {}\n\n    const msg = 'ok';\n\n    const ABeforeMockIsUnchanged = Object.getOwnPropertyDescriptor(\n      A,\n      A.someTask.name\n    );\n    const BBeforeMockIsUnchanged = Object.getOwnPropertyDescriptor(\n      B,\n      B.someTask.name\n    );\n    const CBeforeMockShouldNotHaveDesc = Object.getOwnPropertyDescriptor(\n      C,\n      C.someTask.name\n    );\n\n    mock.method(C, C.someTask.name);\n    C.someTask(msg);\n    const BAfterMockIsUnchanged = Object.getOwnPropertyDescriptor(\n      B,\n      B.someTask.name\n    );\n\n    const AAfterMockIsUnchanged = Object.getOwnPropertyDescriptor(\n      A,\n      A.someTask.name\n    );\n    const CAfterMockHasDescriptor = Object.getOwnPropertyDescriptor(\n      C,\n      C.someTask.name\n    );\n\n    strictEqual(CBeforeMockShouldNotHaveDesc, undefined);\n    ok(CAfterMockHasDescriptor);\n\n    deepStrictEqual(ABeforeMockIsUnchanged, AAfterMockIsUnchanged);\n    strictEqual(BBeforeMockIsUnchanged, BAfterMockIsUnchanged);\n    strictEqual(BBeforeMockIsUnchanged, undefined);\n\n    strictEqual(C.someTask.mock.restore(), undefined);\n    const CAfterRestoreKeepsDescriptor = Object.getOwnPropertyDescriptor(\n      C,\n      C.someTask.name\n    );\n    ok(CAfterRestoreKeepsDescriptor);\n  },\n};\n\nexport const mockedFunctionsReportThrownErrors = {\n  test() {\n    const testError = new Error('test error');\n    const fn = mock.fn(() => {\n      throw testError;\n    });\n\n    throws(fn, /test error/);\n    strictEqual(fn.mock.calls.length, 1);\n\n    const call = fn.mock.calls[0];\n\n    deepStrictEqual(call.arguments, []);\n    strictEqual(call.error, testError);\n    strictEqual(call.result, undefined);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, undefined);\n  },\n};\n\nexport const mockedConstructorsReportThrownErrors = {\n  test() {\n    const testError = new Error('test error');\n    class Clazz {\n      constructor() {\n        throw testError;\n      }\n    }\n\n    const ctor = mock.fn(Clazz);\n\n    throws(() => {\n      new ctor();\n    }, /test error/);\n    strictEqual(ctor.mock.calls.length, 1);\n\n    const call = ctor.mock.calls[0];\n\n    deepStrictEqual(call.arguments, []);\n    strictEqual(call.error, testError);\n    strictEqual(call.result, undefined);\n    strictEqual(call.target, Clazz);\n    strictEqual(call.this, undefined);\n  },\n};\n\nexport const mocksAFunction = {\n  test() {\n    const sum = (arg1, arg2) => arg1 + arg2;\n    const difference = (arg1, arg2) => arg1 - arg2;\n    const fn = mock.fn(sum, difference);\n\n    strictEqual(fn.mock.calls.length, 0);\n    strictEqual(fn(3, 4), -1);\n    strictEqual(fn(9, 1), 8);\n    strictEqual(fn.mock.calls.length, 2);\n\n    let call = fn.mock.calls[0];\n    deepStrictEqual(call.arguments, [3, 4]);\n    strictEqual(call.result, -1);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, undefined);\n\n    call = fn.mock.calls[1];\n    deepStrictEqual(call.arguments, [9, 1]);\n    strictEqual(call.result, 8);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, undefined);\n\n    strictEqual(fn.mock.restore(), undefined);\n    strictEqual(fn(2, 11), 13);\n  },\n};\n\nexport const mocksAConstructor = {\n  test() {\n    class ParentClazz {\n      constructor(c) {\n        this.c = c;\n      }\n    }\n\n    class Clazz extends ParentClazz {\n      #privateValue;\n\n      constructor(a, b) {\n        super(a + b);\n        this.a = a;\n        this.#privateValue = b;\n      }\n\n      getPrivateValue() {\n        return this.#privateValue;\n      }\n    }\n\n    class MockClazz {\n      constructor(z) {\n        this.z = z;\n      }\n    }\n\n    const ctor = mock.fn(Clazz, MockClazz);\n    const instance = new ctor(42, 85);\n\n    ok(!(instance instanceof MockClazz));\n    ok(instance instanceof Clazz);\n    ok(instance instanceof ParentClazz);\n    strictEqual(instance.a, undefined);\n    strictEqual(instance.c, undefined);\n    strictEqual(instance.z, 42);\n    strictEqual(ctor.mock.calls.length, 1);\n\n    const call = ctor.mock.calls[0];\n\n    deepStrictEqual(call.arguments, [42, 85]);\n    strictEqual(call.result, instance);\n    strictEqual(call.target, Clazz);\n    strictEqual(call.this, instance);\n    throws(() => {\n      instance.getPrivateValue();\n    }, /TypeError: Cannot read private member #privateValue /);\n  },\n};\n\nexport const mocksAnObjectMethod = {\n  test() {\n    const obj = {\n      prop: 5,\n      method(a, b) {\n        return a + b + this.prop;\n      },\n    };\n\n    function mockMethod(a) {\n      return a + this.prop;\n    }\n\n    strictEqual(obj.method(1, 3), 9);\n    mock.method(obj, 'method', mockMethod);\n    strictEqual(obj.method.mock.calls.length, 0);\n    strictEqual(obj.method(1, 3), 6);\n\n    const call = obj.method.mock.calls[0];\n\n    deepStrictEqual(call.arguments, [1, 3]);\n    strictEqual(call.result, 6);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, obj);\n\n    strictEqual(obj.method.mock.restore(), undefined);\n    strictEqual(obj.method(1, 3), 9);\n    strictEqual(obj.method.mock, undefined);\n  },\n};\n\nexport const mocksAGetter = {\n  test() {\n    const obj = {\n      prop: 5,\n      get method() {\n        return this.prop;\n      },\n    };\n\n    function mockMethod() {\n      return this.prop - 1;\n    }\n\n    strictEqual(obj.method, 5);\n\n    const getter = mock.method(obj, 'method', mockMethod, { getter: true });\n\n    strictEqual(getter.mock.calls.length, 0);\n    strictEqual(obj.method, 4);\n\n    const call = getter.mock.calls[0];\n\n    deepStrictEqual(call.arguments, []);\n    strictEqual(call.result, 4);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, obj);\n\n    strictEqual(getter.mock.restore(), undefined);\n    strictEqual(obj.method, 5);\n  },\n};\n\nexport const mocksASetter = {\n  test() {\n    const obj = {\n      prop: 100,\n      // eslint-disable-next-line accessor-pairs\n      set method(val) {\n        this.prop = val;\n      },\n    };\n\n    function mockMethod(val) {\n      this.prop = -val;\n    }\n\n    strictEqual(obj.prop, 100);\n    obj.method = 88;\n    strictEqual(obj.prop, 88);\n\n    const setter = mock.method(obj, 'method', mockMethod, { setter: true });\n\n    strictEqual(setter.mock.calls.length, 0);\n    obj.method = 77;\n    strictEqual(obj.prop, -77);\n    strictEqual(setter.mock.calls.length, 1);\n\n    const call = setter.mock.calls[0];\n\n    deepStrictEqual(call.arguments, [77]);\n    strictEqual(call.result, undefined);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, obj);\n\n    strictEqual(setter.mock.restore(), undefined);\n    strictEqual(obj.prop, -77);\n    obj.method = 65;\n    strictEqual(obj.prop, 65);\n  },\n};\n\nexport const mocksAGetterWithSyntaxSugar = {\n  test() {\n    const obj = {\n      prop: 5,\n      get method() {\n        return this.prop;\n      },\n    };\n\n    function mockMethod() {\n      return this.prop - 1;\n    }\n    const getter = mock.getter(obj, 'method', mockMethod);\n    strictEqual(getter.mock.calls.length, 0);\n    strictEqual(obj.method, 4);\n\n    const call = getter.mock.calls[0];\n\n    deepStrictEqual(call.arguments, []);\n    strictEqual(call.result, 4);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, obj);\n\n    strictEqual(getter.mock.restore(), undefined);\n    strictEqual(obj.method, 5);\n  },\n};\n\nexport const mocksASetterWithSyntaxSugar = {\n  test() {\n    const obj = {\n      prop: 100,\n      // eslint-disable-next-line accessor-pairs\n      set method(val) {\n        this.prop = val;\n      },\n    };\n\n    function mockMethod(val) {\n      this.prop = -val;\n    }\n\n    strictEqual(obj.prop, 100);\n    obj.method = 88;\n    strictEqual(obj.prop, 88);\n\n    const setter = mock.setter(obj, 'method', mockMethod);\n\n    strictEqual(setter.mock.calls.length, 0);\n    obj.method = 77;\n    strictEqual(obj.prop, -77);\n    strictEqual(setter.mock.calls.length, 1);\n\n    const call = setter.mock.calls[0];\n\n    deepStrictEqual(call.arguments, [77]);\n    strictEqual(call.result, undefined);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, obj);\n\n    strictEqual(setter.mock.restore(), undefined);\n    strictEqual(obj.prop, -77);\n    obj.method = 65;\n    strictEqual(obj.prop, 65);\n  },\n};\n\nexport const mockedFunctionsMatchNameAndLength = {\n  test() {\n    function getNameAndLength(fn) {\n      return {\n        name: Object.getOwnPropertyDescriptor(fn, 'name'),\n        length: Object.getOwnPropertyDescriptor(fn, 'length'),\n      };\n    }\n\n    function func1() {}\n    const func2 = function (a) {}; // eslint-disable-line func-style\n    const arrow = (a, b, c) => {};\n    const obj = { method(a, b) {} };\n\n    deepStrictEqual(getNameAndLength(func1), getNameAndLength(mock.fn(func1)));\n    deepStrictEqual(getNameAndLength(func2), getNameAndLength(mock.fn(func2)));\n    deepStrictEqual(getNameAndLength(arrow), getNameAndLength(mock.fn(arrow)));\n    deepStrictEqual(\n      getNameAndLength(obj.method),\n      getNameAndLength(mock.method(obj, 'method', func1))\n    );\n  },\n};\n\nexport const methodFailsIfMethodCannotBeRedefined = {\n  test() {\n    const obj = {\n      prop: 5,\n    };\n\n    Object.defineProperty(obj, 'method', {\n      configurable: false,\n      value(a, b) {\n        return a + b + this.prop;\n      },\n    });\n\n    function mockMethod(a) {\n      return a + this.prop;\n    }\n\n    throws(() => {\n      mock.method(obj, 'method', mockMethod);\n    }, /Cannot redefine property: method/);\n    strictEqual(obj.method(1, 3), 9);\n    strictEqual(obj.method.mock, undefined);\n  },\n};\n\nexport const methodFailsIfFieldIsAPropertyInsteadOfAMethod = {\n  test() {\n    const obj = {\n      prop: 5,\n      method: 100,\n    };\n\n    function mockMethod(a) {\n      return a + this.prop;\n    }\n\n    throws(() => {\n      mock.method(obj, 'method', mockMethod);\n    }, /The argument 'methodName' must be a method/);\n    strictEqual(obj.method, 100);\n    strictEqual(obj.method.mock, undefined);\n  },\n};\n\nexport const mocksCanBeRestored = {\n  test() {\n    let cnt = 0;\n\n    function addOne() {\n      cnt++;\n      return cnt;\n    }\n\n    function addTwo() {\n      cnt += 2;\n      return cnt;\n    }\n\n    const fn = mock.fn(addOne, addTwo, { times: 2 });\n\n    strictEqual(fn(), 2);\n    strictEqual(fn(), 4);\n    strictEqual(fn(), 5);\n    strictEqual(fn(), 6);\n  },\n};\n\nexport const mockImplementationCanBeChangedDynamically = {\n  test() {\n    let cnt = 0;\n\n    function addOne() {\n      cnt++;\n      return cnt;\n    }\n\n    function addTwo() {\n      cnt += 2;\n      return cnt;\n    }\n\n    function addThree() {\n      cnt += 3;\n      return cnt;\n    }\n\n    function mustNotCall() {\n      return function () {\n        throw new Error('This function should not be called');\n      };\n    }\n\n    const fn = mock.fn(addOne);\n\n    strictEqual(fn.mock.callCount(), 0);\n    strictEqual(fn(), 1);\n    strictEqual(fn(), 2);\n    strictEqual(fn(), 3);\n    strictEqual(fn.mock.callCount(), 3);\n\n    fn.mock.mockImplementation(addTwo);\n    strictEqual(fn(), 5);\n    strictEqual(fn(), 7);\n    strictEqual(fn.mock.callCount(), 5);\n\n    fn.mock.restore();\n    strictEqual(fn(), 8);\n    strictEqual(fn(), 9);\n    strictEqual(fn.mock.callCount(), 7);\n\n    throws(() => {\n      fn.mock.mockImplementationOnce(mustNotCall(), 6);\n    }, /The value of \"onCall\" is out of range\\. It must be >= 7/);\n\n    fn.mock.mockImplementationOnce(addThree, 7);\n    fn.mock.mockImplementationOnce(addTwo, 8);\n    strictEqual(fn(), 12);\n    strictEqual(fn(), 14);\n    strictEqual(fn(), 15);\n    strictEqual(fn.mock.callCount(), 10);\n    fn.mock.mockImplementationOnce(addThree);\n    strictEqual(fn(), 18);\n    strictEqual(fn(), 19);\n    strictEqual(fn.mock.callCount(), 12);\n  },\n};\n\nexport const resetMockCalls = {\n  test() {\n    const sum = (arg1, arg2) => arg1 + arg2;\n    const difference = (arg1, arg2) => arg1 - arg2;\n    const fn = mock.fn(sum, difference);\n\n    strictEqual(fn(1, 2), -1);\n    strictEqual(fn(2, 1), 1);\n    strictEqual(fn.mock.calls.length, 2);\n    strictEqual(fn.mock.callCount(), 2);\n\n    fn.mock.resetCalls();\n    strictEqual(fn.mock.calls.length, 0);\n    strictEqual(fn.mock.callCount(), 0);\n\n    strictEqual(fn(3, 2), 1);\n  },\n};\n\nexport const usesTopLevelMock = {\n  test() {\n    function sum(a, b) {\n      return a + b;\n    }\n\n    function difference(a, b) {\n      return a - b;\n    }\n\n    const fn = mock.fn(sum, difference);\n\n    strictEqual(fn.mock.calls.length, 0);\n    strictEqual(fn(3, 4), -1);\n    strictEqual(fn.mock.calls.length, 1);\n    mock.reset();\n    strictEqual(fn(3, 4), 7);\n    strictEqual(fn.mock.calls.length, 2);\n  },\n};\n\nexport const theGetterAndSetterOptionsCannotBeUsedTogether = {\n  test() {\n    throws(() => {\n      mock.method({}, 'method', { getter: true, setter: true });\n    }, /The property 'options\\.setter' cannot be used with 'options\\.getter'/);\n  },\n};\n\nexport const methodNamesMustBeStringsOrSymbols = {\n  test() {\n    const symbol = Symbol();\n    const obj = {\n      method() {},\n      [symbol]() {},\n    };\n\n    mock.method(obj, 'method');\n    mock.method(obj, symbol);\n\n    throws(() => {\n      mock.method(obj, {});\n    }, /The \"methodName\" argument must be one of type string or symbol/);\n  },\n};\n\nexport const theTimesOptionMustBeAnIntegerGreaterOrEqualToOne = {\n  test() {\n    throws(() => {\n      mock.fn({ times: null });\n    }, /The \"options\\.times\" property must be of type number/);\n\n    throws(() => {\n      mock.fn({ times: 0 });\n    }, /The value of \"options\\.times\" is out of range/);\n\n    throws(() => {\n      mock.fn(() => {}, { times: 3.14159 });\n    }, /The value of \"options\\.times\" is out of range/);\n  },\n};\n\nexport const spiesOnAClassPrototypeMethod = {\n  test() {\n    class Clazz {\n      constructor(c) {\n        this.c = c;\n      }\n\n      getC() {\n        return this.c;\n      }\n    }\n\n    const instance = new Clazz(85);\n\n    strictEqual(instance.getC(), 85);\n    mock.method(Clazz.prototype, 'getC');\n\n    strictEqual(instance.getC.mock.calls.length, 0);\n    strictEqual(instance.getC(), 85);\n    strictEqual(instance.getC.mock.calls.length, 1);\n    strictEqual(Clazz.prototype.getC.mock.calls.length, 1);\n\n    const call = instance.getC.mock.calls[0];\n    deepStrictEqual(call.arguments, []);\n    strictEqual(call.result, 85);\n    strictEqual(call.error, undefined);\n    strictEqual(call.target, undefined);\n    strictEqual(call.this, instance);\n  },\n};\n\nexport const getterFailsIfGetterOptionsSetToFalse = {\n  test() {\n    throws(() => {\n      mock.getter({}, 'method', { getter: false });\n    }, /The property 'options\\.getter' cannot be false/);\n  },\n};\n\nexport const setterFailsIfSetterOptionsSetToFalse = {\n  test() {\n    throws(() => {\n      mock.setter({}, 'method', { setter: false });\n    }, /The property 'options\\.setter' cannot be false/);\n  },\n};\n\nexport const getterFailsIfSetterOptionsIsTrue = {\n  test() {\n    throws(() => {\n      mock.getter({}, 'method', { setter: true });\n    }, /The property 'options\\.setter' cannot be used with 'options\\.getter'/);\n  },\n};\n\nexport const setterFailsIfGetterOptionsIsTrue = {\n  test() {\n    throws(() => {\n      mock.setter({}, 'method', { getter: true });\n    }, /The property 'options\\.setter' cannot be used with 'options\\.getter'/);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/assert-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"assert-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"assert-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/async_hooks-nodejs-test.js",
    "content": "import async_hooks from 'node:async_hooks';\nimport { ok, deepStrictEqual, strictEqual } from 'node:assert';\n\nexport const testErrorMethodNotImplemented = {\n  async test() {\n    deepStrictEqual(async_hooks.executionAsyncId(), 0);\n    deepStrictEqual(async_hooks.triggerAsyncId(), 0);\n    deepStrictEqual(async_hooks.executionAsyncResource(), Object.create(null));\n    ok(async_hooks.createHook({}));\n  },\n};\n\nexport const asyncLocalStorageOptions = {\n  test() {\n    const als = new async_hooks.AsyncLocalStorage({\n      defaultValue: 1,\n      name: 'foo',\n    });\n    strictEqual(als.name, 'foo');\n    strictEqual(als.getStore(), 1);\n    strictEqual(\n      als.run(2, () => als.getStore()),\n      2\n    );\n    strictEqual(als.getStore(), 1);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/async_hooks-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"nodejs-async_hooks-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"async_hooks-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/bound-als-test.js",
    "content": "import { AsyncLocalStorage } from 'node:async_hooks';\nimport { strictEqual } from 'node:assert';\n\n// This test verifies that the AsyncLocalStorage snapshot and bind APIs appropriately\n// throw an error when called from outside the request context in which they were created.\n// This is a workers specific limitation and is not present in Node.js.\n\n// The test will only work if the associated compatibility flag is enabled.\nstrictEqual(\n  Cloudflare.compatibilityFlags.bind_asynclocalstorage_snapshot_to_request,\n  true\n);\n\nconst kErr =\n  'Cannot call this AsyncLocalStorage bound function outside of the ' +\n  'request in which it was created.';\n\nexport const test = {\n  async test(_, env) {\n    const resp = await env.subrequest.fetch(\n      'http://AsyncLocalStorage/snapshot'\n    );\n    strictEqual(resp.status, 200);\n    strictEqual(await resp.text(), 'ok');\n    const resp2 = await env.subrequest.fetch(\n      'http://AsyncLocalStorage/snapshot'\n    );\n    strictEqual(resp2.status, 500);\n    strictEqual(await resp2.text(), kErr);\n\n    const resp3 = await env.subrequest.fetch('http://AsyncLocalStorage/bind');\n    strictEqual(resp3.status, 200);\n    strictEqual(await resp3.text(), 'ok');\n    const resp4 = await env.subrequest.fetch('http://AsyncLocalStorage/bind');\n    strictEqual(resp4.status, 500);\n    strictEqual(await resp4.text(), kErr);\n  },\n};\n\nlet snapshot;\nlet boundFn;\n\nconst als = new AsyncLocalStorage();\nconst noRequestSnapshot = als.run(123, () => AsyncLocalStorage.snapshot());\n\nexport default {\n  async fetch(req) {\n    // A snapshot created outside of a request can still be used, and can\n    // perform i/o operation...\n    strictEqual(\n      noRequestSnapshot(() => {\n        // Works if no error is thrown. We don't care about the actual\n        // callback here.\n        setTimeout(() => {}, 0);\n        return als.getStore();\n      }),\n      123\n    );\n\n    try {\n      if (req.url.endsWith('/snapshot')) {\n        snapshot ??= AsyncLocalStorage.snapshot();\n        snapshot(() => {});\n        return new Response('ok');\n      } else if (req.url.endsWith('/bind')) {\n        boundFn ??= AsyncLocalStorage.bind(() => {});\n        boundFn();\n        return new Response('ok');\n      }\n      throw new Error('Unknown request');\n    } catch (err) {\n      return new Response(err.message, { status: 500 });\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/bound-als-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"bound-als-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"bound-als-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"nodejs_compat_v2\",\n          \"bind_asynclocalstorage_snapshot_to_request\",\n        ],\n        bindings = [\n          (name = \"subrequest\", service = \"bound-als-test\"),\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/buffer-effective-size-concat-test.js",
    "content": "import { Buffer } from 'node:buffer';\nimport { strictEqual } from 'node:assert';\n\nexport const BufferConcatResizeDuringGetter = {\n  async test() {\n    const ab = new ArrayBuffer(1024, { maxByteLength: 2048 });\n    const view = new Uint8Array(ab);\n    view.fill(0x41);\n\n    const arr = [view, new Uint8Array(0)];\n\n    Object.defineProperty(arr, '1', {\n      get() {\n        ab.resize(0);\n        return new Uint8Array(0);\n      },\n      enumerable: true,\n      configurable: true,\n    });\n\n    const result = Buffer.concat(arr, 1024);\n    strictEqual(result.length, 1024);\n    strictEqual(\n      result.every((b) => b === 0),\n      true\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/buffer-effective-size-concat-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"buffer-effective-size-concat-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"buffer-effective-size-concat-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat_v2\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/buffer-nodejs-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport {\n  deepStrictEqual,\n  notDeepStrictEqual,\n  notStrictEqual,\n  ok,\n  strictEqual,\n  throws,\n} from 'node:assert';\nimport util from 'node:util';\n\nimport {\n  Buffer,\n  File,\n  SlowBuffer,\n  constants,\n  isAscii,\n  isUtf8,\n  kMaxLength,\n  kStringMaxLength,\n  transcode,\n} from 'node:buffer';\n\nimport * as buffer from 'node:buffer';\nif (\n  buffer.Buffer !== Buffer ||\n  buffer.SlowBuffer !== SlowBuffer ||\n  buffer.kMaxLength !== kMaxLength ||\n  buffer.kStringMaxLength !== kStringMaxLength ||\n  buffer.constants !== constants\n) {\n  throw new Error('Incorrect default exports');\n}\n\nconst { MAX_LENGTH, MAX_STRING_LENGTH } = constants;\n\nexport const simpleAlloc = {\n  // test-buffer-alloc.js\n  test(ctrl, env, ctx) {\n    const b = Buffer.allocUnsafe(1024);\n    strictEqual(b.length, 1024);\n\n    b[0] = -1;\n    strictEqual(b[0], 255);\n\n    for (let i = 0; i < 1024; i++) {\n      b[i] = i % 256;\n    }\n\n    for (let i = 0; i < 1024; i++) {\n      strictEqual(i % 256, b[i]);\n    }\n\n    const c = Buffer.allocUnsafe(512);\n    strictEqual(c.length, 512);\n\n    const d = Buffer.from([]);\n    strictEqual(d.length, 0);\n  },\n};\n\nexport const offsetProperties = {\n  test(ctrl, env, ctx) {\n    const b = Buffer.alloc(128);\n    strictEqual(b.length, 128);\n    strictEqual(b.byteOffset, 0);\n    strictEqual(b.offset, 0);\n  },\n};\n\nexport const bufferFromUint8Array = {\n  test(ctrl, env, ctx) {\n    {\n      const ui8 = new Uint8Array(4).fill(42);\n      const e = Buffer.from(ui8);\n      for (const [index, value] of e.entries()) {\n        strictEqual(value, ui8[index]);\n      }\n    }\n\n    {\n      const ui8 = new Uint8Array(4).fill(42);\n      const e = Buffer(ui8);\n      for (const [key, value] of e.entries()) {\n        strictEqual(value, ui8[key]);\n      }\n    }\n  },\n};\n\nexport const bufferFromUint32Array = {\n  test(ctrl, env, ctx) {\n    {\n      const ui32 = new Uint32Array(4).fill(42);\n      const e = Buffer.from(ui32);\n      for (const [index, value] of e.entries()) {\n        strictEqual(value, ui32[index]);\n      }\n    }\n    {\n      const ui32 = new Uint32Array(4).fill(42);\n      const e = Buffer(ui32);\n      for (const [key, value] of e.entries()) {\n        strictEqual(value, ui32[key]);\n      }\n    }\n  },\n};\n\nexport const invalidEncodingForToString = {\n  test(ctrl, env, ctx) {\n    const b = Buffer.allocUnsafe(10);\n    // Test invalid encoding for Buffer.toString\n    throws(() => b.toString('invalid'), /Unknown encoding: invalid/);\n    // // Invalid encoding for Buffer.write\n    throws(\n      () => b.write('test string', 0, 5, 'invalid'),\n      /Unknown encoding: invalid/\n    );\n    // Unsupported arguments for Buffer.write\n\n    throws(() => b.write('test', 'utf8', 0), { code: 'ERR_INVALID_ARG_TYPE' });\n  },\n};\n\nexport const toStringWithUndefinedEncoding = {\n  test(ctrl, env, ctx) {\n    // Test that Buffer.toString with undefined encoding defaults to utf8\n    const buf = Buffer.from('hello world');\n    strictEqual(buf.toString(undefined), 'hello world');\n    strictEqual(buf.toString(undefined), buf.toString('utf8'));\n\n    // Test with UTF-8 characters\n    const utf8Buf = Buffer.from('¡hέlló wôrld!');\n    strictEqual(utf8Buf.toString(undefined), '¡hέlló wôrld!');\n    strictEqual(utf8Buf.toString(undefined), utf8Buf.toString('utf8'));\n\n    // Test with start and end parameters\n    const sliceBuf = Buffer.from('hello world');\n    strictEqual(sliceBuf.toString(undefined, 0, 5), 'hello');\n    strictEqual(sliceBuf.toString(undefined, 6), 'world');\n    strictEqual(\n      sliceBuf.toString(undefined, 0, 5),\n      sliceBuf.toString('utf8', 0, 5)\n    );\n  },\n};\n\nexport const zeroLengthBuffers = {\n  test(ctrl, env, ctx) {\n    Buffer.from('');\n    Buffer.from('', 'ascii');\n    Buffer.from('', 'latin1');\n    Buffer.alloc(0);\n    Buffer.allocUnsafe(0);\n    new Buffer('');\n    new Buffer('', 'ascii');\n    new Buffer('', 'latin1');\n    new Buffer('', 'binary');\n    Buffer(0);\n  },\n};\n\nexport const outOfBoundsWrites = {\n  test(ctrl, env, ctx) {\n    const outOfRangeError = {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    };\n\n    const b = Buffer.alloc(1024);\n\n    // Try to write a 0-length string beyond the end of b\n    throws(() => b.write('', 2048), outOfRangeError);\n\n    // Throw when writing to negative offset\n    throws(() => b.write('a', -1), outOfRangeError);\n\n    // Throw when writing past bounds from the pool\n    throws(() => b.write('a', 2048), outOfRangeError);\n\n    // Throw when writing to negative offset\n    throws(() => b.write('a', -1), outOfRangeError);\n\n    // Try to copy 0 bytes worth of data into an empty buffer\n    b.copy(Buffer.alloc(0), 0, 0, 0);\n\n    // Try to copy 0 bytes past the end of the target buffer\n    b.copy(Buffer.alloc(0), 1, 1, 1);\n    b.copy(Buffer.alloc(1), 1, 1, 1);\n\n    // Try to copy 0 bytes from past the end of the source buffer\n    b.copy(Buffer.alloc(1), 0, 2048, 2048);\n\n    Buffer.alloc(1).write('', 1, 0);\n  },\n};\n\nexport const smartDefaults = {\n  test(ctrl, env, ctx) {\n    const writeTest = Buffer.from('abcdes');\n    writeTest.write('n', 'ascii');\n    throws(() => writeTest.write('o', '1', 'ascii'), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    writeTest.write('o', 1, 'ascii');\n    writeTest.write('d', 2, 'ascii');\n    writeTest.write('e', 3, 'ascii');\n    writeTest.write('j', 4, 'ascii');\n    strictEqual(writeTest.toString(), 'nodejs');\n  },\n};\n\nexport const asciiSlice = {\n  test(ctrl, env, ctx) {\n    const b = Buffer.alloc(1024);\n\n    {\n      const asciiString = 'hello world';\n\n      for (let i = 0; i < asciiString.length; i++) {\n        b[i] = asciiString.charCodeAt(i);\n      }\n      const asciiSlice = b.toString('ascii', 0, asciiString.length);\n      strictEqual(asciiString, asciiSlice);\n    }\n\n    {\n      const asciiString = 'hello world';\n      const offset = 100;\n\n      strictEqual(asciiString.length, b.write(asciiString, offset, 'ascii'));\n      const asciiSlice = b.toString(\n        'ascii',\n        offset,\n        offset + asciiString.length\n      );\n      strictEqual(asciiString, asciiSlice);\n    }\n\n    {\n      const asciiString = 'hello world';\n      const offset = 100;\n\n      const sliceA = b.slice(offset, offset + asciiString.length);\n      const sliceB = b.slice(offset, offset + asciiString.length);\n      for (let i = 0; i < asciiString.length; i++) {\n        strictEqual(sliceA[i], sliceB[i]);\n      }\n    }\n  },\n};\n\nexport const utf8Slice = {\n  test(ctrl, env, ctx) {\n    const b = Buffer.alloc(1024);\n    {\n      const utf8String = '¡hέlló wôrld!';\n      const offset = 100;\n\n      b.write(utf8String, 0, Buffer.byteLength(utf8String), 'utf8');\n      let utf8Slice = b.toString('utf8', 0, Buffer.byteLength(utf8String));\n      strictEqual(utf8String, utf8Slice);\n\n      strictEqual(\n        Buffer.byteLength(utf8String),\n        b.write(utf8String, offset, 'utf8')\n      );\n      utf8Slice = b.toString(\n        'utf8',\n        offset,\n        offset + Buffer.byteLength(utf8String)\n      );\n      strictEqual(utf8String, utf8Slice);\n\n      const sliceA = b.slice(offset, offset + Buffer.byteLength(utf8String));\n      const sliceB = b.slice(offset, offset + Buffer.byteLength(utf8String));\n      for (let i = 0; i < Buffer.byteLength(utf8String); i++) {\n        strictEqual(sliceA[i], sliceB[i]);\n      }\n    }\n    {\n      const slice = b.slice(100, 150);\n      strictEqual(slice.length, 50);\n      for (let i = 0; i < 50; i++) {\n        strictEqual(b[100 + i], slice[i]);\n      }\n    }\n\n    {\n      // Make sure only top level parent propagates from allocPool\n      // (We don't actually implement pooling, this is just carried over from Node.js)\n      const b = Buffer.allocUnsafe(5);\n      const c = b.slice(0, 4);\n      const d = c.slice(0, 2);\n      strictEqual(b.parent, c.parent);\n      strictEqual(b.parent, d.parent);\n    }\n\n    {\n      // Also from a non-pooled instance\n      const b = Buffer.allocUnsafeSlow(5);\n      const c = b.slice(0, 4);\n      const d = c.slice(0, 2);\n      strictEqual(c.parent, d.parent);\n    }\n\n    {\n      // Bug regression test\n      const testValue = '\\u00F6\\u65E5\\u672C\\u8A9E'; // ö日本語\n      const buffer = Buffer.allocUnsafe(32);\n      const size = buffer.write(testValue, 0, 'utf8');\n      const slice = buffer.toString('utf8', 0, size);\n      strictEqual(slice, testValue);\n    }\n\n    {\n      // Test triple  slice\n      const a = Buffer.allocUnsafe(8);\n      for (let i = 0; i < 8; i++) a[i] = i;\n      const b = a.slice(4, 8);\n      strictEqual(b[0], 4);\n      strictEqual(b[1], 5);\n      strictEqual(b[2], 6);\n      strictEqual(b[3], 7);\n      const c = b.slice(2, 4);\n      strictEqual(c[0], 6);\n      strictEqual(c[1], 7);\n    }\n  },\n};\n\nexport const bufferFrom = {\n  test(ctrl, env, ctx) {\n    {\n      const d = Buffer.from([23, 42, 255]);\n      strictEqual(d.length, 3);\n      strictEqual(d[0], 23);\n      strictEqual(d[1], 42);\n      strictEqual(d[2], 255);\n      deepStrictEqual(d, Buffer.from(d));\n    }\n    {\n      // Test for proper UTF-8 Encoding\n      const e = Buffer.from('über');\n      deepStrictEqual(e, Buffer.from([195, 188, 98, 101, 114]));\n    }\n    {\n      // Test for proper ascii Encoding, length should be 4\n      const f = Buffer.from('über', 'ascii');\n      deepStrictEqual(f, Buffer.from([252, 98, 101, 114]));\n    }\n    ['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach((encoding) => {\n      {\n        // Test for proper UTF16LE encoding, length should be 8\n        const f = Buffer.from('über', encoding);\n        deepStrictEqual(f, Buffer.from([252, 0, 98, 0, 101, 0, 114, 0]));\n      }\n\n      {\n        // Length should be 12\n        const f = Buffer.from('привет', encoding);\n        deepStrictEqual(\n          f,\n          Buffer.from([63, 4, 64, 4, 56, 4, 50, 4, 53, 4, 66, 4])\n        );\n        strictEqual(f.toString(encoding), 'привет');\n      }\n\n      {\n        const f = Buffer.from([0, 0, 0, 0, 0]);\n        strictEqual(f.length, 5);\n        const size = f.write('あいうえお', encoding);\n        strictEqual(size, 4);\n        deepStrictEqual(f, Buffer.from([0x42, 0x30, 0x44, 0x30, 0x00]));\n      }\n\n      {\n        const f = Buffer.from('\\uD83D\\uDC4D', 'utf-16le'); // THUMBS UP SIGN (U+1F44D)\n        strictEqual(f.length, 4);\n        deepStrictEqual(f, Buffer.from('3DD84DDC', 'hex'));\n      }\n\n      {\n        const arrayIsh = { 0: 0, 1: 1, 2: 2, 3: 3, length: 4 };\n        let g = Buffer.from(arrayIsh);\n        deepStrictEqual(g, Buffer.from([0, 1, 2, 3]));\n        const strArrayIsh = { 0: '0', 1: '1', 2: '2', 3: '3', length: 4 };\n        g = Buffer.from(strArrayIsh);\n        deepStrictEqual(g, Buffer.from([0, 1, 2, 3]));\n      }\n    });\n\n    {\n      const checkString = 'test';\n\n      const check = Buffer.from(checkString);\n\n      class MyString extends String {\n        constructor() {\n          super(checkString);\n        }\n      }\n\n      class MyPrimitive {\n        [Symbol.toPrimitive]() {\n          return checkString;\n        }\n      }\n\n      class MyBadPrimitive {\n        [Symbol.toPrimitive]() {\n          return 1;\n        }\n      }\n\n      deepStrictEqual(Buffer.from(new String(checkString)), check);\n      deepStrictEqual(Buffer.from(new MyString()), check);\n      deepStrictEqual(Buffer.from(new MyPrimitive()), check);\n\n      [\n        {},\n        new Boolean(true),\n        {\n          valueOf() {\n            return null;\n          },\n        },\n        {\n          valueOf() {\n            return undefined;\n          },\n        },\n        { valueOf: null },\n        { __proto__: null },\n        new Number(true),\n        new MyBadPrimitive(),\n        Symbol(),\n        5n,\n        (one, two, three) => {},\n        undefined,\n        null,\n      ].forEach((input) => {\n        const errObj = {\n          name: 'TypeError',\n        };\n        throws(() => Buffer.from(input), errObj);\n        throws(() => Buffer.from(input, 'hex'), errObj);\n      });\n\n      Buffer.allocUnsafe(10); // Should not throw.\n      Buffer.from('deadbeaf', 'hex'); // Should not throw.\n    }\n  },\n};\n\nexport const base64 = {\n  test(ctrl, env, ctx) {\n    const base64flavors = ['base64', 'base64url'];\n    {\n      strictEqual(Buffer.from('Man').toString('base64'), 'TWFu');\n      strictEqual(Buffer.from('Woman').toString('base64'), 'V29tYW4=');\n      strictEqual(Buffer.from('Man').toString('base64url'), 'TWFu');\n      strictEqual(Buffer.from('Woman').toString('base64url'), 'V29tYW4');\n    }\n\n    {\n      const expected = [0xff, 0xff, 0xbe, 0xff, 0xef, 0xbf, 0xfb, 0xef, 0xff];\n      deepStrictEqual(\n        Buffer.from('//++/++/++//', 'base64'),\n        Buffer.from(expected)\n      );\n      deepStrictEqual(\n        Buffer.from('__--_--_--__', 'base64'),\n        Buffer.from(expected)\n      );\n      deepStrictEqual(\n        Buffer.from('//++/++/++//', 'base64url'),\n        Buffer.from(expected)\n      );\n      deepStrictEqual(\n        Buffer.from('__--_--_--__', 'base64url'),\n        Buffer.from(expected)\n      );\n    }\n\n    {\n      // Test that regular and URL-safe base64 both work both ways with padding\n      const expected = [\n        0xff, 0xff, 0xbe, 0xff, 0xef, 0xbf, 0xfb, 0xef, 0xff, 0xfb,\n      ];\n      deepStrictEqual(\n        Buffer.from('//++/++/++//+w==', 'base64'),\n        Buffer.from(expected)\n      );\n      deepStrictEqual(\n        Buffer.from('//++/++/++//+w==', 'base64'),\n        Buffer.from(expected)\n      );\n      deepStrictEqual(\n        Buffer.from('//++/++/++//+w==', 'base64url'),\n        Buffer.from(expected)\n      );\n      deepStrictEqual(\n        Buffer.from('//++/++/++//+w==', 'base64url'),\n        Buffer.from(expected)\n      );\n    }\n\n    {\n      // big example\n      const quote =\n        'Man is distinguished, not only by his reason, but by this ' +\n        'singular passion from other animals, which is a lust ' +\n        'of the mind, that by a perseverance of delight in the ' +\n        'continued and indefatigable generation of knowledge, ' +\n        'exceeds the short vehemence of any carnal pleasure.';\n      const expected =\n        'TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb' +\n        '24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlci' +\n        'BhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQ' +\n        'gYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu' +\n        'dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZ' +\n        'GdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm' +\n        '5hbCBwbGVhc3VyZS4=';\n      strictEqual(Buffer.from(quote).toString('base64'), expected);\n      strictEqual(\n        Buffer.from(quote).toString('base64url'),\n        expected.replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '')\n      );\n\n      base64flavors.forEach((encoding) => {\n        let b = Buffer.allocUnsafe(1024);\n        let bytesWritten = b.write(expected, 0, encoding);\n        strictEqual(quote.length, bytesWritten);\n        strictEqual(quote, b.toString('ascii', 0, quote.length));\n\n        // Check that the base64 decoder ignores whitespace\n        const expectedWhite =\n          `${expected.slice(0, 60)} \\n` +\n          `${expected.slice(60, 120)} \\n` +\n          `${expected.slice(120, 180)} \\n` +\n          `${expected.slice(180, 240)} \\n` +\n          `${expected.slice(240, 300)}\\n` +\n          `${expected.slice(300, 360)}\\n`;\n        b = Buffer.allocUnsafe(1024);\n        bytesWritten = b.write(expectedWhite, 0, encoding);\n        strictEqual(quote.length, bytesWritten);\n        strictEqual(quote, b.toString('ascii', 0, quote.length));\n\n        // Check that the base64 decoder on the constructor works\n        // even in the presence of whitespace.\n        b = Buffer.from(expectedWhite, encoding);\n        strictEqual(quote.length, b.length);\n        strictEqual(quote, b.toString('ascii', 0, quote.length));\n\n        // Check that the base64 decoder ignores illegal chars\n        const expectedIllegal =\n          expected.slice(0, 60) +\n          ' \\x80' +\n          expected.slice(60, 120) +\n          ' \\xff' +\n          expected.slice(120, 180) +\n          ' \\x00' +\n          expected.slice(180, 240) +\n          ' \\x98' +\n          expected.slice(240, 300) +\n          '\\x03' +\n          expected.slice(300, 360);\n        b = Buffer.from(expectedIllegal, encoding);\n        strictEqual(quote.length, b.length);\n        strictEqual(quote, b.toString('ascii', 0, quote.length));\n      });\n    }\n\n    base64flavors.forEach((encoding) => {\n      strictEqual(Buffer.from('', encoding).toString(), '');\n      strictEqual(Buffer.from('K', encoding).toString(), '');\n\n      // multiple-of-4 with padding\n      strictEqual(Buffer.from('Kg==', encoding).toString(), '*');\n      strictEqual(Buffer.from('Kio=', encoding).toString(), '*'.repeat(2));\n      strictEqual(Buffer.from('Kioq', encoding).toString(), '*'.repeat(3));\n      strictEqual(Buffer.from('KioqKg==', encoding).toString(), '*'.repeat(4));\n      strictEqual(Buffer.from('KioqKio=', encoding).toString(), '*'.repeat(5));\n      strictEqual(Buffer.from('KioqKioq', encoding).toString(), '*'.repeat(6));\n      strictEqual(\n        Buffer.from('KioqKioqKg==', encoding).toString(),\n        '*'.repeat(7)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKio=', encoding).toString(),\n        '*'.repeat(8)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioq', encoding).toString(),\n        '*'.repeat(9)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioqKg==', encoding).toString(),\n        '*'.repeat(10)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioqKio=', encoding).toString(),\n        '*'.repeat(11)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioqKioq', encoding).toString(),\n        '*'.repeat(12)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioqKioqKg==', encoding).toString(),\n        '*'.repeat(13)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioqKioqKio=', encoding).toString(),\n        '*'.repeat(14)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioqKioqKioq', encoding).toString(),\n        '*'.repeat(15)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioqKioqKioqKg==', encoding).toString(),\n        '*'.repeat(16)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioqKioqKioqKio=', encoding).toString(),\n        '*'.repeat(17)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioqKioqKioqKioq', encoding).toString(),\n        '*'.repeat(18)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioqKioqKioqKioqKg==', encoding).toString(),\n        '*'.repeat(19)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioqKioqKioqKioqKio=', encoding).toString(),\n        '*'.repeat(20)\n      );\n\n      // No padding, not a multiple of 4\n      strictEqual(Buffer.from('Kg', encoding).toString(), '*');\n      strictEqual(Buffer.from('Kio', encoding).toString(), '*'.repeat(2));\n      strictEqual(Buffer.from('KioqKg', encoding).toString(), '*'.repeat(4));\n      strictEqual(Buffer.from('KioqKio', encoding).toString(), '*'.repeat(5));\n      strictEqual(\n        Buffer.from('KioqKioqKg', encoding).toString(),\n        '*'.repeat(7)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKio', encoding).toString(),\n        '*'.repeat(8)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioqKg', encoding).toString(),\n        '*'.repeat(10)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioqKio', encoding).toString(),\n        '*'.repeat(11)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioqKioqKg', encoding).toString(),\n        '*'.repeat(13)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioqKioqKio', encoding).toString(),\n        '*'.repeat(14)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioqKioqKioqKg', encoding).toString(),\n        '*'.repeat(16)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioqKioqKioqKio', encoding).toString(),\n        '*'.repeat(17)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioqKioqKioqKioqKg', encoding).toString(),\n        '*'.repeat(19)\n      );\n      strictEqual(\n        Buffer.from('KioqKioqKioqKioqKioqKioqKio', encoding).toString(),\n        '*'.repeat(20)\n      );\n    });\n\n    // Handle padding graciously, multiple-of-4 or not\n    strictEqual(\n      Buffer.from('72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw==', 'base64')\n        .length,\n      32\n    );\n    strictEqual(\n      Buffer.from('72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw==', 'base64url')\n        .length,\n      32\n    );\n    strictEqual(\n      Buffer.from('72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw=', 'base64')\n        .length,\n      32\n    );\n    strictEqual(\n      Buffer.from('72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw=', 'base64url')\n        .length,\n      32\n    );\n    strictEqual(\n      Buffer.from('72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw', 'base64')\n        .length,\n      32\n    );\n    strictEqual(\n      Buffer.from('72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw', 'base64url')\n        .length,\n      32\n    );\n    strictEqual(\n      Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg==', 'base64')\n        .length,\n      31\n    );\n    strictEqual(\n      Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg==', 'base64url')\n        .length,\n      31\n    );\n    strictEqual(\n      Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg=', 'base64')\n        .length,\n      31\n    );\n    strictEqual(\n      Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg=', 'base64url')\n        .length,\n      31\n    );\n    strictEqual(\n      Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg', 'base64')\n        .length,\n      31\n    );\n    strictEqual(\n      Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg', 'base64url')\n        .length,\n      31\n    );\n\n    {\n      // This string encodes single '.' character in UTF-16\n      const dot = Buffer.from('//4uAA==', 'base64');\n      strictEqual(dot[0], 0xff);\n      strictEqual(dot[1], 0xfe);\n      strictEqual(dot[2], 0x2e);\n      strictEqual(dot[3], 0x00);\n      strictEqual(dot.toString('base64'), '//4uAA==');\n    }\n\n    {\n      // This string encodes single '.' character in UTF-16\n      const dot = Buffer.from('//4uAA', 'base64url');\n      strictEqual(dot[0], 0xff);\n      strictEqual(dot[1], 0xfe);\n      strictEqual(dot[2], 0x2e);\n      strictEqual(dot[3], 0x00);\n      strictEqual(dot.toString('base64url'), '__4uAA');\n    }\n\n    {\n      // Writing base64 at a position > 0 should not mangle the result.\n      //\n      // https://github.com/joyent/node/issues/402\n      const segments = ['TWFkbmVzcz8h', 'IFRoaXM=', 'IGlz', 'IG5vZGUuanMh'];\n      const b = Buffer.allocUnsafe(64);\n      let pos = 0;\n\n      for (let i = 0; i < segments.length; ++i) {\n        pos += b.write(segments[i], pos, 'base64');\n      }\n      strictEqual(b.toString('latin1', 0, pos), 'Madness?! This is node.js!');\n    }\n\n    {\n      // Writing base64url at a position > 0 should not mangle the result.\n      //\n      // https://github.com/joyent/node/issues/402\n      const segments = ['TWFkbmVzcz8h', 'IFRoaXM', 'IGlz', 'IG5vZGUuanMh'];\n      const b = Buffer.allocUnsafe(64);\n      let pos = 0;\n\n      for (let i = 0; i < segments.length; ++i) {\n        pos += b.write(segments[i], pos, 'base64url');\n      }\n      strictEqual(b.toString('latin1', 0, pos), 'Madness?! This is node.js!');\n    }\n\n    // Regression test for https://github.com/nodejs/node/issues/3496.\n    strictEqual(Buffer.from('=bad'.repeat(1e4), 'base64').length, 0);\n\n    // // Regression test for https://github.com/nodejs/node/issues/11987.\n    deepStrictEqual(Buffer.from('w0  ', 'base64'), Buffer.from('w0', 'base64'));\n\n    // // Regression test for https://github.com/nodejs/node/issues/13657.\n    deepStrictEqual(\n      Buffer.from(' YWJvcnVtLg', 'base64'),\n      Buffer.from('YWJvcnVtLg', 'base64')\n    );\n  },\n};\n\nexport const hex = {\n  test(ctrl, env, ctx) {\n    {\n      // test hex toString\n      const hexb = Buffer.allocUnsafe(256);\n      for (let i = 0; i < 256; i++) {\n        hexb[i] = i;\n      }\n      const hexStr = hexb.toString('hex');\n      strictEqual(\n        hexStr,\n        '000102030405060708090a0b0c0d0e0f' +\n          '101112131415161718191a1b1c1d1e1f' +\n          '202122232425262728292a2b2c2d2e2f' +\n          '303132333435363738393a3b3c3d3e3f' +\n          '404142434445464748494a4b4c4d4e4f' +\n          '505152535455565758595a5b5c5d5e5f' +\n          '606162636465666768696a6b6c6d6e6f' +\n          '707172737475767778797a7b7c7d7e7f' +\n          '808182838485868788898a8b8c8d8e8f' +\n          '909192939495969798999a9b9c9d9e9f' +\n          'a0a1a2a3a4a5a6a7a8a9aaabacadaeaf' +\n          'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' +\n          'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' +\n          'd0d1d2d3d4d5d6d7d8d9dadbdcdddedf' +\n          'e0e1e2e3e4e5e6e7e8e9eaebecedeeef' +\n          'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'\n      );\n\n      const hexb2 = Buffer.from(hexStr, 'hex');\n      for (let i = 0; i < 256; i++) {\n        strictEqual(hexb2[i], hexb[i]);\n      }\n    }\n\n    // Test single hex character is discarded.\n    strictEqual(Buffer.from('A', 'hex').length, 0);\n\n    // Test that if a trailing character is discarded, rest of string is processed.\n    deepStrictEqual(Buffer.from('Abx', 'hex'), Buffer.from('Ab', 'hex'));\n\n    // Test single base64 char encodes as 0.\n    strictEqual(Buffer.from('A', 'base64').length, 0);\n\n    {\n      // Test an invalid slice end.\n      const b = Buffer.from([1, 2, 3, 4, 5]);\n      const b2 = b.toString('hex', 1, 10000);\n      const b3 = b.toString('hex', 1, 5);\n      const b4 = b.toString('hex', 1);\n      strictEqual(b2, b3);\n      strictEqual(b2, b4);\n    }\n  },\n};\n\nexport const slicing = {\n  test(ctrl, env, ctx) {\n    function buildBuffer(data) {\n      if (Array.isArray(data)) {\n        const buffer = Buffer.allocUnsafe(data.length);\n        data.forEach((v, k) => (buffer[k] = v));\n        return buffer;\n      }\n      return null;\n    }\n\n    const x = buildBuffer([\n      0x81, 0xa3, 0x66, 0x6f, 0x6f, 0xa3, 0x62, 0x61, 0x72,\n    ]);\n\n    {\n      const z = x.slice(4);\n      strictEqual(z.length, 5);\n      strictEqual(z[0], 0x6f);\n      strictEqual(z[1], 0xa3);\n      strictEqual(z[2], 0x62);\n      strictEqual(z[3], 0x61);\n      strictEqual(z[4], 0x72);\n    }\n\n    {\n      const z = x.slice(0);\n      strictEqual(z.length, x.length);\n    }\n\n    {\n      const z = x.slice(0, 4);\n      strictEqual(z.length, 4);\n      strictEqual(z[0], 0x81);\n      strictEqual(z[1], 0xa3);\n    }\n\n    {\n      const z = x.slice(0, 9);\n      strictEqual(z.length, 9);\n    }\n\n    {\n      const z = x.slice(1, 4);\n      strictEqual(z.length, 3);\n      strictEqual(z[0], 0xa3);\n    }\n\n    {\n      const z = x.slice(2, 4);\n      strictEqual(z.length, 2);\n      strictEqual(z[0], 0x66);\n      strictEqual(z[1], 0x6f);\n    }\n  },\n};\n\nexport const writing = {\n  test(ctrl, env, ctx) {\n    ['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach((encoding) => {\n      const b = Buffer.allocUnsafe(10);\n      b.write('あいうえお', encoding);\n      strictEqual(b.toString(encoding), 'あいうえお');\n    });\n\n    ['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach((encoding) => {\n      const b = Buffer.allocUnsafe(11);\n      b.write('あいうえお', 1, encoding);\n      strictEqual(b.toString(encoding, 1), 'あいうえお');\n    });\n\n    {\n      // latin1 encoding should write only one byte per character.\n      const b = Buffer.from([0xde, 0xad, 0xbe, 0xef]);\n      let s = String.fromCharCode(0xffff);\n      b.write(s, 0, 'latin1');\n      strictEqual(b[0], 0xff);\n      strictEqual(b[1], 0xad);\n      strictEqual(b[2], 0xbe);\n      strictEqual(b[3], 0xef);\n      s = String.fromCharCode(0xaaee);\n      b.write(s, 0, 'latin1');\n      strictEqual(b[0], 0xee);\n      strictEqual(b[1], 0xad);\n      strictEqual(b[2], 0xbe);\n      strictEqual(b[3], 0xef);\n    }\n\n    {\n      // Binary encoding should write only one byte per character.\n      const b = Buffer.from([0xde, 0xad, 0xbe, 0xef]);\n      let s = String.fromCharCode(0xffff);\n      b.write(s, 0, 'latin1');\n      strictEqual(b[0], 0xff);\n      strictEqual(b[1], 0xad);\n      strictEqual(b[2], 0xbe);\n      strictEqual(b[3], 0xef);\n      s = String.fromCharCode(0xaaee);\n      b.write(s, 0, 'latin1');\n      strictEqual(b[0], 0xee);\n      strictEqual(b[1], 0xad);\n      strictEqual(b[2], 0xbe);\n      strictEqual(b[3], 0xef);\n    }\n\n    {\n      const buf = Buffer.allocUnsafe(2);\n      strictEqual(buf.write(''), 0); // 0bytes\n      strictEqual(buf.write('\\0'), 1); // 1byte (v8 adds null terminator)\n      strictEqual(buf.write('a\\0'), 2); // 1byte * 2\n      strictEqual(buf.write('あ'), 0); // 3bytes\n      strictEqual(buf.write('\\0あ'), 1); // 1byte + 3bytes\n      strictEqual(buf.write('\\0\\0あ'), 2); // 1byte * 2 + 3bytes\n    }\n\n    {\n      const buf = Buffer.allocUnsafe(10);\n      strictEqual(buf.write('あいう'), 9); // 3bytes * 3 (v8 adds null term.)\n      strictEqual(buf.write('あいう\\0'), 10); // 3bytes * 3 + 1byte\n    }\n\n    {\n      // https://github.com/nodejs/node-v0.x-archive/issues/243\n      // Test write() with maxLength\n      const buf = Buffer.allocUnsafe(4);\n      buf.fill(0xff);\n      strictEqual(buf.write('abcd', 1, 2, 'utf8'), 2);\n      strictEqual(buf[0], 0xff);\n      strictEqual(buf[1], 0x61);\n      strictEqual(buf[2], 0x62);\n      strictEqual(buf[3], 0xff);\n\n      buf.fill(0xff);\n      strictEqual(buf.write('abcd', 1, 4), 3);\n      strictEqual(buf[0], 0xff);\n      strictEqual(buf[1], 0x61);\n      strictEqual(buf[2], 0x62);\n      strictEqual(buf[3], 0x63);\n\n      buf.fill(0xff);\n      strictEqual(buf.write('abcd', 1, 2, 'utf8'), 2);\n      strictEqual(buf[0], 0xff);\n      strictEqual(buf[1], 0x61);\n      strictEqual(buf[2], 0x62);\n      strictEqual(buf[3], 0xff);\n\n      buf.fill(0xff);\n      strictEqual(buf.write('abcdef', 1, 2, 'hex'), 2);\n      strictEqual(buf[0], 0xff);\n      strictEqual(buf[1], 0xab);\n      strictEqual(buf[2], 0xcd);\n      strictEqual(buf[3], 0xff);\n\n      ['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach((encoding) => {\n        buf.fill(0xff);\n        strictEqual(buf.write('abcd', 0, 2, encoding), 2);\n        strictEqual(buf[0], 0x61);\n        strictEqual(buf[1], 0x00);\n        strictEqual(buf[2], 0xff);\n        strictEqual(buf[3], 0xff);\n      });\n    }\n\n    {\n      // Test offset returns are correct\n      const b = Buffer.allocUnsafe(16);\n      strictEqual(b.writeUInt32LE(0, 0), 4);\n      strictEqual(b.writeUInt16LE(0, 4), 6);\n      strictEqual(b.writeUInt8(0, 6), 7);\n      strictEqual(b.writeInt8(0, 7), 8);\n      strictEqual(b.writeDoubleLE(0, 8), 16);\n    }\n\n    {\n      // Test for buffer overrun\n      const buf = Buffer.from([0, 0, 0, 0, 0]); // length: 5\n      const sub = buf.slice(0, 4); // length: 4\n      strictEqual(sub.write('12345', 'latin1'), 4);\n      strictEqual(buf[4], 0);\n      strictEqual(sub.write('12345', 'binary'), 4);\n      strictEqual(buf[4], 0);\n    }\n\n    // Test for common write(U)IntLE/BE\n    {\n      let buf = Buffer.allocUnsafe(3);\n      buf.writeUIntLE(0x123456, 0, 3);\n      deepStrictEqual(buf.toJSON().data, [0x56, 0x34, 0x12]);\n      strictEqual(buf.readUIntLE(0, 3), 0x123456);\n\n      buf.fill(0xff);\n      buf.writeUIntBE(0x123456, 0, 3);\n      deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56]);\n      strictEqual(buf.readUIntBE(0, 3), 0x123456);\n\n      buf.fill(0xff);\n      buf.writeIntLE(0x123456, 0, 3);\n      deepStrictEqual(buf.toJSON().data, [0x56, 0x34, 0x12]);\n      strictEqual(buf.readIntLE(0, 3), 0x123456);\n\n      buf.fill(0xff);\n      buf.writeIntBE(0x123456, 0, 3);\n      deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56]);\n      strictEqual(buf.readIntBE(0, 3), 0x123456);\n\n      buf.fill(0xff);\n      buf.writeIntLE(-0x123456, 0, 3);\n      deepStrictEqual(buf.toJSON().data, [0xaa, 0xcb, 0xed]);\n      strictEqual(buf.readIntLE(0, 3), -0x123456);\n\n      buf.fill(0xff);\n      buf.writeIntBE(-0x123456, 0, 3);\n      deepStrictEqual(buf.toJSON().data, [0xed, 0xcb, 0xaa]);\n      strictEqual(buf.readIntBE(0, 3), -0x123456);\n\n      buf.fill(0xff);\n      buf.writeIntLE(-0x123400, 0, 3);\n      deepStrictEqual(buf.toJSON().data, [0x00, 0xcc, 0xed]);\n      strictEqual(buf.readIntLE(0, 3), -0x123400);\n\n      buf.fill(0xff);\n      buf.writeIntBE(-0x123400, 0, 3);\n      deepStrictEqual(buf.toJSON().data, [0xed, 0xcc, 0x00]);\n      strictEqual(buf.readIntBE(0, 3), -0x123400);\n\n      buf.fill(0xff);\n      buf.writeIntLE(-0x120000, 0, 3);\n      deepStrictEqual(buf.toJSON().data, [0x00, 0x00, 0xee]);\n      strictEqual(buf.readIntLE(0, 3), -0x120000);\n\n      buf.fill(0xff);\n      buf.writeIntBE(-0x120000, 0, 3);\n      deepStrictEqual(buf.toJSON().data, [0xee, 0x00, 0x00]);\n      strictEqual(buf.readIntBE(0, 3), -0x120000);\n\n      buf = Buffer.allocUnsafe(5);\n      buf.writeUIntLE(0x1234567890, 0, 5);\n      deepStrictEqual(buf.toJSON().data, [0x90, 0x78, 0x56, 0x34, 0x12]);\n      strictEqual(buf.readUIntLE(0, 5), 0x1234567890);\n\n      buf.fill(0xff);\n      buf.writeUIntBE(0x1234567890, 0, 5);\n      deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56, 0x78, 0x90]);\n      strictEqual(buf.readUIntBE(0, 5), 0x1234567890);\n\n      buf.fill(0xff);\n      buf.writeIntLE(0x1234567890, 0, 5);\n      deepStrictEqual(buf.toJSON().data, [0x90, 0x78, 0x56, 0x34, 0x12]);\n      strictEqual(buf.readIntLE(0, 5), 0x1234567890);\n\n      buf.fill(0xff);\n      buf.writeIntBE(0x1234567890, 0, 5);\n      deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56, 0x78, 0x90]);\n      strictEqual(buf.readIntBE(0, 5), 0x1234567890);\n\n      buf.fill(0xff);\n      buf.writeIntLE(-0x1234567890, 0, 5);\n      deepStrictEqual(buf.toJSON().data, [0x70, 0x87, 0xa9, 0xcb, 0xed]);\n      strictEqual(buf.readIntLE(0, 5), -0x1234567890);\n\n      buf.fill(0xff);\n      buf.writeIntBE(-0x1234567890, 0, 5);\n      deepStrictEqual(buf.toJSON().data, [0xed, 0xcb, 0xa9, 0x87, 0x70]);\n      strictEqual(buf.readIntBE(0, 5), -0x1234567890);\n\n      buf.fill(0xff);\n      buf.writeIntLE(-0x0012000000, 0, 5);\n      deepStrictEqual(buf.toJSON().data, [0x00, 0x00, 0x00, 0xee, 0xff]);\n      strictEqual(buf.readIntLE(0, 5), -0x0012000000);\n\n      buf.fill(0xff);\n      buf.writeIntBE(-0x0012000000, 0, 5);\n      deepStrictEqual(buf.toJSON().data, [0xff, 0xee, 0x00, 0x00, 0x00]);\n      strictEqual(buf.readIntBE(0, 5), -0x0012000000);\n    }\n  },\n};\n\nexport const misc = {\n  test(ctrl, env, ctx) {\n    {\n      // https://github.com/nodejs/node-v0.x-archive/pull/1210\n      // Test UTF-8 string includes null character\n      let buf = Buffer.from('\\0');\n      strictEqual(buf.length, 1);\n      buf = Buffer.from('\\0\\0');\n      strictEqual(buf.length, 2);\n    }\n\n    {\n      // Test unmatched surrogates not producing invalid utf8 output\n      // ef bf bd = utf-8 representation of unicode replacement character\n      // see https://codereview.chromium.org/121173009/\n      const buf = Buffer.from('ab\\ud800cd', 'utf8');\n      strictEqual(buf[0], 0x61);\n      strictEqual(buf[1], 0x62);\n      strictEqual(buf[2], 0xef);\n      strictEqual(buf[3], 0xbf);\n      strictEqual(buf[4], 0xbd);\n      strictEqual(buf[5], 0x63);\n      strictEqual(buf[6], 0x64);\n    }\n\n    {\n      // Test alloc with fill option\n      const buf = Buffer.alloc(5, '800A', 'hex');\n      strictEqual(buf[0], 128);\n      strictEqual(buf[1], 10);\n      strictEqual(buf[2], 128);\n      strictEqual(buf[3], 10);\n      strictEqual(buf[4], 128);\n    }\n\n    // Call .fill() first, stops Valgrind warning about uninitialized memory reads.\n    Buffer.allocUnsafe(3.3).fill().toString();\n    // Throws bad argument error in commit 43cb4ec\n    Buffer.alloc(3.3).fill().toString();\n    strictEqual(Buffer.allocUnsafe(3.3).length, 3);\n    strictEqual(Buffer.from({ length: 3.3 }).length, 3);\n    strictEqual(Buffer.from({ length: 'BAM' }).length, 0);\n\n    strictEqual(Buffer.from('99').length, 2);\n    strictEqual(Buffer.from('13.37').length, 5);\n\n    // Ensure that the length argument is respected.\n    ['ascii', 'utf8', 'hex', 'base64', 'latin1', 'binary'].forEach((enc) => {\n      strictEqual(Buffer.allocUnsafe(1).write('aaaaaa', 0, 1, enc), 1);\n    });\n\n    {\n      // Regression test, guard against buffer overrun in the base64 decoder.\n      const a = Buffer.allocUnsafe(3);\n      const b = Buffer.from('xxx');\n      a.write('aaaaaaaa', 'base64');\n      strictEqual(b.toString(), 'xxx');\n    }\n\n    // issue GH-3416\n    Buffer.from(Buffer.allocUnsafe(0), 0, 0);\n\n    const outOfRangeError = {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    };\n\n    // issue GH-5587\n    throws(() => Buffer.alloc(8).writeFloatLE(0, 5), outOfRangeError);\n    throws(() => Buffer.alloc(16).writeDoubleLE(0, 9), outOfRangeError);\n\n    // Attempt to overflow buffers, similar to previous bug in array buffers\n    throws(\n      () => Buffer.allocUnsafe(8).writeFloatLE(0.0, 0xffffffff),\n      outOfRangeError\n    );\n    throws(\n      () => Buffer.allocUnsafe(8).writeFloatLE(0.0, 0xffffffff),\n      outOfRangeError\n    );\n\n    // Ensure negative values can't get past offset\n    throws(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, -1), outOfRangeError);\n    throws(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, -1), outOfRangeError);\n\n    // Regression test for https://github.com/nodejs/node-v0.x-archive/issues/5482:\n    // should throw but not assert in C++ land.\n    throws(() => Buffer.from('', 'buffer'), {\n      code: 'ERR_UNKNOWN_ENCODING',\n      name: 'TypeError',\n      message: 'Unknown encoding: buffer',\n    });\n\n    // Regression test for https://github.com/nodejs/node-v0.x-archive/issues/6111.\n    // Constructing a buffer from another buffer should a) work, and b) not corrupt\n    // the source buffer.\n    {\n      const a = [...Array(128).keys()]; // [0, 1, 2, 3, ... 126, 127]\n      const b = Buffer.from(a);\n      const c = Buffer.from(b);\n      strictEqual(b.length, a.length);\n      strictEqual(c.length, a.length);\n      for (let i = 0, k = a.length; i < k; ++i) {\n        strictEqual(a[i], i);\n        strictEqual(b[i], i);\n        strictEqual(c[i], i);\n      }\n    }\n\n    throws(() => Buffer.allocUnsafe(10).copy(), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n      message:\n        'The \"target\" argument must be an instance of Buffer or ' +\n        'Uint8Array. Received undefined',\n    });\n\n    throws(() => Buffer.from(), {\n      name: 'TypeError',\n    });\n    throws(() => Buffer.from(null), {\n      name: 'TypeError',\n    });\n\n    // Test prototype getters don't throw\n    strictEqual(Buffer.prototype.parent, undefined);\n    strictEqual(Buffer.prototype.offset, undefined);\n    strictEqual(SlowBuffer.prototype.parent, undefined);\n    strictEqual(SlowBuffer.prototype.offset, undefined);\n\n    {\n      // Test that large negative Buffer length inputs don't affect the pool offset.\n      // Use the fromArrayLike() variant here because it's more lenient\n      // about its input and passes the length directly to allocate().\n      deepStrictEqual(Buffer.from({ length: -3456 }), Buffer.from(''));\n      deepStrictEqual(Buffer.from({ length: -100 }), Buffer.from(''));\n\n      // Check pool offset after that by trying to write string into the pool.\n      Buffer.from('abc');\n    }\n\n    // Test that ParseArrayIndex handles full uint32\n    {\n      throws(() => Buffer.from(new ArrayBuffer(0), -1 >>> 0), {\n        code: 'ERR_BUFFER_OUT_OF_BOUNDS',\n        name: 'RangeError',\n        message: '\"offset\" is outside of buffer bounds',\n      });\n    }\n\n    // ParseArrayIndex() should reject values that don't fit in a 32 bits size_t.\n    throws(() => {\n      const a = Buffer.alloc(1);\n      const b = Buffer.alloc(1);\n      a.copy(b, 0, 0x100000000, 0x100000001);\n    }, outOfRangeError);\n\n    // Unpooled buffer (replaces SlowBuffer)\n    {\n      const ubuf = Buffer.allocUnsafeSlow(10);\n      ok(ubuf);\n      ok(ubuf.buffer);\n      strictEqual(ubuf.buffer.byteLength, 10);\n    }\n\n    // Regression test to verify that an empty ArrayBuffer does not throw.\n    Buffer.from(new ArrayBuffer());\n\n    throws(\n      () => Buffer.alloc({ valueOf: () => 1 }),\n      /\"size\" argument must be of type number/\n    );\n    throws(\n      () => Buffer.alloc({ valueOf: () => -1 }),\n      /\"size\" argument must be of type number/\n    );\n\n    strictEqual(Buffer.prototype.toLocaleString, Buffer.prototype.toString);\n    {\n      const buf = Buffer.from('test');\n      strictEqual(buf.toLocaleString(), buf.toString());\n    }\n\n    throws(\n      () => {\n        Buffer.alloc(0x1000, 'This is not correctly encoded', 'hex');\n      },\n      {\n        name: 'TypeError',\n      }\n    );\n\n    throws(\n      () => {\n        Buffer.alloc(0x1000, 'c', 'hex');\n      },\n      {\n        name: 'TypeError',\n      }\n    );\n\n    throws(\n      () => {\n        Buffer.alloc(1, Buffer.alloc(0));\n      },\n      {\n        code: 'ERR_INVALID_ARG_VALUE',\n        name: 'TypeError',\n      }\n    );\n\n    throws(\n      () => {\n        Buffer.alloc(40, 'x', 20);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n      }\n    );\n  },\n};\n\nexport const arrayBuffers = {\n  test(ctrl, env, ctx) {\n    const LENGTH = 16;\n\n    const ab = new ArrayBuffer(LENGTH);\n    const dv = new DataView(ab);\n    const ui = new Uint8Array(ab);\n    const buf = Buffer.from(ab);\n\n    ok(buf instanceof Buffer);\n    strictEqual(buf.parent, buf.buffer);\n    strictEqual(buf.buffer, ab);\n    strictEqual(buf.length, ab.byteLength);\n\n    buf.fill(0xc);\n    for (let i = 0; i < LENGTH; i++) {\n      strictEqual(ui[i], 0xc);\n      ui[i] = 0xf;\n      strictEqual(buf[i], 0xf);\n    }\n\n    buf.writeUInt32LE(0xf00, 0);\n    buf.writeUInt32BE(0xb47, 4);\n    buf.writeDoubleLE(3.1415, 8);\n\n    strictEqual(dv.getUint32(0, true), 0xf00);\n    strictEqual(dv.getUint32(4), 0xb47);\n    strictEqual(dv.getFloat64(8, true), 3.1415);\n\n    // Now test protecting users from doing stupid things\n\n    throws(\n      function () {\n        function AB() {}\n        Object.setPrototypeOf(AB, ArrayBuffer);\n        Object.setPrototypeOf(AB.prototype, ArrayBuffer.prototype);\n        Buffer.from(new AB());\n      },\n      {\n        name: 'TypeError',\n      }\n    );\n\n    // Test the byteOffset and length arguments\n    {\n      const ab = new Uint8Array(5);\n      ab[0] = 1;\n      ab[1] = 2;\n      ab[2] = 3;\n      ab[3] = 4;\n      ab[4] = 5;\n      const buf = Buffer.from(ab.buffer, 1, 3);\n      strictEqual(buf.length, 3);\n      strictEqual(buf[0], 2);\n      strictEqual(buf[1], 3);\n      strictEqual(buf[2], 4);\n      buf[0] = 9;\n      strictEqual(ab[1], 9);\n\n      throws(() => Buffer.from(ab.buffer, 6), {\n        code: 'ERR_BUFFER_OUT_OF_BOUNDS',\n        name: 'RangeError',\n        message: '\"offset\" is outside of buffer bounds',\n      });\n      throws(() => Buffer.from(ab.buffer, 3, 6), {\n        code: 'ERR_BUFFER_OUT_OF_BOUNDS',\n        name: 'RangeError',\n        message: '\"length\" is outside of buffer bounds',\n      });\n    }\n\n    // Test the deprecated Buffer() version also\n    {\n      const ab = new Uint8Array(5);\n      ab[0] = 1;\n      ab[1] = 2;\n      ab[2] = 3;\n      ab[3] = 4;\n      ab[4] = 5;\n      const buf = Buffer(ab.buffer, 1, 3);\n      strictEqual(buf.length, 3);\n      strictEqual(buf[0], 2);\n      strictEqual(buf[1], 3);\n      strictEqual(buf[2], 4);\n      buf[0] = 9;\n      strictEqual(ab[1], 9);\n\n      throws(() => Buffer(ab.buffer, 6), {\n        code: 'ERR_BUFFER_OUT_OF_BOUNDS',\n        name: 'RangeError',\n        message: '\"offset\" is outside of buffer bounds',\n      });\n      throws(() => Buffer(ab.buffer, 3, 6), {\n        code: 'ERR_BUFFER_OUT_OF_BOUNDS',\n        name: 'RangeError',\n        message: '\"length\" is outside of buffer bounds',\n      });\n    }\n\n    {\n      // If byteOffset is not numeric, it defaults to 0.\n      const ab = new ArrayBuffer(10);\n      const expected = Buffer.from(ab, 0);\n      deepStrictEqual(Buffer.from(ab, 'fhqwhgads'), expected);\n      deepStrictEqual(Buffer.from(ab, NaN), expected);\n      deepStrictEqual(Buffer.from(ab, {}), expected);\n      deepStrictEqual(Buffer.from(ab, []), expected);\n\n      // If byteOffset can be converted to a number, it will be.\n      deepStrictEqual(Buffer.from(ab, [1]), Buffer.from(ab, 1));\n\n      // If byteOffset is Infinity, throw.\n      throws(\n        () => {\n          Buffer.from(ab, Infinity);\n        },\n        {\n          code: 'ERR_BUFFER_OUT_OF_BOUNDS',\n          name: 'RangeError',\n          message: '\"offset\" is outside of buffer bounds',\n        }\n      );\n    }\n\n    {\n      // If length is not numeric, it defaults to 0.\n      const ab = new ArrayBuffer(10);\n      const expected = Buffer.from(ab, 0, 0);\n      deepStrictEqual(Buffer.from(ab, 0, 'fhqwhgads'), expected);\n      deepStrictEqual(Buffer.from(ab, 0, NaN), expected);\n      deepStrictEqual(Buffer.from(ab, 0, {}), expected);\n      deepStrictEqual(Buffer.from(ab, 0, []), expected);\n\n      // If length can be converted to a number, it will be.\n      deepStrictEqual(Buffer.from(ab, 0, [1]), Buffer.from(ab, 0, 1));\n\n      // If length is Infinity, throw.\n      throws(\n        () => {\n          Buffer.from(ab, 0, Infinity);\n        },\n        {\n          code: 'ERR_BUFFER_OUT_OF_BOUNDS',\n          name: 'RangeError',\n          message: '\"length\" is outside of buffer bounds',\n        }\n      );\n    }\n\n    // Test an array like entry with the length set to NaN.\n    deepStrictEqual(Buffer.from({ length: NaN }), Buffer.alloc(0));\n  },\n};\n\nexport const ascii = {\n  test(ctrl, env, ctx) {\n    // ASCII conversion in node.js simply masks off the high bits,\n    // it doesn't do transliteration.\n    strictEqual(Buffer.from('hérité').toString('ascii'), 'hC)ritC)');\n    // 71 characters, 78 bytes. The ’ character is a triple-byte sequence.\n    const input =\n      'C’est, graphiquement, la réunion d’un accent aigu ' +\n      'et d’un accent grave.';\n\n    const expected =\n      'Cb\\u0000\\u0019est, graphiquement, la rC)union ' +\n      'db\\u0000\\u0019un accent aigu et db\\u0000\\u0019un ' +\n      'accent grave.';\n\n    const buf = Buffer.from(input);\n\n    for (let i = 0; i < expected.length; ++i) {\n      strictEqual(buf.slice(i).toString('ascii'), expected.slice(i));\n\n      // Skip remainder of multi-byte sequence.\n      if (input.charCodeAt(i) > 65535) ++i;\n      if (input.charCodeAt(i) > 127) ++i;\n    }\n  },\n};\n\nexport const badHex = {\n  test(ctrl, env, ctx) {\n    // Test hex strings and bad hex strings\n    {\n      const buf = Buffer.alloc(4);\n      strictEqual(buf.length, 4);\n      deepStrictEqual(buf, Buffer.from([0, 0, 0, 0]));\n      strictEqual(buf.write('abcdxx', 0, 'hex'), 2);\n      deepStrictEqual(buf, Buffer.from([0xab, 0xcd, 0x00, 0x00]));\n      strictEqual(buf.toString('hex'), 'abcd0000');\n      strictEqual(buf.write('abcdef01', 0, 'hex'), 4);\n      deepStrictEqual(buf, Buffer.from([0xab, 0xcd, 0xef, 0x01]));\n      strictEqual(buf.toString('hex'), 'abcdef01');\n\n      const copy = Buffer.from(buf.toString('hex'), 'hex');\n      strictEqual(buf.toString('hex'), copy.toString('hex'));\n    }\n\n    {\n      const buf = Buffer.alloc(5);\n      strictEqual(buf.write('abcdxx', 1, 'hex'), 2);\n      strictEqual(buf.toString('hex'), '00abcd0000');\n    }\n\n    {\n      const buf = Buffer.alloc(4);\n      deepStrictEqual(buf, Buffer.from([0, 0, 0, 0]));\n      strictEqual(buf.write('xxabcd', 0, 'hex'), 0);\n      deepStrictEqual(buf, Buffer.from([0, 0, 0, 0]));\n      strictEqual(buf.write('xxab', 1, 'hex'), 0);\n      deepStrictEqual(buf, Buffer.from([0, 0, 0, 0]));\n      strictEqual(buf.write('cdxxab', 0, 'hex'), 1);\n      deepStrictEqual(buf, Buffer.from([0xcd, 0, 0, 0]));\n    }\n\n    {\n      const buf = Buffer.alloc(256);\n      for (let i = 0; i < 256; i++) buf[i] = i;\n\n      const hex = buf.toString('hex');\n      deepStrictEqual(Buffer.from(hex, 'hex'), buf);\n\n      const badHex = `${hex.slice(0, 256)}xx${hex.slice(256, 510)}`;\n      deepStrictEqual(Buffer.from(badHex, 'hex'), buf.slice(0, 128));\n    }\n  },\n};\n\nexport const bigint64 = {\n  test(ctrl, env, ctx) {\n    const buf = Buffer.allocUnsafe(8);\n\n    ['LE', 'BE'].forEach(function (endianness) {\n      // Should allow simple BigInts to be written and read\n      let val = 123456789n;\n      buf[`writeBigInt64${endianness}`](val, 0);\n      let rtn = buf[`readBigInt64${endianness}`](0);\n      strictEqual(val, rtn);\n\n      // Should allow INT64_MAX to be written and read\n      val = 0x7fffffffffffffffn;\n      buf[`writeBigInt64${endianness}`](val, 0);\n      rtn = buf[`readBigInt64${endianness}`](0);\n      strictEqual(val, rtn);\n\n      // Should read and write a negative signed 64-bit integer\n      val = -123456789n;\n      buf[`writeBigInt64${endianness}`](val, 0);\n      strictEqual(val, buf[`readBigInt64${endianness}`](0));\n\n      // Should read and write an unsigned 64-bit integer\n      val = 123456789n;\n      buf[`writeBigUInt64${endianness}`](val, 0);\n      strictEqual(val, buf[`readBigUInt64${endianness}`](0));\n\n      // Should throw a RangeError upon INT64_MAX+1 being written\n      throws(function () {\n        const val = 0x8000000000000000n;\n        buf[`writeBigInt64${endianness}`](val, 0);\n      }, RangeError);\n\n      // Should throw a RangeError upon UINT64_MAX+1 being written\n      throws(\n        function () {\n          const val = 0x10000000000000000n;\n          buf[`writeBigUInt64${endianness}`](val, 0);\n        },\n        {\n          code: 'ERR_OUT_OF_RANGE',\n          message:\n            'The value of \"value\" is out of range. It must be ' +\n            '>= 0n and < 2n ** 64n. Received 18_446_744_073_709_551_616n',\n        }\n      );\n\n      // Should throw a TypeError upon invalid input\n      throws(function () {\n        buf[`writeBigInt64${endianness}`]('bad', 0);\n      }, TypeError);\n\n      // Should throw a TypeError upon invalid input\n      throws(function () {\n        buf[`writeBigUInt64${endianness}`]('bad', 0);\n      }, TypeError);\n    });\n  },\n};\n\nexport const byteLength = {\n  test(ctrl, env, ctx) {\n    [[32, 'latin1'], [NaN, 'utf8'], [{}, 'latin1'], []].forEach((args) => {\n      throws(() => Buffer.byteLength(...args), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n      });\n    });\n\n    ok(ArrayBuffer.isView(new Buffer(10)));\n    ok(ArrayBuffer.isView(new SlowBuffer(10)));\n    ok(ArrayBuffer.isView(Buffer.alloc(10)));\n    ok(ArrayBuffer.isView(Buffer.allocUnsafe(10)));\n    ok(ArrayBuffer.isView(Buffer.allocUnsafeSlow(10)));\n    ok(ArrayBuffer.isView(Buffer.from('')));\n\n    // buffer\n    const incomplete = Buffer.from([0xe4, 0xb8, 0xad, 0xe6, 0x96]);\n    strictEqual(Buffer.byteLength(incomplete), 5);\n    const ascii = Buffer.from('abc');\n    strictEqual(Buffer.byteLength(ascii), 3);\n\n    // ArrayBuffer\n    const buffer = new ArrayBuffer(8);\n    strictEqual(Buffer.byteLength(buffer), 8);\n\n    // TypedArray\n    const int8 = new Int8Array(8);\n    strictEqual(Buffer.byteLength(int8), 8);\n    const uint8 = new Uint8Array(8);\n    strictEqual(Buffer.byteLength(uint8), 8);\n    const uintc8 = new Uint8ClampedArray(2);\n    strictEqual(Buffer.byteLength(uintc8), 2);\n    const int16 = new Int16Array(8);\n    strictEqual(Buffer.byteLength(int16), 16);\n    const uint16 = new Uint16Array(8);\n    strictEqual(Buffer.byteLength(uint16), 16);\n    const int32 = new Int32Array(8);\n    strictEqual(Buffer.byteLength(int32), 32);\n    const uint32 = new Uint32Array(8);\n    strictEqual(Buffer.byteLength(uint32), 32);\n    const float16 = new Float16Array(8);\n    strictEqual(Buffer.byteLength(float16), 16);\n    const float32 = new Float32Array(8);\n    strictEqual(Buffer.byteLength(float32), 32);\n    const float64 = new Float64Array(8);\n    strictEqual(Buffer.byteLength(float64), 64);\n\n    // DataView\n    const dv = new DataView(new ArrayBuffer(2));\n    strictEqual(Buffer.byteLength(dv), 2);\n\n    // Special case: zero length string\n    strictEqual(Buffer.byteLength('', 'ascii'), 0);\n    strictEqual(Buffer.byteLength('', 'HeX'), 0);\n\n    // utf8\n    strictEqual(Buffer.byteLength('∑éllö wørl∂!', 'utf-8'), 19);\n    strictEqual(Buffer.byteLength('κλμνξο', 'utf8'), 12);\n    strictEqual(Buffer.byteLength('挵挶挷挸挹', 'utf-8'), 15);\n    strictEqual(Buffer.byteLength('𠝹𠱓𠱸', 'UTF8'), 12);\n    // Without an encoding, utf8 should be assumed\n    strictEqual(Buffer.byteLength('hey there'), 9);\n    strictEqual(Buffer.byteLength('𠱸挶νξ#xx :)'), 17);\n    strictEqual(Buffer.byteLength('hello world', ''), 11);\n    // It should also be assumed with unrecognized encoding\n\n    strictEqual(Buffer.byteLength('hello world', 'abc'), 11);\n    strictEqual(Buffer.byteLength('ßœ∑≈', 'unkn0wn enc0ding'), 10);\n\n    // base64\n    strictEqual(Buffer.byteLength('aGVsbG8gd29ybGQ=', 'base64'), 11);\n    strictEqual(Buffer.byteLength('aGVsbG8gd29ybGQ=', 'BASE64'), 11);\n    strictEqual(Buffer.byteLength('bm9kZS5qcyByb2NrcyE=', 'base64'), 14);\n    strictEqual(Buffer.byteLength('aGkk', 'base64'), 3);\n    strictEqual(\n      Buffer.byteLength('bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw==', 'base64'),\n      25\n    );\n    // base64url\n    strictEqual(Buffer.byteLength('aGVsbG8gd29ybGQ', 'base64url'), 11);\n    strictEqual(Buffer.byteLength('aGVsbG8gd29ybGQ', 'BASE64URL'), 11);\n    strictEqual(Buffer.byteLength('bm9kZS5qcyByb2NrcyE', 'base64url'), 14);\n    strictEqual(Buffer.byteLength('aGkk', 'base64url'), 3);\n    strictEqual(\n      Buffer.byteLength('bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw', 'base64url'),\n      25\n    );\n    // special padding\n    strictEqual(Buffer.byteLength('aaa=', 'base64'), 2);\n    strictEqual(Buffer.byteLength('aaaa==', 'base64'), 3);\n    strictEqual(Buffer.byteLength('aaa=', 'base64url'), 2);\n    strictEqual(Buffer.byteLength('aaaa==', 'base64url'), 3);\n\n    strictEqual(Buffer.byteLength('Il était tué'), 14);\n    strictEqual(Buffer.byteLength('Il était tué', 'utf8'), 14);\n\n    ['ascii', 'latin1', 'binary']\n      .reduce((es, e) => es.concat(e, e.toUpperCase()), [])\n      .forEach((encoding) => {\n        strictEqual(Buffer.byteLength('Il était tué', encoding), 12);\n      });\n\n    ['ucs2', 'ucs-2', 'utf16le', 'utf-16le']\n      .reduce((es, e) => es.concat(e, e.toUpperCase()), [])\n      .forEach((encoding) => {\n        strictEqual(Buffer.byteLength('Il était tué', encoding), 24);\n      });\n\n    // Verify that invalid encodings are treated as utf8\n    for (let i = 1; i < 10; i++) {\n      const encoding = String(i).repeat(i);\n\n      ok(!Buffer.isEncoding(encoding));\n      strictEqual(\n        Buffer.byteLength('foo', encoding),\n        Buffer.byteLength('foo', 'utf8')\n      );\n    }\n  },\n};\n\nexport const compareOffset = {\n  test(ctrl, env, ctx) {\n    const a = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]);\n    const b = Buffer.from([5, 6, 7, 8, 9, 0, 1, 2, 3, 4]);\n\n    strictEqual(a.compare(b), -1);\n\n    // Equivalent to a.compare(b).\n    strictEqual(a.compare(b, 0), -1);\n    throws(() => a.compare(b, '0'), { code: 'ERR_INVALID_ARG_TYPE' });\n    strictEqual(a.compare(b, undefined), -1);\n\n    // Equivalent to a.compare(b).\n    strictEqual(a.compare(b, 0, undefined, 0), -1);\n\n    // Zero-length target, return 1\n    strictEqual(a.compare(b, 0, 0, 0), 1);\n    throws(() => a.compare(b, 0, '0', '0'), { code: 'ERR_INVALID_ARG_TYPE' });\n\n    // Equivalent to Buffer.compare(a, b.slice(6, 10))\n    strictEqual(a.compare(b, 6, 10), 1);\n\n    // Zero-length source, return -1\n    strictEqual(a.compare(b, 6, 10, 0, 0), -1);\n\n    // Zero-length source and target, return 0\n    strictEqual(a.compare(b, 0, 0, 0, 0), 0);\n    strictEqual(a.compare(b, 1, 1, 2, 2), 0);\n\n    // Equivalent to Buffer.compare(a.slice(4), b.slice(0, 5))\n    strictEqual(a.compare(b, 0, 5, 4), 1);\n\n    // Equivalent to Buffer.compare(a.slice(1), b.slice(5))\n    strictEqual(a.compare(b, 5, undefined, 1), 1);\n\n    // Equivalent to Buffer.compare(a.slice(2), b.slice(2, 4))\n    strictEqual(a.compare(b, 2, 4, 2), -1);\n\n    // Equivalent to Buffer.compare(a.slice(4), b.slice(0, 7))\n    strictEqual(a.compare(b, 0, 7, 4), -1);\n\n    // Equivalent to Buffer.compare(a.slice(4, 6), b.slice(0, 7));\n    strictEqual(a.compare(b, 0, 7, 4, 6), -1);\n\n    // Null is ambiguous.\n    throws(() => a.compare(b, 0, null), { code: 'ERR_INVALID_ARG_TYPE' });\n\n    // Values do not get coerced.\n    throws(() => a.compare(b, 0, { valueOf: () => 5 }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    // Infinity should not be coerced.\n    throws(() => a.compare(b, Infinity, -Infinity), {\n      code: 'ERR_OUT_OF_RANGE',\n    });\n\n    // Zero length target because default for targetEnd <= targetSource\n    strictEqual(a.compare(b, 0xff), 1);\n\n    throws(() => a.compare(b, '0xff'), { code: 'ERR_INVALID_ARG_TYPE' });\n    throws(() => a.compare(b, 0, '0xff'), { code: 'ERR_INVALID_ARG_TYPE' });\n\n    const oor = { code: 'ERR_OUT_OF_RANGE' };\n\n    throws(() => a.compare(b, 0, 100, 0), oor);\n    throws(() => a.compare(b, 0, 1, 0, 100), oor);\n    throws(() => a.compare(b, -1), oor);\n    throws(() => a.compare(b, 0, Infinity), oor);\n    throws(() => a.compare(b, 0, 1, -1), oor);\n    throws(() => a.compare(b, -Infinity, Infinity), oor);\n    throws(() => a.compare(), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n      message:\n        'The \"target\" argument must be an instance of ' +\n        'Buffer or Uint8Array. Received undefined',\n    });\n  },\n};\n\nexport const compare = {\n  test(ctrl, env, ctx) {\n    const b = Buffer.alloc(1, 'a');\n    const c = Buffer.alloc(1, 'c');\n    const d = Buffer.alloc(2, 'aa');\n    const e = new Uint8Array([0x61, 0x61]); // ASCII 'aa', same as d\n\n    strictEqual(b.compare(c), -1);\n    strictEqual(c.compare(d), 1);\n    strictEqual(d.compare(b), 1);\n    strictEqual(d.compare(e), 0);\n    strictEqual(b.compare(d), -1);\n    strictEqual(b.compare(b), 0);\n\n    strictEqual(Buffer.compare(b, c), -1);\n    strictEqual(Buffer.compare(c, d), 1);\n    strictEqual(Buffer.compare(d, b), 1);\n    strictEqual(Buffer.compare(b, d), -1);\n    strictEqual(Buffer.compare(c, c), 0);\n    strictEqual(Buffer.compare(e, e), 0);\n    strictEqual(Buffer.compare(d, e), 0);\n    strictEqual(Buffer.compare(d, b), 1);\n\n    strictEqual(Buffer.compare(Buffer.alloc(0), Buffer.alloc(0)), 0);\n    strictEqual(Buffer.compare(Buffer.alloc(0), Buffer.alloc(1)), -1);\n    strictEqual(Buffer.compare(Buffer.alloc(1), Buffer.alloc(0)), 1);\n\n    throws(() => Buffer.compare(Buffer.alloc(1), 'abc'), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => Buffer.compare('abc', Buffer.alloc(1)), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    throws(() => Buffer.alloc(1).compare('abc'), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n    });\n  },\n};\n\nexport const concat = {\n  test(ctrl, env, ctx) {\n    const zero = [];\n    const one = [Buffer.from('asdf')];\n    const long = [];\n    for (let i = 0; i < 10; i++) long.push(Buffer.from('asdf'));\n\n    const flatZero = Buffer.concat(zero);\n    const flatOne = Buffer.concat(one);\n    const flatLong = Buffer.concat(long);\n    const flatLongLen = Buffer.concat(long, 40);\n\n    strictEqual(flatZero.length, 0);\n    strictEqual(flatOne.toString(), 'asdf');\n\n    const check = 'asdf'.repeat(10);\n\n    // A special case where concat used to return the first item,\n    // if the length is one. This check is to make sure that we don't do that.\n    notStrictEqual(flatOne, one[0]);\n    strictEqual(flatLong.toString(), check);\n    strictEqual(flatLongLen.toString(), check);\n\n    [undefined, null, Buffer.from('hello')].forEach((value) => {\n      throws(\n        () => {\n          Buffer.concat(value);\n        },\n        {\n          name: 'TypeError',\n        }\n      );\n    });\n\n    [[42], ['hello', Buffer.from('world')]].forEach((value) => {\n      throws(\n        () => {\n          Buffer.concat(value);\n        },\n        {\n          name: 'TypeError',\n          //code: 'ERR_INVALID_ARG_TYPE',\n        }\n      );\n    });\n\n    throws(\n      () => {\n        Buffer.concat([Buffer.from('hello'), 3]);\n      },\n      {\n        name: 'TypeError',\n        //code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // eslint-disable-next-line node-core/crypto-check\n    const random10 = Buffer.alloc(10);\n    crypto.getRandomValues(random10);\n    const empty = Buffer.alloc(0);\n\n    notDeepStrictEqual(random10, empty);\n    notDeepStrictEqual(random10, Buffer.alloc(10));\n\n    deepStrictEqual(Buffer.concat([], 100), empty);\n    deepStrictEqual(Buffer.concat([random10], 0), empty);\n    deepStrictEqual(Buffer.concat([random10], 10), random10);\n    deepStrictEqual(Buffer.concat([random10, random10], 10), random10);\n    deepStrictEqual(Buffer.concat([empty, random10]), random10);\n    deepStrictEqual(Buffer.concat([random10, empty, empty]), random10);\n\n    // The tail should be zero-filled\n    deepStrictEqual(Buffer.concat([empty], 100), Buffer.alloc(100));\n    deepStrictEqual(Buffer.concat([empty], 4096), Buffer.alloc(4096));\n    deepStrictEqual(\n      Buffer.concat([random10], 40),\n      Buffer.concat([random10, Buffer.alloc(30)])\n    );\n\n    deepStrictEqual(\n      Buffer.concat([\n        new Uint8Array([0x41, 0x42]),\n        new Uint8Array([0x43, 0x44]),\n      ]),\n      Buffer.from('ABCD')\n    );\n  },\n};\n\nexport const konstants = {\n  test(ctrl, env, ctx) {\n    strictEqual(typeof MAX_LENGTH, 'number');\n    strictEqual(typeof MAX_STRING_LENGTH, 'number');\n    ok(MAX_STRING_LENGTH <= MAX_LENGTH);\n    throws(\n      () => ' '.repeat(MAX_STRING_LENGTH + 1),\n      /^RangeError: Invalid string length$/\n    );\n    ' '.repeat(MAX_STRING_LENGTH); // Should not throw.\n    // Legacy values match:\n    strictEqual(kMaxLength, MAX_LENGTH);\n    strictEqual(kStringMaxLength, MAX_STRING_LENGTH);\n  },\n};\n\nexport const copy = {\n  test(ctrl, env, ctx) {\n    const b = Buffer.allocUnsafe(1024);\n    const c = Buffer.allocUnsafe(512);\n\n    let cntr = 0;\n\n    {\n      // copy 512 bytes, from 0 to 512.\n      b.fill(++cntr);\n      c.fill(++cntr);\n      const copied = b.copy(c, 0, 0, 512);\n      strictEqual(copied, 512);\n      for (let i = 0; i < c.length; i++) {\n        strictEqual(c[i], b[i]);\n      }\n    }\n\n    {\n      // Current behavior is to coerce values to integers.\n      b.fill(++cntr);\n      c.fill(++cntr);\n      const copied = b.copy(c, '0', '0', '512');\n      strictEqual(copied, 512);\n      for (let i = 0; i < c.length; i++) {\n        strictEqual(c[i], b[i]);\n      }\n    }\n\n    {\n      // Floats will be converted to integers via `Math.floor`\n      b.fill(++cntr);\n      c.fill(++cntr);\n      const copied = b.copy(c, 0, 0, 512.5);\n      strictEqual(copied, 512);\n      for (let i = 0; i < c.length; i++) {\n        strictEqual(c[i], b[i]);\n      }\n    }\n\n    {\n      // Copy c into b, without specifying sourceEnd\n      b.fill(++cntr);\n      c.fill(++cntr);\n      const copied = c.copy(b, 0, 0);\n      strictEqual(copied, c.length);\n      for (let i = 0; i < c.length; i++) {\n        strictEqual(b[i], c[i]);\n      }\n    }\n\n    {\n      // Copy c into b, without specifying sourceStart\n      b.fill(++cntr);\n      c.fill(++cntr);\n      const copied = c.copy(b, 0);\n      strictEqual(copied, c.length);\n      for (let i = 0; i < c.length; i++) {\n        strictEqual(b[i], c[i]);\n      }\n    }\n\n    {\n      // Copied source range greater than source length\n      b.fill(++cntr);\n      c.fill(++cntr);\n      const copied = c.copy(b, 0, 0, c.length + 1);\n      strictEqual(copied, c.length);\n      for (let i = 0; i < c.length; i++) {\n        strictEqual(b[i], c[i]);\n      }\n    }\n\n    {\n      // Copy longer buffer b to shorter c without targetStart\n      b.fill(++cntr);\n      c.fill(++cntr);\n      const copied = b.copy(c);\n      strictEqual(copied, c.length);\n      for (let i = 0; i < c.length; i++) {\n        strictEqual(c[i], b[i]);\n      }\n    }\n\n    {\n      // Copy starting near end of b to c\n      b.fill(++cntr);\n      c.fill(++cntr);\n      const copied = b.copy(c, 0, b.length - Math.floor(c.length / 2));\n      strictEqual(copied, Math.floor(c.length / 2));\n      for (let i = 0; i < Math.floor(c.length / 2); i++) {\n        strictEqual(c[i], b[b.length - Math.floor(c.length / 2) + i]);\n      }\n      for (let i = Math.floor(c.length / 2) + 1; i < c.length; i++) {\n        strictEqual(c[c.length - 1], c[i]);\n      }\n    }\n\n    {\n      // Try to copy 513 bytes, and check we don't overrun c\n      b.fill(++cntr);\n      c.fill(++cntr);\n      const copied = b.copy(c, 0, 0, 513);\n      strictEqual(copied, c.length);\n      for (let i = 0; i < c.length; i++) {\n        strictEqual(c[i], b[i]);\n      }\n    }\n\n    {\n      // copy 768 bytes from b into b\n      b.fill(++cntr);\n      b.fill(++cntr, 256);\n      const copied = b.copy(b, 0, 256, 1024);\n      strictEqual(copied, 768);\n      for (let i = 0; i < b.length; i++) {\n        strictEqual(b[i], cntr);\n      }\n    }\n\n    // Copy string longer than buffer length (failure will segfault)\n    const bb = Buffer.allocUnsafe(10);\n    bb.fill('hello crazy world');\n\n    // Try to copy from before the beginning of b. Should not throw.\n    b.copy(c, 0, 100, 10);\n\n    // Throw with invalid source type\n    throws(() => Buffer.prototype.copy.call(0), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n    });\n\n    // Copy throws at negative targetStart\n    throws(() => Buffer.allocUnsafe(5).copy(Buffer.allocUnsafe(5), -1, 0), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n      message:\n        'The value of \"targetStart\" is out of range. ' +\n        'It must be >= 0. Received -1',\n    });\n\n    // Copy throws at negative sourceStart\n    throws(() => Buffer.allocUnsafe(5).copy(Buffer.allocUnsafe(5), 0, -1), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n      message:\n        'The value of \"sourceStart\" is out of range. ' +\n        'It must be >= 0. Received -1',\n    });\n\n    {\n      // Check sourceEnd resets to targetEnd if former is greater than the latter\n      b.fill(++cntr);\n      c.fill(++cntr);\n      b.copy(c, 0, 0, 1025);\n      for (let i = 0; i < c.length; i++) {\n        strictEqual(c[i], b[i]);\n      }\n    }\n\n    // Throw with negative sourceEnd\n    throws(() => b.copy(c, 0, 0, -1), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n      message:\n        'The value of \"sourceEnd\" is out of range. ' +\n        'It must be >= 0. Received -1',\n    });\n\n    // When sourceStart is greater than sourceEnd, zero copied\n    strictEqual(b.copy(c, 0, 100, 10), 0);\n\n    // When targetStart > targetLength, zero copied\n    strictEqual(b.copy(c, 512, 0, 10), 0);\n\n    // Test that the `target` can be a Uint8Array.\n    {\n      const d = new Uint8Array(c);\n      // copy 512 bytes, from 0 to 512.\n      b.fill(++cntr);\n      d.fill(++cntr);\n      const copied = b.copy(d, 0, 0, 512);\n      strictEqual(copied, 512);\n      for (let i = 0; i < d.length; i++) {\n        strictEqual(d[i], b[i]);\n      }\n    }\n\n    // Test that the source can be a Uint8Array, too.\n    {\n      const e = new Uint8Array(b);\n      // copy 512 bytes, from 0 to 512.\n      e.fill(++cntr);\n      c.fill(++cntr);\n      const copied = Buffer.prototype.copy.call(e, c, 0, 0, 512);\n      strictEqual(copied, 512);\n      for (let i = 0; i < c.length; i++) {\n        strictEqual(c[i], e[i]);\n      }\n    }\n\n    // https://github.com/nodejs/node/issues/23668: Do not crash for invalid input.\n    c.fill('c');\n    b.copy(c, 'not a valid offset');\n    // Make sure this acted like a regular copy with `0` offset.\n    deepStrictEqual(c, b.slice(0, c.length));\n\n    {\n      c.fill('C');\n      throws(() => {\n        b.copy(c, {\n          [Symbol.toPrimitive]() {\n            throw new Error('foo');\n          },\n        });\n      }, /foo/);\n      // No copying took place:\n      deepStrictEqual(c.toString(), 'C'.repeat(c.length));\n    }\n  },\n};\n\nexport const equals = {\n  test(ctrl, env, ctx) {\n    const b = Buffer.from('abcdf');\n    const c = Buffer.from('abcdf');\n    const d = Buffer.from('abcde');\n    const e = Buffer.from('abcdef');\n\n    ok(b.equals(c));\n    ok(!c.equals(d));\n    ok(!d.equals(e));\n    ok(d.equals(d));\n    ok(d.equals(new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65])));\n\n    throws(() => Buffer.alloc(1).equals('abc'), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n    });\n  },\n};\n\nexport const failedAllocTypedArrays = {\n  test(ctrl, env, ctx) {\n    // Test failed or zero-sized Buffer allocations not affecting typed arrays.\n    // This test exists because of a regression that occurred. Because Buffer\n    // instances are allocated with the same underlying allocator as TypedArrays,\n    // but Buffer's can optional be non-zero filled, there was a regression that\n    // occurred when a Buffer allocated failed, the internal flag specifying\n    // whether or not to zero-fill was not being reset, causing TypedArrays to\n    // allocate incorrectly.\n    const zeroArray = new Uint32Array(10).fill(0);\n    const sizes = [1e10, 0, 0.1, -1, 'a', undefined, null, NaN];\n    const allocators = [\n      Buffer,\n      SlowBuffer,\n      Buffer.alloc,\n      Buffer.allocUnsafe,\n      Buffer.allocUnsafeSlow,\n    ];\n    for (const allocator of allocators) {\n      for (const size of sizes) {\n        try {\n          // Some of these allocations are known to fail. If they do,\n          // Uint32Array should still produce a zeroed out result.\n          allocator(size);\n        } catch {\n          deepStrictEqual(zeroArray, new Uint32Array(10));\n        }\n      }\n    }\n  },\n};\n\nexport const fakes = {\n  test(ctrl, env, ctx) {\n    function FakeBuffer() {}\n    Object.setPrototypeOf(FakeBuffer, Buffer);\n    Object.setPrototypeOf(FakeBuffer.prototype, Buffer.prototype);\n\n    const fb = new FakeBuffer();\n\n    throws(function () {\n      Buffer.from(fb);\n    }, TypeError);\n\n    throws(function () {\n      +Buffer.prototype; // eslint-disable-line no-unused-expressions\n    }, TypeError);\n\n    throws(function () {\n      Buffer.compare(fb, Buffer.alloc(0));\n    }, TypeError);\n\n    throws(function () {\n      fb.write('foo');\n    }, TypeError);\n\n    throws(function () {\n      Buffer.concat([fb, fb]);\n    }, TypeError);\n\n    throws(function () {\n      fb.toString();\n    }, TypeError);\n\n    throws(function () {\n      fb.equals(Buffer.alloc(0));\n    }, TypeError);\n\n    throws(function () {\n      fb.indexOf(5);\n    }, TypeError);\n\n    throws(function () {\n      fb.readFloatLE(0);\n    }, TypeError);\n\n    throws(function () {\n      fb.writeFloatLE(0);\n    }, TypeError);\n\n    throws(function () {\n      fb.fill(0);\n    }, TypeError);\n  },\n};\n\nexport const fill = {\n  test(ctrl, env, ctx) {\n    const SIZE = 28;\n    const buf1 = Buffer.allocUnsafe(SIZE);\n    const buf2 = Buffer.allocUnsafe(SIZE);\n\n    // Default encoding\n    testBufs('abc');\n    testBufs('\\u0222aa');\n    testBufs('a\\u0234b\\u0235c\\u0236');\n    testBufs('abc', 4);\n    testBufs('abc', 5);\n    testBufs('abc', SIZE);\n    testBufs('\\u0222aa', 2);\n    testBufs('\\u0222aa', 8);\n    testBufs('a\\u0234b\\u0235c\\u0236', 4);\n    testBufs('a\\u0234b\\u0235c\\u0236', 12);\n    testBufs('abc', 4, 1);\n    testBufs('abc', 5, 1);\n    testBufs('\\u0222aa', 8, 1);\n    testBufs('a\\u0234b\\u0235c\\u0236', 4, 1);\n    testBufs('a\\u0234b\\u0235c\\u0236', 12, 1);\n\n    // UTF8\n    testBufs('abc', 'utf8');\n    testBufs('\\u0222aa', 'utf8');\n    testBufs('a\\u0234b\\u0235c\\u0236', 'utf8');\n    testBufs('abc', 4, 'utf8');\n    testBufs('abc', 5, 'utf8');\n    testBufs('abc', SIZE, 'utf8');\n    testBufs('\\u0222aa', 2, 'utf8');\n    testBufs('\\u0222aa', 8, 'utf8');\n    testBufs('a\\u0234b\\u0235c\\u0236', 4, 'utf8');\n    testBufs('a\\u0234b\\u0235c\\u0236', 12, 'utf8');\n    testBufs('abc', 4, 1, 'utf8');\n    testBufs('abc', 5, 1, 'utf8');\n    testBufs('\\u0222aa', 8, 1, 'utf8');\n    testBufs('a\\u0234b\\u0235c\\u0236', 4, 1, 'utf8');\n    testBufs('a\\u0234b\\u0235c\\u0236', 12, 1, 'utf8');\n    strictEqual(Buffer.allocUnsafe(1).fill(0).fill('\\u0222')[0], 0xc8);\n\n    // BINARY\n    testBufs('abc', 'binary');\n    testBufs('\\u0222aa', 'binary');\n    testBufs('a\\u0234b\\u0235c\\u0236', 'binary');\n    testBufs('abc', 4, 'binary');\n    testBufs('abc', 5, 'binary');\n    testBufs('abc', SIZE, 'binary');\n    testBufs('\\u0222aa', 2, 'binary');\n    testBufs('\\u0222aa', 8, 'binary');\n    testBufs('a\\u0234b\\u0235c\\u0236', 4, 'binary');\n    testBufs('a\\u0234b\\u0235c\\u0236', 12, 'binary');\n    testBufs('abc', 4, 1, 'binary');\n    testBufs('abc', 5, 1, 'binary');\n    testBufs('\\u0222aa', 8, 1, 'binary');\n    testBufs('a\\u0234b\\u0235c\\u0236', 4, 1, 'binary');\n    testBufs('a\\u0234b\\u0235c\\u0236', 12, 1, 'binary');\n\n    // LATIN1\n    testBufs('abc', 'latin1');\n    testBufs('\\u0222aa', 'latin1');\n    testBufs('a\\u0234b\\u0235c\\u0236', 'latin1');\n    testBufs('abc', 4, 'latin1');\n    testBufs('abc', 5, 'latin1');\n    testBufs('abc', SIZE, 'latin1');\n    testBufs('\\u0222aa', 2, 'latin1');\n    testBufs('\\u0222aa', 8, 'latin1');\n    testBufs('a\\u0234b\\u0235c\\u0236', 4, 'latin1');\n    testBufs('a\\u0234b\\u0235c\\u0236', 12, 'latin1');\n    testBufs('abc', 4, 1, 'latin1');\n    testBufs('abc', 5, 1, 'latin1');\n    testBufs('\\u0222aa', 8, 1, 'latin1');\n    testBufs('a\\u0234b\\u0235c\\u0236', 4, 1, 'latin1');\n    testBufs('a\\u0234b\\u0235c\\u0236', 12, 1, 'latin1');\n\n    // UCS2\n    testBufs('abc', 'ucs2');\n    testBufs('\\u0222aa', 'ucs2');\n    testBufs('a\\u0234b\\u0235c\\u0236', 'ucs2');\n    testBufs('abc', 4, 'ucs2');\n    testBufs('abc', SIZE, 'ucs2');\n    testBufs('\\u0222aa', 2, 'ucs2');\n    testBufs('\\u0222aa', 8, 'ucs2');\n    testBufs('a\\u0234b\\u0235c\\u0236', 4, 'ucs2');\n    testBufs('a\\u0234b\\u0235c\\u0236', 12, 'ucs2');\n    testBufs('abc', 4, 1, 'ucs2');\n    testBufs('abc', 5, 1, 'ucs2');\n    testBufs('\\u0222aa', 8, 1, 'ucs2');\n    testBufs('a\\u0234b\\u0235c\\u0236', 4, 1, 'ucs2');\n    testBufs('a\\u0234b\\u0235c\\u0236', 12, 1, 'ucs2');\n    strictEqual(Buffer.allocUnsafe(1).fill('\\u0222', 'ucs2')[0], 0x22);\n\n    // HEX\n    testBufs('616263', 'hex');\n    testBufs('c8a26161', 'hex');\n    testBufs('61c8b462c8b563c8b6', 'hex');\n    testBufs('616263', 4, 'hex');\n    testBufs('616263', 5, 'hex');\n    testBufs('616263', SIZE, 'hex');\n    testBufs('c8a26161', 2, 'hex');\n    testBufs('c8a26161', 8, 'hex');\n    testBufs('61c8b462c8b563c8b6', 4, 'hex');\n    testBufs('61c8b462c8b563c8b6', 12, 'hex');\n    testBufs('616263', 4, 1, 'hex');\n    testBufs('616263', 5, 1, 'hex');\n    testBufs('c8a26161', 8, 1, 'hex');\n    testBufs('61c8b462c8b563c8b6', 4, 1, 'hex');\n    testBufs('61c8b462c8b563c8b6', 12, 1, 'hex');\n\n    throws(\n      () => {\n        const buf = Buffer.allocUnsafe(SIZE);\n\n        buf.fill('yKJh', 'hex');\n      },\n      {\n        name: 'TypeError',\n      }\n    );\n\n    throws(\n      () => {\n        const buf = Buffer.allocUnsafe(SIZE);\n\n        buf.fill('\\u0222', 'hex');\n      },\n      {\n        name: 'TypeError',\n      }\n    );\n\n    // BASE64\n    testBufs('YWJj', 'base64');\n    testBufs('yKJhYQ==', 'base64');\n    testBufs('Yci0Ysi1Y8i2', 'base64');\n    testBufs('YWJj', 4, 'base64');\n    testBufs('YWJj', SIZE, 'base64');\n    testBufs('yKJhYQ==', 2, 'base64');\n    testBufs('yKJhYQ==', 8, 'base64');\n    testBufs('Yci0Ysi1Y8i2', 4, 'base64');\n    testBufs('Yci0Ysi1Y8i2', 12, 'base64');\n    testBufs('YWJj', 4, 1, 'base64');\n    testBufs('YWJj', 5, 1, 'base64');\n    testBufs('yKJhYQ==', 8, 1, 'base64');\n    testBufs('Yci0Ysi1Y8i2', 4, 1, 'base64');\n    testBufs('Yci0Ysi1Y8i2', 12, 1, 'base64');\n\n    // BASE64URL\n    testBufs('YWJj', 'base64url');\n    testBufs('yKJhYQ', 'base64url');\n    testBufs('Yci0Ysi1Y8i2', 'base64url');\n    testBufs('YWJj', 4, 'base64url');\n    testBufs('YWJj', SIZE, 'base64url');\n    testBufs('yKJhYQ', 2, 'base64url');\n    testBufs('yKJhYQ', 8, 'base64url');\n    testBufs('Yci0Ysi1Y8i2', 4, 'base64url');\n    testBufs('Yci0Ysi1Y8i2', 12, 'base64url');\n    testBufs('YWJj', 4, 1, 'base64url');\n    testBufs('YWJj', 5, 1, 'base64url');\n    testBufs('yKJhYQ', 8, 1, 'base64url');\n    testBufs('Yci0Ysi1Y8i2', 4, 1, 'base64url');\n    testBufs('Yci0Ysi1Y8i2', 12, 1, 'base64url');\n\n    function deepStrictEqualValues(buf, arr) {\n      for (const [index, value] of buf.entries()) {\n        deepStrictEqual(value, arr[index]);\n      }\n    }\n\n    const buf2Fill = Buffer.allocUnsafe(1).fill(2);\n    deepStrictEqualValues(genBuffer(4, [buf2Fill]), [2, 2, 2, 2]);\n    deepStrictEqualValues(genBuffer(4, [buf2Fill, 1]), [0, 2, 2, 2]);\n    deepStrictEqualValues(genBuffer(4, [buf2Fill, 1, 3]), [0, 2, 2, 0]);\n    deepStrictEqualValues(genBuffer(4, [buf2Fill, 1, 1]), [0, 0, 0, 0]);\n    const hexBufFill = Buffer.allocUnsafe(2).fill(0).fill('0102', 'hex');\n    deepStrictEqualValues(genBuffer(4, [hexBufFill]), [1, 2, 1, 2]);\n    deepStrictEqualValues(genBuffer(4, [hexBufFill, 1]), [0, 1, 2, 1]);\n    deepStrictEqualValues(genBuffer(4, [hexBufFill, 1, 3]), [0, 1, 2, 0]);\n    deepStrictEqualValues(genBuffer(4, [hexBufFill, 1, 1]), [0, 0, 0, 0]);\n\n    // Check exceptions\n    [\n      [0, -1],\n      [0, 0, buf1.length + 1],\n      ['', -1],\n      ['', 0, buf1.length + 1],\n      ['', 1, -1],\n    ].forEach((args) => {\n      throws(() => buf1.fill(...args), { name: 'RangeError' });\n    });\n\n    throws(() => buf1.fill('a', 0, buf1.length, 'node rocks!'), {\n      code: 'ERR_UNKNOWN_ENCODING',\n      name: 'TypeError',\n      message: 'Unknown encoding: node rocks!',\n    });\n\n    [\n      ['a', 0, 0, NaN],\n      ['a', 0, 0, false],\n    ].forEach((args) => {\n      throws(() => buf1.fill(...args), {\n        name: 'TypeError',\n      });\n    });\n\n    throws(() => buf1.fill('a', 0, 0, 'foo'), {\n      code: 'ERR_UNKNOWN_ENCODING',\n      name: 'TypeError',\n      message: 'Unknown encoding: foo',\n    });\n\n    function genBuffer(size, args) {\n      const b = Buffer.allocUnsafe(size);\n      return b.fill(0).fill.apply(b, args);\n    }\n\n    function bufReset() {\n      buf1.fill(0);\n      buf2.fill(0);\n    }\n\n    // This is mostly accurate. Except write() won't write partial bytes to the\n    // string while fill() blindly copies bytes into memory. To account for that an\n    // error will be thrown if not all the data can be written, and the SIZE has\n    // been massaged to work with the input characters.\n    function writeToFill(string, offset, end, encoding) {\n      if (typeof offset === 'string') {\n        encoding = offset;\n        offset = 0;\n        end = buf2.length;\n      } else if (typeof end === 'string') {\n        encoding = end;\n        end = buf2.length;\n      } else if (end === undefined) {\n        end = buf2.length;\n      }\n\n      // Should never be reached.\n      if (offset < 0 || end > buf2.length) throw new ERR_OUT_OF_RANGE();\n\n      if (end <= offset) return buf2;\n\n      offset >>>= 0;\n      end >>>= 0;\n      ok(offset <= buf2.length);\n\n      // Convert \"end\" to \"length\" (which write understands).\n      const length = end - offset < 0 ? 0 : end - offset;\n\n      let wasZero = false;\n      do {\n        const written = buf2.write(string, offset, length, encoding);\n        offset += written;\n        // Safety check in case write falls into infinite loop.\n        if (written === 0) {\n          if (wasZero) throw new Error('Could not write all data to Buffer');\n          else wasZero = true;\n        }\n      } while (offset < buf2.length);\n\n      return buf2;\n    }\n\n    function testBufs(string, offset, length, encoding) {\n      bufReset();\n      buf1.fill.apply(buf1, arguments);\n      // Swap bytes on BE archs for ucs2 encoding.\n      deepStrictEqual(\n        buf1.fill.apply(buf1, arguments),\n        writeToFill.apply(null, arguments)\n      );\n    }\n\n    // Make sure these throw.\n    throws(() => Buffer.allocUnsafe(8).fill('a', -1), {\n      code: 'ERR_OUT_OF_RANGE',\n    });\n    throws(() => Buffer.allocUnsafe(8).fill('a', 0, 9), {\n      code: 'ERR_OUT_OF_RANGE',\n    });\n\n    // // Make sure this doesn't hang indefinitely.\n    Buffer.allocUnsafe(8).fill('');\n    Buffer.alloc(8, '');\n\n    {\n      const buf = Buffer.alloc(64, 10);\n      for (let i = 0; i < buf.length; i++) strictEqual(buf[i], 10);\n\n      buf.fill(11, 0, buf.length >> 1);\n      for (let i = 0; i < buf.length >> 1; i++) strictEqual(buf[i], 11);\n      for (let i = (buf.length >> 1) + 1; i < buf.length; i++)\n        strictEqual(buf[i], 10);\n\n      buf.fill('h');\n      for (let i = 0; i < buf.length; i++)\n        strictEqual(buf[i], 'h'.charCodeAt(0));\n\n      buf.fill(0);\n      for (let i = 0; i < buf.length; i++) strictEqual(buf[i], 0);\n\n      buf.fill(null);\n      for (let i = 0; i < buf.length; i++) strictEqual(buf[i], 0);\n\n      buf.fill(1, 16, 32);\n      for (let i = 0; i < 16; i++) strictEqual(buf[i], 0);\n      for (let i = 16; i < 32; i++) strictEqual(buf[i], 1);\n      for (let i = 32; i < buf.length; i++) strictEqual(buf[i], 0);\n    }\n\n    {\n      const buf = Buffer.alloc(10, 'abc');\n      strictEqual(buf.toString(), 'abcabcabca');\n      buf.fill('է');\n      strictEqual(buf.toString(), 'էէէէէ');\n    }\n\n    // Make sure \"end\" is properly checked, even if it's magically mangled using\n    // Symbol.toPrimitive.\n    {\n      throws(\n        () => {\n          const end = {\n            [Symbol.toPrimitive]() {\n              return 1;\n            },\n          };\n          Buffer.alloc(1).fill(Buffer.alloc(1), 0, end);\n        },\n        {\n          code: 'ERR_INVALID_ARG_TYPE',\n          message:\n            'The \"end\" argument must be of type number. Received an ' +\n            'instance of Object',\n        }\n      );\n    }\n\n    // Test that bypassing 'length' won't cause an abort.\n    // Node.js throws an error in this case because it's often the case that the\n    // Buffer might contain uninitialized memory and we need to prevent overreads.\n    // However, our implementation is backed entirely by ArrayBuffer/Uint8Array\n    // and always has initialized memory, so if the user does something funky\n    // like this, they'll get back undefineds.\n    {\n      const buf = Buffer.from('w00t');\n      Object.defineProperty(buf, 'length', {\n        value: 1337,\n        enumerable: true,\n      });\n      buf.fill('');\n    }\n\n    deepStrictEqual(\n      Buffer.allocUnsafeSlow(16).fill('ab', 'utf16le'),\n      Buffer.from('61006200610062006100620061006200', 'hex')\n    );\n\n    deepStrictEqual(\n      Buffer.allocUnsafeSlow(15).fill('ab', 'utf16le'),\n      Buffer.from('610062006100620061006200610062', 'hex')\n    );\n\n    deepStrictEqual(\n      Buffer.allocUnsafeSlow(16).fill('ab', 'utf16le'),\n      Buffer.from('61006200610062006100620061006200', 'hex')\n    );\n    deepStrictEqual(\n      Buffer.allocUnsafeSlow(16).fill('a', 'utf16le'),\n      Buffer.from('61006100610061006100610061006100', 'hex')\n    );\n\n    strictEqual(\n      Buffer.allocUnsafeSlow(16).fill('a', 'utf16le').toString('utf16le'),\n      'a'.repeat(8)\n    );\n    strictEqual(\n      Buffer.allocUnsafeSlow(16).fill('a', 'latin1').toString('latin1'),\n      'a'.repeat(16)\n    );\n    strictEqual(\n      Buffer.allocUnsafeSlow(16).fill('a', 'utf8').toString('utf8'),\n      'a'.repeat(16)\n    );\n\n    strictEqual(\n      Buffer.allocUnsafeSlow(16).fill('Љ', 'utf16le').toString('utf16le'),\n      'Љ'.repeat(8)\n    );\n    strictEqual(\n      Buffer.allocUnsafeSlow(16).fill('Љ', 'latin1').toString('latin1'),\n      '\\t'.repeat(16)\n    );\n    strictEqual(\n      Buffer.allocUnsafeSlow(16).fill('Љ', 'utf8').toString('utf8'),\n      'Љ'.repeat(8)\n    );\n\n    throws(\n      () => {\n        const buf = Buffer.from('a'.repeat(1000));\n\n        buf.fill('This is not correctly encoded', 'hex');\n      },\n      {\n        name: 'TypeError',\n      }\n    );\n\n    {\n      const bufEmptyString = Buffer.alloc(5, '');\n      strictEqual(bufEmptyString.toString(), '\\x00\\x00\\x00\\x00\\x00');\n\n      const bufEmptyArray = Buffer.alloc(5, []);\n      strictEqual(bufEmptyArray.toString(), '\\x00\\x00\\x00\\x00\\x00');\n\n      const bufEmptyBuffer = Buffer.alloc(5, Buffer.alloc(5));\n      strictEqual(bufEmptyBuffer.toString(), '\\x00\\x00\\x00\\x00\\x00');\n\n      const bufZero = Buffer.alloc(5, 0);\n      strictEqual(bufZero.toString(), '\\x00\\x00\\x00\\x00\\x00');\n    }\n  },\n};\n\nexport const includes = {\n  test(ctrl, env, ctx) {\n    const b = Buffer.from('abcdef');\n    const buf_a = Buffer.from('a');\n    const buf_bc = Buffer.from('bc');\n    const buf_f = Buffer.from('f');\n    const buf_z = Buffer.from('z');\n    const buf_empty = Buffer.from('');\n\n    ok(b.includes('a'));\n    ok(!b.includes('a', 1));\n    ok(!b.includes('a', -1));\n    ok(!b.includes('a', -4));\n    ok(b.includes('a', -b.length));\n    ok(b.includes('a', NaN));\n    ok(b.includes('a', -Infinity));\n    ok(!b.includes('a', Infinity));\n    ok(b.includes('bc'));\n    ok(!b.includes('bc', 2));\n    ok(!b.includes('bc', -1));\n    ok(!b.includes('bc', -3));\n    ok(b.includes('bc', -5));\n    ok(b.includes('bc', NaN));\n    ok(b.includes('bc', -Infinity));\n    ok(!b.includes('bc', Infinity));\n    ok(b.includes('f'), b.length - 1);\n    ok(!b.includes('z'));\n    ok(b.includes(''));\n    ok(b.includes('', 1));\n    ok(b.includes('', b.length + 1));\n    ok(b.includes('', Infinity));\n    ok(b.includes(buf_a));\n    ok(!b.includes(buf_a, 1));\n    ok(!b.includes(buf_a, -1));\n    ok(!b.includes(buf_a, -4));\n    ok(b.includes(buf_a, -b.length));\n    ok(b.includes(buf_a, NaN));\n    ok(b.includes(buf_a, -Infinity));\n    ok(!b.includes(buf_a, Infinity));\n    ok(b.includes(buf_bc));\n    ok(!b.includes(buf_bc, 2));\n    ok(!b.includes(buf_bc, -1));\n    ok(!b.includes(buf_bc, -3));\n    ok(b.includes(buf_bc, -5));\n    ok(b.includes(buf_bc, NaN));\n    ok(b.includes(buf_bc, -Infinity));\n    ok(!b.includes(buf_bc, Infinity));\n    ok(b.includes(buf_f), b.length - 1);\n    ok(!b.includes(buf_z));\n    ok(b.includes(buf_empty));\n    ok(b.includes(buf_empty, 1));\n    ok(b.includes(buf_empty, b.length + 1));\n    ok(b.includes(buf_empty, Infinity));\n    ok(b.includes(0x61));\n    ok(!b.includes(0x61, 1));\n    ok(!b.includes(0x61, -1));\n    ok(!b.includes(0x61, -4));\n    ok(b.includes(0x61, -b.length));\n    ok(b.includes(0x61, NaN));\n    ok(b.includes(0x61, -Infinity));\n    ok(!b.includes(0x61, Infinity));\n    ok(!b.includes(0x0));\n\n    // test offsets\n    ok(b.includes('d', 2));\n    ok(b.includes('f', 5));\n    ok(b.includes('f', -1));\n    ok(!b.includes('f', 6));\n\n    ok(b.includes(Buffer.from('d'), 2));\n    ok(b.includes(Buffer.from('f'), 5));\n    ok(b.includes(Buffer.from('f'), -1));\n    ok(!b.includes(Buffer.from('f'), 6));\n\n    ok(!Buffer.from('ff').includes(Buffer.from('f'), 1, 'ucs2'));\n\n    // test hex encoding\n    strictEqual(\n      Buffer.from(b.toString('hex'), 'hex').includes('64', 0, 'hex'),\n      true\n    );\n    strictEqual(\n      Buffer.from(b.toString('hex'), 'hex').includes(\n        Buffer.from('64', 'hex'),\n        0,\n        'hex'\n      ),\n      true\n    );\n\n    // Test base64 encoding\n    strictEqual(\n      Buffer.from(b.toString('base64'), 'base64').includes('ZA==', 0, 'base64'),\n      true\n    );\n    strictEqual(\n      Buffer.from(b.toString('base64'), 'base64').includes(\n        Buffer.from('ZA==', 'base64'),\n        0,\n        'base64'\n      ),\n      true\n    );\n\n    // test ascii encoding\n    strictEqual(\n      Buffer.from(b.toString('ascii'), 'ascii').includes('d', 0, 'ascii'),\n      true\n    );\n    strictEqual(\n      Buffer.from(b.toString('ascii'), 'ascii').includes(\n        Buffer.from('d', 'ascii'),\n        0,\n        'ascii'\n      ),\n      true\n    );\n\n    // Test latin1 encoding\n    strictEqual(\n      Buffer.from(b.toString('latin1'), 'latin1').includes('d', 0, 'latin1'),\n      true\n    );\n    strictEqual(\n      Buffer.from(b.toString('latin1'), 'latin1').includes(\n        Buffer.from('d', 'latin1'),\n        0,\n        'latin1'\n      ),\n      true\n    );\n\n    // Test binary encoding\n    strictEqual(\n      Buffer.from(b.toString('binary'), 'binary').includes('d', 0, 'binary'),\n      true\n    );\n    strictEqual(\n      Buffer.from(b.toString('binary'), 'binary').includes(\n        Buffer.from('d', 'binary'),\n        0,\n        'binary'\n      ),\n      true\n    );\n\n    // test ucs2 encoding\n    let twoByteString = Buffer.from('\\u039a\\u0391\\u03a3\\u03a3\\u0395', 'ucs2');\n\n    ok(twoByteString.includes('\\u0395', 4, 'ucs2'));\n    ok(twoByteString.includes('\\u03a3', -4, 'ucs2'));\n    ok(twoByteString.includes('\\u03a3', -6, 'ucs2'));\n    ok(twoByteString.includes(Buffer.from('\\u03a3', 'ucs2'), -6, 'ucs2'));\n    ok(!twoByteString.includes('\\u03a3', -2, 'ucs2'));\n\n    const mixedByteStringUcs2 = Buffer.from(\n      '\\u039a\\u0391abc\\u03a3\\u03a3\\u0395',\n      'ucs2'\n    );\n    ok(mixedByteStringUcs2.includes('bc', 0, 'ucs2'));\n    ok(mixedByteStringUcs2.includes('\\u03a3', 0, 'ucs2'));\n    ok(!mixedByteStringUcs2.includes('\\u0396', 0, 'ucs2'));\n\n    ok(mixedByteStringUcs2.includes(Buffer.from('bc', 'ucs2'), 0, 'ucs2'));\n    ok(mixedByteStringUcs2.includes(Buffer.from('\\u03a3', 'ucs2'), 0, 'ucs2'));\n    ok(!mixedByteStringUcs2.includes(Buffer.from('\\u0396', 'ucs2'), 0, 'ucs2'));\n\n    twoByteString = Buffer.from('\\u039a\\u0391\\u03a3\\u03a3\\u0395', 'ucs2');\n\n    // Test single char pattern\n    ok(twoByteString.includes('\\u039a', 0, 'ucs2'));\n    ok(twoByteString.includes('\\u0391', 0, 'ucs2'), 'Alpha');\n    ok(twoByteString.includes('\\u03a3', 0, 'ucs2'), 'First Sigma');\n    ok(twoByteString.includes('\\u03a3', 6, 'ucs2'), 'Second Sigma');\n    ok(twoByteString.includes('\\u0395', 0, 'ucs2'), 'Epsilon');\n    ok(!twoByteString.includes('\\u0392', 0, 'ucs2'), 'Not beta');\n\n    // Test multi-char pattern\n    ok(twoByteString.includes('\\u039a\\u0391', 0, 'ucs2'), 'Lambda Alpha');\n    ok(twoByteString.includes('\\u0391\\u03a3', 0, 'ucs2'), 'Alpha Sigma');\n    ok(twoByteString.includes('\\u03a3\\u03a3', 0, 'ucs2'), 'Sigma Sigma');\n    ok(twoByteString.includes('\\u03a3\\u0395', 0, 'ucs2'), 'Sigma Epsilon');\n\n    const mixedByteStringUtf8 = Buffer.from(\n      '\\u039a\\u0391abc\\u03a3\\u03a3\\u0395'\n    );\n    ok(mixedByteStringUtf8.includes('bc'));\n    ok(mixedByteStringUtf8.includes('bc', 5));\n    ok(mixedByteStringUtf8.includes('bc', -8));\n    ok(mixedByteStringUtf8.includes('\\u03a3'));\n    ok(!mixedByteStringUtf8.includes('\\u0396'));\n\n    // Test complex string includes algorithms. Only trigger for long strings.\n    // Long string that isn't a simple repeat of a shorter string.\n    let longString = 'A';\n    for (let i = 66; i < 76; i++) {\n      // from 'B' to 'K'\n      longString = longString + String.fromCharCode(i) + longString;\n    }\n\n    const longBufferString = Buffer.from(longString);\n\n    // Pattern of 15 chars, repeated every 16 chars in long\n    let pattern = 'ABACABADABACABA';\n    for (let i = 0; i < longBufferString.length - pattern.length; i += 7) {\n      const includes = longBufferString.includes(pattern, i);\n      ok(includes, `Long ABACABA...-string at index ${i}`);\n    }\n    ok(longBufferString.includes('AJABACA'), 'Long AJABACA, First J');\n    ok(longBufferString.includes('AJABACA', 511), 'Long AJABACA, Second J');\n\n    pattern = 'JABACABADABACABA';\n    ok(longBufferString.includes(pattern), 'Long JABACABA..., First J');\n    ok(longBufferString.includes(pattern, 512), 'Long JABACABA..., Second J');\n\n    // Search for a non-ASCII string in a pure ASCII string.\n    const asciiString = Buffer.from(\n      'arglebargleglopglyfarglebargleglopglyfarglebargleglopglyf'\n    );\n    ok(!asciiString.includes('\\x2061'));\n    ok(asciiString.includes('leb', 0));\n\n    // Search in string containing many non-ASCII chars.\n    const allCodePoints = [];\n    for (let i = 0; i < 65534; i++) allCodePoints[i] = i;\n    const allCharsString =\n      String.fromCharCode.apply(String, allCodePoints) +\n      String.fromCharCode(65534, 65535);\n    const allCharsBufferUtf8 = Buffer.from(allCharsString);\n    const allCharsBufferUcs2 = Buffer.from(allCharsString, 'ucs2');\n\n    // Search for string long enough to trigger complex search with ASCII pattern\n    // and UC16 subject.\n    ok(!allCharsBufferUtf8.includes('notfound'));\n    ok(!allCharsBufferUcs2.includes('notfound'));\n\n    // Find substrings in Utf8.\n    let lengths = [1, 3, 15]; // Single char, simple and complex.\n    let indices = [0x5, 0x60, 0x400, 0x680, 0x7ee, 0xff02, 0x16610, 0x2f77b];\n    for (let lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) {\n      for (let i = 0; i < indices.length; i++) {\n        const index = indices[i];\n        let length = lengths[lengthIndex];\n\n        if (index + length > 0x7f) {\n          length = 2 * length;\n        }\n\n        if (index + length > 0x7ff) {\n          length = 3 * length;\n        }\n\n        if (index + length > 0xffff) {\n          length = 4 * length;\n        }\n\n        const patternBufferUtf8 = allCharsBufferUtf8.slice(\n          index,\n          index + length\n        );\n        ok(index, allCharsBufferUtf8.includes(patternBufferUtf8));\n\n        const patternStringUtf8 = patternBufferUtf8.toString();\n        ok(index, allCharsBufferUtf8.includes(patternStringUtf8));\n      }\n    }\n\n    // Find substrings in Usc2.\n    lengths = [2, 4, 16]; // Single char, simple and complex.\n    indices = [0x5, 0x65, 0x105, 0x205, 0x285, 0x2005, 0x2085, 0xfff0];\n    for (let lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) {\n      for (let i = 0; i < indices.length; i++) {\n        const index = indices[i] * 2;\n        const length = lengths[lengthIndex];\n\n        const patternBufferUcs2 = allCharsBufferUcs2.slice(\n          index,\n          index + length\n        );\n        ok(allCharsBufferUcs2.includes(patternBufferUcs2, 0, 'ucs2'));\n\n        const patternStringUcs2 = patternBufferUcs2.toString('ucs2');\n        ok(allCharsBufferUcs2.includes(patternStringUcs2, 0, 'ucs2'));\n      }\n    }\n\n    [() => {}, {}, []].forEach((val) => {\n      throws(() => b.includes(val), {\n        name: 'TypeError',\n      });\n    });\n\n    // Test truncation of Number arguments to uint8\n    {\n      const buf = Buffer.from('this is a test');\n      ok(buf.includes(0x6973));\n      ok(buf.includes(0x697320));\n      ok(buf.includes(0x69732069));\n      ok(buf.includes(0x697374657374));\n      ok(buf.includes(0x69737374));\n      ok(buf.includes(0x69737465));\n      ok(buf.includes(0x69737465));\n      ok(buf.includes(-140));\n      ok(buf.includes(-152));\n      ok(!buf.includes(0xff));\n      ok(!buf.includes(0xffff));\n    }\n  },\n};\n\nexport const indexof = {\n  test(ctrl, env, ctx) {\n    const b = Buffer.from('abcdef');\n    const buf_a = Buffer.from('a');\n    const buf_bc = Buffer.from('bc');\n    const buf_f = Buffer.from('f');\n    const buf_z = Buffer.from('z');\n    const buf_empty = Buffer.from('');\n\n    const s = 'abcdef';\n\n    strictEqual(b.indexOf('a'), 0);\n    strictEqual(b.indexOf('a', 1), -1);\n    strictEqual(b.indexOf('a', -1), -1);\n    strictEqual(b.indexOf('a', -4), -1);\n    strictEqual(b.indexOf('a', -b.length), 0);\n    strictEqual(b.indexOf('a', NaN), 0);\n    strictEqual(b.indexOf('a', -Infinity), 0);\n    strictEqual(b.indexOf('a', Infinity), -1);\n    strictEqual(b.indexOf('bc'), 1);\n    strictEqual(b.indexOf('bc', 2), -1);\n    strictEqual(b.indexOf('bc', -1), -1);\n    strictEqual(b.indexOf('bc', -3), -1);\n    strictEqual(b.indexOf('bc', -5), 1);\n    strictEqual(b.indexOf('bc', NaN), 1);\n    strictEqual(b.indexOf('bc', -Infinity), 1);\n    strictEqual(b.indexOf('bc', Infinity), -1);\n    strictEqual(b.indexOf('f'), b.length - 1);\n    strictEqual(b.indexOf('z'), -1);\n    strictEqual(b.indexOf(''), 0);\n    strictEqual(b.indexOf('', 1), 1);\n    strictEqual(b.indexOf('', b.length + 1), b.length);\n    strictEqual(b.indexOf('', Infinity), b.length);\n    strictEqual(b.indexOf(buf_a), 0);\n    strictEqual(b.indexOf(buf_a, 1), -1);\n    strictEqual(b.indexOf(buf_a, -1), -1);\n    strictEqual(b.indexOf(buf_a, -4), -1);\n    strictEqual(b.indexOf(buf_a, -b.length), 0);\n    strictEqual(b.indexOf(buf_a, NaN), 0);\n    strictEqual(b.indexOf(buf_a, -Infinity), 0);\n    strictEqual(b.indexOf(buf_a, Infinity), -1);\n    strictEqual(b.indexOf(buf_bc), 1);\n    strictEqual(b.indexOf(buf_bc, 2), -1);\n    strictEqual(b.indexOf(buf_bc, -1), -1);\n    strictEqual(b.indexOf(buf_bc, -3), -1);\n    strictEqual(b.indexOf(buf_bc, -5), 1);\n    strictEqual(b.indexOf(buf_bc, NaN), 1);\n    strictEqual(b.indexOf(buf_bc, -Infinity), 1);\n    strictEqual(b.indexOf(buf_bc, Infinity), -1);\n    strictEqual(b.indexOf(buf_f), b.length - 1);\n    strictEqual(b.indexOf(buf_z), -1);\n    strictEqual(b.indexOf(buf_empty), 0);\n    strictEqual(b.indexOf(buf_empty, 1), 1);\n    strictEqual(b.indexOf(buf_empty, b.length + 1), b.length);\n    strictEqual(b.indexOf(buf_empty, Infinity), b.length);\n    strictEqual(b.indexOf(0x61), 0);\n    strictEqual(b.indexOf(0x61, 1), -1);\n    strictEqual(b.indexOf(0x61, -1), -1);\n    strictEqual(b.indexOf(0x61, -4), -1);\n    strictEqual(b.indexOf(0x61, -b.length), 0);\n    strictEqual(b.indexOf(0x61, NaN), 0);\n    strictEqual(b.indexOf(0x61, -Infinity), 0);\n    strictEqual(b.indexOf(0x61, Infinity), -1);\n    strictEqual(b.indexOf(0x0), -1);\n\n    // test offsets\n    strictEqual(b.indexOf('d', 2), 3);\n    strictEqual(b.indexOf('f', 5), 5);\n    strictEqual(b.indexOf('f', -1), 5);\n    strictEqual(b.indexOf('f', 6), -1);\n\n    strictEqual(b.indexOf(Buffer.from('d'), 2), 3);\n    strictEqual(b.indexOf(Buffer.from('f'), 5), 5);\n    strictEqual(b.indexOf(Buffer.from('f'), -1), 5);\n    strictEqual(b.indexOf(Buffer.from('f'), 6), -1);\n\n    strictEqual(Buffer.from('ff').indexOf(Buffer.from('f'), 1, 'ucs2'), -1);\n\n    // Test invalid and uppercase encoding\n    strictEqual(b.indexOf('b', 'utf8'), 1);\n    strictEqual(b.indexOf('b', 'UTF8'), 1);\n    strictEqual(b.indexOf('62', 'HEX'), 1);\n\n    throws(() => b.indexOf('bad', 'enc'), /Unknown encoding: enc/);\n\n    // test hex encoding\n    strictEqual(\n      Buffer.from(b.toString('hex'), 'hex').indexOf('64', 0, 'hex'),\n      3\n    );\n    strictEqual(\n      Buffer.from(b.toString('hex'), 'hex').indexOf(\n        Buffer.from('64', 'hex'),\n        0,\n        'hex'\n      ),\n      3\n    );\n\n    // Test base64 encoding\n    strictEqual(\n      Buffer.from(b.toString('base64'), 'base64').indexOf('ZA==', 0, 'base64'),\n      3\n    );\n    strictEqual(\n      Buffer.from(b.toString('base64'), 'base64').indexOf(\n        Buffer.from('ZA==', 'base64'),\n        0,\n        'base64'\n      ),\n      3\n    );\n\n    // Test base64url encoding\n    strictEqual(\n      Buffer.from(b.toString('base64url'), 'base64url').indexOf(\n        'ZA==',\n        0,\n        'base64url'\n      ),\n      3\n    );\n\n    // test ascii encoding\n    strictEqual(\n      Buffer.from(b.toString('ascii'), 'ascii').indexOf('d', 0, 'ascii'),\n      3\n    );\n    strictEqual(\n      Buffer.from(b.toString('ascii'), 'ascii').indexOf(\n        Buffer.from('d', 'ascii'),\n        0,\n        'ascii'\n      ),\n      3\n    );\n\n    // Test latin1 encoding\n    strictEqual(\n      Buffer.from(b.toString('latin1'), 'latin1').indexOf('d', 0, 'latin1'),\n      3\n    );\n    strictEqual(\n      Buffer.from(b.toString('latin1'), 'latin1').indexOf(\n        Buffer.from('d', 'latin1'),\n        0,\n        'latin1'\n      ),\n      3\n    );\n    strictEqual(\n      Buffer.from('aa\\u00e8aa', 'latin1').indexOf('\\u00e8', 'latin1'),\n      2\n    );\n    strictEqual(Buffer.from('\\u00e8', 'latin1').indexOf('\\u00e8', 'latin1'), 0);\n    strictEqual(\n      Buffer.from('\\u00e8', 'latin1').indexOf(\n        Buffer.from('\\u00e8', 'latin1'),\n        'latin1'\n      ),\n      0\n    );\n\n    // Test binary encoding\n    strictEqual(\n      Buffer.from(b.toString('binary'), 'binary').indexOf('d', 0, 'binary'),\n      3\n    );\n    strictEqual(\n      Buffer.from(b.toString('binary'), 'binary').indexOf(\n        Buffer.from('d', 'binary'),\n        0,\n        'binary'\n      ),\n      3\n    );\n    strictEqual(\n      Buffer.from('aa\\u00e8aa', 'binary').indexOf('\\u00e8', 'binary'),\n      2\n    );\n    strictEqual(Buffer.from('\\u00e8', 'binary').indexOf('\\u00e8', 'binary'), 0);\n    strictEqual(\n      Buffer.from('\\u00e8', 'binary').indexOf(\n        Buffer.from('\\u00e8', 'binary'),\n        'binary'\n      ),\n      0\n    );\n\n    // Test optional offset with passed encoding\n    strictEqual(Buffer.from('aaaa0').indexOf('30', 'hex'), 4);\n    strictEqual(Buffer.from('aaaa00a').indexOf('3030', 'hex'), 4);\n\n    {\n      // Test usc2 and utf16le encoding\n      ['ucs2', 'utf16le'].forEach((encoding) => {\n        const twoByteString = Buffer.from(\n          '\\u039a\\u0391\\u03a3\\u03a3\\u0395',\n          encoding\n        );\n\n        strictEqual(twoByteString.indexOf('\\u0395', 4, encoding), 8);\n        strictEqual(twoByteString.indexOf('\\u03a3', -4, encoding), 6);\n        strictEqual(twoByteString.indexOf('\\u03a3', -6, encoding), 4);\n        strictEqual(\n          twoByteString.indexOf(Buffer.from('\\u03a3', encoding), -6, encoding),\n          4\n        );\n        strictEqual(-1, twoByteString.indexOf('\\u03a3', -2, encoding));\n      });\n    }\n\n    const mixedByteStringUcs2 = Buffer.from(\n      '\\u039a\\u0391abc\\u03a3\\u03a3\\u0395',\n      'ucs2'\n    );\n    strictEqual(mixedByteStringUcs2.indexOf('bc', 0, 'ucs2'), 6);\n    strictEqual(mixedByteStringUcs2.indexOf('\\u03a3', 0, 'ucs2'), 10);\n    strictEqual(-1, mixedByteStringUcs2.indexOf('\\u0396', 0, 'ucs2'));\n\n    strictEqual(\n      mixedByteStringUcs2.indexOf(Buffer.from('bc', 'ucs2'), 0, 'ucs2'),\n      6\n    );\n    strictEqual(\n      mixedByteStringUcs2.indexOf(Buffer.from('\\u03a3', 'ucs2'), 0, 'ucs2'),\n      10\n    );\n    strictEqual(\n      -1,\n      mixedByteStringUcs2.indexOf(Buffer.from('\\u0396', 'ucs2'), 0, 'ucs2')\n    );\n\n    {\n      const twoByteString = Buffer.from(\n        '\\u039a\\u0391\\u03a3\\u03a3\\u0395',\n        'ucs2'\n      );\n\n      // Test single char pattern\n      strictEqual(twoByteString.indexOf('\\u039a', 0, 'ucs2'), 0);\n      let index = twoByteString.indexOf('\\u0391', 0, 'ucs2');\n      strictEqual(index, 2, `Alpha - at index ${index}`);\n      index = twoByteString.indexOf('\\u03a3', 0, 'ucs2');\n      strictEqual(index, 4, `First Sigma - at index ${index}`);\n      index = twoByteString.indexOf('\\u03a3', 6, 'ucs2');\n      strictEqual(index, 6, `Second Sigma - at index ${index}`);\n      index = twoByteString.indexOf('\\u0395', 0, 'ucs2');\n      strictEqual(index, 8, `Epsilon - at index ${index}`);\n      index = twoByteString.indexOf('\\u0392', 0, 'ucs2');\n      strictEqual(-1, index, `Not beta - at index ${index}`);\n\n      // Test multi-char pattern\n      index = twoByteString.indexOf('\\u039a\\u0391', 0, 'ucs2');\n      strictEqual(index, 0, `Lambda Alpha - at index ${index}`);\n      index = twoByteString.indexOf('\\u0391\\u03a3', 0, 'ucs2');\n      strictEqual(index, 2, `Alpha Sigma - at index ${index}`);\n      index = twoByteString.indexOf('\\u03a3\\u03a3', 0, 'ucs2');\n      strictEqual(index, 4, `Sigma Sigma - at index ${index}`);\n      index = twoByteString.indexOf('\\u03a3\\u0395', 0, 'ucs2');\n      strictEqual(index, 6, `Sigma Epsilon - at index ${index}`);\n    }\n\n    const mixedByteStringUtf8 = Buffer.from(\n      '\\u039a\\u0391abc\\u03a3\\u03a3\\u0395'\n    );\n    strictEqual(mixedByteStringUtf8.indexOf('bc'), 5);\n    strictEqual(mixedByteStringUtf8.indexOf('bc', 5), 5);\n    strictEqual(mixedByteStringUtf8.indexOf('bc', -8), 5);\n    strictEqual(mixedByteStringUtf8.indexOf('\\u03a3'), 7);\n    strictEqual(mixedByteStringUtf8.indexOf('\\u0396'), -1);\n\n    // Test complex string indexOf algorithms. Only trigger for long strings.\n    // Long string that isn't a simple repeat of a shorter string.\n    let longString = 'A';\n    for (let i = 66; i < 76; i++) {\n      // from 'B' to 'K'\n      longString = longString + String.fromCharCode(i) + longString;\n    }\n\n    const longBufferString = Buffer.from(longString);\n\n    // Pattern of 15 chars, repeated every 16 chars in long\n    let pattern = 'ABACABADABACABA';\n    for (let i = 0; i < longBufferString.length - pattern.length; i += 7) {\n      const index = longBufferString.indexOf(pattern, i);\n      strictEqual(\n        (i + 15) & ~0xf,\n        index,\n        `Long ABACABA...-string at index ${i}`\n      );\n    }\n\n    let index = longBufferString.indexOf('AJABACA');\n    strictEqual(index, 510, `Long AJABACA, First J - at index ${index}`);\n    index = longBufferString.indexOf('AJABACA', 511);\n    strictEqual(index, 1534, `Long AJABACA, Second J - at index ${index}`);\n\n    pattern = 'JABACABADABACABA';\n    index = longBufferString.indexOf(pattern);\n    strictEqual(index, 511, `Long JABACABA..., First J - at index ${index}`);\n    index = longBufferString.indexOf(pattern, 512);\n    strictEqual(index, 1535, `Long JABACABA..., Second J - at index ${index}`);\n\n    // Search for a non-ASCII string in a pure ASCII string.\n    const asciiString = Buffer.from(\n      'arglebargleglopglyfarglebargleglopglyfarglebargleglopglyf'\n    );\n    strictEqual(-1, asciiString.indexOf('\\x2061'));\n    strictEqual(asciiString.indexOf('leb', 0), 3);\n\n    // Search in string containing many non-ASCII chars.\n    const allCodePoints = [];\n    for (let i = 0; i < 65534; i++) allCodePoints[i] = i;\n    const allCharsString =\n      String.fromCharCode.apply(String, allCodePoints) +\n      String.fromCharCode(65534, 65535);\n    const allCharsBufferUtf8 = Buffer.from(allCharsString);\n    const allCharsBufferUcs2 = Buffer.from(allCharsString, 'ucs2');\n\n    // Search for string long enough to trigger complex search with ASCII pattern\n    // and UC16 subject.\n    strictEqual(-1, allCharsBufferUtf8.indexOf('notfound'));\n    strictEqual(-1, allCharsBufferUcs2.indexOf('notfound'));\n\n    // Needle is longer than haystack, but only because it's encoded as UTF-16\n    strictEqual(Buffer.from('aaaa').indexOf('a'.repeat(4), 'ucs2'), -1);\n\n    strictEqual(Buffer.from('aaaa').indexOf('a'.repeat(4), 'utf8'), 0);\n    strictEqual(Buffer.from('aaaa').indexOf('你好', 'ucs2'), -1);\n\n    // Haystack has odd length, but the needle is UCS2.\n    strictEqual(Buffer.from('aaaaa').indexOf('b', 'ucs2'), -1);\n\n    {\n      // Find substrings in Utf8.\n      const lengths = [1, 3, 15]; // Single char, simple and complex.\n      const indices = [\n        0x5, 0x60, 0x400, 0x680, 0x7ee, 0xff02, 0x16610, 0x2f77b,\n      ];\n      for (let lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) {\n        for (let i = 0; i < indices.length; i++) {\n          const index = indices[i];\n          let length = lengths[lengthIndex];\n\n          if (index + length > 0x7f) {\n            length = 2 * length;\n          }\n\n          if (index + length > 0x7ff) {\n            length = 3 * length;\n          }\n\n          if (index + length > 0xffff) {\n            length = 4 * length;\n          }\n\n          const patternBufferUtf8 = allCharsBufferUtf8.slice(\n            index,\n            index + length\n          );\n          strictEqual(index, allCharsBufferUtf8.indexOf(patternBufferUtf8));\n\n          const patternStringUtf8 = patternBufferUtf8.toString();\n          strictEqual(index, allCharsBufferUtf8.indexOf(patternStringUtf8));\n        }\n      }\n    }\n\n    {\n      // Find substrings in Usc2.\n      const lengths = [2, 4, 16]; // Single char, simple and complex.\n      const indices = [0x5, 0x65, 0x105, 0x205, 0x285, 0x2005, 0x2085, 0xfff0];\n      for (let lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) {\n        for (let i = 0; i < indices.length; i++) {\n          const index = indices[i] * 2;\n          const length = lengths[lengthIndex];\n\n          const patternBufferUcs2 = allCharsBufferUcs2.slice(\n            index,\n            index + length\n          );\n          strictEqual(\n            index,\n            allCharsBufferUcs2.indexOf(patternBufferUcs2, 0, 'ucs2')\n          );\n\n          const patternStringUcs2 = patternBufferUcs2.toString('ucs2');\n          strictEqual(\n            index,\n            allCharsBufferUcs2.indexOf(patternStringUcs2, 0, 'ucs2')\n          );\n        }\n      }\n    }\n\n    [() => {}, {}, []].forEach((val) => {\n      throws(() => b.indexOf(val), {\n        name: 'TypeError',\n      });\n    });\n\n    // Test weird offset arguments.\n    // The following offsets coerce to NaN or 0, searching the whole Buffer\n    strictEqual(b.indexOf('b', undefined), 1);\n    strictEqual(b.indexOf('b', {}), 1);\n    strictEqual(b.indexOf('b', 0), 1);\n    strictEqual(b.indexOf('b', null), 1);\n    strictEqual(b.indexOf('b', []), 1);\n\n    // The following offset coerces to 2, in other words +[2] === 2\n    strictEqual(b.indexOf('b', [2]), -1);\n\n    // Behavior should match String.indexOf()\n    strictEqual(b.indexOf('b', undefined), s.indexOf('b', undefined));\n    strictEqual(b.indexOf('b', {}), s.indexOf('b', {}));\n    strictEqual(b.indexOf('b', 0), s.indexOf('b', 0));\n    strictEqual(b.indexOf('b', null), s.indexOf('b', null));\n    strictEqual(b.indexOf('b', []), s.indexOf('b', []));\n    strictEqual(b.indexOf('b', [2]), s.indexOf('b', [2]));\n\n    // All code for handling encodings is shared between Buffer.indexOf and\n    // Buffer.lastIndexOf, so only testing the separate lastIndexOf semantics.\n\n    // Test lastIndexOf basic functionality; Buffer b contains 'abcdef'.\n    // lastIndexOf string:\n    strictEqual(b.lastIndexOf('a'), 0);\n    strictEqual(b.lastIndexOf('a', 1), 0);\n    strictEqual(b.lastIndexOf('b', 1), 1);\n    strictEqual(b.lastIndexOf('c', 1), -1);\n    strictEqual(b.lastIndexOf('a', -1), 0);\n    strictEqual(b.lastIndexOf('a', -4), 0);\n    strictEqual(b.lastIndexOf('a', -b.length), 0);\n    strictEqual(b.lastIndexOf('a', -b.length - 1), -1);\n    strictEqual(b.lastIndexOf('a', NaN), 0);\n    strictEqual(b.lastIndexOf('a', -Infinity), -1);\n    strictEqual(b.lastIndexOf('a', Infinity), 0);\n    // lastIndexOf Buffer:\n    strictEqual(b.lastIndexOf(buf_a), 0);\n    strictEqual(b.lastIndexOf(buf_a, 1), 0);\n    strictEqual(b.lastIndexOf(buf_a, -1), 0);\n    strictEqual(b.lastIndexOf(buf_a, -4), 0);\n    strictEqual(b.lastIndexOf(buf_a, -b.length), 0);\n    strictEqual(b.lastIndexOf(buf_a, -b.length - 1), -1);\n    strictEqual(b.lastIndexOf(buf_a, NaN), 0);\n    strictEqual(b.lastIndexOf(buf_a, -Infinity), -1);\n    strictEqual(b.lastIndexOf(buf_a, Infinity), 0);\n    strictEqual(b.lastIndexOf(buf_bc), 1);\n    strictEqual(b.lastIndexOf(buf_bc, 2), 1);\n    strictEqual(b.lastIndexOf(buf_bc, -1), 1);\n    strictEqual(b.lastIndexOf(buf_bc, -3), 1);\n    strictEqual(b.lastIndexOf(buf_bc, -5), 1);\n    strictEqual(b.lastIndexOf(buf_bc, -6), -1);\n    strictEqual(b.lastIndexOf(buf_bc, NaN), 1);\n    strictEqual(b.lastIndexOf(buf_bc, -Infinity), -1);\n    strictEqual(b.lastIndexOf(buf_bc, Infinity), 1);\n    strictEqual(b.lastIndexOf(buf_f), b.length - 1);\n    strictEqual(b.lastIndexOf(buf_z), -1);\n    strictEqual(b.lastIndexOf(buf_empty), b.length);\n    strictEqual(b.lastIndexOf(buf_empty, 1), 1);\n    strictEqual(b.lastIndexOf(buf_empty, b.length + 1), b.length);\n    strictEqual(b.lastIndexOf(buf_empty, Infinity), b.length);\n    // lastIndexOf number:\n    strictEqual(b.lastIndexOf(0x61), 0);\n    strictEqual(b.lastIndexOf(0x61, 1), 0);\n    strictEqual(b.lastIndexOf(0x61, -1), 0);\n    strictEqual(b.lastIndexOf(0x61, -4), 0);\n    strictEqual(b.lastIndexOf(0x61, -b.length), 0);\n    strictEqual(b.lastIndexOf(0x61, -b.length - 1), -1);\n    strictEqual(b.lastIndexOf(0x61, NaN), 0);\n    strictEqual(b.lastIndexOf(0x61, -Infinity), -1);\n    strictEqual(b.lastIndexOf(0x61, Infinity), 0);\n    strictEqual(b.lastIndexOf(0x0), -1);\n\n    // Test weird offset arguments.\n    // The following offsets coerce to NaN, searching the whole Buffer\n    strictEqual(b.lastIndexOf('b', undefined), 1);\n    strictEqual(b.lastIndexOf('b', {}), 1);\n\n    // The following offsets coerce to 0\n    strictEqual(b.lastIndexOf('b', 0), -1);\n    strictEqual(b.lastIndexOf('b', null), -1);\n    strictEqual(b.lastIndexOf('b', []), -1);\n\n    // The following offset coerces to 2, in other words +[2] === 2\n    strictEqual(b.lastIndexOf('b', [2]), 1);\n\n    // Behavior should match String.lastIndexOf()\n    strictEqual(b.lastIndexOf('b', undefined), s.lastIndexOf('b', undefined));\n    strictEqual(b.lastIndexOf('b', {}), s.lastIndexOf('b', {}));\n    strictEqual(b.lastIndexOf('b', 0), s.lastIndexOf('b', 0));\n    strictEqual(b.lastIndexOf('b', null), s.lastIndexOf('b', null));\n    strictEqual(b.lastIndexOf('b', []), s.lastIndexOf('b', []));\n    strictEqual(b.lastIndexOf('b', [2]), s.lastIndexOf('b', [2]));\n\n    // Test needles longer than the haystack.\n    strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 'ucs2'), -1);\n    strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 'utf8'), -1);\n    strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 'latin1'), -1);\n    strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 'binary'), -1);\n    strictEqual(b.lastIndexOf(Buffer.from('aaaaaaaaaaaaaaa')), -1);\n    strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 2, 'ucs2'), -1);\n    strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 3, 'utf8'), -1);\n    strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 5, 'latin1'), -1);\n    strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 5, 'binary'), -1);\n    strictEqual(b.lastIndexOf(Buffer.from('aaaaaaaaaaaaaaa'), 7), -1);\n\n    // 你好 expands to a total of 6 bytes using UTF-8 and 4 bytes using UTF-16\n    strictEqual(buf_bc.lastIndexOf('你好', 'ucs2'), -1);\n    strictEqual(buf_bc.lastIndexOf('你好', 'utf8'), -1);\n    strictEqual(buf_bc.lastIndexOf('你好', 'latin1'), -1);\n    strictEqual(buf_bc.lastIndexOf('你好', 'binary'), -1);\n    strictEqual(buf_bc.lastIndexOf(Buffer.from('你好')), -1);\n    strictEqual(buf_bc.lastIndexOf('你好', 2, 'ucs2'), -1);\n    strictEqual(buf_bc.lastIndexOf('你好', 3, 'utf8'), -1);\n    strictEqual(buf_bc.lastIndexOf('你好', 5, 'latin1'), -1);\n    strictEqual(buf_bc.lastIndexOf('你好', 5, 'binary'), -1);\n    strictEqual(buf_bc.lastIndexOf(Buffer.from('你好'), 7), -1);\n\n    // Test lastIndexOf on a longer buffer:\n    const bufferString = Buffer.from('a man a plan a canal panama');\n    strictEqual(bufferString.lastIndexOf('canal'), 15);\n    strictEqual(bufferString.lastIndexOf('panama'), 21);\n    strictEqual(bufferString.lastIndexOf('a man a plan a canal panama'), 0);\n    strictEqual(-1, bufferString.lastIndexOf('a man a plan a canal mexico'));\n    strictEqual(\n      -1,\n      bufferString.lastIndexOf('a man a plan a canal mexico city')\n    );\n    strictEqual(-1, bufferString.lastIndexOf(Buffer.from('a'.repeat(1000))));\n    strictEqual(bufferString.lastIndexOf('a man a plan', 4), 0);\n    strictEqual(bufferString.lastIndexOf('a '), 13);\n    strictEqual(bufferString.lastIndexOf('a ', 13), 13);\n    strictEqual(bufferString.lastIndexOf('a ', 12), 6);\n    strictEqual(bufferString.lastIndexOf('a ', 5), 0);\n    strictEqual(bufferString.lastIndexOf('a ', -1), 13);\n    strictEqual(bufferString.lastIndexOf('a ', -27), 0);\n    strictEqual(-1, bufferString.lastIndexOf('a ', -28));\n\n    // Test lastIndexOf for the case that the first character can be found,\n    // but in a part of the buffer that does not make search to search\n    // due do length constraints.\n    const abInUCS2 = Buffer.from('ab', 'ucs2');\n    strictEqual(-1, Buffer.from('µaaaa¶bbbb', 'latin1').lastIndexOf('µ'));\n    strictEqual(-1, Buffer.from('µaaaa¶bbbb', 'binary').lastIndexOf('µ'));\n    strictEqual(-1, Buffer.from('bc').lastIndexOf('ab'));\n    strictEqual(-1, Buffer.from('abc').lastIndexOf('qa'));\n    strictEqual(-1, Buffer.from('abcdef').lastIndexOf('qabc'));\n    strictEqual(-1, Buffer.from('bc').lastIndexOf(Buffer.from('ab')));\n    strictEqual(-1, Buffer.from('bc', 'ucs2').lastIndexOf('ab', 'ucs2'));\n    strictEqual(-1, Buffer.from('bc', 'ucs2').lastIndexOf(abInUCS2));\n\n    strictEqual(Buffer.from('abc').lastIndexOf('ab'), 0);\n    strictEqual(Buffer.from('abc').lastIndexOf('ab', 1), 0);\n    strictEqual(Buffer.from('abc').lastIndexOf('ab', 2), 0);\n    strictEqual(Buffer.from('abc').lastIndexOf('ab', 3), 0);\n\n    // The above tests test the LINEAR and SINGLE-CHAR strategies.\n    // Now, we test the BOYER-MOORE-HORSPOOL strategy.\n    // Test lastIndexOf on a long buffer w multiple matches:\n    // pattern = 'JABACABADABACABA';\n    strictEqual(longBufferString.lastIndexOf(pattern), 1535);\n    strictEqual(longBufferString.lastIndexOf(pattern, 1535), 1535);\n    strictEqual(longBufferString.lastIndexOf(pattern, 1534), 511);\n\n    // Generate a really long Thue-Morse sequence of 'yolo' and 'swag',\n    // \"yolo swag swag yolo swag yolo yolo swag\" ..., goes on for about 5MB.\n    // This is hard to search because it all looks similar, but never repeats.\n\n    // countBits returns the number of bits in the binary representation of n.\n    function countBits(n) {\n      let count;\n      for (count = 0; n > 0; count++) {\n        n = n & (n - 1); // remove top bit\n      }\n      return count;\n    }\n    const parts = [];\n    for (let i = 0; i < 1000000; i++) {\n      parts.push(countBits(i) % 2 === 0 ? 'yolo' : 'swag');\n    }\n    const reallyLong = Buffer.from(parts.join(' '));\n    strictEqual(reallyLong.slice(0, 19).toString(), 'yolo swag swag yolo');\n\n    // Expensive reverse searches. Stress test lastIndexOf:\n    pattern = reallyLong.slice(0, 100000); // First 1/50th of the pattern.\n    strictEqual(reallyLong.lastIndexOf(pattern), 4751360);\n    strictEqual(reallyLong.lastIndexOf(pattern, 4000000), 3932160);\n    strictEqual(reallyLong.lastIndexOf(pattern, 3000000), 2949120);\n    pattern = reallyLong.slice(100000, 200000); // Second 1/50th.\n    strictEqual(reallyLong.lastIndexOf(pattern), 4728480);\n    pattern = reallyLong.slice(0, 1000000); // First 1/5th.\n    strictEqual(reallyLong.lastIndexOf(pattern), 3932160);\n    pattern = reallyLong.slice(0, 2000000); // first 2/5ths.\n    strictEqual(reallyLong.lastIndexOf(pattern), 0);\n\n    // Test truncation of Number arguments to uint8\n    {\n      const buf = Buffer.from('this is a test');\n      strictEqual(buf.indexOf(0x6973), 3);\n      strictEqual(buf.indexOf(0x697320), 4);\n      strictEqual(buf.indexOf(0x69732069), 2);\n      strictEqual(buf.indexOf(0x697374657374), 0);\n      strictEqual(buf.indexOf(0x69737374), 0);\n      strictEqual(buf.indexOf(0x69737465), 11);\n      strictEqual(buf.indexOf(0x69737465), 11);\n      strictEqual(buf.indexOf(-140), 0);\n      strictEqual(buf.indexOf(-152), 1);\n      strictEqual(buf.indexOf(0xff), -1);\n      strictEqual(buf.indexOf(0xffff), -1);\n    }\n\n    // Test that Uint8Array arguments are okay.\n    {\n      const needle = new Uint8Array([0x66, 0x6f, 0x6f]);\n      const haystack = Buffer.from('a foo b foo');\n      strictEqual(haystack.indexOf(needle), 2);\n      strictEqual(haystack.lastIndexOf(needle), haystack.length - 3);\n    }\n  },\n};\n\nexport const inheritance = {\n  test(ctrl, env, ctx) {\n    function T(n) {\n      const ui8 = new Uint8Array(n);\n      Object.setPrototypeOf(ui8, T.prototype);\n      return ui8;\n    }\n    Object.setPrototypeOf(T.prototype, Buffer.prototype);\n    Object.setPrototypeOf(T, Buffer);\n\n    T.prototype.sum = function sum() {\n      let cntr = 0;\n      for (let i = 0; i < this.length; i++) cntr += this[i];\n      return cntr;\n    };\n\n    const vals = [new T(4), T(4)];\n\n    vals.forEach(function (t) {\n      strictEqual(t.constructor, T);\n      strictEqual(Object.getPrototypeOf(t), T.prototype);\n      strictEqual(\n        Object.getPrototypeOf(Object.getPrototypeOf(t)),\n        Buffer.prototype\n      );\n\n      t.fill(5);\n      let cntr = 0;\n      for (let i = 0; i < t.length; i++) cntr += t[i];\n      strictEqual(cntr, t.length * 5);\n\n      // Check this does not throw\n      t.toString();\n    });\n  },\n};\n\nexport const iterator = {\n  test(ctrl, env, ctx) {\n    const buffer = Buffer.from([1, 2, 3, 4, 5]);\n    let arr;\n    let b;\n\n    // Buffers should be iterable\n\n    arr = [];\n\n    for (b of buffer) arr.push(b);\n\n    deepStrictEqual(arr, [1, 2, 3, 4, 5]);\n\n    // Buffer iterators should be iterable\n\n    arr = [];\n\n    for (b of buffer[Symbol.iterator]()) arr.push(b);\n\n    deepStrictEqual(arr, [1, 2, 3, 4, 5]);\n\n    // buffer#values() should return iterator for values\n\n    arr = [];\n\n    for (b of buffer.values()) arr.push(b);\n\n    deepStrictEqual(arr, [1, 2, 3, 4, 5]);\n\n    // buffer#keys() should return iterator for keys\n\n    arr = [];\n\n    for (b of buffer.keys()) arr.push(b);\n\n    deepStrictEqual(arr, [0, 1, 2, 3, 4]);\n\n    // buffer#entries() should return iterator for entries\n\n    arr = [];\n\n    for (b of buffer.entries()) arr.push(b);\n\n    deepStrictEqual(arr, [\n      [0, 1],\n      [1, 2],\n      [2, 3],\n      [3, 4],\n      [4, 5],\n    ]);\n  },\n};\n\nexport const negativeAlloc = {\n  test(ctrl, env, ctx) {\n    const msg = {\n      name: 'RangeError',\n    };\n\n    // Test that negative Buffer length inputs throw errors.\n\n    throws(() => Buffer(-100), msg);\n    throws(() => Buffer(-1), msg);\n    throws(() => Buffer(NaN), msg);\n\n    throws(() => Buffer.alloc(-100), msg);\n    throws(() => Buffer.alloc(-1), msg);\n    throws(() => Buffer.alloc(NaN), msg);\n\n    throws(() => Buffer.allocUnsafe(-100), msg);\n    throws(() => Buffer.allocUnsafe(-1), msg);\n    throws(() => Buffer.allocUnsafe(NaN), msg);\n\n    throws(() => Buffer.allocUnsafeSlow(-100), msg);\n    throws(() => Buffer.allocUnsafeSlow(-1), msg);\n    throws(() => Buffer.allocUnsafeSlow(NaN), msg);\n\n    throws(() => SlowBuffer(-100), msg);\n    throws(() => SlowBuffer(-1), msg);\n    throws(() => SlowBuffer(NaN), msg);\n  },\n};\n\nexport const overMaxLength = {\n  test(ctrl, env, ctx) {\n    const bufferMaxSizeMsg = {\n      name: 'RangeError',\n    };\n\n    throws(() => Buffer((-1 >>> 0) + 2), bufferMaxSizeMsg);\n    throws(() => SlowBuffer((-1 >>> 0) + 2), bufferMaxSizeMsg);\n    throws(() => Buffer.alloc((-1 >>> 0) + 2), bufferMaxSizeMsg);\n    throws(() => Buffer.allocUnsafe((-1 >>> 0) + 2), bufferMaxSizeMsg);\n    throws(() => Buffer.allocUnsafeSlow((-1 >>> 0) + 2), bufferMaxSizeMsg);\n\n    throws(() => Buffer(kMaxLength + 1), bufferMaxSizeMsg);\n    throws(() => SlowBuffer(kMaxLength + 1), bufferMaxSizeMsg);\n    throws(() => Buffer.alloc(kMaxLength + 1), bufferMaxSizeMsg);\n    throws(() => Buffer.allocUnsafe(kMaxLength + 1), bufferMaxSizeMsg);\n    throws(() => Buffer.allocUnsafeSlow(kMaxLength + 1), bufferMaxSizeMsg);\n\n    // issue GH-4331\n    throws(() => Buffer.allocUnsafe(0x100000001), bufferMaxSizeMsg);\n    throws(() => Buffer.allocUnsafe(0xfffffffff), bufferMaxSizeMsg);\n  },\n};\n\nexport const read = {\n  test(ctrl, env, ctx) {\n    // Testing basic buffer read functions\n    const buf = Buffer.from([\n      0xa4, 0xfd, 0x48, 0xea, 0xcf, 0xff, 0xd9, 0x01, 0xde,\n    ]);\n\n    function read(buff, funx, args, expected) {\n      strictEqual(buff[funx](...args), expected);\n      throws(() => buff[funx](-1, args[1]), { code: 'ERR_OUT_OF_RANGE' });\n    }\n\n    // Testing basic functionality of readDoubleBE() and readDoubleLE()\n    read(buf, 'readDoubleBE', [1], -3.1827727774563287e295);\n    read(buf, 'readDoubleLE', [1], -6.966010051009108e144);\n\n    // Testing basic functionality of readFloatBE() and readFloatLE()\n    read(buf, 'readFloatBE', [1], -1.6691549692541768e37);\n    read(buf, 'readFloatLE', [1], -7861303808);\n\n    // Testing basic functionality of readInt8()\n    read(buf, 'readInt8', [1], -3);\n\n    // Testing basic functionality of readInt16BE() and readInt16LE()\n    read(buf, 'readInt16BE', [1], -696);\n    read(buf, 'readInt16LE', [1], 0x48fd);\n\n    // Testing basic functionality of readInt32BE() and readInt32LE()\n    read(buf, 'readInt32BE', [1], -45552945);\n    read(buf, 'readInt32LE', [1], -806729475);\n\n    // Testing basic functionality of readIntBE() and readIntLE()\n    read(buf, 'readIntBE', [1, 1], -3);\n    read(buf, 'readIntLE', [2, 1], 0x48);\n\n    // Testing basic functionality of readUInt8()\n    read(buf, 'readUInt8', [1], 0xfd);\n\n    // Testing basic functionality of readUInt16BE() and readUInt16LE()\n    read(buf, 'readUInt16BE', [2], 0x48ea);\n    read(buf, 'readUInt16LE', [2], 0xea48);\n\n    // Testing basic functionality of readUInt32BE() and readUInt32LE()\n    read(buf, 'readUInt32BE', [1], 0xfd48eacf);\n    read(buf, 'readUInt32LE', [1], 0xcfea48fd);\n\n    // Testing basic functionality of readUIntBE() and readUIntLE()\n    read(buf, 'readUIntBE', [2, 2], 0x48ea);\n    read(buf, 'readUIntLE', [2, 2], 0xea48);\n\n    // Error name and message\n    const OOR_ERROR = {\n      name: 'RangeError',\n    };\n\n    const OOB_ERROR = {\n      name: 'RangeError',\n      message: 'Attempt to access memory outside buffer bounds',\n    };\n\n    // Attempt to overflow buffers, similar to previous bug in array buffers\n    throws(() => Buffer.allocUnsafe(8).readFloatBE(0xffffffff), OOR_ERROR);\n\n    throws(() => Buffer.allocUnsafe(8).readFloatLE(0xffffffff), OOR_ERROR);\n\n    // Ensure negative values can't get past offset\n    throws(() => Buffer.allocUnsafe(8).readFloatBE(-1), OOR_ERROR);\n    throws(() => Buffer.allocUnsafe(8).readFloatLE(-1), OOR_ERROR);\n\n    // Offset checks\n    {\n      const buf = Buffer.allocUnsafe(0);\n\n      throws(() => buf.readUInt8(0), OOB_ERROR);\n      throws(() => buf.readInt8(0), OOB_ERROR);\n    }\n\n    [16, 32].forEach((bit) => {\n      const buf = Buffer.allocUnsafe(bit / 8 - 1);\n      [`Int${bit}B`, `Int${bit}L`, `UInt${bit}B`, `UInt${bit}L`].forEach(\n        (fn) => {\n          throws(() => buf[`read${fn}E`](0), OOB_ERROR);\n        }\n      );\n    });\n\n    [16, 32].forEach((bits) => {\n      const buf = Buffer.from([0xff, 0xff, 0xff, 0xff]);\n      ['LE', 'BE'].forEach((endian) => {\n        strictEqual(\n          buf[`readUInt${bits}${endian}`](0),\n          0xffffffff >>> (32 - bits)\n        );\n\n        strictEqual(\n          buf[`readInt${bits}${endian}`](0),\n          0xffffffff >> (32 - bits)\n        );\n      });\n    });\n  },\n};\n\nexport const readDouble = {\n  test(ctrl, env, ctx) {\n    // Test (64 bit) double\n    const buffer = Buffer.allocUnsafe(8);\n\n    buffer[0] = 0x55;\n    buffer[1] = 0x55;\n    buffer[2] = 0x55;\n    buffer[3] = 0x55;\n    buffer[4] = 0x55;\n    buffer[5] = 0x55;\n    buffer[6] = 0xd5;\n    buffer[7] = 0x3f;\n    strictEqual(buffer.readDoubleBE(0), 1.1945305291680097e103);\n    strictEqual(buffer.readDoubleLE(0), 0.3333333333333333);\n\n    buffer[0] = 1;\n    buffer[1] = 0;\n    buffer[2] = 0;\n    buffer[3] = 0;\n    buffer[4] = 0;\n    buffer[5] = 0;\n    buffer[6] = 0xf0;\n    buffer[7] = 0x3f;\n    strictEqual(buffer.readDoubleBE(0), 7.291122019655968e-304);\n    strictEqual(buffer.readDoubleLE(0), 1.0000000000000002);\n\n    buffer[0] = 2;\n    strictEqual(buffer.readDoubleBE(0), 4.778309726801735e-299);\n    strictEqual(buffer.readDoubleLE(0), 1.0000000000000004);\n\n    buffer[0] = 1;\n    buffer[6] = 0;\n    buffer[7] = 0;\n    // eslint-disable-next-line no-loss-of-precision\n    strictEqual(buffer.readDoubleBE(0), 7.291122019556398e-304);\n    strictEqual(buffer.readDoubleLE(0), 5e-324);\n\n    buffer[0] = 0xff;\n    buffer[1] = 0xff;\n    buffer[2] = 0xff;\n    buffer[3] = 0xff;\n    buffer[4] = 0xff;\n    buffer[5] = 0xff;\n    buffer[6] = 0x0f;\n    buffer[7] = 0x00;\n    ok(Number.isNaN(buffer.readDoubleBE(0)));\n    strictEqual(buffer.readDoubleLE(0), 2.225073858507201e-308);\n\n    buffer[6] = 0xef;\n    buffer[7] = 0x7f;\n    ok(Number.isNaN(buffer.readDoubleBE(0)));\n    strictEqual(buffer.readDoubleLE(0), 1.7976931348623157e308);\n\n    buffer[0] = 0;\n    buffer[1] = 0;\n    buffer[2] = 0;\n    buffer[3] = 0;\n    buffer[4] = 0;\n    buffer[5] = 0;\n    buffer[6] = 0xf0;\n    buffer[7] = 0x3f;\n    strictEqual(buffer.readDoubleBE(0), 3.03865e-319);\n    strictEqual(buffer.readDoubleLE(0), 1);\n\n    buffer[6] = 0;\n    buffer[7] = 0x40;\n    strictEqual(buffer.readDoubleBE(0), 3.16e-322);\n    strictEqual(buffer.readDoubleLE(0), 2);\n\n    buffer[7] = 0xc0;\n    strictEqual(buffer.readDoubleBE(0), 9.5e-322);\n    strictEqual(buffer.readDoubleLE(0), -2);\n\n    buffer[6] = 0x10;\n    buffer[7] = 0;\n    strictEqual(buffer.readDoubleBE(0), 2.0237e-320);\n    strictEqual(buffer.readDoubleLE(0), 2.2250738585072014e-308);\n\n    buffer[6] = 0;\n    strictEqual(buffer.readDoubleBE(0), 0);\n    strictEqual(buffer.readDoubleLE(0), 0);\n    ok(1 / buffer.readDoubleLE(0) >= 0);\n\n    buffer[7] = 0x80;\n    strictEqual(buffer.readDoubleBE(0), 6.3e-322);\n    strictEqual(buffer.readDoubleLE(0), -0);\n    ok(1 / buffer.readDoubleLE(0) < 0);\n\n    buffer[6] = 0xf0;\n    buffer[7] = 0x7f;\n    strictEqual(buffer.readDoubleBE(0), 3.0418e-319);\n    strictEqual(buffer.readDoubleLE(0), Infinity);\n\n    buffer[7] = 0xff;\n    strictEqual(buffer.readDoubleBE(0), 3.04814e-319);\n    strictEqual(buffer.readDoubleLE(0), -Infinity);\n\n    ['readDoubleLE', 'readDoubleBE'].forEach((fn) => {\n      // Verify that default offset works fine.\n      buffer[fn](undefined);\n      buffer[fn]();\n\n      ['', '0', null, {}, [], () => {}, true, false].forEach((off) => {\n        throws(() => buffer[fn](off), { code: 'ERR_INVALID_ARG_TYPE' });\n      });\n\n      [Infinity, -1, 1].forEach((offset) => {\n        throws(() => buffer[fn](offset), {\n          code: 'ERR_OUT_OF_RANGE',\n          name: 'RangeError',\n        });\n      });\n\n      throws(() => Buffer.alloc(1)[fn](1), {\n        code: 'ERR_BUFFER_OUT_OF_BOUNDS',\n        name: 'RangeError',\n        message: 'Attempt to access memory outside buffer bounds',\n      });\n\n      [NaN, 1.01].forEach((offset) => {\n        throws(() => buffer[fn](offset), {\n          code: 'ERR_OUT_OF_RANGE',\n          name: 'RangeError',\n          message:\n            'The value of \"offset\" is out of range. ' +\n            `It must be an integer. Received ${offset}`,\n        });\n      });\n    });\n  },\n};\n\nexport const readFloat = {\n  test(ctrl, env, ctx) {\n    // Test 32 bit float\n    const buffer = Buffer.alloc(4);\n\n    buffer[0] = 0;\n    buffer[1] = 0;\n    buffer[2] = 0x80;\n    buffer[3] = 0x3f;\n    strictEqual(buffer.readFloatBE(0), 4.600602988224807e-41);\n    strictEqual(buffer.readFloatLE(0), 1);\n\n    buffer[0] = 0;\n    buffer[1] = 0;\n    buffer[2] = 0;\n    buffer[3] = 0xc0;\n    strictEqual(buffer.readFloatBE(0), 2.6904930515036488e-43);\n    strictEqual(buffer.readFloatLE(0), -2);\n\n    buffer[0] = 0xff;\n    buffer[1] = 0xff;\n    buffer[2] = 0x7f;\n    buffer[3] = 0x7f;\n    ok(Number.isNaN(buffer.readFloatBE(0)));\n    strictEqual(buffer.readFloatLE(0), 3.4028234663852886e38);\n\n    buffer[0] = 0xab;\n    buffer[1] = 0xaa;\n    buffer[2] = 0xaa;\n    buffer[3] = 0x3e;\n    strictEqual(buffer.readFloatBE(0), -1.2126478207002966e-12);\n    strictEqual(buffer.readFloatLE(0), 0.3333333432674408);\n\n    buffer[0] = 0;\n    buffer[1] = 0;\n    buffer[2] = 0;\n    buffer[3] = 0;\n    strictEqual(buffer.readFloatBE(0), 0);\n    strictEqual(buffer.readFloatLE(0), 0);\n    ok(1 / buffer.readFloatLE(0) >= 0);\n\n    buffer[3] = 0x80;\n    strictEqual(buffer.readFloatBE(0), 1.793662034335766e-43);\n    strictEqual(buffer.readFloatLE(0), -0);\n    ok(1 / buffer.readFloatLE(0) < 0);\n\n    buffer[0] = 0;\n    buffer[1] = 0;\n    buffer[2] = 0x80;\n    buffer[3] = 0x7f;\n    strictEqual(buffer.readFloatBE(0), 4.609571298396486e-41);\n    strictEqual(buffer.readFloatLE(0), Infinity);\n\n    buffer[0] = 0;\n    buffer[1] = 0;\n    buffer[2] = 0x80;\n    buffer[3] = 0xff;\n    strictEqual(buffer.readFloatBE(0), 4.627507918739843e-41);\n    strictEqual(buffer.readFloatLE(0), -Infinity);\n\n    ['readFloatLE', 'readFloatBE'].forEach((fn) => {\n      // Verify that default offset works fine.\n      buffer[fn](undefined);\n      buffer[fn]();\n\n      ['', '0', null, {}, [], () => {}, true, false].forEach((off) => {\n        throws(() => buffer[fn](off), { code: 'ERR_INVALID_ARG_TYPE' });\n      });\n\n      [Infinity, -1, 1].forEach((offset) => {\n        throws(() => buffer[fn](offset), {\n          code: 'ERR_OUT_OF_RANGE',\n          name: 'RangeError',\n        });\n      });\n\n      throws(() => Buffer.alloc(1)[fn](1), {\n        code: 'ERR_BUFFER_OUT_OF_BOUNDS',\n        name: 'RangeError',\n        message: 'Attempt to access memory outside buffer bounds',\n      });\n\n      [NaN, 1.01].forEach((offset) => {\n        throws(() => buffer[fn](offset), {\n          code: 'ERR_OUT_OF_RANGE',\n          name: 'RangeError',\n          message:\n            'The value of \"offset\" is out of range. ' +\n            `It must be an integer. Received ${offset}`,\n        });\n      });\n    });\n  },\n};\n\nexport const readInt = {\n  test(ctrl, env, ctx) {\n    // Test OOB\n    {\n      const buffer = Buffer.alloc(4);\n\n      ['Int8', 'Int16BE', 'Int16LE', 'Int32BE', 'Int32LE'].forEach((fn) => {\n        // Verify that default offset works fine.\n        buffer[`read${fn}`](undefined);\n        buffer[`read${fn}`]();\n\n        ['', '0', null, {}, [], () => {}, true, false].forEach((o) => {\n          throws(() => buffer[`read${fn}`](o), {\n            code: 'ERR_INVALID_ARG_TYPE',\n            name: 'TypeError',\n          });\n        });\n\n        [Infinity, -1, -4294967295].forEach((offset) => {\n          throws(() => buffer[`read${fn}`](offset), {\n            code: 'ERR_OUT_OF_RANGE',\n            name: 'RangeError',\n          });\n        });\n\n        [NaN, 1.01].forEach((offset) => {\n          throws(() => buffer[`read${fn}`](offset), {\n            code: 'ERR_OUT_OF_RANGE',\n            name: 'RangeError',\n          });\n        });\n      });\n    }\n\n    // Test 8 bit signed integers\n    {\n      const data = Buffer.from([0x23, 0xab, 0x7c, 0xef]);\n\n      strictEqual(data.readInt8(0), 0x23);\n\n      data[0] = 0xff;\n      strictEqual(data.readInt8(0), -1);\n\n      data[0] = 0x87;\n      strictEqual(data.readInt8(0), -121);\n      strictEqual(data.readInt8(1), -85);\n      strictEqual(data.readInt8(2), 124);\n      strictEqual(data.readInt8(3), -17);\n    }\n\n    // Test 16 bit integers\n    {\n      const buffer = Buffer.from([0x16, 0x79, 0x65, 0x6e, 0x69, 0x78]);\n\n      strictEqual(buffer.readInt16BE(0), 0x1679);\n      strictEqual(buffer.readInt16LE(0), 0x7916);\n\n      buffer[0] = 0xff;\n      buffer[1] = 0x80;\n      strictEqual(buffer.readInt16BE(0), -128);\n      strictEqual(buffer.readInt16LE(0), -32513);\n\n      buffer[0] = 0x77;\n      buffer[1] = 0x65;\n      strictEqual(buffer.readInt16BE(0), 0x7765);\n      strictEqual(buffer.readInt16BE(1), 0x6565);\n      strictEqual(buffer.readInt16BE(2), 0x656e);\n      strictEqual(buffer.readInt16BE(3), 0x6e69);\n      strictEqual(buffer.readInt16BE(4), 0x6978);\n      strictEqual(buffer.readInt16LE(0), 0x6577);\n      strictEqual(buffer.readInt16LE(1), 0x6565);\n      strictEqual(buffer.readInt16LE(2), 0x6e65);\n      strictEqual(buffer.readInt16LE(3), 0x696e);\n      strictEqual(buffer.readInt16LE(4), 0x7869);\n    }\n\n    // Test 32 bit integers\n    {\n      const buffer = Buffer.from([0x43, 0x53, 0x16, 0x79, 0x36, 0x17]);\n\n      strictEqual(buffer.readInt32BE(0), 0x43531679);\n      strictEqual(buffer.readInt32LE(0), 0x79165343);\n\n      buffer[0] = 0xff;\n      buffer[1] = 0xfe;\n      buffer[2] = 0xef;\n      buffer[3] = 0xfa;\n      strictEqual(buffer.readInt32BE(0), -69638);\n      strictEqual(buffer.readInt32LE(0), -84934913);\n\n      buffer[0] = 0x42;\n      buffer[1] = 0xc3;\n      buffer[2] = 0x95;\n      buffer[3] = 0xa9;\n      strictEqual(buffer.readInt32BE(0), 0x42c395a9);\n      strictEqual(buffer.readInt32BE(1), -1013601994);\n      strictEqual(buffer.readInt32BE(2), -1784072681);\n      strictEqual(buffer.readInt32LE(0), -1449802942);\n      strictEqual(buffer.readInt32LE(1), 917083587);\n      strictEqual(buffer.readInt32LE(2), 389458325);\n    }\n\n    // Test Int\n    {\n      const buffer = Buffer.from([\n        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,\n      ]);\n\n      strictEqual(buffer.readIntLE(0, 1), 0x01);\n      strictEqual(buffer.readIntBE(0, 1), 0x01);\n      strictEqual(buffer.readIntLE(0, 3), 0x030201);\n      strictEqual(buffer.readIntBE(0, 3), 0x010203);\n      strictEqual(buffer.readIntLE(0, 5), 0x0504030201);\n      strictEqual(buffer.readIntBE(0, 5), 0x0102030405);\n      strictEqual(buffer.readIntLE(0, 6), 0x060504030201);\n      strictEqual(buffer.readIntBE(0, 6), 0x010203040506);\n      strictEqual(buffer.readIntLE(1, 6), 0x070605040302);\n      strictEqual(buffer.readIntBE(1, 6), 0x020304050607);\n      strictEqual(buffer.readIntLE(2, 6), 0x080706050403);\n      strictEqual(buffer.readIntBE(2, 6), 0x030405060708);\n\n      // Check byteLength.\n      ['readIntBE', 'readIntLE'].forEach((fn) => {\n        ['', '0', null, {}, [], () => {}, true, false, undefined].forEach(\n          (len) => {\n            throws(() => buffer[fn](0, len), { name: 'RangeError' });\n          }\n        );\n\n        [Infinity, -1].forEach((byteLength) => {\n          throws(() => buffer[fn](0, byteLength), {\n            name: 'RangeError',\n          });\n        });\n\n        [NaN, 1.01].forEach((byteLength) => {\n          throws(() => buffer[fn](0, byteLength), {\n            name: 'RangeError',\n          });\n        });\n      });\n\n      // Test 1 to 6 bytes.\n      for (let i = 1; i <= 6; i++) {\n        ['readIntBE', 'readIntLE'].forEach((fn) => {\n          ['', '0', null, {}, [], () => {}, true, false, undefined].forEach(\n            (o) => {\n              throws(() => buffer[fn](o, i), {\n                name: 'TypeError',\n              });\n            }\n          );\n\n          [Infinity, -1, -4294967295].forEach((offset) => {\n            throws(() => buffer[fn](offset, i), {\n              name: 'RangeError',\n            });\n          });\n\n          [NaN, 1.01].forEach((offset) => {\n            throws(() => buffer[fn](offset, i), {\n              name: 'RangeError',\n            });\n          });\n        });\n      }\n    }\n  },\n};\n\nexport const readUint = {\n  test(ctrl, env, ctx) {\n    // Test OOB\n    {\n      const buffer = Buffer.alloc(4);\n\n      ['UInt8', 'UInt16BE', 'UInt16LE', 'UInt32BE', 'UInt32LE'].forEach(\n        (fn) => {\n          // Verify that default offset works fine.\n          buffer[`read${fn}`](undefined);\n          buffer[`read${fn}`]();\n\n          ['', '0', null, {}, [], () => {}, true, false].forEach((o) => {\n            throws(() => buffer[`read${fn}`](o), {\n              code: 'ERR_INVALID_ARG_TYPE',\n              name: 'TypeError',\n            });\n          });\n\n          [Infinity, -1, -4294967295].forEach((offset) => {\n            throws(() => buffer[`read${fn}`](offset), {\n              code: 'ERR_OUT_OF_RANGE',\n              name: 'RangeError',\n            });\n          });\n\n          [NaN, 1.01].forEach((offset) => {\n            throws(() => buffer[`read${fn}`](offset), {\n              code: 'ERR_OUT_OF_RANGE',\n              name: 'RangeError',\n            });\n          });\n        }\n      );\n    }\n\n    // Test 8 bit unsigned integers\n    {\n      const data = Buffer.from([0xff, 0x2a, 0x2a, 0x2a]);\n      strictEqual(data.readUInt8(0), 255);\n      strictEqual(data.readUInt8(1), 42);\n      strictEqual(data.readUInt8(2), 42);\n      strictEqual(data.readUInt8(3), 42);\n    }\n\n    // Test 16 bit unsigned integers\n    {\n      const data = Buffer.from([0x00, 0x2a, 0x42, 0x3f]);\n      strictEqual(data.readUInt16BE(0), 0x2a);\n      strictEqual(data.readUInt16BE(1), 0x2a42);\n      strictEqual(data.readUInt16BE(2), 0x423f);\n      strictEqual(data.readUInt16LE(0), 0x2a00);\n      strictEqual(data.readUInt16LE(1), 0x422a);\n      strictEqual(data.readUInt16LE(2), 0x3f42);\n\n      data[0] = 0xfe;\n      data[1] = 0xfe;\n      strictEqual(data.readUInt16BE(0), 0xfefe);\n      strictEqual(data.readUInt16LE(0), 0xfefe);\n    }\n\n    // Test 32 bit unsigned integers\n    {\n      const data = Buffer.from([0x32, 0x65, 0x42, 0x56, 0x23, 0xff]);\n      strictEqual(data.readUInt32BE(0), 0x32654256);\n      strictEqual(data.readUInt32BE(1), 0x65425623);\n      strictEqual(data.readUInt32BE(2), 0x425623ff);\n      strictEqual(data.readUInt32LE(0), 0x56426532);\n      strictEqual(data.readUInt32LE(1), 0x23564265);\n      strictEqual(data.readUInt32LE(2), 0xff235642);\n    }\n\n    // Test UInt\n    {\n      const buffer = Buffer.from([\n        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,\n      ]);\n\n      strictEqual(buffer.readUIntLE(0, 1), 0x01);\n      strictEqual(buffer.readUIntBE(0, 1), 0x01);\n      strictEqual(buffer.readUIntLE(0, 3), 0x030201);\n      strictEqual(buffer.readUIntBE(0, 3), 0x010203);\n      strictEqual(buffer.readUIntLE(0, 5), 0x0504030201);\n      strictEqual(buffer.readUIntBE(0, 5), 0x0102030405);\n      strictEqual(buffer.readUIntLE(0, 6), 0x060504030201);\n      strictEqual(buffer.readUIntBE(0, 6), 0x010203040506);\n      strictEqual(buffer.readUIntLE(1, 6), 0x070605040302);\n      strictEqual(buffer.readUIntBE(1, 6), 0x020304050607);\n      strictEqual(buffer.readUIntLE(2, 6), 0x080706050403);\n      strictEqual(buffer.readUIntBE(2, 6), 0x030405060708);\n\n      // Check byteLength.\n      ['readUIntBE', 'readUIntLE'].forEach((fn) => {\n        ['', '0', null, {}, [], () => {}, true, false, undefined].forEach(\n          (len) => {\n            throws(() => buffer[fn](0, len), { name: 'RangeError' });\n          }\n        );\n\n        [Infinity, -1].forEach((byteLength) => {\n          throws(() => buffer[fn](0, byteLength), {\n            name: 'RangeError',\n          });\n        });\n\n        [NaN, 1.01].forEach((byteLength) => {\n          throws(() => buffer[fn](0, byteLength), {\n            name: 'RangeError',\n          });\n        });\n      });\n\n      // Test 1 to 6 bytes.\n      for (let i = 1; i <= 6; i++) {\n        ['readUIntBE', 'readUIntLE'].forEach((fn) => {\n          ['', '0', null, {}, [], () => {}, true, false, undefined].forEach(\n            (o) => {\n              throws(() => buffer[fn](o, i), {\n                name: 'TypeError',\n              });\n            }\n          );\n\n          [Infinity, -1, -4294967295].forEach((offset) => {\n            throws(() => buffer[fn](offset, i), {\n              name: 'RangeError',\n            });\n          });\n\n          [NaN, 1.01].forEach((offset) => {\n            throws(() => buffer[fn](offset, i), {\n              name: 'RangeError',\n            });\n          });\n        });\n      }\n    }\n  },\n};\n\nexport const sharedArrayBuffer = {\n  test(ctrl, env, ctx) {\n    const sab = new SharedArrayBuffer(24);\n    const arr1 = new Uint16Array(sab);\n    const arr2 = new Uint16Array(12);\n    arr2[0] = 5000;\n    arr1[0] = 5000;\n    arr1[1] = 4000;\n    arr2[1] = 4000;\n\n    const arr_buf = Buffer.from(arr1.buffer);\n    const ar_buf = Buffer.from(arr2.buffer);\n\n    deepStrictEqual(arr_buf, ar_buf);\n\n    arr1[1] = 6000;\n    arr2[1] = 6000;\n\n    deepStrictEqual(arr_buf, ar_buf);\n\n    // Checks for calling Buffer.byteLength on a SharedArrayBuffer.\n    strictEqual(Buffer.byteLength(sab), sab.byteLength);\n\n    Buffer.from({ buffer: sab }); // Should not throw.\n  },\n};\n\nexport const slice = {\n  test(ctrl, env, ctx) {\n    strictEqual(Buffer.from('hello', 'utf8').slice(0, 0).length, 0);\n    strictEqual(Buffer('hello', 'utf8').slice(0, 0).length, 0);\n\n    const buf = Buffer.from('0123456789', 'utf8');\n    const expectedSameBufs = [\n      [buf.slice(-10, 10), Buffer.from('0123456789', 'utf8')],\n      [buf.slice(-20, 10), Buffer.from('0123456789', 'utf8')],\n      [buf.slice(-20, -10), Buffer.from('', 'utf8')],\n      [buf.slice(), Buffer.from('0123456789', 'utf8')],\n      [buf.slice(0), Buffer.from('0123456789', 'utf8')],\n      [buf.slice(0, 0), Buffer.from('', 'utf8')],\n      [buf.slice(undefined), Buffer.from('0123456789', 'utf8')],\n      [buf.slice('foobar'), Buffer.from('0123456789', 'utf8')],\n      [buf.slice(undefined, undefined), Buffer.from('0123456789', 'utf8')],\n      [buf.slice(2), Buffer.from('23456789', 'utf8')],\n      [buf.slice(5), Buffer.from('56789', 'utf8')],\n      [buf.slice(10), Buffer.from('', 'utf8')],\n      [buf.slice(5, 8), Buffer.from('567', 'utf8')],\n      [buf.slice(8, -1), Buffer.from('8', 'utf8')],\n      [buf.slice(-10), Buffer.from('0123456789', 'utf8')],\n      [buf.slice(0, -9), Buffer.from('0', 'utf8')],\n      [buf.slice(0, -10), Buffer.from('', 'utf8')],\n      [buf.slice(0, -1), Buffer.from('012345678', 'utf8')],\n      [buf.slice(2, -2), Buffer.from('234567', 'utf8')],\n      [buf.slice(0, 65536), Buffer.from('0123456789', 'utf8')],\n      [buf.slice(65536, 0), Buffer.from('', 'utf8')],\n      [buf.slice(-5, -8), Buffer.from('', 'utf8')],\n      [buf.slice(-5, -3), Buffer.from('56', 'utf8')],\n      [buf.slice(-10, 10), Buffer.from('0123456789', 'utf8')],\n      [buf.slice('0', '1'), Buffer.from('0', 'utf8')],\n      [buf.slice('-5', '10'), Buffer.from('56789', 'utf8')],\n      [buf.slice('-10', '10'), Buffer.from('0123456789', 'utf8')],\n      [buf.slice('-10', '-5'), Buffer.from('01234', 'utf8')],\n      [buf.slice('-10', '-0'), Buffer.from('', 'utf8')],\n      [buf.slice('111'), Buffer.from('', 'utf8')],\n      [buf.slice('0', '-111'), Buffer.from('', 'utf8')],\n    ];\n\n    for (let i = 0, s = buf.toString(); i < buf.length; ++i) {\n      expectedSameBufs.push(\n        [buf.slice(i), Buffer.from(s.slice(i))],\n        [buf.slice(0, i), Buffer.from(s.slice(0, i))],\n        [buf.slice(-i), Buffer.from(s.slice(-i))],\n        [buf.slice(0, -i), Buffer.from(s.slice(0, -i))]\n      );\n    }\n\n    expectedSameBufs.forEach(([buf1, buf2]) => {\n      strictEqual(Buffer.compare(buf1, buf2), 0);\n    });\n\n    const utf16Buf = Buffer.from('0123456789', 'utf16le');\n    deepStrictEqual(utf16Buf.slice(0, 6), Buffer.from('012', 'utf16le'));\n    // Try to slice a zero length Buffer.\n    // See https://github.com/joyent/node/issues/5881\n    strictEqual(Buffer.alloc(0).slice(0, 1).length, 0);\n\n    {\n      // Single argument slice\n      strictEqual(\n        Buffer.from('abcde', 'utf8').slice(1).toString('utf8'),\n        'bcde'\n      );\n    }\n\n    // slice(0,0).length === 0\n    strictEqual(Buffer.from('hello', 'utf8').slice(0, 0).length, 0);\n\n    {\n      // Regression tests for https://github.com/nodejs/node/issues/9096\n      const buf = Buffer.from('abcd', 'utf8');\n      strictEqual(buf.slice(buf.length / 3).toString('utf8'), 'bcd');\n      strictEqual(buf.slice(buf.length / 3, buf.length).toString(), 'bcd');\n    }\n\n    {\n      const buf = Buffer.from('abcdefg', 'utf8');\n      strictEqual(\n        buf.slice(-(-1 >>> 0) - 1).toString('utf8'),\n        buf.toString('utf8')\n      );\n    }\n\n    {\n      const buf = Buffer.from('abc', 'utf8');\n      strictEqual(buf.slice(-0.5).toString('utf8'), buf.toString('utf8'));\n    }\n\n    {\n      const buf = Buffer.from([\n        1, 29, 0, 0, 1, 143, 216, 162, 92, 254, 248, 63, 0, 0, 0, 18, 184, 6, 0,\n        175, 29, 0, 8, 11, 1, 0, 0,\n      ]);\n      const chunk1 = Buffer.from([\n        1, 29, 0, 0, 1, 143, 216, 162, 92, 254, 248, 63, 0,\n      ]);\n      const chunk2 = Buffer.from([\n        0, 0, 18, 184, 6, 0, 175, 29, 0, 8, 11, 1, 0, 0,\n      ]);\n      const middle = buf.length / 2;\n\n      deepStrictEqual(buf.slice(0, middle), chunk1);\n      deepStrictEqual(buf.slice(middle), chunk2);\n    }\n  },\n};\n\nexport const swap = {\n  test(ctrl, env, ctx) {\n    // Test buffers small enough to use the JS implementation\n    {\n      const buf = Buffer.from([\n        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,\n        0x0d, 0x0e, 0x0f, 0x10,\n      ]);\n\n      strictEqual(buf, buf.swap16());\n      deepStrictEqual(\n        buf,\n        Buffer.from([\n          0x02, 0x01, 0x04, 0x03, 0x06, 0x05, 0x08, 0x07, 0x0a, 0x09, 0x0c,\n          0x0b, 0x0e, 0x0d, 0x10, 0x0f,\n        ])\n      );\n      buf.swap16(); // restore\n\n      strictEqual(buf, buf.swap32());\n      deepStrictEqual(\n        buf,\n        Buffer.from([\n          0x04, 0x03, 0x02, 0x01, 0x08, 0x07, 0x06, 0x05, 0x0c, 0x0b, 0x0a,\n          0x09, 0x10, 0x0f, 0x0e, 0x0d,\n        ])\n      );\n      buf.swap32(); // restore\n\n      strictEqual(buf, buf.swap64());\n      deepStrictEqual(\n        buf,\n        Buffer.from([\n          0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x10, 0x0f, 0x0e,\n          0x0d, 0x0c, 0x0b, 0x0a, 0x09,\n        ])\n      );\n    }\n\n    // Operates in-place\n    {\n      const buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7]);\n      buf.slice(1, 5).swap32();\n      deepStrictEqual(buf, Buffer.from([0x1, 0x5, 0x4, 0x3, 0x2, 0x6, 0x7]));\n      buf.slice(1, 5).swap16();\n      deepStrictEqual(buf, Buffer.from([0x1, 0x4, 0x5, 0x2, 0x3, 0x6, 0x7]));\n\n      // Length assertions\n      const re16 = /Buffer size must be a multiple of 16-bits/;\n      const re32 = /Buffer size must be a multiple of 32-bits/;\n      const re64 = /Buffer size must be a multiple of 64-bits/;\n\n      throws(() => Buffer.from(buf).swap16(), re16);\n      throws(() => Buffer.alloc(1025).swap16(), re16);\n      throws(() => Buffer.from(buf).swap32(), re32);\n      throws(() => buf.slice(1, 3).swap32(), re32);\n      throws(() => Buffer.alloc(1025).swap32(), re32);\n      throws(() => buf.slice(1, 3).swap64(), re64);\n      throws(() => Buffer.alloc(1025).swap64(), re64);\n    }\n\n    {\n      const buf = Buffer.from([\n        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,\n        0x0d, 0x0e, 0x0f, 0x10, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,\n        0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,\n      ]);\n\n      buf.slice(2, 18).swap64();\n\n      deepStrictEqual(\n        buf,\n        Buffer.from([\n          0x01, 0x02, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02,\n          0x01, 0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x03, 0x04, 0x05, 0x06,\n          0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,\n        ])\n      );\n    }\n\n    // Force use of native code (Buffer size above threshold limit for js impl)\n    {\n      const bufData = new Uint32Array(256).fill(0x04030201);\n      const buf = Buffer.from(bufData.buffer, bufData.byteOffset);\n      const otherBufData = new Uint32Array(256).fill(0x03040102);\n      const otherBuf = Buffer.from(\n        otherBufData.buffer,\n        otherBufData.byteOffset\n      );\n      buf.swap16();\n      deepStrictEqual(buf, otherBuf);\n    }\n\n    {\n      const bufData = new Uint32Array(256).fill(0x04030201);\n      const buf = Buffer.from(bufData.buffer);\n      const otherBufData = new Uint32Array(256).fill(0x01020304);\n      const otherBuf = Buffer.from(\n        otherBufData.buffer,\n        otherBufData.byteOffset\n      );\n      buf.swap32();\n      deepStrictEqual(buf, otherBuf);\n    }\n\n    {\n      const bufData = new Uint8Array(256 * 8);\n      const otherBufData = new Uint8Array(256 * 8);\n      for (let i = 0; i < bufData.length; i++) {\n        bufData[i] = i % 8;\n        otherBufData[otherBufData.length - i - 1] = i % 8;\n      }\n      const buf = Buffer.from(bufData.buffer, bufData.byteOffset);\n      const otherBuf = Buffer.from(\n        otherBufData.buffer,\n        otherBufData.byteOffset\n      );\n      buf.swap64();\n      deepStrictEqual(buf, otherBuf);\n    }\n\n    // Test native code with buffers that are not memory-aligned\n    {\n      const bufData = new Uint8Array(256 * 8);\n      const otherBufData = new Uint8Array(256 * 8 - 2);\n      for (let i = 0; i < bufData.length; i++) {\n        bufData[i] = i % 2;\n      }\n      for (let i = 1; i < otherBufData.length; i++) {\n        otherBufData[otherBufData.length - i] = (i + 1) % 2;\n      }\n      const buf = Buffer.from(bufData.buffer, bufData.byteOffset);\n      // 0|1 0|1 0|1...\n      const otherBuf = Buffer.from(\n        otherBufData.buffer,\n        otherBufData.byteOffset\n      );\n      // 0|0 1|0 1|0...\n\n      buf.slice(1, buf.length - 1).swap16();\n      deepStrictEqual(buf.slice(0, otherBuf.length), otherBuf);\n    }\n\n    {\n      const bufData = new Uint8Array(256 * 8);\n      const otherBufData = new Uint8Array(256 * 8 - 4);\n      for (let i = 0; i < bufData.length; i++) {\n        bufData[i] = i % 4;\n      }\n      for (let i = 1; i < otherBufData.length; i++) {\n        otherBufData[otherBufData.length - i] = (i + 1) % 4;\n      }\n      const buf = Buffer.from(bufData.buffer, bufData.byteOffset);\n      // 0|1 2 3 0|1 2 3...\n      const otherBuf = Buffer.from(\n        otherBufData.buffer,\n        otherBufData.byteOffset\n      );\n      // 0|0 3 2 1|0 3 2...\n\n      buf.slice(1, buf.length - 3).swap32();\n      deepStrictEqual(buf.slice(0, otherBuf.length), otherBuf);\n    }\n\n    {\n      const bufData = new Uint8Array(256 * 8);\n      const otherBufData = new Uint8Array(256 * 8 - 8);\n      for (let i = 0; i < bufData.length; i++) {\n        bufData[i] = i % 8;\n      }\n      for (let i = 1; i < otherBufData.length; i++) {\n        otherBufData[otherBufData.length - i] = (i + 1) % 8;\n      }\n      const buf = Buffer.from(bufData.buffer, bufData.byteOffset);\n      // 0|1 2 3 4 5 6 7 0|1 2 3 4...\n      const otherBuf = Buffer.from(\n        otherBufData.buffer,\n        otherBufData.byteOffset\n      );\n      // 0|0 7 6 5 4 3 2 1|0 7 6 5...\n\n      buf.slice(1, buf.length - 7).swap64();\n      deepStrictEqual(buf.slice(0, otherBuf.length), otherBuf);\n    }\n  },\n};\n\nexport const json = {\n  test(ctrl, env, ctx) {\n    {\n      strictEqual(\n        JSON.stringify(Buffer.alloc(0)),\n        '{\"type\":\"Buffer\",\"data\":[]}'\n      );\n      strictEqual(\n        JSON.stringify(Buffer.from([1, 2, 3, 4])),\n        '{\"type\":\"Buffer\",\"data\":[1,2,3,4]}'\n      );\n    }\n\n    // issue GH-7849\n    {\n      const buf = Buffer.from('test');\n      const json = JSON.stringify(buf);\n      const obj = JSON.parse(json);\n      const copy = Buffer.from(obj);\n\n      deepStrictEqual(buf, copy);\n    }\n\n    // GH-5110\n    {\n      const buffer = Buffer.from('test');\n      const string = JSON.stringify(buffer);\n\n      strictEqual(string, '{\"type\":\"Buffer\",\"data\":[116,101,115,116]}');\n\n      function receiver(key, value) {\n        return value && value.type === 'Buffer'\n          ? Buffer.from(value.data)\n          : value;\n      }\n\n      deepStrictEqual(buffer, JSON.parse(string, receiver));\n    }\n  },\n};\n\nexport const writeUint8 = {\n  test(ctrl, env, ctx) {\n    {\n      // OOB\n      const data = Buffer.alloc(8);\n      ['UInt8', 'UInt16BE', 'UInt16LE', 'UInt32BE', 'UInt32LE'].forEach(\n        (fn) => {\n          // Verify that default offset works fine.\n          data[`write${fn}`](23, undefined);\n          data[`write${fn}`](23);\n\n          ['', '0', null, {}, [], () => {}, true, false].forEach((o) => {\n            throws(() => data[`write${fn}`](23, o), { name: 'TypeError' });\n          });\n\n          [NaN, Infinity, -1, 1.01].forEach((o) => {\n            throws(() => data[`write${fn}`](23, o), { name: 'RangeError' });\n          });\n        }\n      );\n    }\n\n    {\n      // Test 8 bit\n      const data = Buffer.alloc(4);\n\n      data.writeUInt8(23, 0);\n      data.writeUInt8(23, 1);\n      data.writeUInt8(23, 2);\n      data.writeUInt8(23, 3);\n      ok(data.equals(new Uint8Array([23, 23, 23, 23])));\n\n      data.writeUInt8(23, 0);\n      data.writeUInt8(23, 1);\n      data.writeUInt8(23, 2);\n      data.writeUInt8(23, 3);\n      ok(data.equals(new Uint8Array([23, 23, 23, 23])));\n\n      data.writeUInt8(255, 0);\n      strictEqual(data[0], 255);\n\n      data.writeUInt8(255, 0);\n      strictEqual(data[0], 255);\n    }\n\n    // Test 16 bit\n    {\n      let value = 0x2343;\n      const data = Buffer.alloc(4);\n\n      data.writeUInt16BE(value, 0);\n      ok(data.equals(new Uint8Array([0x23, 0x43, 0, 0])));\n\n      data.writeUInt16BE(value, 1);\n      ok(data.equals(new Uint8Array([0x23, 0x23, 0x43, 0])));\n\n      data.writeUInt16BE(value, 2);\n      ok(data.equals(new Uint8Array([0x23, 0x23, 0x23, 0x43])));\n\n      data.writeUInt16LE(value, 0);\n      ok(data.equals(new Uint8Array([0x43, 0x23, 0x23, 0x43])));\n\n      data.writeUInt16LE(value, 1);\n      ok(data.equals(new Uint8Array([0x43, 0x43, 0x23, 0x43])));\n\n      data.writeUInt16LE(value, 2);\n      ok(data.equals(new Uint8Array([0x43, 0x43, 0x43, 0x23])));\n\n      value = 0xff80;\n      data.writeUInt16LE(value, 0);\n      ok(data.equals(new Uint8Array([0x80, 0xff, 0x43, 0x23])));\n\n      data.writeUInt16BE(value, 0);\n      ok(data.equals(new Uint8Array([0xff, 0x80, 0x43, 0x23])));\n\n      value = 0xfffff;\n      ['writeUInt16BE', 'writeUInt16LE'].forEach((fn) => {\n        throws(() => data[fn](value, 0), {\n          name: 'RangeError',\n        });\n      });\n    }\n\n    // Test 32 bit\n    {\n      const data = Buffer.alloc(6);\n      const value = 0xe7f90a6d;\n\n      data.writeUInt32BE(value, 0);\n      ok(data.equals(new Uint8Array([0xe7, 0xf9, 0x0a, 0x6d, 0, 0])));\n\n      data.writeUInt32BE(value, 1);\n      ok(data.equals(new Uint8Array([0xe7, 0xe7, 0xf9, 0x0a, 0x6d, 0])));\n\n      data.writeUInt32BE(value, 2);\n      ok(data.equals(new Uint8Array([0xe7, 0xe7, 0xe7, 0xf9, 0x0a, 0x6d])));\n\n      data.writeUInt32LE(value, 0);\n      ok(data.equals(new Uint8Array([0x6d, 0x0a, 0xf9, 0xe7, 0x0a, 0x6d])));\n\n      data.writeUInt32LE(value, 1);\n      ok(data.equals(new Uint8Array([0x6d, 0x6d, 0x0a, 0xf9, 0xe7, 0x6d])));\n\n      data.writeUInt32LE(value, 2);\n      ok(data.equals(new Uint8Array([0x6d, 0x6d, 0x6d, 0x0a, 0xf9, 0xe7])));\n    }\n\n    // Test 48 bit\n    {\n      const value = 0x1234567890ab;\n      const data = Buffer.allocUnsafe(6);\n      data.writeUIntBE(value, 0, 6);\n      ok(data.equals(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90, 0xab])));\n\n      data.writeUIntLE(value, 0, 6);\n      ok(data.equals(new Uint8Array([0xab, 0x90, 0x78, 0x56, 0x34, 0x12])));\n    }\n\n    // Test UInt\n    {\n      const data = Buffer.alloc(8);\n      let val = 0x100;\n\n      // Check byteLength.\n      ['writeUIntBE', 'writeUIntLE'].forEach((fn) => {\n        ['', '0', null, {}, [], () => {}, true, false, undefined].forEach(\n          (bl) => {\n            throws(() => data[fn](23, 0, bl), { name: 'RangeError' });\n          }\n        );\n\n        [Infinity, -1].forEach((byteLength) => {\n          throws(() => data[fn](23, 0, byteLength), {\n            name: 'RangeError',\n          });\n        });\n\n        [NaN, 1.01].forEach((byteLength) => {\n          throws(() => data[fn](42, 0, byteLength), {\n            name: 'RangeError',\n          });\n        });\n      });\n\n      // Test 1 to 6 bytes.\n      for (let i = 1; i <= 6; i++) {\n        const range = i < 5 ? `= ${val - 1}` : ` 2 ** ${i * 8}`;\n        const received =\n          i > 4 ? String(val).replace(/(\\d)(?=(\\d\\d\\d)+(?!\\d))/g, '$1_') : val;\n        ['writeUIntBE', 'writeUIntLE'].forEach((fn) => {\n          throws(\n            () => {\n              data[fn](val, 0, i);\n            },\n            {\n              name: 'RangeError',\n            }\n          );\n\n          ['', '0', null, {}, [], () => {}, true, false].forEach((o) => {\n            throws(() => data[fn](23, o, i), {\n              name: 'TypeError',\n            });\n          });\n\n          [Infinity, -1, -4294967295].forEach((offset) => {\n            throws(() => data[fn](val - 1, offset, i), {\n              name: 'RangeError',\n            });\n          });\n\n          [NaN, 1.01].forEach((offset) => {\n            throws(() => data[fn](val - 1, offset, i), {\n              name: 'RangeError',\n            });\n          });\n        });\n\n        val *= 0x100;\n      }\n    }\n\n    for (const fn of [\n      'UInt8',\n      'UInt16LE',\n      'UInt16BE',\n      'UInt32LE',\n      'UInt32BE',\n      'UIntLE',\n      'UIntBE',\n      'BigUInt64LE',\n      'BigUInt64BE',\n    ]) {\n      const p = Buffer.prototype;\n      const lowerFn = fn.replace(/UInt/, 'Uint');\n      strictEqual(p[`write${fn}`], p[`write${lowerFn}`]);\n      strictEqual(p[`read${fn}`], p[`read${lowerFn}`]);\n    }\n  },\n};\n\nexport const writeInt = {\n  test(ctrl, env, ctx) {\n    const errorOutOfBounds = {\n      name: 'RangeError',\n    };\n\n    // Test 8 bit\n    {\n      const buffer = Buffer.alloc(2);\n\n      buffer.writeInt8(0x23, 0);\n      buffer.writeInt8(-5, 1);\n      ok(buffer.equals(new Uint8Array([0x23, 0xfb])));\n\n      /* Make sure we handle min/max correctly */\n      buffer.writeInt8(0x7f, 0);\n      buffer.writeInt8(-0x80, 1);\n      ok(buffer.equals(new Uint8Array([0x7f, 0x80])));\n\n      throws(() => {\n        buffer.writeInt8(0x7f + 1, 0);\n      }, errorOutOfBounds);\n      throws(() => {\n        buffer.writeInt8(-0x80 - 1, 0);\n      }, errorOutOfBounds);\n\n      // Verify that default offset works fine.\n      buffer.writeInt8(23, undefined);\n      buffer.writeInt8(23);\n\n      ['', '0', null, {}, [], () => {}, true, false].forEach((off) => {\n        throws(() => buffer.writeInt8(23, off), { name: 'TypeError' });\n      });\n\n      [NaN, Infinity, -1, 1.01].forEach((off) => {\n        throws(() => buffer.writeInt8(23, off), { name: 'RangeError' });\n      });\n    }\n\n    // Test 16 bit\n    {\n      const buffer = Buffer.alloc(4);\n\n      buffer.writeInt16BE(0x0023, 0);\n      buffer.writeInt16LE(0x0023, 2);\n      ok(buffer.equals(new Uint8Array([0x00, 0x23, 0x23, 0x00])));\n\n      buffer.writeInt16BE(-5, 0);\n      buffer.writeInt16LE(-5, 2);\n      ok(buffer.equals(new Uint8Array([0xff, 0xfb, 0xfb, 0xff])));\n\n      buffer.writeInt16BE(-1679, 0);\n      buffer.writeInt16LE(-1679, 2);\n      ok(buffer.equals(new Uint8Array([0xf9, 0x71, 0x71, 0xf9])));\n\n      /* Make sure we handle min/max correctly */\n      buffer.writeInt16BE(0x7fff, 0);\n      buffer.writeInt16BE(-0x8000, 2);\n      ok(buffer.equals(new Uint8Array([0x7f, 0xff, 0x80, 0x00])));\n\n      buffer.writeInt16LE(0x7fff, 0);\n      buffer.writeInt16LE(-0x8000, 2);\n      ok(buffer.equals(new Uint8Array([0xff, 0x7f, 0x00, 0x80])));\n\n      ['writeInt16BE', 'writeInt16LE'].forEach((fn) => {\n        // Verify that default offset works fine.\n        buffer[fn](23, undefined);\n        buffer[fn](23);\n\n        throws(() => {\n          buffer[fn](0x7fff + 1, 0);\n        }, errorOutOfBounds);\n        throws(() => {\n          buffer[fn](-0x8000 - 1, 0);\n        }, errorOutOfBounds);\n\n        ['', '0', null, {}, [], () => {}, true, false].forEach((off) => {\n          throws(() => buffer[fn](23, off), { code: 'ERR_INVALID_ARG_TYPE' });\n        });\n\n        [NaN, Infinity, -1, 1.01].forEach((off) => {\n          throws(() => buffer[fn](23, off), { code: 'ERR_OUT_OF_RANGE' });\n        });\n      });\n    }\n\n    // Test 32 bit\n    {\n      const buffer = Buffer.alloc(8);\n\n      buffer.writeInt32BE(0x23, 0);\n      buffer.writeInt32LE(0x23, 4);\n      ok(\n        buffer.equals(\n          new Uint8Array([0x00, 0x00, 0x00, 0x23, 0x23, 0x00, 0x00, 0x00])\n        )\n      );\n\n      buffer.writeInt32BE(-5, 0);\n      buffer.writeInt32LE(-5, 4);\n      ok(\n        buffer.equals(\n          new Uint8Array([0xff, 0xff, 0xff, 0xfb, 0xfb, 0xff, 0xff, 0xff])\n        )\n      );\n\n      buffer.writeInt32BE(-805306713, 0);\n      buffer.writeInt32LE(-805306713, 4);\n      ok(\n        buffer.equals(\n          new Uint8Array([0xcf, 0xff, 0xfe, 0xa7, 0xa7, 0xfe, 0xff, 0xcf])\n        )\n      );\n\n      /* Make sure we handle min/max correctly */\n      buffer.writeInt32BE(0x7fffffff, 0);\n      buffer.writeInt32BE(-0x80000000, 4);\n      ok(\n        buffer.equals(\n          new Uint8Array([0x7f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00])\n        )\n      );\n\n      buffer.writeInt32LE(0x7fffffff, 0);\n      buffer.writeInt32LE(-0x80000000, 4);\n      ok(\n        buffer.equals(\n          new Uint8Array([0xff, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x80])\n        )\n      );\n\n      ['writeInt32BE', 'writeInt32LE'].forEach((fn) => {\n        // Verify that default offset works fine.\n        buffer[fn](23, undefined);\n        buffer[fn](23);\n\n        throws(() => {\n          buffer[fn](0x7fffffff + 1, 0);\n        }, errorOutOfBounds);\n        throws(() => {\n          buffer[fn](-0x80000000 - 1, 0);\n        }, errorOutOfBounds);\n\n        ['', '0', null, {}, [], () => {}, true, false].forEach((off) => {\n          throws(() => buffer[fn](23, off), { code: 'ERR_INVALID_ARG_TYPE' });\n        });\n\n        [NaN, Infinity, -1, 1.01].forEach((off) => {\n          throws(() => buffer[fn](23, off), { code: 'ERR_OUT_OF_RANGE' });\n        });\n      });\n    }\n\n    // Test 48 bit\n    {\n      const value = 0x1234567890ab;\n      const buffer = Buffer.allocUnsafe(6);\n      buffer.writeIntBE(value, 0, 6);\n      ok(buffer.equals(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90, 0xab])));\n\n      buffer.writeIntLE(value, 0, 6);\n      ok(buffer.equals(new Uint8Array([0xab, 0x90, 0x78, 0x56, 0x34, 0x12])));\n    }\n\n    // Test Int\n    {\n      const data = Buffer.alloc(8);\n\n      // Check byteLength.\n      ['writeIntBE', 'writeIntLE'].forEach((fn) => {\n        ['', '0', null, {}, [], () => {}, true, false, undefined].forEach(\n          (bl) => {\n            throws(() => data[fn](23, 0, bl), { name: 'RangeError' });\n          }\n        );\n\n        [Infinity, -1].forEach((byteLength) => {\n          throws(() => data[fn](23, 0, byteLength), {\n            name: 'RangeError',\n          });\n        });\n\n        [NaN, 1.01].forEach((byteLength) => {\n          throws(() => data[fn](42, 0, byteLength), {\n            name: 'RangeError',\n          });\n        });\n      });\n\n      // Test 1 to 6 bytes.\n      for (let i = 1; i <= 6; i++) {\n        ['writeIntBE', 'writeIntLE'].forEach((fn) => {\n          const min = -(2 ** (i * 8 - 1));\n          const max = 2 ** (i * 8 - 1) - 1;\n          let range = `>= ${min} and <= ${max}`;\n          if (i > 4) {\n            range = `>= -(2 ** ${i * 8 - 1}) and < 2 ** ${i * 8 - 1}`;\n          }\n          [min - 1, max + 1].forEach((val) => {\n            const received =\n              i > 4\n                ? String(val).replace(/(\\d)(?=(\\d\\d\\d)+(?!\\d))/g, '$1_')\n                : val;\n            throws(\n              () => {\n                data[fn](val, 0, i);\n              },\n              {\n                name: 'RangeError',\n              }\n            );\n          });\n\n          ['', '0', null, {}, [], () => {}, true, false, undefined].forEach(\n            (o) => {\n              throws(() => data[fn](min, o, i), {\n                name: 'TypeError',\n              });\n            }\n          );\n\n          [Infinity, -1, -4294967295].forEach((offset) => {\n            throws(() => data[fn](min, offset, i), {\n              name: 'RangeError',\n            });\n          });\n\n          [NaN, 1.01].forEach((offset) => {\n            throws(() => data[fn](max, offset, i), {\n              name: 'RangeError',\n            });\n          });\n        });\n      }\n    }\n  },\n};\n\nexport const writeFloat = {\n  test(ctrl, env, ctx) {\n    const buffer = Buffer.allocUnsafe(8);\n\n    buffer.writeFloatBE(1, 0);\n    buffer.writeFloatLE(1, 4);\n    ok(\n      buffer.equals(\n        new Uint8Array([0x3f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f])\n      )\n    );\n\n    buffer.writeFloatBE(1 / 3, 0);\n    buffer.writeFloatLE(1 / 3, 4);\n    ok(\n      buffer.equals(\n        new Uint8Array([0x3e, 0xaa, 0xaa, 0xab, 0xab, 0xaa, 0xaa, 0x3e])\n      )\n    );\n\n    buffer.writeFloatBE(3.4028234663852886e38, 0);\n    buffer.writeFloatLE(3.4028234663852886e38, 4);\n    ok(\n      buffer.equals(\n        new Uint8Array([0x7f, 0x7f, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x7f])\n      )\n    );\n\n    buffer.writeFloatLE(1.1754943508222875e-38, 0);\n    buffer.writeFloatBE(1.1754943508222875e-38, 4);\n    ok(\n      buffer.equals(\n        new Uint8Array([0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00])\n      )\n    );\n\n    buffer.writeFloatBE(0 * -1, 0);\n    buffer.writeFloatLE(0 * -1, 4);\n    ok(\n      buffer.equals(\n        new Uint8Array([0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80])\n      )\n    );\n\n    buffer.writeFloatBE(Infinity, 0);\n    buffer.writeFloatLE(Infinity, 4);\n    ok(\n      buffer.equals(\n        new Uint8Array([0x7f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x7f])\n      )\n    );\n\n    strictEqual(buffer.readFloatBE(0), Infinity);\n    strictEqual(buffer.readFloatLE(4), Infinity);\n\n    buffer.writeFloatBE(-Infinity, 0);\n    buffer.writeFloatLE(-Infinity, 4);\n    ok(\n      buffer.equals(\n        new Uint8Array([0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff])\n      )\n    );\n\n    strictEqual(buffer.readFloatBE(0), -Infinity);\n    strictEqual(buffer.readFloatLE(4), -Infinity);\n\n    buffer.writeFloatBE(NaN, 0);\n    buffer.writeFloatLE(NaN, 4);\n\n    // JS only knows a single NaN but there exist two platform specific\n    // implementations. Therefore, allow both quiet and signalling NaNs.\n    if (buffer[1] === 0xbf) {\n      ok(\n        buffer.equals(\n          new Uint8Array([0x7f, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xbf, 0x7f])\n        )\n      );\n    } else {\n      ok(\n        buffer.equals(\n          new Uint8Array([0x7f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x7f])\n        )\n      );\n    }\n\n    ok(Number.isNaN(buffer.readFloatBE(0)));\n    ok(Number.isNaN(buffer.readFloatLE(4)));\n\n    // OOB in writeFloat{LE,BE} should throw.\n    {\n      const small = Buffer.allocUnsafe(1);\n\n      ['writeFloatLE', 'writeFloatBE'].forEach((fn) => {\n        // Verify that default offset works fine.\n        buffer[fn](23, undefined);\n        buffer[fn](23);\n\n        throws(() => small[fn](11.11, 0), {\n          name: 'RangeError',\n        });\n\n        ['', '0', null, {}, [], () => {}, true, false].forEach((off) => {\n          throws(() => small[fn](23, off), { name: 'TypeError' });\n        });\n\n        [Infinity, -1, 5].forEach((offset) => {\n          throws(() => buffer[fn](23, offset), {\n            name: 'RangeError',\n          });\n        });\n\n        [NaN, 1.01].forEach((offset) => {\n          throws(() => buffer[fn](42, offset), {\n            name: 'RangeError',\n          });\n        });\n      });\n    }\n  },\n};\n\nexport const writeDouble = {\n  test(ctrl, env, ctx) {\n    const buffer = Buffer.allocUnsafe(16);\n\n    buffer.writeDoubleBE(2.225073858507201e-308, 0);\n    buffer.writeDoubleLE(2.225073858507201e-308, 8);\n    ok(\n      buffer.equals(\n        new Uint8Array([\n          0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n          0xff, 0xff, 0xff, 0x0f, 0x00,\n        ])\n      )\n    );\n\n    buffer.writeDoubleBE(1.0000000000000004, 0);\n    buffer.writeDoubleLE(1.0000000000000004, 8);\n    ok(\n      buffer.equals(\n        new Uint8Array([\n          0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00,\n          0x00, 0x00, 0x00, 0xf0, 0x3f,\n        ])\n      )\n    );\n\n    buffer.writeDoubleBE(-2, 0);\n    buffer.writeDoubleLE(-2, 8);\n    ok(\n      buffer.equals(\n        new Uint8Array([\n          0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n          0x00, 0x00, 0x00, 0x00, 0xc0,\n        ])\n      )\n    );\n\n    buffer.writeDoubleBE(1.7976931348623157e308, 0);\n    buffer.writeDoubleLE(1.7976931348623157e308, 8);\n    ok(\n      buffer.equals(\n        new Uint8Array([\n          0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n          0xff, 0xff, 0xff, 0xef, 0x7f,\n        ])\n      )\n    );\n\n    buffer.writeDoubleBE(0 * -1, 0);\n    buffer.writeDoubleLE(0 * -1, 8);\n    ok(\n      buffer.equals(\n        new Uint8Array([\n          0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n          0x00, 0x00, 0x00, 0x00, 0x80,\n        ])\n      )\n    );\n\n    buffer.writeDoubleBE(Infinity, 0);\n    buffer.writeDoubleLE(Infinity, 8);\n\n    ok(\n      buffer.equals(\n        new Uint8Array([\n          0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n          0x00, 0x00, 0x00, 0xf0, 0x7f,\n        ])\n      )\n    );\n\n    strictEqual(buffer.readDoubleBE(0), Infinity);\n    strictEqual(buffer.readDoubleLE(8), Infinity);\n\n    buffer.writeDoubleBE(-Infinity, 0);\n    buffer.writeDoubleLE(-Infinity, 8);\n\n    ok(\n      buffer.equals(\n        new Uint8Array([\n          0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n          0x00, 0x00, 0x00, 0xf0, 0xff,\n        ])\n      )\n    );\n\n    strictEqual(buffer.readDoubleBE(0), -Infinity);\n    strictEqual(buffer.readDoubleLE(8), -Infinity);\n\n    buffer.writeDoubleBE(NaN, 0);\n    buffer.writeDoubleLE(NaN, 8);\n\n    // JS only knows a single NaN but there exist two platform specific\n    // implementations. Therefore, allow both quiet and signalling NaNs.\n    if (buffer[1] === 0xf7) {\n      ok(\n        buffer.equals(\n          new Uint8Array([\n            0x7f, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n            0xff, 0xff, 0xff, 0xf7, 0x7f,\n          ])\n        )\n      );\n    } else {\n      ok(\n        buffer.equals(\n          new Uint8Array([\n            0x7f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0xf8, 0x7f,\n          ])\n        )\n      );\n    }\n\n    ok(Number.isNaN(buffer.readDoubleBE(0)));\n    ok(Number.isNaN(buffer.readDoubleLE(8)));\n\n    // OOB in writeDouble{LE,BE} should throw.\n    {\n      const small = Buffer.allocUnsafe(1);\n\n      ['writeDoubleLE', 'writeDoubleBE'].forEach((fn) => {\n        // Verify that default offset works fine.\n        buffer[fn](23, undefined);\n        buffer[fn](23);\n\n        throws(() => small[fn](11.11, 0), {\n          code: 'ERR_BUFFER_OUT_OF_BOUNDS',\n          name: 'RangeError',\n          message: 'Attempt to access memory outside buffer bounds',\n        });\n\n        ['', '0', null, {}, [], () => {}, true, false].forEach((off) => {\n          throws(() => small[fn](23, off), { name: 'TypeError' });\n        });\n\n        [Infinity, -1, 9].forEach((offset) => {\n          throws(() => buffer[fn](23, offset), {\n            name: 'RangeError',\n          });\n        });\n\n        [NaN, 1.01].forEach((offset) => {\n          throws(() => buffer[fn](42, offset), {\n            name: 'RangeError',\n          });\n        });\n      });\n    }\n  },\n};\n\nexport const write = {\n  test(ctrl, env, ctx) {\n    [-1, 10].forEach((offset) => {\n      throws(() => Buffer.alloc(9).write('foo', offset), {\n        name: 'RangeError',\n      });\n    });\n\n    const resultMap = new Map([\n      ['utf8', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])],\n      ['ucs2', Buffer.from([102, 0, 111, 0, 111, 0, 0, 0, 0])],\n      ['ascii', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])],\n      ['latin1', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])],\n      ['binary', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])],\n      ['utf16le', Buffer.from([102, 0, 111, 0, 111, 0, 0, 0, 0])],\n      ['base64', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])],\n      ['base64url', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])],\n      ['hex', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])],\n    ]);\n\n    // utf8, ucs2, ascii, latin1, utf16le\n    const encodings = [\n      'utf8',\n      'utf-8',\n      'ascii',\n      'latin1',\n      'binary',\n      'ucs2',\n      'ucs-2',\n      'utf16le',\n      'utf-16le',\n    ];\n\n    encodings\n      .reduce((es, e) => es.concat(e, e.toUpperCase()), [])\n      .forEach((encoding) => {\n        const buf = Buffer.alloc(9);\n        const len = Buffer.byteLength('foo', encoding);\n        strictEqual(buf.write('foo', 0, len, encoding), len);\n\n        if (encoding.includes('-')) encoding = encoding.replace('-', '');\n\n        deepStrictEqual(buf, resultMap.get(encoding.toLowerCase()));\n      });\n\n    // base64\n    ['base64', 'BASE64', 'base64url', 'BASE64URL'].forEach((encoding) => {\n      const buf = Buffer.alloc(9);\n      const len = Buffer.byteLength('Zm9v', encoding);\n\n      strictEqual(buf.write('Zm9v', 0, len, encoding), len);\n      deepStrictEqual(buf, resultMap.get(encoding.toLowerCase()));\n    });\n\n    // hex\n    ['hex', 'HEX'].forEach((encoding) => {\n      const buf = Buffer.alloc(9);\n      const len = Buffer.byteLength('666f6f', encoding);\n\n      strictEqual(buf.write('666f6f', 0, len, encoding), len);\n      deepStrictEqual(buf, resultMap.get(encoding.toLowerCase()));\n    });\n\n    // Invalid encodings\n    for (let i = 1; i < 10; i++) {\n      const encoding = String(i).repeat(i);\n      const error = {\n        name: 'TypeError',\n      };\n\n      ok(!Buffer.isEncoding(encoding));\n      throws(() => Buffer.alloc(9).write('foo', encoding), error);\n    }\n\n    // UCS-2 overflow CVE-2018-12115\n    for (let i = 1; i < 4; i++) {\n      // Allocate two Buffers sequentially off the pool. Run more than once in case\n      // we hit the end of the pool and don't get sequential allocations\n      const x = Buffer.allocUnsafe(4).fill(0);\n      const y = Buffer.allocUnsafe(4).fill(1);\n      // Should not write anything, pos 3 doesn't have enough room for a 16-bit char\n      strictEqual(x.write('ыыыыыы', 3, 'ucs2'), 0);\n      // CVE-2018-12115 experienced via buffer overrun to next block in the pool\n      strictEqual(Buffer.compare(y, Buffer.alloc(4, 1)), 0);\n    }\n\n    // Should not write any data when there is no space for 16-bit chars\n    const z = Buffer.alloc(4, 0);\n    strictEqual(z.write('\\u0001', 3, 'ucs2'), 0);\n    strictEqual(Buffer.compare(z, Buffer.alloc(4, 0)), 0);\n    // Make sure longer strings are written up to the buffer end.\n    strictEqual(z.write('abcd', 2), 2);\n    deepStrictEqual([...z], [0, 0, 0x61, 0x62]);\n\n    // Large overrun could corrupt the process\n    strictEqual(Buffer.alloc(4).write('ыыыыыы'.repeat(100), 3, 'utf16le'), 0);\n\n    {\n      // .write() does not affect the byte after the written-to slice of the Buffer.\n      // Refs: https://github.com/nodejs/node/issues/26422\n      const buf = Buffer.alloc(8);\n      strictEqual(buf.write('ыы', 1, 'utf16le'), 4);\n      deepStrictEqual([...buf], [0, 0x4b, 0x04, 0x4b, 0x04, 0, 0, 0]);\n    }\n  },\n};\n\nexport const toString = {\n  test(ctrl, env, ctx) {\n    // utf8, ucs2, ascii, latin1, utf16le\n    const encodings = [\n      'utf8',\n      'utf-8',\n      'ucs2',\n      'ucs-2',\n      'ascii',\n      'latin1',\n      'binary',\n      'utf16le',\n      'utf-16le',\n    ];\n\n    encodings\n      .reduce((es, e) => es.concat(e, e.toUpperCase()), [])\n      .forEach((encoding) => {\n        strictEqual(Buffer.from('foo', encoding).toString(encoding), 'foo');\n      });\n\n    // base64\n    ['base64', 'BASE64'].forEach((encoding) => {\n      strictEqual(Buffer.from('Zm9v', encoding).toString(encoding), 'Zm9v');\n    });\n\n    // hex\n    ['hex', 'HEX'].forEach((encoding) => {\n      strictEqual(Buffer.from('666f6f', encoding).toString(encoding), '666f6f');\n    });\n\n    // default utf-8 if undefined\n    strictEqual(Buffer.from('utf-8').toString(), 'utf-8');\n\n    const invalidEncodings = Array.from({ length: 10 }, (_, i) =>\n      String(i + 1).repeat(i + 1)\n    );\n    // Invalid encodings\n    for (const encoding of [...invalidEncodings, null]) {\n      const error = {\n        code: 'ERR_UNKNOWN_ENCODING',\n        name: 'TypeError',\n        message: `Unknown encoding: ${encoding}`,\n      };\n      ok(!Buffer.isEncoding(encoding));\n      throws(() => Buffer.from('foo').toString(encoding), error);\n    }\n  },\n};\n\nexport const toStringRangeError = {\n  test(ctrl, env, ctx) {\n    const len = 1422561062959;\n    const message = {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    };\n    throws(() => Buffer(len).toString('utf8'), message);\n    throws(() => SlowBuffer(len).toString('utf8'), message);\n    throws(() => Buffer.alloc(len).toString('utf8'), message);\n    throws(() => Buffer.allocUnsafe(len).toString('utf8'), message);\n    throws(() => Buffer.allocUnsafeSlow(len).toString('utf8'), message);\n  },\n};\n\nexport const toStringRange = {\n  test(ctrl, env, ctx) {\n    const rangeBuffer = Buffer.from('abc');\n    // If start >= buffer's length, empty string will be returned\n    strictEqual(rangeBuffer.toString('ascii', 3), '');\n    strictEqual(rangeBuffer.toString('ascii', +Infinity), '');\n    strictEqual(rangeBuffer.toString('ascii', 3.14, 3), '');\n    strictEqual(rangeBuffer.toString('ascii', 'Infinity', 3), '');\n\n    // If end <= 0, empty string will be returned\n    strictEqual(rangeBuffer.toString('ascii', 1, 0), '');\n    strictEqual(rangeBuffer.toString('ascii', 1, -1.2), '');\n    strictEqual(rangeBuffer.toString('ascii', 1, -100), '');\n    strictEqual(rangeBuffer.toString('ascii', 1, -Infinity), '');\n\n    // If start < 0, start will be taken as zero\n    strictEqual(rangeBuffer.toString('ascii', -1, 3), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', -1.99, 3), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', -Infinity, 3), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', '-1', 3), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', '-1.99', 3), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', '-Infinity', 3), 'abc');\n\n    // If start is an invalid integer, start will be taken as zero\n    strictEqual(rangeBuffer.toString('ascii', 'node.js', 3), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', {}, 3), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', [], 3), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', NaN, 3), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', null, 3), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', undefined, 3), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', false, 3), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', '', 3), 'abc');\n\n    // But, if start is an integer when coerced, then it will be coerced and used.\n    strictEqual(rangeBuffer.toString('ascii', '-1', 3), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', '1', 3), 'bc');\n    strictEqual(rangeBuffer.toString('ascii', '-Infinity', 3), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', '3', 3), '');\n    strictEqual(rangeBuffer.toString('ascii', Number(3), 3), '');\n    strictEqual(rangeBuffer.toString('ascii', '3.14', 3), '');\n    strictEqual(rangeBuffer.toString('ascii', '1.99', 3), 'bc');\n    strictEqual(rangeBuffer.toString('ascii', '-1.99', 3), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', 1.99, 3), 'bc');\n    strictEqual(rangeBuffer.toString('ascii', true, 3), 'bc');\n\n    // If end > buffer's length, end will be taken as buffer's length\n    strictEqual(rangeBuffer.toString('ascii', 0, 5), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', 0, 6.99), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', 0, Infinity), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', 0, '5'), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', 0, '6.99'), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', 0, 'Infinity'), 'abc');\n\n    // If end is an invalid integer, end will be taken as buffer's length\n    strictEqual(rangeBuffer.toString('ascii', 0, 'node.js'), '');\n    strictEqual(rangeBuffer.toString('ascii', 0, {}), '');\n    strictEqual(rangeBuffer.toString('ascii', 0, NaN), '');\n    strictEqual(rangeBuffer.toString('ascii', 0, undefined), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', 0), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', 0, null), '');\n    strictEqual(rangeBuffer.toString('ascii', 0, []), '');\n    strictEqual(rangeBuffer.toString('ascii', 0, false), '');\n    strictEqual(rangeBuffer.toString('ascii', 0, ''), '');\n\n    // But, if end is an integer when coerced, then it will be coerced and used.\n    strictEqual(rangeBuffer.toString('ascii', 0, '-1'), '');\n    strictEqual(rangeBuffer.toString('ascii', 0, '1'), 'a');\n    strictEqual(rangeBuffer.toString('ascii', 0, '-Infinity'), '');\n    strictEqual(rangeBuffer.toString('ascii', 0, '3'), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', 0, Number(3)), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', 0, '3.14'), 'abc');\n    strictEqual(rangeBuffer.toString('ascii', 0, '1.99'), 'a');\n    strictEqual(rangeBuffer.toString('ascii', 0, '-1.99'), '');\n    strictEqual(rangeBuffer.toString('ascii', 0, 1.99), 'a');\n    strictEqual(rangeBuffer.toString('ascii', 0, true), 'a');\n\n    // Try toString() with an object as an encoding\n    strictEqual(\n      rangeBuffer.toString({\n        toString: function () {\n          return 'ascii';\n        },\n      }),\n      'abc'\n    );\n\n    // Try toString() with 0 and null as the encoding\n    throws(\n      () => {\n        rangeBuffer.toString(0, 1, 2);\n      },\n      {\n        name: 'TypeError',\n      },\n      'toString() with 0 and null as the encoding should have thrown'\n    );\n\n    throws(\n      () => {\n        rangeBuffer.toString(null, 1, 2);\n      },\n      {\n        name: 'TypeError',\n      },\n      'toString() with null encoding should have thrown'\n    );\n  },\n};\n\nexport const inspect = {\n  // test-buffer-inspect.js\n  async test(ctrl, env, ctx) {\n    let b = Buffer.allocUnsafe(60);\n    b.fill('0123456789'.repeat(6));\n\n    let s = buffer.SlowBuffer(60);\n    s.fill('0123456789'.repeat(6));\n\n    let expected =\n      '<Buffer 30 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 37 38 39 ... 10 more bytes>';\n\n    strictEqual(util.inspect(b), expected);\n    strictEqual(util.inspect(s), expected);\n\n    b = Buffer.allocUnsafe(2);\n    b.fill('12');\n\n    s = buffer.SlowBuffer(2);\n    s.fill('12');\n\n    expected = '<Buffer 31 32>';\n\n    strictEqual(util.inspect(b), expected);\n    strictEqual(util.inspect(s), expected);\n\n    b.inspect = undefined;\n    b.prop = new Uint8Array(0);\n    strictEqual(\n      util.inspect(b),\n      '<Buffer 31 32, inspect: undefined, prop: Uint8Array(0) []>'\n    );\n\n    b = Buffer.alloc(0);\n    b.prop = 123;\n\n    strictEqual(util.inspect(b), '<Buffer prop: 123>');\n  },\n};\n\nexport const isAsciiTest = {\n  test(ctrl, env, ctx) {\n    const encoder = new TextEncoder();\n    strictEqual(isAscii(encoder.encode('hello')), true);\n    strictEqual(isAscii(encoder.encode('ğ')), false);\n    strictEqual(isAscii(Buffer.from([])), true);\n\n    [\n      undefined,\n      '',\n      'hello',\n      false,\n      true,\n      0,\n      1,\n      0n,\n      1n,\n      Symbol(),\n      () => {},\n      {},\n      [],\n      null,\n    ].forEach((input) => {\n      throws(() => isAscii(input));\n    });\n  },\n};\n\nexport const isUtf8Test = {\n  test(ctrl, env, ctx) {\n    const encoder = new TextEncoder();\n\n    strictEqual(isUtf8(encoder.encode('hello')), true);\n    strictEqual(isUtf8(encoder.encode('ğ')), true);\n    strictEqual(isUtf8(Buffer.from([])), true);\n\n    // Taken from test/fixtures/wpt/encoding/textdecoder-fatal.any.js\n    [\n      [0xff], // 'invalid code'\n      [0xc0], // 'ends early'\n      [0xe0], // 'ends early 2'\n      [0xc0, 0x00], // 'invalid trail'\n      [0xc0, 0xc0], // 'invalid trail 2'\n      [0xe0, 0x00], // 'invalid trail 3'\n      [0xe0, 0xc0], // 'invalid trail 4'\n      [0xe0, 0x80, 0x00], // 'invalid trail 5'\n      [0xe0, 0x80, 0xc0], // 'invalid trail 6'\n      [0xfc, 0x80, 0x80, 0x80, 0x80, 0x80], // '> 0x10FFFF'\n      [0xfe, 0x80, 0x80, 0x80, 0x80, 0x80], // 'obsolete lead byte'\n\n      // Overlong encodings\n      [0xc0, 0x80], // 'overlong U+0000 - 2 bytes'\n      [0xe0, 0x80, 0x80], // 'overlong U+0000 - 3 bytes'\n      [0xf0, 0x80, 0x80, 0x80], // 'overlong U+0000 - 4 bytes'\n      [0xf8, 0x80, 0x80, 0x80, 0x80], // 'overlong U+0000 - 5 bytes'\n      [0xfc, 0x80, 0x80, 0x80, 0x80, 0x80], // 'overlong U+0000 - 6 bytes'\n\n      [0xc1, 0xbf], // 'overlong U+007F - 2 bytes'\n      [0xe0, 0x81, 0xbf], // 'overlong U+007F - 3 bytes'\n      [0xf0, 0x80, 0x81, 0xbf], // 'overlong U+007F - 4 bytes'\n      [0xf8, 0x80, 0x80, 0x81, 0xbf], // 'overlong U+007F - 5 bytes'\n      [0xfc, 0x80, 0x80, 0x80, 0x81, 0xbf], // 'overlong U+007F - 6 bytes'\n\n      [0xe0, 0x9f, 0xbf], // 'overlong U+07FF - 3 bytes'\n      [0xf0, 0x80, 0x9f, 0xbf], // 'overlong U+07FF - 4 bytes'\n      [0xf8, 0x80, 0x80, 0x9f, 0xbf], // 'overlong U+07FF - 5 bytes'\n      [0xfc, 0x80, 0x80, 0x80, 0x9f, 0xbf], // 'overlong U+07FF - 6 bytes'\n\n      [0xf0, 0x8f, 0xbf, 0xbf], // 'overlong U+FFFF - 4 bytes'\n      [0xf8, 0x80, 0x8f, 0xbf, 0xbf], // 'overlong U+FFFF - 5 bytes'\n      [0xfc, 0x80, 0x80, 0x8f, 0xbf, 0xbf], // 'overlong U+FFFF - 6 bytes'\n\n      [0xf8, 0x84, 0x8f, 0xbf, 0xbf], // 'overlong U+10FFFF - 5 bytes'\n      [0xfc, 0x80, 0x84, 0x8f, 0xbf, 0xbf], // 'overlong U+10FFFF - 6 bytes'\n\n      // UTF-16 surrogates encoded as code points in UTF-8\n      [0xed, 0xa0, 0x80], // 'lead surrogate'\n      [0xed, 0xb0, 0x80], // 'trail surrogate'\n      [0xed, 0xa0, 0x80, 0xed, 0xb0, 0x80], // 'surrogate pair'\n    ].forEach((input) => {\n      strictEqual(isUtf8(Buffer.from(input)), false);\n    });\n\n    [null, undefined, 'hello', true, false].forEach((input) => {\n      throws(() => isUtf8(input));\n    });\n  },\n};\n\n// Adapted from test/parallel/test-icu-transcode.js\nexport const transcodeTest = {\n  test(ctrl, env, ctx) {\n    const orig = Buffer.from('těst ☕', 'utf8');\n    const tests = {\n      latin1: [0x74, 0x3f, 0x73, 0x74, 0x20, 0x3f],\n      ascii: [0x74, 0x3f, 0x73, 0x74, 0x20, 0x3f],\n      ucs2: [\n        0x74, 0x00, 0x1b, 0x01, 0x73, 0x00, 0x74, 0x00, 0x20, 0x00, 0x15, 0x26,\n      ],\n    };\n\n    for (const test in tests) {\n      const dest = transcode(orig, 'utf8', test);\n      strictEqual(\n        dest.length,\n        tests[test].length,\n        `utf8->${test} length (${dest.length}, ${tests[test].length})`\n      );\n      for (let n = 0; n < tests[test].length; n++) {\n        strictEqual(dest[n], tests[test][n], `utf8->${test} char ${n}`);\n      }\n    }\n\n    {\n      const dest = transcode(Buffer.from(tests.ucs2), 'ucs2', 'utf8');\n      strictEqual(dest.toString(), orig.toString());\n    }\n\n    // Test utf16le to ascii/latin1 output length\n    {\n      const input = Buffer.from('AAA', 'utf16le');\n      strictEqual(input.length, 6);\n      const asciiOutput = transcode(input, 'utf16le', 'ascii');\n      strictEqual(asciiOutput.length, 3);\n      deepStrictEqual(asciiOutput, Buffer.from('AAA', 'ascii'));\n      const latin1Output = transcode(input, 'utf16le', 'latin1');\n      strictEqual(latin1Output.length, 3);\n      deepStrictEqual(latin1Output, Buffer.from('AAA', 'latin1'));\n    }\n\n    {\n      const utf8 = Buffer.from('€'.repeat(4000), 'utf8');\n      const ucs2 = Buffer.from('€'.repeat(4000), 'ucs2');\n      const utf8_to_ucs2 = transcode(utf8, 'utf8', 'ucs2');\n      const ucs2_to_utf8 = transcode(ucs2, 'ucs2', 'utf8');\n      deepStrictEqual(utf8, ucs2_to_utf8);\n      deepStrictEqual(ucs2, utf8_to_ucs2);\n      strictEqual(ucs2_to_utf8.toString('utf8'), utf8_to_ucs2.toString('ucs2'));\n    }\n\n    {\n      deepStrictEqual(\n        transcode(Buffer.from('hi', 'ascii'), 'ascii', 'utf16le'),\n        Buffer.from('hi', 'utf16le')\n      );\n      deepStrictEqual(\n        transcode(Buffer.from('hi', 'latin1'), 'latin1', 'utf16le'),\n        Buffer.from('hi', 'utf16le')\n      );\n      deepStrictEqual(\n        transcode(Buffer.from('hä', 'latin1'), 'latin1', 'utf16le'),\n        Buffer.from('hä', 'utf16le')\n      );\n    }\n\n    {\n      const dest = transcode(new Uint8Array(), 'utf8', 'latin1');\n      strictEqual(dest.length, 0);\n    }\n\n    // Test that Uint8Array arguments are okay.\n    {\n      const uint8array = new Uint8Array(Buffer.from('hä', 'latin1'));\n      deepStrictEqual(\n        transcode(uint8array, 'latin1', 'utf16le'),\n        Buffer.from('hä', 'utf16le')\n      );\n    }\n\n    // Invalid arguments should fail\n    throws(() => transcode(null, 'utf8', 'ascii'));\n    throws(() => transcode(Buffer.from('a'), 'b', 'utf8'));\n    throws(() => transcode(Buffer.from('a'), 'uf8', 'b'));\n\n    // Throws error for buffer bigger than 128mb.\n    {\n      const ISOLATE_MAX_SIZE = 134217728;\n      const val = Buffer.from('a'.repeat(ISOLATE_MAX_SIZE));\n\n      throws(() => transcode(val, 'utf16le', 'utf8'));\n      throws(() => transcode(val, 'latin1', 'utf16le'));\n    }\n\n    // Make sure same fromEncoding and toEncoding results in copy.\n    {\n      const original = Buffer.from('a');\n      const copied_value = transcode(original, 'utf8', 'utf8');\n      // Let's detach the copied_value\n      const _ = copied_value.buffer.transfer();\n      ok(copied_value.buffer.detached);\n      ok(!original.buffer.detached);\n    }\n\n    // Same encoding types should return in a value that replaces\n    // invalid characters with replacement characters.\n    deepStrictEqual(\n      transcode(Buffer.from([0x80]), 'utf8', 'utf8'),\n      Buffer.from([0xef, 0xbf, 0xbd])\n    );\n  },\n};\n\n// TranscodeFromUTF16 should not over-allocate the output buffer.\n// Regression test for a bug where `limit * sizeof(char16_t)` doubled the\n// allocation and `destPtr.size()` passed char16_t element count instead of\n// byte count to ucnv_fromUChars.\nexport const transcodeFromUTF16BufferSizeTest = {\n  test() {\n    const utf16 = Buffer.from('Hello', 'utf16le');\n    const latin1 = transcode(utf16, 'utf16le', 'latin1');\n    strictEqual(latin1.length, 5);\n    strictEqual(latin1.buffer.byteLength, latin1.length);\n  },\n};\n\n// Invalid UTF-8 input to transcode('utf8', 'utf16le') should produce\n// \"Unable to transcode buffer\", not an internal assertion mismatch.\n// Regression test for a bug where JSG_REQUIRE(actual == expected) threw\n// before the `if (actual == 0) return kj::none` path could be reached.\nexport const transcodeUTF8ToUTF16InvalidInputTest = {\n  test() {\n    throws(\n      () =>\n        transcode(\n          Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x80]),\n          'utf8',\n          'utf16le'\n        ),\n      { message: /Unable to transcode buffer/ }\n    );\n  },\n};\n\n// TranscodeFromUTF16 should reject odd-byte UTF-16LE input, matching\n// the guard that TranscodeUTF8FromUTF16 already has.\n// Regression test for a bug where `source.size() / sizeof(char16_t)`\n// silently dropped the trailing byte.\nexport const transcodeFromUTF16OddByteInputTest = {\n  test() {\n    const oddInput = Buffer.from([0x41, 0x00, 0x42]);\n    throws(() => transcode(oddInput, 'utf16le', 'utf8'));\n    throws(() => transcode(oddInput, 'utf16le', 'latin1'));\n    throws(() => transcode(oddInput, 'utf16le', 'ascii'));\n  },\n};\n\n// Tests are taken from Node.js\n// https://github.com/nodejs/node/blob/a4f609fa/test/parallel/test-file.js\nexport const fileTest = {\n  test() {\n    throws(() => new File(), TypeError);\n    throws(() => new File([]), TypeError);\n    throws(() => File.prototype.name, TypeError);\n    throws(() => File.prototype.lastModified, TypeError);\n\n    {\n      const keys = Object.keys(File.prototype).sort();\n      deepStrictEqual(keys, ['lastModified', 'name']);\n    }\n\n    {\n      const file = new File([], 'dummy.txt.exe');\n      strictEqual(file.name, 'dummy.txt.exe');\n      strictEqual(file.size, 0);\n      strictEqual(typeof file.lastModified, 'number');\n      ok(file.lastModified <= Date.now());\n    }\n\n    {\n      const toPrimitive = {\n        [Symbol.toPrimitive]() {\n          return 'NaN';\n        },\n      };\n\n      const invalidLastModified = [null, 'string', false, toPrimitive];\n\n      for (const lastModified of invalidLastModified) {\n        const file = new File([], '', { lastModified });\n        strictEqual(file.lastModified, 0);\n      }\n    }\n\n    {\n      const file = new File([], '', { lastModified: undefined });\n      notStrictEqual(file.lastModified, 0);\n    }\n\n    {\n      const toPrimitive = {\n        [Symbol.toPrimitive]() {\n          throw new TypeError('boom');\n        },\n      };\n\n      const throwValues = [BigInt(3n), toPrimitive];\n\n      for (const lastModified of throwValues) {\n        throws(() => new File([], '', { lastModified }), TypeError);\n      }\n    }\n\n    {\n      const valid = [\n        {\n          [Symbol.toPrimitive]() {\n            return 10;\n          },\n        },\n        new Number(10),\n        10,\n      ];\n\n      for (const lastModified of valid) {\n        strictEqual(new File([], '', { lastModified }).lastModified, 10);\n      }\n    }\n\n    {\n      function MyClass() {}\n      MyClass.prototype.lastModified = 10;\n\n      const file = new File([], '', new MyClass());\n      strictEqual(file.lastModified, 10);\n    }\n\n    {\n      let counter = 0;\n      new File([], '', {\n        get lastModified() {\n          counter++;\n          return 10;\n        },\n      });\n      strictEqual(counter, 1);\n    }\n  },\n};\n\n// Ref: https://github.com/cloudflare/workerd/issues/2538\nexport const sliceOffsetLimits = {\n  test() {\n    // Make sure the second parameter represents the \"end\" index, not length.\n    strictEqual(Buffer.from('abcd').utf8Slice(2, 3).toString(), 'c');\n    // Make sure to handle (end < start) edge case.\n    strictEqual(Buffer.from('abcd').utf8Slice(1, 0).toString(), '');\n  },\n};\n\n// Ref: https://github.com/unjs/unenv/pull/325\n// Without `.bind(globalThis)` the following tests fail.\nexport const invalidThisTests = {\n  async test() {\n    const bufferModule = await import('node:buffer');\n    strictEqual(bufferModule.btoa('hello'), 'aGVsbG8=');\n    strictEqual(bufferModule.atob('aGVsbG8='), 'hello');\n    ok(new bufferModule.File([], 'file'));\n    ok(new bufferModule.Blob([]));\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/buffer-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"nodejs-buffer-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"buffer-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_fs_module\", \"workers_api_getters_setters_on_prototype\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/child_process-nodejs-test.js",
    "content": "import { strictEqual, throws, ok, deepStrictEqual } from 'node:assert';\nimport * as child_process from 'node:child_process';\nimport {\n  ChildProcess,\n  _forkChild,\n  exec,\n  execFile,\n  execFileSync,\n  execSync,\n  fork,\n  spawn,\n  spawnSync,\n} from 'node:child_process';\n\nexport const testExports = {\n  async test() {\n    strictEqual(typeof ChildProcess, 'function');\n    strictEqual(typeof _forkChild, 'function');\n    strictEqual(typeof exec, 'function');\n    strictEqual(typeof execFile, 'function');\n    strictEqual(typeof execFileSync, 'function');\n    strictEqual(typeof execSync, 'function');\n    strictEqual(typeof fork, 'function');\n    strictEqual(typeof spawn, 'function');\n    strictEqual(typeof spawnSync, 'function');\n\n    strictEqual(child_process.default.ChildProcess, ChildProcess);\n    strictEqual(child_process.default._forkChild, _forkChild);\n    strictEqual(child_process.default.exec, exec);\n    strictEqual(child_process.default.execFile, execFile);\n    strictEqual(child_process.default.execFileSync, execFileSync);\n    strictEqual(child_process.default.execSync, execSync);\n    strictEqual(child_process.default.fork, fork);\n    strictEqual(child_process.default.spawn, spawn);\n    strictEqual(child_process.default.spawnSync, spawnSync);\n  },\n};\n\nexport const testChildProcessConstructor = {\n  async test() {\n    throws(\n      () => {\n        new ChildProcess();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n\nexport const testForkChild = {\n  async test() {\n    throws(\n      () => {\n        _forkChild(1, 2);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n\nexport const testExec = {\n  async test() {\n    throws(\n      () => {\n        exec(123);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        exec(null);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        exec('ls', 'invalid');\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        exec('ls', {}, 'invalid');\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        exec('ls');\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        exec('ls', {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        exec('ls', null);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        exec('ls', {}, () => {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n\nexport const testExecFile = {\n  async test() {\n    throws(\n      () => {\n        execFile('node');\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        execFile('node', ['-v']);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        execFile('node', ['-v'], {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        execFile('node', ['-v'], {}, () => {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n\nexport const testExecFileSync = {\n  async test() {\n    throws(\n      () => {\n        execFileSync('node');\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        execFileSync('node', ['-v']);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        execFileSync('node', ['-v'], {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n\nexport const testExecSync = {\n  async test() {\n    throws(\n      () => {\n        execSync('ls');\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        execSync('ls', {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n\nexport const testFork = {\n  async test() {\n    throws(\n      () => {\n        fork('script.js');\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        fork('script.js', ['arg1', 'arg2']);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        fork('script.js', ['arg1'], {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        fork('script.js', {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        fork('script.js', 'invalid');\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        fork('script.js', 123);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        fork('script.js', [], 'invalid');\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n  },\n};\n\nexport const testSpawn = {\n  async test() {\n    throws(\n      () => {\n        spawn();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        spawn('ls');\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        spawn('ls', ['-l']);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n\nexport const testSpawnSync = {\n  async test() {\n    throws(\n      () => {\n        spawnSync(123);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        spawnSync(null);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        spawnSync(undefined);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        spawnSync('ls');\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        spawnSync('ls', ['-l']);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        spawnSync('ls', ['-l'], {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n\nexport const testChildProcessProperties = {\n  async test() {\n    const proto = ChildProcess.prototype;\n\n    strictEqual(typeof proto.kill, 'function');\n    strictEqual(typeof proto.send, 'function');\n    strictEqual(typeof proto.disconnect, 'function');\n    strictEqual(typeof proto.unref, 'function');\n    strictEqual(typeof proto.ref, 'function');\n\n    ok(Symbol.dispose in proto);\n    strictEqual(typeof proto[Symbol.dispose], 'function');\n  },\n};\n\nexport const testExecReturnsChildProcess = {\n  async test() {\n    let result;\n    try {\n      result = exec('ls');\n    } catch (e) {\n      // Expected to throw\n    }\n    ok(true);\n  },\n};\n\nexport const testForkEdgeCases = {\n  async test() {\n    throws(\n      () => {\n        fork('script.js', null);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        fork('script.js', undefined);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        fork('script.js', [], null);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        fork('script.js', [], undefined);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n\nexport const testExecCallbacks = {\n  async test() {\n    throws(\n      () => {\n        exec('ls', {}, null);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        exec('ls', {}, undefined);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        exec('ls', {}, (error, stdout, stderr) => {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        exec('ls', {}, function (error, stdout, stderr) {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n\nexport const testSpawnSyncWithOptions = {\n  async test() {\n    throws(\n      () => {\n        spawnSync('ls', ['-l'], { cwd: '/tmp' });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        spawnSync('ls', { cwd: '/tmp' });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        spawnSync('ls', []);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        spawnSync('ls', [], {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n\nexport const testDefaultExport = {\n  async test() {\n    const defaultExport = child_process.default;\n\n    ok(defaultExport);\n    strictEqual(typeof defaultExport, 'object');\n\n    ok('ChildProcess' in defaultExport);\n    ok('_forkChild' in defaultExport);\n    ok('exec' in defaultExport);\n    ok('execFile' in defaultExport);\n    ok('execFileSync' in defaultExport);\n    ok('execSync' in defaultExport);\n    ok('fork' in defaultExport);\n    ok('spawn' in defaultExport);\n    ok('spawnSync' in defaultExport);\n\n    const expectedKeys = [\n      'ChildProcess',\n      '_forkChild',\n      'exec',\n      'execFile',\n      'execFileSync',\n\n      'execSync',\n      'fork',\n      'spawn',\n      'spawnSync',\n    ];\n    deepStrictEqual(Object.keys(defaultExport).sort(), expectedKeys.sort());\n  },\n};\n\nexport const testExecEmptyCommand = {\n  async test() {\n    throws(\n      () => {\n        exec('');\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        exec('', {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        exec('', {}, () => {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n\nexport const testSpawnSyncEmptyCommand = {\n  async test() {\n    throws(\n      () => {\n        spawnSync('');\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        spawnSync('', []);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        spawnSync('', [], {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n\nexport const testErrorCodes = {\n  async test() {\n    throws(\n      () => {\n        new ChildProcess();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        _forkChild(1, 2);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        exec('ls');\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        execFile('node');\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        execFileSync('node');\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        execSync('ls');\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        fork('script.js');\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        spawn();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        spawnSync('ls');\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/child_process-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"child_process-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"child_process-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_child_process_module\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/cluster-nodejs-test.js",
    "content": "import cluster from 'node:cluster';\nimport assert from 'node:assert';\n\nexport const clusterConstants = {\n  test() {\n    // Test scheduling constants\n    assert.strictEqual(cluster.SCHED_NONE, 1);\n    assert.strictEqual(cluster.SCHED_RR, 2);\n\n    // Test the default scheduling policy\n    assert.strictEqual(cluster.schedulingPolicy, cluster.SCHED_RR);\n  },\n};\n\nexport const clusterMasterWorkerFlags = {\n  test() {\n    // Test master/primary flags\n    assert.strictEqual(cluster.isMaster, true);\n    assert.strictEqual(cluster.isPrimary, true);\n    assert.strictEqual(cluster.isWorker, false);\n\n    // Test that isMaster and isPrimary are the same\n    assert.strictEqual(cluster.isMaster, cluster.isPrimary);\n  },\n};\n\nexport const clusterSettings = {\n  test() {\n    // Settings should be an empty object\n    assert.deepStrictEqual(cluster.settings, {});\n    assert.strictEqual(typeof cluster.settings, 'object');\n  },\n};\n\nexport const clusterWorkers = {\n  test() {\n    // Workers should be an empty object\n    assert.deepStrictEqual(cluster.workers, {});\n    assert.strictEqual(typeof cluster.workers, 'object');\n  },\n};\n\nexport const clusterForkMethod = {\n  test() {\n    // Test fork method throws ERR_METHOD_NOT_IMPLEMENTED\n    assert.throws(() => cluster.fork(), { code: 'ERR_METHOD_NOT_IMPLEMENTED' });\n\n    // Test fork with env parameter\n    assert.throws(() => cluster.fork({ NODE_ENV: 'test' }), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n  },\n};\n\nexport const clusterDisconnectMethod = {\n  test() {\n    // Test disconnect method throws ERR_METHOD_NOT_IMPLEMENTED\n    assert.throws(() => cluster.disconnect(), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n  },\n};\n\nexport const clusterSetupMethods = {\n  test() {\n    // Test setupPrimary method throws ERR_METHOD_NOT_IMPLEMENTED\n    assert.throws(() => cluster.setupPrimary(), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n\n    // Test setupMaster method throws ERR_METHOD_NOT_IMPLEMENTED\n    assert.throws(() => cluster.setupMaster(), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n  },\n};\n\nexport const clusterWorkerClass = {\n  test() {\n    // Create a Worker instance\n    const Worker = cluster.Worker;\n    const worker = new Worker();\n\n    // Test initial properties\n    assert.strictEqual(worker._connected, false);\n    assert.strictEqual(worker.id, 0);\n    assert.strictEqual(worker.exitedAfterDisconnect, false);\n\n    // Test isConnected method\n    assert.strictEqual(worker.isConnected(), false);\n\n    // Test isDead method\n    assert.strictEqual(worker.isDead(), true);\n\n    // Test send method always returns false\n    assert.strictEqual(worker.send('message'), false);\n    assert.strictEqual(worker.send('message', null), false);\n    assert.strictEqual(worker.send('message', null, {}), false);\n    assert.strictEqual(\n      worker.send('message', null, {}, () => {}),\n      false\n    );\n\n    // Test kill method\n    worker._connected = true;\n    worker.kill();\n    assert.strictEqual(worker._connected, false);\n\n    // Test kill with signal\n    worker._connected = true;\n    worker.kill('SIGTERM');\n    assert.strictEqual(worker._connected, false);\n\n    // Test destroy method\n    worker._connected = true;\n    worker.destroy();\n    assert.strictEqual(worker._connected, false);\n\n    // Test destroy with signal\n    worker._connected = true;\n    worker.destroy('SIGTERM');\n    assert.strictEqual(worker._connected, false);\n\n    // Test disconnect method returns this\n    worker._connected = true;\n    const result = worker.disconnect();\n    assert.strictEqual(result, worker);\n    assert.strictEqual(worker._connected, false);\n\n    // Test process property\n    assert.strictEqual(worker.process, globalThis.process);\n\n    // Test Worker extends EventEmitter\n    assert.strictEqual(typeof worker.on, 'function');\n    assert.strictEqual(typeof worker.emit, 'function');\n    assert.strictEqual(typeof worker.removeListener, 'function');\n  },\n};\n\nexport const clusterEventEmitter = {\n  test() {\n    // Test that cluster extends EventEmitter\n    assert.strictEqual(typeof cluster.on, 'function');\n    assert.strictEqual(typeof cluster.emit, 'function');\n    assert.strictEqual(typeof cluster.once, 'function');\n    assert.strictEqual(typeof cluster.removeListener, 'function');\n    assert.strictEqual(typeof cluster.removeAllListeners, 'function');\n    assert.strictEqual(typeof cluster.setMaxListeners, 'function');\n    assert.strictEqual(typeof cluster.getMaxListeners, 'function');\n    assert.strictEqual(typeof cluster.listeners, 'function');\n    assert.strictEqual(typeof cluster.rawListeners, 'function');\n    assert.strictEqual(typeof cluster.listenerCount, 'function');\n    assert.strictEqual(typeof cluster.prependListener, 'function');\n    assert.strictEqual(typeof cluster.prependOnceListener, 'function');\n    assert.strictEqual(typeof cluster.eventNames, 'function');\n\n    // Test adding and removing listeners\n    let called = false;\n    const listener = () => {\n      called = true;\n    };\n\n    cluster.on('test', listener);\n    cluster.emit('test');\n    assert.strictEqual(called, true);\n\n    called = false;\n    cluster.removeListener('test', listener);\n    cluster.emit('test');\n    assert.strictEqual(called, false);\n  },\n};\n\nexport const clusterInstanceProperties = {\n  test() {\n    // Test that cluster instance has all expected properties\n    assert.strictEqual(cluster.isMaster, true);\n    assert.strictEqual(cluster.isPrimary, true);\n    assert.strictEqual(cluster.isWorker, false);\n    assert.strictEqual(cluster.SCHED_NONE, 1);\n    assert.strictEqual(cluster.SCHED_RR, 2);\n    assert.strictEqual(cluster.schedulingPolicy, 2);\n    assert.deepStrictEqual(cluster.settings, {});\n    assert.deepStrictEqual(cluster.workers, {});\n    assert.strictEqual(typeof cluster.Worker, 'function');\n    assert.strictEqual(typeof cluster.setupPrimary, 'function');\n    assert.strictEqual(typeof cluster.setupMaster, 'function');\n    assert.strictEqual(typeof cluster.disconnect, 'function');\n    assert.strictEqual(typeof cluster.fork, 'function');\n  },\n};\n\nexport const clusterModuleExports = {\n  async test() {\n    // Test module exports\n    const clusterModule = await import('node:cluster');\n\n    // Test default export\n    assert.strictEqual(typeof clusterModule.default, 'object');\n    assert.strictEqual(clusterModule.default.isPrimary, true);\n\n    // Test named exports\n    assert.strictEqual(clusterModule.SCHED_NONE, 1);\n    assert.strictEqual(clusterModule.SCHED_RR, 2);\n    assert.strictEqual(clusterModule.isMaster, true);\n    assert.strictEqual(clusterModule.isPrimary, true);\n    assert.strictEqual(clusterModule.isWorker, false);\n    assert.strictEqual(clusterModule.schedulingPolicy, 2);\n    assert.deepStrictEqual(clusterModule.settings, {});\n    assert.deepStrictEqual(clusterModule.workers, {});\n    assert.strictEqual(typeof clusterModule.Worker, 'function');\n    assert.strictEqual(typeof clusterModule.Cluster, 'function');\n    assert.strictEqual(typeof clusterModule.fork, 'function');\n    assert.strictEqual(typeof clusterModule.disconnect, 'function');\n    assert.strictEqual(typeof clusterModule.setupPrimary, 'function');\n    assert.strictEqual(typeof clusterModule.setupMaster, 'function');\n  },\n};\n\nexport const clusterWorkerEventEmitter = {\n  test() {\n    const Worker = cluster.Worker;\n    const worker = new Worker();\n\n    // Test Worker event emitter functionality\n    let eventFired = false;\n    const handler = () => {\n      eventFired = true;\n    };\n\n    worker.on('custom', handler);\n    worker.emit('custom');\n    assert.strictEqual(eventFired, true);\n\n    eventFired = false;\n    worker.removeListener('custom', handler);\n    worker.emit('custom');\n    assert.strictEqual(eventFired, false);\n\n    // Test once\n    let onceCount = 0;\n    worker.once('once-event', () => {\n      onceCount++;\n    });\n    worker.emit('once-event');\n    worker.emit('once-event');\n    assert.strictEqual(onceCount, 1);\n  },\n};\n\nexport const clusterClassInstance = {\n  test() {\n    // Test creating a new Cluster instance\n    const ClusterClass = cluster.constructor;\n    const newCluster = new ClusterClass();\n\n    // Test that the new instance has all expected properties\n    assert.strictEqual(newCluster.isMaster, true);\n    assert.strictEqual(newCluster.isPrimary, true);\n    assert.strictEqual(newCluster.isWorker, false);\n    assert.strictEqual(newCluster.SCHED_NONE, 1);\n    assert.strictEqual(newCluster.SCHED_RR, 2);\n    assert.strictEqual(newCluster.schedulingPolicy, 2);\n    assert.deepStrictEqual(newCluster.settings, {});\n    assert.deepStrictEqual(newCluster.workers, {});\n    assert.strictEqual(typeof newCluster.Worker, 'function');\n\n    // Test that methods throw the expected errors\n    assert.throws(() => newCluster.setupPrimary(), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n\n    assert.throws(() => newCluster.setupMaster(), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n\n    assert.throws(() => newCluster.disconnect(), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n\n    assert.throws(() => newCluster.fork(), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/cluster-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"cluster-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"cluster-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_cluster_module\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/console-nodejs-test.js",
    "content": "import Console from 'node:console';\nimport { strictEqual, doesNotThrow, throws } from 'node:assert';\n\n// These methods are retrieved using Object.keys(require('node:console'))\n// on Node.js v22.17.1\nconst methods = [\n  'log',\n  'info',\n  'debug',\n  'warn',\n  'error',\n  'dir',\n  'time',\n  'timeEnd',\n  'timeLog',\n  'trace',\n  'assert',\n  'clear',\n  'count',\n  'countReset',\n  'group',\n  'groupEnd',\n  'table',\n  'dirxml',\n  'groupCollapsed',\n  'Console',\n  'profile',\n  'profileEnd',\n  'timeStamp',\n  'context',\n  'createTask',\n];\n\nconst notImplementedMethods = ['context', 'createTask', 'Console'];\n\nexport const testMethodNames = {\n  async test() {\n    for (const method of methods) {\n      strictEqual(\n        typeof Console[method],\n        'function',\n        `Method Console.${method} is not a function`\n      );\n    }\n  },\n};\n\nexport const testNotImplemented = {\n  async test() {\n    for (const method of notImplementedMethods) {\n      throws(() => Console[method](), {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      });\n    }\n  },\n};\n\nexport const testMethods = {\n  async test() {\n    const implementedMethods = methods.filter(\n      (m) => !notImplementedMethods.includes(m)\n    );\n    for (const method of implementedMethods) {\n      doesNotThrow(() => Console[method](`Calling Console.${method}()`));\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/console-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"console-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"console-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_console_module\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/constants-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { ok, strictEqual, throws } from 'node:assert';\n\nimport constants from 'node:constants';\nimport { POINT_CONVERSION_HYBRID } from 'node:constants';\n\nexport const constantsTest = {\n  test() {\n    [\n      'RTLD_LAZY',\n      'RTLD_NOW',\n      'RTLD_GLOBAL',\n      'RTLD_LOCAL',\n      'E2BIG',\n      'EACCES',\n      'EADDRINUSE',\n      'EADDRNOTAVAIL',\n      'EAFNOSUPPORT',\n      'EAGAIN',\n      'EALREADY',\n      'EBADF',\n      'EBADMSG',\n      'EBUSY',\n      'ECANCELED',\n      'ECHILD',\n      'ECONNABORTED',\n      'ECONNREFUSED',\n      'ECONNRESET',\n      'EDEADLK',\n      'EDESTADDRREQ',\n      'EDOM',\n      'EDQUOT',\n      'EEXIST',\n      'EFAULT',\n      'EFBIG',\n      'EHOSTUNREACH',\n      'EIDRM',\n      'EILSEQ',\n      'EINPROGRESS',\n      'EINTR',\n      'EINVAL',\n      'EIO',\n      'EISCONN',\n      'EISDIR',\n      'ELOOP',\n      'EMFILE',\n      'EMLINK',\n      'EMSGSIZE',\n      'EMULTIHOP',\n      'ENAMETOOLONG',\n      'ENETDOWN',\n      'ENETRESET',\n      'ENETUNREACH',\n      'ENFILE',\n      'ENOBUFS',\n      'ENODATA',\n      'ENODEV',\n      'ENOENT',\n      'ENOEXEC',\n      'ENOLCK',\n      'ENOLINK',\n      'ENOMEM',\n      'ENOMSG',\n      'ENOPROTOOPT',\n      'ENOSPC',\n      'ENOSR',\n      'ENOSTR',\n      'ENOSYS',\n      'ENOTCONN',\n      'ENOTDIR',\n      'ENOTEMPTY',\n      'ENOTSOCK',\n      'ENOTSUP',\n      'ENOTTY',\n      'ENXIO',\n      'EOPNOTSUPP',\n      'EOVERFLOW',\n      'EPERM',\n      'EPIPE',\n      'EPROTO',\n      'EPROTONOSUPPORT',\n      'EPROTOTYPE',\n      'ERANGE',\n      'EROFS',\n      'ESPIPE',\n      'ESRCH',\n      'ESTALE',\n      'ETIME',\n      'ETIMEDOUT',\n      'ETXTBSY',\n      'EWOULDBLOCK',\n      'EXDEV',\n      'PRIORITY_LOW',\n      'PRIORITY_BELOW_NORMAL',\n      'PRIORITY_NORMAL',\n      'PRIORITY_ABOVE_NORMAL',\n      'PRIORITY_HIGH',\n      'PRIORITY_HIGHEST',\n      'SIGHUP',\n      'SIGINT',\n      'SIGQUIT',\n      'SIGILL',\n      'SIGTRAP',\n      'SIGABRT',\n      'SIGIOT',\n      'SIGBUS',\n      'SIGFPE',\n      'SIGKILL',\n      'SIGUSR1',\n      'SIGSEGV',\n      'SIGUSR2',\n      'SIGPIPE',\n      'SIGALRM',\n      'SIGTERM',\n      'SIGCHLD',\n      'SIGCONT',\n      'SIGSTOP',\n      'SIGTSTP',\n      'SIGTTIN',\n      'SIGTTOU',\n      'SIGURG',\n      'SIGXCPU',\n      'SIGXFSZ',\n      'SIGVTALRM',\n      'SIGPROF',\n      'SIGWINCH',\n      'SIGIO',\n      'SIGINFO',\n      'SIGSYS',\n      'UV_FS_SYMLINK_DIR',\n      'UV_FS_SYMLINK_JUNCTION',\n      'O_RDONLY',\n      'O_WRONLY',\n      'O_RDWR',\n      'UV_DIRENT_UNKNOWN',\n      'UV_DIRENT_FILE',\n      'UV_DIRENT_DIR',\n      'UV_DIRENT_LINK',\n      'UV_DIRENT_FIFO',\n      'UV_DIRENT_SOCKET',\n      'UV_DIRENT_CHAR',\n      'UV_DIRENT_BLOCK',\n      'S_IFMT',\n      'S_IFREG',\n      'S_IFDIR',\n      'S_IFCHR',\n      'S_IFBLK',\n      'S_IFIFO',\n      'S_IFLNK',\n      'S_IFSOCK',\n      'O_CREAT',\n      'O_EXCL',\n      'UV_FS_O_FILEMAP',\n      'O_NOCTTY',\n      'O_TRUNC',\n      'O_APPEND',\n      'O_DIRECTORY',\n      'O_NOFOLLOW',\n      'O_SYNC',\n      'O_DSYNC',\n      'O_SYMLINK',\n      'O_NONBLOCK',\n      'S_IRWXU',\n      'S_IRUSR',\n      'S_IWUSR',\n      'S_IXUSR',\n      'S_IRWXG',\n      'S_IRGRP',\n      'S_IWGRP',\n      'S_IXGRP',\n      'S_IRWXO',\n      'S_IROTH',\n      'S_IWOTH',\n      'S_IXOTH',\n      'F_OK',\n      'R_OK',\n      'W_OK',\n      'X_OK',\n      'UV_FS_COPYFILE_EXCL',\n      'COPYFILE_EXCL',\n      'UV_FS_COPYFILE_FICLONE',\n      'COPYFILE_FICLONE',\n      'UV_FS_COPYFILE_FICLONE_FORCE',\n      'COPYFILE_FICLONE_FORCE',\n      'OPENSSL_VERSION_NUMBER',\n      'SSL_OP_ALL',\n      'SSL_OP_ALLOW_NO_DHE_KEX',\n      'SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION',\n      'SSL_OP_CIPHER_SERVER_PREFERENCE',\n      'SSL_OP_CISCO_ANYCONNECT',\n      'SSL_OP_COOKIE_EXCHANGE',\n      'SSL_OP_CRYPTOPRO_TLSEXT_BUG',\n      'SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS',\n      'SSL_OP_LEGACY_SERVER_CONNECT',\n      'SSL_OP_NO_COMPRESSION',\n      'SSL_OP_NO_ENCRYPT_THEN_MAC',\n      'SSL_OP_NO_QUERY_MTU',\n      'SSL_OP_NO_RENEGOTIATION',\n      'SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION',\n      'SSL_OP_NO_SSLv2',\n      'SSL_OP_NO_SSLv3',\n      'SSL_OP_NO_TICKET',\n      'SSL_OP_NO_TLSv1',\n      'SSL_OP_NO_TLSv1_1',\n      'SSL_OP_NO_TLSv1_2',\n      'SSL_OP_NO_TLSv1_3',\n      'SSL_OP_PRIORITIZE_CHACHA',\n      'SSL_OP_TLS_ROLLBACK_BUG',\n      'ENGINE_METHOD_RSA',\n      'ENGINE_METHOD_DSA',\n      'ENGINE_METHOD_DH',\n      'ENGINE_METHOD_RAND',\n      'ENGINE_METHOD_EC',\n      'ENGINE_METHOD_CIPHERS',\n      'ENGINE_METHOD_DIGESTS',\n      'ENGINE_METHOD_PKEY_METHS',\n      'ENGINE_METHOD_PKEY_ASN1_METHS',\n      'ENGINE_METHOD_ALL',\n      'ENGINE_METHOD_NONE',\n      'DH_CHECK_P_NOT_SAFE_PRIME',\n      'DH_CHECK_P_NOT_PRIME',\n      'DH_UNABLE_TO_CHECK_GENERATOR',\n      'DH_NOT_SUITABLE_GENERATOR',\n      'RSA_PKCS1_PADDING',\n      'RSA_NO_PADDING',\n      'RSA_PKCS1_OAEP_PADDING',\n      'RSA_X931_PADDING',\n      'RSA_PKCS1_PSS_PADDING',\n      'RSA_PSS_SALTLEN_DIGEST',\n      'RSA_PSS_SALTLEN_MAX_SIGN',\n      'RSA_PSS_SALTLEN_AUTO',\n      'defaultCoreCipherList',\n      'TLS1_VERSION',\n      'TLS1_1_VERSION',\n      'TLS1_2_VERSION',\n      'TLS1_3_VERSION',\n      'POINT_CONVERSION_COMPRESSED',\n      'POINT_CONVERSION_UNCOMPRESSED',\n      'POINT_CONVERSION_HYBRID',\n    ].forEach((key) => {\n      ok(key in constants);\n    });\n\n    strictEqual(constants.POINT_CONVERSION_HYBRID, POINT_CONVERSION_HYBRID);\n    throws(\n      () => {\n        constants.POINT_CONVERSION_HYBRID = 'abc';\n      },\n      {\n        message: /read only property/,\n      }\n    );\n    strictEqual(constants.POINT_CONVERSION_HYBRID, POINT_CONVERSION_HYBRID);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/constants-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"constants-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"constants-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_cipher-test.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport {\n  createCipheriv,\n  createDecipheriv,\n  randomBytes,\n  createSecretKey,\n  publicDecrypt,\n  publicEncrypt,\n  privateDecrypt,\n  privateEncrypt,\n  createPublicKey,\n  createPrivateKey,\n  getCiphers,\n  getCipherInfo,\n  createCipher,\n  createDecipher,\n  Cipher,\n  Decipher,\n} from 'node:crypto';\n\nimport { strictEqual, deepStrictEqual, throws, ok } from 'node:assert';\n\nconst tests = [\n  { name: 'aes-128-cbc', size: 16, iv: 16 },\n  { name: 'aes-192-cbc', size: 24, iv: 16 },\n  { name: 'aes-256-cbc', size: 32, iv: 16 },\n  { name: 'aes-128-ctr', size: 16, iv: 16 },\n  { name: 'aes-192-ctr', size: 24, iv: 16 },\n  { name: 'aes-256-ctr', size: 32, iv: 16 },\n  { name: 'aes-128-ecb', size: 16, iv: 0 },\n  { name: 'aes-192-ecb', size: 24, iv: 0 },\n  { name: 'aes-256-ecb', size: 32, iv: 0 },\n  { name: 'aes-128-ofb', size: 16, iv: 16 },\n  { name: 'aes-192-ofb', size: 24, iv: 16 },\n  { name: 'aes-256-ofb', size: 32, iv: 16 },\n];\n\nconst authTagTests = [\n  { name: 'aes-128-gcm', size: 16, iv: 16 },\n  { name: 'aes-192-gcm', size: 24, iv: 16 },\n  { name: 'aes-256-gcm', size: 32, iv: 16 },\n  { name: 'chacha20-poly1305', size: 32, iv: 12 },\n];\n\nexport const cipheriv = {\n  async test() {\n    tests.forEach((test) => {\n      const key = createSecretKey(Buffer.alloc(test.size));\n      const iv = Buffer.alloc(test.iv);\n\n      const cipher = createCipheriv(test.name, key, iv);\n      const decipher = createDecipheriv(test.name, key, iv);\n\n      let data = '';\n      data += decipher.update(cipher.update('Hello World', 'utf8'));\n      data += decipher.update(cipher.final());\n      data += decipher.final();\n      strictEqual(data, 'Hello World');\n    });\n\n    // Test that the streams API works also\n    await Promise.all(\n      tests.map(async (test) => {\n        const { promise, resolve, reject } = Promise.withResolvers();\n\n        const key = createSecretKey(Buffer.alloc(test.size));\n        const iv = Buffer.alloc(test.iv);\n\n        const cipher = createCipheriv(test.name, key, iv);\n        const decipher = createDecipheriv(test.name, key, iv);\n        decipher.setEncoding('utf8');\n\n        cipher.end('Hello World');\n        cipher.on('data', (chunk) => {\n          decipher.write(chunk);\n        });\n        cipher.on('end', () => {\n          decipher.end();\n        });\n\n        cipher.on('error', reject);\n        decipher.on('error', reject);\n\n        let res = '';\n        decipher.on('data', (chunk) => {\n          res += chunk;\n        });\n        decipher.on('end', resolve);\n\n        await promise;\n        strictEqual(res, 'Hello World', test.name);\n      })\n    );\n\n    authTagTests.forEach((test) => {\n      const key = createSecretKey(Buffer.alloc(test.size));\n      const iv = Buffer.alloc(test.iv);\n\n      const cipher = createCipheriv(test.name, key, iv);\n\n      let data = '';\n      cipher.setAAD(Buffer.from('hello'));\n      data += cipher.update('Hello World', 'utf8', 'hex');\n      data += cipher.final('hex');\n\n      const tag = cipher.getAuthTag();\n\n      //tag[1] = 0xbb;\n\n      const decipher = createDecipheriv(test.name, key, iv);\n      decipher.setAuthTag(tag);\n      decipher.setAAD(Buffer.from('hello'));\n      let res = '';\n      res += decipher.update(data, 'hex');\n      res += decipher.final();\n      strictEqual(res, 'Hello World');\n    });\n  },\n};\n\nexport const largeData = {\n  async test() {\n    const { promise, resolve, reject } = Promise.withResolvers();\n\n    const key = createSecretKey(Buffer.alloc(16));\n    const iv = Buffer.alloc(16);\n    const cipher = createCipheriv('aes-128-cbc', key, iv);\n    const chunks = [\n      randomBytes(1024),\n      randomBytes(2048),\n      randomBytes(4096),\n      randomBytes(8192),\n      randomBytes(16304),\n    ];\n    chunks.forEach((chunk) => cipher.write(chunk));\n    cipher.end();\n\n    const decipher = createDecipheriv('aes-128-cbc', key, iv);\n    cipher.pipe(decipher);\n\n    const output = [];\n    decipher.on('data', (chunk) => output.push(chunk));\n\n    decipher.on('end', () => {\n      const inputCombined = Buffer.concat(chunks);\n      const outputCombined = Buffer.concat(output);\n      deepStrictEqual(inputCombined, outputCombined);\n      resolve();\n    });\n\n    decipher.on('error', reject);\n\n    await promise;\n  },\n};\n\nexport const publicEncryptPrivateDecrypt = {\n  test(_, env) {\n    const pub = createPublicKey(env['rsa_public.pem']);\n    const pvt = createPrivateKey(env['rsa_private.pem']);\n\n    pub.oaepLabel = 'test';\n    pub.oaepHash = 'sha256';\n    pub.padding = 4;\n    pub.encoding = 'utf8';\n\n    pvt.oaepLabel = 'test';\n    pvt.oaepHash = 'sha256';\n    pvt.padding = 4;\n    pvt.encoding = 'utf8';\n\n    strictEqual(\n      privateDecrypt(pvt, publicEncrypt(pub, 'hello')).toString(),\n      'hello'\n    );\n  },\n};\n\nexport const publicEncryptPrivateDecryptDer = {\n  test(_, env) {\n    const pub = createPublicKey(env['rsa_public.pem']);\n    const pvt = createPrivateKey(env['rsa_private.pem']);\n\n    const pubDer = {\n      key: pub.export({ type: 'pkcs1', format: 'der' }),\n      format: 'der',\n      type: 'pkcs1',\n    };\n\n    const pvtDer = {\n      key: pvt.export({ type: 'pkcs8', format: 'der' }),\n      format: 'der',\n      type: 'pkcs8',\n    };\n\n    strictEqual(\n      privateDecrypt(pvtDer, publicEncrypt(pubDer, 'hello')).toString(),\n      'hello'\n    );\n  },\n};\n\nexport const publicEncryptPrivateDecryptPem = {\n  test(_, env) {\n    const pub = createPublicKey(env['rsa_public.pem']);\n    const pvt = createPrivateKey(env['rsa_private.pem']);\n\n    const pubPem = {\n      key: pub.export({ type: 'pkcs1', format: 'pem' }),\n      format: 'pem',\n      type: 'pkcs1',\n    };\n\n    const pvtPem = {\n      key: pvt.export({ type: 'pkcs8', format: 'pem' }),\n      format: 'pem',\n      type: 'pkcs8',\n    };\n\n    strictEqual(\n      privateDecrypt(pvtPem, publicEncrypt(pubPem, 'hello')).toString(),\n      'hello'\n    );\n  },\n};\n\nexport const publicEncryptPrivateDecryptPem2 = {\n  test(_, env) {\n    const pub = createPublicKey(env['rsa_public.pem']);\n    const pvt = createPrivateKey(env['rsa_private.pem']);\n\n    const pubPem = pub.export({ type: 'pkcs1', format: 'pem' });\n    const pvtPem = pvt.export({ type: 'pkcs8', format: 'pem' });\n\n    strictEqual(\n      privateDecrypt(pvtPem, publicEncrypt(pubPem, 'hello')).toString(),\n      'hello'\n    );\n  },\n};\n\nexport const publicEncryptPrivateDecryptPem3 = {\n  test(_, env) {\n    const pub = createPublicKey(env['rsa_public.pem']);\n    const pvt = createPrivateKey(env['rsa_private.pem']);\n\n    const pubPem = pub.export({ type: 'spki', format: 'pem' });\n    const pvtPem = pvt.export({ type: 'pkcs8', format: 'pem' });\n\n    strictEqual(\n      privateDecrypt(pvtPem, publicEncrypt(pubPem, 'hello')).toString(),\n      'hello'\n    );\n  },\n};\n\nexport const privateEncryptPublicDecrypt = {\n  test(_, env) {\n    const pub = createPublicKey(env['rsa_public.pem']);\n    const pvt = createPrivateKey(env['rsa_private.pem']);\n\n    pub.oaepLabel = 'test';\n    pub.oaepHash = 'sha256';\n    pub.padding = 3;\n    pub.encoding = 'utf8';\n\n    pvt.oaepLabel = 'test';\n    pvt.oaepHash = 'sha256';\n    pvt.padding = 3;\n    pvt.encoding = 'utf8';\n\n    const input = 'a'.repeat(256);\n    strictEqual(\n      publicDecrypt(pub, privateEncrypt(pvt, input)).toString(),\n      input\n    );\n  },\n};\n\nexport const missingArgChecks = {\n  test() {\n    throws(() => publicEncrypt(), {\n      code: 'ERR_MISSING_ARGS',\n    });\n    throws(() => publicDecrypt(), {\n      code: 'ERR_MISSING_ARGS',\n    });\n    throws(() => privateEncrypt(), {\n      code: 'ERR_MISSING_ARGS',\n    });\n    throws(() => privateDecrypt(), {\n      code: 'ERR_MISSING_ARGS',\n    });\n    throws(() => publicEncrypt('key'), {\n      code: 'ERR_MISSING_ARGS',\n    });\n    throws(() => publicDecrypt('key'), {\n      code: 'ERR_MISSING_ARGS',\n    });\n    throws(() => privateEncrypt('key'), {\n      code: 'ERR_MISSING_ARGS',\n    });\n    throws(() => privateDecrypt('key'), {\n      code: 'ERR_MISSING_ARGS',\n    });\n  },\n};\n\nexport const known_ciphers_info = {\n  test() {\n    const ciphers = getCiphers();\n    ciphers.forEach((i) => {\n      const info = getCipherInfo(i);\n      ok(info);\n      strictEqual(typeof info, 'object');\n      strictEqual(typeof info.name, 'string');\n      strictEqual(typeof info.nid, 'number');\n      strictEqual(typeof info.blockSize, 'number');\n      strictEqual(typeof info.ivLength, 'number');\n      strictEqual(typeof info.keyLength, 'number');\n      strictEqual(typeof info.mode, 'string');\n    });\n  },\n};\n\nexport const test_cipher_info = {\n  test() {\n    const info = getCipherInfo('aes-128-cbc', {\n      ivLength: 16,\n      keyLength: 16,\n    });\n    ok(info);\n    strictEqual(typeof info, 'object');\n    strictEqual(typeof info.name, 'string');\n    strictEqual(typeof info.nid, 'number');\n    strictEqual(typeof info.blockSize, 'number');\n    strictEqual(typeof info.ivLength, 'number');\n    strictEqual(typeof info.keyLength, 'number');\n    strictEqual(typeof info.mode, 'string');\n  },\n};\n\nexport const test_cipher_info2 = {\n  test() {\n    const info = getCipherInfo('aes-128-cbc', {\n      ivLength: 17,\n      keyLength: 15,\n    });\n    strictEqual(info, undefined);\n  },\n};\n\nexport const test_cipher_info3 = {\n  test() {\n    const info = getCipherInfo('aes-128-cbc', {\n      ivLength: -1,\n      keyLength: 16,\n    });\n    strictEqual(info, undefined);\n  },\n};\n\nexport const test_cipher_info4 = {\n  test() {\n    const info = getCipherInfo('aes-128-cbc', {\n      ivLength: 16,\n      keyLength: -1,\n    });\n    strictEqual(info, undefined);\n  },\n};\n\nexport const testUnimplemented = {\n  async test() {\n    strictEqual(typeof Cipher, 'function');\n    strictEqual(typeof Decipher, 'function');\n    strictEqual(typeof createCipher, 'function');\n    strictEqual(typeof createDecipher, 'function');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_cipher-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"crypto_cipher-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"crypto_cipher-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat_v2\", \"experimental\"],\n        bindings = [\n          ( name = \"rsa_private.pem\", text = embed \"fixtures/rsa_private.pem\" ),\n          ( name = \"rsa_public.pem\", text = embed \"fixtures/rsa_public.pem\" ),\n          ( name = \"ed25519_private.pem\", text = embed \"fixtures/ed25519_private.pem\" ),\n          ( name = \"ed25519_public.pem\", text = embed \"fixtures/ed25519_public.pem\" ),\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_dh-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n'use strict';\n\nimport { Buffer } from 'node:buffer';\n\nimport * as assert from 'node:assert';\nimport * as crypto from 'node:crypto';\n\nexport const dh_test = {\n  test(ctrl, env, ctx) {\n    // https://github.com/nodejs/node/issues/32738\n    // XXX(bnoordhuis) validateInt32() throwing ERR_OUT_OF_RANGE and RangeError\n    // instead of ERR_INVALID_ARG_TYPE and TypeError is questionable, IMO.\n    assert.throws(() => crypto.createDiffieHellman(13.37), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n      message:\n        'The value of \"sizeOrKey\" is out of range. ' +\n        'It must be an integer. Received 13.37',\n    });\n\n    assert.throws(() => crypto.createDiffieHellman('abcdef', 13.37), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n      message:\n        'The value of \"generator\" is out of range. ' +\n        'It must be an integer. Received 13.37',\n    });\n    // BoringSSL throws when key sizes > 10000 bits are requested, we proactively return a\n    // RangeError instead which is more descriptive.\n    assert.throws(() => crypto.createDiffieHellman(10001), {\n      name: 'RangeError',\n    });\n\n    for (const bits of [-1, 0, 1]) {\n      assert.throws(() => crypto.createDiffieHellman(bits), {\n        name: 'Error',\n      });\n    }\n\n    // Through a fluke of history, g=0 defaults to DH_GENERATOR (2).\n    {\n      const g = 0;\n      crypto.createDiffieHellman('abcdef', 'hex', g);\n    }\n\n    for (const g of [-1, 1]) {\n      const ex = {\n        name: 'RangeError',\n      };\n      assert.throws(() => crypto.createDiffieHellman('abcdef', g), ex);\n      assert.throws(() => crypto.createDiffieHellman('abcdef', 'hex', g), ex);\n    }\n\n    // Calls with even p, or p values that are interpreted as <= g will be rejected.\n    crypto.createDiffieHellman('abcdef', 'hex', Buffer.from([2])); // OK\n    for (const bits of ['abcdef', Buffer.from([1]), Buffer.from([4])]) {\n      assert.throws(() => crypto.createDiffieHellman(bits), {\n        name: 'Error',\n      });\n    }\n\n    for (const g of [Buffer.from([]), Buffer.from([0]), Buffer.from([1])]) {\n      const ex = {\n        name: 'Error',\n      };\n      assert.throws(() => crypto.createDiffieHellman('abcdef', g), ex);\n      assert.throws(() => crypto.createDiffieHellman('abcdef', 'hex', g), ex);\n    }\n\n    [[0x1, 0x2], () => {}, /abc/, {}].forEach((input) => {\n      assert.throws(() => crypto.createDiffieHellman(input), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n      });\n    });\n\n    assert.throws(\n      function () {\n        crypto.getDiffieHellman('unknown-group');\n      },\n      {\n        name: 'Error',\n      },\n      \"crypto.getDiffieHellman('unknown-group') \" +\n        'failed to throw the expected error.'\n    );\n\n    assert.throws(() => crypto.createDiffieHellman('', true), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    [true, Symbol(), {}, () => {}, []].forEach((generator) =>\n      assert.throws(() => crypto.createDiffieHellman('', 'base64', generator), {\n        name: 'TypeError',\n      })\n    );\n  },\n};\n\n///////////////\n\nexport const dh_verify_error_test = {\n  test(ctrl, env, ctx) {\n    // Second OAKLEY group, see\n    // https://github.com/nodejs/node-v0.x-archive/issues/2338 and\n    // https://xml2rfc.tools.ietf.org/public/rfc/html/rfc2412.html#anchor49\n    const p =\n      'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' +\n      '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' +\n      '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' +\n      'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF';\n    crypto.createDiffieHellman(p, 'hex');\n\n    // Confirm DH_check() results are exposed for optional examination. Use an odd value for prime\n    // since even values are rejected immediately.\n    const bad_dh = crypto.createDiffieHellman('03', 'hex');\n    assert.notStrictEqual(bad_dh.verifyError, 0);\n  },\n};\n\n/////////////////////\n\nexport const dh_constructor_test = {\n  test(ctrl, env, ctx) {\n    const DiffieHellmanGroup = crypto.DiffieHellmanGroup;\n    const dhg = DiffieHellmanGroup('modp14');\n    assert.ok(\n      dhg instanceof DiffieHellmanGroup,\n      'DiffieHellmanGroup is expected ' +\n        'to return a new instance when ' +\n        'called without `new`'\n    );\n\n    const p1 = dhg.getPrime('buffer');\n    const DiffieHellman = crypto.DiffieHellman;\n    const dh = DiffieHellman(p1, 'buffer');\n    assert.ok(\n      dh instanceof DiffieHellman,\n      'DiffieHellman is expected to return a ' +\n        'new instance when called without `new`'\n    );\n  },\n};\n////////////////////\n\n// This test will fail if BoringSSL runs in FIPS mode and succeed otherwise; disable it for now.\n\n/*\nfunction test() {\n  const odd = Buffer.alloc(39, 'A');\n\n  const c = crypto.createDiffieHellman(size);\n  c.setPrivateKey(odd);\n  c.generateKeys();\n}\n\n// FIPS requires a length of at least 1024\nif (!common.hasFipsCrypto) {\n  test();\n} else {\n  assert.throws(function() { test(); }, /key size too small/);\n}\n*/\n\n//////////////////\n\nexport const dh_group_test = {\n  test(ctrl, env, ctx) {\n    assert.throws(\n      function () {\n        crypto.getDiffieHellman('modp14').setPrivateKey('');\n      },\n      new RegExp(\n        '^TypeError: crypto\\\\.getDiffieHellman\\\\(\\\\.\\\\.\\\\.\\\\)\\\\.' +\n          'setPrivateKey is not a function$'\n      ),\n      \"crypto.getDiffieHellman('modp14').setPrivateKey('') \" +\n        'failed to throw the expected error.'\n    );\n    assert.throws(\n      function () {\n        crypto.getDiffieHellman('modp14').setPublicKey('');\n      },\n      new RegExp(\n        '^TypeError: crypto\\\\.getDiffieHellman\\\\(\\\\.\\\\.\\\\.\\\\)\\\\.' +\n          'setPublicKey is not a function$'\n      ),\n      \"crypto.getDiffieHellman('modp14').setPublicKey('') \" +\n        'failed to throw the expected error.'\n    );\n  },\n};\n\n////////////////\n\nexport const dh_exchange_test = {\n  test(ctrl, env, ctx) {\n    const alice = crypto.createDiffieHellmanGroup('modp14');\n    const bob = crypto.createDiffieHellmanGroup('modp14');\n    alice.generateKeys();\n    bob.generateKeys();\n    const aSecret = alice.computeSecret(bob.getPublicKey()).toString('hex');\n    const bSecret = bob.computeSecret(alice.getPublicKey()).toString('hex');\n    assert.strictEqual(aSecret, bSecret);\n  },\n};\n\n////////////////\n\n// This test verifies padding with leading zeroes for shared\n// secrets that are strictly smaller than the modulus (prime).\n// See:\n//  RFC 4346: https://www.ietf.org/rfc/rfc4346.txt\n//  https://github.com/nodejs/node-v0.x-archive/issues/7906\n//  https://github.com/nodejs/node-v0.x-archive/issues/5239\n//\n// In FIPS mode OPENSSL_DH_FIPS_MIN_MODULUS_BITS = 1024, meaning we need\n// a FIPS-friendly >= 1024 bit prime, we can use MODP 14 from RFC 3526:\n// https://www.ietf.org/rfc/rfc3526.txt\n//\n// We can generate appropriate values with this code:\n//\n// crypto = require('crypto');\n//\n// for (;;) {\n//   var a = crypto.getDiffieHellman('modp14'),\n//   var b = crypto.getDiffieHellman('modp14');\n//\n//   a.generateKeys();\n//   b.generateKeys();\n//\n//   var aSecret = a.computeSecret(b.getPublicKey()).toString('hex');\n//   console.log(\"A public: \" + a.getPublicKey().toString('hex'));\n//   console.log(\"A private: \" + a.getPrivateKey().toString('hex'));\n//   console.log(\"B public: \" + b.getPublicKey().toString('hex'));\n//   console.log(\"B private: \" + b.getPrivateKey().toString('hex'));\n//   console.log(\"A secret: \" + aSecret);\n//   console.log('-------------------------------------------------');\n//   if(aSecret.substring(0,2) === \"00\") {\n//     console.log(\"found short key!\");\n//     return;\n//   }\n// }\n\nconst apub =\n  '5484455905d3eff34c70980e871f27f05448e66f5a6efbb97cbcba4e927196c2bd9ea272cded91\\\n10a4977afa8d9b16c9139a444ed2d954a794650e5d7cb525204f385e1af81530518563822ecd0f9\\\n524a958d02b3c269e79d6d69850f0968ad567a4404fbb0b19efc8bc73e267b6136b88cafb33299f\\\nf7c7cace3ffab1a88c2c9ee841f88b4c3679b4efc465f5c93cca11d487be57373e4c5926f634c4e\\\nefee6721d01db91cd66321615b2522f96368dbc818875d422140d0edf30bdb97d9721feddcb9ff6\\\n453741a4f687ee46fc54bf1198801f1210ac789879a5ee123f79e2d2ce1209df2445d32166bc9e4\\\n8f89e944ec9c3b2e16c8066cd8eebd4e33eb941';\nconst bpub =\n  '3fca64510e36bc7da8a3a901c7b74c2eabfa25deaf7cbe1d0c50235866136ad677317279e1fb0\\\n06e9c0a07f63e14a3363c8e016fbbde2b2c7e79fed1cc3e08e95f7459f547a8cd0523ee9dc744d\\\ne5a956d92b937db4448917e1f6829437f05e408ee7aea70c0362b37370c7c75d14449d8b2d2133\\\n04ac972302d349975e2265ca7103cfebd019d9e91234d638611abd049014f7abf706c1c5da6c88\\\n788a1fdc6cdf17f5fffaf024ce8711a2ebde0b52e9f1cb56224483826d6e5ac6ecfaae07b75d20\\\n6e8ac97f5be1a5b68f20382f2a7dac189cf169325c4cf845b26a0cd616c31fec905c5d9035e5f7\\\n8e9880c812374ac0f3ca3d365f06e4be526b5affd4b79';\nconst apriv =\n  '62411e34704637d99c6c958a7db32ac22fcafafbe1c33d2cfdb76e12ded41f38fc16b792b9041\\\n2e4c82755a3815ba52f780f0ee296ad46e348fc4d1dcd6b64f4eea1b231b2b7d95c5b1c2e26d34\\\n83520558b9860a6eb668f01422a54e6604aa7702b4e67511397ef3ecb912bff1a83899c5a5bfb2\\\n0ee29249a91b8a698e62486f7009a0e9eaebda69d77ecfa2ca6ba2db6c8aa81759c8c90c675979\\\n08c3b3e6fc60668f7be81cce6784482af228dd7f489005253a165e292802cfd0399924f6c56827\\\n7012f68255207722355634290acc7fddeefbba75650a85ece95b6a12de67eac016ba78960108dd\\\n5dbadfaa43cc9fed515a1f307b7d90ae0623bc7b8cefb';\nconst secret =\n  '00c37b1e06a436d6717816a40e6d72907a6f255638b93032267dcb9a5f0b4a9aa0236f3dce63b\\\n1c418c60978a00acd1617dfeecf1661d8a3fafb4d0d8824386750f4853313400e7e4afd22847e4\\\nfa56bc9713872021265111906673b38db83d10cbfa1dea3b6b4c97c8655f4ae82125281af7f234\\\n8916a15c6f95649367d169d587697480df4d10b381479e86d5518b520d9d8fb764084eab518224\\\ndc8fe984ddaf532fc1531ce43155fa0ab32532bf1ece5356b8a3447b5267798a904f16f3f4e635\\\n597adc0179d011132dcffc0bbcb0dd2c8700872f8663ec7ddd897c659cc2efebccc73f38f0ec96\\\n8612314311231f905f91c63a1aea52e0b60cead8b57df';\n\nexport const dh_padding_test = {\n  test(ctrl, env, ctx) {\n    /* FIPS-friendly 2048 bit prime */\n    const p = crypto.createDiffieHellman(\n      crypto.getDiffieHellman('modp14').getPrime()\n    );\n\n    p.setPublicKey(apub, 'hex');\n    p.setPrivateKey(apriv, 'hex');\n\n    assert.strictEqual(\n      p.computeSecret(bpub, 'hex', 'hex').toString('hex'),\n      secret\n    );\n  },\n};\n\nexport const dhKeygenTest = {\n  test() {\n    // FIPS-mode BoringSSL mandates keys of at least 1024 bits. RFC 8270 recommends that sizes of\n    // at least 2048 bits should be used, 1024-bit primes are sufficient for these tests though.\n    const size = 1024;\n    const dh1 = crypto.createDiffieHellman(size);\n    const p1 = dh1.getPrime('buffer');\n    const dh2 = crypto.createDiffieHellman(p1, 'buffer');\n    const key1 = dh1.generateKeys();\n    const key2 = dh2.generateKeys('hex');\n    const secret1 = dh1.computeSecret(key2, 'hex', 'base64');\n    const secret2 = dh2.computeSecret(key1, 'latin1', 'buffer');\n\n    // Test Diffie-Hellman with two parties sharing a secret,\n    // using various encodings as we go along\n    assert.strictEqual(secret2.toString('base64'), secret1);\n\n    assert.strictEqual(dh1.verifyError, 0);\n    assert.strictEqual(dh2.verifyError, 0);\n\n    // Create \"another dh1\" using generated keys from dh1,\n    // and compute secret again\n    const dh3 = crypto.createDiffieHellman(p1, 'buffer');\n    const privkey1 = dh1.getPrivateKey();\n    dh3.setPublicKey(key1);\n    dh3.setPrivateKey(privkey1);\n\n    assert.deepStrictEqual(dh1.getPrime(), dh3.getPrime());\n    assert.deepStrictEqual(dh1.getGenerator(), dh3.getGenerator());\n    assert.deepStrictEqual(dh1.getPublicKey(), dh3.getPublicKey());\n    assert.deepStrictEqual(dh1.getPrivateKey(), dh3.getPrivateKey());\n    assert.strictEqual(dh3.verifyError, 0);\n\n    const secret3 = dh3.computeSecret(key2, 'hex', 'base64');\n\n    assert.strictEqual(secret1, secret3);\n\n    // computeSecret works without a public key set at all.\n    const dh4 = crypto.createDiffieHellman(p1, 'buffer');\n    dh4.setPrivateKey(privkey1);\n\n    assert.deepStrictEqual(dh1.getPrime(), dh4.getPrime());\n    assert.deepStrictEqual(dh1.getGenerator(), dh4.getGenerator());\n    assert.deepStrictEqual(dh1.getPrivateKey(), dh4.getPrivateKey());\n    assert.strictEqual(dh4.verifyError, 0);\n\n    const secret4 = dh4.computeSecret(key2, 'hex', 'base64');\n\n    assert.strictEqual(secret1, secret4);\n\n    assert.throws(\n      () => {\n        dh3.computeSecret('');\n      },\n      { name: 'Error' }\n    );\n  },\n};\n\nexport const ecdh = {\n  test() {\n    const curves = crypto.getCurves();\n    curves.forEach((i) => {\n      const alice = crypto.createECDH(i);\n      const bob = crypto.createECDH(i);\n\n      alice.generateKeys();\n      bob.generateKeys();\n\n      const aliceSecret = alice.computeSecret(bob.getPublicKey(), null, 'hex');\n      const bobSecret = bob.computeSecret(alice.getPublicKey(), null, 'hex');\n\n      assert.strictEqual(aliceSecret, bobSecret);\n    });\n  },\n};\n\nexport const ecdhConvertKey = {\n  test() {\n    const ecdh = crypto.createECDH('prime256v1');\n    ecdh.generateKeys();\n\n    const compressedKey = ecdh.getPublicKey('hex', 'compressed');\n\n    const uncompressedKey = crypto.ECDH.convertKey(\n      compressedKey,\n      'prime256v1',\n      'hex',\n      'hex',\n      'uncompressed'\n    );\n\n    // The converted key and the uncompressed public key should be the same\n    assert.strictEqual(uncompressedKey, ecdh.getPublicKey('hex'));\n  },\n};\n\nexport const statelessDh = {\n  test() {\n    // DH keygen is currently unsupported by the boringssl+fips\n    // we use internally. This test works for workerd but fails\n    // on the internal run.\n    // const pair1 = crypto.generateKeyPairSync('dh', {\n    //   prime: Buffer.from([31, 0, 0, 0, 0, 1]),\n    // });\n    // const pair2 = crypto.generateKeyPairSync('dh', {\n    //   prime: Buffer.from([31, 0, 0, 0, 0, 1]),\n    // });\n    // const sec1 = crypto.diffieHellman({\n    //   publicKey: pair1.publicKey,\n    //   privateKey: pair2.privateKey,\n    // });\n    // const sec2 = crypto.diffieHellman({\n    //   publicKey: pair2.publicKey,\n    //   privateKey: pair1.privateKey,\n    // });\n    // assert.deepStrictEqual(sec1, sec2);\n  },\n};\n\nexport const statelessDhX25591 = {\n  test() {\n    const pair1 = crypto.generateKeyPairSync('x25519');\n    const pair2 = crypto.generateKeyPairSync('x25519');\n    const sec1 = crypto.diffieHellman({\n      publicKey: pair1.publicKey,\n      privateKey: pair2.privateKey,\n    });\n    const sec2 = crypto.diffieHellman({\n      publicKey: pair2.publicKey,\n      privateKey: pair1.privateKey,\n    });\n    assert.deepStrictEqual(sec1, sec2);\n  },\n};\n\nexport const statelessDhEc = {\n  test() {\n    // The Boringssl-based implementation currently does not support\n    // the mechanisms for stateless dh using named EC curve.\n    const pair1 = crypto.generateKeyPairSync('ec', { namedCurve: 'secp224r1' });\n    const pair2 = crypto.generateKeyPairSync('ec', { namedCurve: 'secp224r1' });\n    assert.throws(\n      () => {\n        crypto.diffieHellman({\n          publicKey: pair1.publicKey,\n          privateKey: pair2.privateKey,\n        });\n      },\n      {\n        message: /Failed to derive/,\n      }\n    );\n  },\n};\n\nexport const helloTest = {\n  test() {\n    const v2 = crypto.createDiffieHellman('hello');\n    assert.throws(() => v2.getPrivateKey(v2, 'hello', v2, v2, 'hello'), {\n      message: /No private key/,\n    });\n  },\n};\n\nexport const DhLargeGeneratorParam = {\n  async test() {\n    const prime = Buffer.from([\n      0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda,\n      0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c,\n      0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, 0xcc, 0x74,\n    ]);\n\n    const generator = new Uint8Array(100 * 1024 * 1024);\n    generator.fill(0x02);\n\n    assert.throws(() => crypto.createDiffieHellman(prime, generator), {\n      name: 'RangeError',\n      message: /generator is too large/,\n    });\n  },\n};\n\n// Test that invalid public keys are rejected by setPublicKey and computeSecret.\nexport const dhPubKeyValidation = {\n  test() {\n    const dh = crypto.createDiffieHellman(1024);\n    dh.generateKeys();\n    const prime = dh.getPrime();\n\n    // Public key of 0 should be rejected (too small)\n    const dh2 = crypto.createDiffieHellman(prime);\n    dh2.generateKeys();\n    assert.throws(() => dh2.setPublicKey(Buffer.from([0x00])), {\n      message: /too small|invalid public key/,\n    });\n\n    // Public key of 1 should be rejected (too small)\n    assert.throws(() => dh2.setPublicKey(Buffer.from([0x01])), {\n      message: /too small|invalid public key/,\n    });\n\n    // Public key equal to the prime should be rejected (too large)\n    assert.throws(() => dh2.setPublicKey(prime), {\n      message: /too large|invalid public key/,\n    });\n\n    // computeSecret should also reject invalid peer keys proactively\n    assert.throws(() => dh2.computeSecret(Buffer.from([0x00])), {\n      message: /too small|invalid peer public key/,\n    });\n    assert.throws(() => dh2.computeSecret(Buffer.from([0x01])), {\n      message: /too small|invalid peer public key/,\n    });\n    assert.throws(() => dh2.computeSecret(prime), {\n      message: /too large|invalid peer public key/,\n    });\n\n    // Valid exchange should still work\n    const dh3 = crypto.createDiffieHellman(prime);\n    dh3.generateKeys();\n    const secret1 = dh.computeSecret(dh3.getPublicKey());\n    const secret2 = dh3.computeSecret(dh.getPublicKey());\n    assert.deepStrictEqual(secret1, secret2);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_dh-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"crypto_dh-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"crypto_dh-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_hash-test.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n'use strict';\n\nimport { Buffer } from 'node:buffer';\nimport * as assert from 'node:assert';\nimport * as crypto from 'node:crypto';\nimport * as stream from 'node:stream';\n\nlet cryptoType;\nlet digest;\n\nexport const hash_correctness_tests = {\n  async test(ctrl, env, ctx) {\n    const a1 = crypto.createHash('sha1').update('Test123').digest('hex');\n    const a2 = crypto.createHash('sha256').update('Test123').digest('base64');\n    const a3 = crypto.createHash('sha512').update('Test123').digest(); // buffer\n    const a4 = crypto.createHash('sha1').update('Test123').digest('buffer');\n\n    // stream interface\n    let a5 = crypto.createHash('sha512');\n    a5.end('Test123');\n    a5 = a5.read();\n\n    let a6 = crypto.createHash('sha512');\n    a6.write('Te');\n    a6.write('st');\n    a6.write('123');\n    a6.end();\n    a6 = a6.read();\n\n    let a7 = crypto.createHash('sha512');\n    a7.end();\n    a7 = a7.read();\n\n    let a8 = crypto.createHash('sha512');\n    a8.write('');\n    a8.end();\n    a8 = a8.read();\n\n    cryptoType = 'md5';\n    digest = 'latin1';\n    const a0 = crypto.createHash(cryptoType).update('Test123').digest(digest);\n    assert.strictEqual(\n      a0,\n      'h\\u00ea\\u00cb\\u0097\\u00d8o\\fF!\\u00fa+\\u000e\\u0017\\u00ca\\u00bd\\u008c',\n      `${cryptoType} with ${digest} digest failed to evaluate to expected hash`\n    );\n\n    cryptoType = 'md5';\n    digest = 'hex';\n    assert.strictEqual(\n      a1,\n      '8308651804facb7b9af8ffc53a33a22d6a1c8ac2',\n      `${cryptoType} with ${digest} digest failed to evaluate to expected hash`\n    );\n    cryptoType = 'sha256';\n    digest = 'base64';\n    assert.strictEqual(\n      a2,\n      '2bX1jws4GYKTlxhloUB09Z66PoJZW+y+hq5R8dnx9l4=',\n      `${cryptoType} with ${digest} digest failed to evaluate to expected hash`\n    );\n\n    cryptoType = 'sha512';\n    digest = 'latin1';\n    assert.deepStrictEqual(\n      a3,\n      Buffer.from(\n        \"\\u00c1(4\\u00f1\\u0003\\u001fd\\u0097!O'\\u00d4C/&Qz\\u00d4\" +\n          '\\u0094\\u0015l\\u00b8\\u008dQ+\\u00db\\u001d\\u00c4\\u00b5}\\u00b2' +\n          '\\u00d6\\u0092\\u00a3\\u00df\\u00a2i\\u00a1\\u009b\\n\\n*\\u000f' +\n          '\\u00d7\\u00d6\\u00a2\\u00a8\\u0085\\u00e3<\\u0083\\u009c\\u0093' +\n          \"\\u00c2\\u0006\\u00da0\\u00a1\\u00879(G\\u00ed'\",\n        'latin1'\n      ),\n      `${cryptoType} with ${digest} digest failed to evaluate to expected hash`\n    );\n\n    cryptoType = 'sha1';\n    digest = 'hex';\n    assert.deepStrictEqual(\n      a4,\n      Buffer.from('8308651804facb7b9af8ffc53a33a22d6a1c8ac2', 'hex'),\n      `${cryptoType} with ${digest} digest failed to evaluate to expected hash`\n    );\n\n    cryptoType = 'sha1';\n    digest = 'hex';\n    assert.deepStrictEqual(\n      a4,\n      Buffer.from('8308651804FACB7B9AF8FFC53A33A22D6A1C8AC2', 'hex'),\n      `${cryptoType} with ${digest} digest failed to evaluate to expected hash`\n    );\n\n    // Stream interface should produce the same result.\n    assert.deepStrictEqual(a5, a3);\n    assert.deepStrictEqual(a6, a3);\n    assert.notStrictEqual(a7, undefined);\n    assert.notStrictEqual(a8, undefined);\n\n    // Test multiple updates to same hash\n    const h1 = crypto.createHash('sha1').update('Test123').digest('hex');\n    const h2 = crypto\n      .createHash('sha1')\n      .update('Test')\n      .update('123')\n      .digest('hex');\n    assert.strictEqual(h1, h2);\n  },\n};\n\nexport const hash_error_test = {\n  async test(ctrl, env, ctx) {\n    // Issue https://github.com/nodejs/node-v0.x-archive/issues/2227: unknown digest\n    // method should throw an error.\n    assert.throws(function () {\n      crypto.createHash('xyzzy');\n    }, /Digest method not supported/);\n\n    // Issue https://github.com/nodejs/node/issues/9819: throwing encoding used to\n    // segfault.\n    assert.throws(\n      () =>\n        crypto.createHash('sha256').digest({\n          toString: () => {\n            throw new Error('boom');\n          },\n        }),\n      {\n        name: 'Error',\n        message: 'boom',\n      }\n    );\n\n    // Issue https://github.com/nodejs/node/issues/25487: error message for invalid\n    // arg type to update method should include all possible types\n    assert.throws(() => crypto.createHash('sha256').update(), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n    });\n\n    // Default UTF-8 encoding\n    const hutf8 = crypto\n      .createHash('sha512')\n      .update('УТФ-8 text')\n      .digest('hex');\n    assert.strictEqual(\n      hutf8,\n      '4b21bbd1a68e690a730ddcb5a8bc94ead9879ffe82580767ad7ec6fa8ba2dea6' +\n        '43a821af66afa9a45b6a78c712fecf0e56dc7f43aef4bcfc8eb5b4d8dca6ea5b'\n    );\n\n    assert.notStrictEqual(\n      hutf8,\n      crypto.createHash('sha512').update('УТФ-8 text', 'latin1').digest('hex')\n    );\n\n    const h3 = crypto.createHash('sha256');\n    h3.digest();\n\n    assert.throws(() => h3.digest(), {\n      code: 'ERR_CRYPTO_HASH_FINALIZED',\n      name: 'Error',\n    });\n\n    assert.throws(() => h3.update('foo'), {\n      code: 'ERR_CRYPTO_HASH_FINALIZED',\n      name: 'Error',\n    });\n\n    assert.strictEqual(\n      crypto.createHash('sha256').update('test').digest('ucs2'),\n      crypto.createHash('sha256').update('test').digest().toString('ucs2')\n    );\n\n    assert.throws(() => crypto.createHash(), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n      message:\n        'The \"algorithm\" argument must be of type string. ' +\n        'Received undefined',\n    });\n\n    {\n      const Hash = crypto.Hash;\n      const instance = crypto.Hash('sha256');\n      assert.ok(\n        instance instanceof Hash,\n        'Hash is expected to return a new instance' +\n          ' when called without `new`'\n      );\n    }\n\n    // shake*-based tests for XOF hash function are not supported in FIPS and have been removed.\n    {\n      // Non-XOF hash functions should accept valid outputLength options as well.\n      assert.strictEqual(\n        crypto.createHash('sha224', { outputLength: 28 }).digest('hex'),\n        'd14a028c2a3a2bc9476102bb288234c4' + '15a2b01f828ea62ac5b3e42f'\n      );\n\n      // Passing invalid sizes should throw during creation.\n      assert.throws(\n        () => {\n          crypto.createHash('sha256', { outputLength: 28 });\n        },\n        {\n          name: 'Error',\n        }\n      );\n\n      for (const outputLength of [null, {}, 'foo', false]) {\n        assert.throws(() => crypto.createHash('sha256', { outputLength }), {\n          code: 'ERR_INVALID_ARG_TYPE',\n        });\n      }\n\n      for (const outputLength of [-1, 0.5, Infinity, 2 ** 90]) {\n        assert.throws(() => crypto.createHash('sha256', { outputLength }), {\n          code: 'ERR_OUT_OF_RANGE',\n        });\n      }\n    }\n  },\n};\n\nexport const hash_copy_test = {\n  async test(ctrl, env, ctx) {\n    const h = crypto.createHash('sha512');\n    h.digest();\n    assert.throws(() => h.copy(), { code: 'ERR_CRYPTO_HASH_FINALIZED' });\n    assert.throws(() => h.digest(), { code: 'ERR_CRYPTO_HASH_FINALIZED' });\n\n    const a = crypto.createHash('sha512').update('abc');\n    const b = a.copy();\n    const c = b.copy().update('def');\n    const d = crypto.createHash('sha512').update('abcdef');\n    assert.strictEqual(a.digest('hex'), b.digest('hex'));\n    assert.strictEqual(c.digest('hex'), d.digest('hex'));\n  },\n};\n\nexport const hash_pipe_test = {\n  async test(ctrl, env, ctx) {\n    const p = Promise.withResolvers();\n\n    const s = new stream.PassThrough();\n    const h = crypto.createHash('sha512');\n    const expect =\n      'fba055c6fd0c5b6645407749ed7a8b41' +\n      'b8f629f2163c3ca3701d864adabda1f8' +\n      '93c37bf82b22fdd151ba8e357f611da4' +\n      '88a74b6a5525dd9b69554c6ce5138ad7';\n\n    s.pipe(h)\n      .on('data', function (c) {\n        // Calling digest() after piping into a stream with SHA3 should not cause\n        // a segmentation fault, see https://github.com/nodejs/node/issues/28245.\n        if (c !== expect || h.digest('hex') !== expect) {\n          p.reject('Unexpected value for stream-based hash');\n        }\n        p.resolve();\n      })\n      .setEncoding('hex');\n\n    s.end('aoeu');\n    await p.promise;\n  },\n};\n\nexport const hash_one_shot = {\n  test() {\n    const string = 'Node.js';\n    assert.strictEqual(\n      crypto.hash('sha1', 'Node.js'),\n      '10b3493287f831e81a438811a1ffba01f8cec4b7'\n    );\n\n    const check = Buffer.from(\n      '10b3493287f831e81a438811a1ffba01f8cec4b7',\n      'hex'\n    );\n    const base64 = 'Tm9kZS5qcw==';\n    assert.deepStrictEqual(\n      crypto.hash('sha1', Buffer.from(base64, 'base64'), 'buffer'),\n      check\n    );\n\n    assert.throws(() => crypto.hash(1), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    assert.throws(() => crypto.hash('sha1', 12), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    assert.throws(() => crypto.hash('sha1', 'test', 123), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    assert.throws(() => crypto.hash('unknown', 'what'), {\n      message: /Digest method not supported/,\n    });\n  },\n};\n\nexport const HashCopyAfterDigest = {\n  async test() {\n    for (let i = 0; i < 50; i++) {\n      const hash = crypto.createHash('sha256');\n      hash.update('x' + i);\n\n      const kHandle = Object.getOwnPropertySymbols(hash).find(\n        (s) => s.toString() === 'Symbol(kHandle)'\n      );\n      const handle = hash[kHandle];\n      handle.digest();\n\n      assert.throws(() => hash.copy(), {\n        message: /already been finalized/,\n      });\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_hash-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"crypto_hash-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"crypto_hash-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_hkdf-test.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n'use strict';\n\nimport { Buffer, kMaxLength } from 'node:buffer';\nimport * as assert from 'node:assert';\n\nimport {\n  // createSecretKey,\n  hkdf,\n  hkdfSync,\n  getHashes,\n  createSecretKey,\n} from 'node:crypto';\n\nexport const hkdf_error_tests = {\n  async test(ctrl, env, ctx) {\n    assert.throws(() => hkdf(), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      message: /The \"digest\" argument must be of type string/,\n    });\n\n    [1, {}, [], false, Infinity].forEach((i) => {\n      assert.throws(() => hkdf(i, 'a'), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        message: /^The \"digest\" argument must be of type string/,\n      });\n      assert.throws(() => hkdfSync(i, 'a'), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        message: /^The \"digest\" argument must be of type string/,\n      });\n    });\n\n    [1, {}, [], false, Infinity].forEach((i) => {\n      assert.throws(() => hkdf('sha256', i), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        message: /^The \"ikm\" argument must be /,\n      });\n      assert.throws(() => hkdfSync('sha256', i), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        message: /^The \"ikm\" argument must be /,\n      });\n    });\n\n    [1, {}, [], false, Infinity].forEach((i) => {\n      assert.throws(() => hkdf('sha256', 'secret', i), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        message: /^The \"salt\" argument must be /,\n      });\n      assert.throws(() => hkdfSync('sha256', 'secret', i), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        message: /^The \"salt\" argument must be /,\n      });\n    });\n\n    [1, {}, [], false, Infinity].forEach((i) => {\n      assert.throws(() => hkdf('sha256', 'secret', 'salt', i), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        message: /^The \"info\" argument must be /,\n      });\n      assert.throws(() => hkdfSync('sha256', 'secret', 'salt', i), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        message: /^The \"info\" argument must be /,\n      });\n    });\n\n    ['test', {}, [], false].forEach((i) => {\n      assert.throws(() => hkdf('sha256', 'secret', 'salt', 'info', i), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        message: /^The \"length\" argument must be of type number/,\n      });\n      assert.throws(() => hkdfSync('sha256', 'secret', 'salt', 'info', i), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        message: /^The \"length\" argument must be of type number/,\n      });\n    });\n\n    assert.throws(() => hkdf('sha256', 'secret', 'salt', 'info', -1), {\n      code: 'ERR_OUT_OF_RANGE',\n    });\n    assert.throws(() => hkdfSync('sha256', 'secret', 'salt', 'info', -1), {\n      code: 'ERR_OUT_OF_RANGE',\n    });\n    assert.throws(\n      () => hkdf('sha256', 'secret', 'salt', 'info', kMaxLength + 1),\n      {\n        code: 'ERR_OUT_OF_RANGE',\n      }\n    );\n    assert.throws(\n      () => hkdfSync('sha256', 'secret', 'salt', 'info', kMaxLength + 1),\n      {\n        code: 'ERR_OUT_OF_RANGE',\n      }\n    );\n\n    {\n      const p = Promise.withResolvers();\n      hkdf('unknown', 'a', '', '', 10, (err, asyncResult) => {\n        if (err) {\n          return p.reject(err);\n        }\n        p.resolve();\n      });\n      await assert.rejects(p.promise);\n    }\n    assert.throws(() => hkdfSync('unknown', 'a', '', '', 10), {\n      name: 'TypeError',\n    });\n\n    assert.throws(() => hkdf('unknown', 'a', '', Buffer.alloc(1025), 10), {\n      code: 'ERR_OUT_OF_RANGE',\n    });\n    assert.throws(() => hkdfSync('unknown', 'a', '', Buffer.alloc(1025), 10), {\n      code: 'ERR_OUT_OF_RANGE',\n    });\n\n    {\n      const p = Promise.withResolvers();\n      hkdf('sha512', 'a', '', '', 64 * 255 + 1, (err, asyncResult) => {\n        if (err) {\n          return p.reject(err);\n        }\n        p.resolve();\n      });\n      await assert.rejects(p.promise);\n    }\n    assert.throws(() => hkdfSync('sha512', 'a', '', '', 64 * 255 + 1), {\n      name: 'RangeError',\n    });\n  },\n};\n\nasync function hkdfTestAlg([hash, secret, salt, info, length]) {\n  {\n    const syncResult = hkdfSync(hash, secret, salt, info, length);\n    assert.ok(syncResult instanceof ArrayBuffer);\n    let is_async = false;\n    const p = Promise.withResolvers();\n\n    hkdf(hash, secret, salt, info, length, (err, asyncResult) => {\n      if (err) return p.reject(err);\n      assert.ok(is_async);\n      assert.ok(asyncResult instanceof ArrayBuffer);\n      assert.deepStrictEqual(syncResult, asyncResult);\n      p.resolve();\n    });\n    // Keep this after the hkdf call above. This verifies\n    // that the callback is invoked asynchronously.\n    is_async = true;\n    await p.promise;\n  }\n\n  {\n    const buf_secret = Buffer.from(secret);\n    const buf_salt = Buffer.from(salt);\n    const buf_info = Buffer.from(info);\n    const p = Promise.withResolvers();\n\n    const syncResult = hkdfSync(hash, buf_secret, buf_salt, buf_info, length);\n    hkdf(hash, buf_secret, buf_salt, buf_info, length, (err, asyncResult) => {\n      if (err) return p.reject(err);\n      assert.deepStrictEqual(syncResult, asyncResult);\n      p.resolve();\n    });\n    await p.promise;\n  }\n\n  // Disabled for now, requires KeyObject support\n  // {\n  //   const key_secret = createSecretKey(Buffer.from(secret));\n  //   const buf_salt = Buffer.from(salt);\n  //   const buf_info = Buffer.from(info);\n  //   const p = Promise.withResolvers();\n  //\n  //   const syncResult = hkdfSync(hash, key_secret, buf_salt, buf_info, length);\n  //   hkdf(hash, key_secret, buf_salt, buf_info, length, (err, asyncResult) => {\n  //        if (err) return p.reject(err);\n  //        assert.deepStrictEqual(syncResult, asyncResult);\n  //        p.resolve();\n  //   });\n  //   await p.promise;\n  // }\n\n  {\n    const p = Promise.withResolvers();\n    const ta_secret = new Uint8Array(Buffer.from(secret));\n    const ta_salt = new Uint16Array(Buffer.from(salt));\n    const ta_info = new Uint32Array(Buffer.from(info));\n\n    const syncResult = hkdfSync(hash, ta_secret, ta_salt, ta_info, length);\n    hkdf(hash, ta_secret, ta_salt, ta_info, length, (err, asyncResult) => {\n      if (err) return p.reject(err);\n      assert.deepStrictEqual(syncResult, asyncResult);\n      p.resolve();\n    });\n    await p.promise;\n\n    const syncResultBuf = hkdfSync(\n      hash,\n      ta_secret.buffer,\n      ta_salt.buffer,\n      ta_info.buffer,\n      length\n    );\n    assert.deepStrictEqual(syncResult, syncResultBuf);\n  }\n\n  {\n    const p = Promise.withResolvers();\n    const ta_secret = new Uint8Array(Buffer.from(secret));\n    const a_salt = new ArrayBuffer(0);\n    const a_info = new ArrayBuffer(1);\n\n    const syncResult = hkdfSync(hash, ta_secret.buffer, a_salt, a_info, length);\n    hkdf(hash, ta_secret, a_salt, a_info, length, (err, asyncResult) => {\n      if (err) return p.reject(err);\n      assert.deepStrictEqual(syncResult, asyncResult);\n      p.resolve();\n    });\n    await p.promise;\n  }\n}\n\nexport const hkdf_correctness_tests = {\n  async test(ctrl, env, ctx) {\n    const algorithms = [\n      ['sha256', 'secret', 'salt', 'info', 10],\n      ['sha256', '', '', '', 10],\n      ['sha256', '', 'salt', '', 10],\n      ['sha512', 'secret', 'salt', '', 15],\n    ];\n    for (const element of algorithms) {\n      await hkdfTestAlg(element);\n    }\n\n    getHashes().forEach((hash) => {\n      assert.ok(hkdfSync(hash, 'key', 'salt', 'info', 5));\n    });\n  },\n};\n\nexport const hkdf_secret_key = {\n  async test() {\n    const check = 'ae1db23b1051ea6bc1f182e5114d869c1b5e0b8d';\n\n    const key = createSecretKey('abcdef01020304', 'hex');\n    const result = hkdfSync('sha256', key, 'hello', 'there', 20);\n    assert.strictEqual(Buffer.from(result).toString('hex'), check);\n\n    const { promise, resolve, reject } = Promise.withResolvers();\n\n    hkdf('sha256', key, 'hello', 'there', 20, (err, result) => {\n      if (err) return reject(err);\n      resolve(Buffer.from(result).toString('hex'));\n    });\n\n    assert.strictEqual(await promise, check);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_hkdf-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"crypto_hkdf-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"crypto_hkdf-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_hmac-test.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n'use strict';\n\nimport { Buffer } from 'node:buffer';\nimport * as assert from 'node:assert';\nimport * as crypto from 'node:crypto';\n\nexport const hmac_error_tests = {\n  async test(ctrl, env, ctx) {\n    {\n      const Hmac = crypto.Hmac;\n      const instance = crypto.Hmac('sha256', 'Node');\n      assert.ok(\n        instance instanceof Hmac,\n        'Hmac is expected to return a new instance' +\n          ' when called without `new`'\n      );\n    }\n\n    assert.throws(() => crypto.createHmac(null), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n      message: 'The \"hmac\" argument must be of type string. Received null',\n    });\n\n    assert.throws(\n      () =>\n        crypto.createHmac('sha256', 'key').digest({\n          toString: () => {\n            throw new Error('boom');\n          },\n        }),\n      {\n        name: 'Error',\n        message: 'boom',\n      }\n    );\n\n    assert.throws(() => crypto.createHmac('sha1', null), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n    });\n  },\n};\n\nfunction testHmac(algo, key, data, expected) {\n  if (!Array.isArray(data)) data = [data];\n\n  // If the key is a Buffer, test Hmac with a key object as well.\n  const keyWrappers = [\n    (key) => key,\n    ...(typeof key === 'string' ? [] : [crypto.createSecretKey]),\n  ];\n\n  for (const keyWrapper of keyWrappers) {\n    const hmac = crypto.createHmac(algo, keyWrapper(key));\n    for (const chunk of data) hmac.update(chunk);\n    const actual = hmac.digest('hex');\n    assert.strictEqual(actual, expected);\n  }\n}\n\nexport const hmac_correctness_tests = {\n  async test(ctrl, env, ctx) {\n    {\n      // Test HMAC with multiple updates.\n      testHmac(\n        'sha1',\n        'Node',\n        ['some data', 'to hmac'],\n        '19fd6e1ba73d9ed2224dd5094a71babe85d9a892'\n      );\n    }\n\n    // Test HMAC (Wikipedia Test Cases)\n    const wikipedia = [\n      {\n        key: 'key',\n        data: 'The quick brown fox jumps over the lazy dog',\n        hmac: {\n          // HMACs lifted from Wikipedia.\n          md5: '80070713463e7749b90c2dc24911e275',\n          sha1: 'de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9',\n          sha256:\n            'f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc' +\n            '2d1a3cd8',\n        },\n      },\n      {\n        key: 'key',\n        data: '',\n        hmac: {\n          // Intermediate test to help debugging.\n          md5: '63530468a04e386459855da0063b6596',\n          sha1: 'f42bb0eeb018ebbd4597ae7213711ec60760843f',\n          sha256:\n            '5d5d139563c95b5967b9bd9a8c9b233a9dedb45072794cd232dc1b74' +\n            '832607d0',\n        },\n      },\n      {\n        key: '',\n        data: 'The quick brown fox jumps over the lazy dog',\n        hmac: {\n          // Intermediate test to help debugging.\n          md5: 'ad262969c53bc16032f160081c4a07a0',\n          sha1: '2ba7f707ad5f187c412de3106583c3111d668de8',\n          sha256:\n            'fb011e6154a19b9a4c767373c305275a5a69e8b68b0b4c9200c383dc' +\n            'ed19a416',\n        },\n      },\n      {\n        key: '',\n        data: '',\n        hmac: {\n          // HMACs lifted from Wikipedia.\n          md5: '74e6f7298a9c2d168935f58c001bad88',\n          sha1: 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d',\n          sha256:\n            'b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c71214' +\n            '4292c5ad',\n        },\n      },\n    ];\n\n    for (const { key, data, hmac } of wikipedia) {\n      for (const hash in hmac) testHmac(hash, key, data, hmac[hash]);\n    }\n\n    // Test HMAC-SHA-* (rfc 4231 Test Cases)\n    const rfc4231 = [\n      {\n        key: Buffer.from('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'),\n        data: Buffer.from('4869205468657265', 'hex'), // 'Hi There'\n        hmac: {\n          sha224: '896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22',\n          sha256:\n            'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c' +\n            '2e32cff7',\n          sha384:\n            'afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c' +\n            '7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6',\n          sha512:\n            '87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b305' +\n            '45e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f170' +\n            '2e696c203a126854',\n        },\n      },\n      {\n        key: Buffer.from('4a656665', 'hex'), // 'Jefe'\n        data: Buffer.from(\n          '7768617420646f2079612077616e7420666f72206e6f74686' + '96e673f',\n          'hex'\n        ), // 'what do ya want for nothing?'\n        hmac: {\n          sha224: 'a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44',\n          sha256:\n            '5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b9' +\n            '64ec3843',\n          sha384:\n            'af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec373' +\n            '6322445e8e2240ca5e69e2c78b3239ecfab21649',\n          sha512:\n            '164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7' +\n            'ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b' +\n            '636e070a38bce737',\n        },\n      },\n      {\n        key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'hex'),\n        data: Buffer.from(\n          'ddddddddddddddddddddddddddddddddddddddddddddddddd' +\n            'ddddddddddddddddddddddddddddddddddddddddddddddddddd',\n          'hex'\n        ),\n        hmac: {\n          sha224: '7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea',\n          sha256:\n            '773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514' +\n            'ced565fe',\n          sha384:\n            '88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e5' +\n            '5966144b2a5ab39dc13814b94e3ab6e101a34f27',\n          sha512:\n            'fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33' +\n            'b2279d39bf3e848279a722c806b485a47e67c807b946a337bee89426' +\n            '74278859e13292fb',\n        },\n      },\n      {\n        key: Buffer.from(\n          '0102030405060708090a0b0c0d0e0f10111213141516171819',\n          'hex'\n        ),\n        data: Buffer.from(\n          'cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc' +\n            'dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd',\n          'hex'\n        ),\n        hmac: {\n          sha224: '6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a',\n          sha256:\n            '82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff4' +\n            '6729665b',\n          sha384:\n            '3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e' +\n            '1f573b4e6801dd23c4a7d679ccf8a386c674cffb',\n          sha512:\n            'b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050' +\n            '361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2d' +\n            'e2adebeb10a298dd',\n        },\n      },\n\n      {\n        key: Buffer.from('0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', 'hex'),\n        // 'Test With Truncation'\n        data: Buffer.from('546573742057697468205472756e636174696f6e', 'hex'),\n        hmac: {\n          sha224: '0e2aea68a90c8d37c988bcdb9fca6fa8',\n          sha256: 'a3b6167473100ee06e0c796c2955552b',\n          sha384: '3abf34c3503b2a23a46efc619baef897',\n          sha512: '415fad6271580a531d4179bc891d87a6',\n        },\n        truncate: true,\n      },\n      {\n        key: Buffer.from(\n          'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaa',\n          'hex'\n        ),\n        // 'Test Using Larger Than Block-Size Key - Hash Key First'\n        data: Buffer.from(\n          '54657374205573696e67204c6172676572205468616e20426' +\n            'c6f636b2d53697a65204b6579202d2048617368204b657920' +\n            '4669727374',\n          'hex'\n        ),\n        hmac: {\n          sha224: '95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e',\n          sha256:\n            '60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f' +\n            '0ee37f54',\n          sha384:\n            '4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05' +\n            '033ac4c60c2ef6ab4030fe8296248df163f44952',\n          sha512:\n            '80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b0137' +\n            '83f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec' +\n            '8b915a985d786598',\n        },\n      },\n      {\n        key: Buffer.from(\n          'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaa',\n          'hex'\n        ),\n        // 'This is a test using a larger than block-size key and a larger ' +\n        // 'than block-size data. The key needs to be hashed before being ' +\n        // 'used by the HMAC algorithm.'\n        data: Buffer.from(\n          '5468697320697320612074657374207573696e672061206c6' +\n            '172676572207468616e20626c6f636b2d73697a65206b6579' +\n            '20616e642061206c6172676572207468616e20626c6f636b2' +\n            'd73697a6520646174612e20546865206b6579206e65656473' +\n            '20746f20626520686173686564206265666f7265206265696' +\n            'e6720757365642062792074686520484d414320616c676f72' +\n            '6974686d2e',\n          'hex'\n        ),\n        hmac: {\n          sha224: '3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1',\n          sha256:\n            '9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f5153' +\n            '5c3a35e2',\n          sha384:\n            '6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82' +\n            '461e99c5a678cc31e799176d3860e6110c46523e',\n          sha512:\n            'e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d' +\n            '20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de04460' +\n            '65c97440fa8c6a58',\n        },\n      },\n    ];\n\n    for (let i = 0, l = rfc4231.length; i < l; i++) {\n      for (const hash in rfc4231[i].hmac) {\n        const str = crypto.createHmac(hash, rfc4231[i].key);\n        str.end(rfc4231[i].data);\n        let strRes = str.read().toString('hex');\n        let actual = crypto\n          .createHmac(hash, rfc4231[i].key)\n          .update(rfc4231[i].data)\n          .digest('hex');\n        if (rfc4231[i].truncate) {\n          actual = actual.substr(0, 32); // first 128 bits == 32 hex chars\n          strRes = strRes.substr(0, 32);\n        }\n        const expected = rfc4231[i].hmac[hash];\n        assert.strictEqual(\n          actual,\n          expected,\n          `Test HMAC-${hash} rfc 4231 case ${i + 1}: ${actual} must be ${expected}`\n        );\n        assert.strictEqual(\n          actual,\n          strRes,\n          `Should get same result from stream (hash: ${hash} and case: ${i + 1})` +\n            ` => ${actual} must be ${strRes}`\n        );\n      }\n    }\n\n    // Test HMAC-MD5/SHA1 (rfc 2202 Test Cases)\n    const rfc2202_md5 = [\n      {\n        key: Buffer.from('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'),\n        data: 'Hi There',\n        hmac: '9294727a3638bb1c13f48ef8158bfc9d',\n      },\n      {\n        key: 'Jefe',\n        data: 'what do ya want for nothing?',\n        hmac: '750c783e6ab0b503eaa86e310a5db738',\n      },\n      {\n        key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'hex'),\n        data: Buffer.from(\n          'ddddddddddddddddddddddddddddddddddddddddddddddddd' +\n            'ddddddddddddddddddddddddddddddddddddddddddddddddddd',\n          'hex'\n        ),\n        hmac: '56be34521d144c88dbb8c733f0e8b3f6',\n      },\n      {\n        key: Buffer.from(\n          '0102030405060708090a0b0c0d0e0f10111213141516171819',\n          'hex'\n        ),\n        data: Buffer.from(\n          'cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc' +\n            'dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd' +\n            'cdcdcdcdcd',\n          'hex'\n        ),\n        hmac: '697eaf0aca3a3aea3a75164746ffaa79',\n      },\n      {\n        key: Buffer.from('0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', 'hex'),\n        data: 'Test With Truncation',\n        hmac: '56461ef2342edc00f9bab995690efd4c',\n      },\n      {\n        key: Buffer.from(\n          'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaa',\n          'hex'\n        ),\n        data: 'Test Using Larger Than Block-Size Key - Hash Key First',\n        hmac: '6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd',\n      },\n      {\n        key: Buffer.from(\n          'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaa',\n          'hex'\n        ),\n        data:\n          'Test Using Larger Than Block-Size Key and Larger Than One ' +\n          'Block-Size Data',\n        hmac: '6f630fad67cda0ee1fb1f562db3aa53e',\n      },\n    ];\n\n    for (const { key, data, hmac } of rfc2202_md5)\n      testHmac('md5', key, data, hmac);\n\n    const rfc2202_sha1 = [\n      {\n        key: Buffer.from('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'),\n        data: 'Hi There',\n        hmac: 'b617318655057264e28bc0b6fb378c8ef146be00',\n      },\n      {\n        key: 'Jefe',\n        data: 'what do ya want for nothing?',\n        hmac: 'effcdf6ae5eb2fa2d27416d5f184df9c259a7c79',\n      },\n      {\n        key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'hex'),\n        data: Buffer.from(\n          'ddddddddddddddddddddddddddddddddddddddddddddd' +\n            'ddddddddddddddddddddddddddddddddddddddddddddd' +\n            'dddddddddd',\n          'hex'\n        ),\n        hmac: '125d7342b9ac11cd91a39af48aa17b4f63f175d3',\n      },\n      {\n        key: Buffer.from(\n          '0102030405060708090a0b0c0d0e0f10111213141516171819',\n          'hex'\n        ),\n        data: Buffer.from(\n          'cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc' +\n            'dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd' +\n            'cdcdcdcdcd',\n          'hex'\n        ),\n        hmac: '4c9007f4026250c6bc8414f9bf50c86c2d7235da',\n      },\n      {\n        key: Buffer.from('0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', 'hex'),\n        data: 'Test With Truncation',\n        hmac: '4c1a03424b55e07fe7f27be1d58bb9324a9a5a04',\n      },\n      {\n        key: Buffer.from(\n          'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaa',\n          'hex'\n        ),\n        data: 'Test Using Larger Than Block-Size Key - Hash Key First',\n        hmac: 'aa4ae5e15272d00e95705637ce8a3b55ed402112',\n      },\n      {\n        key: Buffer.from(\n          'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +\n            'aaaaaaaaaaaaaaaaaaaaaa',\n          'hex'\n        ),\n        data:\n          'Test Using Larger Than Block-Size Key and Larger Than One ' +\n          'Block-Size Data',\n        hmac: 'e8e99d0f45237d786d6bbaa7965c7808bbff1a91',\n      },\n    ];\n\n    for (const { key, data, hmac } of rfc2202_sha1)\n      testHmac('sha1', key, data, hmac);\n  },\n};\n\nexport const hmac_misc_tests = {\n  async test(ctrl, env, ctx) {\n    assert.strictEqual(\n      crypto.createHmac('sha256', 'w00t').digest('ucs2'),\n      crypto.createHmac('sha256', 'w00t').digest().toString('ucs2')\n    );\n\n    // Check initialized -> uninitialized state transition after calling digest().\n    {\n      const expected =\n        '\\u0010\\u0041\\u0052\\u00c5\\u00bf\\u00dc\\u00a0\\u007b\\u00c6\\u0033' +\n        '\\u00ee\\u00bd\\u0046\\u0019\\u009f\\u0002\\u0055\\u00c9\\u00f4\\u009d';\n      {\n        const h = crypto.createHmac('sha1', 'key').update('data');\n        assert.deepStrictEqual(\n          h.digest('buffer'),\n          Buffer.from(expected, 'latin1')\n        );\n        assert.deepStrictEqual(h.digest('buffer'), Buffer.from(''));\n      }\n      {\n        const h = crypto.createHmac('sha1', 'key').update('data');\n        assert.strictEqual(h.digest('latin1'), expected);\n        assert.strictEqual(h.digest('latin1'), '');\n      }\n    }\n\n    // Check initialized -> uninitialized state transition after calling digest().\n    // Calls to update() omitted intentionally.\n    {\n      const expected =\n        '\\u00f4\\u002b\\u00b0\\u00ee\\u00b0\\u0018\\u00eb\\u00bd\\u0045\\u0097' +\n        '\\u00ae\\u0072\\u0013\\u0071\\u001e\\u00c6\\u0007\\u0060\\u0084\\u003f';\n      {\n        const h = crypto.createHmac('sha1', 'key');\n        assert.deepStrictEqual(\n          h.digest('buffer'),\n          Buffer.from(expected, 'latin1')\n        );\n        assert.deepStrictEqual(h.digest('buffer'), Buffer.from(''));\n      }\n      {\n        const h = crypto.createHmac('sha1', 'key');\n        assert.strictEqual(h.digest('latin1'), expected);\n        assert.strictEqual(h.digest('latin1'), '');\n      }\n    }\n\n    {\n      assert.throws(\n        () => crypto.createHmac('sha7', 'key'),\n        /Digest method not supported/\n      );\n    }\n\n    {\n      const buf = Buffer.alloc(0);\n      const keyObject = crypto.createSecretKey(Buffer.alloc(0));\n      assert.deepStrictEqual(\n        crypto.createHmac('sha256', buf).update('foo').digest(),\n        crypto.createHmac('sha256', keyObject).update('foo').digest()\n      );\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_hmac-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"crypto_hmac-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"crypto_hmac-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_keys-test.js",
    "content": "import {\n  notDeepStrictEqual,\n  deepStrictEqual,\n  strictEqual,\n  ok,\n  rejects,\n  throws,\n} from 'node:assert';\nimport {\n  KeyObject,\n  SecretKeyObject,\n  PrivateKeyObject,\n  createSecretKey,\n  createPrivateKey,\n  createPublicKey,\n  createHmac,\n  generateKey,\n  generateKeySync,\n  generateKeyPair,\n  generateKeyPairSync,\n  generatePrimeSync,\n  diffieHellman,\n} from 'node:crypto';\nimport { Buffer } from 'node:buffer';\nimport { promisify } from 'node:util';\n\nexport const secret_key_equals_test = {\n  async test() {\n    const secretKeyData1 = Buffer.from('abcdefghijklmnop');\n    const secretKeyData2 = Buffer.from('abcdefghijklmnop'.repeat(2));\n    const aes1 = await crypto.subtle.importKey(\n      'raw',\n      secretKeyData1,\n      { name: 'AES-CBC' },\n      true,\n      ['encrypt', 'decrypt']\n    );\n    const aes2 = await crypto.subtle.importKey(\n      'raw',\n      secretKeyData1,\n      { name: 'AES-CBC' },\n      true,\n      ['encrypt', 'decrypt']\n    );\n    const aes3 = await crypto.subtle.importKey(\n      'raw',\n      secretKeyData2,\n      { name: 'AES-CBC' },\n      true,\n      ['encrypt', 'decrypt']\n    );\n    const hmac = await crypto.subtle.importKey(\n      'raw',\n      secretKeyData1,\n      {\n        name: 'HMAC',\n        hash: 'SHA-256',\n      },\n      true,\n      ['sign', 'verify']\n    );\n    const hmac2 = await crypto.subtle.importKey(\n      'raw',\n      secretKeyData1,\n      {\n        name: 'HMAC',\n        hash: 'SHA-256',\n      },\n      false,\n      ['sign', 'verify']\n    );\n\n    const aes1_ko = KeyObject.from(aes1);\n    const aes2_ko = KeyObject.from(aes2);\n    const aes3_ko = KeyObject.from(aes3);\n    const hmac_ko = KeyObject.from(hmac);\n    const hmac2_ko = KeyObject.from(hmac2);\n\n    strictEqual(aes1_ko.type, 'secret');\n    strictEqual(aes2_ko.type, 'secret');\n    strictEqual(aes3_ko.type, 'secret');\n    strictEqual(hmac_ko.type, 'secret');\n\n    ok(aes1_ko.equals(aes1_ko));\n    ok(aes1_ko.equals(aes2_ko));\n    ok(aes1_ko.equals(hmac_ko));\n    ok(hmac_ko.equals(aes1_ko));\n    ok(hmac_ko.equals(aes2_ko));\n\n    ok(!aes1_ko.equals(aes3_ko));\n    ok(!hmac_ko.equals(aes3_ko));\n\n    // Unable to determine equality if either key is not extractable.\n    ok(!hmac_ko.equals(hmac2_ko));\n    ok(!hmac2_ko.equals(hmac_ko));\n  },\n};\n\n// In case anyone comes across these tests and wonders why there are\n// keys embedded... these are test keys only. They are not sensitive\n// or secret in any way. They are used only in the test here and are\n// pulled directly from MDN examples.\n\nconst jwkEcKey = {\n  crv: 'P-384',\n  d: 'wouCtU7Nw4E8_7n5C1-xBjB4xqSb_liZhYMsy8MGgxUny6Q8NCoH9xSiviwLFfK_',\n  ext: true,\n  key_ops: ['sign'],\n  kty: 'EC',\n  x: 'SzrRXmyI8VWFJg1dPUNbFcc9jZvjZEfH7ulKI1UkXAltd7RGWrcfFxqyGPcwu6AQ',\n  y: 'hHUag3OvDzEr0uUQND4PXHQTXP5IDGdYhJhL-WLKjnGjQAw0rNGy5V29-aV-yseW',\n};\n\nconst pemEncodedKey = `\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy3Xo3U13dc+xojwQYWoJLCbOQ5fOVY8LlnqcJm1W1BFtxIhOAJWohiHuIRMctv7d\nzx47TLlmARSKvTRjd0dF92jx/xY20Lz+DXp8YL5yUWAFgA3XkO3LSJ\ngEOex10NB8jfkmgSb7QIudTVvbbUDfd5fwIBmCtaCwWx7NyeWWDb7A\n9cFxj7EjRdrDaK3ux/ToMLHFXVLqSL341TkCf4ZQoz96RFPUGPPLOf\nvN0x66CM1PQCkdhzjE6U5XGE964ZkkYUPPsy6Dcie4obhW4vDjgUmL\nzv0z7UD010RLIneUgDE2FqBfY/C+uWigNPBPkkQ+Bv/UigS6dHqTCV\neD5wgyBQIDAQAB\n`;\n\nexport const asymmetric_key_equals_test = {\n  async test() {\n    const jwk1_ec = await crypto.subtle.importKey(\n      'jwk',\n      jwkEcKey,\n      {\n        name: 'ECDSA',\n        namedCurve: 'P-384',\n      },\n      true,\n      ['sign']\n    );\n    const jwk2_ec = await crypto.subtle.importKey(\n      'jwk',\n      jwkEcKey,\n      {\n        name: 'ECDSA',\n        namedCurve: 'P-384',\n      },\n      true,\n      ['sign']\n    );\n    strictEqual(jwk1_ec.type, 'private');\n\n    // fetch the part of the PEM string between header and footer\n    const pemContent = Buffer.from(pemEncodedKey, 'base64');\n\n    const rsa = await crypto.subtle.importKey(\n      'spki',\n      pemContent,\n      {\n        name: 'RSA-OAEP',\n        hash: 'SHA-256',\n      },\n      true,\n      ['encrypt']\n    );\n    const rsa2 = await crypto.subtle.importKey(\n      'spki',\n      pemContent,\n      {\n        name: 'RSA-OAEP',\n        hash: 'SHA-256',\n      },\n      false,\n      ['encrypt']\n    );\n    strictEqual(rsa.type, 'public');\n\n    const jwk1_ko = KeyObject.from(jwk1_ec);\n    const jwk2_ko = KeyObject.from(jwk2_ec);\n    const rsa_ko = KeyObject.from(rsa);\n    const rsa2_ko = KeyObject.from(rsa2);\n\n    strictEqual(jwk1_ko.asymmetricKeyType, 'ec');\n    strictEqual(rsa_ko.asymmetricKeyType, 'rsa');\n    strictEqual(rsa_ko.asymmetricKeyDetails.modulusLength, 2048);\n    strictEqual(rsa_ko.asymmetricKeyDetails.publicExponent, 65537n);\n    strictEqual(jwk1_ko.asymmetricKeyDetails.namedCurve, 'secp384r1');\n\n    ok(jwk1_ko.equals(jwk1_ko));\n    ok(jwk1_ko.equals(jwk2_ko));\n    ok(!rsa_ko.equals(jwk1_ko));\n    ok(!jwk1_ko.equals(rsa_ko));\n    ok(!rsa_ko.equals(rsa2_ko));\n\n    jwk1_ko.export({\n      type: 'pkcs8',\n      format: 'der',\n      cipher: 'aes-128-cbc',\n      passphrase: Buffer.alloc(0),\n    });\n    jwk1_ko.export({\n      type: 'pkcs8',\n      format: 'der',\n      cipher: 'aes-128-cbc',\n      passphrase: '',\n    });\n  },\n};\n\nexport const secret_key_test = {\n  test() {\n    const buf = Buffer.from('hello');\n    strictEqual(buf.toString(), 'hello');\n    const key1 = createSecretKey('hello');\n    const key2 = createSecretKey(buf);\n    const key3 = createSecretKey('there');\n\n    // Creating a zero-length key should also just work.\n    const key4 = createSecretKey('');\n    const key5 = createSecretKey(Buffer.alloc(0));\n    ok(key4.equals(key5));\n    strictEqual(key4.export().toString(), '');\n\n    // The key should be immutable after creation,\n    // so modifying the buf now should have no impact\n    // on the test.\n    buf.fill(0);\n    strictEqual(buf.toString(), '\\0\\0\\0\\0\\0');\n\n    // Now let's make sure the keys we created meet our expectations.\n    strictEqual(key1.export().toString(), 'hello');\n    strictEqual(key2.export().toString(), 'hello');\n    strictEqual(key3.export({ format: 'buffer' }).toString(), 'there');\n\n    strictEqual(key1.toString(), '[object KeyObject]');\n    strictEqual(key2.toString(), '[object KeyObject]');\n    strictEqual(key3.toString(), '[object KeyObject]');\n    ok(key1 instanceof SecretKeyObject);\n    ok(key2 instanceof SecretKeyObject);\n    ok(key3 instanceof SecretKeyObject);\n    strictEqual(key1.type, 'secret');\n    strictEqual(key2.type, 'secret');\n    strictEqual(key3.type, 'secret');\n    ok(key1.equals(key2));\n    ok(!key1.equals(key3));\n    ok(!key3.equals(key1));\n\n    strictEqual(key1.symmetricKeySize, 5);\n\n    const jwk = key3.export({ format: 'jwk' });\n    ok(jwk);\n    strictEqual(typeof jwk, 'object');\n    strictEqual(jwk.kty, 'oct');\n    strictEqual(jwk.ext, true);\n    strictEqual(jwk.k, 'dGhlcmU');\n  },\n};\n\nexport const create_private_key_test_generic = {\n  test(_, env) {\n    // TODO(later): These error messages are inconsistent with one another\n    // despite performing the same basic validation.\n    throws(() => createPrivateKey(1), {\n      message: /The \"options.key\" property must be of type string/,\n    });\n    throws(() => createPrivateKey(true), {\n      message: /The \"options.key\" property must be of type string/,\n    });\n    throws(() => createPrivateKey({ key: 1 }), {\n      message: /The \"key\" argument/,\n    });\n    throws(() => createPrivateKey({ key: true }), {\n      message: /The \"key\" argument/,\n    });\n\n    // The \"bad\" cases here are just the ones that we expect to fail\n    // creating as private keys, generally because boringssl does not\n    // support these variations.\n    [\n      'dh_private.pem',\n      'ec_secp256k1_private.pem',\n      'ed448_private.pem',\n      'rsa_pss_private_2048.pem',\n      'rsa_pss_private_2048_sha1_sha1_20.pem',\n      'rsa_pss_private_2048_sha256_sha256_16.pem',\n      'rsa_pss_private_2048_sha512_sha256_20.pem',\n      'x448_private.pem',\n    ].forEach((i) => {\n      throws(() => createPrivateKey(env[i]), {\n        message: 'Failed to parse private key',\n      });\n    });\n\n    // These next three are encrypted and require a passphrase.\n    createPrivateKey({\n      key: env['rsa_private_encrypted.pem'],\n      passphrase: 'password',\n    });\n\n    // Trying to parse an encrypted private key without specifying a\n    // passphrase, or specifying the wrong passphrase, should fail.\n    throws(\n      () => {\n        createPrivateKey({\n          key: env['rsa_private_encrypted.pem'],\n          passphrase: 'password_bad',\n        });\n      },\n      { message: 'Failed to parse private key' }\n    );\n\n    throws(\n      () => {\n        createPrivateKey({\n          key: env['rsa_private_encrypted.pem'],\n        });\n      },\n      { message: 'Failed to parse private key' }\n    );\n\n    createPrivateKey({\n      key: env['dsa_private_encrypted.pem'],\n      passphrase: 'password',\n    });\n\n    createPrivateKey({\n      key: env['dsa_private_encrypted_1025.pem'],\n      passphrase: 'secret',\n    });\n\n    [\n      'dsa_private.pem',\n      'dsa_private_1025.pem',\n      'dsa_private_pkcs8.pem',\n      'ed25519_private.pem',\n      'ec_p256_private.pem',\n      'ec_p384_private.pem',\n      'ec_p521_private.pem',\n      'rsa_private.pem',\n      'rsa_private_b.pem',\n      'rsa_private_2048.pem',\n      'rsa_private_pkcs8_bad.pem',\n      'rsa_private_4096.pem',\n      'rsa_private_pkcs8.pem',\n      'x25519_private.pem',\n    ].forEach((i) => {\n      try {\n        const key = createPrivateKey(env[i]);\n\n        // These variations should also just work.\n        const buf = Buffer.from(env[i]);\n        createPrivateKey({ key: env[i] });\n        createPrivateKey(buf);\n        createPrivateKey(buf.buffer);\n        createPrivateKey(new DataView(buf.buffer));\n        createPrivateKey({ key: buf });\n        createPrivateKey({ key: buf.buffer });\n        createPrivateKey({ key: new DataView(buf.buffer) });\n\n        ok(key instanceof PrivateKeyObject);\n\n        const details = key.asymmetricKeyDetails;\n        switch (key.asymmetricKeyType) {\n          case 'dsa': {\n            switch (i) {\n              case 'dsa_private.pem': {\n                strictEqual(details.modulusLength, 2048);\n                strictEqual(details.divisorLength, 256);\n                break;\n              }\n              case 'dsa_private_1025.pem': {\n                strictEqual(details.modulusLength, 1088);\n                strictEqual(details.divisorLength, 160);\n                break;\n              }\n              case 'dsa_private_pkcs8.pem': {\n                strictEqual(details.modulusLength, 2048);\n                strictEqual(details.divisorLength, 256);\n                break;\n              }\n            }\n            break;\n          }\n          case 'rsa': {\n            switch (i) {\n              case 'rsa_private.pem': {\n                strictEqual(details.modulusLength, 2048);\n                strictEqual(details.publicExponent, 65537n);\n                break;\n              }\n              case 'rsa_private_b.pem': {\n                strictEqual(details.modulusLength, 2048);\n                strictEqual(details.publicExponent, 65537n);\n                break;\n              }\n              case 'rsa_private_2048.pem': {\n                strictEqual(details.modulusLength, 2048);\n                strictEqual(details.publicExponent, 65537n);\n                break;\n              }\n              case 'rsa_private_pkcs8_bad.pem': {\n                strictEqual(details.modulusLength, 2048);\n                strictEqual(details.publicExponent, 65537n);\n                break;\n              }\n              case 'rsa_private_4096.pem': {\n                strictEqual(details.modulusLength, 4096);\n                strictEqual(details.publicExponent, 65537n);\n                break;\n              }\n              case 'rsa_private_pkcs8.pem': {\n                strictEqual(details.modulusLength, 2048);\n                strictEqual(details.publicExponent, 65537n);\n                break;\n              }\n            }\n            break;\n          }\n          case 'ed25519': {\n            break;\n          }\n          case 'x25519': {\n            break;\n          }\n          case 'ec': {\n            switch (i) {\n              case 'ec_p256_private.pem': {\n                strictEqual(details.namedCurve, 'prime256v1');\n                break;\n              }\n              case 'ec_p384_private.pem': {\n                strictEqual(details.namedCurve, 'secp384r1');\n                break;\n              }\n              case 'ec_p521_private.pem': {\n                strictEqual(details.namedCurve, 'secp521r1');\n                break;\n              }\n            }\n            break;\n          }\n          default:\n            throw new Error('Invalid key type');\n        }\n\n        // Export/Import roundtrip should work\n        {\n          const exported = key.export({ format: 'pem', type: 'pkcs8' });\n          const key2 = createPrivateKey(exported);\n          const exported2 = key2.export({ format: 'pem', type: 'pkcs8' });\n          strictEqual(exported.toString(), exported2.toString());\n          ok(key.equals(key2));\n        }\n        {\n          const exported = key.export({ format: 'der', type: 'pkcs8' });\n          const key2 = createPrivateKey({ key: exported, format: 'der' });\n          const exported2 = key2.export({ format: 'der', type: 'pkcs8' });\n          strictEqual(exported.toString(), exported2.toString());\n          ok(key.equals(key2));\n        }\n\n        if (key.asymmetricKeyType == 'ec') {\n          const exported = key.export({ format: 'der', type: 'sec1' });\n          const key2 = createPrivateKey({\n            key: exported,\n            format: 'der',\n            type: 'sec1',\n          });\n          const exported2 = key2.export({ format: 'der', type: 'sec1' });\n          strictEqual(exported.toString(), exported2.toString());\n        } else {\n          throws(() => key.export({ format: 'der', type: 'sec1' }), {\n            message: 'SEC1 can only be used for EC keys',\n          });\n        }\n      } catch (err) {\n        console.log(`${i} FAILED`, err.message);\n      }\n    });\n  },\n};\n\nexport const private_key_import_type = {\n  test() {\n    // This is a DER-encoded PKCS8 ed25519 private key. In this case, just like\n    // with Node.js, the specific \"type\" argument ends up being ignored.\n    // The actual behavior in Node.js is a bit inconsistent and depends on\n    // the type of key. For instance, for RSA keys, the type argument is\n    // respected and it is not always possible, for instance, to import a\n    // PKCS1 exported key as PKCS8, etc. The edges cases here appear to be\n    // undocumented in Node.js and may not be fully tested in Node.js either.\n    const derData = Buffer.from([\n      0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70,\n      0x04, 0x22, 0x04, 0x20, 0xc1, 0x52, 0xba, 0x33, 0x74, 0x8c, 0x85, 0x08,\n      0x77, 0x34, 0xaf, 0xbb, 0x19, 0x1a, 0xd2, 0x57, 0xe0, 0x55, 0x59, 0x0c,\n      0x75, 0x14, 0xee, 0x69, 0x5b, 0xc8, 0x61, 0x41, 0xeb, 0xbf, 0x35, 0xd0,\n    ]);\n\n    // All three of these variations should work, depsite the type being different.\n    const k1 = createPrivateKey({ key: derData, format: 'der', type: 'pkcs1' });\n    const k2 = createPrivateKey({ key: derData, format: 'der', type: 'pkcs8' });\n    const k3 = createPrivateKey({ key: derData, format: 'der', type: 'sec1' });\n\n    const t1 = k1.export({ format: 'pem', type: 'pkcs8' });\n    const t2 = k2.export({ format: 'pem', type: 'pkcs8' });\n    const t3 = k3.export({ format: 'pem', type: 'pkcs8' });\n    strictEqual(t1.toString(), t2.toString());\n    strictEqual(t1.toString(), t3.toString());\n\n    ok(k1.equals(k2));\n    ok(k2.equals(k3));\n    ok(k3.equals(k1));\n  },\n};\n\nexport const private_key_import_rsa_der = {\n  test() {\n    // A DER-encoded RSA PKCS8 private key\n    const derData = Buffer.from(\n      '30820942020100300d06092a864886f70d01010105000482092c3082092802' +\n        '01000282020100c5e4adc287db8ed66ea25e25c0a9f5c34f00cdc48d2c77c2' +\n        'a8d1476be5bb7abf22b3b2d0b0f963b4f4f8ca7e25b1aa3cde780716f482de' +\n        'd6cc891343a541d8a87db006e524b8e982b589192e2d37d3b1f40b071557a5' +\n        'b93f7b4d12df34209f8faa5471b539277b352d6ce6ac6c88bf6a8ae75561be' +\n        'a6c82a0c6ac5678a13b16b621344af8bbb03a8de44d9d8a3987fd8627967c2' +\n        'ab338331ddb409c0469d5b1b7e04eb8b2a58298ba0609f3dd1edca6fc249dc' +\n        '4e2ceb03575e63af0383650fd3259e6d864c0a651c6eee0f5fc25840d4f888' +\n        'e560cfe2b79735aa9ad9b4a0af3a40963404c58b383742fba24495389417f4' +\n        '111aa16b6af0d2d4f0ca4c746a073435345ebeb42dfaf9f19e6a9fc44612f9' +\n        '53c6809b3c9ccf576ce0cc3fe5984a9fb7a3677d3725bde037b1df314ab381' +\n        '527e28dd2f214b46fd4aeecf06e9078ccf7afded117a822c852c179504cbaf' +\n        '616df0ac2ea723e6d65b4d7130ed8ea1dc21653a060fa43f2f66c5256d1866' +\n        '5800fb6a1b334103f9bea0e8dafce48fc406e53dd967ebdd43683b99b399ee' +\n        '6421b019c2da256822ca8aaf5c07325c3645c87e0e65987caec04b29b9c55c' +\n        '767fce21b4700a3313281910f25aa69c3c2f1fda772ed62ff4898f1a2888a6' +\n        'a973b0677f2f1e48b577f9700ac612d6fcc43a0a127e7bff04c40634d9c6b8' +\n        '3290afab0224f537d5e8533ca561aa1f34058f4bf734bd0203010001028202' +\n        '00536fc7936d94b4f4d450c14149aa5f64a9babd07523e9d80058db77f56ad' +\n        '6563914e12e6cab75bc2c046e599aa6aee4c1bc09fbc9dfb4fd96103aa8baa' +\n        'f1c857c226a5c1976a1f8a6ce0112dd702e2cef50671461e5e516ce29fec85' +\n        '0f8571c1311fc9918f37864b358be4f66e0c7a2881c867c77e8af37a4721fd' +\n        '795a4e534fe35a1c6ba78e824c80eaa6af20cafb9c5068bfc6e44823d8b291' +\n        '664b1b7add1f0a5328bcd46db796975825cbfae737a34757bcfb7914dda3c8' +\n        'b85ee22c544007d6a4a5a92a0677fb350a4a91256ff065db245d12249482b3' +\n        'ce7cd02d5a6b25767a24da69e8a07a63526aa650245a669672e18348ebf17a' +\n        'f869afdc9bbfb9b4af1f3a3140b96f814a9602756c862bc5d5324898748888' +\n        'b22f04a3e9dbd483ea6696213d430bdb0a0b813582aed53b8efada5d7fff31' +\n        '0e018ed72b467b2eb6d93b85971e7b2014ea46b6143783d422fd278cea66f9' +\n        'c442530540539a4846b35c7c8e897371adf49aabfc4fadd6e0f5e45c799974' +\n        'aeefc87e6eb1e888b3478651e6982bcf765a07f1081a36e0908d42201c1ffc' +\n        '13c15e25e3244ea9e1f65784a16c86260b395afbeeefb0f616840c1047d787' +\n        'd3ee2881391a310ae6d7aa65664d1ecd55fd04a2bc2939dde06f7f4f5f15b2' +\n        'd41b8a8c05dbf70a9c62823aaae6a11daf1ce92c41d5095669f0091a94d6cc' +\n        'a6071858d06488a6cf7cbfe954604c4a410282010100fee558136fc778d715' +\n        'fee58023d2eca403571c9137dd314c67c4aec8e62da77318262ce6435b93ec' +\n        'e9b62c46a70e7362cfe7c0421ac4be8d6c0c817dc4e01d7b6aa3207fa20f37' +\n        '0bbc53e00b782442ca458d4df533d38ee71023cc1c5f2f7283886d8cb664d1' +\n        '84db8a257e7a7823aa904a7a58a696380df971c68a607c4dccc4b8f70782bf' +\n        'de593e947dab4275b20cc3ec7b55878cae5c8d35fc10bc174466623b6cb94a' +\n        '3e3e607ab0ed8af78baaaa8d063d4a2cecc860adb7dfdcbdfa12438f826462' +\n        '035015672d6c28ac542aca464379b57e0f8203ff86646724bd8915a2dd6516' +\n        'bc26ad2d9da24f04853d4771525d7befbe8f1ef326c44b80546f2875e49702' +\n        '82010100c6c01fc484238e32a6bb2242cc8bf11bb967bb3a2f772e0e11d4ae' +\n        '24be322b99b7e011c91e7fb21de44872b0e724a5135187df707fce8ce9c94b' +\n        'de0103ac07d6d12ebc6433e8b57a529c00a59d4cff699d9cfd1e0b62f626d4' +\n        '912fc99dc5fcaa5c396858ca43eeb9dc059e8284b119eac5986ba581413011' +\n        '98f55e3f252934d1b6ab29b1377fef44118606e9c3de3eb35f4334b3358ae6' +\n        '253eaac603cb72b308a1bc154ed7c70cd16cfee2011443e47a6d666cb65b9d' +\n        '84260d6e8669555c9326df648405fac3da80e69678c0a9a65daa2e4cab67b8' +\n        'b81ddd84cf51c09a694802c6058ea4f4379a9c015b3a56b0309ae2da993b07' +\n        '4a610a7f83376a60d3e5b7cb028201005e4f0cdf64243199a311c4683cd8f5' +\n        'a5597709a2d1408dd4ef2fde5b868eadbdefd970136228a7faa81e37138d0b' +\n        'd3b563a7238351d4298cb9c586c3b9ec11fc6fe01b4e1deff335ec603c2d02' +\n        '2ea8679e8441abcf991eee6f124f9acfbd06699438b42f67edfd721d12f250' +\n        'edd284710e9d65df7d05106692aa1ad8c82520f648595df60a77821d9d6341' +\n        'd23d29bb7f6227dfe55f2fc41e9b32c01e579d7f24294878e5f751acf0b835' +\n        'ab8d1ba7f1a26c0491453df6858ec0d19b22cf3ba2b39e52f5d0b3f8b74c1f' +\n        '108d7236c2d06c76c3a7f8a4ea45c8bbad4df2b29dc6bc93826deb01783732' +\n        'ae79c5b27e94771d0f960cb377880f77e15781e5feda5fd1028201007f3514' +\n        'a018db10f64654dbd6d94870678841664a157b3854f500a4fd0b66dd1523e5' +\n        '1c3d17722fb4861a009e4d32dd1d023feeb8f874612879183fdd725637263c' +\n        'f8a6c79399cc1da0a60c9bf394069db8ad742c38a97c56da129afd7627f451' +\n        'ad7968d9fb8b834e1e0ed2a742fa7f560e6641efca4cc8d15a8f216555098c' +\n        'aef5359417c327f52221fd208b9a3bb2f1e7750253f95f0f72a32b7655936f' +\n        'b43b40193ba21ce55fc4e2f837faecd78f72f4766bfa43a50ba1b75318606e' +\n        'ac33dadb7c602bdb966351c14469c116544efacf6b6f0191eef5de84549544' +\n        'ab0fdb713b00ef8d9069ce612f550e7fd1812a812bdc8b355d5bc2f65e2ba7' +\n        'c0959f20050282010100baf8445f5361adfb0889993acfaf27c14644a33387' +\n        '5114c0ed8fbee213be44edbbfec4e088f2c40a397640289245900860017228' +\n        '83249118516a9c517e39a900a13a0b66354da877a7fdb60c83171379292f1c' +\n        '137d8d5d5deb257b376c166620a2d23c44dfdee20fb84a453e3a5d479bf98e' +\n        '557e4c38c7e11ac8b44de10c2bf2a535e15d73265a9dd57d86e011f20fd37b' +\n        '91da22212fefe0dc152a6ebcb03c4b1356248d34cfee0434326a65cb5c1d5b' +\n        '43623f39be3523cd8f0f588d0aa1a2ed9bd9dde5581a0513432c3a4383bc82' +\n        '54cf3fd702485557c04f601db2def13a9f4d9096acce9cac65fa05bea1576b' +\n        '6a36c03eec9ebd53d346bb0e4636a5e369f5',\n      'hex'\n    );\n\n    // Just like with Node.js, it is possible to import a PKCS8 private key (in this case an\n    // rsa key) using any of pkcs1, pkcs8, or sec1 as the type value.\n    const k1 = createPrivateKey({ key: derData, format: 'der', type: 'pkcs1' });\n    const k2 = createPrivateKey({ key: derData, format: 'der', type: 'pkcs8' });\n    const k3 = createPrivateKey({ key: derData, format: 'der', type: 'sec1' });\n    ok(k1.equals(k2));\n    ok(k1.equals(k3));\n    ok(k3.equals(k2));\n\n    // A DER-encoded RSA PKCS1 private key\n    const derData2 = Buffer.from(\n      '308209280201000282020100c5e4adc287db8ed66ea25e25c0a9f5c34f' +\n        '00cdc48d2c77c2a8d1476be5bb7abf22b3b2d0b0f963b4f4f8ca7e25b1' +\n        'aa3cde780716f482ded6cc891343a541d8a87db006e524b8e982b58919' +\n        '2e2d37d3b1f40b071557a5b93f7b4d12df34209f8faa5471b539277b35' +\n        '2d6ce6ac6c88bf6a8ae75561bea6c82a0c6ac5678a13b16b621344af8b' +\n        'bb03a8de44d9d8a3987fd8627967c2ab338331ddb409c0469d5b1b7e04' +\n        'eb8b2a58298ba0609f3dd1edca6fc249dc4e2ceb03575e63af0383650f' +\n        'd3259e6d864c0a651c6eee0f5fc25840d4f888e560cfe2b79735aa9ad9' +\n        'b4a0af3a40963404c58b383742fba24495389417f4111aa16b6af0d2d4' +\n        'f0ca4c746a073435345ebeb42dfaf9f19e6a9fc44612f953c6809b3c9c' +\n        'cf576ce0cc3fe5984a9fb7a3677d3725bde037b1df314ab381527e28dd' +\n        '2f214b46fd4aeecf06e9078ccf7afded117a822c852c179504cbaf616d' +\n        'f0ac2ea723e6d65b4d7130ed8ea1dc21653a060fa43f2f66c5256d1866' +\n        '5800fb6a1b334103f9bea0e8dafce48fc406e53dd967ebdd43683b99b3' +\n        '99ee6421b019c2da256822ca8aaf5c07325c3645c87e0e65987caec04b' +\n        '29b9c55c767fce21b4700a3313281910f25aa69c3c2f1fda772ed62ff4' +\n        '898f1a2888a6a973b0677f2f1e48b577f9700ac612d6fcc43a0a127e7b' +\n        'ff04c40634d9c6b83290afab0224f537d5e8533ca561aa1f34058f4bf7' +\n        '34bd020301000102820200536fc7936d94b4f4d450c14149aa5f64a9ba' +\n        'bd07523e9d80058db77f56ad6563914e12e6cab75bc2c046e599aa6aee' +\n        '4c1bc09fbc9dfb4fd96103aa8baaf1c857c226a5c1976a1f8a6ce0112d' +\n        'd702e2cef50671461e5e516ce29fec850f8571c1311fc9918f37864b35' +\n        '8be4f66e0c7a2881c867c77e8af37a4721fd795a4e534fe35a1c6ba78e' +\n        '824c80eaa6af20cafb9c5068bfc6e44823d8b291664b1b7add1f0a5328' +\n        'bcd46db796975825cbfae737a34757bcfb7914dda3c8b85ee22c544007' +\n        'd6a4a5a92a0677fb350a4a91256ff065db245d12249482b3ce7cd02d5a' +\n        '6b25767a24da69e8a07a63526aa650245a669672e18348ebf17af869af' +\n        'dc9bbfb9b4af1f3a3140b96f814a9602756c862bc5d5324898748888b2' +\n        '2f04a3e9dbd483ea6696213d430bdb0a0b813582aed53b8efada5d7fff' +\n        '310e018ed72b467b2eb6d93b85971e7b2014ea46b6143783d422fd278c' +\n        'ea66f9c442530540539a4846b35c7c8e897371adf49aabfc4fadd6e0f5' +\n        'e45c799974aeefc87e6eb1e888b3478651e6982bcf765a07f1081a36e0' +\n        '908d42201c1ffc13c15e25e3244ea9e1f65784a16c86260b395afbeeef' +\n        'b0f616840c1047d787d3ee2881391a310ae6d7aa65664d1ecd55fd04a2' +\n        'bc2939dde06f7f4f5f15b2d41b8a8c05dbf70a9c62823aaae6a11daf1c' +\n        'e92c41d5095669f0091a94d6cca6071858d06488a6cf7cbfe954604c4a' +\n        '410282010100fee558136fc778d715fee58023d2eca403571c9137dd31' +\n        '4c67c4aec8e62da77318262ce6435b93ece9b62c46a70e7362cfe7c042' +\n        '1ac4be8d6c0c817dc4e01d7b6aa3207fa20f370bbc53e00b782442ca45' +\n        '8d4df533d38ee71023cc1c5f2f7283886d8cb664d184db8a257e7a7823' +\n        'aa904a7a58a696380df971c68a607c4dccc4b8f70782bfde593e947dab' +\n        '4275b20cc3ec7b55878cae5c8d35fc10bc174466623b6cb94a3e3e607a' +\n        'b0ed8af78baaaa8d063d4a2cecc860adb7dfdcbdfa12438f8264620350' +\n        '15672d6c28ac542aca464379b57e0f8203ff86646724bd8915a2dd6516' +\n        'bc26ad2d9da24f04853d4771525d7befbe8f1ef326c44b80546f2875e4' +\n        '970282010100c6c01fc484238e32a6bb2242cc8bf11bb967bb3a2f772e' +\n        '0e11d4ae24be322b99b7e011c91e7fb21de44872b0e724a5135187df70' +\n        '7fce8ce9c94bde0103ac07d6d12ebc6433e8b57a529c00a59d4cff699d' +\n        '9cfd1e0b62f626d4912fc99dc5fcaa5c396858ca43eeb9dc059e8284b1' +\n        '19eac5986ba58141301198f55e3f252934d1b6ab29b1377fef44118606' +\n        'e9c3de3eb35f4334b3358ae6253eaac603cb72b308a1bc154ed7c70cd1' +\n        '6cfee2011443e47a6d666cb65b9d84260d6e8669555c9326df648405fa' +\n        'c3da80e69678c0a9a65daa2e4cab67b8b81ddd84cf51c09a694802c605' +\n        '8ea4f4379a9c015b3a56b0309ae2da993b074a610a7f83376a60d3e5b7' +\n        'cb028201005e4f0cdf64243199a311c4683cd8f5a5597709a2d1408dd4' +\n        'ef2fde5b868eadbdefd970136228a7faa81e37138d0bd3b563a7238351' +\n        'd4298cb9c586c3b9ec11fc6fe01b4e1deff335ec603c2d022ea8679e84' +\n        '41abcf991eee6f124f9acfbd06699438b42f67edfd721d12f250edd284' +\n        '710e9d65df7d05106692aa1ad8c82520f648595df60a77821d9d6341d2' +\n        '3d29bb7f6227dfe55f2fc41e9b32c01e579d7f24294878e5f751acf0b8' +\n        '35ab8d1ba7f1a26c0491453df6858ec0d19b22cf3ba2b39e52f5d0b3f8' +\n        'b74c1f108d7236c2d06c76c3a7f8a4ea45c8bbad4df2b29dc6bc93826d' +\n        'eb01783732ae79c5b27e94771d0f960cb377880f77e15781e5feda5fd1' +\n        '028201007f3514a018db10f64654dbd6d94870678841664a157b3854f5' +\n        '00a4fd0b66dd1523e51c3d17722fb4861a009e4d32dd1d023feeb8f874' +\n        '612879183fdd725637263cf8a6c79399cc1da0a60c9bf394069db8ad74' +\n        '2c38a97c56da129afd7627f451ad7968d9fb8b834e1e0ed2a742fa7f56' +\n        '0e6641efca4cc8d15a8f216555098caef5359417c327f52221fd208b9a' +\n        '3bb2f1e7750253f95f0f72a32b7655936fb43b40193ba21ce55fc4e2f8' +\n        '37faecd78f72f4766bfa43a50ba1b75318606eac33dadb7c602bdb9663' +\n        '51c14469c116544efacf6b6f0191eef5de84549544ab0fdb713b00ef8d' +\n        '9069ce612f550e7fd1812a812bdc8b355d5bc2f65e2ba7c0959f200502' +\n        '82010100baf8445f5361adfb0889993acfaf27c14644a333875114c0ed' +\n        '8fbee213be44edbbfec4e088f2c40a3976402892459008600172288324' +\n        '9118516a9c517e39a900a13a0b66354da877a7fdb60c83171379292f1c' +\n        '137d8d5d5deb257b376c166620a2d23c44dfdee20fb84a453e3a5d479b' +\n        'f98e557e4c38c7e11ac8b44de10c2bf2a535e15d73265a9dd57d86e011' +\n        'f20fd37b91da22212fefe0dc152a6ebcb03c4b1356248d34cfee043432' +\n        '6a65cb5c1d5b43623f39be3523cd8f0f588d0aa1a2ed9bd9dde5581a05' +\n        '13432c3a4383bc8254cf3fd702485557c04f601db2def13a9f4d9096ac' +\n        'ce9cac65fa05bea1576b6a36c03eec9ebd53d346bb0e4636a5e369f5',\n      'hex'\n    );\n\n    // However, just like with Node.js, when the der data is exported as pkcs1, trying to read\n    // it as pkcs8 fails... tho oddly sec1 is ok. Silly software.\n    const k4 = createPrivateKey({\n      key: derData2,\n      format: 'der',\n      type: 'pkcs1',\n    });\n    const k6 = createPrivateKey({ key: derData2, format: 'der', type: 'sec1' });\n\n    ok(k4.equals(k6));\n\n    throws(\n      () => createPrivateKey({ key: derData2, format: 'der', type: 'pkcs8' }),\n      {\n        message: 'Failed to parse private key',\n      }\n    );\n  },\n};\n\nexport const private_key_jwk_export = {\n  test(_, env) {\n    // These are invalid for JWK export\n    [\n      'dsa_private.pem',\n      'dsa_private_1025.pem',\n      'dsa_private_pkcs8.pem',\n    ].forEach((i) => {\n      throws(() => createPrivateKey(env[i]).export({ format: 'jwk' }), {\n        message: 'Key type is invalid for JWK export',\n      });\n    });\n\n    deepStrictEqual(\n      createPrivateKey(env['ed25519_private.pem']).export({ format: 'jwk' }),\n      {\n        alg: 'EdDSA',\n        crv: 'Ed25519',\n        d: 'wVK6M3SMhQh3NK-7GRrSV-BVWQx1FO5pW8hhQeu_NdA',\n        kty: 'OKP',\n        x: 'K1wIouqnuiA04b3WrMa-xKIKIpfHetNZRv3h9fBf768',\n      }\n    );\n\n    deepStrictEqual(\n      createPrivateKey(env['x25519_private.pem']).export({ format: 'jwk' }),\n      {\n        crv: 'X25519',\n        d: 'mL_IWm55RrALUGRfJYzw40gEYWMvtRkesP9mj8o8Omc',\n        kty: 'OKP',\n        x: 'aSb8Q-RndwfNnPeOYGYPDUN3uhAPnMLzXyfi-mqfhig',\n      }\n    );\n\n    deepStrictEqual(\n      createPrivateKey(env['ec_p256_private.pem']).export({ format: 'jwk' }),\n      {\n        crv: 'P-256',\n        d: 'DxBsPQPIgMuMyQbxzbb9toew6Ev6e9O6ZhpxLNgmAEo',\n        kty: 'EC',\n        x: 'X0mMYR_uleZSIPjNztIkAS3_ud5LhNpbiIFp6fNf2Gs',\n        y: 'UbJuPy2Xi0lW7UYTBxPK3yGgDu9EAKYIecjkHX5s2lI',\n      }\n    );\n\n    deepStrictEqual(\n      createPrivateKey(env['ec_p384_private.pem']).export({ format: 'jwk' }),\n      {\n        crv: 'P-384',\n        d: 'dwfuHuAtTlMRn7ZBCBm_0grpc1D_4hPeNAgevgelljuC0--k_LDFosDgBlLLmZsi',\n        kty: 'EC',\n        x: 'hON3nzGJgv-08fdHpQxgRJFZzlK-GZDGa5f3KnvM31cvvjJmsj4UeOgIdy3rDAjV',\n        y: 'fidHhtecNCGCfLqmrLjDena1NSzWzWH1u_oUdMKGo5XSabxzD7-8JZxjpc8sR9cl',\n      }\n    );\n\n    deepStrictEqual(\n      createPrivateKey(env['ec_p521_private.pem']).export({ format: 'jwk' }),\n      {\n        crv: 'P-521',\n        d:\n          'ABIIbmn3Gm_Y11uIDkC3g2ijpRxIrJEBY4i_JJYo5OougzTl3BX2ifRluP' +\n          'JMaaHcNerbQH_WdVkLLX86ShlHrRyJ',\n        kty: 'EC',\n        x:\n          'AaLFgjwZtznM3N7qsfb86awVXe6c6djUYOob1FN-kllekv0KEXV0bwcDjPGQ' +\n          'z5f6MxLCbhMeHRavUS6P10rsTtBn',\n        y:\n          'Ad3flexBeAfXceNzRBH128kFbOWD6W41NjwKRqqIF26vmgW_8COldGKZjFkO' +\n          'SEASxPBcvA2iFJRUyQ3whC00j0Np',\n      }\n    );\n\n    deepStrictEqual(\n      createPrivateKey(env['rsa_private.pem']).export({ format: 'jwk' }),\n      {\n        d:\n          'ktnq2LvIMqBj4txP82IEOorIRQGVsw1khbm8A-cEpuEkgM71Yi_0WzupKktuc' +\n          'UeevQ5i0Yh8w9e1SJiTLDRAlJz66kdky9uejiWWl6zR4dyNZVMFYRM43ijLC-' +\n          'P8rPne9Fz16IqHFW5VbJqA1xCBhKmuPMsD71RNxZ4Hrsa7Kt_xglQTYsLbdGI' +\n          'wDmcZihId9VGXRzvmCPsDRf2fCkAj7HDeRxpUdEiEDpajADc-PWikra3r3b40' +\n          'tVHKWm8wxJLivOIN7GiYXKQIW6RhZgH-Rk45JIRNKxNagxdeXUqqyhnwhbTo1' +\n          'Hite0iBDexN9tgoZk0XmdYWBn6ElXHRZ7VCDQ',\n        dp:\n          'qS_Mdr5CMRGGMH0bKhPUWEtAixUGZhJaunX5wY71Xoc_Gh4cnO-b7BNJ_-5L' +\n          '8WZog0vr6PgiLhrqBaCYm2wjpyoG2o2wDHm-NAlzN_wp3G2EFhrSxdOux-S1' +\n          'c0kpRcyoiAO2n29rNDa-jOzwBBcU8ACEPdLOCQl0IEFFJO33tl8',\n        dq:\n          'WAziKpxLKL7LnL4dzDcx8JIPIuwnTxh0plCDdCffyLaT8WJ9lXbXHFTjOvt8' +\n          'WfPrlDP_Ylxmfkw5BbGZOP1VLGjZn2DkH9aMiwNmbDXFPdG0G3hzQovx_9fa' +\n          'jiRV4DWghLHeT9wzJfZabRRiI0VQR472300AVEeX4vgbrDBn600',\n        e: 'AQAB',\n        kty: 'RSA',\n        n:\n          't9xYiIonscC3vz_A2ceR7KhZZlDu_5bye53nCVTcKnWd2seY6UAdKersX6njr' +\n          '83Dd5OVe1BW_wJvp5EjWTAGYbFswlNmeD44edEGM939B6Lq-_8iBkrTi8mGN4' +\n          'YCytivE24YI0D4XZMPfkLSpab2y_Hy4DjQKBq1ThZ0UBnK-9IhX37Ju_ZoGYS' +\n          'lTIGIhzyaiYBh7wrZBoPczIEu6et_kN2VnnbRUtkYTF97ggcv5h-hDpUQjQW0' +\n          'ZgOMcTc8n-RkGpIt0_iM_bTjI3Tz_gsFdi6hHcpZgbopPL630296iByyigQCP' +\n          'JVzdusFrQN5DeC-zT_nGypQkZanLb4ZspSx9Q',\n        p:\n          '8UovlB4nrBm7xH-u7XXBMbqxADQm5vaEZxw9eluc-tP7cIAI4sglMIvL_FMpb' +\n          'd2pEeP_BkR76NTDzzDuPAZvUGRavgEjy0O9j2NAs_WPK4tZF-vFdunhnSh4EH' +\n          'AF4Ij9kbsUi90NOpbGfVqPdOaHqzgHKoR23Cuusk9wFQ2XTV8',\n        q:\n          'wxHdEYT9xrpfrHPqSBQPpO0dWGKJEkrWOb-76rSfuL8wGR4OBNmQdhLuU9zTI' +\n          'h22pog-XPnLPAecC-4yu_wtJ2SPCKiKDbJBre0CKPyRfGqzvA3njXwMxXazU4' +\n          'kGs-2Fg-xu_iKbaIjxXrclBLhkxhBtySrwAFhxxOk6fFcPLSs',\n        qi:\n          'k7czBCT9rHn_PNwCa17hlTy88C4vXkwbz83Oa-aX5L4e5gw5lhcR2ZuZHLb2' +\n          'r6oMt9rlD7EIDItSs-u21LOXWPTAlazdnpYUyw_CzogM_PN-qNwMRXn5uXFF' +\n          'hmlP2mVg2EdELTahXch8kWqHaCSX53yvqCtRKu_j76V31TfQZGM',\n      }\n    );\n  },\n};\n\nexport const rsa_private_key_jwk_import = {\n  test() {\n    const jwk = {\n      e: 'AQAB',\n      n:\n        't9xYiIonscC3vz_A2ceR7KhZZlDu_5bye53nCVTcKnWd2seY6UAdKersX6njr83Dd5OVe' +\n        '1BW_wJvp5EjWTAGYbFswlNmeD44edEGM939B6Lq-_8iBkrTi8mGN4YCytivE24YI0D4XZ' +\n        'MPfkLSpab2y_Hy4DjQKBq1ThZ0UBnK-9IhX37Ju_ZoGYSlTIGIhzyaiYBh7wrZBoPczIE' +\n        'u6et_kN2VnnbRUtkYTF97ggcv5h-hDpUQjQW0ZgOMcTc8n-RkGpIt0_iM_bTjI3Tz_gsF' +\n        'di6hHcpZgbopPL630296iByyigQCPJVzdusFrQN5DeC-zT_nGypQkZanLb4ZspSx9Q',\n      d:\n        'ktnq2LvIMqBj4txP82IEOorIRQGVsw1khbm8A-cEpuEkgM71Yi_0WzupKktucUeevQ5i0' +\n        'Yh8w9e1SJiTLDRAlJz66kdky9uejiWWl6zR4dyNZVMFYRM43ijLC-P8rPne9Fz16IqHFW' +\n        '5VbJqA1xCBhKmuPMsD71RNxZ4Hrsa7Kt_xglQTYsLbdGIwDmcZihId9VGXRzvmCPsDRf2' +\n        'fCkAj7HDeRxpUdEiEDpajADc-PWikra3r3b40tVHKWm8wxJLivOIN7GiYXKQIW6RhZgH-' +\n        'Rk45JIRNKxNagxdeXUqqyhnwhbTo1Hite0iBDexN9tgoZk0XmdYWBn6ElXHRZ7VCDQ',\n      p:\n        '8UovlB4nrBm7xH-u7XXBMbqxADQm5vaEZxw9eluc-tP7cIAI4sglMIvL_FMpbd2pEeP_B' +\n        'kR76NTDzzDuPAZvUGRavgEjy0O9j2NAs_WPK4tZF-vFdunhnSh4EHAF4Ij9kbsUi90NOp' +\n        'bGfVqPdOaHqzgHKoR23Cuusk9wFQ2XTV8',\n      q:\n        'wxHdEYT9xrpfrHPqSBQPpO0dWGKJEkrWOb-76rSfuL8wGR4OBNmQdhLuU9zTIh22pog-X' +\n        'PnLPAecC-4yu_wtJ2SPCKiKDbJBre0CKPyRfGqzvA3njXwMxXazU4kGs-2Fg-xu_iKbaI' +\n        'jxXrclBLhkxhBtySrwAFhxxOk6fFcPLSs',\n      dp:\n        'qS_Mdr5CMRGGMH0bKhPUWEtAixUGZhJaunX5wY71Xoc_Gh4cnO-b7BNJ_-5L8WZog0vr' +\n        '6PgiLhrqBaCYm2wjpyoG2o2wDHm-NAlzN_wp3G2EFhrSxdOux-S1c0kpRcyoiAO2n29rN' +\n        'Da-jOzwBBcU8ACEPdLOCQl0IEFFJO33tl8',\n      dq:\n        'WAziKpxLKL7LnL4dzDcx8JIPIuwnTxh0plCDdCffyLaT8WJ9lXbXHFTjOvt8WfPrlDP_' +\n        'Ylxmfkw5BbGZOP1VLGjZn2DkH9aMiwNmbDXFPdG0G3hzQovx_9fajiRV4DWghLHeT9wzJ' +\n        'fZabRRiI0VQR472300AVEeX4vgbrDBn600',\n      qi:\n        'k7czBCT9rHn_PNwCa17hlTy88C4vXkwbz83Oa-aX5L4e5gw5lhcR2ZuZHLb2r6oMt9rl' +\n        'D7EIDItSs-u21LOXWPTAlazdnpYUyw_CzogM_PN-qNwMRXn5uXFFhmlP2mVg2EdELTahX' +\n        'ch8kWqHaCSX53yvqCtRKu_j76V31TfQZGM',\n      kty: 'RSA',\n    };\n\n    const key = createPrivateKey({ key: jwk, format: 'jwk' });\n    strictEqual(key.asymmetricKeyType, 'rsa');\n    deepStrictEqual(key.asymmetricKeyDetails, {\n      modulusLength: 2048,\n      publicExponent: 65537n,\n    });\n\n    // The fail due to missing information in the JWK\n    throws(\n      () => {\n        createPrivateKey({\n          key: {\n            kty: 'RSA',\n          },\n          format: 'jwk',\n        });\n      },\n      {\n        message: 'RSA JWK missing n parameter',\n      }\n    );\n\n    throws(\n      () => {\n        createPrivateKey({\n          key: {\n            kty: 'RSA',\n            n:\n              't9xYiIonscC3vz_A2ceR7KhZZlDu_5bye53nCVTcKnWd2seY6UAdKersX6njr83Dd5OVe' +\n              '1BW_wJvp5EjWTAGYbFswlNmeD44edEGM939B6Lq-_8iBkrTi8mGN4YCytivE24YI0D4XZ' +\n              'MPfkLSpab2y_Hy4DjQKBq1ThZ0UBnK-9IhX37Ju_ZoGYSlTIGIhzyaiYBh7wrZBoPczIE' +\n              'u6et_kN2VnnbRUtkYTF97ggcv5h-hDpUQjQW0ZgOMcTc8n-RkGpIt0_iM_bTjI3Tz_gsF' +\n              'di6hHcpZgbopPL630296iByyigQCPJVzdusFrQN5DeC-zT_nGypQkZanLb4ZspSx9Q',\n          },\n          format: 'jwk',\n        });\n      },\n      {\n        message: 'RSA JWK missing e parameter',\n      }\n    );\n\n    throws(\n      () => {\n        createPrivateKey({\n          key: {\n            kty: 'RSA',\n            n:\n              't9xYiIonscC3vz_A2ceR7KhZZlDu_5bye53nCVTcKnWd2seY6UAdKersX6njr83Dd5OVe' +\n              '1BW_wJvp5EjWTAGYbFswlNmeD44edEGM939B6Lq-_8iBkrTi8mGN4YCytivE24YI0D4XZ' +\n              'MPfkLSpab2y_Hy4DjQKBq1ThZ0UBnK-9IhX37Ju_ZoGYSlTIGIhzyaiYBh7wrZBoPczIE' +\n              'u6et_kN2VnnbRUtkYTF97ggcv5h-hDpUQjQW0ZgOMcTc8n-RkGpIt0_iM_bTjI3Tz_gsF' +\n              'di6hHcpZgbopPL630296iByyigQCPJVzdusFrQN5DeC-zT_nGypQkZanLb4ZspSx9Q',\n            e: 'AQAB',\n          },\n          format: 'jwk',\n        });\n      },\n      {\n        message: 'RSA JWK missing d parameter',\n      }\n    );\n  },\n};\n\nexport const ec_private_key_jwk_import = {\n  test() {\n    const jwk = {\n      crv: 'P-256',\n      d: 'DxBsPQPIgMuMyQbxzbb9toew6Ev6e9O6ZhpxLNgmAEo',\n      kty: 'EC',\n      x: 'X0mMYR_uleZSIPjNztIkAS3_ud5LhNpbiIFp6fNf2Gs',\n      y: 'UbJuPy2Xi0lW7UYTBxPK3yGgDu9EAKYIecjkHX5s2lI',\n    };\n\n    const key = createPrivateKey({ key: jwk, format: 'jwk' });\n    strictEqual(key.asymmetricKeyType, 'ec');\n    deepStrictEqual(key.asymmetricKeyDetails, { namedCurve: 'prime256v1' });\n\n    // The fail due to missing information in the JWK\n    throws(\n      () => {\n        createPrivateKey({\n          key: {\n            kty: 'EC',\n          },\n          format: 'jwk',\n        });\n      },\n      {\n        message: 'EC JWK missing crv parameter',\n      }\n    );\n\n    throws(\n      () => {\n        createPrivateKey({\n          key: {\n            kty: 'EC',\n            crv: 'P-256',\n          },\n          format: 'jwk',\n        });\n      },\n      {\n        message: 'EC JWK missing x parameter',\n      }\n    );\n\n    throws(\n      () => {\n        createPrivateKey({\n          key: {\n            kty: 'EC',\n            crv: 'P-256',\n            x: 'X0mMYR_uleZSIPjNztIkAS3_ud5LhNpbiIFp6fNf2Gs',\n          },\n          format: 'jwk',\n        });\n      },\n      {\n        message: 'EC JWK missing y parameter',\n      }\n    );\n  },\n};\n\nexport const ed_private_key_jwk_import = {\n  test() {\n    const jwk = {\n      crv: 'Ed25519',\n      x: 'K1wIouqnuiA04b3WrMa-xKIKIpfHetNZRv3h9fBf768',\n      d: 'wVK6M3SMhQh3NK-7GRrSV-BVWQx1FO5pW8hhQeu_NdA',\n      kty: 'OKP',\n    };\n\n    const key = createPrivateKey({ key: jwk, format: 'jwk' });\n    strictEqual(key.asymmetricKeyType, 'ed25519');\n\n    const jwk2 = {\n      crv: 'X25519',\n      x: 'K1wIouqnuiA04b3WrMa-xKIKIpfHetNZRv3h9fBf768',\n      d: 'wVK6M3SMhQh3NK-7GRrSV-BVWQx1FO5pW8hhQeu_NdA',\n      kty: 'OKP',\n    };\n\n    const key2 = createPrivateKey({ key: jwk2, format: 'jwk' });\n    strictEqual(key2.asymmetricKeyType, 'x25519');\n\n    // The fail due to missing information in the JWK\n    throws(\n      () => {\n        createPrivateKey({\n          key: {\n            kty: 'OKP',\n          },\n          format: 'jwk',\n        });\n      },\n      {\n        message: 'OKP JWK missing crv parameter',\n      }\n    );\n  },\n};\n\nexport const private_key_hmac_fails = {\n  test(_, env) {\n    // Verify that creating an hmac with a private key fails.\n    // TODO(soon): Also try public key, which should also fail.\n    const key = createPrivateKey(env['dsa_private.pem']);\n    throws(() => createHmac('sha256', key), {\n      message: 'Invalid key object type private, expected secret.',\n    });\n  },\n};\n\nexport const private_key_tocryptokey = {\n  async test(_, env) {\n    const key = createPrivateKey(env['dsa_private.pem']);\n    // TODO(soon): Getting a CryptoKey from the KeyObject currently does not work.\n    // Will will implement this conversion as a follow up step.\n    throws(() => key.toCryptoKey(), {\n      message: 'The toCryptoKey method is not implemented',\n    });\n\n    // Getting the private key from a CryptoKey should generally work...\n    const { privateKey } = await crypto.subtle.generateKey(\n      {\n        name: 'RSASSA-PKCS1-v1_5',\n        modulusLength: 2048,\n        publicExponent: new Uint8Array([1, 0, 1]),\n        hash: 'SHA-256',\n      },\n      true,\n      ['sign', 'verify']\n    );\n\n    const key2 = KeyObject.from(privateKey);\n    strictEqual(key2.asymmetricKeyDetails.modulusLength, 2048);\n    strictEqual(key2.asymmetricKeyDetails.publicExponent, 65537n);\n    strictEqual(key2.asymmetricKeyType, 'rsa');\n    strictEqual(key2.type, 'private');\n  },\n};\n\nexport const create_public_key = {\n  test(_, env) {\n    // TODO(later): These error messages are inconsistent with one another\n    // despite performing the same basic validation.\n    throws(() => createPublicKey(1), {\n      message: /The \"options.key\" property must be of type string/,\n    });\n    throws(() => createPublicKey(true), {\n      message: /The \"options.key\" property must be of type string/,\n    });\n    throws(() => createPublicKey({ key: 1 }), {\n      message: /The \"key\" argument/,\n    });\n    throws(() => createPublicKey({ key: true }), {\n      message: /The \"key\" argument/,\n    });\n\n    [\n      // These are the public key types that are not supported by boringssl\n      'dh_public.pem',\n      'ec_secp256k1_public.pem',\n      'ed448_public.pem',\n      'rsa_pss_public_2048.pem',\n      'rsa_pss_public_2048_sha1_sha1_20.pem',\n      'rsa_pss_public_2048_sha256_sha256_16.pem',\n      'rsa_pss_public_2048_sha512_sha256_20.pem',\n      'x448_public.pem',\n    ].forEach((i) => {\n      throws(() => createPublicKey(env[i]), {\n        message: 'Failed to parse public key',\n      });\n    });\n\n    [\n      'dsa_public_1025.pem',\n      'dsa_public.pem',\n      'ec_p256_public.pem',\n      'ec_p384_public.pem',\n      'ec_p521_public.pem',\n      'ed25519_public.pem',\n      'rsa_public_2048.pem',\n      'rsa_public_4096.pem',\n      'rsa_public_b.pem',\n      'rsa_public.pem',\n      'x25519_public.pem',\n\n      // It is also possible to use an X509 cert as the source of a public key\n      'agent1-cert.pem',\n\n      // It is possible to create a public key from a private key\n      'dsa_private.pem',\n      'dsa_private_1025.pem',\n      'dsa_private_pkcs8.pem',\n      'ed25519_private.pem',\n      'ec_p256_private.pem',\n      'ec_p384_private.pem',\n      'ec_p521_private.pem',\n      'rsa_private.pem',\n      'rsa_private_b.pem',\n      'rsa_private_2048.pem',\n      'rsa_private_pkcs8_bad.pem',\n      'rsa_private_4096.pem',\n      'rsa_private_pkcs8.pem',\n      'x25519_private.pem',\n    ].forEach((i) => {\n      const key = createPublicKey(env[i]);\n      strictEqual(key.type, 'public');\n\n      switch (i) {\n        case 'dsa_public_1025.pem': {\n          strictEqual(key.asymmetricKeyType, 'dsa');\n          strictEqual(key.asymmetricKeyDetails.modulusLength, 1088);\n          strictEqual(key.asymmetricKeyDetails.divisorLength, 160);\n          break;\n        }\n        case 'dsa_public.pem': {\n          strictEqual(key.asymmetricKeyType, 'dsa');\n          strictEqual(key.asymmetricKeyDetails.modulusLength, 2048);\n          strictEqual(key.asymmetricKeyDetails.divisorLength, 256);\n          break;\n        }\n        case 'ec_p256_public.pem': {\n          strictEqual(key.asymmetricKeyType, 'ec');\n          strictEqual(key.asymmetricKeyDetails.namedCurve, 'prime256v1');\n          break;\n        }\n        case 'ec_p384_public.pem': {\n          strictEqual(key.asymmetricKeyType, 'ec');\n          strictEqual(key.asymmetricKeyDetails.namedCurve, 'secp384r1');\n          break;\n        }\n        case 'ec_p521_public.pem': {\n          strictEqual(key.asymmetricKeyType, 'ec');\n          strictEqual(key.asymmetricKeyDetails.namedCurve, 'secp521r1');\n          break;\n        }\n        case 'ed25519_public.pem': {\n          strictEqual(key.asymmetricKeyType, 'ed25519');\n          break;\n        }\n        case 'rsa_public_2048.pem': {\n          strictEqual(key.asymmetricKeyType, 'rsa');\n          strictEqual(key.asymmetricKeyDetails.modulusLength, 2048);\n          strictEqual(key.asymmetricKeyDetails.publicExponent, 65537n);\n          break;\n        }\n        case 'rsa_public_4096.pem': {\n          strictEqual(key.asymmetricKeyType, 'rsa');\n          strictEqual(key.asymmetricKeyDetails.modulusLength, 4096);\n          strictEqual(key.asymmetricKeyDetails.publicExponent, 65537n);\n          break;\n        }\n        case 'rsa_public_b.pem': {\n          strictEqual(key.asymmetricKeyType, 'rsa');\n          strictEqual(key.asymmetricKeyDetails.modulusLength, 2048);\n          strictEqual(key.asymmetricKeyDetails.publicExponent, 65537n);\n          break;\n        }\n        case 'rsa_public.pem': {\n          strictEqual(key.asymmetricKeyType, 'rsa');\n          strictEqual(key.asymmetricKeyDetails.modulusLength, 2048);\n          strictEqual(key.asymmetricKeyDetails.publicExponent, 65537n);\n          break;\n        }\n        case 'x25519_public.pem': {\n          strictEqual(key.asymmetricKeyType, 'x25519');\n          break;\n        }\n        case 'dsa_private.pem': {\n          strictEqual(key.asymmetricKeyType, 'dsa');\n          strictEqual(key.asymmetricKeyDetails.modulusLength, 2048);\n          strictEqual(key.asymmetricKeyDetails.divisorLength, 256);\n          break;\n        }\n        case 'dsa_private_1025.pem': {\n          strictEqual(key.asymmetricKeyType, 'dsa');\n          strictEqual(key.asymmetricKeyDetails.modulusLength, 1088);\n          strictEqual(key.asymmetricKeyDetails.divisorLength, 160);\n          break;\n        }\n        case 'dsa_private_pkcs8.pem': {\n          strictEqual(key.asymmetricKeyType, 'dsa');\n          strictEqual(key.asymmetricKeyDetails.modulusLength, 2048);\n          strictEqual(key.asymmetricKeyDetails.divisorLength, 256);\n          break;\n        }\n        case 'ed25519_private.pem': {\n          strictEqual(key.asymmetricKeyType, 'ed25519');\n          break;\n        }\n        case 'ec_p256_private.pem': {\n          strictEqual(key.asymmetricKeyType, 'ec');\n          strictEqual(key.asymmetricKeyDetails.namedCurve, 'prime256v1');\n          break;\n        }\n        case 'ec_p384_private.pem': {\n          strictEqual(key.asymmetricKeyType, 'ec');\n          strictEqual(key.asymmetricKeyDetails.namedCurve, 'secp384r1');\n          break;\n        }\n        case 'ec_p521_private.pem': {\n          strictEqual(key.asymmetricKeyType, 'ec');\n          strictEqual(key.asymmetricKeyDetails.namedCurve, 'secp521r1');\n          break;\n        }\n        case 'rsa_private.pem': {\n          strictEqual(key.asymmetricKeyType, 'rsa');\n          strictEqual(key.asymmetricKeyDetails.modulusLength, 2048);\n          strictEqual(key.asymmetricKeyDetails.publicExponent, 65537n);\n          break;\n        }\n        case 'rsa_private_b.pem': {\n          strictEqual(key.asymmetricKeyType, 'rsa');\n          strictEqual(key.asymmetricKeyDetails.modulusLength, 2048);\n          strictEqual(key.asymmetricKeyDetails.publicExponent, 65537n);\n          break;\n        }\n        case 'rsa_private_2048.pem': {\n          strictEqual(key.asymmetricKeyType, 'rsa');\n          strictEqual(key.asymmetricKeyDetails.modulusLength, 2048);\n          strictEqual(key.asymmetricKeyDetails.publicExponent, 65537n);\n          break;\n        }\n        case 'rsa_private_pkcs8_bad.pem': {\n          strictEqual(key.asymmetricKeyType, 'rsa');\n          strictEqual(key.asymmetricKeyDetails.modulusLength, 2048);\n          strictEqual(key.asymmetricKeyDetails.publicExponent, 65537n);\n          break;\n        }\n        case 'rsa_private_4096.pem': {\n          strictEqual(key.asymmetricKeyType, 'rsa');\n          strictEqual(key.asymmetricKeyDetails.modulusLength, 4096);\n          strictEqual(key.asymmetricKeyDetails.publicExponent, 65537n);\n          break;\n        }\n        case 'rsa_private_pkcs8.pem': {\n          strictEqual(key.asymmetricKeyType, 'rsa');\n          strictEqual(key.asymmetricKeyDetails.modulusLength, 2048);\n          strictEqual(key.asymmetricKeyDetails.publicExponent, 65537n);\n          break;\n        }\n        case 'x25519_private.pem': {\n          strictEqual(key.asymmetricKeyType, 'x25519');\n          break;\n        }\n      }\n\n      if (key.asymmetricKeyType === 'rsa') {\n        const exp = key.export({ format: 'pem', type: 'pkcs1' });\n        const key2 = createPublicKey(exp);\n        ok(key.equals(key2));\n\n        const exp2 = key.export({ format: 'der', type: 'pkcs1' });\n        const key3 = createPublicKey({\n          key: exp2,\n          format: 'der',\n          type: 'pkcs1',\n        });\n        ok(key.equals(key3));\n      }\n      {\n        const exp = key.export({ format: 'pem', type: 'spki' });\n        const key2 = createPublicKey(exp);\n        ok(key.equals(key2));\n      }\n      {\n        const exp = key.export({ format: 'der', type: 'spki' });\n        const key2 = createPublicKey({ key: exp, format: 'der', type: 'spki' });\n        ok(key.equals(key2));\n      }\n    });\n\n    const privateKey = createPrivateKey(env['rsa_private.pem']);\n    const publicKey = createPublicKey(privateKey);\n    strictEqual(publicKey.type, 'public');\n    strictEqual(publicKey.asymmetricKeyType, 'rsa');\n    strictEqual(publicKey.asymmetricKeyDetails.modulusLength, 2048);\n    strictEqual(publicKey.asymmetricKeyDetails.publicExponent, 65537n);\n  },\n};\n\nexport const public_key_tocryptokey = {\n  async test(_, env) {\n    const key = createPublicKey(env['dsa_public_1025.pem']);\n    // TODO(soon): Getting a CryptoKey from the KeyObject currently does not work.\n    // Will will implement this conversion as a follow up step.\n    throws(() => key.toCryptoKey(), {\n      message: 'The toCryptoKey method is not implemented',\n    });\n\n    // Getting the private key from a CryptoKey should generally work...\n    const { publicKey } = await crypto.subtle.generateKey(\n      {\n        name: 'RSASSA-PKCS1-v1_5',\n        modulusLength: 2048,\n        publicExponent: new Uint8Array([1, 0, 1]),\n        hash: 'SHA-256',\n      },\n      true,\n      ['sign', 'verify']\n    );\n\n    const key2 = KeyObject.from(publicKey);\n    strictEqual(key2.asymmetricKeyDetails.modulusLength, 2048);\n    strictEqual(key2.asymmetricKeyDetails.publicExponent, 65537n);\n    strictEqual(key2.asymmetricKeyType, 'rsa');\n    strictEqual(key2.type, 'public');\n  },\n};\n\nexport const export_public_key_jwk = {\n  test(_, env) {\n    // These are invalid for JWK export\n    [\n      'dsa_private.pem',\n      'dsa_private_1025.pem',\n      'dsa_private_pkcs8.pem',\n    ].forEach((i) => {\n      throws(() => createPrivateKey(env[i]).export({ format: 'jwk' }), {\n        message: 'Key type is invalid for JWK export',\n      });\n    });\n\n    deepStrictEqual(\n      createPublicKey(env['ec_p256_public.pem']).export({ format: 'jwk' }),\n      {\n        crv: 'P-256',\n        kty: 'EC',\n        x: 'X0mMYR_uleZSIPjNztIkAS3_ud5LhNpbiIFp6fNf2Gs',\n        y: 'UbJuPy2Xi0lW7UYTBxPK3yGgDu9EAKYIecjkHX5s2lI',\n      }\n    );\n\n    deepStrictEqual(\n      createPublicKey(env['ec_p384_public.pem']).export({ format: 'jwk' }),\n      {\n        crv: 'P-384',\n        kty: 'EC',\n        x: 'hON3nzGJgv-08fdHpQxgRJFZzlK-GZDGa5f3KnvM31cvvjJmsj4UeOgIdy3rDAjV',\n        y: 'fidHhtecNCGCfLqmrLjDena1NSzWzWH1u_oUdMKGo5XSabxzD7-8JZxjpc8sR9cl',\n      }\n    );\n\n    deepStrictEqual(\n      createPublicKey(env['ec_p521_public.pem']).export({ format: 'jwk' }),\n      {\n        crv: 'P-521',\n        kty: 'EC',\n        x:\n          'AaLFgjwZtznM3N7qsfb86awVXe6c6djUYOob1FN-kllekv0KEXV0bwcDjPGQz5f' +\n          '6MxLCbhMeHRavUS6P10rsTtBn',\n        y:\n          'Ad3flexBeAfXceNzRBH128kFbOWD6W41NjwKRqqIF26vmgW_8COldGKZjFkOSEA' +\n          'SxPBcvA2iFJRUyQ3whC00j0Np',\n      }\n    );\n\n    deepStrictEqual(\n      createPublicKey(env['ed25519_public.pem']).export({ format: 'jwk' }),\n      {\n        alg: 'EdDSA',\n        crv: 'Ed25519',\n        kty: 'OKP',\n        x: 'K1wIouqnuiA04b3WrMa-xKIKIpfHetNZRv3h9fBf768',\n      }\n    );\n\n    deepStrictEqual(\n      createPublicKey(env['x25519_public.pem']).export({ format: 'jwk' }),\n      {\n        crv: 'X25519',\n        kty: 'OKP',\n        x: 'aSb8Q-RndwfNnPeOYGYPDUN3uhAPnMLzXyfi-mqfhig',\n      }\n    );\n\n    deepStrictEqual(\n      createPublicKey(env['rsa_public_2048.pem']).export({ format: 'jwk' }),\n      {\n        e: 'AQAB',\n        kty: 'RSA',\n        n:\n          'rk4OqxBqU5_k0FoUDU7CpZpjz6YJEXUpyqeJmFRVZPMUv_Rc7U4seLY-Qp6k26' +\n          'T_wlQ2WJWuyY-VJcbQNWLvjJWks5HWknwDuVs6sjuTM8CfHWn1960JkK5Ec2Tj' +\n          'RhCQ1KJy-uc3GJLtWb4rWVgTbbaaC5fiR1_GeuJ8JH1Q50lB3mDsNGIk1U5jhN' +\n          'aYY82hYvlbErf6Ft5njHK0BOM5OTvQ6BBv7c363WNG7tYlNw1J40dup9OQPo5J' +\n          'mXN_h-sRbdgG8iUxrkRibuGv7loh52QQgq2snznuRMdKidRfUZjCDGgwbgK23Q' +\n          '7n8VZ9Y10j8PIvPTLJ83PX4lOEA37Jlw',\n      }\n    );\n\n    // TODO(now): JWK import\n    throws(() => createPublicKey({ key: {}, format: 'jwk' }), {\n      message: 'JWK public key import is not implemented for this key type',\n    });\n  },\n};\n\nexport const rsa_public_key_jwk_import = {\n  test() {\n    const jwk = {\n      e: 'AQAB',\n      n:\n        't9xYiIonscC3vz_A2ceR7KhZZlDu_5bye53nCVTcKnWd2seY6UAdKersX6njr83Dd5OVe' +\n        '1BW_wJvp5EjWTAGYbFswlNmeD44edEGM939B6Lq-_8iBkrTi8mGN4YCytivE24YI0D4XZ' +\n        'MPfkLSpab2y_Hy4DjQKBq1ThZ0UBnK-9IhX37Ju_ZoGYSlTIGIhzyaiYBh7wrZBoPczIE' +\n        'u6et_kN2VnnbRUtkYTF97ggcv5h-hDpUQjQW0ZgOMcTc8n-RkGpIt0_iM_bTjI3Tz_gsF' +\n        'di6hHcpZgbopPL630296iByyigQCPJVzdusFrQN5DeC-zT_nGypQkZanLb4ZspSx9Q',\n      kty: 'RSA',\n    };\n\n    const key = createPublicKey({ key: jwk, format: 'jwk' });\n    strictEqual(key.asymmetricKeyType, 'rsa');\n    deepStrictEqual(key.asymmetricKeyDetails, {\n      modulusLength: 2048,\n      publicExponent: 65537n,\n    });\n\n    // The fail due to missing information in the JWK\n    throws(\n      () => {\n        createPublicKey({\n          key: {\n            kty: 'RSA',\n          },\n          format: 'jwk',\n        });\n      },\n      {\n        message: 'RSA JWK missing n parameter',\n      }\n    );\n\n    throws(\n      () => {\n        createPublicKey({\n          key: {\n            kty: 'RSA',\n            n:\n              't9xYiIonscC3vz_A2ceR7KhZZlDu_5bye53nCVTcKnWd2seY6UAdKersX6njr83Dd5OVe' +\n              '1BW_wJvp5EjWTAGYbFswlNmeD44edEGM939B6Lq-_8iBkrTi8mGN4YCytivE24YI0D4XZ' +\n              'MPfkLSpab2y_Hy4DjQKBq1ThZ0UBnK-9IhX37Ju_ZoGYSlTIGIhzyaiYBh7wrZBoPczIE' +\n              'u6et_kN2VnnbRUtkYTF97ggcv5h-hDpUQjQW0ZgOMcTc8n-RkGpIt0_iM_bTjI3Tz_gsF' +\n              'di6hHcpZgbopPL630296iByyigQCPJVzdusFrQN5DeC-zT_nGypQkZanLb4ZspSx9Q',\n          },\n          format: 'jwk',\n        });\n      },\n      {\n        message: 'RSA JWK missing e parameter',\n      }\n    );\n  },\n};\n\nexport const ec_public_key_jwk_import = {\n  test() {\n    const jwk = {\n      crv: 'P-256',\n      kty: 'EC',\n      x: 'X0mMYR_uleZSIPjNztIkAS3_ud5LhNpbiIFp6fNf2Gs',\n      y: 'UbJuPy2Xi0lW7UYTBxPK3yGgDu9EAKYIecjkHX5s2lI',\n    };\n\n    const key = createPublicKey({ key: jwk, format: 'jwk' });\n    strictEqual(key.asymmetricKeyType, 'ec');\n    deepStrictEqual(key.asymmetricKeyDetails, { namedCurve: 'prime256v1' });\n\n    // The fail due to missing information in the JWK\n    throws(\n      () => {\n        createPublicKey({\n          key: {\n            kty: 'EC',\n          },\n          format: 'jwk',\n        });\n      },\n      {\n        message: 'EC JWK missing crv parameter',\n      }\n    );\n\n    throws(\n      () => {\n        createPublicKey({\n          key: {\n            kty: 'EC',\n            crv: 'P-256',\n          },\n          format: 'jwk',\n        });\n      },\n      {\n        message: 'EC JWK missing x parameter',\n      }\n    );\n\n    throws(\n      () => {\n        createPublicKey({\n          key: {\n            kty: 'EC',\n            crv: 'P-256',\n            x: 'X0mMYR_uleZSIPjNztIkAS3_ud5LhNpbiIFp6fNf2Gs',\n          },\n          format: 'jwk',\n        });\n      },\n      {\n        message: 'EC JWK missing y parameter',\n      }\n    );\n  },\n};\n\nexport const ed_public_key_jwk_import = {\n  test() {\n    const jwk = {\n      crv: 'Ed25519',\n      x: 'K1wIouqnuiA04b3WrMa-xKIKIpfHetNZRv3h9fBf768',\n      kty: 'OKP',\n    };\n\n    const key = createPublicKey({ key: jwk, format: 'jwk' });\n    strictEqual(key.asymmetricKeyType, 'ed25519');\n\n    const jwk2 = {\n      crv: 'X25519',\n      x: 'K1wIouqnuiA04b3WrMa-xKIKIpfHetNZRv3h9fBf768',\n      kty: 'OKP',\n    };\n\n    const key2 = createPublicKey({ key: jwk2, format: 'jwk' });\n    strictEqual(key2.asymmetricKeyType, 'x25519');\n\n    // The fail due to missing information in the JWK\n    throws(\n      () => {\n        createPublicKey({\n          key: {\n            kty: 'OKP',\n          },\n          format: 'jwk',\n        });\n      },\n      {\n        message: 'OKP JWK missing crv parameter',\n      }\n    );\n  },\n};\n\nexport const generate_hmac_secret_key = {\n  async test() {\n    // Length is intentionally not a multiple of 8\n    const key = generateKeySync('hmac', { length: 33 });\n    strictEqual(key.type, 'secret');\n    strictEqual(key.symmetricKeySize, 4);\n\n    const { promise, resolve, reject } = Promise.withResolvers();\n    generateKey('hmac', { length: 33 }, (err, key) => {\n      if (err) {\n        reject(err);\n        return;\n      }\n      strictEqual(key.type, 'secret');\n      strictEqual(key.symmetricKeySize, 4);\n      resolve();\n    });\n    await promise;\n\n    throws(() => generateKeySync('hmac', { length: 0 }), {\n      message:\n        'The value of \"options.length\" is out of range. It must ' +\n        'be >= 8 && <= 65536. Received 0',\n    });\n    throws(() => generateKeySync('hmac', { length: 65537 }), {\n      message:\n        'The value of \"options.length\" is out of range. It must ' +\n        'be >= 8 && <= 65536. Received 65537',\n    });\n\n    const h = createHmac('sha256', key);\n    ok(h.update('test').digest());\n  },\n};\n\nexport const generate_aes_secret_key = {\n  async test() {\n    const key = generateKeySync('aes', { length: 128 });\n    strictEqual(key.type, 'secret');\n    strictEqual(key.symmetricKeySize, 16);\n\n    const { promise, resolve, reject } = Promise.withResolvers();\n    generateKey('aes', { length: 128 }, (err, key) => {\n      if (err) {\n        reject(err);\n        return;\n      }\n      strictEqual(key.type, 'secret');\n      strictEqual(key.symmetricKeySize, 16);\n      resolve();\n    });\n    await promise;\n\n    throws(() => generateKeySync('aes', { length: 0 }), {\n      message:\n        \"The property 'options.length' must be one of: 128, 192, \" +\n        '256. Received 0',\n    });\n  },\n};\n\nexport const generate_key_pair_arg_validation = {\n  async test() {\n    throws(() => generateKeyPairSync('invalid type'), {\n      message:\n        \"The argument 'type' must be one of: 'rsa', \" +\n        \"'ec', 'ed25519', 'x25519', 'dh'. Received \" +\n        \"'invalid type'\",\n    });\n\n    throws(() => generateKeyPairSync('rsa', { modulusLength: -1 }), {\n      message:\n        'The value of \"options.modulusLength\" is out of range. ' +\n        'It must be >= 0 && < 4294967296. Received -1',\n    });\n\n    throws(() => generateKeyPairSync('rsa', { modulusLength: 'foo' }), {\n      message:\n        'The \"options.modulusLength\" property must be of type number. ' +\n        \"Received type string ('foo')\",\n    });\n\n    throws(\n      () =>\n        generateKeyPairSync('rsa', {\n          modulusLength: 512,\n          publicExponent: 'foo',\n        }),\n      {\n        message:\n          'The \"options.publicExponent\" property must be of type number. ' +\n          \"Received type string ('foo')\",\n      }\n    );\n\n    // TODO(later): BoringSSL does not currently support rsa-pss key generation\n    // in the way Node.js does. Uncomment these later when it does.\n    // throws(\n    //   () =>\n    //     generateKeyPairSync('rsa-pss', {\n    //       modulusLength: 512,\n    //       hashAlgorithm: false,\n    //     }),\n    //   {\n    //     message:\n    //       'The \"options.hashAlgorithm\" property must be of type string. ' +\n    //       'Received type boolean (false)',\n    //   }\n    // );\n\n    // throws(\n    //   () =>\n    //     generateKeyPairSync('rsa-pss', {\n    //       modulusLength: 512,\n    //       mgf1HashAlgorithm: false,\n    //     }),\n    //   {\n    //     message:\n    //       'The \"options.mgf1HashAlgorithm\" property must be of type ' +\n    //       'string. Received type boolean (false)',\n    //   }\n    // );\n\n    // throws(\n    //   () =>\n    //     generateKeyPairSync('rsa-pss', {\n    //       modulusLength: 512,\n    //       saltLength: 'foo',\n    //     }),\n    //   {\n    //     message:\n    //       'The \"options.saltLength\" property must be of type number. ' +\n    //       \"Received type string ('foo')\",\n    //   }\n    // );\n\n    // TODO(later): BoringSSL currently does not support dsa key generation\n    // in the same way Node.js does. Uncomment this when it does.\n    // throws(\n    //   () =>\n    //     generateKeyPairSync('dsa', {\n    //       modulusLength: 512,\n    //       divisorLength: 'foo',\n    //     }),\n    //   {\n    //     message:\n    //       'The \"options.divisorLength\" property must be of type number. ' +\n    //       \"Received type string ('foo')\",\n    //   }\n    // );\n\n    throws(() => generateKeyPairSync('dh', { prime: 'foo' }), {\n      message:\n        'The \"options.prime\" property must be an instance of Buffer, ' +\n        \"TypedArray, or ArrayBuffer. Received type string ('foo')\",\n    });\n\n    throws(() => generateKeyPairSync('dh', { primeLength: 'foo' }), {\n      message:\n        'The \"options.primeLength\" property must be of type number. ' +\n        \"Received type string ('foo')\",\n    });\n\n    throws(() => generateKeyPairSync('dh', { primeLength: -1 }), {\n      message:\n        'The value of \"options.primeLength\" is out of range. It ' +\n        'must be >= 0 && <= 2147483647. Received -1',\n    });\n\n    throws(\n      () => generateKeyPairSync('dh', { primeLength: 10, generator: -1 }),\n      {\n        message:\n          'The value of \"options.generator\" is out of range. It must ' +\n          'be >= 0 && <= 2147483647. Received -1',\n      }\n    );\n\n    throws(() => generateKeyPairSync('dh', { groupName: 123 }), {\n      message:\n        'The \"options.group\" property must be of type string. ' +\n        'Received type number (123)',\n    });\n\n    throws(\n      () =>\n        generateKeyPairSync('ec', { namedCurve: 'foo', paramEncoding: 'foo' }),\n      {\n        message:\n          \"The property 'options.paramEncoding' must be one of: \" +\n          \"'named', 'explicit'. Received 'foo'\",\n      }\n    );\n\n    throws(() => generateKeyPairSync('ed25519', { publicKeyEncoding: 'foo' }), {\n      message:\n        'The \"options.publicKeyEncoding\" property must be of type ' +\n        \"object. Received type string ('foo')\",\n    });\n\n    throws(() => generateKeyPairSync('x25519', { privateKeyEncoding: 'foo' }), {\n      message:\n        'The \"options.privateKeyEncoding\" property must be of type ' +\n        \"object. Received type string ('foo')\",\n    });\n\n    // ====\n    async function wrapped(...args) {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      generateKeyPair(...args, (err) => {\n        if (err) {\n          reject(err);\n          return;\n        }\n        resolve();\n      });\n      await promise;\n    }\n\n    await rejects(wrapped('invalid type', {}), {\n      message:\n        \"The argument 'type' must be one of: 'rsa', \" +\n        \"'ec', 'ed25519', 'x25519', 'dh'. Received \" +\n        \"'invalid type'\",\n    });\n\n    await rejects(wrapped('rsa', { modulusLength: -1 }), {\n      message:\n        'The value of \"options.modulusLength\" is out of range. ' +\n        'It must be >= 0 && < 4294967296. Received -1',\n    });\n\n    await rejects(wrapped('rsa', { modulusLength: 'foo' }), {\n      message:\n        'The \"options.modulusLength\" property must be of type number. ' +\n        \"Received type string ('foo')\",\n    });\n\n    await rejects(\n      wrapped('rsa', { modulusLength: 512, publicExponent: 'foo' }),\n      {\n        message:\n          'The \"options.publicExponent\" property must be of type number. ' +\n          \"Received type string ('foo')\",\n      }\n    );\n\n    // TODO(later): BoringSSL currently does not support rsa-pss key generation\n    // in the same way Node.js does. Uncomment these later when it does.\n    // await rejects(\n    //   wrapped('rsa-pss', { modulusLength: 512, hashAlgorithm: false }),\n    //   {\n    //     message:\n    //       'The \"options.hashAlgorithm\" property must be of type string. ' +\n    //       'Received type boolean (false)',\n    //   }\n    // );\n\n    // await rejects(\n    //   wrapped('rsa-pss', { modulusLength: 512, mgf1HashAlgorithm: false }),\n    //   {\n    //     message:\n    //       'The \"options.mgf1HashAlgorithm\" property must be of type ' +\n    //       'string. Received type boolean (false)',\n    //   }\n    // );\n\n    // await rejects(\n    //   wrapped('rsa-pss', { modulusLength: 512, saltLength: 'foo' }),\n    //   {\n    //     message:\n    //       'The \"options.saltLength\" property must be of type number. ' +\n    //       \"Received type string ('foo')\",\n    //   }\n    // );\n\n    // TODO(later): BoringSSL currently does not support dsa key generation\n    // in the same way Node.js does. Uncomment this later when it does.\n    // await rejects(\n    //   wrapped('dsa', { modulusLength: 512, divisorLength: 'foo' }),\n    //   {\n    //     message:\n    //       'The \"options.divisorLength\" property must be of type number. ' +\n    //       \"Received type string ('foo')\",\n    //   }\n    // );\n\n    await rejects(wrapped('dh', { prime: 'foo' }), {\n      message:\n        'The \"options.prime\" property must be an instance of Buffer, ' +\n        \"TypedArray, or ArrayBuffer. Received type string ('foo')\",\n    });\n\n    await rejects(wrapped('dh', { primeLength: 'foo' }), {\n      message:\n        'The \"options.primeLength\" property must be of type number. ' +\n        \"Received type string ('foo')\",\n    });\n\n    await rejects(wrapped('dh', { primeLength: -1 }), {\n      message:\n        'The value of \"options.primeLength\" is out of range. It ' +\n        'must be >= 0 && <= 2147483647. Received -1',\n    });\n\n    await rejects(wrapped('dh', { primeLength: 10, generator: -1 }), {\n      message:\n        'The value of \"options.generator\" is out of range. It must ' +\n        'be >= 0 && <= 2147483647. Received -1',\n    });\n\n    await rejects(wrapped('dh', { groupName: 123 }), {\n      message:\n        'The \"options.group\" property must be of type string. ' +\n        'Received type number (123)',\n    });\n\n    await rejects(wrapped('ec', { namedCurve: 'foo', paramEncoding: 'foo' }), {\n      message:\n        \"The property 'options.paramEncoding' must be one of: \" +\n        \"'named', 'explicit'. Received 'foo'\",\n    });\n\n    await rejects(wrapped('ed25519', { publicKeyEncoding: 'foo' }), {\n      message:\n        'The \"options.publicKeyEncoding\" property must be of type ' +\n        \"object. Received type string ('foo')\",\n    });\n\n    await rejects(wrapped('x25519', { privateKeyEncoding: 'foo' }), {\n      message:\n        'The \"options.privateKeyEncoding\" property must be of type ' +\n        \"object. Received type string ('foo')\",\n    });\n  },\n};\n\nexport const generate_rsa_key_pair = {\n  test() {\n    const { publicKey, privateKey } = generateKeyPairSync('rsa', {\n      modulusLength: 2048,\n    });\n    strictEqual(publicKey.type, 'public');\n    strictEqual(publicKey.asymmetricKeyDetails.modulusLength, 2048);\n    strictEqual(publicKey.asymmetricKeyDetails.publicExponent, 65537n);\n\n    strictEqual(privateKey.type, 'private');\n    strictEqual(privateKey.asymmetricKeyDetails.modulusLength, 2048);\n    strictEqual(privateKey.asymmetricKeyDetails.publicExponent, 65537n);\n  },\n};\n\nexport const generate_rsa_key_pair_enc = {\n  test() {\n    const { publicKey, privateKey } = generateKeyPairSync('rsa', {\n      modulusLength: 2048,\n      publicKeyEncoding: {\n        format: 'der',\n        type: 'pkcs1',\n      },\n      privateKeyEncoding: {\n        format: 'pem',\n        type: 'pkcs8',\n      },\n    });\n\n    const pubImported = createPublicKey({\n      key: publicKey,\n      format: 'der',\n      type: 'pkcs1',\n    });\n    strictEqual(pubImported.type, 'public');\n    strictEqual(pubImported.asymmetricKeyDetails.modulusLength, 2048);\n    strictEqual(pubImported.asymmetricKeyDetails.publicExponent, 65537n);\n\n    const imported = createPrivateKey(privateKey);\n    strictEqual(imported.type, 'private');\n    strictEqual(imported.asymmetricKeyDetails.modulusLength, 2048);\n    strictEqual(imported.asymmetricKeyDetails.publicExponent, 65537n);\n  },\n};\n\nexport const generate_ec_key_pair = {\n  test() {\n    const { privateKey, publicKey } = generateKeyPairSync('ec', {\n      namedCurve: 'P-256',\n    });\n    strictEqual(publicKey.type, 'public');\n    strictEqual(publicKey.asymmetricKeyDetails.namedCurve, 'prime256v1');\n    strictEqual(privateKey.type, 'private');\n    strictEqual(privateKey.asymmetricKeyDetails.namedCurve, 'prime256v1');\n  },\n};\n\nexport const generate_ed25519_key_pair = {\n  test() {\n    const { privateKey, publicKey } = generateKeyPairSync('ed25519', {});\n    strictEqual(publicKey.type, 'public');\n    strictEqual(privateKey.type, 'private');\n    strictEqual(publicKey.asymmetricKeyType, 'ed25519');\n    strictEqual(privateKey.asymmetricKeyType, 'ed25519');\n  },\n};\n\nexport const generate_x25519_key_pair = {\n  test() {\n    const { privateKey, publicKey } = generateKeyPairSync('x25519', {});\n    strictEqual(publicKey.type, 'public');\n    strictEqual(privateKey.type, 'private');\n    strictEqual(publicKey.asymmetricKeyType, 'x25519');\n    strictEqual(privateKey.asymmetricKeyType, 'x25519');\n  },\n};\n\n// TODO(later): BoringSSL with fips does not support generating DH keys\n// this way. Uncomment this later when it does.\n// export const generate_dh_key_pair = {\n//   test() {\n//     const { privateKey, publicKey } = generateKeyPairSync('dh', {\n//       group: 'modp14',\n//     });\n//     strictEqual(publicKey.type, 'public');\n//     strictEqual(privateKey.type, 'private');\n//     strictEqual(publicKey.asymmetricKeyType, 'dh');\n//     strictEqual(privateKey.asymmetricKeyType, 'dh');\n\n//     const res = diffieHellman({ privateKey, publicKey });\n//     ok(res instanceof Buffer);\n//     strictEqual(res.byteLength, 256);\n//   },\n// };\n\n// TODO(later): BoringSSL with fips does not support generating DH keys\n// this way. Uncomment this later when it does.\n// export const generate_dh_from_fixed_prime = {\n//   test() {\n//     const prime = generatePrimeSync(1024);\n\n//     const { privateKey: privateKey1, publicKey: publicKey1 } =\n//       generateKeyPairSync('dh', {\n//         prime,\n//       });\n//     strictEqual(publicKey1.type, 'public');\n//     strictEqual(privateKey1.type, 'private');\n//     strictEqual(publicKey1.asymmetricKeyType, 'dh');\n//     strictEqual(privateKey1.asymmetricKeyType, 'dh');\n\n//     const { privateKey: privateKey2, publicKey: publicKey2 } =\n//       generateKeyPairSync('dh', {\n//         prime,\n//       });\n//     strictEqual(publicKey2.type, 'public');\n//     strictEqual(privateKey2.type, 'private');\n//     strictEqual(publicKey2.asymmetricKeyType, 'dh');\n//     strictEqual(privateKey2.asymmetricKeyType, 'dh');\n\n//     ok(!publicKey1.equals(publicKey2));\n//     ok(!privateKey1.equals(privateKey2));\n\n//     // Once we generate the keys, let's make sure they are usable.\n\n//     const res1 = diffieHellman({\n//       privateKey: privateKey2,\n//       publicKey: publicKey1,\n//     });\n//     ok(res1 instanceof Buffer);\n//     strictEqual(res1.byteLength, 128);\n\n//     const res2 = diffieHellman({\n//       privateKey: privateKey2,\n//       publicKey: publicKey1,\n//     });\n//     ok(res2 instanceof Buffer);\n//     strictEqual(res2.byteLength, 128);\n\n//     deepStrictEqual(res1, res2);\n//     // It's actual data and not just zeroes right?\n//     notDeepStrictEqual(res1, Buffer.alloc(128, 0));\n\n//     // Keys generated from different prime groups aren't compatible and should throw.\n//     const prime2 = generatePrimeSync(1024);\n//     const { privateKey: privateKey3, publicKey: publicKey3 } =\n//       generateKeyPairSync('dh', {\n//         prime: prime2,\n//       });\n//     strictEqual(publicKey3.type, 'public');\n//     strictEqual(privateKey3.type, 'private');\n//     strictEqual(publicKey3.asymmetricKeyType, 'dh');\n//     strictEqual(privateKey3.asymmetricKeyType, 'dh');\n\n//     throws(\n//       () => diffieHellman({ publicKey: publicKey1, privateKey: privateKey3 }),\n//       {\n//         message: 'Failed to derive shared diffie-hellman secret',\n//       }\n//     );\n//   },\n// };\n\nexport const generate_dh_key_pair_by_length = {\n  test() {\n    throws(\n      () =>\n        generateKeyPairSync('dh', {\n          primeLength: 1048,\n        }),\n      {\n        message:\n          'Generating DH keys from a prime length is not yet implemented',\n      }\n    );\n  },\n};\n\nexport const generate_ed_keypair_promisified = {\n  async test() {\n    const promisifiedGenKeyPair = promisify(generateKeyPair);\n    const { publicKey, privateKey } = await promisifiedGenKeyPair(\n      'ed25519',\n      {}\n    );\n    strictEqual(publicKey.asymmetricKeyType, 'ed25519');\n    strictEqual(privateKey.asymmetricKeyType, 'ed25519');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_keys-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"crypto_keys-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"crypto_keys-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"disable_fast_jsg_struct\"],\n        bindings = [\n          ( name = \"dh_private.pem\", text = embed \"fixtures/dh_private.pem\" ),\n          ( name = \"dsa_private_pkcs8.pem\", text = embed \"fixtures/dsa_private_pkcs8.pem\" ),\n          ( name = \"ed25519_private.pem\", text = embed \"fixtures/ed25519_private.pem\" ),\n          ( name = \"rsa_private_encrypted.pem\", text = embed \"fixtures/rsa_private_encrypted.pem\" ),\n          ( name = \"rsa_pss_private_2048_sha1_sha1_20.pem\", text = embed \"fixtures/rsa_pss_private_2048_sha1_sha1_20.pem\" ),\n          ( name = \"dsa_private_1025.pem\", text = embed \"fixtures/dsa_private_1025.pem\" ),\n          ( name = \"ec_p256_private.pem\", text = embed \"fixtures/ec_p256_private.pem\" ),\n          ( name = \"ed448_private.pem\", text = embed \"fixtures/ed448_private.pem\" ),\n          ( name = \"rsa_private.pem\", text = embed \"fixtures/rsa_private.pem\" ),\n          ( name = \"rsa_pss_private_2048_sha256_sha256_16.pem\", text = embed \"fixtures/rsa_pss_private_2048_sha256_sha256_16.pem\" ),\n          ( name = \"dsa_private_encrypted_1025.pem\", text = embed \"fixtures/dsa_private_encrypted_1025.pem\" ),\n          ( name = \"ec_p384_private.pem\", text = embed \"fixtures/ec_p384_private.pem\" ),\n          ( name = \"rsa_private_2048.pem\", text = embed \"fixtures/rsa_private_2048.pem\" ),\n          ( name = \"rsa_private_pkcs8_bad.pem\", text = embed \"fixtures/rsa_private_pkcs8_bad.pem\" ),\n          ( name = \"rsa_pss_private_2048_sha512_sha256_20.pem\", text = embed \"fixtures/rsa_pss_private_2048_sha512_sha256_20.pem\" ),\n          ( name = \"dsa_private_encrypted.pem\", text = embed \"fixtures/dsa_private_encrypted.pem\" ),\n          ( name = \"ec_p521_private.pem\", text = embed \"fixtures/ec_p521_private.pem\" ),\n          ( name = \"rsa_private_4096.pem\", text = embed \"fixtures/rsa_private_4096.pem\" ),\n          ( name = \"rsa_private_pkcs8.pem\", text = embed \"fixtures/rsa_private_pkcs8.pem\" ),\n          ( name = \"x25519_private.pem\", text = embed \"fixtures/x25519_private.pem\" ),\n          ( name = \"dsa_private.pem\", text = embed \"fixtures/dsa_private.pem\" ),\n          ( name = \"ec_secp256k1_private.pem\", text = embed \"fixtures/ec_secp256k1_private.pem\" ),\n          ( name = \"rsa_private_b.pem\", text = embed \"fixtures/rsa_private_b.pem\" ),\n          ( name = \"rsa_pss_private_2048.pem\", text = embed \"fixtures/rsa_pss_private_2048.pem\" ),\n          ( name = \"x448_private.pem\", text = embed \"fixtures/x448_private.pem\" ),\n\n          ( name = \"dh_public.pem\", text = embed \"fixtures/dh_public.pem\" ),\n          ( name = \"dsa_public_1025.pem\", text = embed \"fixtures/dsa_public_1025.pem\" ),\n          ( name = \"dsa_public.pem\", text = embed \"fixtures/dsa_public.pem\" ),\n          ( name = \"ec_p256_public.pem\", text = embed \"fixtures/ec_p256_public.pem\" ),\n          ( name = \"ec_p384_public.pem\", text = embed \"fixtures/ec_p384_public.pem\" ),\n          ( name = \"ec_p521_public.pem\", text = embed \"fixtures/ec_p521_public.pem\" ),\n          ( name = \"ec_secp256k1_public.pem\", text = embed \"fixtures/ec_secp256k1_public.pem\" ),\n          ( name = \"ed25519_public.pem\", text = embed \"fixtures/ed25519_public.pem\" ),\n          ( name = \"ed448_public.pem\", text = embed \"fixtures/ed448_public.pem\" ),\n          ( name = \"rsa_pss_public_2048.pem\", text = embed \"fixtures/rsa_pss_public_2048.pem\" ),\n          ( name = \"rsa_pss_public_2048_sha1_sha1_20.pem\", text = embed \"fixtures/rsa_pss_public_2048_sha1_sha1_20.pem\" ),\n          ( name = \"rsa_pss_public_2048_sha256_sha256_16.pem\", text = embed \"fixtures/rsa_pss_public_2048_sha256_sha256_16.pem\" ),\n          ( name = \"rsa_pss_public_2048_sha512_sha256_20.pem\", text = embed \"fixtures/rsa_pss_public_2048_sha512_sha256_20.pem\" ),\n          ( name = \"rsa_public_2048.pem\", text = embed \"fixtures/rsa_public_2048.pem\" ),\n          ( name = \"rsa_public_4096.pem\", text = embed \"fixtures/rsa_public_4096.pem\" ),\n          ( name = \"rsa_public_b.pem\", text = embed \"fixtures/rsa_public_b.pem\" ),\n          ( name = \"rsa_public.pem\", text = embed \"fixtures/rsa_public.pem\" ),\n          ( name = \"x25519_public.pem\", text = embed \"fixtures/x25519_public.pem\" ),\n          ( name = \"x448_public.pem\", text = embed \"fixtures/x448_public.pem\" ),\n\n          ( name = \"agent1-cert.pem\", text = embed \"fixtures/agent1-cert.pem\" ),\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_pbkdf2-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n'use strict';\n\nimport * as assert from 'node:assert';\nimport * as crypto from 'node:crypto';\n\nfunction mustNotCall() {\n  return () => {};\n}\n\nasync function runPBKDF2(password, salt, iterations, keylen, hash) {\n  const syncResult = crypto.pbkdf2Sync(\n    password,\n    salt,\n    iterations,\n    keylen,\n    hash\n  );\n\n  const p = Promise.withResolvers();\n  crypto.pbkdf2(\n    password,\n    salt,\n    iterations,\n    keylen,\n    hash,\n    (err, asyncResult) => {\n      assert.deepStrictEqual(asyncResult, syncResult);\n      p.resolve();\n    }\n  );\n  await p.promise;\n\n  return syncResult;\n}\n\nasync function testPBKDF2(\n  password,\n  salt,\n  iterations,\n  keylen,\n  expected,\n  encoding\n) {\n  const actual = await runPBKDF2(password, salt, iterations, keylen, 'sha256');\n  assert.strictEqual(actual.toString(encoding || 'latin1'), expected);\n}\n\n//\n// Test PBKDF2 with RFC 6070 test vectors (except #4)\n//\n\nexport const pbkdf2_correctness_tests = {\n  async test(ctrl, env, ctx) {\n    await testPBKDF2(\n      'password',\n      'salt',\n      1,\n      20,\n      '\\x12\\x0f\\xb6\\xcf\\xfc\\xf8\\xb3\\x2c\\x43\\xe7\\x22\\x52' +\n        '\\x56\\xc4\\xf8\\x37\\xa8\\x65\\x48\\xc9'\n    );\n\n    await testPBKDF2(\n      'password',\n      'salt',\n      2,\n      20,\n      '\\xae\\x4d\\x0c\\x95\\xaf\\x6b\\x46\\xd3\\x2d\\x0a\\xdf\\xf9' +\n        '\\x28\\xf0\\x6d\\xd0\\x2a\\x30\\x3f\\x8e'\n    );\n\n    await testPBKDF2(\n      'password',\n      'salt',\n      4096,\n      20,\n      '\\xc5\\xe4\\x78\\xd5\\x92\\x88\\xc8\\x41\\xaa\\x53\\x0d\\xb6' +\n        '\\x84\\x5c\\x4c\\x8d\\x96\\x28\\x93\\xa0'\n    );\n\n    await testPBKDF2(\n      'passwordPASSWORDpassword',\n      'saltSALTsaltSALTsaltSALTsaltSALTsalt',\n      4096,\n      25,\n      '\\x34\\x8c\\x89\\xdb\\xcb\\xd3\\x2b\\x2f\\x32\\xd8\\x14\\xb8\\x11' +\n        '\\x6e\\x84\\xcf\\x2b\\x17\\x34\\x7e\\xbc\\x18\\x00\\x18\\x1c'\n    );\n\n    await testPBKDF2(\n      'pass\\0word',\n      'sa\\0lt',\n      4096,\n      16,\n      '\\x89\\xb6\\x9d\\x05\\x16\\xf8\\x29\\x89\\x3c\\x69\\x62\\x26\\x65' + '\\x0a\\x86\\x87'\n    );\n\n    await testPBKDF2(\n      'password',\n      'salt',\n      32,\n      32,\n      '64c486c55d30d4c5a079b8823b7d7cb37ff0556f537da8410233bcec330ed956',\n      'hex'\n    );\n  },\n};\n\nexport const pbkdf2_no_callback_test = {\n  test(ctrl, env, ctx) {\n    // Error path should not leak memory (check with Valgrind).\n    assert.throws(() => crypto.pbkdf2('password', 'salt', 1, 20, 'sha1'), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n    });\n  },\n};\n\nexport const pbkdf2_out_of_range_tests = {\n  test(ctrl, env, ctx) {\n    for (const iterations of [-1, 0, 2147483648]) {\n      assert.throws(\n        () => crypto.pbkdf2Sync('password', 'salt', iterations, 20, 'sha1'),\n        {\n          code: 'ERR_OUT_OF_RANGE',\n          name: 'RangeError',\n        }\n      );\n    }\n\n    ['str', null, undefined, [], {}].forEach((notNumber) => {\n      assert.throws(\n        () => {\n          crypto.pbkdf2Sync('password', 'salt', 1, notNumber, 'sha256');\n        },\n        {\n          code: 'ERR_INVALID_ARG_TYPE',\n          name: 'TypeError',\n        }\n      );\n    });\n\n    [Infinity, -Infinity, NaN].forEach((input) => {\n      assert.throws(\n        () => {\n          crypto.pbkdf2('password', 'salt', 1, input, 'sha256', mustNotCall());\n        },\n        {\n          code: 'ERR_OUT_OF_RANGE',\n          name: 'RangeError',\n          message:\n            'The value of \"keylen\" is out of range. It ' +\n            `must be an integer. Received ${input}`,\n        }\n      );\n    });\n\n    [-1, 2147483648, 4294967296].forEach((input) => {\n      assert.throws(\n        () => {\n          crypto.pbkdf2('password', 'salt', 1, input, 'sha256', mustNotCall());\n        },\n        {\n          code: 'ERR_OUT_OF_RANGE',\n          name: 'RangeError',\n        }\n      );\n    });\n  },\n};\n\nexport const empty_pwd_test = {\n  async test(ctrl, env, ctx) {\n    // Should not get FATAL ERROR with empty password and salt\n    // https://github.com/nodejs/node/issues/8571\n    const p = Promise.withResolvers();\n    crypto.pbkdf2('', '', 1, 32, 'sha256', (err, prime) => {\n      p.resolve();\n    });\n    await p.promise;\n  },\n};\n\nexport const invalid_arg_tests = {\n  test(ctrl, env, ctx) {\n    assert.throws(\n      () => crypto.pbkdf2('password', 'salt', 8, 8, mustNotCall()),\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n        message:\n          'The \"digest\" argument must be of type string. ' +\n          'Received undefined',\n      }\n    );\n\n    assert.throws(() => crypto.pbkdf2Sync('password', 'salt', 8, 8), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n      message:\n        'The \"digest\" argument must be of type string. ' + 'Received undefined',\n    });\n\n    assert.throws(() => crypto.pbkdf2Sync('password', 'salt', 8, 8, null), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n      message:\n        'The \"digest\" argument must be of type string. ' + 'Received null',\n    });\n    [1, {}, [], true, undefined, null].forEach((input) => {\n      assert.throws(\n        () => crypto.pbkdf2(input, 'salt', 8, 8, 'sha256', mustNotCall()),\n        {\n          code: 'ERR_INVALID_ARG_TYPE',\n          name: 'TypeError',\n        }\n      );\n\n      assert.throws(\n        () => crypto.pbkdf2('pass', input, 8, 8, 'sha256', mustNotCall()),\n        {\n          code: 'ERR_INVALID_ARG_TYPE',\n          name: 'TypeError',\n        }\n      );\n\n      assert.throws(() => crypto.pbkdf2Sync(input, 'salt', 8, 8, 'sha256'), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n      });\n\n      assert.throws(() => crypto.pbkdf2Sync('pass', input, 8, 8, 'sha256'), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n      });\n    });\n\n    ['test', {}, [], true, undefined, null].forEach((i) => {\n      assert.throws(\n        () => crypto.pbkdf2('pass', 'salt', i, 8, 'sha256', mustNotCall()),\n        {\n          code: 'ERR_INVALID_ARG_TYPE',\n          name: 'TypeError',\n        }\n      );\n\n      assert.throws(() => crypto.pbkdf2Sync('pass', 'salt', i, 8, 'sha256'), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n      });\n    });\n  },\n};\n\nexport const TypedArray_tests = {\n  async test(ctrl, env, ctx) {\n    // Any TypedArray should work for password and salt.\n    for (const SomeArray of [\n      Uint8Array,\n      Uint16Array,\n      Uint32Array,\n      Float16Array,\n      Float32Array,\n      Float64Array,\n      ArrayBuffer,\n    ]) {\n      await runPBKDF2(new SomeArray(10), 'salt', 8, 8, 'sha256');\n      await runPBKDF2('pass', new SomeArray(10), 8, 8, 'sha256');\n    }\n  },\n};\n\nexport const invalid_digest_tests = {\n  async test(ctrl, env, ctx) {\n    {\n      const p = Promise.withResolvers();\n      crypto.pbkdf2('pass', 'salt', 8, 8, 'md55', (err, prime) => {\n        if (err) return p.reject(err);\n      });\n      await assert.rejects(p.promise);\n    }\n\n    assert.throws(() => crypto.pbkdf2Sync('pass', 'salt', 8, 8, 'md55'), {\n      name: 'TypeError',\n    });\n\n    // TODO(soon): Enable this once crypto.getHashes() is available. Note that shake* is not\n    // supported by BoringSSL so there's no need to filter it out, but we may want to filter other\n    // functions.\n    // const kNotPBKDF2Supported = ['shake128', 'shake256'];\n    // crypto.getHashes()\n    //   .filter((hash) => !kNotPBKDF2Supported.includes(hash))\n    //   .forEach((hash) => {\n    //     runPBKDF2(new Uint8Array(10), 'salt', 8, 8, hash);\n    //   });\n\n    {\n      // This should not crash.\n      assert.throws(() => crypto.pbkdf2Sync('1', '2', 1, 1, '%'), {\n        name: 'TypeError',\n      });\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_pbkdf2-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"crypto_pbkdf2-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"crypto_pbkdf2-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_random-test.js",
    "content": "import { ok, rejects, strictEqual, throws } from 'node:assert';\n\nimport {\n  generatePrime,\n  generatePrimeSync,\n  checkPrime,\n  checkPrimeSync,\n  timingSafeEqual,\n} from 'node:crypto';\n\nimport { Buffer } from 'node:buffer';\n\nexport const test = {\n  async test(ctrl, env, ctx) {\n    [1, 'hello', {}, []].forEach((i) => {\n      throws(() => checkPrimeSync(i), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n    });\n\n    // prettier-ignore\n    for (const checks of [-(2 ** 31), -1, 2 ** 31, 2 ** 32 - 1, 2 ** 32, 2 ** 50]) {\n      throws(() => checkPrimeSync(2n, { checks }), {\n        code: 'ERR_OUT_OF_RANGE',\n        message: /<= 2147483647/\n      });\n    }\n\n    ok(\n      !checkPrimeSync(Buffer.from([0x1]), {\n        fast: true,\n        trialDivision: true,\n        checks: 10,\n      })\n    );\n\n    ok(!checkPrimeSync(Buffer.from([0x1])));\n    ok(checkPrimeSync(Buffer.from([0x2])));\n    ok(checkPrimeSync(Buffer.from([0x3])));\n    ok(!checkPrimeSync(Buffer.from([0x4])));\n\n    ////////////////\n\n    ['hello', false, 123].forEach((i) => {\n      throws(() => generatePrimeSync(80, i), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n    });\n\n    for (const checks of ['hello', {}, []]) {\n      throws(() => checkPrimeSync(2n, { checks }), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        message: /checks/,\n      });\n    }\n\n    // //////////////////\n\n    ['hello', false, {}, []].forEach((i) => {\n      throws(() => generatePrime(i), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n      throws(() => generatePrimeSync(i), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n    });\n\n    ['hello', false, 123].forEach((i) => {\n      throws(() => generatePrime(80, i, {}), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n      throws(() => generatePrimeSync(80, i), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n    });\n\n    ['hello', false, 123].forEach((i) => {\n      throws(() => generatePrime(80, {}), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n    });\n\n    [-1, 0, 2 ** 31, 2 ** 31 + 1, 2 ** 32 - 1, 2 ** 32].forEach((size) => {\n      throws(() => generatePrime(-1), {\n        code: 'ERR_OUT_OF_RANGE',\n        message: />= 1 && <= 2147483647/,\n      });\n      throws(() => generatePrimeSync(size), {\n        code: 'ERR_OUT_OF_RANGE',\n        message: />= 1 && <= 2147483647/,\n      });\n    });\n\n    // TODO: Fix and enable asynchronous tests\n    ['test', -1, {}, []].forEach((i) => {\n      throws(() => generatePrime(8, { safe: i }, () => {}), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n      throws(() => generatePrime(8, { rem: i }, () => {}), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n      throws(() => generatePrime(8, { add: i }, () => {}), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n      throws(() => generatePrimeSync(8, { safe: i }), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n      throws(() => generatePrimeSync(8, { rem: i }), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n      throws(() => generatePrimeSync(8, { add: i }), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n    });\n\n    {\n      // Negative BigInts should not be converted to 0 silently.\n      throws(() => generatePrime(20, { add: -1n }, () => {}), {\n        code: 'ERR_OUT_OF_RANGE',\n      });\n\n      throws(() => generatePrime(20, { rem: -1n }, () => {}), {\n        code: 'ERR_OUT_OF_RANGE',\n      });\n      throws(() => checkPrime(-1n, () => {}), {\n        code: 'ERR_OUT_OF_RANGE',\n      });\n    }\n\n    {\n      const p = Promise.withResolvers();\n      generatePrime(80, (err, prime) => {\n        ok(checkPrimeSync(prime));\n        checkPrime(prime, (err, result) => {\n          ok(result);\n          p.resolve();\n        });\n      });\n      await p.promise;\n    }\n\n    ok(checkPrimeSync(generatePrimeSync(80)));\n\n    {\n      const p = Promise.withResolvers();\n      generatePrime(80, {}, (err, prime) => {\n        if (err) return p.reject(err);\n        ok(checkPrimeSync(prime));\n        p.resolve();\n      });\n      await p.promise;\n    }\n\n    ok(checkPrimeSync(generatePrimeSync(80, {})));\n\n    {\n      const p = Promise.withResolvers();\n      generatePrime(32, { safe: true }, (err, prime) => {\n        if (err) return p.reject(err);\n        ok(checkPrimeSync(prime));\n        const buf = Buffer.from(prime);\n        const val = buf.readUInt32BE();\n        const check = (val - 1) / 2;\n        buf.writeUInt32BE(check);\n        ok(checkPrimeSync(buf));\n        p.resolve();\n      });\n      await p.promise;\n    }\n\n    {\n      const prime = generatePrimeSync(32, { safe: true });\n      ok(checkPrimeSync(prime));\n      const buf = Buffer.from(prime);\n      const val = buf.readUInt32BE();\n      const check = (val - 1) / 2;\n      buf.writeUInt32BE(check);\n      ok(checkPrimeSync(buf));\n    }\n\n    const add = 12;\n    const rem = 11;\n    const add_buf = Buffer.from([add]);\n    const rem_buf = Buffer.from([rem]);\n\n    {\n      const p = Promise.withResolvers();\n      generatePrime(32, { add: add_buf, rem: rem_buf }, (err, prime) => {\n        if (err) return p.reject(err);\n        ok(checkPrimeSync(prime));\n        const buf = Buffer.from(prime);\n        const val = buf.readUInt32BE();\n        strictEqual(val % add, rem);\n        p.resolve();\n      });\n      await p.promise;\n    }\n\n    {\n      const prime = generatePrimeSync(32, { add: add_buf, rem: rem_buf });\n      ok(checkPrimeSync(prime));\n      const buf = Buffer.from(prime);\n      const val = buf.readUInt32BE();\n      strictEqual(val % add, rem);\n    }\n\n    {\n      const prime = generatePrimeSync(32, {\n        add: BigInt(add),\n        rem: BigInt(rem),\n      });\n      ok(checkPrimeSync(prime));\n      const buf = Buffer.from(prime);\n      const val = buf.readUInt32BE();\n      strictEqual(val % add, rem);\n    }\n\n    {\n      const p = Promise.withResolvers();\n      generatePrime(\n        128,\n        {\n          bigint: true,\n          add: 5n,\n        },\n        (err, prime) => {\n          // Fails because the add option is not a supported value\n          if (err) return p.reject(err);\n        }\n      );\n      await rejects(p.promise);\n    }\n    {\n      const p = Promise.withResolvers();\n      generatePrime(\n        128,\n        {\n          bigint: true,\n          safe: true,\n          add: 5n,\n        },\n        (err, prime) => {\n          // Fails because the add option is not a supported value\n          if (err) return p.reject(err);\n        }\n      );\n      await rejects(p.promise);\n    }\n\n    // This is impossible because it implies (prime % 2**64) == 1 and\n    // prime < 2**64, meaning prime = 1, but 1 is not prime.\n    for (const add of [2n ** 64n, 2n ** 65n]) {\n      throws(\n        () => {\n          generatePrimeSync(64, { add });\n        },\n        {\n          name: 'RangeError',\n        }\n      );\n    }\n\n    // Any parameters with rem >= add lead to an impossible condition.\n    for (const rem of [7n, 8n, 3000n]) {\n      throws(\n        () => {\n          generatePrimeSync(64, { add: 7n, rem });\n        },\n        {\n          name: 'RangeError',\n        }\n      );\n    }\n\n    // This is possible, but not allowed. It implies prime == 7, which means that\n    // we did not actually generate a random prime.\n    throws(\n      () => {\n        generatePrimeSync(3, { add: 8n, rem: 7n });\n      },\n      {\n        name: 'RangeError',\n      }\n    );\n\n    // We only allow specific values of add and rem\n    throws(\n      () =>\n        generatePrimeSync(8, {\n          add: 7n,\n          rem: 1n,\n        }),\n      {\n        name: 'RangeError',\n      }\n    );\n    throws(\n      () =>\n        generatePrimeSync(8, {\n          add: 12n,\n          rem: 10n,\n        }),\n      {\n        name: 'RangeError',\n      }\n    );\n    throws(\n      () =>\n        generatePrimeSync(8, {\n          add: 12n,\n        }),\n      {\n        name: 'RangeError',\n      }\n    );\n\n    [1, 'hello', {}, []].forEach((i) => {\n      throws(() => checkPrime(i), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n    });\n\n    for (const checks of ['hello', {}, []]) {\n      throws(() => checkPrime(2n, { checks }, () => {}), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        message: /checks/,\n      });\n      throws(() => checkPrimeSync(2n, { checks }), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        message: /checks/,\n      });\n    }\n\n    // prettier-ignore\n    for (const checks of [-(2 ** 31), -1, 2 ** 31, 2 ** 32 - 1, 2 ** 32, 2 ** 50]) {\n      throws(() => checkPrime(2n, { checks }, () => {}), {\n        code: 'ERR_OUT_OF_RANGE',\n        message: /<= 2147483647/\n      });\n      throws(() => checkPrimeSync(2n, { checks }), {\n        code: 'ERR_OUT_OF_RANGE',\n        message: /<= 2147483647/\n      });\n    }\n\n    ok(\n      !checkPrimeSync(Buffer.from([0x1]), {\n        fast: true,\n        trialDivision: true,\n        checks: 10,\n      })\n    );\n\n    throws(\n      () => {\n        generatePrimeSync(32, { bigint: '' });\n      },\n      { code: 'ERR_INVALID_ARG_TYPE' }\n    );\n\n    throws(\n      () => {\n        generatePrime(32, { bigint: '' }, () => {});\n      },\n      { code: 'ERR_INVALID_ARG_TYPE' }\n    );\n\n    {\n      const prime = generatePrimeSync(3, { bigint: true });\n      strictEqual(typeof prime, 'bigint');\n      strictEqual(prime, 7n);\n      ok(checkPrimeSync(prime));\n      const p = Promise.withResolvers();\n      checkPrime(prime, (err, result) => {\n        if (err) return p.reject(err);\n        p.resolve(result);\n      });\n      await p.promise;\n    }\n\n    {\n      const p = Promise.withResolvers();\n      generatePrime(3, { bigint: true }, (err, prime) => {\n        if (err) return p.reject(err);\n        strictEqual(typeof prime, 'bigint');\n        strictEqual(prime, 7n);\n        ok(checkPrimeSync(prime));\n        checkPrime(prime, (err, result) => {\n          if (err) return p.reject(err);\n          p.resolve(result);\n        });\n      });\n      await p.promise;\n    }\n  },\n};\n\nexport const timingSafeEqualTest = {\n  test() {\n    timingSafeEqual(new Uint8Array(1), new Uint8Array(1));\n  },\n};\n\nexport const randomIntTest = {\n  async test() {\n    const { randomInt } = await import('node:crypto');\n\n    // min === max should throw, not infinite-loop\n    throws(() => randomInt(5, 5), { code: 'ERR_OUT_OF_RANGE' });\n\n    // min > max should throw\n    throws(() => randomInt(10, 5), { code: 'ERR_OUT_OF_RANGE' });\n\n    // Valid range returns value in [min, max)\n    const val = randomInt(0, 10);\n    ok(val >= 0 && val < 10);\n\n    // Single-arg form: randomInt(max) means [0, max)\n    strictEqual(randomInt(1), 0);\n  },\n};\n\nexport const randomFillSyncTest = {\n  async test() {\n    const { randomFillSync } = await import('node:crypto');\n\n    // With offset but no size, should fill only buf.length - offset bytes\n    const buf = Buffer.alloc(10, 0);\n    randomFillSync(buf, 4);\n\n    // First 4 bytes must be untouched (all zero)\n    for (let i = 0; i < 4; i++) {\n      strictEqual(buf[i], 0, `byte ${i} should be untouched`);\n    }\n  },\n};\n\n// Ref: https://github.com/cloudflare/workerd/issues/2716\nexport const getRandomValuesIllegalInvocation = {\n  async test() {\n    const crypto = await import('node:crypto');\n    {\n      // The following assertion doesn't fail as of Node.js v20.17.0\n      const getRandomValues = crypto.getRandomValues;\n      strictEqual(getRandomValues(new Uint8Array(6)).length, 6);\n    }\n    throws(\n      () => {\n        // This ensures that we are replicating Node.js behavior as of v20.17.0\n        const getRandomValues = crypto.webcrypto.getRandomValues;\n        strictEqual(getRandomValues(new Uint8Array(6)).length, 6);\n      },\n      {\n        name: 'TypeError',\n        message: /Illegal invocation/,\n      }\n    );\n    strictEqual(crypto.getRandomValues(new Uint8Array(6)).length, 6);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_random-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"crypto_random-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"crypto_random-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_scrypt-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n'use strict';\n\nimport { strictEqual, throws, rejects } from 'node:assert';\n\nimport { scrypt, scryptSync } from 'node:crypto';\n\nimport { mock } from 'node:test';\n\nconst good = [\n  // Zero-length key is legal, functions as a parameter validation check.\n  {\n    pass: '',\n    salt: '',\n    keylen: 0,\n    N: 16,\n    p: 1,\n    r: 1,\n    expected: '',\n  },\n  // Test vectors from https://tools.ietf.org/html/rfc7914#page-13 that\n  // should pass.  Note that the test vector with N=1048576 is omitted\n  // because it takes too long to complete and uses over 1 GiB of memory.\n  {\n    pass: '',\n    salt: '',\n    keylen: 64,\n    N: 16,\n    p: 1,\n    r: 1,\n    expected:\n      '77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442' +\n      'fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906',\n  },\n  {\n    pass: 'password',\n    salt: 'NaCl',\n    keylen: 64,\n    N: 1024,\n    p: 16,\n    r: 8,\n    expected:\n      'fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162' +\n      '2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640',\n  },\n  {\n    pass: 'pleaseletmein',\n    salt: 'SodiumChloride',\n    keylen: 64,\n    N: 16384,\n    p: 1,\n    r: 8,\n    expected:\n      '7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2' +\n      'd5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887',\n  },\n  {\n    pass: '',\n    salt: '',\n    keylen: 64,\n    cost: 16,\n    parallelization: 1,\n    blockSize: 1,\n    expected:\n      '77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442' +\n      'fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906',\n  },\n  {\n    pass: 'password',\n    salt: 'NaCl',\n    keylen: 64,\n    cost: 1024,\n    parallelization: 16,\n    blockSize: 8,\n    expected:\n      'fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162' +\n      '2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640',\n  },\n  {\n    pass: 'pleaseletmein',\n    salt: 'SodiumChloride',\n    keylen: 64,\n    cost: 16384,\n    parallelization: 1,\n    blockSize: 8,\n    expected:\n      '7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2' +\n      'd5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887',\n  },\n];\n\n// Test vectors that should fail.\nconst bad = [\n  { N: 1, p: 1, r: 1 }, // N < 2\n  { N: 3, p: 1, r: 1 }, // Not power of 2.\n];\n\n// Test vectors where 128*N*r exceeds maxmem.\nconst toobig = [\n  { N: 2 ** 16, p: 1, r: 1 }, // N >= 2**(r*16)\n  { N: 2, p: 2 ** 30, r: 1 }, // p > (2**30-1)/r\n  { N: 2 ** 20, p: 1, r: 8 },\n  { N: 2 ** 10, p: 1, r: 8, maxmem: 2 ** 20 },\n];\n\nconst badargs = [\n  {\n    args: [],\n    expected: { code: 'ERR_INVALID_ARG_TYPE', message: /\"password\"/ },\n  },\n  {\n    args: [null],\n    expected: { code: 'ERR_INVALID_ARG_TYPE', message: /\"password\"/ },\n  },\n  {\n    args: [''],\n    expected: { code: 'ERR_INVALID_ARG_TYPE', message: /\"salt\"/ },\n  },\n  {\n    args: ['', null],\n    expected: { code: 'ERR_INVALID_ARG_TYPE', message: /\"salt\"/ },\n  },\n  {\n    args: ['', ''],\n    expected: { code: 'ERR_INVALID_ARG_TYPE', message: /\"keylen\"/ },\n  },\n  {\n    args: ['', '', null],\n    expected: { code: 'ERR_INVALID_ARG_TYPE', message: /\"keylen\"/ },\n  },\n  {\n    args: ['', '', 0.42],\n    expected: { code: 'ERR_OUT_OF_RANGE', message: /\"keylen\"/ },\n  },\n  {\n    args: ['', '', -42],\n    expected: { code: 'ERR_OUT_OF_RANGE', message: /\"keylen\"/ },\n  },\n  {\n    args: ['', '', 2 ** 31],\n    expected: { code: 'ERR_OUT_OF_RANGE', message: /\"keylen\"/ },\n  },\n  {\n    args: ['', '', 2147485780],\n    expected: { code: 'ERR_OUT_OF_RANGE', message: /\"keylen\"/ },\n  },\n  {\n    args: ['', '', 2 ** 32],\n    expected: { code: 'ERR_OUT_OF_RANGE', message: /\"keylen\"/ },\n  },\n];\n\nexport const goodTests = {\n  async test() {\n    for (const options of good) {\n      const { pass, salt, keylen, expected } = options;\n      const actual = scryptSync(pass, salt, keylen, options);\n      strictEqual(actual.toString('hex'), expected);\n      const { promise, resolve } = Promise.withResolvers();\n      const fn = mock.fn((err, actual) => {\n        strictEqual(actual.toString('hex'), expected);\n        resolve();\n      });\n      scrypt(pass, salt, keylen, options, fn);\n      await promise;\n      strictEqual(fn.mock.calls.length, 1);\n    }\n  },\n};\n\nexport const badTests = {\n  async test() {\n    for (const options of bad) {\n      const { promise, reject } = Promise.withResolvers();\n      const fn = mock.fn((err, actual) => {\n        if (err) reject(err);\n      });\n      scrypt('pass', 'salt', 1, options, fn);\n      await rejects(promise);\n      throws(() => scryptSync('pass', 'salt', 1, options));\n    }\n    throws(() => scryptSync('pass', 'salt', 1, { N: 1, cost: 1 }));\n    throws(() => scryptSync('pass', 'salt', 1, { p: 1, parallelization: 1 }));\n    throws(() => scryptSync('pass', 'salt', 1, { r: 1, blockSize: 1 }));\n  },\n};\n\nexport const tooBigTests = {\n  async test() {\n    for (const options of toobig) {\n      const { promise, reject } = Promise.withResolvers();\n      const fn = mock.fn((err, actual) => {\n        if (err) reject(err);\n      });\n      scrypt('pass', 'salt', 1, options, fn);\n      await rejects(promise);\n      strictEqual(fn.mock.calls.length, 1);\n\n      throws(() => scryptSync('pass', 'salt', 1, options));\n    }\n  },\n};\n\nexport const defaultsTest = {\n  async test() {\n    const defaults = { N: 16384, p: 1, r: 8 };\n    const expected = scryptSync('pass', 'salt', 1, defaults);\n    const actual = scryptSync('pass', 'salt', 1);\n    strictEqual(actual.toString('hex'), expected.toString('hex'));\n    const { promise, resolve } = Promise.withResolvers();\n    const fn = mock.fn((err, actual) => {\n      strictEqual(actual.toString('hex'), expected.toString('hex'));\n      resolve();\n    });\n    scrypt('pass', 'salt', 1, fn);\n    await promise;\n    strictEqual(fn.mock.calls.length, 1);\n  },\n};\n\nexport const badArgsTest = {\n  test() {\n    for (const { args, expected } of badargs) {\n      throws(() => scrypt(...args));\n      throws(() => scryptSync(...args));\n    }\n\n    throws(() => scrypt('', '', 42, null));\n    throws(() => scrypt('', '', 42, {}, null));\n    throws(() => scrypt('', '', 42, {}));\n    throws(() => scrypt('', '', 42, {}, {}));\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_scrypt-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"crypto_scrypt-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"crypto_scrypt-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat_v2\", \"experimental\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_sign-test.js",
    "content": "import {\n  createSign,\n  createVerify,\n  createPrivateKey,\n  createPublicKey,\n  generateKeyPairSync,\n  sign,\n  verify,\n} from 'node:crypto';\n\nimport { ok, strictEqual, throws } from 'node:assert';\n\nconst rsaSig =\n  '26bb4d9641ecec048b791322c6427f62f3f4e21f7198e9e7544c8a56af40' +\n  '27bcfe1b306291188f97ced0e3ceaa5ded1ae1406ec30e46a18434e55dc6' +\n  'c9f237e26b124bf7ec77e54483d7782b805aa9a74bbe0f4aa8658d7620e4' +\n  'd3a305777b5dc8262c675bf23c8dc5acfe8c4fa1ca8acdd956cdcbdf1fb2' +\n  'f879b37dc0ed8ec51815ff02b98eefde44ac79f886902ea69e5ed2561e9e' +\n  'eb2a74ec1de677b285f8108f25e9f34cc826fbbd1ad091d231dc73eaf28a' +\n  'e02f09ad84ec9a38d8a7e28bea26fb7d4db20eecd075b5b261ca7320af94' +\n  'cd58ba4b26d895df4fd6f68bd4d82acdcb35557012e2f69739ce8cf4a66e' +\n  'bf4550ee50f6c9cec642d66fef71495a';\n\nexport const rsaSignVerifyObjects = {\n  test(_, env) {\n    const key = createPrivateKey(env['rsa_private.pem']);\n\n    throws(() => createSign(), {\n      message:\n        'The \"algorithm\" argument must be of type string. Received undefined',\n    });\n\n    const signer = createSign('sha256');\n    signer.update('hello world');\n\n    throws(() => signer.update(1), {\n      message: /argument must be of type string/,\n    });\n\n    throws(() => signer.sign(), {\n      message: 'No key provided to sign',\n    });\n\n    throws(() => signer.sign(env['rsa_public.pem']), {\n      message: 'Failed to parse private key',\n    });\n\n    const pub = createPublicKey(env['rsa_public.pem']);\n    throws(() => signer.sign(pub), {\n      code: 'ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE',\n    });\n\n    const signature = signer.sign(key, 'hex');\n\n    throws(() => signer.sign(key, 'hex'), {\n      message: 'Signing context has already been finalized',\n    });\n\n    strictEqual(signature, rsaSig);\n\n    const verify = createVerify('sha256');\n\n    throws(() => createVerify(), {\n      message:\n        'The \"algorithm\" argument must be of type string. Received undefined',\n    });\n\n    verify.update('hello world');\n\n    throws(() => verify.update(1), {\n      message: /argument must be of type string/,\n    });\n\n    throws(() => verify.verify(), {\n      message: 'No key provided to sign',\n    });\n\n    strictEqual(verify.verify(env['rsa_public.pem'], signature, 'hex'), true);\n\n    throws(() => verify.verify(env['rsa_public.pem'], signature, 'hex'), {\n      message: 'Verification context has already been finalized',\n    });\n  },\n};\n\nexport const rsaSignVerifyOneshot = {\n  test(_, env) {\n    const key = createPrivateKey(env['rsa_private.pem']);\n    const sig = sign('sha256', Buffer.from('hello world'), key);\n    strictEqual(sig.toString('hex'), rsaSig);\n    strictEqual(\n      verify('sha256', Buffer.from('hello world'), env['rsa_public.pem'], sig),\n      true\n    );\n  },\n};\n\nexport const ed25519SignVerifyObjects = {\n  test(_, env) {\n    // Sign object is not allowed with ed25519\n    throws(\n      () => {\n        const key = createPrivateKey(env['ed25519_private.pem']);\n        const signer = createSign('sha256');\n        signer.update('hello world');\n        const signature = signer.sign(key, 'hex');\n      },\n      {\n        message: 'Failed to set signature digest',\n      }\n    );\n  },\n};\n\nexport const ed25519SignVerifyOneshot = {\n  test(_, env) {\n    const key = createPrivateKey(env['ed25519_private.pem']);\n    const sig = sign(null, Buffer.from('hello world'), key);\n    strictEqual(\n      verify(null, Buffer.from('hello world'), env['ed25519_public.pem'], sig),\n      true\n    );\n  },\n};\n\nexport const dsaSignVerifyObjects = {\n  test(_, env) {\n    const pvt = createPrivateKey(env['dsa_private.pem']);\n    const pub = createPublicKey(env['dsa_public.pem']);\n    const signer = createSign('sha256');\n    const verifier = createVerify('sha256');\n    signer.update('');\n    verifier.update('');\n    throws(() => signer.sign(pvt), {\n      message: 'Signing with DSA keys is not currently supported',\n    });\n    throws(() => verifier.verify(pub, Buffer.alloc(0)), {\n      message: 'Verifying with DSA keys is not currently supported',\n    });\n    throws(() => sign('sha256', Buffer.alloc(0), pvt), {\n      message: 'Signing with DSA keys is not currently supported',\n    });\n    throws(() => verify('sha256', Buffer.alloc(0), pub, Buffer.alloc(0)), {\n      message: 'Verifying with DSA keys is not currently supported',\n    });\n  },\n};\n\nexport const testSignLength = {\n  test() {\n    // Tests that generated signatures are not overly long.\n    const message = `Test message 123: ${Math.random().toString(36).substring(2, 15)}`;\n\n    const keyPair = generateKeyPairSync('ec', {\n      namedCurve: 'prime256v1',\n      publicKeyEncoding: {\n        type: 'spki',\n        format: 'pem',\n      },\n      privateKeyEncoding: {\n        type: 'pkcs8',\n        format: 'pem',\n      },\n    });\n\n    for (let n = 0; n < 1000; n++) {\n      const sign = createSign('SHA256');\n      sign.write(Buffer.from(message));\n      sign.end();\n\n      const sig = sign.sign(keyPair.privateKey);\n\n      const verify = createVerify('SHA256');\n      verify.write(Buffer.from(message));\n      verify.end();\n\n      // It will only verify correctly if the signature is the correct length.\n      ok(verify.verify(keyPair.publicKey, sig));\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_sign-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"crypto_sign-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"crypto_sign-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\"],\n        bindings = [\n          ( name = \"rsa_private.pem\", text = embed \"fixtures/rsa_private.pem\" ),\n          ( name = \"rsa_public.pem\", text = embed \"fixtures/rsa_public.pem\" ),\n          ( name = \"ed25519_private.pem\", text = embed \"fixtures/ed25519_private.pem\" ),\n          ( name = \"ed25519_public.pem\", text = embed \"fixtures/ed25519_public.pem\" ),\n          ( name = \"dsa_private.pem\", text = embed \"fixtures/dsa_private.pem\" ),\n          ( name = \"dsa_public.pem\", text = embed \"fixtures/dsa_public.pem\" ),\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_spkac-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { Buffer } from 'node:buffer';\n\nimport * as assert from 'node:assert';\nimport { Certificate } from 'node:crypto';\n\nconst valid = Buffer.from(\n  'MIICUzCCATswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC33FiIiiexwLe/P8DZx5HsqFlmUO7/lvJ7necJVNwqdZ3ax5jpQB0p6uxfqeOvzcN3k5V7UFb/Am+nkSNZMAZhsWzCU2Z4Pjh50QYz3f0Hour7/yIGStOLyYY3hgLK2K8TbhgjQPhdkw9+QtKlpvbL8fLgONAoGrVOFnRQGcr70iFffsm79mgZhKVMgYiHPJqJgGHvCtkGg9zMgS7p63+Q3ZWedtFS2RhMX3uCBy/mH6EOlRCNBbRmA4xxNzyf5GQaki3T+Iz9tOMjdPP+CwV2LqEdylmBuik8vrfTb3qIHLKKBAI8lXN26wWtA3kN4L7NP+cbKlCRlqctvhmylLH1AgMBAAEWE3RoaXMtaXMtYS1jaGFsbGVuZ2UwDQYJKoZIhvcNAQEEBQADggEBAIozmeW1kfDfAVwRQKileZGLRGCD7AjdHLYEe16xTBPve8Af1bDOyuWsAm4qQLYA4FAFROiKeGqxCtIErEvm87/09tCfF1My/1Uj+INjAk39DK9J9alLlTsrwSgd1lb3YlXY7TyitCmh7iXLo4pVhA2chNA3njiMq3CUpSvGbpzrESL2dv97lv590gUD988wkTDVyYsf0T8+X0Kww3AgPWGji+2f2i5/jTfD/s1lK1nqi7ZxFm0pGZoy1MJ51SCEy7Y82ajroI+5786nC02mo9ak7samca4YDZOoxN4d3tax4B/HDF5dqJSm1/31xYLDTfujCM5FkSjRc4m6hnriEkc='\n);\n\nconst invalid = Buffer.from(\n  'UzCCATswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC33FiIiiexwLe/P8DZx5HsqFlmUO7/lvJ7necJVNwqdZ3ax5jpQB0p6uxfqeOvzcN3k5V7UFb/Am+nkSNZMAZhsWzCU2Z4Pjh50QYz3f0Hour7/yIGStOLyYY3hgLK2K8TbhgjQPhdkw9+QtKlpvbL8fLgONAoGrVOFnRQGcr70iFffsm79mgZhKVMgYiHPJqJgGHvCtkGg9zMgS7p63+Q3ZWedtFS2RhMX3uCBy/mH6EOlRCNBbRmA4xxNzyf5GQaki3T+Iz9tOMjdPP+CwV2LqEdylmBuik8vrfTb3qIHLKKBAI8lXN26wWtA3kN4L7NP+cbKlCRlqctvhmylLH1AgMBAAEWE3RoaXMtaXMtYS1jaGFsbGVuZ2UwDQYJKoZIhvcNAQEEBQADggEBAIozmeW1kfDfAVwRQKileZGLRGCD7AjdHLYEe16xTBPve8Af1bDOyuWsAm4qQLYA4FAFROiKeGqxCtIErEvm87/09tCfF1My/1Uj+INjAk39DK9J9alLlTsrwSgd1lb3YlXY7TyitCmh7iXLo4pVhA2chNA3njiMq3CUpSvGbpzrESL2dv97lv590gUD988wkTDVyYsf0T8+X0Kww3AgPWGji+2f2i5/jTfD/s1lK1nqi7ZxFm0pGZoy1MJ51SCEy7Y82ajroI+5786nC02mo9ak7samca4YDZOoxN4d3tax4B/HDF5dqJSm1/31xYLDTfujCM5FkSjRc4m6hnriEkc='\n);\n\nconst key = Buffer.from(`-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt9xYiIonscC3vz/A2ceR\n7KhZZlDu/5bye53nCVTcKnWd2seY6UAdKersX6njr83Dd5OVe1BW/wJvp5EjWTAG\nYbFswlNmeD44edEGM939B6Lq+/8iBkrTi8mGN4YCytivE24YI0D4XZMPfkLSpab2\ny/Hy4DjQKBq1ThZ0UBnK+9IhX37Ju/ZoGYSlTIGIhzyaiYBh7wrZBoPczIEu6et/\nkN2VnnbRUtkYTF97ggcv5h+hDpUQjQW0ZgOMcTc8n+RkGpIt0/iM/bTjI3Tz/gsF\ndi6hHcpZgbopPL630296iByyigQCPJVzdusFrQN5DeC+zT/nGypQkZanLb4ZspSx\n9QIDAQAB\n-----END PUBLIC KEY-----`);\n\nconst challenge = 'this-is-a-challenge';\n\nexport const spkac = {\n  test() {\n    // In workerd, this following check works fine but in the internal\n    // system we prevent anyone from creating a TypedArray with this\n    // size... which means this check passes in workerd but fails internally,\n    // but in exactly the way we want. Just going to comment this out for now.\n    //\n    // const buf = new Uint8Array(2 ** 31);\n    // assert.throws(\n    //   () => Certificate.verifySpkac(buf), {\n    //     name: 'RangeError',\n    //   });\n    // assert.throws(\n    //   () => Certificate.exportChallenge(buf), {\n    //     name: 'RangeError',\n    //   });\n    // assert.throws(\n    //   () => Certificate.exportPublicKey(buf), {\n    //     name: 'RangeError',\n    //   });\n\n    // We should decide what we want to do here. The fact that we run\n    // with fips enabled and SPKAC requires using md5 has the digest\n    // for the signature means that verifySpkac will always fail since\n    // fips mode disables the ability to use md5 here. This check also\n    // fails in Node.js but there we have the options of disabling fips.\n    // assert.ok(Certificate.verifySpkac(valid));\n    assert.ok(!Certificate.verifySpkac(invalid));\n\n    assert.strictEqual(\n      stripLineEndings(Certificate.exportPublicKey(valid).toString('utf8')),\n      stripLineEndings(key.toString('utf8'))\n    );\n    assert.strictEqual(\n      Certificate.exportPublicKey(invalid).toString('utf8'),\n      ''\n    );\n\n    assert.strictEqual(\n      Certificate.exportChallenge(valid).toString('utf8'),\n      challenge\n    );\n    assert.strictEqual(\n      Certificate.exportChallenge(invalid).toString('utf8'),\n      ''\n    );\n\n    const ab = copyArrayBuffer(key);\n    assert.ok(!Certificate.verifySpkac(ab));\n    assert.ok(!Certificate.verifySpkac(new Uint8Array(ab)));\n    assert.ok(!Certificate.verifySpkac(new DataView(ab)));\n\n    assert.ok(Certificate() instanceof Certificate);\n\n    const errObj = { code: 'ERR_INVALID_ARG_TYPE' };\n\n    [1, {}, [], Infinity, true, undefined, null].forEach((val) => {\n      assert.throws(() => Certificate.verifySpkac(val), errObj);\n      assert.throws(() => Certificate.exportPublicKey(val), errObj);\n      assert.throws(() => Certificate.exportChallenge(val), errObj);\n    });\n  },\n};\n\nfunction stripLineEndings(obj) {\n  return obj.replace(/\\n/g, '');\n}\n\nfunction copyArrayBuffer(buf) {\n  return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);\n}\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_spkac-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"crypto_spkac-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"crypto_spkac-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat_v2\", \"experimental\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_x509-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT ORs\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { deepStrictEqual, ok, strictEqual, throws } from 'node:assert';\n\nimport { Buffer } from 'node:buffer';\nimport assert from 'node:assert';\n\nimport { X509Certificate, PublicKeyObject } from 'node:crypto';\n\nconst cert = Buffer.from(`-----BEGIN CERTIFICATE-----\nMIID6DCCAtCgAwIBAgIUFH02wcL3Qgben6tfIibXitsApCYwDQYJKoZIhvcNAQEL\nBQAwejELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEPMA0G\nA1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQDDANjYTExIDAe\nBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMCAXDTIyMDkwMzIxNDAzN1oY\nDzIyOTYwNjE3MjE0MDM3WjB9MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAcMAlNGMQ8wDQYDVQQKDAZKb3llbnQxEDAOBgNVBAsMB05vZGUuanMxDzAN\nBgNVBAMMBmFnZW50MTEgMB4GCSqGSIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcw\nggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUVjIK+yDTgnCT3CxChO0E\n37q9VuHdrlKeKLeQzUJW2yczSfNzX/0zfHpjY+zKWie39z3HCJqWxtiG2wxiOI8c\n3WqWOvzVmdWADlh6EfkIlg+E7VC6JaKDA+zabmhPvnuu3JzogBMnsWl68lCXzuPx\ndeQAmEwNtqjrh74DtM+Ud0ulb//Ixjxo1q3rYKu+aaexSramuee6qJta2rjrB4l8\nB/bU+j1mDf9XQQfSjo9jRnp4hiTFdBl2k+lZzqE2L/rhu6EMjA2IhAq/7xA2MbLo\n9cObVUin6lfoo5+JKRgT9Fp2xEgDOit+2EA/S6oUfPNeLSVUqmXOSWlXlwlb9Nxr\nAgMBAAGjYTBfMF0GCCsGAQUFBwEBBFEwTzAjBggrBgEFBQcwAYYXaHR0cDovL29j\nc3Aubm9kZWpzLm9yZy8wKAYIKwYBBQUHMAKGHGh0dHA6Ly9jYS5ub2RlanMub3Jn\nL2NhLmNlcnQwDQYJKoZIhvcNAQELBQADggEBAMM0mBBjLMt9pYXePtUeNO0VTw9y\nFWCM8nAcAO2kRNwkJwcsispNpkcsHZ5o8Xf5mpCotdvziEWG1hyxwU6nAWyNOLcN\nG0a0KUfbMO3B6ZYe1GwPDjXaQnv75SkAdxgX5zOzca3xnhITcjUUGjQ0fbDfwFV5\nix8mnzvfXjDONdEznVa7PFcN6QliFUMwR/h8pCRHtE5+a10OSPeJSrGG+FtrGnRW\nG1IJUv6oiGF/MvWCr84REVgc1j78xomGANJIu2hN7bnD1nEMON6em8IfnDOUtynV\n9wfWTqiQYD5Zifj6WcGa0aAHMuetyFG4lIfMAHmd3gaKpks7j9l26LwRPvI=\n-----END CERTIFICATE-----\n`);\n\nconst ca = Buffer.from(`-----BEGIN CERTIFICATE-----\nMIIDlDCCAnygAwIBAgIUSrFsjf1qfQ0t/KvfnEsOksatAikwDQYJKoZIhvcNAQEL\nBQAwejELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEPMA0G\nA1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQDDANjYTExIDAe\nBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMCAXDTIyMDkwMzIxNDAzN1oY\nDzIyOTYwNjE3MjE0MDM3WjB6MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAcMAlNGMQ8wDQYDVQQKDAZKb3llbnQxEDAOBgNVBAsMB05vZGUuanMxDDAK\nBgNVBAMMA2NhMTEgMB4GCSqGSIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcwggEi\nMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNvf4OGGep+ak+4DNjbuNgy0S/\nAZPxahEFp4gpbcvsi9YLOPZ31qpilQeQf7d27scIZ02Qx1YBAzljxELB8H/ZxuYS\ncQK0s+DNP22xhmgwMWznO7TezkHP5ujN2UkbfbUpfUxGFgncXeZf9wR7yFWppeHi\nRWNBOgsvY7sTrS12kXjWGjqntF7xcEDHc7h+KyF6ZjVJZJCnP6pJEQ+rUjd51eCZ\nXt4WjowLnQiCS1VKzXiP83a++Ma1BKKkUitTR112/Uwd5eGoiByhmLzb/BhxnHJN\n07GXjhlMItZRm/jfbZsx1mwnNOO3tx4r08l+DaqkinIadvazs+1ugCaKQn8xAgMB\nAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFqG0RXURDam\n56x5accdg9sY5zEGP5VQhkK3ZDc2NyNNa25rwvrjCpO+e0OSwKAmm4aX6iIf2woY\nwF2f9swWYzxn9CG4fDlUA8itwlnHxupeL4fGMTYb72vf31plUXyBySRsTwHwBloc\nF7KvAZpYYKN9EMH1S/267By6H2I33BT/Ethv//n8dSfmuCurR1kYRaiOC4PVeyFk\nB3sj8TtolrN0y/nToWUhmKiaVFnDx3odQ00yhmxR3t21iB7yDkko6D8Vf2dVC4j/\nYYBVprXGlTP/hiYRLDoP20xKOYznx5cvHPJ9p+lVcOZUJsJj/Iy750+2n5UiBmXt\nlz88C25ucKA=\n-----END CERTIFICATE-----\n`);\n\nconst der = Buffer.from(\n  '308203e8308202d0a0030201020214147d36c1c2f74206de9fab5f2226d78adb00a42630' +\n    '0d06092a864886f70d01010b0500307a310b3009060355040613025553310b3009060355' +\n    '04080c024341310b300906035504070c025346310f300d060355040a0c064a6f79656e74' +\n    '3110300e060355040b0c074e6f64652e6a73310c300a06035504030c036361313120301e' +\n    '06092a864886f70d010901161172794074696e79636c6f7564732e6f72673020170d3232' +\n    '303930333231343033375a180f32323936303631373231343033375a307d310b30090603' +\n    '55040613025553310b300906035504080c024341310b300906035504070c025346310f30' +\n    '0d060355040a0c064a6f79656e743110300e060355040b0c074e6f64652e6a73310f300d' +\n    '06035504030c066167656e74313120301e06092a864886f70d010901161172794074696e' +\n    '79636c6f7564732e6f726730820122300d06092a864886f70d01010105000382010f0030' +\n    '82010a0282010100d456320afb20d3827093dc2c4284ed04dfbabd56e1ddae529e28b790' +\n    'cd4256db273349f3735ffd337c7a6363ecca5a27b7f73dc7089a96c6d886db0c62388f1c' +\n    'dd6a963afcd599d5800e587a11f908960f84ed50ba25a28303ecda6e684fbe7baedc9ce8' +\n    '801327b1697af25097cee3f175e400984c0db6a8eb87be03b4cf94774ba56fffc8c63c68' +\n    'd6adeb60abbe69a7b14ab6a6b9e7baa89b5adab8eb07897c07f6d4fa3d660dff574107d2' +\n    '8e8f63467a788624c574197693e959cea1362ffae1bba10c8c0d88840abfef103631b2e8' +\n    'f5c39b5548a7ea57e8a39f89291813f45a76c448033a2b7ed8403f4baa147cf35e2d2554' +\n    'aa65ce49695797095bf4dc6b0203010001a361305f305d06082b06010505070101045130' +\n    '4f302306082b060105050730018617687474703a2f2f6f6373702e6e6f64656a732e6f72' +\n    '672f302806082b06010505073002861c687474703a2f2f63612e6e6f64656a732e6f7267' +\n    '2f63612e63657274300d06092a864886f70d01010b05000382010100c3349810632ccb7d' +\n    'a585de3ed51e34ed154f0f7215608cf2701c00eda444dc2427072c8aca4da6472c1d9e68' +\n    'f177f99a90a8b5dbf3884586d61cb1c14ea7016c8d38b70d1b46b42947db30edc1e9961e' +\n    'd46c0f0e35da427bfbe52900771817e733b371adf19e12137235141a34347db0dfc05579' +\n    '8b1f269f3bdf5e30ce35d1339d56bb3c570de9096215433047f87ca42447b44e7e6b5d0e' +\n    '48f7894ab186f85b6b1a74561b520952fea888617f32f582afce1111581cd63efcc68986' +\n    '00d248bb684dedb9c3d6710c38de9e9bc21f9c3394b729d5f707d64ea890603e5989f8fa' +\n    '59c19ad1a00732e7adc851b89487cc00799dde068aa64b3b8fd976e8bc113ef2',\n  'hex'\n);\n\nconst key = Buffer.from(`-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA1FYyCvsg04Jwk9wsQoTtBN+6vVbh3a5Snii3kM1CVtsnM0nz\nc1/9M3x6Y2Psylont/c9xwialsbYhtsMYjiPHN1qljr81ZnVgA5YehH5CJYPhO1Q\nuiWigwPs2m5oT757rtyc6IATJ7FpevJQl87j8XXkAJhMDbao64e+A7TPlHdLpW//\nyMY8aNat62CrvmmnsUq2prnnuqibWtq46weJfAf21Po9Zg3/V0EH0o6PY0Z6eIYk\nxXQZdpPpWc6hNi/64buhDIwNiIQKv+8QNjGy6PXDm1VIp+pX6KOfiSkYE/RadsRI\nAzorfthAP0uqFHzzXi0lVKplzklpV5cJW/TcawIDAQABAoIBAAvbtHfAhpjJVBgt\n15rvaX04MWmZjIugzKRgib/gdq/7FTlcC+iJl85kSUF7tyGl30n62MxgwqFhAX6m\nhQ6HMhbelrFFIhGbwbyhEHfgwROlrcAysKt0pprCgVvBhrnNXYLqdyjU3jz9P3LK\nTY3s0/YMK2uNFdI+PTjKH+Z9Foqn9NZUnUonEDepGyuRO7fLeccWJPv2L4CR4a/5\nku4VbDgVpvVSVRG3PSVzbmxobnpdpl52og+T7tPx1cLnIknPtVljXPWtZdfekh2E\neAp2KxCCHOKzzG3ItBKsVu0woeqEpy8JcoO6LbgmEoVnZpgmtQClbBgef8+i+oGE\nBgW9nmECgYEA8gA63QQuZOUC56N1QXURexN2PogF4wChPaCTFbQSJXvSBkQmbqfL\nqRSD8P0t7GOioPrQK6pDwFf4BJB01AvkDf8Z6DxxOJ7cqIC7LOwDupXocWX7Q0Qk\nO6cwclBVsrDZK00v60uRRpl/a39GW2dx7IiQDkKQndLh3/0TbMIWHNcCgYEA4J6r\nyinZbLpKw2+ezhi4B4GT1bMLoKboJwpZVyNZZCzYR6ZHv+lS7HR/02rcYMZGoYbf\nn7OHwF4SrnUS7vPhG4g2ZsOhKQnMvFSQqpGmK1ZTuoKGAevyvtouhK/DgtLWzGvX\n9fSahiq/UvfXs/z4M11q9Rv9ztPCmG1cwSEHlo0CgYEAogQNZJK8DMhVnYcNpXke\n7uskqtCeQE/Xo06xqkIYNAgloBRYNpUYAGa/vsOBz1UVN/kzDUi8ezVp0oRz8tLT\nJ5u2WIi+tE2HJTiqF3UbOfvK1sCT64DfUSCpip7GAQ/tFNRkVH8PD9kMOYfILsGe\nv+DdsO5Xq5HXrwHb02BNNZkCgYBsl8lt33WiPx5OBfS8pu6xkk+qjPkeHhM2bKZs\nnkZlS9j0KsudWGwirN/vkkYg8zrKdK5AQ0dqFRDrDuasZ3N5IA1M+V88u+QjWK7o\nB6pSYVXxYZDv9OZSpqC+vUrEQLJf+fNakXrzSk9dCT1bYv2Lt6ox/epix7XYg2bI\nZ/OHMQKBgQC2FUGhlndGeugTJaoJ8nhT/0VfRUX/h6sCgSerk5qFr/hNCBV4T022\nx0NDR2yLG6MXyqApJpG6rh3QIDElQoQCNlI3/KJ6JfEfmqrLLN2OigTvA5sE4fGU\nDp/ha8OQAx95EwXuaG7LgARduvOIK3x8qi8KsZoUGJcg2ywurUbkWA==\n-----END RSA PRIVATE KEY-----\n`);\n\nconst subjectCheck = `C=US\nST=CA\nL=SF\nO=Joyent\nOU=Node.js\nCN=agent1\nemailAddress=ry@tinyclouds.org`;\n\nconst issuerCheck = `C=US\nST=CA\nL=SF\nO=Joyent\nOU=Node.js\nCN=ca1\nemailAddress=ry@tinyclouds.org`;\n\nlet infoAccessCheck = `OCSP - URI:http://ocsp.nodejs.org/\nCA Issuers - URI:http://ca.nodejs.org/ca.cert\\n`;\n\nconst legacyObjectCheck = {\n  subject: {\n    C: 'US',\n    ST: 'CA',\n    L: 'SF',\n    O: 'Joyent',\n    OU: 'Node.js',\n    CN: 'agent1',\n    emailAddress: 'ry@tinyclouds.org',\n  },\n  issuer: {\n    C: 'US',\n    ST: 'CA',\n    L: 'SF',\n    O: 'Joyent',\n    OU: 'Node.js',\n    CN: 'ca1',\n    emailAddress: 'ry@tinyclouds.org',\n  },\n  infoAccess: {\n    'OCSP - URI': ['http://ocsp.nodejs.org/'],\n    'CA Issuers - URI': ['http://ca.nodejs.org/ca.cert'],\n  },\n  modulus:\n    'D456320AFB20D3827093DC2C4284ED04DFBABD56E1DDAE529E28B790CD42' +\n    '56DB273349F3735FFD337C7A6363ECCA5A27B7F73DC7089A96C6D886DB0C' +\n    '62388F1CDD6A963AFCD599D5800E587A11F908960F84ED50BA25A28303EC' +\n    'DA6E684FBE7BAEDC9CE8801327B1697AF25097CEE3F175E400984C0DB6A8' +\n    'EB87BE03B4CF94774BA56FFFC8C63C68D6ADEB60ABBE69A7B14AB6A6B9E7' +\n    'BAA89B5ADAB8EB07897C07F6D4FA3D660DFF574107D28E8F63467A788624' +\n    'C574197693E959CEA1362FFAE1BBA10C8C0D88840ABFEF103631B2E8F5C3' +\n    '9B5548A7EA57E8A39F89291813F45A76C448033A2B7ED8403F4BAA147CF3' +\n    '5E2D2554AA65CE49695797095BF4DC6B',\n  bits: 2048,\n  exponent: '0x10001',\n  valid_from: 'Sep  3 21:40:37 2022 GMT',\n  valid_to: 'Jun 17 21:40:37 2296 GMT',\n  fingerprint: '8B:89:16:C4:99:87:D2:13:1A:64:94:36:38:A5:32:01:F0:95:3B:53',\n  fingerprint256:\n    '2C:62:59:16:91:89:AB:90:6A:3E:98:88:A6:D3:C5:58:58:6C:AE:FF:9C:33:' +\n    '22:7C:B6:77:D3:34:E7:53:4B:05',\n  fingerprint512:\n    '51:62:18:39:E2:E2:77:F5:86:11:E8:C0:CA:54:43:7C:76:83:19:05:D0:03:' +\n    '24:21:B8:EB:14:61:FB:24:16:EB:BD:51:1A:17:91:04:30:03:EB:68:5F:DC:' +\n    '86:E1:D1:7C:FB:AF:78:ED:63:5F:29:9C:32:AF:A1:8E:22:96:D1:02',\n  serialNumber: '147D36C1C2F74206DE9FAB5F2226D78ADB00A426',\n};\n\nexport const test_ok = {\n  async test() {\n    const x509 = new X509Certificate(cert);\n    ok(!x509.ca);\n    strictEqual(x509.subject, subjectCheck);\n    strictEqual(x509.subjectAltName, undefined);\n    strictEqual(x509.issuer, issuerCheck);\n    strictEqual(x509.infoAccess, infoAccessCheck);\n    strictEqual(x509.validFrom, 'Sep  3 21:40:37 2022 GMT');\n    strictEqual(x509.validTo, 'Jun 17 21:40:37 2296 GMT');\n    strictEqual(\n      x509.fingerprint.length,\n      '8B:89:16:C4:99:87:D2:13:1A:64:94:36:38:A5:32:01:F0:95:3B:53'.length\n    );\n    strictEqual(\n      x509.fingerprint256,\n      '2C:62:59:16:91:89:AB:90:6A:3E:98:88:A6:D3:C5:58:58:6C:AE:FF:9C:33:' +\n        '22:7C:B6:77:D3:34:E7:53:4B:05'\n    );\n    strictEqual(\n      x509.fingerprint512,\n      '0B:6F:D0:4D:6B:22:53:99:66:62:51:2D:2C:96:F2:58:3F:95:1C:CC:4C:44:' +\n        '9D:B5:59:AA:AD:A8:F6:2A:24:8A:BB:06:A5:26:42:52:30:A3:37:61:30:A9:' +\n        '5A:42:63:E0:21:2F:D6:70:63:07:96:6F:27:A7:78:12:08:02:7A:8B'\n    );\n    strictEqual(x509.keyUsage, undefined);\n    strictEqual(x509.serialNumber, '147D36C1C2F74206DE9FAB5F2226D78ADB00A426');\n\n    deepStrictEqual(x509.raw, der);\n\n    ok(x509.publicKey instanceof PublicKeyObject);\n    ok(x509.publicKey.type === 'public');\n\n    strictEqual(\n      x509.toString().replaceAll('\\r\\n', '\\n'),\n      cert.toString().replaceAll('\\r\\n', '\\n')\n    );\n    strictEqual(x509.toJSON(), x509.toString());\n\n    // TODO(soon): Need to implement createPrivateKey this check\n    // const privateKey = createPrivateKey(key);\n    // ok(x509.checkPrivateKey(privateKey));\n    throws(() => x509.checkPrivateKey(x509.publicKey), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    strictEqual(x509.checkIP('127.0.0.1'), undefined);\n    strictEqual(x509.checkIP('::'), undefined);\n    strictEqual(x509.checkHost('agent1'), 'agent1');\n    strictEqual(x509.checkHost('agent2'), undefined);\n    strictEqual(x509.checkEmail('ry@tinyclouds.org'), 'ry@tinyclouds.org');\n    strictEqual(x509.checkEmail('sally@example.com'), undefined);\n    ok(!x509.checkIssued(x509));\n\n    throws(() => x509.checkHost('agent\\x001'));\n    throws(() => x509.checkIP('[::]'));\n    throws(() => x509.checkEmail('not\\x00hing'));\n\n    [1, false].forEach((i) => {\n      throws(() => x509.checkHost('agent1', i));\n      throws(() => x509.checkHost('agent1', { subject: i }));\n    });\n\n    [\n      'wildcards',\n      'partialWildcards',\n      'multiLabelWildcards',\n      'singleLabelSubdomains',\n    ].forEach((key) => {\n      [1, '', {}].forEach((i) => {\n        throws(() => x509.checkHost('agent1', { [key]: i }), {\n          code: 'ERR_INVALID_ARG_TYPE',\n        });\n      });\n    });\n\n    const ca_cert = new X509Certificate(ca);\n    ok(ca_cert.ca);\n    ok(x509.checkIssued(ca_cert));\n\n    ok(x509.verify(ca_cert.publicKey));\n\n    throws(() => x509.checkIssued({}));\n    throws(() => x509.checkIssued(''));\n    throws(() => x509.verify({}));\n    throws(() => x509.verify(''));\n    throws(() => x509.verify(privateKey));\n\n    const legacyObject = x509.toLegacyObject();\n    deepStrictEqual(legacyObject.raw, x509.raw);\n\n    deepStrictEqual(legacyObject.subject, legacyObjectCheck.subject);\n    deepStrictEqual(legacyObject.issuer, legacyObjectCheck.issuer);\n    deepStrictEqual(legacyObject.infoAccess, legacyObjectCheck.infoAccess);\n    strictEqual(legacyObject.modulus, legacyObjectCheck.modulus);\n    strictEqual(legacyObject.bits, legacyObjectCheck.bits);\n    strictEqual(legacyObject.exponent, legacyObjectCheck.exponent);\n    strictEqual(legacyObject.valid_from, legacyObjectCheck.valid_from);\n    strictEqual(legacyObject.valid_to, legacyObjectCheck.valid_to);\n    strictEqual(legacyObject.fingerprint, legacyObjectCheck.fingerprint);\n    strictEqual(legacyObject.fingerprint256, legacyObjectCheck.fingerprint256);\n    strictEqual(legacyObject.serialNumber, legacyObjectCheck.serialNumber);\n  },\n};\n\n// This X.509 Certificate can be parsed by OpenSSL because it contains a\n// structurally sound TBSCertificate structure. However, the SPKI field of the\n// TBSCertificate contains the subjectPublicKey as a BIT STRING, and this bit\n// sequence is not a valid public key. Ensure that X509Certificate.publicKey\n// does not abort in this case.\n// TODO(soon): F\n// const badCert = `-----BEGIN CERTIFICATE-----\n// MIIDpDCCAw0CFEc1OZ8g17q+PZnna3iQ/gfoZ7f3MA0GCSqGSIb3DQEBBQUAMIHX\n// MRMwEQYLKwYBBAGCNzwCAQMTAkdJMR0wGwYDVQQPExRQcml2YXRlIE9yZ2FuaXph\n// dGlvbjEOMAwGA1UEBRMFOTkxOTExCzAJBgNVBAYTAkdJMRIwEAYDVQQIFAlHaWJy\n// YWx0YXIxEjAQBgNVBAcUCUdpYnJhbHRhcjEgMB4GA1UEChQXV0hHIChJbnRlcm5h\n// dGlvbmFsKSBMdGQxHDAaBgNVBAsUE0ludGVyYWN0aXZlIEJldHRpbmcxHDAaBgNV\n// BAMUE3d3dy53aWxsaWFtaGlsbC5jb20wIhgPMjAxNDAyMDcwMDAwMDBaGA8yMDE1\n// MDIyMTIzNTk1OVowgbAxCzAJBgNVBAYTAklUMQ0wCwYDVQQIEwRSb21lMRAwDgYD\n// VQQHEwdQb21lemlhMRYwFAYDVQQKEw1UZWxlY29taXRhbGlhMRIwEAYDVQQrEwlB\n// RE0uQVAuUE0xHTAbBgNVBAMTFHd3dy50ZWxlY29taXRhbGlhLml0MTUwMwYJKoZI\n// hvcNAQkBFiZ2YXNlc2VyY2l6aW9wb3J0YWxpY29AdGVsZWNvbWl0YWxpYS5pdDCB\n// nzANBgkqhkiG9w0BAQEFAAOBjQA4gYkCgYEA5m/Vf7PevH+inMfUJOc8GeR7WVhM\n// CQwcMM5k46MSZo7kCk7VZuaq5G2JHGAGnLPaPUkeXlrf5qLpTxXXxHNtz+WrDlFt\n// boAdnTcqpX3+72uBGOaT6Wi/9YRKuCs5D5/cAxAc3XjHfpRXMoXObj9Vy7mLndfV\n// /wsnTfU9QVeBkgsCAwEAAaOBkjCBjzAdBgNVHQ4EFgQUfLjAjEiC83A+NupGrx5+\n// Qe6nhRMwbgYIKwYBBQUHAQwEYjBgoV6gXDBaMFgwVhYJaW1hZ2UvZ2lmMCEwHzAH\n// BgUrDgMCGgQUS2u5KJYGDLvQUjibKaxLB4shBRgwJhYkaHR0cDovL2xvZ28udmVy\n// aXNpZ24uY29tL3ZzbG9nbzEuZ2lmMA0GCSqGSIb3DQEBBQUAA4GBALLiAMX0cIMp\n// +V/JgMRhMEUKbrt5lYKfv9dil/f22ezZaFafb070jGMMPVy9O3/PavDOkHtTv3vd\n// tAt3hIKFD1bJt6c6WtMH2Su3syosWxmdmGk5ihslB00lvLpfj/wed8i3bkcB1doq\n// UcXd/5qu2GhokrKU2cPttU+XAN2Om6a0\n// -----END CERTIFICATE-----`;\n\n// export const test_error = {\n//   test() {\n//     throws(() => new X509Certificate(badCert));\n//   }\n// };\n\n// Verify that invalid certificate data throws a user-facing error (not an internal error).\nexport const test_invalid_cert = {\n  test() {\n    // Completely invalid data\n    throws(() => new X509Certificate('not a certificate'), {\n      code: 'ERR_INVALID_ARG_VALUE',\n    });\n\n    // Invalid base64 in PEM wrapper\n    throws(\n      () =>\n        new X509Certificate(\n          '-----BEGIN CERTIFICATE-----\\n!!!invalid!!!\\n-----END CERTIFICATE-----'\n        ),\n      {\n        code: 'ERR_INVALID_ARG_VALUE',\n      }\n    );\n\n    // Empty buffer\n    throws(() => new X509Certificate(Buffer.alloc(0)), {\n      code: 'ERR_INVALID_ARG_VALUE',\n    });\n\n    // Random bytes (not a valid DER or PEM certificate)\n    throws(() => new X509Certificate(Buffer.from([0x01, 0x02, 0x03])), {\n      code: 'ERR_INVALID_ARG_VALUE',\n    });\n  },\n};\n\n// Ref: https://github.com/unjs/unenv/pull/310\nexport const shouldImportCertificate = {\n  test() {\n    // This is allowed by Node.js as well.\n    assert(process.getBuiltinModule('crypto').Certificate);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/crypto_x509-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"crypto_x509-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"crypto_x509-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat_v2\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/dgram-nodejs-test.js",
    "content": "import * as dgram from 'node:dgram';\nimport { strictEqual, deepStrictEqual, throws, ok } from 'node:assert';\n\nexport const dgramTest = {\n  test() {\n    // Note: test was generated by claude with manual tweaks\n\n    // Test that required exports exist\n    strictEqual(typeof dgram.createSocket, 'function');\n    strictEqual(typeof dgram.Socket, 'function');\n\n    // Test createSocket with string type - should return Socket instance\n    const socket1 = dgram.createSocket('udp4');\n    ok(socket1 instanceof dgram.Socket);\n\n    const socket2 = dgram.createSocket('udp6');\n    ok(socket2 instanceof dgram.Socket);\n\n    // Test createSocket with object type - should return Socket instance\n    const socket3 = dgram.createSocket({ type: 'udp4' });\n    ok(socket3 instanceof dgram.Socket);\n\n    const socket4 = dgram.createSocket({ type: 'udp6' });\n    ok(socket4 instanceof dgram.Socket);\n\n    // Test createSocket with callback - should return Socket instance and register callback\n    const callback1 = () => {};\n    const socket5 = dgram.createSocket('udp4', callback1);\n    ok(socket5 instanceof dgram.Socket);\n\n    const callback2 = () => {};\n    const socket6 = dgram.createSocket({ type: 'udp4' }, callback2);\n    ok(socket6 instanceof dgram.Socket);\n\n    // Test createSocket argument validation - type must be valid object\n    throws(\n      () => {\n        dgram.createSocket(null);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        dgram.createSocket(123);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        dgram.createSocket([]);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Test createSocket callback validation - must be function if provided\n    throws(\n      () => {\n        dgram.createSocket('udp4', 'invalid');\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        dgram.createSocket('udp4', 123);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        dgram.createSocket('udp4', {});\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Test Socket constructor with string type - should return Socket instance\n    const socket7 = new dgram.Socket('udp4');\n    ok(socket7 instanceof dgram.Socket);\n\n    const socket8 = new dgram.Socket('udp6');\n    ok(socket8 instanceof dgram.Socket);\n\n    // Test Socket constructor with object type - should return Socket instance\n    const socket9 = new dgram.Socket({ type: 'udp4' });\n    ok(socket9 instanceof dgram.Socket);\n\n    // Test Socket constructor with callback - should return Socket instance and register callback\n    const callback3 = () => {};\n    const socket10 = new dgram.Socket('udp4', callback3);\n    ok(socket10 instanceof dgram.Socket);\n\n    // Test Socket constructor argument validation\n    throws(\n      () => {\n        new dgram.Socket(null);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        new dgram.Socket(123);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        new dgram.Socket('udp4', 'invalid');\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Test Socket prototype methods exist\n    strictEqual(typeof dgram.Socket.prototype.bind, 'function');\n    strictEqual(typeof dgram.Socket.prototype.connect, 'function');\n    strictEqual(typeof dgram.Socket.prototype.disconnect, 'function');\n    strictEqual(typeof dgram.Socket.prototype.send, 'function');\n    strictEqual(typeof dgram.Socket.prototype.sendto, 'function');\n    strictEqual(typeof dgram.Socket.prototype.close, 'function');\n    strictEqual(typeof dgram.Socket.prototype.address, 'function');\n    strictEqual(typeof dgram.Socket.prototype.remoteAddress, 'function');\n    strictEqual(typeof dgram.Socket.prototype.setBroadcast, 'function');\n    strictEqual(typeof dgram.Socket.prototype.setTTL, 'function');\n    strictEqual(typeof dgram.Socket.prototype.setMulticastTTL, 'function');\n    strictEqual(typeof dgram.Socket.prototype.setMulticastLoopback, 'function');\n    strictEqual(\n      typeof dgram.Socket.prototype.setMulticastInterface,\n      'function'\n    );\n    strictEqual(typeof dgram.Socket.prototype.addMembership, 'function');\n    strictEqual(typeof dgram.Socket.prototype.dropMembership, 'function');\n    strictEqual(\n      typeof dgram.Socket.prototype.addSourceSpecificMembership,\n      'function'\n    );\n    strictEqual(\n      typeof dgram.Socket.prototype.dropSourceSpecificMembership,\n      'function'\n    );\n    strictEqual(typeof dgram.Socket.prototype.ref, 'function');\n    strictEqual(typeof dgram.Socket.prototype.unref, 'function');\n    strictEqual(typeof dgram.Socket.prototype.setRecvBufferSize, 'function');\n    strictEqual(typeof dgram.Socket.prototype.setSendBufferSize, 'function');\n    strictEqual(typeof dgram.Socket.prototype.getRecvBufferSize, 'function');\n    strictEqual(typeof dgram.Socket.prototype.getSendBufferSize, 'function');\n    strictEqual(typeof dgram.Socket.prototype.getSendQueueSize, 'function');\n    strictEqual(typeof dgram.Socket.prototype.getSendQueueCount, 'function');\n\n    // Test methods that are chainable (return this)\n    const testSocket = dgram.createSocket('udp4');\n\n    const bindResult = testSocket.bind();\n    strictEqual(bindResult, testSocket);\n\n    const bindResult2 = testSocket.bind(8080);\n    strictEqual(bindResult2, testSocket);\n\n    const bindResult3 = testSocket.bind(8080, 'localhost');\n    strictEqual(bindResult3, testSocket);\n\n    // connect is a no-op, should not throw\n    testSocket.connect(8080);\n    testSocket.connect(8080, 'localhost');\n\n    // disconnect is a no-op, should not throw\n    testSocket.disconnect();\n\n    // send is a no-op, should not throw\n    testSocket.send(Buffer.from('test'));\n    testSocket.send('test', 8080, 'localhost');\n\n    // sendto is a no-op, should not throw\n    testSocket.sendto(Buffer.from('test'));\n\n    // close is chainable, should not throw\n    const closeResult = testSocket.close();\n    strictEqual(closeResult, testSocket);\n\n    const closeResult2 = testSocket.close(() => {});\n    strictEqual(closeResult2, testSocket);\n\n    // Test methods that return empty objects\n    const testSocket2 = dgram.createSocket('udp4');\n\n    const address = testSocket2.address();\n    strictEqual(typeof address, 'object');\n    deepStrictEqual(address, {});\n\n    const remoteAddress = testSocket2.remoteAddress();\n    strictEqual(typeof remoteAddress, 'object');\n    deepStrictEqual(remoteAddress, {});\n\n    // Test methods that are no-ops (should not throw)\n    const testSocket3 = dgram.createSocket('udp4');\n\n    testSocket3.setBroadcast(true);\n    testSocket3.setBroadcast(false);\n    testSocket3.setBroadcast('invalid');\n\n    testSocket3.setTTL(64);\n    testSocket3.setTTL('invalid');\n\n    testSocket3.setMulticastTTL(1);\n    testSocket3.setMulticastTTL('invalid');\n\n    testSocket3.setMulticastLoopback(true);\n    testSocket3.setMulticastLoopback(false);\n    testSocket3.setMulticastLoopback('invalid');\n\n    testSocket3.setMulticastInterface('127.0.0.1');\n    testSocket3.setMulticastInterface('invalid');\n\n    testSocket3.addMembership('224.0.0.1');\n    testSocket3.addMembership('224.0.0.1', '127.0.0.1');\n    testSocket3.addMembership('invalid', 'invalid');\n\n    testSocket3.dropMembership('224.0.0.1');\n    testSocket3.dropMembership('224.0.0.1', '127.0.0.1');\n    testSocket3.dropMembership('invalid', 'invalid');\n\n    testSocket3.addSourceSpecificMembership('127.0.0.1', '224.0.0.1');\n    testSocket3.addSourceSpecificMembership(\n      '127.0.0.1',\n      '224.0.0.1',\n      '127.0.0.1'\n    );\n    testSocket3.addSourceSpecificMembership('invalid', 'invalid', 'invalid');\n\n    testSocket3.dropSourceSpecificMembership('127.0.0.1', '224.0.0.1');\n    testSocket3.dropSourceSpecificMembership(\n      '127.0.0.1',\n      '224.0.0.1',\n      '127.0.0.1'\n    );\n    testSocket3.dropSourceSpecificMembership('invalid', 'invalid', 'invalid');\n\n    testSocket3.setRecvBufferSize(1024);\n    testSocket3.setRecvBufferSize('invalid');\n\n    testSocket3.setSendBufferSize(1024);\n    testSocket3.setSendBufferSize('invalid');\n\n    // Test methods that return this\n    const testSocket4 = dgram.createSocket('udp4');\n\n    const refResult = testSocket4.ref();\n    strictEqual(refResult, testSocket4);\n\n    const unrefResult = testSocket4.unref();\n    strictEqual(unrefResult, testSocket4);\n\n    // Test methods that return numbers\n    const testSocket5 = dgram.createSocket('udp4');\n\n    const recvBufferSize = testSocket5.getRecvBufferSize();\n    strictEqual(recvBufferSize, 0);\n\n    const sendBufferSize = testSocket5.getSendBufferSize();\n    strictEqual(sendBufferSize, 0);\n\n    const sendQueueSize = testSocket5.getSendQueueSize();\n    strictEqual(sendQueueSize, 0);\n\n    const sendQueueCount = testSocket5.getSendQueueCount();\n    strictEqual(sendQueueCount, 0);\n\n    // Test Symbol.asyncDispose method exists and is a no-op\n    strictEqual(typeof dgram.Socket.prototype[Symbol.asyncDispose], 'function');\n\n    // Test asyncDispose returns a Promise\n    const testSocket6 = dgram.createSocket('udp4');\n    const disposeResult = testSocket6[Symbol.asyncDispose]();\n    ok(disposeResult instanceof Promise);\n\n    // Verify the promise resolves to undefined\n    disposeResult.then((result) => {\n      strictEqual(result, undefined);\n    });\n\n    // Test default export\n    strictEqual(typeof dgram.default, 'object');\n    strictEqual(dgram.default.createSocket, dgram.createSocket);\n    strictEqual(dgram.default.Socket, dgram.Socket);\n\n    // Test createSocket with more complex options\n    const socket11 = dgram.createSocket({\n      type: 'udp4',\n      reuseAddr: true,\n      ipv6Only: false,\n      recvBufferSize: 1024,\n      sendBufferSize: 1024,\n    });\n    ok(socket11 instanceof dgram.Socket);\n\n    // Test edge cases with validation\n    throws(\n      () => {\n        dgram.createSocket({ type: 'udp4' }, null);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Test that Socket extends EventEmitter properly\n    // Note: We can't test this directly since constructor throws, but we can check prototype chain\n    const EventEmitter = dgram.Socket.prototype.constructor;\n    strictEqual(typeof EventEmitter, 'function');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/dgram-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"dgram-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"dgram-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_dgram_module\", \"experimental\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/diagnostics-channel-test.js",
    "content": "import { ok, strictEqual } from 'node:assert';\n\nimport {\n  hasSubscribers,\n  channel,\n  subscribe,\n  unsubscribe,\n  tracingChannel,\n  Channel,\n} from 'node:diagnostics_channel';\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\nexport const test_basics = {\n  async test(ctrl, env, ctx) {\n    ok(!hasSubscribers('foo'));\n    const channel1 = channel('foo');\n    const channel2 = channel('foo');\n    strictEqual(channel1, channel2);\n    ok(channel1 instanceof Channel);\n\n    const messagePromise = Promise.withResolvers();\n\n    const listener = (message) => {\n      try {\n        strictEqual(message, 'hello');\n        messagePromise.resolve();\n      } catch (err) {\n        messagePromise.reject(err);\n      }\n    };\n\n    subscribe('foo', listener);\n\n    ok(hasSubscribers('foo'));\n\n    channel1.publish('hello');\n\n    unsubscribe('foo', listener);\n\n    ok(!hasSubscribers('foo'));\n\n    await messagePromise.promise;\n  },\n};\n\nexport const test_tracing = {\n  async test(ctrl, env, ctx) {\n    const tc = tracingChannel('bar');\n    ok(tc.start instanceof Channel);\n    ok(tc.end instanceof Channel);\n    ok(tc.asyncStart instanceof Channel);\n    ok(tc.asyncEnd instanceof Channel);\n    ok(tc.error instanceof Channel);\n\n    const als = new AsyncLocalStorage();\n    tc.start.bindStore(als);\n\n    const promises = [\n      Promise.withResolvers(),\n      Promise.withResolvers(),\n      Promise.withResolvers(),\n      Promise.withResolvers(),\n      Promise.withResolvers(),\n    ];\n\n    const context = {};\n\n    tc.subscribe({\n      start(_, name) {\n        try {\n          // Since the als is bound to tc.start, the context should be\n          // propagated here to the listener.\n          strictEqual(als.getStore(), context);\n          strictEqual(name, 'tracing:bar:start');\n          promises[0].resolve();\n        } catch (err) {\n          promises[0].reject(err);\n        }\n      },\n      end(_, name) {\n        try {\n          // Since the als is bound to tc.start, the context should be\n          // propagated here to the other listeners even if they aren't\n          // explicitly bound to als.\n          strictEqual(als.getStore(), context);\n          strictEqual(name, 'tracing:bar:end');\n          promises[1].resolve();\n        } catch (err) {\n          promises[1].reject(err);\n        }\n      },\n      asyncStart() {\n        promises[2].resolve();\n      },\n      asyncEnd() {\n        promises[3].resolve();\n      },\n      error() {\n        promises[4].resolve();\n      },\n    });\n\n    tc.tracePromise(async () => {\n      throw new Error('boom');\n    }, context);\n\n    await Promise.all(promises.map((p) => p.promise));\n  },\n};\n\nexport const serFailureTest = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n    const channel1 = channel('ser');\n    subscribe('ser', () => {\n      resolve();\n    });\n    channel1.publish(function () {});\n    await promise;\n  },\n};\n\nexport const DiagChannelSubscribeDuringPublish = {\n  async test() {\n    const ch = channel('test-publish');\n    let callCount = 0;\n    let duringPublishCallCount = 0;\n\n    for (let i = 0; i < 32; i++) {\n      ch.subscribe(() => {\n        callCount++;\n      });\n    }\n\n    ch.subscribe(() => {\n      callCount++;\n      for (let i = 0; i < 64; i++) {\n        ch.subscribe(() => {\n          duringPublishCallCount++;\n        });\n      }\n    });\n\n    for (let i = 0; i < 16; i++) {\n      ch.subscribe(() => {\n        callCount++;\n      });\n    }\n\n    ch.publish({ data: 'trigger' });\n\n    strictEqual(callCount, 49);\n    strictEqual(duringPublishCallCount, 0);\n  },\n};\n\nexport const DiagChannelUnbindDuringRunStores = {\n  async test() {\n    const ch = channel('test');\n    const als = new AsyncLocalStorage();\n    let transformCallCount = 0;\n\n    ch.bindStore(als, (msg) => {\n      transformCallCount++;\n      ch.unbindStore(als);\n      return msg;\n    });\n\n    const result = ch.runStores({}, () => 'done');\n    strictEqual(result, 'done');\n    strictEqual(transformCallCount, 1);\n\n    ch.runStores({}, () => {});\n    strictEqual(transformCallCount, 1);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/diagnostics-channel-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"diagnostics-channel-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"diagnostics-channel-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/dns-nodejs-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\nimport dns from 'node:dns';\nimport dnsPromises from 'node:dns/promises';\nimport { strictEqual, ok, deepStrictEqual, throws } from 'node:assert';\nimport { inspect } from 'node:util';\n\n// Taken from Node.js\n// https://github.com/nodejs/node/blob/d5d1e80763202ffa73307213211148571deac27c/test/common/internet.js\nconst addresses = {\n  // A generic host that has registered common DNS records,\n  // supports both IPv4 and IPv6, and provides basic HTTP/HTTPS services\n  INET_HOST: 'nodejs.org',\n  // A host that provides IPv4 services\n  INET4_HOST: 'nodejs.org',\n  // A host that provides IPv6 services\n  INET6_HOST: 'nodejs.org',\n  // An accessible IPv4 IP,\n  // defaults to the Google Public DNS IPv4 address\n  INET4_IP: '8.8.8.8',\n  // An accessible IPv6 IP,\n  // defaults to the Google Public DNS IPv6 address\n  INET6_IP: '2001:4860:4860::8888',\n  // An invalid host that cannot be resolved\n  // See https://tools.ietf.org/html/rfc2606#section-2\n  INVALID_HOST: 'something.invalid',\n  // A host with MX records registered\n  MX_HOST: 'nodejs.org',\n  // On some systems, .invalid returns a server failure/try again rather than\n  // record not found. Use this to guarantee record not found.\n  NOT_FOUND: 'come.on.fhqwhgads.test',\n  // A host with SRV records registered\n  SRV_HOST: '_caldav._tcp.google.com',\n  // A host with PTR records registered\n  PTR_HOST: '8.8.8.8.in-addr.arpa',\n  // A host with NAPTR records registered\n  NAPTR_HOST: 'sip2sip.info',\n  // A host with SOA records registered\n  SOA_HOST: 'nodejs.org',\n  // A host with CAA record registered\n  CAA_HOST: 'google.com',\n  // A host with CNAME records registered\n  CNAME_HOST: 'blog.nodejs.org',\n  // A host with NS records registered\n  NS_HOST: 'nodejs.org',\n  // A host with TXT records registered\n  TXT_HOST: 'nodejs.org',\n  // An accessible IPv4 DNS server\n  DNS4_SERVER: '8.8.8.8',\n  // An accessible IPv4 DNS server\n  DNS6_SERVER: '2001:4860:4860::8888',\n};\n\nexport const functionsExist = {\n  async test() {\n    const syncFns = [\n      'lookup',\n      'lookupService',\n      'resolve',\n      'resolve4',\n      'resolve6',\n      'resolveAny',\n      'resolveCname',\n      'resolveCaa',\n      'resolveMx',\n      'resolveNaptr',\n      'resolveNs',\n      'resolvePtr',\n      'resolveSoa',\n      'resolveSrv',\n      'setDefaultResultOrder',\n      'getDefaultResultOrder',\n      'setServers',\n    ];\n\n    for (const fn of syncFns) {\n      strictEqual(typeof dns[fn], 'function');\n    }\n\n    ok(dns.promises !== undefined);\n  },\n};\n\nexport const errorCodesExist = {\n  async test() {\n    strictEqual(typeof dns.NODATA, 'string');\n    strictEqual(typeof dnsPromises.NODATA, 'string');\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/d5d1e80763202ffa73307213211148571deac27c/test/internet/test-dns.js#L483\nexport const resolveTxt = {\n  async test() {\n    function validateResult(result) {\n      ok(\n        result.length > 0,\n        `Result length should be greater than 0 but got ${inspect(result)}`\n      );\n      ok(Array.isArray(result[0]));\n      ok(\n        result.some((record) => record[0].startsWith('v=spf1')),\n        `Expected SPF record but got ${inspect(result[0])}`\n      );\n    }\n\n    validateResult(await dnsPromises.resolveTxt(addresses.TXT_HOST));\n\n    {\n      // Callback API\n      const { promise, resolve, reject } = Promise.withResolvers();\n      dns.resolveTxt(addresses.TXT_HOST, (error, results) => {\n        if (error) {\n          reject(error);\n          return;\n        }\n        validateResult(results);\n        resolve();\n      });\n      await promise;\n    }\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/d5d1e80763202ffa73307213211148571deac27c/test/internet/test-dns.js#L402\nexport const resolveCaa = {\n  async test() {\n    function validateResult(result) {\n      ok(Array.isArray(result), `expected array, got ${inspect(result)}`);\n      strictEqual(result.length, 1);\n      strictEqual(typeof result[0].critical, 'number');\n      strictEqual(result[0].critical, 0);\n      strictEqual(result[0].issue, 'pki.goog');\n    }\n\n    validateResult(await dnsPromises.resolveCaa(addresses.CAA_HOST));\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/d5d1e80763202ffa73307213211148571deac27c/test/internet/test-dns.js#L142\nexport const resolveMx = {\n  async test() {\n    function validateResult(result) {\n      ok(result.length > 0);\n\n      for (const item of result) {\n        strictEqual(typeof item, 'object');\n        ok(item.exchange);\n        strictEqual(typeof item.exchange, 'string');\n        strictEqual(typeof item.priority, 'number');\n      }\n    }\n    validateResult(await dnsPromises.resolveMx(addresses.MX_HOST));\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/d7fdbb994cda8b2e1da4240eb97270c6abbaa9dd/test/internet/test-dns.js#L442\nexport const resolveCname = {\n  async test() {\n    function validateResult(result) {\n      ok(result.length > 0);\n\n      for (const item of result) {\n        ok(item);\n        strictEqual(typeof item, 'string');\n      }\n    }\n\n    validateResult(await dnsPromises.resolveCname(addresses.CNAME_HOST));\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/d7fdbb994cda8b2e1da4240eb97270c6abbaa9dd/test/internet/test-dns.js#L184\nexport const resolveNs = {\n  async test() {\n    function validateResult(result) {\n      ok(result.length > 0);\n\n      for (const item of result) {\n        ok(item);\n        strictEqual(typeof item, 'string');\n      }\n    }\n\n    validateResult(await dnsPromises.resolveNs(addresses.NS_HOST));\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/d7fdbb994cda8b2e1da4240eb97270c6abbaa9dd/test/internet/test-dns.js#L268\nexport const resolvePtr = {\n  async test() {\n    function validateResult(result) {\n      ok(result.length > 0);\n\n      for (const item of result) {\n        ok(item);\n        strictEqual(typeof item, 'string');\n      }\n    }\n\n    validateResult(await dnsPromises.resolvePtr(addresses.PTR_HOST));\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/d7fdbb994cda8b2e1da4240eb97270c6abbaa9dd/test/internet/test-dns.js#L224\nexport const resolveSrv = {\n  async test() {\n    function validateResult(result) {\n      ok(result.length > 0);\n\n      for (const item of result) {\n        strictEqual(typeof item, 'object');\n        ok(item.name);\n        strictEqual(typeof item.name, 'string');\n        strictEqual(typeof item.port, 'number');\n        strictEqual(typeof item.priority, 'number');\n        strictEqual(typeof item.weight, 'number');\n      }\n    }\n\n    validateResult(await dnsPromises.resolveSrv(addresses.SRV_HOST));\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/d7fdbb994cda8b2e1da4240eb97270c6abbaa9dd/test/internet/test-dns.js#L353\nexport const resolveSoa = {\n  async test() {\n    function validateResult(result) {\n      strictEqual(typeof result, 'object');\n      strictEqual(typeof result.nsname, 'string');\n      ok(result.nsname.length > 0);\n      strictEqual(typeof result.hostmaster, 'string');\n      ok(result.hostmaster.length > 0);\n      strictEqual(typeof result.serial, 'number');\n      ok(result.serial > 0 && result.serial < 4294967295);\n      strictEqual(typeof result.refresh, 'number');\n      ok(result.refresh > 0 && result.refresh < 2147483647);\n      strictEqual(typeof result.retry, 'number');\n      ok(result.retry > 0 && result.retry < 2147483647);\n      strictEqual(typeof result.expire, 'number');\n      ok(result.expire > 0 && result.expire < 2147483647);\n      strictEqual(typeof result.minttl, 'number');\n      ok(result.minttl >= 0 && result.minttl < 2147483647);\n    }\n\n    validateResult(await dnsPromises.resolveSoa(addresses.SOA_HOST));\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/d7fdbb994cda8b2e1da4240eb97270c6abbaa9dd/test/internet/test-dns.js#L308\nexport const resolveNaptr = {\n  async test() {\n    function validateResult(result) {\n      ok(result.length > 0);\n\n      for (const item of result) {\n        strictEqual(typeof item, 'object');\n        strictEqual(typeof item.flags, 'string');\n        strictEqual(typeof item.service, 'string');\n        strictEqual(typeof item.regexp, 'string');\n        strictEqual(typeof item.replacement, 'string');\n        strictEqual(typeof item.order, 'number');\n        strictEqual(typeof item.preference, 'number');\n      }\n    }\n\n    validateResult(await dnsPromises.resolveNaptr(addresses.NAPTR_HOST));\n  },\n};\n\nexport const resolve4 = {\n  async test() {\n    function validateResult(result) {\n      ok(result.length > 0);\n\n      for (const item of result) {\n        strictEqual(typeof item, 'string');\n        // TODO(soon): Validate IPv4\n        // ok(isIPv4(item));\n      }\n    }\n\n    validateResult(await dnsPromises.resolve4(addresses.INET4_HOST));\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/d5d1e80763202ffa73307213211148571deac27c/test/internet/test-dns.js#L86\nexport const resolve4TTL = {\n  async test() {\n    function validateResult(result) {\n      ok(result.length > 0);\n\n      for (const item of result) {\n        strictEqual(typeof item, 'object');\n        strictEqual(typeof item.ttl, 'number');\n        strictEqual(typeof item.address, 'string');\n        ok(item.ttl >= 0);\n        // TODO(soon): Validate IPv4\n        // ok(isIPv4(item.address));\n      }\n    }\n\n    validateResult(\n      await dnsPromises.resolve4(addresses.INET4_HOST, {\n        ttl: true,\n      })\n    );\n  },\n};\n\nexport const resolve6 = {\n  async test() {\n    function validateResult(result) {\n      ok(result.length > 0);\n\n      for (const item of result) {\n        strictEqual(typeof item, 'string');\n        // TODO(soon): Validate IPv6\n        // ok(isIPv6(item));\n      }\n    }\n\n    validateResult(await dnsPromises.resolve6(addresses.INET6_HOST));\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/d5d1e80763202ffa73307213211148571deac27c/test/internet/test-dns.js#L114\nexport const resolve6TTL = {\n  async test() {\n    function validateResult(result) {\n      ok(result.length > 0);\n\n      for (const item of result) {\n        strictEqual(typeof item, 'object');\n        strictEqual(typeof item.ttl, 'number');\n        strictEqual(typeof item.address, 'string');\n        ok(item.ttl >= 0);\n        // TODO(soon): Validate IPv6\n        // ok(isIPv6(item.address));\n      }\n    }\n\n    validateResult(\n      await dnsPromises.resolve6(addresses.INET6_HOST, {\n        ttl: true,\n      })\n    );\n  },\n};\n\nexport const getServers = {\n  async test() {\n    deepStrictEqual(await dnsPromises.getServers(), [\n      '1.1.1.1',\n      '2606:4700:4700::1111',\n      '1.0.0.1',\n      '2606:4700:4700::1001',\n    ]);\n  },\n};\n\n// Regression: dns.lookup() with all:true and family:0 must return family:6 for\n// IPv6 addresses (was returning family:4 due to copy-paste bug).\nexport const lookupAllFamilyZeroIPv6Family = {\n  async test() {\n    const results = await dnsPromises.lookup(addresses.INET_HOST, {\n      all: true,\n      family: 0,\n    });\n    ok(Array.isArray(results), 'expected array of addresses');\n    ok(results.length > 0, 'expected at least one address');\n\n    for (const entry of results) {\n      strictEqual(typeof entry.address, 'string');\n      ok(\n        entry.family === 4 || entry.family === 6,\n        `family must be 4 or 6, got ${entry.family}`\n      );\n    }\n\n    // If any IPv6 addresses are returned, they must have family:6\n    const ipv6Entries = results.filter((e) => e.address.includes(':'));\n    for (const entry of ipv6Entries) {\n      strictEqual(\n        entry.family,\n        6,\n        `IPv6 address ${entry.address} should have family:6 but got family:${entry.family}`\n      );\n    }\n\n    // If any IPv4 addresses are returned, they must have family:4\n    const ipv4Entries = results.filter((e) => !e.address.includes(':'));\n    for (const entry of ipv4Entries) {\n      strictEqual(\n        entry.family,\n        4,\n        `IPv4 address ${entry.address} should have family:4 but got family:${entry.family}`\n      );\n    }\n  },\n};\n\n// Regression: dns.lookup() with default family (0) and all:false must resolve\n// hosts that only have A records (was only querying AAAA, failing for IPv4-only).\nexport const lookupDefaultFamilyResolvesIPv4 = {\n  async test() {\n    // Default options (family:0, all:false) — should return an address\n    const result = await dnsPromises.lookup(addresses.INET4_HOST);\n    ok(result != null, 'expected a result');\n    strictEqual(typeof result.address, 'string');\n    ok(result.address.length > 0, 'expected non-empty address');\n    ok(\n      result.family === 4 || result.family === 6,\n      `family must be 4 or 6, got ${result.family}`\n    );\n\n    // Also verify via callback API\n    const { promise, resolve, reject } = Promise.withResolvers();\n    dns.lookup(addresses.INET4_HOST, (error, address, family) => {\n      if (error) {\n        reject(error);\n        return;\n      }\n      strictEqual(typeof address, 'string');\n      ok(address.length > 0, 'expected non-empty address from callback');\n      ok(family === 4 || family === 6, `family must be 4 or 6, got ${family}`);\n      resolve();\n    });\n    await promise;\n  },\n};\n\n// Regression: Resolver.resolvePtr must exist (was misspelled as 'esolvePtr').\nexport const resolverResolvePtrExists = {\n  async test() {\n    const resolver = new dnsPromises.Resolver();\n    strictEqual(\n      typeof resolver.resolvePtr,\n      'function',\n      'Resolver.resolvePtr should be a function'\n    );\n    // Actually call it to verify it works end-to-end\n    const result = await resolver.resolvePtr(addresses.PTR_HOST);\n    ok(Array.isArray(result), 'expected array result');\n    ok(result.length > 0, 'expected at least one PTR record');\n    for (const item of result) {\n      strictEqual(typeof item, 'string');\n    }\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/3153c8333e3a8f2015b795642def4d81ec7cd7b3/test/parallel/test-dns-lookup.js\nexport const testDnsLookup = {\n  async test() {\n    {\n      const err = {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n      };\n\n      throws(() => dns.lookup(1, {}), err);\n      throws(() => dnsPromises.lookup(1, {}), err);\n    }\n\n    throws(\n      () => {\n        dns.lookup(false, 'cb');\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n      }\n    );\n\n    throws(\n      () => {\n        dns.lookup(false, 'options', 'cb');\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n      }\n    );\n\n    {\n      const family = 20;\n      const err = {\n        code: 'ERR_INVALID_ARG_VALUE',\n        name: 'TypeError',\n        message: `The property 'options.family' must be one of: 0, 4, 6. Received ${family}`,\n      };\n      const options = {\n        family,\n        all: false,\n      };\n\n      throws(() => {\n        dnsPromises.lookup(false, options);\n      }, err);\n      throws(() => {\n        dns.lookup(false, options, () => {});\n      }, err);\n    }\n\n    [1, 0n, 1n, '', '0', Symbol(), true, false, {}, [], () => {}].forEach(\n      (family) => {\n        const err = { code: 'ERR_INVALID_ARG_VALUE' };\n        const options = { family };\n        throws(() => {\n          dnsPromises.lookup(false, options);\n        }, err);\n        throws(() => {\n          dns.lookup(false, options, () => {});\n        }, err);\n      }\n    );\n    [0n, 1n, '', '0', Symbol(), true, false].forEach((family) => {\n      const err = { code: 'ERR_INVALID_ARG_TYPE' };\n      throws(() => {\n        dnsPromises.lookup(false, family);\n      }, err);\n      throws(() => {\n        dns.lookup(false, family, () => {});\n      }, err);\n    });\n\n    [0, 1, 0n, 1n, '', '0', Symbol(), {}, [], () => {}].forEach((all) => {\n      const err = { code: 'ERR_INVALID_ARG_TYPE' };\n      const options = { all };\n      throws(() => {\n        dnsPromises.lookup(false, options);\n      }, err);\n      throws(() => {\n        dns.lookup(false, options, () => {});\n      }, err);\n    });\n\n    [0, 1, 0n, 1n, '', '0', Symbol(), {}, [], () => {}].forEach((verbatim) => {\n      const err = { code: 'ERR_INVALID_ARG_TYPE' };\n      const options = { verbatim };\n      throws(() => {\n        dnsPromises.lookup(false, options);\n      }, err);\n      throws(() => {\n        dns.lookup(false, options, () => {});\n      }, err);\n    });\n\n    [0, 1, 0n, 1n, '', '0', Symbol(), {}, [], () => {}].forEach((order) => {\n      const err = { code: 'ERR_INVALID_ARG_VALUE' };\n      const options = { order };\n      throws(() => {\n        dnsPromises.lookup(false, options);\n      }, err);\n      throws(() => {\n        dns.lookup(false, options, () => {});\n      }, err);\n    });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/dns-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"nodejs-dns-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"dns-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/domain-nodejs-test.js",
    "content": "import * as domain from 'node:domain';\nimport { strictEqual, throws, ok } from 'node:assert';\nimport { EventEmitter } from 'node:events';\n\nexport const domainTest = {\n  test() {\n    // Note: test was generated by claude with manual tweaks\n\n    // Test that Domain class exists and is a constructor\n    strictEqual(typeof domain.Domain, 'function');\n\n    // Test that createDomain function exists\n    strictEqual(typeof domain.createDomain, 'function');\n\n    // Test that create export exists (should be a Domain instance)\n    ok(domain.create instanceof domain.Domain);\n\n    // Test that active is null (domains are non-operational)\n    strictEqual(domain.active, null);\n\n    // Test creating a new domain\n    const d = domain.createDomain();\n    ok(d instanceof domain.Domain);\n    ok(d instanceof EventEmitter); // Domain extends EventEmitter\n\n    // Test that Domain has expected properties\n    strictEqual(d.members, undefined);\n\n    // Test domain methods exist and are functions\n    strictEqual(typeof d.enter, 'function');\n    strictEqual(typeof d.exit, 'function');\n    strictEqual(typeof d.add, 'function');\n    strictEqual(typeof d.remove, 'function');\n    strictEqual(typeof d.run, 'function');\n    strictEqual(typeof d.intercept, 'function');\n    strictEqual(typeof d.bind, 'function');\n    strictEqual(typeof d._errorHandler, 'function');\n\n    // Test enter() method - should be no-op (not throw)\n    d.enter();\n\n    // Test exit() method - should be no-op (not throw)\n    d.exit();\n\n    // Test add() method - should be no-op (not throw)\n    const ee = new EventEmitter();\n    d.add(ee);\n    d.add(null);\n    d.add(undefined);\n    d.add('string');\n    d.add(123);\n\n    // Test remove() method - should be no-op (not throw)\n    d.remove(ee);\n    d.remove(null);\n    d.remove(undefined);\n    d.remove('string');\n    d.remove(123);\n\n    // Test run() method - should execute function and return result\n    let callCount = 0;\n    const testFn = function (...args) {\n      callCount++;\n      strictEqual(this, d); // this should be the domain\n      return args.reduce((a, b) => a + b, 0);\n    };\n\n    const result = d.run(testFn, 1, 2, 3, 4);\n    strictEqual(result, 10);\n    strictEqual(callCount, 1);\n\n    // Test run() with function that throws\n    const errorFn = function () {\n      throw new Error('test error');\n    };\n    throws(() => {\n      d.run(errorFn);\n    }, /test error/);\n\n    // Test run() with no arguments\n    const noArgFn = function () {\n      return 'no args';\n    };\n    strictEqual(d.run(noArgFn), 'no args');\n\n    // Test intercept() method - should return the callback directly\n    const callback1 = function () {\n      return 'callback1';\n    };\n    const intercepted = d.intercept(callback1);\n    strictEqual(intercepted, callback1);\n    strictEqual(intercepted(), 'callback1');\n\n    // Test bind() method - should return the callback directly\n    const callback2 = function () {\n      return 'callback2';\n    };\n    const bound = d.bind(callback2);\n    strictEqual(bound, callback2);\n    strictEqual(bound(), 'callback2');\n\n    // Test _errorHandler() method - should throw the error passed to it\n    const testError = new Error('test error handler');\n    throws(() => {\n      d._errorHandler(testError);\n    }, testError);\n\n    throws(() => {\n      d._errorHandler('string error');\n    }, /string error/);\n\n    throws(() => {\n      d._errorHandler(null);\n    });\n\n    // Test that multiple domains can be created independently\n    const d1 = domain.createDomain();\n    const d2 = domain.createDomain();\n    ok(d1 !== d2);\n    ok(d1 instanceof domain.Domain);\n    ok(d2 instanceof domain.Domain);\n\n    // Test that domains inherit from EventEmitter properly\n    let emitted = false;\n    d1.on('test', () => {\n      emitted = true;\n    });\n    d1.emit('test');\n    strictEqual(emitted, true);\n\n    // Test domain with async function (should work normally)\n    let asyncResult = null;\n    const asyncFn = async function (value) {\n      return Promise.resolve(value * 2);\n    };\n\n    const asyncPromise = d.run(asyncFn, 5);\n    ok(asyncPromise instanceof Promise);\n\n    // Test that EventEmitter.usingDomains is set to false\n    strictEqual(EventEmitter.usingDomains, false);\n\n    // Test default export\n    strictEqual(typeof domain.default, 'object');\n    strictEqual(domain.default.Domain, domain.Domain);\n    strictEqual(domain.default.active, domain.active);\n    strictEqual(domain.default.createDomain, domain.createDomain);\n    strictEqual(domain.default.create, domain.create);\n\n    // Test edge cases with run()\n    // Test with null function (should throw)\n    throws(() => {\n      d.run(null);\n    });\n\n    throws(() => {\n      d.run(undefined);\n    });\n\n    throws(() => {\n      d.run('not a function');\n    });\n\n    // Test intercept with non-function (should still return it)\n    const nonFunction = 'not a function';\n    strictEqual(d.intercept(nonFunction), nonFunction);\n\n    // Test bind with non-function (should still return it)\n    strictEqual(d.bind(nonFunction), nonFunction);\n\n    // Test that domain methods can be called multiple times safely\n    d.enter();\n    d.enter();\n    d.exit();\n    d.exit();\n\n    // Test members property remains undefined after operations\n    d.add(new EventEmitter());\n    d.remove(new EventEmitter());\n    d.enter();\n    d.exit();\n    strictEqual(d.members, undefined);\n\n    console.log('All domain module tests passed!');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/domain-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"domain-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"domain-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_domain_module\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/agent1-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIID6DCCAtCgAwIBAgIUFH02wcL3Qgben6tfIibXitsApCYwDQYJKoZIhvcNAQEL\nBQAwejELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEPMA0G\nA1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQDDANjYTExIDAe\nBgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMCAXDTIyMDkwMzIxNDAzN1oY\nDzIyOTYwNjE3MjE0MDM3WjB9MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAcMAlNGMQ8wDQYDVQQKDAZKb3llbnQxEDAOBgNVBAsMB05vZGUuanMxDzAN\nBgNVBAMMBmFnZW50MTEgMB4GCSqGSIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcw\nggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUVjIK+yDTgnCT3CxChO0E\n37q9VuHdrlKeKLeQzUJW2yczSfNzX/0zfHpjY+zKWie39z3HCJqWxtiG2wxiOI8c\n3WqWOvzVmdWADlh6EfkIlg+E7VC6JaKDA+zabmhPvnuu3JzogBMnsWl68lCXzuPx\ndeQAmEwNtqjrh74DtM+Ud0ulb//Ixjxo1q3rYKu+aaexSramuee6qJta2rjrB4l8\nB/bU+j1mDf9XQQfSjo9jRnp4hiTFdBl2k+lZzqE2L/rhu6EMjA2IhAq/7xA2MbLo\n9cObVUin6lfoo5+JKRgT9Fp2xEgDOit+2EA/S6oUfPNeLSVUqmXOSWlXlwlb9Nxr\nAgMBAAGjYTBfMF0GCCsGAQUFBwEBBFEwTzAjBggrBgEFBQcwAYYXaHR0cDovL29j\nc3Aubm9kZWpzLm9yZy8wKAYIKwYBBQUHMAKGHGh0dHA6Ly9jYS5ub2RlanMub3Jn\nL2NhLmNlcnQwDQYJKoZIhvcNAQELBQADggEBAMM0mBBjLMt9pYXePtUeNO0VTw9y\nFWCM8nAcAO2kRNwkJwcsispNpkcsHZ5o8Xf5mpCotdvziEWG1hyxwU6nAWyNOLcN\nG0a0KUfbMO3B6ZYe1GwPDjXaQnv75SkAdxgX5zOzca3xnhITcjUUGjQ0fbDfwFV5\nix8mnzvfXjDONdEznVa7PFcN6QliFUMwR/h8pCRHtE5+a10OSPeJSrGG+FtrGnRW\nG1IJUv6oiGF/MvWCr84REVgc1j78xomGANJIu2hN7bnD1nEMON6em8IfnDOUtynV\n9wfWTqiQYD5Zifj6WcGa0aAHMuetyFG4lIfMAHmd3gaKpks7j9l26LwRPvI=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/dh_private.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIBPgIBADCCARcGCSqGSIb3DQEDATCCAQgCggEBAP//////////rfhUWKK7Spqv\n3FYgJz088di5xYPOLTaVqeE2QRRkM/vMk53OJJs++X0v42NjDHXY9oGyAq7EYXrT\n3x7V1f1lYSQz9R9fBm7QhWNlVT3tGvO1VxNef1fJNZhPDHDg5ot34qaJ2vPv6HId\n8VihNq3nNTCsyk9IOnl6vAqxgrMk+2HRCKlLssjj+7lq2rdg1/RoHU9Co945TfSu\nVu3nY3K7GQsHp8juCm1wngL84c334uzANATNKDQvYZFy/pzphYP/jk8SMu7ygYPD\n/jsbTG+tczu1/LwuwiAFxY7xg30Wg7LG80omwbLv+ohrQjhhKFyX//////////8C\nAQIEHgIcKNGyhQRxIhVXoyktdymwbN6MgXv85vPax+8eqQ==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/dh_public.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMIICJTCCARcGCSqGSIb3DQEDATCCAQgCggEBAP//////////rfhUWKK7Spqv3FYg\nJz088di5xYPOLTaVqeE2QRRkM/vMk53OJJs++X0v42NjDHXY9oGyAq7EYXrT3x7V\n1f1lYSQz9R9fBm7QhWNlVT3tGvO1VxNef1fJNZhPDHDg5ot34qaJ2vPv6HId8Vih\nNq3nNTCsyk9IOnl6vAqxgrMk+2HRCKlLssjj+7lq2rdg1/RoHU9Co945TfSuVu3n\nY3K7GQsHp8juCm1wngL84c334uzANATNKDQvYZFy/pzphYP/jk8SMu7ygYPD/jsb\nTG+tczu1/LwuwiAFxY7xg30Wg7LG80omwbLv+ohrQjhhKFyX//////////8CAQID\nggEGAAKCAQEA2whDVdYtNbr/isSFdw7rOSdbmcWrxiX6ppqDZ6yp8XjUj3/CEf/P\n60X7HndX+nXD7YaPtVZxktkIpArI7C+AH7fZxBduuv2eLnvYwK82jFHKe7zvfdMr\n26akMCV0kBA3ktgcftHlqYsIj52BaJlG37FRha3SDOL2yJOij3hNQhHCXTWLg7tP\nGtXmD202OoZ6Ll+LxBzBCFnxVauiKnzBGeawy4gDycUEHmq5oDRR68I2gmxmsLg5\nMQVAP5ljp+FEu4+TZm6hR4wQ5PRjCQ+teq+VqMro7EbbvZpn+X9kAgKSl2WDu0fT\nFbUnBn3HPBmUa/Fv/ooXrlckTUDjLkbWZQ==\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/dsa_private.pem",
    "content": "-----BEGIN DSA PRIVATE KEY-----\nMIIDVwIBAAKCAQEAoSw3Ghf02sMSmd5k2rvSqf6eJPFO7fHDRyvDDbifjO6/BKSI\nkXM43qyCqddC04arKg7wc1QDEQ8gb13pCmnC0RBiljE6ke4yK46Q5JjiEKH9U1eC\nbtTrhcGrLDgwbqvRM06EN6IfAL3OBF6YzS9wn3/EfSwW2Z8gAIkjZrTjEUTV+/gE\nAdfEgd/WAZxcc9zYKOwPy0/LjjldQw5fsPlIEkS1yJFlWMokSsZVYlJLR06h1S4k\nQoE3BqELirH/FQfJ36RMRFsaKZ6nQYS66Qc8rybQw2VlOJsqiRoTSDwREPz6j9oL\nYh1Ee86j5Xt9jbiBrK33UbkTr/jBtO0J2PR0+wIhAIXIexS5LQJPSoi96k6OU4yr\nLLmAIY8gS9mdYTdbpwcdAoIBAQCCN3gTjFiPgBQ/bj/Edp9w90SA+dQ/VnnYDTMc\nz+Mi/8sgtlQ3O9CCFb0327YnOLwvxsmSadT9XrIq1/5jGD2VtjFDVlridjYASrje\nzR2kdr781G+bxtVNQuIOKZl9xqruCmHUSSRL/vuCR6pKsA81ZPfpdcLh3RYYxDIo\nTK6tVX4GrX5bcxGDIUCQiTaqKv9Nzpm1liBLRm6LHczBsFk2OVrRyMsT3gh0J6DS\nUw+dw/Vru1J6glkrr0CxBWoJ65btcqtFyQV/76btor9Qgc/z9suYBoJZ3Ua0yAfv\n3J2ErOs1CAbh4LWNULA9eJObY2R4sAV7Q8wOMT5jmjKo4unpAoIBAQCE1m+DUb9L\nT58u6XV/L1p6K9T2mc6jAmzD51fPiUwsRov9sDGJmSnQjQ5pt3hVp8inVfNkhqOI\n1rpdKmx5W00fPu6VCiPuximuHSNHzJpCAVUrIH8YasS+AurCOwGMdvODLF6dx7yR\nMdxbiszrBry8J0TdvqElHZ1YmQDwoHH7R4pUd31jsk4gnE6pkqLgWwVAy0LXXGsg\n2JfnDvdQY8fIHkuezLdhOyO9pRlXSYv4fLdMaSjHyEcwr2hnm5tm5RsBwM+u0sDc\nyBqUjwoN8NTuLLasfJzzmjeHWDcRGFbzKt/xlUkQ7pf+xdelnLOstuPDGglB1U85\nREhx4rQGKg7nAiA0FX4e4Ms3OXUnmtsTALk5YMiMF3jUp4pRDhHFKBgsYQ==\n-----END DSA PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/dsa_private_1025.pem",
    "content": "-----BEGIN DSA PRIVATE KEY-----\nMIIB0gIBAAKBiQDp3xGIsNcKZWSpur2Ab2f5rirhu5AsYGLI/XZvzueoO4kLPp3s\nzKHXabBObdxU07kRFkfpV9lftdXT3a6E/Wxb+w2d7g0F7pWTdwpmmofcRtwgbynm\nJKvqWt/OwfVFsdUvvpVci9AxY/5rdM/K54Srp6iSpfteBYKGqCpPnswikND0GxKM\n1MbzAhUAqAPKCZ4hxromirUu33tn3Nhsq20CgYgf0tH2nwzWlPLoN4OJvC5dVLPv\nMRyJTQekW8tR0oY6PeshmZOaoI0L3e/M4KeZ/4qiY7IRNksP2YGJxjfS6W+h+MNj\nPO5r0aYYFYCPQ8JbhBO7l0hdXmkTY3FcdyRHeh2gwe3bBX6Auev2MGgL616Cgd/K\nH8PAX9EHmrNPcQOWHem4KcfJXqouAoGIcZ/4bJurOAw5OL4+5BsE4jfSVFq4nmNl\n+m6Iy6ls3hOHOZ9sJlw0Fi+ZtdeddOTYnIngW/kH09XGoF5JJOaLQv2O4GmZfDfn\nla+vfHZPsOxmYqeEbSwA2pqlhOn/dCshBOymhwNLXp1FJ3JUEyh8i605X06XmWoj\n4ZIu/tr1xbnZDLHuFjBmXAIURbB5/IDf2h0sW+qL5gHFO8bgr+E=\n-----END DSA PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/dsa_private_encrypted.pem",
    "content": "-----BEGIN DSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: AES-256-CBC,FABA263DD471F214EF3E02699B837C20\n\nTj2+4x9MEIaQGFQ4o7hk12MriVYyvLO5aCbqq7LG5uhVk546/+bJc6hewdSwb6oT\nMYPbuV+QTdtqshqFESA0McyGlj4w1tOg5TomP84NTKvwTO1EirVLMukfF3dqaguw\nC117AZJkGbqgbi6lZ2bG0Hta6HRbhI5+ODFtOp3rKQ2KwVmtL7zw6vt3PCISeMHN\nfLqikDc2+YoI9V1FJis9/FATyqV8yrJYYQQpP1RQN+gDY4SSs/eUr+Me7RNy6Lz7\noH0tDaPGbiafwrZe1okksjxT2JQz1Q3hciBPikgdQIoE2NWTUlOeRYX0T0N2n37S\n6Odbcr522e+2XjcLj34Ozthp+Q5mIDcLuakazxkXhq0RyhJ7vo+xA2YiP7Q3vH7g\noAnsJPFNVY6wJhprZi2VofKIUJUiajAXGDVX2yEIG/DOA9rnx0ZP+zopXMi4ptu0\nRzWyAL+P4jn0b8vgPf9CYJmn4VNfOcVmomZ1Bw6hzqTE2FnThJCXU3l2eaC/wcSR\nuMRp8c6IM8AR5DUzUBKIckkvXj1m5iSZoKuR8dB7s9BhrRtBAI7K3G254G06sByv\n0pnft8r+BkMqgdfG4rJQoQJw7tVYln+pL/gYPDuYsqyJ9kFuHDqtBvlozqXY5AL1\nXQaXoD6xMACEoJSIv5y+TzFzXwFQrDW+G1724YOSbiioUfGD0tRfjj2ei63PThQr\nZ50SryfKQQf4UgcJeokMhmRWT2vPXEFWEP1b2FMEQxBy6fyKcqwZBAbhqF6usGEB\nnwr/S1HXQAGEsWoc/Z4yynB7uhOwWu/Vpj+V6B98NmC7EUX15Why8zCsT9gzaIgC\nM6sZafHhcmjfwc+lL9xFlU/wnAOz0LeKZWry3D0sXZn1r2FRlOJdtLLx01Sve/MU\nZRsgEDTkzv8E9dDltMeq8HQDCgLT1USTMWcY1kMELBj7y7ZdCWjH1QhTq2KlId+o\n1X28zJOsOL/XRseUSlpjmSSLRw1QQEypNCY2+tcvViAvn3AifipBbdzUNhvygLhc\na2+5rYsd8BBEFnMJx7lDiyqXGnZkBbhbCSIudppNcjC+akFlFp6fBzkp4mKBuKpc\nhwBBdfqdEyzqu6SVHM8nGV/aDoRuu9shV6MX0y/KnIgLedudn8aN2eLgjR5k1+99\n-----END DSA PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/dsa_private_encrypted_1025.pem",
    "content": "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIBvTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIqTW00yecdxMCAggA\nMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBKgO4UF0LfCkPyS+iCvSrtBIIB\nYD3W6FyEZ97/crnoyRqjPUtr2Mm4KJMtaB5ZiGFzZEzd6AH7N/dbtAAMIibtsjmd\nRYdIptpET6xTpUhM8TvpULyYaZnhZJKTpVUrTVdvFTS3DYDutu7aWRLTrle6LzcY\nXpIppeP8ZmYFdRBQxhF+KoDsP4O0QA+vWl2W2VmRfr+sK9R+qV89w0YMjEWHsYY+\nVZsDbJBGKkj9gzIvxIsRyack/+RsbiSDrh6WTw+D0jrX/IMbgPjvYfBFhpxGC7zR\nhDn9r3JaO2KdHh9kMtvQfshA1n636kb0X6ewY57BhEs3J4hpMg46c6YFry94to24\njxl5KutM0CFea7mYGtNf6WJXBsm7JSW03kjlqYoZGK43KNgZhzKAsXaNkoRkA5cw\nBzGfgmG6dHTpeAY9G4vM4inhCmGFA8Tx189g+xzRv16uFXRb8WFIllne1fEFaXRr\n1Rz2G6SPJkA3fsrl8zUIB0Y=\n-----END ENCRYPTED PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/dsa_private_pkcs8.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIICZQIBADCCAjoGByqGSM44BAEwggItAoIBAQChLDcaF/TawxKZ3mTau9Kp/p4k\n8U7t8cNHK8MNuJ+M7r8EpIiRczjerIKp10LThqsqDvBzVAMRDyBvXekKacLREGKW\nMTqR7jIrjpDkmOIQof1TV4Ju1OuFwassODBuq9EzToQ3oh8Avc4EXpjNL3Cff8R9\nLBbZnyAAiSNmtOMRRNX7+AQB18SB39YBnFxz3Ngo7A/LT8uOOV1DDl+w+UgSRLXI\nkWVYyiRKxlViUktHTqHVLiRCgTcGoQuKsf8VB8nfpExEWxopnqdBhLrpBzyvJtDD\nZWU4myqJGhNIPBEQ/PqP2gtiHUR7zqPle32NuIGsrfdRuROv+MG07QnY9HT7AiEA\nhch7FLktAk9KiL3qTo5TjKssuYAhjyBL2Z1hN1unBx0CggEBAII3eBOMWI+AFD9u\nP8R2n3D3RID51D9WedgNMxzP4yL/yyC2VDc70IIVvTfbtic4vC/GyZJp1P1esirX\n/mMYPZW2MUNWWuJ2NgBKuN7NHaR2vvzUb5vG1U1C4g4pmX3Gqu4KYdRJJEv++4JH\nqkqwDzVk9+l1wuHdFhjEMihMrq1VfgatfltzEYMhQJCJNqoq/03OmbWWIEtGbosd\nzMGwWTY5WtHIyxPeCHQnoNJTD53D9Wu7UnqCWSuvQLEFagnrlu1yq0XJBX/vpu2i\nv1CBz/P2y5gGglndRrTIB+/cnYSs6zUIBuHgtY1QsD14k5tjZHiwBXtDzA4xPmOa\nMqji6ekEIgIgNBV+HuDLNzl1J5rbEwC5OWDIjBd41KeKUQ4RxSgYLGE=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/dsa_public.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMIIDSDCCAjoGByqGSM44BAEwggItAoIBAQChLDcaF/TawxKZ3mTau9Kp/p4k8U7t\n8cNHK8MNuJ+M7r8EpIiRczjerIKp10LThqsqDvBzVAMRDyBvXekKacLREGKWMTqR\n7jIrjpDkmOIQof1TV4Ju1OuFwassODBuq9EzToQ3oh8Avc4EXpjNL3Cff8R9LBbZ\nnyAAiSNmtOMRRNX7+AQB18SB39YBnFxz3Ngo7A/LT8uOOV1DDl+w+UgSRLXIkWVY\nyiRKxlViUktHTqHVLiRCgTcGoQuKsf8VB8nfpExEWxopnqdBhLrpBzyvJtDDZWU4\nmyqJGhNIPBEQ/PqP2gtiHUR7zqPle32NuIGsrfdRuROv+MG07QnY9HT7AiEAhch7\nFLktAk9KiL3qTo5TjKssuYAhjyBL2Z1hN1unBx0CggEBAII3eBOMWI+AFD9uP8R2\nn3D3RID51D9WedgNMxzP4yL/yyC2VDc70IIVvTfbtic4vC/GyZJp1P1esirX/mMY\nPZW2MUNWWuJ2NgBKuN7NHaR2vvzUb5vG1U1C4g4pmX3Gqu4KYdRJJEv++4JHqkqw\nDzVk9+l1wuHdFhjEMihMrq1VfgatfltzEYMhQJCJNqoq/03OmbWWIEtGbosdzMGw\nWTY5WtHIyxPeCHQnoNJTD53D9Wu7UnqCWSuvQLEFagnrlu1yq0XJBX/vpu2iv1CB\nz/P2y5gGglndRrTIB+/cnYSs6zUIBuHgtY1QsD14k5tjZHiwBXtDzA4xPmOaMqji\n6ekDggEGAAKCAQEAhNZvg1G/S0+fLul1fy9aeivU9pnOowJsw+dXz4lMLEaL/bAx\niZkp0I0Oabd4VafIp1XzZIajiNa6XSpseVtNHz7ulQoj7sYprh0jR8yaQgFVKyB/\nGGrEvgLqwjsBjHbzgyxence8kTHcW4rM6wa8vCdE3b6hJR2dWJkA8KBx+0eKVHd9\nY7JOIJxOqZKi4FsFQMtC11xrINiX5w73UGPHyB5Lnsy3YTsjvaUZV0mL+Hy3TGko\nx8hHMK9oZ5ubZuUbAcDPrtLA3MgalI8KDfDU7iy2rHyc85o3h1g3ERhW8yrf8ZVJ\nEO6X/sXXpZyzrLbjwxoJQdVPOURIceK0BioO5w==\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/dsa_public_1025.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMIIBzjCCATsGByqGSM44BAEwggEuAoGJAOnfEYiw1wplZKm6vYBvZ/muKuG7kCxg\nYsj9dm/O56g7iQs+nezModdpsE5t3FTTuREWR+lX2V+11dPdroT9bFv7DZ3uDQXu\nlZN3Cmaah9xG3CBvKeYkq+pa387B9UWx1S++lVyL0DFj/mt0z8rnhKunqJKl+14F\ngoaoKk+ezCKQ0PQbEozUxvMCFQCoA8oJniHGuiaKtS7fe2fc2GyrbQKBiB/S0faf\nDNaU8ug3g4m8Ll1Us+8xHIlNB6Rby1HShjo96yGZk5qgjQvd78zgp5n/iqJjshE2\nSw/ZgYnGN9Lpb6H4w2M87mvRphgVgI9DwluEE7uXSF1eaRNjcVx3JEd6HaDB7dsF\nfoC56/YwaAvrXoKB38ofw8Bf0Qeas09xA5Yd6bgpx8leqi4DgYwAAoGIcZ/4bJur\nOAw5OL4+5BsE4jfSVFq4nmNl+m6Iy6ls3hOHOZ9sJlw0Fi+ZtdeddOTYnIngW/kH\n09XGoF5JJOaLQv2O4GmZfDfnla+vfHZPsOxmYqeEbSwA2pqlhOn/dCshBOymhwNL\nXp1FJ3JUEyh8i605X06XmWoj4ZIu/tr1xbnZDLHuFjBmXA==\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/ec_p256_private.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgDxBsPQPIgMuMyQbx\nzbb9toew6Ev6e9O6ZhpxLNgmAEqhRANCAARfSYxhH+6V5lIg+M3O0iQBLf+53kuE\n2luIgWnp81/Ya1Gybj8tl4tJVu1GEwcTyt8hoA7vRACmCHnI5B1+bNpS\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/ec_p256_public.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEX0mMYR/uleZSIPjNztIkAS3/ud5L\nhNpbiIFp6fNf2GtRsm4/LZeLSVbtRhMHE8rfIaAO70QApgh5yOQdfmzaUg==\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/ec_p384_private.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDB3B+4e4C1OUxGftkEI\nGb/SCulzUP/iE940CB6+B6WWO4LT76T8sMWiwOAGUsuZmyKhZANiAASE43efMYmC\n/7Tx90elDGBEkVnOUr4ZkMZrl/cqe8zfVy++MmayPhR46Ah3LesMCNV+J0eG15w0\nIYJ8uqasuMN6drU1LNbNYfW7+hR0woajldJpvHMPv7wlnGOlzyxH1yU=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/ec_p384_public.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEhON3nzGJgv+08fdHpQxgRJFZzlK+GZDG\na5f3KnvM31cvvjJmsj4UeOgIdy3rDAjVfidHhtecNCGCfLqmrLjDena1NSzWzWH1\nu/oUdMKGo5XSabxzD7+8JZxjpc8sR9cl\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/ec_p521_private.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAEghuafcab9jXW4gO\nQLeDaKOlHEiskQFjiL8klijk6i6DNOXcFfaJ9GW48kxpodw16ttAf9Z1WQstfzpK\nGUetHImhgYkDgYYABAGixYI8Gbc5zNze6rH2/OmsFV3unOnY1GDqG9RTfpJZXpL9\nChF1dG8HA4zxkM+X+jMSwm4THh0Wr1Euj9dK7E7QZwHd35XsQXgH13Hjc0QR9dvJ\nBWzlg+luNTY8CkaqiBdur5oFv/AjpXRimYxZDkhAEsTwXLwNohSUVMkN8IQtNI9D\naQ==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/ec_p521_public.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBosWCPBm3Oczc3uqx9vzprBVd7pzp\n2NRg6hvUU36SWV6S/QoRdXRvBwOM8ZDPl/ozEsJuEx4dFq9RLo/XSuxO0GcB3d+V\n7EF4B9dx43NEEfXbyQVs5YPpbjU2PApGqogXbq+aBb/wI6V0YpmMWQ5IQBLE8Fy8\nDaIUlFTJDfCELTSPQ2k=\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/ec_secp256k1_private.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgc34ocwTwpFa9NZZh3l88\nqXyrkoYSxvC0FEsU5v1v4IOhRANCAARw7OEVKlbGFqUJtY10/Yf/JSR0LzUL1PZ1\n4Ol/ErujAPgNwwGU5PSD6aTfn9NycnYB2hby9XwB2qF3+El+DV8q\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/ec_secp256k1_public.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEcOzhFSpWxhalCbWNdP2H/yUkdC81C9T2\ndeDpfxK7owD4DcMBlOT0g+mk35/TcnJ2AdoW8vV8Adqhd/hJfg1fKg==\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/ed25519_private.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIMFSujN0jIUIdzSvuxka0lfgVVkMdRTuaVvIYUHrvzXQ\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/ed25519_public.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAK1wIouqnuiA04b3WrMa+xKIKIpfHetNZRv3h9fBf768=\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/ed448_private.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMEcCAQAwBQYDK2VxBDsEOdOtCnu9bDdBqSHNNZ5xoDA5KdLBTUNPcKFaOADNX32s\ndfpo52pCtPqfku/l3/OfUHsF43EfZsaaWA==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/ed448_public.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoAoX/ee5+jlcU53+BbGRsGIzly0V+SZtJ/oGXY0udf84q2hTW2\nRdstLktvwpkVJOoNb7oDgc2V5ZUA\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/rsa_private.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAt9xYiIonscC3vz/A2ceR7KhZZlDu/5bye53nCVTcKnWd2seY\n6UAdKersX6njr83Dd5OVe1BW/wJvp5EjWTAGYbFswlNmeD44edEGM939B6Lq+/8i\nBkrTi8mGN4YCytivE24YI0D4XZMPfkLSpab2y/Hy4DjQKBq1ThZ0UBnK+9IhX37J\nu/ZoGYSlTIGIhzyaiYBh7wrZBoPczIEu6et/kN2VnnbRUtkYTF97ggcv5h+hDpUQ\njQW0ZgOMcTc8n+RkGpIt0/iM/bTjI3Tz/gsFdi6hHcpZgbopPL630296iByyigQC\nPJVzdusFrQN5DeC+zT/nGypQkZanLb4ZspSx9QIDAQABAoIBAQCS2erYu8gyoGPi\n3E/zYgQ6ishFAZWzDWSFubwD5wSm4SSAzvViL/RbO6kqS25xR569DmLRiHzD17VI\nmJMsNECUnPrqR2TL256OJZaXrNHh3I1lUwVhEzjeKMsL4/ys+d70XPXoiocVblVs\nmoDXEIGEqa48ywPvVE3Fngeuxrsq3/GCVBNiwtt0YjAOZxmKEh31UZdHO+YI+wNF\n/Z8KQCPscN5HGlR0SIQOlqMANz49aKStrevdvjS1UcpabzDEkuK84g3saJhcpAhb\npGFmAf5GTjkkhE0rE1qDF15dSqrKGfCFtOjUeK17SIEN7E322ChmTReZ1hYGfoSV\ncdFntUINAoGBAPFKL5QeJ6wZu8R/ru11wTG6sQA0Jub2hGccPXpbnPrT+3CACOLI\nJTCLy/xTKW3dqRHj/wZEe+jUw88w7jwGb1BkWr4BI8tDvY9jQLP1jyuLWRfrxXbp\n4Z0oeBBwBeCI/ZG7FIvdDTqWxn1aj3Tmh6s4ByqEdtwrrrJPcBUNl01fAoGBAMMR\n3RGE/ca6X6xz6kgUD6TtHVhiiRJK1jm/u+q0n7i/MBkeDgTZkHYS7lPc0yIdtqaI\nPlz5yzwHnAvuMrv8LSdkjwioig2yQa3tAij8kXxqs7wN5418DMV2s1OJBrPthYPs\nbv4im2iI8V63JQS4ZMYQbckq8ABYccTpOnxXDy0rAoGBAKkvzHa+QjERhjB9GyoT\n1FhLQIsVBmYSWrp1+cGO9V6HPxoeHJzvm+wTSf/uS/FmaINL6+j4Ii4a6gWgmJts\nI6cqBtqNsAx5vjQJczf8KdxthBYa0sXTrsfktXNJKUXMqIgDtp9vazQ2vozs8AQX\nFPAAhD3SzgkJdCBBRSTt97ZfAoGAWAziKpxLKL7LnL4dzDcx8JIPIuwnTxh0plCD\ndCffyLaT8WJ9lXbXHFTjOvt8WfPrlDP/Ylxmfkw5BbGZOP1VLGjZn2DkH9aMiwNm\nbDXFPdG0G3hzQovx/9fajiRV4DWghLHeT9wzJfZabRRiI0VQR472300AVEeX4vgb\nrDBn600CgYEAk7czBCT9rHn/PNwCa17hlTy88C4vXkwbz83Oa+aX5L4e5gw5lhcR\n2ZuZHLb2r6oMt9rlD7EIDItSs+u21LOXWPTAlazdnpYUyw/CzogM/PN+qNwMRXn5\nuXFFhmlP2mVg2EdELTahXch8kWqHaCSX53yvqCtRKu/j76V31TfQZGM=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/rsa_private_2048.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEArk4OqxBqU5/k0FoUDU7CpZpjz6YJEXUpyqeJmFRVZPMUv/Rc\n7U4seLY+Qp6k26T/wlQ2WJWuyY+VJcbQNWLvjJWks5HWknwDuVs6sjuTM8CfHWn1\n960JkK5Ec2TjRhCQ1KJy+uc3GJLtWb4rWVgTbbaaC5fiR1/GeuJ8JH1Q50lB3mDs\nNGIk1U5jhNaYY82hYvlbErf6Ft5njHK0BOM5OTvQ6BBv7c363WNG7tYlNw1J40du\np9OQPo5JmXN/h+sRbdgG8iUxrkRibuGv7loh52QQgq2snznuRMdKidRfUZjCDGgw\nbgK23Q7n8VZ9Y10j8PIvPTLJ83PX4lOEA37JlwIDAQABAoIBACoL2Ev5lLyBaI+9\n+vJO2nNaL9OKSMu2SJODIJTnWwYUASBg0P3Jir6/r3sgi8IUJkH5UHbD/LrQcPkA\n4X7PU9vEyUsr1efWFIvk7t7JsjOctoVA5z2Mty74arivUIe5PUadvUC6/7Zk0u6A\nCjLuJRmlH7nGNKZk+xrvgWTH+fkgc5ddbFxoGH129RcVC+ePbsi1EF60C9KbJvp1\nxjUJ5cDtNYnZ/g+ULo6ZJjRG5kUCVSI8H/Nc/DmStKsjN0isKpNGofU5ArEwywGC\nCqxz/tr4hT2haAkVEto04ooYpqDUSqGEfPpLWL+CjFNPfCsWJ1tX5LQRvpu6eukd\nFO72oVECgYEA4+Ot7RQtGOtPeaxl3P7sbEzBZk0W/ZhCk+mzE2iYh0QXU44VtjtO\n/9CENajTklckiwlxjqBZ5NO1SiMQKBcmqkoA03x/DEujo2rMVuNPoc6ZYp1Gc4qA\n4ImkMQNsM7Swum42rKE960WoiWW7dsdEAq6vqgeApZlMU8lcKRAlOZkCgYEAw85H\n3bjF7gMatVibsWzj0zp2L4616m2v5Z3YkgohGRAvm1912DI5ku5Nmy9am57Z1EP2\nUtDOxahd/Vf6mK9lR4YEbNW1TenykViQJ6lmljOFUeZEZYYO3O+fthkyN/42l5yn\nMyUANTTb2rvt8amdRr0ARdRqWJmt5NfJzYBV+q8CgYB1ZjuZoQVCiybcRcYMPX/K\noxgW/avUZPYXgRNx8jZxqNBjiRUCVjdybhdOFXU5NI9s2SaZFV56Fd6VHM8b+CFB\nJPKcAMzqpqTccQ5nzJ6fevFl7iP3LekKw53EakD5uiI5SMH92OsvIymZ7sDOhgUx\nZJC2hTrvFLRPjbJerSSgMQKBgAv5iZuduT0dI30DtkHbjvNUF/ZAnA+CNcetJ5mG\n1Q9bVg4CgIqAR9UcjdJ3yurJhDjfDylxa7Pa4CSmRMUhtOfy4kJlr3jcXeFVsTs7\nuPJmpDimBHjRAgew/+t7Dv8tpNkQ04jlMmYOnYN7CspEvUGePW4H15kjjOb563WN\n67QxAoGAdhJPaHVtDBUrobn50ORVkVNJVZPBhEDbR6tNtHsgEevH7iYjxAdxEeUa\nc9S2iV9lir3hkrQceiYWAADcphWfO1HyP5Uld4sJzYiEGbSM7a0zRQjYlQgXFbZo\nSAc6Gok78kwECPwpmeH4lpGVeKNmzEteSBVYxGb9b6C/SSsu7l0=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/rsa_private_4096.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIJKAIBAAKCAgEAxeStwofbjtZuol4lwKn1w08AzcSNLHfCqNFHa+W7er8is7LQ\nsPljtPT4yn4lsao83ngHFvSC3tbMiRNDpUHYqH2wBuUkuOmCtYkZLi0307H0CwcV\nV6W5P3tNEt80IJ+PqlRxtTknezUtbOasbIi/aornVWG+psgqDGrFZ4oTsWtiE0Sv\ni7sDqN5E2dijmH/YYnlnwqszgzHdtAnARp1bG34E64sqWCmLoGCfPdHtym/CSdxO\nLOsDV15jrwODZQ/TJZ5thkwKZRxu7g9fwlhA1PiI5WDP4reXNaqa2bSgrzpAljQE\nxYs4N0L7okSVOJQX9BEaoWtq8NLU8MpMdGoHNDU0Xr60Lfr58Z5qn8RGEvlTxoCb\nPJzPV2zgzD/lmEqft6NnfTclveA3sd8xSrOBUn4o3S8hS0b9Su7PBukHjM96/e0R\neoIshSwXlQTLr2Ft8KwupyPm1ltNcTDtjqHcIWU6Bg+kPy9mxSVtGGZYAPtqGzNB\nA/m+oOja/OSPxAblPdln691DaDuZs5nuZCGwGcLaJWgiyoqvXAcyXDZFyH4OZZh8\nrsBLKbnFXHZ/ziG0cAozEygZEPJappw8Lx/ady7WL/SJjxooiKapc7Bnfy8eSLV3\n+XAKxhLW/MQ6ChJ+e/8ExAY02ca4MpCvqwIk9TfV6FM8pWGqHzQFj0v3NL0CAwEA\nAQKCAgBTb8eTbZS09NRQwUFJql9kqbq9B1I+nYAFjbd/Vq1lY5FOEubKt1vCwEbl\nmapq7kwbwJ+8nftP2WEDqouq8chXwialwZdqH4ps4BEt1wLizvUGcUYeXlFs4p/s\nhQ+FccExH8mRjzeGSzWL5PZuDHoogchnx36K83pHIf15Wk5TT+NaHGunjoJMgOqm\nryDK+5xQaL/G5Egj2LKRZksbet0fClMovNRtt5aXWCXL+uc3o0dXvPt5FN2jyLhe\n4ixUQAfWpKWpKgZ3+zUKSpElb/Bl2yRdEiSUgrPOfNAtWmsldnok2mnooHpjUmqm\nUCRaZpZy4YNI6/F6+Gmv3Ju/ubSvHzoxQLlvgUqWAnVshivF1TJImHSIiLIvBKPp\n29SD6maWIT1DC9sKC4E1gq7VO4762l1//zEOAY7XK0Z7LrbZO4WXHnsgFOpGthQ3\ng9Qi/SeM6mb5xEJTBUBTmkhGs1x8jolzca30mqv8T63W4PXkXHmZdK7vyH5useiI\ns0eGUeaYK892WgfxCBo24JCNQiAcH/wTwV4l4yROqeH2V4ShbIYmCzla++7vsPYW\nhAwQR9eH0+4ogTkaMQrm16plZk0ezVX9BKK8KTnd4G9/T18VstQbiowF2/cKnGKC\nOqrmoR2vHOksQdUJVmnwCRqU1symBxhY0GSIps98v+lUYExKQQKCAQEA/uVYE2/H\neNcV/uWAI9LspANXHJE33TFMZ8SuyOYtp3MYJizmQ1uT7Om2LEanDnNiz+fAQhrE\nvo1sDIF9xOAde2qjIH+iDzcLvFPgC3gkQspFjU31M9OO5xAjzBxfL3KDiG2MtmTR\nhNuKJX56eCOqkEp6WKaWOA35ccaKYHxNzMS49weCv95ZPpR9q0J1sgzD7HtVh4yu\nXI01/BC8F0RmYjtsuUo+PmB6sO2K94uqqo0GPUos7Mhgrbff3L36EkOPgmRiA1AV\nZy1sKKxUKspGQ3m1fg+CA/+GZGckvYkVot1lFrwmrS2dok8EhT1HcVJde+++jx7z\nJsRLgFRvKHXklwKCAQEAxsAfxIQjjjKmuyJCzIvxG7lnuzovdy4OEdSuJL4yK5m3\n4BHJHn+yHeRIcrDnJKUTUYffcH/OjOnJS94BA6wH1tEuvGQz6LV6UpwApZ1M/2md\nnP0eC2L2JtSRL8mdxfyqXDloWMpD7rncBZ6ChLEZ6sWYa6WBQTARmPVePyUpNNG2\nqymxN3/vRBGGBunD3j6zX0M0szWK5iU+qsYDy3KzCKG8FU7XxwzRbP7iARRD5Hpt\nZmy2W52EJg1uhmlVXJMm32SEBfrD2oDmlnjAqaZdqi5Mq2e4uB3dhM9RwJppSALG\nBY6k9DeanAFbOlawMJri2pk7B0phCn+DN2pg0+W3ywKCAQBeTwzfZCQxmaMRxGg8\n2PWlWXcJotFAjdTvL95bho6tve/ZcBNiKKf6qB43E40L07VjpyODUdQpjLnFhsO5\n7BH8b+AbTh3v8zXsYDwtAi6oZ56EQavPmR7ubxJPms+9BmmUOLQvZ+39ch0S8lDt\n0oRxDp1l330FEGaSqhrYyCUg9khZXfYKd4IdnWNB0j0pu39iJ9/lXy/EHpsywB5X\nnX8kKUh45fdRrPC4NauNG6fxomwEkUU99oWOwNGbIs87orOeUvXQs/i3TB8QjXI2\nwtBsdsOn+KTqRci7rU3ysp3GvJOCbesBeDcyrnnFsn6Udx0Plgyzd4gPd+FXgeX+\n2l/RAoIBAH81FKAY2xD2RlTb1tlIcGeIQWZKFXs4VPUApP0LZt0VI+UcPRdyL7SG\nGgCeTTLdHQI/7rj4dGEoeRg/3XJWNyY8+KbHk5nMHaCmDJvzlAaduK10LDipfFba\nEpr9dif0Ua15aNn7i4NOHg7Sp0L6f1YOZkHvykzI0VqPIWVVCYyu9TWUF8Mn9SIh\n/SCLmjuy8ed1AlP5Xw9yoyt2VZNvtDtAGTuiHOVfxOL4N/rs149y9HZr+kOlC6G3\nUxhgbqwz2tt8YCvblmNRwURpwRZUTvrPa28Bke713oRUlUSrD9txOwDvjZBpzmEv\nVQ5/0YEqgSvcizVdW8L2XiunwJWfIAUCggEBALr4RF9TYa37CImZOs+vJ8FGRKMz\nh1EUwO2PvuITvkTtu/7E4IjyxAo5dkAokkWQCGABciiDJJEYUWqcUX45qQChOgtm\nNU2od6f9tgyDFxN5KS8cE32NXV3rJXs3bBZmIKLSPETf3uIPuEpFPjpdR5v5jlV+\nTDjH4RrItE3hDCvypTXhXXMmWp3VfYbgEfIP03uR2iIhL+/g3BUqbrywPEsTViSN\nNM/uBDQyamXLXB1bQ2I/Ob41I82PD1iNCqGi7ZvZ3eVYGgUTQyw6Q4O8glTPP9cC\nSFVXwE9gHbLe8TqfTZCWrM6crGX6Bb6hV2tqNsA+7J69U9NGuw5GNqXjafU=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/rsa_private_b.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAyb1grrN+29fxeeEbTaSEja6TKDTpT/WXnqrFCS+h7IYcnDoA\nVwcsPU5FZeUPvLKMzi9NHSJ34LQCurqHgH8X+cw0YT3gdYS/7qoQiXs+zKv615Nc\nttD3xlQLceY+NwznoPXyyZwOeZqyU5Hiqbrqu6hdr6gQYogMNLn2NxBW2pGegd6+\nZGMCX3+/BtMP/6tXmttYjY+yhN2SrGz5cKhWpcHiC6X+B7uCKoKZy+t2jUxYVKUw\nWr1ZuM8kpSnuVCcv1OoMGEimEHA7v/eaF/y+z/VdQ4Y88GhTnVN4KbtgZ+o9Pohj\nxLFU62VeTALixU5mPQKSgSICKfjev0FUUurF6wIDAQABAoIBAQCs11C/PM/iYNfl\nmSSAWAStMrWni/Wc6QhXC2422ZV8hMZ8XwEtjtqrR6UTkLXz8HHMsR/7Zy2X2gJA\no1E2mS0ceoUiDxaA+RRL0W7Lq0j5qBsImZukkdLHG/iWRDJnjenhsParHsYUD6Lb\nELFGw/safjyOI4quMGtsvSqisKAJL5ZCPD5JHLhnNP8HXT6icSZrsqGhunb2tsa+\nOgcx1+bZzqdTsbvXdbw07Lnd/LRU0NDhjeEVl4J2yFNYY+OIj7/qrxSnZnGLLG0Y\nDFxiD+HCMvTBSooqvWI6FAipfyCGjUznGsVaRv7TuzHPuKE4LtbIC/Ac3Q10rKWq\nPmHALir5AoGBAOhGUCToWfYnj2zH0GIZQxnkrv9iRqmdGeCDX6ZM00Bs5tASnRo0\no90UtLbhWjHe1PKRKFyD4I7a8iIWxcWWun2XHgOtItctPN+lbjpTHTyE2yA1iZhe\ndKCV3bAo4t+puKrPkZmaBqFD/fQx7DNxYdRERa1giiZGhlMUN3l7/S21AoGBAN5Y\nnZ68NkTgklk4YBzsxwsMpQbgbihyG79gtDFxWonxZUQ29EsL01yd30pJNhg1LxDN\n0fADfHVzkZ3qYz9knge9a75Yk8UBM3DM+xu+DRkjKhK5mPX5oLvj6061u3Scs6tj\norpU/mV1amz5gqrkefMaelsdHRuGGZQVx9KTV2kfAoGBAN7EAL1E8nK4Qj/r6xkK\nbWZ6ArQABxFJELZYiPWvnLOfPka0c2PctIOmBiOXQa+urMDvIqyH9mhL6Al1mbwE\n8VreAfU4qb+BLW649FyPteyC5r2fWxV9EZGp6fG3ZM9psShw5o1QQaeM1BTNhGFa\nDp9L0x+TBSvsW4t2SjYDCjA5AoGASzxxGWVWd7gFzWrmGuOD9pkwvkLzA3yZJwjx\n8EkK+eJVAeAWic5WluBUzi43v7k/U9BRWYXUd2nDvEuziZ/iWXwfGSmf1umxHlo+\nHgURKZBcjDmBKLpvSSS2WsvjwnHD2hq81ZAtBOfWO0myjWECYuByxqHzV3zo6tLz\n6q0wxsECgYA26twPrAoRqvfvPnNj6o0LrsE39Tj6jHIVijT7Lbcf2xVnaDiQ18PQ\nRC6Tgkz5KZf8GKfMRMA3WopGn9QE2luI4RLIbhLozEDrkk2L7wSYqI9DZ1Hd26wf\nv3+3jdpsXkzHwWYz1a2+FhCF5mJJRQl6kd/B0wu00vdfwviK9OVO7w==\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/rsa_private_encrypted.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: AES-256-CBC,DB3D20E60E8FDC3356BD79712FF8EF7E\n\nK+vu0U3IFTJBBi6zW5Zng80O1jXq/ZmlOFs/j/SQpPwfW1Do9i/Dwa7ntBlTwrCm\nsd3IIPgu2ikfLwxvbxsZN540oCaCqaZ/bmmyzH3MyVDA9MllUu+X8+Q3ATzcYa9R\nU5XfF5DAXsSRnstCbmKagWVQpO0oX8k3ratfny6Ixq86Y82tK8+o5YiBFq1kqa+9\n4yat7IWQbqV5ifUtUPCHZwEqBt+WKazX05BqERjkckHdpfaDrBvSSPXTwoLm6uRR\nktkUVpO4tHMZ4VlcTfFtpz8gdYYod0nM6vz26hvbESHSwztSgMhmKdsE5eqmYfgu\nF4WkEN4bqAiPjKK3jnUKPt/vg2oKYFQlVYFl9QnBjiRqcQTi3e9lwn1hI7uoMb6g\nHuaCc57JJHPN/ZLP3ts4ZxFbwUjTGioh5Zh6WozG3L3+Ujwq/sDrAskRyzdcuP7I\nRs3oLbHY03OHyg8IbxR5Iu89l6FLqnR45yvbxXtZ7ImGOPM5Z9pB1CzDhGDx2F6g\nJ/Kf/7ZF2DmYUVbVKDfESEDhRfuMAVzhasDPTRqipSA5QvJVQY+J/6QDPrNNmHVB\n4e4ouHIDWERUf0t1Be7THvP3X8OJozj2HApzqa5ZCaJDo8eaL8TCD5uH75ID5URJ\nVscGHaUXT8/sxfHi1x8BibW5W5J/akFsnrnJU/1BZgGznIxjf5tKfHGppSIVdlKP\n3ghYNmEIFPNJ6cxuUA0D2IOV4uO3FTCU6seIzvJhYkmXnticcZYGtmGxXKrodtzS\nJ1YuaNkkO/YRZah285lQ6QCIhCFo4Oa4ILjgoTQISuw7nQj5ESyncauzLUBXKX0c\nXDUej64KNTvVF9UXdG48fYvNmSZWCnTye4UmPu17FmwpVra38U+EdoLyWyMIAI5t\nrP6Hhgc9BxOo41Im9QpTcAPfKAknP8Rbm3ACJG5T9FKq/c29d1E//eFR6SL51e/a\nyWdCgJN/FJOAX60+erPwoVoRFEttAeDPkklgFGdc8F4LIYAig9gEZ92ykFFz3fWz\njIcUVLrL+IokFbPVUBoMihqVyMQsWH+5Qq9wjxf6EDIf0BVtm9U4BJoOkPStFIfF\nKof7OVv7izyL8R/GIil9VQs9ftwkIUPeXx2Hw0bE3HJ3C8K4+mbLg3tKhGnBDU5Z\nXm5mLHoCRBa3ZRFWZtigX7POszdLAzftYo8o65Be4OtPS+tQAORk9gHsXATv7dDB\nOGw61x5KA55LHVHhWaRvu3J8E7nhxw0q/HskyZhDC+Y+Xs6vmQSb4nO4ET4NYX1P\nm3PMdgGoqRDJ2jZw4eoQdRKCM0EHSepSAYpO1tcAXhPZS4ITogoRgPpVgOebEQUL\nnKNeNu/BxMSH/IH15jjDLF3TiEoguF9xdTaCxIBzE1SFpVO0u9m9vXpWdPThVgsb\nVcEI487p7v9iImP3BYPT8ZYvytC26EH0hyOrwhahTvTb4vXghkLIyvPUg1lZHc6e\naPHb2AzYAHLnp/ehDQGKWrCOJ1JE2vBv8ZkLa+XZo7YASXBRZitPOMlvykEyzxmR\nQAmNhKGvFmeM2mmHAp0aC03rgF3lxNsXQ1CyfEdq3UV9ReSnttq8gtrJfCwxV+wY\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/rsa_private_pkcs8.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC33FiIiiexwLe/\nP8DZx5HsqFlmUO7/lvJ7necJVNwqdZ3ax5jpQB0p6uxfqeOvzcN3k5V7UFb/Am+n\nkSNZMAZhsWzCU2Z4Pjh50QYz3f0Hour7/yIGStOLyYY3hgLK2K8TbhgjQPhdkw9+\nQtKlpvbL8fLgONAoGrVOFnRQGcr70iFffsm79mgZhKVMgYiHPJqJgGHvCtkGg9zM\ngS7p63+Q3ZWedtFS2RhMX3uCBy/mH6EOlRCNBbRmA4xxNzyf5GQaki3T+Iz9tOMj\ndPP+CwV2LqEdylmBuik8vrfTb3qIHLKKBAI8lXN26wWtA3kN4L7NP+cbKlCRlqct\nvhmylLH1AgMBAAECggEBAJLZ6ti7yDKgY+LcT/NiBDqKyEUBlbMNZIW5vAPnBKbh\nJIDO9WIv9Fs7qSpLbnFHnr0OYtGIfMPXtUiYkyw0QJSc+upHZMvbno4llpes0eHc\njWVTBWETON4oywvj/Kz53vRc9eiKhxVuVWyagNcQgYSprjzLA+9UTcWeB67Guyrf\n8YJUE2LC23RiMA5nGYoSHfVRl0c75gj7A0X9nwpAI+xw3kcaVHRIhA6WowA3Pj1o\npK2t692+NLVRylpvMMSS4rziDexomFykCFukYWYB/kZOOSSETSsTWoMXXl1KqsoZ\n8IW06NR4rXtIgQ3sTfbYKGZNF5nWFgZ+hJVx0We1Qg0CgYEA8UovlB4nrBm7xH+u\n7XXBMbqxADQm5vaEZxw9eluc+tP7cIAI4sglMIvL/FMpbd2pEeP/BkR76NTDzzDu\nPAZvUGRavgEjy0O9j2NAs/WPK4tZF+vFdunhnSh4EHAF4Ij9kbsUi90NOpbGfVqP\ndOaHqzgHKoR23Cuusk9wFQ2XTV8CgYEAwxHdEYT9xrpfrHPqSBQPpO0dWGKJEkrW\nOb+76rSfuL8wGR4OBNmQdhLuU9zTIh22pog+XPnLPAecC+4yu/wtJ2SPCKiKDbJB\nre0CKPyRfGqzvA3njXwMxXazU4kGs+2Fg+xu/iKbaIjxXrclBLhkxhBtySrwAFhx\nxOk6fFcPLSsCgYEAqS/Mdr5CMRGGMH0bKhPUWEtAixUGZhJaunX5wY71Xoc/Gh4c\nnO+b7BNJ/+5L8WZog0vr6PgiLhrqBaCYm2wjpyoG2o2wDHm+NAlzN/wp3G2EFhrS\nxdOux+S1c0kpRcyoiAO2n29rNDa+jOzwBBcU8ACEPdLOCQl0IEFFJO33tl8CgYBY\nDOIqnEsovsucvh3MNzHwkg8i7CdPGHSmUIN0J9/ItpPxYn2VdtccVOM6+3xZ8+uU\nM/9iXGZ+TDkFsZk4/VUsaNmfYOQf1oyLA2ZsNcU90bQbeHNCi/H/19qOJFXgNaCE\nsd5P3DMl9lptFGIjRVBHjvbfTQBUR5fi+BusMGfrTQKBgQCTtzMEJP2sef883AJr\nXuGVPLzwLi9eTBvPzc5r5pfkvh7mDDmWFxHZm5kctvavqgy32uUPsQgMi1Kz67bU\ns5dY9MCVrN2elhTLD8LOiAz8836o3AxFefm5cUWGaU/aZWDYR0QtNqFdyHyRaodo\nJJfnfK+oK1Eq7+PvpXfVN9BkYw==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/rsa_private_pkcs8_bad.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC33FiIiiexwLe/\nP8DZx5HsqFlmUO7/lvJ7necJVNwqdZ3ax5jpQB0p6uxfqeOvzcN3k5V7UFb/Am+n\nkSNZMAZhsWzCU2Z4Pjh50QYz3f0Hour7/yIGStOLyYY3hgLK2K8TbhgjQPhdkw9+\nQtKlpvbL8fLgONAoGrVOFnRQGcr70iFffsm79mgZhKVMgYiHPJqJgGHvCtkGg9zM\ngS7p63+Q3ZWedtFS2RhMX3uCBy/mH6EOlRCNBbRmA4xxNzyf5GQaki3T+Iz9tOMj\ndPP+CwV2LqEdylmBuik8vrfTb3qIHLKKBAI8lXN26wWtA3kN4L7NP+cbKlCRlqct\nvhmylLH1AgMBAAECggEBAJLZ6ti7yDKgY+LcT/NiBDqKyEUBlbMNZIW5vAPnBKbh\nJIDO9WIv9Fs7qSpLbnFHnr0OYtGIfMPXtUiYkyw0QJSc+upHZMvbno4llpes0eHc\njWVTBWETON4oywvj/Kz53vRc9eiKhxVuVWyagNcQgYSprjzLA+9UTcWeB67Guyrf\n8YJUE2LC23RiMA5nGYoSHfVRl0c75gj7A0X9nwpAI+xw3kcaVHRIhA6WowA3Pj1o\npK2t692+NLVRylpvMMSS4rziDexomFykCFukYWYB/kZOOSSETSsTWoMXXl1KqsoZ\n8IW06NR4rXtIgQ3sTfbYKGZNF5nWFgZ+hJVx0We1Qg0CgYEA8UovlB4nrBm7xH+u\n7XXBMbqxADQm5vaEZxw9eluc+tP7cIAI4sglMIvL/FMpbd2pEeP/BkR76NTDzzDu\nPAZvUGRavgEjy0O9j2NAs/WPK4tZF+vFdunhnSh4EHAF4Ij9kbsUi90NOpbGfVqP\ndOaHqzgHKoR23Cuusk9wFQ2XTV8CgYEAwxHdEYT9xrpfrHPqSBQPpO0dWGKJEkrW\nOb+76rSfuL8wGR4OBNmQdhLuU9zTIh22pog+XPnLPAecC+4yu/wtJ2SPCKiKDbJB\nre0CKPyRfGqzvA3njXwMxXazU4kGs+2Fg+xu/iKbaIjxXrclBLhkxhBtySrwAFhx\nxOk6fFcPLSsCgYEAqS/Mdr5CMRGGMH0bKhPUWEtAixUGZhJaunX5wY71Xoc/Gh4c\nnO+b7BNJ/+5L8WZog0vr6PgiLhrqBaCYm2wjpyoG2o2wDHm+NAlzN/wp3G2EFhrS\nxdOux+S1c0kpRcyoiAO2n29rNDa+jOzwBBcU8ACEPdLOCQl0IEFFJO33tl8CgYBY\nDOIqnEsovsucvh3MNzHwkg8i7CdPGHSmUIN0J9/ItpPxYn2VdtccVOM6+3xZ8+uU\nM/9iXGZ+TDkFsZk4/VUsaNmfYOQf1oyLA2ZsNcU90bQbeHNCi/H/19qOJFXgNaCE\nsd5P3DMl9lptFGIjRVBHjvbfTQBUR5fi+BusMGfrTQKBgQCTtzMEJP2sef883AJr\nXuGVPLzwLi9eTBvPzc5r5pfkvh7mDDmWFxHZm5kctvavqgy32uUPsQgMi1Kz67bU\ns5dY9MCVrN2elhTLD8LOiAz8836o3AxFefm5cUWGaU/aZWDYR0QtNqFdyHyRaodo\nJJfnfK+oK1Eq7+PvpXfVN9BkYw==\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/rsa_pss_private_2048.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADALBgkqhkiG9w0BAQoEggSoMIIEpAIBAAKCAQEAy4OMdS84PlgI5CRL\nbdbud9Ru7vprFr2YNNUmdT7D3YgApiv8CjzKXLiVDnbMET+lwmtag/EcZsxVCKov\nsu30pYASBriHOiMVYui9+ZaJoQ9yI6lOjG1RbuUBJXNSjHBJxqBqmcgZOb1mdRr/\neXzpAMWJ3hfuLojU2+zUSJ3/rvepepcLFG2q9nA0+PJskJ7Pnh3L0ydnv3U3hduM\nn5OVfm/Jx1FPyZpD184tJff+N+MY3s3hIcfuOnL9Pl4RPGeaTC4T1o460NaG6bG7\nc2Whg6NOaVgaFIaiNbrTTNCpVjeTyalsTXYlQQ3hiKjst0Q7pfFEkJDo8qiqLad1\nMsl59wIDAQABAoIBAQC6G8aqs0/f02nuGDLSc6cH9kCsUlz0ItW6GuJcfdVoFSNi\n0v5d7lGwkSveWk0ryOSw8rOHzUqHx3xLvDZ6jpkXcBMMClu/kq3QEb8JK90YaKOc\ncQvf52h83Pc7ZEatH1KYTcKudwp6fvXfSZ0vYEdD6WG2tHOgIollxSIsdjCHs1qi\n7baNHdK9T4DveuEZNcZ+LraZ1haHmFeqIPcy+KvpGuTaLCg5FPhH2jsIkw9apr7i\niFLi+IJ7S5Bn/8XShmJWk4hPyx0jtIkC5r2iJnHf4x+XYWZfdo7oew3Dg6Pa7T6r\nI164Nnaw0u0LvO4gQdvYaJ/j9A602nHTp7Tsq8chAoGBAOtVHgIqpmdzwR5KjotW\nLuGXDdO9X6Xfge9ca2MlWH1jOj+zqEV7JtrjnZAzzOgP2kgqzpIR71Njs8wkaxTJ\nTle0Ke6R/ghU9YOQgRByKjqJfQXHZnYFPsMg0diNYLroJ4SG8LO4+2SygTYZ4eKL\nqU0bda3QvQ7FL+rTNQBy01b9AoGBAN1jEQI80JxCT7AMvXE6nObIhbkASHte4yOE\n1CBwcOuBEGcuvMOvQVMzKITgea6+kgsu4ids4dM5PTPapQgpKqIIQ2/eSesaf56g\n73clGGSTPHJP0v+EfKg4+GYJf8o2swT0xDHkgWLgjjdsncB9hATc2j6DvHeau18d\npgCLz9kDAoGAXl/SGvhTp2UqayVnKMW1I07agrGNLA4II5+iiS4u4InskCNSNhr/\nKATj6TJ82AuTdCGGmdmLapuvPQzVzI42VsGvlzcA8wJvOwW2XIwMF1GPy8N9eZL8\n6m+89+Uqh4oWXvVmjgx+9JEJdFLI3Xs4t+1tMfll+AhoAPoWZUmnK1kCgYAvEBxR\niXQfg8lE97BeHcO1G/OxfGnsMCPBLT+bFcwrhGhkRv9B6kPM2BdJCB9WEpUhY3oY\nP4FSUdy85UIoFfhGMdOEOJEmNZ/jrPq7LVueJd63vlhwkU2exV2o82QDLNWpvA7p\nPFZ1Gp+hEKoIfaZPElQi7gZmtrIWaksb2pz42QKBgQCct9NP2qJfqeS4206RDnfv\nM238/O2lNhLWdSwY0g+tcN+I1sGs3+4vvrm95cxwAmXZyIM11wjdMcAPNxibodY7\nvufsebPHDBA0j0yuTjGkXefUKd1GdO88i5fppzxB7prDX9//DsWWrFhIMMRNYe0Q\naeHd/NPuHcjZKcnaVBgukQ==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/rsa_pss_private_2048_sha1_sha1_20.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQowAASCBKcwggSjAgEAAoIBAQCpdutzsPFQ1100\nouR5aAwYry8aAtG0c+zX9UqNXGCpRDWzPPpXHUZSB1BmTTL4EhK2tkAfblYNqzRu\nCAYlKHbFpFLs2zLEorfp0WsFNPaBHE9JHpLIM4oXxPCUypZ7JAn56ZYonYCZ8Il5\n8SzD9aoF41RTEmpcx3XkL2RQa022RiSccYZKx/yzskUUAdTvTvYyujH1MkvsfVP+\nNs5bRL6IVqowFd3xv6ctvfQMxz0rltgTC+wOm3CFtn+G63y6P/Z0U2DRdacsNkN6\nPFGXAIB0kSvKzs8gVocEBiSwMkcT/KD3R68PY18b2auqaGcm8gA+gaVJ36KAW4dO\nAjbY+YitAgMBAAECggEAfPvfFXln0Ra1gE+vMDdjzITPuWBg57Uj9fbMIEwEYnKT\nJHmRrNRDe9Y3HuxK7hjuQmFSE5xdzUD6rzgtyBP63TOfkV7tJ4dXGxS/2JxCPeDy\nPNxWp18Ttwoh4as0pudikDYN8DCRm3eC/TO5r2EtH6CVHZuUZI8bTMsDMiihrQ8F\nB8+KucBG5DDy/OlDeieAZxZA4Y0/c+W0DNZ/LIPGwaqMzYCSZJXyV0t33HytUwM2\nQZ+RbWqcUcrCI3lFAO8IyEULCi+RnSByZeJ0xwUkdQTI5jT6+G8BrO70Oiab8g+Q\nRx2s7PxWpIMVS7/JD1PsL4hLrVh3uqh8PZl3/FG9IQKBgQDZWkOR2LA+ixmD6XJb\nQ+7zW2guHnK6wDrQFKmBGLaDdAER64WL1Unt6Umu7FPxth2niYMEgRexBgnj5hQN\nLfPYTiIeXs5ErrU96fVQABsV0Hra1M2Rhve5nynjFFpbHjDXtizzLpE30MsC7YkN\nEqD4YYzjWHrbk/UlQ7tx3eAvtQKBgQDHmNM4TRuyH2yaYxDqnho6fgJv7Z4KgbM0\n1wcUxi5kPDQsFtaVOzFhNserzsWvotQjLkC2+CK5qlCdm59ZlpUqszF6+YyUs5Gq\nWmHdqryduT1VxSV/pd6wGEQo27fxFV7LsT1JhVMh9Iri8MK0b1BD6+kVUf5NcKDB\nOd2o8A1gGQKBgA5Y3Pj1mrymJesFL91CYLWDpR7WN7CIG9m8Y2v4G6QVtjRenZQb\nYiPoMErxoqDj6pUyiIl1lADFa0W13ED6dYwjrDDhBTCXb7NEjELZnvATsOhc/6zJ\ngfSowvUQVN6K4aJ7jgAHZOKQT7ZDw7YvMpzyo4AmSQXRgG8TR34+rRu5AoGACApP\n9+SjSPmbFl0HQWw9Aj4xOvEHfMTcwzQmRN/23nLOZzhETJ6lzpS2VmVt8TVN9lzW\nnohAXdpOhQrP0HwQZjfxtlJ3J0ZUh9g8OQG3t2LO5bWbXRkBb3aKyFqRflSuDOaG\n4X9NagC/14R7U2loglPuf71d0SDIWQBLvZJt94ECgYEAnY7aKHnWdLszcB8uyEkJ\nEJkUEaa+K/nTqOzqffZ01cTWJmUG7a2KuvQ+UQM2BHk2+wBmUo45Iz/dyePOJY0B\nFu2agiV4+R4z2XVQnIvXgY5HaPxvLz0THksY/pD58gBmFaLMx4ADEwQ+s4Y2g12H\nABsKNRHfSnKTwOm/dYvcVqs=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/rsa_pss_private_2048_sha256_sha256_16.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIE7wIBADA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEaMBgGCSqGSIb3\nDQEBCDALBglghkgBZQMEAgGiAwIBEASCBKkwggSlAgEAAoIBAQDfqNM4C+QtD73i\nILqOkqfV8ha3O19jpX8UujIk1Z72bbbuwEzh0+sBw0dD0N8CgkXnePOEEd6q7HNm\nbyCNqRpDK6NDvaCMDWgEaD/PlHkRntvKh81IXSMC5imjRfOcZIE/Gnw7h8tanab0\nn75+ODvLJrmEWUG2q79Im1mWMx7Spod+Np6XEY+7I7nAUUWivr35Yx5DeyxY8rxF\nGpsLtGsi7JNQO4aHyeBpj8tz0Fhv23uPywE2nGmPHfnkXWbrTcHGbzYBgEbeSH9K\nUkRwczqDXNOPhtfaEHEFTm0MoeKCnJe1VOjSywev77dV1KZfpVh3Kh0ZRQIe9YOV\nJhj4lMx3AgMBAAECggEBAIc+IgK5Bg/NfgeXvNdrjPuM+PlxeHvb3h1dfebSGd5v\nd3elZpgDug6F07kJO2Db/4M5mx7YY2m9swZU2j1u7MeDQqU6rDMkBCruEu/lmtPx\n2Hv+ZD6Gux4MqU7mhKmkCJds34Rr16aCwCsZ0WmnfViZoQKLqnXYIsG31pNBdDjx\ngke0HhX1LkA9yTVwlk8xOaHPqI4KfsFAyoiiHzyttGDexzb1PzmM0pybAPDMhpN/\nwXp2kLjyzmUmPe2Y2yva69WVWo7qS6joKjY75MQ1t20HYgEL69IApvCPu4CANfi9\nj3FAaV/+WrnhKCi6QyUi5PCI/+AJLsjNQmqTXIdBEoECgYEA+XsgFbeZ6+ZzEHa7\nHyFH6kiyBLd0q7w+ZLPsoOmEApDaP3yXSC7eJU7M/tPUPj8VQMMSK2D6fgmUDwhb\n3mEXFZxf67UlPFsFjweYcBihwy4r8QKBwury6dEbHPSUq4mXFJF5XRQdGqRGkr/F\n8OLZ0MwmHLUzczA67PxP/wF8TsECgYEA5YD4RxxJJYfAk1rFbqHRhNB8UeYVL+Wo\nwsRET1JeFg+S5grLGUga+KBB8Jn7Ahaip7MVE0Iud1cgaDi4WBEJsbJ6oJTlHJEg\nJq7QAaBafwjeSCSnaEsVBVNvriy2WF7uAomLSKmW6uSUOBBFFt4+G+akG56EfOPc\n7YKBfsf5ITcCgYBvjVZzX307tfeNTQmuibsWTxsKcN2CTNG5RZpw+PlGDG8KJDOg\n2xQJqoqPBzjH/H0MUC03qE1ZPf8uGZa6gL9JsnpRctYLfselhMfsl5b9JxAO3AgZ\nl+S2GAH/mH1BlmwvjjyuGehJmVrVE1r2sviiHCaOf5dZ0h8HCGrco1VqAQKBgQCf\nfYkMofOTSUvjG2mpAHuCOQCsSaDfsFIfSBXQqgUIf7ouc8HAyAM2VOh+NAPj56cR\ns7opsAxqkvnKc+BoEy8Rdl8RyWeO+qvFNicHelBph9gxeod8SvFIyjsKZ7gwoYf1\n63AIBxMCGeeHLodU5Q10hkv1hau8vv2BcPhdCstu8QKBgQDgO4Rr36Pa5rjbNbGN\nPsJqALjthTeU0yaU8hdC7Hc7B/T6npEIhw3s1eNq7e0eeDltqz7tDnY8qysazbQZ\np1cV5TJ8+gtwGmDoADBnU1NXqX4Squfml6OhNEucpTdjux7JdLVmmQFraOT2Eu5f\n9uuNtA+d8uhBEXhskuvEC552ug==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/rsa_pss_private_2048_sha512_sha256_20.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIE5wIBADA4BgkqhkiG9w0BAQowK6ANMAsGCWCGSAFlAwQCA6EaMBgGCSqGSIb3\nDQEBCDALBglghkgBZQMEAgEEggSmMIIEogIBAAKCAQEAvM9NgMCDqy5dqj5Ua2cZ\ncc4zVr+fCF34bZn63OBeYG8RTJKM3j36lO/yamtfDctDhb87b45CS6ipEr8J57I9\nWF55TNngsn6GNpXgwAe0eFXUnKonuqnGEC7x8r3vkAg99PBKhAtFc5oTaaZDAFKM\nzc5dIC/J0Y+kxhqjCPNI0ydQgZmKBrmYjM9cvbOYgRQL10GrWeJ+XHk2E33endaF\n+4dwjgyrzInt/l6OTkiCL8F59J/htk1GPru9BT6w5yOS/vH6q6FD6uizULVznytd\nlHjgnrVaHJmsqVjrYQa9OAZj9GBrTelBWvQ9b6+FBHUFHBp8HSp82lWCZThPrcZ/\nQwIDAQABAoIBADDzUfWic8CKuc/sbviVdzxRKHBCJ9oEeub3d9mR9gXsZcDDcfAg\ng3nfp6q9gZxS6YOga6llaXyyEnuAufGu/UaO38Xz6tR8BxHZ07YViU11ezTOzJQR\ndf82HJZBdf2SlXWOYtNPFMd+16+ZYl+QB19INE6m9Rz2r9KIj2I/qM7NPPVhDRF0\nG4O0Yf2vaPhjoIaewn7xtQ6wmX7pAGcd8dmYEIGGkBi8aY3BVwrRK1X4AmD+oSmE\nwXqR6MQIzD4KdypL4UD1Cb4GoFeVRclXvegOG+EKl0iD+mjTodB4yjoJh98NYe3+\nGtpR/2u3Ltq8RqWpIg8ryShQk/MIqGJ5KpkCgYEA80uNEYWlBt6QGNvVIYrhnw+V\n2nLJWedioKV4H1sr9OLF/7WFOUfsaflpVybnmwfNV15lEyHb/m/sCM9jTrNk1t/q\nqhRnvtmy3kntxWBeoA2o0TRg/XZKWjabZsr/4UE/Ztws2opOvl9x05IYeZlU+PbZ\nB1lX2e+vtMOpllvRr28CgYEAxqtfrYv8brp/fAUqGu/MtdHxQdU1+vE+auN17gam\nZM6ojIeasX4k2z0Rd/+8Dga9wPgO7hjtFZ2NWD5UwfBiw5U2PVZ6bp3iKSBPGHEh\nRsIR+nw8pFIgsQKoYnVK58tEnfQ31GSupKpYybHbaL5SdId+mfXU86SbKX/MefZX\ng20CgYBjn8hAKI2O5ovy4fHALnJ9A5DFRsOUgN8uERPDIz44pLOXJelLr1vreSnd\nehzUqrk20Xxp/S9sXMA2S1XK4EKmikI5KungiJxp0bP/Yprcxzsdj2k34LxJfJrd\n2Lo2rtUbdYUYaBIeek7N58EF6feVit8L11XV9APq7UQAQdD3GQKBgBmxuGIdpLw9\napeDo3pwYS1yxZ0aEi0uXkA8wtfSDFslTy89qogiJGomb8fxT0URIiF+849fseoF\nwm4TQasDiAJ7ndQ5BwSfbsya3R/wIbmhB+o5fy5RYOED0vtI6DMqWumC2GWjz+KE\nFY+gbRwS4V8o1vrajHwmYdrwKGXtskvRAoGADe/EdlCj98r6Zle9giF1C0GDIDTx\n8bR2m+Ogh9ZO218GpDMNaQ/WmbYVPDYMbRSOEA7y2gVkd1OeYhJMF6aYffp3aWhN\nt9g0uojLY0jfEpWBLBQdlYOTl7hOnLWRRcOAKTlHVg+OuD/O/GmdQ2Rg2H/hAWlI\nmuuTQPuQTCV1aTc=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/rsa_pss_public_2048.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMIIBIDALBgkqhkiG9w0BAQoDggEPADCCAQoCggEBAMuDjHUvOD5YCOQkS23W7nfU\nbu76axa9mDTVJnU+w92IAKYr/Ao8yly4lQ52zBE/pcJrWoPxHGbMVQiqL7Lt9KWA\nEga4hzojFWLovfmWiaEPciOpToxtUW7lASVzUoxwScagapnIGTm9ZnUa/3l86QDF\nid4X7i6I1Nvs1Eid/673qXqXCxRtqvZwNPjybJCez54dy9MnZ791N4XbjJ+TlX5v\nycdRT8maQ9fOLSX3/jfjGN7N4SHH7jpy/T5eETxnmkwuE9aOOtDWhumxu3NloYOj\nTmlYGhSGojW600zQqVY3k8mpbE12JUEN4Yio7LdEO6XxRJCQ6PKoqi2ndTLJefcC\nAwEAAQ==\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/rsa_pss_public_2048_sha1_sha1_20.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQowAAOCAQ8AMIIBCgKCAQEAqXbrc7DxUNddNKLkeWgM\nGK8vGgLRtHPs1/VKjVxgqUQ1szz6Vx1GUgdQZk0y+BIStrZAH25WDas0bggGJSh2\nxaRS7NsyxKK36dFrBTT2gRxPSR6SyDOKF8TwlMqWeyQJ+emWKJ2AmfCJefEsw/Wq\nBeNUUxJqXMd15C9kUGtNtkYknHGGSsf8s7JFFAHU7072Mrox9TJL7H1T/jbOW0S+\niFaqMBXd8b+nLb30DMc9K5bYEwvsDptwhbZ/hut8uj/2dFNg0XWnLDZDejxRlwCA\ndJErys7PIFaHBAYksDJHE/yg90evD2NfG9mrqmhnJvIAPoGlSd+igFuHTgI22PmI\nrQIDAQAB\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/rsa_pss_public_2048_sha256_sha256_16.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMIIBUjA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEaMBgGCSqGSIb3DQEB\nCDALBglghkgBZQMEAgGiAwIBEAOCAQ8AMIIBCgKCAQEA36jTOAvkLQ+94iC6jpKn\n1fIWtztfY6V/FLoyJNWe9m227sBM4dPrAcNHQ9DfAoJF53jzhBHequxzZm8gjaka\nQyujQ72gjA1oBGg/z5R5EZ7byofNSF0jAuYpo0XznGSBPxp8O4fLWp2m9J++fjg7\nyya5hFlBtqu/SJtZljMe0qaHfjaelxGPuyO5wFFFor69+WMeQ3ssWPK8RRqbC7Rr\nIuyTUDuGh8ngaY/Lc9BYb9t7j8sBNpxpjx355F1m603Bxm82AYBG3kh/SlJEcHM6\ng1zTj4bX2hBxBU5tDKHigpyXtVTo0ssHr++3VdSmX6VYdyodGUUCHvWDlSYY+JTM\ndwIDAQAB\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/rsa_pss_public_2048_sha512_sha256_20.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMIIBTTA4BgkqhkiG9w0BAQowK6ANMAsGCWCGSAFlAwQCA6EaMBgGCSqGSIb3DQEB\nCDALBglghkgBZQMEAgEDggEPADCCAQoCggEBALzPTYDAg6suXao+VGtnGXHOM1a/\nnwhd+G2Z+tzgXmBvEUySjN49+pTv8mprXw3LQ4W/O2+OQkuoqRK/CeeyPVheeUzZ\n4LJ+hjaV4MAHtHhV1JyqJ7qpxhAu8fK975AIPfTwSoQLRXOaE2mmQwBSjM3OXSAv\nydGPpMYaowjzSNMnUIGZiga5mIzPXL2zmIEUC9dBq1niflx5NhN93p3WhfuHcI4M\nq8yJ7f5ejk5Igi/BefSf4bZNRj67vQU+sOcjkv7x+quhQ+ros1C1c58rXZR44J61\nWhyZrKlY62EGvTgGY/Rga03pQVr0PW+vhQR1BRwafB0qfNpVgmU4T63Gf0MCAwEA\nAQ==\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/rsa_public.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt9xYiIonscC3vz/A2ceR\n7KhZZlDu/5bye53nCVTcKnWd2seY6UAdKersX6njr83Dd5OVe1BW/wJvp5EjWTAG\nYbFswlNmeD44edEGM939B6Lq+/8iBkrTi8mGN4YCytivE24YI0D4XZMPfkLSpab2\ny/Hy4DjQKBq1ThZ0UBnK+9IhX37Ju/ZoGYSlTIGIhzyaiYBh7wrZBoPczIEu6et/\nkN2VnnbRUtkYTF97ggcv5h+hDpUQjQW0ZgOMcTc8n+RkGpIt0/iM/bTjI3Tz/gsF\ndi6hHcpZgbopPL630296iByyigQCPJVzdusFrQN5DeC+zT/nGypQkZanLb4ZspSx\n9QIDAQAB\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/rsa_public_2048.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArk4OqxBqU5/k0FoUDU7C\npZpjz6YJEXUpyqeJmFRVZPMUv/Rc7U4seLY+Qp6k26T/wlQ2WJWuyY+VJcbQNWLv\njJWks5HWknwDuVs6sjuTM8CfHWn1960JkK5Ec2TjRhCQ1KJy+uc3GJLtWb4rWVgT\nbbaaC5fiR1/GeuJ8JH1Q50lB3mDsNGIk1U5jhNaYY82hYvlbErf6Ft5njHK0BOM5\nOTvQ6BBv7c363WNG7tYlNw1J40dup9OQPo5JmXN/h+sRbdgG8iUxrkRibuGv7loh\n52QQgq2snznuRMdKidRfUZjCDGgwbgK23Q7n8VZ9Y10j8PIvPTLJ83PX4lOEA37J\nlwIDAQAB\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/rsa_public_4096.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeStwofbjtZuol4lwKn1\nw08AzcSNLHfCqNFHa+W7er8is7LQsPljtPT4yn4lsao83ngHFvSC3tbMiRNDpUHY\nqH2wBuUkuOmCtYkZLi0307H0CwcVV6W5P3tNEt80IJ+PqlRxtTknezUtbOasbIi/\naornVWG+psgqDGrFZ4oTsWtiE0Svi7sDqN5E2dijmH/YYnlnwqszgzHdtAnARp1b\nG34E64sqWCmLoGCfPdHtym/CSdxOLOsDV15jrwODZQ/TJZ5thkwKZRxu7g9fwlhA\n1PiI5WDP4reXNaqa2bSgrzpAljQExYs4N0L7okSVOJQX9BEaoWtq8NLU8MpMdGoH\nNDU0Xr60Lfr58Z5qn8RGEvlTxoCbPJzPV2zgzD/lmEqft6NnfTclveA3sd8xSrOB\nUn4o3S8hS0b9Su7PBukHjM96/e0ReoIshSwXlQTLr2Ft8KwupyPm1ltNcTDtjqHc\nIWU6Bg+kPy9mxSVtGGZYAPtqGzNBA/m+oOja/OSPxAblPdln691DaDuZs5nuZCGw\nGcLaJWgiyoqvXAcyXDZFyH4OZZh8rsBLKbnFXHZ/ziG0cAozEygZEPJappw8Lx/a\ndy7WL/SJjxooiKapc7Bnfy8eSLV3+XAKxhLW/MQ6ChJ+e/8ExAY02ca4MpCvqwIk\n9TfV6FM8pWGqHzQFj0v3NL0CAwEAAQ==\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/rsa_public_b.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyb1grrN+29fxeeEbTaSE\nja6TKDTpT/WXnqrFCS+h7IYcnDoAVwcsPU5FZeUPvLKMzi9NHSJ34LQCurqHgH8X\n+cw0YT3gdYS/7qoQiXs+zKv615NcttD3xlQLceY+NwznoPXyyZwOeZqyU5Hiqbrq\nu6hdr6gQYogMNLn2NxBW2pGegd6+ZGMCX3+/BtMP/6tXmttYjY+yhN2SrGz5cKhW\npcHiC6X+B7uCKoKZy+t2jUxYVKUwWr1ZuM8kpSnuVCcv1OoMGEimEHA7v/eaF/y+\nz/VdQ4Y88GhTnVN4KbtgZ+o9PohjxLFU62VeTALixU5mPQKSgSICKfjev0FUUurF\n6wIDAQAB\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/tls-nodejs-tcp-server.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDezCCAmOgAwIBAgIUOZ1aoT1hFpxtdlX7/X2MuUBy800wDQYJKoZIhvcNAQEL\nBQAwTTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCk5ldyBKZXJzZXkxFDASBgNVBAcM\nC0plcnNleSBDaXR5MRMwEQYDVQQKDApDbG91ZGZsYXJlMB4XDTI1MDMwNTE2MTQ0\nM1oXDTI3MDMwNTE2MTQ0M1owTTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCk5ldyBK\nZXJzZXkxFDASBgNVBAcMC0plcnNleSBDaXR5MRMwEQYDVQQKDApDbG91ZGZsYXJl\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3Stx2JU+aQiervTNSNYB\n2T4ptflc2hS5Mmr31GXxnluBSUQxWh+nuI3/mDyqnU9VQrgNroIEFxIs8ZqNfsL/\nchxBefFeWAtxozOS67GESFUcKpSV1ckp7OUws5eqJ6wYYJvelO1D0bsvwMLWzb1s\nVd0w4VdiXAqXmcM1myWoUlt/JWKsPeVyYQEH6nTYyoFAaakMtr7W26I6RQLN1N8u\nAqqUphYYdecThUrvb5oijNAFBkrrHKqZ6gDbj0xO6sEely+6igB5zIdT+gy5IpU5\nhFeseCoR4fYsE6AdaUGtmOeyf1g8TQL/2ClZs3X0zg/k0pvOtbwVqH0NdRwuUHXI\nUwIDAQABo1MwUTAdBgNVHQ4EFgQUZZIBpnC7Wc1U1LtDL3WCPCNoMAkwHwYDVR0j\nBBgwFoAUZZIBpnC7Wc1U1LtDL3WCPCNoMAkwDwYDVR0TAQH/BAUwAwEB/zANBgkq\nhkiG9w0BAQsFAAOCAQEAfm4Obn2+OvAY+o2texwWf+UBq3OxQNE1dZFZ7pkCW+7C\nsqAGp4lAS/j4oZhquDNmgbD7cIi/O1FoiRK+9uii6i7sNO7zCbNj0/Ui2sKKFbd/\n5tqsCmDM44nQgGpdx1G0DrSD6cKrf7fLkYpsCXuH004qxGGKdwQwe/u6cyZC1om0\niXzRwYzjU2WfZ63WG5N0ZGl/msHpANGa6avzzXs9FedUuh+HI7oPTr02V669Yv9j\nw2dd27kqELbrVgUORv5+0YFpPXCieRaHvw93mZIrbo4o7JB23ynoTx+s1qt7NMQT\nu74LuGoAw86vDqbIwh+3TrIDRtPOoAV/38k4YUdc2A==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/x25519_private.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VuBCIEIJi/yFpueUawC1BkXyWM8ONIBGFjL7UZHrD/Zo/KPDpn\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/x25519_public.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VuAyEAaSb8Q+RndwfNnPeOYGYPDUN3uhAPnMLzXyfi+mqfhig=\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/x448_private.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMEYCAQAwBQYDK2VvBDoEOLTDbazv6vHZWOmODQ3kk8TUOQgApB4j75rpInT5zSLl\n/xJHK8ixF7f+4uo+mGTCrK1sktI5UmCZ\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fixtures/x448_public.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMEIwBQYDK2VvAzkAioHSHVpTs6hMvghosEJDIR7ceFiE3+Xccxati64oOVJ7NWjf\nozE7ae31PXIUFq6cVYgvSKsDFPA=\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-access-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport { ok, rejects, strictEqual, throws } from 'node:assert';\n\n// Tests node:fs access and exists APIs\n// These tests are not a straight up port of the Node.js tests but have\n// been adapted and verified to match the behavior of the Node.js APIs\n// as closely as possible.\n\nimport {\n  accessSync,\n  existsSync,\n  access,\n  exists,\n  promises,\n  constants,\n  fstatSync,\n  closeSync,\n  openSync,\n  writeSync,\n  writeFileSync,\n  readSync,\n} from 'node:fs';\n\nconst { F_OK, R_OK, W_OK, X_OK } = constants;\n\nstrictEqual(typeof accessSync, 'function');\nstrictEqual(typeof access, 'function');\nstrictEqual(typeof existsSync, 'function');\nstrictEqual(typeof exists, 'function');\nstrictEqual(typeof promises, 'object');\nstrictEqual(typeof promises.access, 'function');\n\nstrictEqual(typeof F_OK, 'number');\nstrictEqual(typeof R_OK, 'number');\nstrictEqual(typeof W_OK, 'number');\nstrictEqual(typeof X_OK, 'number');\nstrictEqual(F_OK, 0);\nstrictEqual(X_OK, 1);\nstrictEqual(W_OK, 2);\nstrictEqual(R_OK, 4);\n\nconst kKnownPaths = [\n  { path: '/', writable: false },\n  { path: '/bundle', writable: false },\n  { path: '/bundle/worker', writable: false },\n  { path: '/dev', writable: false },\n  { path: '/dev/null', writable: true },\n  { path: '/dev/zero', writable: true },\n  { path: '/dev/full', writable: true },\n  { path: '/dev/random', writable: true },\n  { path: '/tmp', writable: true },\n];\n\nconst UV_ENOENT = -2;\n\nconst kNoEntError = {\n  code: 'ENOENT',\n  errno: UV_ENOENT,\n  syscall: 'access',\n};\n\nconst kInvalidArgTypeError = { code: 'ERR_INVALID_ARG_TYPE' };\nconst kOutOfRangeError = { code: 'ERR_OUT_OF_RANGE' };\n\nfunction mustNotCall() {\n  throw new Error('This function should not be called');\n}\n\nexport const accessInputValidationTest = {\n  async test() {\n    // For both sync and async callback versions, input validation errors\n    // are thrown synchronously.\n    [\n      123n,\n      {\n        [Symbol.toPrimitive]() {\n          return R_OK;\n        },\n      },\n      '/',\n    ].forEach((i) => {\n      throws(() => accessSync('/', i), kInvalidArgTypeError);\n      throws(() => access('/', i, mustNotCall), kInvalidArgTypeError);\n      throws(() => access('/', F_OK, i), kInvalidArgTypeError);\n    });\n    throws(() => accessSync(123), kInvalidArgTypeError);\n    throws(() => access(123, '', mustNotCall), kInvalidArgTypeError);\n\n    // Mode is out of range\n    throws(() => accessSync('/', -1), kOutOfRangeError);\n    throws(\n      () => accessSync('/', (F_OK | R_OK | W_OK | X_OK) + 1),\n      kOutOfRangeError\n    );\n\n    // For the promise version, input validation errors cause rejected promises.\n    await rejects(promises.access('/', 123n), kInvalidArgTypeError);\n    await rejects(promises.access(123), kInvalidArgTypeError);\n    await rejects(promises.access('/', -1), kOutOfRangeError);\n    await rejects(\n      promises.access('/', (F_OK | R_OK | W_OK | X_OK) + 1),\n      kOutOfRangeError\n    );\n  },\n};\n\nexport const accessSyncTest = {\n  test() {\n    kKnownPaths.forEach(({ path, writable }) => {\n      const bufferPath = Buffer.from(path);\n      const urlPath = new URL(path, 'file:///');\n      accessSync(path);\n      accessSync(bufferPath);\n      accessSync(urlPath);\n      accessSync(path, F_OK);\n      accessSync(bufferPath, F_OK);\n      accessSync(urlPath, F_OK);\n      accessSync(path, R_OK);\n      accessSync(bufferPath, R_OK);\n      accessSync(urlPath, R_OK);\n\n      // Some paths are writable (W_OK)\n      if (writable) {\n        accessSync(path, W_OK);\n        accessSync(bufferPath, W_OK);\n        accessSync(urlPath, W_OK);\n      } else {\n        throws(() => accessSync(path, W_OK), kNoEntError);\n        throws(() => accessSync(bufferPath, W_OK), kNoEntError);\n        throws(() => accessSync(urlPath, W_OK), kNoEntError);\n      }\n\n      // No path is executable (X_OK)\n      throws(() => accessSync(path, X_OK), kNoEntError);\n      throws(() => accessSync(bufferPath, X_OK), kNoEntError);\n      throws(() => accessSync(urlPath, X_OK), kNoEntError);\n    });\n\n    // Paths that don't exist...\n    throws(() => accessSync('/does/not/exist'), kNoEntError);\n    throws(() => accessSync('/does/not/exist', F_OK), kNoEntError);\n    throws(() => accessSync('/does/not/exist', R_OK), kNoEntError);\n    throws(() => accessSync('/does/not/exist', W_OK), kNoEntError);\n    throws(() => accessSync('/does/not/exist', X_OK), kNoEntError);\n  },\n};\n\nexport const accessCallbackTest = {\n  async test() {\n    const doTest = (path, mode) => {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      access(path, mode, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n      return promise;\n    };\n\n    const cases = [];\n    kKnownPaths.forEach(({ path, writable }) => {\n      const bufferPath = Buffer.from(path);\n      const urlPath = new URL(path, 'file:///');\n      cases.push(\n        doTest(path, F_OK),\n        doTest(bufferPath, F_OK),\n        doTest(urlPath, F_OK),\n        doTest(path, R_OK),\n        doTest(bufferPath, R_OK),\n        doTest(urlPath, R_OK)\n      );\n      if (writable) {\n        cases.push(\n          doTest(path, W_OK),\n          doTest(bufferPath, W_OK),\n          doTest(urlPath, W_OK)\n        );\n      } else {\n        cases.push(\n          rejects(doTest(path, W_OK), kNoEntError),\n          rejects(doTest(bufferPath, W_OK), kNoEntError),\n          rejects(doTest(urlPath, W_OK), kNoEntError)\n        );\n      }\n      // No paths are executable (X_OK)\n      cases.push(\n        rejects(doTest(path, X_OK), kNoEntError),\n        rejects(doTest(bufferPath, X_OK), kNoEntError),\n        rejects(doTest(urlPath, X_OK), kNoEntError)\n      );\n    });\n    // Paths that don't exist have no permissions.\n    cases.push(\n      rejects(doTest('/does/not/exist', F_OK), kNoEntError),\n      rejects(doTest('/does/not/exist', R_OK), kNoEntError),\n      rejects(doTest('/does/not/exist', W_OK), kNoEntError),\n      rejects(doTest('/does/not/exist', X_OK), kNoEntError)\n    );\n    strictEqual(cases.length, kKnownPaths.length * 12 + 4);\n    await Promise.all(cases);\n  },\n};\n\nexport const accessPromiseTest = {\n  async test() {\n    const cases = [];\n    kKnownPaths.forEach(({ path, writable }) => {\n      const bufferPath = Buffer.from(path);\n      const urlPath = new URL(path, 'file:///');\n      cases.push(\n        promises.access(path, F_OK),\n        promises.access(bufferPath, F_OK),\n        promises.access(urlPath, F_OK),\n        promises.access(path, R_OK),\n        promises.access(bufferPath, R_OK),\n        promises.access(urlPath, R_OK)\n      );\n      if (writable) {\n        cases.push(\n          promises.access(path, W_OK),\n          promises.access(bufferPath, W_OK),\n          promises.access(urlPath, W_OK)\n        );\n      } else {\n        cases.push(\n          rejects(promises.access(path, W_OK), kNoEntError),\n          rejects(promises.access(bufferPath, W_OK), kNoEntError),\n          rejects(promises.access(urlPath, W_OK), kNoEntError)\n        );\n      }\n      // No paths are executable (X_OK)\n      cases.push(\n        rejects(promises.access(path, X_OK), kNoEntError),\n        rejects(promises.access(bufferPath, X_OK), kNoEntError),\n        rejects(promises.access(urlPath, X_OK), kNoEntError)\n      );\n    });\n    // Paths that don't exist have no permissions.\n    cases.push(\n      rejects(promises.access('/does/not/exist', F_OK), kNoEntError),\n      rejects(promises.access('/does/not/exist', R_OK), kNoEntError),\n      rejects(promises.access('/does/not/exist', W_OK), kNoEntError),\n      rejects(promises.access('/does/not/exist', X_OK), kNoEntError)\n    );\n    strictEqual(cases.length, kKnownPaths.length * 12 + 4);\n    await Promise.all(cases);\n  },\n};\n\nexport const existsSyncTest = {\n  test() {\n    kKnownPaths.forEach(({ path }) => {\n      const bufferPath = Buffer.from(path);\n      const urlPath = new URL(path, 'file:///');\n      ok(existsSync(path));\n      ok(existsSync(bufferPath));\n      ok(existsSync(urlPath));\n    });\n\n    // Paths that don't exist\n    ok(!existsSync('/does/not/exist'));\n    // Incorrect inputs types results in false returned\n    ok(!existsSync(123));\n  },\n};\n\nexport const existsCallbackTest = {\n  async test() {\n    const doTest = (path, expected) => {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      exists(path, (exists) => {\n        if (exists !== expected)\n          reject(new Error(`Expected ${path} to be ${expected}`));\n        else resolve();\n      });\n      return promise;\n    };\n\n    const cases = [];\n    kKnownPaths.forEach(({ path }) => {\n      const bufferPath = Buffer.from(path);\n      const urlPath = new URL(path, 'file:///');\n      cases.push(\n        doTest(path, true),\n        doTest(bufferPath, true),\n        doTest(urlPath, true)\n      );\n    });\n    // Paths that don't exist\n    cases.push(doTest('/does/not/exist', false));\n    // Incorrect inputs types results in false returned\n    cases.push(doTest(123, false));\n\n    strictEqual(cases.length, kKnownPaths.length * 3 + 2);\n    await Promise.all(cases);\n  },\n};\n\nexport const stdioFds = {\n  test() {\n    let stat1 = fstatSync(0);\n    let stat2 = fstatSync(1);\n    let stat3 = fstatSync(2);\n    ok(stat1.isFile());\n    ok(stat2.isFile());\n    ok(stat3.isFile());\n    closeSync(0);\n    closeSync(1);\n    closeSync(2);\n    // After closing the stdio file descriptors, they can still be used.\n    // That is, we don't actually close them.\n    stat1 = fstatSync(0);\n    stat2 = fstatSync(1);\n    stat3 = fstatSync(2);\n    ok(stat1.isFile());\n    ok(stat2.isFile());\n    ok(stat3.isFile());\n\n    // Writing to the stdio file descriptors is permitted, always in append mode.\n    // Position is ignored.\n    writeSync(0, 'Hello, stdin!\\n', 1000);\n    writeSync(0, 'test', 500);\n    writeSync(1, 'Hello, stdin!\\n', 1000);\n    writeSync(1, 'test', 500);\n    writeSync(2, 'Hello, stdin!\\n', 1000);\n    writeSync(2, 'test', 500);\n\n    stat1 = fstatSync(0);\n    strictEqual(stat1.size, 0);\n\n    const buf = Buffer.alloc(20);\n    const bytesRead = readSync(0, buf, { position: 1000 });\n\n    const fd = openSync('/tmp/test.txt', 'w');\n    strictEqual(fd, 3);\n  },\n};\n// There is no promise version of exists in Node.js, so no test for it here.\n\nexport const fsPercentEncodingTest = {\n  test() {\n    // The ? and # in the string path will be percent-encoded as part of the\n    // normalization, and the empty // in the path will be replaced as /.\n    // the %FF is left as is.\n    const path = '/tmp//?#a%FF';\n    ok(!existsSync('/tmp/%3F%23a%FF'));\n    writeFileSync(path, 'test');\n\n    // The casing of the percent-encoded characters is not significant.\n    ok(existsSync('/tmp/%3f%23a%FF'));\n    ok(existsSync('/tmp/%3F%23a%ff'));\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-access-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"fs-access-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"fs-access-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_fs_module\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-chown-chmod-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport { rejects, strictEqual, throws } from 'node:assert';\n\n// Tests node:fs chmod and chown APIS\n// These tests are not a straight up port of the Node.js tests but have\n// been adapted and verified to match the behavior of the Node.js APIs\n// as closely as possible.\n\n// Our implementation of these APIs is mostly a non-op. The most we do\n// is validate the input arguments and verify that the file exists and\n// is accessible. The actual chown and chmod operations are not performed\n// since we do not have a concept of users, groups, or posix-style permissions.\n\nimport {\n  openSync,\n  closeSync,\n  statSync,\n  fstatSync,\n  lstatSync,\n  symlinkSync,\n  chmod,\n  lchmod,\n  fchmod,\n  chmodSync,\n  lchmodSync,\n  fchmodSync,\n  chown,\n  lchown,\n  fchown,\n  chownSync,\n  lchownSync,\n  fchownSync,\n  promises,\n} from 'node:fs';\n\nstrictEqual(typeof openSync, 'function');\nstrictEqual(typeof closeSync, 'function');\nstrictEqual(typeof statSync, 'function');\nstrictEqual(typeof fstatSync, 'function');\nstrictEqual(typeof lstatSync, 'function');\nstrictEqual(typeof symlinkSync, 'function');\nstrictEqual(typeof chmod, 'function');\nstrictEqual(typeof lchmod, 'function');\nstrictEqual(typeof fchmod, 'function');\nstrictEqual(typeof chmodSync, 'function');\nstrictEqual(typeof lchmodSync, 'function');\nstrictEqual(typeof fchmodSync, 'function');\nstrictEqual(typeof chown, 'function');\nstrictEqual(typeof lchown, 'function');\nstrictEqual(typeof fchown, 'function');\nstrictEqual(typeof chownSync, 'function');\nstrictEqual(typeof lchownSync, 'function');\nstrictEqual(typeof fchownSync, 'function');\nstrictEqual(typeof promises.chown, 'function');\nstrictEqual(typeof promises.lchown, 'function');\n\nconst kInvalidArgTypeError = { code: 'ERR_INVALID_ARG_TYPE' };\nconst kOutOfRangeError = { code: 'ERR_OUT_OF_RANGE' };\n\nfunction checkStat(path) {\n  const { uid, gid } = statSync(path);\n  strictEqual(uid, 0);\n  strictEqual(gid, 0);\n}\n\nfunction checkfStat(fd) {\n  const { uid, gid } = fstatSync(fd);\n  strictEqual(uid, 0);\n  strictEqual(gid, 0);\n}\n\nconst path = '/tmp';\nconst bufferPath = Buffer.from(path);\nconst urlPath = new URL(path, 'file:///');\n\nexport const chownSyncTest = {\n  test() {\n    // Incorrect input types should throw.\n    throws(() => chownSync(123), kInvalidArgTypeError);\n    throws(() => chownSync('/', {}), kInvalidArgTypeError);\n    throws(() => chownSync('/', 0, {}), kInvalidArgTypeError);\n    throws(() => chownSync(path, -1000, 0), kOutOfRangeError);\n    throws(() => chownSync(path, 0, -1000), kOutOfRangeError);\n\n    // We stat the file before and after to verify the impact\n    // of the chown operation. Specifically, the uid and gid\n    // should not change since our impl is a non-op.\n    checkStat(path);\n    chownSync(path, 1000, 1000);\n    checkStat(path);\n\n    chownSync(bufferPath, 1000, 1000);\n    checkStat(bufferPath);\n\n    chownSync(urlPath, 1000, 1000);\n    checkStat(urlPath);\n\n    // A non-existent path should throw ENOENT\n    throws(() => chownSync('/non-existent-path', 1000, 1000), {\n      code: 'ENOENT',\n      // Access because it is an access check under the covers.\n      syscall: 'chown',\n    });\n  },\n};\n\nexport const chownCallbackTest = {\n  async test() {\n    // Incorrect input types should throw synchronously\n    throws(() => chown(123), kInvalidArgTypeError);\n    throws(() => chown('/', {}), kInvalidArgTypeError);\n    throws(() => chown('/', 0, {}), kInvalidArgTypeError);\n    throws(() => chownSync(path, -1000, 0), kOutOfRangeError);\n    throws(() => chownSync(path, 0, -1000), kOutOfRangeError);\n\n    async function callChown(path) {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      chown(path, 1000, 1000, (err) => {\n        if (err) return reject(err);\n        resolve();\n      });\n      await promise;\n    }\n\n    // Should be non-op\n    checkStat(path);\n    await callChown(path);\n    checkStat(path);\n\n    await callChown(bufferPath);\n    checkStat(bufferPath);\n\n    await callChown(urlPath);\n    checkStat(urlPath);\n\n    // A non-existent path should throw ENOENT\n    const { promise, resolve, reject } = Promise.withResolvers();\n    chown('/non-existent-path', 1000, 1000, (err) => {\n      if (err) return reject(err);\n      resolve();\n    });\n    await rejects(promise, {\n      code: 'ENOENT',\n      // Access because it is an access check under the covers.\n      syscall: 'chown',\n    });\n  },\n};\n\nexport const chownPromiseTest = {\n  async test() {\n    // Incorrect input types should reject the promise.\n    await rejects(promises.chown(123), kInvalidArgTypeError);\n    await rejects(promises.chown('/', {}), kInvalidArgTypeError);\n    await rejects(promises.chown('/', 0, {}), kInvalidArgTypeError);\n    await rejects(promises.chown(path, -1000, 0), kOutOfRangeError);\n    await rejects(promises.chown(path, 0, -1000), kOutOfRangeError);\n\n    // Should be non-op\n    checkStat(path);\n    await promises.chown(path, 1000, 1000);\n    checkStat(path);\n\n    await promises.chown(bufferPath, 1000, 1000);\n    checkStat(bufferPath);\n\n    await promises.chown(urlPath, 1000, 1000);\n    checkStat(urlPath);\n\n    // A non-existent path should throw ENOENT\n    await rejects(promises.chown('/non-existent-path', 1000, 1000), {\n      code: 'ENOENT',\n      // Access because it is an access check under the covers.\n      syscall: 'chown',\n    });\n  },\n};\n\nexport const lchownSyncTest = {\n  test() {\n    // Incorrect input types should throw.\n    throws(() => lchownSync(123), kInvalidArgTypeError);\n    throws(() => lchownSync('/', {}), kInvalidArgTypeError);\n    throws(() => lchownSync('/', 0, {}), kInvalidArgTypeError);\n    throws(() => lchownSync(path, -1000, 0), kOutOfRangeError);\n    throws(() => lchownSync(path, 0, -1000), kOutOfRangeError);\n\n    // We stat the file before and after to verify the impact\n    // of the chown operation. Specifically, the uid and gid\n    // should not change since our impl is a non-op.\n    checkStat(path);\n    lchownSync(path, 1000, 1000);\n    checkStat(path);\n\n    lchownSync(bufferPath, 1000, 1000);\n    checkStat(bufferPath);\n\n    lchownSync(urlPath, 1000, 1000);\n    checkStat(urlPath);\n\n    // A non-existent path should throw ENOENT\n    throws(() => lchownSync('/non-existent-path', 1000, 1000), {\n      code: 'ENOENT',\n      // Access because it is an access check under the covers.\n      syscall: 'lchown',\n    });\n  },\n};\n\nexport const lchownCallbackTest = {\n  async test() {\n    // Incorrect input types should throw synchronously\n    throws(() => lchown(123), kInvalidArgTypeError);\n    throws(() => lchown('/', {}), kInvalidArgTypeError);\n    throws(() => lchown('/', 0, {}), kInvalidArgTypeError);\n    throws(() => lchownSync(path, -1000, 0), kOutOfRangeError);\n    throws(() => lchownSync(path, 0, -1000), kOutOfRangeError);\n\n    async function callChown(path) {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      lchown(path, 1000, 1000, (err) => {\n        if (err) return reject(err);\n        resolve();\n      });\n      await promise;\n    }\n\n    // Should be non-op\n    checkStat(path);\n    await callChown(path);\n    checkStat(path);\n\n    await callChown(bufferPath);\n    checkStat(bufferPath);\n\n    await callChown(urlPath);\n    checkStat(urlPath);\n\n    // A non-existent path should throw ENOENT\n    const { promise, resolve, reject } = Promise.withResolvers();\n    lchown('/non-existent-path', 1000, 1000, (err) => {\n      if (err) return reject(err);\n      resolve();\n    });\n    await rejects(promise, {\n      code: 'ENOENT',\n      // Access because it is an access check under the covers.\n      syscall: 'lchown',\n    });\n  },\n};\n\nexport const lchownPromiseTest = {\n  async test() {\n    // Incorrect input types should reject the promise.\n    await rejects(promises.lchown(123), kInvalidArgTypeError);\n    await rejects(promises.lchown('/', {}), kInvalidArgTypeError);\n    await rejects(promises.lchown('/', 0, {}), kInvalidArgTypeError);\n    await rejects(promises.lchown(path, -1000, 0), kOutOfRangeError);\n    await rejects(promises.lchown(path, 0, -1000), kOutOfRangeError);\n\n    // Should be non-op\n    checkStat(path);\n    await promises.lchown(path, 1000, 1000);\n    checkStat(path);\n\n    await promises.lchown(bufferPath, 1000, 1000);\n    checkStat(bufferPath);\n\n    await promises.lchown(urlPath, 1000, 1000);\n    checkStat(urlPath);\n\n    // A non-existent path should throw ENOENT\n    await rejects(promises.lchown('/non-existent-path', 1000, 1000), {\n      code: 'ENOENT',\n      syscall: 'lchown',\n    });\n  },\n};\n\nexport const fchownSyncTest = {\n  test() {\n    // Incorrect input types should throw.\n    throws(() => fchownSync({}), kInvalidArgTypeError);\n    throws(() => fchownSync(123), kInvalidArgTypeError);\n    throws(() => fchownSync(123, {}), kInvalidArgTypeError);\n    throws(() => fchownSync(123, 0, {}), kInvalidArgTypeError);\n    throws(() => fchownSync(123, -1000, 0), kOutOfRangeError);\n    throws(() => fchownSync(123, 0, -1000), kOutOfRangeError);\n\n    const fd = openSync('/tmp');\n\n    // We stat the file before and after to verify the impact\n    // of the chown operation. Specifically, the uid and gid\n    // should not change since our impl is a non-op.\n    checkfStat(fd);\n    fchownSync(fd, 1000, 1000);\n    checkfStat(fd);\n\n    throws(() => fchownSync(999, 1000, 1000), {\n      code: 'EBADF',\n      syscall: 'fstat',\n    });\n\n    closeSync(fd);\n  },\n};\n\nexport const fchownCallbackTest = {\n  async test() {\n    // Incorrect input types should throw synchronously\n    throws(() => fchown({}), kInvalidArgTypeError);\n    throws(() => fchown(123), kInvalidArgTypeError);\n    throws(() => fchown(123, {}), kInvalidArgTypeError);\n    throws(() => fchown(123, 0, {}), kInvalidArgTypeError);\n    throws(() => fchown(123, -1000, 0), kOutOfRangeError);\n    throws(() => fchown(123, 0, -1000), kOutOfRangeError);\n\n    const fd = openSync('/tmp');\n\n    async function callChown() {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      fchown(fd, 1000, 1000, (err) => {\n        if (err) return reject(err);\n        resolve();\n      });\n      await promise;\n    }\n\n    // Should be non-op\n    checkfStat(fd);\n    await callChown();\n    checkfStat(fd);\n\n    const { promise, resolve, reject } = Promise.withResolvers();\n    fchown(999, 1000, 1000, (err) => {\n      if (err) return reject(err);\n      resolve();\n    });\n    await rejects(promise, {\n      code: 'EBADF',\n      syscall: 'fstat',\n    });\n\n    closeSync(fd);\n  },\n};\n\n// ===========================================================================\n\nexport const chmodSyncTest = {\n  test() {\n    // Incorrect input types should throw.\n    throws(() => chmodSync(123), kInvalidArgTypeError);\n    throws(() => chmodSync('/', {}), kInvalidArgTypeError);\n    throws(() => chmodSync('/tmp', -1), kOutOfRangeError);\n\n    // Should be non-op\n    checkStat(path);\n    chmodSync(path, 0o777);\n    checkStat(path);\n\n    chmodSync(bufferPath, 0o777);\n    checkStat(bufferPath);\n\n    chmodSync(urlPath, 0o777);\n    checkStat(urlPath);\n\n    throws(() => chmodSync('/non-existent-path', 0o777), {\n      code: 'ENOENT',\n      // Access because it is an access check under the covers.\n      syscall: 'chmod',\n    });\n  },\n};\n\nexport const chmodCallbackTest = {\n  async test() {\n    // Incorrect input types should throw.\n    throws(() => chmod(123), kInvalidArgTypeError);\n    throws(() => chmod('/', {}), kInvalidArgTypeError);\n    throws(() => chmod('/tmp', -1), kOutOfRangeError);\n\n    async function callChmod(path) {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      chmod(path, 0o000, (err) => {\n        if (err) return reject(err);\n        resolve();\n      });\n      await promise;\n    }\n\n    checkStat(path);\n    await callChmod(path);\n    checkStat(path);\n\n    await callChmod(bufferPath);\n    checkStat(bufferPath);\n\n    await callChmod(urlPath);\n    checkStat(bufferPath);\n\n    const { promise, resolve, reject } = Promise.withResolvers();\n    chmod('/non-existent-path', 0o777, (err) => {\n      if (err) return reject(err);\n      resolve();\n    });\n    await rejects(promise, {\n      code: 'ENOENT',\n      // Access because it is an access check under the covers.\n      syscall: 'chmod',\n    });\n  },\n};\n\nexport const chmodPromiseTest = {\n  async test() {\n    // Incorrect input types should reject the promise.\n    await rejects(promises.chmod(123), kInvalidArgTypeError);\n    await rejects(promises.chmod('/', {}), kInvalidArgTypeError);\n    await rejects(promises.chmod('/tmp', -1), kOutOfRangeError);\n\n    // Should be non-op\n    checkStat(path);\n    await promises.chmod(path, 0o777);\n    checkStat(path);\n\n    await promises.chmod(bufferPath, 0o777);\n    checkStat(bufferPath);\n\n    await promises.chmod(urlPath, 0o777);\n    checkStat(urlPath);\n\n    await rejects(promises.chmod('/non-existent-path', 0o777), {\n      code: 'ENOENT',\n      syscall: 'chmod',\n    });\n  },\n};\n\nexport const lchmodSyncTest = {\n  test() {\n    // Incorrect input types should throw.\n    throws(() => lchmodSync(123), kInvalidArgTypeError);\n    throws(() => lchmodSync('/', {}), kInvalidArgTypeError);\n    throws(() => lchmodSync('/tmp', -1), kOutOfRangeError);\n\n    // Should be non-op\n    checkStat(path);\n    lchmodSync(path, 0o777);\n    checkStat(path);\n\n    lchmodSync(bufferPath, 0o777);\n    checkStat(bufferPath);\n\n    lchmodSync(urlPath, 0o777);\n    checkStat(urlPath);\n\n    throws(() => lchmodSync('/non-existent-path', 0o777), {\n      code: 'ENOENT',\n      // Access because it is an access check under the covers.\n      syscall: 'lchmod',\n    });\n  },\n};\n\nexport const lchmodCallbackTest = {\n  async test() {\n    // Incorrect input types should throw.\n    throws(() => lchmod(123), kInvalidArgTypeError);\n    throws(() => lchmod('/', {}), kInvalidArgTypeError);\n    throws(() => lchmod('/tmp', -1), kOutOfRangeError);\n\n    async function callChmod(path) {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      lchmod(path, 0o000, (err) => {\n        if (err) return reject(err);\n        resolve();\n      });\n      await promise;\n    }\n\n    checkStat(path);\n    await callChmod(path);\n    checkStat(path);\n\n    await callChmod(bufferPath);\n    checkStat(bufferPath);\n\n    await callChmod(urlPath);\n    checkStat(bufferPath);\n\n    const { promise, resolve, reject } = Promise.withResolvers();\n    lchmod('/non-existent-path', 0o777, (err) => {\n      if (err) return reject(err);\n      resolve();\n    });\n    await rejects(promise, {\n      code: 'ENOENT',\n      // Access because it is an access check under the covers.\n      syscall: 'lchmod',\n    });\n  },\n};\n\nexport const lchmodPromiseTest = {\n  async test() {\n    // Incorrect input types should reject the promise.\n    await rejects(promises.lchmod(123), kInvalidArgTypeError);\n    await rejects(promises.lchmod('/', {}), kInvalidArgTypeError);\n    await rejects(promises.lchmod('/tmp', -1), kOutOfRangeError);\n\n    // Should be non-op\n    checkStat(path);\n    await promises.lchmod(path, 0o777);\n    checkStat(path);\n\n    await promises.lchmod(bufferPath, 0o777);\n    checkStat(bufferPath);\n\n    await promises.lchmod(urlPath, 0o777);\n    checkStat(urlPath);\n\n    await rejects(promises.lchmod('/non-existent-path', 0o777), {\n      code: 'ENOENT',\n      syscall: 'lchmod',\n    });\n  },\n};\n\nexport const fchmodSyncTest = {\n  test() {\n    // Incorrect input types should throw.\n    throws(() => fchmodSync({}), kInvalidArgTypeError);\n    throws(() => fchmodSync(123), kInvalidArgTypeError);\n    throws(() => fchmodSync(123, {}), kInvalidArgTypeError);\n    throws(() => fchmodSync(123, -1000), kOutOfRangeError);\n\n    const fd = openSync('/tmp');\n\n    // We stat the file before and after to verify the impact\n    // of the chown operation. Specifically, the uid and gid\n    // should not change since our impl is a non-op.\n    checkfStat(fd);\n    fchmodSync(fd, 0o777);\n    checkfStat(fd);\n\n    throws(() => fchmodSync(999, 0o777), {\n      code: 'EBADF',\n      syscall: 'fstat',\n    });\n\n    closeSync(fd);\n  },\n};\n\nexport const fchmodCallbackTest = {\n  async test() {\n    // Incorrect input types should throw synchronously\n    throws(() => fchmod({}), kInvalidArgTypeError);\n    throws(() => fchmod(123), kInvalidArgTypeError);\n    throws(() => fchmod(123, {}), kInvalidArgTypeError);\n\n    const fd = openSync('/tmp');\n\n    async function callChmod() {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      fchmod(fd, 0o777, (err) => {\n        if (err) return reject(err);\n        resolve();\n      });\n      await promise;\n    }\n\n    // Should be non-op\n    checkfStat(fd);\n    await callChmod();\n    checkfStat(fd);\n\n    const { promise, resolve, reject } = Promise.withResolvers();\n    fchmod(999, 0o777, (err) => {\n      if (err) return reject(err);\n      resolve();\n    });\n    await rejects(promise, {\n      code: 'EBADF',\n      syscall: 'fstat',\n    });\n\n    closeSync(fd);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-chown-chmod-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"fs-chown-chmod-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"fs-chown-chmod-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_fs_module\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-cp-into-subdirectory-test.js",
    "content": "import { cpSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { throws } from 'node:assert';\n\nexport const FsCpIntoSubdirectory = {\n  test() {\n    mkdirSync(new URL('file:///tmp/src'), { recursive: true });\n    writeFileSync(new URL('file:///tmp/src/file.txt'), 'test');\n\n    throws(\n      () =>\n        cpSync(new URL('file:///tmp/src'), new URL('file:///tmp/src/dest'), {\n          recursive: true,\n        }),\n      {\n        code: 'ERR_FS_CP_EINVAL',\n      }\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-cp-into-subdirectory-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"fs-cp-into-subdirectory-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"fs-cp-into-subdirectory-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_fs_module\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-cp-test.js",
    "content": "import {\n  writeFileSync,\n  mkdirSync,\n  symlinkSync,\n  cpSync,\n  cp,\n  existsSync,\n  readFileSync,\n  lstatSync,\n  statSync,\n  promises as fsPromises,\n} from 'node:fs';\n\nimport { ok, deepStrictEqual, strictEqual, throws, rejects } from 'node:assert';\n\nconst pathA = new URL('file:///tmp/a'); // File\nconst pathB = new URL('file:///tmp/b'); // Directory\nconst pathBE = new URL('file:///tmp/b/e'); // File in directory\nconst pathC = new URL('file:///tmp/c'); // Symlink to file\nconst pathD = new URL('file:///tmp/d'); // Symlink to directory\nconst pathE = new URL('file:///tmp/e'); // Directory\nconst pathF = new URL('file:///tmp/f'); // Path to copy to\n\nfunction setupPaths() {\n  writeFileSync(pathA, 'foo');\n  mkdirSync(pathB);\n  symlinkSync(pathA, pathC);\n  symlinkSync(pathB, pathD);\n  mkdirSync(pathE);\n  writeFileSync(pathBE, 'bar');\n}\n\nexport const simpleFileCopy = {\n  test() {\n    setupPaths();\n    ok(existsSync(pathA));\n    deepStrictEqual(readFileSync(pathA, 'utf8'), 'foo');\n    ok(!existsSync(pathF));\n    cpSync(pathA, pathF);\n    ok(existsSync(pathF));\n    deepStrictEqual(readFileSync(pathF, 'utf8'), 'foo');\n    writeFileSync(pathA, 'baz');\n    deepStrictEqual(readFileSync(pathA, 'utf8'), 'baz');\n    deepStrictEqual(readFileSync(pathF, 'utf8'), 'foo');\n    cpSync(pathA, pathF);\n    cpSync(pathA, pathF, { errorOnExist: true }); // force is true by default, weird but true.\n\n    // Copying a file when the destination already exists and force is false and\n    // errorOnExist is true, should throw an error.\n    throws(() => cpSync(pathA, pathF, { force: false, errorOnExist: true }), {\n      code: 'ERR_FS_CP_EEXIST',\n    });\n\n    // Copying a file to a directory should throw an error.\n    throws(() => cpSync(pathA, pathB), {\n      code: 'ERR_FS_CP_NON_DIR_TO_DIR',\n    });\n\n    // Copy a file to a symlink to a directory should throw by default\n    throws(() => cpSync(pathA, pathD, { dereference: true }), {\n      code: 'ERR_FS_CP_NON_DIR_TO_DIR',\n    });\n\n    ok(statSync(pathD).isDirectory());\n    ok(lstatSync(pathD).isSymbolicLink());\n    cpSync(pathA, pathD);\n    ok(lstatSync(pathD).isFile());\n  },\n};\n\n// Copy file to non-existent directory (should fail)\nexport const copyFileToNonExistentDirectory = {\n  test() {\n    setupPaths();\n    // The directory will be created by cpSync if it doesn't exist.\n    const pathG = new URL('file:///tmp/nonexistent');\n    const pathH = new URL('file:///tmp/nonexistent/g');\n    ok(!existsSync(pathG));\n    ok(!existsSync(pathH));\n    cpSync(pathA, pathH);\n    ok(existsSync(pathG));\n    ok(existsSync(pathH));\n  },\n};\n\n// Copy file to existing file with force\nexport const copyFileToExistingFileWithForce = {\n  test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    writeFileSync(pathG, 'original');\n    cpSync(pathA, pathG, { force: true });\n    deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');\n  },\n};\n\n// Copy file to existing file without force\nexport const copyFileToExistingFileWithoutForce = {\n  test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    writeFileSync(pathG, 'original');\n    // When force is false, it should not overwrite the existing file.\n    cpSync(pathA, pathG, { force: false });\n    deepStrictEqual(readFileSync(pathG, 'utf8'), 'original');\n  },\n};\n\n// Copy file to existing directory\nexport const copyFileToExistingDirectory = {\n  test() {\n    setupPaths();\n    throws(() => cpSync(pathA, pathB), {\n      code: 'ERR_FS_CP_NON_DIR_TO_DIR',\n    });\n  },\n};\n\n// Copy file to existing symlink to directory\nexport const copyFileToExistingSymlinkToDirectory = {\n  test() {\n    setupPaths();\n    // With dereference: true, should throw EISDIR\n    throws(() => cpSync(pathA, pathD, { dereference: true }), {\n      code: 'ERR_FS_CP_NON_DIR_TO_DIR',\n    });\n\n    // Without dereference (default), should replace the symlink\n    cpSync(pathA, pathD);\n    ok(lstatSync(pathD).isFile());\n    deepStrictEqual(readFileSync(pathD, 'utf8'), 'foo');\n  },\n};\n\n// Copy directory to non-existent directory\nexport const copyDirectoryToNonExistentDirectory = {\n  test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    cpSync(pathB, pathG, { recursive: true });\n    ok(existsSync(pathG));\n    ok(lstatSync(pathG).isDirectory());\n    ok(existsSync(new URL('file:///tmp/g/e')));\n    deepStrictEqual(readFileSync(new URL('file:///tmp/g/e'), 'utf8'), 'bar');\n  },\n};\n\n// Copy directory to existing directory\nexport const copyDirectoryToExistingDirectory = {\n  test() {\n    setupPaths();\n    cpSync(pathB, pathE, { recursive: true });\n    ok(existsSync(new URL('file:///tmp/e/e')));\n    deepStrictEqual(readFileSync(new URL('file:///tmp/e/e'), 'utf8'), 'bar');\n  },\n};\n\n// Copy directory to existing file\nexport const copyDirectoryToExistingFile = {\n  test() {\n    setupPaths();\n    throws(() => cpSync(pathB, pathA, { recursive: true }), {\n      code: 'ERR_FS_CP_DIR_TO_NON_DIR',\n    });\n  },\n};\n\n// Copy directory to existing symlink to file\nexport const copyDirectoryToExistingSymlinkToFile = {\n  test() {\n    setupPaths();\n    throws(() => cpSync(pathB, pathC, { recursive: true, dereference: true }), {\n      code: 'ERR_FS_CP_DIR_TO_NON_DIR',\n    });\n  },\n};\n\n// Copy symlink to file\nexport const copySymlinkToFile = {\n  test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n\n    // With dereference: false (default), should copy the symlink\n    cpSync(pathC, pathG);\n    ok(lstatSync(pathG).isSymbolicLink());\n    deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');\n\n    // With dereference: true, should copy the target file\n    const pathH = new URL('file:///tmp/h');\n    cpSync(pathC, pathH, { dereference: true });\n    ok(lstatSync(pathH).isFile());\n    deepStrictEqual(readFileSync(pathH, 'utf8'), 'foo');\n  },\n};\n\n// Copy symlink to directory\nexport const copySymlinkToDirectory = {\n  test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n\n    // With dereference: false (default), should copy the symlink\n    cpSync(pathD, pathG);\n    ok(lstatSync(pathG).isSymbolicLink());\n    ok(statSync(pathG).isDirectory());\n\n    // With dereference: true, should copy the target directory\n    const pathH = new URL('file:///tmp/h');\n    cpSync(pathD, pathH, { recursive: true, dereference: true });\n    ok(lstatSync(pathH).isDirectory());\n    ok(existsSync(new URL('file:///tmp/h/e')));\n  },\n};\n\n// Copy symlink to existing file\nexport const copySymlinkToExistingFile = {\n  test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    writeFileSync(pathG, 'original');\n\n    // Should replace the file with symlink\n    cpSync(pathC, pathG);\n    ok(lstatSync(pathG).isSymbolicLink());\n    deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');\n  },\n};\n\n// Copy symlink to existing directory\nexport const copySymlinkToExistingDirectory = {\n  test() {\n    setupPaths();\n    throws(() => cpSync(pathC, pathE), {\n      code: 'ENOTDIR',\n    });\n  },\n};\n\n// Copy symlink to existing symlink to directory\nexport const copySymlinkToExistingSymlinkToDirectory = {\n  test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    const pathH = new URL('file:///tmp/h');\n    mkdirSync(pathH);\n    symlinkSync(pathH, pathG);\n\n    // Should replace the symlink\n    cpSync(pathD, pathG);\n    ok(lstatSync(pathG).isSymbolicLink());\n    ok(statSync(pathG).isDirectory());\n  },\n};\n\n// Copy symlink operations with various option combinations\nexport const copySymlinkWithDereferenceOptions = {\n  test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    const pathH = new URL('file:///tmp/h');\n    const pathI = new URL('file:///tmp/i');\n\n    // Test with force: false, errorOnExist: true\n    symlinkSync(pathA, pathG);\n    throws(() => cpSync(pathC, pathG, { force: false, errorOnExist: true }), {\n      code: 'ERR_FS_CP_EEXIST',\n    });\n\n    // Test with force: false, errorOnExist: false\n    cpSync(pathC, pathG, { force: false, errorOnExist: false });\n    ok(lstatSync(pathG).isSymbolicLink());\n\n    // Test with dereference and errorOnExist combinations\n    writeFileSync(pathH, 'existing');\n    throws(\n      () =>\n        cpSync(pathC, pathH, {\n          dereference: true,\n          force: false,\n          errorOnExist: true,\n        }),\n      {\n        code: 'ERR_FS_CP_EEXIST',\n      }\n    );\n\n    // Test copying to non-existent path\n    cpSync(pathC, pathI);\n    ok(lstatSync(pathI).isSymbolicLink());\n    deepStrictEqual(readFileSync(pathI, 'utf8'), 'foo');\n  },\n};\n\n// Option validation tests\nexport const optionValidationTests = {\n  test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n\n    // Test invalid option types\n    throws(() => cpSync(pathA, pathG, { dereference: 'invalid' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    throws(() => cpSync(pathA, pathG, { errorOnExist: 'invalid' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    throws(() => cpSync(pathA, pathG, { force: 'invalid' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    throws(() => cpSync(pathA, pathG, { recursive: 'invalid' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    throws(() => cpSync(pathA, pathG, { preserveTimestamps: 'invalid' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    throws(() => cpSync(pathA, pathG, { verbatimSymlinks: 'invalid' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    // Test invalid filter type\n    throws(() => cpSync(pathA, pathG, { filter: 'not-a-function' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    // Test filter option throws unsupported operation\n    throws(() => cpSync(pathA, pathG, { filter: () => true }), {\n      code: 'ERR_UNSUPPORTED_OPERATION',\n    });\n\n    // Test COPYFILE_FICLONE_FORCE mode\n    throws(() => cpSync(pathA, pathG, { mode: 4 }), {\n      // COPYFILE_FICLONE_FORCE = 4\n      code: 'ERR_INVALID_ARG_VALUE',\n    });\n  },\n};\n\n// Recursive option tests\nexport const recursiveOptionTests = {\n  test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    const pathH = new URL('file:///tmp/h');\n\n    // Test copying directory without recursive option should fail\n    throws(() => cpSync(pathB, pathG), {\n      code: 'ERR_FS_EISDIR',\n    });\n\n    // Test with recursive: false explicitly\n    throws(() => cpSync(pathB, pathG, { recursive: false }), {\n      code: 'ERR_FS_EISDIR',\n    });\n\n    // Test copying directory with recursive: true should work\n    cpSync(pathB, pathG, { recursive: true });\n    ok(existsSync(pathG));\n    ok(lstatSync(pathG).isDirectory());\n    ok(existsSync(new URL('file:///tmp/g/e')));\n\n    // Test deep directory structure\n    mkdirSync(new URL('file:///tmp/deep'));\n    mkdirSync(new URL('file:///tmp/deep/nested'));\n    mkdirSync(new URL('file:///tmp/deep/nested/very'));\n    writeFileSync(new URL('file:///tmp/deep/nested/very/deep.txt'), 'content');\n\n    cpSync(new URL('file:///tmp/deep'), pathH, { recursive: true });\n    ok(existsSync(new URL('file:///tmp/h/nested/very/deep.txt')));\n    deepStrictEqual(\n      readFileSync(new URL('file:///tmp/h/nested/very/deep.txt'), 'utf8'),\n      'content'\n    );\n  },\n};\n\n// PreserveTimestamps option tests\nexport const preserveTimestampsTests = {\n  test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    const pathH = new URL('file:///tmp/h');\n\n    // Get original timestamps\n    const originalStat = lstatSync(pathA);\n    const originalMtime = originalStat.mtime;\n\n    // Copy without preserveTimestamps (default behavior)\n    cpSync(pathA, pathG);\n    const copiedStat = lstatSync(pathG);\n\n    // Copy with preserveTimestamps: true\n    cpSync(pathA, pathH, { preserveTimestamps: true });\n    const preservedStat = lstatSync(pathH);\n\n    // The preserved timestamps should match the original\n    // Note: This test depends on the implementation actually preserving timestamps\n    // Since the implementation is noted as having this option, we test it\n    ok(\n      Math.abs(preservedStat.mtime.getTime() - originalMtime.getTime()) < 1000\n    );\n  },\n};\n\n// Error handling edge cases\nexport const errorHandlingTests = {\n  test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    const nonExistent = new URL('file:///tmp/nonexistent');\n\n    // Test copying from non-existent source\n    throws(() => cpSync(nonExistent, pathG), {\n      code: 'ENOENT',\n    });\n  },\n};\n\n// Symlink edge cases\nexport const symlinkEdgeCases = {\n  test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    const pathH = new URL('file:///tmp/h');\n    const pathI = new URL('file:///tmp/i');\n    const pathJ = new URL('file:///tmp/j');\n\n    // Create a broken symlink\n    const brokenTarget = new URL('file:///tmp/broken-target');\n    symlinkSync(brokenTarget, pathG);\n\n    // Test copying broken symlink without dereference\n    cpSync(pathG, pathH);\n    ok(lstatSync(pathH).isSymbolicLink());\n\n    // Test copying broken symlink with dereference should fail\n    throws(() => cpSync(pathG, pathI, { dereference: true }), {\n      code: 'ENOENT',\n    });\n\n    // Create symlink chain: J -> I -> A\n    symlinkSync(pathA, pathI);\n    symlinkSync(pathI, pathJ);\n\n    // Test copying symlink chain\n    const pathK = new URL('file:///tmp/k');\n    cpSync(pathJ, pathK);\n    ok(lstatSync(pathK).isSymbolicLink());\n    deepStrictEqual(readFileSync(pathK, 'utf8'), 'foo');\n  },\n};\n\n// Combined options tests\nexport const combinedOptionsTests = {\n  test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n\n    // Test multiple options together\n    cpSync(pathB, pathG, {\n      recursive: true,\n      dereference: true,\n      force: true,\n      preserveTimestamps: true,\n      errorOnExist: false,\n    });\n\n    ok(existsSync(pathG));\n    ok(lstatSync(pathG).isDirectory());\n  },\n};\n\n// Path type tests\nexport const pathTypeTests = {\n  test() {\n    setupPaths();\n\n    // Test with Buffer paths\n    const pathG = new URL('file:///tmp/g');\n    const bufferSrc = Buffer.from('/tmp/a');\n    const bufferDest = Buffer.from('/tmp/g');\n\n    // Note: The implementation uses normalizePath which should handle Buffer inputs\n    // This tests the path normalization behavior\n    cpSync(bufferSrc, bufferDest);\n    ok(existsSync(pathG));\n    deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');\n  },\n};\n\n// ===== CALLBACK-BASED API TESTS =====\n\n// Simple file copy - callback version\nexport const simpleFileCopyCallback = {\n  async test() {\n    setupPaths();\n    ok(existsSync(pathA));\n    deepStrictEqual(readFileSync(pathA, 'utf8'), 'foo');\n    ok(!existsSync(pathF));\n\n    await new Promise((resolve, reject) => {\n      cp(pathA, pathF, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n\n    ok(existsSync(pathF));\n    deepStrictEqual(readFileSync(pathF, 'utf8'), 'foo');\n    writeFileSync(pathA, 'baz');\n    deepStrictEqual(readFileSync(pathA, 'utf8'), 'baz');\n    deepStrictEqual(readFileSync(pathF, 'utf8'), 'foo');\n\n    // Test with errorOnExist: true\n    await new Promise((resolve, reject) => {\n      cp(pathA, pathF, { errorOnExist: true }, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n\n    // Test error case: force: false, errorOnExist: true\n    await new Promise((resolve) => {\n      cp(pathA, pathF, { force: false, errorOnExist: true }, (err) => {\n        ok(err);\n        strictEqual(err.code, 'ERR_FS_CP_EEXIST');\n        resolve();\n      });\n    });\n\n    // Test error case: copying file to directory\n    await new Promise((resolve) => {\n      cp(pathA, pathB, (err) => {\n        ok(err);\n        strictEqual(err.code, 'ERR_FS_CP_NON_DIR_TO_DIR');\n        resolve();\n      });\n    });\n\n    // Test copying to symlink to directory\n    await new Promise((resolve) => {\n      cp(pathA, pathD, { dereference: true }, (err) => {\n        ok(err);\n        strictEqual(err.code, 'ERR_FS_CP_NON_DIR_TO_DIR');\n        resolve();\n      });\n    });\n\n    // Test copying to symlink without dereference\n    await new Promise((resolve, reject) => {\n      cp(pathA, pathD, (err) => {\n        if (err) reject(err);\n        else {\n          ok(lstatSync(pathD).isFile());\n          resolve();\n        }\n      });\n    });\n  },\n};\n\n// Copy file to non-existent directory - callback version\nexport const copyFileToNonExistentDirectoryCallback = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/nonexistent');\n    const pathH = new URL('file:///tmp/nonexistent/g');\n    ok(!existsSync(pathG));\n    ok(!existsSync(pathH));\n\n    await new Promise((resolve, reject) => {\n      cp(pathA, pathH, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n\n    ok(existsSync(pathG));\n    ok(existsSync(pathH));\n  },\n};\n\n// Copy file to existing file with force - callback version\nexport const copyFileToExistingFileWithForceCallback = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    writeFileSync(pathG, 'original');\n\n    await new Promise((resolve, reject) => {\n      cp(pathA, pathG, { force: true }, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n\n    deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');\n  },\n};\n\n// Copy file to existing file without force - callback version\nexport const copyFileToExistingFileWithoutForceCallback = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    writeFileSync(pathG, 'original');\n\n    await new Promise((resolve, reject) => {\n      cp(pathA, pathG, { force: false }, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n\n    deepStrictEqual(readFileSync(pathG, 'utf8'), 'original');\n  },\n};\n\n// Copy file to existing directory - callback version\nexport const copyFileToExistingDirectoryCallback = {\n  async test() {\n    setupPaths();\n\n    await new Promise((resolve) => {\n      cp(pathA, pathB, (err) => {\n        ok(err);\n        strictEqual(err.code, 'ERR_FS_CP_NON_DIR_TO_DIR');\n        resolve();\n      });\n    });\n  },\n};\n\n// Copy file to existing symlink to directory - callback version\nexport const copyFileToExistingSymlinkToDirectoryCallback = {\n  async test() {\n    setupPaths();\n\n    // With dereference: true, should throw EISDIR\n    await new Promise((resolve) => {\n      cp(pathA, pathD, { dereference: true }, (err) => {\n        ok(err);\n        strictEqual(err.code, 'ERR_FS_CP_NON_DIR_TO_DIR');\n        resolve();\n      });\n    });\n\n    // Without dereference (default), should replace the symlink\n    await new Promise((resolve, reject) => {\n      cp(pathA, pathD, (err) => {\n        if (err) reject(err);\n        else {\n          ok(lstatSync(pathD).isFile());\n          deepStrictEqual(readFileSync(pathD, 'utf8'), 'foo');\n          resolve();\n        }\n      });\n    });\n  },\n};\n\n// Copy directory to non-existent directory - callback version\nexport const copyDirectoryToNonExistentDirectoryCallback = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n\n    await new Promise((resolve, reject) => {\n      cp(pathB, pathG, { recursive: true }, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n\n    ok(existsSync(pathG));\n    ok(lstatSync(pathG).isDirectory());\n    ok(existsSync(new URL('file:///tmp/g/e')));\n    deepStrictEqual(readFileSync(new URL('file:///tmp/g/e'), 'utf8'), 'bar');\n  },\n};\n\n// Copy directory to existing directory - callback version\nexport const copyDirectoryToExistingDirectoryCallback = {\n  async test() {\n    setupPaths();\n\n    await new Promise((resolve, reject) => {\n      cp(pathB, pathE, { recursive: true }, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n\n    ok(existsSync(new URL('file:///tmp/e/e')));\n    deepStrictEqual(readFileSync(new URL('file:///tmp/e/e'), 'utf8'), 'bar');\n  },\n};\n\n// Copy directory to existing file - callback version\nexport const copyDirectoryToExistingFileCallback = {\n  async test() {\n    setupPaths();\n\n    await new Promise((resolve) => {\n      cp(pathB, pathA, { recursive: true }, (err) => {\n        ok(err);\n        strictEqual(err.code, 'ERR_FS_CP_DIR_TO_NON_DIR');\n        resolve();\n      });\n    });\n  },\n};\n\n// Copy directory to existing symlink to file - callback version\nexport const copyDirectoryToExistingSymlinkToFileCallback = {\n  async test() {\n    setupPaths();\n\n    await new Promise((resolve) => {\n      cp(pathB, pathC, { recursive: true, dereference: true }, (err) => {\n        ok(err);\n        strictEqual(err.code, 'ERR_FS_CP_DIR_TO_NON_DIR');\n        resolve();\n      });\n    });\n  },\n};\n\n// Copy symlink to file - callback version\nexport const copySymlinkToFileCallback = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n\n    // With dereference: false (default), should copy the symlink\n    await new Promise((resolve, reject) => {\n      cp(pathC, pathG, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n\n    ok(lstatSync(pathG).isSymbolicLink());\n    deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');\n\n    // With dereference: true, should copy the target file\n    const pathH = new URL('file:///tmp/h');\n    await new Promise((resolve, reject) => {\n      cp(pathC, pathH, { dereference: true }, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n\n    ok(lstatSync(pathH).isFile());\n    deepStrictEqual(readFileSync(pathH, 'utf8'), 'foo');\n  },\n};\n\n// Copy symlink to directory - callback version\nexport const copySymlinkToDirectoryCallback = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n\n    // With dereference: false (default), should copy the symlink\n    await new Promise((resolve, reject) => {\n      cp(pathD, pathG, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n\n    ok(lstatSync(pathG).isSymbolicLink());\n    ok(statSync(pathG).isDirectory());\n\n    // With dereference: true, should copy the target directory\n    const pathH = new URL('file:///tmp/h');\n    await new Promise((resolve, reject) => {\n      cp(pathD, pathH, { recursive: true, dereference: true }, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n\n    ok(lstatSync(pathH).isDirectory());\n    ok(existsSync(new URL('file:///tmp/h/e')));\n  },\n};\n\n// Copy symlink to existing file - callback version\nexport const copySymlinkToExistingFileCallback = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    writeFileSync(pathG, 'original');\n\n    // Should replace the file with symlink\n    await new Promise((resolve, reject) => {\n      cp(pathC, pathG, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n\n    ok(lstatSync(pathG).isSymbolicLink());\n    deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');\n  },\n};\n\n// Copy symlink to existing directory - callback version\nexport const copySymlinkToExistingDirectoryCallback = {\n  async test() {\n    setupPaths();\n\n    await new Promise((resolve) => {\n      cp(pathC, pathE, (err) => {\n        ok(err);\n        strictEqual(err.code, 'ENOTDIR');\n        resolve();\n      });\n    });\n  },\n};\n\n// Copy symlink to existing symlink to directory - callback version\nexport const copySymlinkToExistingSymlinkToDirectoryCallback = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    const pathH = new URL('file:///tmp/h');\n    mkdirSync(pathH);\n    symlinkSync(pathH, pathG);\n\n    // Should replace the symlink\n    await new Promise((resolve, reject) => {\n      cp(pathD, pathG, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n\n    ok(lstatSync(pathG).isSymbolicLink());\n    ok(statSync(pathG).isDirectory());\n  },\n};\n\n// Copy symlink operations with various option combinations - callback version\nexport const copySymlinkWithDereferenceOptionsCallback = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    const pathH = new URL('file:///tmp/h');\n    const pathI = new URL('file:///tmp/i');\n\n    // Test with force: false, errorOnExist: true\n    symlinkSync(pathA, pathG);\n    await new Promise((resolve) => {\n      cp(pathC, pathG, { force: false, errorOnExist: true }, (err) => {\n        ok(err);\n        strictEqual(err.code, 'ERR_FS_CP_EEXIST');\n        resolve();\n      });\n    });\n\n    // Test with force: false, errorOnExist: false\n    await new Promise((resolve, reject) => {\n      cp(pathC, pathG, { force: false, errorOnExist: false }, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n    ok(lstatSync(pathG).isSymbolicLink());\n\n    // Test with dereference and errorOnExist combinations\n    writeFileSync(pathH, 'existing');\n    await new Promise((resolve) => {\n      cp(\n        pathC,\n        pathH,\n        { dereference: true, force: false, errorOnExist: true },\n        (err) => {\n          ok(err);\n          strictEqual(err.code, 'ERR_FS_CP_EEXIST');\n          resolve();\n        }\n      );\n    });\n\n    // Test copying to non-existent path\n    await new Promise((resolve, reject) => {\n      cp(pathC, pathI, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n    ok(lstatSync(pathI).isSymbolicLink());\n    deepStrictEqual(readFileSync(pathI, 'utf8'), 'foo');\n  },\n};\n\n// Option validation tests - callback version\nexport const optionValidationTestsCallback = {\n  test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n\n    // Test invalid option types - these throw synchronously\n    throws(() => cp(pathA, pathG, { dereference: 'invalid' }, () => {}), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    throws(() => cp(pathA, pathG, { errorOnExist: 'invalid' }, () => {}), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    throws(() => cp(pathA, pathG, { force: 'invalid' }, () => {}), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    throws(() => cp(pathA, pathG, { recursive: 'invalid' }, () => {}), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    throws(\n      () => cp(pathA, pathG, { preserveTimestamps: 'invalid' }, () => {}),\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(() => cp(pathA, pathG, { verbatimSymlinks: 'invalid' }, () => {}), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    // Test invalid filter type - throws synchronously\n    throws(() => cp(pathA, pathG, { filter: 'not-a-function' }, () => {}), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    // Test filter option throws unsupported operation - throws synchronously\n    throws(() => cp(pathA, pathG, { filter: () => true }, () => {}), {\n      code: 'ERR_UNSUPPORTED_OPERATION',\n    });\n\n    // Test COPYFILE_FICLONE_FORCE mode - throws synchronously\n    throws(() => cp(pathA, pathG, { mode: 4 }, () => {}), {\n      code: 'ERR_INVALID_ARG_VALUE',\n    });\n  },\n};\n\n// Recursive option tests - callback version\nexport const recursiveOptionTestsCallback = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    const pathH = new URL('file:///tmp/h');\n\n    // Test copying directory without recursive option should fail\n    await new Promise((resolve) => {\n      cp(pathB, pathG, (err) => {\n        ok(err);\n        strictEqual(err.code, 'ERR_FS_EISDIR');\n        resolve();\n      });\n    });\n\n    // Test with recursive: false explicitly\n    await new Promise((resolve) => {\n      cp(pathB, pathG, { recursive: false }, (err) => {\n        ok(err);\n        strictEqual(err.code, 'ERR_FS_EISDIR');\n        resolve();\n      });\n    });\n\n    // Test copying directory with recursive: true should work\n    await new Promise((resolve, reject) => {\n      cp(pathB, pathG, { recursive: true }, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n\n    ok(existsSync(pathG));\n    ok(lstatSync(pathG).isDirectory());\n    ok(existsSync(new URL('file:///tmp/g/e')));\n\n    // Test deep directory structure\n    mkdirSync(new URL('file:///tmp/deep'));\n    mkdirSync(new URL('file:///tmp/deep/nested'));\n    mkdirSync(new URL('file:///tmp/deep/nested/very'));\n    writeFileSync(new URL('file:///tmp/deep/nested/very/deep.txt'), 'content');\n\n    await new Promise((resolve, reject) => {\n      cp(new URL('file:///tmp/deep'), pathH, { recursive: true }, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n\n    ok(existsSync(new URL('file:///tmp/h/nested/very/deep.txt')));\n    deepStrictEqual(\n      readFileSync(new URL('file:///tmp/h/nested/very/deep.txt'), 'utf8'),\n      'content'\n    );\n  },\n};\n\n// PreserveTimestamps option tests - callback version\nexport const preserveTimestampsTestsCallback = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    const pathH = new URL('file:///tmp/h');\n\n    // Get original timestamps\n    const originalStat = lstatSync(pathA);\n    const originalMtime = originalStat.mtime;\n\n    // Copy without preserveTimestamps (default behavior)\n    await new Promise((resolve, reject) => {\n      cp(pathA, pathG, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n    const copiedStat = lstatSync(pathG);\n\n    // Copy with preserveTimestamps: true\n    await new Promise((resolve, reject) => {\n      cp(pathA, pathH, { preserveTimestamps: true }, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n    const preservedStat = lstatSync(pathH);\n\n    ok(\n      Math.abs(preservedStat.mtime.getTime() - originalMtime.getTime()) < 1000\n    );\n  },\n};\n\n// Error handling edge cases - callback version\nexport const errorHandlingTestsCallback = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    const nonExistent = new URL('file:///tmp/nonexistent');\n\n    // Test copying from non-existent source\n    await new Promise((resolve) => {\n      cp(nonExistent, pathG, (err) => {\n        ok(err);\n        strictEqual(err.code, 'ENOENT');\n        resolve();\n      });\n    });\n  },\n};\n\n// Symlink edge cases - callback version\nexport const symlinkEdgeCasesCallback = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    const pathH = new URL('file:///tmp/h');\n    const pathI = new URL('file:///tmp/i');\n    const pathJ = new URL('file:///tmp/j');\n\n    // Create a broken symlink\n    const brokenTarget = new URL('file:///tmp/broken-target');\n    symlinkSync(brokenTarget, pathG);\n\n    // Test copying broken symlink without dereference\n    await new Promise((resolve, reject) => {\n      cp(pathG, pathH, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n    ok(lstatSync(pathH).isSymbolicLink());\n\n    // Test copying broken symlink with dereference should fail\n    await new Promise((resolve) => {\n      cp(pathG, pathI, { dereference: true }, (err) => {\n        ok(err);\n        strictEqual(err.code, 'ENOENT');\n        resolve();\n      });\n    });\n\n    // Create symlink chain: J -> I -> A\n    symlinkSync(pathA, pathI);\n    symlinkSync(pathI, pathJ);\n\n    // Test copying symlink chain\n    const pathK = new URL('file:///tmp/k');\n    await new Promise((resolve, reject) => {\n      cp(pathJ, pathK, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n\n    ok(lstatSync(pathK).isSymbolicLink());\n    deepStrictEqual(readFileSync(pathK, 'utf8'), 'foo');\n  },\n};\n\n// Combined options tests - callback version\nexport const combinedOptionsTestsCallback = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n\n    // Test multiple options together\n    await new Promise((resolve, reject) => {\n      cp(\n        pathB,\n        pathG,\n        {\n          recursive: true,\n          dereference: true,\n          force: true,\n          preserveTimestamps: true,\n          errorOnExist: false,\n        },\n        (err) => {\n          if (err) reject(err);\n          else resolve();\n        }\n      );\n    });\n\n    ok(existsSync(pathG));\n    ok(lstatSync(pathG).isDirectory());\n  },\n};\n\n// Path type tests - callback version\nexport const pathTypeTestsCallback = {\n  async test() {\n    setupPaths();\n\n    // Test with Buffer paths\n    const pathG = new URL('file:///tmp/g');\n    const bufferSrc = Buffer.from('/tmp/a');\n    const bufferDest = Buffer.from('/tmp/g');\n\n    await new Promise((resolve, reject) => {\n      cp(bufferSrc, bufferDest, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n\n    ok(existsSync(pathG));\n    deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');\n  },\n};\n\n// ===== PROMISES-BASED API TESTS =====\n\n// Simple file copy - promises version\nexport const simpleFileCopyPromises = {\n  async test() {\n    setupPaths();\n    ok(existsSync(pathA));\n    deepStrictEqual(readFileSync(pathA, 'utf8'), 'foo');\n    ok(!existsSync(pathF));\n\n    await fsPromises.cp(pathA, pathF);\n    ok(existsSync(pathF));\n    deepStrictEqual(readFileSync(pathF, 'utf8'), 'foo');\n    writeFileSync(pathA, 'baz');\n    deepStrictEqual(readFileSync(pathA, 'utf8'), 'baz');\n    deepStrictEqual(readFileSync(pathF, 'utf8'), 'foo');\n\n    // Test with errorOnExist: true\n    await fsPromises.cp(pathA, pathF, { errorOnExist: true });\n\n    // Test error case: force: false, errorOnExist: true\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathA, pathF, { force: false, errorOnExist: true });\n      },\n      { code: 'ERR_FS_CP_EEXIST' }\n    );\n\n    // Test error case: copying file to directory\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathA, pathB);\n      },\n      { code: 'ERR_FS_CP_NON_DIR_TO_DIR' }\n    );\n\n    // Test copying to symlink to directory\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathA, pathD, { dereference: true });\n      },\n      { code: 'ERR_FS_CP_NON_DIR_TO_DIR' }\n    );\n\n    // Test copying to symlink without dereference\n    await fsPromises.cp(pathA, pathD);\n    ok(lstatSync(pathD).isFile());\n  },\n};\n\n// Copy file to non-existent directory - promises version\nexport const copyFileToNonExistentDirectoryPromises = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/nonexistent');\n    const pathH = new URL('file:///tmp/nonexistent/g');\n    ok(!existsSync(pathG));\n    ok(!existsSync(pathH));\n\n    await fsPromises.cp(pathA, pathH);\n    ok(existsSync(pathG));\n    ok(existsSync(pathH));\n  },\n};\n\n// Copy file to existing file with force - promises version\nexport const copyFileToExistingFileWithForcePromises = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    writeFileSync(pathG, 'original');\n\n    await fsPromises.cp(pathA, pathG, { force: true });\n    deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');\n  },\n};\n\n// Copy file to existing file without force - promises version\nexport const copyFileToExistingFileWithoutForcePromises = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    writeFileSync(pathG, 'original');\n\n    await fsPromises.cp(pathA, pathG, { force: false });\n    deepStrictEqual(readFileSync(pathG, 'utf8'), 'original');\n  },\n};\n\n// Copy file to existing directory - promises version\nexport const copyFileToExistingDirectoryPromises = {\n  async test() {\n    setupPaths();\n\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathA, pathB);\n      },\n      { code: 'ERR_FS_CP_NON_DIR_TO_DIR' }\n    );\n  },\n};\n\n// Copy file to existing symlink to directory - promises version\nexport const copyFileToExistingSymlinkToDirectoryPromises = {\n  async test() {\n    setupPaths();\n\n    // With dereference: true, should throw EISDIR\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathA, pathD, { dereference: true });\n      },\n      { code: 'ERR_FS_CP_NON_DIR_TO_DIR' }\n    );\n\n    // Without dereference (default), should replace the symlink\n    await fsPromises.cp(pathA, pathD);\n    ok(lstatSync(pathD).isFile());\n    deepStrictEqual(readFileSync(pathD, 'utf8'), 'foo');\n  },\n};\n\n// Copy directory to non-existent directory - promises version\nexport const copyDirectoryToNonExistentDirectoryPromises = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n\n    await fsPromises.cp(pathB, pathG, { recursive: true });\n    ok(existsSync(pathG));\n    ok(lstatSync(pathG).isDirectory());\n    ok(existsSync(new URL('file:///tmp/g/e')));\n    deepStrictEqual(readFileSync(new URL('file:///tmp/g/e'), 'utf8'), 'bar');\n  },\n};\n\n// Copy directory to existing directory - promises version\nexport const copyDirectoryToExistingDirectoryPromises = {\n  async test() {\n    setupPaths();\n\n    await fsPromises.cp(pathB, pathE, { recursive: true });\n    ok(existsSync(new URL('file:///tmp/e/e')));\n    deepStrictEqual(readFileSync(new URL('file:///tmp/e/e'), 'utf8'), 'bar');\n  },\n};\n\n// Copy directory to existing file - promises version\nexport const copyDirectoryToExistingFilePromises = {\n  async test() {\n    setupPaths();\n\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathB, pathA, { recursive: true });\n      },\n      { code: 'ERR_FS_CP_DIR_TO_NON_DIR' }\n    );\n  },\n};\n\n// Copy directory to existing symlink to file - promises version\nexport const copyDirectoryToExistingSymlinkToFilePromises = {\n  async test() {\n    setupPaths();\n\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathB, pathC, {\n          recursive: true,\n          dereference: true,\n        });\n      },\n      { code: 'ERR_FS_CP_DIR_TO_NON_DIR' }\n    );\n  },\n};\n\n// Copy symlink to file - promises version\nexport const copySymlinkToFilePromises = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n\n    // With dereference: false (default), should copy the symlink\n    await fsPromises.cp(pathC, pathG);\n    ok(lstatSync(pathG).isSymbolicLink());\n    deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');\n\n    // With dereference: true, should copy the target file\n    const pathH = new URL('file:///tmp/h');\n    await fsPromises.cp(pathC, pathH, { dereference: true });\n    ok(lstatSync(pathH).isFile());\n    deepStrictEqual(readFileSync(pathH, 'utf8'), 'foo');\n  },\n};\n\n// Copy symlink to directory - promises version\nexport const copySymlinkToDirectoryPromises = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n\n    // With dereference: false (default), should copy the symlink\n    await fsPromises.cp(pathD, pathG);\n    ok(lstatSync(pathG).isSymbolicLink());\n    ok(statSync(pathG).isDirectory());\n\n    // With dereference: true, should copy the target directory\n    const pathH = new URL('file:///tmp/h');\n    await fsPromises.cp(pathD, pathH, { recursive: true, dereference: true });\n    ok(lstatSync(pathH).isDirectory());\n    ok(existsSync(new URL('file:///tmp/h/e')));\n  },\n};\n\n// Copy symlink to existing file - promises version\nexport const copySymlinkToExistingFilePromises = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    writeFileSync(pathG, 'original');\n\n    // Should replace the file with symlink\n    await fsPromises.cp(pathC, pathG);\n    ok(lstatSync(pathG).isSymbolicLink());\n    deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');\n  },\n};\n\n// Copy symlink to existing directory - promises version\nexport const copySymlinkToExistingDirectoryPromises = {\n  async test() {\n    setupPaths();\n\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathC, pathE);\n      },\n      { code: 'ENOTDIR' }\n    );\n  },\n};\n\n// Copy symlink to existing symlink to directory - promises version\nexport const copySymlinkToExistingSymlinkToDirectoryPromises = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    const pathH = new URL('file:///tmp/h');\n    mkdirSync(pathH);\n    symlinkSync(pathH, pathG);\n\n    // Should replace the symlink\n    await fsPromises.cp(pathD, pathG);\n    ok(lstatSync(pathG).isSymbolicLink());\n    ok(statSync(pathG).isDirectory());\n  },\n};\n\n// Copy symlink operations with various option combinations - promises version\nexport const copySymlinkWithDereferenceOptionsPromises = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    const pathH = new URL('file:///tmp/h');\n    const pathI = new URL('file:///tmp/i');\n\n    // Test with force: false, errorOnExist: true\n    symlinkSync(pathA, pathG);\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathC, pathG, { force: false, errorOnExist: true });\n      },\n      { code: 'ERR_FS_CP_EEXIST' }\n    );\n\n    // Test with force: false, errorOnExist: false\n    await fsPromises.cp(pathC, pathG, { force: false, errorOnExist: false });\n    ok(lstatSync(pathG).isSymbolicLink());\n\n    // Test with dereference and errorOnExist combinations\n    writeFileSync(pathH, 'existing');\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathC, pathH, {\n          dereference: true,\n          force: false,\n          errorOnExist: true,\n        });\n      },\n      { code: 'ERR_FS_CP_EEXIST' }\n    );\n\n    // Test copying to non-existent path\n    await fsPromises.cp(pathC, pathI);\n    ok(lstatSync(pathI).isSymbolicLink());\n    deepStrictEqual(readFileSync(pathI, 'utf8'), 'foo');\n  },\n};\n\n// Option validation tests - promises version\nexport const optionValidationTestsPromises = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n\n    // Test invalid option types\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathA, pathG, { dereference: 'invalid' });\n      },\n      { code: 'ERR_INVALID_ARG_TYPE' }\n    );\n\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathA, pathG, { errorOnExist: 'invalid' });\n      },\n      { code: 'ERR_INVALID_ARG_TYPE' }\n    );\n\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathA, pathG, { force: 'invalid' });\n      },\n      { code: 'ERR_INVALID_ARG_TYPE' }\n    );\n\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathA, pathG, { recursive: 'invalid' });\n      },\n      { code: 'ERR_INVALID_ARG_TYPE' }\n    );\n\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathA, pathG, { preserveTimestamps: 'invalid' });\n      },\n      { code: 'ERR_INVALID_ARG_TYPE' }\n    );\n\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathA, pathG, { verbatimSymlinks: 'invalid' });\n      },\n      { code: 'ERR_INVALID_ARG_TYPE' }\n    );\n\n    // Test invalid filter type\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathA, pathG, { filter: 'not-a-function' });\n      },\n      { code: 'ERR_INVALID_ARG_TYPE' }\n    );\n\n    // Test filter option throws unsupported operation\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathA, pathG, { filter: () => true });\n      },\n      { code: 'ERR_UNSUPPORTED_OPERATION' }\n    );\n\n    // Test COPYFILE_FICLONE_FORCE mode\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathA, pathG, { mode: 4 });\n      },\n      { code: 'ERR_INVALID_ARG_VALUE' }\n    );\n  },\n};\n\n// Recursive option tests - promises version\nexport const recursiveOptionTestsPromises = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    const pathH = new URL('file:///tmp/h');\n\n    // Test copying directory without recursive option should fail\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathB, pathG);\n      },\n      { code: 'ERR_FS_EISDIR' }\n    );\n\n    // Test with recursive: false explicitly\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathB, pathG, { recursive: false });\n      },\n      { code: 'ERR_FS_EISDIR' }\n    );\n\n    // Test copying directory with recursive: true should work\n    await fsPromises.cp(pathB, pathG, { recursive: true });\n    ok(existsSync(pathG));\n    ok(lstatSync(pathG).isDirectory());\n    ok(existsSync(new URL('file:///tmp/g/e')));\n\n    // Test deep directory structure\n    mkdirSync(new URL('file:///tmp/deep'));\n    mkdirSync(new URL('file:///tmp/deep/nested'));\n    mkdirSync(new URL('file:///tmp/deep/nested/very'));\n    writeFileSync(new URL('file:///tmp/deep/nested/very/deep.txt'), 'content');\n\n    await fsPromises.cp(new URL('file:///tmp/deep'), pathH, {\n      recursive: true,\n    });\n    ok(existsSync(new URL('file:///tmp/h/nested/very/deep.txt')));\n    deepStrictEqual(\n      readFileSync(new URL('file:///tmp/h/nested/very/deep.txt'), 'utf8'),\n      'content'\n    );\n  },\n};\n\n// PreserveTimestamps option tests - promises version\nexport const preserveTimestampsTestsPromises = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    const pathH = new URL('file:///tmp/h');\n\n    // Get original timestamps\n    const originalStat = lstatSync(pathA);\n    const originalMtime = originalStat.mtime;\n\n    // Copy without preserveTimestamps (default behavior)\n    await fsPromises.cp(pathA, pathG);\n    const copiedStat = lstatSync(pathG);\n\n    // Copy with preserveTimestamps: true\n    await fsPromises.cp(pathA, pathH, { preserveTimestamps: true });\n    const preservedStat = lstatSync(pathH);\n\n    ok(\n      Math.abs(preservedStat.mtime.getTime() - originalMtime.getTime()) < 1000\n    );\n  },\n};\n\n// Error handling edge cases - promises version\nexport const errorHandlingTestsPromises = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    const nonExistent = new URL('file:///tmp/nonexistent');\n\n    // Test copying from non-existent source\n    await rejects(\n      async () => {\n        await fsPromises.cp(nonExistent, pathG);\n      },\n      { code: 'ENOENT' }\n    );\n  },\n};\n\n// Symlink edge cases - promises version\nexport const symlinkEdgeCasesPromises = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n    const pathH = new URL('file:///tmp/h');\n    const pathI = new URL('file:///tmp/i');\n    const pathJ = new URL('file:///tmp/j');\n\n    // Create a broken symlink\n    const brokenTarget = new URL('file:///tmp/broken-target');\n    symlinkSync(brokenTarget, pathG);\n\n    // Test copying broken symlink without dereference\n    await fsPromises.cp(pathG, pathH);\n    ok(lstatSync(pathH).isSymbolicLink());\n\n    // Test copying broken symlink with dereference should fail\n    await rejects(\n      async () => {\n        await fsPromises.cp(pathG, pathI, { dereference: true });\n      },\n      { code: 'ENOENT' }\n    );\n\n    // Create symlink chain: J -> I -> A\n    symlinkSync(pathA, pathI);\n    symlinkSync(pathI, pathJ);\n\n    // Test copying symlink chain\n    const pathK = new URL('file:///tmp/k');\n    await fsPromises.cp(pathJ, pathK);\n\n    ok(lstatSync(pathK).isSymbolicLink());\n    deepStrictEqual(readFileSync(pathK, 'utf8'), 'foo');\n  },\n};\n\n// Combined options tests - promises version\nexport const combinedOptionsTestsPromises = {\n  async test() {\n    setupPaths();\n    const pathG = new URL('file:///tmp/g');\n\n    // Test multiple options together\n    await fsPromises.cp(pathB, pathG, {\n      recursive: true,\n      dereference: true,\n      force: true,\n      preserveTimestamps: true,\n      errorOnExist: false,\n    });\n\n    ok(existsSync(pathG));\n    ok(lstatSync(pathG).isDirectory());\n  },\n};\n\n// Path type tests - promises version\nexport const pathTypeTestsPromises = {\n  async test() {\n    setupPaths();\n\n    // Test with Buffer paths\n    const pathG = new URL('file:///tmp/g');\n    const bufferSrc = Buffer.from('/tmp/a');\n    const bufferDest = Buffer.from('/tmp/g');\n\n    await fsPromises.cp(bufferSrc, bufferDest);\n    ok(existsSync(pathG));\n    deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');\n  },\n};\n\n// Deep directory structure tests - sync version\nexport const deepDirectoryStructureTestsSync = {\n  test() {\n    const sourceRoot = '/tmp/deep_source';\n    const destRoot = '/tmp/deep_dest';\n\n    // Create source directory structure\n    mkdirSync(sourceRoot, { recursive: true });\n    mkdirSync(`${sourceRoot}/level1`, { recursive: true });\n    mkdirSync(`${sourceRoot}/level1/level2`, { recursive: true });\n    mkdirSync(`${sourceRoot}/level1/level2/level3`, { recursive: true });\n    mkdirSync(`${sourceRoot}/level1/level2/level3/level4`, { recursive: true });\n    mkdirSync(`${sourceRoot}/level1/level2/level3/level4/level5`, {\n      recursive: true,\n    });\n\n    // Create files in various levels\n    writeFileSync(`${sourceRoot}/root_file.txt`, 'root content');\n    writeFileSync(`${sourceRoot}/level1/level1_file.txt`, 'level1 content');\n    writeFileSync(\n      `${sourceRoot}/level1/level2/level2_file.txt`,\n      'level2 content'\n    );\n    writeFileSync(\n      `${sourceRoot}/level1/level2/level3/level3_file.txt`,\n      'level3 content'\n    );\n    writeFileSync(\n      `${sourceRoot}/level1/level2/level3/level4/level4_file.txt`,\n      'level4 content'\n    );\n    writeFileSync(\n      `${sourceRoot}/level1/level2/level3/level4/level5/level5_file.txt`,\n      'level5 content'\n    );\n\n    // Create symlinks (will not be followed)\n    writeFileSync(`${sourceRoot}/link_target.txt`, 'link target content');\n    symlinkSync(\n      `${sourceRoot}/link_target.txt`,\n      `${sourceRoot}/level1/level2/symlink_to_file`\n    );\n    symlinkSync(\n      `${sourceRoot}/level1`,\n      `${sourceRoot}/level1/level2/level3/symlink_to_dir`\n    );\n\n    // Create destination directory structure with some existing files (duplicates)\n    mkdirSync(destRoot, { recursive: true });\n    mkdirSync(`${destRoot}/level1`, { recursive: true });\n    mkdirSync(`${destRoot}/level1/level2`, { recursive: true });\n    mkdirSync(`${destRoot}/existing_dir`, { recursive: true });\n\n    // Create files that will be duplicates (should be ignored)\n    writeFileSync(`${destRoot}/duplicate_file.txt`, 'existing content');\n    writeFileSync(\n      `${destRoot}/level1/duplicate_file.txt`,\n      'existing level1 content'\n    );\n\n    // Add some files to source that match existing files (duplicates)\n    writeFileSync(\n      `${sourceRoot}/duplicate_file.txt`,\n      'source duplicate content'\n    );\n    writeFileSync(\n      `${sourceRoot}/level1/duplicate_file.txt`,\n      'source level1 duplicate content'\n    );\n\n    // Perform recursive copy (should ignore duplicates and not follow symlinks)\n    cpSync(sourceRoot, destRoot, { recursive: true, force: false });\n\n    // Verify destination structure\n    ok(existsSync(`${destRoot}/root_file.txt`));\n    ok(existsSync(`${destRoot}/level1/level1_file.txt`));\n    ok(existsSync(`${destRoot}/level1/level2/level2_file.txt`));\n    ok(existsSync(`${destRoot}/level1/level2/level3/level3_file.txt`));\n    ok(existsSync(`${destRoot}/level1/level2/level3/level4/level4_file.txt`));\n    ok(\n      existsSync(\n        `${destRoot}/level1/level2/level3/level4/level5/level5_file.txt`\n      )\n    );\n\n    // Verify file contents\n    deepStrictEqual(\n      readFileSync(`${destRoot}/root_file.txt`, 'utf8'),\n      'root content'\n    );\n    deepStrictEqual(\n      readFileSync(`${destRoot}/level1/level1_file.txt`, 'utf8'),\n      'level1 content'\n    );\n    deepStrictEqual(\n      readFileSync(`${destRoot}/level1/level2/level2_file.txt`, 'utf8'),\n      'level2 content'\n    );\n    deepStrictEqual(\n      readFileSync(`${destRoot}/level1/level2/level3/level3_file.txt`, 'utf8'),\n      'level3 content'\n    );\n    deepStrictEqual(\n      readFileSync(\n        `${destRoot}/level1/level2/level3/level4/level4_file.txt`,\n        'utf8'\n      ),\n      'level4 content'\n    );\n    deepStrictEqual(\n      readFileSync(\n        `${destRoot}/level1/level2/level3/level4/level5/level5_file.txt`,\n        'utf8'\n      ),\n      'level5 content'\n    );\n\n    // Verify symlinks are copied as symlinks (not followed)\n    ok(lstatSync(`${destRoot}/level1/level2/symlink_to_file`).isSymbolicLink());\n    ok(\n      lstatSync(\n        `${destRoot}/level1/level2/level3/symlink_to_dir`\n      ).isSymbolicLink()\n    );\n\n    // Verify duplicate files were ignored (original content preserved)\n    deepStrictEqual(\n      readFileSync(`${destRoot}/duplicate_file.txt`, 'utf8'),\n      'existing content'\n    );\n    deepStrictEqual(\n      readFileSync(`${destRoot}/level1/duplicate_file.txt`, 'utf8'),\n      'existing level1 content'\n    );\n\n    // Verify existing directory structure remains\n    ok(existsSync(`${destRoot}/existing_dir`));\n  },\n};\n\n// Deep directory structure tests - callback version\nexport const deepDirectoryStructureTestsCallback = {\n  async test() {\n    const sourceRoot = '/tmp/deep_source_cb';\n    const destRoot = '/tmp/deep_dest_cb';\n\n    // Create source directory structure\n    mkdirSync(sourceRoot, { recursive: true });\n    mkdirSync(`${sourceRoot}/level1`, { recursive: true });\n    mkdirSync(`${sourceRoot}/level1/level2`, { recursive: true });\n    mkdirSync(`${sourceRoot}/level1/level2/level3`, { recursive: true });\n    mkdirSync(`${sourceRoot}/level1/level2/level3/level4`, { recursive: true });\n    mkdirSync(`${sourceRoot}/level1/level2/level3/level4/level5`, {\n      recursive: true,\n    });\n\n    // Create files in various levels\n    writeFileSync(`${sourceRoot}/root_file.txt`, 'root content');\n    writeFileSync(`${sourceRoot}/level1/level1_file.txt`, 'level1 content');\n    writeFileSync(\n      `${sourceRoot}/level1/level2/level2_file.txt`,\n      'level2 content'\n    );\n    writeFileSync(\n      `${sourceRoot}/level1/level2/level3/level3_file.txt`,\n      'level3 content'\n    );\n    writeFileSync(\n      `${sourceRoot}/level1/level2/level3/level4/level4_file.txt`,\n      'level4 content'\n    );\n    writeFileSync(\n      `${sourceRoot}/level1/level2/level3/level4/level5/level5_file.txt`,\n      'level5 content'\n    );\n\n    // Create symlinks (will not be followed)\n    writeFileSync(`${sourceRoot}/link_target.txt`, 'link target content');\n    symlinkSync(\n      `${sourceRoot}/link_target.txt`,\n      `${sourceRoot}/level1/level2/symlink_to_file`\n    );\n    symlinkSync(\n      `${sourceRoot}/level1`,\n      `${sourceRoot}/level1/level2/level3/symlink_to_dir`\n    );\n\n    // Create destination directory structure with some existing files (duplicates)\n    mkdirSync(destRoot, { recursive: true });\n    mkdirSync(`${destRoot}/level1`, { recursive: true });\n    mkdirSync(`${destRoot}/level1/level2`, { recursive: true });\n    mkdirSync(`${destRoot}/existing_dir`, { recursive: true });\n\n    // Create files that will be duplicates (should be ignored)\n    writeFileSync(`${destRoot}/duplicate_file.txt`, 'existing content');\n    writeFileSync(\n      `${destRoot}/level1/duplicate_file.txt`,\n      'existing level1 content'\n    );\n\n    // Add some files to source that match existing files (duplicates)\n    writeFileSync(\n      `${sourceRoot}/duplicate_file.txt`,\n      'source duplicate content'\n    );\n    writeFileSync(\n      `${sourceRoot}/level1/duplicate_file.txt`,\n      'source level1 duplicate content'\n    );\n    // Perform recursive copy (should ignore duplicates and not follow symlinks)\n    await new Promise((resolve, reject) => {\n      cp(sourceRoot, destRoot, { recursive: true, force: false }, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n\n    // Verify destination structure\n    ok(existsSync(`${destRoot}/root_file.txt`));\n    ok(existsSync(`${destRoot}/level1/level1_file.txt`));\n    ok(existsSync(`${destRoot}/level1/level2/level2_file.txt`));\n    ok(existsSync(`${destRoot}/level1/level2/level3/level3_file.txt`));\n    ok(existsSync(`${destRoot}/level1/level2/level3/level4/level4_file.txt`));\n    ok(\n      existsSync(\n        `${destRoot}/level1/level2/level3/level4/level5/level5_file.txt`\n      )\n    );\n\n    // Verify file contents\n    deepStrictEqual(\n      readFileSync(`${destRoot}/root_file.txt`, 'utf8'),\n      'root content'\n    );\n    deepStrictEqual(\n      readFileSync(`${destRoot}/level1/level1_file.txt`, 'utf8'),\n      'level1 content'\n    );\n    deepStrictEqual(\n      readFileSync(`${destRoot}/level1/level2/level2_file.txt`, 'utf8'),\n      'level2 content'\n    );\n    deepStrictEqual(\n      readFileSync(`${destRoot}/level1/level2/level3/level3_file.txt`, 'utf8'),\n      'level3 content'\n    );\n    deepStrictEqual(\n      readFileSync(\n        `${destRoot}/level1/level2/level3/level4/level4_file.txt`,\n        'utf8'\n      ),\n      'level4 content'\n    );\n    deepStrictEqual(\n      readFileSync(\n        `${destRoot}/level1/level2/level3/level4/level5/level5_file.txt`,\n        'utf8'\n      ),\n      'level5 content'\n    );\n\n    // Verify symlinks are copied as symlinks (not followed)\n    ok(lstatSync(`${destRoot}/level1/level2/symlink_to_file`).isSymbolicLink());\n    ok(\n      lstatSync(\n        `${destRoot}/level1/level2/level3/symlink_to_dir`\n      ).isSymbolicLink()\n    );\n\n    // Verify duplicate files were ignored (original content preserved)\n    deepStrictEqual(\n      readFileSync(`${destRoot}/duplicate_file.txt`, 'utf8'),\n      'existing content'\n    );\n    deepStrictEqual(\n      readFileSync(`${destRoot}/level1/duplicate_file.txt`, 'utf8'),\n      'existing level1 content'\n    );\n\n    // Verify existing directory structure remains\n    ok(existsSync(`${destRoot}/existing_dir`));\n  },\n};\n\n// Deep directory structure tests - promises version\nexport const deepDirectoryStructureTestsPromises = {\n  async test() {\n    const sourceRoot = '/tmp/deep_source_pr';\n    const destRoot = '/tmp/deep_dest_pr';\n\n    // Create source directory structure\n    mkdirSync(sourceRoot, { recursive: true });\n    mkdirSync(`${sourceRoot}/level1`, { recursive: true });\n    mkdirSync(`${sourceRoot}/level1/level2`, { recursive: true });\n    mkdirSync(`${sourceRoot}/level1/level2/level3`, { recursive: true });\n    mkdirSync(`${sourceRoot}/level1/level2/level3/level4`, { recursive: true });\n    mkdirSync(`${sourceRoot}/level1/level2/level3/level4/level5`, {\n      recursive: true,\n    });\n\n    // Create files in various levels\n    writeFileSync(`${sourceRoot}/root_file.txt`, 'root content');\n    writeFileSync(`${sourceRoot}/level1/level1_file.txt`, 'level1 content');\n    writeFileSync(\n      `${sourceRoot}/level1/level2/level2_file.txt`,\n      'level2 content'\n    );\n    writeFileSync(\n      `${sourceRoot}/level1/level2/level3/level3_file.txt`,\n      'level3 content'\n    );\n    writeFileSync(\n      `${sourceRoot}/level1/level2/level3/level4/level4_file.txt`,\n      'level4 content'\n    );\n    writeFileSync(\n      `${sourceRoot}/level1/level2/level3/level4/level5/level5_file.txt`,\n      'level5 content'\n    );\n\n    // Create symlinks (will not be followed)\n    writeFileSync(`${sourceRoot}/link_target.txt`, 'link target content');\n    symlinkSync(\n      `${sourceRoot}/link_target.txt`,\n      `${sourceRoot}/level1/level2/symlink_to_file`\n    );\n    symlinkSync(\n      `${sourceRoot}/level1`,\n      `${sourceRoot}/level1/level2/level3/symlink_to_dir`\n    );\n\n    // Create destination directory structure with some existing files (duplicates)\n    mkdirSync(destRoot, { recursive: true });\n    mkdirSync(`${destRoot}/level1`, { recursive: true });\n    mkdirSync(`${destRoot}/level1/level2`, { recursive: true });\n    mkdirSync(`${destRoot}/existing_dir`, { recursive: true });\n\n    // Create files that will be duplicates (should be ignored)\n    writeFileSync(`${destRoot}/duplicate_file.txt`, 'existing content');\n    writeFileSync(\n      `${destRoot}/level1/duplicate_file.txt`,\n      'existing level1 content'\n    );\n\n    // Add some files to source that match existing files (duplicates)\n    writeFileSync(\n      `${sourceRoot}/duplicate_file.txt`,\n      'source duplicate content'\n    );\n    writeFileSync(\n      `${sourceRoot}/level1/duplicate_file.txt`,\n      'source level1 duplicate content'\n    );\n\n    // Perform recursive copy (should ignore duplicates and not follow symlinks)\n    await fsPromises.cp(sourceRoot, destRoot, {\n      recursive: true,\n      force: false,\n    });\n\n    // Verify destination structure\n    ok(existsSync(`${destRoot}/root_file.txt`));\n    ok(existsSync(`${destRoot}/level1/level1_file.txt`));\n    ok(existsSync(`${destRoot}/level1/level2/level2_file.txt`));\n    ok(existsSync(`${destRoot}/level1/level2/level3/level3_file.txt`));\n    ok(existsSync(`${destRoot}/level1/level2/level3/level4/level4_file.txt`));\n    ok(\n      existsSync(\n        `${destRoot}/level1/level2/level3/level4/level5/level5_file.txt`\n      )\n    );\n\n    // Verify file contents\n    deepStrictEqual(\n      readFileSync(`${destRoot}/root_file.txt`, 'utf8'),\n      'root content'\n    );\n    deepStrictEqual(\n      readFileSync(`${destRoot}/level1/level1_file.txt`, 'utf8'),\n      'level1 content'\n    );\n    deepStrictEqual(\n      readFileSync(`${destRoot}/level1/level2/level2_file.txt`, 'utf8'),\n      'level2 content'\n    );\n    deepStrictEqual(\n      readFileSync(`${destRoot}/level1/level2/level3/level3_file.txt`, 'utf8'),\n      'level3 content'\n    );\n    deepStrictEqual(\n      readFileSync(\n        `${destRoot}/level1/level2/level3/level4/level4_file.txt`,\n        'utf8'\n      ),\n      'level4 content'\n    );\n    deepStrictEqual(\n      readFileSync(\n        `${destRoot}/level1/level2/level3/level4/level5/level5_file.txt`,\n        'utf8'\n      ),\n      'level5 content'\n    );\n\n    // Verify symlinks are copied as symlinks (not followed)\n    ok(lstatSync(`${destRoot}/level1/level2/symlink_to_file`).isSymbolicLink());\n    ok(\n      lstatSync(\n        `${destRoot}/level1/level2/level3/symlink_to_dir`\n      ).isSymbolicLink()\n    );\n\n    // Verify duplicate files were ignored (original content preserved)\n    deepStrictEqual(\n      readFileSync(`${destRoot}/duplicate_file.txt`, 'utf8'),\n      'existing content'\n    );\n    deepStrictEqual(\n      readFileSync(`${destRoot}/level1/duplicate_file.txt`, 'utf8'),\n      'existing level1 content'\n    );\n\n    // Verify existing directory structure remains\n    ok(existsSync(`${destRoot}/existing_dir`));\n  },\n};\n\nexport const copyTwiceTest = {\n  test() {\n    cpSync('/bundle/worker', '/tmp/a');\n    cpSync('/tmp/a', '/tmp/b');\n    const a = readFileSync('/bundle/worker');\n    const b = readFileSync('/tmp/a');\n    const c = readFileSync('/tmp/b');\n    deepStrictEqual(a, b);\n    deepStrictEqual(b, c);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-cp-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"fs-cp-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"fs-cp-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_fs_module\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-dir-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport {\n  deepStrictEqual,\n  ok,\n  match,\n  rejects,\n  strictEqual,\n  throws,\n} from 'node:assert';\n\nimport {\n  existsSync,\n  writeFileSync,\n  mkdirSync,\n  mkdtempSync,\n  rmSync,\n  rmdirSync,\n  readdirSync,\n  mkdtemp,\n  mkdir,\n  rm,\n  rmdir,\n  readdir,\n  promises,\n  opendirSync,\n  opendir,\n} from 'node:fs';\n\nstrictEqual(typeof existsSync, 'function');\nstrictEqual(typeof writeFileSync, 'function');\nstrictEqual(typeof mkdirSync, 'function');\nstrictEqual(typeof mkdtempSync, 'function');\nstrictEqual(typeof rmSync, 'function');\nstrictEqual(typeof rmdirSync, 'function');\nstrictEqual(typeof readdirSync, 'function');\nstrictEqual(typeof mkdtemp, 'function');\nstrictEqual(typeof mkdir, 'function');\nstrictEqual(typeof rm, 'function');\nstrictEqual(typeof rmdir, 'function');\nstrictEqual(typeof readdir, 'function');\nstrictEqual(typeof opendirSync, 'function');\nstrictEqual(typeof opendir, 'function');\nstrictEqual(typeof promises.mkdir, 'function');\nstrictEqual(typeof promises.mkdtemp, 'function');\nstrictEqual(typeof promises.rm, 'function');\nstrictEqual(typeof promises.rmdir, 'function');\nstrictEqual(typeof promises.readdir, 'function');\nstrictEqual(typeof promises.opendir, 'function');\n\nconst kInvalidArgTypeError = { code: 'ERR_INVALID_ARG_TYPE' };\nconst kInvalidArgValueError = { code: 'ERR_INVALID_ARG_VALUE' };\nconst kEPermError = { code: 'EPERM' };\nconst kENoEntError = { code: 'ENOENT' };\nconst kEExistError = { code: 'EEXIST' };\nconst kENotDirError = { code: 'ENOTDIR' };\nconst kENotEmptyError = { code: 'ENOTEMPTY' };\n\nexport const mkdirSyncTest = {\n  test() {\n    throws(() => mkdirSync(), kInvalidArgTypeError);\n    throws(() => mkdirSync(123), kInvalidArgTypeError);\n    throws(() => mkdirSync('/tmp/testdir', 'hello'), kInvalidArgTypeError);\n    throws(\n      () => mkdirSync('/tmp/testdir', { recursive: 123 }),\n      kInvalidArgTypeError\n    );\n\n    // Make a directory.\n    ok(!existsSync('/tmp/testdir'));\n    strictEqual(mkdirSync('/tmp/testdir'), undefined);\n    ok(existsSync('/tmp/testdir'));\n\n    // Making a subdirectory in a non-existing path fails by default\n    ok(!existsSync('/tmp/testdir/a/b/c'));\n    throws(() => mkdirSync('/tmp/testdir/a/b/c'), kENoEntError);\n\n    // But passing the recursive option allows the entire path to be created.\n    ok(!existsSync('/tmp/testdir/a/b/c'));\n    strictEqual(\n      mkdirSync('/tmp/testdir/a/b/c', { recursive: true }),\n      '/tmp/testdir/a'\n    );\n    ok(existsSync('/tmp/testdir/a/b/c'));\n\n    // Cannot make a directory in a read-only location\n    throws(() => mkdirSync('/bundle/a'), kEPermError);\n\n    // Making a directory that already exists is a non-op\n    mkdirSync('/tmp/testdir');\n\n    // Attempting to create a directory that already exists as a file throws\n    writeFileSync('/tmp/abc', 'Hello World');\n    throws(() => mkdirSync('/tmp/abc'), kEExistError);\n\n    // Attempting to create a directory recursively when a parent is a file\n    // throws\n    throws(() => mkdirSync('/tmp/abc/foo', { recursive: true }), kENotDirError);\n  },\n};\n\nexport const mkdirAsyncCallbackTest = {\n  async test() {\n    throws(() => mkdir(), kInvalidArgTypeError);\n    throws(() => mkdir(123), kInvalidArgTypeError);\n    throws(() => mkdir('/tmp/testdir', 'hello'), kInvalidArgTypeError);\n    throws(\n      () => mkdir('/tmp/testdir', { recursive: 123 }),\n      kInvalidArgTypeError\n    );\n\n    // Make a directory.\n    ok(!existsSync('/tmp/testdir'));\n    await new Promise((resolve, reject) => {\n      mkdir('/tmp/testdir', (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n    ok(existsSync('/tmp/testdir'));\n\n    // Making a subdirectory in a non-existing path fails by default\n    ok(!existsSync('/tmp/testdir/a/b/c'));\n    await new Promise((resolve, reject) => {\n      mkdir('/tmp/testdir/a/b/c', (err) => {\n        if (err && err.code === kENoEntError.code) resolve();\n        else reject(err);\n      });\n    });\n\n    // But passing the recursive option allows the entire path to be created.\n    ok(!existsSync('/tmp/testdir/a/b/c'));\n    await new Promise((resolve, reject) => {\n      mkdir('/tmp/testdir/a/b/c', { recursive: true }, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n    ok(existsSync('/tmp/testdir/a/b/c'));\n\n    // Cannot make a directory in a read-only location\n    await new Promise((resolve, reject) => {\n      mkdir('/bundle/a', (err) => {\n        if (err && err.code === kEPermError.code) resolve();\n        else reject(err);\n      });\n    });\n\n    // Making a directory that already exists is a non-op\n    await new Promise((resolve, reject) => {\n      mkdir('/tmp/testdir', (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n\n    // Attempting to create a directory that already exists as a file throws\n    writeFileSync('/tmp/abc', 'Hello World');\n    await new Promise((resolve, reject) => {\n      mkdir('/tmp/abc', (err) => {\n        if (err && err.code === kEExistError.code) resolve();\n        else reject(err);\n      });\n    });\n\n    // Attempting to create a directory recursively when a parent is a file\n    // throws\n    await new Promise((resolve, reject) => {\n      mkdir('/tmp/abc/foo', { recursive: true }, (err) => {\n        if (err && err.code === kENotDirError.code) resolve();\n        else reject(err);\n      });\n    });\n  },\n};\n\nexport const mkdirAsyncPromiseTest = {\n  async test() {\n    await rejects(promises.mkdir(), kInvalidArgTypeError);\n    await rejects(promises.mkdir(123), kInvalidArgTypeError);\n    await rejects(\n      promises.mkdir('/tmp/testdir', 'hello'),\n      kInvalidArgTypeError\n    );\n    await rejects(\n      promises.mkdir('/tmp/testdir', { recursive: 123 }),\n      kInvalidArgTypeError\n    );\n\n    // Make a directory.\n    ok(!existsSync('/tmp/testdir'));\n    await promises.mkdir('/tmp/testdir');\n    ok(existsSync('/tmp/testdir'));\n\n    // Making a subdirectory in a non-existing path fails by default\n    ok(!existsSync('/tmp/testdir/a/b/c'));\n    await rejects(promises.mkdir('/tmp/testdir/a/b/c'), kENoEntError);\n\n    // But passing the recursive option allows the entire path to be created.\n    ok(!existsSync('/tmp/testdir/a/b/c'));\n    await promises.mkdir('/tmp/testdir/a/b/c', { recursive: true });\n    ok(existsSync('/tmp/testdir/a/b/c'));\n\n    // Cannot make a directory in a read-only location\n    await rejects(promises.mkdir('/bundle/a'), kEPermError);\n\n    // Making a directory that already exists is a non-op\n    await promises.mkdir('/tmp/testdir');\n\n    // Attempting to create a directory that already exists as a file throws\n    writeFileSync('/tmp/abc', 'Hello World');\n    await rejects(promises.mkdir('/tmp/abc'), kEExistError);\n\n    // Attempting to create a directory recursively when a parent is a file\n    // throws\n    await rejects(\n      promises.mkdir('/tmp/abc/foo', { recursive: true }),\n      kENotDirError\n    );\n  },\n};\n\nexport const mkdtempSyncTest = {\n  test() {\n    throws(() => mkdtempSync(), kInvalidArgTypeError);\n    const ret1 = mkdtempSync('/tmp/testdir-');\n    const ret2 = mkdtempSync('/tmp/testdir-');\n    match(ret1, /\\/tmp\\/testdir-\\d+/);\n    match(ret2, /\\/tmp\\/testdir-\\d+/);\n    ok(existsSync(ret1));\n    ok(existsSync(ret2));\n    throws(() => mkdtempSync('/bundle/testdir-'), kEPermError);\n  },\n};\n\nexport const mkdtempAsyncCallbackTest = {\n  async test() {\n    throws(() => mkdtemp(), kInvalidArgTypeError);\n    const ret1 = await new Promise((resolve, reject) => {\n      mkdtemp('/tmp/testdir-', (err, dir) => {\n        if (err) reject(err);\n        else resolve(dir);\n      });\n    });\n    const ret2 = await new Promise((resolve, reject) => {\n      mkdtemp('/tmp/testdir-', (err, dir) => {\n        if (err) reject(err);\n        else resolve(dir);\n      });\n    });\n    match(ret1, /\\/tmp\\/testdir-\\d+/);\n    match(ret2, /\\/tmp\\/testdir-\\d+/);\n    ok(existsSync(ret1));\n    ok(existsSync(ret2));\n    await new Promise((resolve, reject) => {\n      mkdtemp('/bundle/testdir-', (err) => {\n        if (err && err.code === kEPermError.code) resolve();\n        else reject(err);\n      });\n    });\n  },\n};\n\nexport const mkdtempAsyncPromiseTest = {\n  async test() {\n    await rejects(promises.mkdtemp(), kInvalidArgTypeError);\n    const ret1 = await promises.mkdtemp('/tmp/testdir-');\n    const ret2 = await promises.mkdtemp('/tmp/testdir-');\n    match(ret1, /\\/tmp\\/testdir-\\d+/);\n    match(ret2, /\\/tmp\\/testdir-\\d+/);\n    ok(existsSync(ret1));\n    ok(existsSync(ret2));\n    await rejects(promises.mkdtemp('/bundle/testdir-'), kEPermError);\n  },\n};\n\nexport const rmSyncTest = {\n  test() {\n    // Passing incorrect types for options throws\n    throws(\n      () => rmSync('/tmp/testdir', { recursive: 'yes' }),\n      kInvalidArgTypeError\n    );\n    throws(() => rmSync('/tmp/testdir', 'abc'), kInvalidArgTypeError);\n    throws(\n      () => rmSync('/tmp/testdir', { force: 'yes' }),\n      kInvalidArgTypeError\n    );\n    throws(\n      () => rmSync('/tmp/testdir', { maxRetries: 'yes' }),\n      kInvalidArgTypeError\n    );\n    throws(\n      () => rmSync('/tmp/testdir', { retryDelay: 'yes' }),\n      kInvalidArgTypeError\n    );\n    throws(\n      () => rmSync('/tmp/testdir', { maxRetries: 1, retryDelay: 'yes' }),\n      kInvalidArgTypeError\n    );\n    throws(\n      () => rmSync('/tmp/testdir', { maxRetries: 'yes', retryDelay: 1 }),\n      kInvalidArgTypeError\n    );\n    throws(\n      () =>\n        rmSync('/tmp/testdir', { maxRetries: 1, retryDelay: 1, force: 'yes' }),\n      kInvalidArgTypeError\n    );\n\n    throws(\n      () => rmdirSync('/tmp/testdir', { recursive: 'yes' }),\n      kInvalidArgTypeError\n    );\n    throws(() => rmdirSync('/tmp/testdir', 'abc'), kInvalidArgTypeError);\n    throws(\n      () => rmdirSync('/tmp/testdir', { maxRetries: 'yes' }),\n      kInvalidArgTypeError\n    );\n    throws(\n      () => rmdirSync('/tmp/testdir', { retryDelay: 'yes' }),\n      kInvalidArgTypeError\n    );\n    throws(\n      () => rmdirSync('/tmp/testdir', { maxRetries: 1, retryDelay: 'yes' }),\n      kInvalidArgTypeError\n    );\n    throws(\n      () => rmdirSync('/tmp/testdir', { maxRetries: 'yes', retryDelay: 1 }),\n      kInvalidArgTypeError\n    );\n\n    ok(!existsSync('/tmp/testdir'));\n    mkdirSync('/tmp/testdir');\n    writeFileSync('/tmp/testdir/a.txt', 'Hello World');\n\n    // When the recusive option is not set, then removing a directory\n    // with children throws...\n    throws(() => rmdirSync('/tmp/testdir'), kENotEmptyError);\n    ok(existsSync('/tmp/testdir'));\n\n    // But works when the recursive option is set\n    rmdirSync('/tmp/testdir', { recursive: true });\n    ok(!existsSync('/tmp/testdir'));\n\n    mkdirSync('/tmp/testdir');\n    writeFileSync('/tmp/testdir/a.txt', 'Hello World');\n    writeFileSync('/tmp/testdir/b.txt', 'Hello World');\n    ok(existsSync('/tmp/testdir/a.txt'));\n\n    // trying to remove a file with rmdir throws\n    throws(() => rmdirSync('/tmp/testdir/a.txt'), kENotDirError);\n\n    // removing a file with rm works\n    rmSync('/tmp/testdir/a.txt');\n    ok(!existsSync('/tmp/testdir/a.txt'));\n\n    // Calling rmSync when the directory is not empty throws\n    throws(() => rmSync('/tmp/testdir'), kENotEmptyError);\n    ok(existsSync('/tmp/testdir'));\n\n    // But works when the recursive option is set\n    throws(() => rmSync('/tmp/testdir'));\n    rmSync('/tmp/testdir', { recursive: true });\n    ok(!existsSync('/tmp/testdir'));\n  },\n};\n\nexport const rmAsyncCallbackTest = {\n  async test() {\n    // Passing incorrect types for options throws\n    throws(\n      () => rm('/tmp/testdir', { recursive: 'yes' }),\n      kInvalidArgTypeError\n    );\n    throws(() => rm('/tmp/testdir', 'abc'), kInvalidArgTypeError);\n    throws(() => rm('/tmp/testdir', { force: 'yes' }), kInvalidArgTypeError);\n    throws(\n      () => rm('/tmp/testdir', { maxRetries: 'yes' }),\n      kInvalidArgTypeError\n    );\n    throws(\n      () => rm('/tmp/testdir', { retryDelay: 'yes' }),\n      kInvalidArgTypeError\n    );\n    throws(\n      () => rm('/tmp/testdir', { maxRetries: 1, retryDelay: 'yes' }),\n      kInvalidArgTypeError\n    );\n    throws(\n      () => rm('/tmp/testdir', { maxRetries: 'yes', retryDelay: 1 }),\n      kInvalidArgTypeError\n    );\n    throws(\n      () => rm('/tmp/testdir', { maxRetries: 1, retryDelay: 1, force: 'yes' }),\n      kInvalidArgTypeError\n    );\n\n    throws(\n      () => rmdir('/tmp/testdir', { recursive: 'yes' }),\n      kInvalidArgTypeError\n    );\n    throws(() => rmdir('/tmp/testdir', 'abc'), kInvalidArgTypeError);\n    throws(\n      () => rmdir('/tmp/testdir', { maxRetries: 'yes' }),\n      kInvalidArgTypeError\n    );\n    throws(\n      () => rmdir('/tmp/testdir', { retryDelay: 'yes' }),\n      kInvalidArgTypeError\n    );\n    throws(\n      () => rmdir('/tmp/testdir', { maxRetries: 1, retryDelay: 'yes' }),\n      kInvalidArgTypeError\n    );\n    throws(\n      () => rmdir('/tmp/testdir', { maxRetries: 'yes', retryDelay: 1 }),\n      kInvalidArgTypeError\n    );\n\n    ok(!existsSync('/tmp/testdir'));\n    mkdirSync('/tmp/testdir');\n    writeFileSync('/tmp/testdir/a.txt', 'Hello World');\n\n    // When the recusive option is not set, then removing a directory\n    // with children throws...\n    await new Promise((resolve, reject) => {\n      rmdir('/tmp/testdir', (err) => {\n        if (err && err.code === kENotEmptyError.code) resolve();\n        else reject(err);\n      });\n    });\n\n    ok(existsSync('/tmp/testdir'));\n    // But works when the recursive option is set\n    await new Promise((resolve, reject) => {\n      rmdir('/tmp/testdir', { recursive: true }, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n    ok(!existsSync('/tmp/testdir'));\n    mkdirSync('/tmp/testdir');\n    writeFileSync('/tmp/testdir/a.txt', 'Hello World');\n    writeFileSync('/tmp/testdir/b.txt', 'Hello World');\n\n    ok(existsSync('/tmp/testdir/a.txt'));\n    // trying to remove a file with rmdir throws\n    await new Promise((resolve, reject) => {\n      rmdir('/tmp/testdir/a.txt', (err) => {\n        if (err && err.code === kENotDirError.code) resolve();\n        else reject(err);\n      });\n    });\n    // removing a file with rm works\n    await new Promise((resolve, reject) => {\n      rm('/tmp/testdir/a.txt', (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n    ok(!existsSync('/tmp/testdir/a.txt'));\n    // Calling rm when the directory is not empty throws\n    await new Promise((resolve, reject) => {\n      rm('/tmp/testdir', (err) => {\n        if (err && err.code === kENotEmptyError.code) resolve();\n        else reject(err);\n      });\n    });\n    ok(existsSync('/tmp/testdir'));\n    // But works when the recursive option is set\n    await new Promise((resolve, reject) => {\n      rm('/tmp/testdir', { recursive: true }, (err) => {\n        if (err) reject(err);\n        else resolve();\n      });\n    });\n    ok(!existsSync('/tmp/testdir'));\n  },\n};\n\nexport const rmAsyncPromiseTest = {\n  async test() {\n    // Passing incorrect types for options throws\n    await rejects(\n      promises.rm('/tmp/testdir', { recursive: 'yes' }),\n      kInvalidArgTypeError\n    );\n    await rejects(promises.rm('/tmp/testdir', 'abc'), kInvalidArgTypeError);\n    await rejects(\n      promises.rm('/tmp/testdir', { force: 'yes' }),\n      kInvalidArgTypeError\n    );\n    await rejects(\n      promises.rm('/tmp/testdir', { maxRetries: 'yes' }),\n      kInvalidArgTypeError\n    );\n    await rejects(\n      promises.rm('/tmp/testdir', { retryDelay: 'yes' }),\n      kInvalidArgTypeError\n    );\n    await rejects(\n      promises.rm('/tmp/testdir', { maxRetries: 1, retryDelay: 'yes' }),\n      kInvalidArgTypeError\n    );\n    await rejects(\n      promises.rm('/tmp/testdir', { maxRetries: 'yes', retryDelay: 1 }),\n      kInvalidArgTypeError\n    );\n    await rejects(\n      promises.rm('/tmp/testdir', {\n        maxRetries: 1,\n        retryDelay: 1,\n        force: 'yes',\n      }),\n      kInvalidArgTypeError\n    );\n\n    await rejects(\n      promises.rmdir('/tmp/testdir', { recursive: 'yes' }),\n      kInvalidArgTypeError\n    );\n    await rejects(promises.rmdir('/tmp/testdir', 'abc'), kInvalidArgTypeError);\n    await rejects(\n      promises.rmdir('/tmp/testdir', { maxRetries: 'yes' }),\n      kInvalidArgTypeError\n    );\n    await rejects(\n      promises.rmdir('/tmp/testdir', { retryDelay: 'yes' }),\n      kInvalidArgTypeError\n    );\n    await rejects(\n      promises.rmdir('/tmp/testdir', { maxRetries: 1, retryDelay: 'yes' }),\n      kInvalidArgTypeError\n    );\n    await rejects(\n      promises.rmdir('/tmp/testdir', { maxRetries: 'yes', retryDelay: 1 }),\n      kInvalidArgTypeError\n    );\n\n    ok(!existsSync('/tmp/testdir'));\n    mkdirSync('/tmp/testdir');\n    writeFileSync('/tmp/testdir/a.txt', 'Hello World');\n\n    // When the recusive option is not set, then removing a directory\n    // with children throws...\n    await rejects(promises.rmdir('/tmp/testdir'), kENotEmptyError);\n\n    ok(existsSync('/tmp/testdir'));\n    // But works when the recursive option is set\n    await promises.rmdir('/tmp/testdir', { recursive: true });\n    ok(!existsSync('/tmp/testdir'));\n    mkdirSync('/tmp/testdir');\n    writeFileSync('/tmp/testdir/a.txt', 'Hello World');\n    writeFileSync('/tmp/testdir/b.txt', 'Hello World');\n    ok(existsSync('/tmp/testdir/a.txt'));\n    // trying to remove a file with rmdir throws\n    await rejects(promises.rmdir('/tmp/testdir/a.txt'), kENotDirError);\n    // removing a file with rm works\n    await promises.rm('/tmp/testdir/a.txt');\n    ok(!existsSync('/tmp/testdir/a.txt'));\n    // Calling rm when the directory is not empty throws\n    await rejects(promises.rm('/tmp/testdir'), kENotEmptyError);\n    ok(existsSync('/tmp/testdir'));\n    // But works when the recursive option is set\n    await promises.rm('/tmp/testdir', { recursive: true });\n    ok(!existsSync('/tmp/testdir'));\n  },\n};\n\nexport const readdirSyncTest = {\n  test() {\n    throws(() => readdirSync(), kInvalidArgTypeError);\n    throws(() => readdirSync(123), kInvalidArgTypeError);\n    throws(\n      () => readdirSync('/tmp/testdir', { withFileTypes: 123 }),\n      kInvalidArgTypeError\n    );\n    throws(\n      () => readdirSync('/tmp/testdir', { recursive: 123 }),\n      kInvalidArgTypeError\n    );\n    throws(\n      () =>\n        readdirSync('/tmp/testdir', { withFileTypes: true, recursive: 123 }),\n      kInvalidArgTypeError\n    );\n\n    deepStrictEqual(readdirSync('/'), ['bundle', 'tmp', 'dev']);\n\n    deepStrictEqual(readdirSync('/', 'buffer'), [\n      Buffer.from('bundle'),\n      Buffer.from('tmp'),\n      Buffer.from('dev'),\n    ]);\n\n    {\n      const ents = readdirSync('/', { withFileTypes: true });\n      strictEqual(ents.length, 3);\n\n      strictEqual(ents[0].name, 'bundle');\n      strictEqual(ents[0].isDirectory(), true);\n      strictEqual(ents[0].isFile(), false);\n      strictEqual(ents[0].isBlockDevice(), false);\n      strictEqual(ents[0].isCharacterDevice(), false);\n      strictEqual(ents[0].isFIFO(), false);\n      strictEqual(ents[0].isSocket(), false);\n      strictEqual(ents[0].isSymbolicLink(), false);\n      strictEqual(ents[0].parentPath, '/');\n    }\n\n    {\n      const ents = readdirSync('/', {\n        withFileTypes: true,\n        encoding: 'buffer',\n      });\n      strictEqual(ents.length, 3);\n\n      deepStrictEqual(ents[0].name, Buffer.from('bundle'));\n      strictEqual(ents[0].isDirectory(), true);\n      strictEqual(ents[0].isFile(), false);\n      strictEqual(ents[0].isBlockDevice(), false);\n      strictEqual(ents[0].isCharacterDevice(), false);\n      strictEqual(ents[0].isFIFO(), false);\n      strictEqual(ents[0].isSocket(), false);\n      strictEqual(ents[0].isSymbolicLink(), false);\n      strictEqual(ents[0].parentPath, '/');\n    }\n\n    {\n      const ents = readdirSync('/', { withFileTypes: true, recursive: true });\n      strictEqual(ents.length, 8);\n\n      strictEqual(ents[0].name, 'bundle');\n      strictEqual(ents[0].isDirectory(), true);\n      strictEqual(ents[0].isFile(), false);\n      strictEqual(ents[0].isBlockDevice(), false);\n      strictEqual(ents[0].isCharacterDevice(), false);\n      strictEqual(ents[0].isFIFO(), false);\n      strictEqual(ents[0].isSocket(), false);\n      strictEqual(ents[0].isSymbolicLink(), false);\n      strictEqual(ents[0].parentPath, '/');\n\n      strictEqual(ents[1].name, 'bundle/worker');\n      strictEqual(ents[1].isDirectory(), false);\n      strictEqual(ents[1].isFile(), true);\n      strictEqual(ents[1].isBlockDevice(), false);\n      strictEqual(ents[1].isCharacterDevice(), false);\n      strictEqual(ents[1].isFIFO(), false);\n      strictEqual(ents[1].isSocket(), false);\n      strictEqual(ents[1].isSymbolicLink(), false);\n      strictEqual(ents[1].parentPath, '/bundle');\n\n      strictEqual(ents[4].name, 'dev/null');\n      strictEqual(ents[4].isDirectory(), false);\n      strictEqual(ents[4].isFile(), false);\n      strictEqual(ents[4].isBlockDevice(), false);\n      strictEqual(ents[4].isCharacterDevice(), true);\n      strictEqual(ents[4].isFIFO(), false);\n      strictEqual(ents[4].isSocket(), false);\n      strictEqual(ents[4].isSymbolicLink(), false);\n      strictEqual(ents[4].parentPath, '/dev');\n    }\n  },\n};\n\nexport const readdirAsyncCallbackTest = {\n  async test() {\n    deepStrictEqual(\n      await new Promise((resolve, reject) => {\n        readdir('/', (err, files) => {\n          if (err) reject(err);\n          else resolve(files);\n        });\n      }),\n      ['bundle', 'tmp', 'dev']\n    );\n\n    {\n      const ents = await new Promise((resolve, reject) => {\n        readdir('/', { withFileTypes: true }, (err, files) => {\n          if (err) reject(err);\n          else resolve(files);\n        });\n      });\n      strictEqual(ents.length, 3);\n\n      strictEqual(ents[0].name, 'bundle');\n      strictEqual(ents[0].isDirectory(), true);\n      strictEqual(ents[0].isFile(), false);\n      strictEqual(ents[0].isBlockDevice(), false);\n      strictEqual(ents[0].isCharacterDevice(), false);\n      strictEqual(ents[0].isFIFO(), false);\n      strictEqual(ents[0].isSocket(), false);\n      strictEqual(ents[0].isSymbolicLink(), false);\n      strictEqual(ents[0].parentPath, '/');\n    }\n\n    {\n      const ents = await new Promise((resolve, reject) => {\n        readdir(\n          '/',\n          { withFileTypes: true, encoding: 'buffer' },\n          (err, files) => {\n            if (err) reject(err);\n            else resolve(files);\n          }\n        );\n      });\n      strictEqual(ents.length, 3);\n\n      deepStrictEqual(ents[0].name, Buffer.from('bundle'));\n      strictEqual(ents[0].isDirectory(), true);\n      strictEqual(ents[0].isFile(), false);\n      strictEqual(ents[0].isBlockDevice(), false);\n      strictEqual(ents[0].isCharacterDevice(), false);\n      strictEqual(ents[0].isFIFO(), false);\n      strictEqual(ents[0].isSocket(), false);\n      strictEqual(ents[0].isSymbolicLink(), false);\n      strictEqual(ents[0].parentPath, '/');\n    }\n\n    {\n      const ents = await new Promise((resolve, reject) => {\n        readdir('/', { withFileTypes: true, recursive: true }, (err, files) => {\n          if (err) reject(err);\n          else resolve(files);\n        });\n      });\n      strictEqual(ents.length, 8);\n\n      strictEqual(ents[0].name, 'bundle');\n      strictEqual(ents[0].isDirectory(), true);\n      strictEqual(ents[0].isFile(), false);\n      strictEqual(ents[0].isBlockDevice(), false);\n      strictEqual(ents[0].isCharacterDevice(), false);\n      strictEqual(ents[0].isFIFO(), false);\n      strictEqual(ents[0].isSocket(), false);\n      strictEqual(ents[0].isSymbolicLink(), false);\n      strictEqual(ents[0].parentPath, '/');\n\n      strictEqual(ents[1].name, 'bundle/worker');\n      strictEqual(ents[1].isDirectory(), false);\n      strictEqual(ents[1].isFile(), true);\n      strictEqual(ents[1].isBlockDevice(), false);\n      strictEqual(ents[1].isCharacterDevice(), false);\n      strictEqual(ents[1].isFIFO(), false);\n      strictEqual(ents[1].isSocket(), false);\n      strictEqual(ents[1].isSymbolicLink(), false);\n      strictEqual(ents[1].parentPath, '/bundle');\n\n      strictEqual(ents[4].name, 'dev/null');\n      strictEqual(ents[4].isDirectory(), false);\n      strictEqual(ents[4].isFile(), false);\n      strictEqual(ents[4].isBlockDevice(), false);\n      strictEqual(ents[4].isCharacterDevice(), true);\n      strictEqual(ents[4].isFIFO(), false);\n      strictEqual(ents[4].isSocket(), false);\n      strictEqual(ents[4].isSymbolicLink(), false);\n      strictEqual(ents[4].parentPath, '/dev');\n    }\n  },\n};\n\nexport const readdirAsyncPromiseTest = {\n  async test() {\n    deepStrictEqual(await promises.readdir('/'), ['bundle', 'tmp', 'dev']);\n\n    {\n      const ents = await promises.readdir('/', { withFileTypes: true });\n      strictEqual(ents.length, 3);\n\n      strictEqual(ents[0].name, 'bundle');\n      strictEqual(ents[0].isDirectory(), true);\n      strictEqual(ents[0].isFile(), false);\n      strictEqual(ents[0].isBlockDevice(), false);\n      strictEqual(ents[0].isCharacterDevice(), false);\n      strictEqual(ents[0].isFIFO(), false);\n      strictEqual(ents[0].isSocket(), false);\n      strictEqual(ents[0].isSymbolicLink(), false);\n      strictEqual(ents[0].parentPath, '/');\n    }\n\n    {\n      const ents = await promises.readdir('/', {\n        withFileTypes: true,\n        encoding: 'buffer',\n      });\n      strictEqual(ents.length, 3);\n\n      deepStrictEqual(ents[0].name, Buffer.from('bundle'));\n      strictEqual(ents[0].isDirectory(), true);\n      strictEqual(ents[0].isFile(), false);\n      strictEqual(ents[0].isBlockDevice(), false);\n      strictEqual(ents[0].isCharacterDevice(), false);\n      strictEqual(ents[0].isFIFO(), false);\n      strictEqual(ents[0].isSocket(), false);\n      strictEqual(ents[0].isSymbolicLink(), false);\n      strictEqual(ents[0].parentPath, '/');\n    }\n\n    {\n      const ents = await promises.readdir('/', {\n        withFileTypes: true,\n        recursive: true,\n      });\n      strictEqual(ents.length, 8);\n\n      strictEqual(ents[0].name, 'bundle');\n      strictEqual(ents[0].isDirectory(), true);\n      strictEqual(ents[0].isFile(), false);\n      strictEqual(ents[0].isBlockDevice(), false);\n      strictEqual(ents[0].isCharacterDevice(), false);\n      strictEqual(ents[0].isFIFO(), false);\n      strictEqual(ents[0].isSocket(), false);\n      strictEqual(ents[0].isSymbolicLink(), false);\n      strictEqual(ents[0].parentPath, '/');\n\n      strictEqual(ents[1].name, 'bundle/worker');\n      strictEqual(ents[1].isDirectory(), false);\n      strictEqual(ents[1].isFile(), true);\n      strictEqual(ents[1].isBlockDevice(), false);\n      strictEqual(ents[1].isCharacterDevice(), false);\n      strictEqual(ents[1].isFIFO(), false);\n      strictEqual(ents[1].isSocket(), false);\n      strictEqual(ents[1].isSymbolicLink(), false);\n      strictEqual(ents[1].parentPath, '/bundle');\n      strictEqual(ents[4].name, 'dev/null');\n      strictEqual(ents[4].isDirectory(), false);\n      strictEqual(ents[4].isFile(), false);\n      strictEqual(ents[4].isBlockDevice(), false);\n      strictEqual(ents[4].isCharacterDevice(), true);\n      strictEqual(ents[4].isFIFO(), false);\n      strictEqual(ents[4].isSocket(), false);\n      strictEqual(ents[4].isSymbolicLink(), false);\n      strictEqual(ents[4].parentPath, '/dev');\n    }\n  },\n};\n\nexport const opendirSyncTest = {\n  test() {\n    throws(() => opendirSync(), kInvalidArgTypeError);\n    throws(() => opendirSync(123), kInvalidArgTypeError);\n    throws(() => opendirSync('/tmp', { encoding: 123 }), kInvalidArgValueError);\n\n    const dir = opendirSync('/', { recursive: true });\n    strictEqual(dir.path, '/');\n    strictEqual(dir.readSync().name, 'bundle');\n    strictEqual(dir.readSync().name, 'bundle/worker');\n    strictEqual(dir.readSync().name, 'tmp');\n    strictEqual(dir.readSync().name, 'dev');\n    strictEqual(dir.readSync().name, 'dev/null');\n    strictEqual(dir.readSync().name, 'dev/zero');\n    strictEqual(dir.readSync().name, 'dev/full');\n    strictEqual(dir.readSync().name, 'dev/random');\n    strictEqual(dir.readSync(), null); // All done.\n    dir.closeSync();\n\n    // Closing again throws\n    throws(() => dir.closeSync(), { code: 'ERR_DIR_CLOSED' });\n    // Reading again throws\n    throws(() => dir.readSync(), { code: 'ERR_DIR_CLOSED' });\n  },\n};\n\nexport const opendirSyncAndAsyncTest = {\n  async test() {\n    throws(() => opendir(), kInvalidArgTypeError);\n    throws(() => opendir(123), kInvalidArgTypeError);\n    throws(() => opendir('/tmp', { encoding: 123 }), kInvalidArgValueError);\n\n    const { promise, resolve, reject } = Promise.withResolvers();\n    opendir('/', { recursive: true }, (err, dir) => {\n      if (err) reject(err);\n      else resolve(dir);\n    });\n\n    await using dir = await promise;\n\n    strictEqual((await dir.read()).name, 'bundle');\n    strictEqual((await dir.read()).name, 'bundle/worker');\n    strictEqual((await dir.read()).name, 'tmp');\n    strictEqual((await dir.read()).name, 'dev');\n    strictEqual((await dir.read()).name, 'dev/null');\n    strictEqual((await dir.read()).name, 'dev/zero');\n    strictEqual((await dir.read()).name, 'dev/full');\n    strictEqual((await dir.read()).name, 'dev/random');\n    strictEqual(await dir.read(), null); // All done.\n  },\n};\n\nexport const opendirSyncAndAsyncTest2 = {\n  async test() {\n    throws(() => opendir(), kInvalidArgTypeError);\n    throws(() => opendir(123), kInvalidArgTypeError);\n    throws(() => opendir('/tmp', { encoding: 123 }), kInvalidArgValueError);\n\n    const { promise, resolve, reject } = Promise.withResolvers();\n    opendir('/', { recursive: true }, (err, dir) => {\n      if (err) reject(err);\n      else resolve(dir);\n    });\n\n    await using dir = await promise;\n\n    const entries = await Array.fromAsync(dir);\n\n    strictEqual(entries.length, 8);\n    strictEqual(entries[0].name, 'bundle');\n    strictEqual(entries[1].name, 'bundle/worker');\n    strictEqual(entries[2].name, 'tmp');\n    strictEqual(entries[3].name, 'dev');\n    strictEqual(entries[4].name, 'dev/null');\n    strictEqual(entries[5].name, 'dev/zero');\n    strictEqual(entries[6].name, 'dev/full');\n    strictEqual(entries[7].name, 'dev/random');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-dir-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"fs-dir-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"fs-dir-test.js\"),\n          # Should be ignored due to non-file: URL\n          (name = \"abc:foo\", text = \"abc-foo\"),\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_fs_module\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-filehandle-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport { ok, notStrictEqual, strictEqual, rejects } from 'node:assert';\n\n// sync and datasync are generally non-ops. The most they do is\n// verify that the file descriptor is valid.\n\nimport { promises } from 'node:fs';\n\nexport const openCloseTest = {\n  async test() {\n    const fileHandle = await promises.open('/tmp/test.txt', 'w+');\n    strictEqual(fileHandle.constructor.name, 'FileHandle');\n\n    // If the FileHandle is opened successfully, stat should work.\n    const stat = await fileHandle.stat();\n    ok(stat.isFile());\n\n    // Close the file handle\n    await fileHandle.close();\n\n    // Verify that the file handle is closed\n    await rejects(fileHandle.stat(), {\n      code: 'EBADF',\n    });\n  },\n};\n\nexport const chmodChownTest = {\n  async test() {\n    await using fileHandle = await promises.open('/tmp/test.txt', 'w+');\n\n    const stat = await fileHandle.stat();\n    ok(stat.isFile());\n\n    // Change the file mode\n    await fileHandle.chmod(0o644);\n\n    // Verify the mode has not changed since chmod is a no-op\n    const newStat = await fileHandle.stat();\n    strictEqual(newStat.mode, stat.mode);\n\n    // Change the file ownership\n    await fileHandle.chown(1000, 1000);\n    // Verify the ownership has not changed since chown is a no-op\n    const newStat2 = await fileHandle.stat();\n    strictEqual(newStat2.uid, stat.uid);\n    strictEqual(newStat2.gid, stat.gid);\n  },\n};\n\nexport const syncTest = {\n  async test() {\n    await using fileHandle = await promises.open('/tmp/test.txt', 'w+');\n    strictEqual(fileHandle.constructor.name, 'FileHandle');\n\n    // If the FileHandle is opened successfully, stat should work.\n    const stat = await fileHandle.stat();\n    ok(stat.isFile());\n\n    // These are non-ops with no testable side effect. Just make\n    // sure they don't reject.\n    await fileHandle.sync();\n    await fileHandle.datasync();\n  },\n};\n\nexport const writeAppendReadFileTest = {\n  async test() {\n    await using fileHandle = await promises.open('/tmp/test.txt', 'a+');\n    strictEqual(fileHandle.constructor.name, 'FileHandle');\n\n    await fileHandle.writeFile('Hello, World');\n\n    // Append some data to the file\n    await fileHandle.appendFile('!!!!\\n');\n\n    // Read the file back\n    const data = await fileHandle.readFile('utf8');\n    strictEqual(data, 'Hello, World!!!!\\n');\n  },\n};\n\nexport const writeReadTest = {\n  async test() {\n    // Note that we're opening the file in append mode...\n    await using fileHandle = await promises.open('/tmp/test.txt', 'a+');\n    strictEqual(fileHandle.constructor.name, 'FileHandle');\n\n    // Write some data to the file\n    await fileHandle.write('Hello, World');\n\n    // Append some data to the file\n    await fileHandle.write('!!!!\\n');\n\n    // Read the file back\n    const buffer = Buffer.alloc(100);\n    const { bytesRead } = await fileHandle.read(buffer, 0, buffer.length, 0);\n    strictEqual(bytesRead, 17);\n    strictEqual(buffer.toString('utf8', 0, bytesRead), 'Hello, World!!!!\\n');\n\n    // Use readv\n    const buffer2 = Buffer.alloc(10);\n    const buffer3 = Buffer.alloc(10);\n    const { bytesRead: bytesRead2 } = await fileHandle.readv(\n      [buffer2, buffer3],\n      0\n    );\n    strictEqual(bytesRead2, 17);\n    const buffer4 = Buffer.concat([buffer2, buffer3]);\n    strictEqual(buffer4.toString('utf8', 0, bytesRead2), 'Hello, World!!!!\\n');\n\n    await fileHandle.writev(\n      [Buffer.from('More data'), Buffer.from(' to write\\n')],\n      0\n    );\n    // Read the file back again\n    const buffer5 = Buffer.alloc(100);\n    const { bytesRead: bytesRead3 } = await fileHandle.read(\n      buffer5,\n      0,\n      buffer5.length,\n      0\n    );\n    strictEqual(bytesRead3, 36);\n    strictEqual(\n      buffer5.toString('utf8', 0, bytesRead3),\n      'Hello, World!!!!\\nMore data to write\\n'\n    );\n  },\n};\n\nexport const truncateTest = {\n  async test() {\n    await using fileHandle = await promises.open('/tmp/test.txt', 'w+');\n    strictEqual(fileHandle.constructor.name, 'FileHandle');\n\n    // Write some data to the file\n    await fileHandle.writeFile('Hello, World');\n\n    // Truncate the file to 5 bytes\n    await fileHandle.truncate(5);\n\n    // Read the file back\n    const data = await fileHandle.readFile('utf8');\n    strictEqual(data, 'Hello');\n  },\n};\n\nexport const utimesTest = {\n  async test() {\n    await using fileHandle = await promises.open('/tmp/test.txt', 'w+');\n    strictEqual(fileHandle.constructor.name, 'FileHandle');\n\n    // If the FileHandle is opened successfully, stat should work.\n    const stat = await fileHandle.stat();\n    ok(stat.isFile());\n\n    // Update the access and modification times\n    const now = new Date();\n    await fileHandle.utimes(now, now);\n\n    const newStat = await fileHandle.stat();\n\n    // atime should not be changed, mtime should.\n    strictEqual(newStat.atime.getTime(), stat.atime.getTime());\n    notStrictEqual(newStat.mtime.getTime(), stat.mtime.getTime());\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-filehandle-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  v8Flags = [\"--expose-gc\"],\n  services = [\n    ( name = \"fs-filehandle-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"fs-filehandle-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_fs_module\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-glob-test.js",
    "content": "import { throws } from 'node:assert';\nimport { glob, globSync, promises } from 'node:fs';\n\nfunction mustNotCall() {\n  throw new Error('This function should not be called');\n}\n\nexport const globTest = {\n  test() {\n    // Glob is unsupported currently in workerd.\n    // Verify that we're throwing an error as expected.\n\n    throws(() => glob('*.js', {}, mustNotCall), {\n      code: 'ERR_UNSUPPORTED_OPERATION',\n    });\n\n    throws(() => globSync('*.js', {}), {\n      code: 'ERR_UNSUPPORTED_OPERATION',\n    });\n\n    throws(() => promises.glob('*.js', {}), {\n      code: 'ERR_UNSUPPORTED_OPERATION',\n    });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-glob-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"fs-glob-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"fs-glob-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_fs_module\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-link-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport { deepStrictEqual, ok, rejects, strictEqual, throws } from 'node:assert';\n\nimport {\n  existsSync,\n  statSync,\n  linkSync,\n  lstatSync,\n  symlinkSync,\n  readlinkSync,\n  realpathSync,\n  unlinkSync,\n  link,\n  symlink,\n  readlink,\n  realpath,\n  unlink,\n  promises,\n} from 'node:fs';\n\nstrictEqual(typeof existsSync, 'function');\nstrictEqual(typeof statSync, 'function');\nstrictEqual(typeof linkSync, 'function');\nstrictEqual(typeof lstatSync, 'function');\nstrictEqual(typeof symlinkSync, 'function');\nstrictEqual(typeof readlinkSync, 'function');\nstrictEqual(typeof realpathSync, 'function');\nstrictEqual(typeof unlinkSync, 'function');\nstrictEqual(typeof link, 'function');\nstrictEqual(typeof symlink, 'function');\nstrictEqual(typeof readlink, 'function');\nstrictEqual(typeof realpath, 'function');\nstrictEqual(typeof unlink, 'function');\nstrictEqual(typeof promises.link, 'function');\nstrictEqual(typeof promises.symlink, 'function');\nstrictEqual(typeof promises.readlink, 'function');\nstrictEqual(typeof promises.realpath, 'function');\nstrictEqual(typeof promises.unlink, 'function');\n\nconst src = '/dev/null';\nconst dst = '/tmp/test-link';\n\nconst kInvalidArgTypeError = { code: 'ERR_INVALID_ARG_TYPE' };\nconst kEInvalError = { code: 'EINVAL' };\nconst kENoentError = { code: 'ENOENT' };\nconst kEExistError = { code: 'EEXIST' };\nconst kEPermError = { code: 'EPERM' };\nconst kEIsDir = { code: 'EISDIR' };\n\nexport const linkAndUnlinkSyncTest = {\n  test() {\n    throws(() => linkSync(), kInvalidArgTypeError);\n    throws(() => linkSync(''), kInvalidArgTypeError);\n    throws(() => realpathSync(), kInvalidArgTypeError);\n    throws(() => readlinkSync(), kInvalidArgTypeError);\n    throws(() => unlinkSync(), kInvalidArgTypeError);\n\n    ok(existsSync(src));\n    ok(!existsSync(dst));\n    linkSync(src, dst);\n    ok(existsSync(dst));\n\n    // These are the same file.\n    deepStrictEqual(statSync(dst), statSync(src));\n    deepStrictEqual(lstatSync(dst), statSync(src));\n\n    // Because this is a hard link, the realpath is /tmp/a\n    strictEqual(realpathSync(dst), dst);\n\n    // And readlinkSync throws\n    throws(() => readlinkSync(dst), kEInvalError);\n\n    // Linking from a non-existent file throws.\n    throws(() => linkSync('/does/not/exist', '/tmp/abc'), kENoentError);\n\n    // Make sure the destination does not exist after the failed link.\n    ok(!existsSync('/tmp/abc'));\n    throws(() => linkSync('/dev/foo', '/tmp/abc'), kENoentError);\n    ok(!existsSync('/tmp/abc'));\n\n    // Creating a link in a non-existent directory throws.\n    throws(() => linkSync('/dev/null', '/tmp/abc/xyz'), kENoentError);\n\n    // Linking when the path already exists throws.\n    throws(() => linkSync(src, dst), kEExistError);\n\n    // Linking with an empty file name throws.\n    throws(() => linkSync(src, '/foo/'), kEInvalError);\n\n    // Invalid paths throw\n    throws(() => linkSync(src, '/dev/null/test'), kEInvalError);\n    throws(() => linkSync('/dev/null/test', '/tmp/null/test'), kENoentError);\n\n    // Attempting to link directories throws\n    throws(() => linkSync('/tmp', '/tmp/abc'), kEPermError);\n\n    // The destination can be unlinked\n    unlinkSync(dst);\n    ok(!existsSync(dst));\n\n    // But unlinking does not remove the source.\n    ok(existsSync(src));\n\n    // Cannot unlink read-only files\n    throws(() => unlinkSync('/bundle/worker'), kEPermError);\n    throws(() => unlinkSync('/bundle'), kEIsDir);\n\n    // Make sure that the failed unlink had no effect.\n    ok(existsSync('/bundle/worker'));\n    ok(existsSync('/bundle'));\n  },\n};\n\nexport const linkAndUnlinkAsyncCallbackTest = {\n  async test() {\n    const doLinkTest = async (src, dst) => {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      link(src, dst, (err) => {\n        try {\n          if (err) return reject(err);\n          ok(existsSync(dst));\n          deepStrictEqual(statSync(dst), statSync(src));\n          deepStrictEqual(lstatSync(dst), statSync(src));\n          strictEqual(realpathSync(dst), dst);\n          throws(() => readlinkSync(dst), kEInvalError);\n        } catch (err) {\n          reject(err);\n        }\n        resolve();\n      });\n      await promise;\n    };\n\n    const doFailedLinkTest = async (src, dst, expectedError) => {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      link(src, dst, (err) => {\n        try {\n          strictEqual(err.code, expectedError.code);\n          ok(!existsSync(dst));\n        } catch (err) {\n          reject(err);\n        }\n        resolve();\n      });\n      await promise;\n    };\n\n    const doUnlinkTest = async (dst) => {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      unlink(dst, (err) => {\n        try {\n          if (err) return reject(err);\n          ok(!existsSync(dst));\n          ok(existsSync(src));\n        } catch (err) {\n          reject(err);\n        }\n        resolve();\n      });\n      await promise;\n    };\n\n    const doFailedUnlinkTest = async (dst, expectedError) => {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      unlink(dst, (err) => {\n        try {\n          strictEqual(err.code, expectedError.code);\n          ok(existsSync(dst));\n        } catch (err) {\n          reject(err);\n        }\n        resolve();\n      });\n      await promise;\n    };\n\n    throws(() => link(), kInvalidArgTypeError);\n    throws(() => link(''), kInvalidArgTypeError);\n    throws(() => link('', ''), kInvalidArgTypeError);\n    throws(() => realpath(), kInvalidArgTypeError);\n    throws(() => realpath(''), kInvalidArgTypeError);\n    throws(() => readlink(), kInvalidArgTypeError);\n    throws(() => readlink(''), kInvalidArgTypeError);\n    throws(() => unlink(), kInvalidArgTypeError);\n    throws(() => unlink(''), kInvalidArgTypeError);\n\n    ok(existsSync(src));\n    ok(!existsSync(dst));\n    await doLinkTest(src, dst);\n\n    await doFailedLinkTest('/does/not/exist', '/tmp/abc', kENoentError);\n    ok(!existsSync('/tmp/abc'));\n\n    await doFailedLinkTest('/dev/foo', '/tmp/abc', kENoentError);\n    ok(!existsSync('/tmp/abc'));\n\n    // Creating a link in a non-existent directory throws.\n    await doFailedLinkTest('/dev/null', '/tmp/abc/xyz', kENoentError);\n\n    // Invalid paths throw\n    await doFailedLinkTest(src, '/dev/null/test', kEInvalError);\n    await doFailedLinkTest('/dev/null/test', '/tmp/null/test', kENoentError);\n\n    // Attempting to link directories throws\n    await doFailedLinkTest('/tmp', '/tmp/abc', kEPermError);\n\n    await doUnlinkTest(dst);\n\n    await doFailedUnlinkTest('/bundle/worker', kEPermError);\n    await doFailedUnlinkTest('/bundle', kEIsDir);\n  },\n};\n\nexport const linkAndLinkPromisesTest = {\n  async test() {\n    await rejects(promises.link(), kInvalidArgTypeError);\n    await rejects(promises.link(''), kInvalidArgTypeError);\n    await rejects(promises.realpath(), kInvalidArgTypeError);\n    await rejects(promises.readlink(), kInvalidArgTypeError);\n    await rejects(promises.unlink(), kInvalidArgTypeError);\n\n    ok(existsSync(src));\n    ok(!existsSync(dst));\n    await promises.link(src, dst);\n\n    // These are the same file.\n    deepStrictEqual(statSync(dst), statSync(src));\n    deepStrictEqual(lstatSync(dst), statSync(src));\n\n    // Because this is a hard link, the realpath is /tmp/a\n    strictEqual(await promises.realpath(dst), dst);\n\n    // And readlinkSync throws\n    await rejects(promises.readlink(dst), kEInvalError);\n\n    await rejects(promises.link('/does/not/exist', '/tmp/abc'), kENoentError);\n    ok(!existsSync('/tmp/abc'));\n\n    await rejects(promises.link('/dev/null', '/tmp/abc/xyz'), kENoentError);\n\n    await rejects(promises.link(src, dst), kEExistError);\n\n    await rejects(promises.link(src, '/foo/'), kEInvalError);\n\n    await rejects(promises.link(src, '/dev/null/test'), kEInvalError);\n\n    await rejects(\n      promises.link('/dev/null/test', '/tmp/null/test'),\n      kENoentError\n    );\n\n    await rejects(promises.link('/tmp', '/tmp/abc'), kEPermError);\n\n    await promises.unlink(dst);\n    ok(!existsSync(dst));\n    // But unlinking does not remove the source.\n    ok(existsSync(src));\n\n    await rejects(promises.unlink('/bundle/worker'), kEPermError);\n    await rejects(promises.unlink('/bundle'), kEIsDir);\n  },\n};\n\nexport const symlinkAndUnlinkSyncTest = {\n  test() {\n    throws(() => symlinkSync(), kInvalidArgTypeError);\n    throws(() => symlinkSync(''), kInvalidArgTypeError);\n\n    ok(existsSync(src));\n    ok(!existsSync(dst));\n    symlinkSync(src, dst);\n    ok(existsSync(dst));\n\n    deepStrictEqual(statSync(dst), statSync(src));\n\n    // The symlink points to the source.\n    strictEqual(readlinkSync(dst), src);\n\n    // The realpath is the destination\n    strictEqual(realpathSync(dst), src);\n\n    // And readlinkSync does not throw\n    strictEqual(readlinkSync(dst), src);\n\n    // Linking from a non-existent file works\n    symlinkSync('/does/not/exist', '/tmp/abc');\n\n    // Exists sync follows the symlink, and show return false.\n    ok(!existsSync('/tmp/abc'));\n\n    // But the symlink does exist.\n    const linkStat = lstatSync('/tmp/abc');\n    ok(linkStat.isSymbolicLink());\n\n    // Creating a link in a non-existent directory throws.\n    throws(() => symlinkSync('/dev/null', '/tmp/abc/xyz'), kENoentError);\n\n    // Linking when the path already exists throws.\n    throws(() => symlinkSync(src, dst), kEExistError);\n\n    // Linking with an empty file name throws.\n    throws(() => symlinkSync(src, '/foo/'), kEInvalError);\n\n    // Invalid paths throw\n    throws(() => symlinkSync(src, '/dev/null/test'), kEInvalError);\n    throws(() => symlinkSync('/dev/null/test', '/tmp/null/test'), kENoentError);\n\n    // Symlinking directories works.\n    symlinkSync('/tmp', '/tmp/linked-dir');\n    ok(existsSync('/tmp/linked-dir'));\n\n    // The destination can be unlinked\n    unlinkSync(dst);\n    ok(!existsSync(dst));\n\n    // But unlinking does not remove the source.\n    ok(existsSync(src));\n  },\n};\n\nexport const symlinkAndUnlinkAsyncCallbackTest = {\n  async test() {\n    throws(() => symlink(), kInvalidArgTypeError);\n    throws(() => symlink(''), kInvalidArgTypeError);\n    throws(() => symlink('', ''), kInvalidArgTypeError);\n    throws(() => realpath(), kInvalidArgTypeError);\n    throws(() => realpath(''), kInvalidArgTypeError);\n    throws(() => readlink(), kInvalidArgTypeError);\n    throws(() => readlink(''), kInvalidArgTypeError);\n    throws(() => unlink(), kInvalidArgTypeError);\n    throws(() => unlink(''), kInvalidArgTypeError);\n\n    const doSymlinkTest = async (src, dst) => {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      symlink(src, dst, (err) => {\n        if (err) return reject(err);\n        try {\n          const srcStat = statSync(src, { throwIfNoEntry: false });\n          if (srcStat != null) {\n            const dstStat = statSync(dst, { throwIfNoEntry: false });\n            deepStrictEqual(dstStat, srcStat);\n          } else {\n            // The source does not exist but the symlink should still.\n            const dstStat = lstatSync(dst);\n            ok(dstStat.isSymbolicLink());\n          }\n          strictEqual(readlinkSync(dst), src);\n          strictEqual(realpathSync(dst), src);\n        } catch (err) {\n          reject(err);\n        }\n        resolve();\n      });\n      await promise;\n    };\n\n    const doFailedSymlinkTest = async (src, dst, expectedError) => {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      symlink(src, dst, (err) => {\n        try {\n          strictEqual(err.code, expectedError.code);\n        } catch (err) {\n          reject(err);\n        }\n        resolve();\n      });\n      await promise;\n    };\n\n    const doUnlinkTest = async (dst) => {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      unlink(dst, (err) => {\n        try {\n          if (err) return reject(err);\n          ok(!existsSync(dst));\n          ok(existsSync(src));\n        } catch (err) {\n          reject(err);\n        }\n        resolve();\n      });\n      await promise;\n    };\n\n    ok(existsSync(src));\n    ok(!existsSync(dst));\n    await doSymlinkTest(src, dst);\n    await doSymlinkTest('/does/not/exist', '/tmp/abc');\n\n    // Exists sync follows the symlink, and show return false.\n    ok(!existsSync('/tmp/abc'));\n\n    await doFailedSymlinkTest(src, dst, kEExistError);\n    await doFailedSymlinkTest(src, '/foo/', kEInvalError);\n    await doFailedSymlinkTest(src, '/dev/null/test', kEInvalError);\n    await doFailedSymlinkTest('/dev/null/test', '/tmp/null/test', kENoentError);\n\n    // Symlinking directories works.\n    await doSymlinkTest('/tmp', '/tmp/linked-dir');\n\n    // The destination can be unlinked\n    await doUnlinkTest(dst);\n  },\n};\n\nexport const symlinkAndUnlinkPromisesTest = {\n  async test() {\n    await rejects(promises.symlink(), kInvalidArgTypeError);\n    await rejects(promises.symlink(''), kInvalidArgTypeError);\n    await rejects(promises.realpath(), kInvalidArgTypeError);\n    await rejects(promises.readlink(), kInvalidArgTypeError);\n    await rejects(promises.unlink(), kInvalidArgTypeError);\n\n    ok(existsSync(src));\n    ok(!existsSync(dst));\n    await promises.symlink(src, dst);\n\n    deepStrictEqual(statSync(dst), statSync(src));\n\n    // The symlink points to the source.\n    strictEqual(await promises.readlink(dst), src);\n\n    // The realpath is the destination\n    strictEqual(await promises.realpath(dst), src);\n\n    // And readlink does not throw\n    strictEqual(await promises.readlink(dst), src);\n\n    // Linking from a non-existent file works\n    await promises.symlink('/does/not/exist', '/tmp/abc');\n\n    // Exists sync follows the symlink, and show return false.\n    ok(!existsSync('/tmp/abc'));\n\n    // But the symlink does exist.\n    const linkStat = lstatSync('/tmp/abc');\n    ok(linkStat.isSymbolicLink());\n\n    // Creating a link in a non-existent directory throws.\n    await rejects(promises.symlink('/dev/null', '/tmp/abc/xyz'), kENoentError);\n\n    // Linking when the path already exists throws.\n    await rejects(promises.symlink(src, dst), kEExistError);\n\n    // Linking with an empty file name throws.\n    await rejects(promises.symlink(src, '/foo/'), kEInvalError);\n\n    // Invalid paths throw\n    await rejects(promises.symlink(src, '/dev/null/test'), kEInvalError);\n    await rejects(\n      promises.symlink('/dev/null/test', '/tmp/null/test'),\n      kENoentError\n    );\n\n    // Symlinking directories works.\n    await promises.symlink('/tmp', '/tmp/linked-dir');\n    ok(existsSync('/tmp/linked-dir'));\n\n    // The destination can be unlinked\n    await promises.unlink(dst);\n    ok(!existsSync(dst));\n\n    // But unlinking does not remove the source.\n    ok(existsSync(src));\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-link-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"fs-link-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"fs-link-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_fs_module\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-misc-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport { notStrictEqual, strictEqual, throws } from 'node:assert';\n\n// sync and datasync are generally non-ops. The most they do is\n// verify that the file descriptor is valid.\n\nimport {\n  openSync,\n  closeSync,\n  fsyncSync,\n  fdatasyncSync,\n  fdatasync,\n  fsync,\n  statfs,\n  statfsSync,\n  openAsBlob,\n  WriteStream,\n  FileWriteStream,\n  ReadStream,\n  FileReadStream,\n  constants,\n  F_OK,\n  R_OK,\n  W_OK,\n  X_OK,\n  writeFileSync,\n  writeSync,\n} from 'node:fs';\n\nstrictEqual(typeof openSync, 'function');\nstrictEqual(typeof closeSync, 'function');\nstrictEqual(typeof fdatasyncSync, 'function');\nstrictEqual(typeof fsyncSync, 'function');\nstrictEqual(typeof fdatasync, 'function');\nstrictEqual(typeof fsync, 'function');\n\nconst kInvalidArgTypeError = { code: 'ERR_INVALID_ARG_TYPE' };\nconst kBadFError = { code: 'EBADF' };\n\nexport const miscTest = {\n  async test() {\n    throws(() => fsyncSync(), kInvalidArgTypeError);\n    throws(() => fdatasyncSync(), kInvalidArgTypeError);\n    throws(() => fsyncSync('hello'), kInvalidArgTypeError);\n    throws(() => fdatasyncSync('hello'), kInvalidArgTypeError);\n    throws(() => fsync(), kInvalidArgTypeError);\n    throws(() => fdatasync(), kInvalidArgTypeError);\n    throws(() => fsync('hello'), kInvalidArgTypeError);\n    throws(() => fdatasync('hello'), kInvalidArgTypeError);\n    throws(() => fsyncSync(123), kBadFError);\n    throws(() => fdatasyncSync(123), kBadFError);\n\n    const fd = openSync('/dev/null', 'r');\n    fsyncSync(fd);\n    fdatasyncSync(fd);\n\n    {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      fsync(fd, (err) => {\n        if (err) {\n          reject(err);\n          return;\n        }\n        resolve();\n      });\n      await promise;\n    }\n\n    {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      fdatasync(fd, (err) => {\n        if (err) {\n          reject(err);\n          return;\n        }\n        resolve();\n      });\n      await promise;\n    }\n\n    {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      fsync(123, (err) => {\n        if (!err) {\n          reject(new Error('Expected an error'));\n          return;\n        }\n        strictEqual(err.code, 'EBADF');\n        resolve();\n      });\n      await promise;\n    }\n\n    {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      fdatasync(123, (err) => {\n        if (!err) {\n          reject(new Error('Expected an error'));\n          return;\n        }\n        strictEqual(err.code, 'EBADF');\n        resolve();\n      });\n      await promise;\n    }\n\n    closeSync(fd);\n  },\n};\n\nexport const statFsTest = {\n  async test() {\n    const stat = statfsSync('/');\n    strictEqual(typeof stat, 'object');\n    strictEqual(stat.type, 0);\n    strictEqual(stat.bsize, 0);\n    strictEqual(stat.blocks, 0);\n    strictEqual(stat.bfree, 0);\n    strictEqual(stat.bavail, 0);\n    strictEqual(stat.files, 0);\n    strictEqual(stat.ffree, 0);\n\n    throws(() => statfsSync(123), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    throws(() => statfsSync('/does/not/exist', { bigint: 123 }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    const { promise, resolve, reject } = Promise.withResolvers();\n    statfs('/', (err, stat) => {\n      if (err) reject(err);\n      else {\n        strictEqual(typeof stat, 'object');\n        strictEqual(stat.type, 0);\n        strictEqual(stat.bsize, 0);\n        strictEqual(stat.blocks, 0);\n        strictEqual(stat.bfree, 0);\n        strictEqual(stat.bavail, 0);\n        strictEqual(stat.files, 0);\n        strictEqual(stat.ffree, 0);\n        resolve();\n      }\n    });\n    await promise;\n\n    throws(() => statfs(123, () => {}), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    throws(() => statfs('/does/not/exist', { bigint: 123 }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n  },\n};\n\nexport const pathLimitTest = {\n  test() {\n    // Trying to open a path longer than 4096 characters should throw an error.\n    const longPath = '/tmp/a'.repeat(4097);\n    throws(() => openSync(longPath, 'r'), {\n      message: /File path is too long/,\n    });\n\n    // Trying to open a path with more than 48 segments should throw an error.\n    const tooManySegments = '/a'.repeat(49);\n    throws(() => openSync(tooManySegments, 'r'), {\n      message: /File path has too many segments/,\n    });\n  },\n};\n\nexport const openAsBlobTest = {\n  async test() {\n    // We currently do not implement openAsBlob, but let's verify arg validation.\n    throws(() => openAsBlob(), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => openAsBlob(1), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => openAsBlob('/', 123), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => openAsBlob('/', { type: 123 }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    writeFileSync('/tmp/abc', '123');\n    throws(() => openAsBlob('/'), {\n      message: /illegal operation on a directory/,\n    });\n    throws(() => openAsBlob(new URL('file:///')), {\n      message: /illegal operation on a directory/,\n    });\n\n    const blob = openAsBlob('/tmp/abc', { type: 'text/plain' });\n    strictEqual(blob instanceof Blob, true);\n    strictEqual(blob.size, 3);\n    strictEqual(blob.type, 'text/plain');\n    strictEqual(await blob.text(), '123');\n  },\n};\n\nexport const otherExportsTest = {\n  test() {\n    strictEqual(WriteStream, FileWriteStream);\n    strictEqual(ReadStream, FileReadStream);\n    strictEqual(constants.F_OK, F_OK);\n    strictEqual(constants.R_OK, R_OK);\n    strictEqual(constants.W_OK, W_OK);\n    strictEqual(constants.X_OK, X_OK);\n    notStrictEqual(F_OK, undefined);\n    notStrictEqual(R_OK, undefined);\n    notStrictEqual(W_OK, undefined);\n    notStrictEqual(X_OK, undefined);\n    notStrictEqual(WriteStream, undefined);\n    notStrictEqual(ReadStream, undefined);\n  },\n};\n\nexport const oobWriteTest = {\n  test() {\n    const v3 = Buffer.from('Test data for write operations');\n    const fd = openSync('/tmp/write-test.bin');\n    throws(\n      () => {\n        strictEqual(writeSync(fd, v3, 10, 10, 4294967295), 0);\n      },\n      {\n        message: /File size limit exceeded/,\n      }\n    );\n\n    strictEqual(writeSync(fd, v3, 10, 10, 134217718), 10);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-misc-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"fs-misc-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"fs-misc-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_fs_module\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-nodejs-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport {\n  ifError,\n  ok,\n  rejects,\n  deepStrictEqual,\n  strictEqual,\n  notStrictEqual,\n  throws,\n  doesNotThrow,\n} from 'node:assert';\nimport zlib from 'node:zlib';\nimport { Readable } from 'node:stream';\n\nimport {\n  existsSync,\n  statSync,\n  openSync,\n  closeSync,\n  fstatSync,\n  ftruncateSync,\n  truncateSync,\n  writeSync,\n  writevSync,\n  readSync,\n  readvSync,\n  readFileSync,\n  writeFileSync,\n  appendFileSync,\n  copyFileSync,\n  renameSync,\n  close,\n  fstat,\n  ftruncate,\n  truncate,\n  unlinkSync,\n  write,\n  writev,\n  writeFile,\n  appendFile,\n  read,\n  readdir,\n  readlink,\n  realpath,\n  mkdtemp,\n  ReadStream,\n  WriteStream,\n  readdirSync,\n  readlinkSync,\n  readv,\n  readFile,\n  rename,\n  copyFile,\n  realpathSync,\n  mkdtempSync,\n  constants,\n  promises,\n  createWriteStream,\n} from 'node:fs';\n\nimport { join } from 'node:path';\n\nconst { COPYFILE_EXCL } = constants;\n\nconst kErrInvalidArgType = { code: 'ERR_INVALID_ARG_TYPE' };\nconst kErrInvalidArgValue = { code: 'ERR_INVALID_ARG_VALUE' };\nconst kErrEBadf = { code: 'EBADF' };\nconst kErrEExist = { code: 'EEXIST' };\nconst kErrOutOfRange = { code: 'ERR_OUT_OF_RANGE' };\n\nexport const openCloseTest = {\n  async test() {\n    throws(() => fstatSync(123), kErrEBadf);\n    throws(() => fstatSync(123, { bigint: 'yes' }), kErrInvalidArgType);\n    throws(() => fstatSync('abc'), kErrInvalidArgType);\n\n    // Test that all the mode combinations work\n    const modes = [\n      'r',\n      'r+',\n      'w',\n      'w+',\n      'a',\n      'a+',\n      'rs',\n      'rs+',\n      'wx',\n      'wx+',\n      'ax',\n      'ax+',\n    ];\n    for (const mode of modes) {\n      // Open the file\n      const fd = openSync('/tmp/test.txt', mode);\n      ok(existsSync('/tmp/test.txt'));\n      const stat = fstatSync(fd, { bigint: true });\n      ok(stat);\n      unlinkSync('/tmp/test.txt');\n    }\n\n    ok(!existsSync('/tmp/test.txt'));\n    const fd = openSync('/tmp/test.txt', 'w+');\n    ok(existsSync('/tmp/test.txt'));\n\n    // Check the exclusive option fails when the file already exists\n    throws(() => openSync('/tmp/test.txt', 'wx'), kErrEExist);\n    throws(() => openSync('/tmp/test.txt', 'wx+'), kErrEExist);\n    throws(() => openSync('/tmp/test.txt', 'ax'), kErrEExist);\n    throws(() => openSync('/tmp/test.txt', 'ax+'), kErrEExist);\n    throws(() => openSync(Buffer.from('/tmp/test.txt'), 'wx'), kErrEExist);\n    throws(() => openSync(Buffer.from('/tmp/test.txt'), 'wx+'), kErrEExist);\n    throws(() => openSync(Buffer.from('/tmp/test.txt'), 'ax'), kErrEExist);\n    throws(() => openSync(Buffer.from('/tmp/test.txt'), 'ax+'), kErrEExist);\n\n    const stat = fstatSync(fd, { bigint: true });\n    ok(stat);\n    ok(stat.isFile());\n    ok(!stat.isDirectory());\n    strictEqual(stat.size, 0n);\n\n    {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      fstat(fd, (err, stat) => {\n        try {\n          ifError(err);\n          ok(stat);\n          ok(stat.isFile());\n          ok(!stat.isDirectory());\n          resolve();\n        } catch (err) {\n          reject(err);\n        }\n      });\n      await promise;\n    }\n\n    // Close the file\n    closeSync(fd);\n    // Can close multiple times\n    closeSync(fd);\n    // Can close non-existent file descriptors\n    closeSync(123);\n\n    ['a', {}, null].forEach((i) => {\n      throws(() => closeSync(i), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n      throws(() => close(i), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n      throws(() => close(0, i), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n    });\n\n    {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      close(fd, (err) => {\n        if (err) return reject(err);\n        resolve();\n      });\n      await promise;\n    }\n  },\n};\n\nexport const ftruncateTest = {\n  async test() {\n    const fd = openSync('/tmp/test.txt', 'w+');\n    ok(existsSync('/tmp/test.txt'));\n\n    throws(() => ftruncateSync('hello'), kErrInvalidArgType);\n    throws(() => ftruncateSync(123), kErrEBadf);\n    throws(() => ftruncateSync(123, 10), kErrEBadf);\n    throws(() => ftruncateSync(fd, 'hello'), kErrInvalidArgType);\n\n    const stat = fstatSync(fd, { bigint: true });\n    strictEqual(stat.size, 0n);\n\n    // Truncate to 10 bytes\n    ftruncateSync(fd, 10);\n    const stat2 = fstatSync(fd, { bigint: true });\n    strictEqual(stat2.size, 10n);\n\n    // Truncate to 0 bytes\n    ftruncateSync(fd, 0);\n    const stat3 = fstatSync(fd, { bigint: true });\n    strictEqual(stat3.size, 0n);\n\n    // Truncate to a negative size throws an error\n    throws(() => ftruncateSync(fd, -1), kErrOutOfRange);\n\n    {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      ftruncate(fd, 5, (err) => {\n        if (err) return reject(err);\n        const stat4 = fstatSync(fd, { bigint: true });\n        strictEqual(stat4.size, 5n);\n        resolve();\n      });\n      await promise;\n    }\n\n    {\n      throws(() => ftruncate(fd, -1, () => {}), {\n        code: 'ERR_OUT_OF_RANGE',\n      });\n    }\n\n    {\n      throws(() => ftruncateSync(fd, 0xffffffff), {\n        message: /File size limit exceeded/,\n      });\n      throws(() => ftruncateSync(fd, 0x08000000 + 1), {\n        message: /File size limit exceeded/,\n      });\n      // 0x08000000 is the maximum allowed file size.\n      ftruncateSync(fd, 0x08000000);\n    }\n\n    closeSync(fd);\n  },\n};\n\nexport const truncateTest = {\n  async test() {\n    throws(() => truncateSync(123), kErrInvalidArgType);\n    throws(() => truncateSync('/', 'hello'), kErrInvalidArgType);\n\n    ok(!existsSync('/tmp/test.txt'));\n    throws(() => truncateSync('/tmp/test.txt', 10), {\n      code: 'ENOENT',\n      path: '/tmp/test.txt',\n    });\n\n    // Create the file\n    writeFileSync('/tmp/test.txt', 'Hello World');\n    ok(existsSync('/tmp/test.txt'));\n    let stat = statSync('/tmp/test.txt');\n    strictEqual(stat.size, 11);\n\n    // Truncate to 10 bytes\n    truncateSync('/tmp/test.txt', 10);\n    stat = statSync('/tmp/test.txt');\n    strictEqual(stat.size, 10);\n\n    // Truncate to 20 bytes\n    truncateSync('/tmp/test.txt', 20);\n    stat = statSync('/tmp/test.txt');\n    strictEqual(stat.size, 20);\n\n    // Truncate to 0 bytes\n    truncateSync('/tmp/test.txt', 0);\n    stat = statSync('/tmp/test.txt');\n    strictEqual(stat.size, 0);\n\n    // Truncate to a negative size throws an error\n    throws(() => truncateSync('/tmp/test.txt', -1), kErrOutOfRange);\n\n    {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      truncate('/tmp/test.txt', 5, (err) => {\n        if (err) return reject(err);\n        const stat2 = statSync('/tmp/test.txt');\n        strictEqual(stat2.size, 5);\n        resolve();\n      });\n      await promise;\n    }\n\n    throws(() => truncate('/tmp/test.txt', -1), kErrOutOfRange);\n  },\n};\n\nexport const writeSyncTest = {\n  test() {\n    const fd = openSync('/tmp/test.txt', 'w+');\n    ok(existsSync('/tmp/test.txt'));\n\n    const stat = fstatSync(fd, { bigint: true });\n    strictEqual(stat.size, 0n);\n\n    throws(() => writeSync(''), kErrInvalidArgType);\n    throws(() => writeSync(fd, 'Hello World', ''), kErrInvalidArgType);\n    throws(\n      () => writeSync(fd, 'Hello World', { position: 'hello' }),\n      kErrInvalidArgType\n    );\n    throws(() => writeSync(123, 'Hello World'), kErrEBadf);\n    throws(() => writeSync(fd, Buffer.alloc(2), { offset: 5 }), {\n      code: 'ERR_BUFFER_OUT_OF_BOUNDS',\n    });\n    throws(() => writeSync(fd, Buffer.alloc(2), { length: 5 }), {\n      code: 'ERR_BUFFER_OUT_OF_BOUNDS',\n    });\n    throws(\n      () => writeSync(fd, Buffer.alloc(2), { offset: 'hello' }),\n      kErrInvalidArgType\n    );\n\n    writeSync(fd, 'Hello World');\n    writeSync(fd, '!!!!');\n    const stat2 = fstatSync(fd, { bigint: true });\n    strictEqual(stat2.size, 15n);\n\n    const dest = Buffer.alloc(Number(stat2.size));\n\n    // When we don't specify a position, it reads from the current position,\n    // which currently is the end of the file... so we get nothing here.\n    strictEqual(readSync(fd, dest), 0);\n\n    // But when we do specify a position, we can read from the beginning...\n    strictEqual(readSync(fd, dest, 0, dest.byteLength, 0), dest.byteLength);\n    strictEqual(dest.toString(), 'Hello World!!!!');\n\n    // Likewise, we can use an options object for the position\n    dest.fill(0);\n    strictEqual(dest.toString(), '\\0'.repeat(dest.byteLength));\n    strictEqual(readSync(fd, dest, { position: 0 }), 15);\n    strictEqual(dest.toString(), 'Hello World!!!!');\n\n    const dest2 = readFileSync('/tmp/test.txt');\n    const dest3 = readFileSync(fd);\n    const dest4 = readFileSync(fd, { encoding: 'utf8' });\n    strictEqual(dest2.toString(), 'Hello World!!!!');\n    strictEqual(dest3.toString(), 'Hello World!!!!');\n    strictEqual(dest4, 'Hello World!!!!');\n\n    closeSync(fd);\n  },\n};\n\nexport const writeAsyncCallbackTest = {\n  async test() {\n    const fd = openSync('/tmp/test.txt', 'w+');\n    ok(existsSync('/tmp/test.txt'));\n\n    const stat = fstatSync(fd, { bigint: true });\n    strictEqual(stat.size, 0n);\n\n    function mustNotCall() {\n      throw new Error('This function must not be called');\n    }\n\n    throws(() => write(fd, 'Hello World'), kErrInvalidArgType);\n    throws(() => write(fd, 'Hello World', '', mustNotCall), kErrInvalidArgType);\n    throws(\n      () => write(fd, 'Hello World', { position: 'hello' }, mustNotCall),\n      kErrInvalidArgType\n    );\n    throws(() => write(fd, Buffer.alloc(2), { offset: 5 }, mustNotCall), {\n      code: 'ERR_BUFFER_OUT_OF_BOUNDS',\n    });\n    throws(() => write(fd, Buffer.alloc(2), { length: 5 }, mustNotCall), {\n      code: 'ERR_BUFFER_OUT_OF_BOUNDS',\n    });\n    throws(\n      () => write(fd, Buffer.alloc(2), { offset: 'hello' }, mustNotCall),\n      kErrInvalidArgType\n    );\n\n    await new Promise((resolve, reject) => {\n      write(fd, 'Hello World', (err, written) => {\n        if (err) return reject(err);\n        strictEqual(written, 11);\n        resolve();\n      });\n    });\n\n    await new Promise((resolve, reject) => {\n      write(fd, '!!!!', (err, written) => {\n        if (err) return reject(err);\n        strictEqual(written, 4);\n        resolve();\n      });\n    });\n\n    await rejects(\n      new Promise((resolve, reject) => {\n        write(123, 'Hello World', { position: 0 }, (err) => {\n          if (err) return reject(err);\n          resolve();\n        });\n      }),\n      kErrEBadf\n    );\n\n    const stat2 = fstatSync(fd, { bigint: true });\n    strictEqual(stat2.size, 15n);\n\n    const dest = Buffer.alloc(Number(stat2.size));\n\n    // When we don't specify a position, it reads from the current position,\n    // which currently is the end of the file... so we get nothing here.\n    strictEqual(readSync(fd, dest), 0);\n\n    // But when we do specify a position, we can read from the beginning...\n    strictEqual(readSync(fd, dest, 0, dest.byteLength, 0), dest.byteLength);\n    strictEqual(dest.toString(), 'Hello World!!!!');\n\n    closeSync(fd);\n  },\n};\n\nexport const writeSyncTest2 = {\n  test() {\n    const fd = openSync('/tmp/test.txt', 'w+');\n    ok(existsSync('/tmp/test.txt'));\n\n    const stat = fstatSync(fd, { bigint: true });\n    strictEqual(stat.size, 0n);\n\n    strictEqual(writeSync(fd, 'Hello World', 2n), 11);\n\n    // Writing to a position beyond max uint32_t is not allowed.\n    throws(() => writeSync(fd, 'Hello World', 2n ** 32n), {\n      code: 'EINVAL',\n    });\n    throws(() => writeSync(fd, 'Hello World', 2 ** 32), {\n      code: 'ERR_OUT_OF_RANGE',\n    });\n\n    strictEqual(writeSync(fd, 'aa', 0, 'ascii'), 2);\n\n    const stat2 = fstatSync(fd);\n    strictEqual(stat2.size, 13);\n\n    const dest = Buffer.alloc(stat2.size);\n    strictEqual(readSync(fd, dest, 0, dest.byteLength, 0), 13);\n    strictEqual(dest.toString(), 'aaHello World');\n\n    closeSync(fd);\n  },\n};\n\nexport const writeSyncTest3 = {\n  test() {\n    const fd = openSync('/tmp/test.txt', 'w+');\n    ok(existsSync('/tmp/test.txt'));\n\n    const stat = fstatSync(fd, { bigint: true });\n    strictEqual(stat.size, 0n);\n\n    writeSync(fd, Buffer.from('Hello World'));\n    const stat2 = fstatSync(fd, { bigint: true });\n    strictEqual(stat2.size, 11n);\n\n    closeSync(fd);\n  },\n};\n\nexport const writeSyncTest4 = {\n  test() {\n    const fd = openSync('/tmp/test.txt', 'w+');\n    ok(existsSync('/tmp/test.txt'));\n\n    const stat = fstatSync(fd, { bigint: true });\n    strictEqual(stat.size, 0n);\n\n    // Writing a partial buffer works\n    writeSync(fd, Buffer.from('Hello World'), 1, 3, 1);\n\n    // Specifying an offset or length beyond the buffer size is not allowed.\n    throws(() => writeSync(fd, Buffer.from('Hello World'), 100, 3), {\n      message: /outside of buffer bounds/,\n    });\n    // Specifying an offset or length beyond the buffer size is not allowed.\n    throws(() => writeSync(fd, Buffer.from('Hello World'), 0, 100), {\n      message: /outside of buffer bounds/,\n    });\n\n    throws(() => writeSync(fd, Buffer.from('hello world'), 'a'), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    throws(() => writeSync(fd, Buffer.from('hello world'), 1n), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    throws(() => writeSync(fd, Buffer.from('hello world'), 0, 'a'), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    throws(() => writeSync(fd, Buffer.from('hello world'), 1, 1n), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    throws(() => writeSync(fd, 123), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    const stat2 = fstatSync(fd, { bigint: true });\n    strictEqual(stat2.size, 4n);\n\n    closeSync(fd);\n  },\n};\n\nexport const writeSyncAppend = {\n  test() {\n    const fd = openSync('/tmp/test.txt', 'a');\n    ok(existsSync('/tmp/test.txt'));\n\n    const stat = fstatSync(fd, { bigint: true });\n    strictEqual(stat.size, 0n);\n\n    // In append mode, the position is ignored.\n\n    writeSync(fd, 'Hello World', 1000);\n    const stat2 = fstatSync(fd, { bigint: true });\n    strictEqual(stat2.size, 11n);\n\n    writeSync(fd, '!!!!', 2000);\n    const stat3 = fstatSync(fd, { bigint: true });\n    strictEqual(stat3.size, 15n);\n\n    closeSync(fd);\n  },\n};\n\nexport const writevSyncTest = {\n  test() {\n    const fd = openSync('/tmp/test.txt', 'w+');\n    ok(existsSync('/tmp/test.txt'));\n\n    const stat = fstatSync(fd, { bigint: true });\n    strictEqual(stat.size, 0n);\n\n    writevSync(fd, [Buffer.from('Hello World'), Buffer.from('!!!!')]);\n\n    throws(() => writevSync(fd, [1, 2]), {\n      code: /ERR_INVALID_ARG_TYPE/,\n    });\n\n    throws(() => writevSync(100, [Buffer.from('')]), {\n      message: 'bad file descriptor',\n      code: 'EBADF',\n    });\n\n    const stat2 = fstatSync(fd, { bigint: true });\n    strictEqual(stat2.size, 15n);\n\n    const dest1 = Buffer.alloc(5);\n    const dest2 = Buffer.alloc(10);\n    const dest3 = Buffer.alloc(5);\n    let read = readvSync(fd, [dest1, dest2, dest3], 0);\n    strictEqual(read, 15);\n    let dest = Buffer.concat([dest1, dest2, dest3]);\n    strictEqual(dest.toString('utf8', 0, read), 'Hello World!!!!');\n\n    dest1.fill(0);\n    dest2.fill(0);\n    dest3.fill(0);\n    read = readvSync(fd, [dest1, dest2, dest3], 1);\n    strictEqual(read, 14);\n    dest = Buffer.concat([dest1, dest2, dest3]);\n    strictEqual(dest.toString('utf8', 0, read), 'ello World!!!!');\n\n    // Reading from a position beyond the end of the file returns nothing.\n    strictEqual(readvSync(fd, [dest1], 100), 0);\n\n    closeSync(fd);\n  },\n};\n\nexport const writevAsyncCallbackTest = {\n  async test() {\n    const fd = openSync('/tmp/test.txt', 'w+');\n    ok(existsSync('/tmp/test.txt'));\n\n    const stat = fstatSync(fd, { bigint: true });\n    strictEqual(stat.size, 0n);\n\n    throws(() => writev('hello'), kErrInvalidArgType);\n    throws(() => writev(0, 123), kErrInvalidArgType);\n    throws(() => writev(fd, [Buffer.from('')], 123), kErrInvalidArgType);\n\n    await new Promise((resolve, reject) => {\n      writev(\n        fd,\n        [Buffer.from('Hello World'), Buffer.from('!!!!')],\n        (err, written) => {\n          if (err) return reject(err);\n          strictEqual(written, 15);\n          resolve();\n        }\n      );\n    });\n\n    throws(\n      () =>\n        writev(fd, [1, 2], (err) => {\n          if (err) throw err;\n        }),\n      {\n        code: /ERR_INVALID_ARG_TYPE/,\n      }\n    );\n\n    await rejects(\n      new Promise((resolve, reject) => {\n        writev(100, [Buffer.from('')], (err) => {\n          if (err) return reject(err);\n          resolve();\n        });\n      }),\n      {\n        code: 'EBADF',\n      }\n    );\n\n    const stat2 = fstatSync(fd, { bigint: true });\n    strictEqual(stat2.size, 15n);\n\n    const dest1 = Buffer.alloc(5);\n    const dest2 = Buffer.alloc(10);\n    const dest3 = Buffer.alloc(5);\n    let read = readvSync(fd, [dest1, dest2, dest3], 0);\n    strictEqual(read, 15);\n    let dest = Buffer.concat([dest1, dest2, dest3]);\n    strictEqual(dest.toString('utf8', 0, read), 'Hello World!!!!');\n\n    dest1.fill(0);\n    dest2.fill(0);\n    dest3.fill(0);\n    read = readvSync(fd, [dest1, dest2, dest3], 1);\n    strictEqual(read, 14);\n    dest = Buffer.concat([dest1, dest2, dest3]);\n    strictEqual(dest.toString('utf8', 0, read), 'ello World!!!!');\n\n    // Reading from a position beyond the end of the file returns nothing.\n    strictEqual(readvSync(fd, [dest1], 100), 0);\n\n    closeSync(fd);\n  },\n};\n\nexport const writeFileSyncTest = {\n  test() {\n    ok(!existsSync('/tmp/test.txt'));\n    strictEqual(writeFileSync('/tmp/test.txt', 'Hello World'), 11);\n    ok(existsSync('/tmp/test.txt'));\n    let stat = statSync('/tmp/test.txt');\n    strictEqual(stat.size, 11);\n    strictEqual(readFileSync('/tmp/test.txt').toString(), 'Hello World');\n\n    strictEqual(appendFileSync('/tmp/test.txt', '!!!!'), 4);\n    stat = statSync('/tmp/test.txt');\n    strictEqual(stat.size, 15);\n    strictEqual(readFileSync('/tmp/test.txt').toString(), 'Hello World!!!!');\n\n    // We can also use a file descriptor\n    const fd = openSync('/tmp/test.txt', 'a+');\n    writeFileSync(fd, '##');\n    strictEqual(readFileSync(fd).toString(), 'Hello World!!!!##');\n    closeSync(fd);\n  },\n};\n\nexport const appendFileSyncFlush = {\n  test() {\n    ok(!existsSync('/tmp/test.txt'));\n\n    // The flush option really is not supported in any particular way in\n    // our implementation but let's verify it.\n    appendFileSync('/tmp/test.txt', 'hello world', { flush: true });\n\n    ['no', {}, null, -1].forEach((i) => {\n      throws(\n        () => appendFileSync('/tmp/test.txt', 'hello world', { flush: 'no' }),\n        {\n          code: 'ERR_INVALID_ARG_TYPE',\n        }\n      );\n    });\n\n    ok(existsSync('/tmp/test.txt'));\n    strictEqual(readFileSync('/tmp/test.txt').toString(), 'hello world');\n  },\n};\n\nexport const writeFileAsyncCallbackTest = {\n  async test() {\n    ok(!existsSync('/tmp/test.txt'));\n\n    throws(() => writeFile({}), kErrInvalidArgType);\n    throws(() => writeFile('', 123), kErrInvalidArgType);\n    throws(() => writeFile('', '', 123), kErrInvalidArgType);\n    throws(() => writeFile('', '', {}), kErrInvalidArgType);\n\n    await new Promise((resolve, reject) => {\n      writeFile('/tmp/test.txt', 'Hello World', (err) => {\n        if (err) return reject(err);\n        ok(existsSync('/tmp/test.txt'));\n        resolve();\n      });\n    });\n\n    let stat = statSync('/tmp/test.txt');\n    strictEqual(stat.size, 11);\n    strictEqual(readFileSync('/tmp/test.txt').toString(), 'Hello World');\n\n    await new Promise((resolve, reject) => {\n      appendFile('/tmp/test.txt', '!!!!', (err) => {\n        strictEqual(\n          readFileSync('/tmp/test.txt').toString(),\n          'Hello World!!!!'\n        );\n        if (err) return reject(err);\n        resolve();\n      });\n    });\n\n    stat = statSync('/tmp/test.txt');\n    strictEqual(stat.size, 15);\n    strictEqual(readFileSync('/tmp/test.txt').toString(), 'Hello World!!!!');\n\n    // We can also use a file descriptor\n    const fd = openSync('/tmp/test.txt', 'a+');\n    await new Promise((resolve, reject) => {\n      writeFile(fd, '##', 'utf8', (err) => {\n        if (err) return reject(err);\n        resolve();\n      });\n    });\n    strictEqual(readFileSync(fd).toString(), 'Hello World!!!!##');\n    closeSync(fd);\n\n    // We can use the promise API as well.\n    await promises.appendFile('/tmp/test.txt', '!!!');\n    strictEqual(\n      readFileSync('/tmp/test.txt').toString(),\n      'Hello World!!!!##!!!'\n    );\n  },\n};\n\nexport const appendFileCases = {\n  async test() {\n    ok(!existsSync('/tmp/test.txt'));\n    // It accepts bufers\n    appendFileSync('/tmp/test.txt', Buffer.from('Hello World'));\n    ok(existsSync('/tmp/test.txt'));\n    strictEqual(readFileSync('/tmp/test.txt').toString(), 'Hello World');\n\n    // With the callback API also\n    const { promise, resolve, reject } = Promise.withResolvers();\n    appendFile('/tmp/test.txt', Buffer.from('!!!!'), (err) => {\n      if (err) return reject(err);\n      strictEqual(readFileSync('/tmp/test.txt').toString(), 'Hello World!!!!');\n      resolve();\n    });\n    await promise;\n\n    // And the promises API\n    await promises.appendFile('/tmp/test.txt', Buffer.from('!!!'));\n    strictEqual(readFileSync('/tmp/test.txt').toString(), 'Hello World!!!!!!!');\n\n    // But invalid types throw errors\n    [123, {}, null, []].forEach((data) => {\n      throws(() => appendFileSync('/tmp/test.txt', data), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n    });\n  },\n};\n\nexport const readAsyncCallbackTest = {\n  async test() {\n    const fd = openSync('/tmp/test.txt', 'w+');\n    ok(existsSync('/tmp/test.txt'));\n\n    throws(() => read('hello'), kErrInvalidArgType);\n    throws(() => read(0, 123), kErrInvalidArgType);\n    throws(() => read(0, Buffer.alloc(1), 123), kErrInvalidArgType);\n    throws(() => read(0, Buffer.alloc(1), {}), kErrInvalidArgType);\n\n    const stat = fstatSync(fd, { bigint: true });\n    strictEqual(stat.size, 0n);\n\n    writeSync(fd, 'Hello World');\n    writeSync(fd, '!!!!');\n    const stat2 = fstatSync(fd, { bigint: true });\n    strictEqual(stat2.size, 15n);\n\n    const dest = Buffer.alloc(15);\n    let bytesRead = await new Promise((resolve, reject) => {\n      read(fd, dest, 0, dest.byteLength, 0, (err, bytesRead) => {\n        if (err) return reject(err);\n        resolve(bytesRead);\n      });\n    });\n\n    strictEqual(bytesRead, 15);\n    strictEqual(dest.toString(), 'Hello World!!!!');\n\n    closeSync(fd);\n  },\n};\n\nexport const readvAsyncCallbackTest = {\n  async test() {\n    const fd = openSync('/tmp/test.txt', 'w+');\n    ok(existsSync('/tmp/test.txt'));\n\n    throws(() => readv('hello'), kErrInvalidArgType);\n    throws(() => readv(0, 123), kErrInvalidArgType);\n    throws(() => readv(0, [Buffer.alloc(1)], 123), kErrInvalidArgType);\n    throws(() => readv(0, [Buffer.alloc(1)], {}), kErrInvalidArgType);\n\n    const stat = fstatSync(fd, { bigint: true });\n    strictEqual(stat.size, 0n);\n\n    writeSync(fd, 'Hello World');\n    writeSync(fd, '!!!!');\n    const stat2 = fstatSync(fd, { bigint: true });\n    strictEqual(stat2.size, 15n);\n\n    const dest1 = Buffer.alloc(5);\n    const dest2 = Buffer.alloc(10);\n    let bytesRead = await new Promise((resolve, reject) => {\n      readv(fd, [dest1, dest2], 0, (err, bytesRead) => {\n        if (err) return reject(err);\n        resolve(bytesRead);\n      });\n    });\n\n    strictEqual(bytesRead, 15);\n    strictEqual(Buffer.concat([dest1, dest2]).toString(), 'Hello World!!!!');\n\n    closeSync(fd);\n  },\n};\n\nexport const readFileAsyncCallbackTest = {\n  async test() {\n    const fd = openSync('/tmp/test.txt', 'w+');\n    ok(existsSync('/tmp/test.txt'));\n\n    throws(() => readFile('hello'), kErrInvalidArgType);\n    throws(() => readFile(0, 123), kErrInvalidArgType);\n    throws(() => readFile(0, {}), kErrInvalidArgType);\n\n    const stat = fstatSync(fd, { bigint: true });\n    strictEqual(stat.size, 0n);\n\n    writeSync(fd, 'Hello World');\n    writeSync(fd, '!!!!');\n    const stat2 = fstatSync(fd, { bigint: true });\n    strictEqual(stat2.size, 15n);\n\n    const content = await new Promise((resolve, reject) => {\n      readFile(fd, (err, content) => {\n        if (err) return reject(err);\n        resolve(content);\n      });\n    });\n\n    strictEqual(content.toString(), 'Hello World!!!!');\n\n    closeSync(fd);\n  },\n};\n\nexport const copyAndRenameSyncTest = {\n  test() {\n    ok(!existsSync('/tmp/test.txt'));\n    ok(!existsSync('/tmp/test2.txt'));\n    writeFileSync('/tmp/test.txt', 'Hello World');\n    ok(existsSync('/tmp/test.txt'));\n    ok(!existsSync('/tmp/test2.txt'));\n\n    copyFileSync('/tmp/test.txt', '/tmp/test2.txt');\n    // Both files exist\n    ok(existsSync('/tmp/test.txt'));\n    ok(existsSync('/tmp/test2.txt'));\n\n    strictEqual(\n      readFileSync('/tmp/test.txt').toString(),\n      readFileSync('/tmp/test2.txt').toString()\n    );\n\n    copyFileSync('/tmp/test.txt', '/tmp/test4.txt', 0);\n    // Both files exist\n    ok(existsSync('/tmp/test.txt'));\n    ok(existsSync('/tmp/test4.txt'));\n\n    strictEqual(\n      readFileSync('/tmp/test.txt').toString(),\n      readFileSync('/tmp/test4.txt').toString()\n    );\n\n    throws(\n      () => copyFileSync('/tmp/test.txt', '/tmp/test4.txt', COPYFILE_EXCL),\n      {\n        code: 'EEXIST',\n      }\n    );\n\n    throws(\n      () =>\n        copyFileSync(\n          '/tmp/test.txt',\n          '/tmp/nope.txt',\n          constants.COPYFILE_FICLONE_FORCE\n        ),\n      {\n        message: /unsupported/,\n      }\n    );\n\n    copyFileSync('/tmp/test.txt', '/tmp/test5.txt', constants.COPYFILE_FICLONE);\n    // Both files exist\n    ok(existsSync('/tmp/test.txt'));\n    ok(existsSync('/tmp/test5.txt'));\n\n    strictEqual(\n      readFileSync('/tmp/test.txt').toString(),\n      readFileSync('/tmp/test5.txt').toString()\n    );\n\n    [false, 1, {}, [], null, undefined].forEach((i) => {\n      throws(() => copyFileSync(i, '/tmp/nope.txt'), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n      throws(() => copyFileSync('/tmp/test.txt', i), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n    });\n    [false, {}, [], null].forEach((i) => {\n      throws(() => copyFileSync('/tmp/test.txt', '/tmp/test2.txt', i), {\n        code: 'ERR_INVALID_ARG_VALUE',\n      });\n    });\n\n    // We can modify one of the files and the other remains unchanged\n    writeFileSync('/tmp/test.txt', 'Hello World 2');\n    strictEqual(readFileSync('/tmp/test.txt').toString(), 'Hello World 2');\n    strictEqual(readFileSync('/tmp/test2.txt').toString(), 'Hello World');\n\n    // Renaming the files work\n    renameSync('/tmp/test.txt', '/tmp/test3.txt');\n    ok(!existsSync('/tmp/test.txt'));\n    ok(existsSync('/tmp/test3.txt'));\n    strictEqual(readFileSync('/tmp/test3.txt').toString(), 'Hello World 2');\n  },\n};\n\nexport const copyAndRenameAsyncCallbackTest = {\n  async test() {\n    ok(!existsSync('/tmp/test.txt'));\n    ok(!existsSync('/tmp/test2.txt'));\n    writeFileSync('/tmp/test.txt', 'Hello World');\n    ok(existsSync('/tmp/test.txt'));\n    ok(!existsSync('/tmp/test2.txt'));\n\n    throws(() => copyFile(123), kErrInvalidArgType);\n    throws(() => copyFile('/tmp/test.txt', 123), kErrInvalidArgType);\n    throws(\n      () => copyFile('/tmp/test.txt', '/tmp/test2.txt', 123),\n      kErrInvalidArgValue\n    );\n    throws(\n      () => copyFile('/tmp/test.txt', '/tmp/test2.txt', 0),\n      kErrInvalidArgType\n    );\n\n    throws(() => rename(123), kErrInvalidArgType);\n    throws(() => rename('/tmp/test.txt', 123), kErrInvalidArgType);\n    throws(\n      () => rename('/tmp/test.txt', '/tmp/test2.txt', 0),\n      kErrInvalidArgType\n    );\n\n    await new Promise((resolve, reject) => {\n      copyFile('/tmp/test.txt', '/tmp/test2.txt', (err) => {\n        if (err) return reject(err);\n        resolve();\n      });\n    });\n\n    // Test the exclusive option fails when the file already exists\n    await rejects(\n      new Promise((resolve, reject) => {\n        copyFile('/tmp/test.txt', '/tmp/test2.txt', COPYFILE_EXCL, (err) => {\n          if (err) return reject(err);\n          resolve();\n        });\n      }),\n      kErrEExist\n    );\n\n    // Both files exist\n    ok(existsSync('/tmp/test.txt'));\n    ok(existsSync('/tmp/test2.txt'));\n\n    strictEqual(\n      readFileSync('/tmp/test.txt').toString(),\n      readFileSync('/tmp/test2.txt').toString()\n    );\n\n    // We can modify one of the files and the other remains unchanged\n    writeFileSync('/tmp/test.txt', 'Hello World 2');\n    strictEqual(readFileSync('/tmp/test.txt').toString(), 'Hello World 2');\n    strictEqual(readFileSync('/tmp/test2.txt').toString(), 'Hello World');\n\n    // Renaming the files work\n    await new Promise((resolve, reject) => {\n      rename('/tmp/test.txt', '/tmp/test3.txt', (err) => {\n        if (err) return reject(err);\n        resolve();\n      });\n    });\n\n    ok(!existsSync('/tmp/test.txt'));\n    ok(existsSync('/tmp/test3.txt'));\n    strictEqual(readFileSync('/tmp/test3.txt').toString(), 'Hello World 2');\n  },\n};\n\nexport const fsCwdTest = {\n  test() {\n    process.chdir('/bundle');\n\n    throws(\n      () => {\n        writeFileSync('test-cwd.txt', 'Hello from original cwd');\n      },\n      { code: 'EPERM' }\n    );\n\n    process.chdir('/tmp');\n\n    writeFileSync('test-cwd.txt', 'Hello from /tmp');\n    ok(existsSync('test-cwd.txt'));\n    ok(existsSync('/tmp/test-cwd.txt'));\n\n    ok(existsSync('test-cwd.txt'));\n    ok(!existsSync(`/bundle/test-cwd.txt`));\n\n    strictEqual(readFileSync('test-cwd.txt').toString(), 'Hello from /tmp');\n    strictEqual(\n      readFileSync('/tmp/test-cwd.txt').toString(),\n      'Hello from /tmp'\n    );\n\n    process.chdir('/bundle');\n\n    ok(!existsSync('test-cwd.txt'));\n    throws(\n      () => {\n        readFileSync('test-cwd.txt');\n      },\n      { code: 'ENOENT', path: '/bundle/test-cwd.txt' }\n    );\n\n    unlinkSync('/tmp/test-cwd.txt');\n  },\n};\n\nexport const readBadEncoding = {\n  test() {\n    const kErrorObj = {\n      code: 'ERR_INVALID_ARG_VALUE',\n    };\n    throws(() => readFileSync('/tmp/test.txt', 'bad-encoding'), kErrorObj);\n    throws(\n      () => appendFileSync('/tmp/test.txt', 'data', 'bad-encoding'),\n      kErrorObj\n    );\n    throws(() => readdirSync('/tmp/test.txt', 'bad-encoding'), kErrorObj);\n    throws(() => readlinkSync('/tmp/test.txt', 'bad-encoding'), kErrorObj);\n    throws(\n      () => writeFileSync('/tmp/test.txt', 'data', 'bad-encoding'),\n      kErrorObj\n    );\n    throws(\n      () => appendFileSync('/tmp/test.txt', 'data', 'bad-encoding'),\n      kErrorObj\n    );\n    throws(() => realpathSync('/tmp/test.txt', 'bad-encoding'), kErrorObj);\n    throws(() => mkdtempSync('/tmp/test.txt', 'bad-encoding'), kErrorObj);\n    throws(() => ReadStream('/tmp/test.txt', 'bad-encoding'), kErrorObj);\n    throws(() => WriteStream('/tmp/test.txt', 'bad-encoding'), kErrorObj);\n\n    throws(\n      () => writeFile('/tmp/test.txt', 'data', 'bad-encoding', mustNotCall),\n      kErrorObj\n    );\n    throws(\n      () => appendFile('/tmp/test.txt', 'data', 'bad-encoding', mustNotCall),\n      kErrorObj\n    );\n\n    function mustNotCall() {\n      throw new Error('This function must not be called');\n    }\n\n    throws(\n      () => readFile('/tmp/test.txt', 'bad-encoding', mustNotCall),\n      kErrorObj\n    );\n    throws(\n      () => readdir('/tmp/test.txt', 'bad-encoding', mustNotCall),\n      kErrorObj\n    );\n    throws(\n      () => readlink('/tmp/test.txt', 'bad-encoding', mustNotCall),\n      kErrorObj\n    );\n    throws(\n      () => realpath('/tmp/test.txt', 'bad-encoding', mustNotCall),\n      kErrorObj\n    );\n    throws(\n      () => mkdtemp('/tmp/test.txt', 'bad-encoding', mustNotCall),\n      kErrorObj\n    );\n  },\n};\n\nexport const fsConstantsTest = {\n  test() {\n    // Check if the two constants accepted by chmod() on Windows are defined.\n    notStrictEqual(constants.S_IRUSR, undefined);\n    notStrictEqual(constants.S_IWUSR, undefined);\n\n    // Check null prototype.\n    strictEqual(Object.getPrototypeOf(constants), null);\n\n    const knownFsConstantNames = [\n      'UV_FS_SYMLINK_DIR',\n      'UV_FS_SYMLINK_JUNCTION',\n      'O_RDONLY',\n      'O_WRONLY',\n      'O_RDWR',\n      'UV_DIRENT_UNKNOWN',\n      'UV_DIRENT_FILE',\n      'UV_DIRENT_DIR',\n      'UV_DIRENT_LINK',\n      'UV_DIRENT_FIFO',\n      'UV_DIRENT_SOCKET',\n      'UV_DIRENT_CHAR',\n      'UV_DIRENT_BLOCK',\n      'S_IFMT',\n      'S_IFREG',\n      'S_IFDIR',\n      'S_IFCHR',\n      'S_IFBLK',\n      'S_IFIFO',\n      'S_IFLNK',\n      'S_IFSOCK',\n      'O_CREAT',\n      'O_EXCL',\n      'UV_FS_O_FILEMAP',\n      'O_NOCTTY',\n      'O_TRUNC',\n      'O_APPEND',\n      'O_DIRECTORY',\n      'O_EXCL',\n      'O_NOATIME',\n      'O_NOFOLLOW',\n      'O_SYNC',\n      'O_DSYNC',\n      'O_SYMLINK',\n      'O_DIRECT',\n      'O_NONBLOCK',\n      'S_IRWXU',\n      'S_IRUSR',\n      'S_IWUSR',\n      'S_IXUSR',\n      'S_IRWXG',\n      'S_IRGRP',\n      'S_IWGRP',\n      'S_IXGRP',\n      'S_IRWXO',\n      'S_IROTH',\n      'S_IWOTH',\n      'S_IXOTH',\n      'F_OK',\n      'R_OK',\n      'W_OK',\n      'X_OK',\n      'UV_FS_COPYFILE_EXCL',\n      'COPYFILE_EXCL',\n      'UV_FS_COPYFILE_FICLONE',\n      'COPYFILE_FICLONE',\n      'UV_FS_COPYFILE_FICLONE_FORCE',\n      'COPYFILE_FICLONE_FORCE',\n      'EXTENSIONLESS_FORMAT_JAVASCRIPT',\n      'EXTENSIONLESS_FORMAT_WASM',\n    ];\n\n    const fsConstantNames = Object.keys(constants);\n    const unknownFsConstantNames = fsConstantNames.filter((constant) => {\n      return !knownFsConstantNames.includes(constant);\n    });\n    deepStrictEqual(\n      unknownFsConstantNames,\n      [],\n      `Unknown fs.constants: ${unknownFsConstantNames.join(', ')}`\n    );\n\n    strictEqual(typeof constants.COPYFILE_EXCL, 'number');\n    strictEqual(typeof constants.COPYFILE_FICLONE, 'number');\n    strictEqual(typeof constants.COPYFILE_FICLONE_FORCE, 'number');\n    strictEqual(typeof constants.UV_FS_COPYFILE_EXCL, 'number');\n    strictEqual(typeof constants.UV_FS_COPYFILE_FICLONE, 'number');\n    strictEqual(typeof constants.UV_FS_COPYFILE_FICLONE_FORCE, 'number');\n    strictEqual(constants.COPYFILE_EXCL, constants.UV_FS_COPYFILE_EXCL);\n    strictEqual(constants.COPYFILE_FICLONE, constants.UV_FS_COPYFILE_FICLONE);\n    strictEqual(\n      constants.COPYFILE_FICLONE_FORCE,\n      constants.UV_FS_COPYFILE_FICLONE_FORCE\n    );\n  },\n};\n\nexport const fmapIgnoredTest = {\n  test() {\n    const filename = '/tmp/foo';\n    const text = 'Memory File Mapping Test';\n\n    const mw =\n      constants.UV_FS_O_FILEMAP |\n      constants.O_TRUNC |\n      constants.O_CREAT |\n      constants.O_WRONLY;\n    const mr = constants.UV_FS_O_FILEMAP | constants.O_RDONLY;\n\n    writeFileSync(filename, text, { flag: mw });\n    const r1 = readFileSync(filename, { encoding: 'utf8', flag: mr });\n    strictEqual(r1, text);\n  },\n};\n\nexport const fileNamesWithNullBytesTest = {\n  test() {\n    const filename = '/tmp/test\\0.txt';\n    throws(() => writeFileSync(filename, 'Hello World'), {\n      code: 'ERR_INVALID_ARG_VALUE',\n    });\n  },\n};\n\nexport const fileNamesWithSurrogatePairsTest = {\n  test() {\n    const tempdir = mkdtempSync('/tmp/emoji-fruit-🍇 🍈 🍉 🍊 🍋');\n    ok(existsSync(tempdir));\n    const filename = '🚀🔥🛸.txt';\n    const content = 'Test content';\n    writeFileSync(join(tempdir, filename), content);\n    const readContent = readFileSync(join(tempdir, filename), 'utf8');\n    strictEqual(readContent, content);\n  },\n};\n\nexport const testFsWithZlib = {\n  async test() {\n    const imgBase64 =\n      'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII';\n\n    const imageStream = Readable.from(Buffer.from(imgBase64, 'base64'));\n    await new Promise((resolve, reject) => {\n      const outStream = createWriteStream(`/tmp/image-stream.png.gz`);\n      outStream.on('close', resolve).on('error', reject);\n      imageStream.pipe(zlib.createGzip()).pipe(outStream);\n    });\n\n    doesNotThrow(() => {\n      zlib\n        .gunzipSync(readFileSync('/tmp/image-stream.png.gz'))\n        .toString('base64');\n    });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"fs-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"fs-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"nodejs_compat_v2\",\n          \"enable_nodejs_process_v2\",\n          \"experimental\",\n          \"enable_nodejs_fs_module\",\n          \"streams_enable_constructors\",\n          \"nodejs_zlib\"\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-readstream-test.js",
    "content": "import {\n  ReadStream,\n  createReadStream,\n  statSync,\n  writeFileSync,\n  readFileSync,\n  openSync,\n  open as openAsync,\n  close as closeAsync,\n  read as readAsync,\n  promises,\n} from 'node:fs';\n\nimport { notStrictEqual, ok, strictEqual, throws } from 'node:assert';\nimport { mock } from 'node:test';\n\nstrictEqual(typeof ReadStream, 'function');\nstrictEqual(typeof createReadStream, 'function');\n\nexport const simpleReadStreamTest = {\n  async test() {\n    const largeData = 'abc'.repeat(100_000);\n    writeFileSync('/tmp/foo', largeData);\n\n    const stream = createReadStream('/tmp/foo', {\n      encoding: 'utf8',\n      highWaterMark: 10_000,\n    });\n\n    let data = '';\n    for await (const chunk of stream) {\n      data += chunk;\n    }\n    strictEqual(data, largeData);\n  },\n};\n\nfunction prepareFile() {\n  const path = '/tmp/elipses.txt';\n  writeFileSync(path, '…'.repeat(10000));\n  return path;\n}\n\nasync function runTest(options) {\n  let paused = false;\n  let bytesRead = 0;\n\n  const path = prepareFile();\n\n  const file = createReadStream(path, options);\n  const fileSize = statSync(path).size;\n\n  strictEqual(file.bytesRead, 0);\n\n  const promises = [];\n\n  const { promise: openPromise, resolve: openResolve } =\n    Promise.withResolvers();\n  const { promise: endPromise, resolve: endResolve } = Promise.withResolvers();\n  const { promise: closePromise, resolve: closeResolve } =\n    Promise.withResolvers();\n  promises.push(openPromise);\n  promises.push(endPromise);\n  promises.push(closePromise);\n\n  const onOpen = mock.fn((fd) => {\n    file.length = 0;\n    strictEqual(typeof fd, 'number');\n    strictEqual(file.bytesRead, 0);\n    ok(file.readable);\n    file.pause();\n    file.resume();\n    file.pause();\n    file.resume();\n    openResolve();\n  });\n\n  const onData = mock.fn((data) => {\n    ok(data instanceof Buffer);\n    ok(data.byteOffset % 8 === 0);\n    ok(!paused);\n    file.length += data.length;\n\n    bytesRead += data.length;\n    strictEqual(file.bytesRead, bytesRead);\n\n    paused = true;\n    file.pause();\n\n    setTimeout(function () {\n      paused = false;\n      file.resume();\n    }, 10);\n  });\n\n  const onEnd = mock.fn(() => {\n    strictEqual(bytesRead, fileSize);\n    strictEqual(file.bytesRead, fileSize);\n    endResolve();\n  });\n\n  const onClose = mock.fn(() => {\n    strictEqual(bytesRead, fileSize);\n    strictEqual(file.bytesRead, fileSize);\n    closeResolve();\n  });\n\n  file.once('open', onOpen);\n  file.once('end', onEnd);\n  file.once('close', onClose);\n  file.on('data', onData);\n\n  await Promise.all(promises);\n\n  strictEqual(file.length, 30000);\n  strictEqual(onOpen.mock.callCount(), 1);\n  strictEqual(onEnd.mock.callCount(), 1);\n  strictEqual(onClose.mock.callCount(), 1);\n  strictEqual(onData.mock.callCount(), 1);\n}\n\nexport const readStreamTest1 = {\n  async test() {\n    await runTest({});\n  },\n};\n\nexport const readStreamTest2 = {\n  async test() {\n    const customFs = {\n      open: mock.fn((...args) => openAsync(...args)),\n      read: mock.fn((...args) => readAsync(...args)),\n      close: mock.fn((...args) => closeAsync(...args)),\n    };\n    await runTest({\n      fs: customFs,\n    });\n    strictEqual(customFs.open.mock.callCount(), 1);\n    strictEqual(customFs.read.mock.callCount(), 2);\n    strictEqual(customFs.close.mock.callCount(), 1);\n  },\n};\n\nexport const readStreamTest3 = {\n  async test() {\n    const path = prepareFile();\n    const file = createReadStream(path, { encoding: 'utf8' });\n    file.length = 0;\n    file.on('data', function (data) {\n      strictEqual(typeof data, 'string');\n      file.length += data.length;\n\n      for (let i = 0; i < data.length; i++) {\n        // http://www.fileformat.info/info/unicode/char/2026/index.htm\n        strictEqual(data[i], '\\u2026');\n      }\n    });\n\n    const { promise, resolve } = Promise.withResolvers();\n    file.on('close', resolve);\n\n    await promise;\n\n    strictEqual(file.length, 10000);\n  },\n};\n\nexport const readStreamTest4 = {\n  async test() {\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz');\n    const file = createReadStream(path, { bufferSize: 1, start: 1, end: 2 });\n    let contentRead = '';\n    file.on('data', function (data) {\n      contentRead += data.toString('utf-8');\n    });\n    const { promise, resolve } = Promise.withResolvers();\n    file.on('end', function (data) {\n      strictEqual(contentRead, 'yz');\n      resolve();\n    });\n    await promise;\n  },\n};\n\nexport const readStreamTest5 = {\n  async test() {\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz\\n');\n    const file = createReadStream(path, { bufferSize: 1, start: 1 });\n    file.data = '';\n    file.on('data', function (data) {\n      file.data += data.toString('utf-8');\n    });\n    const { promise, resolve } = Promise.withResolvers();\n    file.on('end', function () {\n      strictEqual(file.data, 'yz\\n');\n      resolve();\n    });\n    await promise;\n  },\n};\n\nexport const readStreamTest6 = {\n  async test() {\n    // Ref: https://github.com/nodejs/node-v0.x-archive/issues/2320\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz\\n');\n    const file = createReadStream(path, { bufferSize: 1.23, start: 1 });\n    file.data = '';\n    file.on('data', function (data) {\n      file.data += data.toString('utf-8');\n    });\n    const { promise, resolve } = Promise.withResolvers();\n    file.on('end', function () {\n      strictEqual(file.data, 'yz\\n');\n      resolve();\n    });\n    await promise;\n  },\n};\n\nexport const readStreamTest7 = {\n  test() {\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz\\n');\n    throws(() => createReadStream(path, { start: 10, end: 2 }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n  },\n};\n\nexport const readStreamTest8 = {\n  async test() {\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz\\n');\n    const stream = createReadStream(path, { start: 0, end: 0 });\n    stream.data = '';\n\n    stream.on('data', function (chunk) {\n      stream.data += chunk;\n    });\n    const { promise, resolve } = Promise.withResolvers();\n    stream.on('end', function () {\n      strictEqual(stream.data, 'x');\n      resolve();\n    });\n    await promise;\n  },\n};\n\nexport const readStreamTest9 = {\n  async test() {\n    // Verify that end works when start is not specified.\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz\\n');\n    const stream = new createReadStream(path, { end: 1 });\n    stream.data = '';\n\n    stream.on('data', function (chunk) {\n      stream.data += chunk;\n    });\n\n    const { promise, resolve } = Promise.withResolvers();\n    stream.on('end', function () {\n      strictEqual(stream.data, 'xy');\n      resolve();\n    });\n    await promise;\n  },\n};\n\nexport const readStreamTest10 = {\n  async test() {\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz\\n');\n    let file = createReadStream(path, { autoClose: false });\n    let data = '';\n    file.on('data', function (chunk) {\n      data += chunk;\n    });\n    const { promise, resolve, reject } = Promise.withResolvers();\n    file.on('end', function () {\n      strictEqual(data, 'xyz\\n');\n      process.nextTick(function () {\n        ok(!file.closed);\n        ok(!file.destroyed);\n        fileNext().then(resolve, reject);\n      });\n    });\n\n    await promise;\n\n    async function fileNext() {\n      // This will tell us if the fd is usable again or not.\n      file = createReadStream(null, { fd: file.fd, start: 0 });\n      file.data = '';\n      file.on('data', function (data) {\n        file.data += data;\n      });\n      const { promise, resolve } = Promise.withResolvers();\n      const { promise: endPromise, resolve: endResolve } =\n        Promise.withResolvers();\n      file.on('end', function (err) {\n        strictEqual(file.data, 'xyz\\n');\n        endResolve();\n      });\n      file.on('close', resolve);\n      await Promise.all([promise, endPromise]);\n      ok(file.closed);\n      ok(file.destroyed);\n    }\n  },\n};\n\nexport const readStreamTest11 = {\n  async test() {\n    // Just to make sure autoClose won't close the stream because of error.\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const file = createReadStream(null, { fd: 13337, autoClose: false });\n    file.on('data', () => reject(new Error('should not be called')));\n    file.on('error', resolve);\n    await promise;\n    ok(!file.closed);\n    ok(!file.destroyed);\n    ok(file.fd);\n  },\n};\n\nexport const readStreamTest12 = {\n  async test() {\n    // Make sure stream is destroyed when file does not exist.\n    const file = createReadStream('/path/to/file/that/does/not/exist');\n    const { promise, resolve, reject } = Promise.withResolvers();\n    file.on('data', () => reject(new Error('should not be called')));\n    file.on('error', resolve);\n    await promise;\n    ok(file.closed);\n    ok(file.destroyed);\n  },\n};\n\nexport const readStreamTest13 = {\n  async test() {\n    const example = '/tmp/x.txt';\n    writeFileSync(example, 'xyz\\n');\n    createReadStream(example, undefined);\n    createReadStream(example, null);\n    createReadStream(example, 'utf8');\n    createReadStream(example, { encoding: 'utf8' });\n\n    const createReadStreamErr = (path, opt, error) => {\n      throws(() => createReadStream(path, opt), error);\n    };\n\n    const typeError = {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n    };\n\n    const rangeError = {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    };\n\n    [123, 0, true, false].forEach((opts) =>\n      createReadStreamErr(example, opts, typeError)\n    );\n\n    // Case 0: Should not throw if either start or end is undefined\n    [{}, { start: 0 }, { end: Infinity }].forEach((opts) =>\n      createReadStream(example, opts)\n    );\n\n    // Case 1: Should throw TypeError if either start or end is not of type 'number'\n    [\n      { start: 'invalid' },\n      { end: 'invalid' },\n      { start: 'invalid', end: 'invalid' },\n    ].forEach((opts) => createReadStreamErr(example, opts, typeError));\n\n    // Case 2: Should throw RangeError if either start or end is NaN\n    [{ start: NaN }, { end: NaN }, { start: NaN, end: NaN }].forEach((opts) =>\n      createReadStreamErr(example, opts, rangeError)\n    );\n\n    // Case 3: Should throw RangeError if either start or end is negative\n    [{ start: -1 }, { end: -1 }, { start: -1, end: -1 }].forEach((opts) =>\n      createReadStreamErr(example, opts, rangeError)\n    );\n\n    // Case 4: Should throw RangeError if either start or end is fractional\n    [{ start: 0.1 }, { end: 0.1 }, { start: 0.1, end: 0.1 }].forEach((opts) =>\n      createReadStreamErr(example, opts, rangeError)\n    );\n\n    // Case 5: Should not throw if both start and end are whole numbers\n    createReadStream(example, { start: 1, end: 5 });\n\n    // Case 6: Should throw RangeError if start is greater than end\n    createReadStreamErr(example, { start: 5, end: 1 }, rangeError);\n\n    // Case 7: Should throw RangeError if start or end is not safe integer\n    const NOT_SAFE_INTEGER = 2 ** 53;\n    [\n      { start: NOT_SAFE_INTEGER, end: Infinity },\n      { start: 0, end: NOT_SAFE_INTEGER },\n    ].forEach((opts) => createReadStreamErr(example, opts, rangeError));\n  },\n};\n\nexport const readStreamTest14 = {\n  async test() {\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz\\n');\n    let data = '';\n    let first = true;\n\n    const stream = createReadStream(path);\n    stream.setEncoding('utf8');\n    stream.on('data', function (chunk) {\n      data += chunk;\n      if (first) {\n        first = false;\n        stream.resume();\n      }\n    });\n\n    const { promise, resolve } = Promise.withResolvers();\n\n    process.nextTick(function () {\n      stream.pause();\n      setTimeout(function () {\n        stream.resume();\n        resolve();\n      }, 100);\n    });\n\n    await promise;\n    strictEqual(data, 'xyz\\n');\n  },\n};\n\nexport const readStreamTest15 = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n    ReadStream.prototype.open = resolve;\n    createReadStream('asd');\n    await promise;\n    delete ReadStream.prototype.open;\n  },\n};\n\n// const fn = fixtures.path('elipses.txt');\n// const rangeFile = fixtures.path('x.txt');\n\nexport const readStreamTest16 = {\n  async test() {\n    let paused = false;\n    const path = prepareFile();\n\n    const file = ReadStream(path);\n\n    const promises = [];\n    const { promise: openPromise, resolve: openResolve } =\n      Promise.withResolvers();\n    const { promise: endPromise, resolve: endResolve } =\n      Promise.withResolvers();\n    const { promise: closePromise, resolve: closeResolve } =\n      Promise.withResolvers();\n    promises.push(openPromise);\n    promises.push(endPromise);\n    promises.push(closePromise);\n\n    file.on('open', function (fd) {\n      file.length = 0;\n      strictEqual(typeof fd, 'number');\n      ok(file.readable);\n\n      // GH-535\n      file.pause();\n      file.resume();\n      file.pause();\n      file.resume();\n      openResolve();\n    });\n\n    file.on('data', function (data) {\n      ok(data instanceof Buffer);\n      ok(!paused);\n      file.length += data.length;\n\n      paused = true;\n      file.pause();\n\n      setTimeout(function () {\n        paused = false;\n        file.resume();\n      }, 10);\n    });\n\n    file.on('end', endResolve);\n\n    file.on('close', function () {\n      strictEqual(file.length, 30000);\n      closeResolve();\n    });\n\n    await Promise.all(promises);\n  },\n};\n\nexport const readStreamTest17 = {\n  async test() {\n    const path = prepareFile();\n    const file = createReadStream(path, { __proto__: { encoding: 'utf8' } });\n    file.length = 0;\n    file.on('data', function (data) {\n      strictEqual(typeof data, 'string');\n      file.length += data.length;\n\n      for (let i = 0; i < data.length; i++) {\n        // http://www.fileformat.info/info/unicode/char/2026/index.htm\n        strictEqual(data[i], '\\u2026');\n      }\n    });\n\n    const { promise, resolve } = Promise.withResolvers();\n    file.on('close', function () {\n      strictEqual(file.length, 10000);\n      resolve();\n    });\n\n    await promise;\n  },\n};\n\nexport const readStreamTest18 = {\n  async test() {\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz\\n');\n    const options = { __proto__: { bufferSize: 1, start: 1, end: 2 } };\n    const file = createReadStream(path, options);\n    strictEqual(file.start, 1);\n    strictEqual(file.end, 2);\n    let contentRead = '';\n    file.on('data', function (data) {\n      contentRead += data.toString('utf-8');\n    });\n\n    const { promise, resolve } = Promise.withResolvers();\n    file.on('end', function () {\n      strictEqual(contentRead, 'yz');\n      resolve();\n    });\n    await promise;\n  },\n};\n\nexport const readStreamTest19 = {\n  async test() {\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz\\n');\n    const options = { __proto__: { bufferSize: 1, start: 1 } };\n    const file = createReadStream(path, options);\n    strictEqual(file.start, 1);\n    file.data = '';\n    file.on('data', function (data) {\n      file.data += data.toString('utf-8');\n    });\n    const { promise, resolve } = Promise.withResolvers();\n    file.on('end', function () {\n      strictEqual(file.data, 'yz\\n');\n      resolve();\n    });\n    await promise;\n  },\n};\n\n// https://github.com/joyent/node/issues/2320\nexport const readStreamTest20 = {\n  async test() {\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz\\n');\n    const options = { __proto__: { bufferSize: 1.23, start: 1 } };\n    const file = createReadStream(path, options);\n    strictEqual(file.start, 1);\n    file.data = '';\n    file.on('data', function (data) {\n      file.data += data.toString('utf-8');\n    });\n    const { promise, resolve } = Promise.withResolvers();\n    file.on('end', function () {\n      strictEqual(file.data, 'yz\\n');\n      resolve();\n    });\n    await promise;\n  },\n};\n\nexport const readStreamTest21 = {\n  test() {\n    const path = '/tmp/x.txt';\n    throws(() => createReadStream(path, { __proto__: { start: 10, end: 2 } }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n  },\n};\n\nexport const readStreamTest22 = {\n  async test() {\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz\\n');\n    const options = { __proto__: { start: 0, end: 0 } };\n    const stream = createReadStream(path, options);\n    strictEqual(stream.start, 0);\n    strictEqual(stream.end, 0);\n    stream.data = '';\n\n    stream.on('data', function (chunk) {\n      stream.data += chunk;\n    });\n\n    const { promise, resolve } = Promise.withResolvers();\n    stream.on('end', function () {\n      strictEqual(stream.data, 'x');\n      resolve();\n    });\n    await promise;\n  },\n};\n\nexport const readStreamTest23 = {\n  async test() {\n    const path = '/tmp/x.txt';\n    let output = '';\n    writeFileSync(path, 'hello world');\n    const fd = openSync(path, 'r');\n    const stream = createReadStream(null, { fd: fd, encoding: 'utf8' });\n    strictEqual(stream.path, undefined);\n    stream.on('data', (data) => {\n      output += data;\n    });\n    const { promise, resolve } = Promise.withResolvers();\n    stream.on('close', resolve);\n    await promise;\n    strictEqual(output, 'hello world');\n  },\n};\n\nexport const readStreamTest24 = {\n  async test() {\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'hello world');\n    const stream = createReadStream(path);\n    const { promise: promise1, resolve: resolve1 } = Promise.withResolvers();\n    const { promise: promise2, resolve: resolve2 } = Promise.withResolvers();\n    stream.close(resolve1);\n    stream.close(resolve2);\n    await Promise.all([promise1, promise2]);\n  },\n};\n\nexport const readStreamTest25 = {\n  async test() {\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'hello world');\n    const stream = createReadStream(path);\n    const { promise: promise1, resolve: resolve1 } = Promise.withResolvers();\n    const { promise: promise2, resolve: resolve2 } = Promise.withResolvers();\n    stream.destroy(null, resolve1);\n    stream.destroy(null, resolve2);\n    await Promise.all([promise1, promise2]);\n  },\n};\n\nexport const readStreamTest26 = {\n  async test() {\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'hello world');\n    const fh = await promises.open(path, 'r');\n    const { promise: closePromise, resolve: closeResolve } =\n      Promise.withResolvers();\n    fh.on('close', closeResolve);\n    const stream = createReadStream(null, { fd: fh, encoding: 'utf8' });\n    let data = '';\n    stream.on('data', (chunk) => (data += chunk));\n    const { promise, resolve } = Promise.withResolvers();\n    stream.on('end', () => resolve());\n    await Promise.all([promise, closePromise]);\n    strictEqual(data, 'hello world');\n    strictEqual(fh.fd, undefined);\n  },\n};\n\nexport const readStreamTest27 = {\n  async test() {\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz\\n');\n    const handle = await promises.open(path, 'r');\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const { promise: closePromise, resolve: closeResolve } =\n      Promise.withResolvers();\n    handle.on('close', resolve);\n    const stream = createReadStream(null, { fd: handle });\n    stream.on('data', reject);\n    stream.on('close', closeResolve);\n    handle.close();\n    await Promise.all([promise, closePromise]);\n  },\n};\n\nexport const readStreamTest28 = {\n  async test() {\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz\\n');\n    const handle = await promises.open(path, 'r');\n    const { promise: handlePromise, resolve: handleResolve } =\n      Promise.withResolvers();\n    const { promise: streamPromise, resolve: streamResolve } =\n      Promise.withResolvers();\n    handle.on('close', handleResolve);\n    const stream = createReadStream(null, { fd: handle });\n    stream.on('close', streamResolve);\n    stream.on('data', () => handle.close());\n    await Promise.all([handlePromise, streamPromise]);\n  },\n};\n\nexport const readStreamTest29 = {\n  async test() {\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz\\n');\n    const handle = await promises.open(path, 'r');\n    const { promise: handlePromise, resolve: handleResolve } =\n      Promise.withResolvers();\n    const { promise: streamPromise, resolve: streamResolve } =\n      Promise.withResolvers();\n    handle.on('close', handleResolve);\n    const stream = createReadStream(null, { fd: handle });\n    stream.on('close', streamResolve);\n    stream.close();\n    await Promise.all([handlePromise, streamPromise]);\n  },\n};\n\nexport const readStreamTest30 = {\n  async test() {\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz\\n');\n    const handle = await promises.open(path, 'r');\n    throws(() => createReadStream(null, { fd: handle, fs: {} }), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n    handle.close();\n  },\n};\n\nexport const readStreamTest31 = {\n  async test() {\n    // AbortSignal option test\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz\\n');\n    const handle = await promises.open(path, 'r');\n    const controller = new AbortController();\n    const { signal } = controller;\n    const stream = handle.createReadStream({ signal });\n\n    stream.on('data', () => {\n      throw new Error('boom');\n    });\n    stream.on('end', () => {\n      throw new Error('boom');\n    });\n\n    const { promise: errorPromise, resolve: errorResolve } =\n      Promise.withResolvers();\n    const { promise: closePromise, resolve: closeResolve } =\n      Promise.withResolvers();\n    stream.on('error', (err) => {\n      strictEqual(err.name, 'AbortError');\n      errorResolve();\n    });\n\n    handle.on('close', closeResolve);\n    stream.on('close', () => handle.close());\n\n    controller.abort();\n\n    await Promise.all([errorPromise, closePromise]);\n  },\n};\n\nexport const readStreamTest32 = {\n  async test() {\n    // Already-aborted signal test\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz\\n');\n    const handle = await promises.open(path, 'r');\n\n    const signal = AbortSignal.abort();\n    const stream = handle.createReadStream({ signal });\n\n    stream.on('data', () => {\n      throw new Error('boom');\n    });\n    stream.on('end', () => {\n      throw new Error('boom');\n    });\n\n    const { promise: errorPromise, resolve: errorResolve } =\n      Promise.withResolvers();\n    const { promise: closePromise, resolve: closeResolve } =\n      Promise.withResolvers();\n\n    stream.on('error', (err) => {\n      strictEqual(err.name, 'AbortError');\n      errorResolve();\n    });\n\n    handle.on('close', closeResolve);\n    stream.on('close', () => handle.close());\n\n    await Promise.all([errorPromise, closePromise]);\n  },\n};\n\nexport const readStreamTest33 = {\n  async test() {\n    // Invalid signal type test\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz\\n');\n    const handle = await promises.open(path, 'r');\n\n    for (const signal of [\n      1,\n      {},\n      [],\n      '',\n      NaN,\n      1n,\n      () => {},\n      Symbol(),\n      false,\n      true,\n    ]) {\n      throws(() => handle.createReadStream({ signal }), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n      });\n    }\n    handle.close();\n  },\n};\n\nexport const readStreamTest34 = {\n  async test() {\n    // Custom abort reason test\n    const path = '/tmp/x.txt';\n    writeFileSync(path, 'xyz\\n');\n    const handle = await promises.open(path, 'r');\n    const controller = new AbortController();\n    const { signal } = controller;\n    const reason = new Error('some silly abort reason');\n    const stream = handle.createReadStream({ signal });\n\n    const { promise: errorPromise, resolve: errorResolve } =\n      Promise.withResolvers();\n    const { promise: closePromise, resolve: closeResolve } =\n      Promise.withResolvers();\n\n    stream.on('error', (err) => {\n      strictEqual(err.name, 'AbortError');\n      strictEqual(err.cause, reason);\n      errorResolve();\n    });\n\n    handle.on('close', closeResolve);\n    stream.on('close', () => handle.close());\n\n    controller.abort(reason);\n\n    await Promise.all([errorPromise, closePromise]);\n  },\n};\n\nexport const emptyReadStreamTest = {\n  async test() {\n    writeFileSync('/tmp/empty.txt', '');\n    const stream = createReadStream('/tmp/empty.txt');\n    const { promise, resolve, reject } = Promise.withResolvers();\n    stream.once('data', () => {\n      reject(new Error('should not emit data'));\n    });\n    stream.once('end', resolve);\n    await promise;\n    strictEqual(stream.bytesRead, 0);\n  },\n};\n\nexport const fileHandleReadableWebStreamTest = {\n  async test() {\n    writeFileSync('/tmp/stream.txt', 'abcde'.repeat(1000));\n    const fh = await promises.open('/tmp/stream.txt', 'r');\n    const stream = fh.readableWebStream();\n    const enc = new TextEncoder();\n    let data = '';\n    for await (const chunk of stream) {\n      strictEqual(chunk instanceof Uint8Array, true);\n      data += new TextDecoder().decode(chunk);\n    }\n    strictEqual(data, 'abcde'.repeat(1000));\n    strictEqual(fh.fd, undefined);\n\n    // Should throw if the stream is closed.\n    throws(() => fh.readableWebStream(), {\n      code: 'EBADF',\n    });\n\n    const fh2 = await promises.open('/tmp/stream.txt', 'r');\n    const stream2 = fh2.readableWebStream({ autoClose: false });\n    await fh2.close();\n    const res = await stream2.getReader().read();\n    strictEqual(res.done, true);\n    strictEqual(res.value, undefined);\n  },\n};\n\n/**\n * TODO(node-fs): Revisit\n * Temporarily comment out. These are larger tests causing timeouts\n * In CI. Will move them out to separate tests in a follow on PR\nexport const readStreamTest98 = {\n  async test() {\n    const path = prepareFile();\n    const content = readFileSync(path);\n\n    const N = 20;\n    let started = 0;\n    let done = 0;\n\n    const arrayBuffers = new Set();\n    const promises = [];\n\n    async function startRead() {\n      ++started;\n      const chunks = [];\n      const promises = [];\n      const { promise, resolve } = Promise.withResolvers();\n      promises.push(promise);\n      createReadStream(path)\n        .on('data', (chunk) => {\n          chunks.push(chunk);\n          arrayBuffers.add(chunk.buffer);\n        })\n        .on('end', () => {\n          if (started < N) promises.push(startRead());\n          deepStrictEqual(Buffer.concat(chunks), content);\n          if (++done === N) {\n            const retainedMemory = [...arrayBuffers]\n              .map((ab) => ab.byteLength)\n              .reduce((a, b) => a + b);\n            ok(\n              retainedMemory / (N * content.length) <= 3,\n              `Retaining ${retainedMemory} bytes in ABs for ${N} ` +\n                `chunks of size ${content.length}`\n            );\n          }\n          resolve();\n        });\n      await Promise.all(promises);\n    }\n\n    // Don’t start the reads all at once – that way we would have to allocate\n    // a large amount of memory upfront.\n    for (let i = 0; i < 6; ++i) {\n      promises.push(startRead());\n    }\n    await Promise.all(promises);\n  },\n};\n\nexport const readStreamTest99 = {\n  async test() {\n    const path = '/tmp/read_stream_pos_test.txt';\n    writeFileSync(path, '');\n\n    let counter = 0;\n\n    const writeInterval = setInterval(() => {\n      counter = counter + 1;\n      const line = `hello at ${counter}\\n`;\n      writeFileSync(path, line, { flag: 'a' });\n    }, 1);\n\n    const hwm = 10;\n    let bufs = [];\n    let isLow = false;\n    let cur = 0;\n    let stream;\n\n    const readInterval = setInterval(() => {\n      if (stream) return;\n\n      stream = createReadStream(path, {\n        highWaterMark: hwm,\n        start: cur,\n      });\n      stream.on('data', (chunk) => {\n        cur += chunk.length;\n        bufs.push(chunk);\n        if (isLow) {\n          const brokenLines = Buffer.concat(bufs)\n            .toString()\n            .split('\\n')\n            .filter((line) => {\n              const s = 'hello at'.slice(0, line.length);\n              if (line && !line.startsWith(s)) {\n                return true;\n              }\n              return false;\n            });\n          strictEqual(brokenLines.length, 0);\n          exitTest();\n          return;\n        }\n        if (chunk.length !== hwm) {\n          isLow = true;\n        }\n      });\n      stream.on('end', () => {\n        stream = null;\n        isLow = false;\n        bufs = [];\n      });\n    }, 10);\n\n    // Time longer than 10 seconds to exit safely\n    await scheduler.wait(5_000);\n\n    clearInterval(readInterval);\n    clearInterval(writeInterval);\n\n    if (stream && !stream.destroyed) {\n      const { promise, resolve } = Promise.withResolvers();\n      stream.on('close', resolve);\n      stream.destroy();\n      await promise;\n    }\n  },\n};\n**/\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-readstream-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"fs-readstream-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"fs-readstream-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_fs_module\", \"streams_enable_constructors\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-stat-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport { deepStrictEqual, ok, rejects, strictEqual, throws } from 'node:assert';\n\nimport {\n  openSync,\n  closeSync,\n  symlinkSync,\n  statSync,\n  lstatSync,\n  fstatSync,\n  stat,\n  lstat,\n  fstat,\n  promises,\n  statfsSync,\n  statfs,\n  Stats,\n} from 'node:fs';\n\nstrictEqual(typeof openSync, 'function');\nstrictEqual(typeof closeSync, 'function');\nstrictEqual(typeof statSync, 'function');\nstrictEqual(typeof lstatSync, 'function');\nstrictEqual(typeof fstatSync, 'function');\nstrictEqual(typeof stat, 'function');\nstrictEqual(typeof lstat, 'function');\nstrictEqual(typeof fstat, 'function');\nstrictEqual(typeof symlinkSync, 'function');\nstrictEqual(typeof statfsSync, 'function');\nstrictEqual(typeof statfs, 'function');\nstrictEqual(typeof promises.stat, 'function');\nstrictEqual(typeof promises.lstat, 'function');\n\nconst kInvalidArgTypeError = { code: 'ERR_INVALID_ARG_TYPE' };\n\nfunction checkRootStat(stat, bigint = false) {\n  ok(stat instanceof Stats);\n  strictEqual(stat.isDirectory(), true);\n  strictEqual(stat.isFile(), false);\n  strictEqual(stat.isBlockDevice(), false);\n  strictEqual(stat.isCharacterDevice(), false);\n  strictEqual(stat.isSymbolicLink(), false);\n  strictEqual(stat.isFIFO(), false);\n  strictEqual(stat.isSocket(), false);\n  strictEqual(stat.dev, bigint ? 0n : 0);\n  strictEqual(stat.ino, bigint ? 0n : 0);\n  strictEqual(stat.mode, bigint ? 16676n : 16676);\n  strictEqual(stat.nlink, bigint ? 1n : 1);\n  strictEqual(stat.uid, bigint ? 0n : 0);\n  strictEqual(stat.gid, bigint ? 0n : 0);\n  strictEqual(stat.rdev, bigint ? 0n : 0);\n  strictEqual(stat.size, bigint ? 0n : 0);\n  strictEqual(stat.blksize, bigint ? 0n : 0);\n  strictEqual(stat.blocks, bigint ? 0n : 0);\n  strictEqual(stat.atime.getTime(), 0);\n  strictEqual(stat.mtime.getTime(), 0);\n  strictEqual(stat.ctime.getTime(), 0);\n  strictEqual(stat.birthtime.getTime(), 0);\n  strictEqual(stat.atimeMs, bigint ? 0n : 0);\n  strictEqual(stat.mtimeMs, bigint ? 0n : 0);\n  strictEqual(stat.ctimeMs, bigint ? 0n : 0);\n  strictEqual(stat.birthtimeMs, bigint ? 0n : 0);\n  if (bigint) {\n    strictEqual(stat.atimeNs, 0n);\n    strictEqual(stat.mtimeNs, 0n);\n    strictEqual(stat.ctimeNs, 0n);\n    strictEqual(stat.birthtimeNs, 0n);\n  }\n}\n\nfunction checkFileStat(stat, bigint = false) {\n  ok(stat instanceof Stats);\n  strictEqual(stat.isDirectory(), false);\n  strictEqual(stat.isFile(), true);\n  strictEqual(stat.isBlockDevice(), false);\n  strictEqual(stat.isCharacterDevice(), false);\n  strictEqual(stat.isSymbolicLink(), false);\n  strictEqual(stat.isFIFO(), false);\n  strictEqual(stat.isSocket(), false);\n  strictEqual(stat.dev, bigint ? 0n : 0);\n  strictEqual(stat.ino, bigint ? 0n : 0);\n  strictEqual(stat.mode, bigint ? 33060n : 33060);\n  strictEqual(stat.nlink, bigint ? 1n : 1);\n  strictEqual(stat.uid, bigint ? 0n : 0);\n  strictEqual(stat.gid, bigint ? 0n : 0);\n  strictEqual(stat.rdev, bigint ? 0n : 0);\n  ok(stat.size > 0n || stat.size > 0);\n  strictEqual(stat.blksize, bigint ? 0n : 0);\n  strictEqual(stat.blocks, bigint ? 0n : 0);\n  // The size is going to be constantly changing for this test file,\n  // so let's just check that it's not zero.\n}\n\nfunction checkCharacterDeviceStat(stat, bigint = false) {\n  ok(stat instanceof Stats);\n  strictEqual(stat.isDirectory(), false);\n  strictEqual(stat.isFile(), false);\n  strictEqual(stat.isBlockDevice(), false);\n  strictEqual(stat.isCharacterDevice(), true);\n  strictEqual(stat.isSymbolicLink(), false);\n  strictEqual(stat.isFIFO(), false);\n  strictEqual(stat.isSocket(), false);\n  strictEqual(stat.dev, bigint ? 1n : 1);\n  strictEqual(stat.ino, bigint ? 0n : 0);\n  strictEqual(stat.mode, bigint ? 8630n : 8630);\n  strictEqual(stat.nlink, bigint ? 1n : 1);\n  strictEqual(stat.uid, bigint ? 0n : 0);\n  strictEqual(stat.gid, bigint ? 0n : 0);\n  strictEqual(stat.rdev, bigint ? 0n : 0);\n}\n\nfunction checkLinkStat(stat, bigint = false) {\n  ok(stat instanceof Stats);\n  strictEqual(stat.isDirectory(), false);\n  strictEqual(stat.isFile(), false);\n  strictEqual(stat.isBlockDevice(), false);\n  strictEqual(stat.isCharacterDevice(), false);\n  strictEqual(stat.isSymbolicLink(), true);\n  strictEqual(stat.isFIFO(), false);\n  strictEqual(stat.isSocket(), false);\n  strictEqual(stat.dev, bigint ? 0n : 0);\n  strictEqual(stat.ino, bigint ? 0n : 0);\n  strictEqual(stat.mode, bigint ? 41252n : 41252);\n  strictEqual(stat.nlink, bigint ? 1n : 1);\n  strictEqual(stat.uid, bigint ? 0n : 0);\n  strictEqual(stat.gid, bigint ? 0n : 0);\n  strictEqual(stat.rdev, bigint ? 0n : 0);\n}\n\nexport const statSyncTest = {\n  async test() {\n    throws(() => statSync(), kInvalidArgTypeError);\n    throws(() => statSync('/', 'foo'), kInvalidArgTypeError);\n    throws(() => statSync('/', { bigint: 'yes' }), kInvalidArgTypeError);\n    throws(() => lstatSync(), kInvalidArgTypeError);\n    throws(() => lstatSync('/', 'foo'), kInvalidArgTypeError);\n    throws(() => lstatSync('/', { bigint: 'yes' }), kInvalidArgTypeError);\n    throws(() => fstatSync(), kInvalidArgTypeError);\n    throws(() => fstatSync(123, 'foo'), kInvalidArgTypeError);\n    throws(() => fstatSync(123, { bigint: 'yes' }), kInvalidArgTypeError);\n\n    symlinkSync('/', '/tmp/link-root');\n    symlinkSync('/bundle/worker', '/tmp/link-bundle-worker');\n    symlinkSync('/dev/null', '/tmp/link-dev-null');\n\n    const fd1 = openSync('/', 'r');\n    const fd2 = openSync('/bundle/worker', 'r');\n    const fd3 = openSync('/dev/null', 'r');\n\n    checkRootStat(statSync('/'));\n    checkRootStat(statSync('/', { bigint: true }), true);\n    checkRootStat(lstatSync('/'));\n    checkRootStat(lstatSync('/', { bigint: true }), true);\n    checkRootStat(fstatSync(fd1));\n    checkRootStat(fstatSync(fd1, { bigint: true }), true);\n\n    checkFileStat(statSync('/bundle/worker'));\n    checkFileStat(statSync('/bundle/worker', { bigint: true }), true);\n    checkFileStat(lstatSync('/bundle/worker'));\n    checkFileStat(lstatSync('/bundle/worker', { bigint: true }), true);\n    checkFileStat(fstatSync(fd2));\n\n    checkCharacterDeviceStat(statSync('/dev/null'));\n    checkCharacterDeviceStat(statSync('/dev/null', { bigint: true }), true);\n    checkCharacterDeviceStat(lstatSync('/dev/null'));\n    checkCharacterDeviceStat(lstatSync('/dev/null', { bigint: true }), true);\n    checkCharacterDeviceStat(fstatSync(fd3));\n\n    // Check that symlinks are followed by default with statSync.\n    checkRootStat(statSync('/tmp/link-root'));\n    checkRootStat(statSync('/tmp/link-root', { bigint: true }), true);\n    checkFileStat(statSync('/tmp/link-bundle-worker'));\n    checkFileStat(statSync('/tmp/link-bundle-worker', { bigint: true }), true);\n    checkCharacterDeviceStat(statSync('/tmp/link-dev-null'));\n    checkCharacterDeviceStat(\n      statSync('/tmp/link-dev-null', { bigint: true }),\n      true\n    );\n\n    // But lstatSync does not follow symlinks.\n    checkLinkStat(lstatSync('/tmp/link-root'));\n    checkLinkStat(lstatSync('/tmp/link-root', { bigint: true }), true);\n    checkLinkStat(lstatSync('/tmp/link-bundle-worker'));\n    checkLinkStat(lstatSync('/tmp/link-bundle-worker', { bigint: true }), true);\n    checkLinkStat(lstatSync('/tmp/link-dev-null'));\n    checkLinkStat(lstatSync('/tmp/link-dev-null', { bigint: true }), true);\n\n    // Non-existent paths and file descriptors\n    throws(() => statSync('/non-existent-path'), { code: 'ENOENT' });\n    throws(() => lstatSync('/non-existent-path'), { code: 'ENOENT' });\n    throws(() => fstatSync(123), { code: 'EBADF' });\n\n    strictEqual(\n      statSync('/non-existent-path', { throwIfNoEntry: false }),\n      undefined\n    );\n    strictEqual(\n      lstatSync('/non-existent-path', { throwIfNoEntry: false }),\n      undefined\n    );\n\n    closeSync(fd1);\n    closeSync(fd2);\n    closeSync(fd3);\n  },\n};\n\nexport const statTest = {\n  async test() {\n    throws(() => stat(), kInvalidArgTypeError);\n    throws(() => stat('/', 'foo'), kInvalidArgTypeError);\n    throws(() => stat('/', { bigint: 'yes' }), kInvalidArgTypeError);\n    throws(() => stat('/'), kInvalidArgTypeError);\n    throws(() => lstat(), kInvalidArgTypeError);\n    throws(() => lstat('/', 'foo'), kInvalidArgTypeError);\n    throws(() => lstat('/', { bigint: 'yes' }), kInvalidArgTypeError);\n    throws(() => lstat('/'), kInvalidArgTypeError);\n    throws(() => fstat(), kInvalidArgTypeError);\n    throws(() => fstat(123, 'foo'), kInvalidArgTypeError);\n    throws(() => fstat(123, { bigint: 'yes' }), kInvalidArgTypeError);\n    throws(() => fstat(123), kInvalidArgTypeError);\n\n    symlinkSync('/', '/tmp/link-root');\n    symlinkSync('/bundle/worker', '/tmp/link-bundle-worker');\n    symlinkSync('/dev/null', '/tmp/link-dev-null');\n\n    const fd1 = openSync('/', 'r');\n    const fd2 = openSync('/bundle/worker', 'r');\n    const fd3 = openSync('/dev/null', 'r');\n\n    const doStatTest = async (statFn, path, bigint, checkFn) => {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      statFn(path, { bigint }, (err, stat) => {\n        if (err) {\n          reject(err);\n        } else {\n          try {\n            checkFn(stat, bigint);\n            resolve();\n          } catch (e) {\n            reject(e);\n          }\n        }\n      });\n      await promise;\n    };\n\n    const doFailTest = async (statFn, path, code) => {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      statFn(path, (err) => {\n        if (err) {\n          strictEqual(err.code, code);\n          resolve();\n        } else {\n          reject(new Error(`Expected error for path: ${path}`));\n        }\n      });\n      await promise;\n    };\n\n    await doStatTest(stat, '/', false, checkRootStat);\n    await doStatTest(stat, '/', true, checkRootStat);\n    await doStatTest(stat, '/bundle/worker', false, checkFileStat);\n    await doStatTest(stat, '/bundle/worker', true, checkFileStat);\n    await doStatTest(stat, '/dev/null', false, checkCharacterDeviceStat);\n    await doStatTest(stat, '/dev/null', true, checkCharacterDeviceStat);\n\n    await doStatTest(stat, '/tmp/link-root', false, checkRootStat);\n    await doStatTest(stat, '/tmp/link-root', true, checkRootStat);\n    await doStatTest(stat, '/tmp/link-bundle-worker', false, checkFileStat);\n    await doStatTest(stat, '/tmp/link-bundle-worker', true, checkFileStat);\n    await doStatTest(\n      stat,\n      '/tmp/link-dev-null',\n      false,\n      checkCharacterDeviceStat\n    );\n    await doStatTest(\n      stat,\n      '/tmp/link-dev-null',\n      true,\n      checkCharacterDeviceStat\n    );\n\n    await doStatTest(lstat, '/', false, checkRootStat);\n    await doStatTest(lstat, '/', true, checkRootStat);\n    await doStatTest(lstat, '/bundle/worker', false, checkFileStat);\n    await doStatTest(lstat, '/bundle/worker', true, checkFileStat);\n    await doStatTest(lstat, '/dev/null', false, checkCharacterDeviceStat);\n    await doStatTest(lstat, '/dev/null', true, checkCharacterDeviceStat);\n\n    await doStatTest(lstat, '/tmp/link-root', false, checkLinkStat);\n    await doStatTest(lstat, '/tmp/link-root', true, checkLinkStat);\n    await doStatTest(lstat, '/tmp/link-bundle-worker', false, checkLinkStat);\n    await doStatTest(lstat, '/tmp/link-bundle-worker', true, checkLinkStat);\n    await doStatTest(lstat, '/tmp/link-dev-null', false, checkLinkStat);\n    await doStatTest(lstat, '/tmp/link-dev-null', true, checkLinkStat);\n\n    await doStatTest(fstat, fd1, false, checkRootStat);\n    await doStatTest(fstat, fd1, true, checkRootStat);\n    await doStatTest(fstat, fd2, false, checkFileStat);\n    await doStatTest(fstat, fd2, true, checkFileStat);\n    await doStatTest(fstat, fd3, false, checkCharacterDeviceStat);\n    await doStatTest(fstat, fd3, true, checkCharacterDeviceStat);\n\n    // // Non-existent paths and file descriptors\n    await doFailTest(stat, '/non-existent-path', 'ENOENT');\n    await doFailTest(lstat, '/non-existent-path', 'ENOENT');\n    await doFailTest(fstat, 123, 'EBADF');\n\n    closeSync(fd1);\n    closeSync(fd2);\n    closeSync(fd3);\n  },\n};\n\nexport const statPromisesTest = {\n  async test() {\n    await rejects(promises.stat(), kInvalidArgTypeError);\n    await rejects(promises.stat('/', 'foo'), kInvalidArgTypeError);\n    await rejects(promises.stat('/', { bigint: 'yes' }), kInvalidArgTypeError);\n    await rejects(promises.lstat(), kInvalidArgTypeError);\n    await rejects(promises.lstat('/', 'foo'), kInvalidArgTypeError);\n    await rejects(promises.lstat('/', { bigint: 'yes' }), kInvalidArgTypeError);\n\n    symlinkSync('/', '/tmp/link-root');\n    symlinkSync('/bundle/worker', '/tmp/link-bundle-worker');\n    symlinkSync('/dev/null', '/tmp/link-dev-null');\n\n    await checkRootStat(await promises.stat('/'));\n    await checkRootStat(await promises.stat('/', { bigint: true }), true);\n    await checkRootStat(await promises.lstat('/'));\n    await checkRootStat(await promises.lstat('/', { bigint: true }), true);\n\n    await checkFileStat(await promises.stat('/bundle/worker'));\n    await checkFileStat(\n      await promises.stat('/bundle/worker', { bigint: true }),\n      true\n    );\n    await checkFileStat(await promises.lstat('/bundle/worker'));\n    await checkFileStat(\n      await promises.lstat('/bundle/worker', { bigint: true }),\n      true\n    );\n\n    await checkCharacterDeviceStat(await promises.stat('/dev/null'));\n    await checkCharacterDeviceStat(\n      await promises.stat('/dev/null', { bigint: true }),\n      true\n    );\n    await checkCharacterDeviceStat(await promises.lstat('/dev/null'));\n    await checkCharacterDeviceStat(\n      await promises.lstat('/dev/null', { bigint: true }),\n      true\n    );\n\n    await checkRootStat(await promises.stat('/tmp/link-root'));\n    await checkRootStat(\n      await promises.stat('/tmp/link-root', { bigint: true }),\n      true\n    );\n    await checkFileStat(await promises.stat('/tmp/link-bundle-worker'));\n    await checkFileStat(\n      await promises.stat('/tmp/link-bundle-worker', { bigint: true }),\n      true\n    );\n    await checkCharacterDeviceStat(await promises.stat('/tmp/link-dev-null'));\n    await checkCharacterDeviceStat(\n      await promises.stat('/tmp/link-dev-null', { bigint: true }),\n      true\n    );\n\n    await checkLinkStat(await promises.lstat('/tmp/link-root'));\n    await checkLinkStat(\n      await promises.lstat('/tmp/link-root', { bigint: true }),\n      true\n    );\n    await checkLinkStat(await promises.lstat('/tmp/link-bundle-worker'));\n    await checkLinkStat(\n      await promises.lstat('/tmp/link-bundle-worker', { bigint: true }),\n      true\n    );\n    await checkLinkStat(await promises.lstat('/tmp/link-dev-null'));\n    await checkLinkStat(\n      await promises.lstat('/tmp/link-dev-null', { bigint: true }),\n      true\n    );\n  },\n};\n\nexport const statfsTest = {\n  async test() {\n    const check = {\n      type: 0,\n      bsize: 0,\n      blocks: 0,\n      bfree: 0,\n      bavail: 0,\n      files: 0,\n      ffree: 0,\n    };\n    const checkBn = {\n      type: 0n,\n      bsize: 0n,\n      blocks: 0n,\n      bfree: 0n,\n      bavail: 0n,\n      files: 0n,\n      ffree: 0n,\n    };\n\n    deepStrictEqual(statfsSync('/'), check);\n    deepStrictEqual(statfsSync('/bundle', { bigint: true }), checkBn);\n\n    async function callStatfs(path, fn, bigint = false) {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      statfs(path, { bigint }, (err, stat) => {\n        if (err) return reject(err);\n        try {\n          fn(stat);\n          resolve();\n        } catch (err) {\n          reject(err);\n        }\n      });\n      await promise;\n    }\n\n    await callStatfs('/', (stat) => {\n      deepStrictEqual(stat, check);\n    });\n    await callStatfs(\n      '/',\n      (stat) => {\n        deepStrictEqual(stat, checkBn);\n      },\n      true /* bigint */\n    );\n\n    // Throws if the path is invalid / wrong type\n    throws(() => statfs(123), {\n      code: /ERR_INVALID_ARG_TYPE/,\n    });\n\n    throws(() => statfs('/', { bigint: 'yes' }), {\n      code: /ERR_INVALID_ARG_TYPE/,\n    });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-stat-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"fs-stat-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"fs-stat-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_fs_module\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-utimes-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport { ok, match, rejects, strictEqual, throws } from 'node:assert';\n\nimport {\n  openSync,\n  existsSync,\n  closeSync,\n  statSync,\n  fstatSync,\n  utimesSync,\n  futimesSync,\n  lutimesSync,\n  utimes,\n  futimes,\n  lutimes,\n  promises,\n} from 'node:fs';\n\nstrictEqual(typeof openSync, 'function');\nstrictEqual(typeof closeSync, 'function');\nstrictEqual(typeof statSync, 'function');\nstrictEqual(typeof utimesSync, 'function');\nstrictEqual(typeof futimesSync, 'function');\nstrictEqual(typeof lutimesSync, 'function');\nstrictEqual(typeof utimes, 'function');\nstrictEqual(typeof futimes, 'function');\nstrictEqual(typeof lutimes, 'function');\nstrictEqual(typeof promises.utimes, 'function');\nstrictEqual(typeof promises.lutimes, 'function');\n\nconst kInvalidArgTypeError = { code: 'ERR_INVALID_ARG_TYPE' };\n\nfunction checkStat(path, mtimeMsCheck) {\n  const bigint = typeof mtimeMsCheck === 'bigint';\n  const { atimeMs, mtimeMs, ctimeMs, birthtimeMs } =\n    typeof path === 'number'\n      ? fstatSync(path, { bigint })\n      : statSync(path, { bigint });\n  strictEqual(mtimeMs, mtimeMsCheck);\n  strictEqual(ctimeMs, mtimeMsCheck);\n  strictEqual(atimeMs, bigint ? 0n : 0);\n  strictEqual(birthtimeMs, bigint ? 0n : 0);\n}\n\nexport const utimesTest = {\n  async test() {\n    throws(() => utimesSync(123), kInvalidArgTypeError);\n    throws(() => utimesSync('', {}), kInvalidArgTypeError);\n    throws(() => utimesSync('', 0, {}), kInvalidArgTypeError);\n    throws(() => lutimesSync(123), kInvalidArgTypeError);\n    throws(() => lutimesSync('', {}), kInvalidArgTypeError);\n    throws(() => lutimesSync('', 0, {}), kInvalidArgTypeError);\n    throws(() => futimesSync(''), kInvalidArgTypeError);\n    throws(() => futimesSync(0, {}), kInvalidArgTypeError);\n    throws(() => futimesSync(0, 0, {}), kInvalidArgTypeError);\n    throws(() => utimes(0), kInvalidArgTypeError);\n    throws(() => utimes('', {}), kInvalidArgTypeError);\n    throws(() => utimes('', 0, {}), kInvalidArgTypeError);\n    throws(() => utimes('', 0, 0), kInvalidArgTypeError);\n    throws(() => utimes(''), kInvalidArgTypeError);\n    throws(() => lutimes(0), kInvalidArgTypeError);\n    throws(() => lutimes('', {}), kInvalidArgTypeError);\n    throws(() => lutimes('', 0, {}), kInvalidArgTypeError);\n    throws(() => lutimes('', 0, 0), kInvalidArgTypeError);\n    throws(() => lutimes(''), kInvalidArgTypeError);\n    throws(() => futimes(''), kInvalidArgTypeError);\n    throws(() => futimes(0, {}), kInvalidArgTypeError);\n    throws(() => futimes(0, 0, {}), kInvalidArgTypeError);\n    throws(() => futimes(0, 0, 0), kInvalidArgTypeError);\n    throws(() => futimes(0), kInvalidArgTypeError);\n    await rejects(promises.utimes(123), kInvalidArgTypeError);\n    await rejects(promises.utimes('', {}), kInvalidArgTypeError);\n    await rejects(promises.utimes('', 0, {}), kInvalidArgTypeError);\n    await rejects(promises.lutimes(123), kInvalidArgTypeError);\n    await rejects(promises.lutimes('', {}), kInvalidArgTypeError);\n    await rejects(promises.lutimes('', 0, {}), kInvalidArgTypeError);\n\n    throws(\n      () => {\n        utimesSync('/tmp/test.txt', 1000, new Date('not a valid date'));\n      },\n      {\n        message: /^The value cannot be converted/,\n        name: 'TypeError',\n      }\n    );\n\n    const fd = openSync('/tmp/test.txt', 'w+');\n    ok(existsSync('/tmp/test.txt'));\n\n    checkStat(fd, 0n);\n\n    utimesSync('/tmp/test.txt', 1000, 2000);\n    checkStat(fd, 2000n);\n\n    utimesSync('/tmp/test.txt', 1000, new Date(0));\n    checkStat(fd, 0);\n\n    utimesSync('/tmp/test.txt', 3000, '1970-01-01T01:00:00.000Z');\n    checkStat(fd, 3600000n);\n\n    lutimesSync('/tmp/test.txt', 3000, 4000);\n    checkStat(fd, 4000n);\n\n    lutimesSync('/tmp/test.txt', 1000, new Date(0));\n    checkStat(fd, 0);\n\n    lutimesSync('/tmp/test.txt', 3000, '1970-01-01T01:00:00.000Z');\n    checkStat(fd, 3600000n);\n\n    futimesSync(fd, 5000, 6000);\n    checkStat(fd, 6000n);\n\n    futimesSync(fd, 1000, new Date(0));\n    checkStat(fd, 0);\n\n    futimesSync(fd, 3000, '1970-01-01T01:00:00.000Z');\n    checkStat(fd, 3600000n);\n\n    {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      utimes('/tmp/test.txt', 8000, new Date('not a valid date'), (err) => {\n        try {\n          ok(err);\n          strictEqual(err.name, 'TypeError');\n          match(err.message, /The value cannot be converted/);\n          resolve();\n        } catch (err) {\n          reject(err);\n        }\n      });\n      await promise;\n    }\n\n    {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      utimes('/tmp/test.txt', 8000, 9000, (err) => {\n        if (err) return reject(err);\n        try {\n          checkStat(fd, 9000n);\n          resolve();\n        } catch (err) {\n          reject(err);\n        }\n      });\n      await promise;\n    }\n\n    {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      lutimes('/tmp/test.txt', 8000, 10000, (err) => {\n        if (err) return reject(err);\n        try {\n          checkStat(fd, 10000n);\n          resolve();\n        } catch (err) {\n          reject(err);\n        }\n      });\n      await promise;\n    }\n\n    {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      futimes(fd, 7000, 11000, (err) => {\n        if (err) return reject(err);\n        try {\n          checkStat(fd, 11000n);\n          resolve();\n        } catch (err) {\n          reject(err);\n        }\n      });\n      await promise;\n    }\n\n    await promises.utimes('/tmp/test.txt', 12000, 13000);\n    checkStat(fd, 13000n);\n    await promises.lutimes('/tmp/test.txt', 14000, 15000);\n    checkStat(fd, 15000n);\n\n    closeSync(fd);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-utimes-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"fs-utimes-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"fs-utimes-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_fs_module\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-writestream-test.js",
    "content": "import {\n  WriteStream,\n  createWriteStream,\n  createReadStream,\n  readFileSync,\n  closeSync,\n  open as openAsync,\n  close as closeAsync,\n  write as writeAsync,\n  writev as writevAsync,\n  fsync as fsyncAsync,\n  promises,\n} from 'node:fs';\n\nimport { Writable } from 'node:stream';\n\nimport {\n  deepStrictEqual,\n  ok,\n  notStrictEqual,\n  strictEqual,\n  throws,\n} from 'node:assert';\nimport { mock } from 'node:test';\n\nstrictEqual(typeof WriteStream, 'function');\nstrictEqual(typeof createWriteStream, 'function');\n\nexport const simpleWriteStreamTest = {\n  async test() {\n    const path = '/tmp/workerd-fs-writestream-test.txt';\n    const data = 'Hello, World!';\n    const writeStream = createWriteStream(path, { flags: 'w' });\n    writeStream.write(data);\n    writeStream.end();\n    const { promise, resolve } = Promise.withResolvers();\n    const { promise: finishPromise, resolve: finishResolve } =\n      Promise.withResolvers();\n    writeStream.on('finish', finishResolve);\n    writeStream.on('close', resolve);\n    await Promise.all([promise, finishPromise]);\n    const check = readFileSync(path, 'utf8');\n    strictEqual(check, data);\n  },\n};\n\nexport const writeStreamTest1 = {\n  async test() {\n    const path = '/tmp/workerd-fs-writestream-test1.txt';\n    const { promise, resolve } = Promise.withResolvers();\n    const stream = WriteStream(path, {\n      fs: {\n        close(fd) {\n          ok(fd);\n          closeSync(fd);\n          resolve();\n        },\n      },\n    });\n    stream.destroy();\n    await promise;\n  },\n};\n\nexport const writeStreamTest2 = {\n  async test() {\n    const path = '/tmp/workerd-fs-writestream-test2.txt';\n    const stream = createWriteStream(path);\n\n    const { promise, resolve, reject } = Promise.withResolvers();\n\n    stream.on('drain', reject);\n    stream.on('close', resolve);\n    stream.destroy();\n    await promise;\n  },\n};\n\nexport const writeStreamTest3 = {\n  async test() {\n    const path = '/tmp/workerd-fs-writestream-test3.txt';\n    const stream = createWriteStream(path);\n    const { promise, resolve, reject } = Promise.withResolvers();\n    stream.on('error', reject);\n    stream.on('close', resolve);\n    throws(() => stream.write(42), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n    });\n    stream.destroy();\n    await promise;\n  },\n};\n\nexport const writeStreamTest4 = {\n  test() {\n    const example = '/tmp/workerd-fs-writestream-test4.txt';\n    createWriteStream(example, undefined).end();\n    createWriteStream(example, null).end();\n    createWriteStream(example, 'utf8').end();\n    createWriteStream(example, { encoding: 'utf8' }).end();\n\n    const createWriteStreamErr = (path, opt) => {\n      throws(() => createWriteStream(path, opt), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n      });\n    };\n\n    createWriteStreamErr(example, 123);\n    createWriteStreamErr(example, 0);\n    createWriteStreamErr(example, true);\n    createWriteStreamErr(example, false);\n  },\n};\n\nexport const writeStreamTest5 = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n    WriteStream.prototype.open = resolve;\n    createWriteStream('/tmp/test');\n    await promise;\n    delete WriteStream.prototype.open;\n  },\n};\n\nexport const writeStreamTest6 = {\n  async test() {\n    const path = '/tmp/write-end-test0.txt';\n    const fs = {\n      open: mock.fn(openAsync),\n      write: mock.fn(writeAsync),\n      close: mock.fn(closeAsync),\n    };\n    const { promise, resolve } = Promise.withResolvers();\n    const stream = createWriteStream(path, { fs });\n    stream.on('close', resolve);\n    stream.end('asd');\n\n    await promise;\n    strictEqual(fs.open.mock.callCount(), 1);\n    strictEqual(fs.write.mock.callCount(), 1);\n    strictEqual(fs.close.mock.callCount(), 1);\n  },\n};\n\nexport const writeStreamTest7 = {\n  async test() {\n    const path = '/tmp/write-end-test1.txt';\n    const fs = {\n      open: mock.fn(openAsync),\n      write: writeAsync,\n      writev: mock.fn(writevAsync),\n      close: mock.fn(closeAsync),\n    };\n    const stream = createWriteStream(path, { fs });\n    stream.write('asd');\n    stream.write('asd');\n    stream.write('asd');\n    stream.end();\n    const { promise, resolve } = Promise.withResolvers();\n    stream.on('close', resolve);\n    await promise;\n\n    strictEqual(fs.open.mock.callCount(), 1);\n    strictEqual(fs.writev.mock.callCount(), 1);\n    strictEqual(fs.close.mock.callCount(), 1);\n  },\n};\n\nlet cnt = 0;\nfunction nextFile() {\n  return `/tmp/${cnt++}.out`;\n}\n\nexport const writeStreamTest8 = {\n  test() {\n    for (const flush of ['true', '', 0, 1, [], {}, Symbol()]) {\n      throws(\n        () => {\n          createWriteStream(nextFile(), { flush });\n        },\n        { code: 'ERR_INVALID_ARG_TYPE' }\n      );\n    }\n  },\n};\n\nexport const writeStreamTest9 = {\n  async test() {\n    const fs = {\n      fsync: mock.fn(fsyncAsync),\n    };\n    const stream = createWriteStream(nextFile(), { flush: true, fs });\n\n    const { promise, resolve, reject } = Promise.withResolvers();\n\n    stream.write('hello', (err) => {\n      if (err) return reject();\n      stream.close((err) => {\n        if (err) return reject(err);\n        resolve();\n      });\n    });\n\n    await promise;\n\n    strictEqual(fs.fsync.mock.callCount(), 1);\n  },\n};\n\nexport const writeStreamTest10 = {\n  async test() {\n    const values = [undefined, null, false];\n    const fs = {\n      fsync: mock.fn(fsyncAsync),\n    };\n    let cnt = 0;\n\n    const { promise, resolve, reject } = Promise.withResolvers();\n\n    for (const flush of values) {\n      const file = nextFile();\n      const stream = createWriteStream(file, { flush });\n      stream.write('hello world', (err) => {\n        if (err) return reject(err);\n        stream.close((err) => {\n          if (err) return reject(err);\n          strictEqual(readFileSync(file, 'utf8'), 'hello world');\n          cnt++;\n          if (cnt === values.length) {\n            strictEqual(fs.fsync.mock.callCount(), 0);\n            resolve();\n          }\n        });\n      });\n    }\n\n    await promise;\n  },\n};\n\nexport const writeStreamTest11 = {\n  async test() {\n    const file = nextFile();\n    const handle = await promises.open(file, 'w');\n    const stream = handle.createWriteStream({ flush: true });\n\n    const { promise, resolve, reject } = Promise.withResolvers();\n\n    stream.write('hello', (err) => {\n      if (err) return reject(err);\n      stream.close((err) => {\n        if (err) return reject(err);\n        strictEqual(readFileSync(file, 'utf8'), 'hello');\n        resolve();\n      });\n    });\n\n    await promise;\n  },\n};\n\nexport const writeStreamTest12 = {\n  async test() {\n    const file = nextFile();\n    const handle = await promises.open(file, 'w+');\n\n    const { promise, resolve } = Promise.withResolvers();\n    handle.on('close', resolve);\n    const stream = createWriteStream(null, { fd: handle });\n\n    stream.end('hello');\n    stream.on('close', () => {\n      const output = readFileSync(file, 'utf-8');\n      strictEqual(output, 'hello');\n    });\n\n    await promise;\n  },\n};\n\nexport const writeStreamTest13 = {\n  async test() {\n    const file = nextFile();\n    const handle = await promises.open(file, 'w+');\n    let calls = 0;\n    const { write: originalWriteFunction, writev: originalWritevFunction } =\n      handle;\n    handle.write = mock.fn(handle.write.bind(handle));\n    handle.writev = mock.fn(handle.writev.bind(handle));\n    const stream = createWriteStream(null, { fd: handle });\n    stream.end('hello');\n    const { promise, resolve } = Promise.withResolvers();\n    stream.on('close', () => {\n      console.log('test');\n      ok(handle.write.mock.callCount() + handle.writev.mock.callCount() > 0);\n      resolve();\n    });\n    await promise;\n  },\n};\n\nexport const writeStreamTest14 = {\n  async test() {\n    const path = '/tmp/out';\n\n    let writeCalls = 0;\n    const fs = {\n      write: mock.fn((...args) => {\n        switch (writeCalls++) {\n          case 0: {\n            return writeAsync(...args);\n          }\n          case 1: {\n            args[args.length - 1](new Error('BAM'));\n            break;\n          }\n          default: {\n            // It should not be called again!\n            throw new Error('BOOM!');\n          }\n        }\n      }),\n      close: mock.fn(closeAsync),\n    };\n\n    const stream = createWriteStream(path, {\n      highWaterMark: 10,\n      fs,\n    });\n\n    const { promise: errorPromise, resolve: errorResolve } =\n      Promise.withResolvers();\n    const { promise: writePromise, resolve: writeResolve } =\n      Promise.withResolvers();\n\n    stream.on('error', (err) => {\n      strictEqual(stream.fd, null);\n      strictEqual(err.message, 'BAM');\n      errorResolve();\n    });\n\n    stream.write(Buffer.allocUnsafe(256), () => {\n      stream.write(Buffer.allocUnsafe(256), (err) => {\n        strictEqual(err.message, 'BAM');\n        writeResolve();\n      });\n    });\n\n    await Promise.all([errorPromise, writePromise]);\n  },\n};\n\nexport const writeStreamTest15 = {\n  async test() {\n    const file = '/tmp/write-end-test0.txt';\n    const stream = createWriteStream(file);\n    stream.end();\n    const { promise, resolve } = Promise.withResolvers();\n    stream.on('close', resolve);\n    await promise;\n  },\n};\n\nexport const writeStreamTest16 = {\n  async test() {\n    const file = '/tmp/write-end-test1.txt';\n    const stream = createWriteStream(file);\n    stream.end('a\\n', 'utf8');\n    const { promise, resolve } = Promise.withResolvers();\n    stream.on('close', () => {\n      const content = readFileSync(file, 'utf8');\n      strictEqual(content, 'a\\n');\n      resolve();\n    });\n    await promise;\n  },\n};\n\nexport const writeStreamTest17 = {\n  async test() {\n    const file = '/tmp/write-end-test2.txt';\n    const stream = createWriteStream(file);\n    stream.end();\n\n    const { promise: openPromise, resolve: openResolve } =\n      Promise.withResolvers();\n    const { promise: finishPromise, resolve: finishResolve } =\n      Promise.withResolvers();\n    stream.on('open', openResolve);\n    stream.on('finish', finishResolve);\n    await Promise.all([openPromise, finishPromise]);\n  },\n};\n\nexport const writeStreamTest18 = {\n  async test() {\n    const examplePath = '/tmp/a';\n    const dummyPath = '/tmp/b';\n    const firstEncoding = 'base64';\n    const secondEncoding = 'latin1';\n\n    const exampleReadStream = createReadStream(examplePath, {\n      encoding: firstEncoding,\n    });\n\n    const dummyWriteStream = createWriteStream(dummyPath, {\n      encoding: firstEncoding,\n    });\n\n    const { promise, resolve } = Promise.withResolvers();\n    exampleReadStream.pipe(dummyWriteStream).on('finish', () => {\n      const assertWriteStream = new Writable({\n        write: function (chunk, enc, next) {\n          const expected = Buffer.from('xyz\\n');\n          deepStrictEqual(expected, chunk);\n        },\n      });\n      assertWriteStream.setDefaultEncoding(secondEncoding);\n      createReadStream(dummyPath, {\n        encoding: secondEncoding,\n      })\n        .pipe(assertWriteStream)\n        .on('close', resolve);\n    });\n\n    await promise;\n  },\n};\n\nexport const writeStreamTest19 = {\n  async test() {\n    const file = '/tmp/write-end-test3.txt';\n    const stream = createWriteStream(file);\n    const { promise: closePromise1, resolve: closeResolve1 } =\n      Promise.withResolvers();\n    const { promise: closePromise2, resolve: closeResolve2 } =\n      Promise.withResolvers();\n    stream.close(closeResolve1);\n    stream.close(closeResolve2);\n    await Promise.all([closePromise1, closePromise2]);\n  },\n};\n\nexport const writeStreamTest20 = {\n  async test() {\n    const file = '/tmp/write-autoclose-opt1.txt';\n    let stream = createWriteStream(file, { flags: 'w+', autoClose: false });\n    stream.write('Test1');\n    stream.end();\n    const { promise, resolve, reject } = Promise.withResolvers();\n    stream.on('finish', () => {\n      stream.on('close', reject);\n      process.nextTick(() => {\n        strictEqual(stream.closed, false);\n        notStrictEqual(stream.fd, null);\n        resolve();\n      });\n    });\n    await promise;\n\n    const { promise: nextPromise, resolve: nextResolve } =\n      Promise.withResolvers();\n    const stream2 = createWriteStream(null, { fd: stream.fd, start: 0 });\n    stream2.write('Test2');\n    stream2.end();\n    stream2.on('finish', () => {\n      strictEqual(stream2.closed, false);\n      stream2.on('close', () => {\n        strictEqual(stream2.fd, null);\n        strictEqual(stream2.closed, true);\n        nextResolve();\n      });\n    });\n\n    await nextPromise;\n\n    const data = readFileSync(file, 'utf8');\n    strictEqual(data, 'Test2');\n  },\n};\n\nexport const writeStreamTest21 = {\n  async test() {\n    // This is to test success scenario where autoClose is true\n    const file = '/tmp/write-autoclose-opt2.txt';\n    const stream = createWriteStream(file, { autoClose: true });\n    stream.write('Test3');\n    stream.end();\n    const { promise, resolve } = Promise.withResolvers();\n    stream.on('finish', () => {\n      strictEqual(stream.closed, false);\n      stream.on('close', () => {\n        strictEqual(stream.fd, null);\n        strictEqual(stream.closed, true);\n        resolve();\n      });\n    });\n    await promise;\n  },\n};\n\nexport const writeStreamTest22 = {\n  test() {\n    throws(() => WriteStream.prototype.autoClose, {\n      code: 'ERR_INVALID_THIS',\n    });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/fs-writestream-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"fs-writestream-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"fs-writestream-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_fs_module\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/http-agent-nodejs-server.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// This is a sidecar that runs alongside the http-client-nodejs-test.* tests.\n// It is executed using the appropriate Node.js version defined in build/deps/nodejs.MODULE.bazel.\nconst http = require('node:http');\n\nfunction reportAddress(server) {\n  const address = server.address();\n  console.info(`Listening on ${address.address}:${address.port}`);\n}\n\nconst pongServer = http.createServer((req, res) => {\n  req.resume();\n  req.on('end', () => {\n    res.writeHead(200);\n    res.end('pong');\n  });\n});\npongServer.listen(\n  process.env.PONG_SERVER_PORT,\n  process.env.SIDECAR_HOSTNAME,\n  () => reportAddress(pongServer)\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/http-agent-nodejs-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport http from 'node:http';\nimport https from 'node:https';\nimport { strictEqual, ok } from 'node:assert';\n\nexport const checkPortsSetCorrectly = {\n  test(_ctrl, env) {\n    const keys = ['SIDECAR_HOSTNAME', 'PONG_SERVER_PORT'];\n    for (const key of keys) {\n      strictEqual(typeof env[key], 'string');\n      ok(env[key].length > 0);\n    }\n  },\n};\n\n// Test is taken from test/parallel/test-http-agent-getname.js\nexport const testHttpAgentGetName = {\n  async test() {\n    const agent = new http.Agent();\n\n    // Default to localhost\n    strictEqual(\n      agent.getName({\n        port: 80,\n        localAddress: '192.168.1.1',\n      }),\n      'localhost:80:192.168.1.1'\n    );\n\n    // empty argument\n    strictEqual(agent.getName(), 'localhost::');\n\n    // empty options\n    strictEqual(agent.getName({}), 'localhost::');\n\n    // pass all arguments\n    strictEqual(\n      agent.getName({\n        host: '0.0.0.0',\n        port: 80,\n        localAddress: '192.168.1.1',\n      }),\n      '0.0.0.0:80:192.168.1.1'\n    );\n\n    // unix socket\n    strictEqual(\n      agent.getName({\n        socketPath: '/tmp/foo/bar',\n      }),\n      `localhost:::/tmp/foo/bar`\n    );\n\n    for (const family of [0, null, undefined, 'bogus'])\n      strictEqual(agent.getName({ family }), 'localhost::');\n\n    for (const family of [4, 6])\n      strictEqual(agent.getName({ family }), `localhost:::${family}`);\n  },\n};\n\n// Test is taken from test/parallel/test-http-agent-null.js\nexport const testHttpAgentNull = {\n  async test(_ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    http.get(\n      {\n        agent: null,\n        port: env.PONG_SERVER_PORT,\n        host: env.SIDECAR_HOSTNAME,\n      },\n      resolve\n    );\n    await promise;\n  },\n};\n\n// Test is taken from test/parallel/test-https-agent-constructor.js\nexport const testHttpsAgentConstructor = {\n  async test() {\n    ok(new https.Agent() instanceof https.Agent);\n    strictEqual(typeof https.request, 'function');\n    strictEqual(typeof http.get, 'function');\n  },\n};\n\n// Tests covering http-agent\n// - [ ] test/parallel/test-http-agent-abort-controller.js\n// - [ ] test/parallel/test-http-agent-close.js\n// - [ ] test/parallel/test-http-agent-destroyed-socket.js\n// - [ ] test/parallel/test-http-agent-domain-reused-gc.js\n// - [ ] test/parallel/test-http-agent-error-on-idle.js\n// - [ ] test/parallel/test-http-agent-false.js\n// - [x] test/parallel/test-http-agent-getname.js\n// - [ ] test/parallel/test-http-agent-keepalive-delay.js\n// - [ ] test/parallel/test-http-agent-keepalive.js\n// - [ ] test/parallel/test-http-agent-maxsockets-respected.js\n// - [ ] test/parallel/test-http-agent-maxsockets.js\n// - [ ] test/parallel/test-http-agent-maxtotalsockets.js\n// - [ ] test/parallel/test-http-agent-no-protocol.js\n// - [x] test/parallel/test-http-agent-null.js\n// - [ ] test/parallel/test-http-agent-remove.js\n// - [ ] test/parallel/test-http-agent-reuse-drained-socket-only.js\n// - [ ] test/parallel/test-http-agent-scheduling.js\n// - [ ] test/parallel/test-http-agent-timeout-option.js\n// - [ ] test/parallel/test-http-agent-timeout.js\n// - [ ] test/parallel/test-http-agent-uninitialized-with-handle.js\n// - [ ] test/parallel/test-http-agent.js\n// - [ ] test/parallel/test-https-agent-abort-controller.js\n// - [ ] test/parallel/test-https-agent-additional-options.js\n// - [x] test/parallel/test-https-agent-constructor.js\n// - [ ] test/parallel/test-https-agent-create-connection.js\n// - [ ] test/parallel/test-https-agent-disable-session-reuse.js\n// - [ ] test/parallel/test-https-agent-getname.js\n// - [ ] test/parallel/test-https-agent-keylog.js\n// - [ ] test/parallel/test-https-agent-servername.js\n// - [ ] test/parallel/test-https-agent-session-eviction.js\n// - [ ] test/parallel/test-https-agent-session-injection.js\n// - [ ] test/parallel/test-https-agent-session-reuse.js\n// - [ ] test/parallel/test-https-agent-sni.js\n// - [ ] test/parallel/test-https-agent-sockets-leak.js\n// - [ ] test/parallel/test-https-agent-unref-socket.js\n// - [ ] test/parallel/test-https-agent.js\n\n// Tests doesn't make sense for workerd:\n//\n// - [ ] test/parallel/test-http-agent-uninitialized.js\n"
  },
  {
    "path": "src/workerd/api/node/tests/http-agent-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"http-agent-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"http-agent-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_http_modules\"],\n        bindings = [\n          (name = \"SIDECAR_HOSTNAME\", fromEnvironment = \"SIDECAR_HOSTNAME\"),\n          (name = \"PONG_SERVER_PORT\", fromEnvironment = \"PONG_SERVER_PORT\"),\n        ],\n      )\n    ),\n    ( name = \"internet\", network = ( allow = [\"private\"] ) ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/http-client-nodejs-server.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// This is a sidecar that runs alongside the http-client-nodejs-test.* tests.\n// It is executed using the appropriate Node.js version defined in build/deps/nodejs.MODULE.bazel.\nconst http = require('node:http');\nconst assert = require('node:assert/strict');\nconst zlib = require('node:zlib');\n\nfunction listenTo(server, port) {\n  server.listen(port, process.env.SIDECAR_HOSTNAME, () => {\n    const address = server.address();\n    console.info(`Listening on ${address.address}:${address.port}`);\n  });\n}\n\nconst pongServer = http.createServer((req, res) => {\n  req.resume();\n  req.on('end', () => {\n    res.writeHead(200);\n    res.end('pong');\n  });\n});\n\nlistenTo(pongServer, process.env.PONG_SERVER_PORT);\n\nconst asdServer = http.createServer((_req, res) => {\n  res.end('asd');\n});\n\nlistenTo(asdServer, process.env.ASD_SERVER_PORT);\n\n{\n  const expectedHeaders = {\n    DELETE: ['content-length', 'host'],\n    GET: ['host'],\n    HEAD: ['host'],\n    OPTIONS: ['content-length', 'host'],\n    POST: ['content-length', 'host'],\n    PUT: ['content-length', 'host'],\n    TRACE: ['content-length', 'host'],\n  };\n\n  const defaultHeadersExistServer = http.createServer((req, res) => {\n    res.end();\n\n    assert(\n      Object.hasOwn(expectedHeaders, req.method),\n      `${req.method} was an unexpected method`\n    );\n\n    const requestHeaders = Object.keys(req.headers);\n    for (const header of requestHeaders) {\n      assert.ok(\n        expectedHeaders[req.method].includes(header.toLowerCase()),\n        `${header} should not exist for method ${req.method}`\n      );\n    }\n\n    assert.deepStrictEqual(\n      requestHeaders,\n      expectedHeaders[req.method],\n      `Unexpected headers for method ${req.method}`\n    );\n  });\n\n  listenTo(defaultHeadersExistServer, process.env.DEFAULT_HEADERS_EXIST_PORT);\n}\n\nconst requestArgumentsServer = http.createServer((req, res) => {\n  assert.strictEqual(req.url, '/testpath');\n  res.end();\n});\n\nlistenTo(requestArgumentsServer, process.env.REQUEST_ARGUMENTS_PORT);\n\nconst helloWorldServer = http.createServer((req, res) => {\n  res.removeHeader('Date');\n  res.setHeader('Keep-Alive', 'timeout=1');\n\n  switch (req.url.slice(1).split('?').at(0)) {\n    case 'join-duplicate-headers': {\n      res.writeHead(200, [\n        'authorization',\n        '3',\n        'authorization',\n        '4',\n        'cookie',\n        'foo',\n        'cookie',\n        'bar',\n      ]);\n      res.end();\n      break;\n    }\n\n    case 'search-path': {\n      res.writeHead(200, { 'Content-Type': 'text/plain', url: req.url });\n      res.end('Hello, World!');\n      break;\n    }\n\n    case 'echo': {\n      // Echo the request body back as the response\n      let body = '';\n      req.on('data', (chunk) => {\n        body += chunk.toString();\n      });\n      req.on('end', () => {\n        res.writeHead(200, { 'Content-Type': 'text/plain' });\n        res.end(body);\n      });\n      break;\n    }\n\n    default: {\n      res.end();\n    }\n  }\n});\n\nlistenTo(helloWorldServer, process.env.HELLO_WORLD_SERVER_PORT);\n\nconst gzipServer = http.createServer((_req, res) => {\n  const body = zlib.gzipSync(Buffer.from('hello from gzip server'));\n  res.writeHead(200, {\n    'Content-Encoding': 'gzip',\n    'Content-Type': 'text/plain',\n  });\n  res.end(body);\n});\n\nlistenTo(gzipServer, process.env.GZIP_SERVER_PORT);\n"
  },
  {
    "path": "src/workerd/api/node/tests/http-client-nodejs-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport http from 'node:http';\nimport { strictEqual, ok, throws, deepStrictEqual } from 'node:assert';\nimport { once } from 'node:events';\nimport { get } from 'node:http';\nimport { gunzipSync } from 'node:zlib';\n\nexport const checkPortsSetCorrectly = {\n  test(_ctrl, env) {\n    const keys = [\n      'SIDECAR_HOSTNAME',\n      'PONG_SERVER_PORT',\n      'ASD_SERVER_PORT',\n      'DEFAULT_HEADERS_EXIST_PORT',\n      'REQUEST_ARGUMENTS_PORT',\n      'HELLO_WORLD_SERVER_PORT',\n      'GZIP_SERVER_PORT',\n    ];\n    for (const key of keys) {\n      strictEqual(typeof env[key], 'string');\n      ok(env[key].length > 0);\n    }\n  },\n};\n\n// TODO(soon): This is triggering io-context error. Fix this edge case.\n// Test is taken from Node.js: test/parallel/test-http-client-request-options.js\n// export const testHttpClientRequestOptions = {\n//   async test(_ctrl, env) {\n//     const headers = { foo: 'Bar' };\n//     const { promise, resolve, reject } = Promise.withResolvers();\n//     const url = new URL(`http://localhost:${env.PONG_SERVER_PORT}/ping?q=term`);\n//     url.headers = headers;\n//     const clientReq = http.request(url);\n//     clientReq.on('close', resolve);\n//     clientReq.on('error', reject)\n//     clientReq.end();\n//     await promise;\n//   },\n// };\n\n// Test is taken from test/parallel/test-http-client-res-destroyed.js\nexport const testHttpClientResDestroyed = {\n  async test(_ctrl, env) {\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      http.get(\n        {\n          hostname: env.SIDECAR_HOSTNAME,\n          port: env.ASD_SERVER_PORT,\n        },\n        (res) => {\n          strictEqual(res.destroyed, false);\n          res.destroy();\n          strictEqual(res.destroyed, true);\n          res.on('close', resolve);\n        }\n      );\n      await promise;\n    }\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      http.get(\n        {\n          hostname: env.SIDECAR_HOSTNAME,\n          port: env.ASD_SERVER_PORT,\n        },\n        (res) => {\n          strictEqual(res.destroyed, false);\n          res\n            .on('close', () => {\n              strictEqual(res.destroyed, true);\n              resolve();\n            })\n            .resume();\n        }\n      );\n      await promise;\n    }\n  },\n};\n\n// TODO(soon): Support this test case, if possible with the current implementation\n// Test is taken from test/parallel/test-http-client-response-timeout.js\n// export const testHttpClientResponseTimeout = {\n//   async test(_ctrl, env) {\n//     const { promise, resolve } = Promise.withResolvers();\n//     const req =\n//       http.get({ port: env.TIMEOUT_SERVER_PORT }, (res) => {\n//         res.on('timeout', () => {\n//           resolve();\n//           req.destroy();\n//         });\n//         res.setTimeout(1);\n//       });\n//     await promise;\n//   },\n// };\n\n// TODO(soon): Handle this edge case.\n// Test is taken from test/parallel/test-http-client-close-event.js\n// export const testHttpClientCloseEvent = {\n//   async test(_ctrl, env) {\n//     const { promise, resolve, reject } = Promise.withResolvers();\n//     const req = http.get({ port: env.PONG_SERVER_PORT }, () => {\n//       reject(new Error('Should not have called this callback'));\n//     });\n\n//     const errFn = mock.fn((err) => {\n//       strictEqual(err.constructor, Error);\n//       strictEqual(err.message, 'socket hang up');\n//       strictEqual(err.code, 'ECONNRESET');\n//     });\n//     req.on('error', errFn);\n\n//     req.on('close', () => {\n//       strictEqual(req.destroyed, true);\n//       strictEqual(errFn.mock.callCount(), 1);\n//       resolve();\n//     });\n\n//     req.destroy();\n//     await promise;\n//   },\n// };\n\n// Test is taken from test/parallel/test-http-client-default-headers-exist.js\nexport const testHttpClientDefaultHeadersExist = {\n  async test(_ctrl, env) {\n    const expectedHeaders = {\n      DELETE: ['host', 'connection'],\n      GET: ['host', 'connection'],\n      HEAD: ['host', 'connection'],\n      OPTIONS: ['host', 'connection'],\n      POST: ['host', 'connection', 'content-length'],\n      PUT: ['host', 'connection', 'content-length'],\n      TRACE: ['host', 'connection'],\n    };\n\n    const expectedMethods = Object.keys(expectedHeaders);\n\n    await Promise.all(\n      expectedMethods.map(async (method) => {\n        const request = http\n          .request({\n            method: method,\n            hostname: env.SIDECAR_HOSTNAME,\n            port: env.DEFAULT_HEADERS_EXIST_PORT,\n          })\n          .end();\n        return once(request, 'response');\n      })\n    );\n  },\n};\n\n// Test is taken from test/parallel/test-http-client-defaults.js\nexport const testHttpClientDefaults = {\n  async test() {\n    {\n      const req = new http.ClientRequest({});\n      strictEqual(req.path, '/');\n      strictEqual(req.method, 'GET');\n    }\n\n    {\n      const req = new http.ClientRequest({ method: '' });\n      strictEqual(req.path, '/');\n      strictEqual(req.method, 'GET');\n    }\n\n    {\n      const req = new http.ClientRequest({ path: '' });\n      strictEqual(req.path, '/');\n      strictEqual(req.method, 'GET');\n    }\n  },\n};\n\n// Test is taken from test/parallel/test-http-client-encoding.js\nexport const testHttpClientEncoding = {\n  async test(_ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    http\n      .request(\n        {\n          hostname: env.SIDECAR_HOSTNAME,\n          port: env.PONG_SERVER_PORT,\n          encoding: 'utf8',\n        },\n        (res) => {\n          let data = '';\n          res.on('data', (chunk) => (data += chunk));\n          res.on('end', () => {\n            strictEqual(data, 'pong');\n            resolve();\n          });\n        }\n      )\n      .end();\n    await promise;\n  },\n};\n\n// Test is taken from test/parallel/test-http-client-headers-host-array.js\nexport const testHttpClientHeadersHostArray = {\n  async test() {\n    const options = {\n      port: '80',\n      path: '/',\n      headers: {\n        host: [],\n      },\n    };\n\n    throws(\n      () => {\n        http.request(options);\n      },\n      {\n        code: /ERR_INVALID_ARG_TYPE/,\n      },\n      'http request should throw when passing array as header host'\n    );\n  },\n};\n\n// Test is taken from test/parallel/test-http-client-input-function.js\nexport const testHttpClientInputFunction = {\n  async test(_ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    const req = new http.ClientRequest(\n      { hostname: env.SIDECAR_HOSTNAME, port: env.PONG_SERVER_PORT },\n      (response) => {\n        let body = '';\n        response.setEncoding('utf8');\n        response.on('data', (chunk) => {\n          body += chunk;\n        });\n\n        response.on('end', () => {\n          strictEqual(body, 'pong');\n          resolve();\n        });\n      }\n    );\n\n    req.end();\n    await promise;\n  },\n};\n\n// Test is taken from test/parallel/test-http-client-invalid-path.js\nexport const testHttpClientInvalidPath = {\n  async test() {\n    throws(\n      () => {\n        http\n          .request({\n            path: '/thisisinvalid\\uffe2',\n          })\n          .end();\n      },\n      {\n        code: 'ERR_UNESCAPED_CHARACTERS',\n        name: 'TypeError',\n      }\n    );\n  },\n};\n\n// Test is taken from test/parallel/test-http-client-unescaped-path.js\nexport const testHttpClientUnescapedPath = {\n  async test() {\n    for (let i = 0; i <= 32; i += 1) {\n      const path = `bad${String.fromCharCode(i)}path`;\n      throws(\n        () =>\n          http.get({ path }, () => {\n            throw new Error('This should not happen');\n          }),\n        {\n          code: 'ERR_UNESCAPED_CHARACTERS',\n          name: 'TypeError',\n          message: 'Request path contains unescaped characters',\n        }\n      );\n    }\n  },\n};\n\n// Test is taken from test/parallel/test-http-request-arguments.js\nexport const testHttpRequestArguments = {\n  async test(_ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    http.get(\n      'http://example.com/testpath',\n      { hostname: env.SIDECAR_HOSTNAME, port: env.REQUEST_ARGUMENTS_PORT },\n      (res) => {\n        res.resume();\n        resolve();\n      }\n    );\n    await promise;\n  },\n};\n\n// Test is taken from test/parallel/test-http-request-dont-override-options.js\nexport const testHttpRequestDontOverrideOptions = {\n  async test(_ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    const agent = new http.Agent();\n    agent.defaultPort = env.PONG_SERVER_PORT;\n\n    // Options marked as explicitly undefined for readability\n    // in this test, they should STAY undefined as options should not\n    // be mutable / modified\n    const options = {\n      host: undefined,\n      hostname: env.SIDECAR_HOSTNAME,\n      port: undefined,\n      defaultPort: undefined,\n      path: undefined,\n      method: undefined,\n      agent: agent,\n    };\n\n    http\n      .request(options, function (res) {\n        res.resume();\n        strictEqual(options.host, undefined);\n        strictEqual(options.hostname, env.SIDECAR_HOSTNAME);\n        strictEqual(options.port, undefined);\n        strictEqual(options.defaultPort, undefined);\n        strictEqual(options.path, undefined);\n        strictEqual(options.method, undefined);\n        resolve();\n      })\n      .end();\n    await promise;\n  },\n};\n\n// Test is taken from test/parallel/test-http-request-host-header.js\nexport const testHttpRequestHostHeader = {\n  async test(_ctrl, env) {\n    // From RFC 7230 5.4 https://datatracker.ietf.org/doc/html/rfc7230#section-5.4\n    // A server MUST respond with a 400 (Bad Request) status code to any\n    // HTTP/1.1 request message that lacks a Host header field\n    const { promise, resolve } = Promise.withResolvers();\n    http.get(\n      {\n        hostname: env.SIDECAR_HOSTNAME,\n        port: env.HEADER_VALIDATION_SERVER_PORT,\n        headers: [],\n      },\n      (res) => {\n        strictEqual(res.statusCode, 400);\n        strictEqual(res.headers.connection, 'close');\n        resolve();\n      }\n    );\n    await promise;\n  },\n};\n\n// Test is taken from test/parallel/test-http-request-invalid-method-error.js\nexport const testHttpRequestInvalidMethodError = {\n  async test() {\n    throws(() => http.request({ method: '\\0' }), {\n      code: 'ERR_INVALID_HTTP_TOKEN',\n      name: 'TypeError',\n      message: 'Method must be a valid HTTP token [\"\\u0000\"]',\n    });\n  },\n};\n\n// Test is taken from test/parallel/test-http-request-join-authorization-headers.js\nexport const testHttpRequestJoinAuthorizationHeaders = {\n  async test(_ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    http.get(\n      {\n        hostname: env.SIDECAR_HOSTNAME,\n        port: env.HELLO_WORLD_SERVER_PORT,\n        method: 'POST',\n        headers: [\n          'authorization',\n          '1',\n          'authorization',\n          '2',\n          'cookie',\n          'foo',\n          'cookie',\n          'bar',\n        ],\n        joinDuplicateHeaders: true,\n        path: '/join-duplicate-headers',\n      },\n      (res) => {\n        strictEqual(res.statusCode, 200);\n        strictEqual(res.headers.authorization, '3, 4');\n        strictEqual(res.headers.cookie, 'foo; bar');\n        resolve();\n      }\n    );\n    await promise;\n  },\n};\n\nexport const testGetExport = {\n  async test() {\n    const { get: getFn } = await import('node:http');\n    deepStrictEqual(get, getFn);\n  },\n};\n\nexport const testPostRequestBodyEcho = {\n  async test(_ctrl, env) {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const testData =\n      'Hello, this is test data for POST request with body echoing!';\n\n    const req = http.request(\n      {\n        hostname: env.SIDECAR_HOSTNAME,\n        port: env.HELLO_WORLD_SERVER_PORT,\n        path: '/echo',\n        method: 'POST',\n        headers: {\n          'Content-Type': 'text/plain',\n          'Content-Length': Buffer.byteLength(testData),\n        },\n      },\n      (res) => {\n        let responseBody = '';\n        res.on('data', (chunk) => {\n          responseBody += chunk.toString();\n        });\n        res.on('end', () => {\n          try {\n            strictEqual(res.statusCode, 200);\n            strictEqual(\n              responseBody,\n              testData,\n              `Response body should match the request body. Expected: ${JSON.stringify(testData)}, Got: ${JSON.stringify(responseBody)}`\n            );\n            resolve();\n          } catch (err) {\n            reject(err);\n          }\n        });\n      }\n    );\n\n    req.on('error', (err) => {\n      reject(new Error(`Request failed: ${err.message}`));\n    });\n\n    req.write(testData);\n    req.end();\n\n    await promise;\n  },\n};\n\nexport const testHttpGetTestSearchParams = {\n  async test(_ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    http.get(\n      {\n        hostname: env.SIDECAR_HOSTNAME,\n        port: env.HELLO_WORLD_SERVER_PORT,\n        method: 'POST',\n        path: '/search-path?hello=world',\n      },\n      (res) => {\n        strictEqual(res.statusCode, 200);\n        strictEqual(res.headers.url, '/search-path?hello=world');\n        resolve();\n      }\n    );\n    await promise;\n  },\n};\n\n// Regression test for https://github.com/cloudflare/workerd/issues/5148\nexport const testBodyDataDuplicationRegression = {\n  async test(_ctrl, env) {\n    const { promise, resolve, reject } = Promise.withResolvers();\n\n    http\n      .request(\n        {\n          hostname: env.SIDECAR_HOSTNAME,\n          port: env.HELLO_WORLD_SERVER_PORT,\n          path: '/echo',\n          method: 'post',\n          headers: {\n            'content-type': 'application/json;charset=utf-8',\n          },\n        },\n        (response) => {\n          strictEqual(response.statusCode, 200);\n          let expected = '';\n          response.on('data', (chunk) => (expected += chunk));\n          response.on('end', () => {\n            strictEqual(\n              expected,\n              '{\"email\":\"posting-wrangler@email.mail\",\"from\":\"wrangler\"}'\n            );\n            resolve(response);\n          });\n        }\n      )\n      .on('error', reject)\n      .end(\n        Buffer.from(\n          JSON.stringify({\n            email: 'posting-wrangler@email.mail',\n            from: 'wrangler',\n          })\n        )\n      );\n\n    await promise;\n  },\n};\n\n// Verify that the http module does not automatically decompress response bodies.\n// Node.js http passes raw bytes through, leaving Content-Encoding handling to the caller.\nexport const testHttpClientGzipResponseNotAutoDecompressed = {\n  async test(_ctrl, env) {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    http.get(\n      {\n        hostname: env.SIDECAR_HOSTNAME,\n        port: env.GZIP_SERVER_PORT,\n      },\n      (res) => {\n        strictEqual(res.headers['content-encoding'], 'gzip');\n        const chunks = [];\n        res.on('data', (chunk) => chunks.push(chunk));\n        res.on('end', () => {\n          try {\n            const raw = Buffer.concat(chunks);\n            // The body should still be compressed — manually gunzipping\n            // should recover the original text.\n            const decompressed = gunzipSync(raw);\n            strictEqual(decompressed.toString(), 'hello from gzip server');\n            resolve();\n          } catch (err) {\n            reject(err);\n          }\n        });\n      }\n    );\n    await promise;\n  },\n};\n\n// Relevant Node.js tests\n// - [ ] test/parallel/test-http-client-abort-destroy.js\n// - [ ] test/parallel/test-http-client-abort-event.js\n// - [ ] test/parallel/test-http-client-abort-keep-alive-destroy-res.js\n// - [ ] test/parallel/test-http-client-abort-keep-alive-queued-tcp-socket.js\n// - [ ] test/parallel/test-http-client-abort-keep-alive-queued-unix-socket.js\n// - [ ] test/parallel/test-http-client-abort-no-agent.js\n// - [ ] test/parallel/test-http-client-abort-response-event.js\n// - [ ] test/parallel/test-http-client-abort-unix-socket.js\n// - [ ] test/parallel/test-http-client-abort.js\n// - [ ] test/parallel/test-http-client-abort2.js\n// - [ ] test/parallel/test-http-client-abort3.js\n// - [ ] test/parallel/test-http-client-aborted-event.js\n// - [ ] test/parallel/test-http-client-agent-abort-close-event.js\n// - [ ] test/parallel/test-http-client-agent-end-close-event.js\n// - [ ] test/parallel/test-http-client-agent.js\n// - [ ] test/parallel/test-http-client-check-http-token.js\n// - [x] test/parallel/test-http-client-close-event.js\n// - [ ] test/parallel/test-http-client-close-with-default-agent.js\n// - [x] test/parallel/test-http-client-default-headers-exist.js\n// - [x] test/parallel/test-http-client-defaults.js\n// - [x] test/parallel/test-http-client-encoding.js\n// - [ ] test/parallel/test-http-client-finished.js\n// - [ ] test/parallel/test-http-client-get-url.js\n// - [ ] test/parallel/test-http-client-headers-array.js\n// - [x] test/parallel/test-http-client-headers-host-array.js\n// - [ ] test/parallel/test-http-client-incomingmessage-destroy.js\n// - [x] test/parallel/test-http-client-input-function.js\n// - [x] test/parallel/test-http-client-invalid-path.js\n// - [ ] test/parallel/test-http-client-keep-alive-hint.js\n// - [ ] test/parallel/test-http-client-keep-alive-release-before-finish.js\n// - [ ] test/parallel/test-http-client-override-global-agent.js\n// - [ ] test/parallel/test-http-client-race-2.js\n// - [ ] test/parallel/test-http-client-race.js\n// - [ ] test/parallel/test-http-client-read-in-error.js\n// - [ ] test/parallel/test-http-client-readable.js\n// - [ ] test/parallel/test-http-client-reject-chunked-with-content-length.js\n// - [ ] test/parallel/test-http-client-reject-cr-no-lf.js\n// - [ ] test/parallel/test-http-client-reject-unexpected-agent.js\n// - [ ] test/parallel/test-http-client-req-error-dont-double-fire.js\n// - [x] test/parallel/test-http-client-request-options.js\n// - [x] test/parallel/test-http-client-res-destroyed.js\n// - [x] test/parallel/test-http-client-response-timeout.js\n// - [x] test/parallel/test-http-client-set-timeout.js\n// - [ ] test/parallel/test-http-client-spurious-aborted.js\n// - [ ] test/parallel/test-http-client-timeout-agent.js\n// - [ ] test/parallel/test-http-client-timeout-connect-listener.js\n// - [ ] test/parallel/test-http-client-timeout-event.js\n// - [ ] test/parallel/test-http-client-timeout-on-connect.js\n// - [ ] test/parallel/test-http-client-timeout-option-listeners.js\n// - [ ] test/parallel/test-http-client-timeout-option-with-agent.js\n// - [ ] test/parallel/test-http-client-timeout-option.js\n// - [ ] test/parallel/test-http-client-timeout-with-data.js\n// - [ ] test/parallel/test-http-client-timeout.js\n// - [x] test/parallel/test-http-client-unescaped-path.js\n// - [x] test/parallel/test-http-request-arguments.js\n// - [x] test/parallel/test-http-request-dont-override-options.js\n// - [ ] test/parallel/test-http-request-end-twice.js\n// - [ ] test/parallel/test-http-request-end.js\n// - [x] test/parallel/test-http-request-host-header.js\n// - [x] test/parallel/test-http-request-invalid-method-error.js\n// - [x] test/parallel/test-http-request-join-authorization-headers.js\n// - [ ] test/parallel/test-http-request-large-payload.js\n// - [ ] test/parallel/test-http-request-smuggling-content-length.js\n\n// Tests doesn't make sense for workerd:\n// - test/parallel/test-http-client-error-rawbytes.js\n// - test/parallel/test-http-client-immediate-error.js\n// - test/parallel/test-http-client-insecure-http-parser-error.js\n// - test/parallel/test-http-client-parse-error.js\n// - test/parallel/test-http-client-pipe-end.js\n// - test/parallel/test-http-client-response-domain.js\n// - test/parallel/test-http-client-set-timeout-after-end.js\n// - test/parallel/test-http-client-upload-buf.js\n// - test/parallel/test-http-client-upload.js\n// - test/parallel/test-http-client-with-create-connection.js\n// - test/parallel/test-http-request-method-delete-payload.js\n// - test/parallel/test-http-request-methods.js\n"
  },
  {
    "path": "src/workerd/api/node/tests/http-client-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"http-client-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"http-client-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_http_modules\", \"nodejs_zlib\"],\n        bindings = [\n          (name = \"SIDECAR_HOSTNAME\", fromEnvironment = \"SIDECAR_HOSTNAME\"),\n          (name = \"PONG_SERVER_PORT\", fromEnvironment = \"PONG_SERVER_PORT\"),\n          (name = \"ASD_SERVER_PORT\", fromEnvironment = \"ASD_SERVER_PORT\"),\n          (name = \"DEFAULT_HEADERS_EXIST_PORT\", fromEnvironment = \"DEFAULT_HEADERS_EXIST_PORT\"),\n          (name = \"REQUEST_ARGUMENTS_PORT\", fromEnvironment = \"REQUEST_ARGUMENTS_PORT\"),\n          (name = \"HELLO_WORLD_SERVER_PORT\", fromEnvironment = \"HELLO_WORLD_SERVER_PORT\"),\n          (name = \"GZIP_SERVER_PORT\", fromEnvironment = \"GZIP_SERVER_PORT\"),\n        ],\n      )\n    ),\n    ( name = \"internet\", network = ( allow = [\"private\"] ) ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/http-incoming-nodejs-test.js",
    "content": "import http from 'node:http';\nimport { deepStrictEqual, strictEqual } from 'node:assert';\n\n// Test is taken from test/parallel/test-http-incoming-matchKnownFields.js\nexport const testHttpIncomingMatchKnownFields = {\n  async test() {\n    function checkDest(field, result, value) {\n      const dest = {};\n\n      const incomingMessage = new http.IncomingMessage(field);\n      // Dest is changed by IncomingMessage._addHeaderLine\n      if (value) incomingMessage._addHeaderLine(field, 'test', dest);\n      incomingMessage._addHeaderLine(field, value, dest);\n      deepStrictEqual(dest, result);\n    }\n\n    checkDest('', { '': undefined });\n    checkDest('Content-Type', { 'content-type': undefined });\n    checkDest('content-type', { 'content-type': 'test' }, 'value');\n    checkDest('User-Agent', { 'user-agent': undefined });\n    checkDest('user-agent', { 'user-agent': 'test' }, 'value');\n    checkDest('Referer', { referer: undefined });\n    checkDest('referer', { referer: 'test' }, 'value');\n    checkDest('Host', { host: undefined });\n    checkDest('host', { host: 'test' }, 'value');\n    checkDest('Authorization', { authorization: undefined }, undefined);\n    checkDest('authorization', { authorization: 'test' }, 'value');\n    checkDest('Proxy-Authorization', { 'proxy-authorization': undefined });\n    checkDest(\n      'proxy-authorization',\n      { 'proxy-authorization': 'test' },\n      'value'\n    );\n    checkDest('If-Modified-Since', { 'if-modified-since': undefined });\n    checkDest('if-modified-since', { 'if-modified-since': 'test' }, 'value');\n    checkDest('If-Unmodified-Since', { 'if-unmodified-since': undefined });\n    checkDest(\n      'if-unmodified-since',\n      { 'if-unmodified-since': 'test' },\n      'value'\n    );\n    checkDest('Form', { form: undefined });\n    checkDest('form', { form: 'test, value' }, 'value');\n    checkDest('Location', { location: undefined });\n    checkDest('location', { location: 'test' }, 'value');\n    checkDest('Max-Forwards', { 'max-forwards': undefined });\n    checkDest('max-forwards', { 'max-forwards': 'test' }, 'value');\n    checkDest('Retry-After', { 'retry-after': undefined });\n    checkDest('retry-after', { 'retry-after': 'test' }, 'value');\n    checkDest('Etag', { etag: undefined });\n    checkDest('etag', { etag: 'test' }, 'value');\n    checkDest('Last-Modified', { 'last-modified': undefined });\n    checkDest('last-modified', { 'last-modified': 'test' }, 'value');\n    checkDest('Server', { server: undefined });\n    checkDest('server', { server: 'test' }, 'value');\n    checkDest('Age', { age: undefined });\n    checkDest('age', { age: 'test' }, 'value');\n    checkDest('Expires', { expires: undefined });\n    checkDest('expires', { expires: 'test' }, 'value');\n    checkDest('Set-Cookie', { 'set-cookie': [undefined] });\n    checkDest('set-cookie', { 'set-cookie': ['test', 'value'] }, 'value');\n    checkDest('Transfer-Encoding', { 'transfer-encoding': undefined });\n    checkDest(\n      'transfer-encoding',\n      { 'transfer-encoding': 'test, value' },\n      'value'\n    );\n    checkDest('Date', { date: undefined });\n    checkDest('date', { date: 'test, value' }, 'value');\n    checkDest('Connection', { connection: undefined });\n    checkDest('connection', { connection: 'test, value' }, 'value');\n    checkDest('Cache-Control', { 'cache-control': undefined });\n    checkDest('cache-control', { 'cache-control': 'test, value' }, 'value');\n    checkDest('Transfer-Encoding', { 'transfer-encoding': undefined });\n    checkDest(\n      'transfer-encoding',\n      { 'transfer-encoding': 'test, value' },\n      'value'\n    );\n    checkDest('Vary', { vary: undefined });\n    checkDest('vary', { vary: 'test, value' }, 'value');\n    checkDest('Content-Encoding', { 'content-encoding': undefined }, undefined);\n    checkDest(\n      'content-encoding',\n      { 'content-encoding': 'test, value' },\n      'value'\n    );\n    checkDest('Cookie', { cookie: undefined });\n    checkDest('cookie', { cookie: 'test; value' }, 'value');\n    checkDest('Origin', { origin: undefined });\n    checkDest('origin', { origin: 'test, value' }, 'value');\n    checkDest('Upgrade', { upgrade: undefined });\n    checkDest('upgrade', { upgrade: 'test, value' }, 'value');\n    checkDest('Expect', { expect: undefined });\n    checkDest('expect', { expect: 'test, value' }, 'value');\n    checkDest('If-Match', { 'if-match': undefined });\n    checkDest('if-match', { 'if-match': 'test, value' }, 'value');\n    checkDest('If-None-Match', { 'if-none-match': undefined });\n    checkDest('if-none-match', { 'if-none-match': 'test, value' }, 'value');\n    checkDest('Accept', { accept: undefined });\n    checkDest('accept', { accept: 'test, value' }, 'value');\n    checkDest('Accept-Encoding', { 'accept-encoding': undefined });\n    checkDest('accept-encoding', { 'accept-encoding': 'test, value' }, 'value');\n    checkDest('Accept-Language', { 'accept-language': undefined });\n    checkDest('accept-language', { 'accept-language': 'test, value' }, 'value');\n    checkDest('X-Forwarded-For', { 'x-forwarded-for': undefined });\n    checkDest('x-forwarded-for', { 'x-forwarded-for': 'test, value' }, 'value');\n    checkDest('X-Forwarded-Host', { 'x-forwarded-host': undefined });\n    checkDest(\n      'x-forwarded-host',\n      { 'x-forwarded-host': 'test, value' },\n      'value'\n    );\n    checkDest('X-Forwarded-Proto', { 'x-forwarded-proto': undefined });\n    checkDest(\n      'x-forwarded-proto',\n      { 'x-forwarded-proto': 'test, value' },\n      'value'\n    );\n    checkDest('X-Foo', { 'x-foo': undefined });\n    checkDest('x-foo', { 'x-foo': 'test, value' }, 'value');\n  },\n};\n\n// Test is taken from test/parallel/test-http-incoming-message-connection-setter.js\nexport const testHttpIncomingMessageConnectionSetter = {\n  async test() {\n    const incomingMessage = new http.IncomingMessage();\n\n    strictEqual(incomingMessage.connection, undefined);\n    strictEqual(incomingMessage.socket, undefined);\n\n    incomingMessage.connection = 'fhqwhgads';\n\n    strictEqual(incomingMessage.connection, 'fhqwhgads');\n    strictEqual(incomingMessage.socket, 'fhqwhgads');\n  },\n};\n\n// Test is taken from test/parallel/test-http-incoming-message-destroy.js\nexport const testHttpIncomingMessageOptions = {\n  async test() {\n    const incomingMessage = new http.IncomingMessage();\n\n    strictEqual(incomingMessage.destroy(), incomingMessage);\n  },\n};\n\n// Node.js tests\n// - [x] test/parallel/test-http-incoming-matchKnownFields.js\n// - [x] test/parallel/test-http-incoming-message-connection-setter.js\n// - [x] test/parallel/test-http-incoming-message-destroy.js\n// - [x] test/parallel/test-http-incoming-message-options.js\n// - [ ] test/parallel/test-http-incoming-pipelined-socket-destroy.js\n// - [ ] test/parallel/test-http-incoming-message-options.js\n// - [ ] test/parallel/test-http-incoming-pipelined-socket-destroy.js\n"
  },
  {
    "path": "src/workerd/api/node/tests/http-incoming-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"http-incoming-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"http-incoming-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_http_modules\"],\n        bindings = [],\n      )\n    ),\n    ( name = \"internet\", network = ( allow = [\"private\"] ) ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/http-nodejs-server.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// This is a sidecar that runs alongside the http-client-nodejs-test.* tests.\n// It is executed using the appropriate Node.js version defined in build/deps/nodejs.MODULE.bazel.\nconst http = require('node:http');\nconst assert = require('node:assert/strict');\n\nfunction reportPort(server) {\n  const address = server.address();\n  console.info(`Listening on ${address.address}:${address.port}`);\n}\n\nconst pongServer = http.createServer((req, res) => {\n  req.resume();\n  req.on('end', () => {\n    res.writeHead(200);\n    res.end('pong');\n  });\n});\npongServer.listen(\n  process.env.PONG_SERVER_PORT,\n  process.env.SIDECAR_HOSTNAME,\n  () => reportPort(pongServer)\n);\n\nconst asdServer = http.createServer((_req, res) => {\n  res.end('asd');\n});\nasdServer.listen(\n  process.env.ASD_SERVER_PORT,\n  process.env.SIDECAR_HOSTNAME,\n  () => reportPort(asdServer)\n);\n\nconst timeoutServer = http.createServer((_req, res) => {\n  setTimeout(() => {\n    res.end('pong');\n  }, 1000);\n});\ntimeoutServer.listen(\n  process.env.TIMEOUT_SERVER_PORT,\n  process.env.SIDECAR_HOSTNAME,\n  () => reportPort(timeoutServer)\n);\n\nconst helloWorldServer = http.createServer((req, res) => {\n  res.removeHeader('Date');\n  res.setHeader('Keep-Alive', 'timeout=1');\n\n  switch (req.url.slice(1)) {\n    case 'multiple-writes':\n      res.write('hello');\n      res.end('world');\n      break;\n    case 'end-with-data':\n      res.end('hello world');\n      break;\n    case 'content-length0':\n      res.writeHead(200, { 'Content-Length': '0' });\n      res.end();\n      break;\n    default:\n      res.end();\n  }\n});\nhelloWorldServer.listen(\n  process.env.HELLO_WORLD_SERVER_PORT,\n  process.env.SIDECAR_HOSTNAME,\n  () => reportPort(helloWorldServer)\n);\n\nlet headerValidationServerCount = 0;\nconst headerValidationServer = http.createServer((req, res) => {\n  if (headerValidationServerCount++ !== 0) {\n    switch (req.url.slice(1)) {\n      case 'test-1':\n        assert.deepStrictEqual(req.rawHeaders, [\n          'test',\n          'value',\n          'Host',\n          `${process.env.SIDECAR_HOSTNAME}:${headerValidationServer.address().port}`,\n          'foo',\n          'bar',\n          'foo',\n          'baz',\n          'connection',\n          'close',\n        ]);\n        break;\n      case 'test-2':\n        assert.deepStrictEqual(req.rawHeaders, [\n          'Content-Length',\n          '0',\n          'Host',\n          `${process.env.SIDECAR_HOSTNAME}:${headerValidationServer.address().port}`,\n        ]);\n        break;\n      default:\n        break;\n    }\n  }\n\n  res.end('ok');\n});\nheaderValidationServer.listen(\n  process.env.HEADER_VALIDATION_SERVER_PORT,\n  process.env.SIDECAR_HOSTNAME,\n  () => reportPort(headerValidationServer)\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/http-nodejs-test.js",
    "content": "import { throws, ok, strictEqual, deepStrictEqual } from 'node:assert';\nimport { validateHeaderName, validateHeaderValue, METHODS } from 'node:http';\nimport httpCommon from 'node:_http_common';\nimport { inspect } from 'node:util';\nimport http from 'node:http';\n\nexport const checkPortsSetCorrectly = {\n  test(ctrl, env, ctx) {\n    const keys = [\n      'SIDECAR_HOSTNAME',\n      'PONG_SERVER_PORT',\n      'ASD_SERVER_PORT',\n      'TIMEOUT_SERVER_PORT',\n      'HELLO_WORLD_SERVER_PORT',\n      'HEADER_VALIDATION_SERVER_PORT',\n    ];\n    for (const key of keys) {\n      strictEqual(typeof env[key], 'string');\n      ok(env[key].length > 0);\n    }\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/c514e8f781b2acedb6a2b42208d8f8f4d8392f09/test/parallel/test-http-header-validators.js\nexport const testHttpHeaderValidators = {\n  async test() {\n    // Expected static methods\n    isFunc(validateHeaderName, 'validateHeaderName');\n    isFunc(validateHeaderValue, 'validateHeaderValue');\n\n    // - when used with valid header names - should not throw\n    ['user-agent', 'USER-AGENT', 'User-Agent', 'x-forwarded-for'].forEach(\n      (name) => {\n        validateHeaderName(name);\n      }\n    );\n\n    // - when used with invalid header names:\n    ['איקס-פורוורד-פור', 'x-forwarded-fםr'].forEach((name) => {\n      throws(() => validateHeaderName(name), {\n        code: 'ERR_INVALID_HTTP_TOKEN',\n      });\n    });\n\n    // - when used with valid header values - should not throw\n    [\n      ['x-valid', 1],\n      ['x-valid', '1'],\n      ['x-valid', 'string'],\n    ].forEach(([name, value]) => {\n      validateHeaderValue(name, value);\n    });\n\n    // - when used with invalid header values:\n    [\n      // [header, value, expectedCode]\n      ['x-undefined', undefined, 'ERR_HTTP_INVALID_HEADER_VALUE'],\n      ['x-bad-char', 'לא תקין', 'ERR_INVALID_CHAR'],\n    ].forEach(([name, value, code]) => {\n      throws(() => validateHeaderValue(name, value), { code });\n    });\n\n    // Misc.\n    function isFunc(v, ttl) {\n      ok(v.constructor === Function, `${ttl} is expected to be a function`);\n    }\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/c514e8f781b2acedb6a2b42208d8f8f4d8392f09/test/parallel/test-http-invalidheaderfield2.js\nexport const testInvalidHeaderField2 = {\n  async test() {\n    // Good header field names\n    [\n      'TCN',\n      'ETag',\n      'date',\n      'alt-svc',\n      'Content-Type',\n      '0',\n      'Set-Cookie2',\n      'Set_Cookie',\n      'foo`bar^',\n      'foo|bar',\n      '~foobar',\n      'FooBar!',\n      '#Foo',\n      '$et-Cookie',\n      '%%Test%%',\n      'Test&123',\n      \"It's_fun\",\n      '2*3',\n      '4+2',\n      '3.14159265359',\n    ].forEach(function (str) {\n      strictEqual(\n        httpCommon._checkIsHttpToken(str),\n        true,\n        `_checkIsHttpToken(${inspect(str)}) unexpectedly failed`\n      );\n    });\n    // Bad header field names\n    [\n      ':',\n      '@@',\n      '中文呢', // unicode\n      '((((())))',\n      ':alternate-protocol',\n      'alternate-protocol:',\n      'foo\\nbar',\n      'foo\\rbar',\n      'foo\\r\\nbar',\n      'foo\\x00bar',\n      '\\x7FMe!',\n      '{Start',\n      '(Start',\n      '[Start',\n      'End}',\n      'End)',\n      'End]',\n      '\"Quote\"',\n      'This,That',\n    ].forEach(function (str) {\n      strictEqual(\n        httpCommon._checkIsHttpToken(str),\n        false,\n        `_checkIsHttpToken(${inspect(str)}) unexpectedly succeeded`\n      );\n    });\n\n    // Good header field values\n    [\n      'foo bar',\n      'foo\\tbar',\n      '0123456789ABCdef',\n      '!@#$%^&*()-_=+\\\\;\\':\"[]{}<>,./?|~`',\n    ].forEach(function (str) {\n      strictEqual(\n        httpCommon._checkInvalidHeaderChar(str),\n        false,\n        `_checkInvalidHeaderChar(${inspect(str)}) unexpectedly failed`\n      );\n    });\n\n    // Bad header field values\n    [\n      'foo\\rbar',\n      'foo\\nbar',\n      'foo\\r\\nbar',\n      '中文呢', // unicode\n      '\\x7FMe!',\n      'Testing 123\\x00',\n      'foo\\vbar',\n      'Ding!\\x07',\n    ].forEach(function (str) {\n      strictEqual(\n        httpCommon._checkInvalidHeaderChar(str),\n        true,\n        `_checkInvalidHeaderChar(${inspect(str)}) unexpectedly succeeded`\n      );\n    });\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/c514e8f781b2acedb6a2b42208d8f8f4d8392f09/test/parallel/test-http-methods.js\nexport const testHttpMethods = {\n  async test() {\n    const methods = [\n      'ACL',\n      'BIND',\n      'CHECKOUT',\n      'CONNECT',\n      'COPY',\n      'DELETE',\n      'GET',\n      'HEAD',\n      'LINK',\n      'LOCK',\n      'M-SEARCH',\n      'MERGE',\n      'MKACTIVITY',\n      'MKCALENDAR',\n      'MKCOL',\n      'MOVE',\n      'NOTIFY',\n      'OPTIONS',\n      'PATCH',\n      'POST',\n      'PROPFIND',\n      'PROPPATCH',\n      'PURGE',\n      'PUT',\n      'QUERY',\n      'REBIND',\n      'REPORT',\n      'SEARCH',\n      'SOURCE',\n      'SUBSCRIBE',\n      'TRACE',\n      'UNBIND',\n      'UNLINK',\n      'UNLOCK',\n      'UNSUBSCRIBE',\n    ];\n\n    deepStrictEqual(METHODS, methods.toSorted());\n    deepStrictEqual(httpCommon.methods, methods.toSorted());\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/c514e8f781b2acedb6a2b42208d8f8f4d8392f09/test/parallel/test-http-common.js\nexport const testHttpCommon = {\n  async test() {\n    const checkIsHttpToken = httpCommon._checkIsHttpToken;\n    const checkInvalidHeaderChar = httpCommon._checkInvalidHeaderChar;\n\n    // checkIsHttpToken\n    ok(checkIsHttpToken('t'));\n    ok(checkIsHttpToken('tt'));\n    ok(checkIsHttpToken('ttt'));\n    ok(checkIsHttpToken('tttt'));\n    ok(checkIsHttpToken('ttttt'));\n    ok(checkIsHttpToken('content-type'));\n    ok(checkIsHttpToken('etag'));\n\n    strictEqual(checkIsHttpToken(''), false);\n    strictEqual(checkIsHttpToken(' '), false);\n    strictEqual(checkIsHttpToken('あ'), false);\n    strictEqual(checkIsHttpToken('あa'), false);\n    strictEqual(checkIsHttpToken('aaaaあaaaa'), false);\n\n    // checkInvalidHeaderChar\n    ok(checkInvalidHeaderChar('あ'));\n    ok(checkInvalidHeaderChar('aaaaあaaaa'));\n\n    strictEqual(checkInvalidHeaderChar(''), false);\n    strictEqual(checkInvalidHeaderChar(1), false);\n    strictEqual(checkInvalidHeaderChar(' '), false);\n    strictEqual(checkInvalidHeaderChar(false), false);\n    strictEqual(checkInvalidHeaderChar('t'), false);\n    strictEqual(checkInvalidHeaderChar('tt'), false);\n    strictEqual(checkInvalidHeaderChar('ttt'), false);\n    strictEqual(checkInvalidHeaderChar('tttt'), false);\n    strictEqual(checkInvalidHeaderChar('ttttt'), false);\n  },\n};\n\n// Test is taken from test/parallel/test-http-request-invalid-method-error.js\nexport const testHttpRequestInvalidMethodError = {\n  async test() {\n    throws(() => http.request({ method: '\\0' }), {\n      code: 'ERR_INVALID_HTTP_TOKEN',\n      name: 'TypeError',\n      message: 'Method must be a valid HTTP token [\"\\u0000\"]',\n    });\n  },\n};\n\n// Test is taken from test/parallel/test-http-content-length.js\nexport const testHttpContentLength = {\n  async test(_ctrl, env) {\n    const expectedHeadersEndWithData = {\n      connection: 'keep-alive',\n      'content-length': String('hello world'.length),\n    };\n\n    const expectedHeadersEndNoData = {\n      connection: 'keep-alive',\n      'content-length': '0',\n    };\n\n    const { promise, resolve } = Promise.withResolvers();\n    let req;\n\n    req = http.request({\n      hostname: env.SIDECAR_HOSTNAME,\n      port: env.HELLO_WORLD_SERVER_PORT,\n      method: 'POST',\n      path: '/end-with-data',\n    });\n    req.removeHeader('Date');\n    req.end('hello world');\n    req.on('response', function (res) {\n      deepStrictEqual(res.headers, {\n        ...expectedHeadersEndWithData,\n        'keep-alive': 'timeout=1',\n      });\n      res.resume();\n    });\n\n    req = http.request({\n      hostname: env.SIDECAR_HOSTNAME,\n      port: env.HELLO_WORLD_SERVER_PORT,\n      method: 'POST',\n      path: '/empty',\n    });\n    req.removeHeader('Date');\n    req.end();\n    req.on('response', function (res) {\n      deepStrictEqual(res.headers, {\n        ...expectedHeadersEndNoData,\n        'keep-alive': 'timeout=1',\n      });\n      res.resume();\n      resolve();\n    });\n    await promise;\n  },\n};\n\n// Test is taken from test/parallel/test-http-contentLength0.js\nexport const testHttpContentLength0 = {\n  async test(_ctrl, env) {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const request = http.request(\n      {\n        hostname: env.SIDECAR_HOSTNAME,\n        port: env.HELLO_WORLD_SERVER_PORT,\n        method: 'POST',\n        path: '/content-length0',\n      },\n      (response) => {\n        response.on('error', reject);\n        response.resume();\n        response.on('end', resolve);\n      }\n    );\n    request.on('error', reject);\n    request.end();\n    await promise;\n  },\n};\n\n// Test is taken from test/parallel/test-http-dont-set-default-headers-with-set-header.js\nexport const testHttpDontSetDefaultHeadersWithSetHeader = {\n  async test(_ctrl, env) {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const req = http.request({\n      method: 'POST',\n      hostname: env.SIDECAR_HOSTNAME,\n      port: env.HEADER_VALIDATION_SERVER_PORT,\n      setDefaultHeaders: false,\n      path: '/test-1',\n    });\n\n    req.setHeader('test', 'value');\n    req.setHeader(\n      'HOST',\n      `${env.SIDECAR_HOSTNAME}:${env.HEADER_VALIDATION_SERVER_PORT}`\n    );\n    req.setHeader('foo', ['bar', 'baz']);\n    req.setHeader('connection', 'close');\n    req.on('response', resolve);\n    req.on('error', reject);\n    strictEqual(req.headersSent, false);\n    req.end();\n    await promise;\n    strictEqual(req.headersSent, true);\n  },\n};\n\n// Test is taken from test/parallel/test-http-dont-set-default-headers-with-setHost.js\nexport const testHttpDontSetDefaultHeadersWithSetHost = {\n  async test(_ctrl, env) {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    http\n      .request({\n        method: 'POST',\n        hostname: env.SIDECAR_HOSTNAME,\n        port: env.HEADER_VALIDATION_SERVER_PORT,\n        setDefaultHeaders: false,\n        setHost: true,\n        path: '/test-2',\n      })\n      .on('error', reject)\n      .on('response', resolve)\n      .end();\n    await promise;\n  },\n};\n\n// Test is taken from test/parallel/test-http-request-end-twice.js\nexport const testHttpRequestEndTwice = {\n  async test(_ctrl, env) {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const req = http\n      .get(\n        {\n          hostname: env.SIDECAR_HOSTNAME,\n          port: env.HEADER_VALIDATION_SERVER_PORT,\n        },\n        function (res) {\n          res.on('error', reject).on('end', function () {\n            strictEqual(req.end(), req);\n            resolve();\n          });\n          res.resume();\n        }\n      )\n      .on('error', reject);\n    await promise;\n  },\n};\n\n// Test is taken from test/parallel/test-http-set-timeout.js\nexport const testHttpSetTimeout = {\n  async test(_ctrl, env) {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const request = http.get({\n      hostname: env.SIDECAR_HOSTNAME,\n      port: env.TIMEOUT_SERVER_PORT,\n      path: '/',\n    });\n    request.setTimeout(100);\n    request.on('error', reject);\n    request.on('timeout', resolve);\n    request.end();\n    await promise;\n  },\n};\n\nexport const httpRedirectsAreNotFollowed = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n    const req = http.request(\n      {\n        port: 80,\n        method: 'GET',\n        protocol: 'http:',\n        hostname: 'cloudflare.com',\n        path: '/',\n      },\n      (res) => {\n        strictEqual(res.statusCode, 301);\n        resolve();\n      }\n    );\n    req.end();\n    await promise;\n  },\n};\n\nexport const testExports = {\n  async test() {\n    strictEqual(typeof http.WebSocket, 'function');\n    strictEqual(typeof http.CloseEvent, 'function');\n    strictEqual(typeof http.MessageEvent, 'function');\n    strictEqual(typeof http._connectionListener, 'function');\n    strictEqual(typeof http.setMaxIdleHTTPParsers, 'function');\n  },\n};\n\n// The following tests does not make sense for workerd\n//\n// - [ ] test/parallel/test-http-parser-bad-ref.js\n// - [ ] test/parallel/test-http-parser-finish-error.js\n// - [ ] test/parallel/test-http-parser-free.js\n// - [ ] test/parallel/test-http-parser-freed-before-upgrade.js\n// - [ ] test/parallel/test-http-parser-lazy-loaded.js\n// - [ ] test/parallel/test-http-parser-memory-retention.js\n// - [ ] test/parallel/test-http-parser-multiple-execute.js\n// - [ ] test/parallel/test-http-parser-timeout-reset.js\n// - [ ] test/parallel/test-http-parser.js\n"
  },
  {
    "path": "src/workerd/api/node/tests/http-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"http-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"http-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_http_modules\"],\n        bindings = [\n          (name = \"SIDECAR_HOSTNAME\", fromEnvironment = \"SIDECAR_HOSTNAME\"),\n          (name = \"PONG_SERVER_PORT\", fromEnvironment = \"PONG_SERVER_PORT\"),\n          (name = \"ASD_SERVER_PORT\", fromEnvironment = \"ASD_SERVER_PORT\"),\n          (name = \"TIMEOUT_SERVER_PORT\", fromEnvironment = \"TIMEOUT_SERVER_PORT\"),\n          (name = \"HELLO_WORLD_SERVER_PORT\", fromEnvironment = \"HELLO_WORLD_SERVER_PORT\"),\n          (name = \"HEADER_VALIDATION_SERVER_PORT\", fromEnvironment = \"HEADER_VALIDATION_SERVER_PORT\"),\n        ],\n      )\n    ),\n    ( name = \"internet\", network = ( allow = [\"private\", \"public\"] ) ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/http-outgoing-nodejs-server.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// This is a sidecar that runs alongside the http-client-nodejs-test.* tests.\n// It is executed using the appropriate Node.js version defined in build/deps/nodejs.MODULE.bazel.\nconst http = require('node:http');\nconst assert = require('node:assert/strict');\n\nfunction reportPort(server) {\n  const address = server.address();\n  console.info(`Listening on ${address.address}:${address.port}`);\n}\n\nconst finishWritableServer = http.createServer((req, res) => {\n  assert.strictEqual(res.writable, true);\n  assert.strictEqual(res.finished, false);\n  assert.strictEqual(res.writableEnded, false);\n  res.end();\n\n  // res.writable is set to false after it has finished sending\n  // Ref: https://github.com/nodejs/node/issues/15029\n  assert.strictEqual(res.writable, true);\n  assert.strictEqual(res.finished, true);\n  assert.strictEqual(res.writableEnded, true);\n});\nfinishWritableServer.listen(\n  process.env.FINISH_WRITABLE_PORT,\n  process.env.SIDECAR_HOSTNAME,\n  () => reportPort(finishWritableServer)\n);\n\nconst writableFinishedServer = http.createServer((req, res) => {\n  assert.strictEqual(res.writableFinished, false);\n  res.on('finish', () => {\n    assert.strictEqual(res.writableFinished, true);\n  });\n  res.end();\n});\nwritableFinishedServer.listen(\n  process.env.WRITABLE_FINISHED_PORT,\n  process.env.SIDECAR_HOSTNAME,\n  () => reportPort(writableFinishedServer)\n);\n\nconst propertiesServer = http.createServer((req, res) => {\n  res.end();\n});\npropertiesServer.listen(\n  process.env.PROPERTIES_PORT,\n  process.env.SIDECAR_HOSTNAME,\n  () => reportPort(propertiesServer)\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/http-outgoing-nodejs-test.js",
    "content": "import http from 'node:http';\nimport { throws, strictEqual, ok, deepStrictEqual } from 'node:assert';\nimport { httpServerHandler } from 'cloudflare:node';\nimport { mock } from 'node:test';\nimport stream from 'node:stream';\n\nexport const checkPortsSetCorrectly = {\n  test(_ctrl, env) {\n    const keys = [\n      'SIDECAR_HOSTNAME',\n      'FINISH_WRITABLE_PORT',\n      'WRITABLE_FINISHED_PORT',\n      'PROPERTIES_PORT',\n    ];\n    for (const key of keys) {\n      strictEqual(typeof env[key], 'string');\n      ok(env[key].length > 0);\n    }\n  },\n};\n\n// Test is taken from test/parallel/test-http-outgoing-destroy.js\nexport const testHttpOutgoingDestroy = {\n  async test() {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const msg = new http.OutgoingMessage();\n    strictEqual(msg.destroyed, false);\n    msg.destroy();\n    strictEqual(msg.destroyed, true);\n    msg.write('asd', (err) => {\n      strictEqual(err.code, 'ERR_STREAM_DESTROYED');\n      resolve();\n    });\n    msg.on('error', reject);\n    await promise;\n  },\n};\n\n// Tests are taken from test/parallel/test-http-outgoing-finish-writable.js\nexport const testHttpOutgoingFinishWritable = {\n  async test(_ctrl, env) {\n    const clientRequest = http.request({\n      hostname: env.SIDECAR_HOSTNAME,\n      port: env.FINISH_WRITABLE_PORT,\n      method: 'GET',\n      path: '/',\n    });\n\n    strictEqual(clientRequest.writable, true);\n    clientRequest.end();\n\n    // Writable is still true when close\n    // THIS IS LEGACY, we cannot change it\n    // unless we break error detection\n    strictEqual(clientRequest.writable, true);\n  },\n};\n\n// Tests are taken from test/parallel/test-http-outgoing-properties.js\nexport const testHttpOutgoingProperties = {\n  async test(_ctrl, env) {\n    {\n      const msg = new http.OutgoingMessage();\n      msg._implicitHeader = function () {};\n      strictEqual(msg.writableLength, 0);\n      // TODO(soon): Implement _write() method of OutgoingMessage\n      // msg.write('asd');\n      // strictEqual(msg.writableLength, 3);\n    }\n\n    {\n      const req = http.request({\n        hostname: env.SIDECAR_HOSTNAME,\n        port: env.PROPERTIES_PORT,\n        method: 'GET',\n        path: '/',\n      });\n\n      strictEqual(req.path, '/');\n      strictEqual(req.method, 'GET');\n      strictEqual(req.host, env.SIDECAR_HOSTNAME);\n      strictEqual(req.protocol, 'http:');\n      req.end();\n    }\n  },\n};\n\n// This is a modified test which is taken from\n// https://github.com/nodejs/node/blob/462c74181d8e15e74bc5a25d55290d93bd7edf65/test/parallel/test-http-outgoing-proto.js#L47\nexport const testHttpOutgoingProto = {\n  async test() {\n    throws(\n      () => {\n        const outgoingMessage = new http.OutgoingMessage();\n        outgoingMessage.setHeader();\n      },\n      {\n        code: 'ERR_INVALID_HTTP_TOKEN',\n        name: 'TypeError',\n        message: 'Header name must be a valid HTTP token [\"undefined\"]',\n      }\n    );\n\n    throws(\n      () => {\n        const outgoingMessage = new http.OutgoingMessage();\n        outgoingMessage.setHeader('test');\n      },\n      {\n        code: 'ERR_HTTP_INVALID_HEADER_VALUE',\n        name: 'TypeError',\n        message: 'Invalid value \"undefined\" for header \"test\"',\n      }\n    );\n\n    throws(\n      () => {\n        const outgoingMessage = new http.OutgoingMessage();\n        outgoingMessage.setHeader(404);\n      },\n      {\n        code: 'ERR_INVALID_HTTP_TOKEN',\n        name: 'TypeError',\n        message: 'Header name must be a valid HTTP token [\"404\"]',\n      }\n    );\n\n    throws(\n      () => {\n        const outgoingMessage = new http.OutgoingMessage();\n        outgoingMessage.setHeader('200', 'あ');\n      },\n      {\n        code: 'ERR_INVALID_CHAR',\n        name: 'TypeError',\n        message: 'Invalid character in header content [\"200\"]',\n      }\n    );\n\n    {\n      const outgoingMessage = new http.OutgoingMessage();\n      strictEqual(outgoingMessage.destroyed, false);\n      outgoingMessage.destroy();\n      strictEqual(outgoingMessage.destroyed, true);\n    }\n  },\n};\n\n// Tests are taken from test/parallel/test-http-outgoing-renderHeaders.js\nexport const testHttpOutgoingRenderHeaders = {\n  async test() {\n    const outgoingMessage = new http.OutgoingMessage();\n    outgoingMessage._header = {};\n    throws(() => outgoingMessage._renderHeaders(), {\n      code: 'ERR_HTTP_HEADERS_SENT',\n      name: 'Error',\n      message: 'Cannot render headers after they are sent to the client',\n    });\n  },\n};\n\n// Tests are taken from test/parallel/test-http-outgoing-writableFinished.js\nexport const testHttpOutgoingWritableFinished = {\n  async test(_ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    const clientRequest = http.request({\n      hostname: env.SIDECAR_HOSTNAME,\n      port: env.WRITABLE_FINISHED_PORT,\n      method: 'GET',\n      path: '/',\n    });\n\n    strictEqual(clientRequest.writableFinished, false);\n    clientRequest\n      .on('finish', () => {\n        strictEqual(clientRequest.writableFinished, true);\n        resolve();\n      })\n      .end();\n    strictEqual(clientRequest.writableFinished, false);\n    await promise;\n  },\n};\n\n// Test is taken from test/parallel/test-http-outgoing-destroyed.js\nexport const testHttpOutgoingDestroyed = {\n  async test(_ctrl, env) {\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      const errorFn = mock.fn();\n      await using server = http.createServer((req, res) => {\n        strictEqual(res.closed, false);\n        req.pipe(res);\n        res.on('error', errorFn);\n        res.on('close', () => {\n          strictEqual(res.closed, true);\n          res.end('asd');\n          process.nextTick(resolve);\n        });\n      });\n\n      server.listen(8080);\n\n      await env.SERVICE.fetch('https://cloudflare.com', {\n        method: 'PUT',\n        body: 'asd',\n      });\n      await promise;\n      strictEqual(errorFn.mock.callCount(), 0);\n    }\n\n    {\n      await using server = http.createServer((req, res) => {\n        strictEqual(res.closed, false);\n        res.end();\n        res.destroy();\n        // Make sure not to emit 'error' after .destroy().\n        res.end('asd');\n        strictEqual(res.errored, undefined);\n      });\n\n      server.listen(8080);\n\n      await env.SERVICE.fetch('https://cloudflare.com');\n    }\n  },\n};\n\n// Test is taken from test/parallel/test-http-outgoing-end-multiple.js\nexport const testHttpOutgoingEndMultiple = {\n  async test(_ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    const onWriteAfterEndError = mock.fn((err) => {\n      strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END');\n    });\n    const resEndFn = mock.fn();\n    await using server = http.createServer(function (req, res) {\n      res.end('testing ended state', resEndFn);\n      strictEqual(res.writableCorked, 0);\n      res.end((err) => {\n        strictEqual(err.code, 'ERR_STREAM_ALREADY_FINISHED');\n      });\n      strictEqual(res.writableCorked, 0);\n      res.end('end', onWriteAfterEndError);\n      strictEqual(res.writableCorked, 0);\n      res.on('error', onWriteAfterEndError);\n      res.on('finish', () => {\n        res.end((err) => {\n          strictEqual(err.code, 'ERR_STREAM_ALREADY_FINISHED');\n          resolve();\n        });\n      });\n    });\n\n    server.listen(8080);\n\n    await env.SERVICE.fetch('https://cloudflare.com/');\n    await promise;\n\n    strictEqual(onWriteAfterEndError.mock.callCount(), 2);\n    strictEqual(resEndFn.mock.callCount(), 1);\n  },\n};\n\n// Test is taken from test/parallel/test-http-outgoing-write-types.js\nexport const testHttpOutgoingWriteTypes = {\n  async test(_ctrl, env) {\n    await using httpServer = http.createServer(function (req, res) {\n      throws(\n        () => {\n          res.write(['Throws.']);\n        },\n        { code: 'ERR_INVALID_ARG_TYPE' }\n      );\n      // should not throw\n      res.write('1a2b3c');\n      // should not throw\n      res.write(new Uint8Array(1024));\n      // should not throw\n      res.write(Buffer.from('1'.repeat(1024)));\n      res.end();\n    });\n\n    httpServer.listen(8080);\n\n    await env.SERVICE.fetch('https://cloudflare.com');\n  },\n};\n\n// Test is taken from test/parallel/test-http-outgoing-buffer.js\nexport const testHttpOutgoingBuffer = {\n  async test() {\n    const msg = new http.OutgoingMessage();\n    msg._implicitHeader = function () {};\n\n    // Writes should be buffered until highwatermark\n    // even when no socket is assigned.\n\n    strictEqual(msg.write('asd'), true);\n    while (msg.write('asd'));\n    const highwatermark = msg.writableHighWaterMark || 65535;\n    ok(msg.outputSize >= highwatermark);\n  },\n};\n\n// Test is taken from test/parallel/test-http-outgoing-finished.js\nexport const testHttpOutgoingFinished = {\n  async test(_ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    await using server = http.createServer(function (req, res) {\n      let closed = false;\n      res\n        .on('close', () => {\n          closed = true;\n          stream.finished(res, resolve);\n        })\n        .end();\n      stream.finished(res, () => {\n        strictEqual(closed, true);\n      });\n    });\n\n    server.listen(8080);\n\n    await env.SERVICE.fetch('https://cloudflare.com');\n  },\n};\n\n// Test is taken from test/parallel/test-http-outgoing-end-types.js\nexport const testHttpOutgoingEndTypes = {\n  async test(_ctrl, env) {\n    await using httpServer = http.createServer(function (req, res) {\n      throws(\n        () => {\n          res.end(['Throws.']);\n        },\n        {\n          code: 'ERR_INVALID_ARG_TYPE',\n        }\n      );\n      res.end();\n    });\n\n    httpServer.listen(8080);\n\n    await env.SERVICE.fetch('https://cloudflare.com');\n  },\n};\n\n// Test is taken from test/parallel/test-http-outgoing-first-chunk-singlebyte-encoding.js\nexport const testHttpOutgoingFirstChunkSingleByteEncoding = {\n  async test(_ctrl, env) {\n    // TODO(soon): Add utf-16 to this list when it's supported.\n    for (const enc of ['utf8', 'latin1', 'UTF-8']) {\n      await using server = http.createServer((req, res) => {\n        res.setHeader('content-type', `text/plain; charset=${enc}`);\n        res.write('helloworld', enc);\n        res.end();\n      });\n\n      server.listen(8080);\n\n      const res = await env.SERVICE.fetch('http://cloudflare.com', {\n        headers: {\n          'content-type': `text/plain; charset=${enc}`,\n        },\n      });\n      strictEqual(res.status, 200);\n      strictEqual(\n        res.headers.get('content-type'),\n        `text/plain; charset=${enc}`\n      );\n      const buf = await res.arrayBuffer();\n      strictEqual(Buffer.from(buf, enc).toString(), 'helloworld');\n    }\n  },\n};\n\n// Test client request lines don't cause parsing errors\nexport const testHttpOutgoingClientRequestLine = {\n  async test() {\n    const msg = new http.OutgoingMessage();\n\n    // Client request line (POST /path HTTP/1.1) should not throw parsing errors\n    msg._header =\n      'POST /w/?token=BQYAAABQFGo%2b___snip___t90WZ%2bjnA%3d HTTP/1.1\\r\\n\\r\\n';\n\n    const result = msg._send('test data');\n    strictEqual(typeof result, 'boolean');\n  },\n};\n\n// Test server response lines emit _headersSent event\nexport const testHttpOutgoingServerResponseLine = {\n  async test() {\n    const msg = new http.OutgoingMessage();\n    let headersSentEvent = null;\n\n    msg.on('_headersSent', (event) => {\n      headersSentEvent = event;\n    });\n\n    // Server response status line should be parsed and emit _headersSent event\n    msg._header = 'HTTP/1.1 200 OK\\r\\nContent-Type: application/json\\r\\n\\r\\n';\n\n    const result = msg._send('test data');\n\n    strictEqual(typeof result, 'boolean');\n    ok(headersSentEvent, '_headersSent event should be emitted');\n    strictEqual(headersSentEvent.statusCode, 200);\n    strictEqual(headersSentEvent.statusMessage, 'OK');\n    strictEqual(\n      headersSentEvent.headers.get('content-type'),\n      'application/json'\n    );\n  },\n};\n\n// Test different HTTP versions work\nexport const testHttpOutgoingDifferentVersions = {\n  async test() {\n    for (const version of ['HTTP/1.0', 'HTTP/1.1', 'HTTP/2.0']) {\n      const msg = new http.OutgoingMessage();\n      let headersSentEvent = null;\n      msg.on('_headersSent', (event) => {\n        headersSentEvent = event;\n      });\n      msg._header = `${version} 404 Not Found\\r\\n\\r\\n`;\n      msg._send('test');\n      ok(headersSentEvent, `${version} should work`);\n      strictEqual(headersSentEvent.statusCode, 404);\n      strictEqual(headersSentEvent.statusMessage, 'Not Found');\n    }\n  },\n};\n\nexport default httpServerHandler({ port: 8080 });\n\n// Relevant Node.js tests\n//\n// - [x] test/parallel/test-http-outgoing-buffer.js\n// - [x] test/parallel/test-http-outgoing-destroy.js\n// - [x] test/parallel/test-http-outgoing-destroyed.js\n// - [x] test/parallel/test-http-outgoing-end-multiple.js\n// - [x] test/parallel/test-http-outgoing-end-types.js\n// - [x] test/parallel/test-http-outgoing-first-chunk-singlebyte-encoding.js\n// - [x] test/parallel/test-http-outgoing-finished.js\n// - [x] test/parallel/test-http-outgoing-finish-writable.js\n// - [x] test/parallel/test-http-outgoing-properties.js\n// - [x] test/parallel/test-http-outgoing-proto.js\n// - [x] test/parallel/test-http-outgoing-renderHeaders.js\n// - [x] test/parallel/test-http-outgoing-writableFinished.js\n// - [x] test/parallel/test-http-outgoing-write-types.js\n\n// The following tests is not relevant to us:\n//\n// - [ ] test/parallel/test-http-outgoing-end-cork.js\n// - [ ] test/parallel/test-http-outgoing-finish.js\n// - [ ] test/parallel/test-http-outgoing-message-capture-rejection.js\n// - [ ] test/parallel/test-http-outgoing-message-inheritance.js\n// - [ ] test/parallel/test-http-outgoing-message-write-callback.js\n// - [ ] test/parallel/test-http-outgoing-settimeout.js\n"
  },
  {
    "path": "src/workerd/api/node/tests/http-outgoing-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"http-outgoing-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"http-outgoing-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_http_modules\", \"enable_nodejs_http_server_modules\", \"streams_enable_constructors\"],\n        bindings = [\n          (name = \"SIDECAR_HOSTNAME\", fromEnvironment = \"SIDECAR_HOSTNAME\"),\n          (name = \"FINISH_WRITABLE_PORT\", fromEnvironment = \"FINISH_WRITABLE_PORT\"),\n          (name = \"WRITABLE_FINISHED_PORT\", fromEnvironment = \"WRITABLE_FINISHED_PORT\"),\n          (name = \"PROPERTIES_PORT\", fromEnvironment = \"PROPERTIES_PORT\"),\n          (name = \"SERVICE\", service = \"http-outgoing-nodejs-test\"),\n        ],\n      )\n    ),\n    ( name = \"internet\", network = ( allow = [\"private\"] ) ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/http-server-nodejs-global-test.js",
    "content": "import http from 'node:http';\nimport { strictEqual } from 'node:assert';\nimport { httpServerHandler } from 'cloudflare:node';\n\nconst server = http.createServer((req, res) => {\n  res.end('hello world');\n});\n\n// The server can be started by passing it to the httpServerHandler\n// which will call the server's listen method if it hasn't already\n// been called.\nexport default httpServerHandler(server);\nstrictEqual(typeof server.address().port, 'number');\n\nexport const checkItWorks = {\n  async test(_ctrl, env) {\n    const resp = await env.SUBREQUEST.fetch('http://example.com');\n    strictEqual(await resp.text(), 'hello world');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/http-server-nodejs-global-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"http-server-nodejs-global-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"http-server-nodejs-global-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_http_modules\", \"enable_nodejs_http_server_modules\", \"streams_enable_constructors\"],\n        bindings = [\n          ( name = \"SUBREQUEST\", service = \"http-server-nodejs-global-test\" ),\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/http-server-nodejs-server.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// This is a sidecar that runs alongside the http-client-nodejs-test.* tests.\n// It is executed using the appropriate Node.js version defined in build/deps/nodejs.MODULE.bazel.\nconst http = require('node:http');\n\nfunction reportPort(server) {\n  console.info(`Listening on port ${server.address().port}`);\n}\n\nconst pongServer = http.createServer((req, res) => {\n  req.resume();\n  req.on('end', () => {\n    res.writeHead(200);\n    res.end('pong');\n  });\n});\npongServer.listen(process.env.PONG_SERVER_PORT, () => reportPort(pongServer));\n"
  },
  {
    "path": "src/workerd/api/node/tests/http-server-nodejs-test.js",
    "content": "import http from 'node:http';\nimport { WorkerEntrypoint } from 'cloudflare:workers';\nimport { strictEqual, ok, throws, notStrictEqual, rejects } from 'node:assert';\nimport { httpServerHandler, handleAsNodeRequest } from 'cloudflare:node';\nimport { mock } from 'node:test';\nimport url from 'node:url';\nimport qs from 'node:querystring';\n\nexport const checkPortsSetCorrectly = {\n  test(_ctrl, env) {\n    const keys = ['PONG_SERVER_PORT'];\n    for (const key of keys) {\n      strictEqual(typeof env[key], 'string');\n      ok(env[key].length > 0);\n    }\n  },\n};\n\nexport class GlobalService extends WorkerEntrypoint {\n  async fetch(request) {\n    await rejects(handleAsNodeRequest({}, request), {\n      message: /Failed to determine port for server/,\n    });\n    await rejects(handleAsNodeRequest(1234, request), {\n      message: /^Http server with port 1234 not found/,\n    });\n    return await handleAsNodeRequest({ port: 9090 }, request);\n  }\n}\n\nexport const testHttpServerHandler = {\n  test() {\n    throws(() => httpServerHandler(null), {\n      message: /Server descriptor cannot be null or undefined/,\n    });\n\n    throws(() => httpServerHandler({}), {\n      message: /Failed to determine port for server/,\n    });\n  },\n};\n\nconst globalServer = http.createServer((req, res) => {\n  res.writeHead(200);\n  res.end('Hello, World!');\n});\n\nglobalServer.listen(9090);\n\nexport const testGlobalHttpServe = {\n  async test(_ctrl, env) {\n    strictEqual(globalServer.listening, true);\n    strictEqual(globalServer.address().port, 9090);\n\n    const res = await env.GLOBAL_SERVICE.fetch('https://cloudflare.com');\n    strictEqual(res.status, 200);\n    strictEqual(await res.text(), 'Hello, World!');\n\n    globalServer.close();\n  },\n};\n\n// Test is taken from test/parallel/test-http-server-async-dispose.js\nexport const testHttpServerAsyncDispose = {\n  async test() {\n    const server = http.createServer();\n\n    server.listen(8080);\n    ok(server.listening);\n    const closeFn = mock.fn();\n    server.on('close', closeFn);\n    await server[Symbol.asyncDispose]();\n    ok(!server.listening);\n    strictEqual(closeFn.mock.callCount(), 1);\n  },\n};\n\n// Test is taken from test/parallel/test-http-server-incomingmessage-destroy.js\nexport const testHttpServerIncomingMessageDestroy = {\n  async test(_ctrl, env) {\n    const onErrorFn = mock.fn();\n    await using server = http.createServer((req, res) => {\n      const path = req.url;\n\n      ok('cloudflare' in req);\n      ok('cf' in req.cloudflare);\n\n      if (path === '/destroy-with-error') {\n        req.on('error', (err) => {\n          res.statusCode = 400;\n          res.end('Request destroyed: ' + err.message);\n        });\n        req.destroy(new Error('Destroy test'));\n      } else if (path === '/destroy-without-error') {\n        req.once('error', onErrorFn);\n        req.on('close', () => {\n          res.statusCode = 200;\n          res.end('Request destroyed without error');\n        });\n        req.destroy();\n      } else if (path === '/response-destroy-with-error') {\n        res.destroy(new Error('Response destroy test'));\n      }\n    });\n\n    server.listen(8080);\n\n    {\n      const res = await env.SERVICE.fetch(\n        'https://cloudflare.com/destroy-with-error'\n      );\n      strictEqual(res.status, 400);\n      strictEqual(await res.text(), 'Request destroyed: Destroy test');\n    }\n\n    {\n      const res = await env.SERVICE.fetch(\n        'https://cloudflare.com/destroy-without-error'\n      );\n      strictEqual(res.status, 200);\n      strictEqual(await res.text(), 'Request destroyed without error');\n      strictEqual(onErrorFn.mock.callCount(), 0);\n    }\n  },\n};\n\n// Test is taken from test/parallel/test-http-server-method.query.js\nexport const testHttpServerMethodQuery = {\n  async test(_ctrl, env) {\n    await using server = http.createServer((req, res) => {\n      strictEqual(req.method, 'QUERY');\n      res.end('OK');\n    });\n    server.listen(8080);\n\n    const res = await env.SERVICE.fetch('https://cloudflare.com', {\n      method: 'QUERY',\n    });\n    strictEqual(res.status, 200);\n    strictEqual(await res.text(), 'OK');\n  },\n};\n\n// Tests is taken from test/parallel/test-http-server-multiheaders.js\nexport const testHttpServerMultiHeaders = {\n  async test(_ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    const server = http.createServer(function (req, res) {\n      strictEqual(req.headers.accept, 'abc, def, ghijklmnopqrst');\n      strictEqual(req.headers.host, 'foo');\n      strictEqual(req.headers['www-authenticate'], 'foo, bar, baz');\n      strictEqual(req.headers['proxy-authenticate'], 'foo, bar, baz');\n      strictEqual(req.headers['x-foo'], 'bingo');\n      strictEqual(req.headers['x-bar'], 'banjo, bango');\n      strictEqual(req.headers['sec-websocket-protocol'], 'chat, share');\n      strictEqual(\n        req.headers['sec-websocket-extensions'],\n        'foo; 1, bar; 2, baz'\n      );\n      strictEqual(req.headers.constructor, 'foo, bar, baz');\n\n      res.writeHead(200, { 'Content-Type': 'text/plain' });\n      res.end('EOF');\n\n      server.close();\n    });\n\n    server.listen(8080, async function () {\n      const res = await env.SERVICE.fetch('https://cloudflare.com', {\n        headers: [\n          ['accept', 'abc'],\n          ['accept', 'def'],\n          ['Accept', 'ghijklmnopqrst'],\n          ['host', 'foo'],\n          ['Host', 'bar'],\n          ['hOst', 'baz'],\n          ['www-authenticate', 'foo'],\n          ['WWW-Authenticate', 'bar'],\n          ['WWW-AUTHENTICATE', 'baz'],\n          ['proxy-authenticate', 'foo'],\n          ['Proxy-Authenticate', 'bar'],\n          ['PROXY-AUTHENTICATE', 'baz'],\n          ['x-foo', 'bingo'],\n          ['x-bar', 'banjo'],\n          ['x-bar', 'bango'],\n          ['sec-websocket-protocol', 'chat'],\n          ['sec-websocket-protocol', 'share'],\n          ['sec-websocket-extensions', 'foo; 1'],\n          ['sec-websocket-extensions', 'bar; 2'],\n          ['sec-websocket-extensions', 'baz'],\n          ['constructor', 'foo'],\n          ['constructor', 'bar'],\n          ['constructor', 'baz'],\n        ],\n      });\n\n      strictEqual(res.status, 200);\n      strictEqual(res.headers.get('content-type'), 'text/plain');\n      strictEqual(await res.text(), 'EOF');\n\n      resolve();\n    });\n\n    await promise;\n  },\n};\n\n// Test is taken from test/parallel/test-http-server-multiheaders2.js\nexport const testHttpServerMultiHeaders2 = {\n  async test(_ctrl, env) {\n    // One difference between Node.js and Cloudflare workers is that Cookie is allowed\n    // to have multiple values but in Workers it is not supported.\n    const multipleAllowed = [\n      'Accept',\n      'Accept-Charset',\n      'Accept-Encoding',\n      'Accept-Language',\n      'Connection',\n      'DAV', // GH-2750\n      'Pragma', // GH-715\n      'Link', // GH-1187\n      'WWW-Authenticate', // GH-1083\n      'Proxy-Authenticate', // GH-4052\n      'Sec-Websocket-Extensions', // GH-2764\n      'Sec-Websocket-Protocol', // GH-2764\n      'Via', // GH-6660\n\n      // not a special case, just making sure it's parsed correctly\n      'X-Forwarded-For',\n\n      // Make sure that unspecified headers is treated as multiple\n      'Some-Random-Header',\n      'X-Some-Random-Header',\n    ];\n\n    const multipleForbidden = [\n      'Content-Type',\n      'User-Agent',\n      'Referer',\n      'Host',\n      'Authorization',\n      'Proxy-Authorization',\n      'If-Modified-Since',\n      'If-Unmodified-Since',\n      'From',\n      'Location',\n      'Max-Forwards',\n    ];\n\n    await using server = http.createServer(function (req, res) {\n      for (const header of multipleForbidden) {\n        const value = req.headers[header.toLowerCase()];\n        strictEqual(\n          value,\n          'foo',\n          `multiple forbidden header parsed incorrectly: ${header} with value: \"${value}\"`\n        );\n      }\n      for (const header of multipleAllowed) {\n        const sep = header.toLowerCase() === 'cookie' ? '; ' : ', ';\n        strictEqual(\n          req.headers[header.toLowerCase()],\n          `foo${sep}bar`,\n          `multiple allowed header parsed incorrectly: ${header}`\n        );\n      }\n\n      res.writeHead(200, { 'Content-Type': 'text/plain' });\n      res.end('EOF');\n    });\n\n    function makeHeader(value) {\n      return function (header) {\n        return [header, value];\n      };\n    }\n\n    const headers = []\n      .concat(multipleAllowed.map(makeHeader('foo')))\n      .concat(multipleForbidden.map(makeHeader('foo')))\n      .concat(multipleAllowed.map(makeHeader('bar')))\n      .concat(multipleForbidden.map(makeHeader('bar')));\n\n    server.listen(8080);\n\n    const res = await env.SERVICE.fetch('https://cloudflare.com/', {\n      headers,\n    });\n    strictEqual(res.status, 200);\n  },\n};\n\n// Test for RFC 7230 compliant header splitting with quoted strings and escaped characters\nexport const testHttpServerQuotedStringHeaders = {\n  async test(_ctrl, env) {\n    await using server = http.createServer((req, res) => {\n      // Basic quoted strings with commas\n      strictEqual(req.headers['content-type'], 'text/plain; f=\"a, b, c\"');\n      strictEqual(req.headers['authorization'], 'Bearer token=\"abc, def\"');\n      strictEqual(\n        req.headers['proxy-authorization'],\n        'Basic realm=\"test, realm\"'\n      );\n\n      // Escaped characters in quoted strings\n      strictEqual(\n        req.headers['user-agent'],\n        'Mozilla/5.0; comment=\"has \\\\\"quotes\\\\\" and, commas\"'\n      );\n\n      res.writeHead(200, { 'content-type': 'text/plain' });\n      res.end('ok');\n    });\n\n    server.listen(8080);\n    const res = await env.SERVICE.fetch('https://cloudflare.com', {\n      method: 'GET',\n      headers: [\n        // Basic quoted string tests\n        ['content-type', 'text/plain; f=\"a, b, c\"'],\n        ['content-type', 'text/foo; a=\"1, 2, 3\"'],\n        ['authorization', 'Bearer token=\"abc, def\"'],\n        ['authorization', 'Bearer token=\"ghi, jkl\"'],\n        ['proxy-authorization', 'Basic realm=\"test, realm\"'],\n        ['proxy-authorization', 'Basic realm=\"another, realm\"'],\n        // Escaped character tests\n        ['user-agent', 'Mozilla/5.0; comment=\"has \\\\\"quotes\\\\\" and, commas\"'],\n        ['user-agent', 'Chrome/100.0; info=\"version \\\\\"100\\\\\"\"'],\n      ],\n    });\n\n    strictEqual(res.status, 200);\n    strictEqual(await res.text(), 'ok');\n  },\n};\n\n// Test is taken from test/parallel/test-http-server-non-utf8-header.js\nexport const testHttpServerNonUtf8Header = {\n  async test(_ctrl, env) {\n    const nonUtf8Header = 'bår';\n    const nonUtf8ToLatin1 = Buffer.from(nonUtf8Header).toString('latin1');\n\n    {\n      await using server = http.createServer((req, res) => {\n        res.writeHead(200, [\n          'content-disposition',\n          Buffer.from(nonUtf8Header).toString('binary'),\n        ]);\n        res.end('hello');\n      });\n\n      server.listen(8080);\n      const res = await env.SERVICE.fetch('https://cloudflare.com', {\n        method: 'GET',\n      });\n      strictEqual(res.status, 200);\n      strictEqual(res.headers.get('content-disposition'), nonUtf8ToLatin1);\n    }\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      // Test multi-value header\n      await using server = http.createServer((req, res) => {\n        res.writeHead(200, [\n          'content-disposition',\n          [Buffer.from(nonUtf8Header).toString('binary')],\n        ]);\n        res.end('hello');\n      });\n\n      server.listen(8080);\n      const res = await env.SERVICE.fetch('https://cloudflare.com');\n      strictEqual(res.status, 200);\n      strictEqual(res.headers.get('content-disposition'), nonUtf8ToLatin1);\n    }\n\n    {\n      await using server = http.createServer((req, res) => {\n        res.writeHead(200, [\n          'Content-Length',\n          '5',\n          'content-disposition',\n          Buffer.from(nonUtf8Header).toString('binary'),\n        ]);\n        res.end('hello');\n      });\n\n      server.listen(8080);\n\n      const res = await env.SERVICE.fetch('https://cloudflare.com');\n      strictEqual(res.status, 200);\n      // The issue is that Content-Length causes different header encoding behavior\n      // We expect the raw bytes to be interpreted as UTF-8 by the fetch API\n      strictEqual(res.headers.get('content-disposition'), nonUtf8Header);\n    }\n  },\n};\n\n// Test is taken from test/parallel/test-http-server-options-incoming-message.js\nexport const testHttpServerOptionsIncomingMessage = {\n  async test(_ctrl, env) {\n    class MyIncomingMessage extends http.IncomingMessage {\n      getUserAgent() {\n        return this.headers['user-agent'] || 'unknown';\n      }\n    }\n\n    await using server = http.createServer(\n      {\n        IncomingMessage: MyIncomingMessage,\n      },\n      (req, res) => {\n        strictEqual(req.getUserAgent(), 'node-test');\n        res.statusCode = 200;\n        res.end();\n      }\n    );\n    server.listen(8080);\n\n    const res = await env.SERVICE.fetch('https://cloudflare.com', {\n      headers: { 'User-Agent': 'node-test' },\n    });\n    strictEqual(res.status, 200);\n  },\n};\n\n// Test is taken from test/parallel/test-http-server-options-server-response.js\nexport const testHttpServerOptionsServerResponse = {\n  async test(_ctrl, env) {\n    class MyServerResponse extends http.ServerResponse {\n      status(code) {\n        return this.writeHead(code, { 'Content-Type': 'text/plain' });\n      }\n    }\n\n    await using server = http.createServer(\n      {\n        ServerResponse: MyServerResponse,\n      },\n      (_req, res) => {\n        res.status(200);\n        res.end();\n      }\n    );\n    server.listen(8080);\n\n    const res = await env.SERVICE.fetch('https://cloudflare.com');\n    strictEqual(res.status, 200);\n    strictEqual(res.headers.get('content-type'), 'text/plain');\n  },\n};\n\n// Test is taken from test/parallel/test-http-server-timeouts-validation.js\nexport const testHttpServerTimeoutsValidation = {\n  async test() {\n    // This test validates that the HTTP server timeouts are properly validated and set.\n\n    {\n      const server = http.createServer();\n      strictEqual(server.headersTimeout, 60000);\n      strictEqual(server.requestTimeout, 300000);\n    }\n\n    {\n      const server = http.createServer({\n        headersTimeout: 10000,\n        requestTimeout: 20000,\n      });\n      strictEqual(server.headersTimeout, 10000);\n      strictEqual(server.requestTimeout, 20000);\n    }\n\n    {\n      const server = http.createServer({\n        headersTimeout: 10000,\n        requestTimeout: 10000,\n      });\n      strictEqual(server.headersTimeout, 10000);\n      strictEqual(server.requestTimeout, 10000);\n    }\n\n    {\n      const server = http.createServer({ headersTimeout: 10000 });\n      strictEqual(server.headersTimeout, 10000);\n      strictEqual(server.requestTimeout, 300000);\n    }\n\n    {\n      const server = http.createServer({ requestTimeout: 20000 });\n      strictEqual(server.headersTimeout, 20000);\n      strictEqual(server.requestTimeout, 20000);\n    }\n\n    {\n      const server = http.createServer({ requestTimeout: 100000 });\n      strictEqual(server.headersTimeout, 60000);\n      strictEqual(server.requestTimeout, 100000);\n    }\n\n    {\n      throws(\n        () =>\n          http.createServer({ headersTimeout: 10000, requestTimeout: 1000 }),\n        { code: 'ERR_OUT_OF_RANGE' }\n      );\n    }\n  },\n};\n\n// Test is taken from test/parallel/test-http-server-write-after-end.js\nexport const testHttpServerWriteAfterEnd = {\n  async test(_ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    await using server = http.createServer(handle);\n\n    function handle(_req, res) {\n      res.write('hello');\n      res.end();\n\n      queueMicrotask(() => {\n        res.write('world', (err) => {\n          strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END');\n          resolve();\n        });\n      });\n    }\n\n    server.listen(8080);\n    await env.SERVICE.fetch('https://cloudflare.com');\n    await promise;\n  },\n};\n\n// Test is taken from test/parallel/test-http-server-write-end-after-end.js\nexport const testHttpServerWriteEndAfterEnd = {\n  async test(_ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    const handle = mock.fn((req, res) => {\n      res.write('hello');\n      res.end();\n\n      queueMicrotask(() => {\n        res.end('world');\n        res.write('world', (err) => {\n          strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END');\n          resolve();\n        });\n      });\n    });\n    const server = http.createServer(handle);\n    server.listen(8080);\n\n    await env.SERVICE.fetch('https://cloudflare.com');\n    await promise;\n    strictEqual(handle.mock.callCount(), 1);\n    server.close();\n  },\n};\n\nexport const testHandleZeroNullUndefinedPortNumber = {\n  async test() {\n    // Test zero port number.\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      const server = http.createServer();\n      const listeningFn = mock.fn();\n      server.on('listening', listeningFn);\n      server.listen(0, () => {\n        ok(server.listening);\n        notStrictEqual(server.address().port, 0);\n        strictEqual(listeningFn.mock.callCount(), 1);\n        server.close();\n        resolve();\n      });\n      await promise;\n    }\n    // Test null/undefined port number.\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      const server = http.createServer();\n      const listeningFn = mock.fn();\n      server.on('listening', listeningFn);\n      server.listen(() => {\n        ok(server.listening);\n        notStrictEqual(server.address().port, 0);\n        strictEqual(listeningFn.mock.callCount(), 1);\n        server.close();\n        resolve();\n      });\n      await promise;\n    }\n  },\n};\n\nexport const testInvalidPorts = {\n  async test() {\n    const server = http.createServer();\n    for (const value of [NaN, Infinity, -1, 1.1, 9999999]) {\n      throws(() => server.listen(value), {\n        code: 'ERR_SOCKET_BAD_PORT',\n      });\n    }\n    strictEqual(server.listening, false);\n  },\n};\n\nexport const consumeRequestPayloadData = {\n  async test(_ctrl, env) {\n    await using server = http.createServer((req, res) => {\n      const path = req.url;\n\n      if (path === '/small') {\n        strictEqual(req.method, 'POST');\n        let data = '';\n        req.setEncoding('utf8');\n        req.on('data', (d) => (data += d));\n        req.on('end', () => {\n          strictEqual(data, 'hello world');\n          res.setHeaders(new Headers({ hello: 'world' }));\n          res.end(data + ' x2');\n        });\n      } else if (path === '/large-streaming') {\n        strictEqual(req.method, 'POST');\n        const dataEvents = [];\n        let totalBytes = 0;\n\n        req.on('data', (chunk) => {\n          dataEvents.push({\n            size: chunk.length,\n            firstByte: chunk[0],\n            lastByte: chunk[chunk.length - 1],\n          });\n          totalBytes += chunk.length;\n        });\n\n        req.on('end', () => {\n          res.writeHead(200, { 'Content-Type': 'application/json' });\n          res.end(\n            JSON.stringify({\n              totalEvents: dataEvents.length,\n              totalBytes,\n              firstEventSize: dataEvents[0]?.size,\n              lastEventSize: dataEvents[dataEvents.length - 1]?.size,\n              allSamePattern: dataEvents.every(\n                (e) => e.firstByte === e.lastByte\n              ),\n            })\n          );\n        });\n      }\n    });\n\n    server.listen(8080);\n\n    {\n      const res = await env.SERVICE.fetch('https://cloudflare.com/small', {\n        body: 'hello world',\n        method: 'POST',\n      });\n      strictEqual(res.status, 200);\n      strictEqual(await res.text(), 'hello world x2');\n      strictEqual(res.headers.get('hello'), 'world');\n    }\n\n    {\n      const largeData = Buffer.alloc(256 * 1024, 123); // 256KB of byte value 123\n      const res = await env.SERVICE.fetch(\n        'https://cloudflare.com/large-streaming',\n        {\n          body: largeData,\n          method: 'POST',\n        }\n      );\n      strictEqual(res.status, 200);\n\n      const result = JSON.parse(await res.text());\n      ok(\n        result.totalEvents > 1,\n        `Should have multiple data events, got ${result.totalEvents}`\n      );\n      strictEqual(result.totalBytes, 256 * 1024);\n      ok(\n        result.allSamePattern,\n        'All chunks should contain the same byte pattern'\n      );\n      ok(\n        result.firstEventSize > 0 && result.lastEventSize > 0,\n        'Events should have positive sizes'\n      );\n    }\n  },\n};\n\n// Test large streaming responses and various data types\nexport const testStreamingResponses = {\n  async test(_ctrl, env) {\n    await using server = http.createServer((req, res) => {\n      const path = req.url;\n\n      if (path === '/large') {\n        // Test 1: Large payload streaming\n        const CHUNK_SIZE = 1024 * 64; // 64KB\n        const NUM_CHUNKS = 10; // 640KB total\n        res.writeHead(200, { 'X-Test': 'large' });\n        for (let i = 0; i < NUM_CHUNKS; i++) {\n          res.write(Buffer.alloc(CHUNK_SIZE, i % 256));\n        }\n        res.end();\n      } else if (path === '/echo') {\n        // Test 2: Echo with backpressure\n        res.writeHead(200);\n        req.pipe(res);\n      } else if (path === '/mixed') {\n        // Test 3: Mixed data types and implicit headers\n        res.setHeader('X-Custom', 'mixed');\n        res.write('string|');\n        res.write(Buffer.from('buffer|'));\n        res.write(new Uint8Array([85, 56, 124])); // \"U8|\"\n        res.write(''); // Empty\n        res.write('utf8-ñ|', 'utf8');\n        setTimeout(() => res.end('async'), 10);\n      } else if (path === '/many') {\n        // Test 4: Many small writes\n        res.writeHead(200);\n        for (let i = 0; i < 100; i++) {\n          res.write(`${i}|`);\n        }\n        res.end('END');\n      }\n    });\n\n    server.listen(8080);\n\n    // Test 1: Large payload\n    const res1 = await env.SERVICE.fetch('https://cloudflare.com/large');\n    strictEqual(res1.status, 200);\n    const data1 = await res1.arrayBuffer();\n    strictEqual(data1.byteLength, 1024 * 64 * 10);\n\n    // Test 2: Echo\n    const testData = Buffer.alloc(1024 * 128, 42);\n    const res2 = await env.SERVICE.fetch('https://cloudflare.com/echo', {\n      method: 'POST',\n      body: testData,\n    });\n    const echo = Buffer.from(await res2.arrayBuffer());\n    ok(echo.equals(testData));\n\n    // Test 3: Mixed data types\n    const res3 = await env.SERVICE.fetch('https://cloudflare.com/mixed');\n    strictEqual(res3.status, 200);\n    strictEqual(res3.headers.get('X-Custom'), 'mixed');\n    strictEqual(await res3.text(), 'string|buffer|U8|utf8-ñ|async');\n\n    // Test 4: Many writes\n    const res4 = await env.SERVICE.fetch('https://cloudflare.com/many');\n    const text4 = await res4.text();\n    ok(text4.endsWith('98|99|END'));\n  },\n};\n\n// Test Content-Length enforcement\nexport const testContentLengthEnforcement = {\n  async test(_ctrl, env) {\n    await using server = http.createServer((req, res) => {\n      const path = req.url;\n\n      if (path === '/too-few') {\n        res.writeHead(200, { 'Content-Length': '20' });\n        res.write('0123456789'); // Only 10 bytes\n        res.end();\n      } else if (path === '/too-many') {\n        res.writeHead(200, { 'Content-Length': '10' });\n        res.write('0123456789');\n        res.write('0123456789'); // 20 bytes total\n        res.end();\n      } else if (path === '/exact') {\n        res.writeHead(200, { 'Content-Length': '15' });\n        res.write('Hello ');\n        res.write('World!!!');\n        res.end('!');\n      }\n    });\n\n    server.listen(8080);\n\n    // Test 1: Too few bytes\n    {\n      const res = await env.SERVICE.fetch('https://cloudflare.com/too-few');\n      strictEqual(res.status, 200);\n      strictEqual(await res.text(), '0123456789');\n    }\n\n    // Test 2: Too many bytes\n    {\n      const res = await env.SERVICE.fetch('https://cloudflare.com/too-many');\n      strictEqual(res.status, 200);\n      strictEqual(await res.text(), '0123456789');\n    }\n\n    // Test 3: Exact match\n    {\n      const res = await env.SERVICE.fetch('https://cloudflare.com/exact');\n      strictEqual(res.status, 200);\n      strictEqual(await res.text(), 'Hello World!!!!');\n    }\n  },\n};\n\nexport const testCorkUncorkBasic = {\n  async test(_ctrl, env) {\n    await using server = http.createServer((req, res) => {\n      res.writeHead(200, { 'Content-Type': 'text/plain' });\n\n      strictEqual(res.writableLength, 0);\n      res.cork();\n      strictEqual(res.writableLength, 0);\n      res.write('chunk1');\n      strictEqual(res.writableLength, 108);\n      res.write('chunk2');\n      strictEqual(res.writableLength, 114);\n      res.write('chunk3');\n      strictEqual(res.writableLength, 120);\n      res.uncork();\n      strictEqual(res.writableLength, 0);\n\n      res.end('final');\n    });\n\n    server.listen(8080);\n\n    const res = await env.SERVICE.fetch('https://cloudflare.com');\n    strictEqual(res.status, 200);\n    strictEqual(await res.text(), 'chunk1chunk2chunk3final');\n  },\n};\n\nexport const testBackpressureSignaling = {\n  async test(_ctrl, env) {\n    const events = [];\n\n    await using server = http.createServer((req, res) => {\n      res.writeHead(200, { 'Content-Type': 'application/octet-stream' });\n\n      let writeCount = 0;\n      let drainCount = 0;\n\n      res.on('drain', () => {\n        drainCount++;\n        events.push({ type: 'drain', count: drainCount });\n        continueWriting();\n      });\n\n      const continueWriting = () => {\n        while (writeCount < 50) {\n          const chunk = Buffer.alloc(1024 * 32, writeCount % 256);\n          const canContinue = res.write(chunk);\n\n          strictEqual(typeof canContinue, 'boolean');\n          events.push({\n            type: 'write',\n            canContinue,\n            writeCount,\n            writableLength: res.writableLength,\n          });\n\n          writeCount++;\n\n          if (!canContinue) {\n            events.push({ type: 'backpressure', writeCount });\n            return;\n          }\n        }\n\n        res.end();\n      };\n\n      continueWriting();\n    });\n\n    server.listen(8080);\n\n    const res = await env.SERVICE.fetch('https://cloudflare.com');\n    strictEqual(res.status, 200);\n    strictEqual((await res.arrayBuffer()).byteLength, 1638400);\n\n    const writeEvents = events.filter((e) => e.type === 'write');\n    const backpressureEvents = events.filter((e) => e.type === 'backpressure');\n    const drainEvents = events.filter((e) => e.type === 'drain');\n\n    strictEqual(writeEvents.length, 50);\n\n    writeEvents.forEach((e) => {\n      strictEqual(typeof e.canContinue, 'boolean');\n      strictEqual(typeof e.writableLength, 'number');\n      ok(e.writableLength >= 0);\n    });\n\n    if (backpressureEvents.length > 0) {\n      ok(\n        drainEvents.length > 0,\n        'Should emit drain events when backpressure occurs'\n      );\n\n      for (let i = 0; i < backpressureEvents.length; i++) {\n        ok(\n          drainEvents[i],\n          `Should have corresponding drain event for backpressure ${i}`\n        );\n      }\n    }\n  },\n};\n\n// Test is taken from test/parallel/test-http-server.js\nexport const testHttpServer = {\n  async test(_ctrl, env) {\n    const invalid_options = ['foo', 42, true, []];\n\n    for (const option of invalid_options) {\n      throws(\n        () => {\n          new http.Server(option);\n        },\n        {\n          code: 'ERR_INVALID_ARG_TYPE',\n        }\n      );\n    }\n\n    let request_number = 0;\n\n    await using server = http.createServer(function (req, res) {\n      res.id = request_number;\n      req.id = request_number++;\n\n      strictEqual(res.req, req);\n\n      if (req.id === 0) {\n        strictEqual(req.method, 'GET');\n        strictEqual(url.parse(req.url).pathname, '/hello');\n        strictEqual(qs.parse(url.parse(req.url).query).hello, 'world');\n        strictEqual(qs.parse(url.parse(req.url).query).foo, 'b==ar');\n      }\n\n      if (req.id === 1) {\n        strictEqual(req.method, 'POST');\n        strictEqual(url.parse(req.url).pathname, '/quit');\n      }\n\n      if (req.id === 2) {\n        strictEqual(req.headers['x-x'], 'foo');\n      }\n\n      if (req.id === 3) {\n        strictEqual(req.headers['x-x'], 'bar');\n        this.close();\n      }\n\n      setTimeout(function () {\n        res.writeHead(200, { 'Content-Type': 'text/plain' });\n        res.write(url.parse(req.url).pathname);\n        res.end();\n      }, 1);\n    });\n    server.listen(8080);\n\n    server.httpAllowHalfOpen = true;\n\n    const hello = await env.SERVICE.fetch(\n      'https://example.com/hello?hello=world&foo=b==ar'\n    );\n    strictEqual(hello.status, 200);\n    strictEqual(await hello.text(), '/hello');\n\n    const quit = await env.SERVICE.fetch('https://example.com/quit', {\n      method: 'POST',\n    });\n    strictEqual(quit.status, 200);\n    strictEqual(await quit.text(), '/quit');\n\n    const xxFoo = await env.SERVICE.fetch('https://example.com/', {\n      method: 'POST',\n      headers: {\n        'x-x': 'foo',\n      },\n    });\n    strictEqual(xxFoo.status, 200);\n    strictEqual(await xxFoo.text(), '/');\n\n    const xxBar = await env.SERVICE.fetch('https://example.com/', {\n      method: 'POST',\n      headers: {\n        'x-x': 'bar',\n      },\n    });\n    strictEqual(xxBar.status, 200);\n    strictEqual(await xxBar.text(), '/');\n\n    strictEqual(request_number, 4);\n  },\n};\n\n// Test multiple pipe destinations (Node.js feature that web streams don't support)\nexport const testMultiplePipeDestinations = {\n  async test(_ctrl, env) {\n    const { Writable } = await import('node:stream');\n\n    await using server = http.createServer((req, res) => {\n      const path = req.url;\n\n      if (path === '/multipipe') {\n        res.writeHead(200, { 'Content-Type': 'application/json' });\n\n        // Create multiple writable destinations\n        const dest1Data = [];\n        const dest2Data = [];\n        const dest3Data = [];\n\n        const dest1 = new Writable({\n          write(chunk, encoding, callback) {\n            dest1Data.push(chunk);\n            callback();\n          },\n        });\n\n        const dest2 = new Writable({\n          write(chunk, encoding, callback) {\n            dest2Data.push(chunk);\n            callback();\n          },\n        });\n\n        const dest3 = new Writable({\n          write(chunk, encoding, callback) {\n            dest3Data.push(chunk);\n            callback();\n          },\n        });\n\n        // Set up finish handlers to track completion\n        let finishedCount = 0;\n        const onFinish = () => {\n          finishedCount++;\n          if (finishedCount === 3) {\n            // All destinations finished, send response\n            const result = {\n              dest1: Buffer.concat(dest1Data).toString(),\n              dest2: Buffer.concat(dest2Data).toString(),\n              dest3: Buffer.concat(dest3Data).toString(),\n              allSame:\n                Buffer.concat(dest1Data).equals(Buffer.concat(dest2Data)) &&\n                Buffer.concat(dest2Data).equals(Buffer.concat(dest3Data)),\n            };\n            res.end(JSON.stringify(result));\n          }\n        };\n\n        dest1.on('finish', onFinish);\n        dest2.on('finish', onFinish);\n        dest3.on('finish', onFinish);\n\n        // Pipe to multiple destinations - this is the key test!\n        req.pipe(dest1);\n        req.pipe(dest2);\n        req.pipe(dest3);\n      } else {\n        res.writeHead(404);\n        res.end('Not Found');\n      }\n    });\n\n    server.listen(8080);\n\n    // Send test data\n    const testData =\n      'Hello from multiple pipes! This data should reach all destinations.';\n    const response = await env.SERVICE.fetch(\n      'https://cloudflare.com/multipipe',\n      {\n        method: 'POST',\n        body: testData,\n        headers: {\n          'Content-Type': 'text/plain',\n        },\n      }\n    );\n\n    strictEqual(response.status, 200);\n    const result = await response.json();\n\n    // Verify all destinations received the same data\n    strictEqual(\n      result.dest1,\n      testData,\n      'Destination 1 should receive correct data'\n    );\n    strictEqual(\n      result.dest2,\n      testData,\n      'Destination 2 should receive correct data'\n    );\n    strictEqual(\n      result.dest3,\n      testData,\n      'Destination 3 should receive correct data'\n    );\n    strictEqual(\n      result.allSame,\n      true,\n      'All destinations should receive identical data'\n    );\n  },\n};\n\nexport const testScheduled = {\n  async test(_ctrl, env) {\n    strictEqual(typeof env.SERVICE.scheduled, 'function');\n\n    await env.SERVICE.scheduled({\n      scheduledTime: Date.now(),\n      cron: '0 0 * * *',\n    });\n\n    strictEqual(scheduledCallCount, 1);\n  },\n};\n\nexport const testConfigurableHighWaterMark = {\n  async test(_ctrl, env) {\n    {\n      await using server = http.createServer({ highWaterMark: 9999 });\n      strictEqual(server.highWaterMark, 9999);\n    }\n\n    {\n      // Node.js supports 1.1 as a value for highWaterMark\n      await using server = http.createServer({ highWaterMark: 1.11 });\n      strictEqual(server.highWaterMark, 1.11);\n    }\n\n    {\n      // Node.js omits negative values\n      await using server = http.createServer({ highWaterMark: -1 });\n      strictEqual(server.highWaterMark, 65536);\n    }\n\n    for (const highWaterMark of [null, 'hello world', ['merhaba dunya']]) {\n      throws(() => http.createServer({ highWaterMark }), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n    }\n  },\n};\n\nexport const testIncomingMessageSocket = {\n  async test(_ctrl, env) {\n    await using server = http.createServer((req, res) => {\n      strictEqual(req.socket.encrypted, false);\n      strictEqual(req.socket.localPort, 8080);\n      strictEqual(req.socket.localAddress, '127.0.0.1');\n      strictEqual(req.socket.remoteAddress, '127.0.0.1');\n      strictEqual(typeof req.socket.remotePort, 'number');\n      ok(req.socket.remotePort >= Math.pow(2, 15));\n      ok(req.socket.remotePort <= Math.pow(2, 16));\n      strictEqual(req.socket.remoteFamily, 'IPv4');\n      ok('on' in req.socket);\n\n      res.writeHead(200);\n      res.end('Hello, World!');\n    });\n\n    server.listen(8080);\n\n    const res = await env.SERVICE.fetch('https://cloudflare.com');\n    strictEqual(res.status, 200);\n    strictEqual(await res.text(), 'Hello, World!');\n  },\n};\n\n// Test for header duplication bug where headers like \"ttl: 60\" become \"ttl: TTL, 60\"\nexport const testWebPushHeaderDuplication = {\n  async test(_ctrl, env) {\n    await using server = http.createServer((req, res) => {\n      res.writeHead(200, { 'Content-Type': 'application/json' });\n      res.end(\n        JSON.stringify(\n          Object.fromEntries(\n            Object.entries(req.headers).filter(\n              ([key, _]) =>\n                key === 'ttl' || key === 'urgency' || key === 'topic'\n            )\n          )\n        )\n      );\n    });\n\n    server.listen(8080);\n\n    const res = await env.SERVICE.fetch('https://google.com', {\n      headers: {\n        TTL: '60',\n        Urgency: 'high',\n        Topic: 'test',\n      },\n    });\n    const body = await res.json();\n    strictEqual(body.ttl, '60');\n    strictEqual(body.urgency, 'high');\n    strictEqual(body.topic, 'test');\n  },\n};\n\nexport const testHttpRequestFields = {\n  async test(_ctrl, env) {\n    await using server = http.createServer((req, res) => {\n      strictEqual(req._consuming, false, 'Consuming should be false');\n      strictEqual(req._dumped, false, 'Dumped should be false');\n      strictEqual(req._paused, false, 'Paused should be false');\n      res.end('OK');\n    });\n    server.listen(8080);\n    await env.SERVICE.fetch('https://cloudflare.com');\n  },\n};\n\nexport const testBufferedDataEventEmission = {\n  async test(_ctrl, env) {\n    const dataFn = mock.fn((chunk) => {\n      strictEqual(chunk instanceof Buffer, true, 'Chunk should be a Buffer');\n      strictEqual(chunk.toString(), 'test data');\n    });\n    const errorFn = mock.fn();\n    const endFn = mock.fn();\n\n    await using server = http.createServer((req, res) => {\n      // Test that data events are emitted when listener is attached after data is buffered\n      dataFn.mock.resetCalls();\n      endFn.mock.resetCalls();\n\n      req.on('data', dataFn);\n      req.on('error', errorFn);\n      req.on('end', () => {\n        endFn();\n        res.end('OK');\n      });\n    });\n    server.listen(8080);\n\n    await env.SERVICE.fetch('https://cloudflare.com', {\n      method: 'POST',\n      body: 'test data',\n    });\n    strictEqual(\n      dataFn.mock.callCount(),\n      1,\n      '.on(\"data\") should have been called'\n    );\n    strictEqual(\n      endFn.mock.callCount(),\n      1,\n      '.on(\"end\") should have been called'\n    );\n\n    await env.SERVICE.fetch('https://cloudflare.com', {\n      method: 'GET',\n    });\n    strictEqual(errorFn.mock.callCount(), 0, 'Error should not be emitted');\n    strictEqual(dataFn.mock.callCount(), 0);\n    strictEqual(endFn.mock.callCount(), 1);\n  },\n};\n\nlet scheduledCallCount = 0;\n\nexport default {\n  fetch(request) {\n    return handleAsNodeRequest(8080, request);\n  },\n  async scheduled(event) {\n    scheduledCallCount++;\n    strictEqual(typeof event.scheduledTime, 'number');\n    strictEqual(typeof event.cron, 'string');\n  },\n};\n\n// Relevant Node.js tests\n// - [x] test/parallel/test-http-server-async-dispose.js\n// - [ ] test/parallel/test-http-server-capture-rejections.js\n// - [ ] test/parallel/test-http-server-close-idle-wait-response.js\n// - [ ] test/parallel/test-http-server-close-idle.js\n// - [ ] test/parallel/test-http-server-consumed-timeout.js\n// - [ ] test/parallel/test-http-server-headers-timeout-delayed-headers.js\n// - [ ] test/parallel/test-http-server-headers-timeout-interrupted-headers.js\n// - [ ] test/parallel/test-http-server-headers-timeout-keepalive.js\n// - [ ] test/parallel/test-http-server-headers-timeout-pipelining.js\n// - [x] test/parallel/test-http-server-incomingmessage-destroy.js\n// - [x] test/parallel/test-http-server-method.query.js\n// - [x] test/parallel/test-http-server-multiheaders.js\n// - [x] test/parallel/test-http-server-multiheaders2.js\n// - [x] test/parallel/test-http-server-non-utf8-header.js\n// - [x] test/parallel/test-http-server-options-incoming-message.js\n// - [x] test/parallel/test-http-server-options-server-response.js\n// - [ ] test/parallel/test-http-server-request-timeout-delayed-body.js\n// - [ ] test/parallel/test-http-server-request-timeout-delayed-headers.js\n// - [ ] test/parallel/test-http-server-request-timeout-interrupted-body.js\n// - [ ] test/parallel/test-http-server-request-timeout-interrupted-headers.js\n// - [ ] test/parallel/test-http-server-request-timeout-keepalive.js\n// - [ ] test/parallel/test-http-server-request-timeout-pipelining.js\n// - [ ] test/parallel/test-http-server-request-timeout-upgrade.js\n// - [x] test/parallel/test-http-server-timeouts-validation.js\n// - [x] test/parallel/test-http-server-write-after-end.js\n// - [x] test/parallel/test-http-server-write-end-after-end.js\n// - [x] test/parallel/test-http-server.js\n\n// Tests that does not apply to workerd.\n// - [ ] test/parallel/test-http-server-connection-list-when-close.js\n// - [ ] test/parallel/test-http-server-connections-checking-leak.js\n// - [ ] test/parallel/test-http-server-clear-timer.js\n// - [ ] test/parallel/test-http-server-client-error.js\n// - [ ] test/parallel/test-http-server-close-all.js\n// - [ ] test/parallel/test-http-server-close-destroy-timeout.js\n// - [ ] test/parallel/test-http-server-de-chunked-trailer.js\n// - [ ] test/parallel/test-http-server-delete-parser.js\n// - [ ] test/parallel/test-http-server-destroy-socket-on-client-error.js\n// - [ ] test/parallel/test-http-server-keep-alive-defaults.js\n// - [ ] test/parallel/test-http-server-keep-alive-max-requests-null.js\n// - [ ] test/parallel/test-http-server-keep-alive-timeout.js\n// - [ ] test/parallel/test-http-server-keepalive-end.js\n// - [ ] test/parallel/test-http-server-keepalive-req-gc.js\n// - [ ] test/parallel/test-http-server-options-highwatermark.js\n// - [ ] test/parallel/test-http-server-multiple-client-error.js\n// - [ ] test/parallel/test-http-server-reject-chunked-with-content-length.js\n// - [ ] test/parallel/test-http-server-reject-cr-no-lf.js\n// - [ ] test/parallel/test-http-server-response-standalone.js\n// - [ ] test/parallel/test-http-server-stale-close.js\n// - [ ] test/parallel/test-http-server-unconsume.js\n// - [ ] test/parallel/test-http-server-unconsume-consume.js\n"
  },
  {
    "path": "src/workerd/api/node/tests/http-server-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"http-server-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"http-server-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_http_modules\", \"enable_nodejs_http_server_modules\", \"streams_enable_constructors\"],\n        bindings = [\n          ( name = \"SERVICE\", service = \"http-server-nodejs-test\" ),\n          ( name = \"PONG_SERVER_PORT\", fromEnvironment = \"PONG_SERVER_PORT\" ),\n          ( name = \"GLOBAL_SERVICE\", service = (name = \"http-server-nodejs-test\", entrypoint = \"GlobalService\") ),\n        ],\n      )\n    ),\n    ( name = \"internet\", network = ( allow = [\"private\"] ) ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/http2-test.js",
    "content": "import http2 from 'node:http2';\nimport { strictEqual, deepStrictEqual, throws } from 'node:assert';\n\nexport const http2Test = {\n  test() {\n    strictEqual(typeof http2, 'object');\n    strictEqual(typeof http2.constants, 'object');\n    strictEqual(typeof http2.createSecureServer, 'function');\n    strictEqual(typeof http2.createServer, 'function');\n    strictEqual(typeof http2.Http2ServerRequest, 'function');\n    strictEqual(typeof http2.Http2ServerResponse, 'function');\n    strictEqual(typeof http2.connect, 'function');\n    strictEqual(typeof http2.getDefaultSettings, 'function');\n    strictEqual(typeof http2.getPackedSettings, 'function');\n    strictEqual(typeof http2.getUnpackedSettings, 'function');\n    strictEqual(typeof http2.performServerHandshake, 'function');\n    strictEqual(typeof http2.sensitiveHeaders, 'symbol');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/http2-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"http2-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"http2-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_http2_module\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/inspector-nodejs-test.js",
    "content": "import inspector from 'node:inspector';\nimport assert from 'node:assert';\nimport { EventEmitter } from 'node:events';\nimport promises from 'node:inspector/promises';\n\nexport const inspectorClose = {\n  test() {\n    assert.doesNotThrow(() => inspector.close());\n    assert.strictEqual(inspector.close(), undefined);\n  },\n};\n\nexport const inspectorConsole = {\n  test() {\n    assert.strictEqual(typeof inspector.console, 'object');\n\n    const consoleMethods = [\n      'debug',\n      'error',\n      'info',\n      'log',\n      'warn',\n      'dir',\n      'dirxml',\n      'table',\n      'trace',\n      'group',\n      'groupCollapsed',\n      'groupEnd',\n      'clear',\n      'count',\n      'countReset',\n      'assert',\n      'profile',\n      'profileEnd',\n      'time',\n      'timeLog',\n      'timeStamp',\n    ];\n\n    for (const method of consoleMethods) {\n      assert.strictEqual(typeof inspector.console[method], 'function');\n      assert.doesNotThrow(() => inspector.console[method]());\n      assert.strictEqual(inspector.console[method](), undefined);\n      assert.strictEqual(inspector.console[method]('test'), undefined);\n      assert.strictEqual(inspector.console[method]('test', 'args'), undefined);\n    }\n  },\n};\n\nexport const inspectorOpen = {\n  test() {\n    const disposable = inspector.open();\n    assert.strictEqual(typeof disposable, 'object');\n    assert.strictEqual(typeof disposable[Symbol.dispose], 'function');\n\n    const disposePromise = disposable[Symbol.dispose]();\n    assert.strictEqual(disposePromise instanceof Promise, true);\n\n    disposable[Symbol.dispose]().then((result) => {\n      assert.strictEqual(result, undefined);\n    });\n\n    assert.doesNotThrow(() => inspector.open(9229));\n    assert.doesNotThrow(() => inspector.open(9229, 'localhost'));\n    assert.doesNotThrow(() => inspector.open(9229, 'localhost', true));\n    assert.doesNotThrow(() => inspector.open(undefined, undefined, false));\n  },\n};\n\nexport const inspectorUrl = {\n  test() {\n    assert.strictEqual(inspector.url(), undefined);\n    assert.strictEqual(typeof inspector.url(), 'undefined');\n  },\n};\n\nexport const inspectorWaitForDebugger = {\n  test() {\n    assert.doesNotThrow(() => inspector.waitForDebugger());\n    assert.strictEqual(inspector.waitForDebugger(), undefined);\n  },\n};\n\nexport const inspectorSession = {\n  test() {\n    assert.throws(() => new inspector.Session(), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      message: /Session/,\n    });\n\n    assert.strictEqual(typeof inspector.Session, 'function');\n  },\n};\n\nexport const inspectorSessionMethods = {\n  test() {\n    let session;\n    try {\n      session = new inspector.Session();\n    } catch (e) {\n      const SessionProto = inspector.Session.prototype;\n\n      assert.strictEqual(typeof SessionProto.connect, 'function');\n      assert.strictEqual(typeof SessionProto.connectToMainThread, 'function');\n      assert.strictEqual(typeof SessionProto.disconnect, 'function');\n      assert.strictEqual(typeof SessionProto.post, 'function');\n\n      assert.strictEqual(typeof SessionProto.on, 'function');\n      assert.strictEqual(typeof SessionProto.emit, 'function');\n      assert.strictEqual(typeof SessionProto.once, 'function');\n      assert.strictEqual(typeof SessionProto.removeListener, 'function');\n    }\n  },\n};\n\nexport const inspectorNetwork = {\n  test() {\n    assert.strictEqual(typeof inspector.Network, 'object');\n\n    assert.throws(() => inspector.Network.requestWillBeSent({}), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      message: /Network\\.requestWillBeSent/,\n    });\n\n    assert.throws(() => inspector.Network.dataReceived({}), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      message: /Network\\.dataReceived/,\n    });\n\n    assert.throws(() => inspector.Network.responseReceived({}), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      message: /Network\\.responseReceived/,\n    });\n\n    assert.throws(() => inspector.Network.loadingFinished({}), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      message: /Network\\.loadingFinished/,\n    });\n\n    assert.throws(() => inspector.Network.loadingFailed({}), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      message: /Network\\.loadingFailed/,\n    });\n  },\n};\n\nexport const inspectorNetworkMethods = {\n  test() {\n    const networkMethods = [\n      'requestWillBeSent',\n      'dataReceived',\n      'responseReceived',\n      'loadingFinished',\n      'loadingFailed',\n    ];\n\n    for (const method of networkMethods) {\n      assert.strictEqual(typeof inspector.Network[method], 'function');\n      assert.throws(() => inspector.Network[method]({}), {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      });\n      assert.throws(() => inspector.Network[method](undefined), {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      });\n      assert.throws(() => inspector.Network[method](null), {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      });\n    }\n  },\n};\n\nexport const inspectorDefaultExport = {\n  test() {\n    assert.strictEqual(typeof inspector, 'object');\n    assert.strictEqual(typeof inspector.Session, 'function');\n    assert.strictEqual(typeof inspector.close, 'function');\n    assert.strictEqual(typeof inspector.console, 'object');\n    assert.strictEqual(typeof inspector.open, 'function');\n    assert.strictEqual(typeof inspector.url, 'function');\n    assert.strictEqual(typeof inspector.waitForDebugger, 'function');\n    assert.strictEqual(typeof inspector.Network, 'object');\n  },\n};\n\nexport const inspectorModuleExports = {\n  async test() {\n    const inspectorModule = await import('node:inspector');\n\n    assert.strictEqual(typeof inspectorModule.default, 'object');\n    assert.strictEqual(typeof inspectorModule.Session, 'function');\n    assert.strictEqual(typeof inspectorModule.close, 'function');\n    assert.strictEqual(typeof inspectorModule.console, 'object');\n    assert.strictEqual(typeof inspectorModule.open, 'function');\n    assert.strictEqual(typeof inspectorModule.url, 'function');\n    assert.strictEqual(typeof inspectorModule.waitForDebugger, 'function');\n    assert.strictEqual(typeof inspectorModule.Network, 'object');\n\n    assert.strictEqual(\n      inspectorModule.default.Session,\n      inspectorModule.Session\n    );\n    assert.strictEqual(inspectorModule.default.close, inspectorModule.close);\n    assert.strictEqual(\n      inspectorModule.default.console,\n      inspectorModule.console\n    );\n    assert.strictEqual(inspectorModule.default.open, inspectorModule.open);\n    assert.strictEqual(inspectorModule.default.url, inspectorModule.url);\n    assert.strictEqual(\n      inspectorModule.default.waitForDebugger,\n      inspectorModule.waitForDebugger\n    );\n    assert.strictEqual(\n      inspectorModule.default.Network,\n      inspectorModule.Network\n    );\n  },\n};\n\nexport const inspectorOpenReturnValue = {\n  async test() {\n    const disposable1 = inspector.open();\n    const disposable2 = inspector.open(8080);\n    const disposable3 = inspector.open(8080, '127.0.0.1');\n    const disposable4 = inspector.open(8080, '127.0.0.1', false);\n\n    for (const disposable of [\n      disposable1,\n      disposable2,\n      disposable3,\n      disposable4,\n    ]) {\n      assert.strictEqual(typeof disposable, 'object');\n      assert.strictEqual(typeof disposable[Symbol.dispose], 'function');\n\n      const result = await disposable[Symbol.dispose]();\n      assert.strictEqual(result, undefined);\n    }\n  },\n};\n\nexport const inspectorConsoleNoOp = {\n  test() {\n    const testArgs = [\n      [],\n      ['test'],\n      ['test', 123],\n      ['test', 123, { key: 'value' }],\n      [null, undefined, NaN, Infinity],\n      [() => {}, [], {}],\n    ];\n\n    const consoleMethods = Object.keys(inspector.console);\n\n    for (const method of consoleMethods) {\n      for (const args of testArgs) {\n        assert.strictEqual(inspector.console[method](...args), undefined);\n      }\n    }\n  },\n};\n\nexport const inspectorSessionInheritance = {\n  test() {\n    assert.strictEqual(\n      inspector.Session.prototype instanceof EventEmitter,\n      true\n    );\n\n    const proto = inspector.Session.prototype;\n    const eventMethods = [\n      'on',\n      'once',\n      'emit',\n      'removeListener',\n      'removeAllListeners',\n      'setMaxListeners',\n      'getMaxListeners',\n      'listeners',\n      'rawListeners',\n      'listenerCount',\n      'prependListener',\n      'prependOnceListener',\n      'eventNames',\n    ];\n\n    for (const method of eventMethods) {\n      assert.strictEqual(typeof proto[method], 'function');\n    }\n  },\n};\n\nexport const inspectorPromises = {\n  test() {\n    assert.strictEqual(typeof promises, 'object');\n    assert.strictEqual(typeof promises.Session, 'function');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/inspector-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"inspector-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"inspector-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_inspector_module\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/legacy_url-nodejs-test.js",
    "content": "import url from 'node:url';\nimport assert from 'node:assert';\nimport { inspect } from 'node:util';\n\n// Ref: https://github.com/nodejs/node/blob/e92446536ed4e268c9eef6ae6f911e384c98eecf/test/parallel/test-url-format.js\nexport const format = {\n  async test() {\n    const formatTests = {\n      'http://example.com?': {\n        href: 'http://example.com/?',\n        protocol: 'http:',\n        slashes: true,\n        host: 'example.com',\n        hostname: 'example.com',\n        search: '?',\n        query: {},\n        pathname: '/',\n      },\n      'http://example.com?foo=bar#frag': {\n        href: 'http://example.com/?foo=bar#frag',\n        protocol: 'http:',\n        host: 'example.com',\n        hostname: 'example.com',\n        hash: '#frag',\n        search: '?foo=bar',\n        query: 'foo=bar',\n        pathname: '/',\n      },\n      'http://example.com?foo=@bar#frag': {\n        href: 'http://example.com/?foo=@bar#frag',\n        protocol: 'http:',\n        host: 'example.com',\n        hostname: 'example.com',\n        hash: '#frag',\n        search: '?foo=@bar',\n        query: 'foo=@bar',\n        pathname: '/',\n      },\n      'http://example.com?foo=/bar/#frag': {\n        href: 'http://example.com/?foo=/bar/#frag',\n        protocol: 'http:',\n        host: 'example.com',\n        hostname: 'example.com',\n        hash: '#frag',\n        search: '?foo=/bar/',\n        query: 'foo=/bar/',\n        pathname: '/',\n      },\n      'http://example.com?foo=?bar/#frag': {\n        href: 'http://example.com/?foo=?bar/#frag',\n        protocol: 'http:',\n        host: 'example.com',\n        hostname: 'example.com',\n        hash: '#frag',\n        search: '?foo=?bar/',\n        query: 'foo=?bar/',\n        pathname: '/',\n      },\n      'http://example.com#frag=?bar/#frag': {\n        href: 'http://example.com/#frag=?bar/#frag',\n        protocol: 'http:',\n        host: 'example.com',\n        hostname: 'example.com',\n        hash: '#frag=?bar/#frag',\n        pathname: '/',\n      },\n      'http://google.com\" onload=\"alert(42)/': {\n        href: 'http://google.com/%22%20onload=%22alert(42)/',\n        protocol: 'http:',\n        host: 'google.com',\n        pathname: '/%22%20onload=%22alert(42)/',\n      },\n      'http://a.com/a/b/c?s#h': {\n        href: 'http://a.com/a/b/c?s#h',\n        protocol: 'http',\n        host: 'a.com',\n        pathname: 'a/b/c',\n        hash: 'h',\n        search: 's',\n      },\n      'xmpp:isaacschlueter@jabber.org': {\n        href: 'xmpp:isaacschlueter@jabber.org',\n        protocol: 'xmpp:',\n        host: 'jabber.org',\n        auth: 'isaacschlueter',\n        hostname: 'jabber.org',\n      },\n      'http://atpass:foo%40bar@127.0.0.1/': {\n        href: 'http://atpass:foo%40bar@127.0.0.1/',\n        auth: 'atpass:foo@bar',\n        hostname: '127.0.0.1',\n        protocol: 'http:',\n        pathname: '/',\n      },\n      'http://atslash%2F%40:%2F%40@foo/': {\n        href: 'http://atslash%2F%40:%2F%40@foo/',\n        auth: 'atslash/@:/@',\n        hostname: 'foo',\n        protocol: 'http:',\n        pathname: '/',\n      },\n      'svn+ssh://foo/bar': {\n        href: 'svn+ssh://foo/bar',\n        hostname: 'foo',\n        protocol: 'svn+ssh:',\n        pathname: '/bar',\n        slashes: true,\n      },\n      'dash-test://foo/bar': {\n        href: 'dash-test://foo/bar',\n        hostname: 'foo',\n        protocol: 'dash-test:',\n        pathname: '/bar',\n        slashes: true,\n      },\n      'dash-test:foo/bar': {\n        href: 'dash-test:foo/bar',\n        hostname: 'foo',\n        protocol: 'dash-test:',\n        pathname: '/bar',\n      },\n      'dot.test://foo/bar': {\n        href: 'dot.test://foo/bar',\n        hostname: 'foo',\n        protocol: 'dot.test:',\n        pathname: '/bar',\n        slashes: true,\n      },\n      'dot.test:foo/bar': {\n        href: 'dot.test:foo/bar',\n        hostname: 'foo',\n        protocol: 'dot.test:',\n        pathname: '/bar',\n      },\n      // IPv6 support\n      'coap:u:p@[::1]:61616/.well-known/r?n=Temperature': {\n        href: 'coap:u:p@[::1]:61616/.well-known/r?n=Temperature',\n        protocol: 'coap:',\n        auth: 'u:p',\n        hostname: '::1',\n        port: '61616',\n        pathname: '/.well-known/r',\n        search: 'n=Temperature',\n      },\n      'coap:[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:61616/s/stopButton': {\n        href: 'coap:[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:61616/s/stopButton',\n        protocol: 'coap',\n        host: '[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:61616',\n        pathname: '/s/stopButton',\n      },\n      'http://[::]/': {\n        href: 'http://[::]/',\n        protocol: 'http:',\n        hostname: '[::]',\n        pathname: '/',\n      },\n\n      // Encode context-specific delimiters in path and query, but do not touch\n      // other non-delimiter chars like `%`.\n      // <https://github.com/nodejs/node-v0.x-archive/issues/4082>\n\n      // `#`,`?` in path\n      '/path/to/%%23%3F+=&.txt?foo=theA1#bar': {\n        href: '/path/to/%%23%3F+=&.txt?foo=theA1#bar',\n        pathname: '/path/to/%#?+=&.txt',\n        query: {\n          foo: 'theA1',\n        },\n        hash: '#bar',\n      },\n\n      // `#`,`?` in path + `#` in query\n      '/path/to/%%23%3F+=&.txt?foo=the%231#bar': {\n        href: '/path/to/%%23%3F+=&.txt?foo=the%231#bar',\n        pathname: '/path/to/%#?+=&.txt',\n        query: {\n          foo: 'the#1',\n        },\n        hash: '#bar',\n      },\n\n      // `#` in path end + `#` in query\n      '/path/to/%%23?foo=the%231#bar': {\n        href: '/path/to/%%23?foo=the%231#bar',\n        pathname: '/path/to/%#',\n        query: {\n          foo: 'the#1',\n        },\n        hash: '#bar',\n      },\n\n      // `?` and `#` in path and search\n      'http://ex.com/foo%3F100%m%23r?abc=the%231?&foo=bar#frag': {\n        href: 'http://ex.com/foo%3F100%m%23r?abc=the%231?&foo=bar#frag',\n        protocol: 'http:',\n        hostname: 'ex.com',\n        hash: '#frag',\n        search: '?abc=the#1?&foo=bar',\n        pathname: '/foo?100%m#r',\n      },\n\n      // `?` and `#` in search only\n      'http://ex.com/fooA100%mBr?abc=the%231?&foo=bar#frag': {\n        href: 'http://ex.com/fooA100%mBr?abc=the%231?&foo=bar#frag',\n        protocol: 'http:',\n        hostname: 'ex.com',\n        hash: '#frag',\n        search: '?abc=the#1?&foo=bar',\n        pathname: '/fooA100%mBr',\n      },\n\n      // Multiple `#` in search\n      'http://example.com/?foo=bar%231%232%233&abc=%234%23%235#frag': {\n        href: 'http://example.com/?foo=bar%231%232%233&abc=%234%23%235#frag',\n        protocol: 'http:',\n        slashes: true,\n        host: 'example.com',\n        hostname: 'example.com',\n        hash: '#frag',\n        search: '?foo=bar#1#2#3&abc=#4##5',\n        query: {},\n        pathname: '/',\n      },\n\n      // More than 255 characters in hostname which exceeds the limit\n      [`http://${'a'.repeat(255)}.com/node`]: {\n        href: 'http:///node',\n        protocol: 'http:',\n        slashes: true,\n        host: '',\n        hostname: '',\n        pathname: '/node',\n        path: '/node',\n      },\n\n      // Greater than or equal to 63 characters after `.` in hostname\n      [`http://www.${'z'.repeat(63)}example.com/node`]: {\n        href: `http://www.${'z'.repeat(63)}example.com/node`,\n        protocol: 'http:',\n        slashes: true,\n        host: `www.${'z'.repeat(63)}example.com`,\n        hostname: `www.${'z'.repeat(63)}example.com`,\n        pathname: '/node',\n        path: '/node',\n      },\n\n      // https://github.com/nodejs/node/issues/3361\n      'file:///home/user': {\n        href: 'file:///home/user',\n        protocol: 'file',\n        pathname: '/home/user',\n        path: '/home/user',\n      },\n\n      // surrogate in auth\n      'http://%F0%9F%98%80@www.example.com/': {\n        href: 'http://%F0%9F%98%80@www.example.com/',\n        protocol: 'http:',\n        auth: '\\uD83D\\uDE00',\n        hostname: 'www.example.com',\n        pathname: '/',\n      },\n    };\n\n    for (const u in formatTests) {\n      const expect = formatTests[u].href;\n      delete formatTests[u].href;\n      const actual = url.format(u);\n      const actualObj = url.format(formatTests[u]);\n      assert.strictEqual(\n        actual,\n        expect,\n        `wonky format(${u}) == ${expect}\\nactual:${actual}`\n      );\n      assert.strictEqual(\n        actualObj,\n        expect,\n        `wonky format(${JSON.stringify(formatTests[u])}) == ${\n          expect\n        }\\nactual: ${actualObj}`\n      );\n    }\n  },\n};\n\n// Ref: https://github.com/nodejs/node/blob/e92446536ed4e268c9eef6ae6f911e384c98eecf/test/parallel/test-url-relative.js\nexport const relative = {\n  async test() {\n    // When source is false\n    assert.strictEqual(url.resolveObject('', 'foo'), 'foo');\n\n    // [from, path, expected]\n    const relativeTests = [\n      ['/foo/bar/baz', 'quux', '/foo/bar/quux'],\n      ['/foo/bar/baz', 'quux/asdf', '/foo/bar/quux/asdf'],\n      ['/foo/bar/baz', 'quux/baz', '/foo/bar/quux/baz'],\n      ['/foo/bar/baz', '../quux/baz', '/foo/quux/baz'],\n      ['/foo/bar/baz', '/bar', '/bar'],\n      ['/foo/bar/baz/', 'quux', '/foo/bar/baz/quux'],\n      ['/foo/bar/baz/', 'quux/baz', '/foo/bar/baz/quux/baz'],\n      ['/foo/bar/baz', '../../../../../../../../quux/baz', '/quux/baz'],\n      ['/foo/bar/baz', '../../../../../../../quux/baz', '/quux/baz'],\n      ['/foo', '.', '/'],\n      ['/foo', '..', '/'],\n      ['/foo/', '.', '/foo/'],\n      ['/foo/', '..', '/'],\n      ['/foo/bar', '.', '/foo/'],\n      ['/foo/bar', '..', '/'],\n      ['/foo/bar/', '.', '/foo/bar/'],\n      ['/foo/bar/', '..', '/foo/'],\n      ['foo/bar', '../../../baz', '../../baz'],\n      ['foo/bar/', '../../../baz', '../baz'],\n      [\n        'http://example.com/b//c//d;p?q#blarg',\n        'https:#hash2',\n        'https:///#hash2',\n      ],\n      [\n        'http://example.com/b//c//d;p?q#blarg',\n        'https:/p/a/t/h?s#hash2',\n        'https://p/a/t/h?s#hash2',\n      ],\n      [\n        'http://example.com/b//c//d;p?q#blarg',\n        'https://u:p@h.com/p/a/t/h?s#hash2',\n        'https://u:p@h.com/p/a/t/h?s#hash2',\n      ],\n      [\n        'http://example.com/b//c//d;p?q#blarg',\n        'https:/a/b/c/d',\n        'https://a/b/c/d',\n      ],\n      [\n        'http://example.com/b//c//d;p?q#blarg',\n        'http:#hash2',\n        'http://example.com/b//c//d;p?q#hash2',\n      ],\n      [\n        'http://example.com/b//c//d;p?q#blarg',\n        'http:/p/a/t/h?s#hash2',\n        'http://example.com/p/a/t/h?s#hash2',\n      ],\n      [\n        'http://example.com/b//c//d;p?q#blarg',\n        'http://u:p@h.com/p/a/t/h?s#hash2',\n        'http://u:p@h.com/p/a/t/h?s#hash2',\n      ],\n      [\n        'http://example.com/b//c//d;p?q#blarg',\n        'http:/a/b/c/d',\n        'http://example.com/a/b/c/d',\n      ],\n      ['/foo/bar/baz', '/../etc/passwd', '/etc/passwd'],\n      ['http://localhost', 'file:///Users/foo', 'file:///Users/foo'],\n      ['http://localhost', 'file://foo/Users', 'file://foo/Users'],\n      [\n        'https://registry.npmjs.org',\n        '@foo/bar',\n        'https://registry.npmjs.org/@foo/bar',\n      ],\n    ];\n    for (let i = 0; i < relativeTests.length; i++) {\n      const relativeTest = relativeTests[i];\n\n      const a = url.resolve(relativeTest[0], relativeTest[1]);\n      const e = relativeTest[2];\n      assert.strictEqual(\n        a,\n        e,\n        `resolve(${relativeTest[0]}, ${relativeTest[1]})` +\n          ` == ${e}\\n  actual=${a}`\n      );\n    }\n\n    //\n    // Tests below taken from Chiron\n    // http://code.google.com/p/chironjs/source/browse/trunk/src/test/http/url.js\n    //\n    // Copyright (c) 2002-2008 Kris Kowal <http://cixar.com/~kris.kowal>\n    // used with permission under MIT License\n    //\n    // Changes marked with @isaacs\n\n    const bases = [\n      'http://a/b/c/d;p?q',\n      'http://a/b/c/d;p?q=1/2',\n      'http://a/b/c/d;p=1/2?q',\n      'fred:///s//a/b/c',\n      'http:///s//a/b/c',\n    ];\n\n    // [to, from, result]\n    const relativeTests2 = [\n      // http://lists.w3.org/Archives/Public/uri/2004Feb/0114.html\n      ['../c', 'foo:a/b', 'foo:c'],\n      ['foo:.', 'foo:a', 'foo:'],\n      ['/foo/../../../bar', 'zz:abc', 'zz:/bar'],\n      ['/foo/../bar', 'zz:abc', 'zz:/bar'],\n      // @isaacs Disagree. Not how web browsers resolve this.\n      ['foo/../../../bar', 'zz:abc', 'zz:bar'],\n      // ['foo/../../../bar',  'zz:abc', 'zz:../../bar'], // @isaacs Added\n      ['foo/../bar', 'zz:abc', 'zz:bar'],\n      ['zz:.', 'zz:abc', 'zz:'],\n      ['/.', bases[0], 'http://a/'],\n      ['/.foo', bases[0], 'http://a/.foo'],\n      ['.foo', bases[0], 'http://a/b/c/.foo'],\n\n      // http://gbiv.com/protocols/uri/test/rel_examples1.html\n      // examples from RFC 2396\n      ['g:h', bases[0], 'g:h'],\n      ['g', bases[0], 'http://a/b/c/g'],\n      ['./g', bases[0], 'http://a/b/c/g'],\n      ['g/', bases[0], 'http://a/b/c/g/'],\n      ['/g', bases[0], 'http://a/g'],\n      ['//g', bases[0], 'http://g/'],\n      // Changed with RFC 2396bis\n      // ('?y', bases[0], 'http://a/b/c/d;p?y'],\n      ['?y', bases[0], 'http://a/b/c/d;p?y'],\n      ['g?y', bases[0], 'http://a/b/c/g?y'],\n      // Changed with RFC 2396bis\n      // ('#s', bases[0], CURRENT_DOC_URI + '#s'],\n      ['#s', bases[0], 'http://a/b/c/d;p?q#s'],\n      ['g#s', bases[0], 'http://a/b/c/g#s'],\n      ['g?y#s', bases[0], 'http://a/b/c/g?y#s'],\n      [';x', bases[0], 'http://a/b/c/;x'],\n      ['g;x', bases[0], 'http://a/b/c/g;x'],\n      ['g;x?y#s', bases[0], 'http://a/b/c/g;x?y#s'],\n      // Changed with RFC 2396bis\n      // ('', bases[0], CURRENT_DOC_URI],\n      ['', bases[0], 'http://a/b/c/d;p?q'],\n      ['.', bases[0], 'http://a/b/c/'],\n      ['./', bases[0], 'http://a/b/c/'],\n      ['..', bases[0], 'http://a/b/'],\n      ['../', bases[0], 'http://a/b/'],\n      ['../g', bases[0], 'http://a/b/g'],\n      ['../..', bases[0], 'http://a/'],\n      ['../../', bases[0], 'http://a/'],\n      ['../../g', bases[0], 'http://a/g'],\n      ['../../../g', bases[0], ('http://a/../g', 'http://a/g')],\n      ['../../../../g', bases[0], ('http://a/../../g', 'http://a/g')],\n      // Changed with RFC 2396bis\n      // ('/./g', bases[0], 'http://a/./g'],\n      ['/./g', bases[0], 'http://a/g'],\n      // Changed with RFC 2396bis\n      // ('/../g', bases[0], 'http://a/../g'],\n      ['/../g', bases[0], 'http://a/g'],\n      ['g.', bases[0], 'http://a/b/c/g.'],\n      ['.g', bases[0], 'http://a/b/c/.g'],\n      ['g..', bases[0], 'http://a/b/c/g..'],\n      ['..g', bases[0], 'http://a/b/c/..g'],\n      ['./../g', bases[0], 'http://a/b/g'],\n      ['./g/.', bases[0], 'http://a/b/c/g/'],\n      ['g/./h', bases[0], 'http://a/b/c/g/h'],\n      ['g/../h', bases[0], 'http://a/b/c/h'],\n      ['g;x=1/./y', bases[0], 'http://a/b/c/g;x=1/y'],\n      ['g;x=1/../y', bases[0], 'http://a/b/c/y'],\n      ['g?y/./x', bases[0], 'http://a/b/c/g?y/./x'],\n      ['g?y/../x', bases[0], 'http://a/b/c/g?y/../x'],\n      ['g#s/./x', bases[0], 'http://a/b/c/g#s/./x'],\n      ['g#s/../x', bases[0], 'http://a/b/c/g#s/../x'],\n      ['http:g', bases[0], ('http:g', 'http://a/b/c/g')],\n      ['http:', bases[0], ('http:', bases[0])],\n      // Not sure where this one originated\n      ['/a/b/c/./../../g', bases[0], 'http://a/a/g'],\n\n      // http://gbiv.com/protocols/uri/test/rel_examples2.html\n      // slashes in base URI's query args\n      ['g', bases[1], 'http://a/b/c/g'],\n      ['./g', bases[1], 'http://a/b/c/g'],\n      ['g/', bases[1], 'http://a/b/c/g/'],\n      ['/g', bases[1], 'http://a/g'],\n      ['//g', bases[1], 'http://g/'],\n      // Changed in RFC 2396bis\n      // ('?y', bases[1], 'http://a/b/c/?y'],\n      ['?y', bases[1], 'http://a/b/c/d;p?y'],\n      ['g?y', bases[1], 'http://a/b/c/g?y'],\n      ['g?y/./x', bases[1], 'http://a/b/c/g?y/./x'],\n      ['g?y/../x', bases[1], 'http://a/b/c/g?y/../x'],\n      ['g#s', bases[1], 'http://a/b/c/g#s'],\n      ['g#s/./x', bases[1], 'http://a/b/c/g#s/./x'],\n      ['g#s/../x', bases[1], 'http://a/b/c/g#s/../x'],\n      ['./', bases[1], 'http://a/b/c/'],\n      ['../', bases[1], 'http://a/b/'],\n      ['../g', bases[1], 'http://a/b/g'],\n      ['../../', bases[1], 'http://a/'],\n      ['../../g', bases[1], 'http://a/g'],\n\n      // http://gbiv.com/protocols/uri/test/rel_examples3.html\n      // slashes in path params\n      // all of these changed in RFC 2396bis\n      ['g', bases[2], 'http://a/b/c/d;p=1/g'],\n      ['./g', bases[2], 'http://a/b/c/d;p=1/g'],\n      ['g/', bases[2], 'http://a/b/c/d;p=1/g/'],\n      ['g?y', bases[2], 'http://a/b/c/d;p=1/g?y'],\n      [';x', bases[2], 'http://a/b/c/d;p=1/;x'],\n      ['g;x', bases[2], 'http://a/b/c/d;p=1/g;x'],\n      ['g;x=1/./y', bases[2], 'http://a/b/c/d;p=1/g;x=1/y'],\n      ['g;x=1/../y', bases[2], 'http://a/b/c/d;p=1/y'],\n      ['./', bases[2], 'http://a/b/c/d;p=1/'],\n      ['../', bases[2], 'http://a/b/c/'],\n      ['../g', bases[2], 'http://a/b/c/g'],\n      ['../../', bases[2], 'http://a/b/'],\n      ['../../g', bases[2], 'http://a/b/g'],\n\n      // http://gbiv.com/protocols/uri/test/rel_examples4.html\n      // double and triple slash, unknown scheme\n      ['g:h', bases[3], 'g:h'],\n      ['g', bases[3], 'fred:///s//a/b/g'],\n      ['./g', bases[3], 'fred:///s//a/b/g'],\n      ['g/', bases[3], 'fred:///s//a/b/g/'],\n      ['/g', bases[3], 'fred:///g'], // May change to fred:///s//a/g\n      ['//g', bases[3], 'fred://g'], // May change to fred:///s//g\n      ['//g/x', bases[3], 'fred://g/x'], // May change to fred:///s//g/x\n      ['///g', bases[3], 'fred:///g'],\n      ['./', bases[3], 'fred:///s//a/b/'],\n      ['../', bases[3], 'fred:///s//a/'],\n      ['../g', bases[3], 'fred:///s//a/g'],\n\n      ['../../', bases[3], 'fred:///s//'],\n      ['../../g', bases[3], 'fred:///s//g'],\n      ['../../../g', bases[3], 'fred:///s/g'],\n      // May change to fred:///s//a/../../../g\n      ['../../../../g', bases[3], 'fred:///g'],\n\n      // http://gbiv.com/protocols/uri/test/rel_examples5.html\n      // double and triple slash, well-known scheme\n      ['g:h', bases[4], 'g:h'],\n      ['g', bases[4], 'http:///s//a/b/g'],\n      ['./g', bases[4], 'http:///s//a/b/g'],\n      ['g/', bases[4], 'http:///s//a/b/g/'],\n      ['/g', bases[4], 'http:///g'], // May change to http:///s//a/g\n      ['//g', bases[4], 'http://g/'], // May change to http:///s//g\n      ['//g/x', bases[4], 'http://g/x'], // May change to http:///s//g/x\n      ['///g', bases[4], 'http:///g'],\n      ['./', bases[4], 'http:///s//a/b/'],\n      ['../', bases[4], 'http:///s//a/'],\n      ['../g', bases[4], 'http:///s//a/g'],\n      ['../../', bases[4], 'http:///s//'],\n      ['../../g', bases[4], 'http:///s//g'],\n      // May change to http:///s//a/../../g\n      ['../../../g', bases[4], 'http:///s/g'],\n      // May change to http:///s//a/../../../g\n      ['../../../../g', bases[4], 'http:///g'],\n\n      // From Dan Connelly's tests in http://www.w3.org/2000/10/swap/uripath.py\n      ['bar:abc', 'foo:xyz', 'bar:abc'],\n      ['../abc', 'http://example/x/y/z', 'http://example/x/abc'],\n      ['http://example/x/abc', 'http://example2/x/y/z', 'http://example/x/abc'],\n      ['../r', 'http://ex/x/y/z', 'http://ex/x/r'],\n      ['q/r', 'http://ex/x/y', 'http://ex/x/q/r'],\n      ['q/r#s', 'http://ex/x/y', 'http://ex/x/q/r#s'],\n      ['q/r#s/t', 'http://ex/x/y', 'http://ex/x/q/r#s/t'],\n      ['ftp://ex/x/q/r', 'http://ex/x/y', 'ftp://ex/x/q/r'],\n      ['', 'http://ex/x/y', 'http://ex/x/y'],\n      ['', 'http://ex/x/y/', 'http://ex/x/y/'],\n      ['', 'http://ex/x/y/pdq', 'http://ex/x/y/pdq'],\n      ['z/', 'http://ex/x/y/', 'http://ex/x/y/z/'],\n      [\n        '#Animal',\n        'file:/swap/test/animal.rdf',\n        'file:/swap/test/animal.rdf#Animal',\n      ],\n      ['../abc', 'file:/e/x/y/z', 'file:/e/x/abc'],\n      ['/example/x/abc', 'file:/example2/x/y/z', 'file:/example/x/abc'],\n      ['../r', 'file:/ex/x/y/z', 'file:/ex/x/r'],\n      ['/r', 'file:/ex/x/y/z', 'file:/r'],\n      ['q/r', 'file:/ex/x/y', 'file:/ex/x/q/r'],\n      ['q/r#s', 'file:/ex/x/y', 'file:/ex/x/q/r#s'],\n      ['q/r#', 'file:/ex/x/y', 'file:/ex/x/q/r#'],\n      ['q/r#s/t', 'file:/ex/x/y', 'file:/ex/x/q/r#s/t'],\n      ['ftp://ex/x/q/r', 'file:/ex/x/y', 'ftp://ex/x/q/r'],\n      ['', 'file:/ex/x/y', 'file:/ex/x/y'],\n      ['', 'file:/ex/x/y/', 'file:/ex/x/y/'],\n      ['', 'file:/ex/x/y/pdq', 'file:/ex/x/y/pdq'],\n      ['z/', 'file:/ex/x/y/', 'file:/ex/x/y/z/'],\n      [\n        'file://meetings.example.com/cal#m1',\n        'file:/devel/WWW/2000/10/swap/test/reluri-1.n3',\n        'file://meetings.example.com/cal#m1',\n      ],\n      [\n        'file://meetings.example.com/cal#m1',\n        'file:/home/connolly/w3ccvs/WWW/2000/10/swap/test/reluri-1.n3',\n        'file://meetings.example.com/cal#m1',\n      ],\n      ['./#blort', 'file:/some/dir/foo', 'file:/some/dir/#blort'],\n      ['./#', 'file:/some/dir/foo', 'file:/some/dir/#'],\n      // Ryan Lee\n      ['./', 'http://example/x/abc.efg', 'http://example/x/'],\n\n      // Graham Klyne's tests\n      // http://www.ninebynine.org/Software/HaskellUtils/Network/UriTest.xls\n      // 01-31 are from Connelly's cases\n\n      // 32-49\n      ['./q:r', 'http://ex/x/y', 'http://ex/x/q:r'],\n      ['./p=q:r', 'http://ex/x/y', 'http://ex/x/p=q:r'],\n      ['?pp/rr', 'http://ex/x/y?pp/qq', 'http://ex/x/y?pp/rr'],\n      ['y/z', 'http://ex/x/y?pp/qq', 'http://ex/x/y/z'],\n      [\n        'local/qual@domain.org#frag',\n        'mailto:local',\n        'mailto:local/qual@domain.org#frag',\n      ],\n      [\n        'more/qual2@domain2.org#frag',\n        'mailto:local/qual1@domain1.org',\n        'mailto:local/more/qual2@domain2.org#frag',\n      ],\n      ['y?q', 'http://ex/x/y?q', 'http://ex/x/y?q'],\n      ['/x/y?q', 'http://ex?p', 'http://ex/x/y?q'],\n      ['c/d', 'foo:a/b', 'foo:a/c/d'],\n      ['/c/d', 'foo:a/b', 'foo:/c/d'],\n      ['', 'foo:a/b?c#d', 'foo:a/b?c'],\n      ['b/c', 'foo:a', 'foo:b/c'],\n      ['../b/c', 'foo:/a/y/z', 'foo:/a/b/c'],\n      ['./b/c', 'foo:a', 'foo:b/c'],\n      ['/./b/c', 'foo:a', 'foo:/b/c'],\n      ['../../d', 'foo://a//b/c', 'foo://a/d'],\n      ['.', 'foo:a', 'foo:'],\n      ['..', 'foo:a', 'foo:'],\n\n      // 50-57[cf. TimBL comments --\n      //  http://lists.w3.org/Archives/Public/uri/2003Feb/0028.html,\n      //  http://lists.w3.org/Archives/Public/uri/2003Jan/0008.html)\n      ['abc', 'http://example/x/y%2Fz', 'http://example/x/abc'],\n      ['../../x%2Fabc', 'http://example/a/x/y/z', 'http://example/a/x%2Fabc'],\n      ['../x%2Fabc', 'http://example/a/x/y%2Fz', 'http://example/a/x%2Fabc'],\n      ['abc', 'http://example/x%2Fy/z', 'http://example/x%2Fy/abc'],\n      ['q%3Ar', 'http://ex/x/y', 'http://ex/x/q%3Ar'],\n      ['/x%2Fabc', 'http://example/x/y%2Fz', 'http://example/x%2Fabc'],\n      ['/x%2Fabc', 'http://example/x/y/z', 'http://example/x%2Fabc'],\n      ['/x%2Fabc', 'http://example/x/y%2Fz', 'http://example/x%2Fabc'],\n\n      // 70-77\n      [\n        'local2@domain2',\n        'mailto:local1@domain1?query1',\n        'mailto:local2@domain2',\n      ],\n      [\n        'local2@domain2?query2',\n        'mailto:local1@domain1',\n        'mailto:local2@domain2?query2',\n      ],\n      [\n        'local2@domain2?query2',\n        'mailto:local1@domain1?query1',\n        'mailto:local2@domain2?query2',\n      ],\n      ['?query2', 'mailto:local@domain?query1', 'mailto:local@domain?query2'],\n      ['local@domain?query2', 'mailto:?query1', 'mailto:local@domain?query2'],\n      ['?query2', 'mailto:local@domain?query1', 'mailto:local@domain?query2'],\n      ['http://example/a/b?c/../d', 'foo:bar', 'http://example/a/b?c/../d'],\n      ['http://example/a/b#c/../d', 'foo:bar', 'http://example/a/b#c/../d'],\n\n      // 82-88\n      // @isaacs Disagree. Not how browsers do it.\n      // ['http:this', 'http://example.org/base/uri', 'http:this'],\n      // @isaacs Added\n      [\n        'http:this',\n        'http://example.org/base/uri',\n        'http://example.org/base/this',\n      ],\n      ['http:this', 'http:base', 'http:this'],\n      ['.//g', 'f:/a', 'f://g'],\n      ['b/c//d/e', 'f://example.org/base/a', 'f://example.org/base/b/c//d/e'],\n      [\n        'm2@example.ord/c2@example.org',\n        'mid:m@example.ord/c@example.org',\n        'mid:m@example.ord/m2@example.ord/c2@example.org',\n      ],\n      [\n        'mini1.xml',\n        'file:///C:/DEV/Haskell/lib/HXmlToolbox-3.01/examples/',\n        'file:///C:/DEV/Haskell/lib/HXmlToolbox-3.01/examples/mini1.xml',\n      ],\n      ['../b/c', 'foo:a/y/z', 'foo:a/b/c'],\n\n      // changing auth\n      [\n        'http://diff:auth@www.example.com',\n        'http://asdf:qwer@www.example.com',\n        'http://diff:auth@www.example.com/',\n      ],\n\n      // changing port\n      [\n        'https://example.com:81/',\n        'https://example.com:82/',\n        'https://example.com:81/',\n      ],\n\n      // https://github.com/nodejs/node/issues/1435\n      [\n        'https://another.host.com/',\n        'https://user:password@example.org/',\n        'https://another.host.com/',\n      ],\n      [\n        '//another.host.com/',\n        'https://user:password@example.org/',\n        'https://another.host.com/',\n      ],\n      [\n        'http://another.host.com/',\n        'https://user:password@example.org/',\n        'http://another.host.com/',\n      ],\n      [\n        'mailto:another.host.com',\n        'mailto:user@example.org',\n        'mailto:another.host.com',\n      ],\n      [\n        'https://example.com/foo',\n        'https://user:password@example.com',\n        'https://user:password@example.com/foo',\n      ],\n\n      // No path at all\n      ['#hash1', '#hash2', '#hash1'],\n    ];\n    for (let i = 0; i < relativeTests2.length; i++) {\n      const relativeTest = relativeTests2[i];\n\n      const a = url.resolve(relativeTest[1], relativeTest[0]);\n      const e = url.format(relativeTest[2]);\n      assert.strictEqual(\n        a,\n        e,\n        `resolve(${relativeTest[0]}, ${relativeTest[1]})` +\n          ` == ${e}\\n  actual=${a}`\n      );\n    }\n\n    // If format and parse are inverse operations then\n    // resolveObject(parse(x), y) == parse(resolve(x, y))\n\n    // format: [from, path, expected]\n    for (let i = 0; i < relativeTests.length; i++) {\n      const relativeTest = relativeTests[i];\n\n      let actual = url.resolveObject(\n        url.parse(relativeTest[0]),\n        relativeTest[1]\n      );\n      let expected = url.parse(relativeTest[2]);\n\n      assert.deepStrictEqual(actual, expected);\n\n      expected = relativeTest[2];\n      actual = url.format(actual);\n\n      assert.strictEqual(\n        actual,\n        expected,\n        `format(${actual}) == ${expected}\\n` + `actual: ${actual}`\n      );\n    }\n\n    // format: [to, from, result]\n    // the test: ['.//g', 'f:/a', 'f://g'] is a fundamental problem\n    // url.parse('f:/a') does not have a host\n    // url.resolve('f:/a', './/g') does not have a host because you have moved\n    // down to the g directory.  i.e. f:     //g, however when this url is parsed\n    // f:// will indicate that the host is g which is not the case.\n    // it is unclear to me how to keep this information from being lost\n    // it may be that a pathname of ////g should collapse to /g but this seems\n    // to be a lot of work for an edge case.  Right now I remove the test\n    if (\n      relativeTests2[181][0] === './/g' &&\n      relativeTests2[181][1] === 'f:/a' &&\n      relativeTests2[181][2] === 'f://g'\n    ) {\n      relativeTests2.splice(181, 1);\n    }\n    for (let i = 0; i < relativeTests2.length; i++) {\n      const relativeTest = relativeTests2[i];\n\n      let actual = url.resolveObject(\n        url.parse(relativeTest[1]),\n        relativeTest[0]\n      );\n      let expected = url.parse(relativeTest[2]);\n\n      assert.deepStrictEqual(\n        actual,\n        expected,\n        `expected ${inspect(expected)} but got ${inspect(actual)}`\n      );\n\n      expected = url.format(relativeTest[2]);\n      actual = url.format(actual);\n\n      assert.strictEqual(\n        actual,\n        expected,\n        `format(${relativeTest[1]}) == ${expected}\\n` + `actual: ${actual}`\n      );\n    }\n  },\n};\n\n// Ref: https://github.com/nodejs/node/blob/e92446536ed4e268c9eef6ae6f911e384c98eecf/test/parallel/test-url-urltooptions.js\nexport const urlToHttpOptions = {\n  async test() {\n    // Test urlToHttpOptions\n    const urlObj = new URL('http://user:pass@foo.bar.com:21/aaa/zzz?l=24#test');\n    const opts = url.urlToHttpOptions(urlObj);\n    assert.strictEqual(opts instanceof URL, false);\n    assert.strictEqual(opts.protocol, 'http:');\n    assert.strictEqual(opts.auth, 'user:pass');\n    assert.strictEqual(opts.hostname, 'foo.bar.com');\n    assert.strictEqual(opts.port, 21);\n    assert.strictEqual(opts.path, '/aaa/zzz?l=24');\n    assert.strictEqual(opts.pathname, '/aaa/zzz');\n    assert.strictEqual(opts.search, '?l=24');\n    assert.strictEqual(opts.hash, '#test');\n\n    const { hostname } = url.urlToHttpOptions(new URL('http://[::1]:21'));\n    assert.strictEqual(hostname, '::1');\n\n    // If a WHATWG URL object is copied, it is possible that the resulting copy\n    // contains the Symbols that Node uses for brand checking, but not the data\n    // properties, which are getters. Verify that urlToHttpOptions() can handle\n    // such a case.\n    const copiedUrlObj = { ...urlObj };\n    const copiedOpts = url.urlToHttpOptions(copiedUrlObj);\n    assert.strictEqual(copiedOpts instanceof URL, false);\n    assert.strictEqual(copiedOpts.protocol, undefined);\n    assert.strictEqual(copiedOpts.auth, undefined);\n    assert.strictEqual(copiedOpts.hostname, undefined);\n    assert.strictEqual(copiedOpts.port, NaN);\n    assert.strictEqual(copiedOpts.path, '');\n    assert.strictEqual(copiedOpts.pathname, undefined);\n    assert.strictEqual(copiedOpts.search, undefined);\n    assert.strictEqual(copiedOpts.hash, undefined);\n    assert.strictEqual(copiedOpts.href, undefined);\n  },\n};\n\n// Ref: https://github.com/nodejs/node/blob/e92446536ed4e268c9eef6ae6f911e384c98eecf/test/parallel/test-url-format-whatwg.js\nexport const formatWhatwg = {\n  async test() {\n    const myURL = new URL(\n      'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c'\n    );\n\n    // should format\n    assert.strictEqual(\n      url.format(myURL),\n      'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, {}),\n      'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c'\n    );\n\n    // handle invalid arguments\n    for (const value of [true, 1, 'test', Infinity]) {\n      assert.throws(() => url.format(myURL, value), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n      });\n    }\n\n    // any falsy value other than undefined will be treated as false\n    assert.strictEqual(\n      url.format(myURL, { auth: false }),\n      'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, { auth: '' }),\n      'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, { auth: 0 }),\n      'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, { auth: 1 }),\n      'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, { auth: {} }),\n      'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, { fragment: false }),\n      'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, { fragment: '' }),\n      'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, { fragment: 0 }),\n      'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, { fragment: 1 }),\n      'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, { fragment: {} }),\n      'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, { search: false }),\n      'http://user:pass@xn--lck1c3crb1723bpq4a.com/a#c'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, { search: '' }),\n      'http://user:pass@xn--lck1c3crb1723bpq4a.com/a#c'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, { search: 0 }),\n      'http://user:pass@xn--lck1c3crb1723bpq4a.com/a#c'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, { search: 1 }),\n      'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, { search: {} }),\n      'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, { unicode: true }),\n      'http://user:pass@理容ナカムラ.com/a?a=b#c'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, { unicode: 1 }),\n      'http://user:pass@理容ナカムラ.com/a?a=b#c'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, { unicode: {} }),\n      'http://user:pass@理容ナカムラ.com/a?a=b#c'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, { unicode: false }),\n      'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c'\n    );\n\n    assert.strictEqual(\n      url.format(myURL, { unicode: 0 }),\n      'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c'\n    );\n\n    // should format with unicode: true\n    assert.strictEqual(\n      url.format(new URL('http://user:pass@xn--0zwm56d.com:8080/path'), {\n        unicode: true,\n      }),\n      'http://user:pass@测试.com:8080/path'\n    );\n\n    // should format tel: prefix\n    assert.strictEqual(\n      url.format(new URL('tel:123')),\n      url.format(new URL('tel:123'), { unicode: true })\n    );\n  },\n};\n\n// Ref: https://github.com/nodejs/node/blob/e92446536ed4e268c9eef6ae6f911e384c98eecf/test/parallel/test-url-parse-query.js\nexport const urlParseQuery = {\n  async test() {\n    function createWithNoPrototype(properties = []) {\n      const noProto = { __proto__: null };\n      properties.forEach((property) => {\n        noProto[property.key] = property.value;\n      });\n      return noProto;\n    }\n\n    function check(actual, expected) {\n      assert.notStrictEqual(Object.getPrototypeOf(actual), Object.prototype);\n      assert.deepStrictEqual(\n        Object.keys(actual).sort(),\n        Object.keys(expected).sort()\n      );\n      Object.keys(expected).forEach(function (key) {\n        assert.deepStrictEqual(actual[key], expected[key]);\n      });\n    }\n\n    const parseTestsWithQueryString = {\n      '/foo/bar?baz=quux#frag': {\n        href: '/foo/bar?baz=quux#frag',\n        hash: '#frag',\n        search: '?baz=quux',\n        query: createWithNoPrototype([{ key: 'baz', value: 'quux' }]),\n        pathname: '/foo/bar',\n        path: '/foo/bar?baz=quux',\n      },\n      'http://example.com': {\n        href: 'http://example.com/',\n        protocol: 'http:',\n        slashes: true,\n        host: 'example.com',\n        hostname: 'example.com',\n        query: createWithNoPrototype(),\n        search: null,\n        pathname: '/',\n        path: '/',\n      },\n      '/example': {\n        protocol: null,\n        slashes: null,\n        auth: undefined,\n        host: null,\n        port: null,\n        hostname: null,\n        hash: null,\n        search: null,\n        query: createWithNoPrototype(),\n        pathname: '/example',\n        path: '/example',\n        href: '/example',\n      },\n      '/example?query=value': {\n        protocol: null,\n        slashes: null,\n        auth: undefined,\n        host: null,\n        port: null,\n        hostname: null,\n        hash: null,\n        search: '?query=value',\n        query: createWithNoPrototype([{ key: 'query', value: 'value' }]),\n        pathname: '/example',\n        path: '/example?query=value',\n        href: '/example?query=value',\n      },\n    };\n    for (const u in parseTestsWithQueryString) {\n      const actual = url.parse(u, true);\n      const expected = Object.assign(\n        new url.Url(),\n        parseTestsWithQueryString[u]\n      );\n      for (const i in actual) {\n        if (actual[i] === null && expected[i] === undefined) {\n          expected[i] = null;\n        }\n      }\n\n      const properties = Object.keys(actual).sort();\n      assert.deepStrictEqual(properties, Object.keys(expected).sort());\n      properties.forEach((property) => {\n        if (property === 'query') {\n          check(actual[property], expected[property]);\n        } else {\n          assert.deepStrictEqual(actual[property], expected[property]);\n        }\n      });\n    }\n  },\n};\n\n// Ref: https://github.com/nodejs/node/blob/e92446536ed4e268c9eef6ae6f911e384c98eecf/test/parallel/test-url-parse-format.js\nexport const urlParseFormat = {\n  async test() {\n    // URLs to parse, and expected data\n    // { url : parsed }\n    const parseTests = {\n      '//some_path': {\n        href: '//some_path',\n        pathname: '//some_path',\n        path: '//some_path',\n      },\n\n      'http:\\\\\\\\evil-phisher\\\\foo.html#h\\\\a\\\\s\\\\h': {\n        protocol: 'http:',\n        slashes: true,\n        host: 'evil-phisher',\n        hostname: 'evil-phisher',\n        pathname: '/foo.html',\n        path: '/foo.html',\n        hash: '#h%5Ca%5Cs%5Ch',\n        href: 'http://evil-phisher/foo.html#h%5Ca%5Cs%5Ch',\n      },\n\n      'http:\\\\\\\\evil-phisher\\\\foo.html?json=\"\\\\\"foo\\\\\"\"#h\\\\a\\\\s\\\\h': {\n        protocol: 'http:',\n        slashes: true,\n        host: 'evil-phisher',\n        hostname: 'evil-phisher',\n        pathname: '/foo.html',\n        search: '?json=%22%5C%22foo%5C%22%22',\n        query: 'json=%22%5C%22foo%5C%22%22',\n        path: '/foo.html?json=%22%5C%22foo%5C%22%22',\n        hash: '#h%5Ca%5Cs%5Ch',\n        href: 'http://evil-phisher/foo.html?json=%22%5C%22foo%5C%22%22#h%5Ca%5Cs%5Ch',\n      },\n\n      'http:\\\\\\\\evil-phisher\\\\foo.html#h\\\\a\\\\s\\\\h?blarg': {\n        protocol: 'http:',\n        slashes: true,\n        host: 'evil-phisher',\n        hostname: 'evil-phisher',\n        pathname: '/foo.html',\n        path: '/foo.html',\n        hash: '#h%5Ca%5Cs%5Ch?blarg',\n        href: 'http://evil-phisher/foo.html#h%5Ca%5Cs%5Ch?blarg',\n      },\n\n      'http:\\\\\\\\evil-phisher\\\\foo.html': {\n        protocol: 'http:',\n        slashes: true,\n        host: 'evil-phisher',\n        hostname: 'evil-phisher',\n        pathname: '/foo.html',\n        path: '/foo.html',\n        href: 'http://evil-phisher/foo.html',\n      },\n\n      'HTTP://www.example.com/': {\n        href: 'http://www.example.com/',\n        protocol: 'http:',\n        slashes: true,\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        pathname: '/',\n        path: '/',\n      },\n\n      'HTTP://www.example.com': {\n        href: 'http://www.example.com/',\n        protocol: 'http:',\n        slashes: true,\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        pathname: '/',\n        path: '/',\n      },\n\n      'http://www.ExAmPlE.com/': {\n        href: 'http://www.example.com/',\n        protocol: 'http:',\n        slashes: true,\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        pathname: '/',\n        path: '/',\n      },\n\n      'http://user:pw@www.ExAmPlE.com/': {\n        href: 'http://user:pw@www.example.com/',\n        protocol: 'http:',\n        slashes: true,\n        auth: 'user:pw',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        pathname: '/',\n        path: '/',\n      },\n\n      'http://USER:PW@www.ExAmPlE.com/': {\n        href: 'http://USER:PW@www.example.com/',\n        protocol: 'http:',\n        slashes: true,\n        auth: 'USER:PW',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        pathname: '/',\n        path: '/',\n      },\n\n      'http://user@www.example.com/': {\n        href: 'http://user@www.example.com/',\n        protocol: 'http:',\n        slashes: true,\n        auth: 'user',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        pathname: '/',\n        path: '/',\n      },\n\n      'http://user%3Apw@www.example.com/': {\n        href: 'http://user:pw@www.example.com/',\n        protocol: 'http:',\n        slashes: true,\n        auth: 'user:pw',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        pathname: '/',\n        path: '/',\n      },\n\n      \"http://x.com/path?that's#all, folks\": {\n        href: 'http://x.com/path?that%27s#all,%20folks',\n        protocol: 'http:',\n        slashes: true,\n        host: 'x.com',\n        hostname: 'x.com',\n        search: '?that%27s',\n        query: 'that%27s',\n        pathname: '/path',\n        hash: '#all,%20folks',\n        path: '/path?that%27s',\n      },\n\n      'HTTP://X.COM/Y': {\n        href: 'http://x.com/Y',\n        protocol: 'http:',\n        slashes: true,\n        host: 'x.com',\n        hostname: 'x.com',\n        pathname: '/Y',\n        path: '/Y',\n      },\n\n      // Whitespace in the front\n      ' http://www.example.com/': {\n        href: 'http://www.example.com/',\n        protocol: 'http:',\n        slashes: true,\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        pathname: '/',\n        path: '/',\n      },\n\n      // + not an invalid host character\n      // per https://url.spec.whatwg.org/#host-parsing\n      'http://x.y.com+a/b/c': {\n        href: 'http://x.y.com+a/b/c',\n        protocol: 'http:',\n        slashes: true,\n        host: 'x.y.com+a',\n        hostname: 'x.y.com+a',\n        pathname: '/b/c',\n        path: '/b/c',\n      },\n\n      // An unexpected invalid char in the hostname.\n      'HtTp://x.y.cOm;a/b/c?d=e#f g<h>i': {\n        href: 'http://x.y.com/;a/b/c?d=e#f%20g%3Ch%3Ei',\n        protocol: 'http:',\n        slashes: true,\n        host: 'x.y.com',\n        hostname: 'x.y.com',\n        pathname: ';a/b/c',\n        search: '?d=e',\n        query: 'd=e',\n        hash: '#f%20g%3Ch%3Ei',\n        path: ';a/b/c?d=e',\n      },\n\n      // Make sure that we don't accidentally lcast the path parts.\n      'HtTp://x.y.cOm;A/b/c?d=e#f g<h>i': {\n        href: 'http://x.y.com/;A/b/c?d=e#f%20g%3Ch%3Ei',\n        protocol: 'http:',\n        slashes: true,\n        host: 'x.y.com',\n        hostname: 'x.y.com',\n        pathname: ';A/b/c',\n        search: '?d=e',\n        query: 'd=e',\n        hash: '#f%20g%3Ch%3Ei',\n        path: ';A/b/c?d=e',\n      },\n\n      'http://x...y...#p': {\n        href: 'http://x...y.../#p',\n        protocol: 'http:',\n        slashes: true,\n        host: 'x...y...',\n        hostname: 'x...y...',\n        hash: '#p',\n        pathname: '/',\n        path: '/',\n      },\n\n      'http://x/p/\"quoted\"': {\n        href: 'http://x/p/%22quoted%22',\n        protocol: 'http:',\n        slashes: true,\n        host: 'x',\n        hostname: 'x',\n        pathname: '/p/%22quoted%22',\n        path: '/p/%22quoted%22',\n      },\n\n      '<http://goo.corn/bread> Is a URL!': {\n        href: '%3Chttp://goo.corn/bread%3E%20Is%20a%20URL!',\n        pathname: '%3Chttp://goo.corn/bread%3E%20Is%20a%20URL!',\n        path: '%3Chttp://goo.corn/bread%3E%20Is%20a%20URL!',\n      },\n\n      'http://www.narwhaljs.org/blog/categories?id=news': {\n        href: 'http://www.narwhaljs.org/blog/categories?id=news',\n        protocol: 'http:',\n        slashes: true,\n        host: 'www.narwhaljs.org',\n        hostname: 'www.narwhaljs.org',\n        search: '?id=news',\n        query: 'id=news',\n        pathname: '/blog/categories',\n        path: '/blog/categories?id=news',\n      },\n\n      'http://mt0.google.com/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=': {\n        href: 'http://mt0.google.com/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=',\n        protocol: 'http:',\n        slashes: true,\n        host: 'mt0.google.com',\n        hostname: 'mt0.google.com',\n        pathname: '/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=',\n        path: '/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=',\n      },\n\n      'http://mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=': {\n        href:\n          'http://mt0.google.com/vt/lyrs=m@114???&hl=en&src=api' +\n          '&x=2&y=2&z=3&s=',\n        protocol: 'http:',\n        slashes: true,\n        host: 'mt0.google.com',\n        hostname: 'mt0.google.com',\n        search: '???&hl=en&src=api&x=2&y=2&z=3&s=',\n        query: '??&hl=en&src=api&x=2&y=2&z=3&s=',\n        pathname: '/vt/lyrs=m@114',\n        path: '/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=',\n      },\n\n      'http://user:pass@mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=':\n        {\n          href: 'http://user:pass@mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=',\n          protocol: 'http:',\n          slashes: true,\n          host: 'mt0.google.com',\n          auth: 'user:pass',\n          hostname: 'mt0.google.com',\n          search: '???&hl=en&src=api&x=2&y=2&z=3&s=',\n          query: '??&hl=en&src=api&x=2&y=2&z=3&s=',\n          pathname: '/vt/lyrs=m@114',\n          path: '/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=',\n        },\n\n      'file:///etc/passwd': {\n        href: 'file:///etc/passwd',\n        slashes: true,\n        protocol: 'file:',\n        pathname: '/etc/passwd',\n        hostname: '',\n        host: '',\n        path: '/etc/passwd',\n      },\n\n      'file://localhost/etc/passwd': {\n        href: 'file://localhost/etc/passwd',\n        protocol: 'file:',\n        slashes: true,\n        pathname: '/etc/passwd',\n        hostname: 'localhost',\n        host: 'localhost',\n        path: '/etc/passwd',\n      },\n\n      'file://foo/etc/passwd': {\n        href: 'file://foo/etc/passwd',\n        protocol: 'file:',\n        slashes: true,\n        pathname: '/etc/passwd',\n        hostname: 'foo',\n        host: 'foo',\n        path: '/etc/passwd',\n      },\n\n      'file:///etc/node/': {\n        href: 'file:///etc/node/',\n        slashes: true,\n        protocol: 'file:',\n        pathname: '/etc/node/',\n        hostname: '',\n        host: '',\n        path: '/etc/node/',\n      },\n\n      'file://localhost/etc/node/': {\n        href: 'file://localhost/etc/node/',\n        protocol: 'file:',\n        slashes: true,\n        pathname: '/etc/node/',\n        hostname: 'localhost',\n        host: 'localhost',\n        path: '/etc/node/',\n      },\n\n      'file://foo/etc/node/': {\n        href: 'file://foo/etc/node/',\n        protocol: 'file:',\n        slashes: true,\n        pathname: '/etc/node/',\n        hostname: 'foo',\n        host: 'foo',\n        path: '/etc/node/',\n      },\n\n      'http:/baz/../foo/bar': {\n        href: 'http:/baz/../foo/bar',\n        protocol: 'http:',\n        pathname: '/baz/../foo/bar',\n        path: '/baz/../foo/bar',\n      },\n\n      'http://user:pass@example.com:8000/foo/bar?baz=quux#frag': {\n        href: 'http://user:pass@example.com:8000/foo/bar?baz=quux#frag',\n        protocol: 'http:',\n        slashes: true,\n        host: 'example.com:8000',\n        auth: 'user:pass',\n        port: '8000',\n        hostname: 'example.com',\n        hash: '#frag',\n        search: '?baz=quux',\n        query: 'baz=quux',\n        pathname: '/foo/bar',\n        path: '/foo/bar?baz=quux',\n      },\n\n      '//user:pass@example.com:8000/foo/bar?baz=quux#frag': {\n        href: '//user:pass@example.com:8000/foo/bar?baz=quux#frag',\n        slashes: true,\n        host: 'example.com:8000',\n        auth: 'user:pass',\n        port: '8000',\n        hostname: 'example.com',\n        hash: '#frag',\n        search: '?baz=quux',\n        query: 'baz=quux',\n        pathname: '/foo/bar',\n        path: '/foo/bar?baz=quux',\n      },\n\n      '/foo/bar?baz=quux#frag': {\n        href: '/foo/bar?baz=quux#frag',\n        hash: '#frag',\n        search: '?baz=quux',\n        query: 'baz=quux',\n        pathname: '/foo/bar',\n        path: '/foo/bar?baz=quux',\n      },\n\n      'http:/foo/bar?baz=quux#frag': {\n        href: 'http:/foo/bar?baz=quux#frag',\n        protocol: 'http:',\n        hash: '#frag',\n        search: '?baz=quux',\n        query: 'baz=quux',\n        pathname: '/foo/bar',\n        path: '/foo/bar?baz=quux',\n      },\n\n      'mailto:foo@bar.com?subject=hello': {\n        href: 'mailto:foo@bar.com?subject=hello',\n        protocol: 'mailto:',\n        host: 'bar.com',\n        auth: 'foo',\n        hostname: 'bar.com',\n        search: '?subject=hello',\n        query: 'subject=hello',\n        path: '?subject=hello',\n      },\n\n      \"javascript:alert('hello');\": {\n        href: \"javascript:alert('hello');\",\n        protocol: 'javascript:',\n        pathname: \"alert('hello');\",\n        path: \"alert('hello');\",\n      },\n\n      'xmpp:isaacschlueter@jabber.org': {\n        href: 'xmpp:isaacschlueter@jabber.org',\n        protocol: 'xmpp:',\n        host: 'jabber.org',\n        auth: 'isaacschlueter',\n        hostname: 'jabber.org',\n      },\n\n      'http://atpass:foo%40bar@127.0.0.1:8080/path?search=foo#bar': {\n        href: 'http://atpass:foo%40bar@127.0.0.1:8080/path?search=foo#bar',\n        protocol: 'http:',\n        slashes: true,\n        host: '127.0.0.1:8080',\n        auth: 'atpass:foo@bar',\n        hostname: '127.0.0.1',\n        port: '8080',\n        pathname: '/path',\n        search: '?search=foo',\n        query: 'search=foo',\n        hash: '#bar',\n        path: '/path?search=foo',\n      },\n\n      'svn+ssh://foo/bar': {\n        href: 'svn+ssh://foo/bar',\n        host: 'foo',\n        hostname: 'foo',\n        protocol: 'svn+ssh:',\n        pathname: '/bar',\n        path: '/bar',\n        slashes: true,\n      },\n\n      'dash-test://foo/bar': {\n        href: 'dash-test://foo/bar',\n        host: 'foo',\n        hostname: 'foo',\n        protocol: 'dash-test:',\n        pathname: '/bar',\n        path: '/bar',\n        slashes: true,\n      },\n\n      'dash-test:foo/bar': {\n        href: 'dash-test:foo/bar',\n        host: 'foo',\n        hostname: 'foo',\n        protocol: 'dash-test:',\n        pathname: '/bar',\n        path: '/bar',\n      },\n\n      'dot.test://foo/bar': {\n        href: 'dot.test://foo/bar',\n        host: 'foo',\n        hostname: 'foo',\n        protocol: 'dot.test:',\n        pathname: '/bar',\n        path: '/bar',\n        slashes: true,\n      },\n\n      'dot.test:foo/bar': {\n        href: 'dot.test:foo/bar',\n        host: 'foo',\n        hostname: 'foo',\n        protocol: 'dot.test:',\n        pathname: '/bar',\n        path: '/bar',\n      },\n\n      // IDNA tests\n      'http://www.日本語.com/': {\n        href: 'http://www.xn--wgv71a119e.com/',\n        protocol: 'http:',\n        slashes: true,\n        host: 'www.xn--wgv71a119e.com',\n        hostname: 'www.xn--wgv71a119e.com',\n        pathname: '/',\n        path: '/',\n      },\n\n      'http://example.Bücher.com/': {\n        href: 'http://example.xn--bcher-kva.com/',\n        protocol: 'http:',\n        slashes: true,\n        host: 'example.xn--bcher-kva.com',\n        hostname: 'example.xn--bcher-kva.com',\n        pathname: '/',\n        path: '/',\n      },\n\n      'http://www.Äffchen.com/': {\n        href: 'http://www.xn--ffchen-9ta.com/',\n        protocol: 'http:',\n        slashes: true,\n        host: 'www.xn--ffchen-9ta.com',\n        hostname: 'www.xn--ffchen-9ta.com',\n        pathname: '/',\n        path: '/',\n      },\n\n      'http://www.Äffchen.cOm;A/b/c?d=e#f g<h>i': {\n        href: 'http://www.xn--ffchen-9ta.com/;A/b/c?d=e#f%20g%3Ch%3Ei',\n        protocol: 'http:',\n        slashes: true,\n        host: 'www.xn--ffchen-9ta.com',\n        hostname: 'www.xn--ffchen-9ta.com',\n        pathname: ';A/b/c',\n        search: '?d=e',\n        query: 'd=e',\n        hash: '#f%20g%3Ch%3Ei',\n        path: ';A/b/c?d=e',\n      },\n\n      'http://SÉLIER.COM/': {\n        href: 'http://xn--slier-bsa.com/',\n        protocol: 'http:',\n        slashes: true,\n        host: 'xn--slier-bsa.com',\n        hostname: 'xn--slier-bsa.com',\n        pathname: '/',\n        path: '/',\n      },\n\n      'http://ليهمابتكلموشعربي؟.ي؟/': {\n        href: 'http://xn--egbpdaj6bu4bxfgehfvwxn.xn--egb9f/',\n        protocol: 'http:',\n        slashes: true,\n        host: 'xn--egbpdaj6bu4bxfgehfvwxn.xn--egb9f',\n        hostname: 'xn--egbpdaj6bu4bxfgehfvwxn.xn--egb9f',\n        pathname: '/',\n        path: '/',\n      },\n\n      'http://➡.ws/➡': {\n        href: 'http://xn--hgi.ws/➡',\n        protocol: 'http:',\n        slashes: true,\n        host: 'xn--hgi.ws',\n        hostname: 'xn--hgi.ws',\n        pathname: '/➡',\n        path: '/➡',\n      },\n\n      'http://bucket_name.s3.amazonaws.com/image.jpg': {\n        protocol: 'http:',\n        slashes: true,\n        host: 'bucket_name.s3.amazonaws.com',\n        hostname: 'bucket_name.s3.amazonaws.com',\n        pathname: '/image.jpg',\n        href: 'http://bucket_name.s3.amazonaws.com/image.jpg',\n        path: '/image.jpg',\n      },\n\n      'git+http://github.com/joyent/node.git': {\n        protocol: 'git+http:',\n        slashes: true,\n        host: 'github.com',\n        hostname: 'github.com',\n        pathname: '/joyent/node.git',\n        path: '/joyent/node.git',\n        href: 'git+http://github.com/joyent/node.git',\n      },\n\n      // If local1@domain1 is uses as a relative URL it may\n      // be parse into auth@hostname, but here there is no\n      // way to make it work in url.parse, I add the test to be explicit\n      'local1@domain1': {\n        pathname: 'local1@domain1',\n        path: 'local1@domain1',\n        href: 'local1@domain1',\n      },\n\n      // While this may seem counter-intuitive, a browser will parse\n      // <a href='www.google.com'> as a path.\n      'www.example.com': {\n        href: 'www.example.com',\n        pathname: 'www.example.com',\n        path: 'www.example.com',\n      },\n\n      // ipv6 support\n      '[fe80::1]': {\n        href: '[fe80::1]',\n        pathname: '[fe80::1]',\n        path: '[fe80::1]',\n      },\n\n      'coap://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]': {\n        protocol: 'coap:',\n        slashes: true,\n        host: '[fedc:ba98:7654:3210:fedc:ba98:7654:3210]',\n        hostname: 'fedc:ba98:7654:3210:fedc:ba98:7654:3210',\n        href: 'coap://[fedc:ba98:7654:3210:fedc:ba98:7654:3210]/',\n        pathname: '/',\n        path: '/',\n      },\n\n      'coap://[1080:0:0:0:8:800:200C:417A]:61616/': {\n        protocol: 'coap:',\n        slashes: true,\n        host: '[1080:0:0:0:8:800:200c:417a]:61616',\n        port: '61616',\n        hostname: '1080:0:0:0:8:800:200c:417a',\n        href: 'coap://[1080:0:0:0:8:800:200c:417a]:61616/',\n        pathname: '/',\n        path: '/',\n      },\n\n      'http://user:password@[3ffe:2a00:100:7031::1]:8080': {\n        protocol: 'http:',\n        slashes: true,\n        auth: 'user:password',\n        host: '[3ffe:2a00:100:7031::1]:8080',\n        port: '8080',\n        hostname: '3ffe:2a00:100:7031::1',\n        href: 'http://user:password@[3ffe:2a00:100:7031::1]:8080/',\n        pathname: '/',\n        path: '/',\n      },\n\n      'coap://u:p@[::192.9.5.5]:61616/.well-known/r?n=Temperature': {\n        protocol: 'coap:',\n        slashes: true,\n        auth: 'u:p',\n        host: '[::192.9.5.5]:61616',\n        port: '61616',\n        hostname: '::192.9.5.5',\n        href: 'coap://u:p@[::192.9.5.5]:61616/.well-known/r?n=Temperature',\n        search: '?n=Temperature',\n        query: 'n=Temperature',\n        pathname: '/.well-known/r',\n        path: '/.well-known/r?n=Temperature',\n      },\n\n      // empty port\n      'http://example.com:': {\n        protocol: 'http:',\n        slashes: true,\n        host: 'example.com',\n        hostname: 'example.com',\n        href: 'http://example.com/',\n        pathname: '/',\n        path: '/',\n      },\n\n      'http://example.com:/a/b.html': {\n        protocol: 'http:',\n        slashes: true,\n        host: 'example.com',\n        hostname: 'example.com',\n        href: 'http://example.com/a/b.html',\n        pathname: '/a/b.html',\n        path: '/a/b.html',\n      },\n\n      'http://example.com:?a=b': {\n        protocol: 'http:',\n        slashes: true,\n        host: 'example.com',\n        hostname: 'example.com',\n        href: 'http://example.com/?a=b',\n        search: '?a=b',\n        query: 'a=b',\n        pathname: '/',\n        path: '/?a=b',\n      },\n\n      'http://example.com:#abc': {\n        protocol: 'http:',\n        slashes: true,\n        host: 'example.com',\n        hostname: 'example.com',\n        href: 'http://example.com/#abc',\n        hash: '#abc',\n        pathname: '/',\n        path: '/',\n      },\n\n      'http://[fe80::1]:/a/b?a=b#abc': {\n        protocol: 'http:',\n        slashes: true,\n        host: '[fe80::1]',\n        hostname: 'fe80::1',\n        href: 'http://[fe80::1]/a/b?a=b#abc',\n        search: '?a=b',\n        query: 'a=b',\n        hash: '#abc',\n        pathname: '/a/b',\n        path: '/a/b?a=b',\n      },\n\n      'http://-lovemonsterz.tumblr.com/rss': {\n        protocol: 'http:',\n        slashes: true,\n        host: '-lovemonsterz.tumblr.com',\n        hostname: '-lovemonsterz.tumblr.com',\n        href: 'http://-lovemonsterz.tumblr.com/rss',\n        pathname: '/rss',\n        path: '/rss',\n      },\n\n      'http://-lovemonsterz.tumblr.com:80/rss': {\n        protocol: 'http:',\n        slashes: true,\n        port: '80',\n        host: '-lovemonsterz.tumblr.com:80',\n        hostname: '-lovemonsterz.tumblr.com',\n        href: 'http://-lovemonsterz.tumblr.com:80/rss',\n        pathname: '/rss',\n        path: '/rss',\n      },\n\n      'http://user:pass@-lovemonsterz.tumblr.com/rss': {\n        protocol: 'http:',\n        slashes: true,\n        auth: 'user:pass',\n        host: '-lovemonsterz.tumblr.com',\n        hostname: '-lovemonsterz.tumblr.com',\n        href: 'http://user:pass@-lovemonsterz.tumblr.com/rss',\n        pathname: '/rss',\n        path: '/rss',\n      },\n\n      'http://user:pass@-lovemonsterz.tumblr.com:80/rss': {\n        protocol: 'http:',\n        slashes: true,\n        auth: 'user:pass',\n        port: '80',\n        host: '-lovemonsterz.tumblr.com:80',\n        hostname: '-lovemonsterz.tumblr.com',\n        href: 'http://user:pass@-lovemonsterz.tumblr.com:80/rss',\n        pathname: '/rss',\n        path: '/rss',\n      },\n\n      'http://_jabber._tcp.google.com/test': {\n        protocol: 'http:',\n        slashes: true,\n        host: '_jabber._tcp.google.com',\n        hostname: '_jabber._tcp.google.com',\n        href: 'http://_jabber._tcp.google.com/test',\n        pathname: '/test',\n        path: '/test',\n      },\n\n      'http://user:pass@_jabber._tcp.google.com/test': {\n        protocol: 'http:',\n        slashes: true,\n        auth: 'user:pass',\n        host: '_jabber._tcp.google.com',\n        hostname: '_jabber._tcp.google.com',\n        href: 'http://user:pass@_jabber._tcp.google.com/test',\n        pathname: '/test',\n        path: '/test',\n      },\n\n      'http://_jabber._tcp.google.com:80/test': {\n        protocol: 'http:',\n        slashes: true,\n        port: '80',\n        host: '_jabber._tcp.google.com:80',\n        hostname: '_jabber._tcp.google.com',\n        href: 'http://_jabber._tcp.google.com:80/test',\n        pathname: '/test',\n        path: '/test',\n      },\n\n      'http://user:pass@_jabber._tcp.google.com:80/test': {\n        protocol: 'http:',\n        slashes: true,\n        auth: 'user:pass',\n        port: '80',\n        host: '_jabber._tcp.google.com:80',\n        hostname: '_jabber._tcp.google.com',\n        href: 'http://user:pass@_jabber._tcp.google.com:80/test',\n        pathname: '/test',\n        path: '/test',\n      },\n\n      'http://x:1/\\' <>\"`/{}|\\\\^~`/': {\n        protocol: 'http:',\n        slashes: true,\n        host: 'x:1',\n        port: '1',\n        hostname: 'x',\n        pathname: '/%27%20%3C%3E%22%60/%7B%7D%7C/%5E~%60/',\n        path: '/%27%20%3C%3E%22%60/%7B%7D%7C/%5E~%60/',\n        href: 'http://x:1/%27%20%3C%3E%22%60/%7B%7D%7C/%5E~%60/',\n      },\n\n      'http://a@b@c/': {\n        protocol: 'http:',\n        slashes: true,\n        auth: 'a@b',\n        host: 'c',\n        hostname: 'c',\n        href: 'http://a%40b@c/',\n        path: '/',\n        pathname: '/',\n      },\n\n      'http://a@b?@c': {\n        protocol: 'http:',\n        slashes: true,\n        auth: 'a',\n        host: 'b',\n        hostname: 'b',\n        href: 'http://a@b/?@c',\n        path: '/?@c',\n        pathname: '/',\n        search: '?@c',\n        query: '@c',\n      },\n\n      'http://a.b/\\tbc\\ndr\\ref g\"hq\\'j<kl>?mn\\\\op^q=r`99{st|uv}wz': {\n        protocol: 'http:',\n        slashes: true,\n        host: 'a.b',\n        port: null,\n        hostname: 'a.b',\n        hash: null,\n        pathname: '/%09bc%0Adr%0Def%20g%22hq%27j%3Ckl%3E',\n        path: '/%09bc%0Adr%0Def%20g%22hq%27j%3Ckl%3E?mn%5Cop%5Eq=r%6099%7Bst%7Cuv%7Dwz',\n        search: '?mn%5Cop%5Eq=r%6099%7Bst%7Cuv%7Dwz',\n        query: 'mn%5Cop%5Eq=r%6099%7Bst%7Cuv%7Dwz',\n        href: 'http://a.b/%09bc%0Adr%0Def%20g%22hq%27j%3Ckl%3E?mn%5Cop%5Eq=r%6099%7Bst%7Cuv%7Dwz',\n      },\n\n      'http://a\\r\" \\t\\n<\\'b:b@c\\r\\nd/e?f': {\n        protocol: 'http:',\n        slashes: true,\n        auth: 'a\" <\\'b:b',\n        host: 'cd',\n        port: null,\n        hostname: 'cd',\n        hash: null,\n        search: '?f',\n        query: 'f',\n        pathname: '/e',\n        path: '/e?f',\n        href: \"http://a%22%20%3C'b:b@cd/e?f\",\n      },\n\n      // Git urls used by npm\n      'git+ssh://git@github.com:npm/npm': {\n        protocol: 'git+ssh:',\n        slashes: true,\n        auth: 'git',\n        host: 'github.com',\n        port: null,\n        hostname: 'github.com',\n        hash: null,\n        search: null,\n        query: null,\n        pathname: '/:npm/npm',\n        path: '/:npm/npm',\n        href: 'git+ssh://git@github.com/:npm/npm',\n      },\n\n      'https://*': {\n        protocol: 'https:',\n        slashes: true,\n        auth: null,\n        host: '*',\n        port: null,\n        hostname: '*',\n        hash: null,\n        search: null,\n        query: null,\n        pathname: '/',\n        path: '/',\n        href: 'https://*/',\n      },\n\n      // The following two URLs are the same, but they differ for a capital A.\n      // Verify that the protocol is checked in a case-insensitive manner.\n      'javascript:alert(1);a=\\x27@white-listed.com\\x27': {\n        protocol: 'javascript:',\n        slashes: null,\n        auth: null,\n        host: null,\n        port: null,\n        hostname: null,\n        hash: null,\n        search: null,\n        query: null,\n        pathname: \"alert(1);a='@white-listed.com'\",\n        path: \"alert(1);a='@white-listed.com'\",\n        href: \"javascript:alert(1);a='@white-listed.com'\",\n      },\n\n      'javAscript:alert(1);a=\\x27@white-listed.com\\x27': {\n        protocol: 'javascript:',\n        slashes: null,\n        auth: null,\n        host: null,\n        port: null,\n        hostname: null,\n        hash: null,\n        search: null,\n        query: null,\n        pathname: \"alert(1);a='@white-listed.com'\",\n        path: \"alert(1);a='@white-listed.com'\",\n        href: \"javascript:alert(1);a='@white-listed.com'\",\n      },\n\n      'ws://www.example.com': {\n        protocol: 'ws:',\n        slashes: true,\n        hostname: 'www.example.com',\n        host: 'www.example.com',\n        pathname: '/',\n        path: '/',\n        href: 'ws://www.example.com/',\n      },\n\n      'wss://www.example.com': {\n        protocol: 'wss:',\n        slashes: true,\n        hostname: 'www.example.com',\n        host: 'www.example.com',\n        pathname: '/',\n        path: '/',\n        href: 'wss://www.example.com/',\n      },\n\n      '//fhqwhgads@example.com/everybody-to-the-limit': {\n        protocol: null,\n        slashes: true,\n        auth: 'fhqwhgads',\n        host: 'example.com',\n        port: null,\n        hostname: 'example.com',\n        hash: null,\n        search: null,\n        query: null,\n        pathname: '/everybody-to-the-limit',\n        path: '/everybody-to-the-limit',\n        href: '//fhqwhgads@example.com/everybody-to-the-limit',\n      },\n\n      '//fhqwhgads@example.com/everybody#to-the-limit': {\n        protocol: null,\n        slashes: true,\n        auth: 'fhqwhgads',\n        host: 'example.com',\n        port: null,\n        hostname: 'example.com',\n        hash: '#to-the-limit',\n        search: null,\n        query: null,\n        pathname: '/everybody',\n        path: '/everybody',\n        href: '//fhqwhgads@example.com/everybody#to-the-limit',\n      },\n\n      '\\bhttp://example.com/\\b': {\n        protocol: 'http:',\n        slashes: true,\n        auth: null,\n        host: 'example.com',\n        port: null,\n        hostname: 'example.com',\n        hash: null,\n        search: null,\n        query: null,\n        pathname: '/',\n        path: '/',\n        href: 'http://example.com/',\n      },\n\n      'https://evil.com$.example.com': {\n        protocol: 'https:',\n        slashes: true,\n        auth: null,\n        host: 'evil.com$.example.com',\n        port: null,\n        hostname: 'evil.com$.example.com',\n        hash: null,\n        search: null,\n        query: null,\n        pathname: '/',\n        path: '/',\n        href: 'https://evil.com$.example.com/',\n      },\n\n      // Validate the output of hostname with commas.\n      'x://0.0,1.1/': {\n        protocol: 'x:',\n        slashes: true,\n        auth: null,\n        host: '0.0,1.1',\n        port: null,\n        hostname: '0.0,1.1',\n        hash: null,\n        search: null,\n        query: null,\n        pathname: '/',\n        path: '/',\n        href: 'x://0.0,1.1/',\n      },\n    };\n\n    // should parse and format\n    {\n      for (const u in parseTests) {\n        let actual = url.parse(u);\n        const spaced = url.parse(`     \\t  ${u}\\n\\t`);\n        let expected = Object.assign(new url.Url(), parseTests[u]);\n\n        Object.keys(actual).forEach(function (i) {\n          if (expected[i] === undefined && actual[i] === null) {\n            expected[i] = null;\n          }\n        });\n\n        assert.deepStrictEqual(\n          actual,\n          expected,\n          `parsing ${u} and expected ${inspect(expected)} but got ${inspect(actual)}`\n        );\n        assert.deepStrictEqual(\n          spaced,\n          expected,\n          `expected ${inspect(expected)}, got ${inspect(spaced)}`\n        );\n\n        expected = parseTests[u].href;\n        actual = url.format(parseTests[u]);\n\n        assert.strictEqual(\n          actual,\n          expected,\n          `format(${u}) == ${u}\\nactual:${actual}`\n        );\n      }\n    }\n\n    // parse result should equal new url.Url()\n    {\n      const parsed = url\n        .parse('http://nodejs.org/')\n        .resolveObject('jAvascript:alert(1);a=\\x27@white-listed.com\\x27');\n\n      const expected = Object.assign(new url.Url(), {\n        protocol: 'javascript:',\n        slashes: null,\n        auth: null,\n        host: null,\n        port: null,\n        hostname: null,\n        hash: null,\n        search: null,\n        query: null,\n        pathname: \"alert(1);a='@white-listed.com'\",\n        path: \"alert(1);a='@white-listed.com'\",\n        href: \"javascript:alert(1);a='@white-listed.com'\",\n      });\n\n      assert.deepStrictEqual(parsed, expected);\n    }\n  },\n};\n\n// Ref: https://github.com/nodejs/node/blob/e92446536ed4e268c9eef6ae6f911e384c98eecf/test/parallel/test-url-parse-invalid-input.js\nexport const urlParseInvalidInput = {\n  async test() {\n    // https://github.com/joyent/node/issues/568\n    [\n      [undefined, 'undefined'],\n      [null, 'object'],\n      [true, 'boolean'],\n      [false, 'boolean'],\n      [0.0, 'number'],\n      [0, 'number'],\n      [[], 'object'],\n      [{}, 'object'],\n      [() => {}, 'function'],\n      [Symbol('foo'), 'symbol'],\n    ].forEach(([val, type]) => {\n      assert.throws(\n        () => {\n          url.parse(val);\n        },\n        {\n          code: 'ERR_INVALID_ARG_TYPE',\n          name: 'TypeError',\n        }\n      );\n    });\n\n    assert.throws(\n      () => {\n        url.parse('http://%E0%A4%A@fail');\n      },\n      (e) => {\n        // The error should be a URIError.\n        if (!(e instanceof URIError)) return false;\n\n        // The error should be from the JS engine and not from Node.js.\n        // JS engine errors do not have the `code` property.\n        return e.code === undefined;\n      }\n    );\n\n    assert.throws(\n      () => {\n        url.parse('http://[127.0.0.1\\x00c8763]:8000/');\n      },\n      { code: 'ERR_INVALID_URL', input: 'http://[127.0.0.1\\x00c8763]:8000/' }\n    );\n\n    {\n      // An array of Unicode code points whose Unicode NFKD contains a \"bad\n      // character\".\n      const badIDNA = (() => {\n        const BAD_CHARS = '#%/:?@[\\\\]^|';\n        const out = [];\n        for (let i = 0x80; i < 0x110000; i++) {\n          const cp = String.fromCodePoint(i);\n          for (const badChar of BAD_CHARS) {\n            if (cp.normalize('NFKD').includes(badChar)) {\n              out.push(cp);\n            }\n          }\n        }\n        return out;\n      })();\n\n      // The generation logic above should at a minimum produce these two\n      // characters.\n      assert(badIDNA.includes('℀'));\n      assert(badIDNA.includes('＠'));\n\n      for (const badCodePoint of badIDNA) {\n        const badURL = `http://fail${badCodePoint}fail.com/`;\n        assert.throws(\n          () => {\n            url.parse(badURL);\n          },\n          (e) => e.code === 'ERR_INVALID_URL',\n          `parsing ${badURL}`\n        );\n      }\n\n      assert.throws(\n        () => {\n          url.parse('http://\\u00AD/bad.com/');\n        },\n        (e) => e.code === 'ERR_INVALID_URL',\n        'parsing http://\\u00AD/bad.com/'\n      );\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/legacy_url-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"legacy_url-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"legacy_url-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"url_standard\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/mimetype-test.js",
    "content": "import { deepStrictEqual, ok, strictEqual } from 'node:assert';\n\nimport { MIMEType } from 'node:util';\n\nexport const test_ok = {\n  test() {\n    const mt = new MIMEType('TeXt/PlAiN; ChArsEt=\"utf-8\"');\n    strictEqual(mt.type, 'text');\n    strictEqual(mt.subtype, 'plain');\n    strictEqual(mt.essence, 'text/plain');\n    strictEqual(mt.toString(), 'text/plain;charset=utf-8');\n    strictEqual(JSON.stringify(mt), '\"text/plain;charset=utf-8\"');\n    strictEqual(mt.params.get('charset'), 'utf-8');\n    ok(mt.params.has('charset'));\n    ok(!mt.params.has('boundary'));\n    mt.params.set('boundary', 'foo');\n    ok(mt.params.has('boundary'));\n    strictEqual(mt.toString(), 'text/plain;charset=utf-8; boundary=foo');\n\n    deepStrictEqual(Array.from(mt.params), [\n      ['charset', 'utf-8'],\n      ['boundary', 'foo'],\n    ]);\n\n    deepStrictEqual(Array.from(mt.params.keys()), ['charset', 'boundary']);\n\n    deepStrictEqual(Array.from(mt.params.values()), ['utf-8', 'foo']);\n\n    mt.params.delete('boundary');\n    ok(!mt.params.has('boundary'));\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/mimetype-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"mimetype-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"mimetype-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/module-create-require-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport { createRequire, isBuiltin, builtinModules } from 'node:module';\nimport { ok, strictEqual, throws } from 'node:assert';\n\nexport const doTheTest = {\n  async test() {\n    const require = createRequire('/');\n    ok(typeof require === 'function');\n\n    const foo = require('foo');\n    const bar = require('bar');\n    const baz = require('baz');\n    const qux = require('worker/qux');\n\n    // When require_returns_default_export flag is enabled, require() returns the\n    // default export directly. Otherwise it returns the namespace object.\n    if (Cloudflare.compatibilityFlags.require_returns_default_export) {\n      strictEqual(foo, 1);\n    } else {\n      strictEqual(foo.default, 1);\n    }\n    strictEqual(bar, 2);\n    strictEqual(baz, 3);\n    strictEqual(qux, '4');\n\n    const assert = await import('node:assert');\n    const required = require('node:assert');\n\n    // When require_returns_default_export flag is enabled, require() returns the\n    // default export directly (assert.default === required).\n    // When the flag is disabled, require() returns the namespace (assert === required).\n    if (Cloudflare.compatibilityFlags.require_returns_default_export) {\n      strictEqual(assert.default, required);\n    } else {\n      strictEqual(assert, required);\n    }\n\n    throws(() => require('invalid'), {\n      message: 'Top-level await in module is not permitted at this time.',\n    });\n    // Trying to require the module again should throw the same error.\n    throws(() => require('invalid'), {\n      message: 'Top-level await in module is not permitted at this time.',\n    });\n    throws(() => require('invalid2'), {\n      message: 'Top-level await in module is not permitted at this time.',\n    });\n\n    throws(() => require('does not exist'));\n    throws(() => createRequire('not a valid path'), {\n      message: /The argument must be a file URL object/,\n    });\n    throws(() => createRequire(new URL('http://example.org')), {\n      message: /The argument must be a file URL object/,\n    });\n\n    // TODO(soon): Later when we when complete the new module registry, query strings\n    // and hash fragments will be allowed when the new registry is being used.\n    throws(() => createRequire('file://test?abc'), {\n      message:\n        'The specifier must not have query string parameters or hash fragments.',\n    });\n    throws(() => createRequire('file://test#123'), {\n      message:\n        'The specifier must not have query string parameters or hash fragments.',\n    });\n\n    // These should not throw...\n    createRequire('file:///');\n    createRequire('file:///tmp');\n    createRequire(new URL('file:///'));\n  },\n};\n\nexport const isBuiltinTest = {\n  test() {\n    ok(isBuiltin('fs'));\n    ok(isBuiltin('http'));\n    ok(isBuiltin('https'));\n    ok(isBuiltin('path'));\n    ok(isBuiltin('node:fs'));\n    ok(isBuiltin('node:http'));\n    ok(isBuiltin('node:https'));\n    ok(isBuiltin('node:path'));\n    ok(isBuiltin('node:test'));\n    ok(!isBuiltin('test'));\n    ok(!isBuiltin('worker'));\n    ok(!isBuiltin('worker/qux'));\n\n    builtinModules.forEach((module) => {\n      ok(isBuiltin(module));\n    });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/module-create-require-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"module-create-require-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"module-create-require-test.js\"),\n          (name = \"foo\", esModule = \"export default 1;\"),\n          (name = \"bar\", esModule = \"export default 2; export const __cjsUnwrapDefault = true;\"),\n          (name = \"baz\", commonJsModule = \"module.exports = 3;\"),\n          (name = \"worker/qux\", text = \"4\"),\n          (name = \"invalid\", esModule = \"await new Promise(() => {});\"),\n          (name = \"invalid2\", commonJsModule = \"require('invalid3');\"),\n          (name = \"invalid3\", esModule = \"await new Promise(() => {});\"),\n        ],\n        compatibilityFlags = [\"disable_top_level_await_in_require\", \"nodejs_compat_v2\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/module-nodejs-test.js",
    "content": "import module from 'node:module';\nimport { ok, deepStrictEqual } from 'node:assert';\n\nexport const testUnimplemented = {\n  async test() {\n    const fields = [\n      '_cache',\n      '_pathCache',\n      '_extensions',\n      'globalPaths',\n      '_debug',\n      'isBuiltin',\n      '_findPath',\n      '_nodeModulePaths',\n      '_resolveLookupPaths',\n      '_load',\n      '_resolveFilename',\n      'createRequire',\n      '_initPaths',\n      '_preloadModules',\n      'syncBuiltinESMExports',\n      'Module',\n      'registerHooks',\n      'builtinModules',\n      'runMain',\n      'register',\n      'constants',\n      'enableCompileCache',\n      'findPackageJSON',\n      'flushCompileCache',\n      'stripTypeScriptTypes',\n      'getCompileCacheDir',\n      'findSourceMap',\n      'SourceMap',\n      'getSourceMapsSupport',\n      'setSourceMapsSupport',\n    ];\n\n    for (const field of fields) {\n      ok(field in module, `${field} is not in module`);\n      ok(module[field] != null, `${field} is ${typeof module[field]}`);\n    }\n\n    deepStrictEqual(Object.keys(module.Module.prototype), [\n      'load',\n      'require',\n      '_compile',\n    ]);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/module-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"module-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"module-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/module-require-mutable-exports-test.js",
    "content": "// Copyright (c) 2017-2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport { createRequire } from 'node:module';\nimport { strictEqual, notStrictEqual, ok, doesNotThrow } from 'node:assert';\n\nconst require = createRequire('/');\n\nexport const testTimersPromisesMutable = {\n  async test() {\n    const timersPromises = require('node:timers/promises');\n    const originalSetImmediate = timersPromises.setImmediate;\n    ok(typeof originalSetImmediate === 'function');\n\n    const patchedSetImmediate = async function patchedSetImmediate() {\n      return 'patched';\n    };\n\n    doesNotThrow(() => {\n      timersPromises.setImmediate = patchedSetImmediate;\n    });\n\n    strictEqual(timersPromises.setImmediate, patchedSetImmediate);\n    timersPromises.setImmediate = originalSetImmediate;\n    strictEqual(timersPromises.setImmediate, originalSetImmediate);\n\n    // require() should return the same object as import { default }\n    const { default: timersPromises2 } = await import('node:timers/promises');\n    strictEqual(timersPromises, timersPromises2);\n\n    // But import() returns the namespace object, not the default export\n    const timersPromisesNamespace = await import('node:timers/promises');\n    notStrictEqual(timersPromises, timersPromisesNamespace);\n    strictEqual(timersPromisesNamespace.default, timersPromises);\n  },\n};\n\n// Test that patching the require()'d object doesn't affect the original named exports\nexport const testPatchingDoesNotAffectNamedExports = {\n  async test() {\n    const timersPromises = require('node:timers/promises');\n    const originalSetTimeout = timersPromises.setTimeout;\n\n    // Patch the require()'d object\n    const patchedSetTimeout = async function patched() {\n      return 'patched';\n    };\n    timersPromises.setTimeout = patchedSetTimeout;\n\n    // The named export from the module namespace should be unaffected\n    const { setTimeout: namedSetTimeout } =\n      await import('node:timers/promises');\n    notStrictEqual(namedSetTimeout, patchedSetTimeout);\n    strictEqual(namedSetTimeout, originalSetTimeout);\n\n    // Restore\n    timersPromises.setTimeout = originalSetTimeout;\n  },\n};\n\nexport const testTimersMutable = {\n  test() {\n    const timers = require('node:timers');\n    const originalSetTimeout = timers.setTimeout;\n    ok(typeof originalSetTimeout === 'function');\n\n    const patchedSetTimeout = function patchedSetTimeout() {\n      return 'patched';\n    };\n\n    doesNotThrow(() => {\n      timers.setTimeout = patchedSetTimeout;\n    });\n\n    strictEqual(timers.setTimeout, patchedSetTimeout);\n    timers.setTimeout = originalSetTimeout;\n  },\n};\n\nexport const testBufferMutable = {\n  test() {\n    const buffer = require('node:buffer');\n    const originalBuffer = buffer.Buffer;\n    ok(typeof originalBuffer === 'function');\n\n    const patchedBuffer = function PatchedBuffer() {\n      return 'patched';\n    };\n\n    doesNotThrow(() => {\n      buffer.Buffer = patchedBuffer;\n    });\n\n    strictEqual(buffer.Buffer, patchedBuffer);\n    buffer.Buffer = originalBuffer;\n  },\n};\n\nexport const testUtilMutable = {\n  test() {\n    const util = require('node:util');\n    const originalPromisify = util.promisify;\n    ok(typeof originalPromisify === 'function');\n\n    const patchedPromisify = function patchedPromisify() {\n      return 'patched';\n    };\n\n    doesNotThrow(() => {\n      util.promisify = patchedPromisify;\n    });\n\n    strictEqual(util.promisify, patchedPromisify);\n    util.promisify = originalPromisify;\n  },\n};\n\nexport const testRequireCachesMutableObject = {\n  test() {\n    const timersPromises1 = require('node:timers/promises');\n    const timersPromises2 = require('node:timers/promises');\n\n    strictEqual(timersPromises1, timersPromises2);\n\n    const patchedSetImmediate = async function patched() {\n      return 'patched';\n    };\n    const original = timersPromises1.setImmediate;\n\n    timersPromises1.setImmediate = patchedSetImmediate;\n    strictEqual(timersPromises2.setImmediate, patchedSetImmediate);\n    timersPromises1.setImmediate = original;\n  },\n};\n\n// When require_returns_default_export is enabled, require() should return the\n// default export directly (which is the object with all the functions),\n// not the namespace wrapper with both `default` and named exports.\nexport const testRequireReturnsDefaultExport = {\n  test() {\n    const timers = require('node:timers');\n    // With require_returns_default_export enabled, timers should be the\n    // default export object directly, not the namespace wrapper.\n    // The default export IS the object with setTimeout, setInterval, etc.\n    ok(typeof timers.setTimeout === 'function');\n    ok(typeof timers.setInterval === 'function');\n    ok(typeof timers.clearTimeout === 'function');\n    ok(typeof timers.clearInterval === 'function');\n    // The namespace wrapper would have a 'default' property, but when\n    // we return the default export directly, there's no 'default' property\n    // on the returned object (unless the default export itself has one).\n    strictEqual(timers.default, undefined);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/module-require-mutable-exports-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"module-require-mutable-exports-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"module-require-mutable-exports-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"require_returns_default_export\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/net-nodejs-tcp-server.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// This file is used as a sidecar for the net-nodejs-test tests.\n// It creates 2 TCP servers to act as a source of truth for the node:net tests.\n// We execute this command using Node.js, which makes net.createServer available.\nconst net = require('node:net');\n\nfunction reportPort(server) {\n  const address = server.address();\n  console.info(`Listening on ${address.address}:${address.port}`);\n}\n\nconst server = net.createServer((s) => {\n  s.on('error', () => {\n    // Do nothing\n  });\n  s.end();\n});\nserver.listen(process.env.SERVER_PORT, process.env.SIDECAR_HOSTNAME, () =>\n  reportPort(server)\n);\n\nconst echoServer = net.createServer((s) => {\n  s.setTimeout(100);\n  s.on('error', () => {\n    // Do nothing\n  });\n  s.pipe(s);\n});\nechoServer.listen(\n  process.env.ECHO_SERVER_PORT,\n  process.env.SIDECAR_HOSTNAME,\n  () => reportPort(echoServer)\n);\n\nconst timeoutServer = net.createServer((s) => {\n  s.setTimeout(100);\n  s.resume();\n  s.once('timeout', () => {\n    // Try to reset the timeout.\n    s.write('WHAT.');\n  });\n\n  s.on('end', () => {\n    s.end();\n  });\n  s.on('error', () => {\n    // Do nothing\n  });\n});\ntimeoutServer.listen(\n  process.env.TIMEOUT_SERVER_PORT,\n  process.env.SIDECAR_HOSTNAME,\n  () => reportPort(timeoutServer)\n);\n\nconst endServer = net.createServer((s) => {\n  s.end();\n});\nendServer.listen(\n  process.env.END_SERVER_PORT,\n  process.env.SIDECAR_HOSTNAME,\n  () => reportPort(endServer)\n);\n\nlet count = 0;\nconst serverThatDies = net.createServer(function (s) {\n  // We ignore the first event because wd_test checks for the connected state\n  // while preparing the sidecar test suite.\n  if (count++ > 0) {\n    serverThatDies.close();\n  }\n  s.end();\n});\nserverThatDies.listen(\n  process.env.SERVER_THAT_DIES_PORT,\n  process.env.SIDECAR_HOSTNAME,\n  () => reportPort(serverThatDies)\n);\n\nconst reconnectServer = net.createServer((s) => {\n  s.resume();\n  s.on('error', () => {\n    // Do nothing\n  });\n  s.write('hello\\r\\n');\n  s.on('end', () => {\n    s.end();\n  });\n});\nreconnectServer.listen(\n  process.env.RECONNECT_SERVER_PORT,\n  process.env.SIDECAR_HOSTNAME,\n  () => reportPort(reconnectServer)\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/net-nodejs-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT ORs\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { fail, ok, strictEqual, deepStrictEqual, throws } from 'node:assert';\nimport { mock } from 'node:test';\nimport { once } from 'node:events';\nimport * as net from 'node:net';\nimport * as tls from 'node:tls';\n\nexport const checkPortsSetCorrectly = {\n  test(ctrl, env, ctx) {\n    const keys = [\n      'SIDECAR_HOSTNAME',\n      'SERVER_PORT',\n      'ECHO_SERVER_PORT',\n      'TIMEOUT_SERVER_PORT',\n      'END_SERVER_PORT',\n      'SERVER_THAT_DIES_PORT',\n      'RECONNECT_SERVER_PORT',\n    ];\n    for (const key of keys) {\n      strictEqual(typeof env[key], 'string');\n      ok(env[key].length > 0);\n    }\n  },\n};\n\n// test/parallel/test-net-access-byteswritten.js\nexport const testNetAccessBytesWritten = {\n  test() {\n    // Check that the bytesWritten getter doesn't crash if object isn't\n    // constructed.\n    strictEqual(net.Socket.prototype.bytesWritten, undefined);\n    strictEqual(\n      Object.getPrototypeOf(tls.TLSSocket).prototype.bytesWritten,\n      undefined\n    );\n    strictEqual(tls.TLSSocket.prototype.bytesWritten, undefined);\n  },\n};\n\n// test/parallel/test-net-after-close.js\nexport const testNetAfterClose = {\n  async test(ctrl, env, ctx) {\n    const { promise, resolve } = Promise.withResolvers();\n    const c = net.connect(Number(env.SERVER_PORT), env.SIDECAR_HOSTNAME);\n    c.resume();\n    c.on('close', () => resolve());\n    await promise;\n\n    // Calling functions / accessing properties of a closed socket should not throw\n    c.setNoDelay();\n    c.setKeepAlive();\n    c.bufferSize;\n    c.pause();\n    c.resume();\n    c.address();\n    c.remoteAddress;\n    c.remotePort;\n  },\n};\n\n// test/parallel/test-net-allow-half-open.js\nexport const testNetAllowHalfOpen = {\n  async test(ctrl, env, ctx) {\n    // Verify that the socket closes properly when the other end closes\n    // and allowHalfOpen is false.\n\n    const { promise, resolve } = Promise.withResolvers();\n    const c = net.connect(Number(env.SERVER_PORT), env.SIDECAR_HOSTNAME);\n    strictEqual(c.allowHalfOpen, false);\n    c.resume();\n\n    const endFn = mock.fn(() => {\n      queueMicrotask(() => {\n        ok(!c.destroyed);\n      });\n    });\n    const finishFn = mock.fn(() => {\n      ok(!c.destroyed);\n    });\n    const closeFn = mock.fn(resolve);\n    c.on('end', endFn);\n\n    // Even tho we're not writing anything, since the socket receives a\n    // EOS and allowHalfOpen is false, the socket should close both the\n    // readable and writable sides, meaning we should definitely get a\n    // finish event.\n    c.on('finish', finishFn);\n    c.on('close', closeFn);\n    await promise;\n    strictEqual(endFn.mock.callCount(), 1);\n    strictEqual(finishFn.mock.callCount(), 1);\n    strictEqual(closeFn.mock.callCount(), 1);\n  },\n};\n\n// test/parallel/test-net-better-error-messages-port-hostname.js\nexport const testNetBetterErrorMessagesPortHostname = {\n  async test() {\n    // This is intentionally not a completely faithful reproduction of the\n    // original test, as we don't have the ability to mock DNS lookups.\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const c = net.connect(0, 'invalid address');\n    c.on('connect', () => {\n      reject(new Error('should not connect'));\n    });\n    const errorFn = mock.fn((error) => {\n      // TODO(review): Currently our errors do not match the errors that\n      // Node.js produces. Do we need them to? Specifically, in a case like\n      // this, Node.js' error would have a meaningful `code` property along\n      // with `hostname` and `syscall` properties. We, instead, are passing\n      // along the underlying error returned from the internal Socket API.\n      try {\n        strictEqual(error.message, 'Specified address could not be parsed.');\n      } catch (err) {\n        console.log(err.message);\n      }\n      resolve();\n    });\n    c.on('error', errorFn);\n    await promise;\n    strictEqual(errorFn.mock.callCount(), 1);\n  },\n};\n\n// test/parallel/test-net-binary.js\nexport const testNetBinary = {\n  async test(ctrl, env, ctx) {\n    const { promise, resolve } = Promise.withResolvers();\n\n    // Connect to the echo server\n    const c = net.connect(env.ECHO_SERVER_PORT, env.SIDECAR_HOSTNAME);\n    c.setEncoding('latin1');\n    let result = '';\n    c.on('data', (chunk) => {\n      result += chunk;\n    });\n\n    let binaryString = '';\n    for (let i = 255; i >= 0; i--) {\n      c.write(String.fromCharCode(i), 'latin1');\n      binaryString += String.fromCharCode(i);\n    }\n    c.end();\n    c.on('close', () => {\n      resolve();\n    });\n    await promise;\n    strictEqual(result, binaryString);\n  },\n};\n\n// test/parallel/test-net-buffersize.js\nexport const testNetBuffersize = {\n  async test(ctrl, env, ctx) {\n    const { promise, resolve } = Promise.withResolvers();\n    const c = net.connect(env.SERVER_PORT, env.SIDECAR_HOSTNAME);\n    const finishFn = mock.fn(() => {\n      strictEqual(c.bufferSize, 0);\n      resolve();\n    });\n    c.on('finish', finishFn);\n\n    strictEqual(c.bufferSize, 0);\n    c.write('a');\n    c.end();\n    strictEqual(c.bufferSize, 1);\n    await promise;\n    strictEqual(finishFn.mock.callCount(), 1);\n  },\n};\n\n// test/parallel/test-net-bytes-read.js\nexport const testNetBytesRead = {\n  async test(ctrl, env, ctx) {\n    const { promise, resolve } = Promise.withResolvers();\n    // Connect to the echo server\n    const c = net.connect(env.ECHO_SERVER_PORT, env.SIDECAR_HOSTNAME);\n    c.resume();\n    c.write('hello');\n    c.end();\n    const endFn = mock.fn(() => {\n      strictEqual(c.bytesRead, 5);\n      resolve();\n    });\n    c.on('end', endFn);\n\n    await promise;\n\n    strictEqual(endFn.mock.callCount(), 1);\n  },\n};\n\nexport const testNetBytesStats = {\n  async test(ctrl, env) {\n    // This is intentionally not a completely faithful reproduction of the\n    // original test which checks the bytesRead on the server side.\n    // Connect to the echo server\n    const { promise, resolve } = Promise.withResolvers();\n    const c = net.connect(env.ECHO_SERVER_PORT, env.SIDECAR_HOSTNAME);\n    let bytesDelivered = 0;\n    c.on('data', (chunk) => (bytesDelivered += chunk.byteLength));\n    c.write('hello');\n    c.end();\n    const endFn = mock.fn(() => {\n      strictEqual(c.bytesWritten, 0);\n      strictEqual(bytesDelivered, 5);\n      strictEqual(c.bytesRead, 5);\n      resolve();\n    });\n    c.on('end', endFn);\n\n    await promise;\n    strictEqual(endFn.mock.callCount(), 1);\n  },\n};\n\n// test/parallel/test-net-bytes-written-large.js\nconst N = 10000000;\nexport const testNetBytesWrittenLargeVariant1 = {\n  async test(ctrl, env, ctx) {\n    const { promise, resolve } = Promise.withResolvers();\n    const c = net.connect(env.ECHO_SERVER_PORT, env.SIDECAR_HOSTNAME);\n    c.resume();\n\n    const writeFn = mock.fn(() => {\n      strictEqual(c.bytesWritten, N);\n      resolve();\n    });\n\n    c.end(Buffer.alloc(N), writeFn);\n\n    await promise;\n  },\n};\n\n// test/parallel/test-net-can-reset-timeout.js\nexport const testNetCanResetTimeout = {\n  async test(ctrl, env, ctx) {\n    const { promise, resolve } = Promise.withResolvers();\n    const c = net.connect(env.TIMEOUT_SERVER_PORT, env.SIDECAR_HOSTNAME);\n    const dataFn = mock.fn(() => {\n      c.end();\n    });\n    c.on('data', dataFn);\n    c.on('end', resolve);\n\n    await promise;\n\n    strictEqual(dataFn.mock.callCount(), 1);\n  },\n};\n\nasync function assertAbort(socket, testName) {\n  try {\n    await once(socket, 'close');\n    fail(`close ${testName} should have thrown`);\n  } catch (err) {\n    strictEqual(err.name, 'AbortError', err.message);\n  }\n}\n\n// test/parallel/test-net-connect-abort-controller.js\nexport const testNetConnectAbortControllerPostAbort = {\n  async test(ctrl, env, ctx) {\n    const ac = new AbortController();\n    const { signal } = ac;\n    const socket = net.connect({\n      host: env.SIDECAR_HOSTNAME,\n      port: env.SERVER_PORT,\n      signal,\n    });\n    ac.abort();\n    await assertAbort(socket, 'postAbort');\n  },\n};\n\nexport const testNetConnectAbortControllerPreAbort = {\n  async test(ctrl, env, ctx) {\n    const ac = new AbortController();\n    const { signal } = ac;\n    ac.abort();\n    const socket = net.connect({\n      host: env.SIDECAR_HOSTNAME,\n      port: env.SERVER_PORT,\n      signal,\n    });\n    await assertAbort(socket, 'preAbort');\n  },\n};\n\nexport const testNetConnectAbortControllerTickAbort = {\n  async test(ctrl, env, ctx) {\n    const ac = new AbortController();\n    const { signal } = ac;\n    queueMicrotask(() => ac.abort());\n    const socket = net.connect({\n      host: env.SIDECAR_HOSTNAME,\n      port: env.SERVER_PORT,\n      signal,\n    });\n    await assertAbort(socket, 'tickAbort');\n  },\n};\n\nexport const testNetConnectAbortControllerConstructor = {\n  async test() {\n    const ac = new AbortController();\n    const { signal } = ac;\n    ac.abort();\n    const socket = new net.Socket({ signal });\n    await assertAbort(socket, 'testConstructor');\n  },\n};\n\nexport const testNetConnectAbortControllerConstructorPost = {\n  async test() {\n    const ac = new AbortController();\n    const { signal } = ac;\n    const socket = new net.Socket({ signal });\n    ac.abort();\n    await assertAbort(socket, 'testConstructorPost');\n  },\n};\n\nexport const testNetConnectAbortControllerConstructorPostTick = {\n  async test() {\n    const ac = new AbortController();\n    const { signal } = ac;\n    queueMicrotask(() => ac.abort());\n    const socket = new net.Socket({ signal });\n    await assertAbort(socket, 'testConstructorPostTick');\n  },\n};\n\n// test/parallel/test-net-connect-after-destroy.js\nexport const testNetConnectAfterDestroy = {\n  async test() {\n    // Connect to something that we need to lookup, then delay\n    // the lookup so that the connect attempt happens after the\n    // destroy\n    const lookup = mock.fn((host, options, callback) => {\n      setTimeout(() => callback(null, 'localhost'), 100);\n    });\n    const c = net.connect({\n      port: 80,\n      host: 'example.org',\n      lookup,\n    });\n    c.on('connect', () => {\n      throw new Error('should not connect');\n    });\n    c.destroy();\n    strictEqual(c.destroyed, true);\n    strictEqual(lookup.mock.callCount(), 1);\n  },\n};\n\n// test/parallel/test-net-connect-buffer.js\nexport const testNetConnectBuffer = {\n  async test(ctrl, env, ctx) {\n    const { promise, resolve } = Promise.withResolvers();\n    const c = net.connect({\n      port: env.ECHO_SERVER_PORT,\n      host: env.SIDECAR_HOSTNAME,\n      highWaterMark: 0,\n    });\n\n    strictEqual(c.pending, true);\n    strictEqual(c.connecting, true);\n    strictEqual(c.readyState, 'opening');\n    strictEqual(c.bytesWritten, 0);\n\n    // Write a string that contains a multi-byte character sequence to test that\n    // `bytesWritten` is incremented with the # of bytes, not # of characters.\n    const a = \"L'État, c'est \";\n    const b = 'moi';\n\n    let result = '';\n    c.setEncoding('utf8');\n    c.on('data', (chunk) => {\n      result += chunk;\n    });\n    const endFn = mock.fn(() => {\n      strictEqual(result, a + b);\n    });\n    c.on('end', endFn);\n\n    const writeFn = mock.fn(() => {\n      strictEqual(c.pending, false);\n      strictEqual(c.connecting, false);\n      strictEqual(c.readyState, 'readOnly');\n      strictEqual(c.bytesWritten, Buffer.from(a + b).length);\n    });\n    c.write(a, writeFn);\n\n    const closeFn = mock.fn(() => {\n      resolve();\n    });\n    c.on('close', closeFn);\n\n    c.end(b);\n\n    await promise;\n    strictEqual(closeFn.mock.callCount(), 1);\n    strictEqual(writeFn.mock.callCount(), 1);\n    strictEqual(endFn.mock.callCount(), 1);\n  },\n};\n\n// test/parallel/test-net-connect-destroy.js\nexport const testNetConnectDestroy = {\n  async test(ctrl, env, ctx) {\n    const { promise, resolve } = Promise.withResolvers();\n    const c = net.connect(env.SERVER_PORT, env.SIDECAR_HOSTNAME);\n    c.on('close', () => resolve());\n    c.destroy();\n    await promise;\n  },\n};\n\n// test/parallel/test-net-connect-immediate-destroy.js\nexport const testNetConnectImmediateDestroy = {\n  async test(ctrl, env, ctx) {\n    const connectFn = mock.fn();\n    const socket = net.connect(\n      env.SERVER_PORT,\n      env.SIDECAR_HOSTNAME,\n      connectFn\n    );\n    socket.destroy();\n    await Promise.resolve();\n    strictEqual(connectFn.mock.callCount(), 0);\n  },\n};\n\n// test/parallel/test-net-connect-immediate-finish.js\nexport const testNetConnectImmediateFinish = {\n  async text(ctrl, env, ctx) {\n    const { promise, resolve } = Promise.withResolvers();\n    const c = net.connect(env.SERVER_PORT, env.SIDECAR_HOSTNAME);\n    c.end();\n    c.on('finish', () => resolve());\n    await promise;\n  },\n};\n\n// test/parallel/test-net-connect-keepalive.js\n// test/parallel/test-net-keepalive.js\n// We don't actually support keep alive so this test does\n// something different than the original Node.js test\nexport const testNetConnectKeepAlive = {\n  async test() {\n    // We are not throwing on truthy keepAlive value for mysql/redis clients.\n    // throws(() => new net.Socket({ keepAlive: true }));\n    const c = new net.Socket();\n    c.setKeepAlive(false);\n    c.setKeepAlive(true);\n    // throws(() => c.setKeepAlive(true));\n  },\n};\n\n// test/parallel/test-net-connect-memleak.js\nexport const testNetConnectMemleak = {\n  async test(ctrl, env, ctx) {\n    const { promise, resolve } = Promise.withResolvers();\n    const connectFn = mock.fn(() => resolve());\n    const c = net.connect(env.SERVER_PORT, env.SIDECAR_HOSTNAME, connectFn);\n    c.emit('connect');\n    await promise;\n    strictEqual(connectFn.mock.callCount(), 1);\n  },\n};\n\n// test/parallel/test-net-connect-no-arg.js\nexport const testNetConnectNoArg = {\n  test() {\n    throws(() => net.connect(), {\n      code: 'ERR_MISSING_ARGS',\n      message: 'The \"options\" or \"port\" or \"path\" argument must be specified',\n    });\n    throws(() => new net.Socket().connect(), {\n      code: 'ERR_MISSING_ARGS',\n      message: 'The \"options\" or \"port\" or \"path\" argument must be specified',\n    });\n    throws(() => net.connect({}), {\n      code: 'ERR_MISSING_ARGS',\n      message: 'The \"options\" or \"port\" or \"path\" argument must be specified',\n    });\n    throws(() => new net.Socket().connect({}), {\n      code: 'ERR_MISSING_ARGS',\n      message: 'The \"options\" or \"port\" or \"path\" argument must be specified',\n    });\n  },\n};\n\n// test/parallel/test-net-connect-options-allowhalfopen.js\n// Simplified version of the equivalent Node.js test\nexport const testNetConnectOptionsAllowHalfOpen = {\n  async test(ctrl, env, ctx) {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const c = net.connect({\n      host: env.SIDECAR_HOSTNAME,\n      port: env.SERVER_PORT,\n      allowHalfOpen: true,\n    });\n    c.resume();\n    const writeFn = mock.fn(() => {\n      c.write('hello', (err) => {\n        if (err) reject(err);\n        resolve();\n      });\n    });\n    const endFn = mock.fn(() => {\n      strictEqual(c.readable, false);\n      strictEqual(c.writable, true);\n      queueMicrotask(writeFn);\n    });\n    c.on('end', endFn);\n    await promise;\n  },\n};\n\n// test/parallel/test-net-connect-options-fd.js\n// We do not support the fd option so this test does something different\n// than the original Node.js test\nexport const testNetConnectOptionsFd = {\n  async test() {\n    throws(() => new net.Socket({ fd: 42 }));\n  },\n};\n\n// test/parallel/test-net-connect-options-invalid.js\nexport const testNetConnectOptionsInvalid = {\n  test() {\n    ['objectMode', 'readableObjectMode', 'writableObjectMode'].forEach(\n      (invalidKey) => {\n        const option = {\n          port: 8080,\n          [invalidKey]: true,\n        };\n        const message = `The property 'options.${invalidKey}' is not supported. Received true`;\n\n        throws(() => net.connect(option), {\n          code: 'ERR_INVALID_ARG_VALUE',\n          name: 'TypeError',\n          message: new RegExp(message),\n        });\n      }\n    );\n  },\n};\n\n// test/parallel/test-net-connect-options-ipv6.js\n// TODO(soon): sidecar-supervisor assigns IPv4 addresses only. IPv6 support should be added.\n/*\nexport const testNetConnectOptionsIpv6 = {\n  async test(ctrl, env, ctx) {\n    const { promise, resolve } = Promise.withResolvers();\n    const c = net.connect({ host: '::1', port: env.SERVER_PORT });\n    c.on('connect', () => resolve());\n    await promise;\n  },\n};\n*/\n\n// test/parallel/test-net-connect-options-path.js\n// We do not support the path option so this test does something different\n// than the original Node.js test\nexport const testNetConnectOptionsPath = {\n  async test() {\n    throws(() => net.connect({ path: '/tmp/sock' }));\n  },\n};\n\n// test/parallel/test-net-connect-options-port.js\n// TODO(soon): Update this test.\nexport const testNetConnectOptionsPort = {\n  async test() {\n    [true, false].forEach((port) => {\n      throws(() => net.connect(port), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n      });\n    });\n    [-1, 65537].forEach((port) => {\n      throws(() => net.connect(port), {\n        code: 'ERR_SOCKET_BAD_PORT',\n      });\n    });\n  },\n};\n\n// test/parallel/test-net-connect-paused-connection.js\n// The original test is a bit different given that it uses unref to avoid\n// the paused connection from keeping the process alive. We don't have unref\n// so let's just make sure things clean up okay when the IoContext is destroyed.\nexport const testNetConnectPausedConnection = {\n  test(ctrl, env, ctx) {\n    net.connect(env.SERVER_PORT, env.SIDECAR_HOSTNAME).pause();\n  },\n};\n\n// test/parallel/test-net-connect-reset.js\nexport const testNetConnectReset = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n    const socket = new net.Socket();\n    socket.resetAndDestroy();\n    // Emit error if socket is not connecting/connected\n    socket.on('error', (err) => {\n      strictEqual(err.code, 'ERR_SOCKET_CLOSED');\n      strictEqual(err.name, 'Error');\n      resolve();\n    });\n    await promise;\n  },\n};\n\n// test/parallel/test-net-dns-custom-lookup.js\n// test/parallel/test-net-dns-lookup.js\nexport const testNetDnsCustomLookup = {\n  async test(ctrl, env, ctx) {\n    const { promise, resolve } = Promise.withResolvers();\n    const lookup = mock.fn((host, options, callback) => {\n      strictEqual(host, 'localhost');\n      strictEqual(options.family, 4);\n      queueMicrotask(() => callback(null, '127.0.0.1', 4));\n    });\n    const c = net.connect({\n      port: env.SERVER_PORT,\n      host: 'localhost',\n      lookup,\n      family: 4,\n    });\n    c.on('lookup', (err, ip, type) => {\n      strictEqual(err, null);\n      strictEqual(ip, '127.0.0.1');\n      strictEqual(type, 4);\n      resolve();\n    });\n    await promise;\n    strictEqual(lookup.mock.callCount(), 1);\n  },\n};\n\n// test/parallel/test-net-dns-error.js\n// This test differs from original Node.js test since our error code for isValidHost\n// is different from Node.js.\nexport const testNetDnsError = {\n  async test() {\n    const p1 = Promise.withResolvers();\n    const p2 = Promise.withResolvers();\n    const host = '*'.repeat(64);\n    const socket = net.connect(42, host, () =>\n      p1.reject(new Error('Should not have called'))\n    );\n    socket.on('error', function (err) {\n      strictEqual(err.name, 'TypeError');\n      strictEqual(\n        err.message,\n        'Specified address is empty string, contains unsupported characters or is too long.'\n      );\n      p1.resolve();\n    });\n    socket.on('lookup', function (err, ip, type) {\n      strictEqual(err.name, 'TypeError');\n      strictEqual(\n        err.message,\n        'Specified address is empty string, contains unsupported characters or is too long.'\n      );\n      strictEqual(ip, undefined);\n      strictEqual(type, undefined);\n      p2.resolve();\n    });\n    await Promise.all([p1, p2]);\n  },\n};\n\n// test/parallel/test-net-dns-lookup-skip.js\nexport const testNetDnsLookupSkip = {\n  async test(ctrl, env, ctx) {\n    const lookup = mock.fn();\n    [env.SIDECAR_HOSTNAME, '::1'].forEach((host) => {\n      net.connect({ host, port: env.SERVER_PORT, lookup }).destroy();\n    });\n    strictEqual(lookup.mock.callCount(), 0);\n  },\n};\n\n// test/parallel/test-net-during-close.js\nexport const testNetDuringClose = {\n  test(ctrl, env, ctx) {\n    const c = net.connect(env.SERVER_PORT, env.SIDECAR_HOSTNAME);\n    c.destroy();\n    c.remoteAddress;\n    c.remoteFamily;\n    c.remotePort;\n  },\n};\n\n// test/parallel/test-net-end-destroyed.js\nexport const testNetEndDestroyed = {\n  async test(ctrl, env, ctx) {\n    const { promise, resolve } = Promise.withResolvers();\n    const c = net.connect(env.SERVER_PORT, env.SIDECAR_HOSTNAME);\n    c.resume();\n\n    const endFn = mock.fn(() => {\n      strictEqual(c.destroyed, false);\n      resolve();\n    });\n    c.on('end', endFn);\n    await promise;\n  },\n};\n\n// test/parallel/test-net-end-without-connect.js\nexport const testNetEndWithoutConnect = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n    const c = new net.Socket();\n    const endFn = mock.fn(() => {\n      strictEqual(c.writable, false);\n      resolve();\n    });\n    c.end(endFn);\n    await promise;\n  },\n};\n\n// test/parallel/test-net-isip.js\nexport const testNetIsIp = {\n  test() {\n    strictEqual(net.isIP('127.0.0.1'), 4);\n    strictEqual(net.isIP('x127.0.0.1'), 0);\n    strictEqual(net.isIP('example.com'), 0);\n    strictEqual(net.isIP('0000:0000:0000:0000:0000:0000:0000:0000'), 6);\n    strictEqual(net.isIP('0000:0000:0000:0000:0000:0000:0000:0000::0000'), 0);\n    strictEqual(net.isIP('1050:0:0:0:5:600:300c:326b'), 6);\n    strictEqual(net.isIP('2001:252:0:1::2008:6'), 6);\n    strictEqual(net.isIP('2001:dead:beef:1::2008:6'), 6);\n    strictEqual(net.isIP('2001::'), 6);\n    strictEqual(net.isIP('2001:dead::'), 6);\n    strictEqual(net.isIP('2001:dead:beef::'), 6);\n    strictEqual(net.isIP('2001:dead:beef:1::'), 6);\n    strictEqual(net.isIP('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'), 6);\n    strictEqual(net.isIP(':2001:252:0:1::2008:6:'), 0);\n    strictEqual(net.isIP(':2001:252:0:1::2008:6'), 0);\n    strictEqual(net.isIP('2001:252:0:1::2008:6:'), 0);\n    strictEqual(net.isIP('2001:252::1::2008:6'), 0);\n    strictEqual(net.isIP('::2001:252:1:2008:6'), 6);\n    strictEqual(net.isIP('::2001:252:1:1.1.1.1'), 6);\n    strictEqual(net.isIP('::2001:252:1:255.255.255.255'), 6);\n    strictEqual(net.isIP('::2001:252:1:255.255.255.255.76'), 0);\n    strictEqual(net.isIP('fe80::2008%eth0'), 6);\n    strictEqual(net.isIP('fe80::2008%eth0.0'), 6);\n    strictEqual(net.isIP('fe80::2008%eth0@1'), 0);\n    strictEqual(net.isIP('::anything'), 0);\n    strictEqual(net.isIP('::1'), 6);\n    strictEqual(net.isIP('::'), 6);\n    strictEqual(net.isIP('0000:0000:0000:0000:0000:0000:12345:0000'), 0);\n    strictEqual(net.isIP('0'), 0);\n    strictEqual(net.isIP(), 0);\n    strictEqual(net.isIP(''), 0);\n    strictEqual(net.isIP(null), 0);\n    strictEqual(net.isIP(123), 0);\n    strictEqual(net.isIP(true), 0);\n    strictEqual(net.isIP({}), 0);\n    strictEqual(\n      net.isIP({ toString: () => '::2001:252:1:255.255.255.255' }),\n      6\n    );\n    strictEqual(net.isIP({ toString: () => '127.0.0.1' }), 4);\n    strictEqual(net.isIP({ toString: () => 'bla' }), 0);\n\n    strictEqual(net.isIPv4('127.0.0.1'), true);\n    strictEqual(net.isIPv4('example.com'), false);\n    strictEqual(net.isIPv4('2001:252:0:1::2008:6'), false);\n    strictEqual(net.isIPv4(), false);\n    strictEqual(net.isIPv4(''), false);\n    strictEqual(net.isIPv4(null), false);\n    strictEqual(net.isIPv4(123), false);\n    strictEqual(net.isIPv4(true), false);\n    strictEqual(net.isIPv4({}), false);\n    strictEqual(\n      net.isIPv4({ toString: () => '::2001:252:1:255.255.255.255' }),\n      false\n    );\n    strictEqual(net.isIPv4({ toString: () => '127.0.0.1' }), true);\n    strictEqual(net.isIPv4({ toString: () => 'bla' }), false);\n\n    strictEqual(net.isIPv6('127.0.0.1'), false);\n    strictEqual(net.isIPv6('example.com'), false);\n    strictEqual(net.isIPv6('2001:252:0:1::2008:6'), true);\n    strictEqual(net.isIPv6(), false);\n    strictEqual(net.isIPv6(''), false);\n    strictEqual(net.isIPv6(null), false);\n    strictEqual(net.isIPv6(123), false);\n    strictEqual(net.isIPv6(true), false);\n    strictEqual(net.isIPv6({}), false);\n    strictEqual(\n      net.isIPv6({ toString: () => '::2001:252:1:255.255.255.255' }),\n      true\n    );\n    strictEqual(net.isIPv6({ toString: () => '127.0.0.1' }), false);\n    strictEqual(net.isIPv6({ toString: () => 'bla' }), false);\n  },\n};\n\n// test/parallel/test-net-isipv4.js\nexport const testNetIsIpv4 = {\n  test() {\n    const v4 = [\n      '0.0.0.0',\n      '8.8.8.8',\n      '127.0.0.1',\n      '100.100.100.100',\n      '192.168.0.1',\n      '18.101.25.153',\n      '123.23.34.2',\n      '172.26.168.134',\n      '212.58.241.131',\n      '128.0.0.0',\n      '23.71.254.72',\n      '223.255.255.255',\n      '192.0.2.235',\n      '99.198.122.146',\n      '46.51.197.88',\n      '173.194.34.134',\n    ];\n\n    const v4not = [\n      '.100.100.100.100',\n      '100..100.100.100.',\n      '100.100.100.100.',\n      '999.999.999.999',\n      '256.256.256.256',\n      '256.100.100.100.100',\n      '123.123.123',\n      'http://123.123.123',\n      '1000.2.3.4',\n      '999.2.3.4',\n      '0000000192.168.0.200',\n      '192.168.0.2000000000',\n    ];\n\n    for (const ip of v4) {\n      strictEqual(net.isIPv4(ip), true);\n    }\n\n    for (const ip of v4not) {\n      strictEqual(net.isIPv4(ip), false);\n    }\n  },\n};\n\n// test/parallel/test-net-isipv6.js\nexport const testNetIsIpv6 = {\n  test() {\n    const v6 = [\n      '::',\n      '1::',\n      '::1',\n      '1::8',\n      '1::7:8',\n      '1:2:3:4:5:6:7:8',\n      '1:2:3:4:5:6::8',\n      '1:2:3:4:5:6:7::',\n      '1:2:3:4:5::7:8',\n      '1:2:3:4:5::8',\n      '1:2:3::8',\n      '1::4:5:6:7:8',\n      '1::6:7:8',\n      '1::3:4:5:6:7:8',\n      '1:2:3:4::6:7:8',\n      '1:2::4:5:6:7:8',\n      '::2:3:4:5:6:7:8',\n      '1:2::8',\n      '2001:0000:1234:0000:0000:C1C0:ABCD:0876',\n      '3ffe:0b00:0000:0000:0001:0000:0000:000a',\n      'FF02:0000:0000:0000:0000:0000:0000:0001',\n      '0000:0000:0000:0000:0000:0000:0000:0001',\n      '0000:0000:0000:0000:0000:0000:0000:0000',\n      '::ffff:192.168.1.26',\n      '2::10',\n      'ff02::1',\n      'fe80::',\n      '2002::',\n      '2001:db8::',\n      '2001:0db8:1234::',\n      '::ffff:0:0',\n      '::ffff:192.168.1.1',\n      '1:2:3:4::8',\n      '1::2:3:4:5:6:7',\n      '1::2:3:4:5:6',\n      '1::2:3:4:5',\n      '1::2:3:4',\n      '1::2:3',\n      '::2:3:4:5:6:7',\n      '::2:3:4:5:6',\n      '::2:3:4:5',\n      '::2:3:4',\n      '::2:3',\n      '::8',\n      '1:2:3:4:5:6::',\n      '1:2:3:4:5::',\n      '1:2:3:4::',\n      '1:2:3::',\n      '1:2::',\n      '1:2:3:4::7:8',\n      '1:2:3::7:8',\n      '1:2::7:8',\n      '1:2:3:4:5:6:1.2.3.4',\n      '1:2:3:4:5::1.2.3.4',\n      '1:2:3:4::1.2.3.4',\n      '1:2:3::1.2.3.4',\n      '1:2::1.2.3.4',\n      '1::1.2.3.4',\n      '1:2:3:4::5:1.2.3.4',\n      '1:2:3::5:1.2.3.4',\n      '1:2::5:1.2.3.4',\n      '1::5:1.2.3.4',\n      '1::5:11.22.33.44',\n      'fe80::217:f2ff:254.7.237.98',\n      'fe80::217:f2ff:fe07:ed62',\n      '2001:DB8:0:0:8:800:200C:417A',\n      'FF01:0:0:0:0:0:0:101',\n      '0:0:0:0:0:0:0:1',\n      '0:0:0:0:0:0:0:0',\n      '2001:DB8::8:800:200C:417A',\n      'FF01::101',\n      '0:0:0:0:0:0:13.1.68.3',\n      '0:0:0:0:0:FFFF:129.144.52.38',\n      '::13.1.68.3',\n      '::FFFF:129.144.52.38',\n      'fe80:0000:0000:0000:0204:61ff:fe9d:f156',\n      'fe80:0:0:0:204:61ff:fe9d:f156',\n      'fe80::204:61ff:fe9d:f156',\n      'fe80:0:0:0:204:61ff:254.157.241.86',\n      'fe80::204:61ff:254.157.241.86',\n      'fe80::1',\n      '2001:0db8:85a3:0000:0000:8a2e:0370:7334',\n      '2001:db8:85a3:0:0:8a2e:370:7334',\n      '2001:db8:85a3::8a2e:370:7334',\n      '2001:0db8:0000:0000:0000:0000:1428:57ab',\n      '2001:0db8:0000:0000:0000::1428:57ab',\n      '2001:0db8:0:0:0:0:1428:57ab',\n      '2001:0db8:0:0::1428:57ab',\n      '2001:0db8::1428:57ab',\n      '2001:db8::1428:57ab',\n      '::ffff:12.34.56.78',\n      '::ffff:0c22:384e',\n      '2001:0db8:1234:0000:0000:0000:0000:0000',\n      '2001:0db8:1234:ffff:ffff:ffff:ffff:ffff',\n      '2001:db8:a::123',\n      '::ffff:192.0.2.128',\n      '::ffff:c000:280',\n      'a:b:c:d:e:f:f1:f2',\n      'a:b:c::d:e:f:f1',\n      'a:b:c::d:e:f',\n      'a:b:c::d:e',\n      'a:b:c::d',\n      '::a',\n      '::a:b:c',\n      '::a:b:c:d:e:f:f1',\n      'a::',\n      'a:b:c::',\n      'a:b:c:d:e:f:f1::',\n      'a:bb:ccc:dddd:000e:00f:0f::',\n      '0:a:0:a:0:0:0:a',\n      '0:a:0:0:a:0:0:a',\n      '2001:db8:1:1:1:1:0:0',\n      '2001:db8:1:1:1:0:0:0',\n      '2001:db8:1:1:0:0:0:0',\n      '2001:db8:1:0:0:0:0:0',\n      '2001:db8:0:0:0:0:0:0',\n      '2001:0:0:0:0:0:0:0',\n      'A:BB:CCC:DDDD:000E:00F:0F::',\n      '0:0:0:0:0:0:0:a',\n      '0:0:0:0:a:0:0:0',\n      '0:0:0:a:0:0:0:0',\n      'a:0:0:a:0:0:a:a',\n      'a:0:0:a:0:0:0:a',\n      'a:0:0:0:a:0:0:a',\n      'a:0:0:0:a:0:0:0',\n      'a:0:0:0:0:0:0:0',\n      'fe80::7:8%eth0',\n      'fe80::7:8%1',\n    ];\n\n    const v6not = [\n      '',\n      '1:',\n      ':1',\n      '11:36:12',\n      '02001:0000:1234:0000:0000:C1C0:ABCD:0876',\n      '2001:0000:1234:0000:00001:C1C0:ABCD:0876',\n      '2001:0000:1234: 0000:0000:C1C0:ABCD:0876',\n      '2001:1:1:1:1:1:255Z255X255Y255',\n      '3ffe:0b00:0000:0001:0000:0000:000a',\n      'FF02:0000:0000:0000:0000:0000:0000:0000:0001',\n      '3ffe:b00::1::a',\n      '::1111:2222:3333:4444:5555:6666::',\n      '1:2:3::4:5::7:8',\n      '12345::6:7:8',\n      '1::5:400.2.3.4',\n      '1::5:260.2.3.4',\n      '1::5:256.2.3.4',\n      '1::5:1.256.3.4',\n      '1::5:1.2.256.4',\n      '1::5:1.2.3.256',\n      '1::5:300.2.3.4',\n      '1::5:1.300.3.4',\n      '1::5:1.2.300.4',\n      '1::5:1.2.3.300',\n      '1::5:900.2.3.4',\n      '1::5:1.900.3.4',\n      '1::5:1.2.900.4',\n      '1::5:1.2.3.900',\n      '1::5:300.300.300.300',\n      '1::5:3000.30.30.30',\n      '1::400.2.3.4',\n      '1::260.2.3.4',\n      '1::256.2.3.4',\n      '1::1.256.3.4',\n      '1::1.2.256.4',\n      '1::1.2.3.256',\n      '1::300.2.3.4',\n      '1::1.300.3.4',\n      '1::1.2.300.4',\n      '1::1.2.3.300',\n      '1::900.2.3.4',\n      '1::1.900.3.4',\n      '1::1.2.900.4',\n      '1::1.2.3.900',\n      '1::300.300.300.300',\n      '1::3000.30.30.30',\n      '::400.2.3.4',\n      '::260.2.3.4',\n      '::256.2.3.4',\n      '::1.256.3.4',\n      '::1.2.256.4',\n      '::1.2.3.256',\n      '::300.2.3.4',\n      '::1.300.3.4',\n      '::1.2.300.4',\n      '::1.2.3.300',\n      '::900.2.3.4',\n      '::1.900.3.4',\n      '::1.2.900.4',\n      '::1.2.3.900',\n      '::300.300.300.300',\n      '::3000.30.30.30',\n      '2001:DB8:0:0:8:800:200C:417A:221',\n      'FF01::101::2',\n      '1111:2222:3333:4444::5555:',\n      '1111:2222:3333::5555:',\n      '1111:2222::5555:',\n      '1111::5555:',\n      '::5555:',\n      ':::',\n      '1111:',\n      ':',\n      ':1111:2222:3333:4444::5555',\n      ':1111:2222:3333::5555',\n      ':1111:2222::5555',\n      ':1111::5555',\n      ':::5555',\n      '1.2.3.4:1111:2222:3333:4444::5555',\n      '1.2.3.4:1111:2222:3333::5555',\n      '1.2.3.4:1111:2222::5555',\n      '1.2.3.4:1111::5555',\n      '1.2.3.4::5555',\n      '1.2.3.4::',\n      'fe80:0000:0000:0000:0204:61ff:254.157.241.086',\n      '123',\n      'ldkfj',\n      '2001::FFD3::57ab',\n      '2001:db8:85a3::8a2e:37023:7334',\n      '2001:db8:85a3::8a2e:370k:7334',\n      '1:2:3:4:5:6:7:8:9',\n      '1::2::3',\n      '1:::3:4:5',\n      '1:2:3::4:5:6:7:8:9',\n      '::ffff:2.3.4',\n      '::ffff:257.1.2.3',\n      '::ffff:12345678901234567890.1.26',\n      '2001:0000:1234:0000:0000:C1C0:ABCD:0876 0',\n      '02001:0000:1234:0000:0000:C1C0:ABCD:0876',\n    ];\n\n    for (const ip of v6) {\n      strictEqual(net.isIPv6(ip), true);\n    }\n\n    for (const ip of v6not) {\n      strictEqual(net.isIPv6(ip), false);\n    }\n  },\n};\n\n// test/parallel/test-net-large-string.js\nexport const testNetLargeString = {\n  async test(ctrl, env, ctx) {\n    const { promise, resolve } = Promise.withResolvers();\n    const c = net.connect(env.ECHO_SERVER_PORT, env.SIDECAR_HOSTNAME);\n    let response = '';\n    const size = 40 * 1024;\n    const data = 'あ'.repeat(size);\n    c.setEncoding('utf8');\n    c.on('data', (data) => (response += data));\n    c.end(data);\n    c.on('close', resolve);\n    await promise;\n    strictEqual(response.length, size);\n    strictEqual(response, data);\n  },\n};\n\n// test/parallel/test-net-local-address-port.js\n// The localAddress information is a non-op in our implementation\nexport const testNetLocalAddressPort = {\n  async test(ctrl, env, ctx) {\n    const { promise, resolve } = Promise.withResolvers();\n    const c = net.connect(env.SERVER_PORT, env.SIDECAR_HOSTNAME);\n    c.on('connect', () => {\n      strictEqual(c.localAddress, '0.0.0.0');\n      strictEqual(c.localPort, 0);\n      resolve();\n    });\n    await promise;\n  },\n};\n\n// test/parallel/test-net-localerror.js\nexport const testNetLocalError = {\n  async test() {\n    const connect = (opts, code, type) => {\n      throws(() => net.connect(opts), { code, name: type.name });\n    };\n\n    connect(\n      {\n        host: 'localhost',\n        port: 0,\n        localAddress: 'foobar',\n      },\n      'ERR_INVALID_IP_ADDRESS',\n      TypeError\n    );\n\n    connect(\n      {\n        host: 'localhost',\n        port: 0,\n        localPort: 'foobar',\n      },\n      'ERR_INVALID_ARG_TYPE',\n      TypeError\n    );\n  },\n};\n\n// test/parallel/test-net-onread-static-buffer.js\nexport const testNetOnReadStaticBuffer = {\n  async test(ctrl, env, ctx) {\n    const { promise, resolve } = Promise.withResolvers();\n    const buffer = Buffer.alloc(1024);\n    const fn = mock.fn((nread, buf) => {\n      strictEqual(nread, 5);\n      strictEqual(buf.buffer.byteLength, 1024);\n      resolve();\n    });\n    const c = net.connect({\n      port: env.ECHO_SERVER_PORT,\n      host: env.SIDECAR_HOSTNAME,\n      onread: {\n        buffer,\n        callback: fn,\n      },\n    });\n    c.on('data', () => {\n      throw new Error('Should not have failed');\n    });\n    c.write('hello');\n    await promise;\n    strictEqual(fn.mock.callCount(), 1);\n  },\n};\n\nexport const testNetReconnect = {\n  async test(ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    const N = 50;\n    const client = net.connect(env.RECONNECT_SERVER_PORT, env.SIDECAR_HOSTNAME);\n\n    client.setEncoding('UTF8');\n\n    const onDataFn = mock.fn((chunk) => {\n      strictEqual(chunk, 'hello\\r\\n');\n      client.end();\n    });\n    client.on('data', onDataFn);\n\n    const endFn = mock.fn(() => {});\n    client.on('end', endFn);\n\n    const closeFn = mock.fn((had_error) => {\n      strictEqual(had_error, false);\n      if (closeFn.mock.callCount() < N) {\n        client.connect(env.RECONNECT_SERVER_PORT, env.SIDECAR_HOSTNAME); // reconnect\n      } else {\n        resolve();\n      }\n    });\n    client.on('close', closeFn);\n\n    await promise;\n    strictEqual(closeFn.mock.callCount(), N + 1);\n    strictEqual(onDataFn.mock.callCount(), N + 1);\n    strictEqual(endFn.mock.callCount(), N + 1);\n  },\n};\n\n// test/parallel/test-net-remote-address-port.js\n// test/parallel/test-net-remote-address.js\nexport const testNetRemoteAddress = {\n  async test(ctrl, env, ctx) {\n    const { promise, resolve } = Promise.withResolvers();\n    const c = net.connect(env.SERVER_PORT, env.SIDECAR_HOSTNAME);\n    c.on('connect', () => {\n      strictEqual(c.remoteAddress, env.SIDECAR_HOSTNAME);\n      strictEqual(c.remotePort, parseInt(env.SERVER_PORT));\n      resolve();\n    });\n    await promise;\n  },\n};\n\n// // test/parallel/test-net-socket-byteswritten.js\nexport const testNetSocketBytesWritten = {\n  async test(ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    const socket = net.connect(env.END_SERVER_PORT, env.SIDECAR_HOSTNAME);\n\n    // Cork the socket, then write twice; this should cause a writev, which\n    // previously caused an err in the bytesWritten count.\n    socket.cork();\n\n    socket.write('one');\n    socket.write(Buffer.from('twø', 'utf8'));\n\n    socket.uncork();\n\n    // one = 3 bytes, twø = 4 bytes\n    strictEqual(socket.bytesWritten, 3 + 4);\n\n    const connectFn = mock.fn(() => {\n      strictEqual(socket.bytesWritten, 3 + 4);\n    });\n    socket.on('connect', connectFn);\n\n    socket.on('end', function () {\n      strictEqual(socket.bytesWritten, 3 + 4);\n      resolve();\n    });\n\n    await promise;\n    strictEqual(connectFn.mock.callCount(), 1);\n  },\n};\n\n// test/parallel/test-net-socket-connect-invalid-autoselectfamily.js\nexport const testSocketConnectInvalidAutoselectFamily = {\n  async test() {\n    throws(\n      () => {\n        net.connect({ port: 8080, autoSelectFamily: 'INVALID' });\n      },\n      { code: 'ERR_INVALID_ARG_TYPE' }\n    );\n  },\n};\n\n// test/parallel/test-net-socket-connect-invalid-autoselectfamilyattempttimeout.js\nexport const testSocketConnectInvalidAutoselectFamilyTimeout = {\n  async test() {\n    for (const autoSelectFamilyAttemptTimeout of [-10, 0]) {\n      throws(\n        () => {\n          net.connect({\n            port: 8080,\n            autoSelectFamily: true,\n            autoSelectFamilyAttemptTimeout,\n          });\n        },\n        { code: 'ERR_OUT_OF_RANGE' }\n      );\n\n      throws(\n        () => {\n          net.setDefaultAutoSelectFamilyAttemptTimeout(\n            autoSelectFamilyAttemptTimeout\n          );\n        },\n        { code: 'ERR_OUT_OF_RANGE' }\n      );\n    }\n\n    // Check the default value of autoSelectFamilyAttemptTimeout is 10\n    // if passed number is less than 10\n    for (const autoSelectFamilyAttemptTimeout of [1, 9]) {\n      net.setDefaultAutoSelectFamilyAttemptTimeout(\n        autoSelectFamilyAttemptTimeout\n      );\n      strictEqual(net.getDefaultAutoSelectFamilyAttemptTimeout(), 10);\n    }\n  },\n};\n\n// test/parallel/test-net-socket-connect-without-cb.js\nexport const testNetSocketConnectWithoutCb = {\n  async test(ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    const client = new net.Socket();\n    client.on('connect', resolve);\n    client.connect(env.SERVER_PORT, env.SIDECAR_HOSTNAME);\n    await promise;\n  },\n};\n\n// test/parallel/test-net-socket-connecting.js\nexport const testNetSocketConnecting = {\n  async test(ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    const client = net.connect(env.SERVER_PORT, env.SIDECAR_HOSTNAME, () => {\n      strictEqual(client.connecting, false);\n\n      // Legacy getter\n      strictEqual(client._connecting, false);\n      resolve();\n    });\n    strictEqual(client.connecting, true);\n\n    // Legacy getter\n    strictEqual(client._connecting, true);\n    await promise;\n  },\n};\n\n// test/parallel/test-net-socket-destroy-send.js\nexport const testNetSocketDestroySend = {\n  async test(ctrl, env) {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const conn = net.createConnection(env.SERVER_PORT, env.SIDECAR_HOSTNAME);\n\n    conn.on('connect', function () {\n      // Test destroy returns this, even on multiple calls when it short-circuits.\n      strictEqual(conn, conn.destroy().destroy());\n      conn.on('error', reject);\n\n      conn.write(Buffer.from('kaboom'), (err) => {\n        strictEqual(err.code, 'ERR_STREAM_DESTROYED');\n        strictEqual(err.name, 'Error');\n        strictEqual(\n          err.message,\n          'Cannot call write after a stream was destroyed'\n        );\n        resolve();\n      });\n    });\n\n    await promise;\n  },\n};\n\n// test/parallel/test-net-socket-end-callback.js\nexport const testNetSocketEndCallback = {\n  async test(ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    const connect = (...args) => {\n      const socket = net.createConnection(\n        env.SERVER_PORT,\n        env.SIDECAR_HOSTNAME,\n        () => {\n          socket.end(...args);\n        }\n      );\n    };\n\n    let count = 0;\n    const cb = mock.fn(() => {\n      if (++count === 3) {\n        resolve();\n      }\n    });\n\n    connect(cb);\n    connect('foo', cb);\n    connect('foo', 'utf8', cb);\n    await promise;\n    strictEqual(cb.mock.callCount(), 3);\n  },\n};\n\n// test/parallel/test-net-socket-no-halfopen-enforcer.js\nexport const testNetSocketNoHalfopenEnforcer = {\n  async test() {\n    const socket = new net.Socket({ allowHalfOpen: false });\n    strictEqual(socket.listenerCount('end'), 1);\n  },\n};\n\n// test/parallel/test-net-socket-ready-without-cb.js\nexport const testNetSocketReadyWithoutCb = {\n  async test(ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    const client = new net.Socket();\n    client.on('ready', resolve);\n    client.connect(env.SERVER_PORT, env.SIDECAR_HOSTNAME);\n    await promise;\n  },\n};\n\n// test/parallel/test-net-socket-reset-send.js\nexport const testNetSocketResetSend = {\n  async test(ctrl, env) {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const conn = net.createConnection(env.SERVER_PORT, env.SIDECAR_HOSTNAME);\n    conn.on('connect', () => {\n      strictEqual(conn, conn.resetAndDestroy().destroy());\n      conn.on('error', reject);\n\n      conn.write(Buffer.from('fzfzfzfzfz'), (err) => {\n        strictEqual(err.code, 'ERR_STREAM_DESTROYED');\n        strictEqual(err.name, 'Error');\n        strictEqual(\n          err.message,\n          'Cannot call write after a stream was destroyed'\n        );\n        resolve();\n      });\n    });\n    await promise;\n  },\n};\n\n// test/parallel/test-net-socket-timeout.js\nexport const testNetSocketTimeout = {\n  async test(ctrl, env) {\n    // Verify that invalid delays throw\n    const s = new net.Socket();\n    const nonNumericDelays = [\n      '100',\n      true,\n      false,\n      undefined,\n      null,\n      '',\n      {},\n      () => {},\n      [],\n    ];\n    const badRangeDelays = [-0.001, -1, -Infinity, Infinity, NaN];\n    const validDelays = [0, 0.001, 1, 1e6];\n    const invalidCallbacks = [\n      1,\n      '100',\n      true,\n      false,\n      null,\n      {},\n      [],\n      Symbol('test'),\n    ];\n\n    for (let i = 0; i < nonNumericDelays.length; i++) {\n      throws(\n        () => {\n          s.setTimeout(nonNumericDelays[i], () => {});\n        },\n        { code: 'ERR_INVALID_ARG_TYPE' },\n        nonNumericDelays[i]\n      );\n    }\n\n    for (let i = 0; i < badRangeDelays.length; i++) {\n      throws(\n        () => {\n          s.setTimeout(badRangeDelays[i], () => {});\n        },\n        { code: 'ERR_OUT_OF_RANGE' },\n        badRangeDelays[i]\n      );\n    }\n\n    for (let i = 0; i < validDelays.length; i++) {\n      s.setTimeout(validDelays[i], () => {});\n    }\n\n    for (let i = 0; i < invalidCallbacks.length; i++) {\n      [0, 1].forEach((msec) =>\n        throws(() => s.setTimeout(msec, invalidCallbacks[i]), {\n          code: 'ERR_INVALID_ARG_TYPE',\n          name: 'TypeError',\n        })\n      );\n    }\n\n    {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      const socket = net.createConnection(\n        env.SERVER_PORT,\n        env.SIDECAR_HOSTNAME\n      );\n      strictEqual(\n        socket.setTimeout(1, () => {\n          socket.destroy();\n          strictEqual(\n            socket.setTimeout(1, () =>\n              reject(new Error('Should not have called setTimeout callback'))\n            ),\n            socket\n          );\n          resolve();\n        }),\n        socket\n      );\n      await promise;\n    }\n  },\n};\n\n// test/parallel/test-net-socket-write-after-close.js\nexport const testNetSocketWriteAfterClose = {\n  async test(ctrl, env) {\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      const client = net.connect(env.SERVER_PORT, env.SIDECAR_HOSTNAME, () => {\n        client.on('error', (err) => {\n          strictEqual(err.name, 'Error');\n          // Node.js tests for a different error message.\n          strictEqual(err.message, 'Socket is closed');\n          strictEqual(err.code, 'ERR_SOCKET_CLOSED');\n          resolve();\n        });\n        client._handle = null;\n        client.write('foo');\n      });\n      await promise;\n    }\n  },\n};\n\n// test/parallel/test-net-socket-write-error.js\nexport const testNetSocketWriteError = {\n  async test(ctrl, env) {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const client = net.createConnection(\n      env.SERVER_PORT,\n      env.SIDECAR_HOSTNAME,\n      () => {\n        client.on('error', reject);\n        throws(\n          () => {\n            client.write(1337);\n          },\n          {\n            code: 'ERR_INVALID_ARG_TYPE',\n            name: 'TypeError',\n          }\n        );\n\n        resolve();\n      }\n    );\n    await promise;\n  },\n};\n\n// test/parallel/test-net-sync-cork.js\nexport const testNetSyncCork = {\n  async test(ctrl, env) {\n    const N = 100;\n    const buf = Buffer.alloc(2, 'a');\n\n    const { promise, resolve } = Promise.withResolvers();\n    const conn = net.connect(env.SERVER_PORT, env.SIDECAR_HOSTNAME);\n\n    conn.on('connect', () => {\n      let res = true;\n      let i = 0;\n      for (; i < N && res; i++) {\n        conn.cork();\n        conn.write(buf);\n        res = conn.write(buf);\n        conn.uncork();\n      }\n      strictEqual(i, N);\n      resolve();\n    });\n    await promise;\n  },\n};\n\n// test/parallel/test-net-timeout-no-handle.js\nexport const testNetTimeoutNoHandle = {\n  async test() {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const socket = new net.Socket();\n    socket.setTimeout(50);\n\n    socket.on('timeout', () => {\n      strictEqual(socket._handle, null);\n      resolve();\n    });\n\n    socket.on('connect', reject);\n\n    // Since the timeout is unrefed, the code will exit without this\n    setTimeout(() => {}, 200);\n    await promise;\n  },\n};\n\n// test/parallel/test-net-writable.js\nexport const testNetWritable = {\n  async test(ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    const socket = net.connect(env.SERVER_THAT_DIES_PORT, env.SIDECAR_HOSTNAME);\n    socket.on('end', () => {\n      strictEqual(socket.writable, true);\n      socket.write('hello world');\n      resolve();\n    });\n    await promise;\n  },\n};\n\n// test/parallel/test-net-write-arguments.js\nexport const testNetWriteArguments = {\n  async test() {\n    const socket = net.Stream({ highWaterMark: 0 });\n\n    throws(\n      () => {\n        socket.write(null);\n      },\n      {\n        code: 'ERR_STREAM_NULL_VALUES',\n        name: 'TypeError',\n        message: 'May not write null values to stream',\n      }\n    );\n\n    [true, false, undefined, 1, 1.0, +Infinity, -Infinity, [], {}].forEach(\n      (value) => {\n        const socket = net.Stream({ highWaterMark: 0 });\n        // We need to check the callback since 'error' will only\n        // be emitted once per instance.\n        throws(\n          () => {\n            socket.write(value);\n          },\n          {\n            code: 'ERR_INVALID_ARG_TYPE',\n            name: 'TypeError',\n          }\n        );\n      }\n    );\n  },\n};\n\n// test/parallel/test-net-write-cb-on-destroy-before-connect.js\nexport const testNetWriteCbOnDestroyBefureConnected = {\n  async test(ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    const socket = new net.Socket();\n\n    socket.on('connect', () => {\n      reject(new Error('Connect should not have been called'));\n    });\n\n    socket.connect(Number(env.SERVER_PORT), env.SIDECAR_HOSTNAME);\n\n    ok(socket.connecting);\n\n    socket.write('foo', (err) => {\n      strictEqual(err.code, 'ERR_SOCKET_CLOSED_BEFORE_CONNECTION');\n      strictEqual(err.name, 'Error');\n      resolve();\n    });\n\n    socket.destroy();\n    await promise;\n  },\n};\n\n// test/parallel/test-net-write-connect-write.js\nexport const testNetWriteConnectWrite = {\n  async test(ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    const conn = net.connect(env.ECHO_SERVER_PORT, env.SIDECAR_HOSTNAME);\n    let received = '';\n\n    conn.setEncoding('utf8');\n    conn.on('connect', function () {\n      conn.write(' after');\n    });\n    conn.on('data', function (buf) {\n      received += buf;\n      conn.end();\n    });\n    conn.write('before');\n\n    conn.on('end', function () {\n      strictEqual(received, 'before after');\n      resolve();\n    });\n    await promise;\n  },\n};\n\nexport const testSocketAddress = {\n  test() {\n    // Test constructor with default options\n    const addr1 = new net.SocketAddress();\n    strictEqual(addr1.address, '127.0.0.1');\n    strictEqual(addr1.port, 0);\n    strictEqual(addr1.family, 'ipv4');\n    strictEqual(addr1.flowlabel, 0);\n\n    // Test constructor with IPv4 options\n    const addr2 = new net.SocketAddress({\n      address: '192.168.1.1',\n      port: 8080,\n      family: 'ipv4',\n    });\n    strictEqual(addr2.address, '192.168.1.1');\n    strictEqual(addr2.port, 8080);\n    strictEqual(addr2.family, 'ipv4');\n    strictEqual(addr2.flowlabel, 0);\n\n    // Test constructor with IPv6 options\n    const addr3 = new net.SocketAddress({\n      address: '::1',\n      port: 3000,\n      family: 'ipv6',\n      flowlabel: 123,\n    });\n    strictEqual(addr3.address, '::1');\n    strictEqual(addr3.port, 3000);\n    strictEqual(addr3.family, 'ipv6');\n    strictEqual(addr3.flowlabel, 123);\n\n    // Test default IPv6 address\n    const addr4 = new net.SocketAddress({ family: 'ipv6' });\n    strictEqual(addr4.address, '::');\n    strictEqual(addr4.family, 'ipv6');\n\n    // Test toJSON method\n    const addr5 = new net.SocketAddress({\n      address: '10.0.0.1',\n      port: 9000,\n      family: 'ipv4',\n    });\n    const json = addr5.toJSON();\n    strictEqual(json.address, '10.0.0.1');\n    strictEqual(json.port, 9000);\n    strictEqual(json.family, 'ipv4');\n    strictEqual(json.flowlabel, 0);\n\n    // Test isSocketAddress static method\n    ok(net.SocketAddress.isSocketAddress(addr1));\n    ok(net.SocketAddress.isSocketAddress(addr2));\n    ok(!net.SocketAddress.isSocketAddress({}));\n    ok(!net.SocketAddress.isSocketAddress(null));\n    ok(!net.SocketAddress.isSocketAddress('string'));\n\n    // Test parse static method with IPv4\n    const parsed1 = net.SocketAddress.parse('192.168.1.100:8080');\n    ok(parsed1);\n    strictEqual(parsed1.address, '192.168.1.100');\n    strictEqual(parsed1.port, 8080);\n    strictEqual(parsed1.family, 'ipv4');\n\n    // Test parse static method with IPv6\n    const parsed2 = net.SocketAddress.parse('[::1]:3000');\n    ok(parsed2);\n    strictEqual(parsed2.address, '::1');\n    strictEqual(parsed2.port, 3000);\n    strictEqual(parsed2.family, 'ipv6');\n\n    // Test parse with invalid input\n    strictEqual(net.SocketAddress.parse('invalid'), undefined);\n    strictEqual(net.SocketAddress.parse(''), undefined);\n\n    // Test constructor validation - invalid family\n    throws(() => new net.SocketAddress({ family: 'invalid' }), {\n      code: 'ERR_INVALID_ARG_VALUE',\n    });\n\n    // Test constructor validation - invalid IPv4 address\n    throws(\n      () =>\n        new net.SocketAddress({\n          address: '300.300.300.300',\n          family: 'ipv4',\n        }),\n      {\n        code: 'ERR_INVALID_ADDRESS',\n      }\n    );\n\n    // Test constructor validation - invalid IPv6 address\n    throws(\n      () =>\n        new net.SocketAddress({\n          address: 'invalid::ipv6',\n          family: 'ipv6',\n        }),\n      {\n        code: 'ERR_INVALID_ADDRESS',\n      }\n    );\n\n    // Test constructor validation - invalid port\n    throws(() => new net.SocketAddress({ port: -1 }), {\n      code: 'ERR_SOCKET_BAD_PORT',\n    });\n\n    throws(() => new net.SocketAddress({ port: 65536 }), {\n      code: 'ERR_SOCKET_BAD_PORT',\n    });\n\n    // Test family case insensitive\n    const addr6 = new net.SocketAddress({ family: 'IPv4' });\n    strictEqual(addr6.family, 'ipv4');\n\n    const addr7 = new net.SocketAddress({ family: 'IPv6' });\n    strictEqual(addr7.family, 'ipv6');\n\n    // Test port type coercion\n    const addr8 = new net.SocketAddress({ port: '8080' });\n    console.log(typeof addr8.port);\n    strictEqual(addr8.port, 8080);\n  },\n};\n\nexport const testBlockList = {\n  test() {\n    // Test BlockList constructor and static methods\n    const bl = new net.BlockList();\n    ok(net.BlockList.isBlockList(bl));\n    ok(!net.BlockList.isBlockList({}));\n    ok(!net.BlockList.isBlockList(null));\n\n    // Test addAddress with IPv4\n    bl.addAddress('192.168.1.1');\n    bl.addAddress('10.0.0.1', 'ipv4');\n\n    // Test check method with IPv4 addresses\n    ok(bl.check('192.168.1.1'));\n    ok(bl.check('10.0.0.1', 'ipv4'));\n    ok(!bl.check('192.168.1.2'));\n    ok(!bl.check('192.168.1.1', 'ipv6'));\n\n    // Test addAddress with IPv6\n    bl.addAddress('2001:db8::1', 'ipv6');\n    ok(bl.check('2001:db8::1', 'ipv6'));\n    ok(!bl.check('2001:db8::2', 'ipv6'));\n\n    // Test addAddress with SocketAddress\n    const addr1 = new net.SocketAddress({ address: '172.16.0.1', port: 80 });\n    bl.addAddress(addr1);\n    ok(bl.check(addr1));\n    ok(bl.check('172.16.0.1'));\n\n    // Test addRange with IPv4\n    bl.addRange('192.168.2.1', '192.168.2.10');\n    ok(bl.check('192.168.2.1'));\n    ok(bl.check('192.168.2.5'));\n    ok(bl.check('192.168.2.10'));\n    ok(!bl.check('192.168.2.11'));\n    ok(!bl.check('192.168.1.255'));\n\n    // Test addRange with IPv6\n    bl.addRange('2001:db8::10', '2001:db8::20', 'ipv6');\n    ok(bl.check('2001:db8::15', 'ipv6'));\n    ok(bl.check('2001:db8::10', 'ipv6'));\n    ok(bl.check('2001:db8::20', 'ipv6'));\n    ok(!bl.check('2001:db8::21', 'ipv6'));\n\n    // Test addRange with SocketAddress\n    const startAddr = new net.SocketAddress({\n      address: '172.16.1.1',\n      port: 80,\n    });\n    const endAddr = new net.SocketAddress({ address: '172.16.1.5', port: 80 });\n    bl.addRange(startAddr, endAddr);\n    ok(bl.check('172.16.1.3'));\n    ok(!bl.check('172.16.1.6'));\n\n    // Test addSubnet with IPv4\n    bl.addSubnet('10.1.0.0', 24);\n    ok(bl.check('10.1.0.1'));\n    ok(bl.check('10.1.0.255'));\n    ok(!bl.check('10.1.1.1'));\n\n    // Test addSubnet with IPv6\n    bl.addSubnet('2001:db8::', 32, 'ipv6');\n    ok(bl.check('2001:db8::1', 'ipv6'));\n    ok(bl.check('2001:db8:ffff::1', 'ipv6'));\n    ok(!bl.check('2001:db9::1', 'ipv6'));\n\n    // Test addSubnet with SocketAddress\n    const subnetAddr = new net.SocketAddress({\n      address: '172.17.0.0',\n      port: 80,\n    });\n    bl.addSubnet(subnetAddr, 16);\n    ok(bl.check('172.17.1.1'));\n    ok(bl.check('172.17.255.255'));\n    ok(!bl.check('172.18.0.1'));\n\n    // Test rules getter\n    const rules = bl.rules;\n    ok(Array.isArray(rules));\n    ok(rules.length > 0);\n    ok(rules.some((rule) => rule.includes('Address: IPv4 192.168.1.1')));\n    ok(\n      rules.some((rule) =>\n        rule.includes('Range: IPv4 192.168.2.1-192.168.2.10')\n      )\n    );\n    ok(rules.some((rule) => rule.includes('Subnet: IPv4 10.1.0.0/24')));\n\n    // Test toJSON\n    const json = bl.toJSON();\n    ok(Array.isArray(json));\n    deepStrictEqual(json, rules);\n\n    // Test fromJSON\n    const bl2 = new net.BlockList();\n    bl2.fromJSON(['Address: IPv4 203.0.113.1', 'Subnet: IPv4 198.51.100.0/24']);\n    ok(bl2.check('203.0.113.1'));\n    ok(bl2.check('198.51.100.100'));\n    ok(!bl2.check('203.0.113.2'));\n\n    // Test error cases\n    throws(() => bl.addAddress(123), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    throws(() => bl.addAddress('invalid.ip'), {\n      code: 'ERR_INVALID_IP_ADDRESS',\n    });\n\n    throws(() => bl.addAddress('192.168.1.1', 'invalid'), {\n      code: 'ERR_INVALID_ARG_VALUE',\n    });\n\n    throws(() => bl.addRange('192.168.1.10', '192.168.1.1'), {\n      code: 'ERR_INVALID_ARG_VALUE',\n    });\n\n    throws(() => bl.addSubnet('192.168.1.0', 33), {\n      code: 'ERR_OUT_OF_RANGE',\n    });\n\n    throws(() => bl.addSubnet('192.168.1.0', -1), {\n      code: 'ERR_OUT_OF_RANGE',\n    });\n\n    // Test IPv4-mapped IPv6 addresses\n    bl.addAddress('192.168.10.1');\n    ok(bl.check('::ffff:192.168.10.1', 'ipv6'));\n\n    bl.addRange('192.168.11.1', '192.168.11.10');\n    ok(bl.check('::ffff:192.168.11.5', 'ipv6'));\n\n    bl.addSubnet('192.168.12.0', 24);\n    ok(bl.check('::ffff:192.168.12.100', 'ipv6'));\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/net-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"net-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"net-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"url_standard\"],\n        bindings = [\n          (name = \"SIDECAR_HOSTNAME\", fromEnvironment = \"SIDECAR_HOSTNAME\"),\n          (name = \"SERVER_PORT\", fromEnvironment = \"SERVER_PORT\"),\n          (name = \"ECHO_SERVER_PORT\", fromEnvironment = \"ECHO_SERVER_PORT\"),\n          (name = \"TIMEOUT_SERVER_PORT\", fromEnvironment = \"TIMEOUT_SERVER_PORT\"),\n          (name = \"END_SERVER_PORT\", fromEnvironment = \"END_SERVER_PORT\"),\n          (name = \"SERVER_THAT_DIES_PORT\", fromEnvironment = \"SERVER_THAT_DIES_PORT\"),\n          (name = \"RECONNECT_SERVER_PORT\", fromEnvironment = \"RECONNECT_SERVER_PORT\"),\n        ]\n      )\n    ),\n    ( name = \"internet\", network = ( allow = [\"private\"] ) ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/node-compat-v2-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n\n// Imports of Node.js built-ins should work both with and without\n// the 'node:' prefix.\nimport { default as assert } from 'node:assert';\nimport { default as assert2 } from 'assert';\nimport { AsyncLocalStorage } from 'async_hooks';\n\nconst assert3 = (await import('node:assert')).default;\nconst assert4 = (await import('assert')).default;\n\nassert.strictEqual(assert, assert2);\nassert.strictEqual(assert, assert3);\nassert.strictEqual(assert, assert4);\n\nexport const nodeJsExpectedGlobals = {\n  async test() {\n    // Expected Node.js globals Buffer, process, and global should be present.\n    const { Buffer } = await import('node:buffer');\n    assert.strictEqual(Buffer, globalThis.Buffer);\n\n    const { default: process } = await import('node:process');\n    assert.strictEqual(process, globalThis.process);\n\n    assert.strictEqual(global, globalThis);\n  },\n};\n\nexport const nodeJsGetBuiltins = {\n  async test() {\n    // node:* modules in the worker bundle should override the built-in modules...\n    const { default: fs } = await import('node:fs');\n    const { default: path } = await import('node:path');\n\n    await import('node:path');\n\n    // But process.getBuiltinModule should always return the built-in module.\n    const builtInPath = process.getBuiltinModule('node:path');\n    const builtInVm = process.getBuiltinModule('node:vm');\n\n    // The built-in node:vm module is not available in workers, so this should\n    // return undefined.\n    assert.strictEqual(builtInVm, undefined);\n\n    // These are from the worker bundle....\n    assert.strictEqual(fs, 1);\n    assert.strictEqual(path, 2);\n\n    // node:path is implemented tho...\n    assert.notStrictEqual(path, builtInPath);\n    assert.strictEqual(typeof builtInPath, 'object');\n    assert.strictEqual(typeof builtInPath.join, 'function');\n\n    // While process.getBuiltinModule(...) in Node.js only returns Node.js\n    // built-ins, our impl will return cloudflare: and workerd: built-ins\n    // also, for completeness. A key difference, however, is that for non-Node.js\n    // built-ins, the return value is the module namespace rather than the default\n    // export.\n\n    const socket = await import('cloudflare:sockets');\n    assert.strictEqual(process.getBuiltinModule('cloudflare:sockets'), socket);\n  },\n};\n\nexport const nodeJsEventsExports = {\n  async test() {\n    // Expected node:events exports should be present\n    const { EventEmitter, getMaxListeners, usingDomains } =\n      await import('node:events');\n    assert.notEqual(getMaxListeners, undefined);\n    assert.strictEqual(getMaxListeners, EventEmitter.getMaxListeners);\n    assert.notEqual(usingDomains, undefined);\n    assert.strictEqual(usingDomains, EventEmitter.usingDomains);\n  },\n};\n\nexport const nodeJsBufferExports = {\n  async test() {\n    // Expected node:buffer exports should be present\n    const { atob, btoa, Blob } = await import('node:buffer');\n    assert.notEqual(atob, undefined);\n    assert.notEqual(btoa, undefined);\n    // We cannot do strictEqual with atob and globalThis.atob\n    // since, buffer module exports them with `.bind(globalThis)`\n    assert.strictEqual(typeof atob, 'function');\n    assert.strictEqual(typeof btoa, 'function');\n    assert.notEqual(Blob, undefined);\n    assert.strictEqual(Blob, globalThis.Blob);\n  },\n};\n\nexport const nodeJsSetImmediate = {\n  async test() {\n    const als = new AsyncLocalStorage();\n    const { promise, resolve } = Promise.withResolvers();\n    als.run('abc', () =>\n      setImmediate((a) => {\n        assert.strictEqual(als.getStore(), 'abc');\n        resolve(a);\n      }, 1)\n    );\n    assert.strictEqual(await promise, 1);\n\n    const i = setImmediate(() => {\n      throw new Error('should not have fired');\n    });\n    i[Symbol.dispose](); // Calls clear immediate\n    i[Symbol.dispose](); // Should be a no-op\n\n    const i2 = setImmediate(() => {\n      throw new Error('should not have fired');\n    });\n    clearImmediate(i2);\n    clearImmediate(i2); // clearing twice works fine\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/node-compat-v2-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"node-compat-v2-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"node-compat-v2-test.js\"),\n          (name = \"node:fs\", esModule = \"export default 1\"),\n          (name = \"node:path\", esModule = \"export default 2\"),\n        ],\n        compatibilityFlags = [\"nodejs_compat_v2\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/os-test.js",
    "content": "import os from 'node:os';\nimport { strictEqual, deepStrictEqual, throws } from 'node:assert';\n\nexport const osTest = {\n  test() {\n    strictEqual(os.EOL, '\\n');\n    strictEqual(os.devNull, '/dev/null');\n    strictEqual(os.availableParallelism(), 1);\n    strictEqual(os.arch(), 'x64');\n    deepStrictEqual(os.cpus(), []);\n    strictEqual(os.endianness(), 'LE');\n    strictEqual(os.freemem(), 0);\n    strictEqual(os.getPriority(), 0);\n    strictEqual(os.homedir(), '/tmp/');\n    strictEqual(os.hostname(), 'localhost');\n    deepStrictEqual(os.loadavg(), [0, 0, 0]);\n    strictEqual(os.machine(), 'x86_64');\n    deepStrictEqual(os.networkInterfaces(), {});\n    strictEqual(os.platform(), 'linux');\n    strictEqual(os.release(), '');\n    strictEqual(os.tmpdir(), '/tmp/');\n    strictEqual(os.totalmem(), 0);\n    strictEqual(os.type(), 'Linux');\n    strictEqual(os.uptime(), 0);\n    deepStrictEqual(os.userInfo(), {});\n    strictEqual(os.version(), '');\n\n    // setPriority and getPriority are non-ops\n    os.setPriority(1, 2);\n    strictEqual(os.getPriority(1), 0);\n    throws(() => os.setPriority('a', 2), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => os.setPriority(1, 'a'), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => os.getPriority('a'), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => os.userInfo(1), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => os.userInfo({ encoding: 'bad encoding' }), {\n      code: 'ERR_INVALID_ARG_VALUE',\n    });\n    throws(() => os.userInfo({ encoding: 123 }), {\n      code: 'ERR_INVALID_ARG_VALUE',\n    });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/os-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"os-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"os-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_os_module\", \"experimental\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/path-test.js",
    "content": "import {\n  deepEqual,\n  deepStrictEqual,\n  doesNotMatch,\n  doesNotReject,\n  doesNotThrow,\n  equal,\n  fail,\n  ifError,\n  match,\n  notDeepEqual,\n  notDeepStrictEqual,\n  notEqual,\n  notStrictEqual,\n  ok,\n  rejects,\n  strictEqual,\n  throws,\n} from 'node:assert';\n\nimport { default as path } from 'node:path';\n\nexport const test_path = {\n  test(ctrl, env, ctx) {\n    // Test thrown TypeErrors\n    const typeErrorTests = [true, false, 7, null, {}, undefined, [], NaN];\n\n    function fail(fn) {\n      const args = Array.from(arguments).slice(1);\n\n      throws(\n        () => {\n          fn.apply(null, args);\n        },\n        { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' }\n      );\n    }\n\n    typeErrorTests.forEach((test) => {\n      [path.posix].forEach((namespace) => {\n        fail(namespace.join, test);\n        fail(namespace.resolve, test);\n        fail(namespace.normalize, test);\n        fail(namespace.isAbsolute, test);\n        fail(namespace.relative, test, 'foo');\n        fail(namespace.relative, 'foo', test);\n        fail(namespace.parse, test);\n        fail(namespace.dirname, test);\n        fail(namespace.basename, test);\n        fail(namespace.extname, test);\n\n        // Undefined is a valid value as the second argument to basename\n        if (test !== undefined) {\n          fail(namespace.basename, 'foo', test);\n        }\n      });\n    });\n\n    // path.sep tests\n    // windows\n    strictEqual(path.win32.sep, '\\\\');\n    // posix\n    strictEqual(path.posix.sep, '/');\n\n    // path.delimiter tests\n    // windows\n    strictEqual(path.win32.delimiter, ';');\n    // posix\n    strictEqual(path.posix.delimiter, ':');\n\n    strictEqual(path, path.posix);\n  },\n};\n\nexport const test_path_zero_length_strings = {\n  test(ctrl, env, ctx) {\n    // Join will internally ignore all the zero-length strings and it will return\n    // '.' if the joined string is a zero-length string.\n    strictEqual(path.posix.join(''), '.');\n    strictEqual(path.posix.join('', ''), '.');\n    strictEqual(path.join('/'), '/');\n    strictEqual(path.join('/', ''), '/');\n\n    // Normalize will return '.' if the input is a zero-length string\n    strictEqual(path.posix.normalize(''), '.');\n    strictEqual(path.normalize('/'), '/');\n\n    // Since '' is not a valid path in any of the common environments, return false\n    strictEqual(path.posix.isAbsolute(''), false);\n\n    // Resolve, internally ignores all the zero-length strings and returns the\n    // current working directory\n    strictEqual(path.resolve(''), '/');\n    strictEqual(path.resolve('', ''), '/');\n\n    // Relative, internally calls resolve. So, '' is actually the current directory\n    strictEqual(path.relative('', '/'), '');\n    strictEqual(path.relative('/', ''), '');\n    strictEqual(path.relative('/', '/'), '');\n  },\n};\n\n// Ref: https://github.com/nodejs/node/blob/4d6d7d644be4f10f90e5c9c66563736112fffbff/test/parallel/test-path-resolve.js\nexport const test_path_resolve = {\n  test(ctrl, env, ctx) {\n    const failures = [];\n    const posixyCwd = '/';\n\n    const resolveTests = [\n      [['/var/lib', '../', 'file/'], '/var/file'],\n      [['/var/lib', '/../', 'file/'], '/file'],\n      [['a/b/c/', '../../..'], posixyCwd],\n      [['.'], posixyCwd],\n      [['/some/dir', '.', '/absolute/'], '/absolute'],\n      [['/foo/tmp.3/', '../tmp.3/cycles/root.js'], '/foo/tmp.3/cycles/root.js'],\n    ];\n    resolveTests.forEach(([test, expected]) => {\n      const actual = path.resolve.apply(null, test);\n      const message = `path.posix.resolve(${test.map(JSON.stringify).join(',')})\\n  expect=${JSON.stringify(\n        expected\n      )}\\n  actual=${JSON.stringify(actual)}`;\n      if (actual !== expected) failures.push(message);\n    });\n    strictEqual(failures.length, 0, failures.join('\\n'));\n  },\n};\n\n// Ref: https://github.com/nodejs/node/blob/4d6d7d644be4f10f90e5c9c66563736112fffbff/test/parallel/test-path-relative.js\nexport const test_path_relative = {\n  test(ctrl, env, ctx) {\n    const failures = [];\n\n    const relativeTests = [\n      ['/var/lib', '/var', '..'],\n      ['/var/lib', '/bin', '../../bin'],\n      ['/var/lib', '/var/lib', ''],\n      ['/var/lib', '/var/apache', '../apache'],\n      ['/var/', '/var/lib', 'lib'],\n      ['/', '/var/lib', 'var/lib'],\n      ['/foo/test', '/foo/test/bar/package.json', 'bar/package.json'],\n      ['/Users/a/web/b/test/mails', '/Users/a/web/b', '../..'],\n      ['/foo/bar/baz-quux', '/foo/bar/baz', '../baz'],\n      ['/foo/bar/baz', '/foo/bar/baz-quux', '../baz-quux'],\n      ['/baz-quux', '/baz', '../baz'],\n      ['/baz', '/baz-quux', '../baz-quux'],\n      ['/page1/page2/foo', '/', '../../..'],\n    ];\n    relativeTests.forEach((test) => {\n      const actual = path.relative(test[0], test[1]);\n      const expected = test[2];\n      if (actual !== expected) {\n        const message = `path.posix.relative(${test\n          .slice(0, 2)\n          .map(JSON.stringify)\n          .join(',')})\\n  expect=${JSON.stringify(\n          expected\n        )}\\n  actual=${JSON.stringify(actual)}`;\n        failures.push(`\\n${message}`);\n      }\n    });\n    strictEqual(failures.length, 0, failures.join(''));\n  },\n};\n\nexport const test_path_parse_format = {\n  test(ctrl, env, ctx) {\n    const unixPaths = [\n      // [path, root]\n      ['/home/user/dir/file.txt', '/'],\n      ['/home/user/a dir/another File.zip', '/'],\n      ['/home/user/a dir//another&File.', '/'],\n      ['/home/user/a$$$dir//another File.zip', '/'],\n      ['user/dir/another File.zip', ''],\n      ['file', ''],\n      ['.\\\\file', ''],\n      ['./file', ''],\n      ['C:\\\\foo', ''],\n      ['/', '/'],\n      ['', ''],\n      ['.', ''],\n      ['..', ''],\n      ['/foo', '/'],\n      ['/foo.', '/'],\n      ['/foo.bar', '/'],\n      ['/.', '/'],\n      ['/.foo', '/'],\n      ['/.foo.bar', '/'],\n      ['/foo/bar.baz', '/'],\n    ];\n\n    const unixSpecialCaseFormatTests = [\n      [{ dir: 'some/dir' }, 'some/dir/'],\n      [{ base: 'index.html' }, 'index.html'],\n      [{ root: '/' }, '/'],\n      [{ name: 'index', ext: '.html' }, 'index.html'],\n      [{ dir: 'some/dir', name: 'index', ext: '.html' }, 'some/dir/index.html'],\n      [{ root: '/', name: 'index', ext: '.html' }, '/index.html'],\n      [{}, ''],\n    ];\n\n    const errors = [\n      { method: 'parse', input: [null] },\n      { method: 'parse', input: [{}] },\n      { method: 'parse', input: [true] },\n      { method: 'parse', input: [1] },\n      { method: 'parse', input: [] },\n      { method: 'format', input: [null] },\n      { method: 'format', input: [''] },\n      { method: 'format', input: [true] },\n      { method: 'format', input: [1] },\n    ];\n\n    checkParseFormat(unixPaths);\n    checkErrors();\n    checkFormat(unixSpecialCaseFormatTests);\n\n    // Test removal of trailing path separators\n    const trailingTests = [\n      ['./', { root: '', dir: '', base: '.', ext: '', name: '.' }],\n      ['//', { root: '/', dir: '/', base: '', ext: '', name: '' }],\n      ['///', { root: '/', dir: '/', base: '', ext: '', name: '' }],\n      ['/foo///', { root: '/', dir: '/', base: 'foo', ext: '', name: 'foo' }],\n      [\n        '/foo///bar.baz',\n        { root: '/', dir: '/foo//', base: 'bar.baz', ext: '.baz', name: 'bar' },\n      ],\n    ];\n    const failures = [];\n    trailingTests.forEach((test) => {\n      const actual = path.parse(test[0]);\n      const expected = test[1];\n      const message = `path.posix.parse(${JSON.stringify(test[0])})\\n  expect=${JSON.stringify(\n        expected\n      )}\\n  actual=${JSON.stringify(actual)}`;\n      const actualKeys = Object.keys(actual);\n      const expectedKeys = Object.keys(expected);\n      let failed = actualKeys.length !== expectedKeys.length;\n      if (!failed) {\n        for (let i = 0; i < actualKeys.length; ++i) {\n          const key = actualKeys[i];\n          if (!expectedKeys.includes(key) || actual[key] !== expected[key]) {\n            failed = true;\n            break;\n          }\n        }\n      }\n      if (failed) failures.push(`\\n${message}`);\n    });\n    strictEqual(failures.length, 0, failures.join(''));\n\n    function checkErrors() {\n      errors.forEach(({ method, input }) => {\n        throws(\n          () => {\n            path[method].apply(path, input);\n          },\n          {\n            code: 'ERR_INVALID_ARG_TYPE',\n            name: 'TypeError',\n          }\n        );\n      });\n    }\n\n    function checkParseFormat(paths) {\n      paths.forEach(([element, root]) => {\n        const output = path.parse(element);\n        strictEqual(typeof output.root, 'string');\n        strictEqual(typeof output.dir, 'string');\n        strictEqual(typeof output.base, 'string');\n        strictEqual(typeof output.ext, 'string');\n        strictEqual(typeof output.name, 'string');\n        strictEqual(path.format(output), element);\n        strictEqual(output.root, root);\n        ok(output.dir.startsWith(output.root));\n        strictEqual(output.dir, output.dir ? path.dirname(element) : '');\n        strictEqual(output.base, path.basename(element));\n        strictEqual(output.ext, path.extname(element));\n      });\n    }\n\n    function checkFormat(testCases) {\n      testCases.forEach(([element, expect]) => {\n        strictEqual(path.format(element), expect);\n      });\n\n      [null, undefined, 1, true, false, 'string'].forEach((pathObject) => {\n        throws(\n          () => {\n            path.format(pathObject);\n          },\n          {\n            code: 'ERR_INVALID_ARG_TYPE',\n            name: 'TypeError',\n          }\n        );\n      });\n    }\n\n    // See https://github.com/nodejs/node/issues/44343\n    strictEqual(path.format({ name: 'x', ext: 'png' }), 'x.png');\n    strictEqual(path.format({ name: 'x', ext: '.png' }), 'x.png');\n  },\n};\n\nexport const test_path_normalize = {\n  test(ctrl, env, ctx) {\n    strictEqual(\n      path.win32.normalize('./fixtures///b/../b/c.js'),\n      'fixtures\\\\b\\\\c.js'\n    );\n    strictEqual(path.win32.normalize('/foo/../../../bar'), '\\\\bar');\n    strictEqual(path.win32.normalize('a//b//../b'), 'a\\\\b');\n    strictEqual(path.win32.normalize('a//b//./c'), 'a\\\\b\\\\c');\n    strictEqual(path.win32.normalize('a//b//.'), 'a\\\\b');\n    strictEqual(\n      path.win32.normalize('//server/share/dir/file.ext'),\n      '\\\\\\\\server\\\\share\\\\dir\\\\file.ext'\n    );\n    strictEqual(path.win32.normalize('/a/b/c/../../../x/y/z'), '\\\\x\\\\y\\\\z');\n    strictEqual(path.win32.normalize('C:'), 'C:.');\n    strictEqual(path.win32.normalize('C:..\\\\abc'), 'C:..\\\\abc');\n    strictEqual(\n      path.win32.normalize('C:..\\\\..\\\\abc\\\\..\\\\def'),\n      'C:..\\\\..\\\\def'\n    );\n    strictEqual(path.win32.normalize('C:\\\\.'), 'C:\\\\');\n    strictEqual(path.win32.normalize('file:stream'), 'file:stream');\n    strictEqual(path.win32.normalize('bar\\\\foo..\\\\..\\\\'), 'bar\\\\');\n    strictEqual(path.win32.normalize('bar\\\\foo..\\\\..'), 'bar');\n    strictEqual(path.win32.normalize('bar\\\\foo..\\\\..\\\\baz'), 'bar\\\\baz');\n    strictEqual(path.win32.normalize('bar\\\\foo..\\\\'), 'bar\\\\foo..\\\\');\n    strictEqual(path.win32.normalize('bar\\\\foo..'), 'bar\\\\foo..');\n    strictEqual(path.win32.normalize('..\\\\foo..\\\\..\\\\..\\\\bar'), '..\\\\..\\\\bar');\n    strictEqual(\n      path.win32.normalize('..\\\\...\\\\..\\\\.\\\\...\\\\..\\\\..\\\\bar'),\n      '..\\\\..\\\\bar'\n    );\n    strictEqual(\n      path.win32.normalize('../../../foo/../../../bar'),\n      '..\\\\..\\\\..\\\\..\\\\..\\\\bar'\n    );\n    strictEqual(\n      path.win32.normalize('../../../foo/../../../bar/../../'),\n      '..\\\\..\\\\..\\\\..\\\\..\\\\..\\\\'\n    );\n    strictEqual(\n      path.win32.normalize('../foobar/barfoo/foo/../../../bar/../../'),\n      '..\\\\..\\\\'\n    );\n    strictEqual(\n      path.win32.normalize('../.../../foobar/../../../bar/../../baz'),\n      '..\\\\..\\\\..\\\\..\\\\baz'\n    );\n    strictEqual(path.win32.normalize('foo/bar\\\\baz'), 'foo\\\\bar\\\\baz');\n\n    strictEqual(\n      path.posix.normalize('./fixtures///b/../b/c.js'),\n      'fixtures/b/c.js'\n    );\n    strictEqual(path.posix.normalize('/foo/../../../bar'), '/bar');\n    strictEqual(path.posix.normalize('a//b//../b'), 'a/b');\n    strictEqual(path.posix.normalize('a//b//./c'), 'a/b/c');\n    strictEqual(path.posix.normalize('a//b//.'), 'a/b');\n    strictEqual(path.posix.normalize('/a/b/c/../../../x/y/z'), '/x/y/z');\n    strictEqual(path.posix.normalize('///..//./foo/.//bar'), '/foo/bar');\n    strictEqual(path.posix.normalize('bar/foo../../'), 'bar/');\n    strictEqual(path.posix.normalize('bar/foo../..'), 'bar');\n    strictEqual(path.posix.normalize('bar/foo../../baz'), 'bar/baz');\n    strictEqual(path.posix.normalize('bar/foo../'), 'bar/foo../');\n    strictEqual(path.posix.normalize('bar/foo..'), 'bar/foo..');\n    strictEqual(path.posix.normalize('../foo../../../bar'), '../../bar');\n    strictEqual(path.posix.normalize('../.../.././.../../../bar'), '../../bar');\n    strictEqual(\n      path.posix.normalize('../../../foo/../../../bar'),\n      '../../../../../bar'\n    );\n    strictEqual(\n      path.posix.normalize('../../../foo/../../../bar/../../'),\n      '../../../../../../'\n    );\n    strictEqual(\n      path.posix.normalize('../foobar/barfoo/foo/../../../bar/../../'),\n      '../../'\n    );\n    strictEqual(\n      path.posix.normalize('../.../../foobar/../../../bar/../../baz'),\n      '../../../../baz'\n    );\n    strictEqual(path.posix.normalize('foo/bar\\\\baz'), 'foo/bar\\\\baz');\n  },\n};\n\nexport const test_path_join = {\n  test(ctrl, env, ctx) {\n    const failures = [];\n    const backslashRE = /\\\\/g;\n\n    const joinTests = [\n      [['.', 'x/b', '..', '/b/c.js'], 'x/b/c.js'],\n      [[], '.'],\n      [['/.', 'x/b', '..', '/b/c.js'], '/x/b/c.js'],\n      [['/foo', '../../../bar'], '/bar'],\n      [['foo', '../../../bar'], '../../bar'],\n      [['foo/', '../../../bar'], '../../bar'],\n      [['foo/x', '../../../bar'], '../bar'],\n      [['foo/x', './bar'], 'foo/x/bar'],\n      [['foo/x/', './bar'], 'foo/x/bar'],\n      [['foo/x/', '.', 'bar'], 'foo/x/bar'],\n      [['./'], './'],\n      [['.', './'], './'],\n      [['.', '.', '.'], '.'],\n      [['.', './', '.'], '.'],\n      [['.', '/./', '.'], '.'],\n      [['.', '/////./', '.'], '.'],\n      [['.'], '.'],\n      [['', '.'], '.'],\n      [['', 'foo'], 'foo'],\n      [['foo', '/bar'], 'foo/bar'],\n      [['', '/foo'], '/foo'],\n      [['', '', '/foo'], '/foo'],\n      [['', '', 'foo'], 'foo'],\n      [['foo', ''], 'foo'],\n      [['foo/', ''], 'foo/'],\n      [['foo', '', '/bar'], 'foo/bar'],\n      [['./', '..', '/foo'], '../foo'],\n      [['./', '..', '..', '/foo'], '../../foo'],\n      [['.', '..', '..', '/foo'], '../../foo'],\n      [['', '..', '..', '/foo'], '../../foo'],\n      [['/'], '/'],\n      [['/', '.'], '/'],\n      [['/', '..'], '/'],\n      [['/', '..', '..'], '/'],\n      [[''], '.'],\n      [['', ''], '.'],\n      [[' /foo'], ' /foo'],\n      [[' ', 'foo'], ' /foo'],\n      [[' ', '.'], ' '],\n      [[' ', '/'], ' /'],\n      [[' ', ''], ' '],\n      [['/', 'foo'], '/foo'],\n      [['/', '/foo'], '/foo'],\n      [['/', '//foo'], '/foo'],\n      [['/', '', '/foo'], '/foo'],\n      [['', '/', 'foo'], '/foo'],\n      [['', '/', '/foo'], '/foo'],\n    ];\n\n    joinTests.forEach((test) => {\n      const actual = path.join.apply(null, test[0]);\n      const expected = test[1];\n      if (actual !== expected && actualAlt !== expected) {\n        const delimiter = test[0].map(JSON.stringify).join(',');\n        const message = `path.posix.join(${delimiter})\\n  expect=${JSON.stringify(\n          expected\n        )}\\n  actual=${JSON.stringify(actual)}`;\n        failures.push(`\\n${message}`);\n      }\n    });\n    strictEqual(failures.length, 0, failures.join(''));\n  },\n};\n\nexport const test_path_isabsolute = {\n  test(ctrl, env, ctx) {\n    strictEqual(path.posix.isAbsolute('/home/foo'), true);\n    strictEqual(path.posix.isAbsolute('/home/foo/..'), true);\n    strictEqual(path.posix.isAbsolute('bar/'), false);\n    strictEqual(path.posix.isAbsolute('./baz'), false);\n    strictEqual(path.isAbsolute('/home/foo'), true);\n    strictEqual(path.isAbsolute('/home/foo/..'), true);\n    strictEqual(path.isAbsolute('bar/'), false);\n    strictEqual(path.isAbsolute('./baz'), false);\n  },\n};\n\nexport const test_path_extname = {\n  test(ctrl, env, ctx) {\n    const failures = [];\n    const slashRE = /\\//g;\n\n    [\n      ['', ''],\n      ['/path/to/file', ''],\n      ['/path/to/file.ext', '.ext'],\n      ['/path.to/file.ext', '.ext'],\n      ['/path.to/file', ''],\n      ['/path.to/.file', ''],\n      ['/path.to/.file.ext', '.ext'],\n      ['/path/to/f.ext', '.ext'],\n      ['/path/to/..ext', '.ext'],\n      ['/path/to/..', ''],\n      ['file', ''],\n      ['file.ext', '.ext'],\n      ['.file', ''],\n      ['.file.ext', '.ext'],\n      ['/file', ''],\n      ['/file.ext', '.ext'],\n      ['/.file', ''],\n      ['/.file.ext', '.ext'],\n      ['.path/file.ext', '.ext'],\n      ['file.ext.ext', '.ext'],\n      ['file.', '.'],\n      ['.', ''],\n      ['./', ''],\n      ['.file.ext', '.ext'],\n      ['.file', ''],\n      ['.file.', '.'],\n      ['.file..', '.'],\n      ['..', ''],\n      ['../', ''],\n      ['..file.ext', '.ext'],\n      ['..file', '.file'],\n      ['..file.', '.'],\n      ['..file..', '.'],\n      ['...', '.'],\n      ['...ext', '.ext'],\n      ['....', '.'],\n      ['file.ext/', '.ext'],\n      ['file.ext//', '.ext'],\n      ['file/', ''],\n      ['file//', ''],\n      ['file./', '.'],\n      ['file.//', '.'],\n    ].forEach((test) => {\n      const expected = test[1];\n      const actual = path.extname(test[0]);\n      const message = `path.posix.extname(${JSON.stringify(test[0])})\\n  expect=${JSON.stringify(\n        expected\n      )}\\n  actual=${JSON.stringify(actual)}`;\n      if (actual !== expected) failures.push(`\\n${message}`);\n    });\n\n    strictEqual(failures.length, 0, failures.join(''));\n\n    // On *nix, backslash is a valid name component like any other character.\n    strictEqual(path.posix.extname('.\\\\'), '');\n    strictEqual(path.posix.extname('..\\\\'), '.\\\\');\n    strictEqual(path.posix.extname('file.ext\\\\'), '.ext\\\\');\n    strictEqual(path.posix.extname('file.ext\\\\\\\\'), '.ext\\\\\\\\');\n    strictEqual(path.posix.extname('file\\\\'), '');\n    strictEqual(path.posix.extname('file\\\\\\\\'), '');\n    strictEqual(path.posix.extname('file.\\\\'), '.\\\\');\n    strictEqual(path.posix.extname('file.\\\\\\\\'), '.\\\\\\\\');\n  },\n};\n\nexport const test_path_dirname = {\n  test(ctrl, env, ctx) {\n    strictEqual(path.posix.dirname('/a/b/'), '/a');\n    strictEqual(path.posix.dirname('/a/b'), '/a');\n    strictEqual(path.posix.dirname('/a'), '/');\n    strictEqual(path.posix.dirname(''), '.');\n    strictEqual(path.posix.dirname('/'), '/');\n    strictEqual(path.posix.dirname('////'), '/');\n    strictEqual(path.posix.dirname('//a'), '//');\n    strictEqual(path.posix.dirname('foo'), '.');\n    strictEqual(path.dirname('/a/b/'), '/a');\n    strictEqual(path.dirname('/a/b'), '/a');\n    strictEqual(path.dirname('/a'), '/');\n    strictEqual(path.dirname(''), '.');\n    strictEqual(path.dirname('/'), '/');\n    strictEqual(path.dirname('////'), '/');\n    strictEqual(path.dirname('//a'), '//');\n    strictEqual(path.dirname('foo'), '.');\n  },\n};\n\nexport const test_path_basename = {\n  test(ctrl, env, ctx) {\n    strictEqual(path.basename('.js', '.js'), '');\n    strictEqual(path.basename('js', '.js'), 'js');\n    strictEqual(path.basename('file.js', '.ts'), 'file.js');\n    strictEqual(path.basename('file', '.js'), 'file');\n    strictEqual(path.basename('file.js.old', '.js.old'), 'file');\n    strictEqual(path.basename(''), '');\n    strictEqual(path.basename('/dir/basename.ext'), 'basename.ext');\n    strictEqual(path.basename('/basename.ext'), 'basename.ext');\n    strictEqual(path.basename('basename.ext'), 'basename.ext');\n    strictEqual(path.basename('basename.ext/'), 'basename.ext');\n    strictEqual(path.basename('basename.ext//'), 'basename.ext');\n    strictEqual(path.basename('aaa/bbb', '/bbb'), 'bbb');\n    strictEqual(path.basename('aaa/bbb', 'a/bbb'), 'bbb');\n    strictEqual(path.basename('aaa/bbb', 'bbb'), 'bbb');\n    strictEqual(path.basename('aaa/bbb//', 'bbb'), 'bbb');\n    strictEqual(path.basename('aaa/bbb', 'bb'), 'b');\n    strictEqual(path.basename('aaa/bbb', 'b'), 'bb');\n    strictEqual(path.basename('/aaa/bbb', '/bbb'), 'bbb');\n    strictEqual(path.basename('/aaa/bbb', 'a/bbb'), 'bbb');\n    strictEqual(path.basename('/aaa/bbb', 'bbb'), 'bbb');\n    strictEqual(path.basename('/aaa/bbb//', 'bbb'), 'bbb');\n    strictEqual(path.basename('/aaa/bbb', 'bb'), 'b');\n    strictEqual(path.basename('/aaa/bbb', 'b'), 'bb');\n    strictEqual(path.basename('/aaa/bbb'), 'bbb');\n    strictEqual(path.basename('/aaa/'), 'aaa');\n    strictEqual(path.basename('/aaa/b'), 'b');\n    strictEqual(path.basename('/a/b'), 'b');\n    strictEqual(path.basename('//a'), 'a');\n    strictEqual(path.basename('a', 'a'), '');\n\n    // On unix a backslash is just treated as any other character.\n    strictEqual(\n      path.posix.basename('\\\\dir\\\\basename.ext'),\n      '\\\\dir\\\\basename.ext'\n    );\n    strictEqual(path.posix.basename('\\\\basename.ext'), '\\\\basename.ext');\n    strictEqual(path.posix.basename('basename.ext'), 'basename.ext');\n    strictEqual(path.posix.basename('basename.ext\\\\'), 'basename.ext\\\\');\n    strictEqual(path.posix.basename('basename.ext\\\\\\\\'), 'basename.ext\\\\\\\\');\n    strictEqual(path.posix.basename('foo'), 'foo');\n\n    // POSIX filenames may include control characters\n    // c.f. http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html\n    const controlCharFilename = `Icon${String.fromCharCode(13)}`;\n    strictEqual(\n      path.posix.basename(`/a/b/${controlCharFilename}`),\n      controlCharFilename\n    );\n  },\n};\n\nexport const make_sure_posix_win32_works = {\n  async test() {\n    {\n      const pathModule = await import('node:path/win32');\n      strictEqual(typeof pathModule.default.resolve, 'function');\n      strictEqual(\n        typeof process.getBuiltinModule('node:path/win32').resolve,\n        'function'\n      );\n    }\n    {\n      const pathModule = await import('node:path/posix');\n      strictEqual(typeof pathModule.default.resolve, 'function');\n      strictEqual(\n        typeof process.getBuiltinModule('node:path/posix').resolve,\n        'function'\n      );\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/path-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"path-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"path-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/perf-hooks-nodejs-test.js",
    "content": "import {\n  performance as perfHooksPerformance,\n  Performance,\n  PerformanceEntry,\n  PerformanceMark,\n  PerformanceMeasure,\n  PerformanceObserver,\n  PerformanceObserverEntryList,\n  PerformanceResourceTiming,\n  createHistogram,\n  monitorEventLoopDelay,\n  eventLoopUtilization,\n  timerify,\n  constants,\n} from 'node:perf_hooks';\nimport {\n  deepStrictEqual,\n  doesNotThrow,\n  ok,\n  throws,\n  strictEqual,\n  notStrictEqual,\n} from 'node:assert';\n\nexport const testPerformanceExports = {\n  test() {\n    // Test that all expected exports exist\n    ok(performance);\n    ok(Performance);\n    ok(PerformanceEntry);\n    ok(PerformanceMark);\n    ok(PerformanceMeasure);\n    ok(PerformanceObserver);\n    ok(PerformanceObserverEntryList);\n    ok(PerformanceResourceTiming);\n    ok(createHistogram);\n    ok(monitorEventLoopDelay);\n    ok(eventLoopUtilization);\n    ok(timerify);\n    ok(constants);\n\n    // Test that performance is an instance of Performance\n    ok(perfHooksPerformance instanceof Performance);\n  },\n};\n\nexport const testPerformanceConstants = {\n  test() {\n    deepStrictEqual(constants, {\n      // GC type constants\n      NODE_PERFORMANCE_GC_MAJOR: 4,\n      NODE_PERFORMANCE_GC_MINOR: 1,\n      NODE_PERFORMANCE_GC_INCREMENTAL: 8,\n      NODE_PERFORMANCE_GC_WEAKCB: 16,\n\n      // GC flags constants\n      NODE_PERFORMANCE_GC_FLAGS_NO: 0,\n      NODE_PERFORMANCE_GC_FLAGS_CONSTRUCT_RETAINED: 2,\n      NODE_PERFORMANCE_GC_FLAGS_FORCED: 4,\n      NODE_PERFORMANCE_GC_FLAGS_SYNCHRONOUS_PHANTOM_PROCESSING: 8,\n      NODE_PERFORMANCE_GC_FLAGS_ALL_AVAILABLE_GARBAGE: 16,\n      NODE_PERFORMANCE_GC_FLAGS_ALL_EXTERNAL_MEMORY: 32,\n      NODE_PERFORMANCE_GC_FLAGS_SCHEDULE_IDLE: 64,\n\n      // Entry type constants\n      NODE_PERFORMANCE_ENTRY_TYPE_GC: 0,\n      NODE_PERFORMANCE_ENTRY_TYPE_HTTP: 1,\n      NODE_PERFORMANCE_ENTRY_TYPE_HTTP2: 2,\n      NODE_PERFORMANCE_ENTRY_TYPE_NET: 3,\n      NODE_PERFORMANCE_ENTRY_TYPE_DNS: 4,\n\n      // Milestone constants\n      NODE_PERFORMANCE_MILESTONE_TIME_ORIGIN_TIMESTAMP: 0,\n      NODE_PERFORMANCE_MILESTONE_TIME_ORIGIN: 1,\n      NODE_PERFORMANCE_MILESTONE_ENVIRONMENT: 2,\n      NODE_PERFORMANCE_MILESTONE_NODE_START: 3,\n      NODE_PERFORMANCE_MILESTONE_V8_START: 4,\n      NODE_PERFORMANCE_MILESTONE_LOOP_START: 5,\n      NODE_PERFORMANCE_MILESTONE_LOOP_EXIT: 6,\n      NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE: 7,\n    });\n    ok(Object.isFrozen(constants));\n  },\n};\n\nexport const testPerformanceBasicFunctionality = {\n  test() {\n    // Test that performance.now() works\n    const start = perfHooksPerformance.now();\n    ok(typeof start === 'number');\n    ok(start >= 0, 'start should be bigger than or equal to 0');\n\n    // Test that consecutive calls return increasing values\n    const end = perfHooksPerformance.now();\n    ok(end >= start, 'end should be bigger than or equal to start');\n\n    // Test performance.timeOrigin\n    ok(typeof perfHooksPerformance.timeOrigin === 'number');\n    ok(\n      perfHooksPerformance.timeOrigin >= 0,\n      'timeOrigin should be bigger than or equal to 0'\n    );\n  },\n};\n\nexport const testUnimplementedMethods = {\n  test() {\n    // These methods throw because they cannot be meaningfully stubbed\n    throws(() => createHistogram(), { message: /is not implemented/ });\n    throws(() => monitorEventLoopDelay(), {\n      message: /is not implemented/,\n    });\n  },\n};\n\nexport const testStandaloneEventLoopUtilization = {\n  test() {\n    // eventLoopUtilization returns stub values for compatibility\n    const result = eventLoopUtilization();\n    ok(typeof result === 'object', 'should return an object');\n    strictEqual(result.idle, 0, 'idle should be 0');\n    strictEqual(result.active, 0, 'active should be 0');\n    strictEqual(result.utilization, 0, 'utilization should be 0');\n\n    // Should accept optional parameters without error\n    const result2 = eventLoopUtilization(result);\n    ok(\n      typeof result2 === 'object',\n      'should return an object with one argument'\n    );\n\n    const result3 = eventLoopUtilization(result, result2);\n    ok(\n      typeof result3 === 'object',\n      'should return an object with two arguments'\n    );\n  },\n};\n\nexport const testStandaloneTimerify = {\n  test() {\n    // timerify returns the function as-is (stub behavior)\n    const testFn = () => 42;\n    const timerified = timerify(testFn);\n\n    strictEqual(timerified, testFn, 'timerify should return the same function');\n    strictEqual(timerified(), 42, 'timerified function should work correctly');\n  },\n};\n\nexport const testPerformanceNodeTiming = {\n  test() {\n    // nodeTiming is a Node.js-specific property available on both\n    // the perf_hooks performance export and globalThis.performance\n    ok(\n      perfHooksPerformance.nodeTiming,\n      'nodeTiming should exist on perf_hooks'\n    );\n    ok(\n      globalThis.performance.nodeTiming,\n      'nodeTiming should exist on globalThis'\n    );\n\n    const nodeTiming = perfHooksPerformance.nodeTiming;\n    strictEqual(nodeTiming.name, 'node', 'name should be \"node\"');\n    strictEqual(nodeTiming.entryType, 'node', 'entryType should be \"node\"');\n    strictEqual(\n      typeof nodeTiming.startTime,\n      'number',\n      'startTime should be a number'\n    );\n    strictEqual(\n      typeof nodeTiming.duration,\n      'number',\n      'duration should be a number'\n    );\n    strictEqual(\n      typeof nodeTiming.nodeStart,\n      'number',\n      'nodeStart should be a number'\n    );\n    strictEqual(\n      typeof nodeTiming.v8Start,\n      'number',\n      'v8Start should be a number'\n    );\n    strictEqual(\n      typeof nodeTiming.bootstrapComplete,\n      'number',\n      'bootstrapComplete should be a number'\n    );\n    strictEqual(\n      typeof nodeTiming.environment,\n      'number',\n      'environment should be a number'\n    );\n    strictEqual(\n      typeof nodeTiming.loopStart,\n      'number',\n      'loopStart should be a number'\n    );\n    strictEqual(\n      typeof nodeTiming.loopExit,\n      'number',\n      'loopExit should be a number'\n    );\n    strictEqual(\n      typeof nodeTiming.idleTime,\n      'number',\n      'idleTime should be a number'\n    );\n\n    // uvMetricsInfo should return an object with libuv metrics (stub values)\n    ok(\n      typeof nodeTiming.uvMetricsInfo === 'object',\n      'uvMetricsInfo should be an object'\n    );\n    strictEqual(\n      nodeTiming.uvMetricsInfo.loopCount,\n      0,\n      'uvMetricsInfo.loopCount should be 0'\n    );\n    strictEqual(\n      nodeTiming.uvMetricsInfo.events,\n      0,\n      'uvMetricsInfo.events should be 0'\n    );\n    strictEqual(\n      nodeTiming.uvMetricsInfo.eventsWaiting,\n      0,\n      'uvMetricsInfo.eventsWaiting should be 0'\n    );\n\n    // Verify that nodeTiming properties are instance (own) properties, not prototype properties\n    // This matches Node.js behavior where Reflect.ownKeys(performance.nodeTiming) includes\n    // all properties like nodeStart, v8Start, etc.\n    const ownKeys = Reflect.ownKeys(nodeTiming);\n    ok(ownKeys.includes('nodeStart'), 'nodeStart should be an own property');\n    ok(ownKeys.includes('v8Start'), 'v8Start should be an own property');\n    ok(\n      ownKeys.includes('bootstrapComplete'),\n      'bootstrapComplete should be an own property'\n    );\n    ok(\n      ownKeys.includes('environment'),\n      'environment should be an own property'\n    );\n    ok(ownKeys.includes('loopStart'), 'loopStart should be an own property');\n    ok(ownKeys.includes('loopExit'), 'loopExit should be an own property');\n    ok(ownKeys.includes('idleTime'), 'idleTime should be an own property');\n    ok(\n      ownKeys.includes('uvMetricsInfo'),\n      'uvMetricsInfo should be an own property'\n    );\n\n    // Verify prototype only has constructor and toJSON (matching Node.js behavior)\n    const protoKeys = Reflect.ownKeys(Object.getPrototypeOf(nodeTiming));\n    ok(protoKeys.includes('constructor'), 'prototype should have constructor');\n    ok(protoKeys.includes('toJSON'), 'prototype should have toJSON');\n\n    // toJSON should work and include uvMetricsInfo\n    ok(typeof nodeTiming.toJSON === 'function', 'toJSON should be a function');\n    const json = nodeTiming.toJSON();\n    ok(typeof json === 'object', 'toJSON should return an object');\n    ok(\n      typeof json.uvMetricsInfo === 'object',\n      'toJSON should include uvMetricsInfo'\n    );\n  },\n};\n\nexport const testPerformanceEventLoopUtilization = {\n  test() {\n    // performance.eventLoopUtilization() should return stub values (not throw)\n    const result = perfHooksPerformance.eventLoopUtilization();\n    ok(typeof result === 'object', 'should return an object');\n    strictEqual(result.idle, 0, 'idle should be 0');\n    strictEqual(result.active, 0, 'active should be 0');\n    strictEqual(result.utilization, 0, 'utilization should be 0');\n  },\n};\n\nexport const testPerformanceTimerify = {\n  test() {\n    // performance.timerify() should return the function as-is (stub behavior)\n    const testFn = () => 'test';\n    const timerified = perfHooksPerformance.timerify(testFn);\n\n    strictEqual(timerified, testFn, 'timerify should return the same function');\n    strictEqual(\n      timerified(),\n      'test',\n      'timerified function should work correctly'\n    );\n  },\n};\n\nexport const testPerformanceMarkResourceTiming = {\n  test() {\n    // markResourceTiming should not throw (no-op stub)\n    perfHooksPerformance.markResourceTiming();\n    // If we get here without throwing, the test passes\n    ok(true, 'markResourceTiming should not throw');\n  },\n};\n\nexport const testPerformanceMark = {\n  test() {\n    perfHooksPerformance.clearMarks();\n\n    const mark1 = perfHooksPerformance.mark('test-mark-1');\n    ok(\n      mark1 instanceof PerformanceMark,\n      'mark should return a PerformanceMark instance'\n    );\n    strictEqual(mark1.name, 'test-mark-1', 'mark name should match');\n    strictEqual(mark1.entryType, 'mark', 'entryType should be \"mark\"');\n    ok(typeof mark1.startTime === 'number', 'startTime should be a number');\n    strictEqual(mark1.duration, 0, 'duration should be 0 for marks');\n\n    const customTime = 100;\n    const mark2 = perfHooksPerformance.mark('test-mark-2', {\n      startTime: customTime,\n    });\n    strictEqual(mark2.startTime, customTime, 'custom startTime should be set');\n\n    const detail = { key: 'value' };\n    const mark3 = perfHooksPerformance.mark('test-mark-3', { detail });\n    ok(mark3.detail, 'detail should be set');\n    deepStrictEqual(mark3.detail, detail, 'detail should match');\n\n    const entries = perfHooksPerformance.getEntries();\n    ok(entries.length >= 3, 'should have at least 3 entries');\n\n    const marks = perfHooksPerformance.getEntriesByType('mark');\n    ok(marks.length >= 3, 'should have at least 3 marks');\n\n    perfHooksPerformance.clearMarks();\n  },\n};\n\nexport const testPerformanceMeasure = {\n  test() {\n    perfHooksPerformance.clearMarks();\n    perfHooksPerformance.clearMeasures();\n\n    const mark1 = perfHooksPerformance.mark('start-mark');\n    const mark2 = perfHooksPerformance.mark('end-mark');\n\n    const measure1 = perfHooksPerformance.measure(\n      'test-measure-1',\n      'start-mark',\n      'end-mark'\n    );\n    ok(\n      measure1 instanceof PerformanceMeasure,\n      'measure should return a PerformanceMeasure instance'\n    );\n    strictEqual(measure1.name, 'test-measure-1', 'measure name should match');\n    strictEqual(measure1.entryType, 'measure', 'entryType should be \"measure\"');\n    ok(typeof measure1.startTime === 'number', 'startTime should be a number');\n    ok(typeof measure1.duration === 'number', 'duration should be a number');\n    strictEqual(\n      measure1.startTime,\n      mark1.startTime,\n      'measure startTime should match start mark'\n    );\n    strictEqual(\n      measure1.detail,\n      null,\n      'detail should be null when using string marks'\n    );\n\n    const measure2 = perfHooksPerformance.measure('test-measure-2', {\n      start: 50,\n      end: 150,\n    });\n    strictEqual(\n      measure2.startTime,\n      50,\n      'measure with options should have correct startTime'\n    );\n    strictEqual(\n      measure2.duration,\n      100,\n      'measure with options should have correct duration'\n    );\n    strictEqual(\n      measure2.detail,\n      null,\n      'detail should be null when not provided in options'\n    );\n\n    const measure3 = perfHooksPerformance.measure('test-measure-3', {\n      start: 100,\n      duration: 50,\n    });\n    strictEqual(\n      measure3.startTime,\n      100,\n      'measure with duration should have correct startTime'\n    );\n    strictEqual(\n      measure3.duration,\n      50,\n      'measure with duration should have correct duration'\n    );\n    strictEqual(\n      measure3.detail,\n      null,\n      'detail should be null when not provided in options'\n    );\n\n    const customDetail = { customKey: 'customValue' };\n    const measure4 = perfHooksPerformance.measure('test-measure-4', {\n      start: 200,\n      end: 300,\n      detail: customDetail,\n    });\n    ok(measure4.detail, 'measure should have detail');\n    deepStrictEqual(measure4.detail, customDetail, 'detail should match');\n\n    const measures = perfHooksPerformance.getEntriesByType('measure');\n    ok(measures.length >= 4, 'should have at least 4 measures');\n\n    perfHooksPerformance.clearMarks();\n    perfHooksPerformance.clearMeasures();\n  },\n};\n\nexport const testPerformanceMeasureNameOnly = {\n  test() {\n    perfHooksPerformance.clearMarks();\n    perfHooksPerformance.clearMeasures();\n\n    const before = perfHooksPerformance.now();\n    const measure = perfHooksPerformance.measure('name-only-measure');\n    const after = perfHooksPerformance.now();\n\n    ok(\n      measure instanceof PerformanceMeasure,\n      'measure should return a PerformanceMeasure instance'\n    );\n    strictEqual(measure.name, 'name-only-measure', 'measure name should match');\n    strictEqual(measure.entryType, 'measure', 'entryType should be \"measure\"');\n    strictEqual(measure.startTime, 0, 'startTime should be 0 (timeOrigin)');\n    ok(\n      measure.duration >= before && measure.duration <= after,\n      'duration should be approximately the current time'\n    );\n    strictEqual(\n      measure.detail,\n      null,\n      'detail should be null when not provided'\n    );\n\n    perfHooksPerformance.clearMeasures();\n  },\n};\n\nexport const testGetEntriesByName = {\n  test() {\n    perfHooksPerformance.clearMarks();\n    perfHooksPerformance.clearMeasures();\n\n    perfHooksPerformance.mark('test-name');\n    perfHooksPerformance.mark('test-name');\n    perfHooksPerformance.mark('other-name');\n    perfHooksPerformance.measure('test-name', { start: 0, end: 100 });\n\n    const entriesByName = perfHooksPerformance.getEntriesByName('test-name');\n    ok(Array.isArray(entriesByName), 'getEntriesByName should return an array');\n    strictEqual(\n      entriesByName.length,\n      3,\n      'should have 3 entries with name \"test-name\"'\n    );\n\n    const marksByName = perfHooksPerformance.getEntriesByName(\n      'test-name',\n      'mark'\n    );\n    strictEqual(\n      marksByName.length,\n      2,\n      'should have 2 marks with name \"test-name\"'\n    );\n\n    const measuresByName = perfHooksPerformance.getEntriesByName(\n      'test-name',\n      'measure'\n    );\n    strictEqual(\n      measuresByName.length,\n      1,\n      'should have 1 measure with name \"test-name\"'\n    );\n\n    const noEntries = perfHooksPerformance.getEntriesByName('non-existent');\n    ok(\n      Array.isArray(noEntries),\n      'should return empty array for non-existent name'\n    );\n    strictEqual(noEntries.length, 0, 'should have 0 entries');\n\n    perfHooksPerformance.clearMarks();\n    perfHooksPerformance.clearMeasures();\n  },\n};\n\nexport const testClearMethods = {\n  test() {\n    perfHooksPerformance.mark('mark-to-keep');\n    perfHooksPerformance.mark('mark-to-remove');\n    perfHooksPerformance.mark('mark-to-remove');\n    perfHooksPerformance.mark('another-mark');\n\n    perfHooksPerformance.clearMarks('mark-to-remove');\n    const marksAfterClear = perfHooksPerformance.getEntriesByType('mark');\n    strictEqual(\n      marksAfterClear.length,\n      2,\n      'should have 2 marks after clearing specific name'\n    );\n    ok(\n      marksAfterClear.every((m) => m.name !== 'mark-to-remove'),\n      'should not have removed marks'\n    );\n    ok(\n      marksAfterClear.some((m) => m.name === 'mark-to-keep'),\n      'should have kept other marks'\n    );\n\n    perfHooksPerformance.clearMarks();\n    const allMarksCleared = perfHooksPerformance.getEntriesByType('mark');\n    strictEqual(\n      allMarksCleared.length,\n      0,\n      'should have 0 marks after clearing all'\n    );\n\n    perfHooksPerformance.measure('measure-to-keep', { start: 0, end: 100 });\n    perfHooksPerformance.measure('measure-to-remove', { start: 100, end: 200 });\n    perfHooksPerformance.measure('measure-to-remove', { start: 200, end: 300 });\n\n    perfHooksPerformance.clearMeasures('measure-to-remove');\n    const measuresAfterClear = perfHooksPerformance.getEntriesByType('measure');\n    strictEqual(\n      measuresAfterClear.length,\n      1,\n      'should have 1 measure after clearing specific name'\n    );\n    strictEqual(\n      measuresAfterClear[0].name,\n      'measure-to-keep',\n      'should have kept the right measure'\n    );\n\n    perfHooksPerformance.clearMeasures();\n    const allMeasuresCleared = perfHooksPerformance.getEntriesByType('measure');\n    strictEqual(\n      allMeasuresCleared.length,\n      0,\n      'should have 0 measures after clearing all'\n    );\n\n    perfHooksPerformance.clearResourceTimings();\n    const resourceTimings = perfHooksPerformance.getEntriesByType('resource');\n    strictEqual(resourceTimings.length, 0, 'should have 0 resource timings');\n  },\n};\n\nexport const testPerformanceEntryTypes = {\n  test() {\n    const allEntries = perfHooksPerformance.getEntries();\n    ok(Array.isArray(allEntries));\n\n    const markEntries = perfHooksPerformance.getEntriesByType('mark');\n    ok(Array.isArray(markEntries));\n\n    const measureEntries = perfHooksPerformance.getEntriesByType('measure');\n    ok(Array.isArray(measureEntries));\n\n    perfHooksPerformance.clearMarks();\n    perfHooksPerformance.clearMeasures();\n    perfHooksPerformance.clearResourceTimings();\n  },\n};\n\nexport const testEventCounts = {\n  test() {\n    const eventCounts = perfHooksPerformance.eventCounts;\n    ok(eventCounts, 'eventCounts should exist');\n    ok(typeof eventCounts === 'object', 'eventCounts should be an object');\n\n    ok(\n      typeof eventCounts.get === 'function',\n      'eventCounts should have get method'\n    );\n    ok(\n      typeof eventCounts.has === 'function',\n      'eventCounts should have has method'\n    );\n    ok(\n      typeof eventCounts.entries === 'function',\n      'eventCounts should have entries method'\n    );\n    ok(\n      typeof eventCounts.keys === 'function',\n      'eventCounts should have keys method'\n    );\n    ok(\n      typeof eventCounts.values === 'function',\n      'eventCounts should have values method'\n    );\n    ok(\n      typeof eventCounts.forEach === 'function',\n      'eventCounts should have forEach method'\n    );\n\n    ok('size' in eventCounts, 'eventCounts should have size property');\n    ok(typeof eventCounts.size === 'number', 'size should be a number');\n    ok(eventCounts.size >= 0, 'size should be non-negative');\n    strictEqual(eventCounts.size, 0, 'size should be 0 for empty EventCounts');\n\n    ok(!('set' in eventCounts), 'eventCounts should not have set method');\n    ok(!('delete' in eventCounts), 'eventCounts should not have delete method');\n    ok(!('clear' in eventCounts), 'eventCounts should not have clear method');\n\n    const testKeys = [\n      'click',\n      'keydown',\n      '',\n      'special-chars!@#',\n      '123',\n      'undefined',\n      'null',\n    ];\n    for (const key of testKeys) {\n      const result = eventCounts.get(key);\n      strictEqual(\n        result,\n        undefined,\n        `get('${key}') should return undefined for non-existent keys`\n      );\n\n      const hasResult = eventCounts.has(key);\n      strictEqual(\n        hasResult,\n        false,\n        `has('${key}') should return false for non-existent keys`\n      );\n    }\n\n    ok(\n      typeof eventCounts[Symbol.iterator] === 'function',\n      'eventCounts should be iterable'\n    );\n\n    const entriesIter = eventCounts.entries();\n    ok(\n      entriesIter && typeof entriesIter.next === 'function',\n      'entries() should return an iterator'\n    );\n    ok(\n      typeof entriesIter[Symbol.iterator] === 'function',\n      'entries iterator should be iterable'\n    );\n    const entriesResult = entriesIter.next();\n    strictEqual(\n      entriesResult.done,\n      true,\n      'entries iterator should be done immediately (empty map)'\n    );\n    strictEqual(\n      entriesResult.value,\n      undefined,\n      'entries iterator value should be undefined when done'\n    );\n\n    const keysIter = eventCounts.keys();\n    ok(\n      keysIter && typeof keysIter.next === 'function',\n      'keys() should return an iterator'\n    );\n    ok(\n      typeof keysIter[Symbol.iterator] === 'function',\n      'keys iterator should be iterable'\n    );\n    const keysResult = keysIter.next();\n    strictEqual(\n      keysResult.done,\n      true,\n      'keys iterator should be done immediately (empty map)'\n    );\n    strictEqual(\n      keysResult.value,\n      undefined,\n      'keys iterator value should be undefined when done'\n    );\n\n    const valuesIter = eventCounts.values();\n    ok(\n      valuesIter && typeof valuesIter.next === 'function',\n      'values() should return an iterator'\n    );\n    ok(\n      typeof valuesIter[Symbol.iterator] === 'function',\n      'values iterator should be iterable'\n    );\n    const valuesResult = valuesIter.next();\n    strictEqual(\n      valuesResult.done,\n      true,\n      'values iterator should be done immediately (empty map)'\n    );\n    strictEqual(\n      valuesResult.value,\n      undefined,\n      'values iterator value should be undefined when done'\n    );\n\n    let forEachCalled = false;\n    let forEachContext = null;\n    const customThis = { custom: true };\n\n    eventCounts.forEach(function () {\n      forEachCalled = true;\n    });\n    ok(!forEachCalled, 'forEach should not call callback when map is empty');\n\n    eventCounts.forEach(function () {\n      forEachContext = this;\n    }, customThis);\n    strictEqual(\n      forEachContext,\n      null,\n      'forEach with thisArg should not be called for empty map'\n    );\n\n    const entries = [];\n    for (const entry of eventCounts) {\n      entries.push(entry);\n    }\n    ok(Array.isArray(entries), 'Should be able to iterate with for...of');\n    strictEqual(entries.length, 0, 'Should have no entries currently');\n\n    const spreadEntries = [...eventCounts];\n    ok(Array.isArray(spreadEntries), 'Should be able to use spread operator');\n    strictEqual(spreadEntries.length, 0, 'Spread should produce empty array');\n\n    const spreadKeys = [...eventCounts.keys()];\n    ok(Array.isArray(spreadKeys), 'Should be able to spread keys');\n    strictEqual(spreadKeys.length, 0, 'Keys spread should produce empty array');\n\n    const spreadValues = [...eventCounts.values()];\n    ok(Array.isArray(spreadValues), 'Should be able to spread values');\n    strictEqual(\n      spreadValues.length,\n      0,\n      'Values spread should produce empty array'\n    );\n\n    const fromEntries = Array.from(eventCounts);\n    ok(Array.isArray(fromEntries), 'Array.from should work with eventCounts');\n    strictEqual(fromEntries.length, 0, 'Array.from should produce empty array');\n\n    const eventCounts2 = perfHooksPerformance.eventCounts;\n    ok(\n      eventCounts2,\n      'Second call to performance.eventCounts should also return an object'\n    );\n  },\n};\n\nexport const testPerformanceMarkToJSON = {\n  test() {\n    const mark1 = perfHooksPerformance.mark('test-mark-json');\n    const json1 = mark1.toJSON();\n    strictEqual(json1.name, 'test-mark-json');\n    strictEqual(json1.entryType, 'mark');\n    strictEqual(json1.duration, 0);\n    ok(!('detail' in json1));\n\n    const detail = { key: 'value' };\n    const mark2 = perfHooksPerformance.mark('test-mark-json-2', { detail });\n    const json2 = mark2.toJSON();\n    deepStrictEqual(json2.detail, detail);\n  },\n};\n\nexport const testPerformanceMeasureToJSON = {\n  test() {\n    perfHooksPerformance.mark('start');\n    perfHooksPerformance.mark('end');\n    const measure1 = perfHooksPerformance.measure('test-json', 'start', 'end');\n    const json1 = measure1.toJSON();\n    strictEqual(json1.name, 'test-json');\n    strictEqual(json1.entryType, 'measure');\n    // detail should not be in JSON when not provided (it's null)\n    ok(!('detail' in json1), 'detail should not be in JSON when not provided');\n\n    const customDetail = { value: 42 };\n    const measure2 = perfHooksPerformance.measure('test-json-2', {\n      start: 50,\n      end: 150,\n      detail: customDetail,\n    });\n    const json2 = measure2.toJSON();\n    deepStrictEqual(json2.detail, customDetail);\n  },\n};\n\nexport const testPerformanceToJSON = {\n  test() {\n    const json = perfHooksPerformance.toJSON();\n    strictEqual(typeof json, 'object');\n    strictEqual(json.timeOrigin, 0);\n    // toJSON should not include other properties beyond timeOrigin\n    deepStrictEqual(Object.keys(json), ['timeOrigin']);\n  },\n};\n\nexport const testSetResourceTimingBufferSize = {\n  test() {\n    doesNotThrow(() => perfHooksPerformance.setResourceTimingBufferSize(0));\n  },\n};\n\nexport const testGlobalThis = {\n  async test() {\n    notStrictEqual(typeof globalThis.performance, 'undefined');\n    ok('addEventListener' in globalThis.performance);\n    notStrictEqual(typeof globalThis.Performance, 'undefined');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/perf-hooks-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"perf-hooks-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"perf-hooks-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_perf_hooks_module\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/process-exit-test.js",
    "content": "import { fail, ok, rejects, strictEqual } from 'assert';\n\nlet called = false;\n\nprocess.exit(9999);\n\nexport const test = {\n  async test(_, env) {\n    // We don't really have a way to verifying that the correct log messages\n    // we emitted. For now, we need to manually check the log out, and here\n    // we check only that we received an internal error and that no other\n    // lines of code ran after the process.exit call.\n    await rejects(env.subrequest.fetch('http://example.org'), {\n      message: /^The Node.js process.exit/,\n    });\n    ok(!called);\n  },\n};\n\nlet fooCreateCount = 0;\n\nexport const test2 = {\n  async test(_, env) {\n    // We don't really have a way to verifying that the correct log messages\n    // we emitted. For now, we need to manually check the log out, and here\n    // we check only that we received an internal error and that no other\n    // lines of code ran after the process.exit call.\n    {\n      let obj = env.foo.get(\n        env.foo.idFromName('210bd0cbd803ef7883a1ee9d86cce06f')\n      );\n      await rejects(obj.fetch('http://example.org'), {\n        message: /^The Node.js process.exit/,\n      });\n      await rejects(obj.fetch('http://example.org'), {\n        message: /^The Node.js process.exit/,\n      });\n      // The durable object should have been created once, since we didn't recreate the stub.\n      strictEqual(fooCreateCount, 1);\n\n      // If we recreate the stub and try again, then the counter will increment.\n      obj = env.foo.get(env.foo.idFromName('210bd0cbd803ef7883a1ee9d86cce06f'));\n      await rejects(obj.fetch('http://example.org'), {\n        message: /^The Node.js process.exit/,\n      });\n      strictEqual(fooCreateCount, 2);\n    }\n  },\n};\n\nexport default {\n  async fetch() {\n    try {\n      process.exit(123);\n    } finally {\n      called = true;\n    }\n  },\n};\n\nexport class Foo {\n  constructor() {\n    fooCreateCount++;\n  }\n\n  fetch() {\n    process.exit(123);\n    fail('Should never get here.');\n  }\n}\n"
  },
  {
    "path": "src/workerd/api/node/tests/process-exit-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"process-exit-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"process-exit-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\"],\n        durableObjectNamespaces = [\n          (className = \"Foo\", uniqueKey = \"210bd0cbd803ef7883a1ee9d86cce06f\"),\n        ],\n        durableObjectStorage = (inMemory = void),\n        bindings = [\n          (name = \"subrequest\", service = \"process-exit-test\"),\n          (name = \"foo\", durableObjectNamespace = \"Foo\"),\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/process-getbuiltin-newmodreg-test.js",
    "content": "import { strictEqual } from 'node:assert';\n\nexport const getBuiltinModulePublicBuiltins = {\n  test() {\n    // process.getBuiltinModule must be able to resolve known public built-ins.\n    strictEqual(typeof process.getBuiltinModule('node:process'), 'object');\n    strictEqual(typeof process.getBuiltinModule('node:buffer'), 'object');\n    strictEqual(typeof process.getBuiltinModule('node:assert'), 'object');\n  },\n};\n\nexport const getBuiltinModuleNonExistent = {\n  test() {\n    // Non-existent modules return undefined.\n    strictEqual(process.getBuiltinModule('node:nonexistent'), undefined);\n    strictEqual(process.getBuiltinModule('non-existent'), undefined);\n  },\n};\n\nexport const getBuiltinModuleInternalNotExposed = {\n  test() {\n    // Internal modules must not be accessible via getBuiltinModule.\n    // These are BUILTIN_ONLY modules that should only be importable\n    // by other built-in modules, never by user code.\n    strictEqual(process.getBuiltinModule('node-internal:process'), undefined);\n    strictEqual(\n      process.getBuiltinModule('node-internal:public_process'),\n      undefined\n    );\n    strictEqual(\n      process.getBuiltinModule('node-internal:legacy_process'),\n      undefined\n    );\n    strictEqual(\n      process.getBuiltinModule('node-internal:internal_timers_global_override'),\n      undefined\n    );\n    strictEqual(process.getBuiltinModule('cloudflare-internal:env'), undefined);\n    strictEqual(\n      process.getBuiltinModule('cloudflare-internal:filesystem'),\n      undefined\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/process-getbuiltin-newmodreg-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"process-getbuiltin-newmodreg-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"process-getbuiltin-newmodreg-test.js\"),\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat_v2\",\n          \"new_module_registry\",\n          \"experimental\",\n        ],\n      ),\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/process-legacy-nodejs-test.js",
    "content": "import assert from 'node:assert';\nimport * as processMod from 'node:process';\n\n// Test that when enable_nodejs_process_v2 is disabled, the process module\n// only exports the legacy subset of properties: nextTick, env, exit,\n// getBuiltinModule, platform, and features\n\nconst expectedLegacyKeys = [\n  'default',\n  'env',\n  'exit',\n  'features',\n  'getBuiltinModule',\n  'nextTick',\n  'platform',\n];\n\nexport const processLegacyNamedExports = {\n  test() {\n    // Test that named exports only include the legacy subset\n    const actualKeys = Object.keys(processMod).sort();\n    assert.deepStrictEqual(actualKeys, expectedLegacyKeys);\n  },\n};\n\nexport const processLegacyDefaultExport = {\n  test() {\n    // Test that default export only includes the legacy subset\n    const actualKeys = Object.keys(processMod.default).sort();\n    const expectedDefaultKeys = expectedLegacyKeys.filter(\n      (key) => key !== 'default'\n    );\n    assert.deepStrictEqual(actualKeys, expectedDefaultKeys);\n  },\n};\n\nexport const processLegacyGlobalProcess = {\n  test() {\n    // Test that global process only includes the legacy subset\n    const actualKeys = Object.keys(process)\n      .filter((v) => v[0] !== '_') // Filter out private properties\n      .sort();\n    const expectedDefaultKeys = expectedLegacyKeys.filter(\n      (key) => key !== 'default'\n    );\n    assert.deepStrictEqual(actualKeys, expectedDefaultKeys);\n  },\n};\n\nexport const processLegacyFunctionality = {\n  async test() {\n    // Test that all legacy properties are functional\n\n    // Test nextTick\n    let nextTickCalled = false;\n    process.nextTick(() => {\n      nextTickCalled = true;\n    });\n    await scheduler.wait(0);\n    assert.ok(nextTickCalled);\n\n    // Test env (should have FOO=BAR from binding)\n    assert.strictEqual(process.env.FOO, 'BAR');\n    assert.strictEqual(typeof process.env, 'object');\n\n    // Test platform\n    assert.strictEqual(typeof process.platform, 'string');\n    assert.ok(['darwin', 'win32', 'linux'].includes(process.platform));\n\n    // Test features\n    assert.strictEqual(typeof process.features, 'object');\n    assert.ok(process.features !== null);\n\n    // Test getBuiltinModule\n    assert.strictEqual(typeof process.getBuiltinModule, 'function');\n    const processBuiltin = process.getBuiltinModule('node:process');\n    assert.strictEqual(typeof processBuiltin, 'object');\n\n    // Test exit function exists (but don't call it)\n    assert.strictEqual(typeof process.exit, 'function');\n  },\n};\n\nexport const processLegacyNoV2Properties = {\n  test() {\n    // Test that v2-only properties are NOT present\n    const v2OnlyProperties = [\n      'version',\n      'versions',\n      'title',\n      'argv',\n      'argv0',\n      'execArgv',\n      'arch',\n      'config',\n      'pid',\n      'ppid',\n      'hrtime',\n      'emitWarning',\n      'abort',\n      'uptime',\n      'loadEnvFile',\n      'getegid',\n      'geteuid',\n      'getgid',\n      'getgroups',\n      'getuid',\n      'setegid',\n      'seteuid',\n      'setgid',\n      'setgroups',\n      'setuid',\n      'initgroups',\n      'setSourceMapsEnabled',\n      'getSourceMapsSupport',\n      'allowedNodeEnvironmentFlags',\n    ];\n\n    for (const prop of v2OnlyProperties) {\n      assert.strictEqual(\n        process[prop],\n        undefined,\n        `process.${prop} should be undefined in legacy mode`\n      );\n      assert.strictEqual(\n        processMod[prop],\n        undefined,\n        `processMod.${prop} should be undefined in legacy mode`\n      );\n      assert.strictEqual(\n        processMod.default[prop],\n        undefined,\n        `processMod.default.${prop} should be undefined in legacy mode`\n      );\n    }\n  },\n};\n\nexport const processLegacyConsistency = {\n  test() {\n    // Test that all three ways of accessing process are consistent\n    const processKeys = Object.keys(process)\n      .filter((k) => k[0] !== '_')\n      .sort();\n    const processModKeys = Object.keys(processMod)\n      .filter((k) => k !== 'default')\n      .sort();\n    const processDefaultKeys = Object.keys(processMod.default).sort();\n\n    assert.deepStrictEqual(processKeys, processDefaultKeys);\n    assert.deepStrictEqual(processModKeys, processDefaultKeys);\n\n    // Test that function references are the same\n    assert.strictEqual(process.nextTick, processMod.nextTick);\n    assert.strictEqual(process.nextTick, processMod.default.nextTick);\n    assert.strictEqual(process.exit, processMod.exit);\n    assert.strictEqual(process.exit, processMod.default.exit);\n    assert.strictEqual(process.getBuiltinModule, processMod.getBuiltinModule);\n    assert.strictEqual(\n      process.getBuiltinModule,\n      processMod.default.getBuiltinModule\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/process-legacy-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"nodejs-process-legacy-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"process-legacy-nodejs-test.js\"),\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat_populate_process_env\",\n          \"nodejs_compat\",\n          \"nodejs_compat_v2\",\n          \"experimental\",\n          \"disable_nodejs_process_v2\"\n        ],\n        bindings = [\n          (name = \"FOO\", text = \"BAR\"),\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/process-nodejs-test.js",
    "content": "import assert from 'node:assert';\nimport { readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport * as processMod from 'node:process';\n\nconst processBuiltin = processMod.getBuiltinModule('process');\nconst processBuiltinScheme = processMod.getBuiltinModule('node:process');\n\n// -------------------------------------------------------\n// Ensures the globalThis process and Buffer properties are handled correctly.\n// Placement of these at the top level scope before any thing else runs is\n// intentional. Do not move these elsewhere in the test.\nqueueMicrotask(() => process);\nqueueMicrotask(() => Buffer);\nprocess.env.QUX = 1;\nBuffer;\n\nconst originalProcess = process;\nglobalThis.process = 123;\nassert.strictEqual(globalThis.process, 123);\nglobalThis.process = originalProcess;\nassert.strictEqual(globalThis.process, process);\n// -------------------------------------------------------\n\nconst pEnv = { ...process.env };\n\nexport const processPlatform = {\n  test() {\n    assert.ok(['darwin', 'win32', 'linux'].includes(process.platform));\n  },\n};\n\n// undefined process properties\nconst processUndefinedKeys = [\n  '_channel',\n  '_disconnect',\n  '_handleQueue',\n  '_maxListeners',\n  '_pendingMessage',\n  '_send',\n  'exitCode',\n];\n\n// functions which just return\nconst processNoopKeys = [\n  '_debugEnd',\n  '_debugProcess',\n  '_startProfilerIdleNotifier',\n  '_stopProfilerIdleNotifier',\n  '_tickCallback',\n  'ref',\n  'setSourceMapsEnabled',\n  'unref',\n];\n\n// functions which throw unsupported\nconst processUnimplementedKeys = [\n  '_kill',\n  '_linkedBinding',\n  'binding',\n  'cpuUsage',\n  'dlopen',\n  'execve',\n  'getActiveResourcesInfo',\n  'hasUncaughtExceptionCaptureCallback',\n  'kill',\n  'setUncaughtExceptionCaptureCallback',\n  'threadCpuUsage',\n];\n\n// remaining keys\nconst processImplementedOrStubbedKeys = [\n  '_events',\n  '_eventsCount',\n  '_exiting',\n  '_fatalException',\n  '_getActiveHandles',\n  '_getActiveRequests',\n  '_preload_modules',\n  '_rawDebug',\n  'abort',\n  'allowedNodeEnvironmentFlags',\n  'arch',\n  'argv',\n  'argv0',\n  'assert',\n  'availableMemory',\n  'channel',\n  'chdir',\n  'config',\n  'connected',\n  'constrainedMemory',\n  'cwd',\n  'debugPort',\n  'default',\n  'domain',\n  'emitWarning',\n  'env',\n  'execArgv',\n  'execPath',\n  'exit',\n  'features',\n  'finalization',\n  'getBuiltinModule',\n  'getSourceMapsSupport',\n  'getegid',\n  'geteuid',\n  'getgid',\n  'getgroups',\n  'getuid',\n  'hrtime',\n  'initgroups',\n  'loadEnvFile',\n  'memoryUsage',\n  'moduleLoadList',\n  'nextTick',\n  'noDeprecation',\n  'openStdin',\n  'permission',\n  'pid',\n  'platform',\n  'ppid',\n  'reallyExit',\n  'release',\n  'report',\n  'resourceUsage',\n  'send',\n  'setegid',\n  'seteuid',\n  'setgid',\n  'setgroups',\n  'setuid',\n  'sourceMapsEnabled',\n  'stderr',\n  'stdin',\n  'stdout',\n  'throwDeprecation',\n  'title',\n  'traceDeprecation',\n  'umask',\n  'uptime',\n  'version',\n  'versions',\n];\n\nconst allProcessKeys = [\n  ...processUndefinedKeys,\n  ...processUnimplementedKeys,\n  ...processNoopKeys,\n  ...processImplementedOrStubbedKeys,\n].sort();\n\nexport const processKeys = {\n  test() {\n    assert.deepStrictEqual(Object.keys(processMod), allProcessKeys);\n    allProcessKeys.splice(allProcessKeys.indexOf('default'), 1);\n    assert.deepStrictEqual(Object.keys(process).sort(), allProcessKeys);\n    assert.deepStrictEqual(\n      Object.keys(processMod.default).sort(),\n      allProcessKeys\n    );\n    assert.deepStrictEqual(Object.keys(processBuiltin).sort(), allProcessKeys);\n    assert.deepStrictEqual(\n      Object.keys(processBuiltinScheme).sort(),\n      allProcessKeys\n    );\n  },\n};\n\nexport const processUndefined = {\n  test() {\n    for (const key of processUndefinedKeys) {\n      const msg = `process.${key}`;\n      assert.strictEqual(processMod[key], undefined, msg);\n      assert.strictEqual(process[key], undefined, msg);\n      assert.strictEqual(processBuiltin[key], undefined, msg);\n      assert.strictEqual(processBuiltinScheme[key], undefined, msg);\n    }\n  },\n};\n\nexport const processNoop = {\n  test() {\n    for (const key of processNoopKeys) {\n      const msg = `process.${key}`;\n\n      assert.strictEqual(typeof processMod[key], 'function', msg);\n      assert.strictEqual(typeof process[key], 'function', msg);\n      assert.strictEqual(typeof processBuiltin[key], 'function', msg);\n      assert.strictEqual(typeof processBuiltinScheme[key], 'function', msg);\n\n      assert.strictEqual(processMod[key](), undefined, msg);\n      assert.strictEqual(process[key](), undefined, msg);\n      assert.strictEqual(processBuiltin[key](), undefined, msg);\n      assert.strictEqual(processBuiltinScheme[key](), undefined, msg);\n    }\n  },\n};\n\nexport const processUnimplemented = {\n  test() {\n    for (const key of processUnimplementedKeys) {\n      const msg = `process.${key}`;\n\n      assert.strictEqual(typeof processMod[key], 'function', msg);\n      assert.strictEqual(typeof process[key], 'function', msg);\n      assert.strictEqual(typeof processBuiltin[key], 'function', msg);\n      assert.strictEqual(typeof processBuiltinScheme[key], 'function', msg);\n\n      try {\n        processMod[key]();\n        assert.fail(msg);\n      } catch (e) {\n        assert.strictEqual(e.code, 'ERR_METHOD_NOT_IMPLEMENTED', msg);\n        assert.ok(e.message.includes(key), msg);\n      }\n      try {\n        process[key]();\n        assert.fail(msg);\n      } catch (e) {\n        assert.strictEqual(e.code, 'ERR_METHOD_NOT_IMPLEMENTED', msg);\n      }\n      try {\n        processBuiltin[key]();\n        assert.fail(msg);\n      } catch (e) {\n        assert.strictEqual(e.code, 'ERR_METHOD_NOT_IMPLEMENTED', msg);\n      }\n      try {\n        processBuiltinScheme[key]();\n        assert.fail(msg);\n      } catch (e) {\n        assert.strictEqual(e.code, 'ERR_METHOD_NOT_IMPLEMENTED', msg);\n      }\n    }\n  },\n};\n\nexport const processEnv = {\n  async test(ctrl, env) {\n    assert.notDeepStrictEqual(env, process.env);\n\n    assert.strictEqual(pEnv.FOO, 'BAR');\n\n    // It should be possible to mutate the process.env at runtime.\n    // All values manually set are coerced to strings.\n    assert.strictEqual(pEnv.QUX, '1');\n\n    // JSON bindings that do not parse to strings come through as\n    // raw unparsed JSON strings....\n    assert.strictEqual(pEnv.BAR, '{}');\n    JSON.parse(pEnv.BAR);\n\n    // JSON bindings that parse as strings come through as the\n    // parsed string value.\n    assert.strictEqual(pEnv.BAZ, 'abc');\n\n    // Throws because althought defined as a JSON binding, the value\n    // that comes through to process.env in this case is not a JSON\n    // parseable string because it will already have been parsed.\n    // This assertion is only true when the JSON bindings value\n    // happens to parse as a string.\n    assert.throws(() => JSON.parse(pEnv.BAZ));\n\n    // JSON bindings that parse as strings that happen to be double\n    // escaped might be JSON parseable.\n    assert.strictEqual(JSON.parse(pEnv.DUB), 'abc');\n\n    // Test that imports can see the process.env at the top level\n    const { FOO } = await import('mod');\n    assert.strictEqual(FOO, 'BAR');\n\n    // Mutating the env argument does not change process.env, and\n    // vis versa. The reason for this is that just mutations may\n    // be entirely surprising and unexpected for users.\n    assert.strictEqual(env.QUX, undefined);\n    env.ZZZ = 'a';\n    assert.strictEqual(process.env.ZZZ, undefined);\n\n    env.FOO = ['just some other value'];\n    assert.strictEqual(process.env.FOO, 'BAR');\n\n    // Other kinds of bindings will be on env but not process.env\n    assert.strictEqual(pEnv.NON, undefined);\n    assert.deepStrictEqual(\n      new Uint8Array(env.NON),\n      new Uint8Array([97, 98, 99, 100, 101, 102])\n    );\n\n    // Test process bindings delete https://github.com/nodejs/node/blob/5f7dbf45a3d3e3070d5f58f9a9c2c43dbecc8672/test/parallel/test-process-env-delete.js\n    {\n      process.env.foo = 'foo';\n      assert.strictEqual(process.env.foo, 'foo');\n      process.env.foo = undefined;\n      assert.strictEqual(process.env.foo, 'undefined');\n\n      process.env.foo = 'foo';\n      assert.strictEqual(process.env.foo, 'foo');\n      delete process.env.foo;\n      assert.strictEqual(process.env.foo, undefined);\n    }\n\n    // Test process env ignore getter setter https://github.com/nodejs/node/blob/5f7dbf45a3d3e3070d5f58f9a9c2c43dbecc8672/test/parallel/test-process-env-ignore-getter-setter.js\n    {\n      assert.throws(\n        () => {\n          Object.defineProperty(process.env, 'foo', {\n            value: 'foo1',\n          });\n        },\n        {\n          code: 'ERR_INVALID_ARG_VALUE',\n          name: 'TypeError',\n        }\n      );\n\n      assert.strictEqual(process.env.foo, undefined);\n      process.env.foo = 'foo2';\n      assert.strictEqual(process.env.foo, 'foo2');\n\n      assert.throws(\n        () => {\n          Object.defineProperty(process.env, 'goo', {\n            get() {\n              return 'goo';\n            },\n            set() {},\n          });\n        },\n        {\n          code: 'ERR_INVALID_ARG_VALUE',\n          name: 'TypeError',\n        }\n      );\n\n      const attributes = ['configurable', 'writable', 'enumerable'];\n\n      for (const attribute of attributes) {\n        assert.throws(\n          () => {\n            Object.defineProperty(process.env, 'goo', {\n              [attribute]: false,\n            });\n          },\n          {\n            code: 'ERR_INVALID_ARG_VALUE',\n            name: 'TypeError',\n          }\n        );\n      }\n\n      assert.strictEqual(process.env.goo, undefined);\n      Object.defineProperty(process.env, 'goo', {\n        value: 'goo',\n        configurable: true,\n        writable: true,\n        enumerable: true,\n      });\n      assert.strictEqual(process.env.goo, 'goo');\n    }\n\n    // Process env symbols (https://github.com/nodejs/node/blob/5f7dbf45a3d3e3070d5f58f9a9c2c43dbecc8672/test/parallel/test-process-env-symbols.js)\n    {\n      const symbol = Symbol('sym');\n\n      // Verify that getting via a symbol key returns undefined.\n      assert.strictEqual(process.env[symbol], undefined);\n\n      // Verify that assigning via a symbol key throws.\n      // The message depends on the JavaScript engine and so will be different between\n      // different JavaScript engines. Confirm that the `Error` is a `TypeError` only.\n      assert.throws(() => {\n        process.env[symbol] = 42;\n      }, TypeError);\n\n      // Verify that assigning a symbol value throws.\n      // The message depends on the JavaScript engine and so will be different between\n      // different JavaScript engines. Confirm that the `Error` is a `TypeError` only.\n      assert.throws(() => {\n        process.env.foo = symbol;\n      }, TypeError);\n\n      // Verify that using a symbol with the in operator returns false.\n      assert.strictEqual(symbol in process.env, false);\n\n      // Verify that deleting a symbol key returns true.\n      assert.strictEqual(delete process.env[symbol], true);\n\n      // Checks that well-known symbols like `Symbol.toStringTag` won’t throw.\n      Object.prototype.toString.call(process.env);\n    }\n  },\n};\n\nexport const processProperties = {\n  test() {\n    // Test basic process properties\n    assert.strictEqual(typeof process.title, 'string');\n    assert.ok(process.title.length > 0);\n\n    assert.ok(Array.isArray(process.argv));\n    assert.ok(process.argv.length >= 1);\n\n    assert.strictEqual(typeof process.arch, 'string');\n    assert.strictEqual(process.arch, 'x64');\n\n    assert.strictEqual(typeof process.version, 'string');\n    assert.ok(process.version.startsWith('v'));\n\n    // Test additional properties\n    assert.strictEqual(process.argv0, 'workerd');\n    assert.ok(Array.isArray(process.execArgv));\n    assert.strictEqual(process.execArgv.length, 0);\n\n    assert.strictEqual(process.pid, 1);\n    assert.strictEqual(process.ppid, 0);\n\n    // Test config object\n    assert.strictEqual(typeof process.config, 'object');\n    assert.strictEqual(typeof process.config.target_defaults, 'object');\n    assert.strictEqual(typeof process.config.variables, 'object');\n\n    // Test uid/gid functions\n    assert.strictEqual(process.getegid(), 0);\n    assert.strictEqual(process.geteuid(), 0);\n    assert.throws(\n      () => {\n        process.setegid(1000);\n      },\n      { code: 'EPERM' }\n    );\n    assert.throws(\n      () => {\n        process.seteuid(1000);\n      },\n      { code: 'EPERM' }\n    );\n    process.setegid(0);\n    assert.strictEqual(process.getegid(), 0);\n    assert.strictEqual(process.geteuid(), 0);\n\n    // Test other functions\n    process.setSourceMapsEnabled(false); // Should be no-op\n\n    // Test allowedNodeEnvironmentFlags\n    assert.ok(process.allowedNodeEnvironmentFlags instanceof Set);\n    assert.strictEqual(process.allowedNodeEnvironmentFlags.size, 0);\n  },\n};\n\n// Test implemented process APIs - no longer undefined\nexport const processImplemented = {\n  test() {\n    // No-op functions\n    assert.strictEqual(typeof process.ref, 'function');\n    assert.strictEqual(typeof process.unref, 'function');\n\n    // Error-throwing functions\n    assert.strictEqual(typeof process.kill, 'function');\n    assert.strictEqual(typeof process.binding, 'function');\n    assert.strictEqual(typeof process.dlopen, 'function');\n\n    // Mock implementations\n    assert.strictEqual(typeof process.memoryUsage, 'function');\n    assert.strictEqual(typeof process.resourceUsage, 'function');\n    assert.strictEqual(typeof process.threadCpuUsage, 'function');\n    assert.strictEqual(typeof process.cpuUsage, 'function');\n\n    // Properties with default values\n    assert.strictEqual(process.exitCode, undefined);\n    assert.strictEqual(process.channel, null);\n    assert.strictEqual(process.connected, false);\n    assert.strictEqual(typeof process.debugPort, 'number');\n    assert.strictEqual(process.noDeprecation, false);\n    assert.strictEqual(process.traceDeprecation, false);\n    assert.strictEqual(process.throwDeprecation, false);\n    assert.strictEqual(process.sourceMapsEnabled, false);\n    assert.strictEqual(typeof process.execPath, 'string');\n\n    // Objects\n    assert.strictEqual(typeof process.permission, 'object');\n    assert.strictEqual(typeof process.release, 'object');\n    assert.strictEqual(typeof process.report, 'object');\n    assert.strictEqual(typeof process.finalization, 'object');\n\n    // Additional APIs\n    assert.strictEqual(typeof process.constrainedMemory, 'function');\n    assert.strictEqual(typeof process.availableMemory, 'function');\n    assert.strictEqual(typeof process.send, 'function');\n    assert.strictEqual(process.send('test'), false);\n\n    // Test that error-throwing functions actually throw\n    assert.throws(() => process.getActiveResourcesInfo(), {\n      message: /process\\.getActiveResourcesInfo/,\n    });\n    assert.throws(() => process.setUncaughtExceptionCaptureCallback(), {\n      message: /process\\.setUncaughtExceptionCaptureCallback/,\n    });\n    assert.throws(() => process.hasUncaughtExceptionCaptureCallback(), {\n      message: /process\\.hasUncaughtExceptionCaptureCallback/,\n    });\n    assert.throws(() => process.kill(1), { message: /process\\.kill/ });\n    assert.throws(() => process.binding('test'), {\n      message: /process\\.binding/,\n    });\n    assert.throws(() => process.dlopen({}, 'test'), {\n      message: /process\\.dlopen/,\n    });\n  },\n};\n\nexport const processVersions = {\n  test() {\n    assert.strictEqual(typeof process.versions, 'object');\n    assert.ok(process.versions !== null);\n\n    const expectedVersions = ['node'];\n\n    // Check that all expected versions are included and are strings\n    for (const versionKey of expectedVersions) {\n      assert.strictEqual(\n        typeof process.versions[versionKey],\n        'string',\n        `process.versions.${versionKey} should be a string`\n      );\n    }\n  },\n};\n\nexport const processFeatures = {\n  test() {\n    assert.strictEqual(typeof process.features, 'object');\n    assert.ok(process.features != null);\n\n    // Process features should be an object that can be inspected\n    const features = Object.keys(process.features);\n    assert.ok(Array.isArray(features));\n  },\n};\n\nexport const processNextTick = {\n  async test() {\n    let called = false;\n    let order = [];\n\n    // Test basic nextTick functionality\n    process.nextTick(() => {\n      called = true;\n      order.push('nextTick1');\n    });\n\n    // Test multiple nextTick calls\n    process.nextTick(() => {\n      order.push('nextTick2');\n    });\n\n    // Test nextTick with arguments\n    process.nextTick(\n      (arg1, arg2) => {\n        assert.strictEqual(arg1, 'hello');\n        assert.strictEqual(arg2, 'world');\n        order.push('nextTick3');\n      },\n      'hello',\n      'world'\n    );\n\n    order.push('sync');\n\n    // Wait for microtasks to complete\n    await scheduler.wait(0);\n\n    assert.ok(called);\n    assert.deepStrictEqual(order, [\n      'sync',\n      'nextTick1',\n      'nextTick2',\n      'nextTick3',\n    ]);\n  },\n};\n\nexport const processGetBuiltinModule = {\n  test() {\n    // Should be able to get built-in modules\n    const processBuiltin = process.getBuiltinModule('node:process');\n    assert.strictEqual(typeof processBuiltin, 'object');\n\n    // Should return null/undefined for non-existent modules\n    const nonExistent = process.getBuiltinModule('node:nonexistent');\n    assert.ok(nonExistent == null);\n\n    // Internal modules must not be accessible via getBuiltinModule.\n    // These are BUILTIN_ONLY modules that should only be importable\n    // by other built-in modules, never by user code.\n    assert.strictEqual(\n      process.getBuiltinModule('node-internal:process'),\n      undefined\n    );\n    assert.strictEqual(\n      process.getBuiltinModule('node-internal:public_process'),\n      undefined\n    );\n    assert.strictEqual(\n      process.getBuiltinModule('node-internal:legacy_process'),\n      undefined\n    );\n    assert.strictEqual(\n      process.getBuiltinModule('node-internal:internal_timers_global_override'),\n      undefined\n    );\n    assert.strictEqual(\n      process.getBuiltinModule('cloudflare-internal:env'),\n      undefined\n    );\n    assert.strictEqual(\n      process.getBuiltinModule('cloudflare-internal:filesystem'),\n      undefined\n    );\n  },\n};\n\nexport const processEmitWarning = {\n  async test() {\n    const testMsg = 'A Warning';\n    const testCode = 'CODE001';\n    const testDetail = 'Some detail';\n    const testType = 'CustomWarning';\n\n    let calledWarningHandler = false;\n    process.on(\n      'warning',\n      (warning) => {\n        calledWarningHandler = true;\n        assert(warning);\n        assert.match(warning.name, /^(?:Warning|CustomWarning)/);\n        assert.strictEqual(warning.message, testMsg);\n        if (warning.code) assert.strictEqual(warning.code, testCode);\n        if (warning.detail) assert.strictEqual(warning.detail, testDetail);\n      },\n      15\n    );\n\n    class CustomWarning extends Error {\n      constructor() {\n        super();\n        this.name = testType;\n        this.message = testMsg;\n        this.code = testCode;\n        Error.captureStackTrace(this, CustomWarning);\n      }\n    }\n\n    [\n      [testMsg],\n      [testMsg, testType],\n      [testMsg, CustomWarning],\n      [testMsg, testType, CustomWarning],\n      [testMsg, testType, testCode],\n      [testMsg, { type: testType }],\n      [testMsg, { type: testType, code: testCode }],\n      [testMsg, { type: testType, code: testCode, detail: testDetail }],\n      [new CustomWarning()],\n      // Detail will be ignored for the following. No errors thrown\n      [testMsg, { type: testType, code: testCode, detail: true }],\n      [testMsg, { type: testType, code: testCode, detail: [] }],\n      [testMsg, { type: testType, code: testCode, detail: null }],\n      [testMsg, { type: testType, code: testCode, detail: 1 }],\n    ].forEach((args) => {\n      process.emitWarning(...args);\n    });\n\n    const warningNoToString = new CustomWarning();\n    warningNoToString.toString = null;\n    process.emitWarning(warningNoToString);\n\n    const warningThrowToString = new CustomWarning();\n    warningThrowToString.toString = function () {\n      throw new Error('invalid toString');\n    };\n    process.emitWarning(warningThrowToString);\n\n    // TypeError is thrown on invalid input\n    [\n      [1],\n      [{}],\n      [true],\n      [[]],\n      ['', '', {}],\n      ['', 1],\n      ['', '', 1],\n      ['', true],\n      ['', '', true],\n      ['', []],\n      ['', '', []],\n      [],\n      [undefined, 'foo', 'bar'],\n      [undefined],\n    ].forEach((args) => {\n      assert.throws(() => process.emitWarning(...args), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n      });\n    });\n\n    await new Promise(process.nextTick);\n    assert(calledWarningHandler);\n  },\n};\n\nexport const processHrtime = {\n  async test() {\n    // process.hrtime\n    // https://github.com/nodejs/node/blob/5f7dbf45a3d3e3070d5f58f9a9c2c43dbecc8672/test/parallel/test-process-hrtime.js\n    {\n      // The default behavior, return an Array \"tuple\" of numbers\n      const tuple = process.hrtime();\n\n      // Validate the default behavior\n      validateTuple(tuple);\n\n      // Validate that passing an existing tuple returns another valid tuple\n      validateTuple(process.hrtime(tuple));\n\n      // Test that only an Array may be passed to process.hrtime()\n      assert.throws(\n        () => {\n          process.hrtime(1);\n        },\n        {\n          code: 'ERR_INVALID_ARG_TYPE',\n          name: 'TypeError',\n        }\n      );\n      assert.throws(\n        () => {\n          process.hrtime([]);\n        },\n        {\n          code: 'ERR_OUT_OF_RANGE',\n          name: 'RangeError',\n        }\n      );\n      assert.throws(\n        () => {\n          process.hrtime([1]);\n        },\n        {\n          code: 'ERR_OUT_OF_RANGE',\n          name: 'RangeError',\n        }\n      );\n      assert.throws(\n        () => {\n          process.hrtime([1, 2, 3]);\n        },\n        {\n          code: 'ERR_OUT_OF_RANGE',\n          name: 'RangeError',\n        }\n      );\n\n      function validateTuple(tuple) {\n        assert(Array.isArray(tuple));\n        assert.strictEqual(tuple.length, 2);\n        assert(Number.isInteger(tuple[0]));\n        assert(Number.isInteger(tuple[1]));\n      }\n\n      const diff = process.hrtime([0, 1e9 - 1]);\n      assert(diff[1] >= 0); // https://github.com/nodejs/node/issues/4751\n    }\n\n    // process.hrtime.bigint\n    // https://github.com/nodejs/node/blob/5f7dbf45a3d3e3070d5f58f9a9c2c43dbecc8672/test/parallel/test-process-hrtime-bigint.js\n    {\n      const start = process.hrtime.bigint();\n\n      await scheduler.wait(1000);\n\n      const end = process.hrtime.bigint();\n      assert.strictEqual(typeof end, 'bigint');\n\n      assert(end - start >= 0n);\n\n      // Ideally, this should be closer to 1000, or we could test\n      // a smaller interval, but this is to work around the\n      // test runner time accuracy.\n      assert(end - start >= 1_000_000n);\n    }\n  },\n};\n\nconst basicValidEnv = `BASIC=overriden\n`;\n\nconst validEnv = `BASIC=basic\n\n# COMMENTS=work\n#BASIC=basic2\n#BASIC=basic3\n\n# previous line intentionally left blank\nAFTER_LINE=after_line\nA=\"B=C\"\nB=C=D\nEMPTY=\nEMPTY_SINGLE_QUOTES=''\nEMPTY_DOUBLE_QUOTES=\"\"\nEMPTY_BACKTICKS=\\`\\`\nSINGLE_QUOTES='single_quotes'\nSINGLE_QUOTES_SPACED='    single quotes    '\nDOUBLE_QUOTES=\"double_quotes\"\nDOUBLE_QUOTES_SPACED=\"    double quotes    \"\nDOUBLE_QUOTES_INSIDE_SINGLE='double \"quotes\" work inside single quotes'\nDOUBLE_QUOTES_WITH_NO_SPACE_BRACKET=\"{ port: $MONGOLAB_PORT}\"\nSINGLE_QUOTES_INSIDE_DOUBLE=\"single 'quotes' work inside double quotes\"\nBACKTICKS_INSIDE_SINGLE='\\`backticks\\` work inside single quotes'\nBACKTICKS_INSIDE_DOUBLE=\"\\`backticks\\` work inside double quotes\"\nBACKTICKS=\\`backticks\\`\nBACKTICKS_SPACED=\\`    backticks    \\`\nDOUBLE_QUOTES_INSIDE_BACKTICKS=\\`double \"quotes\" work inside backticks\\`\nSINGLE_QUOTES_INSIDE_BACKTICKS=\\`single 'quotes' work inside backticks\\`\nDOUBLE_AND_SINGLE_QUOTES_INSIDE_BACKTICKS=\\`double \"quotes\" and single 'quotes' work inside backticks\\`\nEXPAND_NEWLINES=\"expand\\\\nnew\\\\nlines\"\nDONT_EXPAND_UNQUOTED=dontexpand\\\\nnewlines\nDONT_EXPAND_SQUOTED='dontexpand\\\\nnewlines'\n# COMMENTS=work\nINLINE_COMMENTS=inline comments # work #very #well\nINLINE_COMMENTS_SINGLE_QUOTES='inline comments outside of #singlequotes' # work\nINLINE_COMMENTS_DOUBLE_QUOTES=\"inline comments outside of #doublequotes\" # work\nINLINE_COMMENTS_BACKTICKS=\\`inline comments outside of #backticks\\` # work\nINLINE_COMMENTS_SPACE=inline comments start with a#number sign. no space required.\nEQUAL_SIGNS=equals==\nRETAIN_INNER_QUOTES={\"foo\": \"bar\"}\nRETAIN_INNER_QUOTES_AS_STRING='{\"foo\": \"bar\"}'\nRETAIN_INNER_QUOTES_AS_BACKTICKS=\\`{\"foo\": \"bar's\"}\\`\nTRIM_SPACE_FROM_UNQUOTED=    some spaced out string\nSPACE_BEFORE_DOUBLE_QUOTES=   \"space before double quotes\"\nEMAIL=therealnerdybeast@example.tld\n    SPACED_KEY = parsed\nEDGE_CASE_INLINE_COMMENTS=\"VALUE1\" # or \"VALUE2\" or \"VALUE3\"\n\nMULTI_DOUBLE_QUOTED=\"THIS\nIS\nA\nMULTILINE\nSTRING\"\n\nMULTI_SINGLE_QUOTED='THIS\nIS\nA\nMULTILINE\nSTRING'\n\nMULTI_BACKTICKED=\\`THIS\nIS\nA\n\"MULTILINE'S\"\nSTRING\\`\nexport EXPORT_EXAMPLE = ignore export\n\nMULTI_NOT_VALID_QUOTE=\"\nMULTI_NOT_VALID=THIS\nIS NOT MULTILINE`;\n\n// process.loadEnvFile\n// https://github.com/nodejs/node/blob/5f7dbf45a3d3e3070d5f58f9a9c2c43dbecc8672/test/parallel/test-process-load-env-file.js\nexport const processLoadEnvFile = {\n  async test() {\n    const basicValidEnvFilePath = '/tmp/basic-valid.env';\n    const validEnvFilePath = '/tmp/valid.env';\n    const missingEnvFilePath =\n      '/tmp/dir%20with unusual\"chars \\'åß∂ƒ©∆¬…`/non-existent-file.env';\n\n    // Test prep: write the basic env file and valid env file\n    writeFileSync(basicValidEnvFilePath, basicValidEnv);\n    writeFileSync(validEnvFilePath, validEnv);\n\n    // supports passing path\n    {\n      process.loadEnvFile(validEnvFilePath);\n      assert.strictEqual(process.env.BASIC, 'basic');\n    }\n\n    // supports not-passing a path\n    {\n      // Uses `/tmp/.env` file.\n      try {\n        process.loadEnvFile();\n        assert.fail();\n      } catch (e) {\n        assert.strictEqual(e.code, 'ENOENT');\n        // TODO(soon): Enable once `path` is supported on ENOENT\n        // assert.strictEqual(e.path, '/bundle/.env');\n      }\n    }\n\n    // fails on missing paths\n    {\n      try {\n        process.loadEnvFile(missingEnvFilePath);\n      } catch (e) {\n        assert.strictEqual(e.code, 'ENOENT');\n        // TODO(soon): Enable once `path` is supported on ENOENT errors\n        // assert.strictEqual(e.path, missingEnvFile);\n      }\n    }\n\n    // should override\n    {\n      process.loadEnvFile(basicValidEnvFilePath);\n      assert.strictEqual(process.env.BASIC, 'overriden');\n    }\n\n    // supports cwd\n    {\n      const originalCwd = process.cwd();\n      process.chdir('/tmp');\n      writeFileSync('.env', validEnv);\n      try {\n        process.loadEnvFile();\n      } finally {\n        process.chdir(originalCwd);\n      }\n    }\n  },\n};\n\nexport const processRejectionListeners = {\n  async test() {\n    const e = new Error();\n\n    const { promise: unhandledPromise, resolve } = Promise.withResolvers();\n    process.on('unhandledRejection', (reason, promise) => {\n      resolve({ reason, promise });\n    });\n    Promise.reject(e);\n    const { reason, promise } = await unhandledPromise;\n    assert.strictEqual(reason, e);\n\n    {\n      const { promise: handledPromise, resolve } = Promise.withResolvers();\n      process.on('rejectionHandled', (promise) => {\n        resolve({ promise });\n      });\n      await new Promise((resolve) => promise.catch(resolve));\n      const { promise: promise2 } = await handledPromise;\n      assert.strictEqual(promise, promise2);\n    }\n  },\n};\n\n// init time process.cwd checks\nassert.strictEqual(process.cwd(), '/bundle');\nprocess.chdir('/');\nassert.deepStrictEqual(readdirSync('.'), ['bundle', 'tmp', 'dev']);\n\nexport const processCwd = {\n  test() {\n    assert.strictEqual(process.cwd(), '/bundle');\n\n    const originalCwd = process.cwd();\n\n    process.chdir('/tmp');\n    assert.strictEqual(process.cwd(), '/tmp');\n    writeFileSync('foo', 'foo');\n\n    process.chdir(originalCwd);\n    assert.strictEqual(process.cwd(), originalCwd);\n\n    assert.throws(\n      () => {\n        readFileSync('foo');\n      },\n      { code: 'ENOENT' }\n    );\n\n    assert.strictEqual(readFileSync('/tmp/foo', 'utf8'), 'foo');\n\n    assert.throws(\n      () => {\n        process.chdir('/nonexistent/directory');\n      },\n      { code: 'ENOENT' }\n    );\n  },\n};\n\nexport const processCwdRelative = {\n  test() {\n    const originalCwd = process.cwd();\n\n    // Test relative path navigation\n    process.chdir('/tmp');\n    assert.strictEqual(process.cwd(), '/tmp');\n\n    // Test going up from /tmp (should work if parent exists)\n    try {\n      process.chdir('..');\n      const newCwd = process.cwd();\n      assert.strictEqual(newCwd, '/');\n    } catch (e) {\n      // If parent doesn't exist, that's fine for this test\n      if (e.code !== 'ENOENT') {\n        throw e;\n      }\n    }\n\n    // Test relative path from root\n    process.chdir('/');\n    process.chdir('tmp');\n    assert.strictEqual(process.cwd(), '/tmp');\n\n    // Test current directory \".\"\n    process.chdir('.');\n    assert.strictEqual(process.cwd(), '/tmp');\n\n    // Restore original directory\n    process.chdir(originalCwd);\n    assert.strictEqual(process.cwd(), originalCwd);\n  },\n};\n\nexport const processCwdBadInput = {\n  test() {\n    const longPath = 'a'.repeat(4097); // Just over the path length limit\n    assert.throws(\n      () => {\n        process.chdir(longPath);\n      },\n      { code: 'ENAMETOOLONG' }\n    );\n\n    try {\n      process.chdir('/tmp/basic-valid.env');\n      assert.fail('Expected chdir to throw an error');\n    } catch (err) {\n      assert.strictEqual(err.code, 'ENOENT');\n    }\n\n    assert.throws(\n      () => {\n        process.chdir('');\n      },\n      { code: 'ENOENT' }\n    );\n  },\n};\n\nexport const processUmask = {\n  test() {\n    assert.strictEqual(typeof process.umask, 'function');\n\n    assert.strictEqual(process.umask(), 18);\n\n    assert.strictEqual(process.umask(0), 18);\n    assert.strictEqual(process.umask(0o022), 18);\n    assert.strictEqual(process.umask(0o755), 18);\n    assert.strictEqual(process.umask(0xffffffff), 18);\n\n    assert.strictEqual(process.umask('0'), 18);\n    assert.strictEqual(process.umask('022'), 18);\n    assert.strictEqual(process.umask('755'), 18);\n    assert.strictEqual(process.umask('37777777777'), 18); // max 32-bit octal\n\n    assert.throws(() => process.umask('8'), { code: 'ERR_INVALID_ARG_VALUE' });\n    assert.throws(() => process.umask('9'), { code: 'ERR_INVALID_ARG_VALUE' });\n    assert.throws(() => process.umask('abc'), {\n      code: 'ERR_INVALID_ARG_VALUE',\n    });\n    assert.throws(() => process.umask('0x123'), {\n      code: 'ERR_INVALID_ARG_VALUE',\n    });\n    assert.throws(() => process.umask(''), { code: 'ERR_INVALID_ARG_VALUE' });\n\n    assert.throws(() => process.umask(-1), { code: 'ERR_INVALID_ARG_VALUE' });\n    assert.throws(() => process.umask(1.5), { code: 'ERR_INVALID_ARG_VALUE' });\n    assert.throws(\n      () => process.umask(0x100000000), // 2^32\n      { code: 'ERR_INVALID_ARG_VALUE' }\n    );\n    assert.throws(() => process.umask(NaN), { code: 'ERR_INVALID_ARG_VALUE' });\n    assert.throws(() => process.umask(Infinity), {\n      code: 'ERR_INVALID_ARG_VALUE',\n    });\n\n    assert.throws(() => process.umask({}), { code: 'ERR_INVALID_ARG_TYPE' });\n    assert.throws(() => process.umask([]), { code: 'ERR_INVALID_ARG_TYPE' });\n    assert.throws(() => process.umask(null), { code: 'ERR_INVALID_ARG_TYPE' });\n    assert.throws(() => process.umask(true), { code: 'ERR_INVALID_ARG_TYPE' });\n\n    assert.throws(\n      () => process.umask('40000000000'), // > 32-bit max octal\n      { code: 'ERR_INVALID_ARG_VALUE' }\n    );\n  },\n};\n\nexport const processStdio = {\n  test() {\n    assert.ok(process.stdout, 'process.stdout should exist');\n    assert.ok(process.stderr, 'process.stderr should exist');\n\n    assert.ok(process.stdout.writable, 'process.stdout should be writable');\n    assert.ok(process.stderr.writable, 'process.stderr should be writable');\n\n    assert.strictEqual(\n      typeof process.stdout.write,\n      'function',\n      'process.stdout.write should be a function'\n    );\n    assert.strictEqual(\n      typeof process.stderr.write,\n      'function',\n      'process.stderr.write should be a function'\n    );\n\n    assert.notStrictEqual(\n      process.stdout,\n      process.stderr,\n      'stdout and stderr should be different objects'\n    );\n\n    assert.doesNotThrow(() => {\n      process.stdout.write('test stdout');\n      process.stderr.write('test stderr');\n    }, 'Writing to stdio streams should not throw');\n\n    assert.ok(process.stdin, 'process.stdin should exist');\n    assert.ok(process.stdin.readable, 'process.stdin should be readable');\n    assert.strictEqual(process.stdin.read(), null);\n\n    assert.strictEqual(\n      process.stdout.isTTY,\n      undefined,\n      'process.stdout.isTTY should be undefined'\n    );\n    assert(!('isTTY' in process.stdin));\n    assert(!('isTTY' in process.stdout));\n    assert(!('isTTY' in process.stderr));\n\n    assert.strictEqual(process.stdin.fd, 0, 'process.stdin.fd should be 0');\n    assert.strictEqual(process.stdout.fd, 1, 'process.stdout.fd should be 1');\n    assert.strictEqual(process.stderr.fd, 2, 'process.stderr.fd should be 2');\n  },\n};\n\nexport const processAssert = {\n  test() {\n    assert.ok('assert' in process);\n    assert.notStrictEqual(process.assert, undefined);\n    assert.strictEqual(typeof process.assert, 'function');\n  },\n};\n\nexport const processToStringTag = {\n  test() {\n    assert.strictEqual(\n      Object.prototype.toString.call(process),\n      '[object process]'\n    );\n  },\n};\n\nexport const processGetReport = {\n  test() {\n    assert.deepStrictEqual(process.report.getReport(), {});\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/process-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"nodejs-process-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"process-nodejs-test.js\"),\n          (name = \"mod\", esModule = \"export const { FOO } = process.env;\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"nodejs_compat_v2\",\n          \"enable_nodejs_process_v2\",\n          \"enable_nodejs_fs_module\",\n          \"nodejs_compat_populate_process_env\"\n        ],\n        bindings = [\n          (name = \"FOO\", text = \"BAR\"),\n          (name = \"BAR\", json = \"{}\"),\n          (name = \"BAZ\", json = \"\\\"abc\\\"\"),\n          (name = \"DUB\", json = \"\\\"\\\\\\\"abc\\\\\\\"\\\"\"),\n          (name = \"NON\", data = \"abcdef\")\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/process-stdio-fs-nodejs-test.expected_stderr",
    "content": ""
  },
  {
    "path": "src/workerd/api/node/tests/process-stdio-fs-nodejs-test.expected_stdout",
    "content": "> Direct writeSync to stdout\nDirect writeSync to stderr\n> BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
  },
  {
    "path": "src/workerd/api/node/tests/process-stdio-fs-nodejs-test.js",
    "content": "import { writeSync, ReadStream } from 'node:fs';\nimport { Buffer } from 'node:buffer';\nimport { Readable } from 'node:stream';\nimport assert from 'node:assert';\n\nexport const processStdioPropertiesTest = {\n  test() {\n    assert.strictEqual(process.stdin.fd, 0, 'stdin should have fd 0');\n    assert(\n      process.stdin instanceof Readable,\n      'stdin should be instance of Readable'\n    );\n    assert(\n      process.stdin instanceof ReadStream,\n      'stdin should be instance of ReadStream'\n    );\n  },\n};\n\nexport const fdBasedOperationsTest = {\n  test() {\n    const message = 'Direct writeSync to stdout\\n';\n    const buffer = Buffer.from(message);\n    const bytesWritten = writeSync(1, buffer);\n    assert.strictEqual(\n      bytesWritten,\n      buffer.length,\n      'writeSync should return correct byte count'\n    );\n\n    const errorMessage = 'Direct writeSync to stderr\\n';\n    const errorBuffer = Buffer.from(errorMessage);\n    const errorBytesWritten = writeSync(2, errorBuffer);\n    assert.strictEqual(\n      errorBytesWritten,\n      errorBuffer.length,\n      'writeSync to stderr should return correct byte count'\n    );\n  },\n};\n\nexport const largeWriteTruncationTest = {\n  test() {\n    const largeBuffer = Buffer.alloc(4001 * 5, 'A'.repeat(4000) + '\\n');\n    largeBuffer[0] = 66;\n    largeBuffer[4001 * 5 - 1] = 67;\n    const bytesWritten = writeSync(1, largeBuffer);\n\n    assert.strictEqual(\n      bytesWritten,\n      16 * 1024,\n      'Direct fd write should be truncated to 16KiB'\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/process-stdio-fs-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"process-stdio-fs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"process-stdio-fs-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"nodejs_compat_v2\",\n          \"enable_nodejs_fs_module\",\n          \"enable_nodejs_process_v2\"\n        ],\n      )\n    ),\n  ],\n  logging = (\n    stdoutPrefix = \">\",\n    stderrPrefix = \"\"\n  )\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/process-stdio-nodejs-test.expected_stderr",
    "content": "console.error\nconsole.error interleaves\n"
  },
  {
    "path": "src/workerd/api/node/tests/process-stdio-nodejs-test.expected_stdout",
    "content": "stdout: BABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABX\nstdout: AFTER_FLUSH\nstdout: CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD\nstdout: END_ROLLING\nstderr: Error Line 1\nstderr: Error Line 2\nstderr: Error Line 3\nstderr: Error First\nstderr:\nstderr: Error Second with empty line\nstderr: Error with trailing newlines\nstderr:\nstderr:\nstderr: Error Start\nstderr: Error Mid1\nstderr: Error Mid2\nstderr: Error End\nstdout: Line 1\nstdout: Line 2\nstdout: Line 3\nstdout: First\nstdout:\nstdout: Second with empty line before\nstdout: Multiple trailing newlines\nstdout:\nstdout:\nstdout: Start\nstdout: Middle1\nstdout: Middle2\nstdout: End\nstdout: Test with callback\nstdout: Test with encoding and callback\nstdout: Corked write 1\nstdout: Corked write 2\nstdout: Test string write to stdout\nstderr: Test string write to stderr\nstdout: Test buffer write\nstderr: Test buffer write\nstdout: Hello\nstderr: Hello\nstdout: Test UTF-8: café\nconsole.log\nstdout: Test base64: Hello World!\nconsole.log interleaves\nstdout: 13�\nstderr: 24\nstdout: �\nstderr: 😀\n"
  },
  {
    "path": "src/workerd/api/node/tests/process-stdio-nodejs-test.js",
    "content": "import { Buffer } from 'node:buffer';\nimport { Readable, Writable } from 'node:stream';\nimport assert from 'node:assert';\nimport process from 'node:process';\n\nexport const processStdioPropertiesTest = {\n  test() {\n    assert.strictEqual(process.stdin.fd, 0, 'stdin should have fd 0');\n    assert(\n      process.stdin instanceof Readable,\n      'stdin should be instance of Readable'\n    );\n\n    assert.strictEqual(process.stdout.fd, 1, 'stdout should have fd 1');\n    assert.strictEqual(\n      process.stdout.readable,\n      false,\n      'stdout should not be readable'\n    );\n    assert.strictEqual(\n      process.stdout._type,\n      'fs',\n      'stdout should have _type \"fs\"'\n    );\n    assert.strictEqual(\n      process.stdout._isStdio,\n      true,\n      'stdout should have _isStdio true'\n    );\n    assert(\n      process.stdout instanceof Writable,\n      'stdout should be instance of Writable'\n    );\n\n    assert.strictEqual(process.stderr.fd, 2, 'stderr should have fd 2');\n    assert.strictEqual(\n      process.stderr.readable,\n      false,\n      'stderr should not be readable'\n    );\n    assert.strictEqual(\n      process.stderr._type,\n      'fs',\n      'stderr should have _type \"fs\"'\n    );\n    assert.strictEqual(\n      process.stderr._isStdio,\n      true,\n      'stderr should have _isStdio true'\n    );\n    assert(\n      process.stderr instanceof Writable,\n      'stderr should be instance of Writable'\n    );\n  },\n};\n\nexport const processStdioWriteTest = {\n  async test() {\n    process.stdout.write('Test string write to stdout\\n');\n    process.stderr.write('Test string write to stderr\\n');\n\n    const bufferData = Buffer.from('Test buffer write\\n');\n    process.stdout.write(bufferData);\n    process.stderr.write(bufferData);\n\n    const uint8Array = new Uint8Array([72, 101, 108, 108, 111, 10]); // \"Hello\\n\"\n    process.stdout.write(uint8Array);\n    process.stderr.write(uint8Array);\n\n    process.stdout.write('Test UTF-8: café\\n', 'utf8');\n    console.error('console.error');\n    console.log('console.log');\n    process.stdout.write('Test base64: ', 'utf8');\n    process.stdout.write('SGVsbG8gV29ybGQh', 'base64'); // \"Hello World!\"\n    process.stdout.write('\\n');\n\n    // these get ignored!\n    console.dir('test');\n    console.table([\n      { a: 'a', b: 'b' },\n      { a: 'c', b: 'd' },\n    ]);\n    console.log('console.log interleaves');\n    console.error('console.error interleaves');\n\n    process.stdout.write('1');\n    process.stderr.write('2');\n    process.stdout.write('3');\n    process.stderr.write('4');\n\n    process.stdout.write('\\uD83D');\n    await new Promise((resolve) => setTimeout(resolve, 1));\n    process.stdout.write('\\uDE00');\n    process.stderr.write('😀');\n\n    // should buffer until the newline, supporting the partial emoji above\n    process.stdout.write('\\n');\n    // stderr doesn't get final newline -> should still be a flush before exit\n  },\n};\n\nexport const processStdioCallbackTest = {\n  async test() {\n    await new Promise((resolve, reject) => {\n      process.stdout.write('Test with callback\\n', (err) => {\n        if (err) {\n          reject(err);\n        } else {\n          resolve();\n        }\n      });\n    });\n\n    await new Promise((resolve, reject) => {\n      process.stdout.write(\n        'Test with encoding and callback\\n',\n        'utf8',\n        (err) => {\n          if (err) {\n            reject(err);\n          } else {\n            resolve();\n          }\n        }\n      );\n    });\n  },\n};\n\nexport const processStdioStreamMethodsTest = {\n  test() {\n    assert(\n      typeof process.stdout.end === 'function',\n      'stdout should have end method'\n    );\n    assert(\n      typeof process.stdout.cork === 'function',\n      'stdout should have cork method'\n    );\n    assert(\n      typeof process.stdout.uncork === 'function',\n      'stdout should have uncork method'\n    );\n    assert(\n      typeof process.stdout.destroy === 'function',\n      'stdout should have destroy method'\n    );\n\n    process.stdout.cork();\n    process.stdout.write('Corked write 1\\n');\n    process.stdout.write('Corked write 2\\n');\n    process.stdout.uncork(); // Should flush the buffered writes\n  },\n};\n\nexport const processStdinTest = {\n  async test() {\n    assert.strictEqual(process.stdin.fd, 0, 'stdin should have fd 0');\n    assert(process.stdin instanceof Readable, 'stdin should be Readable');\n\n    let dataReceived = false;\n    let endReceived = false;\n\n    process.stdin.on('data', (chunk) => {\n      dataReceived = true;\n      console.log('Received data from stdin:', chunk);\n    });\n\n    await new Promise((resolve) =>\n      process.stdin.on('end', () => {\n        endReceived = true;\n        resolve();\n      })\n    );\n\n    assert.strictEqual(dataReceived, false);\n    assert.strictEqual(endReceived, true);\n  },\n};\n\nexport const lineBufferTruncationTest = {\n  async test() {\n    process.stdout.write(Buffer.alloc(4096, 'AB'));\n    process.stdout.write('X');\n    await new Promise((resolve) => setTimeout(resolve, 1));\n    process.stdout.write('AFTER_FLUSH\\n');\n    process.stdout.write(Buffer.alloc(3000, 'C'));\n    process.stdout.write(Buffer.alloc(2000, 'D'));\n    await new Promise((resolve) => setTimeout(resolve, 1));\n    process.stdout.write('END_ROLLING\\n');\n  },\n};\n\nexport const multipleNewlinesStdoutTest = {\n  test() {\n    process.stdout.write('Line 1\\nLine 2\\nLine 3\\n');\n    process.stdout.write('First\\n\\nSecond with empty line before\\n');\n    process.stdout.write('Multiple trailing newlines\\n\\n\\n');\n    process.stdout.write('Start\\nMiddle1\\nMiddle2\\nEnd');\n    process.stdout.write('\\n');\n  },\n};\n\nexport const multipleNewlinesStderrTest = {\n  test() {\n    process.stderr.write('Error Line 1\\nError Line 2\\nError Line 3\\n');\n    process.stderr.write('Error First\\n\\nError Second with empty line\\n');\n    process.stderr.write('Error with trailing newlines\\n\\n\\n');\n    process.stderr.write('Error Start\\nError Mid1\\nError Mid2\\nError End');\n    process.stderr.write('\\n');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/process-stdio-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"process-stdio-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"process-stdio-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"nodejs_compat_v2\",\n          \"enable_nodejs_process_v2\"\n        ],\n      )\n    ),\n  ]\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/punycode-nodejs-test.js",
    "content": "import assert from 'node:assert';\nimport punycode from 'node:punycode';\n\nexport const testPunycode = {\n  test() {\n    assert.strictEqual(punycode.encode('ü'), 'tda');\n    assert.strictEqual(punycode.encode('Goethe'), 'Goethe-');\n    assert.strictEqual(punycode.encode('Bücher'), 'Bcher-kva');\n    assert.strictEqual(\n      punycode.encode(\n        'Willst du die Blüthe des frühen, die Früchte des späteren Jahres'\n      ),\n      'Willst du die Blthe des frhen, die Frchte des spteren Jahres-x9e96lkal'\n    );\n    assert.strictEqual(punycode.encode('日本語'), 'wgv71a119e');\n    assert.strictEqual(punycode.encode('𩸽'), 'x73l');\n\n    assert.strictEqual(punycode.decode('tda'), 'ü');\n    assert.strictEqual(punycode.decode('Goethe-'), 'Goethe');\n    assert.strictEqual(punycode.decode('Bcher-kva'), 'Bücher');\n    assert.strictEqual(\n      punycode.decode(\n        'Willst du die Blthe des frhen, die Frchte des spteren Jahres-x9e96lkal'\n      ),\n      'Willst du die Blüthe des frühen, die Früchte des späteren Jahres'\n    );\n    assert.strictEqual(punycode.decode('wgv71a119e'), '日本語');\n    assert.strictEqual(punycode.decode('x73l'), '𩸽');\n    assert.throws(() => {\n      punycode.decode(' ');\n    }, /^RangeError: Invalid input$/);\n    assert.throws(() => {\n      punycode.decode('α-');\n    }, /^RangeError: Illegal input >= 0x80 \\(not a basic code point\\)$/);\n    assert.throws(() => {\n      punycode.decode('あ');\n    }, /^RangeError: Invalid input$/);\n\n    // http://tools.ietf.org/html/rfc3492#section-7.1\n    const tests = [\n      // (A) Arabic (Egyptian)\n      {\n        encoded: 'egbpdaj6bu4bxfgehfvwxn',\n        decoded:\n          '\\u0644\\u064A\\u0647\\u0645\\u0627\\u0628\\u062A\\u0643\\u0644\\u0645' +\n          '\\u0648\\u0634\\u0639\\u0631\\u0628\\u064A\\u061F',\n      },\n\n      // (B) Chinese (simplified)\n      {\n        encoded: 'ihqwcrb4cv8a8dqg056pqjye',\n        decoded: '\\u4ED6\\u4EEC\\u4E3A\\u4EC0\\u4E48\\u4E0D\\u8BF4\\u4E2D\\u6587',\n      },\n\n      // (C) Chinese (traditional)\n      {\n        encoded: 'ihqwctvzc91f659drss3x8bo0yb',\n        decoded: '\\u4ED6\\u5011\\u7232\\u4EC0\\u9EBD\\u4E0D\\u8AAA\\u4E2D\\u6587',\n      },\n\n      // (D) Czech: Pro<ccaron>prost<ecaron>nemluv<iacute><ccaron>esky\n      {\n        encoded: 'Proprostnemluvesky-uyb24dma41a',\n        decoded:\n          '\\u0050\\u0072\\u006F\\u010D\\u0070\\u0072\\u006F\\u0073\\u0074\\u011B' +\n          '\\u006E\\u0065\\u006D\\u006C\\u0075\\u0076\\u00ED\\u010D\\u0065\\u0073\\u006B\\u0079',\n      },\n\n      // (E) Hebrew\n      {\n        encoded: '4dbcagdahymbxekheh6e0a7fei0b',\n        decoded:\n          '\\u05DC\\u05DE\\u05D4\\u05D4\\u05DD\\u05E4\\u05E9\\u05D5\\u05D8\\u05DC' +\n          '\\u05D0\\u05DE\\u05D3\\u05D1\\u05E8\\u05D9\\u05DD\\u05E2\\u05D1\\u05E8\\u05D9\\u05EA',\n      },\n\n      // (F) Hindi (Devanagari)\n      {\n        encoded: 'i1baa7eci9glrd9b2ae1bj0hfcgg6iyaf8o0a1dig0cd',\n        decoded:\n          '\\u092F\\u0939\\u0932\\u094B\\u0917\\u0939\\u093F\\u0928\\u094D\\u0926' +\n          '\\u0940\\u0915\\u094D\\u092F\\u094B\\u0902\\u0928\\u0939\\u0940\\u0902\\u092C' +\n          '\\u094B\\u0932\\u0938\\u0915\\u0924\\u0947\\u0939\\u0948\\u0902',\n      },\n\n      // (G) Japanese (kanji and hiragana)\n      {\n        encoded: 'n8jok5ay5dzabd5bym9f0cm5685rrjetr6pdxa',\n        decoded:\n          '\\u306A\\u305C\\u307F\\u3093\\u306A\\u65E5\\u672C\\u8A9E\\u3092\\u8A71' +\n          '\\u3057\\u3066\\u304F\\u308C\\u306A\\u3044\\u306E\\u304B',\n      },\n\n      // (H) Korean (Hangul syllables)\n      {\n        encoded:\n          '989aomsvi5e83db1d2a355cv1e0vak1dwrv93d5xbh15a0dt30a5jpsd879' +\n          'ccm6fea98c',\n        decoded:\n          '\\uC138\\uACC4\\uC758\\uBAA8\\uB4E0\\uC0AC\\uB78C\\uB4E4\\uC774\\uD55C' +\n          '\\uAD6D\\uC5B4\\uB97C\\uC774\\uD574\\uD55C\\uB2E4\\uBA74\\uC5BC\\uB9C8\\uB098' +\n          '\\uC88B\\uC744\\uAE4C',\n      },\n\n      // (I) Russian (Cyrillic)\n      {\n        encoded: 'b1abfaaepdrnnbgefbadotcwatmq2g4l',\n        decoded:\n          '\\u043F\\u043E\\u0447\\u0435\\u043C\\u0443\\u0436\\u0435\\u043E\\u043D' +\n          '\\u0438\\u043D\\u0435\\u0433\\u043E\\u0432\\u043E\\u0440\\u044F\\u0442\\u043F' +\n          '\\u043E\\u0440\\u0443\\u0441\\u0441\\u043A\\u0438',\n      },\n\n      // (J) Spanish: Porqu<eacute>nopuedensimplementehablarenEspa<ntilde>ol\n      {\n        encoded: 'PorqunopuedensimplementehablarenEspaol-fmd56a',\n        decoded:\n          '\\u0050\\u006F\\u0072\\u0071\\u0075\\u00E9\\u006E\\u006F\\u0070\\u0075' +\n          '\\u0065\\u0064\\u0065\\u006E\\u0073\\u0069\\u006D\\u0070\\u006C\\u0065\\u006D' +\n          '\\u0065\\u006E\\u0074\\u0065\\u0068\\u0061\\u0062\\u006C\\u0061\\u0072\\u0065' +\n          '\\u006E\\u0045\\u0073\\u0070\\u0061\\u00F1\\u006F\\u006C',\n      },\n\n      // (K) Vietnamese: T<adotbelow>isaoh<odotbelow>kh<ocirc>ngth\n      // <ecirchookabove>ch<ihookabove>n<oacute>iti<ecircacute>ngVi<ecircdotbelow>t\n      {\n        encoded: 'TisaohkhngthchnitingVit-kjcr8268qyxafd2f1b9g',\n        decoded:\n          '\\u0054\\u1EA1\\u0069\\u0073\\u0061\\u006F\\u0068\\u1ECD\\u006B\\u0068' +\n          '\\u00F4\\u006E\\u0067\\u0074\\u0068\\u1EC3\\u0063\\u0068\\u1EC9\\u006E\\u00F3' +\n          '\\u0069\\u0074\\u0069\\u1EBF\\u006E\\u0067\\u0056\\u0069\\u1EC7\\u0074',\n      },\n\n      // (L) 3<nen>B<gumi><kinpachi><sensei>\n      {\n        encoded: '3B-ww4c5e180e575a65lsy2b',\n        decoded: '\\u0033\\u5E74\\u0042\\u7D44\\u91D1\\u516B\\u5148\\u751F',\n      },\n\n      // (M) <amuro><namie>-with-SUPER-MONKEYS\n      {\n        encoded: '-with-SUPER-MONKEYS-pc58ag80a8qai00g7n9n',\n        decoded:\n          '\\u5B89\\u5BA4\\u5948\\u7F8E\\u6075\\u002D\\u0077\\u0069\\u0074\\u0068' +\n          '\\u002D\\u0053\\u0055\\u0050\\u0045\\u0052\\u002D\\u004D\\u004F\\u004E\\u004B' +\n          '\\u0045\\u0059\\u0053',\n      },\n\n      // (N) Hello-Another-Way-<sorezore><no><basho>\n      {\n        encoded: 'Hello-Another-Way--fc4qua05auwb3674vfr0b',\n        decoded:\n          '\\u0048\\u0065\\u006C\\u006C\\u006F\\u002D\\u0041\\u006E\\u006F\\u0074' +\n          '\\u0068\\u0065\\u0072\\u002D\\u0057\\u0061\\u0079\\u002D\\u305D\\u308C\\u305E' +\n          '\\u308C\\u306E\\u5834\\u6240',\n      },\n\n      // (O) <hitotsu><yane><no><shita>2\n      {\n        encoded: '2-u9tlzr9756bt3uc0v',\n        decoded: '\\u3072\\u3068\\u3064\\u5C4B\\u6839\\u306E\\u4E0B\\u0032',\n      },\n\n      // (P) Maji<de>Koi<suru>5<byou><mae>\n      {\n        encoded: 'MajiKoi5-783gue6qz075azm5e',\n        decoded:\n          '\\u004D\\u0061\\u006A\\u0069\\u3067\\u004B\\u006F\\u0069\\u3059\\u308B' +\n          '\\u0035\\u79D2\\u524D',\n      },\n\n      // (Q) <pafii>de<runba>\n      {\n        encoded: 'de-jg4avhby1noc0d',\n        decoded: '\\u30D1\\u30D5\\u30A3\\u30FC\\u0064\\u0065\\u30EB\\u30F3\\u30D0',\n      },\n\n      // (R) <sono><supiido><de>\n      {\n        encoded: 'd9juau41awczczp',\n        decoded: '\\u305D\\u306E\\u30B9\\u30D4\\u30FC\\u30C9\\u3067',\n      },\n\n      // (S) -> $1.00 <-\n      {\n        encoded: '-> $1.00 <--',\n        decoded:\n          '\\u002D\\u003E\\u0020\\u0024\\u0031\\u002E\\u0030\\u0030\\u0020\\u003C' +\n          '\\u002D',\n      },\n    ];\n\n    let errors = 0;\n    const handleError = (error, name) => {\n      console.error(\n        `FAIL: ${name} expected ${error.expected}, got ${error.actual}`\n      );\n      errors++;\n    };\n\n    const regexNonASCII = /[^\\x20-\\x7E]/;\n    const testBattery = {\n      encode: (test) =>\n        assert.strictEqual(punycode.encode(test.decoded), test.encoded),\n      decode: (test) =>\n        assert.strictEqual(punycode.decode(test.encoded), test.decoded),\n      toASCII: (test) =>\n        assert.strictEqual(\n          punycode.toASCII(test.decoded),\n          regexNonASCII.test(test.decoded)\n            ? `xn--${test.encoded}`\n            : test.decoded\n        ),\n      toUnicode: (test) =>\n        assert.strictEqual(\n          punycode.toUnicode(\n            regexNonASCII.test(test.decoded)\n              ? `xn--${test.encoded}`\n              : test.decoded\n          ),\n          regexNonASCII.test(test.decoded)\n            ? test.decoded.toLowerCase()\n            : test.decoded\n        ),\n    };\n\n    tests.forEach((testCase) => {\n      Object.keys(testBattery).forEach((key) => {\n        try {\n          testBattery[key](testCase);\n        } catch (error) {\n          handleError(error, key);\n        }\n      });\n    });\n\n    // BMP code point\n    assert.strictEqual(punycode.ucs2.encode([0x61]), 'a');\n    // Supplementary code point (surrogate pair)\n    assert.strictEqual(punycode.ucs2.encode([0x1d306]), '\\uD834\\uDF06');\n    // high surrogate\n    assert.strictEqual(punycode.ucs2.encode([0xd800]), '\\uD800');\n    // High surrogate followed by non-surrogates\n    assert.strictEqual(punycode.ucs2.encode([0xd800, 0x61, 0x62]), '\\uD800ab');\n    // low surrogate\n    assert.strictEqual(punycode.ucs2.encode([0xdc00]), '\\uDC00');\n    // Low surrogate followed by non-surrogates\n    assert.strictEqual(punycode.ucs2.encode([0xdc00, 0x61, 0x62]), '\\uDC00ab');\n\n    assert.strictEqual(errors, 0);\n\n    // test map domain\n    assert.strictEqual(\n      punycode.toASCII('Bücher@日本語.com'),\n      'Bücher@xn--wgv71a119e.com'\n    );\n    assert.strictEqual(\n      punycode.toUnicode('Bücher@xn--wgv71a119e.com'),\n      'Bücher@日本語.com'\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/punycode-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"nodejs-punycode-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"punycode-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_punycode_module\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/querystring-nodejs-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\nimport qs from 'node:querystring';\nimport assert from 'node:assert';\nimport { inspect } from 'node:util';\n\n// Ref: https://github.com/nodejs/node/blob/4d6d7d644be4f10f90e5c9c66563736112fffbff/test/parallel/test-querystring.js\nexport const testQuerystring = {\n  async test() {\n    function createWithNoPrototype(properties) {\n      const noProto = { __proto__: null };\n      properties.forEach((property) => {\n        noProto[property.key] = property.value;\n      });\n      return noProto;\n    }\n\n    // Folding block, commented to pass gjslint\n    // {{{\n    // [ wonkyQS, canonicalQS, obj ]\n    const qsTestCases = [\n      [\n        '__proto__=1',\n        '__proto__=1',\n        createWithNoPrototype([{ key: '__proto__', value: '1' }]),\n      ],\n      [\n        '__defineGetter__=asdf',\n        '__defineGetter__=asdf',\n        JSON.parse('{\"__defineGetter__\":\"asdf\"}'),\n      ],\n      [\n        'foo=918854443121279438895193',\n        'foo=918854443121279438895193',\n        { foo: '918854443121279438895193' },\n      ],\n      ['foo=bar', 'foo=bar', { foo: 'bar' }],\n      ['foo=bar&foo=quux', 'foo=bar&foo=quux', { foo: ['bar', 'quux'] }],\n      ['foo=1&bar=2', 'foo=1&bar=2', { foo: '1', bar: '2' }],\n      [\n        'my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F',\n        \"my%20weird%20field=q1!2%22'w%245%267%2Fz8)%3F\",\n        { 'my weird field': 'q1!2\"\\'w$5&7/z8)?' },\n      ],\n      ['foo%3Dbaz=bar', 'foo%3Dbaz=bar', { 'foo=baz': 'bar' }],\n      ['foo=baz=bar', 'foo=baz%3Dbar', { foo: 'baz=bar' }],\n      [\n        'str=foo&arr=1&arr=2&arr=3&somenull=&undef=',\n        'str=foo&arr=1&arr=2&arr=3&somenull=&undef=',\n        { str: 'foo', arr: ['1', '2', '3'], somenull: '', undef: '' },\n      ],\n      [' foo = bar ', '%20foo%20=%20bar%20', { ' foo ': ' bar ' }],\n      ['foo=%zx', 'foo=%25zx', { foo: '%zx' }],\n      ['foo=%EF%BF%BD', 'foo=%EF%BF%BD', { foo: '\\ufffd' }],\n      // See: https://github.com/joyent/node/issues/1707\n      [\n        'hasOwnProperty=x&toString=foo&valueOf=bar&__defineGetter__=baz',\n        'hasOwnProperty=x&toString=foo&valueOf=bar&__defineGetter__=baz',\n        {\n          hasOwnProperty: 'x',\n          toString: 'foo',\n          valueOf: 'bar',\n          __defineGetter__: 'baz',\n        },\n      ],\n      // See: https://github.com/joyent/node/issues/3058\n      ['foo&bar=baz', 'foo=&bar=baz', { foo: '', bar: 'baz' }],\n      ['a=b&c&d=e', 'a=b&c=&d=e', { a: 'b', c: '', d: 'e' }],\n      ['a=b&c=&d=e', 'a=b&c=&d=e', { a: 'b', c: '', d: 'e' }],\n      ['a=b&=c&d=e', 'a=b&=c&d=e', { a: 'b', '': 'c', d: 'e' }],\n      ['a=b&=&c=d', 'a=b&=&c=d', { a: 'b', '': '', c: 'd' }],\n      ['&&foo=bar&&', 'foo=bar', { foo: 'bar' }],\n      ['&', '', {}],\n      ['&&&&', '', {}],\n      ['&=&', '=', { '': '' }],\n      ['&=&=', '=&=', { '': ['', ''] }],\n      ['=', '=', { '': '' }],\n      ['+', '%20=', { ' ': '' }],\n      ['+=', '%20=', { ' ': '' }],\n      ['+&', '%20=', { ' ': '' }],\n      ['=+', '=%20', { '': ' ' }],\n      ['+=&', '%20=', { ' ': '' }],\n      ['a&&b', 'a=&b=', { a: '', b: '' }],\n      ['a=a&&b=b', 'a=a&b=b', { a: 'a', b: 'b' }],\n      ['&a', 'a=', { a: '' }],\n      ['&=', '=', { '': '' }],\n      ['a&a&', 'a=&a=', { a: ['', ''] }],\n      ['a&a&a&', 'a=&a=&a=', { a: ['', '', ''] }],\n      ['a&a&a&a&', 'a=&a=&a=&a=', { a: ['', '', '', ''] }],\n      ['a=&a=value&a=', 'a=&a=value&a=', { a: ['', 'value', ''] }],\n      ['foo+bar=baz+quux', 'foo%20bar=baz%20quux', { 'foo bar': 'baz quux' }],\n      ['+foo=+bar', '%20foo=%20bar', { ' foo': ' bar' }],\n      ['a+', 'a%20=', { 'a ': '' }],\n      ['=a+', '=a%20', { '': 'a ' }],\n      ['a+&', 'a%20=', { 'a ': '' }],\n      ['=a+&', '=a%20', { '': 'a ' }],\n      ['%20+', '%20%20=', { '  ': '' }],\n      ['=%20+', '=%20%20', { '': '  ' }],\n      ['%20+&', '%20%20=', { '  ': '' }],\n      ['=%20+&', '=%20%20', { '': '  ' }],\n      [null, '', {}],\n      [undefined, '', {}],\n    ];\n\n    // [ wonkyQS, canonicalQS, obj ]\n    const qsColonTestCases = [\n      ['foo:bar', 'foo:bar', { foo: 'bar' }],\n      ['foo:bar;foo:quux', 'foo:bar;foo:quux', { foo: ['bar', 'quux'] }],\n      [\n        'foo:1&bar:2;baz:quux',\n        'foo:1%26bar%3A2;baz:quux',\n        { foo: '1&bar:2', baz: 'quux' },\n      ],\n      ['foo%3Abaz:bar', 'foo%3Abaz:bar', { 'foo:baz': 'bar' }],\n      ['foo:baz:bar', 'foo:baz%3Abar', { foo: 'baz:bar' }],\n    ];\n\n    // [wonkyObj, qs, canonicalObj]\n    function extendedFunction() {}\n    extendedFunction.prototype = { a: 'b' };\n    const qsWeirdObjects = [\n      // eslint-disable-next-line node-core/no-unescaped-regexp-dot\n      [{ regexp: /./g }, 'regexp=', { regexp: '' }],\n      // eslint-disable-next-line node-core/no-unescaped-regexp-dot\n      [{ regexp: new RegExp('.', 'g') }, 'regexp=', { regexp: '' }],\n      [{ fn: () => {} }, 'fn=', { fn: '' }],\n      [{ math: Math }, 'math=', { math: '' }],\n      [{ e: extendedFunction }, 'e=', { e: '' }],\n      [{ d: new Date() }, 'd=', { d: '' }],\n      [{ d: Date }, 'd=', { d: '' }],\n      [\n        { f: new Boolean(false), t: new Boolean(true) },\n        'f=&t=',\n        { f: '', t: '' },\n      ],\n      [{ f: false, t: true }, 'f=false&t=true', { f: 'false', t: 'true' }],\n      [{ n: null }, 'n=', { n: '' }],\n      [{ nan: NaN }, 'nan=', { nan: '' }],\n      [{ inf: Infinity }, 'inf=', { inf: '' }],\n      [{ a: [], b: [] }, '', {}],\n      [{ a: 1, b: [] }, 'a=1', { a: '1' }],\n    ];\n    // }}}\n\n    const qsNoMungeTestCases = [\n      ['', {}],\n      ['foo=bar&foo=baz', { foo: ['bar', 'baz'] }],\n      ['blah=burp', { blah: 'burp' }],\n      [\"a=!-._~'()*\", { a: \"!-._~'()*\" }],\n      ['a=abcdefghijklmnopqrstuvwxyz', { a: 'abcdefghijklmnopqrstuvwxyz' }],\n      ['a=ABCDEFGHIJKLMNOPQRSTUVWXYZ', { a: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' }],\n      ['a=0123456789', { a: '0123456789' }],\n      ['gragh=1&gragh=3&goo=2', { gragh: ['1', '3'], goo: '2' }],\n      [\n        'frappucino=muffin&goat%5B%5D=scone&pond=moose',\n        { frappucino: 'muffin', 'goat[]': 'scone', pond: 'moose' },\n      ],\n      ['trololol=yes&lololo=no', { trololol: 'yes', lololo: 'no' }],\n    ];\n\n    const qsUnescapeTestCases = [\n      [\n        'there is nothing to unescape here',\n        'there is nothing to unescape here',\n      ],\n      [\n        'there%20are%20several%20spaces%20that%20need%20to%20be%20unescaped',\n        'there are several spaces that need to be unescaped',\n      ],\n      [\n        'there%2Qare%0-fake%escaped values in%%%%this%9Hstring',\n        'there%2Qare%0-fake%escaped values in%%%%this%9Hstring',\n      ],\n      [\n        '%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F%30%31%32%33%34%35%36%37',\n        ' !\"#$%&\\'()*+,-./01234567',\n      ],\n      ['%%2a', '%*'],\n      ['%2sf%2a', '%2sf*'],\n      ['%2%2af%2a', '%2*f*'],\n    ];\n\n    assert.strictEqual(\n      qs.parse('id=918854443121279438895193').id,\n      '918854443121279438895193'\n    );\n\n    function check(actual, expected, input) {\n      assert(!(actual instanceof Object));\n      const actualKeys = Object.keys(actual).sort();\n      const expectedKeys = Object.keys(expected).sort();\n      let msg;\n      if (typeof input === 'string') {\n        msg =\n          `Input: ${inspect(input)}\\n` +\n          `Actual keys: ${inspect(actualKeys)}\\n` +\n          `Expected keys: ${inspect(expectedKeys)}`;\n      }\n      assert.deepStrictEqual(actualKeys, expectedKeys, msg);\n      expectedKeys.forEach((key) => {\n        if (typeof input === 'string') {\n          msg =\n            `Input: ${inspect(input)}\\n` +\n            `Key: ${inspect(key)}\\n` +\n            `Actual value: ${inspect(actual[key])}\\n` +\n            `Expected value: ${inspect(expected[key])}`;\n        } else {\n          msg = undefined;\n        }\n        assert.deepStrictEqual(actual[key], expected[key], msg);\n      });\n    }\n\n    // Test that the canonical qs is parsed properly.\n    qsTestCases.forEach((testCase) => {\n      check(qs.parse(testCase[0]), testCase[2], testCase[0]);\n    });\n\n    // Test that the colon test cases can do the same\n    qsColonTestCases.forEach((testCase) => {\n      check(qs.parse(testCase[0], ';', ':'), testCase[2], testCase[0]);\n    });\n\n    // Test the weird objects, that they get parsed properly\n    qsWeirdObjects.forEach((testCase) => {\n      check(qs.parse(testCase[1]), testCase[2], testCase[1]);\n    });\n\n    qsNoMungeTestCases.forEach((testCase) => {\n      assert.deepStrictEqual(qs.stringify(testCase[1], '&', '='), testCase[0]);\n    });\n\n    // Test the nested qs-in-qs case\n    {\n      const f = qs.parse('a=b&q=x%3Dy%26y%3Dz');\n      check(\n        f,\n        createWithNoPrototype([\n          { key: 'a', value: 'b' },\n          { key: 'q', value: 'x=y&y=z' },\n        ])\n      );\n\n      f.q = qs.parse(f.q);\n      const expectedInternal = createWithNoPrototype([\n        { key: 'x', value: 'y' },\n        { key: 'y', value: 'z' },\n      ]);\n      check(f.q, expectedInternal);\n    }\n\n    // nested in colon\n    {\n      const f = qs.parse('a:b;q:x%3Ay%3By%3Az', ';', ':');\n      check(\n        f,\n        createWithNoPrototype([\n          { key: 'a', value: 'b' },\n          { key: 'q', value: 'x:y;y:z' },\n        ])\n      );\n      f.q = qs.parse(f.q, ';', ':');\n      const expectedInternal = createWithNoPrototype([\n        { key: 'x', value: 'y' },\n        { key: 'y', value: 'z' },\n      ]);\n      check(f.q, expectedInternal);\n    }\n\n    // Now test stringifying\n\n    // basic\n    qsTestCases.forEach((testCase) => {\n      assert.strictEqual(qs.stringify(testCase[2]), testCase[1]);\n    });\n\n    qsColonTestCases.forEach((testCase) => {\n      assert.strictEqual(qs.stringify(testCase[2], ';', ':'), testCase[1]);\n    });\n\n    qsWeirdObjects.forEach((testCase) => {\n      assert.strictEqual(qs.stringify(testCase[0]), testCase[1]);\n    });\n\n    // BigInt values\n\n    assert.strictEqual(\n      qs.stringify({ foo: 2n ** 1023n }),\n      'foo=' + 2n ** 1023n\n    );\n    assert.strictEqual(qs.stringify([0n, 1n, 2n]), '0=0&1=1&2=2');\n\n    assert.strictEqual(\n      qs.stringify({ foo: 2n ** 1023n }, null, null, {\n        encodeURIComponent: (c) => c,\n      }),\n      'foo=' + 2n ** 1023n\n    );\n    assert.strictEqual(\n      qs.stringify([0n, 1n, 2n], null, null, { encodeURIComponent: (c) => c }),\n      '0=0&1=1&2=2'\n    );\n\n    // Invalid surrogate pair throws URIError\n    assert.throws(() => qs.stringify({ foo: '\\udc00' }), {\n      code: 'ERR_INVALID_URI',\n      name: 'URIError',\n      message: 'URI malformed',\n    });\n\n    // Coerce numbers to string\n    assert.strictEqual(qs.stringify({ foo: 0 }), 'foo=0');\n    assert.strictEqual(qs.stringify({ foo: -0 }), 'foo=0');\n    assert.strictEqual(qs.stringify({ foo: 3 }), 'foo=3');\n    assert.strictEqual(qs.stringify({ foo: -72.42 }), 'foo=-72.42');\n    assert.strictEqual(qs.stringify({ foo: NaN }), 'foo=');\n    assert.strictEqual(qs.stringify({ foo: 1e21 }), 'foo=1e%2B21');\n    assert.strictEqual(qs.stringify({ foo: Infinity }), 'foo=');\n\n    // nested\n    {\n      const f = qs.stringify({\n        a: 'b',\n        q: qs.stringify({\n          x: 'y',\n          y: 'z',\n        }),\n      });\n      assert.strictEqual(f, 'a=b&q=x%3Dy%26y%3Dz');\n    }\n\n    qs.parse(undefined); // Should not throw.\n\n    // nested in colon\n    {\n      const f = qs.stringify(\n        {\n          a: 'b',\n          q: qs.stringify(\n            {\n              x: 'y',\n              y: 'z',\n            },\n            ';',\n            ':'\n          ),\n        },\n        ';',\n        ':'\n      );\n      assert.strictEqual(f, 'a:b;q:x%3Ay%3By%3Az');\n    }\n\n    // empty string\n    assert.strictEqual(qs.stringify(), '');\n    assert.strictEqual(qs.stringify(0), '');\n    assert.strictEqual(qs.stringify([]), '');\n    assert.strictEqual(qs.stringify(null), '');\n    assert.strictEqual(qs.stringify(true), '');\n\n    check(qs.parse(), {});\n\n    // empty sep\n    check(qs.parse('a', []), { a: '' });\n\n    // empty eq\n    check(qs.parse('a', null, []), { '': 'a' });\n\n    // Test limiting\n    assert.strictEqual(\n      Object.keys(qs.parse('a=1&b=1&c=1', null, null, { maxKeys: 1 })).length,\n      1\n    );\n\n    // Test limiting with a case that starts from `&`\n    assert.strictEqual(\n      Object.keys(qs.parse('&a', null, null, { maxKeys: 1 })).length,\n      0\n    );\n\n    // Test removing limit\n    {\n      function testUnlimitedKeys() {\n        const query = {};\n\n        for (let i = 0; i < 2000; i++) query[i] = i;\n\n        const url = qs.stringify(query);\n\n        assert.strictEqual(\n          Object.keys(qs.parse(url, null, null, { maxKeys: 0 })).length,\n          2000\n        );\n      }\n\n      testUnlimitedKeys();\n    }\n\n    {\n      const b = qs.unescapeBuffer(\n        '%d3%f2Ug%1f6v%24%5e%98%cb' + '%0d%ac%a2%2f%9d%eb%d8%a2%e6'\n      );\n      // <Buffer d3 f2 55 67 1f 36 76 24 5e 98 cb 0d ac a2 2f 9d eb d8 a2 e6>\n      assert.strictEqual(b[0], 0xd3);\n      assert.strictEqual(b[1], 0xf2);\n      assert.strictEqual(b[2], 0x55);\n      assert.strictEqual(b[3], 0x67);\n      assert.strictEqual(b[4], 0x1f);\n      assert.strictEqual(b[5], 0x36);\n      assert.strictEqual(b[6], 0x76);\n      assert.strictEqual(b[7], 0x24);\n      assert.strictEqual(b[8], 0x5e);\n      assert.strictEqual(b[9], 0x98);\n      assert.strictEqual(b[10], 0xcb);\n      assert.strictEqual(b[11], 0x0d);\n      assert.strictEqual(b[12], 0xac);\n      assert.strictEqual(b[13], 0xa2);\n      assert.strictEqual(b[14], 0x2f);\n      assert.strictEqual(b[15], 0x9d);\n      assert.strictEqual(b[16], 0xeb);\n      assert.strictEqual(b[17], 0xd8);\n      assert.strictEqual(b[18], 0xa2);\n      assert.strictEqual(b[19], 0xe6);\n    }\n\n    assert.strictEqual(qs.unescapeBuffer('a+b', true).toString(), 'a b');\n    assert.strictEqual(qs.unescapeBuffer('a+b').toString(), 'a+b');\n    assert.strictEqual(qs.unescapeBuffer('a%').toString(), 'a%');\n    assert.strictEqual(qs.unescapeBuffer('a%2').toString(), 'a%2');\n    assert.strictEqual(qs.unescapeBuffer('a%20').toString(), 'a ');\n    assert.strictEqual(qs.unescapeBuffer('a%2g').toString(), 'a%2g');\n    assert.strictEqual(qs.unescapeBuffer('a%%').toString(), 'a%%');\n\n    // Test invalid encoded string\n    check(qs.parse('%\\u0100=%\\u0101'), { '%Ā': '%ā' });\n\n    // Test custom decode\n    {\n      function demoDecode(str) {\n        return str + str;\n      }\n\n      check(\n        qs.parse('a=a&b=b&c=c', null, null, { decodeURIComponent: demoDecode }),\n        { aa: 'aa', bb: 'bb', cc: 'cc' }\n      );\n      check(\n        qs.parse('a=a&b=b&c=c', null, '==', {\n          decodeURIComponent: (str) => str,\n        }),\n        { 'a=a': '', 'b=b': '', 'c=c': '' }\n      );\n    }\n\n    // Test QueryString.unescape\n    {\n      function errDecode(str) {\n        throw new Error('To jump to the catch scope');\n      }\n\n      check(qs.parse('a=a', null, null, { decodeURIComponent: errDecode }), {\n        a: 'a',\n      });\n    }\n\n    // Test custom encode\n    {\n      function demoEncode(str) {\n        return str[0];\n      }\n\n      const obj = { aa: 'aa', bb: 'bb', cc: 'cc' };\n      assert.strictEqual(\n        qs.stringify(obj, null, null, { encodeURIComponent: demoEncode }),\n        'a=a&b=b&c=c'\n      );\n    }\n\n    // Test custom encode for different types\n    {\n      const obj = {\n        number: 1,\n        bigint: 2n,\n        true: true,\n        false: false,\n        object: {},\n      };\n      assert.strictEqual(\n        qs.stringify(obj, null, null, { encodeURIComponent: (v) => v }),\n        'number=1&bigint=2&true=true&false=false&object='\n      );\n    }\n\n    // Test QueryString.unescapeBuffer\n    qsUnescapeTestCases.forEach((testCase) => {\n      assert.strictEqual(qs.unescape(testCase[0]), testCase[1]);\n      assert.strictEqual(\n        qs.unescapeBuffer(testCase[0]).toString(),\n        testCase[1]\n      );\n    });\n\n    // Test separator and \"equals\" parsing order\n    check(qs.parse('foo&bar', '&', '&'), { foo: '', bar: '' });\n  },\n};\n\n// Ref: https://github.com/nodejs/node/blob/4d6d7d644be4f10f90e5c9c66563736112fffbff/test/parallel/test-querystring-escape.js\nexport const testQueryStringEscape = {\n  async test() {\n    assert.strictEqual(qs.escape(5), '5');\n    assert.strictEqual(qs.escape('test'), 'test');\n    assert.strictEqual(qs.escape({}), '%5Bobject%20Object%5D');\n    assert.strictEqual(qs.escape([5, 10]), '5%2C10');\n    assert.strictEqual(qs.escape('Ŋōđĕ'), '%C5%8A%C5%8D%C4%91%C4%95');\n    assert.strictEqual(qs.escape('testŊōđĕ'), 'test%C5%8A%C5%8D%C4%91%C4%95');\n    assert.strictEqual(\n      qs.escape(`${String.fromCharCode(0xd800 + 1)}test`),\n      '%F0%90%91%B4est'\n    );\n\n    assert.throws(() => qs.escape(String.fromCharCode(0xd800 + 1)), {\n      code: 'ERR_INVALID_URI',\n      name: 'URIError',\n      message: 'URI malformed',\n    });\n\n    // Using toString for objects\n    assert.strictEqual(\n      qs.escape({ test: 5, toString: () => 'test', valueOf: () => 10 }),\n      'test'\n    );\n\n    // `toString` is not callable, must throw an error.\n    // Error message will vary between different JavaScript engines, so only check\n    // that it is a `TypeError`.\n    assert.throws(() => qs.escape({ toString: 5 }), TypeError);\n\n    // Should use valueOf instead of non-callable toString.\n    assert.strictEqual(\n      qs.escape({ toString: 5, valueOf: () => 'test' }),\n      'test'\n    );\n\n    // Error message will vary between different JavaScript engines, so only check\n    // that it is a `TypeError`.\n    assert.throws(() => qs.escape(Symbol('test')), TypeError);\n  },\n};\n\n// Ref: https://github.com/nodejs/node/blob/4d6d7d644be4f10f90e5c9c66563736112fffbff/test/parallel/test-querystring-maxKeys-non-finite.js\nexport const maxKeysNonFinite = {\n  async test() {\n    // Taken from express-js/body-parser\n    // https://github.com/expressjs/body-parser/blob/ed25264fb494cf0c8bc992b8257092cd4f694d5e/test/urlencoded.js#L636-L651\n    function createManyParams(count) {\n      let str = '';\n\n      if (count === 0) {\n        return str;\n      }\n\n      str += '0=0';\n\n      for (let i = 1; i < count; i++) {\n        const n = i.toString(36);\n        str += `&${n}=${n}`;\n      }\n\n      return str;\n    }\n\n    const count = 10000;\n    const originalMaxLength = 1000;\n    const params = createManyParams(count);\n\n    // thealphanerd\n    // 27def4f introduced a change to parse that would cause Infinity\n    // to be passed to String.prototype.split as an argument for limit\n    // In this instance split will always return an empty array\n    // this test confirms that the output of parse is the expected length\n    // when passed Infinity as the argument for maxKeys\n    const resultInfinity = qs.parse(params, undefined, undefined, {\n      maxKeys: Infinity,\n    });\n    const resultNaN = qs.parse(params, undefined, undefined, {\n      maxKeys: NaN,\n    });\n    const resultInfinityString = qs.parse(params, undefined, undefined, {\n      maxKeys: 'Infinity',\n    });\n    const resultNaNString = qs.parse(params, undefined, undefined, {\n      maxKeys: 'NaN',\n    });\n\n    // Non Finite maxKeys should return the length of input\n    assert.strictEqual(Object.keys(resultInfinity).length, count);\n    assert.strictEqual(Object.keys(resultNaN).length, count);\n    // Strings maxKeys should return the maxLength\n    // defined by parses internals\n    assert.strictEqual(\n      Object.keys(resultInfinityString).length,\n      originalMaxLength\n    );\n    assert.strictEqual(Object.keys(resultNaNString).length, originalMaxLength);\n  },\n};\n\n// Ref: https://github.com/nodejs/node/blob/4d6d7d644be4f10f90e5c9c66563736112fffbff/test/parallel/test-querystring-multichar-separator.js\nexport const multiCharSeparator = {\n  async test() {\n    function check(actual, expected) {\n      assert(!(actual instanceof Object));\n      assert.deepStrictEqual(\n        Object.keys(actual).sort(),\n        Object.keys(expected).sort()\n      );\n      Object.keys(expected).forEach(function (key) {\n        assert.deepStrictEqual(actual[key], expected[key]);\n      });\n    }\n\n    check(qs.parse('foo=>bar&&bar=>baz', '&&', '=>'), {\n      foo: 'bar',\n      bar: 'baz',\n    });\n\n    check(\n      qs.stringify({ foo: 'bar', bar: 'baz' }, '&&', '=>'),\n      'foo=>bar&&bar=>baz'\n    );\n\n    check(qs.parse('foo==>bar, bar==>baz', ', ', '==>'), {\n      foo: 'bar',\n      bar: 'baz',\n    });\n\n    check(\n      qs.stringify({ foo: 'bar', bar: 'baz' }, ', ', '==>'),\n      'foo==>bar, bar==>baz'\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/querystring-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"nodejs-querystring-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"querystring-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/readline-nodejs-test.js",
    "content": "import readline from 'node:readline';\nimport assert from 'node:assert';\nimport { EventEmitter } from 'node:events';\nimport promises from 'node:readline/promises';\n\nexport const readlineClearLine = {\n  test() {\n    assert.strictEqual(readline.clearLine(), false);\n    assert.strictEqual(typeof readline.clearLine, 'function');\n  },\n};\n\nexport const readlineClearScreenDown = {\n  test() {\n    assert.strictEqual(readline.clearScreenDown(), false);\n    assert.strictEqual(typeof readline.clearScreenDown, 'function');\n  },\n};\n\nexport const readlineCursorTo = {\n  test() {\n    assert.strictEqual(readline.cursorTo(), false);\n    assert.strictEqual(typeof readline.cursorTo, 'function');\n  },\n};\n\nexport const readlineMoveCursor = {\n  test() {\n    assert.strictEqual(readline.moveCursor(), false);\n    assert.strictEqual(typeof readline.moveCursor, 'function');\n  },\n};\n\nexport const readlineEmitKeypressEvents = {\n  test() {\n    assert.doesNotThrow(() => readline.emitKeypressEvents());\n    assert.strictEqual(readline.emitKeypressEvents(), undefined);\n    assert.strictEqual(typeof readline.emitKeypressEvents, 'function');\n  },\n};\n\nexport const readlineCreateInterface = {\n  test() {\n    // createInterface returns a no-op stub that matches unenv behavior\n    const rl = readline.createInterface();\n    assert.ok(rl instanceof readline.Interface);\n    assert.strictEqual(rl.terminal, false);\n    assert.strictEqual(rl.line, '');\n    assert.strictEqual(rl.cursor, 0);\n    assert.strictEqual(typeof readline.createInterface, 'function');\n  },\n};\n\nexport const readlineInterface = {\n  test() {\n    // Interface constructor returns a no-op stub that matches unenv behavior\n    const rl = new readline.Interface();\n    assert.ok(rl instanceof readline.Interface);\n    assert.strictEqual(rl.terminal, false);\n    assert.strictEqual(rl.line, '');\n    assert.strictEqual(rl.cursor, 0);\n\n    // Test methods return sensible defaults instead of throwing\n    assert.strictEqual(rl.getPrompt(), '');\n    rl.setPrompt('test'); // Should not throw\n    rl.prompt(); // Should not throw\n    rl.close(); // Should not throw\n    rl.write('test'); // Should not throw\n    assert.deepStrictEqual(rl.getCursorPos(), { rows: 0, cols: 0 });\n    assert.strictEqual(rl.pause(), rl);\n    assert.strictEqual(rl.resume(), rl);\n\n    // question calls callback with empty string\n    let questionAnswer = null;\n    rl.question('test?', (answer) => {\n      questionAnswer = answer;\n    });\n    assert.strictEqual(questionAnswer, '');\n\n    // question with options also works\n    let questionAnswer2 = null;\n    rl.question('test?', {}, (answer) => {\n      questionAnswer2 = answer;\n    });\n    assert.strictEqual(questionAnswer2, '');\n\n    assert.strictEqual(typeof readline.Interface, 'function');\n  },\n};\n\nexport const readlineInterfaceMethods = {\n  test() {\n    const InterfaceProto = readline.Interface.prototype;\n\n    assert.strictEqual(typeof InterfaceProto.getPrompt, 'function');\n    assert.strictEqual(typeof InterfaceProto.setPrompt, 'function');\n    assert.strictEqual(typeof InterfaceProto.prompt, 'function');\n    assert.strictEqual(typeof InterfaceProto.question, 'function');\n    assert.strictEqual(typeof InterfaceProto.pause, 'function');\n    assert.strictEqual(typeof InterfaceProto.resume, 'function');\n    assert.strictEqual(typeof InterfaceProto.close, 'function');\n    assert.strictEqual(typeof InterfaceProto.write, 'function');\n    assert.strictEqual(typeof InterfaceProto.getCursorPos, 'function');\n\n    assert.strictEqual(typeof InterfaceProto.on, 'function');\n    assert.strictEqual(typeof InterfaceProto.emit, 'function');\n    assert.strictEqual(typeof InterfaceProto.once, 'function');\n    assert.strictEqual(typeof InterfaceProto.removeListener, 'function');\n  },\n};\n\nexport const readlineInterfaceInheritance = {\n  test() {\n    assert.strictEqual(\n      readline.Interface.prototype instanceof EventEmitter,\n      true\n    );\n  },\n};\n\nexport const readlinePromises = {\n  test() {\n    assert.strictEqual(typeof readline.promises, 'object');\n    assert.strictEqual(typeof readline.promises.Interface, 'function');\n    assert.strictEqual(typeof readline.promises.Readline, 'function');\n    assert.strictEqual(typeof readline.promises.createInterface, 'function');\n  },\n};\n\nexport const readlinePromisesInterface = {\n  async test() {\n    // promises.Interface constructor returns a no-op stub that matches unenv behavior\n    const rl = new readline.promises.Interface();\n    assert.ok(rl instanceof readline.promises.Interface);\n    assert.strictEqual(rl.terminal, false);\n    assert.strictEqual(rl.line, '');\n    assert.strictEqual(rl.cursor, 0);\n\n    // Test methods return sensible defaults instead of throwing\n    assert.strictEqual(rl.getPrompt(), '');\n    rl.setPrompt('test'); // Should not throw\n    rl.prompt(); // Should not throw\n    rl.close(); // Should not throw\n    rl.write('test'); // Should not throw\n    assert.deepStrictEqual(rl.getCursorPos(), { rows: 0, cols: 0 });\n    assert.strictEqual(rl.pause(), rl);\n    assert.strictEqual(rl.resume(), rl);\n\n    // question returns a promise that resolves to empty string\n    const answer = await rl.question('test?');\n    assert.strictEqual(answer, '');\n  },\n};\n\nexport const readlinePromisesCreateInterface = {\n  test() {\n    // promises.createInterface returns a no-op stub that matches unenv behavior\n    const rl = readline.promises.createInterface();\n    assert.ok(rl instanceof readline.promises.Interface);\n    assert.strictEqual(rl.terminal, false);\n    assert.strictEqual(rl.line, '');\n    assert.strictEqual(rl.cursor, 0);\n  },\n};\n\nexport const readlinePromisesReadline = {\n  async test() {\n    // Readline constructor returns a no-op stub that matches unenv behavior\n    const rl = new readline.promises.Readline(process.stdout);\n    assert.ok(rl instanceof readline.promises.Readline);\n\n    // Test methods return sensible defaults instead of throwing\n    assert.strictEqual(rl.clearLine(0), rl);\n    assert.strictEqual(rl.clearScreenDown(), rl);\n    assert.strictEqual(rl.cursorTo(0), rl);\n    assert.strictEqual(rl.cursorTo(0, 0), rl);\n    assert.strictEqual(rl.moveCursor(1, 1), rl);\n    assert.strictEqual(rl.rollback(), rl);\n\n    // commit returns a promise that resolves to undefined\n    const result = await rl.commit();\n    assert.strictEqual(result, undefined);\n  },\n};\n\nexport const readlineDefaultExport = {\n  test() {\n    assert.strictEqual(typeof readline, 'object');\n    assert.strictEqual(typeof readline.clearLine, 'function');\n    assert.strictEqual(typeof readline.clearScreenDown, 'function');\n    assert.strictEqual(typeof readline.createInterface, 'function');\n    assert.strictEqual(typeof readline.cursorTo, 'function');\n    assert.strictEqual(typeof readline.emitKeypressEvents, 'function');\n    assert.strictEqual(typeof readline.moveCursor, 'function');\n    assert.strictEqual(typeof readline.Interface, 'function');\n    assert.strictEqual(typeof readline.promises, 'object');\n  },\n};\n\nexport const readlineModuleExports = {\n  async test() {\n    const readlineModule = await import('node:readline');\n\n    assert.strictEqual(typeof readlineModule.default, 'object');\n    assert.strictEqual(typeof readlineModule.clearLine, 'function');\n    assert.strictEqual(typeof readlineModule.clearScreenDown, 'function');\n    assert.strictEqual(typeof readlineModule.createInterface, 'function');\n    assert.strictEqual(typeof readlineModule.cursorTo, 'function');\n    assert.strictEqual(typeof readlineModule.emitKeypressEvents, 'function');\n    assert.strictEqual(typeof readlineModule.moveCursor, 'function');\n    assert.strictEqual(typeof readlineModule.Interface, 'function');\n    assert.strictEqual(typeof readlineModule.promises, 'object');\n\n    assert.strictEqual(\n      readlineModule.default.clearLine,\n      readlineModule.clearLine\n    );\n    assert.strictEqual(\n      readlineModule.default.clearScreenDown,\n      readlineModule.clearScreenDown\n    );\n    assert.strictEqual(\n      readlineModule.default.createInterface,\n      readlineModule.createInterface\n    );\n    assert.strictEqual(\n      readlineModule.default.cursorTo,\n      readlineModule.cursorTo\n    );\n    assert.strictEqual(\n      readlineModule.default.emitKeypressEvents,\n      readlineModule.emitKeypressEvents\n    );\n    assert.strictEqual(\n      readlineModule.default.moveCursor,\n      readlineModule.moveCursor\n    );\n    assert.strictEqual(\n      readlineModule.default.Interface,\n      readlineModule.Interface\n    );\n    assert.strictEqual(\n      readlineModule.default.promises,\n      readlineModule.promises\n    );\n  },\n};\n\nexport const readlineFunctionReturnValues = {\n  test() {\n    const testArgs = [\n      [],\n      [null],\n      [undefined],\n      [1, 2],\n      ['test', 123],\n      [{}, [], () => {}],\n    ];\n\n    for (const args of testArgs) {\n      assert.strictEqual(readline.clearLine(...args), false);\n      assert.strictEqual(readline.clearScreenDown(...args), false);\n      assert.strictEqual(readline.cursorTo(...args), false);\n      assert.strictEqual(readline.moveCursor(...args), false);\n      assert.strictEqual(readline.emitKeypressEvents(...args), undefined);\n    }\n  },\n};\n\nexport const readlineImportedPromisesModule = {\n  test() {\n    assert.strictEqual(typeof promises, 'object');\n    assert.strictEqual(typeof promises.createInterface, 'function');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/readline-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"readline-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"readline-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_readline_module\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/repl-nodejs-test.js",
    "content": "import repl from 'node:repl';\nimport assert from 'node:assert';\nimport { EventEmitter } from 'node:events';\n\nexport const replWriter = {\n  test() {\n    assert.throws(() => repl.writer(), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      message: /writer/,\n    });\n    assert.strictEqual(typeof repl.writer, 'function');\n  },\n};\n\nexport const replStart = {\n  test() {\n    assert.throws(() => repl.start(), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      message: /start/,\n    });\n    assert.strictEqual(typeof repl.start, 'function');\n  },\n};\n\nexport const replRecoverable = {\n  test() {\n    const error = new Error('test error');\n    const recoverable = new repl.Recoverable(error);\n\n    assert.strictEqual(recoverable instanceof SyntaxError, true);\n    assert.strictEqual(recoverable instanceof repl.Recoverable, true);\n    assert.strictEqual(recoverable.err, error);\n    assert.strictEqual(recoverable.err.message, 'test error');\n\n    const anotherError = new TypeError('type error');\n    const anotherRecoverable = new repl.Recoverable(anotherError);\n    assert.strictEqual(anotherRecoverable.err, anotherError);\n  },\n};\n\nexport const replREPLServer = {\n  test() {\n    assert.throws(() => new repl.REPLServer(), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      message: /REPLServer/,\n    });\n    assert.strictEqual(typeof repl.REPLServer, 'function');\n  },\n};\n\nexport const replREPLServerMethods = {\n  test() {\n    const REPLServerProto = repl.REPLServer.prototype;\n\n    assert.strictEqual(typeof REPLServerProto.setupHistory, 'function');\n    assert.strictEqual(typeof REPLServerProto.defineCommand, 'function');\n    assert.strictEqual(typeof REPLServerProto.displayPrompt, 'function');\n    assert.strictEqual(typeof REPLServerProto.clearBufferedCommand, 'function');\n\n    assert.strictEqual(typeof REPLServerProto.on, 'function');\n    assert.strictEqual(typeof REPLServerProto.emit, 'function');\n    assert.strictEqual(typeof REPLServerProto.once, 'function');\n    assert.strictEqual(typeof REPLServerProto.removeListener, 'function');\n  },\n};\n\nexport const replREPLServerInheritance = {\n  test() {\n    assert.strictEqual(repl.REPLServer.prototype instanceof EventEmitter, true);\n  },\n};\n\nexport const replModes = {\n  test() {\n    assert.strictEqual(typeof repl.REPL_MODE_SLOPPY, 'symbol');\n    assert.strictEqual(typeof repl.REPL_MODE_STRICT, 'symbol');\n    assert.notStrictEqual(repl.REPL_MODE_SLOPPY, repl.REPL_MODE_STRICT);\n\n    assert.strictEqual(repl.REPL_MODE_SLOPPY.toString(), 'Symbol(repl-sloppy)');\n    assert.strictEqual(repl.REPL_MODE_STRICT.toString(), 'Symbol(repl-strict)');\n  },\n};\n\nexport const replBuiltinModules = {\n  test() {\n    assert.strictEqual(Array.isArray(repl.builtinModules), true);\n    assert.strictEqual(repl.builtinModules.length > 0, true);\n\n    assert.strictEqual(repl.builtinModules.includes('fs'), true);\n    assert.strictEqual(repl.builtinModules.includes('path'), true);\n    assert.strictEqual(repl.builtinModules.includes('http'), true);\n\n    for (const module of repl.builtinModules) {\n      assert.strictEqual(typeof module, 'string');\n      assert.strictEqual(module[0] !== '_', true);\n    }\n  },\n};\n\nexport const replBuiltinLibs = {\n  test() {\n    assert.strictEqual(Array.isArray(repl._builtinLibs), true);\n    assert.deepStrictEqual(repl._builtinLibs, repl.builtinModules);\n    assert.strictEqual(repl._builtinLibs, repl.builtinModules);\n  },\n};\n\nexport const replDefaultExport = {\n  test() {\n    assert.strictEqual(typeof repl, 'object');\n    assert.strictEqual(typeof repl.writer, 'function');\n    assert.strictEqual(typeof repl.start, 'function');\n    assert.strictEqual(typeof repl.Recoverable, 'function');\n    assert.strictEqual(typeof repl.REPLServer, 'function');\n    assert.strictEqual(Array.isArray(repl.builtinModules), true);\n    assert.strictEqual(Array.isArray(repl._builtinLibs), true);\n    assert.strictEqual(typeof repl.REPL_MODE_SLOPPY, 'symbol');\n    assert.strictEqual(typeof repl.REPL_MODE_STRICT, 'symbol');\n  },\n};\n\nexport const replModuleExports = {\n  async test() {\n    const replModule = await import('node:repl');\n\n    assert.strictEqual(typeof replModule.default, 'object');\n    assert.strictEqual(typeof replModule.writer, 'function');\n    assert.strictEqual(typeof replModule.start, 'function');\n    assert.strictEqual(typeof replModule.Recoverable, 'function');\n    assert.strictEqual(typeof replModule.REPLServer, 'function');\n    assert.strictEqual(Array.isArray(replModule.builtinModules), true);\n    assert.strictEqual(Array.isArray(replModule._builtinLibs), true);\n    assert.strictEqual(typeof replModule.REPL_MODE_SLOPPY, 'symbol');\n    assert.strictEqual(typeof replModule.REPL_MODE_STRICT, 'symbol');\n\n    assert.strictEqual(replModule.default.writer, replModule.writer);\n    assert.strictEqual(replModule.default.start, replModule.start);\n    assert.strictEqual(replModule.default.Recoverable, replModule.Recoverable);\n    assert.strictEqual(replModule.default.REPLServer, replModule.REPLServer);\n    assert.strictEqual(\n      replModule.default.builtinModules,\n      replModule.builtinModules\n    );\n    assert.strictEqual(\n      replModule.default._builtinLibs,\n      replModule._builtinLibs\n    );\n    assert.strictEqual(\n      replModule.default.REPL_MODE_SLOPPY,\n      replModule.REPL_MODE_SLOPPY\n    );\n    assert.strictEqual(\n      replModule.default.REPL_MODE_STRICT,\n      replModule.REPL_MODE_STRICT\n    );\n  },\n};\n\nexport const replRecoverableError = {\n  test() {\n    const errors = [\n      new Error('standard error'),\n      new TypeError('type error'),\n\n      new RangeError('range error'),\n      new SyntaxError('syntax error'),\n    ];\n\n    for (const error of errors) {\n      const recoverable = new repl.Recoverable(error);\n      assert.strictEqual(recoverable.err, error);\n      assert.strictEqual(recoverable instanceof SyntaxError, true);\n      assert.strictEqual(recoverable instanceof Error, true);\n    }\n  },\n};\n\nexport const replRecoverableConstructor = {\n  test() {\n    assert.strictEqual(typeof repl.Recoverable, 'function');\n    assert.strictEqual(repl.Recoverable.name, 'Recoverable');\n\n    const recoverable = new repl.Recoverable(new Error());\n    assert.strictEqual(recoverable.constructor, repl.Recoverable);\n    assert.strictEqual(recoverable.constructor.name, 'Recoverable');\n  },\n};\n\nexport const replBuiltinModulesFiltered = {\n  test() {\n    for (const module of repl.builtinModules) {\n      assert.strictEqual(module.startsWith('_'), false);\n    }\n\n    for (const module of repl._builtinLibs) {\n      assert.strictEqual(module.startsWith('_'), false);\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/repl-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"repl-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"repl-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_repl_module\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/sidecar-supervisor.mjs",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n/*\n * Sidecars test framework\n * ------------------------\n *\n *  A `wd_test` may specify a `sidecar` to run alongside it. The sidecar is an auxiliary server\n *  process that runs alongside the test to provide realistic network endpoints.\n *\n * The sidecar and test processes are run together by the `sidecar_supervisor` (this file).\n * The supervisor is responsible for assigning a random IP address for the sidecar and test to\n * communicate (stored in the environment variable `SIDECAR_HOSTNAME`), as well as a set of random\n * port numbers. Each environment variable specified in `sidecar_port_bindings` will be filled in\n * with a random port number.\n *\n * This architecture is designed to allow running unmodified TCP servers, such as wptserve for the\n * WPT tests. If necessary, IP address randomization can be disabled by setting\n * `sidecar_randomize_ip` to False.\n *\n *\n * ┌───────────────────────────────────────────────────────┐\n * │                                                       │\n * │                       bazel test                      │\n * │                                                       │\n * └───────────────────────────┬───────────────────────────┘\n *                             │\n *                 env vars: PORTS_TO_ASSIGN\n *                             ▼\n * ┌───────────────────────────────────────────────────────┐\n * │                                                       │\n * │                   sidecar supervisor                  ├────────────────────────────────────────────────────────────┐\n * │                                                       │                                                            │\n * └───────────────────────────┬───────────────────────────┘                                                            │\n *                             │                                                                  env vars: SIDECAR_HOSTNAME, SERVER_PORT, ...\n *       env vars: SIDECAR_HOSTNAME, SERVER_PORT, ...                                                                   │\n *                             ▼                                                                                        ▼\n * ┌───────────────────────────────────────────────────────┐                                    ┌──────────────────────────────────────────────┐\n * │                                                       │                                    │                                              │\n * │                        wd-test                        │                                    │                   sidecar                    │\n * │                                                       │                                    │                                              │\n * └───────────────────────────┬───────────────────────────┘                                    └──────────────────────────────────────────────┘\n *                             │                                                                                        ▲\n *    env var bindings: SIDECAR_HOSTNAME, SERVER_PORT, ...                                                              │\n *                             ▼                                                                                        │\n * ┌───────────────────────────────────────────────────────┐                                                            │\n * │                                                       │                                                            │\n * │                          test                         ├────────────tcp:─SIDECAR_HOSTNAME,─SERVER_PORT──────────────┘\n * │                                                       │\n * └───────────────────────────────────────────────────────┘\n */\n\nimport net from 'node:net';\nimport child_process from 'node:child_process';\nimport crypto from 'node:crypto';\n\nconst ANY_PORT = 0;\nconst CONNECT_POLL_INTERVAL_MS = 500;\n\nfunction getListeningServer(hostname) {\n  const { promise, resolve } = Promise.withResolvers();\n  const server = net.createServer();\n  server.listen(ANY_PORT, hostname).once('listening', () => resolve(server));\n  return promise;\n}\n\nfunction closeServer(server) {\n  const { promise, resolve } = Promise.withResolvers();\n  server.close(resolve);\n  return promise;\n}\n\nasync function reservePorts(hostname, envVarNames) {\n  const servers = await Promise.all(\n    envVarNames.map((_) => getListeningServer(hostname))\n  );\n  const ports = Object.fromEntries(\n    envVarNames.map((envVar, i) => [envVar, servers[i].address().port])\n  );\n  Object.assign(process.env, ports);\n\n  // TODO(soon): We need to close the ports we found so sidecarCommand can bind to them.\n  // During this time, another unrelated process could end up taking the ports we're using.\n  // SO_REUSEPORT is safer but the sidecarCommand must know to use it.\n  await Promise.all(servers.map(closeServer));\n\n  return ports;\n}\n\nfunction waitForListening(port, hostname) {\n  const { promise, resolve, reject } = Promise.withResolvers();\n  const interval = setInterval(() => {\n    const conn = net\n      .connect(port, hostname, () => {\n        conn.destroy();\n        clearInterval(interval);\n        resolve();\n      })\n      .once('error', (err) => console.log('waiting for sidecar...', err.code));\n  }, CONNECT_POLL_INTERVAL_MS);\n  return promise;\n}\n\nfunction runSidecar(cmd) {\n  const { promise, resolve } = Promise.withResolvers();\n  const proc = child_process.spawn(cmd, {\n    shell: true,\n    stdio: ['inherit', 'inherit', 'inherit'],\n  });\n  proc.once('exit', resolve);\n  return { promise, proc };\n}\n\nfunction getRandomLoopbackAddress() {\n  // Pick a random address in 127.0.0.0/8.\n  // Range is chosen not to use network address, gateway address, or broadcast address.\n\n  // TODO: There is still a faint possibility of collision. We could use OS-specific APIs to see\n  // if a potential address is in use.\n  return `127.${crypto.randomInt(2, 255)}.${crypto.randomInt(2, 255)}.${crypto.randomInt(2, 255)}`;\n}\n\nfunction canUseRandomAddress() {\n  if (process.env.RANDOMIZE_IP === 'false') {\n    // Test explicitly disabled randomization\n    return false;\n  }\n\n  switch (process.platform) {\n    case 'linux':\n      return true;\n\n    case 'win32':\n      return true;\n\n    case 'darwin':\n      // Address randomization works on macOS but requires sudo, so we'll only\n      // use it in CI for dev convenience.\n      return process.env.CI === 'true';\n\n    default:\n      throw new Error(\n        `Address randomization strategy not known for ${process.platform}`\n      );\n  }\n}\n\nfunction assignLoopbackAddress() {\n  if (!canUseRandomAddress()) {\n    return '127.0.0.1';\n  }\n\n  const randomAddress = getRandomLoopbackAddress();\n  if (process.platform === 'darwin') {\n    // On macOS, we need to explicitly assign this IP to the loopback interface\n    child_process.spawnSync(\n      'sudo',\n      ['/sbin/ifconfig', 'lo0', 'alias', randomAddress],\n      { stdio: 'inherit' }\n    );\n  }\n\n  return randomAddress;\n}\n\nconst portsToAssign = process.env.PORTS_TO_ASSIGN.split(',');\nconst sidecarCommand = process.env.SIDECAR_COMMAND;\nconst testArgv = process.argv.slice(2); // Shift off the stuff Node puts in argv\n\nconst hostname = assignLoopbackAddress();\nprocess.env.SIDECAR_HOSTNAME = hostname;\n\nconst ports = await reservePorts(hostname, portsToAssign);\nconst sidecar = runSidecar(sidecarCommand);\nawait Promise.all(\n  Object.values(ports).map((port) => waitForListening(port, hostname))\n);\n\nprocess.exitCode = child_process.spawnSync(testArgv[0], testArgv.slice(1), {\n  stdio: ['inherit', 'inherit', 'inherit'],\n}).status;\n\nsidecar.proc.kill();\nawait sidecar.promise;\n"
  },
  {
    "path": "src/workerd/api/node/tests/sqlite-nodejs-test.js",
    "content": "import sqlite from 'node:sqlite';\nimport { deepStrictEqual, notStrictEqual, throws } from 'node:assert';\n\nexport const testExports = {\n  async test() {\n    notStrictEqual(sqlite.DatabaseSync, undefined);\n    notStrictEqual(sqlite.StatementSync, undefined);\n    notStrictEqual(sqlite.backup, undefined);\n    notStrictEqual(sqlite.constants, undefined);\n\n    deepStrictEqual(sqlite.constants, {\n      SQLITE_CHANGESET_OMIT: 0,\n      SQLITE_CHANGESET_REPLACE: 1,\n      SQLITE_CHANGESET_ABORT: 2,\n      SQLITE_CHANGESET_DATA: 1,\n      SQLITE_CHANGESET_NOTFOUND: 2,\n      SQLITE_CHANGESET_CONFLICT: 3,\n      SQLITE_CHANGESET_CONSTRAINT: 4,\n      SQLITE_CHANGESET_FOREIGN_KEY: 5,\n    });\n\n    throws(() => sqlite.backup(), {\n      message: /is not implemented/,\n    });\n\n    throws(() => new sqlite.DatabaseSync(), {\n      message: /Illegal constructor/,\n    });\n\n    throws(() => new sqlite.StatementSync(), {\n      message: /Illegal constructor/,\n    });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/sqlite-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"sqlite-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"sqlite-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_sqlite_module\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/streams-nodejs-test.js",
    "content": "import { Duplex, Readable, Writable } from 'node:stream';\nimport { strictEqual, deepStrictEqual, ok } from 'node:assert';\nimport { mock } from 'node:test';\n\nexport const testStreamDuplexDestroy = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n    const duplex = new Duplex({\n      read() {},\n      write(chunk, enc, cb) {\n        cb();\n      },\n    });\n\n    duplex.cork();\n    duplex.write('foo', (err) => {\n      strictEqual(err.code, 'ERR_STREAM_DESTROYED');\n      resolve();\n    });\n    duplex.destroy();\n    await promise;\n  },\n};\n\n// Prevents stream unexpected pause when highWaterMark set to 0\n// Ref: https://github.com/nodejs/node/commit/50695e5de14ccd8255537972181bbc9a1f44368e\nexport const testStreamsHighwatermark = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n    const res = [];\n    const r = new Readable({\n      read() {},\n    });\n    const w = new Writable({\n      highWaterMark: 0,\n      write(chunk, encoding, callback) {\n        res.push(chunk.toString());\n        callback();\n      },\n    });\n\n    r.pipe(w);\n    r.push('a');\n    r.push('b');\n    r.push('c');\n    r.push(null);\n\n    r.on('end', () => {\n      deepStrictEqual(res, ['a', 'b', 'c']);\n      resolve();\n    });\n    await promise;\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/9cc019575961ad9fcc18883993c3f9056699908d/test/parallel/test-stream-readable-data.js\n\nexport const testStreamReadableData = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n    const readable = new Readable({\n      read() {},\n    });\n\n    const onReadableFn = mock.fn();\n    readable.setEncoding('utf8');\n    readable.on('readable', onReadableFn);\n    readable.removeListener('readable', onReadableFn);\n    readable.on('end', resolve);\n\n    const onDataFn = mock.fn();\n    queueMicrotask(function () {\n      readable.on('data', onDataFn);\n      readable.push('hello');\n\n      queueMicrotask(() => {\n        readable.push(null);\n      });\n    });\n\n    await promise;\n    strictEqual(onDataFn.mock.callCount(), 1);\n    strictEqual(onReadableFn.mock.callCount(), 0);\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/9cc019575961ad9fcc18883993c3f9056699908d/test/parallel/test-stream-readable-ended.js\nexport const testStreamReadableEnded = {\n  async test() {\n    // basic\n    {\n      // Find it on Readable.prototype\n      ok(Object.hasOwn(Readable.prototype, 'readableEnded'));\n    }\n\n    // event\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      const readable = new Readable();\n\n      readable._read = () => {\n        // The state ended should start in false.\n        strictEqual(readable.readableEnded, false);\n        readable.push('asd');\n        strictEqual(readable.readableEnded, false);\n        readable.push(null);\n        strictEqual(readable.readableEnded, false);\n      };\n\n      const onDataFn = mock.fn(() => {\n        strictEqual(readable.readableEnded, false);\n      });\n\n      readable.on('end', () => {\n        strictEqual(readable.readableEnded, true);\n        strictEqual(onDataFn.mock.callCount(), 1);\n        resolve();\n      });\n\n      readable.on('data', onDataFn);\n\n      await promise;\n    }\n\n    // Verifies no `error` triggered on multiple .push(null) invocations\n    {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      const readable = new Readable();\n\n      readable.on('readable', () => {\n        readable.read();\n      });\n      readable.on('error', reject);\n      readable.on('end', resolve);\n\n      readable.push('a');\n      readable.push(null);\n      readable.push(null);\n\n      await promise;\n    }\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/9cc019575961ad9fcc18883993c3f9056699908d/test/parallel/test-stream-readable-hwm-0.js\nexport const testStreamReadableHwm0 = {\n  async test() {\n    // This test ensures that Readable stream will call _read() for streams\n    // with highWaterMark === 0 upon .read(0) instead of just trying to\n    // emit 'readable' event.\n    const { promise, resolve } = Promise.withResolvers();\n    const onReadFn = mock.fn();\n    const r = new Readable({\n      // Must be called only once upon setting 'readable' listener\n      read: onReadFn,\n      highWaterMark: 0,\n    });\n\n    let pushedNull = false;\n    // This will trigger read(0) but must only be called after push(null)\n    // because the we haven't pushed any data\n    const onReadableFn = mock.fn(() => {\n      strictEqual(r.read(), null);\n      strictEqual(pushedNull, true);\n    });\n    r.on('readable', onReadableFn);\n    r.on('end', resolve);\n    queueMicrotask(() => {\n      strictEqual(r.read(), null);\n      pushedNull = true;\n      r.push(null);\n    });\n\n    await promise;\n    strictEqual(onReadFn.mock.callCount(), 1);\n    strictEqual(onReadableFn.mock.callCount(), 1);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/streams-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"streams-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"streams-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\"],\n      )\n    ),\n    ( name = \"internet\", network = ( allow = [\"private\"] ) ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/streams-test.js",
    "content": "import {\n  deepStrictEqual,\n  fail,\n  ifError,\n  notStrictEqual,\n  ok,\n  rejects,\n  strictEqual,\n  throws,\n} from 'node:assert';\n\nimport { Buffer } from 'node:buffer';\n\nimport {\n  Readable,\n  Writable,\n  Duplex,\n  Stream,\n  Transform,\n  PassThrough,\n  addAbortSignal,\n  destroy,\n  finished,\n  isDisturbed,\n  isErrored,\n  pipeline,\n  promises,\n  duplexPair,\n} from 'node:stream';\n\nimport { ByteLengthQueuingStrategy } from 'node:stream/web';\nstrictEqual(ByteLengthQueuingStrategy, globalThis.ByteLengthQueuingStrategy);\n\nimport { EventEmitter } from 'node:events';\n\nfunction countedDeferred(n, action) {\n  let resolve;\n  const promise = new Promise((a) => {\n    resolve = a;\n  });\n  return {\n    promise,\n    resolve(...args) {\n      action(...args);\n      if (--n === 0) resolve();\n    },\n  };\n}\n\nexport const legacyAliases = {\n  async test(ctrl, env, ctx) {\n    strictEqual(Readable, (await import('node:_stream_readable')).Readable);\n    strictEqual(Writable, (await import('node:_stream_writable')).Writable);\n    strictEqual(Duplex, (await import('node:_stream_duplex')).Duplex);\n    strictEqual(Transform, (await import('node:_stream_transform')).Transform);\n    strictEqual(\n      PassThrough,\n      (await import('node:_stream_passthrough')).PassThrough\n    );\n  },\n};\n\nexport const addAbortSignalTest = {\n  test(ctrl, env, ctx) {\n    const ac = new AbortController();\n    addAbortSignal(ac.signal, new Readable());\n\n    {\n      throws(() => {\n        addAbortSignal('INVALID_SIGNAL');\n      }, /ERR_INVALID_ARG_TYPE/);\n\n      throws(() => {\n        addAbortSignal(ac.signal, 'INVALID_STREAM');\n      }, /ERR_INVALID_ARG_TYPE/);\n    }\n  },\n};\n\nexport const autoDestroy = {\n  async test(ctrl, env, ctx) {\n    {\n      const rDestroyed = Promise.withResolvers();\n      const rEnded = Promise.withResolvers();\n      const rClosed = Promise.withResolvers();\n\n      const r = new Readable({\n        autoDestroy: true,\n        read() {\n          this.push('hello');\n          this.push('world');\n          this.push(null);\n        },\n        destroy(err, cb) {\n          rDestroyed.resolve();\n          cb();\n        },\n      });\n\n      let ended = false;\n      r.resume();\n      r.on('end', rEnded.resolve);\n      r.on('close', rClosed.resolve);\n\n      await Promise.all([rDestroyed.promise, rEnded.promise, rClosed.promise]);\n    }\n\n    {\n      const rDestroyed = Promise.withResolvers();\n      const rFinished = Promise.withResolvers();\n      const rClosed = Promise.withResolvers();\n\n      const w = new Writable({\n        autoDestroy: true,\n        write(data, enc, cb) {\n          cb(null);\n        },\n        destroy(err, cb) {\n          rDestroyed.resolve();\n          cb();\n        },\n      });\n\n      w.write('hello');\n      w.write('world');\n      w.end();\n\n      w.on('finish', rFinished.resolve);\n      w.on('close', rClosed.resolve);\n\n      await Promise.all([\n        rDestroyed.promise,\n        rFinished.promise,\n        rClosed.promise,\n      ]);\n    }\n\n    {\n      const rDestroyed = Promise.withResolvers();\n      const rEnded = Promise.withResolvers();\n      const rFinished = Promise.withResolvers();\n      const rClosed = Promise.withResolvers();\n\n      const t = new Transform({\n        autoDestroy: true,\n        transform(data, enc, cb) {\n          cb(null, data);\n        },\n        destroy(err, cb) {\n          rDestroyed.resolve();\n          cb();\n        },\n      });\n\n      t.write('hello');\n      t.write('world');\n      t.end();\n\n      t.resume();\n\n      t.on('end', rEnded.resolve);\n      t.on('finish', rFinished.resolve);\n      t.on('close', rClosed.resolve);\n\n      await Promise.all([\n        rDestroyed.promise,\n        rEnded.promise,\n        rFinished.promise,\n        rClosed.promise,\n      ]);\n    }\n\n    {\n      const rDestroyed = Promise.withResolvers();\n\n      const err = new Error('fail');\n\n      const r = new Readable({\n        read() {\n          r2.emit('error', err);\n        },\n      });\n      const r2 = new Readable({\n        autoDestroy: true,\n        destroy(err, cb) {\n          rDestroyed.resolve(err);\n        },\n      });\n\n      r.pipe(r2);\n\n      const check = await rDestroyed.promise;\n      strictEqual(check, err);\n    }\n\n    {\n      const rDestroyed = Promise.withResolvers();\n      const err = new Error('fail');\n      const r = new Readable({\n        read() {\n          w.emit('error', err);\n        },\n      });\n\n      const w = new Writable({\n        autoDestroy: true,\n        destroy(err, cb) {\n          rDestroyed.resolve(err);\n          cb();\n        },\n      });\n\n      r.pipe(w);\n\n      const check = await rDestroyed.promise;\n      strictEqual(check, err);\n    }\n  },\n};\n\nexport const awaitDrainWritersInSyncRecursionWrite = {\n  async test(ctrl, env, ctx) {\n    const encode = new PassThrough({\n      highWaterMark: 1,\n    });\n\n    const decode = new PassThrough({\n      highWaterMark: 1,\n    });\n\n    const send = countedDeferred(4, (buf) => {\n      encode.write(buf);\n    });\n\n    let i = 0;\n    const onData = () => {\n      if (++i === 2) {\n        send.resolve(Buffer.from([0x3]));\n        send.resolve(Buffer.from([0x4]));\n      }\n    };\n\n    encode.pipe(decode).on('data', onData);\n\n    send.resolve(Buffer.from([0x1]));\n    send.resolve(Buffer.from([0x2]));\n\n    await send.promise;\n  },\n};\n\nexport const backpressure = {\n  async test(ctrl, env, ctx) {\n    let pushes = 0;\n    const total = 65500 + 40 * 1024;\n    let reads = 0;\n    let writes = 0;\n    const rs = new Readable({\n      read() {\n        if (++reads > 11) throw new Error('incorrect number of reads');\n        if (pushes++ === 10) {\n          this.push(null);\n          return;\n        }\n\n        const length = this._readableState.length;\n\n        // We are at most doing two full runs of _reads\n        // before stopping, because Readable is greedy\n        // to keep its buffer full\n        ok(length <= total);\n\n        this.push(Buffer.alloc(65500));\n        for (let i = 0; i < 40; i++) {\n          this.push(Buffer.alloc(1024));\n        }\n\n        // We will be over highWaterMark at this point\n        // but a new call to _read is scheduled anyway.\n      },\n    });\n\n    const closed = Promise.withResolvers();\n\n    const ws = Writable({\n      write(data, enc, cb) {\n        queueMicrotask(cb);\n        if (++writes === 410) closed.resolve();\n      },\n    });\n\n    rs.pipe(ws);\n\n    await closed.promise;\n  },\n};\n\nexport const bigPacket = {\n  async test(ctrl, env, ctx) {\n    const rPassed = Promise.withResolvers();\n\n    class TestStream extends Transform {\n      _transform(chunk, encoding, done) {\n        // Char 'a' only exists in the last write\n        if (chunk.includes('a')) rPassed.resolve();\n        done();\n      }\n    }\n\n    const s1 = new Transform({\n      transform(chunk, encoding, cb) {\n        queueMicrotask(() => cb(null, chunk));\n      },\n    });\n    const s2 = new PassThrough();\n    const s3 = new TestStream();\n    s1.pipe(s3);\n    // Don't let s2 auto close which may close s3\n    s2.pipe(s3, { end: false });\n\n    // We must write a buffer larger than highWaterMark\n    const big = Buffer.alloc(s1.writableHighWaterMark + 1, 'x');\n\n    // Since big is larger than highWaterMark, it will be buffered internally.\n    ok(!s1.write(big));\n    // 'tiny' is small enough to pass through internal buffer.\n    ok(s2.write('tiny'));\n\n    // Write some small data in next IO loop, which will never be written to s3\n    // Because 'drain' event is not emitted from s1 and s1 is still paused\n    queueMicrotask(() => s1.write('later'));\n\n    await rPassed.promise;\n  },\n};\n\nexport const bigPush = {\n  async test(ctrl, env, ctx) {\n    const str = 'asdfasdfasdfasdfasdf';\n\n    const r = new Readable({\n      highWaterMark: 5,\n      encoding: 'utf8',\n    });\n\n    let reads = 0;\n\n    function _read() {\n      if (reads === 0) {\n        setTimeout(() => {\n          r.push(str);\n        }, 1);\n        reads++;\n      } else if (reads === 1) {\n        const ret = r.push(str);\n        strictEqual(ret, false);\n        reads++;\n      } else {\n        r.push(null);\n      }\n    }\n\n    const read = countedDeferred(3, _read);\n    const ended = Promise.withResolvers();\n\n    r._read = read.resolve;\n    r.on('end', ended.resolve);\n\n    // Push some data in to start.\n    // We've never gotten any read event at this point.\n    const ret = r.push(str);\n    // Should be false.  > hwm\n    ok(!ret);\n    let chunk = r.read();\n    strictEqual(chunk, str);\n    chunk = r.read();\n    strictEqual(chunk, null);\n\n    r.once('readable', () => {\n      // This time, we'll get *all* the remaining data, because\n      // it's been added synchronously, as the read WOULD take\n      // us below the hwm, and so it triggered a _read() again,\n      // which synchronously added more, which we then return.\n      chunk = r.read();\n      strictEqual(chunk, str + str);\n\n      chunk = r.read();\n      strictEqual(chunk, null);\n    });\n\n    await Promise.all([read.promise, ended.promise]);\n  },\n};\n\nexport const catchRejections = {\n  async test(ctrl, env, ctx) {\n    {\n      const rErrored = Promise.withResolvers();\n\n      const r = new Readable({\n        captureRejections: true,\n        read() {},\n      });\n      r.push('hello');\n      r.push('world');\n\n      const err = new Error('kaboom');\n\n      r.on('error', (_err) => {\n        strictEqual(err, _err);\n        strictEqual(r.destroyed, true);\n        rErrored.resolve();\n      });\n\n      r.on('data', async () => {\n        throw err;\n      });\n\n      await rErrored.promise;\n    }\n\n    {\n      const wErrored = Promise.withResolvers();\n\n      const w = new Writable({\n        captureRejections: true,\n        highWaterMark: 1,\n        write(chunk, enc, cb) {\n          queueMicrotask(cb);\n        },\n      });\n\n      const err = new Error('kaboom');\n\n      w.write('hello', () => {\n        w.write('world');\n      });\n\n      w.on('error', (_err) => {\n        strictEqual(err, _err);\n        strictEqual(w.destroyed, true);\n        wErrored.resolve();\n      });\n\n      w.on('drain', async () => {\n        throw err;\n      });\n\n      await wErrored.promise;\n    }\n  },\n};\n\nexport const construct = {\n  async test(ctrl, env, ctx) {\n    {\n      const errored = Promise.withResolvers();\n      // Multiple callback.\n      new Writable({\n        construct: (callback) => {\n          callback();\n          callback();\n        },\n      }).on('error', (err) => {\n        errored.resolve(err);\n      });\n\n      const check = await errored.promise;\n      strictEqual(check.message, 'Callback called multiple times');\n    }\n\n    {\n      const errored = Promise.withResolvers();\n      // Multiple callback.\n      new Readable({\n        construct: (callback) => {\n          callback();\n          callback();\n        },\n      }).on('error', (err) => {\n        errored.resolve(err);\n      });\n\n      const check = await errored.promise;\n      strictEqual(check.message, 'Callback called multiple times');\n    }\n\n    {\n      // Synchronous error.\n      const errored = Promise.withResolvers();\n      const err = new Error('test');\n      new Writable({\n        construct: (callback) => {\n          callback(err);\n        },\n      }).on('error', (err) => {\n        errored.resolve(err);\n      });\n\n      const check = await errored.promise;\n      strictEqual(check, err);\n    }\n\n    {\n      const errored = Promise.withResolvers();\n      const err = new Error('test');\n\n      new Readable({\n        construct: (callback) => {\n          callback(err);\n        },\n      }).on('error', (err) => {\n        errored.resolve(err);\n      });\n\n      const check = await errored.promise;\n      strictEqual(check, err);\n    }\n\n    {\n      // Asynchronous error.\n      const errored = Promise.withResolvers();\n      const err = new Error('test');\n\n      new Writable({\n        construct: (callback) => {\n          queueMicrotask(() => callback(err));\n        },\n      }).on('error', (err) => {\n        errored.resolve(err);\n      });\n\n      const check = await errored.promise;\n      strictEqual(check, err);\n    }\n\n    {\n      // Asynchronous error.\n      const errored = Promise.withResolvers();\n      const err = new Error('test');\n\n      new Readable({\n        construct: (callback) => {\n          queueMicrotask(() => callback(err));\n        },\n      }).on('error', errored.resolve);\n\n      const check = await errored.promise;\n      strictEqual(check, err);\n    }\n\n    async function testDestroy(factory) {\n      {\n        const constructed = Promise.withResolvers();\n        const closed = Promise.withResolvers();\n        const s = factory({\n          construct: (cb) => {\n            constructed.resolve();\n            queueMicrotask(cb);\n          },\n        });\n        s.on('close', closed.resolve);\n        s.destroy();\n        await Promise.all([constructed.promise, closed.promise]);\n      }\n\n      {\n        const constructed = Promise.withResolvers();\n        const closed = Promise.withResolvers();\n        const destroyed = Promise.withResolvers();\n        const s = factory({\n          construct: (cb) => {\n            constructed.resolve();\n            queueMicrotask(cb);\n          },\n        });\n        s.on('close', closed.resolve);\n        s.destroy(null, () => {\n          destroyed.resolve();\n        });\n\n        await Promise.all([\n          constructed.promise,\n          closed.promise,\n          destroyed.promise,\n        ]);\n      }\n\n      {\n        const constructed = Promise.withResolvers();\n        const closed = Promise.withResolvers();\n        const s = factory({\n          construct: (cb) => {\n            constructed.resolve();\n            queueMicrotask(cb);\n          },\n        });\n        s.on('close', closed.resolve);\n        s.destroy();\n        await Promise.all([constructed.promise, closed.promise]);\n      }\n\n      {\n        const constructed = Promise.withResolvers();\n        const closed = Promise.withResolvers();\n        const errored = Promise.withResolvers();\n\n        const s = factory({\n          construct: (cb) => {\n            constructed.resolve();\n            queueMicrotask(cb);\n          },\n        });\n        s.on('close', closed.resolve);\n        s.on('error', (err) => {\n          strictEqual(err.message, 'kaboom');\n          errored.resolve();\n        });\n        s.destroy(new Error('kaboom'), (err) => {\n          strictEqual(err.message, 'kaboom');\n        });\n        await Promise.all([\n          constructed.promise,\n          closed.promise,\n          errored.promise,\n        ]);\n      }\n\n      {\n        const constructed = Promise.withResolvers();\n        const errored = Promise.withResolvers();\n        const closed = Promise.withResolvers();\n        const s = factory({\n          construct: (cb) => {\n            constructed.resolve();\n            queueMicrotask(cb);\n          },\n        });\n        s.on('error', errored.resolve);\n        s.on('close', closed.resolve);\n        s.destroy(new Error());\n\n        await Promise.all([\n          constructed.promise,\n          closed.promise,\n          errored.promise,\n        ]);\n      }\n    }\n    await testDestroy(\n      (opts) =>\n        new Readable({\n          read: () => {\n            throw new Error('must not call');\n          },\n          ...opts,\n        })\n    );\n    await testDestroy(\n      (opts) =>\n        new Writable({\n          write: () => {\n            throw new Error('must not call');\n          },\n          final: () => {\n            throw new Error('must not call');\n          },\n          ...opts,\n        })\n    );\n\n    {\n      const constructed = Promise.withResolvers();\n      const read = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n\n      const r = new Readable({\n        autoDestroy: true,\n        construct: (cb) => {\n          constructed.resolve();\n          queueMicrotask(cb);\n        },\n        read: () => {\n          r.push(null);\n          read.resolve();\n        },\n      });\n      r.on('close', closed.resolve);\n      r.on('data', () => {\n        throw new Error('should not call');\n      });\n\n      await Promise.all([constructed.promise, read.promise, closed.promise]);\n    }\n\n    {\n      const constructed = Promise.withResolvers();\n      const write = Promise.withResolvers();\n      const final = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n\n      const w = new Writable({\n        autoDestroy: true,\n        construct: (cb) => {\n          constructed.resolve();\n          queueMicrotask(cb);\n        },\n        write: (chunk, encoding, cb) => {\n          write.resolve();\n          queueMicrotask(cb);\n        },\n        final: (cb) => {\n          final.resolve();\n          queueMicrotask(cb);\n        },\n      });\n      w.on('close', closed.resolve);\n      w.end('data');\n\n      await Promise.all([\n        constructed.promise,\n        write.promise,\n        final.promise,\n        closed.promise,\n      ]);\n    }\n\n    {\n      const constructed = Promise.withResolvers();\n      const final = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      const w = new Writable({\n        autoDestroy: true,\n        construct: (cb) => {\n          constructed.resolve();\n          queueMicrotask(cb);\n        },\n        write: () => {\n          throw new Error('should not call');\n        },\n        final: (cb) => {\n          final.resolve();\n          queueMicrotask(cb);\n        },\n      });\n      w.on('close', closed.resolve);\n      w.end();\n\n      await Promise.all([constructed.promise, final.promise, closed.promise]);\n    }\n\n    {\n      let constructed = true;\n      new Duplex({\n        construct() {\n          constructed = true;\n        },\n      });\n      ok(constructed);\n    }\n\n    {\n      // https://github.com/nodejs/node/issues/34448\n\n      const constructed = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      const d = new Duplex({\n        readable: false,\n        construct: (callback) => {\n          queueMicrotask(() => {\n            constructed.resolve();\n            callback();\n          });\n        },\n        write(chunk, encoding, callback) {\n          callback();\n        },\n        read() {\n          this.push(null);\n        },\n      });\n      d.resume();\n      d.end('foo');\n      d.on('close', closed.resolve);\n\n      await Promise.all([constructed.promise, closed.promise]);\n    }\n\n    {\n      // Construct should not cause stream to read.\n      new Readable({\n        construct: (callback) => {\n          callback();\n        },\n        read() {\n          throw new Error('should not call');\n        },\n      });\n    }\n  },\n};\n\nexport const decoderObjectMode = {\n  test(ctrl, env, ctx) {\n    const readable = new Readable({\n      read: () => {},\n      encoding: 'utf16le',\n      objectMode: true,\n    });\n\n    readable.push(Buffer.from('abc', 'utf16le'));\n    readable.push(Buffer.from('def', 'utf16le'));\n    readable.push(null);\n\n    // Without object mode, these would be concatenated into a single chunk.\n    strictEqual(readable.read(), 'abc');\n    strictEqual(readable.read(), 'def');\n    strictEqual(readable.read(), null);\n  },\n};\n\nexport const destroyEventOrder = {\n  async test(ctrl, env, ctx) {\n    const destroyed = Promise.withResolvers();\n    const events = [];\n    const rs = new Readable({\n      read() {},\n    });\n\n    let closed = false;\n    let errored = false;\n\n    rs.on('close', () => events.push('close'));\n\n    rs.on('error', (err) => events.push('errored'));\n\n    rs.destroy(new Error('kaboom'), () => {\n      strictEqual(events, ['errored', 'close']);\n      destroyed.resolve();\n    });\n\n    await destroyed;\n  },\n};\n\nexport const destroyTests = {\n  async test(ctrl, env, ctx) {\n    {\n      const errored = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      const r = new Readable({ read() {} });\n      destroy(r);\n      strictEqual(r.destroyed, true);\n      r.on('error', errored.resolve);\n      r.on('close', closed.resolve);\n      const check = await errored.promise;\n      strictEqual(check.name, 'AbortError');\n      await closed.promise;\n    }\n\n    {\n      const errored = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      const err = new Error('asd');\n      const r = new Readable({ read() {} });\n      destroy(r, err);\n      strictEqual(r.destroyed, true);\n      r.on('error', errored.resolve);\n      r.on('close', closed.resolve);\n      const check = await errored.promise;\n      strictEqual(check, err);\n      await closed.promise;\n    }\n\n    {\n      const errored = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      const w = new Writable({ write() {} });\n      destroy(w);\n      strictEqual(w.destroyed, true);\n      w.on('error', errored.resolve);\n      w.on('close', closed.resolve);\n      const check = await errored.promise;\n      strictEqual(check.name, 'AbortError');\n      await closed.promise;\n    }\n\n    {\n      const errored = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      const err = new Error('asd');\n      const w = new Writable({ write() {} });\n      destroy(w, err);\n      strictEqual(w.destroyed, true);\n      w.on('error', errored.resolve);\n      w.on('close', closed.resolve);\n      const check = await errored.promise;\n      strictEqual(check, err);\n      await closed.promise;\n    }\n  },\n};\n\nexport const duplexDestroy = {\n  async test(ctrl, env, ctx) {\n    {\n      const closed = Promise.withResolvers();\n\n      const duplex = new Duplex({\n        write(chunk, enc, cb) {\n          cb();\n        },\n        read() {},\n      });\n\n      duplex.resume();\n\n      duplex.on('end', () => {\n        throw new Error('should not call');\n      });\n      duplex.on('finish', () => {\n        throw new Error('should not call');\n      });\n      duplex.on('close', closed.resolve);\n\n      duplex.destroy();\n      strictEqual(duplex.destroyed, true);\n      await closed.promise;\n    }\n\n    {\n      const errored = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      const duplex = new Duplex({\n        write(chunk, enc, cb) {\n          cb();\n        },\n        read() {},\n      });\n      duplex.resume();\n\n      const expected = new Error('kaboom');\n\n      duplex.on('end', closed.reject);\n      duplex.on('finish', closed.reject);\n      duplex.on('close', closed.resolve);\n      duplex.on('error', errored.resolve);\n\n      duplex.destroy(expected);\n      strictEqual(duplex.destroyed, true);\n      const check = await errored.promise;\n      strictEqual(check, expected);\n      await closed.promise;\n    }\n\n    {\n      const errored = Promise.withResolvers();\n      const destroyCalled = Promise.withResolvers();\n      const duplex = new Duplex({\n        write(chunk, enc, cb) {\n          cb();\n        },\n        read() {},\n      });\n\n      const expected = new Error('kaboom');\n\n      duplex._destroy = function (err, cb) {\n        strictEqual(err, expected);\n        cb(err);\n        destroyCalled.resolve();\n      };\n\n      duplex.on('finish', errored.reject);\n      duplex.on('error', errored.resolve);\n\n      duplex.destroy(expected);\n      strictEqual(duplex.destroyed, true);\n      const check = await errored.promise;\n      strictEqual(check, expected);\n      await destroyCalled.promise;\n    }\n\n    {\n      const closed = Promise.withResolvers();\n      const destroyCalled = Promise.withResolvers();\n      const expected = new Error('kaboom');\n      const duplex = new Duplex({\n        write(chunk, enc, cb) {\n          cb();\n        },\n        read() {},\n        destroy: function (err, cb) {\n          strictEqual(err, expected);\n          cb();\n          destroyCalled.resolve();\n        },\n      });\n      duplex.resume();\n\n      duplex.on('end', closed.reject);\n      duplex.on('finish', closed.reject);\n\n      // Error is swallowed by the custom _destroy\n      duplex.on('error', closed.reject);\n      duplex.on('close', closed.resolve);\n\n      duplex.destroy(expected);\n      strictEqual(duplex.destroyed, true);\n      await Promise.all([closed.promise, destroyCalled.promise]);\n    }\n\n    {\n      const destroyCalled = Promise.withResolvers();\n      const duplex = new Duplex({\n        write(chunk, enc, cb) {\n          cb();\n        },\n        read() {},\n      });\n\n      duplex._destroy = function (err, cb) {\n        strictEqual(err, null);\n        cb();\n        destroyCalled.resolve();\n      };\n\n      duplex.destroy();\n      strictEqual(duplex.destroyed, true);\n      await destroyCalled.promise;\n    }\n\n    {\n      const destroyCalled = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      const duplex = new Duplex({\n        write(chunk, enc, cb) {\n          cb();\n        },\n        read() {},\n      });\n      duplex.resume();\n\n      duplex._destroy = function (err, cb) {\n        strictEqual(err, null);\n        queueMicrotask(() => {\n          this.push(null);\n          this.end();\n          cb();\n          destroyCalled.resolve();\n        });\n      };\n\n      const fail = closed.reject;\n\n      duplex.on('finish', fail);\n      duplex.on('end', fail);\n\n      duplex.destroy();\n\n      duplex.removeListener('end', fail);\n      duplex.removeListener('finish', fail);\n      duplex.on('end', fail);\n      duplex.on('finish', fail);\n      duplex.on('close', closed.resolve);\n      strictEqual(duplex.destroyed, true);\n      await Promise.all([closed.promise, destroyCalled.promise]);\n    }\n\n    {\n      const destroyCalled = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      const duplex = new Duplex({\n        write(chunk, enc, cb) {\n          cb();\n        },\n        read() {},\n      });\n\n      const expected = new Error('kaboom');\n\n      duplex._destroy = function (err, cb) {\n        strictEqual(err, null);\n        cb(expected);\n        destroyCalled.resolve();\n      };\n\n      duplex.on('error', errored.resolve);\n\n      duplex.destroy();\n      strictEqual(duplex.destroyed, true);\n      const check = await errored.promise;\n      strictEqual(check, expected);\n      await destroyCalled.promise;\n    }\n\n    {\n      const closed = Promise.withResolvers();\n      const duplex = new Duplex({\n        write(chunk, enc, cb) {\n          cb();\n        },\n        read() {},\n        allowHalfOpen: true,\n      });\n      duplex.resume();\n\n      duplex.on('finish', closed.reject);\n      duplex.on('end', closed.reject);\n      duplex.on('close', closed.resolve);\n\n      duplex.destroy();\n      strictEqual(duplex.destroyed, true);\n      await closed.promise;\n    }\n\n    {\n      const closed = Promise.withResolvers();\n      const duplex = new Duplex({\n        write(chunk, enc, cb) {\n          cb();\n        },\n        read() {},\n        destroy() {\n          throw new Error('should not be called');\n        },\n      });\n\n      duplex.destroyed = true;\n      strictEqual(duplex.destroyed, true);\n\n      duplex.destroy();\n    }\n\n    {\n      function MyDuplex() {\n        strictEqual(this.destroyed, false);\n        this.destroyed = false;\n        Duplex.call(this);\n      }\n\n      Object.setPrototypeOf(MyDuplex.prototype, Duplex.prototype);\n      Object.setPrototypeOf(MyDuplex, Duplex);\n\n      new MyDuplex();\n    }\n\n    {\n      const closed = Promise.withResolvers();\n      const duplex = new Duplex({\n        writable: false,\n        autoDestroy: true,\n        write(chunk, enc, cb) {\n          cb();\n        },\n        read() {},\n      });\n      duplex.push(null);\n      duplex.resume();\n      duplex.on('close', closed.resolve);\n      await closed.promise;\n    }\n\n    {\n      const closed = Promise.withResolvers();\n      const duplex = new Duplex({\n        readable: false,\n        autoDestroy: true,\n        write(chunk, enc, cb) {\n          cb();\n        },\n        read() {},\n      });\n      duplex.end();\n      duplex.on('close', closed.resolve);\n      await closed.promise;\n    }\n\n    {\n      const closed = Promise.withResolvers();\n      const duplex = new Duplex({\n        allowHalfOpen: false,\n        autoDestroy: true,\n        write(chunk, enc, cb) {\n          cb();\n        },\n        read() {},\n      });\n      duplex.push(null);\n      duplex.resume();\n      const orgEnd = duplex.end;\n      duplex.end = closed.reject;\n      duplex.on('end', () => {\n        // Ensure end() is called in next tick to allow\n        // any pending writes to be invoked first.\n        queueMicrotask(() => {\n          duplex.end = orgEnd;\n        });\n      });\n      duplex.on('close', closed.resolve);\n      await closed.promise;\n    }\n\n    {\n      // Check abort signal\n      const closed = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      const controller = new AbortController();\n      const { signal } = controller;\n      const duplex = new Duplex({\n        write(chunk, enc, cb) {\n          cb();\n        },\n        read() {},\n        signal,\n      });\n      let count = 0;\n      duplex.on('error', (e) => {\n        strictEqual(count++, 0); // Ensure not called twice\n        strictEqual(e.name, 'AbortError');\n        errored.resolve();\n      });\n      duplex.on('close', closed.resolve);\n      controller.abort();\n      await Promise.all([errored.promise, closed.promise]);\n    }\n  },\n};\n\nexport const duplexEnd = {\n  async test(ctrl, env, ctx) {\n    {\n      const stream = new Duplex({\n        read() {},\n      });\n      strictEqual(stream.allowHalfOpen, true);\n      stream.on('finish', () => {\n        throw new Error('should not have been called');\n      });\n      strictEqual(stream.listenerCount('end'), 0);\n      stream.resume();\n      stream.push(null);\n    }\n\n    {\n      const finishedCalled = Promise.withResolvers();\n      const stream = new Duplex({\n        read() {},\n        allowHalfOpen: false,\n      });\n      strictEqual(stream.allowHalfOpen, false);\n      stream.on('finish', finishedCalled.resolve);\n      strictEqual(stream.listenerCount('end'), 0);\n      stream.resume();\n      stream.push(null);\n      await finishedCalled.promise;\n    }\n\n    {\n      const stream = new Duplex({\n        read() {},\n        allowHalfOpen: false,\n      });\n      strictEqual(stream.allowHalfOpen, false);\n      stream._writableState.ended = true;\n      stream.on('finish', () => {\n        throw new Error('Should not have been called');\n      });\n      strictEqual(stream.listenerCount('end'), 0);\n      stream.resume();\n      stream.push(null);\n    }\n  },\n};\n\nexport const duplexProps = {\n  test(ctrl, env, ctx) {\n    {\n      const d = new Duplex({\n        objectMode: true,\n        highWaterMark: 100,\n      });\n\n      strictEqual(d.writableObjectMode, true);\n      strictEqual(d.writableHighWaterMark, 100);\n      strictEqual(d.readableObjectMode, true);\n      strictEqual(d.readableHighWaterMark, 100);\n    }\n\n    {\n      const d = new Duplex({\n        readableObjectMode: false,\n        readableHighWaterMark: 10,\n        writableObjectMode: true,\n        writableHighWaterMark: 100,\n      });\n\n      strictEqual(d.writableObjectMode, true);\n      strictEqual(d.writableHighWaterMark, 100);\n      strictEqual(d.readableObjectMode, false);\n      strictEqual(d.readableHighWaterMark, 10);\n    }\n  },\n};\n\nexport const duplexReadableEnd = {\n  async test(ctrl, env, ctx) {\n    const ended = Promise.withResolvers();\n    let loops = 5;\n\n    const src = new Readable({\n      read() {\n        if (loops--) this.push(Buffer.alloc(80000));\n      },\n    });\n\n    const dst = new Transform({\n      transform(chunk, output, fn) {\n        this.push(null);\n        fn();\n      },\n    });\n\n    src.pipe(dst);\n\n    dst.on('data', () => {});\n    dst.on('end', () => {\n      strictEqual(loops, 3);\n      ok(src.isPaused());\n      ended.resolve();\n    });\n\n    await ended.promise;\n  },\n};\n\nexport const duplexReadableWritable = {\n  async test(ctrl, env, ctx) {\n    {\n      const errored = Promise.withResolvers();\n      const duplex = new Duplex({\n        readable: false,\n      });\n      strictEqual(duplex.readable, false);\n      duplex.push('asd');\n      duplex.on('error', (err) => {\n        strictEqual(err.code, 'ERR_STREAM_PUSH_AFTER_EOF');\n        errored.resolve();\n      });\n      duplex.on('data', errored.reject);\n      duplex.on('end', errored.reject);\n      await errored.promise;\n    }\n\n    {\n      const closed = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      const duplex = new Duplex({\n        writable: false,\n        write: closed.reject,\n      });\n      strictEqual(duplex.writable, false);\n      duplex.write('asd');\n      duplex.on('error', (err) => {\n        strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END');\n        errored.resolve();\n      });\n      duplex.on('finish', closed.reject);\n      duplex.on('close', closed.resolve);\n      await Promise.all([closed.promise, errored.promise]);\n    }\n\n    {\n      const closed = Promise.withResolvers();\n      const duplex = new Duplex({\n        readable: false,\n      });\n      strictEqual(duplex.readable, false);\n      duplex.on('data', closed.reject);\n      duplex.on('end', closed.reject);\n      duplex.on('close', closed.resolve);\n      async function run() {\n        for await (const chunk of duplex) {\n          ok(false, chunk);\n        }\n      }\n      await Promise.all([run(), closed.promise]);\n    }\n  },\n};\n\nexport const duplexWritableFinished = {\n  async test(ctrl, env, ctx) {\n    {\n      // Find it on Duplex.prototype\n      ok(Object.hasOwn(Duplex.prototype, 'writableFinished'));\n    }\n\n    // event\n    {\n      const finishedCalled = Promise.withResolvers();\n      const ended = Promise.withResolvers();\n      const duplex = new Duplex();\n\n      duplex._write = (chunk, encoding, cb) => {\n        // The state finished should start in false.\n        strictEqual(duplex.writableFinished, false);\n        cb();\n      };\n\n      duplex.on('finish', () => {\n        strictEqual(duplex.writableFinished, true);\n        finishedCalled.resolve();\n      });\n\n      duplex.end('testing finished state', () => {\n        strictEqual(duplex.writableFinished, true);\n        ended.resolve();\n      });\n\n      await Promise.all([finishedCalled.promise, ended.promise]);\n    }\n  },\n};\n\nexport const duplex = {\n  async test(ctrl, env, ctx) {\n    const stream = new Duplex({ objectMode: true });\n\n    ok(Duplex() instanceof Duplex);\n    ok(stream._readableState.objectMode);\n    ok(stream._writableState.objectMode);\n    ok(stream.allowHalfOpen);\n    strictEqual(stream.listenerCount('end'), 0);\n\n    let written;\n    let read;\n\n    stream._write = (obj, _, cb) => {\n      written = obj;\n      cb();\n    };\n\n    stream._read = () => {};\n\n    stream.on('data', (obj) => {\n      read = obj;\n    });\n\n    stream.push({ val: 1 });\n    stream.end({ val: 2 });\n\n    // We have no equivalent for on('exit') ... should we?\n    // process.on('exit', () => {\n    //   strictEqual(read.val, 1);\n    //   strictEqual(written.val, 2);\n    // });\n\n    // Duplex.fromWeb\n    {\n      const dataToRead = Buffer.from('hello');\n      const dataToWrite = Buffer.from('world');\n\n      const readable = new ReadableStream({\n        start(controller) {\n          controller.enqueue(dataToRead);\n        },\n      });\n\n      const writable = new WritableStream({\n        write: (chunk) => {\n          strictEqual(chunk, dataToWrite);\n        },\n      });\n\n      const pair = { readable, writable };\n      const duplex = Duplex.fromWeb(pair);\n\n      duplex.write(dataToWrite);\n      const p = Promise.withResolvers();\n      duplex.once('data', (chunk) => {\n        strictEqual(chunk, dataToRead);\n        p.resolve();\n      });\n      await p.promise;\n    }\n\n    // Duplex.fromWeb - using utf8 and objectMode\n    {\n      const dataToRead = 'hello';\n      const dataToWrite = 'world';\n\n      const readable = new ReadableStream({\n        start(controller) {\n          controller.enqueue(dataToRead);\n        },\n      });\n\n      const writable = new WritableStream({\n        write: (chunk) => {\n          strictEqual(chunk, dataToWrite);\n        },\n      });\n\n      const pair = {\n        readable,\n        writable,\n      };\n      const duplex = Duplex.fromWeb(pair, {\n        encoding: 'utf8',\n        objectMode: true,\n      });\n\n      duplex.write(dataToWrite);\n      const p = Promise.withResolvers();\n      duplex.once('data', (chunk) => {\n        strictEqual(chunk, dataToRead);\n        p.resolve();\n      });\n      await p.promise;\n    }\n\n    // Duplex.toWeb\n    {\n      const dataToRead = Buffer.from('hello');\n      const dataToWrite = Buffer.from('world');\n\n      const duplex = Duplex({\n        read() {\n          this.push(dataToRead);\n          this.push(null);\n        },\n        write: (chunk) => {\n          strictEqual(chunk, dataToWrite);\n        },\n      });\n\n      const { writable, readable } = Duplex.toWeb(duplex);\n      writable.getWriter().write(dataToWrite);\n\n      const p = Promise.withResolvers();\n      readable\n        .getReader()\n        .read()\n        .then((result) => {\n          deepStrictEqual(Buffer.from(result.value), dataToRead);\n          p.resolve();\n        });\n      await p.promise;\n    }\n  },\n};\n\nexport const end_of_streams = {\n  async test(ctrl, env, ctx) {\n    throws(\n      () => {\n        // Passing empty object to mock invalid stream\n        // should throw error\n        finished({}, () => {});\n      },\n      { code: 'ERR_INVALID_ARG_TYPE' }\n    );\n\n    const streamObj = new Duplex();\n    streamObj.end();\n    // Below code should not throw any errors as the\n    // streamObj is `Stream`\n    finished(streamObj, () => {});\n  },\n};\n\nexport const end_paused = {\n  async test(ctrl, env, ctx) {\n    const calledRead = Promise.withResolvers();\n    const ended = Promise.withResolvers();\n    const stream = new Readable();\n    stream._read = function () {\n      this.push(null);\n      calledRead.resolve();\n    };\n\n    stream.on('data', ended.reject);\n    stream.on('end', ended.resolve);\n    stream.pause();\n\n    setTimeout(function () {\n      stream.resume();\n    });\n\n    await Promise.all([calledRead.promise, ended.promise]);\n  },\n};\n\nexport const error_once = {\n  async test(ctrl, env, ctx) {\n    {\n      const errored = Promise.withResolvers();\n      const writable = new Writable();\n      writable.on('error', errored.resolve);\n      writable.end();\n      writable.write('h');\n      writable.write('h');\n      await errored.promise;\n    }\n\n    {\n      const errored = Promise.withResolvers();\n      const readable = new Readable();\n      readable.on('error', errored.resolve);\n      readable.push(null);\n      readable.push('h');\n      readable.push('h');\n      await errored.promise;\n    }\n  },\n};\n\nexport const finishedTest = {\n  async test(ctrl, env, ctx) {\n    const promisify = (await import('node:util')).promisify;\n    {\n      const finishedOk = Promise.withResolvers();\n      const rs = new Readable({\n        read() {},\n      });\n\n      finished(rs, (err) => {\n        if (err == null) finishedOk.resolve();\n        else finishedOk.reject(err);\n      });\n\n      rs.push(null);\n      rs.resume();\n\n      await finishedOk.promise;\n    }\n\n    {\n      const finishedOk = Promise.withResolvers();\n\n      const ws = new Writable({\n        write(data, enc, cb) {\n          cb();\n        },\n      });\n\n      finished(ws, (err) => {\n        if (err == null) finishedOk.resolve();\n        else finishedOk.reject(err);\n      });\n\n      ws.end();\n\n      await finishedOk.promise;\n    }\n\n    {\n      const finishedOk = Promise.withResolvers();\n      const tr = new Transform({\n        transform(data, enc, cb) {\n          cb();\n        },\n      });\n\n      let finish = false;\n      let ended = false;\n\n      tr.on('end', () => {\n        ended = true;\n      });\n\n      tr.on('finish', () => {\n        finish = true;\n      });\n\n      finished(tr, (err) => {\n        ok(finish);\n        ok(ended);\n        if (err == null) finishedOk.resolve();\n        else finishedOk.reject(err);\n      });\n\n      tr.end();\n      tr.resume();\n\n      await finishedOk.promise;\n    }\n\n    {\n      const finishedCalled = Promise.withResolvers();\n      const rs = new Readable({\n        read() {\n          this.push(enc.encode('a'));\n          this.push(enc.encode('b'));\n          this.push(null);\n        },\n      });\n\n      rs.resume();\n      finished(rs, finishedCalled.resolve);\n      await finishedCalled.promise;\n    }\n\n    {\n      const finishedPromise = promisify(finished);\n\n      async function run() {\n        const enc = new TextEncoder();\n        const rs = new Readable({\n          read() {\n            this.push(enc.encode('a'));\n            this.push(enc.encode('b'));\n            this.push(null);\n          },\n        });\n\n        let ended = false;\n        rs.resume();\n        rs.on('end', () => {\n          ended = true;\n        });\n        await finishedPromise(rs);\n        ok(ended);\n      }\n\n      await run();\n    }\n\n    {\n      // Check pre-cancelled\n      const signal = new EventTarget();\n      signal.aborted = true;\n\n      const rs = Readable.from((function* () {})());\n      const errored = Promise.withResolvers();\n      finished(rs, { signal }, errored.resolve);\n      const check = await errored.promise;\n      strictEqual(check.name, 'AbortError');\n    }\n\n    {\n      // Check cancelled before the stream ends sync.\n      const ac = new AbortController();\n      const { signal } = ac;\n\n      const errored = Promise.withResolvers();\n      const rs = Readable.from((function* () {})());\n      finished(rs, { signal }, errored.resolve);\n      ac.abort();\n      const check = await errored.promise;\n      strictEqual(check.name, 'AbortError');\n    }\n\n    {\n      // Check cancelled before the stream ends async.\n      const ac = new AbortController();\n      const { signal } = ac;\n\n      const rs = Readable.from((function* () {})());\n      setTimeout(() => ac.abort(), 1);\n      const errored = Promise.withResolvers();\n      finished(rs, { signal }, errored.resolve);\n      const check = await errored.promise;\n      strictEqual(check.name, 'AbortError');\n    }\n\n    {\n      // Check cancelled after doesn't throw.\n      const ac = new AbortController();\n      const { signal } = ac;\n\n      const rs = Readable.from(\n        (function* () {\n          yield 5;\n          queueMicrotask(() => ac.abort());\n        })()\n      );\n      rs.resume();\n      const finishedOk = Promise.withResolvers();\n      finished(rs, { signal }, finishedOk.resolve);\n      const check = await finishedOk.promise;\n      strictEqual(check.name, 'AbortError');\n    }\n\n    {\n      // Promisified abort works\n      const finishedPromise = promisify(finished);\n      async function run() {\n        const ac = new AbortController();\n        const { signal } = ac;\n        const rs = Readable.from((function* () {})());\n        queueMicrotask(() => ac.abort());\n        await finishedPromise(rs, { signal });\n      }\n\n      await rejects(run, { name: 'AbortError' });\n    }\n\n    {\n      // Promisified pre-aborted works\n      const finishedPromise = promisify(finished);\n      async function run() {\n        const signal = new EventTarget();\n        signal.aborted = true;\n        const rs = Readable.from((function* () {})());\n        await finishedPromise(rs, { signal });\n      }\n\n      await rejects(run, { name: 'AbortError' });\n    }\n\n    {\n      const rs = new Readable();\n\n      const finishedOk = Promise.withResolvers();\n\n      finished(rs, finishedOk.resolve);\n\n      rs.push(null);\n      rs.emit('close'); // Should not trigger an error\n      rs.resume();\n\n      const check = await finishedOk.promise;\n      ifError(check);\n    }\n\n    {\n      const rs = new Readable();\n      const errored = Promise.withResolvers();\n\n      finished(rs, errored.resolve);\n\n      rs.emit('close'); // Should trigger error\n      rs.push(null);\n      rs.resume();\n\n      const check = await errored.promise;\n      ok(check);\n    }\n\n    // Test faulty input values and options.\n    {\n      const rs = new Readable({\n        read() {},\n      });\n\n      throws(() => finished(rs, 'foo'), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        message: /callback/,\n      });\n      throws(() => finished(rs, 'foo', () => {}), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        message: /options/,\n      });\n      throws(() => finished(rs, {}, 'foo'), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        message: /callback/,\n      });\n\n      const finishedOk = Promise.withResolvers();\n      finished(rs, null, finishedOk.resolve);\n\n      rs.push(null);\n      rs.resume();\n\n      const check = await finishedOk.promise;\n      ifError(check);\n    }\n\n    // Test that calling returned function removes listeners\n    {\n      const ws = new Writable({\n        write(data, env, cb) {\n          cb();\n        },\n      });\n      const removeListener = finished(ws, () => {\n        throw new Error('should not have been called');\n      });\n      removeListener();\n      ws.end();\n    }\n\n    {\n      const rs = new Readable();\n      const removeListeners = finished(rs, () => {\n        throw new Error('should not have been called');\n      });\n      removeListeners();\n\n      rs.emit('close');\n      rs.push(null);\n      rs.resume();\n    }\n\n    {\n      const EventEmitter = (await import('node:events')).EventEmitter;\n      const streamLike = new EventEmitter();\n      streamLike.readableEnded = true;\n      streamLike.readable = true;\n      throws(\n        () => {\n          finished(streamLike, () => {});\n        },\n        { code: 'ERR_INVALID_ARG_TYPE' }\n      );\n      streamLike.emit('close');\n    }\n\n    {\n      const writable = new Writable({ write() {} });\n      writable.writable = false;\n      writable.destroy();\n      const errored = Promise.withResolvers();\n      finished(writable, errored.resolve);\n      const check = await errored.promise;\n      strictEqual(check.code, 'ERR_STREAM_PREMATURE_CLOSE');\n    }\n\n    {\n      const readable = new Readable();\n      readable.readable = false;\n      readable.destroy();\n      const errored = Promise.withResolvers();\n      finished(readable, errored.resolve);\n      const check = await errored.promise;\n      strictEqual(check.code, 'ERR_STREAM_PREMATURE_CLOSE');\n    }\n  },\n};\n\nexport const highWaterMark = {\n  async test(ctrl, env, ctx) {\n    {\n      // This test ensures that the stream implementation correctly handles values\n      // for highWaterMark which exceed the range of signed 32 bit integers and\n      // rejects invalid values.\n\n      // This number exceeds the range of 32 bit integer arithmetic but should still\n      // be handled correctly.\n      const ovfl = Number.MAX_SAFE_INTEGER;\n      const readable = Readable({\n        highWaterMark: ovfl,\n      });\n      strictEqual(readable._readableState.highWaterMark, ovfl);\n      const writable = Writable({\n        highWaterMark: ovfl,\n      });\n      strictEqual(writable._writableState.highWaterMark, ovfl);\n      for (const invalidHwm of [true, false, '5', {}, -5, NaN]) {\n        for (const type of [Readable, Writable]) {\n          throws(\n            () => {\n              type({\n                highWaterMark: invalidHwm,\n              });\n            },\n            {\n              name: 'TypeError',\n              code: 'ERR_INVALID_ARG_VALUE',\n            }\n          );\n        }\n      }\n    }\n    {\n      // This test ensures that the push method's implementation\n      // correctly handles the edge case where the highWaterMark and\n      // the state.length are both zero\n\n      const readable = Readable({\n        highWaterMark: 0,\n      });\n      for (let i = 0; i < 3; i++) {\n        const needMoreData = readable.push();\n        strictEqual(needMoreData, true);\n      }\n    }\n    {\n      // This test ensures that the read(n) method's implementation\n      // correctly handles the edge case where the highWaterMark, state.length\n      // and n are all zero\n\n      const readable = Readable({\n        highWaterMark: 0,\n      });\n      const readCalled = Promise.withResolvers();\n      readable._read = readCalled.resolve;\n      readable.read(0);\n      await readCalled.promise;\n    }\n    {\n      const readCalled = Promise.withResolvers();\n      // Parse size as decimal integer\n      await Promise.all(\n        ['1', '1.0', 1].map(async (size) => {\n          const readable = new Readable({\n            read: readCalled.resolve,\n            highWaterMark: 0,\n          });\n          readable.read(size);\n          strictEqual(readable._readableState.highWaterMark, Number(size));\n          await readCalled.promise;\n        })\n      );\n    }\n    {\n      // Test highwatermark limit\n      const hwm = 0x40000000 + 1;\n      const readable = Readable({\n        read() {},\n      });\n      throws(() => readable.read(hwm), {\n        code: 'ERR_OUT_OF_RANGE',\n      });\n    }\n  },\n};\n\nexport const pauseThenRead = {\n  async test(ctrl, env, ctx) {\n    const totalChunks = 100;\n    const chunkSize = 99;\n    const expectTotalData = totalChunks * chunkSize;\n    let expectEndingData = expectTotalData;\n    const closed = Promise.withResolvers();\n    const r = new Readable({\n      highWaterMark: 1000,\n    });\n    r.on('close', closed.resolve);\n    let chunks = totalChunks;\n    r._read = function (n) {\n      if (!(chunks % 2)) queueMicrotask(push);\n      else if (!(chunks % 3)) queueMicrotask(push);\n      else push();\n    };\n    let totalPushed = 0;\n    function push() {\n      const chunk = chunks-- > 0 ? Buffer.alloc(chunkSize, 'x') : null;\n      if (chunk) {\n        totalPushed += chunk.length;\n      }\n      r.push(chunk);\n    }\n    read100();\n    await closed.promise;\n\n    // First we read 100 bytes.\n    function read100() {\n      readn(100, onData);\n    }\n    function readn(n, then) {\n      expectEndingData -= n;\n      (function read() {\n        const c = r.read(n);\n        if (!c) r.once('readable', read);\n        else {\n          strictEqual(c.length, n);\n          ok(!r.readableFlowing);\n          then();\n        }\n      })();\n    }\n\n    // Then we listen to some data events.\n    function onData() {\n      expectEndingData -= 100;\n      let seen = 0;\n      r.on('data', function od(c) {\n        seen += c.length;\n        if (seen >= 100) {\n          // Seen enough\n          r.removeListener('data', od);\n          r.pause();\n          if (seen > 100) {\n            // Oh no, seen too much!\n            // Put the extra back.\n            const diff = seen - 100;\n            r.unshift(c.slice(c.length - diff));\n          }\n\n          // Nothing should be lost in-between.\n          queueMicrotask(pipeLittle);\n        }\n      });\n    }\n\n    // Just pipe 200 bytes, then unshift the extra and unpipe.\n    function pipeLittle() {\n      expectEndingData -= 200;\n      const w = new Writable();\n      let written = 0;\n      w.on('finish', () => {\n        strictEqual(written, 200);\n        queueMicrotask(read1234);\n      });\n      w._write = function (chunk, encoding, cb) {\n        written += chunk.length;\n        if (written >= 200) {\n          r.unpipe(w);\n          w.end();\n          cb();\n          if (written > 200) {\n            const diff = written - 200;\n            written -= diff;\n            r.unshift(chunk.slice(chunk.length - diff));\n          }\n        } else {\n          queueMicrotask(cb);\n        }\n      };\n      r.pipe(w);\n    }\n\n    // Now read 1234 more bytes.\n    function read1234() {\n      readn(1234, resumePause);\n    }\n    function resumePause() {\n      // Don't read anything, just resume and re-pause a whole bunch.\n      r.resume();\n      r.pause();\n      r.resume();\n      r.pause();\n      r.resume();\n      r.pause();\n      r.resume();\n      r.pause();\n      r.resume();\n      r.pause();\n      queueMicrotask(pipe);\n    }\n    function pipe() {\n      const w = new Writable();\n      let written = 0;\n      w._write = function (chunk, encoding, cb) {\n        written += chunk.length;\n        cb();\n      };\n      w.on('finish', () => {\n        strictEqual(written, expectEndingData);\n        strictEqual(totalPushed, expectTotalData);\n      });\n      r.pipe(w);\n    }\n  },\n};\n\nexport const corkUncork = {\n  async test(ctrl, env, ctx) {\n    // Test the buffering behavior of Writable streams.\n    //\n    // The call to cork() triggers storing chunks which are flushed\n    // on calling uncork() in the same tick.\n    //\n    // node version target: 0.12\n\n    const closed = Promise.withResolvers();\n    const expectedChunks = ['please', 'buffer', 'me', 'kindly'];\n    const inputChunks = expectedChunks.slice(0);\n    let seenChunks = [];\n    let seenEnd = false;\n    const w = new Writable();\n    // Let's arrange to store the chunks.\n    w._write = function (chunk, encoding, cb) {\n      // Default encoding given none was specified.\n      strictEqual(encoding, 'buffer');\n      seenChunks.push(chunk);\n      cb();\n    };\n    // Let's record the stream end event.\n    w.on('finish', () => {\n      seenEnd = true;\n    });\n\n    w.on('close', closed.resolve);\n\n    function writeChunks(remainingChunks, callback) {\n      const writeChunk = remainingChunks.shift();\n      let writeState;\n      if (writeChunk) {\n        queueMicrotask(() => {\n          writeState = w.write(writeChunk);\n          // We were not told to stop writing.\n          ok(writeState);\n          writeChunks(remainingChunks, callback);\n        });\n      } else {\n        callback();\n      }\n    }\n\n    // Do an initial write.\n    w.write('stuff');\n    // The write was immediate.\n    strictEqual(seenChunks.length, 1);\n    // Reset the chunks seen so far.\n    seenChunks = [];\n\n    // Trigger stream buffering.\n    w.cork();\n\n    // Write the bufferedChunks.\n    writeChunks(inputChunks, () => {\n      // Should not have seen anything yet.\n      strictEqual(seenChunks.length, 0);\n\n      // Trigger writing out the buffer.\n      w.uncork();\n\n      // Buffered bytes should be seen in current tick.\n      strictEqual(seenChunks.length, 4);\n\n      // Did the chunks match.\n      for (let i = 0, l = expectedChunks.length; i < l; i++) {\n        const seen = seenChunks[i];\n        // There was a chunk.\n        ok(seen);\n        const expected = Buffer.from(expectedChunks[i]);\n        // It was what we expected.\n        ok(seen.equals(expected));\n      }\n      queueMicrotask(() => {\n        // The stream should not have been ended.\n        ok(!seenEnd);\n        w.end();\n      });\n    });\n\n    await closed.promise;\n  },\n};\n\nexport const corkEnd = {\n  async test(ctrl, env, ctx) {\n    // Test the buffering behavior of Writable streams.\n    //\n    // The call to cork() triggers storing chunks which are flushed\n    // on calling end() and the stream subsequently ended.\n    //\n    // node version target: 0.12\n\n    const closed = Promise.withResolvers();\n    const expectedChunks = ['please', 'buffer', 'me', 'kindly'];\n    const inputChunks = expectedChunks.slice(0);\n    let seenChunks = [];\n    let seenEnd = false;\n    const w = new Writable();\n    // Let's arrange to store the chunks.\n    w._write = function (chunk, encoding, cb) {\n      // Stream end event is not seen before the last write.\n      ok(!seenEnd);\n      // Default encoding given none was specified.\n      strictEqual(encoding, 'buffer');\n      seenChunks.push(chunk);\n      cb();\n    };\n    // Let's record the stream end event.\n    w.on('finish', () => {\n      seenEnd = true;\n    });\n    function writeChunks(remainingChunks, callback) {\n      const writeChunk = remainingChunks.shift();\n      let writeState;\n      if (writeChunk) {\n        queueMicrotask(() => {\n          writeState = w.write(writeChunk);\n          // We were not told to stop writing.\n          ok(writeState);\n          writeChunks(remainingChunks, callback);\n        });\n      } else {\n        callback();\n      }\n    }\n\n    w.on('close', closed.resolve);\n\n    // Do an initial write.\n    w.write('stuff');\n    // The write was immediate.\n    strictEqual(seenChunks.length, 1);\n    // Reset the seen chunks.\n    seenChunks = [];\n\n    // Trigger stream buffering.\n    w.cork();\n\n    // Write the bufferedChunks.\n    writeChunks(inputChunks, () => {\n      // Should not have seen anything yet.\n      strictEqual(seenChunks.length, 0);\n\n      // Trigger flush and ending the stream.\n      w.end();\n\n      // Stream should not ended in current tick.\n      ok(!seenEnd);\n\n      // Buffered bytes should be seen in current tick.\n      strictEqual(seenChunks.length, 4);\n\n      // Did the chunks match.\n      for (let i = 0, l = expectedChunks.length; i < l; i++) {\n        const seen = seenChunks[i];\n        // There was a chunk.\n        ok(seen);\n        const expected = Buffer.from(expectedChunks[i]);\n        // It was what we expected.\n        ok(seen.equals(expected));\n      }\n      queueMicrotask(() => {\n        // Stream should have ended in next tick.\n        ok(seenEnd);\n      });\n    });\n\n    await closed.promise;\n  },\n};\n\nexport const writable2Writable = {\n  async test(ctrl, env, ctx) {\n    class TestWriter extends Writable {\n      constructor(opts) {\n        super(opts);\n        this.buffer = [];\n        this.written = 0;\n      }\n      _write(chunk, encoding, cb) {\n        // Simulate a small unpredictable latency\n        queueMicrotask(\n          () => {\n            this.buffer.push(chunk.toString());\n            this.written += chunk.length;\n            cb();\n          },\n          Math.floor(Math.random() * 10)\n        );\n      }\n    }\n    const chunks = Array.from({ length: 50 }, (_, i) => 'x'.repeat(i));\n    {\n      // Verify fast writing\n      const tw = new TestWriter({\n        highWaterMark: 100,\n      });\n      const finishCalled = Promise.withResolvers();\n      tw.on('finish', function () {\n        // Got chunks in the right order\n        deepStrictEqual(tw.buffer, chunks);\n        finishCalled.resolve();\n      });\n      chunks.forEach(function (chunk) {\n        // Ignore backpressure. Just buffer it all up.\n        tw.write(chunk);\n      });\n      tw.end();\n      await finishCalled.promise;\n    }\n    {\n      // Verify slow writing\n      const tw = new TestWriter({\n        highWaterMark: 100,\n      });\n      const finishedCalled = Promise.withResolvers();\n      tw.on('finish', function () {\n        //  Got chunks in the right order\n        deepStrictEqual(tw.buffer, chunks);\n        finishedCalled.resolve();\n      });\n      let i = 0;\n      (function W() {\n        tw.write(chunks[i++]);\n        if (i < chunks.length) setTimeout(W, 10);\n        else tw.end();\n      })();\n      await finishedCalled.promise;\n    }\n    {\n      // Verify write backpressure\n      const tw = new TestWriter({\n        highWaterMark: 50,\n      });\n      let drains = 0;\n      const finishCalled = Promise.withResolvers();\n      tw.on('finish', function () {\n        // Got chunks in the right order\n        deepStrictEqual(tw.buffer, chunks);\n        strictEqual(drains, 17);\n        finishCalled.resolve();\n      });\n      tw.on('drain', function () {\n        drains++;\n      });\n      let i = 0;\n      (function W() {\n        let ret;\n        do {\n          ret = tw.write(chunks[i++]);\n        } while (ret !== false && i < chunks.length);\n        if (i < chunks.length) {\n          ok(tw.writableLength >= 50);\n          tw.once('drain', W);\n        } else {\n          tw.end();\n        }\n      })();\n      await finishCalled.promise;\n    }\n    {\n      // Verify write callbacks\n      const callbacks = chunks\n        .map(function (chunk, i) {\n          return [\n            i,\n            function () {\n              callbacks._called[i] = chunk;\n            },\n          ];\n        })\n        .reduce(function (set, x) {\n          set[`callback-${x[0]}`] = x[1];\n          return set;\n        }, {});\n      callbacks._called = [];\n      const finishCalled = Promise.withResolvers();\n      const tw = new TestWriter({\n        highWaterMark: 100,\n      });\n      tw.on('finish', function () {\n        queueMicrotask(function () {\n          // Got chunks in the right order\n          deepStrictEqual(tw.buffer, chunks);\n          // Called all callbacks\n          deepStrictEqual(callbacks._called, chunks);\n          finishCalled.resolve();\n        });\n      });\n      chunks.forEach(function (chunk, i) {\n        tw.write(chunk, callbacks[`callback-${i}`]);\n      });\n      tw.end();\n      await finishCalled.promise;\n    }\n    {\n      // Verify end() callback\n      const tw = new TestWriter();\n      const endCalled = Promise.withResolvers();\n      tw.end(endCalled.resolve);\n      await endCalled.promise;\n    }\n    const helloWorldBuffer = Buffer.from('hello world');\n    {\n      // Verify end() callback with chunk\n      const tw = new TestWriter();\n      const endCalled = Promise.withResolvers();\n      tw.end(helloWorldBuffer, endCalled.resolve);\n      await endCalled.promise;\n    }\n    {\n      // Verify end() callback with chunk and encoding\n      const tw = new TestWriter();\n      const endCalled = Promise.withResolvers();\n      tw.end('hello world', 'ascii', endCalled.resolve);\n      await endCalled.promise;\n    }\n    {\n      // Verify end() callback after write() call\n      const tw = new TestWriter();\n      const endCalled = Promise.withResolvers();\n      tw.write(helloWorldBuffer);\n      tw.end(endCalled.resolve);\n      await endCalled.promise;\n    }\n    {\n      // Verify end() callback after write() callback\n      const tw = new TestWriter();\n      const endCalled = Promise.withResolvers();\n      let writeCalledback = false;\n      tw.write(helloWorldBuffer, function () {\n        writeCalledback = true;\n      });\n      tw.end(function () {\n        strictEqual(writeCalledback, true);\n        endCalled.resolve();\n      });\n      await endCalled.promise;\n    }\n    {\n      // Verify encoding is ignored for buffers\n      const tw = new Writable();\n      const hex = '018b5e9a8f6236ffe30e31baf80d2cf6eb';\n      const writeCalled = Promise.withResolvers();\n      tw._write = function (chunk) {\n        strictEqual(chunk.toString('hex'), hex);\n        writeCalled.resolve();\n      };\n      const buf = Buffer.from(hex, 'hex');\n      tw.write(buf, 'latin1');\n      await writeCalled.promise;\n    }\n    {\n      // Verify writables cannot be piped\n      const w = new Writable({\n        autoDestroy: false,\n      });\n      const gotError = Promise.withResolvers();\n      w._write = gotError.reject;\n      w.on('error', gotError.resolve);\n      w.pipe(new Writable({ write() {} }));\n      await gotError.promise;\n    }\n    {\n      // Verify that duplex streams can be piped\n      const d = new Duplex();\n      const readCalled = Promise.withResolvers();\n      d._read = readCalled.resolve;\n      d._write = () => {\n        throw new Error('should not be called');\n      };\n      let gotError = false;\n      d.on('error', function () {\n        gotError = true;\n      });\n      d.pipe(new Writable({ write() {} }));\n      strictEqual(gotError, false);\n    }\n    {\n      // Verify that end(chunk) twice is an error\n      const w = new Writable();\n      const writeCalled = Promise.withResolvers();\n      const gotError = Promise.withResolvers();\n      w._write = (msg) => {\n        strictEqual(msg.toString(), 'this is the end');\n        writeCalled.resolve();\n      };\n      w.on('error', function (er) {\n        strictEqual(er.message, 'write after end');\n        gotError.resolve();\n      });\n      w.end('this is the end');\n      w.end('and so is this');\n      await Promise.all([writeCalled.promise, gotError.promise]);\n    }\n    {\n      // Verify stream doesn't end while writing\n      const w = new Writable();\n      const wrote = Promise.withResolvers();\n      const finishCalled = Promise.withResolvers();\n      w._write = function (chunk, e, cb) {\n        strictEqual(this.writing, undefined);\n        wrote.resolve();\n        this.writing = true;\n        queueMicrotask(() => {\n          this.writing = false;\n          cb();\n        }, 1);\n      };\n      w.on('finish', function () {\n        strictEqual(this.writing, false);\n        finishCalled.resolve();\n      });\n      w.write(Buffer.alloc(0));\n      w.end();\n      await Promise.all([wrote.promise, finishCalled.promise]);\n    }\n    {\n      // Verify finish does not come before write() callback\n      const w = new Writable();\n      const finishCalled = Promise.withResolvers();\n      let writeCb = false;\n      w._write = function (chunk, e, cb) {\n        setTimeout(function () {\n          writeCb = true;\n          cb();\n        }, 10);\n      };\n      w.on('finish', function () {\n        strictEqual(writeCb, true);\n        finishCalled.resolve();\n      });\n      w.write(Buffer.alloc(0));\n      w.end();\n      await finishCalled.promise;\n    }\n    {\n      // Verify finish does not come before synchronous _write() callback\n      const w = new Writable();\n      const finishCalled = Promise.withResolvers();\n      let writeCb = false;\n      w._write = function (chunk, e, cb) {\n        cb();\n      };\n      w.on('finish', function () {\n        strictEqual(writeCb, true);\n        finishCalled.resolve();\n      });\n      w.write(Buffer.alloc(0), function () {\n        writeCb = true;\n      });\n      w.end();\n      await finishCalled.promise;\n    }\n    {\n      // Verify finish is emitted if the last chunk is empty\n      const w = new Writable();\n      w._write = function (chunk, e, cb) {\n        queueMicrotask(cb);\n      };\n      const finishCalled = Promise.withResolvers();\n      w.on('finish', finishCalled.resolve);\n      w.write(Buffer.allocUnsafe(1));\n      w.end(Buffer.alloc(0));\n      await finishCalled.promise;\n    }\n    {\n      // Verify that finish is emitted after shutdown\n      const w = new Writable();\n      let shutdown = false;\n      const finalCalled = Promise.withResolvers();\n      const finishCalled = Promise.withResolvers();\n      w._final = function (cb) {\n        strictEqual(this, w);\n        setTimeout(function () {\n          finalCalled.resolve();\n          shutdown = true;\n          cb();\n        }, 100);\n      };\n      w._write = function (chunk, e, cb) {\n        queueMicrotask(cb);\n      };\n      w.on('finish', function () {\n        strictEqual(shutdown, true);\n        finishCalled.resolve();\n      });\n      w.write(Buffer.allocUnsafe(1));\n      w.end(Buffer.allocUnsafe(0));\n      await Promise.all([finalCalled.promise, finishCalled.promise]);\n    }\n    {\n      // Verify that error is only emitted once when failing in _finish.\n      const w = new Writable();\n      const finalCalled = Promise.withResolvers();\n      const gotError = Promise.withResolvers();\n      w._final = function (cb) {\n        cb(new Error('test'));\n        finalCalled.resolve();\n      };\n      w.on('error', (err) => {\n        gotError.resolve();\n        strictEqual(w._writableState.errorEmitted, true);\n        strictEqual(err.message, 'test');\n        w.on('error', () => {\n          throw new Error('should not be called again');\n        });\n        w.destroy(new Error());\n      });\n      w.end();\n      await Promise.all([finalCalled.promise, gotError.promise]);\n    }\n    {\n      // Verify that error is only emitted once when failing in write.\n      const w = new Writable();\n      throws(\n        () => {\n          w.write(null);\n        },\n        {\n          code: 'ERR_STREAM_NULL_VALUES',\n        }\n      );\n    }\n    {\n      // Verify that error is only emitted once when failing in write after end.\n      const w = new Writable();\n      const gotError = Promise.withResolvers();\n      w.on('error', (err) => {\n        strictEqual(w._writableState.errorEmitted, true);\n        strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END');\n        gotError.resolve();\n      });\n      w.end();\n      w.write('hello');\n      w.destroy(new Error());\n      await gotError.promise;\n    }\n    {\n      // Verify that finish is not emitted after error\n      const w = new Writable();\n      const finalCalled = Promise.withResolvers();\n      const gotError = Promise.withResolvers();\n      w._final = function (cb) {\n        cb(new Error());\n        finalCalled.resolve();\n      };\n      w._write = function (chunk, e, cb) {\n        queueMicrotask(cb);\n      };\n      w.on('error', gotError.resolve);\n      w.on('prefinish', gotError.reject);\n      w.on('finish', gotError.reject);\n      w.write(Buffer.allocUnsafe(1));\n      w.end(Buffer.allocUnsafe(0));\n      await Promise.all([finalCalled.promise, gotError.promise]);\n    }\n  },\n};\n\nexport const unpipeLeak = {\n  async test(ctrl, env, ctx) {\n    const chunk = Buffer.from('hallo');\n    class TestWriter extends Writable {\n      _write(buffer, encoding, callback) {\n        callback(null);\n      }\n    }\n    const dest = new TestWriter();\n\n    // Set this high so that we'd trigger a nextTick warning\n    // and/or RangeError if we do maybeReadMore wrong.\n    class TestReader extends Readable {\n      constructor() {\n        super({\n          highWaterMark: 0x10000,\n        });\n      }\n      _read(size) {\n        this.push(chunk);\n      }\n    }\n    const src = new TestReader();\n    for (let i = 0; i < 10; i++) {\n      src.pipe(dest);\n      src.unpipe(dest);\n    }\n\n    strictEqual(src.listeners('end').length, 0);\n    strictEqual(src.listeners('readable').length, 0);\n    strictEqual(dest.listeners('unpipe').length, 0);\n    strictEqual(dest.listeners('drain').length, 0);\n    strictEqual(dest.listeners('error').length, 0);\n    strictEqual(dest.listeners('close').length, 0);\n    strictEqual(dest.listeners('finish').length, 0);\n  },\n};\n\nexport const unpipeDrain = {\n  async test(ctrl, env, ctx) {\n    class TestWriter extends Writable {\n      _write(buffer, encoding, callback) {}\n    }\n\n    const dest = new TestWriter();\n    class TestReader extends Readable {\n      done = Promise.withResolvers();\n      constructor() {\n        super();\n        this.reads = 0;\n      }\n      _read(size) {\n        this.reads += 1;\n        this.push(Buffer.alloc(size));\n        if (this.reads == 2) this.done.resolve();\n      }\n    }\n    const src1 = new TestReader();\n    const src2 = new TestReader();\n    src1.pipe(dest);\n    src1.once('readable', () => {\n      queueMicrotask(() => {\n        src2.pipe(dest);\n        src2.once('readable', () => {\n          queueMicrotask(() => {\n            src1.unpipe(dest);\n          });\n        });\n      });\n    });\n    await Promise.all([src1.done.promise, src2.done.promise]);\n  },\n};\n\nexport const transformTest = {\n  async test(ctrl, env, ctx) {\n    {\n      // Verify writable side consumption\n      const tx = new Transform({\n        highWaterMark: 10,\n      });\n      let transformed = 0;\n      tx._transform = function (chunk, encoding, cb) {\n        transformed += chunk.length;\n        tx.push(chunk);\n        cb();\n      };\n      for (let i = 1; i <= 10; i++) {\n        tx.write(Buffer.allocUnsafe(i));\n      }\n      tx.end();\n      strictEqual(tx.readableLength, 10);\n      strictEqual(transformed, 10);\n      deepStrictEqual(\n        tx.writableBuffer.map(function (c) {\n          return c.chunk.length;\n        }),\n        [5, 6, 7, 8, 9, 10]\n      );\n    }\n    {\n      // Verify passthrough behavior\n      const pt = new PassThrough();\n      pt.write(Buffer.from('foog'));\n      pt.write(Buffer.from('bark'));\n      pt.write(Buffer.from('bazy'));\n      pt.write(Buffer.from('kuel'));\n      pt.end();\n      strictEqual(pt.read(5).toString(), 'foogb');\n      strictEqual(pt.read(5).toString(), 'arkba');\n      strictEqual(pt.read(5).toString(), 'zykue');\n      strictEqual(pt.read(5).toString(), 'l');\n    }\n    {\n      // Verify object passthrough behavior\n      const pt = new PassThrough({\n        objectMode: true,\n      });\n      pt.write(1);\n      pt.write(true);\n      pt.write(false);\n      pt.write(0);\n      pt.write('foo');\n      pt.write('');\n      pt.write({\n        a: 'b',\n      });\n      pt.end();\n      strictEqual(pt.read(), 1);\n      strictEqual(pt.read(), true);\n      strictEqual(pt.read(), false);\n      strictEqual(pt.read(), 0);\n      strictEqual(pt.read(), 'foo');\n      strictEqual(pt.read(), '');\n      deepStrictEqual(pt.read(), {\n        a: 'b',\n      });\n    }\n    {\n      // Verify passthrough constructor behavior\n      const pt = PassThrough();\n      ok(pt instanceof PassThrough);\n    }\n    {\n      // Verify transform constructor behavior\n      const pt = Transform();\n      ok(pt instanceof Transform);\n    }\n    {\n      // Perform a simple transform\n      const pt = new Transform();\n      pt._transform = function (c, e, cb) {\n        const ret = Buffer.alloc(c.length, 'x');\n        pt.push(ret);\n        cb();\n      };\n      pt.write(Buffer.from('foog'));\n      pt.write(Buffer.from('bark'));\n      pt.write(Buffer.from('bazy'));\n      pt.write(Buffer.from('kuel'));\n      pt.end();\n      strictEqual(pt.read(5).toString(), 'xxxxx');\n      strictEqual(pt.read(5).toString(), 'xxxxx');\n      strictEqual(pt.read(5).toString(), 'xxxxx');\n      strictEqual(pt.read(5).toString(), 'x');\n    }\n    {\n      // Verify simple object transform\n      const pt = new Transform({\n        objectMode: true,\n      });\n      pt._transform = function (c, e, cb) {\n        pt.push(JSON.stringify(c));\n        cb();\n      };\n      pt.write(1);\n      pt.write(true);\n      pt.write(false);\n      pt.write(0);\n      pt.write('foo');\n      pt.write('');\n      pt.write({\n        a: 'b',\n      });\n      pt.end();\n      strictEqual(pt.read(), '1');\n      strictEqual(pt.read(), 'true');\n      strictEqual(pt.read(), 'false');\n      strictEqual(pt.read(), '0');\n      strictEqual(pt.read(), '\"foo\"');\n      strictEqual(pt.read(), '\"\"');\n      strictEqual(pt.read(), '{\"a\":\"b\"}');\n    }\n    {\n      // Verify async passthrough\n      const pt = new Transform();\n      pt._transform = function (chunk, encoding, cb) {\n        setTimeout(function () {\n          pt.push(chunk);\n          cb();\n        }, 10);\n      };\n      pt.write(Buffer.from('foog'));\n      pt.write(Buffer.from('bark'));\n      pt.write(Buffer.from('bazy'));\n      pt.write(Buffer.from('kuel'));\n      pt.end();\n      const finishCalled = Promise.withResolvers();\n      pt.on('finish', function () {\n        strictEqual(pt.read(5).toString(), 'foogb');\n        strictEqual(pt.read(5).toString(), 'arkba');\n        strictEqual(pt.read(5).toString(), 'zykue');\n        strictEqual(pt.read(5).toString(), 'l');\n        finishCalled.resolve();\n      });\n      await finishCalled.promise;\n    }\n    {\n      // Verify asymmetric transform (expand)\n      const pt = new Transform();\n\n      // Emit each chunk 2 times.\n      pt._transform = function (chunk, encoding, cb) {\n        setTimeout(function () {\n          pt.push(chunk);\n          setTimeout(function () {\n            pt.push(chunk);\n            cb();\n          }, 10);\n        }, 10);\n      };\n      pt.write(Buffer.from('foog'));\n      pt.write(Buffer.from('bark'));\n      pt.write(Buffer.from('bazy'));\n      pt.write(Buffer.from('kuel'));\n      pt.end();\n      const finishCalled = Promise.withResolvers();\n      pt.on('finish', function () {\n        strictEqual(pt.read(5).toString(), 'foogf');\n        strictEqual(pt.read(5).toString(), 'oogba');\n        strictEqual(pt.read(5).toString(), 'rkbar');\n        strictEqual(pt.read(5).toString(), 'kbazy');\n        strictEqual(pt.read(5).toString(), 'bazyk');\n        strictEqual(pt.read(5).toString(), 'uelku');\n        strictEqual(pt.read(5).toString(), 'el');\n        finishCalled.resolve();\n      });\n      await finishCalled.promise;\n    }\n    {\n      // Verify asymmetric transform (compress)\n      const pt = new Transform();\n\n      // Each output is the first char of 3 consecutive chunks,\n      // or whatever's left.\n      pt.state = '';\n      pt._transform = function (chunk, encoding, cb) {\n        if (!chunk) chunk = '';\n        const s = chunk.toString();\n        setTimeout(() => {\n          this.state += s.charAt(0);\n          if (this.state.length === 3) {\n            pt.push(Buffer.from(this.state));\n            this.state = '';\n          }\n          cb();\n        }, 10);\n      };\n      pt._flush = function (cb) {\n        // Just output whatever we have.\n        pt.push(Buffer.from(this.state));\n        this.state = '';\n        cb();\n      };\n      pt.write(Buffer.from('aaaa'));\n      pt.write(Buffer.from('bbbb'));\n      pt.write(Buffer.from('cccc'));\n      pt.write(Buffer.from('dddd'));\n      pt.write(Buffer.from('eeee'));\n      pt.write(Buffer.from('aaaa'));\n      pt.write(Buffer.from('bbbb'));\n      pt.write(Buffer.from('cccc'));\n      pt.write(Buffer.from('dddd'));\n      pt.write(Buffer.from('eeee'));\n      pt.write(Buffer.from('aaaa'));\n      pt.write(Buffer.from('bbbb'));\n      pt.write(Buffer.from('cccc'));\n      pt.write(Buffer.from('dddd'));\n      pt.end();\n\n      const finishCalled = Promise.withResolvers();\n      // 'abcdeabcdeabcd'\n      pt.on('finish', function () {\n        strictEqual(pt.read(5).toString(), 'abcde');\n        strictEqual(pt.read(5).toString(), 'abcde');\n        strictEqual(pt.read(5).toString(), 'abcd');\n        finishCalled.resolve();\n      });\n      await finishCalled.promise;\n    }\n\n    // This tests for a stall when data is written to a full stream\n    // that has empty transforms.\n    {\n      // Verify complex transform behavior\n      let count = 0;\n      let saved = null;\n      const pt = new Transform({\n        highWaterMark: 3,\n      });\n      pt._transform = function (c, e, cb) {\n        if (count++ === 1) saved = c;\n        else {\n          if (saved) {\n            pt.push(saved);\n            saved = null;\n          }\n          pt.push(c);\n        }\n        cb();\n      };\n      const finishCalled = Promise.withResolvers();\n      pt.on('finish', finishCalled.resolve);\n      pt.once('readable', function () {\n        queueMicrotask(function () {\n          pt.write(Buffer.from('d'));\n          pt.write(Buffer.from('ef'), function () {\n            pt.end();\n          });\n          strictEqual(pt.read().toString(), 'abcdef');\n          strictEqual(pt.read(), null);\n        });\n      });\n      pt.write(Buffer.from('abc'));\n      await finishCalled.promise;\n    }\n    {\n      // Verify passthrough event emission\n      const pt = new PassThrough();\n      let emits = 0;\n      pt.on('readable', function () {\n        emits++;\n      });\n      pt.write(Buffer.from('foog'));\n      pt.write(Buffer.from('bark'));\n      strictEqual(emits, 0);\n      strictEqual(pt.read(5).toString(), 'foogb');\n      strictEqual(String(pt.read(5)), 'null');\n      strictEqual(emits, 0);\n      pt.write(Buffer.from('bazy'));\n      pt.write(Buffer.from('kuel'));\n      strictEqual(emits, 0);\n      strictEqual(pt.read(5).toString(), 'arkba');\n      strictEqual(pt.read(5).toString(), 'zykue');\n      strictEqual(pt.read(5), null);\n      pt.end();\n      strictEqual(emits, 1);\n      strictEqual(pt.read(5).toString(), 'l');\n      strictEqual(pt.read(5), null);\n      strictEqual(emits, 1);\n    }\n    {\n      // Verify passthrough event emission reordering\n      const pt = new PassThrough();\n      let emits = 0;\n      pt.on('readable', function () {\n        emits++;\n      });\n      pt.write(Buffer.from('foog'));\n      pt.write(Buffer.from('bark'));\n      strictEqual(emits, 0);\n      strictEqual(pt.read(5).toString(), 'foogb');\n      strictEqual(pt.read(5), null);\n      const readable1 = Promise.withResolvers();\n      const readable2 = Promise.withResolvers();\n      const readable3 = Promise.withResolvers();\n      pt.once('readable', function () {\n        strictEqual(pt.read(5).toString(), 'arkba');\n        strictEqual(pt.read(5), null);\n        pt.once('readable', function () {\n          strictEqual(pt.read(5).toString(), 'zykue');\n          strictEqual(pt.read(5), null);\n          pt.once('readable', function () {\n            strictEqual(pt.read(5).toString(), 'l');\n            strictEqual(pt.read(5), null);\n            strictEqual(emits, 3);\n            readable3.resolve();\n          });\n          pt.end();\n          readable2.resolve();\n        });\n        pt.write(Buffer.from('kuel'));\n        readable1.resolve();\n      });\n      pt.write(Buffer.from('bazy'));\n      await Promise.all([\n        readable1.promise,\n        readable2.promise,\n        readable3.promise,\n      ]);\n    }\n    {\n      // Verify passthrough facade\n      const pt = new PassThrough();\n      const datas = [];\n      pt.on('data', function (chunk) {\n        datas.push(chunk.toString());\n      });\n      const endCalled = Promise.withResolvers();\n      pt.on('end', function () {\n        deepStrictEqual(datas, ['foog', 'bark', 'bazy', 'kuel']);\n        endCalled.resolve();\n      });\n      pt.write(Buffer.from('foog'));\n      setTimeout(function () {\n        pt.write(Buffer.from('bark'));\n        setTimeout(function () {\n          pt.write(Buffer.from('bazy'));\n          setTimeout(function () {\n            pt.write(Buffer.from('kuel'));\n            setTimeout(function () {\n              pt.end();\n            }, 10);\n          }, 10);\n        }, 10);\n      }, 10);\n\n      await endCalled.promise;\n    }\n    {\n      // Verify object transform (JSON parse)\n      const jp = new Transform({\n        objectMode: true,\n      });\n      jp._transform = function (data, encoding, cb) {\n        try {\n          jp.push(JSON.parse(data));\n          cb();\n        } catch (er) {\n          cb(er);\n        }\n      };\n\n      // Anything except null/undefined is fine.\n      // those are \"magic\" in the stream API, because they signal EOF.\n      const objects = [\n        {\n          foo: 'bar',\n        },\n        100,\n        'string',\n        {\n          nested: {\n            things: [\n              {\n                foo: 'bar',\n              },\n              100,\n              'string',\n            ],\n          },\n        },\n      ];\n      const ended = Promise.withResolvers();\n      jp.on('end', ended.resolve);\n      objects.forEach(function (obj) {\n        jp.write(JSON.stringify(obj));\n        const res = jp.read();\n        deepStrictEqual(res, obj);\n      });\n      jp.end();\n      // Read one more time to get the 'end' event\n      jp.read();\n      await ended.promise;\n    }\n    {\n      // Verify object transform (JSON stringify)\n      const js = new Transform({\n        objectMode: true,\n      });\n      js._transform = function (data, encoding, cb) {\n        try {\n          js.push(JSON.stringify(data));\n          cb();\n        } catch (er) {\n          cb(er);\n        }\n      };\n\n      // Anything except null/undefined is fine.\n      // those are \"magic\" in the stream API, because they signal EOF.\n      const objects = [\n        {\n          foo: 'bar',\n        },\n        100,\n        'string',\n        {\n          nested: {\n            things: [\n              {\n                foo: 'bar',\n              },\n              100,\n              'string',\n            ],\n          },\n        },\n      ];\n      const ended = Promise.withResolvers();\n      js.on('end', ended.resolve);\n      objects.forEach(function (obj) {\n        js.write(obj);\n        const res = js.read();\n        strictEqual(res, JSON.stringify(obj));\n      });\n      js.end();\n      // Read one more time to get the 'end' event\n      js.read();\n      await ended.promise;\n    }\n  },\n};\n\nexport const set_encoding = {\n  async test(ctrl, env, ctx) {\n    class TestReader extends Readable {\n      constructor(n, opts) {\n        super(opts);\n        this.pos = 0;\n        this.len = n || 100;\n      }\n      _read(n) {\n        setTimeout(() => {\n          if (this.pos >= this.len) {\n            // Double push(null) to test eos handling\n            this.push(null);\n            return this.push(null);\n          }\n          n = Math.min(n, this.len - this.pos);\n          if (n <= 0) {\n            // Double push(null) to test eos handling\n            this.push(null);\n            return this.push(null);\n          }\n          this.pos += n;\n          const ret = Buffer.alloc(n, 'a');\n          return this.push(ret);\n        }, 1);\n      }\n    }\n    {\n      // Verify utf8 encoding\n      const tr = new TestReader(100);\n      tr.setEncoding('utf8');\n      const out = [];\n      const expect = [\n        'aaaaaaaaaa',\n        'aaaaaaaaaa',\n        'aaaaaaaaaa',\n        'aaaaaaaaaa',\n        'aaaaaaaaaa',\n        'aaaaaaaaaa',\n        'aaaaaaaaaa',\n        'aaaaaaaaaa',\n        'aaaaaaaaaa',\n        'aaaaaaaaaa',\n      ];\n      tr.on('readable', function flow() {\n        let chunk;\n        while (null !== (chunk = tr.read(10))) out.push(chunk);\n      });\n      const ended = Promise.withResolvers();\n      tr.on('end', function () {\n        deepStrictEqual(out, expect);\n        ended.resolve();\n      });\n      await ended.promise;\n    }\n    {\n      // Verify hex encoding\n      const tr = new TestReader(100);\n      tr.setEncoding('hex');\n      const out = [];\n      const expect = [\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n      ];\n      tr.on('readable', function flow() {\n        let chunk;\n        while (null !== (chunk = tr.read(10))) out.push(chunk);\n      });\n      const ended = Promise.withResolvers();\n      tr.on('end', function () {\n        deepStrictEqual(out, expect);\n        ended.resolve();\n      });\n      await ended.promise;\n    }\n    {\n      // Verify hex encoding with read(13)\n      const tr = new TestReader(100);\n      tr.setEncoding('hex');\n      const out = [];\n      const expect = [\n        '6161616161616',\n        '1616161616161',\n        '6161616161616',\n        '1616161616161',\n        '6161616161616',\n        '1616161616161',\n        '6161616161616',\n        '1616161616161',\n        '6161616161616',\n        '1616161616161',\n        '6161616161616',\n        '1616161616161',\n        '6161616161616',\n        '1616161616161',\n        '6161616161616',\n        '16161',\n      ];\n      tr.on('readable', function flow() {\n        let chunk;\n        while (null !== (chunk = tr.read(13))) out.push(chunk);\n      });\n      const ended = Promise.withResolvers();\n      tr.on('end', function () {\n        deepStrictEqual(out, expect);\n        ended.resolve();\n      });\n      await ended.promise;\n    }\n    {\n      // Verify base64 encoding\n      const tr = new TestReader(100);\n      tr.setEncoding('base64');\n      const out = [];\n      const expect = [\n        'YWFhYWFhYW',\n        'FhYWFhYWFh',\n        'YWFhYWFhYW',\n        'FhYWFhYWFh',\n        'YWFhYWFhYW',\n        'FhYWFhYWFh',\n        'YWFhYWFhYW',\n        'FhYWFhYWFh',\n        'YWFhYWFhYW',\n        'FhYWFhYWFh',\n        'YWFhYWFhYW',\n        'FhYWFhYWFh',\n        'YWFhYWFhYW',\n        'FhYQ==',\n      ];\n      tr.on('readable', function flow() {\n        let chunk;\n        while (null !== (chunk = tr.read(10))) out.push(chunk);\n      });\n      const ended = Promise.withResolvers();\n      tr.on('end', function () {\n        deepStrictEqual(out, expect);\n        ended.resolve();\n      });\n      await ended.promise;\n    }\n    {\n      // Verify utf8 encoding\n      const tr = new TestReader(100, {\n        encoding: 'utf8',\n      });\n      const out = [];\n      const expect = [\n        'aaaaaaaaaa',\n        'aaaaaaaaaa',\n        'aaaaaaaaaa',\n        'aaaaaaaaaa',\n        'aaaaaaaaaa',\n        'aaaaaaaaaa',\n        'aaaaaaaaaa',\n        'aaaaaaaaaa',\n        'aaaaaaaaaa',\n        'aaaaaaaaaa',\n      ];\n      tr.on('readable', function flow() {\n        let chunk;\n        while (null !== (chunk = tr.read(10))) out.push(chunk);\n      });\n      const ended = Promise.withResolvers();\n      tr.on('end', function () {\n        deepStrictEqual(out, expect);\n        ended.resolve();\n      });\n      await ended.promise;\n    }\n    {\n      // Verify hex encoding\n      const tr = new TestReader(100, {\n        encoding: 'hex',\n      });\n      const out = [];\n      const expect = [\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n        '6161616161',\n      ];\n      tr.on('readable', function flow() {\n        let chunk;\n        while (null !== (chunk = tr.read(10))) out.push(chunk);\n      });\n      const ended = Promise.withResolvers();\n      tr.on('end', function () {\n        deepStrictEqual(out, expect);\n        ended.resolve();\n      });\n      await ended.promise;\n    }\n    {\n      // Verify hex encoding with read(13)\n      const tr = new TestReader(100, {\n        encoding: 'hex',\n      });\n      const out = [];\n      const expect = [\n        '6161616161616',\n        '1616161616161',\n        '6161616161616',\n        '1616161616161',\n        '6161616161616',\n        '1616161616161',\n        '6161616161616',\n        '1616161616161',\n        '6161616161616',\n        '1616161616161',\n        '6161616161616',\n        '1616161616161',\n        '6161616161616',\n        '1616161616161',\n        '6161616161616',\n        '16161',\n      ];\n      tr.on('readable', function flow() {\n        let chunk;\n        while (null !== (chunk = tr.read(13))) out.push(chunk);\n      });\n      const ended = Promise.withResolvers();\n      tr.on('end', function () {\n        deepStrictEqual(out, expect);\n        ended.resolve();\n      });\n      await ended.promise;\n    }\n    {\n      // Verify base64 encoding\n      const tr = new TestReader(100, {\n        encoding: 'base64',\n      });\n      const out = [];\n      const expect = [\n        'YWFhYWFhYW',\n        'FhYWFhYWFh',\n        'YWFhYWFhYW',\n        'FhYWFhYWFh',\n        'YWFhYWFhYW',\n        'FhYWFhYWFh',\n        'YWFhYWFhYW',\n        'FhYWFhYWFh',\n        'YWFhYWFhYW',\n        'FhYWFhYWFh',\n        'YWFhYWFhYW',\n        'FhYWFhYWFh',\n        'YWFhYWFhYW',\n        'FhYQ==',\n      ];\n      tr.on('readable', function flow() {\n        let chunk;\n        while (null !== (chunk = tr.read(10))) out.push(chunk);\n      });\n      const ended = Promise.withResolvers();\n      tr.on('end', function () {\n        deepStrictEqual(out, expect);\n        ended.resolve();\n      });\n      await ended.promise;\n    }\n    {\n      // Verify chaining behavior\n      const tr = new TestReader(100);\n      deepStrictEqual(tr.setEncoding('utf8'), tr);\n    }\n  },\n};\n\nexport const readable_wrap = {\n  async test(ctrl, env, ctx) {\n    async function runTest(highWaterMark, objectMode, produce) {\n      const rEnded = Promise.withResolvers();\n      const old = new EventEmitter();\n      const r = new Readable({\n        highWaterMark,\n        objectMode,\n      });\n      strictEqual(r, r.wrap(old));\n\n      r.on('end', rEnded.resolve);\n      old.pause = function () {\n        old.emit('pause');\n        flowing = false;\n      };\n      old.resume = function () {\n        old.emit('resume');\n        flow();\n      };\n\n      // Make sure pause is only emitted once.\n      let pausing = false;\n      r.on('pause', () => {\n        strictEqual(pausing, false);\n        pausing = true;\n        queueMicrotask(() => {\n          pausing = false;\n        });\n      });\n      let flowing;\n      let chunks = 10;\n      let oldEnded = false;\n      const expected = [];\n      function flow() {\n        flowing = true;\n        while (flowing && chunks-- > 0) {\n          const item = produce();\n          expected.push(item);\n          old.emit('data', item);\n        }\n        if (chunks <= 0) {\n          oldEnded = true;\n          old.emit('end');\n        }\n      }\n      const w = new Writable({\n        highWaterMark: highWaterMark * 2,\n        objectMode,\n      });\n      const written = [];\n      w._write = function (chunk, encoding, cb) {\n        written.push(chunk);\n        setTimeout(cb, 1);\n      };\n      const finishCalled = Promise.withResolvers();\n      w.on('finish', function () {\n        performAsserts();\n        finishCalled.resolve();\n      });\n      r.pipe(w);\n      flow();\n      function performAsserts() {\n        ok(oldEnded);\n        deepStrictEqual(written, expected);\n      }\n      await Promise.all([rEnded.promise, finishCalled.promise]);\n    }\n    await runTest(100, false, function () {\n      return Buffer.allocUnsafe(100);\n    });\n    await runTest(10, false, function () {\n      return Buffer.from('xxxxxxxxxx');\n    });\n    await runTest(1, true, function () {\n      return {\n        foo: 'bar',\n      };\n    });\n    const objectChunks = [\n      5,\n      'a',\n      false,\n      0,\n      '',\n      'xyz',\n      {\n        x: 4,\n      },\n      7,\n      [],\n      555,\n    ];\n    await runTest(1, true, function () {\n      return objectChunks.shift();\n    });\n  },\n};\n\nexport const readable_wrap_error = {\n  async test(ctrl, env, ctx) {\n    class LegacyStream extends EventEmitter {\n      pause() {}\n      resume() {}\n    }\n    {\n      const err = new Error();\n      const oldStream = new LegacyStream();\n      const errored = Promise.withResolvers();\n      const r = new Readable({\n        autoDestroy: true,\n      })\n        .wrap(oldStream)\n        .on('error', () => {\n          strictEqual(r._readableState.errorEmitted, true);\n          strictEqual(r._readableState.errored, err);\n          strictEqual(r.destroyed, true);\n          errored.resolve();\n        });\n      oldStream.emit('error', err);\n      await errored.promise;\n    }\n    {\n      const err = new Error();\n      const oldStream = new LegacyStream();\n      const errored = Promise.withResolvers();\n      const r = new Readable({\n        autoDestroy: false,\n      })\n        .wrap(oldStream)\n        .on('error', () => {\n          strictEqual(r._readableState.errorEmitted, true);\n          strictEqual(r._readableState.errored, err);\n          strictEqual(r.destroyed, false);\n          errored.resolve();\n        });\n      oldStream.emit('error', err);\n      await errored.promise;\n    }\n  },\n};\n\nexport const readable_wrap_empty = {\n  async test(ctrl, env, ctx) {\n    const oldStream = new EventEmitter();\n    oldStream.pause = () => {};\n    oldStream.resume = () => {};\n    const newStream = new Readable().wrap(oldStream);\n    const ended = Promise.withResolvers();\n    newStream.on('readable', () => {}).on('end', ended.resolve);\n    oldStream.emit('end');\n    await ended.promise;\n  },\n};\n\nexport const readable_wrap_destroy = {\n  async test(ctrl, env, ctx) {\n    const oldStream = new EventEmitter();\n    oldStream.pause = () => {};\n    oldStream.resume = () => {};\n    {\n      const destroyCalled = Promise.withResolvers();\n      new Readable({\n        autoDestroy: false,\n        destroy: destroyCalled.resolve,\n      }).wrap(oldStream);\n      oldStream.emit('destroy');\n      await destroyCalled.promise;\n    }\n    {\n      const destroyCalled = Promise.withResolvers();\n      new Readable({\n        autoDestroy: false,\n        destroy: destroyCalled.resolve,\n      }).wrap(oldStream);\n      oldStream.emit('close');\n      await destroyCalled.promise;\n    }\n  },\n};\n\nexport const readable_non_empty_end = {\n  async test(_ctrl, _env, _ctx) {\n    let len = 0;\n    const chunks = Array.from({ length: 10 }, (_el, i) => {\n      len += i + 1;\n      return Buffer.allocUnsafe(i + 1);\n    });\n    const test = new Readable();\n    let n = 0;\n    test._read = function (size) {\n      const chunk = chunks[n++];\n      setTimeout(function () {\n        test.push(chunk === undefined ? null : chunk);\n      }, 1);\n    };\n    test.on('end', thrower);\n    function thrower() {\n      throw new Error('this should not happen!');\n    }\n    let bytesread = 0;\n    const ended = Promise.withResolvers();\n    test.on('readable', function () {\n      const b = len - bytesread - 1;\n      const res = test.read(b);\n      if (res) {\n        bytesread += res.length;\n        setTimeout(next, 1);\n      }\n      test.read(0);\n    });\n    test.read(0);\n    function next() {\n      // Now let's make 'end' happen\n      test.removeListener('end', thrower);\n      test.on('end', ended.resolve);\n\n      // One to get the last byte\n      let r = test.read();\n      ok(r);\n      strictEqual(r.length, 1);\n      r = test.read();\n      strictEqual(r, null);\n    }\n    await ended.promise;\n  },\n};\n\nexport const readable_legacy_drain = {\n  async test(ctrl, env, ctx) {\n    const r = new Readable();\n    const N = 256;\n    let reads = 0;\n    r._read = function (n) {\n      return r.push(++reads === N ? null : Buffer.allocUnsafe(1));\n    };\n    const ended1 = Promise.withResolvers();\n    r.on('end', ended1.resolve);\n    const w = new Stream();\n    w.writable = true;\n    let buffered = 0;\n    w.write = function (c) {\n      buffered += c.length;\n      queueMicrotask(drain);\n      return false;\n    };\n    function drain() {\n      ok(buffered <= 3);\n      buffered = 0;\n      w.emit('drain');\n    }\n    const ended2 = Promise.withResolvers();\n    w.end = ended2.resolve;\n    r.pipe(w);\n    await Promise.all([ended1.promise, ended2.promise]);\n  },\n};\n\nexport const read_sync_stack = {\n  async test(ctrl, env, ctx) {\n    // This tests synchronous read callbacks and verifies that even if they nest\n    // heavily the process handles it without an error\n\n    const r = new Readable();\n    const N = 256 * 1024;\n    let reads = 0;\n    r._read = function (n) {\n      const chunk = reads++ === N ? null : Buffer.allocUnsafe(1);\n      r.push(chunk);\n    };\n    r.on('readable', function onReadable() {\n      r.read(N * 2);\n    });\n    const ended = Promise.withResolvers();\n    r.on('end', ended.resolve);\n    r.read(0);\n    await ended.promise;\n  },\n};\n\nexport const stream2_push = {\n  async test(ctrl, env, ctx) {\n    const stream = new Readable({\n      highWaterMark: 16,\n      encoding: 'utf8',\n    });\n    const source = new EventEmitter();\n    stream._read = function () {\n      readStart();\n    };\n    const ended = Promise.withResolvers();\n    stream.on('end', ended.resolve);\n    source.on('data', function (chunk) {\n      const ret = stream.push(chunk);\n      if (!ret) readStop();\n    });\n    source.on('end', function () {\n      stream.push(null);\n    });\n    let reading = false;\n    function readStart() {\n      reading = true;\n    }\n    function readStop() {\n      reading = false;\n      queueMicrotask(function () {\n        const r = stream.read();\n        if (r !== null) writer.write(r);\n      });\n    }\n    const writer = new Writable({\n      decodeStrings: false,\n    });\n    const written = [];\n    const expectWritten = [\n      'asdfgasdfgasdfgasdfg',\n      'asdfgasdfgasdfgasdfg',\n      'asdfgasdfgasdfgasdfg',\n      'asdfgasdfgasdfgasdfg',\n      'asdfgasdfgasdfgasdfg',\n      'asdfgasdfgasdfgasdfg',\n    ];\n    writer._write = function (chunk, encoding, cb) {\n      written.push(chunk);\n      queueMicrotask(cb);\n    };\n    writer.on('finish', finish);\n\n    // Now emit some chunks.\n\n    const chunk = 'asdfg';\n    let set = 0;\n    readStart();\n    data();\n    function data() {\n      ok(reading);\n      source.emit('data', chunk);\n      ok(reading);\n      source.emit('data', chunk);\n      ok(reading);\n      source.emit('data', chunk);\n      ok(reading);\n      source.emit('data', chunk);\n      ok(!reading);\n      if (set++ < 5) setTimeout(data, 10);\n      else end();\n    }\n    function finish() {\n      deepStrictEqual(written, expectWritten);\n    }\n    function end() {\n      source.emit('end');\n      ok(!reading);\n      writer.end(stream.read());\n    }\n    await ended.promise;\n  },\n};\n\nexport const stream2_pipe_error_once_listener = {\n  async test(ctrl, env, ctx) {\n    class Read extends Readable {\n      _read(size) {\n        this.push('x');\n        this.push(null);\n      }\n    }\n    class Write extends Writable {\n      _write(buffer, encoding, cb) {\n        this.emit('error', new Error('boom'));\n      }\n    }\n    const read = new Read();\n    const write = new Write();\n    const errored = Promise.withResolvers();\n    write.once('error', errored.resolve);\n    read.pipe(write);\n\n    await errored.promise;\n  },\n};\n\nexport const stream2_pipe_error_handling = {\n  async test(ctrl, env, ctx) {\n    {\n      let count = 1000;\n      const source = new Readable();\n      source._read = function (n) {\n        n = Math.min(count, n);\n        count -= n;\n        source.push(Buffer.allocUnsafe(n));\n      };\n      let unpipedDest;\n      source.unpipe = function (dest) {\n        unpipedDest = dest;\n        Readable.prototype.unpipe.call(this, dest);\n      };\n      const dest = new Writable();\n      dest._write = function (chunk, encoding, cb) {\n        cb();\n      };\n      source.pipe(dest);\n      let gotErr = null;\n      dest.on('error', function (err) {\n        gotErr = err;\n      });\n      let unpipedSource;\n      dest.on('unpipe', function (src) {\n        unpipedSource = src;\n      });\n      const err = new Error('This stream turned into bacon.');\n      dest.emit('error', err);\n      strictEqual(gotErr, err);\n      strictEqual(unpipedSource, source);\n      strictEqual(unpipedDest, dest);\n    }\n    {\n      let count = 1000;\n      const source = new Readable();\n      source._read = function (n) {\n        n = Math.min(count, n);\n        count -= n;\n        source.push(Buffer.allocUnsafe(n));\n      };\n      let unpipedDest;\n      source.unpipe = function (dest) {\n        unpipedDest = dest;\n        Readable.prototype.unpipe.call(this, dest);\n      };\n      const dest = new Writable({\n        autoDestroy: false,\n      });\n      dest._write = function (chunk, encoding, cb) {\n        cb();\n      };\n      source.pipe(dest);\n      let unpipedSource;\n      dest.on('unpipe', function (src) {\n        unpipedSource = src;\n      });\n      const err = new Error('This stream turned into bacon.');\n      let gotErr = null;\n      try {\n        dest.emit('error', err);\n      } catch (e) {\n        gotErr = e;\n      }\n      strictEqual(gotErr, err);\n      strictEqual(unpipedSource, source);\n      strictEqual(unpipedDest, dest);\n    }\n  },\n};\n\nexport const stream2_objects = {\n  async test(ctrl, env, ctx) {\n    function toArray(callback) {\n      const stream = new Writable({\n        objectMode: true,\n      });\n      const list = [];\n      stream.write = function (chunk) {\n        list.push(chunk);\n      };\n      stream.end = function () {\n        callback(list);\n        this.ended.resolve();\n      };\n      stream.ended = Promise.withResolvers();\n      return stream;\n    }\n    function fromArray(list) {\n      const r = new Readable({\n        objectMode: true,\n      });\n      r._read = () => {\n        throw new Error('should not have been called');\n      };\n      list.forEach(function (chunk) {\n        r.push(chunk);\n      });\n      r.push(null);\n      return r;\n    }\n    {\n      // Verify that objects can be read from the stream\n      const r = fromArray([\n        {\n          one: '1',\n        },\n        {\n          two: '2',\n        },\n      ]);\n      const v1 = r.read();\n      const v2 = r.read();\n      const v3 = r.read();\n      deepStrictEqual(v1, {\n        one: '1',\n      });\n      deepStrictEqual(v2, {\n        two: '2',\n      });\n      strictEqual(v3, null);\n    }\n    {\n      // Verify that objects can be piped into the stream\n      const r = fromArray([\n        {\n          one: '1',\n        },\n        {\n          two: '2',\n        },\n      ]);\n      const w = toArray(function (list) {\n        deepStrictEqual(list, [\n          {\n            one: '1',\n          },\n          {\n            two: '2',\n          },\n        ]);\n      });\n      r.pipe(w);\n      await w.ended.promise;\n    }\n    {\n      // Verify that read(n) is ignored\n      const r = fromArray([\n        {\n          one: '1',\n        },\n        {\n          two: '2',\n        },\n      ]);\n      const value = r.read(2);\n      deepStrictEqual(value, {\n        one: '1',\n      });\n    }\n    {\n      // Verify that objects can be synchronously read\n      const r = new Readable({\n        objectMode: true,\n      });\n      const list = [\n        {\n          one: '1',\n        },\n        {\n          two: '2',\n        },\n      ];\n      r._read = function (n) {\n        const item = list.shift();\n        r.push(item || null);\n      };\n      const dest = toArray(function (list) {\n        deepStrictEqual(list, [\n          {\n            one: '1',\n          },\n          {\n            two: '2',\n          },\n        ]);\n      });\n      r.pipe(dest);\n      await dest.ended.promise;\n    }\n    {\n      // Verify that objects can be asynchronously read\n      const r = new Readable({\n        objectMode: true,\n      });\n      const list = [\n        {\n          one: '1',\n        },\n        {\n          two: '2',\n        },\n      ];\n      r._read = function (n) {\n        const item = list.shift();\n        queueMicrotask(function () {\n          r.push(item || null);\n        });\n      };\n      const dest = toArray(function (list) {\n        deepStrictEqual(list, [\n          {\n            one: '1',\n          },\n          {\n            two: '2',\n          },\n        ]);\n      });\n      r.pipe(dest);\n      await dest.ended.promise;\n    }\n    {\n      // Verify that strings can be read as objects\n      const r = new Readable({\n        objectMode: true,\n      });\n      r._read = () => {\n        throw new Error('should not have been called');\n      };\n      const list = ['one', 'two', 'three'];\n      list.forEach(function (str) {\n        r.push(str);\n      });\n      r.push(null);\n      const dest = toArray(function (array) {\n        deepStrictEqual(array, list);\n      });\n      r.pipe(dest);\n      await dest.ended.promise;\n    }\n    {\n      // Verify read(0) behavior for object streams\n      const r = new Readable({\n        objectMode: true,\n      });\n      r.push('foobar');\n      r.push(null);\n      const dest = toArray(function (array) {\n        deepStrictEqual(array, ['foobar']);\n      });\n      r.pipe(dest);\n      await dest.ended.promise;\n    }\n    {\n      // Verify the behavior of pushing falsey values\n      const r = new Readable({\n        objectMode: true,\n      });\n      r.push(false);\n      r.push(0);\n      r.push('');\n      r.push(null);\n      const dest = toArray(function (array) {\n        deepStrictEqual(array, [false, 0, '']);\n      });\n      r.pipe(dest);\n      await dest.ended.promise;\n    }\n    {\n      // Verify high watermark _read() behavior\n      const r = new Readable({\n        highWaterMark: 6,\n        objectMode: true,\n      });\n      let calls = 0;\n      const list = ['1', '2', '3', '4', '5', '6', '7', '8'];\n      r._read = function (n) {\n        calls++;\n      };\n      list.forEach(function (c) {\n        r.push(c);\n      });\n      const v = r.read();\n      strictEqual(calls, 0);\n      strictEqual(v, '1');\n      const v2 = r.read();\n      strictEqual(v2, '2');\n      const v3 = r.read();\n      strictEqual(v3, '3');\n      strictEqual(calls, 1);\n    }\n    {\n      // Verify high watermark push behavior\n      const r = new Readable({\n        highWaterMark: 6,\n        objectMode: true,\n      });\n      r._read = () => {\n        throw new Error('should not have been called');\n      };\n      for (let i = 0; i < 6; i++) {\n        const bool = r.push(i);\n        strictEqual(bool, i !== 5);\n      }\n    }\n    {\n      // Verify that objects can be written to stream\n      const w = new Writable({\n        objectMode: true,\n      });\n      w._write = function (chunk, encoding, cb) {\n        deepStrictEqual(chunk, {\n          foo: 'bar',\n        });\n        cb();\n      };\n      const finishCalled = Promise.withResolvers();\n      w.on('finish', finishCalled.resolve);\n      w.write({\n        foo: 'bar',\n      });\n      w.end();\n      await finishCalled.promise;\n    }\n    {\n      // Verify that multiple objects can be written to stream\n      const w = new Writable({\n        objectMode: true,\n      });\n      const list = [];\n      w._write = function (chunk, encoding, cb) {\n        list.push(chunk);\n        cb();\n      };\n      const finishCalled = Promise.withResolvers();\n      w.on('finish', function () {\n        deepStrictEqual(list, [0, 1, 2, 3, 4]);\n        finishCalled.resolve();\n      });\n      w.write(0);\n      w.write(1);\n      w.write(2);\n      w.write(3);\n      w.write(4);\n      w.end();\n      await finishCalled.promise;\n    }\n    {\n      // Verify that strings can be written as objects\n      const w = new Writable({\n        objectMode: true,\n      });\n      const list = [];\n      w._write = function (chunk, encoding, cb) {\n        list.push(chunk);\n        queueMicrotask(cb);\n      };\n      const finishCalled = Promise.withResolvers();\n      w.on('finish', function () {\n        deepStrictEqual(list, ['0', '1', '2', '3', '4']);\n        finishCalled.resolve();\n      });\n      w.write('0');\n      w.write('1');\n      w.write('2');\n      w.write('3');\n      w.write('4');\n      w.end();\n      await finishCalled.promise;\n    }\n    {\n      // Verify that stream buffers finish until callback is called\n      const w = new Writable({\n        objectMode: true,\n      });\n      let called = false;\n      w._write = function (chunk, encoding, cb) {\n        strictEqual(chunk, 'foo');\n        queueMicrotask(function () {\n          called = true;\n          cb();\n        });\n      };\n      const finishCalled = Promise.withResolvers();\n      w.on('finish', function () {\n        strictEqual(called, true);\n        finishCalled.resolve();\n      });\n      w.write('foo');\n      w.end();\n      await finishCalled.promise;\n    }\n  },\n};\n\nexport const stream2_large_read_stall = {\n  async test(ctrl, env, ctx) {\n    // If everything aligns so that you do a read(n) of exactly the\n    // remaining buffer, then make sure that 'end' still emits.\n\n    const READSIZE = 100;\n    const PUSHSIZE = 20;\n    const PUSHCOUNT = 1000;\n    const HWM = 50;\n    const r = new Readable({\n      highWaterMark: HWM,\n    });\n    const rs = r._readableState;\n    r._read = push;\n    r.on('readable', function () {\n      let ret;\n      do {\n        ret = r.read(READSIZE);\n      } while (ret && ret.length === READSIZE);\n    });\n    const ended = Promise.withResolvers();\n    r.on('end', function () {\n      strictEqual(pushes, PUSHCOUNT + 1);\n      ended.resolve();\n    });\n    let pushes = 0;\n    function push() {\n      if (pushes > PUSHCOUNT) return;\n      if (pushes++ === PUSHCOUNT) {\n        return r.push(null);\n      }\n      if (r.push(Buffer.allocUnsafe(PUSHSIZE))) setTimeout(push, 1);\n    }\n    await ended.promise;\n  },\n};\n\nexport const stream2_decode_partial = {\n  async test(ctrl, env, ctx) {\n    let buf = '';\n    const euro = Buffer.from([0xe2, 0x82, 0xac]);\n    const cent = Buffer.from([0xc2, 0xa2]);\n    const source = Buffer.concat([euro, cent]);\n    const readable = Readable({\n      encoding: 'utf8',\n    });\n    readable.push(source.slice(0, 2));\n    readable.push(source.slice(2, 4));\n    readable.push(source.slice(4, 6));\n    readable.push(null);\n    readable.on('data', function (data) {\n      buf += data;\n    });\n    const closed = Promise.withResolvers();\n    readable.on('close', closed.resolve);\n    await closed.promise;\n\n    strictEqual(buf, '€¢');\n  },\n};\n\nexport const stream2_compatibility = {\n  async test(ctrl, env, ctx) {\n    let ondataCalled = 0;\n    class TestReader extends Readable {\n      constructor() {\n        super();\n        this._buffer = Buffer.alloc(100, 'x');\n        this.on('data', () => {\n          ondataCalled++;\n        });\n      }\n      _read(n) {\n        this.push(this._buffer);\n        this._buffer = Buffer.alloc(0);\n      }\n    }\n    const reader = new TestReader();\n    queueMicrotask(function () {\n      strictEqual(ondataCalled, 1);\n      reader.push(null);\n    });\n    class TestWriter extends Writable {\n      constructor() {\n        super();\n        this.write('foo');\n        this.end();\n      }\n      _write(chunk, enc, cb) {\n        cb();\n      }\n    }\n    const writer = new TestWriter();\n\n    const readerClose = Promise.withResolvers();\n    const writerClose = Promise.withResolvers();\n    reader.on('close', readerClose.resolve);\n    writer.on('close', writerClose.resolve);\n\n    await Promise.all([readerClose.promise, writerClose.promise]);\n\n    strictEqual(reader.readable, false);\n    strictEqual(writer.writable, false);\n  },\n};\n\nexport const stream2_basic = {\n  async test(ctrl, env, ctx) {\n    class TestReader extends Readable {\n      constructor(n) {\n        super();\n        this._buffer = Buffer.alloc(n || 100, 'x');\n        this._pos = 0;\n        this._bufs = 10;\n      }\n      _read(n) {\n        const max = this._buffer.length - this._pos;\n        n = Math.max(n, 0);\n        const toRead = Math.min(n, max);\n        if (toRead === 0) {\n          // Simulate the read buffer filling up with some more bytes some time\n          // in the future.\n          setTimeout(() => {\n            this._pos = 0;\n            this._bufs -= 1;\n            if (this._bufs <= 0) {\n              // read them all!\n              if (!this.ended) this.push(null);\n            } else {\n              // now we have more.\n              // kinda cheating by calling _read, but whatever,\n              // it's just fake anyway.\n              this._read(n);\n            }\n          }, 10);\n          return;\n        }\n        const ret = this._buffer.slice(this._pos, this._pos + toRead);\n        this._pos += toRead;\n        this.push(ret);\n      }\n    }\n\n    class TestWriter extends EventEmitter {\n      constructor() {\n        super();\n        this.received = [];\n        this.flush = false;\n      }\n      write(c) {\n        this.received.push(c.toString());\n        this.emit('write', c);\n        return true;\n      }\n      end(c) {\n        if (c) this.write(c);\n        this.emit('end', this.received);\n      }\n    }\n\n    {\n      // Test basic functionality\n      const r = new TestReader(20);\n      const reads = [];\n      const expect = [\n        'x',\n        'xx',\n        'xxx',\n        'xxxx',\n        'xxxxx',\n        'xxxxxxxxx',\n        'xxxxxxxxxx',\n        'xxxxxxxxxxxx',\n        'xxxxxxxxxxxxx',\n        'xxxxxxxxxxxxxxx',\n        'xxxxxxxxxxxxxxxxx',\n        'xxxxxxxxxxxxxxxxxxx',\n        'xxxxxxxxxxxxxxxxxxxxx',\n        'xxxxxxxxxxxxxxxxxxxxxxx',\n        'xxxxxxxxxxxxxxxxxxxxxxxxx',\n        'xxxxxxxxxxxxxxxxxxxxx',\n      ];\n      r.on('end', function () {\n        deepStrictEqual(reads, expect);\n      });\n      let readSize = 1;\n      function flow() {\n        let res;\n        while (null !== (res = r.read(readSize++))) {\n          reads.push(res.toString());\n        }\n        r.once('readable', flow);\n      }\n      flow();\n\n      await promises.finished(r);\n    }\n    {\n      // Verify pipe\n      const r = new TestReader(5);\n      const expect = [\n        'xxxxx',\n        'xxxxx',\n        'xxxxx',\n        'xxxxx',\n        'xxxxx',\n        'xxxxx',\n        'xxxxx',\n        'xxxxx',\n        'xxxxx',\n        'xxxxx',\n      ];\n      const w = new TestWriter();\n      w.on('end', function (received) {\n        deepStrictEqual(received, expect);\n      });\n      r.pipe(w);\n      await promises.finished(r);\n    }\n    await Promise.all(\n      [1, 2, 3, 4, 5, 6, 7, 8, 9].map(async function (SPLIT) {\n        // Verify unpipe\n        const r = new TestReader(5);\n\n        // Unpipe after 3 writes, then write to another stream instead.\n        let expect = [\n          'xxxxx',\n          'xxxxx',\n          'xxxxx',\n          'xxxxx',\n          'xxxxx',\n          'xxxxx',\n          'xxxxx',\n          'xxxxx',\n          'xxxxx',\n          'xxxxx',\n        ];\n        expect = [expect.slice(0, SPLIT), expect.slice(SPLIT)];\n        const w = [new TestWriter(), new TestWriter()];\n        let writes = SPLIT;\n        w[0].on('write', function () {\n          if (--writes === 0) {\n            r.unpipe();\n            deepStrictEqual(r._readableState.pipes, []);\n            w[0].end();\n            r.pipe(w[1]);\n            deepStrictEqual(r._readableState.pipes, [w[1]]);\n          }\n        });\n        let ended = 0;\n        w[0].on('end', function (results) {\n          ended++;\n          strictEqual(ended, 1);\n          deepStrictEqual(results, expect[0]);\n        });\n        w[1].on('end', function (results) {\n          ended++;\n          strictEqual(ended, 2);\n          deepStrictEqual(results, expect[1]);\n        });\n        r.pipe(w[0]);\n        await promises.finished(r);\n      })\n    );\n    {\n      // Verify both writers get the same data when piping to destinations\n      const r = new TestReader(5);\n      const w = [new TestWriter(), new TestWriter()];\n      const expect = [\n        'xxxxx',\n        'xxxxx',\n        'xxxxx',\n        'xxxxx',\n        'xxxxx',\n        'xxxxx',\n        'xxxxx',\n        'xxxxx',\n        'xxxxx',\n        'xxxxx',\n      ];\n      w[0].on('end', function (received) {\n        deepStrictEqual(received, expect);\n      });\n      w[1].on('end', function (received) {\n        deepStrictEqual(received, expect);\n      });\n      r.pipe(w[0]);\n      r.pipe(w[1]);\n      await promises.finished(r);\n    }\n    await Promise.all(\n      [1, 2, 3, 4, 5, 6, 7, 8, 9].map(async function (SPLIT) {\n        // Verify multi-unpipe\n        const r = new TestReader(5);\n\n        // Unpipe after 3 writes, then write to another stream instead.\n        let expect = [\n          'xxxxx',\n          'xxxxx',\n          'xxxxx',\n          'xxxxx',\n          'xxxxx',\n          'xxxxx',\n          'xxxxx',\n          'xxxxx',\n          'xxxxx',\n          'xxxxx',\n        ];\n        expect = [expect.slice(0, SPLIT), expect.slice(SPLIT)];\n        const w = [new TestWriter(), new TestWriter(), new TestWriter()];\n        let writes = SPLIT;\n        w[0].on('write', function () {\n          if (--writes === 0) {\n            r.unpipe();\n            w[0].end();\n            r.pipe(w[1]);\n          }\n        });\n        let ended = 0;\n        w[0].on('end', function (results) {\n          ended++;\n          strictEqual(ended, 1);\n          deepStrictEqual(results, expect[0]);\n        });\n        w[1].on('end', function (results) {\n          ended++;\n          strictEqual(ended, 2);\n          deepStrictEqual(results, expect[1]);\n        });\n        r.pipe(w[0]);\n        r.pipe(w[2]);\n        await promises.finished(r);\n      })\n    );\n    {\n      // Verify that back pressure is respected\n      const r = new Readable({\n        objectMode: true,\n      });\n      r._read = () => {\n        throw new Error('should not have been called');\n      };\n      let counter = 0;\n      r.push(['one']);\n      r.push(['two']);\n      r.push(['three']);\n      r.push(['four']);\n      r.push(null);\n      const w1 = new Readable();\n      w1.write = function (chunk) {\n        strictEqual(chunk[0], 'one');\n        w1.emit('close');\n        queueMicrotask(function () {\n          r.pipe(w2);\n          r.pipe(w3);\n        });\n      };\n      w1.end = () => {\n        throw new Error('should not have been called');\n      };\n      r.pipe(w1);\n      const expected = ['two', 'two', 'three', 'three', 'four', 'four'];\n      const w2 = new Readable();\n      w2.write = function (chunk) {\n        strictEqual(chunk[0], expected.shift());\n        strictEqual(counter, 0);\n        counter++;\n        if (chunk[0] === 'four') {\n          return true;\n        }\n        setTimeout(function () {\n          counter--;\n          w2.emit('drain');\n        }, 10);\n        return false;\n      };\n      const ended2 = Promise.withResolvers();\n      w2.end = ended2.resolve;\n      const w3 = new Readable();\n      w3.write = function (chunk) {\n        strictEqual(chunk[0], expected.shift());\n        strictEqual(counter, 1);\n        counter++;\n        if (chunk[0] === 'four') {\n          return true;\n        }\n        setTimeout(function () {\n          counter--;\n          w3.emit('drain');\n        }, 50);\n        return false;\n      };\n      const ended3 = Promise.withResolvers();\n      w3.end = function () {\n        strictEqual(counter, 2);\n        strictEqual(expected.length, 0);\n        ended3.resolve();\n      };\n      await Promise.all([ended2.promise, ended3.promise]);\n    }\n    {\n      // Verify read(0) behavior for ended streams\n      const r = new Readable();\n      let written = false;\n      let ended = false;\n      r._read = () => {\n        throw new Error('should not have been called');\n      };\n      r.push(Buffer.from('foo'));\n      r.push(null);\n      const v = r.read(0);\n      strictEqual(v, null);\n      const w = new Readable();\n      const writeCalled = Promise.withResolvers();\n      w.write = function (buffer) {\n        written = true;\n        strictEqual(ended, false);\n        strictEqual(buffer.toString(), 'foo');\n        writeCalled.resolve();\n      };\n      const endCalled = Promise.withResolvers();\n      w.end = function () {\n        ended = true;\n        strictEqual(written, true);\n        endCalled.resolve();\n      };\n      r.pipe(w);\n      await Promise.all([endCalled.promise, writeCalled.promise]);\n    }\n    {\n      // Verify synchronous _read ending\n      const r = new Readable();\n      let called = false;\n      r._read = function (n) {\n        r.push(null);\n      };\n      r.once('end', function () {\n        // Verify that this is called before the next tick\n        called = true;\n      });\n      r.read();\n      queueMicrotask(function () {\n        strictEqual(called, true);\n      });\n    }\n    {\n      // Verify that adding readable listeners trigger data flow\n      const r = new Readable({\n        highWaterMark: 5,\n      });\n      let onReadable = false;\n      let readCalled = 0;\n      r._read = function (n) {\n        if (readCalled++ === 2) r.push(null);\n        else r.push(Buffer.from('asdf'));\n      };\n      r.on('readable', function () {\n        onReadable = true;\n        r.read();\n      });\n      const endCalled = Promise.withResolvers();\n      r.on('end', function () {\n        strictEqual(readCalled, 3);\n        ok(onReadable);\n        endCalled.resolve();\n      });\n      await endCalled.promise;\n    }\n    {\n      // Verify that streams are chainable\n      const r = new Readable();\n      const readCalled = Promise.withResolvers();\n      r._read = readCalled.resolve;\n      const r2 = r.setEncoding('utf8').pause().resume().pause();\n      strictEqual(r, r2);\n      await readCalled.promise;\n    }\n    {\n      // Verify readableEncoding property\n      ok(Reflect.has(Readable.prototype, 'readableEncoding'));\n      const r = new Readable({\n        encoding: 'utf8',\n      });\n      strictEqual(r.readableEncoding, 'utf8');\n    }\n    {\n      // Verify readableObjectMode property\n      ok(Reflect.has(Readable.prototype, 'readableObjectMode'));\n      const r = new Readable({\n        objectMode: true,\n      });\n      strictEqual(r.readableObjectMode, true);\n    }\n    {\n      // Verify writableObjectMode property\n      ok(Reflect.has(Writable.prototype, 'writableObjectMode'));\n      const w = new Writable({\n        objectMode: true,\n      });\n      strictEqual(w.writableObjectMode, true);\n    }\n  },\n};\n\nexport const stream2_base64_single_char_read_end = {\n  async test(ctrl, env, ctx) {\n    const src = new Readable({\n      encoding: 'base64',\n    });\n    const dst = new Writable();\n    let hasRead = false;\n    const accum = [];\n    src._read = function (n) {\n      if (!hasRead) {\n        hasRead = true;\n        queueMicrotask(function () {\n          src.push(Buffer.from('1'));\n          src.push(null);\n        });\n      }\n    };\n    dst._write = function (chunk, enc, cb) {\n      accum.push(chunk);\n      cb();\n    };\n    src.on('end', function () {\n      strictEqual(String(Buffer.concat(accum)), 'MQ==');\n      clearTimeout(timeout);\n    });\n    src.pipe(dst);\n    const timeout = setTimeout(function () {\n      fail('timed out waiting for _write');\n    }, 100);\n  },\n};\n\nexport const writev = {\n  async test(ctrl, env, ctx) {\n    const queue = [];\n    for (let decode = 0; decode < 2; decode++) {\n      for (let uncork = 0; uncork < 2; uncork++) {\n        for (let multi = 0; multi < 2; multi++) {\n          queue.push([!!decode, !!uncork, !!multi]);\n        }\n      }\n    }\n    run();\n    function run() {\n      const t = queue.pop();\n      if (t) test(t[0], t[1], t[2], run);\n    }\n    function test(decode, uncork, multi, next) {\n      let counter = 0;\n      let expectCount = 0;\n      function cnt(msg) {\n        expectCount++;\n        const expect = expectCount;\n        return function (er) {\n          ifError(er);\n          counter++;\n          strictEqual(counter, expect);\n        };\n      }\n      const w = new Writable({\n        decodeStrings: decode,\n      });\n      w._write = () => {\n        throw new Error('Should not call _write');\n      };\n      const expectChunks = decode\n        ? [\n            {\n              encoding: 'buffer',\n              chunk: [104, 101, 108, 108, 111, 44, 32],\n            },\n            {\n              encoding: 'buffer',\n              chunk: [119, 111, 114, 108, 100],\n            },\n            {\n              encoding: 'buffer',\n              chunk: [33],\n            },\n            {\n              encoding: 'buffer',\n              chunk: [10, 97, 110, 100, 32, 116, 104, 101, 110, 46, 46, 46],\n            },\n            {\n              encoding: 'buffer',\n              chunk: [\n                250, 206, 190, 167, 222, 173, 190, 239, 222, 202, 251, 173,\n              ],\n            },\n          ]\n        : [\n            {\n              encoding: 'ascii',\n              chunk: 'hello, ',\n            },\n            {\n              encoding: 'utf8',\n              chunk: 'world',\n            },\n            {\n              encoding: 'buffer',\n              chunk: [33],\n            },\n            {\n              encoding: 'latin1',\n              chunk: '\\nand then...',\n            },\n            {\n              encoding: 'hex',\n              chunk: 'facebea7deadbeefdecafbad',\n            },\n          ];\n      let actualChunks;\n      w._writev = function (chunks, cb) {\n        actualChunks = chunks.map(function (chunk) {\n          return {\n            encoding: chunk.encoding,\n            chunk: Buffer.isBuffer(chunk.chunk)\n              ? Array.prototype.slice.call(chunk.chunk)\n              : chunk.chunk,\n          };\n        });\n        cb();\n      };\n      w.cork();\n      w.write('hello, ', 'ascii', cnt('hello'));\n      w.write('world', 'utf8', cnt('world'));\n      if (multi) w.cork();\n      w.write(Buffer.from('!'), 'buffer', cnt('!'));\n      w.write('\\nand then...', 'latin1', cnt('and then'));\n      if (multi) w.uncork();\n      w.write('facebea7deadbeefdecafbad', 'hex', cnt('hex'));\n      if (uncork) w.uncork();\n      w.end(cnt('end'));\n      w.on('finish', function () {\n        // Make sure finish comes after all the write cb\n        cnt('finish')();\n        deepStrictEqual(actualChunks, expectChunks);\n        next();\n      });\n    }\n    {\n      const writeCalled = Promise.withResolvers();\n      const writeFinished = Promise.withResolvers();\n      const w = new Writable({\n        writev: function (chunks, cb) {\n          cb();\n          writeCalled.resolve();\n        },\n      });\n      w.write('asd', writeFinished.resolve);\n      await Promise.all([writeCalled.promise, writeFinished.promise]);\n    }\n  },\n};\n\nexport const writeFinal = {\n  async test(ctrl, env, ctx) {\n    const finalCalled = Promise.withResolvers();\n    const finishCalled = Promise.withResolvers();\n    let shutdown = false;\n    const w = new Writable({\n      final: function (cb) {\n        strictEqual(this, w);\n        setTimeout(function () {\n          shutdown = true;\n          cb();\n          finalCalled.resolve();\n        }, 100);\n      },\n      write: function (chunk, e, cb) {\n        queueMicrotask(cb);\n      },\n    });\n    w.on('finish', function () {\n      ok(shutdown);\n      finishCalled.resolve();\n    });\n    w.write(Buffer.allocUnsafe(1));\n    w.end(Buffer.allocUnsafe(0));\n    await Promise.all([finalCalled.promise, finishCalled.promise]);\n  },\n};\n\nexport const writeDrain = {\n  async test(ctrl, env, ctx) {\n    const w = new Writable({\n      write(data, enc, cb) {\n        queueMicrotask(cb);\n      },\n      highWaterMark: 1,\n    });\n    w.on('drain', () => {\n      throw new Error('should not be called');\n    });\n    w.write('asd');\n    w.end();\n  },\n};\n\nexport const writeDestroy = {\n  async test(ctrl, env, ctx) {\n    for (const withPendingData of [false, true]) {\n      for (const useEnd of [false, true]) {\n        const callbacks = [];\n        const w = new Writable({\n          write(data, enc, cb) {\n            callbacks.push(cb);\n          },\n          // Effectively disable the HWM to observe 'drain' events more easily.\n          highWaterMark: 1,\n        });\n        let chunksWritten = 0;\n        let drains = 0;\n        w.on('drain', () => drains++);\n        function onWrite(err) {\n          if (err) {\n            strictEqual(w.destroyed, true);\n            strictEqual(err.code, 'ERR_STREAM_DESTROYED');\n          } else {\n            chunksWritten++;\n          }\n        }\n        w.write('abc', onWrite);\n        strictEqual(chunksWritten, 0);\n        strictEqual(drains, 0);\n        callbacks.shift()();\n        strictEqual(chunksWritten, 1);\n        strictEqual(drains, 1);\n        if (withPendingData) {\n          // Test 2 cases: There either is or is not data still in the write queue.\n          // (The second write will never actually get executed either way.)\n          w.write('def', onWrite);\n        }\n        if (useEnd) {\n          // Again, test 2 cases: Either we indicate that we want to end the\n          // writable or not.\n          w.end('ghi', onWrite);\n        } else {\n          w.write('ghi', onWrite);\n        }\n        strictEqual(chunksWritten, 1);\n        w.destroy();\n        strictEqual(chunksWritten, 1);\n        callbacks.shift()();\n        strictEqual(chunksWritten, useEnd && !withPendingData ? 1 : 2);\n        strictEqual(callbacks.length, 0);\n        strictEqual(drains, 1);\n      }\n    }\n  },\n};\n\nexport const writableState_uncorked_bufferedRequestCount = {\n  async test(ctrl, env, ctx) {\n    const writable = new Writable();\n    const writevCalled = Promise.withResolvers();\n    const writeCalled = Promise.withResolvers();\n    writable._writev = (chunks, cb) => {\n      strictEqual(chunks.length, 2);\n      cb();\n      writevCalled.resolve();\n    };\n    writable._write = (chunk, encoding, cb) => {\n      cb();\n      writeCalled.resolve();\n    };\n\n    // first cork\n    writable.cork();\n    strictEqual(writable._writableState.corked, 1);\n    strictEqual(writable._writableState.bufferedRequestCount, 0);\n\n    // cork again\n    writable.cork();\n    strictEqual(writable._writableState.corked, 2);\n\n    // The first chunk is buffered\n    writable.write('first chunk');\n    strictEqual(writable._writableState.bufferedRequestCount, 1);\n\n    // First uncork does nothing\n    writable.uncork();\n    strictEqual(writable._writableState.corked, 1);\n    strictEqual(writable._writableState.bufferedRequestCount, 1);\n    queueMicrotask(uncork);\n\n    // The second chunk is buffered, because we uncork at the end of tick\n    writable.write('second chunk');\n    strictEqual(writable._writableState.corked, 1);\n    strictEqual(writable._writableState.bufferedRequestCount, 2);\n    const uncorkCalled = Promise.withResolvers();\n    function uncork() {\n      // Second uncork flushes the buffer\n      writable.uncork();\n      strictEqual(writable._writableState.corked, 0);\n      strictEqual(writable._writableState.bufferedRequestCount, 0);\n\n      // Verify that end() uncorks correctly\n      writable.cork();\n      writable.write('third chunk');\n      writable.end();\n\n      // End causes an uncork() as well\n      strictEqual(writable._writableState.corked, 0);\n      strictEqual(writable._writableState.bufferedRequestCount, 0);\n      uncorkCalled.resolve();\n    }\n\n    await Promise.all([\n      writevCalled.promise,\n      writeCalled.promise,\n      uncorkCalled.promise,\n    ]);\n  },\n};\n\nexport const writeableState_ending = {\n  async test(ctrl, env, ctx) {\n    const writable = new Writable();\n    function testStates(ending, finished, ended) {\n      strictEqual(writable._writableState.ending, ending);\n      strictEqual(writable._writableState.finished, finished);\n      strictEqual(writable._writableState.ended, ended);\n    }\n    writable._write = (chunk, encoding, cb) => {\n      // Ending, finished, ended start in false.\n      testStates(false, false, false);\n      cb();\n    };\n    writable.on('finish', () => {\n      // Ending, finished, ended = true.\n      testStates(true, true, true);\n    });\n    const result = writable.end('testing function end()', () => {\n      // Ending, finished, ended = true.\n      testStates(true, true, true);\n    });\n\n    // End returns the writable instance\n    strictEqual(result, writable);\n\n    // Ending, ended = true.\n    // finished = false.\n    testStates(true, false, true);\n  },\n};\n\nexport const writable_write_writev_finish = {\n  async test(ctrl, env, ctx) {\n    {\n      const writable = new Writable();\n      const errored = Promise.withResolvers();\n      writable._write = (chunks, encoding, cb) => {\n        cb(new Error('write test error'));\n      };\n      writable.on('finish', errored.reject);\n      writable.on('prefinish', errored.reject);\n      writable.on('error', (er) => {\n        strictEqual(er.message, 'write test error');\n        errored.resolve();\n      });\n      writable.end('test');\n      await errored.promise;\n    }\n    {\n      const writable = new Writable();\n      const errored = Promise.withResolvers();\n      writable._write = (chunks, encoding, cb) => {\n        queueMicrotask(() => cb(new Error('write test error')));\n      };\n      writable.on('finish', errored.reject);\n      writable.on('prefinish', errored.reject);\n      writable.on('error', (er) => {\n        strictEqual(er.message, 'write test error');\n        errored.resolve();\n      });\n      writable.end('test');\n      await errored.promise;\n    }\n    {\n      const writable = new Writable();\n      const errored = Promise.withResolvers();\n      writable._write = (chunks, encoding, cb) => {\n        cb(new Error('write test error'));\n      };\n      writable._writev = (chunks, cb) => {\n        cb(new Error('writev test error'));\n      };\n      writable.on('finish', errored.reject);\n      writable.on('prefinish', errored.reject);\n      writable.on('error', (er) => {\n        strictEqual(er.message, 'writev test error');\n        errored.resolve();\n      });\n      writable.cork();\n      writable.write('test');\n      queueMicrotask(function () {\n        writable.end('test');\n      });\n      await errored.promise;\n    }\n    {\n      const writable = new Writable();\n      const errored = Promise.withResolvers();\n      writable._write = (chunks, encoding, cb) => {\n        queueMicrotask(() => cb(new Error('write test error')));\n      };\n      writable._writev = (chunks, cb) => {\n        queueMicrotask(() => cb(new Error('writev test error')));\n      };\n      writable.on('finish', errored.reject);\n      writable.on('prefinish', errored.reject);\n      writable.on('error', (er) => {\n        strictEqual(er.message, 'writev test error');\n        errored.resolve();\n      });\n      writable.cork();\n      writable.write('test');\n      queueMicrotask(function () {\n        writable.end('test');\n      });\n      await errored.promise;\n    }\n\n    // Regression test for\n    // https://github.com/nodejs/node/issues/13812\n\n    {\n      const rs = new Readable();\n      rs.push('ok');\n      rs.push(null);\n      rs._read = () => {};\n      const ws = new Writable();\n      const errored = Promise.withResolvers();\n      ws.on('finish', errored.reject);\n      ws.on('error', errored.resolve);\n      ws._write = (chunk, encoding, done) => {\n        queueMicrotask(() => done(new Error()));\n      };\n      rs.pipe(ws);\n      await errored.promise;\n    }\n    {\n      const rs = new Readable();\n      rs.push('ok');\n      rs.push(null);\n      rs._read = () => {};\n      const ws = new Writable();\n      const errored = Promise.withResolvers();\n      ws.on('finish', errored.reject);\n      ws.on('error', errored.resolve);\n      ws._write = (chunk, encoding, done) => {\n        done(new Error());\n      };\n      rs.pipe(ws);\n      await errored.promise;\n    }\n    {\n      const w = new Writable();\n      w._write = (chunk, encoding, cb) => {\n        queueMicrotask(cb);\n      };\n      const errored = Promise.withResolvers();\n      w.on('error', errored.resolve);\n      w.on('finish', errored.reject);\n      w.on('prefinish', () => {\n        w.write(\"shouldn't write in prefinish listener\");\n      });\n      w.end();\n      await errored.promise;\n    }\n    {\n      const w = new Writable();\n      w._write = (chunk, encoding, cb) => {\n        queueMicrotask(cb);\n      };\n      const errored = Promise.withResolvers();\n      w.on('error', errored.resolve);\n      w.on('finish', () => {\n        w.write(\"shouldn't write in finish listener\");\n      });\n      w.end();\n      await errored.promise;\n    }\n  },\n};\n\nexport const writable_write_error = {\n  async test(ctrl, env, ctx) {\n    async function expectError(w, args, code, sync) {\n      if (sync) {\n        if (code) {\n          throws(() => w.write(...args), {\n            code,\n          });\n        } else {\n          w.write(...args);\n        }\n      } else {\n        let ticked = false;\n        const writeCalled = Promise.withResolvers();\n        const errorCalled = Promise.withResolvers();\n        w.write(...args, (err) => {\n          strictEqual(ticked, true);\n          strictEqual(err.code, code);\n          writeCalled.resolve();\n        });\n        ticked = true;\n        w.on('error', (err) => {\n          strictEqual(err.code, code);\n          errorCalled.resolve();\n        });\n        await Promise.all([writeCalled.promise, errorCalled.promise]);\n      }\n    }\n    async function test(autoDestroy) {\n      {\n        const w = new Writable({\n          autoDestroy,\n          _write() {},\n        });\n        w.end();\n        await expectError(w, ['asd'], 'ERR_STREAM_WRITE_AFTER_END');\n      }\n      {\n        const w = new Writable({\n          autoDestroy,\n          _write() {},\n        });\n        w.destroy();\n      }\n      {\n        const w = new Writable({\n          autoDestroy,\n          _write() {},\n        });\n        await expectError(w, [null], 'ERR_STREAM_NULL_VALUES', true);\n      }\n      {\n        const w = new Writable({\n          autoDestroy,\n          _write() {},\n        });\n        await expectError(w, [{}], 'ERR_INVALID_ARG_TYPE', true);\n      }\n      {\n        const w = new Writable({\n          decodeStrings: false,\n          autoDestroy,\n          _write() {},\n        });\n        await expectError(\n          w,\n          ['asd', 'noencoding'],\n          'ERR_UNKNOWN_ENCODING',\n          true\n        );\n      }\n    }\n    await test(false);\n    await test(true);\n  },\n};\n\nexport const writable_write_cb_twice = {\n  async test(ctrl, env, ctx) {\n    {\n      // Sync + Sync\n      const writeCalled = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      const writable = new Writable({\n        write: (buf, enc, cb) => {\n          cb();\n          cb();\n          writeCalled.resolve();\n        },\n      });\n      writable.write('hi');\n      writable.on('error', function (err) {\n        strictEqual(err.code, 'ERR_MULTIPLE_CALLBACK');\n        errored.resolve();\n      });\n      await Promise.all([writeCalled.promise, errored.promise]);\n    }\n    {\n      // Sync + Async\n      const writeCalled = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      const writable = new Writable({\n        write: (buf, enc, cb) => {\n          cb();\n          queueMicrotask(() => {\n            cb();\n            writeCalled.resolve();\n          });\n        },\n      });\n      writable.write('hi');\n      writable.on('error', function (err) {\n        strictEqual(err.code, 'ERR_MULTIPLE_CALLBACK');\n        errored.resolve();\n      });\n      await Promise.all([writeCalled.promise, errored.promise]);\n    }\n    {\n      // Async + Async\n      const writeCalled = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      const writable = new Writable({\n        write: (buf, enc, cb) => {\n          queueMicrotask(cb);\n          queueMicrotask(() => {\n            cb();\n            writeCalled.resolve();\n          });\n        },\n      });\n      writable.write('hi');\n      writable.on('error', function (err) {\n        strictEqual(err.code, 'ERR_MULTIPLE_CALLBACK');\n        errored.resolve();\n      });\n      await Promise.all([writeCalled.promise, errored.promise]);\n    }\n  },\n};\n\nexport const writable_write_cb_error = {\n  async test(ctrl, env, ctx) {\n    {\n      let callbackCalled = false;\n      // Sync Error\n      const writeCalled = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      const writeFinished = Promise.withResolvers();\n      const writable = new Writable({\n        write: (buf, enc, cb) => {\n          cb(new Error());\n          writeCalled.resolve();\n        },\n      });\n      writable.on('error', () => {\n        strictEqual(callbackCalled, true);\n        errored.resolve();\n      });\n      writable.write('hi', () => {\n        callbackCalled = true;\n        writeFinished.resolve();\n      });\n      await Promise.all([\n        writeCalled.promise,\n        errored.promise,\n        writeFinished.promise,\n      ]);\n    }\n    {\n      let callbackCalled = false;\n      // Async Error\n      const writeCalled = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      const writeFinished = Promise.withResolvers();\n      const writable = new Writable({\n        write: (buf, enc, cb) => {\n          queueMicrotask(() => cb(new Error()));\n          writeCalled.resolve();\n        },\n      });\n      writable.on('error', () => {\n        strictEqual(callbackCalled, true);\n        errored.resolve();\n      });\n      writable.write('hi', () => {\n        callbackCalled = true;\n        writeFinished.resolve();\n      });\n      await Promise.all([\n        writeCalled.promise,\n        errored.promise,\n        writeFinished.promise,\n      ]);\n    }\n    {\n      // Sync Error\n      const errored = Promise.withResolvers();\n      const writeCalled = Promise.withResolvers();\n      const writable = new Writable({\n        write: (buf, enc, cb) => {\n          cb(new Error());\n          writeCalled.resolve();\n        },\n      });\n      writable.on('error', errored.resolve);\n      let cnt = 0;\n      // Ensure we don't live lock on sync error\n      while (writable.write('a')) cnt++;\n      strictEqual(cnt, 0);\n      await Promise.all([writeCalled.promise, errored.promise]);\n    }\n  },\n};\n\nexport const writable_writable = {\n  async test(ctrl, env, ctx) {\n    {\n      const w = new Writable({\n        write() {},\n      });\n      strictEqual(w.writable, true);\n      w.destroy();\n      strictEqual(w.writable, false);\n    }\n    {\n      const writeCalled = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      const w = new Writable({\n        write: (chunk, encoding, callback) => {\n          callback(new Error());\n          writeCalled.resolve();\n        },\n      });\n      strictEqual(w.writable, true);\n      w.write('asd');\n      strictEqual(w.writable, false);\n      w.on('error', errored.resolve);\n      await Promise.all([writeCalled.promise, errored.promise]);\n    }\n    {\n      const writeCalled = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      const w = new Writable({\n        write: (chunk, encoding, callback) => {\n          queueMicrotask(() => {\n            callback(new Error());\n            strictEqual(w.writable, false);\n            writeCalled.resolve();\n          });\n        },\n      });\n      w.write('asd');\n      w.on('error', errored.resolve);\n      await Promise.all([writeCalled.promise, errored.promise]);\n    }\n    {\n      const closed = Promise.withResolvers();\n      const w = new Writable({\n        write: closed.reject,\n      });\n      w.on('close', closed.resolve);\n      strictEqual(w.writable, true);\n      w.end();\n      strictEqual(w.writable, false);\n      await closed.promise;\n    }\n  },\n};\n\nexport const writable_properties = {\n  test(ctrl, env, ctx) {\n    {\n      const w = new Writable();\n      strictEqual(w.writableCorked, 0);\n      w.uncork();\n      strictEqual(w.writableCorked, 0);\n      w.cork();\n      strictEqual(w.writableCorked, 1);\n      w.cork();\n      strictEqual(w.writableCorked, 2);\n      w.uncork();\n      strictEqual(w.writableCorked, 1);\n      w.uncork();\n      strictEqual(w.writableCorked, 0);\n      w.uncork();\n      strictEqual(w.writableCorked, 0);\n    }\n  },\n};\n\nexport const writable_null = {\n  async test(ctrl, env, ctx) {\n    class MyWritable extends Writable {\n      constructor(options) {\n        super({\n          autoDestroy: false,\n          ...options,\n        });\n      }\n      _write(chunk, encoding, callback) {\n        notStrictEqual(chunk, null);\n        callback();\n      }\n    }\n    {\n      const m = new MyWritable({\n        objectMode: true,\n      });\n      m.on('error', () => {\n        throw new Error('should not be called');\n      });\n      throws(\n        () => {\n          m.write(null);\n        },\n        {\n          code: 'ERR_STREAM_NULL_VALUES',\n        }\n      );\n    }\n    {\n      const m = new MyWritable();\n      m.on('error', () => {\n        throw new Error('should not be called');\n      });\n      throws(\n        () => {\n          m.write(false);\n        },\n        {\n          code: 'ERR_INVALID_ARG_TYPE',\n        }\n      );\n    }\n    {\n      // Should not throw.\n      const m = new MyWritable({\n        objectMode: true,\n      });\n      m.write(false, ifError);\n    }\n    {\n      // Should not throw.\n      const m = new MyWritable({\n        objectMode: true,\n      }).on('error', (e) => {\n        ifError(e || new Error('should not get here'));\n      });\n      m.write(false, ifError);\n    }\n  },\n};\n\nexport const writable_needdrain_state = {\n  async test(ctrl, env, ctx) {\n    const transform = new Transform({\n      transform: _transform,\n      highWaterMark: 1,\n    });\n    const transformCalled = Promise.withResolvers();\n    const writeFinished = Promise.withResolvers();\n    function _transform(chunk, encoding, cb) {\n      queueMicrotask(() => {\n        strictEqual(transform._writableState.needDrain, true);\n        cb();\n        transformCalled.resolve();\n      });\n    }\n    strictEqual(transform._writableState.needDrain, false);\n    transform.write('asdasd', () => {\n      strictEqual(transform._writableState.needDrain, false);\n      writeFinished.resolve();\n    });\n    strictEqual(transform._writableState.needDrain, true);\n    await Promise.all([transformCalled.promise, writeFinished.promise]);\n  },\n};\n\nexport const writable_invalid_chunk = {\n  test(ctrl, env, ctx) {\n    function testWriteType(val, objectMode, code) {\n      const writable = new Writable({\n        objectMode,\n        write: () => {},\n      });\n      writable.on('error', () => {\n        throw new Error('should not have been called');\n      });\n      if (code) {\n        throws(\n          () => {\n            writable.write(val);\n          },\n          {\n            code,\n          }\n        );\n      } else {\n        writable.write(val);\n      }\n    }\n    testWriteType([], false, 'ERR_INVALID_ARG_TYPE');\n    testWriteType({}, false, 'ERR_INVALID_ARG_TYPE');\n    testWriteType(0, false, 'ERR_INVALID_ARG_TYPE');\n    testWriteType(true, false, 'ERR_INVALID_ARG_TYPE');\n    testWriteType(0.0, false, 'ERR_INVALID_ARG_TYPE');\n    testWriteType(undefined, false, 'ERR_INVALID_ARG_TYPE');\n    testWriteType(null, false, 'ERR_STREAM_NULL_VALUES');\n    testWriteType([], true);\n    testWriteType({}, true);\n    testWriteType(0, true);\n    testWriteType(true, true);\n    testWriteType(0.0, true);\n    testWriteType(undefined, true);\n    testWriteType(null, true, 'ERR_STREAM_NULL_VALUES');\n  },\n};\n\nexport const writable_finished = {\n  async test(ctrl, env, ctx) {\n    // basic\n    {\n      // Find it on Writable.prototype\n      ok(Reflect.has(Writable.prototype, 'writableFinished'));\n    }\n\n    // event\n    {\n      const writable = new Writable();\n      writable._write = (chunk, encoding, cb) => {\n        // The state finished should start in false.\n        strictEqual(writable.writableFinished, false);\n        cb();\n      };\n      const finishCalled = Promise.withResolvers();\n      const endCalled = Promise.withResolvers();\n      writable.on('finish', () => {\n        strictEqual(writable.writableFinished, true);\n        finishCalled.resolve();\n      });\n      writable.end('testing finished state', () => {\n        strictEqual(writable.writableFinished, true);\n        endCalled.resolve();\n      });\n      await Promise.all([finishCalled.promise, endCalled.promise]);\n    }\n    {\n      // Emit finish asynchronously.\n\n      const w = new Writable({\n        write(chunk, encoding, cb) {\n          cb();\n        },\n      });\n      w.end();\n      const finishCalled = Promise.withResolvers();\n      w.on('finish', finishCalled.resolve);\n      await finishCalled.promise;\n    }\n    {\n      // Emit prefinish synchronously.\n\n      const w = new Writable({\n        write(chunk, encoding, cb) {\n          cb();\n        },\n      });\n      let sync = true;\n      w.on('prefinish', () => {\n        strictEqual(sync, true);\n      });\n      w.end();\n      sync = false;\n    }\n    {\n      // Emit prefinish synchronously w/ final.\n\n      const w = new Writable({\n        write(chunk, encoding, cb) {\n          cb();\n        },\n        final(cb) {\n          cb();\n        },\n      });\n      let sync = true;\n      w.on('prefinish', () => {\n        strictEqual(sync, true);\n      });\n      w.end();\n      sync = false;\n    }\n    {\n      // Call _final synchronously.\n\n      let sync = true;\n      const w = new Writable({\n        write(chunk, encoding, cb) {\n          cb();\n        },\n        final: (cb) => {\n          strictEqual(sync, true);\n          cb();\n        },\n      });\n      w.end();\n      sync = false;\n    }\n  },\n};\n\nexport const writable_finished_state = {\n  async test(ctrl, env, ctx) {\n    const writable = new Writable();\n    writable._write = (chunk, encoding, cb) => {\n      // The state finished should start in false.\n      strictEqual(writable._writableState.finished, false);\n      cb();\n    };\n    const finishCalled = Promise.withResolvers();\n    const endCalled = Promise.withResolvers();\n    writable.on('finish', () => {\n      strictEqual(writable._writableState.finished, true);\n      finishCalled.resolve();\n    });\n    writable.end('testing finished state', () => {\n      strictEqual(writable._writableState.finished, true);\n      endCalled.resolve();\n    });\n    await Promise.all([finishCalled.promise, endCalled.promise]);\n  },\n};\n\nexport const writable_finish_destroyed = {\n  async test(ctrl, env, ctx) {\n    {\n      const writeCalled = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      const w = new Writable({\n        write: (chunk, encoding, cb) => {\n          w.on('close', () => {\n            cb();\n            writeCalled.resolve();\n          });\n        },\n      });\n      w.on('close', closed.resolve);\n      w.on('finish', closed.reject);\n      w.end('asd');\n      w.destroy();\n      await Promise.all([writeCalled.promise, closed.promise]);\n    }\n    {\n      const writeCalled = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      const w = new Writable({\n        write: (chunk, encoding, cb) => {\n          w.on('close', () => {\n            cb();\n            w.end();\n            writeCalled.resolve();\n          });\n        },\n      });\n      w.on('finish', closed.reject);\n      w.on('close', closed.resolve);\n      w.write('asd');\n      w.destroy();\n      await Promise.all([writeCalled.promise, closed.promise]);\n    }\n    {\n      const w = new Writable({\n        write() {},\n      });\n      const closed = Promise.withResolvers();\n      w.on('finish', closed.reject);\n      w.on('close', closed.resolve);\n      w.end();\n      w.destroy();\n      await closed.promise;\n    }\n  },\n};\n\nexport const writable_final_throw = {\n  async test(ctrl, env, ctx) {\n    class Foo extends Duplex {\n      _final(callback) {\n        throw new Error('fhqwhgads');\n      }\n      _read() {}\n    }\n    const writeCalled = Promise.withResolvers();\n    const endFinished = Promise.withResolvers();\n    const errored = Promise.withResolvers();\n    const foo = new Foo();\n    foo._write = (chunk, encoding, cb) => {\n      cb();\n      writeCalled.resolve();\n    };\n    foo.end('test', function (err) {\n      strictEqual(err.message, 'fhqwhgads');\n      endFinished.resolve();\n    });\n    foo.on('error', errored.resolve);\n    await Promise.all([\n      writeCalled.promise,\n      endFinished.promise,\n      errored.promise,\n    ]);\n  },\n};\n\nexport const writable_final_destroy = {\n  async test(ctrl, env, ctx) {\n    const w = new Writable({\n      write(chunk, encoding, callback) {\n        callback(null);\n      },\n      final(callback) {\n        queueMicrotask(callback);\n      },\n    });\n    const closed = Promise.withResolvers();\n    w.end();\n    w.destroy();\n    w.on('prefinish', closed.reject);\n    w.on('finish', closed.reject);\n    w.on('close', closed.resolve);\n    await closed.promise;\n  },\n};\n\nexport const writable_final_async = {\n  async test(ctrl, env, ctx) {\n    {\n      class Foo extends Duplex {\n        async _final(callback) {\n          await scheduler.wait(10);\n          callback();\n        }\n        _read() {}\n      }\n      const foo = new Foo();\n      const writeCalled = Promise.withResolvers();\n      const endCalled = Promise.withResolvers();\n      foo._write = (chunk, encoding, cb) => {\n        cb();\n        writeCalled.resolve();\n      };\n      foo.end('test', endCalled.resolve);\n      foo.on('error', endCalled.reject);\n      await Promise.all([endCalled.promise, writeCalled.promise]);\n    }\n  },\n};\n\nexport const writable_ended_state = {\n  async test(ctrl, env, ctx) {\n    const writable = new Writable();\n    const writeCalled = Promise.withResolvers();\n    const endCalled = Promise.withResolvers();\n    writable._write = (chunk, encoding, cb) => {\n      strictEqual(writable._writableState.ended, false);\n      strictEqual(writable._writableState.writable, undefined);\n      strictEqual(writable.writableEnded, false);\n      cb();\n      writeCalled.resolve();\n    };\n    strictEqual(writable._writableState.ended, false);\n    strictEqual(writable._writableState.writable, undefined);\n    strictEqual(writable.writable, true);\n    strictEqual(writable.writableEnded, false);\n    writable.end('testing ended state', () => {\n      strictEqual(writable._writableState.ended, true);\n      strictEqual(writable._writableState.writable, undefined);\n      strictEqual(writable.writable, false);\n      strictEqual(writable.writableEnded, true);\n      endCalled.resolve();\n    });\n    strictEqual(writable._writableState.ended, true);\n    strictEqual(writable._writableState.writable, undefined);\n    strictEqual(writable.writable, false);\n    strictEqual(writable.writableEnded, true);\n    await Promise.all([writeCalled.promise, endCalled.promise]);\n  },\n};\n\nexport const writable_end_multiple = {\n  async test(ctrl, env, ctx) {\n    const writable = new Writable();\n    writable._write = (chunk, encoding, cb) => {\n      setTimeout(() => cb(), 10);\n    };\n    const endCalled1 = Promise.withResolvers();\n    const endCalled2 = Promise.withResolvers();\n    const finishCalled = Promise.withResolvers();\n    writable.end('testing ended state', endCalled1.resolve);\n    writable.end(endCalled2.resolve);\n    writable.on('finish', () => {\n      let ticked = false;\n      writable.end((err) => {\n        strictEqual(ticked, true);\n        strictEqual(err.code, 'ERR_STREAM_ALREADY_FINISHED');\n        finishCalled.resolve();\n      });\n      ticked = true;\n    });\n    await Promise.all([\n      endCalled1.promise,\n      endCalled2.promise,\n      finishCalled.promise,\n    ]);\n  },\n};\n\nexport const writable_end_cb_error = {\n  async test(ctrl, env, ctx) {\n    {\n      // Invoke end callback on failure.\n      const writable = new Writable();\n      const _err = new Error('kaboom');\n      writable._write = (chunk, encoding, cb) => {\n        queueMicrotask(() => cb(_err));\n      };\n      const errored = Promise.withResolvers();\n      const endCalled1 = Promise.withResolvers();\n      const endCalled2 = Promise.withResolvers();\n      writable.on('error', (err) => {\n        strictEqual(err, _err);\n        errored.resolve();\n      });\n      writable.write('asd');\n      writable.end((err) => {\n        strictEqual(err, _err);\n        endCalled1.resolve();\n      });\n      writable.end((err) => {\n        strictEqual(err, _err);\n        endCalled2.resolve();\n      });\n      await Promise.all([\n        errored.promise,\n        endCalled1.promise,\n        endCalled2.promise,\n      ]);\n    }\n    {\n      // Don't invoke end callback twice\n      const writable = new Writable();\n      writable._write = (chunk, encoding, cb) => {\n        queueMicrotask(cb);\n      };\n      let called = false;\n      const endCalled = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      const finishCalled = Promise.withResolvers();\n      writable.end('asd', (err) => {\n        called = true;\n        strictEqual(err, null);\n        endCalled.resolve();\n      });\n      writable.on('error', (err) => {\n        strictEqual(err.message, 'kaboom');\n        errored.resolve();\n      });\n      writable.on('finish', () => {\n        strictEqual(called, true);\n        writable.emit('error', new Error('kaboom'));\n        finishCalled.resolve();\n      });\n      await Promise.all([\n        endCalled.promise,\n        errored.promise,\n        finishCalled.promise,\n      ]);\n    }\n    {\n      const w = new Writable({\n        write(chunk, encoding, callback) {\n          queueMicrotask(callback);\n        },\n        finish(callback) {\n          queueMicrotask(callback);\n        },\n      });\n      const endCalled1 = Promise.withResolvers();\n      const endCalled2 = Promise.withResolvers();\n      const endCalled3 = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      w.end('testing ended state', (err) => {\n        strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END');\n        endCalled1.resolve();\n      });\n      strictEqual(w.destroyed, false);\n      strictEqual(w.writableEnded, true);\n      w.end((err) => {\n        strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END');\n        endCalled2.resolve();\n      });\n      strictEqual(w.destroyed, false);\n      strictEqual(w.writableEnded, true);\n      w.end('end', (err) => {\n        strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END');\n        endCalled3.resolve();\n      });\n      strictEqual(w.destroyed, true);\n      w.on('error', (err) => {\n        strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END');\n        errored.resolve();\n      });\n      w.on('finish', errored.reject);\n      await Promise.all([\n        endCalled1.promise,\n        endCalled2.promise,\n        endCalled3.promise,\n        errored.promise,\n      ]);\n    }\n  },\n};\n\nexport const writable_destroy = {\n  async test(ctrl, env, ctx) {\n    {\n      const write = new Writable({\n        write(chunk, enc, cb) {\n          cb();\n        },\n      });\n      const closed = Promise.withResolvers();\n      write.on('finish', closed.reject);\n      write.on('close', closed.resolve);\n      write.destroy();\n      strictEqual(write.destroyed, true);\n      await closed.promise;\n    }\n    {\n      const write = new Writable({\n        write(chunk, enc, cb) {\n          this.destroy(new Error('asd'));\n          cb();\n        },\n      });\n      const errored = Promise.withResolvers();\n      write.on('error', errored.resolve);\n      write.on('finish', errored.reject);\n      write.end('asd');\n      strictEqual(write.destroyed, true);\n      await errored.promise;\n    }\n    {\n      const write = new Writable({\n        write(chunk, enc, cb) {\n          cb();\n        },\n      });\n      const expected = new Error('kaboom');\n      const errored = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      write.on('finish', closed.reject);\n      write.on('close', closed.resolve);\n      write.on('error', (err) => {\n        strictEqual(err, expected);\n        errored.resolve();\n      });\n      write.destroy(expected);\n      strictEqual(write.destroyed, true);\n      await Promise.all([errored.promise, closed.promise]);\n    }\n    {\n      const write = new Writable({\n        write(chunk, enc, cb) {\n          cb();\n        },\n      });\n      write._destroy = function (err, cb) {\n        strictEqual(err, expected);\n        cb(err);\n      };\n      const errored = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      const expected = new Error('kaboom');\n      write.on('finish', closed.reject);\n      write.on('close', closed.resolve);\n      write.on('error', (err) => {\n        strictEqual(err, expected);\n        errored.resolve();\n      });\n      write.destroy(expected);\n      strictEqual(write.destroyed, true);\n      await Promise.all([closed.promise, errored.promise]);\n    }\n    {\n      const closed = Promise.withResolvers();\n      const destroyCalled = Promise.withResolvers();\n      const write = new Writable({\n        write(chunk, enc, cb) {\n          cb();\n        },\n        destroy: function (err, cb) {\n          strictEqual(err, expected);\n          cb();\n          destroyCalled.resolve();\n        },\n      });\n      const expected = new Error('kaboom');\n      write.on('finish', closed.reject);\n      write.on('close', closed.resolve);\n\n      // Error is swallowed by the custom _destroy\n      write.on('error', closed.reject);\n      write.destroy(expected);\n      strictEqual(write.destroyed, true);\n      await Promise.all([destroyCalled.promise, closed.promise]);\n    }\n    {\n      const write = new Writable({\n        write(chunk, enc, cb) {\n          cb();\n        },\n      });\n      const destroyCalled = Promise.withResolvers();\n      write._destroy = function (err, cb) {\n        strictEqual(err, null);\n        cb();\n        destroyCalled.resolve();\n      };\n      write.destroy();\n      strictEqual(write.destroyed, true);\n      await destroyCalled.promise;\n    }\n    {\n      const write = new Writable({\n        write(chunk, enc, cb) {\n          cb();\n        },\n      });\n      const destroyCalled = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      write._destroy = function (err, cb) {\n        strictEqual(err, null);\n        queueMicrotask(() => {\n          this.end();\n          cb();\n          destroyCalled.resolve();\n        });\n      };\n      write.on('finish', closed.reject);\n      write.on('close', closed.resolve);\n      write.destroy();\n      strictEqual(write.destroyed, true);\n      await Promise.all([destroyCalled.promise, closed.promise]);\n    }\n    {\n      const write = new Writable({\n        write(chunk, enc, cb) {\n          cb();\n        },\n      });\n      const expected = new Error('kaboom');\n      const destroyCalled = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      write._destroy = function (err, cb) {\n        strictEqual(err, null);\n        cb(expected);\n        destroyCalled.resolve();\n      };\n      write.on('close', closed.resolve);\n      write.on('finish', closed.reject);\n      write.on('error', (err) => {\n        strictEqual(err, expected);\n        errored.resolve();\n      });\n      write.destroy();\n      strictEqual(write.destroyed, true);\n      await Promise.all([\n        destroyCalled.promise,\n        closed.promise,\n        errored.promise,\n      ]);\n    }\n    {\n      // double error case\n      const write = new Writable({\n        write(chunk, enc, cb) {\n          cb();\n        },\n      });\n      let ticked = false;\n      const writeFinished = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      write.on('close', () => {\n        strictEqual(ticked, true);\n        writeFinished.resolve();\n      });\n      write.on('error', (err) => {\n        strictEqual(ticked, true);\n        strictEqual(err.message, 'kaboom 1');\n        strictEqual(write._writableState.errorEmitted, true);\n        errored.resolve();\n      });\n      const expected = new Error('kaboom 1');\n      write.destroy(expected);\n      write.destroy(new Error('kaboom 2'));\n      strictEqual(write._writableState.errored, expected);\n      strictEqual(write._writableState.errorEmitted, false);\n      strictEqual(write.destroyed, true);\n      ticked = true;\n      await Promise.all([writeFinished.promise, errored.promise]);\n    }\n    {\n      const writable = new Writable({\n        destroy: function (err, cb) {\n          queueMicrotask(() => cb(new Error('kaboom 1')));\n        },\n        write(chunk, enc, cb) {\n          cb();\n        },\n      });\n      let ticked = false;\n      const closed = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      writable.on('close', () => {\n        writable.on('error', () => {\n          throw new Error('should not have been called');\n        });\n        writable.destroy(new Error('hello'));\n        strictEqual(ticked, true);\n        strictEqual(writable._writableState.errorEmitted, true);\n        closed.resolve();\n      });\n      writable.on('error', (err) => {\n        strictEqual(ticked, true);\n        strictEqual(err.message, 'kaboom 1');\n        strictEqual(writable._writableState.errorEmitted, true);\n        errored.resolve();\n      });\n      writable.destroy();\n      strictEqual(writable.destroyed, true);\n      strictEqual(writable._writableState.errored, null);\n      strictEqual(writable._writableState.errorEmitted, false);\n\n      // Test case where `writable.destroy()` is called again with an error before\n      // the `_destroy()` callback is called.\n      writable.destroy(new Error('kaboom 2'));\n      strictEqual(writable._writableState.errorEmitted, false);\n      strictEqual(writable._writableState.errored, null);\n      ticked = true;\n\n      await Promise.all([closed.promise, errored.promise]);\n    }\n    {\n      const write = new Writable({\n        write(chunk, enc, cb) {\n          cb();\n        },\n      });\n      write.destroyed = true;\n      strictEqual(write.destroyed, true);\n\n      // The internal destroy() mechanism should not be triggered\n      write.on('close', () => {\n        throw new Error('should not have been called');\n      });\n      write.destroy();\n    }\n    {\n      function MyWritable() {\n        strictEqual(this.destroyed, false);\n        this.destroyed = false;\n        Writable.call(this);\n      }\n      Object.setPrototypeOf(MyWritable.prototype, Writable.prototype);\n      Object.setPrototypeOf(MyWritable, Writable);\n      new MyWritable();\n    }\n    {\n      // Destroy and destroy callback\n      const write = new Writable({\n        write(chunk, enc, cb) {\n          cb();\n        },\n      });\n      write.destroy();\n      const expected = new Error('kaboom');\n      const destroyed = Promise.withResolvers();\n      write.destroy(expected, (err) => {\n        strictEqual(err, undefined);\n        destroyed.resolve();\n      });\n      await destroyed.promise;\n    }\n    {\n      // Checks that `._undestroy()` restores the state so that `final` will be\n      // called again.\n      const writeCalled = Promise.withResolvers();\n      const finalCalled = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      let finalCounter = 0;\n      const write = new Writable({\n        write: writeCalled.resolve(),\n        final: (cb) => {\n          cb();\n          if (++finalCounter === 2) finalCalled.resolve();\n        },\n        autoDestroy: true,\n      });\n      write.end();\n      write.once('close', () => {\n        write._undestroy();\n        write.end();\n        closed.resolve();\n      });\n      await Promise.all([\n        writeCalled.promise,\n        finalCalled.promise,\n        closed.promise,\n      ]);\n    }\n    {\n      const write = new Writable();\n      write.destroy();\n      const writeFinished = Promise.withResolvers();\n      write.on('error', writeFinished.reject);\n      write.write('asd', function (err) {\n        strictEqual(err.code, 'ERR_STREAM_DESTROYED');\n        writeFinished.resolve();\n      });\n      await writeFinished.promise;\n    }\n    {\n      const write = new Writable({\n        write(chunk, enc, cb) {\n          cb();\n        },\n      });\n      const writeFinished1 = Promise.withResolvers();\n      const writeFinished2 = Promise.withResolvers();\n      const writeFinished3 = Promise.withResolvers();\n      write.on('error', writeFinished1.reject);\n      write.cork();\n      write.write('asd', writeFinished1.resolve);\n      write.uncork();\n      write.cork();\n      write.write('asd', function (err) {\n        strictEqual(err.code, 'ERR_STREAM_DESTROYED');\n        writeFinished2.resolve();\n      });\n      write.destroy();\n      write.write('asd', function (err) {\n        strictEqual(err.code, 'ERR_STREAM_DESTROYED');\n        writeFinished3.resolve();\n      });\n      write.uncork();\n      await Promise.all([\n        writeFinished1.promise,\n        writeFinished2.promise,\n        writeFinished3.promise,\n      ]);\n    }\n    {\n      // Call end(cb) after error & destroy\n\n      const write = new Writable({\n        write(chunk, enc, cb) {\n          cb(new Error('asd'));\n        },\n      });\n      const errored = Promise.withResolvers();\n      write.on('error', () => {\n        write.destroy();\n        let ticked = false;\n        write.end((err) => {\n          strictEqual(ticked, true);\n          strictEqual(err.code, 'ERR_STREAM_DESTROYED');\n          errored.resolve();\n        });\n        ticked = true;\n      });\n      write.write('asd');\n      await errored.promise;\n    }\n    {\n      // Call end(cb) after finish & destroy\n\n      const write = new Writable({\n        write(chunk, enc, cb) {\n          cb();\n        },\n      });\n      const finishCalled = Promise.withResolvers();\n      write.on('finish', () => {\n        write.destroy();\n        let ticked = false;\n        write.end((err) => {\n          strictEqual(ticked, true);\n          strictEqual(err.code, 'ERR_STREAM_ALREADY_FINISHED');\n          finishCalled.resolve();\n        });\n        ticked = true;\n      });\n      write.end();\n      await finishCalled.promise;\n    }\n    {\n      // Call end(cb) after error & destroy and don't trigger\n      // unhandled exception.\n\n      const write = new Writable({\n        write(chunk, enc, cb) {\n          queueMicrotask(cb);\n        },\n      });\n      const _err = new Error('asd');\n      const errored = Promise.withResolvers();\n      const ended = Promise.withResolvers();\n      write.once('error', (err) => {\n        strictEqual(err.message, 'asd');\n        errored.resolve();\n      });\n      write.end('asd', (err) => {\n        strictEqual(err, _err);\n        ended.resolve();\n      });\n      write.destroy(_err);\n      await Promise.all([errored.promise, ended.promise]);\n    }\n    {\n      // Call buffered write callback with error\n\n      const _err = new Error('asd');\n      const write = new Writable({\n        write(chunk, enc, cb) {\n          queueMicrotask(() => cb(_err));\n        },\n        autoDestroy: false,\n      });\n      write.cork();\n      const writeFinished1 = Promise.withResolvers();\n      const writeFinished2 = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      write.write('asd', (err) => {\n        strictEqual(err, _err);\n        writeFinished1.resolve();\n      });\n      write.write('asd', (err) => {\n        strictEqual(err, _err);\n        writeFinished2.resolve();\n      });\n      write.on('error', (err) => {\n        strictEqual(err, _err);\n        errored.resolve();\n      });\n      write.uncork();\n      await Promise.all([\n        writeFinished1.promise,\n        writeFinished2.promise,\n        errored.promise,\n      ]);\n    }\n    {\n      // Ensure callback order.\n\n      let state = 0;\n      const write = new Writable({\n        write(chunk, enc, cb) {\n          // `queueMicrotask()` is used on purpose to ensure the callback is called\n          // after `queueMicrotask()` callbacks.\n          queueMicrotask(cb);\n        },\n      });\n      const writeFinished1 = Promise.withResolvers();\n      const writeFinished2 = Promise.withResolvers();\n      write.write('asd', () => {\n        strictEqual(state++, 0);\n        writeFinished1.resolve();\n      });\n      write.write('asd', (err) => {\n        strictEqual(err.code, 'ERR_STREAM_DESTROYED');\n        strictEqual(state++, 1);\n        writeFinished2.resolve();\n      });\n      write.destroy();\n      await Promise.all([writeFinished1.promise, writeFinished2.promise]);\n    }\n    {\n      const write = new Writable({\n        autoDestroy: false,\n        write(chunk, enc, cb) {\n          cb();\n          cb();\n        },\n      });\n      const errored = Promise.withResolvers();\n      write.on('error', () => {\n        ok(write._writableState.errored);\n        errored.resolve();\n      });\n      write.write('asd');\n      await errored.promise;\n    }\n    {\n      const ac = new AbortController();\n      const write = addAbortSignal(\n        ac.signal,\n        new Writable({\n          write(chunk, enc, cb) {\n            cb();\n          },\n        })\n      );\n      const errored = Promise.withResolvers();\n      write.on('error', (e) => {\n        strictEqual(e.name, 'AbortError');\n        strictEqual(write.destroyed, true);\n        errored.resolve();\n      });\n      write.write('asd');\n      ac.abort();\n      await errored.promise;\n    }\n    {\n      const ac = new AbortController();\n      const write = new Writable({\n        signal: ac.signal,\n        write(chunk, enc, cb) {\n          cb();\n        },\n      });\n      const errored = Promise.withResolvers();\n      write.on('error', (e) => {\n        strictEqual(e.name, 'AbortError');\n        strictEqual(write.destroyed, true);\n        errored.resolve();\n      });\n      write.write('asd');\n      ac.abort();\n      await errored.promise;\n    }\n    {\n      const signal = AbortSignal.abort();\n      const write = new Writable({\n        signal,\n        write(chunk, enc, cb) {\n          cb();\n        },\n      });\n      const errored = Promise.withResolvers();\n      write.on('error', (e) => {\n        strictEqual(e.name, 'AbortError');\n        strictEqual(write.destroyed, true);\n        errored.resolve();\n      });\n      await errored.promise;\n    }\n    {\n      // Destroy twice\n      const write = new Writable({\n        write(chunk, enc, cb) {\n          cb();\n        },\n      });\n      const ended = Promise.withResolvers();\n      write.end(ended.resolve);\n      write.destroy();\n      write.destroy();\n      await ended.promise;\n    }\n    {\n      // https://github.com/nodejs/node/issues/39356\n      const s = new Writable({\n        final() {},\n      });\n      const _err = new Error('oh no');\n      // Remove `callback` and it works\n      const ended = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      s.end((err) => {\n        strictEqual(err, _err);\n        ended.resolve();\n      });\n      s.on('error', (err) => {\n        strictEqual(err, _err);\n        errored.resolve();\n      });\n      s.destroy(_err);\n      await Promise.all([errored.promise, ended.promise]);\n    }\n  },\n};\n\nexport const writable_decoded_encoding = {\n  async test(ctrl, env, ctx) {\n    class MyWritable extends Writable {\n      constructor(fn, options) {\n        super(options);\n        this.fn = fn;\n      }\n      _write(chunk, encoding, callback) {\n        this.fn(Buffer.isBuffer(chunk), typeof chunk, encoding);\n        callback();\n      }\n    }\n    {\n      const m = new MyWritable(\n        function (isBuffer, type, enc) {\n          ok(isBuffer);\n          strictEqual(type, 'object');\n          strictEqual(enc, 'buffer');\n        },\n        {\n          decodeStrings: true,\n        }\n      );\n      m.write('some-text', 'utf8');\n      m.end();\n      await promises.finished(m);\n    }\n    {\n      const m = new MyWritable(\n        function (isBuffer, type, enc) {\n          ok(!isBuffer);\n          strictEqual(type, 'string');\n          strictEqual(enc, 'utf8');\n        },\n        {\n          decodeStrings: false,\n        }\n      );\n      m.write('some-text', 'utf8');\n      m.end();\n      await promises.finished(m);\n    }\n  },\n};\n\nexport const writable_constructor_set_methods = {\n  async test(ctrl, env, ctx) {\n    const bufferBlerg = Buffer.from('blerg');\n    const w = new Writable();\n    throws(\n      () => {\n        w.end(bufferBlerg);\n      },\n      {\n        name: 'Error',\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n        message: 'The _write() method is not implemented',\n      }\n    );\n    const writeCalled = Promise.withResolvers();\n    const writevCalled = Promise.withResolvers();\n    const _write = (chunk, _, next) => {\n      next();\n      writeCalled.resolve();\n    };\n    const _writev = (chunks, next) => {\n      strictEqual(chunks.length, 2);\n      next();\n      writevCalled.resolve();\n    };\n    const w2 = new Writable({\n      write: _write,\n      writev: _writev,\n    });\n    strictEqual(w2._write, _write);\n    strictEqual(w2._writev, _writev);\n    w2.write(bufferBlerg);\n    w2.cork();\n    w2.write(bufferBlerg);\n    w2.write(bufferBlerg);\n    w2.end();\n    await Promise.all([writeCalled.promise, writevCalled.promise]);\n  },\n};\n\nexport const writable_clear_buffer = {\n  async test(ctrl, env, ctx) {\n    class StreamWritable extends Writable {\n      constructor() {\n        super({\n          objectMode: true,\n        });\n      }\n\n      // Refs: https://github.com/nodejs/node/issues/6758\n      // We need a timer like on the original issue thread.\n      // Otherwise the code will never reach our test case.\n      _write(chunk, encoding, cb) {\n        queueMicrotask(cb);\n      }\n    }\n    const testStream = new StreamWritable();\n    testStream.cork();\n    const writePromises = [];\n    for (let i = 1; i <= 5; i++) {\n      const p = Promise.withResolvers();\n      writePromises.push(p.promise);\n      testStream.write(i, () => {\n        strictEqual(\n          testStream._writableState.bufferedRequestCount,\n          testStream._writableState.getBuffer().length\n        );\n        p.resolve();\n      });\n    }\n    testStream.end();\n    await Promise.all(writePromises);\n  },\n};\n\nexport const writable_change_deafult_encoding = {\n  async test(ctrl, env, ctx) {\n    class MyWritable extends Writable {\n      constructor(fn, options) {\n        super(options);\n        this.fn = fn;\n      }\n      _write(chunk, encoding, callback) {\n        this.fn(Buffer.isBuffer(chunk), typeof chunk, encoding);\n        callback();\n      }\n    }\n\n    await (async function defaultCondingIsUtf8() {\n      const m = new MyWritable(\n        function (isBuffer, type, enc) {\n          strictEqual(enc, 'utf8');\n        },\n        {\n          decodeStrings: false,\n        }\n      );\n      m.write('foo');\n      m.end();\n      await promises.finished(m);\n    })();\n\n    await (async function changeDefaultEncodingToAscii() {\n      const m = new MyWritable(\n        function (isBuffer, type, enc) {\n          strictEqual(enc, 'ascii');\n        },\n        {\n          decodeStrings: false,\n        }\n      );\n      m.setDefaultEncoding('ascii');\n      m.write('bar');\n      m.end();\n      await promises.finished(m);\n    })();\n\n    // Change default encoding to invalid value.\n    throws(\n      () => {\n        const m = new MyWritable((isBuffer, type, enc) => {}, {\n          decodeStrings: false,\n        });\n        m.setDefaultEncoding({});\n        m.write('bar');\n        m.end();\n      },\n      {\n        code: 'ERR_UNKNOWN_ENCODING',\n      }\n    );\n    await (async function checkVariableCaseEncoding() {\n      const m = new MyWritable(\n        function (isBuffer, type, enc) {\n          strictEqual(enc, 'ascii');\n        },\n        {\n          decodeStrings: false,\n        }\n      );\n      m.setDefaultEncoding('AsCii');\n      m.write('bar');\n      m.end();\n      await promises.finished(m);\n    })();\n  },\n};\n\nexport const writable_aborted = {\n  async test(ctrl, env, ctx) {\n    {\n      const writable = new Writable({\n        write() {},\n      });\n      strictEqual(writable.writableAborted, false);\n      writable.destroy();\n      strictEqual(writable.writableAborted, true);\n    }\n    {\n      const writable = new Writable({\n        write() {},\n      });\n      strictEqual(writable.writableAborted, false);\n      writable.end();\n      writable.destroy();\n      strictEqual(writable.writableAborted, true);\n    }\n  },\n};\n\nexport const unshift_read_race = {\n  async test(ctrl, env, ctx) {\n    const hwm = 10;\n    const r = Readable({\n      highWaterMark: hwm,\n      autoDestroy: false,\n    });\n    const chunks = 10;\n    const data = Buffer.allocUnsafe(chunks * hwm + Math.ceil(hwm / 2));\n    for (let i = 0; i < data.length; i++) {\n      const c = 'asdf'.charCodeAt(i % 4);\n      data[i] = c;\n    }\n    let pos = 0;\n    let pushedNull = false;\n    r._read = function (n) {\n      ok(!pushedNull, '_read after null push');\n\n      // Every third chunk is fast\n      push(!(chunks % 3));\n      function push(fast) {\n        ok(!pushedNull, 'push() after null push');\n        const c = pos >= data.length ? null : data.slice(pos, pos + n);\n        pushedNull = c === null;\n        if (fast) {\n          pos += n;\n          r.push(c);\n          if (c === null) pushError();\n        } else {\n          setTimeout(function () {\n            pos += n;\n            r.push(c);\n            if (c === null) pushError();\n          }, 1);\n        }\n      }\n    };\n    function pushError() {\n      r.unshift(Buffer.allocUnsafe(1));\n      w.end();\n      throws(\n        () => {\n          r.push(Buffer.allocUnsafe(1));\n        },\n        {\n          code: 'ERR_STREAM_PUSH_AFTER_EOF',\n          name: 'Error',\n          message: 'stream.push() after EOF',\n        }\n      );\n    }\n    const w = Writable();\n    const written = [];\n    const finishCalled = Promise.withResolvers();\n    w._write = function (chunk, encoding, cb) {\n      written.push(chunk.toString());\n      cb();\n    };\n    r.on('end', finishCalled.reject);\n    r.on('readable', function () {\n      let chunk;\n      while (null !== (chunk = r.read(10))) {\n        w.write(chunk);\n        if (chunk.length > 4) r.unshift(Buffer.from('1234'));\n      }\n    });\n    w.on('finish', function () {\n      // Each chunk should start with 1234, and then be asfdasdfasdf...\n      // The first got pulled out before the first unshift('1234'), so it's\n      // lacking that piece.\n      strictEqual(written[0], 'asdfasdfas');\n      let asdf = 'd';\n      for (let i = 1; i < written.length; i++) {\n        strictEqual(written[i].slice(0, 4), '1234');\n        for (let j = 4; j < written[i].length; j++) {\n          const c = written[i].charAt(j);\n          strictEqual(c, asdf);\n          switch (asdf) {\n            case 'a':\n              asdf = 's';\n              break;\n            case 's':\n              asdf = 'd';\n              break;\n            case 'd':\n              asdf = 'f';\n              break;\n            case 'f':\n              asdf = 'a';\n              break;\n          }\n        }\n      }\n      finishCalled.resolve();\n    });\n    await finishCalled.promise;\n    strictEqual(written.length, 18);\n  },\n};\n\nexport const unshift_empty_chunk = {\n  async test(ctrl, env, ctx) {\n    const r = new Readable();\n    let nChunks = 10;\n    const chunk = Buffer.alloc(10, 'x');\n    r._read = function (n) {\n      queueMicrotask(() => {\n        r.push(--nChunks === 0 ? null : chunk);\n      });\n    };\n    let readAll = false;\n    const seen = [];\n    r.on('readable', () => {\n      let chunk;\n      while ((chunk = r.read()) !== null) {\n        seen.push(chunk.toString());\n        // Simulate only reading a certain amount of the data,\n        // and then putting the rest of the chunk back into the\n        // stream, like a parser might do.  We just fill it with\n        // 'y' so that it's easy to see which bits were touched,\n        // and which were not.\n        const putBack = Buffer.alloc(readAll ? 0 : 5, 'y');\n        readAll = !readAll;\n        r.unshift(putBack);\n      }\n    });\n    const expect = [\n      'xxxxxxxxxx',\n      'yyyyy',\n      'xxxxxxxxxx',\n      'yyyyy',\n      'xxxxxxxxxx',\n      'yyyyy',\n      'xxxxxxxxxx',\n      'yyyyy',\n      'xxxxxxxxxx',\n      'yyyyy',\n      'xxxxxxxxxx',\n      'yyyyy',\n      'xxxxxxxxxx',\n      'yyyyy',\n      'xxxxxxxxxx',\n      'yyyyy',\n      'xxxxxxxxxx',\n      'yyyyy',\n    ];\n    r.on('end', () => {\n      deepStrictEqual(seen, expect);\n    });\n    await promises.finished(r);\n  },\n};\n\nexport const unpipe_event = {\n  async test(ctrl, env, ctx) {\n    class NullWriteable extends Writable {\n      _write(chunk, encoding, callback) {\n        return callback();\n      }\n    }\n    class QuickEndReadable extends Readable {\n      _read() {\n        this.push(null);\n      }\n    }\n    class NeverEndReadable extends Readable {\n      _read() {}\n    }\n    {\n      const pipeCalled = Promise.withResolvers();\n      const unpipeCalled = Promise.withResolvers();\n      const dest = new NullWriteable();\n      const src = new QuickEndReadable();\n      dest.on('pipe', pipeCalled.resolve);\n      dest.on('unpipe', unpipeCalled.resolve);\n      src.pipe(dest);\n      await Promise.all([pipeCalled.promise, unpipeCalled.promise]);\n      strictEqual(src._readableState.pipes.length, 0);\n    }\n    {\n      const pipeCalled = Promise.withResolvers();\n      const dest = new NullWriteable();\n      const src = new NeverEndReadable();\n      dest.on('pipe', pipeCalled.resolve);\n      dest.on('unpipe', pipeCalled.reject);\n      src.pipe(dest);\n      await pipeCalled.promise;\n      strictEqual(src._readableState.pipes.length, 1);\n    }\n    {\n      const pipeCalled = Promise.withResolvers();\n      const unpipeCalled = Promise.withResolvers();\n      const dest = new NullWriteable();\n      const src = new NeverEndReadable();\n      dest.on('pipe', pipeCalled.resolve);\n      dest.on('unpipe', unpipeCalled.resolve);\n      src.pipe(dest);\n      src.unpipe(dest);\n      await Promise.all([pipeCalled.promise, unpipeCalled.promise]);\n      strictEqual(src._readableState.pipes.length, 0);\n    }\n    {\n      const pipeCalled = Promise.withResolvers();\n      const unpipeCalled = Promise.withResolvers();\n      const dest = new NullWriteable();\n      const src = new QuickEndReadable();\n      dest.on('pipe', pipeCalled.resolve);\n      dest.on('unpipe', unpipeCalled.resolve);\n      src.pipe(dest, {\n        end: false,\n      });\n      await Promise.all([pipeCalled.promise, unpipeCalled.promise]);\n      strictEqual(src._readableState.pipes.length, 0);\n    }\n    {\n      const pipeCalled = Promise.withResolvers();\n      const dest = new NullWriteable();\n      const src = new NeverEndReadable();\n      dest.on('pipe', pipeCalled.resolve);\n      dest.on('unpipe', pipeCalled.reject);\n      src.pipe(dest, {\n        end: false,\n      });\n      await pipeCalled.promise;\n      strictEqual(src._readableState.pipes.length, 1);\n    }\n    {\n      const pipeCalled = Promise.withResolvers();\n      const unpipeCalled = Promise.withResolvers();\n      const dest = new NullWriteable();\n      const src = new NeverEndReadable();\n      dest.on('pipe', pipeCalled.resolve);\n      dest.on('unpipe', unpipeCalled.resolve);\n      src.pipe(dest, {\n        end: false,\n      });\n      src.unpipe(dest);\n      await Promise.all([pipeCalled.promise, unpipeCalled.promise]);\n      strictEqual(src._readableState.pipes.length, 0);\n    }\n  },\n};\n\nexport const uint8array = {\n  async test(ctrl, env, ctx) {\n    const ABC = new Uint8Array([0x41, 0x42, 0x43]);\n    const DEF = new Uint8Array([0x44, 0x45, 0x46]);\n    const GHI = new Uint8Array([0x47, 0x48, 0x49]);\n    {\n      // Simple Writable test.\n\n      let n = 0;\n      const writeCalled = Promise.withResolvers();\n      let writeCount = 0;\n      const writable = new Writable({\n        write: (chunk, encoding, cb) => {\n          ok(chunk instanceof Buffer);\n          if (n++ === 0) {\n            strictEqual(String(chunk), 'ABC');\n          } else {\n            strictEqual(String(chunk), 'DEF');\n          }\n          cb();\n          if (++writeCount === 2) writeCalled.resolve();\n        },\n      });\n      writable.write(ABC);\n      writable.end(DEF);\n      await writeCalled.promise;\n    }\n    {\n      // Writable test, pass in Uint8Array in object mode.\n      const writeCalled = Promise.withResolvers();\n      const writable = new Writable({\n        objectMode: true,\n        write: (chunk, encoding, cb) => {\n          ok(!(chunk instanceof Buffer));\n          ok(chunk instanceof Uint8Array);\n          strictEqual(chunk, ABC);\n          strictEqual(encoding, undefined);\n          cb();\n          writeCalled.resolve();\n        },\n      });\n      writable.end(ABC);\n      await writeCalled.promise;\n    }\n    {\n      // Writable test, multiple writes carried out via writev.\n      let callback;\n      const writeCalled = Promise.withResolvers();\n      const writevCalled = Promise.withResolvers();\n      const writable = new Writable({\n        write: (chunk, encoding, cb) => {\n          ok(chunk instanceof Buffer);\n          strictEqual(encoding, 'buffer');\n          strictEqual(String(chunk), 'ABC');\n          callback = cb;\n          writeCalled.resolve();\n        },\n        writev: (chunks, cb) => {\n          strictEqual(chunks.length, 2);\n          strictEqual(chunks[0].encoding, 'buffer');\n          strictEqual(chunks[1].encoding, 'buffer');\n          strictEqual(chunks[0].chunk + chunks[1].chunk, 'DEFGHI');\n          writevCalled.resolve();\n        },\n      });\n      writable.write(ABC);\n      writable.write(DEF);\n      writable.end(GHI);\n      callback();\n      await Promise.all([writeCalled.promise, writevCalled.promise]);\n    }\n    {\n      // Simple Readable test.\n      const readable = new Readable({\n        read() {},\n      });\n      readable.push(DEF);\n      readable.unshift(ABC);\n      const buf = readable.read();\n      ok(buf instanceof Buffer);\n      deepStrictEqual([...buf], [...ABC, ...DEF]);\n    }\n    {\n      // Readable test, setEncoding.\n      const readable = new Readable({\n        read() {},\n      });\n      readable.setEncoding('utf8');\n      readable.push(DEF);\n      readable.unshift(ABC);\n      const out = readable.read();\n      strictEqual(out, 'ABCDEF');\n    }\n  },\n};\n\nexport const transform_split_objectmode = {\n  async test(ctrl, env, ctx) {\n    const parser = new Transform({\n      readableObjectMode: true,\n    });\n    ok(parser._readableState.objectMode);\n    ok(!parser._writableState.objectMode);\n    strictEqual(parser.readableHighWaterMark, 16);\n    strictEqual(parser.writableHighWaterMark, 64 * 1024);\n    strictEqual(\n      parser.readableHighWaterMark,\n      parser._readableState.highWaterMark\n    );\n    strictEqual(\n      parser.writableHighWaterMark,\n      parser._writableState.highWaterMark\n    );\n    parser._transform = function (chunk, enc, callback) {\n      callback(null, {\n        val: chunk[0],\n      });\n    };\n    let parsed;\n    parser.on('data', function (obj) {\n      parsed = obj;\n    });\n    parser.end(Buffer.from([42]));\n\n    const serializer = new Transform({\n      writableObjectMode: true,\n    });\n    ok(!serializer._readableState.objectMode);\n    ok(serializer._writableState.objectMode);\n    strictEqual(serializer.readableHighWaterMark, 64 * 1024);\n    strictEqual(serializer.writableHighWaterMark, 16);\n    strictEqual(\n      parser.readableHighWaterMark,\n      parser._readableState.highWaterMark\n    );\n    strictEqual(\n      parser.writableHighWaterMark,\n      parser._writableState.highWaterMark\n    );\n    serializer._transform = function (obj, _, callback) {\n      callback(null, Buffer.from([obj.val]));\n    };\n    let serialized;\n    serializer.on('data', function (chunk) {\n      serialized = chunk;\n    });\n    serializer.write({\n      val: 42,\n    });\n\n    strictEqual(parsed.val, 42);\n    strictEqual(serialized[0], 42);\n  },\n};\n\nexport const transform_split_highwatermark = {\n  async test(ctrl, env, ctx) {\n    const DEFAULT = 64 * 1024;\n    function testTransform(expectedReadableHwm, expectedWritableHwm, options) {\n      const t = new Transform(options);\n      strictEqual(t._readableState.highWaterMark, expectedReadableHwm);\n      strictEqual(t._writableState.highWaterMark, expectedWritableHwm);\n    }\n\n    // Test overriding defaultHwm\n    testTransform(666, DEFAULT, {\n      readableHighWaterMark: 666,\n    });\n    testTransform(DEFAULT, 777, {\n      writableHighWaterMark: 777,\n    });\n    testTransform(666, 777, {\n      readableHighWaterMark: 666,\n      writableHighWaterMark: 777,\n    });\n\n    // Test highWaterMark overriding\n    testTransform(555, 555, {\n      highWaterMark: 555,\n      readableHighWaterMark: 666,\n    });\n    testTransform(555, 555, {\n      highWaterMark: 555,\n      writableHighWaterMark: 777,\n    });\n    testTransform(555, 555, {\n      highWaterMark: 555,\n      readableHighWaterMark: 666,\n      writableHighWaterMark: 777,\n    });\n\n    // Test undefined, null\n    [undefined, null].forEach((v) => {\n      testTransform(DEFAULT, DEFAULT, {\n        readableHighWaterMark: v,\n      });\n      testTransform(DEFAULT, DEFAULT, {\n        writableHighWaterMark: v,\n      });\n      testTransform(666, DEFAULT, {\n        highWaterMark: v,\n        readableHighWaterMark: 666,\n      });\n      testTransform(DEFAULT, 777, {\n        highWaterMark: v,\n        writableHighWaterMark: 777,\n      });\n    });\n\n    // test NaN\n    {\n      throws(\n        () => {\n          new Transform({\n            readableHighWaterMark: NaN,\n          });\n        },\n        {\n          name: 'TypeError',\n          code: 'ERR_INVALID_ARG_VALUE',\n        }\n      );\n      throws(\n        () => {\n          new Transform({\n            writableHighWaterMark: NaN,\n          });\n        },\n        {\n          name: 'TypeError',\n          code: 'ERR_INVALID_ARG_VALUE',\n        }\n      );\n    }\n\n    // Test non Duplex streams ignore the options\n    {\n      const r = new Readable({\n        readableHighWaterMark: 666,\n      });\n      strictEqual(r._readableState.highWaterMark, DEFAULT);\n      const w = new Writable({\n        writableHighWaterMark: 777,\n      });\n      strictEqual(w._writableState.highWaterMark, DEFAULT);\n    }\n  },\n};\n\nexport const transform_objectmode_falsey_value = {\n  async test(ctrl, env, ctx) {\n    const src = new PassThrough({\n      objectMode: true,\n    });\n    const tx = new PassThrough({\n      objectMode: true,\n    });\n    const dest = new PassThrough({\n      objectMode: true,\n    });\n    const expect = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n    const results = [];\n    const dataCalled = Promise.withResolvers();\n    const intervalFinished = Promise.withResolvers();\n    let dataCount = 0;\n    let intCount = 0;\n    dest.on('data', function (x) {\n      results.push(x);\n      if (++dataCount === expect.length) dataCalled.resolve();\n    });\n    src.pipe(tx).pipe(dest);\n    let i = -1;\n    const int = setInterval(function () {\n      if (results.length === expect.length) {\n        src.end();\n        clearInterval(int);\n        deepStrictEqual(results, expect);\n      } else {\n        src.write(i++);\n      }\n      if (++intCount === expect.length + 1) intervalFinished.resolve();\n    }, 1);\n    await Promise.all([dataCalled.promise, intervalFinished.promise]);\n  },\n};\n\nexport const transform_hwm0 = {\n  async test(ctrl, env, ctx) {\n    const t = new Transform({\n      objectMode: true,\n      highWaterMark: 0,\n      transform(chunk, enc, callback) {\n        queueMicrotask(() => callback(null, chunk, enc));\n      },\n    });\n    strictEqual(t.write(1), false);\n    const drainCalled = Promise.withResolvers();\n    const readableCalled = Promise.withResolvers();\n    t.on('drain', () => {\n      strictEqual(t.write(2), false);\n      t.end();\n      drainCalled.resolve();\n    });\n    t.once('readable', () => {\n      strictEqual(t.read(), 1);\n      queueMicrotask(() => {\n        strictEqual(t.read(), null);\n        t.once('readable', () => {\n          strictEqual(t.read(), 2);\n          readableCalled.resolve();\n        });\n      });\n    });\n    await Promise.all([drainCalled.promise, readableCalled.promise]);\n  },\n};\n\nexport const transform_flush_data = {\n  async test(ctrl, env, ctx) {\n    const expected = 'asdf';\n    function _transform(d, e, n) {\n      n();\n    }\n    function _flush(n) {\n      n(null, expected);\n    }\n    const t = new Transform({\n      transform: _transform,\n      flush: _flush,\n    });\n    t.end(Buffer.from('blerg'));\n    t.on('data', (data) => {\n      strictEqual(data.toString(), expected);\n    });\n    await promises.finished(t);\n  },\n};\n\nexport const transform_final = {\n  async test(ctrl, env, ctx) {\n    let state = 0;\n    const transformCalled = Promise.withResolvers();\n    const finalCalled = Promise.withResolvers();\n    const flushCalled = Promise.withResolvers();\n    const finishCalled = Promise.withResolvers();\n    const endCalled = Promise.withResolvers();\n    const dataCalled = Promise.withResolvers();\n    const endFinished = Promise.withResolvers();\n    let dataCount = 0;\n    let transformCount = 0;\n    const t = new Transform({\n      objectMode: true,\n      transform: function (chunk, _, next) {\n        // transformCallback part 1\n        strictEqual(++state, chunk);\n        this.push(state);\n        // transformCallback part 2\n        strictEqual(++state, chunk + 2);\n        queueMicrotask(next);\n        if (++transformCount === 3) transformCalled.resolve();\n      },\n      final: function (done) {\n        state++;\n        // finalCallback part 1\n        strictEqual(state, 10);\n        setTimeout(function () {\n          state++;\n          // finalCallback part 2\n          strictEqual(state, 11);\n          done();\n          finalCalled.resolve();\n        }, 100);\n      },\n      flush: function (done) {\n        state++;\n        // flushCallback part 1\n        strictEqual(state, 12);\n        queueMicrotask(function () {\n          state++;\n          // flushCallback part 2\n          strictEqual(state, 13);\n          done();\n          flushCalled.resolve();\n        });\n      },\n    });\n    t.on('finish', function () {\n      state++;\n      // finishListener\n      strictEqual(state, 15);\n      finishCalled.resolve();\n    });\n    t.on('end', function () {\n      state++;\n      // end event\n      strictEqual(state, 16);\n      endCalled.resolve();\n    });\n    t.on('data', function (d) {\n      // dataListener\n      strictEqual(++state, d + 1);\n      if (++dataCount) dataCalled.resolve();\n    });\n    t.write(1);\n    t.write(4);\n    t.end(7, function () {\n      state++;\n      // endMethodCallback\n      strictEqual(state, 14);\n      endFinished.resolve();\n    });\n    await Promise.all([\n      transformCalled.promise,\n      finalCalled.promise,\n      flushCalled.promise,\n      finishCalled.promise,\n      endCalled.promise,\n      dataCalled.promise,\n      endFinished.promise,\n    ]);\n  },\n};\n\nexport const transform_final_sync = {\n  async test(ctrl, env, ctx) {\n    let state = 0;\n    const transformCalled = Promise.withResolvers();\n    const finalCalled = Promise.withResolvers();\n    const flushCalled = Promise.withResolvers();\n    const finishCalled = Promise.withResolvers();\n    const endCalled = Promise.withResolvers();\n    const dataCalled = Promise.withResolvers();\n    const endFinished = Promise.withResolvers();\n    let transformCount = 0;\n    let dataCount = 0;\n    const t = new Transform({\n      objectMode: true,\n      transform: function (chunk, _, next) {\n        // transformCallback part 1\n        strictEqual(++state, chunk);\n        this.push(state);\n        // transformCallback part 2\n        strictEqual(++state, chunk + 2);\n        queueMicrotask(next);\n        if (++transformCount === 3) transformCalled.resolve();\n      },\n      final: function (done) {\n        state++;\n        // finalCallback part 1\n        strictEqual(state, 10);\n        state++;\n        // finalCallback part 2\n        strictEqual(state, 11);\n        done();\n        finalCalled.resolve();\n      },\n      flush: function (done) {\n        state++;\n        // fluchCallback part 1\n        strictEqual(state, 12);\n        queueMicrotask(function () {\n          state++;\n          // fluchCallback part 2\n          strictEqual(state, 13);\n          done();\n          flushCalled.resolve();\n        });\n      },\n    });\n    t.on('finish', function () {\n      state++;\n      // finishListener\n      strictEqual(state, 15);\n      finishCalled.resolve();\n    });\n    t.on('end', function () {\n      state++;\n      // endEvent\n      strictEqual(state, 16);\n      endCalled.resolve();\n    });\n    t.on('data', function (d) {\n      // dataListener\n      strictEqual(++state, d + 1);\n      if (++dataCount === 3) dataCalled.resolve();\n    });\n    t.write(1);\n    t.write(4);\n    t.end(7, function () {\n      state++;\n      // endMethodCallback\n      strictEqual(state, 14);\n      endFinished.resolve();\n    });\n    await Promise.all([\n      transformCalled.promise,\n      finalCalled.promise,\n      flushCalled.promise,\n      finishCalled.promise,\n      endCalled.promise,\n      dataCalled.promise,\n      endFinished.promise,\n    ]);\n  },\n};\n\nexport const transform_destroy = {\n  async test(ctrl, env, ctx) {\n    {\n      const transform = new Transform({\n        transform(chunk, enc, cb) {},\n      });\n      transform.resume();\n      const closed = Promise.withResolvers();\n      transform.on('end', closed.reject);\n      transform.on('close', closed.resolve);\n      transform.on('finish', closed.reject);\n      transform.destroy();\n      await closed.promise;\n    }\n    {\n      const transform = new Transform({\n        transform(chunk, enc, cb) {},\n      });\n      transform.resume();\n      const expected = new Error('kaboom');\n      const errored = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      transform.on('end', closed.reject);\n      transform.on('finish', closed.reject);\n      transform.on('close', closed.resolve);\n      transform.on('error', (err) => {\n        strictEqual(err, expected);\n        errored.resolve();\n      });\n      transform.destroy(expected);\n      await Promise.all([closed.promise, errored.promise]);\n    }\n    {\n      const transform = new Transform({\n        transform(chunk, enc, cb) {},\n      });\n      transform._destroy = function (err, cb) {\n        strictEqual(err, expected);\n        cb(err);\n      };\n      const expected = new Error('kaboom');\n      const closed = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      transform.on('finish', closed.reject);\n      transform.on('close', closed.resolve);\n      transform.on('error', (err) => {\n        strictEqual(err, expected);\n        errored.resolve();\n      });\n      transform.destroy(expected);\n      await Promise.all([closed.promise, errored.promise]);\n    }\n    {\n      const expected = new Error('kaboom');\n      const destroyCalled = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      const transform = new Transform({\n        transform(chunk, enc, cb) {},\n        destroy: function (err, cb) {\n          strictEqual(err, expected);\n          cb();\n          destroyCalled.resolve();\n        },\n      });\n      transform.resume();\n      transform.on('end', closed.reject);\n      transform.on('close', closed.resolve);\n      transform.on('finish', closed.reject);\n\n      // Error is swallowed by the custom _destroy\n      transform.on('error', closed.reject);\n      transform.destroy(expected);\n      await Promise.all([destroyCalled.promise, closed.promise]);\n    }\n    {\n      const transform = new Transform({\n        transform(chunk, enc, cb) {},\n      });\n      const destroyCalled = Promise.withResolvers();\n      transform._destroy = function (err, cb) {\n        strictEqual(err, null);\n        cb();\n        destroyCalled.resolve();\n      };\n      transform.destroy();\n      await destroyCalled.promise;\n    }\n    {\n      const transform = new Transform({\n        transform(chunk, enc, cb) {},\n      });\n      transform.resume();\n      const destroyCalled = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      const endCalled = Promise.withResolvers();\n      transform._destroy = function (err, cb) {\n        strictEqual(err, null);\n        queueMicrotask(() => {\n          this.push(null);\n          this.end();\n          cb();\n          destroyCalled.resolve();\n        });\n      };\n      transform.on('finish', closed.reject);\n      transform.on('end', closed.reject);\n      transform.on('close', closed.resolve);\n      transform.destroy();\n      transform.removeListener('end', closed.reject);\n      transform.removeListener('finish', closed.reject);\n      transform.on('end', endCalled.resolve);\n      transform.on('finish', closed.reject);\n      await Promise.all([\n        destroyCalled.promise,\n        closed.promise,\n        endCalled.promise,\n      ]);\n    }\n    {\n      const transform = new Transform({\n        transform(chunk, enc, cb) {},\n      });\n      const expected = new Error('kaboom');\n      const destroyCalled = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      transform._destroy = function (err, cb) {\n        strictEqual(err, null);\n        cb(expected);\n        destroyCalled.resolve();\n      };\n      transform.on('close', closed.resolve);\n      transform.on('finish', closed.reject);\n      transform.on('end', closed.reject);\n      transform.on('error', (err) => {\n        strictEqual(err, expected);\n        errored.resolve();\n      });\n      transform.destroy();\n      await Promise.all([\n        destroyCalled.promise,\n        closed.promise,\n        errored.promise,\n      ]);\n    }\n  },\n};\n\nexport const transform_constructor_set_methods = {\n  async test(ctrl, env, ctx) {\n    const t = new Transform();\n    throws(\n      () => {\n        t.end(Buffer.from('blerg'));\n      },\n      {\n        name: 'Error',\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n        message: 'The _transform() method is not implemented',\n      }\n    );\n    const transformCalled = Promise.withResolvers();\n    const finalCalled = Promise.withResolvers();\n    const flushCalled = Promise.withResolvers();\n    const _transform = (chunk, _, next) => {\n      next();\n      transformCalled.resolve();\n    };\n    const _final = (next) => {\n      next();\n      finalCalled.resolve();\n    };\n    const _flush = (next) => {\n      next();\n      flushCalled.resolve();\n    };\n    const t2 = new Transform({\n      transform: _transform,\n      flush: _flush,\n      final: _final,\n    });\n    strictEqual(t2._transform, _transform);\n    strictEqual(t2._flush, _flush);\n    strictEqual(t2._final, _final);\n    t2.end(Buffer.from('blerg'));\n    t2.resume();\n    await Promise.all([\n      transformCalled.promise,\n      finalCalled.promise,\n      flushCalled.promise,\n    ]);\n  },\n};\n\nexport const transform_callback_twice = {\n  async test(ctrl, env, ctx) {\n    const stream = new Transform({\n      transform(chunk, enc, cb) {\n        cb();\n        cb();\n      },\n    });\n    const errored = Promise.withResolvers();\n    stream.on('error', function (err) {\n      strictEqual(err.code, 'ERR_MULTIPLE_CALLBACK');\n      errored.resolve();\n    });\n    stream.write('foo');\n    await errored.promise;\n  },\n};\n\nexport const toarray = {\n  async test(ctrl, env, ctx) {\n    {\n      // Works on a synchronous stream\n      await (async () => {\n        const tests = [\n          [],\n          [1],\n          [1, 2, 3],\n          Array.from({ length: 100 }, (_, i) => i),\n        ];\n        for (const test of tests) {\n          const stream = Readable.from(test);\n          const result = await stream.toArray();\n          deepStrictEqual(result, test);\n        }\n      })();\n    }\n    {\n      // Works on a non-object-mode stream\n      await (async () => {\n        const firstBuffer = Buffer.from([1, 2, 3]);\n        const secondBuffer = Buffer.from([4, 5, 6]);\n        const stream = Readable.from([firstBuffer, secondBuffer], {\n          objectMode: false,\n        });\n        const result = await stream.toArray();\n        strictEqual(Array.isArray(result), true);\n        deepStrictEqual(result, [firstBuffer, secondBuffer]);\n      })();\n    }\n    {\n      // Works on an asynchronous stream\n      await (async () => {\n        const tests = [\n          [],\n          [1],\n          [1, 2, 3],\n          Array.from({ length: 100 }, (_, i) => i),\n        ];\n        for (const test of tests) {\n          const stream = Readable.from(test).map((x) => Promise.resolve(x));\n          const result = await stream.toArray();\n          deepStrictEqual(result, test);\n        }\n      })();\n    }\n    {\n      // Support for AbortSignal\n      const ac = new AbortController();\n      let stream;\n      queueMicrotask(() => ac.abort());\n      await rejects(\n        async () => {\n          stream = Readable.from([1, 2, 3]).map(async (x) => {\n            if (x === 3) {\n              await new Promise(() => {}); // Explicitly do not pass signal here\n            }\n\n            return Promise.resolve(x);\n          });\n          await stream.toArray({\n            signal: ac.signal,\n          });\n        },\n        {\n          name: 'AbortError',\n        }\n      );\n\n      // Only stops toArray, does not destroy the stream\n      ok(stream.destroyed, false);\n    }\n    {\n      // Test result is a Promise\n      const result = Readable.from([1, 2, 3, 4, 5]).toArray();\n      strictEqual(result instanceof Promise, true);\n    }\n    {\n      // Error cases\n      await rejects(async () => {\n        await Readable.from([1]).toArray(1);\n      }, /ERR_INVALID_ARG_TYPE/);\n      await rejects(async () => {\n        await Readable.from([1]).toArray({\n          signal: true,\n        });\n      }, /ERR_INVALID_ARG_TYPE/);\n    }\n  },\n};\n\nexport const some_find_every = {\n  async test(ctrl, env, ctx) {\n    function oneTo5() {\n      return Readable.from([1, 2, 3, 4, 5]);\n    }\n\n    function oneTo5Async() {\n      return oneTo5().map(async (x) => {\n        await Promise.resolve();\n        return x;\n      });\n    }\n    {\n      // Some, find, and every work with a synchronous stream and predicate\n      strictEqual(await oneTo5().some((x) => x > 3), true);\n      strictEqual(await oneTo5().every((x) => x > 3), false);\n      strictEqual(await oneTo5().find((x) => x > 3), 4);\n      strictEqual(await oneTo5().some((x) => x > 6), false);\n      strictEqual(await oneTo5().every((x) => x < 6), true);\n      strictEqual(await oneTo5().find((x) => x > 6), undefined);\n      strictEqual(await Readable.from([]).some(() => true), false);\n      strictEqual(await Readable.from([]).every(() => true), true);\n      strictEqual(await Readable.from([]).find(() => true), undefined);\n    }\n\n    {\n      // Some, find, and every work with an asynchronous stream and synchronous predicate\n      strictEqual(await oneTo5Async().some((x) => x > 3), true);\n      strictEqual(await oneTo5Async().every((x) => x > 3), false);\n      strictEqual(await oneTo5Async().find((x) => x > 3), 4);\n      strictEqual(await oneTo5Async().some((x) => x > 6), false);\n      strictEqual(await oneTo5Async().every((x) => x < 6), true);\n      strictEqual(await oneTo5Async().find((x) => x > 6), undefined);\n    }\n\n    {\n      // Some, find, and every work on synchronous streams with an asynchronous predicate\n      strictEqual(await oneTo5().some(async (x) => x > 3), true);\n      strictEqual(await oneTo5().every(async (x) => x > 3), false);\n      strictEqual(await oneTo5().find(async (x) => x > 3), 4);\n      strictEqual(await oneTo5().some(async (x) => x > 6), false);\n      strictEqual(await oneTo5().every(async (x) => x < 6), true);\n      strictEqual(await oneTo5().find(async (x) => x > 6), undefined);\n    }\n\n    {\n      // Some, find, and every work on asynchronous streams with an asynchronous predicate\n      strictEqual(await oneTo5Async().some(async (x) => x > 3), true);\n      strictEqual(await oneTo5Async().every(async (x) => x > 3), false);\n      strictEqual(await oneTo5Async().find(async (x) => x > 3), 4);\n      strictEqual(await oneTo5Async().some(async (x) => x > 6), false);\n      strictEqual(await oneTo5Async().every(async (x) => x < 6), true);\n      strictEqual(await oneTo5Async().find(async (x) => x > 6), undefined);\n    }\n\n    {\n      async function checkDestroyed(stream) {\n        await scheduler.wait(1);\n        strictEqual(stream.destroyed, true);\n      }\n\n      {\n        // Some, find, and every short circuit\n        const someStream = oneTo5();\n        await someStream.some((x) => x > 2);\n        await checkDestroyed(someStream);\n\n        const everyStream = oneTo5();\n        await everyStream.every((x) => x < 3);\n        await checkDestroyed(everyStream);\n\n        const findStream = oneTo5();\n        await findStream.find((x) => x > 1);\n        await checkDestroyed(findStream);\n\n        // When short circuit isn't possible the whole stream is iterated\n        await oneTo5().some(() => false);\n        await oneTo5().every(() => true);\n        await oneTo5().find(() => false);\n      }\n\n      {\n        // Some, find, and every short circuit async stream/predicate\n        const someStream = oneTo5Async();\n        await someStream.some(async (x) => x > 2);\n        await checkDestroyed(someStream);\n\n        const everyStream = oneTo5Async();\n        await everyStream.every(async (x) => x < 3);\n        await checkDestroyed(everyStream);\n\n        const findStream = oneTo5Async();\n        await findStream.find(async (x) => x > 1);\n        await checkDestroyed(findStream);\n\n        // When short circuit isn't possible the whole stream is iterated\n        await oneTo5Async().some(async () => false);\n        await oneTo5Async().every(async () => true);\n        await oneTo5Async().find(async () => false);\n      }\n    }\n\n    {\n      // Concurrency doesn't affect which value is found.\n      const found = await Readable.from([1, 2]).find(\n        async (val) => {\n          if (val === 1) {\n            await scheduler.wait(100);\n          }\n          return true;\n        },\n        { concurrency: 2 }\n      );\n      strictEqual(found, 1);\n    }\n\n    {\n      // Support for AbortSignal\n      for (const op of ['some', 'every', 'find']) {\n        {\n          const ac = new AbortController();\n          queueMicrotask(() => ac.abort());\n          await rejects(\n            Readable.from([1, 2, 3])[op](() => new Promise(() => {}), {\n              signal: ac.signal,\n            }),\n            {\n              name: 'AbortError',\n            },\n            `${op} should abort correctly with sync abort`\n          );\n        }\n        {\n          // Support for pre-aborted AbortSignal\n          await rejects(\n            Readable.from([1, 2, 3])[op](() => new Promise(() => {}), {\n              signal: AbortSignal.abort(),\n            }),\n            {\n              name: 'AbortError',\n            },\n            `${op} should abort with pre-aborted abort controller`\n          );\n        }\n      }\n    }\n    {\n      // Error cases\n      for (const op of ['some', 'every', 'find']) {\n        await rejects(\n          async () => {\n            await Readable.from([1])[op](1);\n          },\n          /ERR_INVALID_ARG_TYPE/,\n          `${op} should throw for invalid function`\n        );\n        await rejects(\n          async () => {\n            await Readable.from([1])[op]((x) => x, {\n              concurrency: 'Foo',\n            });\n          },\n          /RangeError/,\n          `${op} should throw for invalid concurrency`\n        );\n        await rejects(\n          async () => {\n            await Readable.from([1])[op]((x) => x, 1);\n          },\n          /ERR_INVALID_ARG_TYPE/,\n          `${op} should throw for invalid concurrency`\n        );\n        await rejects(\n          async () => {\n            await Readable.from([1])[op]((x) => x, {\n              signal: true,\n            });\n          },\n          /ERR_INVALID_ARG_TYPE/,\n          `${op} should throw for invalid signal`\n        );\n      }\n    }\n    {\n      for (const op of ['some', 'every', 'find']) {\n        const stream = oneTo5();\n        Object.defineProperty(stream, 'map', {\n          value: () => {\n            throw new Error('should not be called');\n          },\n        });\n        // Check that map isn't getting called.\n        stream[op](() => {});\n      }\n    }\n  },\n};\n\nexport const reduce = {\n  async test(ctrl, env, ctx) {\n    function sum(p, c) {\n      return p + c;\n    }\n    {\n      // Does the same thing as `(await stream.toArray()).reduce(...)`\n      await (async () => {\n        const tests = [\n          [[], sum, 0],\n          [[1], sum, 0],\n          [[1, 2, 3, 4, 5], sum, 0],\n          [[...Array(100).keys()], sum, 0],\n          [['a', 'b', 'c'], sum, ''],\n          [[1, 2], sum],\n          [[1, 2, 3], (x, y) => y],\n        ];\n        for (const [values, fn, initial] of tests) {\n          const streamReduce = await Readable.from(values).reduce(fn, initial);\n          const arrayReduce = values.reduce(fn, initial);\n          deepStrictEqual(streamReduce, arrayReduce);\n        }\n        // Does the same thing as `(await stream.toArray()).reduce(...)` with an\n        // asynchronous reducer\n        for (const [values, fn, initial] of tests) {\n          const streamReduce = await Readable.from(values)\n            .map(async (x) => x)\n            .reduce(fn, initial);\n          const arrayReduce = values.reduce(fn, initial);\n          deepStrictEqual(streamReduce, arrayReduce);\n        }\n      })();\n    }\n    {\n      // Works with an async reducer, with or without initial value\n      await (async () => {\n        const six = await Readable.from([1, 2, 3]).reduce(\n          async (p, c) => p + c,\n          0\n        );\n        strictEqual(six, 6);\n      })();\n      await (async () => {\n        const six = await Readable.from([1, 2, 3]).reduce(\n          async (p, c) => p + c\n        );\n        strictEqual(six, 6);\n      })();\n    }\n    {\n      // Works lazily\n      await rejects(\n        Readable.from([1, 2, 3, 4, 5, 6])\n          .map((x) => {\n            return x;\n          }) // Two consumed and one buffered by `map` due to default concurrency\n          .reduce(async (p, c) => {\n            if (p === 1) {\n              throw new Error('boom');\n            }\n            return c;\n          }, 0),\n        /boom/\n      );\n    }\n    {\n      // Support for AbortSignal\n      const ac = new AbortController();\n      queueMicrotask(() => ac.abort());\n      await rejects(\n        async () => {\n          await Readable.from([1, 2, 3]).reduce(\n            async (p, c) => {\n              if (c === 3) {\n                await new Promise(() => {}); // Explicitly do not pass signal here\n              }\n\n              return Promise.resolve();\n            },\n            0,\n            {\n              signal: ac.signal,\n            }\n          );\n        },\n        {\n          name: 'AbortError',\n        }\n      );\n    }\n    {\n      // Support for AbortSignal - pre aborted\n      const stream = Readable.from([1, 2, 3]);\n      await rejects(\n        async () => {\n          await stream.reduce(\n            async (p, c) => {\n              if (c === 3) {\n                await new Promise(() => {}); // Explicitly do not pass signal here\n              }\n\n              return Promise.resolve();\n            },\n            0,\n            {\n              signal: AbortSignal.abort(),\n            }\n          );\n        },\n        {\n          name: 'AbortError',\n        }\n      );\n\n      strictEqual(stream.destroyed, true);\n    }\n    {\n      // Support for AbortSignal - deep\n      const stream = Readable.from([1, 2, 3]);\n      await rejects(\n        async () => {\n          await stream.reduce(\n            async (p, c, { signal }) => {\n              signal.addEventListener('abort', () => {}, {\n                once: true,\n              });\n              if (c === 3) {\n                await new Promise(() => {}); // Explicitly do not pass signal here\n              }\n\n              return Promise.resolve();\n            },\n            0,\n            {\n              signal: AbortSignal.abort(),\n            }\n          );\n        },\n        {\n          name: 'AbortError',\n        }\n      );\n      strictEqual(stream.destroyed, true);\n    }\n    {\n      // Error cases\n      await rejects(() => Readable.from([]).reduce(1), /TypeError/);\n      await rejects(() => Readable.from([]).reduce('5'), /TypeError/);\n      await rejects(\n        () => Readable.from([]).reduce((x, y) => x + y, 0, 1),\n        /ERR_INVALID_ARG_TYPE/\n      );\n      await rejects(\n        () =>\n          Readable.from([]).reduce((x, y) => x + y, 0, {\n            signal: true,\n          }),\n        /ERR_INVALID_ARG_TYPE/\n      );\n    }\n    {\n      // Test result is a Promise\n      const result = Readable.from([1, 2, 3, 4, 5]).reduce(sum, 0);\n      ok(result instanceof Promise);\n    }\n  },\n};\n\nexport const readablelistening_state = {\n  async test(ctrl, env, ctx) {\n    const r = new Readable({\n      read: () => {},\n    });\n\n    // readableListening state should start in `false`.\n    strictEqual(r._readableState.readableListening, false);\n    const readableCalled = Promise.withResolvers();\n    const dataCalled = Promise.withResolvers();\n    r.on('readable', () => {\n      // Inside the readable event this state should be true.\n      strictEqual(r._readableState.readableListening, true);\n      readableCalled.resolve();\n    });\n    r.push(Buffer.from('Testing readableListening state'));\n    const r2 = new Readable({\n      read: () => {},\n    });\n\n    // readableListening state should start in `false`.\n    strictEqual(r2._readableState.readableListening, false);\n    r2.on('data', (chunk) => {\n      // readableListening should be false because we don't have\n      // a `readable` listener\n      strictEqual(r2._readableState.readableListening, false);\n      dataCalled.resolve();\n    });\n    r2.push(Buffer.from('Testing readableListening state'));\n    await Promise.all([readableCalled.promise, dataCalled.promise]);\n  },\n};\n\nexport const readable_with_unimplemented_read = {\n  async test(ctrl, env, ctx) {\n    const readable = new Readable();\n    readable.read();\n    const errored = Promise.withResolvers();\n    const closed = Promise.withResolvers();\n    readable.on('error', function (err) {\n      strictEqual(err.code, 'ERR_METHOD_NOT_IMPLEMENTED');\n      errored.resolve();\n    });\n    readable.on('close', closed.resolve);\n    await Promise.all([errored.promise, closed.promise]);\n  },\n};\n\nexport const readable_unshift = {\n  async test(ctrl, env, ctx) {\n    {\n      // Check that strings are saved as Buffer\n      const readable = new Readable({\n        read() {},\n      });\n      const string = 'abc';\n      const dataCalled = Promise.withResolvers();\n      readable.on('data', (chunk) => {\n        ok(Buffer.isBuffer(chunk));\n        strictEqual(chunk.toString('utf8'), string);\n        dataCalled.resolve();\n      });\n      readable.unshift(string);\n      await dataCalled.promise;\n    }\n    {\n      // Check that data goes at the beginning\n      const readable = new Readable({\n        read() {},\n      });\n      const unshift = 'front';\n      const push = 'back';\n      const expected = [unshift, push];\n      const dataCalled = Promise.withResolvers();\n      let dataCount = 0;\n      readable.on('data', (chunk) => {\n        strictEqual(chunk.toString('utf8'), expected.shift());\n        if (++dataCount === 2) dataCalled.resolve();\n      });\n      readable.push(push);\n      readable.unshift(unshift);\n      await dataCalled.promise;\n    }\n    {\n      // Check that buffer is saved with correct encoding\n      const readable = new Readable({\n        read() {},\n      });\n      const encoding = 'base64';\n      const string = Buffer.from('abc').toString(encoding);\n      const dataCalled = Promise.withResolvers();\n      readable.on('data', (chunk) => {\n        strictEqual(chunk.toString(encoding), string);\n        dataCalled.resolve();\n      });\n      readable.unshift(string, encoding);\n      await dataCalled.promise;\n    }\n    {\n      const streamEncoding = 'base64';\n      const dataCalled = Promise.withResolvers();\n      function checkEncoding(readable) {\n        // chunk encodings\n        const encodings = ['utf8', 'binary', 'hex', 'base64'];\n        const expected = [];\n        let dataCount = 0;\n        readable.on('data', (chunk) => {\n          const { encoding, string } = expected.pop();\n          strictEqual(chunk.toString(encoding), string);\n          if (++dataCount === encodings.length) dataCalled.resolve();\n        });\n        for (const encoding of encodings) {\n          const string = 'abc';\n\n          // If encoding is the same as the state.encoding the string is\n          // saved as is\n          const expect =\n            encoding !== streamEncoding\n              ? Buffer.from(string, encoding).toString(streamEncoding)\n              : string;\n          expected.push({\n            encoding,\n            string: expect,\n          });\n          readable.unshift(string, encoding);\n        }\n      }\n      const r1 = new Readable({\n        read() {},\n      });\n      r1.setEncoding(streamEncoding);\n      checkEncoding(r1);\n      const r2 = new Readable({\n        read() {},\n        encoding: streamEncoding,\n      });\n      checkEncoding(r2);\n      await dataCalled.promise;\n    }\n    {\n      // Both .push & .unshift should have the same behavior\n      // When setting an encoding, each chunk should be emitted with that encoding\n      const encoding = 'base64';\n      const dataCalled = Promise.withResolvers();\n      function checkEncoding(readable) {\n        const string = 'abc';\n        let dataCount = 0;\n        readable.on('data', (chunk) => {\n          strictEqual(chunk, Buffer.from(string).toString(encoding));\n          if (++dataCount === 2) dataCalled.resolve();\n        });\n        readable.push(string);\n        readable.unshift(string);\n      }\n      const r1 = new Readable({\n        read() {},\n      });\n      r1.setEncoding(encoding);\n      checkEncoding(r1);\n      const r2 = new Readable({\n        read() {},\n        encoding,\n      });\n      checkEncoding(r2);\n      await dataCalled.promise;\n    }\n    {\n      // Check that ObjectMode works\n      const readable = new Readable({\n        objectMode: true,\n        read() {},\n      });\n      const chunks = ['a', 1, {}, []];\n      const dataCalled = Promise.withResolvers();\n      let dataCount = 0;\n      readable.on('data', (chunk) => {\n        strictEqual(chunk, chunks.pop());\n        if (++dataCount === chunks.length) dataCalled.resolve();\n      });\n      for (const chunk of chunks) {\n        readable.unshift(chunk);\n      }\n      await dataCalled.promise;\n    }\n    {\n      // Should not throw: https://github.com/nodejs/node/issues/27192\n      const highWaterMark = 50;\n      class ArrayReader extends Readable {\n        constructor(opt) {\n          super({\n            highWaterMark,\n          });\n          // The error happened only when pushing above hwm\n          this.buffer = Array.from({ length: highWaterMark * 2 }, () => '0');\n        }\n        _read(size) {\n          while (this.buffer.length) {\n            const chunk = this.buffer.shift();\n            if (!this.buffer.length) {\n              this.push(chunk);\n              this.push(null);\n              return true;\n            }\n            if (!this.push(chunk)) return;\n          }\n        }\n      }\n      const readCalled = Promise.withResolvers();\n      function onRead() {\n        while (null !== stream.read()) {\n          // Remove the 'readable' listener before unshifting\n          stream.removeListener('readable', onRead);\n          stream.unshift('a');\n          break;\n        }\n        readCalled.resolve();\n      }\n      const stream = new ArrayReader();\n      stream.once('readable', onRead);\n      await readCalled.promise;\n    }\n  },\n};\n\nexport const readable_setencoding_null = {\n  async test(ctrl, env, ctx) {\n    const readable = new Readable({\n      encoding: 'hex',\n    });\n    strictEqual(readable._readableState.encoding, 'hex');\n    readable.setEncoding(null);\n    strictEqual(readable._readableState.encoding, 'utf8');\n  },\n};\n\nexport const readable_setencoding_existing_buffers = {\n  async test(ctrl, env, ctx) {\n    {\n      // Call .setEncoding() while there are bytes already in the buffer.\n      const r = new Readable({\n        read() {},\n      });\n      r.push(Buffer.from('a'));\n      r.push(Buffer.from('b'));\n      r.setEncoding('utf8');\n      const chunks = [];\n      r.on('data', (chunk) => chunks.push(chunk));\n      queueMicrotask(() => {\n        deepStrictEqual(chunks, ['ab']);\n      });\n    }\n    {\n      // Call .setEncoding() while the buffer contains a complete,\n      // but chunked character.\n      const r = new Readable({\n        read() {},\n      });\n      r.push(Buffer.from([0xf0]));\n      r.push(Buffer.from([0x9f]));\n      r.push(Buffer.from([0x8e]));\n      r.push(Buffer.from([0x89]));\n      r.setEncoding('utf8');\n      const chunks = [];\n      r.on('data', (chunk) => chunks.push(chunk));\n      queueMicrotask(() => {\n        deepStrictEqual(chunks, ['🎉']);\n      });\n    }\n    {\n      // Call .setEncoding() while the buffer contains an incomplete character,\n      // and finish the character later.\n      const r = new Readable({\n        read() {},\n      });\n      r.push(Buffer.from([0xf0]));\n      r.push(Buffer.from([0x9f]));\n      r.setEncoding('utf8');\n      r.push(Buffer.from([0x8e]));\n      r.push(Buffer.from([0x89]));\n      const chunks = [];\n      r.on('data', (chunk) => chunks.push(chunk));\n      queueMicrotask(() => {\n        deepStrictEqual(chunks, ['🎉']);\n      });\n    }\n  },\n};\n\nexport const readable_resumescheduled = {\n  async test(ctrl, env, ctx) {\n    {\n      // pipe() test case\n      const r = new Readable({\n        read() {},\n      });\n      const w = new Writable();\n\n      // resumeScheduled should start = `false`.\n      strictEqual(r._readableState.resumeScheduled, false);\n\n      // Calling pipe() should change the state value = true.\n      r.pipe(w);\n      strictEqual(r._readableState.resumeScheduled, true);\n      queueMicrotask(() => {\n        strictEqual(r._readableState.resumeScheduled, false);\n      });\n    }\n    {\n      // 'data' listener test case\n      const r = new Readable({\n        read() {},\n      });\n\n      // resumeScheduled should start = `false`.\n      strictEqual(r._readableState.resumeScheduled, false);\n      r.push(Buffer.from([1, 2, 3]));\n\n      // Adding 'data' listener should change the state value\n      r.on('data', () => {\n        strictEqual(r._readableState.resumeScheduled, false);\n      });\n      strictEqual(r._readableState.resumeScheduled, true);\n      queueMicrotask(() => {\n        strictEqual(r._readableState.resumeScheduled, false);\n      });\n    }\n    {\n      // resume() test case\n      const r = new Readable({\n        read() {},\n      });\n\n      // resumeScheduled should start = `false`.\n      strictEqual(r._readableState.resumeScheduled, false);\n\n      // Calling resume() should change the state value.\n      r.resume();\n      strictEqual(r._readableState.resumeScheduled, true);\n      r.on('resume', () => {\n        // The state value should be `false` again\n        strictEqual(r._readableState.resumeScheduled, false);\n      });\n      queueMicrotask(() => {\n        strictEqual(r._readableState.resumeScheduled, false);\n      });\n    }\n  },\n};\n\nexport const readable_resume_hwm = {\n  async test(ctrl, env, ctx) {\n    const readable = new Readable({\n      read: () => {\n        throw new Error('should not be called');\n      },\n      highWaterMark: 100,\n    });\n\n    // Fill up the internal buffer so that we definitely exceed the HWM:\n    for (let i = 0; i < 10; i++) readable.push('a'.repeat(200));\n\n    // Call resume, and pause after one chunk.\n    // The .pause() is just so that we don’t empty the buffer fully, which would\n    // be a valid reason to call ._read().\n    readable.resume();\n    const dataCalled = Promise.withResolvers();\n    readable.once('data', () => {\n      readable.pause();\n      dataCalled.resolve();\n    });\n    await dataCalled.promise;\n  },\n};\n\nexport const readable_reading_readingmore = {\n  async test(ctrl, env, ctx) {\n    {\n      const readable = new Readable({\n        read(size) {},\n      });\n      const state = readable._readableState;\n\n      // Starting off with false initially.\n      strictEqual(state.reading, false);\n      strictEqual(state.readingMore, false);\n      let dataCount = 0;\n      let readableCount = 0;\n      const dataCalled = Promise.withResolvers();\n      const readableCalled = Promise.withResolvers();\n      const ended = Promise.withResolvers();\n      readable.on('data', (data) => {\n        // While in a flowing state with a 'readable' listener\n        // we should not be reading more\n        if (readable.readableFlowing) strictEqual(state.readingMore, true);\n\n        // Reading as long as we've not ended\n        strictEqual(state.reading, !state.ended);\n        if (++dataCount === 2) dataCalled.resolve();\n      });\n      function onStreamEnd() {\n        // End of stream; state.reading is false\n        // And so should be readingMore.\n        strictEqual(state.readingMore, false);\n        strictEqual(state.reading, false);\n        ended.resolve();\n      }\n      const expectedReadingMore = [true, true, false];\n      readable.on('readable', () => {\n        // There is only one readingMore scheduled from on('data'),\n        // after which everything is governed by the .read() call\n        strictEqual(state.readingMore, expectedReadingMore.shift());\n\n        // If the stream has ended, we shouldn't be reading\n        strictEqual(state.ended, !state.reading);\n\n        // Consume all the data\n        while (readable.read() !== null);\n        if (expectedReadingMore.length === 0)\n          // Reached end of stream\n          queueMicrotask(onStreamEnd);\n        if (++readableCount === 3) readableCalled.resolve();\n      });\n      readable.on('end', onStreamEnd);\n      readable.push('pushed');\n      readable.read(6);\n\n      // reading\n      strictEqual(state.reading, true);\n      strictEqual(state.readingMore, true);\n\n      // add chunk to front\n      readable.unshift('unshifted');\n\n      // end\n      readable.push(null);\n      await Promise.all([\n        dataCalled.promise,\n        readableCalled.promise,\n        ended.promise,\n      ]);\n    }\n    {\n      const readable = new Readable({\n        read(size) {},\n      });\n      const state = readable._readableState;\n\n      // Starting off with false initially.\n      strictEqual(state.reading, false);\n      strictEqual(state.readingMore, false);\n      let dataCount = 0;\n      const dataCalled = Promise.withResolvers();\n      const ended = Promise.withResolvers();\n      readable.on('data', (data) => {\n        // While in a flowing state without a 'readable' listener\n        // we should be reading more\n        if (readable.readableFlowing) strictEqual(state.readingMore, true);\n\n        // Reading as long as we've not ended\n        strictEqual(state.reading, !state.ended);\n        if (++dataCount === 2) dataCalled.resolve();\n      });\n      function onStreamEnd() {\n        // End of stream; state.reading is false\n        // And so should be readingMore.\n        strictEqual(state.readingMore, false);\n        strictEqual(state.reading, false);\n        ended.resolve();\n      }\n      readable.on('end', onStreamEnd);\n      readable.push('pushed');\n\n      // Stop emitting 'data' events\n      strictEqual(state.flowing, true);\n      readable.pause();\n\n      // paused\n      strictEqual(state.reading, false);\n      strictEqual(state.flowing, false);\n      readable.resume();\n      strictEqual(state.reading, false);\n      strictEqual(state.flowing, true);\n\n      // add chunk to front\n      readable.unshift('unshifted');\n\n      // end\n      readable.push(null);\n      await Promise.all([dataCalled.promise, ended.promise]);\n    }\n    {\n      const readable = new Readable({\n        read(size) {},\n      });\n      const state = readable._readableState;\n\n      let dataCount = 0;\n      const dataCalled = Promise.withResolvers();\n      const ended = Promise.withResolvers();\n\n      // Starting off with false initially.\n      strictEqual(state.reading, false);\n      strictEqual(state.readingMore, false);\n      readable.on('readable', fail);\n      readable.on('data', (data) => {\n        // Reading as long as we've not ended\n        strictEqual(state.reading, !state.ended);\n        if (++dataCount === 2) dataCalled.resolve();\n      });\n      readable.removeListener('readable', fail);\n      function onStreamEnd() {\n        // End of stream; state.reading is false\n        // And so should be readingMore.\n        strictEqual(state.readingMore, false);\n        strictEqual(state.reading, false);\n        ended.resolve();\n      }\n      readable.on('end', onStreamEnd);\n      readable.push('pushed');\n\n      // We are still not flowing, we will be resuming in the next tick\n      strictEqual(state.flowing, false);\n\n      // Wait for nextTick, so the readableListener flag resets\n      queueMicrotask(function () {\n        readable.resume();\n\n        // Stop emitting 'data' events\n        strictEqual(state.flowing, true);\n        readable.pause();\n\n        // paused\n        strictEqual(state.flowing, false);\n        readable.resume();\n        strictEqual(state.flowing, true);\n\n        // add chunk to front\n        readable.unshift('unshifted');\n\n        // end\n        readable.push(null);\n      });\n\n      await Promise.all([dataCalled.promise, ended.promise]);\n    }\n  },\n};\n\nexport const readable_readable = {\n  async test(ctrl, env, ctx) {\n    {\n      const r = new Readable({\n        read() {},\n      });\n      strictEqual(r.readable, true);\n      r.destroy();\n      strictEqual(r.readable, false);\n    }\n    {\n      const r = new Readable({\n        read() {},\n      });\n      strictEqual(r.readable, true);\n      r.on('end', fail);\n      r.resume();\n      r.push(null);\n      strictEqual(r.readable, true);\n      r.off('end', fail);\n      const ended = Promise.withResolvers();\n      r.on('end', () => {\n        strictEqual(r.readable, false);\n        ended.resolve();\n      });\n    }\n    {\n      const readCalled = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      const r = new Readable({\n        read: () => {\n          queueMicrotask(() => {\n            r.destroy(new Error());\n            strictEqual(r.readable, false);\n            readCalled.resolve();\n          });\n        },\n      });\n      r.resume();\n      r.on('error', () => {\n        strictEqual(r.readable, false);\n        errored.resolve();\n      });\n      await Promise.all([readCalled.promise, errored.promise]);\n    }\n  },\n};\n\nexport const readable_readable_then_resume = {\n  async test(ctrl, env, ctx) {\n    await check(\n      new Readable({\n        objectMode: true,\n        highWaterMark: 1,\n        read() {\n          if (!this.first) {\n            this.push('hello');\n            this.first = true;\n            return;\n          }\n          this.push(null);\n        },\n      })\n    );\n    async function check(s) {\n      const ended = Promise.withResolvers();\n      s.on('readable', ended.reject);\n      s.on('end', ended.resolve);\n      strictEqual(s.removeListener, s.off);\n      s.removeListener('readable', ended.reject);\n      s.resume();\n      await ended.promise;\n    }\n  },\n};\n\nexport const readable_pause_and_resume = {\n  async test(ctrl, env, ctx) {\n    let ticks = 18;\n    let expectedData = 19;\n    const rs = new Readable({\n      objectMode: true,\n      read: () => {\n        if (ticks-- > 0) return queueMicrotask(() => rs.push({}));\n        rs.push({});\n        rs.push(null);\n      },\n    });\n    const ended = Promise.withResolvers();\n    const ondataCalled = Promise.withResolvers();\n    rs.on('end', ended.resolve);\n    await readAndPause();\n    async function readAndPause() {\n      // Does a on(data) -> pause -> wait -> resume -> on(data) ... loop.\n      // Expects on(data) to never fire if the stream is paused.\n      const ondata = (data) => {\n        rs.pause();\n        expectedData--;\n        if (expectedData <= 0) return;\n        queueMicrotask(function () {\n          rs.removeListener('data', ondata);\n          readAndPause();\n          rs.resume();\n          ondataCalled.resolve();\n        });\n      }; // Only call ondata once\n\n      rs.on('data', ondata);\n      await Promise.all([ended.promise, ondataCalled.promise]);\n    }\n    {\n      const readable = new Readable({\n        read() {},\n      });\n      function read() {}\n      readable.setEncoding('utf8');\n      readable.on('readable', read);\n      readable.removeListener('readable', read);\n      readable.pause();\n      queueMicrotask(function () {\n        ok(readable.isPaused());\n      });\n    }\n    {\n      const source3 = new PassThrough();\n      const target3 = new PassThrough();\n      const chunk = Buffer.allocUnsafe(1000);\n      while (target3.write(chunk));\n      source3.pipe(target3);\n      const drainCalled = Promise.withResolvers();\n      target3.on('drain', () => {\n        ok(!source3.isPaused());\n        drainCalled.resolve();\n      });\n      target3.on('data', () => {});\n      await drainCalled.promise;\n    }\n  },\n};\n\nexport const readable_object_multi_push_async = {\n  async test(ctrl, env, ctx) {\n    const MAX = 42;\n    const BATCH = 10;\n    {\n      const readCalled = Promise.withResolvers();\n      const ended = Promise.withResolvers();\n      let readCount = 0;\n      const readable = new Readable({\n        objectMode: true,\n        read: function () {\n          fetchData((err, data) => {\n            if (err) {\n              this.destroy(err);\n              return;\n            }\n            if (data.length === 0) {\n              this.push(null);\n              return;\n            }\n            data.forEach((d) => this.push(d));\n          });\n          if (++readCount === Math.floor(MAX / BATCH) + 2) readCalled.resolve();\n        },\n      });\n      let i = 0;\n      function fetchData(cb) {\n        if (i > MAX) {\n          setTimeout(cb, 10, null, []);\n        } else {\n          const array = [];\n          const max = i + BATCH;\n          for (; i < max; i++) {\n            array.push(i);\n          }\n          setTimeout(cb, 10, null, array);\n        }\n      }\n      readable.on('readable', () => {\n        let data;\n        while ((data = readable.read()) !== null) {}\n      });\n      readable.on('end', () => {\n        strictEqual(i, (Math.floor(MAX / BATCH) + 1) * BATCH);\n        ended.resolve();\n      });\n      await Promise.all([readCalled.promise, ended.promise]);\n    }\n    {\n      const readCalled = Promise.withResolvers();\n      const ended = Promise.withResolvers();\n      let readCount = 0;\n      const readable = new Readable({\n        objectMode: true,\n        read: function () {\n          fetchData((err, data) => {\n            if (err) {\n              this.destroy(err);\n              return;\n            }\n            if (data.length === 0) {\n              this.push(null);\n              return;\n            }\n            data.forEach((d) => this.push(d));\n          });\n          if (++readCount === Math.floor(MAX / BATCH) + 2) readCalled.resolve();\n        },\n      });\n      let i = 0;\n      function fetchData(cb) {\n        if (i > MAX) {\n          setTimeout(cb, 10, null, []);\n        } else {\n          const array = [];\n          const max = i + BATCH;\n          for (; i < max; i++) {\n            array.push(i);\n          }\n          setTimeout(cb, 10, null, array);\n        }\n      }\n      readable.on('data', (data) => {});\n      readable.on('end', () => {\n        strictEqual(i, (Math.floor(MAX / BATCH) + 1) * BATCH);\n        ended.resolve();\n      });\n      await Promise.all([readCalled.promise, ended.promise]);\n    }\n    {\n      const readCalled = Promise.withResolvers();\n      const ended = Promise.withResolvers();\n      let readCount = 0;\n      const readable = new Readable({\n        objectMode: true,\n        read: function () {\n          fetchData((err, data) => {\n            if (err) {\n              this.destroy(err);\n              return;\n            }\n            data.forEach((d) => this.push(d));\n            if (data[BATCH - 1] >= MAX) {\n              this.push(null);\n            }\n          });\n          if (++readCount === Math.floor(MAX / BATCH) + 1) readCalled.resolve();\n        },\n      });\n      let i = 0;\n      function fetchData(cb) {\n        const array = [];\n        const max = i + BATCH;\n        for (; i < max; i++) {\n          array.push(i);\n        }\n        setTimeout(cb, 10, null, array);\n      }\n      readable.on('data', (data) => {});\n      readable.on('end', () => {\n        strictEqual(i, (Math.floor(MAX / BATCH) + 1) * BATCH);\n        ended.resolve();\n      });\n      await Promise.all([readCalled.promise, ended.promise]);\n    }\n    {\n      const ended = Promise.withResolvers();\n      const readable = new Readable({\n        objectMode: true,\n        read() {},\n      });\n      readable.on('data', ended.reject);\n      readable.push(null);\n      let nextTickPassed = false;\n      queueMicrotask(() => {\n        nextTickPassed = true;\n      });\n      readable.on('end', () => {\n        strictEqual(nextTickPassed, true);\n        ended.resolve();\n      });\n      await ended.promise;\n    }\n    {\n      const ended = Promise.withResolvers();\n      const readCalled = Promise.withResolvers();\n      const readable = new Readable({\n        objectMode: true,\n        read: readCalled.resolve,\n      });\n      readable.on('data', (data) => {});\n      readable.on('end', ended.resolve);\n      queueMicrotask(() => {\n        readable.push('aaa');\n        readable.push(null);\n      });\n      await Promise.all([readCalled.promise, ended.promise]);\n    }\n  },\n};\n\nexport const readable_no_unneeded_readable = {\n  async test(ctrl, env, ctx) {\n    async function test(r) {\n      const wrapper = new Readable({\n        read: () => {\n          let data = r.read();\n          if (data) {\n            wrapper.push(data);\n            return;\n          }\n          r.once('readable', function () {\n            data = r.read();\n            if (data) {\n              wrapper.push(data);\n            }\n            // else: the end event should fire\n          });\n        },\n      });\n\n      r.once('end', function () {\n        wrapper.push(null);\n      });\n      const ended = Promise.withResolvers();\n      wrapper.resume();\n      wrapper.once('end', ended.resolve);\n      await ended.promise;\n    }\n    {\n      const source = new Readable({\n        read: () => {},\n      });\n      source.push('foo');\n      source.push('bar');\n      source.push(null);\n      const pt = source.pipe(new PassThrough());\n      await test(pt);\n    }\n    {\n      // This is the underlying cause of the above test case.\n      const pushChunks = ['foo', 'bar'];\n      const r = new Readable({\n        read: () => {\n          const chunk = pushChunks.shift();\n          if (chunk) {\n            // synchronous call\n            r.push(chunk);\n          } else {\n            // asynchronous call\n            queueMicrotask(() => r.push(null));\n          }\n        },\n      });\n      await test(r);\n    }\n  },\n};\n\nexport const readable_next_no_null = {\n  async test(ctrl, env, ctx) {\n    async function* generate() {\n      yield null;\n    }\n    const stream = Readable.from(generate());\n    const errored = Promise.withResolvers();\n    stream.on('error', function (err) {\n      strictEqual(err.code, 'ERR_STREAM_NULL_VALUES');\n      errored.resolve();\n    });\n    stream.on('data', errored.reject);\n    stream.on('end', errored.reject);\n    await errored.promise;\n  },\n};\n\nexport const readable_needreadable = {\n  async test(ctrl, env, ctx) {\n    const readable = new Readable({\n      read: () => {},\n    });\n\n    // Initialized to false.\n    strictEqual(readable._readableState.needReadable, false);\n    const readableCalled1 = Promise.withResolvers();\n    const ended = Promise.withResolvers();\n    readable.on('readable', () => {\n      // When the readable event fires, needReadable is reset.\n      strictEqual(readable._readableState.needReadable, false, '1');\n      readable.read();\n      readableCalled1.resolve();\n    });\n\n    // If a readable listener is attached, then a readable event is needed.\n    strictEqual(readable._readableState.needReadable, true);\n    readable.push('foo');\n    readable.push(null);\n    readable.on('end', () => {\n      // No need to emit readable anymore when the stream ends.\n      strictEqual(readable._readableState.needReadable, false, '2');\n      ended.resolve();\n    });\n    await Promise.all([readableCalled1.promise, ended.promise]);\n\n    const readableCalled2 = Promise.withResolvers();\n    const ended2 = Promise.withResolvers();\n    const dataCalled = Promise.withResolvers();\n    let readableCount = 0;\n    let dataCount = 0;\n    const asyncReadable = new Readable({\n      read: () => {},\n    });\n\n    asyncReadable.on('readable', () => {\n      if (asyncReadable.read() !== null) {\n        // After each read(), the buffer is empty.\n        // If the stream doesn't end now,\n        // then we need to notify the reader on future changes.\n        readableCalled2.resolve();\n      }\n    });\n    queueMicrotask(() => {\n      asyncReadable.push('foooo');\n    });\n    queueMicrotask(() => {\n      asyncReadable.push('bar');\n    });\n    queueMicrotask(() => {\n      asyncReadable.push(null);\n      strictEqual(asyncReadable._readableState.needReadable, false);\n    });\n    const flowing = new Readable({\n      read: () => {},\n    });\n\n    // Notice this must be above the on('data') call.\n    flowing.push('foooo');\n    flowing.push('bar');\n    flowing.push('quo');\n    queueMicrotask(() => {\n      flowing.push(null);\n    });\n\n    // When the buffer already has enough data, and the stream is\n    // in flowing mode, there is no need for the readable event.\n    flowing.on('data', function (data) {\n      strictEqual(flowing._readableState.needReadable, false);\n      if (++dataCount === 3) dataCalled.resolve();\n    });\n    await Promise.all([\n      readableCalled2.promise,\n      dataCalled.promise,\n      ended.promise,\n    ]);\n\n    // const slowProducer = new Readable({\n    //   read: () => {}\n    // });\n    // const readableCalled3 = Promise.withResolvers();;\n    // readableCount = 0;\n    // slowProducer.on(\n    //   'readable',\n    //   () => {\n    //     const chunk = slowProducer.read(8);\n    //     const state = slowProducer._readableState;\n    //     if (chunk === null) {\n    //       // The buffer doesn't have enough data, and the stream is not need,\n    //       // we need to notify the reader when data arrives.\n    //       strictEqual(state.needReadable, true);\n    //     } else {\n    //       strictEqual(state.needReadable, false);\n    //     }\n    //     if (++readableCount === 4) readableCalled3.resolve();\n    //   }\n    // );\n    // queueMicrotask(\n    //   () => {\n    //     slowProducer.push('foo')\n    //     queueMicrotask(\n    //       () => {\n    //         slowProducer.push('foo')\n    //         queueMicrotask(\n    //           () => {\n    //             slowProducer.push('foo')\n    //             queueMicrotask(\n    //               () => {\n    //                 slowProducer.push(null)\n    //               }\n    //             );\n    //           }\n    //         );\n    //       }\n    //     );\n    //   }\n    // );\n  },\n};\n\nexport const readable_invalid_chunk = {\n  async test(ctrl, env, ctx) {\n    async function testPushArg(val) {\n      const readable = new Readable({\n        read: () => {},\n      });\n      const errored = Promise.withResolvers();\n      readable.on('error', function (err) {\n        strictEqual(err.code, 'ERR_INVALID_ARG_TYPE');\n        errored.resolve();\n      });\n      readable.push(val);\n      await errored.promise;\n    }\n    await Promise.all([testPushArg([]), testPushArg({}), testPushArg(0)]);\n    async function testUnshiftArg(val) {\n      const readable = new Readable({\n        read: () => {},\n      });\n      const errored = Promise.withResolvers();\n      readable.on('error', function (err) {\n        strictEqual(err.code, 'ERR_INVALID_ARG_TYPE');\n        errored.resolve();\n      });\n      readable.unshift(val);\n      await errored.promise;\n    }\n    await Promise.all([\n      testUnshiftArg([]),\n      testUnshiftArg({}),\n      testUnshiftArg(0),\n    ]);\n  },\n};\n\nexport const readable_hwm_0 = {\n  async test(ctrl, env, ctx) {\n    const readableCalled = Promise.withResolvers();\n    const readCalled = Promise.withResolvers();\n    const ended = Promise.withResolvers();\n    const r = new Readable({\n      // Must be called only once upon setting 'readable' listener\n      read: readCalled.resolve,\n      highWaterMark: 0,\n    });\n    let pushedNull = false;\n    // This will trigger read(0) but must only be called after push(null)\n    // because the we haven't pushed any data\n    r.on('readable', () => {\n      strictEqual(r.read(), null);\n      strictEqual(pushedNull, true);\n      readableCalled.resolve();\n    });\n    r.on('end', ended.resolve);\n    queueMicrotask(() => {\n      strictEqual(r.read(), null);\n      pushedNull = true;\n      r.push(null);\n    });\n    await Promise.all([\n      readableCalled.promise,\n      readCalled.promise,\n      ended.promise,\n    ]);\n  },\n};\n\nexport const readable_hwm_0_async = {\n  async test(ctrl, env, ctx) {\n    let count = 5;\n    const readCalled = Promise.withResolvers();\n    const ended = Promise.withResolvers();\n    const dataCalled = Promise.withResolvers();\n    let readCount = 0;\n    let dataCount = 0;\n    const r = new Readable({\n      // Called 6 times: First 5 return data, last one signals end of stream.\n      read: () => {\n        queueMicrotask(() => {\n          if (count--) r.push('a');\n          else r.push(null);\n        });\n        if (++readCount === 6) readCalled.resolve();\n      },\n      highWaterMark: 0,\n    });\n    r.on('end', ended.resolve);\n    r.on('data', () => {\n      if (++dataCount === 5) dataCalled.resolve();\n    });\n    await Promise.all([readCalled.promise, ended.promise, dataCalled.promise]);\n  },\n};\n\nexport const readable_event = {\n  async test(ctrl, env, ctx) {\n    {\n      // First test, not reading when the readable is added.\n      // make sure that on('readable', ...) triggers a readable event.\n      const r = new Readable({\n        highWaterMark: 3,\n      });\n      const readableCalled = Promise.withResolvers();\n      r._read = readableCalled.reject;\n\n      // This triggers a 'readable' event, which is lost.\n      r.push(Buffer.from('blerg'));\n      setTimeout(function () {\n        // We're testing what we think we are\n        ok(!r._readableState.reading);\n        r.on('readable', readableCalled.resolve);\n      }, 1);\n      await readableCalled.promise;\n    }\n    {\n      // Second test, make sure that readable is re-emitted if there's\n      // already a length, while it IS reading.\n\n      const r = new Readable({\n        highWaterMark: 3,\n      });\n      const readCalled = Promise.withResolvers();\n      const readableCalled = Promise.withResolvers();\n      r._read = readCalled.resolve;\n\n      // This triggers a 'readable' event, which is lost.\n      r.push(Buffer.from('bl'));\n      setTimeout(function () {\n        // Assert we're testing what we think we are\n        ok(r._readableState.reading);\n        r.on('readable', readableCalled.resolve);\n      }, 1);\n      await Promise.all([readCalled.promise, readableCalled.promise]);\n    }\n    {\n      // Third test, not reading when the stream has not passed\n      // the highWaterMark but *has* reached EOF.\n      const r = new Readable({\n        highWaterMark: 30,\n      });\n\n      // This triggers a 'readable' event, which is lost.\n      r.push(Buffer.from('blerg'));\n      r.push(null);\n      const readableCalled = Promise.withResolvers();\n      r._read = readableCalled.reject;\n      setTimeout(function () {\n        // Assert we're testing what we think we are\n        ok(!r._readableState.reading);\n        r.on('readable', readableCalled.resolve);\n      }, 1);\n      await readableCalled.promise;\n    }\n    {\n      // Pushing an empty string in non-objectMode should\n      // trigger next `read()`.\n      const underlyingData = ['', 'x', 'y', '', 'z'];\n      const expected = underlyingData.filter((data) => data);\n      const result = [];\n      const r = new Readable({\n        encoding: 'utf8',\n      });\n      r._read = function () {\n        queueMicrotask(() => {\n          if (!underlyingData.length) {\n            this.push(null);\n          } else {\n            this.push(underlyingData.shift());\n          }\n        });\n      };\n      r.on('readable', () => {\n        const data = r.read();\n        if (data !== null) result.push(data);\n      });\n      const ended = Promise.withResolvers();\n      r.on('end', () => {\n        deepStrictEqual(result, expected);\n        ended.resolve();\n      });\n      await ended.promise;\n    }\n    {\n      // #20923\n      const r = new Readable();\n      r._read = function () {\n        // Actually doing thing here\n      };\n      r.on('data', function () {});\n      r.removeAllListeners();\n      strictEqual(r.eventNames().length, 0);\n    }\n  },\n};\n\nexport const readable_error_end = {\n  async test(ctrl, env, ctx) {\n    const r = new Readable({\n      read() {},\n    });\n    const data = Promise.withResolvers();\n    const errored = Promise.withResolvers();\n    r.on('end', errored.reject);\n    r.on('data', data.resolve);\n    r.on('error', errored.resolve);\n    r.push('asd');\n    r.push(null);\n    r.destroy(new Error('kaboom'));\n    await Promise.all([data.promise, errored.promise]);\n  },\n};\n\nexport const readable_ended = {\n  async test(ctrl, env, ctx) {\n    {\n      // Find it on Readable.prototype\n      ok(Reflect.has(Readable.prototype, 'readableEnded'));\n    }\n\n    // event\n    {\n      const readable = new Readable();\n      readable._read = () => {\n        // The state ended should start in false.\n        strictEqual(readable.readableEnded, false);\n        readable.push('asd');\n        strictEqual(readable.readableEnded, false);\n        readable.push(null);\n        strictEqual(readable.readableEnded, false);\n      };\n      const ended = Promise.withResolvers();\n      const dataCalled = Promise.withResolvers();\n      readable.on('end', () => {\n        strictEqual(readable.readableEnded, true);\n        ended.resolve();\n      });\n      readable.on('data', () => {\n        strictEqual(readable.readableEnded, false);\n        dataCalled.resolve();\n      });\n      await Promise.all([ended.promise, dataCalled.promise]);\n    }\n\n    // Verifies no `error` triggered on multiple .push(null) invocations\n    {\n      const readable = new Readable();\n      readable.on('readable', () => {\n        readable.read();\n      });\n      const ended = Promise.withResolvers();\n      readable.on('error', ended.reject);\n      readable.on('end', ended.resolve);\n      readable.push('a');\n      readable.push(null);\n      readable.push(null);\n      await ended.promise;\n    }\n  },\n};\n\nexport const readable_end_destroyed = {\n  async test(ctrl, env, ctx) {\n    const r = new Readable();\n    const closed = Promise.withResolvers();\n    r.on('end', fail);\n    r.resume();\n    r.destroy();\n    r.on('close', () => {\n      r.push(null);\n      closed.resolve();\n    });\n    await closed.promise;\n  },\n};\n\nexport const readable_short_stream = {\n  async test(ctrl, env, ctx) {\n    {\n      const readCalled = Promise.withResolvers();\n      const transformCalled = Promise.withResolvers();\n      const flushCalled = Promise.withResolvers();\n      const readableCalled = Promise.withResolvers();\n      let readableCount = 0;\n      const r = new Readable({\n        read: function () {\n          this.push('content');\n          this.push(null);\n          readCalled.resolve();\n        },\n      });\n      const t = new Transform({\n        transform: function (chunk, encoding, callback) {\n          transformCalled.resolve();\n          this.push(chunk);\n          return callback();\n        },\n        flush: function (callback) {\n          flushCalled.resolve();\n          return callback();\n        },\n      });\n      r.pipe(t);\n      t.on('readable', function () {\n        while (true) {\n          const chunk = t.read();\n          if (!chunk) break;\n          strictEqual(chunk.toString(), 'content');\n        }\n        if (++readableCount === 2) readableCalled.resolve();\n      });\n      await Promise.all([\n        readCalled.promise,\n        transformCalled.promise,\n        flushCalled.promise,\n        readableCalled.promise,\n      ]);\n    }\n    {\n      const transformCalled = Promise.withResolvers();\n      const flushCalled = Promise.withResolvers();\n      const readableCalled = Promise.withResolvers();\n      const t = new Transform({\n        transform: function (chunk, encoding, callback) {\n          transformCalled.resolve();\n          this.push(chunk);\n          return callback();\n        },\n        flush: function (callback) {\n          flushCalled.resolve();\n          return callback();\n        },\n      });\n      t.end('content');\n      t.on('readable', function () {\n        while (true) {\n          const chunk = t.read();\n          if (!chunk) break;\n          strictEqual(chunk.toString(), 'content');\n        }\n        readableCalled.resolve();\n      });\n      await Promise.all([\n        transformCalled.promise,\n        flushCalled.promise,\n        readableCalled.promise,\n      ]);\n    }\n    {\n      const transformCalled = Promise.withResolvers();\n      const flushCalled = Promise.withResolvers();\n      const readableCalled = Promise.withResolvers();\n      const t = new Transform({\n        transform: function (chunk, encoding, callback) {\n          transformCalled.resolve();\n          this.push(chunk);\n          return callback();\n        },\n        flush: function (callback) {\n          flushCalled.resolve();\n          return callback();\n        },\n      });\n      t.write('content');\n      t.end();\n      t.on('readable', function () {\n        while (true) {\n          const chunk = t.read();\n          if (!chunk) break;\n          strictEqual(chunk.toString(), 'content');\n          readableCalled.resolve();\n        }\n      });\n      await Promise.all([\n        transformCalled.promise,\n        flushCalled.promise,\n        readableCalled.promise,\n      ]);\n    }\n    {\n      const t = new Readable({\n        read() {},\n      });\n      const readableCalled = Promise.withResolvers();\n      t.on('readable', function () {\n        while (true) {\n          const chunk = t.read();\n          if (!chunk) break;\n          strictEqual(chunk.toString(), 'content');\n        }\n        readableCalled.resolve();\n      });\n      t.push('content');\n      t.push(null);\n      await readableCalled.promise;\n    }\n    {\n      const t = new Readable({\n        read() {},\n      });\n      const readableCalled = Promise.withResolvers();\n      let readableCount = 0;\n      t.on('readable', function () {\n        while (true) {\n          const chunk = t.read();\n          if (!chunk) break;\n          strictEqual(chunk.toString(), 'content');\n        }\n        if (++readableCount === 2) readableCalled.resolve();\n      });\n      queueMicrotask(() => {\n        t.push('content');\n        t.push(null);\n      });\n      await readableCalled.promise;\n    }\n    {\n      const transformCalled = Promise.withResolvers();\n      const flushCalled = Promise.withResolvers();\n      const readableCalled = Promise.withResolvers();\n      let readableCount = 0;\n      const t = new Transform({\n        transform: function (chunk, encoding, callback) {\n          transformCalled.resolve();\n          this.push(chunk);\n          return callback();\n        },\n        flush: function (callback) {\n          flushCalled.resolve();\n          return callback();\n        },\n      });\n      t.on('readable', function () {\n        while (true) {\n          const chunk = t.read();\n          if (!chunk) break;\n          strictEqual(chunk.toString(), 'content');\n        }\n        if (++readableCount === 2) readableCalled.resolve();\n      });\n      t.write('content');\n      t.end();\n      await Promise.all([\n        transformCalled.promise,\n        flushCalled.promise,\n        readableCalled.promise,\n      ]);\n    }\n  },\n};\n\nexport const readable_didread = {\n  async test(ctrl, env, ctx) {\n    function noop() {}\n    async function check(readable, data, fn) {\n      const closed = Promise.withResolvers();\n      strictEqual(readable.readableDidRead, false);\n      strictEqual(isDisturbed(readable), false);\n      strictEqual(isErrored(readable), false);\n      if (data === -1) {\n        readable.on('error', () => {\n          strictEqual(isErrored(readable), true);\n        });\n        readable.on('data', fail);\n        readable.on('end', fail);\n      } else {\n        readable.on('error', fail);\n        if (data === -2) {\n          readable.on('end', fail);\n        } else {\n          readable.on('end', () => {});\n        }\n        if (data > 0) {\n          readable.on('data', () => {});\n        } else {\n          readable.on('data', fail);\n        }\n      }\n      readable.on('close', closed.resolve);\n      fn();\n      queueMicrotask(() => {\n        strictEqual(readable.readableDidRead, data > 0);\n        if (data > 0) {\n          strictEqual(isDisturbed(readable), true);\n        }\n      });\n      await closed.promise;\n    }\n    {\n      const readable = new Readable({\n        read() {\n          this.push(null);\n        },\n      });\n      await check(readable, 0, () => {\n        readable.read();\n      });\n    }\n    {\n      const readable = new Readable({\n        read() {\n          this.push(null);\n        },\n      });\n      await check(readable, 0, () => {\n        readable.resume();\n      });\n    }\n    {\n      const readable = new Readable({\n        read() {\n          this.push(null);\n        },\n      });\n      await check(readable, -2, () => {\n        readable.destroy();\n      });\n    }\n    {\n      const readable = new Readable({\n        read() {\n          this.push(null);\n        },\n      });\n      await check(readable, -1, () => {\n        readable.destroy(new Error());\n      });\n    }\n    {\n      const readable = new Readable({\n        read() {\n          this.push('data');\n          this.push(null);\n        },\n      });\n      await check(readable, 1, () => {\n        readable.on('data', noop);\n      });\n    }\n    {\n      const readable = new Readable({\n        read() {\n          this.push('data');\n          this.push(null);\n        },\n      });\n      await check(readable, 1, () => {\n        readable.on('data', noop);\n        readable.off('data', noop);\n      });\n    }\n  },\n};\n\nexport const readable_destroy = {\n  async test(ctrl, env, ctx) {\n    {\n      const closed = Promise.withResolvers();\n      const read = new Readable({\n        read() {},\n      });\n      read.resume();\n      read.on('close', closed.resolve);\n      read.destroy();\n      strictEqual(read.errored, null);\n      strictEqual(read.destroyed, true);\n      await closed.promise;\n    }\n    {\n      const read = new Readable({\n        read() {},\n      });\n      const closed = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      read.resume();\n      const expected = new Error('kaboom');\n      read.on('end', closed.reject);\n      read.on('close', closed.resolve);\n      read.on('error', (err) => {\n        strictEqual(err, expected);\n        errored.resolve();\n      });\n      read.destroy(expected);\n      strictEqual(read.errored, expected);\n      strictEqual(read.destroyed, true);\n      await Promise.all([closed.promise, errored.promise]);\n    }\n    {\n      const destroyCalled = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      const read = new Readable({\n        read() {},\n      });\n      read._destroy = function (err, cb) {\n        strictEqual(err, expected);\n        cb(err);\n        destroyCalled.resolve();\n      };\n      const expected = new Error('kaboom');\n      read.on('end', closed.reject);\n      read.on('close', closed.resolve);\n      read.on('error', (err) => {\n        strictEqual(err, expected);\n        errored.resolve();\n      });\n      read.destroy(expected);\n      strictEqual(read.destroyed, true);\n      await Promise.all([\n        destroyCalled.promise,\n        closed.promise,\n        errored.promise,\n      ]);\n    }\n    {\n      const destroyCalled = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      const read = new Readable({\n        read() {},\n        destroy: function (err, cb) {\n          strictEqual(err, expected);\n          cb();\n          destroyCalled.resolve();\n        },\n      });\n      const expected = new Error('kaboom');\n      read.on('end', closed.reject);\n\n      // Error is swallowed by the custom _destroy\n      read.on('error', fail);\n      read.on('close', closed.resolve);\n      read.destroy(expected);\n      strictEqual(read.destroyed, true);\n      await Promise.all([destroyCalled.promise, closed.promise]);\n    }\n    {\n      const read = new Readable({\n        read() {},\n      });\n      const destroyCalled = Promise.withResolvers();\n      read._destroy = function (err, cb) {\n        strictEqual(err, null);\n        cb();\n        destroyCalled.resolve();\n      };\n      read.destroy();\n      strictEqual(read.destroyed, true);\n      await destroyCalled.promise;\n    }\n    {\n      const read = new Readable({\n        read() {},\n      });\n      read.resume();\n      const closed = Promise.withResolvers();\n      const destroyCalled = Promise.withResolvers();\n      read._destroy = function (err, cb) {\n        strictEqual(err, null);\n        queueMicrotask(() => {\n          this.push(null);\n          cb();\n          destroyCalled.resolve();\n        });\n      };\n      read.on('end', fail);\n      read.on('close', closed.resolve);\n      read.destroy();\n      read.removeListener('end', fail);\n      read.on('end', closed.reject);\n      strictEqual(read.destroyed, true);\n      await Promise.all([closed.promise, destroyCalled.promise]);\n    }\n    {\n      const read = new Readable({\n        read() {},\n      });\n      const expected = new Error('kaboom');\n      const destroyCalled = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      read._destroy = function (err, cb) {\n        strictEqual(err, null);\n        cb(expected);\n        destroyCalled.resolve();\n      };\n      let ticked = false;\n      read.on('end', closed.reject);\n      read.on('close', closed.resolve);\n      read.on('error', (err) => {\n        strictEqual(ticked, true);\n        strictEqual(read._readableState.errorEmitted, true);\n        strictEqual(read._readableState.errored, expected);\n        strictEqual(err, expected);\n        errored.resolve();\n      });\n      read.destroy();\n      strictEqual(read._readableState.errorEmitted, false);\n      strictEqual(read._readableState.errored, expected);\n      strictEqual(read.destroyed, true);\n      ticked = true;\n      await Promise.all([\n        destroyCalled.promise,\n        closed.promise,\n        errored.promise,\n      ]);\n    }\n    {\n      function MyReadable() {\n        strictEqual(this.destroyed, false);\n        this.destroyed = false;\n        Readable.call(this);\n      }\n      Object.setPrototypeOf(MyReadable.prototype, Readable.prototype);\n      Object.setPrototypeOf(MyReadable, Readable);\n      new MyReadable();\n    }\n    {\n      // Destroy and destroy callback\n      const read = new Readable({\n        read() {},\n      });\n      read.resume();\n      const expected = new Error('kaboom');\n      let ticked = false;\n      const closed = Promise.withResolvers();\n      const destroyCalled = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      read.on('close', () => {\n        strictEqual(read._readableState.errorEmitted, true);\n        strictEqual(ticked, true);\n        closed.resolve();\n      });\n      read.on('error', (err) => {\n        strictEqual(err, expected);\n        errored.resolve();\n      });\n      strictEqual(read._readableState.errored, null);\n      strictEqual(read._readableState.errorEmitted, false);\n      read.destroy(expected, function (err) {\n        strictEqual(read._readableState.errored, expected);\n        strictEqual(err, expected);\n        destroyCalled.resolve();\n      });\n      strictEqual(read._readableState.errorEmitted, false);\n      strictEqual(read._readableState.errored, expected);\n      ticked = true;\n      await Promise.all([\n        closed.promise,\n        destroyCalled.promise,\n        errored.promise,\n      ]);\n    }\n    {\n      const destroyCalled = Promise.withResolvers();\n      const closed = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      const readable = new Readable({\n        destroy: function (err, cb) {\n          queueMicrotask(() => cb(new Error('kaboom 1')));\n          destroyCalled.resolve();\n        },\n        read() {},\n      });\n      let ticked = false;\n      readable.on('close', () => {\n        strictEqual(ticked, true);\n        strictEqual(readable._readableState.errorEmitted, true);\n        closed.resolve();\n      });\n      readable.on('error', (err) => {\n        strictEqual(ticked, true);\n        strictEqual(err.message, 'kaboom 1');\n        strictEqual(readable._readableState.errorEmitted, true);\n        errored.resolve();\n      });\n      readable.destroy();\n      strictEqual(readable.destroyed, true);\n      strictEqual(readable._readableState.errored, null);\n      strictEqual(readable._readableState.errorEmitted, false);\n\n      // Test case where `readable.destroy()` is called again with an error before\n      // the `_destroy()` callback is called.\n      readable.destroy(new Error('kaboom 2'));\n      strictEqual(readable._readableState.errorEmitted, false);\n      strictEqual(readable._readableState.errored, null);\n      ticked = true;\n\n      await Promise.all([\n        destroyCalled.promise,\n        closed.promise,\n        errored.promise,\n      ]);\n    }\n    {\n      const read = new Readable({\n        read() {},\n      });\n      const closed = Promise.withResolvers();\n      read.destroy();\n      read.push('hi');\n      read.on('data', fail);\n      read.on('close', closed.resolve);\n      await closed.promise;\n    }\n    {\n      const closed = Promise.withResolvers();\n      const read = new Readable({\n        read: closed.reject,\n      });\n      read.on('close', closed.resolve);\n      read.destroy();\n      strictEqual(read.destroyed, true);\n      read.read();\n      await closed.promise;\n    }\n    {\n      const read = new Readable({\n        autoDestroy: false,\n        read() {\n          this.push(null);\n          this.push('asd');\n        },\n      });\n      const errored = Promise.withResolvers();\n      read.on('error', () => {\n        ok(read._readableState.errored);\n        errored.resolve();\n      });\n      read.resume();\n      await errored.promise;\n    }\n    {\n      const controller = new AbortController();\n      const read = addAbortSignal(\n        controller.signal,\n        new Readable({\n          read() {\n            this.push('asd');\n          },\n        })\n      );\n      const closed = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      read.on('error', (e) => {\n        strictEqual(e.name, 'AbortError');\n        errored.resolve();\n      });\n      controller.abort();\n      read.on('data', closed.reject);\n      read.on('close', closed.resolve);\n      await Promise.all([closed.promise, errored.promise]);\n    }\n    {\n      const controller = new AbortController();\n      const read = new Readable({\n        signal: controller.signal,\n        read() {\n          this.push('asd');\n        },\n      });\n      const closed = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      read.on('error', (e) => {\n        strictEqual(e.name, 'AbortError');\n        errored.resolve();\n      });\n      controller.abort();\n      read.on('data', closed.reject);\n      read.on('close', closed.resolve);\n      await Promise.all([closed.promise, errored.promise]);\n    }\n    {\n      const controller = new AbortController();\n      const read = addAbortSignal(\n        controller.signal,\n        new Readable({\n          objectMode: true,\n          read() {\n            return false;\n          },\n        })\n      );\n      read.push('asd');\n      const errored = Promise.withResolvers();\n      read.on('error', (e) => {\n        strictEqual(e.name, 'AbortError');\n        errored.resolve();\n      });\n      const rejected = rejects(\n        (async () => {\n          // eslint-disable-next-line no-unused-vars, no-empty\n          for await (const chunk of read) {\n          }\n        })(),\n        /AbortError/\n      );\n      setTimeout(() => controller.abort(), 0);\n      await Promise.all([errored.promise, rejected]);\n    }\n    {\n      const read = new Readable({\n        read() {},\n      });\n      const closed = Promise.withResolvers();\n      const errored = Promise.withResolvers();\n      read.on('data', closed.reject);\n      read.on('error', (e) => {\n        read.push('asd');\n        read.read();\n        errored.resolve();\n      });\n      read.on('close', (e) => {\n        read.push('asd');\n        read.read();\n        closed.resolve();\n      });\n      read.destroy(new Error('asd'));\n      await Promise.all([closed.promise, errored.promise]);\n    }\n    {\n      const read = new Readable({\n        read() {},\n      });\n      const closed = Promise.withResolvers();\n      read.on('data', closed.reject);\n      read.on('close', (e) => {\n        read.push('asd');\n        read.read();\n        closed.resolve();\n      });\n      read.destroy();\n      await closed.promise;\n    }\n    {\n      const read = new Readable({\n        read() {},\n      });\n      const closed = Promise.withResolvers();\n      read.on('data', closed.reject);\n      read.on('close', (e) => {\n        read.push('asd');\n        read.unshift('asd');\n        closed.resolve();\n      });\n      read.destroy();\n      await closed.promise;\n    }\n    {\n      const read = new Readable({\n        read() {},\n      });\n      const closed = Promise.withResolvers();\n      read.on('data', closed.reject);\n      read.on('close', closed.resolve);\n      read.destroy();\n      read.unshift('asd');\n      await closed.promise;\n    }\n    {\n      const read = new Readable({\n        read() {},\n      });\n      read.resume();\n      const closed = Promise.withResolvers();\n      read.on('data', closed.reject);\n      read.on('close', (e) => {\n        read.push('asd');\n        closed.resolve();\n      });\n      read.destroy();\n      await closed.promise;\n    }\n    {\n      const read = new Readable({\n        read() {},\n      });\n      const closed = Promise.withResolvers();\n      read.on('data', closed.reject);\n      read.on('close', closed.resolve);\n      read.destroy();\n      read.push('asd');\n      await closed.promise;\n    }\n  },\n};\n\nexport const readable_data = {\n  async test(ctrl, env, ctx) {\n    const readable = new Readable({\n      read() {},\n    });\n    function read() {}\n    readable.setEncoding('utf8');\n    readable.on('readable', read);\n    readable.removeListener('readable', read);\n    const dataCalled = Promise.withResolvers();\n    queueMicrotask(function () {\n      readable.on('data', dataCalled.resolve);\n      readable.push('hello');\n    });\n    await dataCalled.promise;\n  },\n};\n\nexport const readable_constructor_set_methods = {\n  async test(ctrl, env, ctx) {\n    const readCalled = Promise.withResolvers();\n    const _read = function _read(n) {\n      this.push(null);\n      readCalled.resolve();\n    };\n    const r = new Readable({\n      read: _read,\n    });\n    r.resume();\n    await readCalled.promise;\n  },\n};\n\nexport const readable_add_chunk_during_data = {\n  async test(ctrl, env, ctx) {\n    const dataCalled = Promise.withResolvers();\n    let dataCount = 0;\n    for (const method of ['push', 'unshift']) {\n      const r = new Readable({\n        read() {},\n      });\n      r.once('data', (chunk) => {\n        strictEqual(r.readableLength, 0);\n        r[method](chunk);\n        strictEqual(r.readableLength, chunk.length);\n        r.on('data', (chunk) => {\n          strictEqual(chunk.toString(), 'Hello, world');\n          if (++dataCount === 2) dataCalled.resolve();\n        });\n      });\n      r.push('Hello, world');\n    }\n  },\n};\n\nexport const readable_aborted = {\n  async test(ctrl, env, ctx) {\n    {\n      const readable = new Readable({\n        read() {},\n      });\n      strictEqual(readable.readableAborted, false);\n      readable.destroy();\n      strictEqual(readable.readableAborted, true);\n    }\n    {\n      const readable = new Readable({\n        read() {},\n      });\n      strictEqual(readable.readableAborted, false);\n      readable.push(null);\n      readable.destroy();\n      strictEqual(readable.readableAborted, true);\n    }\n    {\n      const readable = new Readable({\n        read() {},\n      });\n      strictEqual(readable.readableAborted, false);\n      readable.push('asd');\n      readable.destroy();\n      strictEqual(readable.readableAborted, true);\n    }\n    {\n      const readable = new Readable({\n        read() {},\n      });\n      strictEqual(readable.readableAborted, false);\n      readable.push('asd');\n      readable.push(null);\n      strictEqual(readable.readableAborted, false);\n      const ended = Promise.withResolvers();\n      readable.on('end', () => {\n        strictEqual(readable.readableAborted, false);\n        readable.destroy();\n        strictEqual(readable.readableAborted, false);\n        queueMicrotask(() => {\n          strictEqual(readable.readableAborted, false);\n          ended.resolve();\n        });\n      });\n      readable.resume();\n      await ended.promise;\n    }\n    {\n      const duplex = new Duplex({\n        readable: false,\n        write() {},\n      });\n      duplex.destroy();\n      strictEqual(duplex.readableAborted, false);\n    }\n  },\n};\n\nexport const push_strings = {\n  async test(ctrl, env, ctx) {\n    class MyStream extends Readable {\n      constructor(options) {\n        super(options);\n        this._chunks = 3;\n      }\n      _read(n) {\n        switch (this._chunks--) {\n          case 0:\n            return this.push(null);\n          case 1:\n            return setTimeout(() => {\n              this.push('last chunk');\n            }, 100);\n          case 2:\n            return this.push('second to last chunk');\n          case 3:\n            return queueMicrotask(() => {\n              this.push('first chunk');\n            });\n          default:\n            throw new Error('?');\n        }\n      }\n    }\n    const ms = new MyStream();\n    const results = [];\n    ms.on('readable', function () {\n      let chunk;\n      while (null !== (chunk = ms.read())) results.push(String(chunk));\n    });\n    const expect = ['first chunksecond to last chunk', 'last chunk'];\n    await promises.finished(ms);\n    strictEqual(ms._chunks, -1);\n    deepStrictEqual(results, expect);\n  },\n};\n\nexport const filter = {\n  async test(ctrl, env, ctx) {\n    {\n      // Filter works on synchronous streams with a synchronous predicate\n      const stream = Readable.from([1, 2, 3, 4, 5]).filter((x) => x < 3);\n      const result = [1, 2];\n\n      for await (const item of stream) {\n        strictEqual(item, result.shift());\n      }\n    }\n    {\n      // Filter works on synchronous streams with an asynchronous predicate\n      const stream = Readable.from([1, 2, 3, 4, 5]).filter(async (x) => {\n        await Promise.resolve();\n        return x > 3;\n      });\n      const result = [4, 5];\n      for await (const item of stream) {\n        strictEqual(item, result.shift());\n      }\n    }\n    {\n      // Map works on asynchronous streams with a asynchronous mapper\n      const stream = Readable.from([1, 2, 3, 4, 5])\n        .map(async (x) => {\n          await Promise.resolve();\n          return x + x;\n        })\n        .filter((x) => x > 5);\n      const result = [6, 8, 10];\n      for await (const item of stream) {\n        strictEqual(item, result.shift());\n      }\n    }\n    {\n      // Filter works on an infinite stream\n      const stream = Readable.from(\n        (async function* () {\n          while (true) yield 1;\n        })()\n      ).filter(async (x) => x < 3);\n      let i = 1;\n      for await (const item of stream) {\n        strictEqual(item, 1);\n        if (++i === 5) break;\n      }\n    }\n    {\n      // Filter works on constructor created streams\n      let i = 0;\n      const stream = new Readable({\n        read() {\n          if (i === 10) {\n            this.push(null);\n            return;\n          }\n          this.push(Uint8Array.from([i]));\n          i++;\n        },\n        highWaterMark: 0,\n      }).filter(async ([x]) => x !== 5);\n      const result = (await stream.toArray()).map((x) => x[0]);\n      const expected = [...Array(10).keys()].filter((x) => x !== 5);\n      deepStrictEqual(result, expected);\n    }\n    {\n      // Throwing an error during `filter` (sync)\n      const stream = Readable.from([1, 2, 3, 4, 5]).filter((x) => {\n        if (x === 3) {\n          throw new Error('boom');\n        }\n        return true;\n      });\n      await rejects(stream.map((x) => x + x).toArray(), /boom/);\n    }\n    {\n      // Throwing an error during `filter` (async)\n      const stream = Readable.from([1, 2, 3, 4, 5]).filter(async (x) => {\n        if (x === 3) {\n          throw new Error('boom');\n        }\n        return true;\n      });\n      await rejects(stream.filter(() => true).toArray(), /boom/);\n    }\n    {\n      function once(ee, event) {\n        const deferred = Promise.withResolvers();\n        ee.once(event, deferred.resolve);\n        return deferred.promise;\n      }\n      // Concurrency + AbortSignal\n      const ac = new AbortController();\n      let calls = 0;\n      const stream = Readable.from([1, 2, 3, 4]).filter(\n        async (_, { signal }) => {\n          calls++;\n          await once(signal, 'abort');\n        },\n        {\n          signal: ac.signal,\n          concurrency: 2,\n        }\n      );\n      queueMicrotask(() => ac.abort());\n      // pump\n      await rejects(\n        async () => {\n          for await (const item of stream) {\n            // nope\n          }\n        },\n        {\n          name: 'AbortError',\n        }\n      );\n    }\n    {\n      // Concurrency result order\n      const stream = Readable.from([1, 2]).filter(\n        async (item, { signal }) => {\n          await scheduler.wait(10 - item);\n          return true;\n        },\n        {\n          concurrency: 2,\n        }\n      );\n      const expected = [1, 2];\n      for await (const item of stream) {\n        strictEqual(item, expected.shift());\n      }\n    }\n    {\n      // Error cases\n      throws(() => Readable.from([1]).filter(1), /ERR_INVALID_ARG_TYPE/);\n      throws(\n        () =>\n          Readable.from([1]).filter((x) => x, {\n            concurrency: 'Foo',\n          }),\n        { code: 'ERR_OUT_OF_RANGE' }\n      );\n      throws(() => Readable.from([1]).filter((x) => x, 1), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n    }\n    {\n      // Test result is a Readable\n      const stream = Readable.from([1, 2, 3, 4, 5]).filter((x) => true);\n      strictEqual(stream.readable, true);\n    }\n  },\n};\n\nexport const pipeWithoutListenerCount = {\n  async test(ctrl, env, ctx) {\n    const r = new Stream();\n    r.listenerCount = undefined;\n    const w = new Stream();\n    w.listenerCount = undefined;\n    w.on('pipe', function () {\n      r.emit('error', new Error('Readable Error'));\n      w.emit('error', new Error('Writable Error'));\n    });\n    const rErrored = Promise.withResolvers();\n    const wErrored = Promise.withResolvers();\n    r.on('error', rErrored.resolve);\n    w.on('error', wErrored.resolve);\n    r.pipe(w);\n    await Promise.all([rErrored.promise, wErrored.promise]);\n  },\n};\n\nexport const pipeUnpipeStreams = {\n  async test(ctrl, env, ctx) {\n    const source = Readable({\n      read: () => {},\n    });\n    const dest1 = Writable({\n      write: () => {},\n    });\n    const dest2 = Writable({\n      write: () => {},\n    });\n    source.pipe(dest1);\n    source.pipe(dest2);\n\n    const unpipe1 = Promise.withResolvers();\n    const unpipe2 = Promise.withResolvers();\n\n    dest1.on('unpipe', unpipe1.resolve);\n    dest2.on('unpipe', unpipe2.resolve);\n    strictEqual(source._readableState.pipes[0], dest1);\n    strictEqual(source._readableState.pipes[1], dest2);\n    strictEqual(source._readableState.pipes.length, 2);\n\n    // Should be able to unpipe them in the reverse order that they were piped.\n\n    source.unpipe(dest2);\n    deepStrictEqual(source._readableState.pipes, [dest1]);\n    notStrictEqual(source._readableState.pipes, dest2);\n    dest2.on('unpipe', fail);\n    source.unpipe(dest2);\n    source.unpipe(dest1);\n    strictEqual(source._readableState.pipes.length, 0);\n    {\n      // Test `cleanup()` if we unpipe all streams.\n      const source = Readable({\n        read: () => {},\n      });\n      const dest1 = Writable({\n        write: () => {},\n      });\n      const dest2 = Writable({\n        write: () => {},\n      });\n      let destCount = 0;\n      const srcCheckEventNames = ['end', 'data'];\n      const destCheckEventNames = [\n        'close',\n        'finish',\n        'drain',\n        'error',\n        'unpipe',\n      ];\n      const checkSrcCleanup = () => {\n        strictEqual(source._readableState.pipes.length, 0);\n        strictEqual(source._readableState.flowing, false);\n        srcCheckEventNames.forEach((eventName) => {\n          strictEqual(\n            source.listenerCount(eventName),\n            0,\n            `source's '${eventName}' event listeners not removed`\n          );\n        });\n      };\n      async function checkDestCleanup(dest) {\n        const done = Promise.withResolvers();\n        const currentDestId = ++destCount;\n        source.pipe(dest);\n        const unpipeChecker = () => {\n          destCheckEventNames.forEach((eventName) => {\n            strictEqual(\n              dest.listenerCount(eventName),\n              0,\n              `destination{${currentDestId}}'s '${eventName}' event ` +\n                'listeners not removed'\n            );\n          });\n          if (--destCount === 0) checkSrcCleanup();\n          done.resolve();\n        };\n        dest.once('unpipe', unpipeChecker);\n        await done.promise;\n      }\n      const p1 = checkDestCleanup(dest1);\n      const p2 = checkDestCleanup(dest2);\n      source.unpipe();\n      await Promise.all([p1, p2, unpipe1.promise, unpipe2.promise]);\n    }\n    {\n      const src = Readable({\n        read: () => {},\n      });\n      const dst = Writable({\n        write: () => {},\n      });\n      src.pipe(dst);\n      const resumeCalled = Promise.withResolvers();\n      src.on('resume', () => {\n        src.on('pause', resumeCalled.resolve);\n        src.unpipe(dst);\n      });\n      await resumeCalled.promise;\n    }\n  },\n};\n\nexport const pipeSameDestinationTwice = {\n  async test(ctrl, env, ctx) {\n    // Regression test for https://github.com/nodejs/node/issues/12718.\n    // Tests that piping a source stream twice to the same destination stream\n    // works, and that a subsequent unpipe() call only removes the pipe *once*.\n    {\n      const passThrough = new PassThrough();\n      const writeCalled = Promise.withResolvers();\n      const dest = new Writable({\n        write: (chunk, encoding, cb) => {\n          strictEqual(`${chunk}`, 'foobar');\n          cb();\n          writeCalled.resolve();\n        },\n      });\n      passThrough.pipe(dest);\n      passThrough.pipe(dest);\n      strictEqual(passThrough._events.data.length, 2);\n      strictEqual(passThrough._readableState.pipes.length, 2);\n      strictEqual(passThrough._readableState.pipes[0], dest);\n      strictEqual(passThrough._readableState.pipes[1], dest);\n      passThrough.unpipe(dest);\n      strictEqual(passThrough._events.data.length, 1);\n      strictEqual(passThrough._readableState.pipes.length, 1);\n      deepStrictEqual(passThrough._readableState.pipes, [dest]);\n      passThrough.write('foobar');\n      passThrough.pipe(dest);\n      await writeCalled.promise;\n    }\n    {\n      const passThrough = new PassThrough();\n      const writeCalled = Promise.withResolvers();\n      let writeCount = 0;\n      const dest = new Writable({\n        write: (chunk, encoding, cb) => {\n          strictEqual(`${chunk}`, 'foobar');\n          cb();\n          if (++writeCount == 2) writeCalled.resolve();\n        },\n      });\n      passThrough.pipe(dest);\n      passThrough.pipe(dest);\n      strictEqual(passThrough._events.data.length, 2);\n      strictEqual(passThrough._readableState.pipes.length, 2);\n      strictEqual(passThrough._readableState.pipes[0], dest);\n      strictEqual(passThrough._readableState.pipes[1], dest);\n      passThrough.write('foobar');\n      await writeCalled.promise;\n    }\n    {\n      const passThrough = new PassThrough();\n      const dest = new Writable({\n        write: fail,\n      });\n      passThrough.pipe(dest);\n      passThrough.pipe(dest);\n      strictEqual(passThrough._events.data.length, 2);\n      strictEqual(passThrough._readableState.pipes.length, 2);\n      strictEqual(passThrough._readableState.pipes[0], dest);\n      strictEqual(passThrough._readableState.pipes[1], dest);\n      passThrough.unpipe(dest);\n      passThrough.unpipe(dest);\n      strictEqual(passThrough._events.data, undefined);\n      strictEqual(passThrough._readableState.pipes.length, 0);\n      passThrough.write('foobar');\n    }\n  },\n};\n\nexport const pipeNeedDrain = {\n  async test(ctrl, env, ctx) {\n    // Pipe should pause temporarily if writable needs drain.\n    const w = new Writable({\n      write(buf, encoding, callback) {\n        queueMicrotask(callback);\n      },\n      highWaterMark: 1,\n    });\n    while (w.write('asd'));\n    strictEqual(w.writableNeedDrain, true);\n    const r = new Readable({\n      read() {\n        this.push('asd');\n        this.push(null);\n      },\n    });\n    const pauseCalled = Promise.withResolvers();\n    const endCalled = Promise.withResolvers();\n    let pauseCount = 0;\n    r.on('pause', () => {\n      if (++pauseCount === 2) pauseCalled.resolve();\n    });\n    r.on('end', endCalled.resolve);\n    r.pipe(w);\n    await Promise.all([pauseCalled.promise, endCalled.promise]);\n  },\n};\n\nexport const pipeMultiplePipes = {\n  async test(ctrl, env, ctx) {\n    const readable = new Readable({\n      read: () => {},\n    });\n    const writables = [];\n    const promises = [];\n    for (let i = 0; i < 5; i++) {\n      const writeCalled = Promise.withResolvers();\n      promises.push(writeCalled.promise);\n      const target = new Writable({\n        write: (chunk, encoding, callback) => {\n          target.output.push(chunk);\n          callback();\n          writeCalled.resolve();\n        },\n      });\n      const pipeCalled = Promise.withResolvers();\n      promises.push(pipeCalled.promise);\n      target.output = [];\n      target.on('pipe', pipeCalled.resolve);\n      readable.pipe(target);\n      writables.push(target);\n    }\n    const input = Buffer.from([1, 2, 3, 4, 5]);\n    readable.push(input);\n\n    await Promise.all(promises);\n\n    // The pipe() calls will postpone emission of the 'resume' event using nextTick,\n    // so no data will be available to the writable streams until then.\n\n    for (const target of writables) {\n      deepStrictEqual(target.output, [input]);\n      readable.unpipe(target);\n    }\n    readable.push('something else'); // This does not get through.\n    readable.push(null);\n    readable.resume(); // Make sure the 'end' event gets emitted.\n\n    const ended = Promise.withResolvers();\n    readable.on('end', () => {\n      for (const target of writables) {\n        deepStrictEqual(target.output, [input]);\n      }\n      ended.resolve();\n    });\n    await ended.promise;\n  },\n};\n\nexport const pipeManualResume = {\n  async test(ctrl, env, ctx) {\n    async function test(throwCodeInbetween) {\n      // Check that a pipe does not stall if .read() is called unexpectedly\n      // (i.e. the stream is not resumed by the pipe).\n\n      const n = 1000;\n      let counter = n;\n      const rClosed = Promise.withResolvers();\n      const wClosed = Promise.withResolvers();\n      const rs = Readable({\n        objectMode: true,\n        read: () => {\n          if (--counter >= 0) rs.push({ counter });\n          else rs.push(null);\n        },\n      });\n      const ws = Writable({\n        objectMode: true,\n        write: (data, enc, cb) => {\n          queueMicrotask(cb);\n        },\n      });\n      rs.on('close', rClosed.resolve);\n      ws.on('close', wClosed.resolve);\n      queueMicrotask(() => throwCodeInbetween(rs, ws));\n      rs.pipe(ws);\n      await Promise.all([rClosed.promise, wClosed.promise]);\n    }\n\n    await Promise.all([\n      test((rs) => rs.read()),\n      test((rs) => rs.resume()),\n      test(() => 0),\n    ]);\n  },\n};\n\nexport const pipeFlow = {\n  async test(ctrl, env, ctx) {\n    {\n      let ticks = 17;\n      const rs = new Readable({\n        objectMode: true,\n        read: () => {\n          if (ticks-- > 0) return queueMicrotask(() => rs.push({}));\n          rs.push({});\n          rs.push(null);\n        },\n      });\n      const ws = new Writable({\n        highWaterMark: 0,\n        objectMode: true,\n        write: (data, end, cb) => queueMicrotask(cb),\n      });\n      const ended = Promise.withResolvers();\n      const finished = Promise.withResolvers();\n      rs.on('end', ended.resolve);\n      ws.on('finish', finished.resolve);\n      rs.pipe(ws);\n      await Promise.all([ended.promise, finished.promise]);\n    }\n    {\n      let missing = 8;\n      const rs = new Readable({\n        objectMode: true,\n        read: () => {\n          if (missing--) rs.push({});\n          else rs.push(null);\n        },\n      });\n      const pt = rs\n        .pipe(\n          new PassThrough({\n            objectMode: true,\n            highWaterMark: 2,\n          })\n        )\n        .pipe(\n          new PassThrough({\n            objectMode: true,\n            highWaterMark: 2,\n          })\n        );\n      pt.on('end', () => {\n        wrapper.push(null);\n      });\n      const wrapper = new Readable({\n        objectMode: true,\n        read: () => {\n          queueMicrotask(() => {\n            let data = pt.read();\n            if (data === null) {\n              pt.once('readable', () => {\n                data = pt.read();\n                if (data !== null) wrapper.push(data);\n              });\n            } else {\n              wrapper.push(data);\n            }\n          });\n        },\n      });\n      wrapper.resume();\n      const ended = Promise.withResolvers();\n      wrapper.on('end', ended.resolve);\n      await ended.promise;\n    }\n    {\n      // Only register drain if there is backpressure.\n      const rs = new Readable({\n        read() {},\n      });\n      const pt = rs.pipe(\n        new PassThrough({\n          objectMode: true,\n          highWaterMark: 2,\n        })\n      );\n      strictEqual(pt.listenerCount('drain'), 0);\n      pt.on('finish', () => {\n        strictEqual(pt.listenerCount('drain'), 0);\n      });\n      rs.push('asd');\n      strictEqual(pt.listenerCount('drain'), 0);\n      const done = Promise.withResolvers();\n      queueMicrotask(() => {\n        rs.push('asd');\n        strictEqual(pt.listenerCount('drain'), 0);\n        rs.push(null);\n        strictEqual(pt.listenerCount('drain'), 0);\n        done.resolve();\n      });\n      await done.promise;\n    }\n  },\n};\n\nexport const pipeErrorHandling = {\n  async test(ctrl, env, ctx) {\n    {\n      const source = new Stream();\n      const dest = new Stream();\n      source.pipe(dest);\n      let gotErr = null;\n      source.on('error', function (err) {\n        gotErr = err;\n      });\n      const err = new Error('This stream turned into bacon.');\n      source.emit('error', err);\n      strictEqual(gotErr, err);\n    }\n    {\n      const source = new Stream();\n      const dest = new Stream();\n      source.pipe(dest);\n      const err = new Error('This stream turned into bacon.');\n      let gotErr = null;\n      try {\n        source.emit('error', err);\n      } catch (e) {\n        gotErr = e;\n      }\n      strictEqual(gotErr, err);\n    }\n    {\n      const r = new Readable({\n        autoDestroy: false,\n      });\n      const w = new Writable({\n        autoDestroy: false,\n      });\n      let removed = false;\n      r._read = function () {\n        setTimeout(function () {\n          ok(removed);\n          throws(function () {\n            w.emit('error', new Error('fail'));\n          }, /^Error: fail$/);\n        }, 1);\n      };\n      w.on('error', myOnError);\n      r.pipe(w);\n      w.removeListener('error', myOnError);\n      removed = true;\n      function myOnError() {\n        throw new Error('this should not happen');\n      }\n    }\n    {\n      const r = new Readable();\n      const w = new Writable();\n      let removed = false;\n      r._read = function () {\n        setTimeout(function () {\n          ok(removed);\n          w.emit('error', new Error('fail'));\n        }, 1);\n      };\n      const errored = Promise.withResolvers();\n      w.on('error', errored.resolve);\n      w._write = () => {};\n      r.pipe(w);\n      // Removing some OTHER random listener should not do anything\n      w.removeListener('error', () => {});\n      removed = true;\n      await errored.promise;\n    }\n    {\n      const _err = new Error('this should be handled');\n      const destination = new PassThrough();\n      const errored = Promise.withResolvers();\n      destination.once('error', (err) => {\n        strictEqual(err, _err);\n        errored.resolve();\n      });\n      const stream = new Stream();\n      stream.pipe(destination);\n      destination.destroy(_err);\n      await errored.promise;\n    }\n  },\n};\n\nexport const pipeCleanup = {\n  async test(ctrl, env, ctx) {\n    function Writable() {\n      this.writable = true;\n      this.endCalls = 0;\n      Stream.call(this);\n    }\n    Object.setPrototypeOf(Writable.prototype, Stream.prototype);\n    Object.setPrototypeOf(Writable, Stream);\n    Writable.prototype.end = function () {\n      this.endCalls++;\n    };\n    Writable.prototype.destroy = function () {\n      this.endCalls++;\n    };\n    function Readable() {\n      this.readable = true;\n      Stream.call(this);\n    }\n    Object.setPrototypeOf(Readable.prototype, Stream.prototype);\n    Object.setPrototypeOf(Readable, Stream);\n    function Duplex() {\n      this.readable = true;\n      Writable.call(this);\n    }\n    Object.setPrototypeOf(Duplex.prototype, Writable.prototype);\n    Object.setPrototypeOf(Duplex, Writable);\n    let i = 0;\n    const limit = 100;\n    let w = new Writable();\n    let r;\n    for (i = 0; i < limit; i++) {\n      r = new Readable();\n      r.pipe(w);\n      r.emit('end');\n    }\n    strictEqual(r.listeners('end').length, 0);\n    strictEqual(w.endCalls, limit);\n    w.endCalls = 0;\n    for (i = 0; i < limit; i++) {\n      r = new Readable();\n      r.pipe(w);\n      r.emit('close');\n    }\n    strictEqual(r.listeners('close').length, 0);\n    strictEqual(w.endCalls, limit);\n    w.endCalls = 0;\n    r = new Readable();\n    for (i = 0; i < limit; i++) {\n      w = new Writable();\n      r.pipe(w);\n      w.emit('close');\n    }\n    strictEqual(w.listeners('close').length, 0);\n    r = new Readable();\n    w = new Writable();\n    const d = new Duplex();\n    r.pipe(d); // pipeline A\n    d.pipe(w); // pipeline B\n    strictEqual(r.listeners('end').length, 2); // A.onend, A.cleanup\n    strictEqual(r.listeners('close').length, 2); // A.onclose, A.cleanup\n    strictEqual(d.listeners('end').length, 2); // B.onend, B.cleanup\n    // A.cleanup, B.onclose, B.cleanup\n    strictEqual(d.listeners('close').length, 3);\n    strictEqual(w.listeners('end').length, 0);\n    strictEqual(w.listeners('close').length, 1); // B.cleanup\n\n    r.emit('end');\n    strictEqual(d.endCalls, 1);\n    strictEqual(w.endCalls, 0);\n    strictEqual(r.listeners('end').length, 0);\n    strictEqual(r.listeners('close').length, 0);\n    strictEqual(d.listeners('end').length, 2); // B.onend, B.cleanup\n    strictEqual(d.listeners('close').length, 2); // B.onclose, B.cleanup\n    strictEqual(w.listeners('end').length, 0);\n    strictEqual(w.listeners('close').length, 1); // B.cleanup\n\n    d.emit('end');\n    strictEqual(d.endCalls, 1);\n    strictEqual(w.endCalls, 1);\n    strictEqual(r.listeners('end').length, 0);\n    strictEqual(r.listeners('close').length, 0);\n    strictEqual(d.listeners('end').length, 0);\n    strictEqual(d.listeners('close').length, 0);\n    strictEqual(w.listeners('end').length, 0);\n    strictEqual(w.listeners('close').length, 0);\n  },\n};\n\nexport const pipeCleanupPause = {\n  async test(ctrl, env, ctx) {\n    const done = Promise.withResolvers();\n    const reader = new Readable();\n    const writer1 = new Writable();\n    const writer2 = new Writable();\n\n    // 560000 is chosen here because it is larger than the (default) highWaterMark\n    // and will cause `.write()` to return false\n    // See: https://github.com/nodejs/node/issues/2323\n    const buffer = Buffer.allocUnsafe(560000);\n    reader._read = () => {};\n    writer1._write = function (chunk, encoding, cb) {\n      this.emit('chunk-received');\n      cb();\n    };\n    writer1.once('chunk-received', function () {\n      reader.unpipe(writer1);\n      reader.pipe(writer2);\n      reader.push(buffer);\n      queueMicrotask(function () {\n        reader.push(buffer);\n        queueMicrotask(function () {\n          reader.push(buffer);\n          done.resolve();\n        });\n      });\n    });\n    writer2._write = function (chunk, encoding, cb) {\n      cb();\n    };\n    reader.pipe(writer1);\n    reader.push(buffer);\n    await done.promise;\n  },\n};\n\nexport const writableAdapter = {\n  async test(ctrl, env, ctx) {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    // toWeb\n    {\n      const p = Promise.withResolvers();\n      const w = new Writable({\n        write(chunk, encoding, callback) {\n          strictEqual(dec.decode(chunk), 'ok');\n          p.resolve();\n        },\n      });\n      const ws = Writable.toWeb(w);\n      const writer = ws.getWriter();\n      await Promise.all([writer.write(enc.encode('ok')), p.promise]);\n    }\n\n    // fromWeb\n    {\n      const p = Promise.withResolvers();\n      const ws = new WritableStream({\n        write(chunk) {\n          strictEqual(dec.decode(chunk), 'ok');\n          p.resolve();\n        },\n      });\n      const w = Writable.fromWeb(ws);\n      const p2 = Promise.withResolvers();\n      w.write(enc.encode('ok'), p2.resolve);\n      await Promise.all([p.promise, p2.promise]);\n    }\n  },\n};\n\nexport const readableAdapter = {\n  async test(ctrl, env, ctx) {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    // toWeb\n    {\n      const r = new Readable({\n        read() {\n          this.push(enc.encode('ok'));\n        },\n      });\n      const rs = Readable.toWeb(r);\n      const reader = rs.getReader();\n      const value = await reader.read();\n      strictEqual(dec.decode(value.value), 'ok');\n    }\n\n    {\n      // Using a Node.js stream to feed into a Response...\n      const r = new Readable({\n        highWaterMark: 2,\n        read() {},\n      });\n      setTimeout(() => r.push(enc.encode('ok')), 10);\n      setTimeout(() => r.push(enc.encode(' there')), 20);\n      setTimeout(() => r.push(null), 30);\n      const rs = Readable.toWeb(r);\n      const res = new Response(rs);\n      const text = await res.text();\n      strictEqual(text, 'ok there');\n    }\n\n    // fromWeb\n    {\n      const rs = new ReadableStream({\n        pull(c) {\n          c.enqueue(enc.encode('ok'));\n          c.close();\n        },\n      });\n      const r = Readable.fromWeb(rs);\n      const p = Promise.withResolvers();\n      r.on('data', (chunk) => {\n        strictEqual(dec.decode(chunk), 'ok');\n        p.resolve();\n      });\n      await p.promise;\n    }\n  },\n};\n\nexport const errorHandling = {\n  async test() {\n    const rs = new ReadableStream({\n      start(c) {\n        throw new Error('boom');\n      },\n    });\n    const ns = Readable.fromWeb(rs);\n    try {\n      for await (const chunk of ns) {\n      }\n      throw new Error('should have thrown');\n    } catch (err) {\n      // The error should have propagated correctly through the adapter\n      strictEqual(err.message, 'boom');\n    }\n  },\n};\n\nexport const errorHandling2 = {\n  async test() {\n    const rs = new ReadableStream({\n      async pull(c) {\n        await scheduler.wait(100);\n        throw new Error('boom');\n      },\n    });\n    const ns = Readable.fromWeb(rs);\n    try {\n      for await (const chunk of ns) {\n      }\n      throw new Error('should have thrown');\n    } catch (err) {\n      // The error should have propagated correctly through the adapter\n      strictEqual(err.message, 'boom');\n    }\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/98513884684bccf944d7834f4820b061af41fb36/test/parallel/test-stream-duplexpair.js\nexport const testDuplexPair = {\n  async test() {\n    {\n      const pair = duplexPair();\n\n      ok(pair[0] instanceof Duplex);\n      ok(pair[1] instanceof Duplex);\n      notStrictEqual(pair[0], pair[1]);\n    }\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      const [clientSide, serverSide] = duplexPair();\n      ok(clientSide instanceof Duplex);\n      ok(serverSide instanceof Duplex);\n      serverSide.on('data', (d) => strictEqual(`${d}`, 'foo'));\n      serverSide.on('end', resolve);\n      clientSide.end('foo');\n      await promise;\n    }\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      const [serverSide, clientSide] = duplexPair();\n      serverSide.cork();\n      serverSide.write('abc');\n      serverSide.write('12');\n      serverSide.end('\\n');\n      serverSide.uncork();\n      let characters = '';\n      clientSide.on('readable', function () {\n        for (let segment; (segment = this.read()) !== null; )\n          characters += segment;\n      });\n      clientSide.on('end', function () {\n        strictEqual(characters, 'abc12\\n');\n        resolve();\n      });\n      await promise;\n    }\n\n    // Test the case where the the _write never calls [kCallback]\n    // because a zero-size push doesn't trigger a _read\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      const [serverSide, clientSide] = duplexPair();\n      serverSide.write('');\n      serverSide.write('12');\n      serverSide.write('');\n      serverSide.write('');\n      serverSide.end('\\n');\n      let characters = '';\n      clientSide.on('readable', function () {\n        for (let segment; (segment = this.read()) !== null; )\n          characters += segment;\n      });\n      clientSide.on('end', function () {\n        strictEqual(characters, '12\\n');\n        resolve();\n      });\n      await promise;\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/streams-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"streams-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"streams-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"streams_enable_constructors\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/streams-v24-nodejs-test.js",
    "content": "// Test for Node.js v24 streams compatibility\n// These tests specifically verify the v24 behavior changes work correctly\n\nimport { Readable, Writable } from 'node:stream';\nimport { strictEqual } from 'node:assert';\n\n// Regression test for v24 compat: large stream piping with backpressure\n// This tests that needDrain is calculated AFTER write() completes, not before.\n//\n// Bug context: When needDrain was calculated before _write(), synchronous writes that modified\n// state.length would cause the backpressure flag to be set based on stale buffer state. This\n// created a deadlock where the stream would never emit 'drain', causing pipes to hang.\n//\n// Symptoms if broken:\n// - Test will timeout (stream never completes)\n// - totalWritten will be stuck at approximately 2.3MB (around the buffer threshold)\n// - The stream hangs waiting for a 'drain' event that never fires\n// - Worker will show \"code had hung and would never generate a response\" error\nexport const testLargeStreamPipeBackpressureV24 = {\n  async test() {\n    const { promise, resolve, reject } = Promise.withResolvers();\n\n    // Create a 6MB buffer to ensure we exceed highWaterMark multiple times\n    const chunkSize = 64 * 1024; // 64KB chunks\n    const numChunks = 100; // ~6.4MB total\n    const expectedTotal = chunkSize * numChunks;\n\n    let totalWritten = 0;\n    let writesCompleted = 0;\n\n    const readable = new Readable({\n      read() {\n        // Push data in chunks\n        if (writesCompleted < numChunks) {\n          const chunk = Buffer.alloc(chunkSize, writesCompleted % 256);\n          this.push(chunk);\n          writesCompleted++;\n        } else {\n          this.push(null); // End the stream\n        }\n      },\n    });\n\n    const writable = new Writable({\n      // Set highWaterMark low to trigger backpressure\n      highWaterMark: 128 * 1024, // 128KB\n      write(chunk, encoding, callback) {\n        totalWritten += chunk.length;\n        // Simulate async write to ensure backpressure logic is exercised\n        setImmediate(callback);\n      },\n    });\n\n    writable.on('finish', () => {\n      try {\n        strictEqual(\n          totalWritten,\n          expectedTotal,\n          `Expected ${expectedTotal} bytes but got ${totalWritten}`\n        );\n        resolve();\n      } catch (err) {\n        reject(err);\n      }\n    });\n\n    writable.on('error', reject);\n    readable.on('error', reject);\n\n    // Pipe with backpressure handling\n    readable.pipe(writable);\n\n    // Add timeout to catch the hang condition if the bug regresses\n    const timeout = setTimeout(() => {\n      reject(\n        new Error(\n          `Stream hung after ${totalWritten} bytes (expected ${expectedTotal}). ` +\n            `This indicates needDrain backpressure calculation is broken.`\n        )\n      );\n    }, 10000);\n\n    try {\n      await promise;\n    } finally {\n      clearTimeout(timeout);\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/streams-v24-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"streams-v24-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"streams-v24-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_streams_nodejs_v24_compat\"],\n      )\n    ),\n    ( name = \"internet\", network = ( allow = [\"private\"] ) ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/string-decoder-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { ok, fail, strictEqual, throws } from 'node:assert';\n\nimport { Buffer } from 'node:buffer';\n\nimport { StringDecoder } from 'node:string_decoder';\n\nimport * as string_decoder from 'node:string_decoder';\n\nif (string_decoder.StringDecoder !== StringDecoder) {\n  throw new Error('Incorrect default exports');\n}\n\nfunction getArrayBufferViews(buf) {\n  const { buffer, byteOffset, byteLength } = buf;\n\n  const out = [];\n\n  const arrayBufferViews = [\n    Int8Array,\n    Uint8Array,\n    Uint8ClampedArray,\n    Int16Array,\n    Uint16Array,\n    Int32Array,\n    Uint32Array,\n    Float16Array,\n    Float32Array,\n    Float64Array,\n    BigInt64Array,\n    BigUint64Array,\n    DataView,\n  ];\n\n  for (const type of arrayBufferViews) {\n    const { BYTES_PER_ELEMENT = 1 } = type;\n    if (byteLength % BYTES_PER_ELEMENT === 0) {\n      out.push(new type(buffer, byteOffset, byteLength / BYTES_PER_ELEMENT));\n    }\n  }\n  return out;\n}\n\n// Test verifies that StringDecoder will correctly decode the given input\n// buffer with the given encoding to the expected output. It will attempt all\n// possible ways to write() the input buffer, see writeSequences(). The\n// singleSequence allows for easy debugging of a specific sequence which is\n// useful in case of test failures.\nfunction test_(encoding, input, expected, singleSequence) {\n  let sequences;\n  if (!singleSequence) {\n    sequences = writeSequences(input.length);\n  } else {\n    sequences = [singleSequence];\n  }\n  const hexNumberRE = /.{2}/g;\n  sequences.forEach((sequence) => {\n    const decoder = new StringDecoder(encoding);\n    let output = '';\n    sequence.forEach((write) => {\n      output += decoder.write(input.slice(write[0], write[1]));\n    });\n    output += decoder.end();\n    if (output !== expected) {\n      const message =\n        `Expected \"${unicodeEscape(expected)}\", ` +\n        `but got \"${unicodeEscape(output)}\"\\n` +\n        `input: ${input.toString('hex').match(hexNumberRE)}\\n` +\n        `Write sequence: ${JSON.stringify(sequence)}\\n` +\n        `Full Decoder State: ${decoder}`;\n      fail(message);\n    }\n  });\n}\n\n// writeSequences returns an array of arrays that describes all possible ways a\n// buffer of the given length could be split up and passed to sequential write\n// calls.\n//\n// e.G. writeSequences(3) will return: [\n//   [ [ 0, 3 ] ],\n//   [ [ 0, 2 ], [ 2, 3 ] ],\n//   [ [ 0, 1 ], [ 1, 3 ] ],\n//   [ [ 0, 1 ], [ 1, 2 ], [ 2, 3 ] ]\n// ]\nfunction writeSequences(length, start, sequence) {\n  if (start === undefined) {\n    start = 0;\n    sequence = [];\n  } else if (start === length) {\n    return [sequence];\n  }\n  let sequences = [];\n  for (let end = length; end > start; end--) {\n    const subSequence = sequence.concat([[start, end]]);\n    const subSequences = writeSequences(length, end, subSequence, sequences);\n    sequences = sequences.concat(subSequences);\n  }\n  return sequences;\n}\n\n// unicodeEscape prints the str contents as unicode escape codes.\nfunction unicodeEscape(str) {\n  let r = '';\n  for (let i = 0; i < str.length; i++) {\n    r += `\\\\u${str.charCodeAt(i).toString(16)}`;\n  }\n  return r;\n}\n\nexport const stringDecoder = {\n  test(ctrl, env, ctx) {\n    // Test default encoding\n    let decoder = new StringDecoder();\n    strictEqual(decoder.encoding, 'utf8');\n\n    // Should work without 'new' keyword\n    const decoder2 = {};\n    StringDecoder.call(decoder2);\n    strictEqual(decoder2.encoding, 'utf8');\n\n    // UTF-8\n    test_('utf-8', Buffer.from('$', 'utf-8'), '$');\n    test_('utf-8', Buffer.from('¢', 'utf-8'), '¢');\n    test_('utf-8', Buffer.from('€', 'utf-8'), '€');\n    test_('utf-8', Buffer.from('𤭢', 'utf-8'), '𤭢');\n    // A mixed ascii and non-ascii string\n    // Test stolen from deps/v8/test/cctest/test-strings.cc\n    // U+02E4 -> CB A4\n    // U+0064 -> 64\n    // U+12E4 -> E1 8B A4\n    // U+0030 -> 30\n    // U+3045 -> E3 81 85\n\n    test_(\n      'utf-8',\n      Buffer.from([0xcb, 0xa4, 0x64, 0xe1, 0x8b, 0xa4, 0x30, 0xe3, 0x81, 0x85]),\n      '\\u02e4\\u0064\\u12e4\\u0030\\u3045'\n    );\n\n    // Some invalid input, known to have caused trouble with chunking\n    // in https://github.com/nodejs/node/pull/7310#issuecomment-226445923\n    // 00: |00000000 ASCII\n    // 41: |01000001 ASCII\n    // B8: 10|111000 continuation\n    // CC: 110|01100 two-byte head\n    // E2: 1110|0010 three-byte head\n    // F0: 11110|000 four-byte head\n    // F1: 11110|001'another four-byte head\n    // FB: 111110|11 \"five-byte head\", not UTF-8\n    test_('utf-8', Buffer.from('C9B5A941', 'hex'), '\\u0275\\ufffdA');\n    test_('utf-8', Buffer.from('E2', 'hex'), '\\ufffd');\n    test_('utf-8', Buffer.from('E241', 'hex'), '\\ufffdA');\n    test_('utf-8', Buffer.from('CCCCB8', 'hex'), '\\ufffd\\u0338');\n    test_('utf-8', Buffer.from('F0B841', 'hex'), '\\ufffdA');\n    test_('utf-8', Buffer.from('F1CCB8', 'hex'), '\\ufffd\\u0338');\n    test_('utf-8', Buffer.from('F0FB00', 'hex'), '\\ufffd\\ufffd\\0');\n    test_('utf-8', Buffer.from('CCE2B8B8', 'hex'), '\\ufffd\\u2e38');\n    test_('utf-8', Buffer.from('E2B8CCB8', 'hex'), '\\ufffd\\u0338');\n    test_('utf-8', Buffer.from('E2FBCC01', 'hex'), '\\ufffd\\ufffd\\ufffd\\u0001');\n    test_('utf-8', Buffer.from('CCB8CDB9', 'hex'), '\\u0338\\u0379');\n    // CESU-8 of U+1D40D\n\n    // V8 has changed their invalid UTF-8 handling, see\n    // https://chromium-review.googlesource.com/c/v8/v8/+/671020 for more info.\n    test_(\n      'utf-8',\n      Buffer.from('EDA0B5EDB08D', 'hex'),\n      '\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd'\n    );\n\n    // UCS-2\n    test_('ucs2', Buffer.from('ababc', 'ucs2'), 'ababc');\n\n    // UTF-16LE\n    test_('utf16le', Buffer.from('3DD84DDC', 'hex'), '\\ud83d\\udc4d'); // thumbs up\n\n    // Additional UTF-8 tests\n    decoder = new StringDecoder('utf8');\n    strictEqual(decoder.write(Buffer.from('E1', 'hex')), '');\n\n    // A quick test for lastChar, lastNeed & lastTotal which are undocumented.\n    ok(decoder.lastChar.equals(new Uint8Array([0xe1, 0, 0, 0])));\n    strictEqual(decoder.lastNeed, 2);\n    strictEqual(decoder.lastTotal, 3);\n\n    strictEqual(decoder.end(), '\\ufffd');\n\n    // ArrayBufferView tests\n    const arrayBufferViewStr = 'String for ArrayBufferView tests\\n';\n    const inputBuffer = Buffer.from(arrayBufferViewStr.repeat(8), 'utf8');\n    for (const expectView of getArrayBufferViews(inputBuffer)) {\n      strictEqual(decoder.write(expectView), inputBuffer.toString('utf8'));\n      strictEqual(decoder.end(), '');\n    }\n\n    decoder = new StringDecoder('utf8');\n    strictEqual(decoder.write(Buffer.from('E18B', 'hex')), '');\n    strictEqual(decoder.end(), '\\ufffd');\n\n    decoder = new StringDecoder('utf8');\n    strictEqual(decoder.write(Buffer.from('\\ufffd')), '\\ufffd');\n    strictEqual(decoder.end(), '');\n\n    decoder = new StringDecoder('utf8');\n    strictEqual(\n      decoder.write(Buffer.from('\\ufffd\\ufffd\\ufffd')),\n      '\\ufffd\\ufffd\\ufffd'\n    );\n    strictEqual(decoder.end(), '');\n\n    decoder = new StringDecoder('utf8');\n    strictEqual(decoder.write(Buffer.from('EFBFBDE2', 'hex')), '\\ufffd');\n    strictEqual(decoder.end(), '\\ufffd');\n\n    decoder = new StringDecoder('utf8');\n    strictEqual(decoder.write(Buffer.from('F1', 'hex')), '');\n    strictEqual(decoder.write(Buffer.from('41F2', 'hex')), '\\ufffdA');\n    strictEqual(decoder.end(), '\\ufffd');\n\n    // Additional utf8Text test\n    decoder = new StringDecoder('utf8');\n    strictEqual(decoder.text(Buffer.from([0x41]), 2), '');\n\n    // Additional UTF-16LE surrogate pair tests\n    decoder = new StringDecoder('utf16le');\n    strictEqual(decoder.write(Buffer.from('3DD8', 'hex')), '');\n    strictEqual(decoder.write(Buffer.from('4D', 'hex')), '');\n    strictEqual(decoder.write(Buffer.from('DC', 'hex')), '\\ud83d\\udc4d');\n    strictEqual(decoder.end(), '');\n\n    decoder = new StringDecoder('utf16le');\n    strictEqual(decoder.write(Buffer.from('3DD8', 'hex')), '');\n    strictEqual(decoder.end(), '\\ud83d');\n\n    decoder = new StringDecoder('utf16le');\n    strictEqual(decoder.write(Buffer.from('3DD8', 'hex')), '');\n    strictEqual(decoder.write(Buffer.from('4D', 'hex')), '');\n    strictEqual(decoder.end(), '\\ud83d');\n\n    decoder = new StringDecoder('utf16le');\n    strictEqual(decoder.write(Buffer.from('3DD84D', 'hex')), '\\ud83d');\n    strictEqual(decoder.end(), '');\n\n    // Regression test for https://github.com/nodejs/node/issues/22358\n    // (unaligned UTF-16 access).\n    decoder = new StringDecoder('utf16le');\n    strictEqual(decoder.write(Buffer.alloc(1)), '');\n    strictEqual(decoder.write(Buffer.alloc(20)), '\\0'.repeat(10));\n    strictEqual(decoder.write(Buffer.alloc(48)), '\\0'.repeat(24));\n    strictEqual(decoder.end(), '');\n\n    // Regression tests for https://github.com/nodejs/node/issues/22626\n    // (not enough replacement chars when having seen more than one byte of an\n    // incomplete multibyte characters).\n    decoder = new StringDecoder('utf8');\n    strictEqual(decoder.write(Buffer.from('f69b', 'hex')), '');\n    strictEqual(decoder.write(Buffer.from('d1', 'hex')), '\\ufffd\\ufffd');\n    strictEqual(decoder.end(), '\\ufffd');\n    strictEqual(decoder.write(Buffer.from('f4', 'hex')), '');\n    strictEqual(decoder.write(Buffer.from('bde5', 'hex')), '\\ufffd\\ufffd');\n    strictEqual(decoder.end(), '\\ufffd');\n\n    throws(() => new StringDecoder(1), {\n      code: 'ERR_UNKNOWN_ENCODING',\n      name: 'TypeError',\n      message: 'Unknown encoding: 1',\n    });\n\n    throws(() => new StringDecoder('test'), {\n      code: 'ERR_UNKNOWN_ENCODING',\n      name: 'TypeError',\n      message: 'Unknown encoding: test',\n    });\n\n    throws(() => new StringDecoder('utf8').write(null), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n    });\n\n    throws(\n      () => new StringDecoder('utf8').__proto__.write(Buffer.from('abc')),\n      {\n        code: 'ERR_INVALID_THIS',\n      }\n    );\n  },\n};\n\nexport const stringDecoderEnd = {\n  test(ctrl, env, ctx) {\n    const encodings = ['base64', 'base64url', 'hex', 'utf8', 'utf16le', 'ucs2'];\n    const bufs = ['☃💩', 'asdf'].map((b) => Buffer.from(b));\n\n    // Also test just arbitrary bytes from 0-15.\n    for (let i = 1; i <= 16; i++) {\n      const bytes = '.'\n        .repeat(i - 1)\n        .split('.')\n        .map((_, j) => j + 0x78);\n      bufs.push(Buffer.from(bytes));\n    }\n\n    encodings.forEach(testEncoding);\n\n    testEnd('utf8', Buffer.of(0xe2), Buffer.of(0x61), '\\uFFFDa');\n    testEnd('utf8', Buffer.of(0xe2), Buffer.of(0x82), '\\uFFFD\\uFFFD');\n    testEnd('utf8', Buffer.of(0xe2), Buffer.of(0xe2), '\\uFFFD\\uFFFD');\n    testEnd('utf8', Buffer.of(0xe2, 0x82), Buffer.of(0x61), '\\uFFFDa');\n    testEnd('utf8', Buffer.of(0xe2, 0x82), Buffer.of(0xac), '\\uFFFD\\uFFFD');\n    testEnd('utf8', Buffer.of(0xe2, 0x82), Buffer.of(0xe2), '\\uFFFD\\uFFFD');\n    testEnd('utf8', Buffer.of(0xe2, 0x82, 0xac), Buffer.of(0x61), '€a');\n\n    testEnd('utf16le', Buffer.of(0x3d), Buffer.of(0x61, 0x00), 'a');\n    testEnd('utf16le', Buffer.of(0x3d), Buffer.of(0xd8, 0x4d, 0xdc), '\\u4DD8');\n    testEnd('utf16le', Buffer.of(0x3d, 0xd8), Buffer.of(), '\\uD83D');\n    testEnd('utf16le', Buffer.of(0x3d, 0xd8), Buffer.of(0x61, 0x00), '\\uD83Da');\n    testEnd(\n      'utf16le',\n      Buffer.of(0x3d, 0xd8),\n      Buffer.of(0x4d, 0xdc),\n      '\\uD83D\\uDC4D'\n    );\n    testEnd('utf16le', Buffer.of(0x3d, 0xd8, 0x4d), Buffer.of(), '\\uD83D');\n    testEnd(\n      'utf16le',\n      Buffer.of(0x3d, 0xd8, 0x4d),\n      Buffer.of(0x61, 0x00),\n      '\\uD83Da'\n    );\n    testEnd('utf16le', Buffer.of(0x3d, 0xd8, 0x4d), Buffer.of(0xdc), '\\uD83D');\n    testEnd(\n      'utf16le',\n      Buffer.of(0x3d, 0xd8, 0x4d, 0xdc),\n      Buffer.of(0x61, 0x00),\n      '👍a'\n    );\n\n    testEnd('base64', Buffer.of(0x61), Buffer.of(), 'YQ==');\n    testEnd('base64', Buffer.of(0x61), Buffer.of(0x61), 'YQ==YQ==');\n    testEnd('base64', Buffer.of(0x61, 0x61), Buffer.of(), 'YWE=');\n    testEnd('base64', Buffer.of(0x61, 0x61), Buffer.of(0x61), 'YWE=YQ==');\n    testEnd('base64', Buffer.of(0x61, 0x61, 0x61), Buffer.of(), 'YWFh');\n    testEnd('base64', Buffer.of(0x61, 0x61, 0x61), Buffer.of(0x61), 'YWFhYQ==');\n\n    testEnd('base64url', Buffer.of(0x61), Buffer.of(), 'YQ');\n    testEnd('base64url', Buffer.of(0x61), Buffer.of(0x61), 'YQYQ');\n    testEnd('base64url', Buffer.of(0x61, 0x61), Buffer.of(), 'YWE');\n    testEnd('base64url', Buffer.of(0x61, 0x61), Buffer.of(0x61), 'YWEYQ');\n    testEnd('base64url', Buffer.of(0x61, 0x61, 0x61), Buffer.of(), 'YWFh');\n    testEnd(\n      'base64url',\n      Buffer.of(0x61, 0x61, 0x61),\n      Buffer.of(0x61),\n      'YWFhYQ'\n    );\n\n    function testEncoding(encoding) {\n      bufs.forEach((buf) => {\n        testBuf(encoding, buf);\n      });\n    }\n\n    function testBuf(encoding, buf) {\n      // Write one byte at a time.\n      let s = new StringDecoder(encoding);\n      let res1 = '';\n      for (let i = 0; i < buf.length; i++) {\n        res1 += s.write(buf.slice(i, i + 1));\n      }\n      res1 += s.end();\n\n      // Write the whole buffer at once.\n      let res2 = '';\n      s = new StringDecoder(encoding);\n      res2 += s.write(buf);\n      res2 += s.end();\n\n      // .toString() on the buffer\n      const res3 = buf.toString(encoding);\n\n      // One byte at a time should match toString\n      strictEqual(res1, res3);\n      // All bytes at once should match toString\n      strictEqual(res2, res3);\n    }\n\n    function testEnd(encoding, incomplete, next, expected) {\n      let res = '';\n      const s = new StringDecoder(encoding);\n      res += s.write(incomplete);\n      res += s.end();\n      res += s.write(next);\n      res += s.end();\n\n      strictEqual(res, expected);\n    }\n  },\n};\n\nexport const stringDecoderFuzz = {\n  test(ctrl, env, ctx) {\n    function rand(max) {\n      return Math.floor(Math.random() * max);\n    }\n\n    function randBuf(maxLen) {\n      const buf = Buffer.allocUnsafe(rand(maxLen));\n      for (let i = 0; i < buf.length; i++) buf[i] = rand(256);\n      return buf;\n    }\n\n    const encodings = [\n      'utf16le',\n      'utf8',\n      'ascii',\n      'hex',\n      'base64',\n      'latin1',\n      'base64url',\n    ];\n\n    function runSingleFuzzTest() {\n      const enc = encodings[rand(encodings.length)];\n      const sd = new StringDecoder(enc);\n      const bufs = [];\n      const strings = [];\n\n      const N = rand(10);\n      for (let i = 0; i < N; ++i) {\n        const buf = randBuf(50);\n        bufs.push(buf);\n        strings.push(sd.write(buf));\n      }\n      strings.push(sd.end());\n\n      strictEqual(\n        strings.join(''),\n        Buffer.concat(bufs).toString(enc),\n        `Mismatch:\\n${strings}\\n` +\n          bufs.map((buf) => buf.toString('hex')) +\n          `\\nfor encoding ${enc}`\n      );\n    }\n\n    const start = Date.now();\n    while (Date.now() - start < 100) runSingleFuzzTest();\n  },\n};\n\nexport const stringDecoderHacking = {\n  test(ctrl, env, ctx) {\n    throws(\n      () => {\n        const sd = new StringDecoder();\n        const sym = Object.getOwnPropertySymbols(sd)[0];\n        sd[sym] = 'not a buffer';\n        sd.write(Buffer.from(\"this shouldn't crash\"));\n      },\n      {\n        name: 'TypeError',\n      }\n    );\n\n    throws(\n      () => {\n        const sd = new StringDecoder();\n        const sym = Object.getOwnPropertySymbols(sd)[0];\n        sd[sym] = Buffer.alloc(1);\n        sd.write(Buffer.from(\"this shouldn't crash\"));\n      },\n      {\n        message: 'Invalid StringDecoder',\n      }\n    );\n\n    throws(\n      () => {\n        const sd = new StringDecoder();\n        const sym = Object.getOwnPropertySymbols(sd)[0];\n        sd[sym] = Buffer.alloc(9);\n        sd.write(Buffer.from(\"this shouldn't crash\"));\n      },\n      {\n        message: 'Invalid StringDecoder',\n      }\n    );\n\n    throws(\n      () => {\n        const sd = new StringDecoder();\n        const sym = Object.getOwnPropertySymbols(sd)[0];\n        sd[sym][5] = 100;\n        sd.write(Buffer.from(\"this shouldn't crash\"));\n      },\n      {\n        message: 'Buffered bytes cannot exceed 4',\n      }\n    );\n\n    throws(\n      () => {\n        const sd = new StringDecoder();\n        const sym = Object.getOwnPropertySymbols(sd)[0];\n        sd[sym][4] = 100;\n        sd.write(Buffer.from(\"this shouldn't crash\"));\n      },\n      {\n        message: 'Missing bytes cannot exceed 4',\n      }\n    );\n\n    throws(\n      () => {\n        const sd = new StringDecoder();\n        const sym = Object.getOwnPropertySymbols(sd)[0];\n        sd[sym][6] = 100;\n        sd.write(Buffer.from(\"this shouldn't crash\"));\n      },\n      {\n        message: 'Invalid StringDecoder state',\n      }\n    );\n\n    throws(\n      () => {\n        const sd = new StringDecoder();\n        const sym = Object.getOwnPropertySymbols(sd)[0];\n        sd[sym][4] = 3;\n        sd[sym][5] = 2;\n        sd.write(Buffer.from(\"this shouldn't crash\"));\n      },\n      {\n        message: 'Invalid StringDecoder state',\n      }\n    );\n\n    {\n      // fuzz a bit with random values\n      const messages = [\n        'Invalid StringDecoder state',\n        'Missing bytes cannot exceed 4',\n        'Buffered bytes cannot exceed 4',\n      ];\n      for (let n = 0; n < 255; n++) {\n        try {\n          const sd = new StringDecoder();\n          const sym = Object.getOwnPropertySymbols(sd)[0];\n          crypto.getRandomValues(sd[sym]);\n          sd.write(Buffer.from(\"this shouldn't crash\"));\n        } catch (err) {\n          if (!messages.includes(err.message)) {\n            throw err;\n          }\n        }\n      }\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/string-decoder-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"string-decoder-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"string-decoder-test.js\")\n        ],\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\", \"nodejs_compat_v2\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/sys-nodejs-test.js",
    "content": "import assert from 'node:assert';\nimport module from 'node:module';\n\nexport const getBuiltinModule = {\n  async test() {\n    assert.deepStrictEqual(\n      process.getBuiltinModule('node:sys'),\n      process.getBuiltinModule('node:util')\n    );\n  },\n};\n\nexport const canBeRequired = {\n  async test() {\n    const require = module.createRequire('/hello');\n    assert.deepStrictEqual(require('node:sys'), require('node:util'));\n    assert.deepStrictEqual(require('sys'), require('node:util'));\n  },\n};\n\nexport const canBeImported = {\n  async test() {\n    const sys = await import('node:sys');\n    const util = await import('node:util');\n    assert.deepStrictEqual(sys, util);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/sys-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"nodejs-sys-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"sys-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/test_process_stdio.sh",
    "content": "#!/bin/bash\n\n# Test script to verify process stdio output against golden files\nset -euo pipefail\n\n# Arguments passed from Bazel\nWORKERD_BINARY=\"$1\"\nTEST_CONFIG=\"$2\"\nEXPECTED_STDOUT=\"$3\"\nEXPECTED_STDERR=\"$4\"\n\n# Run the workerd test and capture stdout and stderr\nACTUAL_STDOUT=$(mktemp)\nACTUAL_STDERR=$(mktemp)\nFILTERED_STDOUT=$(mktemp)\nFILTERED_STDERR=$(mktemp)\ntrap \"rm -f $ACTUAL_STDOUT $ACTUAL_STDERR $FILTERED_STDOUT $FILTERED_STDERR\" EXIT\n\n\"$WORKERD_BINARY\" test \"$TEST_CONFIG\" --experimental >\"$ACTUAL_STDOUT\" 2>\"$ACTUAL_STDERR\" --compat-date 2025-05-01\n\n# Remove [ PASS ] [ TEST ] [ FAIL ] lines from stderr\ngrep -vE \"\\[ PASS \\]|\\[ FAIL \\]|\\[ TEST \\]\" \"$ACTUAL_STDERR\" > \"$FILTERED_STDERR\" || true\n\n# Compare with expected output (normalize line endings for cross-platform compatibility)\necho \"Comparing stdout...\"\nif ! diff -u <(tr -d '\\r' < \"$EXPECTED_STDOUT\") <(tr -d '\\r' < \"$ACTUAL_STDOUT\"); then\n    echo \"FAIL: stdout does not match expected output\"\n    exit 1\nfi\n\n# Compare with expected output\necho \"Comparing stderr...\"\nif ! diff -u <(tr -d '\\r' < \"$EXPECTED_STDERR\") <(tr -d '\\r' < \"$FILTERED_STDERR\"); then\n    echo \"FAIL: stderr does not match expected output\"\n    exit 1\nfi\n\necho \"PASS: All stdio output matches golden files\"\n"
  },
  {
    "path": "src/workerd/api/node/tests/timers-global-override-test.js",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Tests for the enable_nodejs_global_timers compat flag.\n// When this flag is enabled:\n// - globalThis.setTimeout and globalThis.setInterval return Node.js-compatible\n//   Timeout objects instead of numeric IDs\n// - globalThis.setImmediate and globalThis.clearImmediate are available\n\nimport { strictEqual, ok } from 'node:assert';\n\n// Test that globalThis.setTimeout returns a Timeout object with expected methods\nexport const testGlobalSetTimeoutReturnsObject = {\n  async test() {\n    const timeout = globalThis.setTimeout(() => {}, 1000);\n\n    // Verify it's an object, not a number\n    strictEqual(typeof timeout, 'object', 'setTimeout should return an object');\n\n    // Verify it has the expected methods\n    strictEqual(\n      typeof timeout.refresh,\n      'function',\n      'Timeout should have refresh method'\n    );\n    strictEqual(\n      typeof timeout.ref,\n      'function',\n      'Timeout should have ref method'\n    );\n    strictEqual(\n      typeof timeout.unref,\n      'function',\n      'Timeout should have unref method'\n    );\n    strictEqual(\n      typeof timeout.hasRef,\n      'function',\n      'Timeout should have hasRef method'\n    );\n    strictEqual(\n      typeof timeout.close,\n      'function',\n      'Timeout should have close method'\n    );\n\n    // Verify Symbol.toPrimitive allows using as numeric ID for backward compat\n    strictEqual(\n      typeof +timeout,\n      'number',\n      'Timeout should be convertible to number'\n    );\n    ok(+timeout > 0, 'Timeout numeric ID should be positive');\n\n    // Verify Symbol.dispose is present\n    strictEqual(\n      typeof timeout[Symbol.dispose],\n      'function',\n      'Timeout should have Symbol.dispose method'\n    );\n\n    // Clean up\n    globalThis.clearTimeout(timeout);\n  },\n};\n\n// Test that globalThis.setInterval returns a Timeout object with expected methods\nexport const testGlobalSetIntervalReturnsObject = {\n  async test() {\n    const interval = globalThis.setInterval(() => {}, 1000);\n\n    // Verify it's an object, not a number\n    strictEqual(\n      typeof interval,\n      'object',\n      'setInterval should return an object'\n    );\n\n    // Verify it has the expected methods\n    strictEqual(\n      typeof interval.refresh,\n      'function',\n      'Interval should have refresh method'\n    );\n    strictEqual(\n      typeof interval.ref,\n      'function',\n      'Interval should have ref method'\n    );\n    strictEqual(\n      typeof interval.unref,\n      'function',\n      'Interval should have unref method'\n    );\n    strictEqual(\n      typeof interval.hasRef,\n      'function',\n      'Interval should have hasRef method'\n    );\n\n    // Verify Symbol.toPrimitive allows using as numeric ID for backward compat\n    strictEqual(\n      typeof +interval,\n      'number',\n      'Interval should be convertible to number'\n    );\n    ok(+interval > 0, 'Interval numeric ID should be positive');\n\n    // Clean up\n    globalThis.clearInterval(interval);\n  },\n};\n\n// Test that clearTimeout accepts both Timeout objects and numeric IDs\nexport const testClearTimeoutAcceptsBothTypes = {\n  async test() {\n    // Test clearing with Timeout object\n    const t1 = globalThis.setTimeout(() => {\n      throw new Error('timeout1 should have been cleared');\n    }, 100);\n    globalThis.clearTimeout(t1);\n\n    // Test clearing with numeric ID (backward compatibility via Symbol.toPrimitive)\n    const t2 = globalThis.setTimeout(() => {\n      throw new Error('timeout2 should have been cleared');\n    }, 100);\n    globalThis.clearTimeout(+t2);\n\n    // Wait to ensure cleared timeouts don't fire\n    await new Promise((r) => globalThis.setTimeout(r, 200));\n  },\n};\n\n// Test that clearInterval accepts both Timeout objects and numeric IDs\nexport const testClearIntervalAcceptsBothTypes = {\n  async test() {\n    // Test clearing with Timeout object\n    const i1 = globalThis.setInterval(() => {\n      throw new Error('interval1 should have been cleared');\n    }, 50);\n    globalThis.clearInterval(i1);\n\n    // Test clearing with numeric ID (backward compatibility via Symbol.toPrimitive)\n    const i2 = globalThis.setInterval(() => {\n      throw new Error('interval2 should have been cleared');\n    }, 50);\n    globalThis.clearInterval(+i2);\n\n    // Wait to ensure cleared intervals don't fire\n    await new Promise((r) => globalThis.setTimeout(r, 200));\n  },\n};\n\n// Test that the refresh() method works correctly\n// NOTE: Using larger timing margins (200ms/100ms instead of 50ms/30ms) because\n// Windows has ~15.6ms timer resolution and significant jitter under load.\nexport const testTimeoutRefresh = {\n  async test() {\n    let callCount = 0;\n\n    // Create a timeout that would fire in 200ms\n    const timeout = globalThis.setTimeout(() => {\n      callCount++;\n    }, 200);\n\n    // Wait 100ms, then refresh (reset the timer)\n    await new Promise((r) => globalThis.setTimeout(r, 100));\n    timeout.refresh();\n\n    // Wait another 100ms (total 200ms from start, but only 100ms from refresh)\n    await new Promise((r) => globalThis.setTimeout(r, 100));\n\n    // The callback shouldn't have fired yet since we refreshed\n    strictEqual(\n      callCount,\n      0,\n      'Callback should not have fired yet after refresh'\n    );\n\n    // Wait another 150ms for the refreshed timer to fire (with margin)\n    await new Promise((r) => globalThis.setTimeout(r, 150));\n\n    // Now it should have fired\n    strictEqual(callCount, 1, 'Callback should have fired once');\n\n    // Clean up (already fired, but good practice)\n    globalThis.clearTimeout(timeout);\n  },\n};\n\n// Test ref() and unref() methods (no-ops but should work without errors)\nexport const testRefUnref = {\n  async test() {\n    const timeout = globalThis.setTimeout(() => {}, 1000);\n\n    // Initially, hasRef should return true (default behavior)\n    strictEqual(timeout.hasRef(), true, 'hasRef should return true by default');\n\n    // unref() should return the timeout object (chainable)\n    const unrefResult = timeout.unref();\n    strictEqual(unrefResult, timeout, 'unref should return the timeout object');\n    strictEqual(\n      timeout.hasRef(),\n      false,\n      'hasRef should return false after unref'\n    );\n\n    // ref() should return the timeout object (chainable)\n    const refResult = timeout.ref();\n    strictEqual(refResult, timeout, 'ref should return the timeout object');\n    strictEqual(timeout.hasRef(), true, 'hasRef should return true after ref');\n\n    // Clean up\n    globalThis.clearTimeout(timeout);\n  },\n};\n\n// Test that close() method clears the timeout\nexport const testClose = {\n  async test() {\n    let called = false;\n    const timeout = globalThis.setTimeout(() => {\n      called = true;\n    }, 50);\n\n    // close() should return the timeout object (chainable)\n    const closeResult = timeout.close();\n    strictEqual(closeResult, timeout, 'close should return the timeout object');\n\n    // Wait to ensure the callback doesn't fire\n    await new Promise((r) => globalThis.setTimeout(r, 100));\n    strictEqual(\n      called,\n      false,\n      'Callback should not have been called after close'\n    );\n  },\n};\n\n// Test that setTimeout works with callback arguments\nexport const testSetTimeoutWithArgs = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n\n    globalThis.setTimeout(\n      (a, b, c) => {\n        strictEqual(a, 1, 'First arg should be 1');\n        strictEqual(b, 'hello', 'Second arg should be \"hello\"');\n        strictEqual(c, true, 'Third arg should be true');\n        resolve();\n      },\n      10,\n      1,\n      'hello',\n      true\n    );\n\n    await promise;\n  },\n};\n\n// Test that setInterval works with callback arguments\nexport const testSetIntervalWithArgs = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n\n    const interval = globalThis.setInterval(\n      (a, b) => {\n        strictEqual(a, 'foo', 'First arg should be \"foo\"');\n        strictEqual(b, 42, 'Second arg should be 42');\n        globalThis.clearInterval(interval);\n        resolve();\n      },\n      10,\n      'foo',\n      42\n    );\n\n    await promise;\n  },\n};\n\n// Test Symbol.dispose functionality\nexport const testSymbolDispose = {\n  async test() {\n    let called = false;\n    const timeout = globalThis.setTimeout(() => {\n      called = true;\n    }, 50);\n\n    // Use Symbol.dispose to clean up\n    timeout[Symbol.dispose]();\n\n    // Wait to ensure the callback doesn't fire\n    await new Promise((r) => globalThis.setTimeout(r, 100));\n    strictEqual(\n      called,\n      false,\n      'Callback should not have been called after dispose'\n    );\n  },\n};\n\n// Test that globalThis.setImmediate is available and returns an Immediate object\nexport const testGlobalSetImmediateAvailable = {\n  async test() {\n    strictEqual(\n      typeof globalThis.setImmediate,\n      'function',\n      'setImmediate should be available on globalThis'\n    );\n    strictEqual(\n      typeof globalThis.clearImmediate,\n      'function',\n      'clearImmediate should be available on globalThis'\n    );\n\n    const { promise, resolve } = Promise.withResolvers();\n\n    const immediate = globalThis.setImmediate(() => {\n      resolve();\n    });\n\n    // Verify it's an object with expected methods\n    strictEqual(\n      typeof immediate,\n      'object',\n      'setImmediate should return an object'\n    );\n    strictEqual(\n      typeof immediate.ref,\n      'function',\n      'Immediate should have ref method'\n    );\n    strictEqual(\n      typeof immediate.unref,\n      'function',\n      'Immediate should have unref method'\n    );\n    strictEqual(\n      typeof immediate.hasRef,\n      'function',\n      'Immediate should have hasRef method'\n    );\n\n    await promise;\n  },\n};\n\n// Test that clearImmediate works correctly\nexport const testGlobalClearImmediate = {\n  async test() {\n    let called = false;\n    const immediate = globalThis.setImmediate(() => {\n      called = true;\n    });\n\n    globalThis.clearImmediate(immediate);\n\n    // Wait a bit to ensure the callback doesn't fire\n    await new Promise((r) => globalThis.setTimeout(r, 50));\n    strictEqual(\n      called,\n      false,\n      'Callback should not have been called after clearImmediate'\n    );\n  },\n};\n\n// Test that setImmediate works with callback arguments\nexport const testSetImmediateWithArgs = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n\n    globalThis.setImmediate(\n      (a, b) => {\n        strictEqual(a, 'test', 'First arg should be \"test\"');\n        strictEqual(b, 123, 'Second arg should be 123');\n        resolve();\n      },\n      'test',\n      123\n    );\n\n    await promise;\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/timers-global-override-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"nodejs-timers-global-override-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"timers-global-override-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"experimental\", \"enable_nodejs_global_timers\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/timers-nodejs-test.js",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport timers from 'node:timers';\nimport timersPromises from 'node:timers/promises';\nimport { throws, strictEqual, deepStrictEqual, rejects, ok } from 'node:assert';\n\nexport const testEnroll = {\n  async test() {\n    throws(() => timers.enroll(), /Not implemented/);\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/b3641fe85d55525127c03be730596154705b798e/test/parallel/test-timers-immediate.js\nexport const testSetImmediate = {\n  async test() {\n    {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      let mainFinished = false;\n      timers.setImmediate(() => {\n        strictEqual(mainFinished, true);\n        timers.clearImmediate(immediateB);\n        resolve();\n      });\n\n      const immediateB = timers.setImmediate(reject);\n      mainFinished = true;\n\n      await promise;\n    }\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      timers.setImmediate(\n        (...args) => {\n          deepStrictEqual(args, [1, 2, 3]);\n          resolve();\n        },\n        1,\n        2,\n        3\n      );\n      await promise;\n    }\n  },\n};\n\nexport const testSetTimeout = {\n  async test() {\n    {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      let mainFinished = false;\n      timers.setTimeout(() => {\n        strictEqual(mainFinished, true);\n        timers.clearTimeout(timeoutB);\n        resolve();\n      });\n\n      const timeoutB = timers.setTimeout(reject);\n      mainFinished = true;\n\n      await promise;\n    }\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      timers.setTimeout(\n        (...args) => {\n          deepStrictEqual(args, [1, 2, 3]);\n          resolve();\n        },\n        100,\n        1,\n        2,\n        3\n      );\n      await promise;\n    }\n  },\n};\n\nexport const testSetInterval = {\n  async test() {\n    {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      let mainFinished = false;\n      const thisInterval = timers.setInterval(() => {\n        strictEqual(mainFinished, true);\n        timers.clearInterval(intervalB);\n        timers.clearInterval(thisInterval);\n        resolve();\n      }, 100);\n\n      const intervalB = timers.setInterval(reject, 100);\n      mainFinished = true;\n\n      await promise;\n    }\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      const thisInterval = timers.setInterval(\n        (...args) => {\n          deepStrictEqual(args, [1, 2, 3]);\n          timers.clearInterval(thisInterval);\n          resolve();\n        },\n        100,\n        1,\n        2,\n        3\n      );\n      await promise;\n    }\n  },\n};\n\nexport const testRefresh = {\n  async test() {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    timers.clearTimeout(\n      timers\n        .setTimeout(() => {\n          reject();\n        }, 1)\n        .refresh()\n    );\n\n    timers.setTimeout(() => {\n      resolve();\n    }, 2);\n\n    await promise;\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/7bc2946293757389468f1fa09714860f8e1147b7/test/parallel/test-timers-immediate-promisified.js\nexport const timersImmediatePromise = {\n  async test() {\n    {\n      const ac = new AbortController();\n      const signal = ac.signal;\n      const { promise, resolve } = Promise.withResolvers();\n      rejects(timersPromises.setImmediate(10, { signal }), /AbortError/).then(\n        resolve\n      );\n      ac.abort();\n      await promise;\n    }\n\n    {\n      const signal = AbortSignal.abort(); // Abort in advance\n      await rejects(timersPromises.setImmediate(10, { signal }), /AbortError/);\n    }\n\n    {\n      // Check that aborting after resolve will not reject.\n      const ac = new AbortController();\n      const signal = ac.signal;\n      await timersPromises.setImmediate(10, { signal }).then(() => {\n        ac.abort();\n      });\n    }\n\n    {\n      await Promise.all(\n        [1, '', false, Infinity].map((i) =>\n          rejects(timersPromises.setImmediate(10, i), {\n            code: 'ERR_INVALID_ARG_TYPE',\n          })\n        )\n      );\n\n      await Promise.all(\n        [1, '', false, Infinity, null, {}].map((signal) =>\n          rejects(timersPromises.setImmediate(10, { signal }), {\n            code: 'ERR_INVALID_ARG_TYPE',\n          })\n        )\n      );\n\n      await Promise.all(\n        [1, '', Infinity, null, {}].map((ref) =>\n          rejects(timersPromises.setImmediate(10, { ref }), {\n            code: 'ERR_INVALID_ARG_TYPE',\n          })\n        )\n      );\n    }\n\n    {\n      const signal = AbortSignal.abort('boom');\n      await rejects(timersPromises.setImmediate(undefined, { signal }), {\n        cause: 'boom',\n      });\n    }\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/7bc2946293757389468f1fa09714860f8e1147b7/test/parallel/test-timers-timeout-promisified.js\nexport const timersTimeoutPromise = {\n  async test() {\n    {\n      const promise = timersPromises.setTimeout(1);\n      strictEqual(await promise, undefined);\n    }\n\n    {\n      const promise = timersPromises.setTimeout(1, 'foobar');\n      strictEqual(await promise, 'foobar');\n    }\n\n    {\n      const ac = new AbortController();\n      const signal = ac.signal;\n      const { promise, resolve } = Promise.withResolvers();\n      rejects(\n        timersPromises.setTimeout(10, undefined, { signal }),\n        /AbortError/\n      ).then(resolve);\n      ac.abort();\n      await promise;\n    }\n\n    {\n      const signal = AbortSignal.abort(); // Abort in advance\n      await rejects(\n        timersPromises.setTimeout(10, undefined, { signal }),\n        /AbortError/\n      );\n    }\n\n    {\n      // Check that aborting after resolve will not reject.\n      const ac = new AbortController();\n      const signal = ac.signal;\n      await timersPromises.setTimeout(10, undefined, { signal }).then(() => {\n        ac.abort();\n      });\n    }\n\n    {\n      for (const delay of ['', false]) {\n        await rejects(\n          () => timersPromises.setTimeout(delay, null, {}),\n          /ERR_INVALID_ARG_TYPE/\n        );\n      }\n\n      for (const options of [1, '', false, Infinity]) {\n        await rejects(\n          () => timersPromises.setTimeout(10, null, options),\n          /ERR_INVALID_ARG_TYPE/\n        );\n      }\n\n      for (const signal of [1, '', false, Infinity, null, {}]) {\n        await rejects(\n          () => timersPromises.setTimeout(10, null, { signal }),\n          /ERR_INVALID_ARG_TYPE/\n        );\n      }\n\n      for (const ref of [1, '', Infinity, null, {}]) {\n        await rejects(\n          () => timersPromises.setTimeout(10, null, { ref }),\n          /ERR_INVALID_ARG_TYPE/\n        );\n      }\n    }\n\n    {\n      const signal = AbortSignal.abort('boom');\n      await rejects(timersPromises.setTimeout(1, undefined, { signal }), {\n        cause: 'boom',\n      });\n    }\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/7bc2946293757389468f1fa09714860f8e1147b7/test/parallel/test-timers-interval-promisified.js\nexport const timersIntervalPromise = {\n  async test() {\n    {\n      const iterable = timersPromises.setInterval(1, undefined);\n      const iterator = iterable[Symbol.asyncIterator]();\n      const result = iterator.next();\n      ok(!result.done, 'iterator was wrongly marked as done');\n      strictEqual(result.value, undefined);\n      await iterator.return();\n    }\n\n    {\n      const iterable = timersPromises.setInterval(1, 'foobar');\n      const iterator = iterable[Symbol.asyncIterator]();\n      const result = await iterator.next();\n      ok(!result.done, 'iterator was wronly marked as done');\n      strictEqual(result.value, 'foobar');\n      await iterator.return();\n    }\n\n    {\n      const iterable = timersPromises.setInterval(1, 'foobar');\n      const iterator = iterable[Symbol.asyncIterator]();\n      const result = await iterator.next();\n\n      ok(!result.done, 'iterator was wronly marked as done');\n      strictEqual(result.value, 'foobar');\n      await iterator.next();\n    }\n\n    {\n      const signal = AbortSignal.abort(); // Abort in advance\n\n      const iterable = timersPromises.setInterval(1, undefined, { signal });\n      const iterator = iterable[Symbol.asyncIterator]();\n      await rejects(iterator.next(), /AbortError/);\n    }\n\n    {\n      const ac = new AbortController();\n      const { signal } = ac;\n\n      const iterable = timersPromises.setInterval(100, undefined, { signal });\n      const iterator = iterable[Symbol.asyncIterator]();\n\n      // This promise should take 100 seconds to resolve, so now aborting it should\n      // mean we abort early\n      const promise = iterator.next();\n\n      ac.abort(); // Abort in after we have a next promise\n\n      await rejects(promise, /AbortError/);\n    }\n\n    {\n      // Check aborting after getting a value.\n      const ac = new AbortController();\n      const { signal } = ac;\n\n      const iterable = timersPromises.setInterval(100, undefined, { signal });\n      const iterator = iterable[Symbol.asyncIterator]();\n\n      const promise = iterator.next();\n      const abortPromise = promise.then(ac.abort()).then(() => iterator.next());\n      await rejects(abortPromise, /AbortError/);\n    }\n\n    {\n      for (const ref of [1, '', Infinity, null, {}]) {\n        const iterable = timersPromises.setInterval(10, undefined, { ref });\n        await rejects(\n          () => iterable[Symbol.asyncIterator]().next(),\n          /ERR_INVALID_ARG_TYPE/\n        );\n      }\n\n      for (const signal of [1, '', Infinity, null, {}]) {\n        const iterable = timersPromises.setInterval(10, undefined, { signal });\n        await rejects(\n          () => iterable[Symbol.asyncIterator]().next(),\n          /ERR_INVALID_ARG_TYPE/\n        );\n      }\n\n      for (const options of [1, '', Infinity, null, true, false]) {\n        const iterable = timersPromises.setInterval(10, undefined, options);\n        await rejects(\n          () => iterable[Symbol.asyncIterator]().next(),\n          /ERR_INVALID_ARG_TYPE/\n        );\n      }\n    }\n\n    {\n      async function runInterval(fn, intervalTime, signal) {\n        const input = 'foobar';\n        const interval = timersPromises.setInterval(intervalTime, input, {\n          signal,\n        });\n        let iteration = 0;\n        for await (const value of interval) {\n          strictEqual(value, input);\n          iteration++;\n          await fn(iteration);\n        }\n      }\n\n      {\n        // Check that we call the correct amount of times.\n        const controller = new AbortController();\n        const { signal } = controller;\n\n        let loopCount = 0;\n        const delay = 20;\n        const timeoutLoop = runInterval(\n          () => {\n            loopCount++;\n            if (loopCount === 5) controller.abort();\n            if (loopCount > 5) throw new Error('ran too many times');\n          },\n          delay,\n          signal\n        );\n\n        await rejects(timeoutLoop, /AbortError/);\n        strictEqual(loopCount, 5);\n      }\n\n      {\n        // Check that if we abort when we have some unresolved callbacks,\n        // we actually call them.\n        const controller = new AbortController();\n        const { signal } = controller;\n        const delay = 10;\n        let totalIterations = 0;\n        const timeoutLoop = runInterval(\n          async (iterationNumber) => {\n            await timersPromises.setTimeout(delay * 4);\n            if (iterationNumber <= 2) {\n              strictEqual(signal.aborted, false);\n            }\n            if (iterationNumber === 2) {\n              controller.abort();\n            }\n            if (iterationNumber > 2) {\n              strictEqual(signal.aborted, true);\n            }\n            if (iterationNumber > totalIterations) {\n              totalIterations = iterationNumber;\n            }\n          },\n          delay,\n          signal\n        );\n\n        await timeoutLoop.catch(() => {\n          ok(totalIterations >= 3, `iterations was ${totalIterations} < 3`);\n        });\n      }\n    }\n\n    {\n      // Check that the timing is correct\n      let pre = false;\n      let post = false;\n\n      const time_unit = 50;\n      await Promise.all([\n        timersPromises.setTimeout(1).then(() => (pre = true)),\n        new Promise((res) => {\n          const iterable = timersPromises.setInterval(time_unit * 2);\n          const iterator = iterable[Symbol.asyncIterator]();\n\n          iterator\n            .next()\n            .then(() => {\n              ok(pre, 'interval ran too early');\n              ok(!post, 'interval ran too late');\n              return iterator.next();\n            })\n            .then(() => {\n              ok(post, 'second interval ran too early');\n              return iterator.return();\n            })\n            .then(res);\n        }),\n        timersPromises.setTimeout(time_unit * 3).then(() => (post = true)),\n      ]);\n    }\n  },\n};\n\nexport const testImmediateFromOtherContext = {\n  async test(_, env) {\n    // Set up the global setImmediate...\n    await env.SUBREQUEST.fetch('http://example.com');\n\n    // Try to access it from a different IoContext.\n    await env.SUBREQUEST.fetch('http://example.com');\n  },\n};\n\nexport default {\n  fetch() {\n    if (globalThis.IMMEDIATE_TEST === undefined) {\n      globalThis.IMMEDIATE_TEST = setImmediate(() => {});\n      globalThis.IMMEDIATE_TEST[Symbol.dispose]();\n    } else {\n      throws(() => globalThis.IMMEDIATE_TEST[Symbol.dispose](), {\n        message: /perform I\\/O on behalf of a different request/,\n      });\n    }\n    return new Response();\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/timers-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"nodejs-timers-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"timers-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\"],\n        bindings = [\n          ( name = \"SUBREQUEST\", service = \"nodejs-timers-test\" ),\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/tls-nodejs-tcp-server.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// This file is used as a sidecar for the tls-nodejs-test tests.\nconst tls = require('node:tls');\n\nfunction reportPort(server) {\n  const address = server.address();\n  console.info(`Listening on ${address.address}:${address.port}`);\n}\n\n// Taken from https://github.com/nodejs/node/blob/304743655d5236c2edc39094336ee2667600b684/test/fixtures/keys/agent1-key.pem\nconst AGENT1_KEY_PEM = `-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCn5Ieb/G+/y5iD\nMPWMJTjHhUDb4U5UiWyD69/jJ3W3tKPS62PaOHaOyuhd4aqNT0q3Eq3VhwlqkuoU\nRUmE+8r7QkU9vnIcALf9/rvt8g9K1BAx+EUPjFwWOXgjCs/8Z5KPDrQ3cKxFTzkB\n7TCcequLz/cKPet7lA16ZRe9d5QlhuUWqGlUuIAu78h0I7OZKrhE4bp5ZJcHGtO8\n3JLp4CLxE28MZgzlxebvvLS+1fohoc1IFaxG0yxwYawPsjSFDaTLlM/IqL6imLUH\n+KHrI9QIEWkrsNfqaTu5FlsKA5OrecFePw5Mgm65ljtRz+hISW2YFYT0GB07oaUl\nc3HnNgLpAgMBAAECggEAGGIaR887s5EwDy2XG8l0G5YAu25XX/OtbONe2rCqagm0\nGTfSgqjcnxRc9vWFPYycf0YZNP+toGrB0DvX15Zx/le7kqIMFIEON7c9N+uFyQpP\nZ9J0xTNPVHL4Pa6eUjwAjwJFrh+RBWfiEaOPAcrXCzEi4bvobUQtSO9RqVSqkWBv\nwEp6oI0gJvIpK7pRrUTCTR0qfFXTsaabvpWZvdmMtoDgiTRJhg2Dhvzy4Ox1y4/Z\nfNKO/+KirKLS/FNje6b2LbzxFZ9YePDn0w3XD/iLaRduJ3UHNuMCl3tl3zVlgvKI\nHhlCBnXcAbSWn3WqcQAUF57eRbwlt11za/gZi57DGQKBgQDRUtQthF9JleEoZ2YF\ntQBanwz3AzEV2KHt3QrbjBxq3zhipx6K1iFr2SB6nDOigbJB/87gtoBdBbg9ujwj\n2K3bO3FoQsCLTikXUJ5OD01vCY4RDAjddNVdnJEtbONEZOVSV1GGxtEzMsrJT1Gs\n2LKmiSXEMRQhXJWgFaif8sFQ7wKBgQDNVKIS9Rh7ShSzXUhRwVmNvfHSiYEgeeGz\n3mJpAUKMHp2WzWZrWfTOBfm4+2/dg7Q8dTE6okzS/I+6a3PPnB3lTSLYJmnI64DB\nYccKKuxpk4zixxTWQk6YtJfBCyb16qWQoXAHBM7OVr3hd5egArLGNKxafERH8orX\nkY2rtzs5pwKBgEZJX8Gg7zYQQ7iDb7h+3I2RVpMi2TqSsVzjmh+6Xlhsd8x4fUL1\nP+es0sEY7iWlEywiL185KMUThJgFjugie85fmWb+8xRTvGx9v4pKjR+5v6BtwBRM\nhNCYIA92vqFal74cX923qMteRMVwAubdJK/S4YGNUUsagYttel+q7cq1AoGAHvn4\npYGCWv83FkQpZ+QSfZa9R7Tk3SBmE3umPw8omfj4b0q3e9SLYRV3sheErddztnc7\noQvhKSdfC5GwXA7CV9iGPDO3W89jkkkM/RSyq87Nv1ynYReJwfHkvwPOseTfa21f\neD+ab3iYls4y+rnNfKdvpQsARhZqKdFUnSY8chsCgYBKx7zcgqSg+bdM9DB8Wzph\n4ZQBLo3n2HZGpNTfT5UaKguYXqswQ5F/zPfxJBIwTAfL92bJnzRZpzVPcPg6QHhR\n0DNeltg5Ij061Zyz2/RnDHS16uyQNENb4lcSlqdanlOsluVqE0GQC8y+Rvf+tA10\nh0ttUVaTtmba2EGRzoaNWA==\n-----END PRIVATE KEY-----`;\n\n// Taken from https://github.com/nodejs/node/blob/304743655d5236c2edc39094336ee2667600b684/test/fixtures/keys/agent1-cert.pem\nconst AGENT1_CERT_PEM = `-----BEGIN CERTIFICATE-----\nMIIDeDCCAmCgAwIBAgIUdzLeCZ2khgssLeLiVYURZo/Tx8EwDQYJKoZIhvcNAQEL\nBQAwTTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCk5ldyBKZXJzZXkxFDASBgNVBAcM\nC0plcnNleSBDaXR5MRMwEQYDVQQKDApDbG91ZGZsYXJlMB4XDTI1MDMwNTE2MTUy\nMVoXDTI3MDMwNTE2MTUyMVowRTELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUt\nU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJ\nKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKfkh5v8b7/LmIMw9YwlOMeFQNvhTlSJ\nbIPr3+Mndbe0o9LrY9o4do7K6F3hqo1PSrcSrdWHCWqS6hRFSYT7yvtCRT2+chwA\nt/3+u+3yD0rUEDH4RQ+MXBY5eCMKz/xnko8OtDdwrEVPOQHtMJx6q4vP9wo963uU\nDXplF713lCWG5RaoaVS4gC7vyHQjs5kquEThunlklwca07zckungIvETbwxmDOXF\n5u+8tL7V+iGhzUgVrEbTLHBhrA+yNIUNpMuUz8iovqKYtQf4oesj1AgRaSuw1+pp\nO7kWWwoDk6t5wV4/DkyCbrmWO1HP6EhJbZgVhPQYHTuhpSVzcec2AukCAwEAAaNY\nMFYwFAYDVR0RBA0wC4IJbG9jYWxob3N0MB0GA1UdDgQWBBT1Uf2E3xOj0HPkG2e5\nZiCx0GSYKDAfBgNVHSMEGDAWgBRlkgGmcLtZzVTUu0MvdYI8I2gwCTANBgkqhkiG\n9w0BAQsFAAOCAQEAuv0c58unZvUOK2nL007Vp/bjjfKf/rpsvlHKgZ2Oa4Xik/Io\nO3Jh4/uTlTlq0GI5zSTQSdiI28yiKqGRmCtGkES9mjPzUkL+pTj/pFgyCjYfu/Z0\nlIn1zwVEp1AXxhID3QvUDEeEM5+sE3i1KlGpF/0eYFJN6/ClS+VbJXmacdJATGnU\nW3QKeel2BcFNDFreh8g+Ebb6K2P3k4+PHTWAuzcqu7eIEe4cKEjIDXI9vdckjq4Q\nNbc7EfWb2Gh/6S/Uz6y+AIN3l7ybJTqzWnOWZPCzoMpJuS+d5ir36DJ3BpXKCTHT\nVRpM7av//cc39AeMvU6RTQSYVDy4NJx+mfRqLw==\n-----END CERTIFICATE-----`;\n\nconst options = {\n  key: AGENT1_KEY_PEM,\n  cert: AGENT1_CERT_PEM,\n  rejectUnauthorized: true,\n};\n\nconst echoServer = tls.createServer(options, (s) => {\n  s.setTimeout(100);\n  s.on('error', () => {\n    // Do nothing\n  });\n  s.pipe(s);\n});\nechoServer.listen(\n  process.env.ECHO_SERVER_PORT,\n  process.env.SIDECAR_HOSTNAME,\n  () => reportPort(echoServer)\n);\n\n// Taken from test-tls-connect-given-socket.js\nconst helloServer = tls.createServer(options, (socket) => {\n  socket.end('Hello');\n});\nhelloServer.listen(\n  process.env.HELLO_SERVER_PORT,\n  process.env.SIDECAR_HOSTNAME,\n  () => reportPort(helloServer)\n);\n\nconst jsStreamServer = tls.createServer(options, (socket) => {\n  socket.resume();\n  socket.end('ohai');\n});\njsStreamServer.listen(\n  process.env.JS_STREAM_SERVER_PORT,\n  process.env.SIDECAR_HOSTNAME,\n  () => reportPort(jsStreamServer)\n);\n\n// Taken from test/parallel/test-tls-streamwrap-buffersize.js\nconst streamWrapServer = tls.createServer(options, (socket) => {\n  let str = '';\n  socket.setEncoding('utf-8');\n  socket.on('data', (chunk) => {\n    str += chunk;\n  });\n\n  socket.on(\n    'end',\n    common.mustCall(() => {\n      strictEqual(str, 'a'.repeat(iter - 1));\n      server.close();\n    })\n  );\n});\nstreamWrapServer.listen(\n  process.env.STREAM_WRAP_SERVER_PORT,\n  process.env.SIDECAR_HOSTNAME,\n  () => reportPort(streamWrapServer)\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/tls-nodejs-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\nimport { connect } from 'cloudflare:sockets';\nimport tls from 'node:tls';\nimport {\n  strictEqual,\n  ok,\n  rejects,\n  throws,\n  doesNotThrow,\n  deepStrictEqual,\n} from 'node:assert';\nimport { once } from 'node:events';\nimport { inspect } from 'node:util';\nimport net from 'node:net';\nimport { translatePeerCertificate } from '_tls_common';\nimport { mock } from 'node:test';\nimport stream from 'node:stream';\n\nexport const checkPortsSetCorrectly = {\n  test(ctrl, env, ctx) {\n    ok(env.ECHO_SERVER_PORT);\n    ok(env.HELLO_SERVER_PORT);\n    ok(env.JS_STREAM_SERVER_PORT);\n    ok(env.STREAM_WRAP_SERVER_PORT);\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/304743655d5236c2edc39094336ee2667600b684/test/parallel/test-tls-connect-abort-controller.js\nexport const tlsConnectAbortController = {\n  async test(ctrl, env, ctx) {\n    // Our tests differ from Node.js\n    // We don't check for abortSignal listener count because it's not supported.\n    const connectOptions = (signal) => ({\n      port: env.ECHO_SERVER_PORT,\n      host: 'localhost',\n      signal,\n    });\n\n    const assertAbort = (socket, testName) => {\n      return rejects(\n        () => once(socket, 'close'),\n        {\n          name: 'AbortError',\n        },\n        `AbortError should have been thrown on ${testName}`\n      );\n    };\n\n    async function postAbort() {\n      const ac = new AbortController();\n      const { signal } = ac;\n      const socket = tls.connect(connectOptions(signal));\n      ac.abort();\n      await assertAbort(socket, 'postAbort');\n    }\n\n    async function preAbort() {\n      const ac = new AbortController();\n      const { signal } = ac;\n      ac.abort();\n      const socket = tls.connect(connectOptions(signal));\n      await assertAbort(socket, 'preAbort');\n    }\n\n    async function tickAbort() {\n      const ac = new AbortController();\n      const { signal } = ac;\n      const socket = tls.connect(connectOptions(signal));\n      setImmediate(() => ac.abort());\n      await assertAbort(socket, 'tickAbort');\n    }\n\n    async function testConstructor() {\n      const ac = new AbortController();\n      const { signal } = ac;\n      ac.abort();\n      const socket = new tls.TLSSocket(undefined, connectOptions(signal));\n      await assertAbort(socket, 'testConstructor');\n    }\n\n    async function testConstructorPost() {\n      const ac = new AbortController();\n      const { signal } = ac;\n      const socket = new tls.TLSSocket(undefined, connectOptions(signal));\n      ac.abort();\n      await assertAbort(socket, 'testConstructorPost');\n    }\n\n    async function testConstructorPostTick() {\n      const ac = new AbortController();\n      const { signal } = ac;\n      const socket = new tls.TLSSocket(undefined, connectOptions(signal));\n      setImmediate(() => ac.abort());\n      await assertAbort(socket, 'testConstructorPostTick');\n    }\n\n    await postAbort();\n    await preAbort();\n    await tickAbort();\n    await testConstructor();\n    await testConstructorPost();\n    await testConstructorPostTick();\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/304743655d5236c2edc39094336ee2667600b684/test/parallel/test-tls-connect-allow-half-open-option.js\nexport const connectAllowHalfOpenOption = {\n  async test(ctrl, env, ctx) {\n    {\n      const socket = tls.connect({ port: 42, lookup() {} });\n      strictEqual(socket.allowHalfOpen, false);\n    }\n\n    {\n      const socket = tls.connect({\n        port: 42,\n        allowHalfOpen: false,\n        lookup() {},\n      });\n      strictEqual(socket.allowHalfOpen, false);\n    }\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      const socket = tls.connect(\n        {\n          port: env.ECHO_SERVER_PORT,\n          allowHalfOpen: true,\n        },\n        () => {\n          let message = '';\n\n          socket.on('data', (chunk) => {\n            message += chunk;\n          });\n\n          socket.on('end', () => {\n            strictEqual(message, 'Hello');\n            resolve();\n          });\n\n          socket.write('Hello');\n          socket.end();\n        }\n      );\n\n      socket.setEncoding('utf8');\n\n      await promise;\n    }\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/755e4603fd1679de72d250514ea5096b272ae8d6/test/parallel/test-tls-connect-simple.js\nexport const tlsConnectSimple = {\n  async test(ctrl, env, ctx) {\n    const promise1 = Promise.withResolvers();\n    const promise2 = Promise.withResolvers();\n    const options = { port: env.ECHO_SERVER_PORT };\n    const client1 = tls.connect(options, function () {\n      client1.end();\n      promise1.resolve();\n    });\n    const client2 = tls.connect(options);\n    client2.on('secureConnect', function () {\n      client2.end();\n      promise2.resolve();\n    });\n    await Promise.all([promise1.promise, promise2.promise]);\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/755e4603fd1679de72d250514ea5096b272ae8d6/test/parallel/test-tls-connect-timeout-option.js\nexport const tlsConnectTimeoutOption = {\n  async test(ctrl, env, ctx) {\n    const socket = tls.connect({\n      port: env.ECHO_SERVER_PORT,\n      lookup: () => {},\n      timeout: 1000,\n    });\n\n    strictEqual(socket.timeout, 1000);\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/755e4603fd1679de72d250514ea5096b272ae8d6/test/parallel/test-tls-connect-no-host.js\nexport const tlsConnectNoHost = {\n  async test(ctrl, env, ctx) {\n    const { promise, resolve } = Promise.withResolvers();\n    const socket = tls.connect(\n      {\n        port: env.ECHO_SERVER_PORT,\n        // No host set here. 'localhost' is the default,\n        // but tls.checkServerIdentity() breaks before the fix with:\n        // Error: Hostname/IP doesn't match certificate's altnames:\n        //   \"Host: undefined. is not cert's CN: localhost\"\n      },\n      function () {\n        ok(socket.authorized);\n        resolve();\n      }\n    );\n    await promise;\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/755e4603fd1679de72d250514ea5096b272ae8d6/test/parallel/test-tls-connect-given-socket.js\nexport const tlsConnectGivenSocket = {\n  async test(ctrl, env, ctx) {\n    const promises = [];\n    let waiting = 2;\n    function establish(socket, calls) {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      promises.push(promise);\n      const onConnectFn = mock.fn(() => {\n        if (calls === 0) {\n          reject(new Error('Should not have called onConnect callback'));\n          return;\n        }\n        let data = '';\n        let dataFn = mock.fn((chunk) => {\n          data += chunk.toString();\n        });\n        client.on('data', dataFn);\n        client.on('end', () => {\n          strictEqual(data, 'Hello');\n          if (--waiting === 0) {\n            ok(dataFn.mock.callCount());\n            resolve();\n          }\n        });\n      });\n      const client = tls.connect({ socket }, onConnectFn);\n      ok(client.readable);\n      ok(client.writable);\n\n      if (calls === 0) {\n        queueMicrotask(resolve);\n      }\n\n      return client;\n    }\n\n    const port = env.HELLO_SERVER_PORT;\n    // Immediate death socket\n    const immediateDeath = net.connect(port);\n    establish(immediateDeath, 0).destroy();\n\n    // Outliving\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      promises.push(promise);\n      const outlivingTCP = net.connect(port, () => {\n        outlivingTLS.destroy();\n        next();\n        resolve();\n      });\n      const outlivingTLS = establish(outlivingTCP, 0);\n    }\n\n    function next() {\n      // Already connected socket\n      const { promise, resolve } = Promise.withResolvers();\n      promises.push(promise);\n      const connected = net.connect(port, () => {\n        establish(connected);\n        resolve();\n      });\n\n      // Connecting socket\n      const connecting = net.connect(port);\n      establish(connecting);\n    }\n\n    await Promise.all(promises);\n  },\n};\n\nexport const testSecureContext = {\n  async test() {\n    throws(() => tls.connect({ port: 42, secureContext: {} }), {\n      code: 'ERR_TLS_INVALID_CONTEXT',\n    });\n\n    doesNotThrow(() => {\n      const secureContext = tls.createSecureContext({});\n      tls.connect({ port: 42, secureContext });\n    });\n\n    doesNotThrow(() => {\n      const secureContext = tls.SecureContext();\n      tls.connect({ port: 42, secureContext });\n    });\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/98513884684bccf944d7834f4820b061af41fb36/test/parallel/test-tls-check-server-identity.js\nexport const testCheckServerIdentity = {\n  async test() {\n    const tests = [\n      // False-y values.\n      {\n        host: false,\n        cert: { subject: { CN: 'a.com' } },\n        error: \"Host: false. is not cert's CN: a.com\",\n      },\n      {\n        host: null,\n        cert: { subject: { CN: 'a.com' } },\n        error: \"Host: null. is not cert's CN: a.com\",\n      },\n      {\n        host: undefined,\n        cert: { subject: { CN: 'a.com' } },\n        error: \"Host: undefined. is not cert's CN: a.com\",\n      },\n\n      // Basic CN handling\n      { host: 'a.com', cert: { subject: { CN: 'a.com' } } },\n      { host: 'a.com', cert: { subject: { CN: 'A.COM' } } },\n      {\n        host: 'a.com',\n        cert: { subject: { CN: 'b.com' } },\n        error: \"Host: a.com. is not cert's CN: b.com\",\n      },\n      { host: 'a.com', cert: { subject: { CN: 'a.com.' } } },\n      {\n        host: 'a.com',\n        cert: { subject: { CN: '.a.com' } },\n        error: \"Host: a.com. is not cert's CN: .a.com\",\n      },\n\n      // IP address in CN. Technically allowed but so rare that we reject\n      // it anyway. If we ever do start allowing them, we should take care\n      // to only allow public (non-internal, non-reserved) IP addresses,\n      // because that's what the spec mandates.\n      {\n        host: '8.8.8.8',\n        cert: { subject: { CN: '8.8.8.8' } },\n        error: \"IP: 8.8.8.8 is not in the cert's list: \",\n      },\n\n      // The spec suggests that a \"DNS:\" Subject Alternative Name containing an\n      // IP address is valid but it seems so suspect that we currently reject it.\n      {\n        host: '8.8.8.8',\n        cert: { subject: { CN: '8.8.8.8' }, subjectaltname: 'DNS:8.8.8.8' },\n        error: \"IP: 8.8.8.8 is not in the cert's list: \",\n      },\n\n      // Likewise for \"URI:\" Subject Alternative Names.\n      // See also https://github.com/nodejs/node/issues/8108.\n      {\n        host: '8.8.8.8',\n        cert: {\n          subject: { CN: '8.8.8.8' },\n          subjectaltname: 'URI:http://8.8.8.8/',\n        },\n        error: \"IP: 8.8.8.8 is not in the cert's list: \",\n      },\n\n      // An \"IP Address:\" Subject Alternative Name however is acceptable.\n      {\n        host: '8.8.8.8',\n        cert: {\n          subject: { CN: '8.8.8.8' },\n          subjectaltname: 'IP Address:8.8.8.8',\n        },\n      },\n\n      // But not when it's a CIDR.\n      {\n        host: '8.8.8.8',\n        cert: {\n          subject: { CN: '8.8.8.8' },\n          subjectaltname: 'IP Address:8.8.8.0/24',\n        },\n        error: \"IP: 8.8.8.8 is not in the cert's list: \",\n      },\n\n      // Wildcards in CN\n      { host: 'b.a.com', cert: { subject: { CN: '*.a.com' } } },\n      {\n        host: 'ba.com',\n        cert: { subject: { CN: '*.a.com' } },\n        error: \"Host: ba.com. is not cert's CN: *.a.com\",\n      },\n      {\n        host: '\\n.b.com',\n        cert: { subject: { CN: '*n.b.com' } },\n        error: \"Host: \\n.b.com. is not cert's CN: *n.b.com\",\n      },\n      {\n        host: 'b.a.com',\n        cert: {\n          subjectaltname: 'DNS:omg.com',\n          subject: { CN: '*.a.com' },\n        },\n        error: \"Host: b.a.com. is not in the cert's altnames: \" + 'DNS:omg.com',\n      },\n      {\n        host: 'b.a.com',\n        cert: { subject: { CN: 'b*b.a.com' } },\n        error: \"Host: b.a.com. is not cert's CN: b*b.a.com\",\n      },\n\n      // Empty Cert\n      {\n        host: 'a.com',\n        cert: {},\n        error: 'Cert does not contain a DNS name',\n      },\n\n      // Empty Subject w/DNS name\n      {\n        host: 'a.com',\n        cert: {\n          subjectaltname: 'DNS:a.com',\n        },\n      },\n\n      // Empty Subject w/URI name\n      {\n        host: 'a.b.a.com',\n        cert: {\n          subjectaltname: 'URI:http://a.b.a.com/',\n        },\n        error: 'Cert does not contain a DNS name',\n      },\n\n      // Multiple CN fields\n      {\n        host: 'foo.com',\n        cert: {\n          subject: { CN: ['foo.com', 'bar.com'] }, // CN=foo.com; CN=bar.com;\n        },\n      },\n\n      // DNS names and CN\n      {\n        host: 'a.com',\n        cert: {\n          subjectaltname: 'DNS:*',\n          subject: { CN: 'b.com' },\n        },\n        error: \"Host: a.com. is not in the cert's altnames: \" + 'DNS:*',\n      },\n      {\n        host: 'a.com',\n        cert: {\n          subjectaltname: 'DNS:*.com',\n          subject: { CN: 'b.com' },\n        },\n        error: \"Host: a.com. is not in the cert's altnames: \" + 'DNS:*.com',\n      },\n      {\n        host: 'a.co.uk',\n        cert: {\n          subjectaltname: 'DNS:*.co.uk',\n          subject: { CN: 'b.com' },\n        },\n      },\n      {\n        host: 'a.com',\n        cert: {\n          subjectaltname: 'DNS:*.a.com',\n          subject: { CN: 'a.com' },\n        },\n        error: \"Host: a.com. is not in the cert's altnames: \" + 'DNS:*.a.com',\n      },\n      {\n        host: 'a.com',\n        cert: {\n          subjectaltname: 'DNS:*.a.com',\n          subject: { CN: 'b.com' },\n        },\n        error: \"Host: a.com. is not in the cert's altnames: \" + 'DNS:*.a.com',\n      },\n      {\n        host: 'a.com',\n        cert: {\n          subjectaltname: 'DNS:a.com',\n          subject: { CN: 'b.com' },\n        },\n      },\n      {\n        host: 'a.com',\n        cert: {\n          subjectaltname: 'DNS:A.COM',\n          subject: { CN: 'b.com' },\n        },\n      },\n\n      // DNS names\n      {\n        host: 'a.com',\n        cert: {\n          subjectaltname: 'DNS:*.a.com',\n          subject: {},\n        },\n        error: \"Host: a.com. is not in the cert's altnames: \" + 'DNS:*.a.com',\n      },\n      {\n        host: 'b.a.com',\n        cert: {\n          subjectaltname: 'DNS:*.a.com',\n          subject: {},\n        },\n      },\n      {\n        host: 'c.b.a.com',\n        cert: {\n          subjectaltname: 'DNS:*.a.com',\n          subject: {},\n        },\n        error:\n          \"Host: c.b.a.com. is not in the cert's altnames: \" + 'DNS:*.a.com',\n      },\n      {\n        host: 'b.a.com',\n        cert: {\n          subjectaltname: 'DNS:*b.a.com',\n          subject: {},\n        },\n      },\n      {\n        host: 'a-cb.a.com',\n        cert: {\n          subjectaltname: 'DNS:*b.a.com',\n          subject: {},\n        },\n      },\n      {\n        host: 'a.b.a.com',\n        cert: {\n          subjectaltname: 'DNS:*b.a.com',\n          subject: {},\n        },\n        error:\n          \"Host: a.b.a.com. is not in the cert's altnames: \" + 'DNS:*b.a.com',\n      },\n      // Multiple DNS names\n      {\n        host: 'a.b.a.com',\n        cert: {\n          subjectaltname: 'DNS:*b.a.com, DNS:a.b.a.com',\n          subject: {},\n        },\n      },\n      // URI names\n      {\n        host: 'a.b.a.com',\n        cert: {\n          subjectaltname: 'URI:http://a.b.a.com/',\n          subject: {},\n        },\n        error: 'Cert does not contain a DNS name',\n      },\n      {\n        host: 'a.b.a.com',\n        cert: {\n          subjectaltname: 'URI:http://*.b.a.com/',\n          subject: {},\n        },\n        error: 'Cert does not contain a DNS name',\n      },\n      // IP addresses\n      {\n        host: 'a.b.a.com',\n        cert: {\n          subjectaltname: 'IP Address:127.0.0.1',\n          subject: {},\n        },\n        error: 'Cert does not contain a DNS name',\n      },\n      {\n        host: '127.0.0.1',\n        cert: {\n          subjectaltname: 'IP Address:127.0.0.1',\n          subject: {},\n        },\n      },\n      {\n        host: '127.0.0.2',\n        cert: {\n          subjectaltname: 'IP Address:127.0.0.1',\n          subject: {},\n        },\n        error: \"IP: 127.0.0.2 is not in the cert's list: \" + '127.0.0.1',\n      },\n      {\n        host: '127.0.0.1',\n        cert: {\n          subjectaltname: 'DNS:a.com',\n          subject: {},\n        },\n        error: \"IP: 127.0.0.1 is not in the cert's list: \",\n      },\n      {\n        host: 'localhost',\n        cert: {\n          subjectaltname: 'DNS:a.com',\n          subject: { CN: 'localhost' },\n        },\n        error: \"Host: localhost. is not in the cert's altnames: \" + 'DNS:a.com',\n      },\n      // IDNA\n      {\n        host: 'xn--bcher-kva.example.com',\n        cert: { subject: { CN: '*.example.com' } },\n      },\n      // RFC 6125, section 6.4.3: \"[...] the client SHOULD NOT attempt to match\n      // a presented identifier where the wildcard character is embedded within\n      // an A-label [...]\"\n      {\n        host: 'xn--bcher-kva.example.com',\n        cert: { subject: { CN: 'xn--*.example.com' } },\n        error:\n          \"Host: xn--bcher-kva.example.com. is not cert's CN: \" +\n          'xn--*.example.com',\n      },\n    ];\n\n    tests.forEach(function (test, i) {\n      const err = tls.checkServerIdentity(test.host, test.cert);\n      strictEqual(\n        err?.reason,\n        test.error,\n        `Test# ${i} failed: ${inspect(test)} \\n` +\n          `${test.error} != ${err?.reason}`\n      );\n    });\n  },\n};\n\n// Tests are taken from\n// https://github.com/nodejs/node/blob/1b5b019de1be9259e4374ca1d6ee7b3b28c48856/test/parallel/test-tls-translate-peer-certificate.js\nexport const testTlsTranslatePeerCertificate = {\n  async test() {\n    const certString = '__proto__=42\\nA=1\\nB=2\\nC=3';\n\n    strictEqual(translatePeerCertificate(null), null);\n    strictEqual(translatePeerCertificate(undefined), null);\n\n    strictEqual(translatePeerCertificate(0), null);\n    strictEqual(translatePeerCertificate(1), 1);\n\n    deepStrictEqual(translatePeerCertificate({}), {});\n\n    // Earlier versions of Node.js parsed the issuer property but did so\n    // incorrectly. This behavior has now reached end-of-life and user-supplied\n    // strings will not be parsed at all.\n    deepStrictEqual(translatePeerCertificate({ issuer: '' }), { issuer: '' });\n    deepStrictEqual(translatePeerCertificate({ issuer: null }), {\n      issuer: null,\n    });\n    deepStrictEqual(translatePeerCertificate({ issuer: certString }), {\n      issuer: certString,\n    });\n\n    // Earlier versions of Node.js parsed the issuer property but did so\n    // incorrectly. This behavior has now reached end-of-life and user-supplied\n    // strings will not be parsed at all.\n    deepStrictEqual(translatePeerCertificate({ subject: '' }), { subject: '' });\n    deepStrictEqual(translatePeerCertificate({ subject: null }), {\n      subject: null,\n    });\n    deepStrictEqual(translatePeerCertificate({ subject: certString }), {\n      subject: certString,\n    });\n\n    deepStrictEqual(translatePeerCertificate({ issuerCertificate: '' }), {\n      issuerCertificate: null,\n    });\n    deepStrictEqual(translatePeerCertificate({ issuerCertificate: null }), {\n      issuerCertificate: null,\n    });\n    deepStrictEqual(\n      translatePeerCertificate({ issuerCertificate: { subject: certString } }),\n      { issuerCertificate: { subject: certString } }\n    );\n\n    {\n      const cert = {};\n      cert.issuerCertificate = cert;\n      deepStrictEqual(translatePeerCertificate(cert), {\n        issuerCertificate: cert,\n      });\n    }\n\n    deepStrictEqual(translatePeerCertificate({ infoAccess: '' }), {\n      infoAccess: { __proto__: null },\n    });\n    deepStrictEqual(translatePeerCertificate({ infoAccess: null }), {\n      infoAccess: null,\n    });\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/b1402835a512f14fa9f8dd23d3e0cee8cfe888a2/test/parallel/test-tls-basic-validations.js\nexport const testConvertALPNProtocols = {\n  async test() {\n    {\n      const buffer = Buffer.from('abcd');\n      const out = {};\n      tls.convertALPNProtocols(buffer, out);\n      out.ALPNProtocols.write('efgh');\n      ok(buffer.equals(Buffer.from('abcd')));\n      ok(out.ALPNProtocols.equals(Buffer.from('efgh')));\n    }\n\n    {\n      const protocols = [new String('a').repeat(500)];\n      const out = {};\n      throws(() => tls.convertALPNProtocols(protocols, out), {\n        code: 'ERR_OUT_OF_RANGE',\n        message:\n          'The byte length of the protocol at index 0 exceeds the ' +\n          'maximum length. It must be <= 255. Received 500',\n      });\n    }\n  },\n};\n\nexport const testStartTlsBehaviorOnUpgrade = {\n  async test(ctrl, env) {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const socket = connect(`localhost:${env.HELLO_SERVER_PORT}`, {\n      secureTransport: 'starttls',\n    });\n    strictEqual(socket.secureTransport, 'starttls');\n    strictEqual(socket.upgraded, false);\n    await socket.opened;\n    strictEqual(socket.upgraded, false);\n    socket.closed\n      .then(() => {\n        strictEqual(socket.secureTransport, 'starttls');\n        strictEqual(socket.upgraded, true);\n        resolve();\n      })\n      .catch(reject);\n    const secureSocket = socket.startTls();\n    // The newly created socket instance is not upgraded.\n    strictEqual(secureSocket.upgraded, false);\n    strictEqual(secureSocket.secureTransport, 'on');\n    await promise;\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/52d95f53e466016120048fb43b3732ff9089ecd7/test/parallel/test-tls-destroy-whilst-write.js\nexport const testTlsDestroyWhilstWrite = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n    const delay = new stream.Duplex({\n      read: function read() {},\n      write: function write(data, enc, cb) {\n        queueMicrotask(cb);\n      },\n    });\n\n    const secure = tls.connect({\n      socket: delay,\n    });\n    queueMicrotask(function () {\n      secure.destroy();\n    });\n    secure.on('close', resolve);\n    await promise;\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/52d95f53e466016120048fb43b3732ff9089ecd7/test/parallel/test-tls-js-stream.js\nexport const testTlsJsStream = {\n  async test(ctrl, env) {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const raw = net.connect(env.JS_STREAM_SERVER_PORT);\n\n    let pending = false;\n    raw.on('readable', function () {\n      if (pending) socket._read();\n    });\n\n    raw.on('end', function () {\n      socket.push(null);\n    });\n\n    const socket = new stream.Duplex({\n      read: function read() {\n        pending = false;\n\n        const chunk = raw.read();\n        if (chunk) {\n          this.push(chunk);\n        } else {\n          pending = true;\n        }\n      },\n      write: function write(data, enc, cb) {\n        raw.write(data, enc, cb);\n      },\n    });\n\n    const onConnectFn = mock.fn(() => {\n      socket.resume();\n      socket.end('hello');\n    });\n    const conn = tls.connect({ socket }, onConnectFn);\n    conn.once('error', reject);\n    conn.once('close', resolve);\n\n    await promise;\n    strictEqual(onConnectFn.mock.callCount(), 1);\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/b1402835a512f14fa9f8dd23d3e0cee8cfe888a2/test/parallel/test-tls-junk-closes-server.js\nexport const testTlsJunkClosesServer = {\n  async test(ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    const c = net.createConnection(env.HELLO_SERVER_PORT);\n\n    c.on('data', function () {\n      // We must consume all data sent by the server. Otherwise the\n      // end event will not be sent and the test will hang.\n      // For example, when compiled with OpenSSL32 we see the\n      // following response '15 03 03 00 02 02 16' which\n      // decodes as a fatal (0x02) TLS error alert number 22 (0x16),\n      // which corresponds to TLS1_AD_RECORD_OVERFLOW which matches\n      // the error we see if NODE_DEBUG is turned on.\n      // Some earlier OpenSSL versions did not seem to send a response\n      // but the TLS spec seems to indicate there should be one\n      // https://datatracker.ietf.org/doc/html/rfc8446#page-85\n      // and error handling seems to have been re-written/improved\n      // in OpenSSL32. Consuming the data allows the test to pass\n      // either way.\n    });\n\n    const onConnectFn = mock.fn(() => {\n      c.write('blah\\nblah\\nblah\\n');\n    });\n    c.on('connect', onConnectFn);\n    c.on('end', resolve);\n    await promise;\n    strictEqual(onConnectFn.mock.callCount(), 1);\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/91d8a524ada001103a2d1c6825ca17b8393c183f/test/parallel/test-tls-on-empty-socket.js\nexport const testTlsOnEmptySocket = {\n  async test(ctrl, env) {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const socket = new net.Socket();\n    let out = '';\n\n    const s = tls.connect({ socket }, function () {\n      s.on('error', reject);\n      s.on('data', function (chunk) {\n        out += chunk;\n      });\n      s.on('end', resolve);\n    });\n\n    const onConnectFn = mock.fn();\n    socket.connect(env.HELLO_SERVER_PORT, onConnectFn);\n\n    await promise;\n    strictEqual(out, 'Hello');\n    strictEqual(onConnectFn.mock.callCount(), 1);\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/91d8a524ada001103a2d1c6825ca17b8393c183f/test/parallel/test-tls-pause.js\nexport const testTlsPause = {\n  async test(ctrl, env) {\n    const { promise, resolve } = Promise.withResolvers();\n    const bufSize = 1024 * 1024;\n    let sent = 0;\n    let received = 0;\n    let resumed = false;\n    const client = tls.connect(\n      {\n        port: env.ECHO_SERVER_PORT,\n      },\n      () => {\n        client.pause();\n        const send = (() => {\n          const ret = client.write(Buffer.allocUnsafe(bufSize));\n          if (ret !== false) {\n            sent += bufSize;\n            ok(sent < 100 * 1024 * 1024); // max 100MB\n            return process.nextTick(send);\n          }\n          sent += bufSize;\n          resumed = true;\n          client.resume();\n        })();\n      }\n    );\n    client.on('data', (data) => {\n      ok(resumed);\n      received += data.length;\n      if (received >= sent) {\n        client.end();\n        resolve();\n      }\n    });\n\n    await promise;\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/cb5f671a34da32e3c2d70d7f3e7f869cda6b806b/test/parallel/test-tls-socket-allow-half-open-option.js\nexport const testTlsSocketAllowHalfOpenOption = {\n  async test() {\n    {\n      // The option is ignored when the `socket` argument is a `net.Socket`.\n      const socket = new tls.TLSSocket(new net.Socket(), {\n        allowHalfOpen: true,\n      });\n      strictEqual(socket.allowHalfOpen, false);\n    }\n\n    {\n      // The option is ignored when the `socket` argument is a generic\n      // `stream.Duplex`.\n      const duplex = new stream.Duplex({\n        allowHalfOpen: false,\n        read() {},\n      });\n      const socket = new tls.TLSSocket(duplex, { allowHalfOpen: true });\n      strictEqual(socket.allowHalfOpen, false);\n    }\n\n    {\n      const socket = new tls.TLSSocket();\n      strictEqual(socket.allowHalfOpen, false);\n    }\n\n    {\n      // The option is honored when the `socket` argument is not specified.\n      const socket = new tls.TLSSocket(undefined, { allowHalfOpen: true });\n      strictEqual(socket.allowHalfOpen, true);\n    }\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/52d95f53e466016120048fb43b3732ff9089ecd7/test/parallel/test-tls-streamwrap-buffersize.js\nexport const testTlsStreamwrapBuffersize = {\n  async test(ctrl, env) {\n    // This test ensures that `bufferSize` also works for those tlsSockets\n    // created from `socket` of `Duplex`, with which, TLSSocket will wrap\n    // sockets in `StreamWrap`.\n    const iter = 10;\n\n    function createDuplex() {\n      const [clientSide, serverSide] = stream.duplexPair();\n      const dp = Promise.withResolvers();\n\n      const socket = net.connect(env.STREAM_WRAP_SERVER_PORT, () => {\n        clientSide.pipe(socket);\n        socket.pipe(clientSide);\n        clientSide.on('close', () => socket.destroy());\n        socket.on('close', () => clientSide.destroy());\n\n        dp.resolve(serverSide);\n      });\n\n      return dp.promise;\n    }\n\n    const socket = await createDuplex();\n    const { promise, resolve } = Promise.withResolvers();\n    const onCloseFn = mock.fn(() => {\n      // TODO(soon): This should be undefined, not 0.\n      strictEqual(client.bufferSize, 0);\n      resolve();\n    });\n    const client = tls.connect({ socket }, () => {\n      strictEqual(client.bufferSize, 0);\n\n      for (let i = 1; i < iter; i++) {\n        client.write('a');\n        strictEqual(client.bufferSize, i);\n      }\n\n      client.end();\n    });\n\n    client.on('close', onCloseFn);\n\n    await promise;\n    strictEqual(onCloseFn.mock.callCount(), 1);\n  },\n};\n\nexport const testEOLMethods = {\n  async test() {\n    strictEqual(typeof tls.createSecurePair, 'function');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/tls-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"tls-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"tls-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"streams_enable_constructors\", \"add_nodejs_compat_eol_v24\"],\n        bindings = [\n          (name = \"ECHO_SERVER_PORT\", fromEnvironment = \"ECHO_SERVER_PORT\"),\n          (name = \"HELLO_SERVER_PORT\", fromEnvironment = \"HELLO_SERVER_PORT\"),\n          (name = \"JS_STREAM_SERVER_PORT\", fromEnvironment = \"JS_STREAM_SERVER_PORT\"),\n          (name = \"STREAM_WRAP_SERVER_PORT\", fromEnvironment = \"STREAM_WRAP_SERVER_PORT\"),\n        ]\n      ),\n    ),\n    ( name = \"internet\",\n      network = (\n        allow = [\"private\"],\n        tlsOptions = (\n          trustedCertificates = [\n            embed \"fixtures/tls-nodejs-tcp-server.pem\",\n          ],\n        ),\n      ),\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/trace-events-nodejs-test.js",
    "content": "import * as trace_events from 'node:trace_events';\nimport {\n  strictEqual,\n  deepStrictEqual,\n  throws,\n  ok,\n  doesNotThrow,\n} from 'node:assert';\n\nexport const traceEventsTest = {\n  test() {\n    // Note: test was generated by claude with manual tweaks\n\n    // Test that required exports exist\n    strictEqual(typeof trace_events.createTracing, 'function');\n    strictEqual(typeof trace_events.getEnabledCategories, 'function');\n\n    doesNotThrow(() => {\n      trace_events.createTracing({ categories: ['node'] });\n    });\n\n    doesNotThrow(() => {\n      trace_events.createTracing({ categories: ['node', 'v8'] });\n    });\n\n    throws(\n      () => {\n        trace_events.createTracing({ categories: [] });\n      },\n      { code: 'ERR_TRACE_EVENTS_CATEGORY_REQUIRED' }\n    );\n\n    // Test createTracing options validation - options must be an object\n    for (const input of [\n      'invalid',\n      null,\n      123,\n      [],\n      { categories: 'invalid' },\n      { categories: {} },\n      { categories: 123 },\n      { categories: [123] },\n      { categories: ['valid', 123] },\n      { categories: ['valid', null] },\n      { categories: ['valid', {}] },\n    ]) {\n      throws(\n        () => {\n          trace_events.createTracing(input);\n        },\n        {\n          code: 'ERR_INVALID_ARG_TYPE',\n        }\n      );\n    }\n\n    // Test getEnabledCategories - should return undefined\n    const enabledCategories = trace_events.getEnabledCategories();\n    strictEqual(enabledCategories, undefined);\n\n    // Test getEnabledCategories with arguments (should ignore them)\n    const enabledCategories2 = trace_events.getEnabledCategories('ignored');\n    strictEqual(enabledCategories2, undefined);\n\n    const enabledCategories3 = trace_events.getEnabledCategories(123, {});\n    strictEqual(enabledCategories3, undefined);\n\n    // Test Tracing class through prototype (can't instantiate due to constructor throwing)\n    // We need to access the Tracing class through the internal module structure\n    // Since we can't instantiate it, we'll test what we can access\n\n    // Test that createTracing validates properly before throwing\n    // Test with additional unknown properties (should be accepted)\n    doesNotThrow(() => {\n      trace_events.createTracing({\n        categories: ['node'],\n        unknownProperty: 'should be ignored',\n      });\n    });\n\n    // Test edge cases with categories array\n    doesNotThrow(() => {\n      trace_events.createTracing({ categories: [''] }); // empty string should be valid\n    });\n\n    doesNotThrow(() => {\n      trace_events.createTracing({ categories: ['a'.repeat(1000)] }); // very long string\n    });\n\n    // Test that categories with special characters are accepted\n    doesNotThrow(() => {\n      trace_events.createTracing({\n        categories: ['node.fs', 'v8.gc', 'custom-category'],\n      });\n    });\n\n    // Test with undefined categories (should pass validation)\n    throws(\n      () => {\n        trace_events.createTracing({ categories: undefined });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE', // undefined categories should fail validation\n      }\n    );\n\n    // Test default export\n    strictEqual(typeof trace_events.default, 'object');\n    strictEqual(trace_events.default.createTracing, trace_events.createTracing);\n    strictEqual(\n      trace_events.default.getEnabledCategories,\n      trace_events.getEnabledCategories\n    );\n\n    // Test that getEnabledCategories is consistent across calls\n    const categories1 = trace_events.getEnabledCategories();\n    const categories2 = trace_events.getEnabledCategories();\n    strictEqual(categories1, undefined);\n    strictEqual(categories2, undefined);\n    strictEqual(categories1, categories2);\n\n    // Test various valid category names\n    const validCategoryNames = [\n      'node',\n      'v8',\n      'node.fs',\n      'node.http',\n      'v8.gc',\n      'custom',\n      'my-category',\n      'category_with_underscores',\n      'category.with.dots',\n      'category123',\n      '123category',\n      'a',\n      'very_long_category_name_that_should_still_be_valid',\n    ];\n\n    for (const category of validCategoryNames) {\n      doesNotThrow(() => {\n        trace_events.createTracing({ categories: [category] });\n      });\n    }\n\n    // Test with multiple valid categories\n    doesNotThrow(() => {\n      trace_events.createTracing({ categories: validCategoryNames });\n    });\n\n    // Test with duplicate categories (should be valid)\n    doesNotThrow(() => {\n      trace_events.createTracing({\n        categories: ['node', 'node', 'v8', 'v8'],\n      });\n    });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/trace-events-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"trace-events-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"trace-events-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_trace_events_module\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/tty-nodejs-test.js",
    "content": "import * as tty from 'node:tty';\nimport { strictEqual, deepStrictEqual, ok } from 'node:assert';\n\nexport const ttyTest = {\n  test() {\n    // Note: test was generated by claude with manual tweaks\n\n    // Test that required exports exist\n    strictEqual(typeof tty.isatty, 'function');\n    strictEqual(typeof tty.ReadStream, 'function');\n    strictEqual(typeof tty.WriteStream, 'function');\n\n    // Test isatty function - should always return false\n    strictEqual(tty.isatty(0), false); // stdin\n    strictEqual(tty.isatty(1), false); // stdout\n    strictEqual(tty.isatty(2), false); // stderr\n    strictEqual(tty.isatty(3), false); // arbitrary fd\n    strictEqual(tty.isatty(-1), false); // invalid fd\n    strictEqual(tty.isatty(999), false); // high fd number\n\n    // Test ReadStream constructor - should succeed and store fd\n    const readStream = new tty.ReadStream(0);\n    strictEqual(readStream.fd, 0);\n    strictEqual(readStream.isRaw, false);\n    strictEqual(readStream.isTTY, false);\n\n    const readStream2 = new tty.ReadStream(1, {});\n    strictEqual(readStream2.fd, 1);\n\n    const readStream3 = new tty.ReadStream(2, { readable: true });\n    strictEqual(readStream3.fd, 2);\n\n    // Test WriteStream constructor - should succeed and store fd\n    const writeStream = new tty.WriteStream(1);\n    strictEqual(writeStream.fd, 1);\n    strictEqual(writeStream.columns, 0);\n    strictEqual(writeStream.rows, 0);\n    strictEqual(writeStream.isTTY, true);\n\n    const writeStream2 = new tty.WriteStream(2, {});\n    strictEqual(writeStream2.fd, 2);\n\n    const writeStream3 = new tty.WriteStream(1, { writable: true });\n    strictEqual(writeStream3.fd, 1);\n\n    // Test ReadStream prototype methods and properties\n    strictEqual(typeof tty.ReadStream.prototype.setRawMode, 'function');\n\n    // Test WriteStream prototype properties and methods\n    strictEqual(tty.WriteStream.prototype.isTTY, true);\n    strictEqual(typeof tty.WriteStream.prototype.getColorDepth, 'function');\n    strictEqual(typeof tty.WriteStream.prototype.hasColors, 'function');\n    strictEqual(typeof tty.WriteStream.prototype._refreshSize, 'function');\n    strictEqual(typeof tty.WriteStream.prototype.cursorTo, 'function');\n    strictEqual(typeof tty.WriteStream.prototype.moveCursor, 'function');\n    strictEqual(typeof tty.WriteStream.prototype.clearLine, 'function');\n    strictEqual(typeof tty.WriteStream.prototype.clearScreenDown, 'function');\n    strictEqual(typeof tty.WriteStream.prototype.getWindowSize, 'function');\n\n    // Test WriteStream instance methods behavior\n    // Test getColorDepth - should return 8\n    const colorDepth = writeStream.getColorDepth();\n    strictEqual(colorDepth, 8);\n\n    // Test hasColors - should return false\n    const hasColors = writeStream.hasColors();\n    strictEqual(hasColors, false);\n\n    // Test _refreshSize - should be a no-op (not throw)\n    writeStream._refreshSize();\n\n    // Test getWindowSize - should return [columns, rows]\n    const windowSize = writeStream.getWindowSize();\n    deepStrictEqual(windowSize, [0, 0]);\n\n    // Test cursorTo - should be a no-op and return false\n    strictEqual(writeStream.cursorTo(10, 5), false);\n    let callbackCalled = false;\n    strictEqual(\n      writeStream.cursorTo(0, 0, () => {\n        callbackCalled = true;\n      }),\n      false\n    );\n    strictEqual(callbackCalled, true);\n\n    // Test cursorTo with callback as second argument\n    callbackCalled = false;\n    strictEqual(\n      writeStream.cursorTo(0, () => {\n        callbackCalled = true;\n      }),\n      false\n    );\n    strictEqual(callbackCalled, true);\n\n    // Test moveCursor - should be a no-op and return false\n    strictEqual(writeStream.moveCursor(1, 1), false);\n    callbackCalled = false;\n    strictEqual(\n      writeStream.moveCursor(-1, 0, () => {\n        callbackCalled = true;\n      }),\n      false\n    );\n    strictEqual(callbackCalled, true);\n\n    // Test clearLine - should be a no-op and return false\n    strictEqual(writeStream.clearLine(0), false);\n    callbackCalled = false;\n    strictEqual(\n      writeStream.clearLine(1, () => {\n        callbackCalled = true;\n      }),\n      false\n    );\n    strictEqual(callbackCalled, true);\n\n    // Test clearScreenDown - should be a no-op and return false\n    strictEqual(writeStream.clearScreenDown(), false);\n    callbackCalled = false;\n    strictEqual(\n      writeStream.clearScreenDown(() => {\n        callbackCalled = true;\n      }),\n      false\n    );\n    strictEqual(callbackCalled, true);\n\n    // Test ReadStream setRawMode - should set isRaw and return this\n    const setRawModeResult = readStream.setRawMode(true);\n    strictEqual(setRawModeResult, readStream);\n    strictEqual(readStream.isRaw, true);\n\n    const setRawModeFalseResult = readStream.setRawMode(false);\n    strictEqual(setRawModeFalseResult, readStream);\n    strictEqual(readStream.isRaw, false);\n\n    // Test edge cases for isatty\n    strictEqual(tty.isatty(Number.MAX_SAFE_INTEGER), false);\n    strictEqual(tty.isatty(Number.MIN_SAFE_INTEGER), false);\n    strictEqual(tty.isatty(0.5), false); // Non-integer\n    strictEqual(tty.isatty(NaN), false);\n    strictEqual(tty.isatty(Infinity), false);\n    strictEqual(tty.isatty(-Infinity), false);\n\n    // Test that constructors are functions\n    strictEqual(typeof tty.ReadStream, 'function');\n    strictEqual(typeof tty.WriteStream, 'function');\n\n    // Test default export\n    strictEqual(typeof tty.default, 'object');\n    strictEqual(tty.default.isatty, tty.isatty);\n    strictEqual(tty.default.ReadStream, tty.ReadStream);\n    strictEqual(tty.default.WriteStream, tty.WriteStream);\n\n    // Test hasColors with different arguments (should always return false)\n    strictEqual(writeStream.hasColors(), false);\n    strictEqual(writeStream.hasColors(8), false);\n    strictEqual(writeStream.hasColors(256), false);\n\n    // Test getColorDepth with different arguments (should always return 8)\n    strictEqual(writeStream.getColorDepth(), 8);\n    strictEqual(writeStream.getColorDepth({}), 8);\n\n    // Test setRawMode with different argument types\n    strictEqual(readStream.setRawMode(true), readStream);\n    strictEqual(readStream.setRawMode(false), readStream);\n    strictEqual(readStream.setRawMode(1), readStream);\n    strictEqual(readStream.setRawMode(0), readStream);\n    strictEqual(readStream.setRawMode('true'), readStream);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/tty-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"tty-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"tty-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_tty_module\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/url-nodejs-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport {\n  strictEqual,\n  throws,\n  ok as assert,\n  match,\n  deepStrictEqual,\n} from 'node:assert';\nimport {\n  domainToASCII,\n  domainToUnicode,\n  URL as URLImpl,\n  URLSearchParams as URLSearchParamsImpl,\n  fileURLToPath,\n  pathToFileURL,\n} from 'node:url';\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/321a14b36d6b3304aedfd183e12ddba35dc704bd/test/parallel/test-url-domain-ascii-unicode.js\nexport const testDomainAsciiUnicode = {\n  test() {\n    const domainWithASCII = [\n      ['ıíd', 'xn--d-iga7r'],\n      ['يٴ', 'xn--mhb8f'],\n      ['www.ϧƽəʐ.com', 'www.xn--cja62apfr6c.com'],\n      ['новини.com', 'xn--b1amarcd.com'],\n      ['名がドメイン.com', 'xn--v8jxj3d1dzdz08w.com'],\n      ['افغانستا.icom.museum', 'xn--mgbaal8b0b9b2b.icom.museum'],\n      ['الجزائر.icom.fake', 'xn--lgbbat1ad8j.icom.fake'],\n      ['भारत.org', 'xn--h2brj9c.org'],\n      ['aaa', 'aaa'],\n    ];\n\n    for (const [domain, ascii] of domainWithASCII) {\n      strictEqual(domainToASCII(domain), ascii);\n      strictEqual(domainToUnicode(ascii), domain);\n    }\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/321a14b36d6b3304aedfd183e12ddba35dc704bd/test/parallel/test-whatwg-url-custom-domainto.js\nexport const whatwgURLCustomDomainTo = {\n  test() {\n    {\n      const expectedError = { code: 'ERR_MISSING_ARGS', name: 'TypeError' };\n      throws(() => domainToASCII(), expectedError);\n      throws(() => domainToUnicode(), expectedError);\n      strictEqual(domainToASCII(undefined), 'undefined');\n      strictEqual(domainToUnicode(undefined), 'undefined');\n    }\n  },\n};\n\nexport const urlAndSearchParams = {\n  test() {\n    assert(URLImpl, 'URL should be exported from node:url');\n    assert(\n      URLSearchParamsImpl,\n      'URLSearchParams should be exported by node:url'\n    );\n  },\n};\n\nexport const getBuiltinModule = {\n  async test() {\n    const bim = process.getBuiltinModule('node:url');\n    const url = await import('node:url');\n    strictEqual(bim, url.default);\n  },\n};\n\n// Ref: https://github.com/nodejs/node/blob/e92446536ed4e268c9eef6ae6f911e384c98eecf/test/parallel/test-url-fileurltopath.js\nexport const testFileURLToPath = {\n  async test() {\n    // invalid arguments\n    for (const arg of [null, undefined, 1, {}, true]) {\n      throws(() => fileURLToPath(arg), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n    }\n\n    // input must be a file URL\n    throws(() => fileURLToPath('https://a/b/c'), {\n      code: 'ERR_INVALID_URL_SCHEME',\n    });\n\n    {\n      // fileURLToPath with host\n      const withHost = new URL('file://host/a');\n\n      throws(() => fileURLToPath(withHost), {\n        code: 'ERR_INVALID_FILE_URL_HOST',\n      });\n    }\n\n    const windowsTestCases = [\n      // Lowercase ascii alpha\n      { path: 'C:\\\\foo', fileURL: 'file:///C:/foo' },\n      // Uppercase ascii alpha\n      { path: 'C:\\\\FOO', fileURL: 'file:///C:/FOO' },\n      // dir\n      { path: 'C:\\\\dir\\\\foo', fileURL: 'file:///C:/dir/foo' },\n      // trailing separator\n      { path: 'C:\\\\dir\\\\', fileURL: 'file:///C:/dir/' },\n      // dot\n      { path: 'C:\\\\foo.mjs', fileURL: 'file:///C:/foo.mjs' },\n      // space\n      { path: 'C:\\\\foo bar', fileURL: 'file:///C:/foo%20bar' },\n      // question mark\n      { path: 'C:\\\\foo?bar', fileURL: 'file:///C:/foo%3Fbar' },\n      // number sign\n      { path: 'C:\\\\foo#bar', fileURL: 'file:///C:/foo%23bar' },\n      // ampersand\n      { path: 'C:\\\\foo&bar', fileURL: 'file:///C:/foo&bar' },\n      // equals\n      { path: 'C:\\\\foo=bar', fileURL: 'file:///C:/foo=bar' },\n      // colon\n      { path: 'C:\\\\foo:bar', fileURL: 'file:///C:/foo:bar' },\n      // semicolon\n      { path: 'C:\\\\foo;bar', fileURL: 'file:///C:/foo;bar' },\n      // percent\n      { path: 'C:\\\\foo%bar', fileURL: 'file:///C:/foo%25bar' },\n      // backslash\n      { path: 'C:\\\\foo\\\\bar', fileURL: 'file:///C:/foo/bar' },\n      // backspace\n      { path: 'C:\\\\foo\\bbar', fileURL: 'file:///C:/foo%08bar' },\n      // tab\n      { path: 'C:\\\\foo\\tbar', fileURL: 'file:///C:/foo%09bar' },\n      // newline\n      { path: 'C:\\\\foo\\nbar', fileURL: 'file:///C:/foo%0Abar' },\n      // carriage return\n      { path: 'C:\\\\foo\\rbar', fileURL: 'file:///C:/foo%0Dbar' },\n      // latin1\n      { path: 'C:\\\\fóóbàr', fileURL: 'file:///C:/f%C3%B3%C3%B3b%C3%A0r' },\n      // Euro sign (BMP code point)\n      { path: 'C:\\\\€', fileURL: 'file:///C:/%E2%82%AC' },\n      // Rocket emoji (non-BMP code point)\n      { path: 'C:\\\\🚀', fileURL: 'file:///C:/%F0%9F%9A%80' },\n      // UNC path (see https://docs.microsoft.com/en-us/archive/blogs/ie/file-uris-in-windows)\n      {\n        path: '\\\\\\\\nas\\\\My Docs\\\\File.doc',\n        fileURL: 'file://nas/My%20Docs/File.doc',\n      },\n    ];\n\n    const posixTestCases = [\n      // Lowercase ascii alpha\n      { path: '/foo', fileURL: 'file:///foo' },\n      // Uppercase ascii alpha\n      { path: '/FOO', fileURL: 'file:///FOO' },\n      // dir\n      { path: '/dir/foo', fileURL: 'file:///dir/foo' },\n      // trailing separator\n      { path: '/dir/', fileURL: 'file:///dir/' },\n      // dot\n      { path: '/foo.mjs', fileURL: 'file:///foo.mjs' },\n      // space\n      { path: '/foo bar', fileURL: 'file:///foo%20bar' },\n      // question mark\n      { path: '/foo?bar', fileURL: 'file:///foo%3Fbar' },\n      // number sign\n      { path: '/foo#bar', fileURL: 'file:///foo%23bar' },\n      // ampersand\n      { path: '/foo&bar', fileURL: 'file:///foo&bar' },\n      // equals\n      { path: '/foo=bar', fileURL: 'file:///foo=bar' },\n      // colon\n      { path: '/foo:bar', fileURL: 'file:///foo:bar' },\n      // semicolon\n      { path: '/foo;bar', fileURL: 'file:///foo;bar' },\n      // percent\n      { path: '/foo%bar', fileURL: 'file:///foo%25bar' },\n      // backslash\n      { path: '/foo\\\\bar', fileURL: 'file:///foo%5Cbar' },\n      // backspace\n      { path: '/foo\\bbar', fileURL: 'file:///foo%08bar' },\n      // tab\n      { path: '/foo\\tbar', fileURL: 'file:///foo%09bar' },\n      // newline\n      { path: '/foo\\nbar', fileURL: 'file:///foo%0Abar' },\n      // carriage return\n      { path: '/foo\\rbar', fileURL: 'file:///foo%0Dbar' },\n      // latin1\n      { path: '/fóóbàr', fileURL: 'file:///f%C3%B3%C3%B3b%C3%A0r' },\n      // Euro sign (BMP code point)\n      { path: '/€', fileURL: 'file:///%E2%82%AC' },\n      // Rocket emoji (non-BMP code point)\n      { path: '/🚀', fileURL: 'file:///%F0%9F%9A%80' },\n    ];\n\n    // fileURLToPath with windows path\n    for (const { path, fileURL } of windowsTestCases) {\n      const fromString = fileURLToPath(fileURL, { windows: true });\n      strictEqual(fromString, path);\n      const fromURL = fileURLToPath(new URL(fileURL), { windows: true });\n      strictEqual(fromURL, path);\n    }\n\n    // fileURLToPath with posix path\n    for (const { path, fileURL } of posixTestCases) {\n      const fromString = fileURLToPath(fileURL, { windows: false });\n      strictEqual(fromString, path);\n      const fromURL = fileURLToPath(new URL(fileURL), { windows: false });\n      strictEqual(fromURL, path);\n    }\n\n    {\n      // options is null\n      const whenNullActual = fileURLToPath(\n        new URL(posixTestCases[0].fileURL),\n        null\n      );\n      strictEqual(whenNullActual, posixTestCases[0].path);\n\n      // default test cases\n      for (const { path, fileURL } of posixTestCases) {\n        const fromString = fileURLToPath(fileURL);\n        strictEqual(fromString, path);\n        const fromURL = fileURLToPath(new URL(fileURL));\n        strictEqual(fromURL, path);\n      }\n    }\n  },\n};\n\n// Ref: https://github.com/nodejs/node/blob/e92446536ed4e268c9eef6ae6f911e384c98eecf/test/parallel/test-url-pathtofileurl.js\nexport const testPathToFileURL = {\n  async test() {\n    {\n      const fileURL = pathToFileURL('test/').href;\n      assert(fileURL.startsWith('file:///'));\n      assert(fileURL.endsWith('/'));\n    }\n\n    {\n      const fileURL = pathToFileURL('test\\\\', { windows: false }).href;\n      assert(fileURL.startsWith('file:///'));\n      match(fileURL, /%5C$/);\n    }\n\n    {\n      const fileURL = pathToFileURL('test\\\\', { windows: true }).href;\n      assert(fileURL.startsWith('file:///'));\n      match(fileURL, /\\/$/);\n    }\n\n    {\n      const fileURL = pathToFileURL('test/%').href;\n      assert(fileURL.includes('%25'));\n    }\n\n    {\n      // UNC path: \\\\server\\share\\resource\n      // Missing server:\n      throws(() => pathToFileURL('\\\\\\\\\\\\no-server', { windows: true }), {\n        code: 'ERR_INVALID_ARG_VALUE',\n      });\n\n      // Missing share or resource:\n      throws(() => pathToFileURL('\\\\\\\\host', { windows: true }), {\n        code: 'ERR_INVALID_ARG_VALUE',\n      });\n\n      // Regression test for direct String.prototype.startsWith call\n      throws(\n        () =>\n          pathToFileURL(\n            ['\\\\\\\\', { [Symbol.toPrimitive]: () => 'blep\\\\blop' }],\n            { windows: true }\n          ),\n        {\n          code: 'ERR_INVALID_ARG_TYPE',\n        }\n      );\n      throws(() => pathToFileURL(['\\\\\\\\', 'blep\\\\blop'], { windows: true }), {\n        code: 'ERR_INVALID_ARG_TYPE',\n      });\n      throws(\n        () =>\n          pathToFileURL(\n            {\n              [Symbol.toPrimitive]: () => '\\\\\\\\blep\\\\blop',\n            },\n            { windows: true }\n          ),\n        {\n          code: 'ERR_INVALID_ARG_TYPE',\n        }\n      );\n\n      // UNC paths on Posix are considered a single path that has backslashes:\n      const fileURL = pathToFileURL('\\\\\\\\nas\\\\share\\\\path.txt').href;\n      match(fileURL, /file:\\/\\/.+%5C%5Cnas%5Cshare%5Cpath\\.txt$/);\n    }\n\n    const windowsTestCases = [\n      // Lowercase ascii alpha\n      { path: 'C:\\\\foo', expected: 'file:///C:/foo' },\n      // Uppercase ascii alpha\n      { path: 'C:\\\\FOO', expected: 'file:///C:/FOO' },\n      // dir\n      { path: 'C:\\\\dir\\\\foo', expected: 'file:///C:/dir/foo' },\n      // trailing separator\n      { path: 'C:\\\\dir\\\\', expected: 'file:///C:/dir/' },\n      // dot\n      { path: 'C:\\\\foo.mjs', expected: 'file:///C:/foo.mjs' },\n      // space\n      { path: 'C:\\\\foo bar', expected: 'file:///C:/foo%20bar' },\n      // question mark\n      { path: 'C:\\\\foo?bar', expected: 'file:///C:/foo%3Fbar' },\n      // number sign\n      { path: 'C:\\\\foo#bar', expected: 'file:///C:/foo%23bar' },\n      // ampersand\n      { path: 'C:\\\\foo&bar', expected: 'file:///C:/foo&bar' },\n      // equals\n      { path: 'C:\\\\foo=bar', expected: 'file:///C:/foo=bar' },\n      // colon\n      { path: 'C:\\\\foo:bar', expected: 'file:///C:/foo:bar' },\n      // semicolon\n      { path: 'C:\\\\foo;bar', expected: 'file:///C:/foo;bar' },\n      // percent\n      { path: 'C:\\\\foo%bar', expected: 'file:///C:/foo%25bar' },\n      // backslash\n      { path: 'C:\\\\foo\\\\bar', expected: 'file:///C:/foo/bar' },\n      // backspace\n      { path: 'C:\\\\foo\\bbar', expected: 'file:///C:/foo%08bar' },\n      // tab\n      { path: 'C:\\\\foo\\tbar', expected: 'file:///C:/foo%09bar' },\n      // newline\n      { path: 'C:\\\\foo\\nbar', expected: 'file:///C:/foo%0Abar' },\n      // carriage return\n      { path: 'C:\\\\foo\\rbar', expected: 'file:///C:/foo%0Dbar' },\n      // latin1\n      { path: 'C:\\\\fóóbàr', expected: 'file:///C:/f%C3%B3%C3%B3b%C3%A0r' },\n      // Euro sign (BMP code point)\n      { path: 'C:\\\\€', expected: 'file:///C:/%E2%82%AC' },\n      // Rocket emoji (non-BMP code point)\n      { path: 'C:\\\\🚀', expected: 'file:///C:/%F0%9F%9A%80' },\n      // caret\n      { path: 'C:\\\\foo^bar', expected: 'file:///C:/foo%5Ebar' },\n      // left bracket\n      { path: 'C:\\\\foo[bar', expected: 'file:///C:/foo%5Bbar' },\n      // right bracket\n      { path: 'C:\\\\foo]bar', expected: 'file:///C:/foo%5Dbar' },\n      // Local extended path\n      {\n        path: '\\\\\\\\?\\\\C:\\\\path\\\\to\\\\file.txt',\n        expected: 'file:///C:/path/to/file.txt',\n      },\n      // UNC path (see https://docs.microsoft.com/en-us/archive/blogs/ie/file-uris-in-windows)\n      {\n        path: '\\\\\\\\nas\\\\My Docs\\\\File.doc',\n        expected: 'file://nas/My%20Docs/File.doc',\n      },\n      // Extended UNC path\n      {\n        path: '\\\\\\\\?\\\\UNC\\\\server\\\\share\\\\folder\\\\file.txt',\n        expected: 'file://server/share/folder/file.txt',\n      },\n    ];\n    const posixTestCases = [\n      // Lowercase ascii alpha\n      { path: '/foo', expected: 'file:///foo' },\n      // Uppercase ascii alpha\n      { path: '/FOO', expected: 'file:///FOO' },\n      // dir\n      { path: '/dir/foo', expected: 'file:///dir/foo' },\n      // trailing separator\n      { path: '/dir/', expected: 'file:///dir/' },\n      // dot\n      { path: '/foo.mjs', expected: 'file:///foo.mjs' },\n      // space\n      { path: '/foo bar', expected: 'file:///foo%20bar' },\n      // question mark\n      { path: '/foo?bar', expected: 'file:///foo%3Fbar' },\n      // number sign\n      { path: '/foo#bar', expected: 'file:///foo%23bar' },\n      // ampersand\n      { path: '/foo&bar', expected: 'file:///foo&bar' },\n      // equals\n      { path: '/foo=bar', expected: 'file:///foo=bar' },\n      // colon\n      { path: '/foo:bar', expected: 'file:///foo:bar' },\n      // semicolon\n      { path: '/foo;bar', expected: 'file:///foo;bar' },\n      // percent\n      { path: '/foo%bar', expected: 'file:///foo%25bar' },\n      // backslash\n      { path: '/foo\\\\bar', expected: 'file:///foo%5Cbar' },\n      // backspace\n      { path: '/foo\\bbar', expected: 'file:///foo%08bar' },\n      // tab\n      { path: '/foo\\tbar', expected: 'file:///foo%09bar' },\n      // newline\n      { path: '/foo\\nbar', expected: 'file:///foo%0Abar' },\n      // carriage return\n      { path: '/foo\\rbar', expected: 'file:///foo%0Dbar' },\n      // latin1\n      { path: '/fóóbàr', expected: 'file:///f%C3%B3%C3%B3b%C3%A0r' },\n      // Euro sign (BMP code point)\n      { path: '/€', expected: 'file:///%E2%82%AC' },\n      // Rocket emoji (non-BMP code point)\n      { path: '/🚀', expected: 'file:///%F0%9F%9A%80' },\n      // \"unsafe\" chars\n      {\n        path: '/foo\\r\\n\\t<>\"#%{}|^[\\\\~]`?bar',\n        expected:\n          'file:///foo%0D%0A%09%3C%3E%22%23%25%7B%7D%7C%5E%5B%5C%7E%5D%60%3Fbar',\n      },\n    ];\n\n    for (const { path, expected } of windowsTestCases) {\n      const actual = pathToFileURL(path, { windows: true }).href;\n      strictEqual(actual, expected);\n    }\n\n    for (const { path, expected } of posixTestCases) {\n      const actual = pathToFileURL(path, { windows: false }).href;\n      strictEqual(actual, expected);\n    }\n\n    for (const { path, expected } of windowsTestCases) {\n      const actual = pathToFileURL(path, { windows: true }).href;\n      strictEqual(actual, expected);\n    }\n\n    for (const { path, expected } of posixTestCases) {\n      const actual = pathToFileURL(path, { windows: false }).href;\n      strictEqual(actual, expected);\n    }\n\n    // Test for non-string parameter\n    {\n      for (const badPath of [\n        undefined,\n        null,\n        true,\n        42,\n        42n,\n        Symbol('42'),\n        NaN,\n        {},\n        [],\n        () => {},\n        Promise.resolve('foo'),\n        new Date(),\n        new String('notPrimitive'),\n        {\n          toString() {\n            return 'amObject';\n          },\n        },\n        { [Symbol.toPrimitive]: (hint) => 'amObject' },\n      ]) {\n        throws(() => pathToFileURL(badPath), {\n          code: 'ERR_INVALID_ARG_TYPE',\n        });\n      }\n    }\n  },\n};\n\n// Regression test for: https://github.com/cloudflare/workerd/issues/5144\nexport const testUrlPattern = {\n  async test(ctrl, env) {\n    const url = 'https://example.com/??';\n    const pattern = new URLPattern({ hostname: '*' });\n    const obj = new URL(url);\n    deepStrictEqual(pattern.exec(obj), {\n      inputs: [\n        {\n          protocol: 'https:',\n          username: '',\n          password: '',\n          hostname: 'example.com',\n          port: '',\n          pathname: '/',\n          search: '??',\n          hash: '',\n        },\n      ],\n      protocol: { input: 'https', groups: { 0: 'https' } },\n      username: { input: '', groups: { 0: '' } },\n      password: { input: '', groups: { 0: '' } },\n      hostname: { input: 'example.com', groups: { 0: 'example.com' } },\n      port: { input: '', groups: { 0: '' } },\n      pathname: { input: '/', groups: { 0: '/' } },\n      search: { input: '', groups: { 0: '' } },\n      hash: { input: '', groups: { 0: '' } },\n    });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/url-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"nodejs-url-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"url-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"url_standard\", \"urlpattern_standard\", \"disable_fast_jsg_struct\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/util-nodejs-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport assert from 'node:assert';\nimport { mock } from 'node:test';\nimport util, {\n  inspect,\n  callbackify,\n  inherits,\n  promisify,\n  stripVTControlCharacters,\n  styleText,\n  parseEnv,\n} from 'node:util';\n\nconst remainingMustCallErrors = new Set();\nfunction commonMustCall(f) {\n  const error = new Error('Expected function to be called');\n  remainingMustCallErrors.add(error);\n  return function (...args) {\n    remainingMustCallErrors.delete(error);\n    return f.call(this, ...args);\n  };\n}\nfunction assertCalledMustCalls() {\n  try {\n    for (const error of remainingMustCallErrors) throw error;\n  } finally {\n    remainingMustCallErrors.clear();\n  }\n}\n\nfunction invalidArgTypeHelper(input) {\n  if (input == null) {\n    return ` Received ${input}`;\n  }\n  if (typeof input === 'function') {\n    return ` Received function ${input.name}`;\n  }\n  if (typeof input === 'object') {\n    if (input.constructor?.name) {\n      return ` Received an instance of ${input.constructor.name}`;\n    }\n    return ` Received ${inspect(input, { depth: -1 })}`;\n  }\n\n  let inspected = inspect(input, { colors: false });\n  if (inspected.length > 28) {\n    inspected = `${inspected.slice(inspected, 0, 25)}...`;\n  }\n\n  return ` Received type ${typeof input} (${inspected})`;\n}\n\nexport const utilInspect = {\n  // test-util-inspect.js\n  test(ctrl, env, ctx) {\n    assert.strictEqual(util.inspect(1), '1');\n    assert.strictEqual(util.inspect(false), 'false');\n    assert.strictEqual(util.inspect(''), \"''\");\n    assert.strictEqual(util.inspect('hello'), \"'hello'\");\n    assert.strictEqual(\n      util.inspect(function abc() {}),\n      '[Function: abc]'\n    );\n    assert.strictEqual(\n      util.inspect(() => {}),\n      '[Function (anonymous)]'\n    );\n    assert.strictEqual(\n      util.inspect(async function () {}),\n      '[AsyncFunction (anonymous)]'\n    );\n    assert.strictEqual(\n      util.inspect(async () => {}),\n      '[AsyncFunction (anonymous)]'\n    );\n\n    // Special function inspection.\n    {\n      const fn = (() => function* () {})();\n      assert.strictEqual(util.inspect(fn), '[GeneratorFunction (anonymous)]');\n      assert.strictEqual(\n        util.inspect(async function* abc() {}),\n        '[AsyncGeneratorFunction: abc]'\n      );\n      Object.setPrototypeOf(\n        fn,\n        Object.getPrototypeOf(async () => {})\n      );\n      assert.strictEqual(\n        util.inspect(fn),\n        '[GeneratorFunction (anonymous)] AsyncFunction'\n      );\n      Object.defineProperty(fn, 'name', { value: 5, configurable: true });\n      assert.strictEqual(\n        util.inspect(fn),\n        '[GeneratorFunction: 5] AsyncFunction'\n      );\n      Object.defineProperty(fn, Symbol.toStringTag, {\n        value: 'Foobar',\n        configurable: true,\n      });\n      assert.strictEqual(\n        util.inspect({ ['5']: fn }),\n        \"{ '5': [GeneratorFunction: 5] AsyncFunction [Foobar] }\"\n      );\n      Object.defineProperty(fn, 'name', { value: '5', configurable: true });\n      Object.setPrototypeOf(fn, null);\n      assert.strictEqual(\n        util.inspect(fn),\n        '[GeneratorFunction (null prototype): 5] [Foobar]'\n      );\n      assert.strictEqual(\n        util.inspect({ ['5']: fn }),\n        \"{ '5': [GeneratorFunction (null prototype): 5] [Foobar] }\"\n      );\n    }\n\n    assert.strictEqual(util.inspect(undefined), 'undefined');\n    assert.strictEqual(util.inspect(null), 'null');\n    assert.strictEqual(util.inspect(/foo(bar\\n)?/gi), '/foo(bar\\\\n)?/gi');\n    assert.strictEqual(\n      util.inspect(new Date('Sun, 14 Feb 2010 11:48:40 GMT')),\n      new Date('2010-02-14T12:48:40+01:00').toISOString()\n    );\n    assert.strictEqual(util.inspect(new Date('')), new Date('').toString());\n    assert.strictEqual(util.inspect('\\n\\x01'), \"'\\\\n\\\\x01'\");\n    assert.strictEqual(\n      util.inspect(`${Array(75).fill(1)}'\\n\\x1d\\n\\x03\\x85\\x7f\\x7e\\x9f\\xa0`),\n      // eslint-disable-next-line no-irregular-whitespace\n      `\"${Array(75).fill(1)}'\\\\n\" +\\n  '\\\\x1D\\\\n' +\\n  '\\\\x03\\\\x85\\\\x7F~\\\\x9F '`\n    );\n    assert.strictEqual(util.inspect([]), '[]');\n    assert.strictEqual(util.inspect({ __proto__: [] }), 'Array {}');\n    assert.strictEqual(util.inspect([1, 2]), '[ 1, 2 ]');\n    assert.strictEqual(util.inspect([1, [2, 3]]), '[ 1, [ 2, 3 ] ]');\n    assert.strictEqual(util.inspect({}), '{}');\n    assert.strictEqual(util.inspect({ a: 1 }), '{ a: 1 }');\n    assert.strictEqual(\n      util.inspect({ a: function () {} }),\n      '{ a: [Function: a] }'\n    );\n    assert.strictEqual(util.inspect({ a: () => {} }), '{ a: [Function: a] }');\n    // eslint-disable-next-line func-name-matching\n    assert.strictEqual(\n      util.inspect({ a: async function abc() {} }),\n      '{ a: [AsyncFunction: abc] }'\n    );\n    assert.strictEqual(\n      util.inspect({ a: async () => {} }),\n      '{ a: [AsyncFunction: a] }'\n    );\n    assert.strictEqual(\n      util.inspect({ a: function* () {} }),\n      '{ a: [GeneratorFunction: a] }'\n    );\n    assert.strictEqual(util.inspect({ a: 1, b: 2 }), '{ a: 1, b: 2 }');\n    assert.strictEqual(util.inspect({ a: {} }), '{ a: {} }');\n    assert.strictEqual(util.inspect({ a: { b: 2 } }), '{ a: { b: 2 } }');\n    assert.strictEqual(\n      util.inspect({ a: { b: { c: { d: 2 } } } }),\n      '{ a: { b: { c: [Object] } } }'\n    );\n    assert.strictEqual(\n      util.inspect({ a: { b: { c: { d: 2 } } } }, false, null),\n      '{\\n  a: { b: { c: { d: 2 } } }\\n}'\n    );\n    assert.strictEqual(\n      util.inspect([1, 2, 3], true),\n      '[ 1, 2, 3, [length]: 3 ]'\n    );\n    assert.strictEqual(\n      util.inspect({ a: { b: { c: 2 } } }, false, 0),\n      '{ a: [Object] }'\n    );\n    assert.strictEqual(\n      util.inspect({ a: { b: { c: 2 } } }, false, 1),\n      '{ a: { b: [Object] } }'\n    );\n    assert.strictEqual(\n      util.inspect({ a: { b: ['c'] } }, false, 1),\n      '{ a: { b: [Array] } }'\n    );\n    assert.strictEqual(util.inspect(new Uint8Array(0)), 'Uint8Array(0) []');\n    assert(\n      inspect(new Uint8Array(0), { showHidden: true }).includes('[buffer]')\n    );\n    assert.strictEqual(\n      util.inspect(\n        Object.create(\n          {},\n          { visible: { value: 1, enumerable: true }, hidden: { value: 2 } }\n        )\n      ),\n      '{ visible: 1 }'\n    );\n    assert.strictEqual(\n      util.inspect(\n        Object.assign(new String('hello'), { [Symbol('foo')]: 123 }),\n        { showHidden: true }\n      ),\n      \"[String: 'hello'] { [length]: 5, Symbol(foo): 123 }\"\n    );\n\n    {\n      const regexp = /regexp/;\n      regexp.aprop = 42;\n      assert.strictEqual(\n        util.inspect({ a: regexp }, false, 0),\n        '{ a: /regexp/ }'\n      );\n    }\n\n    assert.match(\n      util.inspect({ a: { a: { a: { a: {} } } } }, undefined, undefined, true),\n      /Object/\n    );\n    assert.doesNotMatch(\n      util.inspect({ a: { a: { a: { a: {} } } } }, undefined, null, true),\n      /Object/\n    );\n\n    {\n      const showHidden = true;\n      const ab = new Uint8Array([1, 2, 3, 4]).buffer;\n      const dv = new DataView(ab, 1, 2);\n      assert.strictEqual(\n        util.inspect(ab, showHidden),\n        'ArrayBuffer { [Uint8Contents]: <01 02 03 04>, byteLength: 4 }'\n      );\n      assert.strictEqual(\n        util.inspect(new DataView(ab, 1, 2), showHidden),\n        'DataView {\\n' +\n          '  byteLength: 2,\\n' +\n          '  byteOffset: 1,\\n' +\n          '  buffer: ArrayBuffer {' +\n          ' [Uint8Contents]: <01 02 03 04>, byteLength: 4 }\\n}'\n      );\n      assert.strictEqual(\n        util.inspect(ab, showHidden),\n        'ArrayBuffer { [Uint8Contents]: <01 02 03 04>, byteLength: 4 }'\n      );\n      assert.strictEqual(\n        util.inspect(dv, showHidden),\n        'DataView {\\n' +\n          '  byteLength: 2,\\n' +\n          '  byteOffset: 1,\\n' +\n          '  buffer: ArrayBuffer { [Uint8Contents]: ' +\n          '<01 02 03 04>, byteLength: 4 }\\n}'\n      );\n      ab.x = 42;\n      dv.y = 1337;\n      assert.strictEqual(\n        util.inspect(ab, showHidden),\n        'ArrayBuffer { [Uint8Contents]: <01 02 03 04>, ' +\n          'byteLength: 4, x: 42 }'\n      );\n      assert.strictEqual(\n        util.inspect(dv, showHidden),\n        'DataView {\\n' +\n          '  byteLength: 2,\\n' +\n          '  byteOffset: 1,\\n' +\n          '  buffer: ArrayBuffer { [Uint8Contents]: <01 02 03 04>,' +\n          ' byteLength: 4, x: 42 },\\n' +\n          '  y: 1337\\n}'\n      );\n    }\n\n    {\n      const ab = new ArrayBuffer(42);\n      assert.strictEqual(ab.byteLength, 42);\n      structuredClone(ab, { transfer: [ab] });\n      assert.strictEqual(ab.byteLength, 0);\n      assert.strictEqual(\n        util.inspect(ab),\n        'ArrayBuffer { (detached), byteLength: 0 }'\n      );\n    }\n\n    // Truncate output for ArrayBuffers using plural or singular bytes\n    {\n      const ab = new ArrayBuffer(3);\n      assert.strictEqual(\n        util.inspect(ab, { showHidden: true, maxArrayLength: 2 }),\n        'ArrayBuffer { [Uint8Contents]' +\n          ': <00 00 ... 1 more byte>, byteLength: 3 }'\n      );\n      assert.strictEqual(\n        util.inspect(ab, { showHidden: true, maxArrayLength: 1 }),\n        'ArrayBuffer { [Uint8Contents]' +\n          ': <00 ... 2 more bytes>, byteLength: 3 }'\n      );\n    }\n\n    [\n      Float16Array,\n      Float32Array,\n      Float64Array,\n      Int16Array,\n      Int32Array,\n      Int8Array,\n      Uint16Array,\n      Uint32Array,\n      Uint8Array,\n      Uint8ClampedArray,\n    ].forEach((constructor) => {\n      const length = 2;\n      const byteLength = length * constructor.BYTES_PER_ELEMENT;\n      const array = new constructor(new ArrayBuffer(byteLength), 0, length);\n      array[0] = 65;\n      array[1] = 97;\n      assert.strictEqual(\n        util.inspect(array, { showHidden: true }),\n        `${constructor.name}(${length}) [\\n` +\n          '  65,\\n' +\n          '  97,\\n' +\n          `  [BYTES_PER_ELEMENT]: ${constructor.BYTES_PER_ELEMENT},\\n` +\n          `  [length]: ${length},\\n` +\n          `  [byteLength]: ${byteLength},\\n` +\n          '  [byteOffset]: 0,\\n' +\n          `  [buffer]: ArrayBuffer { byteLength: ${byteLength} }\\n]`\n      );\n      assert.strictEqual(\n        util.inspect(array, false),\n        `${constructor.name}(${length}) [ 65, 97 ]`\n      );\n    });\n\n    {\n      const brokenLength = new Float32Array(2);\n      Object.defineProperty(brokenLength, 'length', { value: -1 });\n      assert.strictEqual(inspect(brokenLength), 'Float32Array(2) [ 0n, 0n ]');\n    }\n\n    assert.strictEqual(\n      util.inspect(\n        Object.create(\n          {},\n          {\n            visible: { value: 1, enumerable: true },\n            hidden: { value: 2 },\n          }\n        ),\n        { showHidden: true }\n      ),\n      '{ visible: 1, [hidden]: 2 }'\n    );\n    // Objects without prototype.\n    assert.strictEqual(\n      util.inspect(\n        Object.create(null, {\n          name: { value: 'Tim', enumerable: true },\n          hidden: { value: 'secret' },\n        }),\n        { showHidden: true }\n      ),\n      \"[Object: null prototype] { name: 'Tim', [hidden]: 'secret' }\"\n    );\n\n    assert.strictEqual(\n      util.inspect(\n        Object.create(null, {\n          name: { value: 'Tim', enumerable: true },\n          hidden: { value: 'secret' },\n        })\n      ),\n      \"[Object: null prototype] { name: 'Tim' }\"\n    );\n\n    // Dynamic properties.\n    {\n      assert.strictEqual(\n        util.inspect({\n          get readonly() {\n            return 1;\n          },\n        }),\n        '{ readonly: [Getter] }'\n      );\n\n      assert.strictEqual(\n        util.inspect({\n          get readwrite() {\n            return 1;\n          },\n          set readwrite(val) {},\n        }),\n        '{ readwrite: [Getter/Setter] }'\n      );\n\n      assert.strictEqual(\n        // eslint-disable-next-line accessor-pairs\n        util.inspect({ set writeonly(val) {} }),\n        '{ writeonly: [Setter] }'\n      );\n\n      const value = {};\n      value.a = value;\n      assert.strictEqual(util.inspect(value), '<ref *1> { a: [Circular *1] }');\n      const getterFn = {\n        get one() {\n          return null;\n        },\n      };\n      assert.strictEqual(\n        util.inspect(getterFn, { getters: true }),\n        '{ one: [Getter: null] }'\n      );\n    }\n\n    // Array with dynamic properties.\n    {\n      const value = [1, 2, 3];\n      Object.defineProperty(value, 'growingLength', {\n        enumerable: true,\n        get: function () {\n          this.push(true);\n          return this.length;\n        },\n      });\n      Object.defineProperty(value, '-1', {\n        enumerable: true,\n        value: -1,\n      });\n      assert.strictEqual(\n        util.inspect(value),\n        \"[ 1, 2, 3, growingLength: [Getter], '-1': -1 ]\"\n      );\n    }\n\n    // Array with inherited number properties.\n    {\n      class CustomArray extends Array {}\n      CustomArray.prototype[5] = 'foo';\n      CustomArray.prototype[49] = 'bar';\n      CustomArray.prototype.foo = true;\n      const arr = new CustomArray(50);\n      arr[49] = 'I win';\n      assert.strictEqual(\n        util.inspect(arr),\n        \"CustomArray(50) [ <49 empty items>, 'I win' ]\"\n      );\n      assert.strictEqual(\n        util.inspect(arr, { showHidden: true }),\n        'CustomArray(50) [\\n' +\n          '  <49 empty items>,\\n' +\n          \"  'I win',\\n\" +\n          '  [length]: 50,\\n' +\n          \"  '5': 'foo',\\n\" +\n          '  foo: true\\n' +\n          ']'\n      );\n    }\n\n    // Array with extra properties.\n    {\n      const arr = [1, 2, 3, ,]; // eslint-disable-line no-sparse-arrays\n      arr.foo = 'bar';\n      assert.strictEqual(\n        util.inspect(arr),\n        \"[ 1, 2, 3, <1 empty item>, foo: 'bar' ]\"\n      );\n\n      const arr2 = [];\n      assert.strictEqual(\n        util.inspect([], { showHidden: true }),\n        '[ [length]: 0 ]'\n      );\n      arr2['00'] = 1;\n      assert.strictEqual(util.inspect(arr2), \"[ '00': 1 ]\");\n      assert.strictEqual(\n        util.inspect(arr2, { showHidden: true }),\n        \"[ [length]: 0, '00': 1 ]\"\n      );\n      arr2[1] = 0;\n      assert.strictEqual(util.inspect(arr2), \"[ <1 empty item>, 0, '00': 1 ]\");\n      assert.strictEqual(\n        util.inspect(arr2, { showHidden: true }),\n        \"[ <1 empty item>, 0, [length]: 2, '00': 1 ]\"\n      );\n      delete arr2[1];\n      assert.strictEqual(util.inspect(arr2), \"[ <2 empty items>, '00': 1 ]\");\n      assert.strictEqual(\n        util.inspect(arr2, { showHidden: true }),\n        \"[ <2 empty items>, [length]: 2, '00': 1 ]\"\n      );\n      arr2['01'] = 2;\n      assert.strictEqual(\n        util.inspect(arr2),\n        \"[ <2 empty items>, '00': 1, '01': 2 ]\"\n      );\n      assert.strictEqual(\n        util.inspect(arr2, { showHidden: true }),\n        \"[ <2 empty items>, [length]: 2, '00': 1, '01': 2 ]\"\n      );\n      delete arr2['00'];\n      arr2[0] = 0;\n      assert.strictEqual(util.inspect(arr2), \"[ 0, <1 empty item>, '01': 2 ]\");\n      assert.strictEqual(\n        util.inspect(arr2, { showHidden: true }),\n        \"[ 0, <1 empty item>, [length]: 2, '01': 2 ]\"\n      );\n      delete arr2['01'];\n      arr2[2 ** 32 - 2] = 'max';\n      arr2[2 ** 32 - 1] = 'too far';\n      assert.strictEqual(\n        util.inspect(arr2),\n        \"[ 0, <4294967293 empty items>, 'max', '4294967295': 'too far' ]\"\n      );\n\n      const arr3 = [];\n      arr3[-1] = -1;\n      assert.strictEqual(util.inspect(arr3), \"[ '-1': -1 ]\");\n    }\n\n    // Indices out of bounds.\n    {\n      const arr = [];\n      arr[2 ** 32] = true; // Not a valid array index.\n      assert.strictEqual(util.inspect(arr), \"[ '4294967296': true ]\");\n      arr[0] = true;\n      arr[10] = true;\n      assert.strictEqual(\n        util.inspect(arr),\n        \"[ true, <9 empty items>, true, '4294967296': true ]\"\n      );\n      arr[2 ** 32 - 2] = true;\n      arr[2 ** 32 - 1] = true;\n      arr[2 ** 32 + 1] = true;\n      delete arr[0];\n      delete arr[10];\n      assert.strictEqual(\n        util.inspect(arr),\n        [\n          '[',\n          '<4294967294 empty items>,',\n          'true,',\n          \"'4294967296': true,\",\n          \"'4294967295': true,\",\n          \"'4294967297': true\\n]\",\n        ].join('\\n  ')\n      );\n    }\n\n    // Function with properties.\n    {\n      const value = () => {};\n      value.aprop = 42;\n      assert.strictEqual(\n        util.inspect(value),\n        '[Function: value] { aprop: 42 }'\n      );\n    }\n\n    // Anonymous function with properties.\n    {\n      const value = (() => function () {})();\n      value.aprop = 42;\n      assert.strictEqual(\n        util.inspect(value),\n        '[Function (anonymous)] { aprop: 42 }'\n      );\n    }\n\n    // Regular expressions with properties.\n    {\n      const value = /123/gi;\n      value.aprop = 42;\n      assert.strictEqual(util.inspect(value), '/123/gi { aprop: 42 }');\n    }\n\n    // Dates with properties.\n    {\n      const value = new Date('Sun, 14 Feb 2010 11:48:40 GMT');\n      value.aprop = 42;\n      assert.strictEqual(\n        util.inspect(value),\n        '2010-02-14T11:48:40.000Z { aprop: 42 }'\n      );\n    }\n\n    // Test positive/negative zero.\n    assert.strictEqual(util.inspect(0), '0');\n    assert.strictEqual(util.inspect(-0), '-0');\n    // Edge case from check.\n    assert.strictEqual(util.inspect(-5e-324), '-5e-324');\n\n    // Test for sparse array.\n    {\n      const a = ['foo', 'bar', 'baz'];\n      assert.strictEqual(util.inspect(a), \"[ 'foo', 'bar', 'baz' ]\");\n      delete a[1];\n      assert.strictEqual(util.inspect(a), \"[ 'foo', <1 empty item>, 'baz' ]\");\n      assert.strictEqual(\n        util.inspect(a, true),\n        \"[ 'foo', <1 empty item>, 'baz', [length]: 3 ]\"\n      );\n      assert.strictEqual(util.inspect(new Array(5)), '[ <5 empty items> ]');\n      a[3] = 'bar';\n      a[100] = 'qux';\n      assert.strictEqual(\n        util.inspect(a, { breakLength: Infinity }),\n        \"[ 'foo', <1 empty item>, 'baz', 'bar', <96 empty items>, 'qux' ]\"\n      );\n      delete a[3];\n      assert.strictEqual(\n        util.inspect(a, { maxArrayLength: 4 }),\n        \"[ 'foo', <1 empty item>, 'baz', <97 empty items>, ... 1 more item ]\"\n      );\n      // test 4 special case\n      assert.strictEqual(\n        util.inspect(a, {\n          maxArrayLength: 2,\n        }),\n        \"[ 'foo', <1 empty item>, ... 99 more items ]\"\n      );\n    }\n\n    // Test for property descriptors.\n    {\n      const getter = Object.create(null, {\n        a: {\n          get: function () {\n            return 'aaa';\n          },\n        },\n      });\n      const setter = Object.create(null, {\n        b: {\n          // eslint-disable-line accessor-pairs\n          set: function () {},\n        },\n      });\n      const getterAndSetter = Object.create(null, {\n        c: {\n          get: function () {\n            return 'ccc';\n          },\n          set: function () {},\n        },\n      });\n      assert.strictEqual(\n        util.inspect(getter, true),\n        '[Object: null prototype] { [a]: [Getter] }'\n      );\n      assert.strictEqual(\n        util.inspect(setter, true),\n        '[Object: null prototype] { [b]: [Setter] }'\n      );\n      assert.strictEqual(\n        util.inspect(getterAndSetter, true),\n        '[Object: null prototype] { [c]: [Getter/Setter] }'\n      );\n    }\n\n    // Exceptions should print the error message, not '{}'.\n    {\n      [\n        new Error(),\n        new Error('FAIL'),\n        new TypeError('FAIL'),\n        new SyntaxError('FAIL'),\n      ].forEach((err) => {\n        assert.strictEqual(util.inspect(err), err.stack);\n      });\n      assert.throws(\n        () => undef(), // eslint-disable-line no-undef\n        (e) => {\n          assert.strictEqual(util.inspect(e), e.stack);\n          return true;\n        }\n      );\n\n      const ex = util.inspect(new Error('FAIL'), true);\n      assert(ex.includes('Error: FAIL'));\n      assert(ex.includes('[stack]'));\n      assert(ex.includes('[message]'));\n    }\n\n    {\n      const falsyCause1 = new Error('', { cause: false });\n      delete falsyCause1.stack;\n      const falsyCause2 = new Error(undefined, { cause: null });\n      falsyCause2.stack = '';\n      const undefinedCause = new Error('', { cause: undefined });\n      undefinedCause.stack = '';\n\n      assert.strictEqual(\n        util.inspect(falsyCause1),\n        '[Error] { [cause]: false }'\n      );\n      assert.strictEqual(\n        util.inspect(falsyCause2),\n        '[Error] { [cause]: null }'\n      );\n      assert.strictEqual(\n        util.inspect(undefinedCause),\n        '[Error] { [cause]: undefined }'\n      );\n    }\n\n    {\n      const tmp = Error.stackTraceLimit;\n      Error.stackTraceLimit = 0;\n      const err = new Error('foo');\n      const err2 = new Error('foo\\nbar');\n      assert.strictEqual(util.inspect(err, { compact: true }), '[Error: foo]');\n      assert(err.stack);\n      delete err.stack;\n      assert(!err.stack);\n      assert.strictEqual(util.inspect(err, { compact: true }), '[Error: foo]');\n      assert.strictEqual(\n        util.inspect(err2, { compact: true }),\n        '[Error: foo\\nbar]'\n      );\n\n      err.bar = true;\n      err2.bar = true;\n\n      assert.strictEqual(\n        util.inspect(err, { compact: true }),\n        '{ [Error: foo] bar: true }'\n      );\n      assert.strictEqual(\n        util.inspect(err2, { compact: true }),\n        '{ [Error: foo\\nbar]\\n  bar: true }'\n      );\n      assert.strictEqual(\n        util.inspect(err, { compact: true, breakLength: 5 }),\n        '{ [Error: foo]\\n  bar: true }'\n      );\n      assert.strictEqual(\n        util.inspect(err, { compact: true, breakLength: 1 }),\n        '{ [Error: foo]\\n  bar:\\n   true }'\n      );\n      assert.strictEqual(\n        util.inspect(err2, { compact: true, breakLength: 5 }),\n        '{ [Error: foo\\nbar]\\n  bar: true }'\n      );\n      assert.strictEqual(\n        util.inspect(err, { compact: false }),\n        '[Error: foo] {\\n  bar: true\\n}'\n      );\n      assert.strictEqual(\n        util.inspect(err2, { compact: false }),\n        '[Error: foo\\nbar] {\\n  bar: true\\n}'\n      );\n\n      Error.stackTraceLimit = tmp;\n    }\n\n    // Prevent enumerable error properties from being printed.\n    {\n      let err = new Error('foobar');\n      err.message = 'foobar';\n      let out = util.inspect(err).split('\\n');\n      assert.strictEqual(out[0], 'Error: foobar');\n      assert(out[1].startsWith('    at '));\n      // Reset the error, the stack is otherwise not recreated.\n      err = new Error();\n      err.message = 'foobar';\n      out = util.inspect(err).split('\\n');\n      assert.strictEqual(out[0], 'Error');\n      assert(out[1].startsWith('    at '));\n      assert.strictEqual(out[out.length - 2], \"  message: 'foobar'\");\n      assert.strictEqual(out[out.length - 1], '}');\n      // Reset the error, the stack is otherwise not recreated.\n      err = new Error('foobar');\n      err.name = 'Unique';\n      Object.defineProperty(err, 'stack', {\n        value: err.stack,\n        enumerable: true,\n      });\n      out = util.inspect(err).split('\\n');\n      assert.strictEqual(out[0], 'Unique: foobar');\n      assert(out[1].startsWith('    at '));\n      err.name = 'Baz';\n      out = util.inspect(err).split('\\n');\n      assert.strictEqual(out[0], 'Unique: foobar');\n      assert.strictEqual(out[out.length - 2], \"  name: 'Baz'\");\n      assert.strictEqual(out[out.length - 1], '}');\n    }\n\n    // Doesn't capture stack trace.\n    {\n      function BadCustomError(msg) {\n        Error.call(this);\n        Object.defineProperty(this, 'message', {\n          value: msg,\n          enumerable: false,\n        });\n        Object.defineProperty(this, 'name', {\n          value: 'BadCustomError',\n          enumerable: false,\n        });\n      }\n      Object.setPrototypeOf(BadCustomError.prototype, Error.prototype);\n      Object.setPrototypeOf(BadCustomError, Error);\n      assert.strictEqual(\n        util.inspect(new BadCustomError('foo')),\n        '[BadCustomError: foo]'\n      );\n    }\n\n    // Tampered error stack or name property (different type than string).\n    // Note: Symbols are not supported by `Error#toString()` which is called by\n    // accessing the `stack` property.\n    [\n      [404, '404 [RangeError]: foo', '[404]'],\n      [0, '0 [RangeError]: foo', '[RangeError: foo]'],\n      [0n, '0 [RangeError]: foo', '[RangeError: foo]'],\n      [null, 'null: foo', '[RangeError: foo]'],\n      [undefined, 'RangeError: foo', '[RangeError: foo]'],\n      [false, 'false [RangeError]: foo', '[RangeError: foo]'],\n      ['', 'foo', '[RangeError: foo]'],\n      [[1, 2, 3], '1,2,3 [RangeError]: foo', '[[\\n  1,\\n  2,\\n  3\\n]]'],\n    ].forEach(([value, outputStart, stack]) => {\n      let err = new RangeError('foo');\n      err.name = value;\n      assert(\n        util.inspect(err).startsWith(outputStart),\n        util.format(\n          'The name set to %o did not result in the expected output \"%s\"',\n          value,\n          outputStart\n        )\n      );\n\n      err = new RangeError('foo');\n      err.stack = value;\n      assert.strictEqual(util.inspect(err), stack);\n    });\n\n    // https://github.com/nodejs/node-v0.x-archive/issues/1941\n    assert.strictEqual(util.inspect({ __proto__: Date.prototype }), 'Date {}');\n\n    // https://github.com/nodejs/node-v0.x-archive/issues/1944\n    {\n      const d = new Date();\n      d.toUTCString = null;\n      util.inspect(d);\n    }\n\n    // Should not throw.\n    {\n      const d = new Date();\n      d.toISOString = null;\n      util.inspect(d);\n    }\n\n    // Should not throw.\n    {\n      const r = /regexp/;\n      r.toString = null;\n      util.inspect(r);\n    }\n\n    // See https://github.com/nodejs/node-v0.x-archive/issues/2225\n    {\n      const x = { [util.inspect.custom]: util.inspect };\n      assert(\n        util\n          .inspect(x)\n          .includes(\n            'Symbol(nodejs.util.inspect.custom): [Function: inspect] {\\n'\n          )\n      );\n    }\n\n    // `util.inspect` should display the escaped value of a key.\n    {\n      const w = {\n        '\\\\': 1,\n        '\\\\\\\\': 2,\n        '\\\\\\\\\\\\': 3,\n        '\\\\\\\\\\\\\\\\': 4,\n        '\\n': 5,\n        '\\r': 6,\n      };\n\n      const y = ['a', 'b', 'c'];\n      y['\\\\\\\\'] = 'd';\n      y['\\n'] = 'e';\n      y['\\r'] = 'f';\n\n      assert.strictEqual(\n        util.inspect(w),\n        \"{ '\\\\\\\\': 1, '\\\\\\\\\\\\\\\\': 2, '\\\\\\\\\\\\\\\\\\\\\\\\': 3, \" +\n          \"'\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\': 4, '\\\\n': 5, '\\\\r': 6 }\"\n      );\n      assert.strictEqual(\n        util.inspect(y),\n        \"[ 'a', 'b', 'c', '\\\\\\\\\\\\\\\\': 'd', \" + \"'\\\\n': 'e', '\\\\r': 'f' ]\"\n      );\n    }\n\n    // Escape unpaired surrogate pairs.\n    {\n      const edgeChar = String.fromCharCode(0xd799);\n\n      for (let charCode = 0xd800; charCode < 0xdfff; charCode++) {\n        const surrogate = String.fromCharCode(charCode);\n\n        assert.strictEqual(\n          util.inspect(surrogate),\n          `'\\\\u${charCode.toString(16)}'`\n        );\n        assert.strictEqual(\n          util.inspect(`${'a'.repeat(200)}${surrogate}`),\n          `'${'a'.repeat(200)}\\\\u${charCode.toString(16)}'`\n        );\n        assert.strictEqual(\n          util.inspect(`${surrogate}${'a'.repeat(200)}`),\n          `'\\\\u${charCode.toString(16)}${'a'.repeat(200)}'`\n        );\n        if (charCode < 0xdc00) {\n          const highSurrogate = surrogate;\n          const lowSurrogate = String.fromCharCode(charCode + 1024);\n          assert(\n            !util\n              .inspect(`${edgeChar}${highSurrogate}${lowSurrogate}${edgeChar}`)\n              .includes('\\\\u')\n          );\n          assert.strictEqual(\n            (\n              util\n                .inspect(`${highSurrogate}${highSurrogate}${lowSurrogate}`)\n                .match(/\\\\u/g) ?? []\n            ).length,\n            1\n          );\n        } else {\n          assert.strictEqual(\n            util.inspect(`${edgeChar}${surrogate}${edgeChar}`),\n            `'${edgeChar}\\\\u${charCode.toString(16)}${edgeChar}'`\n          );\n        }\n      }\n    }\n\n    // Test util.inspect.styles and util.inspect.colors.\n    {\n      function testColorStyle(style, input) {\n        const colorName = util.inspect.styles[style];\n        let color = ['', ''];\n        if (util.inspect.colors[colorName])\n          color = util.inspect.colors[colorName];\n\n        const withoutColor = util.inspect(input, false, 0, false);\n        const withColor = util.inspect(input, false, 0, true);\n        const expect = `\\u001b[${color[0]}m${withoutColor}\\u001b[${color[1]}m`;\n        assert.strictEqual(\n          withColor,\n          expect,\n          `util.inspect color for style ${style}`\n        );\n      }\n\n      testColorStyle('special', function () {});\n      testColorStyle('number', 123.456);\n      testColorStyle('boolean', true);\n      testColorStyle('undefined', undefined);\n      testColorStyle('null', null);\n      testColorStyle('string', 'test string');\n      testColorStyle('date', new Date());\n      testColorStyle('regexp', /regexp/);\n    }\n\n    // An object with \"hasOwnProperty\" overwritten should not throw.\n    util.inspect({ hasOwnProperty: null });\n\n    // New API, accepts an \"options\" object.\n    {\n      const subject = { foo: 'bar', hello: 31, a: { b: { c: { d: 0 } } } };\n      Object.defineProperty(subject, 'hidden', {\n        enumerable: false,\n        value: null,\n      });\n\n      assert.strictEqual(\n        util.inspect(subject, { showHidden: false }).includes('hidden'),\n        false\n      );\n      assert.strictEqual(\n        util.inspect(subject, { showHidden: true }).includes('hidden'),\n        true\n      );\n      assert.strictEqual(\n        util.inspect(subject, { colors: false }).includes('\\u001b[32m'),\n        false\n      );\n      assert.strictEqual(\n        util.inspect(subject, { colors: true }).includes('\\u001b[32m'),\n        true\n      );\n      assert.strictEqual(\n        util.inspect(subject, { depth: 2 }).includes('c: [Object]'),\n        true\n      );\n      assert.strictEqual(\n        util.inspect(subject, { depth: 0 }).includes('a: [Object]'),\n        true\n      );\n      assert.strictEqual(\n        util.inspect(subject, { depth: null }).includes('{ d: 0 }'),\n        true\n      );\n      assert.strictEqual(\n        util.inspect(subject, { depth: undefined }).includes('{ d: 0 }'),\n        true\n      );\n    }\n\n    {\n      // \"customInspect\" option can enable/disable calling [util.inspect.custom]().\n      const subject = { [util.inspect.custom]: () => 123 };\n\n      assert.strictEqual(\n        util.inspect(subject, { customInspect: true }).includes('123'),\n        true\n      );\n      assert.strictEqual(\n        util.inspect(subject, { customInspect: true }).includes('inspect'),\n        false\n      );\n      assert.strictEqual(\n        util.inspect(subject, { customInspect: false }).includes('123'),\n        false\n      );\n      assert.strictEqual(\n        util.inspect(subject, { customInspect: false }).includes('inspect'),\n        true\n      );\n\n      // A custom [util.inspect.custom]() should be able to return other Objects.\n      subject[util.inspect.custom] = () => ({ foo: 'bar' });\n\n      assert.strictEqual(util.inspect(subject), \"{ foo: 'bar' }\");\n\n      subject[util.inspect.custom] = commonMustCall((depth, opts, inspect) => {\n        const clone = { ...opts };\n        // This might change at some point but for now we keep the stylize function.\n        // The function should either be documented or an alternative should be\n        // implemented.\n        assert.strictEqual(typeof opts.stylize, 'function');\n        assert.strictEqual(opts.seen, undefined);\n        assert.strictEqual(opts.budget, undefined);\n        assert.strictEqual(opts.indentationLvl, undefined);\n        assert.strictEqual(opts.showHidden, false);\n        assert.strictEqual(inspect, util.inspect);\n        assert.deepStrictEqual(\n          new Set(Object.keys(inspect.defaultOptions).concat(['stylize'])),\n          new Set(Object.keys(opts))\n        );\n        opts.showHidden = true;\n        return {\n          [inspect.custom]: commonMustCall((depth, opts2) => {\n            assert.deepStrictEqual(clone, opts2);\n          }),\n        };\n      });\n\n      util.inspect(subject);\n\n      // util.inspect.custom is a shared symbol which can be accessed as\n      // Symbol.for(\"nodejs.util.inspect.custom\").\n      const inspect = Symbol.for('nodejs.util.inspect.custom');\n\n      subject[inspect] = () => ({ baz: 'quux' });\n\n      assert.strictEqual(util.inspect(subject), \"{ baz: 'quux' }\");\n\n      subject[inspect] = (depth, opts) => {\n        assert.strictEqual(opts.customInspectOptions, true);\n        assert.strictEqual(opts.seen, null);\n        return {};\n      };\n\n      util.inspect(subject, { customInspectOptions: true, seen: null });\n    }\n\n    {\n      const subject = {\n        [util.inspect.custom]: commonMustCall((depth, opts) => {\n          assert.strictEqual(depth, null);\n          assert.strictEqual(opts.compact, true);\n        }),\n      };\n      util.inspect(subject, { depth: null, compact: true });\n    }\n\n    {\n      // Returning `this` from a custom inspection function works.\n      const subject = {\n        a: 123,\n        [util.inspect.custom]() {\n          return this;\n        },\n      };\n      const UIC = 'nodejs.util.inspect.custom';\n      assert.strictEqual(\n        util.inspect(subject),\n        `{\\n  a: 123,\\n  Symbol(${UIC}): [Function: [${UIC}]]\\n}`\n      );\n    }\n\n    // Verify that it's possible to use the stylize function to manipulate input.\n    assert.strictEqual(\n      util.inspect([1, 2, 3], {\n        stylize() {\n          return 'x';\n        },\n      }),\n      '[ x, x, x ]'\n    );\n\n    // Using `util.inspect` with \"colors\" option should produce as many lines as\n    // without it.\n    {\n      function testLines(input) {\n        const countLines = (str) => (str.match(/\\n/g) || []).length;\n        const withoutColor = util.inspect(input);\n        const withColor = util.inspect(input, { colors: true });\n        assert.strictEqual(countLines(withoutColor), countLines(withColor));\n      }\n\n      const bigArray = Array.from({ length: 100 }, (_value, index) => index);\n\n      testLines([1, 2, 3, 4, 5, 6, 7]);\n      testLines(bigArray);\n      testLines({ foo: 'bar', baz: 35, b: { a: 35 } });\n      testLines({\n        a: { a: 3, b: 1, c: 1, d: 1, e: 1, f: 1, g: 1, h: 1 },\n        b: 1,\n      });\n      testLines({\n        foo: 'bar',\n        baz: 35,\n        b: { a: 35 },\n        veryLongKey: 'very long value',\n        evenLongerKey: ['with even longer value in array'],\n      });\n    }\n\n    // Test boxed primitives output the correct values.\n    assert.strictEqual(util.inspect(new String('test')), \"[String: 'test']\");\n    assert.strictEqual(\n      util.inspect(new String('test'), { colors: true }),\n      \"\\u001b[32m[String: 'test']\\u001b[39m\"\n    );\n    assert.strictEqual(\n      util.inspect(Object(Symbol('test'))),\n      '[Symbol: Symbol(test)]'\n    );\n    assert.strictEqual(util.inspect(new Boolean(false)), '[Boolean: false]');\n    assert.strictEqual(\n      util.inspect(Object.setPrototypeOf(new Boolean(true), null)),\n      '[Boolean (null prototype): true]'\n    );\n    assert.strictEqual(util.inspect(new Number(0)), '[Number: 0]');\n    assert.strictEqual(\n      util.inspect(\n        Object.defineProperty(\n          Object.setPrototypeOf(new Number(-0), Array.prototype),\n          Symbol.toStringTag,\n          { value: 'Foobar' }\n        )\n      ),\n      '[Number (Array): -0] [Foobar]'\n    );\n    assert.strictEqual(util.inspect(new Number(-1.1)), '[Number: -1.1]');\n    assert.strictEqual(util.inspect(new Number(13.37)), '[Number: 13.37]');\n\n    // Test boxed primitives with own properties.\n    {\n      const str = new String('baz');\n      str.foo = 'bar';\n      assert.strictEqual(util.inspect(str), \"[String: 'baz'] { foo: 'bar' }\");\n\n      const bool = new Boolean(true);\n      bool.foo = 'bar';\n      assert.strictEqual(util.inspect(bool), \"[Boolean: true] { foo: 'bar' }\");\n\n      const num = new Number(13.37);\n      num.foo = 'bar';\n      assert.strictEqual(util.inspect(num), \"[Number: 13.37] { foo: 'bar' }\");\n\n      const sym = Object(Symbol('foo'));\n      sym.foo = 'bar';\n      assert.strictEqual(\n        util.inspect(sym),\n        \"[Symbol: Symbol(foo)] { foo: 'bar' }\"\n      );\n\n      const big = Object(BigInt(55));\n      big.foo = 'bar';\n      assert.strictEqual(util.inspect(big), \"[BigInt: 55n] { foo: 'bar' }\");\n    }\n\n    // Test es6 Symbol.\n    if (typeof Symbol !== 'undefined') {\n      assert.strictEqual(util.inspect(Symbol()), 'Symbol()');\n      assert.strictEqual(util.inspect(Symbol(123)), 'Symbol(123)');\n      assert.strictEqual(util.inspect(Symbol('hi')), 'Symbol(hi)');\n      assert.strictEqual(util.inspect([Symbol()]), '[ Symbol() ]');\n      assert.strictEqual(util.inspect({ foo: Symbol() }), '{ foo: Symbol() }');\n\n      const options = { showHidden: true };\n      let subject = {};\n\n      subject[Symbol('sym\\nbol')] = 42;\n\n      assert.strictEqual(util.inspect(subject), '{ Symbol(sym\\\\nbol): 42 }');\n      assert.strictEqual(\n        util.inspect(subject, options),\n        '{ Symbol(sym\\\\nbol): 42 }'\n      );\n\n      Object.defineProperty(subject, Symbol(), {\n        enumerable: false,\n        value: 'non-enum',\n      });\n      assert.strictEqual(util.inspect(subject), '{ Symbol(sym\\\\nbol): 42 }');\n      assert.strictEqual(\n        util.inspect(subject, options),\n        \"{ Symbol(sym\\\\nbol): 42, [Symbol()]: 'non-enum' }\"\n      );\n\n      subject = [1, 2, 3];\n      subject[Symbol('symbol')] = 42;\n\n      assert.strictEqual(\n        util.inspect(subject),\n        '[ 1, 2, 3, Symbol(symbol): 42 ]'\n      );\n    }\n\n    // Test Set.\n    {\n      assert.strictEqual(util.inspect(new Set()), 'Set(0) {}');\n      assert.strictEqual(\n        util.inspect(new Set([1, 2, 3])),\n        'Set(3) { 1, 2, 3 }'\n      );\n      assert.strictEqual(\n        util.inspect(new Set([1, 2, 3]), { maxArrayLength: 1 }),\n        'Set(3) { 1, ... 2 more items }'\n      );\n      const set = new Set(['foo']);\n      set.bar = 42;\n      assert.strictEqual(\n        util.inspect(set, { showHidden: true }),\n        \"Set(1) { 'foo', bar: 42 }\"\n      );\n    }\n\n    // Test circular Set.\n    {\n      const set = new Set();\n      set.add(set);\n      assert.strictEqual(\n        util.inspect(set),\n        '<ref *1> Set(1) { [Circular *1] }'\n      );\n    }\n\n    // Test Map.\n    {\n      assert.strictEqual(util.inspect(new Map()), 'Map(0) {}');\n      assert.strictEqual(\n        util.inspect(\n          new Map([\n            [1, 'a'],\n            [2, 'b'],\n            [3, 'c'],\n          ])\n        ),\n        \"Map(3) { 1 => 'a', 2 => 'b', 3 => 'c' }\"\n      );\n      assert.strictEqual(\n        util.inspect(\n          new Map([\n            [1, 'a'],\n            [2, 'b'],\n            [3, 'c'],\n          ]),\n          { maxArrayLength: 1 }\n        ),\n        \"Map(3) { 1 => 'a', ... 2 more items }\"\n      );\n      const map = new Map([['foo', null]]);\n      map.bar = 42;\n      assert.strictEqual(\n        util.inspect(map, true),\n        \"Map(1) { 'foo' => null, bar: 42 }\"\n      );\n    }\n\n    // Test circular Map.\n    {\n      const map = new Map();\n      map.set(map, 'map');\n      assert.strictEqual(\n        inspect(map),\n        \"<ref *1> Map(1) { [Circular *1] => 'map' }\"\n      );\n      map.set(map, map);\n      assert.strictEqual(\n        inspect(map),\n        '<ref *1> Map(1) { [Circular *1] => [Circular *1] }'\n      );\n      map.delete(map);\n      map.set('map', map);\n      assert.strictEqual(\n        inspect(map),\n        \"<ref *1> Map(1) { 'map' => [Circular *1] }\"\n      );\n    }\n\n    // Test multiple circular references.\n    {\n      const obj = {};\n      obj.a = [obj];\n      obj.b = {};\n      obj.b.inner = obj.b;\n      obj.b.obj = obj;\n\n      assert.strictEqual(\n        inspect(obj),\n        '<ref *1> {\\n' +\n          '  a: [ [Circular *1] ],\\n' +\n          '  b: <ref *2> { inner: [Circular *2], obj: [Circular *1] }\\n' +\n          '}'\n      );\n    }\n\n    // Test Promise.\n    {\n      const resolved = Promise.resolve(3);\n      assert.strictEqual(util.inspect(resolved), 'Promise { 3 }');\n\n      const rejected = Promise.reject(3);\n      assert.strictEqual(util.inspect(rejected), 'Promise { <rejected> 3 }');\n      // Squelch UnhandledPromiseRejection.\n      rejected.catch(() => {});\n\n      const pending = new Promise(() => {});\n      assert.strictEqual(util.inspect(pending), 'Promise { <pending> }');\n\n      const promiseWithProperty = Promise.resolve('foo');\n      promiseWithProperty.bar = 42;\n      assert.strictEqual(\n        util.inspect(promiseWithProperty),\n        \"Promise { 'foo', bar: 42 }\"\n      );\n    }\n\n    // Make sure it doesn't choke on polyfills. Unlike Set/Map, there is no standard\n    // interface to synchronously inspect a Promise, so our techniques only work on\n    // a bonafide native Promise.\n    {\n      const oldPromise = Promise;\n      globalThis.Promise = function () {\n        this.bar = 42;\n      };\n      assert.strictEqual(util.inspect(new Promise()), '{ bar: 42 }');\n      globalThis.Promise = oldPromise;\n    }\n\n    // Test Map iterators.\n    {\n      const map = new Map([['foo', 'bar']]);\n      assert.strictEqual(util.inspect(map.keys()), \"[Map Iterator] { 'foo' }\");\n      const mapValues = map.values();\n      Object.defineProperty(mapValues, Symbol.toStringTag, { value: 'Foo' });\n      assert.strictEqual(\n        util.inspect(mapValues),\n        \"[Foo] [Map Iterator] { 'bar' }\"\n      );\n      map.set('A', 'B!');\n      assert.strictEqual(\n        util.inspect(map.entries(), { maxArrayLength: 1 }),\n        \"[Map Entries] { [ 'foo', 'bar' ], ... 1 more item }\"\n      );\n      // Make sure the iterator doesn't get consumed.\n      const keys = map.keys();\n      assert.strictEqual(util.inspect(keys), \"[Map Iterator] { 'foo', 'A' }\");\n      assert.strictEqual(util.inspect(keys), \"[Map Iterator] { 'foo', 'A' }\");\n      keys.extra = true;\n      assert.strictEqual(\n        util.inspect(keys, { maxArrayLength: 0 }),\n        '[Map Iterator] { ... 2 more items, extra: true }'\n      );\n    }\n\n    // Test Set iterators.\n    {\n      const aSet = new Set([1]);\n      assert.strictEqual(\n        util.inspect(aSet.entries(), { compact: false }),\n        '[Set Entries] {\\n  [\\n    1,\\n    1\\n  ]\\n}'\n      );\n      aSet.add(3);\n      assert.strictEqual(util.inspect(aSet.keys()), '[Set Iterator] { 1, 3 }');\n      assert.strictEqual(\n        util.inspect(aSet.values()),\n        '[Set Iterator] { 1, 3 }'\n      );\n      const setEntries = aSet.entries();\n      Object.defineProperty(setEntries, Symbol.toStringTag, { value: 'Foo' });\n      assert.strictEqual(\n        util.inspect(setEntries),\n        '[Foo] [Set Entries] { [ 1, 1 ], [ 3, 3 ] }'\n      );\n      // Make sure the iterator doesn't get consumed.\n      const keys = aSet.keys();\n      Object.defineProperty(keys, Symbol.toStringTag, { value: null });\n      assert.strictEqual(util.inspect(keys), '[Set Iterator] { 1, 3 }');\n      assert.strictEqual(util.inspect(keys), '[Set Iterator] { 1, 3 }');\n      keys.extra = true;\n      assert.strictEqual(\n        util.inspect(keys, { maxArrayLength: 1 }),\n        '[Set Iterator] { 1, ... 1 more item, extra: true }'\n      );\n    }\n\n    // Minimal inspection should still return as much information as possible about\n    // the constructor and Symbol.toStringTag.\n    {\n      class Foo {\n        get [Symbol.toStringTag]() {\n          return 'ABC';\n        }\n      }\n      const a = new Foo();\n      assert.strictEqual(inspect(a, { depth: -1 }), 'Foo [ABC] {}');\n      a.foo = true;\n      assert.strictEqual(inspect(a, { depth: -1 }), '[Foo [ABC]]');\n      Object.defineProperty(a, Symbol.toStringTag, {\n        value: 'Foo',\n        configurable: true,\n        writable: true,\n      });\n      assert.strictEqual(inspect(a, { depth: -1 }), '[Foo]');\n      delete a[Symbol.toStringTag];\n      Object.setPrototypeOf(a, null);\n      assert.strictEqual(inspect(a, { depth: -1 }), '[Foo: null prototype]');\n      delete a.foo;\n      assert.strictEqual(inspect(a, { depth: -1 }), '[Foo: null prototype] {}');\n      Object.defineProperty(a, Symbol.toStringTag, {\n        value: 'ABC',\n        configurable: true,\n      });\n      assert.strictEqual(\n        inspect(a, { depth: -1 }),\n        '[Foo: null prototype] [ABC] {}'\n      );\n      Object.defineProperty(a, Symbol.toStringTag, {\n        value: 'Foo',\n        configurable: true,\n      });\n      assert.strictEqual(\n        inspect(a, { depth: -1 }),\n        '[Object: null prototype] [Foo] {}'\n      );\n    }\n\n    // Test alignment of items in container.\n    // Assumes that the first numeric character is the start of an item.\n    {\n      function checkAlignment(container, start, lineX, end) {\n        const lines = util.inspect(container).split('\\n');\n        lines.forEach((line, i) => {\n          if (i === 0) {\n            assert.strictEqual(line, start);\n          } else if (i === lines.length - 1) {\n            assert.strictEqual(line, end);\n          } else {\n            let expected = lineX.replace('X', i - 1);\n            if (i !== lines.length - 2) expected += ',';\n            assert.strictEqual(line, expected);\n          }\n        });\n      }\n\n      const bigArray = [];\n      for (let i = 0; i < 100; i++) {\n        bigArray.push(i);\n      }\n\n      const obj = {};\n      bigArray.forEach((prop) => {\n        obj[prop] = null;\n      });\n\n      checkAlignment(obj, '{', \"  'X': null\", '}');\n      checkAlignment(new Set(bigArray), 'Set(100) {', '  X', '}');\n      checkAlignment(\n        new Map(bigArray.map((number) => [number, null])),\n        'Map(100) {',\n        '  X => null',\n        '}'\n      );\n    }\n\n    // Test display of constructors.\n    {\n      class ObjectSubclass {}\n      class ArraySubclass extends Array {}\n      class SetSubclass extends Set {}\n      class MapSubclass extends Map {}\n      class PromiseSubclass extends Promise {}\n      class SymbolNameClass {\n        static name = Symbol('name');\n      }\n\n      const x = new ObjectSubclass();\n      x.foo = 42;\n      assert.strictEqual(util.inspect(x), 'ObjectSubclass { foo: 42 }');\n      assert.strictEqual(\n        util.inspect(new ArraySubclass(1, 2, 3)),\n        'ArraySubclass(3) [ 1, 2, 3 ]'\n      );\n      assert.strictEqual(\n        util.inspect(new SetSubclass([1, 2, 3])),\n        'SetSubclass(3) [Set] { 1, 2, 3 }'\n      );\n      assert.strictEqual(\n        util.inspect(new MapSubclass([['foo', 42]])),\n        \"MapSubclass(1) [Map] { 'foo' => 42 }\"\n      );\n      assert.strictEqual(\n        util.inspect(new PromiseSubclass(() => {})),\n        'PromiseSubclass [Promise] { <pending> }'\n      );\n      assert.strictEqual(\n        util.inspect(new SymbolNameClass()),\n        'Symbol(name) {}'\n      );\n      assert.strictEqual(\n        util.inspect(\n          { a: { b: new ArraySubclass([1, [2], 3]) } },\n          { depth: 1 }\n        ),\n        '{ a: { b: [ArraySubclass] } }'\n      );\n      assert.strictEqual(\n        util.inspect(Object.setPrototypeOf(x, null)),\n        '[ObjectSubclass: null prototype] { foo: 42 }'\n      );\n    }\n\n    // Empty and circular before depth.\n    {\n      const arr = [[[[]]]];\n      assert.strictEqual(util.inspect(arr), '[ [ [ [] ] ] ]');\n      arr[0][0][0][0] = [];\n      assert.strictEqual(util.inspect(arr), '[ [ [ [Array] ] ] ]');\n      arr[0][0][0] = {};\n      assert.strictEqual(util.inspect(arr), '[ [ [ {} ] ] ]');\n      arr[0][0][0] = { a: 2 };\n      assert.strictEqual(util.inspect(arr), '[ [ [ [Object] ] ] ]');\n      arr[0][0][0] = arr;\n      assert.strictEqual(\n        util.inspect(arr),\n        '<ref *1> [ [ [ [Circular *1] ] ] ]'\n      );\n      arr[0][0][0] = arr[0][0];\n      assert.strictEqual(\n        util.inspect(arr),\n        '[ [ <ref *1> [ [Circular *1] ] ] ]'\n      );\n    }\n\n    // Corner cases.\n    {\n      const x = { constructor: 42 };\n      assert.strictEqual(util.inspect(x), '{ constructor: 42 }');\n    }\n\n    {\n      const x = {};\n      Object.defineProperty(x, 'constructor', {\n        get: function () {\n          throw new Error('should not access constructor');\n        },\n        enumerable: true,\n      });\n      assert.strictEqual(util.inspect(x), '{ constructor: [Getter] }');\n    }\n\n    {\n      const x = new (function () {})(); // eslint-disable-line new-parens\n      assert.strictEqual(util.inspect(x), '{}');\n    }\n\n    {\n      const x = { __proto__: null };\n      assert.strictEqual(util.inspect(x), '[Object: null prototype] {}');\n    }\n\n    {\n      const x = [];\n      x[''] = 1;\n      assert.strictEqual(util.inspect(x), \"[ '': 1 ]\");\n    }\n\n    // The following maxArrayLength tests were introduced after v6.0.0 was released.\n    // Do not backport to v5/v4 unless all of\n    // https://github.com/nodejs/node/pull/6334 is backported.\n    {\n      const x = Array.from({ length: 101 });\n      assert(util.inspect(x).endsWith('1 more item\\n]'));\n      assert(\n        !util.inspect(x, { maxArrayLength: 101 }).endsWith('1 more item\\n]')\n      );\n      assert.strictEqual(\n        util.inspect(x, { maxArrayLength: -1 }),\n        '[ ... 101 more items ]'\n      );\n      assert.strictEqual(\n        util.inspect(x, { maxArrayLength: 0 }),\n        '[ ... 101 more items ]'\n      );\n    }\n\n    {\n      const x = Array.from({ length: 101 });\n      assert.strictEqual(\n        util.inspect(x, { maxArrayLength: 0 }),\n        '[ ... 101 more items ]'\n      );\n      assert(\n        !util.inspect(x, { maxArrayLength: null }).endsWith('1 more item\\n]')\n      );\n      assert(\n        !util.inspect(x, { maxArrayLength: Infinity }).endsWith('1 more item ]')\n      );\n    }\n\n    {\n      const x = new Uint8Array(101);\n      assert(util.inspect(x).endsWith('1 more item\\n]'));\n      assert(!util.inspect(x, { maxArrayLength: 101 }).includes('1 more item'));\n      assert.strictEqual(\n        util.inspect(x, { maxArrayLength: 0 }),\n        'Uint8Array(101) [ ... 101 more items ]'\n      );\n      assert(\n        !util.inspect(x, { maxArrayLength: null }).includes('1 more item')\n      );\n      assert(\n        util.inspect(x, { maxArrayLength: Infinity }).endsWith(' 0, 0\\n]')\n      );\n    }\n\n    {\n      const obj = { foo: 'abc', bar: 'xyz' };\n      const oneLine = util.inspect(obj, { breakLength: Infinity });\n      // Subtract four for the object's two curly braces and two spaces of padding.\n      // Add one more to satisfy the strictly greater than condition in the code.\n      const breakpoint = oneLine.length - 5;\n      const twoLines = util.inspect(obj, { breakLength: breakpoint });\n\n      assert.strictEqual(oneLine, \"{ foo: 'abc', bar: 'xyz' }\");\n      assert.strictEqual(\n        util.inspect(obj, { breakLength: breakpoint + 1 }),\n        twoLines\n      );\n      assert.strictEqual(twoLines, \"{\\n  foo: 'abc',\\n  bar: 'xyz'\\n}\");\n    }\n\n    // util.inspect.defaultOptions tests.\n    {\n      const arr = Array.from({ length: 101 });\n      const obj = { a: { a: { a: { a: 1 } } } };\n\n      const oldOptions = { ...util.inspect.defaultOptions };\n\n      // Set single option through property assignment.\n      util.inspect.defaultOptions.maxArrayLength = null;\n      assert.doesNotMatch(util.inspect(arr), /1 more item/);\n      util.inspect.defaultOptions.maxArrayLength = oldOptions.maxArrayLength;\n      assert.match(util.inspect(arr), /1 more item/);\n      util.inspect.defaultOptions.depth = null;\n      assert.doesNotMatch(util.inspect(obj), /Object/);\n      util.inspect.defaultOptions.depth = oldOptions.depth;\n      assert.match(util.inspect(obj), /Object/);\n      assert.strictEqual(\n        JSON.stringify(util.inspect.defaultOptions),\n        JSON.stringify(oldOptions)\n      );\n\n      // Set multiple options through object assignment.\n      util.inspect.defaultOptions = { maxArrayLength: null, depth: 2 };\n      assert.doesNotMatch(util.inspect(arr), /1 more item/);\n      assert.match(util.inspect(obj), /Object/);\n      util.inspect.defaultOptions = oldOptions;\n      assert.match(util.inspect(arr), /1 more item/);\n      assert.match(util.inspect(obj), /Object/);\n      assert.strictEqual(\n        JSON.stringify(util.inspect.defaultOptions),\n        JSON.stringify(oldOptions)\n      );\n\n      assert.throws(\n        () => {\n          util.inspect.defaultOptions = null;\n        },\n        {\n          code: 'ERR_INVALID_ARG_TYPE',\n          name: 'TypeError',\n          message:\n            'The \"options\" argument must be of type object. ' + 'Received null',\n        }\n      );\n\n      assert.throws(\n        () => {\n          util.inspect.defaultOptions = 'bad';\n        },\n        {\n          code: 'ERR_INVALID_ARG_TYPE',\n          name: 'TypeError',\n          message:\n            'The \"options\" argument must be of type object. ' +\n            \"Received type string ('bad')\",\n        }\n      );\n    }\n\n    // Setting custom inspect property to a non-function should do nothing.\n    {\n      const obj = { [util.inspect.custom]: 'fhqwhgads' };\n      assert.strictEqual(\n        util.inspect(obj),\n        \"{ Symbol(nodejs.util.inspect.custom): 'fhqwhgads' }\"\n      );\n    }\n\n    {\n      // @@toStringTag\n      const obj = { [Symbol.toStringTag]: 'a' };\n      assert.strictEqual(\n        util.inspect(obj),\n        \"{ Symbol(Symbol.toStringTag): 'a' }\"\n      );\n      Object.defineProperty(obj, Symbol.toStringTag, {\n        value: 'a',\n        enumerable: false,\n      });\n      assert.strictEqual(util.inspect(obj), 'Object [a] {}');\n      assert.strictEqual(\n        util.inspect(obj, { showHidden: true }),\n        \"{ [Symbol(Symbol.toStringTag)]: 'a' }\"\n      );\n\n      class Foo {\n        constructor() {\n          this.foo = 'bar';\n        }\n\n        get [Symbol.toStringTag]() {\n          return this.foo;\n        }\n      }\n\n      assert.strictEqual(\n        util.inspect(\n          Object.create(null, { [Symbol.toStringTag]: { value: 'foo' } })\n        ),\n        '[Object: null prototype] [foo] {}'\n      );\n\n      assert.strictEqual(util.inspect(new Foo()), \"Foo [bar] { foo: 'bar' }\");\n\n      assert.strictEqual(\n        util.inspect(new (class extends Foo {})()),\n        \"Foo [bar] { foo: 'bar' }\"\n      );\n\n      assert.strictEqual(\n        util.inspect(\n          Object.create(\n            { __proto__: Foo.prototype },\n            {\n              foo: { value: 'bar', enumerable: true },\n            }\n          )\n        ),\n        \"Foo [bar] { foo: 'bar' }\"\n      );\n\n      class ThrowingClass {\n        get [Symbol.toStringTag]() {\n          throw new Error('toStringTag error');\n        }\n      }\n\n      assert.throws(\n        () => util.inspect(new ThrowingClass()),\n        /toStringTag error/\n      );\n\n      class NotStringClass {\n        get [Symbol.toStringTag]() {\n          return null;\n        }\n      }\n\n      assert.strictEqual(\n        util.inspect(new NotStringClass()),\n        'NotStringClass {}'\n      );\n    }\n\n    {\n      const o = {\n        a: [\n          1,\n          2,\n          [\n            [\n              'Lorem ipsum dolor\\nsit amet,\\tconsectetur adipiscing elit, sed do ' +\n                'eiusmod tempor incididunt ut labore et dolore magna aliqua.',\n              'test',\n              'foo',\n            ],\n          ],\n          4,\n        ],\n        b: new Map([\n          ['za', 1],\n          ['zb', 'test'],\n        ]),\n      };\n\n      let out = util.inspect(o, { compact: true, depth: 5, breakLength: 80 });\n      let expect = [\n        '{ a:',\n        '   [ 1,',\n        '     2,',\n        \"     [ [ 'Lorem ipsum dolor\\\\nsit amet,\\\\tconsectetur adipiscing elit, \" +\n          \"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',\",\n        \"         'test',\",\n        \"         'foo' ] ],\",\n        '     4 ],',\n        \"  b: Map(2) { 'za' => 1, 'zb' => 'test' } }\",\n      ].join('\\n');\n      assert.strictEqual(out, expect);\n\n      out = util.inspect(o, { compact: false, depth: 5, breakLength: 60 });\n      expect = [\n        '{',\n        '  a: [',\n        '    1,',\n        '    2,',\n        '    [',\n        '      [',\n        \"        'Lorem ipsum dolor\\\\n' +\",\n        \"          'sit amet,\\\\tconsectetur adipiscing elit, sed do eiusmod \" +\n          \"tempor incididunt ut labore et dolore magna aliqua.',\",\n        \"        'test',\",\n        \"        'foo'\",\n        '      ]',\n        '    ],',\n        '    4',\n        '  ],',\n        '  b: Map(2) {',\n        \"    'za' => 1,\",\n        \"    'zb' => 'test'\",\n        '  }',\n        '}',\n      ].join('\\n');\n      assert.strictEqual(out, expect);\n\n      out = util.inspect(o.a[2][0][0], { compact: false, breakLength: 30 });\n      expect = [\n        \"'Lorem ipsum dolor\\\\n' +\",\n        \"  'sit amet,\\\\tconsectetur adipiscing elit, sed do eiusmod tempor \" +\n          \"incididunt ut labore et dolore magna aliqua.'\",\n      ].join('\\n');\n      assert.strictEqual(out, expect);\n\n      out = util.inspect('12345678901234567890123456789012345678901234567890', {\n        compact: false,\n        breakLength: 3,\n      });\n      expect = \"'12345678901234567890123456789012345678901234567890'\";\n      assert.strictEqual(out, expect);\n\n      out = util.inspect(\n        '12 45 78 01 34 67 90 23 56 89 123456789012345678901234567890',\n        { compact: false, breakLength: 3 }\n      );\n      expect = [\n        \"'12 45 78 01 34 67 90 23 56 89 123456789012345678901234567890'\",\n      ].join('\\n');\n      assert.strictEqual(out, expect);\n\n      o.a = () => {};\n      o.b = new Number(3);\n      out = util.inspect(o, { compact: false, breakLength: 3 });\n      expect = [\n        '{',\n        '  a: [Function (anonymous)],',\n        '  b: [Number: 3]',\n        '}',\n      ].join('\\n');\n      assert.strictEqual(out, expect);\n\n      out = util.inspect(o, {\n        compact: false,\n        breakLength: 3,\n        showHidden: true,\n      });\n      expect = [\n        '{',\n        '  a: [Function (anonymous)] {',\n        '    [length]: 0,',\n        \"    [name]: ''\",\n        '  },',\n        '  b: [Number: 3]',\n        '}',\n      ].join('\\n');\n      assert.strictEqual(out, expect);\n\n      o[util.inspect.custom] = () => 42;\n      out = util.inspect(o, { compact: false, breakLength: 3 });\n      expect = '42';\n      assert.strictEqual(out, expect);\n\n      o[util.inspect.custom] = () => '12 45 78 01 34 67 90 23';\n      out = util.inspect(o, { compact: false, breakLength: 3 });\n      expect = '12 45 78 01 34 67 90 23';\n      assert.strictEqual(out, expect);\n\n      o[util.inspect.custom] = () => ({ a: '12 45 78 01 34 67 90 23' });\n      out = util.inspect(o, { compact: false, breakLength: 3 });\n      expect = \"{\\n  a: '12 45 78 01 34 67 90 23'\\n}\";\n      assert.strictEqual(out, expect);\n    }\n\n    // Check compact indentation.\n    {\n      const typed = new Uint8Array();\n      typed.buffer.foo = true;\n      const set = new Set([[1, 2]]);\n      const promise = Promise.resolve([[1, set]]);\n      const map = new Map([[promise, typed]]);\n      map.set(set.values(), map.values());\n\n      let out = util.inspect(map, {\n        compact: false,\n        showHidden: true,\n        depth: 9,\n      });\n      let expected = [\n        'Map(2) {',\n        '  Promise {',\n        '    [',\n        '      [',\n        '        1,',\n        '        Set(1) {',\n        '          [',\n        '            1,',\n        '            2,',\n        '            [length]: 2',\n        '          ]',\n        '        },',\n        '        [length]: 2',\n        '      ],',\n        '      [length]: 1',\n        '    ]',\n        '  } => Uint8Array(0) [',\n        '    [BYTES_PER_ELEMENT]: 1,',\n        '    [length]: 0,',\n        '    [byteLength]: 0,',\n        '    [byteOffset]: 0,',\n        '    [buffer]: ArrayBuffer {',\n        '      byteLength: 0,',\n        '      foo: true',\n        '    }',\n        '  ],',\n        '  [Set Iterator] {',\n        '    [',\n        '      1,',\n        '      2,',\n        '      [length]: 2',\n        '    ],',\n        \"    [Symbol(Symbol.toStringTag)]: 'Set Iterator'\",\n        '  } => <ref *1> [Map Iterator] {',\n        '    Uint8Array(0) [',\n        '      [BYTES_PER_ELEMENT]: 1,',\n        '      [length]: 0,',\n        '      [byteLength]: 0,',\n        '      [byteOffset]: 0,',\n        '      [buffer]: ArrayBuffer {',\n        '        byteLength: 0,',\n        '        foo: true',\n        '      }',\n        '    ],',\n        '    [Circular *1],',\n        \"    [Symbol(Symbol.toStringTag)]: 'Map Iterator'\",\n        '  }',\n        '}',\n      ].join('\\n');\n\n      assert.strict.equal(out, expected);\n\n      out = util.inspect(map, { compact: 2, showHidden: true, depth: 9 });\n\n      expected = [\n        'Map(2) {',\n        '  Promise {',\n        '    [',\n        '      [',\n        '        1,',\n        '        Set(1) { [ 1, 2, [length]: 2 ] },',\n        '        [length]: 2',\n        '      ],',\n        '      [length]: 1',\n        '    ]',\n        '  } => Uint8Array(0) [',\n        '    [BYTES_PER_ELEMENT]: 1,',\n        '    [length]: 0,',\n        '    [byteLength]: 0,',\n        '    [byteOffset]: 0,',\n        '    [buffer]: ArrayBuffer { byteLength: 0, foo: true }',\n        '  ],',\n        '  [Set Iterator] {',\n        '    [ 1, 2, [length]: 2 ],',\n        \"    [Symbol(Symbol.toStringTag)]: 'Set Iterator'\",\n        '  } => <ref *1> [Map Iterator] {',\n        '    Uint8Array(0) [',\n        '      [BYTES_PER_ELEMENT]: 1,',\n        '      [length]: 0,',\n        '      [byteLength]: 0,',\n        '      [byteOffset]: 0,',\n        '      [buffer]: ArrayBuffer { byteLength: 0, foo: true }',\n        '    ],',\n        '    [Circular *1],',\n        \"    [Symbol(Symbol.toStringTag)]: 'Map Iterator'\",\n        '  }',\n        '}',\n      ].join('\\n');\n\n      assert.strict.equal(out, expected);\n\n      out = util.inspect(map, {\n        showHidden: true,\n        depth: 9,\n        breakLength: 4,\n        compact: true,\n      });\n      expected = [\n        'Map(2) {',\n        '  Promise {',\n        '    [ [ 1,',\n        '        Set(1) {',\n        '          [ 1,',\n        '            2,',\n        '            [length]: 2 ] },',\n        '        [length]: 2 ],',\n        '      [length]: 1 ] } => Uint8Array(0) [',\n        '    [BYTES_PER_ELEMENT]: 1,',\n        '    [length]: 0,',\n        '    [byteLength]: 0,',\n        '    [byteOffset]: 0,',\n        '    [buffer]: ArrayBuffer {',\n        '      byteLength: 0,',\n        '      foo: true } ],',\n        '  [Set Iterator] {',\n        '    [ 1,',\n        '      2,',\n        '      [length]: 2 ],',\n        '    [Symbol(Symbol.toStringTag)]:',\n        \"     'Set Iterator' } => <ref *1> [Map Iterator] {\",\n        '    Uint8Array(0) [',\n        '      [BYTES_PER_ELEMENT]: 1,',\n        '      [length]: 0,',\n        '      [byteLength]: 0,',\n        '      [byteOffset]: 0,',\n        '      [buffer]: ArrayBuffer {',\n        '        byteLength: 0,',\n        '        foo: true } ],',\n        '    [Circular *1],',\n        '    [Symbol(Symbol.toStringTag)]:',\n        \"     'Map Iterator' } }\",\n      ].join('\\n');\n\n      assert.strict.equal(out, expected);\n    }\n\n    {\n      // Test WeakMap && WeakSet\n      const obj = {};\n      const arr = [];\n      const weakMap = new WeakMap([\n        [obj, arr],\n        [arr, obj],\n      ]);\n      let out = util.inspect(weakMap, { showHidden: true });\n      let expect = 'WeakMap { [ [length]: 0 ] => {}, {} => [ [length]: 0 ] }';\n      assert.strictEqual(out, expect);\n\n      out = util.inspect(weakMap);\n      expect = 'WeakMap { <items unknown> }';\n      assert.strictEqual(out, expect);\n\n      out = util.inspect(weakMap, { maxArrayLength: 0, showHidden: true });\n      expect = 'WeakMap { ... 2 more items }';\n      assert.strictEqual(out, expect);\n\n      weakMap.extra = true;\n      out = util.inspect(weakMap, { maxArrayLength: 1, showHidden: true });\n      // It is not possible to determine the output reliable.\n      expect =\n        'WeakMap { [ [length]: 0 ] => {}, ... 1 more item, extra: true }';\n      let expectAlt =\n        'WeakMap { {} => [ [length]: 0 ], ... 1 more item, ' + 'extra: true }';\n      assert(\n        out === expect || out === expectAlt,\n        `Found: \"${out}\"\\nrather than: \"${expect}\"\\nor: \"${expectAlt}\"`\n      );\n\n      // Test WeakSet\n      arr.push(1);\n      const weakSet = new WeakSet([obj, arr]);\n      out = util.inspect(weakSet, { showHidden: true });\n      expect = 'WeakSet { [ 1, [length]: 1 ], {} }';\n      assert.strictEqual(out, expect);\n\n      out = util.inspect(weakSet);\n      expect = 'WeakSet { <items unknown> }';\n      assert.strictEqual(out, expect);\n\n      out = util.inspect(weakSet, { maxArrayLength: -2, showHidden: true });\n      expect = 'WeakSet { ... 2 more items }';\n      assert.strictEqual(out, expect);\n\n      weakSet.extra = true;\n      out = util.inspect(weakSet, { maxArrayLength: 1, showHidden: true });\n      // It is not possible to determine the output reliable.\n      expect = 'WeakSet { {}, ... 1 more item, extra: true }';\n      expectAlt =\n        'WeakSet { [ 1, [length]: 1 ], ... 1 more item, extra: true }';\n      assert(\n        out === expect || out === expectAlt,\n        `Found: \"${out}\"\\nrather than: \"${expect}\"\\nor: \"${expectAlt}\"`\n      );\n      // Keep references to the WeakMap entries, otherwise they could be GC'ed too\n      // early.\n      assert(obj && arr);\n    }\n\n    {\n      // Test argument objects.\n      const args = (function () {\n        return arguments;\n      })('a');\n      assert.strictEqual(util.inspect(args), \"[Arguments] { '0': 'a' }\");\n    }\n\n    {\n      // Test that a long linked list can be inspected without throwing an error.\n      const list = {};\n      let head = list;\n      // A linked list of length 100k should be inspectable in some way, even though\n      // the real cutoff value is much lower than 100k.\n      for (let i = 0; i < 100000; i++) head = head.next = {};\n      assert.strictEqual(\n        util.inspect(list),\n        '{ next: { next: { next: [Object] } } }'\n      );\n      const longList = util.inspect(list, { depth: Infinity });\n      const match = longList.match(/next/g);\n      assert(match.length > 500 && match.length < 10000);\n      assert(\n        longList.includes(\n          '[Object: Inspection interrupted ' +\n            'prematurely. Maximum call stack size exceeded.]'\n        )\n      );\n    }\n\n    // Do not escape single quotes if no double quote or backtick is present.\n    assert.strictEqual(util.inspect(\"'\"), '\"\\'\"');\n    assert.strictEqual(util.inspect('\"\\''), '`\"\\'`');\n    // eslint-disable-next-line no-template-curly-in-string\n    assert.strictEqual(util.inspect('\"\\'${a}'), \"'\\\"\\\\'${a}'\");\n\n    // Errors should visualize as much information as possible.\n    // If the name is not included in the stack, visualize it as well.\n    [\n      [class Foo extends TypeError {}, 'test'],\n      [class Foo extends TypeError {}, undefined],\n      [class BarError extends Error {}, 'test'],\n      [\n        class BazError extends Error {\n          get name() {\n            return 'BazError';\n          }\n        },\n        undefined,\n      ],\n    ].forEach(([Class, message], i) => {\n      console.log('Test %i', i);\n      const foo = new Class(message);\n      const name = foo.name;\n      const extra = Class.name.includes('Error') ? '' : ` [${foo.name}]`;\n      assert(\n        util\n          .inspect(foo)\n          .startsWith(\n            `${Class.name}${extra}${message ? `: ${message}` : '\\n'}`\n          ),\n        util.inspect(foo)\n      );\n      Object.defineProperty(foo, Symbol.toStringTag, {\n        value: 'WOW',\n        writable: true,\n        configurable: true,\n      });\n      const stack = foo.stack;\n      foo.stack = 'This is a stack';\n      assert.strictEqual(util.inspect(foo), '[This is a stack]');\n      foo.stack = stack;\n      assert(\n        util\n          .inspect(foo)\n          .startsWith(\n            `${Class.name} [WOW]${extra}${message ? `: ${message}` : '\\n'}`\n          ),\n        util.inspect(foo)\n      );\n      Object.setPrototypeOf(foo, null);\n      assert(\n        util\n          .inspect(foo)\n          .startsWith(\n            `[${name}: null prototype] [WOW]${message ? `: ${message}` : '\\n'}`\n          ),\n        util.inspect(foo)\n      );\n      foo.bar = true;\n      delete foo[Symbol.toStringTag];\n      assert(\n        util\n          .inspect(foo)\n          .startsWith(\n            `[${name}: null prototype]${message ? `: ${message}` : '\\n'}`\n          ),\n        util.inspect(foo)\n      );\n      foo.stack = 'This is a stack';\n      assert.strictEqual(\n        util.inspect(foo),\n        '[[Error: null prototype]: This is a stack] { bar: true }'\n      );\n      foo.stack = stack.split('\\n')[0];\n      assert.strictEqual(\n        util.inspect(foo),\n        `[[${name}: null prototype]${message ? `: ${message}` : ''}] { bar: true }`\n      );\n    });\n\n    // Verify that classes are properly inspected.\n    [\n      /* eslint-disable spaced-comment, no-multi-spaces, brace-style */\n      // The whitespace is intentional.\n      [class {}, '[class (anonymous)]'],\n      [\n        class extends Error {\n          log() {}\n        },\n        '[class (anonymous) extends Error]',\n      ],\n      [\n        class A {\n          constructor(a) {\n            this.a = a;\n          }\n          log() {\n            return this.a;\n          }\n        },\n        '[class A]',\n      ],\n      [\n        class // Random { // comments /* */ are part of the toString() result\n        /* eslint-disable-next-line space-before-blocks */\n        äß /**/\n          extends /*{*/ TypeError {},\n        '[class äß extends TypeError]',\n      ],\n      /* The whitespace and new line is intended! */\n      // Foobar !!!\n      [\n        class X extends /****/ Error {\n          // More comments\n        },\n        '[class X extends Error]',\n      ],\n      /* eslint-enable spaced-comment, no-multi-spaces, brace-style */\n    ].forEach(([clazz, string]) => {\n      const inspected = util.inspect(clazz);\n      assert.strictEqual(inspected, string);\n      Object.defineProperty(clazz, Symbol.toStringTag, {\n        value: 'Woohoo',\n      });\n      const parts = inspected.slice(0, -1).split(' ');\n      const [, name, ...rest] = parts;\n      rest.unshift('[Woohoo]');\n      if (rest.length) {\n        rest[rest.length - 1] += ']';\n      }\n      assert.strictEqual(\n        util.inspect(clazz),\n        ['[class', name, ...rest].join(' ')\n      );\n      if (rest.length) {\n        rest[rest.length - 1] = rest[rest.length - 1].slice(0, -1);\n        rest.length = 1;\n      }\n      Object.setPrototypeOf(clazz, Map.prototype);\n      assert.strictEqual(\n        util.inspect(clazz),\n        ['[class', name, '[Map]', ...rest].join(' ') + ']'\n      );\n      Object.setPrototypeOf(clazz, null);\n      assert.strictEqual(\n        util.inspect(clazz),\n        ['[class', name, ...rest, 'extends [null prototype]]'].join(' ')\n      );\n      Object.defineProperty(clazz, 'name', { value: 'Foo' });\n      const res = ['[class', 'Foo', ...rest, 'extends [null prototype]]'].join(\n        ' '\n      );\n      assert.strictEqual(util.inspect(clazz), res);\n      clazz.foo = true;\n      assert.strictEqual(util.inspect(clazz), `${res} { foo: true }`);\n    });\n\n    // \"class\" properties should not be detected as \"class\".\n    {\n      // eslint-disable-next-line space-before-function-paren\n      let obj = { class() {} };\n      assert.strictEqual(util.inspect(obj), '{ class: [Function: class] }');\n      obj = { class: () => {} };\n      assert.strictEqual(util.inspect(obj), '{ class: [Function: class] }');\n      obj = { ['class Foo {}']() {} };\n      assert.strictEqual(\n        util.inspect(obj),\n        \"{ 'class Foo {}': [Function: class Foo {}] }\"\n      );\n      function Foo() {}\n      Object.defineProperty(Foo, 'toString', { value: () => 'class Foo {}' });\n      assert.strictEqual(util.inspect(Foo), '[Function: Foo]');\n      function fn() {}\n      Object.defineProperty(fn, 'name', { value: 'class Foo {}' });\n      assert.strictEqual(util.inspect(fn), '[Function: class Foo {}]');\n    }\n\n    // Verify that throwing in valueOf and toString still produces nice results.\n    [\n      [new String(55), \"[String: '55']\"],\n      [new Boolean(true), '[Boolean: true]'],\n      [new Number(55), '[Number: 55]'],\n      [Object(BigInt(55)), '[BigInt: 55n]'],\n      [Object(Symbol('foo')), '[Symbol: Symbol(foo)]'],\n      [function () {}, '[Function (anonymous)]'],\n      [() => {}, '[Function (anonymous)]'],\n      [[1, 2], '[ 1, 2 ]'],\n      // eslint-disable-next-line no-sparse-arrays\n      [[, , 5, , , ,], '[ <2 empty items>, 5, <3 empty items> ]'],\n      [{ a: 5 }, '{ a: 5 }'],\n      [new Set([1, 2]), 'Set(2) { 1, 2 }'],\n      [new Map([[1, 2]]), 'Map(1) { 1 => 2 }'],\n      [new Set([1, 2]).entries(), '[Set Entries] { [ 1, 1 ], [ 2, 2 ] }'],\n      [new Map([[1, 2]]).keys(), '[Map Iterator] { 1 }'],\n      [new Date(2000), '1970-01-01T00:00:02.000Z'],\n      [new Uint8Array(2), 'Uint8Array(2) [ 0, 0 ]'],\n      [\n        new Promise((resolve) => setTimeout(resolve, 10)),\n        'Promise { <pending> }',\n      ],\n      [new WeakSet(), 'WeakSet { <items unknown> }'],\n      [new WeakMap(), 'WeakMap { <items unknown> }'],\n      [/foobar/g, '/foobar/g'],\n    ].forEach(([value, expected]) => {\n      Object.defineProperty(value, 'valueOf', {\n        get() {\n          throw new Error('valueOf');\n        },\n      });\n      Object.defineProperty(value, 'toString', {\n        get() {\n          throw new Error('toString');\n        },\n      });\n      assert.strictEqual(util.inspect(value), expected);\n      value.foo = 'bar';\n      assert.notStrictEqual(util.inspect(value), expected);\n      delete value.foo;\n      value[Symbol('foo')] = 'yeah';\n      assert.notStrictEqual(util.inspect(value), expected);\n    });\n\n    // Verify that having no prototype still produces nice results.\n    [\n      [[1, 3, 4], '[Array(3): null prototype] [ 1, 3, 4 ]'],\n      [new Set([1, 2]), '[Set(2): null prototype] { 1, 2 }'],\n      [new Map([[1, 2]]), '[Map(1): null prototype] { 1 => 2 }'],\n      [\n        new Promise((resolve) => setTimeout(resolve, 10)),\n        '[Promise: null prototype] { <pending> }',\n      ],\n      [new WeakSet(), '[WeakSet: null prototype] { <items unknown> }'],\n      [new WeakMap(), '[WeakMap: null prototype] { <items unknown> }'],\n      [new Uint8Array(2), '[Uint8Array(2): null prototype] [ 0, 0 ]'],\n      [new Uint16Array(2), '[Uint16Array(2): null prototype] [ 0, 0 ]'],\n      [new Uint32Array(2), '[Uint32Array(2): null prototype] [ 0, 0 ]'],\n      [new Int8Array(2), '[Int8Array(2): null prototype] [ 0, 0 ]'],\n      [new Int16Array(2), '[Int16Array(2): null prototype] [ 0, 0 ]'],\n      [new Int32Array(2), '[Int32Array(2): null prototype] [ 0, 0 ]'],\n      [new Float16Array(2), '[Float16Array(2): null prototype] [ 0, 0 ]'],\n      [new Float32Array(2), '[Float32Array(2): null prototype] [ 0, 0 ]'],\n      [new Float64Array(2), '[Float64Array(2): null prototype] [ 0, 0 ]'],\n      [new BigInt64Array(2), '[BigInt64Array(2): null prototype] [ 0n, 0n ]'],\n      [new BigUint64Array(2), '[BigUint64Array(2): null prototype] [ 0n, 0n ]'],\n      [\n        new ArrayBuffer(16),\n        '[ArrayBuffer: null prototype] {\\n' +\n          '  [Uint8Contents]: <00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>,\\n' +\n          '  byteLength: undefined\\n}',\n      ],\n      [\n        new DataView(new ArrayBuffer(16)),\n        '[DataView: null prototype] {\\n  byteLength: undefined,\\n  ' +\n          'byteOffset: undefined,\\n  buffer: undefined\\n}',\n      ],\n      [\n        new SharedArrayBuffer(2),\n        '[SharedArrayBuffer: null prototype] ' +\n          '{\\n  [Uint8Contents]: <00 00>,\\n  byteLength: undefined\\n}',\n      ],\n      [/foobar/, '[RegExp: null prototype] /foobar/'],\n      [\n        new Date('Sun, 14 Feb 2010 11:48:40 GMT'),\n        '[Date: null prototype] 2010-02-14T11:48:40.000Z',\n      ],\n    ].forEach(([value, expected]) => {\n      assert.strictEqual(\n        util.inspect(Object.setPrototypeOf(value, null)),\n        expected\n      );\n      value.foo = 'bar';\n      assert.notStrictEqual(util.inspect(value), expected);\n      delete value.foo;\n      value[Symbol('foo')] = 'yeah';\n      assert.notStrictEqual(util.inspect(value), expected);\n    });\n\n    // Verify that subclasses with and without prototype produce nice results.\n    [\n      [RegExp, ['foobar', 'g'], '/foobar/g'],\n      [WeakSet, [[{}]], '{ <items unknown> }'],\n      [WeakMap, [[[{}, {}]]], '{ <items unknown> }'],\n      [\n        BigInt64Array,\n        [10],\n        '[\\n  0n, 0n, 0n, 0n, 0n,\\n  0n, 0n, 0n, 0n, 0n\\n]',\n      ],\n      [Date, ['Sun, 14 Feb 2010 11:48:40 GMT'], '2010-02-14T11:48:40.000Z'],\n      [Date, ['invalid_date'], 'Invalid Date'],\n    ].forEach(([base, input, rawExpected]) => {\n      class Foo extends base {}\n      const value = new Foo(...input);\n      const symbol = value[Symbol.toStringTag];\n      const size = base.name.includes('Array') ? `(${input[0]})` : '';\n      const expected = `Foo${size} ${symbol ? `[${symbol}] ` : ''}${rawExpected}`;\n      const expectedWithoutProto = `[${base.name}${size}: null prototype] ${rawExpected}`;\n      assert.strictEqual(util.inspect(value), expected);\n      value.foo = 'bar';\n      assert.notStrictEqual(util.inspect(value), expected);\n      delete value.foo;\n      assert.strictEqual(\n        util.inspect(Object.setPrototypeOf(value, null)),\n        expectedWithoutProto\n      );\n      value.foo = 'bar';\n      let res = util.inspect(value);\n      assert.notStrictEqual(res, expectedWithoutProto);\n      assert.match(res, /foo: 'bar'/);\n      delete value.foo;\n      value[Symbol('foo')] = 'yeah';\n      res = util.inspect(value);\n      assert.notStrictEqual(res, expectedWithoutProto);\n      assert.match(res, /Symbol\\(foo\\): 'yeah'/);\n    });\n\n    assert.strictEqual(inspect(1n), '1n');\n    assert.strictEqual(inspect(Object(-1n)), '[BigInt: -1n]');\n    assert.strictEqual(inspect(Object(13n)), '[BigInt: 13n]');\n    assert.strictEqual(\n      inspect(new BigInt64Array([0n])),\n      'BigInt64Array(1) [ 0n ]'\n    );\n    assert.strictEqual(\n      inspect(new BigUint64Array([0n])),\n      'BigUint64Array(1) [ 0n ]'\n    );\n\n    // Verify non-enumerable keys get escaped.\n    {\n      const obj = {};\n      Object.defineProperty(obj, 'Non\\nenumerable\\tkey', { value: true });\n      assert.strictEqual(\n        util.inspect(obj, { showHidden: true }),\n        \"{ ['Non\\\\nenumerable\\\\tkey']: true }\"\n      );\n    }\n\n    // Check for special colors.\n    {\n      const special = inspect.colors[inspect.styles.special];\n      const string = inspect.colors[inspect.styles.string];\n\n      assert.strictEqual(\n        inspect(new WeakSet(), { colors: true }),\n        `WeakSet { \\u001b[${special[0]}m<items unknown>\\u001b[${special[1]}m }`\n      );\n      assert.strictEqual(\n        inspect(new WeakMap(), { colors: true }),\n        `WeakMap { \\u001b[${special[0]}m<items unknown>\\u001b[${special[1]}m }`\n      );\n      assert.strictEqual(\n        inspect(new Promise(() => {}), { colors: true }),\n        `Promise { \\u001b[${special[0]}m<pending>\\u001b[${special[1]}m }`\n      );\n\n      const rejection = Promise.reject('Oh no!');\n      assert.strictEqual(\n        inspect(rejection, { colors: true }),\n        `Promise { \\u001b[${special[0]}m<rejected>\\u001b[${special[1]}m ` +\n          `\\u001b[${string[0]}m'Oh no!'\\u001b[${string[1]}m }`\n      );\n      rejection.catch(() => {});\n\n      // Verify that aliases do not show up as key while checking `inspect.colors`.\n      const colors = Object.keys(inspect.colors);\n      const aliases = Object.getOwnPropertyNames(inspect.colors).filter(\n        (c) => !colors.includes(c)\n      );\n      assert(!colors.includes('grey'));\n      assert(colors.includes('gray'));\n      // Verify that all aliases are correctly mapped.\n      for (const alias of aliases) {\n        assert(Array.isArray(inspect.colors[alias]));\n      }\n      // Check consistent naming.\n      [\n        'black',\n        'red',\n        'green',\n        'yellow',\n        'blue',\n        'magenta',\n        'cyan',\n        'white',\n      ].forEach((color, i) => {\n        assert.deepStrictEqual(inspect.colors[color], [30 + i, 39]);\n        assert.deepStrictEqual(inspect.colors[`${color}Bright`], [90 + i, 39]);\n        const bgColor = `bg${color[0].toUpperCase()}${color.slice(1)}`;\n        assert.deepStrictEqual(inspect.colors[bgColor], [40 + i, 49]);\n        assert.deepStrictEqual(inspect.colors[`${bgColor}Bright`], [\n          100 + i,\n          49,\n        ]);\n      });\n\n      // Unknown colors are handled gracefully:\n      const stringStyle = inspect.styles.string;\n      inspect.styles.string = 'UNKNOWN';\n      assert.strictEqual(inspect('foobar', { colors: true }), \"'foobar'\");\n      inspect.styles.string = stringStyle;\n    }\n\n    assert.strictEqual(\n      inspect([1, 3, 2], { sorted: true }),\n      inspect([1, 3, 2])\n    );\n    assert.strictEqual(\n      inspect({ c: 3, a: 1, b: 2 }, { sorted: true }),\n      '{ a: 1, b: 2, c: 3 }'\n    );\n    assert.strictEqual(\n      inspect(\n        { a200: 4, a100: 1, a102: 3, a101: 2 },\n        {\n          sorted(a, b) {\n            return b.localeCompare(a);\n          },\n        }\n      ),\n      '{ a200: 4, a102: 3, a101: 2, a100: 1 }'\n    );\n\n    // Non-indices array properties are sorted as well.\n    {\n      const arr = [3, 2, 1];\n      arr.b = 2;\n      arr.c = 3;\n      arr.a = 1;\n      arr[Symbol('b')] = true;\n      arr[Symbol('a')] = false;\n      assert.strictEqual(\n        inspect(arr, { sorted: true }),\n        '[ 3, 2, 1, Symbol(a): false, Symbol(b): true, a: 1, b: 2, c: 3 ]'\n      );\n    }\n\n    // Manipulate the prototype in weird ways.\n    {\n      let obj = { a: true };\n      let value = (function () {\n        return function () {};\n      })();\n      Object.setPrototypeOf(value, null);\n      Object.setPrototypeOf(obj, value);\n      assert.strictEqual(\n        util.inspect(obj),\n        'Object <[Function (null prototype) (anonymous)]> { a: true }'\n      );\n      assert.strictEqual(\n        util.inspect(obj, { colors: true }),\n        'Object <\\u001b[36m[Function (null prototype) (anonymous)]\\u001b[39m> ' +\n          '{ a: \\u001b[33mtrue\\u001b[39m }'\n      );\n\n      obj = { a: true };\n      value = [];\n      Object.setPrototypeOf(value, null);\n      Object.setPrototypeOf(obj, value);\n      assert.strictEqual(\n        util.inspect(obj),\n        'Object <[Array(0): null prototype] []> { a: true }'\n      );\n\n      function StorageObject() {}\n      StorageObject.prototype = { __proto__: null };\n      assert.strictEqual(\n        util.inspect(new StorageObject()),\n        'StorageObject <[Object: null prototype] {}> {}'\n      );\n\n      obj = [1, 2, 3];\n      Object.setPrototypeOf(obj, Number.prototype);\n      assert.strictEqual(inspect(obj), \"Number { '0': 1, '1': 2, '2': 3 }\");\n\n      Object.setPrototypeOf(obj, { __proto__: null });\n      assert.strictEqual(\n        inspect(obj),\n        \"Array <[Object: null prototype] {}> { '0': 1, '1': 2, '2': 3 }\"\n      );\n\n      StorageObject.prototype = { __proto__: null };\n      Object.setPrototypeOf(StorageObject.prototype, { __proto__: null });\n      Object.setPrototypeOf(Object.getPrototypeOf(StorageObject.prototype), {\n        __proto__: null,\n      });\n      assert.strictEqual(\n        util.inspect(new StorageObject()),\n        'StorageObject <Object <Object <[Object: null prototype] {}>>> {}'\n      );\n      assert.strictEqual(\n        util.inspect(new StorageObject(), { depth: 1 }),\n        'StorageObject <Object <Object <Complex prototype>>> {}'\n      );\n    }\n\n    // Check that the fallback always works.\n    {\n      const obj = new Set([1, 2]);\n      const iterator = obj[Symbol.iterator];\n      Object.setPrototypeOf(obj, null);\n      Object.defineProperty(obj, Symbol.iterator, {\n        value: iterator,\n        configurable: true,\n      });\n      assert.strictEqual(\n        util.inspect(obj),\n        '[Set(2): null prototype] { 1, 2 }'\n      );\n      Object.defineProperty(obj, Symbol.iterator, {\n        value: true,\n        configurable: true,\n      });\n      Object.defineProperty(obj, 'size', {\n        value: NaN,\n        configurable: true,\n        enumerable: true,\n      });\n      assert.strictEqual(\n        util.inspect(obj),\n        '[Set(2): null prototype] { 1, 2, size: NaN }'\n      );\n    }\n\n    // Check the getter option.\n    {\n      let foo = 1;\n      const get = {\n        get foo() {\n          return foo;\n        },\n      };\n      const getset = {\n        get foo() {\n          return foo;\n        },\n        set foo(val) {\n          foo = val;\n        },\n        get inc() {\n          return ++foo;\n        },\n      };\n      const thrower = {\n        get foo() {\n          throw new Error('Oops');\n        },\n      };\n      assert.strictEqual(\n        inspect(get, { getters: true, colors: true }),\n        '{ foo: \\u001b[36m[Getter:\\u001b[39m ' +\n          '\\u001b[33m1\\u001b[39m\\u001b[36m]\\u001b[39m }'\n      );\n      assert.strictEqual(\n        inspect(thrower, { getters: true }),\n        '{ foo: [Getter: <Inspection threw (Oops)>] }'\n      );\n      assert.strictEqual(\n        inspect(getset, { getters: true }),\n        '{ foo: [Getter/Setter: 1], inc: [Getter: 2] }'\n      );\n      assert.strictEqual(\n        inspect(getset, { getters: 'get' }),\n        '{ foo: [Getter/Setter], inc: [Getter: 3] }'\n      );\n      assert.strictEqual(\n        inspect(getset, { getters: 'set' }),\n        '{ foo: [Getter/Setter: 3], inc: [Getter] }'\n      );\n      getset.foo = new Set([[{ a: true }, 2, {}], 'foobar', { x: 1 }]);\n      assert.strictEqual(\n        inspect(getset, { getters: true }),\n        '{\\n  foo: [Getter/Setter] Set(3) { [ [Object], 2, {} ], ' +\n          \"'foobar', { x: 1 } },\\n  inc: [Getter: NaN]\\n}\"\n      );\n    }\n\n    // Check compact number mode.\n    {\n      let obj = {\n        a: {\n          b: {\n            x: 5,\n            c: {\n              x: '10000000000000000 00000000000000000 '.repeat(1e1),\n              d: 2,\n              e: 3,\n            },\n          },\n        },\n        b: [1, 2, [1, 2, { a: 1, b: 2, c: 3 }]],\n        c: ['foo', 4, 444444],\n        d: Array.from({ length: 101 }, (_e, i) => {\n          return i % 2 === 0 ? i * i : i;\n        }),\n        e: Array.from({ length: 6 }, () => 'foobar'),\n        f: Array.from({ length: 9 }, () => 'foobar'),\n        g: Array.from({ length: 21 }, () => 'foobar baz'),\n        h: [100].concat(Array.from({ length: 9 }, (_e, n) => n)),\n        long: Array.from(\n          { length: 9 },\n          () => 'This text is too long for grouping!'\n        ),\n      };\n\n      let out = util.inspect(obj, { compact: 3, depth: 10, breakLength: 60 });\n      let expected = [\n        '{',\n        '  a: {',\n        '    b: {',\n        '      x: 5,',\n        '      c: {',\n        \"        x: '10000000000000000 00000000000000000 10000000000000000 \" +\n          '00000000000000000 10000000000000000 00000000000000000 ' +\n          '10000000000000000 00000000000000000 10000000000000000 ' +\n          '00000000000000000 10000000000000000 00000000000000000 ' +\n          '10000000000000000 00000000000000000 10000000000000000 ' +\n          '00000000000000000 10000000000000000 00000000000000000 ' +\n          \"10000000000000000 00000000000000000 ',\",\n        '        d: 2,',\n        '        e: 3',\n        '      }',\n        '    }',\n        '  },',\n        '  b: [ 1, 2, [ 1, 2, { a: 1, b: 2, c: 3 } ] ],',\n        \"  c: [ 'foo', 4, 444444 ],\",\n        '  d: [',\n        '       0,    1,    4,    3,   16,    5,   36,    7,   64,',\n        '       9,  100,   11,  144,   13,  196,   15,  256,   17,',\n        '     324,   19,  400,   21,  484,   23,  576,   25,  676,',\n        '      27,  784,   29,  900,   31, 1024,   33, 1156,   35,',\n        '    1296,   37, 1444,   39, 1600,   41, 1764,   43, 1936,',\n        '      45, 2116,   47, 2304,   49, 2500,   51, 2704,   53,',\n        '    2916,   55, 3136,   57, 3364,   59, 3600,   61, 3844,',\n        '      63, 4096,   65, 4356,   67, 4624,   69, 4900,   71,',\n        '    5184,   73, 5476,   75, 5776,   77, 6084,   79, 6400,',\n        '      81, 6724,   83, 7056,   85, 7396,   87, 7744,   89,',\n        '    8100,   91, 8464,   93, 8836,   95, 9216,   97, 9604,',\n        '      99,',\n        '    ... 1 more item',\n        '  ],',\n        '  e: [',\n        \"    'foobar',\",\n        \"    'foobar',\",\n        \"    'foobar',\",\n        \"    'foobar',\",\n        \"    'foobar',\",\n        \"    'foobar'\",\n        '  ],',\n        '  f: [',\n        \"    'foobar', 'foobar',\",\n        \"    'foobar', 'foobar',\",\n        \"    'foobar', 'foobar',\",\n        \"    'foobar', 'foobar',\",\n        \"    'foobar'\",\n        '  ],',\n        '  g: [',\n        \"    'foobar baz', 'foobar baz',\",\n        \"    'foobar baz', 'foobar baz',\",\n        \"    'foobar baz', 'foobar baz',\",\n        \"    'foobar baz', 'foobar baz',\",\n        \"    'foobar baz', 'foobar baz',\",\n        \"    'foobar baz', 'foobar baz',\",\n        \"    'foobar baz', 'foobar baz',\",\n        \"    'foobar baz', 'foobar baz',\",\n        \"    'foobar baz', 'foobar baz',\",\n        \"    'foobar baz', 'foobar baz',\",\n        \"    'foobar baz'\",\n        '  ],',\n        '  h: [',\n        '    100, 0, 1, 2, 3,',\n        '      4, 5, 6, 7, 8',\n        '  ],',\n        '  long: [',\n        \"    'This text is too long for grouping!',\",\n        \"    'This text is too long for grouping!',\",\n        \"    'This text is too long for grouping!',\",\n        \"    'This text is too long for grouping!',\",\n        \"    'This text is too long for grouping!',\",\n        \"    'This text is too long for grouping!',\",\n        \"    'This text is too long for grouping!',\",\n        \"    'This text is too long for grouping!',\",\n        \"    'This text is too long for grouping!'\",\n        '  ]',\n        '}',\n      ].join('\\n');\n\n      assert.strictEqual(out, expected);\n\n      obj = [\n        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n        1, 1, 123456789,\n      ];\n\n      out = util.inspect(obj, { compact: 3 });\n\n      expected = [\n        '[',\n        '  1, 1,         1, 1,',\n        '  1, 1,         1, 1,',\n        '  1, 1,         1, 1,',\n        '  1, 1,         1, 1,',\n        '  1, 1,         1, 1,',\n        '  1, 1,         1, 1,',\n        '  1, 1, 123456789',\n        ']',\n      ].join('\\n');\n\n      assert.strictEqual(out, expected);\n\n      // Unicode support. あ has a length of one and a width of two.\n      obj = [\n        '123',\n        '123',\n        '123',\n        '123',\n        'あああ',\n        '123',\n        '123',\n        '123',\n        '123',\n        'あああ',\n      ];\n\n      out = util.inspect(obj, { compact: 3 });\n\n      expected = [\n        '[',\n        \"  '123',    '123',\",\n        \"  '123',    '123',\",\n        \"  'あああ', '123',\",\n        \"  '123',    '123',\",\n        \"  '123',    'あああ'\",\n        ']',\n      ].join('\\n');\n\n      assert.strictEqual(out, expected);\n\n      // Array grouping should prevent lining up outer elements on a single line.\n      obj = [[[1, 2, 3, 4, 5, 6, 7, 8, 9]]];\n\n      out = util.inspect(obj, { compact: 3 });\n\n      expected = [\n        '[',\n        '  [',\n        '    [',\n        '      1, 2, 3, 4, 5,',\n        '      6, 7, 8, 9',\n        '    ]',\n        '  ]',\n        ']',\n      ].join('\\n');\n\n      assert.strictEqual(out, expected);\n\n      // Verify that array grouping and line consolidation does not happen together.\n      obj = {\n        a: {\n          b: {\n            x: 5,\n            c: {\n              d: 2,\n              e: 3,\n            },\n          },\n        },\n        b: Array.from({ length: 9 }, (e, n) => {\n          return n % 2 === 0 ? 'foobar' : 'baz';\n        }),\n      };\n\n      out = util.inspect(obj, {\n        compact: 1,\n        breakLength: Infinity,\n        colors: true,\n      });\n\n      expected = [\n        '{',\n        '  a: {',\n        '    b: { x: \\u001b[33m5\\u001b[39m, c: \\u001b[36m[Object]\\u001b[39m }',\n        '  },',\n        '  b: [',\n        \"    \\u001b[32m'foobar'\\u001b[39m, \\u001b[32m'baz'\\u001b[39m,\",\n        \"    \\u001b[32m'foobar'\\u001b[39m, \\u001b[32m'baz'\\u001b[39m,\",\n        \"    \\u001b[32m'foobar'\\u001b[39m, \\u001b[32m'baz'\\u001b[39m,\",\n        \"    \\u001b[32m'foobar'\\u001b[39m, \\u001b[32m'baz'\\u001b[39m,\",\n        \"    \\u001b[32m'foobar'\\u001b[39m\",\n        '  ]',\n        '}',\n      ].join('\\n');\n\n      assert.strictEqual(out, expected);\n\n      obj = Array.from({ length: 60 }, (_e, i) => i);\n      out = util.inspect(obj, {\n        compact: 1,\n        breakLength: Infinity,\n        colors: true,\n      });\n\n      expected = [\n        '[',\n        '   \\u001b[33m0\\u001b[39m,  \\u001b[33m1\\u001b[39m,  \\u001b[33m2\\u001b[39m,  \\u001b[33m3\\u001b[39m,',\n        '   \\u001b[33m4\\u001b[39m,  \\u001b[33m5\\u001b[39m,  \\u001b[33m6\\u001b[39m,  \\u001b[33m7\\u001b[39m,',\n        '   \\u001b[33m8\\u001b[39m,  \\u001b[33m9\\u001b[39m, \\u001b[33m10\\u001b[39m, \\u001b[33m11\\u001b[39m,',\n        '  \\u001b[33m12\\u001b[39m, \\u001b[33m13\\u001b[39m, \\u001b[33m14\\u001b[39m, \\u001b[33m15\\u001b[39m,',\n        '  \\u001b[33m16\\u001b[39m, \\u001b[33m17\\u001b[39m, \\u001b[33m18\\u001b[39m, \\u001b[33m19\\u001b[39m,',\n        '  \\u001b[33m20\\u001b[39m, \\u001b[33m21\\u001b[39m, \\u001b[33m22\\u001b[39m, \\u001b[33m23\\u001b[39m,',\n        '  \\u001b[33m24\\u001b[39m, \\u001b[33m25\\u001b[39m, \\u001b[33m26\\u001b[39m, \\u001b[33m27\\u001b[39m,',\n        '  \\u001b[33m28\\u001b[39m, \\u001b[33m29\\u001b[39m, \\u001b[33m30\\u001b[39m, \\u001b[33m31\\u001b[39m,',\n        '  \\u001b[33m32\\u001b[39m, \\u001b[33m33\\u001b[39m, \\u001b[33m34\\u001b[39m, \\u001b[33m35\\u001b[39m,',\n        '  \\u001b[33m36\\u001b[39m, \\u001b[33m37\\u001b[39m, \\u001b[33m38\\u001b[39m, \\u001b[33m39\\u001b[39m,',\n        '  \\u001b[33m40\\u001b[39m, \\u001b[33m41\\u001b[39m, \\u001b[33m42\\u001b[39m, \\u001b[33m43\\u001b[39m,',\n        '  \\u001b[33m44\\u001b[39m, \\u001b[33m45\\u001b[39m, \\u001b[33m46\\u001b[39m, \\u001b[33m47\\u001b[39m,',\n        '  \\u001b[33m48\\u001b[39m, \\u001b[33m49\\u001b[39m, \\u001b[33m50\\u001b[39m, \\u001b[33m51\\u001b[39m,',\n        '  \\u001b[33m52\\u001b[39m, \\u001b[33m53\\u001b[39m, \\u001b[33m54\\u001b[39m, \\u001b[33m55\\u001b[39m,',\n        '  \\u001b[33m56\\u001b[39m, \\u001b[33m57\\u001b[39m, \\u001b[33m58\\u001b[39m, \\u001b[33m59\\u001b[39m',\n        ']',\n      ].join('\\n');\n\n      assert.strictEqual(out, expected);\n\n      out = util.inspect([1, 2, 3, 4], { compact: 1, colors: true });\n      expected =\n        '[ \\u001b[33m1\\u001b[39m, \\u001b[33m2\\u001b[39m, ' +\n        '\\u001b[33m3\\u001b[39m, \\u001b[33m4\\u001b[39m ]';\n\n      assert.strictEqual(out, expected);\n\n      obj = [\n        'Object',\n        'Function',\n        'Array',\n        'Number',\n        'parseFloat',\n        'parseInt',\n        'Infinity',\n        'NaN',\n        'undefined',\n        'Boolean',\n        'String',\n        'Symbol',\n        'Date',\n        'Promise',\n        'RegExp',\n        'Error',\n        'EvalError',\n        'RangeError',\n        'ReferenceError',\n        'SyntaxError',\n        'TypeError',\n        'URIError',\n        'JSON',\n        'Math',\n        'console',\n        'Intl',\n        'ArrayBuffer',\n        'Uint8Array',\n        'Int8Array',\n        'Uint16Array',\n        'Int16Array',\n        'Uint32Array',\n        'Int32Array',\n        'Float32Array',\n        'Float64Array',\n        'Uint8ClampedArray',\n        'BigUint64Array',\n        'BigInt64Array',\n        'DataView',\n        'Map',\n        'BigInt',\n        'Set',\n        'WeakMap',\n        'WeakSet',\n        'Proxy',\n        'Reflect',\n        'decodeURI',\n        'decodeURIComponent',\n        'encodeURI',\n        'encodeURIComponent',\n        'escape',\n        'unescape',\n        'eval',\n        'isFinite',\n        'isNaN',\n        'SharedArrayBuffer',\n        'Atomics',\n        'globalThis',\n        'WebAssembly',\n        'global',\n        'process',\n        'Buffer',\n        'URL',\n        'URLSearchParams',\n        'TextEncoder',\n        'TextDecoder',\n        'clearInterval',\n        'clearTimeout',\n        'setInterval',\n        'setTimeout',\n        'queueMicrotask',\n        'clearImmediate',\n        'setImmediate',\n        'module',\n        'require',\n        'assert',\n        'async_hooks',\n        'buffer',\n        'child_process',\n        'cluster',\n        'crypto',\n        'dgram',\n        'dns',\n        'domain',\n        'events',\n        'fs',\n        'http',\n        'http2',\n        'https',\n        'inspector',\n        'net',\n        'os',\n        'path',\n        'perf_hooks',\n        'punycode',\n        'querystring',\n        'readline',\n        'repl',\n        'stream',\n        'string_decoder',\n        'tls',\n        'trace_events',\n        'tty',\n        'url',\n        'v8',\n        'vm',\n        'worker_threads',\n        'zlib',\n        '_',\n        '_error',\n        'util',\n      ];\n\n      out = util.inspect(obj, {\n        compact: 3,\n        breakLength: 80,\n        maxArrayLength: 250,\n      });\n      expected = [\n        '[',\n        \"  'Object',          'Function',           'Array',\",\n        \"  'Number',          'parseFloat',         'parseInt',\",\n        \"  'Infinity',        'NaN',                'undefined',\",\n        \"  'Boolean',         'String',             'Symbol',\",\n        \"  'Date',            'Promise',            'RegExp',\",\n        \"  'Error',           'EvalError',          'RangeError',\",\n        \"  'ReferenceError',  'SyntaxError',        'TypeError',\",\n        \"  'URIError',        'JSON',               'Math',\",\n        \"  'console',         'Intl',               'ArrayBuffer',\",\n        \"  'Uint8Array',      'Int8Array',          'Uint16Array',\",\n        \"  'Int16Array',      'Uint32Array',        'Int32Array',\",\n        \"  'Float32Array',    'Float64Array',       'Uint8ClampedArray',\",\n        \"  'BigUint64Array',  'BigInt64Array',      'DataView',\",\n        \"  'Map',             'BigInt',             'Set',\",\n        \"  'WeakMap',         'WeakSet',            'Proxy',\",\n        \"  'Reflect',         'decodeURI',          'decodeURIComponent',\",\n        \"  'encodeURI',       'encodeURIComponent', 'escape',\",\n        \"  'unescape',        'eval',               'isFinite',\",\n        \"  'isNaN',           'SharedArrayBuffer',  'Atomics',\",\n        \"  'globalThis',      'WebAssembly',        'global',\",\n        \"  'process',         'Buffer',             'URL',\",\n        \"  'URLSearchParams', 'TextEncoder',        'TextDecoder',\",\n        \"  'clearInterval',   'clearTimeout',       'setInterval',\",\n        \"  'setTimeout',      'queueMicrotask',     'clearImmediate',\",\n        \"  'setImmediate',    'module',             'require',\",\n        \"  'assert',          'async_hooks',        'buffer',\",\n        \"  'child_process',   'cluster',            'crypto',\",\n        \"  'dgram',           'dns',                'domain',\",\n        \"  'events',          'fs',                 'http',\",\n        \"  'http2',           'https',              'inspector',\",\n        \"  'net',             'os',                 'path',\",\n        \"  'perf_hooks',      'punycode',           'querystring',\",\n        \"  'readline',        'repl',               'stream',\",\n        \"  'string_decoder',  'tls',                'trace_events',\",\n        \"  'tty',             'url',                'v8',\",\n        \"  'vm',              'worker_threads',     'zlib',\",\n        \"  '_',               '_error',             'util'\",\n        ']',\n      ].join('\\n');\n\n      assert.strictEqual(out, expected);\n    }\n\n    {\n      // Cross platform checks.\n      const err = new Error('foo');\n      util\n        .inspect(err, { colors: true })\n        .split('\\n')\n        .forEach((line, i) => {\n          assert(i < 2 || line.startsWith('\\u001b[90m'));\n        });\n    }\n\n    // Inspect prototype properties.\n    {\n      class Foo extends Map {\n        prop = false;\n        prop2 = true;\n        get abc() {\n          return true;\n        }\n        get def() {\n          return false;\n        }\n        set def(v) {}\n        get xyz() {\n          return 'Should be ignored';\n        }\n        func(a) {}\n        [util.inspect.custom]() {\n          return this;\n        }\n      }\n\n      class Bar extends Foo {\n        abc = true;\n        prop = true;\n        get xyz() {\n          return 'YES!';\n        }\n        [util.inspect.custom]() {\n          return this;\n        }\n      }\n\n      const bar = new Bar();\n\n      assert.strictEqual(\n        inspect(bar),\n        'Bar(0) [Map] { prop: true, prop2: true, abc: true }'\n      );\n      assert.strictEqual(\n        inspect(bar, { showHidden: true, getters: true, colors: false }),\n        'Bar(0) [Map] {\\n' +\n          '  prop: true,\\n' +\n          '  prop2: true,\\n' +\n          '  abc: true,\\n' +\n          \"  [xyz]: [Getter: 'YES!'],\\n\" +\n          '  [def]: [Getter/Setter: false]\\n' +\n          '}'\n      );\n      assert.strictEqual(\n        inspect(bar, { showHidden: true, getters: false, colors: true }),\n        'Bar(0) [Map] {\\n' +\n          '  prop: \\x1B[33mtrue\\x1B[39m,\\n' +\n          '  prop2: \\x1B[33mtrue\\x1B[39m,\\n' +\n          '  abc: \\x1B[33mtrue\\x1B[39m,\\n' +\n          '  \\x1B[2m[xyz]: \\x1B[36m[Getter]\\x1B[39m\\x1B[22m,\\n' +\n          '  \\x1B[2m[def]: \\x1B[36m[Getter/Setter]\\x1B[39m\\x1B[22m\\n' +\n          '}'\n      );\n\n      const obj = { __proto__: { abc: true, def: 5, toString() {} } };\n      assert.strictEqual(\n        inspect(obj, { showHidden: true, colors: true }),\n        '{ \\x1B[2mabc: \\x1B[33mtrue\\x1B[39m\\x1B[22m, ' +\n          '\\x1B[2mdef: \\x1B[33m5\\x1B[39m\\x1B[22m }'\n      );\n\n      assert.strictEqual(\n        inspect(Object.getPrototypeOf(bar), {\n          showHidden: true,\n          getters: true,\n        }),\n        '<ref *1> Foo [Map] {\\n' +\n          '    [constructor]: [class Bar extends Foo] {\\n' +\n          '      [length]: 0,\\n' +\n          \"      [name]: 'Bar',\\n\" +\n          '      [prototype]: [Circular *1],\\n' +\n          '      [Symbol(Symbol.species)]: [Getter: <Inspection threw ' +\n          \"(Symbol.prototype.toString requires that 'this' be a Symbol)>]\\n\" +\n          '    },\\n' +\n          \"    [xyz]: [Getter: 'YES!'],\\n\" +\n          '    [Symbol(nodejs.util.inspect.custom)]: ' +\n          '[Function: [nodejs.util.inspect.custom]] {\\n' +\n          '      [length]: 0,\\n' +\n          \"      [name]: '[nodejs.util.inspect.custom]'\\n\" +\n          '    },\\n' +\n          '    [abc]: [Getter: true],\\n' +\n          '    [def]: [Getter/Setter: false]\\n' +\n          '  }'\n      );\n\n      assert.strictEqual(inspect(Object.getPrototypeOf(bar)), 'Foo [Map] {}');\n\n      assert.strictEqual(inspect(Object.getPrototypeOf(new Foo())), 'Map {}');\n    }\n\n    // Check that prototypes with a null prototype are inspectable.\n    // Regression test for https://github.com/nodejs/node/issues/35730\n    {\n      function Func() {}\n      Func.prototype = null;\n      const object = {};\n      object.constructor = Func;\n\n      assert.strictEqual(\n        util.inspect(object),\n        '{ constructor: [Function: Func] }'\n      );\n    }\n\n    // Test changing util.inspect.colors colors and aliases.\n    {\n      const colors = util.inspect.colors;\n\n      const originalValue = colors.gray;\n\n      // \"grey\" is reference-equal alias of \"gray\".\n      assert.strictEqual(colors.grey, colors.gray);\n\n      // Assigning one should assign the other. This tests that the alias setter\n      // function keeps things reference-equal.\n      colors.gray = [0, 0];\n      assert.deepStrictEqual(colors.gray, [0, 0]);\n      assert.strictEqual(colors.grey, colors.gray);\n\n      colors.grey = [1, 1];\n      assert.deepStrictEqual(colors.grey, [1, 1]);\n      assert.strictEqual(colors.grey, colors.gray);\n\n      // Restore original value to avoid side effects in other tests.\n      colors.gray = originalValue;\n      assert.deepStrictEqual(colors.gray, originalValue);\n      assert.strictEqual(colors.grey, colors.gray);\n    }\n\n    // Truncate output for Primitives with 1 character left\n    {\n      assert.strictEqual(\n        util.inspect('bl', { maxStringLength: 1 }),\n        \"'b'... 1 more character\"\n      );\n    }\n\n    {\n      const x = 'a'.repeat(1e6);\n      assert(util.inspect(x).endsWith('... 990000 more characters'));\n      assert.strictEqual(\n        util.inspect(x, { maxStringLength: 4 }),\n        \"'aaaa'... 999996 more characters\"\n      );\n      assert.match(util.inspect(x, { maxStringLength: null }), /a'$/);\n    }\n\n    {\n      // Confirm that own constructor value displays correctly.\n\n      function Fhqwhgads() {}\n\n      const sterrance = new Fhqwhgads();\n      sterrance.constructor = Fhqwhgads;\n\n      assert.strictEqual(\n        util.inspect(sterrance, { showHidden: true }),\n        'Fhqwhgads {\\n' +\n          '  constructor: <ref *1> [Function: Fhqwhgads] {\\n' +\n          '    [length]: 0,\\n' +\n          \"    [name]: 'Fhqwhgads',\\n\" +\n          '    [prototype]: { [constructor]: [Circular *1] }\\n' +\n          '  }\\n' +\n          '}'\n      );\n    }\n\n    // TODO(soon): figure out why we're generating `Iterator [Generator]` instead of `Object [Generator]`\n    {\n      // Confirm null prototype of generator prototype displays as expected.\n\n      function getProtoOfProto() {\n        return Object.getPrototypeOf(Object.getPrototypeOf(function* () {}));\n      }\n\n      function* generator() {}\n\n      const generatorPrototype = Object.getPrototypeOf(generator);\n      const originalProtoOfProto = Object.getPrototypeOf(generatorPrototype);\n      assert.strictEqual(getProtoOfProto(), originalProtoOfProto);\n      Object.setPrototypeOf(generatorPrototype, null);\n      assert.notStrictEqual(getProtoOfProto, originalProtoOfProto);\n\n      // This is the actual test. The other assertions in this block are about\n      // making sure the test is set up correctly and isn't polluting other tests.\n      const expected1 =\n        '[GeneratorFunction: generator] {\\n' +\n        '  [length]: 0,\\n' +\n        \"  [name]: 'generator',\\n\" +\n        \"  [prototype]: Object [Generator] { [Symbol(Symbol.toStringTag)]: 'Generator' },\\n\" + // eslint-disable-line max-len\n        \"  [Symbol(Symbol.toStringTag)]: 'GeneratorFunction'\\n\" +\n        '}';\n      const expected2 = // TODO: Remove this once updated to new V8.\n        '[GeneratorFunction: generator] {\\n' +\n        '  [length]: 0,\\n' +\n        \"  [name]: 'generator',\\n\" +\n        \"  [prototype]: Iterator [Generator] { [Symbol(Symbol.toStringTag)]: 'Generator' },\\n\" + // eslint-disable-line max-len\n        \"  [Symbol(Symbol.toStringTag)]: 'GeneratorFunction'\\n\" +\n        '}';\n      const actual = util.inspect(generator, { showHidden: true });\n      assert.ok(actual == expected1 || actual == expected2);\n\n      // Reset so we don't pollute other tests\n      Object.setPrototypeOf(generatorPrototype, originalProtoOfProto);\n      assert.strictEqual(getProtoOfProto(), originalProtoOfProto);\n    }\n\n    {\n      // Test for when breakLength results in a single column.\n      const obj = Array.from(\n        { length: 9 },\n        () => 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf'\n      );\n      assert.strictEqual(\n        util.inspect(obj, { breakLength: 256 }),\n        '[\\n' +\n          \"  'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\\n\" +\n          \"  'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\\n\" +\n          \"  'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\\n\" +\n          \"  'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\\n\" +\n          \"  'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\\n\" +\n          \"  'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\\n\" +\n          \"  'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\\n\" +\n          \"  'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\\n\" +\n          \"  'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf'\\n\" +\n          ']'\n      );\n    }\n\n    {\n      assert.strictEqual(\n        util.inspect({ ['__proto__']: { a: 1 } }),\n        \"{ ['__proto__']: { a: 1 } }\"\n      );\n    }\n\n    {\n      const { numericSeparator } = util.inspect.defaultOptions;\n      util.inspect.defaultOptions.numericSeparator = true;\n\n      assert.strictEqual(\n        // eslint-disable-next-line no-loss-of-precision\n        util.inspect(1234567891234567891234),\n        '1.234567891234568e+21'\n      );\n      assert.strictEqual(\n        util.inspect(123456789.12345678),\n        '123_456_789.123_456_78'\n      );\n\n      assert.strictEqual(util.inspect(10_000_000), '10_000_000');\n      assert.strictEqual(util.inspect(1_000_000), '1_000_000');\n      assert.strictEqual(util.inspect(100_000), '100_000');\n      assert.strictEqual(util.inspect(99_999.9), '99_999.9');\n      assert.strictEqual(util.inspect(9_999), '9_999');\n      assert.strictEqual(util.inspect(999), '999');\n      assert.strictEqual(util.inspect(NaN), 'NaN');\n      assert.strictEqual(util.inspect(Infinity), 'Infinity');\n      assert.strictEqual(util.inspect(-Infinity), '-Infinity');\n\n      assert.strictEqual(\n        util.inspect(new Float64Array([100_000_000])),\n        'Float64Array(1) [ 100_000_000 ]'\n      );\n      assert.strictEqual(\n        util.inspect(new BigInt64Array([9_100_000_100n])),\n        'BigInt64Array(1) [ 9_100_000_100n ]'\n      );\n\n      assert.strictEqual(util.inspect(123456789), '123_456_789');\n      assert.strictEqual(util.inspect(123456789n), '123_456_789n');\n\n      util.inspect.defaultOptions.numericSeparator = numericSeparator;\n\n      assert.strictEqual(\n        util.inspect(123456789.12345678, { numericSeparator: true }),\n        '123_456_789.123_456_78'\n      );\n\n      assert.strictEqual(\n        util.inspect(-123456789.12345678, { numericSeparator: true }),\n        '-123_456_789.123_456_78'\n      );\n    }\n\n    // Regression test for https://github.com/nodejs/node/issues/41244\n    {\n      assert.strictEqual(\n        util.inspect({\n          get [Symbol.iterator]() {\n            throw new Error();\n          },\n        }),\n        '{ Symbol(Symbol.iterator): [Getter] }'\n      );\n    }\n\n    {\n      const sym = Symbol('bar()');\n      const o = {\n        foo: 0,\n        'Symbol(foo)': 0,\n        [Symbol('foo')]: 0,\n        [Symbol('foo()')]: 0,\n        [sym]: 0,\n      };\n      Object.defineProperty(o, sym, { enumerable: false });\n\n      assert.strictEqual(\n        util.inspect(o, { showHidden: true }),\n        '{\\n' +\n          '  foo: 0,\\n' +\n          \"  'Symbol(foo)': 0,\\n\" +\n          '  Symbol(foo): 0,\\n' +\n          '  Symbol(foo()): 0,\\n' +\n          '  [Symbol(bar())]: 0\\n' +\n          '}'\n      );\n    }\n\n    {\n      const o = {};\n      const { prototype: BuiltinPrototype } = Object;\n      const desc = Reflect.getOwnPropertyDescriptor(\n        BuiltinPrototype,\n        'constructor'\n      );\n      Object.defineProperty(BuiltinPrototype, 'constructor', {\n        get: () => BuiltinPrototype,\n        configurable: true,\n      });\n      assert.strictEqual(util.inspect(o), '{}');\n      Object.defineProperty(BuiltinPrototype, 'constructor', desc);\n    }\n\n    {\n      const o = { f() {} };\n      const { prototype: BuiltinPrototype } = Function;\n      const desc = Reflect.getOwnPropertyDescriptor(\n        BuiltinPrototype,\n        'constructor'\n      );\n      Object.defineProperty(BuiltinPrototype, 'constructor', {\n        get: () => BuiltinPrototype,\n        configurable: true,\n      });\n      assert.strictEqual(util.inspect(o), '{ f: [Function: f] }');\n      Object.defineProperty(BuiltinPrototype, 'constructor', desc);\n    }\n    {\n      const prototypes = [\n        Array.prototype,\n        ArrayBuffer.prototype,\n        Buffer.prototype,\n        Function.prototype,\n        Map.prototype,\n        Object.prototype,\n        Reflect.getPrototypeOf(Uint8Array.prototype),\n        Set.prototype,\n        Uint8Array.prototype,\n      ];\n      const descriptors = new Map();\n      const buffer = Buffer.from('Hello');\n      const o = {\n        arrayBuffer: new ArrayBuffer(),\n        buffer,\n        typedArray: Uint8Array.from(buffer),\n        array: [],\n        func() {},\n        set: new Set([1]),\n        map: new Map(),\n      };\n      for (const BuiltinPrototype of prototypes) {\n        descriptors.set(\n          BuiltinPrototype,\n          Reflect.getOwnPropertyDescriptor(BuiltinPrototype, 'constructor')\n        );\n        Object.defineProperty(BuiltinPrototype, 'constructor', {\n          get: () => BuiltinPrototype,\n          configurable: true,\n        });\n      }\n      assert.strictEqual(\n        util.inspect(o),\n        '{\\n' +\n          '  arrayBuffer: ArrayBuffer { [Uint8Contents]: <>, byteLength: 0 },\\n' +\n          '  buffer: <Buffer 48 65 6c 6c 6f>,\\n' +\n          '  typedArray: TypedArray(5) [Uint8Array] [ 72, 101, 108, 108, 111 ],\\n' +\n          '  array: [],\\n' +\n          '  func: [Function: func],\\n' +\n          '  set: Set(1) { 1 },\\n' +\n          '  map: Map(0) {}\\n' +\n          '}'\n      );\n      for (const [BuiltinPrototype, desc] of descriptors) {\n        Object.defineProperty(BuiltinPrototype, 'constructor', desc);\n      }\n    }\n\n    {\n      function f() {}\n      Object.defineProperty(f, 'name', { value: Symbol('f') });\n\n      assert.strictEqual(util.inspect(f), '[Function: Symbol(f)]');\n    }\n\n    {\n      const error = new EvalError();\n      const re = /a/g;\n      error.name = re;\n      assert.strictEqual(error.name, re);\n      assert.strictEqual(\n        util.inspect(error),\n        `${re} [EvalError]\n${error.stack.split('\\n').slice(1).join('\\n')}`\n      );\n    }\n\n    {\n      const error = new Error();\n      error.stack = [Symbol('foo')];\n\n      assert.strictEqual(inspect(error), '[[\\n  Symbol(foo)\\n]]');\n    }\n\n    assertCalledMustCalls();\n  },\n};\n\nexport const utilInspectProxy = {\n  // test-util-inspect-proxy.js\n  test(ctrl, env, ctx) {\n    const opts = { showProxy: true };\n\n    let proxyObj;\n    let called = false;\n    const target = {\n      [util.inspect.custom](depth, { showProxy }) {\n        if (showProxy === false) {\n          called = true;\n          if (proxyObj !== this) {\n            throw new Error('Failed');\n          }\n        }\n        return [1, 2, 3];\n      },\n    };\n    const handler = {\n      getPrototypeOf() {\n        throw new Error('getPrototypeOf');\n      },\n      setPrototypeOf() {\n        throw new Error('setPrototypeOf');\n      },\n      isExtensible() {\n        throw new Error('isExtensible');\n      },\n      preventExtensions() {\n        throw new Error('preventExtensions');\n      },\n      getOwnPropertyDescriptor() {\n        throw new Error('getOwnPropertyDescriptor');\n      },\n      defineProperty() {\n        throw new Error('defineProperty');\n      },\n      has() {\n        throw new Error('has');\n      },\n      get() {\n        throw new Error('get');\n      },\n      set() {\n        throw new Error('set');\n      },\n      deleteProperty() {\n        throw new Error('deleteProperty');\n      },\n      ownKeys() {\n        throw new Error('ownKeys');\n      },\n      apply() {\n        throw new Error('apply');\n      },\n      construct() {\n        throw new Error('construct');\n      },\n    };\n    proxyObj = new Proxy(target, handler);\n\n    // Inspecting the proxy should not actually walk its properties\n    util.inspect(proxyObj, opts);\n\n    // Make sure inspecting object does not trigger any proxy traps.\n    util.format('%s', proxyObj);\n    // %i%f%d use Symbol.toPrimitive to convert the value to a string.\n    // %j uses JSON.stringify, accessing the value's toJSON and toString method.\n    util.format('%s%o%O%c', proxyObj, proxyObj, proxyObj, proxyObj);\n    const nestedFormatProxy = new Proxy(new Proxy({}, handler), {});\n    util.format(\n      '%s%o%O%c',\n      nestedFormatProxy,\n      nestedFormatProxy,\n      nestedFormatProxy,\n      nestedFormatProxy\n    );\n\n    const r = Proxy.revocable({}, {});\n    r.revoke();\n\n    assert.strictEqual(util.inspect(r.proxy), '<Revoked Proxy>');\n    assert.strictEqual(\n      util.inspect(r, { showProxy: true }),\n      '{ proxy: <Revoked Proxy>, revoke: [Function (anonymous)] }'\n    );\n\n    assert.strictEqual(util.format('%s', r.proxy), '<Revoked Proxy>');\n\n    assert.strictEqual(\n      util.inspect(proxyObj, opts),\n      'Proxy [\\n' +\n        '  [ 1, 2, 3 ],\\n' +\n        '  {\\n' +\n        '    getPrototypeOf: [Function: getPrototypeOf],\\n' +\n        '    setPrototypeOf: [Function: setPrototypeOf],\\n' +\n        '    isExtensible: [Function: isExtensible],\\n' +\n        '    preventExtensions: [Function: preventExtensions],\\n' +\n        '    getOwnPropertyDescriptor: [Function: getOwnPropertyDescriptor],\\n' +\n        '    defineProperty: [Function: defineProperty],\\n' +\n        '    has: [Function: has],\\n' +\n        '    get: [Function: get],\\n' +\n        '    set: [Function: set],\\n' +\n        '    deleteProperty: [Function: deleteProperty],\\n' +\n        '    ownKeys: [Function: ownKeys],\\n' +\n        '    apply: [Function: apply],\\n' +\n        '    construct: [Function: construct]\\n' +\n        '  }\\n' +\n        ']'\n    );\n\n    // Inspecting a proxy without the showProxy option set to true should not\n    // trigger any proxy handlers.\n    assert.strictEqual(util.inspect(proxyObj), '[ 1, 2, 3 ]');\n    assert(called);\n\n    // Yo dawg, I heard you liked Proxy so I put a Proxy\n    // inside your Proxy that proxies your Proxy's Proxy.\n    const proxy1 = new Proxy({}, {});\n    const proxy2 = new Proxy(proxy1, {});\n    const proxy3 = new Proxy(proxy2, proxy1);\n    const proxy4 = new Proxy(proxy1, proxy2);\n    const proxy5 = new Proxy(proxy3, proxy4);\n    const proxy6 = new Proxy(proxy5, proxy5);\n    const expected0 = 'Proxy({})';\n    const expected1 = 'Proxy [ {}, {} ]';\n    const expected2 = 'Proxy [ Proxy [ {}, {} ], {} ]';\n    const expected3 =\n      'Proxy [ Proxy [ Proxy [ {}, {} ], {} ], Proxy [ {}, {} ] ]';\n    const expected4 =\n      'Proxy [ Proxy [ {}, {} ], Proxy [ Proxy [ {}, {} ], {} ] ]';\n    const expected5 =\n      'Proxy [\\n  ' +\n      'Proxy [ Proxy [ Proxy [Array], {} ], Proxy [ {}, {} ] ],\\n' +\n      '  Proxy [ Proxy [ {}, {} ], Proxy [ Proxy [Array], {} ] ]' +\n      '\\n]';\n    const expected6 =\n      'Proxy [\\n' +\n      '  Proxy [\\n' +\n      '    Proxy [ Proxy [Array], Proxy [Array] ],\\n' +\n      '    Proxy [ Proxy [Array], Proxy [Array] ]\\n' +\n      '  ],\\n' +\n      '  Proxy [\\n' +\n      '    Proxy [ Proxy [Array], Proxy [Array] ],\\n' +\n      '    Proxy [ Proxy [Array], Proxy [Array] ]\\n' +\n      '  ]\\n' +\n      ']';\n    const expected2NoShowProxy = 'Proxy(Proxy({}))';\n    const expected3NoShowProxy = 'Proxy(Proxy(Proxy({})))';\n    const expected4NoShowProxy = 'Proxy(Proxy(Proxy(Proxy({}))))';\n    const expected5NoShowProxy = 'Proxy(Proxy(Proxy(Proxy(Proxy({})))))';\n    assert.strictEqual(\n      util.inspect(proxy1, { showProxy: 1, depth: null }),\n      expected1\n    );\n    assert.strictEqual(util.inspect(proxy2, opts), expected2);\n    assert.strictEqual(util.inspect(proxy3, opts), expected3);\n    assert.strictEqual(util.inspect(proxy4, opts), expected4);\n    assert.strictEqual(util.inspect(proxy5, opts), expected5);\n    assert.strictEqual(util.inspect(proxy6, opts), expected6);\n    assert.strictEqual(util.inspect(proxy1), expected0);\n    assert.strictEqual(util.inspect(proxy2), expected2NoShowProxy);\n    assert.strictEqual(util.inspect(proxy3), expected3NoShowProxy);\n    assert.strictEqual(util.inspect(proxy4), expected2NoShowProxy);\n    assert.strictEqual(util.inspect(proxy5), expected4NoShowProxy);\n    assert.strictEqual(util.inspect(proxy6), expected5NoShowProxy);\n\n    // Just for fun, let's create a Proxy using Arrays.\n    const proxy7 = new Proxy([], []);\n    const expected7 = 'Proxy [ [], [] ]';\n    assert.strictEqual(util.inspect(proxy7, opts), expected7);\n    assert.strictEqual(util.inspect(proxy7), 'Proxy([])');\n\n    // Now we're just getting silly, right?\n    const proxy8 = new Proxy(Date, []);\n    const proxy9 = new Proxy(Date, String);\n    const expected8 = 'Proxy [ [Function: Date], [] ]';\n    const expected9 = 'Proxy [ [Function: Date], [Function: String] ]';\n    assert.strictEqual(util.inspect(proxy8, opts), expected8);\n    assert.strictEqual(util.inspect(proxy9, opts), expected9);\n    assert.strictEqual(util.inspect(proxy8), 'Proxy([Function: Date])');\n    assert.strictEqual(util.inspect(proxy9), 'Proxy([Function: Date])');\n\n    const proxy10 = new Proxy(() => {}, {});\n    const proxy11 = new Proxy(() => {}, {\n      get() {\n        return proxy11;\n      },\n      apply() {\n        return proxy11;\n      },\n    });\n    const expected10 = 'Proxy([Function (anonymous)])';\n    const expected11 = 'Proxy([Function (anonymous)])';\n    assert.strictEqual(util.inspect(proxy10), expected10);\n    assert.strictEqual(util.inspect(proxy11), expected11);\n\n    const proxy12 = new Proxy([1, 2, 3], proxy5);\n    assert.strictEqual(\n      util.inspect(proxy12, { colors: true, breakLength: 1 }),\n      '\\x1B[36mProxy(\\x1B[39m' +\n        '[\\n  \\x1B[33m1\\x1B[39m,\\n  \\x1B[33m2\\x1B[39m,\\n  \\x1B[33m3\\x1B[39m\\n]\\x1B[36m' +\n        ')\\x1B[39m'\n    );\n    assert.strictEqual(util.format('%s', proxy12), 'Proxy([ 1, 2, 3 ])');\n\n    {\n      // Nested proxies should not trigger any proxy handlers.\n      const nestedProxy = new Proxy(new Proxy(new Proxy({}, handler), {}), {});\n\n      assert.strictEqual(\n        util.inspect(nestedProxy, { showProxy: true }),\n        'Proxy [ Proxy [ Proxy [ {}, [Object] ], {} ], {} ]'\n      );\n      assert.strictEqual(\n        util.inspect(nestedProxy, { showProxy: false }),\n        expected3NoShowProxy\n      );\n    }\n\n    {\n      // Nested revoked proxies should work as expected as well as custom\n      // inspection functions.\n      const revocable = Proxy.revocable({}, handler);\n      revocable.revoke();\n      const nestedProxy = new Proxy(revocable.proxy, {});\n\n      assert.strictEqual(\n        util.inspect(nestedProxy, { showProxy: true }),\n        'Proxy [ <Revoked Proxy>, {} ]'\n      );\n      assert.strictEqual(\n        util.inspect(nestedProxy, { showProxy: false }),\n        'Proxy(<Revoked Proxy>)'\n      );\n    }\n  },\n};\n\nexport const utilInspectGettersAccessingThis = {\n  // test-util-inspect-getters-accessing-this.js\n  test(ctrl, env, ctx) {\n    {\n      class X {\n        constructor() {\n          this._y = 123;\n        }\n\n        get y() {\n          return this._y;\n        }\n      }\n\n      const result = inspect(new X(), {\n        getters: true,\n        showHidden: true,\n      });\n\n      assert.strictEqual(result, 'X { _y: 123, [y]: [Getter: 123] }');\n    }\n\n    // Regression test for https://github.com/nodejs/node/issues/37054\n    {\n      class A {\n        constructor(B) {\n          this.B = B;\n        }\n        get b() {\n          return this.B;\n        }\n      }\n\n      class B {\n        constructor() {\n          this.A = new A(this);\n        }\n        get a() {\n          return this.A;\n        }\n      }\n\n      const result = inspect(new B(), {\n        depth: 1,\n        getters: true,\n        showHidden: true,\n      });\n\n      assert.strictEqual(\n        result,\n        '<ref *1> B {\\n' +\n          '  A: A { B: [Circular *1], [b]: [Getter] [Circular *1] },\\n' +\n          '  [a]: [Getter] A { B: [Circular *1], [b]: [Getter] [Circular *1] }\\n' +\n          '}'\n      );\n    }\n  },\n};\n\nexport const utilFormat = {\n  // test-util-format.js\n  async test(ctrl, env, ctx) {\n    const symbol = Symbol('foo');\n\n    assert.strictEqual(util.format(), '');\n    assert.strictEqual(util.format(''), '');\n    assert.strictEqual(util.format([]), '[]');\n    assert.strictEqual(util.format([0]), '[ 0 ]');\n    assert.strictEqual(util.format({}), '{}');\n    assert.strictEqual(util.format({ foo: 42 }), '{ foo: 42 }');\n    assert.strictEqual(util.format(null), 'null');\n    assert.strictEqual(util.format(true), 'true');\n    assert.strictEqual(util.format(false), 'false');\n    assert.strictEqual(util.format('test'), 'test');\n\n    // CHECKME this is for console.log() compatibility - but is it *right*?\n    assert.strictEqual(util.format('foo', 'bar', 'baz'), 'foo bar baz');\n\n    // ES6 Symbol handling\n    assert.strictEqual(util.format(symbol), 'Symbol(foo)');\n    assert.strictEqual(util.format('foo', symbol), 'foo Symbol(foo)');\n    assert.strictEqual(util.format('%s', symbol), 'Symbol(foo)');\n    assert.strictEqual(util.format('%j', symbol), 'undefined');\n\n    // Number format specifier\n    assert.strictEqual(util.format('%d'), '%d');\n    assert.strictEqual(util.format('%d', 42.0), '42');\n    assert.strictEqual(util.format('%d', 42), '42');\n    assert.strictEqual(util.format('%d', '42'), '42');\n    assert.strictEqual(util.format('%d', '42.0'), '42');\n    assert.strictEqual(util.format('%d', 1.5), '1.5');\n    assert.strictEqual(util.format('%d', -0.5), '-0.5');\n    assert.strictEqual(util.format('%d', -0.0), '-0');\n    assert.strictEqual(util.format('%d', ''), '0');\n    assert.strictEqual(util.format('%d', ' -0.000'), '-0');\n    assert.strictEqual(util.format('%d', Symbol()), 'NaN');\n    assert.strictEqual(util.format('%d', Infinity), 'Infinity');\n    assert.strictEqual(util.format('%d', -Infinity), '-Infinity');\n    assert.strictEqual(util.format('%d %d', 42, 43), '42 43');\n    assert.strictEqual(util.format('%d %d', 42), '42 %d');\n    assert.strictEqual(\n      util.format('%d', 1180591620717411303424),\n      '1.1805916207174113e+21'\n    );\n    assert.strictEqual(\n      util.format('%d', 1180591620717411303424n),\n      '1180591620717411303424n'\n    );\n    assert.strictEqual(\n      util.format('%d %d', 1180591620717411303424n, 12345678901234567890123n),\n      '1180591620717411303424n 12345678901234567890123n'\n    );\n\n    {\n      const { numericSeparator } = util.inspect.defaultOptions;\n      util.inspect.defaultOptions.numericSeparator = true;\n\n      assert.strictEqual(\n        util.format('%d', 1180591620717411303424),\n        '1.1805916207174113e+21'\n      );\n\n      assert.strictEqual(\n        util.format(\n          // eslint-disable-next-line no-loss-of-precision\n          '%d %s %i',\n          118059162071741130342,\n          118059162071741130342,\n          123_123_123\n        ),\n        '118_059_162_071_741_140_000 118_059_162_071_741_140_000 123_123_123'\n      );\n\n      assert.strictEqual(\n        util.format(\n          '%d %s',\n          1_180_591_620_717_411_303_424n,\n          12_345_678_901_234_567_890_123n\n        ),\n        '1_180_591_620_717_411_303_424n 12_345_678_901_234_567_890_123n'\n      );\n\n      assert.strictEqual(\n        util.format('%i', 1_180_591_620_717_411_303_424n),\n        '1_180_591_620_717_411_303_424n'\n      );\n\n      util.inspect.defaultOptions.numericSeparator = numericSeparator;\n    }\n    // Integer format specifier\n    assert.strictEqual(util.format('%i'), '%i');\n    assert.strictEqual(util.format('%i', 42.0), '42');\n    assert.strictEqual(util.format('%i', 42), '42');\n    assert.strictEqual(util.format('%i', '42'), '42');\n    assert.strictEqual(util.format('%i', '42.0'), '42');\n    assert.strictEqual(util.format('%i', 1.5), '1');\n    assert.strictEqual(util.format('%i', -0.5), '-0');\n    assert.strictEqual(util.format('%i', ''), 'NaN');\n    assert.strictEqual(util.format('%i', Infinity), 'NaN');\n    assert.strictEqual(util.format('%i', -Infinity), 'NaN');\n    assert.strictEqual(util.format('%i', Symbol()), 'NaN');\n    assert.strictEqual(util.format('%i %i', 42, 43), '42 43');\n    assert.strictEqual(util.format('%i %i', 42), '42 %i');\n    assert.strictEqual(util.format('%i', 1180591620717411303424), '1');\n    assert.strictEqual(\n      util.format('%i', 1180591620717411303424n),\n      '1180591620717411303424n'\n    );\n    assert.strictEqual(\n      util.format('%i %i', 1180591620717411303424n, 12345678901234567890123n),\n      '1180591620717411303424n 12345678901234567890123n'\n    );\n\n    assert.strictEqual(\n      util.format('%d %i', 1180591620717411303424n, 12345678901234567890123n),\n      '1180591620717411303424n 12345678901234567890123n'\n    );\n\n    assert.strictEqual(\n      util.format('%i %d', 1180591620717411303424n, 12345678901234567890123n),\n      '1180591620717411303424n 12345678901234567890123n'\n    );\n\n    assert.strictEqual(\n      util.formatWithOptions(\n        { numericSeparator: true },\n        '%i %d',\n        1180591620717411303424n,\n        12345678901234567890123n\n      ),\n      '1_180_591_620_717_411_303_424n 12_345_678_901_234_567_890_123n'\n    );\n\n    // Float format specifier\n    assert.strictEqual(util.format('%f'), '%f');\n    assert.strictEqual(util.format('%f', 42.0), '42');\n    assert.strictEqual(util.format('%f', 42), '42');\n    assert.strictEqual(util.format('%f', '42'), '42');\n    assert.strictEqual(util.format('%f', '-0.0'), '-0');\n    assert.strictEqual(util.format('%f', '42.0'), '42');\n    assert.strictEqual(util.format('%f', 1.5), '1.5');\n    assert.strictEqual(util.format('%f', -0.5), '-0.5');\n    assert.strictEqual(util.format('%f', Math.PI), '3.141592653589793');\n    assert.strictEqual(util.format('%f', ''), 'NaN');\n    assert.strictEqual(util.format('%f', Symbol('foo')), 'NaN');\n    assert.strictEqual(util.format('%f', 5n), '5');\n    assert.strictEqual(util.format('%f', Infinity), 'Infinity');\n    assert.strictEqual(util.format('%f', -Infinity), '-Infinity');\n    assert.strictEqual(util.format('%f %f', 42, 43), '42 43');\n    assert.strictEqual(util.format('%f %f', 42), '42 %f');\n\n    // String format specifier\n    assert.strictEqual(util.format('%s'), '%s');\n    assert.strictEqual(util.format('%s', undefined), 'undefined');\n    assert.strictEqual(util.format('%s', null), 'null');\n    assert.strictEqual(util.format('%s', 'foo'), 'foo');\n    assert.strictEqual(util.format('%s', 42), '42');\n    assert.strictEqual(util.format('%s', '42'), '42');\n    assert.strictEqual(util.format('%s', -0), '-0');\n    assert.strictEqual(util.format('%s', '-0.0'), '-0.0');\n    assert.strictEqual(util.format('%s %s', 42, 43), '42 43');\n    assert.strictEqual(util.format('%s %s', 42), '42 %s');\n    assert.strictEqual(util.format('%s', 42n), '42n');\n    assert.strictEqual(util.format('%s', Symbol('foo')), 'Symbol(foo)');\n    assert.strictEqual(util.format('%s', true), 'true');\n    assert.strictEqual(util.format('%s', { a: [1, 2, 3] }), '{ a: [Array] }');\n    assert.strictEqual(\n      util.format('%s', {\n        toString() {\n          return 'Foo';\n        },\n      }),\n      'Foo'\n    );\n    assert.strictEqual(util.format('%s', { toString: 5 }), '{ toString: 5 }');\n    assert.strictEqual(\n      util.format('%s', () => 5),\n      '() => 5'\n    );\n    assert.strictEqual(util.format('%s', Infinity), 'Infinity');\n    assert.strictEqual(util.format('%s', -Infinity), '-Infinity');\n\n    // String format specifier including `toString` properties on the prototype.\n    {\n      class Foo {\n        toString() {\n          return 'Bar';\n        }\n      }\n      assert.strictEqual(util.format('%s', new Foo()), 'Bar');\n      assert.strictEqual(\n        util.format('%s', Object.setPrototypeOf(new Foo(), null)),\n        '[Foo: null prototype] {}'\n      );\n      globalThis.Foo = Foo;\n      assert.strictEqual(util.format('%s', new Foo()), 'Bar');\n      delete globalThis.Foo;\n      class Bar {\n        abc = true;\n      }\n      assert.strictEqual(util.format('%s', new Bar()), 'Bar { abc: true }');\n      class Foobar extends Array {\n        aaa = true;\n      }\n      assert.strictEqual(\n        util.format('%s', new Foobar(5)),\n        'Foobar(5) [ <5 empty items>, aaa: true ]'\n      );\n\n      // Subclassing:\n      class B extends Foo {}\n\n      function C() {}\n      C.prototype.toString = function () {\n        return 'Custom';\n      };\n\n      function D() {\n        C.call(this);\n      }\n      D.prototype = { __proto__: C.prototype };\n\n      assert.strictEqual(util.format('%s', new B()), 'Bar');\n      assert.strictEqual(util.format('%s', new C()), 'Custom');\n      assert.strictEqual(util.format('%s', new D()), 'Custom');\n\n      D.prototype.constructor = D;\n      assert.strictEqual(util.format('%s', new D()), 'Custom');\n\n      D.prototype.constructor = null;\n      assert.strictEqual(util.format('%s', new D()), 'Custom');\n\n      D.prototype.constructor = { name: 'Foobar' };\n      assert.strictEqual(util.format('%s', new D()), 'Custom');\n\n      Object.defineProperty(D.prototype, 'constructor', {\n        get() {\n          throw new Error();\n        },\n        configurable: true,\n      });\n      assert.strictEqual(util.format('%s', new D()), 'Custom');\n\n      assert.strictEqual(\n        util.format('%s', { __proto__: null }),\n        '[Object: null prototype] {}'\n      );\n    }\n\n    // JSON format specifier\n    assert.strictEqual(util.format('%j'), '%j');\n    assert.strictEqual(util.format('%j', 42), '42');\n    assert.strictEqual(util.format('%j', '42'), '\"42\"');\n    assert.strictEqual(util.format('%j %j', 42, 43), '42 43');\n    assert.strictEqual(util.format('%j %j', 42), '42 %j');\n\n    // Object format specifier\n    const obj = {\n      foo: 'bar',\n      foobar: 1,\n      func: function () {},\n    };\n    const nestedObj = {\n      foo: 'bar',\n      foobar: {\n        foo: 'bar',\n        func: function () {},\n      },\n    };\n    const nestedObj2 = {\n      foo: 'bar',\n      foobar: 1,\n      func: [{ a: function () {} }],\n    };\n    assert.strictEqual(util.format('%o'), '%o');\n    assert.strictEqual(util.format('%o', 42), '42');\n    assert.strictEqual(util.format('%o', 'foo'), \"'foo'\");\n    assert.strictEqual(\n      util.format('%o', obj),\n      '{\\n' +\n        \"  foo: 'bar',\\n\" +\n        '  foobar: 1,\\n' +\n        '  func: <ref *1> [Function: func] {\\n' +\n        '    [length]: 0,\\n' +\n        \"    [name]: 'func',\\n\" +\n        '    [prototype]: { [constructor]: [Circular *1] }\\n' +\n        '  }\\n' +\n        '}'\n    );\n    assert.strictEqual(\n      util.format('%o', nestedObj2),\n      '{\\n' +\n        \"  foo: 'bar',\\n\" +\n        '  foobar: 1,\\n' +\n        '  func: [\\n' +\n        '    {\\n' +\n        '      a: <ref *1> [Function: a] {\\n' +\n        '        [length]: 0,\\n' +\n        \"        [name]: 'a',\\n\" +\n        '        [prototype]: { [constructor]: [Circular *1] }\\n' +\n        '      }\\n' +\n        '    },\\n' +\n        '    [length]: 1\\n' +\n        '  ]\\n' +\n        '}'\n    );\n    assert.strictEqual(\n      util.format('%o', nestedObj),\n      '{\\n' +\n        \"  foo: 'bar',\\n\" +\n        '  foobar: {\\n' +\n        \"    foo: 'bar',\\n\" +\n        '    func: <ref *1> [Function: func] {\\n' +\n        '      [length]: 0,\\n' +\n        \"      [name]: 'func',\\n\" +\n        '      [prototype]: { [constructor]: [Circular *1] }\\n' +\n        '    }\\n' +\n        '  }\\n' +\n        '}'\n    );\n    assert.strictEqual(\n      util.format('%o %o', obj, obj),\n      '{\\n' +\n        \"  foo: 'bar',\\n\" +\n        '  foobar: 1,\\n' +\n        '  func: <ref *1> [Function: func] {\\n' +\n        '    [length]: 0,\\n' +\n        \"    [name]: 'func',\\n\" +\n        '    [prototype]: { [constructor]: [Circular *1] }\\n' +\n        '  }\\n' +\n        '} {\\n' +\n        \"  foo: 'bar',\\n\" +\n        '  foobar: 1,\\n' +\n        '  func: <ref *1> [Function: func] {\\n' +\n        '    [length]: 0,\\n' +\n        \"    [name]: 'func',\\n\" +\n        '    [prototype]: { [constructor]: [Circular *1] }\\n' +\n        '  }\\n' +\n        '}'\n    );\n    assert.strictEqual(\n      util.format('%o %o', obj),\n      '{\\n' +\n        \"  foo: 'bar',\\n\" +\n        '  foobar: 1,\\n' +\n        '  func: <ref *1> [Function: func] {\\n' +\n        '    [length]: 0,\\n' +\n        \"    [name]: 'func',\\n\" +\n        '    [prototype]: { [constructor]: [Circular *1] }\\n' +\n        '  }\\n' +\n        '} %o'\n    );\n\n    assert.strictEqual(util.format('%O'), '%O');\n    assert.strictEqual(util.format('%O', 42), '42');\n    assert.strictEqual(util.format('%O', 'foo'), \"'foo'\");\n    assert.strictEqual(\n      util.format('%O', obj),\n      \"{ foo: 'bar', foobar: 1, func: [Function: func] }\"\n    );\n    assert.strictEqual(\n      util.format('%O', nestedObj),\n      \"{ foo: 'bar', foobar: { foo: 'bar', func: [Function: func] } }\"\n    );\n    assert.strictEqual(\n      util.format('%O %O', obj, obj),\n      \"{ foo: 'bar', foobar: 1, func: [Function: func] } \" +\n        \"{ foo: 'bar', foobar: 1, func: [Function: func] }\"\n    );\n    assert.strictEqual(\n      util.format('%O %O', obj),\n      \"{ foo: 'bar', foobar: 1, func: [Function: func] } %O\"\n    );\n\n    // Various format specifiers\n    assert.strictEqual(util.format('%%s%s', 'foo'), '%sfoo');\n    assert.strictEqual(util.format('%s:%s'), '%s:%s');\n    assert.strictEqual(util.format('%s:%s', undefined), 'undefined:%s');\n    assert.strictEqual(util.format('%s:%s', 'foo'), 'foo:%s');\n    assert.strictEqual(util.format('%s:%i', 'foo'), 'foo:%i');\n    assert.strictEqual(util.format('%s:%f', 'foo'), 'foo:%f');\n    assert.strictEqual(util.format('%s:%s', 'foo', 'bar'), 'foo:bar');\n    assert.strictEqual(\n      util.format('%s:%s', 'foo', 'bar', 'baz'),\n      'foo:bar baz'\n    );\n    assert.strictEqual(util.format('%%%s%%', 'hi'), '%hi%');\n    assert.strictEqual(util.format('%%%s%%%%', 'hi'), '%hi%%');\n    assert.strictEqual(util.format('%sbc%%def', 'a'), 'abc%def');\n    assert.strictEqual(util.format('%d:%d', 12, 30), '12:30');\n    assert.strictEqual(util.format('%d:%d', 12), '12:%d');\n    assert.strictEqual(util.format('%d:%d'), '%d:%d');\n    assert.strictEqual(util.format('%i:%i', 12, 30), '12:30');\n    assert.strictEqual(util.format('%i:%i', 12), '12:%i');\n    assert.strictEqual(util.format('%i:%i'), '%i:%i');\n    assert.strictEqual(util.format('%f:%f', 12, 30), '12:30');\n    assert.strictEqual(util.format('%f:%f', 12), '12:%f');\n    assert.strictEqual(util.format('%f:%f'), '%f:%f');\n    assert.strictEqual(util.format('o: %j, a: %j', {}, []), 'o: {}, a: []');\n    assert.strictEqual(util.format('o: %j, a: %j', {}), 'o: {}, a: %j');\n    assert.strictEqual(util.format('o: %j, a: %j'), 'o: %j, a: %j');\n    assert.strictEqual(util.format('o: %o, a: %O', {}, []), 'o: {}, a: []');\n    assert.strictEqual(util.format('o: %o, a: %o', {}), 'o: {}, a: %o');\n    assert.strictEqual(util.format('o: %O, a: %O'), 'o: %O, a: %O');\n\n    // Invalid format specifiers\n    assert.strictEqual(util.format('a% b', 'x'), 'a% b x');\n    assert.strictEqual(\n      util.format('percent: %d%, fraction: %d', 10, 0.1),\n      'percent: 10%, fraction: 0.1'\n    );\n    assert.strictEqual(util.format('abc%', 1), 'abc% 1');\n\n    // Additional arguments after format specifiers\n    assert.strictEqual(util.format('%i', 1, 'number'), '1 number');\n    assert.strictEqual(\n      util.format('%i', 1, () => {}),\n      '1 [Function (anonymous)]'\n    );\n\n    // %c from https://console.spec.whatwg.org/\n    assert.strictEqual(util.format('%c'), '%c');\n    assert.strictEqual(util.format('%cab'), '%cab');\n    assert.strictEqual(util.format('%cab', 'color: blue'), 'ab');\n    assert.strictEqual(util.format('%cab', 'color: blue', 'c'), 'ab c');\n\n    {\n      const o = {};\n      o.o = o;\n      assert.strictEqual(util.format('%j', o), '[Circular]');\n    }\n\n    {\n      const o = {\n        toJSON() {\n          throw new Error('Not a circular object but still not serializable');\n        },\n      };\n      assert.throws(\n        () => util.format('%j', o),\n        /^Error: Not a circular object but still not serializable$/\n      );\n    }\n\n    // Errors\n    const err = new Error('foo');\n    assert.strictEqual(util.format(err), err.stack);\n    class CustomError extends Error {\n      constructor(msg) {\n        super();\n        Object.defineProperty(this, 'message', {\n          value: msg,\n          enumerable: false,\n        });\n        Object.defineProperty(this, 'name', {\n          value: 'CustomError',\n          enumerable: false,\n        });\n        Error.captureStackTrace(this, CustomError);\n      }\n    }\n    const customError = new CustomError('bar');\n    assert.strictEqual(util.format(customError), customError.stack);\n    // Doesn't capture stack trace\n    function BadCustomError(msg) {\n      Error.call(this);\n      Object.defineProperty(this, 'message', { value: msg, enumerable: false });\n      Object.defineProperty(this, 'name', {\n        value: 'BadCustomError',\n        enumerable: false,\n      });\n    }\n    Object.setPrototypeOf(BadCustomError.prototype, Error.prototype);\n    Object.setPrototypeOf(BadCustomError, Error);\n    assert.strictEqual(\n      util.format(new BadCustomError('foo')),\n      '[BadCustomError: foo]'\n    );\n\n    // The format of arguments should not depend on type of the first argument\n    assert.strictEqual(util.format('1', '1'), '1 1');\n    assert.strictEqual(util.format(1, '1'), '1 1');\n    assert.strictEqual(util.format('1', 1), '1 1');\n    assert.strictEqual(util.format(1, -0), '1 -0');\n    assert.strictEqual(\n      util.format('1', () => {}),\n      '1 [Function (anonymous)]'\n    );\n    assert.strictEqual(\n      util.format(1, () => {}),\n      '1 [Function (anonymous)]'\n    );\n    assert.strictEqual(util.format('1', \"'\"), \"1 '\");\n    assert.strictEqual(util.format(1, \"'\"), \"1 '\");\n    assert.strictEqual(util.format('1', 'number'), '1 number');\n    assert.strictEqual(util.format(1, 'number'), '1 number');\n    assert.strictEqual(util.format(5n), '5n');\n    assert.strictEqual(util.format(5n, 5n), '5n 5n');\n\n    // Check `formatWithOptions`.\n    assert.strictEqual(\n      util.formatWithOptions(\n        { colors: true },\n        true,\n        undefined,\n        Symbol(),\n        1,\n        5n,\n        null,\n        'foobar'\n      ),\n      '\\u001b[33mtrue\\u001b[39m ' +\n        '\\u001b[90mundefined\\u001b[39m ' +\n        '\\u001b[32mSymbol()\\u001b[39m ' +\n        '\\u001b[33m1\\u001b[39m ' +\n        '\\u001b[33m5n\\u001b[39m ' +\n        '\\u001b[1mnull\\u001b[22m ' +\n        'foobar'\n    );\n\n    assert.strictEqual(\n      util.format(new SharedArrayBuffer(4)),\n      'SharedArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }'\n    );\n\n    assert.strictEqual(\n      util.formatWithOptions({ colors: true, compact: 3 }, '%s', [\n        1,\n        { a: true },\n      ]),\n      '[ 1, [Object] ]'\n    );\n\n    [undefined, null, false, 5n, 5, 'test', Symbol()].forEach(\n      (invalidOptions) => {\n        assert.throws(\n          () => {\n            util.formatWithOptions(invalidOptions, { a: true });\n          },\n          {\n            code: 'ERR_INVALID_ARG_TYPE',\n            message: /\"inspectOptions\".+object/,\n          }\n        );\n      }\n    );\n  },\n};\n\nexport const utilInspectError = {\n  // message/util_inspect_error.js\n  async test(ctrl, env, ctx) {\n    const err = new Error('foo\\nbar');\n\n    assert.match(\n      util.inspect({ err, nested: { err } }, { compact: true }),\n      /{ err: Error: foo\\n   bar\\n       at .+,\\n  nested: { err: Error: foo\\n      bar\\n          at .+ } }/s\n    );\n    assert.match(\n      util.inspect({ err, nested: { err } }, { compact: false }),\n      /{\\n  err: Error: foo\\n  bar\\n      at .+,\\n  nested: {\\n    err: Error: foo\\n    bar\\n        at .+\\n  }\\n}/s\n    );\n\n    err.foo = 'bar';\n    assert.match(\n      util.inspect(err, { compact: true, breakLength: 5 }),\n      /{ Error: foo\\nbar\\n    at .+\\n  foo: 'bar' }/\n    );\n  },\n};\n\nexport const logTest = {\n  test() {\n    const original = console.log;\n    console.log = mock.fn();\n\n    util.log('test');\n\n    assert.strictEqual(console.log.mock.callCount(), 1);\n    const args = console.log.mock.calls[0].arguments;\n    assert.strictEqual(args.length, 3);\n    assert.strictEqual(args[0], '%s - %s');\n    // skipping the check on args[1] since it'll be a timestamp that changes\n    assert.strictEqual(args[2], 'test');\n\n    console.log = original;\n  },\n};\n\nexport const aborted = {\n  async test() {\n    const signal = AbortSignal.timeout(10);\n    await util.aborted(signal, {});\n\n    await assert.rejects(util.aborted({}, {}), {\n      message:\n        'The \"signal\" argument must be an instance of AbortSignal. ' +\n        'Received an instance of Object',\n    });\n  },\n};\n\nexport const debuglog = {\n  test() {\n    const original = console.log;\n    console.log = mock.fn();\n\n    util.debuglog('test')('hello');\n\n    assert.strictEqual(console.log.mock.callCount(), 1);\n    const args = console.log.mock.calls[0].arguments;\n    assert.strictEqual(args.length, 1);\n    assert.strictEqual(args[0], 'TEST: hello\\n');\n\n    console.log = original;\n  },\n};\n\nexport const getCallSitesTest = {\n  test() {\n    const callSites = util.getCallSites();\n    assert.strictEqual(callSites.length, 1);\n    const [stack] = callSites;\n    assert.strictEqual(stack.functionName, 'test');\n    assert.strictEqual(stack.scriptName, 'worker');\n    assert.strictEqual(typeof stack.lineNumber, 'number');\n    assert.strictEqual(typeof stack.columnNumber, 'number');\n    assert.strictEqual(typeof stack.column, 'number');\n\n    // We leave this implementation for compat reasons.\n    // Node.js originally introduced the API with the name `getCallSite()` as an experimental\n    // API but then renamed it to `getCallSites()` soon after. We had already implemented the\n    // API with the original name in a release. To avoid the possibility of breaking, we export\n    // the function using both names.\n    assert.strictEqual(typeof util.getCallSite, 'function');\n  },\n};\n\nexport const isDeepStrictEqual = {\n  // https://github.com/nodejs/node/blob/2be863be08ff9f16eae6bb907388c354c55c3bfc/test/parallel/test-util-isDeepStrictEqual.js\n  test() {\n    function utilIsDeepStrict(a, b) {\n      assert.strictEqual(util.isDeepStrictEqual(a, b), true);\n      assert.strictEqual(util.isDeepStrictEqual(b, a), true);\n    }\n\n    function notUtilIsDeepStrict(a, b) {\n      assert.strictEqual(util.isDeepStrictEqual(a, b), false);\n      assert.strictEqual(util.isDeepStrictEqual(b, a), false);\n    }\n\n    // Handle boxed primitives\n    {\n      const boxedString = new String('test');\n      const boxedSymbol = Object(Symbol());\n      notUtilIsDeepStrict(new Boolean(true), Object(false));\n      notUtilIsDeepStrict(Object(true), new Number(1));\n      notUtilIsDeepStrict(new Number(2), new Number(1));\n      notUtilIsDeepStrict(boxedSymbol, Object(Symbol()));\n      notUtilIsDeepStrict(boxedSymbol, {});\n      utilIsDeepStrict(boxedSymbol, boxedSymbol);\n      utilIsDeepStrict(Object(true), Object(true));\n      utilIsDeepStrict(Object(2), Object(2));\n      utilIsDeepStrict(boxedString, Object('test'));\n      boxedString.slow = true;\n      notUtilIsDeepStrict(boxedString, Object('test'));\n      boxedSymbol.slow = true;\n      notUtilIsDeepStrict(boxedSymbol, {});\n      utilIsDeepStrict(Object(BigInt(1)), Object(BigInt(1)));\n      notUtilIsDeepStrict(Object(BigInt(1)), Object(BigInt(2)));\n\n      const booleanish = new Boolean(true);\n      Object.defineProperty(booleanish, Symbol.toStringTag, {\n        value: 'String',\n      });\n      Object.setPrototypeOf(booleanish, String.prototype);\n      notUtilIsDeepStrict(booleanish, new String('true'));\n\n      const numberish = new Number(42);\n      Object.defineProperty(numberish, Symbol.toStringTag, { value: 'String' });\n      Object.setPrototypeOf(numberish, String.prototype);\n      notUtilIsDeepStrict(numberish, new String('42'));\n\n      const stringish = new String('0');\n      Object.defineProperty(stringish, Symbol.toStringTag, { value: 'Number' });\n      Object.setPrototypeOf(stringish, Number.prototype);\n      notUtilIsDeepStrict(stringish, new Number(0));\n\n      const bigintish = new Object(BigInt(42));\n      Object.defineProperty(bigintish, Symbol.toStringTag, { value: 'String' });\n      Object.setPrototypeOf(bigintish, String.prototype);\n      notUtilIsDeepStrict(bigintish, new String('42'));\n\n      const symbolish = new Object(Symbol('fhqwhgads'));\n      Object.defineProperty(symbolish, Symbol.toStringTag, { value: 'String' });\n      Object.setPrototypeOf(symbolish, String.prototype);\n      notUtilIsDeepStrict(symbolish, new String('fhqwhgads'));\n    }\n\n    // Handle symbols (enumerable only)\n    {\n      const symbol1 = Symbol();\n      const obj1 = { [symbol1]: 1 };\n      const obj2 = { [symbol1]: 1 };\n      const obj3 = { [Symbol()]: 1 };\n      const obj4 = {};\n      // Add a non enumerable symbol as well. It is going to be ignored!\n      Object.defineProperty(obj2, Symbol(), { value: 1 });\n      Object.defineProperty(obj4, symbol1, { value: 1 });\n      notUtilIsDeepStrict(obj1, obj3);\n      utilIsDeepStrict(obj1, obj2);\n      notUtilIsDeepStrict(obj1, obj4);\n      // TypedArrays have a fast path. Test for this as well.\n      const a = new Uint8Array(4);\n      const b = new Uint8Array(4);\n      a[symbol1] = true;\n      b[symbol1] = false;\n      notUtilIsDeepStrict(a, b);\n      b[symbol1] = true;\n      utilIsDeepStrict(a, b);\n      // The same as TypedArrays is valid for boxed primitives\n      const boxedStringA = new String('test');\n      const boxedStringB = new String('test');\n      boxedStringA[symbol1] = true;\n      notUtilIsDeepStrict(boxedStringA, boxedStringB);\n      boxedStringA[symbol1] = true;\n      utilIsDeepStrict(a, b);\n    }\n  },\n};\n\nexport const isArray = {\n  test() {\n    assert.ok(util.isArray([]));\n    assert.ok(!util.isArray('hello world'));\n  },\n};\n\nexport const makeSureUtilTypesIsExported = {\n  async test() {\n    const types = await import('node:util/types');\n    assert.ok(types, 'node:util/types is not exported');\n    assert.ok(types.isTypedArray);\n    assert.ok(types.default.isTypedArray);\n  },\n};\n\nexport const testTypes = {\n  async test() {\n    const {\n      isCryptoKey,\n      isKeyObject,\n      isAsyncFunction,\n      isGeneratorFunction,\n      isGeneratorObject,\n      isAnyArrayBuffer,\n      isArrayBuffer,\n      isArgumentsObject,\n      isBoxedPrimitive,\n      isDataView,\n      isMap,\n      isMapIterator,\n      isModuleNamespaceObject,\n      isNativeError,\n      isPromise,\n      isProxy,\n      isSet,\n      isSetIterator,\n      isSharedArrayBuffer,\n      isWeakMap,\n      isWeakSet,\n      isRegExp,\n      isDate,\n      isStringObject,\n      isSymbolObject,\n      isNumberObject,\n      isBooleanObject,\n      isBigIntObject,\n      isArrayBufferView,\n      isBigInt64Array,\n      isBigUint64Array,\n      isFloat16Array,\n      isFloat32Array,\n      isFloat64Array,\n      isInt8Array,\n      isInt16Array,\n      isInt32Array,\n      isTypedArray,\n      isUint8Array,\n      isUint8ClampedArray,\n      isUint16Array,\n      isUint32Array,\n      isExternal,\n    } = await import('node:util/types');\n\n    {\n      const key = await crypto.subtle.importKey(\n        'raw',\n        new Uint8Array(3),\n        {\n          name: 'HMAC',\n          hash: 'SHA-256',\n        },\n        false,\n        ['sign']\n      );\n      assert.ok(isCryptoKey(key));\n      assert.ok(!isCryptoKey(1));\n    }\n\n    {\n      const { createSecretKey } = await import('node:crypto');\n      const key = createSecretKey('hello', 'utf8');\n      assert.ok(isKeyObject(key));\n      assert.ok(!isKeyObject(1));\n    }\n\n    {\n      const foo = async () => {};\n      assert.ok(isAsyncFunction(foo));\n      assert.ok(!isAsyncFunction(1));\n    }\n\n    {\n      function* foo() {}\n      assert.ok(isGeneratorFunction(foo));\n      assert.ok(!isGeneratorFunction(1));\n    }\n\n    {\n      function* foo() {}\n      const gen = foo();\n      assert.ok(isGeneratorObject(gen));\n      assert.ok(!isGeneratorObject(1));\n    }\n\n    {\n      assert.ok(isAnyArrayBuffer(new ArrayBuffer(0)));\n      assert.ok(isAnyArrayBuffer(new SharedArrayBuffer(0)));\n      assert.ok(!isAnyArrayBuffer(1));\n    }\n\n    {\n      assert.ok(isArrayBuffer(new ArrayBuffer(0)));\n      assert.ok(!isArrayBuffer(new SharedArrayBuffer(0)));\n      assert.ok(!isArrayBuffer(1));\n    }\n\n    {\n      (function () {\n        assert.ok(isArgumentsObject(arguments));\n        assert.ok(!isArgumentsObject(1));\n      })();\n    }\n\n    {\n      assert.ok(isBoxedPrimitive(new String('')));\n      assert.ok(!isBoxedPrimitive(1));\n    }\n\n    {\n      assert.ok(isDataView(new DataView(new ArrayBuffer(1))));\n      assert.ok(!isDataView(1));\n    }\n\n    {\n      assert.ok(isMap(new Map()));\n      assert.ok(!isMap(1));\n    }\n\n    {\n      const map = new Map();\n      assert.ok(isMapIterator(map.values()));\n      assert.ok(!isMapIterator(1));\n    }\n\n    {\n      const mod = await import('node:net');\n      assert.ok(isModuleNamespaceObject(mod));\n      assert.ok(!isModuleNamespaceObject(1));\n    }\n\n    {\n      assert.ok(isNativeError(new Error()));\n      assert.ok(!isNativeError(1));\n    }\n\n    {\n      assert.ok(isPromise(Promise.resolve()));\n      assert.ok(!isPromise(1));\n    }\n\n    {\n      assert.ok(isProxy(new Proxy({}, {})));\n      assert.ok(!isProxy(1));\n    }\n\n    {\n      assert.ok(isSet(new Set()));\n      assert.ok(!isSet(1));\n    }\n\n    {\n      const set = new Set();\n      assert.ok(isSetIterator(set.values()));\n      assert.ok(!isSetIterator(1));\n    }\n\n    {\n      assert.ok(isSharedArrayBuffer(new SharedArrayBuffer(0)));\n      assert.ok(!isSharedArrayBuffer(new ArrayBuffer(0)));\n      assert.ok(!isSharedArrayBuffer(1));\n    }\n\n    {\n      assert.ok(isWeakMap(new WeakMap()));\n      assert.ok(!isWeakMap(1));\n    }\n\n    {\n      assert.ok(isWeakSet(new WeakSet()));\n      assert.ok(!isWeakSet(1));\n    }\n\n    {\n      assert.ok(isRegExp(/abc/));\n      assert.ok(!isRegExp(1));\n    }\n\n    {\n      assert.ok(isDate(new Date()));\n      assert.ok(!isDate(1));\n    }\n\n    {\n      assert.ok(isStringObject(new String('')));\n      assert.ok(!isStringObject(''));\n    }\n\n    {\n      assert.ok(isSymbolObject(Object(Symbol('test'))));\n      assert.ok(!isSymbolObject(1));\n    }\n\n    {\n      assert.ok(isNumberObject(new Number(1)));\n      assert.ok(!isNumberObject(1));\n    }\n\n    {\n      assert.ok(isBooleanObject(new Boolean()));\n      assert.ok(!isBooleanObject(1));\n    }\n\n    {\n      assert.ok(isBigIntObject(Object(1n)));\n      assert.ok(!isBigIntObject(1));\n    }\n\n    {\n      assert.ok(isArrayBufferView(new Uint8Array(0)));\n      assert.ok(isArrayBufferView(new DataView(new ArrayBuffer(0))));\n      assert.ok(!isArrayBufferView(1));\n    }\n\n    {\n      assert.ok(isBigInt64Array(new BigInt64Array(0)));\n      assert.ok(!isBigInt64Array(1));\n    }\n\n    {\n      assert.ok(isBigUint64Array(new BigUint64Array(0)));\n      assert.ok(!isBigUint64Array(1));\n    }\n\n    {\n      assert.ok(isFloat16Array(new Float16Array(0)));\n      assert.ok(!isFloat16Array(1));\n    }\n\n    {\n      assert.ok(isFloat32Array(new Float32Array(0)));\n      assert.ok(!isFloat32Array(1));\n    }\n\n    {\n      assert.ok(isFloat64Array(new Float64Array(0)));\n      assert.ok(!isFloat64Array(1));\n    }\n\n    {\n      assert.ok(isInt8Array(new Int8Array(0)));\n      assert.ok(!isInt8Array(1));\n    }\n\n    {\n      assert.ok(isInt16Array(new Int16Array(0)));\n      assert.ok(!isInt16Array(1));\n    }\n\n    {\n      assert.ok(isInt32Array(new Int32Array(0)));\n      assert.ok(!isInt32Array(1));\n    }\n\n    {\n      assert.ok(isTypedArray(new Uint8Array(0)));\n      assert.ok(!isTypedArray(new DataView(new ArrayBuffer(0))));\n      assert.ok(!isTypedArray(1));\n    }\n\n    {\n      assert.ok(isUint8Array(new Uint8Array(0)));\n      assert.ok(!isUint8Array(1));\n    }\n\n    {\n      assert.ok(isUint8ClampedArray(new Uint8ClampedArray(0)));\n      assert.ok(!isUint8ClampedArray(new Uint8Array(0)));\n      assert.ok(!isUint8ClampedArray(1));\n    }\n\n    {\n      assert.ok(isUint16Array(new Uint16Array(0)));\n      assert.ok(!isUint16Array(1));\n    }\n\n    {\n      assert.ok(isUint32Array(new Uint32Array(0)));\n      assert.ok(!isUint32Array(1));\n    }\n\n    {\n      // We don't really expose any externals in any existing APIS\n      // where this would be useful, but hey, let's test it anyway.\n      assert.ok(!isExternal({}));\n    }\n  },\n};\n\n// https://github.com/nodejs/node/blob/2be863be08ff9f16eae6bb907388c354c55c3bfc/test/parallel/test-util-inspect-getters-accessing-this.js\nexport const testInspectGetters = {\n  async test() {\n    // This test ensures that util.inspect logs getters\n    // which access this.\n    {\n      class X {\n        constructor() {\n          this._y = 123;\n        }\n\n        get y() {\n          return this._y;\n        }\n      }\n\n      const result = inspect(new X(), {\n        getters: true,\n        showHidden: true,\n      });\n\n      assert.strictEqual(result, 'X { _y: 123, [y]: [Getter: 123] }');\n    }\n\n    // Regression test for https://github.com/nodejs/node/issues/37054\n    {\n      class A {\n        constructor(B) {\n          this.B = B;\n        }\n        get b() {\n          return this.B;\n        }\n      }\n\n      class B {\n        constructor() {\n          this.A = new A(this);\n        }\n        get a() {\n          return this.A;\n        }\n      }\n\n      const result = inspect(new B(), {\n        depth: 1,\n        getters: true,\n        showHidden: true,\n      });\n\n      assert.strictEqual(\n        result,\n        '<ref *1> B {\\n' +\n          '  A: A { B: [Circular *1], [b]: [Getter] [Circular *1] },\\n' +\n          '  [a]: [Getter] A { B: [Circular *1], [b]: [Getter] [Circular *1] }\\n' +\n          '}'\n      );\n    }\n  },\n};\n\n// https://github.com/nodejs/node/blob/2be863be08ff9f16eae6bb907388c354c55c3bfc/test/parallel/test-util-callbackify.js\nexport const testCallbackify = {\n  async test() {\n    const values = [\n      'hello world',\n      null,\n      undefined,\n      false,\n      0,\n      {},\n      { key: 'value' },\n      Symbol('I am a symbol'),\n      function ok() {},\n      ['array', 'with', 4, 'values'],\n      new Error('boo'),\n    ];\n\n    {\n      // Test that the resolution value is passed as second argument to callback\n      for (const value of values) {\n        // Test and `async function`\n        async function asyncFn() {\n          return value;\n        }\n\n        const cbAsyncFn = callbackify(asyncFn);\n        cbAsyncFn(\n          commonMustCall(function (err, ret) {\n            assert.ifError(err);\n            assert.strictEqual(ret, value);\n          })\n        );\n\n        // Test Promise factory\n        function promiseFn() {\n          return Promise.resolve(value);\n        }\n\n        const cbPromiseFn = callbackify(promiseFn);\n        cbPromiseFn(\n          commonMustCall(function (err, ret) {\n            assert.ifError(err);\n            assert.strictEqual(ret, value);\n          })\n        );\n\n        // Test Thenable\n        function thenableFn() {\n          return {\n            then(onRes, onRej) {\n              onRes(value);\n            },\n          };\n        }\n\n        const cbThenableFn = callbackify(thenableFn);\n        cbThenableFn(\n          commonMustCall(function (err, ret) {\n            assert.ifError(err);\n            assert.strictEqual(ret, value);\n          })\n        );\n      }\n    }\n\n    {\n      // Test that rejection reason is passed as first argument to callback\n      for (const value of values) {\n        // Test an `async function`\n        async function asyncFn() {\n          return Promise.reject(value);\n        }\n\n        const cbAsyncFn = callbackify(asyncFn);\n        assert.strictEqual(cbAsyncFn.length, 1);\n        assert.strictEqual(cbAsyncFn.name, 'asyncFnCallbackified');\n        cbAsyncFn(\n          commonMustCall((err, ret) => {\n            assert.strictEqual(ret, undefined);\n            if (err instanceof Error) {\n              if ('reason' in err) {\n                assert(!value);\n                assert.strictEqual(err.code, 'ERR_FALSY_VALUE_REJECTION');\n                assert.strictEqual(err.reason, value);\n              } else {\n                assert.strictEqual(String(value).endsWith(err.message), true);\n              }\n            } else {\n              assert.strictEqual(err, value);\n            }\n          })\n        );\n\n        // Test a Promise factory\n        function promiseFn() {\n          return Promise.reject(value);\n        }\n        const obj = {};\n        Object.defineProperty(promiseFn, 'name', {\n          value: obj,\n          writable: false,\n          enumerable: false,\n          configurable: true,\n        });\n\n        const cbPromiseFn = callbackify(promiseFn);\n        assert.strictEqual(promiseFn.name, obj);\n        cbPromiseFn(\n          commonMustCall((err, ret) => {\n            assert.strictEqual(ret, undefined);\n            if (err instanceof Error) {\n              if ('reason' in err) {\n                assert(!value);\n                assert.strictEqual(err.code, 'ERR_FALSY_VALUE_REJECTION');\n                assert.strictEqual(err.reason, value);\n              } else {\n                assert.strictEqual(String(value).endsWith(err.message), true);\n              }\n            } else {\n              assert.strictEqual(err, value);\n            }\n          })\n        );\n\n        // Test Thenable\n        function thenableFn() {\n          return {\n            then(onRes, onRej) {\n              onRej(value);\n            },\n          };\n        }\n\n        const cbThenableFn = callbackify(thenableFn);\n        cbThenableFn(\n          commonMustCall((err, ret) => {\n            assert.strictEqual(ret, undefined);\n            if (err instanceof Error) {\n              if ('reason' in err) {\n                assert(!value);\n                assert.strictEqual(err.code, 'ERR_FALSY_VALUE_REJECTION');\n                assert.strictEqual(err.reason, value);\n              } else {\n                assert.strictEqual(String(value).endsWith(err.message), true);\n              }\n            } else {\n              assert.strictEqual(err, value);\n            }\n          })\n        );\n      }\n    }\n\n    await new Promise((resolve) => queueMicrotask(resolve));\n\n    {\n      // Test that arguments passed to callbackified function are passed to original\n      for (const value of values) {\n        async function asyncFn(arg) {\n          assert.strictEqual(arg, value);\n          return arg;\n        }\n\n        const cbAsyncFn = callbackify(asyncFn);\n        assert.strictEqual(cbAsyncFn.length, 2);\n        assert.notStrictEqual(\n          Object.getPrototypeOf(cbAsyncFn),\n          Object.getPrototypeOf(asyncFn)\n        );\n        assert.strictEqual(\n          Object.getPrototypeOf(cbAsyncFn),\n          Function.prototype\n        );\n        cbAsyncFn(\n          value,\n          commonMustCall(function (err, ret) {\n            assert.ifError(err);\n            assert.strictEqual(ret, value);\n          })\n        );\n\n        function promiseFn(arg) {\n          assert.strictEqual(arg, value);\n          return Promise.resolve(arg);\n        }\n        const obj = {};\n        Object.defineProperty(promiseFn, 'length', {\n          value: obj,\n          writable: false,\n          enumerable: false,\n          configurable: true,\n        });\n\n        const cbPromiseFn = callbackify(promiseFn);\n        assert.strictEqual(promiseFn.length, obj);\n        cbPromiseFn(\n          value,\n          commonMustCall(function (err, ret) {\n            assert.ifError(err);\n            assert.strictEqual(ret, value);\n          })\n        );\n      }\n    }\n\n    {\n      // Test that `this` binding is the same for callbackified and original\n      for (const value of values) {\n        const iAmThis = {\n          fn(arg) {\n            assert.strictEqual(this, iAmThis);\n            return Promise.resolve(arg);\n          },\n        };\n        iAmThis.cbFn = callbackify(iAmThis.fn);\n        iAmThis.cbFn(\n          value,\n          commonMustCall(function (err, ret) {\n            assert.ifError(err);\n            assert.strictEqual(ret, value);\n            assert.strictEqual(this, iAmThis);\n          })\n        );\n\n        const iAmThat = {\n          async fn(arg) {\n            assert.strictEqual(this, iAmThat);\n            return arg;\n          },\n        };\n        iAmThat.cbFn = callbackify(iAmThat.fn);\n        iAmThat.cbFn(\n          value,\n          commonMustCall(function (err, ret) {\n            assert.ifError(err);\n            assert.strictEqual(ret, value);\n            assert.strictEqual(this, iAmThat);\n          })\n        );\n      }\n    }\n\n    {\n      // Verify that non-function inputs throw.\n      ['foo', null, undefined, false, 0, {}, Symbol(), []].forEach((value) => {\n        assert.throws(\n          () => {\n            callbackify(value);\n          },\n          {\n            code: 'ERR_INVALID_ARG_TYPE',\n            name: 'TypeError',\n            message:\n              'The \"original\" argument must be of type function.' +\n              invalidArgTypeHelper(value),\n          }\n        );\n      });\n    }\n\n    {\n      async function asyncFn() {\n        return 42;\n      }\n\n      const cb = callbackify(asyncFn);\n      const args = [];\n\n      // Verify that the last argument to the callbackified function is a function.\n      ['foo', null, undefined, false, 0, {}, Symbol(), []].forEach((value) => {\n        args.push(value);\n        assert.throws(\n          () => {\n            cb(...args);\n          },\n          {\n            code: 'ERR_INVALID_ARG_TYPE',\n            name: 'TypeError',\n            message:\n              'The last argument must be of type function.' +\n              invalidArgTypeHelper(value),\n          }\n        );\n      });\n    }\n\n    {\n      // Test Promise factory\n      function promiseFn(value) {\n        return Promise.reject(value);\n      }\n\n      const cbPromiseFn = callbackify(promiseFn);\n\n      cbPromiseFn(null, (err) => {\n        assert.strictEqual(\n          err.message,\n          'Promise was rejected with falsy value'\n        );\n        assert.strictEqual(err.code, 'ERR_FALSY_VALUE_REJECTION');\n        assert.strictEqual(err.reason, null);\n        // const stack = err.stack.split(/[\\r\\n]+/);\n        // assert.match(stack[1], /at process\\.processTicksAndRejections/);\n      });\n    }\n\n    await new Promise((resolve) => queueMicrotask(resolve));\n\n    assertCalledMustCalls();\n  },\n};\n\n// https://github.com/nodejs/node/blob/2be863be08ff9f16eae6bb907388c354c55c3bfc/test/parallel/test-util-inherits.js\nexport const testInherits = {\n  async test() {\n    // Super constructor\n    function A() {\n      this._a = 'a';\n    }\n    A.prototype.a = function () {\n      return this._a;\n    };\n\n    // One level of inheritance\n    function B(value) {\n      A.call(this);\n      this._b = value;\n    }\n    inherits(B, A);\n    B.prototype.b = function () {\n      return this._b;\n    };\n\n    assert.deepStrictEqual(Object.getOwnPropertyDescriptor(B, 'super_'), {\n      value: A,\n      enumerable: false,\n      configurable: true,\n      writable: true,\n    });\n\n    const b = new B('b');\n    assert.strictEqual(b.a(), 'a');\n    assert.strictEqual(b.b(), 'b');\n    assert.strictEqual(b.constructor, B);\n\n    // Two levels of inheritance\n    function C() {\n      B.call(this, 'b');\n      this._c = 'c';\n    }\n    inherits(C, B);\n    C.prototype.c = function () {\n      return this._c;\n    };\n    C.prototype.getValue = function () {\n      return this.a() + this.b() + this.c();\n    };\n\n    assert.strictEqual(C.super_, B);\n\n    const c = new C();\n    assert.strictEqual(c.getValue(), 'abc');\n    assert.strictEqual(c.constructor, C);\n\n    // Inherits can be called after setting prototype properties\n    function D() {\n      C.call(this);\n      this._d = 'd';\n    }\n\n    D.prototype.d = function () {\n      return this._d;\n    };\n    inherits(D, C);\n\n    assert.strictEqual(D.super_, C);\n\n    const d = new D();\n    assert.strictEqual(d.c(), 'c');\n    assert.strictEqual(d.d(), 'd');\n    assert.strictEqual(d.constructor, D);\n\n    // ES6 classes can inherit from a constructor function\n    class E {\n      constructor() {\n        D.call(this);\n        this._e = 'e';\n      }\n      e() {\n        return this._e;\n      }\n    }\n    inherits(E, D);\n\n    assert.strictEqual(E.super_, D);\n\n    const e = new E();\n    assert.strictEqual(e.getValue(), 'abc');\n    assert.strictEqual(e.d(), 'd');\n    assert.strictEqual(e.e(), 'e');\n    assert.strictEqual(e.constructor, E);\n\n    // Should throw with invalid arguments\n    assert.throws(\n      () => {\n        inherits(A, {});\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n        message:\n          'The \"superCtor.prototype\" property must be of type object. ' +\n          'Received undefined',\n      }\n    );\n\n    assert.throws(\n      () => {\n        inherits(A, null);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n        message:\n          'The \"superCtor\" argument must be of type function. ' +\n          'Received null',\n      }\n    );\n\n    assert.throws(\n      () => {\n        inherits(null, A);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n        message: 'The \"ctor\" argument must be of type function. Received null',\n      }\n    );\n  },\n};\n\n// https://github.com/nodejs/node/blob/2be863be08ff9f16eae6bb907388c354c55c3bfc/test/parallel/test-util-promisify.js\nexport const testPromisify = {\n  async test() {\n    // TODO(soon): Enable once fs supported\n    // const stat = promisify(fs.stat);\n\n    // {\n    //   const promise = stat(__filename);\n    //   assert(promise instanceof Promise);\n    //   promise.then(mustCall((value) => {\n    //     assert.deepStrictEqual(value, fs.statSync(__filename));\n    //   }));\n    // }\n\n    // {\n    //   const promise = stat('/dontexist');\n    //   promise.catch(mustCall((error) => {\n    //     assert(error.message.includes('ENOENT: no such file or directory, stat'));\n    //   }));\n    // }\n\n    {\n      function fn() {}\n\n      function promisifedFn() {}\n      fn[promisify.custom] = promisifedFn;\n      assert.strictEqual(promisify(fn), promisifedFn);\n      assert.strictEqual(promisify(promisify(fn)), promisifedFn);\n    }\n\n    {\n      function fn() {}\n\n      function promisifiedFn() {}\n\n      // util.promisify.custom is a shared symbol which can be accessed\n      // as `Symbol.for(\"nodejs.util.promisify.custom\")`.\n      const kCustomPromisifiedSymbol = Symbol.for(\n        'nodejs.util.promisify.custom'\n      );\n      fn[kCustomPromisifiedSymbol] = promisifiedFn;\n\n      assert.strictEqual(kCustomPromisifiedSymbol, promisify.custom);\n      assert.strictEqual(promisify(fn), promisifiedFn);\n      assert.strictEqual(promisify(promisify(fn)), promisifiedFn);\n    }\n\n    {\n      function fn() {}\n      fn[promisify.custom] = 42;\n      assert.throws(() => promisify(fn), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n      });\n    }\n\n    // TODO(soon): customPromisifyArgs unsupported\n    // {\n    //   const firstValue = 5;\n    //   const secondValue = 17;\n\n    //   function fn(callback) {\n    //     callback(null, firstValue, secondValue);\n    //   }\n\n    //   fn[customPromisifyArgs] = ['first', 'second'];\n\n    //   promisify(fn)().then(mustCall((obj) => {\n    //     assert.deepStrictEqual(obj, { first: firstValue, second: secondValue });\n    //   }));\n    // }\n\n    // vm unsupported\n    // {\n    //   const fn = vm.runInNewContext('(function() {})');\n    //   assert.notStrictEqual(Object.getPrototypeOf(promisify(fn)),\n    //                         Function.prototype);\n    // }\n\n    {\n      function fn(callback) {\n        callback(null, 'foo', 'bar');\n      }\n      promisify(fn)().then(\n        commonMustCall((value) => {\n          assert.strictEqual(value, 'foo');\n        })\n      );\n    }\n\n    {\n      function fn(callback) {\n        callback(null);\n      }\n      promisify(fn)().then(\n        commonMustCall((value) => {\n          assert.strictEqual(value, undefined);\n        })\n      );\n    }\n\n    {\n      function fn(callback) {\n        callback();\n      }\n      promisify(fn)().then(\n        commonMustCall((value) => {\n          assert.strictEqual(value, undefined);\n        })\n      );\n    }\n\n    {\n      function fn(err, val, callback) {\n        callback(err, val);\n      }\n      promisify(fn)(null, 42).then(\n        commonMustCall((value) => {\n          assert.strictEqual(value, 42);\n        })\n      );\n    }\n\n    {\n      function fn(err, val, callback) {\n        callback(err, val);\n      }\n      promisify(fn)(new Error('oops'), null).catch(\n        commonMustCall((err) => {\n          assert.strictEqual(err.message, 'oops');\n        })\n      );\n    }\n\n    {\n      function fn(err, val, callback) {\n        callback(err, val);\n      }\n\n      (async () => {\n        const value = await promisify(fn)(null, 42);\n        assert.strictEqual(value, 42);\n      })().then(commonMustCall());\n    }\n\n    {\n      const o = {};\n      const fn = promisify(function (cb) {\n        cb(null, this === o);\n      });\n\n      o.fn = fn;\n\n      o.fn().then(commonMustCall((val) => assert(val)));\n    }\n\n    {\n      const err = new Error(\n        'Should not have called the callback with the error.'\n      );\n      const stack = err.stack;\n\n      const fn = promisify(function (cb) {\n        cb(null);\n        cb(err);\n      });\n\n      (async () => {\n        await fn();\n        await Promise.resolve();\n        return assert.strictEqual(stack, err.stack);\n      })().then(commonMustCall());\n    }\n\n    {\n      function c() {}\n      const a = promisify(function () {});\n      const b = promisify(a);\n      assert.notStrictEqual(c, a);\n      assert.strictEqual(a, b);\n    }\n\n    {\n      let errToThrow;\n      const thrower = promisify(function (a, b, c, cb) {\n        errToThrow = new Error();\n        throw errToThrow;\n      });\n      thrower(1, 2, 3)\n        .then(assert.fail)\n        .then(assert.fail, (e) => assert.strictEqual(e, errToThrow));\n    }\n\n    {\n      const err = new Error();\n\n      const a = promisify((cb) => cb(err))();\n      const b = promisify(() => {\n        throw err;\n      })();\n\n      await Promise.all([\n        a.then(assert.fail, function (e) {\n          assert.strictEqual(err, e);\n        }),\n        b.then(assert.fail, function (e) {\n          assert.strictEqual(err, e);\n        }),\n      ]);\n    }\n\n    [undefined, null, true, 0, 'str', {}, [], Symbol()].forEach((input) => {\n      assert.throws(() => promisify(input), {\n        code: 'ERR_INVALID_ARG_TYPE',\n        name: 'TypeError',\n        message:\n          'The \"original\" argument must be of type function.' +\n          invalidArgTypeHelper(input),\n      });\n    });\n  },\n};\n\n// https://github.com/nodejs/node/blob/2be863be08ff9f16eae6bb907388c354c55c3bfc/test/parallel/test-util-stripvtcontrolcharacters.js\nexport const testStripVtControlCharacters = {\n  async test() {\n    // Ref: https://github.com/chalk/ansi-regex/blob/main/test.js\n    const tests = [\n      // [before, expected]\n      [\n        '\\u001B[0m\\u001B[4m\\u001B[42m\\u001B[31mfoo\\u001B[39m\\u001B[49m\\u001B[24mfoo\\u001B[0m',\n        'foofoo',\n      ], // Basic ANSI\n      ['\\u001B[0;33;49;3;9;4mbar\\u001B[0m', 'bar'], // Advanced colors\n      ['foo\\u001B[0gbar', 'foobar'], // Clear tabs\n      ['foo\\u001B[Kbar', 'foobar'], // Clear line\n      ['foo\\u001B[2Jbar', 'foobar'], // Clear screen\n    ];\n\n    for (const ST of ['\\u0007', '\\u001B\\u005C', '\\u009C']) {\n      tests.push(\n        [`\\u001B]8;;mailto:no-replay@mail.com${ST}mail\\u001B]8;;${ST}`, 'mail'],\n        [\n          `\\u001B]8;k=v;https://example-a.com/?a_b=1&c=2#tit%20le${ST}click\\u001B]8;;${ST}`,\n          'click',\n        ]\n      );\n    }\n\n    for (const [before, expected] of tests) {\n      assert.strictEqual(stripVTControlCharacters(before), expected);\n    }\n  },\n};\n\nexport const testStyleText = {\n  async test() {\n    const styled = '\\u001b[31mtest\\u001b[39m';\n    const noChange = 'test';\n\n    [undefined, null, false, 5n, 5, Symbol(), () => {}, {}].forEach(\n      (invalidOption) => {\n        assert.throws(\n          () => {\n            styleText(invalidOption, 'test');\n          },\n          {\n            code: 'ERR_INVALID_ARG_VALUE',\n          }\n        );\n        assert.throws(\n          () => {\n            styleText('red', invalidOption);\n          },\n          {\n            code: 'ERR_INVALID_ARG_TYPE',\n          }\n        );\n      }\n    );\n\n    assert.throws(\n      () => {\n        styleText('invalid', 'text');\n      },\n      {\n        code: 'ERR_INVALID_ARG_VALUE',\n      }\n    );\n\n    assert.strictEqual(\n      styleText('red', 'test', { validateStream: false }),\n      '\\u001b[31mtest\\u001b[39m'\n    );\n\n    assert.strictEqual(\n      styleText(['bold', 'red'], 'test', { validateStream: false }),\n      '\\u001b[1m\\u001b[31mtest\\u001b[39m\\u001b[22m'\n    );\n\n    assert.strictEqual(\n      styleText(['bold', 'red'], 'test', { validateStream: false }),\n      styleText('bold', styleText('red', 'test', { validateStream: false }), {\n        validateStream: false,\n      })\n    );\n\n    assert.throws(\n      () => {\n        styleText(['invalid'], 'text');\n      },\n      {\n        code: 'ERR_INVALID_ARG_VALUE',\n      }\n    );\n\n    assert.throws(\n      () => {\n        styleText('red', 'text', { stream: {} });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // does not throw\n    styleText('red', 'text', { stream: {}, validateStream: false });\n\n    assert.strictEqual(\n      styleText('red', 'test', { validateStream: false }),\n      styled\n    );\n\n    assert.strictEqual(styleText('none', 'test'), 'test');\n  },\n};\n\nconst validContent = `BASIC=basic\n\n# COMMENTS=work\n#BASIC=basic2\n#BASIC=basic3\n\n# previous line intentionally left blank\nAFTER_LINE=after_line\nA=\"B=C\"\nB=C=D\nEMPTY=\nEMPTY_SINGLE_QUOTES=''\nEMPTY_DOUBLE_QUOTES=\"\"\nEMPTY_BACKTICKS=\\`\\`\nSINGLE_QUOTES='single_quotes'\nSINGLE_QUOTES_SPACED='    single quotes    '\nDOUBLE_QUOTES=\"double_quotes\"\nDOUBLE_QUOTES_SPACED=\"    double quotes    \"\nDOUBLE_QUOTES_INSIDE_SINGLE='double \"quotes\" work inside single quotes'\nDOUBLE_QUOTES_WITH_NO_SPACE_BRACKET=\"{ port: $MONGOLAB_PORT}\"\nSINGLE_QUOTES_INSIDE_DOUBLE=\"single 'quotes' work inside double quotes\"\nBACKTICKS_INSIDE_SINGLE='\\`backticks\\` work inside single quotes'\nBACKTICKS_INSIDE_DOUBLE=\"\\`backticks\\` work inside double quotes\"\nBACKTICKS=\\`backticks\\`\nBACKTICKS_SPACED=\\`    backticks    \\`\nDOUBLE_QUOTES_INSIDE_BACKTICKS=\\`double \"quotes\" work inside backticks\\`\nSINGLE_QUOTES_INSIDE_BACKTICKS=\\`single 'quotes' work inside backticks\\`\nDOUBLE_AND_SINGLE_QUOTES_INSIDE_BACKTICKS=\\`double \"quotes\" and single 'quotes' work inside backticks\\`\nEXPAND_NEWLINES=\"expand\\\\nnew\\\\nlines\"\nDONT_EXPAND_UNQUOTED=dontexpand\\\\nnewlines\nDONT_EXPAND_SQUOTED='dontexpand\\\\nnewlines'\n# COMMENTS=work\nINLINE_COMMENTS=inline comments # work #very #well\nINLINE_COMMENTS_SINGLE_QUOTES='inline comments outside of #singlequotes' # work\nINLINE_COMMENTS_DOUBLE_QUOTES=\"inline comments outside of #doublequotes\" # work\nINLINE_COMMENTS_BACKTICKS=\\`inline comments outside of #backticks\\` # work\nINLINE_COMMENTS_SPACE=inline comments start with a#number sign. no space required.\nEQUAL_SIGNS=equals==\nRETAIN_INNER_QUOTES={\"foo\": \"bar\"}\nRETAIN_INNER_QUOTES_AS_STRING='{\"foo\": \"bar\"}'\nRETAIN_INNER_QUOTES_AS_BACKTICKS=\\`{\"foo\": \"bar's\"}\\`\nTRIM_SPACE_FROM_UNQUOTED=    some spaced out string\nSPACE_BEFORE_DOUBLE_QUOTES=   \"space before double quotes\"\nEMAIL=therealnerdybeast@example.tld\n    SPACED_KEY = parsed\nEDGE_CASE_INLINE_COMMENTS=\"VALUE1\" # or \"VALUE2\" or \"VALUE3\"\n\nMULTI_DOUBLE_QUOTED=\"THIS\nIS\nA\nMULTILINE\nSTRING\"\n\nMULTI_SINGLE_QUOTED='THIS\nIS\nA\nMULTILINE\nSTRING'\n\nMULTI_BACKTICKED=\\`THIS\nIS\nA\n\"MULTILINE'S\"\nSTRING\\`\nexport EXPORT_EXAMPLE = ignore export\n\nMULTI_NOT_VALID_QUOTE=\"\nMULTI_NOT_VALID=THIS\nIS NOT MULTILINE`;\n\nexport const testParseEnv = {\n  async test() {\n    {\n      assert.deepStrictEqual(parseEnv(validContent), {\n        A: 'B=C',\n        B: 'C=D',\n        AFTER_LINE: 'after_line',\n        BACKTICKS: 'backticks',\n        BACKTICKS_INSIDE_DOUBLE: '`backticks` work inside double quotes',\n        BACKTICKS_INSIDE_SINGLE: '`backticks` work inside single quotes',\n        BACKTICKS_SPACED: '    backticks    ',\n        BASIC: 'basic',\n        DONT_EXPAND_SQUOTED: 'dontexpand\\\\nnewlines',\n        DONT_EXPAND_UNQUOTED: 'dontexpand\\\\nnewlines',\n        DOUBLE_AND_SINGLE_QUOTES_INSIDE_BACKTICKS:\n          'double \"quotes\" and single \\'quotes\\' work inside backticks',\n        DOUBLE_QUOTES: 'double_quotes',\n        DOUBLE_QUOTES_INSIDE_BACKTICKS: 'double \"quotes\" work inside backticks',\n        DOUBLE_QUOTES_INSIDE_SINGLE:\n          'double \"quotes\" work inside single quotes',\n        DOUBLE_QUOTES_SPACED: '    double quotes    ',\n        DOUBLE_QUOTES_WITH_NO_SPACE_BRACKET: '{ port: $MONGOLAB_PORT}',\n        EDGE_CASE_INLINE_COMMENTS: 'VALUE1',\n        EMAIL: 'therealnerdybeast@example.tld',\n        EMPTY: '',\n        EMPTY_BACKTICKS: '',\n        EMPTY_DOUBLE_QUOTES: '',\n        EMPTY_SINGLE_QUOTES: '',\n        EQUAL_SIGNS: 'equals==',\n        EXPORT_EXAMPLE: 'ignore export',\n        EXPAND_NEWLINES: 'expand\\nnew\\nlines',\n        INLINE_COMMENTS: 'inline comments',\n        INLINE_COMMENTS_BACKTICKS: 'inline comments outside of #backticks',\n        INLINE_COMMENTS_DOUBLE_QUOTES:\n          'inline comments outside of #doublequotes',\n        INLINE_COMMENTS_SINGLE_QUOTES:\n          'inline comments outside of #singlequotes',\n        INLINE_COMMENTS_SPACE: 'inline comments start with a',\n        MULTI_BACKTICKED: 'THIS\\nIS\\nA\\n\"MULTILINE\\'S\"\\nSTRING',\n        MULTI_DOUBLE_QUOTED: 'THIS\\nIS\\nA\\nMULTILINE\\nSTRING',\n        MULTI_NOT_VALID: 'THIS',\n        MULTI_NOT_VALID_QUOTE: '\"',\n        MULTI_SINGLE_QUOTED: 'THIS\\nIS\\nA\\nMULTILINE\\nSTRING',\n        RETAIN_INNER_QUOTES: '{\"foo\": \"bar\"}',\n        RETAIN_INNER_QUOTES_AS_BACKTICKS: '{\"foo\": \"bar\\'s\"}',\n        RETAIN_INNER_QUOTES_AS_STRING: '{\"foo\": \"bar\"}',\n        SINGLE_QUOTES: 'single_quotes',\n        SINGLE_QUOTES_INSIDE_BACKTICKS: \"single 'quotes' work inside backticks\",\n        SINGLE_QUOTES_INSIDE_DOUBLE:\n          \"single 'quotes' work inside double quotes\",\n        SINGLE_QUOTES_SPACED: '    single quotes    ',\n        SPACED_KEY: 'parsed',\n        SPACE_BEFORE_DOUBLE_QUOTES: 'space before double quotes',\n        TRIM_SPACE_FROM_UNQUOTED: 'some spaced out string',\n      });\n    }\n\n    assert.deepStrictEqual(parseEnv(''), {});\n    assert.deepStrictEqual(parseEnv('FOO=bar\\nFOO=baz\\n'), { FOO: 'baz' });\n\n    // Test for invalid input.\n    assert.throws(\n      () => {\n        for (const value of [null, undefined, {}, []]) {\n          parseEnv(value);\n        }\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n  },\n};\n\nexport const testNotImplemented = {\n  async test() {\n    for (const method of ['_errnoException', '_exceptionWithHostPort']) {\n      assert.throws(() => util[method](), {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      });\n    }\n  },\n};\n\nexport const testEndOfLife = {\n  async test() {\n    assert.strictEqual(typeof util.isArray, 'function');\n    assert.strictEqual(typeof util.isBoolean, 'function');\n    assert.strictEqual(typeof util.isBuffer, 'function');\n    assert.strictEqual(typeof util.isDate, 'function');\n    assert.strictEqual(typeof util.isError, 'function');\n    assert.strictEqual(typeof util.isFunction, 'function');\n    assert.strictEqual(typeof util.isNull, 'function');\n    assert.strictEqual(typeof util.isNullOrUndefined, 'function');\n    assert.strictEqual(typeof util.isNumber, 'function');\n    assert.strictEqual(typeof util.isObject, 'function');\n    assert.strictEqual(typeof util.isPrimitive, 'function');\n    assert.strictEqual(typeof util.isRegExp, 'function');\n    assert.strictEqual(typeof util.isString, 'function');\n    assert.strictEqual(typeof util.isSymbol, 'function');\n    assert.strictEqual(typeof util.isUndefined, 'function');\n\n    assert.strictEqual(util.isBuffer(true), false);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/util-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"nodejs-util-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"util-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/v8-nodejs-test.js",
    "content": "import * as v8 from 'node:v8';\nimport { strictEqual, deepStrictEqual, throws } from 'node:assert';\n\nexport const v8Test = {\n  test() {\n    // Note: test was generated by claude with manual tweaks\n\n    // Test cachedDataVersionTag function\n    strictEqual(typeof v8.cachedDataVersionTag, 'function');\n    strictEqual(v8.cachedDataVersionTag(), 0);\n\n    // Test getHeapStatistics - should return object with expected structure\n    const heapStats = v8.getHeapStatistics();\n    strictEqual(typeof heapStats, 'object');\n    strictEqual(heapStats.total_heap_size, 0);\n    strictEqual(heapStats.total_heap_size_executable, 0);\n    strictEqual(heapStats.total_physical_size, 0);\n    strictEqual(heapStats.total_available_size, 0);\n    strictEqual(heapStats.used_heap_size, 0);\n    strictEqual(heapStats.heap_size_limit, 0);\n    strictEqual(heapStats.malloced_memory, 0);\n    strictEqual(heapStats.peak_malloced_memory, 0);\n    strictEqual(heapStats.does_zap_garbage, 0);\n    strictEqual(heapStats.number_of_native_contexts, 0);\n    strictEqual(heapStats.number_of_detached_contexts, 0);\n    strictEqual(heapStats.total_global_handles_size, 0);\n    strictEqual(heapStats.used_global_handles_size, 0);\n    strictEqual(heapStats.external_memory, 0);\n\n    // Test getHeapSpaceStatistics - should return empty array\n    const heapSpaceStats = v8.getHeapSpaceStatistics();\n    deepStrictEqual(heapSpaceStats, []);\n\n    // Test getHeapCodeStatistics - should return object with expected structure\n    const heapCodeStats = v8.getHeapCodeStatistics();\n    strictEqual(typeof heapCodeStats, 'object');\n    strictEqual(heapCodeStats.code_and_metadata_size, 0);\n    strictEqual(heapCodeStats.bytecode_and_metadata_size, 0);\n    strictEqual(heapCodeStats.external_script_source_size, 0);\n    strictEqual(heapCodeStats.cpu_profiler_metadata_size, 0);\n\n    // Test getCppHeapStatistics with default 'detailed' type\n    const cppHeapStats = v8.getCppHeapStatistics();\n    strictEqual(typeof cppHeapStats, 'object');\n    strictEqual(cppHeapStats.committed_size_bytes, 0);\n    strictEqual(cppHeapStats.resident_size_bytes, 0);\n    strictEqual(cppHeapStats.used_size_bytes, 0);\n    deepStrictEqual(cppHeapStats.space_statistics, []);\n    deepStrictEqual(cppHeapStats.type_names, []);\n    strictEqual(cppHeapStats.detail_level, 'detailed');\n\n    // Test getCppHeapStatistics with 'brief' type\n    const cppHeapStatsBrief = v8.getCppHeapStatistics('brief');\n    strictEqual(cppHeapStatsBrief.detail_level, 'brief');\n\n    // Test getCppHeapStatistics with invalid type\n    throws(\n      () => {\n        v8.getCppHeapStatistics('invalid');\n      },\n      {\n        code: 'ERR_INVALID_ARG_VALUE',\n      }\n    );\n\n    // Test writeHeapSnapshot - should throw ERR_METHOD_NOT_IMPLEMENTED\n    throws(\n      () => {\n        v8.writeHeapSnapshot();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        v8.writeHeapSnapshot('test.heapsnapshot');\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        v8.writeHeapSnapshot('test.heapsnapshot', { exposeInternals: true });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test writeHeapSnapshot argument validation\n    throws(\n      () => {\n        v8.writeHeapSnapshot(123);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        v8.writeHeapSnapshot('test.heapsnapshot', 'invalid');\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        v8.writeHeapSnapshot('test.heapsnapshot', {\n          exposeInternals: 'invalid',\n        });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        v8.writeHeapSnapshot('test.heapsnapshot', {\n          exposeNumericValues: 'invalid',\n        });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Test getHeapSnapshot - should throw ERR_METHOD_NOT_IMPLEMENTED\n    throws(\n      () => {\n        v8.getHeapSnapshot();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        v8.getHeapSnapshot({ exposeInternals: false });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test getHeapSnapshot argument validation\n    throws(\n      () => {\n        v8.getHeapSnapshot('invalid');\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        v8.getHeapSnapshot({ exposeInternals: 'invalid' });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Test setFlagsFromString - should throw ERR_METHOD_NOT_IMPLEMENTED\n    throws(\n      () => {\n        v8.setFlagsFromString('--expose-gc');\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test setFlagsFromString argument validation\n    throws(\n      () => {\n        v8.setFlagsFromString(123);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        v8.setFlagsFromString();\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Test isStringOneByteRepresentation - should throw ERR_METHOD_NOT_IMPLEMENTED\n    throws(\n      () => {\n        v8.isStringOneByteRepresentation('hello');\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test isStringOneByteRepresentation argument validation\n    throws(\n      () => {\n        v8.isStringOneByteRepresentation(123);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        v8.isStringOneByteRepresentation();\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Test setHeapSnapshotNearHeapLimit - should throw ERR_METHOD_NOT_IMPLEMENTED\n    throws(\n      () => {\n        v8.setHeapSnapshotNearHeapLimit(1);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test setHeapSnapshotNearHeapLimit argument validation\n    throws(\n      () => {\n        v8.setHeapSnapshotNearHeapLimit(-1);\n      },\n      {\n        code: 'ERR_OUT_OF_RANGE',\n      }\n    );\n\n    throws(\n      () => {\n        v8.setHeapSnapshotNearHeapLimit('invalid');\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        v8.setHeapSnapshotNearHeapLimit();\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Test Serializer class\n    throws(\n      () => {\n        new v8.Serializer();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test Deserializer class\n    throws(\n      () => {\n        new v8.Deserializer(Buffer.from('test'));\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test DefaultSerializer class\n    throws(\n      () => {\n        new v8.DefaultSerializer();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test DefaultDeserializer class\n    throws(\n      () => {\n        new v8.DefaultDeserializer(Buffer.from('test'));\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test serialize function\n    throws(\n      () => {\n        v8.serialize({ test: 'value' });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test deserialize function\n    throws(\n      () => {\n        v8.deserialize(Buffer.from('test'));\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test GCProfiler class\n    const gcProfiler = new v8.GCProfiler();\n    throws(\n      () => {\n        gcProfiler.start();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        gcProfiler.stop();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test promiseHooks\n    throws(\n      () => {\n        v8.promiseHooks.createHook();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        v8.promiseHooks.onInit();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        v8.promiseHooks.onBefore();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        v8.promiseHooks.onAfter();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        v8.promiseHooks.onSettled();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test queryObjects function\n    throws(\n      () => {\n        v8.queryObjects(Object);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        v8.queryObjects(Array, { format: 'count' });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        v8.queryObjects(Array, { format: 'summary' });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test queryObjects argument validation\n    throws(\n      () => {\n        v8.queryObjects('invalid');\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        v8.queryObjects();\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        v8.queryObjects(Object, 'invalid');\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        v8.queryObjects(Object, { format: 'invalid' });\n      },\n      {\n        code: 'ERR_INVALID_ARG_VALUE',\n      }\n    );\n\n    // Test takeCoverage function\n    throws(\n      () => {\n        v8.takeCoverage();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test stopCoverage function\n    throws(\n      () => {\n        v8.stopCoverage();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test startupSnapshot methods\n    throws(\n      () => {\n        v8.startupSnapshot.addSerializeCallback(() => {}, {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        v8.startupSnapshot.addDeserializeCallback(() => {}, {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        v8.startupSnapshot.setDeserializeMainFunction(() => {}, {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test startupSnapshot.isBuildingSnapshot - should return false (not throw)\n    strictEqual(v8.startupSnapshot.isBuildingSnapshot(), false);\n\n    // Test default export exists and has expected properties\n    strictEqual(typeof v8.default, 'object');\n    strictEqual(v8.default.cachedDataVersionTag, v8.cachedDataVersionTag);\n    strictEqual(v8.default.getHeapStatistics, v8.getHeapStatistics);\n    strictEqual(v8.default.Serializer, v8.Serializer);\n    strictEqual(v8.default.Deserializer, v8.Deserializer);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/v8-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"v8-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"v8-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_v8_module\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/vm-test.js",
    "content": "import * as vm from 'node:vm';\nimport { doesNotThrow, notStrictEqual, strictEqual, throws } from 'node:assert';\n\nexport const vmTest = {\n  test() {\n    // Note: test was generated by claude with manual tweaks\n\n    // Test constants\n    strictEqual(typeof vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, 'symbol');\n    strictEqual(typeof vm.constants.DONT_CONTEXTIFY, 'symbol');\n\n    // Test isContext function\n    // Should validate object argument\n    throws(() => vm.isContext(null), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => vm.isContext(undefined), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => vm.isContext('string'), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => vm.isContext(123), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    // Should work with objects and arrays (but always return false)\n    strictEqual(vm.isContext({}), false);\n    strictEqual(vm.isContext([]), false);\n    strictEqual(vm.isContext(new Object()), false);\n\n    // Should return true for a valid vm.Context\n    const context = vm.createContext();\n    strictEqual(vm.isContext(context), true);\n\n    // Basic construction should return a Script object that throws on run methods\n    const script1 = new vm.Script('code');\n    strictEqual(script1 instanceof vm.Script, true);\n    throws(() => script1.runInContext(context), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n    throws(() => script1.runInNewContext(context), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n    throws(() => script1.runInThisContext(), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n\n    // Test Script constructor argument validation\n    throws(() => new vm.Script('code', { filename: 123 }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => new vm.Script('code', { lineOffset: 'invalid' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => new vm.Script('code', { columnOffset: 'invalid' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => new vm.Script('code', { cachedData: 'invalid' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => new vm.Script('code', { produceCachedData: 'invalid' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(\n      () => new vm.Script('code', { importModuleDynamically: 'invalid' }),\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Script can accept string options (should be treated as filename)\n    doesNotThrow(() => new vm.Script('code', 'filename.js'));\n\n    // Test runInContext function\n    throws(() => vm.runInContext('this.a = 1;', {}), {\n      message: /must be an vm.Context/,\n    });\n    throws(() => vm.runInContext('code', null), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => vm.runInContext('code', 'invalid'), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    throws(() => vm.runInNewContext('code'), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n    throws(() => vm.runInNewContext('code', {}), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n\n    // Test runInThisContext function\n    throws(() => vm.runInThisContext('code'), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n\n    // Test runInThisContext with string options\n    throws(() => vm.runInThisContext('code', 'filename.js'), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n\n    // Test createContext function with no options\n    const context1 = vm.createContext();\n    strictEqual(vm.isContext(context1), true);\n\n    // Test createContext function passing in a context object\n    const context2 = vm.createContext(context1);\n    strictEqual(context2, context1);\n\n    // Test createContext function passing in a non-context object\n    const obj = { a: 1 };\n    const context3 = vm.createContext(obj);\n    strictEqual(vm.isContext(context3), true);\n    notStrictEqual(context3, obj);\n\n    // Test createContext argument validation\n    throws(() => vm.createContext({}, { name: 123 }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => vm.createContext({}, { origin: 123 }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => vm.createContext({}, { codeGeneration: 'invalid' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => vm.createContext({}, { importModuleDynamically: 'invalid' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => vm.createContext({}, { microtaskMode: 'invalid' }), {\n      code: 'ERR_INVALID_ARG_VALUE',\n    });\n\n    // Test createScript function returns a Script that throws on run methods\n    const script2 = vm.createScript('code');\n    strictEqual(script2 instanceof vm.Script, true);\n    throws(() => script2.runInContext(context), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n    throws(() => script2.runInNewContext(context), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n    throws(() => script2.runInThisContext(), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n\n    // Test compileFunction\n    throws(() => vm.compileFunction('code'), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n\n    // Test compileFunction argument validation\n    throws(() => vm.compileFunction(123), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => vm.compileFunction('code', 'invalid_params'), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => vm.compileFunction('code', ['param'], { filename: 123 }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(\n      () => vm.compileFunction('code', ['param'], { columnOffset: 'invalid' }),\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n    throws(\n      () => vm.compileFunction('code', ['param'], { lineOffset: 'invalid' }),\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n    throws(\n      () => vm.compileFunction('code', ['param'], { cachedData: 'invalid' }),\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n    throws(\n      () =>\n        vm.compileFunction('code', ['param'], { produceCachedData: 'invalid' }),\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n    throws(\n      () =>\n        vm.compileFunction('code', ['param'], {\n          importModuleDynamically: 'invalid',\n        }),\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n    throws(\n      () =>\n        vm.compileFunction('code', ['param'], { parsingContext: 'invalid' }),\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n    throws(\n      () =>\n        vm.compileFunction('code', ['param'], { contextExtensions: 'invalid' }),\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Test measureMemory\n    throws(() => vm.measureMemory(), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n    throws(() => vm.measureMemory({}), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n\n    // Test measureMemory argument validation\n    throws(() => vm.measureMemory({ mode: 'invalid' }), {\n      code: 'ERR_INVALID_ARG_VALUE',\n    });\n    throws(() => vm.measureMemory({ execution: 'invalid' }), {\n      code: 'ERR_INVALID_ARG_VALUE',\n    });\n\n    // Test edge cases with various input types\n    throws(() => vm.runInContext(null, {}), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => vm.runInNewContext(null), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n    throws(() => vm.runInThisContext(null), {\n      code: 'ERR_METHOD_NOT_IMPLEMENTED',\n    });\n\n    // Test with complex nested validation\n    throws(\n      () =>\n        vm.createContext(\n          {},\n          {\n            codeGeneration: {\n              strings: 'invalid',\n            },\n          }\n        ),\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () =>\n        vm.createContext(\n          {},\n          {\n            codeGeneration: {\n              wasm: 'invalid',\n            },\n          }\n        ),\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Test compileFunction with invalid params array elements\n    throws(() => vm.compileFunction('code', [123]), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => vm.compileFunction('code', ['valid', null]), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    // Test compileFunction with invalid contextExtensions\n    throws(\n      () =>\n        vm.compileFunction('code', [], {\n          contextExtensions: ['invalid'],\n        }),\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Test timeout validation in runInThisContext options (must be strictly positive)\n    throws(() => vm.runInThisContext('code', { timeout: -1 }), {\n      code: 'ERR_OUT_OF_RANGE',\n    });\n\n    // Test boolean validations\n    throws(() => vm.runInThisContext('code', { breakOnSigint: 'invalid' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n    throws(() => vm.runInThisContext('code', { displayErrors: 'invalid' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n\n    // Test that constants object is frozen\n    throws(() => {\n      vm.constants.NEW_PROP = 'test';\n    });\n    throws(() => {\n      delete vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER;\n    });\n\n    strictEqual(vm.default.Script, vm.Script);\n    strictEqual(vm.default.createContext, vm.createContext);\n    strictEqual(vm.default.isContext, vm.isContext);\n    strictEqual(vm.default.constants, vm.constants);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/vm-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"vm-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"vm-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_vm_module\", \"experimental\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/wasi-nodejs-test.js",
    "content": "import * as wasi from 'node:wasi';\nimport { strictEqual, deepStrictEqual, throws, ok } from 'node:assert';\n\nexport const wasiTest = {\n  test() {\n    // Note: test was generated by claude with manual tweaks\n\n    // Test that WASI class exists and is a constructor\n    strictEqual(typeof wasi.WASI, 'function');\n\n    // Test constructor with default options - should throw ERR_METHOD_NOT_IMPLEMENTED\n    throws(\n      () => {\n        new wasi.WASI();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test constructor with minimal valid options - should throw ERR_METHOD_NOT_IMPLEMENTED\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable' });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'preview1' });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test constructor argument validation - options must be an object\n    throws(\n      () => {\n        new wasi.WASI('invalid');\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI(null);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI(123);\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Test version validation - must be 'unstable' or 'preview1'\n    throws(\n      () => {\n        new wasi.WASI({ version: 'invalid' });\n      },\n      {\n        code: 'ERR_INVALID_ARG_VALUE',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'v1' });\n      },\n      {\n        code: 'ERR_INVALID_ARG_VALUE',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: null });\n      },\n      {\n        code: 'ERR_INVALID_ARG_VALUE',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 123 });\n      },\n      {\n        code: 'ERR_INVALID_ARG_VALUE',\n      }\n    );\n\n    // Test args validation - must be an array if provided\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', args: 'invalid' });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', args: {} });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', args: 123 });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Test valid args - should still throw ERR_METHOD_NOT_IMPLEMENTED but after validation\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', args: [] });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', args: ['arg1', 'arg2'] });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test env validation - must be an object if provided\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', env: 'invalid' });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', env: [] });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', env: 123 });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Test valid env - should still throw ERR_METHOD_NOT_IMPLEMENTED but after validation\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', env: {} });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', env: { PATH: '/usr/bin' } });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test preopens validation - must be an object if provided\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', preopens: 'invalid' });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', preopens: [] });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', preopens: 123 });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Test valid preopens - should still throw ERR_METHOD_NOT_IMPLEMENTED but after validation\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', preopens: {} });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', preopens: { '/': '.' } });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test stdin validation - must be a valid int32 >= 0\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', stdin: -1 });\n      },\n      {\n        code: 'ERR_OUT_OF_RANGE',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', stdin: 'invalid' });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', stdin: 2147483648 }); // > MAX_INT32\n      },\n      {\n        code: 'ERR_OUT_OF_RANGE',\n      }\n    );\n\n    // Test valid stdin values - should still throw ERR_METHOD_NOT_IMPLEMENTED but after validation\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', stdin: 0 });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', stdin: 3 });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test stdout validation - must be a valid int32 >= 0\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', stdout: -1 });\n      },\n      {\n        code: 'ERR_OUT_OF_RANGE',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', stdout: 'invalid' });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Test valid stdout values\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', stdout: 1 });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test stderr validation - must be a valid int32 >= 0\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', stderr: -1 });\n      },\n      {\n        code: 'ERR_OUT_OF_RANGE',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', stderr: 'invalid' });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Test valid stderr values\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', stderr: 2 });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test returnOnExit validation - must be a boolean if provided\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', returnOnExit: 'invalid' });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', returnOnExit: 1 });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', returnOnExit: {} });\n      },\n      {\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n\n    // Test valid returnOnExit values\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', returnOnExit: true });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', returnOnExit: false });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test complex valid options combination\n    throws(\n      () => {\n        new wasi.WASI({\n          version: 'preview1',\n          args: ['node', 'script.js'],\n          env: { NODE_ENV: 'production' },\n          preopens: { '/tmp': '/tmp' },\n          stdin: 0,\n          stdout: 1,\n          stderr: 2,\n          returnOnExit: true,\n        });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test that getImportObject can be called on prototype (even though we can't instantiate)\n    strictEqual(typeof wasi.WASI.prototype.getImportObject, 'function');\n\n    // Test getImportObject returns empty object\n    const importObject = wasi.WASI.prototype.getImportObject.call({});\n    strictEqual(typeof importObject, 'object');\n    deepStrictEqual(importObject, {});\n\n    // Test that other methods exist on prototype\n    strictEqual(typeof wasi.WASI.prototype.finalizeBindings, 'function');\n    strictEqual(typeof wasi.WASI.prototype.start, 'function');\n    strictEqual(typeof wasi.WASI.prototype.initialize, 'function');\n\n    // Test that other methods throw ERR_METHOD_NOT_IMPLEMENTED when called on prototype\n    throws(\n      () => {\n        wasi.WASI.prototype.finalizeBindings.call({}, {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        wasi.WASI.prototype.start.call({}, {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        wasi.WASI.prototype.initialize.call({}, {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test finalizeBindings with various parameters\n    throws(\n      () => {\n        wasi.WASI.prototype.finalizeBindings.call({}, {}, {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        wasi.WASI.prototype.finalizeBindings.call({}, {}, { memory: null });\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    // Test default export\n    strictEqual(typeof wasi.default, 'object');\n    strictEqual(wasi.default.WASI, wasi.WASI);\n\n    // Test edge cases with undefined/null in options\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', args: undefined }); // undefined should be ok\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', env: undefined }); // undefined should be ok\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', preopens: undefined }); // undefined should be ok\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        new wasi.WASI({ version: 'unstable', returnOnExit: undefined }); // undefined should be ok\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    console.log('All wasi module tests passed!');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/wasi-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"wasi-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"wasi-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_wasi_module\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/worker_threads-nodejs-test.js",
    "content": "import { strictEqual, throws, ok, deepStrictEqual, rejects } from 'node:assert';\nimport * as worker_threads from 'node:worker_threads';\nimport {\n  BroadcastChannel,\n  MessageChannel,\n  MessagePort,\n  Worker,\n  SHARE_ENV,\n  getEnvironmentData,\n  isMainThread,\n  isMarkedAsUntransferable,\n  markAsUntransferable,\n  markAsUncloneable,\n  moveMessagePortToContext,\n  parentPort,\n  receiveMessageOnPort,\n  resourceLimits,\n  setEnvironmentData,\n  postMessageToThread,\n  threadId,\n  workerData,\n  isInternalThread,\n} from 'node:worker_threads';\n\nexport const testExports = {\n  async test() {\n    strictEqual(typeof BroadcastChannel, 'function');\n    strictEqual(typeof MessageChannel, 'function');\n    strictEqual(typeof MessagePort, 'function');\n    strictEqual(typeof Worker, 'function');\n    strictEqual(typeof getEnvironmentData, 'function');\n    strictEqual(typeof setEnvironmentData, 'function');\n    strictEqual(typeof isMarkedAsUntransferable, 'function');\n    strictEqual(typeof markAsUntransferable, 'function');\n    strictEqual(typeof markAsUncloneable, 'function');\n    strictEqual(typeof moveMessagePortToContext, 'function');\n    strictEqual(typeof receiveMessageOnPort, 'function');\n    strictEqual(typeof postMessageToThread, 'function');\n\n    strictEqual(typeof isMainThread, 'boolean');\n    strictEqual(typeof SHARE_ENV, 'symbol');\n    strictEqual(typeof resourceLimits, 'object');\n    strictEqual(typeof threadId, 'number');\n    strictEqual(typeof isInternalThread, 'boolean');\n\n    strictEqual(worker_threads.default.BroadcastChannel, BroadcastChannel);\n    strictEqual(worker_threads.default.MessageChannel, MessageChannel);\n    strictEqual(worker_threads.default.MessagePort, MessagePort);\n    strictEqual(worker_threads.default.Worker, Worker);\n    strictEqual(worker_threads.default.getEnvironmentData, getEnvironmentData);\n    strictEqual(worker_threads.default.setEnvironmentData, setEnvironmentData);\n    strictEqual(worker_threads.default.isMainThread, isMainThread);\n    strictEqual(\n      worker_threads.default.isMarkedAsUntransferable,\n      isMarkedAsUntransferable\n    );\n    strictEqual(\n      worker_threads.default.markAsUntransferable,\n      markAsUntransferable\n    );\n    strictEqual(worker_threads.default.markAsUncloneable, markAsUncloneable);\n    strictEqual(\n      worker_threads.default.moveMessagePortToContext,\n      moveMessagePortToContext\n    );\n    strictEqual(worker_threads.default.parentPort, parentPort);\n    strictEqual(\n      worker_threads.default.receiveMessageOnPort,\n      receiveMessageOnPort\n    );\n    strictEqual(worker_threads.default.resourceLimits, resourceLimits);\n    strictEqual(worker_threads.default.SHARE_ENV, SHARE_ENV);\n    strictEqual(\n      worker_threads.default.postMessageToThread,\n      postMessageToThread\n    );\n    strictEqual(worker_threads.default.threadId, threadId);\n    strictEqual(worker_threads.default.workerData, workerData);\n    strictEqual(worker_threads.default.isInternalThread, isInternalThread);\n  },\n};\n\nexport const testBroadcastChannelConstructor = {\n  async test() {\n    throws(\n      () => {\n        new BroadcastChannel();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n\nexport const testWorkerConstructor = {\n  async test() {\n    throws(\n      () => {\n        new Worker();\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n\nexport const testWorkerProperties = {\n  async test() {\n    const proto = Worker.prototype;\n\n    strictEqual(typeof proto.postMessage, 'function');\n    strictEqual(typeof proto.postMessageToThread, 'function');\n    strictEqual(typeof proto.ref, 'function');\n    strictEqual(typeof proto.unref, 'function');\n    strictEqual(typeof proto.terminate, 'function');\n    strictEqual(typeof proto.getHeapSnapshot, 'function');\n    strictEqual(typeof proto.getHeapStatistics, 'function');\n\n    ok(Symbol.asyncDispose in proto);\n    strictEqual(typeof proto[Symbol.asyncDispose], 'function');\n  },\n};\n\nexport const testMessageChannelAndPort = {\n  async test() {\n    // Only check reference equality if the global MessageChannel is exposed.\n    // The node:worker_threads module now imports from cloudflare-internal:messagechannel\n    // so it works independently of the expose_global_message_channel compat flag.\n    if (typeof globalThis.MessageChannel !== 'undefined') {\n      strictEqual(MessageChannel, globalThis.MessageChannel);\n      strictEqual(MessagePort, globalThis.MessagePort);\n    }\n\n    const channel = new MessageChannel();\n    ok(channel instanceof MessageChannel);\n    ok(channel.port1 instanceof MessagePort);\n    ok(channel.port2 instanceof MessagePort);\n  },\n};\n\nexport const testEnvironmentData = {\n  async test() {\n    const key1 = 'test_key_1';\n    const key2 = 'test_key_2';\n    const value1 = 'test_value_1';\n    const value2 = { test: 'object' };\n\n    strictEqual(getEnvironmentData(key1), undefined);\n    strictEqual(getEnvironmentData(key2), undefined);\n\n    setEnvironmentData(key1, value1);\n    strictEqual(getEnvironmentData(key1), value1);\n\n    setEnvironmentData(key2, value2);\n    deepStrictEqual(getEnvironmentData(key2), value2);\n\n    setEnvironmentData(key1, null);\n    strictEqual(getEnvironmentData(key1), null);\n  },\n};\n\nexport const testIsMainThread = {\n  async test() {\n    strictEqual(isMainThread, true);\n    strictEqual(typeof isMainThread, 'boolean');\n  },\n};\n\nexport const testIsMarkedAsUntransferable = {\n  async test() {\n    strictEqual(isMarkedAsUntransferable({}), false);\n    strictEqual(isMarkedAsUntransferable(null), false);\n    strictEqual(isMarkedAsUntransferable(undefined), false);\n    strictEqual(isMarkedAsUntransferable(123), false);\n    strictEqual(isMarkedAsUntransferable('string'), false);\n    strictEqual(isMarkedAsUntransferable(new ArrayBuffer(8)), false);\n  },\n};\n\nexport const testMarkAsUntransferable = {\n  async test() {\n    const obj = {};\n    const buffer = new ArrayBuffer(8);\n\n    markAsUntransferable(obj);\n    markAsUntransferable(buffer);\n    markAsUntransferable(null);\n    markAsUntransferable(undefined);\n\n    ok(true);\n  },\n};\n\nexport const testMarkAsUncloneable = {\n  async test() {\n    const obj = {};\n    const buffer = new ArrayBuffer(8);\n\n    markAsUncloneable(obj);\n    markAsUncloneable(buffer);\n    markAsUncloneable(null);\n    markAsUncloneable(undefined);\n\n    ok(true);\n  },\n};\n\nexport const testParentPort = {\n  async test() {\n    strictEqual(parentPort, null);\n  },\n};\n\nexport const testReceiveMessageOnPort = {\n  async test() {\n    const channel = new MessageChannel();\n\n    strictEqual(receiveMessageOnPort(channel.port1), undefined);\n    strictEqual(receiveMessageOnPort(channel.port2), undefined);\n  },\n};\n\nexport const testSHARE_ENV = {\n  async test() {\n    strictEqual(typeof SHARE_ENV, 'symbol');\n    strictEqual(SHARE_ENV, Symbol.for('nodejs.worker_threads.SHARE_ENV'));\n  },\n};\n\nexport const testResourceLimits = {\n  async test() {\n    strictEqual(typeof resourceLimits, 'object');\n    ok(resourceLimits !== null);\n    deepStrictEqual(resourceLimits, {});\n  },\n};\n\nexport const testThreadId = {\n  async test() {\n    strictEqual(typeof threadId, 'number');\n    strictEqual(threadId, 0);\n  },\n};\n\nexport const testWorkerData = {\n  async test() {\n    strictEqual(workerData, null);\n  },\n};\n\nexport const testPostMessageToThread = {\n  async test() {\n    throws(\n      () => {\n        postMessageToThread(1, 'message');\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        postMessageToThread(1, 'message', 1000);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        postMessageToThread(0, {});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n\n    throws(\n      () => {\n        postMessageToThread(123, null);\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n\nexport const testIsInternalThread = {\n  async test() {\n    strictEqual(typeof isInternalThread, 'boolean');\n    strictEqual(isInternalThread, false);\n  },\n};\n\nexport const testDefaultExport = {\n  async test() {\n    const defaultExport = worker_threads.default;\n\n    ok(defaultExport);\n    strictEqual(typeof defaultExport, 'object');\n\n    ok('BroadcastChannel' in defaultExport);\n    ok('MessageChannel' in defaultExport);\n    ok('MessagePort' in defaultExport);\n    ok('Worker' in defaultExport);\n    ok('SHARE_ENV' in defaultExport);\n    ok('getEnvironmentData' in defaultExport);\n    ok('isMainThread' in defaultExport);\n    ok('isMarkedAsUntransferable' in defaultExport);\n    ok('markAsUntransferable' in defaultExport);\n    ok('markAsUncloneable' in defaultExport);\n    ok('moveMessagePortToContext' in defaultExport);\n    ok('parentPort' in defaultExport);\n    ok('receiveMessageOnPort' in defaultExport);\n    ok('resourceLimits' in defaultExport);\n    ok('setEnvironmentData' in defaultExport);\n    ok('postMessageToThread' in defaultExport);\n    ok('threadId' in defaultExport);\n    ok('workerData' in defaultExport);\n    ok('isInternalThread' in defaultExport);\n\n    const expectedKeys = [\n      'BroadcastChannel',\n      'MessageChannel',\n      'MessagePort',\n      'Worker',\n      'SHARE_ENV',\n      'getEnvironmentData',\n      'isMainThread',\n      'isMarkedAsUntransferable',\n      'markAsUntransferable',\n      'markAsUncloneable',\n      'moveMessagePortToContext',\n      'parentPort',\n      'receiveMessageOnPort',\n      'resourceLimits',\n      'setEnvironmentData',\n      'postMessageToThread',\n      'threadId',\n      'workerData',\n      'isInternalThread',\n    ];\n    deepStrictEqual(Object.keys(defaultExport).sort(), expectedKeys.sort());\n  },\n};\n\nexport const testWorkerTerminate = {\n  async test() {\n    const proto = Worker.prototype;\n    await rejects(\n      async () => {\n        await proto.terminate.call({});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n\nexport const testWorkerGetHeapSnapshot = {\n  async test() {\n    const proto = Worker.prototype;\n    await rejects(\n      async () => {\n        await proto.getHeapSnapshot.call({});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n\nexport const testWorkerGetHeapStatistics = {\n  async test() {\n    const proto = Worker.prototype;\n    await rejects(\n      async () => {\n        await proto.getHeapStatistics.call({});\n      },\n      {\n        code: 'ERR_METHOD_NOT_IMPLEMENTED',\n      }\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/worker_threads-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"worker_threads-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"worker_threads-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"enable_nodejs_worker_threads_module\", \"global_navigator\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/tests/zlib-nodejs-test.js",
    "content": "import assert from 'node:assert';\nimport { Buffer } from 'node:buffer';\nimport {\n  Readable,\n  Writable,\n  Stream,\n  getDefaultHighWaterMark,\n} from 'node:stream';\nimport { inspect, promisify } from 'node:util';\nimport { mock } from 'node:test';\nimport zlib from 'node:zlib';\nimport crypto from 'node:crypto';\n\n// The following test data comes from\n// https://github.com/zlib-ng/zlib-ng/blob/5401b24/test/test_crc32.cc\n// test_crc32.cc -- crc32 unit test\n// Copyright (C) 2019-2021 IBM Corporation\n// Authors: Rogerio Alves    <rogealve@br.ibm.com>\n//          Matheus Castanho <msc@linux.ibm.com>\n// For conditions of distribution and use, see copyright notice in zlib.h\nexport const crc32Test = {\n  test() {\n    const tests = [\n      [0x0, 0x0, 0, 0x0],\n      [0xffffffff, 0x0, 0, 0x0],\n      [0x0, 0x0, 255, 0x0] /*  BZ 174799.  */,\n      [0x0, 0x0, 256, 0x0],\n      [0x0, 0x0, 257, 0x0],\n      [0x0, 0x0, 32767, 0x0],\n      [0x0, 0x0, 32768, 0x0],\n      [0x0, 0x0, 32769, 0x0],\n      [0x0, '', 0, 0x0],\n      [0xffffffff, '', 0, 0xffffffff],\n      [0x0, 'abacus', 6, 0xc3d7115b],\n      [0x0, 'backlog', 7, 0x269205],\n      [0x0, 'campfire', 8, 0x22a515f8],\n      [0x0, 'delta', 5, 0x9643fed9],\n      [0x0, 'executable', 10, 0xd68eda01],\n      [0x0, 'file', 4, 0x8c9f3610],\n      [0x0, 'greatest', 8, 0xc1abd6cd],\n      [0x0, 'hello', 5, 0x3610a686],\n      [0x0, 'inverter', 8, 0xc9e962c9],\n      [0x0, 'jigsaw', 6, 0xce4e3f69],\n      [0x0, 'karate', 6, 0x890be0e2],\n      [0x0, 'landscape', 9, 0xc4e0330b],\n      [0x0, 'machine', 7, 0x1505df84],\n      [0x0, 'nanometer', 9, 0xd4e19f39],\n      [0x0, 'oblivion', 8, 0xdae9de77],\n      [0x0, 'panama', 6, 0x66b8979c],\n      [0x0, 'quest', 5, 0x4317f817],\n      [0x0, 'resource', 8, 0xbc91f416],\n      [0x0, 'secret', 6, 0x5ca2e8e5],\n      [0x0, 'test', 4, 0xd87f7e0c],\n      [0x0, 'ultimate', 8, 0x3fc79b0b],\n      [0x0, 'vector', 6, 0x1b6e485b],\n      [0x0, 'walrus', 6, 0xbe769b97],\n      [0x0, 'xeno', 4, 0xe7a06444],\n      [0x0, 'yelling', 7, 0xfe3944e5],\n      [0x0, 'zlib', 4, 0x73887d3a],\n      [0x0, '4BJD7PocN1VqX0jXVpWB', 20, 0xd487a5a1],\n      [0x0, 'F1rPWI7XvDs6nAIRx41l', 20, 0x61a0132e],\n      [0x0, 'ldhKlsVkPFOveXgkGtC2', 20, 0xdf02f76],\n      [0x0, '5KKnGOOrs8BvJ35iKTOS', 20, 0x579b2b0a],\n      [0x0, '0l1tw7GOcem06Ddu7yn4', 20, 0xf7d16e2d],\n      [0x0, 'MCr47CjPIn9R1IvE1Tm5', 20, 0x731788f5],\n      [0x0, 'UcixbzPKTIv0SvILHVdO', 20, 0x7112bb11],\n      [0x0, 'dGnAyAhRQDsWw0ESou24', 20, 0xf32a0dac],\n      [0x0, 'di0nvmY9UYMYDh0r45XT', 20, 0x625437bb],\n      [0x0, '2XKDwHfAhFsV0RhbqtvH', 20, 0x896930f9],\n      [0x0, 'ZhrANFIiIvRnqClIVyeD', 20, 0x8579a37],\n      [0x0, 'v7Q9ehzioTOVeDIZioT1', 20, 0x632aa8e0],\n      [0x0, 'Yod5hEeKcYqyhfXbhxj2', 20, 0xc829af29],\n      [0x0, 'GehSWY2ay4uUKhehXYb0', 20, 0x1b08b7e8],\n      [0x0, 'kwytJmq6UqpflV8Y8GoE', 20, 0x4e33b192],\n      [0x0, '70684206568419061514', 20, 0x59a179f0],\n      [0x0, '42015093765128581010', 20, 0xcd1013d7],\n      [0x0, '88214814356148806939', 20, 0xab927546],\n      [0x0, '43472694284527343838', 20, 0x11f3b20c],\n      [0x0, '49769333513942933689', 20, 0xd562d4ca],\n      [0x0, '54979784887993251199', 20, 0x233395f7],\n      [0x0, '58360544869206793220', 20, 0x2d167fd5],\n      [0x0, '27347953487840714234', 20, 0x8b5108ba],\n      [0x0, '07650690295365319082', 20, 0xc46b3cd8],\n      [0x0, '42655507906821911703', 20, 0xc10b2662],\n      [0x0, '29977409200786225655', 20, 0xc9a0f9d2],\n      [0x0, '85181542907229116674', 20, 0x9341357b],\n      [0x0, '87963594337989416799', 20, 0xf0424937],\n      [0x0, '21395988329504168551', 20, 0xd7c4c31f],\n      [0x0, '51991013580943379423', 20, 0xf11edcc4],\n      [0x0, '*]+@!);({_$;}[_},?{?;(_?,=-][@', 30, 0x40795df4],\n      [0x0, '_@:_).&(#.[:[{[:)$++-($_;@[)}+', 30, 0xdd61a631],\n      [0x0, '&[!,[$_==}+.]@!;*(+},[;:)$;)-@', 30, 0xca907a99],\n      [0x0, ']{.[.+?+[[=;[?}_#&;[=)__$$:+=_', 30, 0xf652deac],\n      [0x0, '-%.)=/[@].:.(:,()$;=%@-$?]{%+%', 30, 0xaf39a5a9],\n      [0x0, '+]#$(@&.=:,*];/.!]%/{:){:@(;)$', 30, 0x6bebb4cf],\n      // eslint-disable-next-line no-template-curly-in-string\n      [0x0, ')-._.:?[&:.=+}(*$/=!.${;(=$@!}', 30, 0x76430bac],\n      [0x0, ':(_*&%/[[}+,?#$&*+#[([*-/#;%(]', 30, 0x6c80c388],\n      [0x0, '{[#-;:$/{)(+[}#]/{&!%(@)%:@-$:', 30, 0xd54d977d],\n      [0x0, '_{$*,}(&,@.)):=!/%(&(,,-?$}}}!', 30, 0xe3966ad5],\n      [\n        0x0,\n        'e$98KNzqaV)Y:2X?]77].{gKRD4G5{mHZk,Z)SpU%L3FSgv!Wb8MLAFdi{+fp)c,@8m6v)yXg@]HBDFk?.4&}g5_udE*JHCiH=aL',\n        100,\n        0xe7c71db9,\n      ],\n      [\n        0x0,\n        'r*Fd}ef+5RJQ;+W=4jTR9)R*p!B;]Ed7tkrLi;88U7g@3v!5pk2X6D)vt,.@N8c]@yyEcKi[vwUu@.Ppm@C6%Mv*3Nw}Y,58_aH)',\n        100,\n        0xeaa52777,\n      ],\n      [\n        0x0,\n        'h{bcmdC+a;t+Cf{6Y_dFq-{X4Yu&7uNfVDh?q&_u.UWJU],-GiH7ADzb7-V.Q%4=+v!$L9W+T=bP]$_:]Vyg}A.ygD.r;h-D]m%&',\n        100,\n        0xcd472048,\n      ],\n      [0x7a30360d, 'abacus', 6, 0xf8655a84],\n      [0x6fd767ee, 'backlog', 7, 0x1ed834b1],\n      [0xefeb7589, 'campfire', 8, 0x686cfca],\n      [0x61cf7e6b, 'delta', 5, 0x1554e4b1],\n      [0xdc712e2, 'executable', 10, 0x761b4254],\n      [0xad23c7fd, 'file', 4, 0x7abdd09b],\n      [0x85cb2317, 'greatest', 8, 0x4ba91c6b],\n      [0x9eed31b0, 'inverter', 8, 0xd5e78ba5],\n      [0xb94f34ca, 'jigsaw', 6, 0x23649109],\n      [0xab058a2, 'karate', 6, 0xc5591f41],\n      [0x5bff2b7a, 'landscape', 9, 0xf10eb644],\n      [0x605c9a5f, 'machine', 7, 0xbaa0a636],\n      [0x51bdeea5, 'nanometer', 9, 0x6af89afb],\n      [0x85c21c79, 'oblivion', 8, 0xecae222b],\n      [0x97216f56, 'panama', 6, 0x47dffac4],\n      [0x18444af2, 'quest', 5, 0x70c2fe36],\n      [0xbe6ce359, 'resource', 8, 0x1471d925],\n      [0x843071f1, 'secret', 6, 0x50c9a0db],\n      [0xf2480c60, 'ultimate', 8, 0xf973daf8],\n      [0x2d2feb3d, 'vector', 6, 0x344ac03d],\n      [0x7490310a, 'walrus', 6, 0x6d1408ef],\n      [0x97d247d4, 'xeno', 4, 0xe62670b5],\n      [0x93cf7599, 'yelling', 7, 0x1b36da38],\n      [0x73c84278, 'zlib', 4, 0x6432d127],\n      [0x228a87d1, '4BJD7PocN1VqX0jXVpWB', 20, 0x997107d0],\n      [0xa7a048d0, 'F1rPWI7XvDs6nAIRx41l', 20, 0xdc567274],\n      [0x1f0ded40, 'ldhKlsVkPFOveXgkGtC2', 20, 0xdcc63870],\n      [0xa804a62f, '5KKnGOOrs8BvJ35iKTOS', 20, 0x6926cffd],\n      [0x508fae6a, '0l1tw7GOcem06Ddu7yn4', 20, 0xb52b38bc],\n      [0xe5adaf4f, 'MCr47CjPIn9R1IvE1Tm5', 20, 0xf83b8178],\n      [0x67136a40, 'UcixbzPKTIv0SvILHVdO', 20, 0xc5213070],\n      [0xb00c4a10, 'dGnAyAhRQDsWw0ESou24', 20, 0xbc7648b0],\n      [0x2e0c84b5, 'di0nvmY9UYMYDh0r45XT', 20, 0xd8123a72],\n      [0x81238d44, '2XKDwHfAhFsV0RhbqtvH', 20, 0xd5ac5620],\n      [0xf853aa92, 'ZhrANFIiIvRnqClIVyeD', 20, 0xceae099d],\n      [0x5a692325, 'v7Q9ehzioTOVeDIZioT1', 20, 0xb07d2b24],\n      [0x3275b9f, 'Yod5hEeKcYqyhfXbhxj2', 20, 0x24ce91df],\n      [0x38371feb, 'GehSWY2ay4uUKhehXYb0', 20, 0x707b3b30],\n      [0xafc8bf62, 'kwytJmq6UqpflV8Y8GoE', 20, 0x16abc6a9],\n      [0x9b07db73, '70684206568419061514', 20, 0xae1fb7b7],\n      [0xe75b214, '42015093765128581010', 20, 0xd4eecd2d],\n      [0x72d0fe6f, '88214814356148806939', 20, 0x4660ec7],\n      [0xf857a4b1, '43472694284527343838', 20, 0xfd8afdf7],\n      [0x54b8e14, '49769333513942933689', 20, 0xc6d1b5f2],\n      [0xd6aa5616, '54979784887993251199', 20, 0x32476461],\n      [0x11e63098, '58360544869206793220', 20, 0xd917cf1a],\n      [0xbe92385, '27347953487840714234', 20, 0x4ad14a12],\n      [0x49511de0, '07650690295365319082', 20, 0xe37b5c6c],\n      [0x3db13bc1, '42655507906821911703', 20, 0x7cc497f1],\n      [0xbb899bea, '29977409200786225655', 20, 0x99781bb2],\n      [0xf6cd9436, '85181542907229116674', 20, 0x132256a1],\n      [0x9109e6c3, '87963594337989416799', 20, 0xbfdb2c83],\n      [0x75770fc, '21395988329504168551', 20, 0x8d9d1e81],\n      [0x69b1d19b, '51991013580943379423', 20, 0x7b6d4404],\n      [0xc6132975, '*]+@!);({_$;}[_},?{?;(_?,=-][@', 30, 0x8619f010],\n      [0xd58cb00c, '_@:_).&(#.[:[{[:)$++-($_;@[)}+', 30, 0x15746ac3],\n      [0xb63b8caa, '&[!,[$_==}+.]@!;*(+},[;:)$;)-@', 30, 0xaccf812f],\n      [0x8a45a2b8, ']{.[.+?+[[=;[?}_#&;[=)__$$:+=_', 30, 0x78af45de],\n      [0xcbe95b78, '-%.)=/[@].:.(:,()$;=%@-$?]{%+%', 30, 0x25b06b59],\n      [0x4ef8a54b, '+]#$(@&.=:,*];/.!]%/{:){:@(;)$', 30, 0x4ba0d08f],\n      // eslint-disable-next-line no-template-curly-in-string\n      [0x76ad267a, ')-._.:?[&:.=+}(*$/=!.${;(=$@!}', 30, 0xe26b6aac],\n      [0x569e613c, ':(_*&%/[[}+,?#$&*+#[([*-/#;%(]', 30, 0x7e2b0a66],\n      [0x36aa61da, '{[#-;:$/{)(+[}#]/{&!%(@)%:@-$:', 30, 0xb3430dc7],\n      [0xf67222df, '_{$*,}(&,@.)):=!/%(&(,,-?$}}}!', 30, 0x626c17a],\n      [\n        0x74b34fd3,\n        'e$98KNzqaV)Y:2X?]77].{gKRD4G5{mHZk,Z)SpU%L3FSgv!Wb8MLAFdi{+fp)c,@8m6v)yXg@]HBDFk?.4&}g5_udE*JHCiH=aL',\n        100,\n        0xccf98060,\n      ],\n      [\n        0x351fd770,\n        'r*Fd}ef+5RJQ;+W=4jTR9)R*p!B;]Ed7tkrLi;88U7g@3v!5pk2X6D)vt,.@N8c]@yyEcKi[vwUu@.Ppm@C6%Mv*3Nw}Y,58_aH)',\n        100,\n        0xd8b95312,\n      ],\n      [\n        0xc45aef77,\n        'h{bcmdC+a;t+Cf{6Y_dFq-{X4Yu&7uNfVDh?q&_u.UWJU],-GiH7ADzb7-V.Q%4=+v!$L9W+T=bP]$_:]Vyg}A.ygD.r;h-D]m%&',\n        100,\n        0xbb1c9912,\n      ],\n      [\n        0xc45aef77,\n        'h{bcmdC+a;t+Cf{6Y_dFq-{X4Yu&7uNfVDh?q&_u.UWJU],-GiH7ADzb7-V.Q%4=+v!$L9W+T=bP]$_:]Vyg}A.ygD.r;h-D]m%&' +\n          'h{bcmdC+a;t+Cf{6Y_dFq-{X4Yu&7uNfVDh?q&_u.UWJU],-GiH7ADzb7-V.Q%4=+v!$L9W+T=bP]$_:]Vyg}A.ygD.r;h-D]m%&' +\n          'h{bcmdC+a;t+Cf{6Y_dFq-{X4Yu&7uNfVDh?q&_u.UWJU],-GiH7ADzb7-V.Q%4=+v!$L9W+T=bP]$_:]Vyg}A.ygD.r;h-D]m%&' +\n          'h{bcmdC+a;t+Cf{6Y_dFq-{X4Yu&7uNfVDh?q&_u.UWJU],-GiH7ADzb7-V.Q%4=+v!$L9W+T=bP]$_:]Vyg}A.ygD.r;h-D]m%&' +\n          'h{bcmdC+a;t+Cf{6Y_dFq-{X4Yu&7uNfVDh?q&_u.UWJU],-GiH7ADzb7-V.Q%4=+v!$L9W+T=bP]$_:]Vyg}A.ygD.r;h-D]m%&' +\n          'h{bcmdC+a;t+Cf{6Y_dFq-{X4Yu&7uNfVDh?q&_u.UWJU],-GiH7ADzb7-V.Q%4=+v!$L9W+T=bP]$_:]Vyg}A.ygD.r;h-D]m%&',\n        600,\n        0x888afa5b,\n      ],\n    ];\n\n    for (const [crc, data, len, expected] of tests) {\n      if (data === 0) {\n        continue;\n      }\n      const buf = Buffer.from(data, 'utf8');\n      assert.strictEqual(buf.length, len);\n      assert.strictEqual(\n        zlib.crc32(buf, crc),\n        expected,\n        `crc32('${data}', ${crc}) in buffer is not ${expected}`\n      );\n      assert.strictEqual(\n        zlib.crc32(buf.toString(), crc),\n        expected,\n        `crc32('${data}', ${crc}) in string is not ${expected}`\n      );\n      if (crc === 0) {\n        assert.strictEqual(\n          zlib.crc32(buf),\n          expected,\n          `crc32('${data}') in buffer is not ${expected}`\n        );\n        assert.strictEqual(\n          zlib.crc32(buf.toString()),\n          expected,\n          `crc32('${data}') in string is not ${expected}`\n        );\n      }\n    }\n\n    for (const invalid of [undefined, null, true, 1, () => {}, {}]) {\n      assert.throws(() => {\n        zlib.crc32(invalid);\n      }, new TypeError(\"Failed to execute 'crc32' on 'ZlibUtil': parameter 1 is not of type 'string or ArrayBuffer or ArrayBufferView'.\"));\n    }\n\n    for (const invalid of [null, true, () => {}, {}]) {\n      assert.throws(\n        () => {\n          zlib.crc32('test', invalid);\n        },\n        { code: 'ERR_INVALID_ARG_TYPE' }\n      );\n    }\n  },\n};\n\nexport const constantsTest = {\n  test() {\n    assert.deepStrictEqual(Object.keys(zlib.constants).sort(), [\n      'BROTLI_DECODE',\n      'BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES',\n      'BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP',\n      'BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES',\n      'BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1',\n      'BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2',\n      'BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS',\n      'BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET',\n      'BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1',\n      'BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2',\n      'BROTLI_DECODER_ERROR_FORMAT_CL_SPACE',\n      'BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT',\n      'BROTLI_DECODER_ERROR_FORMAT_DICTIONARY',\n      'BROTLI_DECODER_ERROR_FORMAT_DISTANCE',\n      'BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE',\n      'BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE',\n      'BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE',\n      'BROTLI_DECODER_ERROR_FORMAT_PADDING_1',\n      'BROTLI_DECODER_ERROR_FORMAT_PADDING_2',\n      'BROTLI_DECODER_ERROR_FORMAT_RESERVED',\n      'BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET',\n      'BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME',\n      'BROTLI_DECODER_ERROR_FORMAT_TRANSFORM',\n      'BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS',\n      'BROTLI_DECODER_ERROR_INVALID_ARGUMENTS',\n      'BROTLI_DECODER_ERROR_UNREACHABLE',\n      'BROTLI_DECODER_NEEDS_MORE_INPUT',\n      'BROTLI_DECODER_NEEDS_MORE_OUTPUT',\n      'BROTLI_DECODER_NO_ERROR',\n      'BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION',\n      'BROTLI_DECODER_PARAM_LARGE_WINDOW',\n      'BROTLI_DECODER_RESULT_ERROR',\n      'BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT',\n      'BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT',\n      'BROTLI_DECODER_RESULT_SUCCESS',\n      'BROTLI_DECODER_SUCCESS',\n      'BROTLI_DEFAULT_MODE',\n      'BROTLI_DEFAULT_QUALITY',\n      'BROTLI_DEFAULT_WINDOW',\n      'BROTLI_ENCODE',\n      'BROTLI_LARGE_MAX_WINDOW_BITS',\n      'BROTLI_MAX_INPUT_BLOCK_BITS',\n      'BROTLI_MAX_QUALITY',\n      'BROTLI_MAX_WINDOW_BITS',\n      'BROTLI_MIN_INPUT_BLOCK_BITS',\n      'BROTLI_MIN_QUALITY',\n      'BROTLI_MIN_WINDOW_BITS',\n      'BROTLI_MODE_FONT',\n      'BROTLI_MODE_GENERIC',\n      'BROTLI_MODE_TEXT',\n      'BROTLI_OPERATION_EMIT_METADATA',\n      'BROTLI_OPERATION_FINISH',\n      'BROTLI_OPERATION_FLUSH',\n      'BROTLI_OPERATION_PROCESS',\n      'BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING',\n      'BROTLI_PARAM_LARGE_WINDOW',\n      'BROTLI_PARAM_LGBLOCK',\n      'BROTLI_PARAM_LGWIN',\n      'BROTLI_PARAM_MODE',\n      'BROTLI_PARAM_NDIRECT',\n      'BROTLI_PARAM_NPOSTFIX',\n      'BROTLI_PARAM_QUALITY',\n      'BROTLI_PARAM_SIZE_HINT',\n      'DEFLATE',\n      'DEFLATERAW',\n      'GUNZIP',\n      'GZIP',\n      'INFLATE',\n      'INFLATERAW',\n      'UNZIP',\n      'ZLIB_VERNUM',\n      'ZSTD_CLEVEL_DEFAULT',\n      'ZSTD_DECODE',\n      'ZSTD_ENCODE',\n      'ZSTD_btlazy2',\n      'ZSTD_btopt',\n      'ZSTD_btultra',\n      'ZSTD_btultra2',\n      'ZSTD_c_chainLog',\n      'ZSTD_c_checksumFlag',\n      'ZSTD_c_compressionLevel',\n      'ZSTD_c_contentSizeFlag',\n      'ZSTD_c_dictIDFlag',\n      'ZSTD_c_enableLongDistanceMatching',\n      'ZSTD_c_hashLog',\n      'ZSTD_c_jobSize',\n      'ZSTD_c_ldmBucketSizeLog',\n      'ZSTD_c_ldmHashLog',\n      'ZSTD_c_ldmHashRateLog',\n      'ZSTD_c_ldmMinMatch',\n      'ZSTD_c_minMatch',\n      'ZSTD_c_nbWorkers',\n      'ZSTD_c_overlapLog',\n      'ZSTD_c_searchLog',\n      'ZSTD_c_strategy',\n      'ZSTD_c_targetLength',\n      'ZSTD_c_windowLog',\n      'ZSTD_d_windowLogMax',\n      'ZSTD_dfast',\n      'ZSTD_e_continue',\n      'ZSTD_e_end',\n      'ZSTD_e_flush',\n      'ZSTD_fast',\n      'ZSTD_greedy',\n      'ZSTD_lazy',\n      'ZSTD_lazy2',\n      'Z_BEST_COMPRESSION',\n      'Z_BEST_SPEED',\n      'Z_BLOCK',\n      'Z_BUF_ERROR',\n      'Z_DATA_ERROR',\n      'Z_DEFAULT_CHUNK',\n      'Z_DEFAULT_COMPRESSION',\n      'Z_DEFAULT_LEVEL',\n      'Z_DEFAULT_MEMLEVEL',\n      'Z_DEFAULT_STRATEGY',\n      'Z_DEFAULT_WINDOWBITS',\n      'Z_ERRNO',\n      'Z_FILTERED',\n      'Z_FINISH',\n      'Z_FIXED',\n      'Z_FULL_FLUSH',\n      'Z_HUFFMAN_ONLY',\n      'Z_MAX_CHUNK',\n      'Z_MAX_LEVEL',\n      'Z_MAX_MEMLEVEL',\n      'Z_MAX_WINDOWBITS',\n      'Z_MEM_ERROR',\n      'Z_MIN_CHUNK',\n      'Z_MIN_LEVEL',\n      'Z_MIN_MEMLEVEL',\n      'Z_MIN_WINDOWBITS',\n      'Z_NEED_DICT',\n      'Z_NO_COMPRESSION',\n      'Z_NO_FLUSH',\n      'Z_OK',\n      'Z_PARTIAL_FLUSH',\n      'Z_RLE',\n      'Z_STREAM_END',\n      'Z_STREAM_ERROR',\n      'Z_SYNC_FLUSH',\n      'Z_VERSION_ERROR',\n    ]);\n  },\n};\n\n// This test makes sure constants can be accessed from the zlib module directly\n// This is supported by NodeJS, but is deprecated.\nexport const constantsDirectlyUnderZlibTest = {\n  test() {\n    assert.deepStrictEqual(zlib.Z_NEED_DICT, 2);\n    assert.deepStrictEqual(zlib.Z_OK, 0);\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/561bc87c7607208f0d3db6dcd9231efeb48cfe2f/test/parallel/test-zlib-zero-windowBits.js\nexport const testZeroWindowBits = {\n  test() {\n    // windowBits is a special case in zlib. On the compression side, 0 is invalid.\n    // On the decompression side, it indicates that zlib should use the value from\n    // the header of the compressed stream.\n    {\n      const inflate = zlib.createInflate({ windowBits: 0 });\n      assert(inflate instanceof zlib.Inflate);\n    }\n\n    {\n      const gunzip = zlib.createGunzip({ windowBits: 0 });\n      assert(gunzip instanceof zlib.Gunzip);\n    }\n\n    {\n      const unzip = zlib.createUnzip({ windowBits: 0 });\n      assert(unzip instanceof zlib.Unzip);\n    }\n\n    assert.throws(() => zlib.createGzip({ windowBits: 0 }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n      message:\n        'The value of \"options.windowBits\" is out of range. ' +\n        'It must be >= 9 and <= 15. Received 0',\n    });\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/561bc87c7607208f0d3db6dcd9231efeb48cfe2f/test/parallel/test-zlib-create-raw.js\nexport const testCreateRaw = {\n  test() {\n    {\n      const inflateRaw = zlib.createInflateRaw();\n      assert(inflateRaw instanceof zlib.InflateRaw);\n    }\n\n    {\n      const deflateRaw = zlib.createDeflateRaw();\n      assert(deflateRaw instanceof zlib.DeflateRaw);\n    }\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/561bc87c7607208f0d3db6dcd9231efeb48cfe2f/test/parallel/test-zlib-deflate-constructors.js\nexport const testDeflateConstructors = {\n  test() {\n    assert(new zlib.Deflate() instanceof zlib.Deflate);\n    assert(new zlib.DeflateRaw() instanceof zlib.DeflateRaw);\n\n    // Throws if `options.chunkSize` is invalid\n    assert.throws(() => new zlib.Deflate({ chunkSize: 'test' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n    });\n\n    assert.throws(() => new zlib.Deflate({ chunkSize: -Infinity }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    assert.throws(() => new zlib.Deflate({ chunkSize: 0 }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    // Throws if `options.windowBits` is invalid\n    assert.throws(() => new zlib.Deflate({ windowBits: 'test' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n    });\n\n    assert.throws(() => new zlib.Deflate({ windowBits: -Infinity }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    assert.throws(() => new zlib.Deflate({ windowBits: Infinity }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    assert.throws(() => new zlib.Deflate({ windowBits: 0 }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    // Throws if `options.level` is invalid\n    assert.throws(() => new zlib.Deflate({ level: 'test' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n    });\n\n    assert.throws(() => new zlib.Deflate({ level: -Infinity }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    assert.throws(() => new zlib.Deflate({ level: Infinity }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    assert.throws(() => new zlib.Deflate({ level: -2 }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    // Throws if `level` invalid in  `Deflate.prototype.params()`\n    assert.throws(() => new zlib.Deflate().params('test'), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n    });\n\n    assert.throws(() => new zlib.Deflate().params(-Infinity), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    assert.throws(() => new zlib.Deflate().params(Infinity), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    assert.throws(() => new zlib.Deflate().params(-2), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    // Throws if options.memLevel is invalid\n    assert.throws(() => new zlib.Deflate({ memLevel: 'test' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n    });\n\n    assert.throws(() => new zlib.Deflate({ memLevel: -Infinity }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    assert.throws(() => new zlib.Deflate({ memLevel: Infinity }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    assert.throws(() => new zlib.Deflate({ memLevel: -2 }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    // Does not throw if opts.strategy is valid\n    new zlib.Deflate({ strategy: zlib.constants.Z_FILTERED });\n    new zlib.Deflate({ strategy: zlib.constants.Z_HUFFMAN_ONLY });\n    new zlib.Deflate({ strategy: zlib.constants.Z_RLE });\n    new zlib.Deflate({ strategy: zlib.constants.Z_FIXED });\n    new zlib.Deflate({ strategy: zlib.constants.Z_DEFAULT_STRATEGY });\n\n    // Throws if options.strategy is invalid\n    assert.throws(() => new zlib.Deflate({ strategy: 'test' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n    });\n\n    assert.throws(() => new zlib.Deflate({ strategy: -Infinity }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    assert.throws(() => new zlib.Deflate({ strategy: Infinity }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    assert.throws(() => new zlib.Deflate({ strategy: -2 }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    // Throws TypeError if `strategy` is invalid in `Deflate.prototype.params()`\n    assert.throws(() => new zlib.Deflate().params(0, 'test'), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n    });\n\n    assert.throws(() => new zlib.Deflate().params(0, -Infinity), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    assert.throws(() => new zlib.Deflate().params(0, Infinity), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    assert.throws(() => new zlib.Deflate().params(0, -2), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    // Throws if opts.dictionary is not a Buffer\n    assert.throws(() => new zlib.Deflate({ dictionary: 'not a buffer' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n    });\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/561bc87c7607208f0d3db6dcd9231efeb48cfe2f/test/parallel/test-zlib-failed-init.js\nexport const testFailedInit = {\n  test() {\n    assert.throws(() => zlib.createGzip({ chunkSize: 0 }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    assert.throws(() => zlib.createGzip({ windowBits: 0 }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    assert.throws(() => zlib.createGzip({ memLevel: 0 }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n    });\n\n    {\n      const stream = zlib.createGzip({ level: NaN });\n      assert.strictEqual(stream._level, zlib.constants.Z_DEFAULT_COMPRESSION);\n    }\n\n    {\n      const stream = zlib.createGzip({ strategy: NaN });\n      assert.strictEqual(stream._strategy, zlib.constants.Z_DEFAULT_STRATEGY);\n    }\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/561bc87c7607208f0d3db6dcd9231efeb48cfe2f/test/parallel/test-zlib-destroy.js\nexport const zlibDestroyTest = {\n  async test() {\n    const promises = [];\n    // Verify that the zlib transform does clean up\n    // the handle when calling destroy.\n    {\n      const ts = zlib.createGzip();\n      ts.destroy();\n      assert.strictEqual(ts._handle, null);\n\n      const { promise, resolve, reject } = Promise.withResolvers();\n      promises.push(promise);\n      ts.on('error', reject);\n      ts.on('close', () => {\n        ts.close(() => {\n          resolve();\n        });\n      });\n    }\n\n    {\n      // Ensure 'error' is only emitted once.\n      const decompress = zlib.createGunzip(15);\n      const { promise, resolve, reject } = Promise.withResolvers();\n      promises.push(promise);\n      let errorCount = 0;\n      decompress.on('error', (err) => {\n        errorCount++;\n        decompress.close();\n        assert.strictEqual(errorCount, 1, 'Error should only be emitted once');\n        resolve();\n      });\n\n      decompress.write('something invalid');\n      decompress.destroy(new Error('asd'));\n    }\n\n    await Promise.all(promises);\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/561bc87c7607208f0d3db6dcd9231efeb48cfe2f/test/parallel/test-zlib-close-after-error.js\nexport const closeAfterError = {\n  async test() {\n    const decompress = zlib.createGunzip(15);\n    const { promise, resolve } = Promise.withResolvers();\n    let errorHasBeenCalled = false;\n\n    decompress.on('error', () => {\n      errorHasBeenCalled = true;\n      assert.strictEqual(decompress._closed, true);\n      decompress.close();\n    });\n\n    assert.strictEqual(decompress._closed, false);\n    decompress.write('something invalid');\n    decompress.on('close', resolve);\n\n    await promise;\n\n    assert(errorHasBeenCalled, 'Error handler should have been called');\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/561bc87c7607208f0d3db6dcd9231efeb48cfe2f/test/parallel/test-zlib-write-after-close.js\nexport const writeAfterClose = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n    let close = mock.fn();\n    zlib.gzip('hello', function (err, out) {\n      const unzip = zlib.createGunzip();\n      unzip.close(close);\n      unzip.write('asd', (err) => {\n        assert.strictEqual(err.code, 'ERR_STREAM_DESTROYED');\n        assert.strictEqual(err.name, 'Error');\n        resolve();\n      });\n    });\n    await promise;\n    assert.strictEqual(close.mock.callCount(), 1);\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/9edf4a0856681a7665bd9dcf2ca7cac252784b98/test/parallel/test-zlib-bytes-read.js\nexport const testZlibBytesRead = {\n  async test() {\n    const expectStr = 'abcdefghijklmnopqrstuvwxyz'.repeat(2);\n    const expectBuf = Buffer.from(expectStr);\n\n    function createWriter(target, buffer) {\n      const writer = { size: 0 };\n      const write = () => {\n        target.write(Buffer.from([buffer[writer.size++]]), () => {\n          if (writer.size < buffer.length) {\n            target.flush(write);\n          } else {\n            target.end();\n          }\n        });\n      };\n      write();\n      return writer;\n    }\n\n    // This test is simplified a lot because of test runner limitations.\n    for (const method of [\n      'createGzip',\n      'createDeflate',\n      'createDeflateRaw',\n      'createBrotliCompress',\n    ]) {\n      assert(method in zlib, `${method} is not available in \"node:zlib\"`);\n      const { promise, resolve, reject } = Promise.withResolvers();\n      let compData = Buffer.alloc(0);\n      const comp = zlib[method]();\n      const compWriter = createWriter(comp, expectBuf);\n      comp.on('data', function (d) {\n        compData = Buffer.concat([compData, d]);\n        assert.strictEqual(\n          this.bytesWritten,\n          compWriter.size,\n          `Should get write size on ${method} data.`\n        );\n      });\n      comp.on('error', reject);\n      comp.on('end', function () {\n        assert.strictEqual(\n          this.bytesWritten,\n          compWriter.size,\n          `Should get write size on ${method} end.`\n        );\n        assert.strictEqual(\n          this.bytesWritten,\n          expectStr.length,\n          `Should get data size on ${method} end.`\n        );\n\n        resolve();\n      });\n\n      await promise;\n    }\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/3a71ccf6c473357e89be61b26739fd9139dce4db/test/parallel/test-zlib-const.js\nexport const zlibConst = {\n  test() {\n    assert.strictEqual(zlib.constants.Z_OK, 0, 'Expected Z_OK to be 0');\n    assert.throws(() => {\n      zlib.constants.Z_OK = 1;\n    }, /Cannot assign to read only property/);\n    assert.strictEqual(zlib.constants.Z_OK, 0, 'Z_OK should be immutable');\n    assert.strictEqual(\n      zlib.codes.Z_OK,\n      0,\n      `Expected Z_OK to be 0; got ${zlib.codes.Z_OK}`\n    );\n    assert.throws(() => {\n      zlib.codes.Z_OK = 1;\n    }, /Cannot assign to read only property/);\n    assert.strictEqual(zlib.codes.Z_OK, 0, 'Z_OK should be immutable');\n    assert(Object.isFrozen(zlib.codes), 'Expected zlib.codes to be frozen');\n\n    assert.deepStrictEqual(zlib.codes, {\n      '-1': 'Z_ERRNO',\n      '-2': 'Z_STREAM_ERROR',\n      '-3': 'Z_DATA_ERROR',\n      '-4': 'Z_MEM_ERROR',\n      '-5': 'Z_BUF_ERROR',\n      '-6': 'Z_VERSION_ERROR',\n      0: 'Z_OK',\n      1: 'Z_STREAM_END',\n      2: 'Z_NEED_DICT',\n      Z_BUF_ERROR: -5,\n      Z_DATA_ERROR: -3,\n      Z_ERRNO: -1,\n      Z_MEM_ERROR: -4,\n      Z_NEED_DICT: 2,\n      Z_OK: 0,\n      Z_STREAM_END: 1,\n      Z_STREAM_ERROR: -2,\n      Z_VERSION_ERROR: -6,\n    });\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/3a71ccf6c473357e89be61b26739fd9139dce4db/test/parallel/test-zlib-object-write.js\n\nexport const zlibObjectWrite = {\n  async test() {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const gunzip = new zlib.Gunzip({ objectMode: true });\n    gunzip.on('error', reject);\n    assert.throws(\n      () => {\n        gunzip.write({});\n      },\n      {\n        name: 'TypeError',\n        code: 'ERR_INVALID_ARG_TYPE',\n      }\n    );\n    gunzip.on('close', resolve);\n    gunzip.close();\n    await promise;\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/3a71ccf6c473357e89be61b26739fd9139dce4db/test/parallel/test-zlib-zero-byte.js\nexport const zlibZeroByte = {\n  async test() {\n    for (const Compressor of [zlib.Gzip, zlib.BrotliCompress]) {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      let endCalled = false;\n      const gz = new Compressor();\n      const emptyBuffer = Buffer.alloc(0);\n      let received = 0;\n      gz.on('data', function (c) {\n        received += c.length;\n      });\n\n      gz.on('end', function () {\n        const expected = Compressor === zlib.Gzip ? 20 : 1;\n        assert.strictEqual(\n          received,\n          expected,\n          `${received}, ${expected}, ${Compressor.name}`\n        );\n        endCalled = true;\n      });\n      gz.on('error', reject);\n      gz.on('finish', resolve);\n      gz.write(emptyBuffer);\n      gz.end();\n\n      await promise;\n      assert(endCalled, 'End should have been called');\n    }\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/431ac161e65698152de197703fb30c89da2b6686/test/parallel/test-zlib-dictionary-fail.js\nexport const zlibDictionaryFail = {\n  async test() {\n    // String \"test\" encoded with dictionary \"dict\".\n    const input = Buffer.from([0x78, 0xbb, 0x04, 0x09, 0x01, 0xa5]);\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      const stream = zlib.createInflate();\n\n      stream.on('error', function (err) {\n        assert.match(err.message, /Missing dictionary/);\n        resolve();\n      });\n\n      stream.write(input);\n      await promise;\n    }\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      const stream = zlib.createInflate({ dictionary: Buffer.from('fail') });\n\n      stream.on('error', function (err) {\n        assert.match(err.message, /Bad dictionary/);\n        resolve();\n      });\n\n      stream.write(input);\n      await promise;\n    }\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      const stream = zlib.createInflateRaw({ dictionary: Buffer.from('fail') });\n\n      stream.on('error', function (err) {\n        // It's not possible to separate invalid dict and invalid data when using\n        // the raw format\n        assert.match(\n          err.message,\n          /(invalid|Operation-Ending-Supplemental Code is 0x12)/\n        );\n        resolve();\n      });\n\n      stream.write(input);\n      await promise;\n    }\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/431ac161e65698152de197703fb30c89da2b6686/test/parallel/test-zlib-close-in-ondata.js\nexport const zlibCloseInOnData = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n    const ts = zlib.createGzip();\n    const buf = Buffer.alloc(1024 * 1024 * 20);\n\n    ts.on('data', function () {\n      ts.close();\n      resolve();\n    });\n    ts.end(buf);\n    await promise;\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/431ac161e65698152de197703fb30c89da2b6686/test/parallel/test-zlib-flush-flags.js\nexport const zlibFlushFlags = {\n  test() {\n    zlib.createGzip({ flush: zlib.constants.Z_SYNC_FLUSH });\n\n    assert.throws(() => zlib.createGzip({ flush: 'foobar' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n      message:\n        'The \"options.flush\" property must be of type number. ' +\n        \"Received type string ('foobar')\",\n    });\n\n    assert.throws(() => zlib.createGzip({ flush: 10000 }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n      message:\n        'The value of \"options.flush\" is out of range. It must ' +\n        'be >= 0 and <= 5. Received 10000',\n    });\n\n    zlib.createGzip({ finishFlush: zlib.constants.Z_SYNC_FLUSH });\n\n    assert.throws(() => zlib.createGzip({ finishFlush: 'foobar' }), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n      message:\n        'The \"options.finishFlush\" property must be of type number. ' +\n        \"Received type string ('foobar')\",\n    });\n\n    assert.throws(() => zlib.createGzip({ finishFlush: 10000 }), {\n      code: 'ERR_OUT_OF_RANGE',\n      name: 'RangeError',\n      message:\n        'The value of \"options.finishFlush\" is out of range. It must ' +\n        'be >= 0 and <= 5. Received 10000',\n    });\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/431ac161e65698152de197703fb30c89da2b6686/test/parallel/test-zlib-reset-before-write.js\nexport const zlibResetBeforeWrite = {\n  async test() {\n    // Tests that zlib streams support .reset() and .params()\n    // before the first write. That is important to ensure that\n    // lazy init of zlib native library handles these cases.\n\n    for (const fn of [\n      (z, cb) => {\n        z.reset();\n        cb();\n      },\n      (z, cb) => z.params(0, zlib.constants.Z_DEFAULT_STRATEGY, cb),\n    ]) {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      const deflate = zlib.createDeflate();\n      const inflate = zlib.createInflate();\n\n      deflate.pipe(inflate);\n\n      const output = [];\n      inflate\n        .on('error', reject)\n        .on('data', (chunk) => output.push(chunk))\n        .on('end', resolve);\n\n      fn(deflate, () => {\n        fn(inflate, () => {\n          deflate.write('abc');\n          deflate.end();\n        });\n      });\n\n      await promise;\n\n      assert.strictEqual(Buffer.concat(output).toString(), 'abc');\n    }\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/6bf7b6e342f97cf48319e0bc251200fabe132c21/test/parallel/test-zlib-invalid-input.js\nexport const zlibInvalidInput = {\n  async test() {\n    const nonStringInputs = [1, true, { a: 1 }, ['a']];\n\n    // zlib.Unzip classes need to get valid data, or else they'll throw.\n    const unzips = [\n      new zlib.Unzip(),\n      new zlib.Gunzip(),\n      new zlib.Inflate(),\n      new zlib.InflateRaw(),\n      new zlib.BrotliDecompress(),\n    ];\n\n    for (const input of nonStringInputs) {\n      assert.throws(\n        () => {\n          zlib.gunzip(input);\n        },\n        {\n          name: 'TypeError',\n        }\n      );\n    }\n\n    for (const uz of unzips) {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      uz.on('error', resolve);\n      uz.on('end', reject);\n\n      // This will trigger error event\n      uz.write('this is not valid compressed data.');\n      await promise;\n    }\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/5e6aab0ecad6394e538e06357d6e16e155951a8b/test/parallel/test-zlib-unzip-one-byte-chunks.js\nexport const zlibUnzipOneByteChunks = {\n  async test() {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const data = Buffer.concat([zlib.gzipSync('abc'), zlib.gzipSync('def')]);\n\n    const resultBuffers = [];\n\n    const unzip = zlib\n      .createUnzip()\n      .on('error', reject)\n      .on('data', (data) => resultBuffers.push(data))\n      .on('finish', resolve);\n\n    for (let i = 0; i < data.length; i++) {\n      // Write each single byte individually.\n      unzip.write(Buffer.from([data[i]]));\n    }\n\n    unzip.end();\n    await promise;\n    assert.strictEqual(Buffer.concat(resultBuffers).toString(), 'abcdef');\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/4f14eb15454b9f6ae7f0145947debd2c79a2a84f/test/parallel/test-zlib-truncated.js\nexport const zlibTruncated = {\n  async test() {\n    const inputString =\n      'ΩΩLorem ipsum dolor sit amet, consectetur adipiscing eli' +\n      't. Morbi faucibus, purus at gravida dictum, libero arcu ' +\n      'convallis lacus, in commodo libero metus eu nisi. Nullam' +\n      ' commodo, neque nec porta placerat, nisi est fermentum a' +\n      'ugue, vitae gravida tellus sapien sit amet tellus. Aenea' +\n      'n non diam orci. Proin quis elit turpis. Suspendisse non' +\n      ' diam ipsum. Suspendisse nec ullamcorper odio. Vestibulu' +\n      'm arcu mi, sodales non suscipit id, ultrices ut massa. S' +\n      'ed ac sem sit amet arcu malesuada fermentum. Nunc sed. ';\n\n    const errMessage = /unexpected end of file/;\n\n    const cases = [\n      { comp: 'gzip', decomp: 'gunzip', decompSync: 'gunzipSync' },\n      { comp: 'gzip', decomp: 'unzip', decompSync: 'unzipSync' },\n      { comp: 'deflate', decomp: 'inflate', decompSync: 'inflateSync' },\n      {\n        comp: 'deflateRaw',\n        decomp: 'inflateRaw',\n        decompSync: 'inflateRawSync',\n      },\n    ];\n    for (const methods of cases) {\n      const { promise, resolve } = Promise.withResolvers();\n      zlib[methods.comp](inputString, async function (err, compressed) {\n        assert.ifError(err);\n        const truncated = compressed.slice(0, compressed.length / 2);\n        const toUTF8 = (buffer) => buffer.toString('utf-8');\n\n        // sync sanity\n        const decompressed = zlib[methods.decompSync](compressed);\n        assert.strictEqual(toUTF8(decompressed), inputString);\n\n        // async sanity\n        {\n          const { promise, resolve } = Promise.withResolvers();\n          zlib[methods.decomp](compressed, function (err, result) {\n            assert.ifError(err);\n            assert.strictEqual(toUTF8(result), inputString);\n            resolve();\n          });\n\n          await promise;\n        }\n\n        // Sync truncated input test\n        assert.throws(function () {\n          zlib[methods.decompSync](truncated);\n        }, errMessage);\n\n        // Async truncated input test\n        {\n          const { promise, resolve } = Promise.withResolvers();\n          zlib[methods.decomp](truncated, function (err) {\n            assert.match(err.message, errMessage);\n            resolve();\n          });\n\n          await promise;\n        }\n\n        const syncFlushOpt = { finishFlush: zlib.constants.Z_SYNC_FLUSH };\n\n        // Sync truncated input test, finishFlush = Z_SYNC_FLUSH\n        const result = toUTF8(\n          zlib[methods.decompSync](truncated, syncFlushOpt)\n        );\n        assert.strictEqual(result, inputString.slice(0, result.length));\n\n        // Async truncated input test, finishFlush = Z_SYNC_FLUSH\n        {\n          const { promise, resolve } = Promise.withResolvers();\n          zlib[methods.decomp](\n            truncated,\n            syncFlushOpt,\n            function (err, decompressed) {\n              assert.ifError(err);\n              const result = toUTF8(decompressed);\n              assert.strictEqual(result, inputString.slice(0, result.length));\n              resolve();\n            }\n          );\n          await promise;\n        }\n\n        resolve();\n      });\n\n      await promise;\n    }\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/beabcec41ca456e7e895e276acbc5f2d93db032f/test/parallel/test-zlib-from-concatenated-gzip.js\nexport const zlibFromConcatenatedGzip = {\n  async test() {\n    const abc = 'abc';\n    const def = 'def';\n\n    const abcEncoded = zlib.gzipSync(abc);\n    const defEncoded = zlib.gzipSync(def);\n\n    const data = Buffer.concat([abcEncoded, defEncoded]);\n\n    assert.strictEqual(zlib.gunzipSync(data).toString(), abc + def);\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      zlib.gunzip(data, (err, result) => {\n        assert.ifError(err);\n        assert.strictEqual(result.toString(), abc + def);\n        resolve();\n      });\n\n      await promise;\n    }\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      zlib.unzip(data, (err, result) => {\n        assert.ifError(err);\n        assert.strictEqual(result.toString(), abc + def);\n        resolve();\n      });\n\n      await promise;\n    }\n\n    // Multi-member support does not apply to zlib inflate/deflate.\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      zlib.unzip(\n        Buffer.concat([zlib.deflateSync('abc'), zlib.deflateSync('def')]),\n        (err, result) => {\n          assert.ifError(err);\n          assert.strictEqual(result.toString(), abc);\n          resolve();\n        }\n      );\n\n      await promise;\n    }\n\n    {\n      // Files that have the \"right\" magic bytes for starting a new gzip member\n      // in the middle of themselves, even if they are part of a single\n      // regularly compressed member\n      const pmmDataZlib = Buffer.from(\n        'eJztwTEBAAAAwqD1T+1vBqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +\n          'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +\n          'AAAAAAAAAAAAAAAAAAAAAAAAAAAIAz+w4AAQ==',\n        'base64'\n      );\n      const pmmDataGz = Buffer.from(\n        'H4sIAMyK8lYCA+3BMQEAAADCoPVP7WENoAAAAAAAAAAAAAAAAAAAAAAAH4sAAAAAAAAAAAAAAAAA' +\n          'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +\n          'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABuGn4Inv/6AQA=',\n        'base64'\n      );\n      const pmmExpected = zlib.inflateSync(pmmDataZlib);\n      const pmmResultBuffers = [];\n      const { promise, resolve, reject } = Promise.withResolvers();\n\n      Readable.from([pmmDataGz])\n        .pipe(zlib.createGunzip())\n        .on('error', reject)\n        .on('data', (data) => pmmResultBuffers.push(data))\n        .on('finish', resolve);\n\n      await promise;\n      // Result should match original random garbage\n      assert.deepStrictEqual(Buffer.concat(pmmResultBuffers), pmmExpected);\n    }\n\n    // Test that the next gzip member can wrap around the input buffer boundary\n    for (const offset of [0, 1, 2, 3, 4, defEncoded.length]) {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      const resultBuffers = [];\n\n      const unzip = zlib\n        .createGunzip()\n        .on('error', reject)\n        .on('data', (data) => resultBuffers.push(data))\n        .on('finish', resolve);\n\n      // First write: write \"abc\" + the first bytes of \"def\"\n      unzip.write(Buffer.concat([abcEncoded, defEncoded.slice(0, offset)]));\n\n      // Write remaining bytes of \"def\"\n      unzip.end(defEncoded.slice(offset));\n\n      await promise;\n\n      assert.strictEqual(\n        Buffer.concat(resultBuffers).toString(),\n        'abcdef',\n        `result should match original input (offset = ${offset})`\n      );\n    }\n  },\n};\n\n// Test taken from:\n// https://github.com/nodejs/node/blob/fc02b88f89f8d5abf5ee4414a1026444c18d77b3/test/parallel/test-zlib-not-string-or-buffer.js\nexport const zlibNotStringOrBuffer = {\n  test() {\n    for (const input of [\n      undefined,\n      null,\n      true,\n      false,\n      0,\n      1,\n      [1, 2, 3],\n      { foo: 'bar' },\n    ]) {\n      assert.throws(\n        () => zlib.deflateSync(input),\n        // TODO(soon): Use same error code as NodeJS\n        {\n          name: 'TypeError',\n        }\n      );\n    }\n  },\n};\n\n// Test taken from:\n// https://github.com/nodejs/node/blob/fc02b88f89f8d5abf5ee4414a1026444c18d77b3/test/parallel/test-zlib-from-gzip-with-trailing-garbage.js\nexport const zlibFromGzipWithTrailingGarbage = {\n  async test() {\n    // Should ignore trailing null-bytes\n    let data = Buffer.concat([\n      zlib.gzipSync('abc'),\n      zlib.gzipSync('def'),\n      Buffer.alloc(10),\n    ]);\n\n    assert.strictEqual(zlib.gunzipSync(data).toString(), 'abcdef');\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n\n      zlib.gunzip(data, (err, result) => {\n        assert.ifError(err);\n        assert.strictEqual(\n          result.toString(),\n          'abcdef',\n          `result '${result.toString()}' should match original string`\n        );\n        resolve();\n      });\n\n      await promise;\n    }\n\n    // If the trailing garbage happens to look like a gzip header, it should\n    // throw an error.\n    data = Buffer.concat([\n      zlib.gzipSync('abc'),\n      zlib.gzipSync('def'),\n      Buffer.from([0x1f, 0x8b, 0xff, 0xff]),\n      Buffer.alloc(10),\n    ]);\n\n    assert.throws(\n      () => zlib.gunzipSync(data),\n      /^Error: unknown compression method$/\n    );\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      zlib.gunzip(data, (err, result) => {\n        // TODO(soon): use same error code as NodeJS\n        // TODO(soon): Do our messages have a redundant \"Error: \" word?\n        assert.strictEqual(err.name, 'Error');\n        assert.match(err.message, /unknown compression method/);\n        assert.strictEqual(result, undefined);\n        resolve();\n      });\n\n      await promise;\n    }\n\n    // In this case the trailing junk is too short to be a gzip segment\n    // So we ignore it and decompression succeeds.\n    data = Buffer.concat([\n      zlib.gzipSync('abc'),\n      zlib.gzipSync('def'),\n      Buffer.from([0x1f, 0x8b, 0xff, 0xff]),\n    ]);\n\n    assert.throws(\n      () => zlib.gunzipSync(data),\n      /^Error: unknown compression method$/\n    );\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      zlib.gunzip(data, (err, result) => {\n        // TODO(soon): Use same error code as NodeJS\n        // TODO(soon): Do our messages have a redundant \"Error: \" word?\n        assert.strictEqual(err.name, 'Error');\n        assert.match(err.message, /unknown compression method/);\n        assert.strictEqual(result, undefined);\n        resolve();\n      });\n\n      await promise;\n    }\n  },\n};\n\n// Test taken from\n// https://github.com/nodejs/node/blob/fc02b88f89f8d5abf5ee4414a1026444c18d77b3/test/parallel/test-zlib-from-string.js\nexport const zlibFromString = {\n  async test() {\n    const inputString =\n      'ΩΩLorem ipsum dolor sit amet, consectetur adipiscing eli' +\n      't. Morbi faucibus, purus at gravida dictum, libero arcu ' +\n      'convallis lacus, in commodo libero metus eu nisi. Nullam' +\n      ' commodo, neque nec porta placerat, nisi est fermentum a' +\n      'ugue, vitae gravida tellus sapien sit amet tellus. Aenea' +\n      'n non diam orci. Proin quis elit turpis. Suspendisse non' +\n      ' diam ipsum. Suspendisse nec ullamcorper odio. Vestibulu' +\n      'm arcu mi, sodales non suscipit id, ultrices ut massa. S' +\n      'ed ac sem sit amet arcu malesuada fermentum. Nunc sed. ';\n    const expectedBase64Deflate =\n      'eJxdUUtOQzEMvMoc4OndgT0gJCT2buJWlpI4jePeqZfpmX' +\n      'AKLRKbLOzx/HK73q6vOrhCunlF1qIDJhNUeW5I2ozT5OkD' +\n      'lKWLJWkncJG5403HQXAkT3Jw29B9uIEmToMukglZ0vS6oc' +\n      'iBh4JG8sV4oVLEUCitK2kxq1WzPnChHDzsaGKy491LofoA' +\n      'bWh8do43oeuYhB5EPCjcLjzYJo48KrfQBvnJecNFJvHT1+' +\n      'RSQsGoC7dn2t/xjhduTA1NWyQIZR0pbHwMDatnD+crPqKS' +\n      'qGPHp1vnlsWM/07ubf7bheF7kqSj84Bm0R1fYTfaK8vqqq' +\n      'fKBtNMhe3OZh6N95CTvMX5HJJi4xOVzCgUOIMSLH7wmeOH' +\n      'aFE4RdpnGavKtrB5xzfO/Ll9';\n    const expectedBase64Gzip =\n      'H4sIAAAAAAAAA11RS05DMQy8yhzg6d2BPSAkJPZu4laWkjiN4' +\n      '96pl+mZcAotEpss7PH8crverq86uEK6eUXWogMmE1R5bkjajN' +\n      'Pk6QOUpYslaSdwkbnjTcdBcCRPcnDb0H24gSZOgy6SCVnS9Lq' +\n      'hyIGHgkbyxXihUsRQKK0raTGrVbM+cKEcPOxoYrLj3Uuh+gBt' +\n      'aHx2jjeh65iEHkQ8KNwuPNgmjjwqt9AG+cl5w0Um8dPX5FJCw' +\n      'agLt2fa3/GOF25MDU1bJAhlHSlsfAwNq2cP5ys+opKoY8enW+' +\n      'eWxYz/Tu5t/tuF4XuSpKPzgGbRHV9hN9ory+qqp8oG00yF7c5' +\n      'mHo33kJO8xfkckmLjE5XMKBQ4gxIsfvCZ44doUThF2mcZq8q2' +\n      'sHnHNzRtagj5AQAA';\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      zlib.deflate(inputString, (err, buffer) => {\n        zlib.inflate(buffer, (err, inflated) => {\n          assert.strictEqual(inflated.toString(), inputString);\n          resolve();\n        });\n      });\n      await promise;\n    }\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      zlib.gzip(inputString, (err, buffer) => {\n        assert.ifError(err);\n        // Can't actually guarantee that we'll get exactly the same\n        // deflated bytes when we compress a string, since the header\n        // depends on stuff other than the input string itself.\n        // However, decrypting it should definitely yield the same\n        // result that we're expecting, and this should match what we get\n        // from inflating the known valid deflate data.\n        zlib.gunzip(buffer, (err, gunzipped) => {\n          assert.ifError(err);\n          assert.strictEqual(gunzipped.toString(), inputString);\n          resolve();\n        });\n      });\n      await promise;\n    }\n\n    let buffer = Buffer.from(expectedBase64Deflate, 'base64');\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      zlib.unzip(buffer, (err, buffer) => {\n        assert.ifError(err);\n        assert.strictEqual(buffer.toString(), inputString);\n        resolve();\n      });\n\n      await promise;\n    }\n\n    buffer = Buffer.from(expectedBase64Gzip, 'base64');\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      zlib.unzip(buffer, (err, buffer) => {\n        assert.ifError(err);\n        assert.strictEqual(buffer.toString(), inputString);\n        resolve();\n      });\n      await promise;\n    }\n  },\n};\n\n// Test taken from\n// https://github.com/nodejs/node/blob/fc02b88f89f8d5abf5ee4414a1026444c18d77b3/test/parallel/test-zlib-empty-buffer.js\nexport const zlibEmptyBuffer = {\n  async test() {\n    const emptyBuffer = Buffer.alloc(0);\n\n    for (const [compress, decompress, method] of [\n      [zlib.deflateRawSync, zlib.inflateRawSync, 'raw sync'],\n      [zlib.deflateSync, zlib.inflateSync, 'deflate sync'],\n      [zlib.gzipSync, zlib.gunzipSync, 'gzip sync'],\n      [zlib.brotliCompressSync, zlib.brotliDecompressSync, 'br sync'],\n      [promisify(zlib.deflateRaw), promisify(zlib.inflateRaw), 'raw'],\n      [promisify(zlib.deflate), promisify(zlib.inflate), 'deflate'],\n      [promisify(zlib.gzip), promisify(zlib.gunzip), 'gzip'],\n      [promisify(zlib.brotliCompress), promisify(zlib.brotliDecompress), 'br'],\n    ]) {\n      const compressed = await compress(emptyBuffer);\n      const decompressed = await decompress(compressed);\n      assert.deepStrictEqual(\n        emptyBuffer,\n        decompressed,\n        `Expected ${inspect(compressed)} to match ${inspect(decompressed)} ` +\n          `to match for ${method}`\n      );\n    }\n  },\n};\n\n// Test taken from:\n// https://github.com/nodejs/node/blob/d75e253c506310ea6728329981beb3284fa431b5/test/parallel/test-zlib-flush.js\nexport const zlibFlush = {\n  async test() {\n    const opts = { level: 0 };\n    const deflater = zlib.createDeflate(opts);\n\n    const chunk = Buffer.from('/9j/4AAQSkZJRgABAQEASA==', 'base64');\n    const expectedNone = Buffer.from([0x78, 0x01]);\n    const blkhdr = Buffer.from([0x00, 0x10, 0x00, 0xef, 0xff]);\n    const adler32 = Buffer.from([0x00, 0x00, 0x00, 0xff, 0xff]);\n    const expectedFull = Buffer.concat([blkhdr, chunk, adler32]);\n    let actualNone;\n    let actualFull;\n\n    const { promise, resolve } = Promise.withResolvers();\n    deflater.write(chunk, function () {\n      deflater.flush(zlib.constants.Z_NO_FLUSH, function () {\n        actualNone = deflater.read();\n        deflater.flush(function () {\n          const bufs = [];\n          let buf;\n          while ((buf = deflater.read()) !== null) bufs.push(buf);\n          actualFull = Buffer.concat(bufs);\n\n          resolve();\n        });\n      });\n    });\n\n    await promise;\n    assert.deepStrictEqual(actualNone, expectedNone);\n    assert.deepStrictEqual(actualFull, expectedFull);\n  },\n};\n\n// Test taken from:\n// https://github.com/nodejs/node/blob/9bdf2ee1d184e7ec5c690319e068894ed324b595/test/parallel/test-zlib-dictionary.js\nexport const zlibDictionary = {\n  async test() {\n    const spdyDict = Buffer.from(\n      [\n        'optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-',\n        'languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi',\n        'f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser',\n        '-agent10010120020120220320420520630030130230330430530630740040140240340440',\n        '5406407408409410411412413414415416417500501502503504505accept-rangesageeta',\n        'glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic',\n        'ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran',\n        'sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati',\n        'oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo',\n        'ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe',\n        'pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic',\n        'ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1',\n        '.1statusversionurl\\0',\n      ].join('')\n    );\n\n    const input = [\n      'HTTP/1.1 200 Ok',\n      'Server: node.js',\n      'Content-Length: 0',\n      '',\n    ].join('\\r\\n');\n\n    // basicDictionaryTest\n    {\n      let { promise, resolve, reject } = Promise.withResolvers();\n      let output = '';\n      const deflate = zlib.createDeflate({ dictionary: spdyDict });\n      const inflate = zlib.createInflate({ dictionary: spdyDict });\n      inflate.setEncoding('utf-8');\n\n      deflate\n        .on('data', function (chunk) {\n          inflate.write(chunk);\n        })\n        .on('error', reject)\n        .on('end', function () {\n          inflate.end();\n        });\n\n      inflate\n        .on('error', reject)\n        .on('end', resolve)\n        .on('data', function (chunk) {\n          output += chunk;\n        });\n\n      deflate.write(input);\n      deflate.end();\n      await promise;\n      assert.strictEqual(input, output);\n    }\n\n    // deflateResetDictionaryTest\n    {\n      let { promise, resolve, reject } = Promise.withResolvers();\n      let doneReset = false;\n      let output = '';\n      const deflate = zlib.createDeflate({ dictionary: spdyDict });\n      const inflate = zlib.createInflate({ dictionary: spdyDict });\n      inflate.setEncoding('utf-8');\n\n      deflate\n        .on('data', function (chunk) {\n          if (doneReset) inflate.write(chunk);\n        })\n        .on('error', reject)\n        .on('end', function () {\n          inflate.end();\n        });\n\n      inflate\n        .on('data', function (chunk) {\n          output += chunk;\n        })\n        .on('error', reject)\n        .on('end', resolve);\n\n      deflate.write(input);\n      deflate.flush(function () {\n        deflate.reset();\n        doneReset = true;\n        deflate.write(input);\n        deflate.end();\n      });\n\n      await promise;\n      assert.strictEqual(input, output);\n    }\n\n    // rawDictionaryTest\n    {\n      let { promise, resolve, reject } = Promise.withResolvers();\n      let output = '';\n      const deflate = zlib.createDeflateRaw({ dictionary: spdyDict });\n      const inflate = zlib.createInflateRaw({ dictionary: spdyDict });\n      inflate.setEncoding('utf-8');\n\n      deflate\n        .on('data', function (chunk) {\n          inflate.write(chunk);\n        })\n        .on('error', reject)\n        .on('end', function () {\n          inflate.end();\n        });\n      inflate\n        .on('data', function (chunk) {\n          output += chunk;\n        })\n        .on('error', reject)\n        .on('end', resolve);\n\n      deflate.write(input);\n      deflate.end();\n\n      await promise;\n      assert.strictEqual(input, output);\n    }\n\n    // deflateRawResetDictionaryTest\n    {\n      let { promise, resolve, reject } = Promise.withResolvers();\n      let doneReset = false;\n      let output = '';\n      const deflate = zlib.createDeflateRaw({ dictionary: spdyDict });\n      const inflate = zlib.createInflateRaw({ dictionary: spdyDict });\n      inflate.setEncoding('utf-8');\n\n      deflate\n        .on('data', function (chunk) {\n          if (doneReset) inflate.write(chunk);\n        })\n        .on('error', reject)\n        .on('end', function () {\n          inflate.end();\n        });\n\n      inflate\n        .on('data', function (chunk) {\n          output += chunk;\n        })\n        .on('error', reject)\n        .on('end', resolve);\n\n      deflate.write(input);\n      deflate.flush(function () {\n        deflate.reset();\n        doneReset = true;\n        deflate.write(input);\n        deflate.end();\n      });\n\n      await promise;\n      assert.strictEqual(input, output);\n    }\n  },\n};\n\n// Test taken from\n// https://github.com/nodejs/node/blob/ef6b9ffc8dfc7b2a395c864d2729a0ce1be9ef18/test/parallel/test-zlib-close-after-write.js\nexport const closeAfterWrite = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n    zlib.gzip('hello', (err, out) => {\n      assert.ifError(err);\n\n      const unzip = zlib.createGunzip();\n      unzip.write(out);\n      unzip.close(resolve);\n    });\n    await promise;\n  },\n};\n\nexport const inflateSyncTest = {\n  test() {\n    const BIG_DATA = 'horse'.repeat(50_000) + 'cow'.repeat(49_000);\n    assert.strictEqual(\n      zlib.inflateSync(zlib.deflateSync(BIG_DATA)).toString(),\n      BIG_DATA\n    );\n\n    assert.throws(\n      () => zlib.inflateSync('garbage data'),\n      new Error('incorrect header check')\n    );\n\n    assert.strictEqual(\n      zlib\n        .inflateSync(Buffer.from('OE9LyixKUUiCEQAmfgUk', 'base64'), {\n          windowBits: 11,\n          level: 4,\n        })\n        .toString(),\n      'bird bird bird'\n    );\n  },\n};\n\nexport const zipBombTest = {\n  test() {\n    // 225 bytes (raw)\n    const ZLIB_BOMB_3 =\n      'eNqruPX2jqHeUkaJtov/25xEzoho9fL6aF70yPBZvUBsqZPmzHO+F253euVbWScs28QQ2LG' +\n      'CpdBc8ef84mkfpubvs6n/Z/9vrk9b4oy4ffG2ujp8DAwMB074/p97traX8fCt43fPvbMDCh' +\n      'mUtZ/2/rw58MHbhNLblkCBhC96ZuZ1Z+xY5vn3P5/LDBRxy3/6NTF7G+udupj/d4r5gSISe' +\n      '6sPAikGD0EeINkgBuZkQDgSyJxRmVGZUZlRmVGZURlCMpZRcVE/bjGdi/fzr+O/n/P32XWD' +\n      'nyxJaZvuAwDXRDs+';\n\n    // 1799 bytes\n    const zlib_bomb_2 = zlib.inflateSync(Buffer.from(ZLIB_BOMB_3, 'base64'));\n\n    // ~ 1MB\n    const zlib_bomb_1 = zlib.inflateSync(zlib_bomb_2);\n\n    // Would be 1 GB, if we let it\n    assert.throws(\n      () => zlib.inflateSync(zlib_bomb_1),\n      new RangeError('Memory limit exceeded')\n    );\n  },\n};\n\nexport const deflateSyncTest = {\n  test() {\n    function maskOsId(buf) {\n      // Clear the OS ID byte in gzip, which varies based on the platform used to run the tests\n      return buf.fill(0x0, 9, 10);\n    }\n\n    assert.strictEqual(\n      zlib\n        .deflateSync('bird bird bird', { windowBits: 11, level: 4 })\n        .toString('base64'),\n      'OE9LyixKUUiCEQAmfgUk'\n    );\n\n    assert.deepStrictEqual(\n      maskOsId(\n        zlib.gzipSync('water, water, everywhere, nor any drop to drink')\n      ).toString('base64'),\n      'H4sIAAAAAAAAACtPLEkt0lEoh1CpZalFleUZqUWpOgp5+UUKiXmVCilF+QUKJfkKKUWZedkAqpLyPC8AAAA='\n    );\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/5949e169bd886fcc965172f671671b47ac07d2e2/test/parallel/test-zlib-premature-end.js\nexport const prematureEnd = {\n  async test() {\n    const input = '0123456789'.repeat(4);\n\n    for (const [compress, decompressor] of [\n      [zlib.deflateRawSync, zlib.createInflateRaw],\n      [zlib.deflateSync, zlib.createInflate],\n      [zlib.brotliCompressSync, zlib.createBrotliDecompress],\n    ]) {\n      const compressed = compress(input);\n      const trailingData = Buffer.from('not valid compressed data');\n\n      for (const variant of [\n        (stream) => {\n          stream.end(compressed);\n        },\n        (stream) => {\n          stream.write(compressed);\n          stream.write(trailingData);\n        },\n        (stream) => {\n          stream.write(compressed);\n          stream.end(trailingData);\n        },\n        (stream) => {\n          stream.write(Buffer.concat([compressed, trailingData]));\n        },\n        (stream) => {\n          stream.end(Buffer.concat([compressed, trailingData]));\n        },\n      ]) {\n        let output = '';\n        const { promise, resolve, reject } = Promise.withResolvers();\n        const stream = decompressor();\n        stream.setEncoding('utf8');\n        stream\n          .on('data', (chunk) => (output += chunk))\n          .on('end', () => {\n            assert.strictEqual(output, input);\n            assert.strictEqual(stream.bytesWritten, compressed.length);\n            resolve();\n          })\n          .on('error', reject);\n        variant(stream);\n        await promise;\n      }\n    }\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/5949e169bd886fcc965172f671671b47ac07d2e2/test/parallel/test-zlib-write-after-flush.js\nexport const writeAfterFlush = {\n  async test() {\n    for (const [createCompress, createDecompress] of [\n      [zlib.createGzip, zlib.createGunzip],\n      [zlib.createBrotliCompress, zlib.createBrotliDecompress],\n    ]) {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      const gzip = createCompress();\n      const gunz = createDecompress();\n\n      gzip.pipe(gunz);\n\n      let output = '';\n      const input = 'A line of data\\n';\n      gunz.setEncoding('utf8');\n      gunz\n        .on('error', reject)\n        .on('data', (c) => (output += c))\n        .on('end', resolve);\n\n      // Make sure that flush/write doesn't trigger an assert failure\n      gzip.flush();\n      gzip.write(input);\n      gzip.end();\n      gunz.read(0);\n      await promise;\n      assert.strictEqual(output, input);\n    }\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/5949e169bd886fcc965172f671671b47ac07d2e2/test/parallel/test-zlib-destroy-pipe.js\nexport const destroyPipe = {\n  async test() {\n    const ts = zlib.createGzip();\n    const { promise, resolve } = Promise.withResolvers();\n\n    const ws = new Writable({\n      write: (chunk, enc, cb) => {\n        queueMicrotask(cb);\n        ts.destroy();\n        resolve();\n      },\n    });\n\n    const buf = Buffer.allocUnsafe(1024 * 1024 * 20);\n    ts.end(buf);\n    ts.pipe(ws);\n    await promise;\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/5949e169bd886fcc965172f671671b47ac07d2e2/test/parallel/test-zlib-write-after-end.js\nexport const writeAfterEnd = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n    const data = zlib.deflateRawSync('Welcome');\n    const inflate = zlib.createInflateRaw();\n    const writeCallback = mock.fn();\n    inflate.resume();\n    inflate.write(data, writeCallback);\n    inflate.write(Buffer.from([0x00]), writeCallback);\n    inflate.write(Buffer.from([0x00]), writeCallback);\n    inflate.flush(resolve);\n    await promise;\n    assert.strictEqual(writeCallback.mock.callCount(), 3);\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/5949e169bd886fcc965172f671671b47ac07d2e2/test/parallel/test-zlib-convenience-methods.js\nexport const convenienceMethods = {\n  async test() {\n    // Must be a multiple of 4 characters in total to test all ArrayBufferView\n    // types.\n    const expectStr = 'blah'.repeat(8);\n    const expectBuf = Buffer.from(expectStr);\n\n    const opts = {\n      level: 9,\n      chunkSize: 1024,\n    };\n\n    const optsInfo = {\n      info: true,\n    };\n\n    for (const [type, expect] of [\n      ['string', expectStr],\n      ['Buffer', expectBuf],\n    ]) {\n      for (const method of [\n        ['gzip', 'gunzip', 'Gzip', 'Gunzip'],\n        ['gzip', 'unzip', 'Gzip', 'Unzip'],\n        ['deflate', 'inflate', 'Deflate', 'Inflate'],\n        ['deflateRaw', 'inflateRaw', 'DeflateRaw', 'InflateRaw'],\n        [\n          'brotliCompress',\n          'brotliDecompress',\n          'BrotliCompress',\n          'BrotliDecompress',\n        ],\n      ]) {\n        {\n          const { promise, resolve } = Promise.withResolvers();\n          zlib[method[0]](expect, opts, (err, result) => {\n            assert.ifError(err);\n            zlib[method[1]](result, opts, (err, result) => {\n              assert.ifError(err);\n              assert.strictEqual(\n                result.toString(),\n                expectStr,\n                `Should get original string after ${method[0]}/` +\n                  `${method[1]} ${type} with options.`\n              );\n              resolve();\n            });\n          });\n          await promise;\n        }\n\n        {\n          const { promise, resolve } = Promise.withResolvers();\n          zlib[method[0]](expect, (err, result) => {\n            assert.ifError(err);\n            zlib[method[1]](result, (err, result) => {\n              assert.ifError(err);\n              assert.strictEqual(\n                result.toString(),\n                expectStr,\n                `Should get original string after ${method[0]}/` +\n                  `${method[1]} ${type} without options.`\n              );\n              resolve();\n            });\n          });\n          await promise;\n        }\n\n        {\n          const { promise, resolve } = Promise.withResolvers();\n          zlib[method[0]](expect, optsInfo, (err, result) => {\n            assert.ifError(err);\n\n            const compressed = result.buffer;\n            zlib[method[1]](compressed, optsInfo, (err, result) => {\n              assert.ifError(err);\n              assert.strictEqual(\n                result.buffer.toString(),\n                expectStr,\n                `Should get original string after ${method[0]}/` +\n                  `${method[1]} ${type} with info option.`\n              );\n              resolve();\n            });\n          });\n          await promise;\n        }\n\n        {\n          const compressed = zlib[`${method[0]}Sync`](expect, opts);\n          const decompressed = zlib[`${method[1]}Sync`](compressed, opts);\n          assert.strictEqual(\n            decompressed.toString(),\n            expectStr,\n            `Should get original string after ${method[0]}Sync/` +\n              `${method[1]}Sync ${type} with options.`\n          );\n        }\n\n        {\n          const compressed = zlib[`${method[0]}Sync`](expect);\n          const decompressed = zlib[`${method[1]}Sync`](compressed);\n          assert.strictEqual(\n            decompressed.toString(),\n            expectStr,\n            `Should get original string after ${method[0]}Sync/` +\n              `${method[1]}Sync ${type} without options.`\n          );\n        }\n\n        {\n          const compressed = zlib[`${method[0]}Sync`](expect, optsInfo);\n          const decompressed = zlib[`${method[1]}Sync`](\n            compressed.buffer,\n            optsInfo\n          );\n          assert.strictEqual(\n            decompressed.buffer.toString(),\n            expectStr,\n            `Should get original string after ${method[0]}Sync/` +\n              `${method[1]}Sync ${type} without options.`\n          );\n          assert.ok(\n            decompressed.engine instanceof zlib[method[3]],\n            `Should get engine ${method[3]} after ${method[0]} ` +\n              `${type} with info option.`\n          );\n        }\n      }\n    }\n\n    assert.throws(() => zlib.gzip('abc'), {\n      code: 'ERR_INVALID_ARG_TYPE',\n      name: 'TypeError',\n      message:\n        'The \"callback\" argument must be of type function. ' +\n        'Received undefined',\n    });\n  },\n};\n\n// Test taken from\n// https://github.com/nodejs/node/blob/2bd6a57b7b934afcaf437a90e2abebcb79c13acf/test/parallel/test-zlib-brotli-from-string.js\nexport const brotliFromString = {\n  async test() {\n    const inputString =\n      'ΩΩLorem ipsum dolor sit amet, consectetur adipiscing eli' +\n      't. Morbi faucibus, purus at gravida dictum, libero arcu ' +\n      'convallis lacus, in commodo libero metus eu nisi. Nullam' +\n      ' commodo, neque nec porta placerat, nisi est fermentum a' +\n      'ugue, vitae gravida tellus sapien sit amet tellus. Aenea' +\n      'n non diam orci. Proin quis elit turpis. Suspendisse non' +\n      ' diam ipsum. Suspendisse nec ullamcorper odio. Vestibulu' +\n      'm arcu mi, sodales non suscipit id, ultrices ut massa. S' +\n      'ed ac sem sit amet arcu malesuada fermentum. Nunc sed. ';\n    const compressedString =\n      'G/gBQBwHdky2aHV5KK9Snf05//1pPdmNw/7232fnIm1IB' +\n      'K1AA8RsN8OB8Nb7Lpgk3UWWUlzQXZyHQeBBbXMTQXC1j7' +\n      'wg3LJs9LqOGHRH2bj/a2iCTLLx8hBOyTqgoVuD1e+Qqdn' +\n      'f1rkUNyrWq6LtOhWgxP3QUwdhKGdZm3rJWaDDBV7+pDk1' +\n      'MIkrmjp4ma2xVi5MsgJScA3tP1I7mXeby6MELozrwoBQD' +\n      'mVTnEAicZNj4lkGqntJe2qSnGyeMmcFgraK94vCg/4iLu' +\n      'Tw5RhKhnVY++dZ6niUBmRqIutsjf5TzwF5iAg8a9UkjF5' +\n      '2eZ0tB2vo6v8SqVfNMkBmmhxr0NT9LkYF69aEjlYzj7IE' +\n      'KmEUQf1HBogRYhFIt4ymRNEgHAIzOyNEsQM=';\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n\n      zlib.brotliCompress(inputString, (err, buffer) => {\n        assert.ifError(err);\n        assert(inputString.length > buffer.length);\n\n        zlib.brotliDecompress(buffer, (err, buffer) => {\n          assert.ifError(err);\n          assert.strictEqual(buffer.toString(), inputString);\n          resolve();\n        });\n      });\n      await promise;\n    }\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      const buffer = Buffer.from(compressedString, 'base64');\n      zlib.brotliDecompress(buffer, (err, buffer) => {\n        assert.ifError(err);\n        assert.strictEqual(buffer.toString(), inputString);\n        resolve();\n      });\n\n      await promise;\n    }\n  },\n};\n\n// Test taken from\n// https://github.com/nodejs/node/blob/26eb062a9b9c0ae8cee9cb5c378e43bca363207c/test/parallel/test-zlib-maxOutputLength.js\nexport const maxOutputLength = {\n  async test() {\n    const encoded = Buffer.from('G38A+CXCIrFAIAM=', 'base64');\n\n    // Async\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      zlib.brotliDecompress(encoded, { maxOutputLength: 64 }, (err) => {\n        // TODO(soon): Make error the same as NodeJS\n        assert.match(err.message, /Memory limit exceeded/);\n        resolve();\n      });\n      await promise;\n    }\n\n    // Sync\n    assert.throws(function () {\n      zlib.brotliDecompressSync(encoded, { maxOutputLength: 64 });\n    }, RangeError);\n\n    // Async\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      zlib.brotliDecompress(encoded, { maxOutputLength: 256 }, function (err) {\n        assert.strictEqual(err, null);\n        resolve();\n      });\n\n      await promise;\n    }\n\n    // Sync\n    zlib.brotliDecompressSync(encoded, { maxOutputLength: 256 });\n  },\n};\n\n// Test for maxOutputLength: 0 - should throw immediately instead of infinite loop\nexport const maxOutputLengthZero = {\n  async test() {\n    const expectedError = {\n      name: 'RangeError',\n      message:\n        /The value of \"options\\.maxOutputLength\" is out of range\\. It must be >= 1/,\n    };\n\n    // Sync zlib - deflateSync with maxOutputLength: 0 should throw\n    assert.throws(\n      () => zlib.deflateSync('data', { maxOutputLength: 0 }),\n      expectedError\n    );\n\n    // Sync zlib - inflateSync with maxOutputLength: 0 should throw\n    const compressed = zlib.deflateSync('data');\n    assert.throws(\n      () => zlib.inflateSync(compressed, { maxOutputLength: 0 }),\n      expectedError\n    );\n\n    // Sync brotli - brotliCompressSync with maxOutputLength: 0 should throw\n    assert.throws(\n      () => zlib.brotliCompressSync('data', { maxOutputLength: 0 }),\n      expectedError\n    );\n\n    // Sync brotli - brotliDecompressSync with maxOutputLength: 0 should throw\n    const brotliCompressed = zlib.brotliCompressSync('data');\n    assert.throws(\n      () => zlib.brotliDecompressSync(brotliCompressed, { maxOutputLength: 0 }),\n      expectedError\n    );\n\n    // Async zlib - deflate with maxOutputLength: 0 should error\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      zlib.deflate('data', { maxOutputLength: 0 }, (err) => {\n        assert.match(err.message, expectedError.message);\n        resolve();\n      });\n      await promise;\n    }\n\n    // Async brotli - brotliCompress with maxOutputLength: 0 should error\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      zlib.brotliCompress('data', { maxOutputLength: 0 }, (err) => {\n        assert.match(err.message, expectedError.message);\n        resolve();\n      });\n      await promise;\n    }\n  },\n};\n\n// Test taken from\n// https://github.com/nodejs/node/blob/24302c9fe94e1dd755ac8a8cc1f6aa4444f75cb3/test/parallel/test-zlib-invalid-arg-value-brotli-compress.js\nexport const invalidArgValueBrotliCompress = {\n  test() {\n    const opts = {\n      params: {\n        [zlib.constants.BROTLI_PARAM_MODE]: 'lol',\n      },\n    };\n\n    // TODO(soon): Node's test invokes BrotliCompress without new, but we barf if you try that.\n    assert.throws(() => new zlib.BrotliCompress(opts), {\n      code: 'ERR_INVALID_ARG_TYPE',\n    });\n  },\n};\n\n// Test taken from\n// https://github.com/nodejs/node/blob/24302c9fe94e1dd755ac8a8cc1f6aa4444f75cb3/test/parallel/test-zlib-brotli-flush.js\nexport const brotliFlush = {\n  async test() {\n    const deflater = new zlib.BrotliCompress();\n\n    const chunk = Buffer.from('/9j/4AAQSkZJRgABAQEASA==', 'base64');\n    const expectedFull = Buffer.from('iweA/9j/4AAQSkZJRgABAQEASA==', 'base64');\n    let actualFull;\n\n    {\n      const { promise, resolve } = Promise.withResolvers();\n      deflater.write(chunk, function () {\n        deflater.flush(function () {\n          const bufs = [];\n          let buf;\n          while ((buf = deflater.read()) !== null) bufs.push(buf);\n          actualFull = Buffer.concat(bufs);\n          resolve();\n        });\n      });\n\n      await promise;\n    }\n    assert.deepStrictEqual(actualFull, expectedFull);\n  },\n};\n\n// Test taken from\n// https://github.com/nodejs/node/blob/24302c9fe94e1dd755ac8a8cc1f6aa4444f75cb3/test/parallel/test-zlib-brotli.js\nexport const brotli = {\n  async test() {\n    {\n      const sampleBuffer = Buffer.from(PSS_VECTORS_JSON);\n\n      // Test setting the quality parameter at stream creation:\n      const sizes = [];\n      for (\n        let quality = zlib.constants.BROTLI_MIN_QUALITY;\n        quality <= zlib.constants.BROTLI_MAX_QUALITY;\n        quality++\n      ) {\n        const encoded = zlib.brotliCompressSync(sampleBuffer, {\n          params: {\n            [zlib.constants.BROTLI_PARAM_QUALITY]: quality,\n          },\n        });\n        sizes.push(encoded.length);\n      }\n\n      // Increasing quality should roughly correspond to decreasing compressed size:\n      for (let i = 0; i < sizes.length - 1; i++) {\n        assert(sizes[i + 1] <= sizes[i] * 1.05, sizes); // 5 % margin of error.\n      }\n      assert(sizes[0] > sizes[sizes.length - 1], sizes);\n    }\n\n    {\n      // Test that setting out-of-bounds option values or keys fails.\n      assert.throws(\n        () => {\n          zlib.createBrotliCompress({\n            params: {\n              10000: 0,\n            },\n          });\n        },\n        {\n          code: 'ERR_BROTLI_INVALID_PARAM',\n          name: 'RangeError',\n          message: '10000 is not a valid Brotli parameter',\n        }\n      );\n\n      // Test that accidentally using duplicate keys fails.\n      assert.throws(\n        () => {\n          zlib.createBrotliCompress({\n            params: {\n              0: 0,\n              '00': 0,\n            },\n          });\n        },\n        {\n          code: 'ERR_BROTLI_INVALID_PARAM',\n          name: 'RangeError',\n          message: '00 is not a valid Brotli parameter',\n        }\n      );\n\n      {\n        assert.throws(\n          () => {\n            zlib.createBrotliCompress({\n              params: {\n                // This is a boolean flag\n                [zlib.constants.BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING]:\n                  42,\n              },\n            });\n          },\n          {\n            code: 'ERR_ZLIB_INITIALIZATION_FAILED',\n            name: 'Error',\n            message: 'Initialization failed',\n          }\n        );\n      }\n\n      {\n        // Test options.flush range\n        // TODO(soon): Use the same code and message as NodeJS\n        assert.throws(\n          () => {\n            zlib.brotliCompressSync('', { flush: zlib.constants.Z_FINISH });\n          },\n          {\n            //code: 'ERR_OUT_OF_RANGE',\n            name: 'RangeError',\n            message:\n              'The value of \"options.flush\" is out of range. It must be >= 0 ' +\n              'and <= 3. Received 4',\n          }\n        );\n\n        assert.throws(\n          () => {\n            zlib.brotliCompressSync('', {\n              finishFlush: zlib.constants.Z_FINISH,\n            });\n          },\n          {\n            //code: 'ERR_OUT_OF_RANGE',\n            name: 'RangeError',\n            message:\n              'The value of \"options.finishFlush\" is out of range. It must be ' +\n              '>= 0 and <= 3. Received 4',\n          }\n        );\n      }\n    }\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/2ef4b15604082abfd7aa26a4619a46802258ff3c/test/parallel/test-zlib-random-byte-pipes.js\nexport const zlibRandomBytePipes = {\n  async test() {\n    // Emit random bytes, and keep a shasum\n    class RandomReadStream extends Stream {\n      constructor(opt) {\n        super();\n\n        this.readable = true;\n        this._paused = false;\n        this._processing = false;\n        this._hasher = crypto.createHash('sha1');\n        opt ??= {};\n\n        // base block size.\n        opt.block ??= 256 * 1024;\n        // Total number of bytes to emit\n        opt.total ??= 256 * 1024 * 1024;\n        this._remaining = opt.total;\n        // How variable to make the block sizes\n        opt.jitter ??= 1024;\n        this._opt = opt;\n        this._process = this._process.bind(this);\n        queueMicrotask(this._process);\n      }\n\n      pause() {\n        this._paused = true;\n        this.emit('pause');\n      }\n\n      resume() {\n        this._paused = false;\n        this.emit('resume');\n        this._process();\n      }\n\n      _process() {\n        if (this._processing) return;\n        if (this._paused) return;\n        this._processing = true;\n\n        if (!this._remaining) {\n          this._hash = this._hasher.digest('hex').toLowerCase().trim();\n          this._processing = false;\n\n          this.emit('end');\n          return;\n        }\n\n        // Figure out how many bytes to output\n        // if finished, then just emit end.\n        let block = this._opt.block;\n        const jitter = this._opt.jitter;\n        if (jitter) {\n          block += Math.ceil(Math.random() * jitter - jitter / 2);\n        }\n        block = Math.min(block, this._remaining);\n        const buf = Buffer.allocUnsafe(block);\n        for (let i = 0; i < block; i++) {\n          buf[i] = Math.random() * 256;\n        }\n\n        this._hasher.update(buf);\n        this._remaining -= block;\n        this._processing = false;\n        this.emit('data', buf);\n        queueMicrotask(this._process);\n      }\n    }\n\n    // A filter that just verifies a shasum\n    class HashStream extends Stream {\n      constructor() {\n        super();\n        this.readable = this.writable = true;\n        this._hasher = crypto.createHash('sha1');\n      }\n\n      write(c) {\n        // Simulate the way that an fs.ReadStream returns false\n        // on *every* write, only to resume a moment later.\n        this._hasher.update(c);\n        queueMicrotask(() => this.resume());\n        return false;\n      }\n\n      resume() {\n        this.emit('resume');\n        queueMicrotask(() => this.emit('drain'));\n      }\n\n      end(c) {\n        if (c) {\n          this.write(c);\n        }\n        this._hash = this._hasher.digest('hex').toLowerCase().trim();\n        this.emit('data', this._hash);\n        this.emit('end');\n      }\n    }\n\n    for (const [createCompress, createDecompress] of [\n      [zlib.createGzip, zlib.createGunzip],\n      [zlib.createBrotliCompress, zlib.createBrotliDecompress],\n    ]) {\n      const { promise, resolve, reject } = Promise.withResolvers();\n      const inp = new RandomReadStream({ total: 1024, block: 256, jitter: 16 });\n      const out = new HashStream();\n      const gzip = createCompress();\n      const gunz = createDecompress();\n\n      inp.pipe(gzip).pipe(gunz).pipe(out);\n\n      const onDataFn = mock.fn();\n      onDataFn.mock.mockImplementation((c) => {\n        assert.strictEqual(c, inp._hash, `Hash '${c}' equals '${inp._hash}'.`);\n      });\n\n      out.on('data', onDataFn).on('end', resolve).on('error', reject);\n\n      await promise;\n\n      assert.ok(onDataFn.mock.callCount() > 0, 'Should have called onData');\n    }\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/2ef4b15604082abfd7aa26a4619a46802258ff3c/test/parallel/test-zlib-sync-no-event.js\nexport const zlibSyncNoEvent = {\n  async test() {\n    const { promise, resolve, reject } = Promise.withResolvers();\n    const message = 'Come on, Fhqwhgads.';\n    const buffer = Buffer.from(message);\n\n    const zipper = new zlib.Gzip();\n    zipper.on('close', reject);\n\n    const zipped = zipper._processChunk(buffer, zlib.constants.Z_FINISH);\n\n    const unzipper = new zlib.Gunzip();\n    unzipper.on('close', reject);\n\n    const unzipped = unzipper._processChunk(zipped, zlib.constants.Z_FINISH);\n    assert.notStrictEqual(zipped.toString(), message);\n    assert.strictEqual(unzipped.toString(), message);\n\n    queueMicrotask(resolve);\n\n    await promise;\n  },\n};\n\n// Tests are taken from:\n// https://github.com/nodejs/node/blob/2ef4b15604082abfd7aa26a4619a46802258ff3c/test/parallel/test-zlib-brotli-16GB.js\nexport const zlibBrotli16GB = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n    const content =\n      'cfffff7ff82700e2b14020f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c32200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bff3f';\n    const buf = Buffer.from(content, 'hex');\n    const decoder = zlib.createBrotliDecompress();\n\n    decoder.end(buf);\n\n    queueMicrotask(() => {\n      // There is only one chunk in the buffer\n      assert.strictEqual(\n        decoder._readableState.buffer.length,\n        getDefaultHighWaterMark() / (16 * 1024)\n      );\n      resolve();\n    });\n\n    await promise;\n  },\n};\n\n// Large test data is added at the end of the file in order to make the test code itself more readable\nconst PSS_VECTORS_JSON = `{\n  \"example01\": {\n    \"publicKey\": [\n      \"-----BEGIN PUBLIC KEY-----\",\n      \"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClbkoOcBAXWJpRh9x+qEHRVvLs\",\n      \"DjatUqRN/rHmH3rZkdjFEFb/7bFitMDyg6EqiKOU3/Umq3KRy7MHzqv84LHf1c2V\",\n      \"CAltWyuLbfXWce9jd8CSHLI8Jwpw4lmOb/idGfEFrMLT8Ms18pKA4Thrb2TE7yLh\",\n      \"4fINDOjP+yJJvZohNwIDAQAB\",\n      \"-----END PUBLIC KEY-----\"\n    ],\n    \"tests\": [\n      {\n        \"message\": \"cdc87da223d786df3b45e0bbbc721326d1ee2af806cc315475cc6f0d9c66e1b62371d45ce2392e1ac92844c310102f156a0d8d52c1f4c40ba3aa65095786cb769757a6563ba958fed0bcc984e8b517a3d5f515b23b8a41e74aa867693f90dfb061a6e86dfaaee64472c00e5f20945729cbebe77f06ce78e08f4098fba41f9d6193c0317e8b60d4b6084acb42d29e3808a3bc372d85e331170fcbf7cc72d0b71c296648b3a4d10f416295d0807aa625cab2744fd9ea8fd223c42537029828bd16be02546f130fd2e33b936d2676e08aed1b73318b750a0167d0\",\n        \"salt\": \"dee959c7e06411361420ff80185ed57f3e6776af\",\n        \"signature\": \"9074308fb598e9701b2294388e52f971faac2b60a5145af185df5287b5ed2887e57ce7fd44dc8634e407c8e0e4360bc226f3ec227f9d9e54638e8d31f5051215df6ebb9c2f9579aa77598a38f914b5b9c1bd83c4e2f9f382a0d0aa3542ffee65984a601bc69eb28deb27dca12c82c2d4c3f66cd500f1ff2b994d8a4e30cbb33c\"\n      },\n      {\n        \"message\": \"851384cdfe819c22ed6c4ccb30daeb5cf059bc8e1166b7e3530c4c233e2b5f8f71a1cca582d43ecc72b1bca16dfc7013226b9e\",\n        \"salt\": \"ef2869fa40c346cb183dab3d7bffc98fd56df42d\",\n        \"signature\": \"3ef7f46e831bf92b32274142a585ffcefbdca7b32ae90d10fb0f0c729984f04ef29a9df0780775ce43739b97838390db0a5505e63de927028d9d29b219ca2c4517832558a55d694a6d25b9dab66003c4cccd907802193be5170d26147d37b93590241be51c25055f47ef62752cfbe21418fafe98c22c4d4d47724fdb5669e843\"\n      },\n      {\n        \"message\": \"a4b159941761c40c6a82f2b80d1b94f5aa2654fd17e12d588864679b54cd04ef8bd03012be8dc37f4b83af7963faff0dfa225477437c48017ff2be8191cf3955fc07356eab3f322f7f620e21d254e5db4324279fe067e0910e2e81ca2cab31c745e67a54058eb50d993cdb9ed0b4d029c06d21a94ca661c3ce27fae1d6cb20f4564d66ce4767583d0e5f060215b59017be85ea848939127bd8c9c4d47b51056c031cf336f17c9980f3b8f5b9b6878e8b797aa43b882684333e17893fe9caa6aa299f7ed1a18ee2c54864b7b2b99b72618fb02574d139ef50f019c9eef416971338e7d470\",\n        \"salt\": \"710b9c4747d800d4de87f12afdce6df18107cc77\",\n        \"signature\": \"666026fba71bd3e7cf13157cc2c51a8e4aa684af9778f91849f34335d141c00154c4197621f9624a675b5abc22ee7d5baaffaae1c9baca2cc373b3f33e78e6143c395a91aa7faca664eb733afd14d8827259d99a7550faca501ef2b04e33c23aa51f4b9e8282efdb728cc0ab09405a91607c6369961bc8270d2d4f39fce612b1\"\n      },\n      {\n        \"message\": \"bc656747fa9eafb3f0\",\n        \"salt\": \"056f00985de14d8ef5cea9e82f8c27bef720335e\",\n        \"signature\": \"4609793b23e9d09362dc21bb47da0b4f3a7622649a47d464019b9aeafe53359c178c91cd58ba6bcb78be0346a7bc637f4b873d4bab38ee661f199634c547a1ad8442e03da015b136e543f7ab07c0c13e4225b8de8cce25d4f6eb8400f81f7e1833b7ee6e334d370964ca79fdb872b4d75223b5eeb08101591fb532d155a6de87\"\n      },\n      {\n        \"message\": \"b45581547e5427770c768e8b82b75564e0ea4e9c32594d6bff706544de0a8776c7a80b4576550eee1b2acabc7e8b7d3ef7bb5b03e462c11047eadd00629ae575480ac1470fe046f13a2bf5af17921dc4b0aa8b02bee6334911651d7f8525d10f32b51d33be520d3ddf5a709955a3dfe78283b9e0ab54046d150c177f037fdccc5be4ea5f68b5e5a38c9d7edcccc4975f455a6909b4\",\n        \"salt\": \"80e70ff86a08de3ec60972b39b4fbfdcea67ae8e\",\n        \"signature\": \"1d2aad221ca4d31ddf13509239019398e3d14b32dc34dc5af4aeaea3c095af73479cf0a45e5629635a53a018377615b16cb9b13b3e09d671eb71e387b8545c5960da5a64776e768e82b2c93583bf104c3fdb23512b7b4e89f633dd0063a530db4524b01c3f384c09310e315a79dcd3d684022a7f31c865a664e316978b759fad\"\n      },\n      {\n        \"message\": \"10aae9a0ab0b595d0841207b700d48d75faedde3b775cd6b4cc88ae06e4694ec74ba18f8520d4f5ea69cbbe7cc2beba43efdc10215ac4eb32dc302a1f53dc6c4352267e7936cfebf7c8d67035784a3909fa859c7b7b59b8e39c5c2349f1886b705a30267d402f7486ab4f58cad5d69adb17ab8cd0ce1caf5025af4ae24b1fb8794c6070cc09a51e2f9911311e3877d0044c71c57a993395008806b723ac38373d395481818528c1e7053739282053529510e935cd0fa77b8fa53cc2d474bd4fb3cc5c672d6ffdc90a00f9848712c4bcfe46c60573659b11e6457e861f0f604b6138d144f8ce4e2da73\",\n        \"salt\": \"a8ab69dd801f0074c2a1fc60649836c616d99681\",\n        \"signature\": \"2a34f6125e1f6b0bf971e84fbd41c632be8f2c2ace7de8b6926e31ff93e9af987fbc06e51e9be14f5198f91f3f953bd67da60a9df59764c3dc0fe08e1cbef0b75f868d10ad3fba749fef59fb6dac46a0d6e504369331586f58e4628f39aa278982543bc0eeb537dc61958019b394fb273f215858a0a01ac4d650b955c67f4c58\"\n      }\n    ]\n  },\n  \"example10\": {\n    \"publicKey\": [\n      \"-----BEGIN PUBLIC KEY-----\",\n      \"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApd2GesTLAvkLlFfUjBSn\",\n      \"cO+ZHFbDnA7GX9Ea+ok3zqV7m+esc7RcABdhW4LWIuMYdTtgJ8D9FXvhL4CQ/uKn\",\n      \"rc0O73WfiLpJl8ekLVjJqhLLma4AH+UhwTu1QxRFqNWuT15MfpSKwifTYEBx8g5X\",\n      \"fpBfvrFd+vBtHeWuYlPWOmohILMaXaXavJVQYA4g8n03OeJieSX+o8xQnyHf8E5u\",\n      \"6kVJxUDWgJ/5MH7t6R//WHM9g4WiN9bTcFoz45GQCZIHDfet8TV89+NwDONmfeg/\",\n      \"F7jfF3jbOB3OCctK0FilEQAac4GY7ifPVaE7dUU5kGWC7IsXS9WNXR89dnxhNyGu\",\n      \"BQIDAQAB\",\n      \"-----END PUBLIC KEY-----\"\n    ],\n    \"tests\": [\n      {\n        \"message\": \"883177e5126b9be2d9a9680327d5370c6f26861f5820c43da67a3ad609\",\n        \"salt\": \"04e215ee6ff934b9da70d7730c8734abfcecde89\",\n        \"signature\": \"82c2b160093b8aa3c0f7522b19f87354066c77847abf2a9fce542d0e84e920c5afb49ffdfdace16560ee94a1369601148ebad7a0e151cf16331791a5727d05f21e74e7eb811440206935d744765a15e79f015cb66c532c87a6a05961c8bfad741a9a6657022894393e7223739796c02a77455d0f555b0ec01ddf259b6207fd0fd57614cef1a5573baaff4ec00069951659b85f24300a25160ca8522dc6e6727e57d019d7e63629b8fe5e89e25cc15beb3a647577559299280b9b28f79b0409000be25bbd96408ba3b43cc486184dd1c8e62553fa1af4040f60663de7f5e49c04388e257f1ce89c95dab48a315d9b66b1b7628233876ff2385230d070d07e1666\"\n      },\n      {\n        \"message\": \"dd670a01465868adc93f26131957a50c52fb777cdbaa30892c9e12361164ec13979d43048118e4445db87bee58dd987b3425d02071d8dbae80708b039dbb64dbd1de5657d9fed0c118a54143742e0ff3c87f74e45857647af3f79eb0a14c9d75ea9a1a04b7cf478a897a708fd988f48e801edb0b7039df8c23bb3c56f4e821ac\",\n        \"salt\": \"8b2bdd4b40faf545c778ddf9bc1a49cb57f9b71b\",\n        \"signature\": \"14ae35d9dd06ba92f7f3b897978aed7cd4bf5ff0b585a40bd46ce1b42cd2703053bb9044d64e813d8f96db2dd7007d10118f6f8f8496097ad75e1ff692341b2892ad55a633a1c55e7f0a0ad59a0e203a5b8278aec54dd8622e2831d87174f8caff43ee6c46445345d84a59659bfb92ecd4c818668695f34706f66828a89959637f2bf3e3251c24bdba4d4b7649da0022218b119c84e79a6527ec5b8a5f861c159952e23ec05e1e717346faefe8b1686825bd2b262fb2531066c0de09acde2e4231690728b5d85e115a2f6b92b79c25abc9bd9399ff8bcf825a52ea1f56ea76dd26f43baafa18bfa92a504cbd35699e26d1dcc5a2887385f3c63232f06f3244c3\"\n      },\n      {\n        \"message\": \"48b2b6a57a63c84cea859d65c668284b08d96bdcaabe252db0e4a96cb1bac6019341db6fbefb8d106b0e90eda6bcc6c6262f37e7ea9c7e5d226bd7df85ec5e71efff2f54c5db577ff729ff91b842491de2741d0c631607df586b905b23b91af13da12304bf83eca8a73e871ff9db\",\n        \"salt\": \"4e96fc1b398f92b44671010c0dc3efd6e20c2d73\",\n        \"signature\": \"6e3e4d7b6b15d2fb46013b8900aa5bbb3939cf2c095717987042026ee62c74c54cffd5d7d57efbbf950a0f5c574fa09d3fc1c9f513b05b4ff50dd8df7edfa20102854c35e592180119a70ce5b085182aa02d9ea2aa90d1df03f2daae885ba2f5d05afdac97476f06b93b5bc94a1a80aa9116c4d615f333b098892b25fface266f5db5a5a3bcc10a824ed55aad35b727834fb8c07da28fcf416a5d9b2224f1f8b442b36f91e456fdea2d7cfe3367268de0307a4c74e924159ed33393d5e0655531c77327b89821bdedf880161c78cd4196b5419f7acc3f13e5ebf161b6e7c6724716ca33b85c2e25640192ac2859651d50bde7eb976e51cec828b98b6563b86bb\"\n      },\n      {\n        \"message\": \"0b8777c7f839baf0a64bbbdbc5ce79755c57a205b845c174e2d2e90546a089c4e6ec8adffa23a7ea97bae6b65d782b82db5d2b5a56d22a29a05e7c4433e2b82a621abba90add05ce393fc48a840542451a\",\n        \"salt\": \"c7cd698d84b65128d8835e3a8b1eb0e01cb541ec\",\n        \"signature\": \"34047ff96c4dc0dc90b2d4ff59a1a361a4754b255d2ee0af7d8bf87c9bc9e7ddeede33934c63ca1c0e3d262cb145ef932a1f2c0a997aa6a34f8eaee7477d82ccf09095a6b8acad38d4eec9fb7eab7ad02da1d11d8e54c1825e55bf58c2a23234b902be124f9e9038a8f68fa45dab72f66e0945bf1d8bacc9044c6f07098c9fcec58a3aab100c805178155f030a124c450e5acbda47d0e4f10b80a23f803e774d023b0015c20b9f9bbe7c91296338d5ecb471cafb032007b67a60be5f69504a9f01abb3cb467b260e2bce860be8d95bf92c0c8e1496ed1e528593a4abb6df462dde8a0968dffe4683116857a232f5ebf6c85be238745ad0f38f767a5fdbf486fb\"\n      },\n      {\n        \"message\": \"f1036e008e71e964dadc9219ed30e17f06b4b68a955c16b312b1eddf028b74976bed6b3f6a63d4e77859243c9cccdc98016523abb02483b35591c33aad81213bb7c7bb1a470aabc10d44256c4d4559d916\",\n        \"salt\": \"efa8bff96212b2f4a3f371a10d574152655f5dfb\",\n        \"signature\": \"7e0935ea18f4d6c1d17ce82eb2b3836c55b384589ce19dfe743363ac9948d1f346b7bfddfe92efd78adb21faefc89ade42b10f374003fe122e67429a1cb8cbd1f8d9014564c44d120116f4990f1a6e38774c194bd1b8213286b077b0499d2e7b3f434ab12289c556684deed78131934bb3dd6537236f7c6f3dcb09d476be07721e37e1ceed9b2f7b406887bd53157305e1c8b4f84d733bc1e186fe06cc59b6edb8f4bd7ffefdf4f7ba9cfb9d570689b5a1a4109a746a690893db3799255a0cb9215d2d1cd490590e952e8c8786aa0011265252470c041dfbc3eec7c3cbf71c24869d115c0cb4a956f56d530b80ab589acfefc690751ddf36e8d383f83cedd2cc\"\n      },\n      {\n        \"message\": \"25f10895a87716c137450bb9519dfaa1f207faa942ea88abf71e9c17980085b555aebab76264ae2a3ab93c2d12981191ddac6fb5949eb36aee3c5da940f00752c916d94608fa7d97ba6a2915b688f20323d4e9d96801d89a72ab5892dc2117c07434fcf972e058cf8c41ca4b4ff554f7d5068ad3155fced0f3125bc04f9193378a8f5c4c3b8cb4dd6d1cc69d30ecca6eaa51e36a05730e9e342e855baf099defb8afd7\",\n        \"salt\": \"ad8b1523703646224b660b550885917ca2d1df28\",\n        \"signature\": \"6d3b5b87f67ea657af21f75441977d2180f91b2c5f692de82955696a686730d9b9778d970758ccb26071c2209ffbd6125be2e96ea81b67cb9b9308239fda17f7b2b64ecda096b6b935640a5a1cb42a9155b1c9ef7a633a02c59f0d6ee59b852c43b35029e73c940ff0410e8f114eed46bbd0fae165e42be2528a401c3b28fd818ef3232dca9f4d2a0f5166ec59c42396d6c11dbc1215a56fa17169db9575343ef34f9de32a49cdc3174922f229c23e18e45df9353119ec4319cedce7a17c64088c1f6f52be29634100b3919d38f3d1ed94e6891e66a73b8fb849f5874df59459e298c7bbce2eee782a195aa66fe2d0732b25e595f57d3e061b1fc3e4063bf98f\"\n      }\n    ]\n  }\n}`;\n\nexport const compatFlagTest = {\n  async test(_, env) {\n    const waitForIt = async (test) => {\n      const res = await env[test].fetch('http://example.org');\n      return await res.text();\n    };\n    const results = await Promise.allSettled([\n      waitForIt('compat'),\n      waitForIt('compatv2'),\n      waitForIt('compatNoV2'),\n    ]);\n    assert.deepStrictEqual(results, [\n      { status: 'fulfilled', value: 'true' },\n      { status: 'fulfilled', value: 'true' },\n      { status: 'fulfilled', value: 'true' },\n    ]);\n  },\n};\n\nexport const stateSizeTest = {\n  test() {\n    const Uint32Array_orig = Uint32Array;\n    assert.throws(\n      () => {\n        const message = 'Come on, Fhqwhgads.';\n        const buffer = Buffer.from(message);\n        globalThis.Uint32Array = function (...args) {\n          if (args.length == 1 && arguments[0] == 2) {\n            return new Uint32Array_orig(new ArrayBuffer(32), 32);\n          }\n          return new Uint32Array_orig(...args);\n        };\n\n        const zipper = new zlib.Gzip();\n        const zipped = zipper._processChunk(buffer, zlib.constants.FINISH);\n        const unzipper = new zlib.Gunzip();\n        unzipper._processChunk(zipped, zlib.constants.FINISH);\n      },\n      {\n        message: /Invalid write result buffer/,\n      }\n    );\n    globalThis.Uint32Array = Uint32Array_orig;\n  },\n};\n\nexport const zlibStreamTest = {\n  async test() {\n    const imgBase64 =\n      'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII';\n\n    const buf = Buffer.from(imgBase64, 'base64');\n    const imageStream = Readable.from(Buffer.from(imgBase64, 'base64'));\n    const g = zlib.createGzip();\n\n    const { promise, resolve } = Promise.withResolvers();\n    const chunks = [];\n    const w = new Writable({\n      construct(callback) {\n        // Double queueMicrotask is intentional to reproduce the bug.\n        // Do not remove any of them.\n        queueMicrotask(() => {\n          queueMicrotask(() => {\n            callback(null);\n          });\n        });\n      },\n      write(chunk, encoding, callback) {\n        chunks.push(chunk);\n        callback();\n      },\n    });\n    w.on('close', resolve);\n    const pp = imageStream.pipe(g);\n    pp.pipe(w);\n\n    await promise;\n\n    assert.strictEqual(\n      zlib.gunzipSync(Buffer.concat(chunks)).toString('hex'),\n      buf.toString('hex')\n    );\n  },\n};\n\n// Regression test: after writeSync() returns, z_stream must not retain stale\n// buffer pointers.  When handle.params() is called directly (bypassing the\n// TypeScript wrapper's flush-before-params protection), deflateParams() may\n// internally call deflate(Z_BLOCK) to flush pending data.  Before the fix,\n// z_stream.next_out still pointed into the kj::Array that backed the output\n// buffer — memory whose BackingStore ref had already been released.  If the\n// JS ArrayBuffer was then garbage-collected, that write went to freed memory.\n// The fix clears z_stream buffer pointers after every write, so\n// deflateParams() sees avail_out=0 and returns Z_BUF_ERROR (non-fatal)\n// instead of writing through a dangling pointer.\nexport const zlibParamsAfterWriteNoStalePointers = {\n  test() {\n    // Configuration that leaves data pending in zlib's internal buffers:\n    // level 0 (stored blocks) + minimum memory/window + Z_NO_FLUSH.\n    const deflate = zlib.createDeflateRaw({\n      level: 0,\n      memLevel: 1,\n      windowBits: 9,\n    });\n\n    const handle = deflate._handle;\n    assert.ok(handle, 'native handle should be accessible');\n\n    const input = Buffer.from('A'.repeat(64));\n    const output1 = Buffer.alloc(1024);\n\n    // Step 1: writeSync with Z_NO_FLUSH — data is buffered internally.\n    handle.writeSync(0, input, 0, input.length, output1, 0, output1.length);\n\n    // Capture how many bytes were produced so far.\n    const state = deflate._writeState;\n    const bytesOut1 = output1.length - state[0];\n\n    // Step 2: params() directly on native handle — no preceding flush.\n    // Before the fix this could write to a stale next_out pointer.\n    // After the fix this safely returns Z_BUF_ERROR (handled as non-fatal).\n    handle.params(\n      zlib.constants.Z_DEFAULT_COMPRESSION,\n      zlib.constants.Z_DEFAULT_STRATEGY\n    );\n\n    // Step 3: finish the stream — must still produce a valid deflate stream.\n    const output2 = Buffer.alloc(1024);\n    handle.writeSync(\n      zlib.constants.Z_FINISH,\n      Buffer.alloc(0),\n      0,\n      0,\n      output2,\n      0,\n      output2.length\n    );\n    const bytesOut2 = output2.length - state[0];\n\n    // Step 4: concatenate both output chunks and decompress.\n    const compressed = Buffer.concat([\n      output1.slice(0, bytesOut1),\n      output2.slice(0, bytesOut2),\n    ]);\n    const decompressed = zlib.inflateRawSync(compressed, { windowBits: 9 });\n    assert.strictEqual(\n      decompressed.toString(),\n      input.toString(),\n      'round-trip through write/params/finish must preserve data'\n    );\n  },\n};\n\n// Regression test for https://github.com/cloudflare/workerd/issues/6286\n// inflateRawSync throws \"Memory limit exceeded\" when maxOutputLength is set\n// to exactly the decompressed size. This is an off-by-one error in the\n// GrowableBuffer: after zlib fills the buffer completely (avail_out == 0),\n// the processing loop tries to add another chunk which exceeds maxCapacity.\nexport const maxOutputLengthExactSize = {\n  test() {\n    // Create a known payload and compress it with deflateRaw\n    const original = Buffer.from('a]b]c]d]e]f]g]h]i]j]k]l]m]n]o]p]'.repeat(32));\n    const compressed = zlib.deflateRawSync(original);\n    const exactSize = original.length;\n\n    // This should succeed — maxOutputLength is exactly the output size.\n    // Before the fix, this throws RangeError: \"Memory limit exceeded\"\n    const decompressed = zlib.inflateRawSync(compressed, {\n      maxOutputLength: exactSize,\n    });\n    assert.deepStrictEqual(decompressed, original);\n\n    // Sanity check: maxOutputLength + 1 also works\n    const decompressed2 = zlib.inflateRawSync(compressed, {\n      maxOutputLength: exactSize + 1,\n    });\n    assert.deepStrictEqual(decompressed2, original);\n\n    // Same bug affects inflateSync\n    const compressedZlib = zlib.deflateSync(original);\n    const decompressed3 = zlib.inflateSync(compressedZlib, {\n      maxOutputLength: exactSize,\n    });\n    assert.deepStrictEqual(decompressed3, original);\n\n    // And gunzipSync\n    const compressedGzip = zlib.gzipSync(original);\n    const decompressed4 = zlib.gunzipSync(compressedGzip, {\n      maxOutputLength: exactSize,\n    });\n    assert.deepStrictEqual(decompressed4, original);\n\n    // And brotliDecompressSync\n    const compressedBrotli = zlib.brotliCompressSync(original);\n    const decompressed5 = zlib.brotliDecompressSync(compressedBrotli, {\n      maxOutputLength: exactSize,\n    });\n    assert.deepStrictEqual(decompressed5, original);\n\n    // Verify that a maxOutputLength that's genuinely too small still throws\n    assert.throws(\n      () => zlib.inflateRawSync(compressed, { maxOutputLength: exactSize - 1 }),\n      RangeError\n    );\n  },\n};\n\n// Regression test for the exception-safety gap in the clearBuffers() fix.\n//\n// When the writeState buffer passed to initialize() is too small (fewer\n// than 2 uint32 elements), updateWriteResult() throws after deflate()\n// has already run.  The original clearBuffers() fix placed explicit calls\n// *after* updateWriteResult(), so the exception unwinds past them —\n// leaving z_stream.next_out pointing into the output buffer's BackingStore.\n// If that buffer is later garbage-collected, a subsequent call to\n// params() -> deflateParams() -> flush_pending() writes to freed memory.\n//\n// The fix uses KJ_DEFER so clearBuffers() runs on every scope exit,\n// including exception unwinding.\n//\n// NOTE: The actual UAF only manifests when GC frees the backing stores\n// (detectable via ASAN + forced gc()).  This test exercises the vulnerable\n// code path to guard against regressions and ensure no crash occurs; the\n// memory-safety guarantee is provided by the KJ_DEFER fix itself.\nexport const zlibParamsAfterFailedWriteNoStalePointers = {\n  test() {\n    const streams = [];\n    for (let j = 0; j < 10; j++) {\n      // Access the native handle directly and re-initialize with a writeState\n      // buffer that is intentionally too small (1 uint32 instead of 2).\n      // This causes updateWriteResult() to throw after a successful deflate.\n      const handle = new zlib.DeflateRaw({ level: 0 })._handle;\n      handle.initialize(\n        15,\n        0,\n        8,\n        0,\n        Buffer.from(new Uint32Array(1).buffer),\n        () => {},\n        undefined\n      );\n\n      const payload = Buffer.alloc(64, 0x41);\n      let outputBuffer = Buffer.alloc(4096);\n\n      // writeSync succeeds in deflate() but throws in updateWriteResult()\n      // due to the undersized writeState.  Before the KJ_DEFER fix, this\n      // left z_stream.next_out pointing into outputBuffer.\n      try {\n        handle.writeSync(\n          0,\n          payload,\n          0,\n          payload.length,\n          outputBuffer,\n          0,\n          outputBuffer.length\n        );\n      } catch (e) {\n        // Expected: \"Invalid write result buffer\"\n      }\n      streams.push({ handle, outputBuffer });\n    }\n\n    // Drop JS references to output buffers.\n    for (const s of streams) s.outputBuffer = null;\n\n    // params() calls deflateParams() which may internally flush.  Before the\n    // fix, this could write through a dangling next_out pointer.  After the\n    // fix, next_out points to a safe dummy byte with avail_out=0, so\n    // deflateParams() returns Z_BUF_ERROR (non-fatal).\n    for (const s of streams) {\n      try {\n        s.handle.params(\n          zlib.constants.Z_DEFAULT_COMPRESSION,\n          zlib.constants.Z_DEFAULT_STRATEGY\n        );\n      } catch (e) {\n        // May throw due to stream state; that's fine — the point is no UAF.\n      }\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/zlib-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"nodejs-zlib-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"zlib-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\", \"nodejs_compat_v2\", \"nodejs_zlib\"],\n        bindings = [\n          ( name = \"compat\", service = \"compat\" ),\n          ( name = \"compatv2\", service = \"compatv2\" ),\n          ( name = \"compatNoV2\", service = \"compatNoV2\" ),\n        ],\n      )\n    ),\n    (\n      name = \"compat\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = \"export default {fetch() {return new Response(`${JSON.stringify(globalThis.Cloudflare.compatibilityFlags.nodejs_zlib)}`);}}\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_zlib\"],\n      )\n    ),\n    (\n      name = \"compatv2\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = \"export default {fetch() {return new Response(`${JSON.stringify(globalThis.Cloudflare.compatibilityFlags.nodejs_zlib)}`);}}\")\n        ],\n        compatibilityFlags = [\"nodejs_compat_v2\", \"nodejs_zlib\"],\n      )\n    ),\n    (\n      name = \"compatNoV2\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = \"export default {fetch() {return new Response(`${JSON.stringify(globalThis.Cloudflare.compatibilityFlags.nodejs_zlib)}`);}}\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"no_nodejs_compat_v2\", \"nodejs_zlib\"],\n      )\n    )\n  ],\n);\n\n"
  },
  {
    "path": "src/workerd/api/node/tests/zlib-zstd-nodejs-test.js",
    "content": "import assert from 'node:assert';\nimport { Buffer } from 'node:buffer';\nimport zlib from 'node:zlib';\n\n// Basic sync compress/decompress test\nexport const zstdBasicSyncTest = {\n  test() {\n    const input = Buffer.from('Hello, Zstd compression!');\n    const compressed = zlib.zstdCompressSync(input);\n    assert(Buffer.isBuffer(compressed), 'Compressed output should be a buffer');\n    assert(compressed.length > 0, 'Compressed output should not be empty');\n\n    const decompressed = zlib.zstdDecompressSync(compressed);\n    assert(\n      Buffer.isBuffer(decompressed),\n      'Decompressed output should be a buffer'\n    );\n    assert.strictEqual(\n      decompressed.toString(),\n      input.toString(),\n      'Round-trip should match'\n    );\n  },\n};\n\n// Basic async compress/decompress test\nexport const zstdBasicAsyncTest = {\n  async test() {\n    const input = Buffer.from('Hello, async Zstd compression!');\n\n    const {\n      promise: compressPromise,\n      resolve: compressResolve,\n      reject: compressReject,\n    } = Promise.withResolvers();\n    zlib.zstdCompress(input, (err, res) => {\n      if (err) compressReject(err);\n      else compressResolve(res);\n    });\n    const compressed = await compressPromise;\n\n    assert(Buffer.isBuffer(compressed), 'Compressed output should be a buffer');\n    assert(compressed.length > 0, 'Compressed output should not be empty');\n\n    const {\n      promise: decompressPromise,\n      resolve: decompressResolve,\n      reject: decompressReject,\n    } = Promise.withResolvers();\n    zlib.zstdDecompress(compressed, (err, res) => {\n      if (err) decompressReject(err);\n      else decompressResolve(res);\n    });\n    const decompressed = await decompressPromise;\n\n    assert(\n      Buffer.isBuffer(decompressed),\n      'Decompressed output should be a buffer'\n    );\n    assert.strictEqual(\n      decompressed.toString(),\n      input.toString(),\n      'Round-trip should match'\n    );\n  },\n};\n\n// Test with string input\nexport const zstdStringInputTest = {\n  test() {\n    const input = 'This is a string input for Zstd compression';\n    const compressed = zlib.zstdCompressSync(input);\n    const decompressed = zlib.zstdDecompressSync(compressed);\n    assert.strictEqual(\n      decompressed.toString(),\n      input,\n      'String input round-trip should match'\n    );\n  },\n};\n\n// Test with larger data\nexport const zstdLargeDataTest = {\n  test() {\n    // Create a 100KB buffer with repetitive content (compresses well)\n    const input = Buffer.alloc(100 * 1024);\n    for (let i = 0; i < input.length; i++) {\n      input[i] = i % 256;\n    }\n\n    const compressed = zlib.zstdCompressSync(input);\n    assert(\n      compressed.length < input.length,\n      'Compressed should be smaller than input'\n    );\n\n    const decompressed = zlib.zstdDecompressSync(compressed);\n    assert(input.equals(decompressed), 'Large data round-trip should match');\n  },\n};\n\n// Test with different compression levels\nexport const zstdCompressionLevelsTest = {\n  test() {\n    const input = Buffer.from(\n      'Test data for compression level testing'.repeat(100)\n    );\n\n    // Test compression level 1 (fastest)\n    const compressedFast = zlib.zstdCompressSync(input, {\n      params: { [zlib.constants.ZSTD_c_compressionLevel]: 1 },\n    });\n\n    // Test compression level 19 (best compression)\n    const compressedBest = zlib.zstdCompressSync(input, {\n      params: { [zlib.constants.ZSTD_c_compressionLevel]: 19 },\n    });\n\n    // Both should decompress correctly\n    const decompressedFast = zlib.zstdDecompressSync(compressedFast);\n    const decompressedBest = zlib.zstdDecompressSync(compressedBest);\n\n    assert(\n      input.equals(decompressedFast),\n      'Fast compression should decompress correctly'\n    );\n    assert(\n      input.equals(decompressedBest),\n      'Best compression should decompress correctly'\n    );\n\n    // Higher compression level should typically produce smaller output\n    assert(\n      compressedBest.length <= compressedFast.length,\n      'Higher compression level should produce smaller or equal output'\n    );\n  },\n};\n\n// Test with default compression level\nexport const zstdDefaultCompressionTest = {\n  test() {\n    const input = Buffer.from('Testing default compression level');\n    const compressed = zlib.zstdCompressSync(input);\n    const decompressed = zlib.zstdDecompressSync(compressed);\n    assert.strictEqual(\n      decompressed.toString(),\n      input.toString(),\n      'Default compression should work'\n    );\n  },\n};\n\n// Test stream API\nexport const zstdStreamTest = {\n  async test() {\n    const input = Buffer.from('Stream compression test data'.repeat(50));\n\n    // Create compress stream\n    const compress = zlib.createZstdCompress();\n    const decompress = zlib.createZstdDecompress();\n\n    const chunks = [];\n\n    // Pipe through compress\n    compress.on('data', (chunk) => chunks.push(chunk));\n\n    await new Promise((resolve, reject) => {\n      compress.on('end', resolve);\n      compress.on('error', reject);\n      compress.end(input);\n    });\n\n    const compressed = Buffer.concat(chunks);\n    assert(compressed.length > 0, 'Stream should produce output');\n\n    // Decompress\n    const decompressedChunks = [];\n    decompress.on('data', (chunk) => decompressedChunks.push(chunk));\n\n    await new Promise((resolve, reject) => {\n      decompress.on('end', resolve);\n      decompress.on('error', reject);\n      decompress.end(compressed);\n    });\n\n    const decompressed = Buffer.concat(decompressedChunks);\n    assert(input.equals(decompressed), 'Stream round-trip should match');\n  },\n};\n\n// Test empty input\nexport const zstdEmptyInputTest = {\n  test() {\n    const input = Buffer.alloc(0);\n    const compressed = zlib.zstdCompressSync(input);\n    const decompressed = zlib.zstdDecompressSync(compressed);\n    assert.strictEqual(\n      decompressed.length,\n      0,\n      'Empty input should produce empty output'\n    );\n  },\n};\n\n// Test invalid compressed data\nexport const zstdInvalidDataTest = {\n  test() {\n    const invalidData = Buffer.from('This is not valid zstd compressed data');\n    assert.throws(\n      () => zlib.zstdDecompressSync(invalidData),\n      /Zstd decompression failed/,\n      'Should throw on invalid compressed data'\n    );\n  },\n};\n\n// Test chunkSize option\nexport const zstdChunkSizeTest = {\n  test() {\n    const input = Buffer.from('Testing chunk size option'.repeat(100));\n    const compressed = zlib.zstdCompressSync(input, { chunkSize: 1024 });\n    const decompressed = zlib.zstdDecompressSync(compressed, {\n      chunkSize: 1024,\n    });\n    assert(\n      input.equals(decompressed),\n      'Custom chunkSize should work correctly'\n    );\n  },\n};\n\n// Test maxOutputLength option\nexport const zstdMaxOutputLengthTest = {\n  test() {\n    const input = Buffer.from('A'.repeat(1000));\n    const compressed = zlib.zstdCompressSync(input);\n\n    // Try to decompress with a maxOutputLength that's too small\n    assert.throws(\n      () => zlib.zstdDecompressSync(compressed, { maxOutputLength: 10 }),\n      /Memory limit exceeded/,\n      'Should throw when maxOutputLength is exceeded'\n    );\n  },\n};\n\n// Test callback error handling\nexport const zstdCallbackErrorTest = {\n  async test() {\n    const invalidData = Buffer.from('invalid zstd data');\n\n    const { promise, resolve, reject } = Promise.withResolvers();\n    zlib.zstdDecompress(invalidData, (err, res) => {\n      if (err) reject(err);\n      else resolve(res);\n    });\n\n    try {\n      await promise;\n      assert.fail('Should have thrown');\n    } catch (err) {\n      assert(err instanceof Error, 'Should receive an error');\n    }\n  },\n};\n\n// Test with params for strategy\nexport const zstdStrategyTest = {\n  test() {\n    const input = Buffer.from('Testing compression strategy'.repeat(50));\n\n    // Test with ZSTD_fast strategy\n    const compressedFast = zlib.zstdCompressSync(input, {\n      params: {\n        [zlib.constants.ZSTD_c_strategy]: zlib.constants.ZSTD_fast,\n      },\n    });\n\n    const decompressed = zlib.zstdDecompressSync(compressedFast);\n    assert(input.equals(decompressed), 'Strategy option should work correctly');\n  },\n};\n\n// Test with info option\nexport const zstdInfoOptionTest = {\n  test() {\n    const input = Buffer.from('Testing info option');\n    const result = zlib.zstdCompressSync(input, { info: true });\n\n    // When info is true, result should be an object with buffer and engine properties\n    assert(\n      typeof result === 'object',\n      'Result should be an object when info is true'\n    );\n    assert(result.buffer, 'Result should have a buffer property');\n    assert(result.engine, 'Result should have an engine property');\n  },\n};\n\n// Test classes are exported\nexport const zstdClassesExportedTest = {\n  test() {\n    assert.strictEqual(\n      typeof zlib.ZstdCompress,\n      'function',\n      'ZstdCompress should be exported'\n    );\n    assert.strictEqual(\n      typeof zlib.ZstdDecompress,\n      'function',\n      'ZstdDecompress should be exported'\n    );\n    assert.strictEqual(\n      typeof zlib.createZstdCompress,\n      'function',\n      'createZstdCompress should be exported'\n    );\n    assert.strictEqual(\n      typeof zlib.createZstdDecompress,\n      'function',\n      'createZstdDecompress should be exported'\n    );\n  },\n};\n\n// Test sync functions are exported\nexport const zstdSyncFunctionsExportedTest = {\n  test() {\n    assert.strictEqual(\n      typeof zlib.zstdCompressSync,\n      'function',\n      'zstdCompressSync should be exported'\n    );\n    assert.strictEqual(\n      typeof zlib.zstdDecompressSync,\n      'function',\n      'zstdDecompressSync should be exported'\n    );\n    assert.strictEqual(\n      typeof zlib.zstdCompress,\n      'function',\n      'zstdCompress should be exported'\n    );\n    assert.strictEqual(\n      typeof zlib.zstdDecompress,\n      'function',\n      'zstdDecompress should be exported'\n    );\n  },\n};\n\n// Test sync decompression of truncated compressed data (EOF mid-frame)\nexport const zstdTruncatedSyncTest = {\n  test() {\n    const input = Buffer.from('Hello, Zstd truncation test!'.repeat(100));\n    const compressed = zlib.zstdCompressSync(input);\n\n    // Cut the compressed data in half\n    const truncated = compressed.subarray(0, Math.floor(compressed.length / 2));\n    assert.throws(\n      () => zlib.zstdDecompressSync(truncated),\n      (err) => err instanceof Error,\n      'Should throw on truncated compressed data'\n    );\n  },\n};\n\n// Test async decompression of truncated compressed data\nexport const zstdTruncatedAsyncTest = {\n  async test() {\n    const input = Buffer.from('Hello, Zstd truncation test!'.repeat(100));\n    const compressed = zlib.zstdCompressSync(input);\n    const truncated = compressed.subarray(0, Math.floor(compressed.length / 2));\n\n    const { promise, resolve, reject } = Promise.withResolvers();\n    zlib.zstdDecompress(truncated, (err, res) => {\n      if (err) reject(err);\n      else resolve(res);\n    });\n\n    try {\n      await promise;\n      assert.fail('Should have thrown on truncated data');\n    } catch (err) {\n      assert(\n        err instanceof Error,\n        'Should receive an error for truncated data'\n      );\n    }\n  },\n};\n\n// Test stream decompression of truncated compressed data\nexport const zstdTruncatedStreamTest = {\n  async test() {\n    const input = Buffer.from('Stream truncation test data'.repeat(100));\n    const compressed = zlib.zstdCompressSync(input);\n    const truncated = compressed.subarray(0, Math.floor(compressed.length / 2));\n\n    const decompress = zlib.createZstdDecompress();\n\n    let errored = false;\n    decompress.on('error', () => {\n      errored = true;\n    });\n\n    decompress.end(truncated);\n    try {\n      for await (const _chunk of decompress) {\n        // consume chunks\n      }\n    } catch {\n      errored = true;\n    }\n\n    assert(errored, 'Stream should error on truncated compressed data');\n  },\n};\n\n// Test stream decompression with large output and small chunkSize to exercise\n// the processCallback recursion path; verify we don't get a stack overflow.\nexport const zstdStreamLargeDecompressTest = {\n  async test() {\n    // 1MB of compressible data — compresses small, decompresses large.\n    // With chunkSize=64, this requires ~16,000 output chunks, which triggers\n    // deep recursion in processCallback if the callback is synchronous.\n    const input = Buffer.alloc(1024 * 1024, 'A');\n    const compressed = zlib.zstdCompressSync(input);\n\n    const decompress = zlib.createZstdDecompress({ chunkSize: 64 });\n    let totalBytes = 0;\n\n    decompress.end(compressed);\n    for await (const chunk of decompress) {\n      totalBytes += chunk.length;\n    }\n\n    assert.strictEqual(\n      totalBytes,\n      input.length,\n      `Decompressed size should be ${input.length}, got ${totalBytes}`\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/node/tests/zlib-zstd-nodejs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"nodejs-zstd-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"zlib-zstd-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\", \"nodejs_compat_v2\", \"nodejs_zlib\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/node/timers.c++",
    "content": "#include \"timers.h\"\n\n#include <workerd/api/global-scope.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api::node {\n\n// The setImmediate/clearImmediate methods are only exposed on globalThis if the\n// node_compat_v2 flag is set. However, we want them exposed via `node:timers`\n// generally when just the original node_compat is enabled. Therefore, we provide\n// this alternative route to the implementations on ServiceWorkerGlobalScope.\njsg::Ref<Immediate> TimersUtil::setImmediate(jsg::Lock& js,\n    jsg::Function<void(jsg::Arguments<jsg::Value>)> function,\n    jsg::Arguments<jsg::Value> args) {\n  auto context = js.v8Context();\n  auto& global =\n      jsg::extractInternalPointer<ServiceWorkerGlobalScope, true>(context, context->Global());\n  return global.setImmediate(js, kj::mv(function), kj::mv(args));\n}\n\nvoid TimersUtil::clearImmediate(jsg::Lock& js, kj::Maybe<jsg::Ref<Immediate>> maybeImmediate) {\n  auto context = js.v8Context();\n  auto& global =\n      jsg::extractInternalPointer<ServiceWorkerGlobalScope, true>(context, context->Global());\n  global.clearImmediate(kj::mv(maybeImmediate));\n}\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/timers.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <workerd/jsg/jsg.h>\n\n#include <kj/common.h>\n\nnamespace workerd::api {\n\nclass Immediate;\n\nnamespace node {\nclass TimersUtil final: public jsg::Object {\n public:\n  TimersUtil() = default;\n  TimersUtil(jsg::Lock&, const jsg::Url&) {}\n\n  jsg::Ref<Immediate> setImmediate(jsg::Lock& js,\n      jsg::Function<void(jsg::Arguments<jsg::Value>)> function,\n      jsg::Arguments<jsg::Value> args);\n  void clearImmediate(jsg::Lock& js, kj::Maybe<jsg::Ref<Immediate>> immediate);\n\n  JSG_RESOURCE_TYPE(TimersUtil) {\n    JSG_METHOD(setImmediate);\n    JSG_METHOD(clearImmediate);\n  }\n};\n\n#define EW_NODE_TIMERS_ISOLATE_TYPES api::node::TimersUtil\n}  // namespace node\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/node/url.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#include \"url.h\"\n\n#include \"ada.h\"\n\n#include <workerd/rust/net/lib.rs.h>\n\n#include <kj-rs/kj-rs.h>\n\nusing namespace kj_rs;\n\nnamespace workerd::api::node {\n\nnamespace {\n\n// Implementation is used by `domainToASCII` and `domainToUnicode`\nkj::Maybe<std::string> GetHostName(kj::StringPtr domain) {\n  if (domain.size() == 0) {\n    return kj::none;\n  }\n\n  // It is important to have an initial value that contains a special scheme.\n  // Since it will change the implementation of `set_hostname` according to URL\n  // spec.\n  auto out = ada::parse<ada::url>(\"ws://x\");\n  JSG_REQUIRE(out.has_value(), Error, \"URL parsing failed\"_kj);\n  if (!out->set_hostname({domain.begin(), domain.size()})) {\n    return kj::none;\n  }\n  return out->get_hostname();\n}\n\n}  // namespace\n\njsg::JsString UrlUtil::domainToASCII(jsg::Lock& js, kj::String domain) {\n  KJ_IF_SOME(hostname, GetHostName(domain)) {\n    return js.str(kj::StringPtr(hostname.data(), hostname.size()));\n  }\n  return js.str(\"\"_kj);\n}\n\njsg::JsString UrlUtil::domainToUnicode(jsg::Lock& js, kj::String domain) {\n  KJ_IF_SOME(hostname, GetHostName(domain)) {\n    auto result = ada::idna::to_unicode(hostname);\n    return js.str(kj::StringPtr(result.data(), result.size()));\n  }\n\n  return js.str(\"\"_kj);\n}\n\njsg::JsString UrlUtil::toASCII(jsg::Lock& js, kj::String url) {\n  auto out = ada::idna::to_ascii({url.begin(), url.size()});\n  return js.str(kj::StringPtr(out.data(), out.size()));\n}\n\njsg::JsString UrlUtil::format(\n    jsg::Lock& js, kj::String input, bool hash, bool unicode, bool search, bool auth) {\n  // We deliberately use `ada::url` rather than `ada::url_aggregator` because\n  // ada::url is faster on setting fields. Since we don't need individual components\n  // there is no need to use url_aggregator.\n  auto out = ada::parse<ada::url>({input.begin(), input.size()}, nullptr);\n\n  JSG_REQUIRE(out.has_value(), Error, \"Failed to parse URL\"_kj);\n\n  if (!hash) {\n    out->hash = std::nullopt;\n  }\n\n  if (unicode && out->has_hostname()) {\n    out->host = ada::idna::to_unicode(out->get_hostname());\n  }\n\n  if (!search) {\n    out->query = std::nullopt;\n  }\n\n  if (!auth) {\n    out->username = \"\";\n    out->password = \"\";\n  }\n\n  auto href = out->get_href();\n  return js.str(kj::StringPtr(href.data(), href.size()));\n}\n\n// We return empty string if the input is not a valid IP address.\njsg::JsString UrlUtil::canonicalizeIp(jsg::Lock& js, kj::String input) {\n  auto out = rust::net::canonicalize_ip({input.begin(), input.size()});\n  return js.str(kj::StringPtr(out.c_str(), out.size()));\n}\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/url.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <workerd/jsg/jsg.h>\n\n#include <kj/string.h>\n\nnamespace workerd::api::node {\n\nclass UrlUtil final: public jsg::Object {\n public:\n  UrlUtil() = default;\n  UrlUtil(jsg::Lock&, const jsg::Url&) {}\n\n  jsg::JsString domainToUnicode(jsg::Lock& js, kj::String domain);\n  jsg::JsString domainToASCII(jsg::Lock& js, kj::String domain);\n  jsg::JsString format(\n      jsg::Lock& js, kj::String href, bool hash, bool unicode, bool search, bool auth);\n  jsg::JsString toASCII(jsg::Lock& js, kj::String url);\n  jsg::JsString canonicalizeIp(jsg::Lock& js, kj::String input);\n\n  JSG_RESOURCE_TYPE(UrlUtil) {\n    JSG_METHOD(domainToUnicode);\n    JSG_METHOD(domainToASCII);\n    JSG_METHOD(canonicalizeIp);\n\n    // Legacy APIs\n    JSG_METHOD(format);\n    JSG_METHOD(toASCII);\n  }\n};\n\n#define EW_NODE_URL_ISOLATE_TYPES api::node::UrlUtil\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/util.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#include \"util.h\"\n\n#include <workerd/io/features.h>\n#include <workerd/io/io-context.h>\n#include <workerd/io/tracer.h>\n#include <workerd/jsg/jsg.h>\n\n#include <kj/vector.h>\n\nnamespace workerd::api::node {\n\nMIMEParams::MIMEParams(kj::Maybe<MimeType&> mimeType): mimeType(mimeType) {}\n\n// Oddly, Node.js allows creating MIMEParams directly but it's not actually\n// functional. But, to match, we'll go ahead and allow it.\njsg::Ref<MIMEParams> MIMEParams::constructor(jsg::Lock& js) {\n  return js.alloc<MIMEParams>(kj::none);\n}\n\nvoid MIMEParams::delete_(kj::String name) {\n  KJ_IF_SOME(inner, mimeType) {\n    inner.eraseParam(name);\n  }\n}\n\nkj::Maybe<kj::StringPtr> MIMEParams::get(kj::String name) {\n  KJ_IF_SOME(inner, mimeType) {\n    return inner.params().find(name);\n  }\n  return kj::none;\n}\n\nbool MIMEParams::has(kj::String name) {\n  KJ_IF_SOME(inner, mimeType) {\n    return inner.params().find(name) != kj::none;\n  }\n  return false;\n}\n\nvoid MIMEParams::set(kj::String name, kj::String value) {\n  KJ_IF_SOME(inner, mimeType) {\n    JSG_REQUIRE(inner.addParam(name, value), TypeError, \"Not a valid MIME parameter\");\n  }\n}\n\nkj::String MIMEParams::toString() {\n  KJ_IF_SOME(inner, mimeType) {\n    return inner.paramsToString();\n  }\n  return kj::String();\n}\n\njsg::Ref<MIMEParams::EntryIterator> MIMEParams::entries(jsg::Lock& js) {\n  kj::Vector<kj::Array<kj::String>> vec;\n  KJ_IF_SOME(inner, mimeType) {\n    for (const auto& entry: inner.params()) {\n      vec.add(kj::arr(kj::str(entry.key), kj::str(entry.value)));\n    }\n  }\n  return js.alloc<EntryIterator>(IteratorState<kj::Array<kj::String>>{vec.releaseAsArray()});\n}\n\njsg::Ref<MIMEParams::KeyIterator> MIMEParams::keys(jsg::Lock& js) {\n  kj::Vector<kj::String> vec;\n  KJ_IF_SOME(inner, mimeType) {\n    for (const auto& entry: inner.params()) {\n      vec.add(kj::str(entry.key));\n    }\n  }\n  return js.alloc<KeyIterator>(IteratorState<kj::String>{vec.releaseAsArray()});\n}\n\njsg::Ref<MIMEParams::ValueIterator> MIMEParams::values(jsg::Lock& js) {\n  kj::Vector<kj::String> vec;\n  KJ_IF_SOME(inner, mimeType) {\n    for (const auto& entry: inner.params()) {\n      vec.add(kj::str(entry.value));\n    }\n  }\n  return js.alloc<ValueIterator>(IteratorState<kj::String>{vec.releaseAsArray()});\n}\n\nMIMEType::MIMEType(jsg::Lock& js, MimeType inner)\n    : inner(kj::mv(inner)),\n      params(js.alloc<MIMEParams>(this->inner)) {}\n\nMIMEType::~MIMEType() noexcept(false) {\n  // Break the connection with the MIMEParams\n  params->mimeType = kj::none;\n}\n\njsg::Ref<MIMEType> MIMEType::constructor(jsg::Lock& js, kj::String input) {\n  auto parsed =\n      JSG_REQUIRE_NONNULL(MimeType::tryParse(input), TypeError, \"Not a valid MIME type: \", input);\n  return js.alloc<MIMEType>(js, kj::mv(parsed));\n}\n\nkj::StringPtr MIMEType::getType() {\n  return inner.type();\n}\n\nvoid MIMEType::setType(kj::String type) {\n  JSG_REQUIRE(inner.setType(type), TypeError, \"Not a valid MIME type\");\n}\n\nkj::StringPtr MIMEType::getSubtype() {\n  return inner.subtype();\n}\n\nvoid MIMEType::setSubtype(kj::String subtype) {\n  JSG_REQUIRE(inner.setSubtype(subtype), TypeError, \"Not a valid MIME subtype\");\n}\n\nkj::String MIMEType::getEssence() {\n  return inner.essence();\n}\n\njsg::Ref<MIMEParams> MIMEType::getParams() {\n  return params.addRef();\n}\n\nkj::String MIMEType::toString() {\n  return inner.toString();\n}\n\njsg::JsArray UtilModule::getOwnNonIndexProperties(jsg::Lock& js, jsg::JsObject value, int filter) {\n  auto propertyFilter = static_cast<jsg::PropertyFilter>(filter);\n  return value.getPropertyNames(\n      js, jsg::KeyCollectionFilter::OWN_ONLY, propertyFilter, jsg::IndexFilter::SKIP_INDICES);\n}\n\njsg::Optional<UtilModule::PromiseDetails> UtilModule::getPromiseDetails(\n    jsg::Lock& js, jsg::JsValue value) {\n  auto promise = KJ_UNWRAP_OR_RETURN(value.tryCast<jsg::JsPromise>(), kj::none);\n  auto state = promise.state();\n  if (state != jsg::PromiseState::PENDING) {\n    auto result = promise.result();\n    return PromiseDetails{\n      .state = state,\n      .result = jsg::JsRef(js, result),\n    };\n  } else {\n    return PromiseDetails{\n      .state = state,\n      .result = kj::none,\n    };\n  }\n}\n\njsg::Optional<UtilModule::ProxyDetails> UtilModule::getProxyDetails(\n    jsg::Lock& js, jsg::JsValue value) {\n  auto proxy = KJ_UNWRAP_OR_RETURN(value.tryCast<jsg::JsProxy>(), kj::none);\n  auto target = proxy.target();\n  auto handler = proxy.handler();\n  return ProxyDetails{\n    .target = jsg::JsRef(js, target),\n    .handler = jsg::JsRef(js, handler),\n  };\n}\n\njsg::Optional<UtilModule::PreviewedEntries> UtilModule::previewEntries(\n    jsg::Lock& js, jsg::JsValue value) {\n  auto object = KJ_UNWRAP_OR_RETURN(value.tryCast<jsg::JsObject>(), kj::none);\n  bool isKeyValue;\n  auto entries = object.previewEntries(&isKeyValue);\n  return PreviewedEntries{\n    .entries = jsg::JsRef(js, entries),\n    .isKeyValue = isKeyValue,\n  };\n}\n\njsg::JsString UtilModule::getConstructorName(jsg::Lock& js, jsg::JsObject value) {\n  return js.str(value.getConstructorName());\n}\n\n#define V(Type)                                                                                    \\\n  bool UtilModule::is##Type(jsg::JsValue value) {                                                  \\\n    return value.is##Type();                                                                       \\\n  };\nJS_UTIL_IS_TYPES(V)\n#undef V\nbool UtilModule::isAnyArrayBuffer(jsg::JsValue value) {\n  return value.isArrayBuffer() || value.isSharedArrayBuffer();\n}\nbool UtilModule::isBoxedPrimitive(jsg::JsValue value) {\n  return value.isNumberObject() || value.isStringObject() || value.isBooleanObject() ||\n      value.isBigIntObject() || value.isSymbolObject();\n}\n\njsg::Name UtilModule::getResourceTypeInspect(jsg::Lock& js) {\n  return js.newApiSymbol(\"kResourceTypeInspect\"_kj);\n}\n\nkj::Array<UtilModule::CallSiteEntry> UtilModule::getCallSites(\n    jsg::Lock& js, jsg::Optional<int> frames) {\n  KJ_IF_SOME(f, frames) {\n    JSG_REQUIRE(f >= 1 && f <= 200, Error, \"Frame count should be between 1 and 200 inclusive.\"_kj);\n  }\n\n  auto stack = v8::StackTrace::CurrentStackTrace(js.v8Isolate, frames.orDefault(10) + 1);\n  const int frameCount = stack->GetFrameCount();\n  auto objects = kj::Vector<CallSiteEntry>();\n  objects.reserve(frameCount - 1);\n\n  for (int i = 0; i < frameCount; ++i) {\n    auto stack_frame = stack->GetFrame(js.v8Isolate, i);\n\n    auto function_name = stack_frame->GetFunctionName();\n    auto script_name = stack_frame->GetScriptName();\n\n    if (!function_name.IsEmpty() && !script_name.IsEmpty()) {\n\n      objects.add(CallSiteEntry{\n        .functionName = js.toString(function_name),\n        .scriptName = js.toString(script_name),\n        .lineNumber = stack_frame->GetLineNumber(),\n        // Node.js originally implemented the experimental API using the \"column\" field\n        // then later renamed it to columnNumber. We had already implemented the API\n        // using column. To ensure backwards compat without the complexity of a compat\n        // flag, we just export both.\n        .columnNumber = stack_frame->GetColumn(),\n        .column = stack_frame->GetColumn(),\n      });\n    }\n  }\n\n  return objects.releaseAsArray();\n}\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/util.h",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <workerd/api/performance.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/util/mimetype.h>\n\nnamespace workerd::api::node {\n\n// Originally implemented by Node.js contributors.\n// Available at https://github.com/nodejs/node/blob/b7b96282b212a2274b9db605ac29d388246754de/src/node_buffer.h#L75\n// This is verbose to be explicit with inline commenting\nstatic constexpr bool IsWithinBounds(size_t off, size_t len, size_t max) noexcept {\n  // Asking to seek too far into the buffer\n  // check to avoid wrapping in subsequent subtraction\n  if (off > max) return false;\n\n  // Asking for more than is left over in the buffer\n  if (max - off < len) return false;\n\n  // Otherwise we're in bounds\n  return true;\n}\n\nclass MIMEType;\n\nclass MIMEParams final: public jsg::Object {\n private:\n  template <typename T>\n  struct IteratorState final {\n    kj::Array<T> values;\n    uint index = 0;\n  };\n\n public:\n  MIMEParams(kj::Maybe<MimeType&> mimeType = kj::none);\n\n  static jsg::Ref<MIMEParams> constructor(jsg::Lock& js);\n\n  void delete_(kj::String name);\n  kj::Maybe<kj::StringPtr> get(kj::String name);\n  bool has(kj::String name);\n  void set(kj::String name, kj::String value);\n  kj::String toString();\n\n  JSG_ITERATOR(EntryIterator,\n      entries,\n      kj::Array<kj::String>,\n      IteratorState<kj::Array<kj::String>>,\n      iteratorNext<kj::Array<kj::String>>);\n  JSG_ITERATOR(KeyIterator, keys, kj::String, IteratorState<kj::String>, iteratorNext<kj::String>);\n  JSG_ITERATOR(\n      ValueIterator, values, kj::String, IteratorState<kj::String>, iteratorNext<kj::String>);\n\n  JSG_RESOURCE_TYPE(MIMEParams) {\n    JSG_METHOD_NAMED(delete, delete_);\n    JSG_METHOD(get);\n    JSG_METHOD(has);\n    JSG_METHOD(set);\n    JSG_METHOD(entries);\n    JSG_METHOD(keys);\n    JSG_METHOD(values);\n    JSG_METHOD(toString);\n    JSG_METHOD_NAMED(toJSON, toString);\n    JSG_ITERABLE(entries);\n  }\n\n private:\n  template <typename T>\n  static kj::Maybe<T> iteratorNext(jsg::Lock& js, IteratorState<T>& state) {\n    if (state.index >= state.values.size()) {\n      return kj::none;\n    }\n    auto& item = state.values[state.index++];\n    if constexpr (kj::isSameType<T, kj::Array<kj::String>>()) {\n      return KJ_MAP(i, item) { return kj::str(i); };\n    } else {\n      static_assert(kj::isSameType<T, kj::String>());\n      return kj::str(item);\n    }\n    KJ_UNREACHABLE;\n  }\n\n  kj::Maybe<MimeType&> mimeType;\n  friend class MIMEType;\n};\n\nclass MIMEType final: public jsg::Object {\n public:\n  explicit MIMEType(jsg::Lock& js, MimeType inner);\n  ~MIMEType() noexcept(false);\n  static jsg::Ref<MIMEType> constructor(jsg::Lock& js, kj::String input);\n\n  kj::StringPtr getType();\n  void setType(kj::String type);\n  kj::StringPtr getSubtype();\n  void setSubtype(kj::String subtype);\n  kj::String getEssence();\n  jsg::Ref<MIMEParams> getParams();\n  kj::String toString();\n\n  JSG_RESOURCE_TYPE(MIMEType) {\n    JSG_PROTOTYPE_PROPERTY(type, getType, setType);\n    JSG_PROTOTYPE_PROPERTY(subtype, getSubtype, setSubtype);\n    JSG_READONLY_PROTOTYPE_PROPERTY(essence, getEssence);\n    JSG_READONLY_PROTOTYPE_PROPERTY(params, getParams);\n    JSG_METHOD(toString);\n    JSG_METHOD_NAMED(toJSON, toString);\n  }\n\n private:\n  workerd::MimeType inner;\n  jsg::Ref<MIMEParams> params;\n};\n\n#define JS_UTIL_IS_TYPES(V)                                                                        \\\n  V(ArrayBufferView)                                                                               \\\n  V(ArgumentsObject)                                                                               \\\n  V(ArrayBuffer)                                                                                   \\\n  V(AsyncFunction)                                                                                 \\\n  V(BigInt64Array)                                                                                 \\\n  V(BigIntObject)                                                                                  \\\n  V(BigUint64Array)                                                                                \\\n  V(BooleanObject)                                                                                 \\\n  V(DataView)                                                                                      \\\n  V(Date)                                                                                          \\\n  V(External)                                                                                      \\\n  V(Float16Array)                                                                                  \\\n  V(Float32Array)                                                                                  \\\n  V(Float64Array)                                                                                  \\\n  V(GeneratorFunction)                                                                             \\\n  V(GeneratorObject)                                                                               \\\n  V(Int8Array)                                                                                     \\\n  V(Int16Array)                                                                                    \\\n  V(Int32Array)                                                                                    \\\n  V(Map)                                                                                           \\\n  V(MapIterator)                                                                                   \\\n  V(ModuleNamespaceObject)                                                                         \\\n  V(NativeError)                                                                                   \\\n  V(NumberObject)                                                                                  \\\n  V(Promise)                                                                                       \\\n  V(Proxy)                                                                                         \\\n  V(RegExp)                                                                                        \\\n  V(Set)                                                                                           \\\n  V(SetIterator)                                                                                   \\\n  V(SharedArrayBuffer)                                                                             \\\n  V(StringObject)                                                                                  \\\n  V(SymbolObject)                                                                                  \\\n  V(TypedArray)                                                                                    \\\n  V(Uint8Array)                                                                                    \\\n  V(Uint8ClampedArray)                                                                             \\\n  V(Uint16Array)                                                                                   \\\n  V(Uint32Array)                                                                                   \\\n  V(WeakMap)                                                                                       \\\n  V(WeakSet)\n\nclass UtilModule final: public jsg::Object {\n public:\n  UtilModule() = default;\n  UtilModule(jsg::Lock&, const jsg::Url&) {}\n\n  jsg::Name getResourceTypeInspect(jsg::Lock& js);\n\n  // `getOwnNonIndexProperties()` `filter`s\n  static constexpr int ALL_PROPERTIES = jsg::PropertyFilter::ALL_PROPERTIES;\n  static constexpr int ONLY_ENUMERABLE = jsg::PropertyFilter::ONLY_ENUMERABLE;\n\n  jsg::JsArray getOwnNonIndexProperties(jsg::Lock& js, jsg::JsObject value, int filter);\n\n  // `PromiseDetails` `state`s\n  static constexpr int kPending = jsg::PromiseState::PENDING;\n  static constexpr int kFulfilled = jsg::PromiseState::FULFILLED;\n  static constexpr int kRejected = jsg::PromiseState::REJECTED;\n\n  struct PromiseDetails {\n    int state;  // TODO: can we make this a `jsg::PromiseState`\n    jsg::Optional<jsg::JsRef<jsg::JsValue>> result;\n\n    JSG_STRUCT(state, result);\n  };\n  jsg::Optional<PromiseDetails> getPromiseDetails(jsg::Lock& js, jsg::JsValue value);\n\n  struct ProxyDetails {\n    jsg::JsRef<jsg::JsValue> target;\n    jsg::JsRef<jsg::JsValue> handler;\n\n    JSG_STRUCT(target, handler);\n  };\n  jsg::Optional<ProxyDetails> getProxyDetails(jsg::Lock& js, jsg::JsValue value);\n\n  struct PreviewedEntries {\n    jsg::JsRef<jsg::JsArray> entries;\n    bool isKeyValue;\n\n    JSG_STRUCT(entries, isKeyValue);\n  };\n  jsg::Optional<PreviewedEntries> previewEntries(jsg::Lock& js, jsg::JsValue value);\n\n  jsg::JsString getConstructorName(jsg::Lock& js, jsg::JsObject value);\n\n  struct CallSiteEntry {\n    kj::String functionName;\n    kj::String scriptName;\n    int lineNumber;\n    // Node.js originally introduced the API with the name `getCallSite()` as an experimental\n    // API but then renamed it to `getCallSites()` soon after. We had already implemented the\n    // API with the original name in a release. To avoid the possibility of breaking, we export\n    // the function using both names.\n    int columnNumber;\n    int column;\n\n    JSG_STRUCT(functionName, scriptName, lineNumber, columnNumber, column);\n  };\n  kj::Array<CallSiteEntry> getCallSites(jsg::Lock& js, jsg::Optional<int> frames);\n\n#define V(Type) bool is##Type(jsg::JsValue value);\n  JS_UTIL_IS_TYPES(V)\n#undef V\n  bool isAnyArrayBuffer(jsg::JsValue value);\n  bool isBoxedPrimitive(jsg::JsValue value);\n\n  JSG_RESOURCE_TYPE(UtilModule) {\n    JSG_NESTED_TYPE(MIMEType);\n    JSG_NESTED_TYPE(MIMEParams);\n\n    JSG_READONLY_INSTANCE_PROPERTY(kResourceTypeInspect, getResourceTypeInspect);\n\n    JSG_STATIC_CONSTANT(ALL_PROPERTIES);\n    JSG_STATIC_CONSTANT(ONLY_ENUMERABLE);\n    JSG_METHOD(getOwnNonIndexProperties);\n\n    JSG_STATIC_CONSTANT(kPending);\n    JSG_STATIC_CONSTANT(kFulfilled);\n    JSG_STATIC_CONSTANT(kRejected);\n    JSG_METHOD(getPromiseDetails);\n\n    JSG_METHOD(getProxyDetails);\n    JSG_METHOD(previewEntries);\n    JSG_METHOD(getConstructorName);\n    JSG_METHOD(getCallSites);\n\n    JSG_NESTED_TYPE(Performance);\n    JSG_NESTED_TYPE(PerformanceEntry);\n    JSG_NESTED_TYPE(PerformanceMeasure);\n    JSG_NESTED_TYPE(PerformanceMark);\n    JSG_NESTED_TYPE(PerformanceObserver);\n    JSG_NESTED_TYPE(PerformanceObserverEntryList);\n    JSG_NESTED_TYPE(PerformanceResourceTiming);\n\n#define V(Type) JSG_METHOD(is##Type);\n    JS_UTIL_IS_TYPES(V)\n#undef V\n    JSG_METHOD(isAnyArrayBuffer);\n    JSG_METHOD(isBoxedPrimitive);\n  }\n};\n\n#define EW_NODE_UTIL_ISOLATE_TYPES                                                                 \\\n  api::node::UtilModule, api::node::UtilModule::PromiseDetails,                                    \\\n      api::node::UtilModule::ProxyDetails, api::node::UtilModule::PreviewedEntries,                \\\n      api::node::MIMEType, api::node::MIMEParams, api::node::MIMEParams::EntryIterator,            \\\n      api::node::MIMEParams::ValueIterator, api::node::MIMEParams::KeyIterator,                    \\\n      api::node::MIMEParams::EntryIterator::Next, api::node::MIMEParams::ValueIterator::Next,      \\\n      api::node::MIMEParams::KeyIterator::Next, api::node::UtilModule::CallSiteEntry\n\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/zlib-util.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n\n#include \"zlib-util.h\"\n\n#include \"util.h\"\n\n// The following implementation is adapted from Node.js\n// and therefore follows Node.js style as opposed to kj style.\n// Latest implementation of Node.js zlib can be found at:\n// https://github.com/nodejs/node/blob/main/src/node_zlib.cc\nnamespace workerd::api::node {\n\nkj::ArrayPtr<const kj::byte> getInputFromSource(const ZlibUtil::InputSource& data) {\n  KJ_SWITCH_ONEOF(data) {\n    KJ_CASE_ONEOF(dataBuf, kj::Array<kj::byte>) {\n      JSG_REQUIRE(dataBuf.size() < Z_MAX_CHUNK, RangeError, \"Memory limit exceeded\"_kj);\n      return dataBuf.asPtr();\n    }\n\n    KJ_CASE_ONEOF(dataStr, jsg::NonCoercible<kj::String>) {\n      JSG_REQUIRE(dataStr.value.size() < Z_MAX_CHUNK, RangeError, \"Memory limit exceeded\"_kj);\n      return dataStr.value.asBytes();\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\nuint32_t ZlibUtil::crc32Sync(InputSource data, uint32_t value) {\n  auto dataPtr = getInputFromSource(data);\n  return crc32(value, dataPtr.begin(), dataPtr.size());\n}\n\nnamespace {\nclass GrowableBuffer final {\n  // A copy of kj::Vector with some additional methods for use as a growable buffer with a maximum\n  // size\n public:\n  inline explicit GrowableBuffer(size_t _chunkSize, size_t _maxCapacity)\n      : maxCapacity(_maxCapacity) {\n    auto maxChunkSize = kj::min(_chunkSize, maxCapacity);\n    builder = kj::heapArrayBuilder<kj::byte>(maxChunkSize);\n    chunkSize = maxChunkSize;\n  }\n\n  size_t size() const {\n    return builder.size();\n  }\n  bool empty() const {\n    return size() == 0;\n  }\n  size_t capacity() const {\n    return builder.capacity();\n  }\n  size_t available() const {\n    return capacity() - size();\n  }\n\n  kj::byte* begin() KJ_LIFETIMEBOUND {\n    return builder.begin();\n  }\n  kj::byte* end() KJ_LIFETIMEBOUND {\n    return builder.end();\n  }\n\n  kj::Array<kj::byte> releaseAsArray() {\n    // TODO(perf):  Avoid a copy/move by allowing Array<T> to point to incomplete space?\n    if (!builder.isFull()) {\n      setCapacity(size());\n    }\n    return builder.finish();\n  }\n\n  void adjustUnused(size_t unused) {\n    resize(capacity() - unused);\n  }\n\n  void resize(size_t size) {\n    if (size > builder.capacity()) grow(size);\n    builder.resize(size);\n  }\n\n  void addChunk() {\n    reserve(size() + chunkSize);\n  }\n\n  void reserve(size_t size) {\n    if (size > builder.capacity()) {\n      grow(size);\n    }\n  }\n\n  bool atMaxCapacity() const {\n    return size() >= maxCapacity;\n  }\n\n private:\n  kj::ArrayBuilder<kj::byte> builder;\n  size_t chunkSize;\n  size_t maxCapacity;\n\n  void grow(size_t minCapacity = 0) {\n    JSG_REQUIRE(minCapacity <= maxCapacity, RangeError, \"Memory limit exceeded\");\n    setCapacity(kj::min(maxCapacity, kj::max(minCapacity, capacity() == 0 ? 4 : capacity() * 2)));\n  }\n  void setCapacity(size_t newSize) {\n    if (builder.size() > newSize) {\n      builder.truncate(newSize);\n    }\n\n    kj::ArrayBuilder<kj::byte> newBuilder = kj::heapArrayBuilder<kj::byte>(newSize);\n    newBuilder.addAll(kj::mv(builder));\n    builder = kj::mv(newBuilder);\n  }\n};\n}  // namespace\n\nvoid ZlibContext::initialize(int _level,\n    int _windowBits,\n    int _memLevel,\n    int _strategy,\n    jsg::Optional<kj::Array<kj::byte>> _dictionary) {\n  if (!((_windowBits == 0) &&\n          (mode == ZlibMode::INFLATE || mode == ZlibMode::GUNZIP || mode == ZlibMode::UNZIP))) {\n    JSG_ASSERT(_windowBits >= Z_MIN_WINDOWBITS && _windowBits <= Z_MAX_WINDOWBITS, RangeError,\n        kj::str(\"The value of \\\"options.windowBits\\\" is out of range. It must be >= \",\n            Z_MIN_WINDOWBITS, \" and <= \", Z_MAX_WINDOWBITS, \". Received \", _windowBits));\n  }\n\n  JSG_REQUIRE(_level >= Z_MIN_LEVEL && _level <= Z_MAX_LEVEL, RangeError,\n      kj::str(\"The value of \\\"options.level\\\" is out of range. It must be >= \", Z_MIN_LEVEL,\n          \" and <= \", Z_MAX_LEVEL, \". Received \", _level));\n  JSG_REQUIRE(_memLevel >= Z_MIN_MEMLEVEL && _memLevel <= Z_MAX_MEMLEVEL, RangeError,\n      kj::str(\"The value of \\\"options.memLevel\\\" is out of range. It must be >= \", Z_MIN_MEMLEVEL,\n          \" and <= \", Z_MAX_MEMLEVEL, \". Received \", _memLevel));\n  JSG_REQUIRE(_strategy == Z_FILTERED || _strategy == Z_HUFFMAN_ONLY || _strategy == Z_RLE ||\n          _strategy == Z_FIXED || _strategy == Z_DEFAULT_STRATEGY,\n      Error, \"invalid strategy\"_kj);\n\n  level = _level;\n  windowBits = _windowBits;\n  memLevel = _memLevel;\n  strategy = _strategy;\n  flush = Z_NO_FLUSH;\n  err = Z_OK;\n\n  switch (mode) {\n    case ZlibMode::GZIP:\n    case ZlibMode::GUNZIP:\n      windowBits += 16;\n      break;\n    case ZlibMode::UNZIP:\n      windowBits += 32;\n      break;\n    case ZlibMode::DEFLATERAW:\n    case ZlibMode::INFLATERAW:\n      windowBits *= -1;\n      break;\n    default:\n      break;\n  }\n\n  KJ_IF_SOME(dict, _dictionary) {\n    dictionary = kj::mv(dict);\n  }\n}\n\nkj::Maybe<CompressionError> ZlibContext::getError() const {\n  // Acceptable error states depend on the type of zlib stream.\n  switch (err) {\n    case Z_OK:\n    case Z_BUF_ERROR:\n      if (stream.avail_out != 0 && flush == Z_FINISH) {\n        return constructError(\"unexpected end of file\"_kj);\n      }\n      break;\n    case Z_STREAM_END:\n      // normal statuses, not fatal\n      break;\n    case Z_NEED_DICT:\n      if (dictionary.empty()) {\n        return constructError(\"Missing dictionary\"_kj);\n      } else {\n        return constructError(\"Bad dictionary\"_kj);\n      }\n    default:\n      // something else.\n      return constructError(\"Zlib error\");\n  }\n\n  return {};\n}\n\nkj::Maybe<CompressionError> ZlibContext::setDictionary() {\n  if (dictionary.empty()) {\n    return kj::none;\n  }\n\n  err = Z_OK;\n\n  switch (mode) {\n    case ZlibMode::DEFLATE:\n    case ZlibMode::DEFLATERAW:\n      err = deflateSetDictionary(&stream, dictionary.begin(), dictionary.size());\n      break;\n    case ZlibMode::INFLATERAW:\n      err = inflateSetDictionary(&stream, dictionary.begin(), dictionary.size());\n      break;\n    default:\n      break;\n  }\n\n  if (err != Z_OK) {\n    return constructError(\"Failed to set dictionary\"_kj);\n  }\n\n  return kj::none;\n}\n\nbool ZlibContext::initializeZlib() {\n  if (initialized) {\n    return false;\n  }\n  switch (mode) {\n    case ZlibMode::DEFLATE:\n    case ZlibMode::GZIP:\n    case ZlibMode::DEFLATERAW:\n      err = deflateInit2(&stream, level, Z_DEFLATED, windowBits, memLevel, strategy);\n      break;\n    case ZlibMode::INFLATE:\n    case ZlibMode::GUNZIP:\n    case ZlibMode::INFLATERAW:\n    case ZlibMode::UNZIP:\n      err = inflateInit2(&stream, windowBits);\n      break;\n    default:\n      KJ_UNREACHABLE;\n  }\n\n  if (err != Z_OK) {\n    dictionary.clear();\n    mode = ZlibMode::NONE;\n    return true;\n  }\n\n  setDictionary();\n  initialized = true;\n  return true;\n}\n\nkj::Maybe<CompressionError> ZlibContext::resetStream() {\n  bool initialized_now = initializeZlib();\n  if (initialized_now && err != Z_OK) {\n    return constructError(\"Failed to init stream before reset\");\n  }\n  err = Z_OK;\n  switch (mode) {\n    case ZlibMode::DEFLATE:\n    case ZlibMode::DEFLATERAW:\n    case ZlibMode::GZIP:\n      err = deflateReset(&stream);\n      break;\n    case ZlibMode::INFLATE:\n    case ZlibMode::INFLATERAW:\n    case ZlibMode::GUNZIP:\n      err = inflateReset(&stream);\n      break;\n    default:\n      break;\n  }\n\n  if (err != Z_OK) {\n    return constructError(\"Failed to reset stream\"_kj);\n  }\n\n  return setDictionary();\n}\n\nvoid ZlibContext::work() {\n  bool initialized_now = initializeZlib();\n  if (initialized_now && err != Z_OK) {\n    return;\n  }\n\n  const Bytef* next_expected_header_byte = nullptr;\n\n  // If the avail_out is left at 0, then it means that it ran out\n  // of room.  If there was avail_out left over, then it means\n  // that all the input was consumed.\n  switch (mode) {\n    case ZlibMode::DEFLATE:\n    case ZlibMode::GZIP:\n    case ZlibMode::DEFLATERAW:\n      err = deflate(&stream, flush);\n      break;\n    case ZlibMode::UNZIP:\n      if (stream.avail_in > 0) {\n        next_expected_header_byte = stream.next_in;\n      }\n\n      switch (gzip_id_bytes_read) {\n        case 0:\n          if (next_expected_header_byte == nullptr) {\n            break;\n          }\n\n          if (*next_expected_header_byte == GZIP_HEADER_ID1) {\n            gzip_id_bytes_read = 1;\n            next_expected_header_byte++;\n\n            if (stream.avail_in == 1) {\n              // The only available byte was already read.\n              break;\n            }\n          } else {\n            mode = ZlibMode::INFLATE;\n            break;\n          }\n\n          [[fallthrough]];\n        case 1:\n          if (next_expected_header_byte == nullptr) {\n            break;\n          }\n\n          if (*next_expected_header_byte == GZIP_HEADER_ID2) {\n            gzip_id_bytes_read = 2;\n            mode = ZlibMode::GUNZIP;\n          } else {\n            // There is no actual difference between INFLATE and INFLATERAW\n            // (after initialization).\n            mode = ZlibMode::INFLATE;\n          }\n\n          break;\n        default:\n          JSG_FAIL_REQUIRE(Error, \"Invalid number of gzip magic number bytes read\");\n      }\n\n      [[fallthrough]];\n    case ZlibMode::INFLATE:\n    case ZlibMode::GUNZIP:\n    case ZlibMode::INFLATERAW:\n      err = inflate(&stream, flush);\n\n      // If data was encoded with dictionary (INFLATERAW will have it set in\n      // SetDictionary, don't repeat that here)\n      if (mode != ZlibMode::INFLATERAW && err == Z_NEED_DICT && !dictionary.empty()) {\n        // Load it\n        err = inflateSetDictionary(&stream, dictionary.begin(), dictionary.size());\n        if (err == Z_OK) {\n          // And try to decode again\n          err = inflate(&stream, flush);\n        } else if (err == Z_DATA_ERROR) {\n          // Both inflateSetDictionary() and inflate() return Z_DATA_ERROR.\n          // Make it possible for After() to tell a bad dictionary from bad\n          // input.\n          err = Z_NEED_DICT;\n        }\n      }\n\n      while (stream.avail_in > 0 && mode == ZlibMode::GUNZIP && err == Z_STREAM_END &&\n          stream.next_in[0] != 0x00) {\n        // Bytes remain in input buffer. Perhaps this is another compressed\n        // member in the same archive, or just trailing garbage.\n        // Trailing zero bytes are okay, though, since they are frequently\n        // used for padding.\n\n        resetStream();\n        err = inflate(&stream, flush);\n      }\n      break;\n    default:\n      KJ_UNREACHABLE;\n  }\n}\n\nkj::Maybe<CompressionError> ZlibContext::setParams(int _level, int _strategy) {\n  bool initialized_now = initializeZlib();\n  if (initialized_now && err != Z_OK) {\n    return constructError(\"Failed to init stream before set parameters\");\n  }\n  err = Z_OK;\n\n  switch (mode) {\n    case ZlibMode::DEFLATE:\n    case ZlibMode::DEFLATERAW:\n      err = deflateParams(&stream, _level, _strategy);\n      break;\n    default:\n      break;\n  }\n\n  if (err != Z_OK && err != Z_BUF_ERROR) {\n    return constructError(\"Failed to set parameters\");\n  }\n\n  return kj::none;\n}\n\nZlibContext::~ZlibContext() noexcept(false) {\n  if (!initialized) {\n    return;\n  }\n\n  auto status = Z_OK;\n  switch (mode) {\n    case ZlibMode::DEFLATE:\n    case ZlibMode::DEFLATERAW:\n    case ZlibMode::GZIP:\n      status = deflateEnd(&stream);\n      break;\n    case ZlibMode::INFLATE:\n    case ZlibMode::INFLATERAW:\n    case ZlibMode::GUNZIP:\n    case ZlibMode::UNZIP:\n      status = inflateEnd(&stream);\n      break;\n    default:\n      break;\n  }\n\n  JSG_REQUIRE(\n      status == Z_OK || status == Z_DATA_ERROR, Error, \"Uncaught error on closing zlib stream\");\n}\n\nvoid ZlibContext::setBuffers(kj::ArrayPtr<kj::byte> input, kj::ArrayPtr<kj::byte> output) {\n  stream.avail_in = input.size();\n  stream.next_in = input.begin();\n  stream.avail_out = output.size();\n  stream.next_out = output.begin();\n}\n\nvoid ZlibContext::setInputBuffer(kj::ArrayPtr<const kj::byte> input) {\n  // The define Z_CONST is not set, so zlib always takes mutable pointers\n  stream.next_in = const_cast<kj::byte*>(input.begin());\n  stream.avail_in = input.size();\n}\n\nvoid ZlibContext::setOutputBuffer(kj::ArrayPtr<kj::byte> output) {\n  stream.next_out = output.begin();\n  stream.avail_out = output.size();\n}\n\ntemplate <typename CompressionContext>\njsg::Ref<ZlibUtil::CompressionStream<CompressionContext>> ZlibUtil::CompressionStream<\n    CompressionContext>::constructor(jsg::Lock& js, ZlibModeValue mode) {\n  return js.alloc<CompressionStream>(static_cast<ZlibMode>(mode), js.getExternalMemoryTarget());\n}\n\ntemplate <typename CompressionContext>\nZlibUtil::CompressionStream<CompressionContext>::~CompressionStream() {\n  JSG_ASSERT(!writing, Error, \"Writing to compression stream\"_kj);\n  close();\n}\n\ntemplate <typename CompressionContext>\nvoid ZlibUtil::CompressionStream<CompressionContext>::emitError(\n    jsg::Lock& js, const CompressionError& error) {\n  KJ_IF_SOME(onError, errorHandler) {\n    onError(js, error.err, kj::mv(error.code), kj::mv(error.message));\n  }\n\n  writing = false;\n  if (pending_close) {\n    close();\n  }\n}\n\ntemplate <typename CompressionContext>\ntemplate <bool async>\nvoid ZlibUtil::CompressionStream<CompressionContext>::writeStream(\n    jsg::Lock& js, int flush, kj::ArrayPtr<kj::byte> input, kj::ArrayPtr<kj::byte> output) {\n  JSG_REQUIRE(initialized, Error, \"Writing before initializing\"_kj);\n  JSG_REQUIRE(!closed, Error, \"Already finalized\"_kj);\n  JSG_REQUIRE(!writing, Error, \"Writing is in progress\"_kj);\n  JSG_REQUIRE(!pending_close, Error, \"Pending close\"_kj);\n\n  writing = true;\n\n  context()->setBuffers(input, output);\n  context()->setFlush(flush);\n\n  // Clear buffer pointers from the compression context when this scope exits.\n  // The input/output kj::Array parameters are backed by V8 BackingStores\n  // whose lifetimes are tied to their JavaScript ArrayBuffer objects. Once\n  // this method returns, the kj::Array destructor releases its shared_ptr\n  // to the BackingStore. If the JS buffer subsequently becomes unreachable\n  // and is garbage-collected, the BackingStore is freed — leaving any\n  // retained pointers (e.g. z_stream.next_out) dangling. A later call to\n  // deflateParams() (via params()) could then write to freed memory.\n  //\n  // Using KJ_DEFER ensures the pointers are cleared even if an exception\n  // is thrown (e.g. from updateWriteResult() when the writeState buffer is\n  // too small). Without this, the exception would unwind the stack before\n  // reaching an explicit clearBuffers() call, leaving stale pointers.\n  KJ_DEFER(context()->clearBuffers());\n\n  if constexpr (!async) {\n    context()->work();\n    if (checkError(js)) {\n      writing = false;\n      updateWriteResult(js);\n    }\n    return;\n  }\n\n  // On Node.js, this is called as a result of `ScheduleWork()` call.\n  // Since, we implement the whole thing as sync, we're going to ahead and call the whole thing here.\n  context()->work();\n\n  // This is implemented slightly differently in Node.js\n  // Node.js calls AfterThreadPoolWork().\n  // Ref: https://github.com/nodejs/node/blob/9edf4a0856681a7665bd9dcf2ca7cac252784b98/src/node_zlib.cc#L402\n  writing = false;\n  if (!checkError(js)) {\n    return;\n  }\n  updateWriteResult(js);\n  KJ_IF_SOME(cb, writeCallback) {\n    cb(js);\n  }\n\n  if (pending_close) {\n    close();\n  }\n}\n\ntemplate <typename CompressionContext>\nvoid ZlibUtil::CompressionStream<CompressionContext>::close() {\n  pending_close = writing;\n  if (writing) {\n    return;\n  }\n  closed = true;\n  JSG_ASSERT(initialized, Error, \"Closing before initialized\"_kj);\n  // Context is closed on the destructor of the CompressionContext.\n}\n\ntemplate <typename CompressionContext>\nbool ZlibUtil::CompressionStream<CompressionContext>::checkError(jsg::Lock& js) {\n  KJ_IF_SOME(error, context()->getError()) {\n    emitError(js, kj::mv(error));\n    return false;\n  }\n  return true;\n}\n\ntemplate <typename CompressionContext>\nvoid ZlibUtil::CompressionStream<CompressionContext>::initializeStream(\n    jsg::Lock& js, jsg::JsArrayBufferView& _writeResult, jsg::Function<void()> _writeCallback) {\n  writeResult = _writeResult.addRef(js);\n  writeCallback = kj::mv(_writeCallback);\n  initialized = true;\n}\n\ntemplate <typename CompressionContext>\nvoid ZlibUtil::CompressionStream<CompressionContext>::updateWriteResult(jsg::Lock& js) {\n  KJ_IF_SOME(wr, writeResult) {\n    auto result = wr.getHandle(js);\n    auto ptr = result.template asArrayPtr<uint32_t>();\n    JSG_REQUIRE(ptr.size() >= 2, Error, \"Invalid write result buffer\"_kj);\n    context()->getAfterWriteResult(&ptr[1], &ptr[0]);\n  }\n}\n\ntemplate <typename CompressionContext>\ntemplate <bool async>\nvoid ZlibUtil::CompressionStream<CompressionContext>::write(jsg::Lock& js,\n    int flush,\n    jsg::Optional<kj::Array<kj::byte>> input,\n    uint32_t inputOffset,\n    uint32_t inputLength,\n    kj::Array<kj::byte> output,\n    uint32_t outputOffset,\n    uint32_t outputLength) {\n  if (flush != Z_NO_FLUSH && flush != Z_PARTIAL_FLUSH && flush != Z_SYNC_FLUSH &&\n      flush != Z_FULL_FLUSH && flush != Z_FINISH && flush != Z_BLOCK) {\n    JSG_FAIL_REQUIRE(Error, \"Invalid flush value\");\n  }\n\n  // Use default values if input is not determined\n  if (input == kj::none) {\n    inputLength = 0;\n    inputOffset = 0;\n  }\n\n  auto input_ensured = input.map([](auto& val) { return val.asPtr(); }).orDefault({});\n\n  // Check for integer overflow...\n  JSG_REQUIRE(inputOffset + inputLength >= inputOffset, Error, \"Input access it not within bounds\");\n  JSG_REQUIRE(\n      outputOffset + outputLength >= outputOffset, Error, \"Input access it not within bounds\");\n  JSG_REQUIRE(IsWithinBounds(inputOffset, inputLength, input_ensured.size()), Error,\n      \"Input access is not within bounds\"_kj);\n  JSG_REQUIRE(IsWithinBounds(outputOffset, outputLength, output.size()), Error,\n      \"Output access is not within bounds\"_kj);\n\n  writeStream<async>(js, flush, input_ensured.slice(inputOffset, inputOffset + inputLength),\n      output.slice(outputOffset, outputOffset + outputLength));\n}\n\ntemplate <typename CompressionContext>\nvoid ZlibUtil::CompressionStream<CompressionContext>::reset(jsg::Lock& js) {\n  KJ_IF_SOME(error, context()->resetStream()) {\n    emitError(js, kj::mv(error));\n  }\n}\n\njsg::Ref<ZlibUtil::ZlibStream> ZlibUtil::ZlibStream::constructor(\n    jsg::Lock& js, ZlibModeValue mode) {\n  return js.alloc<ZlibStream>(static_cast<ZlibMode>(mode), js.getExternalMemoryTarget());\n}\n\nvoid ZlibUtil::ZlibStream::initialize(jsg::Lock& js,\n    int windowBits,\n    int level,\n    int memLevel,\n    int strategy,\n    jsg::JsArrayBufferView writeState,\n    jsg::Function<void()> writeCallback,\n    jsg::Optional<kj::Array<kj::byte>> dictionary) {\n  initializeStream(js, writeState, kj::mv(writeCallback));\n  allocator.configure(context()->getStream());\n  context()->initialize(level, windowBits, memLevel, strategy, kj::mv(dictionary));\n}\n\nvoid ZlibUtil::ZlibStream::params(jsg::Lock& js, int _level, int _strategy) {\n  context()->setParams(_level, _strategy);\n  KJ_IF_SOME(err, context()->getError()) {\n    emitError(js, kj::mv(err));\n  }\n}\n\nvoid BrotliContext::setBuffers(kj::ArrayPtr<kj::byte> input, kj::ArrayPtr<kj::byte> output) {\n  nextIn = reinterpret_cast<const uint8_t*>(input.begin());\n  nextOut = output.begin();\n  availIn = input.size();\n  availOut = output.size();\n}\n\nvoid BrotliContext::setInputBuffer(kj::ArrayPtr<const kj::byte> input) {\n  nextIn = input.begin();\n  availIn = input.size();\n}\n\nvoid BrotliContext::setOutputBuffer(kj::ArrayPtr<kj::byte> output) {\n  nextOut = output.begin();\n  availOut = output.size();\n}\n\nuint BrotliContext::getAvailOut() const {\n  return availOut;\n}\n\nvoid BrotliContext::setFlush(int _flush) {\n  flush = static_cast<BrotliEncoderOperation>(_flush);\n}\n\nvoid BrotliContext::getAfterWriteResult(uint32_t* _availIn, uint32_t* _availOut) const {\n  *_availIn = availIn;\n  *_availOut = availOut;\n}\n\nBrotliEncoderContext::BrotliEncoderContext(ZlibMode _mode): BrotliContext(_mode) {\n  auto instance = BrotliEncoderCreateInstance(alloc_brotli, free_brotli, alloc_opaque_brotli);\n  state = kj::disposeWith<BrotliEncoderDestroyInstance>(instance);\n}\n\nvoid BrotliEncoderContext::work() {\n  JSG_REQUIRE(mode == ZlibMode::BROTLI_ENCODE, Error, \"Mode should be BROTLI_ENCODE\"_kj);\n  JSG_REQUIRE_NONNULL(state.get(), Error, \"State should not be empty\"_kj);\n\n  const uint8_t* internalNext = nextIn;\n  lastResult = BrotliEncoderCompressStream(\n      state.get(), flush, &availIn, &internalNext, &availOut, &nextOut, nullptr);\n  nextIn += internalNext - nextIn;\n\n  streamEnd = lastResult && BrotliEncoderIsFinished(state.get());\n}\n\nkj::Maybe<CompressionError> BrotliEncoderContext::initialize(\n    brotli_alloc_func init_alloc_func, brotli_free_func init_free_func, void* init_opaque_func) {\n  alloc_brotli = init_alloc_func;\n  free_brotli = init_free_func;\n  alloc_opaque_brotli = init_opaque_func;\n\n  auto instance = BrotliEncoderCreateInstance(alloc_brotli, free_brotli, alloc_opaque_brotli);\n  state = kj::disposeWith<BrotliEncoderDestroyInstance>(kj::mv(instance));\n\n  if (state.get() == nullptr) {\n    return CompressionError(\n        \"Could not initialize Brotli instance\"_kj, \"ERR_ZLIB_INITIALIZATION_FAILED\"_kj, -1);\n  }\n\n  return kj::none;\n}\n\nkj::Maybe<CompressionError> BrotliEncoderContext::resetStream() {\n  return initialize(alloc_brotli, free_brotli, alloc_opaque_brotli);\n}\n\nkj::Maybe<CompressionError> BrotliEncoderContext::setParams(int key, uint32_t value) {\n  if (!BrotliEncoderSetParameter(state.get(), static_cast<BrotliEncoderParameter>(key), value)) {\n    return CompressionError(\"Setting parameter failed\", \"ERR_BROTLI_PARAM_SET_FAILED\", -1);\n  }\n\n  return kj::none;\n}\n\nkj::Maybe<CompressionError> BrotliEncoderContext::getError() const {\n  if (!lastResult) {\n    return CompressionError(\"Compression failed\", \"ERR_BROTLI_COMPRESSION_FAILED\", -1);\n  }\n\n  return kj::none;\n}\n\nbool BrotliEncoderContext::isStreamEnd() const {\n  return streamEnd;\n}\n\nBrotliDecoderContext::BrotliDecoderContext(ZlibMode _mode): BrotliContext(_mode) {\n  auto instance = BrotliDecoderCreateInstance(alloc_brotli, free_brotli, alloc_opaque_brotli);\n  state = kj::disposeWith<BrotliDecoderDestroyInstance>(instance);\n}\n\nkj::Maybe<CompressionError> BrotliDecoderContext::initialize(\n    brotli_alloc_func init_alloc_func, brotli_free_func init_free_func, void* init_opaque_func) {\n  alloc_brotli = init_alloc_func;\n  free_brotli = init_free_func;\n  alloc_opaque_brotli = init_opaque_func;\n\n  auto instance = BrotliDecoderCreateInstance(alloc_brotli, free_brotli, alloc_opaque_brotli);\n  state = kj::disposeWith<BrotliDecoderDestroyInstance>(kj::mv(instance));\n\n  if (state.get() == nullptr) {\n    return CompressionError(\n        \"Could not initialize Brotli instance\", \"ERR_ZLIB_INITIALIZATION_FAILED\", -1);\n  }\n\n  return kj::none;\n}\n\nvoid BrotliDecoderContext::work() {\n  JSG_REQUIRE(mode == ZlibMode::BROTLI_DECODE, Error, \"Mode should have been BROTLI_DECODE\"_kj);\n  JSG_REQUIRE_NONNULL(state.get(), Error, \"State should not be empty\"_kj);\n  const uint8_t* internalNext = nextIn;\n  lastResult = BrotliDecoderDecompressStream(\n      state.get(), &availIn, &internalNext, &availOut, &nextOut, nullptr);\n  nextIn += internalNext - nextIn;\n\n  if (lastResult == BROTLI_DECODER_RESULT_ERROR) {\n    error = BrotliDecoderGetErrorCode(state.get());\n    errorString = kj::str(\"ERR_\", BrotliDecoderErrorString(error));\n  }\n}\n\nkj::Maybe<CompressionError> BrotliDecoderContext::resetStream() {\n  return initialize(alloc_brotli, free_brotli, alloc_opaque_brotli);\n}\n\nkj::Maybe<CompressionError> BrotliDecoderContext::setParams(int key, uint32_t value) {\n  if (!BrotliDecoderSetParameter(state.get(), static_cast<BrotliDecoderParameter>(key), value)) {\n    return CompressionError(\"Setting parameter failed\", \"ERR_BROTLI_PARAM_SET_FAILED\", -1);\n  }\n\n  return kj::none;\n}\n\nkj::Maybe<CompressionError> BrotliDecoderContext::getError() const {\n  if (error != BROTLI_DECODER_NO_ERROR) {\n    return CompressionError(\"Compression failed\", errorString, -1);\n  }\n\n  if (flush == BROTLI_OPERATION_FINISH && lastResult == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {\n    // Match zlib behavior, as brotli doesn't have its own code for this.\n    return CompressionError(\"Unexpected end of file\", \"Z_BUF_ERROR\", Z_BUF_ERROR);\n  }\n\n  return kj::none;\n}\n\nbool BrotliDecoderContext::isStreamEnd() const {\n  return lastResult == BROTLI_DECODER_RESULT_SUCCESS;\n}\n\n// =======================================================================================\n// Zstd Implementation\n\nvoid ZstdContext::setBuffers(kj::ArrayPtr<kj::byte> input, kj::ArrayPtr<kj::byte> output) {\n  setInputBuffer(input);\n  setOutputBuffer(output);\n}\n\nvoid ZstdContext::setInputBuffer(kj::ArrayPtr<const kj::byte> input) {\n  input_.src = input.begin();\n  input_.size = input.size();\n  input_.pos = 0;\n}\n\nvoid ZstdContext::setOutputBuffer(kj::ArrayPtr<kj::byte> output) {\n  output_.dst = output.begin();\n  output_.size = output.size();\n  output_.pos = 0;\n}\n\nvoid ZstdContext::setFlush(int flush) {\n  KJ_DASSERT(flush >= ZSTD_e_continue && flush <= ZSTD_e_end,\n      \"flush must be a valid ZSTD_EndDirective value\");\n  flush_ = static_cast<ZSTD_EndDirective>(flush);\n}\n\nkj::uint ZstdContext::getAvailOut() const {\n  return output_.size - output_.pos;\n}\n\nvoid ZstdContext::getAfterWriteResult(uint32_t* availIn, uint32_t* availOut) const {\n  *availIn = input_.size - input_.pos;\n  *availOut = output_.size - output_.pos;\n}\n\nnamespace {\n// Helper to check ZSTD errors and return a CompressionError if present.\n// Also sets the error code in the provided reference for later retrieval.\nkj::Maybe<CompressionError> zstdCheckError(\n    size_t result, ZSTD_ErrorCode& error, kj::StringPtr errorCode) {\n  if (ZSTD_isError(result)) {\n    error = ZSTD_getErrorCode(result);\n    return CompressionError(ZSTD_getErrorName(result), errorCode, -1);\n  }\n  return kj::none;\n}\n\n// Wrappers for ZSTD free functions that return void (for use with kj::disposeWith).\nvoid zstdFreeCCtx(ZSTD_CCtx* cctx) {\n  ZSTD_freeCCtx(cctx);\n}\nvoid zstdFreeDCtx(ZSTD_DCtx* dctx) {\n  ZSTD_freeDCtx(dctx);\n}\n}  // namespace\n\nZstdEncoderContext::ZstdEncoderContext(ZlibMode _mode)\n    : ZstdContext(_mode),\n      cctx_(kj::disposeWith<zstdFreeCCtx>(ZSTD_createCCtx())) {}\n\nkj::Maybe<CompressionError> ZstdEncoderContext::initialize(uint64_t pledgedSrcSize) {\n  if (cctx_.get() == nullptr) {\n    return CompressionError(\n        \"Could not initialize Zstd instance\"_kj, \"ERR_ZLIB_INITIALIZATION_FAILED\"_kj, -1);\n  }\n\n  if (pledgedSrcSize != ZSTD_CONTENTSIZE_UNKNOWN) {\n    size_t result = ZSTD_CCtx_setPledgedSrcSize(cctx_.get(), pledgedSrcSize);\n    KJ_IF_SOME(err, zstdCheckError(result, error_, \"ERR_ZSTD_COMPRESSION_FAILED\"_kj)) {\n      return kj::mv(err);\n    }\n  }\n\n  return kj::none;\n}\n\nvoid ZstdEncoderContext::work() {\n  JSG_REQUIRE(mode == ZlibMode::ZSTD_ENCODE, Error, \"Mode should be ZSTD_ENCODE\"_kj);\n  JSG_REQUIRE(cctx_.get() != nullptr, Error, \"Zstd context should not be null\"_kj);\n\n  lastResult = ZSTD_compressStream2(cctx_.get(), &output_, &input_, flush_);\n\n  if (ZSTD_isError(lastResult)) {\n    error_ = ZSTD_getErrorCode(lastResult);\n  }\n}\n\nkj::Maybe<CompressionError> ZstdEncoderContext::resetStream() {\n  if (cctx_.get() != nullptr) {\n    size_t result = ZSTD_CCtx_reset(cctx_.get(), ZSTD_reset_session_only);\n    KJ_IF_SOME(err, zstdCheckError(result, error_, \"ERR_ZSTD_COMPRESSION_FAILED\"_kj)) {\n      return kj::mv(err);\n    }\n  }\n  return kj::none;\n}\n\nkj::Maybe<CompressionError> ZstdEncoderContext::setParams(int key, int value) {\n  KJ_DASSERT(key >= ZSTD_c_compressionLevel,\n      \"key must be a valid ZSTD_cParameter (first valid value is ZSTD_c_compressionLevel)\");\n  size_t result = ZSTD_CCtx_setParameter(cctx_.get(), static_cast<ZSTD_cParameter>(key), value);\n  if (ZSTD_isError(result)) {\n    return CompressionError(kj::str(\"Setting parameter failed: \", ZSTD_getErrorName(result)),\n        \"ERR_ZSTD_PARAM_SET_FAILED\"_kj, -1);\n  }\n  return kj::none;\n}\n\nkj::Maybe<CompressionError> ZstdEncoderContext::getError() const {\n  if (error_ != ZSTD_error_no_error) {\n    return CompressionError(kj::str(\"Zstd compression failed: \", ZSTD_getErrorString(error_)),\n        kj::str(\"ERR_ZSTD_COMPRESSION_FAILED\"), -1);\n  }\n\n  if (flush_ == ZSTD_e_end && lastResult != 0) {\n    // lastResult > 0 means more output is needed, which shouldn't happen at end\n    return CompressionError(\"Unexpected end of file\"_kj, \"Z_BUF_ERROR\"_kj, Z_BUF_ERROR);\n  }\n\n  return kj::none;\n}\n\nbool ZstdEncoderContext::isStreamEnd() const {\n  // ZSTD_compressStream2 returns 0 when flush_ == ZSTD_e_end and the frame is fully flushed.\n  return !ZSTD_isError(lastResult) && lastResult == 0;\n}\n\nZstdDecoderContext::ZstdDecoderContext(ZlibMode _mode)\n    : ZstdContext(_mode),\n      dctx_(kj::disposeWith<zstdFreeDCtx>(ZSTD_createDCtx())) {}\n\nkj::Maybe<CompressionError> ZstdDecoderContext::initialize() {\n  // dctx_ is created in the constructor. It can only be nullptr if ZSTD_createDCtx()\n  // failed due to memory allocation failure.\n  if (dctx_.get() == nullptr) {\n    return CompressionError(\n        \"Could not initialize Zstd instance\"_kj, \"ERR_ZLIB_INITIALIZATION_FAILED\"_kj, -1);\n  }\n\n  return kj::none;\n}\n\nvoid ZstdDecoderContext::work() {\n  JSG_REQUIRE(mode == ZlibMode::ZSTD_DECODE, Error, \"Mode should be ZSTD_DECODE\"_kj);\n  JSG_REQUIRE(dctx_.get() != nullptr, Error, \"Zstd context should not be null\"_kj);\n\n  lastResult = ZSTD_decompressStream(dctx_.get(), &output_, &input_);\n\n  if (ZSTD_isError(lastResult)) {\n    error_ = ZSTD_getErrorCode(lastResult);\n  } else if (input_.size > 0) {\n    // Track whether we're mid-frame: lastResult > 0 means more data needed,\n    // lastResult == 0 means frame is complete.\n    frameInProgress_ = (lastResult > 0);\n  }\n}\n\nkj::Maybe<CompressionError> ZstdDecoderContext::resetStream() {\n  if (dctx_.get() != nullptr) {\n    size_t result = ZSTD_DCtx_reset(dctx_.get(), ZSTD_reset_session_only);\n    KJ_IF_SOME(err, zstdCheckError(result, error_, \"ERR_ZSTD_DECOMPRESSION_FAILED\"_kj)) {\n      return kj::mv(err);\n    }\n  }\n  frameInProgress_ = false;\n  return kj::none;\n}\n\nkj::Maybe<CompressionError> ZstdDecoderContext::setParams(int key, int value) {\n  KJ_DASSERT(dctx_.get() != nullptr, \"Zstd decompression context should not be null\");\n  size_t result = ZSTD_DCtx_setParameter(dctx_.get(), static_cast<ZSTD_dParameter>(key), value);\n  if (ZSTD_isError(result)) {\n    return CompressionError(kj::str(\"Setting parameter failed: \", ZSTD_getErrorName(result)),\n        \"ERR_ZSTD_PARAM_SET_FAILED\"_kj, -1);\n  }\n  return kj::none;\n}\n\nkj::Maybe<CompressionError> ZstdDecoderContext::getError() const {\n  if (error_ != ZSTD_error_no_error) {\n    return CompressionError(kj::str(\"Zstd decompression failed: \", ZSTD_getErrorString(error_)),\n        kj::str(\"ERR_ZSTD_DECOMPRESSION_FAILED\"), -1);\n  }\n\n  // If this is the final flush, we're mid-frame (frame was started but never\n  // completed), and the output buffer is not full (decoder had space but\n  // couldn't produce more output), the input was truncated.\n  if (flush_ == ZSTD_e_end && frameInProgress_ && output_.pos < output_.size) {\n    return CompressionError(\"unexpected end of file\"_kj, \"ERR_ZSTD_DECOMPRESSION_FAILED\"_kj, -1);\n  }\n\n  return kj::none;\n}\n\nbool ZstdDecoderContext::isStreamEnd() const {\n  // ZSTD_decompressStream returns 0 when a frame is completely decoded and fully flushed.\n  return !ZSTD_isError(lastResult) && lastResult == 0;\n}\n\ntemplate <typename CompressionContext>\njsg::Ref<ZlibUtil::ZstdCompressionStream<CompressionContext>> ZlibUtil::ZstdCompressionStream<\n    CompressionContext>::constructor(jsg::Lock& js, ZlibModeValue mode) {\n  return js.alloc<ZstdCompressionStream>(static_cast<ZlibMode>(mode), js.getExternalMemoryTarget());\n}\n\ntemplate <typename CompressionContext>\nbool ZlibUtil::ZstdCompressionStream<CompressionContext>::initialize(jsg::Lock& js,\n    jsg::JsArrayBufferView params,\n    jsg::JsArrayBufferView writeResult,\n    jsg::Function<void()> writeCallback,\n    jsg::Optional<uint64_t> pledgedSrcSize) {\n  this->initializeStream(js, writeResult, kj::mv(writeCallback));\n\n  uint64_t srcSize = pledgedSrcSize.orDefault(ZSTD_CONTENTSIZE_UNKNOWN);\n\n  kj::Maybe<CompressionError> maybeError;\n  if constexpr (CompressionContext::Mode == ZlibMode::ZSTD_ENCODE) {\n    maybeError = this->context()->initialize(srcSize);\n  } else {\n    maybeError = this->context()->initialize();\n  }\n\n  KJ_IF_SOME(err, maybeError) {\n    this->emitError(js, kj::mv(err));\n    return false;\n  }\n\n  auto results = params.template asArrayPtr<int>();\n\n  for (size_t i = 0; i < results.size(); i++) {\n    if (results[i] == -1) {\n      continue;\n    }\n\n    KJ_IF_SOME(err, this->context()->setParams(i, results[i])) {\n      this->emitError(js, kj::mv(err));\n      return false;\n    }\n  }\n  return true;\n}\n\ntemplate <typename CompressionContext>\njsg::Ref<ZlibUtil::BrotliCompressionStream<CompressionContext>> ZlibUtil::BrotliCompressionStream<\n    CompressionContext>::constructor(jsg::Lock& js, ZlibModeValue mode) {\n  return js.alloc<BrotliCompressionStream>(\n      static_cast<ZlibMode>(mode), js.getExternalMemoryTarget());\n}\n\ntemplate <typename CompressionContext>\nbool ZlibUtil::BrotliCompressionStream<CompressionContext>::initialize(jsg::Lock& js,\n    jsg::JsArrayBufferView params,\n    jsg::JsArrayBufferView writeResult,\n    jsg::Function<void()> writeCallback) {\n  this->initializeStream(js, writeResult, kj::mv(writeCallback));\n  auto maybeError = this->context()->initialize(\n      CompressionAllocator::AllocForBrotli, CompressionAllocator::FreeForZlib, &this->allocator);\n\n  KJ_IF_SOME(err, maybeError) {\n    this->emitError(js, kj::mv(err));\n    return false;\n  }\n\n  auto results = params.template asArrayPtr<uint32_t>();\n\n  for (int i = 0; i < results.size(); i++) {\n    if (results[i] == static_cast<uint32_t>(-1)) {\n      continue;\n    }\n\n    KJ_IF_SOME(err, this->context()->setParams(i, results[i])) {\n      this->emitError(js, kj::mv(err));\n      return false;\n    }\n  }\n  return true;\n}\n\nnamespace {\ntemplate <typename Context>\nstatic kj::Array<kj::byte> syncProcessBuffer(Context& ctx, GrowableBuffer& result) {\n  do {\n    result.addChunk();\n    ctx.setOutputBuffer(kj::ArrayPtr(result.end(), result.available()));\n\n    ctx.work();\n\n    KJ_IF_SOME(error, ctx.getError()) {\n      JSG_FAIL_REQUIRE(Error, error.message);\n    }\n\n    result.adjustUnused(ctx.getAvailOut());\n\n    if (ctx.getAvailOut() == 0 && result.atMaxCapacity()) {\n      // The output buffer was completely filled and has reached maxOutputLength.\n      // If the stream is done, the output just happened to fit exactly — return it.\n      // Otherwise the decompressed data exceeds maxOutputLength.\n      JSG_REQUIRE(ctx.isStreamEnd(), RangeError, \"Memory limit exceeded\");\n      break;\n    }\n  } while (ctx.getAvailOut() == 0);\n\n  return result.releaseAsArray();\n}\n}  // namespace\n\nkj::Array<kj::byte> ZlibUtil::zlibSync(\n    jsg::Lock& js, ZlibUtil::InputSource data, ZlibContext::Options opts, ZlibModeValue mode) {\n  // Any use of zlib APIs constitutes an implicit dependency on Allocator which must\n  // remain alive until the zlib stream is destroyed\n  CompressionAllocator allocator(js.getExternalMemoryTarget());\n  ZlibContext ctx(static_cast<ZlibMode>(mode));\n  allocator.configure(ctx.getStream());\n\n  auto chunkSize = opts.chunkSize.orDefault(ZLIB_PERFORMANT_CHUNK_SIZE);\n  auto maxOutputLength = opts.maxOutputLength.orDefault(Z_MAX_CHUNK);\n\n  // TODO(soon): Extend JSG_REQUIRE so we can pass the full level of info NodeJS provides, like the code field\n  JSG_REQUIRE(Z_MIN_CHUNK <= chunkSize && chunkSize <= Z_MAX_CHUNK, RangeError,\n      kj::str(\"The value of \\\"options.chunkSize\\\" is out of range. It must be >= \", Z_MIN_CHUNK,\n          \" and <= \", Z_MAX_CHUNK, \". Received \", chunkSize));\n  JSG_REQUIRE(maxOutputLength >= 1 && maxOutputLength <= Z_MAX_CHUNK, RangeError,\n      kj::str(\"The value of \\\"options.maxOutputLength\\\" is out of range. It must be >= 1 and <= \",\n          Z_MAX_CHUNK, \". Received \", maxOutputLength));\n  GrowableBuffer result(ZLIB_PERFORMANT_CHUNK_SIZE, maxOutputLength);\n\n  ctx.initialize(opts.level.orDefault(Z_DEFAULT_LEVEL),\n      opts.windowBits.orDefault(Z_DEFAULT_WINDOWBITS), opts.memLevel.orDefault(Z_DEFAULT_MEMLEVEL),\n      opts.strategy.orDefault(Z_DEFAULT_STRATEGY), kj::mv(opts.dictionary));\n\n  auto flush = opts.flush.orDefault(Z_NO_FLUSH);\n  JSG_REQUIRE(Z_NO_FLUSH <= flush && flush <= Z_TREES, RangeError,\n      kj::str(\"The value of \\\"options.flush\\\" is out of range. It must be >= \", Z_NO_FLUSH,\n          \" and <= \", Z_TREES, \". Received \", flush));\n\n  auto finishFlush = opts.finishFlush.orDefault(Z_FINISH);\n  JSG_REQUIRE(Z_NO_FLUSH <= finishFlush && finishFlush <= Z_TREES, RangeError,\n      kj::str(\"The value of \\\"options.finishFlush\\\" is out of range. It must be >= \", Z_NO_FLUSH,\n          \" and <= \", Z_TREES, \". Received \", flush));\n\n  ctx.setFlush(finishFlush);\n  ctx.setInputBuffer(getInputFromSource(data));\n  return syncProcessBuffer(ctx, result);\n}\n\nvoid ZlibUtil::zlibWithCallback(jsg::Lock& js,\n    InputSource data,\n    ZlibContext::Options options,\n    ZlibModeValue mode,\n    CompressCallback cb) {\n  // Capture only relevant errors so they can be passed to the callback\n  auto res = js.tryCatch([&]() {\n    return CompressCallbackArg(zlibSync(js, kj::mv(data), kj::mv(options), mode));\n  }, [&](jsg::Value&& exception) {\n    return CompressCallbackArg(jsg::JsValue(exception.getHandle(js)));\n  });\n\n  // Ensure callback is invoked only once\n  cb(js, kj::mv(res));\n}\n\ntemplate <typename Context>\nkj::Array<kj::byte> ZlibUtil::brotliSync(\n    jsg::Lock& js, InputSource data, BrotliContext::Options opts) {\n  // Any use of brotli APIs constitutes an implicit dependency on Allocator which must\n  // remain alive until the brotli state is destroyed\n  CompressionAllocator allocator(js.getExternalMemoryTarget());\n  Context ctx(Context::Mode);\n\n  auto chunkSize = opts.chunkSize.orDefault(ZLIB_PERFORMANT_CHUNK_SIZE);\n  auto maxOutputLength = opts.maxOutputLength.orDefault(Z_MAX_CHUNK);\n\n  // TODO(soon): Extend JSG_REQUIRE so we can pass the full level of info NodeJS provides, like the code field\n  JSG_REQUIRE(Z_MIN_CHUNK <= chunkSize && chunkSize <= Z_MAX_CHUNK, RangeError,\n      kj::str(\"The value of \\\"options.chunkSize\\\" is out of range. It must be >= \", Z_MIN_CHUNK,\n          \" and <= \", Z_MAX_CHUNK, \". Received \", chunkSize));\n  JSG_REQUIRE(maxOutputLength >= 1 && maxOutputLength <= Z_MAX_CHUNK, RangeError,\n      kj::str(\"The value of \\\"options.maxOutputLength\\\" is out of range. It must be >= 1 and <= \",\n          Z_MAX_CHUNK, \". Received \", maxOutputLength));\n  GrowableBuffer result(ZLIB_PERFORMANT_CHUNK_SIZE, maxOutputLength);\n\n  KJ_IF_SOME(err,\n      ctx.initialize(\n          CompressionAllocator::AllocForBrotli, CompressionAllocator::FreeForZlib, &allocator)) {\n    JSG_FAIL_REQUIRE(Error, err.message);\n  }\n\n  KJ_IF_SOME(params, opts.params) {\n    for (const auto& field: params.fields) {\n      KJ_IF_SOME(err, ctx.setParams(field.name.parseAs<int>(), field.value)) {\n        JSG_FAIL_REQUIRE(Error, err.message);\n      }\n    }\n  }\n\n  auto flush = opts.flush.orDefault(BROTLI_OPERATION_PROCESS);\n  JSG_REQUIRE(BROTLI_OPERATION_PROCESS <= flush && flush <= BROTLI_OPERATION_EMIT_METADATA,\n      RangeError,\n      kj::str(\"The value of \\\"options.flush\\\" is out of range. It must be >= \",\n          BROTLI_OPERATION_PROCESS, \" and <= \", BROTLI_OPERATION_EMIT_METADATA, \". Received \",\n          flush));\n\n  auto finishFlush = opts.finishFlush.orDefault(BROTLI_OPERATION_FINISH);\n  JSG_REQUIRE(\n      BROTLI_OPERATION_PROCESS <= finishFlush && finishFlush <= BROTLI_OPERATION_EMIT_METADATA,\n      RangeError,\n      kj::str(\"The value of \\\"options.finishFlush\\\" is out of range. It must be >= \",\n          BROTLI_OPERATION_PROCESS, \" and <= \", BROTLI_OPERATION_EMIT_METADATA, \". Received \",\n          finishFlush));\n\n  ctx.setFlush(finishFlush);\n  ctx.setInputBuffer(getInputFromSource(data));\n  return syncProcessBuffer(ctx, result);\n}\n\ntemplate <typename Context>\nvoid ZlibUtil::brotliWithCallback(\n    jsg::Lock& js, InputSource data, BrotliContext::Options options, CompressCallback cb) {\n  // Capture only relevant errors so they can be passed to the callback\n  auto res = js.tryCatch([&]() {\n    return CompressCallbackArg(brotliSync<Context>(js, kj::mv(data), kj::mv(options)));\n  }, [&](jsg::Value&& exception) {\n    return CompressCallbackArg(jsg::JsValue(exception.getHandle(js)));\n  });\n\n  // Ensure callback is invoked only once\n  cb(js, kj::mv(res));\n}\n\ntemplate <typename Context>\nkj::Array<kj::byte> ZlibUtil::zstdSync(jsg::Lock& js, InputSource data, ZstdContext::Options opts) {\n  Context ctx(Context::Mode);\n\n  auto chunkSize = opts.chunkSize.orDefault(ZLIB_PERFORMANT_CHUNK_SIZE);\n  auto maxOutputLength = opts.maxOutputLength.orDefault(Z_MAX_CHUNK);\n\n  JSG_REQUIRE(Z_MIN_CHUNK <= chunkSize && chunkSize <= Z_MAX_CHUNK, RangeError,\n      kj::str(\"The value of \\\"options.chunkSize\\\" is out of range. It must be >= \", Z_MIN_CHUNK,\n          \" and <= \", Z_MAX_CHUNK, \". Received \", chunkSize));\n  JSG_REQUIRE(maxOutputLength >= 1 && maxOutputLength <= Z_MAX_CHUNK, RangeError,\n      kj::str(\"The value of \\\"options.maxOutputLength\\\" is out of range. It must be >= 1 and <= \",\n          Z_MAX_CHUNK, \". Received \", maxOutputLength));\n  GrowableBuffer result(ZLIB_PERFORMANT_CHUNK_SIZE, maxOutputLength);\n\n  // Initialize the context\n  if constexpr (Context::Mode == ZlibMode::ZSTD_ENCODE) {\n    uint64_t pledgedSrcSize = opts.pledgedSrcSize.orDefault(ZSTD_CONTENTSIZE_UNKNOWN);\n    KJ_IF_SOME(err, ctx.initialize(pledgedSrcSize)) {\n      JSG_FAIL_REQUIRE(Error, err.message);\n    }\n  } else {\n    KJ_IF_SOME(err, ctx.initialize()) {\n      JSG_FAIL_REQUIRE(Error, err.message);\n    }\n  }\n\n  // Set parameters\n  KJ_IF_SOME(params, opts.params) {\n    for (const auto& field: params.fields) {\n      KJ_IF_SOME(err, ctx.setParams(field.name.parseAs<int>(), field.value)) {\n        JSG_FAIL_REQUIRE(Error, err.message);\n      }\n    }\n  }\n\n  auto flush = opts.flush.orDefault(ZSTD_e_continue);\n  JSG_REQUIRE(ZSTD_e_continue <= flush && flush <= ZSTD_e_end, RangeError,\n      kj::str(\"The value of \\\"options.flush\\\" is out of range. It must be >= \", ZSTD_e_continue,\n          \" and <= \", ZSTD_e_end, \". Received \", flush));\n\n  auto finishFlush = opts.finishFlush.orDefault(ZSTD_e_end);\n  JSG_REQUIRE(ZSTD_e_continue <= finishFlush && finishFlush <= ZSTD_e_end, RangeError,\n      kj::str(\"The value of \\\"options.finishFlush\\\" is out of range. It must be >= \",\n          ZSTD_e_continue, \" and <= \", ZSTD_e_end, \". Received \", finishFlush));\n\n  ctx.setFlush(finishFlush);\n  ctx.setInputBuffer(getInputFromSource(data));\n  return syncProcessBuffer(ctx, result);\n}\n\ntemplate <typename Context>\nvoid ZlibUtil::zstdWithCallback(\n    jsg::Lock& js, InputSource data, ZstdContext::Options options, CompressCallback cb) {\n  // Capture only relevant errors so they can be passed to the callback\n  auto res = js.tryCatch([&]() {\n    return CompressCallbackArg(zstdSync<Context>(js, kj::mv(data), kj::mv(options)));\n  }, [&](jsg::Value&& exception) {\n    return CompressCallbackArg(jsg::JsValue(exception.getHandle(js)));\n  });\n\n  // Ensure callback is invoked only once\n  cb(js, kj::mv(res));\n}\n\n#ifndef CREATE_TEMPLATE\n#define CREATE_TEMPLATE(T)                                                                         \\\n  template class ZlibUtil::CompressionStream<T>;                                                   \\\n  template void ZlibUtil::CompressionStream<T>::write<false>(jsg::Lock & js, int flush,            \\\n      jsg::Optional<kj::Array<kj::byte>> input, uint32_t inputOffset, uint32_t inputLength,        \\\n      kj::Array<kj::byte> output, uint32_t outputOffset, uint32_t outputLength);                   \\\n  template void ZlibUtil::CompressionStream<T>::write<true>(jsg::Lock & js, int flush,             \\\n      jsg::Optional<kj::Array<kj::byte>> input, uint32_t inputOffset, uint32_t inputLength,        \\\n      kj::Array<kj::byte> output, uint32_t outputOffset, uint32_t outputLength);\n\nCREATE_TEMPLATE(ZlibContext)\nCREATE_TEMPLATE(BrotliEncoderContext)\nCREATE_TEMPLATE(BrotliDecoderContext)\nCREATE_TEMPLATE(ZstdEncoderContext)\nCREATE_TEMPLATE(ZstdDecoderContext)\n\ntemplate class ZlibUtil::BrotliCompressionStream<BrotliEncoderContext>;\ntemplate class ZlibUtil::BrotliCompressionStream<BrotliDecoderContext>;\n\ntemplate class ZlibUtil::ZstdCompressionStream<ZstdEncoderContext>;\ntemplate class ZlibUtil::ZstdCompressionStream<ZstdDecoderContext>;\n\ntemplate kj::Array<kj::byte> ZlibUtil::brotliSync<BrotliEncoderContext>(\n    jsg::Lock& js, InputSource data, BrotliContext::Options opts);\ntemplate kj::Array<kj::byte> ZlibUtil::brotliSync<BrotliDecoderContext>(\n    jsg::Lock& js, InputSource data, BrotliContext::Options opts);\ntemplate void ZlibUtil::brotliWithCallback<BrotliEncoderContext>(\n    jsg::Lock& js, InputSource data, BrotliContext::Options options, CompressCallback cb);\ntemplate void ZlibUtil::brotliWithCallback<BrotliDecoderContext>(\n    jsg::Lock& js, InputSource data, BrotliContext::Options options, CompressCallback cb);\n\ntemplate kj::Array<kj::byte> ZlibUtil::zstdSync<ZstdEncoderContext>(\n    jsg::Lock& js, InputSource data, ZstdContext::Options opts);\ntemplate kj::Array<kj::byte> ZlibUtil::zstdSync<ZstdDecoderContext>(\n    jsg::Lock& js, InputSource data, ZstdContext::Options opts);\ntemplate void ZlibUtil::zstdWithCallback<ZstdEncoderContext>(\n    jsg::Lock& js, InputSource data, ZstdContext::Options options, CompressCallback cb);\ntemplate void ZlibUtil::zstdWithCallback<ZstdDecoderContext>(\n    jsg::Lock& js, InputSource data, ZstdContext::Options options, CompressCallback cb);\n#undef CREATE_TEMPLATE\n#endif\n}  // namespace workerd::api::node\n"
  },
  {
    "path": "src/workerd/api/node/zlib-util.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n#pragma once\n\n#include <workerd/api/streams/compression.h>\n#include <workerd/jsg/jsg.h>\n\n#include <brotli/decode.h>\n#include <brotli/encode.h>\n#include <zlib.h>\n#include <zstd.h>\n#include <zstd_errors.h>\n\n#include <kj/array.h>\n#include <kj/one-of.h>\n#include <kj/vector.h>\n\n// The following implementation is adapted from Node.js\n// and therefore follows Node.js style as opposed to kj style.\n// Latest implementation of Node.js zlib can be found at:\n// https://github.com/nodejs/node/blob/main/src/node_zlib.cc\nnamespace workerd::api::node {\n\n#ifndef ZLIB_ERROR_CODES\n#define ZLIB_ERROR_CODES(V)                                                                        \\\n  V(Z_OK)                                                                                          \\\n  V(Z_STREAM_END)                                                                                  \\\n  V(Z_NEED_DICT)                                                                                   \\\n  V(Z_ERRNO)                                                                                       \\\n  V(Z_STREAM_ERROR)                                                                                \\\n  V(Z_DATA_ERROR)                                                                                  \\\n  V(Z_MEM_ERROR)                                                                                   \\\n  V(Z_BUF_ERROR)                                                                                   \\\n  V(Z_VERSION_ERROR)\n\ninline const char* ZlibStrerror(int err) {\n#define V(code)                                                                                    \\\n  if (err == code) return #code;\n  ZLIB_ERROR_CODES(V)\n#undef V\n  return \"Z_UNKNOWN_ERROR\";\n}\n#endif  // ZLIB_ERROR_CODES\n\n// Certain zlib constants are defined by Node.js itself\nstatic constexpr auto Z_MIN_CHUNK = 64;\nstatic constexpr auto Z_MAX_CHUNK = 128 * 1024 * 1024;\nstatic constexpr auto Z_DEFAULT_CHUNK = 16 * 1024;\nstatic constexpr auto Z_MIN_MEMLEVEL = 1;\n\nstatic constexpr auto Z_MAX_MEMLEVEL = 9;\nstatic constexpr auto Z_DEFAULT_MEMLEVEL = 8;\nstatic constexpr auto Z_MIN_LEVEL = -1;\nstatic constexpr auto Z_MAX_LEVEL = 9;\nstatic constexpr auto Z_DEFAULT_LEVEL = Z_DEFAULT_COMPRESSION;\nstatic constexpr auto Z_MIN_WINDOWBITS = 8;\nstatic constexpr auto Z_MAX_WINDOWBITS = 15;\nstatic constexpr auto Z_DEFAULT_WINDOWBITS = 15;\n\nstatic constexpr uint8_t GZIP_HEADER_ID1 = 0x1f;\nstatic constexpr uint8_t GZIP_HEADER_ID2 = 0x8b;\n\nusing ZlibModeValue = uint8_t;\nenum class ZlibMode : ZlibModeValue {\n  NONE,\n  DEFLATE,\n  INFLATE,\n  GZIP,\n  GUNZIP,\n  DEFLATERAW,\n  INFLATERAW,\n  UNZIP,\n  BROTLI_DECODE,\n  BROTLI_ENCODE,\n  ZSTD_ENCODE,\n  ZSTD_DECODE\n};\n\n// When possible, we intentionally override chunkSize to a value that is likely to perform better\nstatic constexpr auto ZLIB_PERFORMANT_CHUNK_SIZE = 40 * 1024;\n\nstruct CompressionError {\n  CompressionError(kj::StringPtr _message, kj::StringPtr _code, int _err)\n      : message(kj::str(_message)),\n        code(kj::str(_code)),\n        err(_err) {\n    JSG_REQUIRE(message.size() != 0, Error, \"Compression error message should not be null\");\n  }\n\n  kj::String message;\n  kj::String code;\n  int err;\n};\n\nclass ZlibContext final {\n public:\n  explicit ZlibContext(ZlibMode _mode): mode(_mode) {}\n  ZlibContext() = default;\n  ~ZlibContext() noexcept(false);\n\n  KJ_DISALLOW_COPY_AND_MOVE(ZlibContext);\n\n  void setBuffers(kj::ArrayPtr<kj::byte> input, kj::ArrayPtr<kj::byte> output);\n\n  void setInputBuffer(kj::ArrayPtr<const kj::byte> input);\n  void setOutputBuffer(kj::ArrayPtr<kj::byte> output);\n\n  // Clear all buffer pointers from z_stream to prevent stale pointer access.\n  // Must be called after each write operation completes and results have been\n  // captured, so that subsequent operations (e.g. deflateParams) cannot use\n  // dangling pointers into freed backing stores.\n  //\n  // Note: zlib's deflate() rejects next_out == NULL with Z_STREAM_ERROR even\n  // when avail_out == 0, so we point next_out at a valid dummy byte instead.\n  // With avail_out == 0, no data will actually be written to it.\n  void clearBuffers() {\n    stream.next_in = nullptr;\n    stream.avail_in = 0;\n    stream.next_out = &dummyByte;\n    stream.avail_out = 0;\n  }\n\n  int getFlush() const {\n    return flush;\n  };\n  void setFlush(int value) {\n    flush = value;\n  };\n  // Function signature is same as Node.js implementation.\n  // Ref: https://github.com/nodejs/node/blob/9edf4a0856681a7665bd9dcf2ca7cac252784b98/src/node_zlib.cc#L880\n  void getAfterWriteResult(uint32_t* availIn, uint32_t* availOut) const {\n    *availIn = stream.avail_in;\n    *availOut = stream.avail_out;\n  }\n  void setMode(ZlibMode value) {\n    mode = value;\n  };\n  kj::Maybe<CompressionError> resetStream();\n  kj::Maybe<CompressionError> getError() const;\n  void setAllocationFunctions(alloc_func alloc, free_func free, void* opaque) {\n    stream.zalloc = alloc;\n    stream.zfree = free;\n    stream.opaque = opaque;\n  }\n\n  // Equivalent to Node.js' `DoThreadPoolWork` function.\n  // Ref: https://github.com/nodejs/node/blob/9edf4a0856681a7665bd9dcf2ca7cac252784b98/src/node_zlib.cc#L760\n  void work();\n\n  // Returns true when the zlib stream has reached Z_STREAM_END, indicating\n  // that all compressed data has been fully processed.\n  bool isStreamEnd() const {\n    return err == Z_STREAM_END;\n  }\n\n  uint getAvailIn() const {\n    return stream.avail_in;\n  };\n  void setAvailIn(uint value) {\n    stream.avail_in = value;\n  };\n  uint getAvailOut() const {\n    return stream.avail_out;\n  }\n  void setAvailOut(uint value) {\n    stream.avail_out = value;\n  };\n\n  z_stream* getStream() {\n    return &stream;\n  }\n\n  // Zlib\n  void initialize(int _level,\n      int _windowBits,\n      int _memLevel,\n      int _strategy,\n      jsg::Optional<kj::Array<kj::byte>> _dictionary);\n  kj::Maybe<CompressionError> setParams(int level, int strategy);\n  struct Options {\n    jsg::Optional<int> flush;\n    jsg::Optional<int> finishFlush;\n    jsg::Optional<uint> chunkSize;\n    jsg::Optional<kj::uint> windowBits;\n    jsg::Optional<int> level;\n    jsg::Optional<kj::uint> memLevel;\n    jsg::Optional<kj::uint> strategy;\n    jsg::Optional<kj::Array<kj::byte>> dictionary;\n    jsg::Optional<kj::uint> maxOutputLength;\n\n    JSG_STRUCT(flush,\n        finishFlush,\n        chunkSize,\n        windowBits,\n        level,\n        memLevel,\n        strategy,\n        dictionary,\n        maxOutputLength);\n  };\n\n private:\n  bool initializeZlib();\n  kj::Maybe<CompressionError> setDictionary();\n\n  CompressionError constructError(kj::StringPtr message) const {\n    if (stream.msg != nullptr) message = kj::StringPtr(stream.msg);\n\n    return {kj::str(message), kj::str(ZlibStrerror(err)), err};\n  };\n\n  bool initialized = false;\n  ZlibMode mode = ZlibMode::NONE;\n  int flush = Z_NO_FLUSH;\n  int windowBits = 0;\n  int level = 0;\n  int memLevel = 0;\n  int strategy = 0;\n  kj::Vector<kj::byte> dictionary;\n\n  int err = Z_OK;\n  unsigned int gzip_id_bytes_read = 0;\n  z_stream stream{};\n  // Dummy byte target for clearBuffers(). zlib's deflate() rejects\n  // next_out == NULL even when avail_out == 0, so we need a valid address.\n  Bytef dummyByte = 0;\n};\n\nusing CompressionStreamErrorHandler = jsg::Function<void(int, kj::StringPtr, kj::StringPtr)>;\n\nclass BrotliContext {\n public:\n  explicit BrotliContext(ZlibMode _mode): mode(_mode) {}\n  KJ_DISALLOW_COPY(BrotliContext);\n  void setBuffers(kj::ArrayPtr<kj::byte> input, kj::ArrayPtr<kj::byte> output);\n  void setInputBuffer(kj::ArrayPtr<const kj::byte> input);\n  void setOutputBuffer(kj::ArrayPtr<kj::byte> output);\n  void setFlush(int flush);\n  kj::uint getAvailOut() const;\n  void getAfterWriteResult(uint32_t* availIn, uint32_t* availOut) const;\n  void setMode(ZlibMode _mode) {\n    mode = _mode;\n  }\n\n  void clearBuffers() {\n    nextIn = nullptr;\n    nextOut = nullptr;\n    availIn = 0;\n    availOut = 0;\n  }\n\n  struct Options {\n    jsg::Optional<int> flush;\n    jsg::Optional<int> finishFlush;\n    jsg::Optional<kj::uint> chunkSize;\n    jsg::Optional<jsg::Dict<int>> params;\n    jsg::Optional<kj::uint> maxOutputLength;\n    JSG_STRUCT(flush, finishFlush, chunkSize, params, maxOutputLength);\n  };\n\n protected:\n  ZlibMode mode;\n  const uint8_t* nextIn = nullptr;\n  uint8_t* nextOut = nullptr;\n  size_t availIn = 0;\n  size_t availOut = 0;\n  BrotliEncoderOperation flush = BROTLI_OPERATION_PROCESS;\n\n  // TODO(addaleax): These should not need to be stored here.\n  // This is currently only done this way to make implementing ResetStream()\n  // easier.\n  brotli_alloc_func alloc_brotli = nullptr;\n  brotli_free_func free_brotli = nullptr;\n  void* alloc_opaque_brotli = nullptr;\n};\n\nclass BrotliEncoderContext final: public BrotliContext {\n public:\n  static const ZlibMode Mode = ZlibMode::BROTLI_ENCODE;\n  explicit BrotliEncoderContext(ZlibMode _mode);\n\n  KJ_DISALLOW_COPY_AND_MOVE(BrotliEncoderContext);\n\n  // Equivalent to Node.js' `DoThreadPoolWork` implementation.\n  void work();\n  kj::Maybe<CompressionError> initialize(\n      brotli_alloc_func init_alloc_func, brotli_free_func init_free_func, void* init_opaque_func);\n  kj::Maybe<CompressionError> resetStream();\n  kj::Maybe<CompressionError> setParams(int key, uint32_t value);\n  kj::Maybe<CompressionError> getError() const;\n  bool isStreamEnd() const;\n\n private:\n  bool lastResult = false;\n  bool streamEnd = false;\n  kj::Own<BrotliEncoderStateStruct> state;\n};\n\nclass BrotliDecoderContext final: public BrotliContext {\n public:\n  static const ZlibMode Mode = ZlibMode::BROTLI_DECODE;\n  explicit BrotliDecoderContext(ZlibMode _mode);\n\n  KJ_DISALLOW_COPY_AND_MOVE(BrotliDecoderContext);\n\n  // Equivalent to Node.js' `DoThreadPoolWork` implementation.\n  void work();\n  kj::Maybe<CompressionError> initialize(\n      brotli_alloc_func init_alloc_func, brotli_free_func init_free_func, void* init_opaque_func);\n  kj::Maybe<CompressionError> resetStream();\n  kj::Maybe<CompressionError> setParams(int key, uint32_t value);\n  kj::Maybe<CompressionError> getError() const;\n  bool isStreamEnd() const;\n\n private:\n  BrotliDecoderResult lastResult = BROTLI_DECODER_RESULT_SUCCESS;\n  BrotliDecoderErrorCode error = BROTLI_DECODER_NO_ERROR;\n  kj::String errorString;\n  kj::Own<BrotliDecoderStateStruct> state;\n};\n\nclass ZstdContext {\n public:\n  explicit ZstdContext(ZlibMode _mode): mode(_mode) {}\n  KJ_DISALLOW_COPY(ZstdContext);\n\n  void setBuffers(kj::ArrayPtr<kj::byte> input, kj::ArrayPtr<kj::byte> output);\n  void setInputBuffer(kj::ArrayPtr<const kj::byte> input);\n  void setOutputBuffer(kj::ArrayPtr<kj::byte> output);\n  void setFlush(int flush);\n  kj::uint getAvailOut() const;\n  void getAfterWriteResult(uint32_t* availIn, uint32_t* availOut) const;\n  void setMode(ZlibMode _mode) {\n    mode = _mode;\n  }\n\n  void clearBuffers() {\n    input_ = {nullptr, 0, 0};\n    output_ = {nullptr, 0, 0};\n  }\n\n  struct Options {\n    jsg::Optional<int> flush;\n    jsg::Optional<int> finishFlush;\n    jsg::Optional<kj::uint> chunkSize;\n    jsg::Optional<jsg::Dict<int>> params;\n    jsg::Optional<kj::uint> maxOutputLength;\n    jsg::Optional<uint64_t> pledgedSrcSize;\n    JSG_STRUCT(flush, finishFlush, chunkSize, params, maxOutputLength, pledgedSrcSize);\n  };\n\n protected:\n  ZlibMode mode;\n  ZSTD_inBuffer input_{nullptr, 0, 0};\n  ZSTD_outBuffer output_{nullptr, 0, 0};\n  ZSTD_EndDirective flush_ = ZSTD_e_continue;\n};\n\nclass ZstdEncoderContext final: public ZstdContext {\n public:\n  static const ZlibMode Mode = ZlibMode::ZSTD_ENCODE;\n  explicit ZstdEncoderContext(ZlibMode _mode);\n  KJ_DISALLOW_COPY_AND_MOVE(ZstdEncoderContext);\n\n  void work();\n  kj::Maybe<CompressionError> initialize(uint64_t pledgedSrcSize);\n  kj::Maybe<CompressionError> resetStream();\n  kj::Maybe<CompressionError> setParams(int key, int value);\n  kj::Maybe<CompressionError> getError() const;\n  bool isStreamEnd() const;\n\n private:\n  size_t lastResult = 0;\n  kj::Own<ZSTD_CCtx> cctx_;\n  ZSTD_ErrorCode error_ = ZSTD_error_no_error;\n};\n\nclass ZstdDecoderContext final: public ZstdContext {\n public:\n  static const ZlibMode Mode = ZlibMode::ZSTD_DECODE;\n  explicit ZstdDecoderContext(ZlibMode _mode);\n  KJ_DISALLOW_COPY_AND_MOVE(ZstdDecoderContext);\n\n  void work();\n  kj::Maybe<CompressionError> initialize();\n  kj::Maybe<CompressionError> resetStream();\n  kj::Maybe<CompressionError> setParams(int key, int value);\n  kj::Maybe<CompressionError> getError() const;\n  bool isStreamEnd() const;\n\n private:\n  size_t lastResult = 0;\n  kj::Own<ZSTD_DCtx> dctx_;\n  ZSTD_ErrorCode error_ = ZSTD_error_no_error;\n  bool frameInProgress_ = false;\n};\n\n// Implements utilities in support of the Node.js Zlib\nclass ZlibUtil final: public jsg::Object {\n public:\n  ZlibUtil() = default;\n  ZlibUtil(jsg::Lock&, const jsg::Url&) {}\n\n  template <class CompressionContext>\n  class CompressionStream: public jsg::Object {\n   public:\n    explicit CompressionStream(\n        ZlibMode _mode, kj::Arc<const jsg::ExternalMemoryTarget>&& externalMemoryTarget)\n        : allocator(kj::mv(externalMemoryTarget)),\n          context_(_mode) {}\n    // TODO(soon): Find a way to add noexcept(false) to this destructor.\n    ~CompressionStream();\n    KJ_DISALLOW_COPY_AND_MOVE(CompressionStream);\n\n    static jsg::Ref<CompressionStream> constructor(jsg::Lock& js, ZlibModeValue mode);\n\n    void close();\n    bool checkError(jsg::Lock& js);\n    void emitError(jsg::Lock& js, const CompressionError& error);\n    template <bool async>\n    void writeStream(\n        jsg::Lock& js, int flush, kj::ArrayPtr<kj::byte> input, kj::ArrayPtr<kj::byte> output);\n    void setErrorHandler(CompressionStreamErrorHandler handler) {\n      errorHandler = kj::mv(handler);\n    }\n\n    void updateWriteResult(jsg::Lock& js);\n\n    template <bool async>\n    void write(jsg::Lock& js,\n        int flush,\n        jsg::Optional<kj::Array<kj::byte>> input,\n        uint32_t inputOffset,\n        uint32_t inputLength,\n        kj::Array<kj::byte> output,\n        uint32_t outputOffset,\n        uint32_t outputLength);\n    void reset(jsg::Lock& js);\n\n    JSG_RESOURCE_TYPE(CompressionStream) {\n      JSG_METHOD(close);\n      JSG_METHOD_NAMED(write, template write<true>);\n      JSG_METHOD_NAMED(writeSync, template write<false>);\n      JSG_METHOD(reset);\n      JSG_METHOD(setErrorHandler);\n    }\n\n   protected:\n    CompressionContext* context() {\n      return &context_;\n    }\n\n    void initializeStream(\n        jsg::Lock& js, jsg::JsArrayBufferView& _write_result, jsg::Function<void()> writeCallback);\n\n    // Used to store allocations in Brotli* operations.\n    // This declaration should be physically positioned before\n    // context to avoid `heap-use-after-free` ASan error.\n    CompressionAllocator allocator;\n\n   private:\n    CompressionContext context_;\n    bool initialized = false;\n    bool writing = false;\n    bool pending_close = false;\n    bool closed = false;\n\n    // Equivalent to `write_js_callback` in Node.js\n    jsg::Optional<jsg::Function<void()>> writeCallback;\n    jsg::Optional<jsg::JsRef<jsg::JsArrayBufferView>> writeResult;\n    jsg::Optional<CompressionStreamErrorHandler> errorHandler;\n  };\n\n  class ZlibStream final: public CompressionStream<ZlibContext> {\n   public:\n    explicit ZlibStream(\n        ZlibMode mode, kj::Arc<const jsg::ExternalMemoryTarget>&& externalMemoryTarget)\n        : CompressionStream(mode, kj::mv(externalMemoryTarget)) {}\n    KJ_DISALLOW_COPY_AND_MOVE(ZlibStream);\n    static jsg::Ref<ZlibStream> constructor(jsg::Lock& js, ZlibModeValue mode);\n\n    // Instance methods\n    void initialize(jsg::Lock& js,\n        int windowBits,\n        int level,\n        int memLevel,\n        int strategy,\n        jsg::JsArrayBufferView writeState,\n        jsg::Function<void()> writeCallback,\n        jsg::Optional<kj::Array<kj::byte>> dictionary);\n    void params(jsg::Lock& js, int level, int strategy);\n\n    JSG_RESOURCE_TYPE(ZlibStream) {\n      JSG_INHERIT(CompressionStream<ZlibContext>);\n\n      JSG_METHOD(initialize);\n      JSG_METHOD(params);\n    }\n  };\n\n  template <typename CompressionContext>\n  class BrotliCompressionStream: public CompressionStream<CompressionContext> {\n   public:\n    explicit BrotliCompressionStream(\n        ZlibMode _mode, kj::Arc<const jsg::ExternalMemoryTarget>&& externalMemoryTarget)\n        : CompressionStream<CompressionContext>(_mode, kj::mv(externalMemoryTarget)) {}\n    KJ_DISALLOW_COPY_AND_MOVE(BrotliCompressionStream);\n    static jsg::Ref<BrotliCompressionStream> constructor(jsg::Lock& js, ZlibModeValue mode);\n\n    bool initialize(jsg::Lock& js,\n        jsg::JsArrayBufferView params,\n        jsg::JsArrayBufferView writeResult,\n        jsg::Function<void()> writeCallback);\n\n    void params() {\n      // Currently a no-op, and not accessed from JS land.\n      // At some point Brotli may support changing parameters on the fly,\n      // in which case we can implement this and a JS equivalent similar to\n      // the zlib Params() function.\n    }\n\n    JSG_RESOURCE_TYPE(BrotliCompressionStream) {\n      JSG_INHERIT(CompressionStream<CompressionContext>);\n\n      JSG_METHOD(initialize);\n      JSG_METHOD(params);\n    }\n\n    CompressionContext* context() {\n      return this->CompressionStream<CompressionContext>::context();\n    }\n  };\n\n  template <typename CompressionContext>\n  class ZstdCompressionStream: public CompressionStream<CompressionContext> {\n   public:\n    explicit ZstdCompressionStream(\n        ZlibMode _mode, kj::Arc<const jsg::ExternalMemoryTarget>&& externalMemoryTarget)\n        : CompressionStream<CompressionContext>(_mode, kj::mv(externalMemoryTarget)) {}\n    KJ_DISALLOW_COPY_AND_MOVE(ZstdCompressionStream);\n    static jsg::Ref<ZstdCompressionStream> constructor(jsg::Lock& js, ZlibModeValue mode);\n\n    bool initialize(jsg::Lock& js,\n        jsg::JsArrayBufferView params,\n        jsg::JsArrayBufferView writeResult,\n        jsg::Function<void()> writeCallback,\n        jsg::Optional<uint64_t> pledgedSrcSize);\n\n    void params() {\n      // Currently a no-op, and not accessed from JS land.\n    }\n\n    JSG_RESOURCE_TYPE(ZstdCompressionStream) {\n      JSG_INHERIT(CompressionStream<CompressionContext>);\n\n      JSG_METHOD(initialize);\n      JSG_METHOD(params);\n    }\n\n    CompressionContext* context() {\n      return this->CompressionStream<CompressionContext>::context();\n    }\n  };\n\n  using InputSource = kj::OneOf<jsg::NonCoercible<kj::String>, kj::Array<kj::byte>>;\n  using CompressCallbackArg = kj::OneOf<jsg::JsValue, kj::Array<kj::byte>>;\n  using CompressCallback = jsg::Function<void(CompressCallbackArg)>;\n\n  uint32_t crc32Sync(InputSource data, uint32_t value);\n  void zlibWithCallback(jsg::Lock& js,\n      InputSource data,\n      ZlibContext::Options options,\n      ZlibModeValue mode,\n      CompressCallback cb);\n  kj::Array<kj::byte> zlibSync(\n      jsg::Lock& js, InputSource data, ZlibContext::Options options, ZlibModeValue mode);\n\n  template <typename Context>\n  kj::Array<kj::byte> brotliSync(jsg::Lock& js, InputSource data, BrotliContext::Options options);\n  template <typename Context>\n  void brotliWithCallback(\n      jsg::Lock& js, InputSource data, BrotliContext::Options options, CompressCallback cb);\n\n  template <typename Context>\n  kj::Array<kj::byte> zstdSync(jsg::Lock& js, InputSource data, ZstdContext::Options options);\n  template <typename Context>\n  void zstdWithCallback(\n      jsg::Lock& js, InputSource data, ZstdContext::Options options, CompressCallback cb);\n\n  JSG_RESOURCE_TYPE(ZlibUtil) {\n    JSG_METHOD_NAMED(crc32, crc32Sync);\n    JSG_METHOD(zlibSync);\n    JSG_METHOD_NAMED(zlib, zlibWithCallback);\n\n    JSG_METHOD_NAMED(brotliDecompressSync, template brotliSync<BrotliDecoderContext>);\n    JSG_METHOD_NAMED(brotliCompressSync, template brotliSync<BrotliEncoderContext>);\n    JSG_METHOD_NAMED(brotliDecompress, template brotliWithCallback<BrotliDecoderContext>);\n    JSG_METHOD_NAMED(brotliCompress, template brotliWithCallback<BrotliEncoderContext>);\n\n    JSG_METHOD_NAMED(zstdDecompressSync, template zstdSync<ZstdDecoderContext>);\n    JSG_METHOD_NAMED(zstdCompressSync, template zstdSync<ZstdEncoderContext>);\n    JSG_METHOD_NAMED(zstdDecompress, template zstdWithCallback<ZstdDecoderContext>);\n    JSG_METHOD_NAMED(zstdCompress, template zstdWithCallback<ZstdEncoderContext>);\n\n    JSG_NESTED_TYPE(ZlibStream);\n    JSG_NESTED_TYPE_NAMED(BrotliCompressionStream<BrotliEncoderContext>, BrotliEncoder);\n    JSG_NESTED_TYPE_NAMED(BrotliCompressionStream<BrotliDecoderContext>, BrotliDecoder);\n    JSG_NESTED_TYPE_NAMED(ZstdCompressionStream<ZstdEncoderContext>, ZstdEncoder);\n    JSG_NESTED_TYPE_NAMED(ZstdCompressionStream<ZstdDecoderContext>, ZstdDecoder);\n\n    // zlib.constants (part of the API contract for node:zlib)\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_NO_FLUSH, Z_NO_FLUSH);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_PARTIAL_FLUSH, Z_PARTIAL_FLUSH);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_SYNC_FLUSH, Z_SYNC_FLUSH);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_FULL_FLUSH, Z_FULL_FLUSH);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_FINISH, Z_FINISH);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_BLOCK, Z_BLOCK);\n\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_OK, Z_OK);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_STREAM_END, Z_STREAM_END);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_NEED_DICT, Z_NEED_DICT);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_ERRNO, Z_ERRNO);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_STREAM_ERROR, Z_STREAM_ERROR);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_DATA_ERROR, Z_DATA_ERROR);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_MEM_ERROR, Z_MEM_ERROR);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_BUF_ERROR, Z_BUF_ERROR);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_VERSION_ERROR, Z_VERSION_ERROR);\n\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_NO_COMPRESSION, Z_NO_COMPRESSION);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_BEST_SPEED, Z_BEST_SPEED);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_BEST_COMPRESSION, Z_BEST_COMPRESSION);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_DEFAULT_COMPRESSION, Z_DEFAULT_COMPRESSION);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_FILTERED, Z_FILTERED);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_HUFFMAN_ONLY, Z_HUFFMAN_ONLY);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_RLE, Z_RLE);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_FIXED, Z_FIXED);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_DEFAULT_STRATEGY, Z_DEFAULT_STRATEGY);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZLIB_VERNUM, ZLIB_VERNUM);\n\n    JSG_STATIC_CONSTANT_NAMED(CONST_DEFLATE, static_cast<ZlibModeValue>(ZlibMode::DEFLATE));\n    JSG_STATIC_CONSTANT_NAMED(CONST_INFLATE, static_cast<ZlibModeValue>(ZlibMode::INFLATE));\n    JSG_STATIC_CONSTANT_NAMED(CONST_GZIP, static_cast<ZlibModeValue>(ZlibMode::GZIP));\n    JSG_STATIC_CONSTANT_NAMED(CONST_GUNZIP, static_cast<ZlibModeValue>(ZlibMode::GUNZIP));\n    JSG_STATIC_CONSTANT_NAMED(CONST_DEFLATERAW, static_cast<ZlibModeValue>(ZlibMode::DEFLATERAW));\n    JSG_STATIC_CONSTANT_NAMED(CONST_INFLATERAW, static_cast<ZlibModeValue>(ZlibMode::INFLATERAW));\n    JSG_STATIC_CONSTANT_NAMED(CONST_UNZIP, static_cast<ZlibModeValue>(ZlibMode::UNZIP));\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODE, static_cast<ZlibModeValue>(ZlibMode::BROTLI_DECODE));\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_ENCODE, static_cast<ZlibModeValue>(ZlibMode::BROTLI_ENCODE));\n\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_MIN_WINDOWBITS, Z_MIN_WINDOWBITS);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_MAX_WINDOWBITS, Z_MAX_WINDOWBITS);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_DEFAULT_WINDOWBITS, Z_DEFAULT_WINDOWBITS);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_MIN_CHUNK, Z_MIN_CHUNK);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_MAX_CHUNK, Z_MAX_CHUNK);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_DEFAULT_CHUNK, Z_DEFAULT_CHUNK);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_MIN_MEMLEVEL, Z_MIN_MEMLEVEL);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_MAX_MEMLEVEL, Z_MAX_MEMLEVEL);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_DEFAULT_MEMLEVEL, Z_DEFAULT_MEMLEVEL);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_MIN_LEVEL, Z_MIN_LEVEL);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_MAX_LEVEL, Z_MAX_LEVEL);\n    JSG_STATIC_CONSTANT_NAMED(CONST_Z_DEFAULT_LEVEL, Z_DEFAULT_LEVEL);\n\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_PROCESS);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_OPERATION_FLUSH, BROTLI_OPERATION_FLUSH);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_OPERATION_FINISH, BROTLI_OPERATION_FINISH);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_OPERATION_EMIT_METADATA, BROTLI_OPERATION_EMIT_METADATA);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_PARAM_MODE, BROTLI_PARAM_MODE);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_MODE_GENERIC, BROTLI_MODE_GENERIC);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_MODE_TEXT, BROTLI_MODE_TEXT);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_MODE_FONT, BROTLI_MODE_FONT);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_DEFAULT_MODE, BROTLI_DEFAULT_MODE);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_PARAM_QUALITY, BROTLI_PARAM_QUALITY);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_MIN_QUALITY, BROTLI_MIN_QUALITY);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_MAX_QUALITY, BROTLI_MAX_QUALITY);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_QUALITY);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_PARAM_LGWIN, BROTLI_PARAM_LGWIN);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_MIN_WINDOW_BITS, BROTLI_MIN_WINDOW_BITS);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_MAX_WINDOW_BITS, BROTLI_MAX_WINDOW_BITS);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_LARGE_MAX_WINDOW_BITS, BROTLI_LARGE_MAX_WINDOW_BITS);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_WINDOW);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_PARAM_LGBLOCK, BROTLI_PARAM_LGBLOCK);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_MIN_INPUT_BLOCK_BITS, BROTLI_MIN_INPUT_BLOCK_BITS);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_MAX_INPUT_BLOCK_BITS, BROTLI_MAX_INPUT_BLOCK_BITS);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING,\n        BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_PARAM_SIZE_HINT, BROTLI_PARAM_SIZE_HINT);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_PARAM_LARGE_WINDOW, BROTLI_PARAM_LARGE_WINDOW);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_PARAM_NPOSTFIX, BROTLI_PARAM_NPOSTFIX);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_PARAM_NDIRECT, BROTLI_PARAM_NDIRECT);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_DECODER_RESULT_ERROR, BROTLI_DECODER_RESULT_ERROR);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_DECODER_RESULT_SUCCESS, BROTLI_DECODER_RESULT_SUCCESS);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT, BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT, BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION,\n        BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_PARAM_LARGE_WINDOW, BROTLI_DECODER_PARAM_LARGE_WINDOW);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_DECODER_NO_ERROR, BROTLI_DECODER_NO_ERROR);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_DECODER_SUCCESS, BROTLI_DECODER_SUCCESS);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_NEEDS_MORE_INPUT, BROTLI_DECODER_NEEDS_MORE_INPUT);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_NEEDS_MORE_OUTPUT, BROTLI_DECODER_NEEDS_MORE_OUTPUT);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE,\n        BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_ERROR_FORMAT_RESERVED, BROTLI_DECODER_ERROR_FORMAT_RESERVED);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE,\n        BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET,\n        BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME,\n        BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_ERROR_FORMAT_CL_SPACE, BROTLI_DECODER_ERROR_FORMAT_CL_SPACE);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE, BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT,\n        BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1,\n        BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2,\n        BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_ERROR_FORMAT_TRANSFORM, BROTLI_DECODER_ERROR_FORMAT_TRANSFORM);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_ERROR_FORMAT_DICTIONARY, BROTLI_DECODER_ERROR_FORMAT_DICTIONARY);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS, BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_ERROR_FORMAT_PADDING_1, BROTLI_DECODER_ERROR_FORMAT_PADDING_1);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_ERROR_FORMAT_PADDING_2, BROTLI_DECODER_ERROR_FORMAT_PADDING_2);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_ERROR_FORMAT_DISTANCE, BROTLI_DECODER_ERROR_FORMAT_DISTANCE);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET, BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_ERROR_INVALID_ARGUMENTS, BROTLI_DECODER_ERROR_INVALID_ARGUMENTS);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES, BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS, BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP, BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1, BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2, BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2);\n    JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES,\n        BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_BROTLI_DECODER_ERROR_UNREACHABLE, BROTLI_DECODER_ERROR_UNREACHABLE);\n\n    // Zstd mode constants\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_ENCODE, static_cast<ZlibModeValue>(ZlibMode::ZSTD_ENCODE));\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_DECODE, static_cast<ZlibModeValue>(ZlibMode::ZSTD_DECODE));\n\n    // Zstd flush directives\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_e_continue, ZSTD_e_continue);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_e_flush, ZSTD_e_flush);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_e_end, ZSTD_e_end);\n\n    // Zstd compression parameters\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_c_compressionLevel, ZSTD_c_compressionLevel);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_c_windowLog, ZSTD_c_windowLog);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_c_hashLog, ZSTD_c_hashLog);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_c_chainLog, ZSTD_c_chainLog);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_c_searchLog, ZSTD_c_searchLog);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_c_minMatch, ZSTD_c_minMatch);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_c_targetLength, ZSTD_c_targetLength);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_c_strategy, ZSTD_c_strategy);\n    JSG_STATIC_CONSTANT_NAMED(\n        CONST_ZSTD_c_enableLongDistanceMatching, ZSTD_c_enableLongDistanceMatching);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_c_ldmHashLog, ZSTD_c_ldmHashLog);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_c_ldmMinMatch, ZSTD_c_ldmMinMatch);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_c_ldmBucketSizeLog, ZSTD_c_ldmBucketSizeLog);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_c_ldmHashRateLog, ZSTD_c_ldmHashRateLog);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_c_contentSizeFlag, ZSTD_c_contentSizeFlag);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_c_checksumFlag, ZSTD_c_checksumFlag);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_c_dictIDFlag, ZSTD_c_dictIDFlag);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_c_nbWorkers, ZSTD_c_nbWorkers);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_c_jobSize, ZSTD_c_jobSize);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_c_overlapLog, ZSTD_c_overlapLog);\n\n    // Zstd decompression parameters\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_d_windowLogMax, ZSTD_d_windowLogMax);\n\n    // Zstd strategy constants\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_fast, ZSTD_fast);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_dfast, ZSTD_dfast);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_greedy, ZSTD_greedy);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_lazy, ZSTD_lazy);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_lazy2, ZSTD_lazy2);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_btlazy2, ZSTD_btlazy2);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_btopt, ZSTD_btopt);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_btultra, ZSTD_btultra);\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_btultra2, ZSTD_btultra2);\n\n    // Zstd default compression level\n    JSG_STATIC_CONSTANT_NAMED(CONST_ZSTD_CLEVEL_DEFAULT, ZSTD_CLEVEL_DEFAULT);\n  }\n};\n\n#define EW_NODE_ZLIB_ISOLATE_TYPES                                                                 \\\n  api::node::ZlibUtil, api::node::ZlibUtil::ZlibStream,                                            \\\n      api::node::ZlibUtil::BrotliCompressionStream<api::node::BrotliEncoderContext>,               \\\n      api::node::ZlibUtil::BrotliCompressionStream<api::node::BrotliDecoderContext>,               \\\n      api::node::ZlibUtil::ZstdCompressionStream<api::node::ZstdEncoderContext>,                   \\\n      api::node::ZlibUtil::ZstdCompressionStream<api::node::ZstdDecoderContext>,                   \\\n      api::node::ZlibUtil::CompressionStream<api::node::ZlibContext>,                              \\\n      api::node::ZlibUtil::CompressionStream<api::node::BrotliEncoderContext>,                     \\\n      api::node::ZlibUtil::CompressionStream<api::node::BrotliDecoderContext>,                     \\\n      api::node::ZlibUtil::CompressionStream<api::node::ZstdEncoderContext>,                       \\\n      api::node::ZlibUtil::CompressionStream<api::node::ZstdDecoderContext>,                       \\\n      api::node::ZlibContext::Options, api::node::BrotliContext::Options,                          \\\n      api::node::ZstdContext::Options\n\n}  // namespace workerd::api::node\n\nKJ_DECLARE_NON_POLYMORPHIC(BrotliEncoderStateStruct)\nKJ_DECLARE_NON_POLYMORPHIC(BrotliDecoderStateStruct)\nKJ_DECLARE_NON_POLYMORPHIC(ZSTD_CCtx)\nKJ_DECLARE_NON_POLYMORPHIC(ZSTD_DCtx)\n"
  },
  {
    "path": "src/workerd/api/performance.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"performance.h\"\n\n#include <workerd/io/io-util.h>\n#include <workerd/io/limit-enforcer.h>\n\n#include <kj/encoding.h>\n\nnamespace workerd::api {\n\ndouble Performance::now(jsg::Lock& js) {\n  // We define performance.now() for compatibility purposes, but due to Spectre concerns it\n  // returns exactly what Date.now() returns.\n  isolateLimitEnforcer.markPerfEvent(\"performance_now\"_kjc);\n  return dateNow();\n}\n\njsg::Ref<PerformanceMark> PerformanceMark::constructor(\n    jsg::Lock& js, kj::String name, jsg::Optional<Options> maybeOptions) {\n  auto options = kj::mv(maybeOptions).orDefault({});\n  return js.alloc<PerformanceMark>(\n      kj::mv(name), kj::mv(options.detail), options.startTime.orDefault(dateNow()));\n}\n\njsg::JsObject PerformanceMark::toJSON(jsg::Lock& js) {\n  auto obj = js.objNoProto();\n  obj.set(js, \"name\"_kj, js.str(name));\n  obj.set(js, \"entryType\"_kj, js.str(entryType));\n  obj.set(js, \"startTime\"_kj, js.num(startTime));\n  obj.set(js, \"duration\"_kj, js.num(duration));\n  KJ_IF_SOME(d, detail) {\n    obj.set(js, \"detail\"_kj, d.getHandle(js));\n  }\n  return kj::mv(obj);\n}\n\njsg::JsObject PerformanceMeasure::toJSON(jsg::Lock& js) {\n  auto obj = js.objNoProto();\n  obj.set(js, \"name\"_kj, js.str(name));\n  obj.set(js, \"entryType\"_kj, js.str(entryType));\n  obj.set(js, \"startTime\"_kj, js.num(startTime));\n  obj.set(js, \"duration\"_kj, js.num(duration));\n  KJ_IF_SOME(d, detail) {\n    obj.set(js, \"detail\"_kj, d.getHandle(js));\n  }\n  return kj::mv(obj);\n}\n\njsg::JsObject PerformanceEntry::toJSON(jsg::Lock& js) {\n  auto obj = js.objNoProto();\n  obj.set(js, \"name\"_kj, js.str(name));\n  obj.set(js, \"entryType\"_kj, js.str(entryType));\n  obj.set(js, \"startTime\"_kj, js.num(startTime));\n  obj.set(js, \"duration\"_kj, js.num(duration));\n  return kj::mv(obj);\n}\n\njsg::JsObject PerformanceResourceTiming::toJSON(jsg::Lock& js) {\n  JSG_FAIL_REQUIRE(Error, \"PerformanceResourceTiming.toJSON is not implemented\"_kj);\n}\n\njsg::JsObject PerformanceNodeTiming::toJSON(jsg::Lock& js) {\n  auto obj = js.objNoProto();\n  obj.set(js, \"name\"_kj, js.str(name));\n  obj.set(js, \"entryType\"_kj, js.str(entryType));\n  obj.set(js, \"startTime\"_kj, js.num(startTime));\n  obj.set(js, \"duration\"_kj, js.num(duration));\n  obj.set(js, \"nodeStart\"_kj, js.num(0));\n  obj.set(js, \"v8Start\"_kj, js.num(0));\n  obj.set(js, \"bootstrapComplete\"_kj, js.num(0));\n  obj.set(js, \"environment\"_kj, js.num(0));\n  obj.set(js, \"loopStart\"_kj, js.num(0));\n  obj.set(js, \"loopExit\"_kj, js.num(0));\n  obj.set(js, \"idleTime\"_kj, js.num(0));\n  // Include uvMetricsInfo in the JSON representation\n  auto uvObj = js.objNoProto();\n  uvObj.set(js, \"loopCount\"_kj, js.num(0));\n  uvObj.set(js, \"events\"_kj, js.num(0));\n  uvObj.set(js, \"eventsWaiting\"_kj, js.num(0));\n  obj.set(js, \"uvMetricsInfo\"_kj, uvObj);\n  return kj::mv(obj);\n}\n\nvoid Performance::clearMarks(jsg::Optional<kj::String> name) {\n  kj::Vector<jsg::Ref<PerformanceEntry>> filtered;\n\n  KJ_IF_SOME(n, name) {\n    for (auto& entry: entries) {\n      if (entry->getName() != n) {\n        filtered.add(kj::mv(entry));\n      }\n    }\n  } else {\n    for (auto& entry: entries) {\n      if (entry->getEntryType() != \"mark\") {\n        filtered.add(kj::mv(entry));\n      }\n    }\n  }\n\n  entries = filtered.releaseAsArray();\n}\n\nvoid Performance::clearMeasures(jsg::Optional<kj::String> name) {\n  kj::Vector<jsg::Ref<PerformanceEntry>> filtered;\n\n  KJ_IF_SOME(n, name) {\n    for (auto& entry: entries) {\n      if (entry->getName() != n) {\n        filtered.add(kj::mv(entry));\n      }\n    }\n  } else {\n    for (auto& entry: entries) {\n      if (entry->getEntryType() != \"measure\") {\n        filtered.add(kj::mv(entry));\n      }\n    }\n  }\n\n  entries = filtered.releaseAsArray();\n}\n\nvoid Performance::clearResourceTimings() {\n  kj::Vector<jsg::Ref<PerformanceEntry>> filtered;\n\n  // Remove entries where entryType is \"resource\" or \"navigation\"\n  for (auto& entry: entries) {\n    auto entryType = entry->getEntryType();\n    if (entryType != \"resource\"_kj && entryType != \"navigation\"_kj) {\n      filtered.add(kj::mv(entry));\n    }\n  }\n\n  entries = filtered.releaseAsArray();\n}\n\nkj::ArrayPtr<jsg::Ref<PerformanceEntry>> Performance::getEntries() {\n  return entries;\n}\n\nkj::Array<jsg::Ref<PerformanceEntry>> Performance::getEntriesByName(\n    kj::String name, jsg::Optional<kj::String> type) {\n  kj::Vector<jsg::Ref<PerformanceEntry>> filtered;\n\n  for (auto& entry: entries) {\n    if (entry->getName() == name) {\n      KJ_IF_SOME(t, type) {\n        if (entry->getEntryType() == t) {\n          filtered.add(entry.addRef());\n        }\n      } else {\n        filtered.add(entry.addRef());\n      }\n    }\n  }\n\n  return filtered.releaseAsArray();\n}\n\nkj::Array<jsg::Ref<PerformanceEntry>> Performance::getEntriesByType(kj::String type) {\n  kj::Vector<jsg::Ref<PerformanceEntry>> filtered;\n\n  for (auto& entry: entries) {\n    if (entry->getEntryType() == type) {\n      filtered.add(entry.addRef());\n    }\n  }\n\n  return filtered.releaseAsArray();\n}\n\nkj::ArrayPtr<jsg::Ref<PerformanceEntry>> PerformanceObserverEntryList::getEntries() {\n  return {};\n}\n\nkj::ArrayPtr<jsg::Ref<PerformanceEntry>> PerformanceObserverEntryList::getEntriesByType(\n    kj::String type) {\n  return {};\n}\n\nkj::ArrayPtr<jsg::Ref<PerformanceEntry>> PerformanceObserverEntryList::getEntriesByName(\n    kj::String name, jsg::Optional<kj::String> type) {\n  return {};\n}\n\njsg::Ref<PerformanceMark> Performance::mark(\n    jsg::Lock& js, kj::String name, jsg::Optional<PerformanceMark::Options> options) {\n  // TODO(someday): Include `name` in the perf event name?\n  isolateLimitEnforcer.markPerfEvent(\"performance_mark\"_kjc);\n  double startTime = dateNow();\n  KJ_IF_SOME(opts, options) {\n    KJ_IF_SOME(time, opts.startTime) {\n      startTime = time;\n    }\n  }\n\n  auto mark = js.alloc<PerformanceMark>(kj::mv(name), kj::none, startTime);\n\n  KJ_IF_SOME(opts, options) {\n    KJ_IF_SOME(d, opts.detail) {\n      mark->detail = kj::mv(d);\n    }\n  }\n\n  entries.add(mark.addRef());\n  return mark;\n}\n\njsg::Ref<PerformanceMeasure> Performance::measure(jsg::Lock& js,\n    kj::String measureName,\n    jsg::Optional<kj::OneOf<PerformanceMeasure::Options, kj::String>> measureOptionsOrStartMark,\n    jsg::Optional<kj::String> maybeEndMark) {\n  isolateLimitEnforcer.markPerfEvent(\"performance_measure\"_kjc);\n  // Default: measure from timeOrigin (0) to now, per the Web Performance API spec.\n  double startTime = 0;\n  double endTime = dateNow();\n\n  KJ_IF_SOME(startOrOptions, measureOptionsOrStartMark) {\n    KJ_SWITCH_ONEOF(startOrOptions) {\n      KJ_CASE_ONEOF(startMark, kj::String) {\n        auto startMarks = getEntriesByName(kj::str(startMark), kj::str(\"mark\"));\n        if (startMarks.size() > 0) {\n          startTime = startMarks[0]->getStartTime();\n        }\n\n        KJ_IF_SOME(endMark, maybeEndMark) {\n          auto endMarks = getEntriesByName(kj::str(endMark), kj::str(\"mark\"));\n          if (endMarks.size() > 0) {\n            endTime = endMarks[0]->getStartTime();\n          }\n        }\n      }\n      KJ_CASE_ONEOF(options, PerformanceMeasure::Options) {\n        KJ_IF_SOME(start, options.start) {\n          startTime = start;\n        }\n\n        KJ_IF_SOME(end, options.end) {\n          endTime = end;\n        } else KJ_IF_SOME(duration, options.duration) {\n          endTime = startTime + duration;\n        }\n      }\n    }\n  }\n\n  double duration = endTime >= startTime ? endTime - startTime : 0;\n  auto measure = js.alloc<PerformanceMeasure>(kj::mv(measureName), startTime, duration);\n\n  // Per the spec, detail defaults to null. Only set it if explicitly provided in options.\n  KJ_IF_SOME(startOrOptions, measureOptionsOrStartMark) {\n    KJ_SWITCH_ONEOF(startOrOptions) {\n      KJ_CASE_ONEOF(startMark, kj::String) {\n        // detail remains null when using string marks\n      }\n      KJ_CASE_ONEOF(options, PerformanceMeasure::Options) {\n        KJ_IF_SOME(d, options.detail) {\n          measure->detail = jsg::JsRef<jsg::JsObject>(js, d.getHandle(js));\n        }\n        // If options.detail is not provided, detail remains null\n      }\n    }\n  }\n\n  entries.add(measure.addRef());\n  return measure;\n}\n\nvoid Performance::setResourceTimingBufferSize(uint32_t size) {\n  // No-op stub for compatibility. Resource timing is not applicable in the Workers context,\n  // but we avoid throwing so that isomorphic code calling this defensively does not break.\n}\n\njsg::JsObject Performance::toJSON(jsg::Lock& js) {\n  auto obj = js.objNoProto();\n  obj.set(js, \"timeOrigin\"_kj, js.num(getTimeOrigin()));\n  return kj::mv(obj);\n}\n\njsg::Ref<PerformanceObserver> PerformanceObserver::constructor(jsg::Lock& js, Callback callback) {\n  return js.alloc<PerformanceObserver>(js, callback);\n}\n\nvoid PerformanceObserver::disconnect() {\n  // Leaving it as a no-op for now.\n}\n\nvoid PerformanceObserver::observe(jsg::Optional<ObserveOptions> options) {\n  // Leaving it as a no-op for now.\n}\n\nkj::Array<jsg::Ref<PerformanceEntry>> PerformanceObserver::takeRecords() {\n  return {};\n}\nkj::ArrayPtr<const kj::StringPtr> PerformanceObserver::getSupportedEntryTypes() {\n  return supportedEntryTypes.asPtr();\n}\n\nPerformance::EventLoopUtilization Performance::eventLoopUtilization() {\n  // Return stub values - actual event loop utilization metrics are not available in workerd.\n  // This provides compatibility with code that expects Node.js-style performance APIs.\n  return EventLoopUtilization{\n    .idle = 0,\n    .active = 0,\n    .utilization = 0,\n  };\n}\n\njsg::Ref<PerformanceNodeTiming> Performance::getNodeTiming(jsg::Lock& js) {\n  return js.alloc<PerformanceNodeTiming>();\n}\n\nvoid Performance::markResourceTiming() {\n  // No-op stub - resource timing is not applicable in the Workers context.\n  // This provides compatibility with code that expects this API to exist.\n}\n\njsg::Function<void()> Performance::timerify(jsg::Lock& js, jsg::Function<void()> fn) {\n  // We currently don't support timerify, so we just return the function as is.\n  return kj::mv(fn);\n}\n\njsg::Optional<uint32_t> EventCounts::get(kj::String eventType) {\n  KJ_IF_SOME(count, eventCounts.find(eventType)) {\n    return count;\n  }\n  return kj::none;\n}\n\nbool EventCounts::has(kj::String eventType) {\n  return eventCounts.find(eventType) != kj::none;\n}\n\nuint32_t EventCounts::getSize() {\n  return eventCounts.size();\n}\n\njsg::Ref<EventCounts::EntryIterator> EventCounts::entries(jsg::Lock& js) {\n  return jsg::alloc<EntryIterator>(IteratorState(JSG_THIS));\n}\n\nkj::Maybe<EventCounts::EntryIteratorType> EventCounts::entryIteratorNext(\n    jsg::Lock& js, IteratorState& state) {\n  if (state.index >= state.entries.size()) {\n    return kj::none;\n  }\n  auto& entry = state.entries[state.index++];\n  // Return [key, value] as an array\n  return kj::arr(kj::str(kj::get<0>(entry)), kj::str(kj::get<1>(entry)));\n}\n\njsg::Ref<EventCounts::KeyIterator> EventCounts::keys(jsg::Lock& js) {\n  return jsg::alloc<KeyIterator>(IteratorState(JSG_THIS));\n}\n\nkj::Maybe<EventCounts::KeyIteratorType> EventCounts::keyIteratorNext(\n    jsg::Lock& js, IteratorState& state) {\n  if (state.index >= state.entries.size()) {\n    return kj::none;\n  }\n  auto& entry = state.entries[state.index++];\n  return kj::str(kj::get<0>(entry));\n}\n\njsg::Ref<EventCounts::ValueIterator> EventCounts::values(jsg::Lock& js) {\n  return jsg::alloc<ValueIterator>(IteratorState(JSG_THIS));\n}\n\nkj::Maybe<EventCounts::ValueIteratorType> EventCounts::valueIteratorNext(\n    jsg::Lock& js, IteratorState& state) {\n  if (state.index >= state.entries.size()) {\n    return kj::none;\n  }\n  auto& entry = state.entries[state.index++];\n  return kj::get<1>(entry);\n}\n\nvoid EventCounts::forEach(jsg::Lock& js,\n    jsg::Function<void(uint32_t, kj::String, jsg::Ref<EventCounts>)> callback,\n    jsg::Optional<jsg::JsValue> thisArg) {\n  // Since we don't track events, this is effectively a no-op\n  for (auto& entry: eventCounts) {\n    callback(js, entry.value, kj::str(entry.key), JSG_THIS);\n  }\n}\n\njsg::Ref<EventCounts> Performance::getEventCounts(jsg::Lock& js) {\n  // Return a new EventCounts instance (currently empty as we don't track events)\n  return js.alloc<EventCounts>();\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/performance.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/api/basics.h>\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd {\nclass IsolateLimitEnforcer;\n};\n\nnamespace workerd::api {\n\n// ======================================================================================\n// Performance API\n// ======================================================================================\n//\n// This implementation provides a subset of the Performance API for compatibility with\n// other JavaScript runtimes. We are not intending to fully implement in-worker\n// performance-timing feedback as Cloudflare Workers run in a different context than\n// traditional browser or Node.js environments.\n//\n// The APIs here are primarily provided to support code portability and to prevent\n// runtime errors when code expects these standard APIs to exist.\n//\n// Specifications:\n// - W3C Performance Timeline: https://w3c.github.io/performance-timeline/\n// - W3C User Timing: https://w3c.github.io/user-timing/\n// - MDN Documentation: https://developer.mozilla.org/en-US/docs/Web/API/Performance_API\n//\n// Current limitations:\n// - No actual performance metrics collection within workers\n// - PerformanceObserver is provided but with minimal functionality\n// - Most entry types are not supported\n// - Timing data may not reflect actual worker execution characteristics\n\nclass PerformanceEntry: public jsg::Object {\n public:\n  PerformanceEntry(\n      kj::String name, kj::LiteralStringConst entryType, double startTime, double duration)\n      : name(kj::mv(name)),\n        entryType(kj::mv(entryType)),\n        startTime(startTime),\n        duration(duration) {}\n\n  kj::StringPtr getName() {\n    return name;\n  };\n  kj::StringPtr getEntryType() {\n    return entryType;\n  };\n  double getStartTime() {\n    return startTime;\n  };\n  double getDuration() {\n    return duration;\n  };\n\n  jsg::JsObject toJSON(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(PerformanceEntry) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(name, getName);\n    JSG_READONLY_PROTOTYPE_PROPERTY(entryType, getEntryType);\n    JSG_READONLY_PROTOTYPE_PROPERTY(startTime, getStartTime);\n    JSG_READONLY_PROTOTYPE_PROPERTY(duration, getDuration);\n    JSG_METHOD(toJSON);\n\n    JSG_TS_OVERRIDE({\n      toJSON(): object;\n    });\n  }\n\n protected:\n  kj::String name;\n  kj::LiteralStringConst entryType;\n  double startTime;\n  double duration;\n};\n\nclass PerformanceMark: public PerformanceEntry {\n public:\n  friend class Performance;\n\n  struct Options {\n    jsg::Optional<jsg::JsRef<jsg::JsObject>> detail;\n    jsg::Optional<double> startTime;\n\n    JSG_STRUCT(detail, startTime);\n  };\n\n  PerformanceMark(\n      kj::String name, jsg::Optional<jsg::JsRef<jsg::JsObject>> detail, double startTime)\n      : PerformanceEntry(kj::mv(name), \"mark\"_kjc, startTime, 0),\n        detail(kj::mv(detail)) {}\n\n  static jsg::Ref<PerformanceMark> constructor(\n      jsg::Lock& js, kj::String name, jsg::Optional<Options> maybeOptions);\n\n  jsg::JsValue getDetail(jsg::Lock& js) {\n    KJ_IF_SOME(d, detail) {\n      return d.getHandle(js);\n    }\n    return js.null();\n  }\n\n  jsg::JsObject toJSON(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(PerformanceMark) {\n    JSG_INHERIT(PerformanceEntry);\n    JSG_READONLY_PROTOTYPE_PROPERTY(detail, getDetail);\n    JSG_METHOD(toJSON);\n\n    JSG_TS_OVERRIDE({\n      toJSON(): object;\n    });\n  }\n\n private:\n  jsg::Optional<jsg::JsRef<jsg::JsObject>> detail;\n};\n\n// UvMetricsInfo represents libuv event loop metrics.\n// In workerd, this returns stub values since actual libuv metrics are not available.\nstruct UvMetricsInfo {\n  double loopCount;\n  double events;\n  double eventsWaiting;\n\n  JSG_STRUCT(loopCount, events, eventsWaiting);\n};\n\n// PerformanceNodeTiming provides Node.js-specific timing information.\n// In workerd, this returns stub values since actual Node.js startup metrics are not applicable.\n// This class is only exposed when the Node.js perf_hooks compat flag is enabled.\n//\n// Spec: https://nodejs.org/api/perf_hooks.html#class-performancenodetiming\nclass PerformanceNodeTiming: public PerformanceEntry {\n public:\n  PerformanceNodeTiming(): PerformanceEntry(kj::str(\"node\"), \"node\"_kjc, 0, 0) {}\n\n  static jsg::Ref<PerformanceNodeTiming> constructor() = delete;\n\n  // All timing values return 0 as stubs since Node.js startup metrics\n  // are not applicable in the Workers context.\n  double getNodeStart() {\n    return 0;\n  }\n  double getV8Start() {\n    return 0;\n  }\n  double getBootstrapComplete() {\n    return 0;\n  }\n  double getEnvironment() {\n    return 0;\n  }\n  double getLoopStart() {\n    return 0;\n  }\n  double getLoopExit() {\n    return 0;\n  }\n  double getIdleTime() {\n    return 0;\n  }\n\n  // uvMetricsInfo returns libuv event loop metrics.\n  // In workerd, this returns stub values since actual libuv metrics are not available.\n  // Spec: https://nodejs.org/api/perf_hooks.html#performancenodetiminguvmetricsinfo\n  UvMetricsInfo getUvMetricsInfo(jsg::Lock& js) {\n    return UvMetricsInfo{\n      .loopCount = 0,\n      .events = 0,\n      .eventsWaiting = 0,\n    };\n  }\n\n  jsg::JsObject toJSON(jsg::Lock& js);\n\n  // Node.js exposes these as instance properties (own properties on the object),\n  // not prototype properties. This matches Node.js behavior where:\n  //   Reflect.ownKeys(performance.nodeTiming) includes all these properties\n  //   Reflect.ownKeys(performance.nodeTiming.__proto__) only has constructor, toJSON\n  JSG_RESOURCE_TYPE(PerformanceNodeTiming) {\n    JSG_INHERIT(PerformanceEntry);\n    JSG_READONLY_INSTANCE_PROPERTY(nodeStart, getNodeStart);\n    JSG_READONLY_INSTANCE_PROPERTY(v8Start, getV8Start);\n    JSG_READONLY_INSTANCE_PROPERTY(bootstrapComplete, getBootstrapComplete);\n    JSG_READONLY_INSTANCE_PROPERTY(environment, getEnvironment);\n    JSG_READONLY_INSTANCE_PROPERTY(loopStart, getLoopStart);\n    JSG_READONLY_INSTANCE_PROPERTY(loopExit, getLoopExit);\n    JSG_READONLY_INSTANCE_PROPERTY(idleTime, getIdleTime);\n    JSG_READONLY_INSTANCE_PROPERTY(uvMetricsInfo, getUvMetricsInfo);\n    JSG_METHOD(toJSON);\n\n    JSG_TS_OVERRIDE({\n      toJSON(): object;\n    });\n  }\n};\n\nclass PerformanceMeasure: public PerformanceEntry {\n public:\n  PerformanceMeasure(kj::String name, double startTime, double duration)\n      : PerformanceEntry(kj::mv(name), \"measure\"_kjc, startTime, duration) {}\n\n  friend class Performance;\n\n  struct Entry {\n    kj::String entryType;\n    kj::String name;\n    kj::OneOf<kj::Date, double> startTime;\n    double duration;\n    jsg::Optional<jsg::JsRef<jsg::JsObject>> detail;\n\n    JSG_STRUCT(entryType, name, startTime, duration, detail);\n  };\n\n  struct Options {\n    jsg::Optional<jsg::JsRef<jsg::JsObject>> detail;\n    jsg::Optional<double> start;\n    jsg::Optional<double> duration;\n    jsg::Optional<double> end;\n\n    JSG_STRUCT(detail, start, duration, end);\n  };\n\n  jsg::JsValue getDetail(jsg::Lock& js) {\n    KJ_IF_SOME(d, detail) {\n      return d.getHandle(js);\n    }\n    return js.null();\n  }\n\n  jsg::JsObject toJSON(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(PerformanceMeasure) {\n    JSG_INHERIT(PerformanceEntry);\n    JSG_READONLY_PROTOTYPE_PROPERTY(detail, getDetail);\n    JSG_METHOD(toJSON);\n\n    JSG_TS_OVERRIDE({\n      toJSON(): object;\n    });\n  }\n\n private:\n  jsg::Optional<jsg::JsRef<jsg::JsObject>> detail;\n};\n\nclass PerformanceResourceTiming: public PerformanceEntry {\n public:\n  PerformanceResourceTiming(kj::String name, double startTime, double duration)\n      : PerformanceEntry(kj::mv(name), \"resource\"_kjc, startTime, duration) {}\n\n  uint32_t getConnectEnd() {\n    return 0;\n  }\n  uint32_t getConnectStart() {\n    return 0;\n  }\n  uint32_t getDecodedBodySize() {\n    return 0;\n  }\n  uint32_t getDomainLookupEnd() {\n    return 0;\n  }\n  uint32_t getDomainLookupStart() {\n    return 0;\n  }\n  uint32_t getEncodedBodySize() {\n    return 0;\n  }\n  uint32_t getFetchStart() {\n    return 0;\n  }\n  jsg::JsString getInitiatorType(jsg::Lock& js) {\n    return js.str();\n  }\n  jsg::JsString getNextHopProtocol(jsg::Lock& js) {\n    return js.str();\n  }\n  uint32_t getRedirectEnd() {\n    return 0;\n  }\n  uint32_t getRedirectStart() {\n    return 0;\n  }\n  uint32_t getRequestStart() {\n    return 0;\n  }\n  uint32_t getResponseEnd() {\n    return 0;\n  }\n  uint32_t getResponseStart() {\n    return 0;\n  }\n  uint32_t getResponseStatus() {\n    return 0;\n  }\n  jsg::Optional<uint32_t> getSecureConnectionStart() {\n    return kj::none;\n  }\n  uint32_t getTransferSize() {\n    return 0;\n  }\n  uint32_t getWorkerStart() {\n    return 0;\n  }\n\n  jsg::JsObject toJSON(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(PerformanceResourceTiming) {\n    JSG_INHERIT(PerformanceEntry);\n    JSG_READONLY_PROTOTYPE_PROPERTY(connectEnd, getConnectEnd);\n    JSG_READONLY_PROTOTYPE_PROPERTY(connectStart, getConnectStart);\n    JSG_READONLY_PROTOTYPE_PROPERTY(decodedBodySize, getDecodedBodySize);\n    JSG_READONLY_PROTOTYPE_PROPERTY(domainLookupEnd, getDomainLookupEnd);\n    JSG_READONLY_PROTOTYPE_PROPERTY(domainLookupStart, getDomainLookupStart);\n    JSG_READONLY_PROTOTYPE_PROPERTY(encodedBodySize, getEncodedBodySize);\n    JSG_READONLY_PROTOTYPE_PROPERTY(fetchStart, getFetchStart);\n    JSG_READONLY_PROTOTYPE_PROPERTY(initiatorType, getInitiatorType);\n    JSG_READONLY_PROTOTYPE_PROPERTY(nextHopProtocol, getNextHopProtocol);\n    JSG_READONLY_PROTOTYPE_PROPERTY(redirectEnd, getRedirectEnd);\n    JSG_READONLY_PROTOTYPE_PROPERTY(redirectStart, getRedirectStart);\n    JSG_READONLY_PROTOTYPE_PROPERTY(requestStart, getRequestStart);\n    JSG_READONLY_PROTOTYPE_PROPERTY(responseEnd, getResponseEnd);\n    JSG_READONLY_PROTOTYPE_PROPERTY(responseStart, getResponseStart);\n    JSG_READONLY_PROTOTYPE_PROPERTY(responseStatus, getResponseStatus);\n    JSG_READONLY_PROTOTYPE_PROPERTY(secureConnectionStart, getSecureConnectionStart);\n    JSG_READONLY_PROTOTYPE_PROPERTY(transferSize, getTransferSize);\n    JSG_READONLY_PROTOTYPE_PROPERTY(workerStart, getWorkerStart);\n  }\n};\n\nclass PerformanceObserverEntryList: public jsg::Object {\n public:\n  kj::ArrayPtr<jsg::Ref<PerformanceEntry>> getEntries();\n  kj::ArrayPtr<jsg::Ref<PerformanceEntry>> getEntriesByType(kj::String type);\n  kj::ArrayPtr<jsg::Ref<PerformanceEntry>> getEntriesByName(\n      kj::String name, jsg::Optional<kj::String> type);\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    // No managed objects to visit currently\n  }\n\n  JSG_RESOURCE_TYPE(PerformanceObserverEntryList) {\n    JSG_METHOD(getEntries);\n    JSG_METHOD(getEntriesByType);\n    JSG_METHOD(getEntriesByName);\n  }\n};\n\n// PerformanceObserver provides a way to observe performance timeline entries.\n// This is a minimal implementation for compatibility purposes.\n//\n// Spec: https://w3c.github.io/performance-timeline/#the-performanceobserver-interface\n// MDN: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver\n//\n// Note: In the Workers environment, this observer will not receive most performance\n// entries as we don't track detailed performance metrics within workers. The API\n// is provided mainly for compatibility with code that expects it to exist.\nclass PerformanceObserver: public jsg::Object {\n public:\n  struct CallbackOptions {\n    jsg::Optional<uint32_t> droppedEntriesCount;\n    JSG_STRUCT(droppedEntriesCount);\n  };\n\n  using Callback = jsg::JsValue;\n\n  static jsg::Ref<PerformanceObserver> constructor(jsg::Lock& js, Callback callback);\n\n  PerformanceObserver(jsg::Lock& js, Callback callback): callback(callback.addRef(js)) {}\n\n  struct ObserveOptions {\n    jsg::Optional<bool> buffered;\n    jsg::Optional<uint32_t> durationThreshold = 104;\n    jsg::Optional<kj::Array<kj::String>> entryTypes;\n    jsg::Optional<kj::String> type;\n\n    JSG_STRUCT(buffered, durationThreshold, entryTypes, type);\n  };\n  void disconnect();\n  void observe(jsg::Optional<ObserveOptions> options);\n  kj::Array<jsg::Ref<PerformanceEntry>> takeRecords();\n  // Spec: https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute\n  static kj::ArrayPtr<const kj::StringPtr> getSupportedEntryTypes();\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(callback);\n  }\n\n  JSG_RESOURCE_TYPE(PerformanceObserver) {\n    JSG_METHOD(disconnect);\n    JSG_METHOD(observe);\n    JSG_METHOD(takeRecords);\n    JSG_STATIC_READONLY_PROPERTY_NAMED(supportedEntryTypes, getSupportedEntryTypes);\n  }\n\n private:\n  jsg::JsRef<jsg::JsValue> callback;\n\n  static constexpr kj::FixedArray<kj::StringPtr, 2> supportedEntryTypes = []() consteval {\n    // We have mark and measure as supported because we implement relevant methods.\n    kj::FixedArray<kj::StringPtr, 2> out;\n    out[0] = \"measure\"_kj;\n    out[1] = \"mark\"_kj;\n    return kj::mv(out);\n  }();\n};\n\n// EventCounts provides a read-only map of event counts per event type.\n// This is a minimal implementation for compatibility with the EventCounts API.\n//\n// Spec: https://w3c.github.io/event-timing/#eventcounts\n// MDN: https://developer.mozilla.org/en-US/docs/Web/API/EventCounts\n//\n// The EventCounts interface is a read-only map-like object where:\n// - Keys are event type strings (e.g., \"click\", \"keydown\")\n// - Values are the number of events dispatched for that type\n// - It doesn't have clear(), delete(), or set() methods\nclass EventCounts: public jsg::Object {\n public:\n  EventCounts() = default;\n\n  jsg::Optional<uint32_t> get(kj::String eventType);\n  bool has(kj::String eventType);\n  uint32_t getSize();\n\n  struct IteratorState {\n    jsg::Ref<EventCounts> parent;\n    size_t index = 0;\n    kj::Vector<kj::Tuple<kj::String, uint32_t>> entries;\n\n    IteratorState(jsg::Ref<EventCounts> parent): parent(kj::mv(parent)) {\n      // Copy the entries from the map into our vector for stable iteration\n      // Check if the map has any entries before iterating\n      if (this->parent->eventCounts.size() > 0) {\n        for (auto& entry: this->parent->eventCounts) {\n          entries.add(kj::tuple(kj::str(entry.key), entry.value));\n        }\n      }\n    }\n\n    void visitForGc(jsg::GcVisitor& visitor) {\n      visitor.visit(parent);\n    }\n  };\n\n  using EntryIteratorType = kj::Array<kj::String>;\n  using KeyIteratorType = kj::String;\n  using ValueIteratorType = uint32_t;\n\n  JSG_ITERATOR(EntryIterator, entries, EntryIteratorType, IteratorState, entryIteratorNext)\n  JSG_ITERATOR(KeyIterator, keys, KeyIteratorType, IteratorState, keyIteratorNext)\n  JSG_ITERATOR(ValueIterator, values, ValueIteratorType, IteratorState, valueIteratorNext)\n\n  void forEach(jsg::Lock& js,\n      jsg::Function<void(uint32_t, kj::String, jsg::Ref<EventCounts>)>,\n      jsg::Optional<jsg::JsValue>);\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    // No managed objects to visit currently\n  }\n\n  JSG_RESOURCE_TYPE(EventCounts) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(size, getSize);\n    JSG_METHOD(get);\n    JSG_METHOD(has);\n    JSG_METHOD(entries);\n    JSG_METHOD(keys);\n    JSG_METHOD(values);\n    JSG_METHOD(forEach);\n    JSG_ITERABLE(entries);\n  }\n\n private:\n  // Static iterator next methods\n  static kj::Maybe<EntryIteratorType> entryIteratorNext(jsg::Lock& js, IteratorState& state);\n  static kj::Maybe<KeyIteratorType> keyIteratorNext(jsg::Lock& js, IteratorState& state);\n  static kj::Maybe<ValueIteratorType> valueIteratorNext(jsg::Lock& js, IteratorState& state);\n\n  // For now, we keep this empty as we don't actually track events in the worker context.\n  // This can be extended in the future to store actual event counts.\n  kj::HashMap<kj::String, uint32_t> eventCounts;\n\n  friend struct IteratorState;\n};\n\n// Performance provides timing-related functionality and performance metrics.\n// This is a minimal implementation focused on compatibility rather than providing\n// detailed performance insights within the Workers environment.\n//\n// Spec: https://w3c.github.io/hr-time/#the-performance-interface\n// MDN: https://developer.mozilla.org/en-US/docs/Web/API/Performance\n//\n// Key limitations in Workers:\n// - performance.now() returns the same precision as Date.now() for security reasons\n// - Most performance entry types are not supported\n// - Resource timing and navigation timing are not applicable in the Workers context\n// - User timing (marks and measures) have limited implementation\nclass Performance: public EventTarget {\n public:\n  static jsg::Ref<Performance> constructor() = delete;\n\n  explicit Performance(const IsolateLimitEnforcer& isolateLimitEnforcer)\n      : isolateLimitEnforcer(isolateLimitEnforcer) {}\n\n  // We always return a time origin of 0, making performance.now() equivalent to Date.now(). There\n  // is no other appropriate time origin to use given that the Worker platform is intended to be\n  // treated like one big computer rather than many individual instances. In particular, if and\n  // when we start snapshotting applications after startup and then starting instances from that\n  // snapshot, what would the right time origin be? The time when the snapshot was created? This\n  // seems to leak implementation details in a weird way.\n  //\n  // Note that the purpose of `timeOrigin` is normally to allow `now()` to return a more-precise\n  // measurement. Measuring against a recent time allows the values returned by `now()` to be\n  // smaller in magnitude, which alzlows them to be more precise due to the nature of floating\n  // point numbers. In our case, though, we don't return precise measurements from this interface\n  // anyway, for Spectre reasons -- it returns the same as Date.now().\n  double getTimeOrigin() {\n    return 0.0;\n  }\n\n  jsg::Ref<EventCounts> getEventCounts(jsg::Lock& js);\n\n  double now(jsg::Lock& js);\n\n  void clearMarks(jsg::Optional<kj::String> name);\n  void clearMeasures(jsg::Optional<kj::String> name);\n  void clearResourceTimings();\n  kj::ArrayPtr<jsg::Ref<PerformanceEntry>> getEntries();\n  kj::Array<jsg::Ref<PerformanceEntry>> getEntriesByName(\n      kj::String name, jsg::Optional<kj::String> type);\n  kj::Array<jsg::Ref<PerformanceEntry>> getEntriesByType(kj::String type);\n  jsg::Ref<PerformanceMark> mark(\n      jsg::Lock& js, kj::String name, jsg::Optional<PerformanceMark::Options> options);\n\n  // Following signatures are supported:\n  // - measure(measureName)\n  // - measure(measureName, startMark)\n  // - measure(measureName, startMark, endMark)\n  // - measure(measureName, measureOptions)\n  // - measure(measureName, measureOptions, endMark)\n  jsg::Ref<PerformanceMeasure> measure(jsg::Lock& js,\n      kj::String measureName,\n      jsg::Optional<kj::OneOf<PerformanceMeasure::Options, kj::String>> measureOptionsOrStartMark =\n          kj::none,\n      jsg::Optional<kj::String> maybeEndMark = kj::none);\n\n  void setResourceTimingBufferSize(uint32_t size);\n\n  jsg::JsObject toJSON(jsg::Lock& js);\n\n  // Node.js-specific performance extensions.\n  // These are provided as stubs for compatibility with code that expects Node.js APIs.\n\n  // EventLoopUtilization represents the utilization of the event loop.\n  // In workerd, we return stub values since actual event loop metrics are not available.\n  struct EventLoopUtilization {\n    double idle = 0;\n    double active = 0;\n    double utilization = 0;\n\n    JSG_STRUCT(idle, active, utilization);\n  };\n\n  EventLoopUtilization eventLoopUtilization();\n\n  // Returns the PerformanceNodeTiming object containing Node.js-specific timing metrics.\n  // In workerd, this returns stub values since Node.js startup metrics are not applicable.\n  jsg::Ref<PerformanceNodeTiming> getNodeTiming(jsg::Lock& js);\n\n  // In the browser, this function is not public. However, it must be used inside fetch\n  // which is a Node.js dependency, not an internal module.\n  // Returns void as a no-op stub since resource timing is not applicable in Workers.\n  void markResourceTiming();\n  jsg::Function<void()> timerify(jsg::Lock& js, jsg::Function<void()> fn);\n\n  JSG_RESOURCE_TYPE(Performance, CompatibilityFlags::Reader flags) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(timeOrigin, getTimeOrigin);\n    JSG_METHOD(now);\n\n    // The following are provided as non-ops to ensure availability\n    // of the APIS but we are currently not planning to provide\n    // performance timing feedback within a worker using these\n    // apis.\n    if (flags.getEnableGlobalPerformanceClasses() || flags.getEnableNodeJsPerfHooksModule()) {\n      JSG_INHERIT(EventTarget);\n      JSG_READONLY_PROTOTYPE_PROPERTY(eventCounts, getEventCounts);\n      JSG_METHOD(clearMarks);\n      JSG_METHOD(clearMeasures);\n      JSG_METHOD(clearResourceTimings);\n      JSG_METHOD(getEntries);\n      JSG_METHOD(getEntriesByName);\n      JSG_METHOD(getEntriesByType);\n      JSG_METHOD(mark);\n      JSG_METHOD(measure);\n      JSG_METHOD(setResourceTimingBufferSize);\n      JSG_METHOD(toJSON);\n    }\n\n    if (flags.getEnableNodeJsPerfHooksModule()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(nodeTiming, getNodeTiming);\n      JSG_METHOD(eventLoopUtilization);\n      JSG_METHOD(markResourceTiming);\n      JSG_METHOD(timerify);\n    }\n\n    JSG_TS_OVERRIDE({\n      toJSON(): object;\n    });\n  }\n\n private:\n  const IsolateLimitEnforcer& isolateLimitEnforcer;\n  kj::Vector<jsg::Ref<PerformanceEntry>> entries;\n};\n\n#define EW_PERFORMANCE_ISOLATE_TYPES                                                               \\\n  api::Performance, api::Performance::EventLoopUtilization, api::PerformanceNodeTiming,            \\\n      api::UvMetricsInfo, api::PerformanceMark, api::PerformanceMeasure,                           \\\n      api::PerformanceMark::Options, api::PerformanceMeasure::Options,                             \\\n      api::PerformanceMeasure::Entry, api::PerformanceObserverEntryList, api::PerformanceEntry,    \\\n      api::PerformanceResourceTiming, api::PerformanceObserver,                                    \\\n      api::PerformanceObserver::ObserveOptions, api::PerformanceObserver::CallbackOptions,         \\\n      api::EventCounts, api::EventCounts::EntryIterator, api::EventCounts::EntryIterator::Next,    \\\n      api::EventCounts::KeyIterator, api::EventCounts::KeyIterator::Next,                          \\\n      api::EventCounts::ValueIterator, api::EventCounts::ValueIterator::Next\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/pyodide/pyodide-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"pyodide.h\"\n\n#include <kj/test.h>\n\nnamespace workerd::api {\nnamespace {\n\nKJ_TEST(\"getPythonSnapshotRelease\") {\n  capnp::MallocMessageBuilder arena;\n  // TODO(beta): Factor out FeatureFlags from WorkerBundle.\n  auto featureFlags = arena.initRoot<CompatibilityFlags>();\n\n  {\n    auto res = getPythonSnapshotRelease(featureFlags);\n    KJ_ASSERT(res == kj::none);\n  }\n\n  featureFlags.setPythonWorkers(true);\n  {\n    auto res = KJ_ASSERT_NONNULL(getPythonSnapshotRelease(featureFlags));\n    KJ_ASSERT(res.getPyodide() == \"0.26.0a2\");\n    KJ_ASSERT(res.getFlagName() == \"pythonWorkers\");\n  }\n\n  featureFlags.setPythonWorkersDevPyodide(true);\n  {\n    auto res = KJ_ASSERT_NONNULL(getPythonSnapshotRelease(featureFlags));\n    KJ_ASSERT(res.getPyodide() == \"dev\");\n    KJ_ASSERT(res.getFlagName() == \"pythonWorkersDevPyodide\");\n  }\n\n  featureFlags.setPythonWorkers(false);\n  {\n    auto res = KJ_ASSERT_NONNULL(getPythonSnapshotRelease(featureFlags));\n    KJ_ASSERT(res.getPyodide() == \"dev\");\n    KJ_ASSERT(res.getFlagName() == \"pythonWorkersDevPyodide\");\n  }\n\n  featureFlags.setPythonWorkers20250116(true);\n  {\n    auto res = KJ_ASSERT_NONNULL(getPythonSnapshotRelease(featureFlags));\n    KJ_ASSERT(res.getPyodide() == \"0.28.2\");\n    KJ_ASSERT(res.getFlagName() == \"pythonWorkers20250116\");\n  }\n\n  featureFlags.setPythonWorkersDevPyodide(false);\n  {\n    auto res = KJ_ASSERT_NONNULL(getPythonSnapshotRelease(featureFlags));\n    KJ_ASSERT(res.getPyodide() == \"0.28.2\");\n    KJ_ASSERT(res.getFlagName() == \"pythonWorkers20250116\");\n  }\n}\n\nKJ_TEST(\"basic `import` tests\") {\n  auto files = kj::heapArrayBuilder<kj::String>(2);\n  files.add(kj::str(\"import a\\nimport z\"));\n  files.add(kj::str(\"import b\"));\n  auto result = pyodide::PythonModuleInfo::parsePythonScriptImports(files.finish());\n  KJ_REQUIRE(result.size() == 3);\n  KJ_REQUIRE(result[0] == \"a\");\n  KJ_REQUIRE(result[1] == \"z\");\n  KJ_REQUIRE(result[2] == \"b\");\n}\n\nKJ_TEST(\"supports whitespace\") {\n  auto files = kj::heapArrayBuilder<kj::String>(1);\n  files.add(kj::str(\"import      a\\nimport    \\n\\tz\"));\n  auto result = pyodide::PythonModuleInfo::parsePythonScriptImports(files.finish());\n  KJ_REQUIRE(result.size() == 2);\n  KJ_REQUIRE(result[0] == \"a\");\n  KJ_REQUIRE(result[1] == \"z\");\n}\n\nKJ_TEST(\"supports windows newlines\") {\n  auto files = kj::heapArrayBuilder<kj::String>(1);\n  files.add(kj::str(\"import      a\\r\\nimport    \\r\\n\\tz\"));\n  auto result = pyodide::PythonModuleInfo::parsePythonScriptImports(files.finish());\n  KJ_REQUIRE(result.size() == 2);\n  KJ_REQUIRE(result[0] == \"a\");\n  KJ_REQUIRE(result[1] == \"z\");\n}\n\nKJ_TEST(\"basic `from` test\") {\n  auto files = kj::heapArrayBuilder<kj::String>(1);\n  files.add(kj::str(\"from x import a,b\\nfrom z import y\"));\n  auto result = pyodide::PythonModuleInfo::parsePythonScriptImports(files.finish());\n  KJ_REQUIRE(result.size() == 2);\n  KJ_REQUIRE(result[0] == \"x\");\n  KJ_REQUIRE(result[1] == \"z\");\n}\n\nKJ_TEST(\"ignores indented blocks\") {\n  auto files = kj::heapArrayBuilder<kj::String>(1);\n  files.add(kj::str(\"import a\\nif True:\\n  import x\\nimport y\"));\n  auto result = pyodide::PythonModuleInfo::parsePythonScriptImports(files.finish());\n  KJ_REQUIRE(result.size() == 2);\n  KJ_REQUIRE(result[0] == \"a\");\n  KJ_REQUIRE(result[1] == \"y\");\n}\n\nKJ_TEST(\"supports nested imports\") {\n  auto files = kj::heapArrayBuilder<kj::String>(1);\n  files.add(kj::str(\"import a.b\\nimport z.x.y.i\"));\n  auto result = pyodide::PythonModuleInfo::parsePythonScriptImports(files.finish());\n  KJ_REQUIRE(result.size() == 2);\n  KJ_REQUIRE(result[0] == \"a.b\");\n  KJ_REQUIRE(result[1] == \"z.x.y.i\");\n}\n\nKJ_TEST(\"nested `from` test\") {\n  auto files = kj::heapArrayBuilder<kj::String>(1);\n  files.add(kj::str(\"from x.y.z import a,b\\nfrom z import y\"));\n  auto result = pyodide::PythonModuleInfo::parsePythonScriptImports(files.finish());\n  KJ_REQUIRE(result.size() == 2);\n  KJ_REQUIRE(result[0] == \"x.y.z\");\n  KJ_REQUIRE(result[1] == \"z\");\n}\n\nKJ_TEST(\"ignores trailing period\") {\n  auto files = kj::heapArrayBuilder<kj::String>(1);\n  files.add(kj::str(\"import a.b.\\nimport z.x.y.i.\"));\n  auto result = pyodide::PythonModuleInfo::parsePythonScriptImports(files.finish());\n  KJ_REQUIRE(result.size() == 0);\n}\n\nKJ_TEST(\"ignores relative import\") {\n  // This is where we diverge from the old AST-based approach. It would have returned `y` in the\n  // input below.\n  auto files = kj::heapArrayBuilder<kj::String>(1);\n  files.add(kj::str(\"import .a.b\\nimport ..z.x\\nfrom .y import x\"));\n  auto result = pyodide::PythonModuleInfo::parsePythonScriptImports(files.finish());\n  KJ_REQUIRE(result.size() == 0);\n}\n\nKJ_TEST(\"supports commas\") {\n  auto files = kj::heapArrayBuilder<kj::String>(1);\n  files.add(kj::str(\"import a,b\"));\n  auto result = pyodide::PythonModuleInfo::parsePythonScriptImports(files.finish());\n  KJ_REQUIRE(result.size() == 2);\n  KJ_REQUIRE(result[0] == \"a\");\n  KJ_REQUIRE(result[1] == \"b\");\n}\n\nKJ_TEST(\"supports backslash\") {\n  auto files = kj::heapArrayBuilder<kj::String>(4);\n  files.add(kj::str(\"import a\\\\\\n,b\"));\n  files.add(kj::str(\"import\\\\\\n q,w\"));\n  files.add(kj::str(\"from \\\\\\nx import y\"));\n  files.add(kj::str(\"from \\\\\\n   c import y\"));\n  auto result = pyodide::PythonModuleInfo::parsePythonScriptImports(files.finish());\n  KJ_REQUIRE(result.size() == 6);\n  KJ_REQUIRE(result[0] == \"a\");\n  KJ_REQUIRE(result[1] == \"b\");\n  KJ_REQUIRE(result[2] == \"q\");\n  KJ_REQUIRE(result[3] == \"w\");\n  KJ_REQUIRE(result[4] == \"x\");\n  KJ_REQUIRE(result[5] == \"c\");\n}\n\nKJ_TEST(\"multiline-strings ignored\") {\n  auto files = kj::heapArrayBuilder<kj::String>(4);\n  files.add(kj::str(R\"SCRIPT(\nFOO=\"\"\"\nimport x\nfrom y import z\n\"\"\"\n)SCRIPT\"));\n  files.add(kj::str(R\"SCRIPT(\nFOO='''\nimport f\nfrom g import z\n'''\n)SCRIPT\"));\n  files.add(kj::str(R\"SCRIPT(FOO = \"\\\nimport b \\\n\")SCRIPT\"));\n  files.add(kj::str(\"FOO=\\\"\\\"\\\"  \\n\", R\"SCRIPT(import x\nfrom y import z\n\"\"\")SCRIPT\"));\n  auto result = pyodide::PythonModuleInfo::parsePythonScriptImports(files.finish());\n  KJ_REQUIRE(result.size() == 0);\n}\n\nKJ_TEST(\"multiline-strings with imports in-between\") {\n  auto files = kj::heapArrayBuilder<kj::String>(1);\n  files.add(kj::str(\n      R\"SCRIPT(FOO=\"\"\"\nimport x\nfrom y import z\n\"\"\"import q\nimport w\nBAR=\"\"\"\nimport e\n\"\"\"\nfrom t import u)SCRIPT\"));\n  auto result = pyodide::PythonModuleInfo::parsePythonScriptImports(files.finish());\n  KJ_REQUIRE(result.size() == 2);\n  KJ_REQUIRE(result[0] == \"w\");\n  KJ_REQUIRE(result[1] == \"t\");\n}\n\nKJ_TEST(\"import after string literal\") {\n  auto files = kj::heapArrayBuilder<kj::String>(1);\n  files.add(kj::str(R\"SCRIPT(import a\n\"import b)SCRIPT\"));\n  auto result = pyodide::PythonModuleInfo::parsePythonScriptImports(files.finish());\n  KJ_REQUIRE(result.size() == 1);\n  KJ_REQUIRE(result[0] == \"a\");\n}\n\nKJ_TEST(\"import after `i`\") {\n  auto files = kj::heapArrayBuilder<kj::String>(1);\n  files.add(kj::str(R\"SCRIPT(import a\niimport b)SCRIPT\"));\n  auto result = pyodide::PythonModuleInfo::parsePythonScriptImports(files.finish());\n  KJ_REQUIRE(result.size() == 1);\n  KJ_REQUIRE(result[0] == \"a\");\n}\n\nKJ_TEST(\"langchain import\") {\n  auto files = kj::heapArrayBuilder<kj::String>(1);\n  files.add(kj::str(R\"SCRIPT(from js import Response, console, URL\nfrom langchain.chat_models import ChatOpenAI\nimport openai)SCRIPT\"));\n  auto result = pyodide::PythonModuleInfo::parsePythonScriptImports(files.finish());\n  KJ_REQUIRE(result.size() == 3);\n  KJ_REQUIRE(result[0] == \"js\");\n  KJ_REQUIRE(result[1] == \"langchain.chat_models\");\n  KJ_REQUIRE(result[2] == \"openai\");\n}\n\nKJ_TEST(\"quote in multiline string\") {\n  auto files = kj::heapArrayBuilder<kj::String>(1);\n  files.add(kj::str(R\"SCRIPT(temp = \"\"\"\nw[\"h\n\"\"\")SCRIPT\"));\n  auto result = pyodide::PythonModuleInfo::parsePythonScriptImports(files.finish());\n  KJ_REQUIRE(result.size() == 0);\n}\n\nusing pyodide::PythonModuleInfo;\n\ntemplate <typename... Params>\nkj::Array<kj::String> strArray(Params&&... params) {\n  return kj::arr(kj::str(params)...);\n}\n\ntemplate <typename... Params>\nkj::Array<kj::Array<kj::byte>> bytesArray(Params&&... params) {\n  return kj::arr(kj::heapArray<kj::byte>(kj::str(params).asBytes())...);\n}\n\ntemplate <typename... Params>\nkj::HashSet<kj::String> strSet(Params&&... params) {\n  auto array = strArray(params...);\n  kj::HashSet<kj::String> set;\n  for (auto& str: array) {\n    set.insert(kj::mv(str));\n  }\n  return set;\n}\n\nKJ_TEST(\"basic test of getPackageSnapshotImports\") {\n  auto a = pyodide::PythonModuleInfo(strArray(\"a.py\"),\n      bytesArray(\"from js import Response\\n\"\n                 \"import asyncio\\n\"\n                 \"import numbers\\n\"\n                 \"def on_fetch(request):\\n\"\n                 \"  return Response.new('Hello')\\n\"));\n  auto result = a.getPackageSnapshotImports(\"0.26.0a2\");\n  KJ_REQUIRE(result.size() == 1);\n  KJ_REQUIRE(result[0] == \"numbers\");\n};\n\nKJ_TEST(\"basic test of getPackageSnapshotImports user module\") {\n  auto a = pyodide::PythonModuleInfo(strArray(\"a.py\", \"numbers.py\"),\n      bytesArray(\"from js import Response\\n\"\n                 \"import asyncio\\n\"\n                 \"import numbers\\n\"\n                 \"def on_fetch(request):\\n\"\n                 \"  return Response.new('Hello')\\n\",\n          \"\"));\n  auto result = a.getPackageSnapshotImports(\"0.26.0a2\");\n  KJ_REQUIRE(result.size() == 0);\n};\n\nkj::Array<kj::String> filterPythonScriptImports(\n    kj::Array<kj::String> names, kj::ArrayPtr<kj::String> imports, kj::StringPtr version) {\n  auto contentsBuilder = kj::heapArrayBuilder<kj::Array<kj::byte>>(names.size());\n  for (auto _: kj::zeroTo(names.size())) {\n    (void)_;\n    contentsBuilder.add(kj::Array<kj::byte>(nullptr));\n  }\n  auto modInfo = pyodide::PythonModuleInfo(kj::mv(names), contentsBuilder.finish());\n  auto modSet = modInfo.getWorkerModuleSet();\n  return PythonModuleInfo::filterPythonScriptImports(kj::mv(modSet), kj::mv(imports), version);\n}\n\nKJ_TEST(\"Simple pass through\") {\n  auto imports = strArray(\"b\", \"c\");\n  auto result = filterPythonScriptImports({}, kj::mv(imports), \"\");\n  KJ_REQUIRE(result.size() == 2);\n  KJ_REQUIRE(result[0] == \"b\");\n  KJ_REQUIRE(result[1] == \"c\");\n}\n\nKJ_TEST(\"pyodide and submodules\") {\n  auto imports = strArray(\"pyodide\", \"pyodide.ffi\");\n  auto result = filterPythonScriptImports({}, kj::mv(imports), \"0.26.0a2\");\n  KJ_REQUIRE(result.size() == 0);\n}\n\nKJ_TEST(\"js and submodules\") {\n  auto imports = strArray(\"js\", \"js.crypto\");\n  auto result = filterPythonScriptImports({}, kj::mv(imports), \"0.26.0a2\");\n  KJ_REQUIRE(result.size() == 0);\n}\n\nKJ_TEST(\"importlib and submodules\") {\n  // importlib and importlib.metadata are imported into the baseline snapshot, but importlib.resources is not.\n  auto imports = strArray(\"importlib\", \"importlib.metadata\", \"importlib.resources\");\n  auto result = filterPythonScriptImports({}, kj::mv(imports), \"\");\n  KJ_REQUIRE(result.size() == 1);\n  KJ_REQUIRE(result[0] == \"importlib.resources\");\n}\n\nKJ_TEST(\"Filter worker .py files\") {\n  auto workerModules = strArray(\"b.py\", \"c.py\");\n  auto imports = strArray(\"b\", \"c\", \"d\");\n  auto result = filterPythonScriptImports(kj::mv(workerModules), kj::mv(imports), \"\");\n  KJ_REQUIRE(result.size() == 1);\n  KJ_REQUIRE(result[0] == \"d\");\n}\n\nKJ_TEST(\"Filter worker module/__init__.py\") {\n  auto workerModules = strArray(\"a/__init__.py\", \"b/__init__.py\", \"c/a.py\");\n  auto imports = strArray(\"a\", \"b\", \"c\");\n  auto result = filterPythonScriptImports(kj::mv(workerModules), kj::mv(imports), \"\");\n  KJ_REQUIRE(result.size() == 0);\n}\n\nKJ_TEST(\"Filters out subdir/submodule\") {\n  auto workerModules = strArray(\"subdir/submodule.py\");\n  auto imports = strArray(\"subdir.submodule\");\n  auto result = filterPythonScriptImports(kj::mv(workerModules), kj::mv(imports), \"\");\n  KJ_REQUIRE(result.size() == 0);\n}\n\nKJ_TEST(\"Filters out so\") {\n  auto workerModules = strArray(\"a.so\", \"b.txt\");\n  auto imports = strArray(\"a\", \"b\");\n  auto result = filterPythonScriptImports(kj::mv(workerModules), kj::mv(imports), \"\");\n  KJ_REQUIRE(result.size() == 1);\n  KJ_REQUIRE(result[0] == \"b\");\n}\n\nKJ_TEST(\"Filters out vendor stuff\") {\n  auto workerModules = strArray(\"python_modules/a.py\", \"python_modules/package/b.py\",\n      \"python_modules/c.so\", \"python_modules/x.txt\");\n  auto imports = strArray(\"a\", \"package\", \"x\");\n  auto result = filterPythonScriptImports(kj::mv(workerModules), kj::mv(imports), \"\");\n  KJ_REQUIRE(result.size() == 1);\n  KJ_REQUIRE(result[0] == \"x\");\n}\n\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/pyodide/pyodide.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#include \"pyodide.h\"\n\n#include \"requirements.h\"\n\n#include <workerd/api/pyodide/setup-emscripten.h>\n#include <workerd/io/compatibility-date.h>\n#include <workerd/io/features.h>\n#include <workerd/io/io-context.h>\n#include <workerd/util/strings.h>\n\n#include <pyodide/generated/pyodide_extra.capnp.h>\n\n#include <capnp/dynamic.h>\n#include <capnp/schema.h>\n#include <kj/array.h>\n#include <kj/common.h>\n#include <kj/compat/gzip.h>\n#include <kj/compat/tls.h>\n#include <kj/debug.h>\n#include <kj/string.h>\n\n#include <algorithm>  // for std::sort\n\nnamespace workerd::api::pyodide {\n\n// singleton that owns bundle\n\nconst kj::Maybe<jsg::Bundle::Reader> PyodideBundleManager::getPyodideBundle(\n    kj::StringPtr version) const {\n  return bundles.lockShared()->find(version).map(\n      [](const MessageBundlePair& t) { return t.bundle; });\n}\n\nvoid PyodideBundleManager::setPyodideBundleData(\n    kj::String version, kj::Array<unsigned char> data) const {\n  auto wordArray = kj::arrayPtr(\n      reinterpret_cast<const capnp::word*>(data.begin()), data.size() / sizeof(capnp::word));\n  // We're going to reuse this in the ModuleRegistry for every Python isolate, so set the traversal\n  // limit to infinity or else eventually a new Python isolate will fail.\n  auto messageReader = kj::heap<capnp::FlatArrayMessageReader>(\n      wordArray, capnp::ReaderOptions{.traversalLimitInWords = kj::maxValue})\n                           .attach(kj::mv(data));\n  auto bundle = messageReader->getRoot<jsg::Bundle>();\n  bundles.lockExclusive()->insert(\n      kj::mv(version), {.messageReader = kj::mv(messageReader), .bundle = bundle});\n}\n\nconst kj::Maybe<const kj::Array<unsigned char>&> PyodidePackageManager::getPyodidePackage(\n    kj::StringPtr id) const {\n  return packages.lockShared()->find(id);\n}\n\nvoid PyodidePackageManager::setPyodidePackageData(\n    kj::String id, kj::Array<unsigned char> data) const {\n  packages.lockExclusive()->insert(kj::mv(id), kj::mv(data));\n}\n\nstatic int readToTarget(\n    kj::ArrayPtr<const kj::byte> source, int offset, kj::ArrayPtr<kj::byte> buf) {\n  int size = source.size();\n  if (offset >= size || offset < 0) {\n    return 0;\n  }\n  int toCopy = buf.size();\n  if (size - offset < toCopy) {\n    toCopy = size - offset;\n  }\n  memcpy(buf.begin(), source.begin() + offset, toCopy);\n  return toCopy;\n}\n\nint ReadOnlyBuffer::read(jsg::Lock& js, int offset, kj::Array<kj::byte> buf) {\n  return readToTarget(source, offset, buf);\n}\n\nkj::Array<kj::StringPtr> PyodideMetadataReader::getNames(\n    jsg::Lock& js, jsg::Optional<kj::String> maybeExtFilter) {\n  auto builder = kj::Vector<kj::StringPtr>(state->moduleInfo.names.size());\n  for (auto i: kj::zeroTo(builder.capacity())) {\n    KJ_IF_SOME(ext, maybeExtFilter) {\n      if (!state->moduleInfo.names[i].endsWith(ext)) {\n        continue;\n      }\n    }\n    builder.add(state->moduleInfo.names[i]);\n  }\n  return builder.releaseAsArray();\n}\n\nvoid PyodideMetadataReader::setCpuLimitNearlyExceededCallback(\n    jsg::Lock& js, kj::Array<kj::byte> wasm_memory, int sig_clock, int sig_flag) {\n  // This callback has to be implemented in C++ because we don't hold the isolate lock when we call\n  // it. It also has to be signal safe since we call it from the cpu time limiter.\n  Worker::Isolate::from(js).setCpuLimitNearlyExceededCallback(\n      [wasm_memory = kj::mv(wasm_memory), sig_clock, sig_flag]() mutable {\n    // Set signal handling clock to fire on the next check.\n    wasm_memory[sig_clock] = 0;\n    // Set signal handling to on\n    wasm_memory[sig_flag] = 1;\n  });\n}\n\nkj::Array<kj::String> PythonModuleInfo::getPythonFileContents() {\n  auto builder = kj::Vector<kj::String>(names.size());\n  for (auto i: kj::zeroTo(names.size())) {\n    if (names[i].endsWith(\".py\")) {\n      builder.add(kj::str(contents[i].asChars()));\n    }\n  }\n  return builder.releaseAsArray();\n}\n\nkj::HashSet<kj::String> PythonModuleInfo::getWorkerModuleSet() {\n  auto result = kj::HashSet<kj::String>();\n  const auto vendor = \"python_modules/\"_kj;\n  const auto dotPy = \".py\"_kj;\n  const auto dotSo = \".so\"_kj;\n  for (auto& item: names) {\n    kj::StringPtr name = item;\n    if (name.startsWith(vendor)) {\n      name = name.slice(vendor.size());\n    }\n    auto firstSlash = name.findFirst('/');\n    KJ_IF_SOME(idx, firstSlash) {\n      result.upsert(kj::str(name.slice(0, idx)), [](auto&&, auto&&) {});\n      continue;\n    }\n    if (name.endsWith(dotPy)) {\n      result.upsert(kj::str(name.slice(0, name.size() - dotPy.size())), [](auto&&, auto&&) {});\n      continue;\n    }\n    if (name.endsWith(dotSo)) {\n      result.upsert(kj::str(name.slice(0, name.size() - dotSo.size())), [](auto&&, auto&&) {});\n      continue;\n    }\n  }\n  return result;\n}\n\nkj::Array<kj::String> PythonModuleInfo::getPackageSnapshotImports(kj::StringPtr version) {\n  auto workerFiles = this->getPythonFileContents();\n  auto importedNames = parsePythonScriptImports(kj::mv(workerFiles));\n  auto workerModules = getWorkerModuleSet();\n  return PythonModuleInfo::filterPythonScriptImports(\n      kj::mv(workerModules), kj::mv(importedNames), version);\n}\n\nkj::Array<kj::String> PyodideMetadataReader::getPackageSnapshotImports(kj::String version) {\n  return state->moduleInfo.getPackageSnapshotImports(version);\n}\n\nkj::Array<jsg::JsRef<jsg::JsString>> PyodideMetadataReader::getRequirements(jsg::Lock& js) {\n  auto builder = kj::heapArrayBuilder<jsg::JsRef<jsg::JsString>>(state->requirements.size());\n  for (auto i: kj::zeroTo(builder.capacity())) {\n    builder.add(js, js.str(state->requirements[i]));\n  }\n  return builder.finish();\n}\n\nkj::Array<int> PyodideMetadataReader::getSizes(jsg::Lock& js) {\n  auto builder = kj::heapArrayBuilder<int>(state->moduleInfo.names.size());\n  for (auto i: kj::zeroTo(builder.capacity())) {\n    builder.add(state->moduleInfo.contents[i].size());\n  }\n  return builder.finish();\n}\n\nint PyodideMetadataReader::read(jsg::Lock& js, int index, int offset, kj::Array<kj::byte> buf) {\n  if (index >= state->moduleInfo.contents.size() || index < 0) {\n    return 0;\n  }\n  auto& data = state->moduleInfo.contents[index];\n  return readToTarget(data, offset, buf);\n}\n\nint PyodideMetadataReader::readMemorySnapshot(int offset, kj::Array<kj::byte> buf) {\n  if (state->memorySnapshot == kj::none) {\n    return 0;\n  }\n  return readToTarget(KJ_REQUIRE_NONNULL(state->memorySnapshot), offset, buf);\n}\n\nkj::HashSet<kj::String> PyodideMetadataReader::getTransitiveRequirements() {\n  auto packages = parseLockFile(state->packagesLock);\n  auto depMap = getDepMapFromPackagesLock(*packages);\n\n  return getPythonPackageNames(*packages, depMap, state->requirements, state->packagesVersion);\n}\n\nint ArtifactBundler::readMemorySnapshot(int offset, kj::Array<kj::byte> buf) {\n  if (inner->existingSnapshot == kj::none) {\n    return 0;\n  }\n  return readToTarget(KJ_REQUIRE_NONNULL(inner->existingSnapshot), offset, buf);\n}\n\nkj::Array<kj::String> PythonModuleInfo::parsePythonScriptImports(kj::Array<kj::String> files) {\n  auto result = kj::Vector<kj::String>();\n\n  for (auto& file: files) {\n    // Returns the number of characters skipped. When `oneOf` is not found, skips to the end of\n    // the string.\n    auto skipUntil = [](kj::StringPtr str, std::initializer_list<char> oneOf, int start) -> int {\n      int result = 0;\n      while (start + result < str.size()) {\n        char c = str[start + result];\n        for (char expected: oneOf) {\n          if (c == expected) {\n            return result;\n          }\n        }\n\n        result++;\n      }\n\n      return result;\n    };\n\n    // Skips while current character is in `oneOf`. Returns the number of characters skipped.\n    auto skipWhile = [](kj::StringPtr str, std::initializer_list<char> oneOf, int start) -> int {\n      int result = 0;\n      while (start + result < str.size()) {\n        char c = str[start + result];\n        bool found = false;\n        for (char expected: oneOf) {\n          if (c == expected) {\n            result++;\n            found = true;\n            break;\n          }\n        }\n\n        if (!found) {\n          break;\n        }\n      }\n\n      return result;\n    };\n\n    // Skips one of the characters (specified in `oneOf`) at the current position. Otherwise\n    // throws. Returns the number of characters skipped.\n    auto skipChar = [](kj::StringPtr str, std::initializer_list<char> oneOf, int start) -> int {\n      for (char expected: oneOf) {\n        if (str[start] == expected) {\n          return 1;\n        }\n      }\n\n      KJ_FAIL_REQUIRE(\"Expected \", oneOf, \"but received\", str[start]);\n    };\n\n    auto parseKeyword = [](kj::StringPtr str, kj::StringPtr ident, int start) -> bool {\n      int i = 0;\n      for (; i < ident.size() && start + i < str.size(); i++) {\n        if (str[start + i] != ident[i]) {\n          return false;\n        }\n      }\n\n      return i == ident.size();\n    };\n\n    // Returns the size of the import identifier or 0 if no identifier exists at `start`.\n    auto parseIdent = [](kj::StringPtr str, int start) -> int {\n      // https://docs.python.org/3/reference/lexical_analysis.html#identifiers\n      //\n      // We also accept `.` because import idents can contain it.\n      // TODO: We don't currently support unicode, but if we see packages that utilize it we will\n      // implement that support.\n      if (isDigit(str[start])) {\n        return 0;\n      }\n      int i = 0;\n      for (; start + i < str.size(); i++) {\n        char c = str[start + i];\n        bool validIdentChar = isAlpha(c) || isDigit(c) || c == '_' || c == '.';\n        if (!validIdentChar) {\n          return i;\n        }\n      }\n\n      return i;\n    };\n\n    int i = 0;\n    while (i < file.size()) {\n      switch (file[i]) {\n        case 'i':\n        case 'f': {\n          auto keywordToParse = file[i] == 'i' ? \"import\"_kj : \"from\"_kj;\n          if (!parseKeyword(file, keywordToParse, i)) {\n            // We cannot simply skip the current char here, doing so would mean that\n            // `iimport x` would be parsed as a valid import.\n            i += skipUntil(file, {'\\n', '\\r', '\"', '\\''}, i);\n            continue;\n          }\n          i += keywordToParse.size();  // skip \"import\" or \"from\"\n\n          while (i < file.size()) {\n            // Python expects a `\\` to be paired with a newline, but we don't have to be as strict\n            // here because we rely on the fact that the script has gone through validation already.\n            i += skipWhile(\n                file, {'\\r', '\\n', ' ', '\\t', '\\\\'}, i);  // skip whitespace and backslash.\n\n            if (file[i] == '.') {\n              // ignore relative imports\n              break;\n            }\n\n            int identLen = parseIdent(file, i);\n            KJ_REQUIRE(identLen > 0);\n\n            kj::String ident = kj::heapString(file.slice(i, i + identLen));\n            if (ident[identLen - 1] != '.') {  // trailing period means the import is invalid\n              result.add(kj::mv(ident));\n            }\n\n            i += identLen;\n\n            // If \"import\" statement then look for comma.\n            if (keywordToParse == \"import\") {\n              i += skipWhile(\n                  file, {'\\r', '\\n', ' ', '\\t', '\\\\'}, i);  // skip whitespace and backslash.\n              // Check if next char is a comma.\n              if (file[i] == ',') {\n                i += 1;  // Skip comma.\n                // Allow while loop to continue\n              } else {\n                // No more idents, so break out of loop.\n                break;\n              }\n            } else {\n              // The \"from\" statement doesn't support commas.\n              break;\n            }\n          }\n          break;\n        }\n        case '\"':\n        case '\\'': {\n          char quote = file[i];\n          // Detect multi-line string literals `\"\"\"` and skip until the corresponding ending `\"\"\"`.\n          if (i + 2 < file.size() && file[i + 1] == quote && file[i + 2] == quote) {\n            i += 3;  // skip start quotes.\n            // skip until terminating quotes.\n            while (i + 2 < file.size() && file[i + 1] != quote && file[i + 2] != quote) {\n              if (file[i] == quote) {\n                i++;\n              }\n              i += skipUntil(file, {quote}, i);\n            }\n            i += 3;  // skip terminating quotes.\n          } else if (i + 2 < file.size() && file[i + 1] == '\\\\' &&\n              (file[i + 2] == '\\n' || file[i + 2] == '\\r')) {\n            // Detect string literal with backslash.\n            i += 3;  // skip `\"\\<NL>`\n            // skip until quote, but ignore `\\\"`.\n            while (file[i] != quote && file[i - 1] != '\\\\') {\n              if (file[i] == quote) {\n                i++;\n              }\n              i += skipUntil(file, {quote}, i);\n            }\n            i += 1;  // skip quote.\n          } else {\n            i += 1;  // skip quote.\n          }\n\n          // skip until EOL so that we don't mistakenly parse and capture `\"import x`.\n          i += skipUntil(file, {'\\n', '\\r', '\"', '\\''}, i);\n          break;\n        }\n        default:\n          // Skip to the next line or \" or '\n          i += skipUntil(file, {'\\n', '\\r', '\"', '\\''}, i);\n          if (file[i] == '\"' || file[i] == '\\'') {\n            continue;  // Allow the quotes to be handled above.\n          }\n          if (file[i] != '\\0') {\n            i += skipChar(file, {'\\n', '\\r'}, i);  // skip newline.\n          }\n      }\n    }\n  }\n\n  return result.releaseAsArray();\n}\n\nconst kj::Array<kj::StringPtr> snapshotImports = kj::arr(\"_pyodide\"_kj,\n    \"_pyodide.docstring\"_kj,\n    \"_pyodide._core_docs\"_kj,\n    \"traceback\"_kj,\n    \"collections.abc\"_kj,\n    // Asyncio is the really slow one here. In native Python on my machine, `import asyncio` takes ~50\n    // ms.\n    \"asyncio\"_kj,\n    \"inspect\"_kj,\n    \"tarfile\"_kj,\n    \"importlib\",\n    \"importlib.metadata\"_kj,\n    \"re\"_kj,\n    \"shutil\"_kj,\n    \"sysconfig\"_kj,\n    \"importlib.machinery\"_kj,\n    \"pathlib\"_kj,\n    \"site\"_kj,\n    \"tempfile\"_kj,\n    \"typing\"_kj,\n    \"zipfile\"_kj);\n\nkj::Array<kj::StringPtr> PyodideMetadataReader::getBaselineSnapshotImports() {\n  return kj::heapArray(snapshotImports.begin(), snapshotImports.size());\n}\n\njsg::JsObject PyodideMetadataReader::getCompatibilityFlags(jsg::Lock& js) {\n  auto flags = FeatureFlags::get(js);\n  auto obj = js.objNoProto();\n  auto dynamic = capnp::toDynamic(flags);\n  auto schema = dynamic.getSchema();\n\n  for (auto field: schema.getFields()) {\n    auto annotations = field.getProto().getAnnotations();\n\n    // Note that disable flags are not exposed.\n    for (auto annotation: annotations) {\n      if (annotation.getId() == COMPAT_ENABLE_FLAG_ANNOTATION_ID) {\n        obj.setReadOnly(\n            js, annotation.getValue().getText(), js.boolean(dynamic.get(field).as<bool>()));\n      }\n    }\n  }\n\n  obj.seal(js);\n  return obj;\n}\n\nPyodideMetadataReader::State::State(const State& other)\n    : mainModule(kj::str(other.mainModule)),\n      moduleInfo(other.moduleInfo.clone()),\n      requirements(KJ_MAP(req, other.requirements) { return kj::str(req); }),\n      pyodideVersion(kj::str(other.pyodideVersion)),\n      packagesVersion(kj::str(other.packagesVersion)),\n      packagesLock(kj::str(other.packagesLock)),\n      isWorkerdFlag(other.isWorkerdFlag),\n      isTracingFlag(other.isTracingFlag),\n      snapshotToDisk(other.snapshotToDisk),\n      createBaselineSnapshot(other.createBaselineSnapshot),\n      memorySnapshot(other.memorySnapshot.map(\n          [](auto& snapshot) { return kj::heapArray<kj::byte>(snapshot); })) {}\n\nkj::Own<PyodideMetadataReader::State> PyodideMetadataReader::State::clone() {\n  return kj::heap<PyodideMetadataReader::State>(*this);\n}\n\nvoid PyodideMetadataReader::State::verifyNoMainModuleInVendor() {\n  // Verify that we don't have module named after the main module in the `python_modules` subdir.\n  // mainModule includes the .py extension, so we need to extract the base name\n  kj::ArrayPtr<const char> mainModuleBase = mainModule;\n  if (mainModule.endsWith(\".py\")) {\n    mainModuleBase = mainModuleBase.slice(0, mainModuleBase.size() - 3);\n  }\n\n  for (auto& name: moduleInfo.names) {\n    if (name.startsWith(kj::str(\"python_modules/\", mainModule))) {\n      JSG_FAIL_REQUIRE(\n          Error, kj::str(\"Python module python_modules/\", mainModule, \" clashes with main module\"));\n    }\n    if (name == kj::str(\"python_modules/\", mainModuleBase, \"/__init__.py\")) {\n      JSG_FAIL_REQUIRE(Error,\n          kj::str(\"Python module python_modules/\", mainModuleBase,\n              \"/__init__.py clashes with main module\"));\n    }\n    if (name == kj::str(\"python_modules/\", mainModuleBase, \".so\")) {\n      JSG_FAIL_REQUIRE(Error,\n          kj::str(\"Python module python_modules/\", mainModuleBase, \".so clashes with main module\"));\n    }\n  }\n}\n\nkj::Array<kj::String> PythonModuleInfo::filterPythonScriptImports(\n    kj::HashSet<kj::String> workerModules,\n    kj::ArrayPtr<kj::String> imports,\n    kj::StringPtr version) {\n  auto baselineSnapshotImportsSet = kj::HashSet<kj::StringPtr>();\n  for (auto& pkgImport: snapshotImports) {\n    baselineSnapshotImportsSet.upsert(kj::mv(pkgImport), [](auto&&, auto&&) {});\n  }\n\n  kj::HashSet<kj::String> filteredImportsSet;\n  filteredImportsSet.reserve(imports.size());\n  for (auto& pkgImport: imports) {\n    auto firstDot = pkgImport.findFirst('.').orDefault(pkgImport.size());\n    auto firstComponent = pkgImport.slice(0, firstDot);\n    // Skip duplicates\n    if (filteredImportsSet.contains(pkgImport)) [[unlikely]] {\n      continue;\n    }\n\n    // don't include modules that we provide and that are likely to be imported by most\n    // workers.\n    if (firstComponent == \"js\"_kj.asArray() || firstComponent == \"asgi\"_kj.asArray() ||\n        firstComponent == \"workers\"_kj.asArray()) {\n      continue;\n    }\n    if (version == \"0.26.0a2\") {\n      if (firstComponent == \"pyodide\"_kj.asArray() || firstComponent == \"httpx\"_kj.asArray() ||\n          firstComponent == \"openai\"_kj.asArray() || firstComponent == \"starlette\"_kj.asArray() ||\n          firstComponent == \"urllib3\"_kj.asArray()) {\n        continue;\n      }\n    }\n\n    // Don't include anything that went into the baseline snapshot\n    if (baselineSnapshotImportsSet.contains(pkgImport)) {\n      continue;\n    }\n\n    // Don't include imports from worker files\n    if (workerModules.contains(firstComponent)) {\n      continue;\n    }\n    filteredImportsSet.upsert(kj::mv(pkgImport), [](auto&&, auto&&) {});\n  }\n\n  auto filteredImportsBuilder = kj::heapArrayBuilder<kj::String>(filteredImportsSet.size());\n  for (auto& pkgImport: filteredImportsSet) {\n    filteredImportsBuilder.add(kj::mv(pkgImport));\n  }\n  return filteredImportsBuilder.finish();\n}\n\nkj::Maybe<kj::String> getPyodideLock(PythonSnapshotRelease::Reader pythonSnapshotRelease) {\n  for (auto pkgLock: *PACKAGE_LOCKS) {\n    if (pkgLock.getPackageDate() == pythonSnapshotRelease.getPackages()) {\n      return kj::str(pkgLock.getLock());\n    }\n  }\n\n  return kj::none;\n}\n\nconst kj::Maybe<kj::Own<const kj::Directory>> DiskCache::NULL_CACHE_ROOT = kj::none;\n\njsg::Optional<kj::Array<kj::byte>> DiskCache::get(jsg::Lock& js, kj::String key) {\n  KJ_IF_SOME(root, cacheRoot) {\n    kj::Path path(key);\n    auto file = root->tryOpenFile(path);\n\n    KJ_IF_SOME(f, file) {\n      return f->readAllBytes();\n    } else {\n      return kj::none;\n    }\n  } else {\n    return kj::none;\n  }\n}\n\nvoid DiskCache::put(jsg::Lock& js, kj::String key, kj::Array<kj::byte> data) {\n  KJ_IF_SOME(root, cacheRoot) {\n    kj::Path path(key);\n    auto file = root->tryOpenFile(path, kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n    KJ_IF_SOME(f, file) {\n      f->writeAll(data);\n    } else {\n      KJ_LOG(ERROR, \"DiskCache: Failed to open file\", key);\n    }\n  } else {\n    return;\n  }\n}\n\nvoid DiskCache::putSnapshot(jsg::Lock& js, kj::String key, kj::Array<kj::byte> data) {\n  KJ_IF_SOME(root, snapshotRoot) {\n    kj::Path path(key);\n    auto file = root->tryOpenFile(path, kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n    KJ_IF_SOME(f, file) {\n      f->writeAll(data);\n    } else {\n      KJ_LOG(ERROR, \"DiskCache: Failed to open file\", key);\n    }\n  } else {\n    return;\n  }\n}\n\njsg::JsValue SetupEmscripten::getModule(jsg::Lock& js) {\n  return emscriptenRuntime.emscriptenRuntime.getHandle(js);\n}\n\nvoid SetupEmscripten::visitForGc(jsg::GcVisitor& visitor) {\n  visitor.visit(emscriptenRuntime.emscriptenRuntime);\n}\n\n}  // namespace workerd::api::pyodide\n\nnamespace workerd {\n\nstruct PythonSnapshotParsedField {\n  PythonSnapshotRelease::Reader pythonSnapshotRelease;\n  capnp::StructSchema::Field field;\n};\n\nkj::Array<const PythonSnapshotParsedField> makePythonSnapshotFieldTable(\n    capnp::StructSchema::FieldList fields) {\n  kj::Vector<PythonSnapshotParsedField> table(fields.size());\n\n  for (auto field: fields) {\n    bool isPythonField = false;\n\n    for (auto annotation: field.getProto().getAnnotations()) {\n      if (annotation.getId() == PYTHON_SNAPSHOT_RELEASE_ANNOTATION_ID) {\n        isPythonField = true;\n        break;\n      }\n    }\n    if (!isPythonField) {\n      continue;\n    }\n\n    auto name = field.getProto().getName();\n    kj::Maybe<PythonSnapshotRelease::Reader> pythonSnapshotRelease;\n    for (auto release: *RELEASES) {\n      if (release.getFlagName() == name) {\n        pythonSnapshotRelease = release;\n        break;\n      }\n    }\n    table.add(PythonSnapshotParsedField{\n      .pythonSnapshotRelease = KJ_REQUIRE_NONNULL(pythonSnapshotRelease),\n      .field = field,\n    });\n  }\n\n  return table.releaseAsArray();\n}\n\nkj::Maybe<PythonSnapshotRelease::Reader> getPythonSnapshotRelease(\n    CompatibilityFlags::Reader featureFlags) {\n  uint latestFieldOrdinal = 0;\n  kj::Maybe<PythonSnapshotRelease::Reader> result;\n\n  static const auto fieldTable =\n      makePythonSnapshotFieldTable(capnp::Schema::from<CompatibilityFlags>().getFields());\n\n  for (auto field: fieldTable) {\n    bool isEnabled = capnp::toDynamic(featureFlags).get(field.field).as<bool>();\n    if (!isEnabled) {\n      continue;\n    }\n\n    // We pick the flag with the highest ordinal value that is enabled and has a\n    // pythonSnapshotRelease annotation.\n    //\n    // The fieldTable is probably ordered by the ordinal anyway, but doesn't hurt to be explicit\n    // here.\n    if (latestFieldOrdinal < field.field.getIndex()) {\n      latestFieldOrdinal = field.field.getIndex();\n      result = field.pythonSnapshotRelease;\n    }\n  }\n\n  return result;\n}\n\nkj::String getPythonBundleName(PythonSnapshotRelease::Reader pyodideRelease) {\n  if (pyodideRelease.getPyodide() == \"dev\") {\n    return kj::str(\"dev\");\n  }\n  return kj::str(pyodideRelease.getPyodide(), \"_\", pyodideRelease.getPyodideRevision(), \"_\",\n      pyodideRelease.getBackport());\n}\n\nnamespace api::pyodide {\n\n// Returns a string containing the contents of the hashset, delimited by \", \"\nkj::String hashsetToString(const kj::HashSet<kj::String>& set) {\n  if (set.size() == 0) {\n    return kj::String();\n  }\n\n  kj::Vector<kj::StringPtr> elems;\n  for (const auto& e: set) {\n    elems.add(e);\n  }\n\n  // Sort the elements for consistent output\n  auto array = elems.releaseAsArray();\n  std::sort(array.begin(), array.end());\n\n  return kj::str(kj::delimited(array, \", \"_kjc));\n}\n\nkj::Array<kj::String> getPythonPackageFiles(kj::StringPtr lockFileContents,\n    kj::ArrayPtr<kj::String> requirements,\n    kj::StringPtr packagesVersion) {\n  auto packages = parseLockFile(lockFileContents);\n  auto depMap = getDepMapFromPackagesLock(*packages);\n\n  auto allRequirements = getPythonPackageNames(*packages, depMap, requirements, packagesVersion);\n\n  // Add the file names of all the requirements to our result array.\n  kj::Vector<kj::String> res;\n  for (const auto& ent: *packages) {\n    auto name = ent.getName();\n    auto obj = ent.getValue().getObject();\n    auto fileName = kj::str(getField(obj, \"file_name\").getString());\n\n    auto maybeRow = allRequirements.find(name);\n    KJ_IF_SOME(row, maybeRow) {\n      allRequirements.erase(row);\n      res.add(kj::mv(fileName));\n    } else if (packagesVersion == \"20240829.4\") {\n      auto packageType = getField(obj, \"package_type\").getString();\n      if (packageType == \"cpython_module\") {\n        res.add(kj::mv(fileName));\n      }\n    }\n  }\n\n  if (allRequirements.size() != 0) {\n    JSG_FAIL_REQUIRE(Error,\n        \"Requested Python package(s) that are not supported: \", hashsetToString(allRequirements));\n  }\n\n  return res.releaseAsArray();\n}\n\nvoid WorkerFatalReporter::reportFatal(jsg::Lock& js, kj::String error) {\n  KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n    kj::runCatchingExceptions([&]() { ioContext.getMetrics().setWorkerFatal(); });\n  }\n}\n\n}  // namespace api::pyodide\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/api/pyodide/pyodide.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <workerd/api/pyodide/setup-emscripten.h>\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/modules-new.h>\n#include <workerd/util/strong-bool.h>\n\n#include <pyodide/generated/pyodide_extra.capnp.h>\n#include <pyodide/pyodide_static.capnp.h>\n\n#include <capnp/serialize.h>\n#include <kj/array.h>\n#include <kj/common.h>\n#include <kj/compat/http.h>\n#include <kj/filesystem.h>\n#include <kj/function.h>\n#include <kj/string.h>\n#include <kj/table.h>\n#include <kj/timer.h>\n\nnamespace workerd::api::pyodide {\n\nWD_STRONG_BOOL(CreateBaselineSnapshot);\nWD_STRONG_BOOL(IsTracing);\nWD_STRONG_BOOL(IsValidating);\nWD_STRONG_BOOL(IsWorkerd);\nWD_STRONG_BOOL(SnapshotToDisk);\n\nconst auto PYTHON_PACKAGES_URL =\n    \"https://storage.googleapis.com/cloudflare-edgeworker-python-packages/\";\nclass PyodideBundleManager {\n public:\n  void setPyodideBundleData(kj::String version, kj::Array<unsigned char> data) const;\n  const kj::Maybe<jsg::Bundle::Reader> getPyodideBundle(kj::StringPtr version) const;\n\n private:\n  struct MessageBundlePair {\n    kj::Own<capnp::FlatArrayMessageReader> messageReader;\n    jsg::Bundle::Reader bundle;\n  };\n  const kj::MutexGuarded<kj::HashMap<kj::String, MessageBundlePair>> bundles;\n};\n\nclass PyodidePackageManager {\n public:\n  void setPyodidePackageData(kj::String id, kj::Array<unsigned char> data) const;\n  const kj::Maybe<const kj::Array<unsigned char>&> getPyodidePackage(kj::StringPtr id) const;\n\n private:\n  const kj::MutexGuarded<kj::HashMap<kj::String, kj::Array<unsigned char>>> packages;\n};\n\nstruct PythonConfig {\n  kj::Maybe<kj::Own<const kj::Directory>> packageDiskCacheRoot;\n  kj::Maybe<kj::Own<const kj::Directory>> pyodideDiskCacheRoot;\n  kj::Maybe<kj::Own<const kj::Directory>> snapshotDirectory;\n  const PyodideBundleManager pyodideBundleManager;\n  const PyodidePackageManager pyodidePackageManager;\n  bool createSnapshot;\n  bool createBaselineSnapshot;\n  kj::Maybe<kj::String> loadSnapshotFromDisk;\n};\n\n// A function to read a segment of the tar file into a buffer\n// Set up this way to avoid copying files that aren't accessed.\nclass ReadOnlyBuffer: public jsg::Object {\n  kj::ArrayPtr<const kj::byte> source;\n\n public:\n  ReadOnlyBuffer(kj::ArrayPtr<const kj::byte> src): source(src) {};\n\n  int read(jsg::Lock& js, int offset, kj::Array<kj::byte> buf);\n\n  JSG_RESOURCE_TYPE(ReadOnlyBuffer) {\n    JSG_METHOD(read);\n  }\n};\n\nclass PythonModuleInfo {\n public:\n  PythonModuleInfo(kj::Array<kj::String> names, kj::Array<kj::Array<kj::byte>> contents)\n      : names(kj::mv(names)),\n        contents(kj::mv(contents)) {\n    KJ_REQUIRE(this->names.size() == this->contents.size());\n  }\n  kj::Array<kj::String> names;\n  kj::Array<kj::Array<kj::byte>> contents;\n\n  PythonModuleInfo clone() const {\n    auto clonedContents =\n        KJ_MAP(content, this->contents) { return kj::heapArray<kj::byte>(content); };\n    auto clonedNames = KJ_MAP(name, this->names) { return kj::str(name); };\n    return PythonModuleInfo(kj::mv(clonedNames), kj::mv(clonedContents));\n  }\n\n  // Return the list of names to import into a package snapshot.\n  kj::Array<kj::String> getPackageSnapshotImports(kj::StringPtr version);\n  // Takes in a list of Python files (their contents). Parses these files to find the import\n  // statements, then returns a list of modules imported via those statements.\n  //\n  // For example:\n  // import a, b, c\n  // from z import x\n  // import t.y.u\n  // from . import k\n  //\n  // -> [\"a\", \"b\", \"c\", \"z\", \"t.y.u\"]\n  //\n  // Package relative imports are ignored.\n  static kj::Array<kj::String> parsePythonScriptImports(kj::Array<kj::String> files);\n  kj::HashSet<kj::String> getWorkerModuleSet();\n  kj::Array<kj::String> getPythonFileContents();\n  static kj::Array<kj::String> filterPythonScriptImports(kj::HashSet<kj::String> workerModules,\n      kj::ArrayPtr<kj::String> imports,\n      kj::StringPtr version);\n};\n\n// A class wrapping the information stored in a WorkerBundle, in particular the Python source files\n// and metadata about the worker.\n//\n// This is done this way to avoid copying files as much as possible. We set up a Metadata File\n// System which reads the contents as they are needed.\nclass PyodideMetadataReader: public jsg::Object {\n public:\n  //\n  struct State {\n    kj::String mainModule;\n    PythonModuleInfo moduleInfo;\n    kj::Array<kj::String> requirements;\n    kj::String pyodideVersion;\n    kj::String packagesVersion;\n    kj::String packagesLock;\n    bool isWorkerdFlag;\n    bool isTracingFlag;\n    bool snapshotToDisk;\n    bool createBaselineSnapshot;\n    kj::Maybe<kj::Array<kj::byte>> memorySnapshot;\n\n    State(kj::String mainModule,\n        kj::Array<kj::String> names,\n        kj::Array<kj::Array<kj::byte>> contents,\n        kj::Array<kj::String> requirements,\n        kj::String pyodideVersion,\n        kj::String packagesVersion,\n        kj::String packagesLock,\n        IsWorkerd isWorkerd,\n        IsTracing isTracing,\n        SnapshotToDisk snapshotToDisk,\n        CreateBaselineSnapshot createBaselineSnapshot,\n        kj::Maybe<kj::Array<kj::byte>> memorySnapshot)\n        : mainModule(kj::mv(mainModule)),\n          moduleInfo(kj::mv(names), kj::mv(contents)),\n          requirements(kj::mv(requirements)),\n          pyodideVersion(kj::mv(pyodideVersion)),\n          packagesVersion(kj::mv(packagesVersion)),\n          packagesLock(kj::mv(packagesLock)),\n          isWorkerdFlag(isWorkerd),\n          isTracingFlag(isTracing),\n          snapshotToDisk(snapshotToDisk),\n          createBaselineSnapshot(createBaselineSnapshot),\n          memorySnapshot(kj::mv(memorySnapshot)) {\n      verifyNoMainModuleInVendor();\n    }\n\n    State(const State& other);\n\n    void verifyNoMainModuleInVendor();\n\n    kj::Own<State> clone();\n  };\n\n  PyodideMetadataReader(kj::Own<State> state): state(kj::mv(state)) {}\n\n  bool isWorkerd() {\n    return state->isWorkerdFlag;\n  }\n\n  bool isTracing() {\n    return state->isTracingFlag;\n  }\n\n  bool shouldSnapshotToDisk() {\n    return state->snapshotToDisk;\n  }\n\n  bool isCreatingBaselineSnapshot() {\n    return state->createBaselineSnapshot;\n  }\n\n  kj::StringPtr getMainModule() {\n    return state->mainModule;\n  }\n\n  // Returns the filenames of the files inside of the WorkerBundle that end with the specified\n  // file extension.\n  // TODO: Remove this.\n  kj::Array<kj::StringPtr> getNames(jsg::Lock& js, jsg::Optional<kj::String> maybeExtFilter);\n  kj::Array<int> getSizes(jsg::Lock& js);\n\n  // Return the list of names to import into a package snapshot.\n  kj::Array<kj::String> getPackageSnapshotImports(kj::String version);\n\n  kj::Array<jsg::JsRef<jsg::JsString>> getRequirements(jsg::Lock& js);\n\n  int read(jsg::Lock& js, int index, int offset, kj::Array<kj::byte> buf);\n\n  bool hasMemorySnapshot() {\n    return state->memorySnapshot != kj::none;\n  }\n  int getMemorySnapshotSize() {\n    if (state->memorySnapshot == kj::none) {\n      return 0;\n    }\n    return KJ_REQUIRE_NONNULL(state->memorySnapshot).size();\n  }\n\n  void disposeMemorySnapshot() {\n    state->memorySnapshot = kj::none;\n  }\n  int readMemorySnapshot(int offset, kj::Array<kj::byte> buf);\n\n  kj::StringPtr getPyodideVersion() {\n    return state->pyodideVersion;\n  }\n\n  kj::StringPtr getPackagesVersion() {\n    return state->packagesVersion;\n  }\n\n  kj::StringPtr getPackagesLock() {\n    return state->packagesLock;\n  }\n\n  kj::HashSet<kj::String> getTransitiveRequirements();\n\n  static kj::Array<kj::StringPtr> getBaselineSnapshotImports();\n\n  // We call this during Python setup with the wasm memory and the addresses of the signal clock and\n  // the flag to indicate whether signal handling is on or off. It sets up the isolate\n  // CpuLimitNearlyExceeded callback to trigger a signal in Python.\n  void setCpuLimitNearlyExceededCallback(\n      jsg::Lock& js, kj::Array<kj::byte> wasm_memory, int sig_clock, int sig_flag);\n\n  // Similar to Cloudflare::::getCompatibilityFlags in global-scope.c++, but the key difference is\n  // that it returns experimental flags even if `experimental` is not enabled. This avoids a gotcha\n  // where an experimental compat flag is enabled in our C++ code, but not in our JS code.\n  //\n  // This is only for use by our Python runtime.\n  jsg::JsObject getCompatibilityFlags(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(PyodideMetadataReader) {\n    JSG_METHOD(isWorkerd);\n    JSG_METHOD(isTracing);\n    JSG_METHOD(getMainModule);\n    JSG_METHOD(getRequirements);\n    JSG_METHOD(getNames);\n    JSG_METHOD(getSizes);\n    JSG_METHOD(getPackageSnapshotImports);\n    JSG_METHOD(read);\n    JSG_METHOD(hasMemorySnapshot);\n    JSG_METHOD(getMemorySnapshotSize);\n    JSG_METHOD(readMemorySnapshot);\n    JSG_METHOD(disposeMemorySnapshot);\n    JSG_METHOD(shouldSnapshotToDisk);\n    JSG_METHOD(getPyodideVersion);\n    JSG_METHOD(getPackagesVersion);\n    JSG_METHOD(getPackagesLock);\n    JSG_METHOD(isCreatingBaselineSnapshot);\n    JSG_METHOD(getTransitiveRequirements);\n    JSG_METHOD(getCompatibilityFlags);\n    JSG_STATIC_METHOD(getBaselineSnapshotImports);\n    JSG_METHOD(setCpuLimitNearlyExceededCallback);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"mainModule\", state->mainModule);\n    for (const auto& name: state->moduleInfo.names) {\n      tracker.trackField(\"name\", name);\n    }\n    for (const auto& content: state->moduleInfo.contents) {\n      tracker.trackField(\"content\", content);\n    }\n    for (const auto& requirement: state->requirements) {\n      tracker.trackField(\"requirement\", requirement);\n    }\n  }\n\n private:\n  kj::Own<State> state;\n};\n\nstruct MemorySnapshotResult {\n  kj::Array<kj::byte> snapshot;\n  kj::Array<kj::String> importedModulesList;\n  kj::String snapshotType;\n  JSG_STRUCT(snapshot, importedModulesList, snapshotType);\n};\n\n// This used to be declared nested as ArtifactBundler::State, but then there was a need to\n// forward-declare it, so here we are.\nstruct ArtifactBundler_State {\n  kj::Maybe<const PyodidePackageManager&> packageManager;\n  // ^ lifetime should be contained by lifetime of ArtifactBundler since there is normally one worker set for the whole process. see worker-set.h\n  // In other words:\n  // WorkerSet lifetime = PackageManager lifetime and Worker lifetime = ArtifactBundler lifetime and WorkerSet owns and will outlive Worker, so PackageManager outlives ArtifactBundler\n\n  // The storedSnapshot is only used while isValidating is true.\n  kj::Maybe<MemorySnapshotResult> storedSnapshot;\n\n  // A memory snapshot of the state of the Python interpreter after initialization. Used to speed\n  // up cold starts.\n  kj::Maybe<kj::Array<const kj::byte>> existingSnapshot;\n\n  // Set only when the validator is running. This is used to determine if it is appropriate\n  // to store a memory snapshot.\n  bool isValidating;\n\n  ArtifactBundler_State(kj::Maybe<const PyodidePackageManager&> packageManager,\n      kj::Maybe<kj::Array<const kj::byte>> existingSnapshot,\n      bool isValidating = false)\n      : packageManager(packageManager),\n        storedSnapshot(kj::none),\n        existingSnapshot(kj::mv(existingSnapshot)),\n        isValidating(isValidating) {};\n\n  kj::Own<ArtifactBundler_State> clone() {\n    return kj::heap<ArtifactBundler_State>(packageManager,\n        existingSnapshot.map(\n            [](kj::Array<const kj::byte>& data) { return kj::heapArray<const kj::byte>(data); }),\n        isValidating);\n  }\n};\n\n// A loaded bundle of artifacts for a particular script id. It can also contain V8 version and\n// CPU architecture-specific artifacts. The logic for loading these is in getArtifacts.\nclass ArtifactBundler: public jsg::Object {\n public:\n  using State = ArtifactBundler_State;\n\n  ArtifactBundler(kj::Own<State> inner): inner(kj::mv(inner)) {};\n\n  void storeMemorySnapshot(jsg::Lock& js, MemorySnapshotResult snapshot) {\n    KJ_REQUIRE(inner->isValidating);\n    inner->storedSnapshot = kj::mv(snapshot);\n  }\n\n  bool hasMemorySnapshot() {\n    return inner->existingSnapshot != kj::none;\n  }\n\n  int getMemorySnapshotSize() {\n    if (inner->existingSnapshot == kj::none) {\n      return 0;\n    }\n    return KJ_REQUIRE_NONNULL(inner->existingSnapshot).size();\n  }\n\n  int readMemorySnapshot(int offset, kj::Array<kj::byte> buf);\n  void disposeMemorySnapshot() {\n    inner->existingSnapshot = kj::none;\n  }\n\n  // Determines whether this ArtifactBundler was created inside the validator.\n  bool isEwValidating() {\n    return inner->isValidating;\n  }\n\n  static kj::Own<State> makeDisabledBundler() {\n    return kj::heap<State>(kj::none, kj::none);\n  }\n\n  // Creates an ArtifactBundler that only grants access to packages, and not a memory snapshot.\n  static kj::Own<State> makePackagesOnlyBundler(kj::Maybe<const PyodidePackageManager&> manager) {\n    return kj::heap<State>(manager, kj::none);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    KJ_IF_SOME(snap, inner->existingSnapshot) {\n      tracker.trackFieldWithSize(\"snapshot\", snap.size());\n    }\n  }\n\n  bool isEnabled() {\n    return false;  // TODO(later): Remove this function once we regenerate the bundle.\n  }\n\n  kj::Maybe<jsg::Ref<ReadOnlyBuffer>> getPackage(jsg::Lock& js, kj::String path) {\n    KJ_IF_SOME(pacman, inner->packageManager) {\n      KJ_IF_SOME(ptr, pacman.getPyodidePackage(path)) {\n        return js.alloc<ReadOnlyBuffer>(ptr);\n      }\n    }\n\n    return kj::none;\n  }\n\n  JSG_RESOURCE_TYPE(ArtifactBundler) {\n    JSG_METHOD(hasMemorySnapshot);\n    JSG_METHOD(getMemorySnapshotSize);\n    JSG_METHOD(readMemorySnapshot);\n    JSG_METHOD(disposeMemorySnapshot);\n    JSG_METHOD(isEwValidating);\n    JSG_METHOD(storeMemorySnapshot);\n    JSG_METHOD(isEnabled);\n    JSG_METHOD(getPackage);\n  }\n\n private:\n  kj::Own<State> inner;\n};\n\nclass DisabledInternalJaeger: public jsg::Object {\n public:\n  static jsg::Ref<DisabledInternalJaeger> create(jsg::Lock& js) {\n    return js.alloc<DisabledInternalJaeger>();\n  }\n  JSG_RESOURCE_TYPE(DisabledInternalJaeger) {}\n};\n\n// This cache is used by Pyodide to store wheels fetched over the internet across workerd restarts in local dev only\nclass DiskCache: public jsg::Object {\n private:\n  static const kj::Maybe<kj::Own<const kj::Directory>> NULL_CACHE_ROOT;  // always set to kj::none\n\n  const kj::Maybe<kj::Own<const kj::Directory>>& cacheRoot;\n  const kj::Maybe<kj::Own<const kj::Directory>>& snapshotRoot;\n\n public:\n  DiskCache(): cacheRoot(NULL_CACHE_ROOT), snapshotRoot(NULL_CACHE_ROOT) {};  // Disabled disk cache\n  DiskCache(const kj::Maybe<kj::Own<const kj::Directory>>& cacheRoot,\n      const kj::Maybe<kj::Own<const kj::Directory>>& snapshotRoot)\n      : cacheRoot(cacheRoot),\n        snapshotRoot(snapshotRoot) {};\n\n  jsg::Optional<kj::Array<kj::byte>> get(jsg::Lock& js, kj::String key);\n  void put(jsg::Lock& js, kj::String key, kj::Array<kj::byte> data);\n  void putSnapshot(jsg::Lock& js, kj::String key, kj::Array<kj::byte> data);\n\n  JSG_RESOURCE_TYPE(DiskCache) {\n    JSG_METHOD(get);\n    JSG_METHOD(put);\n    JSG_METHOD(putSnapshot);\n  }\n};\n\n// Reports worker fatal errors to the request observer for Runtime Analytics.\n// This is exposed to the Python runtime as a module so that the on_fatal callback\n// can report fatal errors.\nclass WorkerFatalReporter: public jsg::Object {\n public:\n  WorkerFatalReporter() {}\n\n  void reportFatal(jsg::Lock& js, kj::String error);\n\n  JSG_RESOURCE_TYPE(WorkerFatalReporter) {\n    JSG_METHOD(reportFatal);\n  }\n};\n\n// A limiter which will throw if the startup is found to exceed limits. The script will still be\n// able to run for longer than the limit, but an error will be thrown as soon as the startup\n// finishes. This way we can enforce a Python-specific startup limit.\n//\n// TODO(later): stop execution as soon limit is reached, instead of doing so after the fact.\nclass SimplePythonLimiter: public jsg::Object {\n private:\n  int startupLimitMs;\n  kj::Maybe<kj::Function<kj::TimePoint()>> getTimeCb;\n\n  kj::Maybe<kj::TimePoint> startTime;\n\n public:\n  SimplePythonLimiter(): startupLimitMs(0), getTimeCb(kj::none) {}\n\n  SimplePythonLimiter(int startupLimitMs, kj::Function<kj::TimePoint()> getTimeCb)\n      : startupLimitMs(startupLimitMs),\n        getTimeCb(kj::mv(getTimeCb)) {}\n\n  static jsg::Ref<SimplePythonLimiter> makeDisabled(jsg::Lock& js) {\n    return js.alloc<SimplePythonLimiter>();\n  }\n\n  void beginStartup() {\n    KJ_IF_SOME(cb, getTimeCb) {\n      JSG_REQUIRE(startTime == kj::none, TypeError, \"Cannot call `beginStartup` multiple times.\");\n      startTime = cb();\n    }\n  }\n\n  void finishStartup(kj::Maybe<kj::String> snapshotType) {\n    KJ_IF_SOME(cb, getTimeCb) {\n      JSG_REQUIRE(startTime != kj::none, TypeError, \"Need to call `beginStartup` first.\");\n      auto endTime = cb();\n      kj::Duration diff = endTime - KJ_ASSERT_NONNULL(startTime);\n      auto diffMs = diff / kj::MILLISECONDS;\n\n      JSG_REQUIRE(diffMs <= startupLimitMs, TypeError, \"Python Worker startup exceeded CPU limit \",\n          diffMs, \"<=\", startupLimitMs, \" with snapshot \", snapshotType.orDefault(kj::str(\"none\")));\n    }\n  }\n\n  JSG_RESOURCE_TYPE(SimplePythonLimiter) {\n    JSG_METHOD(beginStartup);\n    JSG_METHOD(finishStartup);\n  }\n};\n\nclass SetupEmscripten: public jsg::Object {\n public:\n  SetupEmscripten(EmscriptenRuntime emscriptenRuntime)\n      : emscriptenRuntime(kj::mv(emscriptenRuntime)) {};\n\n  jsg::JsValue getModule(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(SetupEmscripten) {\n    JSG_METHOD(getModule);\n  }\n\n private:\n  EmscriptenRuntime emscriptenRuntime;\n  void visitForGc(jsg::GcVisitor& visitor);\n};\n\nkj::Maybe<kj::String> getPyodideLock(PythonSnapshotRelease::Reader pythonSnapshotRelease);\n\n// Returns a list of filenames we need to fetch according to the pyodide-lock.json file\n// in addition to the requirements argument, we also must include all \"stdlib\" packages\n// as well as any transitive dependencies needed\nkj::Array<kj::String> getPythonPackageFiles(kj::StringPtr lockFileContents,\n    kj::ArrayPtr<kj::String> requirements,\n    kj::StringPtr packagesVersion);\n\n// Constructs the path to a Python package in the package repository\nkj::String getPyodidePackagePath(kj::StringPtr packagesVersion, kj::StringPtr filename);\n\n#define EW_PYODIDE_ISOLATE_TYPES                                                                   \\\n  api::pyodide::ReadOnlyBuffer, api::pyodide::PyodideMetadataReader,                               \\\n      api::pyodide::ArtifactBundler, api::pyodide::DiskCache,                                      \\\n      api::pyodide::DisabledInternalJaeger, api::pyodide::SimplePythonLimiter,                     \\\n      api::pyodide::WorkerFatalReporter, api::pyodide::MemorySnapshotResult,                       \\\n      api::pyodide::SetupEmscripten\n\n}  // namespace workerd::api::pyodide\n\nnamespace workerd {\nkj::Maybe<PythonSnapshotRelease::Reader> getPythonSnapshotRelease(\n    CompatibilityFlags::Reader featureFlags);\nkj::String getPythonBundleName(PythonSnapshotRelease::Reader pyodideRelease);\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/api/pyodide/requirements.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#include \"requirements.h\"\n\n#include <capnp/compat/json.h>\n#include <capnp/message.h>\n#include <kj/debug.h>\n\n#include <cctype>\n\nnamespace workerd::api::pyodide {\n\n// getField gets a field of a JSON object by key\ncapnp::json::Value::Reader getField(\n    capnp::List<::capnp::json::Value::Field, capnp::Kind::STRUCT>::Reader &object,\n    kj::StringPtr name) {\n  for (const auto &ent: object) {\n    if (ent.getName() == name) {\n      return ent.getValue();\n    }\n  }\n\n  KJ_FAIL_ASSERT(\"Expected key in JSON object\", name);\n}\n\nkj::String canonicalizePythonPackageName(kj::StringPtr name) {\n  kj::Vector<char> res(name.size());\n\n  auto isSeparator = [](char c) { return c == '-' || c == '_' || c == '.'; };\n\n  for (int i = 0; i < name.size(); i++) {\n    if (isSeparator(name[i])) {\n      res.add('-');\n      // make i point to the last separator in the sequence\n      while (isSeparator(name[i])) i++;\n      i--;\n      continue;\n    }\n\n    res.add(std::tolower(name[i]));\n  }\n\n  res.add(0);  // NUL terminator\n\n  return kj::String(res.releaseAsArray());\n}\n\n// getDepMapFromPackagesLock computes a dependency map (a mapping from requirement to list of dependencies) from the Pyodide lock file JSON\nDepMap getDepMapFromPackagesLock(\n    capnp::List<capnp::json::Value::Field, capnp::Kind::STRUCT>::Reader &packages) {\n  DepMap res;\n\n  for (const auto &ent: packages) {\n    auto packageObj = ent.getValue().getObject();\n    auto depends = getField(packageObj, \"depends\").getArray();\n\n    auto &[_, deps] = res.insert(kj::str(ent.getName()), kj::Vector<kj::String>(depends.size()));\n\n    for (const auto &dep: depends) {\n      deps.add(kj::str(dep.getString()));\n    }\n  }\n\n  return res;\n}\n\n// addWithRecursiveDependencies adds a requirement along with all its dependencies (according to the dependency map) to the requirements set\nvoid addWithRecursiveDependencies(\n    kj::StringPtr requirement, const DepMap &depMap, kj::HashSet<kj::String> &requirementsSet) {\n  auto normalizedName = canonicalizePythonPackageName(requirement);\n  if (requirementsSet.contains(normalizedName)) {\n    return;\n  }\n\n  requirementsSet.insert(kj::str(normalizedName));\n\n  KJ_IF_SOME(deps, depMap.find(normalizedName)) {\n    for (const auto &dep: deps) {\n      addWithRecursiveDependencies(dep, depMap, requirementsSet);\n    }\n  }\n}\n\nkj::Own<capnp::List<capnp::json::Value::Field>::Reader> parseLockFile(\n    kj::StringPtr lockFileContents) {\n  capnp::JsonCodec json;\n  capnp::MallocMessageBuilder message;\n\n  auto lock = message.initRoot<capnp::JsonValue>();\n  json.decodeRaw(lockFileContents, lock);\n\n  auto object = lock.getObject().asReader();\n  auto packages = getField(object, \"packages\").getObject();\n  return capnp::clone(packages);\n}\n\nkj::HashSet<kj::String> getPythonPackageNames(\n    capnp::List<capnp::json::Value::Field>::Reader packages,\n    const DepMap &depMap,\n    kj::ArrayPtr<kj::String> requirements,\n    kj::StringPtr packagesVersion) {\n\n  kj::HashSet<kj::String> allRequirements;  // Requirements including their recursive dependencies.\n\n  // Potentially add the stdlib packages and their recursive dependencies.\n  // TODO: Loading stdlib and its dependencies breaks package snapshots on \"20240829.4\".\n  // Remove this version check once a new package/python release is made.\n  if (packagesVersion != \"20240829.4\") {\n    // We need to scan the packages list for any packages that need to be included because they\n    // are part of Python's stdlib (hashlib etc). These need to be implicitly treated as part of\n    // our `requirements`.\n    for (const auto &ent: packages) {\n      auto name = ent.getName();\n      auto obj = ent.getValue().getObject();\n      auto packageType = getField(obj, \"package_type\").getString();\n\n      if (packageType == \"cpython_module\"_kj) {\n        addWithRecursiveDependencies(name, depMap, allRequirements);\n      }\n    }\n  }\n\n  // Add all recursive dependencies of each requirement.\n  for (const auto &req: requirements) {\n    addWithRecursiveDependencies(req, depMap, allRequirements);\n  }\n\n  return allRequirements;\n}\n\n}  // namespace workerd::api::pyodide\n"
  },
  {
    "path": "src/workerd/api/pyodide/requirements.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <capnp/compat/json.capnp.h>\n#include <capnp/list.h>\n#include <kj/common.h>\n#include <kj/map.h>\n\nnamespace workerd::api::pyodide {\n\ncapnp::json::Value::Reader getField(\n    capnp::List<::capnp::json::Value::Field, capnp::Kind::STRUCT>::Reader &object,\n    kj::StringPtr name);\n\nkj::String canonicalizePythonPackageName(kj::StringPtr name);\n\n// map from requirement to list of dependencies\nusing DepMap = kj::HashMap<kj::String, kj::Vector<kj::String>>;\n\nDepMap getDepMapFromPackagesLock(\n    capnp::List<capnp::json::Value::Field, capnp::Kind::STRUCT>::Reader &packages);\n\nkj::Own<capnp::List<capnp::json::Value::Field>::Reader> parseLockFile(\n    kj::StringPtr lockFileContents);\n\nkj::HashSet<kj::String> getPythonPackageNames(\n    capnp::List<capnp::json::Value::Field>::Reader packages,\n    const DepMap &depMap,\n    kj::ArrayPtr<kj::String> requirements,\n    kj::StringPtr packagesVersion);\n\n}  // namespace workerd::api::pyodide\n"
  },
  {
    "path": "src/workerd/api/pyodide/setup-emscripten.c++",
    "content": "#include \"setup-emscripten.h\"\n\n#include <workerd/io/worker.h>\n\nnamespace workerd::api::pyodide {\n\nv8::Local<v8::Module> loadEmscriptenSetupModule(\n    jsg::Lock& js, capnp::Data::Reader emsciptenSetupJsReader) {\n  v8::Local<v8::String> contentStr = jsg::v8Str(js.v8Isolate, emsciptenSetupJsReader.asChars());\n  v8::ScriptOrigin origin(\n      jsg::v8StrIntern(js.v8Isolate, \"pyodide-internal:generated/emscriptenSetup\"), 0, 0, false, -1,\n      {}, false, false, true);\n  v8::ScriptCompiler::Source source(contentStr, origin);\n  return jsg::check(v8::ScriptCompiler::CompileModule(js.v8Isolate, &source));\n}\n\njsg::JsValue resolvePromise(jsg::Lock& js, jsg::JsValue prom) {\n  auto promise = KJ_ASSERT_NONNULL(prom.tryCast<jsg::JsPromise>());\n  if (promise.state() == jsg::PromiseState::PENDING) {\n    js.runMicrotasks();\n  }\n  KJ_ASSERT(promise.state() == jsg::PromiseState::FULFILLED);\n  return promise.result();\n}\n\nvoid instantiateEmscriptenSetupModule(jsg::Lock& js, v8::Local<v8::Module>& module) {\n  jsg::instantiateModule(js, module);\n  auto evalPromise = KJ_ASSERT_NONNULL(\n      jsg::JsValue(jsg::check(module->Evaluate(js.v8Context()))).tryCast<jsg::JsPromise>());\n  resolvePromise(js, evalPromise);\n  KJ_ASSERT(module->GetStatus() == v8::Module::kEvaluated);\n}\n\njsg::JsFunction getInstantiateEmscriptenModule(jsg::Lock& js, v8::Local<v8::Module>& module) {\n  auto instantiateEmscriptenModule =\n      js.v8Get(module->GetModuleNamespace().As<v8::Object>(), \"instantiateEmscriptenModule\"_kj);\n  KJ_ASSERT(instantiateEmscriptenModule->IsFunction());\n  return jsg::JsFunction(instantiateEmscriptenModule.As<v8::Function>());\n}\n\njsg::JsValue callInstantiateEmscriptenModule(jsg::Lock& js,\n    const jsg::JsFunction& func,\n    bool isWorkerd,\n    capnp::Data::Reader pythonStdlibZipReader,\n    capnp::Data::Reader pyodideAsmWasmReader) {\n  AllowV8BackgroundThreadsScope scope;\n  js.setAllowEval(true);\n  KJ_DEFER(js.setAllowEval(false));\n\n  auto backingStore =\n      js.allocBackingStore(pythonStdlibZipReader.size(), jsg::Lock::AllocOption::UNINITIALIZED);\n  auto pythonStdlibZip = v8::ArrayBuffer::New(js.v8Isolate, kj::mv(backingStore));\n  memcpy(pythonStdlibZip->Data(), pythonStdlibZipReader.begin(), pythonStdlibZipReader.size());\n  auto pyodideAsmWasm = jsg::check(v8::WasmModuleObject::Compile(js.v8Isolate,\n      v8::MemorySpan<const uint8_t>(pyodideAsmWasmReader.begin(), pyodideAsmWasmReader.size())));\n  return resolvePromise(js,\n      func.call(js, js.null(), js.boolean(isWorkerd), jsg::JsValue(pythonStdlibZip),\n          jsg::JsValue(pyodideAsmWasm)));\n}\n\nEmscriptenRuntime EmscriptenRuntime::initialize(\n    jsg::Lock& js, bool isWorkerd, jsg::Bundle::Reader bundle) {\n  kj::Maybe<capnp::Data::Reader> emsciptenSetupJsReader;\n  kj::Maybe<capnp::Data::Reader> pythonStdlibZipReader;\n  kj::Maybe<capnp::Data::Reader> pyodideAsmWasmReader;\n  for (auto module: bundle.getModules()) {\n    if (module.getName().endsWith(\"emscriptenSetup.js\")) {\n      emsciptenSetupJsReader = module.getData();\n    } else if (module.getName().endsWith(\"python_stdlib.zip\")) {\n      pythonStdlibZipReader = module.getData();\n    } else if (module.getName().endsWith(\"pyodide.asm.wasm\")) {\n      pyodideAsmWasmReader = module.getData();\n    }\n  }\n  auto module = loadEmscriptenSetupModule(js, KJ_ASSERT_NONNULL(emsciptenSetupJsReader));\n  instantiateEmscriptenSetupModule(js, module);\n  auto instantiateEmscriptenModule = getInstantiateEmscriptenModule(js, module);\n  auto emscriptenModule = callInstantiateEmscriptenModule(js, instantiateEmscriptenModule,\n      isWorkerd, KJ_ASSERT_NONNULL(pythonStdlibZipReader), KJ_ASSERT_NONNULL(pyodideAsmWasmReader));\n  return EmscriptenRuntime{emscriptenModule.addRef(js)};\n}\n}  // namespace workerd::api::pyodide\n"
  },
  {
    "path": "src/workerd/api/pyodide/setup-emscripten.h",
    "content": "#pragma once\n\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api::pyodide {\nstruct EmscriptenRuntime {\n  jsg::JsRef<jsg::JsValue> emscriptenRuntime;\n  static EmscriptenRuntime initialize(jsg::Lock& js, bool isWorkerd, jsg::Bundle::Reader bundle);\n};\n}  // namespace workerd::api::pyodide\n"
  },
  {
    "path": "src/workerd/api/queue.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"queue.h\"\n\n#include \"util.h\"\n\n#include <workerd/api/global-scope.h>\n#include <workerd/io/features.h>\n#include <workerd/io/tracer.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/ser.h>\n#include <workerd/util/mimetype.h>\n#include <workerd/util/strings.h>\n\n#include <kj/encoding.h>\n\nnamespace workerd::api {\n\nnamespace {\n\n// Header for the message format.\nstatic constexpr kj::StringPtr HDR_MSG_FORMAT = \"X-Msg-Fmt\"_kj;\n\n// Header for the message delivery delay.\nstatic constexpr kj::StringPtr HDR_MSG_DELAY = \"X-Msg-Delay-Secs\"_kj;\n\nauto buildQueueErrorMessage(\n    const kj::HttpClient::Response& response, const ThreadContext::HeaderIdBundle& headerIds) {\n  auto errorCode = response.headers->get(headerIds.cfQueuesErrorCode).orDefault(\"15000\"_kj);\n  auto errorCause =\n      response.headers->get(headerIds.cfQueuesErrorCause).orDefault(\"Unknown Internal Error\"_kj);\n\n  return kj::str(errorCause, \" (\", errorCode, \")\");\n}\n\nkj::StringPtr validateContentType(kj::StringPtr contentType) {\n  auto lowerCase = toLower(contentType);\n  if (lowerCase == IncomingQueueMessage::ContentType::TEXT) {\n    return IncomingQueueMessage::ContentType::TEXT;\n  } else if (lowerCase == IncomingQueueMessage::ContentType::BYTES) {\n    return IncomingQueueMessage::ContentType::BYTES;\n  } else if (lowerCase == IncomingQueueMessage::ContentType::JSON) {\n    return IncomingQueueMessage::ContentType::JSON;\n  } else if (lowerCase == IncomingQueueMessage::ContentType::V8) {\n    return IncomingQueueMessage::ContentType::V8;\n  } else {\n    JSG_FAIL_REQUIRE(TypeError, kj::str(\"Unsupported queue message content type: \", contentType));\n  }\n}\n\nstruct Serialized {\n  kj::Maybe<kj::OneOf<kj::String, kj::Array<kj::byte>, jsg::BufferSource, jsg::BackingStore>> own;\n  // Holds onto the owner of a given array of serialized data.\n  kj::ArrayPtr<kj::byte> data;\n  // A pointer into that data that can be directly written into an outgoing queue send, regardless\n  // of its holder.\n};\n\nSerialized serializeV8(jsg::Lock& js, const jsg::JsValue& body) {\n  // Use a specific serialization version to avoid sending messages using a new version before all\n  // runtimes at the edge know how to read it.\n  jsg::Serializer serializer(js,\n      jsg::Serializer::Options{\n        .version = 15,\n        .omitHeader = false,\n      });\n  serializer.write(js, jsg::JsValue(body));\n  kj::Array<kj::byte> bytes = serializer.release().data;\n  Serialized result;\n  result.data = bytes;\n  result.own = kj::mv(bytes);\n  return kj::mv(result);\n}\n\n// Control whether the serialize() method makes a deep copy of provided ArrayBuffer types or if it\n// just returns a shallow reference that is only valid until the given method returns.\nenum class SerializeArrayBufferBehavior {\n  DEEP_COPY,\n  SHALLOW_REFERENCE,\n};\n\nSerialized serialize(jsg::Lock& js,\n    const jsg::JsValue& body,\n    kj::StringPtr contentType,\n    SerializeArrayBufferBehavior bufferBehavior) {\n  if (contentType == IncomingQueueMessage::ContentType::TEXT) {\n    JSG_REQUIRE(body.isString(), TypeError,\n        kj::str(\"Content Type \\\"\", IncomingQueueMessage::ContentType::TEXT,\n            \"\\\" requires a value of type string, but received: \", body.typeOf(js)));\n\n    kj::String s = body.toString(js);\n    Serialized result;\n    result.data = s.asBytes();\n    result.own = kj::mv(s);\n    return kj::mv(result);\n  } else if (contentType == IncomingQueueMessage::ContentType::BYTES) {\n    JSG_REQUIRE(body.isArrayBufferView(), TypeError,\n        kj::str(\"Content Type \\\"\", IncomingQueueMessage::ContentType::BYTES,\n            \"\\\" requires a value of type ArrayBufferView, but received: \", body.typeOf(js)));\n\n    jsg::BufferSource source(js, body);\n    if (bufferBehavior == SerializeArrayBufferBehavior::SHALLOW_REFERENCE) {\n      // If we know the data will be consumed synchronously, we can avoid copying it.\n      Serialized result;\n      result.data = source.asArrayPtr();\n      result.own = kj::mv(source);\n      return kj::mv(result);\n    } else if (source.canDetach(js)) {\n      // Prefer detaching the input ArrayBuffer whenever possible to avoid needing to copy it.\n      auto backingSource = source.detach(js);\n      Serialized result;\n      result.data = backingSource.asArrayPtr();\n      result.own = kj::mv(backingSource);\n      return kj::mv(result);\n    } else {\n      kj::Array<kj::byte> bytes = kj::heapArray(source.asArrayPtr());\n      Serialized result;\n      result.data = bytes;\n      result.own = kj::mv(bytes);\n      return kj::mv(result);\n    }\n  } else if (contentType == IncomingQueueMessage::ContentType::JSON) {\n    kj::String s = body.toJson(js);\n    Serialized result;\n    result.data = s.asBytes();\n    result.own = kj::mv(s);\n    return kj::mv(result);\n  } else if (contentType == IncomingQueueMessage::ContentType::V8) {\n    return serializeV8(js, body);\n  } else {\n    JSG_FAIL_REQUIRE(TypeError, kj::str(\"Unsupported queue message content type: \", contentType));\n  }\n}\n\nstruct SerializedWithOptions {\n  Serialized body;\n  kj::Maybe<kj::StringPtr> contentType;\n  kj::Maybe<int> delaySeconds;\n};\n\njsg::JsValue deserialize(\n    jsg::Lock& js, kj::Array<kj::byte> body, kj::Maybe<kj::StringPtr> contentType) {\n  auto type = contentType.orDefault(IncomingQueueMessage::ContentType::V8);\n\n  if (type == IncomingQueueMessage::ContentType::TEXT) {\n    return js.str(body);\n  } else if (type == IncomingQueueMessage::ContentType::BYTES) {\n    return jsg::JsValue(js.bytes(kj::mv(body)).getHandle(js));\n  } else if (type == IncomingQueueMessage::ContentType::JSON) {\n    return jsg::JsValue::fromJson(js, body.asChars());\n  } else if (type == IncomingQueueMessage::ContentType::V8) {\n    return jsg::JsValue(jsg::Deserializer(js, body.asPtr()).readValue(js));\n  } else {\n    JSG_FAIL_REQUIRE(TypeError, kj::str(\"Unsupported queue message content type: \", type));\n  }\n}\n\njsg::JsValue deserialize(jsg::Lock& js, rpc::QueueMessage::Reader message) {\n  kj::StringPtr type = message.getContentType();\n  if (type == \"\") {\n    // default to v8 format\n    type = IncomingQueueMessage::ContentType::V8;\n  }\n\n  if (type == IncomingQueueMessage::ContentType::TEXT) {\n    return js.str(message.getData().asChars());\n  } else if (type == IncomingQueueMessage::ContentType::BYTES) {\n    kj::Array<kj::byte> bytes = kj::heapArray(message.getData().asBytes());\n    return jsg::JsValue(js.bytes(kj::mv(bytes)).getHandle(js));\n  } else if (type == IncomingQueueMessage::ContentType::JSON) {\n    return jsg::JsValue::fromJson(js, message.getData().asChars());\n  } else if (type == IncomingQueueMessage::ContentType::V8) {\n    return jsg::JsValue(jsg::Deserializer(js, message.getData()).readValue(js));\n  } else {\n    JSG_FAIL_REQUIRE(TypeError, kj::str(\"Unsupported queue message content type: \", type));\n  }\n}\n}  // namespace\n\nkj::Promise<void> WorkerQueue::send(\n    jsg::Lock& js, jsg::JsValue body, jsg::Optional<SendOptions> options) {\n  auto& context = IoContext::current();\n\n  JSG_REQUIRE(!body.isUndefined(), TypeError, \"Message body cannot be undefined\");\n\n  auto headers = kj::HttpHeaders(context.getHeaderTable());\n  headers.set(kj::HttpHeaderId::CONTENT_TYPE, MimeType::OCTET_STREAM.toString());\n\n  kj::Maybe<kj::StringPtr> contentType;\n  KJ_IF_SOME(opts, options) {\n    KJ_IF_SOME(type, opts.contentType) {\n      auto validatedType = validateContentType(type);\n      headers.addPtrPtr(HDR_MSG_FORMAT, validatedType);\n      contentType = validatedType;\n    }\n    KJ_IF_SOME(secs, opts.delaySeconds) {\n      headers.addPtr(HDR_MSG_DELAY, kj::str(secs));\n    }\n  }\n\n  Serialized serialized;\n  KJ_IF_SOME(type, contentType) {\n    serialized = serialize(js, body, type, SerializeArrayBufferBehavior::DEEP_COPY);\n  } else if (workerd::FeatureFlags::get(js).getQueuesJsonMessages()) {\n    headers.addPtrPtr(\"X-Msg-Fmt\", IncomingQueueMessage::ContentType::JSON);\n    serialized = serialize(\n        js, body, IncomingQueueMessage::ContentType::JSON, SerializeArrayBufferBehavior::DEEP_COPY);\n  } else {\n    // TODO(cleanup) send message format header (v8) by default\n    serialized = serializeV8(js, body);\n  }\n\n  // The stage that we're sending a subrequest to provides a base URL that includes a scheme, the\n  // queue broker's domain, and the start of the URL path including the account ID and queue ID. All\n  // we have to do is provide the end of the path (which is \"/message\") to send a single message.\n\n  auto client = context.getHttpClient(subrequestChannel, true, kj::none, \"queue_send\"_kjc);\n  auto req = client->request(\n      kj::HttpMethod::POST, \"https://fake-host/message\"_kjc, headers, serialized.data.size());\n\n  const auto& headerIds = context.getHeaderIds();\n  const auto exposeErrorCodes = workerd::FeatureFlags::get(js).getQueueExposeErrorCodes();\n\n  static constexpr auto handleSend = [](auto req, auto serialized, auto client, auto& headerIds,\n                                         bool exposeErrorCodes) -> kj::Promise<void> {\n    co_await req.body->write(serialized.data);\n    auto response = co_await req.response;\n\n    if (exposeErrorCodes) {\n      JSG_REQUIRE(response.statusCode == 200, Error, buildQueueErrorMessage(response, headerIds));\n    } else {\n      JSG_REQUIRE(\n          response.statusCode == 200, Error, kj::str(\"Queue send failed: \", response.statusText));\n    }\n\n    // Read and discard response body, otherwise we might burn the HTTP connection.\n    co_await response.body->readAllBytes().ignoreResult();\n  };\n\n  return handleSend(kj::mv(req), kj::mv(serialized), kj::mv(client), headerIds, exposeErrorCodes)\n      .attach(context.registerPendingEvent());\n};\n\nkj::Promise<void> WorkerQueue::sendBatch(jsg::Lock& js,\n    jsg::Sequence<MessageSendRequest> batch,\n    jsg::Optional<SendBatchOptions> options) {\n  auto& context = IoContext::current();\n\n  JSG_REQUIRE(batch.size() > 0, TypeError, \"sendBatch() requires at least one message\");\n\n  size_t totalSize = 0;\n  size_t largestMessage = 0;\n  auto messageCount = batch.size();\n  auto builder = kj::heapArrayBuilder<SerializedWithOptions>(messageCount);\n  for (auto& message: batch) {\n    auto body = message.body.getHandle(js);\n    JSG_REQUIRE(!body.isUndefined(), TypeError, \"Message body cannot be undefined\");\n\n    SerializedWithOptions item;\n    KJ_IF_SOME(secs, message.delaySeconds) {\n      item.delaySeconds = secs;\n    }\n\n    KJ_IF_SOME(contentType, message.contentType) {\n      item.contentType = validateContentType(contentType);\n      item.body = serialize(js, body, contentType, SerializeArrayBufferBehavior::SHALLOW_REFERENCE);\n    } else if (workerd::FeatureFlags::get(js).getQueuesJsonMessages()) {\n      item.contentType = IncomingQueueMessage::ContentType::JSON;\n      item.body = serialize(js, body, IncomingQueueMessage::ContentType::JSON,\n          SerializeArrayBufferBehavior::SHALLOW_REFERENCE);\n    } else {\n      item.body = serializeV8(js, body);\n    }\n\n    builder.add(kj::mv(item));\n    totalSize += builder.back().body.data.size();\n    largestMessage = kj::max(largestMessage, builder.back().body.data.size());\n  }\n  auto serializedBodies = builder.finish();\n\n  // Construct the request body by concatenating the messages together into a JSON message.\n  // Done manually to minimize copies, although it'd be nice to make this safer.\n  // (totalSize + 2) / 3 * 4 is equivalent to ceil(totalSize / 3) * 4 for base64 encoding overhead.\n  auto estimatedSize = (totalSize + 2) / 3 * 4 + messageCount * 64 + 32;\n  kj::Vector<char> bodyBuilder(estimatedSize);\n  bodyBuilder.addAll(\"{\\\"messages\\\":[\"_kj);\n  for (size_t i = 0; i < messageCount; ++i) {\n    bodyBuilder.addAll(\"{\\\"body\\\":\\\"\"_kj);\n    // TODO(perf): We should be able to encode the data directly into bodyBuilder's buffer to\n    // eliminate a lot of data copying (whereas now encodeBase64 allocates a new buffer of its own\n    // to hold its result, which we then have to copy into bodyBuilder).\n    bodyBuilder.addAll(kj::encodeBase64(serializedBodies[i].body.data));\n    bodyBuilder.add('\"');\n\n    KJ_IF_SOME(contentType, serializedBodies[i].contentType) {\n      bodyBuilder.addAll(\",\\\"contentType\\\":\\\"\"_kj);\n      bodyBuilder.addAll(contentType);\n      bodyBuilder.add('\"');\n    }\n\n    KJ_IF_SOME(delaySecs, serializedBodies[i].delaySeconds) {\n      bodyBuilder.addAll(\",\\\"delaySecs\\\": \"_kj);\n      bodyBuilder.addAll(kj::str(delaySecs));\n    }\n\n    bodyBuilder.addAll(\"}\"_kj);\n    if (i < messageCount - 1) {\n      bodyBuilder.add(',');\n    }\n  }\n  bodyBuilder.addAll(\"]}\"_kj);\n  bodyBuilder.add('\\0');\n  KJ_DASSERT(bodyBuilder.size() <= estimatedSize);\n  kj::String body(bodyBuilder.releaseAsArray());\n  KJ_DASSERT(jsg::JsValue::fromJson(js, body).isObject());\n\n  auto client = context.getHttpClient(subrequestChannel, true, kj::none, \"queue_send\"_kjc);\n\n  // We add info about the size of the batch to the headers so that the queue implementation can\n  // decide whether it's too large.\n  // TODO(someday): Enforce the size limits here instead for very slightly better performance.\n  auto headers = kj::HttpHeaders(context.getHeaderTable());\n  headers.addPtr(\"CF-Queue-Batch-Count\"_kj, kj::str(messageCount));\n  headers.addPtr(\"CF-Queue-Batch-Bytes\"_kj, kj::str(totalSize));\n  headers.addPtr(\"CF-Queue-Largest-Msg\"_kj, kj::str(largestMessage));\n  headers.set(kj::HttpHeaderId::CONTENT_TYPE, MimeType::JSON.toString());\n\n  KJ_IF_SOME(opts, options) {\n    KJ_IF_SOME(secs, opts.delaySeconds) {\n      headers.addPtr(HDR_MSG_DELAY, kj::str(secs));\n    }\n  }\n\n  // The stage that we're sending a subrequest to provides a base URL that includes a scheme, the\n  // queue broker's domain, and the start of the URL path including the account ID and queue ID. All\n  // we have to do is provide the end of the path (which is \"/batch\") to send a message batch.\n\n  auto req =\n      client->request(kj::HttpMethod::POST, \"https://fake-host/batch\"_kjc, headers, body.size());\n\n  const auto& headerIds = context.getHeaderIds();\n  const auto exposeErrorCodes = workerd::FeatureFlags::get(js).getQueueExposeErrorCodes();\n  static constexpr auto handleWrite = [](auto req, auto body, auto client, auto& headerIds,\n                                          bool exposeErrorCodes) -> kj::Promise<void> {\n    co_await req.body->write(body.asBytes());\n    auto response = co_await req.response;\n\n    if (exposeErrorCodes) {\n      JSG_REQUIRE(response.statusCode == 200, Error, buildQueueErrorMessage(response, headerIds));\n    } else {\n      JSG_REQUIRE(response.statusCode == 200, Error,\n          kj::str(\"Queue sendBatch failed: \", response.statusText));\n    }\n\n    // Read and discard response body, otherwise we might burn the HTTP connection.\n    co_await response.body->readAllBytes().ignoreResult();\n  };\n\n  return handleWrite(kj::mv(req), kj::mv(body), kj::mv(client), headerIds, exposeErrorCodes)\n      .attach(context.registerPendingEvent());\n};\n\nQueueMessage::QueueMessage(\n    jsg::Lock& js, rpc::QueueMessage::Reader message, IoPtr<QueueEventResult> result)\n    : id(kj::str(message.getId())),\n      timestamp(message.getTimestampNs() * kj::NANOSECONDS + kj::UNIX_EPOCH),\n      body(deserialize(js, message).addRef(js)),\n      attempts(message.getAttempts()),\n      result(result) {}\n// Note that we must make deep copies of all data here since the incoming Reader may be\n// deallocated while JS's GC wrappers still exist.\n\nQueueMessage::QueueMessage(\n    jsg::Lock& js, IncomingQueueMessage message, IoPtr<QueueEventResult> result)\n    : id(kj::mv(message.id)),\n      timestamp(message.timestamp),\n      body(deserialize(js, kj::mv(message.body), message.contentType).addRef(js)),\n      attempts(message.attempts),\n      result(result) {}\n\njsg::JsValue QueueMessage::getBody(jsg::Lock& js) {\n  return body.getHandle(js);\n}\n\nvoid QueueMessage::retry(jsg::Optional<QueueRetryOptions> options) {\n  if (result->ackAll) {\n    auto msg = kj::str(\"Received a call to retry() on message \", id,\n        \" after ackAll() was already called. \"\n        \"Calling retry() on a message after calling ackAll() has no effect.\");\n    IoContext::current().logWarning(msg);\n    return;\n  }\n\n  if (result->explicitAcks.contains(id)) {\n    auto msg = kj::str(\"Received a call to retry() on message \", id,\n        \" after ack() was already called. \"\n        \"Calling retry() on a message after calling ack() has no effect.\");\n    IoContext::current().logWarning(msg);\n    return;\n  }\n\n  auto& entry = result->retries.upsert(kj::heapString(id), {});\n  KJ_IF_SOME(opts, options) {\n    KJ_IF_SOME(secs, opts.delaySeconds) {\n      entry.value.delaySeconds = secs;\n    }\n  }\n}\n\nvoid QueueMessage::ack() {\n  if (result->ackAll) {\n    return;\n  }\n\n  if (result->retryBatch.retry) {\n    auto msg = kj::str(\"Received a call to ack() on message \", id,\n        \" after retryAll() was already called. \"\n        \"Calling ack() on a message after calling retryAll() has no effect.\");\n    IoContext::current().logWarning(msg);\n    return;\n  }\n\n  if (result->retries.find(id) != kj::none) {\n    auto msg = kj::str(\"Received a call to ack() on message \", id,\n        \" after retry() was already called. \"\n        \"Calling ack() on a message after calling retry() has no effect.\");\n    IoContext::current().logWarning(msg);\n    return;\n  }\n  result->explicitAcks.findOrCreate(id, [this]() { return kj::heapString(id); });\n}\n\nQueueEvent::QueueEvent(\n    jsg::Lock& js, rpc::EventDispatcher::QueueParams::Reader params, IoPtr<QueueEventResult> result)\n    : ExtendableEvent(\"queue\"),\n      queueName(kj::heapString(params.getQueueName())),\n      result(result) {\n  // Note that we must make deep copies of all data here since the incoming Reader may be\n  // deallocated while JS's GC wrappers still exist.\n  auto incoming = params.getMessages();\n  auto messagesBuilder = kj::heapArrayBuilder<jsg::Ref<QueueMessage>>(incoming.size());\n  for (auto i: kj::indices(incoming)) {\n    messagesBuilder.add(js.alloc<QueueMessage>(js, incoming[i], result));\n  }\n  messages = messagesBuilder.finish();\n}\n\nQueueEvent::QueueEvent(jsg::Lock& js, Params params, IoPtr<QueueEventResult> result)\n    : ExtendableEvent(\"queue\"),\n      queueName(kj::mv(params.queueName)),\n      result(result) {\n  auto messagesBuilder = kj::heapArrayBuilder<jsg::Ref<QueueMessage>>(params.messages.size());\n  for (auto i: kj::indices(params.messages)) {\n    messagesBuilder.add(js.alloc<QueueMessage>(js, kj::mv(params.messages[i]), result));\n  }\n  messages = messagesBuilder.finish();\n}\n\nvoid QueueEvent::retryAll(jsg::Optional<QueueRetryOptions> options) {\n  if (result->ackAll) {\n    IoContext::current().logWarning(\n        \"Received a call to retryAll() after ackAll() was already called. \"\n        \"Calling retryAll() after calling ackAll() has no effect.\");\n    return;\n  }\n\n  result->retryBatch.retry = true;\n  KJ_IF_SOME(opts, options) {\n    KJ_IF_SOME(secs, opts.delaySeconds) {\n      result->retryBatch.delaySeconds = secs;\n    }\n  }\n}\n\nvoid QueueEvent::ackAll() {\n  if (result->retryBatch.retry) {\n    IoContext::current().logWarning(\n        \"Received a call to ackAll() after retryAll() was already called. \"\n        \"Calling ackAll() after calling retryAll() has no effect.\");\n    return;\n  }\n  result->ackAll = true;\n}\n\nnamespace {\n\nstruct StartQueueEventResponse {\n  jsg::Ref<QueueEvent> event = nullptr;\n  kj::Maybe<kj::Promise<void>> exportedHandlerProm;\n  bool isServiceWorkerHandler = false;\n};\n\nStartQueueEventResponse startQueueEvent(EventTarget& globalEventTarget,\n    IoContext& context,\n    kj::OneOf<rpc::EventDispatcher::QueueParams::Reader, QueueEvent::Params> params,\n    IoPtr<QueueEventResult> result,\n    Worker::Lock& lock,\n    kj::Maybe<ExportedHandler&> exportedHandler,\n    const jsg::TypeHandler<QueueExportedHandler>& handlerHandler) {\n  jsg::Lock& js = lock;\n  jsg::Ref<QueueEvent> event(nullptr);\n  KJ_SWITCH_ONEOF(params) {\n    KJ_CASE_ONEOF(p, rpc::EventDispatcher::QueueParams::Reader) {\n      event = js.alloc<QueueEvent>(js, p, result);\n    }\n    KJ_CASE_ONEOF(p, QueueEvent::Params) {\n      event = js.alloc<QueueEvent>(js, kj::mv(p), result);\n    }\n  }\n\n  kj::Maybe<kj::Promise<void>> exportedHandlerProm;\n  bool isServiceWorkerHandler = false;\n  KJ_IF_SOME(h, exportedHandler) {\n    auto queueHandler = KJ_ASSERT_NONNULL(handlerHandler.tryUnwrap(lock, h.self.getHandle(lock)));\n    KJ_IF_SOME(f, queueHandler.queue) {\n      auto promise = f(lock, js.alloc<QueueController>(event.addRef()),\n          jsg::JsValue(h.env.getHandle(js)).addRef(js), h.getCtx())\n                         .then([event = event.addRef(), &context]() mutable {\n        event->setCompletionStatus(QueueEvent::CompletedSuccessfully{});\n        KJ_IF_SOME(t, context.getWorkerTracer()) {\n          t.setReturn(context.now());\n        }\n      }, [event = event.addRef()](kj::Exception&& e) mutable {\n        event->setCompletionStatus(QueueEvent::CompletedWithError{kj::cp(e)});\n        return kj::mv(e);\n      });\n      if (FeatureFlags::get(js).getQueueConsumerNoWaitForWaitUntil()) {\n        exportedHandlerProm = kj::mv(promise);\n      } else {\n        event->waitUntil(kj::mv(promise));\n      }\n    } else {\n      lock.logWarningOnce(\"Received a QueueEvent but we lack a handler for QueueEvents. \"\n                          \"Did you remember to export a queue() function?\");\n      JSG_FAIL_REQUIRE(Error, \"Handler does not export a queue() function.\");\n    }\n  } else {\n    isServiceWorkerHandler = true;\n    if (globalEventTarget.getHandlerCount(\"queue\") == 0) {\n      lock.logWarningOnce(\"Received a QueueEvent but we lack an event listener for queue events. \"\n                          \"Did you remember to call addEventListener(\\\"queue\\\", ...)?\");\n      JSG_FAIL_REQUIRE(Error, \"No event listener registered for queue messages.\");\n    }\n    globalEventTarget.dispatchEventImpl(lock, event.addRef());\n    event->setCompletionStatus(QueueEvent::CompletedSuccessfully{});\n  }\n\n  return StartQueueEventResponse{\n    kj::mv(event), kj::mv(exportedHandlerProm), isServiceWorkerHandler};\n}\n\n}  // namespace\n\ntracing::EventInfo QueueCustomEvent::getEventInfo() const {\n  kj::String queueName;\n  uint32_t batchSize;\n  KJ_SWITCH_ONEOF(params) {\n    KJ_CASE_ONEOF(p, rpc::EventDispatcher::QueueParams::Reader) {\n      queueName = kj::heapString(p.getQueueName());\n      batchSize = p.getMessages().size();\n    }\n    KJ_CASE_ONEOF(p, QueueEvent::Params) {\n      queueName = kj::heapString(p.queueName);\n      batchSize = p.messages.size();\n    }\n  }\n\n  return tracing::QueueEventInfo(kj::mv(queueName), batchSize);\n}\n\nkj::Promise<WorkerInterface::CustomEvent::Result> QueueCustomEvent::run(\n    kj::Own<IoContext_IncomingRequest> incomingRequest,\n    kj::Maybe<kj::StringPtr> entrypointName,\n    kj::Maybe<Worker::VersionInfo> versionInfo,\n    Frankenvalue props,\n    kj::TaskSet& waitUntilTasks) {\n  // This method has three main chunks of logic:\n  // 1. Do all necessary setup work. This starts right below this comment.\n  // 2. Call into the worker's queue event handler.\n  // 3. Wait on the necessary portions of the worker's code to complete.\n  incomingRequest->delivered();\n  auto& context = incomingRequest->getContext();\n\n  // Create a custom refcounted type for holding the queueEvent so that we can pass it to the\n  // waitUntil'ed callback safely without worrying about whether this coroutine gets canceled.\n  struct QueueEventHolder: public kj::Refcounted {\n    jsg::Ref<QueueEvent> event = nullptr;\n    kj::Maybe<kj::Promise<void>> exportedHandlerProm;\n    bool isServiceWorkerHandler = false;\n  };\n  auto queueEventHolder = kj::refcounted<QueueEventHolder>();\n\n  // 2. This is where we call into the worker's queue event handler\n  auto runProm = context.run(\n      [this, entrypointName = entrypointName, &context, queueEvent = kj::addRef(*queueEventHolder),\n          &metrics = incomingRequest->getMetrics(), versionInfo = kj::mv(versionInfo),\n          props = kj::mv(props)](Worker::Lock& lock) mutable {\n    jsg::AsyncContextFrame::StorageScope traceScope = context.makeAsyncTraceScope(lock);\n\n    auto& typeHandler = lock.getWorker().getIsolate().getApi().getQueueTypeHandler(lock);\n    auto startResp = startQueueEvent(lock.getGlobalScope(), context, kj::mv(params),\n        context.addObject(result), lock,\n        lock.getExportedHandler(\n            entrypointName, kj::mv(versionInfo), kj::mv(props), context.getActor()),\n        typeHandler);\n    queueEvent->event = kj::mv(startResp.event);\n    queueEvent->exportedHandlerProm = kj::mv(startResp.exportedHandlerProm);\n    queueEvent->isServiceWorkerHandler = startResp.isServiceWorkerHandler;\n  });\n\n  // 3. Now that we've (asynchronously) called into the event handler, wait on all necessary async\n  // work to complete. This logic is split into two completely separate code paths depending on\n  // whether the queueConsumerNoWaitForWaitUntil compatibility flag is enabled.\n  // * In the enabled path, the queue event can be considered complete as soon as the event handler\n  //   returns and the promise that it returns (if any) has resolved.\n  // * In the disabled path, the queue event isn't complete until all waitUntil'ed promises resolve.\n  //   This was how Queues originally worked, but made for a poor user experience.\n  auto compatFlags = context.getWorker().getIsolate().getApi().getFeatureFlags();\n  if (compatFlags.getQueueConsumerNoWaitForWaitUntil()) {\n    // The user has opted in to only waiting on their event handler rather than all waitUntil'd\n    // promises.\n    auto timeoutPromise = context.getLimitEnforcer().limitScheduled();\n    // Start invoking the queue handler. The promise chain here is intended to mimic the behavior of\n    // finishScheduled, but only waiting on the promise returned by the event handler rather than on\n    // all waitUntil'ed promises.\n    auto outcome = co_await runProm\n                       .then([queueEvent = kj::addRef(\n                                  *queueEventHolder)]() mutable -> kj::Promise<EventOutcome> {\n      // If the queue handler returned a promise, wait on the promise.\n      KJ_IF_SOME(handlerProm, queueEvent->exportedHandlerProm) {\n        return handlerProm.then([]() { return EventOutcome::OK; });\n      }\n      // If not, we can consider the invocation complete.\n      return EventOutcome::OK;\n    })\n                       .catch_([](kj::Exception&& e) {\n      // If any exceptions were thrown, mark the outcome accordingly.\n      return EventOutcome::EXCEPTION;\n    })\n                       .exclusiveJoin(timeoutPromise.then([] {\n      // Join everything against a timeout to ensure queue handlers can't run forever.\n      return EventOutcome::EXCEEDED_CPU;\n    })).exclusiveJoin(context.onAbort().then([] {\n      // Also handle anything that might cause the worker to get aborted.\n      // This is a change from the outcome we returned on abort before the compat flag, but better\n      // matches the behavior of fetch() handlers and the semantics of what's actually happening.\n      return EventOutcome::EXCEPTION;\n    }, [](kj::Exception&&) { return EventOutcome::EXCEPTION; }));\n\n    if (outcome == EventOutcome::OK && queueEventHolder->isServiceWorkerHandler) {\n      // HACK: For service-worker syntax, we effectively ignore the compatibility flag and wait\n      // for all waitUntil tasks anyway, since otherwise there's no way to do async work from an\n      // event listener callback.\n      // It'd be nicer if we could fall through to the code below for the non-compat-flag logic in\n      // this case, but we don't even know if the worker uses service worker syntax until after\n      // runProm resolves, so we just copy the bare essentials here.\n      auto result = co_await incomingRequest->finishScheduled();\n      bool completed = result == IoContext_IncomingRequest::FinishScheduledResult::COMPLETED;\n      outcome = completed ? context.waitUntilStatus() : EventOutcome::EXCEEDED_CPU;\n    } else {\n      // We're responsible for calling drain() on the incomingRequest to ensure that waitUntil tasks\n      // can continue to run in the backgound for a while even after we return a result to the\n      // caller of this event. But this is only needed in this code path because in all other code\n      // paths we call incomingRequest->finishScheduled(), which already takes care of waiting on\n      // waitUntil tasks.\n      waitUntilTasks.add(incomingRequest->drain().attach(\n          kj::mv(incomingRequest), kj::addRef(*queueEventHolder), kj::addRef(*this)));\n    }\n\n    KJ_IF_SOME(status, context.getLimitEnforcer().getLimitsExceeded()) {\n      outcome = status;\n    }\n    co_return WorkerInterface::CustomEvent::Result{.outcome = outcome};\n  } else {\n    // The user has not opted in to the new waitUntil behavior, so we need to add the queue()\n    // handler's promise to the waitUntil promises and then wait on them all to finish.\n    context.addWaitUntil(kj::mv(runProm));\n\n    // We reuse the finishScheduled() method for convenience, since queues use the same wall clock\n    // timeout as scheduled workers.\n    auto result = co_await incomingRequest->finishScheduled();\n    bool completed = result == IoContext_IncomingRequest::FinishScheduledResult::COMPLETED;\n\n    // Log some debug info if the request timed out or was aborted, to aid in debugging situations\n    // where consumer workers appear to get stuck and repeatedly take 15 minutes.\n    // In particular, detect whether or not the users queue() handler function completed\n    // and include info about other waitUntil tasks that may have caused the request to timeout.\n    if (!completed) {\n      kj::String status;\n      if (queueEventHolder->event.get() == nullptr) {\n        status = kj::str(\"Empty\");\n      } else {\n        KJ_SWITCH_ONEOF(queueEventHolder->event->getCompletionStatus()) {\n          KJ_CASE_ONEOF(i, QueueEvent::Incomplete) {\n            status = kj::str(\"Incomplete\");\n            break;\n          }\n          KJ_CASE_ONEOF(s, QueueEvent::CompletedSuccessfully) {\n            status = kj::str(\"Completed Succesfully\");\n            break;\n          }\n          KJ_CASE_ONEOF(e, QueueEvent::CompletedWithError) {\n            status = kj::str(\"Completed with error:\", e.error);\n            break;\n          }\n        }\n      }\n      auto& ioContext = incomingRequest->getContext();\n      auto scriptId = ioContext.getWorker().getScript().getId();\n      auto tasks = ioContext.getWaitUntilTasks().trace();\n      if (result == IoContext_IncomingRequest::FinishScheduledResult::TIMEOUT) {\n        KJ_LOG(WARNING, \"NOSENTRY queue event hit timeout\", scriptId, status, tasks);\n      } else if (result == IoContext_IncomingRequest::FinishScheduledResult::ABORTED) {\n        // Attempt to grab the error message to understand the reason for the abort.\n        // Include a timeout just in case for some unexpected reason the onAbort promise hasn't\n        // already rejected.\n        kj::String abortError;\n        co_await ioContext.onAbort()\n            .catch_([&abortError](kj::Exception&& e) {\n          abortError = kj::str(e);\n        }).exclusiveJoin(ioContext.afterLimitTimeout(1 * kj::MICROSECONDS).then([&abortError]() {\n          abortError = kj::str(\"onAbort() promise has unexpectedly not yet been rejected\");\n        }));\n        KJ_LOG(WARNING, \"NOSENTRY queue event aborted\", abortError, scriptId, status, tasks);\n      }\n    }\n\n    co_return WorkerInterface::CustomEvent::Result{\n      .outcome = completed ? context.waitUntilStatus() : EventOutcome::EXCEEDED_CPU,\n    };\n  }\n}\n\nkj::Promise<WorkerInterface::CustomEvent::Result> QueueCustomEvent::sendRpc(\n    capnp::HttpOverCapnpFactory& httpOverCapnpFactory,\n    capnp::ByteStreamFactory& byteStreamFactory,\n    rpc::EventDispatcher::Client dispatcher) {\n  auto req = dispatcher.castAs<rpc::EventDispatcher>().queueRequest();\n  KJ_SWITCH_ONEOF(params) {\n    KJ_CASE_ONEOF(p, rpc::EventDispatcher::QueueParams::Reader) {\n      req.setQueueName(p.getQueueName());\n      req.setMessages(p.getMessages());\n    }\n    KJ_CASE_ONEOF(p, QueueEvent::Params) {\n      req.setQueueName(p.queueName);\n      auto messages = req.initMessages(p.messages.size());\n      for (auto i: kj::indices(p.messages)) {\n        messages[i].setId(p.messages[i].id);\n        messages[i].setTimestampNs((p.messages[i].timestamp - kj::UNIX_EPOCH) / kj::NANOSECONDS);\n        messages[i].setData(p.messages[i].body);\n        KJ_IF_SOME(contentType, p.messages[i].contentType) {\n          messages[i].setContentType(contentType);\n        }\n        messages[i].setAttempts(p.messages[i].attempts);\n      }\n    }\n  }\n\n  return req.send().then([this](auto resp) {\n    auto respResult = resp.getResult();\n    this->result.ackAll = respResult.getAckAll();\n    auto retryBatch = respResult.getRetryBatch();\n    this->result.retryBatch.retry = retryBatch.getRetry();\n    if (retryBatch.isDelaySeconds()) {\n      this->result.retryBatch.delaySeconds = retryBatch.getDelaySeconds();\n    }\n\n    this->result.explicitAcks.clear();\n    for (const auto& msgId: respResult.getExplicitAcks()) {\n      this->result.explicitAcks.insert(kj::heapString(msgId));\n    }\n    this->result.retries.clear();\n    for (const auto& retry: respResult.getRetryMessages()) {\n      auto& entry = this->result.retries.upsert(kj::heapString(retry.getMsgId()), {});\n      if (retry.isDelaySeconds()) {\n        entry.value.delaySeconds = retry.getDelaySeconds();\n      }\n    }\n\n    return WorkerInterface::CustomEvent::Result{\n      .outcome = respResult.getOutcome(),\n    };\n  });\n}\n\nkj::Array<QueueRetryMessage> QueueCustomEvent::getRetryMessages() const {\n  auto retryMsgs = kj::heapArrayBuilder<QueueRetryMessage>(result.retries.size());\n  for (const auto& entry: result.retries) {\n    retryMsgs.add(QueueRetryMessage{\n      .msgId = kj::heapString(entry.key), .delaySeconds = entry.value.delaySeconds});\n  }\n  return retryMsgs.finish();\n}\n\nkj::Array<kj::String> QueueCustomEvent::getExplicitAcks() const {\n  auto ackArray = kj::heapArrayBuilder<kj::String>(result.explicitAcks.size());\n  for (const auto& msgId: result.explicitAcks) {\n    ackArray.add(kj::heapString(msgId));\n  }\n  return ackArray.finish();\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/queue.h",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/api/basics.h>\n#include <workerd/io/trace.h>\n#include <workerd/io/worker-interface.capnp.h>\n#include <workerd/io/worker-interface.h>\n#include <workerd/io/worker.h>\n#include <workerd/jsg/jsg.h>\n\n#include <kj/async.h>\n#include <kj/common.h>\n\nnamespace workerd::api {\n\nclass ExecutionContext;\n\n// Binding types\n\n// A capability to a Worker Queue.\nclass WorkerQueue: public jsg::Object {\n public:\n  // `subrequestChannel` is what to pass to IoContext::getHttpClient() to get an HttpClient\n  // representing this queue.\n  WorkerQueue(uint subrequestChannel): subrequestChannel(subrequestChannel) {}\n\n  struct SendOptions {\n    // TODO(soon): Support metadata.\n\n    // contentType determines the serialization format of the message.\n    jsg::Optional<kj::String> contentType;\n\n    // The number of seconds to delay the delivery of the message being sent.\n    jsg::Optional<int> delaySeconds;\n\n    JSG_STRUCT(contentType, delaySeconds);\n    JSG_STRUCT_TS_OVERRIDE(QueueSendOptions { contentType?: QueueContentType; });\n    // NOTE: Any new fields added here should also be added to MessageSendRequest below.\n  };\n\n  struct SendBatchOptions {\n    // The number of seconds to delay the delivery of the message being sent.\n    jsg::Optional<int> delaySeconds;\n\n    JSG_STRUCT(delaySeconds);\n    JSG_STRUCT_TS_OVERRIDE(QueueSendBatchOptions { delaySeconds ?: number; });\n    // NOTE: Any new fields added here should also be added to MessageSendRequest below.\n  };\n\n  struct MessageSendRequest {\n    jsg::JsRef<jsg::JsValue> body;\n\n    // contentType determines the serialization format of the message.\n    jsg::Optional<kj::String> contentType;\n\n    // The number of seconds to delay the delivery of the message being sent.\n    jsg::Optional<int> delaySeconds;\n\n    JSG_STRUCT(body, contentType, delaySeconds);\n    JSG_STRUCT_TS_OVERRIDE(MessageSendRequest<Body = unknown> {\n      body: Body;\n        contentType?: QueueContentType;\n    });\n    // NOTE: Any new fields added to SendOptions must also be added here.\n  };\n\n  kj::Promise<void> send(jsg::Lock& js, jsg::JsValue body, jsg::Optional<SendOptions> options);\n\n  kj::Promise<void> sendBatch(jsg::Lock& js,\n      jsg::Sequence<MessageSendRequest> batch,\n      jsg::Optional<SendBatchOptions> options);\n\n  JSG_RESOURCE_TYPE(WorkerQueue) {\n    JSG_METHOD(send);\n    JSG_METHOD(sendBatch);\n\n    JSG_TS_ROOT();\n    JSG_TS_OVERRIDE(Queue<Body = unknown> {\n      send(message: Body, options?: QueueSendOptions): Promise<void>;\n      sendBatch(messages\n                : Iterable<MessageSendRequest<Body>>, options ?: QueueSendBatchOptions)\n          : Promise<void>;\n    });\n    JSG_TS_DEFINE(type QueueContentType = \"text\" | \"bytes\" | \"json\" | \"v8\");\n  }\n\n private:\n  uint subrequestChannel;\n};\n\n// Event handler types\n\n// Types for other workers passing messages into and responses out of a queue handler.\n\nstruct IncomingQueueMessage {\n  kj::String id;\n  kj::Date timestamp;\n  kj::Array<kj::byte> body;\n  kj::Maybe<kj::String> contentType;\n  uint16_t attempts;\n  JSG_STRUCT(id, timestamp, body, contentType, attempts);\n\n  struct ContentType {\n    static constexpr kj::StringPtr TEXT = \"text\"_kj;\n    static constexpr kj::StringPtr BYTES = \"bytes\"_kj;\n    static constexpr kj::StringPtr JSON = \"json\"_kj;\n    static constexpr kj::StringPtr V8 = \"v8\"_kj;\n  };\n};\n\nstruct QueueRetryBatch {\n  bool retry;\n  jsg::Optional<int> delaySeconds;\n  JSG_STRUCT(retry, delaySeconds);\n};\n\nstruct QueueRetryMessage {\n  kj::String msgId;\n  jsg::Optional<int> delaySeconds;\n  JSG_STRUCT(msgId, delaySeconds);\n};\n\nstruct QueueResponse {\n  uint16_t outcome;\n  bool ackAll;\n  QueueRetryBatch retryBatch;\n  kj::Array<kj::String> explicitAcks;\n  kj::Array<QueueRetryMessage> retryMessages;\n  JSG_STRUCT(outcome, ackAll, retryBatch, explicitAcks, retryMessages);\n};\n\n// Internal-only representation used to accumulate the results of a queue event.\n\nstruct QueueEventResult {\n  struct RetryOptions {\n    jsg::Optional<int> delaySeconds;\n  };\n  struct RetryBatch {\n    bool retry;\n    jsg::Optional<int> delaySeconds;\n  };\n  RetryBatch retryBatch = {.retry = false};\n  bool ackAll = false;\n  kj::HashMap<kj::String, RetryOptions> retries;\n  kj::HashSet<kj::String> explicitAcks;\n};\n\nstruct QueueRetryOptions {\n  jsg::Optional<int> delaySeconds;\n  JSG_STRUCT(delaySeconds);\n};\n\nclass QueueMessage final: public jsg::Object {\n public:\n  QueueMessage(jsg::Lock& js, rpc::QueueMessage::Reader message, IoPtr<QueueEventResult> result);\n  QueueMessage(jsg::Lock& js, IncomingQueueMessage message, IoPtr<QueueEventResult> result);\n\n  kj::StringPtr getId() {\n    return id;\n  }\n  kj::Date getTimestamp() {\n    return timestamp;\n  }\n  jsg::JsValue getBody(jsg::Lock& js);\n  uint16_t getAttempts() {\n    return attempts;\n  };\n\n  void retry(jsg::Optional<QueueRetryOptions> options);\n  void ack();\n\n  // TODO(soon): Add metadata support.\n\n  JSG_RESOURCE_TYPE(QueueMessage) {\n    JSG_READONLY_INSTANCE_PROPERTY(id, getId);\n    JSG_READONLY_INSTANCE_PROPERTY(timestamp, getTimestamp);\n    JSG_READONLY_INSTANCE_PROPERTY(body, getBody);\n    JSG_READONLY_INSTANCE_PROPERTY(attempts, getAttempts);\n    JSG_METHOD(retry);\n    JSG_METHOD(ack);\n\n    JSG_TS_OVERRIDE(Message<Body = unknown> {\n        readonly body: Body;\n    });\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"id\", id);\n    tracker.trackField(\"body\", body);\n    tracker.trackFieldWithSize(\"IoPtr<QueueEventResult>\", sizeof(IoPtr<QueueEventResult>));\n  }\n\n private:\n  kj::String id;\n  kj::Date timestamp;\n  jsg::JsRef<jsg::JsValue> body;\n  uint16_t attempts;\n  IoPtr<QueueEventResult> result;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(body);\n  }\n};\n\nclass QueueEvent final: public ExtendableEvent {\n public:\n  // TODO(cleanup): Should we get around the need for this alternative param type by just having the\n  // service worker caller provide us with capnp-serialized params?\n  struct Params {\n    kj::String queueName;\n    kj::Array<IncomingQueueMessage> messages;\n  };\n\n  explicit QueueEvent(jsg::Lock& js,\n      rpc::EventDispatcher::QueueParams::Reader params,\n      IoPtr<QueueEventResult> result);\n  explicit QueueEvent(jsg::Lock& js, Params params, IoPtr<QueueEventResult> result);\n\n  static jsg::Ref<QueueEvent> constructor(kj::String type) = delete;\n\n  kj::ArrayPtr<jsg::Ref<QueueMessage>> getMessages() {\n    return messages;\n  }\n  kj::StringPtr getQueueName() {\n    return queueName;\n  }\n\n  void retryAll(jsg::Optional<QueueRetryOptions> options);\n  void ackAll();\n\n  JSG_RESOURCE_TYPE(QueueEvent) {\n    JSG_INHERIT(ExtendableEvent);\n\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(messages, getMessages);\n    JSG_READONLY_INSTANCE_PROPERTY(queue, getQueueName);\n\n    JSG_METHOD(retryAll);\n    JSG_METHOD(ackAll);\n\n    JSG_TS_ROOT();\n    JSG_TS_OVERRIDE(QueueEvent<Body = unknown> {\n        readonly messages: readonly Message<Body>[];\n    });\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    for (auto& message: messages) {\n      tracker.trackField(\"message\", message);\n    }\n    tracker.trackField(\"queueName\", queueName);\n    tracker.trackFieldWithSize(\"IoPtr<QueueEventResult>\", sizeof(IoPtr<QueueEventResult>));\n  }\n\n  struct Incomplete {};\n  struct CompletedSuccessfully {};\n  struct CompletedWithError {\n    kj::Exception error;\n  };\n  using CompletionStatus = kj::OneOf<Incomplete, CompletedSuccessfully, CompletedWithError>;\n\n  void setCompletionStatus(CompletionStatus status) {\n    completionStatus = status;\n  }\n\n  CompletionStatus getCompletionStatus() const {\n    return completionStatus;\n  }\n\n private:\n  // TODO(perf): Should we store these in a v8 array directly rather than this intermediate kj\n  // array to avoid one intermediate copy?\n  kj::Array<jsg::Ref<QueueMessage>> messages;\n  kj::String queueName;\n  IoPtr<QueueEventResult> result;\n  CompletionStatus completionStatus = Incomplete{};\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visitAll(messages);\n  }\n};\n\n// Type used when calling a module-exported queue event handler.\nclass QueueController final: public jsg::Object {\n public:\n  QueueController(jsg::Ref<QueueEvent> event): event(kj::mv(event)) {}\n\n  kj::ArrayPtr<jsg::Ref<QueueMessage>> getMessages() {\n    return event->getMessages();\n  }\n  kj::StringPtr getQueueName() {\n    return event->getQueueName();\n  }\n  void retryAll(jsg::Optional<QueueRetryOptions> options) {\n    event->retryAll(options);\n  }\n  void ackAll() {\n    event->ackAll();\n  }\n\n  JSG_RESOURCE_TYPE(QueueController) {\n    JSG_READONLY_INSTANCE_PROPERTY(messages, getMessages);\n    JSG_READONLY_INSTANCE_PROPERTY(queue, getQueueName);\n\n    JSG_METHOD(retryAll);\n    JSG_METHOD(ackAll);\n\n    JSG_TS_ROOT();\n    JSG_TS_OVERRIDE(MessageBatch<Body = unknown> {\n      readonly messages: readonly Message<Body>[];\n    });\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"event\", event);\n  }\n\n private:\n  jsg::Ref<QueueEvent> event;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(event);\n  }\n};\n\n// Extension of ExportedHandler covering queue handlers.\nstruct QueueExportedHandler {\n  using QueueHandler = kj::Promise<void>(jsg::Ref<QueueController> controller,\n      jsg::JsRef<jsg::JsValue> env,\n      jsg::Optional<jsg::Ref<ExecutionContext>> ctx);\n  jsg::LenientOptional<jsg::Function<QueueHandler>> queue;\n\n  JSG_STRUCT(queue);\n};\n\nclass QueueCustomEvent final: public WorkerInterface::CustomEvent, public kj::Refcounted {\n public:\n  QueueCustomEvent(kj::OneOf<QueueEvent::Params, rpc::EventDispatcher::QueueParams::Reader> params)\n      : params(kj::mv(params)) {}\n\n  kj::Promise<Result> run(kj::Own<IoContext_IncomingRequest> incomingRequest,\n      kj::Maybe<kj::StringPtr> entrypointName,\n      kj::Maybe<Worker::VersionInfo> versionInfo,\n      Frankenvalue props,\n      kj::TaskSet& waitUntilTasks) override;\n\n  kj::Promise<Result> sendRpc(capnp::HttpOverCapnpFactory& httpOverCapnpFactory,\n      capnp::ByteStreamFactory& byteStreamFactory,\n      rpc::EventDispatcher::Client dispatcher) override;\n\n  static const uint16_t EVENT_TYPE = 5;\n  uint16_t getType() override {\n    return EVENT_TYPE;\n  }\n\n  tracing::EventInfo getEventInfo() const override;\n\n  QueueRetryBatch getRetryBatch() const {\n    return {.retry = result.retryBatch.retry, .delaySeconds = result.retryBatch.delaySeconds};\n  }\n  bool getAckAll() const {\n    return result.ackAll;\n  }\n  kj::Array<QueueRetryMessage> getRetryMessages() const;\n  kj::Array<kj::String> getExplicitAcks() const;\n\n  kj::Promise<Result> notSupported() override {\n    KJ_UNIMPLEMENTED(\"queue event not supported\");\n  }\n\n private:\n  kj::OneOf<rpc::EventDispatcher::QueueParams::Reader, QueueEvent::Params> params;\n  QueueEventResult result;\n};\n\n#define EW_QUEUE_ISOLATE_TYPES                                                                     \\\n  api::WorkerQueue, api::WorkerQueue::SendOptions, api::WorkerQueue::SendBatchOptions,             \\\n      api::WorkerQueue::MessageSendRequest, api::IncomingQueueMessage, api::QueueRetryBatch,       \\\n      api::QueueRetryMessage, api::QueueResponse, api::QueueRetryOptions, api::QueueMessage,       \\\n      api::QueueEvent, api::QueueController, api::QueueExportedHandler\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/r2-admin.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"r2-admin.h\"\n\n#include \"r2-rpc.h\"\n\n#include <workerd/api/r2-api.capnp.h>\n#include <workerd/util/http-util.h>\n\n#include <capnp/compat/json.h>\n#include <capnp/message.h>\n#include <kj/compat/http.h>\n\nnamespace workerd::api::public_beta {\njsg::Ref<R2Bucket> R2Admin::get(jsg::Lock& js, kj::String bucketName) {\n  KJ_IF_SOME(j, jwt) {\n    return js.alloc<R2Bucket>(\n        featureFlags, subrequestChannel, kj::mv(bucketName), kj::str(j), R2Bucket::friend_tag_t{});\n  }\n  return js.alloc<R2Bucket>(\n      featureFlags, subrequestChannel, kj::mv(bucketName), R2Bucket::friend_tag_t{});\n}\n\njsg::Promise<jsg::Ref<R2Bucket>> R2Admin::create(\n    jsg::Lock& js, kj::String name, const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType) {\n  auto& context = IoContext::current();\n\n  TraceContext traceContext = context.makeUserTraceSpan(\"r2_create_bucket\"_kjc);\n  auto client = context.getHttpClient(subrequestChannel, true, kj::none, traceContext);\n\n  traceContext.setTag(\"rpc.service\"_kjc, \"r2\"_kjc);\n  traceContext.setTag(\"rpc.method\"_kjc, \"CreateBucket\"_kjc);\n  traceContext.setTag(\"cloudflare.r2.bucket\"_kjc, name.asPtr());\n\n  capnp::JsonCodec json;\n  json.handleByAnnotation<R2BindingRequest>();\n  capnp::MallocMessageBuilder requestMessage;\n\n  auto requestBuilder = requestMessage.initRoot<R2BindingRequest>();\n  requestBuilder.setVersion(VERSION_PUBLIC_BETA);\n  auto payloadBuilder = requestBuilder.initPayload();\n  auto createBucketBuilder = payloadBuilder.initCreateBucket();\n  createBucketBuilder.setBucket(name);\n\n  auto requestJson = json.encode(requestBuilder);\n  auto promise =\n      doR2HTTPPutRequest(kj::mv(client), kj::none, kj::none, kj::mv(requestJson), nullptr, jwt);\n\n  auto awaitIoResult = context.awaitIo(js, kj::mv(promise),\n      [this, subrequestChannel = subrequestChannel, name = kj::mv(name), &errorType](\n          jsg::Lock& js, R2Result r2Result) mutable {\n    r2Result.throwIfError(\"createBucket\", errorType);\n    return js.alloc<R2Bucket>(\n        featureFlags, subrequestChannel, kj::mv(name), R2Bucket::friend_tag_t{});\n  });\n\n  return context.attachSpans(js, kj::mv(awaitIoResult), kj::mv(traceContext));\n}\n\njsg::Promise<R2Admin::ListResult> R2Admin::list(jsg::Lock& js,\n    jsg::Optional<ListOptions> options,\n    const jsg::TypeHandler<jsg::Ref<RetrievedBucket>>& retrievedBucketType,\n    const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType,\n    CompatibilityFlags::Reader flags) {\n  auto& context = IoContext::current();\n\n  TraceContext traceContext = context.makeUserTraceSpan(\"r2_list_buckets\"_kjc);\n  auto client = context.getHttpClient(subrequestChannel, true, kj::none, traceContext);\n\n  traceContext.setTag(\"rpc.service\"_kjc, \"r2\"_kjc);\n  traceContext.setTag(\"rpc.method\"_kjc, \"ListBuckets\"_kjc);\n\n  capnp::JsonCodec json;\n  json.handleByAnnotation<R2BindingRequest>();\n  json.setHasMode(capnp::HasMode::NON_DEFAULT);\n  capnp::MallocMessageBuilder requestMessage;\n\n  auto requestBuilder = requestMessage.initRoot<R2BindingRequest>();\n  requestBuilder.setVersion(VERSION_PUBLIC_BETA);\n  auto payloadBuilder = requestBuilder.initPayload();\n  auto listBucketBuilder = payloadBuilder.initListBucket();\n  KJ_IF_SOME(o, options) {\n    KJ_IF_SOME(l, o.limit) {\n      listBucketBuilder.setLimit(l);\n    }\n    KJ_IF_SOME(c, o.cursor) {\n      listBucketBuilder.setCursor(c);\n    }\n  }\n\n  auto requestJson = json.encode(requestBuilder);\n  auto promise = doR2HTTPGetRequest(kj::mv(client), kj::mv(requestJson), nullptr, jwt, flags);\n\n  auto awaitIoResult = context.awaitIo(js, kj::mv(promise),\n      [this, &retrievedBucketType, &errorType](jsg::Lock& js, R2Result r2Result) mutable {\n    r2Result.throwIfError(\"listBucket\", errorType);\n\n    capnp::MallocMessageBuilder responseMessage;\n    capnp::JsonCodec json;\n    json.handleByAnnotation<R2ListResponse>();\n    auto responseBuilder = responseMessage.initRoot<R2ListBucketResponse>();\n    json.decode(KJ_ASSERT_NONNULL(r2Result.metadataPayload), responseBuilder);\n\n    auto buckets = js.map();\n    for (auto b: responseBuilder.getBuckets()) {\n      auto bucket = js.alloc<RetrievedBucket>(featureFlags, subrequestChannel, kj::str(b.getName()),\n          kj::UNIX_EPOCH + b.getCreatedMillisecondsSinceEpoch() * kj::MILLISECONDS);\n      buckets.set(js, b.getName(), jsg::JsValue(retrievedBucketType.wrap(js, kj::mv(bucket))));\n    }\n\n    ListResult result{\n      .buckets = buckets.addRef(js),\n      .truncated = responseBuilder.getTruncated(),\n    };\n    if (responseBuilder.hasCursor()) {\n      result.cursor = kj::str(responseBuilder.getCursor());\n    }\n\n    return kj::mv(result);\n  });\n\n  return context.attachSpans(js, kj::mv(awaitIoResult), kj::mv(traceContext));\n}\n\njsg::Promise<void> R2Admin::delete_(\n    jsg::Lock& js, kj::String name, const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType) {\n  auto& context = IoContext::current();\n\n  TraceContext traceContext = context.makeUserTraceSpan(\"r2_delete_bucket\"_kjc);\n  auto client = context.getHttpClient(subrequestChannel, true, kj::none, traceContext);\n\n  traceContext.setTag(\"rpc.service\"_kjc, \"r2\"_kjc);\n  traceContext.setTag(\"rpc.method\"_kjc, \"DeleteBucket\"_kjc);\n  traceContext.setTag(\"cloudflare.r2.bucket\"_kjc, kj::StringPtr(name));\n\n  capnp::JsonCodec json;\n  json.handleByAnnotation<R2BindingRequest>();\n  capnp::MallocMessageBuilder requestMessage;\n\n  auto requestBuilder = requestMessage.initRoot<R2BindingRequest>();\n  requestBuilder.setVersion(VERSION_PUBLIC_BETA);\n  auto payloadBuilder = requestBuilder.initPayload();\n  auto deleteBucketBuilder = payloadBuilder.initDeleteBucket();\n  deleteBucketBuilder.setBucket(name);\n\n  auto requestJson = json.encode(requestBuilder);\n  auto promise =\n      doR2HTTPPutRequest(kj::mv(client), kj::none, kj::none, kj::mv(requestJson), nullptr, jwt);\n\n  auto awaitIoResult =\n      context.awaitIo(js, kj::mv(promise), [&errorType](jsg::Lock&, R2Result r2Result) mutable {\n    r2Result.throwIfError(\"deleteBucket\", errorType);\n  });\n\n  return context.attachSpans(js, kj::mv(awaitIoResult), kj::mv(traceContext));\n}\n\n}  // namespace workerd::api::public_beta\n"
  },
  {
    "path": "src/workerd/api/r2-admin.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"r2-bucket.h\"\n\n#include <workerd/jsg/jsg.h>\n\nnamespace edgeworker::api {\nclass R2CrossAccount;\n}\n\nnamespace workerd::api::public_beta {\n\n// A capability to an R2 Admin interface.\nclass R2Admin: public jsg::Object {\n\n  // A friend tag that grants access to an internal constructor for the R2CrossAccount binding\n  struct friend_tag_t {};\n\n  struct FeatureFlags: public R2Bucket::FeatureFlags {\n    using R2Bucket::FeatureFlags::FeatureFlags;\n  };\n\n public:\n  // `subrequestChannel` is what to pass to IoContext::getHttpClient() to get an HttpClient\n  // representing this namespace.\n  explicit R2Admin(CompatibilityFlags::Reader featureFlags, uint subrequestChannel)\n      : featureFlags(featureFlags),\n        subrequestChannel(subrequestChannel) {}\n\n  // This constructor is intended to be used by the R2CrossAccount binding, which has access to the\n  // friend_tag\n  R2Admin(FeatureFlags featureFlags, uint subrequestChannel, kj::String jwt, friend_tag_t)\n      : featureFlags(featureFlags),\n        subrequestChannel(subrequestChannel),\n        jwt(kj::mv(jwt)) {}\n\n  struct ListOptions {\n    jsg::Optional<int> limit;\n    jsg::Optional<kj::String> cursor;\n\n    JSG_STRUCT(limit, cursor);\n  };\n\n  class RetrievedBucket: public R2Bucket {\n   public:\n    RetrievedBucket(R2Bucket::FeatureFlags featureFlags,\n        uint subrequestChannel,\n        kj::String name,\n        kj::Date created)\n        : R2Bucket(featureFlags, subrequestChannel, kj::mv(name), R2Bucket::friend_tag_t{}),\n          created(created) {}\n\n    kj::StringPtr getName() const {\n      return KJ_ASSERT_NONNULL(adminBucketName());\n    }\n    kj::Date getCreated() const {\n      return created;\n    }\n\n    JSG_RESOURCE_TYPE(RetrievedBucket) {\n      JSG_INHERIT(R2Bucket);\n      JSG_READONLY_INSTANCE_PROPERTY(name, getName);\n      JSG_READONLY_INSTANCE_PROPERTY(created, getCreated);\n    }\n\n   private:\n    kj::Date created;\n\n    friend class R2Admin;\n  };\n\n  struct ListResult {\n    jsg::JsRef<jsg::JsMap> buckets;\n    bool truncated = false;\n    jsg::Optional<kj::String> cursor;\n\n    JSG_STRUCT(buckets, truncated, cursor);\n  };\n\n  jsg::Promise<jsg::Ref<R2Bucket>> create(\n      jsg::Lock& js, kj::String name, const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType);\n  jsg::Ref<R2Bucket> get(jsg::Lock& js, kj::String name);\n  jsg::Promise<void> delete_(\n      jsg::Lock& js, kj::String bucketName, const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType);\n  jsg::Promise<ListResult> list(jsg::Lock& js,\n      jsg::Optional<ListOptions> options,\n      const jsg::TypeHandler<jsg::Ref<RetrievedBucket>>& retrievedBucketType,\n      const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType,\n      CompatibilityFlags::Reader flags);\n\n  JSG_RESOURCE_TYPE(R2Admin) {\n    JSG_METHOD(create);\n    JSG_METHOD(get);\n    JSG_METHOD_NAMED(delete, delete_);\n    JSG_METHOD(list);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"jwt\", jwt);\n  }\n\n private:\n  R2Bucket::FeatureFlags featureFlags;\n  uint subrequestChannel;\n  kj::Maybe<kj::String> jwt;\n\n  friend class edgeworker::api::R2CrossAccount;\n};\n\n#define EW_R2_PUBLIC_BETA_ADMIN_ISOLATE_TYPES                                                      \\\n  api::public_beta::R2Admin, api::public_beta::R2Admin::RetrievedBucket,                           \\\n      api::public_beta::R2Admin::ListOptions, api::public_beta::R2Admin::ListResult\n\n// The list of r2-admin.h types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE\n\n}  // namespace workerd::api::public_beta\n"
  },
  {
    "path": "src/workerd/api/r2-api.capnp",
    "content": "# Copyright (c) 2017-2022 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n@0xfb0dc52eec08c4d2;\n\nusing Cxx = import \"/capnp/c++.capnp\";\nusing Json = import \"/capnp/compat/json.capnp\";\n\n$Cxx.namespace(\"workerd::api::public_beta\");\n$Cxx.allowCancellation;\n\nconst versionPublicBeta :UInt32 = 1;\n\nstruct R2BindingRequest {\n  version @0 :UInt32;\n  payload :union $Json.flatten() $Json.discriminator(name=\"method\") {\n    head @1 :R2HeadRequest $Json.flatten();\n    get @2 :R2GetRequest $Json.flatten();\n    put @3 :R2PutRequest $Json.flatten();\n    list @4 :R2ListRequest $Json.flatten();\n    delete @5 :R2DeleteRequest $Json.flatten();\n    createBucket @6 :R2CreateBucketRequest $Json.flatten();\n    listBucket @7 :R2ListBucketRequest $Json.flatten();\n    deleteBucket @8 :R2DeleteBucketRequest $Json.flatten();\n    createMultipartUpload @9 :R2CreateMultipartUploadRequest $Json.flatten();\n    uploadPart @10 :R2UploadPartRequest $Json.flatten();\n    completeMultipartUpload @11 :R2CompleteMultipartUploadRequest $Json.flatten();\n    abortMultipartUpload @12 :R2AbortMultipartUploadRequest $Json.flatten();\n  }\n}\n\nstruct Record {\n  k @0 :Text;\n  v @1 :Text;\n}\n\nstruct R2Range {\n  offset @0 :UInt64 = 0xffffffffffffffff;\n  length @1 :UInt64 = 0xffffffffffffffff;\n  suffix @2 :UInt64 = 0xffffffffffffffff;\n}\n\nstruct R2Etag {\n  value @0 :Text;\n  type :union $Json.flatten() $Json.discriminator(name=\"type\") {\n    strong @1 :Void;\n    weak @2 :Void;\n    wildcard @3 :Void;\n  }\n}\n\nstruct R2Conditional {\n  etagMatches @0 :List(R2Etag);\n  etagDoesNotMatch @1 :List(R2Etag);\n  uploadedBefore @2 :UInt64 = 0xffffffffffffffff;\n  uploadedAfter @3 :UInt64 = 0xffffffffffffffff;\n  secondsGranularity @4 :Bool;\n  # Should uploadedBefore / uploadedAfter be evaluated against the seconds granularity of the upload\n  # timestamp.\n}\n\nstruct R2SSECOptions {\n  key @0 :Text;\n}\n\nstruct R2Checksums {\n  # The JSON name of these fields must comform to the representation of the ChecksumAlgorithm in\n  # the R2 gateway worker.\n  md5 @0 :Data $Json.hex $Json.name(\"0\");\n  sha1 @1 :Data $Json.hex $Json.name(\"1\");\n  sha256 @2 :Data $Json.hex $Json.name(\"2\");\n  sha384 @3 :Data $Json.hex $Json.name(\"3\");\n  sha512 @4 :Data $Json.hex $Json.name(\"4\");\n}\n\nstruct R2PublishedPart {\n  etag @0 :Text;\n  part @1 :UInt32;\n}\n\nstruct R2HttpFields {\n  contentType @0 :Text;\n  contentLanguage @1 :Text;\n  contentDisposition @2 :Text;\n  contentEncoding @3 :Text;\n  cacheControl @4 :Text;\n  cacheExpiry @5 :UInt64 = 0xffffffffffffffff;\n}\n\nstruct R2HeadRequest {\n  object @0 :Text;\n}\n\nstruct R2GetRequest {\n  object @0 :Text;\n  range @1 :R2Range;\n  rangeHeader @3 :Text;\n  onlyIf @2 :R2Conditional;\n  ssec @4 :R2SSECOptions;\n}\n\nstruct R2PutRequest {\n  object @0 :Text;\n  customFields @1 :List(Record);\n  httpFields @2 :R2HttpFields;\n  onlyIf @3 :R2Conditional;\n  md5 @4 :Data $Json.base64;\n  sha1 @5 :Data $Json.hex;\n  sha256 @6 :Data $Json.hex;\n  sha384 @7 :Data $Json.hex;\n  sha512 @8 :Data $Json.hex;\n  storageClass @9 :Text;\n  ssec @10 :R2SSECOptions;\n}\n\nstruct R2CreateMultipartUploadRequest {\n  object @0 :Text;\n  customFields @1 :List(Record);\n  httpFields @2 :R2HttpFields;\n  storageClass @3 :Text;\n  ssec @4 :R2SSECOptions;\n}\n\nstruct R2UploadPartRequest {\n  object @0 :Text;\n  uploadId @1 :Text;\n  partNumber @2 :UInt32;\n  ssec @3 :R2SSECOptions;\n}\n\nstruct R2CompleteMultipartUploadRequest {\n  object @0 :Text;\n  uploadId @1 :Text;\n  parts @2 :List(R2PublishedPart);\n}\n\nstruct R2AbortMultipartUploadRequest {\n  object @0 :Text;\n  uploadId @1 :Text;\n}\n\nstruct R2ListRequest {\n  limit @0 :UInt32 = 0xffffffff;\n\n  prefix @1 :Text;\n  cursor @2 :Text;\n  delimiter @3 :Text;\n  startAfter @4 :Text;\n\n  include @5 :List(UInt16);\n  # Additional fields to include in the response that might not normally be rendered.\n  # The values are all IncludeField but we can't actually have that here because otherwise\n  # capnp's builtin JSON encoder will print the enum name instead of the value.\n\n  newRuntime @6 :Bool;\n  # We used to send the include but not honor it. Newer versions of the runtime will send this to\n  # indicate we're sending a version of the `include` that can be honored. Before that the R2 worker\n  # should assume include always means http & custom metadata should be returned. This is a\n  # transitionary field just to avoid coupling needing to simultaneously release the Worker &\n  # runtime and can be removed after the release cut for the week of July 4, 2022.\n\n  enum IncludeField @0xc02f1c58744671f1 {\n    http @0;\n    custom @1;\n  }\n}\n\nstruct R2DeleteRequest {\n  union {\n    object @0 :Text;\n    objects @1 :List(Text);\n  }\n}\n\nstruct R2CreateBucketRequest {\n  bucket @0 :Text;\n}\n\nstruct R2ListBucketRequest {\n  limit @0 :UInt32 = 0xffffffff;\n\n  prefix @1 :Text;\n  cursor @2 :Text;\n}\n\nstruct R2DeleteBucketRequest {\n  bucket @0 :Text;\n}\n\nstruct R2ErrorResponse {\n  version @0 :UInt32;\n  v4code @1 :UInt32;\n  # Bindings use the same error code space as the V4 API.\n  message @2 :Text;\n}\n\nstruct R2SSECResponse {\n  algorithm @0 :Text;\n  keyMd5 @1 :Text;\n}\n\nstruct R2HeadResponse {\n  name @0 :Text;\n  # The name of the object.\n\n  version @1 :Text;\n  # The version ID of the object.\n\n  size @2 :UInt64;\n  # The total size of the object in bytes.\n\n  etag @3 :Text;\n  # The ETag the object has currently.\n\n  uploadedMillisecondsSinceEpoch @4 :UInt64 $Json.name(\"uploaded\");\n  # The timestamp of when the object was uploaded.\n\n  httpFields @5 :R2HttpFields;\n  # The HTTP headers that we were asked to associate with this object on upload.\n\n  customFields @6 :List(Record);\n  # Arbitrary key-value pairs that we were asked to associate with this object on upload.\n  # Since cap'n'proto doesn't really have a natural key-value type, we emulate it with an exploded\n  # list of the entries of the map.\n\n  range @7 :R2Range;\n  # If set, an echo of the range that was requested.\n\n  checksums @8 :R2Checksums;\n  # If set, the available checksums for this object\n\n  storageClass @9 :Text;\n  # The storage class of the object. Standard or Infrequent Access.\n  # Provided on object creation to specify which storage tier R2 should use for this object.\n\n  ssec @10 :R2SSECResponse;\n  # The algorithm/key hash used for encryption(if the user used SSE-C)\n}\n\nusing R2GetResponse = R2HeadResponse;\n\nusing R2PutResponse = R2HeadResponse;\n\nstruct R2CreateMultipartUploadResponse {\n  uploadId @0 :Text;\n  # The unique identifier of this object, required for subsequent operations on\n  # this multipart upload.\n  ssec @1 :R2SSECResponse;\n  # The algorithm/key hash used for encryption(if the user used SSE-C)\n}\n\nstruct R2UploadPartResponse {\n  etag @0 :Text;\n  # The ETag the of the uploaded part.\n  # This ETag is required in order to complete the multipart upload.\n}\n\nusing R2CompleteMultipartUploadResponse = R2PutResponse;\n\nstruct R2AbortMultipartUploadResponse {}\n\nstruct R2ListResponse {\n  objects @0 :List(R2HeadResponse);\n  truncated @1 :Bool;\n  cursor @2 :Text;\n  delimitedPrefixes @3 :List(Text);\n}\n\nstruct R2DeleteResponse {}\n\nstruct R2CreateBucketResponse {}\nstruct R2ListBucketResponse {\n  buckets @0 :List(Bucket);\n  truncated @1 :Bool;\n  cursor @2 :Text;\n\n  struct Bucket {\n    name @0 :Text;\n    createdMillisecondsSinceEpoch @1 :UInt64 $Json.name(\"created\");\n  }\n}\nstruct R2DeleteBucketResponse {}\n"
  },
  {
    "path": "src/workerd/api/r2-bucket.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"r2-bucket.h\"\n\n#include \"r2-multipart.h\"\n#include \"r2-rpc.h\"\n#include \"util.h\"\n\n#include <workerd/api/http.h>\n#include <workerd/api/r2-api.capnp.h>\n#include <workerd/api/streams/readable.h>\n#include <workerd/util/http-util.h>\n\n#include <capnp/compat/json.h>\n#include <capnp/message.h>\n#include <kj/compat/http.h>\n#include <kj/encoding.h>\n\n#include <array>\n#include <cmath>\n#include <regex>\n\nnamespace workerd::api::public_beta {\nkj::Own<kj::HttpClient> r2GetClient(\n    IoContext& context, uint subrequestChannel, R2UserTracing user) {\n  TraceContext traceContext = context.makeUserTraceSpan(user.op);\n  traceContext.setTag(\"rpc.service\"_kjc, \"r2\"_kjc);\n  traceContext.setTag(user.method.key, user.method.value);\n  KJ_IF_SOME(b, user.bucket) {\n    traceContext.setTag(\"cloudflare.r2.bucket\"_kjc, b);\n  }\n  KJ_IF_SOME(tag, user.extraTag) {\n    traceContext.setTag(tag.key, tag.value);\n  }\n\n  // TODO(o11y): Attach trace context to awaitIo call to match operation lifetime better?\n  return context.getHttpClient(subrequestChannel, true, kj::none, traceContext)\n      .attach(kj::mv(traceContext));\n}\n\nstatic bool isWholeNumber(double x) {\n  double intpart;\n  return modf(x, &intpart) == 0;\n}\n\n// TODO(perf): Would be nice to expose the v8 internals for parsing a date/stringifying it as\n// something an embedder can call directly rather than doing this rigamarole. It would also avoid\n// concerns about the user overriding the methods we're invoking.\nstatic kj::Date parseDate(jsg::Lock& js, kj::StringPtr value) {\n  return js.date(value);\n}\n\nstatic kj::String toUTCString(jsg::Lock& js, kj::Date date) {\n  return js.date(date).toUTCString(js);\n}\n\nstatic kj::String toISOString(jsg::Lock& js, kj::Date date) {\n  return js.date(date).toISOString(js);\n}\n\nenum class OptionalMetadata : uint16_t {\n  Http = static_cast<uint8_t>(R2ListRequest::IncludeField::HTTP),\n  Custom = static_cast<uint8_t>(R2ListRequest::IncludeField::CUSTOM),\n};\n\ntemplate <typename T>\nconcept HeadResultT = std::is_base_of_v<R2Bucket::HeadResult, T>;\n\ntemplate <HeadResultT T, typename... Args>\nstatic jsg::Ref<T> parseObjectMetadata(jsg::Lock& js,\n    R2HeadResponse::Reader responseReader,\n    kj::ArrayPtr<const OptionalMetadata> expectedOptionalFields,\n    Args&&... args) {\n  // optionalFieldsExpected is initialized by default to HTTP + CUSTOM if the user doesn't specify\n  // anything. If they specify the empty array, then nothing is returned.\n  kj::Date uploaded =\n      kj::UNIX_EPOCH + responseReader.getUploadedMillisecondsSinceEpoch() * kj::MILLISECONDS;\n\n  jsg::Optional<R2Bucket::HttpMetadata> httpMetadata;\n  if (responseReader.hasHttpFields()) {\n    R2Bucket::HttpMetadata m;\n\n    auto httpFields = responseReader.getHttpFields();\n    if (httpFields.hasContentType()) {\n      m.contentType = kj::str(httpFields.getContentType());\n    }\n    if (httpFields.hasContentDisposition()) {\n      m.contentDisposition = kj::str(httpFields.getContentDisposition());\n    }\n    if (httpFields.hasContentEncoding()) {\n      m.contentEncoding = kj::str(httpFields.getContentEncoding());\n    }\n    if (httpFields.hasContentLanguage()) {\n      m.contentLanguage = kj::str(httpFields.getContentLanguage());\n    }\n    if (httpFields.hasCacheControl()) {\n      m.cacheControl = kj::str(httpFields.getCacheControl());\n    }\n    if (httpFields.getCacheExpiry() != 0xffffffffffffffff) {\n      m.cacheExpiry = kj::UNIX_EPOCH + httpFields.getCacheExpiry() * kj::MILLISECONDS;\n    }\n\n    httpMetadata = kj::mv(m);\n  } else if (std::find(expectedOptionalFields.begin(), expectedOptionalFields.end(),\n                 OptionalMetadata::Http) != expectedOptionalFields.end()) {\n    // HTTP metadata was asked for but the object didn't have anything.\n    httpMetadata = R2Bucket::HttpMetadata{};\n  }\n\n  jsg::Optional<jsg::Dict<kj::String>> customMetadata;\n  if (responseReader.hasCustomFields()) {\n    customMetadata = jsg::Dict<kj::String>{.fields =\n                                               KJ_MAP(field, responseReader.getCustomFields()) {\n      jsg::Dict<kj::String>::Field item;\n      item.name = kj::str(field.getK());\n      item.value = kj::str(field.getV());\n      return item;\n    }};\n  } else if (std::find(expectedOptionalFields.begin(), expectedOptionalFields.end(),\n                 OptionalMetadata::Custom) != expectedOptionalFields.end()) {\n    // Custom metadata was asked for but the object didn't have anything.\n    customMetadata = jsg::Dict<kj::String>{};\n  }\n\n  jsg::Optional<R2Bucket::Range> range;\n\n  if (responseReader.hasRange()) {\n    auto rangeBuilder = responseReader.getRange();\n    range = R2Bucket::Range{\n      .offset = static_cast<double>(rangeBuilder.getOffset()),\n      .length = static_cast<double>(rangeBuilder.getLength()),\n    };\n  }\n\n  jsg::Ref<R2Bucket::Checksums> checksums =\n      js.alloc<R2Bucket::Checksums>(kj::none, kj::none, kj::none, kj::none, kj::none);\n\n  if (responseReader.hasChecksums()) {\n    R2Checksums::Reader checksumsBuilder = responseReader.getChecksums();\n    if (checksumsBuilder.hasMd5()) {\n      // Note that we don't check the length of checksums in here. We know\n      // that some artifact were stored with truncated checksums (e.g. 8 bytes\n      // instead of 16 for some). We're not validating the checksum lengths\n      // here and instead we're just passing them through.\n      checksums->md5 = kj::heapArray(checksumsBuilder.getMd5());\n    }\n    if (checksumsBuilder.hasSha1()) {\n      checksums->sha1 = kj::heapArray(checksumsBuilder.getSha1());\n    }\n    if (checksumsBuilder.hasSha256()) {\n      checksums->sha256 = kj::heapArray(checksumsBuilder.getSha256());\n    }\n    if (checksumsBuilder.hasSha384()) {\n      checksums->sha384 = kj::heapArray(checksumsBuilder.getSha384());\n    }\n    if (checksumsBuilder.hasSha512()) {\n      checksums->sha512 = kj::heapArray(checksumsBuilder.getSha512());\n    }\n  }\n\n  jsg::Optional<kj::String> ssecKeyMd5;\n\n  if (responseReader.hasSsec()) {\n    auto ssecBuilder = responseReader.getSsec();\n    ssecKeyMd5 = kj::str(ssecBuilder.getKeyMd5());\n  }\n\n  return js.alloc<T>(kj::str(responseReader.getName()), kj::str(responseReader.getVersion()),\n      responseReader.getSize(), kj::str(responseReader.getEtag()), kj::mv(checksums), uploaded,\n      kj::mv(httpMetadata), kj::mv(customMetadata), range,\n      kj::str(responseReader.getStorageClass()), kj::mv(ssecKeyMd5), kj::fwd<Args>(args)...);\n}\n\ntemplate <HeadResultT T, typename... Args>\nstatic kj::Maybe<jsg::Ref<T>> parseObjectMetadata(jsg::Lock& js,\n    kj::StringPtr action,\n    R2Result& r2Result,\n    const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType,\n    Args&&... args) {\n  if (r2Result.objectNotFound()) {\n    return kj::none;\n  }\n  if (!r2Result.preconditionFailed()) {\n    r2Result.throwIfError(action, errorType);\n  }\n\n  // Non-list operations always return these.\n  std::array expectedFieldsOwned = {OptionalMetadata::Http, OptionalMetadata::Custom};\n  kj::ArrayPtr<OptionalMetadata> expectedFields = {\n    expectedFieldsOwned.data(), expectedFieldsOwned.size()};\n\n  capnp::MallocMessageBuilder responseMessage;\n  capnp::JsonCodec json;\n  // Annoyingly our R2GetResponse alias isn't emitted.\n  json.handleByAnnotation<R2HeadResponse>();\n  auto responseBuilder = responseMessage.initRoot<R2HeadResponse>();\n  json.decode(KJ_ASSERT_NONNULL(r2Result.metadataPayload), responseBuilder);\n\n  return parseObjectMetadata<T>(js, responseBuilder, expectedFields, kj::fwd<Args>(args)...);\n}\n\nnamespace {\n\nvoid addEtagsToBuilder(\n    capnp::List<R2Etag>::Builder etagListBuilder, kj::ArrayPtr<R2Bucket::Etag> etagArray) {\n  R2Bucket::Etag* currentEtag = etagArray.begin();\n  for (unsigned int i = 0; i < etagArray.size(); i++) {\n    KJ_SWITCH_ONEOF(*currentEtag) {\n      KJ_CASE_ONEOF(e, R2Bucket::WildcardEtag) {\n        etagListBuilder[i].initType().setWildcard();\n      }\n      KJ_CASE_ONEOF(e, R2Bucket::StrongEtag) {\n        etagListBuilder[i].initType().setStrong();\n        etagListBuilder[i].setValue(e.value);\n      }\n      KJ_CASE_ONEOF(e, R2Bucket::WeakEtag) {\n        etagListBuilder[i].initType().setWeak();\n        etagListBuilder[i].setValue(e.value);\n      }\n    }\n    currentEtag = std::next(currentEtag);\n  }\n}\n\n}  // namespace\n\nkj::String buildEtagsString(kj::ArrayPtr<R2Bucket::Etag> etagArray) {\n  return kj::strArray(KJ_MAP(etag, etagArray) {\n    KJ_SWITCH_ONEOF(etag) {\n      KJ_CASE_ONEOF(e, R2Bucket::WildcardEtag) {\n        return kj::str(\"*\");\n      }\n      KJ_CASE_ONEOF(e, R2Bucket::StrongEtag) {\n        return kj::str(e.value);\n      }\n      KJ_CASE_ONEOF(e, R2Bucket::WeakEtag) {\n        return kj::str(e.value);\n      }\n    }\n    KJ_UNREACHABLE;\n  }, \", \");\n}\n\ntemplate <typename Builder, typename Options>\nvoid initOnlyIf(TraceContext& traceContext, jsg::Lock& js, Builder& builder, Options& o) {\n  KJ_IF_SOME(i, o.onlyIf) {\n    R2Bucket::UnwrappedConditional c = [&] {\n      KJ_SWITCH_ONEOF(i) {\n        KJ_CASE_ONEOF(conditional, R2Bucket::Conditional) {\n          return R2Bucket::UnwrappedConditional(conditional);\n        }\n        KJ_CASE_ONEOF(h, jsg::Ref<Headers>) {\n          return R2Bucket::UnwrappedConditional(js, *h);\n        }\n      }\n      KJ_UNREACHABLE;\n    }();\n\n    R2Conditional::Builder onlyIfBuilder = builder.initOnlyIf();\n    KJ_IF_SOME(etagArray, c.etagMatches) {\n      capnp::List<R2Etag>::Builder etagMatchList = onlyIfBuilder.initEtagMatches(etagArray.size());\n      addEtagsToBuilder(\n          etagMatchList, kj::arrayPtr<R2Bucket::Etag>(etagArray.begin(), etagArray.size()));\n      traceContext.setTag(\n          \"cloudflare.r2.request.only_if.etag_matches\"_kjc, buildEtagsString(etagArray));\n    }\n    KJ_IF_SOME(etagArray, c.etagDoesNotMatch) {\n      auto etagDoesNotMatchList = onlyIfBuilder.initEtagDoesNotMatch(etagArray.size());\n      addEtagsToBuilder(\n          etagDoesNotMatchList, kj::arrayPtr<R2Bucket::Etag>(etagArray.begin(), etagArray.size()));\n      traceContext.setTag(\n          \"cloudflare.r2.request.only_if.etag_does_not_match\"_kjc, buildEtagsString(etagArray));\n    }\n    KJ_IF_SOME(d, c.uploadedBefore) {\n      onlyIfBuilder.setUploadedBefore((d - kj::UNIX_EPOCH) / kj::MILLISECONDS);\n      if (c.secondsGranularity) {\n        onlyIfBuilder.setSecondsGranularity(true);\n      }\n      traceContext.setTag(\"cloudflare.r2.request.only_if.uploaded_before\"_kjc, toISOString(js, d));\n    }\n    KJ_IF_SOME(d, c.uploadedAfter) {\n      onlyIfBuilder.setUploadedAfter((d - kj::UNIX_EPOCH) / kj::MILLISECONDS);\n      if (c.secondsGranularity) {\n        onlyIfBuilder.setSecondsGranularity(true);\n      }\n      traceContext.setTag(\"cloudflare.r2.request.only_if.uploaded_after\"_kjc, toISOString(js, d));\n    }\n  }\n}\n\nkj::Maybe<kj::String> buildSsecKey(\n    kj::Maybe<kj::OneOf<kj::Array<byte>, kj::String>> maybeRawSsecKey) {\n  KJ_IF_SOME(rawSsecKey, maybeRawSsecKey) {\n    KJ_SWITCH_ONEOF(rawSsecKey) {\n      KJ_CASE_ONEOF(keyString, kj::String) {\n        JSG_REQUIRE(std::regex_match(keyString.begin(), keyString.end(), std::regex(\"^[0-9a-f]+$\")),\n            Error, \"SSE-C Key has invalid format\");\n        JSG_REQUIRE(keyString.size() == 64, Error, \"SSE-C Key must be 32 bytes in length\");\n        return kj::str(keyString);\n      }\n      KJ_CASE_ONEOF(keyBuff, kj::Array<byte>) {\n        JSG_REQUIRE(keyBuff.size() == 32, Error, \"SSE-C Key must be 32 bytes in length\");\n        return kj::encodeHex(keyBuff);\n      }\n    }\n  }\n  return kj::none;\n}\n\nstatic void addR2ResponseSpanTags(TraceContext& traceContext, R2Result& r2Result) {\n  traceContext.setTag(\"cloudflare.r2.response.success\"_kjc, r2Result.success());\n  KJ_IF_SOME(e, r2Result.getR2ErrorMessage()) {\n    traceContext.setTag(\"error.type\"_kjc, e.asPtr());\n    traceContext.setTag(\"cloudflare.r2.error.message\"_kjc, e.asPtr());\n  }\n  KJ_IF_SOME(v4, r2Result.v4ErrorCode()) {\n    traceContext.setTag(\"cloudflare.r2.error.code\"_kjc, static_cast<int64_t>(v4));\n  }\n}\n\nvoid addHeadResultSpanTags(\n    jsg::Lock& js, TraceContext& traceContext, R2Bucket::HeadResult& headResult) {\n  traceContext.setTag(\"cloudflare.r2.response.etag\"_kjc, headResult.getEtag());\n  traceContext.setTag(\"cloudflare.r2.response.size\"_kjc, headResult.getSize());\n  traceContext.setTag(\n      \"cloudflare.r2.response.uploaded\"_kjc, toISOString(js, headResult.getUploaded()));\n  auto checksums = headResult.getChecksums();\n  KJ_IF_SOME(md5, checksums.get()->md5) {\n    traceContext.setTag(\"cloudflare.r2.response.checksum.md5\"_kjc, kj::encodeHex(md5));\n  }\n  KJ_IF_SOME(sha1, checksums.get()->sha1) {\n    traceContext.setTag(\"cloudflare.r2.response.checksum.sha1\"_kjc, kj::encodeHex(sha1));\n  }\n  KJ_IF_SOME(sha256, checksums.get()->sha256) {\n    traceContext.setTag(\"cloudflare.r2.response.checksum.sha256\"_kjc, kj::encodeHex(sha256));\n  }\n  KJ_IF_SOME(sha384, checksums.get()->sha384) {\n    traceContext.setTag(\"cloudflare.r2.response.checksum.sha384\"_kjc, kj::encodeHex(sha384));\n  }\n  KJ_IF_SOME(sha512, checksums.get()->sha512) {\n    traceContext.setTag(\"cloudflare.r2.response.checksum.sha512\"_kjc, kj::encodeHex(sha512));\n  }\n\n  traceContext.setTag(\"cloudflare.r2.response.storage_class\"_kjc, headResult.getStorageClass());\n  KJ_IF_SOME(_, headResult.getSSECKeyMd5()) {\n    traceContext.setTag(\"cloudflare.r2.response.ssec_key\"_kjc, true);\n  }\n  KJ_IF_SOME(httpMetadata, headResult.getHttpMetadata()) {\n    KJ_IF_SOME(ct, httpMetadata.contentType) {\n      traceContext.setTag(\"cloudflare.r2.response.content_type\"_kjc, ct.asPtr());\n    }\n    KJ_IF_SOME(ce, httpMetadata.contentEncoding) {\n      traceContext.setTag(\"cloudflare.r2.response.content_encoding\"_kjc, ce.asPtr());\n    }\n    KJ_IF_SOME(cd, httpMetadata.contentDisposition) {\n      traceContext.setTag(\"cloudflare.r2.response.content_disposition\"_kjc, cd.asPtr());\n    }\n    KJ_IF_SOME(cl, httpMetadata.contentLanguage) {\n      traceContext.setTag(\"cloudflare.r2.response.content_language\"_kjc, cl.asPtr());\n    }\n    KJ_IF_SOME(cc, httpMetadata.cacheControl) {\n      traceContext.setTag(\"cloudflare.r2.response.cache_control\"_kjc, cc.asPtr());\n    }\n    KJ_IF_SOME(ce, httpMetadata.cacheExpiry) {\n      traceContext.setTag(\"cloudflare.r2.response.cache_expiry\"_kjc, toISOString(js, ce));\n    }\n  }\n  KJ_IF_SOME(_, headResult.getCustomMetadata()) {\n    traceContext.setTag(\"cloudflare.r2.response.custom_metadata\"_kjc, true);\n  }\n}\n\ntemplate <typename Builder, typename Options>\nvoid initGetOptions(TraceContext& traceContext, jsg::Lock& js, Builder& builder, Options& o) {\n  initOnlyIf(traceContext, js, builder, o);\n  KJ_IF_SOME(range, o.range) {\n    KJ_SWITCH_ONEOF(range) {\n      KJ_CASE_ONEOF(r, R2Bucket::Range) {\n        auto rangeBuilder = builder.initRange();\n        KJ_IF_SOME(offset, r.offset) {\n          JSG_REQUIRE(offset >= 0, RangeError, \"Invalid range. Starting offset (\", offset,\n              \") must be greater than or equal to 0.\");\n          JSG_REQUIRE(isWholeNumber(offset), RangeError, \"Invalid range. Starting offset (\", offset,\n              \") must be an integer, not floating point.\");\n          rangeBuilder.setOffset(static_cast<uint64_t>(offset));\n          traceContext.setTag(\n              \"cloudflare.r2.request.range.offset\"_kjc, static_cast<int64_t>(offset));\n        }\n\n        KJ_IF_SOME(length, r.length) {\n          JSG_REQUIRE(length >= 0, RangeError, \"Invalid range. Length (\", length,\n              \") must be greater than or equal to 0.\");\n          JSG_REQUIRE(isWholeNumber(length), RangeError, \"Invalid range. Length (\", length,\n              \") must be an integer, not floating point.\");\n\n          rangeBuilder.setLength(static_cast<uint64_t>(length));\n          traceContext.setTag(\n              \"cloudflare.r2.request.range.length\"_kjc, static_cast<int64_t>(length));\n        }\n        KJ_IF_SOME(suffix, r.suffix) {\n          JSG_REQUIRE(r.offset == kj::none, TypeError, \"Suffix is incompatible with offset.\");\n          JSG_REQUIRE(r.length == kj::none, TypeError, \"Suffix is incompatible with length.\");\n\n          JSG_REQUIRE(suffix >= 0, RangeError, \"Invalid suffix. Suffix (\", suffix,\n              \") must be greater than or equal to 0.\");\n          JSG_REQUIRE(isWholeNumber(suffix), RangeError, \"Invalid range. Suffix (\", suffix,\n              \") must be an integer, not floating point.\");\n\n          rangeBuilder.setSuffix(static_cast<uint64_t>(suffix));\n          traceContext.setTag(\n              \"cloudflare.r2.request.range.suffix\"_kjc, static_cast<int64_t>(suffix));\n        }\n      }\n\n      KJ_CASE_ONEOF(h, jsg::Ref<Headers>) {\n        KJ_IF_SOME(e, h->getCommon(js, capnp::CommonHeaderName::RANGE)) {\n          builder.setRangeHeader(kj::str(e));\n          traceContext.setTag(\"cloudflare.r2.request.range\"_kjc, e.asPtr());\n        }\n      }\n    }\n  }\n  kj::Maybe<kj::String> maybeSsecKey = buildSsecKey(kj::mv(o.ssecKey));\n  KJ_IF_SOME(ssecKey, maybeSsecKey) {\n    auto ssecBuilder = builder.initSsec();\n    ssecBuilder.setKey(ssecKey);\n    traceContext.setTag(\"cloudflare.r2.request.ssec_key\"_kjc, true);\n  }\n}\n\nstatic bool isQuotedEtag(kj::StringPtr etag) {\n  return etag.startsWith(\"\\\"\") && etag.endsWith(\"\\\"\");\n}\n\njsg::Promise<kj::Maybe<jsg::Ref<R2Bucket::HeadResult>>> R2Bucket::head(jsg::Lock& js,\n    kj::String key,\n    const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType,\n    CompatibilityFlags::Reader flags) {\n  return js.evalNow([&] {\n    auto& context = IoContext::current();\n    TraceContext traceContext = context.makeUserTraceSpan(\"r2_head\"_kjc);\n\n    traceContext.setTag(\"cloudflare.binding.type\"_kjc, \"r2\"_kjc);\n    KJ_IF_SOME(b, this->bindingName()) {\n      traceContext.setTag(\"cloudflare.binding.name\"_kjc, b);\n    }\n    traceContext.setTag(\"cloudflare.r2.operation\"_kjc, \"HeadObject\"_kjc);\n    KJ_IF_SOME(b, this->bucketName()) {\n      traceContext.setTag(\"cloudflare.r2.bucket\"_kjc, b);\n    }\n    traceContext.setTag(\"cloudflare.r2.request.key\"_kjc, key.asPtr());\n\n    capnp::JsonCodec json;\n    json.handleByAnnotation<R2BindingRequest>();\n    json.setHasMode(capnp::HasMode::NON_DEFAULT);\n    capnp::MallocMessageBuilder requestMessage;\n\n    auto requestBuilder = requestMessage.initRoot<R2BindingRequest>();\n    requestBuilder.setVersion(VERSION_PUBLIC_BETA);\n    auto payloadBuilder = requestBuilder.initPayload();\n    auto headBuilder = payloadBuilder.initHead();\n    headBuilder.setObject(key);\n\n    auto requestJson = json.encode(requestBuilder);\n    kj::StringPtr components[1];\n    auto path = fillR2Path(components, adminBucket);\n    auto client = context.getHttpClient(clientIndex, true, kj::none, traceContext);\n    auto promise = doR2HTTPGetRequest(kj::mv(client), kj::mv(requestJson), path, jwt, flags);\n\n    return context.awaitIo(js, kj::mv(promise),\n        [&errorType, traceContext = kj::mv(traceContext)](\n            jsg::Lock& js, R2Result r2Result) mutable {\n      addR2ResponseSpanTags(traceContext, r2Result);\n\n      auto result = parseObjectMetadata<HeadResult>(js, \"head\", r2Result, errorType);\n      KJ_IF_SOME(r, result) {\n        addHeadResultSpanTags(js, traceContext, *r.get());\n      }\n      return result;\n    });\n  });\n}\n\nR2Bucket::FeatureFlags::FeatureFlags(CompatibilityFlags::Reader featureFlags)\n    : listHonorsIncludes(featureFlags.getR2ListHonorIncludeFields()) {}\n\njsg::Promise<kj::OneOf<kj::Maybe<jsg::Ref<R2Bucket::GetResult>>, jsg::Ref<R2Bucket::HeadResult>>>\nR2Bucket::get(jsg::Lock& js,\n    kj::String key,\n    jsg::Optional<GetOptions> options,\n    const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType,\n    CompatibilityFlags::Reader flags) {\n  return js.evalNow([&] {\n    auto& context = IoContext::current();\n    TraceContext traceContext = context.makeUserTraceSpan(\"r2_get\"_kjc);\n\n    traceContext.setTag(\"cloudflare.binding.type\"_kjc, \"r2\"_kjc);\n    KJ_IF_SOME(b, this->bindingName()) {\n      traceContext.setTag(\"cloudflare.binding.name\"_kjc, b);\n    }\n    traceContext.setTag(\"cloudflare.r2.operation\"_kjc, \"GetObject\"_kjc);\n    KJ_IF_SOME(b, this->bucketName()) {\n      traceContext.setTag(\"cloudflare.r2.bucket\"_kjc, b);\n    }\n    traceContext.setTag(\"cloudflare.r2.request.key\"_kjc, key.asPtr());\n\n    capnp::JsonCodec json;\n    json.handleByAnnotation<R2BindingRequest>();\n    json.setHasMode(capnp::HasMode::NON_DEFAULT);\n    capnp::MallocMessageBuilder requestMessage;\n\n    auto requestBuilder = requestMessage.initRoot<R2BindingRequest>();\n    requestBuilder.setVersion(VERSION_PUBLIC_BETA);\n    auto payloadBuilder = requestBuilder.initPayload();\n    auto getBuilder = payloadBuilder.initGet();\n    getBuilder.setObject(key);\n\n    KJ_IF_SOME(o, options) {\n      initGetOptions(traceContext, js, getBuilder, o);\n    }\n    auto requestJson = json.encode(requestBuilder);\n    kj::StringPtr components[1];\n    auto path = fillR2Path(components, adminBucket);\n    auto client = context.getHttpClient(clientIndex, true, kj::none, traceContext);\n    auto promise = doR2HTTPGetRequest(kj::mv(client), kj::mv(requestJson), path, jwt, flags);\n\n    return context.awaitIo(js, kj::mv(promise),\n        [&context, &errorType, traceContext = kj::mv(traceContext)](\n            jsg::Lock& js, R2Result r2Result) mutable\n        -> kj::OneOf<kj::Maybe<jsg::Ref<GetResult>>, jsg::Ref<HeadResult>> {\n      kj::OneOf<kj::Maybe<jsg::Ref<GetResult>>, jsg::Ref<HeadResult>> result;\n\n      addR2ResponseSpanTags(traceContext, r2Result);\n      if (r2Result.preconditionFailed()) {\n        result = KJ_ASSERT_NONNULL(parseObjectMetadata<HeadResult>(js, \"get\", r2Result, errorType));\n      } else {\n        jsg::Ref<ReadableStream> body = nullptr;\n\n        KJ_IF_SOME(s, r2Result.stream) {\n          body = js.alloc<ReadableStream>(context, kj::mv(s));\n          r2Result.stream = kj::none;\n        }\n        result = parseObjectMetadata<GetResult>(js, \"get\", r2Result, errorType, kj::mv(body));\n      }\n\n      KJ_SWITCH_ONEOF(result) {\n        KJ_CASE_ONEOF(o, jsg::Ref<HeadResult>) {\n          addHeadResultSpanTags(js, traceContext, *o.get());\n        }\n        KJ_CASE_ONEOF(maybeHeadResult, kj::Maybe<jsg::Ref<GetResult>>) {\n          KJ_IF_SOME(o, maybeHeadResult) {\n            addHeadResultSpanTags(js, traceContext, *o.get());\n          }\n        }\n      }\n\n      return result;\n    });\n  });\n}\n\njsg::Promise<kj::Maybe<jsg::Ref<R2Bucket::HeadResult>>> R2Bucket::put(jsg::Lock& js,\n    kj::String key,\n    kj::Maybe<R2PutValue> value,\n    jsg::Optional<PutOptions> options,\n    const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType) {\n  return js.evalNow([&] {\n    auto cancelReader = kj::defer([&] {\n      KJ_IF_SOME(v, value) {\n        KJ_SWITCH_ONEOF(v) {\n          KJ_CASE_ONEOF(v, jsg::Ref<ReadableStream>) {\n            (*v).cancel(js,\n                js.v8Error(\n                    \"Stream cancelled because the associated put operation encountered an error.\"));\n          }\n          KJ_CASE_ONEOF_DEFAULT {}\n        }\n      }\n    });\n\n    auto& context = IoContext::current();\n    TraceContext traceContext = context.makeUserTraceSpan(\"r2_put\"_kjc);\n\n    traceContext.setTag(\"cloudflare.binding.type\"_kjc, \"r2\"_kjc);\n    KJ_IF_SOME(b, this->bindingName()) {\n      traceContext.setTag(\"cloudflare.binding.name\"_kjc, b);\n    }\n    traceContext.setTag(\"cloudflare.r2.operation\"_kjc, \"PutObject\"_kjc);\n    KJ_IF_SOME(b, this->bucketName()) {\n      traceContext.setTag(\"cloudflare.r2.bucket\"_kjc, b);\n    }\n    traceContext.setTag(\"cloudflare.r2.request.key\"_kjc, key.asPtr());\n\n    capnp::JsonCodec json;\n    json.handleByAnnotation<R2BindingRequest>();\n    json.setHasMode(capnp::HasMode::NON_DEFAULT);\n    capnp::MallocMessageBuilder requestMessage;\n\n    auto requestBuilder = requestMessage.initRoot<R2BindingRequest>();\n    requestBuilder.setVersion(VERSION_PUBLIC_BETA);\n    auto payloadBuilder = requestBuilder.initPayload();\n    auto putBuilder = payloadBuilder.initPut();\n    putBuilder.setObject(key);\n\n    HttpMetadata sentHttpMetadata;\n    jsg::Dict<kj::String> sentCustomMetadata;\n\n    bool hashAlreadySpecified = false;\n    const auto verifyHashNotSpecified = [&] {\n      JSG_REQUIRE(\n          !hashAlreadySpecified, TypeError, \"You cannot specify multiple hashing algorithms.\");\n      hashAlreadySpecified = true;\n    };\n\n    KJ_IF_SOME(o, options) {\n      initOnlyIf(traceContext, js, putBuilder, o);\n      KJ_IF_SOME(m, o.customMetadata) {\n        auto fields = putBuilder.initCustomFields(m.fields.size());\n        for (size_t i = 0; i < m.fields.size(); i++) {\n          fields[i].setK(m.fields[i].name);\n          fields[i].setV(m.fields[i].value);\n        }\n        sentCustomMetadata = kj::mv(m);\n        traceContext.setTag(\"cloudflare.r2.request.custom_metadata\"_kjc, true);\n      }\n      KJ_IF_SOME(m, o.httpMetadata) {\n        auto fields = putBuilder.initHttpFields();\n        sentHttpMetadata = [&]() {\n          KJ_SWITCH_ONEOF(m) {\n            KJ_CASE_ONEOF(m, HttpMetadata) {\n              return kj::mv(m);\n            }\n            KJ_CASE_ONEOF(h, jsg::Ref<Headers>) {\n              return HttpMetadata::fromRequestHeaders(js, *h);\n            }\n          }\n          KJ_UNREACHABLE;\n        }();\n\n        KJ_IF_SOME(ct, sentHttpMetadata.contentType) {\n          fields.setContentType(ct);\n          traceContext.setTag(\"cloudflare.r2.request.http_metadata.content_type\"_kjc, ct.asPtr());\n        }\n        KJ_IF_SOME(ce, sentHttpMetadata.contentEncoding) {\n          fields.setContentEncoding(ce);\n          traceContext.setTag(\n              \"cloudflare.r2.request.http_metadata.content_encoding\"_kjc, ce.asPtr());\n        }\n        KJ_IF_SOME(cd, sentHttpMetadata.contentDisposition) {\n          fields.setContentDisposition(cd);\n          traceContext.setTag(\n              \"cloudflare.r2.request.http_metadata.content_disposition\"_kjc, cd.asPtr());\n        }\n        KJ_IF_SOME(cl, sentHttpMetadata.contentLanguage) {\n          fields.setContentLanguage(cl);\n          traceContext.setTag(\n              \"cloudflare.r2.request.http_metadata.content_language\"_kjc, cl.asPtr());\n        }\n        KJ_IF_SOME(cc, sentHttpMetadata.cacheControl) {\n          fields.setCacheControl(cc);\n          traceContext.setTag(\"cloudflare.r2.request.http_metadata.cache_control\"_kjc, cc.asPtr());\n        }\n        KJ_IF_SOME(ce, sentHttpMetadata.cacheExpiry) {\n          fields.setCacheExpiry((ce - kj::UNIX_EPOCH) / kj::MILLISECONDS);\n          traceContext.setTag(\"cloudflare.r2.request.http_metadata.cache_expiry\"_kjc,\n              (ce - kj::UNIX_EPOCH) / kj::MILLISECONDS);\n        }\n      }\n      KJ_IF_SOME(md5, o.md5) {\n        verifyHashNotSpecified();\n        KJ_SWITCH_ONEOF(md5) {\n          KJ_CASE_ONEOF(bin, jsg::BufferSource) {\n            JSG_REQUIRE(bin.size() == 16, TypeError, \"MD5 is 16 bytes, not \", bin.size());\n            putBuilder.setMd5(bin.asArrayPtr());\n            traceContext.setTag(\"cloudflare.r2.request.checksum.type\"_kjc, \"md5\"_kjc);\n            traceContext.setTag(\n                \"cloudflare.r2.request.checksum.value\"_kjc, kj::encodeHex(bin.asArrayPtr()));\n          }\n          KJ_CASE_ONEOF(hex, jsg::NonCoercible<kj::String>) {\n            JSG_REQUIRE(hex.value.size() == 32, TypeError, \"MD5 is 32 hex characters, not \",\n                hex.value.size());\n            const auto decoded = kj::decodeHex(hex.value);\n            JSG_REQUIRE(!decoded.hadErrors, TypeError, \"Provided MD5 wasn't a valid hex string\");\n            putBuilder.setMd5(decoded);\n            traceContext.setTag(\"cloudflare.r2.request.checksum.type\"_kjc, \"md5\"_kjc);\n            traceContext.setTag(\"cloudflare.r2.request.checksum.value\"_kjc, hex.value.asPtr());\n          }\n        }\n      }\n      KJ_IF_SOME(sha1, o.sha1) {\n        verifyHashNotSpecified();\n        KJ_SWITCH_ONEOF(sha1) {\n          KJ_CASE_ONEOF(bin, jsg::BufferSource) {\n            JSG_REQUIRE(bin.size() == 20, TypeError, \"SHA-1 is 20 bytes, not \", bin.size());\n            putBuilder.setSha1(bin.asArrayPtr());\n            traceContext.setTag(\"cloudflare.r2.request.checksum.type\"_kjc, \"sha1\"_kjc);\n            traceContext.setTag(\n                \"cloudflare.r2.request.checksum.value\"_kjc, kj::encodeHex(bin.asArrayPtr()));\n          }\n          KJ_CASE_ONEOF(hex, jsg::NonCoercible<kj::String>) {\n            JSG_REQUIRE(hex.value.size() == 40, TypeError, \"SHA-1 is 40 hex characters, not \",\n                hex.value.size());\n            const auto decoded = kj::decodeHex(hex.value);\n            JSG_REQUIRE(!decoded.hadErrors, TypeError, \"Provided SHA-1 wasn't a valid hex string\");\n            putBuilder.setSha1(decoded);\n            traceContext.setTag(\"cloudflare.r2.request.checksum.type\"_kjc, \"sha1\"_kjc);\n            traceContext.setTag(\"cloudflare.r2.request.checksum.value\"_kjc, hex.value.asPtr());\n          }\n        }\n      }\n      KJ_IF_SOME(sha256, o.sha256) {\n        verifyHashNotSpecified();\n        KJ_SWITCH_ONEOF(sha256) {\n          KJ_CASE_ONEOF(bin, jsg::BufferSource) {\n            JSG_REQUIRE(bin.size() == 32, TypeError, \"SHA-256 is 32 bytes, not \", bin.size());\n            putBuilder.setSha256(bin.asArrayPtr());\n            traceContext.setTag(\"cloudflare.r2.request.checksum.type\"_kjc, \"sha256\"_kjc);\n            traceContext.setTag(\n                \"cloudflare.r2.request.checksum.value\"_kjc, kj::encodeHex(bin.asArrayPtr()));\n          }\n          KJ_CASE_ONEOF(hex, jsg::NonCoercible<kj::String>) {\n            JSG_REQUIRE(hex.value.size() == 64, TypeError, \"SHA-256 is 64 hex characters, not \",\n                hex.value.size());\n            const auto decoded = kj::decodeHex(hex.value);\n            JSG_REQUIRE(\n                !decoded.hadErrors, TypeError, \"Provided SHA-256 wasn't a valid hex string\");\n            putBuilder.setSha256(decoded);\n            traceContext.setTag(\"cloudflare.r2.request.checksum.type\"_kjc, \"sha256\"_kjc);\n            traceContext.setTag(\"cloudflare.r2.request.checksum.value\"_kjc, hex.value.asPtr());\n          }\n        }\n      }\n      KJ_IF_SOME(sha384, o.sha384) {\n        verifyHashNotSpecified();\n        KJ_SWITCH_ONEOF(sha384) {\n          KJ_CASE_ONEOF(bin, jsg::BufferSource) {\n            JSG_REQUIRE(bin.size() == 48, TypeError, \"SHA-384 is 48 bytes, not \", bin.size());\n            putBuilder.setSha384(bin.asArrayPtr());\n            traceContext.setTag(\"cloudflare.r2.request.checksum.type\"_kjc, \"sha384\"_kjc);\n            traceContext.setTag(\n                \"cloudflare.r2.request.checksum.value\"_kjc, kj::encodeHex(bin.asArrayPtr()));\n          }\n          KJ_CASE_ONEOF(hex, jsg::NonCoercible<kj::String>) {\n            JSG_REQUIRE(hex.value.size() == 96, TypeError, \"SHA-384 is 96 hex characters, not \",\n                hex.value.size());\n            const auto decoded = kj::decodeHex(hex.value);\n            JSG_REQUIRE(\n                !decoded.hadErrors, TypeError, \"Provided SHA-384 wasn't a valid hex string\");\n            putBuilder.setSha384(decoded);\n            traceContext.setTag(\"cloudflare.r2.request.checksum.type\"_kjc, \"sha384\"_kjc);\n            traceContext.setTag(\"cloudflare.r2.request.checksum.value\"_kjc, hex.value.asPtr());\n          }\n        }\n      }\n      KJ_IF_SOME(sha512, o.sha512) {\n        verifyHashNotSpecified();\n        KJ_SWITCH_ONEOF(sha512) {\n          KJ_CASE_ONEOF(bin, jsg::BufferSource) {\n            JSG_REQUIRE(bin.size() == 64, TypeError, \"SHA-512 is 64 bytes, not \", bin.size());\n            putBuilder.setSha512(bin.asArrayPtr());\n            traceContext.setTag(\"cloudflare.r2.request.checksum.type\"_kjc, \"sha512\"_kjc);\n            traceContext.setTag(\n                \"cloudflare.r2.request.checksum.value\"_kjc, kj::encodeHex(bin.asArrayPtr()));\n          }\n          KJ_CASE_ONEOF(hex, jsg::NonCoercible<kj::String>) {\n            JSG_REQUIRE(hex.value.size() == 128, TypeError, \"SHA-512 is 128 hex characters, not \",\n                hex.value.size());\n            const auto decoded = kj::decodeHex(hex.value);\n            JSG_REQUIRE(\n                !decoded.hadErrors, TypeError, \"Provided SHA-512 wasn't a valid hex string\");\n            putBuilder.setSha512(decoded);\n            traceContext.setTag(\"cloudflare.r2.request.checksum.type\"_kjc, \"sha512\"_kjc);\n            traceContext.setTag(\"cloudflare.r2.request.checksum.value\"_kjc, hex.value.asPtr());\n          }\n        }\n      }\n      KJ_IF_SOME(s, o.storageClass) {\n        putBuilder.setStorageClass(s);\n        traceContext.setTag(\"cloudflare.r2.request.storage_class\"_kjc, s.asPtr());\n      }\n      kj::Maybe<kj::String> maybeSsecKey = buildSsecKey(kj::mv(o.ssecKey));\n      KJ_IF_SOME(ssecKey, maybeSsecKey) {\n        auto ssecBuilder = putBuilder.initSsec();\n        ssecBuilder.setKey(ssecKey);\n        traceContext.setTag(\"cloudflare.r2.request.ssec_key\"_kjc, true);\n      }\n    }\n\n    KJ_IF_SOME(v, value) {\n      KJ_SWITCH_ONEOF(v) {\n        KJ_CASE_ONEOF(stream, jsg::Ref<ReadableStream>) {\n          KJ_IF_SOME(size, stream->tryGetLength(StreamEncoding::IDENTITY)) {\n            traceContext.setTag(\"cloudflare.r2.request.size\"_kjc, static_cast<int64_t>(size));\n          }\n        }\n        KJ_CASE_ONEOF(text, jsg::NonCoercible<kj::String>) {\n          traceContext.setTag(\n              \"cloudflare.r2.request.size\"_kjc, static_cast<int64_t>(text.value.size()));\n        }\n        KJ_CASE_ONEOF(data, kj::Array<byte>) {\n          traceContext.setTag(\"cloudflare.r2.request.size\"_kjc, static_cast<int64_t>(data.size()));\n        }\n        KJ_CASE_ONEOF(blob, jsg::Ref<Blob>) {\n          traceContext.setTag(\n              \"cloudflare.r2.request.size\"_kjc, static_cast<int64_t>(blob->getSize()));\n        }\n      }\n    }\n\n    auto requestJson = json.encode(requestBuilder);\n\n    cancelReader.cancel();\n    kj::StringPtr components[1];\n    auto path = fillR2Path(components, adminBucket);\n    auto client = context.getHttpClient(clientIndex, true, kj::none, traceContext);\n    auto promise =\n        doR2HTTPPutRequest(kj::mv(client), kj::mv(value), kj::none, kj::mv(requestJson), path, jwt);\n\n    return context.awaitIo(js, kj::mv(promise),\n        [sentHttpMetadata = kj::mv(sentHttpMetadata),\n            sentCustomMetadata = kj::mv(sentCustomMetadata), &errorType,\n            traceContext = kj::mv(traceContext)](\n            jsg::Lock& js, R2Result r2Result) mutable -> kj::Maybe<jsg::Ref<HeadResult>> {\n      addR2ResponseSpanTags(traceContext, r2Result);\n      if (r2Result.preconditionFailed()) {\n        return kj::none;\n      } else {\n        auto result = parseObjectMetadata<HeadResult>(js, \"put\", r2Result, errorType);\n        KJ_IF_SOME(o, result) {\n          o.get()->httpMetadata = kj::mv(sentHttpMetadata);\n          o.get()->customMetadata = kj::mv(sentCustomMetadata);\n          addHeadResultSpanTags(js, traceContext, *o.get());\n        }\n        return result;\n      }\n    });\n  });\n}\n\njsg::Promise<jsg::Ref<R2MultipartUpload>> R2Bucket::createMultipartUpload(jsg::Lock& js,\n    kj::String key,\n    jsg::Optional<MultipartOptions> options,\n    const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType) {\n  return js.evalNow([&] {\n    auto& context = IoContext::current();\n    TraceContext traceContext = context.makeUserTraceSpan(\"r2_createMultipartUpload\"_kjc);\n\n    traceContext.setTag(\"cloudflare.binding.type\"_kjc, \"r2\"_kjc);\n    KJ_IF_SOME(b, this->bindingName()) {\n      traceContext.setTag(\"cloudflare.binding.name\"_kjc, b);\n    }\n    traceContext.setTag(\"cloudflare.r2.operation\"_kjc, \"CreateMultipartUpload\"_kjc);\n    KJ_IF_SOME(b, this->bucketName()) {\n      traceContext.setTag(\"cloudflare.r2.bucket\"_kjc, b);\n    }\n    traceContext.setTag(\"cloudflare.r2.request.key\"_kjc, key.asPtr());\n\n    capnp::JsonCodec json;\n    json.handleByAnnotation<R2BindingRequest>();\n    json.setHasMode(capnp::HasMode::NON_DEFAULT);\n    capnp::MallocMessageBuilder requestMessage;\n\n    auto requestBuilder = requestMessage.initRoot<R2BindingRequest>();\n    requestBuilder.setVersion(VERSION_PUBLIC_BETA);\n    auto payloadBuilder = requestBuilder.initPayload();\n    auto createMultipartUploadBuilder = payloadBuilder.initCreateMultipartUpload();\n    createMultipartUploadBuilder.setObject(key);\n\n    KJ_IF_SOME(o, options) {\n      KJ_IF_SOME(m, o.customMetadata) {\n        auto fields = createMultipartUploadBuilder.initCustomFields(m.fields.size());\n        for (size_t i = 0; i < m.fields.size(); i++) {\n          fields[i].setK(m.fields[i].name);\n          fields[i].setV(m.fields[i].value);\n        }\n        traceContext.setTag(\"cloudflare.r2.request.custom_metadata\"_kjc, true);\n      }\n      KJ_IF_SOME(m, o.httpMetadata) {\n        auto fields = createMultipartUploadBuilder.initHttpFields();\n        HttpMetadata httpMetadata = [&]() {\n          KJ_SWITCH_ONEOF(m) {\n            KJ_CASE_ONEOF(m, HttpMetadata) {\n              return kj::mv(m);\n            }\n            KJ_CASE_ONEOF(h, jsg::Ref<Headers>) {\n              return HttpMetadata::fromRequestHeaders(js, *h);\n            }\n          }\n          KJ_UNREACHABLE;\n        }();\n\n        KJ_IF_SOME(ct, httpMetadata.contentType) {\n          fields.setContentType(ct);\n          traceContext.setTag(\"cloudflare.r2.request.http_metadata.content_type\"_kjc, ct.asPtr());\n        }\n        KJ_IF_SOME(ce, httpMetadata.contentEncoding) {\n          fields.setContentEncoding(ce);\n          traceContext.setTag(\n              \"cloudflare.r2.request.http_metadata.content_encoding\"_kjc, ce.asPtr());\n        }\n        KJ_IF_SOME(cd, httpMetadata.contentDisposition) {\n          fields.setContentDisposition(cd);\n          traceContext.setTag(\n              \"cloudflare.r2.request.http_metadata.content_disposition\"_kjc, cd.asPtr());\n        }\n        KJ_IF_SOME(cl, httpMetadata.contentLanguage) {\n          fields.setContentLanguage(cl);\n          traceContext.setTag(\n              \"cloudflare.r2.request.http_metadata.content_language\"_kjc, cl.asPtr());\n        }\n        KJ_IF_SOME(cc, httpMetadata.cacheControl) {\n          fields.setCacheControl(cc);\n          traceContext.setTag(\"cloudflare.r2.request.http_metadata.cache_control\"_kjc, cc.asPtr());\n        }\n        KJ_IF_SOME(ce, httpMetadata.cacheExpiry) {\n          fields.setCacheExpiry((ce - kj::UNIX_EPOCH) / kj::MILLISECONDS);\n          traceContext.setTag(\"cloudflare.r2.request.http_metadata.cache_expiry\"_kjc,\n              (ce - kj::UNIX_EPOCH) / kj::MILLISECONDS);\n        }\n      }\n      KJ_IF_SOME(s, o.storageClass) {\n        createMultipartUploadBuilder.setStorageClass(s);\n        traceContext.setTag(\"cloudflare.r2.request.storage_class\"_kjc, s.asPtr());\n      }\n      kj::Maybe<kj::String> maybeSsecKey = buildSsecKey(kj::mv(o.ssecKey));\n      KJ_IF_SOME(ssecKey, maybeSsecKey) {\n        auto ssecBuilder = createMultipartUploadBuilder.initSsec();\n        ssecBuilder.setKey(ssecKey);\n        traceContext.setTag(\"cloudflare.r2.request.ssec_key\"_kjc, true);\n      }\n    }\n\n    auto requestJson = json.encode(requestBuilder);\n    kj::StringPtr components[1];\n    auto path = fillR2Path(components, adminBucket);\n    auto client = context.getHttpClient(clientIndex, true, kj::none, traceContext);\n    auto promise =\n        doR2HTTPPutRequest(kj::mv(client), kj::none, kj::none, kj::mv(requestJson), path, jwt);\n\n    return context.awaitIo(js, kj::mv(promise),\n        [&errorType, key = kj::mv(key), this, traceContext = kj::mv(traceContext)](\n            jsg::Lock& js, R2Result r2Result) mutable {\n      addR2ResponseSpanTags(traceContext, r2Result);\n      r2Result.throwIfError(\"createMultipartUpload\", errorType);\n\n      capnp::MallocMessageBuilder responseMessage;\n      capnp::JsonCodec json;\n      json.handleByAnnotation<R2CreateMultipartUploadResponse>();\n      auto responseBuilder = responseMessage.initRoot<R2CreateMultipartUploadResponse>();\n\n      json.decode(KJ_ASSERT_NONNULL(r2Result.metadataPayload), responseBuilder);\n      kj::StringPtr uploadId = responseBuilder.getUploadId();\n      traceContext.setTag(\"cloudflare.r2.response.upload_id\"_kjc, uploadId);\n      return js.alloc<R2MultipartUpload>(kj::mv(key), kj::str(uploadId), JSG_THIS);\n    });\n  });\n}\n\njsg::Ref<R2MultipartUpload> R2Bucket::resumeMultipartUpload(jsg::Lock& js,\n    kj::String key,\n    kj::String uploadId,\n    const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType) {\n  return js.alloc<R2MultipartUpload>(kj::mv(key), kj::mv(uploadId), JSG_THIS);\n}\n\njsg::Promise<void> R2Bucket::delete_(jsg::Lock& js,\n    kj::OneOf<kj::String, kj::Array<kj::String>> keys,\n    const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType) {\n  return js.evalNow([&] {\n    auto& context = IoContext::current();\n\n    TraceContext traceContext = context.makeUserTraceSpan(\"r2_delete\"_kjc);\n\n    traceContext.setTag(\"cloudflare.binding.type\"_kjc, \"r2\"_kjc);\n    KJ_IF_SOME(b, this->bindingName()) {\n      traceContext.setTag(\"cloudflare.binding.name\"_kjc, b);\n    }\n    traceContext.setTag(\"cloudflare.r2.operation\"_kjc, \"DeleteObject\"_kjc);\n    KJ_IF_SOME(b, this->bucketName()) {\n      traceContext.setTag(\"cloudflare.r2.bucket\"_kjc, b);\n    }\n\n    capnp::JsonCodec json;\n    json.handleByAnnotation<R2BindingRequest>();\n    capnp::MallocMessageBuilder requestMessage;\n\n    auto requestBuilder = requestMessage.initRoot<R2BindingRequest>();\n    requestBuilder.setVersion(VERSION_PUBLIC_BETA);\n    auto deleteBuilder = requestBuilder.initPayload().initDelete();\n\n    KJ_SWITCH_ONEOF(keys) {\n      KJ_CASE_ONEOF(ks, kj::Array<kj::String>) {\n        auto keys = deleteBuilder.initObjects(ks.size());\n        for (unsigned int i = 0; i < ks.size(); i++) {\n          keys.set(i, ks[i]);\n        }\n        traceContext.setTag(\"cloudflare.r2.request.keys\"_kjc, kj::str(ks));\n      }\n      KJ_CASE_ONEOF(k, kj::String) {\n        deleteBuilder.setObject(k);\n        traceContext.setTag(\"cloudflare.r2.request.keys\"_kjc, k.asPtr());\n      }\n    }\n\n    auto requestJson = json.encode(requestBuilder);\n\n    kj::StringPtr components[1];\n    auto path = fillR2Path(components, adminBucket);\n    auto client = context.getHttpClient(clientIndex, true, kj::none, traceContext);\n    auto promise =\n        doR2HTTPPutRequest(kj::mv(client), kj::none, kj::none, kj::mv(requestJson), path, jwt);\n\n    return context.awaitIo(js, kj::mv(promise),\n        [&errorType, traceContext = kj::mv(traceContext)](\n            jsg::Lock& js, R2Result r2Result) mutable {\n      addR2ResponseSpanTags(traceContext, r2Result);\n      if (r2Result.objectNotFound()) {\n        return;\n      }\n\n      r2Result.throwIfError(\"delete\", errorType);\n    });\n  });\n}\n\njsg::Promise<R2Bucket::ListResult> R2Bucket::list(jsg::Lock& js,\n    jsg::Optional<ListOptions> options,\n    const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType,\n    CompatibilityFlags::Reader flags) {\n  return js.evalNow([&] {\n    auto& context = IoContext::current();\n    TraceContext traceContext = context.makeUserTraceSpan(\"r2_list\"_kjc);\n\n    traceContext.setTag(\"cloudflare.binding.type\"_kjc, \"r2\"_kjc);\n    KJ_IF_SOME(b, this->bindingName()) {\n      traceContext.setTag(\"cloudflare.binding.name\"_kjc, b);\n    }\n    traceContext.setTag(\"cloudflare.r2.operation\"_kjc, \"ListObjects\"_kjc);\n    KJ_IF_SOME(b, this->bucketName()) {\n      traceContext.setTag(\"cloudflare.r2.bucket\"_kjc, b);\n    }\n\n    capnp::JsonCodec json;\n    json.handleByAnnotation<R2BindingRequest>();\n    json.setHasMode(capnp::HasMode::NON_DEFAULT);\n    capnp::MallocMessageBuilder requestMessage;\n\n    auto requestBuilder = requestMessage.initRoot<R2BindingRequest>();\n    requestBuilder.setVersion(VERSION_PUBLIC_BETA);\n    auto listBuilder = requestBuilder.initPayload().initList();\n\n    kj::Vector<OptionalMetadata> expectedOptionalFields(2);\n\n    KJ_IF_SOME(o, options) {\n      KJ_IF_SOME(l, o.limit) {\n        listBuilder.setLimit(l);\n        traceContext.setTag(\"cloudflare.r2.request.limit\"_kjc, static_cast<int64_t>(l));\n      }\n      KJ_IF_SOME(p, o.prefix) {\n        listBuilder.setPrefix(p.value);\n        traceContext.setTag(\"cloudflare.r2.request.prefix\"_kjc, p.value.asPtr());\n      }\n      KJ_IF_SOME(c, o.cursor) {\n        listBuilder.setCursor(c.value);\n        traceContext.setTag(\"cloudflare.r2.request.cursor\"_kjc, c.value.asPtr());\n      }\n      KJ_IF_SOME(d, o.delimiter) {\n        listBuilder.setDelimiter(d.value);\n        traceContext.setTag(\"cloudflare.r2.request.delimiter\"_kjc, d.value.asPtr());\n      }\n      KJ_IF_SOME(d, o.startAfter) {\n        listBuilder.setStartAfter(d.value);\n        traceContext.setTag(\"cloudflare.r2.request.start_after\"_kjc, d.value.asPtr());\n      }\n      KJ_IF_SOME(i, o.include) {\n        using Field = jsg::Dict<uint16_t>::Field;\n        static const std::array<Field, 2> fields = {\n          Field{\n            .name = kj::str(\"httpMetadata\"),\n            .value = static_cast<uint16_t>(R2ListRequest::IncludeField::HTTP),\n          },\n          Field{\n            .name = kj::str(\"customMetadata\"),\n            .value = static_cast<uint16_t>(R2ListRequest::IncludeField::CUSTOM),\n          },\n        };\n\n        expectedOptionalFields.clear();\n\n        for (const auto& reqField: i) {\n          if (reqField.value == \"httpMetadata\") {\n            traceContext.setTag(\"cloudflare.r2.request.include.http_metadata\"_kjc, true);\n          } else if (reqField.value == \"customMetadata\") {\n            traceContext.setTag(\"cloudflare.r2.request.include.custom_metadata\"_kjc, true);\n          }\n        }\n\n        listBuilder.setInclude(KJ_MAP(reqField, i) {\n          for (const auto& field: fields) {\n            if (field.name == reqField.value) {\n              expectedOptionalFields.add(static_cast<OptionalMetadata>(field.value));\n              return field.value;\n            }\n          }\n\n          JSG_FAIL_REQUIRE(RangeError, \"Unsupported include value \", reqField.value);\n        });\n      } else if (featureFlags.listHonorsIncludes) {\n        listBuilder.initInclude(0);\n      }\n    }\n\n    // TODO(soon): Remove this after the release for 2022-07-04 is cut (from here & R2 worker).\n    // This just tells the R2 worker that it can honor the `includes` field without breaking back\n    // compat. If we just started spontaneously honoring the `includes` field then existing Workers\n    // might suddenly lose http metadata because they weren't explicitly asking for it even though\n    listBuilder.setNewRuntime(true);\n\n    // TODO(later): Add a sentry message (+ console warning) to check if we have users that aren't\n    // asking for any optional metadata but are asking it in the result anyway just so that we can\n    // kill all the compat flag logic.\n    if (!featureFlags.listHonorsIncludes) {\n      // Unconditionally send this so that when running against an R2 instance that does honor these\n      // we do the right back-compat behavior.\n      auto includes = listBuilder.initInclude(2);\n      includes.set(0, static_cast<uint16_t>(R2ListRequest::IncludeField::HTTP));\n      includes.set(1, static_cast<uint16_t>(R2ListRequest::IncludeField::CUSTOM));\n      expectedOptionalFields.clear();\n      expectedOptionalFields.add(OptionalMetadata::Http);\n      expectedOptionalFields.add(OptionalMetadata::Custom);\n    }\n\n    auto requestJson = json.encode(requestBuilder);\n\n    kj::StringPtr components[1];\n    auto path = fillR2Path(components, adminBucket);\n    auto client = context.getHttpClient(clientIndex, true, kj::none, traceContext);\n    auto promise = doR2HTTPGetRequest(kj::mv(client), kj::mv(requestJson), path, jwt, flags);\n\n    return context.awaitIo(js, kj::mv(promise),\n        [expectedOptionalFields = expectedOptionalFields.releaseAsArray(), &errorType,\n            traceContext = kj::mv(traceContext)](jsg::Lock& js, R2Result r2Result) mutable {\n      addR2ResponseSpanTags(traceContext, r2Result);\n      r2Result.throwIfError(\"list\", errorType);\n\n      R2Bucket::ListResult result;\n      capnp::MallocMessageBuilder responseMessage;\n      capnp::JsonCodec json;\n      json.handleByAnnotation<R2ListResponse>();\n      auto responseBuilder = responseMessage.initRoot<R2ListResponse>();\n\n      json.decode(KJ_ASSERT_NONNULL(r2Result.metadataPayload), responseBuilder);\n\n      result.objects = KJ_MAP(o, responseBuilder.getObjects()) {\n        return parseObjectMetadata<HeadResult>(js, o, expectedOptionalFields);\n      };\n      result.truncated = responseBuilder.getTruncated();\n      if (responseBuilder.hasCursor()) {\n        result.cursor = kj::str(responseBuilder.getCursor());\n        traceContext.setTag(\n            \"cloudflare.r2.response.cursor\"_kjc, KJ_ASSERT_NONNULL(result.cursor).asPtr());\n      }\n      if (responseBuilder.hasDelimitedPrefixes()) {\n        result.delimitedPrefixes =\n          KJ_MAP(e, responseBuilder.getDelimitedPrefixes()) { return kj::str(e); };\n      }\n\n      traceContext.setTag(\"cloudflare.r2.response.returned_objects\"_kjc,\n          static_cast<int64_t>(result.objects.size()));\n      traceContext.setTag(\"cloudflare.r2.response.delimited_prefixes\"_kjc,\n          static_cast<int64_t>(result.delimitedPrefixes.size()));\n      traceContext.setTag(\"cloudflare.r2.response.truncated\"_kjc, result.truncated);\n      return kj::mv(result);\n    });\n  });\n}\n\nnamespace {\nkj::Array<R2Bucket::Etag> parseConditionalEtagHeader(kj::StringPtr condHeader,\n    kj::Vector<R2Bucket::Etag> etagAccumulator = kj::Vector<R2Bucket::Etag>(),\n    bool leadingCommaRequired = false) {\n  // Vague recursion termination proof:\n  // Stop condition triggers when no more etags and wildcards are found\n  // => empty string also results in termination\n  // There are 2 recursive calls in this function body, each of them always moves the start of the\n  // condHeader to some value found in the condHeader + 1.\n  // => upon each recursion, the size of condHeader is reduced by at least 1.\n  // Eventually we must arrive at an empty string, hence triggering the stop condition.\n\n  size_t nextWildcard = condHeader.findFirst('*').orDefault(SIZE_MAX);\n  size_t nextQuotation = condHeader.findFirst('\"').orDefault(SIZE_MAX);\n  size_t nextWeak = condHeader.findFirst('W').orDefault(SIZE_MAX);\n  size_t nextComma = condHeader.findFirst(',').orDefault(SIZE_MAX);\n\n  if (nextQuotation == SIZE_MAX && nextWildcard == SIZE_MAX) {\n    // Both of these being SIZE_MAX means no more wildcards or double quotes are left in the header.\n    // When this is the case, there's no more useful etags that can potentially still be extracted.\n    return etagAccumulator.releaseAsArray();\n  }\n\n  if (nextComma < nextWildcard && nextComma < nextQuotation && nextComma < nextWeak) {\n    // Get rid of leading commas, this can happen during recursion because servers must deal with\n    // empty list elements. E.g.: If-None-Match \"abc\", , \"cdef\" should be accepted by the server.\n    // This slice is always safe, since we're at most setting start to the last index + 1,\n    // which just results in an empty list if it's out of bounds by 1.\n    return parseConditionalEtagHeader(condHeader.slice(nextComma + 1), kj::mv(etagAccumulator));\n  } else if (leadingCommaRequired) {\n    // we don't need to include nextComma in this min check since in this else branch nextComma is\n    // always larger than at least one of nextWildcard, nextQuotation and nextWeak\n    size_t firstEncounteredProblem = std::min({nextWildcard, nextQuotation, nextWeak});\n\n    kj::StringPtr failureReason;\n    if (firstEncounteredProblem == nextWildcard) {\n      failureReason = \"Encountered a wildcard character '*' instead.\"_kjc;\n    } else if (firstEncounteredProblem == nextQuotation) {\n      failureReason = \"Encountered a double quote character '\\\"' instead. \"\n                      \"This would otherwise indicate the start of a new strong etag.\"_kjc;\n    } else if (firstEncounteredProblem == nextWeak) {\n      failureReason = \"Encountered a weak quotation character 'W' instead. \"\n                      \"This would otherwise indicate the start of a new weak etag.\"_kjc;\n    } else {\n      KJ_FAIL_ASSERT(\n          \"We shouldn't be able to reach this point. The above etag parsing code is incorrect.\");\n    }\n\n    // Did not find a leading comma, and we expected a leading comma before any further etags\n    JSG_FAIL_REQUIRE(Error, \"Comma was expected to separate etags. \", failureReason);\n  }\n\n  if (nextWildcard < nextQuotation) {\n    // Unquoted wildcard found\n    // remove all other etags since they're overridden by the wildcard anyways\n    etagAccumulator.clear();\n    struct R2Bucket::WildcardEtag etag = {};\n    etagAccumulator.add(kj::mv(etag));\n    return etagAccumulator.releaseAsArray();\n  }\n  if (nextQuotation < nextWildcard) {\n    size_t etagValueStart = nextQuotation + 1;\n    // Find closing quotation mark, instead of going by the next comma.\n    // This is done because commas are allowed in etags, and double quotes are not.\n    kj::Maybe<size_t> closingQuotation =\n        condHeader.slice(etagValueStart).findFirst('\"').map([=](size_t cq) {\n      return cq + etagValueStart;\n    });\n\n    KJ_IF_SOME(cq, closingQuotation) {\n      // Slice end is non inclusive, meaning that this drops the closingQuotation from the etag\n      kj::String etagValue = kj::str(condHeader.slice(etagValueStart, cq));\n      if (nextWeak < nextQuotation) {\n        JSG_REQUIRE(condHeader.size() > nextWeak + 2 && condHeader[nextWeak + 1] == '/' &&\n                nextWeak + 2 == nextQuotation,\n            Error, \"Weak etags must start with W/ and their value must be quoted\");\n        R2Bucket::WeakEtag etag = {kj::mv(etagValue)};\n        etagAccumulator.add(kj::mv(etag));\n      } else {\n        R2Bucket::StrongEtag etag = {kj::mv(etagValue)};\n        etagAccumulator.add(kj::mv(etag));\n      }\n      return parseConditionalEtagHeader(condHeader.slice(cq + 1), kj::mv(etagAccumulator), true);\n    } else {\n      JSG_FAIL_REQUIRE(Error, \"Unclosed double quote for Etag\");\n    }\n  } else {\n    JSG_FAIL_REQUIRE(Error, \"Invalid conditional header\");\n  }\n}\n\nkj::Array<R2Bucket::Etag> buildSingleEtagArray(kj::StringPtr etagValue) {\n  kj::ArrayBuilder<R2Bucket::Etag> etagArrayBuilder = kj::heapArrayBuilder<R2Bucket::Etag>(1);\n\n  if (etagValue == \"*\") {\n    struct R2Bucket::WildcardEtag etag = {};\n    etagArrayBuilder.add(kj::mv(etag));\n  } else {\n    struct R2Bucket::StrongEtag etag = {.value = kj::str(etagValue)};\n    etagArrayBuilder.add(kj::mv(etag));\n  }\n\n  return etagArrayBuilder.finish();\n}\n\n}  // namespace\n\nR2Bucket::UnwrappedConditional::UnwrappedConditional(jsg::Lock& js, Headers& h)\n    : secondsGranularity(true) {\n  KJ_IF_SOME(e, h.getCommon(js, capnp::CommonHeaderName::IF_MATCH)) {\n    etagMatches = parseConditionalEtagHeader(e);\n    KJ_IF_SOME(arr, etagMatches) {\n      if (arr.size() == 0) {\n        JSG_FAIL_REQUIRE(Error, \"Invalid ETag in if-match header\");\n      }\n    }\n  }\n  KJ_IF_SOME(e, h.getCommon(js, capnp::CommonHeaderName::IF_NONE_MATCH)) {\n    etagDoesNotMatch = parseConditionalEtagHeader(e);\n    KJ_IF_SOME(arr, etagDoesNotMatch) {\n      if (arr.size() == 0) {\n        JSG_FAIL_REQUIRE(Error, \"Invalid ETag in if-none-match header\");\n      }\n    }\n  }\n  KJ_IF_SOME(d, h.getCommon(js, capnp::CommonHeaderName::IF_MODIFIED_SINCE)) {\n    auto date = parseDate(js, d);\n    uploadedAfter = date;\n  }\n  KJ_IF_SOME(d, h.getCommon(js, capnp::CommonHeaderName::IF_UNMODIFIED_SINCE)) {\n    auto date = parseDate(js, d);\n    uploadedBefore = date;\n  }\n}\n\nR2Bucket::UnwrappedConditional::UnwrappedConditional(const Conditional& c)\n    : secondsGranularity(c.secondsGranularity.orDefault(false)) {\n  KJ_IF_SOME(e, c.etagMatches) {\n    JSG_REQUIRE(!isQuotedEtag(e.value), TypeError,\n        \"Conditional ETag should not be wrapped in quotes (\", e.value, \").\");\n    etagMatches = buildSingleEtagArray(e.value);\n  }\n  KJ_IF_SOME(e, c.etagDoesNotMatch) {\n    JSG_REQUIRE(!isQuotedEtag(e.value), TypeError,\n        \"Conditional ETag should not be wrapped in quotes (\", e.value, \").\");\n    etagDoesNotMatch = buildSingleEtagArray(e.value);\n  }\n  KJ_IF_SOME(d, c.uploadedAfter) {\n    uploadedAfter = d;\n  }\n  KJ_IF_SOME(d, c.uploadedBefore) {\n    uploadedBefore = d;\n  }\n}\n\nR2Bucket::HttpMetadata R2Bucket::HttpMetadata::fromRequestHeaders(jsg::Lock& js, Headers& h) {\n  HttpMetadata result;\n  KJ_IF_SOME(ct, h.getCommon(js, capnp::CommonHeaderName::CONTENT_TYPE)) {\n    result.contentType = kj::mv(ct);\n  }\n  KJ_IF_SOME(ce, h.getCommon(js, capnp::CommonHeaderName::CONTENT_ENCODING)) {\n    result.contentEncoding = kj::mv(ce);\n  }\n  KJ_IF_SOME(cd, h.getCommon(js, capnp::CommonHeaderName::CONTENT_DISPOSITION)) {\n    result.contentDisposition = kj::mv(cd);\n  }\n  KJ_IF_SOME(cl, h.getCommon(js, capnp::CommonHeaderName::CONTENT_LANGUAGE)) {\n    result.contentLanguage = kj::mv(cl);\n  }\n  KJ_IF_SOME(cc, h.getCommon(js, capnp::CommonHeaderName::CACHE_CONTROL)) {\n    result.cacheControl = kj::mv(cc);\n  }\n  KJ_IF_SOME(ceStr, h.getCommon(js, capnp::CommonHeaderName::EXPIRES)) {\n    result.cacheExpiry = parseDate(js, ceStr);\n  }\n\n  return result;\n}\n\nR2Bucket::HttpMetadata R2Bucket::HttpMetadata::clone() const {\n  return {\n    .contentType = mapCopyString(contentType),\n    .contentLanguage = mapCopyString(contentLanguage),\n    .contentDisposition = mapCopyString(contentDisposition),\n    .contentEncoding = mapCopyString(contentEncoding),\n    .cacheControl = mapCopyString(cacheControl),\n    .cacheExpiry = cacheExpiry,\n  };\n}\n\nvoid R2Bucket::HeadResult::writeHttpMetadata(jsg::Lock& js, Headers& headers) {\n  JSG_REQUIRE(httpMetadata != kj::none, TypeError, \"HTTP metadata unknown for key `\", name,\n      \"`. Did you forget to add 'httpMetadata' to `include` when listing?\");\n  const auto& m = KJ_REQUIRE_NONNULL(httpMetadata);\n\n  KJ_IF_SOME(ct, m.contentType) {\n    headers.setCommon(capnp::CommonHeaderName::CONTENT_TYPE, kj::str(ct));\n  }\n  KJ_IF_SOME(cl, m.contentLanguage) {\n    headers.setCommon(capnp::CommonHeaderName::CONTENT_LANGUAGE, kj::str(cl));\n  }\n  KJ_IF_SOME(cd, m.contentDisposition) {\n    headers.setCommon(capnp::CommonHeaderName::CONTENT_DISPOSITION, kj::str(cd));\n  }\n  KJ_IF_SOME(ce, m.contentEncoding) {\n    headers.setCommon(capnp::CommonHeaderName::CONTENT_ENCODING, kj::str(ce));\n  }\n  KJ_IF_SOME(cc, m.cacheControl) {\n    headers.setCommon(capnp::CommonHeaderName::CACHE_CONTROL, kj::str(cc));\n  }\n  KJ_IF_SOME(ce, m.cacheExpiry) {\n    headers.setCommon(capnp::CommonHeaderName::EXPIRES, toUTCString(js, ce));\n  }\n}\n\njsg::Promise<jsg::BufferSource> R2Bucket::GetResult::arrayBuffer(jsg::Lock& js) {\n  return js.evalNow([&] {\n    JSG_REQUIRE(!body->isDisturbed(), TypeError,\n        \"Body has already been used. \"\n        \"It can only be used once. Use tee() first if you need to read it twice.\");\n\n    auto& context = IoContext::current();\n    return body->getController().readAllBytes(js, context.getLimitEnforcer().getBufferingLimit());\n  });\n}\n\njsg::Promise<jsg::BufferSource> R2Bucket::GetResult::bytes(jsg::Lock& js) {\n  return js.evalNow([&] {\n    JSG_REQUIRE(!body->isDisturbed(), TypeError,\n        \"Body has already been used. \"\n        \"It can only be used once. Use tee() first if you need to read it twice.\");\n\n    auto& context = IoContext::current();\n    return body->getController()\n        .readAllBytes(js, context.getLimitEnforcer().getBufferingLimit())\n        .then(js, [](jsg::Lock& js, jsg::BufferSource data) {\n      return data.getTypedView<v8::Uint8Array>(js);\n    });\n  });\n}\n\njsg::Promise<kj::String> R2Bucket::GetResult::text(jsg::Lock& js) {\n  // Copy-pasted from http.c++\n  return js.evalNow([&] {\n    JSG_REQUIRE(!body->isDisturbed(), TypeError,\n        \"Body has already been used. \"\n        \"It can only be used once. Use tee() first if you need to read it twice.\");\n\n    auto& context = IoContext::current();\n    // A common mistake is to call .text() on non-text content, e.g. because you're implementing a\n    // search-and-replace across your whole site and you forgot that it'll apply to images too.\n    // When running in the fiddle, let's warn the developer if they do this.\n    if (context.isInspectorEnabled()) {\n      // httpMetadata can't be null because GetResult always populates it.\n      KJ_IF_SOME(type, KJ_REQUIRE_NONNULL(httpMetadata).contentType) {\n        maybeWarnIfNotText(js, type);\n      }\n    }\n\n    return body->getController().readAllText(js, context.getLimitEnforcer().getBufferingLimit());\n  });\n}\n\njsg::Promise<jsg::Value> R2Bucket::GetResult::json(jsg::Lock& js) {\n  // Copy-pasted from http.c++\n  return text(js).then(js, [](jsg::Lock& js, kj::String text) { return js.parseJson(text); });\n}\n\njsg::Promise<jsg::Ref<Blob>> R2Bucket::GetResult::blob(jsg::Lock& js) {\n  // Copy-pasted from http.c++\n  return arrayBuffer(js).then(js, [this](jsg::Lock& js, jsg::BufferSource buffer) {\n    // httpMetadata can't be null because GetResult always populates it.\n    kj::String contentType =\n        mapCopyString(KJ_REQUIRE_NONNULL(httpMetadata).contentType).orDefault(nullptr);\n    return js.alloc<Blob>(js, kj::mv(buffer), kj::mv(contentType));\n  });\n}\n\nR2Bucket::StringChecksums R2Bucket::Checksums::toJSON() {\n  return {\n    .md5 = this->md5.map(kj::encodeHex),\n    .sha1 = this->sha1.map(kj::encodeHex),\n    .sha256 = this->sha256.map(kj::encodeHex),\n    .sha384 = this->sha384.map(kj::encodeHex),\n    .sha512 = this->sha512.map(kj::encodeHex),\n  };\n}\n\nnamespace {\njsg::Optional<jsg::BufferSource> copyHash(\n    jsg::Lock& js, const jsg::Optional<kj::Array<kj::byte>>& maybeHash) {\n  KJ_IF_SOME(hash, maybeHash) {\n    auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, hash.size());\n    backing.asArrayPtr().copyFrom(hash);\n    return jsg::BufferSource(js, kj::mv(backing));\n  }\n  return kj::none;\n}\n}  // namespace\n\njsg::Optional<jsg::BufferSource> R2Bucket::Checksums::getMd5(jsg::Lock& js) {\n  return copyHash(js, md5);\n}\njsg::Optional<jsg::BufferSource> R2Bucket::Checksums::getSha1(jsg::Lock& js) {\n  return copyHash(js, sha1);\n}\njsg::Optional<jsg::BufferSource> R2Bucket::Checksums::getSha256(jsg::Lock& js) {\n  return copyHash(js, sha256);\n}\njsg::Optional<jsg::BufferSource> R2Bucket::Checksums::getSha384(jsg::Lock& js) {\n  return copyHash(js, sha384);\n}\njsg::Optional<jsg::BufferSource> R2Bucket::Checksums::getSha512(jsg::Lock& js) {\n  return copyHash(js, sha512);\n}\n\nkj::Maybe<jsg::Ref<R2Bucket::HeadResult>> parseHeadResultWrapper(jsg::Lock& js,\n    kj::StringPtr action,\n    R2Result& r2Result,\n    const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType) {\n  return parseObjectMetadata<R2Bucket::HeadResult>(js, action, r2Result, errorType);\n}\n\nkj::ArrayPtr<kj::StringPtr> fillR2Path(\n    kj::StringPtr pathStorage[1], const kj::Maybe<kj::String>& bucket) {\n  int numComponents = 0;\n\n  KJ_IF_SOME(b, bucket) {\n    pathStorage[numComponents++] = b;\n  }\n\n  return kj::arrayPtr(pathStorage, numComponents);\n}\n\n}  // namespace workerd::api::public_beta\n"
  },
  {
    "path": "src/workerd/api/r2-bucket.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"r2-rpc.h\"\n\n#include <workerd/api/streams/readable.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api {\nclass Headers;\n}\n\nnamespace workerd::api::public_beta {\n\nstruct StringTagParams {\n  kj::LiteralStringConst key;\n  kj::StringPtr value;\n};\n\nstruct R2UserTracing {\n  kj::LiteralStringConst op;\n  StringTagParams method;\n  // Passing Maybe<kj::StringPtr> instead of Maybe<StringTagParams> here – this avoids a branch on\n  // the caller side when bucket is already a Maybe, which is more convenient.\n  kj::Maybe<kj::StringPtr> bucket;\n  kj::Maybe<StringTagParams> extraTag;\n};\n\n// Helper for creating R2 HTTP Client with the right span tags across operations. This is much\n// cleaner than setting span tags directly in each function.\nkj::Own<kj::HttpClient> r2GetClient(IoContext& context, uint subrequestChannel, R2UserTracing user);\n\nkj::ArrayPtr<kj::StringPtr> fillR2Path(\n    kj::StringPtr pathStorage[1], const kj::Maybe<kj::String>& bucket);\n\nclass R2MultipartUpload;\n\n// A capability to an R2 Bucket.\nclass R2Bucket: public jsg::Object {\n protected:\n  struct friend_tag_t {};\n\n  struct FeatureFlags {\n    FeatureFlags(CompatibilityFlags::Reader featureFlags);\n\n    bool listHonorsIncludes;\n  };\n\n public:\n  // `clientIndex` is what to pass to IoContext::getHttpClient() to get an HttpClient\n  // representing this namespace.\n  explicit R2Bucket(CompatibilityFlags::Reader featureFlags, uint clientIndex)\n      : featureFlags(featureFlags),\n        clientIndex(clientIndex) {}\n\n  explicit R2Bucket(FeatureFlags featureFlags, uint clientIndex, kj::String bucket, friend_tag_t)\n      : featureFlags(featureFlags),\n        clientIndex(clientIndex),\n        adminBucket(kj::mv(bucket)) {}\n\n  explicit R2Bucket(\n      FeatureFlags featureFlags, uint clientIndex, kj::String bucket, kj::String binding)\n      : featureFlags(featureFlags),\n        clientIndex(clientIndex),\n        bucket(kj::mv(bucket)),\n        binding(kj::mv(binding)) {}\n\n  explicit R2Bucket(\n      FeatureFlags featureFlags, uint clientIndex, kj::String bucket, kj::String jwt, friend_tag_t)\n      : featureFlags(featureFlags),\n        clientIndex(clientIndex),\n        adminBucket(kj::mv(bucket)),\n        jwt(kj::mv(jwt)) {}\n\n  struct Range {\n    jsg::Optional<double> offset;\n    jsg::Optional<double> length;\n    jsg::Optional<double> suffix;\n\n    JSG_STRUCT(offset, length, suffix);\n    JSG_STRUCT_TS_OVERRIDE(type R2Range =\n      | { offset: number; length?: number }\n      | { offset?: number; length: number }\n      | { suffix: number }\n    );\n  };\n\n  struct Conditional {\n    jsg::Optional<jsg::NonCoercible<kj::String>> etagMatches;\n    jsg::Optional<jsg::NonCoercible<kj::String>> etagDoesNotMatch;\n    jsg::Optional<kj::Date> uploadedBefore;\n    jsg::Optional<kj::Date> uploadedAfter;\n    jsg::Optional<bool> secondsGranularity;\n\n    JSG_STRUCT(etagMatches, etagDoesNotMatch, uploadedBefore, uploadedAfter, secondsGranularity);\n    JSG_STRUCT_TS_OVERRIDE(R2Conditional);\n  };\n\n  struct GetOptions {\n    jsg::Optional<kj::OneOf<Conditional, jsg::Ref<Headers>>> onlyIf;\n    jsg::Optional<kj::OneOf<Range, jsg::Ref<Headers>>> range;\n    jsg::Optional<kj::OneOf<kj::Array<byte>, kj::String>> ssecKey;\n\n    JSG_STRUCT(onlyIf, range, ssecKey);\n    JSG_STRUCT_TS_OVERRIDE(R2GetOptions);\n  };\n\n  struct StringChecksums {\n    jsg::Optional<kj::String> md5;\n    jsg::Optional<kj::String> sha1;\n    jsg::Optional<kj::String> sha256;\n    jsg::Optional<kj::String> sha384;\n    jsg::Optional<kj::String> sha512;\n\n    JSG_STRUCT(md5, sha1, sha256, sha384, sha512);\n    JSG_STRUCT_TS_OVERRIDE(R2StringChecksums);\n  };\n\n  class Checksums: public jsg::Object {\n   public:\n    Checksums(jsg::Optional<kj::Array<kj::byte>> md5,\n        jsg::Optional<kj::Array<kj::byte>> sha1,\n        jsg::Optional<kj::Array<kj::byte>> sha256,\n        jsg::Optional<kj::Array<kj::byte>> sha384,\n        jsg::Optional<kj::Array<kj::byte>> sha512)\n        : md5(kj::mv(md5)),\n          sha1(kj::mv(sha1)),\n          sha256(kj::mv(sha256)),\n          sha384(kj::mv(sha384)),\n          sha512(kj::mv(sha512)) {}\n\n    jsg::Optional<jsg::BufferSource> getMd5(jsg::Lock& js);\n    jsg::Optional<jsg::BufferSource> getSha1(jsg::Lock& js);\n    jsg::Optional<jsg::BufferSource> getSha256(jsg::Lock& js);\n    jsg::Optional<jsg::BufferSource> getSha384(jsg::Lock& js);\n    jsg::Optional<jsg::BufferSource> getSha512(jsg::Lock& js);\n\n    StringChecksums toJSON();\n\n    JSG_RESOURCE_TYPE(Checksums) {\n      JSG_LAZY_READONLY_INSTANCE_PROPERTY(md5, getMd5);\n      JSG_LAZY_READONLY_INSTANCE_PROPERTY(sha1, getSha1);\n      JSG_LAZY_READONLY_INSTANCE_PROPERTY(sha256, getSha256);\n      JSG_LAZY_READONLY_INSTANCE_PROPERTY(sha384, getSha384);\n      JSG_LAZY_READONLY_INSTANCE_PROPERTY(sha512, getSha512);\n      JSG_METHOD(toJSON);\n      JSG_TS_OVERRIDE(R2Checksums {\n        readonly md5?: ArrayBuffer;\n        readonly sha1?: ArrayBuffer;\n        readonly sha256?: ArrayBuffer;\n        readonly sha384?: ArrayBuffer;\n        readonly sha512?: ArrayBuffer;\n      });\n    }\n\n    jsg::Optional<kj::Array<kj::byte>> md5;\n    jsg::Optional<kj::Array<kj::byte>> sha1;\n    jsg::Optional<kj::Array<kj::byte>> sha256;\n    jsg::Optional<kj::Array<kj::byte>> sha384;\n    jsg::Optional<kj::Array<kj::byte>> sha512;\n\n    void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n      tracker.trackField(\"md5\", md5);\n      tracker.trackField(\"sha1\", sha1);\n      tracker.trackField(\"sha256\", sha256);\n      tracker.trackField(\"sha384\", sha384);\n      tracker.trackField(\"sha512\", sha512);\n    }\n  };\n\n  struct HttpMetadata {\n    static HttpMetadata fromRequestHeaders(jsg::Lock& js, Headers& h);\n\n    jsg::Optional<kj::String> contentType;\n    jsg::Optional<kj::String> contentLanguage;\n    jsg::Optional<kj::String> contentDisposition;\n    jsg::Optional<kj::String> contentEncoding;\n    jsg::Optional<kj::String> cacheControl;\n    jsg::Optional<kj::Date> cacheExpiry;\n\n    JSG_STRUCT(contentType,\n        contentLanguage,\n        contentDisposition,\n        contentEncoding,\n        cacheControl,\n        cacheExpiry);\n    JSG_STRUCT_TS_OVERRIDE(R2HTTPMetadata);\n\n    HttpMetadata clone() const;\n\n    JSG_MEMORY_INFO(HttpMetadata) {\n      tracker.trackField(\"contentType\", contentType);\n      tracker.trackField(\"contentLanguage\", contentLanguage);\n      tracker.trackField(\"contentDisposition\", contentDisposition);\n      tracker.trackField(\"contentEncoding\", contentEncoding);\n      tracker.trackField(\"cacheControl\", cacheControl);\n    }\n  };\n\n  struct PutOptions {\n    jsg::Optional<kj::OneOf<Conditional, jsg::Ref<Headers>>> onlyIf;\n    jsg::Optional<kj::OneOf<HttpMetadata, jsg::Ref<Headers>>> httpMetadata;\n    jsg::Optional<jsg::Dict<kj::String>> customMetadata;\n    jsg::Optional<kj::OneOf<jsg::BufferSource, jsg::NonCoercible<kj::String>>> md5;\n    jsg::Optional<kj::OneOf<jsg::BufferSource, jsg::NonCoercible<kj::String>>> sha1;\n    jsg::Optional<kj::OneOf<jsg::BufferSource, jsg::NonCoercible<kj::String>>> sha256;\n    jsg::Optional<kj::OneOf<jsg::BufferSource, jsg::NonCoercible<kj::String>>> sha384;\n    jsg::Optional<kj::OneOf<jsg::BufferSource, jsg::NonCoercible<kj::String>>> sha512;\n    jsg::Optional<kj::String> storageClass;\n    jsg::Optional<kj::OneOf<kj::Array<byte>, kj::String>> ssecKey;\n\n    JSG_STRUCT(onlyIf,\n        httpMetadata,\n        customMetadata,\n        md5,\n        sha1,\n        sha256,\n        sha384,\n        sha512,\n        storageClass,\n        ssecKey);\n    JSG_STRUCT_TS_OVERRIDE(R2PutOptions);\n  };\n\n  struct MultipartOptions {\n    jsg::Optional<kj::OneOf<HttpMetadata, jsg::Ref<Headers>>> httpMetadata;\n    jsg::Optional<jsg::Dict<kj::String>> customMetadata;\n    jsg::Optional<kj::String> storageClass;\n    jsg::Optional<kj::OneOf<kj::Array<byte>, kj::String>> ssecKey;\n\n    JSG_STRUCT(httpMetadata, customMetadata, storageClass, ssecKey);\n    JSG_STRUCT_TS_OVERRIDE(R2MultipartOptions);\n  };\n\n  class HeadResult: public jsg::Object {\n   public:\n    HeadResult(kj::String name,\n        kj::String version,\n        double size,\n        kj::String etag,\n        jsg::Ref<Checksums> checksums,\n        kj::Date uploaded,\n        jsg::Optional<HttpMetadata> httpMetadata,\n        jsg::Optional<jsg::Dict<kj::String>> customMetadata,\n        jsg::Optional<Range> range,\n        kj::String storageClass,\n        jsg::Optional<kj::String> ssecKeyMd5)\n        : name(kj::mv(name)),\n          version(kj::mv(version)),\n          size(size),\n          etag(kj::mv(etag)),\n          checksums(kj::mv(checksums)),\n          uploaded(uploaded),\n          httpMetadata(kj::mv(httpMetadata)),\n          customMetadata(kj::mv(customMetadata)),\n          range(kj::mv(range)),\n          storageClass(kj::mv(storageClass)),\n          ssecKeyMd5(kj::mv(ssecKeyMd5)) {}\n\n    kj::StringPtr getName() const {\n      return name;\n    }\n    kj::StringPtr getVersion() const {\n      return version;\n    }\n    double getSize() const {\n      return size;\n    }\n    kj::StringPtr getEtag() const {\n      return etag;\n    }\n    kj::String getHttpEtag() const {\n      return kj::str('\"', etag, '\"');\n    }\n    jsg::Ref<Checksums> getChecksums() {\n      return checksums.addRef();\n    }\n    kj::Date getUploaded() const {\n      return uploaded;\n    }\n    kj::StringPtr getStorageClass() const {\n      return storageClass;\n    }\n    jsg::Optional<kj::StringPtr> getSSECKeyMd5() const {\n      return ssecKeyMd5;\n    }\n\n    jsg::Optional<HttpMetadata> getHttpMetadata() const {\n      return httpMetadata.map([](const HttpMetadata& m) { return m.clone(); });\n    }\n\n    const jsg::Optional<jsg::Dict<kj::String>> getCustomMetadata() const {\n      return customMetadata.map([](const jsg::Dict<kj::String>& m) {\n        return jsg::Dict<kj::String>{\n          .fields =\n              KJ_MAP(f, m.fields) {\n          return jsg::Dict<kj::String>::Field{.name = kj::str(f.name), .value = kj::str(f.value)};\n        },\n        };\n      });\n    }\n\n    jsg::Optional<Range> getRange() {\n      return range;\n    }\n\n    void writeHttpMetadata(jsg::Lock& js, Headers& headers);\n\n    JSG_RESOURCE_TYPE(HeadResult) {\n      JSG_LAZY_READONLY_INSTANCE_PROPERTY(key, getName);\n      JSG_LAZY_READONLY_INSTANCE_PROPERTY(version, getVersion);\n      JSG_LAZY_READONLY_INSTANCE_PROPERTY(size, getSize);\n      JSG_LAZY_READONLY_INSTANCE_PROPERTY(etag, getEtag);\n      JSG_LAZY_READONLY_INSTANCE_PROPERTY(httpEtag, getHttpEtag);\n      JSG_LAZY_READONLY_INSTANCE_PROPERTY(checksums, getChecksums);\n      JSG_LAZY_READONLY_INSTANCE_PROPERTY(uploaded, getUploaded);\n      JSG_LAZY_READONLY_INSTANCE_PROPERTY(httpMetadata, getHttpMetadata);\n      JSG_LAZY_READONLY_INSTANCE_PROPERTY(customMetadata, getCustomMetadata);\n      JSG_LAZY_READONLY_INSTANCE_PROPERTY(range, getRange);\n      JSG_LAZY_READONLY_INSTANCE_PROPERTY(storageClass, getStorageClass);\n      JSG_LAZY_READONLY_INSTANCE_PROPERTY(ssecKeyMd5, getSSECKeyMd5);\n      JSG_METHOD(writeHttpMetadata);\n      JSG_TS_OVERRIDE(R2Object);\n    }\n\n    void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n      tracker.trackField(\"name\", name);\n      tracker.trackField(\"version\", version);\n      tracker.trackField(\"etag\", etag);\n      tracker.trackField(\"checksums\", checksums);\n      tracker.trackField(\"httpMetadata\", httpMetadata);\n      tracker.trackField(\"customMetadata\", customMetadata);\n      tracker.trackField(\"ssecKeyMd5\", ssecKeyMd5);\n    }\n\n   protected:\n    kj::String name;\n    kj::String version;\n    double size;\n    kj::String etag;\n    jsg::Ref<Checksums> checksums;\n    kj::Date uploaded;\n    jsg::Optional<HttpMetadata> httpMetadata;\n    jsg::Optional<jsg::Dict<kj::String>> customMetadata;\n\n    jsg::Optional<Range> range;\n    kj::String storageClass;\n    jsg::Optional<kj::String> ssecKeyMd5;\n    friend class R2Bucket;\n  };\n\n  class GetResult: public HeadResult {\n   public:\n    GetResult(kj::String name,\n        kj::String version,\n        double size,\n        kj::String etag,\n        jsg::Ref<Checksums> checksums,\n        kj::Date uploaded,\n        jsg::Optional<HttpMetadata> httpMetadata,\n        jsg::Optional<jsg::Dict<kj::String>> customMetadata,\n        jsg::Optional<Range> range,\n        kj::String storageClass,\n        jsg::Optional<kj::String> ssecKeyMd5,\n        jsg::Ref<ReadableStream> body)\n        : HeadResult(kj::mv(name),\n              kj::mv(version),\n              size,\n              kj::mv(etag),\n              kj::mv(checksums),\n              uploaded,\n              kj::mv(KJ_ASSERT_NONNULL(httpMetadata)),\n              kj::mv(KJ_ASSERT_NONNULL(customMetadata)),\n              range,\n              kj::mv(storageClass),\n              kj::mv(ssecKeyMd5)),\n          body(kj::mv(body)) {}\n\n    jsg::Ref<ReadableStream> getBody() {\n      return body.addRef();\n    }\n\n    bool getBodyUsed() {\n      return body->isDisturbed();\n    }\n\n    jsg::Promise<jsg::BufferSource> arrayBuffer(jsg::Lock& js);\n    jsg::Promise<jsg::BufferSource> bytes(jsg::Lock& js);\n    jsg::Promise<kj::String> text(jsg::Lock& js);\n    jsg::Promise<jsg::Value> json(jsg::Lock& js);\n    jsg::Promise<jsg::Ref<Blob>> blob(jsg::Lock& js);\n\n    JSG_RESOURCE_TYPE(GetResult) {\n      JSG_INHERIT(HeadResult);\n      JSG_READONLY_PROTOTYPE_PROPERTY(body, getBody);\n      JSG_READONLY_PROTOTYPE_PROPERTY(bodyUsed, getBodyUsed);\n      JSG_METHOD(arrayBuffer);\n      JSG_METHOD(bytes);\n      JSG_METHOD(text);\n      JSG_METHOD(json);\n      JSG_METHOD(blob);\n      JSG_TS_OVERRIDE(R2ObjectBody {\n        json<T>(): Promise<T>;\n        bytes(): Promise<Uint8Array>;\n        arrayBuffer(): Promise<ArrayBuffer>;\n      });\n    }\n\n    void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n      tracker.trackField(\"body\", body);\n    }\n\n   private:\n    jsg::Ref<ReadableStream> body;\n  };\n\n  struct ListResult {\n    kj::Array<jsg::Ref<HeadResult>> objects;\n    bool truncated;\n    jsg::Optional<kj::String> cursor;\n    kj::Array<kj::String> delimitedPrefixes;\n\n    JSG_STRUCT(objects, truncated, cursor, delimitedPrefixes);\n    JSG_STRUCT_TS_OVERRIDE(type R2Objects = {\n      objects: R2Object[];\n      delimitedPrefixes: string[];\n    } & (\n      | { truncated: true; cursor: string }\n      | { truncated: false }\n    ));\n  };\n\n  struct ListOptions {\n    jsg::Optional<int> limit;\n    jsg::Optional<jsg::NonCoercible<kj::String>> prefix;\n    jsg::Optional<jsg::NonCoercible<kj::String>> cursor;\n    jsg::Optional<jsg::NonCoercible<kj::String>> delimiter;\n    jsg::Optional<jsg::NonCoercible<kj::String>> startAfter;\n    jsg::Optional<kj::Array<jsg::NonCoercible<kj::String>>> include;\n\n    JSG_STRUCT(limit, prefix, cursor, delimiter, startAfter, include);\n    JSG_STRUCT_TS_OVERRIDE(type R2ListOptions = never);\n    // Delete the auto-generated ListOptions definition, we instead define it\n    // with R2Bucket so we can access compatibility flags. Note, even though\n    // we're deleting the definition, all definitions will still be renamed\n    // from `R2BucketListOptions` to `R2ListOptions`.\n  };\n\n  jsg::Promise<kj::Maybe<jsg::Ref<HeadResult>>> head(jsg::Lock& js,\n      kj::String key,\n      const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType,\n      CompatibilityFlags::Reader flags);\n  jsg::Promise<kj::OneOf<kj::Maybe<jsg::Ref<GetResult>>, jsg::Ref<HeadResult>>> get(jsg::Lock& js,\n      kj::String key,\n      jsg::Optional<GetOptions> options,\n      const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType,\n      CompatibilityFlags::Reader flags);\n  jsg::Promise<kj::Maybe<jsg::Ref<HeadResult>>> put(jsg::Lock& js,\n      kj::String key,\n      kj::Maybe<R2PutValue> value,\n      jsg::Optional<PutOptions> options,\n      const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType);\n  jsg::Promise<jsg::Ref<R2MultipartUpload>> createMultipartUpload(jsg::Lock& js,\n      kj::String key,\n      jsg::Optional<MultipartOptions> options,\n      const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType);\n  jsg::Ref<R2MultipartUpload> resumeMultipartUpload(jsg::Lock& js,\n      kj::String key,\n      kj::String uploadId,\n      const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType);\n  jsg::Promise<void> delete_(jsg::Lock& js,\n      kj::OneOf<kj::String, kj::Array<kj::String>> keys,\n      const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType);\n  jsg::Promise<ListResult> list(jsg::Lock& js,\n      jsg::Optional<ListOptions> options,\n      const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType,\n      CompatibilityFlags::Reader flags);\n\n  JSG_RESOURCE_TYPE(R2Bucket, CompatibilityFlags::Reader flags) {\n    JSG_METHOD(head);\n    JSG_METHOD(get);\n    JSG_METHOD(put);\n    JSG_METHOD(createMultipartUpload);\n    JSG_METHOD(resumeMultipartUpload);\n    JSG_METHOD_NAMED(delete, delete_);\n    JSG_METHOD(list);\n\n    JSG_TS_ROOT();\n    JSG_TS_OVERRIDE({\n      // The order of these matters, since typescript tries to match function signatures in order\n      get(key: string, options: R2GetOptions & { onlyIf: R2BucketConditional | Headers }): Promise<R2ObjectBody | R2Object | null>;\n      get(key: string, options?: R2GetOptions): Promise<R2ObjectBody | null>;\n\n      put(\n        key: string, value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob,\n        options?: R2PutOptions & { onlyIf: R2BucketConditional | Headers }\n      ): Promise<R2Object | null>;\n      put(key: string, value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob, options?: R2PutOptions): Promise<R2Object>;\n    });\n    // Exclude `R2Object` from `get` return type if `onlyIf` not specified, and exclude `null` from `put` return type\n\n    // Rather than using the auto-generated R2ListOptions definition, we define\n    // it here so we can access compatibility flags from JSG_RESOURCE_TYPE.\n    if (flags.getR2ListHonorIncludeFields()) {\n      JSG_TS_DEFINE(interface R2ListOptions {\n        limit?: number;\n        prefix?: string;\n        cursor?: string;\n        delimiter?: string;\n        startAfter?: string;\n        include?: (\"httpMetadata\" | \"customMetadata\")[];\n      });\n    } else {\n      JSG_TS_DEFINE(interface R2ListOptions {\n        limit?: number;\n        prefix?: string;\n        cursor?: string;\n        delimiter?: string;\n        startAfter?: string;\n      });\n      // Omit `include` field if compatibility flag disabled as ignored\n    }\n  }\n\n  struct WildcardEtag {};\n  struct WeakEtag {\n    kj::String value;\n  };\n  struct StrongEtag {\n    kj::String value;\n  };\n  using Etag = kj::OneOf<WildcardEtag, WeakEtag, StrongEtag>;\n\n  struct UnwrappedConditional {\n    UnwrappedConditional(jsg::Lock& js, Headers& h);\n    UnwrappedConditional(const Conditional& c);\n\n    kj::Maybe<kj::Array<Etag>> etagMatches;\n    kj::Maybe<kj::Array<Etag>> etagDoesNotMatch;\n    kj::Maybe<kj::Date> uploadedBefore;\n    kj::Maybe<kj::Date> uploadedAfter;\n    bool secondsGranularity = false;\n  };\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"adminBucket\", adminBucket);\n    tracker.trackField(\"jwt\", jwt);\n  }\n\n protected:\n  kj::Maybe<kj::StringPtr> adminBucketName() const {\n    return adminBucket;\n  }\n\n  kj::Maybe<kj::StringPtr> bucketName() const {\n    return bucket;\n  }\n\n  kj::Maybe<kj::StringPtr> bindingName() const {\n    return binding;\n  }\n\n private:\n  FeatureFlags featureFlags;\n  uint clientIndex;\n  kj::Maybe<kj::String> adminBucket;\n  kj::Maybe<kj::String> bucket;\n  kj::Maybe<kj::String> binding;\n  kj::Maybe<kj::String> jwt;\n\n  friend class R2Admin;\n  friend class R2MultipartUpload;\n};\n\n// Non-generic wrapper avoid moving the parseObjectMetadata implementation into this header file\n// by making use of dynamic dispatch.\nkj::Maybe<jsg::Ref<R2Bucket::HeadResult>> parseHeadResultWrapper(jsg::Lock& js,\n    kj::StringPtr action,\n    R2Result& r2Result,\n    const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType);\n\nvoid addHeadResultSpanTags(jsg::Lock& js, TraceContext& traceContext, R2Bucket::HeadResult& result);\n\n}  // namespace workerd::api::public_beta\n"
  },
  {
    "path": "src/workerd/api/r2-multipart.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"r2-multipart.h\"\n\n#include \"r2-bucket.h\"\n#include \"r2-rpc.h\"\n#include \"workerd/jsg/jsg.h\"\n\n#include <workerd/api/r2-api.capnp.h>\n#include <workerd/util/http-util.h>\n\n#include <capnp/compat/json.h>\n#include <capnp/message.h>\n#include <kj/compat/http.h>\n#include <kj/encoding.h>\n\n#include <regex>\n\nnamespace workerd::api::public_beta {\n\nstatic void addR2ResponseSpanTags(TraceContext& traceContext, R2Result& r2Result) {\n  traceContext.setTag(\"cloudflare.r2.response.success\"_kjc, r2Result.success());\n  KJ_IF_SOME(e, r2Result.getR2ErrorMessage()) {\n    traceContext.setTag(\"error.type\"_kjc, e.asPtr());\n    traceContext.setTag(\"cloudflare.r2.error.message\"_kjc, e.asPtr());\n  }\n  KJ_IF_SOME(v4, r2Result.v4ErrorCode()) {\n    traceContext.setTag(\"cloudflare.r2.error.code\"_kjc, static_cast<int64_t>(v4));\n  }\n}\n\njsg::Promise<R2MultipartUpload::UploadedPart> R2MultipartUpload::uploadPart(jsg::Lock& js,\n    int partNumber,\n    R2PutValue value,\n    jsg::Optional<UploadPartOptions> options,\n    const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType) {\n  return js.evalNow([&] {\n    JSG_REQUIRE(partNumber >= 1 && partNumber <= 10000, TypeError,\n        \"Part number must be between 1 and 10000 (inclusive). Actual value was: \", partNumber);\n\n    auto& context = IoContext::current();\n    TraceContext traceContext = context.makeUserTraceSpan(\"r2_uploadPart\"_kjc);\n\n    traceContext.setTag(\"cloudflare.binding.type\"_kjc, \"r2\"_kjc);\n    KJ_IF_SOME(b, this->bucket->bindingName()) {\n      traceContext.setTag(\"cloudflare.binding.name\"_kjc, b);\n    }\n    traceContext.setTag(\"cloudflare.r2.operation\"_kjc, \"UploadPart\"_kjc);\n    KJ_IF_SOME(b, this->bucket->bucketName()) {\n      traceContext.setTag(\"cloudflare.r2.bucket\"_kjc, b);\n    }\n    traceContext.setTag(\"cloudflare.r2.request.upload_id\"_kjc, uploadId.asPtr());\n    traceContext.setTag(\"cloudflare.r2.request.part_number\"_kjc, static_cast<int64_t>(partNumber));\n    traceContext.setTag(\"cloudflare.r2.request.key\"_kjc, key.asPtr());\n\n    capnp::JsonCodec json;\n    json.handleByAnnotation<R2BindingRequest>();\n    json.setHasMode(capnp::HasMode::NON_DEFAULT);\n    capnp::MallocMessageBuilder requestMessage;\n\n    auto requestBuilder = requestMessage.initRoot<R2BindingRequest>();\n    requestBuilder.setVersion(VERSION_PUBLIC_BETA);\n    auto payloadBuilder = requestBuilder.initPayload();\n    auto uploadPartBuilder = payloadBuilder.initUploadPart();\n\n    uploadPartBuilder.setUploadId(uploadId);\n    uploadPartBuilder.setPartNumber(partNumber);\n    uploadPartBuilder.setObject(key);\n    KJ_IF_SOME(options, options) {\n      KJ_IF_SOME(ssecKey, options.ssecKey) {\n        auto ssecBuilder = uploadPartBuilder.initSsec();\n        KJ_SWITCH_ONEOF(ssecKey) {\n          KJ_CASE_ONEOF(keyString, kj::String) {\n            JSG_REQUIRE(\n                std::regex_match(keyString.begin(), keyString.end(), std::regex(\"^[0-9a-f]+$\")),\n                Error, \"SSE-C Key has invalid format\");\n            JSG_REQUIRE(keyString.size() == 64, Error, \"SSE-C Key must be 32 bytes in length\");\n            ssecBuilder.setKey(kj::str(keyString));\n            traceContext.setTag(\"cloudflare.r2.request.ssec_key\"_kjc, true);\n          }\n          KJ_CASE_ONEOF(keyBuff, kj::Array<byte>) {\n            JSG_REQUIRE(keyBuff.size() == 32, Error, \"SSE-C Key must be 32 bytes in length\");\n            ssecBuilder.setKey(kj::encodeHex(keyBuff));\n            traceContext.setTag(\"cloudflare.r2.request.ssec_key\"_kjc, true);\n          }\n        }\n      }\n    }\n\n    kj::Maybe<int64_t> requestSize = kj::none;\n    KJ_SWITCH_ONEOF(value) {\n      KJ_CASE_ONEOF(stream, jsg::Ref<ReadableStream>) {\n        KJ_IF_SOME(size, stream->tryGetLength(StreamEncoding::IDENTITY)) {\n          requestSize = size;\n        }\n      }\n      KJ_CASE_ONEOF(text, jsg::NonCoercible<kj::String>) {\n        requestSize = text.value.size();\n      }\n      KJ_CASE_ONEOF(data, kj::Array<byte>) {\n        requestSize = data.size();\n      }\n      KJ_CASE_ONEOF(blob, jsg::Ref<Blob>) {\n        requestSize = blob->getSize();\n      }\n    }\n    KJ_IF_SOME(size, requestSize) {\n      traceContext.setTag(\"cloudflare.r2.request.size\"_kjc, size);\n    }\n\n    auto requestJson = json.encode(requestBuilder);\n    auto bucket = this->bucket->adminBucket.map([](auto&& s) { return kj::str(s); });\n\n    kj::StringPtr components[1];\n    auto path = fillR2Path(components, this->bucket->adminBucket);\n    auto client = context.getHttpClient(this->bucket->clientIndex, true, kj::none, traceContext);\n    auto promise = doR2HTTPPutRequest(\n        kj::mv(client), kj::mv(value), kj::none, kj::mv(requestJson), path, kj::none);\n\n    return context.awaitIo(js, kj::mv(promise),\n        [&errorType, partNumber, traceContext = kj::mv(traceContext)](\n            jsg::Lock& js, R2Result r2Result) mutable {\n      addR2ResponseSpanTags(traceContext, r2Result);\n      r2Result.throwIfError(\"uploadPart\", errorType);\n\n      capnp::MallocMessageBuilder responseMessage;\n      capnp::JsonCodec json;\n      json.handleByAnnotation<R2UploadPartResponse>();\n      auto responseBuilder = responseMessage.initRoot<R2UploadPartResponse>();\n\n      json.decode(KJ_ASSERT_NONNULL(r2Result.metadataPayload), responseBuilder);\n      kj::StringPtr etag = responseBuilder.getEtag();\n      traceContext.setTag(\"cloudflare.r2.response.etag\"_kjc, etag);\n      UploadedPart uploadedPart = {partNumber, kj::str(etag)};\n      return uploadedPart;\n    });\n  });\n}\n\njsg::Promise<jsg::Ref<R2Bucket::HeadResult>> R2MultipartUpload::complete(jsg::Lock& js,\n    kj::Array<UploadedPart> uploadedParts,\n    const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType) {\n  return js.evalNow([&] {\n    auto& context = IoContext::current();\n    TraceContext traceContext = context.makeUserTraceSpan(\"r2_completeMultipartUpload\"_kjc);\n\n    traceContext.setTag(\"cloudflare.binding.type\"_kjc, \"r2\"_kjc);\n    KJ_IF_SOME(b, this->bucket->bindingName()) {\n      traceContext.setTag(\"cloudflare.binding.name\"_kjc, b);\n    }\n    traceContext.setTag(\"cloudflare.r2.operation\"_kjc, \"CompleteMultipartUpload\"_kjc);\n    KJ_IF_SOME(b, this->bucket->bucketName()) {\n      traceContext.setTag(\"cloudflare.r2.bucket\"_kjc, b);\n    }\n    traceContext.setTag(\"cloudflare.r2.request.upload_id\"_kjc, uploadId.asPtr());\n    traceContext.setTag(\"cloudflare.r2.request.key\"_kjc, key.asPtr());\n    kj::String partIds =\n        kj::strArray(KJ_MAP(part, uploadedParts) { return kj::str(part.partNumber); }, \", \");\n    traceContext.setTag(\"cloudflare.r2.request.uploaded_parts\"_kjc, kj::mv(partIds));\n\n    capnp::JsonCodec json;\n    json.handleByAnnotation<R2BindingRequest>();\n    capnp::MallocMessageBuilder requestMessage;\n\n    auto requestBuilder = requestMessage.initRoot<R2BindingRequest>();\n    requestBuilder.setVersion(VERSION_PUBLIC_BETA);\n    auto completeMultipartUploadBuilder =\n        requestBuilder.initPayload().initCompleteMultipartUpload();\n\n    completeMultipartUploadBuilder.setObject(key);\n    completeMultipartUploadBuilder.setUploadId(uploadId);\n\n    auto partsList = completeMultipartUploadBuilder.initParts(uploadedParts.size());\n    UploadedPart* currentPart = uploadedParts.begin();\n    for (unsigned int i = 0; i < uploadedParts.size(); i++) {\n      int partNumber = currentPart->partNumber;\n      JSG_REQUIRE(partNumber >= 1 && partNumber <= 10000, TypeError,\n          \"Part number must be between 1 and 10000 (inclusive). Actual value was: \", partNumber);\n      partsList[i].setPart(partNumber);\n      partsList[i].setEtag(currentPart->etag);\n      currentPart = std::next(currentPart);\n    }\n\n    auto requestJson = json.encode(requestBuilder);\n\n    kj::StringPtr components[1];\n    auto path = fillR2Path(components, this->bucket->adminBucket);\n    auto client = context.getHttpClient(this->bucket->clientIndex, true, kj::none, traceContext);\n    auto promise =\n        doR2HTTPPutRequest(kj::mv(client), kj::none, kj::none, kj::mv(requestJson), path, kj::none);\n\n    return context.awaitIo(js, kj::mv(promise),\n        [&errorType, traceContext = kj::mv(traceContext)](\n            jsg::Lock& js, R2Result r2Result) mutable {\n      addR2ResponseSpanTags(traceContext, r2Result);\n      auto parsedObject =\n          parseHeadResultWrapper(js, \"completeMultipartUpload\", r2Result, errorType);\n      KJ_IF_SOME(obj, parsedObject) {\n        addHeadResultSpanTags(js, traceContext, *obj.get());\n        return obj.addRef();\n      } else {\n        KJ_FAIL_ASSERT(\n            \"Shouldn't happen, multipart completion should either error or return an object\");\n      }\n    });\n  });\n}\n\njsg::Promise<void> R2MultipartUpload::abort(\n    jsg::Lock& js, const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType) {\n  return js.evalNow([&] {\n    auto& context = IoContext::current();\n    TraceContext traceContext = context.makeUserTraceSpan(\"r2_abortMultipartUpload\"_kjc);\n\n    traceContext.setTag(\"cloudflare.binding.type\"_kjc, \"r2\"_kjc);\n    KJ_IF_SOME(b, this->bucket->bindingName()) {\n      traceContext.setTag(\"cloudflare.binding.name\"_kjc, b);\n    }\n    traceContext.setTag(\"cloudflare.r2.operation\"_kjc, \"AbortMultipartUpload\"_kjc);\n    KJ_IF_SOME(b, this->bucket->bucketName()) {\n      traceContext.setTag(\"cloudflare.r2.bucket\"_kjc, b);\n    }\n    traceContext.setTag(\"cloudflare.r2.request.upload_id\"_kjc, uploadId.asPtr());\n    traceContext.setTag(\"cloudflare.r2.request.key\"_kjc, key.asPtr());\n\n    capnp::JsonCodec json;\n    json.handleByAnnotation<R2BindingRequest>();\n    capnp::MallocMessageBuilder requestMessage;\n\n    auto requestBuilder = requestMessage.initRoot<R2BindingRequest>();\n    requestBuilder.setVersion(VERSION_PUBLIC_BETA);\n    auto abortMultipartUploadBuilder = requestBuilder.initPayload().initAbortMultipartUpload();\n\n    abortMultipartUploadBuilder.setObject(key);\n    abortMultipartUploadBuilder.setUploadId(uploadId);\n\n    auto requestJson = json.encode(requestBuilder);\n\n    kj::StringPtr components[1];\n    auto path = fillR2Path(components, this->bucket->adminBucket);\n    auto client = context.getHttpClient(this->bucket->clientIndex, true, kj::none, traceContext);\n    auto promise =\n        doR2HTTPPutRequest(kj::mv(client), kj::none, kj::none, kj::mv(requestJson), path, kj::none);\n\n    return context.awaitIo(js, kj::mv(promise),\n        [&errorType, traceContext = kj::mv(traceContext)](\n            jsg::Lock& js, R2Result r2Result) mutable {\n      addR2ResponseSpanTags(traceContext, r2Result);\n      if (r2Result.objectNotFound()) {\n        return;\n      }\n\n      r2Result.throwIfError(\"abortMultipartUpload\", errorType);\n    });\n  });\n}\n}  // namespace workerd::api::public_beta\n"
  },
  {
    "path": "src/workerd/api/r2-multipart.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"r2-bucket.h\"\n\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api::public_beta {\n\nclass R2MultipartUpload: public jsg::Object {\n public:\n  struct UploadedPart {\n    int partNumber;\n    kj::String etag;\n\n    JSG_STRUCT(partNumber, etag);\n    JSG_STRUCT_TS_OVERRIDE(R2UploadedPart);\n  };\n  struct UploadPartOptions {\n    jsg::Optional<kj::OneOf<kj::Array<byte>, kj::String>> ssecKey;\n\n    JSG_STRUCT(ssecKey);\n    JSG_STRUCT_TS_OVERRIDE(R2UploadPartOptions);\n  };\n\n  R2MultipartUpload(kj::String key, kj::String uploadId, jsg::Ref<R2Bucket> bucket)\n      : key(kj::mv(key)),\n        uploadId(kj::mv(uploadId)),\n        bucket(kj::mv(bucket)) {}\n\n  kj::StringPtr getKey() const {\n    return key;\n  }\n  kj::StringPtr getUploadId() const {\n    return uploadId;\n  }\n\n  jsg::Promise<UploadedPart> uploadPart(jsg::Lock& js,\n      int partNumber,\n      R2PutValue value,\n      jsg::Optional<UploadPartOptions> options,\n      const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType);\n  jsg::Promise<void> abort(jsg::Lock& js, const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType);\n  jsg::Promise<jsg::Ref<R2Bucket::HeadResult>> complete(jsg::Lock& js,\n      kj::Array<UploadedPart> uploadedParts,\n      const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType);\n\n  JSG_RESOURCE_TYPE(R2MultipartUpload) {\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(key, getKey);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(uploadId, getUploadId);\n    JSG_METHOD(uploadPart);\n    JSG_METHOD(abort);\n    JSG_METHOD(complete);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"key\", key);\n    tracker.trackField(\"uploadId\", uploadId);\n    tracker.trackField(\"bucket\", bucket);\n  }\n\n protected:\n  kj::String key;\n  kj::String uploadId;\n  jsg::Ref<R2Bucket> bucket;\n\n private:\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(bucket);\n  }\n};\n\n}  // namespace workerd::api::public_beta\n"
  },
  {
    "path": "src/workerd/api/r2-rpc.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"r2-rpc.h\"\n\n#include <workerd/api/r2-api.capnp.h>\n#include <workerd/api/system-streams.h>\n#include <workerd/api/util.h>\n#include <workerd/util/http-util.h>\n// This is imported for the error type and that's shared between internal and public beta.\n\n#include <capnp/compat/json.h>\n#include <capnp/message.h>\n#include <kj/compat/http.h>\n\nnamespace workerd::api {\nstatic kj::Own<R2Error> toError(uint statusCode, kj::StringPtr responseBody) {\n  capnp::JsonCodec json;\n  json.handleByAnnotation<public_beta::R2ErrorResponse>();\n  capnp::MallocMessageBuilder errorMessageArena;\n  auto errorMessage = errorMessageArena.initRoot<public_beta::R2ErrorResponse>();\n  json.decode(responseBody, errorMessage);\n\n  return kj::refcounted<R2Error>(errorMessage.getV4code(), kj::str(errorMessage.getMessage()));\n}\n\njsg::JsValue R2Error::getStack(jsg::Lock& js) {\n  return jsg::JsObject(KJ_ASSERT_NONNULL(errorForStack).Get(js.v8Isolate)).get(js, \"stack\"_kj);\n}\n\nkj::Maybe<uint> R2Result::v4ErrorCode() {\n  KJ_IF_SOME(e, toThrow) {\n    return e->v4Code;\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::String> R2Result::getR2ErrorMessage() {\n  KJ_IF_SOME(e, toThrow) {\n    return kj::str(e->getMessage());\n  }\n  return kj::none;\n}\n\nvoid R2Result::throwIfError(\n    kj::StringPtr action, const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType) {\n  KJ_IF_SOME(e, toThrow) {\n    // TODO(soon): Once jsg::JsPromise exists, switch to using that to tunnel out the exception. As\n    // it stands today, unfortunately, all we can send back to the user is a message. R2Error isn't\n    // a registered type in the runtime. When reenabling, make sure to update overrides/r2.d.ts to\n    // reenable the type\n#if 0\n    auto isolate = IoContext::current().getCurrentLock().getIsolate();\n    (*e)->action = kj::str(action);\n    (*e)->errorForStack = v8::Global<v8::Object>(\n        isolate, v8::Exception::Error(v8::String::Empty(isolate)).As<v8::Object>());\n    isolate->ThrowException(errorType.wrapRef(kj::mv(*e)));\n    throw jsg::JsExceptionThrown();\n#else\n    JSG_FAIL_REQUIRE(Error, kj::str(action, \": \", e.get()->getMessage(), \" (\", e->v4Code, ')'));\n#endif\n  }\n}\n\nnamespace {\nkj::String getFakeUrl(kj::ArrayPtr<kj::StringPtr> path) {\n  kj::Url url;\n  url.scheme = kj::str(\"https\");\n  url.host = kj::str(\"fake-host\");\n  for (const auto& p: path) {\n    url.path.add(kj::str(p));\n  }\n  return url.toString(kj::Url::Context::HTTP_PROXY_REQUEST);\n}\n}  // namespace\n\nkj::Promise<R2Result> doR2HTTPGetRequest(kj::Own<kj::HttpClient> client,\n    kj::String metadataPayload,\n    kj::ArrayPtr<kj::StringPtr> path,\n    kj::Maybe<kj::StringPtr> jwt,\n    CompatibilityFlags::Reader flags) {\n  auto& context = IoContext::current();\n  auto url = getFakeUrl(path);\n\n  auto& headerIds = context.getHeaderIds();\n\n  auto requestHeaders = kj::HttpHeaders(context.getHeaderTable());\n  requestHeaders.set(headerIds.cfBlobRequest, kj::mv(metadataPayload));\n  KJ_IF_SOME(j, jwt) {\n    requestHeaders.set(headerIds.authorization, kj::str(\"Bearer \", j));\n  }\n\n  static auto constexpr processStream =\n      [](kj::StringPtr metadata, kj::HttpClient::Response& response, kj::Own<kj::HttpClient> client,\n          CompatibilityFlags::Reader flags, IoContext& context) -> kj::Promise<R2Result> {\n    auto stream = newSystemStream(response.body.attach(kj::mv(client)),\n        getContentEncoding(context, *response.headers, Response::BodyEncoding::AUTO, flags),\n        context);\n    auto metadataSize = atoi((metadata).cStr());\n    // R2 itself will try to stick to a cap of 256 KiB of response here. However for listing\n    // sometimes our heuristics have corner cases. This way we're more lenient in case someone\n    // finds a corner case for the heuristic so that we don't fail the GET with an opaque\n    // internal error.\n    KJ_REQUIRE(metadataSize <= 1024 * 1024, \"R2 metadata size seems way too large\");\n    KJ_REQUIRE(metadataSize >= 0, \"R2 metadata size parsed as negative\");\n\n    auto metadataBuffer = kj::heapArray<char>(metadataSize);\n    auto metadataReadLength =\n        co_await stream->tryRead(metadataBuffer.begin(), metadataSize, metadataSize);\n\n    KJ_ASSERT(\n        metadataReadLength == metadataBuffer.size(), \"R2 metadata buffer not read fully/overflow?\");\n\n    co_return R2Result{.httpStatus = response.statusCode,\n      .metadataPayload = kj::mv(metadataBuffer),\n      .stream = kj::mv(stream)};\n  };\n\n  auto request =\n      client->request(kj::HttpMethod::GET, url, requestHeaders, static_cast<uint64_t>(0));\n\n  auto response = co_await request.response;\n\n  if (response.statusCode >= 400) {\n    // Error responses should have a cfR2ErrorHeader but don't always. If there\n    // isn't one, we'll use a generic error.\n    if (response.headers->get(headerIds.cfR2ErrorHeader) == kj::none) {\n      LOG_WARNING_ONCE(\n          \"R2 error response does not contain the CF-R2-Error header.\", response.statusCode);\n    }\n    auto error =\n        response.headers->get(headerIds.cfR2ErrorHeader)\n            .orDefault(\"{\\\"version\\\":0,\\\"v4code\\\":0,\\\"message\\\":\\\"Unspecified error\\\"}\"_kj);\n\n    R2Result result = {\n      .httpStatus = response.statusCode,\n      .toThrow = toError(response.statusCode, error),\n    };\n\n    KJ_IF_SOME(m, response.headers->get(headerIds.cfBlobMetadataSize)) {\n      auto processed = co_await processStream(m, response, kj::mv(client), flags, context);\n      result.metadataPayload = kj::mv(processed.metadataPayload);\n      result.stream = kj::mv(processed.stream);\n    }\n\n    co_return kj::mv(result);\n  }\n\n  KJ_IF_SOME(m, response.headers->get(headerIds.cfBlobMetadataSize)) {\n    co_return co_await processStream(m, response, kj::mv(client), flags, context);\n  } else {\n    co_return R2Result{.httpStatus = response.statusCode};\n  }\n}\n\nkj::Promise<R2Result> doR2HTTPPutRequest(kj::Own<kj::HttpClient> client,\n    kj::Maybe<R2PutValue> supportedBody,\n    kj::Maybe<uint64_t> streamSize,\n    kj::String metadataPayload,\n    kj::ArrayPtr<kj::StringPtr> path,\n    kj::Maybe<kj::StringPtr> jwt) {\n  // NOTE: A lot of code here is duplicated with kv.c++. Maybe it can be refactored to be more\n  // reusable?\n  auto& context = IoContext::current();\n  auto headers = kj::HttpHeaders(context.getHeaderTable());\n  auto url = getFakeUrl(path);\n\n  kj::Maybe<uint64_t> expectedBodySize;\n\n  KJ_IF_SOME(b, supportedBody) {\n    KJ_SWITCH_ONEOF(b) {\n      KJ_CASE_ONEOF(stream, jsg::Ref<ReadableStream>) {\n        expectedBodySize = stream->tryGetLength(StreamEncoding::IDENTITY);\n        if (expectedBodySize == kj::none) {\n          expectedBodySize = streamSize;\n        }\n        JSG_REQUIRE(expectedBodySize != kj::none, TypeError,\n            \"Provided readable stream must have a known length (request/response body or readable \"\n            \"half of FixedLengthStream)\");\n        JSG_REQUIRE(streamSize.orDefault(KJ_ASSERT_NONNULL(expectedBodySize)) == expectedBodySize,\n            RangeError, \"Provided stream length (\", streamSize.orDefault(-1),\n            \") doesn't match what \"\n            \"the stream reports (\",\n            KJ_ASSERT_NONNULL(expectedBodySize), \")\");\n      }\n      KJ_CASE_ONEOF(text, jsg::NonCoercible<kj::String>) {\n        expectedBodySize = text.value.size();\n        KJ_REQUIRE(streamSize == kj::none);\n      }\n      KJ_CASE_ONEOF(data, kj::Array<kj::byte>) {\n        expectedBodySize = data.size();\n        KJ_REQUIRE(streamSize == kj::none);\n      }\n      KJ_CASE_ONEOF(data, jsg::Ref<Blob>) {\n        expectedBodySize = data->getSize();\n        KJ_REQUIRE(streamSize == kj::none);\n      }\n    }\n  } else {\n    expectedBodySize = static_cast<uint64_t>(0);\n    KJ_REQUIRE(streamSize == kj::none);\n  }\n\n  headers.set(context.getHeaderIds().cfBlobMetadataSize, kj::str(metadataPayload.size()));\n  KJ_IF_SOME(j, jwt) {\n    headers.set(context.getHeaderIds().authorization, kj::str(\"Bearer \", j));\n  }\n\n  uint64_t combinedSize = metadataPayload.size() + KJ_ASSERT_NONNULL(expectedBodySize);\n\n  co_await context.waitForOutputLocks();\n\n  auto request = client->request(kj::HttpMethod::PUT, url, headers, combinedSize);\n\n  co_await request.body->write(metadataPayload.asBytes());\n\n  KJ_IF_SOME(b, supportedBody) {\n    KJ_SWITCH_ONEOF(b) {\n      KJ_CASE_ONEOF(text, jsg::NonCoercible<kj::String>) {\n        co_await request.body->write(text.value.asBytes());\n      }\n      KJ_CASE_ONEOF(data, kj::Array<byte>) {\n        co_await request.body->write(data);\n      }\n      KJ_CASE_ONEOF(blob, jsg::Ref<Blob>) {\n        auto data = blob->getData();\n        co_await request.body->write(data);\n      }\n      KJ_CASE_ONEOF(stream, jsg::Ref<ReadableStream>) {\n        // Because the ReadableStream might be a fully JavaScript-backed stream, we must\n        // start running the pump within the IoContext/isolate lock.\n        co_await context.run(\n            [dest = newSystemStream(kj::mv(request.body), StreamEncoding::IDENTITY, context),\n                stream = kj::mv(stream)](jsg::Lock& js) mutable {\n          return IoContext::current().waitForDeferredProxy(stream->pumpTo(js, kj::mv(dest), true));\n        });\n      }\n    }\n  }\n\n  auto response = co_await request.response;\n\n  if (response.statusCode >= 400) {\n    // Error responses should have a cfR2ErrorHeader but don't always. If there\n    // isn't one, we'll use a generic error.\n    auto& headerIds = context.getHeaderIds();\n    if (response.headers->get(headerIds.cfR2ErrorHeader) == kj::none) {\n      LOG_WARNING_ONCE(\n          \"R2 error response does not contain the CF-R2-Error header.\", response.statusCode);\n    }\n    auto error =\n        response.headers->get(headerIds.cfR2ErrorHeader)\n            .orDefault(\"{\\\"version\\\":0,\\\"v4code\\\":0,\\\"message\\\":\\\"Unspecified error\\\"}\"_kj);\n\n    co_return R2Result{\n      .httpStatus = response.statusCode,\n      .toThrow = toError(response.statusCode, error),\n    };\n  }\n\n  auto responseBody = co_await response.body->readAllText();\n\n  co_return R2Result{\n    .httpStatus = response.statusCode,\n    .metadataPayload = responseBody.releaseArray(),\n  };\n}\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/r2-rpc.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/api/blob.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace kj {\nclass HttpClient;\n}\n\nnamespace workerd::api {\n\nclass ReadableStreamSource;\nclass ReadableStream;\n\n// NOTE: We don't currently actually use this as a structured object (hence the `kj::Own<R2Error>`\n// that we see pop up).\n// TODO(soon): Switch to structured objects and use jsg::Ref<R2Error> instead of kj::Own<R2Error>\n//   to maintain ownership.\nclass R2Error: public jsg::Object {\n public:\n  R2Error(uint v4Code, kj::String message): v4Code(v4Code), message(kj::mv(message)) {}\n\n  constexpr kj::StringPtr getName() const {\n    return \"R2Error\"_kj;\n  }\n  uint getV4Code() const {\n    return v4Code;\n  }\n  kj::StringPtr getMessage() const {\n    return message;\n  }\n  kj::StringPtr getAction() const {\n    return KJ_ASSERT_NONNULL(action);\n  }\n  jsg::JsValue getStack(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(R2Error) {\n    JSG_INHERIT_INTRINSIC(v8::kErrorPrototype);\n\n    JSG_READONLY_INSTANCE_PROPERTY(name, getName);\n    JSG_READONLY_INSTANCE_PROPERTY(code, getV4Code);\n    JSG_READONLY_INSTANCE_PROPERTY(message, getMessage);\n    JSG_READONLY_INSTANCE_PROPERTY(action, getAction);\n\n    JSG_READONLY_INSTANCE_PROPERTY(stack, getStack);\n    // See getStack in dom-exception.h\n\n    JSG_TS_ROOT();\n  }\n\n private:\n  uint v4Code;\n  kj::String message;\n  kj::Maybe<kj::String> action;\n  // Initialized when thrown.\n\n  kj::Maybe<v8::Global<v8::Object>> errorForStack;\n  // See dom-exception.h.\n\n  friend struct R2Result;\n};\n\nusing R2PutValue = kj::OneOf<jsg::Ref<ReadableStream>,\n    kj::Array<kj::byte>,\n    jsg::NonCoercible<kj::String>,\n    jsg::Ref<Blob>>;\n\nstruct R2Result {\n  uint httpStatus;\n\n  // Non-null if httpStatus >= 400.\n  kj::Maybe<kj::Own<R2Error>> toThrow;\n\n  kj::Maybe<kj::Array<char>> metadataPayload;\n\n  kj::Maybe<kj::Own<workerd::api::ReadableStreamSource>> stream;\n\n  bool objectNotFound() {\n    return httpStatus == 404 && v4ErrorCode() == 10007;\n  }\n\n  bool preconditionFailed() {\n    return httpStatus == 412 && (v4ErrorCode() == 10031 || v4ErrorCode() == 10032);\n  }\n\n  bool success() {\n    return httpStatus >= 200 && httpStatus < 400;\n  }\n\n  kj::Maybe<uint> v4ErrorCode();\n  kj::Maybe<kj::String> getR2ErrorMessage();\n  void throwIfError(kj::StringPtr action, const jsg::TypeHandler<jsg::Ref<R2Error>>& errorType);\n};\n\nkj::Promise<R2Result> doR2HTTPGetRequest(kj::Own<kj::HttpClient> client,\n    kj::String metadataPayload,\n    kj::ArrayPtr<kj::StringPtr> path,\n    kj::Maybe<kj::StringPtr> jwt,\n    CompatibilityFlags::Reader flags);\n\nkj::Promise<R2Result> doR2HTTPPutRequest(kj::Own<kj::HttpClient> client,\n    kj::Maybe<R2PutValue> value,\n    kj::Maybe<uint64_t> streamSize,\n    // Deprecated. For internal beta API only.\n    kj::String metadataPayload,\n    kj::ArrayPtr<kj::StringPtr> path,\n    kj::Maybe<kj::StringPtr> jwt);\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/r2.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"r2-bucket.h\"\n#include \"r2-multipart.h\"\n\nnamespace workerd::api::public_beta {\n#define EW_R2_PUBLIC_BETA_ISOLATE_TYPES                                                            \\\n  api::R2Error, api::public_beta::R2Bucket, api::public_beta::R2MultipartUpload,                   \\\n      api::public_beta::R2MultipartUpload::UploadedPart, api::public_beta::R2Bucket::HeadResult,   \\\n      api::public_beta::R2Bucket::GetResult, api::public_beta::R2Bucket::Range,                    \\\n      api::public_beta::R2Bucket::Conditional, api::public_beta::R2Bucket::GetOptions,             \\\n      api::public_beta::R2Bucket::PutOptions, api::public_beta::R2Bucket::MultipartOptions,        \\\n      api::public_beta::R2Bucket::Checksums, api::public_beta::R2Bucket::StringChecksums,          \\\n      api::public_beta::R2Bucket::HttpMetadata, api::public_beta::R2Bucket::ListOptions,           \\\n      api::public_beta::R2Bucket::ListResult,                                                      \\\n      api::public_beta::R2MultipartUpload::UploadPartOptions\n// The list of r2 types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE\n}  // namespace workerd::api::public_beta\n"
  },
  {
    "path": "src/workerd/api/rtti.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"rtti.h\"\n\n#include <workerd/api/actor-state.h>\n#include <workerd/api/actor.h>\n#include <workerd/api/analytics-engine.h>\n#include <workerd/api/base64.h>\n#include <workerd/api/cache.h>\n#include <workerd/api/crypto/crypto.h>\n#include <workerd/api/encoding.h>\n#include <workerd/api/events.h>\n#include <workerd/api/eventsource.h>\n#include <workerd/api/export-loopback.h>\n#include <workerd/api/filesystem.h>\n#include <workerd/api/global-scope.h>\n#include <workerd/api/html-rewriter.h>\n#include <workerd/api/hyperdrive.h>\n#include <workerd/api/kv.h>\n#include <workerd/api/memory-cache.h>\n#include <workerd/api/messagechannel.h>\n#include <workerd/api/modules.h>\n#include <workerd/api/node/node.h>\n#include <workerd/api/performance.h>\n#include <workerd/api/pyodide/pyodide.h>\n#include <workerd/api/queue.h>\n#include <workerd/api/r2-admin.h>\n#include <workerd/api/r2.h>\n#include <workerd/api/scheduled.h>\n#include <workerd/api/sockets.h>\n#include <workerd/api/sql.h>\n#include <workerd/api/streams.h>\n#include <workerd/api/streams/standard.h>\n#include <workerd/api/sync-kv.h>\n#include <workerd/api/trace.h>\n#include <workerd/api/tracing-module.h>\n#include <workerd/api/unsafe.h>\n#include <workerd/api/url-standard.h>\n#include <workerd/api/urlpattern-standard.h>\n#include <workerd/api/urlpattern.h>\n#include <workerd/api/worker-loader.h>\n#include <workerd/api/worker-rpc.h>\n#include <workerd/api/workers-module.h>\n#include <workerd/io/compatibility-date.h>\n#include <workerd/jsg/modules.capnp.h>\n#include <workerd/jsg/rtti.h>\n\n#include <kj/vector.h>\n\n#define EW_TYPE_GROUP_FOR_EACH(F)                                                                  \\\n  F(\"dom-exception\", jsg::DOMException)                                                            \\\n  F(\"global-scope\", EW_GLOBAL_SCOPE_ISOLATE_TYPES)                                                 \\\n  F(\"durable-objects\", EW_ACTOR_ISOLATE_TYPES)                                                     \\\n  F(\"durable-objects-state\", EW_ACTOR_STATE_ISOLATE_TYPES)                                         \\\n  F(\"analytics-engine\", EW_ANALYTICS_ENGINE_ISOLATE_TYPES)                                         \\\n  F(\"basics\", EW_BASICS_ISOLATE_TYPES)                                                             \\\n  F(\"blob\", EW_BLOB_ISOLATE_TYPES)                                                                 \\\n  F(\"cache\", EW_CACHE_ISOLATE_TYPES)                                                               \\\n  F(\"crypto\", EW_CRYPTO_ISOLATE_TYPES)                                                             \\\n  F(\"encoding\", EW_ENCODING_ISOLATE_TYPES)                                                         \\\n  F(\"events\", EW_EVENTS_ISOLATE_TYPES)                                                             \\\n  F(\"form-data\", EW_FORMDATA_ISOLATE_TYPES)                                                        \\\n  F(\"html-rewriter\", EW_HTML_REWRITER_ISOLATE_TYPES)                                               \\\n  F(\"http\", EW_HTTP_ISOLATE_TYPES)                                                                 \\\n  F(\"hyperdrive\", EW_HYPERDRIVE_ISOLATE_TYPES)                                                     \\\n  F(\"unsafe\", EW_UNSAFE_ISOLATE_TYPES)                                                             \\\n  F(\"memory-cache\", EW_MEMORY_CACHE_ISOLATE_TYPES)                                                 \\\n  F(\"pyodide\", EW_PYODIDE_ISOLATE_TYPES)                                                           \\\n  F(\"kv\", EW_KV_ISOLATE_TYPES)                                                                     \\\n  F(\"queue\", EW_QUEUE_ISOLATE_TYPES)                                                               \\\n  F(\"r2-admin\", EW_R2_PUBLIC_BETA_ADMIN_ISOLATE_TYPES)                                             \\\n  F(\"r2\", EW_R2_PUBLIC_BETA_ISOLATE_TYPES)                                                         \\\n  F(\"worker-rpc\", EW_WORKER_RPC_ISOLATE_TYPES)                                                     \\\n  F(\"scheduled\", EW_SCHEDULED_ISOLATE_TYPES)                                                       \\\n  F(\"streams\", EW_STREAMS_ISOLATE_TYPES)                                                           \\\n  F(\"trace\", EW_TRACE_ISOLATE_TYPES)                                                               \\\n  F(\"url\", EW_URL_ISOLATE_TYPES)                                                                   \\\n  F(\"url-standard\", EW_URL_STANDARD_ISOLATE_TYPES)                                                 \\\n  F(\"url-pattern\", EW_URLPATTERN_ISOLATE_TYPES)                                                    \\\n  F(\"url-pattern-standard\", EW_URLPATTERN_STANDARD_ISOLATE_TYPES)                                  \\\n  F(\"websocket\", EW_WEBSOCKET_ISOLATE_TYPES)                                                       \\\n  F(\"sql\", EW_SQL_ISOLATE_TYPES)                                                                   \\\n  F(\"sockets\", EW_SOCKETS_ISOLATE_TYPES)                                                           \\\n  F(\"base64\", EW_BASE64_ISOLATE_TYPES)                                                             \\\n  F(\"node\", EW_NODE_ISOLATE_TYPES)                                                                 \\\n  F(\"rtti\", EW_RTTI_ISOLATE_TYPES)                                                                 \\\n  F(\"eventsource\", EW_EVENTSOURCE_ISOLATE_TYPES)                                                   \\\n  F(\"container\", EW_CONTAINER_ISOLATE_TYPES)                                                       \\\n  F(\"webfs\", EW_WEB_FILESYSTEM_ISOLATE_TYPE)                                                       \\\n  F(\"messagechannel\", EW_MESSAGECHANNEL_ISOLATE_TYPES)                                             \\\n  F(\"workers-module\", EW_WORKERS_MODULE_ISOLATE_TYPES)                                             \\\n  F(\"export-loopback\", EW_EXPORT_LOOPBACK_ISOLATE_TYPES)                                           \\\n  F(\"sync-kv\", EW_SYNC_KV_ISOLATE_TYPES)                                                           \\\n  F(\"worker-loader\", EW_WORKER_LOADER_ISOLATE_TYPES)                                               \\\n  F(\"performance\", EW_PERFORMANCE_ISOLATE_TYPES)                                                   \\\n  F(\"tracing-module\", EW_TRACING_MODULE_ISOLATE_TYPES)\n\nnamespace workerd::api {\n\nnamespace {\n\nstruct EncoderModuleRegistryImpl {\n  struct CppModuleContents {\n    CppModuleContents(kj::String structureName): structureName(kj::mv(structureName)) {}\n\n    kj::String structureName;\n  };\n  struct TypeScriptModuleContents {\n    TypeScriptModuleContents(kj::StringPtr tsDeclarations): tsDeclarations(tsDeclarations) {}\n\n    kj::StringPtr tsDeclarations;\n  };\n  struct ModuleInfo {\n    ModuleInfo(kj::StringPtr specifier,\n        jsg::ModuleType type,\n        kj::OneOf<CppModuleContents, TypeScriptModuleContents> contents)\n        : specifier(specifier),\n          type(type),\n          contents(kj::mv(contents)) {}\n\n    kj::StringPtr specifier;\n    jsg::ModuleType type;\n    kj::OneOf<CppModuleContents, TypeScriptModuleContents> contents;\n  };\n\n  void addBuiltinBundle(\n      jsg::Bundle::Reader bundle, kj::Maybe<jsg::ModuleRegistry::Type> maybeFilter = kj::none) {\n    for (auto module: bundle.getModules()) {\n      if (module.getType() == maybeFilter.orDefault(module.getType())) addBuiltinModule(module);\n    }\n  }\n\n  template <typename Func>\n  void addBuiltinBundleFiltered(jsg::Bundle::Reader bundle, Func filter) {\n    for (auto module: bundle.getModules()) {\n      if (filter(module)) {\n        addBuiltinModule(module);\n      }\n    }\n  }\n\n  void addBuiltinModule(jsg::Module::Reader module) {\n    TypeScriptModuleContents contents(module.getTsDeclaration());\n    ModuleInfo info(module.getName(), module.getType(), kj::mv(contents));\n    modules.add(kj::mv(info));\n  }\n\n  void addBuiltinModule(kj::StringPtr specifier,\n      jsg::ModuleRegistry::ModuleCallback callback,\n      jsg::ModuleRegistry::Type type = jsg::ModuleRegistry::Type::BUILTIN) {\n    // TODO(soon): Implement this function\n  }\n\n  template <typename T>\n  void addBuiltinModule(kj::StringPtr specifier,\n      jsg::ModuleRegistry::Type type = jsg::ModuleRegistry::Type::BUILTIN) {\n    auto structureName = jsg::fullyQualifiedTypeName(typeid(T));\n    CppModuleContents contents(kj::mv(structureName));\n    ModuleInfo info(specifier, type, kj::mv(contents));\n    modules.add(kj::mv(info));\n  }\n\n  kj::Vector<ModuleInfo> modules;\n};\n\nCompatibilityFlags::Reader compileFlags(capnp::MessageBuilder &message,\n    kj::StringPtr compatDate,\n    bool experimental,\n    kj::ArrayPtr<kj::String> compatFlags) {\n  // Based on src/workerd/io/compatibility-date-test.c++\n  auto orphanage = message.getOrphanage();\n  auto flagListOrphan = orphanage.newOrphan<capnp::List<capnp::Text>>(compatFlags.size());\n  auto flagList = flagListOrphan.get();\n  for (auto i: kj::indices(compatFlags)) {\n    flagList.set(i, compatFlags.begin()[i]);\n  }\n\n  auto output = message.initRoot<CompatibilityFlags>();\n  SimpleWorkerErrorReporter errorReporter;\n\n  compileCompatibilityFlags(compatDate, flagList.asReader(), output, errorReporter, experimental,\n      CompatibilityDateValidation::FUTURE_FOR_TEST);\n\n  if (!errorReporter.errors.empty()) {\n    // TODO(someday): throw an `AggregateError` containing all errors\n    JSG_FAIL_REQUIRE(Error, errorReporter.errors[0]);\n  }\n\n  auto reader = output.asReader();\n  return kj::mv(reader);\n}\n\nCompatibilityFlags::Reader compileAllFlags(capnp::MessageBuilder &message) {\n  auto output = message.initRoot<CompatibilityFlags>();\n  auto schema = capnp::Schema::from<CompatibilityFlags>();\n  auto dynamicOutput = capnp::toDynamic(output);\n  for (auto field: schema.getFields()) {\n    bool isNode = false;\n\n    kj::StringPtr enableFlagName;\n\n    for (auto annotation: field.getProto().getAnnotations()) {\n      if (annotation.getId() == COMPAT_ENABLE_FLAG_ANNOTATION_ID) {\n        enableFlagName = annotation.getValue().getText();\n        // Exclude nodejs_compat, since the type generation scripts don't support node:* imports\n        // TODO: Figure out typing for node compat\n        isNode = enableFlagName == \"nodejs_compat\" || enableFlagName == \"nodejs_compat_v2\";\n      }\n    }\n\n    dynamicOutput.set(field, !isNode);\n  }\n  auto reader = output.asReader();\n  return kj::mv(reader);\n}\n\nstruct TypesEncoder {\n public:\n  TypesEncoder(): compatFlags(kj::heapArray<kj::String>(0)) {}\n  TypesEncoder(kj::String compatDate, kj::Array<kj::String> compatFlags)\n      : compatDate(kj::mv(compatDate)),\n        compatFlags(kj::mv(compatFlags)) {}\n\n  kj::Array<byte> encode() {\n    capnp::MallocMessageBuilder flagsMessage;\n    CompatibilityFlags::Reader flags;\n    KJ_IF_SOME(date, compatDate) {\n      flags = compileFlags(flagsMessage, date, true, compatFlags);\n    } else {\n      flags = compileAllFlags(flagsMessage);\n    }\n    capnp::MallocMessageBuilder message;\n    auto root = message.initRoot<jsg::rtti::StructureGroups>();\n\n    // Encode RTTI structures\n    auto builder = jsg::rtti::Builder(flags);\n\n#define EW_TYPE_GROUP_COUNT(Name, Types) groupsSize++;\n#define EW_TYPE_GROUP_WRITE(Name, Types) writeGroup<Types>(groups, builder, Name);\n\n    unsigned int groupsSize = 0;\n    EW_TYPE_GROUP_FOR_EACH(EW_TYPE_GROUP_COUNT)\n    auto groups = root.initGroups(groupsSize);\n    groupsIndex = 0;\n    EW_TYPE_GROUP_FOR_EACH(EW_TYPE_GROUP_WRITE)\n    KJ_ASSERT(groupsIndex == groupsSize);\n\n#undef EW_TYPE_GROUP_COUNT\n#undef EW_TYPE_GROUP_WRITE\n\n    // Encode modules\n    EncoderModuleRegistryImpl registry;\n    registerModules(registry, flags);\n\n    unsigned int i = 0;\n    auto modulesBuilder = root.initModules(registry.modules.size());\n    for (auto moduleBuilder: modulesBuilder) {\n      auto &module = registry.modules[i++];\n      moduleBuilder.setSpecifier(module.specifier);\n      KJ_SWITCH_ONEOF(module.contents) {\n        KJ_CASE_ONEOF(contents, EncoderModuleRegistryImpl::CppModuleContents) {\n          moduleBuilder.setStructureName(contents.structureName);\n        }\n        KJ_CASE_ONEOF(contents, EncoderModuleRegistryImpl::TypeScriptModuleContents) {\n          moduleBuilder.setTsDeclarations(contents.tsDeclarations);\n        }\n      }\n    }\n\n    auto words = capnp::messageToFlatArray(message);\n    auto bytes = words.asBytes();\n    return kj::heapArray(bytes);\n  }\n\n private:\n  template <typename Type>\n  void writeStructure(jsg::rtti::Builder<CompatibilityFlags::Reader> &builder,\n      capnp::List<jsg::rtti::Structure>::Builder structures) {\n    auto reader = builder.structure<Type>();\n    structures.setWithCaveats(structureIndex++, reader);\n  }\n\n  template <typename... Types>\n  void writeGroup(capnp::List<jsg::rtti::StructureGroups::StructureGroup>::Builder &groups,\n      jsg::rtti::Builder<CompatibilityFlags::Reader> &builder,\n      kj::StringPtr name) {\n    auto group = groups[groupsIndex++];\n    group.setName(name);\n\n    unsigned int structuresSize = sizeof...(Types);\n    auto structures = group.initStructures(structuresSize);\n    structureIndex = 0;\n    (writeStructure<Types>(builder, structures), ...);\n    KJ_ASSERT(structureIndex == structuresSize);\n  }\n\n  kj::Maybe<kj::String> compatDate;\n  kj::Array<kj::String> compatFlags;\n\n  unsigned int groupsIndex = 0;\n  unsigned int structureIndex = 0;\n};\n\n}  // namespace\n\nkj::Array<byte> RTTIModule::exportTypes(kj::String compatDate, kj::Array<kj::String> compatFlags) {\n  TypesEncoder encoder(kj::mv(compatDate), kj::mv(compatFlags));\n  return encoder.encode();\n}\n\nkj::Array<byte> RTTIModule::exportExperimentalTypes() {\n  TypesEncoder encoder;\n  return encoder.encode();\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/rtti.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/modules-new.h>\n#include <workerd/jsg/url.h>\n\n#include <kj/array.h>\n#include <kj/string.h>\n\nnamespace workerd::api {\n\nclass RTTIModule final: public jsg::Object {\n public:\n  RTTIModule() = default;\n  RTTIModule(jsg::Lock&, const jsg::Url&) {}\n\n  kj::Array<byte> exportTypes(kj::String compatDate, kj::Array<kj::String> compatFlags);\n  kj::Array<byte> exportExperimentalTypes();\n\n  JSG_RESOURCE_TYPE(RTTIModule) {\n    JSG_METHOD(exportTypes);\n    JSG_METHOD(exportExperimentalTypes);\n  }\n};\n\ntemplate <class Registry>\nvoid registerRTTIModule(Registry& registry) {\n  registry.template addBuiltinModule<RTTIModule>(\n      \"workerd:rtti\", workerd::jsg::ModuleRegistry::Type::BUILTIN);\n}\n\ntemplate <typename TypeWrapper>\nkj::Own<jsg::modules::ModuleBundle> getExternalRttiModuleBundle(auto featureFlags) {\n  jsg::modules::ModuleBundle::BuiltinBuilder builder(\n      jsg::modules::ModuleBundle::BuiltinBuilder::Type::BUILTIN);\n  static const auto kSpecifier = \"workerd:rtti\"_url;\n  builder.addObject<RTTIModule, TypeWrapper>(kSpecifier);\n  return builder.finish();\n}\n\n#define EW_RTTI_ISOLATE_TYPES api::RTTIModule\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/scheduled.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"scheduled.h\"\n\n#include <workerd/io/io-context.h>\n\nnamespace workerd::api {\n\nScheduledEvent::ScheduledEvent(double scheduledTime, kj::StringPtr cron)\n    : ExtendableEvent(\"scheduled\"),\n      scheduledTime(scheduledTime),\n      cron(kj::str(cron)) {}\n\nvoid ScheduledEvent::noRetry() {\n  IoContext::current().setNoRetryScheduled();\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/scheduled.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"basics.h\"\n\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api {\n\nclass ScheduledEvent final: public ExtendableEvent {\n public:\n  explicit ScheduledEvent(double scheduledTime, kj::StringPtr cron);\n\n  static jsg::Ref<ScheduledEvent> constructor(kj::String type) = delete;\n\n  double getScheduledTime() {\n    return scheduledTime;\n  }\n  kj::StringPtr getCron() {\n    return cron;\n  }\n  void noRetry();\n\n  JSG_RESOURCE_TYPE(ScheduledEvent) {\n    JSG_INHERIT(ExtendableEvent);\n\n    JSG_READONLY_INSTANCE_PROPERTY(scheduledTime, getScheduledTime);\n    JSG_READONLY_INSTANCE_PROPERTY(cron, getCron);\n    JSG_METHOD(noRetry);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"cron\", cron);\n  }\n\n private:\n  double scheduledTime;\n  kj::String cron;\n};\n\n// Type used when calling a module-exported scheduled event handler.\nclass ScheduledController final: public jsg::Object {\n public:\n  ScheduledController(jsg::Ref<ScheduledEvent> event): event(kj::mv(event)) {}\n\n  double getScheduledTime() {\n    return event->getScheduledTime();\n  }\n  kj::StringPtr getCron() {\n    return event->getCron();\n  }\n  void noRetry() {\n    event->noRetry();\n  }\n\n  JSG_RESOURCE_TYPE(ScheduledController) {\n    JSG_READONLY_INSTANCE_PROPERTY(scheduledTime, getScheduledTime);\n    JSG_READONLY_INSTANCE_PROPERTY(cron, getCron);\n    JSG_METHOD(noRetry);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"event\", event);\n  }\n\n private:\n  jsg::Ref<ScheduledEvent> event;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(event);\n  }\n};\n\n#define EW_SCHEDULED_ISOLATE_TYPES api::ScheduledEvent, api::ScheduledController\n// The list of scheduled.h types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/ser-errors-test.c++",
    "content": "#include <workerd/io/features.h>\n#include <workerd/jsg/ser.h>\n#include <workerd/tests/test-fixture.h>\n\n#include <kj/test.h>\n\nnamespace workerd::api {\nnamespace {\n\nKJ_TEST(\"Stacks not preserved in untrusted deserialization\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setEnhancedErrorSerialization(true);\n  flags.setWorkerdExperimental(true);\n  auto t = TestFixture({.featureFlags = flags.asReader()});\n\n  t.runInIoContext([](const workerd::TestFixture::Environment& env) {\n    auto obj = KJ_ASSERT_NONNULL(env.js.typeError(\"\"_kj).tryCast<jsg::JsObject>());\n    obj.set(env.js, \"foo\"_kj, env.js.str(\"bar\"_kj));\n    obj.set(env.js, \"stack\"_kj, env.js.str(\"test stack\"_kj));\n    auto stack = obj.get(env.js, \"stack\"_kj);\n\n    KJ_ASSERT(FeatureFlags::get(env.js).getEnhancedErrorSerialization());\n\n    jsg::Serializer ser(env.js);\n    ser.write(env.js, obj);\n    auto content = ser.release();\n    {\n      // Untrusted... stack must not be preserved.\n      jsg::Deserializer deser(env.js, content.data, kj::none, kj::none,\n          jsg::Deserializer::Options{.preserveStackInErrors = false});\n      auto val = KJ_ASSERT_NONNULL(deser.readValue(env.js).tryCast<jsg::JsObject>());\n      auto checkedStack = val.get(env.js, \"stack\"_kj);\n      KJ_ASSERT(!checkedStack.strictEquals(stack));\n    }\n    {\n      // Trusted ... stack must be preserved.\n      jsg::Deserializer deser(env.js, content.data, kj::none, kj::none,\n          jsg::Deserializer::Options{.preserveStackInErrors = true});\n      auto val = KJ_ASSERT_NONNULL(deser.readValue(env.js).tryCast<jsg::JsObject>());\n      auto checkedStack = val.get(env.js, \"stack\"_kj);\n      KJ_ASSERT(checkedStack.strictEquals(stack));\n    }\n    {\n      // When using structuredClone, stacks are preserved by default.\n      auto obj = KJ_ASSERT_NONNULL(env.js.typeError(\"\"_kj).tryCast<jsg::JsObject>());\n      obj.set(env.js, \"name\"_kj, env.js.str(\"CustomError\"_kj));\n      obj.set(env.js, \"stack\"_kj, env.js.str(\"test stack\"_kj));\n      obj.set(env.js, \"foo\"_kj, env.js.str(\"bar\"_kj));\n\n      auto other = KJ_ASSERT_NONNULL(jsg::structuredClone(env.js, obj).tryCast<jsg::JsObject>());\n      auto checkedStack = other.get(env.js, \"stack\"_kj);\n      KJ_ASSERT(checkedStack.strictEquals(obj.get(env.js, \"stack\"_kj)));\n      KJ_ASSERT(other.get(env.js, \"foo\"_kj).strictEquals(obj.get(env.js, \"foo\"_kj)));\n      KJ_ASSERT(other.get(env.js, \"name\"_kj).strictEquals(obj.get(env.js, \"name\"_kj)));\n    }\n  });\n}\n\nKJ_TEST(\"Stacks preserved by default when using regular deserialization\") {\n  auto t = TestFixture();\n\n  t.runInIoContext([](const workerd::TestFixture::Environment& env) {\n    auto obj = KJ_ASSERT_NONNULL(env.js.typeError(\"\"_kj).tryCast<jsg::JsObject>());\n    obj.set(env.js, \"foo\"_kj, env.js.str(\"bar\"_kj));\n    obj.set(env.js, \"stack\"_kj, env.js.str(\"test stack\"_kj));\n    auto stack = obj.get(env.js, \"stack\"_kj);\n\n    KJ_ASSERT(!FeatureFlags::get(env.js).getEnhancedErrorSerialization());\n\n    jsg::Serializer ser(env.js);\n    ser.write(env.js, obj);\n    auto content = ser.release();\n    {\n      // By default, stacks are preserved.\n      jsg::Deserializer deser(env.js, content.data);\n      auto val = KJ_ASSERT_NONNULL(deser.readValue(env.js).tryCast<jsg::JsObject>());\n      auto checkedStack = val.get(env.js, \"stack\"_kj);\n      KJ_ASSERT(checkedStack.strictEquals(stack));\n    }\n    {\n      // Trusted ... stack must be preserved.\n      jsg::Deserializer deser(env.js, content.data, kj::none, kj::none,\n          // The option is ignored since the compat flag is off\n          jsg::Deserializer::Options{.preserveStackInErrors = false});\n      auto val = KJ_ASSERT_NONNULL(deser.readValue(env.js).tryCast<jsg::JsObject>());\n      auto checkedStack = val.get(env.js, \"stack\"_kj);\n      KJ_ASSERT(checkedStack.strictEquals(stack));\n    }\n  });\n}\n\nKJ_TEST(\"Tunneled exceptions do not preserve stack by default\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setEnhancedErrorSerialization(true);\n  flags.setWorkerdExperimental(true);\n  auto t = TestFixture({.featureFlags = flags.asReader()});\n\n  t.runInIoContext([](const workerd::TestFixture::Environment& env) {\n    auto obj = KJ_ASSERT_NONNULL(env.js.typeError(\"abc\"_kj).tryCast<jsg::JsObject>());\n    obj.set(env.js, \"name\"_kj, env.js.str(\"CustomError\"_kj));\n    obj.set(env.js, \"foo\"_kj, env.js.str(\"bar\"_kj));\n    obj.set(env.js, \"stack\"_kj, env.js.str(\"test stack\"_kj));\n    auto stack = obj.get(env.js, \"stack\"_kj);\n\n    KJ_ASSERT(FeatureFlags::get(env.js).getEnhancedErrorSerialization());\n\n    {\n      // Untrusted... stack must not be preserved.\n      kj::Exception ex = env.js.exceptionToKj(obj);\n      auto val = env.js.exceptionToJsValue(kj::mv(ex));\n      auto obj = KJ_ASSERT_NONNULL(val.getHandle(env.js).tryCast<jsg::JsObject>());\n      auto checkedStack = obj.get(env.js, \"stack\"_kj);\n      KJ_ASSERT(!checkedStack.strictEquals(stack));\n      KJ_ASSERT(obj.get(env.js, \"name\"_kj).strictEquals(env.js.str(\"CustomError\"_kj)));\n      KJ_ASSERT(obj.get(env.js, \"message\"_kj).strictEquals(env.js.str(\"abc\"_kj)));\n      KJ_ASSERT(obj.get(env.js, \"foo\"_kj).strictEquals(env.js.str(\"bar\"_kj)));\n    }\n    {\n      // Trusted... stack must be preserved.\n      kj::Exception ex = env.js.exceptionToKj(obj);\n      auto val = env.js.exceptionToJsValue(kj::mv(ex), {.trusted = true});\n      auto obj = KJ_ASSERT_NONNULL(val.getHandle(env.js).tryCast<jsg::JsObject>());\n      auto checkedStack = obj.get(env.js, \"stack\"_kj);\n      KJ_ASSERT(checkedStack.strictEquals(stack));\n      KJ_ASSERT(obj.get(env.js, \"name\"_kj).strictEquals(env.js.str(\"CustomError\"_kj)));\n      KJ_ASSERT(obj.get(env.js, \"message\"_kj).strictEquals(env.js.str(\"abc\"_kj)));\n      KJ_ASSERT(obj.get(env.js, \"foo\"_kj).strictEquals(env.js.str(\"bar\"_kj)));\n    }\n    {\n      // Ignore detail means we reconstruct the error without the serialized detail\n      kj::Exception e = env.js.exceptionToKj(obj);\n      auto val = env.js.exceptionToJsValue(kj::mv(e), {.ignoreDetail = true});\n      auto obj = KJ_ASSERT_NONNULL(val.getHandle(env.js).tryCast<jsg::JsObject>());\n      auto checkedStack = obj.get(env.js, \"stack\"_kj);\n      KJ_ASSERT(!checkedStack.strictEquals(stack));\n      KJ_ASSERT(obj.get(env.js, \"name\"_kj).strictEquals(env.js.str(\"Error\"_kj)));\n      KJ_ASSERT(obj.get(env.js, \"message\"_kj).strictEquals(env.js.str(\"CustomError: abc\"_kj)));\n      KJ_ASSERT(obj.get(env.js, \"foo\"_kj).strictEquals(env.js.undefined()));\n    }\n  });\n}\n\nKJ_TEST(\"Tunneled exceptions do not preserve stack by default\") {\n  auto t = TestFixture();\n\n  t.runInIoContext([](const workerd::TestFixture::Environment& env) {\n    auto obj = KJ_ASSERT_NONNULL(env.js.typeError(\"abc\"_kj).tryCast<jsg::JsObject>());\n    obj.set(env.js, \"name\"_kj, env.js.str(\"CustomError\"_kj));\n    obj.set(env.js, \"foo\"_kj, env.js.str(\"bar\"_kj));\n    obj.set(env.js, \"stack\"_kj, env.js.str(\"test stack\"_kj));\n    auto stack = obj.get(env.js, \"stack\"_kj);\n\n    KJ_ASSERT(!FeatureFlags::get(env.js).getEnhancedErrorSerialization());\n\n    {\n      kj::Exception ex = env.js.exceptionToKj(obj);\n      auto val = env.js.exceptionToJsValue(kj::mv(ex));\n      auto obj = KJ_ASSERT_NONNULL(val.getHandle(env.js).tryCast<jsg::JsObject>());\n      auto checkedStack = obj.get(env.js, \"stack\"_kj);\n      KJ_ASSERT(!checkedStack.strictEquals(stack));\n      KJ_ASSERT(obj.get(env.js, \"name\"_kj).strictEquals(env.js.str(\"Error\"_kj)));\n      KJ_ASSERT(obj.get(env.js, \"message\"_kj).strictEquals(env.js.str(\"CustomError: abc\"_kj)));\n      KJ_ASSERT(obj.get(env.js, \"foo\"_kj).strictEquals(env.js.undefined()));\n    }\n    {\n      kj::Exception ex = env.js.exceptionToKj(obj);\n      auto val = env.js.exceptionToJsValue(kj::mv(ex), {.ignoreDetail = true});\n      auto obj = KJ_ASSERT_NONNULL(val.getHandle(env.js).tryCast<jsg::JsObject>());\n      auto checkedStack = obj.get(env.js, \"stack\"_kj);\n      KJ_ASSERT(!checkedStack.strictEquals(stack));\n      KJ_ASSERT(obj.get(env.js, \"name\"_kj).strictEquals(env.js.str(\"Error\"_kj)));\n      KJ_ASSERT(obj.get(env.js, \"message\"_kj).strictEquals(env.js.str(\"CustomError: abc\"_kj)));\n      KJ_ASSERT(obj.get(env.js, \"foo\"_kj).strictEquals(env.js.undefined()));\n    }\n    {\n      kj::Exception ex = env.js.exceptionToKj(obj);\n      auto val = env.js.exceptionToJsValue(kj::mv(ex), {.trusted = true});\n      auto obj = KJ_ASSERT_NONNULL(val.getHandle(env.js).tryCast<jsg::JsObject>());\n      auto checkedStack = obj.get(env.js, \"stack\"_kj);\n      KJ_ASSERT(checkedStack.strictEquals(stack));\n      KJ_ASSERT(obj.get(env.js, \"name\"_kj).strictEquals(env.js.str(\"Error\"_kj)));\n      KJ_ASSERT(obj.get(env.js, \"message\"_kj).strictEquals(env.js.str(\"abc\"_kj)));\n      KJ_ASSERT(obj.get(env.js, \"foo\"_kj).strictEquals(env.js.undefined()));\n    }\n  });\n}\n\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/sockets.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"sockets.h\"\n\n#include \"global-scope.h\"\n#include \"streams/standard.h\"\n#include \"system-streams.h\"\n\n#include <workerd/io/worker-interface.h>\n#include <workerd/jsg/exception.h>\n#include <workerd/jsg/url.h>\n\nnamespace workerd::api {\n\nnamespace {\n\n// This function performs some basic length and characters checks, it does not guarantee that\n// the specified host is a valid domain. It should only be used to reject malicious\n// hosts.\nbool isValidHost(kj::StringPtr host) {\n  if (host.size() > 255 || host.size() == 0) {\n    // RFC1035 states that maximum domain name length is 255 octets.\n    //\n    // IP addresses are always shorter, so we take the max domain length instead.\n    return false;\n  }\n\n  for (auto i: kj::indices(host)) {\n    switch (host[i]) {\n      case '-':\n      case '.':\n      case '_':\n      case '[':\n      case ']':\n      case ':':  // For IPv6.\n        break;\n      default:\n        if ((host[i] >= 'a' && host[i] <= 'z') || (host[i] >= 'A' && host[i] <= 'Z') ||\n            (host[i] >= '0' && host[i] <= '9')) {\n          break;\n        }\n        return false;\n    }\n  }\n  return true;\n}\n\nSecureTransportKind parseSecureTransport(SocketOptions& opts) {\n  auto value = KJ_UNWRAP_OR_RETURN(opts.secureTransport, SecureTransportKind::OFF).begin();\n  if (value == \"off\"_kj) {\n    return SecureTransportKind::OFF;\n  } else if (value == \"starttls\"_kj) {\n    return SecureTransportKind::STARTTLS;\n  } else if (value == \"on\"_kj) {\n    return SecureTransportKind::ON;\n  } else {\n    JSG_FAIL_REQUIRE(\n        TypeError, kj::str(\"Unsupported value in secureTransport socket option: \", value));\n  }\n}\n\nbool getAllowHalfOpen(jsg::Optional<SocketOptions>& opts) {\n  KJ_IF_SOME(o, opts) {\n    return o.allowHalfOpen;\n  }\n\n  // The allowHalfOpen flag is false by default.\n  return false;\n}\n\nkj::Maybe<uint64_t> getWritableHighWaterMark(jsg::Optional<SocketOptions>& opts) {\n  KJ_IF_SOME(o, opts) {\n    return o.highWaterMark;\n  }\n  return kj::none;\n}\n\n}  // namespace\n\n// Forward declarations\nclass StreamWorkerInterface;\n\njsg::Ref<Socket> setupSocket(jsg::Lock& js,\n    kj::Own<kj::AsyncIoStream> connection,\n    kj::String remoteAddress,\n    jsg::Optional<SocketOptions> options,\n    kj::Own<kj::TlsStarterCallback> tlsStarter,\n    SecureTransportKind secureTransport,\n    kj::String domain,\n    bool isDefaultFetchPort,\n    kj::Maybe<jsg::PromiseResolverPair<SocketInfo>> maybeOpenedPrPair) {\n  auto& ioContext = IoContext::current();\n\n  // Disconnection handling is annoyingly complicated:\n  //\n  // We can't just context.awaitIo(connection->whenWriteDisconnected()) directly, because the\n  // Socket could be GC'ed before `whenWriteDisconnected()` completes, causing the underlying\n  // `connection` to be destroyed. By KJ rules, we are required to cancel the promise returned by\n  // `whenWriteDisconnected()` before destroying `connection`. But there's no way to cancel a\n  // promise passed to `context.awaitIo()`. We have to hold the promise directly in `Socket`, so\n  // that we can cancel it on destruction. But we *do* want to create a JS promise that resolves\n  // on disconnect, which is what awaitIo() would give us.\n  //\n  // So, we have to chain through a promise/fulfiller pair. The `Socket` holds\n  // `watchForDisconnectTask`, which is a `kj::Promise<void>` representing a task that waits for\n  // `whenWriteDisconnected()` and then fulfills the fulfiller end of `disconnectedPaf` with\n  // `false`. If the task is canceled, we instead fulfill `disconnectedPaf` with `true`.\n  //\n  // We then use `context.awaitIo()` to await the promise end of `disconnectedPaf`, and this gives\n  // us our `closed` promise. Well, almost...\n  //\n  // There's another wrinkle: There are some circumstances where we want to resolve the `closed`\n  // promise directly from an API call. We'd rather this did not have to drop out of the isolate\n  // and enter it a gain. So, our `awaitIo()` actually awaits a task that listens for the\n  // disconnected promise and then resolves some other JS resolver, `closedResolver`.\n  auto disconnectedPaf = kj::newPromiseAndFulfiller<bool>();\n  auto& disconnectedFulfiller = *disconnectedPaf.fulfiller;\n  auto deferredCancelDisconnected =\n      kj::defer([fulfiller = kj::mv(disconnectedPaf.fulfiller)]() mutable {\n    // In case the `whenWriteDisconected()` listener task is canceled without fulfilling the\n    // fulfiller, we want to silently fulfill it. This will happen when the Socket is GC'ed.\n    fulfiller->fulfill(true);\n  });\n\n  static auto constexpr handleDisconnected =\n      [](kj::AsyncIoStream& connection,\n          kj::PromiseFulfiller<bool>& fulfiller) -> kj::Promise<void> {\n    try {\n      co_await connection.whenWriteDisconnected();\n      fulfiller.fulfill(false);\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      fulfiller.reject(kj::mv(exception));\n    }\n  };\n\n  auto watchForDisconnectTask = handleDisconnected(*connection, disconnectedFulfiller)\n                                    .attach(kj::mv(deferredCancelDisconnected));\n\n  auto closedPrPair = js.newPromiseAndResolver<void>();\n  closedPrPair.promise.markAsHandled(js);\n\n  ioContext.awaitIo(js, kj::mv(disconnectedPaf.promise))\n      .then(\n          js, [resolver = closedPrPair.resolver.addRef(js)](jsg::Lock& js, bool canceled) mutable {\n    // We want to silently ignore the canceled case, without ever resolving anything. Note that\n    // if the application actually fetches the `closed` promise, then the JSG glue will prevent\n    // the socket from being GC'ed until that promise resolves, so it won't be canceled.\n    if (!canceled) {\n      resolver.resolve(js);\n    }\n  }, [resolver = closedPrPair.resolver.addRef(js)](jsg::Lock& js, jsg::Value exception) mutable {\n    resolver.reject(js, exception.getHandle(js));\n  });\n\n  auto refcountedConnection = kj::refcountedWrapper(kj::mv(connection));\n  // Initialize the readable/writable streams with the readable/writable sides of an AsyncIoStream.\n  auto sysStreams = newSystemMultiStream(*refcountedConnection, ioContext);\n  auto readable = js.alloc<ReadableStream>(ioContext, kj::mv(sysStreams.readable));\n  auto allowHalfOpen = getAllowHalfOpen(options);\n  kj::Maybe<jsg::Promise<void>> eofPromise;\n  if (!allowHalfOpen) {\n    eofPromise = readable->onEof(js);\n  }\n  auto openedPrPair = kj::mv(maybeOpenedPrPair).orDefault(js.newPromiseAndResolver<SocketInfo>());\n  openedPrPair.promise.markAsHandled(js);\n  auto writable = js.alloc<WritableStream>(ioContext, kj::mv(sysStreams.writable),\n      ioContext.getMetrics().tryCreateWritableByteStreamObserver(),\n      getWritableHighWaterMark(options), openedPrPair.promise.whenResolved(js));\n\n  auto result = js.alloc<Socket>(js, ioContext, kj::mv(refcountedConnection), kj::mv(remoteAddress),\n      kj::mv(readable), kj::mv(writable), kj::mv(closedPrPair), kj::mv(watchForDisconnectTask),\n      kj::mv(options), kj::mv(tlsStarter), secureTransport, kj::mv(domain), isDefaultFetchPort,\n      kj::mv(openedPrPair));\n\n  KJ_IF_SOME(p, eofPromise) {\n    result->handleReadableEof(js, kj::mv(p));\n  }\n  return result;\n}\n\njsg::Ref<Socket> connectImplNoOutputLock(jsg::Lock& js,\n    kj::Maybe<jsg::Ref<Fetcher>> fetcher,\n    AnySocketAddress address,\n    jsg::Optional<SocketOptions> options) {\n\n  auto& ioContext = IoContext::current();\n  JSG_REQUIRE(!ioContext.isFiddle(), TypeError, \"Socket API not supported in web preview mode.\");\n\n  // Extract the domain/ip we are connecting to from the address.\n  kj::String domain;\n  bool isDefaultFetchPort = false;\n\n  KJ_SWITCH_ONEOF(address) {\n    KJ_CASE_ONEOF(str, kj::String) {\n      // We need just the hostname part of the address, i.e. we want to strip out the port.\n      // We do this using the standard URL parser since it will handle IPv6 for us as well.\n      auto input = kj::str(\"fake://\", str);\n      auto url = JSG_REQUIRE_NONNULL(\n          jsg::Url::tryParse(input.asPtr()), TypeError, \"Specified address could not be parsed.\");\n      auto host = url.getHostname();\n      auto port = url.getPort();\n      JSG_REQUIRE(host != \"\"_kj, TypeError, \"Specified address is missing hostname.\");\n      JSG_REQUIRE(port != \"\"_kj, TypeError, \"Specified address is missing port.\");\n      isDefaultFetchPort = port == \"443\"_kj || port == \"80\"_kj;\n      domain = kj::str(host);\n    }\n    KJ_CASE_ONEOF(record, SocketAddress) {\n      domain = kj::heapString(record.hostname);\n      isDefaultFetchPort = record.port == 443 || record.port == 80;\n    }\n  }\n\n  // Convert the address to a string that we can pass to kj.\n  auto addressStr = kj::str(\"\");\n  KJ_SWITCH_ONEOF(address) {\n    KJ_CASE_ONEOF(str, kj::String) {\n      addressStr = kj::mv(str);\n    }\n    KJ_CASE_ONEOF(record, SocketAddress) {\n      addressStr = kj::str(record.hostname, \":\", record.port);\n    }\n  }\n\n  JSG_REQUIRE(isValidHost(addressStr), TypeError,\n      \"Specified address is empty string, contains unsupported characters or is too long.\");\n\n  jsg::Ref<Fetcher> actualFetcher = nullptr;\n  KJ_IF_SOME(f, fetcher) {\n    actualFetcher = kj::mv(f);\n  } else {\n    // Support calling into arbitrary callbacks for any registered \"magic\" addresses for which\n    // custom connect() logic is needed. Note that these overrides should only apply to calls of the\n    // global connect() method, not for fetcher->connect(), hence why we check for them here.\n    KJ_IF_SOME(fn, ioContext.getCurrentLock().getGlobalScope().getConnectOverride(addressStr)) {\n      return fn(js);\n    }\n    actualFetcher =\n        js.alloc<Fetcher>(IoContext::NULL_CLIENT_CHANNEL, Fetcher::RequiresHostAndProtocol::YES);\n  }\n\n  CfProperty cf;\n  kj::Own<WorkerInterface> client =\n      actualFetcher->getClient(ioContext, cf.serialize(js), \"connect\"_kjc);\n\n  // Set up the connection.\n  auto headers = kj::heap<kj::HttpHeaders>(ioContext.getHeaderTable());\n  auto httpClient = asHttpClient(kj::mv(client));\n  kj::HttpConnectSettings httpConnectSettings = {.useTls = false};\n  SecureTransportKind secureTransport = SecureTransportKind::OFF;\n  KJ_IF_SOME(opts, options) {\n    secureTransport = parseSecureTransport(opts);\n    httpConnectSettings.useTls = secureTransport == SecureTransportKind::ON;\n  }\n  kj::Own<kj::TlsStarterCallback> tlsStarter = kj::heap<kj::TlsStarterCallback>();\n  httpConnectSettings.tlsStarter = tlsStarter;\n  auto request = httpClient->connect(addressStr, *headers, httpConnectSettings);\n  request.connection = request.connection.attach(kj::mv(httpClient));\n\n  auto result = setupSocket(js, kj::mv(request.connection), kj::mv(addressStr), kj::mv(options),\n      kj::mv(tlsStarter), secureTransport, kj::mv(domain), isDefaultFetchPort,\n      kj::none /* maybeOpenedPrPair */);\n  // `handleProxyStatus` needs an initialized refcount to use `JSG_THIS`, hence it cannot be\n  // called in Socket's constructor. Also it's only necessary when creating a Socket as a result of\n  // a `connect`.\n  result->handleProxyStatus(js, kj::mv(request.status));\n  return result;\n}\n\njsg::Ref<Socket> connectImpl(jsg::Lock& js,\n    kj::Maybe<jsg::Ref<Fetcher>> fetcher,\n    AnySocketAddress address,\n    jsg::Optional<SocketOptions> options) {\n  // TODO(soon): Doesn't this need to check for the presence of an output lock, and if it finds one\n  // then wait on it, before calling into connectImplNoOutputLock?\n  return connectImplNoOutputLock(js, kj::mv(fetcher), kj::mv(address), kj::mv(options));\n}\n\njsg::Promise<void> Socket::close(jsg::Lock& js) {\n  if (isClosing) {\n    return closedPromiseCopy.whenResolved(js);\n  }\n\n  isClosing = true;\n  writable->getController().setPendingClosure();\n  readable->getController().setPendingClosure();\n\n  // Wait until the socket connects (successfully or otherwise)\n  return openedPromiseCopy.whenResolved(js)\n      .then(js,\n          [this](jsg::Lock& js) {\n    if (!writable->getController().isClosedOrClosing()) {\n      return writable->getController().flush(js);\n    } else {\n      return js.resolvedPromise();\n    }\n  })\n      .then(js,\n          [this](jsg::Lock& js) {\n    // Forcibly abort the readable/writable streams.\n    auto cancelPromise = readable->getController().cancel(js, kj::none);\n    auto abortPromise = writable->getController().abort(js, kj::none);\n\n    // The below is effectively `Promise.all(cancelPromise, abortPromise)`\n    return cancelPromise.then(js, [abortPromise = kj::mv(abortPromise)](jsg::Lock& js) mutable {\n      return kj::mv(abortPromise);\n    });\n  })\n      .then(js, [this](jsg::Lock& js) {\n    // Destroy the connection stream to close the connection.\n    { auto _ = kj::mv(connectionData); }\n    connectionData = kj::none;\n\n    resolveFulfiller(js, kj::none);\n    return js.resolvedPromise();\n  }).catch_(js, [this](jsg::Lock& js, jsg::Value err) { errorHandler(js, kj::mv(err)); });\n}\n\njsg::Ref<Socket> Socket::startTls(jsg::Lock& js, jsg::Optional<TlsOptions> tlsOptions) {\n  JSG_REQUIRE(\n      secureTransport != SecureTransportKind::ON, TypeError, \"Cannot startTls on a TLS socket.\");\n  JSG_REQUIRE(connectionData != kj::none, TypeError,\n      \"The connection was closed before startTls could be started.\");\n  JSG_REQUIRE(domain != nullptr, TypeError, \"startTls can only be called once.\");\n  auto invalidOptKindMsg =\n      \"The `secureTransport` socket option must be set to 'starttls' for startTls to be used.\";\n  JSG_REQUIRE(secureTransport == SecureTransportKind::STARTTLS, TypeError, invalidOptKindMsg);\n\n  // The current socket's writable buffers need to be flushed. The socket's WritableStream is backed\n  // by an AsyncIoStream which doesn't implement any buffering, so we don't need to worry about\n  // flushing. But the JS WritableStream holds a queue so some data may still be buffered. This\n  // means we need to flush the WritableStream.\n  //\n  // Detach the AsyncIoStream from the Writable/Readable streams and make them unusable.\n  auto& context = IoContext::current();\n  auto openedPrPair = js.newPromiseAndResolver<SocketInfo>();\n  auto secureStreamPromise = context.awaitJs(js,\n      writable->flush(js).then(js,\n          // The openedResolver is a jsg::Promise::Resolver. It should be gc visited here in\n          // case the opened promise it resolves captures a circular references to itself in\n          // JavaScript (which is most likely). This prevents a possible memory leak.\n          // We also capture a strong reference to the original Socket instance that is being\n          // upgraded in order to prevent it from being GC'd while we are waiting for the\n          // flush to complete. While it is unlikely to be GC'd while we are waiting because\n          // the user code *likely* is holding a active reference to it at this point, we\n          // don't want to take any chances. This prevents a possible UAF.\n          JSG_VISITABLE_LAMBDA(\n              (self = JSG_THIS, domain = kj::heapString(domain), tlsOptions = kj::mv(tlsOptions),\n                  openedResolver = openedPrPair.resolver.addRef(js),\n                  remoteAddress = kj::str(remoteAddress)),\n              (self, openedResolver), (jsg::Lock & js) mutable {\n                auto& context = IoContext::current();\n\n                self->writable->detach(js);\n                self->readable->detach(js, true);\n\n                // We should set this before closedResolver.resolve() in order to give the user\n                // the option to check if the closed promise is resolved due to upgrade or not.\n                self->upgraded = true;\n                self->closedResolver.resolve(js);\n\n                auto acceptedHostname = domain.asPtr();\n                KJ_IF_SOME(s, tlsOptions) {\n                KJ_IF_SOME(expectedHost, s.expectedServerHostname) {\n                acceptedHostname = expectedHost;\n                } else {\n                }  // Needed to avoid compiler error/warning\n                } else {\n                }  // Needed to avoid compiler error/warning\n\n                // All non-secure sockets should have `connectionData` with a `tlsStarter`.\n                // Though since it's inside an IoOwn, if the request's IoContext has ended\n                // then `connectionData` will be null. This can happen if the flush operation is taking\n                // a particularly long time (EW-8538), so we throw a JSG error if that's the case.\n                auto& connData = JSG_REQUIRE_NONNULL(self->connectionData, TypeError,\n                    \"The connection was closed before startTls completed.\");\n\n                auto& tlsStarter = connData->tlsStarter;\n\n                // Fork the starter promise because we need to create two separate things waiting\n                // on it below. The first is resolving the openedResolver with a JS promise that\n                // wraps one branch, the secnod is the kj::Promise that we use to resolve the\n                // secureStream for the promised stream. This keeps us from having to bounce in and\n                // out of the JS isolate lock.\n                auto forkedPromise = KJ_ASSERT_NONNULL(*tlsStarter)(acceptedHostname).fork();\n\n                openedResolver.resolve(js,\n                    context.awaitIo(js, forkedPromise.addBranch(),\n                        [remoteAddress = kj::mv(remoteAddress)](\n                            jsg::Lock& js) mutable -> SocketInfo {\n                  return SocketInfo{\n                    .remoteAddress = kj::mv(remoteAddress),\n                    .localAddress = kj::none,\n                  };\n                }));\n\n                // Move the stream out of the plain text socket, to ensure the stream is properly\n                // destroyed when the socket is closed.\n                kj::Own<kj::AsyncIoStream> stream = connData->connectionStream->addWrappedRef();\n                self->connectionData = kj::none;\n\n                auto secureStream = forkedPromise.addBranch().then(\n                    [stream = kj::mv(stream)]() mutable { return kj::mv(stream); });\n\n                return kj::newPromisedStream(kj::mv(secureStream));\n              })));\n\n  // The existing tlsStarter gets consumed and we won't need it again. Pass in an empty tlsStarter\n  // to `setupSocket`.\n  auto newTlsStarter = kj::heap<kj::TlsStarterCallback>();\n  return setupSocket(js, kj::newPromisedStream(kj::mv(secureStreamPromise)), kj::str(remoteAddress),\n      kj::mv(options), kj::mv(newTlsStarter), SecureTransportKind::ON, kj::mv(domain),\n      isDefaultFetchPort, kj::mv(openedPrPair));\n}\n\nvoid Socket::handleProxyStatus(\n    jsg::Lock& js, kj::Promise<kj::HttpClient::ConnectRequest::Status> status) {\n  auto& context = IoContext::current();\n  auto errorHandler = [](kj::Exception&& e) {\n    // Let's not log errors when we have a disconnected exception.\n    // If we don't filter this out, whenever connect() fails, we'll\n    // have noisy errors even though the user catches the error on JS side.\n    if (e.getType() != kj::Exception::Type::DISCONNECTED) {\n      LOG_ERROR_PERIODICALLY(\"Socket proxy disconnected abruptly\", e);\n    }\n    return kj::HttpClient::ConnectRequest::Status(500, nullptr, kj::Own<kj::HttpHeaders>());\n  };\n  auto func = [this, self = JSG_THIS](\n                  jsg::Lock& js, kj::HttpClient::ConnectRequest::Status&& status) -> void {\n    if (status.statusCode < 200 || status.statusCode >= 300) {\n      // If the status indicates an unsuccessful connection we need to reject the `closeFulfiller`\n      // with an exception. This will reject the socket's `closed` promise.\n      auto msg = kj::str(\"proxy request failed, cannot connect to the specified address\");\n      if (isDefaultFetchPort) {\n        msg = kj::str(msg, \". It looks like you might be trying to connect to a HTTP-based service\",\n            \" — consider using fetch instead\");\n      } else if (remoteAddress.contains(\".hyperdrive.local\"_kj)) {\n        // No attempts to connect to Hyperdrive should end up here, since they go through the other\n        // version of handleProxyStatus. If they end up here somehow, log about it to get some\n        // context that can aid in debugging.\n        LOG_WARNING_PERIODICALLY(\n            \"attempt to connect to Hyperdrive failed to trigger connectOverride\", remoteAddress,\n            status.statusCode, status.statusText);\n      }\n      handleProxyError(js, JSG_KJ_EXCEPTION(FAILED, Error, msg));\n    } else {\n      // In our implementation we do not expose the local address at all simply\n      // because there's no useful value we can provide.\n      openedResolver.resolve(js,\n          SocketInfo{\n            .remoteAddress = kj::str(remoteAddress),\n            .localAddress = kj::none,\n          });\n    }\n  };\n  auto result = context.awaitIo(js, status.catch_(kj::mv(errorHandler)), kj::mv(func));\n  result.markAsHandled(js);\n}\n\nvoid Socket::handleProxyStatus(jsg::Lock& js, kj::Promise<kj::Maybe<kj::Exception>> connectResult) {\n  // It's kind of weird to take a promise that resolves to a Maybe<Exception> but we can't just use\n  // a Promise<void> and put our logic in the error handler because awaitIo doesn't provide the\n  // jsg::Lock for void promises or to errorFunc implementations, only non-void success callbacks,\n  // but we need the lock in our callback here.\n  // TODO(cleanup): Extend awaitIo to provide the jsg::Lock in more cases.\n  auto& context = IoContext::current();\n  auto errorHandler = [](kj::Exception&& e) -> kj::Maybe<kj::Exception> {\n    LOG_ERROR_PERIODICALLY(\"Socket proxy disconnected abruptly\", e);\n    return KJ_EXCEPTION(FAILED, \"connectResult raised an error\");\n  };\n  auto func = [this, self = JSG_THIS](jsg::Lock& js, kj::Maybe<kj::Exception> result) -> void {\n    if (result != kj::none) {\n      handleProxyError(js, JSG_KJ_EXCEPTION(FAILED, Error, \"connection attempt failed\"));\n    } else {\n      // In our implementation we do not expose the local address at all simply\n      // because there's no useful value we can provide.\n      openedResolver.resolve(js,\n          SocketInfo{\n            .remoteAddress = kj::str(remoteAddress),\n            .localAddress = kj::none,\n          });\n    }\n  };\n  auto result = context.awaitIo(js, connectResult.catch_(kj::mv(errorHandler)), kj::mv(func));\n  result.markAsHandled(js);\n}\n\nvoid Socket::handleProxyError(jsg::Lock& js, kj::Exception e) {\n  resolveFulfiller(js, kj::cp(e));\n  openedResolver.reject(js, kj::cp(e));\n  readable->getController().cancel(js, kj::none).markAsHandled(js);\n  writable->getController().abort(js, js.error(e.getDescription())).markAsHandled(js);\n}\n\nvoid Socket::handleReadableEof(jsg::Lock& js, jsg::Promise<void> onEof) {\n  KJ_ASSERT(!getAllowHalfOpen(options));\n  // Listen for EOF on the ReadableStream.\n  onEof\n      .then(\n          js,\n          JSG_VISITABLE_LAMBDA(\n              (ref = JSG_THIS), (ref), (jsg::Lock& js) { return ref->maybeCloseWriteSide(js); }))\n      .markAsHandled(js);\n}\n\njsg::Promise<void> Socket::maybeCloseWriteSide(jsg::Lock& js) {\n  // When `allowHalfOpen` is set to true then we do not automatically close the write side on EOF.\n  // This code shouldn't even run since we don't set up a callback which calls it unless\n  // `allowHalfOpen` is false.\n  KJ_ASSERT(!getAllowHalfOpen(options));\n\n  // Do not call `close` on a controller that has already been closed or is in the process\n  // of closing.\n  if (writable->getController().isClosedOrClosing()) {\n    return js.resolvedPromise();\n  }\n\n  // We want to close the socket, but only after its WritableStream has been flushed. We do this\n  // below by calling `close` on the WritableStream which ensures that any data pending on it\n  // is flushed. Then once the `close` either completes or fails we can be sure that any data has\n  // been flushed.\n  return writable->getController()\n      .close(js)\n      .catch_(js,\n          JSG_VISITABLE_LAMBDA((ref = JSG_THIS), (ref),\n              (jsg::Lock& js, jsg::Value&& exc) {\n                ref->closedResolver.reject(js, exc.getHandle(js));\n              }))\n      .then(js, JSG_VISITABLE_LAMBDA((ref = JSG_THIS), (ref), (jsg::Lock& js) {\n        ref->closedResolver.resolve(js);\n      }));\n}\n\njsg::Ref<Socket> SocketsModule::connect(\n    jsg::Lock& js, AnySocketAddress address, jsg::Optional<SocketOptions> options) {\n  return connectImpl(js, kj::none, kj::mv(address), kj::mv(options));\n}\n\nkj::Own<kj::AsyncIoStream> Socket::takeConnectionStream(jsg::Lock& js) {\n  // Set this so that if `close` is called after this, that no closure steps are taken and instead\n  // the `close` is a no-op.\n  isClosing = true;\n\n  // We do not care if the socket was disturbed, we require the user to ensure the socket is not\n  // being used.\n  writable->detach(js);\n  readable->detach(js, true);\n\n  // Move the stream out of the socket, to ensure the stream is properly destroyed when the\n  // caller is done with it.\n  auto& dataConn = JSG_REQUIRE_NONNULL(\n      connectionData, TypeError, \"The socket connection is closed or was already taken.\");\n  auto wrapper = dataConn->connectionStream->addWrappedRef();\n  connectionData = kj::none;\n  closedResolver.resolve(js);\n  return wrapper;\n}\n\n// Implementation of the custom factory for creating WorkerInterface instances from a socket\nclass StreamOutgoingFactory final: public Fetcher::OutgoingFactory, public kj::Refcounted {\n public:\n  StreamOutgoingFactory(kj::Own<kj::AsyncIoStream> stream,\n      kj::EntropySource& entropySource,\n      const kj::HttpHeaderTable& headerTable)\n      : stream(kj::mv(stream)),\n        httpClient(\n            kj::newHttpClient(headerTable, *this->stream, {.entropySource = entropySource})) {}\n\n  kj::Own<WorkerInterface> newSingleUseClient(kj::Maybe<kj::String> cfStr) override;\n\n private:\n  kj::Own<kj::AsyncIoStream> stream;\n  kj::Own<kj::HttpClient> httpClient;\n  friend class StreamWorkerInterface;\n};\n\n// Definition of the StreamWorkerInterface class\nclass StreamWorkerInterface final: public WorkerInterface {\n public:\n  StreamWorkerInterface(kj::Own<StreamOutgoingFactory> factory): factory(kj::mv(factory)) {}\n\n  kj::Promise<void> request(kj::HttpMethod method,\n      kj::StringPtr url,\n      const kj::HttpHeaders& headers,\n      kj::AsyncInputStream& requestBody,\n      kj::HttpService::Response& response) override {\n    // Parse the URL to extract the path\n    auto parsedUrl = KJ_REQUIRE_NONNULL(kj::Url::tryParse(url, kj::Url::Context::HTTP_PROXY_REQUEST,\n                                            {.percentDecode = false, .allowEmpty = true}),\n        \"invalid url\", url);\n\n    // We need to convert the URL from proxy format (full URL in request line) to host format\n    // (path in request line, hostname in Host header).\n    auto newHeaders = headers.cloneShallow();\n    newHeaders.setPtr(kj::HttpHeaderId::HOST, parsedUrl.host);\n    auto noHostUrl = parsedUrl.toString(kj::Url::Context::HTTP_REQUEST);\n\n    // Create a new HTTP service from the client\n    auto service = kj::newHttpService(*factory->httpClient);\n\n    // Forward the request to the service\n    co_await service->request(method, noHostUrl, newHeaders, requestBody, response);\n  }\n\n  kj::Promise<void> connect(kj::StringPtr host,\n      const kj::HttpHeaders& headers,\n      kj::AsyncIoStream& connection,\n      ConnectResponse& response,\n      kj::HttpConnectSettings settings) override {\n    JSG_FAIL_REQUIRE(TypeError,\n        \"connect is not something that can be done on a fetcher converted from a socket\");\n  }\n\n  kj::Promise<void> prewarm(kj::StringPtr url) override {\n    KJ_UNIMPLEMENTED(\"prewarm() not supported on StreamWorkerInterface\");\n  }\n\n  kj::Promise<ScheduledResult> runScheduled(kj::Date scheduledTime, kj::StringPtr cron) override {\n    KJ_UNIMPLEMENTED(\"runScheduled() not supported on StreamWorkerInterface\");\n  }\n\n  kj::Promise<AlarmResult> runAlarm(kj::Date scheduledTime, uint32_t retryCount) override {\n    KJ_UNIMPLEMENTED(\"runAlarm() not supported on StreamWorkerInterface\");\n  }\n\n  kj::Promise<CustomEvent::Result> customEvent(kj::Own<CustomEvent> event) override {\n    return event->notSupported();\n  }\n\n private:\n  kj::Own<StreamOutgoingFactory> factory;\n};\n\nkj::Own<WorkerInterface> StreamOutgoingFactory::newSingleUseClient(kj::Maybe<kj::String> cfStr) {\n  JSG_ASSERT(stream.get() != nullptr, Error,\n      \"Fetcher created from internalNewHttpClient can only be used once\");\n  // Create a WorkerInterface that wraps the stream\n  return kj::heap<StreamWorkerInterface>(kj::addRef(*this));\n}\n\njsg::Promise<jsg::Ref<Fetcher>> SocketsModule::internalNewHttpClient(\n    jsg::Lock& js, jsg::Ref<Socket> socket) {\n\n  // TODO(soon) check for nothing to read, this will require things using a promise so this function\n  // must remain returning a jsg::Promise waiting on a TODO for releaseLock\n\n  // Flush the writable stream before taking the connection stream to ensure all data is written\n  // before the stream is detatched\n  return socket->getWritable()->flush(js).then(\n      js, JSG_VISITABLE_LAMBDA((socket = kj::mv(socket)), (socket), (jsg::Lock & js) mutable {\n        auto& ioctx = IoContext::current();\n\n        // Create our custom factory that will create client instances from this socket\n        kj::Own<Fetcher::OutgoingFactory> outgoingFactory = kj::refcounted<StreamOutgoingFactory>(\n            socket->takeConnectionStream(js), ioctx.getEntropySource(), ioctx.getHeaderTable());\n\n        // Create a Fetcher that uses our custom factory\n        auto fetcher = js.alloc<Fetcher>(\n            ioctx.addObject(kj::mv(outgoingFactory)), Fetcher::RequiresHostAndProtocol::YES);\n\n        return kj::mv(fetcher);\n      }));\n}\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/sockets.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/api/streams/readable.h>\n#include <workerd/api/streams/writable.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/modules-new.h>\n#include <workerd/jsg/url.h>\n\nnamespace workerd::api {\n\nclass Fetcher;\n\nenum class SecureTransportKind {\n  // plain-text\n  OFF,\n  // plain-text at first, with `startTls` available to upgrade at a later time\n  STARTTLS,\n  // TLS enabled immediately\n  ON,\n};\n\nstruct SocketAddress {\n  kj::String hostname;\n  uint16_t port;\n  JSG_STRUCT(hostname, port);\n};\n\nstruct SocketInfo {\n  jsg::Optional<kj::String> remoteAddress;\n\n  // The local address is specified by the spec but we don't implement it.\n  // It will always remain empty.\n  jsg::Optional<kj::String> localAddress;\n  JSG_STRUCT(remoteAddress, localAddress);\n};\n\nusing AnySocketAddress = kj::OneOf<SocketAddress, kj::String>;\n\nstruct SocketOptions {\n  jsg::Optional<kj::String> secureTransport;\n  bool allowHalfOpen = false;\n  jsg::Optional<uint64_t> highWaterMark;\n  JSG_STRUCT(secureTransport, allowHalfOpen, highWaterMark);\n  JSG_MEMORY_INFO(SocketOptions) {\n    tracker.trackField(\"secureTransport\", secureTransport);\n  }\n};\n\nstruct TlsOptions {\n  jsg::Optional<kj::String> expectedServerHostname;\n  JSG_STRUCT(expectedServerHostname);\n};\n\nclass Socket: public jsg::Object {\n public:\n  Socket(jsg::Lock& js,\n      IoContext& context,\n      kj::Own<kj::RefcountedWrapper<kj::Own<kj::AsyncIoStream>>> connectionStream,\n      kj::String remoteAddress,\n      jsg::Ref<ReadableStream> readableParam,\n      jsg::Ref<WritableStream> writable,\n      jsg::PromiseResolverPair<void> closedPrPair,\n      kj::Promise<void> watchForDisconnectTask,\n      jsg::Optional<SocketOptions> options,\n      kj::Own<kj::TlsStarterCallback> tlsStarter,\n      SecureTransportKind secureTransport,\n      kj::String domain,\n      bool isDefaultFetchPort,\n      jsg::PromiseResolverPair<SocketInfo> openedPrPair)\n      : connectionData(context.addObject(kj::heap<ConnectionData>(\n            kj::mv(tlsStarter), kj::mv(connectionStream), kj::mv(watchForDisconnectTask)))),\n        readable(kj::mv(readableParam)),\n        writable(kj::mv(writable)),\n        closedResolver(kj::mv(closedPrPair.resolver)),\n        closedPromiseCopy(closedPrPair.promise.whenResolved(js)),\n        closedPromise(kj::mv(closedPrPair.promise)),\n        options(kj::mv(options)),\n        remoteAddress(kj::mv(remoteAddress)),\n        secureTransport(secureTransport),\n        domain(kj::mv(domain)),\n        isDefaultFetchPort(isDefaultFetchPort),\n        openedResolver(kj::mv(openedPrPair.resolver)),\n        openedPromiseCopy(openedPrPair.promise.whenResolved(js)),\n        openedPromise(kj::mv(openedPrPair.promise)) {};\n\n  jsg::Ref<ReadableStream> getReadable() {\n    return readable.addRef();\n  }\n  jsg::Ref<WritableStream> getWritable() {\n    return writable.addRef();\n  }\n  jsg::MemoizedIdentity<jsg::Promise<void>>& getClosed() {\n    return closedPromise;\n  }\n  jsg::MemoizedIdentity<jsg::Promise<SocketInfo>>& getOpened() {\n    return openedPromise;\n  }\n\n  bool getUpgraded() const {\n    return upgraded;\n  }\n\n  kj::StringPtr getSecureTransport() const {\n    switch (secureTransport) {\n      case SecureTransportKind::OFF:\n        return \"off\"_kj;\n      case SecureTransportKind::STARTTLS:\n        return \"starttls\"_kj;\n      case SecureTransportKind::ON:\n        return \"on\"_kj;\n    }\n  }\n\n  // Takes ownership of the underlying connection stream, detaching the readable and writable streams.\n  // This is a destructive operation that renders the Socket unusable for further I/O operations.\n  kj::Own<kj::AsyncIoStream> takeConnectionStream(jsg::Lock& js);\n\n  // Closes the socket connection.\n  //\n  // The closure is only performed after the socket connection is properly\n  // established through any configured proxy. This method also flushes the writable stream prior to\n  // closing.\n  jsg::Promise<void> close(jsg::Lock& js);\n\n  // Flushes write buffers then performs a TLS handshake on the current Socket connection.\n  // The current `Socket` instance is closed and its readable/writable instances are also closed.\n  // All new operations should be performed on the new `Socket` instance.\n  jsg::Ref<Socket> startTls(jsg::Lock& js, jsg::Optional<TlsOptions> options);\n\n  // Sets up relevant callbacks to handle the case when the proxy rejects our connection.\n  // The first variant is useful for connections established using HTTP connect. The latter is for\n  // connections established any other way, where the lack of an exception indicates we connected\n  // successfully.\n  void handleProxyStatus(jsg::Lock& js, kj::Promise<kj::HttpClient::ConnectRequest::Status> status);\n\n  // Sets up relevant callbacks to handle the case when the proxy rejects our connection.\n  // The first variant is useful for connections established using HTTP connect. The latter is for\n  // connections established any other way, where the lack of an exception indicates we connected\n  // successfully.\n  void handleProxyStatus(jsg::Lock& js, kj::Promise<kj::Maybe<kj::Exception>> status);\n\n  void handleReadableEof(jsg::Lock& js, jsg::Promise<void> onEof);\n  // Sets up relevant callbacks to handle the case when the readable stream reaches EOF.\n\n  JSG_RESOURCE_TYPE(Socket) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(readable, getReadable);\n    JSG_READONLY_PROTOTYPE_PROPERTY(writable, getWritable);\n    JSG_READONLY_PROTOTYPE_PROPERTY(closed, getClosed);\n    JSG_READONLY_PROTOTYPE_PROPERTY(opened, getOpened);\n    JSG_READONLY_PROTOTYPE_PROPERTY(upgraded, getUpgraded);\n    JSG_READONLY_PROTOTYPE_PROPERTY(secureTransport, getSecureTransport);\n    JSG_METHOD(close);\n    JSG_METHOD(startTls);\n\n    JSG_TS_OVERRIDE({\n      get secureTransport(): 'on' | 'off' | 'starttls';\n    });\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackFieldWithSize(\"connectionData\", sizeof(IoOwn<ConnectionData>));\n    tracker.trackField(\"readable\", readable);\n    tracker.trackField(\"writable\", writable);\n    tracker.trackField(\"closedResolver\", closedResolver);\n    tracker.trackField(\"closedPromiseCopy\", closedPromiseCopy);\n    tracker.trackField(\"closedPromise\", closedPromise);\n    tracker.trackField(\"options\", options);\n    tracker.trackField(\"domain\", domain);\n    tracker.trackField(\"openedResolver\", openedResolver);\n    tracker.trackField(\"openedPromiseCopy\", openedPromiseCopy);\n    tracker.trackField(\"openedPromise\", openedPromise);\n  }\n\n private:\n  struct ConnectionData {\n    kj::Own<kj::RefcountedWrapper<kj::Own<kj::AsyncIoStream>>> connectionStream;\n    kj::Maybe<kj::Promise<void>> watchForDisconnectTask;\n    // tlsStarter must be declared after connectionStream so that it is destroyed first,\n    // since it holds a reference that keeps the connection alive.\n    kj::Own<kj::TlsStarterCallback> tlsStarter;\n    ConnectionData(kj::Own<kj::TlsStarterCallback> tlsStarter,\n        kj::Own<kj::RefcountedWrapper<kj::Own<kj::AsyncIoStream>>> connStream,\n        kj::Promise<void> disconnectTask)\n        : connectionStream(kj::mv(connStream)),\n          watchForDisconnectTask(kj::mv(disconnectTask)),\n          tlsStarter(kj::mv(tlsStarter)) {}\n  };\n  kj::Maybe<IoOwn<ConnectionData>> connectionData;\n\n  jsg::Ref<ReadableStream> readable;\n  jsg::Ref<WritableStream> writable;\n  // This fulfiller is used to resolve the `closedPromise` below.\n  jsg::Promise<void>::Resolver closedResolver;\n  // Copy kept so that it can be returned from `close`.\n  jsg::Promise<void> closedPromiseCopy;\n  // Memoized copy that is returned by the `closed` attribute.\n  jsg::MemoizedIdentity<jsg::Promise<void>> closedPromise;\n  jsg::Optional<SocketOptions> options;\n  kj::String remoteAddress;\n  // Set to true when the socket is upgraded to a secure one.\n  bool upgraded = false;\n  SecureTransportKind secureTransport;\n  // The domain/ip this socket is connected to. Used for startTls.\n  kj::String domain;\n  // Whether the port this socket connected to is 80/443. Used for nicer errors.\n  bool isDefaultFetchPort;\n  // This fulfiller is used to resolve the `openedPromise` below.\n  jsg::Promise<SocketInfo>::Resolver openedResolver;\n  // Copy kept so that it can be used in `close`.\n  jsg::Promise<void> openedPromiseCopy;\n  jsg::MemoizedIdentity<jsg::Promise<SocketInfo>> openedPromise;\n  // Used to keep track of a pending `close` operation on the socket.\n  bool isClosing = false;\n\n  kj::Promise<kj::Own<kj::AsyncIoStream>> processConnection();\n  jsg::Promise<void> maybeCloseWriteSide(jsg::Lock& js);\n  jsg::Promise<void> closeImplOld(jsg::Lock& js);\n  jsg::Promise<void> closeImplNew(jsg::Lock& js);\n\n  // Helper method for handleProxyStatus implementations.\n  void handleProxyError(jsg::Lock& js, kj::Exception e);\n\n  void resolveFulfiller(jsg::Lock& js, kj::Maybe<kj::Exception> maybeErr) {\n    KJ_IF_SOME(err, maybeErr) {\n      closedResolver.reject(js, kj::cp(err));\n    } else {\n      closedResolver.resolve(js);\n    }\n  };\n\n  void errorHandler(jsg::Lock& js, jsg::Value err) {\n    auto jsException = err.getHandle(js);\n    resolveFulfiller(js, jsg::createTunneledException(js.v8Isolate, jsException));\n  };\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(readable, writable, closedResolver, closedPromiseCopy, closedPromise,\n        openedResolver, openedPromiseCopy, openedPromise);\n  }\n};\n\njsg::Ref<Socket> setupSocket(jsg::Lock& js,\n    kj::Own<kj::AsyncIoStream> connection,\n    kj::String remoteAddress,\n    jsg::Optional<SocketOptions> options,\n    kj::Own<kj::TlsStarterCallback> tlsStarter,\n    SecureTransportKind secureTransport,\n    kj::String domain,\n    bool isDefaultFetchPort,\n    kj::Maybe<jsg::PromiseResolverPair<SocketInfo>> maybeOpenedPrPair);\n\njsg::Ref<Socket> connectImplNoOutputLock(jsg::Lock& js,\n    kj::Maybe<jsg::Ref<Fetcher>> fetcher,\n    AnySocketAddress address,\n    jsg::Optional<SocketOptions> options);\n\njsg::Ref<Socket> connectImpl(jsg::Lock& js,\n    kj::Maybe<jsg::Ref<Fetcher>> fetcher,\n    AnySocketAddress address,\n    jsg::Optional<SocketOptions> options);\n\nclass SocketsModule final: public jsg::Object {\n public:\n  SocketsModule() = default;\n  SocketsModule(jsg::Lock&, const jsg::Url&) {}\n\n  jsg::Ref<Socket> connect(\n      jsg::Lock& js, AnySocketAddress address, jsg::Optional<SocketOptions> options);\n\n  // Creates a Fetcher from a Socket that can perform HTTP requests over the socket connection\n  jsg::Promise<jsg::Ref<Fetcher>> internalNewHttpClient(jsg::Lock& js, jsg::Ref<Socket> socket);\n\n  JSG_RESOURCE_TYPE(SocketsModule, CompatibilityFlags::Reader flags) {\n    JSG_METHOD(connect);\n\n    if (flags.getWorkerdExperimental()) {\n      JSG_METHOD(internalNewHttpClient);\n    }\n  }\n};\n\ntemplate <class Registry>\nvoid registerSocketsModule(Registry& registry, auto featureFlags) {\n  registry.template addBuiltinModule<SocketsModule>(\n      \"cloudflare-internal:sockets\", workerd::jsg::ModuleRegistry::Type::INTERNAL);\n}\n\ntemplate <typename TypeWrapper>\nkj::Own<jsg::modules::ModuleBundle> getInternalSocketModuleBundle(auto featureFlags) {\n  jsg::modules::ModuleBundle::BuiltinBuilder builder(\n      jsg::modules::ModuleBundle::BuiltinBuilder::Type::BUILTIN_ONLY);\n  static const auto kSpecifier = \"cloudflare-internal:sockets\"_url;\n  builder.addObject<SocketsModule, TypeWrapper>(kSpecifier);\n  return builder.finish();\n}\n\n#define EW_SOCKETS_ISOLATE_TYPES                                                                   \\\n  api::Socket, api::SocketOptions, api::SocketAddress, api::TlsOptions, api::SocketsModule,        \\\n      api::SocketInfo\n\n// The list of sockets.h types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/sql.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"sql.h\"\n\n#include \"actor-state.h\"\n\n#include <workerd/io/io-context.h>\n\nnamespace workerd::api {\n\n// Maximum total size of all cached statements (measured in size of the SQL code). If cached\n// statements exceed this, we remove the LRU statement(s).\n//\n// Hopefully most apps don't ever hit this, but it's important to have a limit in case of\n// queries containing dynamic content or excessively large one-off queries.\nstatic constexpr uint SQL_STATEMENT_CACHE_MAX_SIZE = 1024 * 1024;\n\nSqlStorage::SqlStorage(jsg::Ref<DurableObjectStorage> storage)\n    : storage(kj::mv(storage)),\n      statementCache(IoContext::current().addObject(kj::heap<StatementCache>())) {}\n\nSqlStorage::~SqlStorage() {}\n\njsg::Ref<SqlStorage::Cursor> SqlStorage::exec(\n    jsg::Lock& js, jsg::JsString querySql, jsg::Arguments<BindingValue> bindings) {\n  auto& context = IoContext::current();\n  TraceContext traceContext = context.makeUserTraceSpan(\"durable_object_storage_exec\"_kjc);\n  traceContext.setTag(\"db.system.name\"_kjc, \"cloudflare-durable-object-sql\"_kjc);\n  traceContext.setTag(\"db.operation.name\"_kjc, \"exec\"_kjc);\n  traceContext.setTag(\"db.query.text\"_kjc, kj::str(querySql));\n  traceContext.setTag(\n      \"cloudflare.durable_object.query.bindings\"_kjc, static_cast<int64_t>(bindings.size()));\n\n  // Internalize the string, so that the cache can be keyed by string identity rather than content.\n  // Any string we put into the cache is expected to live there for a while anyway, so even if it\n  // is a one-off, internalizing it (which moves it to the old generation) shouldn't hurt.\n  querySql = querySql.internalize(js);\n\n  auto& db = getDb(js);\n  auto& statementCache = *this->statementCache;\n\n  kj::Rc<CachedStatement>& slot = statementCache.map.findOrCreate(querySql, [&]() {\n    auto result = kj::rc<CachedStatement>(js, *this, db, querySql, js.toString(querySql));\n    statementCache.totalSize += result->statementSize;\n    return result;\n  });\n\n  // Move cached statement to end of LRU queue.\n  if (slot->lruLink.isLinked()) {\n    statementCache.lru.remove(*slot.get());\n  }\n  statementCache.lru.add(*slot.get());\n\n  // In order to get accurate statistics, we have to keep the spans around until the query is\n  // actually done, which for read queries that iterate over a cursor won't be until later.\n  kj::Maybe<kj::Function<void(Cursor&)>> doneCallback;\n  if (traceContext.isObserved()) {\n    doneCallback = [traceContext = context.addObject(kj::heap(kj::mv(traceContext)))](\n                       Cursor& cursor) mutable {\n      int64_t rowsRead = cursor.getRowsRead();\n      int64_t rowsWritten = cursor.getRowsWritten();\n      traceContext->setTag(\"cloudflare.durable_object.response.rows_read\"_kjc, rowsRead);\n      traceContext->setTag(\"cloudflare.durable_object.response.rows_written\"_kjc, rowsWritten);\n    };\n  }\n\n  if (slot->isShared()) {\n    // Oops, this CachedStatement is currently in-use (presumably by a Cursor).\n    //\n    // SQLite only allows one instance of a statement to run at a time, so we will have to compile\n    // the statement again as a one-off.\n    //\n    // In theory we could try to cache multiple copies of the statement, but as this is probably\n    // exceedingly rare, it is not worth the added code complexity.\n    SqliteDatabase::Regulator& regulator = *this;\n    return js.alloc<Cursor>(\n        js, kj::mv(doneCallback), db, regulator, js.toString(querySql), kj::mv(bindings));\n  }\n\n  auto result = js.alloc<Cursor>(js, kj::mv(doneCallback), slot.addRef(), kj::mv(bindings));\n\n  // If the statement cache grew too big, drop the least-recently-used entry.\n  while (statementCache.totalSize > SQL_STATEMENT_CACHE_MAX_SIZE) {\n    auto& toRemove = *statementCache.lru.begin();\n    auto oldQuery = jsg::JsString(toRemove.query.getHandle(js));\n    statementCache.totalSize -= toRemove.statementSize;\n    statementCache.lru.remove(toRemove);\n    KJ_ASSERT(statementCache.map.eraseMatch(oldQuery));\n  }\n\n  return result;\n}\n\nSqlStorage::IngestResult SqlStorage::ingest(jsg::Lock& js, kj::String querySql) {\n  auto& context = IoContext::current();\n  TraceContext traceContext = context.makeUserTraceSpan(\"durable_object_storage_ingest\"_kjc);\n  SqliteDatabase::Regulator& regulator = *this;\n  auto result = getDb(js).ingestSql(regulator, querySql);\n\n  traceContext.setTag(\n      \"cloudflare.durable_object.response.rows_read\"_kjc, static_cast<int64_t>(result.rowsRead));\n  traceContext.setTag(\"cloudflare.durable_object.response.rows_written\"_kjc,\n      static_cast<int64_t>(result.rowsWritten));\n  traceContext.setTag(\"cloudflare.durable_object.response.statement_count\"_kjc,\n      static_cast<int64_t>(result.statementCount));\n\n  return IngestResult(\n      kj::str(result.remainder), result.rowsRead, result.rowsWritten, result.statementCount);\n}\n\nvoid SqlStorage::setMaxPageCountForTest(jsg::Lock& js, int count) {\n  auto& db = getDb(js);\n  db.run({.regulator = SqliteDatabase::TRUSTED}, kj::str(\"PRAGMA max_page_count = \", count));\n}\n\njsg::Ref<SqlStorage::Statement> SqlStorage::prepare(jsg::Lock& js, jsg::JsString query) {\n  return js.alloc<Statement>(js, JSG_THIS, query);\n}\n\ndouble SqlStorage::getDatabaseSize(jsg::Lock& js) {\n  auto& context = IoContext::current();\n  TraceContext traceContext =\n      context.makeUserTraceSpan(\"durable_object_storage_getDatabaseSize\"_kjc);\n  traceContext.setTag(\"db.operation.name\"_kjc, \"getDatabaseSize\"_kjc);\n  auto& db = getDb(js);\n  int64_t pages = execMemoized(db, pragmaPageCount,\n      \"select (select * from pragma_page_count) - (select * from pragma_freelist_count);\")\n                      .getInt64(0);\n  auto dbSize = pages * getPageSize(db);\n  traceContext.setTag(\n      \"cloudflare.durable_object.response.db_size\"_kjc, static_cast<int64_t>(dbSize));\n  return dbSize;\n}\n\nbool SqlStorage::isAllowedName(kj::StringPtr name) const {\n  return !name.startsWith(\"_cf_\");\n}\n\nbool SqlStorage::isAllowedTrigger(kj::StringPtr name) const {\n  return true;\n}\n\nvoid SqlStorage::onError(kj::Maybe<int> sqliteErrorCode, kj::StringPtr message) const {\n  JSG_ASSERT(false, Error, message);\n}\n\nbool SqlStorage::allowTransactions() const {\n  JSG_FAIL_REQUIRE(Error,\n      \"To execute a transaction, please use the state.storage.transaction() or \"\n      \"state.storage.transactionSync() APIs instead of the SQL BEGIN TRANSACTION or SAVEPOINT \"\n      \"statements. The JavaScript API is safer because it will automatically roll back on \"\n      \"exceptions, and because it interacts correctly with Durable Objects' automatic atomic \"\n      \"write coalescing.\");\n}\n\nbool SqlStorage::shouldAddQueryStats() const {\n  // Bill for queries executed from JavaScript.\n  return true;\n}\n\nSqlStorage::StatementCache::~StatementCache() noexcept(false) {\n  for (auto& entry: lru) {\n    lru.remove(entry);\n  }\n}\n\njsg::JsValue SqlStorage::wrapSqlValue(jsg::Lock& js, SqlValue value) {\n  KJ_IF_SOME(v, value) {\n    KJ_SWITCH_ONEOF(v) {\n      KJ_CASE_ONEOF(bytes, kj::Array<byte>) {\n        return jsg::JsValue(js.wrapBytes(kj::mv(bytes)));\n      }\n      KJ_CASE_ONEOF(text, kj::StringPtr) {\n        return js.str(text);\n      }\n      KJ_CASE_ONEOF(number, double) {\n        return js.num(number);\n      }\n    }\n    KJ_UNREACHABLE;\n  } else {\n    return js.null();\n  }\n}\n\nSqlStorage::Cursor::State::State(SqliteDatabase& db,\n    SqliteDatabase::Regulator& regulator,\n    kj::StringPtr sqlCode,\n    kj::Array<BindingValue> bindingsParam)\n    : bindings(kj::mv(bindingsParam)),\n      query(db.run({.regulator = regulator}, sqlCode, mapBindings(bindings).asPtr())) {}\n\nSqlStorage::Cursor::State::State(\n    kj::Rc<CachedStatement> cachedStatementParam, kj::Array<BindingValue> bindingsParam)\n    : bindings(kj::mv(bindingsParam)),\n      query(cachedStatement.emplace(kj::mv(cachedStatementParam))\n                ->statement.run(mapBindings(bindings).asPtr())) {}\n\nSqlStorage::Cursor::~Cursor() noexcept(false) {\n  // If this Cursor was created from a Statement, clear the Statement's currentCursor weak ref.\n  KJ_IF_SOME(s, selfRef) {\n    KJ_IF_SOME(p, s) {\n      if (&p == this) {\n        s = kj::none;\n      }\n    }\n  }\n}\n\nvoid SqlStorage::Cursor::initColumnNames(jsg::Lock& js, State& stateRef) {\n  KJ_IF_SOME(cached, stateRef.cachedStatement) {\n    reusedCachedQuery = cached->useCount++ > 0;\n  }\n\n  js.withinHandleScope([&]() {\n    v8::LocalVector<v8::Value> vec(js.v8Isolate);\n    for (auto i: kj::zeroTo(stateRef.query.columnCount())) {\n      vec.push_back(js.str(stateRef.query.getColumnName(i)));\n    }\n    auto array = jsg::JsArray(v8::Array::New(js.v8Isolate, vec.data(), vec.size()));\n    columnNames = jsg::JsRef<jsg::JsArray>(js, array);\n  });\n}\n\ndouble SqlStorage::Cursor::getRowsRead() {\n  KJ_IF_SOME(st, state) {\n    return static_cast<double>(st->query.getRowsRead());\n  } else {\n    return static_cast<double>(rowsRead);\n  }\n}\n\ndouble SqlStorage::Cursor::getRowsWritten() {\n  KJ_IF_SOME(st, state) {\n    return static_cast<double>(st->query.getRowsWritten());\n  } else {\n    return static_cast<double>(rowsWritten);\n  }\n}\n\nSqlStorage::Cursor::RowIterator::Next SqlStorage::Cursor::next(jsg::Lock& js) {\n  auto self = JSG_THIS;\n  auto maybeRow = rowIteratorNext(js, self);\n  bool done = maybeRow == kj::none;\n  return {\n    .done = done,\n    .value = kj::mv(maybeRow),\n  };\n}\n\njsg::JsArray SqlStorage::Cursor::toArray(jsg::Lock& js) {\n  auto self = JSG_THIS;\n  v8::LocalVector<v8::Value> results(js.v8Isolate);\n  for (;;) {\n    auto maybeRow = rowIteratorNext(js, self);\n    KJ_IF_SOME(row, maybeRow) {\n      results.push_back(row);\n    } else {\n      break;\n    }\n  }\n\n  return jsg::JsArray(v8::Array::New(js.v8Isolate, results.data(), results.size()));\n}\n\njsg::JsValue SqlStorage::Cursor::one(jsg::Lock& js) {\n  auto self = JSG_THIS;\n  auto result = JSG_REQUIRE_NONNULL(rowIteratorNext(js, self), Error,\n      \"Expected exactly one result from SQL query, but got no results.\");\n\n  KJ_IF_SOME(s, state) {\n    // It appears that the query had more results, otherwise we would have set `state` to `none`\n    // inside `iteratorImpl()`.\n    endQuery(*s);\n    JSG_FAIL_REQUIRE(\n        Error, \"Expected exactly one result from SQL query, but got multiple results.\");\n  }\n\n  return result;\n}\n\njsg::Ref<SqlStorage::Cursor::RowIterator> SqlStorage::Cursor::rows(jsg::Lock& js) {\n  return js.alloc<RowIterator>(JSG_THIS);\n}\n\nkj::Maybe<jsg::JsObject> SqlStorage::Cursor::rowIteratorNext(jsg::Lock& js, jsg::Ref<Cursor>& obj) {\n  KJ_IF_SOME(values, iteratorImpl(js, obj)) {\n    auto names = obj->columnNames.getHandle(js);\n    jsg::JsObject result = js.obj();\n    KJ_ASSERT(names.size() == values.size());\n    for (auto i: kj::zeroTo(names.size())) {\n      result.set(js, names.get(js, i), jsg::JsValue(values[i]));\n    }\n    return result;\n  } else {\n    return kj::none;\n  }\n}\n\njsg::Ref<SqlStorage::Cursor::RawIterator> SqlStorage::Cursor::raw(jsg::Lock& js) {\n  return js.alloc<RawIterator>(JSG_THIS);\n}\n\n// Returns the set of column names for the current Cursor. An exception will be thrown if the\n// iterator has already been fully consumed. The resulting columns may contain duplicate entries,\n// for instance a `SELECT *` across a join of two tables that share a column name.\njsg::JsArray SqlStorage::Cursor::getColumnNames(jsg::Lock& js) {\n  return columnNames.getHandle(js);\n}\n\nkj::Maybe<jsg::JsArray> SqlStorage::Cursor::rawIteratorNext(jsg::Lock& js, jsg::Ref<Cursor>& obj) {\n  KJ_IF_SOME(values, iteratorImpl(js, obj)) {\n    return jsg::JsArray(v8::Array::New(js.v8Isolate, values.data(), values.size()));\n  } else {\n    return kj::none;\n  }\n}\n\nkj::Maybe<v8::LocalVector<v8::Value>> SqlStorage::Cursor::iteratorImpl(\n    jsg::Lock& js, jsg::Ref<Cursor>& obj) {\n  auto& state = *KJ_UNWRAP_OR(obj->state, {\n    if (obj->canceled) {\n      JSG_FAIL_REQUIRE(Error,\n          \"SQL cursor was closed because the same statement was executed again. If you need to \"\n          \"run multiple copies of the same statement concurrently, you must create multiple \"\n          \"prepared statement objects.\");\n    } else {\n      // Query already done.\n      return kj::none;\n    }\n  });\n\n  auto& query = state.query;\n\n  if (query.isDone()) {\n    obj->endQuery(state);\n    return kj::none;\n  }\n\n  auto n = query.columnCount();\n  v8::LocalVector<v8::Value> results(js.v8Isolate);\n  results.reserve(n);\n  for (auto i: kj::zeroTo(n)) {\n    SqlValue value;\n    KJ_SWITCH_ONEOF(query.getValue(i)) {\n      KJ_CASE_ONEOF(data, kj::ArrayPtr<const byte>) {\n        value.emplace(kj::heapArray(data));\n      }\n      KJ_CASE_ONEOF(text, kj::StringPtr) {\n        value.emplace(text);\n      }\n      KJ_CASE_ONEOF(i, int64_t) {\n        // int64 will become BigInt, but most applications won't want all their integers to be\n        // BigInt. We will coerce to a double here.\n        // TODO(someday): Allow applications to request that certain columns use BigInt.\n        value.emplace(static_cast<double>(i));\n      }\n      KJ_CASE_ONEOF(d, double) {\n        value.emplace(d);\n      }\n      KJ_CASE_ONEOF(_, decltype(nullptr)) {\n        // leave value null\n      }\n    }\n    results.push_back(wrapSqlValue(js, kj::mv(value)));\n  }\n\n  // Proactively iterate to the next row and, if it turns out the query is done, discard it. This\n  // is an optimization to make sure that the statement can be returned to the statement cache once\n  // the application has iterated over all results, even if the application fails to call next()\n  // one last time to get `{done: true}`. A common case where this could happen is if the app is\n  // expecting zero or one results, so it calls `exec(...).next()`. In the case that one result\n  // was returned, the application may not bother calling `next()` again. If we hadn't proactively\n  // iterated ahead by one, then the statement would not be returned to the cache until it was\n  // GC'ed, which might prevent the cache from being effective in the meantime.\n  //\n  // Unfortunately, this does not help with the case where the application stops iterating with\n  // results still available from the cursor. There's not much we can do about that case since\n  // there's no way to know if the app might come back and try to use the cursor again later.\n  query.nextRow();\n  if (query.isDone()) {\n    obj->endQuery(state);\n  }\n\n  return kj::mv(results);\n}\n\nvoid SqlStorage::Cursor::endQuery(State& stateRef) {\n  // Save off row counts before the query goes away.\n  rowsRead = stateRef.query.getRowsRead();\n  rowsWritten = stateRef.query.getRowsWritten();\n\n  KJ_IF_SOME(cb, doneCallback) {\n    cb(*this);\n    doneCallback = kj::none;\n  }\n\n  // Clean up the query proactively.\n  state = kj::none;\n}\n\nkj::Array<const SqliteDatabase::Query::ValuePtr> SqlStorage::Cursor::mapBindings(\n    kj::ArrayPtr<BindingValue> values) {\n  return KJ_MAP(value, values) -> SqliteDatabase::Query::ValuePtr {\n    KJ_IF_SOME(v, value) {\n      KJ_SWITCH_ONEOF(v) {\n        KJ_CASE_ONEOF(data, kj::Array<const byte>) {\n          return data.asPtr();\n        }\n        KJ_CASE_ONEOF(text, kj::String) {\n          return text.asPtr();\n        }\n        KJ_CASE_ONEOF(d, double) {\n          return d;\n        }\n      }\n    } else {\n      return nullptr;\n    }\n    KJ_UNREACHABLE;\n  };\n}\n\njsg::Ref<SqlStorage::Cursor> SqlStorage::Statement::run(\n    jsg::Lock& js, jsg::Arguments<BindingValue> bindings) {\n  return sqlStorage->exec(js, jsg::JsString(query.getHandle(js)), kj::mv(bindings));\n}\n\nvoid SqlStorage::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"storage\", storage);\n  tracker.trackFieldWithSize(\"IoPtr<SqliteDatabase>\", sizeof(IoPtr<SqliteDatabase>));\n  if (pragmaPageCount != kj::none) {\n    tracker.trackFieldWithSize(\n        \"IoPtr<SqllitDatabase::Statement>\", sizeof(IoPtr<SqliteDatabase::Statement>));\n  }\n  if (pragmaGetMaxPageCount != kj::none) {\n    tracker.trackFieldWithSize(\n        \"IoPtr<SqllitDatabase::Statement>\", sizeof(IoPtr<SqliteDatabase::Statement>));\n  }\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/sql.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/api/actor-state.h>\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/io/io-context.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/util/sqlite.h>\n\nnamespace workerd::api {\n\nclass SqlStorage final: public jsg::Object, private SqliteDatabase::Regulator {\n public:\n  SqlStorage(jsg::Ref<DurableObjectStorage> storage);\n  ~SqlStorage();\n\n  using BindingValue = kj::Maybe<kj::OneOf<kj::Array<const byte>, kj::String, double>>;\n\n  class Cursor;\n  class Statement;\n  struct IngestResult;\n\n  // One value returned from SQL. Note that we intentionally return StringPtr instead of String\n  // because we know that the underlying buffer returned by SQLite will be valid long enough to be\n  // converted by JSG into a V8 string. For byte arrays, on the other hand, we pass ownership to\n  // JSG, which does not need to make a copy.\n  using SqlValue = kj::Maybe<kj::OneOf<kj::Array<byte>, kj::StringPtr, double>>;\n\n  jsg::Ref<Cursor> exec(jsg::Lock& js, jsg::JsString query, jsg::Arguments<BindingValue> bindings);\n  IngestResult ingest(jsg::Lock& js, kj::String query);\n  void setMaxPageCountForTest(jsg::Lock& js, int count);\n\n  jsg::Ref<Statement> prepare(jsg::Lock& js, jsg::JsString query);\n\n  double getDatabaseSize(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(SqlStorage, CompatibilityFlags::Reader flags) {\n    JSG_METHOD(exec);\n\n    if (flags.getWorkerdExperimental()) {\n      // Prepared statement API is experimental-only and deprecated. exec() will automatically\n      // handle caching prepared statements, so apps don't need to worry about it.\n      JSG_METHOD(prepare);\n\n      // 'ingest' functionality is still experimental-only\n      JSG_METHOD(ingest);\n\n      JSG_METHOD(setMaxPageCountForTest);\n    }\n\n    JSG_READONLY_PROTOTYPE_PROPERTY(databaseSize, getDatabaseSize);\n\n    JSG_NESTED_TYPE(Cursor);\n    JSG_NESTED_TYPE(Statement);\n\n    JSG_TS_OVERRIDE({\n      exec<T extends Record<string, SqlStorageValue>>(query: string, ...bindings: any[]): SqlStorageCursor<T>\n    });\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(storage);\n  }\n\n  bool isAllowedName(kj::StringPtr name) const override;\n  bool isAllowedTrigger(kj::StringPtr name) const override;\n  void onError(kj::Maybe<int> sqliteErrorCode, kj::StringPtr message) const override;\n  bool allowTransactions() const override;\n  bool shouldAddQueryStats() const override;\n\n  SqliteDatabase& getDb(jsg::Lock& js) {\n    return storage->getSqliteDb(js);\n  }\n\n  jsg::Ref<DurableObjectStorage> storage;\n\n  kj::Maybe<uint> pageSize;\n  kj::Maybe<IoOwn<SqliteDatabase::Statement>> pragmaPageCount;\n  kj::Maybe<IoOwn<SqliteDatabase::Statement>> pragmaGetMaxPageCount;\n\n  // A statement in the statement cache.\n  struct CachedStatement: public kj::Refcounted {\n    jsg::HashableV8Ref<v8::String> query;\n    size_t statementSize;\n    SqliteDatabase::Statement statement;\n    kj::ListLink<CachedStatement> lruLink;\n    uint useCount = 0;\n\n    CachedStatement(jsg::Lock& js,\n        SqlStorage& sqlStorage,\n        SqliteDatabase& db,\n        jsg::JsString jsQuery,\n        kj::String kjQuery)\n        : query(js.v8Isolate, jsQuery),\n          statementSize(kjQuery.size()),\n          statement(db.prepareMulti(sqlStorage, kj::mv(kjQuery))) {}\n  };\n\n  class StatementCacheCallbacks {\n   public:\n    inline const jsg::HashableV8Ref<v8::String>& keyForRow(\n        const kj::Rc<CachedStatement>& entry) const {\n      return entry->query;\n    }\n\n    inline bool matches(const kj::Rc<CachedStatement>& entry, jsg::JsString key) const {\n      return entry->query == key;\n    }\n    inline bool matches(\n        const kj::Rc<CachedStatement>& entry, const jsg::HashableV8Ref<v8::String>& key) const {\n      return entry->query == key;\n    }\n\n    inline auto hashCode(jsg::JsString key) const {\n      return key.hashCode();\n    }\n    inline auto hashCode(const jsg::HashableV8Ref<v8::String>& key) const {\n      return key.hashCode();\n    }\n  };\n\n  // We can't quite just use kj::HashMap here because we want the table key to be\n  // `CachedStatement::query`, which is a member of the refcounted object.\n  using StatementMap = kj::Table<kj::Rc<CachedStatement>, kj::HashIndex<StatementCacheCallbacks>>;\n\n  struct StatementCache {\n    StatementMap map;\n    kj::List<CachedStatement, &CachedStatement::lruLink> lru;\n    size_t totalSize = 0;\n\n    ~StatementCache() noexcept(false);\n  };\n  IoOwn<StatementCache> statementCache;\n\n  template <size_t size, typename... Params>\n  SqliteDatabase::Query execMemoized(SqliteDatabase& db,\n      kj::Maybe<IoOwn<SqliteDatabase::Statement>>& slot,\n      const char (&sqlCode)[size],\n      Params&&... params) {\n    // Run a (trusted) statement, preparing it on the first call and reusing the prepared version\n    // for future calls.\n\n    SqliteDatabase::Statement* stmt;\n    KJ_IF_SOME(s, slot) {\n      stmt = &*s;\n    } else {\n      stmt = &*slot.emplace(IoContext::current().addObject(kj::heap(db.prepare(sqlCode))));\n    }\n    return stmt->run(kj::fwd<Params>(params)...);\n  }\n\n  uint64_t getPageSize(SqliteDatabase& db) {\n    KJ_IF_SOME(p, pageSize) {\n      return p;\n    } else {\n      return pageSize.emplace(db.run(\"PRAGMA page_size;\").getInt64(0));\n    }\n  }\n\n  // Utility functions to convert SqlValue to a JS value. We can't just return the C++ values and\n  // let JSG do the work because we're trying to avoid having to make a copy of string contents out\n  // of SQLite's buffer when the conversion to JS is just going to make another copy. We can't use\n  // jsg::TypeHandler because SqlValue contains StringPtr, which doesn't support unwrapping. We\n  // don't actually ever use unwrapping, but requesting a TypeHandler forces JSG to try to generate\n  // the code for unwrapping, leading to compiler errors.\n  //\n  // TODO(cleanup): Think hard about how to make JSG support this better. Part of the problem is\n  //   that we're being too clever with optimizations to avoid copying strings when we don't need\n  //   to.\n  static jsg::JsValue wrapSqlValue(jsg::Lock& js, SqlValue value);\n};\n\nclass SqlStorage::Cursor final: public jsg::Object {\n public:\n  template <typename... Params>\n  Cursor(jsg::Lock& js, kj::Maybe<kj::Function<void(Cursor&)>> doneCb, Params&&... params)\n      : doneCallback(kj::mv(doneCb)) {\n    auto stateObj = kj::heap<State>(kj::fwd<Params>(params)...);\n    initColumnNames(js, *stateObj);\n    if (stateObj->query.isDone()) {\n      endQuery(*stateObj);\n    } else {\n      state = IoContext::current().addObject(kj::mv(stateObj));\n    }\n  }\n  ~Cursor() noexcept(false);\n\n  double getRowsRead();\n  double getRowsWritten();\n\n  jsg::JsArray getColumnNames(jsg::Lock& js);\n  JSG_RESOURCE_TYPE(Cursor, CompatibilityFlags::Reader flags) {\n    JSG_METHOD(next);\n    JSG_METHOD(toArray);\n    JSG_METHOD(one);\n\n    JSG_ITERABLE(rows);\n    JSG_METHOD(raw);\n    JSG_READONLY_PROTOTYPE_PROPERTY(columnNames, getColumnNames);\n    JSG_READONLY_PROTOTYPE_PROPERTY(rowsRead, getRowsRead);\n    JSG_READONLY_PROTOTYPE_PROPERTY(rowsWritten, getRowsWritten);\n\n    JSG_TS_DEFINE(type SqlStorageValue = ArrayBuffer | string | number | null);\n    JSG_TS_OVERRIDE(<T extends Record<string, SqlStorageValue>> {\n      [Symbol.iterator](): IterableIterator<T>;\n      raw<U extends SqlStorageValue[]>(): IterableIterator<U>;\n      next(): { done?: false, value: T } | { done: true, value?: never };\n      toArray(): T[];\n      one(): T;\n      columnNames: string[];\n    });\n\n    if (flags.getWorkerdExperimental()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(reusedCachedQueryForTest, getReusedCachedQueryForTest);\n    }\n  }\n\n  JSG_ITERATOR(RowIterator, rows, jsg::JsObject, jsg::Ref<Cursor>, rowIteratorNext);\n  JSG_ITERATOR(RawIterator, raw, jsg::JsArray, jsg::Ref<Cursor>, rawIteratorNext);\n\n  RowIterator::Next next(jsg::Lock& js);\n  jsg::JsArray toArray(jsg::Lock& js);\n  jsg::JsValue one(jsg::Lock& js);\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    if (state != kj::none) {\n      tracker.trackFieldWithSize(\"IoOwn<State>\", sizeof(IoOwn<State>));\n    }\n    tracker.trackField(\"columnNames\", columnNames);\n  }\n\n  bool getReusedCachedQueryForTest() {\n    return reusedCachedQuery;\n  }\n\n private:\n  struct State {\n    kj::Maybe<kj::Rc<CachedStatement>> cachedStatement;\n\n    // The bindings that were used to construct `query`. We have to keep these alive until the query\n    // is done since it might contain pointers into strings and blobs.\n    kj::Array<BindingValue> bindings;\n\n    SqliteDatabase::Query query;\n\n    State(SqliteDatabase& db,\n        SqliteDatabase::Regulator& regulator,\n        kj::StringPtr sqlCode,\n        kj::Array<BindingValue> bindings);\n\n    State(kj::Rc<CachedStatement> cachedStatement, kj::Array<BindingValue> bindings);\n  };\n\n  // Nulled out when query is done or canceled.\n  kj::Maybe<IoOwn<State>> state;\n\n  // Called when the query is done or canceled.\n  kj::Maybe<kj::Function<void(Cursor&)>> doneCallback;\n\n  // True if the cursor was canceled by a new call to the same statement. This is used only to\n  // flag an error if the application tries to reuse the cursor.\n  bool canceled = false;\n\n  // Did we reuse a query from the query cache? Tracked for testing purposes.\n  bool reusedCachedQuery = false;\n\n  // Reference to a weak reference that might point back to this object. If so, null it out at\n  // destruction. Used by Statement to invalidate past cursors when the statement is\n  // executed again.\n  kj::Maybe<kj::Maybe<Cursor&>&> selfRef;\n\n  // Row IO counts. These are updated as the query runs. We keep these outside the State so they\n  // remain available even after the query is done or canceled.\n  uint64_t rowsRead = 0;\n  // Row IO counts. These are updated as the query runs. We keep these outside the State so they\n  // remain available even after the query is done or canceled.\n  uint64_t rowsWritten = 0;\n\n  jsg::JsRef<jsg::JsArray> columnNames;\n\n  // Invoke when `query.isDone()`, or when we want to prematurely cancel the query. This records\n  // row counters and then sets `state` to `none` to drop the query and return the prepared\n  // statement to the statement cache.\n  void endQuery(State& stateRef);\n\n  // Initialize `columnNames` from the state object.\n  void initColumnNames(jsg::Lock& js, State& stateRef);\n\n  static kj::Array<const SqliteDatabase::Query::ValuePtr> mapBindings(\n      kj::ArrayPtr<BindingValue> values);\n\n  static kj::Maybe<jsg::JsObject> rowIteratorNext(jsg::Lock& js, jsg::Ref<Cursor>& obj);\n  static kj::Maybe<jsg::JsArray> rawIteratorNext(jsg::Lock& js, jsg::Ref<Cursor>& obj);\n  static kj::Maybe<v8::LocalVector<v8::Value>> iteratorImpl(jsg::Lock& js, jsg::Ref<Cursor>& obj);\n\n  friend class Statement;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(columnNames);\n  }\n};\n\n// The prepared statement API is supported only for backwards compatibility for certain early\n// internal users of SQLite-backed DOs. This API was not released because we chose instead to\n// implement automatic prepared statement caching via the simple `exec()` API. Since this is\n// a compatibility shim only, to simplify things, it is actually just a wrapper around `exec()`.\nclass SqlStorage::Statement final: public jsg::Object {\n public:\n  Statement(jsg::Lock& js, jsg::Ref<SqlStorage> sqlStorage, jsg::JsString query)\n      : sqlStorage(kj::mv(sqlStorage)),\n        // Internalize the string before constructing the statement so that it doesn't have to\n        // re-lookup the internalized string for every invocation.\n        query(js.v8Isolate, query.internalize(js)) {}\n\n  jsg::Ref<Cursor> run(jsg::Lock& js, jsg::Arguments<BindingValue> bindings);\n\n  JSG_RESOURCE_TYPE(Statement) {\n    JSG_CALLABLE(run);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"sqlStorage\", sqlStorage);\n    tracker.trackField(\"query\", query);\n  }\n\n private:\n  jsg::Ref<SqlStorage> sqlStorage;\n  jsg::V8Ref<v8::String> query;\n\n  friend class Cursor;\n};\n\nstruct SqlStorage::IngestResult {\n  IngestResult(kj::String remainder, double rowsRead, double rowsWritten, double statementCount)\n      : remainder(kj::mv(remainder)),\n        rowsRead(rowsRead),\n        rowsWritten(rowsWritten),\n        statementCount(statementCount) {}\n\n  kj::String remainder;\n  double rowsRead;\n  double rowsWritten;\n  double statementCount;\n\n  JSG_STRUCT(remainder, rowsRead, rowsWritten, statementCount);\n};\n\n#define EW_SQL_ISOLATE_TYPES                                                                       \\\n  api::SqlStorage, api::SqlStorage::Statement, api::SqlStorage::Cursor,                            \\\n      api::SqlStorage::IngestResult, api::SqlStorage::Cursor::RowIterator,                         \\\n      api::SqlStorage::Cursor::RowIterator::Next, api::SqlStorage::Cursor::RawIterator,            \\\n      api::SqlStorage::Cursor::RawIterator::Next\n// The list of sql.h types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/AGENTS.md",
    "content": "# src/workerd/api/streams/\n\nWeb Streams API: ReadableStream, WritableStream, TransformStream.\nSee `README.md` for terse reference (classification, state machines, safety patterns).\nSee `docs/streams.md` for narrative tutorial.\n\n## ARCHITECTURE\n\nDual implementation behind unified API:\n\n- **Internal** (`internal.h`): kj-backed, byte-only, single pending read, no JS queue\n- **Standard** (`standard.h`): WHATWG spec, JS promises, value+byte, dual queues (data + pending reads)\n\nController pattern: `ReadableStream` → `ReadableStreamController` → impl-specific controller.\n4 pipe loop variants (kj↔kj, kj↔JS, JS↔kj, JS↔JS) selected by stream type combination.\nTee uses `kj::Rc<Entry>` shared refs (non-standard optimization, avoids data copies).\n\nKey deps: `util/state-machine.h` (stream state FSM), `util/weak-refs.h`, `util/ring-buffer.h`.\n\n## FILE MAP\n\n| File                                | Role                                                                                                |\n| ----------------------------------- | --------------------------------------------------------------------------------------------------- |\n| `readable.{h,c++}`                  | `ReadableStream` public API, `ReaderImpl`, readers                                                  |\n| `writable.{h,c++}`                  | `WritableStream` public API, `WriterImpl`, writer                                                   |\n| `transform.{h,c++}`                 | Standard `TransformStream` (JS algorithms)                                                          |\n| `identity-transform-stream.{h,c++}` | Internal identity transform (byte-only, 1:1 read↔write)                                             |\n| `internal.{h,c++}`                  | `ReadableStreamInternalController`, `WritableStreamInternalController`                              |\n| `standard.{h,c++}`                  | `ReadableStreamJsController`, `WritableStreamJsController` — main complexity (~5400 lines combined) |\n| `readable-source.{h,c++}`           | `ReadableStreamSource` — kj `AsyncInputStream` adapter                                              |\n| `writable-sink.{h,c++}`             | `WritableStreamSink` — kj `AsyncOutputStream` adapter                                               |\n| `readable-source-adapter.{h,c++}`   | Standard→Internal bridge (wraps JS controller as `ReadableStreamSource`)                            |\n| `writable-sink-adapter.{h,c++}`     | Standard→Internal bridge (wraps JS controller as `WritableStreamSink`)                              |\n| `common.{h,c++}`                    | Shared types: `ReadResult`, `StreamStates`, controller interfaces                                   |\n| `queue.{h,c++}`                     | `ValueQueue`/`ByteQueue` for Standard stream backpressure                                           |\n| `compression.{h,c++}`               | `CompressionStream`/`DecompressionStream` (zlib transforms)                                         |\n| `encoding.{h,c++}`                  | `TextEncoderStream`/`TextDecoderStream`                                                             |\n\n## INVARIANTS\n\nThese rules MUST be followed when modifying stream code:\n\n1. **Always use `deferControllerStateChange()`** when calling code that may invoke JS\n   callbacks during a read/write operation\n2. **Always use `snapshot()`** when iterating over consumers if loop body may trigger JS\n3. **Never access `this`** after calling a StateListener callback that may destroy the object\n4. **Always re-check lock state** in lambda continuations that may execute after lock release\n5. **Use WeakRef** for any handle that user code may hold longer than the underlying object\n6. **State before promises**: Transition state before resolving promises; pop queue entries\n   only after resolving/rejecting their associated promises\n7. **No dangling captures**: Prefer capturing `this` + re-acquiring refs inside lambdas\n   over capturing raw references that may become dangling\n\nSee `README.md` §Safety Pattern Catalog for detailed When/Why/How for each pattern.\n\n## CODE REVIEW RULE\n\nWhen reviewing changes to streams code, check whether the change requires updates to\nany of these documentation files:\n\n- **`README.md`** — if the change alters stream classification (e.g. new stream type),\n  state machine behavior, pipe loop selection, or introduces/modifies a safety pattern\n- **`docs/streams.md`** — if the change affects user-visible behavior, data flow\n  semantics, or backpressure mechanics that the tutorial describes\n- **This file (`AGENTS.md`)** — if the change adds/removes files, changes the\n  architecture summary, or introduces new invariants\n\nFlag any needed doc updates in the review. Do not let behavioral or architectural\nchanges land without corresponding documentation updates.\n"
  },
  {
    "path": "src/workerd/api/streams/README.md",
    "content": "# Streams Implementation Reference\n\nTerse reference for the streams subsystem. For narrative tutorial, see\n[docs/streams.md](../../../../docs/streams.md).\n\nFor file map and coding invariants, see [AGENTS.md](AGENTS.md).\n\n## Classification Matrix\n\n| Axis             | Internal                                      | Standard                            |\n| ---------------- | --------------------------------------------- | ----------------------------------- |\n| Spec conformance | Non-standard (kj-backed)                      | WHATWG Streams                      |\n| Data types       | Byte-only (TypedArray/ArrayBuffer)            | Byte or Value (any JS value)        |\n| Queue model      | No queue; single pending read                 | Dual queues (data + pending reads)  |\n| Async model      | kj::Promise / kj event loop                   | JS Promise / microtasks             |\n| Isolate lock     | Data flows outside lock                       | Data flows inside lock              |\n| Backpressure     | Implicit (kj flow control)                    | Explicit (highwater mark + size fn) |\n| Reader types     | Default + BYOB                                | Default + BYOB (byte streams only)  |\n| Readable ctrl    | `ReadableStreamInternalController`            | `ReadableStreamJsController`        |\n| Writable ctrl    | `WritableStreamInternalController`            | `WritableStreamJsController`        |\n| Readable backing | `ReadableStreamSource` (kj::AsyncInputStream) | JS pull/cancel algorithms           |\n| Writable backing | `WritableStreamSink` (kj::AsyncOutputStream)  | JS write/abort/close algorithms     |\n| Creation         | `request.body`, internal APIs                 | `new ReadableStream({...})`         |\n\nNote: the `ReadableStream`/`WritableStream` APIs provide no way to inspect whether a\nstream is byte-oriented or value-oriented at runtime.\n\n## Standard ReadableStream\n\n### Queue Mechanics\n\nTwo queues: **data queue** (chunks waiting to be read) and **pending read queue**\n(unfulfilled read requests).\n\nFour algorithms: **start** (init), **pull** (request data), **cancel** (abort),\n**size** (backpressure calculation).\n\nHighwater mark rules:\n\n- Pull is invoked when data queue size < highwater mark\n- Pull pushes data in; if a pending read exists, it is fulfilled immediately\n- If no pending read, data goes to the queue\n- If enqueued data exceeds what the first pending read needs, excess goes to queue;\n  remaining pending reads drain against queued data\n\n```\n        +----------------+\n        | pull algorithm | <------------------------------------------+\n        +----------------+                                            |\n                |                                                     |\n                v                                                     |\n        +---------------+                                             |\n        | enqueue(data) |                                             |\n        +---------------+                                             |\n                |                                                     |\n                v                                                     |\n      +--------------------+       +-------------------+              |\n      | has a pending read | ----> | has data in queue |              |\n      +--------------------+  yes  +-------------------+              |\n                |                     |      no|                      |\n              no|                     |        v                      |\n                v                     |  +----------------------+     |\n       +-------------------+    yes   |  | fulfill pending read |     |\n       | add data to queue | <--------+  +----------------------+     |\n       +-------------------+                    |                     |\n                       |                        |                     |\n                       |                        v                     |\n                       |    +-----------------------------+     no    |\n                       +--> | is queue at highwater mark? | -----------\n                            +-----------------------------+\n                                      yes |\n                                          v\n                                        (done)\n```\n\nOn `reader.read()`:\n\n- Queue has data -> fulfill immediately; if queue drops below HWM, call pull\n- Queue empty -> add to pending read queue; if below HWM, call pull\n\n```\n    +---------------+\n    | reader.read() |\n    +---------------+\n           |\n           v\n   +-----------------+       +------------------+       +---------------------+\n   | queue has data? | ----> | add pending read | ----> | call pull algorithm |\n   +-----------------+  no   +------------------+       +---------------------+\n           |\n       yes |\n           v\n    +--------------+\n    | fulfill read |\n    +--------------+\n           |\n           v\n         (done)\n```\n\n**WARNING**: Source can push data even with no readers and after backpressure signal.\nQueue grows unbounded. User code can enqueue via controller reference at any time,\nindependent of pull algorithm.\n\n### Tee Behavior\n\nNon-standard optimization: branches hold `kj::Rc<Entry>` shared references instead\nof copying data. Backpressure to trunk based on branch with most unconsumed data.\n\n```\n   +----------------+\n   | pull algorithm |\n   +----------------+\n           |\n           v          ..........................................................\n   +---------------+  .   +---------------------+      +-------------------+\n   | enqueue(data) | ---> | push data to branch | ---> | has pending read? |\n   +---------------+  .   +---------------------+      +-------------------+\n               |      .                                no |    yes |\n               |      .       +-------------------+       |  +--------------+\n               |      .       | add data to queue | <-----+  | fulfill read |\n               |      .       +-------------------+          +--------------+\n               |      ............................................................\n               |      .   +---------------------+      +-------------------+\n               +--------> | push data to branch | ---> | has pending read? |\n                      .   +---------------------+      +-------------------+\n                      .                                no |    yes |\n                      .       +-------------------+       |  +--------------+\n                      .       | add data to queue | <-----+  | fulfill read |\n                      .       +-------------------+          +--------------+\n                      ............................................................\n```\n\nDoes not completely prevent slow-branch buildup but avoids memory pileup when source\nrespects backpressure signals.\n\n## Internal ReadableStream\n\nNo queue. No pull algorithm. Single pending read only (second `read()` throws).\nBacked by `ReadableStreamSource` wrapping `kj::AsyncInputStream`.\n\n`read()` -> `tryRead()` on source -> `kj::Promise<data>`. Data flows only when a\nreader is active. Each read bounded by max byte count.\n\n```\n    +---------------+\n    | reader.read() |\n    +---------------+\n            |\n            v\n  +-------------------+      +-----------------------------------+\n  | has pending read? | ---> | tryRead() on ReadableStreamSource |\n  +-------------------+  no  +-----------------------------------+\n        yes |\n            v\n        +-------+\n        | error |\n        +-------+\n```\n\n## Standard WritableStream\n\nFive algorithms: **start**, **write**, **abort**, **close**, **size**.\n\n`writer.write(data)` invokes write algorithm; promise resolves on completion.\nBackpressure signals:\n\n- `writer.desiredSize` -- space remaining before queue full\n- `writer.ready` -- promise resolved when backpressure clears; replaced with\n  new promise on each backpressure signal\n\nHighwater mark is advisory. Write algorithm must accept calls regardless of\ncurrent mark value.\n\n## Internal WritableStream\n\nBacked by `WritableStreamSink` wrapping `kj::AsyncOutputStream`.\n`write()` passes directly to sink. Behavior depends on specific sink implementation.\n\n## IdentityTransformStream State Machine\n\nByte-only (`ArrayBuffer`/`TypedArray`). Single shared class implements both\n`ReadableStreamSource` and `WritableStreamSink`.\n\nCompat flag: when enabled, `new TransformStream()` creates Standard TransformStream.\nWhen disabled, `new TransformStream()` aliases `new IdentityTransformStream()`.\n\n| Current State | Event     | Next State    | Action                                |\n| ------------- | --------- | ------------- | ------------------------------------- |\n| Idle          | `write()` | Write Pending | Hold data; return pending promise     |\n| Idle          | `read()`  | Read Pending  | Return pending promise                |\n| Read Pending  | `write()` | Idle          | Fulfill read with data; resolve write |\n| Write Pending | `read()`  | Idle          | Fulfill read with held data           |\n\nOnly one in-flight read or write at a time. `write()` promise won't resolve until\na corresponding `read()`, and vice versa.\n\n## Standard TransformStream\n\nThree algorithms: **start**, **transform**, **flush**.\n\n`write()` -> transform algorithm -> `controller.enqueue(result)` -> readable side queue.\nBoth sides are full Standard ReadableStream/WritableStream with independent queues and\nbackpressure. Writes not blocked on reads unless backpressure signals queue full.\nAny JS value, not byte-restricted.\n\n## Pipe Loop Selection\n\nEntry point: `writableController.tryPipeFrom(readable)` -- destination selects loop type.\n\n| Readable | Writable | Loop Type | Isolate Lock | Data Restriction |\n| -------- | -------- | --------- | ------------ | ---------------- |\n| Internal | Internal | kj-to-kj  | Not held     | Bytes only       |\n| Internal | Standard | kj-to-JS  | Held         | Bytes only       |\n| Standard | Internal | JS-to-kj  | Held         | Bytes only       |\n| Standard | Standard | JS-to-JS  | Held         | Any value        |\n\n```\n         +---------------------------+\n         | readable.pipeTo(writable) |\n         +---------------------------+\n                       |\n                       v\n   +------------------------------------------+\n   | writableController.tryPipeFrom(readable) |\n   +------------------------------------------+\n                       |\n                       v\n           +-----------------------+       +-----------------------+\n           | is internal writable? | ----> | is internal readable? |\n           +-----------------------+  yes  +-----------------------+\n                       |                      no |         yes |\n                    no |                         |             |\n                       v                    +----+     +---------------+\n           +-----------------------+        |          | kj-to-kj pipe |\n           | is internal readable? |        |          |      loop     |\n           +-----------------------+        |          +---------------+\n            no  |           yes |           |\n                |               |           |\n                v               |           |\n         +---------------+      |           v\n         | JS-to-JS pipe |      |   +---------------+\n         |      loop     |      |   | JS-to-kj pipe |\n         +---------------+      |   |     loop      |\n                                v   +---------------+\n                        +---------------+\n                        | kj-to-JS pipe |\n                        |     loop      |\n                        +---------------+\n```\n\n### kj-to-kj\n\nFully optimized. kj pipes `AsyncInputStream` to `AsyncOutputStream` via kj event loop.\nNo JS involvement. Data never enters JS heap.\n\n### kj-to-JS / JS-to-kj\n\nBridges kj::Promise and JS Promise. Entire flow under isolate lock. Data restricted\nto bytes (Standard streams may be value-oriented but API provides no way to inspect,\nso byte-only is enforced at the bridge).\n\n### JS-to-JS\n\nPure JS promise chaining under isolate lock. Equivalent to:\n`for await (const chunk of reader) { await writer.write(chunk); }`\n\n## Standard-to-Internal Bridge\n\nAPIs like `new Response(standardReadable)` were built for Internal streams and use\nkj async I/O. Standard ReadableStreams support consumption via the `ReadableStreamSource`\nadapter API (same API Internal streams use).\n\nWhen `pumpTo()` is called on the adapter:\n\n1. Acquires isolate lock\n2. Runs read->write promise loop\n3. Ends when data exhausted or stream errors\n\nImplemented in `readable-source-adapter.{h,c++}`.\n\n## Safety Pattern Catalog\n\n### Pattern: State Machine (`StateMachine<>`)\n\n- **When**: All stream controllers\n- **What**: Terminal states (`Closed`, `Errored`) prevent double-close/double-error.\n  `PendingStates<>` defers transitions during operations. `ActiveState<>` marks\n  operational state.\n- **Where**: `util/state-machine.h`\n\n### Pattern: Deferred State Transitions\n\n- **When**: Calling code that may invoke JS callbacks during a read/write operation\n- **Why**: JS callbacks can trigger close/error mid-operation; state must not change\n  until operation completes\n- **How**: `beginOperation()` increments counter; `endOperation()` applies pending\n  transition when counter hits 0\n- **Entry point**: `deferControllerStateChange()` in `standard.h`\n\n```cpp\ncontroller.state.beginOperation();  // Increment counter\nauto result = readCallback();       // May trigger JS that calls close()\ncontroller.state.endOperation();    // Apply pending state if counter == 0\n```\n\n### Pattern: Consumer Snapshot\n\n- **When**: Iterating over consumers when loop body may trigger JavaScript\n- **Why**: JS callbacks during iteration can add/remove consumers, invalidating iterators\n- **How**: Copy before iterating\n\n```cpp\nauto consumers = ready.consumers.snapshot();\nfor (auto consumer: consumers) {\n    consumer->push(js, entry->clone(js));  // May trigger JS that modifies consumers\n}\n```\n\n### Pattern: WeakRef for User-Held Handles\n\n- **When**: Handles user code may hold longer than underlying object (`ByobRequest`,\n  `PumpToReader`)\n- **How**: Check liveness before use\n\n```cpp\nKJ_IF_SOME(reader, pumpToReader->tryGet()) {\n    reader.pumpLoop(js, ...);  // Safe -- still alive\n}\n```\n\n### Pattern: `Rc<Entry>` for Shared Queue Data\n\n- **When**: Queue entries shared across teed stream consumers\n- **Why**: Prevents use-after-free when one branch consumes before another\n- **How**: `kj::Rc<Entry>` reference counting; `entry->clone(js)` per consumer\n\n```cpp\nclass Entry: public kj::Refcounted {\n    kj::Rc<Entry> clone(jsg::Lock& js);\n};\n```\n\n### Pattern: Lambda Capture Re-check\n\n- **When**: Lambdas attached to promise continuations that may execute after lock release\n- **Why**: Referenced objects may be destroyed between capture and execution\n- **How**: Re-acquire references inside lambda; check lock state before use\n- **Rule**: NEVER capture raw references that may become dangling. Use `addRef()` or\n  re-acquire.\n\n```cpp\nauto onSuccess = JSG_VISITABLE_LAMBDA((this, ref = addRef(), ...), ..., (...) {\n    auto maybePipeLock = lock.tryGetPipe();\n    if (maybePipeLock == kj::none) return js.resolvedPromise();\n    auto& pipeLock = KJ_REQUIRE_NONNULL(maybePipeLock);\n    // Now safe to use pipeLock\n});\n```\n\n### Pattern: StateListener Self-Destruction Guard\n\n- **When**: Consumer state listener callbacks (`onConsumerClose`, etc.)\n- **Why**: Callback may destroy `this` via `owner.doClose()`\n- **Rule**: NEVER access `this` after calling methods that may destroy the listener\n\n```cpp\nvoid onConsumerClose(jsg::Lock& js) override {\n    KJ_IF_SOME(s, state) {\n        s.owner.doClose(js);  // May destroy *this!\n    }\n    // DO NOT ACCESS *this AFTER THIS POINT\n}\n```\n\n### Pattern: Refcounted Pipe State\n\n- **When**: Internal stream pipe operations with async continuations\n- **How**: `Pipe::State` is `kj::Refcounted`; lambdas capture `kj::addRef(*state)`;\n  `~Pipe()` sets `state->aborted = true`; continuations check before proceeding\n\n```cpp\nstruct Pipe {\n    struct State: public kj::Refcounted {\n        bool aborted = false;\n    };\n    kj::Own<State> state;\n    ~Pipe() noexcept(false) { state->aborted = true; }\n};\n```\n\n### Pattern: Generation Counter\n\n- **When**: `writeLoop()` -- detecting queue modification during async operation\n- **How**: Capture generation before async op; assert unchanged after\n\n```cpp\nauto check = [expectedGeneration = queue.currentGeneration()]() {\n    KJ_ASSERT(queue.currentGeneration() == expectedGeneration);\n    return queue.front();\n};\n```\n\n### Pattern: Promise Resolution Ordering\n\n- **When**: State transitions paired with promise resolution\n- **Rule**: Transition state BEFORE resolving promise. Continuations must see\n  consistent state.\n\n```cpp\nvoid doClose(jsg::Lock& js) {\n    state.transitionTo<StreamStates::Closed>();           // State changes NOW\n    maybeResolvePromise(js, locked.getClosedFulfiller()); // Schedules microtask\n}\n```\n\n### Pattern: V8Ref Buffer Ownership\n\n- **When**: Async write operations that reference JS heap data\n- **Why**: GC could collect the buffer while kj async write is in flight\n- **How**: Store `jsg::V8Ref<v8::ArrayBuffer>` alongside raw pointer\n\n```cpp\nstruct Write {\n    jsg::V8Ref<v8::ArrayBuffer> ownBytes;  // Prevents GC\n    kj::ArrayPtr<kj::byte> bytes;          // Raw pointer into ownBytes\n};\n```\n\n## Cross-Request Model\n\nMultiple requests share one isolate via green threads. When one request yields for\nI/O, another may run. `SetPromiseCrossContextResolveCallback` defers promise reactions\nto the correct request context.\n\nStream code interacts with this via:\n\n- `ioContext.addFunctor()` -- binds continuations to correct `IoContext`\n- `IoOwn<>` -- ensures objects accessed from correct context\n- Promise context tagging -- all promises tagged with originating `IoContext`;\n  reactions deferred if tag doesn't match current context\n"
  },
  {
    "path": "src/workerd/api/streams/common.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"common.h\"\n\nnamespace workerd::api {\n\nWritableStreamController::PendingAbort::PendingAbort(\n    jsg::Lock& js, jsg::PromiseResolverPair<void> prp, v8::Local<v8::Value> reason, bool reject)\n    : resolver(kj::mv(prp.resolver)),\n      promise(kj::mv(prp.promise)),\n      reason(js.v8Ref(reason)),\n      reject(reject) {}\n\nWritableStreamController::PendingAbort::PendingAbort(\n    jsg::Lock& js, v8::Local<v8::Value> reason, bool reject)\n    : WritableStreamController::PendingAbort(js, js.newPromiseAndResolver<void>(), reason, reject) {\n}\n\nvoid WritableStreamController::PendingAbort::complete(jsg::Lock& js) {\n  if (reject) {\n    fail(js, reason.getHandle(js));\n  } else {\n    maybeResolvePromise(js, resolver);\n  }\n}\n\nvoid WritableStreamController::PendingAbort::fail(jsg::Lock& js, v8::Local<v8::Value> reason) {\n  maybeRejectPromise<void>(js, resolver, reason);\n}\n\nkj::Maybe<kj::Own<WritableStreamController::PendingAbort>> WritableStreamController::PendingAbort::\n    dequeue(kj::Maybe<kj::Own<WritableStreamController::PendingAbort>>& maybePendingAbort) {\n  return kj::mv(maybePendingAbort);\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/common.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"../basics.h\"\n\n#include <workerd/io/io-context.h>\n#include <workerd/io/worker-interface.capnp.h>\n#include <workerd/jsg/jsg.h>\n\n#if _MSC_VER\nusing ssize_t = long long;\n#endif\n\nnamespace workerd::api {\n\nclass ReadableStream;\nclass ReadableStreamController;\nclass ReadableStreamSource;\nclass ReadableStreamDefaultController;\nclass ReadableByteStreamController;\n\nclass WritableStream;\nclass WritableStreamController;\nclass WritableStreamSink;\nclass WritableStreamDefaultController;\n\nclass TransformStreamDefaultController;\n\nusing rpc::StreamEncoding;\n\nenum class ReadAllTextOption : uint8_t {\n  NONE = 0,\n  NULL_TERMINATE = 1 << 0,\n  STRIP_BOM = 1 << 1,\n};\n\ninline ReadAllTextOption operator|(ReadAllTextOption a, ReadAllTextOption b) {\n  return static_cast<ReadAllTextOption>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));\n}\n\ninline ReadAllTextOption& operator|=(ReadAllTextOption& a, ReadAllTextOption b) {\n  return a = a | b;\n}\n\ninline bool operator&(ReadAllTextOption a, ReadAllTextOption b) {\n  return (static_cast<uint8_t>(a) & static_cast<uint8_t>(b)) != 0;\n}\n\nstatic constexpr kj::byte UTF8_BOM[] = {0xEF, 0xBB, 0xBF};\nstatic constexpr size_t UTF8_BOM_SIZE = sizeof(UTF8_BOM);\n\ninline bool hasUtf8Bom(kj::ArrayPtr<const kj::byte> data) {\n  return data.size() >= UTF8_BOM_SIZE && memcmp(data.begin(), UTF8_BOM, UTF8_BOM_SIZE) == 0;\n}\n\nstruct ReadResult {\n  jsg::Optional<jsg::Value> value;\n  bool done;\n\n  JSG_STRUCT(value, done);\n  JSG_STRUCT_TS_OVERRIDE(type ReadableStreamReadResult<R = any> =\n    | { done: false, value: R; }\n    | { done: true; value?: undefined; }\n  );\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(value);\n  }\n};\n\n// Result type for draining read operations. Always returns bytes, even for value streams.\n// Used by DrainingReader for optimized pipe-to operations with vectored writes.\n// This is a C++ only type - not exposed to JavaScript.\nstruct DrainingReadResult {\n  kj::Array<kj::Array<kj::byte>> chunks;  // Multiple byte arrays for vectored writes\n  bool done = false;                      // True if stream is closed/closing\n};\n\nstruct StreamQueuingStrategy {\n  using SizeAlgorithm = uint64_t(v8::Local<v8::Value>);\n\n  jsg::Optional<uint64_t> highWaterMark;\n  jsg::Optional<jsg::Function<SizeAlgorithm>> size;\n\n  JSG_STRUCT(highWaterMark, size);\n  JSG_STRUCT_TS_OVERRIDE(QueuingStrategy<T = any> {\n    size?: (chunk: T) => number | bigint;\n  });\n};\n\nstruct UnderlyingSource {\n  using Controller =\n      kj::OneOf<jsg::Ref<ReadableStreamDefaultController>, jsg::Ref<ReadableByteStreamController>>;\n  using StartAlgorithm = jsg::Promise<void>(Controller);\n  using PullAlgorithm = jsg::Promise<void>(Controller);\n  using CancelAlgorithm = jsg::Promise<void>(v8::Local<v8::Value> reason);\n\n  // The autoAllocateChunkSize mechanism allows byte streams to operate as if a BYOB\n  // reader is being used even if it is just a default reader. Support is optional\n  // per the streams spec but our implementation will always enable it. Specifically,\n  // if user code does not provide an explicit autoAllocateChunkSize, we'll assume\n  // this default.\n  static constexpr int DEFAULT_AUTO_ALLOCATE_CHUNK_SIZE = 4096;\n\n  // Per the spec, the type property for the UnderlyingSource should be either\n  // undefined, the empty string, or \"bytes\". When undefined, the empty string is\n  // used as the default. When type is the empty string, the stream is considered\n  // to be value-oriented rather than byte-oriented.\n  jsg::Optional<kj::String> type;\n\n  // Used only when type is equal to \"bytes\", the autoAllocateChunkSize defines\n  // the size of automatically allocated buffer that is created when a default\n  // mode read is performed on a byte-oriented ReadableStream that supports\n  // BYOB reads. The stream standard makes this optional to support and defines\n  // no default value. We've chosen to use a default value of 4096. If given,\n  // the value must be greater than zero.\n  jsg::Optional<int> autoAllocateChunkSize;\n\n  jsg::Optional<jsg::Function<StartAlgorithm>> start;\n  jsg::Optional<jsg::Function<PullAlgorithm>> pull;\n  jsg::Optional<jsg::Function<CancelAlgorithm>> cancel;\n\n  // The expectedLength is a non-standard extension used to support specifying the\n  // content-length when using a ReadableStream as the body of a request or response.\n  jsg::Optional<uint64_t> expectedLength;\n\n  JSG_STRUCT(type, autoAllocateChunkSize, start, pull, cancel, expectedLength);\n  JSG_STRUCT_TS_DEFINE(interface UnderlyingByteSource {\n    type: \"bytes\";\n    autoAllocateChunkSize?: number;\n    start?: (controller: ReadableByteStreamController) => void | Promise<void>;\n    pull?: (controller: ReadableByteStreamController) => void | Promise<void>;\n    cancel?: (reason: any) => void | Promise<void>;\n  });\n  JSG_STRUCT_TS_OVERRIDE(<R = any> {\n    type?: \"\" | undefined;\n    autoAllocateChunkSize: never;\n    start?: (controller: ReadableStreamDefaultController<R>) => void | Promise<void>;\n    pull?: (controller: ReadableStreamDefaultController<R>) => void | Promise<void>;\n    cancel?: (reason: any) => void | Promise<void>;\n  });\n};\n\nstruct UnderlyingSink {\n  using Controller = jsg::Ref<WritableStreamDefaultController>;\n  using StartAlgorithm = jsg::Promise<void>(Controller);\n  using WriteAlgorithm = jsg::Promise<void>(v8::Local<v8::Value>, Controller);\n  using AbortAlgorithm = jsg::Promise<void>(v8::Local<v8::Value> reason);\n  using CloseAlgorithm = jsg::Promise<void>();\n\n  // Per the spec, the type property for the UnderlyingSink should always be either\n  // undefined or the empty string. Any other value will trigger a TypeError.\n  jsg::Optional<kj::String> type;\n\n  jsg::Optional<jsg::Function<StartAlgorithm>> start;\n  jsg::Optional<jsg::Function<WriteAlgorithm>> write;\n  jsg::Optional<jsg::Function<AbortAlgorithm>> abort;\n  jsg::Optional<jsg::Function<CloseAlgorithm>> close;\n\n  JSG_STRUCT(type, start, write, abort, close);\n\n  // TODO(cleanup): Get rid of this override and parse the type directly in param-extractor.rs\n  JSG_STRUCT_TS_OVERRIDE(<W = any> {\n    write?: (chunk: W, controller: WritableStreamDefaultController) => void | Promise<void>;\n    start?: (controller: WritableStreamDefaultController) => void | Promise<void>;\n    abort?: (reason: any) => void | Promise<void>;\n    close?: () => void | Promise<void>;\n  });\n};\n\nstruct Transformer {\n  using Controller = jsg::Ref<TransformStreamDefaultController>;\n  using StartAlgorithm = jsg::Promise<void>(Controller);\n  using TransformAlgorithm = jsg::Promise<void>(v8::Local<v8::Value>, Controller);\n  using FlushAlgorithm = jsg::Promise<void>(Controller);\n  using CancelAlgorithm = jsg::Promise<void>(jsg::JsValue reason);\n\n  jsg::Optional<kj::String> readableType;\n  jsg::Optional<kj::String> writableType;\n\n  jsg::Optional<jsg::Function<StartAlgorithm>> start;\n  jsg::Optional<jsg::Function<TransformAlgorithm>> transform;\n  jsg::Optional<jsg::Function<FlushAlgorithm>> flush;\n  jsg::Optional<jsg::Function<CancelAlgorithm>> cancel;\n\n  // The expectedLength is a non-standard extension used to support specifying the\n  // content-length when using a TransformStream readable side as the body of a\n  // request or response.\n  jsg::Optional<uint64_t> expectedLength;\n\n  JSG_STRUCT(readableType, writableType, start, transform, flush, cancel, expectedLength);\n  JSG_STRUCT_TS_OVERRIDE(<I = any, O = any> {\n    start?: (controller: TransformStreamDefaultController<O>) => void | Promise<void>;\n    transform?: (chunk: I, controller: TransformStreamDefaultController<O>) => void | Promise<void>;\n    flush?: (controller: TransformStreamDefaultController<O>) => void | Promise<void>;\n    cancel?: (reason: any) => void | Promise<void>;\n    expectedLength?: number;\n  });\n};\n\n// ReadableStreamSource and WritableStreamSink\n//\n// These are implementation interfaces for ReadableStream and WritableStream. If you just need to\n// use a ReadableStream or WritableStream, you can safely skip reading this. If you need to\n// implement a new kind of stream, read on.\n\n// In the original Workers streams implementation, a ReadableStream would have a\n// ReadableStreamSource backing it. Likewise, a WritableStream would have a WritableStreamSink.\n// The ReadableStreamSource and WritableStreamSink are kj heap objects that provide a thin\n// wrapper on internal native stream sources originating from within the Workers runtime.\n//\n// With implementation of full streams standard support, we introduce the new abstraction APIs\n// ReadableStreamController and WritableStreamController, which will provide the underlying\n// implementation for both ReadableStream and WritableStream, respectively.\n//\n// When creating a new kind of *internal* ReadableStream, where the data is originating internally\n// from a kj stream, you will still implement the ReadableStreamSource API, just as before.\n// Likewise, when creating a new kind of *internal* WritableStream, where the data destination is\n// a kj stream, you will implement the WritableStreamSink API.\n\nclass WritableStreamSink {\n public:\n  virtual kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) KJ_WARN_UNUSED_RESULT = 0;\n  virtual kj::Promise<void> write(\n      kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) KJ_WARN_UNUSED_RESULT = 0;\n\n  virtual kj::Promise<void> end() KJ_WARN_UNUSED_RESULT = 0;\n  // Must call to flush and finish the stream.\n\n  virtual kj::Maybe<kj::Promise<DeferredProxy<void>>> tryPumpFrom(\n      ReadableStreamSource& input, bool end);\n\n  virtual void abort(kj::Exception reason) = 0;\n  // TODO(conform): abort() should return a promise after which closed fulfillers should be\n  //   rejected. This may necessitate an \"erroring\" state.\n\n  // Tells the sink that it is no longer to be responsible for encoding in the correct format.\n  // Instead, the caller takes responsibility. The expected encoding is returned; the caller\n  // promises that all future writes will use this encoding. The default implementation returns\n  // IDENTITY, which is always correct since that's the encoding write()s should have used if\n  // this weren't called at all.\n  virtual StreamEncoding disownEncodingResponsibility() {\n    return StreamEncoding::IDENTITY;\n  }\n};\n\nclass ReadableStreamSource {\n public:\n  virtual kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) = 0;\n\n  // The ReadableStreamSource version of pumpTo() has no `amount` parameter, since the Streams spec\n  // only defines pumping everything.\n  //\n  // If `end` is true, then `output.end()` will be called after pumping. Note that it's especially\n  // important to take advantage of this when using deferred proxying since calling `end()`\n  // directly might attempt to use the `IoContext` to call `registerPendingEvent()`.\n  virtual kj::Promise<DeferredProxy<void>> pumpTo(WritableStreamSink& output, bool end);\n\n  // If pumpTo() pumps to a system stream, what is the best encoding for that system stream to\n  // use? This is just a hint.\n  virtual StreamEncoding getPreferredEncoding() {\n    return StreamEncoding::IDENTITY;\n  };\n\n  virtual kj::Maybe<uint64_t> tryGetLength(StreamEncoding encoding);\n\n  kj::Promise<kj::Array<byte>> readAllBytes(uint64_t limit);\n  kj::Promise<kj::String> readAllText(\n      uint64_t limit, ReadAllTextOption option = ReadAllTextOption::NULL_TERMINATE);\n\n  // Hook to inform this ReadableStreamSource that the ReadableStream has been canceled. This only\n  // really means anything to TransformStreams, which are supposed to propagate the error to the\n  // writable side, and custom ReadableStreams, which we don't implement yet.\n  //\n  // NOTE: By \"propagate the error back to the writable stream\", I mean: if the WritableStream is in\n  //   the Writable state, set it to the Errored state and reject its closed fulfiller with\n  //   `reason`. I'm not sure how I'm going to do this yet.\n  virtual void cancel(kj::Exception reason);\n  // TODO(conform): Should return promise.\n  //\n  // TODO(conform): `reason` should be allowed to be any JS value, and not just an exception.\n  //   That is, something silly like `stream.cancel(42)` should be allowed and trigger a\n  //   rejection with the integer `42`.\n\n  struct Tee {\n    kj::Own<ReadableStreamSource> branches[2];\n  };\n\n  // Implement this if your ReadableStreamSource has a better way to tee a stream than the naive\n  // method, which relies upon `tryRead()`. The default implementation returns nullptr.\n  virtual kj::Maybe<Tee> tryTee(uint64_t limit);\n};\n\nstruct PipeToOptions {\n  jsg::Optional<bool> preventAbort;\n  jsg::Optional<bool> preventCancel;\n  jsg::Optional<bool> preventClose;\n  jsg::Optional<jsg::Ref<AbortSignal>> signal;\n\n  JSG_STRUCT(preventAbort, preventCancel, preventClose, signal);\n  JSG_STRUCT_TS_OVERRIDE(StreamPipeOptions);\n\n  // An additional, internal only property that is used to indicate\n  // when the pipe operation is used for a pipeThrough rather than\n  // a pipeTo. We use this information, for instance, to identify\n  // when we should mark returned rejected promises as handled.\n  bool pipeThrough = false;\n};\n\nnamespace StreamStates {\nstruct Closed {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"closed\"_kj;\n};\nusing Errored = jsg::Value;\nstruct Erroring {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"erroring\"_kj;\n  jsg::Value reason;\n\n  Erroring(jsg::Value reason): reason(kj::mv(reason)) {}\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(reason);\n  }\n};\n}  // namespace StreamStates\n\n// A ReadableStreamController provides the underlying implementation for a ReadableStream.\n// We will generally have three implementations:\n//  * ReadableStreamDefaultController\n//  * ReadableByteStreamController\n//  * ReadableStreamInternalController\n//\n// The ReadableStreamDefaultController and ReadableByteStreamController are defined by the\n// streams standard and source all of the stream data from JavaScript functions provided by\n// user code.\n//\n// The ReadableStreamInternalController is Workers runtime specific and provides a bridge\n// to the existing ReadableStreamSource API. At the API contract layer, the\n// ReadableByteStreamController and ReadableStreamInternalController will appear to be\n// identical. Internally, however, they will be very different from one another.\n//\n// The ReadableStreamController instance is meant to be a private member of the ReadableStream,\n// e.g.\n//    class ReadableStream {\n//    public:\n//      // ...\n//    private:\n//      ReadableStreamController controller;\n//      // ...\n//    }\n//\n// As such, it exists within the V8 heap (it's allocated directly as a member of the\n// ReadableStream) and will always execute within the V8 isolate lock.\n//\n// The methods here return jsg::Promise rather than kj::Promise because the controller\n// operations here do not always require passing through the kj mechanisms or kj event loop.\n// Likewise, we do not make use of kj::Exception in these interfaces because the stream\n// standard dictates that streams can be canceled/aborted/errored using any arbitrary JavaScript\n// value, not just Errors.\nclass ReadableStreamController {\n public:\n  // The ReadableStreamController::Reader interface is a base for all ReadableStream reader\n  // implementations and is used solely as a means of attaching a Reader implementation to\n  // the internal state of the controller. See the ReadableStream::*Reader classes for the\n  // full Reader API.\n  class Reader {\n   public:\n    // True if the reader is a BYOB reader.\n    virtual bool isByteOriented() const = 0;\n\n    // When a Reader is locked to a controller, the controller will attach itself to the reader,\n    // passing along the closed promise that will be used to communicate state to the\n    // user code.\n    //\n    // The Reader will hold a reference to the controller that will be cleared when the reader\n    // is released or destroyed. The controller is guaranteed to either outlive or detach the\n    // reader so the ReadableStreamController& reference should remain valid.\n    virtual void attach(ReadableStreamController& controller, jsg::Promise<void> closedPromise) = 0;\n\n    // When a Reader lock is released, the controller will signal to the reader that it has been\n    // detached.\n    virtual void detach() = 0;\n  };\n\n  struct ByobOptions {\n    static constexpr size_t DEFAULT_AT_LEAST = 1;\n\n    jsg::V8Ref<v8::ArrayBufferView> bufferView;\n    size_t byteOffset = 0;\n    size_t byteLength;\n\n    // The minimum number of bytes that should be read. When not specified, the default\n    // is DEFAULT_AT_LEAST. This is a non-standard, Workers-specific extension to\n    // support the readAtLeast method on the ReadableStreamBYOBReader object.\n    kj::Maybe<size_t> atLeast = DEFAULT_AT_LEAST;\n\n    // True if the given buffer should be detached. Per the spec, we should always be\n    // detaching a BYOB buffer but the original Workers implementation did not.\n    // To avoid breaking backwards compatibility, a compatibility flag is provided to turn\n    // detach on/off as appropriate.\n    bool detachBuffer = true;\n  };\n\n  struct Tee {\n    jsg::Ref<ReadableStream> branch1;\n    jsg::Ref<ReadableStream> branch2;\n  };\n\n  // Abstract API for ReadableStreamController implementations that provide their own\n  // tee implementations that are not backed by kj's tee. Each branch of the tee uses\n  // the TeeController to interface with the shared underlying source, and the\n  // TeeController ensures that each Branch receives the data that is read.\n  class TeeController {\n   public:\n    // Represents an individual ReadableStreamController tee branch registered with\n    // a TeeController. One or more branches is registered with the TeeController.\n    class Branch {\n     public:\n      virtual ~Branch() noexcept(false) {}\n\n      virtual void doClose(jsg::Lock& js) = 0;\n      virtual void doError(jsg::Lock& js, v8::Local<v8::Value> reason) = 0;\n      virtual void handleData(jsg::Lock& js, ReadResult result) = 0;\n    };\n\n    class BranchPtr {\n     public:\n      inline BranchPtr(Branch* branch): inner(branch) {\n        KJ_ASSERT(inner != nullptr);\n      }\n      BranchPtr(BranchPtr&& other) = default;\n      BranchPtr& operator=(BranchPtr&&) = default;\n      BranchPtr(BranchPtr& other) = default;\n\n      inline void doClose(jsg::Lock& js) {\n        inner->doClose(js);\n      }\n\n      inline void doError(jsg::Lock& js, v8::Local<v8::Value> reason) {\n        inner->doError(js, reason);\n      }\n\n      inline void handleData(jsg::Lock& js, ReadResult result) {\n        inner->handleData(js, kj::mv(result));\n      }\n\n      inline uint hashCode() {\n        return kj::hashCode(inner);\n      }\n      inline bool operator==(BranchPtr& other) const {\n        return inner == other.inner;\n      }\n\n     private:\n      Branch* inner;\n    };\n\n    virtual ~TeeController() noexcept(false) {}\n\n    virtual void addBranch(Branch* branch) = 0;\n\n    virtual void close(jsg::Lock& js) = 0;\n\n    virtual void error(jsg::Lock& js, v8::Local<v8::Value> reason) = 0;\n\n    virtual void ensurePulling(jsg::Lock& js) = 0;\n\n    // maybeJs will be nullptr when the isolate lock is not available.\n    // If maybeJs is set, any operations pending for the branch will be canceled.\n    virtual void removeBranch(Branch* branch, kj::Maybe<jsg::Lock&> maybeJs) = 0;\n  };\n\n  // The PipeController simplifies the abstraction between ReadableStreamController\n  // and WritableStreamController so that the pipeTo/pipeThrough/tryPipeTo can work\n  // without caring about what kind of controller it is working with.\n  class PipeController {\n   public:\n    virtual ~PipeController() noexcept(false) {}\n    virtual bool isClosed() = 0;\n    virtual kj::Maybe<v8::Local<v8::Value>> tryGetErrored(jsg::Lock& js) = 0;\n    virtual void cancel(jsg::Lock& js, v8::Local<v8::Value> reason) = 0;\n    virtual void close(jsg::Lock& js) = 0;\n    virtual void error(jsg::Lock& js, v8::Local<v8::Value> reason) = 0;\n    virtual void release(jsg::Lock& js, kj::Maybe<v8::Local<v8::Value>> maybeError = kj::none) = 0;\n    virtual kj::Maybe<kj::Promise<void>> tryPumpTo(WritableStreamSink& sink, bool end) = 0;\n    virtual jsg::Promise<ReadResult> read(jsg::Lock& js) = 0;\n  };\n\n  virtual ~ReadableStreamController() noexcept(false) {}\n\n  virtual void setOwnerRef(ReadableStream& stream) = 0;\n\n  virtual jsg::Ref<ReadableStream> addRef() = 0;\n\n  // Returns true if the underlying source for this controller is byte-oriented and\n  // therefore supports the pull into API. When false, the stream can be used to pass\n  // any arbitrary JavaScript value through.\n  virtual bool isByteOriented() const = 0;\n\n  // Reads data from the stream. If the stream is byte-oriented, then the ByobOptions can be\n  // specified to provide a v8::ArrayBuffer to be filled by the read operation. If the ByobOptions\n  // are provided and the stream is not byte-oriented, the operation will return a rejected promise.\n  virtual kj::Maybe<jsg::Promise<ReadResult>> read(\n      jsg::Lock& js, kj::Maybe<ByobOptions> byobOptions) = 0;\n\n  // Performs a draining read operation that:\n  // 1. Drains all currently buffered data from the queue\n  // 2. Pumps the controller for synchronously available data (respecting pull promise state)\n  // 3. Returns bytes even for value streams (converting ArrayBuffer/ArrayBufferView/string)\n  // 4. Has mutual exclusion with regular reads - returns rejected promise if pending regular reads\n  // 5. Returns done: true with final data when stream is closing\n  //\n  // This is a C++ only API (not exposed to JavaScript) intended for optimized pipe operations.\n  // Returns kj::none if the stream is locked in a way that prevents the read.\n  //\n  // The maxRead parameter provides a soft limit on how much data to read. Both the initial\n  // buffer drain and subsequent synchronous pump attempts stop when the total bytes read\n  // reaches maxRead (after finishing the current item). This prevents unbounded memory\n  // accumulation when a fast producer outpaces a slow consumer.\n  virtual kj::Maybe<jsg::Promise<DrainingReadResult>> drainingRead(\n      jsg::Lock& js, size_t maxRead = kj::maxValue) = 0;\n\n  // The pipeTo implementation fully consumes the stream by directing all of its data at the\n  // destination. Controllers should try to be as efficient as possible here. For instance, if\n  // a ReadableStreamInternalController is piping to a WritableStreamInternalController, then\n  // a more efficient kj pipe should be possible.\n  virtual jsg::Promise<void> pipeTo(\n      jsg::Lock& js, WritableStreamController& destination, PipeToOptions options) = 0;\n\n  // Indicates that the consumer no longer has any interest in the streams data.\n  virtual jsg::Promise<void> cancel(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> reason) = 0;\n\n  // Branches the ReadableStreamController into two ReadableStream instances that will receive\n  // this streams data. The specific details of how the branching occurs is entirely up to the\n  // controller implementation.\n  virtual Tee tee(jsg::Lock& js) = 0;\n\n  virtual bool isClosedOrErrored() const = 0;\n\n  virtual bool isClosed() const = 0;\n\n  virtual bool isDisturbed() = 0;\n\n  // True if a Reader has been locked to this controller.\n  virtual bool isLockedToReader() const = 0;\n\n  // Locks this controller to the given reader, returning true if the lock was successful, or false\n  // if the controller was already locked.\n  virtual bool lockReader(jsg::Lock& js, Reader& reader) = 0;\n\n  // Removes the lock and releases the reader from this controller.\n  // maybeJs will be nullptr when the isolate lock is not available.\n  // If maybeJs is set, the reader's closed promise will be resolved.\n  virtual void releaseReader(Reader& reader, kj::Maybe<jsg::Lock&> maybeJs) = 0;\n\n  virtual kj::Maybe<PipeController&> tryPipeLock() = 0;\n\n  virtual void visitForGc(jsg::GcVisitor& visitor) {};\n\n  // Fully consumes the ReadableStream. If the stream is already locked to a reader or\n  // errored, the returned JS promise will reject. If the stream is already closed, the\n  // returned JS promise will resolve with a zero-length result. Importantly, this will\n  // lock the stream and will fully consume it.\n  //\n  // limit specifies an upper maximum bound on the number of bytes permitted to be read.\n  // The promise will reject if the read will produce more bytes than the limit.\n  virtual jsg::Promise<jsg::BufferSource> readAllBytes(jsg::Lock& js, uint64_t limit) = 0;\n\n  // Fully consumes the ReadableStream. If the stream is already locked to a reader or\n  // errored, the returned JS promise will reject. If the stream is already closed, the\n  // returned JS promise will resolve with a zero-length result. Importantly, this will\n  // lock the stream and will fully consume it.\n  //\n  // limit specifies an upper maximum bound on the number of bytes permitted to be read.\n  // The promise will reject if the read will produce more bytes than the limit.\n  virtual jsg::Promise<kj::String> readAllText(jsg::Lock& js, uint64_t limit) = 0;\n\n  virtual kj::Maybe<uint64_t> tryGetLength(StreamEncoding encoding) = 0;\n\n  virtual void setup(jsg::Lock& js,\n      jsg::Optional<UnderlyingSource> maybeUnderlyingSource,\n      jsg::Optional<StreamQueuingStrategy> maybeQueuingStrategy) {}\n\n  virtual kj::Promise<DeferredProxy<void>> pumpTo(\n      jsg::Lock& js, kj::Own<WritableStreamSink> sink, bool end) = 0;\n\n  // If pumpTo() pumps to a system stream, what is the best encoding for that system stream to\n  // use? This is just a hint.\n  virtual StreamEncoding getPreferredEncoding() {\n    return StreamEncoding::IDENTITY;\n  }\n\n  virtual kj::Own<ReadableStreamController> detach(jsg::Lock& js, bool ignoreDisturbed) = 0;\n\n  // Used by sockets to signal that the ReadableStream shouldn't allow reads due to pending\n  // closure.\n  virtual void setPendingClosure() = 0;\n\n  virtual kj::StringPtr jsgGetMemoryName() const = 0;\n  virtual size_t jsgGetMemorySelfSize() const = 0;\n  virtual void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const = 0;\n};\n\nkj::Own<ReadableStreamController> newReadableStreamJsController();\nkj::Own<ReadableStreamController> newReadableStreamInternalController(\n    IoContext& ioContext, kj::Own<ReadableStreamSource> source);\n\n// A WritableStreamController provides the underlying implementation for a WritableStream.\n// We will generally have two implementations:\n//  * WritableStreamDefaultController\n//  * WritableStreamInternalController\n//\n// The WritableStreamDefaultController is defined by the streams standard and directs all\n// of the stream data to JavaScript functions provided by user code.\n//\n// The WritableStreamInternalController is Workers runtime specific and provides a bridge\n// to the existing WritableStreamSink API.\n//\n// The WritableStreamController instance is meant to be a private member of the WritableStream,\n// e.g.\n//   class WritableStream {\n//   public:\n//     // ...\n//   private:\n//     WritableStreamController controller;\n//   };\n//\n// As such, it exists within the V8 heap  (it's allocated directly as a member of the\n// WritableStream) and will always execute within the V8 isolate lock.\n//\n// The methods here return jsg::Promise rather than kj::Promise because the controller\n// operations here do not always require passing through the kj mechanisms or kj event loop.\n// Likewise, we do not make use of kj::Exception in these interfaces because the stream\n// standard dictates that streams can be canceled/aborted/errored using any arbitrary JavaScript\n// value, not just Errors.\nclass WritableStreamController {\n public:\n  // The WritableStreamController::Writer interface is a base for all WritableStream writer\n  // implementations and is used solely as a means of attaching a Writer implementation to\n  // the internal state of the controller. See the WritableStream::*Writer classes for the\n  // full Writer API.\n  class Writer {\n   public:\n    // When a Writer is locked to a controller, the controller will attach itself to the writer,\n    // passing along the closed and ready promises that will be used to communicate state to the\n    // user code.\n    //\n    // The controller is guaranteed to either outlive the Writer or will detach the Writer so the\n    // WritableStreamController& reference should always remain valid.\n    virtual void attach(jsg::Lock& js,\n        WritableStreamController& controller,\n        jsg::Promise<void> closedPromise,\n        jsg::Promise<void> readyPromise) = 0;\n\n    // When a Writer lock is released, the controller will signal to the writer that is has been\n    // detached.\n    virtual void detach() = 0;\n\n    // The ready promise can be replaced whenever backpressure is signaled by the underlying\n    // controller.\n    virtual void replaceReadyPromise(jsg::Lock& js, jsg::Promise<void> readyPromise) = 0;\n  };\n\n  struct PendingAbort {\n    kj::Maybe<jsg::Promise<void>::Resolver> resolver;\n    jsg::Promise<void> promise;\n    jsg::Value reason;\n    bool reject = false;\n\n    PendingAbort(jsg::Lock& js,\n        jsg::PromiseResolverPair<void> prp,\n        v8::Local<v8::Value> reason,\n        bool reject);\n\n    PendingAbort(jsg::Lock& js, v8::Local<v8::Value> reason, bool reject);\n\n    void complete(jsg::Lock& js);\n\n    void fail(jsg::Lock& js, v8::Local<v8::Value> reason);\n\n    inline jsg::Promise<void> whenResolved(jsg::Lock& js) {\n      return promise.whenResolved(js);\n    }\n\n    inline jsg::Promise<void> whenResolved(auto&& func) {\n      return promise.whenResolved(kj::fwd(func));\n    }\n\n    inline jsg::Promise<void> whenResolved(auto&& func, auto&& errFunc) {\n      return promise.whenResolved(kj::fwd(func), kj::fwd(errFunc));\n    }\n\n    void visitForGc(jsg::GcVisitor& visitor) {\n      visitor.visit(resolver, promise, reason);\n    }\n\n    static kj::Maybe<kj::Own<PendingAbort>> dequeue(\n        kj::Maybe<kj::Own<PendingAbort>>& maybePendingAbort);\n\n    JSG_MEMORY_INFO(PendingAbort) {\n      tracker.trackField(\"resolver\", resolver);\n      tracker.trackField(\"promise\", promise);\n      tracker.trackField(\"reason\", reason);\n    }\n  };\n\n  virtual ~WritableStreamController() noexcept(false) {}\n\n  virtual void setOwnerRef(WritableStream& stream) = 0;\n\n  virtual jsg::Ref<WritableStream> addRef() = 0;\n\n  // The controller implementation will determine what kind of JavaScript data\n  // it is capable of writing, returning a rejected promise if the written\n  // data type is not supported.\n  virtual jsg::Promise<void> write(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> value) = 0;\n\n  // Indicates that no additional data will be written to the controller. All\n  // existing pending writes should be allowed to complete.\n  virtual jsg::Promise<void> close(jsg::Lock& js, bool markAsHandled = false) = 0;\n\n  // Waits for pending data to be written. The returned promise is resolved when all pending writes\n  // have completed.\n  virtual jsg::Promise<void> flush(jsg::Lock& js, bool markAsHandled = false) = 0;\n\n  // Immediately interrupts existing pending writes and errors the stream.\n  virtual jsg::Promise<void> abort(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> reason) = 0;\n\n  // The tryPipeFrom attempts to establish a data pipe where source's data\n  // is delivered to this WritableStreamController as efficiently as possible.\n  virtual kj::Maybe<jsg::Promise<void>> tryPipeFrom(\n      jsg::Lock& js, jsg::Ref<ReadableStream> source, PipeToOptions options) = 0;\n\n  // Only byte-oriented WritableStreamController implementations will have a WritableStreamSink\n  // that can be detached using removeSink. A nullptr should be returned by any controller that\n  // does not support removing the sink. After the WritableStreamSink has been released, all other\n  // methods on the controller should fail with an exception as the WritableStreamSink should be\n  // the only way to interact with the underlying sink.\n  virtual kj::Maybe<kj::Own<WritableStreamSink>> removeSink(jsg::Lock& js) = 0;\n\n  // Detaches the WritableStreamController from its underlying implementation, leaving the\n  // writable stream locked and in a state where no further writes can be made.\n  virtual void detach(jsg::Lock& js) = 0;\n\n  virtual kj::Maybe<int> getDesiredSize() = 0;\n\n  // True if a Writer has been locked to this controller.\n  virtual bool isLockedToWriter() const = 0;\n\n  // Locks this controller to the given writer, returning true if the lock was successful, or false\n  // if the controller was already locked.\n  virtual bool lockWriter(jsg::Lock& js, Writer& writer) = 0;\n\n  // Removes the lock and releases the writer from this controller.\n  // maybeJs will be nullptr when the isolate lock is not available.\n  // If maybeJs is set, the writer's closed and ready promises will be resolved.\n  virtual void releaseWriter(Writer& writer, kj::Maybe<jsg::Lock&> maybeJs) = 0;\n\n  virtual kj::Maybe<v8::Local<v8::Value>> isErroring(jsg::Lock& js) = 0;\n\n  virtual void visitForGc(jsg::GcVisitor& visitor) {};\n\n  virtual void setup(jsg::Lock& js,\n      jsg::Optional<UnderlyingSink> underlyingSink,\n      jsg::Optional<StreamQueuingStrategy> queuingStrategy) {}\n\n  virtual bool isClosedOrClosing() = 0;\n  virtual bool isErrored() = 0;\n\n  // True is this controller requires ArrayBuffer(Views) to be written to it.\n  virtual bool isByteOriented() const = 0;\n\n  // Used by sockets to signal that the WritableStream shouldn't allow writes due to pending\n  // closure.\n  virtual void setPendingClosure() = 0;\n\n  // For memory tracking\n  virtual kj::StringPtr jsgGetMemoryName() const = 0;\n  virtual size_t jsgGetMemorySelfSize() const = 0;\n  virtual void jsgGetMemoryInfo(jsg::MemoryTracker& info) const = 0;\n};\n\nkj::Own<WritableStreamController> newWritableStreamJsController();\nkj::Own<WritableStreamController> newWritableStreamInternalController(IoContext& ioContext,\n    kj::Own<WritableStreamSink> sink,\n    kj::Maybe<kj::Own<ByteStreamObserver>> observer,\n    kj::Maybe<uint64_t> maybeHighWaterMark = kj::none,\n    kj::Maybe<jsg::Promise<void>> maybeClosureWaitable = kj::none);\n\nstruct Unlocked {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"unlocked\"_kj;\n};\nstruct Locked {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"locked\"_kj;\n};\n\n// When a reader is locked to a ReadableStream, a ReaderLock instance\n// is used internally to represent the locked state in the ReadableStreamController.\nclass ReaderLocked {\n public:\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"reader-locked\"_kj;\n  ReaderLocked(ReadableStreamController::Reader& reader,\n      jsg::Promise<void>::Resolver closedFulfiller,\n      kj::Maybe<IoOwn<kj::Canceler>> canceler = kj::none)\n      : reader(reader),\n        closedFulfiller(kj::mv(closedFulfiller)),\n        canceler(kj::mv(canceler)) {}\n\n  ReaderLocked(ReaderLocked&&) = default;\n  ~ReaderLocked() noexcept(false) {\n    KJ_IF_SOME(r, reader) {\n      r.detach();\n    }\n  }\n  KJ_DISALLOW_COPY(ReaderLocked);\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(closedFulfiller);\n  }\n\n  ReadableStreamController::Reader& getReader() {\n    return KJ_ASSERT_NONNULL(reader);\n  }\n\n  kj::Maybe<jsg::Promise<void>::Resolver>& getClosedFulfiller() {\n    return closedFulfiller;\n  }\n\n  kj::Maybe<IoOwn<kj::Canceler>>& getCanceler() {\n    return canceler;\n  }\n\n  void clear() {\n    reader = kj::none;\n    closedFulfiller = kj::none;\n    canceler = kj::none;\n  }\n\n  JSG_MEMORY_INFO(ReaderLocked) {\n    tracker.trackField(\"closedFulfiller\", closedFulfiller);\n    tracker.trackFieldWithSize(\"IoOwn<kj::Canceler>\", sizeof(IoOwn<kj::Canceler>));\n  }\n\n private:\n  kj::Maybe<ReadableStreamController::Reader&> reader;\n  kj::Maybe<jsg::Promise<void>::Resolver> closedFulfiller;\n  kj::Maybe<IoOwn<kj::Canceler>> canceler;\n};\n\n// When a writer is locked to a WritableStream, a WriterLock instance\n// is used internally to represent the locked state in the WritableStreamController.\nclass WriterLocked {\n public:\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"writer-locked\"_kj;\n  WriterLocked(WritableStreamController::Writer& writer,\n      jsg::Promise<void>::Resolver closedFulfiller,\n      kj::Maybe<jsg::Promise<void>::Resolver> readyFulfiller = kj::none)\n      : writer(writer),\n        closedFulfiller(kj::mv(closedFulfiller)),\n        readyFulfiller(kj::mv(readyFulfiller)) {}\n\n  WriterLocked(WriterLocked&&) = default;\n  ~WriterLocked() noexcept(false) {\n    KJ_IF_SOME(w, writer) {\n      w.detach();\n    }\n  }\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(closedFulfiller, readyFulfiller);\n  }\n\n  WritableStreamController::Writer& getWriter() {\n    return KJ_ASSERT_NONNULL(writer);\n  }\n\n  kj::Maybe<jsg::Promise<void>::Resolver>& getClosedFulfiller() {\n    return closedFulfiller;\n  }\n\n  kj::Maybe<jsg::Promise<void>::Resolver>& getReadyFulfiller() {\n    return readyFulfiller;\n  }\n\n  void setReadyFulfiller(jsg::Lock& js, jsg::PromiseResolverPair<void>& pair) {\n    KJ_IF_SOME(w, writer) {\n      readyFulfiller = kj::mv(pair.resolver);\n      w.replaceReadyPromise(js, kj::mv(pair.promise));\n    }\n  }\n\n  void clear() {\n    writer = kj::none;\n    closedFulfiller = kj::none;\n    readyFulfiller = kj::none;\n  }\n\n  JSG_MEMORY_INFO(WriterLocked) {\n    tracker.trackField(\"closedFulfiller\", closedFulfiller);\n    tracker.trackField(\"readyFulfiller\", readyFulfiller);\n  }\n\n private:\n  kj::Maybe<WritableStreamController::Writer&> writer;\n  kj::Maybe<jsg::Promise<void>::Resolver> closedFulfiller;\n  kj::Maybe<jsg::Promise<void>::Resolver> readyFulfiller;\n};\n\ntemplate <typename T>\nvoid maybeResolvePromise(\n    jsg::Lock& js, kj::Maybe<typename jsg::Promise<T>::Resolver>& maybeResolver, T&& t) {\n  KJ_IF_SOME(resolver, maybeResolver) {\n    resolver.resolve(js, kj::fwd<T>(t));\n    maybeResolver = nullptr;\n  }\n}\n\ninline void maybeResolvePromise(\n    jsg::Lock& js, kj::Maybe<jsg::Promise<void>::Resolver>& maybeResolver) {\n  KJ_IF_SOME(resolver, maybeResolver) {\n    resolver.resolve(js);\n    maybeResolver = kj::none;\n  }\n}\n\ntemplate <typename T>\nvoid maybeRejectPromise(jsg::Lock& js,\n    kj::Maybe<typename jsg::Promise<T>::Resolver>& maybeResolver,\n    v8::Local<v8::Value> reason) {\n  KJ_IF_SOME(resolver, maybeResolver) {\n    resolver.reject(js, reason);\n    maybeResolver = kj::none;\n  }\n}\n\ntemplate <typename T>\njsg::Promise<T> rejectedMaybeHandledPromise(\n    jsg::Lock& js, v8::Local<v8::Value> reason, bool handled) {\n  auto prp = js.newPromiseAndResolver<T>();\n  if (handled) {\n    prp.promise.markAsHandled(js);\n  }\n  prp.resolver.reject(js, reason);\n  return kj::mv(prp.promise);\n}\n\ninline kj::Maybe<IoContext&> tryGetIoContext() {\n  // TODO(cleanup): This function is obsolete; callers should just call IoContext::tryCurrent()\n  return IoContext::tryCurrent();\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/compression.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"compression.h\"\n\n#include \"nbytes.h\"\n\n#include <workerd/api/system-streams.h>\n#include <workerd/io/features.h>\n#include <workerd/util/autogate.h>\n#include <workerd/util/ring-buffer.h>\n#include <workerd/util/state-machine.h>\n\nnamespace workerd::api {\nCompressionAllocator::CompressionAllocator(\n    kj::Arc<const jsg::ExternalMemoryTarget>&& externalMemoryTarget)\n    : externalMemoryTarget(kj::mv(externalMemoryTarget)) {}\n\nvoid CompressionAllocator::configure(z_stream* stream) {\n  stream->zalloc = AllocForZlib;\n  stream->zfree = FreeForZlib;\n  stream->opaque = this;\n}\n\nvoid* CompressionAllocator::AllocForZlib(void* data, uInt items, uInt size) {\n  size_t real_size =\n      nbytes::MultiplyWithOverflowCheck(static_cast<size_t>(items), static_cast<size_t>(size));\n  return AllocForBrotli(data, real_size);\n}\n\nvoid* CompressionAllocator::AllocForBrotli(void* opaque, size_t size) {\n  auto* allocator = static_cast<CompressionAllocator*>(opaque);\n  auto data = kj::heapArray<kj::byte>(size);\n  auto begin = data.begin();\n\n  allocator->allocations.insert(begin,\n      {.data = kj::mv(data),\n        .memoryAdjustment = allocator->externalMemoryTarget->getAdjustment(size)});\n  return begin;\n}\n\nvoid CompressionAllocator::FreeForZlib(void* opaque, void* pointer) {\n  if (KJ_UNLIKELY(pointer == nullptr)) return;\n  auto* allocator = static_cast<CompressionAllocator*>(opaque);\n  // No need to destroy memoryAdjustment here.\n  // Dropping the allocation from the hashmap will defer the adjustment\n  // until the isolate lock is held.\n  JSG_REQUIRE(allocator->allocations.erase(pointer), Error, \"Zlib allocation should exist\"_kj);\n}\n\nnamespace {\n\nclass Context {\n public:\n  enum class Mode {\n    COMPRESS,\n    DECOMPRESS,\n  };\n\n  enum class ContextFlags {\n    NONE,\n    STRICT,\n  };\n\n  struct Result {\n    bool success = false;\n    kj::ArrayPtr<const byte> buffer;\n  };\n\n  explicit Context(Mode mode,\n      kj::StringPtr format,\n      ContextFlags flags,\n      kj::Arc<const jsg::ExternalMemoryTarget>&& externalMemoryTarget)\n      : allocator(kj::mv(externalMemoryTarget)),\n        mode(mode),\n        strictCompression(flags)\n\n  {\n    // Configure allocator before any stream operations.\n    allocator.configure(&ctx);\n    int result = Z_OK;\n    switch (mode) {\n      case Mode::COMPRESS:\n        result = deflateInit2(&ctx, Z_DEFAULT_COMPRESSION, Z_DEFLATED, getWindowBits(format),\n            8,  // memLevel = 8 is the default\n            Z_DEFAULT_STRATEGY);\n        break;\n      case Mode::DECOMPRESS:\n        result = inflateInit2(&ctx, getWindowBits(format));\n        break;\n      default:\n        KJ_UNREACHABLE;\n    }\n    JSG_REQUIRE(result == Z_OK, Error, \"Failed to initialize compression context.\"_kj);\n  }\n\n  ~Context() noexcept(false) {\n    switch (mode) {\n      case Mode::COMPRESS:\n        deflateEnd(&ctx);\n        break;\n      case Mode::DECOMPRESS:\n        inflateEnd(&ctx);\n        break;\n    }\n  }\n\n  KJ_DISALLOW_COPY_AND_MOVE(Context);\n\n  void setInput(const void* in, size_t size) {\n    ctx.next_in = const_cast<byte*>(reinterpret_cast<const byte*>(in));\n    ctx.avail_in = size;\n  }\n\n  Result pumpOnce(int flush) {\n    ctx.next_out = buffer;\n    ctx.avail_out = sizeof(buffer);\n\n    int result = Z_OK;\n\n    switch (mode) {\n      case Mode::COMPRESS:\n        result = deflate(&ctx, flush);\n        JSG_REQUIRE(result == Z_OK || result == Z_BUF_ERROR || result == Z_STREAM_END, TypeError,\n            \"Compression failed.\");\n        break;\n      case Mode::DECOMPRESS:\n        result = inflate(&ctx, flush);\n        JSG_REQUIRE(result == Z_OK || result == Z_BUF_ERROR || result == Z_STREAM_END, TypeError,\n            \"Decompression failed.\");\n\n        if (strictCompression == ContextFlags::STRICT) {\n          // The spec requires that a TypeError is produced if there is trailing data after the end\n          // of the compression stream.\n          JSG_REQUIRE(!(result == Z_STREAM_END && ctx.avail_in > 0), TypeError,\n              \"Trailing bytes after end of compressed data\");\n          // Same applies to closing a stream before the complete decompressed data is available.\n          JSG_REQUIRE(\n              !(flush == Z_FINISH && result == Z_BUF_ERROR && ctx.avail_out == sizeof(buffer)),\n              TypeError, \"Called close() on a decompression stream with incomplete data\");\n        }\n        break;\n      default:\n        KJ_UNREACHABLE;\n    }\n\n    return Result{\n      .success = result == Z_OK,\n      .buffer = kj::arrayPtr(buffer, sizeof(buffer) - ctx.avail_out),\n    };\n  }\n\n protected:\n  CompressionAllocator allocator;\n\n private:\n  static int getWindowBits(kj::StringPtr format) {\n    // We use a windowBits value of 15 combined with the magic value\n    // for the compression format type. For gzip, the magic value is\n    // 16, so the value returned is 15 + 16. For deflate, the magic\n    // value is 15. For raw deflate (i.e. deflate without a zlib header)\n    // the negative windowBits value is used, so -15. See the comments for\n    // deflateInit2() in zlib.h for details.\n    static constexpr auto GZIP = 16;\n    static constexpr auto DEFLATE = 15;\n    static constexpr auto DEFLATE_RAW = -15;\n    if (format == \"gzip\")\n      return DEFLATE + GZIP;\n    else if (format == \"deflate\")\n      return DEFLATE;\n    else if (format == \"deflate-raw\")\n      return DEFLATE_RAW;\n    KJ_UNREACHABLE;\n  }\n\n  Mode mode;\n  z_stream ctx = {};\n  kj::byte buffer[16384];\n\n  // For the eponymous compatibility flag\n  ContextFlags strictCompression;\n};\n\n// Buffer class based on std::vector that erases data that has been read from it lazily to avoid\n// excessive copying when reading a larger amount of buffered data in small chunks. valid_size_ is\n// used to track the amount of data that has not been read back yet.\nclass LazyBuffer {\n public:\n  LazyBuffer(): valid_size_(0) {}\n\n  // Return a chunk of data and mark it as invalid. The returned chunk remains valid until data is\n  // shifted, cleared or destructor is called. maybeShift() should be called after the returned data\n  // has been processed.\n  kj::ArrayPtr<byte> take(size_t read_size) {\n    KJ_ASSERT(read_size <= valid_size_);\n    kj::ArrayPtr<byte> chunk = kj::arrayPtr(&output[output.size() - valid_size_], read_size);\n    valid_size_ -= read_size;\n    return chunk;\n  }\n\n  // Shift the output only if doing so results in reducing vector size by at least 1 KiB and 1/8 of\n  // its size to avoid copying for small reads.\n  void maybeShift() {\n    size_t unusedSpace = output.size() - valid_size_;\n    if (unusedSpace >= 1024 && unusedSpace >= (output.size() >> 3)) {\n      // Shifting buffer to erase data that has already been read. valid_size_ remains the same.\n      memmove(output.begin(), output.begin() + unusedSpace, valid_size_);\n      output.truncate(valid_size_);\n    }\n  }\n\n  void write(kj::ArrayPtr<const byte> chunk) {\n    output.addAll(chunk);\n    valid_size_ += chunk.size();\n  }\n\n  void clear() {\n    output.clear();\n    valid_size_ = 0;\n  }\n\n  // For convenience, provide the size of the valid data that has not been read back yet. This may\n  // be smaller than the size of the internal vector, which is not relevant for the stream\n  // implementation.\n  size_t size() {\n    return valid_size_;\n  }\n\n  // As with size(), the buffer is considered empty if there is no valid data remaining.\n  size_t empty() {\n    return valid_size_ == 0;\n  }\n\n private:\n  kj::Vector<kj::byte> output;\n  size_t valid_size_;\n};\n\n// Because we have to use an autogate to switch things over to the new state manager, we need\n// to separate out a common base class for the compression stream internal state and separate\n// two separate impls that differ only in how they manage state. Once the autogate is removed,\n// we can delete the first impl class and merge everything back together.\ntemplate <Context::Mode mode>\nclass CompressionStreamBase: public kj::Refcounted,\n                             public kj::AsyncInputStream,\n                             public capnp::ExplicitEndOutputStream {\n public:\n  explicit CompressionStreamBase(kj::String format,\n      Context::ContextFlags flags,\n      kj::Arc<const jsg::ExternalMemoryTarget>&& externalMemoryTarget)\n      : context(mode, format, flags, kj::mv(externalMemoryTarget)) {}\n\n  // WritableStreamSink implementation ---------------------------------------------------\n\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override final {\n    requireActive(\"Write after close\");\n    context.setInput(buffer.begin(), buffer.size());\n    writeInternal(Z_NO_FLUSH);\n    co_return;\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const kj::byte>> pieces) override final {\n    // We check state here so that we catch errors even if pieces is empty.\n    requireActive(\"Write after close\");\n    for (auto piece: pieces) {\n      co_await write(piece);\n    }\n    co_return;\n  }\n\n  kj::Promise<void> end() override final {\n    transitionToEnded();\n    writeInternal(Z_FINISH);\n    co_return;\n  }\n\n  kj::Promise<void> whenWriteDisconnected() override final {\n    return kj::NEVER_DONE;\n  }\n\n  void abortWrite(kj::Exception&& reason) override final {\n    cancelInternal(kj::mv(reason));\n  }\n\n  // AsyncInputStream implementation -----------------------------------------------------\n\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override final {\n    KJ_ASSERT(minBytes <= maxBytes);\n    // Re-throw any stored exception\n    throwIfException();\n    // If stream has ended normally and no buffered data, return EOF\n    if (isInTerminalState() && output.empty()) {\n      co_return static_cast<size_t>(0);\n    }\n    // Active or terminal with data remaining\n    co_return co_await tryReadInternal(\n        kj::arrayPtr(reinterpret_cast<kj::byte*>(buffer), maxBytes), minBytes);\n  }\n\n protected:\n  virtual void requireActive(kj::StringPtr errorMessage) = 0;\n  virtual void transitionToEnded() = 0;\n  virtual void transitionToErrored(kj::Exception&& reason) = 0;\n  virtual void throwIfException() = 0;\n  virtual bool isInTerminalState() = 0;\n\n private:\n  struct PendingRead {\n    kj::ArrayPtr<kj::byte> buffer;\n    size_t minBytes = 1;\n    size_t filled = 0;\n    kj::Own<kj::PromiseFulfiller<size_t>> promise;\n  };\n\n  void cancelInternal(kj::Exception reason) {\n    output.clear();\n\n    while (!pendingReads.empty()) {\n      auto pending = kj::mv(pendingReads.front());\n      pendingReads.pop_front();\n      if (pending.promise->isWaiting()) {\n        pending.promise->reject(kj::cp(reason));\n      }\n    }\n\n    canceler.cancel(kj::cp(reason));\n    transitionToErrored(kj::mv(reason));\n  }\n\n  kj::Promise<size_t> tryReadInternal(kj::ArrayPtr<kj::byte> dest, size_t minBytes) {\n    const auto copyIntoBuffer = [this](kj::ArrayPtr<kj::byte> dest) {\n      auto maxBytesToCopy = kj::min(dest.size(), output.size());\n      dest.first(maxBytesToCopy).copyFrom(output.take(maxBytesToCopy));\n      output.maybeShift();\n      return maxBytesToCopy;\n    };\n\n    // If the output currently contains >= minBytes, then we'll fulfill\n    // the read immediately, removing as many bytes as possible from the\n    // output queue.\n    // If we reached the end (terminal state), resolve the read immediately\n    // as well, since no new data is expected.\n    if (output.size() >= minBytes || isInTerminalState()) {\n      co_return copyIntoBuffer(dest);\n    }\n\n    // Otherwise, create a pending read.\n    auto promise = kj::newPromiseAndFulfiller<size_t>();\n    auto pendingRead = PendingRead{\n      .buffer = dest,\n      .minBytes = minBytes,\n      .filled = 0,\n      .promise = kj::mv(promise.fulfiller),\n    };\n\n    // If there are any bytes queued, copy as much as possible into the buffer.\n    if (output.size() > 0) {\n      pendingRead.filled = copyIntoBuffer(dest);\n    }\n\n    pendingReads.push_back(kj::mv(pendingRead));\n\n    co_return co_await canceler.wrap(kj::mv(promise.promise));\n  }\n\n  void writeInternal(int flush) {\n    // TODO(later): This does not yet implement any backpressure. A caller can keep calling\n    // write without reading, which will continue to fill the internal buffer.\n    KJ_ASSERT(flush == Z_FINISH || !isInTerminalState());\n    Context::Result result;\n\n    while (true) {\n      KJ_IF_SOME(exception, kj::runCatchingExceptions([this, flush, &result]() {\n        result = context.pumpOnce(flush);\n      })) {\n        cancelInternal(kj::cp(exception));\n        kj::throwFatalException(kj::mv(exception));\n      }\n\n      if (result.buffer.size() == 0) {\n        if (result.success) {\n          // No output produced but input data has been processed based on zlib return code, call\n          // pumpOnce again.\n          continue;\n        }\n        maybeFulfillRead();\n        return;\n      }\n\n      // Output has been produced, copy it to result buffer and continue loop to call pumpOnce\n      // again.\n      output.write(result.buffer);\n    }\n    KJ_UNREACHABLE;\n  }\n\n  // Fulfill as many pending reads as we can from the output buffer.\n  void maybeFulfillRead() {\n    // If there are pending reads and data to be read, we'll loop through\n    // the pending reads and fulfill them as much as possible.\n    while (!pendingReads.empty() && output.size() > 0) {\n      auto& pending = pendingReads.front();\n\n      if (!pending.promise->isWaiting()) {\n        // The pending read was canceled!\n        // Importantly, the pending.buffer is no longer valid here so we definitely want to\n        // make sure we don't try to write anything to it!\n\n        // If the pending read was already partially fulfilled, then we have a problem!\n        // We can't just cancel and continue because the partially read data will be lost\n        // so we need to report an error here and error the stream.\n        if (pending.filled > 0) {\n          auto ex = JSG_KJ_EXCEPTION(FAILED, Error, \"A partially fulfilled read was canceled.\");\n          cancelInternal(kj::cp(ex));\n          kj::throwFatalException(kj::mv(ex));\n        }\n\n        auto ex = JSG_KJ_EXCEPTION(FAILED, Error, \"The pending read was canceled.\");\n        cancelInternal(kj::cp(ex));\n        kj::throwFatalException(kj::mv(ex));\n      }\n\n      // The pending read is still viable so determine how much we can copy in.\n      auto amountToCopy = kj::min(pending.buffer.size() - pending.filled, output.size());\n      kj::ArrayPtr<byte> chunk = output.take(amountToCopy);\n      pending.buffer.slice(pending.filled, pending.filled + amountToCopy).copyFrom(chunk);\n      pending.filled += amountToCopy;\n      output.maybeShift();\n\n      // If we've met the minimum bytes requirement for the pending read, fulfill\n      // the read promise.\n      if (pending.filled >= pending.minBytes) {\n        auto p = kj::mv(pending);\n        pendingReads.pop_front();\n        p.promise->fulfill(kj::mv(p.filled));\n        continue;\n      }\n\n      // If we reached this point in the loop, remaining must be 0 so that we\n      // don't keep iterating through on the same pending read.\n      KJ_ASSERT(output.empty());\n    }\n\n    if (isInTerminalState() && !pendingReads.empty()) {\n      // We are ended and we have pending reads. Because of the loop above,\n      // one of either pendingReads or output must be empty, so if we got this\n      // far, output.empty() must be true. Let's check.\n      KJ_ASSERT(output.empty());\n      // We need to flush any remaining reads.\n      while (!pendingReads.empty()) {\n        auto pending = kj::mv(pendingReads.front());\n        pendingReads.pop_front();\n        if (pending.promise->isWaiting()) {\n          // Fulfill the pending read promise only if it hasn't already been canceled.\n          pending.promise->fulfill(kj::mv(pending.filled));\n        }\n      }\n    }\n  }\n\n  Context context;\n\n  kj::Canceler canceler;\n  LazyBuffer output;\n  RingBuffer<PendingRead, 8> pendingReads;\n};\n\ntemplate <Context::Mode mode>\nclass CompressionStreamImpl final: public CompressionStreamBase<mode> {\n public:\n  explicit CompressionStreamImpl(kj::String format,\n      Context::ContextFlags flags,\n      kj::Arc<const jsg::ExternalMemoryTarget>&& externalMemoryTarget)\n      : CompressionStreamBase<mode>(kj::mv(format), flags, kj::mv(externalMemoryTarget)),\n        state(decltype(state)::template create<Open>()) {}\n\n protected:\n  void requireActive(kj::StringPtr errorMessage) override {\n    KJ_IF_SOME(exception, state.tryGetErrorUnsafe()) {\n      kj::throwFatalException(kj::cp(exception));\n    }\n    // isActive() returns true only if in Open state (the ActiveState)\n    JSG_REQUIRE(state.isActive(), Error, errorMessage);\n  }\n\n  void transitionToEnded() override {\n    // If already in a terminal state (Ended or Exception), this is a no-op.\n    // This matches the V1 behavior where calling end() multiple times was allowed.\n    if (state.isTerminal()) return;\n    auto result = state.template transitionFromTo<Open, Ended>();\n    KJ_REQUIRE(result != kj::none, \"Stream already ended or errored\");\n  }\n\n  void transitionToErrored(kj::Exception&& reason) override {\n    // Use forceTransitionTo because cancelInternal may be called when already\n    // in an error state (e.g., from writeInternal error handling).\n    state.template forceTransitionTo<kj::Exception>(kj::mv(reason));\n  }\n\n  void throwIfException() override {\n    KJ_IF_SOME(exception, state.tryGetErrorUnsafe()) {\n      kj::throwFatalException(kj::cp(exception));\n    }\n  }\n\n  virtual bool isInTerminalState() override {\n    return state.isTerminal();\n  }\n\n private:\n  struct Ended {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"ended\"_kj;\n  };\n  struct Open {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"open\"_kj;\n  };\n\n  // State machine for tracking compression stream lifecycle:\n  //   Open -> Ended (normal close via end())\n  //   Open -> kj::Exception (error via abortWrite())\n  // Ended is terminal, kj::Exception is implicitly terminal via ErrorState.\n  StateMachine<TerminalStates<Ended>,\n      ErrorState<kj::Exception>,\n      ActiveState<Open>,\n      Open,\n      Ended,\n      kj::Exception>\n      state;\n};\n\n// Adapter to bridge CompressionStreamImpl (which implements AsyncInputStream and\n// ExplicitEndOutputStream) to the ReadableStreamSource/WritableStreamSink interfaces.\n// TODO(soon): This class is intended to be replaced by the new ReadableSource/WritableSink\n// interfaces once fully implemented. We will need an adapter that knows how to handle both\n// sides of the stream once fully implemented. The current implementation in system-streams.c++\n// implements separate adapters for each side that are not aware of each other, making it\n// unsuitable for this specific case.\ntemplate <Context::Mode mode>\nclass CompressionStreamAdapter final: public kj::Refcounted,\n                                      public ReadableStreamSource,\n                                      public WritableStreamSink {\n public:\n  explicit CompressionStreamAdapter(kj::Rc<CompressionStreamBase<mode>> impl)\n      : impl(kj::mv(impl)),\n        ioContext(IoContext::current()) {}\n\n  // ReadableStreamSource implementation\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    return impl->tryRead(buffer, minBytes, maxBytes).attach(ioContext.registerPendingEvent());\n  }\n\n  void cancel(kj::Exception reason) override {\n    // AsyncInputStream doesn't have cancel, but we can abort the write side\n    impl->abortWrite(kj::mv(reason));\n  }\n\n  // WritableStreamSink implementation\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override {\n    return impl->write(buffer).attach(ioContext.registerPendingEvent());\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {\n    return impl->write(pieces).attach(ioContext.registerPendingEvent());\n  }\n\n  kj::Promise<void> end() override {\n    return impl->end().attach(ioContext.registerPendingEvent());\n  }\n\n  void abort(kj::Exception reason) override {\n    impl->abortWrite(kj::mv(reason));\n  }\n\n private:\n  kj::Rc<CompressionStreamBase<mode>> impl;\n  IoContext& ioContext;\n};\n\nkj::Rc<CompressionStreamBase<Context::Mode::COMPRESS>> createCompressionStreamImpl(\n    kj::String format,\n    Context::ContextFlags flags,\n    kj::Arc<const jsg::ExternalMemoryTarget>&& externalMemoryTarget) {\n  return kj::rc<CompressionStreamImpl<Context::Mode::COMPRESS>>(\n      kj::mv(format), flags, kj::mv(externalMemoryTarget));\n}\n\nkj::Rc<CompressionStreamBase<Context::Mode::DECOMPRESS>> createDecompressionStreamImpl(\n    kj::String format,\n    Context::ContextFlags flags,\n    kj::Arc<const jsg::ExternalMemoryTarget>&& externalMemoryTarget) {\n  return kj::rc<CompressionStreamImpl<Context::Mode::DECOMPRESS>>(\n      kj::mv(format), flags, kj::mv(externalMemoryTarget));\n}\n\n}  // namespace\n\njsg::Ref<CompressionStream> CompressionStream::constructor(jsg::Lock& js, kj::String format) {\n  JSG_REQUIRE(format == \"deflate\" || format == \"gzip\" || format == \"deflate-raw\", TypeError,\n      \"The compression format must be either 'deflate', 'deflate-raw' or 'gzip'.\");\n\n  // TODO(cleanup): Once the autogate is removed, we can delete CompressionStreamImpl\n  kj::Rc<CompressionStreamBase<Context::Mode::COMPRESS>> impl = createCompressionStreamImpl(\n      kj::mv(format), Context::ContextFlags::NONE, js.getExternalMemoryTarget());\n\n  auto& ioContext = IoContext::current();\n\n  // Create a single adapter that implements both readable and writable sides\n  auto adapter = kj::refcounted<CompressionStreamAdapter<Context::Mode::COMPRESS>>(kj::mv(impl));\n  auto readableSide = kj::addRef(*adapter);\n  auto writableSide = kj::mv(adapter);\n\n  return js.alloc<CompressionStream>(js.alloc<ReadableStream>(ioContext, kj::mv(readableSide)),\n      js.alloc<WritableStream>(ioContext, kj::mv(writableSide),\n          ioContext.getMetrics().tryCreateWritableByteStreamObserver()));\n}\n\njsg::Ref<DecompressionStream> DecompressionStream::constructor(jsg::Lock& js, kj::String format) {\n  JSG_REQUIRE(format == \"deflate\" || format == \"gzip\" || format == \"deflate-raw\", TypeError,\n      \"The compression format must be either 'deflate', 'deflate-raw' or 'gzip'.\");\n\n  kj::Rc<CompressionStreamBase<Context::Mode::DECOMPRESS>> impl =\n      createDecompressionStreamImpl(kj::mv(format),\n          FeatureFlags::get(js).getStrictCompression() ? Context::ContextFlags::STRICT\n                                                       : Context::ContextFlags::NONE,\n          js.getExternalMemoryTarget());\n\n  auto& ioContext = IoContext::current();\n\n  // Create a single adapter that implements both readable and writable sides\n  auto adapter = kj::refcounted<CompressionStreamAdapter<Context::Mode::DECOMPRESS>>(kj::mv(impl));\n  auto readableSide = kj::addRef(*adapter);\n  auto writableSide = kj::mv(adapter);\n\n  return js.alloc<DecompressionStream>(js.alloc<ReadableStream>(ioContext, kj::mv(readableSide)),\n      js.alloc<WritableStream>(ioContext, kj::mv(writableSide),\n          ioContext.getMetrics().tryCreateWritableByteStreamObserver()));\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/compression.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/api/streams/transform.h>\n#include <workerd/jsg/jsg.h>\n\n#include <zlib.h>\n\nnamespace workerd::api {\n\n// A custom allocator to be used by the zlib and brotli libraries.\n// The allocator should not and can not safely hold a reference to the jsg::Lock\n// instance. Therefore, we lookup the current jsg::Lock instance from the\n// isolate pointer and use that to get the external memory adjustment.\nclass CompressionAllocator final {\n public:\n  CompressionAllocator(kj::Arc<const jsg::ExternalMemoryTarget>&& externalMemoryTarget);\n  void configure(z_stream* stream);\n\n  static void* AllocForZlib(void* data, uInt items, uInt size);\n  static void* AllocForBrotli(void* data, size_t size);\n  static void FreeForZlib(void* data, void* pointer);\n\n private:\n  struct Allocation {\n    kj::Array<kj::byte> data;\n    kj::Maybe<jsg::ExternalMemoryAdjustment> memoryAdjustment = kj::none;\n  };\n\n  kj::Arc<const jsg::ExternalMemoryTarget> externalMemoryTarget;\n  kj::HashMap<void*, Allocation> allocations;\n};\n\nclass CompressionStream: public TransformStream {\n public:\n  using TransformStream::TransformStream;\n\n  static jsg::Ref<CompressionStream> constructor(jsg::Lock& js, kj::String format);\n\n  JSG_RESOURCE_TYPE(CompressionStream) {\n    JSG_INHERIT(TransformStream);\n\n    JSG_TS_OVERRIDE(extends TransformStream<ArrayBuffer | ArrayBufferView, Uint8Array> { constructor(format\n                                 : \"gzip\" | \"deflate\" | \"deflate-raw\");\n    });\n  }\n};\n\nclass DecompressionStream: public TransformStream {\n public:\n  using TransformStream::TransformStream;\n\n  static jsg::Ref<DecompressionStream> constructor(jsg::Lock& js, kj::String format);\n\n  JSG_RESOURCE_TYPE(DecompressionStream) {\n    JSG_INHERIT(TransformStream);\n\n    JSG_TS_OVERRIDE(extends TransformStream<ArrayBuffer | ArrayBufferView, Uint8Array> { constructor(format\n                                 : \"gzip\" | \"deflate\" | \"deflate-raw\");\n    });\n  }\n};\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/encoding.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"encoding.h\"\n\n#include \"simdutf.h\"\n\n#include <workerd/api/encoding.h>\n#include <workerd/api/streams/standard.h>\n#include <workerd/io/features.h>\n#include <workerd/jsg/jsg.h>\n\n#include <v8.h>\n\n#include <kj/common.h>\n#include <kj/refcount.h>\n\nnamespace workerd::api {\n\nnamespace {\nconstexpr kj::byte REPLACEMENT_UTF8[] = {0xEF, 0xBF, 0xBD};\n\nstruct Holder: public kj::Refcounted {\n  kj::Maybe<char16_t> pending = kj::none;\n};\n}  // namespace\n\n// TextEncoderStream encodes a stream of JavaScript strings into UTF-8 bytes.\n//\n// WHATWG Encoding spec requirement (https://encoding.spec.whatwg.org/#interface-textencoderstream):\n// The encoder must encode unpaired UTF-16 surrogates as replacement characters.\n//\n// simdutf handles this for us, but we have to be careful of surrogate pairs\n//   (high surrogate, followed by low surrogate) split across chunk boundaries.\n//\n// We do this with the pending field:\n//   holder->pending = kj::none    -> No pending high surrogate from previous chunk\n//   holder->pending = char16_t    -> High surrogate waiting for a matching low surrogate\n//\n// Ref: https://github.com/web-platform-tests/wpt/blob/master/encoding/streams/encode-utf8.any.js\njsg::Ref<TextEncoderStream> TextEncoderStream::constructor(jsg::Lock& js) {\n  auto state = kj::rc<Holder>();\n\n  auto transform = [holder = state.addRef()](jsg::Lock& js, v8::Local<v8::Value> chunk,\n                       jsg::Ref<TransformStreamDefaultController> controller) mutable {\n    auto str = jsg::check(chunk->ToString(js.v8Context()));\n    size_t length = str->Length();\n    if (length == 0) return js.resolvedPromise();\n\n    // Allocate buffer: reserve slot 0 for pending surrogate if we have one\n    size_t prefix = (holder->pending == kj::none) ? 0 : 1;\n    size_t end = prefix + length;\n    auto buf = kj::heapArray<char16_t>(end);\n    str->WriteV2(js.v8Isolate, 0, length, reinterpret_cast<uint16_t*>(buf.begin() + prefix));\n\n    KJ_IF_SOME(lead, holder->pending) {\n      buf.begin()[0] = lead;\n      holder->pending = kj::none;\n    }\n\n    // If chunk ends with high surrogate, save it for next chunk\n    if (end > 0 && U_IS_LEAD(buf[end - 1])) {\n      holder->pending = buf[--end];\n    }\n    if (end == 0) return js.resolvedPromise();\n\n    auto slice = buf.first(end);\n    auto result = simdutf::utf8_length_from_utf16_with_replacement(slice.begin(), slice.size());\n    // Only sanitize if there are surrogates in the buffer - UTF-16 without\n    // surrogates is always well-formed.\n    if (result.error == simdutf::error_code::SURROGATE) {\n      simdutf::to_well_formed_utf16(slice.begin(), slice.size(), slice.begin());\n    }\n    auto utf8Length = result.count;\n    KJ_DASSERT(utf8Length > 0 && utf8Length >= end);\n\n    auto backingStore = js.allocBackingStore(utf8Length, jsg::Lock::AllocOption::UNINITIALIZED);\n    auto dest = kj::ArrayPtr<char>(static_cast<char*>(backingStore->Data()), utf8Length);\n    [[maybe_unused]] auto written =\n        simdutf::convert_utf16_to_utf8(slice.begin(), slice.size(), dest.begin());\n    KJ_DASSERT(written == utf8Length, \"simdutf should write exactly utf8Length bytes\");\n\n    auto array = v8::Uint8Array::New(\n        v8::ArrayBuffer::New(js.v8Isolate, kj::mv(backingStore)), 0, utf8Length);\n    controller->enqueue(js, jsg::JsUint8Array(array));\n    return js.resolvedPromise();\n  };\n\n  auto flush = [holder = state.addRef()](\n                   jsg::Lock& js, jsg::Ref<TransformStreamDefaultController> controller) mutable {\n    // If stream ends with orphaned high surrogate, emit replacement character\n    if (holder->pending != kj::none) {\n      auto backingStore = js.allocBackingStore(3, jsg::Lock::AllocOption::UNINITIALIZED);\n      memcpy(backingStore->Data(), REPLACEMENT_UTF8, 3);\n      controller->enqueue(js, jsg::JsUint8Array::create(js, kj::mv(backingStore), 0, 3));\n    }\n    return js.resolvedPromise();\n  };\n\n  // Per the WHATWG Encoding spec, the readable side HWM should be 0, so writes\n  // block until a reader pulls. Previously StreamQueuingStrategy{} was passed,\n  // which bypassed the orDefault() in TransformStream::constructor and caused\n  // the readable HWM to default to 1, clearing backpressure at startup.\n  // Passing kj::none lets TransformStream apply the spec defaults (writable HWM=1,\n  // readable HWM=0).\n  kj::Maybe<StreamQueuingStrategy> readableStrategy;\n  if (!FeatureFlags::get(js).getEncoderStreamSpecCompliantBackpressure()) {\n    readableStrategy = StreamQueuingStrategy{};\n  }\n  auto transformer = TransformStream::constructor(js,\n      Transformer{.transform = jsg::Function<Transformer::TransformAlgorithm>(kj::mv(transform)),\n        .flush = jsg::Function<Transformer::FlushAlgorithm>(kj::mv(flush))},\n      StreamQueuingStrategy{}, kj::mv(readableStrategy));\n\n  return js.alloc<TextEncoderStream>(transformer->getReadable(), transformer->getWritable());\n}\n\nTextDecoderStream::TextDecoderStream(jsg::Ref<TextDecoder> decoder,\n    jsg::Ref<ReadableStream> readable,\n    jsg::Ref<WritableStream> writable)\n    : TransformStream(kj::mv(readable), kj::mv(writable)),\n      decoder(kj::mv(decoder)) {}\n\njsg::Ref<TextDecoderStream> TextDecoderStream::constructor(\n    jsg::Lock& js, jsg::Optional<kj::String> label, jsg::Optional<TextDecoderStreamInit> options) {\n\n  auto decoder = TextDecoder::constructor(js, kj::mv(label), options.map([&js](auto& opts) {\n    return TextDecoder::ConstructorOptions{\n      // Previously this would default to true. The spec requires a default\n      // of false, however. When the pedanticWpt flag is not set, we continue\n      // to default as true.\n      .fatal = opts.fatal.orDefault(!FeatureFlags::get(js).getPedanticWpt()),\n      .ignoreBOM = opts.ignoreBOM.orDefault(false),\n    };\n  }));\n\n  // The controller will store c++ references to both the readable and writable\n  // streams underlying controllers.\n  // See comment in TextEncoderStream::constructor for why we conditionally pass\n  // kj::none for the readable strategy.\n  kj::Maybe<StreamQueuingStrategy> readableStrategy;\n  if (!FeatureFlags::get(js).getEncoderStreamSpecCompliantBackpressure()) {\n    readableStrategy = StreamQueuingStrategy{};\n  }\n  auto transformer = TransformStream::constructor(js,\n      Transformer{.transform = jsg::Function<Transformer::TransformAlgorithm>( JSG_VISITABLE_LAMBDA(\n                      (decoder = decoder.addRef()), (decoder),\n                      (jsg::Lock& js, auto chunk, auto controller) {\n                        JSG_REQUIRE(chunk->IsArrayBuffer() || chunk->IsArrayBufferView(), TypeError,\n                            \"This TransformStream is being used as a byte stream, \"\n                            \"but received a value that is not a BufferSource.\");\n                        jsg::BufferSource source(js, chunk);\n                        auto decoded =\n                            JSG_REQUIRE_NONNULL(decoder->decodePtr(js, source.asArrayPtr(), false),\n                                TypeError, \"Failed to decode input.\");\n                        // Only enqueue if there's actual output - don't emit empty chunks\n                        // for incomplete multi-byte sequences\n                        if (decoded.length(js) > 0) {\n                        controller->enqueue(js, decoded);\n                        }\n                        return js.resolvedPromise();\n                      })),\n        .flush = jsg::Function<Transformer::FlushAlgorithm>(\n            JSG_VISITABLE_LAMBDA((decoder = decoder.addRef()), (decoder),\n                (jsg::Lock& js, auto controller) {\n                  auto decoded =\n                      JSG_REQUIRE_NONNULL(decoder->decodePtr(js, kj::ArrayPtr<kj::byte>(), true),\n                          TypeError, \"Failed to decode input.\");\n                  // Only enqueue if there's actual output\n                  if (decoded.length(js) > 0) {\n                  controller->enqueue(js, decoded);\n                  }\n                  return js.resolvedPromise();\n                }))},\n      StreamQueuingStrategy{}, kj::mv(readableStrategy));\n\n  return js.alloc<TextDecoderStream>(\n      kj::mv(decoder), transformer->getReadable(), transformer->getWritable());\n}\n\nkj::StringPtr TextDecoderStream::getEncoding() {\n  return decoder->getEncoding();\n}\n\nbool TextDecoderStream::getFatal() {\n  return decoder->getFatal();\n}\n\nbool TextDecoderStream::getIgnoreBOM() {\n  return decoder->getIgnoreBom();\n}\n\nvoid TextDecoderStream::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"decoder\", decoder);\n}\n\nvoid TextDecoderStream::visitForGc(jsg::GcVisitor& visitor) {\n  visitor.visit(decoder);\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/encoding.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/api/encoding.h>\n#include <workerd/api/streams/readable.h>\n#include <workerd/api/streams/transform.h>\n#include <workerd/api/streams/writable.h>\n\nnamespace workerd::api {\n\nclass TextEncoderStream: public TransformStream {\n public:\n  using TransformStream::TransformStream;\n\n  static jsg::Ref<TextEncoderStream> constructor(jsg::Lock& js);\n\n  kj::StringPtr getEncoding() {\n    return \"utf-8\"_kj;\n  }\n\n  JSG_RESOURCE_TYPE(TextEncoderStream) {\n    JSG_INHERIT(TransformStream);\n\n    JSG_READONLY_PROTOTYPE_PROPERTY(encoding, getEncoding);\n\n    JSG_TS_OVERRIDE(extends TransformStream<string, Uint8Array>);\n  }\n};\n\nclass TextDecoderStream: public TransformStream {\n public:\n  struct TextDecoderStreamInit {\n    jsg::Optional<bool> fatal;\n    jsg::Optional<bool> ignoreBOM;\n\n    JSG_STRUCT(fatal, ignoreBOM);\n  };\n\n  TextDecoderStream(jsg::Ref<TextDecoder> decoder,\n      jsg::Ref<ReadableStream> readable,\n      jsg::Ref<WritableStream> writable);\n\n  static jsg::Ref<TextDecoderStream> constructor(\n      jsg::Lock& js, jsg::Optional<kj::String> label, jsg::Optional<TextDecoderStreamInit> options);\n\n  kj::StringPtr getEncoding();\n  bool getFatal();\n  bool getIgnoreBOM();\n\n  JSG_RESOURCE_TYPE(TextDecoderStream) {\n    JSG_INHERIT(TransformStream);\n    JSG_READONLY_PROTOTYPE_PROPERTY(encoding, getEncoding);\n    JSG_READONLY_PROTOTYPE_PROPERTY(fatal, getFatal);\n    JSG_READONLY_PROTOTYPE_PROPERTY(ignoreBOM, getIgnoreBOM);\n\n    JSG_TS_OVERRIDE(extends TransformStream<ArrayBuffer | ArrayBufferView, string>);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  jsg::Ref<TextDecoder> decoder;\n\n  void visitForGc(jsg::GcVisitor& visitor);\n};\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/identity-transform-stream.c++",
    "content": "#include \"identity-transform-stream.h\"\n\n#include \"common.h\"\n\n#include <workerd/util/autogate.h>\n#include <workerd/util/state-machine.h>\n\nnamespace workerd::api {\n\nnamespace {\n// An implementation of ReadableStreamSource and WritableStreamSink which communicates read and\n// write requests via a StateMachine.\n//\n// This class is also used as the implementation of FixedLengthStream, in which case `limit` is\n// non-nullptr.\n\nstruct Idle {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"idle\"_kj;\n};\n\nstruct ReadRequest {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"read-request\"_kj;\n  kj::ArrayPtr<kj::byte> bytes;\n  // WARNING: `bytes` may be invalid if fulfiller->isWaiting() returns false! (This indicates the\n  //   read was canceled.)\n  kj::Own<kj::PromiseFulfiller<size_t>> fulfiller;\n};\n\nstruct WriteRequest {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"write-request\"_kj;\n  kj::ArrayPtr<const kj::byte> bytes;\n  kj::Own<kj::PromiseFulfiller<void>> fulfiller;\n};\n\nstruct Closed {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"closed\"_kj;\n};\n\n// State machine for IdentityTransformStream:\n//   Idle -> ReadRequest (read arrives when no write pending)\n//   Idle -> WriteRequest (write arrives when no read pending)\n//   Idle -> Closed (empty write = close)\n//   ReadRequest -> Idle (write fulfills read completely)\n//   WriteRequest -> Idle (read fulfills write completely)\n//   ReadRequest -> Closed (empty write closes while read pending)\n//   Any -> kj::Exception (cancel/abort)\n//   Closed -> kj::Exception (abort can force-transition a closed stream to error)\n// Closed is terminal, kj::Exception is implicitly terminal via ErrorState.\n// abort() uses forceTransitionTo to allow the exceptional Closed -> Exception transition.\nusing IdentityTransformState = StateMachine<TerminalStates<Closed>,\n    ErrorState<kj::Exception>,\n    Idle,\n    ReadRequest,\n    WriteRequest,\n    Closed,\n    kj::Exception>;\n\nclass IdentityTransformStreamImpl final: public kj::Refcounted,\n                                         public ReadableStreamSource,\n                                         public WritableStreamSink {\n public:\n  // The limit is the maximum number of bytes that can be fed through the stream.\n  // If kj::none, there is no limit.\n  explicit IdentityTransformStreamImpl(kj::Maybe<uint64_t> limit = kj::none)\n      : limit(limit),\n        state(IdentityTransformState::create<Idle>()) {}\n\n  ~IdentityTransformStreamImpl() noexcept(false) {\n    // Due to the different natures of JS and C++ disposal, there is no point in enforcing the limit\n    // for a FixedLengthStream here.\n    //\n    // 1. Creating but not using a `new FixedLengthStream(n)` should not be an error, and ought not\n    //    to logspam us.\n    // 2. Chances are high that by the time this object gets destroyed, it's too late to tell the\n    //    user about the failure.\n  }\n\n  // ReadableStreamSource implementation -------------------------------------------------\n\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    size_t total = 0;\n    while (total < minBytes) {\n      // TODO(perf): tryReadInternal was written assuming minBytes would always be 1 but we've now\n      // introduced an API for user to specify a larger minBytes. For now, this is implemented as a\n      // naive loop dispatching to the 1 byte version but would be better to bake it deeper into\n      // the implementation where it can be more efficient.\n      auto amount = co_await tryReadInternal(buffer, maxBytes);\n      KJ_ASSERT(amount <= maxBytes);\n      if (amount == 0) {\n        // EOF.\n        break;\n      }\n\n      total += amount;\n      buffer = reinterpret_cast<char*>(buffer) + amount;\n      maxBytes -= amount;\n    }\n\n    co_return total;\n  }\n\n  kj::Promise<size_t> tryReadInternal(void* buffer, size_t maxBytes) {\n    auto promise = readHelper(kj::arrayPtr(static_cast<kj::byte*>(buffer), maxBytes));\n\n    KJ_IF_SOME(l, limit) {\n      promise = promise.then([this, &l = l](size_t amount) -> kj::Promise<size_t> {\n        if (amount > l) {\n          auto exception = JSG_KJ_EXCEPTION(\n              FAILED, TypeError, \"Attempt to write too many bytes through a FixedLengthStream.\");\n          cancel(exception);\n          return kj::mv(exception);\n        } else if (amount == 0 && l != 0) {\n          auto exception = JSG_KJ_EXCEPTION(FAILED, TypeError,\n              \"FixedLengthStream did not see all expected bytes before close().\");\n          cancel(exception);\n          return kj::mv(exception);\n        }\n        l -= amount;\n        return amount;\n      });\n    }\n\n    return promise;\n  }\n\n  kj::Promise<DeferredProxy<void>> pumpTo(WritableStreamSink& output, bool end) override {\n#ifdef KJ_NO_RTTI\n    // Yes, I'm paranoid.\n    static_assert(!KJ_NO_RTTI, \"Need RTTI for correctness\");\n#endif\n\n    // HACK: If `output` is another TransformStream, we don't allow pumping to it, in order to\n    //   guarantee that we can't create cycles.\n    JSG_REQUIRE(!isIdentityTransformStream(output), TypeError,\n        \"Inter-TransformStream ReadableStream.pipeTo() is not implemented.\");\n\n    return ReadableStreamSource::pumpTo(output, end);\n  }\n\n  kj::Maybe<uint64_t> tryGetLength(StreamEncoding encoding) override {\n    if (encoding == StreamEncoding::IDENTITY) {\n      return limit;\n    } else {\n      return kj::none;\n    }\n  }\n\n  void cancel(kj::Exception reason) override {\n    // Already errored - nothing to do.\n    if (state.isErrored()) return;\n\n    // Already closed by writable side - nothing to do.\n    if (state.is<Closed>()) return;\n\n    KJ_IF_SOME(request, state.tryGetUnsafe<ReadRequest>()) {\n      request.fulfiller->fulfill(static_cast<size_t>(0));\n    } else KJ_IF_SOME(request, state.tryGetUnsafe<WriteRequest>()) {\n      request.fulfiller->reject(kj::cp(reason));\n    }\n    // Idle state is fine, just transition to error.\n\n    state.forceTransitionTo<kj::Exception>(kj::mv(reason));\n\n    // TODO(conform): Proactively put WritableStream into Errored state.\n  }\n\n  // WritableStreamSink implementation ---------------------------------------------------\n\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override {\n    if (buffer == nullptr) {\n      return kj::READY_NOW;\n    }\n    return writeHelper(buffer);\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const kj::byte>> pieces) override {\n    KJ_UNIMPLEMENTED(\"IdentityTransformStreamImpl piecewise write() not currently supported\");\n    // TODO(soon): This will be called by TeeBranch::pumpTo(). We disallow that anyway, since we\n    //   disallow inter-TransformStream pumping.\n  }\n\n  kj::Promise<void> end() override {\n    // If we're already closed, there's nothing else we need to do here.\n    if (state.is<Closed>()) return kj::READY_NOW;\n\n    return writeHelper(kj::ArrayPtr<const kj::byte>());\n  }\n\n  void abort(kj::Exception reason) override {\n    // Already errored - nothing to do.\n    if (state.isErrored()) return;\n\n    KJ_IF_SOME(request, state.tryGetUnsafe<ReadRequest>()) {\n      request.fulfiller->reject(kj::cp(reason));\n    } else KJ_IF_SOME(request, state.tryGetUnsafe<WriteRequest>()) {\n      // If the fulfiller is not waiting, the write promise was already\n      // canceled and no one is waiting on it.\n      KJ_ASSERT(!request.fulfiller->isWaiting(),\n          \"abort() is supposed to wait for any pending write() to finish\");\n    }\n    // Idle and Closed states are fine, just transition to error.\n    // (Closed can transition to error via abort)\n\n    state.forceTransitionTo<kj::Exception>(kj::mv(reason));\n\n    // TODO(conform): Proactively put ReadableStream into Errored state.\n  }\n\n private:\n  kj::Promise<size_t> readHelper(kj::ArrayPtr<kj::byte> bytes) {\n    // Handle error state first.\n    KJ_IF_SOME(exception, state.tryGetErrorUnsafe()) {\n      return kj::cp(exception);\n    }\n\n    // Handle closed state.\n    if (state.is<Closed>()) {\n      return static_cast<size_t>(0);\n    }\n\n    // Check for already in-flight read.\n    if (state.is<ReadRequest>()) {\n      KJ_FAIL_ASSERT(\"read operation already in flight\");\n    }\n\n    // Check for pending write request.\n    KJ_IF_SOME(request, state.tryGetUnsafe<WriteRequest>()) {\n      if (bytes.size() >= request.bytes.size()) {\n        // The write buffer will entirely fit into our read buffer; fulfill both requests.\n        memcpy(bytes.begin(), request.bytes.begin(), request.bytes.size());\n        auto result = request.bytes.size();\n        request.fulfiller->fulfill();\n\n        // Switch to idle state.\n        state.transitionTo<Idle>();\n\n        return result;\n      }\n\n      // The write buffer won't quite fit into our read buffer; fulfill only the read request.\n      memcpy(bytes.begin(), request.bytes.begin(), bytes.size());\n      request.bytes = request.bytes.slice(bytes.size(), request.bytes.size());\n      return bytes.size();\n    }\n\n    // Must be idle - no outstanding write request, switch to ReadRequest state.\n    KJ_ASSERT(state.is<Idle>());\n    auto paf = kj::newPromiseAndFulfiller<size_t>();\n    state.transitionTo<ReadRequest>(bytes, kj::mv(paf.fulfiller));\n    return kj::mv(paf.promise);\n  }\n\n  kj::Promise<void> writeHelper(kj::ArrayPtr<const kj::byte> bytes) {\n    // Handle error state first.\n    KJ_IF_SOME(exception, state.tryGetErrorUnsafe()) {\n      return kj::cp(exception);\n    }\n\n    // Handle closed state.\n    if (state.is<Closed>()) {\n      KJ_FAIL_ASSERT(\"close operation already in flight\");\n    }\n\n    // Check for already in-flight write.\n    if (state.is<WriteRequest>()) {\n      KJ_FAIL_ASSERT(\"write operation already in flight\");\n    }\n\n    // Check for pending read request.\n    KJ_IF_SOME(request, state.tryGetUnsafe<ReadRequest>()) {\n      if (!request.fulfiller->isWaiting()) {\n        // Oops, the request was canceled. Currently, this happen in particular when pumping a\n        // response body to the client, and the client disconnects, cancelling the pump. In this\n        // specific case, we want to propagate the error back to the write end of the transform\n        // stream. In theory, though, there could be other cases where propagation is incorrect.\n        //\n        // TODO(cleanup): This cancellation should probably be handled at a higher level, e.g.\n        //   in pumpTo(), but I need a quick fix.\n        state.forceTransitionTo<kj::Exception>(KJ_EXCEPTION(DISCONNECTED, \"reader canceled\"));\n\n        // I was going to use a `goto` but Harris choked on his bagel. Recursion it is.\n        return writeHelper(bytes);\n      }\n\n      if (bytes.size() == 0) {\n        // This is a close operation.\n        request.fulfiller->fulfill(static_cast<size_t>(0));\n        state.transitionTo<Closed>();\n        return kj::READY_NOW;\n      }\n\n      KJ_ASSERT(request.bytes.size() > 0);\n\n      if (request.bytes.size() >= bytes.size()) {\n        // Our write buffer will entirely fit into the read buffer; fulfill both requests.\n        memcpy(request.bytes.begin(), bytes.begin(), bytes.size());\n        request.fulfiller->fulfill(bytes.size());\n        state.transitionTo<Idle>();\n        return kj::READY_NOW;\n      }\n\n      // Our write buffer won't quite fit into the read buffer; fulfill only the read request.\n      memcpy(request.bytes.begin(), bytes.begin(), request.bytes.size());\n      bytes = bytes.slice(request.bytes.size(), bytes.size());\n      request.fulfiller->fulfill(request.bytes.size());\n\n      auto paf = kj::newPromiseAndFulfiller<void>();\n      state.transitionTo<WriteRequest>(bytes, kj::mv(paf.fulfiller));\n      return kj::mv(paf.promise);\n    }\n\n    // Must be idle.\n    KJ_ASSERT(state.is<Idle>());\n    if (bytes.size() == 0) {\n      // This is a close operation.\n      state.transitionTo<Closed>();\n      return kj::READY_NOW;\n    }\n\n    auto paf = kj::newPromiseAndFulfiller<void>();\n    state.transitionTo<WriteRequest>(bytes, kj::mv(paf.fulfiller));\n    return kj::mv(paf.promise);\n  }\n\n  kj::Maybe<uint64_t> limit;\n  IdentityTransformState state;\n};\n\nstruct Pair {\n  kj::Own<ReadableStreamSource> readable;\n  kj::Own<WritableStreamSink> writable;\n};\nPair newIdentityPair(kj::Maybe<uint64_t> expectedLength = kj::none) {\n  auto readableSide = kj::refcounted<IdentityTransformStreamImpl>(kj::mv(expectedLength));\n  auto writableSide = kj::addRef(*readableSide);\n  return Pair{.readable = kj::mv(readableSide), .writable = kj::mv(writableSide)};\n}\n}  // namespace\n\njsg::Ref<IdentityTransformStream> IdentityTransformStream::constructor(\n    jsg::Lock& js, jsg::Optional<IdentityTransformStream::QueuingStrategy> maybeQueuingStrategy) {\n\n  auto& ioContext = IoContext::current();\n  auto pipe = newIdentityPipe();\n\n  kj::Maybe<uint64_t> maybeHighWaterMark = kj::none;\n  KJ_IF_SOME(queuingStrategy, maybeQueuingStrategy) {\n    maybeHighWaterMark = queuingStrategy.highWaterMark;\n  }\n  return js.alloc<IdentityTransformStream>(js.alloc<ReadableStream>(ioContext, kj::mv(pipe.in)),\n      js.alloc<WritableStream>(ioContext, kj::mv(pipe.out),\n          ioContext.getMetrics().tryCreateWritableByteStreamObserver(), maybeHighWaterMark));\n}\n\njsg::Ref<FixedLengthStream> FixedLengthStream::constructor(jsg::Lock& js,\n    uint64_t expectedLength,\n    jsg::Optional<IdentityTransformStream::QueuingStrategy> maybeQueuingStrategy) {\n  constexpr uint64_t MAX_SAFE_INTEGER = (1ull << 53) - 1;\n\n  JSG_REQUIRE(expectedLength <= MAX_SAFE_INTEGER, TypeError,\n      \"FixedLengthStream requires an integer expected length less than 2^53.\");\n\n  auto& ioContext = IoContext::current();\n  auto pipe = newIdentityPipe(expectedLength);\n\n  kj::Maybe<uint64_t> maybeHighWaterMark = kj::none;\n  // For a FixedLengthStream we do not want a highWaterMark higher than the expectedLength.\n  KJ_IF_SOME(queuingStrategy, maybeQueuingStrategy) {\n    maybeHighWaterMark = queuingStrategy.highWaterMark.map(\n        [&](uint64_t highWaterMark) { return kj::min(expectedLength, highWaterMark); });\n  }\n\n  return js.alloc<FixedLengthStream>(js.alloc<ReadableStream>(ioContext, kj::mv(pipe.in)),\n      js.alloc<WritableStream>(ioContext, kj::mv(pipe.out),\n          ioContext.getMetrics().tryCreateWritableByteStreamObserver(), maybeHighWaterMark));\n}\n\nOneWayPipe newIdentityPipe(kj::Maybe<uint64_t> expectedLength) {\n  auto pair = newIdentityPair(kj::mv(expectedLength));\n  return OneWayPipe{.in = kj::mv(pair.readable), .out = kj::mv(pair.writable)};\n}\n\nbool isIdentityTransformStream(WritableStreamSink& sink) {\n  return kj::dynamicDowncastIfAvailable<IdentityTransformStreamImpl>(sink) != kj::none;\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/identity-transform-stream.h",
    "content": "#pragma once\n\n#include \"transform.h\"\n\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api {\n\n// The IdentityTransformStream is a non-standard TransformStream implementation that passes\n// the exact bytes written to the writable side on to the readable side without modification.\n// Unlike standard the TransformStream, the readable side of an IdentityTransformStream\n// supports BYOB reads.\n//\n// The IdentityTransformStream is a kj-based implementation backed by a ReadableStreamSource\n// and WritableStreamSink implementation. It is a legacy class that was created before the\n// standard TransformStream constructor was available in workers. It is maintained for\n// backwards compatibility but otherwise has no special significance.\nclass IdentityTransformStream: public TransformStream {\n public:\n  using TransformStream::TransformStream;\n\n  struct QueuingStrategy {\n    jsg::Optional<uint64_t> highWaterMark;\n\n    JSG_STRUCT(highWaterMark);\n  };\n\n  static jsg::Ref<IdentityTransformStream> constructor(\n      jsg::Lock& js, jsg::Optional<QueuingStrategy> queuingStrategy = kj::none);\n\n  JSG_RESOURCE_TYPE(IdentityTransformStream) {\n    JSG_INHERIT(TransformStream);\n\n    JSG_TS_OVERRIDE(extends TransformStream<ArrayBuffer | ArrayBufferView, Uint8Array>);\n  }\n};\n\n// Same as an IdentityTransformStream, except with a known length in bytes on the readable side.\n// We don't currently enforce this limit -- it just convinces the kj-http layer to\n// emit a Content-Length (assuming it doesn't get gzipped or anything).\nclass FixedLengthStream: public IdentityTransformStream {\n public:\n  using IdentityTransformStream::IdentityTransformStream;\n\n  static jsg::Ref<FixedLengthStream> constructor(jsg::Lock& js,\n      uint64_t expectedLength,\n      jsg::Optional<QueuingStrategy> queuingStrategy = kj::none);\n\n  JSG_RESOURCE_TYPE(FixedLengthStream) {\n    JSG_INHERIT(IdentityTransformStream);\n  }\n};\n\nstruct OneWayPipe {\n  kj::Own<ReadableStreamSource> in;\n  kj::Own<WritableStreamSink> out;\n};\n\nOneWayPipe newIdentityPipe(kj::Maybe<uint64_t> expectedLength = kj::none);\n\nbool isIdentityTransformStream(WritableStreamSink& sink);\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/identitytransformstream-backpressure-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { notStrictEqual, strictEqual } from 'node:assert';\n\nexport const identityTransformStream = {\n  async test(ctrl, env, ctx) {\n    const ts = new IdentityTransformStream({ highWaterMark: 10 });\n    const writer = ts.writable.getWriter();\n    const reader = ts.readable.getReader();\n\n    strictEqual(writer.desiredSize, 10);\n\n    // We shouldn't have to wait here.\n    const firstReady = writer.ready;\n    await writer.ready;\n\n    writer.write(new Uint8Array(1));\n    strictEqual(writer.desiredSize, 9);\n\n    // Let's write a second chunk that will be buffered. This one\n    // should impact the desiredSize and the backpressure signal.\n    writer.write(new Uint8Array(9));\n    strictEqual(writer.desiredSize, 0);\n\n    // The ready promise should have been replaced\n    notStrictEqual(firstReady, writer.ready);\n\n    async function waitForReady() {\n      strictEqual(writer.desiredSize, 0);\n      await writer.ready;\n      // The backpressure should have been relieved a bit,\n      // but only by the amount of what we've currently read.\n      strictEqual(writer.desiredSize, 1);\n    }\n\n    await Promise.all([\n      // We call the waitForReady first to ensure that we set up waiting on\n      // the ready promise before we relieve the backpressure using the read.\n      // If the backpressure signal is not working correctly, the test will\n      // fail with an error indicating that a hanging promise was canceled.\n      waitForReady(),\n      reader.read(),\n    ]);\n\n    // If we read again, the backpressure should be fully resolved.\n    await reader.read();\n    strictEqual(writer.desiredSize, 10);\n  },\n};\n\nexport const identityTransformStreamNoHWM = {\n  async test(ctrl, env, ctx) {\n    // Test that the original default behavior still works as expected.\n\n    const ts = new IdentityTransformStream();\n    const writer = ts.writable.getWriter();\n    const reader = ts.readable.getReader();\n\n    strictEqual(writer.desiredSize, 1);\n\n    // We shouldn't have to wait here.\n    const firstReady = writer.ready;\n    await writer.ready;\n\n    writer.write(new Uint8Array(1));\n    strictEqual(writer.desiredSize, 1);\n\n    // Let's write a second chunk that will be buffered. There should\n    // be no indication that the desired size has changed.\n    writer.write(new Uint8Array(9));\n    strictEqual(writer.desiredSize, 1);\n\n    // The ready promise should be exactly the same...\n    strictEqual(firstReady, writer.ready);\n\n    async function waitForReady() {\n      strictEqual(writer.desiredSize, 1);\n      await writer.ready;\n      strictEqual(writer.desiredSize, 1);\n    }\n\n    await Promise.all([\n      // We call the waitForReady first to ensure that we set up waiting on\n      // the ready promise before we relieve the backpressure using the read.\n      // If the backpressure signal is not working correctly, the test will\n      // fail with an error indicating that a hanging promise was canceled.\n      waitForReady(),\n      reader.read(),\n    ]);\n\n    // If we read again, the backpressure should be fully resolved.\n    await reader.read();\n    strictEqual(writer.desiredSize, 1);\n  },\n};\n\nexport const fixedLengthStream = {\n  async test(ctrl, env, ctx) {\n    const ts = new FixedLengthStream(10, { highWaterMark: 100 });\n    const writer = ts.writable.getWriter();\n    const reader = ts.readable.getReader();\n\n    // Even tho we specified 100 as our highWaterMark, we only expect 10\n    // bytes total, so we'll make that our highWaterMark instead.\n    strictEqual(writer.desiredSize, 10);\n\n    // We shouldn't have to wait here.\n    const firstReady = writer.ready;\n    await writer.ready;\n\n    writer.write(new Uint8Array(1));\n    strictEqual(writer.desiredSize, 9);\n\n    // Let's write a second chunk that will be buffered. This one\n    // should impact the desiredSize and the backpressure signal.\n    writer.write(new Uint8Array(9));\n    strictEqual(writer.desiredSize, 0);\n\n    // The ready promise should have been replaced\n    notStrictEqual(firstReady, writer.ready);\n\n    async function waitForReady() {\n      await writer.ready;\n      // The backpressure should have been relieved a bit,\n      // but only by the amount of what we've currently read.\n      strictEqual(writer.desiredSize, 1);\n    }\n\n    await Promise.all([\n      // We call the waitForReady first to ensure that we set up waiting on\n      // the ready promise before we relieve the backpressure using the read.\n      waitForReady(),\n      reader.read(),\n    ]);\n\n    // If we read again, the backpressure should be fully resolved.\n    await reader.read();\n    strictEqual(writer.desiredSize, 10);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/streams/identitytransformstream-backpressure-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"identitytransformstream-backpressure-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"identitytransformstream-backpressure-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/streams/identitytransformstream-byob-test.js",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { strictEqual, deepStrictEqual, rejects } from 'node:assert';\n\n// This is testing non-standard behavior that we support for historical reasons. While the spec\n// dictates that BYOB buffers should be detached when passed to a pending read, historically we've\n// not done so (there's a compat flag to enable that behavior). This means the read/write buffers\n// can be modified after being passed in, including being detached or resized. When a buffer is\n// detached or resized, we need to ensure that the BYOB read still resolves correctly and doesn't\n// end with a v8 assert or produce invalid data.\n\n// Test that transferring the buffer during a pending BYOB read resolves with a zero-length result\nexport const transferBuffer = {\n  async test() {\n    const ts = new IdentityTransformStream();\n    const reader = ts.readable.getReader({ mode: 'byob' });\n    const writer = ts.writable.getWriter();\n\n    const buffer = new ArrayBuffer(4096);\n    const readPromise = reader.read(new Uint8Array(buffer));\n    buffer.transferToFixedLength(64);\n\n    await writer.write(new Uint8Array(1337));\n\n    // When the buffer is detached, the read resolves with a new zero-length ArrayBuffer\n    // (the data that was read is lost)\n    const result = await readPromise;\n    strictEqual(result.done, false);\n    strictEqual(result.value.byteLength, 0);\n    // The buffer should be a new ArrayBuffer, not the original detached one\n    strictEqual(result.value.buffer.byteLength, 0);\n  },\n};\n\n// Test that resizing a resizable buffer smaller during a pending BYOB read returns truncated data\nexport const resizeBufferSmaller = {\n  async test() {\n    const ts = new IdentityTransformStream();\n    const reader = ts.readable.getReader({ mode: 'byob' });\n    const writer = ts.writable.getWriter();\n\n    const buffer = new ArrayBuffer(4096, { maxByteLength: 8192 });\n    const readPromise = reader.read(new Uint8Array(buffer));\n    buffer.resize(64);\n\n    // Write data that would exceed the resized buffer\n    const writeData = new Uint8Array(1337);\n    for (let i = 0; i < writeData.length; i++) {\n      writeData[i] = i % 256;\n    }\n    await writer.write(writeData);\n\n    // When the buffer is resized smaller, we get truncated data\n    // (only the bytes that fit in the new size)\n    const result = await readPromise;\n    strictEqual(result.done, false);\n    strictEqual(result.value.byteLength, 64);\n    // The underlying ArrayBuffer should be the resized buffer\n    strictEqual(result.value.buffer.byteLength, 64);\n    // Verify the truncated data matches the first 64 bytes written\n    deepStrictEqual(\n      Array.from(result.value),\n      Array.from(writeData.slice(0, 64))\n    );\n  },\n};\n\n// Test that resizing a resizable buffer larger during a pending BYOB read works correctly\nexport const resizeBufferLarger = {\n  async test() {\n    const ts = new IdentityTransformStream();\n    const reader = ts.readable.getReader({ mode: 'byob' });\n    const writer = ts.writable.getWriter();\n\n    const buffer = new ArrayBuffer(4096, { maxByteLength: 8192 });\n    const readPromise = reader.read(new Uint8Array(buffer));\n    buffer.resize(8192);\n\n    // Write some data\n    const writeData = new Uint8Array(100);\n    for (let i = 0; i < writeData.length; i++) {\n      writeData[i] = i;\n    }\n    await writer.write(writeData);\n\n    const result = await readPromise;\n    strictEqual(result.done, false);\n    strictEqual(result.value.byteLength, 100);\n    // The underlying ArrayBuffer should be the resized buffer\n    strictEqual(result.value.buffer.byteLength, 8192);\n\n    // Verify the data matches what was written\n    deepStrictEqual(Array.from(result.value), Array.from(writeData));\n\n    // Verify the remaining bytes in the underlying ArrayBuffer are zeroes\n    const fullBuffer = new Uint8Array(result.value.buffer);\n    strictEqual(fullBuffer.byteLength, 8192);\n    for (let i = 100; i < fullBuffer.byteLength; i++) {\n      strictEqual(\n        fullBuffer[i],\n        0,\n        `Expected byte at index ${i} to be 0, got ${fullBuffer[i]}`\n      );\n    }\n  },\n};\n\n// Test that resizing a write buffer smaller after queuing doesn't produce invalid data\nexport const writeBufferResizedSmaller = {\n  async test() {\n    const { writable, readable } = new IdentityTransformStream();\n    const writer = writable.getWriter();\n    const reader = readable.getReader();\n\n    const buf1 = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);\n    const buf2 = new Uint8Array(new ArrayBuffer(8, { maxByteLength: 16 }));\n    buf2.set([11, 12, 13, 14, 15, 16, 17, 18]);\n\n    const p1 = writer.write(buf1);\n    const p2 = writer.write(buf2);\n    // Resize smaller after queuing. The write copies the BackingStore at\n    // time of write\n    buf2.buffer.resize(6);\n\n    const r1 = await reader.read();\n    const r2 = await reader.read();\n\n    await p1;\n    await p2;\n\n    // First write should be unaffected\n    strictEqual(r1.done, false);\n    deepStrictEqual(Array.from(r1.value), [1, 2, 3, 4, 5, 6, 7, 8]);\n\n    // Second write - uses original view size but bytes beyond the resized buffer are zeroed\n    strictEqual(r2.done, false);\n    deepStrictEqual(Array.from(r2.value), [11, 12, 13, 14, 15, 16, 17, 18]);\n  },\n};\n\n// Test that resizing a write buffer larger after queuing doesn't produce invalid data\nexport const writeBufferResizedLarger = {\n  async test() {\n    const { writable, readable } = new IdentityTransformStream();\n    const writer = writable.getWriter();\n    const reader = readable.getReader();\n\n    const buf1 = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);\n    const buf2 = new Uint8Array(new ArrayBuffer(8, { maxByteLength: 16 }));\n    buf2.set([11, 12, 13, 14, 15, 16, 17, 18]);\n\n    const p1 = writer.write(buf1);\n    const p2 = writer.write(buf2);\n    buf2.buffer.resize(12); // Resize larger after queuing\n\n    const r1 = await reader.read();\n    const r2 = await reader.read();\n\n    await p1;\n    await p2;\n\n    // First write should be unaffected\n    strictEqual(r1.done, false);\n    deepStrictEqual(Array.from(r1.value), [1, 2, 3, 4, 5, 6, 7, 8]);\n\n    // Second write - should contain original 8 bytes (view size at time of write)\n    strictEqual(r2.done, false);\n    deepStrictEqual(Array.from(r2.value), [11, 12, 13, 14, 15, 16, 17, 18]);\n  },\n};\n\n// Test that transferring a write buffer after queuing still writes correctly (BackingStore\n// is captured at time of write, so transfer doesn't affect the queued write)\nexport const writeBufferTransferred = {\n  async test() {\n    const { writable, readable } = new IdentityTransformStream();\n    const writer = writable.getWriter();\n    const reader = readable.getReader();\n\n    const buf1 = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);\n    const buf2 = new Uint8Array([9, 10, 11, 12, 13, 14, 15, 16]);\n    const buf3 = new Uint8Array([17, 18, 19, 20, 21, 22, 23, 24]);\n    const buf4 = new Uint8Array([25, 26, 27, 28, 29, 30, 31, 32]);\n\n    // Do not await the writes so they are queued up before the transfer\n    writer.write(buf1);\n    writer.write(buf2);\n    writer.write(buf3);\n    writer.write(buf4);\n\n    buf3.buffer.transfer();\n\n    const r1 = await reader.read();\n    const r2 = await reader.read();\n    const r3 = await reader.read();\n    const r4 = await reader.read();\n\n    // All reads should produce the original data since the transferred buffer's data is\n    // captured at time of write\n    strictEqual(r1.done, false);\n    deepStrictEqual(Array.from(r1.value), [1, 2, 3, 4, 5, 6, 7, 8]);\n    strictEqual(r2.done, false);\n    deepStrictEqual(Array.from(r2.value), [9, 10, 11, 12, 13, 14, 15, 16]);\n    strictEqual(r3.done, false);\n    deepStrictEqual(Array.from(r3.value), [17, 18, 19, 20, 21, 22, 23, 24]);\n    strictEqual(r4.done, false);\n    deepStrictEqual(Array.from(r4.value), [25, 26, 27, 28, 29, 30, 31, 32]);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/streams/identitytransformstream-byob-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"identitytransformstream-byob-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"identitytransformstream-byob-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"streams_byob_reader_does_not_detach_buffer\",\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/streams/internal-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"internal.h\"\n#include \"readable.h\"\n#include \"standard.h\"\n#include \"writable.h\"\n\n#include <workerd/jsg/jsg-test.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/tests/test-fixture.h>\n\n#include <openssl/rand.h>\n\nnamespace workerd::api {\nnamespace {\n\n// ======================================================================================\n// Shared test helpers\n\n// Simple source that returns EOF immediately\nclass EofSource final: public ReadableStreamSource {\n public:\n  kj::Promise<size_t> tryRead(void*, size_t, size_t) override {\n    return static_cast<size_t>(0);\n  }\n};\n\n// Simple sink that accepts all writes\nclass NoopSink final: public WritableStreamSink {\n public:\n  kj::Promise<void> write(kj::ArrayPtr<const byte>) override {\n    return kj::READY_NOW;\n  }\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>>) override {\n    return kj::READY_NOW;\n  }\n  kj::Promise<void> end() override {\n    return kj::READY_NOW;\n  }\n  void abort(kj::Exception) override {}\n};\n\n// Creates a TestFixture with common flags for stream tests\nTestFixture makeStreamTestFixture() {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  return TestFixture({.featureFlags = flags.asReader()});\n}\n\n// Creates a TestFixture with the abortClearsQueue flag for testing abort behavior\nTestFixture makeAbortClearsQueueTestFixture() {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  flags.setInternalWritableStreamAbortClearsQueue(true);\n  return TestFixture({.featureFlags = flags.asReader()});\n}\n\n// Creates a BYOB-capable ReadableStream\njsg::Ref<ReadableStream> makeByteStream(jsg::Lock& js) {\n  auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n  rs->getController().setup(\n      js, UnderlyingSource{.type = kj::str(\"bytes\")}, StreamQueuingStrategy{});\n  return rs;\n}\n\n// ======================================================================================\n// ReadableStreamSource test implementations\n\ntemplate <int size>\nclass FooStream: public ReadableStreamSource {\n public:\n  FooStream(): ptr(&data[0]), remaining_(size) {\n    KJ_ASSERT(RAND_bytes(data, size) == 1);\n  }\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    maxMaxBytesSeen_ = kj::max(maxMaxBytesSeen_, maxBytes);\n    numreads_++;\n    if (remaining_ == 0) return static_cast<size_t>(0);\n    KJ_ASSERT(minBytes == maxBytes);\n    auto amount = kj::min(remaining_, maxBytes);\n    memcpy(buffer, ptr, amount);\n    ptr += amount;\n    remaining_ -= amount;\n    return amount;\n  }\n\n  kj::ArrayPtr<uint8_t> buf() {\n    return data;\n  }\n\n  size_t remaining() {\n    return remaining_;\n  }\n\n  size_t numreads() {\n    return numreads_;\n  }\n\n  size_t maxMaxBytesSeen() {\n    return maxMaxBytesSeen_;\n  }\n\n private:\n  uint8_t data[size];\n  uint8_t* ptr;\n  size_t remaining_;\n  size_t numreads_ = 0;\n  size_t maxMaxBytesSeen_ = 0;\n};\n\ntemplate <int size>\nclass BarStream: public FooStream<size> {\n public:\n  kj::Maybe<uint64_t> tryGetLength(StreamEncoding encoding) override {\n    return size;\n  }\n};\n\nKJ_TEST(\"test\") {\n  kj::EventLoop loop;\n  kj::WaitScope waitScope(loop);\n\n  // In this first case, the stream does not report a length. The read size\n  // is min(limit, DEFAULT_BUFFER_CHUNK) = min(10001, 131072) = 10001, so the\n  // entire stream is consumed in a single read that returns a short read (10000 < 10001).\n  FooStream<10000> stream;\n\n  stream.readAllBytes(10001)\n      .then([&](auto bytes) {\n    KJ_ASSERT(bytes.size() == 10000);\n    KJ_ASSERT(bytes == stream.buf().first(10000));\n  }).wait(waitScope);\n\n  KJ_ASSERT(stream.numreads() == 1);\n  KJ_ASSERT(stream.maxMaxBytesSeen() == 10001);\n}\n\nKJ_TEST(\"test (text)\") {\n  kj::EventLoop loop;\n  kj::WaitScope waitScope(loop);\n\n  // In this first case, the stream does not report a length. The read size\n  // is min(limit, DEFAULT_BUFFER_CHUNK) = min(10001, 131072) = 10001, so the\n  // entire stream is consumed in a single read that returns a short read (10000 < 10001).\n  FooStream<10000> stream;\n\n  stream.readAllText(10001)\n      .then([&](auto bytes) {\n    KJ_ASSERT(bytes.size() == 10000);\n    KJ_ASSERT(bytes.asBytes() == stream.buf().first(10000));\n  }).wait(waitScope);\n\n  KJ_ASSERT(stream.numreads() == 1);\n  KJ_ASSERT(stream.maxMaxBytesSeen() == 10001);\n}\n\nKJ_TEST(\"test2\") {\n  kj::EventLoop loop;\n  kj::WaitScope waitScope(loop);\n\n  // In this second case, the stream does report a size, so we should see\n  // only one read.\n  BarStream<10000> stream;\n\n  stream.readAllBytes(10001)\n      .then([&](auto bytes) {\n    KJ_ASSERT(bytes.size() == 10000);\n    KJ_ASSERT(bytes == stream.buf().first(10000));\n  }).wait(waitScope);\n\n  KJ_ASSERT(stream.numreads() == 2);\n  KJ_ASSERT(stream.maxMaxBytesSeen() == 10000);\n}\n\nKJ_TEST(\"test2 (text)\") {\n  kj::EventLoop loop;\n  kj::WaitScope waitScope(loop);\n\n  // In this second case, the stream does report a size, so we should see\n  // only one read.\n  BarStream<10000> stream;\n\n  stream.readAllText(10001)\n      .then([&](auto bytes) {\n    KJ_ASSERT(bytes.size() == 10000);\n    KJ_ASSERT(bytes.asBytes() == stream.buf().first(10000));\n  }).wait(waitScope);\n\n  KJ_ASSERT(stream.numreads() == 2);\n  KJ_ASSERT(stream.maxMaxBytesSeen() == 10000);\n}\n\nKJ_TEST(\"zero-length stream\") {\n  kj::EventLoop loop;\n  kj::WaitScope waitScope(loop);\n\n  class Zero: public ReadableStreamSource {\n   public:\n    kj::Promise<size_t> tryRead(void*, size_t, size_t) override {\n      return static_cast<size_t>(0);\n    }\n    kj::Maybe<uint64_t> tryGetLength(StreamEncoding encoding) override {\n      return static_cast<size_t>(0);\n    }\n  };\n\n  Zero zero;\n  zero.readAllBytes(10).then([&](kj::Array<kj::byte> bytes) {\n    KJ_ASSERT(bytes.size() == 0);\n  }).wait(waitScope);\n}\n\nKJ_TEST(\"lying stream\") {\n  kj::EventLoop loop;\n  kj::WaitScope waitScope(loop);\n\n  class Dishonest: public FooStream<10000> {\n   public:\n    kj::Maybe<uint64_t> tryGetLength(StreamEncoding encoding) override {\n      return static_cast<size_t>(10);\n    }\n  };\n\n  Dishonest stream;\n  stream.readAllBytes(10001)\n      .then([&](kj::Array<kj::byte> bytes) {\n    // The stream lies! it says there are only 10 bytes but there are more.\n    // oh well, we at least make sure we get the right result.\n    KJ_ASSERT(bytes.size() == 10000);\n  }).wait(waitScope);\n\n  KJ_ASSERT(stream.numreads() == 1001);\n  KJ_ASSERT(stream.maxMaxBytesSeen() == 10);\n}\n\nKJ_TEST(\"honest small stream\") {\n  kj::EventLoop loop;\n  kj::WaitScope waitScope(loop);\n\n  class HonestSmall: public FooStream<100> {\n   public:\n    kj::Maybe<uint64_t> tryGetLength(StreamEncoding encoding) override {\n      return static_cast<size_t>(100);\n    }\n  };\n\n  HonestSmall stream;\n  stream.readAllBytes(1001).then([&](kj::Array<kj::byte> bytes) {\n    KJ_ASSERT(bytes.size() == 100);\n  }).wait(waitScope);\n\n  KJ_ASSERT(stream.numreads() == 2);\n  KJ_ASSERT(stream.maxMaxBytesSeen(), 100);\n}\n\nKJ_TEST(\"WritableStreamInternalController queue size assertion\") {\n  auto fixture = makeStreamTestFixture();\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    // Make sure that while an internal sink is being piped into, no other writes are\n    // allowed to be queued.\n\n    jsg::Ref<ReadableStream> source = ReadableStream::constructor(env.js, kj::none, kj::none);\n    jsg::Ref<WritableStream> sink =\n        env.js.alloc<WritableStream>(env.context, kj::heap<NoopSink>(), kj::none);\n\n    auto pipeTo = source->pipeTo(env.js, sink.addRef(), PipeToOptions{.preventClose = true});\n\n    KJ_ASSERT(sink->isLocked());\n    try {\n      sink->getWriter(env.js);\n      KJ_FAIL_ASSERT(\"Expected getWriter to throw\");\n    } catch (...) {\n      auto ex = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(ex.getDescription() ==\n          \"expected !stream->isLocked(); jsg.TypeError: This WritableStream \"\n          \"is currently locked to a writer.\");\n    }\n\n    auto buffersource = env.js.bytes(kj::heapArray<kj::byte>(10));\n\n    bool writeFailed = false;\n\n    auto write = sink->getController()\n                     .write(env.js, buffersource.getHandle(env.js))\n                     .catch_(env.js, [&](jsg::Lock& js, jsg::Value value) {\n      writeFailed = true;\n      auto ex = js.exceptionToKj(kj::mv(value));\n      KJ_ASSERT(\n          ex.getDescription() == \"jsg.TypeError: This WritableStream is currently being piped to.\");\n    });\n\n    source->getController().cancel(env.js, kj::none);\n\n    env.js.runMicrotasks();\n\n    KJ_ASSERT(!sink->isLocked());\n    KJ_ASSERT(!sink->getController().isClosedOrClosing());\n    KJ_ASSERT(!sink->getController().isErrored());\n    KJ_ASSERT(sink->getController().isErroring(env.js) == kj::none);\n\n    // Getting a writer at this point does not throw...\n    sink->getWriter(env.js);\n  });\n}\n\nKJ_TEST(\"WritableStreamInternalController operations reject when piped to\") {\n  // Tests that close/flush/tryPipeFrom reject with \"currently being piped to\"\n  // during an active pipe operation.\n  auto fixture = makeStreamTestFixture();\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto source = ReadableStream::constructor(env.js, kj::none, kj::none);\n    auto source2 = ReadableStream::constructor(env.js, kj::none, kj::none);\n    auto sink = env.js.alloc<WritableStream>(env.context, kj::heap<NoopSink>(), kj::none);\n\n    auto pipeTo = source->pipeTo(env.js, sink.addRef(), PipeToOptions{.preventClose = true});\n    KJ_ASSERT(sink->isLocked());\n\n    constexpr auto expectedError =\n        \"jsg.TypeError: This WritableStream is currently being piped to.\"_kj;\n\n    auto expectReject = [&](jsg::Promise<void> promise, bool& flag) {\n      promise.catch_(env.js, [&](jsg::Lock& js, jsg::Value value) {\n        flag = true;\n        KJ_ASSERT(js.exceptionToKj(kj::mv(value)).getDescription() == expectedError);\n      });\n    };\n\n    bool closeFailed = false, flushFailed = false, pipeFailed = false;\n\n    expectReject(sink->getController().close(env.js), closeFailed);\n    expectReject(sink->getController().flush(env.js), flushFailed);\n\n    KJ_IF_SOME(secondPipe,\n        sink->getController().tryPipeFrom(\n            env.js, source2.addRef(), PipeToOptions{.preventClose = true})) {\n      expectReject(kj::mv(secondPipe), pipeFailed);\n    } else {\n      KJ_FAIL_ASSERT(\"Expected tryPipeFrom to return a promise\");\n    }\n\n    source->getController().cancel(env.js, kj::none);\n    env.js.runMicrotasks();\n\n    KJ_ASSERT(closeFailed);\n    KJ_ASSERT(flushFailed);\n    KJ_ASSERT(pipeFailed);\n  });\n}\n\nKJ_TEST(\"WritableStreamInternalController observability\") {\n  auto fixture = makeStreamTestFixture();\n\n  class MyObserver final: public ByteStreamObserver {\n   public:\n    void onChunkEnqueued(size_t bytes) override {\n      ++queueSize;\n      queueSizeBytes += bytes;\n    };\n    void onChunkDequeued(size_t bytes) override {\n      queueSizeBytes -= bytes;\n      --queueSize;\n    };\n    uint64_t queueSize = 0;\n    uint64_t queueSizeBytes = 0;\n  };\n\n  auto myObserver = kj::heap<MyObserver>();\n  auto& observer = *myObserver;\n  kj::Maybe<jsg::Ref<WritableStream>> stream;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) -> kj::Promise<void> {\n    stream = env.js.alloc<WritableStream>(env.context, kj::heap<NoopSink>(), kj::mv(myObserver));\n\n    auto write = [&](size_t size) {\n      auto buffersource = env.js.bytes(kj::heapArray<kj::byte>(size));\n      return env.context.awaitJs(env.js,\n          KJ_ASSERT_NONNULL(stream)->getController().write(env.js, buffersource.getHandle(env.js)));\n    };\n\n    KJ_ASSERT(observer.queueSize == 0);\n    KJ_ASSERT(observer.queueSizeBytes == 0);\n\n    auto builder = kj::heapArrayBuilder<kj::Promise<void>>(2);\n    builder.add(write(1));\n\n    KJ_ASSERT(observer.queueSize == 1);\n    KJ_ASSERT(observer.queueSizeBytes == 1);\n\n    builder.add(write(10));\n\n    KJ_ASSERT(observer.queueSize == 2);\n    KJ_ASSERT(observer.queueSizeBytes == 11);\n\n    return kj::joinPromises(builder.finish());\n  });\n\n  KJ_ASSERT(observer.queueSize == 0);\n  KJ_ASSERT(observer.queueSizeBytes == 0);\n}\n\n// Test for use-after-free fix in pipeLoop when abort is called during pending read.\n// The fix ensures the Pipe::State is ref-counted and survives until all callbacks complete.\nKJ_TEST(\"WritableStreamInternalController pipeLoop abort during pending read\") {\n  auto fixture = makeAbortClearsQueueTestFixture();\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    // Create a JavaScript-backed ReadableStream.\n    // The pull function will be called when the pipe tries to read.\n    // We use a JS-backed stream so that pipeLoop is used (not the kj pipe path).\n    //\n    // We need to simulate:\n    // 1. First read succeeds with some data\n    // 2. Second read is pending (the promise from pull is not resolved)\n    // 3. While pending, we abort the writable stream\n    //\n    // Using an UnderlyingSource with a pull callback that enqueues data once,\n    // then on the second call returns without enqueuing (leaving the read pending).\n\n    int pullCount = 0;\n    jsg::Ref<ReadableStream> source = ReadableStream::constructor(env.js,\n        UnderlyingSource{.pull =\n                             [&pullCount](jsg::Lock& js, UnderlyingSource::Controller controller) {\n      pullCount++;\n      auto& c = KJ_ASSERT_NONNULL(controller.tryGet<jsg::Ref<ReadableStreamDefaultController>>());\n      if (pullCount == 1) {\n        // First pull: enqueue some data so the pipe loop can make progress\n        auto data = js.bytes(kj::heapArray<kj::byte>({1, 2, 3, 4}));\n        c->enqueue(js, data.getHandle(js));\n      }\n      // Second pull onwards: don't enqueue anything, leaving the read pending.\n      // This simulates an async data source that hasn't received data yet.\n      // The promise returned by read() will be pending.\n      return js.resolvedPromise();\n    }},\n        kj::none);\n\n    jsg::Ref<WritableStream> sink =\n        env.js.alloc<WritableStream>(env.context, kj::heap<NoopSink>(), kj::none);\n\n    auto pipeTo = source->pipeTo(env.js, sink.addRef(), PipeToOptions{});\n    pipeTo.markAsHandled(env.js);\n    env.js.runMicrotasks();\n\n    // Abort while pipeLoop is waiting for a pending read\n    auto abortPromise = sink->getController().abort(env.js, env.js.v8TypeError(\"Test abort\"_kj));\n    abortPromise.markAsHandled(env.js);\n    env.js.runMicrotasks();\n\n    // If we get here without crashing, the test passes\n    KJ_ASSERT(pullCount >= 1);\n  });\n}\n\n// ======================================================================================\n// DrainingReader tests for internal streams\n//\n// The internal stream implementation's drainingRead() behaves like a normal read() -\n// it returns at most one chunk at a time rather than draining all buffered data.\n// This is because internal streams are backed by kj I/O which is inherently async\n// and doesn't have internal JS-side buffering.\n\nKJ_TEST(\"DrainingReader basic creation and locking (internal stream)\") {\n  auto fixture = makeStreamTestFixture();\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto rs = env.js.alloc<ReadableStream>(env.context, kj::heap<EofSource>());\n    KJ_ASSERT(!rs->isLocked());\n\n    KJ_IF_SOME(reader, DrainingReader::create(env.js, *rs)) {\n      KJ_ASSERT(rs->isLocked());\n      KJ_ASSERT(reader->isAttached());\n\n      reader->releaseLock(env.js);\n      KJ_ASSERT(!rs->isLocked());\n      KJ_ASSERT(!reader->isAttached());\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader cannot be created on locked internal stream\") {\n  auto fixture = makeStreamTestFixture();\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto rs = env.js.alloc<ReadableStream>(env.context, kj::heap<EofSource>());\n\n    KJ_IF_SOME(reader1, DrainingReader::create(env.js, *rs)) {\n      KJ_ASSERT(rs->isLocked());\n      KJ_ASSERT(DrainingReader::create(env.js, *rs) == kj::none);\n      reader1->releaseLock(env.js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create first DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader read after releaseLock rejects (internal stream)\") {\n  auto fixture = makeStreamTestFixture();\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto rs = env.js.alloc<ReadableStream>(env.context, kj::heap<EofSource>());\n\n    KJ_IF_SOME(reader, DrainingReader::create(env.js, *rs)) {\n      reader->releaseLock(env.js);\n\n      bool rejected = false;\n      reader->read(env.js).catch_(env.js, [&](jsg::Lock&, jsg::Value) -> DrainingReadResult {\n        rejected = true;\n        return {.done = true};\n      });\n      env.js.runMicrotasks();\n      KJ_ASSERT(rejected);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader with maxRead parameter (internal stream)\") {\n  // Test that the maxRead parameter is respected for internal streams\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n\n  TestFixture fixture({.featureFlags = flags.asReader()});\n\n  bool testCompleted = false;\n  size_t lastMaxBytes = 0;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) -> kj::Promise<void> {\n    class TestSource final: public ReadableStreamSource {\n     public:\n      explicit TestSource(size_t& maxBytesOut): lastMaxBytesOut(maxBytesOut) {}\n      kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n        readCount++;\n        // Note: maxBytes should be limited by the maxRead parameter\n        lastMaxBytesOut = maxBytes;\n        if (readCount == 1) {\n          // Return less than maxBytes\n          auto toWrite = kj::min(maxBytes, static_cast<size_t>(100));\n          memset(buffer, 'x', toWrite);\n          return toWrite;\n        }\n        return static_cast<size_t>(0);  // EOF\n      }\n      uint readCount = 0;\n      size_t& lastMaxBytesOut;\n    };\n\n    auto rs = env.js.alloc<ReadableStream>(env.context, kj::heap<TestSource>(lastMaxBytes));\n\n    auto maybeReader = DrainingReader::create(env.js, *rs);\n    KJ_ASSERT(maybeReader != kj::none, \"Failed to create DrainingReader\");\n    auto reader = kj::mv(KJ_ASSERT_NONNULL(maybeReader));\n\n    // Pass a small maxRead value\n    auto readPromise = reader->read(env.js, 50);\n\n    return env.context.awaitJs(env.js,\n        kj::mv(readPromise)\n            .then(env.js,\n                [&testCompleted, reader = kj::mv(reader)](\n                    jsg::Lock& js, DrainingReadResult&& result) mutable {\n      KJ_ASSERT(result.chunks.size() == 1);\n      // The internal implementation uses maxRead to allocate the buffer\n      KJ_ASSERT(result.chunks[0].size() <= 50);\n      KJ_ASSERT(!result.done);\n      reader->releaseLock(js);\n      testCompleted = true;\n    }));\n  });\n\n  KJ_ASSERT(testCompleted);\n  // Verify maxBytes was limited\n  KJ_ASSERT(lastMaxBytes == 50);\n}\n\nKJ_TEST(\"DrainingReader with maxRead = 0 (internal stream)\") {\n  // Test that the maxRead = 0 parameter is respected for internal streams\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n\n  TestFixture fixture({.featureFlags = flags.asReader()});\n\n  bool testCompleted = false;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) -> kj::Promise<void> {\n    class TestSource final: public ReadableStreamSource {\n     public:\n      explicit TestSource() = default;\n      kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n        KJ_FAIL_ASSERT(\"tryRead should not be called when maxRead = 0\");\n      }\n    };\n\n    auto rs = env.js.alloc<ReadableStream>(env.context, kj::heap<TestSource>());\n\n    auto maybeReader = DrainingReader::create(env.js, *rs);\n    KJ_ASSERT(maybeReader != kj::none, \"Failed to create DrainingReader\");\n    auto reader = kj::mv(KJ_ASSERT_NONNULL(maybeReader));\n\n    auto readPromise = reader->read(env.js, 0);\n\n    return env.context.awaitJs(env.js,\n        kj::mv(readPromise)\n            .then(env.js,\n                [&testCompleted, reader = kj::mv(reader)](\n                    jsg::Lock& js, DrainingReadResult&& result) mutable {\n      KJ_ASSERT(result.chunks.size() == 0);\n      KJ_ASSERT(!result.done);\n      reader->releaseLock(js);\n      testCompleted = true;\n    }));\n  });\n\n  KJ_ASSERT(testCompleted);\n}\n\nKJ_TEST(\"DrainingReader on stream with pending closure (internal stream)\") {\n  auto fixture = makeStreamTestFixture();\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto rs = env.js.alloc<ReadableStream>(env.context, kj::heap<EofSource>());\n    rs->getController().setPendingClosure();\n\n    KJ_IF_SOME(reader, DrainingReader::create(env.js, *rs)) {\n      bool rejected = false;\n      reader->read(env.js).catch_(\n          env.js, [&](jsg::Lock& js, jsg::Value reason) -> DrainingReadResult {\n        rejected = true;\n        auto msg = kj::str(reason.getHandle(js));\n        KJ_ASSERT(msg.contains(\"closing\"), msg);\n        return {.done = true};\n      });\n      env.js.runMicrotasks();\n      KJ_ASSERT(rejected);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader on closed stream (internal stream)\") {\n  auto fixture = makeStreamTestFixture();\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto rs = env.js.alloc<ReadableStream>(env.context, kj::heap<EofSource>());\n    rs->getController().cancel(env.js, kj::none);\n\n    KJ_IF_SOME(reader, DrainingReader::create(env.js, *rs)) {\n      bool done = false;\n      reader->read(env.js).then(env.js, [&](jsg::Lock&, DrainingReadResult&& result) {\n        done = true;\n        KJ_ASSERT(result.done);\n        KJ_ASSERT(result.chunks.size() == 0);\n      });\n      env.js.runMicrotasks();\n      KJ_ASSERT(done);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader error propagation (internal stream)\") {\n  // Tests that I/O errors from tryRead are propagated and put the stream in errored state.\n  auto fixture = makeStreamTestFixture();\n\n  bool testCompleted = false;\n  KJ_EXPECT_LOG(ERROR, \"Simulated I/O error\");\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) -> kj::Promise<void> {\n    class TestSource final: public ReadableStreamSource {\n     public:\n      kj::Promise<size_t> tryRead(void*, size_t, size_t) override {\n        return KJ_EXCEPTION(FAILED, \"Simulated I/O error\");\n      }\n    };\n\n    auto rs = env.js.alloc<ReadableStream>(env.context, kj::heap<TestSource>());\n    auto maybeReader = DrainingReader::create(env.js, *rs);\n    auto reader = kj::mv(KJ_ASSERT_NONNULL(maybeReader));\n\n    return env.context.awaitJs(env.js,\n        reader->read(env.js)\n            .catch_(env.js,\n                [&, reader = kj::mv(reader)](\n                    jsg::Lock& js, jsg::Value) mutable -> DrainingReadResult {\n      reader->releaseLock(js);\n      testCompleted = true;\n      return {.done = true};\n    }).then(env.js, [](jsg::Lock&, DrainingReadResult&&) {}));\n  });\n\n  KJ_ASSERT(testCompleted);\n}\n\nKJ_TEST(\"DrainingReader concurrent read rejection (internal stream)\") {\n  auto fixture = makeStreamTestFixture();\n  bool testCompleted = false;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) -> kj::Promise<void> {\n    auto paf = kj::newPromiseAndFulfiller<size_t>();\n\n    class TestSource final: public ReadableStreamSource {\n     public:\n      explicit TestSource(kj::Promise<size_t> p): promise(kj::mv(p)) {}\n      kj::Promise<size_t> tryRead(void*, size_t, size_t) override {\n        return kj::mv(promise);\n      }\n      kj::Promise<size_t> promise;\n    };\n\n    auto rs = env.js.alloc<ReadableStream>(env.context, kj::heap<TestSource>(kj::mv(paf.promise)));\n    auto maybeReader = DrainingReader::create(env.js, *rs);\n    auto reader = kj::mv(KJ_ASSERT_NONNULL(maybeReader));\n\n    auto firstRead = reader->read(env.js);\n\n    // Second read while first is pending should reject synchronously\n    bool rejected = false;\n    reader->read(env.js).catch_(\n        env.js, [&](jsg::Lock& js, jsg::Value reason) -> DrainingReadResult {\n      rejected = true;\n      auto msg = kj::str(reason.getHandle(js));\n      KJ_ASSERT(msg.contains(\"single pending read\"), msg);\n      return {.done = true};\n    });\n    env.js.runMicrotasks();\n    KJ_ASSERT(rejected);\n\n    paf.fulfiller->fulfill(0);  // Complete first read with EOF\n\n    return env.context.awaitJs(env.js,\n        kj::mv(firstRead).then(\n            env.js, [&, reader = kj::mv(reader)](jsg::Lock& js, DrainingReadResult&&) mutable {\n      reader->releaseLock(js);\n      testCompleted = true;\n    }));\n  });\n\n  KJ_ASSERT(testCompleted);\n}\n\n// ======================================================================================\n// ReadableStreamBYOBReader validation tests\n\nKJ_TEST(\"ReadableStreamBYOBReader rejects read with zero-sized buffer\") {\n  KJ_EXPECT_LOG(ERROR, \"read() on a BYOB reader requires a positive-sized TypedArray\");\n  auto fixture = makeStreamTestFixture();\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto rs = makeByteStream(env.js);\n    auto reader = ReadableStreamBYOBReader::constructor(env.js, rs.addRef());\n\n    auto buffer = v8::ArrayBuffer::New(env.js.v8Isolate, 0);\n    auto view = v8::Uint8Array::New(buffer, 0, 0);\n\n    bool rejected = false;\n    reader->read(env.js, view, kj::none)\n        .catch_(env.js, [&](jsg::Lock& js, jsg::Value reason) -> ReadResult {\n      rejected = true;\n      auto ex = js.exceptionToKj(kj::mv(reason));\n      KJ_ASSERT(ex.getDescription().contains(\n                    \"read() on a BYOB reader requires a positive-sized TypedArray\"),\n          ex);\n      return {.done = true};\n    });\n    env.js.runMicrotasks();\n    KJ_ASSERT(rejected, \"Expected read() to reject with zero-sized buffer\");\n  });\n}\n\nKJ_TEST(\"ReadableStreamBYOBReader rejects read with atLeast=0\") {\n  auto fixture = makeStreamTestFixture();\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto rs = makeByteStream(env.js);\n    auto reader = ReadableStreamBYOBReader::constructor(env.js, rs.addRef());\n\n    auto buffer = v8::ArrayBuffer::New(env.js.v8Isolate, 10);\n    auto view = v8::Uint8Array::New(buffer, 0, 10);\n\n    bool rejected = false;\n    reader->readAtLeast(env.js, 0, view)\n        .catch_(env.js, [&](jsg::Lock& js, jsg::Value reason) -> ReadResult {\n      rejected = true;\n      auto ex = js.exceptionToKj(kj::mv(reason));\n      KJ_ASSERT(\n          ex.getDescription().contains(\"Requested invalid minimum number of bytes to read (0)\"),\n          ex);\n      return {.done = true};\n    });\n    env.js.runMicrotasks();\n    KJ_ASSERT(rejected, \"Expected readAtLeast() to reject with atLeast=0\");\n  });\n}\n\nKJ_TEST(\"ReadableStreamBYOBReader rejects read when atLeast exceeds buffer size\") {\n  auto fixture = makeStreamTestFixture();\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto rs = makeByteStream(env.js);\n    auto reader = ReadableStreamBYOBReader::constructor(env.js, rs.addRef());\n\n    auto buffer = v8::ArrayBuffer::New(env.js.v8Isolate, 10);\n    auto view = v8::Uint8Array::New(buffer, 0, 10);\n\n    bool rejected = false;\n    reader->readAtLeast(env.js, 20, view)\n        .catch_(env.js, [&](jsg::Lock& js, jsg::Value reason) -> ReadResult {\n      rejected = true;\n      auto ex = js.exceptionToKj(kj::mv(reason));\n      KJ_ASSERT(\n          ex.getDescription().contains(\"Minimum bytes to read (20) exceeds size of buffer (10)\"),\n          ex);\n      return {.done = true};\n    });\n    env.js.runMicrotasks();\n    KJ_ASSERT(rejected, \"Expected readAtLeast() to reject when atLeast exceeds buffer size\");\n  });\n}\n\nKJ_TEST(\"ReadableStreamBYOBReader rejects read after releaseLock\") {\n  auto fixture = makeStreamTestFixture();\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto rs = makeByteStream(env.js);\n    auto reader = ReadableStreamBYOBReader::constructor(env.js, rs.addRef());\n    reader->releaseLock(env.js);\n\n    auto buffer = v8::ArrayBuffer::New(env.js.v8Isolate, 10);\n    auto view = v8::Uint8Array::New(buffer, 0, 10);\n\n    bool rejected = false;\n    reader->read(env.js, view, kj::none)\n        .catch_(env.js, [&](jsg::Lock& js, jsg::Value reason) -> ReadResult {\n      rejected = true;\n      auto ex = js.exceptionToKj(kj::mv(reason));\n      KJ_ASSERT(ex.getDescription().contains(\"This ReadableStream reader has been released\"), ex);\n      return {.done = true};\n    });\n    env.js.runMicrotasks();\n    KJ_ASSERT(rejected, \"Expected read() to reject after releaseLock\");\n  });\n}\n\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/internal.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"internal.h\"\n\n#include \"identity-transform-stream.h\"\n#include \"readable.h\"\n#include \"writable.h\"\n\n#include <workerd/api/util.h>\n#include <workerd/io/features.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/util/string-buffer.h>\n\n#include <kj/vector.h>\n\nnamespace workerd::api {\n\nnamespace {\n// Use this in places where the exception thrown would cause finalizers to run. Your exception\n// will not go anywhere, but we'll log the exception message to the console until the problem this\n// papers over is fixed.\n[[noreturn]] void throwTypeErrorAndConsoleWarn(kj::StringPtr message) {\n  KJ_IF_SOME(context, IoContext::tryCurrent()) {\n    if (context.hasWarningHandler()) {\n      context.logWarning(message);\n    }\n  }\n\n  kj::throwFatalException(kj::Exception(kj::Exception::Type::FAILED, __FILE__, __LINE__,\n      kj::str(JSG_EXCEPTION(TypeError) \": \", message)));\n}\n\nkj::Promise<void> pumpTo(ReadableStreamSource& input, WritableStreamSink& output, bool end) {\n  kj::byte buffer[65536]{};\n\n  while (true) {\n    auto amount = co_await input.tryRead(buffer, 1, kj::size(buffer));\n\n    if (amount == 0) {\n      if (end) {\n        co_await output.end();\n      }\n      co_return;\n    }\n\n    co_await output.write(kj::arrayPtr(buffer, amount));\n  }\n}\n\n// Modified from AllReader in kj/async-io.c++.\nclass AllReader final {\n public:\n  explicit AllReader(ReadableStreamSource& input, uint64_t limit): input(input), limit(limit) {\n    JSG_REQUIRE(limit > 0, TypeError, \"Memory limit exceeded before EOF.\");\n    KJ_IF_SOME(length, input.tryGetLength(StreamEncoding::IDENTITY)) {\n      // Oh hey, we might be able to bail early.\n      JSG_REQUIRE(length < limit, TypeError, \"Memory limit would be exceeded before EOF.\");\n    }\n  }\n  KJ_DISALLOW_COPY_AND_MOVE(AllReader);\n\n  kj::Promise<kj::Array<kj::byte>> readAllBytes() {\n    return read<kj::byte>();\n  }\n\n  kj::Promise<kj::String> readAllText(\n      ReadAllTextOption option = ReadAllTextOption::NULL_TERMINATE) {\n    auto data = co_await read<char>(option);\n    co_return kj::String(kj::mv(data));\n  }\n\n private:\n  ReadableStreamSource& input;\n  uint64_t limit;\n\n  template <typename T>\n  kj::Promise<kj::Array<T>> read(ReadAllTextOption option = ReadAllTextOption::NONE) {\n    // There are a few complexities in this operation that make it difficult to completely\n    // optimize. The most important is that even if a stream reports an expected length\n    // using tryGetLength, we really don't know how much data the stream will produce until\n    // we try to read it. The only signal we have that the stream is done producing data\n    // is a zero-length result from tryRead. Unfortunately, we have to allocate a buffer\n    // in advance of calling tryRead so we have to guess a bit at the size of the buffer\n    // to allocate.\n    //\n    // In the previous implementation of this method, we would just blindly allocate a\n    // 4096 byte buffer on every allocation, limiting each read iteration to a maximum\n    // of 4096 bytes. This works fine for streams producing a small amount of data but\n    // risks requiring a greater number of loop iterations and small allocations for streams\n    // that produce larger amounts of data. Also in the previous implementation, every\n    // loop iteration would allocate a new buffer regardless of how much of the previous\n    // allocation was actually used -- so a stream that produces only 4000 bytes total\n    // but only provides 10 bytes per iteration would end up with 400 reads and 400 4096\n    // byte allocations. Doh! Fortunately our stream implementations tend to be a bit\n    // smarter than that but it's still a worst case possibility that it's likely better\n    // to avoid.\n    //\n    // So this implementation does things a bit differently.\n    // First, we check to see if the stream can give an estimate on how much data it\n    // expects to produce. If that length is within a given threshold, then best case\n    // is we can perform the entire read with at most two allocations and two calls to\n    // tryRead. The first allocation will be for the entire expected size of the stream,\n    // which the first tryRead will attempt to fulfill completely. In the best case the\n    // stream provides all of the data. The next allocation would be smaller and would\n    // end up resulting in a zero-length read signaling that we are done. Hooray!\n    //\n    // Not everything can be best case scenario tho, unfortunately. If our first tryRead\n    // does not fully consume the stream or fully fill the destination buffer, we're\n    // going to need to try again. It is possible that the new allocation in the next\n    // iteration will be wasted if the stream doesn't have any more data so it's important\n    // for us to try to be conservative with the allocation. If the running total of data\n    // we've seen so far is equal to or greater than the expected total length of the stream,\n    // then the most likely case is that the next read will be zero-length -- but unfortunately\n    // we can't know for sure! So for this we will fall back to a more conservative allocation\n    // which is either MIN_BUFFER_CHUNK or the calculated amountToRead, whichever is the lower\n    // number.\n    //\n    // The chunk sizes here are intentionally large to avoid pathological allocation patterns\n    // when reading from tee'd streams. The KJ tee buffers data in 16KB chunks; if we read\n    // with smaller buffers, each partial consume of a tee chunk allocates a new heap array\n    // for the remainder. With many green threads contending on tcmalloc, the cumulative\n    // allocation overhead can exceed watchdog timeouts. Using 128KB default reads ensures\n    // tee chunks are consumed whole, eliminating the amplification entirely.\n\n    kj::Vector<kj::Array<T>> parts;\n    uint64_t runningTotal = 0;\n    static constexpr uint64_t MIN_BUFFER_CHUNK = 65536;                     // 64KB\n    static constexpr uint64_t DEFAULT_BUFFER_CHUNK = 131072;                // 128KB\n    static constexpr uint64_t MAX_BUFFER_CHUNK = DEFAULT_BUFFER_CHUNK * 4;  // 512KB\n\n    // If we know in advance how much data we'll be reading, then we can attempt to\n    // optimize the loop here by setting the value specifically so we are only\n    // allocating at most twice. But, to be safe, let's enforce an upper bound on each\n    // allocation even if we do know the total.\n    kj::Maybe<uint64_t> maybeLength = input.tryGetLength(StreamEncoding::IDENTITY);\n\n    // The amountToRead is the regular allocation size we'll use right up until we've\n    // read the number of expected bytes (if known). This number is calculated as the\n    // minimum of (limit, MAX_BUFFER_CHUNK, maybeLength or DEFAULT_BUFFER_CHUNK). In\n    // the best case scenario, this number is calculated such that we can read the\n    // entire stream in one go if the amount of data is small enough and the stream\n    // is well behaved.\n    // If the stream does report a length, once we've read that number of bytes, we'll\n    // fallback to the conservativeAllocation.\n    uint64_t amountToRead =\n        kj::min(limit, kj::min(MAX_BUFFER_CHUNK, maybeLength.orDefault(DEFAULT_BUFFER_CHUNK)));\n    // amountToRead can be zero if the stream reported a zero-length. While the stream could\n    // be lying about its length, let's skip reading anything in this case.\n    if (amountToRead > 0) {\n      for (;;) {\n        auto bytes = kj::heapArray<T>(amountToRead);\n        // Note that we're passing amountToRead as the *minBytes* here so the tryRead should\n        // attempt to fill the entire buffer. If it doesn't, the implication is that we read\n        // everything.\n        uint64_t amount = co_await input.tryRead(bytes.begin(), amountToRead, amountToRead);\n        KJ_DASSERT(amount <= amountToRead);\n\n        runningTotal += amount;\n        JSG_REQUIRE(runningTotal < limit, TypeError, \"Memory limit exceeded before EOF.\");\n\n        if (amount < amountToRead) {\n          // The stream has indicated that we're all done by returning a value less than the\n          // full buffer length.\n          // It is possible/likely that at least some amount of data was written to the buffer.\n          // In which case we want to add that subset to the parts list here before we exit\n          // the loop.\n          if (amount > 0) {\n            parts.add(bytes.first(amount).attach(kj::mv(bytes)));\n          }\n          break;\n        }\n\n        // Because we specify minSize equal to maxSize in the tryRead above, we should only\n        // get here if the buffer was completely filled by the read. If it wasn't completely\n        // filled, that is an indication that the stream is complete which is handled above.\n        KJ_DASSERT(amount == bytes.size());\n        parts.add(kj::mv(bytes));\n\n        // If the stream provided an expected length and our running total is equal to\n        // or greater than that length then we assume we're done.\n        KJ_IF_SOME(length, maybeLength) {\n          if (runningTotal >= length) {\n            // We've read everything we expect to read but some streams need to be read\n            // completely in order to properly finish and other streams might lie (although\n            // they shouldn't). Sigh. So we're going to make the next allocation potentially\n            // smaller and keep reading until we get a zero length. In the best case, the next\n            // read is going to be zero length but we have to try which will require at least\n            // one additional (potentially wasted) allocation. (If we don't there are multiple\n            // test failures).\n            amountToRead = kj::min(MIN_BUFFER_CHUNK, amountToRead);\n            continue;\n          }\n        }\n      }\n    }\n\n    KJ_IF_SOME(length, maybeLength) {\n      if (runningTotal > length) {\n        // Realistically runningTotal should never be more than length so we'll emit\n        // a warning if it is just so we know. It would be indicative of a bug somewhere\n        // in the implementation.\n        KJ_LOG(WARNING, \"ReadableStream provided more data than advertised\", runningTotal, length);\n      }\n    }\n\n    // Strip UTF-8 BOM if requested\n    size_t skipBytes = 0;\n    if ((option & ReadAllTextOption::STRIP_BOM) && parts.size() > 0 &&\n        hasUtf8Bom(parts[0].asBytes())) {\n      skipBytes = UTF8_BOM_SIZE;\n      runningTotal -= UTF8_BOM_SIZE;\n    }\n\n    if (option & ReadAllTextOption::NULL_TERMINATE) {\n      auto out = kj::heapArray<T>(runningTotal + 1);\n      out[runningTotal] = '\\0';\n      copyInto<T>(out, parts.asPtr(), skipBytes);\n      co_return kj::mv(out);\n    }\n\n    // As an optimization, if there's only a single part in the list, we can avoid\n    // further copies.\n    if (parts.size() == 1) {\n      co_return kj::mv(parts[0]);\n    }\n\n    auto out = kj::heapArray<T>(runningTotal);\n    copyInto<T>(out, parts.asPtr());\n    co_return kj::mv(out);\n  }\n\n  template <typename T>\n  void copyInto(kj::ArrayPtr<T> out, kj::ArrayPtr<kj::Array<T>> in, size_t skipBytes = 0) {\n    for (auto& part: in) {\n      if (out.size() == 0) {\n        break;\n      }\n      // The skipBytes are used to skip the BOM on the first part only.\n      KJ_DASSERT(skipBytes <= part.size());\n      auto slicedPart = skipBytes ? part.slice(skipBytes) : part;\n      skipBytes = 0;\n      if (slicedPart.size() == 0) {\n        continue;\n      }\n      KJ_DASSERT(slicedPart.size() <= out.size());\n      out.first(slicedPart.size()).copyFrom(slicedPart);\n      out = out.slice(slicedPart.size());\n    }\n  }\n};\n\nkj::Exception reasonToException(jsg::Lock& js,\n    jsg::Optional<v8::Local<v8::Value>> maybeReason,\n    kj::String defaultDescription = kj::str(JSG_EXCEPTION(Error) \": Stream was cancelled.\")) {\n  KJ_IF_SOME(reason, maybeReason) {\n    return js.exceptionToKj(js.v8Ref(reason));\n  } else {\n    // We get here if the caller is something like `r.cancel()` (or `r.cancel(undefined)`).\n    return kj::Exception(\n        kj::Exception::Type::FAILED, __FILE__, __LINE__, kj::mv(defaultDescription));\n  }\n}\n\n// =======================================================================================\n\n// Adapt ReadableStreamSource to kj::AsyncInputStream's interface for use with `kj::newTee()`.\nclass TeeAdapter final: public kj::AsyncInputStream {\n public:\n  explicit TeeAdapter(kj::Own<ReadableStreamSource> inner): inner(kj::mv(inner)) {}\n\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    return inner->tryRead(buffer, minBytes, maxBytes);\n  }\n\n  kj::Maybe<uint64_t> tryGetLength() override {\n    return inner->tryGetLength(StreamEncoding::IDENTITY);\n  }\n\n private:\n  kj::Own<ReadableStreamSource> inner;\n};\n\nclass TeeBranch final: public ReadableStreamSource {\n public:\n  explicit TeeBranch(kj::Own<kj::AsyncInputStream> inner): inner(kj::mv(inner)) {}\n\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    return inner->tryRead(buffer, minBytes, maxBytes);\n  }\n\n  kj::Promise<DeferredProxy<void>> pumpTo(WritableStreamSink& output, bool end) override {\n#ifdef KJ_NO_RTTI\n    // Yes, I'm paranoid.\n    static_assert(!KJ_NO_RTTI, \"Need RTTI for correctness\");\n#endif\n\n    // HACK: If `output` is another TransformStream, we don't allow pumping to it, in order to\n    //   guarantee that we can't create cycles. Note that currently TeeBranch only ever wraps\n    //   TransformStreams, never system streams.\n    JSG_REQUIRE(!isIdentityTransformStream(output), TypeError,\n        \"Inter-TransformStream ReadableStream.pipeTo() is not implemented.\");\n\n    // It is important we actually call `inner->pumpTo()` so that `kj::newTee()` is aware of this\n    // pump operation's backpressure. So we can't use the default `ReadableStreamSource::pumpTo()`\n    // implementation, and have to implement our own.\n\n    PumpAdapter outputAdapter(output);\n    co_await inner->pumpTo(outputAdapter);\n\n    if (end) {\n      co_await output.end();\n    }\n\n    // We only use `TeeBranch` when a locally-sourced stream was tee'd (because system streams\n    // implement `tryTee()` in a different way that doesn't use `TeeBranch`). So, we know that\n    // none of the pump can be performed without the IoContext active, and thus we do not\n    // `KJ_CO_MAGIC BEGIN_DEFERRED_PROXYING`.\n    co_return;\n  }\n\n  kj::Maybe<uint64_t> tryGetLength(StreamEncoding encoding) override {\n    if (encoding == StreamEncoding::IDENTITY) {\n      return inner->tryGetLength();\n    } else {\n      return kj::none;\n    }\n  }\n\n  kj::Maybe<Tee> tryTee(uint64_t limit) override {\n    KJ_IF_SOME(t, inner->tryTee(limit)) {\n      auto branch = kj::heap<TeeBranch>(newTeeErrorAdapter(kj::mv(t)));\n      auto consumed = kj::heap<TeeBranch>(kj::mv(inner));\n      return Tee{kj::mv(branch), kj::mv(consumed)};\n    }\n\n    return kj::none;\n  }\n\n  void cancel(kj::Exception reason) override {\n    // TODO(someday): What to do?\n  }\n\n private:\n  // Adapt WritableStreamSink to kj::AsyncOutputStream's interface for use in\n  // `TeeBranch::pumpTo()`. If you squint, the write logic looks very similar to TeeAdapter's\n  // read logic.\n  class PumpAdapter final: public kj::AsyncOutputStream {\n   public:\n    explicit PumpAdapter(WritableStreamSink& inner): inner(inner) {}\n\n    kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override {\n      return inner.write(buffer);\n    }\n\n    kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {\n      return inner.write(pieces);\n    }\n\n    kj::Promise<void> whenWriteDisconnected() override {\n      KJ_UNIMPLEMENTED(\"whenWriteDisconnected() not expected on PumpAdapter\");\n    }\n\n    WritableStreamSink& inner;\n  };\n\n  kj::Own<kj::AsyncInputStream> inner;\n};\n}  // namespace\n\n// =======================================================================================\n\nkj::Promise<DeferredProxy<void>> ReadableStreamSource::pumpTo(\n    WritableStreamSink& output, bool end) {\n  KJ_IF_SOME(p, output.tryPumpFrom(*this, end)) {\n    return kj::mv(p);\n  }\n\n  // Non-optimized pumpTo() is presumed to require the IoContext to remain live, so don't do\n  // anything in the deferred proxy part.\n  return addNoopDeferredProxy(api::pumpTo(*this, output, end));\n}\n\nkj::Maybe<uint64_t> ReadableStreamSource::tryGetLength(StreamEncoding encoding) {\n  return kj::none;\n}\n\nkj::Promise<kj::Array<byte>> ReadableStreamSource::readAllBytes(uint64_t limit) {\n  try {\n    AllReader allReader(*this, limit);\n    co_return co_await allReader.readAllBytes();\n  } catch (...) {\n    // TODO(soon): Temporary logging.\n    auto ex = kj::getCaughtExceptionAsKj();\n    if (ex.getDescription().endsWith(\"exceeded before EOF.\")) {\n      LOG_WARNING_PERIODICALLY(\"NOSENTRY Internal Stream readAllBytes - Exceeded limit\");\n    }\n    kj::throwFatalException(kj::mv(ex));\n  }\n}\n\nkj::Promise<kj::String> ReadableStreamSource::readAllText(\n    uint64_t limit, ReadAllTextOption option) {\n  try {\n    AllReader allReader(*this, limit);\n    co_return co_await allReader.readAllText(option);\n  } catch (...) {\n    // TODO(soon): Temporary logging.\n    auto ex = kj::getCaughtExceptionAsKj();\n    if (ex.getDescription().endsWith(\"exceeded before EOF.\")) {\n      LOG_WARNING_PERIODICALLY(\"NOSENTRY Internal Stream readAllText - Exceeded limit\");\n    }\n    kj::throwFatalException(kj::mv(ex));\n  }\n}\n\nvoid ReadableStreamSource::cancel(kj::Exception reason) {}\n\nkj::Maybe<ReadableStreamSource::Tee> ReadableStreamSource::tryTee(uint64_t limit) {\n  return kj::none;\n}\n\nkj::Maybe<kj::Promise<DeferredProxy<void>>> WritableStreamSink::tryPumpFrom(\n    ReadableStreamSource& input, bool end) {\n  return kj::none;\n}\n\n// =======================================================================================\n\nReadableStreamInternalController::~ReadableStreamInternalController() noexcept(false) {\n  if (readState.is<ReaderLocked>()) {\n    readState.transitionTo<Unlocked>();\n  }\n}\n\njsg::Ref<ReadableStream> ReadableStreamInternalController::addRef() {\n  return KJ_ASSERT_NONNULL(owner).addRef();\n}\n\nkj::Maybe<jsg::Promise<ReadResult>> ReadableStreamInternalController::read(\n    jsg::Lock& js, kj::Maybe<ByobOptions> maybeByobOptions) {\n\n  if (isPendingClosure) {\n    return js.rejectedPromise<ReadResult>(\n        js.v8TypeError(\"This ReadableStream belongs to an object that is closing.\"_kj));\n  }\n\n  v8::Local<v8::ArrayBuffer> store;\n  size_t byteLength = 0;\n  size_t byteOffset = 0;\n  size_t atLeast = 1;\n\n  KJ_IF_SOME(byobOptions, maybeByobOptions) {\n    store = byobOptions.bufferView.getHandle(js)->Buffer();\n    byteOffset = byobOptions.byteOffset;\n    byteLength = byobOptions.byteLength;\n    atLeast = byobOptions.atLeast.orDefault(atLeast);\n    if (byobOptions.detachBuffer) {\n      if (!store->IsDetachable()) {\n        return js.rejectedPromise<ReadResult>(\n            js.v8TypeError(\"Unable to use non-detachable ArrayBuffer\"_kj));\n      }\n      auto backing = store->GetBackingStore();\n      jsg::check(store->Detach(v8::Local<v8::Value>()));\n      store = v8::ArrayBuffer::New(js.v8Isolate, kj::mv(backing));\n    }\n  }\n\n  auto getOrInitStore = [&](bool errorCase = false) {\n    if (store.IsEmpty()) {\n      // In an error case, where store is not provided, we can use zero length\n      byteLength = errorCase ? 0 : UnderlyingSource::DEFAULT_AUTO_ALLOCATE_CHUNK_SIZE;\n\n      if (!v8::ArrayBuffer::MaybeNew(js.v8Isolate, byteLength).ToLocal(&store)) {\n        return v8::Local<v8::ArrayBuffer>();\n      }\n    }\n    return store;\n  };\n\n  disturbed = true;\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      if (maybeByobOptions != kj::none && FeatureFlags::get(js).getInternalStreamByobReturn()) {\n        // When using the BYOB reader, we must return a sized-0 Uint8Array that is backed\n        // by the ArrayBuffer passed in the options.\n        auto theStore = getOrInitStore(true);\n        if (theStore.IsEmpty()) {\n          return js.rejectedPromise<ReadResult>(\n              js.v8TypeError(\"Unable to allocate memory for read\"_kj));\n        }\n        return js.resolvedPromise(ReadResult{\n          .value = js.v8Ref(v8::Uint8Array::New(theStore, 0, 0).As<v8::Value>()),\n          .done = true,\n        });\n      }\n      return js.resolvedPromise(ReadResult{.done = true});\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return js.rejectedPromise<ReadResult>(errored.addRef(js));\n    }\n    KJ_CASE_ONEOF(readable, Readable) {\n      // TODO(conform): Requiring serialized read requests is non-conformant, but we've never had a\n      //   use case for them. At one time, our implementation of TransformStream supported multiple\n      //   simultaneous read requests, but it is highly unlikely that anyone relied on this. Our\n      //   ReadableStream implementation that wraps native streams has never supported them, our\n      //   TransformStream implementation is primarily (only?) used for constructing manually\n      //   streamed Responses, and no teed ReadableStream has ever supported them.\n      if (readPending) {\n        return js.rejectedPromise<ReadResult>(js.v8TypeError(\n            \"This ReadableStream only supports a single pending read request at a time.\"_kj));\n      }\n      readPending = true;\n\n      auto theStore = getOrInitStore();\n      if (theStore.IsEmpty()) {\n        return js.rejectedPromise<ReadResult>(\n            js.v8TypeError(\"Unable to allocate memory for read\"_kj));\n      }\n\n      // In the case the ArrayBuffer is detached/transfered while the read is pending, we\n      // need to make sure that the ptr remains stable, so we grab a shared ptr to the\n      // backing store and use that to get the pointer to the data. If the buffer is detached\n      // while the read is pending, this does mean that the read data will end up being lost,\n      // but there's not really a better option. The best we can do here is warn the user\n      // that this is happening so they can avoid doing it in the future.\n      // Also, the user really shouldn't do this because the read will end up completing into\n      // the detached backing store still which could cause issues with whatever code now actually\n      // owns the transfered buffer. Below we'll warn the user about this if it happens so they\n      // can avoid doing it in the future.\n      auto backing = theStore->GetBackingStore();\n\n      // For resizable ArrayBuffers, the buffer may be resized while the read is\n      // pending, decommitting memory pages and making the pointer invalid (SIGSEGV).\n      // We read into a temporary buffer and copy the data back in the .then()\n      // callback, where we can validate the buffer is still large enough.\n      bool isResizable = theStore->IsResizableByUserJavaScript();\n\n      kj::Array<kj::byte> tempBuffer;\n      kj::byte* readPtr;\n      if (isResizable) {\n        auto currentByteLength = theStore->ByteLength();\n        if (byteOffset >= currentByteLength) {\n          readPending = false;\n          return js.resolvedPromise(ReadResult{\n            .value = js.v8Ref(v8::Uint8Array::New(theStore, 0, 0).As<v8::Value>()),\n            .done = false,\n          });\n        }\n        if (byteOffset + byteLength > currentByteLength) {\n          byteLength = currentByteLength - byteOffset;\n          if (atLeast > byteLength) {\n            atLeast = byteLength > 0 ? byteLength : 1;\n          }\n        }\n        tempBuffer = kj::heapArray<kj::byte>(byteLength);\n        readPtr = tempBuffer.begin();\n      } else {\n        auto ptr = static_cast<kj::byte*>(backing->Data());\n        readPtr = ptr + byteOffset;\n      }\n      auto bytes = kj::arrayPtr(readPtr, byteLength);\n\n      auto promise = kj::evalNow([&] {\n        return readable->tryRead(bytes.begin(), atLeast, bytes.size()).attach(kj::mv(backing));\n      });\n      KJ_IF_SOME(readerLock, readState.tryGetUnsafe<ReaderLocked>()) {\n        promise = KJ_ASSERT_NONNULL(readerLock.getCanceler())->wrap(kj::mv(promise));\n      }\n\n      // TODO(soon): We use awaitIoLegacy() here because if the stream terminates in JavaScript in\n      // this same isolate, then the promise may actually be waiting on JavaScript to do something,\n      // and so should not be considered waiting on external I/O. We will need to use\n      // registerPendingEvent() manually when reading from an external stream. Ideally, we would\n      // refactor the implementation so that when waiting on a JavaScript stream, we strictly use\n      // jsg::Promises and not kj::Promises, so that it doesn't look like I/O at all, and there's\n      // no need to drop the isolate lock and take it again every time some data is read/written.\n      // That's a larger refactor, though.\n      auto& ioContext = IoContext::current();\n      return ioContext.awaitIoLegacy(js, kj::mv(promise))\n          .then(js, ioContext.addFunctor(JSG_VISITABLE_LAMBDA(\n                (this, ref = addRef(), store = js.v8Ref(store),\n                 byteOffset, byteLength, isByob = maybeByobOptions != kj::none,\n                 isResizable, readPtr, tempBuffer = kj::mv(tempBuffer)),\n                (ref),\n                (jsg::Lock& js, size_t amount) mutable -> jsg::Promise<ReadResult> {\n        readPending = false;\n        KJ_ASSERT(amount <= byteLength);\n        if (amount == 0) {\n          if (!state.is<StreamStates::Errored>()) {\n            doClose(js);\n          }\n          KJ_IF_SOME(o, owner) {\n            o.signalEof(js);\n          } else {}\n          if (isByob && FeatureFlags::get(js).getInternalStreamByobReturn()) {\n            // When using the BYOB reader, we must return a sized-0 Uint8Array that is backed\n            // by the ArrayBuffer passed in the options.\n            auto u8 = v8::Uint8Array::New(store.getHandle(js), 0, 0);\n            return js.resolvedPromise(ReadResult{\n              .value = js.v8Ref(u8.As<v8::Value>()),\n              .done = true,\n            });\n          }\n          return js.resolvedPromise(ReadResult{.done = true});\n        }\n        // Return a slice so the script can see how many bytes were read.\n\n        // We have to check to see if the store was detached or resized while we were waiting\n        // for the read to complete.\n        auto handle = store.getHandle(js);\n        if (handle->WasDetached()) {\n          // If the buffer was detached, we resolve with a new zero-length ArrayBuffer.\n          // The bytes that were read are lost, but this is a valid result.\n\n          // Silly user, trix are for kids.\n          IoContext::current().logWarningOnce(\n              \"A buffer that was being used for a read operation on a ReadableStream was detached \"\n              \"while the read was pending. The read completed with a zero-length buffer and the data \"\n              \"that was read is lost. Avoid detaching buffers that are being used for active read \"\n              \"operations on streams, or use the streams_byob_reader_detaches_buffer compatibility \"\n              \"flag, to prevent this from happening.\"_kj);\n\n          auto buffer = v8::ArrayBuffer::New(js.v8Isolate, 0);\n          return js.resolvedPromise(ReadResult{\n            .value = js.v8Ref(v8::Uint8Array::New(buffer, 0, 0).As<v8::Value>()),\n            .done = false,\n          });\n        }\n\n        if (byteOffset + amount > handle->ByteLength()) {\n          // If the buffer was resized smaller, we return a truncated result.\n\n          IoContext::current().logWarningOnce(\n              \"A buffer that was being used for a read operation on a ReadableStream was resized \"\n              \"smaller while the read was pending. The read completed with a truncated buffer \"\n              \"containing only the bytes that fit within the new size. Avoid resizing buffers that \"\n              \"are being used for active read operations on streams, or use the \"\n              \"streams_byob_reader_detaches_buffer compatibility flag, to prevent this from \"\n              \"happening.\"_kj);\n\n          if (byteOffset >= handle->ByteLength()) {\n            return js.resolvedPromise(ReadResult{\n              .value = js.v8Ref(v8::Uint8Array::New(store.getHandle(js), 0, 0).As<v8::Value>()),\n              .done = false,\n            });\n          }\n          amount = handle->ByteLength() - byteOffset;\n        }\n\n        if (isResizable && byteOffset + amount <= handle->ByteLength()) {\n          // For resizable buffers, the data was read into a temporary buffer.\n          // Copy it back into the user's (still valid) buffer region.\n          auto destPtr = static_cast<kj::byte*>(handle->GetBackingStore()->Data());\n          memcpy(destPtr + byteOffset, readPtr, amount);\n        }\n\n        return js.resolvedPromise(ReadResult{\n          .value = js.v8Ref(\n              v8::Uint8Array::New(store.getHandle(js), byteOffset, amount).As<v8::Value>()),\n          .done = false,\n        });\n      })),\n              ioContext.addFunctor(JSG_VISITABLE_LAMBDA(\n                  (this, ref = addRef()),\n                  (ref),\n                  (jsg::Lock& js, jsg::Value reason) -> jsg::Promise<ReadResult> {\n        readPending = false;\n        if (!state.is<StreamStates::Errored>()) {\n          doError(js, reason.getHandle(js));\n        }\n        return js.rejectedPromise<ReadResult>(kj::mv(reason));\n      })));\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::Maybe<jsg::Promise<DrainingReadResult>> ReadableStreamInternalController::drainingRead(\n    jsg::Lock& js, size_t maxRead) {\n  // InternalController does not support draining reads fully since all reads are\n  // async. We implement a simplified version that just performs a normal read\n  // like read(). The significant difference is that with JS-backed streams, a draining\n  // read will pull any already enqueued data from the stream buffer and try synchronously\n  // pumping the stream for more data until either maxRead is satisfied or the stream\n  // indicates EOF, error, or that it needs to wait for more data. Internal streams have\n  // no such internal buffering and never provide data synchronously so drainingRead\n  // is effectively the same as read().\n\n  if (isPendingClosure) {\n    return js.rejectedPromise<DrainingReadResult>(\n        js.v8TypeError(\"This ReadableStream belongs to an object that is closing.\"_kj));\n  }\n\n  static constexpr size_t kAtLeast = 1;\n\n  disturbed = true;\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      return js.resolvedPromise(DrainingReadResult{.done = true});\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return js.rejectedPromise<DrainingReadResult>(errored.addRef(js));\n    }\n    KJ_CASE_ONEOF(readable, Readable) {\n      if (readPending) {\n        return js.rejectedPromise<DrainingReadResult>(js.v8TypeError(\n            \"This ReadableStream only supports a single pending read request at a time.\"_kj));\n      }\n      readPending = true;\n\n      // TODO(later): In the case that maxRead is large, we may consider splitting this into\n      // multiple reads to avoid allocating too large of a buffer at once. The draining read\n      // result can handle multiple chunks so this would be feasible at the cost of more\n      // read calls. For now we just do a single read up to maxRead.\n      // At the very least, we cap maxRead to some reasonable limit to avoid\n      // potential OOM issues.\n      static constexpr size_t kMaxReadCap = 1 * 1024 * 1024;  // 1 MB\n      maxRead = kj::min(maxRead, kMaxReadCap);\n\n      if (maxRead == 0) {\n        // No data requested, return empty result.\n        // This really shouldn't ever happen but let's handle it gracefully.\n        readPending = false;\n        return js.resolvedPromise(DrainingReadResult{\n          .chunks = nullptr,\n          .done = false,\n        });\n      }\n\n      auto store = kj::heapArray<kj::byte>(maxRead);\n\n      auto promise =\n          kj::evalNow([&] { return readable->tryRead(store.begin(), kAtLeast, store.size()); });\n      KJ_IF_SOME(readerLock, readState.tryGetUnsafe<ReaderLocked>()) {\n        promise = KJ_ASSERT_NONNULL(readerLock.getCanceler())->wrap(kj::mv(promise));\n      }\n\n      auto& ioContext = IoContext::current();\n      return ioContext.awaitIoLegacy(js, kj::mv(promise))\n          .then(js, ioContext.addFunctor(JSG_VISITABLE_LAMBDA(\n                (this, ref = addRef(), store = kj::mv(store)),\n                (ref),\n                (jsg::Lock& js, size_t amount) mutable -> jsg::Promise<DrainingReadResult> {\n        readPending = false;\n        KJ_ASSERT(amount <= store.size());\n        if (amount == 0) {\n          if (!state.is<StreamStates::Errored>()) {\n            doClose(js);\n          }\n          KJ_IF_SOME(o, owner) {\n            o.signalEof(js);\n          } else {}\n          return js.resolvedPromise(DrainingReadResult{.done = true});\n        }\n        // Return a slice so the script can see how many bytes were read.\n        return js.resolvedPromise(DrainingReadResult{\n          .chunks = kj::arr(store.slice(0, amount).attach(kj::mv(store))), .done = false});\n      })),\n              ioContext.addFunctor(JSG_VISITABLE_LAMBDA(\n                  (this, ref = addRef()),\n                  (ref),\n                  (jsg::Lock& js, jsg::Value reason) -> jsg::Promise<DrainingReadResult> {\n        readPending = false;\n        if (!state.is<StreamStates::Errored>()) {\n          doError(js, reason.getHandle(js));\n        }\n        return js.rejectedPromise<DrainingReadResult>(kj::mv(reason));\n      })));\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\njsg::Promise<void> ReadableStreamInternalController::pipeTo(\n    jsg::Lock& js, WritableStreamController& destination, PipeToOptions options) {\n\n  KJ_DASSERT(!isLockedToReader());\n  KJ_DASSERT(!destination.isLockedToWriter());\n\n  if (isPendingClosure) {\n    return js.rejectedPromise<void>(\n        js.v8TypeError(\"This ReadableStream belongs to an object that is closing.\"_kj));\n  }\n\n  disturbed = true;\n  KJ_IF_SOME(promise,\n      destination.tryPipeFrom(js, KJ_ASSERT_NONNULL(owner).addRef(), kj::mv(options))) {\n    return kj::mv(promise);\n  }\n\n  return js.rejectedPromise<void>(\n      js.v8TypeError(\"This ReadableStream cannot be piped to this WritableStream.\"_kj));\n}\n\njsg::Promise<void> ReadableStreamInternalController::cancel(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason) {\n  disturbed = true;\n\n  KJ_IF_SOME(errored, state.tryGetUnsafe<StreamStates::Errored>()) {\n    return js.rejectedPromise<void>(errored.getHandle(js));\n  }\n\n  doCancel(js, maybeReason);\n\n  return js.resolvedPromise();\n}\n\nvoid ReadableStreamInternalController::doCancel(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason) {\n  auto exception = reasonToException(js, maybeReason);\n  KJ_IF_SOME(locked, readState.tryGetUnsafe<ReaderLocked>()) {\n    KJ_IF_SOME(canceler, locked.getCanceler()) {\n      canceler->cancel(kj::cp(exception));\n    }\n  }\n  KJ_IF_SOME(readable, state.tryGetUnsafe<Readable>()) {\n    readable->cancel(kj::mv(exception));\n    doClose(js);\n  }\n}\n\nvoid ReadableStreamInternalController::doClose(jsg::Lock& js) {\n  // If already in a terminal state, nothing to do.\n  if (state.isTerminal()) return;\n\n  state.transitionTo<StreamStates::Closed>();\n  KJ_IF_SOME(locked, readState.tryGetUnsafe<ReaderLocked>()) {\n    maybeResolvePromise(js, locked.getClosedFulfiller());\n  } else {\n    (void)readState.transitionFromTo<PipeLocked, Unlocked>();\n  }\n}\n\nvoid ReadableStreamInternalController::doError(jsg::Lock& js, v8::Local<v8::Value> reason) {\n  // If already in a terminal state, nothing to do.\n  if (state.isTerminal()) return;\n\n  state.transitionTo<StreamStates::Errored>(js.v8Ref(reason));\n  KJ_IF_SOME(locked, readState.tryGetUnsafe<ReaderLocked>()) {\n    maybeRejectPromise<void>(js, locked.getClosedFulfiller(), reason);\n  } else {\n    (void)readState.transitionFromTo<PipeLocked, Unlocked>();\n  }\n}\n\nReadableStreamController::Tee ReadableStreamInternalController::tee(jsg::Lock& js) {\n  JSG_REQUIRE(\n      !isLockedToReader(), TypeError, \"This ReadableStream is currently locked to a reader.\");\n  JSG_REQUIRE(\n      !isPendingClosure, TypeError, \"This ReadableStream belongs to an object that is closing.\");\n  readState.transitionTo<Locked>();\n  disturbed = true;\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      // Create two closed ReadableStreams.\n      return Tee{\n        .branch1 = js.alloc<ReadableStream>(kj::heap<ReadableStreamInternalController>(closed)),\n        .branch2 = js.alloc<ReadableStream>(kj::heap<ReadableStreamInternalController>(closed)),\n      };\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      // Create two errored ReadableStreams.\n      return Tee{\n        .branch1 = js.alloc<ReadableStream>(\n            kj::heap<ReadableStreamInternalController>(errored.addRef(js))),\n        .branch2 = js.alloc<ReadableStream>(\n            kj::heap<ReadableStreamInternalController>(errored.addRef(js))),\n      };\n    }\n    KJ_CASE_ONEOF(readable, Readable) {\n      auto& ioContext = IoContext::current();\n\n      auto makeTee = [&](kj::Own<ReadableStreamSource> b1,\n                         kj::Own<ReadableStreamSource> b2) -> Tee {\n        doClose(js);\n        return Tee{\n          .branch1 = js.alloc<ReadableStream>(ioContext, kj::mv(b1)),\n          .branch2 = js.alloc<ReadableStream>(ioContext, kj::mv(b2)),\n        };\n      };\n\n      auto bufferLimit = ioContext.getLimitEnforcer().getBufferingLimit();\n      KJ_IF_SOME(tee, readable->tryTee(bufferLimit)) {\n        // This ReadableStreamSource has an optimized tee implementation.\n        return makeTee(kj::mv(tee.branches[0]), kj::mv(tee.branches[1]));\n      }\n\n      auto tee = kj::newTee(kj::heap<TeeAdapter>(kj::mv(readable)), bufferLimit);\n\n      return makeTee(kj::heap<TeeBranch>(newTeeErrorAdapter(kj::mv(tee.branches[0]))),\n          kj::heap<TeeBranch>(newTeeErrorAdapter(kj::mv(tee.branches[1]))));\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\nkj::Maybe<kj::Own<ReadableStreamSource>> ReadableStreamInternalController::removeSource(\n    jsg::Lock& js, bool ignoreDisturbed) {\n  JSG_REQUIRE(\n      !isLockedToReader(), TypeError, \"This ReadableStream is currently locked to a reader.\");\n  JSG_REQUIRE(!disturbed || ignoreDisturbed, TypeError, \"This ReadableStream is disturbed.\");\n\n  readState.transitionTo<Locked>();\n  disturbed = true;\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      class NullSource final: public ReadableStreamSource {\n       public:\n        kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n          return static_cast<size_t>(0);\n        }\n\n        kj::Maybe<uint64_t> tryGetLength(StreamEncoding encoding) override {\n          return static_cast<uint64_t>(0);\n        }\n      };\n\n      return kj::heap<NullSource>();\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      kj::throwFatalException(js.exceptionToKj(errored.addRef(js)));\n    }\n    KJ_CASE_ONEOF(readable, Readable) {\n      auto result = kj::mv(readable);\n      state.transitionTo<StreamStates::Closed>();\n      return kj::Maybe<kj::Own<ReadableStreamSource>>(kj::mv(result));\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\nbool ReadableStreamInternalController::lockReader(jsg::Lock& js, Reader& reader) {\n  if (isLockedToReader()) {\n    return false;\n  }\n\n  auto prp = js.newPromiseAndResolver<void>();\n  prp.promise.markAsHandled(js);\n\n  auto lock = ReaderLocked(\n      reader, kj::mv(prp.resolver), IoContext::current().addObject(kj::heap<kj::Canceler>()));\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      maybeResolvePromise(js, lock.getClosedFulfiller());\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      maybeRejectPromise<void>(js, lock.getClosedFulfiller(), errored.getHandle(js));\n    }\n    KJ_CASE_ONEOF(readable, Readable) {\n      // Nothing to do.\n    }\n  }\n\n  readState.transitionTo<ReaderLocked>(kj::mv(lock));\n  reader.attach(*this, kj::mv(prp.promise));\n  return true;\n}\n\nvoid ReadableStreamInternalController::releaseReader(\n    Reader& reader, kj::Maybe<jsg::Lock&> maybeJs) {\n  KJ_IF_SOME(locked, readState.tryGetUnsafe<ReaderLocked>()) {\n    KJ_ASSERT(&locked.getReader() == &reader);\n    KJ_IF_SOME(js, maybeJs) {\n      KJ_IF_SOME(canceler, locked.getCanceler()) {\n        JSG_REQUIRE(canceler->isEmpty(), TypeError,\n            \"Cannot call releaseLock() on a reader with outstanding read promises.\");\n      }\n      maybeRejectPromise<void>(js, locked.getClosedFulfiller(),\n          js.v8TypeError(\"This ReadableStream reader has been released.\"_kj));\n    }\n    locked.clear();\n\n    // When maybeJs is nullptr, that means releaseReader was called when the reader is\n    // being deconstructed and not as the result of explicitly calling releaseLock. In\n    // that case, we don't want to change the lock state itself because we do not have\n    // an isolate lock. Clearing the lock above will free the lock state while keeping the\n    // ReadableStream marked as locked.\n    if (maybeJs != kj::none) {\n      readState.transitionTo<Unlocked>();\n    }\n  }\n}\n\nvoid WritableStreamInternalController::Writable::abort(kj::Exception&& ex) {\n  canceler.cancel(kj::cp(ex));\n  sink->abort(kj::mv(ex));\n}\n\nWritableStreamInternalController::~WritableStreamInternalController() noexcept(false) {\n  if (writeState.is<WriterLocked>()) {\n    writeState.transitionTo<Unlocked>();\n  }\n}\n\njsg::Ref<WritableStream> WritableStreamInternalController::addRef() {\n  return KJ_ASSERT_NONNULL(owner).addRef();\n}\n\njsg::Promise<void> WritableStreamInternalController::write(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> value) {\n  if (isPendingClosure) {\n    return js.rejectedPromise<void>(\n        js.v8TypeError(\"This WritableStream belongs to an object that is closing.\"_kj));\n  }\n  if (isClosedOrClosing()) {\n    return js.rejectedPromise<void>(js.v8TypeError(\"This WritableStream has been closed.\"_kj));\n  }\n  if (isPiping()) {\n    return js.rejectedPromise<void>(\n        js.v8TypeError(\"This WritableStream is currently being piped to.\"_kj));\n  }\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      // Handled by isClosedOrClosing().\n      KJ_UNREACHABLE;\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return js.rejectedPromise<void>(errored.addRef(js));\n    }\n    KJ_CASE_ONEOF(writable, IoOwn<Writable>) {\n      if (value == kj::none) {\n        return js.resolvedPromise();\n      }\n      auto chunk = KJ_ASSERT_NONNULL(value);\n\n      std::shared_ptr<v8::BackingStore> store;\n      size_t byteLength = 0;\n      size_t byteOffset = 0;\n      if (chunk->IsArrayBuffer()) {\n        auto buffer = chunk.As<v8::ArrayBuffer>();\n        store = buffer->GetBackingStore();\n        byteLength = buffer->ByteLength();\n      } else if (chunk->IsArrayBufferView()) {\n        auto view = chunk.As<v8::ArrayBufferView>();\n        store = view->Buffer()->GetBackingStore();\n        byteLength = view->ByteLength();\n        byteOffset = view->ByteOffset();\n      } else if (chunk->IsString()) {\n        // TODO(later): This really ought to return a rejected promise and not a sync throw.\n        // This case caused me a moment of confusion during testing, so I think it's worth\n        // a specific error message.\n        throwTypeErrorAndConsoleWarn(\n            \"This TransformStream is being used as a byte stream, but received a string on its \"\n            \"writable side. If you wish to write a string, you'll probably want to explicitly \"\n            \"UTF-8-encode it with TextEncoder.\");\n      } else {\n        // TODO(later): This really ought to return a rejected promise and not a sync throw.\n        throwTypeErrorAndConsoleWarn(\n            \"This TransformStream is being used as a byte stream, but received an object of \"\n            \"non-ArrayBuffer/ArrayBufferView type on its writable side.\");\n      }\n\n      if (byteLength == 0) {\n        return js.resolvedPromise();\n      }\n\n      auto prp = js.newPromiseAndResolver<void>();\n      adjustWriteBufferSize(js, byteLength);\n      KJ_IF_SOME(o, observer) {\n        o->onChunkEnqueued(byteLength);\n      }\n\n      auto src = kj::arrayPtr(static_cast<kj::byte*>(store->Data()) + byteOffset, byteLength);\n      auto data = kj::heapArray<kj::byte>(src.size());\n      data.asPtr().copyFrom(src);\n      auto ptr = data.asPtr();\n      queue.push_back(\n          WriteEvent{.outputLock = IoContext::current().waitForOutputLocksIfNecessaryIoOwn(),\n            .event = kj::heap<Write>({\n              .promise = kj::mv(prp.resolver),\n              .totalBytes = store->ByteLength(),\n              .ownBytes = kj::mv(data),\n              .bytes = ptr,\n            })});\n\n      ensureWriting(js);\n      return kj::mv(prp.promise);\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\nvoid WritableStreamInternalController::adjustWriteBufferSize(jsg::Lock& js, int64_t amount) {\n  KJ_DASSERT(amount >= 0 || std::abs(amount) <= currentWriteBufferSize);\n  currentWriteBufferSize += amount;\n  KJ_IF_SOME(highWaterMark, maybeHighWaterMark) {\n    int64_t desiredSize = highWaterMark - currentWriteBufferSize;\n    updateBackpressure(js, desiredSize <= 0);\n  }\n}\n\nvoid WritableStreamInternalController::updateBackpressure(jsg::Lock& js, bool backpressure) {\n  KJ_IF_SOME(writerLock, writeState.tryGetUnsafe<WriterLocked>()) {\n    if (backpressure) {\n      // Per the spec, when backpressure is updated and is true, we replace the existing\n      // ready promise on the writer with a new pending promise, regardless of whether\n      // the existing one is resolved or not.\n      auto prp = js.newPromiseAndResolver<void>();\n      prp.promise.markAsHandled(js);\n      writerLock.setReadyFulfiller(js, prp);\n      return;\n    }\n\n    // When backpressure is updated and is false, we resolve the ready promise on the writer\n    maybeResolvePromise(js, writerLock.getReadyFulfiller());\n  }\n}\n\nvoid WritableStreamInternalController::setHighWaterMark(uint64_t highWaterMark) {\n  maybeHighWaterMark = highWaterMark;\n}\n\njsg::Promise<void> WritableStreamInternalController::closeImpl(jsg::Lock& js, bool markAsHandled) {\n  if (isClosedOrClosing()) {\n    return js.resolvedPromise();\n  }\n  if (isPiping()) {\n    auto reason = js.v8TypeError(\"This WritableStream is currently being piped to.\"_kj);\n    return rejectedMaybeHandledPromise<void>(js, reason, markAsHandled);\n  }\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      // Handled by isClosedOrClosing().\n      KJ_UNREACHABLE;\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      auto reason = errored.getHandle(js);\n      return rejectedMaybeHandledPromise<void>(js, reason, markAsHandled);\n    }\n    KJ_CASE_ONEOF(writable, IoOwn<Writable>) {\n      auto prp = js.newPromiseAndResolver<void>();\n      if (markAsHandled) {\n        prp.promise.markAsHandled(js);\n      }\n      queue.push_back(\n          WriteEvent{.outputLock = IoContext::current().waitForOutputLocksIfNecessaryIoOwn(),\n            .event = kj::heap<Close>({.promise = kj::mv(prp.resolver)})});\n      ensureWriting(js);\n      return kj::mv(prp.promise);\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\njsg::Promise<void> WritableStreamInternalController::close(jsg::Lock& js, bool markAsHandled) {\n  KJ_IF_SOME(closureWaitable, maybeClosureWaitable) {\n    // If we're already waiting on the closure waitable, then we do not want to try scheduling\n    // it again, let's just wait for the existing one to be resolved.\n    if (waitingOnClosureWritableAlready) {\n      return closureWaitable.whenResolved(js);\n    }\n    waitingOnClosureWritableAlready = true;\n    auto promise = closureWaitable.then(js, [markAsHandled, this](jsg::Lock& js) {\n      return closeImpl(js, markAsHandled);\n    }, [](jsg::Lock& js, jsg::Value) {\n      // Ignore rejection as it will be reported in the Socket's `closed`/`opened` promises\n      // instead.\n      return js.resolvedPromise();\n    });\n    maybeClosureWaitable = promise.whenResolved(js);\n    return kj::mv(promise);\n  } else {\n    return closeImpl(js, markAsHandled);\n  }\n}\n\njsg::Promise<void> WritableStreamInternalController::flush(jsg::Lock& js, bool markAsHandled) {\n  if (isClosedOrClosing()) {\n    auto reason = js.v8TypeError(\"This WritableStream has been closed.\"_kj);\n    return rejectedMaybeHandledPromise<void>(js, reason, markAsHandled);\n  }\n  if (isPiping()) {\n    auto reason = js.v8TypeError(\"This WritableStream is currently being piped to.\"_kj);\n    return rejectedMaybeHandledPromise<void>(js, reason, markAsHandled);\n  }\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      // Handled by isClosedOrClosing().\n      KJ_UNREACHABLE;\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      auto reason = errored.getHandle(js);\n      return rejectedMaybeHandledPromise<void>(js, reason, markAsHandled);\n    }\n    KJ_CASE_ONEOF(writable, IoOwn<Writable>) {\n      auto prp = js.newPromiseAndResolver<void>();\n      if (markAsHandled) {\n        prp.promise.markAsHandled(js);\n      }\n      queue.push_back(\n          WriteEvent{.outputLock = IoContext::current().waitForOutputLocksIfNecessaryIoOwn(),\n            .event = kj::heap<Flush>({.promise = kj::mv(prp.resolver)})});\n      ensureWriting(js);\n      return kj::mv(prp.promise);\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\njsg::Promise<void> WritableStreamInternalController::abort(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason) {\n  // While it may be confusing to users to throw `undefined` rather than a more helpful Error here,\n  // doing so is required by the relevant spec:\n  // https://streams.spec.whatwg.org/#writable-stream-abort\n  return doAbort(js, maybeReason.orDefault(js.v8Undefined()));\n}\n\njsg::Promise<void> WritableStreamInternalController::doAbort(\n    jsg::Lock& js, v8::Local<v8::Value> reason, AbortOptions options) {\n  // If maybePendingAbort is set, then the returned abort promise will be rejected\n  // with the specified error once the abort is completed, otherwise the promise will\n  // be resolved with undefined.\n\n  // If there is already an abort pending, return that pending promise\n  // instead of trying to schedule another.\n  KJ_IF_SOME(pendingAbort, maybePendingAbort) {\n    pendingAbort->reject = options.reject;\n    auto promise = pendingAbort->whenResolved(js);\n    if (options.handled) {\n      promise.markAsHandled(js);\n    }\n    return kj::mv(promise);\n  }\n\n  KJ_IF_SOME(writable, state.tryGetUnsafe<IoOwn<Writable>>()) {\n    auto exception = js.exceptionToKj(js.v8Ref(reason));\n\n    if (FeatureFlags::get(js).getInternalWritableStreamAbortClearsQueue()) {\n      // If this flag is set, we will clear the queue proactively and immediately\n      // error the stream rather than handling the abort lazily. In this case, the\n      // stream will be put into an errored state immediately after draining the\n      // queue. All pending writes and other operations in the queue will be rejected\n      // immediately and an immediately resolved or rejected promise will be returned.\n      writable->abort(kj::cp(exception));\n      drain(js, reason);\n      return options.reject ? rejectedMaybeHandledPromise<void>(js, reason, options.handled)\n                            : js.resolvedPromise();\n    }\n\n    if (queue.empty()) {\n      writable->abort(kj::cp(exception));\n      doError(js, reason);\n      return options.reject ? rejectedMaybeHandledPromise<void>(js, reason, options.handled)\n                            : js.resolvedPromise();\n    }\n\n    maybePendingAbort = kj::heap<PendingAbort>(js, reason, options.reject);\n    auto promise = KJ_ASSERT_NONNULL(maybePendingAbort)->whenResolved(js);\n    if (options.handled) {\n      promise.markAsHandled(js);\n    }\n    return kj::mv(promise);\n  }\n\n  return options.reject ? rejectedMaybeHandledPromise<void>(js, reason, options.handled)\n                        : js.resolvedPromise();\n}\n\nkj::Maybe<jsg::Promise<void>> WritableStreamInternalController::tryPipeFrom(\n    jsg::Lock& js, jsg::Ref<ReadableStream> source, PipeToOptions options) {\n\n  // The ReadableStream source here can be either a JavaScript-backed ReadableStream\n  // or ReadableStreamSource-backed.\n  //\n  // If the source is ReadableStreamSource-backed, then we can use kj's low level mechanisms\n  // for piping the data. If the source is JavaScript-backed, then we need to rely on the\n  // JavaScript-based Promise API for piping the data.\n\n  auto preventAbort = options.preventAbort.orDefault(false);\n  auto preventClose = options.preventClose.orDefault(false);\n  auto preventCancel = options.preventCancel.orDefault(false);\n  auto pipeThrough = options.pipeThrough;\n\n  if (isPiping()) {\n    auto reason = js.v8TypeError(\"This WritableStream is currently being piped to.\"_kj);\n    return rejectedMaybeHandledPromise<void>(js, reason, pipeThrough);\n  }\n\n  // If a signal is provided, we need to check that it is not already triggered. If it\n  // is, we return a rejected promise using the signal's reason.\n  KJ_IF_SOME(signal, options.signal) {\n    if (signal->getAborted(js)) {\n      return rejectedMaybeHandledPromise<void>(js, signal->getReason(js), pipeThrough);\n    }\n  }\n\n  // With either type of source, our first step is to acquire the source pipe lock. This\n  // will help abstract most of the details of which type of source we're working with.\n  auto& sourceLock = KJ_ASSERT_NONNULL(source->getController().tryPipeLock());\n\n  // Let's also acquire the destination pipe lock.\n  writeState.transitionTo<PipeLocked>(*source);\n\n  // If the source has errored, the spec requires us to reject the pipe promise and, if preventAbort\n  // is false, error the destination (Propagate error forward). The errored source will be unlocked\n  // immediately. The destination will be unlocked once the abort completes.\n  KJ_IF_SOME(errored, sourceLock.tryGetErrored(js)) {\n    sourceLock.release(js);\n    if (!preventAbort) {\n      if (state.tryGetUnsafe<IoOwn<Writable>>() != kj::none) {\n        return doAbort(js, errored, {.reject = true, .handled = pipeThrough});\n      }\n    }\n\n    // If preventAbort was true, we're going to unlock the destination now.\n    writeState.transitionTo<Unlocked>();\n    return rejectedMaybeHandledPromise<void>(js, errored, pipeThrough);\n  }\n\n  // If the destination has errored, the spec requires us to reject the pipe promise and, if\n  // preventCancel is false, error the source (Propagate error backward). The errored destination\n  // will be unlocked immediately.\n  KJ_IF_SOME(errored, state.tryGetUnsafe<StreamStates::Errored>()) {\n    writeState.transitionTo<Unlocked>();\n    if (!preventCancel) {\n      sourceLock.release(js, errored.getHandle(js));\n    } else {\n      sourceLock.release(js);\n    }\n    return rejectedMaybeHandledPromise<void>(js, errored.getHandle(js), pipeThrough);\n  }\n\n  // If the source has closed, the spec requires us to close the destination if preventClose\n  // is false (Propagate closing forward). The source is unlocked immediately. The destination\n  // will be unlocked as soon as the close completes.\n  if (sourceLock.isClosed()) {\n    sourceLock.release(js);\n    if (!preventClose) {\n      // The spec would have us check to see if `destination` is errored and, if so, return its\n      // stored error. But if `destination` were errored, we would already have caught that case\n      // above. The spec is probably concerned about cases where the readable and writable sides\n      // transition to such states in a racey way. But our pump implementation will take care of\n      // this naively.\n      KJ_ASSERT(!state.is<StreamStates::Errored>());\n      if (!isClosedOrClosing()) {\n        return close(js);\n      }\n    }\n    writeState.transitionTo<Unlocked>();\n    return js.resolvedPromise();\n  }\n\n  // If the destination has closed, the spec requires us to close the source if\n  // preventCancel is false (Propagate closing backward).\n  if (isClosedOrClosing()) {\n    auto destClosed = js.v8TypeError(\"This destination writable stream is closed.\"_kj);\n    writeState.transitionTo<Unlocked>();\n\n    if (!preventCancel) {\n      sourceLock.release(js, destClosed);\n    } else {\n      sourceLock.release(js);\n    }\n\n    return rejectedMaybeHandledPromise<void>(js, destClosed, pipeThrough);\n  }\n\n  // The pipe will continue until either the source closes or errors, or until the destination\n  // closes or errors. In either case, both will end up being closed or errored, which will\n  // release the locks on both.\n  //\n  // For either type of source, our next step is to wait for the write loop to process the\n  // pending Pipe event we queue below.\n  auto prp = js.newPromiseAndResolver<void>();\n  if (pipeThrough) {\n    prp.promise.markAsHandled(js);\n  }\n  queue.push_back(WriteEvent{\n    .outputLock = IoContext::current().waitForOutputLocksIfNecessaryIoOwn(),\n    .event = kj::heap<Pipe>(*this, sourceLock, kj::mv(prp.resolver), preventAbort, preventClose,\n        preventCancel, kj::mv(options.signal)),\n  });\n  ensureWriting(js);\n  return kj::mv(prp.promise);\n}\n\nkj::Maybe<kj::Own<WritableStreamSink>> WritableStreamInternalController::removeSink(jsg::Lock& js) {\n  JSG_REQUIRE(\n      !isLockedToWriter(), TypeError, \"This WritableStream is currently locked to a writer.\");\n  JSG_REQUIRE(!isClosedOrClosing(), TypeError, \"This WritableStream is closed.\");\n\n  writeState.transitionTo<Locked>();\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      // Handled by the isClosedOrClosing() check above;\n      KJ_UNREACHABLE;\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      kj::throwFatalException(js.exceptionToKj(errored.addRef(js)));\n    }\n    KJ_CASE_ONEOF(writable, IoOwn<Writable>) {\n      auto result = kj::mv(writable->sink);\n      state.transitionTo<StreamStates::Closed>();\n      return kj::Maybe<kj::Own<WritableStreamSink>>(kj::mv(result));\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\nvoid WritableStreamInternalController::detach(jsg::Lock& js) {\n  JSG_REQUIRE(\n      !isLockedToWriter(), TypeError, \"This WritableStream is currently locked to a writer.\");\n  JSG_REQUIRE(!isClosedOrClosing(), TypeError, \"This WritableStream is closed.\");\n\n  writeState.transitionTo<Locked>();\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      // Handled by the isClosedOrClosing() check above;\n      KJ_UNREACHABLE;\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      kj::throwFatalException(js.exceptionToKj(errored.addRef(js)));\n    }\n    KJ_CASE_ONEOF(writable, IoOwn<Writable>) {\n      state.transitionTo<StreamStates::Closed>();\n      return;\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\nkj::Maybe<int> WritableStreamInternalController::getDesiredSize() {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      return 0;\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return kj::none;\n    }\n    KJ_CASE_ONEOF(writable, IoOwn<Writable>) {\n      KJ_IF_SOME(highWaterMark, maybeHighWaterMark) {\n        return highWaterMark - currentWriteBufferSize;\n      }\n      return 1;\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\nbool WritableStreamInternalController::lockWriter(jsg::Lock& js, Writer& writer) {\n  if (isLockedToWriter()) {\n    return false;\n  }\n\n  auto closedPrp = js.newPromiseAndResolver<void>();\n  closedPrp.promise.markAsHandled(js);\n\n  auto readyPrp = js.newPromiseAndResolver<void>();\n  readyPrp.promise.markAsHandled(js);\n\n  auto lock = WriterLocked(writer, kj::mv(closedPrp.resolver), kj::mv(readyPrp.resolver));\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      maybeResolvePromise(js, lock.getClosedFulfiller());\n      maybeResolvePromise(js, lock.getReadyFulfiller());\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      maybeRejectPromise<void>(js, lock.getClosedFulfiller(), errored.getHandle(js));\n      maybeRejectPromise<void>(js, lock.getReadyFulfiller(), errored.getHandle(js));\n    }\n    KJ_CASE_ONEOF(writable, IoOwn<Writable>) {\n      maybeResolvePromise(js, lock.getReadyFulfiller());\n    }\n  }\n\n  writeState.transitionTo<WriterLocked>(kj::mv(lock));\n  writer.attach(js, *this, kj::mv(closedPrp.promise), kj::mv(readyPrp.promise));\n  return true;\n}\n\nvoid WritableStreamInternalController::releaseWriter(\n    Writer& writer, kj::Maybe<jsg::Lock&> maybeJs) {\n  KJ_IF_SOME(locked, writeState.tryGetUnsafe<WriterLocked>()) {\n    KJ_ASSERT(&locked.getWriter() == &writer);\n    KJ_IF_SOME(js, maybeJs) {\n      maybeRejectPromise<void>(js, locked.getClosedFulfiller(),\n          js.v8TypeError(\"This WritableStream writer has been released.\"_kj));\n    }\n    locked.clear();\n\n    // When maybeJs is nullptr, that means releaseWriter was called when the writer is\n    // being deconstructed and not as the result of explicitly calling releaseLock and\n    // we do not have an isolate lock. In that case, we don't want to change the lock\n    // state itself. Clearing the lock above will free the lock state while keeping the\n    // WritableStream marked as locked.\n    if (maybeJs != kj::none) {\n      writeState.transitionTo<Unlocked>();\n    }\n  }\n}\n\nbool WritableStreamInternalController::isClosedOrClosing() {\n\n  bool isClosing = !queue.empty() && queue.back().event.is<kj::Own<Close>>();\n  bool isFlushing = !queue.empty() && queue.back().event.is<kj::Own<Flush>>();\n  return state.is<StreamStates::Closed>() || isClosing || isFlushing;\n}\n\nbool WritableStreamInternalController::isPiping() {\n  return state.is<IoOwn<Writable>>() && !queue.empty() && queue.back().event.is<kj::Own<Pipe>>();\n}\n\nbool WritableStreamInternalController::isErrored() {\n  return state.is<StreamStates::Errored>();\n}\n\nvoid WritableStreamInternalController::doClose(jsg::Lock& js) {\n  // If already in a terminal state, nothing to do.\n  if (state.isTerminal()) return;\n\n  state.transitionTo<StreamStates::Closed>();\n  KJ_IF_SOME(locked, writeState.tryGetUnsafe<WriterLocked>()) {\n    maybeResolvePromise(js, locked.getClosedFulfiller());\n    maybeResolvePromise(js, locked.getReadyFulfiller());\n    writeState.transitionTo<Locked>();\n  } else {\n    (void)writeState.transitionFromTo<PipeLocked, Unlocked>();\n  }\n  PendingAbort::dequeue(maybePendingAbort);\n}\n\nvoid WritableStreamInternalController::doError(jsg::Lock& js, v8::Local<v8::Value> reason) {\n  // If already in a terminal state, nothing to do.\n  if (state.isTerminal()) return;\n\n  state.transitionTo<StreamStates::Errored>(js.v8Ref(reason));\n  KJ_IF_SOME(locked, writeState.tryGetUnsafe<WriterLocked>()) {\n    maybeRejectPromise<void>(js, locked.getClosedFulfiller(), reason);\n    maybeResolvePromise(js, locked.getReadyFulfiller());\n    writeState.transitionTo<Locked>();\n  } else {\n    (void)writeState.transitionFromTo<PipeLocked, Unlocked>();\n  }\n  PendingAbort::dequeue(maybePendingAbort);\n}\n\nvoid WritableStreamInternalController::ensureWriting(jsg::Lock& js) {\n  auto& ioContext = IoContext::current();\n  if (queue.size() == 1) {\n    ioContext.addTask(ioContext.awaitJs(js, writeLoop(js, ioContext)).attach(addRef()));\n  }\n}\n\njsg::Promise<void> WritableStreamInternalController::writeLoop(\n    jsg::Lock& js, IoContext& ioContext) {\n  if (queue.empty()) {\n    return js.resolvedPromise();\n  } else KJ_IF_SOME(promise, queue.front().outputLock) {\n    return ioContext.awaitIo(js, kj::mv(*promise),\n        [this](jsg::Lock& js) -> jsg::Promise<void> { return writeLoopAfterFrontOutputLock(js); });\n  } else {\n    return writeLoopAfterFrontOutputLock(js);\n  }\n}\n\nvoid WritableStreamInternalController::finishClose(jsg::Lock& js) {\n  KJ_IF_SOME(pendingAbort, PendingAbort::dequeue(maybePendingAbort)) {\n    pendingAbort->complete(js);\n  }\n\n  doClose(js);\n}\n\nvoid WritableStreamInternalController::finishError(jsg::Lock& js, v8::Local<v8::Value> reason) {\n  KJ_IF_SOME(pendingAbort, PendingAbort::dequeue(maybePendingAbort)) {\n    // In this case, and only this case, we ignore any pending rejection\n    // that may be stored in the pendingAbort. The current exception takes\n    // precedence.\n    pendingAbort->fail(js, reason);\n  }\n\n  doError(js, reason);\n}\n\njsg::Promise<void> WritableStreamInternalController::writeLoopAfterFrontOutputLock(jsg::Lock& js) {\n  auto& ioContext = IoContext::current();\n\n  // This helper function is just used to enhance the assert logging when checking\n  // that the request in flight is the one we expect.\n  static constexpr auto inspectQueue = [](auto& queue, kj::StringPtr name) {\n    if (queue.size() > 1) {\n      kj::Vector<kj::String> events;\n      for (auto& event: queue) {\n        KJ_SWITCH_ONEOF(event.event) {\n          KJ_CASE_ONEOF(write, kj::Own<Write>) {\n            events.add(kj::str(\"Write\"));\n          }\n          KJ_CASE_ONEOF(flush, kj::Own<Flush>) {\n            events.add(kj::str(\"Flush\"));\n          }\n          KJ_CASE_ONEOF(close, kj::Own<Close>) {\n            events.add(kj::str(\"Close\"));\n          }\n          KJ_CASE_ONEOF(pipe, kj::Own<Pipe>) {\n            events.add(kj::str(\"Pipe\"));\n          }\n        }\n      }\n      return kj::str(\"Too many events in internal writablestream queue: \",\n          kj::delimited(kj::mv(events), \", \"));\n    }\n    return kj::String();\n  };\n\n  const auto makeChecker = [this]() {\n    // Make a helper function that asserts that the queue did not change state during a write/close\n    // operation. We normally only pop/drain the queue after write/close completion. We drain the\n    // queue concurrently during finalization, but finalization would also have canceled our\n    // write/close promise. The helper function also helpfully returns a reference to the current\n    // request in flight.\n    //\n    // We capture the current generation and verify it hasn't changed, rather than using pointer\n    // comparison, because RingBuffer may relocate elements when it grows.\n\n    return [this, expectedGeneration = queue.currentGeneration()]<typename Request>() -> Request& {\n      if constexpr (kj::isSameType<Request, Write>() || kj::isSameType<Request, Flush>()) {\n        // Write and flush requests can have any number of requests backed up after them.\n        KJ_ASSERT(!queue.empty());\n      } else if constexpr (kj::isSameType<Request, Close>()) {\n        // Pipe and Close requests are always the last one in the queue.\n        KJ_ASSERT(queue.size() == 1, queue.size(), inspectQueue(queue, \"Pipe\"));\n      } else if constexpr (kj::isSameType<Request, Pipe>()) {\n        // Pipe and Close requests are always the last one in the queue.\n        KJ_ASSERT(queue.size() == 1, queue.size(), inspectQueue(queue, \"Pipe\"));\n      }\n\n      // Verify nothing was popped from the queue while we were waiting.\n      KJ_ASSERT(queue.currentGeneration() == expectedGeneration);\n\n      return *queue.front().event.get<kj::Own<Request>>();\n    };\n  };\n\n  const auto maybeAbort = [this](jsg::Lock& js) -> bool {\n    auto& writable = KJ_ASSERT_NONNULL(state.tryGetUnsafe<IoOwn<Writable>>());\n    KJ_IF_SOME(pendingAbort, WritableStreamController::PendingAbort::dequeue(maybePendingAbort)) {\n      auto ex = js.exceptionToKj(pendingAbort->reason.addRef(js));\n      writable->abort(kj::mv(ex));\n      drain(js, pendingAbort->reason.getHandle(js));\n      pendingAbort->complete(js);\n      return true;\n    }\n    return false;\n  };\n\n  // Do we have anything left to do?\n  if (queue.empty()) return js.resolvedPromise();\n\n  KJ_SWITCH_ONEOF(queue.front().event) {\n    KJ_CASE_ONEOF(request, kj::Own<Write>) {\n      if (request->bytes.size() == 0) {\n        // Zero-length writes are no-ops with a pending event. If we allowed them, we'd have a hard\n        // time distinguishing between disconnections and zero-length reads on the other end of the\n        // TransformStream.\n        maybeResolvePromise(js, request->promise);\n        queue.pop_front();\n\n        // Note: we don't bother checking for an abort() here because either this write was just\n        //   queued, in which case abort() cannot have been called yet, or this write was processed\n        //   immediately after a previous write, in which case we just checked for an abort().\n        return writeLoop(js, ioContext);\n      }\n\n      // writeLoop() is only called with the sink in the Writable state.\n      auto& writable = state.getUnsafe<IoOwn<Writable>>();\n      auto check = makeChecker();\n\n      auto amountToWrite = request->bytes.size();\n\n      auto promise = writable->sink->write(request->bytes).attach(kj::mv(request->ownBytes));\n\n      // TODO(soon): We use awaitIoLegacy() here because if the stream terminates in JavaScript in\n      // this same isolate, then the promise may actually be waiting on JavaScript to do something,\n      // and so should not be considered waiting on external I/O. We will need to use\n      // registerPendingEvent() manually when reading from an external stream. Ideally, we would\n      // refactor the implementation so that when waiting on a JavaScript stream, we strictly use\n      // jsg::Promises and not kj::Promises, so that it doesn't look like I/O at all, and there's\n      // no need to drop the isolate lock and take it again every time some data is read/written.\n      // That's a larger refactor, though.\n      return ioContext.awaitIoLegacy(js, writable->canceler.wrap(kj::mv(promise)))\n          .then(js,\n              ioContext.addFunctor(\n                  [this, check, maybeAbort, amountToWrite](jsg::Lock& js) -> jsg::Promise<void> {\n        // Under some conditions, the clean up has already happened.\n        if (queue.empty()) return js.resolvedPromise();\n        auto& request = check.template operator()<Write>();\n        maybeResolvePromise(js, request.promise);\n        adjustWriteBufferSize(js, -amountToWrite);\n        KJ_IF_SOME(o, observer) {\n          o->onChunkDequeued(amountToWrite);\n        }\n        queue.pop_front();\n        maybeAbort(js);\n        return writeLoop(js, IoContext::current());\n      }),\n              ioContext.addFunctor([this, check, maybeAbort, amountToWrite](\n                                       jsg::Lock& js, jsg::Value reason) -> jsg::Promise<void> {\n        // Under some conditions, the clean up has already happened.\n        if (queue.empty()) return js.resolvedPromise();\n        auto handle = reason.getHandle(js);\n        auto& request = check.template operator()<Write>();\n        auto& writable = state.getUnsafe<IoOwn<Writable>>();\n        adjustWriteBufferSize(js, -amountToWrite);\n        KJ_IF_SOME(o, observer) {\n          o->onChunkDequeued(amountToWrite);\n        }\n        maybeRejectPromise<void>(js, request.promise, handle);\n        queue.pop_front();\n        if (!maybeAbort(js)) {\n          auto ex = js.exceptionToKj(reason.addRef(js));\n          writable->abort(kj::mv(ex));\n          drain(js, handle);\n        }\n        return js.resolvedPromise();\n      }));\n    }\n    KJ_CASE_ONEOF(request, kj::Own<Pipe>) {\n      // The destination should still be Writable, because the only way to transition to an\n      // errored state would have been if a write request in the queue ahead of us encountered an\n      // error. But in that case, the queue would already have been drained and we wouldn't be here.\n      auto& writable = state.getUnsafe<IoOwn<Writable>>();\n\n      if (request->checkSignal(js)) {\n        // If the signal is triggered, checkSignal will handle erroring the source and destination.\n        return js.resolvedPromise();\n      }\n\n      // The readable side should *should* still be readable here but let's double check, just\n      // to be safe, both for closed state and errored states.\n      if (request->source().isClosed()) {\n        request->source().release(js);\n        // If the source is closed, the spec requires us to close the destination unless the\n        // preventClose option is true.\n        if (!request->preventClose() && !isClosedOrClosing()) {\n          doClose(js);\n        } else {\n          writeState.transitionTo<Unlocked>();\n        }\n        return js.resolvedPromise();\n      }\n\n      KJ_IF_SOME(errored, request->source().tryGetErrored(js)) {\n        request->source().release(js);\n        // If the source is errored, the spec requires us to error the destination unless the\n        // preventAbort option is true.\n        if (!request->preventAbort()) {\n          auto ex = js.exceptionToKj(js.v8Ref(errored));\n          writable->abort(kj::mv(ex));\n          drain(js, errored);\n        } else {\n          writeState.transitionTo<Unlocked>();\n        }\n        return js.resolvedPromise();\n      }\n\n      // Up to this point, we really don't know what kind of ReadableStream source we're dealing\n      // with. If the source is backed by a ReadableStreamSource, then the call to tryPumpTo below\n      // will return a kj::Promise that will be resolved once the kj mechanisms for piping have\n      // completed. From there, the only thing left to do is resolve the JavaScript pipe promise,\n      // unlock things, and continue on. If the call to tryPumpTo returns nullptr, however, the\n      // ReadableStream is JavaScript-backed and we need to setup a JavaScript-promise read/write\n      // loop to pass the data into the destination.\n\n      const auto handlePromise = [this, &ioContext, check = makeChecker(),\n                                     preventAbort = request->preventAbort()](\n                                     jsg::Lock& js, auto promise) {\n        return promise.then(js, ioContext.addFunctor([this, check](jsg::Lock& js) mutable {\n          // Under some conditions, the clean up has already happened.\n          if (queue.empty()) return js.resolvedPromise();\n\n          auto& request = check.template operator()<Pipe>();\n\n          // It's possible we got here because the source errored but preventAbort was set.\n          // In that case, we need to treat preventAbort the same as preventClose. Be\n          // sure to check this before calling sourceLock.close() or the error detail will\n          // be lost.\n          // Capture preventClose now so we can modify it locally if needed.\n          bool preventClose = request.preventClose();\n          KJ_IF_SOME(errored, request.source().tryGetErrored(js)) {\n            if (request.preventAbort()) preventClose = true;\n            // Even through we're not going to close the destination, we still want the\n            // pipe promise itself to be rejected in this case.\n            maybeRejectPromise<void>(js, request.promise(), errored);\n          } else KJ_IF_SOME(errored, state.tryGetUnsafe<StreamStates::Errored>()) {\n            maybeRejectPromise<void>(js, request.promise(), errored.getHandle(js));\n          } else {\n            maybeResolvePromise(js, request.promise());\n          }\n\n          // Always transition the readable side to the closed state, because we read until EOF.\n          // Note that preventClose (below) means \"don't close the writable side\", i.e. don't\n          // call end().\n          request.source().close(js);\n          queue.pop_front();\n\n          if (!preventClose) {\n            // Note: unlike a real Close request, it's not possible for us to have been aborted.\n            return close(js, true);\n          } else {\n            writeState.transitionTo<Unlocked>();\n          }\n          return js.resolvedPromise();\n        }),\n            ioContext.addFunctor(\n                [this, check, preventAbort](jsg::Lock& js, jsg::Value reason) mutable {\n          auto handle = reason.getHandle(js);\n          auto& request = check.template operator()<Pipe>();\n          maybeRejectPromise<void>(js, request.promise(), handle);\n          // TODO(conform): Remember all those checks we performed in ReadableStream::pipeTo()?\n          // We're supposed to perform the same checks continually, e.g., errored writes should\n          // cancel the readable side unless preventCancel is truthy... This would require\n          // deeper integration with the implementation of pumpTo(). Oh well. One consequence\n          // of this is that if there is an error on the writable side, we error the readable\n          // side, rather than close (cancel) it, which is what the spec would have us do.\n          // TODO(now): Warn on the console about this.\n          request.source().error(js, handle);\n          queue.pop_front();\n          if (!preventAbort) {\n            return abort(js, handle);\n          }\n          doError(js, handle);\n          return js.resolvedPromise();\n        }));\n      };\n\n      KJ_IF_SOME(promise, request->source().tryPumpTo(*writable->sink, !request->preventClose())) {\n        return handlePromise(js,\n            ioContext.awaitIo(js,\n                writable->canceler.wrap(\n                    AbortSignal::maybeCancelWrap(js, request->maybeSignal(), kj::mv(promise)))));\n      }\n\n      // The ReadableStream is JavaScript-backed. We can still pipe the data but it's going to be\n      // a bit slower because we will be relying on JavaScript promises when reading the data\n      // from the ReadableStream, then waiting on kj::Promises to write the data. We will keep\n      // reading until either the source or destination errors or until the source signals that\n      // it is done.\n      return handlePromise(js, request->pipeLoop(js));\n    }\n    KJ_CASE_ONEOF(request, kj::Own<Close>) {\n      // writeLoop() is only called with the sink in the Writable state.\n      auto& writable = state.getUnsafe<IoOwn<Writable>>();\n      auto check = makeChecker();\n\n      return ioContext.awaitIo(js, writable->canceler.wrap(writable->sink->end()))\n          .then(js, ioContext.addFunctor([this, check](jsg::Lock& js) {\n        // Under some conditions, the clean up has already happened.\n        if (queue.empty()) return;\n        auto& request = check.template operator()<Close>();\n        maybeResolvePromise(js, request.promise);\n        queue.pop_front();\n        finishClose(js);\n      }),\n              ioContext.addFunctor([this, check](jsg::Lock& js, jsg::Value reason) {\n        // Under some conditions, the clean up has already happened.\n        if (queue.empty()) return;\n        auto handle = reason.getHandle(js);\n        auto& request = check.template operator()<Close>();\n        maybeRejectPromise<void>(js, request.promise, handle);\n        queue.pop_front();\n        finishError(js, handle);\n      }));\n    }\n    KJ_CASE_ONEOF(request, kj::Own<Flush>) {\n      // This is not a standards-defined state for a WritableStream and is only used internally\n      // for Socket's startTls call.\n      //\n      // Flushing is similar to closing the stream, the main difference is that `finishClose`\n      // and `writable->end()` are never called.\n      // Note: For Flush, we don't need makeChecker since we process immediately without async I/O.\n      maybeResolvePromise(js, request->promise);\n      queue.pop_front();\n\n      return js.resolvedPromise();\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\nbool WritableStreamInternalController::Pipe::State::checkSignal(jsg::Lock& js) {\n  // Returns true if the caller should bail out and stop processing. This happens in two cases:\n  // 1. The State was aborted (e.g., by drain()) - the Pipe is being torn down\n  // 2. The AbortSignal was triggered - we handle the abort and return true\n  // In both cases, the caller should return a resolved promise and not continue the pipe loop.\n  if (aborted) return true;\n\n  KJ_IF_SOME(signal, maybeSignal) {\n    if (signal->getAborted(js)) {\n      auto reason = signal->getReason(js);\n\n      // abort process might call parent.drain which will delete this,\n      // move/copy everything we need after into temps.\n      auto& parentRef = this->parent;\n      auto& sourceRef = this->source;\n      auto preventCancelCopy = this->preventCancel;\n      auto promiseCopy = kj::mv(this->promise);\n\n      if (!preventAbort) {\n        KJ_IF_SOME(writable, parent.state.tryGetUnsafe<IoOwn<Writable>>()) {\n          auto ex = js.exceptionToKj(reason);\n          writable->abort(kj::mv(ex));\n          parentRef.drain(js, reason);\n        } else {\n          parent.writeState.transitionTo<Unlocked>();\n        }\n      } else {\n        parent.writeState.transitionTo<Unlocked>();\n      }\n      if (!preventCancelCopy) {\n        sourceRef.release(js, v8::Local<v8::Value>(reason));\n      } else {\n        sourceRef.release(js);\n      }\n      maybeRejectPromise<void>(js, promiseCopy, reason);\n      return true;\n    }\n  }\n  return false;\n}\n\njsg::Promise<void> WritableStreamInternalController::Pipe::State::write(\n    v8::Local<v8::Value> handle) {\n  auto& writable = parent.state.getUnsafe<IoOwn<Writable>>();\n  // TODO(soon): Once jsg::BufferSource lands and we're able to use it, this can be simplified.\n  KJ_ASSERT(handle->IsArrayBuffer() || handle->IsArrayBufferView());\n  std::shared_ptr<v8::BackingStore> store;\n  size_t byteLength = 0;\n  size_t byteOffset = 0;\n  if (handle->IsArrayBuffer()) {\n    auto buffer = handle.template As<v8::ArrayBuffer>();\n    store = buffer->GetBackingStore();\n    byteLength = buffer->ByteLength();\n  } else {\n    auto view = handle.template As<v8::ArrayBufferView>();\n    store = view->Buffer()->GetBackingStore();\n    byteLength = view->ByteLength();\n    byteOffset = view->ByteOffset();\n  }\n  kj::byte* data = reinterpret_cast<kj::byte*>(store->Data()) + byteOffset;\n  // TODO(cleanup): Have this method accept a jsg::Lock& from the caller instead of using\n  // v8::Isolate::GetCurrent();\n  auto& js = jsg::Lock::current();\n\n  // For resizable ArrayBuffers or shared backing stores, we must eagerly copy\n  // the data. A resizable ArrayBuffer's logical byte length can be changed by user\n  // JS after write() returns but before the sink consumes the data, making the\n  // cached byteLength stale.\n  // But also just beacuse of V8 Sandbox requirements, we really should be copying\n  // the data from the ArrayBuffer anyway... We incur an allocation and copy cost\n  // here but that's to be expected.\n  auto backing = kj::heapArray<kj::byte>(byteLength);\n  backing.asPtr().copyFrom(kj::arrayPtr(data, byteLength));\n  return IoContext::current().awaitIo(js,\n      writable->canceler.wrap(writable->sink->write(backing)).attach(kj::mv(backing)),\n      [](jsg::Lock&) {});\n}\n\njsg::Promise<void> WritableStreamInternalController::Pipe::State::pipeLoop(jsg::Lock& js) {\n  // This is a bit of dance. We got here because the source ReadableStream does not support\n  // the internal, more efficient kj pipe (which means it is a JavaScript-backed ReadableStream).\n  // We need to call read() on the source which returns a JavaScript Promise, wait on it to resolve,\n  // then call write() which returns a kj::Promise. Before each iteration we check to see if either\n  // the source or the destination have errored or closed and handle accordingly. At some point we\n  // should explore if there are ways of making this more efficient. For the most part, however,\n  // every read from the source must call into JavaScript to advance the ReadableStream.\n\n  auto& ioContext = IoContext::current();\n\n  if (aborted) {\n    return js.resolvedPromise();\n  }\n\n  if (checkSignal(js)) {\n    // If the signal is triggered, checkSignal will handle erroring the source and destination.\n    return js.resolvedPromise();\n  }\n\n  // Here we check the closed and errored states of both the source and the destination,\n  // propagating those states to the other based on the options. This check must be\n  // performed at the start of each iteration in the pipe loop.\n  //\n  // TODO(soon): These are the same checks made before we entered the loop. Try to\n  // unify the code to reduce duplication.\n\n  KJ_IF_SOME(errored, source.tryGetErrored(js)) {\n    source.release(js);\n    if (!preventAbort) {\n      KJ_IF_SOME(writable, parent.state.tryGetUnsafe<IoOwn<Writable>>()) {\n        auto ex = js.exceptionToKj(js.v8Ref(errored));\n        writable->abort(kj::mv(ex));\n        return js.rejectedPromise<void>(errored);\n      }\n    }\n\n    // If preventAbort was true, we're going to unlock the destination now.\n    // We are not going to propagate the error here tho.\n    parent.writeState.transitionTo<Unlocked>();\n    return js.resolvedPromise();\n  }\n\n  KJ_IF_SOME(errored, parent.state.tryGetUnsafe<StreamStates::Errored>()) {\n    parent.writeState.transitionTo<Unlocked>();\n    if (!preventCancel) {\n      auto reason = errored.getHandle(js);\n      source.release(js, reason);\n      return js.rejectedPromise<void>(reason);\n    }\n    source.release(js);\n    return js.resolvedPromise();\n  }\n\n  if (source.isClosed()) {\n    source.release(js);\n    if (!preventClose) {\n      KJ_ASSERT(!parent.state.is<StreamStates::Errored>());\n      if (!parent.isClosedOrClosing()) {\n        // We'll only be here if the sink is in the Writable state.\n        auto& ioContext = IoContext::current();\n        // Capture a ref to the state to keep it alive during async operations.\n        return ioContext\n            .awaitIo(js, parent.state.getUnsafe<IoOwn<Writable>>()->sink->end(), [](jsg::Lock&) {})\n            .then(js, ioContext.addFunctor([state = kj::addRef(*this)](jsg::Lock& js) {\n          if (state->aborted) return;\n          state->parent.finishClose(js);\n        }),\n                ioContext.addFunctor([state = kj::addRef(*this)](jsg::Lock& js, jsg::Value reason) {\n          if (state->aborted) return;\n          state->parent.finishError(js, reason.getHandle(js));\n        }));\n      }\n      parent.writeState.transitionTo<Unlocked>();\n    }\n    return js.resolvedPromise();\n  }\n\n  if (parent.isClosedOrClosing()) {\n    auto destClosed = js.v8TypeError(\"This destination writable stream is closed.\"_kj);\n    parent.writeState.transitionTo<Unlocked>();\n\n    if (!preventCancel) {\n      source.release(js, destClosed);\n    } else {\n      source.release(js);\n    }\n\n    return js.rejectedPromise<void>(destClosed);\n  }\n\n  return source.read(js).then(js,\n      ioContext.addFunctor([state = kj::addRef(*this)](\n                               jsg::Lock& js, ReadResult result) mutable -> jsg::Promise<void> {\n    if (state->aborted || state->checkSignal(js) || result.done) {\n      return js.resolvedPromise();\n    }\n\n    // WritableStreamInternalControllers only support byte data. If we can't\n    // interpret the result.value as bytes, then we error the pipe; otherwise\n    // we sent those bytes on to the WritableStreamSink.\n    KJ_IF_SOME(value, result.value) {\n      auto handle = value.getHandle(js);\n      if (handle->IsArrayBuffer() || handle->IsArrayBufferView()) {\n        return state->write(handle).then(js,\n            [state = kj::addRef(*state)](jsg::Lock& js) mutable -> jsg::Promise<void> {\n          if (state->aborted) {\n            return js.resolvedPromise();\n          }\n          // The signal will be checked again at the start of the next loop iteration.\n          return state->pipeLoop(js);\n        },\n            [state = kj::addRef(*state)](\n                jsg::Lock& js, jsg::Value reason) mutable -> jsg::Promise<void> {\n          if (state->aborted) {\n            return js.resolvedPromise();\n          }\n          state->parent.doError(js, reason.getHandle(js));\n          return state->pipeLoop(js);\n        });\n      }\n    }\n    // Undefined and null are perfectly valid values to pass through a ReadableStream,\n    // but we can't interpret them as bytes so if we get them here, we error the pipe.\n    auto error = js.v8TypeError(\"This WritableStream only supports writing byte types.\"_kj);\n    auto& writable = state->parent.state.getUnsafe<IoOwn<Writable>>();\n    auto ex = js.exceptionToKj(js.v8Ref(error));\n    writable->abort(kj::mv(ex));\n    // The error condition will be handled at the start of the next iteration.\n    return state->pipeLoop(js);\n  }),\n      ioContext.addFunctor([state = kj::addRef(*this)](\n                               jsg::Lock& js, jsg::Value reason) mutable -> jsg::Promise<void> {\n    if (state->aborted) {\n      return js.resolvedPromise();\n    }\n    // The error will be processed and propagated in the next iteration.\n    return state->pipeLoop(js);\n  }));\n}\n\nvoid WritableStreamInternalController::drain(jsg::Lock& js, v8::Local<v8::Value> reason) {\n  doError(js, reason);\n  while (!queue.empty()) {\n    KJ_SWITCH_ONEOF(queue.front().event) {\n      KJ_CASE_ONEOF(writeRequest, kj::Own<Write>) {\n        maybeRejectPromise<void>(js, writeRequest->promise, reason);\n      }\n      KJ_CASE_ONEOF(pipeRequest, kj::Own<Pipe>) {\n        if (!pipeRequest->preventCancel()) {\n          pipeRequest->source().cancel(js, reason);\n        }\n        maybeRejectPromise<void>(js, pipeRequest->promise(), reason);\n      }\n      KJ_CASE_ONEOF(closeRequest, kj::Own<Close>) {\n        maybeRejectPromise<void>(js, closeRequest->promise, reason);\n      }\n      KJ_CASE_ONEOF(flushRequest, kj::Own<Flush>) {\n        maybeRejectPromise<void>(js, flushRequest->promise, reason);\n      }\n    }\n    queue.pop_front();\n  }\n}\n\nvoid WritableStreamInternalController::visitForGc(jsg::GcVisitor& visitor) {\n  for (auto& event: queue) {\n    KJ_SWITCH_ONEOF(event.event) {\n      KJ_CASE_ONEOF(write, kj::Own<Write>) {\n        visitor.visit(write->promise);\n      }\n      KJ_CASE_ONEOF(close, kj::Own<Close>) {\n        visitor.visit(close->promise);\n      }\n      KJ_CASE_ONEOF(flush, kj::Own<Flush>) {\n        visitor.visit(flush->promise);\n      }\n      KJ_CASE_ONEOF(pipe, kj::Own<Pipe>) {\n        visitor.visit(pipe->maybeSignal(), pipe->promise());\n      }\n    }\n  }\n  KJ_IF_SOME(locked, writeState.tryGetUnsafe<WriterLocked>()) {\n    visitor.visit(locked);\n  }\n  KJ_IF_SOME(pendingAbort, maybePendingAbort) {\n    visitor.visit(*pendingAbort);\n  }\n}\n\nvoid ReadableStreamInternalController::visitForGc(jsg::GcVisitor& visitor) {\n  KJ_IF_SOME(locked, readState.tryGetUnsafe<ReaderLocked>()) {\n    visitor.visit(locked);\n  }\n}\n\nkj::Maybe<ReadableStreamController::PipeController&> ReadableStreamInternalController::\n    tryPipeLock() {\n  if (isLockedToReader()) {\n    return kj::none;\n  }\n  return readState.transitionTo<PipeLocked>(*this);\n}\n\nbool ReadableStreamInternalController::PipeLocked::isClosed() {\n  return inner.state.is<StreamStates::Closed>();\n}\n\nkj::Maybe<v8::Local<v8::Value>> ReadableStreamInternalController::PipeLocked::tryGetErrored(\n    jsg::Lock& js) {\n  KJ_IF_SOME(errored, inner.state.tryGetUnsafe<StreamStates::Errored>()) {\n    return errored.getHandle(js);\n  }\n  return kj::none;\n}\n\nvoid ReadableStreamInternalController::PipeLocked::cancel(\n    jsg::Lock& js, v8::Local<v8::Value> reason) {\n  if (inner.state.is<Readable>()) {\n    inner.doCancel(js, reason);\n  }\n}\n\nvoid ReadableStreamInternalController::PipeLocked::close(jsg::Lock& js) {\n  inner.doClose(js);\n}\n\nvoid ReadableStreamInternalController::PipeLocked::error(\n    jsg::Lock& js, v8::Local<v8::Value> reason) {\n  inner.doError(js, reason);\n}\n\nvoid ReadableStreamInternalController::PipeLocked::release(\n    jsg::Lock& js, kj::Maybe<v8::Local<v8::Value>> maybeError) {\n  KJ_IF_SOME(error, maybeError) {\n    cancel(js, error);\n  }\n  inner.readState.transitionTo<Unlocked>();\n}\n\nkj::Maybe<kj::Promise<void>> ReadableStreamInternalController::PipeLocked::tryPumpTo(\n    WritableStreamSink& sink, bool end) {\n  // This is safe because the caller should have already checked isClosed and tryGetErrored\n  // and handled those before calling tryPumpTo.\n  auto& readable = KJ_ASSERT_NONNULL(inner.state.tryGetUnsafe<Readable>());\n  return IoContext::current().waitForDeferredProxy(readable->pumpTo(sink, end));\n}\n\njsg::Promise<ReadResult> ReadableStreamInternalController::PipeLocked::read(jsg::Lock& js) {\n  return KJ_ASSERT_NONNULL(inner.read(js, kj::none));\n}\n\njsg::Promise<jsg::BufferSource> ReadableStreamInternalController::readAllBytes(\n    jsg::Lock& js, uint64_t limit) {\n  if (isLockedToReader()) {\n    return js.rejectedPromise<jsg::BufferSource>(KJ_EXCEPTION(\n        FAILED, \"jsg.TypeError: This ReadableStream is currently locked to a reader.\"));\n  }\n  if (isPendingClosure) {\n    return js.rejectedPromise<jsg::BufferSource>(\n        js.v8TypeError(\"This ReadableStream belongs to an object that is closing.\"_kj));\n  }\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, 0);\n      return js.resolvedPromise(jsg::BufferSource(js, kj::mv(backing)));\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return js.rejectedPromise<jsg::BufferSource>(errored.addRef(js));\n    }\n    KJ_CASE_ONEOF(readable, Readable) {\n      auto source = KJ_ASSERT_NONNULL(removeSource(js));\n      auto& context = IoContext::current();\n      // TODO(perf): v8 sandboxing will require that backing stores are allocated within\n      // the sandbox. This will require a change to the API of ReadableStreamSource::readAllBytes.\n      // For now, we'll read and allocate into a proper backing store.\n      return context.awaitIoLegacy(js, source->readAllBytes(limit).attach(kj::mv(source)))\n          .then(js, [](jsg::Lock& js, kj::Array<kj::byte> bytes) -> jsg::BufferSource {\n        auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, bytes.size());\n        backing.asArrayPtr().copyFrom(bytes);\n        return jsg::BufferSource(js, kj::mv(backing));\n      });\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\njsg::Promise<kj::String> ReadableStreamInternalController::readAllText(\n    jsg::Lock& js, uint64_t limit) {\n  if (isLockedToReader()) {\n    return js.rejectedPromise<kj::String>(KJ_EXCEPTION(\n        FAILED, \"jsg.TypeError: This ReadableStream is currently locked to a reader.\"));\n  }\n  if (isPendingClosure) {\n    return js.rejectedPromise<kj::String>(\n        js.v8TypeError(\"This ReadableStream belongs to an object that is closing.\"_kj));\n  }\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      return js.resolvedPromise(kj::String());\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return js.rejectedPromise<kj::String>(errored.addRef(js));\n    }\n    KJ_CASE_ONEOF(readable, Readable) {\n      auto source = KJ_ASSERT_NONNULL(removeSource(js));\n      auto& context = IoContext::current();\n      auto option = ReadAllTextOption::NULL_TERMINATE;\n      KJ_IF_SOME(flags, FeatureFlags::tryGet(js)) {\n        if (flags.getStripBomInReadAllText()) {\n          option |= ReadAllTextOption::STRIP_BOM;\n        }\n      }\n      return context.awaitIoLegacy(js, source->readAllText(limit, option).attach(kj::mv(source)));\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::Maybe<uint64_t> ReadableStreamInternalController::tryGetLength(StreamEncoding encoding) {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      return static_cast<uint64_t>(0);\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return kj::none;\n    }\n    KJ_CASE_ONEOF(readable, Readable) {\n      return readable->tryGetLength(encoding);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::Own<ReadableStreamController> ReadableStreamInternalController::detach(\n    jsg::Lock& js, bool ignoreDetached) {\n  return newReadableStreamInternalController(\n      IoContext::current(), KJ_ASSERT_NONNULL(removeSource(js, ignoreDetached)));\n}\n\nkj::Promise<DeferredProxy<void>> ReadableStreamInternalController::pumpTo(\n    jsg::Lock& js, kj::Own<WritableStreamSink> sink, bool end) {\n  auto source = KJ_ASSERT_NONNULL(removeSource(js));\n\n  struct Holder: public kj::Refcounted {\n    kj::Own<WritableStreamSink> sink;\n    kj::Own<ReadableStreamSource> source;\n    bool done = false;\n\n    Holder(kj::Own<WritableStreamSink> sink, kj::Own<ReadableStreamSource> source)\n        : sink(kj::mv(sink)),\n          source(kj::mv(source)) {}\n    ~Holder() noexcept(false) {\n      if (!done) {\n        // It appears the pump was canceled. We should make sure this propagates back to the\n        // source stream. This is important in particular when we're implementing the response\n        // pump for an HTTP event (see Response::send()). Presumably it was canceled because the\n        // client disconnected. If we don't cancel the source, then if the source is one end of\n        // a TransformStream, the write end will just hang. Of course, this is fine if there are\n        // no waitUntil()s running, because the whole I/O context will be canceled anyway. But if\n        // there are waitUntil()s, then the application probably expects to get an exception from\n        // the write() on cancellation, rather than have it hang.\n        source->cancel(KJ_EXCEPTION(DISCONNECTED, \"pump canceled\"));\n      }\n    }\n  };\n\n  auto holder = kj::rc<Holder>(kj::mv(sink), kj::mv(source));\n  return holder->source->pumpTo(*holder->sink, end)\n      .then([holder = holder.addRef()](DeferredProxy<void> proxy) mutable -> DeferredProxy<void> {\n    proxy.proxyTask = proxy.proxyTask.attach(holder.addRef());\n    holder->done = true;\n    return kj::mv(proxy);\n  }, [holder = holder.addRef()](kj::Exception&& ex) mutable {\n    holder->sink->abort(kj::cp(ex));\n    holder->source->cancel(kj::cp(ex));\n    holder->done = true;\n    return kj::mv(ex);\n  });\n}\n\nStreamEncoding ReadableStreamInternalController::getPreferredEncoding() {\n  return state.tryGetUnsafe<Readable>()\n      .map([](Readable& readable) {\n    return readable->getPreferredEncoding();\n  }).orDefault(StreamEncoding::IDENTITY);\n}\n\nkj::Own<ReadableStreamController> newReadableStreamInternalController(\n    IoContext& ioContext, kj::Own<ReadableStreamSource> source) {\n  return kj::heap<ReadableStreamInternalController>(ioContext.addObject(kj::mv(source)));\n}\n\nkj::Own<WritableStreamController> newWritableStreamInternalController(IoContext& ioContext,\n    kj::Own<WritableStreamSink> sink,\n    kj::Maybe<kj::Own<ByteStreamObserver>> observer,\n    kj::Maybe<uint64_t> maybeHighWaterMark,\n    kj::Maybe<jsg::Promise<void>> maybeClosureWaitable) {\n  return kj::heap<WritableStreamInternalController>(\n      kj::mv(sink), kj::mv(observer), maybeHighWaterMark, kj::mv(maybeClosureWaitable));\n}\n\nkj::StringPtr WritableStreamInternalController::jsgGetMemoryName() const {\n  return \"WritableStreamInternalController\"_kjc;\n}\n\nsize_t WritableStreamInternalController::jsgGetMemorySelfSize() const {\n  return sizeof(WritableStreamInternalController);\n}\nvoid WritableStreamInternalController::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {}\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      tracker.trackField(\"error\", errored);\n    }\n    KJ_CASE_ONEOF(_, IoOwn<Writable>) {\n      // Ideally we'd be able to track the size of any pending writes held in the sink's\n      // queue but since it is behind an IoOwn and we won't be holding the IoContext here,\n      // we can't.\n      tracker.trackFieldWithSize(\"IoOwn<WritableStreamSink>\", sizeof(IoOwn<WritableStreamSink>));\n    }\n  }\n  KJ_IF_SOME(writerLocked, writeState.tryGetUnsafe<WriterLocked>()) {\n    tracker.trackField(\"writerLocked\", writerLocked);\n  }\n  tracker.trackField(\"pendingAbort\", maybePendingAbort);\n  tracker.trackField(\"maybeClosureWaitable\", maybeClosureWaitable);\n\n  for (auto& event: queue) {\n    tracker.trackField(\"event\", event);\n  }\n}\n\nkj::StringPtr ReadableStreamInternalController::jsgGetMemoryName() const {\n  return \"ReadableStreamInternalController\"_kjc;\n}\n\nsize_t ReadableStreamInternalController::jsgGetMemorySelfSize() const {\n  return sizeof(ReadableStreamInternalController);\n}\n\nvoid ReadableStreamInternalController::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {}\n    KJ_CASE_ONEOF(error, StreamStates::Errored) {\n      tracker.trackField(\"error\", error);\n    }\n    KJ_CASE_ONEOF(readable, Readable) {\n      // Ideally we'd be able to track the size of any pending reads held in the source's\n      // queue but since it is behind an IoOwn and we won't be holding the IoContext here,\n      // we can't.\n      tracker.trackFieldWithSize(\n          \"IoOwn<ReadableStreamSource>\", sizeof(IoOwn<ReadableStreamSource>));\n    }\n  }\n  KJ_SWITCH_ONEOF(readState) {\n    KJ_CASE_ONEOF(unlocked, Unlocked) {}\n    KJ_CASE_ONEOF(locked, Locked) {}\n    KJ_CASE_ONEOF(pipeLocked, PipeLocked) {}\n    KJ_CASE_ONEOF(readerLocked, ReaderLocked) {\n      tracker.trackField(\"readerLocked\", readerLocked);\n    }\n  }\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/internal.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"common.h\"\n#include \"writable.h\"\n\n#include <workerd/io/io-context.h>\n#include <workerd/io/observer.h>\n#include <workerd/util/ring-buffer.h>\n#include <workerd/util/state-machine.h>\n\n#include <kj/refcount.h>\n\nnamespace workerd::api {\n\n// =======================================================================================\n// The ReadableStreamInternalController and WritableStreamInternalController provide the\n// internal (original) implementation of the ReadableStream/WritableStream objects and are\n// each backed by the ReadableStreamSource and WritableStreamSink respectively. Every stream\n// implementation that originates from *within* the Workers runtime will use these.\n//\n// It is important to understand that the behavior of these are not entirely compliant with\n// the streams specification.\n\n// The ReadableStreamInternalController is always in one of three states: Readable, Closed,\n// or Errored. When the state is Readable, the controller has an associated ReadableStreamSource.\n// When the state is Errored, the ReadableStreamSource has been released and the controller\n// stores a jsg::Value with whatever value was used to error. When Closed, the\n// ReadableStreamSource has been released.\n\n// Likewise, the WritableStreamInternalController is always either Writable, Closed, or Errored.\n// When the state is Writable, the controller has an associated WritableStreamSink. In either of\n// the other two states, the sink has been released.\n\nclass WritableStreamInternalController;\n\nclass ReadableStreamInternalController: public ReadableStreamController {\n public:\n  using Readable = IoOwn<ReadableStreamSource>;\n\n  explicit ReadableStreamInternalController(StreamStates::Closed closed)\n      : state(State::create<StreamStates::Closed>()) {}\n  explicit ReadableStreamInternalController(StreamStates::Errored errored)\n      : state(State::create<StreamStates::Errored>(kj::mv(errored))) {}\n  explicit ReadableStreamInternalController(Readable readable)\n      : state(State::create<Readable>(kj::mv(readable))) {}\n\n  KJ_DISALLOW_COPY_AND_MOVE(ReadableStreamInternalController);\n\n  ~ReadableStreamInternalController() noexcept(false) override;\n\n  void setOwnerRef(ReadableStream& stream) override {\n    owner = stream;\n  }\n\n  jsg::Ref<ReadableStream> addRef() override;\n\n  bool isByteOriented() const override {\n    return true;\n  }\n\n  kj::Maybe<jsg::Promise<ReadResult>> read(\n      jsg::Lock& js, kj::Maybe<ByobOptions> byobOptions) override;\n\n  kj::Maybe<jsg::Promise<DrainingReadResult>> drainingRead(\n      jsg::Lock& js, size_t maxRead = kj::maxValue) override;\n\n  jsg::Promise<void> pipeTo(\n      jsg::Lock& js, WritableStreamController& destination, PipeToOptions options) override;\n\n  jsg::Promise<void> cancel(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> reason) override;\n\n  Tee tee(jsg::Lock& js) override;\n\n  kj::Maybe<kj::Own<ReadableStreamSource>> removeSource(\n      jsg::Lock& js, bool ignoreDisturbed = false);\n\n  bool isClosedOrErrored() const override {\n    return state.is<StreamStates::Closed>() || state.is<StreamStates::Errored>();\n  }\n\n  bool isClosed() const override {\n    return state.is<StreamStates::Closed>();\n  }\n\n  bool isDisturbed() override {\n    return disturbed;\n  }\n\n  bool isLockedToReader() const override {\n    return !readState.is<Unlocked>();\n  }\n\n  bool lockReader(jsg::Lock& js, Reader& reader) override;\n\n  void releaseReader(Reader& reader, kj::Maybe<jsg::Lock&> maybeJs) override;\n  // See the comment for releaseReader in common.h for details on the use of maybeJs\n\n  kj::Maybe<PipeController&> tryPipeLock() override;\n\n  void visitForGc(jsg::GcVisitor& visitor) override;\n\n  jsg::Promise<jsg::BufferSource> readAllBytes(jsg::Lock& js, uint64_t limit) override;\n  jsg::Promise<kj::String> readAllText(jsg::Lock& js, uint64_t limit) override;\n\n  kj::Maybe<uint64_t> tryGetLength(StreamEncoding encoding) override;\n\n  kj::Promise<DeferredProxy<void>> pumpTo(\n      jsg::Lock& js, kj::Own<WritableStreamSink> sink, bool end) override;\n\n  StreamEncoding getPreferredEncoding() override;\n\n  kj::Own<ReadableStreamController> detach(jsg::Lock& js, bool ignoreDisturbed) override;\n\n  void setPendingClosure() override {\n    isPendingClosure = true;\n  }\n\n  kj::StringPtr jsgGetMemoryName() const override;\n  size_t jsgGetMemorySelfSize() const override;\n  void jsgGetMemoryInfo(jsg::MemoryTracker& info) const override;\n\n private:\n  void doCancel(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> reason);\n  void doClose(jsg::Lock& js);\n  void doError(jsg::Lock& js, v8::Local<v8::Value> reason);\n\n  class PipeLocked: public PipeController {\n   public:\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"pipe-locked\"_kj;\n    PipeLocked(ReadableStreamInternalController& inner): inner(inner) {}\n\n    bool isClosed() override;\n\n    kj::Maybe<v8::Local<v8::Value>> tryGetErrored(jsg::Lock& js) override;\n\n    void cancel(jsg::Lock& js, v8::Local<v8::Value> reason) override;\n\n    void close(jsg::Lock& js) override;\n\n    void error(jsg::Lock& js, v8::Local<v8::Value> reason) override;\n\n    void release(jsg::Lock& js, kj::Maybe<v8::Local<v8::Value>> maybeError = kj::none) override;\n\n    kj::Maybe<kj::Promise<void>> tryPumpTo(WritableStreamSink& sink, bool end) override;\n\n    jsg::Promise<ReadResult> read(jsg::Lock& js) override;\n\n   private:\n    ReadableStreamInternalController& inner;\n  };\n\n  kj::Maybe<ReadableStream&> owner;\n\n  // State machine for ReadableStreamInternalController:\n  // Closed is terminal, Errored is implicitly terminal via ErrorState.\n  // Readable is the active state (stream has data).\n  using State = StateMachine<TerminalStates<StreamStates::Closed>,\n      ErrorState<StreamStates::Errored>,\n      ActiveState<Readable>,\n      StreamStates::Closed,\n      StreamStates::Errored,\n      Readable>;\n  State state;\n\n  // Lock state machine for ReadableStreamInternalController:\n  // All states can transition to any other state (no terminal states).\n  //   Unlocked -> Locked (removeSink() or pumpTo() called)\n  //   Unlocked -> ReaderLocked (lockReader() called)\n  //   Unlocked -> PipeLocked (tryPipeLock() called)\n  //   ReaderLocked -> Unlocked (releaseReader() called)\n  //   PipeLocked -> Unlocked (release() or doClose/doError called)\n  //   Locked -> (remains until stream is done)\n  using ReadLockState = StateMachine<Unlocked, Locked, PipeLocked, ReaderLocked>;\n  ReadLockState readState = ReadLockState::create<Unlocked>();\n  bool disturbed = false;\n  bool readPending = false;\n\n  // Used by Sockets code to signal to the ReadableStream that it should error when read from\n  // because the socket is currently being closed.\n  bool isPendingClosure = false;\n\n  friend class ReadableStream;\n  friend class WritableStreamInternalController;\n  friend class PipeLocked;\n};\n\nclass WritableStreamInternalController: public WritableStreamController {\n public:\n  struct Writable {\n    kj::Own<WritableStreamSink> sink;\n    kj::Canceler canceler;\n    Writable(kj::Own<WritableStreamSink> sink): sink(kj::mv(sink)) {}\n    void abort(kj::Exception&& ex);\n  };\n\n  explicit WritableStreamInternalController(StreamStates::Closed closed)\n      : state(State::create<StreamStates::Closed>()) {}\n  explicit WritableStreamInternalController(StreamStates::Errored errored)\n      : state(State::create<StreamStates::Errored>(kj::mv(errored))) {}\n  explicit WritableStreamInternalController(kj::Own<WritableStreamSink> writable,\n      kj::Maybe<kj::Own<ByteStreamObserver>> observer,\n      kj::Maybe<uint64_t> maybeHighWaterMark = kj::none,\n      kj::Maybe<jsg::Promise<void>> maybeClosureWaitable = kj::none)\n      : state(State::create<IoOwn<Writable>>(\n            IoContext::current().addObject(kj::heap<Writable>(kj::mv(writable))))),\n        observer(kj::mv(observer)),\n        maybeHighWaterMark(maybeHighWaterMark),\n        maybeClosureWaitable(kj::mv(maybeClosureWaitable)) {}\n\n  WritableStreamInternalController(WritableStreamInternalController&& other) = default;\n  WritableStreamInternalController& operator=(WritableStreamInternalController&& other) = default;\n\n  ~WritableStreamInternalController() noexcept(false) override;\n\n  void setOwnerRef(WritableStream& stream) override {\n    owner = stream;\n  }\n\n  jsg::Ref<WritableStream> addRef() override;\n\n  jsg::Promise<void> write(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> value) override;\n\n  jsg::Promise<void> close(jsg::Lock& js, bool markAsHandled = false) override;\n\n  jsg::Promise<void> flush(jsg::Lock& js, bool markAsHandled = false) override;\n\n  jsg::Promise<void> abort(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> reason) override;\n\n  kj::Maybe<jsg::Promise<void>> tryPipeFrom(\n      jsg::Lock& js, jsg::Ref<ReadableStream> source, PipeToOptions options) override;\n\n  kj::Maybe<kj::Own<WritableStreamSink>> removeSink(jsg::Lock& js) override;\n  void detach(jsg::Lock& js) override;\n\n  kj::Maybe<int> getDesiredSize() override;\n\n  bool isLockedToWriter() const override {\n    return !writeState.is<Unlocked>();\n  }\n\n  bool lockWriter(jsg::Lock& js, Writer& writer) override;\n\n  void releaseWriter(Writer& writer, kj::Maybe<jsg::Lock&> maybeJs) override;\n  // See the comment for releaseWriter in common.h for details on the use of maybeJs\n\n  kj::Maybe<v8::Local<v8::Value>> isErroring(jsg::Lock& js) override {\n    // TODO(later): The internal controller has no concept of an \"erroring\"\n    // state, so for now we just return kj::none here.\n    return kj::none;\n  }\n\n  void visitForGc(jsg::GcVisitor& visitor) override;\n\n  void setHighWaterMark(uint64_t highWaterMark);\n\n  bool isClosedOrClosing() override;\n  bool isPiping();\n  bool isErrored() override;\n\n  inline bool isByteOriented() const override {\n    return true;\n  }\n\n  void setPendingClosure() override {\n    isPendingClosure = true;\n  }\n\n  kj::StringPtr jsgGetMemoryName() const override;\n  size_t jsgGetMemorySelfSize() const override;\n  void jsgGetMemoryInfo(jsg::MemoryTracker& info) const override;\n\n private:\n  struct AbortOptions {\n    bool reject = false;\n    bool handled = false;\n  };\n\n  jsg::Promise<void> doAbort(jsg::Lock& js,\n      v8::Local<v8::Value> reason,\n      AbortOptions options = {.reject = false, .handled = false});\n  void doClose(jsg::Lock& js);\n  void doError(jsg::Lock& js, v8::Local<v8::Value> reason);\n  void ensureWriting(jsg::Lock& js);\n  jsg::Promise<void> writeLoop(jsg::Lock& js, IoContext& ioContext);\n  jsg::Promise<void> writeLoopAfterFrontOutputLock(jsg::Lock& js);\n\n  void drain(jsg::Lock& js, v8::Local<v8::Value> reason);\n  void finishClose(jsg::Lock& js);\n  void finishError(jsg::Lock& js, v8::Local<v8::Value> reason);\n  jsg::Promise<void> closeImpl(jsg::Lock& js, bool markAsHandled);\n\n  struct PipeLocked {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"pipe-locked\"_kj;\n    ReadableStream& ref;\n  };\n\n  kj::Maybe<WritableStream&> owner;\n\n  // State machine for WritableStreamInternalController:\n  // Closed is terminal, Errored is implicitly terminal via ErrorState.\n  // IoOwn<Writable> is the active state (stream is writable).\n  using State = StateMachine<TerminalStates<StreamStates::Closed>,\n      ErrorState<StreamStates::Errored>,\n      ActiveState<IoOwn<Writable>>,\n      StreamStates::Closed,\n      StreamStates::Errored,\n      IoOwn<Writable>>;\n  State state;\n\n  // Lock state machine for WritableStreamInternalController:\n  // All states can transition to any other state (no terminal states).\n  //   Unlocked -> Locked (removeSink() or detach() called)\n  //   Unlocked -> WriterLocked (lockWriter() called)\n  //   Unlocked -> PipeLocked (tryPipeFrom() called)\n  //   WriterLocked -> Unlocked (releaseWriter() called)\n  //   WriterLocked -> Locked (doClose/doError called - stream closed but writer still attached)\n  //   PipeLocked -> Unlocked (pipe completes)\n  using WriteLockState = StateMachine<Unlocked, Locked, PipeLocked, WriterLocked>;\n  WriteLockState writeState = WriteLockState::create<Unlocked>();\n\n  kj::Maybe<kj::Own<ByteStreamObserver>> observer;\n\n  kj::Maybe<kj::Own<PendingAbort>> maybePendingAbort;\n\n  uint64_t currentWriteBufferSize = 0;\n\n  // The highWaterMark is the total amount of data currently buffered in\n  // the controller waiting to be flushed out to the underlying WritableStreamSink.\n  // It is used to implement backpressure signaling using desiredSize and the ready\n  // promise on the writer.\n  kj::Maybe<uint64_t> maybeHighWaterMark;\n\n  // Used by Sockets code to ensure the connection is established before the associated\n  // WritableStream is closed.\n  kj::Maybe<jsg::Promise<void>> maybeClosureWaitable;\n  bool waitingOnClosureWritableAlready = false;\n\n  // Used by Sockets code to signal to the WritableStream that it should error when written to\n  // because the socket is currently being closed.\n  bool isPendingClosure = false;\n\n  void adjustWriteBufferSize(jsg::Lock& js, int64_t amount);\n  void updateBackpressure(jsg::Lock& js, bool backpressure);\n\n  struct Write {\n    kj::Maybe<jsg::Promise<void>::Resolver> promise;\n    size_t totalBytes;\n    kj::Array<kj::byte> ownBytes;\n    kj::ArrayPtr<const kj::byte> bytes;\n\n    JSG_MEMORY_INFO(Write) {\n      tracker.trackField(\"resolver\", promise);\n      if (ownBytes != nullptr) {\n        tracker.trackFieldWithSize(\"backing\", totalBytes);\n      }\n    }\n  };\n  struct Close {\n    kj::Maybe<jsg::Promise<void>::Resolver> promise;\n    JSG_MEMORY_INFO(Close) {\n      tracker.trackField(\"promise\", promise);\n    }\n  };\n  struct Flush {\n    kj::Maybe<jsg::Promise<void>::Resolver> promise;\n    JSG_MEMORY_INFO(Flush) {\n      tracker.trackField(\"promise\", promise);\n    }\n  };\n  struct Pipe {\n    // PipeState is ref-counted so that it can be safely captured by lambdas in pipeLoop().\n    // When drain() destroys the Pipe, the state survives as long as pending callbacks need it.\n    // The `aborted` flag is set when the Pipe is destroyed.\n    struct State: public kj::Refcounted {\n      WritableStreamInternalController& parent;\n      ReadableStreamController::PipeController& source;\n      kj::Maybe<jsg::Promise<void>::Resolver> promise;\n      kj::Maybe<jsg::Ref<AbortSignal>> maybeSignal;\n\n      bool preventAbort;\n      bool preventClose;\n      bool preventCancel;\n\n      // True when the Pipe is being destroyed\n      bool aborted = false;\n\n      State(WritableStreamInternalController& parent,\n          ReadableStreamController::PipeController& source,\n          kj::Maybe<jsg::Promise<void>::Resolver> promise,\n          bool preventAbort,\n          bool preventClose,\n          bool preventCancel,\n          kj::Maybe<jsg::Ref<AbortSignal>> maybeSignal)\n          : parent(parent),\n            source(source),\n            promise(kj::mv(promise)),\n            maybeSignal(kj::mv(maybeSignal)),\n            preventAbort(preventAbort),\n            preventClose(preventClose),\n            preventCancel(preventCancel) {}\n\n      bool checkSignal(jsg::Lock& js);\n      jsg::Promise<void> pipeLoop(jsg::Lock& js);\n      jsg::Promise<void> write(v8::Local<v8::Value> value);\n\n      JSG_MEMORY_INFO(State) {\n        tracker.trackField(\"resolver\", promise);\n        tracker.trackField(\"signal\", maybeSignal);\n      }\n    };\n\n    kj::Own<State> state;\n\n    Pipe(WritableStreamInternalController& parent,\n        ReadableStreamController::PipeController& source,\n        kj::Maybe<jsg::Promise<void>::Resolver> promise,\n        bool preventAbort,\n        bool preventClose,\n        bool preventCancel,\n        kj::Maybe<jsg::Ref<AbortSignal>> maybeSignal)\n        : state(kj::refcounted<State>(parent,\n              source,\n              kj::mv(promise),\n              preventAbort,\n              preventClose,\n              preventCancel,\n              kj::mv(maybeSignal))) {}\n\n    ~Pipe() noexcept(false) {\n      state->aborted = true;\n    }\n\n    WritableStreamInternalController& parent() {\n      return state->parent;\n    }\n    ReadableStreamController::PipeController& source() {\n      return state->source;\n    }\n    kj::Maybe<jsg::Promise<void>::Resolver>& promise() {\n      return state->promise;\n    }\n    bool preventAbort() const {\n      return state->preventAbort;\n    }\n    bool preventClose() const {\n      return state->preventClose;\n    }\n    bool preventCancel() const {\n      return state->preventCancel;\n    }\n    kj::Maybe<jsg::Ref<AbortSignal>>& maybeSignal() {\n      return state->maybeSignal;\n    }\n\n    bool checkSignal(jsg::Lock& js) {\n      return state->checkSignal(js);\n    }\n    jsg::Promise<void> pipeLoop(jsg::Lock& js) {\n      return state->pipeLoop(js);\n    }\n    jsg::Promise<void> write(v8::Local<v8::Value> value) {\n      return state->write(value);\n    }\n\n    JSG_MEMORY_INFO(Pipe) {\n      tracker.trackField(\"state\", state);\n    }\n  };\n  struct WriteEvent {\n    kj::Maybe<IoOwn<kj::Promise<void>>> outputLock;  // must wait for this before actually writing\n    kj::OneOf<kj::Own<Write>, kj::Own<Pipe>, kj::Own<Close>, kj::Own<Flush>> event;\n\n    JSG_MEMORY_INFO(WriteEvent) {\n      if (outputLock != kj::none) {\n        tracker.trackFieldWithSize(\"outputLock\", sizeof(IoOwn<kj::Promise<void>>));\n      }\n      KJ_SWITCH_ONEOF(event) {\n        KJ_CASE_ONEOF(w, kj::Own<Write>) {\n          tracker.trackField(\"inner\", w);\n        }\n        KJ_CASE_ONEOF(p, kj::Own<Pipe>) {\n          tracker.trackField(\"inner\", p);\n        }\n        KJ_CASE_ONEOF(c, kj::Own<Close>) {\n          tracker.trackField(\"inner\", c);\n        }\n        KJ_CASE_ONEOF(f, kj::Own<Flush>) {\n          tracker.trackField(\"inner\", f);\n        }\n      }\n    }\n  };\n\n  RingBuffer<WriteEvent, 8> queue;\n};\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/queue-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"queue.h\"\n\n#include <workerd/jsg/jsg-test.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/tests/test-fixture.h>\n\nnamespace workerd::api {\nnamespace {\n\nvoid preamble(auto callback) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) { callback(env.js); });\n}\n\nusing ReadContinuation = jsg::Promise<ReadResult>(ReadResult&&);\nusing CloseContinuation = jsg::Promise<void>(ReadResult&&);\nusing ReadErrorContinuation = jsg::Promise<ReadResult>(jsg::Value&&);\n\nconst kj::MutexGuarded<kj::UnwindDetector> unwindDetectorMutex;\n\ntemplate <typename Signature>\nstruct MustCall;\n// Used to create a jsg::Promise continuation function that must be called\n// at least once during the test. If the function is not called, an error\n// will be thrown causing the test to fail.\n// TODO(cleanup): Consider adding this to jsg-test.h\n\ntemplate <typename Signature>\nstruct MustNotCall;\n// Used to create a jsg::Promise continuation function that must not be called\n// during the test. If the function is called, an error will be thrown causing\n// the test to fail.\n// TODO(cleanup): Consider adding this to jsg-test.h\n\ntemplate <typename Ret, typename... Args>\nstruct MustCall<Ret(Args...)> {\n  using Func = jsg::Function<Ret(Args...)>;\n  Func fn;\n  uint expected;\n  kj::SourceLocation location;\n  uint called = false;\n\n  MustCall(Func fn, uint expected = 1, kj::SourceLocation location = kj::SourceLocation())\n      : fn(kj::mv(fn)),\n        expected(expected),\n        location(location) {}\n\n  ~MustCall() {\n    auto unwindDetector = unwindDetectorMutex.lockExclusive();\n    if (!unwindDetector->isUnwinding()) {\n      KJ_ASSERT(called == expected,\n          kj::str(\"MustCall function was not called \", expected, \" times. [actual: \", called, \"]\"),\n          location);\n    }\n  }\n\n  Ret operator()(jsg::Lock& js, Args&&... args) {\n    called++;\n    return fn(js, kj::fwd<Args...>(args...));\n  }\n};\n\ntemplate <typename Ret, typename... Args>\nstruct MustNotCall<Ret(Args...)> {\n  MustNotCall(kj::SourceLocation location = kj::SourceLocation()): location(location) {}\n  kj::SourceLocation location;\n  Ret operator()(jsg::Lock&, Args... args) {\n    KJ_FAIL_REQUIRE(\"MustNotCall function was called!\", location);\n  }\n};\n\nauto read(jsg::Lock& js, auto& consumer) {\n  auto prp = js.newPromiseAndResolver<ReadResult>();\n  consumer.read(js, ValueQueue::ReadRequest{.resolver = kj::mv(prp.resolver)});\n  return kj::mv(prp.promise);\n}\n\nauto byobRead(jsg::Lock& js, auto& consumer, int size) {\n  auto prp = js.newPromiseAndResolver<ReadResult>();\n  consumer.read(js,\n      ByteQueue::ReadRequest(kj::mv(prp.resolver),\n          {\n            .store = jsg::BufferSource(js, jsg::BackingStore::alloc(js, size)),\n            .type = ByteQueue::ReadRequest::Type::BYOB,\n          }));\n  return kj::mv(prp.promise);\n};\n\nauto getEntry(jsg::Lock& js, auto size) {\n  return kj::rc<ValueQueue::Entry>(js.v8Ref(v8::True(js.v8Isolate).As<v8::Value>()), size);\n}\n\n#pragma region ValueQueue Tests\n\nKJ_TEST(\"ValueQueue basics work\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(2);\n\n    // At this point, there are no consumers, data does not get enqueued.\n    KJ_ASSERT(queue.desiredSize() == 2);\n    KJ_ASSERT(queue.size() == 0);\n\n    queue.push(js, getEntry(js, 1));\n\n    // Because there are no consumers, there is no change to backpressure.\n    KJ_ASSERT(queue.desiredSize() == 2);\n    KJ_ASSERT(queue.size() == 0);\n\n    // Closing the queue causes the desiredSize to be zero.\n    queue.close(js);\n\n    try {\n      queue.push(js, getEntry(js, 1));\n      KJ_FAIL_ASSERT(\"The queue push after close should have failed.\");\n    } catch (kj::Exception& ex) {\n      KJ_ASSERT(ex.getDescription().endsWith(\"The queue is closed or errored.\"));\n    }\n\n    KJ_ASSERT(queue.desiredSize() == 0);\n    KJ_ASSERT(queue.size() == 0);\n  });\n}\n\nKJ_TEST(\"ValueQueue erroring works\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(2);\n\n    queue.error(js, js.v8Ref(js.v8Error(\"boom\"_kj)));\n\n    KJ_ASSERT(queue.desiredSize() == 0);\n\n    try {\n      queue.push(js, getEntry(js, 1));\n      KJ_FAIL_ASSERT(\"The queue push after close should have failed.\");\n    } catch (kj::Exception& ex) {\n      KJ_ASSERT(ex.getDescription().endsWith(\"The queue is closed or errored.\"));\n    }\n  });\n}\n\nKJ_TEST(\"ValueQueue with single consumer\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(2);\n\n    ValueQueue::Consumer consumer(queue);\n\n    KJ_ASSERT(queue.desiredSize() == 2);\n\n    queue.push(js, getEntry(js, 2));\n\n    // The item was pushed into the consumer.\n    KJ_ASSERT(consumer.size() == 2);\n\n    // The queue size and desiredSize were updated accordingly.\n    KJ_ASSERT(queue.size() == 2);\n    KJ_ASSERT(queue.desiredSize() == 0);\n\n    auto prp = js.newPromiseAndResolver<ReadResult>();\n    consumer.read(js, ValueQueue::ReadRequest{.resolver = kj::mv(prp.resolver)});\n\n    MustCall<ReadContinuation> readContinuation([&](jsg::Lock& js, auto&& result) -> auto {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      KJ_ASSERT(value.getHandle(js)->IsTrue());\n\n      KJ_ASSERT(consumer.size() == 0);\n      KJ_ASSERT(queue.size() == 0);\n      KJ_ASSERT(queue.desiredSize() == 2);\n\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    prp.promise.then(js, readContinuation);\n\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ValueQueue with multiple consumers\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(2);\n\n    ValueQueue::Consumer consumer1(queue);\n    ValueQueue::Consumer consumer2(queue);\n\n    KJ_ASSERT(queue.desiredSize() == 2);\n\n    queue.push(js, getEntry(js, 2));\n\n    // The item was pushed into the consumer.\n    KJ_ASSERT(consumer1.size() == 2);\n    KJ_ASSERT(consumer2.size() == 2);\n\n    // The queue size and desiredSize were updated accordingly.\n    KJ_ASSERT(queue.size() == 2);\n    KJ_ASSERT(queue.desiredSize() == 0);\n\n    MustCall<ReadContinuation> read1Continuation([&](jsg::Lock& js, auto&& result) -> auto {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      KJ_ASSERT(value.getHandle(js)->IsTrue());\n\n      KJ_ASSERT(consumer1.size() == 0);\n      KJ_ASSERT(consumer2.size() == 2);\n\n      // Backpressure was not relieved since the other consumer has yet to read.\n      KJ_ASSERT(queue.size() == 2);\n      KJ_ASSERT(queue.desiredSize() == 0);\n\n      return read(js, consumer2);\n    });\n\n    MustCall<ReadContinuation> read2Continuation([&](jsg::Lock& js, auto&& result) -> auto {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      KJ_ASSERT(value.getHandle(js)->IsTrue());\n\n      KJ_ASSERT(consumer2.size() == 0);\n\n      // Backpressure was relieved since both consumers have now read.\n      KJ_ASSERT(queue.size() == 0);\n      KJ_ASSERT(queue.desiredSize() == 2);\n\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    MustCall<ReadContinuation> close1Continuation([&](jsg::Lock& js, auto&& result) {\n      KJ_ASSERT(result.done);\n      return read(js, consumer2);\n    });\n\n    MustCall<CloseContinuation> close2Continuation([&](jsg::Lock& js, auto&& result) {\n      KJ_ASSERT(result.done);\n      return js.resolvedPromise();\n    });\n\n    read(js, consumer1).then(js, read1Continuation).then(js, read2Continuation);\n\n    js.runMicrotasks();\n\n    // Closing the queue causes both consumers to be closed...\n    queue.close(js);\n\n    // After close, the consumers will still be usable, but the queue itself\n    // has shutdown and no longer reports backpressure.\n    KJ_ASSERT(queue.desiredSize() == 0);\n    KJ_ASSERT(queue.size() == 0);\n\n    read(js, consumer1).then(js, close1Continuation).then(js, close2Continuation);\n\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ValueQueue consumer with multiple-reads\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(2);\n    ValueQueue::Consumer consumer(queue);\n\n    // The first read will produce a value.\n    MustCall<ReadContinuation> read1Continuation([&](jsg::Lock& js, auto&& result) -> auto {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      KJ_ASSERT(value.getHandle(js)->IsTrue());\n      return js.resolvedPromise(kj::mv(result));\n    });\n    read(js, consumer).then(js, read1Continuation);\n\n    // The second and third reads will both be done = true\n    MustCall<CloseContinuation> closeContinuation([&](jsg::Lock& js, auto&& result) {\n      KJ_ASSERT(result.done);\n      return js.resolvedPromise();\n    }, 2);\n\n    read(js, consumer).then(js, closeContinuation);\n    read(js, consumer).then(js, closeContinuation);\n\n    queue.push(js, getEntry(js, 2));\n\n    // Because there is a consumer reading when the push happens, no backpressure\n    // is applied...\n    KJ_ASSERT(queue.desiredSize() == 2);\n    KJ_ASSERT(queue.size() == 0);\n\n    queue.close(js);\n\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ValueQueue errors consumer with multiple-reads\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(2);\n    ValueQueue::Consumer consumer(queue);\n\n    MustCall<ReadErrorContinuation> errorContinuation([&](jsg::Lock& js, auto&& value) {\n      KJ_ASSERT(value.getHandle(js)->IsNativeError());\n      return js.rejectedPromise<ReadResult>(kj::mv(value));\n    }, 3);\n    MustNotCall<ReadContinuation> readContinuation;\n\n    read(js, consumer).then(js, readContinuation, errorContinuation);\n    read(js, consumer).then(js, readContinuation, errorContinuation);\n    read(js, consumer).then(js, readContinuation, errorContinuation);\n\n    queue.error(js, js.v8Ref(js.v8Error(\"boom\"_kj)));\n\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ValueQueue with multiple consumers with pending reads\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(2);\n\n    ValueQueue::Consumer consumer1(queue);\n    ValueQueue::Consumer consumer2(queue);\n\n    KJ_ASSERT(queue.desiredSize() == 2);\n\n    MustCall<ReadContinuation> readContinuation([&](jsg::Lock& js, auto&& result) -> auto {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      KJ_ASSERT(value.getHandle(js)->IsTrue());\n\n      // Both reads were fulfilled immediately without buffering.\n      KJ_ASSERT(consumer1.size() == 0);\n      KJ_ASSERT(consumer2.size() == 0);\n\n      // Backpressure is not signalled since both consumer reads have been\n      // fulfilled.\n      KJ_ASSERT(queue.size() == 0);\n      KJ_ASSERT(queue.desiredSize() == 2);\n\n      return js.resolvedPromise(kj::mv(result));\n    }, 2);\n\n    read(js, consumer1).then(js, readContinuation);\n    read(js, consumer2).then(js, readContinuation);\n\n    queue.push(js, getEntry(js, 2));\n\n    js.runMicrotasks();\n  });\n}\n\n#pragma endregion ValueQueue Tests\n\n#pragma region ByteQueue Tests\n\nKJ_TEST(\"ByteQueue basics work\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(2);\n\n    // At this point, there are no consumers, data does not get enqueued.\n    KJ_ASSERT(queue.desiredSize() == 2);\n    KJ_ASSERT(queue.size() == 0);\n\n    auto entry = kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, jsg::BackingStore::alloc(js, 4)));\n\n    queue.push(js, kj::mv(entry));\n\n    // Because there are no consumers, there is no change to backpressure.\n    KJ_ASSERT(queue.desiredSize() == 2);\n    KJ_ASSERT(queue.size() == 0);\n\n    // Closing the queue causes the desiredSize to be zero.\n    queue.close(js);\n\n    try {\n      auto entry = kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, jsg::BackingStore::alloc(js, 4)));\n      queue.push(js, kj::mv(entry));\n      KJ_FAIL_ASSERT(\"The queue push after close should have failed.\");\n    } catch (kj::Exception& ex) {\n      KJ_ASSERT(ex.getDescription().endsWith(\"The queue is closed or errored.\"));\n    }\n\n    KJ_ASSERT(queue.desiredSize() == 0);\n    KJ_ASSERT(queue.size() == 0);\n  });\n}\n\nKJ_TEST(\"ByteQueue erroring works\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(2);\n\n    queue.error(js, js.v8Ref(js.v8Error(\"boom\"_kj)));\n\n    KJ_ASSERT(queue.desiredSize() == 0);\n\n    try {\n      auto entry = kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, jsg::BackingStore::alloc(js, 4)));\n      queue.push(js, kj::mv(entry));\n      KJ_FAIL_ASSERT(\"The queue push after close should have failed.\");\n    } catch (kj::Exception& ex) {\n      KJ_ASSERT(ex.getDescription().endsWith(\"The queue is closed or errored.\"));\n    }\n  });\n}\n\nKJ_TEST(\"ByteQueue with single consumer\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(2);\n\n    ByteQueue::Consumer consumer(queue);\n\n    KJ_ASSERT(queue.desiredSize() == 2);\n\n    auto store = jsg::BackingStore::alloc(js, 4);\n    store.asArrayPtr().fill('a');\n\n    auto entry = kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, kj::mv(store)));\n    queue.push(js, kj::mv(entry));\n\n    // The item was pushed into the consumer.\n    KJ_ASSERT(consumer.size() == 4);\n\n    // The queue size and desiredSize were updated accordingly.\n    KJ_ASSERT(queue.size() == 4);\n    KJ_ASSERT(queue.desiredSize() == -2);\n\n    auto prp = js.newPromiseAndResolver<ReadResult>();\n    consumer.read(js,\n        ByteQueue::ReadRequest(kj::mv(prp.resolver),\n            {\n              .store = jsg::BufferSource(js, jsg::BackingStore::alloc(js, 4)),\n            }));\n\n    MustCall<ReadContinuation> readContinuation([&](jsg::Lock& js, auto&& result) -> auto {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      KJ_ASSERT(value.getHandle(js)->IsArrayBufferView());\n      jsg::BufferSource source(js, value.getHandle(js));\n      KJ_ASSERT(source.size() == 4);\n      KJ_ASSERT(source.asArrayPtr()[0] == 'a');\n      KJ_ASSERT(source.asArrayPtr()[1] == 'a');\n      KJ_ASSERT(source.asArrayPtr()[2] == 'a');\n      KJ_ASSERT(source.asArrayPtr()[3] == 'a');\n\n      KJ_ASSERT(consumer.size() == 0);\n      KJ_ASSERT(queue.size() == 0);\n      KJ_ASSERT(queue.desiredSize() == 2);\n\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    prp.promise.then(js, readContinuation);\n\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ByteQueue with single byob consumer\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(2);\n\n    ByteQueue::Consumer consumer(queue);\n\n    auto prp = js.newPromiseAndResolver<ReadResult>();\n    consumer.read(js,\n        ByteQueue::ReadRequest(kj::mv(prp.resolver),\n            {\n              .store = jsg::BufferSource(js, jsg::BackingStore::alloc(js, 4)),\n              .type = ByteQueue::ReadRequest::Type::BYOB,\n            }));\n\n    MustCall<ReadContinuation> readContinuation([&](jsg::Lock& js, auto&& result) -> auto {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      KJ_ASSERT(value.getHandle(js)->IsArrayBufferView());\n      jsg::BufferSource source(js, value.getHandle(js));\n      auto ptr = source.asArrayPtr();\n      KJ_ASSERT(source.size() == 3);\n      KJ_ASSERT(ptr[0] == 'b');\n      KJ_ASSERT(ptr[1] == 'b');\n      KJ_ASSERT(ptr[2] == 'b');\n\n      KJ_ASSERT(consumer.size() == 0);\n      KJ_ASSERT(queue.size() == 0);\n      KJ_ASSERT(queue.desiredSize() == 2);\n\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    prp.promise.then(js, readContinuation);\n\n    auto pendingByob = KJ_ASSERT_NONNULL(queue.nextPendingByobReadRequest());\n\n    KJ_ASSERT(!pendingByob->isInvalidated());\n\n    auto& req = pendingByob->getRequest();\n    auto ptr = req.pullInto.store.asArrayPtr();\n    ptr.first(3).fill('b');\n    pendingByob->respond(js, 3);\n    KJ_ASSERT(pendingByob->isInvalidated());\n\n    // No backpressure is signaled.\n    KJ_ASSERT(queue.desiredSize() == 2);\n    KJ_ASSERT(queue.size() == 0);\n    KJ_ASSERT(consumer.size() == 0);\n\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ByteQueue with byob consumer and default consumer\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(2);\n\n    ByteQueue::Consumer consumer1(queue);\n    ByteQueue::Consumer consumer2(queue);\n\n    auto prp = js.newPromiseAndResolver<ReadResult>();\n    consumer1.read(js,\n        ByteQueue::ReadRequest(kj::mv(prp.resolver),\n            {\n              .store = jsg::BufferSource(js, jsg::BackingStore::alloc(js, 4)),\n              .type = ByteQueue::ReadRequest::Type::BYOB,\n            }));\n\n    MustCall<ReadContinuation> readContinuation([&](jsg::Lock& js, auto&& result) -> auto {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      KJ_ASSERT(value.getHandle(js)->IsArrayBufferView());\n      jsg::BufferSource source(js, value.getHandle(js));\n      auto ptr = source.asArrayPtr();\n      KJ_ASSERT(source.size() == 3);\n      KJ_ASSERT(ptr[0] == 'b');\n      KJ_ASSERT(ptr[1] == 'b');\n      KJ_ASSERT(ptr[2] == 'b');\n\n      KJ_ASSERT(consumer1.size() == 0);\n      KJ_ASSERT(consumer2.size() == 3);\n      KJ_ASSERT(queue.size() == 3);\n      KJ_ASSERT(queue.desiredSize() == -1);\n\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    prp.promise.then(js, readContinuation);\n\n    auto pendingByob = KJ_ASSERT_NONNULL(queue.nextPendingByobReadRequest());\n\n    KJ_ASSERT(!pendingByob->isInvalidated());\n\n    auto& req = pendingByob->getRequest();\n    auto ptr = req.pullInto.store.asArrayPtr();\n    ptr.first(3).fill('b');\n    pendingByob->respond(js, 3);\n    KJ_ASSERT(pendingByob->isInvalidated());\n\n    // Backpressure is signaled because the other consumer hasn't been read from.\n    KJ_ASSERT(queue.desiredSize() == -1);\n    KJ_ASSERT(queue.size() == 3);\n    KJ_ASSERT(consumer1.size() == 0);\n    KJ_ASSERT(consumer2.size() == 3);\n\n    js.runMicrotasks();\n\n    MustCall<ReadContinuation> read2Continuation([&](jsg::Lock& js, auto&& result) -> auto {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      KJ_ASSERT(value.getHandle(js)->IsArrayBufferView());\n      jsg::BufferSource source(js, value.getHandle(js));\n      auto ptr = source.asArrayPtr();\n      // The second consumer receives exactly the same data.\n      KJ_ASSERT(source.size() == 3);\n      KJ_ASSERT(ptr[0] == 'b');\n      KJ_ASSERT(ptr[1] == 'b');\n      KJ_ASSERT(ptr[2] == 'b');\n\n      // The backpressure in the queue has been resolved.\n      KJ_ASSERT(queue.size() == 0);\n      KJ_ASSERT(queue.desiredSize() == 2);\n\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    auto prp2 = js.newPromiseAndResolver<ReadResult>();\n    consumer2.read(js,\n        ByteQueue::ReadRequest(kj::mv(prp2.resolver),\n            {\n              .store = jsg::BufferSource(js, jsg::BackingStore::alloc(js, 4)),\n              .type = ByteQueue::ReadRequest::Type::DEFAULT,\n            }));\n    prp2.promise.then(js, read2Continuation);\n\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ByteQueue with multiple byob consumers\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(2);\n\n    ByteQueue::Consumer consumer1(queue);\n    ByteQueue::Consumer consumer2(queue);\n\n    MustCall<ReadContinuation> readContinuation([&](jsg::Lock& js, auto&& result) -> auto {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      KJ_ASSERT(value.getHandle(js)->IsArrayBufferView());\n      jsg::BufferSource source(js, value.getHandle(js));\n      auto ptr = source.asArrayPtr();\n      KJ_ASSERT(source.size() == 3);\n      KJ_ASSERT(ptr[0] == 'b');\n      KJ_ASSERT(ptr[1] == 'b');\n      KJ_ASSERT(ptr[2] == 'b');\n\n      KJ_ASSERT(consumer1.size() == 0);\n      KJ_ASSERT(consumer2.size() == 0);\n      KJ_ASSERT(queue.size() == 0);\n      KJ_ASSERT(queue.desiredSize() == 2);\n\n      return js.resolvedPromise(kj::mv(result));\n    }, 2);\n\n    // Both reads will receive the data despite there being only a single\n    // byob read responded to.\n    byobRead(js, consumer1, 4).then(js, readContinuation);\n    byobRead(js, consumer2, 4).then(js, readContinuation);\n\n    auto pendingByob = KJ_ASSERT_NONNULL(queue.nextPendingByobReadRequest());\n    auto nextPending = KJ_ASSERT_NONNULL(queue.nextPendingByobReadRequest());\n\n    KJ_ASSERT(!pendingByob->isInvalidated());\n\n    auto& req = pendingByob->getRequest();\n    auto ptr = req.pullInto.store.asArrayPtr();\n    ptr.first(3).fill('b');\n    pendingByob->respond(js, 3);\n    KJ_ASSERT(pendingByob->isInvalidated());\n\n    // No backpressure is signaled because both reads were fulfilled.\n    KJ_ASSERT(queue.desiredSize() == 2);\n    KJ_ASSERT(queue.size() == 0);\n    KJ_ASSERT(consumer1.size() == 0);\n    KJ_ASSERT(consumer2.size() == 0);\n\n    // The next pendingByobReadRequest was invalidated.\n    KJ_ASSERT(nextPending->isInvalidated());\n    KJ_ASSERT(queue.nextPendingByobReadRequest() == kj::none);\n\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ByteQueue with multiple byob consumers\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(2);\n\n    ByteQueue::Consumer consumer1(queue);\n    ByteQueue::Consumer consumer2(queue);\n\n    MustCall<ReadContinuation> readContinuation([&](jsg::Lock& js, auto&& result) -> auto {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      KJ_ASSERT(value.getHandle(js)->IsArrayBufferView());\n      jsg::BufferSource source(js, value.getHandle(js));\n      auto ptr = source.asArrayPtr();\n      KJ_ASSERT(source.size() == 3);\n      KJ_ASSERT(ptr[0] == 'b');\n      KJ_ASSERT(ptr[1] == 'b');\n      KJ_ASSERT(ptr[2] == 'b');\n\n      KJ_ASSERT(consumer1.size() == 0);\n      KJ_ASSERT(consumer2.size() == 0);\n      KJ_ASSERT(queue.size() == 0);\n      KJ_ASSERT(queue.desiredSize() == 2);\n\n      return js.resolvedPromise(kj::mv(result));\n    }, 2);\n\n    // Both reads will receive the data despite there being only a single\n    // byob read responded to.\n    byobRead(js, consumer1, 4).then(js, readContinuation);\n    byobRead(js, consumer2, 4).then(js, readContinuation);\n\n    auto pendingByob = KJ_ASSERT_NONNULL(queue.nextPendingByobReadRequest());\n    auto nextPending = KJ_ASSERT_NONNULL(queue.nextPendingByobReadRequest());\n\n    KJ_ASSERT(!pendingByob->isInvalidated());\n\n    auto& req = pendingByob->getRequest();\n    auto ptr = req.pullInto.store.asArrayPtr();\n    ptr.first(3).fill('b');\n    pendingByob->respond(js, 3);\n    KJ_ASSERT(pendingByob->isInvalidated());\n\n    // No backpressure is signaled because both reads were fulfilled.\n    KJ_ASSERT(queue.desiredSize() == 2);\n    KJ_ASSERT(queue.size() == 0);\n    KJ_ASSERT(consumer1.size() == 0);\n    KJ_ASSERT(consumer2.size() == 0);\n\n    // The next pendingByobReadRequest was invalidated.\n    KJ_ASSERT(nextPending->isInvalidated());\n    KJ_ASSERT(queue.nextPendingByobReadRequest() == kj::none);\n\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ByteQueue with multiple byob consumers (multi-reads)\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(2);\n\n    ByteQueue::Consumer consumer1(queue);\n    ByteQueue::Consumer consumer2(queue);\n\n    MustCall<ReadContinuation> readConsumer1([&](jsg::Lock& js, auto&& result) -> auto {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      KJ_ASSERT(value.getHandle(js)->IsArrayBufferView());\n      jsg::BufferSource source(js, value.getHandle(js));\n      auto ptr = source.asArrayPtr();\n      KJ_ASSERT(source.size() == 3);\n      KJ_ASSERT(ptr[0] == 'a');\n      KJ_ASSERT(ptr[1] == 'a');\n      KJ_ASSERT(ptr[2] == 'a');\n\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    MustCall<ReadContinuation> readConsumer2([&](jsg::Lock& js, auto&& result) -> auto {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      KJ_ASSERT(value.getHandle(js)->IsArrayBufferView());\n      jsg::BufferSource source(js, value.getHandle(js));\n      auto ptr = source.asArrayPtr();\n      KJ_ASSERT(source.size() == 3);\n      KJ_ASSERT(ptr[0] == 'a');\n      KJ_ASSERT(ptr[1] == 'a');\n      KJ_ASSERT(ptr[2] == 'a');\n\n      return byobRead(js, consumer2, 4);\n    });\n\n    MustCall<ReadContinuation> secondReadBothConsumers([&](jsg::Lock& js, auto&& result) -> auto {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      KJ_ASSERT(value.getHandle(js)->IsArrayBufferView());\n      jsg::BufferSource source(js, value.getHandle(js));\n      auto ptr = source.asArrayPtr();\n      KJ_ASSERT(source.size() == 2);\n      KJ_ASSERT(ptr[0] == 'b');\n      KJ_ASSERT(ptr[1] == 'b');\n\n      return js.resolvedPromise(kj::mv(result));\n    }, 2);\n\n    // All reads will be fulfilled correctly even tho there are only two byob\n    // reads processed.\n    byobRead(js, consumer1, 4).then(js, readConsumer1);\n    byobRead(js, consumer1, 4).then(js, secondReadBothConsumers);\n    byobRead(js, consumer2, 4).then(js, readConsumer2).then(js, secondReadBothConsumers);\n\n    // Although there are four distinct reads happening,\n    // there should only be two actual BYOB requests\n    // processed by the queue, which will fulfill all four\n    // reads.\n    MustCall<void(ByteQueue::ByobRequest&)> respond([&](jsg::Lock&, auto& pending) {\n      static uint counter = 0;\n      auto& req = pending.getRequest();\n      auto ptr = req.pullInto.store.asArrayPtr();\n      auto num = 3 - counter;\n      ptr.first(num).fill('a' + counter++);\n      pending.respond(js, num);\n      KJ_ASSERT(pending.isInvalidated());\n    }, 2);\n\n    kj::Maybe<kj::Own<ByteQueue::ByobRequest>> pendingByob;\n    while ((pendingByob = queue.nextPendingByobReadRequest()) != kj::none) {\n      auto& pending = KJ_ASSERT_NONNULL(pendingByob);\n      if (pending->isInvalidated()) {\n        continue;\n      }\n      respond(js, *pending);\n    }\n\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ByteQueue with multiple byob consumers (multi-reads, 2)\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(2);\n\n    ByteQueue::Consumer consumer1(queue);\n    ByteQueue::Consumer consumer2(queue);\n\n    MustCall<ReadContinuation> readConsumer1([&](jsg::Lock& js, auto&& result) -> auto {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      KJ_ASSERT(value.getHandle(js)->IsArrayBufferView());\n      jsg::BufferSource source(js, value.getHandle(js));\n      auto ptr = source.asArrayPtr();\n      KJ_ASSERT(source.size() == 3);\n      KJ_ASSERT(ptr[0] == 'a');\n      KJ_ASSERT(ptr[1] == 'a');\n      KJ_ASSERT(ptr[2] == 'a');\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    MustCall<ReadContinuation> readConsumer2([&](jsg::Lock& js, auto&& result) -> auto {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      KJ_ASSERT(value.getHandle(js)->IsArrayBufferView());\n      jsg::BufferSource source(js, value.getHandle(js));\n      auto ptr = source.asArrayPtr();\n      KJ_ASSERT(source.size() == 3);\n      KJ_ASSERT(ptr[0] == 'a');\n      KJ_ASSERT(ptr[1] == 'a');\n      KJ_ASSERT(ptr[2] == 'a');\n\n      return byobRead(js, consumer2, 4);\n    });\n\n    MustCall<ReadContinuation> secondReadBothConsumers([&](jsg::Lock& js, auto&& result) -> auto {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      KJ_ASSERT(value.getHandle(js)->IsArrayBufferView());\n      jsg::BufferSource source(js, value.getHandle(js));\n      auto ptr = source.asArrayPtr();\n      KJ_ASSERT(source.size() == 2);\n      KJ_ASSERT(ptr[0] == 'b');\n      KJ_ASSERT(ptr[1] == 'b');\n\n      return js.resolvedPromise(kj::mv(result));\n    }, 2);\n\n    // All reads will be fulfilled correctly even tho there are only two BYOB reads\n    // responded to.\n    byobRead(js, consumer2, 4).then(js, readConsumer2).then(js, secondReadBothConsumers);\n    byobRead(js, consumer1, 4).then(js, readConsumer1);\n    byobRead(js, consumer1, 4).then(js, secondReadBothConsumers);\n\n    // Although there are four distinct reads happening,\n    // there should only be two actual BYOB requests\n    // processed by the queue, which will fulfill all four\n    // reads.\n    MustCall<void(ByteQueue::ByobRequest&)> respond([&](jsg::Lock&, auto& pending) {\n      static uint counter = 0;\n      auto& req = pending.getRequest();\n      auto ptr = req.pullInto.store.asArrayPtr();\n      auto num = 3 - counter;\n      ptr.first(num).fill('a' + counter++);\n      pending.respond(js, num);\n      KJ_ASSERT(pending.isInvalidated());\n    }, 2);\n\n    kj::Maybe<kj::Own<ByteQueue::ByobRequest>> pendingByob;\n    while ((pendingByob = queue.nextPendingByobReadRequest()) != kj::none) {\n      auto& pending = KJ_ASSERT_NONNULL(pendingByob);\n      if (pending->isInvalidated()) {\n        continue;\n      }\n      respond(js, *pending);\n    }\n\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ByteQueue with default consumer with atLeast\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(2);\n\n    ByteQueue::Consumer consumer(queue);\n\n    const auto read = [&](jsg::Lock& js, uint atLeast) {\n      auto prp = js.newPromiseAndResolver<ReadResult>();\n      consumer.read(js,\n          ByteQueue::ReadRequest(kj::mv(prp.resolver),\n              {\n                .store = jsg::BufferSource(js, jsg::BackingStore::alloc(js, 5)),\n                .atLeast = atLeast,\n              }));\n      return kj::mv(prp.promise);\n    };\n\n    const auto push = [&](auto store) {\n      try {\n        queue.push(js, kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, kj::mv(store))));\n      } catch (kj::Exception& ex) {\n        KJ_DBG(ex.getDescription());\n      }\n    };\n\n    MustCall<ReadContinuation> readContinuation([&](jsg::Lock& js, auto&& result) {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      auto view = value.getHandle(js);\n      KJ_ASSERT(view->IsArrayBufferView());\n      jsg::BufferSource source(js, view);\n      auto ptr = source.asArrayPtr();\n      KJ_ASSERT(ptr[0] == 1);\n      KJ_ASSERT(ptr[1] == 2);\n      KJ_ASSERT(ptr[2] == 3);\n      KJ_ASSERT(ptr[3] == 4);\n      KJ_ASSERT(ptr[4] == 5);\n      KJ_ASSERT(source.size(), 5);\n      KJ_ASSERT(consumer.size(), 1);\n      return read(js, 1);\n    });\n\n    MustCall<ReadContinuation> read2Continuation([&](jsg::Lock& js, auto&& result) {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      auto view = value.getHandle(js);\n      KJ_ASSERT(view->IsArrayBufferView());\n      jsg::BufferSource source(js, view);\n      KJ_ASSERT(source.asArrayPtr()[0], 6);\n      KJ_ASSERT(source.size() == 1);\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    read(js, 5).then(js, readContinuation).then(js, read2Continuation);\n\n    auto store1 = jsg::BackingStore::alloc(js, 2);\n    store1.asArrayPtr()[0] = 1;\n    store1.asArrayPtr()[1] = 2;\n    push(kj::mv(store1));\n\n    KJ_ASSERT(queue.desiredSize() == 0);\n\n    auto store2 = jsg::BackingStore::alloc(js, 2);\n    store2.asArrayPtr()[0] = 3;\n    store2.asArrayPtr()[1] = 4;\n    push(kj::mv(store2));\n\n    // Backpressure should be accumulating because the read has not yet fullilled.\n    KJ_ASSERT(queue.desiredSize() == -2);\n\n    auto store3 = jsg::BackingStore::alloc(js, 2);\n    store3.asArrayPtr()[0] = 5;\n    store3.asArrayPtr()[1] = 6;\n    push(kj::mv(store3));\n\n    // Some backpressure should be released because pushing the final minimum\n    // amount into the queue should have caused the read to be fulfilled.\n    KJ_ASSERT(queue.desiredSize() == 1);\n\n    // There should be one unread byte left in the queue at this point.\n    // It will be read once the microtask queue is drained.\n    KJ_ASSERT(queue.size() == 1);\n\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ByteQueue with multiple default consumers with atLeast (same rate)\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(2);\n\n    ByteQueue::Consumer consumer1(queue);\n    ByteQueue::Consumer consumer2(queue);\n\n    const auto read = [&](jsg::Lock& js, auto& consumer, uint atLeast = 1) {\n      auto prp = js.newPromiseAndResolver<ReadResult>();\n      consumer.read(js,\n          ByteQueue::ReadRequest(kj::mv(prp.resolver),\n              {\n                .store = jsg::BufferSource(js, jsg::BackingStore::alloc(js, 5)),\n                .atLeast = atLeast,\n              }));\n      return kj::mv(prp.promise);\n    };\n\n    const auto push = [&](auto store) {\n      try {\n        queue.push(js, kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, kj::mv(store))));\n      } catch (kj::Exception& ex) {\n        KJ_DBG(ex.getDescription());\n      }\n    };\n\n    MustCall<ReadContinuation> read1Continuation([&](jsg::Lock& js, auto&& result) {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      auto view = value.getHandle(js);\n      KJ_ASSERT(view->IsArrayBufferView());\n      jsg::BufferSource source(js, view);\n      auto ptr = source.asArrayPtr();\n      KJ_ASSERT(ptr[0] == 1);\n      KJ_ASSERT(ptr[1] == 2);\n      KJ_ASSERT(ptr[2] == 3);\n      KJ_ASSERT(ptr[3] == 4);\n      KJ_ASSERT(ptr[4] == 5);\n      KJ_ASSERT(source.size(), 5);\n      KJ_ASSERT(consumer1.size(), 1);\n      return read(js, consumer1);\n    });\n\n    MustCall<ReadContinuation> read2Continuation([&](jsg::Lock& js, auto&& result) {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      auto view = value.getHandle(js);\n      KJ_ASSERT(view->IsArrayBufferView());\n      jsg::BufferSource source(js, view);\n      auto ptr = source.asArrayPtr();\n      KJ_ASSERT(ptr[0] == 1);\n      KJ_ASSERT(ptr[1] == 2);\n      KJ_ASSERT(ptr[2] == 3);\n      KJ_ASSERT(ptr[3] == 4);\n      KJ_ASSERT(ptr[4] == 5);\n      KJ_ASSERT(source.size(), 5);\n      KJ_ASSERT(consumer2.size(), 1);\n      return read(js, consumer2);\n    });\n\n    MustCall<ReadContinuation> readFinalContinuation([&](jsg::Lock& js, auto&& result) {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      auto view = value.getHandle(js);\n      KJ_ASSERT(view->IsArrayBufferView());\n      jsg::BufferSource source(js, view);\n      KJ_ASSERT(source.asArrayPtr()[0], 6);\n      KJ_ASSERT(source.size() == 1);\n      return js.resolvedPromise(kj::mv(result));\n    }, 2);\n\n    read(js, consumer1, 5).then(js, read1Continuation).then(js, readFinalContinuation);\n    read(js, consumer2, 5).then(js, read2Continuation).then(js, readFinalContinuation);\n\n    auto store1 = jsg::BackingStore::alloc(js, 2);\n    store1.asArrayPtr()[0] = 1;\n    store1.asArrayPtr()[1] = 2;\n    push(kj::mv(store1));\n\n    KJ_ASSERT(queue.desiredSize() == 0);\n\n    auto store2 = jsg::BackingStore::alloc(js, 2);\n    store2.asArrayPtr()[0] = 3;\n    store2.asArrayPtr()[1] = 4;\n    push(kj::mv(store2));\n\n    // Backpressure should be accumulating because the read has not yet fullilled.\n    KJ_ASSERT(queue.desiredSize() == -2);\n\n    auto store3 = jsg::BackingStore::alloc(js, 2);\n    store3.asArrayPtr()[0] = 5;\n    store3.asArrayPtr()[1] = 6;\n    push(kj::mv(store3));\n\n    // Some backpressure should be released because pushing the final minimum\n    // amount into the queue should have caused the read to be fulfilled.\n    KJ_ASSERT(queue.desiredSize() == 1);\n\n    // There should be one unread byte left in the queue at this point.\n    // It will be read once the microtask queue is drained.\n    KJ_ASSERT(queue.size() == 1);\n\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ByteQueue with multiple default consumers with atLeast (different rate)\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(2);\n\n    ByteQueue::Consumer consumer1(queue);\n    ByteQueue::Consumer consumer2(queue);\n\n    const auto read = [&](jsg::Lock& js, auto& consumer, uint atLeast = 1) {\n      auto prp = js.newPromiseAndResolver<ReadResult>();\n      consumer.read(js,\n          ByteQueue::ReadRequest(kj::mv(prp.resolver),\n              {\n                .store = jsg::BufferSource(js, jsg::BackingStore::alloc(js, 5)),\n                .atLeast = atLeast,\n              }));\n      return kj::mv(prp.promise);\n    };\n\n    const auto push = [&](auto store) {\n      try {\n        queue.push(js, kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, kj::mv(store))));\n      } catch (kj::Exception& ex) {\n        KJ_DBG(ex.getDescription());\n      }\n    };\n\n    MustCall<ReadContinuation> read1Continuation([&](jsg::Lock& js, auto&& result) {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      auto view = value.getHandle(js);\n      KJ_ASSERT(view->IsArrayBufferView());\n      jsg::BufferSource source(js, view);\n      KJ_ASSERT(source.size() == 4);\n      auto ptr = source.asArrayPtr();\n      // Our read was for at least 3 bytes, with a maximum of 5.\n      // For this first read, we received 4. One the second read\n      // we should receive 2.\n      KJ_ASSERT(ptr[0] == 1);\n      KJ_ASSERT(ptr[1] == 2);\n      KJ_ASSERT(ptr[2] == 3);\n      KJ_ASSERT(ptr[3] == 4);\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    MustCall<ReadContinuation> read1FinalContinuation([&](jsg::Lock& js, auto&& result) {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      auto view = value.getHandle(js);\n      KJ_ASSERT(view->IsArrayBufferView());\n      jsg::BufferSource source(js, view);\n      KJ_ASSERT(source.size() == 2);\n      auto ptr = source.asArrayPtr();\n      KJ_ASSERT(ptr[0] == 5);\n      KJ_ASSERT(ptr[1] == 6);\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    MustCall<ReadContinuation> read2Continuation([&](jsg::Lock& js, auto&& result) {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      auto view = value.getHandle(js);\n      KJ_ASSERT(view->IsArrayBufferView());\n      jsg::BufferSource source(js, view);\n      auto ptr = source.asArrayPtr();\n      KJ_ASSERT(source.size() == 5);\n      KJ_ASSERT(ptr[0] == 1);\n      KJ_ASSERT(ptr[1] == 2);\n      KJ_ASSERT(ptr[2] == 3);\n      KJ_ASSERT(ptr[3] == 4);\n      KJ_ASSERT(ptr[4] == 5);\n      KJ_ASSERT(consumer2.size() == 1);\n      return read(js, consumer2);\n    });\n\n    MustCall<ReadContinuation> read2FinalContinuation([&](jsg::Lock& js, auto&& result) {\n      KJ_ASSERT(!result.done);\n      auto& value = KJ_ASSERT_NONNULL(result.value);\n      auto view = value.getHandle(js);\n      KJ_ASSERT(view->IsArrayBufferView());\n      jsg::BufferSource source(js, view);\n      KJ_ASSERT(source.asArrayPtr()[0] == 6);\n      KJ_ASSERT(source.size() == 1);\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    // Consumer 1 will read in parallel with smaller minimum chunks...\n    read(js, consumer1, 3).then(js, read1Continuation);\n    read(js, consumer1).then(js, read1FinalContinuation);\n\n    // Consumer 2 will read serially with a larger minimum chunk...\n    read(js, consumer2, 5).then(js, read2Continuation).then(js, read2FinalContinuation);\n\n    auto store1 = jsg::BackingStore::alloc(js, 2);\n    store1.asArrayPtr()[0] = 1;\n    store1.asArrayPtr()[1] = 2;\n    push(kj::mv(store1));\n\n    KJ_ASSERT(queue.desiredSize() == 0);\n\n    auto store2 = jsg::BackingStore::alloc(js, 2);\n    store2.asArrayPtr()[0] = 3;\n    store2.asArrayPtr()[1] = 4;\n    push(kj::mv(store2));\n\n    // Consumer1 should not have any data buffered since its first read was for\n    // between 3 and 5 bytes and it has received four so far.\n    KJ_ASSERT(consumer1.size() == 0);\n\n    // Consumer2 should have 4 bytes buffered since its first read was for 5 bytes\n    // and we've only received 4 so far.\n    KJ_ASSERT(consumer2.size() == 4);\n\n    // Queue backpressure should reflect that consumer2 has data buffered.\n    KJ_ASSERT(queue.desiredSize() == -2);\n\n    auto store3 = jsg::BackingStore::alloc(js, 2);\n    store3.asArrayPtr()[0] = 5;\n    store3.asArrayPtr()[1] = 6;\n    push(kj::mv(store3));\n\n    // Most of the backpressure should have been resolved since we delivered 5 bytes\n    // to consumer2, but there's still one byte remaining.\n    KJ_ASSERT(queue.desiredSize() = 1);\n    KJ_ASSERT(queue.size() == 1);\n\n    js.runMicrotasks();\n  });\n}\n\n#pragma endregion ByteQueue Tests\n\n#pragma region Re-entrancy Safety Tests\n\n// Test that pushing to a closed consumer doesn't crash.\n// This can happen during QueueImpl::push() iteration when resolving a read\n// on one consumer triggers JavaScript that closes another consumer.\nKJ_TEST(\"ValueQueue push to closed consumer is safe\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(2);\n    ValueQueue::Consumer consumer1(queue);\n    ValueQueue::Consumer consumer2(queue);\n\n    // Close consumer2\n    consumer2.close(js);\n\n    // Now push to the queue - this pushes to all consumers\n    // Before the fix, this would crash when trying to push to closed consumer2\n    queue.push(js, getEntry(js, 4));\n\n    // consumer1 should have received the data\n    KJ_ASSERT(consumer1.size() == 4);\n\n    js.runMicrotasks();\n  });\n}\n\n// Test that pushing to a cancelled consumer doesn't crash.\nKJ_TEST(\"ValueQueue push to cancelled consumer is safe\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(2);\n    ValueQueue::Consumer consumer1(queue);\n    ValueQueue::Consumer consumer2(queue);\n\n    // Cancel consumer2\n    consumer2.cancel(js, kj::none);\n\n    // Now push to the queue\n    queue.push(js, getEntry(js, 4));\n\n    // consumer1 should have received the data\n    KJ_ASSERT(consumer1.size() == 4);\n\n    js.runMicrotasks();\n  });\n}\n\n// Test that pushing to an errored consumer doesn't crash.\nKJ_TEST(\"ValueQueue push to errored consumer is safe\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(2);\n    ValueQueue::Consumer consumer1(queue);\n    ValueQueue::Consumer consumer2(queue);\n\n    // Error consumer2\n    consumer2.error(js, js.v8Ref(js.v8Error(\"error reason\"_kj)));\n\n    // Now push to the queue\n    queue.push(js, getEntry(js, 4));\n\n    // consumer1 should have received the data\n    KJ_ASSERT(consumer1.size() == 4);\n\n    js.runMicrotasks();\n  });\n}\n\n// Test ByteQueue version of the safety checks\nKJ_TEST(\"ByteQueue push to closed consumer is safe\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(10);\n    ByteQueue::Consumer consumer1(queue);\n    ByteQueue::Consumer consumer2(queue);\n\n    // Close consumer2\n    consumer2.close(js);\n\n    // Now push to the queue\n    auto store = jsg::BackingStore::alloc(js, 4);\n    memset(store.asArrayPtr().begin(), 'A', 4);\n    auto entry = kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, kj::mv(store)));\n    queue.push(js, kj::mv(entry));\n\n    // consumer1 should have received the data\n    KJ_ASSERT(consumer1.size() == 4);\n\n    js.runMicrotasks();\n  });\n}\n\n#pragma endregion Re - entrancy Safety Tests\n\n#pragma region Draining Read Tests\n\nusing DrainingReadContinuation = jsg::Promise<DrainingReadResult>(DrainingReadResult&&);\nusing DrainingReadErrorContinuation = jsg::Promise<DrainingReadResult>(jsg::Value&&);\n\nKJ_TEST(\"ValueQueue draining read with buffered data\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(10);\n    ValueQueue::Consumer consumer(queue);\n\n    // Push an ArrayBuffer\n    auto store = jsg::BackingStore::alloc(js, 4);\n    store.asArrayPtr()[0] = 'a';\n    store.asArrayPtr()[1] = 'b';\n    store.asArrayPtr()[2] = 'c';\n    store.asArrayPtr()[3] = 'd';\n    auto ab = jsg::BufferSource(js, kj::mv(store)).getHandle(js);\n    queue.push(js, kj::rc<ValueQueue::Entry>(js.v8Ref(ab.As<v8::Value>()), 4));\n\n    // Push a string\n    auto str = jsg::v8Str(js.v8Isolate, \"hello\");\n    queue.push(js, kj::rc<ValueQueue::Entry>(js.v8Ref(str.As<v8::Value>()), 5));\n\n    KJ_ASSERT(consumer.size() == 9);\n\n    MustCall<DrainingReadContinuation> readContinuation([&](jsg::Lock& js, auto&& result) {\n      KJ_ASSERT(!result.done);\n      KJ_ASSERT(result.chunks.size() == 2);\n\n      // First chunk is the ArrayBuffer data\n      KJ_ASSERT(result.chunks[0].size() == 4);\n      KJ_ASSERT(result.chunks[0][0] == 'a');\n      KJ_ASSERT(result.chunks[0][1] == 'b');\n      KJ_ASSERT(result.chunks[0][2] == 'c');\n      KJ_ASSERT(result.chunks[0][3] == 'd');\n\n      // Second chunk is the string converted to UTF-8\n      KJ_ASSERT(result.chunks[1].size() == 5);\n      KJ_ASSERT(result.chunks[1][0] == 'h');\n      KJ_ASSERT(result.chunks[1][1] == 'e');\n      KJ_ASSERT(result.chunks[1][2] == 'l');\n      KJ_ASSERT(result.chunks[1][3] == 'l');\n      KJ_ASSERT(result.chunks[1][4] == 'o');\n\n      KJ_ASSERT(consumer.size() == 0);\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    consumer.drainingRead(js).then(js, readContinuation);\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ValueQueue draining read rejects with pending reads\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(10);\n    ValueQueue::Consumer consumer(queue);\n\n    // Queue a regular read\n    auto prp = js.newPromiseAndResolver<ReadResult>();\n    consumer.read(js, ValueQueue::ReadRequest{.resolver = kj::mv(prp.resolver)});\n\n    KJ_ASSERT(consumer.hasReadRequests());\n\n    // Draining read should reject because there are pending reads\n    MustNotCall<DrainingReadContinuation> readContinuation;\n    MustCall<DrainingReadErrorContinuation> errorContinuation([&](jsg::Lock& js, auto&& value) {\n      KJ_ASSERT(value.getHandle(js)->IsNativeError());\n      return js.rejectedPromise<DrainingReadResult>(kj::mv(value));\n    });\n\n    consumer.drainingRead(js).then(js, readContinuation, errorContinuation);\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ValueQueue read rejects with pending draining read\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(10);\n    ValueQueue::Consumer consumer(queue);\n\n    // No data in buffer, draining read will queue a pending draining read\n    consumer.drainingRead(js);\n\n    KJ_ASSERT(consumer.hasPendingDrainingRead());\n\n    // Regular read should reject because there's a pending draining read\n    auto prp = js.newPromiseAndResolver<ReadResult>();\n\n    MustNotCall<ReadContinuation> readContinuation;\n    MustCall<ReadErrorContinuation> errorContinuation([&](jsg::Lock& js, auto&& value) {\n      KJ_ASSERT(value.getHandle(js)->IsNativeError());\n      return js.rejectedPromise<ReadResult>(kj::mv(value));\n    });\n\n    consumer.read(js, ValueQueue::ReadRequest{.resolver = kj::mv(prp.resolver)});\n    prp.promise.then(js, readContinuation, errorContinuation);\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ValueQueue draining read on closed stream\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(10);\n    ValueQueue::Consumer consumer(queue);\n\n    queue.close(js);\n\n    MustCall<DrainingReadContinuation> readContinuation([&](jsg::Lock& js, auto&& result) {\n      KJ_ASSERT(result.done);\n      KJ_ASSERT(result.chunks.size() == 0);\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    consumer.drainingRead(js).then(js, readContinuation);\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ValueQueue draining read on errored stream\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(10);\n    ValueQueue::Consumer consumer(queue);\n\n    queue.error(js, js.v8Ref(js.v8Error(\"boom\"_kj)));\n\n    MustNotCall<DrainingReadContinuation> readContinuation;\n    MustCall<DrainingReadErrorContinuation> errorContinuation([&](jsg::Lock& js, auto&& value) {\n      KJ_ASSERT(value.getHandle(js)->IsNativeError());\n      return js.rejectedPromise<DrainingReadResult>(kj::mv(value));\n    });\n\n    consumer.drainingRead(js).then(js, readContinuation, errorContinuation);\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ByteQueue draining read with buffered data\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(10);\n    ByteQueue::Consumer consumer(queue);\n\n    // Push first chunk\n    auto store1 = jsg::BackingStore::alloc(js, 4);\n    store1.asArrayPtr()[0] = 'a';\n    store1.asArrayPtr()[1] = 'b';\n    store1.asArrayPtr()[2] = 'c';\n    store1.asArrayPtr()[3] = 'd';\n    queue.push(js, kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, kj::mv(store1))));\n\n    // Push second chunk\n    auto store2 = jsg::BackingStore::alloc(js, 3);\n    store2.asArrayPtr()[0] = 'e';\n    store2.asArrayPtr()[1] = 'f';\n    store2.asArrayPtr()[2] = 'g';\n    queue.push(js, kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, kj::mv(store2))));\n\n    KJ_ASSERT(consumer.size() == 7);\n\n    MustCall<DrainingReadContinuation> readContinuation([&](jsg::Lock& js, auto&& result) {\n      KJ_ASSERT(!result.done);\n      KJ_ASSERT(result.chunks.size() == 2);\n\n      // First chunk\n      KJ_ASSERT(result.chunks[0].size() == 4);\n      KJ_ASSERT(result.chunks[0][0] == 'a');\n      KJ_ASSERT(result.chunks[0][1] == 'b');\n      KJ_ASSERT(result.chunks[0][2] == 'c');\n      KJ_ASSERT(result.chunks[0][3] == 'd');\n\n      // Second chunk\n      KJ_ASSERT(result.chunks[1].size() == 3);\n      KJ_ASSERT(result.chunks[1][0] == 'e');\n      KJ_ASSERT(result.chunks[1][1] == 'f');\n      KJ_ASSERT(result.chunks[1][2] == 'g');\n\n      KJ_ASSERT(consumer.size() == 0);\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    consumer.drainingRead(js).then(js, readContinuation);\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ByteQueue draining read rejects with pending reads\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(10);\n    ByteQueue::Consumer consumer(queue);\n\n    // Queue a regular read\n    auto prp = js.newPromiseAndResolver<ReadResult>();\n    consumer.read(js,\n        ByteQueue::ReadRequest(kj::mv(prp.resolver),\n            {\n              .store = jsg::BufferSource(js, jsg::BackingStore::alloc(js, 4)),\n            }));\n\n    KJ_ASSERT(consumer.hasReadRequests());\n\n    // Draining read should reject because there are pending reads\n    MustNotCall<DrainingReadContinuation> readContinuation;\n    MustCall<DrainingReadErrorContinuation> errorContinuation([&](jsg::Lock& js, auto&& value) {\n      KJ_ASSERT(value.getHandle(js)->IsNativeError());\n      return js.rejectedPromise<DrainingReadResult>(kj::mv(value));\n    });\n\n    consumer.drainingRead(js).then(js, readContinuation, errorContinuation);\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ByteQueue read rejects with pending draining read\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(10);\n    ByteQueue::Consumer consumer(queue);\n\n    // No data in buffer, draining read will queue a pending draining read\n    consumer.drainingRead(js);\n\n    KJ_ASSERT(consumer.hasPendingDrainingRead());\n\n    // Regular read should reject because there's a pending draining read\n    auto prp = js.newPromiseAndResolver<ReadResult>();\n\n    MustNotCall<ReadContinuation> readContinuation;\n    MustCall<ReadErrorContinuation> errorContinuation([&](jsg::Lock& js, auto&& value) {\n      KJ_ASSERT(value.getHandle(js)->IsNativeError());\n      return js.rejectedPromise<ReadResult>(kj::mv(value));\n    });\n\n    consumer.read(js,\n        ByteQueue::ReadRequest(kj::mv(prp.resolver),\n            {\n              .store = jsg::BufferSource(js, jsg::BackingStore::alloc(js, 4)),\n            }));\n    prp.promise.then(js, readContinuation, errorContinuation);\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ByteQueue draining read on closed stream\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(10);\n    ByteQueue::Consumer consumer(queue);\n\n    queue.close(js);\n\n    MustCall<DrainingReadContinuation> readContinuation([&](jsg::Lock& js, auto&& result) {\n      KJ_ASSERT(result.done);\n      KJ_ASSERT(result.chunks.size() == 0);\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    consumer.drainingRead(js).then(js, readContinuation);\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ByteQueue draining read on errored stream\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(10);\n    ByteQueue::Consumer consumer(queue);\n\n    queue.error(js, js.v8Ref(js.v8Error(\"boom\"_kj)));\n\n    MustNotCall<DrainingReadContinuation> readContinuation;\n    MustCall<DrainingReadErrorContinuation> errorContinuation([&](jsg::Lock& js, auto&& value) {\n      KJ_ASSERT(value.getHandle(js)->IsNativeError());\n      return js.rejectedPromise<DrainingReadResult>(kj::mv(value));\n    });\n\n    consumer.drainingRead(js).then(js, readContinuation, errorContinuation);\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ValueQueue draining read with close signal\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(10);\n    ValueQueue::Consumer consumer(queue);\n\n    // Push some data\n    auto store = jsg::BackingStore::alloc(js, 4);\n    store.asArrayPtr()[0] = 'a';\n    store.asArrayPtr()[1] = 'b';\n    store.asArrayPtr()[2] = 'c';\n    store.asArrayPtr()[3] = 'd';\n    auto ab = jsg::BufferSource(js, kj::mv(store)).getHandle(js);\n    queue.push(js, kj::rc<ValueQueue::Entry>(js.v8Ref(ab.As<v8::Value>()), 4));\n\n    // Close the queue\n    queue.close(js);\n\n    MustCall<DrainingReadContinuation> readContinuation([&](jsg::Lock& js, auto&& result) {\n      // Should have the data and done should be true since stream is closed\n      KJ_ASSERT(result.done);\n      KJ_ASSERT(result.chunks.size() == 1);\n      KJ_ASSERT(result.chunks[0].size() == 4);\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    consumer.drainingRead(js).then(js, readContinuation);\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ByteQueue draining read with close signal\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(10);\n    ByteQueue::Consumer consumer(queue);\n\n    // Push some data\n    auto store = jsg::BackingStore::alloc(js, 4);\n    store.asArrayPtr()[0] = 'a';\n    store.asArrayPtr()[1] = 'b';\n    store.asArrayPtr()[2] = 'c';\n    store.asArrayPtr()[3] = 'd';\n    queue.push(js, kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, kj::mv(store))));\n\n    // Close the queue\n    queue.close(js);\n\n    MustCall<DrainingReadContinuation> readContinuation([&](jsg::Lock& js, auto&& result) {\n      // Should have the data and done should be true since stream is closed\n      KJ_ASSERT(result.done);\n      KJ_ASSERT(result.chunks.size() == 1);\n      KJ_ASSERT(result.chunks[0].size() == 4);\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    consumer.drainingRead(js).then(js, readContinuation);\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ValueQueue draining read errors on non-byte value\") {\n  // Test that drainingRead correctly errors when encountering a value that\n  // cannot be converted to bytes (not ArrayBuffer, ArrayBufferView, or string).\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(10);\n    ValueQueue::Consumer consumer(queue);\n\n    // Push a plain object - this cannot be converted to bytes\n    auto obj = v8::Object::New(js.v8Isolate);\n    queue.push(js, kj::rc<ValueQueue::Entry>(js.v8Ref(obj.As<v8::Value>()), 1));\n\n    KJ_ASSERT(consumer.size() == 1);\n\n    MustNotCall<DrainingReadContinuation> readContinuation;\n    MustCall<DrainingReadErrorContinuation> errorContinuation([&](jsg::Lock& js, auto&& value) {\n      // Should get a TypeError about non-convertible value\n      KJ_ASSERT(value.getHandle(js)->IsNativeError());\n      auto message = jsg::JsValue(value.getHandle(js))\n                         .tryCast<jsg::JsObject>()\n                         .map([&](jsg::JsObject obj) {\n        return obj.get(js, \"message\")\n            .tryCast<jsg::JsString>()\n            .map([&](jsg::JsString str) {\n          return str.toString(js);\n        }).orDefault(kj::str());\n      }).orDefault(kj::str());\n      KJ_ASSERT(message.contains(\"cannot be converted to bytes\"));\n      return js.rejectedPromise<DrainingReadResult>(kj::mv(value));\n    });\n\n    consumer.drainingRead(js).then(js, readContinuation, errorContinuation);\n    js.runMicrotasks();\n    // MustCall verifies the error continuation was called\n  });\n}\n\nKJ_TEST(\"ValueQueue draining read errors on number value\") {\n  // Another non-byte value test with a number\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(10);\n    ValueQueue::Consumer consumer(queue);\n\n    // Push a number - this cannot be converted to bytes\n    auto num = v8::Number::New(js.v8Isolate, 42);\n    queue.push(js, kj::rc<ValueQueue::Entry>(js.v8Ref(num.As<v8::Value>()), 1));\n\n    MustNotCall<DrainingReadContinuation> readContinuation;\n    MustCall<DrainingReadErrorContinuation> errorContinuation([&](jsg::Lock& js, auto&& value) {\n      KJ_ASSERT(value.getHandle(js)->IsNativeError());\n      return js.rejectedPromise<DrainingReadResult>(kj::mv(value));\n    });\n\n    consumer.drainingRead(js).then(js, readContinuation, errorContinuation);\n    js.runMicrotasks();\n    // MustCall verifies the error continuation was called\n  });\n}\n\n#pragma endregion Draining Read Tests\n\n#pragma region Draining Read maxRead Tests\n\n// Tests for the maxRead soft limit parameter. Both the initial buffer drain and subsequent\n// synchronous pump attempts stop when totalRead reaches maxRead. This prevents unbounded\n// memory accumulation when a fast producer outpaces a slow consumer.\n//\n// Note: Testing the pump loop behavior requires the full controller infrastructure (stateListener).\n// These tests focus on the buffer drain behavior which can be tested without a listener.\n\nKJ_TEST(\"ValueQueue draining read respects maxRead during buffer drain\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(10);\n    ValueQueue::Consumer consumer(queue);\n\n    // Buffer 200 bytes of data (two 100-byte chunks)\n    auto store1 = jsg::BackingStore::alloc(js, 100);\n    store1.asArrayPtr().fill(0xAA);\n    auto ab1 = jsg::BufferSource(js, kj::mv(store1)).getHandle(js);\n    queue.push(js, kj::rc<ValueQueue::Entry>(js.v8Ref(ab1.As<v8::Value>()), 100));\n\n    auto store2 = jsg::BackingStore::alloc(js, 100);\n    store2.asArrayPtr().fill(0xBB);\n    auto ab2 = jsg::BufferSource(js, kj::mv(store2)).getHandle(js);\n    queue.push(js, kj::rc<ValueQueue::Entry>(js.v8Ref(ab2.As<v8::Value>()), 100));\n\n    KJ_ASSERT(consumer.size() == 200);\n\n    // maxRead=50: the first 100-byte chunk is drained (exceeding maxRead after the first item),\n    // then draining stops because totalRead (100) >= maxRead (50). The second chunk stays buffered.\n    MustCall<DrainingReadContinuation> readContinuation(\n        [&](jsg::Lock& js, DrainingReadResult&& result) {\n      KJ_ASSERT(!result.done);\n      // Should only have the first chunk (maxRead stopped draining before the second)\n      KJ_ASSERT(result.chunks.size() == 1);\n      KJ_ASSERT(result.chunks[0].size() == 100);\n      // Second chunk should still be buffered\n      KJ_ASSERT(consumer.size() == 100);\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    consumer.drainingRead(js, 50).then(js, readContinuation);\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ByteQueue draining read respects maxRead during buffer drain\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(10);\n    ByteQueue::Consumer consumer(queue);\n\n    // Buffer 200 bytes of data (two 100-byte chunks)\n    auto store1 = jsg::BackingStore::alloc(js, 100);\n    store1.asArrayPtr().fill(0xAA);\n    queue.push(js, kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, kj::mv(store1))));\n\n    auto store2 = jsg::BackingStore::alloc(js, 100);\n    store2.asArrayPtr().fill(0xBB);\n    queue.push(js, kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, kj::mv(store2))));\n\n    KJ_ASSERT(consumer.size() == 200);\n\n    // maxRead=50: first 100-byte chunk is drained, then stops. Second chunk stays buffered.\n    MustCall<DrainingReadContinuation> readContinuation(\n        [&](jsg::Lock& js, DrainingReadResult&& result) {\n      KJ_ASSERT(!result.done);\n      KJ_ASSERT(result.chunks.size() == 1);\n      KJ_ASSERT(result.chunks[0].size() == 100);\n      KJ_ASSERT(consumer.size() == 100);\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    consumer.drainingRead(js, 50).then(js, readContinuation);\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ValueQueue draining read with large maxRead drains entire buffer\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(10);\n    ValueQueue::Consumer consumer(queue);\n\n    // Buffer 200 bytes (two 100-byte chunks)\n    auto store1 = jsg::BackingStore::alloc(js, 100);\n    store1.asArrayPtr().fill(0xAA);\n    auto ab1 = jsg::BufferSource(js, kj::mv(store1)).getHandle(js);\n    queue.push(js, kj::rc<ValueQueue::Entry>(js.v8Ref(ab1.As<v8::Value>()), 100));\n\n    auto store2 = jsg::BackingStore::alloc(js, 100);\n    store2.asArrayPtr().fill(0xBB);\n    auto ab2 = jsg::BufferSource(js, kj::mv(store2)).getHandle(js);\n    queue.push(js, kj::rc<ValueQueue::Entry>(js.v8Ref(ab2.As<v8::Value>()), 100));\n\n    KJ_ASSERT(consumer.size() == 200);\n\n    // maxRead=1000: both chunks should be drained since total (200) < maxRead (1000)\n    MustCall<DrainingReadContinuation> readContinuation(\n        [&](jsg::Lock& js, DrainingReadResult&& result) {\n      KJ_ASSERT(!result.done);\n      KJ_ASSERT(result.chunks.size() == 2);\n      KJ_ASSERT(result.chunks[0].size() == 100);\n      KJ_ASSERT(result.chunks[1].size() == 100);\n      KJ_ASSERT(consumer.size() == 0);\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    consumer.drainingRead(js, 1000).then(js, readContinuation);\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ValueQueue draining read with default maxRead (unlimited)\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(10);\n    ValueQueue::Consumer consumer(queue);\n\n    // Buffer some data\n    auto store = jsg::BackingStore::alloc(js, 100);\n    store.asArrayPtr().fill(0xAA);\n    auto ab = jsg::BufferSource(js, kj::mv(store)).getHandle(js);\n    queue.push(js, kj::rc<ValueQueue::Entry>(js.v8Ref(ab.As<v8::Value>()), 100));\n\n    // Default maxRead (kj::maxValue) should drain buffer normally\n    MustCall<DrainingReadContinuation> readContinuation(\n        [&](jsg::Lock& js, DrainingReadResult&& result) {\n      KJ_ASSERT(!result.done);\n      KJ_ASSERT(result.chunks.size() == 1);\n      KJ_ASSERT(result.chunks[0].size() == 100);\n      return js.resolvedPromise(kj::mv(result));\n    });\n\n    consumer.drainingRead(js).then(js, readContinuation);  // No maxRead argument = default\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ValueQueue draining read maxRead bounds multiple iterations\") {\n  // Verify that successive drainingRead calls with maxRead correctly drain\n  // a large buffer incrementally.\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(10);\n    ValueQueue::Consumer consumer(queue);\n\n    // Buffer 400 bytes: four 100-byte chunks\n    for (int i = 0; i < 4; i++) {\n      auto store = jsg::BackingStore::alloc(js, 100);\n      store.asArrayPtr().fill(0x10 * (i + 1));\n      auto ab = jsg::BufferSource(js, kj::mv(store)).getHandle(js);\n      queue.push(js, kj::rc<ValueQueue::Entry>(js.v8Ref(ab.As<v8::Value>()), 100));\n    }\n    KJ_ASSERT(consumer.size() == 400);\n\n    // First read with maxRead=150: drains first chunk (100 bytes, now totalRead=100 < 150),\n    // then drains second chunk (200 bytes total, now >= 150), stops.\n    MustCall<DrainingReadContinuation> read1([&](jsg::Lock& js, DrainingReadResult&& result) {\n      KJ_ASSERT(!result.done);\n      KJ_ASSERT(result.chunks.size() == 2);\n      KJ_ASSERT(consumer.size() == 200);\n      return js.resolvedPromise(kj::mv(result));\n    });\n    consumer.drainingRead(js, 150).then(js, read1);\n    js.runMicrotasks();\n\n    // Second read with maxRead=150: drains next two chunks similarly\n    MustCall<DrainingReadContinuation> read2([&](jsg::Lock& js, DrainingReadResult&& result) {\n      KJ_ASSERT(!result.done);\n      KJ_ASSERT(result.chunks.size() == 2);\n      KJ_ASSERT(consumer.size() == 0);\n      return js.resolvedPromise(kj::mv(result));\n    });\n    consumer.drainingRead(js, 150).then(js, read2);\n    js.runMicrotasks();\n  });\n}\n\n#pragma endregion Draining Read maxRead Tests\n\n#pragma region Queue/Consumer Destruction Order Tests\n\n// These tests verify that destroying the queue before its consumers doesn't crash.\n// This can happen during isolate teardown when the destruction order isn't guaranteed\n// to follow the ownership hierarchy.\n// These will typically only catch in builds with AddressSanitizer enabled.\n\nKJ_TEST(\"ValueQueue destroyed before consumer doesn't crash\") {\n  preamble([](jsg::Lock& js) {\n    // Heap-allocate the queue so we can control its destruction order\n    auto queue = kj::heap<ValueQueue>(2);\n\n    // Create a consumer attached to the queue\n    auto consumer = kj::heap<ValueQueue::Consumer>(*queue);\n\n    // Push some data to make sure the consumer has state\n    queue->push(js, getEntry(js, 4));\n    KJ_ASSERT(consumer->size() == 4);\n\n    // Now destroy the queue FIRST - this simulates the production scenario\n    // where wrapper cleanup destroys the controller (and its queue) before\n    // the consumer that holds a reference to it.\n    queue = nullptr;\n\n    // When the consumer is destroyed (here, or when going out of scope),\n    // its destructor calls queue.removeConsumer(this).\n    // Without the fix, this is a use-after-free.\n    // With the fix, the consumer knows the queue is gone and skips the call.\n    consumer = nullptr;\n\n    // If we get here without crashing, the test passes\n  });\n}\n\nKJ_TEST(\"ValueQueue destroyed before multiple consumers doesn't crash\") {\n  preamble([](jsg::Lock& js) {\n    auto queue = kj::heap<ValueQueue>(2);\n\n    auto consumer1 = kj::heap<ValueQueue::Consumer>(*queue);\n    auto consumer2 = kj::heap<ValueQueue::Consumer>(*queue);\n\n    queue->push(js, getEntry(js, 4));\n    KJ_ASSERT(consumer1->size() == 4);\n    KJ_ASSERT(consumer2->size() == 4);\n\n    // Destroy queue before consumers\n    queue = nullptr;\n\n    // Both consumers should handle destruction gracefully\n    consumer1 = nullptr;\n    consumer2 = nullptr;\n  });\n}\n\nKJ_TEST(\"ByteQueue destroyed before consumer doesn't crash\") {\n  preamble([](jsg::Lock& js) {\n    auto queue = kj::heap<ByteQueue>(2);\n    auto consumer = kj::heap<ByteQueue::Consumer>(*queue);\n\n    auto store = jsg::BackingStore::alloc(js, 4);\n    store.asArrayPtr().fill('a');\n    queue->push(js, kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, kj::mv(store))));\n    KJ_ASSERT(consumer->size() == 4);\n\n    // Destroy queue before consumer\n    queue = nullptr;\n    consumer = nullptr;\n  });\n}\n\nKJ_TEST(\"ValueQueue destroyed with pending read requests doesn't crash\") {\n  preamble([](jsg::Lock& js) {\n    auto queue = kj::heap<ValueQueue>(2);\n    auto consumer = kj::heap<ValueQueue::Consumer>(*queue);\n\n    // Queue a read request (no data pushed, so it will be pending)\n    auto prp = js.newPromiseAndResolver<ReadResult>();\n    consumer->read(js, ValueQueue::ReadRequest{.resolver = kj::mv(prp.resolver)});\n\n    KJ_ASSERT(consumer->hasReadRequests());\n\n    // Destroy queue while there are pending reads\n    queue = nullptr;\n\n    // Consumer destruction should handle this gracefully\n    consumer = nullptr;\n\n    js.runMicrotasks();\n  });\n}\n\nKJ_TEST(\"ValueQueue close then destroy before consumer doesn't crash\") {\n  preamble([](jsg::Lock& js) {\n    auto queue = kj::heap<ValueQueue>(2);\n    auto consumer = kj::heap<ValueQueue::Consumer>(*queue);\n\n    // Close the queue first\n    queue->close(js);\n\n    // Then destroy it\n    queue = nullptr;\n\n    // Consumer should still handle destruction gracefully\n    consumer = nullptr;\n  });\n}\n\nKJ_TEST(\"ValueQueue error then destroy before consumer doesn't crash\") {\n  preamble([](jsg::Lock& js) {\n    auto queue = kj::heap<ValueQueue>(2);\n    auto consumer = kj::heap<ValueQueue::Consumer>(*queue);\n\n    // Error the queue first\n    queue->error(js, js.v8Ref(js.v8Error(\"boom\"_kj)));\n\n    // Then destroy it\n    queue = nullptr;\n\n    // Consumer should still handle destruction gracefully\n    consumer = nullptr;\n  });\n}\n\n#pragma endregion Queue / Consumer Destruction Order Tests\n\n#pragma region Consumer Destroyed During Push Tests\n// These tests verify that the queue handles consumer destruction gracefully.\n// QueueImpl::push() takes a snapshot of consumers and iterates over it. If a consumer\n// is destroyed (removed from allConsumers) between the snapshot and the iteration,\n// the code must check allConsumers.contains() before dereferencing the pointer.\n// That said, the tests do not actually fail without the relevant fix because the\n// issue is extremely timing-dependent and difficult to trigger deterministically, even\n// with asan enabled. These tests at least document the intended behavior and may catch\n// future regressions.\n\nKJ_TEST(\"ByteQueue push skips consumer removed from queue during iteration\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(10);\n\n    // Create two consumers\n    auto consumer1 = kj::heap<ByteQueue::Consumer>(queue);\n    auto consumer2 = kj::heap<ByteQueue::Consumer>(queue);\n\n    // Destroy consumer2 BEFORE pushing. This directly tests that push()\n    // checks if consumers still exist before calling push on them.\n    // The snapshot taken at the start of push() would have included consumer2,\n    // but consumer2 is no longer in allConsumers when we iterate.\n    consumer2 = nullptr;\n\n    // Push data - should not crash even though consumer2 was in the queue\n    // when it was created but is now destroyed.\n    auto store = jsg::BackingStore::alloc(js, 4);\n    store.asArrayPtr().fill('x');\n    queue.push(js, kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, kj::mv(store))));\n\n    // consumer1 should have received the data\n    KJ_ASSERT(consumer1->size() == 4);\n  });\n}\n\nKJ_TEST(\"ValueQueue push skips consumer removed from queue during iteration\") {\n  preamble([](jsg::Lock& js) {\n    ValueQueue queue(10);\n\n    auto consumer1 = kj::heap<ValueQueue::Consumer>(queue);\n    auto consumer2 = kj::heap<ValueQueue::Consumer>(queue);\n\n    // Destroy consumer2 before pushing\n    consumer2 = nullptr;\n\n    queue.push(js, getEntry(js, 4));\n\n    KJ_ASSERT(consumer1->size() == 4);\n  });\n}\n\nKJ_TEST(\"ByteQueue push handles consumer destroyed by microtask between pushes\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(10);\n\n    auto consumer1 = kj::heap<ByteQueue::Consumer>(queue);\n    auto consumer2 = kj::heap<ByteQueue::Consumer>(queue);\n\n    // Set up a pending read on consumer1\n    auto prp = js.newPromiseAndResolver<ReadResult>();\n    consumer1->read(js,\n        ByteQueue::ReadRequest(kj::mv(prp.resolver),\n            {\n              .store = jsg::BufferSource(js, jsg::BackingStore::alloc(js, 4)),\n            }));\n\n    // The continuation destroys consumer2\n    MustCall<ReadContinuation> readContinuation([&consumer2](jsg::Lock& js, ReadResult&& result) {\n      consumer2 = nullptr;\n      return js.resolvedPromise(kj::mv(result));\n    });\n    prp.promise.then(js, readContinuation);\n\n    // First push - resolves consumer1's read, schedules microtask that will destroy consumer2\n    auto store1 = jsg::BackingStore::alloc(js, 4);\n    store1.asArrayPtr().fill('x');\n    queue.push(js, kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, kj::mv(store1))));\n\n    // Run microtasks - this destroys consumer2\n    js.runMicrotasks();\n\n    // Second push - consumer2 is now destroyed, should not crash\n    auto store2 = jsg::BackingStore::alloc(js, 4);\n    store2.asArrayPtr().fill('y');\n    queue.push(js, kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, kj::mv(store2))));\n\n    // consumer1 should have the second push's data buffered\n    KJ_ASSERT(consumer1->size() == 4);\n  });\n}\n\nKJ_TEST(\"ByteQueue maybeUpdateBackpressure skips destroyed consumers\") {\n  preamble([](jsg::Lock& js) {\n    ByteQueue queue(10);\n\n    auto consumer1 = kj::heap<ByteQueue::Consumer>(queue);\n    auto consumer2 = kj::heap<ByteQueue::Consumer>(queue);\n\n    // Push some data so consumers have size\n    auto store = jsg::BackingStore::alloc(js, 4);\n    store.asArrayPtr().fill('x');\n    queue.push(js, kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, kj::mv(store))));\n\n    KJ_ASSERT(consumer1->size() == 4);\n    KJ_ASSERT(consumer2->size() == 4);\n    KJ_ASSERT(queue.size() == 4);\n\n    // Destroy consumer2\n    consumer2 = nullptr;\n\n    // Trigger backpressure recalculation by pushing more data\n    auto store2 = jsg::BackingStore::alloc(js, 4);\n    store2.asArrayPtr().fill('y');\n    queue.push(js, kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, kj::mv(store2))));\n\n    // Should not crash, and size should reflect only consumer1\n    KJ_ASSERT(consumer1->size() == 8);\n    KJ_ASSERT(queue.size() == 8);\n  });\n}\n#pragma endregion Consumer Destroyed During Push Tests\n\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/queue.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"queue.h\"\n\n#include <workerd/io/features.h>\n#include <workerd/jsg/jsg.h>\n\n#include <kj/common.h>\n\n#include <algorithm>\n\nnamespace workerd::api {\n\n// ======================================================================================\n// ValueQueue\n#pragma region ValueQueue\n\n#pragma region ValueQueue::ReadRequest\n\nvoid ValueQueue::ReadRequest::resolveAsDone(jsg::Lock& js) {\n  resolver.resolve(js, ReadResult{.done = true});\n}\n\nvoid ValueQueue::ReadRequest::resolve(jsg::Lock& js, jsg::Value value) {\n  resolver.resolve(js, ReadResult{.value = kj::mv(value), .done = false});\n}\n\nvoid ValueQueue::ReadRequest::reject(jsg::Lock& js, jsg::Value& value) {\n  resolver.reject(js, value.getHandle(js));\n}\n\n#pragma endregion ValueQueue::ReadRequest\n\n#pragma region ValueQueue::Entry\n\nValueQueue::Entry::Entry(jsg::Value value, size_t size): value(kj::mv(value)), size(size) {}\n\njsg::Value ValueQueue::Entry::getValue(jsg::Lock& js) {\n  return value.addRef(js);\n}\n\nsize_t ValueQueue::Entry::getSize() const {\n  return size;\n}\n\nvoid ValueQueue::Entry::visitForGc(jsg::GcVisitor& visitor) {\n  visitor.visit(value);\n}\n\n#pragma endregion ValueQueue::Entry\n\n#pragma region ValueQueue::QueueEntry\n\nkj::Rc<ValueQueue::Entry> ValueQueue::Entry::clone(jsg::Lock& js) {\n  return addRefToThis();\n}\n\nValueQueue::QueueEntry ValueQueue::QueueEntry::clone(jsg::Lock& js) {\n  return QueueEntry{.entry = entry->clone(js)};\n}\n\n#pragma endregion ValueQueue::QueueEntry\n\n#pragma region ValueQueue::Consumer\n\nValueQueue::Consumer::Consumer(\n    ValueQueue& queue, kj::Maybe<ConsumerImpl::StateListener&> stateListener)\n    : impl(queue.impl, stateListener) {}\n\nValueQueue::Consumer::Consumer(\n    QueueImpl& impl, kj::Maybe<ConsumerImpl::StateListener&> stateListener)\n    : impl(impl, stateListener) {}\n\nValueQueue::Consumer::Consumer(kj::Maybe<ConsumerImpl::StateListener&> stateListener)\n    : impl(stateListener) {}\n\nvoid ValueQueue::Consumer::cancel(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason) {\n  impl.cancel(js, maybeReason);\n}\n\nvoid ValueQueue::Consumer::close(jsg::Lock& js) {\n  impl.close(js);\n};\n\nbool ValueQueue::Consumer::empty() {\n  return impl.empty();\n}\n\nvoid ValueQueue::Consumer::error(jsg::Lock& js, jsg::Value reason) {\n  impl.error(js, kj::mv(reason));\n};\n\nvoid ValueQueue::Consumer::read(jsg::Lock& js, ReadRequest request) {\n  impl.read(js, kj::mv(request));\n}\n\nvoid ValueQueue::Consumer::push(jsg::Lock& js, kj::Rc<Entry> entry) {\n  impl.push(js, kj::mv(entry));\n}\n\nvoid ValueQueue::Consumer::reset() {\n  impl.reset();\n};\n\nsize_t ValueQueue::Consumer::size() {\n  return impl.size();\n}\n\nkj::Own<ValueQueue::Consumer> ValueQueue::Consumer::clone(\n    jsg::Lock& js, kj::Maybe<ConsumerImpl::StateListener&> stateListener) {\n  // If the queue was destroyed (e.g., stream was closed), we can still clone\n  // the consumer - the cloneTo() will copy the closed/errored state.\n  kj::Own<Consumer> consumer;\n  KJ_IF_SOME(q, impl.queue) {\n    consumer = kj::heap<Consumer>(q, stateListener);\n  } else {\n    consumer = kj::heap<Consumer>(stateListener);\n  }\n  impl.cloneTo(js, consumer->impl);\n  return kj::mv(consumer);\n}\n\nbool ValueQueue::Consumer::hasReadRequests() {\n  return impl.hasReadRequests();\n}\n\nbool ValueQueue::Consumer::hasPendingDrainingRead() {\n  return impl.state.whenActiveOr(\n      [](const ConsumerImpl::Ready& ready) { return ready.hasPendingDrainingRead; }, false);\n}\n\nnamespace {\n// Helper to convert a JS value to bytes. Returns kj::none if the value cannot be converted.\nkj::Maybe<kj::Array<kj::byte>> valueToBytes(jsg::Lock& js, jsg::Value& value) {\n  auto jsval = jsg::JsValue(value.getHandle(js));\n\n  // Try ArrayBuffer first.\n  KJ_IF_SOME(ab, jsval.tryCast<jsg::JsArrayBuffer>()) {\n    auto src = ab.asArrayPtr();\n    return kj::heapArray(src);\n  }\n\n  // Try ArrayBufferView.\n  KJ_IF_SOME(abView, jsval.tryCast<jsg::JsArrayBufferView>()) {\n    auto src = abView.asArrayPtr();\n    return kj::heapArray(src);\n  }\n\n  // Try string - convert to UTF-8.\n  KJ_IF_SOME(str, jsval.tryCast<jsg::JsString>()) {\n    auto data = str.toUSVString(js);\n    return kj::heapArray(data.asBytes());\n  }\n\n  // Unsupported type.\n  return kj::none;\n}\n}  // namespace\n\njsg::Promise<DrainingReadResult> ValueQueue::Consumer::drainingRead(jsg::Lock& js, size_t maxRead) {\n  // If there are pending regular reads, reject - mutual exclusion.\n  if (hasReadRequests()) {\n    return js.rejectedPromise<DrainingReadResult>(\n        js.typeError(\"Cannot call drainingRead while there are pending reads\"_kj));\n  }\n\n  // Check if already closed or errored.\n  if (impl.state.template is<ConsumerImpl::Closed>()) {\n    return js.resolvedPromise(DrainingReadResult{.chunks = nullptr, .done = true});\n  }\n  KJ_IF_SOME(errored, impl.state.tryGetErrorUnsafe()) {\n    return js.rejectedPromise<DrainingReadResult>(errored.reason.getHandle(js));\n  }\n\n  auto& ready = impl.state.requireActiveUnsafe();\n  ConsumerImpl::UpdateBackpressureScope scope(impl);\n\n  // Mark that we're doing a draining read. This allows onConsumerWantsData()\n  // to use forcePull() which bypasses backpressure checks. The flag is cleared\n  // either synchronously (for immediate returns) or in promise callbacks (for async).\n  ready.hasPendingDrainingRead = true;\n\n  // Collect all buffered data, converting values to bytes.\n  kj::Vector<kj::Array<kj::byte>> chunks;\n  bool isClosing = false;\n  size_t totalRead = 0;\n\n  // Drains buffered data, converting values to bytes. Returns a rejected promise if a value\n  // cannot be converted; otherwise returns kj::none to indicate success. Stops draining\n  // when totalRead reaches or exceeds maxRead (after finishing the current item).\n  static const auto drainBuffer =\n      [](jsg::Lock& js, ConsumerImpl& impl, ConsumerImpl::Ready& ready,\n          kj::Vector<kj::Array<kj::byte>>& chunks, size_t& totalRead, bool& isClosing,\n          size_t maxRead) -> kj::Maybe<jsg::Promise<DrainingReadResult>> {\n    while (!ready.buffer.empty() && !isClosing && totalRead < maxRead) {\n      auto& item = ready.buffer.front();\n      KJ_SWITCH_ONEOF(item) {\n        KJ_CASE_ONEOF(close, ConsumerImpl::Close) {\n          isClosing = true;\n          break;\n        }\n        KJ_CASE_ONEOF(entry, QueueEntry) {\n          auto value = entry.entry->getValue(js);\n          KJ_IF_SOME(bytes, valueToBytes(js, value)) {\n            totalRead += bytes.size();\n            chunks.add(kj::mv(bytes));\n            ready.queueTotalSize -= entry.entry->getSize();\n            ready.buffer.pop_front();\n          } else {\n            auto error = js.typeError(\n                \"Draining read encountered a value that cannot be converted to bytes\"_kj);\n            impl.error(js, jsg::Value(js.v8Isolate, error));\n            return js.rejectedPromise<DrainingReadResult>(error);\n          }\n        }\n      }\n    }\n    return kj::none;\n  };\n\n  // Drain the buffer up to maxRead bytes, then pump for more if under the limit.\n  KJ_IF_SOME(errorPromise, drainBuffer(js, impl, ready, chunks, totalRead, isClosing, maxRead)) {\n    return kj::mv(errorPromise);\n  }\n\n  // Pump the controller for more synchronously available data.\n  // maxRead is checked here: we only proceed with pumping if we haven't exceeded it.\n  KJ_IF_SOME(listener, impl.stateListener) {\n    while (!isClosing && totalRead < maxRead) {\n      size_t prevChunkCount = chunks.size();\n      bool pullCompletedSync = listener.onConsumerWantsData(js);\n\n      // The pull callback may have closed or errored the consumer, which\n      // destroys the Ready state (and its RingBuffer). We must not touch\n      // `ready` after that.\n      if (!impl.state.isActive()) break;\n\n      // Drain buffered data that was added by the pull, respecting maxRead.\n      KJ_IF_SOME(errorPromise,\n          drainBuffer(js, impl, ready, chunks, totalRead, isClosing, maxRead)) {\n        return kj::mv(errorPromise);\n      }\n\n      // If pull is async or no new data was added, stop pumping.\n      if (!pullCompletedSync || chunks.size() == prevChunkCount) {\n        break;\n      }\n    }\n  }\n\n  // If the consumer was closed or errored during pumping, the `ready`\n  // reference is dangling. Return what we have or the appropriate error.\n  if (!impl.state.isActive()) {\n    KJ_IF_SOME(errored, impl.state.tryGetErrorUnsafe()) {\n      return js.rejectedPromise<DrainingReadResult>(errored.reason.getHandle(js));\n    }\n    // Closed — all data was already drained. Return collected chunks.\n    return js.resolvedPromise(DrainingReadResult{\n      .chunks = chunks.releaseAsArray(),\n      .done = true,\n    });\n  }\n\n  // If the controller was canceled during pumping (e.g., pull callback called\n  // controller.cancel()), the QueueImpl is destroyed and the consumer's queue\n  // reference is detached (set to kj::none). The consumer state is still Active\n  // because cancel on the controller doesn't notify consumers — it only closes\n  // the controller's own state. No more data will ever arrive. Drain remaining\n  // buffer data respecting maxRead, and signal done only when the buffer is empty.\n  if (impl.queue == kj::none) {\n    // Drain remaining buffer up to maxRead. If there's still more, the caller\n    // will loop back and we'll drain the rest on subsequent calls.\n    KJ_IF_SOME(errorPromise, drainBuffer(js, impl, ready, chunks, totalRead, isClosing, maxRead)) {\n      return kj::mv(errorPromise);\n    }\n    ready.hasPendingDrainingRead = false;\n    bool done = ready.buffer.empty() || isClosing;\n    // If isClosing, finalize the consumer so onConsumerClose fires promptly.\n    // maybeDrainAndSetState may transition consumer to Closed, making `ready` dangling.\n    if (isClosing) {\n      impl.maybeDrainAndSetState(js);\n    }\n    return js.resolvedPromise(DrainingReadResult{\n      .chunks = chunks.releaseAsArray(),\n      .done = done,\n    });\n  }\n\n  // If we collected data, return it immediately.\n  if (!chunks.empty() || isClosing) {\n    ready.hasPendingDrainingRead = false;\n    // If isClosing, finalize the consumer so onConsumerClose fires promptly.\n    // maybeDrainAndSetState may transition consumer to Closed, making `ready` dangling.\n    if (isClosing) {\n      impl.maybeDrainAndSetState(js);\n    }\n    return js.resolvedPromise(DrainingReadResult{\n      .chunks = chunks.releaseAsArray(),\n      .done = isClosing,\n    });\n  }\n\n  // No data available - need to wait. Queue a pending draining read.\n  // We create a ReadResult promise and transform it to DrainingReadResult.\n  // The flag remains set (was set at the start) and will be cleared by the promise callbacks.\n  auto prp = js.newPromiseAndResolver<ReadResult>();\n\n  ReadRequest request{.resolver = kj::mv(prp.resolver)};\n  ready.readRequests.push_back(kj::heap<ReadRequest>(kj::mv(request)));\n\n  KJ_IF_SOME(listener, impl.stateListener) {\n    listener.onConsumerWantsData(js);\n  }\n\n  // Transform the ReadResult promise to DrainingReadResult.\n  return prp.promise.then(\n      js, [this](jsg::Lock& js, ReadResult result) mutable -> DrainingReadResult {\n    KJ_IF_SOME(ready, impl.state.tryGetActiveUnsafe()) {\n      ready.hasPendingDrainingRead = false;\n    }\n\n    if (result.done) {\n      return DrainingReadResult{.chunks = nullptr, .done = true};\n    }\n\n    // Convert the value to bytes.\n    kj::Vector<kj::Array<kj::byte>> chunks;\n    KJ_IF_SOME(val, result.value) {\n      KJ_IF_SOME(bytes, valueToBytes(js, val)) {\n        chunks.add(kj::mv(bytes));\n      }\n      // If valueToBytes returned kj::none, we just return empty chunks.\n      // The error case should have been caught earlier.\n    }\n\n    return DrainingReadResult{\n      .chunks = chunks.releaseAsArray(),\n      .done = false,\n    };\n  }, [this](jsg::Lock& js, jsg::Value exception) mutable -> DrainingReadResult {\n    KJ_IF_SOME(ready, impl.state.tryGetActiveUnsafe()) {\n      ready.hasPendingDrainingRead = false;\n    }\n    js.throwException(kj::mv(exception));\n  });\n}\n\nvoid ValueQueue::Consumer::cancelPendingReads(jsg::Lock& js, jsg::JsValue reason) {\n  impl.cancelPendingReads(js, reason);\n}\n\nvoid ValueQueue::Consumer::visitForGc(jsg::GcVisitor& visitor) {\n  visitor.visit(impl);\n}\n\n#pragma endregion ValueQueue::Consumer\n\nValueQueue::ValueQueue(size_t highWaterMark): impl(highWaterMark) {}\n\nvoid ValueQueue::close(jsg::Lock& js) {\n  impl.close(js);\n}\n\nssize_t ValueQueue::desiredSize() const {\n  return impl.desiredSize();\n}\n\nvoid ValueQueue::error(jsg::Lock& js, jsg::Value reason) {\n  impl.error(js, kj::mv(reason));\n}\n\nvoid ValueQueue::maybeUpdateBackpressure() {\n  impl.maybeUpdateBackpressure();\n}\n\nvoid ValueQueue::push(jsg::Lock& js, kj::Rc<Entry> entry) {\n  impl.push(js, kj::mv(entry));\n}\n\nsize_t ValueQueue::size() const {\n  return impl.size();\n}\n\nvoid ValueQueue::handlePush(\n    jsg::Lock& js, ConsumerImpl::Ready& state, kj::Maybe<QueueImpl&> queue, kj::Rc<Entry> entry) {\n  // If there are no pending reads, just add the entry to the buffer and return, adjusting\n  // the size of the queue in the process.\n  if (state.readRequests.empty()) {\n    state.queueTotalSize += entry->getSize();\n    state.buffer.push_back(QueueEntry{.entry = kj::mv(entry)});\n    return;\n  }\n\n  // Otherwise, pop the next pending read and resolve it. There should be nothing in the queue.\n  KJ_REQUIRE(state.buffer.empty() && state.queueTotalSize == 0);\n  auto request = kj::mv(state.readRequests.front());\n  state.readRequests.pop_front();\n  request->resolve(js, entry->getValue(js));\n}\n\nvoid ValueQueue::handleRead(jsg::Lock& js,\n    ConsumerImpl::Ready& state,\n    ConsumerImpl& consumer,\n    kj::Maybe<QueueImpl&> queue,\n    ReadRequest request) {\n  // If there are no pending read requests and there is data in the buffer,\n  // we will try to fulfill the read request immediately.\n  if (state.queueTotalSize > 0 && state.buffer.empty()) {\n    // Is our queue accounting correct?\n    LOG_WARNING_ONCE(\"ValueQueue::handleRead encountered a queueTotalSize > 0 \"\n                     \"with an empty buffer. This should not happen.\",\n        state.queueTotalSize);\n  }\n  if (state.readRequests.empty() && !state.buffer.empty()) {\n    auto& entry = state.buffer.front();\n\n    KJ_SWITCH_ONEOF(entry) {\n      KJ_CASE_ONEOF(c, ConsumerImpl::Close) {\n        // This case shouldn't actually happen. The queueTotalSize should be zero if the\n        // only item remaining in the queue is the close sentinel because we decrement the\n        // queueTotalSize every time we remove an item. If we get here, something is wrong.\n        // We'll handle it by resolving the read request and keep going but let's emit a log\n        // warning so we can investigate.\n        // Note that we do not want to remove the close sentinel here so that the next call to\n        // maybeDrainAndSetState will see it and handle the transition to the closed state.\n        KJ_LOG(ERROR,\n            \"ValueQueue::handleRead encountered a close sentinel in the queue \"\n            \"with queueTotalSize > 0. This should not happen.\",\n            state.queueTotalSize);\n        request.resolveAsDone(js);\n        return;\n      }\n      KJ_CASE_ONEOF(entry, QueueEntry) {\n        auto freed = kj::mv(entry);\n        state.buffer.pop_front();\n        request.resolve(js, freed.entry->getValue(js));\n        state.queueTotalSize -= freed.entry->getSize();\n        return;\n      }\n    }\n    KJ_UNREACHABLE;\n  } else if (state.queueTotalSize == 0 && consumer.isClosing()) {\n    // Otherwise, if state.queueTotalSize is zero and isClosing() is true there won't be any\n    // more data coming. Just resolve the read as done and move on.\n    request.resolveAsDone(js);\n  } else {\n    // Otherwise, push the read request into the pending readRequests. It will be\n    // resolved either as soon as there is data available or the consumer closes\n    // or errors.\n    state.readRequests.push_back(kj::heap<ReadRequest>(kj::mv(request)));\n    KJ_IF_SOME(listener, consumer.stateListener) {\n      listener.onConsumerWantsData(js);\n    }\n  }\n}\n\nbool ValueQueue::handleMaybeClose(jsg::Lock& js,\n    ConsumerImpl::Ready& state,\n    ConsumerImpl& consumer,\n    kj::Maybe<QueueImpl&> queue) {\n  // If the value queue is not yet empty we have to keep waiting for more reads to consume it.\n  // Return false to indicate that we cannot close yet.\n  return false;\n}\n\nsize_t ValueQueue::getConsumerCount() {\n  return impl.getConsumerCount();\n}\n\nbool ValueQueue::wantsRead() const {\n  return impl.wantsRead();\n}\n\nbool ValueQueue::hasPartiallyFulfilledRead() {\n  // A ValueQueue can never have a partially fulfilled read.\n  return false;\n}\n\nvoid ValueQueue::visitForGc(jsg::GcVisitor& visitor) {}\n\n#pragma endregion ValueQueue\n\n// ======================================================================================\n// ByteQueue\n#pragma region ByteQueue\n\n#pragma region ByteQueue::ReadRequest\n\nnamespace {\nvoid maybeInvalidateByobRequest(kj::Maybe<ByteQueue::ByobRequest&>& req) {\n  KJ_IF_SOME(byobRequest, req) {\n    byobRequest.invalidate();\n    // The call to byobRequest->invalidate() should have cleared the reference.\n    KJ_ASSERT(req == kj::none);\n  }\n}\n}  // namespace\n\nByteQueue::ReadRequest::ReadRequest(\n    jsg::Promise<ReadResult>::Resolver resolver, ByteQueue::ReadRequest::PullInto pullInto)\n    : resolver(kj::mv(resolver)),\n      pullInto(kj::mv(pullInto)) {}\n\nByteQueue::ReadRequest::~ReadRequest() noexcept(false) {\n  maybeInvalidateByobRequest(byobReadRequest);\n}\n\nvoid ByteQueue::ReadRequest::resolveAsDone(jsg::Lock& js) {\n  if (pullInto.filled > 0) {\n    // There's been at least some data written, we need to respond but not\n    // set done to true since that's what the streams spec requires.\n    pullInto.store.trim(js, pullInto.store.size() - pullInto.filled);\n    resolver.resolve(\n        js, ReadResult{.value = js.v8Ref(pullInto.store.getHandle(js)), .done = false});\n  } else {\n    // Otherwise, we set the length to zero\n    pullInto.store.trim(js, pullInto.store.size());\n    KJ_ASSERT(pullInto.store.size() == 0);\n    resolver.resolve(js, ReadResult{.value = js.v8Ref(pullInto.store.getHandle(js)), .done = true});\n  }\n  maybeInvalidateByobRequest(byobReadRequest);\n}\n\nvoid ByteQueue::ReadRequest::resolve(jsg::Lock& js) {\n  pullInto.store.trim(js, pullInto.store.size() - pullInto.filled);\n  resolver.resolve(js, ReadResult{.value = js.v8Ref(pullInto.store.getHandle(js)), .done = false});\n  maybeInvalidateByobRequest(byobReadRequest);\n}\n\nvoid ByteQueue::ReadRequest::reject(jsg::Lock& js, jsg::Value& value) {\n  resolver.reject(js, value.getHandle(js));\n  maybeInvalidateByobRequest(byobReadRequest);\n}\n\nkj::Own<ByteQueue::ByobRequest> ByteQueue::ReadRequest::makeByobReadRequest(\n    ConsumerImpl& consumer, QueueImpl& queue) {\n  auto req = kj::heap<ByobRequest>(*this, consumer, queue);\n  byobReadRequest = *req;\n  return kj::mv(req);\n}\n\n#pragma endregion ByteQueue::ReadRequest\n\n#pragma region ByteQueue::Entry\n\nByteQueue::Entry::Entry(jsg::BufferSource store): store(kj::mv(store)) {}\n\nkj::ArrayPtr<kj::byte> ByteQueue::Entry::toArrayPtr() {\n  return store.asArrayPtr();\n}\n\nsize_t ByteQueue::Entry::getSize() const {\n  return store.size();\n}\n\nkj::Rc<ByteQueue::Entry> ByteQueue::Entry::clone(jsg::Lock& js) {\n  return addRefToThis();\n}\n\nvoid ByteQueue::Entry::visitForGc(jsg::GcVisitor& visitor) {}\n\n#pragma endregion ByteQueue::Entry\n\n#pragma region ByteQueue::QueueEntry\n\nByteQueue::QueueEntry ByteQueue::QueueEntry::clone(jsg::Lock& js) {\n  return QueueEntry{\n    .entry = entry->clone(js),\n    .offset = offset,\n  };\n}\n\n#pragma endregion ByteQueue::QueueEntry\n\n#pragma region ByteQueue::Consumer\n\nByteQueue::Consumer::Consumer(\n    ByteQueue& queue, kj::Maybe<ConsumerImpl::StateListener&> stateListener)\n    : impl(queue.impl, stateListener) {}\n\nByteQueue::Consumer::Consumer(\n    QueueImpl& impl, kj::Maybe<ConsumerImpl::StateListener&> stateListener)\n    : impl(impl, stateListener) {}\n\nByteQueue::Consumer::Consumer(kj::Maybe<ConsumerImpl::StateListener&> stateListener)\n    : impl(stateListener) {}\n\nvoid ByteQueue::Consumer::cancel(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason) {\n  impl.cancel(js, maybeReason);\n}\n\nvoid ByteQueue::Consumer::close(jsg::Lock& js) {\n  impl.close(js);\n}\n\nbool ByteQueue::Consumer::empty() const {\n  return impl.empty();\n}\n\nvoid ByteQueue::Consumer::error(jsg::Lock& js, jsg::Value reason) {\n  impl.error(js, kj::mv(reason));\n}\n\nvoid ByteQueue::Consumer::read(jsg::Lock& js, ReadRequest request) {\n  impl.read(js, kj::mv(request));\n}\n\nvoid ByteQueue::Consumer::push(jsg::Lock& js, kj::Rc<Entry> entry) {\n  impl.push(js, kj::mv(entry));\n}\n\nvoid ByteQueue::Consumer::reset() {\n  impl.reset();\n}\n\nsize_t ByteQueue::Consumer::size() const {\n  return impl.size();\n}\n\nkj::Own<ByteQueue::Consumer> ByteQueue::Consumer::clone(\n    jsg::Lock& js, kj::Maybe<ConsumerImpl::StateListener&> stateListener) {\n  // If the queue was destroyed (e.g., stream was closed), we can still clone\n  // the consumer - the cloneTo() will copy the closed/errored state.\n  kj::Own<Consumer> consumer;\n  KJ_IF_SOME(q, impl.queue) {\n    consumer = kj::heap<Consumer>(q, stateListener);\n  } else {\n    consumer = kj::heap<Consumer>(stateListener);\n  }\n  impl.cloneTo(js, consumer->impl);\n  return kj::mv(consumer);\n}\n\nbool ByteQueue::Consumer::hasReadRequests() {\n  return impl.hasReadRequests();\n}\n\nbool ByteQueue::Consumer::hasPendingDrainingRead() {\n  return impl.state.whenActiveOr(\n      [](const ConsumerImpl::Ready& ready) { return ready.hasPendingDrainingRead; }, false);\n}\n\njsg::Promise<DrainingReadResult> ByteQueue::Consumer::drainingRead(jsg::Lock& js, size_t maxRead) {\n  // If there are pending regular reads, reject - mutual exclusion.\n  if (hasReadRequests()) {\n    return js.rejectedPromise<DrainingReadResult>(\n        js.typeError(\"Cannot call drainingRead while there are pending reads\"_kj));\n  }\n\n  // Check if already closed or errored.\n  if (impl.state.template is<ConsumerImpl::Closed>()) {\n    return js.resolvedPromise(DrainingReadResult{.chunks = nullptr, .done = true});\n  }\n  KJ_IF_SOME(errored, impl.state.tryGetErrorUnsafe()) {\n    return js.rejectedPromise<DrainingReadResult>(errored.reason.getHandle(js));\n  }\n\n  auto& ready = impl.state.requireActiveUnsafe();\n  ConsumerImpl::UpdateBackpressureScope scope(impl);\n\n  // Mark that we're doing a draining read. This allows onConsumerWantsData()\n  // to use forcePull() which bypasses backpressure checks. The flag is cleared\n  // either synchronously (for immediate returns) or in promise callbacks (for async).\n  ready.hasPendingDrainingRead = true;\n\n  // Collect all buffered data (already bytes for ByteQueue).\n  kj::Vector<kj::Array<kj::byte>> chunks;\n  bool isClosing = false;\n  size_t totalRead = 0;\n\n  // Drains buffered byte data into chunks. Stops draining when totalRead reaches\n  // or exceeds maxRead (after finishing the current item).\n  static const auto drainBuffer = [](ConsumerImpl::Ready& ready,\n                                      kj::Vector<kj::Array<kj::byte>>& chunks, size_t& totalRead,\n                                      bool& isClosing, size_t maxRead) {\n    while (!ready.buffer.empty() && !isClosing && totalRead < maxRead) {\n      auto& item = ready.buffer.front();\n      KJ_SWITCH_ONEOF(item) {\n        KJ_CASE_ONEOF(close, ConsumerImpl::Close) {\n          isClosing = true;\n          break;\n        }\n        KJ_CASE_ONEOF(entry, QueueEntry) {\n          auto ptr = entry.entry->toArrayPtr();\n          auto offset = entry.offset;\n          auto size = ptr.size() - offset;\n          totalRead += size;\n          chunks.add(kj::heapArray(ptr.slice(offset, offset + size)));\n          ready.queueTotalSize -= size;\n          ready.buffer.pop_front();\n        }\n      }\n    }\n  };\n\n  // Drain the buffer up to maxRead bytes, then pump for more if under the limit.\n  drainBuffer(ready, chunks, totalRead, isClosing, maxRead);\n\n  // Pump the controller for more synchronously available data.\n  // maxRead is checked here: we only proceed with pumping if we haven't exceeded it.\n  KJ_IF_SOME(listener, impl.stateListener) {\n    while (!isClosing && totalRead < maxRead) {\n      size_t prevChunkCount = chunks.size();\n      bool pullCompletedSync = listener.onConsumerWantsData(js);\n\n      // The pull callback may have closed or errored the consumer, which\n      // destroys the Ready state (and its RingBuffer). We must not touch\n      // `ready` after that.\n      if (!impl.state.isActive()) break;\n\n      // Drain buffered data that was added by the pull, respecting maxRead.\n      drainBuffer(ready, chunks, totalRead, isClosing, maxRead);\n\n      // If pull is async or no new data was added, stop pumping.\n      if (!pullCompletedSync || chunks.size() == prevChunkCount) {\n        break;\n      }\n    }\n  }\n\n  // If the consumer was closed or errored during pumping, the `ready`\n  // reference is dangling. Return what we have or the appropriate error.\n  if (!impl.state.isActive()) {\n    KJ_IF_SOME(errored, impl.state.tryGetErrorUnsafe()) {\n      return js.rejectedPromise<DrainingReadResult>(errored.reason.getHandle(js));\n    }\n    return js.resolvedPromise(DrainingReadResult{\n      .chunks = chunks.releaseAsArray(),\n      .done = true,\n    });\n  }\n\n  // If the controller was canceled during pumping (e.g., pull callback called\n  // controller.cancel()), the QueueImpl is destroyed and the consumer's queue\n  // reference is detached (set to kj::none). The consumer state is still Active\n  // because cancel on the controller doesn't notify consumers — it only closes\n  // the controller's own state. No more data will ever arrive. Drain remaining\n  // buffer data respecting maxRead, and signal done only when the buffer is empty.\n  if (impl.queue == kj::none) {\n    // Drain remaining buffer up to maxRead. If there's still more, the caller\n    // will loop back and we'll drain the rest on subsequent calls.\n    drainBuffer(ready, chunks, totalRead, isClosing, maxRead);\n    ready.hasPendingDrainingRead = false;\n    bool done = ready.buffer.empty() || isClosing;\n    // If isClosing, finalize the consumer so onConsumerClose fires promptly.\n    // maybeDrainAndSetState may transition consumer to Closed, making `ready` dangling.\n    if (isClosing) {\n      impl.maybeDrainAndSetState(js);\n    }\n    return js.resolvedPromise(DrainingReadResult{\n      .chunks = chunks.releaseAsArray(),\n      .done = done,\n    });\n  }\n\n  // If we collected data, return it immediately.\n  if (!chunks.empty() || isClosing) {\n    ready.hasPendingDrainingRead = false;\n    // If isClosing, finalize the consumer so onConsumerClose fires promptly.\n    // maybeDrainAndSetState may transition consumer to Closed, making `ready` dangling.\n    if (isClosing) {\n      impl.maybeDrainAndSetState(js);\n    }\n    return js.resolvedPromise(DrainingReadResult{\n      .chunks = chunks.releaseAsArray(),\n      .done = isClosing,\n    });\n  }\n\n  // No data available - need to wait. Create a default read request.\n  // We allocate a buffer for the read - the data will be copied into it.\n  // The flag remains set (was set at the start) and will be cleared by the promise callbacks.\n  constexpr size_t kDefaultReadSize = 16384;  // 16KB default buffer\n  KJ_IF_SOME(store, jsg::BufferSource::tryAllocUnsafe(js, kDefaultReadSize)) {\n    auto prp = js.newPromiseAndResolver<ReadResult>();\n\n    ReadRequest::PullInto pullInto{\n      .store = kj::mv(store),\n      .filled = 0,\n      .atLeast = 1,\n      .type = ReadRequest::Type::DEFAULT,\n    };\n    ReadRequest request(kj::mv(prp.resolver), kj::mv(pullInto));\n    ready.readRequests.push_back(kj::heap<ReadRequest>(kj::mv(request)));\n\n    KJ_IF_SOME(listener, impl.stateListener) {\n      listener.onConsumerWantsData(js);\n    }\n\n    // Transform the ReadResult promise to DrainingReadResult.\n    return prp.promise.then(\n        js, [this](jsg::Lock& js, ReadResult result) mutable -> DrainingReadResult {\n      KJ_IF_SOME(ready, impl.state.tryGetActiveUnsafe()) {\n        ready.hasPendingDrainingRead = false;\n      }\n\n      if (result.done) {\n        return DrainingReadResult{.chunks = nullptr, .done = true};\n      }\n\n      kj::Vector<kj::Array<kj::byte>> chunks;\n      KJ_IF_SOME(val, result.value) {\n        auto jsval = jsg::JsValue(val.getHandle(js));\n        KJ_IF_SOME(ab, jsval.tryCast<jsg::JsArrayBuffer>()) {\n          chunks.add(kj::heapArray(ab.asArrayPtr()));\n        } else KJ_IF_SOME(abView, jsval.tryCast<jsg::JsArrayBufferView>()) {\n          chunks.add(kj::heapArray(abView.asArrayPtr()));\n        }\n      }\n\n      return DrainingReadResult{\n        .chunks = chunks.releaseAsArray(),\n        .done = false,\n      };\n    }, [this](jsg::Lock& js, jsg::Value exception) mutable -> DrainingReadResult {\n      KJ_IF_SOME(ready, impl.state.tryGetActiveUnsafe()) {\n        ready.hasPendingDrainingRead = false;\n      }\n      js.throwException(kj::mv(exception));\n    });\n  } else {\n    return js.rejectedPromise<DrainingReadResult>(\n        js.error(\"Failed to allocate buffer for draining read\"_kj));\n  }\n}\n\nvoid ByteQueue::Consumer::cancelPendingReads(jsg::Lock& js, jsg::JsValue reason) {\n  impl.cancelPendingReads(js, reason);\n}\n\nvoid ByteQueue::Consumer::visitForGc(jsg::GcVisitor& visitor) {\n  visitor.visit(impl);\n}\n\n#pragma endregion ByteQueue::Consumer\n\n#pragma region ByteQueue::ByobRequest\n\nByteQueue::ByobRequest::~ByobRequest() noexcept(false) {\n  invalidate();\n}\n\nvoid ByteQueue::ByobRequest::invalidate() {\n  KJ_IF_SOME(req, request) {\n    req.byobReadRequest = kj::none;\n    request = kj::none;\n  }\n}\n\nbool ByteQueue::ByobRequest::isPartiallyFulfilled() {\n  return !isInvalidated() && getRequest().pullInto.filled > 0 &&\n      getRequest().pullInto.store.getElementSize() > 1;\n}\n\nbool ByteQueue::ByobRequest::respond(jsg::Lock& js, size_t amount) {\n  // So what happens here? The read request has been fulfilled directly by writing\n  // into the storage buffer of the request. Unfortunately, this will only resolve\n  // the data for the one consumer from which the request was received. We have to\n  // copy the data into a refcounted ByteQueue::Entry that is pushed into the other\n  // known consumers.\n\n  // First, we check to make sure that the request hasn't been invalidated already.\n  // Here, invalidated is a fancy word for the promise having been resolved or\n  // rejected already.\n  auto& req = KJ_REQUIRE_NONNULL(request, \"the pending byob read request was already invalidated\");\n\n  // The amount cannot be more than the total space in the request store.\n  JSG_REQUIRE(req.pullInto.filled + amount <= req.pullInto.store.size(), RangeError,\n      kj::str(\"Too many bytes [\", amount, \"] in response to a BYOB read request.\"));\n\n  auto sourcePtr = req.pullInto.store.asArrayPtr();\n\n  if (queue.getConsumerCount() > 1) {\n    // Allocate the entry into which we will be copying the provided data for the\n    // other consumers of the queue.\n    KJ_IF_SOME(store, jsg::BufferSource::tryAllocUnsafe(js, amount)) {\n      auto entry = kj::rc<Entry>(kj::mv(store));\n\n      auto start = sourcePtr.slice(req.pullInto.filled);\n\n      // Safely copy the data over into the entry.\n      entry->toArrayPtr().first(amount).copyFrom(start.first(amount));\n\n      // Push the entry into the other consumers.\n      queue.push(js, kj::mv(entry), consumer);\n    } else {\n      js.throwException(js.error(\"Failed to allocate memory for the byob read response.\"_kj));\n    }\n  }\n\n  // For this consumer, if the number of bytes provided in the response does not\n  // align with the element size of the read into buffer, we need to shave off\n  // those extra bytes and push them into the consumers queue so they can be picked\n  // up by the next read.\n  req.pullInto.filled += amount;\n\n  if (amount < req.pullInto.atLeast) {\n    // The response has not yet met the minimal requirement of this byob read.\n    // In this case, we do not want to resolve the read yet, and we do not\n    // want the byob request to be invalidated. We don't need to worry about\n    // unaligned bytes yet. We're just going to return false to tell the caller\n    // not to invalidate and to update the view over this store.\n\n    // We do want to decrease the atLeast by the amount of bytes we received.\n    req.pullInto.atLeast -= amount;\n    return false;\n  }\n\n  // There is no need to adjust the pullInto.atLeast here because we are resolving\n  // the read immediately.\n\n  auto unaligned = req.pullInto.filled % req.pullInto.store.getElementSize();\n  // It is possible that the request was partially filled already.\n  req.pullInto.filled -= unaligned;\n\n  // Fulfill this request!\n  consumer.resolveRead(js, req);\n\n  if (unaligned > 0) {\n    auto start = sourcePtr.slice(amount - unaligned);\n\n    KJ_IF_SOME(store, jsg::BufferSource::tryAllocUnsafe(js, unaligned)) {\n      auto excess = kj::rc<Entry>(kj::mv(store));\n      excess->toArrayPtr().first(unaligned).copyFrom(start.first(unaligned));\n      consumer.push(js, kj::mv(excess));\n    } else {\n      js.throwException(js.error(\"Failed to allocate memory for the byob read response.\"_kj));\n    }\n  }\n\n  return true;\n}\n\nbool ByteQueue::ByobRequest::respondWithNewView(jsg::Lock& js, jsg::BufferSource view) {\n  // The idea here is that rather than filling the view that the controller was given,\n  // it chose to create its own view and fill that, likely over the same ArrayBuffer.\n  // What we do here is perform some basic validations on what we were given, and if\n  // those pass, we'll replace the backing store held in the req.pullInto with the one\n  // given, then continue on issuing the respond as normal.\n  auto& req = KJ_REQUIRE_NONNULL(request, \"the pending byob read request was already invalidated\");\n  auto amount = view.size();\n\n  JSG_REQUIRE(view.canDetach(js), TypeError, \"Unable to use non-detachable ArrayBuffer.\");\n  JSG_REQUIRE(req.pullInto.store.getOffset() + req.pullInto.filled == view.getOffset(), RangeError,\n      \"The given view has an invalid byte offset.\");\n  JSG_REQUIRE(req.pullInto.store.size() == view.underlyingArrayBufferSize(js), RangeError,\n      \"The underlying ArrayBuffer is not the correct length.\");\n  JSG_REQUIRE(req.pullInto.filled + amount <= req.pullInto.store.size(), RangeError,\n      \"The view is not the correct length.\");\n\n  req.pullInto.store = jsg::BufferSource(js, view.detach(js));\n  return respond(js, amount);\n}\n\nsize_t ByteQueue::ByobRequest::getAtLeast() const {\n  KJ_IF_SOME(req, request) {\n    return req.pullInto.atLeast;\n  }\n  return 0;\n}\n\nv8::Local<v8::Uint8Array> ByteQueue::ByobRequest::getView(jsg::Lock& js) {\n  KJ_IF_SOME(req, request) {\n    return req.pullInto.store\n        .getTypedViewSlice<v8::Uint8Array>(js, req.pullInto.filled, req.pullInto.store.size())\n        .getHandle(js)\n        .As<v8::Uint8Array>();\n  }\n  return v8::Local<v8::Uint8Array>();\n}\n\nsize_t ByteQueue::ByobRequest::getOriginalBufferByteLength(jsg::Lock& js) const {\n  KJ_IF_SOME(req, request) {\n    KJ_IF_SOME(size, req.pullInto.store.underlyingArrayBufferSize(js)) {\n      return size;\n    }\n  }\n  return 0;\n}\n\nsize_t ByteQueue::ByobRequest::getOriginalByteOffsetPlusBytesFilled() const {\n  KJ_IF_SOME(req, request) {\n    return req.pullInto.store.getOffset() + req.pullInto.filled;\n  }\n  return 0;\n}\n\n#pragma endregion ByteQueue::ByobRequest\n\nByteQueue::ByteQueue(size_t highWaterMark): impl(highWaterMark) {}\n\nvoid ByteQueue::close(jsg::Lock& js) {\n  // Note: We intentionally do NOT invalidate pending byob requests here.\n  // According to the spec, the byobRequest should remain accessible after close\n  // so that respondWithNewView() can be called on it (which should throw\n  // appropriate errors for invalid views). The byob request will be invalidated\n  // when respond() or respondWithNewView() is called.\n  if (!FeatureFlags::get(js).getPedanticWpt()) {\n    KJ_IF_SOME(ready, impl.state.tryGetUnsafe<ByteQueue::QueueImpl::Ready>()) {\n      while (!ready.pendingByobReadRequests.empty()) {\n        ready.pendingByobReadRequests.front()->invalidate();\n        ready.pendingByobReadRequests.pop_front();\n      }\n    }\n  }\n  impl.close(js);\n}\n\nssize_t ByteQueue::desiredSize() const {\n  return impl.desiredSize();\n}\n\nvoid ByteQueue::error(jsg::Lock& js, jsg::Value reason) {\n  impl.error(js, kj::mv(reason));\n}\n\nvoid ByteQueue::maybeUpdateBackpressure() {\n  KJ_IF_SOME(state, impl.getState()) {\n    // Invalidated byob read requests will accumulate if we do not take\n    // care of them from time to time. Since maybeUpdateBackpressure\n    // is going to be called regularly while the queue is actively in use,\n    // this is as good a place to clean them out as any.\n    //\n    // We iterate through the ring buffer and remove invalidated items from the front.\n    // Since items are typically invalidated in order, this should be efficient for\n    // the common case.\n    while (!state.pendingByobReadRequests.empty() &&\n        state.pendingByobReadRequests.front()->isInvalidated()) {\n      state.pendingByobReadRequests.pop_front();\n    }\n  }\n  impl.maybeUpdateBackpressure();\n}\n\nvoid ByteQueue::push(jsg::Lock& js, kj::Rc<Entry> entry) {\n  impl.push(js, kj::mv(entry));\n}\n\nsize_t ByteQueue::size() const {\n  return impl.size();\n}\n\nvoid ByteQueue::handlePush(jsg::Lock& js,\n    ConsumerImpl::Ready& state,\n    kj::Maybe<QueueImpl&> queue,\n    kj::Rc<Entry> newEntry) {\n  const auto bufferData = [&](size_t offset) {\n    state.queueTotalSize += newEntry->getSize() - offset;\n    state.buffer.emplace_back(QueueEntry{\n      .entry = kj::mv(newEntry),\n      .offset = offset,\n    });\n  };\n\n  // If there are no pending reads add the entry to the buffer.\n  if (state.readRequests.empty()) {\n    return bufferData(0);\n  }\n\n  // Otherwise, check the the pending reads in the buffer. If the amount\n  // of data in the queue + the amount of data provided by this entry\n  // are >= the pending reads atLeast, then we will fulfill the pending\n  // read, and keep fulfilling pending reads as long as they are available.\n  // Once we are out of pending reads, we will buffer the remaining data.\n  auto entrySize = newEntry->getSize();\n  auto amountAvailable = state.queueTotalSize + entrySize;\n  size_t entryOffset = 0;\n\n  while (!state.readRequests.empty() && amountAvailable > 0) {\n    auto& pending = *state.readRequests.front();\n\n    // If the amountAvailable is less than the pending read request's atLeast,\n    // then we're just going to buffer the data and bailout without fulfilling\n    // the read. We will take care of fulfilling the read later once there\n    // is enough data.\n\n    if (amountAvailable < pending.pullInto.atLeast) {\n      return bufferData(0);\n    }\n\n    // There might be at least some data in the buffer. If there is, it should\n    // not be more than the current pending.pullInfo.atLeast or something went\n    // wrong somewhere else.\n    KJ_REQUIRE(state.queueTotalSize < pending.pullInto.atLeast);\n\n    // First, we copy any data in the buffer out to the pending.pullInto. This\n    // should completely consume the current buffer.\n    while (!state.buffer.empty()) {\n      auto& next = state.buffer.front();\n      KJ_SWITCH_ONEOF(next) {\n        KJ_CASE_ONEOF(c, ConsumerImpl::Close) {\n          // This should have been caught by the isClosing() check above.\n          KJ_FAIL_ASSERT(\"The consumer is closed.\");\n        }\n        KJ_CASE_ONEOF(entry, QueueEntry) {\n          auto sourcePtr = entry.entry->toArrayPtr();\n          auto sourceSize = sourcePtr.size() - entry.offset;\n\n          auto destPtr = pending.pullInto.store.asArrayPtr().slice(pending.pullInto.filled);\n          auto destAmount = pending.pullInto.store.size() - pending.pullInto.filled;\n\n          // sourceSize is the amount of data remaining in the current entry to copy.\n          // destAmount is the amount of space remaining to be filled in the pending read.\n          // Because destAmount should be greater than or equal to atLeast, and because we\n          // already checked that the queueTotalSize is less than atLeast, it should not be\n          // possible for sourceSize to be zero nor greater than or equal to destAmount,\n          // so let's verify.\n          KJ_REQUIRE(sourceSize > 0 && sourceSize < destAmount);\n\n          // Safely copy sourceSize bytes from sourcePtr to destPtr\n          destPtr.first(sourceSize).copyFrom(sourcePtr.slice(entry.offset));\n\n          // We have completely consumed the data in this entry and can safely free\n          // our reference to it now. Yay!\n          auto released = kj::mv(next);\n          state.buffer.pop_front();\n\n          pending.pullInto.filled += sourceSize;\n\n          // There is no reason to adjust the pullInto.atLeast here because we\n          // will be immediately resolving the read in the next step.\n\n          state.queueTotalSize -= sourceSize;\n          amountAvailable -= sourceSize;\n        }\n      }\n    }\n\n    // At this point, there shouldn't be any data remaining in the buffer.\n    KJ_REQUIRE(state.queueTotalSize == 0);\n\n    // And there should be data remaining in the pending pullInto destination.\n    KJ_REQUIRE(pending.pullInto.filled < pending.pullInto.store.size());\n\n    // And the amountAvailable should be equal to the current push size.\n    KJ_REQUIRE(amountAvailable == entrySize - entryOffset);\n\n    // Now, we determine how much of the current entry we can copy into the\n    // destination pullInto by taking the lesser of amountAvailable and\n    // destination pullInto size - filled (which gives us the amount of space\n    // remaining in the destination).\n    auto amountToCopy =\n        kj::min(amountAvailable, pending.pullInto.store.size() - pending.pullInto.filled);\n\n    // The amountToCopy should not be more than the entry size minus the entryOffset\n    // (which is the amount of data remaining to be consumed in the current entry).\n    KJ_REQUIRE(amountToCopy <= entrySize - entryOffset);\n\n    // The amountToCopy plus pending.pullInto.filled should be more than or equal to atLeast\n    // and less than or equal pending.pullInto.store.size().\n    KJ_REQUIRE(amountToCopy + pending.pullInto.filled >= pending.pullInto.atLeast &&\n        amountToCopy + pending.pullInto.filled <= pending.pullInto.store.size());\n\n    // Awesome, so now we safely copy amountToCopy bytes from the current entry into\n    // the remaining space in pending.pullInto.store, being careful to account for\n    // the entryOffset and pending.pullInto.filled offsets to determine the range\n    // where we start copying.\n    auto entryPtr = newEntry->toArrayPtr();\n    auto destPtr = pending.pullInto.store.asArrayPtr().slice(pending.pullInto.filled);\n    destPtr.first(amountToCopy).copyFrom(entryPtr.slice(entryOffset).first(amountToCopy));\n\n    // Yay! this pending read has been fulfilled. There might be more tho. Let's adjust\n    // the amountAvailable and continue trying to consume data.\n    amountAvailable -= amountToCopy;\n    entryOffset += amountToCopy;\n    pending.pullInto.filled += amountToCopy;\n\n    // We do not need to adjust the pullInto.atLeast here since we are immediately\n    // fulfilling the read at this point.\n\n    auto request = kj::mv(state.readRequests.front());\n    state.readRequests.pop_front();\n    request->resolve(js);\n  }\n\n  // If the entry was consumed completely by the pending read, then we're done!\n  // We don't have to buffer any data and shouldn't have any data in the buffer!\n  // Since we possibly consumed data from the buffer, however, let's make sure\n  // we tell the queue to update backpressure signaling.\n  if (entryOffset == entrySize) {\n    KJ_REQUIRE(state.queueTotalSize == 0);\n    return;\n  }\n\n  // Otherwise, we need to buffer the remaining data, being careful to set the offset\n  // for the data that we have already consumed.\n  bufferData(entryOffset);\n}\n\nvoid ByteQueue::handleRead(jsg::Lock& js,\n    ConsumerImpl::Ready& state,\n    ConsumerImpl& consumer,\n    kj::Maybe<QueueImpl&> queue,\n    ReadRequest request) {\n  const auto pendingRead = [&]() {\n    bool isByob = request.pullInto.type == ReadRequest::Type::BYOB;\n    state.readRequests.push_back(kj::heap<ReadRequest>(kj::mv(request)));\n    if (isByob) {\n      // Because ReadRequest is movable, and because the ByobRequest captures\n      // a reference to the ReadRequest, we wait until after it is added to\n      // state.readRequests to create the associated ByobRequest.\n      // If the queue is none, the consumer was cloned from a closed stream\n      // and we can't create a ByobRequest. If the queue state is none,\n      // the queue has already been closed.\n      KJ_IF_SOME(q, queue) {\n        KJ_IF_SOME(queueState, q.getState()) {\n          queueState.pendingByobReadRequests.push_back(\n              state.readRequests.back()->makeByobReadRequest(consumer, q));\n        }\n      }\n    }\n    KJ_IF_SOME(listener, consumer.stateListener) {\n      listener.onConsumerWantsData(js);\n    }\n  };\n\n  const auto consume = [&](size_t amountToConsume) {\n    while (amountToConsume > 0) {\n      KJ_REQUIRE(!state.buffer.empty());\n      // There must be at least one item in the buffer.\n      auto& item = state.buffer.front();\n\n      KJ_SWITCH_ONEOF(item) {\n        KJ_CASE_ONEOF(c, ConsumerImpl::Close) {\n          // We reached the end of the buffer! All data has been consumed.\n          return true;\n        }\n        KJ_CASE_ONEOF(entry, QueueEntry) {\n          // The amount to copy is the lesser of the current entry size minus\n          // offset and the data remaining in the destination to fill.\n          auto entrySize = entry.entry->getSize();\n          auto amountToCopy = kj::min(\n              entrySize - entry.offset, request.pullInto.store.size() - request.pullInto.filled);\n          auto elementSize = request.pullInto.store.getElementSize();\n          if (amountToCopy > elementSize) {\n            amountToCopy -= amountToCopy % elementSize;\n          }\n          if (amountToConsume > elementSize) {\n            amountToConsume -= amountToConsume % elementSize;\n          }\n\n          // Once we have the amount, we safely copy amountToCopy bytes from the\n          // entry into the destination request, accounting properly for the offsets.\n          auto sourcePtr = entry.entry->toArrayPtr().slice(entry.offset);\n          auto destPtr = request.pullInto.store.asArrayPtr().slice(request.pullInto.filled);\n\n          destPtr.first(amountToCopy).copyFrom(sourcePtr.first(amountToCopy));\n\n          request.pullInto.filled += amountToCopy;\n\n          // If pullInto.atLeast is greater than amountToCopy, let's adjust\n          // atLeast down by the number of bytes we've consumed, indicating\n          // a smaller minimum read requirement.\n          if (request.pullInto.atLeast > amountToCopy) {\n            request.pullInto.atLeast -= amountToCopy;\n          } else if (request.pullInto.atLeast == amountToCopy) {\n            request.pullInto.atLeast = 1;\n          }\n          entry.offset += amountToCopy;\n          amountToConsume -= amountToCopy;\n          state.queueTotalSize -= amountToCopy;\n\n          // If the entry.offset is equal to the size of the entry, then we've consumed the\n          // entire thing and can free it and continue iterating. The amountToConsume might\n          // be >= 0, we will check it at the start of the next iteration.\n          if (entry.offset == entrySize) {\n            auto released = kj::mv(item);\n            state.buffer.pop_front();\n            continue;\n          }\n\n          // Otherwise, it is OK that there is data remaining but the amountToConsume\n          // should be 0. Specifically, we either consume the entire entry and there\n          // is data left over to consume, or we did not consume the entire entry\n          // but read all that we can.\n          KJ_REQUIRE(amountToConsume == 0);\n        }\n      }\n    }\n    return false;\n  };\n\n  // If there are no pending read requests and there is data in the buffer,\n  // we will try to fulfill the read request immediately.\n  if (state.readRequests.empty() && state.queueTotalSize > 0) {\n    // If the available size is less than the read requests atLeast, then\n    // push the read request into the pending so we can wait for more data...\n\n    if (state.queueTotalSize < request.pullInto.atLeast) {\n      // If there is anything in the consumers queue at this point, We need to\n      // copy those bytes into the byob buffer and advance the filled counter\n      // forward that number of bytes.\n      if (state.queueTotalSize > 0 && consume(state.queueTotalSize)) {\n        return request.resolveAsDone(js);\n      }\n      return pendingRead();\n    }\n\n    // Awesome, ok, it looks like we have enough data in the queue for us\n    // to minimally fill this read request! The amount to copy is the lesser\n    // of the queue total size and the maximum amount of space in the request\n    // pull into.\n    if (consume(kj::min(state.queueTotalSize, request.pullInto.store.size()))) {\n\n      // If consume returns true, the consumer hit the end and we need to\n      // just resolve the request as done and return.\n      return request.resolveAsDone(js);\n    }\n\n    // Now, we can resolve the read promise. Since we consumed data from the\n    // buffer, we also want to make sure to notify the queue so it can update\n    // backpressure signaling.\n    request.resolve(js);\n  } else if (state.queueTotalSize == 0 && consumer.isClosing()) {\n    // Otherwise, if size() is zero and isClosing() is true, we should have already\n    // drained but let's take care of that now. Specifically, in this case there's\n    // no data in the queue and close() has already been called, so there won't be\n    // any more data coming.\n    request.resolveAsDone(js);\n  } else {\n    // Otherwise, push the read request into the pending readRequests. It will be\n    // resolved either as soon as there is data available or the consumer closes\n    // or errors.\n    return pendingRead();\n  }\n}\n\nbool ByteQueue::handleMaybeClose(jsg::Lock& js,\n    ConsumerImpl::Ready& state,\n    ConsumerImpl& consumer,\n    kj::Maybe<QueueImpl&> queue) {\n  // This is called when we know that we are closing and we still have data in\n  // the queue. We want to see if we can drain as much of it into pending reads\n  // as possible. If we're able to drain all of it, then yay! We can go ahead and\n  // close. Otherwise we stay open and wait for more reads to consume the rest.\n\n  // We should only be here if there is data remaining in the queue.\n  KJ_ASSERT(state.queueTotalSize > 0);\n\n  // We should also only be here if the consumer is closing.\n  KJ_ASSERT(consumer.isClosing());\n\n  const auto consume = [&] {\n    // Consume will copy as much of the remaining data in the buffer as possible\n    // to the next pending read. If the remaining data can fit into the remaining\n    // space in the read, awesome, we've consumed everything and we will return\n    // true. If the remaining data cannot fit into the remaining space in the read,\n    // then we'll return false to indicate that there's more data to consume. In\n    // either case, the pending read is popped off the pending queue and resolved.\n\n    KJ_ASSERT(!state.readRequests.empty());\n    auto& pending = *state.readRequests.front();\n\n    while (!state.buffer.empty()) {\n      auto& next = state.buffer.front();\n      KJ_SWITCH_ONEOF(next) {\n        KJ_CASE_ONEOF(c, ConsumerImpl::Close) {\n          // We've reached the end! queueTotalSize should be zero. We need to\n          // resolve and pop the current read and return true to indicate that\n          // we're all done.\n          //\n          // Technically, we really shouldn't get here but the case is covered\n          // just in case.\n          KJ_ASSERT(state.queueTotalSize == 0);\n          auto request = kj::mv(state.readRequests.front());\n          state.readRequests.pop_front();\n          request->resolve(js);\n          return true;\n        }\n        KJ_CASE_ONEOF(entry, QueueEntry) {\n          auto sourcePtr = entry.entry->toArrayPtr();\n          auto sourceSize = sourcePtr.size() - entry.offset;\n\n          auto destPtr = pending.pullInto.store.asArrayPtr().slice(pending.pullInto.filled);\n          auto destAmount = pending.pullInto.store.size() - pending.pullInto.filled;\n\n          // There should be space available to copy into and data to copy from, or\n          // something else went wrong.\n          KJ_ASSERT(destAmount > 0);\n          KJ_ASSERT(sourceSize > 0);\n\n          // sourceSize is the amount of data remaining in the current entry to copy.\n          // destAmount is the amount of space remaining to be filled in the pending read.\n          auto amountToCopy = kj::min(sourceSize, destAmount);\n\n          auto sourceStart = sourcePtr.slice(entry.offset);\n\n          // It shouldn't be possible for sourceEnd to extend past the sourcePtr.end()\n          // but let's make sure just to be safe.\n          KJ_ASSERT(amountToCopy <= sourceStart.size());\n\n          // Safely copy amountToCopy bytes from the source into the destination.\n          destPtr.first(amountToCopy).copyFrom(sourceStart.first(amountToCopy));\n          pending.pullInto.filled += amountToCopy;\n\n          // We do not need to adjust down the atLeast here because, no matter what,\n          // the read is going to be resolved either here or in the next iteration.\n\n          state.queueTotalSize -= amountToCopy;\n          entry.offset += amountToCopy;\n\n          KJ_ASSERT(entry.offset <= sourcePtr.size());\n\n          if (amountToCopy == sourcePtr.size()) {\n            // If amountToCopy is equal to sourcePtr.size(), we've consumed the entire entry\n            // and we can free it.\n            auto released = kj::mv(next);\n            state.buffer.pop_front();\n\n            if (amountToCopy == destAmount) {\n              // If the amountToCopy is equal to destAmount, then we've completely filled\n              // this read request with the data remaining. Resolve the read request. If\n              // state.queueTotalSize happens to be zero, we can safely indicate that we\n              // have read the remaining data as this may have been the last actual value\n              // entry in the buffer.\n              auto request = kj::mv(state.readRequests.front());\n              state.readRequests.pop_front();\n              request->resolve(js);\n\n              if (state.queueTotalSize == 0) {\n                // If the queueTotalSize is zero at this point, the next item in the queue\n                // must be a close and we can return true. All of the data has been consumed.\n                KJ_ASSERT(state.buffer.front().is<ConsumerImpl::Close>());\n                return true;\n              }\n\n              // Otherwise, there's still data to consume, return false here to move on\n              // to the next pending read (if any).\n              return false;\n            }\n\n            // We know that amountToCopy cannot be greater than destAmount because\n            // of the kj::min above.\n\n            // Continuing here means that our pending read still has space to fill\n            // and we might still have value entries to fill it. We'll iterate around\n            // and see where we get.\n            continue;\n          }\n\n          // This read did not consume everything in this entry but doesn't have\n          // any more space to fill. We will resolve this read and return false\n          // to indicate that the outer loop should continue with the next read\n          // request if there is one.\n\n          // At this point, it should be impossible for state.queueTotalSize to\n          // be zero because there is still data remaining to be consumed in this\n          // buffer.\n          KJ_ASSERT(state.queueTotalSize > 0);\n\n          auto request = kj::mv(state.readRequests.front());\n          state.readRequests.pop_front();\n          request->resolve(js);\n          return false;\n        }\n      }\n    }\n\n    return state.queueTotalSize == 0;\n  };\n\n  // We can only consume here if there are pending reads!\n  while (!state.readRequests.empty()) {\n    // We ignore the read request atLeast here since we are closing. Our goal is to\n    // consume as much of the data as possible.\n\n    if (consume()) {\n      // If consume returns true, we reached the end and have no more data to\n      // consume. That's a good thing! It means we can go ahead and close down.\n      return true;\n    }\n\n    // If consume() returns false, there is still data left to consume in the queue.\n    // We will loop around and try again so long as there are still read requests\n    // pending.\n  }\n\n  // At this point, we shouldn't have any read requests and there should be data\n  // left in the queue. We have to keep waiting for more reads to consume the\n  // remaining data.\n  KJ_ASSERT(state.queueTotalSize > 0);\n  KJ_ASSERT(state.readRequests.empty());\n\n  return false;\n}\n\nkj::Maybe<kj::Own<ByteQueue::ByobRequest>> ByteQueue::nextPendingByobReadRequest() {\n  KJ_IF_SOME(state, impl.getState()) {\n    while (!state.pendingByobReadRequests.empty()) {\n      auto request = kj::mv(state.pendingByobReadRequests.front());\n      state.pendingByobReadRequests.pop_front();\n      if (!request->isInvalidated()) {\n        return kj::mv(request);\n      }\n    }\n  }\n  return kj::none;\n}\n\nbool ByteQueue::hasPartiallyFulfilledRead() {\n  KJ_IF_SOME(state, impl.getState()) {\n    if (!state.pendingByobReadRequests.empty()) {\n      auto& pending = state.pendingByobReadRequests.front();\n      if (pending->isPartiallyFulfilled()) {\n        return true;\n      }\n    }\n  }\n  return false;\n}\n\nbool ByteQueue::wantsRead() const {\n  return impl.wantsRead();\n}\n\nsize_t ByteQueue::getConsumerCount() {\n  return impl.getConsumerCount();\n}\n\nvoid ByteQueue::visitForGc(jsg::GcVisitor& visitor) {}\n\n#pragma endregion ByteQueue\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/queue.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"common.h\"\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/util/ring-buffer.h>\n#include <workerd/util/small-set.h>\n#include <workerd/util/state-machine.h>\n#include <workerd/util/weak-refs.h>\n\nnamespace workerd::api {\n\n// ============================================================================\n// Queues\n//\n// There are two kinds of queues used internally by the JavaScript-backed\n// ReadableStream implementation: value queues and byte queues. Each operate\n// in generally the same way but byte queues have a number of unique complexities\n// that make them more difficult.\n//\n// A queue (of either type) has the following general characteristics:\n//\n//  - Every queue has a high water mark. This is the maximum amount of data\n//    that should be stored in the queue pending consumption before backpressure\n//    is signaled. Additional data can always be pushed into the queue beyond\n//    the high water mark, but it is not advisable to do so.\n//\n//  - All data stored in the queue is in the form of entries. The\n//    specific type of entry depends on the queue type. Every entry has a\n//    calculated size, which is dependent on the type of entry.\n//\n//  - Every queue has one or more consumers. Each consumer maintains its own\n//    internal buffer of entries that it has yet to consume. Entries are\n//    structured such that there is ever only one copy of any given chunk of\n//    data in memory, with each entry in each consumer possessing only a reference\n//    to it. Whenever data is pushed into the queue, references are pushed into\n//    each of the consumers. As data is consumed from the internal buffer, the\n//    entries are freed. The underlying data is freed once the last\n//    reference is released.\n//\n//  - Every consumer has an remaining buffer size, which is the sum of the sizes\n//    of all entries remaining to be consumed in its internal buffer.\n//\n//  - A queue has a total queue size, which is the remaining buffer size of the\n//    consumer with the most unconsumed data.\n//\n//  - A queue has a desired size, which is the amount of additional data that\n//    can be pushed into the queue before backpressure is signaled. It is\n//    calculated by subtracting the total queue size from the high water mark.\n//\n//  - Backpressure is signaled when desired size is equal to, or less than zero.\n//\n// Each type of queue has a specific kind of consumer. These generally operate\n// in the same way but Byte Queue consumers have a number of unique details.\n//\n//  - As mentioned above, every consumer maintains an internal data buffer\n//    consisting of references to the data that has been pushed into\n//    the queue.\n//\n//  - Every consumer maintains a list of pending reads. A read is a request to\n//    consume some amount of data from the internal data buffer. If there is\n//    enough data in the internal buffer to immediately fulfill the read request\n//    when it is received, then we do so. Otherwise, the read is moved into the\n//    pending reads list and is fulfilled later once there is enough data provided\n//    to the consumer to do so.\n//\n//  - When data is provided to a consumer by the queue, that data is added to\n//    the internal buffer only if there are no pending reads capable of\n//    immediately consuming the data.\n//\n//  - When data is added to the internal buffer, the remaining buffer size is\n//    incremented. When data is removed from the internal buffer, the remaining\n//    buffer size is decremented.\n//\n//  - Whenever the remaining buffer size for a consumer is modified, the queue\n//    is asked to recalculate the desired size.\n//\n// For value queues, every individual entry is queued and consumed as a whole\n// unit. It is not possible to partially consume a single value entry. The\n// size of a value entry is calculated by a JavaScript function provided by\n// user code (the \"size algorithm\") if one is provided. If a size algorithm\n// is not provided, the default size of a value entry is exactly 1.\n//\n// The bookkeeping for a value queue is fairly simple:\n//\n//  - A single value entry is created.\n//  - Clones of that single value entry are distributed to each of\n//    the value queue consumers.\n//  - If a consumer has a pending read, the read is fulfilled immediately\n//    and the reference is never added to that consumer's internal buffer.\n//  - If the consumer has no pending reads, the reference is added to the\n//    consumer's internal buffer and the remaining buffer size is incremented\n//    by the calculated size of the value entry.\n//  - Once the value entry has been delivered to each of the consumers,\n//    the total queue size is updated by setting it equal to the maximum\n//    remaining buffer size among the consumers.\n//  - Later, when a consumer receives a read that consumes data from the\n//    internal buffer, the remaining buffer size is decremented by the calculated\n//    size of the value entry, and the queue is notified to re-evaluated the\n//    total queue size.\n//\n// For byte queues, the situation becomes much more complicated for two\n// specific reasons: 1) All entries are in the form of arbitrarily long\n// byte sequences that can be partially consumed, and 2) read requests\n// made to the byte queue can be \"BYOB\" (bring your own buffer) in which\n// the intent is to avoid being forced to copy data between buffers by\n// having the reading code allocate and provide a buffer that the stream\n// implementation will read data into. When there is only a single consumer\n// for a streams data, the BYOB model is fairly straightforward and can be\n// implemented to avoid copying entirely. However, when you have multiple\n// consumers for a byte queue, all consuming data at different rates, it is\n// not possible to avoid copying entirely. Reads that consume byte data can\n// specify a range that crosses the boundaries of the individual entries that\n// are stored within the internal buffer, further complicating the process of\n// consuming data.\n//\n// To make matters even more complicated, a stream implementation is permitted\n// to ignore the allocated buffers provided by the BYOB read request and push\n// data into the queue as if the allocated buffer were not provided at all.\n// In such cases, the BYOB read request still needs to be fulfilled with the\n// provided buffer being written into it. Unfortunately, this is not uncommon.\n// React server-side rendering, for instance, will create byte-oriented\n// ReadableStreams that support BYOB reads, but will use the controller.enqueue()\n// API to push data into the stream rather than paying any attention to the\n// BYOB buffers provided by the readers.\n//\n// The requirement to support BYOB reads makes it critical to properly sequence\n// the delivery of BYOB read requests to the stream controller implementation,\n// ensuring the proper order of bytes delivered to each consumer while respecting\n// backpressure signaling such that backpressure is always determined by the\n// consumer that is being consumed at the slowest rate.\n//\n// On top of everything else, Workers introduces the concept of a minRead,\n// that is, a minimum number of bytes that a read request should consume from\n// the queue. The read promise should not be fulfilled unless either that\n// minimum number of bytes has been provided, or the stream is closed or errored.\n\ntemplate <typename Self>\nclass ConsumerImpl;\n\ntemplate <typename Self>\nclass QueueImpl;\n\n// DrainingReadResult is defined in common.h\n\n// Provides the underlying implementation shared by ByteQueue and ValueQueue.\ntemplate <typename Self>\nclass QueueImpl final {\n public:\n  using ConsumerImpl = ConsumerImpl<Self>;\n  using Entry = Self::Entry;\n  using State = Self::State;\n\n  explicit QueueImpl(size_t highWaterMark)\n      : highWaterMark(highWaterMark),\n        state(QueueState::template create<Ready>()) {}\n\n  QueueImpl(QueueImpl&&) = default;\n  QueueImpl& operator=(QueueImpl&&) = default;\n\n  ~QueueImpl() noexcept(false) {\n    // Detach all consumers before destruction to prevent UAF.\n    // This can happen during isolate teardown when the destruction order\n    // of JS wrapper objects doesn't follow the ownership hierarchy.\n    allConsumers.forEach([&](ConsumerImpl& consumer) { consumer.detachQueue(); });\n  }\n\n  // Closes the queue. The close is forwarded on to all consumers.\n  // If we are already closed or errored, do nothing here.\n  void close(jsg::Lock& js) {\n    if (state.isActive()) {\n#ifdef KJ_DEBUG\n      isClosingOrErroring = true;\n      KJ_DEFER(isClosingOrErroring = false);\n#endif\n      allConsumers.forEach([&](ConsumerImpl& consumer) { consumer.close(js); });\n      state.template transitionTo<Closed>();\n    }\n  }\n\n  // The amount of data the Queue needs until it is considered full.\n  // The value can be zero or negative, in which case backpressure is\n  // signaled on the queue.\n  // If the queue is already closed or errored, return 0.\n  inline ssize_t desiredSize() const {\n    return state.isActive() ? highWaterMark - size() : 0;\n  }\n\n  // Errors the queue. The error is forwarded on to all consumers,\n  // which will, in turn, reset their internal buffers and reject\n  // all pending consume promises.\n  // If we are already closed or errored, do nothing here.\n  void error(jsg::Lock& js, jsg::Value reason) {\n    if (state.isActive()) {\n#ifdef KJ_DEBUG\n      isClosingOrErroring = true;\n      KJ_DEFER(isClosingOrErroring = false);\n#endif\n      allConsumers.forEach([&](ConsumerImpl& consumer) { consumer.error(js, reason.addRef(js)); });\n      state.template transitionTo<Errored>(kj::mv(reason));\n    }\n  }\n\n  // Polls all known consumers to collect their current buffer sizes\n  // so that the current queue size can be updated.\n  // If we are already closed or errored, set totalQueueSize to zero.\n  void maybeUpdateBackpressure() {\n    totalQueueSize = 0;\n    if (state.isActive()) {\n      allConsumers.forEach([&](ConsumerImpl& consumer) {\n        totalQueueSize = kj::max(totalQueueSize, consumer.size());\n      });\n    }\n  }\n\n  // Forwards the entry to all consumers (except skipConsumer if given).\n  // For each consumer, the entry will be used to fulfill any pending consume operations.\n  // If the entry type is byteOriented and has not been fully consumed by pending consume\n  // operations, then any left over data will be pushed into the consumer's buffer.\n  // Asserts if the queue is closed or errored.\n  void push(jsg::Lock& js, kj::Rc<Entry> entry, kj::Maybe<ConsumerImpl&> skipConsumer = kj::none) {\n    state.requireActiveUnsafe(\"The queue is closed or errored.\");\n\n    allConsumers.forEach([&](ConsumerImpl& consumer) {\n      KJ_IF_SOME(skip, skipConsumer) {\n        if (&skip == &consumer) {\n          return;\n        }\n      }\n      consumer.push(js, entry->clone(js));\n    });\n  }\n\n  // The current size of consumer with the most stored data.\n  size_t size() const {\n    return totalQueueSize;\n  }\n\n  size_t getConsumerCount() const {\n    return allConsumers.size();\n  }\n\n  bool wantsRead() const {\n    if (state.isActive()) {\n      for (const auto& weakRef: allConsumers) {\n        KJ_IF_SOME(consumer, weakRef->tryGet()) {\n          if (consumer.hasReadRequests()) return true;\n        }\n      }\n    }\n    return false;\n  }\n\n  // Specific queue implementations may provide additional state that is attached\n  // to the Ready struct.\n  kj::Maybe<State&> getState() KJ_LIFETIMEBOUND {\n    KJ_IF_SOME(ready, state.tryGetActiveUnsafe()) {\n      return ready;\n    }\n    return kj::none;\n  }\n\n  inline kj::StringPtr jsgGetMemoryName() const;\n  inline size_t jsgGetMemorySelfSize() const;\n  inline void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  struct Closed {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"closed\"_kj;\n  };\n  struct Errored {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"errored\"_kj;\n    jsg::Value reason;\n  };\n\n  struct Ready final: public State {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"ready\"_kj;\n  };\n\n  // State machine for QueueImpl:\n  //   Ready -> Closed (close() called)\n  //   Ready -> Errored (error() called)\n  // Closed is terminal, Errored is implicitly terminal via ErrorState.\n  using QueueState = StateMachine<TerminalStates<Closed>,\n      ErrorState<Errored>,\n      ActiveState<Ready>,\n      Ready,\n      Closed,\n      Errored>;\n\n  size_t highWaterMark;\n  size_t totalQueueSize = 0;\n  QueueState state;\n  // The set of consumers attached to this queue. In the typical case this\n  // will be a very small number (often just one or two), so we use SmallSet to\n  // optimize for that. This persists across state transitions so we can detach\n  // consumers even after close()/error() transitions the queue to a terminal state.\n  //\n  // We store weak references to consumers to safely handle the case where a consumer\n  // is destroyed during iteration (e.g., resolving a read request triggers JS that\n  // destroys another consumer in the same queue). When iterating, we check if the WeakRef is still valid.\n  SmallSet<kj::Rc<WeakRef<ConsumerImpl>>> allConsumers;\n\n#ifdef KJ_DEBUG\n  // Debug flag to detect if addConsumer is called during close/error iteration.\n  // This should never happen - it would indicate a bug in the streams implementation.\n  bool isClosingOrErroring = false;\n#endif\n\n  void addConsumer(kj::Rc<WeakRef<ConsumerImpl>> weakRef) {\n    KJ_DASSERT(\n        !isClosingOrErroring, \"Cannot add a consumer while the queue is being closed or errored\");\n    allConsumers.add(kj::mv(weakRef));\n  }\n\n  void removeConsumer(ConsumerImpl& consumer) {\n    allConsumers.removeIf([&consumer](const kj::Rc<WeakRef<ConsumerImpl>>& ref) {\n      KJ_IF_SOME(c, ref->tryGet()) {\n        return &c == &consumer;\n      }\n      return false;  // Already invalid, will be cleaned up later\n    });\n    maybeUpdateBackpressure();\n  }\n\n  friend Self;\n  friend ConsumerImpl;\n};\n\n// Provides the underlying implementation shared by ByteQueue::Consumer and ValueQueue::Consumer\ntemplate <typename Self>\nclass ConsumerImpl final {\n public:\n  struct StateListener {\n    virtual void onConsumerClose(jsg::Lock& js) = 0;\n    virtual void onConsumerError(jsg::Lock& js, jsg::Value reason) = 0;\n    // Called when the consumer has a pending read and needs data.\n    // Returns true if the pull algorithm completed synchronously (meaning\n    // more pumping might yield additional synchronous data), false if the\n    // pull is async (promise pending) or no pull was needed.\n    virtual bool onConsumerWantsData(jsg::Lock& js) = 0;\n  };\n\n  using QueueImpl = QueueImpl<Self>;\n\n  // A simple utility to be allocated on any stack where consumer buffer data maybe consumed\n  // or expanded. When the stack is unwound, it ensures the backpressure is appropriately\n  // updated. Captures the queue at construction time since the consumer may be\n  // destroyed before the scope ends (e.g., via onConsumerError -> owner.doError()).\n  struct UpdateBackpressureScope final {\n    kj::Maybe<QueueImpl&> queue;\n    UpdateBackpressureScope(ConsumerImpl& consumer): queue(consumer.queue) {}\n    ~UpdateBackpressureScope() noexcept(false) {\n      KJ_IF_SOME(q, queue) {\n        q.maybeUpdateBackpressure();\n      }\n    }\n    KJ_DISALLOW_COPY_AND_MOVE(UpdateBackpressureScope);\n  };\n\n  using ReadRequest = Self::ReadRequest;\n  using Entry = Self::Entry;\n  using QueueEntry = Self::QueueEntry;\n\n  ConsumerImpl(QueueImpl& queue, kj::Maybe<ConsumerImpl::StateListener&> stateListener = kj::none)\n      : queue(queue),\n        state(ConsumerState::template create<Ready>()),\n        stateListener(stateListener) {\n    queue.addConsumer(selfRef.addRef());\n  }\n\n  explicit ConsumerImpl(kj::Maybe<ConsumerImpl::StateListener&> stateListener)\n      : queue(kj::none),\n        state(ConsumerState::template create<Ready>()),\n        stateListener(stateListener) {}\n\n  KJ_DISALLOW_COPY_AND_MOVE(ConsumerImpl);\n\n  ~ConsumerImpl() noexcept(false) {\n    // queue may be none if the queue was destroyed before this consumer\n    // (e.g., during isolate teardown) or if cloned from a closed stream.\n    // We must remove ourselves before invalidating selfRef, otherwise\n    // removeConsumer won't find us (tryGet() would return none).\n    KJ_IF_SOME(q, queue) {\n      q.removeConsumer(*this);\n    }\n    // Invalidate after removal so any concurrent iteration will skip us.\n    selfRef->invalidate();\n  }\n\n  // Called by QueueImpl destructor to detach this consumer from a queue\n  // that is about to be destroyed.\n  void detachQueue() {\n    queue = kj::none;\n  }\n\n  void cancel(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason) {\n    // Already closed or errored - nothing to do.\n    KJ_IF_SOME(ready, state.tryGetActiveUnsafe()) {\n      for (auto& request: ready.readRequests) {\n        request->resolveAsDone(js);\n      }\n      state.template transitionTo<Closed>();\n    }\n  }\n\n  void close(jsg::Lock& js) {\n    // If we are already closed or errored, then we do nothing here.\n    KJ_IF_SOME(ready, state.tryGetActiveUnsafe()) {\n      // If we are not already closing, enqueue a Close sentinel.\n      if (!isClosing()) {\n        ready.buffer.push_back(Close{});\n      }\n\n      // Then check to see if we need to drain pending reads and\n      // update the state to Closed.\n      return maybeDrainAndSetState(js);\n    }\n  }\n\n  inline bool empty() const {\n    return size() == 0;\n  }\n\n  void error(jsg::Lock& js, jsg::Value reason) {\n    // If we are already closed or errored, then we do nothing here.\n    // The new error doesn't matter.\n    if (state.isActive()) {\n      maybeDrainAndSetState(js, kj::mv(reason));\n    }\n  }\n\n  void push(jsg::Lock& js, kj::Rc<Entry> entry) {\n    // If the consumer is already closed or errored, then we do nothing here.\n    // This can happen during iteration over consumers in QueueImpl::push() when\n    // resolving a read request on one consumer triggers JavaScript code that\n    // closes or errors another consumer in the same queue.\n    KJ_IF_SOME(ready, state.tryGetActiveUnsafe()) {\n      // If the consumer is already closing or the entry is empty, do nothing.\n      // Also skip if queue is none (consumer cloned from closed stream).\n      if (isClosing() || entry->getSize() == 0 || queue == kj::none) {\n        return;\n      }\n\n      UpdateBackpressureScope scope(*this);\n      Self::handlePush(js, ready, queue, kj::mv(entry));\n    }\n  }\n\n  void read(jsg::Lock& js, ReadRequest request) {\n    if (state.template is<Closed>()) {\n      return request.resolveAsDone(js);\n    }\n    KJ_IF_SOME(errored, state.tryGetErrorUnsafe()) {\n      return request.reject(js, errored.reason);\n    }\n    auto& ready = state.requireActiveUnsafe();\n    // Mutual exclusion with draining reads.\n    if (ready.hasPendingDrainingRead) {\n      auto error = jsg::Value(\n          js.v8Isolate, js.typeError(\"Cannot call read while there is a pending draining read\"_kj));\n      return request.reject(js, error);\n    }\n    Self::handleRead(js, ready, *this, queue, kj::mv(request));\n    return maybeDrainAndSetState(js);\n  }\n\n  void reset() {\n    KJ_IF_SOME(ready, state.tryGetActiveUnsafe()) {\n      UpdateBackpressureScope scope(*this);\n      ready.buffer.clear();\n      ready.queueTotalSize = 0;\n    }\n  }\n\n  // The current total calculated size of the consumer's internal buffer.\n  size_t size() const {\n    return state.whenActiveOr([](const Ready& ready) { return ready.queueTotalSize; }, 0ul);\n  }\n\n  void resolveRead(jsg::Lock& js, ReadRequest& req) {\n    auto& ready = state.requireActiveUnsafe();\n    KJ_REQUIRE(!ready.readRequests.empty());\n    KJ_REQUIRE(&req == ready.readRequests.front().get());\n    // Pop the request before resolving to ensure the request is fully owned locally.\n    auto request = kj::mv(ready.readRequests.front());\n    ready.readRequests.pop_front();\n    request->resolve(js);\n  }\n\n  void resolveReadAsDone(jsg::Lock& js, ReadRequest& req) {\n    auto& ready = state.requireActiveUnsafe();\n    KJ_REQUIRE(!ready.readRequests.empty());\n    KJ_REQUIRE(&req == ready.readRequests.front().get());\n    // Pop the request before resolving to ensure the request is fully owned locally.\n    auto request = kj::mv(ready.readRequests.front());\n    ready.readRequests.pop_front();\n    request->resolveAsDone(js);\n  }\n\n  void cloneTo(jsg::Lock& js, ConsumerImpl& other) {\n    if (state.template is<Closed>()) {\n      other.state.template transitionTo<Closed>();\n      return;\n    }\n    KJ_IF_SOME(errored, state.tryGetErrorUnsafe()) {\n      other.state.template transitionTo<Errored>(errored.reason.addRef(js));\n      return;\n    }\n    KJ_IF_SOME(ready, state.tryGetActiveUnsafe()) {\n      // We copy the buffered state but not the readRequests.\n      auto& otherReady = KJ_REQUIRE_NONNULL(\n          other.state.tryGetActiveUnsafe(), \"The new consumer should not be closed or errored.\");\n      otherReady.queueTotalSize = ready.queueTotalSize;\n      for (auto& item: ready.buffer) {\n        KJ_SWITCH_ONEOF(item) {\n          KJ_CASE_ONEOF(c, Close) {\n            otherReady.buffer.push_back(Close{});\n          }\n          KJ_CASE_ONEOF(entry, QueueEntry) {\n            otherReady.buffer.push_back(entry.clone(js));\n          }\n        }\n      }\n    }\n  }\n\n  bool hasReadRequests() const {\n    return state.whenActiveOr(\n        [](const Ready& ready) { return !ready.readRequests.empty(); }, false);\n  }\n\n  void cancelPendingReads(jsg::Lock& js, jsg::JsValue reason) {\n    // Already closed or errored - nothing to do.\n    state.whenActive([&](Ready& ready) {\n      for (auto& request: ready.readRequests) {\n        request->resolver.reject(js, reason);\n      }\n      ready.readRequests.clear();\n    });\n  }\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    // Technically we shouldn't really have to GC visit the stored error here but there\n    // should not be any harm in doing so.\n    KJ_IF_SOME(errored, state.tryGetErrorUnsafe()) {\n      visitor.visit(errored.reason);\n    }\n    // There's no reason to GC visit the promise resolver or buffer in Ready state and it is\n    // potentially problematic if we do. Since the read requests are queued, if we\n    // GC visit it once, remove it from the queue, and GC happens to kick in before\n    // we access the resolver, then v8 could determine that the resolver or buffered\n    // entries are no longer reachable via tracing and free them before we can\n    // actually try to access the held resolver.\n  }\n\n  inline kj::StringPtr jsgGetMemoryName() const;\n  inline size_t jsgGetMemorySelfSize() const;\n  inline void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  // A sentinel used in the buffer to signal that close() has been called.\n  struct Close {};\n\n  struct Closed {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"closed\"_kj;\n  };\n  struct Errored {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"errored\"_kj;\n    jsg::Value reason;\n  };\n  struct Ready {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"ready\"_kj;\n    workerd::RingBuffer<kj::OneOf<QueueEntry, Close>, 16> buffer;\n    // We use kj::Own<ReadRequest> because ByobRequest holds a reference to its associated\n    // ReadRequest. Using RingBuffer directly would invalidate those references when the buffer\n    // grows. By heap-allocating each ReadRequest, we ensure reference stability.\n    workerd::RingBuffer<kj::Own<ReadRequest>, 8> readRequests;\n    size_t queueTotalSize = 0;\n    // True if there is a pending draining read operation. Draining reads are mutually\n    // exclusive with regular reads - read() will reject if this is true, and drainingRead()\n    // will reject if there are pending readRequests.\n    bool hasPendingDrainingRead = false;\n\n    inline kj::StringPtr jsgGetMemoryName() const;\n    inline size_t jsgGetMemorySelfSize() const;\n    inline void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const;\n  };\n\n  // State machine for ConsumerImpl:\n  //   Ready -> Closed (close() called and drained)\n  //   Ready -> Errored (error() called)\n  // Closed is terminal, Errored is implicitly terminal via ErrorState.\n  using ConsumerState = StateMachine<TerminalStates<Closed>,\n      ErrorState<Errored>,\n      ActiveState<Ready>,\n      Ready,\n      Closed,\n      Errored>;\n\n  kj::Maybe<QueueImpl&> queue;\n  ConsumerState state;\n  kj::Maybe<ConsumerImpl::StateListener&> stateListener;\n  // WeakRef to this consumer, used for safe registration with QueueImpl.\n  // When this consumer is destroyed, we invalidate the WeakRef so that\n  // any iteration over allConsumers in QueueImpl will safely skip us.\n  kj::Rc<WeakRef<ConsumerImpl>> selfRef =\n      kj::rc<WeakRef<ConsumerImpl>>(kj::Badge<ConsumerImpl>{}, *this);\n\n  bool isClosing() {\n    // Closing state is determined by whether there is a Close sentinel that has been\n    // pushed into the end of Ready state buffer.\n    return state.whenActiveOr([](Ready& ready) {\n      return !ready.buffer.empty() && ready.buffer.back().template is<Close>();\n    }, false);\n  }\n\n  void maybeDrainAndSetState(jsg::Lock& js, kj::Maybe<jsg::Value> maybeReason = kj::none) {\n    // If the state is already errored or closed then there is nothing to drain.\n    KJ_IF_SOME(ready, state.tryGetActiveUnsafe()) {\n      UpdateBackpressureScope scope(*this);\n      KJ_IF_SOME(reason, maybeReason) {\n        // If maybeReason != nullptr, then we are draining because of an error.\n        // In that case, we want to reset/clear the buffer and reject any remaining\n        // pending read requests using the given reason.\n        for (auto& request: ready.readRequests) {\n          request->reject(js, reason);\n        }\n        state.template transitionTo<Errored>(reason.addRef(js));\n        KJ_IF_SOME(listener, stateListener) {\n          listener.onConsumerError(js, kj::mv(reason));\n          // After this point, we should not assume that this consumer can\n          // be safely used at all. It's most likely the stateListener has\n          // released it.\n        }\n      } else {\n        // Otherwise, if isClosing() is true...\n        if (isClosing()) {\n          if (!empty() && !Self::handleMaybeClose(js, ready, *this, queue)) {\n            // If the queue is not empty, we'll have the implementation see\n            // if it can drain the remaining data into pending reads. If handleMaybeClose\n            // returns false, then it could not and we can't yet close. If it returns true,\n            // yay! Our queue is empty and we can continue closing down.\n            KJ_ASSERT(!empty());  // We're still not empty\n            return;\n          }\n\n          KJ_ASSERT(empty());\n          KJ_REQUIRE(ready.buffer.size() == 1);  // The close should be the only item remaining.\n          for (auto& request: ready.readRequests) {\n            request->resolveAsDone(js);\n          }\n          state.template transitionTo<Closed>();\n          KJ_IF_SOME(listener, stateListener) {\n            listener.onConsumerClose(js);\n            // After this point, we should not assume that this consumer can\n            // be safely used at all. It's most likely the stateListener has\n            // released it.\n          }\n        }\n      }\n    }\n  }\n\n  friend Self::Consumer;\n  friend Self;\n};\n\n// ============================================================================\n// Value queue\n\nclass ValueQueue final {\n public:\n  using ConsumerImpl = ConsumerImpl<ValueQueue>;\n  using QueueImpl = QueueImpl<ValueQueue>;\n\n  struct State {\n    JSG_MEMORY_INFO(ValueQueue::State) {}\n  };\n\n  struct ReadRequest {\n    jsg::Promise<ReadResult>::Resolver resolver;\n\n    void resolveAsDone(jsg::Lock& js);\n    void resolve(jsg::Lock& js, jsg::Value value);\n    void reject(jsg::Lock& js, jsg::Value& value);\n\n    JSG_MEMORY_INFO(ValueQueue::ReadRequest) {\n      tracker.trackField(\"resolver\", resolver);\n    }\n  };\n\n  // A value queue entry consists of an arbitrary JavaScript value and a size that is\n  // calculated by the size algorithm function provided in the stream constructor.\n  class Entry: public kj::Refcounted {\n   public:\n    explicit Entry(jsg::Value value, size_t size);\n    KJ_DISALLOW_COPY_AND_MOVE(Entry);\n\n    jsg::Value getValue(jsg::Lock& js);\n\n    size_t getSize() const;\n\n    void visitForGc(jsg::GcVisitor& visitor);\n\n    kj::Rc<Entry> clone(jsg::Lock& js);\n\n    JSG_MEMORY_INFO(ValueQueue::Entry) {\n      tracker.trackField(\"value\", value);\n    }\n\n   private:\n    jsg::Value value;\n    size_t size;\n  };\n\n  struct QueueEntry {\n    kj::Rc<Entry> entry;\n    QueueEntry clone(jsg::Lock& js);\n\n    JSG_MEMORY_INFO(ValueQueue::QueueEntry) {\n      tracker.trackFieldWithSize(\"entry\", entry->getSize());\n    }\n  };\n\n  class Consumer final {\n   public:\n    Consumer(ValueQueue& queue, kj::Maybe<ConsumerImpl::StateListener&> stateListener = kj::none);\n    Consumer(QueueImpl& queue, kj::Maybe<ConsumerImpl::StateListener&> stateListener = kj::none);\n    // Used when cloning a consumer whose queue has been destroyed.\n    explicit Consumer(kj::Maybe<ConsumerImpl::StateListener&> stateListener);\n    Consumer(Consumer&&) = delete;\n    Consumer(Consumer&) = delete;\n    Consumer& operator=(Consumer&&) = delete;\n    Consumer& operator=(Consumer&) = delete;\n\n    void cancel(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason);\n\n    void close(jsg::Lock& js);\n\n    bool empty();\n\n    void error(jsg::Lock& js, jsg::Value reason);\n\n    void read(jsg::Lock& js, ReadRequest request);\n\n    // Draining read for optimized pipe-to operations. Drains all currently buffered\n    // data, pumps the controller for synchronously available data, and converts\n    // all values to bytes. Values must be ArrayBuffer, ArrayBufferView, or string;\n    // other types will error the stream.\n    // Rejects if there are pending regular reads (mutual exclusion).\n    // Regular read() will reject if there is a pending draining read.\n    // The maxRead parameter is a soft limit - see ReadableStreamController::drainingRead.\n    jsg::Promise<DrainingReadResult> drainingRead(jsg::Lock& js, size_t maxRead = kj::maxValue);\n\n    void push(jsg::Lock& js, kj::Rc<Entry> entry);\n\n    void reset();\n\n    size_t size();\n\n    kj::Own<Consumer> clone(\n        jsg::Lock& js, kj::Maybe<ConsumerImpl::StateListener&> stateListener = kj::none);\n\n    bool hasReadRequests();\n    bool hasPendingDrainingRead();\n    void cancelPendingReads(jsg::Lock& js, jsg::JsValue reason);\n\n    void visitForGc(jsg::GcVisitor& visitor);\n\n    inline kj::StringPtr jsgGetMemoryName() const;\n    inline size_t jsgGetMemorySelfSize() const;\n    inline void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n   private:\n    ConsumerImpl impl;\n\n    friend class ValueQueue;\n  };\n\n  explicit ValueQueue(size_t highWaterMark);\n\n  void close(jsg::Lock& js);\n\n  ssize_t desiredSize() const;\n\n  void error(jsg::Lock& js, jsg::Value reason);\n\n  void maybeUpdateBackpressure();\n\n  void push(jsg::Lock& js, kj::Rc<Entry> entry);\n\n  size_t size() const;\n\n  size_t getConsumerCount();\n\n  bool wantsRead() const;\n\n  bool hasPartiallyFulfilledRead();\n\n  void visitForGc(jsg::GcVisitor& visitor);\n\n  inline kj::StringPtr jsgGetMemoryName() const;\n  inline size_t jsgGetMemorySelfSize() const;\n  inline void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  QueueImpl impl;\n\n  static void handlePush(\n      jsg::Lock& js, ConsumerImpl::Ready& state, kj::Maybe<QueueImpl&> queue, kj::Rc<Entry> entry);\n  static void handleRead(jsg::Lock& js,\n      ConsumerImpl::Ready& state,\n      ConsumerImpl& consumer,\n      kj::Maybe<QueueImpl&> queue,\n      ReadRequest request);\n  static bool handleMaybeClose(jsg::Lock& js,\n      ConsumerImpl::Ready& state,\n      ConsumerImpl& consumer,\n      kj::Maybe<QueueImpl&> queue);\n\n  friend ConsumerImpl;\n};\n\n// ============================================================================\n// Byte queue\n\nclass ByteQueue final {\n public:\n  using ConsumerImpl = ConsumerImpl<ByteQueue>;\n  using QueueImpl = QueueImpl<ByteQueue>;\n\n  class ByobRequest;\n\n  struct ReadRequest final {\n    enum class Type { DEFAULT, BYOB };\n    jsg::Promise<ReadResult>::Resolver resolver;\n    // The reference here should be cleared when the ByobRequest is invalidated,\n    // which happens either when respond(), respondWithNewView(), or invalidate()\n    // is called, or when the ByobRequest is destroyed, whichever comes first.\n    kj::Maybe<ByobRequest&> byobReadRequest;\n\n    struct PullInto {\n      jsg::BufferSource store;\n      size_t filled = 0;\n      size_t atLeast = 1;\n      Type type = Type::DEFAULT;\n\n      JSG_MEMORY_INFO(ByteQueue::ReadRequest::PullInto) {\n        tracker.trackField(\"store\", store);\n      }\n    } pullInto;\n\n    ReadRequest(jsg::Promise<ReadResult>::Resolver resolver, PullInto pullInto);\n    ReadRequest(ReadRequest&&) = default;\n    ReadRequest& operator=(ReadRequest&&) = default;\n    ~ReadRequest() noexcept(false);\n    void resolveAsDone(jsg::Lock& js);\n    void resolve(jsg::Lock& js);\n    void reject(jsg::Lock& js, jsg::Value& value);\n\n    kj::Own<ByobRequest> makeByobReadRequest(ConsumerImpl& consumer, QueueImpl& queue);\n\n    JSG_MEMORY_INFO(ByteQueue::ReadRequest) {\n      tracker.trackField(\"resolver\", resolver);\n      tracker.trackField(\"pullInto\", pullInto);\n    }\n  };\n\n  // The ByobRequest is essentially a handle to the ByteQueue::ReadRequest that can be given to a\n  // ReadableStreamBYOBRequest object to fulfill the request using the BYOB API pattern.\n  //\n  // When isInvalidated() is false, respond() or respondWithNewView() can be called to fulfill\n  // the BYOB read request. Once either of those are called, or once invalidate() is called,\n  // the ByobRequest is no longer usable and should be discarded.\n  class ByobRequest final {\n   public:\n    ByobRequest(ReadRequest& request, ConsumerImpl& consumer, QueueImpl& queue)\n        : request(request),\n          consumer(consumer),\n          queue(queue) {}\n\n    KJ_DISALLOW_COPY_AND_MOVE(ByobRequest);\n\n    ~ByobRequest() noexcept(false);\n\n    inline ReadRequest& getRequest() {\n      return KJ_ASSERT_NONNULL(request);\n    }\n\n    bool respond(jsg::Lock& js, size_t amount);\n\n    bool respondWithNewView(jsg::Lock& js, jsg::BufferSource view);\n\n    // Disconnects this ByobRequest instance from the associated ByteQueue::ReadRequest.\n    // The term \"invalidate\" is adopted from the streams spec for handling BYOB requests.\n    void invalidate();\n\n    inline bool isInvalidated() const {\n      return request == kj::none;\n    }\n\n    bool isPartiallyFulfilled();\n\n    size_t getAtLeast() const;\n\n    v8::Local<v8::Uint8Array> getView(jsg::Lock& js);\n\n    // Returns the byte length of the original underlying ArrayBuffer.\n    size_t getOriginalBufferByteLength(jsg::Lock& js) const;\n\n    // Returns the byte offset of the original view plus bytes filled.\n    size_t getOriginalByteOffsetPlusBytesFilled() const;\n\n    JSG_MEMORY_INFO(ByteQueue::ByobRequest) {}\n\n   private:\n    kj::Maybe<ReadRequest&> request;\n    ConsumerImpl& consumer;\n    QueueImpl& queue;\n  };\n\n  struct State {\n    // We use a ring buffer for pending BYOB read requests. Since we store kj::Own<ByobRequest>,\n    // the actual ByobRequest objects are heap-allocated and won't be invalidated by buffer growth.\n    workerd::RingBuffer<kj::Own<ByobRequest>, 8> pendingByobReadRequests;\n\n    JSG_MEMORY_INFO(ByteQueue::State) {\n      for (auto& request: pendingByobReadRequests) {\n        tracker.trackField(\"pendingByobReadRequest\", request);\n      }\n    }\n  };\n\n  // A byte queue entry consists of a jsg::BufferSource containing a non-zero-length\n  // sequence of bytes. The size is determined by the number of bytes in the entry.\n  class Entry: public kj::Refcounted {\n   public:\n    explicit Entry(jsg::BufferSource store);\n\n    kj::ArrayPtr<kj::byte> toArrayPtr();\n\n    size_t getSize() const;\n\n    void visitForGc(jsg::GcVisitor& visitor);\n\n    kj::Rc<Entry> clone(jsg::Lock& js);\n\n    JSG_MEMORY_INFO(ByteQueue::Entry) {\n      tracker.trackField(\"store\", store);\n    }\n\n   private:\n    jsg::BufferSource store;\n  };\n\n  struct QueueEntry {\n    kj::Rc<Entry> entry;\n    size_t offset;\n\n    QueueEntry clone(jsg::Lock& js);\n\n    JSG_MEMORY_INFO(ByteQueue::QueueEntry) {\n      tracker.trackFieldWithSize(\"entry\", entry->getSize());\n    }\n  };\n\n  class Consumer {\n   public:\n    Consumer(ByteQueue& queue, kj::Maybe<ConsumerImpl::StateListener&> stateListener = kj::none);\n    Consumer(QueueImpl& queue, kj::Maybe<ConsumerImpl::StateListener&> stateListener = kj::none);\n    // Used when cloning a consumer whose queue has been destroyed.\n    explicit Consumer(kj::Maybe<ConsumerImpl::StateListener&> stateListener);\n    Consumer(Consumer&&) = delete;\n    Consumer(Consumer&) = delete;\n    Consumer& operator=(Consumer&&) = delete;\n    Consumer& operator=(Consumer&) = delete;\n\n    void cancel(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason);\n\n    void close(jsg::Lock& js);\n\n    bool empty() const;\n\n    void error(jsg::Lock& js, jsg::Value reason);\n\n    void read(jsg::Lock& js, ReadRequest request);\n\n    // Draining read for optimized pipe-to operations. Drains all currently buffered\n    // data and pumps the controller for synchronously available data.\n    // Returns bytes directly without conversion (data is already bytes).\n    // Rejects if there are pending regular reads (mutual exclusion).\n    // Regular read() will reject if there is a pending draining read.\n    // The maxRead parameter is a soft limit - see ReadableStreamController::drainingRead.\n    jsg::Promise<DrainingReadResult> drainingRead(jsg::Lock& js, size_t maxRead = kj::maxValue);\n\n    void push(jsg::Lock& js, kj::Rc<Entry> entry);\n\n    void reset();\n\n    size_t size() const;\n\n    kj::Own<Consumer> clone(\n        jsg::Lock& js, kj::Maybe<ConsumerImpl::StateListener&> stateListener = kj::none);\n    bool hasReadRequests();\n    bool hasPendingDrainingRead();\n    void cancelPendingReads(jsg::Lock& js, jsg::JsValue reason);\n\n    void visitForGc(jsg::GcVisitor& visitor);\n\n    inline kj::StringPtr jsgGetMemoryName() const;\n    inline size_t jsgGetMemorySelfSize() const;\n    inline void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n   private:\n    ConsumerImpl impl;\n  };\n\n  explicit ByteQueue(size_t highWaterMark);\n\n  void close(jsg::Lock& js);\n\n  ssize_t desiredSize() const;\n\n  void error(jsg::Lock& js, jsg::Value reason);\n\n  void maybeUpdateBackpressure();\n\n  void push(jsg::Lock& js, kj::Rc<Entry> entry);\n\n  size_t size() const;\n\n  size_t getConsumerCount();\n\n  bool wantsRead() const;\n\n  bool hasPartiallyFulfilledRead();\n\n  // nextPendingByobReadRequest will be used to support the ReadableStreamBYOBRequest interface\n  // that is part of ReadableByteStreamController. When user code calls the `controller.byobRequest`\n  // API on a ReadableByteStreamController, they are going to get an instance of a\n  // ReadableStreamBYOBRequest object. That object will own the `kj::Own<ByobReadRequest>` that\n  // is returned here. User code could end up doing something silly like holding a reference to\n  // that byobRequest long after it has been invalidated. We heap-allocate these just to allow\n  // their lifespan to be attached to the ReadableStreamBYOBRequest object but internally they\n  // will be disconnected as appropriate.\n  kj::Maybe<kj::Own<ByobRequest>> nextPendingByobReadRequest();\n\n  void visitForGc(jsg::GcVisitor& visitor);\n\n  inline kj::StringPtr jsgGetMemoryName() const;\n  inline size_t jsgGetMemorySelfSize() const;\n  inline void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  QueueImpl impl;\n\n  static void handlePush(\n      jsg::Lock& js, ConsumerImpl::Ready& state, kj::Maybe<QueueImpl&> queue, kj::Rc<Entry> entry);\n  static void handleRead(jsg::Lock& js,\n      ConsumerImpl::Ready& state,\n      ConsumerImpl& consumer,\n      kj::Maybe<QueueImpl&> queue,\n      ReadRequest request);\n  static bool handleMaybeClose(jsg::Lock& js,\n      ConsumerImpl::Ready& state,\n      ConsumerImpl& consumer,\n      kj::Maybe<QueueImpl&> queue);\n\n  friend ConsumerImpl;\n  friend class Consumer;\n};\n\ntemplate <typename Self>\nkj::StringPtr QueueImpl<Self>::jsgGetMemoryName() const {\n  return \"QueueImpl\"_kjc;\n}\n\ntemplate <typename Self>\nsize_t QueueImpl<Self>::jsgGetMemorySelfSize() const {\n  return sizeof(QueueImpl<Self>);\n}\n\ntemplate <typename Self>\nvoid QueueImpl<Self>::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n  KJ_IF_SOME(errored, state.tryGetErrorUnsafe()) {\n    tracker.trackField(\"error\", errored.reason);\n  }\n}\n\ntemplate <typename Self>\nkj::StringPtr ConsumerImpl<Self>::jsgGetMemoryName() const {\n  return \"ConsumerImpl\"_kjc;\n}\n\ntemplate <typename Self>\nsize_t ConsumerImpl<Self>::jsgGetMemorySelfSize() const {\n  return sizeof(ConsumerImpl<Self>);\n}\n\ntemplate <typename Self>\nvoid ConsumerImpl<Self>::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n  KJ_IF_SOME(errored, state.tryGetErrorUnsafe()) {\n    tracker.trackField(\"error\", errored.reason);\n  } else KJ_IF_SOME(ready, state.tryGetActiveUnsafe()) {\n    tracker.trackField(\"inner\", ready);\n  }\n}\n\ntemplate <typename Self>\nkj::StringPtr ConsumerImpl<Self>::Ready::jsgGetMemoryName() const {\n  return \"ConsumerImpl::Ready\"_kjc;\n}\n\ntemplate <typename Self>\nsize_t ConsumerImpl<Self>::Ready::jsgGetMemorySelfSize() const {\n  return sizeof(Ready);\n}\n\ntemplate <typename Self>\nvoid ConsumerImpl<Self>::Ready::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n  for (auto& entry: buffer) {\n    KJ_SWITCH_ONEOF(entry) {\n      KJ_CASE_ONEOF(c, Close) {\n        tracker.trackFieldWithSize(\"pendingClose\", sizeof(Close));\n      }\n      KJ_CASE_ONEOF(e, QueueEntry) {\n        tracker.trackField(\"entry\", e);\n      }\n    }\n  }\n\n  for (auto& request: readRequests) {\n    tracker.trackField(\"pendingRead\", *request);\n  }\n}\n\nkj::StringPtr ValueQueue::Consumer::jsgGetMemoryName() const {\n  return \"ValueQueue::Consumer\"_kjc;\n}\n\nsize_t ValueQueue::Consumer::jsgGetMemorySelfSize() const {\n  return sizeof(ValueQueue::Consumer);\n}\n\nvoid ValueQueue::Consumer::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"impl\", impl);\n}\n\nkj::StringPtr ValueQueue::jsgGetMemoryName() const {\n  return \"ValueQueue\"_kjc;\n}\n\nsize_t ValueQueue::jsgGetMemorySelfSize() const {\n  return sizeof(ValueQueue);\n}\n\nvoid ValueQueue::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"impl\", impl);\n}\n\nkj::StringPtr ByteQueue::Consumer::jsgGetMemoryName() const {\n  return \"ByteQueue::Consumer\"_kjc;\n}\n\nsize_t ByteQueue::Consumer::jsgGetMemorySelfSize() const {\n  return sizeof(ByteQueue::Consumer);\n}\n\nvoid ByteQueue::Consumer::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"impl\", impl);\n}\n\nkj::StringPtr ByteQueue::jsgGetMemoryName() const {\n  return \"ByteQueue\"_kjc;\n}\n\nsize_t ByteQueue::jsgGetMemorySelfSize() const {\n  return sizeof(ByteQueue);\n}\n\nvoid ByteQueue::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"impl\", impl);\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/readable-source-adapter-test.c++",
    "content": "#include \"readable-source-adapter.h\"\n#include \"standard.h\"\n#include \"writable-sink.h\"\n\n#include <workerd/api/system-streams.h>\n#include <workerd/jsg/jsg-test.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/tests/test-fixture.h>\n#include <workerd/util/own-util.h>\n#include <workerd/util/stream-utils.h>\n\nnamespace workerd::api::streams {\n\nnamespace {\n\nstruct RecordingSource final: public kj::AsyncInputStream {\n  size_t readCalled = 0;\n\n  kj::Promise<size_t> tryRead(void*, size_t minBytes, size_t maxBytes) override {\n    readCalled++;\n    co_return 0;\n  }\n\n  kj::Maybe<uint64_t> tryGetLength() override {\n    static const uint64_t length = 42;\n    return length;\n  }\n};\n\nstruct NeverDoneSource final: public kj::AsyncInputStream {\n  size_t readCalled = 0;\n\n  kj::Promise<size_t> tryRead(void* ptr, size_t minBytes, size_t maxBytes) override {\n    readCalled++;\n    kj::ArrayPtr<kj::byte> buffer(static_cast<kj::byte*>(ptr), maxBytes);\n    buffer.fill('a');\n    return maxBytes;\n  }\n\n  kj::Maybe<uint64_t> tryGetLength() override {\n    return kj::none;\n  }\n};\n\nstruct MinimalReadSource final: public kj::AsyncInputStream {\n  size_t readCalled = 0;\n\n  kj::Promise<size_t> tryRead(void* ptr, size_t minBytes, size_t maxBytes) override {\n    readCalled++;\n    kj::ArrayPtr<kj::byte> buffer(static_cast<kj::byte*>(ptr), minBytes);\n    buffer.fill('a');\n    return minBytes;\n  }\n\n  kj::Maybe<uint64_t> tryGetLength() override {\n    return kj::none;\n  }\n};\n\nstruct FiniteReadSource final: public kj::AsyncInputStream {\n  size_t readCalled = 0;\n  size_t maxReads;\n\n  FiniteReadSource(size_t maxReads): maxReads(maxReads) {}\n\n  kj::Promise<size_t> tryRead(void* ptr, size_t minBytes, size_t maxBytes) override {\n    if (readCalled >= maxReads) {\n      co_return 0;\n    }\n    readCalled++;\n    kj::ArrayPtr<kj::byte> buffer(static_cast<kj::byte*>(ptr), minBytes);\n    buffer.fill('a');\n    co_return minBytes;\n  }\n\n  kj::Maybe<uint64_t> tryGetLength() override {\n    return kj::none;\n  }\n};\n\n}  // namespace\n\nKJ_TEST(\"Test successful construction with valid ReadableStreamSource\") {\n  TestFixture fixture;\n  RecordingSource source;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    KJ_ASSERT(!adapter->isClosed(), \"Adapter should not be closed upon construction\");\n    KJ_ASSERT(\n        adapter->isCanceled() == kj::none, \"Adapter should not be canceled upon construction\");\n\n    return kj::READY_NOW;\n  });\n}\n\nKJ_TEST(\"Adapter shutdown with no reads\") {\n  TestFixture fixture;\n  RecordingSource source;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    KJ_ASSERT(!adapter->isClosed(), \"Adapter should not be closed upon construction\");\n    KJ_ASSERT(\n        adapter->isCanceled() == kj::none, \"Adapter should not be canceled upon construction\");\n\n    adapter->shutdown(env.js);\n    adapter->shutdown(env.js);  // second call is no-op\n\n    // Read after shutdown should be resolved immediate\n    auto read = adapter->read(env.js,\n        ReadableStreamSourceJsAdapter::ReadOptions{\n          .buffer = jsg::BufferSource(env.js, jsg::BackingStore::alloc<v8::Uint8Array>(env.js, 10)),\n        });\n    KJ_ASSERT(read.getState(env.js) ==\n            jsg::Promise<ReadableStreamSourceJsAdapter::ReadResult>::State::FULFILLED,\n        \"Read after shutdown should be resolved immediately\");\n\n    KJ_ASSERT(adapter->isClosed(), \"Adapter shoud be closed after shutdown()\");\n    KJ_ASSERT(adapter->isCanceled() == kj::none, \"Adapter should not be canceled after shutdown()\");\n\n    return kj::READY_NOW;\n  });\n}\n\nKJ_TEST(\"Adapter cancel with no reads\") {\n  TestFixture fixture;\n  RecordingSource source;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    KJ_ASSERT(!adapter->isClosed(), \"Adapter should not be closed upon construction\");\n    KJ_ASSERT(\n        adapter->isCanceled() == kj::none, \"Adapter should not be canceled upon construction\");\n\n    adapter->cancel(env.js, env.js.error(\"boom\"));\n\n    auto read = adapter->read(env.js,\n        ReadableStreamSourceJsAdapter::ReadOptions{\n          .buffer = jsg::BufferSource(env.js, jsg::BackingStore::alloc<v8::Uint8Array>(env.js, 10)),\n        });\n    KJ_ASSERT(read.getState(env.js) ==\n            jsg::Promise<ReadableStreamSourceJsAdapter::ReadResult>::State::REJECTED,\n        \"Read after shutdown should be rejected immediately\");\n\n    adapter->shutdown(env.js);  // shutdown after cancel is no-op\n\n    KJ_ASSERT(!adapter->isClosed(), \"Adapter shoud be canceled, not closed\");\n    auto& ex = KJ_ASSERT_NONNULL(adapter->isCanceled());\n    KJ_ASSERT(ex.getDescription().contains(\"boom\"),\n        \"Adapter should be in canceled state with provided exception\");\n\n    return kj::READY_NOW;\n  });\n}\n\nKJ_TEST(\"Adapter cancel (kj::Exception) with no reads\") {\n  TestFixture fixture;\n  RecordingSource source;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    KJ_ASSERT(!adapter->isClosed(), \"Adapter should not be closed upon construction\");\n    KJ_ASSERT(\n        adapter->isCanceled() == kj::none, \"Adapter should not be canceled upon construction\");\n\n    adapter->cancel(KJ_EXCEPTION(FAILED, \"boom\"));\n\n    KJ_ASSERT(!adapter->isClosed(), \"Adapter shoud be canceled, not closed\");\n    auto& ex = KJ_ASSERT_NONNULL(adapter->isCanceled());\n    KJ_ASSERT(ex.getDescription().contains(\"boom\"),\n        \"Adapter should be in canceled state with provided exception\");\n\n    return kj::READY_NOW;\n  });\n}\n\nKJ_TEST(\"Adapter with single read (ArrayBuffer)\") {\n  TestFixture fixture;\n  NeverDoneSource source;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    KJ_ASSERT(!adapter->isClosed(), \"Adapter should not be closed upon construction\");\n    KJ_ASSERT(\n        adapter->isCanceled() == kj::none, \"Adapter should not be canceled upon construction\");\n\n    const size_t bufferSize = 10;\n    auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(env.js, bufferSize);\n\n    return env.context\n        .awaitJs(env.js,\n            adapter\n                ->read(env.js,\n                    ReadableStreamSourceJsAdapter::ReadOptions{\n                      .buffer = jsg::BufferSource(env.js, kj::mv(backing)),\n                      .minBytes = 5,\n                    })\n                .then(env.js, [](jsg::Lock& js, auto result) {\n      KJ_ASSERT(!result.done, \"Stream should not be done yet\");\n      KJ_ASSERT(result.buffer.asArrayPtr().size() == 10, \"Read buffer should be full size\");\n      KJ_ASSERT(result.buffer.asArrayPtr() == \"aaaaaaaaaa\"_kjb);\n\n      // BufferSource should be an ArrayBuffer\n      auto handle = result.buffer.getHandle(js);\n      KJ_ASSERT(handle->IsArrayBuffer());\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Adapter with single read (Uint8Array)\") {\n  TestFixture fixture;\n  NeverDoneSource source;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    KJ_ASSERT(!adapter->isClosed(), \"Adapter should not be closed upon construction\");\n    KJ_ASSERT(\n        adapter->isCanceled() == kj::none, \"Adapter should not be canceled upon construction\");\n\n    const size_t bufferSize = 10;\n    auto backing = jsg::BackingStore::alloc<v8::Uint8Array>(env.js, bufferSize);\n\n    return env.context\n        .awaitJs(env.js,\n            adapter\n                ->read(env.js,\n                    ReadableStreamSourceJsAdapter::ReadOptions{\n                      .buffer = jsg::BufferSource(env.js, kj::mv(backing)),\n                      .minBytes = 5,\n                    })\n                .then(env.js, [](jsg::Lock& js, auto result) {\n      KJ_ASSERT(!result.done, \"Stream should not be done yet\");\n      KJ_ASSERT(result.buffer.asArrayPtr().size() == 10, \"Read buffer should be full size\");\n      KJ_ASSERT(result.buffer.asArrayPtr() == \"aaaaaaaaaa\"_kjb);\n\n      // BufferSource should be an ArrayBuffer\n      auto handle = result.buffer.getHandle(js);\n      KJ_ASSERT(handle->IsUint8Array());\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Adapter with single read (Int32Array)\") {\n  TestFixture fixture;\n  NeverDoneSource source;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    KJ_ASSERT(!adapter->isClosed(), \"Adapter should not be closed upon construction\");\n    KJ_ASSERT(\n        adapter->isCanceled() == kj::none, \"Adapter should not be canceled upon construction\");\n\n    const size_t bufferSize = 16;\n    auto backing = jsg::BackingStore::alloc<v8::Int32Array>(env.js, bufferSize);\n\n    return env.context\n        .awaitJs(env.js,\n            adapter\n                ->read(env.js,\n                    ReadableStreamSourceJsAdapter::ReadOptions{\n                      .buffer = jsg::BufferSource(env.js, kj::mv(backing)),\n                      .minBytes = 5,\n                    })\n                .then(env.js, [](jsg::Lock& js, auto result) {\n      KJ_ASSERT(!result.done, \"Stream should not be done yet\");\n      KJ_ASSERT(result.buffer.asArrayPtr().size() == 16, \"Read buffer should be full size\");\n      KJ_ASSERT(result.buffer.asArrayPtr() == \"aaaaaaaaaaaaaaaa\"_kjb);\n\n      // BufferSource should be an ArrayBuffer\n      auto handle = result.buffer.getHandle(js);\n      KJ_ASSERT(handle->IsInt32Array());\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Adapter with single large read (ArrayBuffer)\") {\n  TestFixture fixture;\n  NeverDoneSource source;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    KJ_ASSERT(!adapter->isClosed(), \"Adapter should not be closed upon construction\");\n    KJ_ASSERT(\n        adapter->isCanceled() == kj::none, \"Adapter should not be canceled upon construction\");\n\n    const size_t bufferSize = 16 * 1024;\n    auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(env.js, bufferSize);\n\n    return env.context\n        .awaitJs(env.js,\n            adapter\n                ->read(env.js,\n                    ReadableStreamSourceJsAdapter::ReadOptions{\n                      .buffer = jsg::BufferSource(env.js, kj::mv(backing)),\n                      .minBytes = 5,\n                    })\n                .then(env.js, [](jsg::Lock& js, auto result) {\n      KJ_ASSERT(!result.done, \"Stream should not be done yet\");\n      KJ_ASSERT(result.buffer.asArrayPtr().size() == 16 * 1024, \"Read buffer should be full size\");\n\n      // BufferSource should be an ArrayBuffer\n      auto handle = result.buffer.getHandle(js);\n      KJ_ASSERT(handle->IsArrayBuffer());\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Adapter with single small read (ArrayBuffer)\") {\n  TestFixture fixture;\n  NeverDoneSource source;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    KJ_ASSERT(!adapter->isClosed(), \"Adapter should not be closed upon construction\");\n    KJ_ASSERT(\n        adapter->isCanceled() == kj::none, \"Adapter should not be canceled upon construction\");\n\n    const size_t bufferSize = 1;\n    auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(env.js, bufferSize);\n\n    return env.context\n        .awaitJs(env.js,\n            adapter\n                ->read(env.js,\n                    ReadableStreamSourceJsAdapter::ReadOptions{\n                      .buffer = jsg::BufferSource(env.js, kj::mv(backing)),\n                      .minBytes = 5,\n                    })\n                .then(env.js, [](jsg::Lock& js, auto result) {\n      KJ_ASSERT(!result.done, \"Stream should not be done yet\");\n      KJ_ASSERT(result.buffer.asArrayPtr().size() == 1, \"Read buffer should be full size\");\n\n      // BufferSource should be an ArrayBuffer\n      auto handle = result.buffer.getHandle(js);\n      KJ_ASSERT(handle->IsArrayBuffer());\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Adapter with minimal reads (Uint8Array)\") {\n  TestFixture fixture;\n  MinimalReadSource source;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    KJ_ASSERT(!adapter->isClosed(), \"Adapter should not be closed upon construction\");\n    KJ_ASSERT(\n        adapter->isCanceled() == kj::none, \"Adapter should not be canceled upon construction\");\n\n    const size_t bufferSize = 10;\n    auto backing = jsg::BackingStore::alloc<v8::Uint8Array>(env.js, bufferSize);\n\n    auto promise = adapter\n                       ->read(env.js,\n                           ReadableStreamSourceJsAdapter::ReadOptions{\n                             .buffer = jsg::BufferSource(env.js, kj::mv(backing)),\n                             .minBytes = 3,\n                           })\n                       .then(env.js, [](jsg::Lock& js, auto result) {\n      KJ_ASSERT(!result.done, \"Stream should not be done yet\");\n      KJ_ASSERT(result.buffer.asArrayPtr().size() == 3, \"Read buffer should be three bytes\");\n      KJ_ASSERT(result.buffer.asArrayPtr() == \"aaa\"_kjb);\n\n      // BufferSource should be an ArrayBuffer\n      auto handle = result.buffer.getHandle(js);\n      KJ_ASSERT(handle->IsUint8Array());\n    });\n\n    return env.context.awaitJs(env.js, kj::mv(promise)).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Adapter with minimal reads (Uint32Array)\") {\n  TestFixture fixture;\n  MinimalReadSource source;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    KJ_ASSERT(!adapter->isClosed(), \"Adapter should not be closed upon construction\");\n    KJ_ASSERT(\n        adapter->isCanceled() == kj::none, \"Adapter should not be canceled upon construction\");\n\n    const size_t bufferSize = 16;\n    auto backing = jsg::BackingStore::alloc<v8::Uint32Array>(env.js, bufferSize);\n\n    auto promise = adapter\n                       ->read(env.js,\n                           ReadableStreamSourceJsAdapter::ReadOptions{\n                             .buffer = jsg::BufferSource(env.js, kj::mv(backing)),\n                             .minBytes = 3,  // Impl with round up to 4\n                           })\n                       .then(env.js, [](jsg::Lock& js, auto result) {\n      KJ_ASSERT(!result.done, \"Stream should not be done yet\");\n      KJ_ASSERT(result.buffer.asArrayPtr().size() == 4, \"Read buffer should be four bytes\");\n      KJ_ASSERT(result.buffer.asArrayPtr() == \"aaaa\"_kjb);\n\n      // BufferSource should be an ArrayBuffer\n      auto handle = result.buffer.getHandle(js);\n      KJ_ASSERT(handle->IsUint32Array());\n    });\n\n    return env.context.awaitJs(env.js, kj::mv(promise)).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Adapter with over large min reads (Uint32Array)\") {\n  TestFixture fixture;\n  MinimalReadSource source;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    KJ_ASSERT(!adapter->isClosed(), \"Adapter should not be closed upon construction\");\n    KJ_ASSERT(\n        adapter->isCanceled() == kj::none, \"Adapter should not be canceled upon construction\");\n\n    const size_t bufferSize = 16;\n    auto backing = jsg::BackingStore::alloc<v8::Uint32Array>(env.js, bufferSize);\n\n    auto promise = adapter\n                       ->read(env.js,\n                           ReadableStreamSourceJsAdapter::ReadOptions{\n                             .buffer = jsg::BufferSource(env.js, kj::mv(backing)),\n                             .minBytes = 24,  // Impl with round up to 4\n                           })\n                       .then(env.js, [](jsg::Lock& js, auto result) {\n      KJ_ASSERT(!result.done, \"Stream should not be done yet\");\n      KJ_ASSERT(result.buffer.asArrayPtr().size() == 16, \"Read buffer should be four bytes\");\n      KJ_ASSERT(result.buffer.asArrayPtr() == \"aaaaaaaaaaaaaaaa\"_kjb);\n\n      // BufferSource should be an ArrayBuffer\n      auto handle = result.buffer.getHandle(js);\n      KJ_ASSERT(handle->IsUint32Array());\n    });\n\n    return env.context.awaitJs(env.js, kj::mv(promise)).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Adapter with over large min reads (Uint32Array)\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto source = newReadableSource(newNullInputStream());\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(env.js, env.context, kj::mv(source));\n\n    KJ_ASSERT(!adapter->isClosed(), \"Adapter should not be closed upon construction\");\n    KJ_ASSERT(\n        adapter->isCanceled() == kj::none, \"Adapter should not be canceled upon construction\");\n\n    const size_t bufferSize = 1;\n    auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(env.js, bufferSize);\n\n    auto promise = adapter\n                       ->read(env.js,\n                           ReadableStreamSourceJsAdapter::ReadOptions{\n                             .buffer = jsg::BufferSource(env.js, kj::mv(backing)),\n                           })\n                       .then(env.js, [](jsg::Lock& js, auto result) {\n      KJ_ASSERT(result.done, \"Stream should be done\");\n      KJ_ASSERT(result.buffer.asArrayPtr().size() == 0, \"Read buffer should be 0 bytes\");\n      auto handle = result.buffer.getHandle(js);\n      KJ_ASSERT(handle->IsArrayBuffer());\n    });\n\n    return env.context.awaitJs(env.js, kj::mv(promise)).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Adapter with multiple reads (Uint8Array)\") {\n  TestFixture fixture;\n  NeverDoneSource source;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    KJ_ASSERT(!adapter->isClosed(), \"Adapter should not be closed upon construction\");\n    KJ_ASSERT(\n        adapter->isCanceled() == kj::none, \"Adapter should not be canceled upon construction\");\n\n    const size_t bufferSize = 10;\n\n    auto read1 = adapter->read(env.js,\n        ReadableStreamSourceJsAdapter::ReadOptions{\n          .buffer = jsg::BufferSource(\n              env.js, jsg::BackingStore::alloc<v8::Uint8Array>(env.js, bufferSize)),\n        });\n    auto read2 = adapter->read(env.js,\n        ReadableStreamSourceJsAdapter::ReadOptions{\n          .buffer = jsg::BufferSource(\n              env.js, jsg::BackingStore::alloc<v8::Uint8Array>(env.js, bufferSize)),\n        });\n    auto read3 = adapter->read(env.js,\n        ReadableStreamSourceJsAdapter::ReadOptions{\n          .buffer = jsg::BufferSource(\n              env.js, jsg::BackingStore::alloc<v8::Uint8Array>(env.js, bufferSize)),\n        });\n\n    return env.context\n        .awaitJs(env.js,\n            read1\n                .then(env.js,\n                    [read2 = kj::mv(read2)](jsg::Lock& js, auto result) mutable {\n      KJ_ASSERT(!result.done, \"Stream should not be done yet\");\n      KJ_ASSERT(result.buffer.asArrayPtr().size() == 10, \"Read buffer should be full size\");\n      KJ_ASSERT(result.buffer.asArrayPtr() == \"aaaaaaaaaa\"_kjb);\n      return kj::mv(read2);\n    })\n                .then(env.js, [read3 = kj::mv(read3)](jsg::Lock& js, auto result) mutable {\n      KJ_ASSERT(!result.done, \"Stream should not be done yet\");\n      KJ_ASSERT(result.buffer.asArrayPtr().size() == 10, \"Read buffer should be full size\");\n      KJ_ASSERT(result.buffer.asArrayPtr() == \"aaaaaaaaaa\"_kjb);\n      return kj::mv(read3);\n    }).then(env.js, [](jsg::Lock& js, auto result) mutable {\n      KJ_ASSERT(!result.done, \"Stream should not be done yet\");\n      KJ_ASSERT(result.buffer.asArrayPtr().size() == 10, \"Read buffer should be full size\");\n      KJ_ASSERT(result.buffer.asArrayPtr() == \"aaaaaaaaaa\"_kjb);\n      return js.resolvedPromise();\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Adapter with multiple reads shutdown\") {\n  TestFixture fixture;\n  NeverDoneSource source;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    KJ_ASSERT(!adapter->isClosed(), \"Adapter should not be closed upon construction\");\n    KJ_ASSERT(\n        adapter->isCanceled() == kj::none, \"Adapter should not be canceled upon construction\");\n\n    const size_t bufferSize = 10;\n\n    auto read1 = adapter->read(env.js,\n        ReadableStreamSourceJsAdapter::ReadOptions{\n          .buffer = jsg::BufferSource(\n              env.js, jsg::BackingStore::alloc<v8::Uint8Array>(env.js, bufferSize)),\n        });\n    auto read2 = adapter->read(env.js,\n        ReadableStreamSourceJsAdapter::ReadOptions{\n          .buffer = jsg::BufferSource(\n              env.js, jsg::BackingStore::alloc<v8::Uint8Array>(env.js, bufferSize)),\n        });\n    auto read3 = adapter->read(env.js,\n        ReadableStreamSourceJsAdapter::ReadOptions{\n          .buffer = jsg::BufferSource(\n              env.js, jsg::BackingStore::alloc<v8::Uint8Array>(env.js, bufferSize)),\n        });\n\n    adapter->shutdown(env.js);\n\n    return env.context\n        .awaitJs(env.js,\n            read1\n                .then(env.js,\n                    [](jsg::Lock& js, auto result) {\n      return js.rejectedPromise<ReadableStreamSourceJsAdapter::ReadResult>(\n          js.error(\"Should not have completed read after shutdown\"));\n    },\n                    [read2 = kj::mv(read2)](jsg::Lock& js, jsg::Value exception) mutable\n                    -> jsg::Promise<ReadableStreamSourceJsAdapter::ReadResult> {\n      return kj::mv(read2);\n    })\n                .then(env.js,\n                    [](jsg::Lock& js, auto result) {\n      return js.rejectedPromise<ReadableStreamSourceJsAdapter::ReadResult>(\n          js.error(\"Should not have completed read after shutdown\"));\n    },\n                    [read3 = kj::mv(read3)](jsg::Lock& js, jsg::Value exception) mutable\n                    -> jsg::Promise<ReadableStreamSourceJsAdapter::ReadResult> {\n      return kj::mv(read3);\n    }).then(env.js, [](jsg::Lock& js, auto result) {\n      return js.rejectedPromise<void>(js.error(\"Should not have completed read after shutdown\"));\n    }, [](jsg::Lock& js, jsg::Value exception) mutable -> jsg::Promise<void> {\n      return js.resolvedPromise();\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Adapter with multiple reads cancel\") {\n  TestFixture fixture;\n  NeverDoneSource source;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    KJ_ASSERT(!adapter->isClosed(), \"Adapter should not be closed upon construction\");\n    KJ_ASSERT(\n        adapter->isCanceled() == kj::none, \"Adapter should not be canceled upon construction\");\n\n    const size_t bufferSize = 10;\n\n    auto read1 = adapter->read(env.js,\n        ReadableStreamSourceJsAdapter::ReadOptions{\n          .buffer = jsg::BufferSource(\n              env.js, jsg::BackingStore::alloc<v8::Uint8Array>(env.js, bufferSize)),\n        });\n    auto read2 = adapter->read(env.js,\n        ReadableStreamSourceJsAdapter::ReadOptions{\n          .buffer = jsg::BufferSource(\n              env.js, jsg::BackingStore::alloc<v8::Uint8Array>(env.js, bufferSize)),\n        });\n    auto read3 = adapter->read(env.js,\n        ReadableStreamSourceJsAdapter::ReadOptions{\n          .buffer = jsg::BufferSource(\n              env.js, jsg::BackingStore::alloc<v8::Uint8Array>(env.js, bufferSize)),\n        });\n\n    adapter->cancel(env.js, env.js.error(\"boom\"));\n    adapter->cancel(env.js, env.js.error(\"bang\"));\n\n    return env.context\n        .awaitJs(env.js,\n            read1\n                .then(env.js,\n                    [](jsg::Lock& js, auto result) {\n      return js.rejectedPromise<ReadableStreamSourceJsAdapter::ReadResult>(\n          js.error(\"Should not have completed read after shutdown\"));\n    },\n                    [read2 = kj::mv(read2)](jsg::Lock& js, jsg::Value exception) mutable\n                    -> jsg::Promise<ReadableStreamSourceJsAdapter::ReadResult> {\n      auto handle = exception.getHandle(js);\n      KJ_ASSERT(kj::str(handle).contains(\"boom\"),\n          \"Read should have been rejected with cancelation error\");\n      return kj::mv(read2);\n    })\n                .then(env.js,\n                    [](jsg::Lock& js, auto result) {\n      return js.rejectedPromise<ReadableStreamSourceJsAdapter::ReadResult>(\n          js.error(\"Should not have completed read after shutdown\"));\n    },\n                    [read3 = kj::mv(read3)](jsg::Lock& js, jsg::Value exception) mutable\n                    -> jsg::Promise<ReadableStreamSourceJsAdapter::ReadResult> {\n      auto handle = exception.getHandle(js);\n      KJ_ASSERT(kj::str(handle).contains(\"boom\"),\n          \"Read should have been rejected with cancelation error\");\n      return kj::mv(read3);\n    }).then(env.js, [](jsg::Lock& js, auto result) {\n      return js.rejectedPromise<void>(js.error(\"Should not have completed read after shutdown\"));\n    }, [](jsg::Lock& js, jsg::Value exception) mutable -> jsg::Promise<void> {\n      auto handle = exception.getHandle(js);\n      KJ_ASSERT(kj::str(handle).contains(\"boom\"),\n          \"Read should have been rejected with cancelation error\");\n      return js.resolvedPromise();\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Adapter close after read\") {\n  TestFixture fixture;\n  NeverDoneSource source;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    auto read = adapter->read(env.js,\n        ReadableStreamSourceJsAdapter::ReadOptions{\n          .buffer = jsg::BufferSource(env.js, jsg::BackingStore::alloc<v8::Uint8Array>(env.js, 10)),\n        });\n\n    auto closePromise = adapter->close(env.js);\n\n    return env.context\n        .awaitJs(env.js,\n            closePromise.then(\n                env.js, [&adapter = *adapter, read = kj::mv(read)](jsg::Lock& js) mutable {\n      KJ_ASSERT(adapter.isClosed(), \"Adapter should be closed after close()\");\n      KJ_ASSERT(adapter.isCanceled() == kj::none, \"Adapter should not be canceled after close()\");\n\n      KJ_ASSERT(read.getState(js) ==\n              jsg::Promise<ReadableStreamSourceJsAdapter::ReadResult>::State::FULFILLED,\n          \"Read should have completed successfully before close()\");\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Adapter close\") {\n  TestFixture fixture;\n  NeverDoneSource source;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n    auto closePromise = adapter->close(env.js);\n\n    // reads after close should be resoved immediately.\n    auto read = adapter->read(env.js,\n        ReadableStreamSourceJsAdapter::ReadOptions{\n          .buffer = jsg::BufferSource(env.js, jsg::BackingStore::alloc<v8::Uint8Array>(env.js, 10)),\n        });\n    KJ_ASSERT(read.getState(env.js) ==\n            jsg::Promise<ReadableStreamSourceJsAdapter::ReadResult>::State::FULFILLED,\n        \"Read after close should be fullfilled immediately\");\n\n    return env.context\n        .awaitJs(env.js, closePromise.then(env.js, [&adapter = *adapter](jsg::Lock& js) {\n      KJ_ASSERT(adapter.isClosed(), \"Adapter should be closed after close()\");\n      KJ_ASSERT(adapter.isCanceled() == kj::none, \"Adapter should not be canceled after close()\");\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Adapter close superseded by cancel\") {\n  TestFixture fixture;\n  NeverDoneSource source;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    auto closePromise = adapter->close(env.js);\n\n    adapter->cancel(env.js, env.js.error(\"boom\"));\n\n    return env.context\n        .awaitJs(env.js, closePromise.then(env.js, [](jsg::Lock& js) {\n      return js.rejectedPromise<void>(js.error(\"Should not have completed close after cancel\"));\n    }, [](jsg::Lock& js, jsg::Value exception) {\n      auto handle = exception.getHandle(js);\n      KJ_ASSERT(kj::str(handle).contains(\"boom\"),\n          \"Close should have been rejected with cancelation error\");\n      return js.resolvedPromise();\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"After read BackingStore maintains identity\") {\n  TestFixture fixture;\n  NeverDoneSource source;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    std::unique_ptr<v8::BackingStore> backing =\n        v8::ArrayBuffer::NewBackingStore(env.js.v8Isolate, 10);\n    auto* backingPtr = backing.get();\n    v8::Local<v8::ArrayBuffer> originalArrayBuffer =\n        v8::ArrayBuffer::New(env.js.v8Isolate, kj::mv(backing));\n    jsg::BufferSource source(env.js, originalArrayBuffer);\n\n    return env.context\n        .awaitJs(env.js,\n            adapter\n                ->read(env.js,\n                    ReadableStreamSourceJsAdapter::ReadOptions{\n                      .buffer = jsg::BufferSource(env.js, originalArrayBuffer),\n                      .minBytes = 5,\n                    })\n                .then(env.js, [backingPtr](jsg::Lock& js, auto result) {\n      auto handle = result.buffer.getHandle(js);\n      KJ_ASSERT(handle->IsArrayBuffer());\n      auto backing = handle.template As<v8::ArrayBuffer>()->GetBackingStore();\n      KJ_ASSERT(backing.get() == backingPtr);\n      return js.resolvedPromise();\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Read all text\") {\n  TestFixture fixture;\n  FiniteReadSource source(4);\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    return env.context\n        .awaitJs(env.js,\n            adapter->readAllText(env.js).then(\n                env.js, [&adapter = *adapter](jsg::Lock& js, jsg::JsRef<jsg::JsString> result) {\n      auto str = result.getHandle(js).toString(js);\n      // With exponential growth strategy: 1024 + 2048 + 4096 + 8192 = 15360\n      KJ_ASSERT(str.size() == 15360);\n      KJ_ASSERT(adapter.isClosed(), \"Adapter should be closed after readAllText()\");\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Read all bytes\") {\n  TestFixture fixture;\n  FiniteReadSource source(4);\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    return env.context\n        .awaitJs(env.js,\n            adapter->readAllBytes(env.js).then(\n                env.js, [&adapter = *adapter](jsg::Lock& js, jsg::BufferSource result) {\n      // With exponential growth strategy: 1024 + 2048 + 4096 + 8192 = 15360\n      KJ_ASSERT(result.size() == 15360);\n      KJ_ASSERT(adapter.isClosed(), \"Adapter should be closed after readAllText()\");\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Read all text (limit)\") {\n  TestFixture fixture;\n  FiniteReadSource source(2);\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    return env.context\n        .awaitJs(env.js,\n            adapter->readAllText(env.js, 100)\n                .then(env.js,\n                    [](jsg::Lock& js, jsg::JsRef<jsg::JsString> result) -> jsg::Promise<void> {\n      KJ_FAIL_ASSERT(\"Should not have completed readAllText within limit\");\n    }, [&adapter = *adapter](jsg::Lock& js, jsg::Value exception) {\n      return js.resolvedPromise();\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Read all bytes (limit)\") {\n  TestFixture fixture;\n  FiniteReadSource source(2);\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<kj::AsyncInputStream> fake(&source, kj::NullDisposer::instance);\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(\n        env.js, env.context, newReadableSource(kj::mv(fake)));\n\n    return env.context\n        .awaitJs(env.js,\n            adapter->readAllBytes(env.js, 100)\n                .then(env.js, [](jsg::Lock&, auto) -> jsg::Promise<void> {\n      KJ_FAIL_ASSERT(\"Should not have completed readAllBytes within limit\");\n    }, [&adapter = *adapter](jsg::Lock& js, jsg::Value exception) {\n      return js.resolvedPromise();\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"tryGetLength\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto source = newReadableSource(newNullInputStream());\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(env.js, env.context, kj::mv(source));\n    auto length = KJ_ASSERT_NONNULL(adapter->tryGetLength(StreamEncoding::IDENTITY));\n    KJ_ASSERT(length == 0, \"Length of empty stream should be 0\");\n\n    adapter->shutdown(env.js);\n\n    KJ_ASSERT(adapter->tryGetLength(StreamEncoding::IDENTITY) == kj::none,\n        \"Length after shutdown should be none\");\n\n    return kj::READY_NOW;\n  });\n}\n\nKJ_TEST(\"tee successful\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto dataSource = newMemoryInputStream(\"hello world\"_kjb);\n    auto source = newReadableSource(kj::mv(dataSource));\n    auto adapter = kj::heap<ReadableStreamSourceJsAdapter>(env.js, env.context, kj::mv(source));\n\n    auto [branch1, branch2] = KJ_ASSERT_NONNULL(adapter->tryTee(env.js));\n\n    KJ_ASSERT(adapter->isClosed(), \"Original adapter should be closed after tee\");\n    KJ_ASSERT(\n        adapter->isCanceled() == kj::none, \"Original adapter should not be canceled after tee\");\n\n    KJ_ASSERT(!branch1->isClosed(), \"Branch1 should not be closed after tee\");\n    KJ_ASSERT(branch1->isCanceled() == kj::none, \"Branch1 should not be canceled after tee\");\n\n    KJ_ASSERT(!branch2->isClosed(), \"Branch2 should not be closed after tee\");\n    KJ_ASSERT(branch2->isCanceled() == kj::none, \"Branch2 should not be canceled after tee\");\n\n    auto backing1 = jsg::BackingStore::alloc<v8::ArrayBuffer>(env.js, 11);\n    auto buffer1 = jsg::BufferSource(env.js, kj::mv(backing1));\n    auto read1 = branch1->read(env.js,\n        ReadableStreamSourceJsAdapter::ReadOptions{\n          .buffer = kj::mv(buffer1),\n        });\n    auto backing2 = jsg::BackingStore::alloc<v8::ArrayBuffer>(env.js, 11);\n    auto buffer2 = jsg::BufferSource(env.js, kj::mv(backing2));\n    auto read2 = branch2->read(env.js,\n        ReadableStreamSourceJsAdapter::ReadOptions{\n          .buffer = kj::mv(buffer2),\n        });\n\n    return env.context\n        .awaitJs(env.js,\n            kj::mv(read1)\n                .then(env.js, [read2 = kj::mv(read2)](jsg::Lock& js, auto result1) mutable {\n      KJ_ASSERT(!result1.done, \"Stream should not be done yet\");\n      KJ_ASSERT(result1.buffer.asArrayPtr().size() == 11);\n      KJ_ASSERT(result1.buffer.asArrayPtr() == \"hello world\"_kjb);\n      return kj::mv(read2);\n    }).then(env.js, [](jsg::Lock& js, auto result2) {\n      KJ_ASSERT(!result2.done, \"Stream should not be done yet\");\n      KJ_ASSERT(result2.buffer.asArrayPtr().size() == 11);\n      KJ_ASSERT(result2.buffer.asArrayPtr() == \"hello world\"_kjb);\n      return js.resolvedPromise();\n    })).attach(kj::mv(branch1), kj::mv(branch2));\n  });\n}\n\n// ===========================================================================================\n\nnamespace {\nstatic size_t countStatic = 0;\njsg::Ref<ReadableStream> createFiniteBytesReadableStream(\n    jsg::Lock& js, size_t chunkSize = 1024, size_t* count = nullptr) {\n  if (count == nullptr) {\n    countStatic = 0;\n    count = &countStatic;\n  }\n  return ReadableStream::constructor(js,\n      UnderlyingSource{\n        .pull =\n            [chunkSize, count](jsg::Lock& js, auto controller) {\n    auto c = kj::mv(\n        KJ_ASSERT_NONNULL(controller.template tryGet<jsg::Ref<ReadableStreamDefaultController>>()));\n    auto& counter = *count;\n    if (counter++ < 10) {\n      auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, chunkSize);\n      jsg::BufferSource buffer(js, kj::mv(backing));\n      buffer.asArrayPtr().fill(96 + counter);  // fill with 'a'...'j'\n      c->enqueue(js, buffer.getHandle(js));\n    }\n    if (counter == 10) {\n      c->close(js);\n    }\n    return js.resolvedPromise();\n  },\n        .expectedLength = 10 * chunkSize,\n      },\n      StreamQueuingStrategy{\n        .highWaterMark = 0,\n      });\n}\n\njsg::Ref<ReadableStream> createFiniteByobReadableStream(jsg::Lock& js, size_t chunkSize = 1024) {\n  return ReadableStream::constructor(js,\n      UnderlyingSource{\n        .type = kj::str(\"bytes\"),\n        .pull =\n            [chunkSize](jsg::Lock& js, auto controller) {\n    auto c = kj::mv(\n        KJ_ASSERT_NONNULL(controller.template tryGet<jsg::Ref<ReadableByteStreamController>>()));\n    static int count = 0;\n    if (count++ < 10) {\n      auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, chunkSize);\n      jsg::BufferSource buffer(js, kj::mv(backing));\n      c->enqueue(js, kj::mv(buffer));\n    }\n    if (count == 10) {\n      c->close(js);\n    }\n    return js.resolvedPromise();\n  },\n        .expectedLength = 10 * chunkSize,\n      },\n      kj::none);\n}\n\njsg::Ref<ReadableStream> createErroredStream(jsg::Lock& js) {\n  return ReadableStream::constructor(js,\n      UnderlyingSource{.start =\n                           [](jsg::Lock& js, auto controller) {\n    auto c = kj::mv(\n        KJ_ASSERT_NONNULL(controller.template tryGet<jsg::Ref<ReadableStreamDefaultController>>()));\n    c->error(js, js.error(\"boom\"));\n    return js.resolvedPromise();\n  }},\n      kj::none);\n}\n\njsg::Ref<ReadableStream> createClosedStream(jsg::Lock& js) {\n  return ReadableStream::constructor(js,\n      UnderlyingSource{.start =\n                           [](jsg::Lock& js, auto controller) {\n    auto c = kj::mv(\n        KJ_ASSERT_NONNULL(controller.template tryGet<jsg::Ref<ReadableStreamDefaultController>>()));\n    c->close(js);\n    return js.resolvedPromise();\n  }},\n      kj::none);\n}\n\nstruct RecordingSink final: public kj::AsyncOutputStream {\n  kj::Vector<kj::byte> data;\n\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override {\n    data.addAll(buffer.begin(), buffer.end());\n    co_return;\n  }\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {\n    for (auto piece: pieces) {\n      data.addAll(piece.begin(), piece.end());\n    }\n    co_return;\n  }\n\n  kj::Promise<void> whenWriteDisconnected() override {\n    return kj::NEVER_DONE;\n  }\n};\n\nstruct ErrorSink final: public kj::AsyncOutputStream {\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override {\n    KJ_FAIL_REQUIRE(\"worker_do_not_log; Write failed\");\n    co_return;\n  }\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {\n    KJ_FAIL_REQUIRE(\"worker_do_not_log; Write failed\");\n    co_return;\n  }\n\n  kj::Promise<void> whenWriteDisconnected() override {\n    return kj::NEVER_DONE;\n  }\n};\n}  // namespace\n\nKJ_TEST(\"KjAdapter constructor with valid normal ReadableStream\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n\n  // Constructs and drops without failures\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createFiniteBytesReadableStream(env.js, 16 * 1024);\n    KJ_ASSERT(!stream->isLocked(), \"Stream should not be locked before adapter construction\");\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n    KJ_ASSERT(stream->isLocked(), \"Stream should be locked after adapter construction\");\n\n    // The size is known because we provided expectedLength in the source.\n    KJ_ASSERT(KJ_ASSERT_NONNULL(adapter->tryGetLength(StreamEncoding::IDENTITY)), 16 * 1024);\n\n    // The encoding is always IDENTITY\n    KJ_ASSERT(adapter->getEncoding() == StreamEncoding::IDENTITY);\n\n    // Teeing is unsupported so always throws\n    try {\n      adapter->tee(1);\n    } catch (...) {\n      auto ex = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(ex.getDescription().contains(\"not supported\"));\n    }\n\n    return kj::READY_NOW;\n  });\n}\n\nKJ_TEST(\"KjAdapter constructor with valid byob ReadableStream\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n\n  // Constructs and drops without failures\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createFiniteByobReadableStream(env.js, 16 * 1024);\n    KJ_ASSERT(!stream->isLocked(), \"Stream should not be locked before adapter construction\");\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n    KJ_ASSERT(stream->isLocked(), \"Stream should be locked after adapter construction\");\n\n    // The size is known because we provided expectedLength in the source.\n    KJ_ASSERT(KJ_ASSERT_NONNULL(adapter->tryGetLength(StreamEncoding::IDENTITY)), 16 * 1024);\n\n    // The encoding is always IDENTITY\n    KJ_ASSERT(adapter->getEncoding() == StreamEncoding::IDENTITY);\n\n    return kj::READY_NOW;\n  });\n}\n\nKJ_TEST(\"KjAdapter constructor with valid ReadableStream manual cancel\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n\n  // Constructs and drops without failures\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createFiniteBytesReadableStream(env.js, 16 * 1024);\n    KJ_ASSERT(!stream->isLocked(), \"Stream should not be locked before adapter construction\");\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n    KJ_ASSERT(stream->isLocked(), \"Stream should be locked after adapter construction\");\n\n    adapter->cancel(KJ_EXCEPTION(FAILED, \"Manual cancel\"));\n\n    KJ_ASSERT(stream->isLocked(), \"Stream should remain locked after adapter cancel\");\n\n    KJ_ASSERT(adapter->tryGetLength(StreamEncoding::IDENTITY) == kj::none,\n        \"Length after cancel should be none\");\n\n    return kj::READY_NOW;\n  });\n}\n\nKJ_TEST(\"KjAdapter constructor with locked/disturbed stream fails\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n\n  // Constructs and drops without failures\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createFiniteBytesReadableStream(env.js, 16 * 1024);\n    auto reader = stream->getReader(env.js, kj::none);\n    try {\n      kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n      KJ_FAIL_ASSERT(\"Should not be able to get adapter\");\n    } catch (...) {\n      // Expected.\n      auto ex = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(ex.getDescription().contains(\"ReadableStream is locked\"));\n    }\n\n    auto& r = KJ_ASSERT_NONNULL(reader.tryGet<jsg::Ref<ReadableStreamDefaultReader>>());\n    r->read(env.js);\n    r->releaseLock(env.js);\n    KJ_ASSERT(stream->isDisturbed());\n\n    // Disturbed streams are also fatal, even if not locked.\n    KJ_ASSERT(stream->isDisturbed());\n\n    try {\n      kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n      KJ_FAIL_ASSERT(\"Should not be able to get adapter\");\n    } catch (...) {\n      // Expected.\n      auto ex = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(ex.getDescription().contains(\"ReadableStream is disturbed\"));\n    }\n\n    return kj::READY_NOW;\n  });\n}\n\nKJ_TEST(\"KjAdapter read with valid buffer and byte ranges\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  size_t counter = 0;\n\n  // Constructs and drops without failures\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createFiniteBytesReadableStream(env.js, 1024, &counter);\n    KJ_ASSERT(!stream->isLocked(), \"Stream should not be locked before adapter construction\");\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n    KJ_ASSERT(stream->isLocked(), \"Stream should be locked after adapter construction\");\n\n    auto buffer = kj::heapArray<kj::byte>(2049);\n\n    return adapter->read(buffer, 512)\n        .then([buffer = kj::mv(buffer), &adapter = *adapter](size_t bytesRead) mutable {\n      KJ_ASSERT(bytesRead >= 512 && bytesRead <= buffer.size());\n      KJ_ASSERT(bytesRead == 2048);\n\n      kj::FixedArray<kj::byte, 2048> expected;\n      expected.asPtr().first(1024).fill(97);  // 'a'\n      expected.asPtr().slice(1024).fill(98);  // 'b'\n      KJ_ASSERT(buffer.asPtr().first(bytesRead) == expected.asPtr());\n\n      // Perform another read...\n      return adapter.read(buffer, 1).then([buffer = kj::mv(buffer)](size_t bytesRead) {\n        KJ_ASSERT(bytesRead >= 1 && bytesRead <= buffer.size());\n        KJ_ASSERT(bytesRead == 2048);\n\n        kj::FixedArray<kj::byte, 2048> expected;\n        expected.asPtr().first(1024).fill(99);   // 'c'\n        expected.asPtr().slice(1024).fill(100);  // 'd'\n        KJ_ASSERT(buffer.asPtr().first(bytesRead) == expected.asPtr());\n\n        return kj::READY_NOW;\n      });\n    }).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"KjAdapter read with left over (source provides more than requested)\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  size_t counter = 0;\n\n  // Constructs and drops without failures\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createFiniteBytesReadableStream(env.js, 1024, &counter);\n    KJ_ASSERT(!stream->isLocked(), \"Stream should not be locked before adapter construction\");\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n    KJ_ASSERT(stream->isLocked(), \"Stream should be locked after adapter construction\");\n\n    auto buffer = kj::heapArray<kj::byte>(1000);\n\n    return adapter->read(buffer, 1000)\n        .then([buffer = kj::mv(buffer), &adapter = *adapter](size_t bytesRead) mutable {\n      KJ_ASSERT(bytesRead >= 512 && bytesRead <= buffer.size());\n      KJ_ASSERT(bytesRead == 1000);\n\n      kj::FixedArray<kj::byte, 1000> expected;\n      expected.asPtr().fill(97);  // 'a'\n      KJ_ASSERT(buffer.asPtr().first(bytesRead) == expected.asPtr());\n\n      // Perform another read...\n      return adapter.read(buffer, 1).then([buffer = kj::mv(buffer)](size_t bytesRead) {\n        // The next read should be only for the 24 remaining bytes leftover from the first chunk.\n        KJ_ASSERT(bytesRead >= 1 && bytesRead <= buffer.size());\n        KJ_ASSERT(bytesRead == 24);\n\n        kj::FixedArray<kj::byte, 24> expected;\n        expected.asPtr().fill(97);  // 'a'\n        KJ_ASSERT(buffer.asPtr().first(bytesRead) == expected.asPtr());\n\n        return kj::READY_NOW;\n      });\n    }).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"KjAdapter read with clamped minBytes (minBytes=0)\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  size_t counter = 0;\n\n  // Constructs and drops without failures\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createFiniteBytesReadableStream(env.js, 5, &counter);\n    KJ_ASSERT(!stream->isLocked(), \"Stream should not be locked before adapter construction\");\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n    KJ_ASSERT(stream->isLocked(), \"Stream should be locked after adapter construction\");\n\n    auto buffer = kj::heapArray<kj::byte>(3);\n\n    return adapter->read(buffer, 0)\n        .then([buffer = kj::mv(buffer), &adapter = *adapter](size_t bytesRead) mutable {\n      // Should return exactly 1 byte, since minBytes is clamped to 1.\n      KJ_ASSERT(bytesRead >= 1);\n    }).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"KjAdapter read with clamped minBytes (minBytes > maxBytes)\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  size_t counter = 0;\n\n  // Constructs and drops without failures\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createFiniteBytesReadableStream(env.js, 5, &counter);\n    KJ_ASSERT(!stream->isLocked(), \"Stream should not be locked before adapter construction\");\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n    KJ_ASSERT(stream->isLocked(), \"Stream should be locked after adapter construction\");\n\n    auto buffer = kj::heapArray<kj::byte>(3);\n\n    return adapter->read(buffer, 4)\n        .then([buffer = kj::mv(buffer), &adapter = *adapter](size_t bytesRead) mutable {\n      // Should return exactly 3 byte, since minBytes is clamped to 3.\n      KJ_ASSERT(bytesRead == 3);\n    }).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"KjAdapter read with zero length buffer\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  size_t counter = 0;\n\n  // Constructs and drops without failures\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createFiniteBytesReadableStream(env.js, 5, &counter);\n    KJ_ASSERT(!stream->isLocked(), \"Stream should not be locked before adapter construction\");\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n    KJ_ASSERT(stream->isLocked(), \"Stream should be locked after adapter construction\");\n\n    auto buffer = kj::heapArray<kj::byte>(0);\n\n    return adapter->read(buffer, 1)\n        .then([buffer = kj::mv(buffer), &adapter = *adapter](size_t bytesRead) mutable {\n      // Should return exactly 0 byte\n      KJ_ASSERT(bytesRead == 0);\n    }).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"KjAdapter forbid concurrent reads\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  size_t counter = 0;\n\n  // Constructs and drops without failures\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createFiniteBytesReadableStream(env.js, 5, &counter);\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n\n    auto buffer = kj::heapArray<kj::byte>(2);\n\n    // Concurrent reads are not allowed.\n    auto read1 = adapter->read(buffer, 1);\n\n    try {\n      auto read2 KJ_UNUSED = adapter->read(buffer, 1);\n    } catch (...) {\n      auto ex = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(ex.getDescription().contains(\"Cannot have multiple concurrent reads\"));\n    }\n\n    return kj::READY_NOW;\n  });\n}\n\nKJ_TEST(\"KjAdapter cancel in-flight reads\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  size_t counter = 0;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createFiniteBytesReadableStream(env.js, 5, &counter);\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n\n    auto buffer = kj::heapArray<kj::byte>(2);\n\n    // Concurrent reads are not allowed.\n    auto read1 = adapter->read(buffer, 1);\n\n    adapter->cancel(KJ_EXCEPTION(FAILED, \"worker_do_not_log; Manual cancel\"));\n\n    return read1\n        .then([](size_t) { KJ_FAIL_ASSERT(\"Should not have completed read after cancel\"); },\n            [](kj::Exception exception) {\n      KJ_ASSERT(exception.getDescription().contains(\"Manual cancel\"));\n    }).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"KjAdapter read errored stream\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createErroredStream(env.js);\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n\n    auto buffer = kj::heapArray<kj::byte>(2);\n\n    // Concurrent reads are not allowed.\n    auto read1 = adapter->read(buffer, 1);\n\n    return read1\n        .then([](size_t) { KJ_FAIL_ASSERT(\"Should not have completed read after cancel\"); },\n            [&adapter = *adapter](kj::Exception exception) {\n      KJ_ASSERT(exception.getDescription().contains(\"boom\"));\n    })\n        .then([&adapter = *adapter]() {\n      // The adapter should be in the errored state now.\n      kj::FixedArray<kj::byte, 1> buf;\n      return adapter.read(buf, 1).then([](auto) {\n        KJ_FAIL_ASSERT(\"Should not have completed read on errored adapter\");\n      }, [](kj::Exception exception) { KJ_ASSERT(exception.getDescription().contains(\"boom\")); });\n    }).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"KjAdapter read closed stream\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createClosedStream(env.js);\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n\n    auto buffer = kj::heapArray<kj::byte>(2);\n\n    auto read1 = adapter->read(buffer, 1);\n\n    return read1.then([](size_t size) { KJ_ASSERT(size == 0); }).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"KjAdapter pumpTo\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  RecordingSink sink;\n  kj::Own<kj::AsyncOutputStream> fakeOwn(&sink, kj::NullDisposer::instance);\n  auto writableSink = newWritableSink(kj::mv(fakeOwn));\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    // The assertion in pumpToImpl requires totalRead <= buffer.size() after reading,\n    // which means the stream size must be <= bufferSize / 2. With bufferSize = 1024\n    // (for streams < 4096 bytes), we need total size <= 512 bytes.\n    auto stream = createFiniteBytesReadableStream(env.js, 50);\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n\n    return adapter->pumpTo(*writableSink, EndAfterPump::YES).attach(kj::mv(adapter));\n  });\n\n  kj::FixedArray<kj::byte, 10 * 50> expected;\n  expected.asPtr().first(50).fill(97);         // 'a'\n  expected.asPtr().slice(50, 100).fill(98);    // 'b'\n  expected.asPtr().slice(100, 150).fill(99);   // 'c'\n  expected.asPtr().slice(150, 200).fill(100);  // 'd'\n  expected.asPtr().slice(200, 250).fill(101);  // 'e'\n  expected.asPtr().slice(250, 300).fill(102);  // 'f'\n  expected.asPtr().slice(300, 350).fill(103);  // 'g'\n  expected.asPtr().slice(350, 400).fill(104);  // 'h'\n  expected.asPtr().slice(400, 450).fill(105);  // 'i'\n  expected.asPtr().slice(450, 500).fill(106);  // 'j'\n\n  KJ_ASSERT(sink.data.size() == 10 * 50);\n  KJ_ASSERT(sink.data.asPtr() == expected.asPtr());\n}\n\nKJ_TEST(\"KjAdapter pumpTo (no end)\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  RecordingSink sink;\n  kj::Own<kj::AsyncOutputStream> fakeOwn(&sink, kj::NullDisposer::instance);\n  auto writableSink = newWritableSink(kj::mv(fakeOwn));\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    // Use the same size constraint as the previous test.\n    auto stream = createFiniteBytesReadableStream(env.js, 50);\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n\n    return adapter->pumpTo(*writableSink, EndAfterPump::NO).attach(kj::mv(adapter));\n  });\n\n  kj::FixedArray<kj::byte, 10 * 50> expected;\n  expected.asPtr().first(50).fill(97);         // 'a'\n  expected.asPtr().slice(50, 100).fill(98);    // 'b'\n  expected.asPtr().slice(100, 150).fill(99);   // 'c'\n  expected.asPtr().slice(150, 200).fill(100);  // 'd'\n  expected.asPtr().slice(200, 250).fill(101);  // 'e'\n  expected.asPtr().slice(250, 300).fill(102);  // 'f'\n  expected.asPtr().slice(300, 350).fill(103);  // 'g'\n  expected.asPtr().slice(350, 400).fill(104);  // 'h'\n  expected.asPtr().slice(400, 450).fill(105);  // 'i'\n  expected.asPtr().slice(450, 500).fill(106);  // 'j'\n\n  KJ_ASSERT(sink.data.size() == 10 * 50);\n  KJ_ASSERT(sink.data.asPtr() == expected.asPtr());\n}\n\nKJ_TEST(\"KjAdapter pumpTo (errored)\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  RecordingSink sink;\n  kj::Own<kj::AsyncOutputStream> fakeOwn(&sink, kj::NullDisposer::instance);\n  auto writableSink = newWritableSink(kj::mv(fakeOwn));\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createErroredStream(env.js);\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n\n    return env.context.waitForDeferredProxy(adapter->pumpTo(*writableSink, EndAfterPump::NO))\n        .then([]() -> kj::Promise<void> {\n      KJ_FAIL_ASSERT(\"Should not have completed pumpTo on errored stream\");\n    }, [](kj::Exception exception) {\n    }).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"KjAdapter pumpTo (error sink)\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  ErrorSink sink;\n  kj::Own<kj::AsyncOutputStream> fakeOwn(&sink, kj::NullDisposer::instance);\n  auto writableSink = newWritableSink(kj::mv(fakeOwn));\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createFiniteBytesReadableStream(env.js, 1000);\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n\n    return env.context.waitForDeferredProxy(adapter->pumpTo(*writableSink, EndAfterPump::NO))\n        .then([]() -> kj::Promise<void> {\n      KJ_FAIL_ASSERT(\"Should not have completed pumpTo on errored stream\");\n    }, [](kj::Exception exception) {\n      KJ_ASSERT(exception.getDescription().contains(\"Write failed\"));\n    }).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"KjAdapter MinReadPolicy IMMEDIATE behavior\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  size_t counter = 0;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    // Create a stream that returns data in small chunks to test the policy difference\n    auto stream = ReadableStream::constructor(env.js,\n        UnderlyingSource{\n          .pull =\n              [&counter](jsg::Lock& js, auto controller) {\n      auto& c = KJ_ASSERT_NONNULL(\n          controller.template tryGet<jsg::Ref<ReadableStreamDefaultController>>());\n      if (counter < 8) {\n        // Return 256 bytes per chunk, 8 chunks total (2048 bytes)\n        auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, 256);\n        jsg::BufferSource buffer(js, kj::mv(backing));\n        buffer.asArrayPtr().fill(97 + counter);  // 'a', 'b', 'c', etc.\n        c->enqueue(js, buffer.getHandle(js));\n        counter++;\n      } else {\n        c->close(js);\n      }\n      return js.resolvedPromise();\n    },\n          .expectedLength = 2048,\n        },\n        StreamQueuingStrategy{.highWaterMark = 0});\n\n    // Test IMMEDIATE policy - should return as soon as minBytes is satisfied\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef(),\n        ReadableSourceKjAdapter::Options{\n          .minReadPolicy = ReadableSourceKjAdapter::MinReadPolicy::IMMEDIATE});\n\n    auto buffer = kj::heapArray<kj::byte>(2048);\n\n    return adapter->read(buffer, 512)\n        .then([buffer = kj::mv(buffer)](size_t bytesRead) {\n      // With IMMEDIATE policy, should return as soon as minBytes (512) is satisfied\n      KJ_ASSERT(bytesRead == 512, \"Should have read exactly minBytes\");\n\n      // Verify the data content matches expected pattern\n      for (size_t i = 0; i < bytesRead; i++) {\n        size_t chunkIndex = i / 256;\n        KJ_ASSERT(buffer[i] == static_cast<kj::byte>(97 + chunkIndex),\n            \"Data should match expected pattern\");\n      }\n\n      return kj::READY_NOW;\n    }).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"KjAdapter MinReadPolicy OPPORTUNISTIC behavior\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  size_t counter = 0;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    // Create a stream that returns data in small chunks to test the policy difference\n    auto stream = ReadableStream::constructor(env.js,\n        UnderlyingSource{\n          .pull =\n              [&counter](jsg::Lock& js, auto controller) {\n      auto c = kj::mv(KJ_ASSERT_NONNULL(\n          controller.template tryGet<jsg::Ref<ReadableStreamDefaultController>>()));\n\n      if (counter < 8) {\n        // Return 256 bytes per chunk, 8 chunks total (2048 bytes)\n        auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, 256);\n        jsg::BufferSource buffer(js, kj::mv(backing));\n        buffer.asArrayPtr().fill(97 + counter);  // 'a', 'b', 'c', etc.\n        c->enqueue(js, buffer.getHandle(js));\n        counter++;\n      } else {\n        c->close(js);\n      }\n      return js.resolvedPromise();\n    },\n          .expectedLength = 2048,\n        },\n        StreamQueuingStrategy{.highWaterMark = 0});\n\n    // Test OPPORTUNISTIC policy - should try to fill buffer more completely\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef(),\n        ReadableSourceKjAdapter::Options{\n          .minReadPolicy = ReadableSourceKjAdapter::MinReadPolicy::OPPORTUNISTIC});\n\n    auto buffer = kj::heapArray<kj::byte>(2048);\n\n    return adapter->read(buffer, 512)\n        .then([buffer = kj::mv(buffer)](size_t bytesRead) {\n      // With OPPORTUNISTIC policy, should try to fill buffer more completely\n      // when data is readily available\n      KJ_ASSERT(bytesRead == 1792, \"Should have read as much as possible up to maxBytes\");\n\n      // Verify the data content matches expected pattern\n      for (size_t i = 0; i < bytesRead; i++) {\n        size_t chunkIndex = i / 256;\n        KJ_ASSERT(buffer[i] == static_cast<kj::byte>(97 + chunkIndex),\n            \"Data should match expected pattern\");\n      }\n\n      return kj::READY_NOW;\n    }).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"KjAdapter readAllBytes\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) -> kj::Promise<void> {\n    auto stream = createFiniteBytesReadableStream(env.js, 1024);\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n    auto bytes = co_await adapter->readAllBytes(kj::maxValue).attach(kj::mv(adapter));\n    kj::FixedArray<kj::byte, 10 * 1024> expected;\n    expected.asPtr().first(1024).fill(97);          // 'a'\n    expected.asPtr().slice(1024, 2048).fill(98);    // 'b'\n    expected.asPtr().slice(2048, 3072).fill(99);    // 'c'\n    expected.asPtr().slice(3072, 4096).fill(100);   // 'd'\n    expected.asPtr().slice(4096, 5120).fill(101);   // 'e'\n    expected.asPtr().slice(5120, 6144).fill(102);   // 'f'\n    expected.asPtr().slice(6144, 7168).fill(103);   // 'g'\n    expected.asPtr().slice(7168, 8192).fill(104);   // 'h'\n    expected.asPtr().slice(8192, 9216).fill(105);   // 'i'\n    expected.asPtr().slice(9216, 10240).fill(106);  // 'j'\n\n    KJ_ASSERT(bytes.size() == 10 * 1024);\n    KJ_ASSERT(bytes == expected);\n  });\n}\n\nKJ_TEST(\"KjAdapter readAllBytes (limit exceeded)\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) -> kj::Promise<void> {\n    auto stream = createFiniteBytesReadableStream(env.js, 1024);\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n    try {\n      co_await adapter->readAllBytes(100).attach(kj::mv(adapter));\n      KJ_FAIL_ASSERT(\"should have failed\");\n    } catch (...) {\n      auto ex = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(ex.getDescription().contains(\"would be exceeded\"));\n    }\n  });\n}\n\nKJ_TEST(\"KjAdapter readAllText\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) -> kj::Promise<void> {\n    auto stream = createFiniteBytesReadableStream(env.js, 2048);\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n\n    auto text = co_await adapter->readAllText(kj::maxValue).attach(kj::mv(adapter));\n    kj::FixedArray<char, 10 * 2048> expected;\n    expected.asPtr().first(2048).fill(97);           // 'a'\n    expected.asPtr().slice(2048, 4096).fill(98);     // 'b'\n    expected.asPtr().slice(4096, 6144).fill(99);     // 'c'\n    expected.asPtr().slice(6144, 8192).fill(100);    // 'd'\n    expected.asPtr().slice(8192, 10240).fill(101);   // 'e'\n    expected.asPtr().slice(10240, 12288).fill(102);  // 'f'\n    expected.asPtr().slice(12288, 14336).fill(103);  // 'g'\n    expected.asPtr().slice(14336, 16384).fill(104);  // 'h'\n    expected.asPtr().slice(16384, 18432).fill(105);  // 'i'\n    expected.asPtr().slice(18432, 20480).fill(106);  // 'j'\n\n    KJ_ASSERT(text.size() == 10 * 2048);\n    KJ_ASSERT(text == expected.asPtr());\n  });\n}\n\nKJ_TEST(\"KjAdapter readAllText (limit exceeded)\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) -> kj::Promise<void> {\n    auto stream = createFiniteBytesReadableStream(env.js, 1024);\n    auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n    try {\n      co_await adapter->readAllText(100).attach(kj::mv(adapter));\n      KJ_FAIL_ASSERT(\"should have failed\");\n    } catch (...) {\n      auto ex = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(ex.getDescription().contains(\"would be exceeded\"));\n    }\n  });\n}\n\n}  // namespace workerd::api::streams\n"
  },
  {
    "path": "src/workerd/api/streams/readable-source-adapter.c++",
    "content": "#include \"readable-source-adapter.h\"\n\n#include \"writable-sink.h\"\n\n#include <workerd/util/checked-queue.h>\n\n#include <bit>\n\nnamespace workerd::api::streams {\n\nnamespace {\n// Per the ReadableStream spec, when a read(buf) is performed on a BYOB reader,\n// if the stream is already closed, we still need to return the allocated buffer\n// back to the caller, but it must be in a zero-length view. This utility function\n// does that. It takes the original allocation and wraps it into a new ArrayBuffer\n// instance that is wrapped by a zero-length view of the same type as the original\n// TypedArray we were given.\njsg::BufferSource transferToEmptyBuffer(jsg::Lock& js, jsg::BufferSource buffer) {\n  KJ_DASSERT(!buffer.isDetached() && buffer.canDetach(js));\n  auto backing = buffer.detach(js);\n  backing.limit(0);\n  auto buf = jsg::BufferSource(js, kj::mv(backing));\n  KJ_DASSERT(buf.size() == 0);\n  return kj::mv(buf);\n}\n}  // namespace\n\n// The Active state maintains a queue of tasks, such as read or close operations. Each task\n// contains a promise-returning function object and a fulfiller. When the first task is\n// enqueued, the active state begins processing the queue asynchronously. Each function\n// is invoked in order, its promise awaited, and the result passed to the fulfiller. The\n// fulfiller notifies the code which enqueued the task that the task has completed. In\n// this way, read and close operations are safely executed in serial, even if one operation\n// is called before the previous completes. This mechanism satisfies KJ's restriction on\n// concurrent operations on streams.\nstruct ReadableStreamSourceJsAdapter::Active {\n  struct Task {\n    kj::Function<kj::Promise<size_t>()> task;\n    kj::Own<kj::PromiseFulfiller<size_t>> fulfiller;\n    Task(kj::Function<kj::Promise<size_t>()> task, kj::Own<kj::PromiseFulfiller<size_t>> fulfiller)\n        : task(kj::mv(task)),\n          fulfiller(kj::mv(fulfiller)) {}\n    KJ_DISALLOW_COPY_AND_MOVE(Task);\n  };\n  using TaskQueue = workerd::util::Queue<kj::Own<Task>>;\n\n  kj::Own<ReadableSource> source;\n  kj::Canceler canceler;\n  TaskQueue queue;\n  bool canceled = false;\n  bool running = false;\n  bool closePending = false;\n  kj::Maybe<kj::Exception> pendingCancel;\n\n  Active(kj::Own<ReadableSource> source): source(kj::mv(source)) {}\n  KJ_DISALLOW_COPY_AND_MOVE(Active);\n  ~Active() noexcept(false) {\n    // When the Active is dropped, we cancel any remaining pending reads and\n    // abort the sink.\n    cancel(KJ_EXCEPTION(DISCONNECTED, \"Writable stream is canceled or closed.\"));\n\n    // Check invariants for safety.\n    // 1. Our canceler should be empty because we canceled it.\n    KJ_DASSERT(canceler.isEmpty());\n    // 2. The write queue should be empty.\n    KJ_DASSERT(queue.empty());\n  }\n\n  // Explicitly cancel all in-flight and pending tasks in the queue.\n  // This is a non-op if cancel has already been called.\n  void cancel(kj::Exception&& exception) {\n    if (canceled) return;\n    canceled = true;\n    // 1. Cancel our in-flight \"runLoop\", if any.\n    pendingCancel = kj::cp(exception);\n    canceler.cancel(kj::cp(exception));\n    // 2. Drop our queue of pending tasks.\n    queue.drainTo(\n        [&exception](kj::Own<Task>&& task) { task->fulfiller->reject(kj::cp(exception)); });\n    // 3. Cancel and drop the source itself. We're done with it.\n    if (exception.getType() != kj::Exception::Type::DISCONNECTED) {\n      source->cancel(kj::mv(exception));\n    }\n    auto dropped KJ_UNUSED = kj::mv(source);\n  }\n\n  kj::Promise<size_t> enqueue(kj::Function<kj::Promise<size_t>()> task) {\n    KJ_DASSERT(!canceled, \"cannot enqueue tasks on a canceled queue\");\n    auto paf = kj::newPromiseAndFulfiller<size_t>();\n    queue.push(kj::heap<Task>(kj::mv(task), kj::mv(paf.fulfiller)));\n    if (!running) {\n      IoContext::current().addTask(canceler.wrap(run()));\n    }\n    return kj::mv(paf.promise);\n  }\n\n  kj::Promise<void> run() {\n    KJ_DEFER(running = false);\n    running = true;\n    while (!queue.empty() && !canceled) {\n      auto task = KJ_ASSERT_NONNULL(queue.pop());\n      KJ_DEFER({\n        if (task->fulfiller->isWaiting()) {\n          KJ_IF_SOME(pending, pendingCancel) {\n            task->fulfiller->reject(kj::mv(pending));\n          } else {\n            task->fulfiller->reject(KJ_EXCEPTION(DISCONNECTED, \"Task was canceled.\"));\n          }\n        }\n      });\n      bool taskFailed = false;\n      try {\n        task->fulfiller->fulfill(co_await task->task());\n      } catch (...) {\n        auto ex = kj::getCaughtExceptionAsKj();\n        task->fulfiller->reject(kj::mv(ex));\n        taskFailed = true;\n      }\n      // If the task failed, we exit the loop. We're going to abort the\n      // entire remaining queue anyway so there's no point in continuing.\n      if (taskFailed) co_return;\n    }\n  }\n};\n\nReadableStreamSourceJsAdapter::ReadableStreamSourceJsAdapter(\n    jsg::Lock& js, IoContext& ioContext, kj::Own<ReadableSource> source)\n    : state(State::create<Open>(ioContext.addObject(kj::heap<Active>(kj::mv(source))))),\n      selfRef(kj::rc<WeakRef<ReadableStreamSourceJsAdapter>>(\n          kj::Badge<ReadableStreamSourceJsAdapter>{}, *this)) {}\n\nReadableStreamSourceJsAdapter::~ReadableStreamSourceJsAdapter() noexcept(false) {\n  selfRef->invalidate();\n}\n\nvoid ReadableStreamSourceJsAdapter::cancel(kj::Exception exception) {\n  KJ_IF_SOME(open, state.tryGetActiveUnsafe()) {\n    open.active->cancel(kj::cp(exception));\n  }\n  state.forceTransitionTo<kj::Exception>(kj::mv(exception));\n}\n\nvoid ReadableStreamSourceJsAdapter::cancel(jsg::Lock& js, const jsg::JsValue& reason) {\n  cancel(js.exceptionToKj(reason));\n}\n\nvoid ReadableStreamSourceJsAdapter::shutdown(jsg::Lock& js) {\n  KJ_IF_SOME(open, state.tryGetActiveUnsafe()) {\n    open.active->cancel(KJ_EXCEPTION(DISCONNECTED, \"Stream was shut down.\"));\n    state.transitionTo<Closed>();\n  }\n  // If we are are already closed or canceled, this is a no-op.\n}\n\nbool ReadableStreamSourceJsAdapter::isClosed() {\n  return state.is<Closed>();\n}\n\nkj::Maybe<const kj::Exception&> ReadableStreamSourceJsAdapter::isCanceled() {\n  return state.tryGetErrorUnsafe();\n}\n\njsg::Promise<ReadableStreamSourceJsAdapter::ReadResult> ReadableStreamSourceJsAdapter::read(\n    jsg::Lock& js, ReadOptions options) {\n  KJ_IF_SOME(exception, state.tryGetErrorUnsafe()) {\n    // Really should not have been called if errored but just in case,\n    // return a rejected promise.\n    return js.rejectedPromise<ReadResult>(js.exceptionToJs(kj::cp(exception)));\n  }\n\n  if (state.is<Closed>()) {\n    // We are already in a closed state. This is a no-op, just return\n    // an empty buffer.\n    return js.resolvedPromise(ReadResult{\n      .buffer = transferToEmptyBuffer(js, kj::mv(options.buffer)),\n      .done = true,\n    });\n  }\n\n  auto& open = state.requireActiveUnsafe();\n  // Deference the IoOwn once to get the active state.\n  Active& active = *open.active;\n\n  // If close is pending, we cannot accept any more reads.\n  // Treat them as if the stream is closed.\n  if (active.closePending) {\n    return js.resolvedPromise(ReadResult{\n      .buffer = transferToEmptyBuffer(js, kj::mv(options.buffer)),\n      .done = true,\n    });\n  }\n\n  // Ok, we are in a readable state, there are no pending closes.\n  // Let's enqueue our read request.\n  auto& ioContext = IoContext::current();\n\n  auto buffer = kj::mv(options.buffer);\n  auto elementSize = buffer.getElementSize();\n\n  // The buffer size should always be a multiple of the element size and should\n  // always be at least as large as minBytes. This should be handled for us by\n  // the jsg::BufferSource, but just to be safe, we will double-check with a\n  // debug assert here.\n  KJ_DASSERT(buffer.size() % elementSize == 0);\n\n  auto minBytes = kj::min(options.minBytes.orDefault(elementSize), buffer.size());\n  // We want to be sure that minBytes is a multiple of the element size\n  // of the buffer, otherwise we might never be able to satisfy the request\n  // correcty. If the caller provided a minBytes, and it is not a multiple\n  // of the element size, we will round it up to the next multiple.\n  if (elementSize > 1) {\n    minBytes = minBytes + (elementSize - (minBytes % elementSize)) % elementSize;\n  }\n\n  // Note: We do not enforce that the source must provide at least minBytes\n  // if available here as that is part of the contract of the source itself.\n  // We will simply pass minBytes along to the source and it is up to the\n  // source to honor it. We do, however, enforce that the source must\n  // never return more than the size of the buffer we provided.\n\n  // We only pass a kj::ArrayPtr to the buffer into the read call, keeping\n  // the actual buffer instance alive by attaching it to the JS promise\n  // chain that follows the read in order to keep it alive.\n  auto promise = active.enqueue(kj::coCapture(\n      [&active, buffer = buffer.asArrayPtr(), minBytes]() mutable -> kj::Promise<size_t> {\n    // TODO(soon): The underlying kj streams API now supports passing the\n    // kj::ArrayPtr directly to the read call, but ReadableStreamSource has\n    // not yet been updated to do so. When it is, we can update this read to\n    // pass `buffer` directly rather than passing the begin() and size().\n    co_return co_await active.source->read(buffer, minBytes);\n  }));\n  return ioContext\n      .awaitIo(js, kj::mv(promise),\n          [buffer = kj::mv(buffer), self = selfRef.addRef()](jsg::Lock& js,\n              size_t bytesRead) mutable -> jsg::Promise<ReadableStreamSourceJsAdapter::ReadResult> {\n    // If the bytesRead is 0, that indicates the stream is closed. We will\n    // move the stream to a closed state and return the empty buffer.\n    if (bytesRead == 0) {\n      self->runIfAlive([](ReadableStreamSourceJsAdapter& self) {\n        KJ_IF_SOME(open, self.state.tryGetActiveUnsafe()) {\n          open.active->closePending = true;\n        }\n      });\n      return js.resolvedPromise(ReadResult{\n        .buffer = transferToEmptyBuffer(js, kj::mv(buffer)),\n        .done = true,\n      });\n    }\n    KJ_DASSERT(bytesRead <= buffer.size());\n\n    // If bytesRead is not a multiple of the element size, that indicates\n    // that the source either read less than minBytes (and ended), or is\n    // simply unable to satisfy the element size requirement. We cannot\n    // provide a partial element to the caller, so reject the read.\n    if (bytesRead % buffer.getElementSize() != 0) {\n      return js.rejectedPromise<ReadResult>(\n          js.typeError(kj::str(\"The underlying stream failed to provide a multiple of the \"\n                               \"target element size \",\n              buffer.getElementSize())));\n    }\n\n    auto backing = buffer.detach(js);\n    backing.limit(bytesRead);\n    return js.resolvedPromise(ReadResult{\n      .buffer = jsg::BufferSource(js, kj::mv(backing)),\n      .done = false,\n    });\n  })\n      .catch_(js,\n          [self = selfRef.addRef()](\n              jsg::Lock& js, jsg::Value exception) -> ReadableStreamSourceJsAdapter::ReadResult {\n    // If an error occurred while reading, we need to transition the adapter\n    // to the canceled state, but only if the adapter is still alive.\n    auto error = jsg::JsValue(exception.getHandle(js));\n    self->runIfAlive([&](ReadableStreamSourceJsAdapter& self) { self.cancel(js, error); });\n    js.throwException(kj::mv(exception));\n  });\n}\n\n// Transitions the adapter into the closing state. Once the read queue\n// is empty, we will close the source and transition to the closed state.\njsg::Promise<void> ReadableStreamSourceJsAdapter::close(jsg::Lock& js) {\n  KJ_IF_SOME(exception, state.tryGetErrorUnsafe()) {\n    // Really should not have been called if errored but just in case,\n    // return a rejected promise.\n    return js.rejectedPromise<void>(js.exceptionToJs(kj::cp(exception)));\n  }\n\n  if (state.is<Closed>()) {\n    // We are already in a closed state. This is a no-op. This really\n    // should not have been called if closed but just in case, return\n    // a resolved promise.\n    return js.resolvedPromise();\n  }\n\n  auto& open = state.requireActiveUnsafe();\n  auto& ioContext = IoContext::current();\n  auto& active = *open.active;\n\n  if (active.closePending) {\n    return js.rejectedPromise<void>(js.typeError(\"Close already pending, cannot close again.\"));\n  }\n\n  active.closePending = true;\n  auto promise = active.enqueue([]() -> kj::Promise<size_t> { co_return 0; });\n\n  return ioContext\n      .awaitIo(js, kj::mv(promise), [self = selfRef.addRef()](jsg::Lock&, size_t) {\n    self->runIfAlive(\n        [](ReadableStreamSourceJsAdapter& self) { self.state.transitionTo<Closed>(); });\n  }).catch_(js, [self = selfRef.addRef()](jsg::Lock& js, jsg::Value&& exception) {\n    // Likewise, while nothing should be waiting on the ready promise, we\n    // should still reject it just in case.\n    auto error = jsg::JsValue(exception.getHandle(js));\n    self->runIfAlive([&](ReadableStreamSourceJsAdapter& self) { self.cancel(js, error); });\n    js.throwException(kj::mv(exception));\n  });\n}\n\njsg::Promise<jsg::JsRef<jsg::JsString>> ReadableStreamSourceJsAdapter::readAllText(\n    jsg::Lock& js, uint64_t limit) {\n  KJ_IF_SOME(exception, state.tryGetErrorUnsafe()) {\n    // Really should not have been called if errored but just in case,\n    // return a rejected promise.\n    return js.rejectedPromise<jsg::JsRef<jsg::JsString>>(js.exceptionToJs(kj::cp(exception)));\n  }\n\n  if (state.is<Closed>()) {\n    // We are already in a closed state. This is a no-op. This really\n    // should not have been called if closed but just in case, return\n    // a resolved promise.\n    return js.resolvedPromise(jsg::JsRef(js, js.str()));\n  }\n\n  auto& open = state.requireActiveUnsafe();\n  auto& ioContext = IoContext::current();\n  auto& active = *open.active;\n\n  if (active.closePending) {\n    return js.rejectedPromise<jsg::JsRef<jsg::JsString>>(\n        js.typeError(\"Close already pending, cannot read.\"));\n  }\n  active.closePending = true;\n\n  struct Holder {\n    kj::Maybe<kj::String> result;\n  };\n  auto holder = kj::heap<Holder>();\n\n  auto promise = active.enqueue([&active, &holder = *holder, limit]() -> kj::Promise<size_t> {\n    auto str = co_await active.source->readAllText(limit);\n    size_t amount = str.size();\n    holder.result = kj::mv(str);\n    co_return amount;\n  });\n\n  return ioContext\n      .awaitIo(js, kj::mv(promise),\n          [self = selfRef.addRef(), holder = kj::mv(holder)](jsg::Lock& js, size_t amount) {\n    self->runIfAlive(\n        [&](ReadableStreamSourceJsAdapter& self) { self.state.transitionTo<Closed>(); });\n    KJ_IF_SOME(result, holder->result) {\n      KJ_DASSERT(result.size() == amount);\n      return jsg::JsRef(js, js.str(result));\n    } else {\n      return jsg::JsRef(js, js.str());\n    }\n  })\n      .catch_(js,\n          [self = selfRef.addRef()](\n              jsg::Lock& js, jsg::Value&& exception) -> jsg::JsRef<jsg::JsString> {\n    // Likewise, while nothing should be waiting on the ready promise, we\n    // should still reject it just in case.\n    auto error = jsg::JsValue(exception.getHandle(js));\n    self->runIfAlive([&](ReadableStreamSourceJsAdapter& self) { self.cancel(js, error); });\n    js.throwException(kj::mv(exception));\n  });\n}\n\njsg::Promise<jsg::BufferSource> ReadableStreamSourceJsAdapter::readAllBytes(\n    jsg::Lock& js, uint64_t limit) {\n  KJ_IF_SOME(exception, state.tryGetErrorUnsafe()) {\n    // Really should not have been called if errored but just in case,\n    // return a rejected promise.\n    return js.rejectedPromise<jsg::BufferSource>(js.exceptionToJs(kj::cp(exception)));\n  }\n\n  if (state.is<Closed>()) {\n    // We are already in a closed state. This is a no-op. This really\n    // should not have been called if closed but just in case, return\n    // a resolved promise.\n    auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, 0);\n    return js.resolvedPromise(jsg::BufferSource(js, kj::mv(backing)));\n  }\n\n  auto& open = state.requireActiveUnsafe();\n  auto& ioContext = IoContext::current();\n  auto& active = *open.active;\n\n  if (active.closePending) {\n    return js.rejectedPromise<jsg::BufferSource>(\n        js.typeError(\"Close already pending, cannot read.\"));\n  }\n  active.closePending = true;\n\n  struct Holder {\n    kj::Maybe<kj::Array<const kj::byte>> result;\n  };\n  auto holder = kj::heap<Holder>();\n\n  auto promise = active.enqueue([&active, &holder = *holder, limit]() -> kj::Promise<size_t> {\n    auto str = co_await active.source->readAllBytes(limit);\n    size_t amount = str.size();\n    holder.result = kj::mv(str);\n    co_return amount;\n  });\n\n  return ioContext\n      .awaitIo(js, kj::mv(promise),\n          [self = selfRef.addRef(), holder = kj::mv(holder)](jsg::Lock& js, size_t amount) {\n    self->runIfAlive(\n        [&](ReadableStreamSourceJsAdapter& self) { self.state.transitionTo<Closed>(); });\n    KJ_IF_SOME(result, holder->result) {\n      KJ_DASSERT(result.size() == amount);\n      // We have to copy the data into the backing store because of the\n      // v8 sandboxing rules.\n      auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, amount);\n      backing.asArrayPtr().copyFrom(result);\n      return jsg::BufferSource(js, kj::mv(backing));\n    } else {\n      auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, 0);\n      return jsg::BufferSource(js, kj::mv(backing));\n    }\n  })\n      .catch_(js,\n          [self = selfRef.addRef()](jsg::Lock& js, jsg::Value&& exception) -> jsg::BufferSource {\n    // Likewise, while nothing should be waiting on the ready promise, we\n    // should still reject it just in case.\n    auto error = jsg::JsValue(exception.getHandle(js));\n    self->runIfAlive([&](ReadableStreamSourceJsAdapter& self) { self.cancel(js, error); });\n    js.throwException(kj::mv(exception));\n  });\n}\n\nkj::Maybe<uint64_t> ReadableStreamSourceJsAdapter::tryGetLength(StreamEncoding encoding) {\n  KJ_IF_SOME(open, state.tryGetActiveUnsafe()) {\n    return open.active->source->tryGetLength(encoding);\n  }\n  return kj::none;\n}\n\nkj::Maybe<ReadableStreamSourceJsAdapter::Tee> ReadableStreamSourceJsAdapter::tryTee(\n    jsg::Lock& js, uint64_t limit) {\n  KJ_IF_SOME(exception, state.tryGetErrorUnsafe()) {\n    js.throwException(js.exceptionToJs(kj::cp(exception)));\n  }\n\n  if (state.is<Closed>()) {\n    // We are already closed, cannot tee.\n    return kj::none;\n  }\n\n  auto& open = state.requireActiveUnsafe();\n  auto& active = *open.active;\n  // If we are closing, or have pending tasks, we cannot tee.\n  JSG_REQUIRE(!active.closePending && !active.running && active.queue.empty(), Error,\n      \"Cannot tee a stream that is closing or has pending reads.\");\n  auto tee = active.source->tee(limit);\n  auto& ioContext = IoContext::current();\n  state.transitionTo<Closed>();\n  return Tee{\n    .branch1 = kj::heap<ReadableStreamSourceJsAdapter>(js, ioContext, kj::mv(tee.branch1)),\n    .branch2 = kj::heap<ReadableStreamSourceJsAdapter>(js, ioContext, kj::mv(tee.branch2)),\n  };\n}\n\n// ===============================================================================================\n\nstruct ReadableSourceKjAdapter::Active {\n  IoContext& ioContext;\n  jsg::Ref<ReadableStream> stream;\n  jsg::Ref<ReadableStreamDefaultReader> reader;\n  kj::Canceler canceler;\n\n  struct Idle {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"idle\"_kj;\n  };\n  struct Readable {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"readable\"_kj;\n    // Previously read but unconsumed bytes. We keep these around for the next read call.\n    kj::Array<const kj::byte> data;\n    kj::ArrayPtr<const kj::byte> view;\n\n    Readable(kj::Array<const kj::byte>&& data): data(kj::mv(data)), view(this->data) {}\n  };\n  struct Reading {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"reading\"_kj;\n    // The contract for ReadableStreamSource is that there can be only one read() in-flight\n    // against the underlying stream at a time.\n  };\n  struct Done {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"done\"_kj;\n    // If a read returns fewer than the requested minBytes, that indicates the stream is done. We\n    // make note of that here to prevent any further reads. We cannot transition to the closed\n    // state in the promise chain of the read because the adapter will cancel the read promise\n    // itself once Active is destroyed, and that would be a bad thing.\n  };\n  struct Canceling {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"canceling\"_kj;\n    kj::Exception exception;\n  };\n  struct Canceled {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"canceled\"_kj;\n    kj::Exception exception;\n  };\n\n  // Inner state machine for tracking read operation state:\n  //   Idle -> Reading (start read)\n  //   Reading -> Idle (read complete, no leftover)\n  //   Reading -> Readable (read complete, has leftover)\n  //   Reading -> Done (read returned less than minBytes)\n  //   Any -> Canceling (error during read)\n  //   Any -> Canceled (explicit cancel)\n  // Done, Canceling, and Canceled are terminal states.\n  using InnerState = StateMachine<TerminalStates<Done, Canceling, Canceled>,\n      Idle,\n      Readable,\n      Reading,\n      Done,\n      Canceling,\n      Canceled>;\n  InnerState state;\n\n  Active(jsg::Lock& js, IoContext& ioContext, jsg::Ref<ReadableStream> stream);\n  KJ_DISALLOW_COPY_AND_MOVE(Active);\n  ~Active() noexcept(false);\n\n  void cancel(kj::Exception reason);\n};\n\n// The ReadContext struct holds all the state needed to perform a read,\n// including the JS objects that need to be kept alive during the\n// read operation, the buffer we are reading into, and the total\n// number of bytes read so far. This must be kept alive until the\n// read is fully complete and returned back to the adapter when\n// the read is complete.\n//\n// Ownership of the ReadContext is passed into the isolate lock and\n// held by JS promise continuations, so it must not contain any\n// kj I/O objects or references without an IoOwn wrapper.\nstruct ReadableSourceKjAdapter::ReadContext {\n  jsg::Ref<ReadableStream> stream;\n  jsg::Ref<ReadableStreamDefaultReader> reader;\n  kj::ArrayPtr<kj::byte> buffer;\n  // Only set to back the buffer if we need to keep it alive.\n  kj::Maybe<kj::Array<kj::byte>> backingBuffer;\n  size_t totalRead = 0;\n  size_t minBytes = 0;\n  kj::Maybe<Active::Readable> maybeLeftOver;\n  // We keep a weak reference to the adapter itself so we can track\n  // whether it is still alive while we are in a JS promise chain.\n  // If the adapter is gone, or transitions to a closed or canceled\n  // state we will abandon the read. If the ref is not set, then we\n  // are in a pump operation and do not need to check for liveness.\n  kj::Maybe<kj::Rc<WeakRef<ReadableSourceKjAdapter>>> adapterRef;\n\n  void reset() {\n    // Resetting is only allowed if we have the backing buffer.\n    buffer = KJ_ASSERT_NONNULL(backingBuffer);\n    totalRead = 0;\n    minBytes = 0;\n    maybeLeftOver = kj::none;\n  }\n};\n\nnamespace {\nconstexpr size_t kMinRemainingForAdditionalRead = 512;\n\njsg::Ref<ReadableStreamDefaultReader> initReader(jsg::Lock& js, jsg::Ref<ReadableStream>& stream) {\n  JSG_REQUIRE(!stream->isLocked(), TypeError, \"ReadableStream is locked.\");\n  JSG_REQUIRE(!stream->isDisturbed(), TypeError, \"ReadableStream is disturbed.\");\n  auto reader = stream->getReader(js, kj::none);\n  return kj::mv(KJ_ASSERT_NONNULL(reader.tryGet<jsg::Ref<ReadableStreamDefaultReader>>()));\n}\n\nusing JsByteSource = kj::OneOf<jsg::JsRef<jsg::JsString>,\n    jsg::JsRef<jsg::JsArrayBuffer>,\n    jsg::JsRef<jsg::JsArrayBufferView>>;\n\nkj::Maybe<JsByteSource> tryExtractJsByteSource(jsg::Lock& js, const jsg::JsValue& jsval) {\n  KJ_IF_SOME(abView, jsval.tryCast<jsg::JsArrayBuffer>()) {\n    return kj::Maybe(jsg::JsRef(js, abView));\n  } else KJ_IF_SOME(ab, jsval.tryCast<jsg::JsArrayBufferView>()) {\n    return kj::Maybe(jsg::JsRef(js, ab));\n  } else KJ_IF_SOME(str, jsval.tryCast<jsg::JsString>()) {\n    return kj::Maybe(jsg::JsRef(js, str));\n  }\n  return kj::none;\n}\n\n// Copies as much data from source into the context as possible, returning\n// the number of bytes copied.\nkj::Maybe<kj::Array<const kj::byte>> copyFromSource(\n    jsg::Lock& js, ReadableSourceKjAdapter::ReadContext& context, const JsByteSource& source) {\n  KJ_SWITCH_ONEOF(source) {\n    KJ_CASE_ONEOF(str, jsg::JsRef<jsg::JsString>) {\n      auto view = str.getHandle(js);\n      size_t len = view.length(js);\n      size_t toCopy = kj::min(len, context.buffer.size());\n\n      if (toCopy == 0) {\n        return kj::none;\n      }\n\n      if (toCopy < len) {\n        // We are going to have left-over data. Unfortunately in this case\n        // we have to copy the data twice... once into a kj::String and\n        // again into our buffer. This is because the V8 string UTF-8\n        // write API does not support partial writes with an offset.\n        auto data = view.toUSVString(js);\n        context.buffer.first(toCopy).copyFrom(data.asBytes().first(toCopy));\n        context.totalRead += toCopy;\n        context.buffer = context.buffer.slice(toCopy);\n        KJ_DASSERT(context.buffer.size() == 0);\n        return kj::Maybe(data.asBytes().slice(toCopy).attach(kj::mv(data)));\n      }\n\n      // We can copy everything in one go. Yay! This is great because we\n      // can avoid a double copy here.\n      auto ret KJ_UNUSED = view.writeInto(js, context.buffer.asChars().first(toCopy),\n          jsg::JsString::WriteFlags::REPLACE_INVALID_UTF8);\n      KJ_DASSERT(ret.written == toCopy);\n      context.totalRead += toCopy;\n      context.buffer = context.buffer.slice(toCopy);\n      return kj::none;\n    }\n    KJ_CASE_ONEOF(ab, jsg::JsRef<jsg::JsArrayBuffer>) {\n      auto src = ab.getHandle(js).asArrayPtr();\n      size_t toCopy = kj::min(src.size(), context.buffer.size());\n      if (toCopy == 0) {\n        return kj::none;\n      }\n\n      context.buffer.first(toCopy).copyFrom(src.first(toCopy));\n      context.totalRead += toCopy;\n      context.buffer = context.buffer.slice(toCopy);\n\n      if (toCopy < src.size()) {\n        KJ_DASSERT(context.buffer.size() == 0);\n        // TODO(mpk): For now, we have to copy the left-over data into a new array.\n        // Why? I'm happy you asked! Because the src is backed by a\n        // v8::BackingStore protected by the v8 sandboxing rules and we\n        // don't yet have the memory protection key logic in place to safely\n        // share that memory outside of the v8 heap. For now, copy. Later\n        // we can revisit this to hopefully avoid the additinal copy.\n        return kj::Maybe(kj::heapArray(src.slice(toCopy)));\n      }\n\n      return kj::none;\n    }\n    KJ_CASE_ONEOF(view, jsg::JsRef<jsg::JsArrayBufferView>) {\n      auto src = view.getHandle(js).asArrayPtr();\n      size_t toCopy = kj::min(src.size(), context.buffer.size());\n      if (toCopy == 0) {\n        // Copy nothing. Return 0.\n        return kj::none;\n      }\n\n      context.buffer.first(toCopy).copyFrom(src.first(toCopy));\n      context.totalRead += toCopy;\n      context.buffer = context.buffer.slice(toCopy);\n\n      if (toCopy < src.size()) {\n        KJ_DASSERT(context.buffer.size() == 0);\n        return kj::Maybe(kj::heapArray(src.slice(toCopy)));\n      }\n\n      return kj::none;\n    }\n  }\n  KJ_UNREACHABLE;\n}\n}  // namespace\n\nReadableSourceKjAdapter::Active::Active(\n    jsg::Lock& js, IoContext& ioContext, jsg::Ref<ReadableStream> stream)\n    : ioContext(ioContext),\n      stream(kj::mv(stream)),\n      reader(initReader(js, this->stream)),\n      state(InnerState::create<Idle>()) {}\n\nReadableSourceKjAdapter::Active::~Active() noexcept(false) {\n  cancel(KJ_EXCEPTION(DISCONNECTED, \"ReadableSourceKjAdapter is canceled.\"));\n}\n\nvoid ReadableSourceKjAdapter::Active::cancel(kj::Exception reason) {\n  if (state.is<Canceled>()) {\n    return;\n  }\n  bool wasDone = state.is<Done>();\n  state.forceTransitionTo<Canceled>(kj::cp(reason));\n  canceler.cancel(kj::cp(reason));\n  if (!wasDone) {\n    // If the previous read indicated that it was the last read, then\n    // the reader will have already been dropped. We do not need to\n    // cancel it here.\n    ioContext.addTask(ioContext.run([readable = kj::mv(stream), reader = kj::mv(reader),\n                                        exception = kj::mv(reason)](jsg::Lock& js) mutable {\n      auto& ioContext = IoContext::current();\n      auto error = js.exceptionToJsValue(kj::mv(exception));\n      auto promise = reader->cancel(js, error.getHandle(js));\n      return ioContext.awaitJs(js, kj::mv(promise));\n    }));\n  }\n}\n\nReadableSourceKjAdapter::ReadableSourceKjAdapter(\n    jsg::Lock& js, IoContext& ioContext, jsg::Ref<ReadableStream> stream, Options options)\n    : state(KjState::create<KjOpen>(kj::heap<Active>(js, ioContext, kj::mv(stream)))),\n      options(options),\n      selfRef(\n          kj::rc<WeakRef<ReadableSourceKjAdapter>>(kj::Badge<ReadableSourceKjAdapter>{}, *this)) {}\n\nReadableSourceKjAdapter::~ReadableSourceKjAdapter() noexcept(false) {\n  selfRef->invalidate();\n}\n\njsg::Promise<kj::Own<ReadableSourceKjAdapter::ReadContext>> ReadableSourceKjAdapter::readInternal(\n    jsg::Lock& js, kj::Own<ReadContext> context, MinReadPolicy minReadPolicy) {\n  auto& ioContext = IoContext::current();\n  // Pay close attention to the lambda captures here. There are no raw references\n  // captured! The adapter itself may be destroyed or closed while we are in the\n  // promise chain below, so we have to be careful to only hold weak references\n  // and pass ownership of the context along the promise chain.\n  //\n  // The other important thing here is to remember that everything in this function\n  // is running within the isolate lock. The idea is to keep the entire read of the\n  // underlying stream entirely within the lock so that we don't have to bounce\n  // in and out of the isolate lock multiple times. We only return to the kj world\n  // once the entire read is complete.\n  //\n  // Note the uses of addFunctor below. This is important because it ensures\n  // that the promise continuations are run within the correct IoContext.\n  return context->reader->read(js).then(js,\n      ioContext.addFunctor([context = kj::mv(context), minReadPolicy](jsg::Lock& js,\n                               ReadResult result) mutable -> jsg::Promise<kj::Own<ReadContext>> {\n    if (result.done || result.value == kj::none) {\n      // Stream is ended.\n      return js.resolvedPromise(kj::mv(context));\n    }\n\n    auto& value = KJ_ASSERT_NONNULL(result.value);\n\n    // Ok, we have some data. Let's make sure it is bytes.\n    // We accept either an ArrayBuffer, ArrayBufferView, or string.\n    auto jsval = jsg::JsValue(value.getHandle(js));\n    KJ_IF_SOME(result, tryExtractJsByteSource(js, jsval)) {\n      // Process the resulting data.\n      KJ_IF_SOME(leftOver, copyFromSource(js, *context, result)) {\n        KJ_ASSERT(context->buffer.size() == 0);\n        if (leftOver.size() > 0) {\n          context->maybeLeftOver = Active::Readable(kj::mv(leftOver));\n        } else {\n          context->maybeLeftOver = kj::none;\n        }\n        return js.resolvedPromise(kj::mv(context));\n      }\n\n      // At this point, we should have no left over data.\n      KJ_DASSERT(context->maybeLeftOver == kj::none);\n\n      // If the buffer is exactly full (the chunk filled it perfectly), we're done.\n      if (context->buffer.size() == 0) {\n        return js.resolvedPromise(kj::mv(context));\n      }\n\n      // We might continue reading only if the adapter is still alive and\n      // in an active state...\n      bool continueReading = true;\n      KJ_IF_SOME(adapterRef, context->adapterRef) {\n        continueReading = adapterRef->isValid();\n        adapterRef->runIfAlive(\n            [&](ReadableSourceKjAdapter& adapter) { continueReading = adapter.state.isActive(); });\n      }\n\n      // If we have satisfied the minimum read requirement and either\n      // (a) the minReadPolicy is IMMEDIATE or (b) there are fewer\n      // than 512 bytes left in the buffer, we will just return what we\n      // have. The idea here is that while we could just return what we have\n      // and let the caller call read again, that would be inefficient if\n      // the caller has a large buffer and is trying to read a lot of data.\n      // Instead of returning early with a minimally filled buffer, let's\n      // try to fill it up a bit more before returning. The 512 byte limit\n      // is somewhat arbitrary. The risk, of course, is that the next read\n      // will return too much data to fit into the buffer, which will then\n      // have to be stashed away as left over data. There's also a risk that\n      // the stream is slow and we end up with more latency waiting for\n      // the next chunk of data to arrive. In practice, this seems unlikely\n      // to be a problem. The IMMEDIATE policy is useful in the latter case,\n      // when the caller wants to get whatever data is available as soon\n      // as possible, even if it is just a small amount. The downside of the\n      // IMMEDIATE policy is that it can lead to a lot of small reads that\n      // are expensive because they have to grab the isolate lock each time.\n      bool minReadSatisfied = context->totalRead >= context->minBytes &&\n          (minReadPolicy == MinReadPolicy::IMMEDIATE ||\n              context->buffer.size() < kMinRemainingForAdditionalRead);\n\n      if (!continueReading || minReadSatisfied) {\n        return js.resolvedPromise(kj::mv(context));\n      }\n\n      // We still have not satisfied the minimum read requirement or we are\n      // trying to fill up a larger buffer. We will need to read more. Let's\n      // call readInternal again to get the next chunk of data. Keep in mind\n      // that this is not a true recursive call because readInternal returns\n      // a jsg::Promise. We're just chaining the promises together here.\n      return readInternal(js, kj::mv(context), minReadPolicy);\n    }\n\n    // Oooo, invalid type. We cannot handle this and must treat this as a fatal error.\n    // We will cancel the stream and return an error.\n    auto error = js.typeError(\"ReadableStream provided a non-bytes value. Only ArrayBuffer, \"\n                              \"ArrayBufferView, or string are supported.\");\n    context->reader->cancel(js, error);\n    return js.rejectedPromise<kj::Own<ReadContext>>(error);\n  }),\n      ioContext.addFunctor([](jsg::Lock& js, jsg::Value exception) {\n    // In this case, the reader should already be in an errored state\n    // since it it the read that failed. Just propagate the error.\n    return js.rejectedPromise<kj::Own<ReadContext>>(kj::mv(exception));\n  }));\n}\n\n// We separate out the actual read implementation so that it can be used by\n// both read and the pumpToImpl implementation.\nkj::Promise<size_t> ReadableSourceKjAdapter::readImpl(\n    Active& active, kj::ArrayPtr<kj::byte> dest, size_t minBytes) {\n\n  KJ_IF_SOME(readable, active.state.tryGetUnsafe<Active::Readable>()) {\n    // We have some data left over from a previous read. Use that first.\n\n    // If we have enough left over to fully satisfy this read,\n    // Use it, then update our left over view.\n    if (readable.view.size() >= dest.size()) {\n      dest.copyFrom(readable.view.first(dest.size()));\n      readable.view = readable.view.slice(dest.size());\n      if (readable.view.size() == 0) {\n        // We used up all our left over data. We can transition to the idle state.\n        active.state.transitionTo<Active::Idle>();\n      }\n      // Otherwise we still have some left over data. That\n      // is ok, we will keep it around for the next read.\n      // We intentionally do not transition to the idle state\n      // here because we want to keep the left over data for\n      // the next read.\n      return dest.size();\n    }\n\n    // Otherwise, consume what we do have left over.\n    auto size = readable.view.size();\n    dest.first(size).copyFrom(readable.view);\n    dest = dest.slice(size);\n\n    active.state.transitionTo<Active::Idle>();\n\n    // Did we at least satisfy the minimum bytes?\n    if (size >= minBytes) {\n      // Awesome, we are technically done with this read.\n      // While we might actually have more room in our buffer, and the\n      // minReadyPolicy might be OPPORTUNISTIC, we will not try to\n      // read more from the stream right now so that we can avoid having\n      // to grab the isolate lock for this read. Instead, let's return\n      // what we have and let the caller call read again if/when they want.\n      // This risks leaving a fair amount of unused space in the buffer\n      // and requiring more read calls but it avoids the overhead of\n      // an additional isolate lock grab when we know we can at least\n      // provide some data right now.\n      return size;\n    }\n  }\n\n  // If we got here, we still have not satisfied the minimum bytes,\n  // so we will continue on to read more from the stream. But, we\n  // also should not have any more data left over. Let's verify.\n  KJ_ASSERT(active.state.is<Active::Idle>());\n  active.state.transitionTo<Active::Reading>();\n\n  // Our read context holds all the state needed to perform the read.\n  // Ownership of the context is passed into the read operation and\n  // returned back to us when the read is complete.\n  auto context = kj::heap<ReadContext>({\n    .stream = active.stream.addRef(),\n    .reader = active.reader.addRef(),\n    .buffer = dest,\n    .totalRead = 0,\n    .minBytes = minBytes,\n    .adapterRef = selfRef.addRef(),\n  });\n\n  return active.canceler\n      .wrap(\n          // Warning: Do *not* capture \"active\" in this lambda! It may be destroyed\n          // while we are in the promise chain. Instead, we capture a weak\n          // reference to the adapter itself and check that we are still alive\n          // and active before trying to update any state.\n          active.ioContext.run([context = kj::mv(context), self = selfRef.addRef(),\n                                   minReadPolicy = options.minReadPolicy](\n                                   jsg::Lock& js) mutable -> kj::Promise<size_t> {\n    auto& ioContext = IoContext::current();\n\n    // Perform the actual read.\n    return ioContext.awaitJs(js, readInternal(js, kj::mv(context), minReadPolicy))\n        .then([self = kj::mv(self)](kj::Own<ReadContext> context) mutable -> kj::Promise<size_t> {\n      // By the time we get here, it is possible that the adapter has been\n      // destroyed. If that's the case, it's okay, that's what our weak ref\n      // is here for. We will only try to update our state if we are still\n      // alive and active.\n\n      self->runIfAlive([&](ReadableSourceKjAdapter& self) {\n        // Ok, we're still alive! Yay! But, let's check to make sure we didn't\n        // change state while we were reading.\n        KJ_IF_SOME(open, self.state.tryGetActiveUnsafe()) {\n          auto& active = *open.active;\n          // Ok, we're still active. Let's see if we have any left over data\n          // that we need to stash away for the next read.\n          KJ_IF_SOME(leftOver, context->maybeLeftOver) {\n            // We have some left over data. Stash it away for the next read.\n            active.state.transitionTo<Active::Readable>(kj::mv(leftOver));\n            // In this branch, we must have filled the entire destination\n            // buffer and satisfied the minimum read requirement or else\n            // we wouldn't have any left over data. Let's just assert that\n            // invariant just in case.\n            KJ_DASSERT(context->totalRead >= context->minBytes);\n          } else if (context->totalRead < context->minBytes) {\n            // We returned fewer than the minimum bytes requested. This is our\n            // signal that we're done.\n            active.state.transitionTo<Active::Done>();\n            // We cannot change the state to Closed here because we are still\n            // inside the kj::Promise chain wrapped by the canceler. If we\n            // change the state to Closed, the Active would be destroyed, causing\n            // this promise chain to be canceled.\n            auto droppedReader KJ_UNUSED = kj::mv(active.reader);\n            auto droppedStream KJ_UNUSED = kj::mv(active.stream);\n            // In this branch, we should not have any left over data.\n            // Let's assert that invariant just in case.\n            KJ_DASSERT(context->maybeLeftOver == kj::none);\n          } else {\n            // Our read is complete. Return to the idle state and we're done.\n            active.state.transitionTo<Active::Idle>();\n\n            // In this branch, we must have satisfied the minimum read\n            // requirement. Let's just assert that invariant just in case.\n            KJ_DASSERT(context->totalRead >= context->minBytes);\n            // We should not have any left over data.\n            KJ_DASSERT(context->maybeLeftOver == kj::none);\n          }\n        } else {\n          // We were closed or canceled while we were reading. Doh!\n          // That's ok, there's nothing more we can or need to do\n          // here. Just fall-through to the return below.\n        }\n      });\n      return context->totalRead;\n    });\n  })).catch_([self = selfRef.addRef()](kj::Exception exception) -> kj::Promise<size_t> {\n    self->runIfAlive([&](ReadableSourceKjAdapter& self) {\n      KJ_IF_SOME(open, self.state.tryGetActiveUnsafe()) {\n        open.active->state.forceTransitionTo<Active::Canceling>(Active::Canceling{\n          .exception = kj::cp(exception),\n        });\n      }\n    });\n    return kj::mv(exception);\n  });\n}\n\nkj::Promise<size_t> ReadableSourceKjAdapter::read(kj::ArrayPtr<kj::byte> buffer, size_t minBytes) {\n\n  if (buffer.size() == 0) {\n    // Nothing to read. This is a no-op.\n    return static_cast<size_t>(0);\n  }\n\n  // Clamp the minBytes to [1, buffer.size()].\n  minBytes = kj::min(buffer.size(), kj::max(minBytes, 1UL));\n  KJ_DASSERT(minBytes >= 1 && minBytes <= buffer.size(),\n      \"minBytes must be less than or equal to the buffer size.\");\n\n  KJ_IF_SOME(exception, state.tryGetErrorUnsafe()) {\n    return kj::cp(exception);\n  }\n\n  if (state.is<KjClosed>()) {\n    return static_cast<size_t>(0);\n  }\n\n  auto& open = state.requireActiveUnsafe();\n  auto& active = *open.active;\n  KJ_SWITCH_ONEOF(active.state) {\n    KJ_CASE_ONEOF(_, Active::Reading) {\n      KJ_FAIL_REQUIRE(\"Cannot have multiple concurrent reads.\");\n    }\n    KJ_CASE_ONEOF(_, Active::Done) {\n      // The previous read indicated that it was the last read by returning\n      // less than the minimum bytes requested. We have to treat this as\n      // the stream being closed.\n      state.transitionTo<KjClosed>();\n      return static_cast<size_t>(0);\n    }\n    KJ_CASE_ONEOF(canceling, Active::Canceling) {\n      // The stream is being canceled. Propagate the exception and complete\n      // the state transition.\n      return KJ_ASSERT_NONNULL(checkCancelingOrCanceled(active));\n    }\n    KJ_CASE_ONEOF(canceled, Active::Canceled) {\n      // The stream was canceled. Propagate the exception and complete\n      // the state transition.\n      return KJ_ASSERT_NONNULL(checkCancelingOrCanceled(active));\n    }\n    KJ_CASE_ONEOF(r, Active::Readable) {\n      // There is some data left over from a previous read.\n      return readImpl(active, buffer, minBytes);\n    }\n    KJ_CASE_ONEOF(_, Active::Idle) {\n      // There are no pending reads and no left over data.\n      return readImpl(active, buffer, minBytes);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::Maybe<size_t> ReadableSourceKjAdapter::tryGetLength(StreamEncoding encoding) {\n  KJ_IF_SOME(open, state.tryGetActiveUnsafe()) {\n    auto& active = *open.active;\n    if (active.state.is<Active::Done>() || active.state.is<Active::Canceled>()) {\n      // If the previous read indicated that it was the last, then\n      // let's just transition to the closed state now and return kj::none.\n      state.transitionTo<KjClosed>();\n      return kj::none;\n    }\n    if (checkCancelingOrCanceled(active) != kj::none) {\n      return kj::none;\n    }\n    return active.stream->tryGetLength(encoding).map(\n        [](uint64_t len) { return static_cast<size_t>(len); });\n  }\n\n  // The stream is either closed or errored.\n  return kj::none;\n}\n\nvoid ReadableSourceKjAdapter::cancel(kj::Exception reason) {\n  KJ_IF_SOME(open, state.tryGetActiveUnsafe()) {\n    open.active->cancel(kj::cp(reason));\n  }\n  state.forceTransitionTo<kj::Exception>(kj::mv(reason));\n}\n\nkj::Maybe<kj::Exception> ReadableSourceKjAdapter::checkCancelingOrCanceled(Active& active) {\n  KJ_IF_SOME(canceling, active.state.tryGetUnsafe<Active::Canceling>()) {\n    auto exception = kj::mv(canceling.exception);\n    state.forceTransitionTo<kj::Exception>(kj::cp(exception));\n    return kj::mv(exception);\n  }\n  KJ_IF_SOME(canceled, active.state.tryGetUnsafe<Active::Canceled>()) {\n    auto exception = kj::mv(canceled.exception);\n    state.forceTransitionTo<kj::Exception>(kj::cp(exception));\n    return kj::mv(exception);\n  }\n  return kj::none;\n}\n\nvoid ReadableSourceKjAdapter::throwIfCancelingOrCanceled(Active& active) {\n  KJ_IF_SOME(exception, checkCancelingOrCanceled(active)) {\n    kj::throwFatalException(kj::mv(exception));\n  }\n}\n\nkj::Promise<void> ReadableSourceKjAdapter::pumpToImpl(\n    kj::Own<Active> active, WritableSink& output, EndAfterPump end) {\n  // This implementation uses DrainingReader to efficiently pull all synchronously\n  // available data from the underlying JS stream in each iteration. This minimizes\n  // the number of isolate lock acquisitions by getting all available data at once\n  // rather than reading into fixed-size buffers.\n\n  KJ_DASSERT(active->state.is<Active::Idle>() || active->state.is<Active::Readable>(),\n      \"pumpToImpl called when stream is not in an active state.\");\n\n  bool writeFailed = false;\n\n  // First, if the active state is in the Readable state, we need to drain the\n  // left over data before starting the main read loop.\n  // This is unlikely to occur in the typical case, but we need to handle it\n  // nonetheless.\n  KJ_IF_SOME(readable, active->state.tryGetUnsafe<Active::Readable>()) {\n    co_await output.write(readable.view);\n    active->state.transitionTo<Active::Idle>();\n  }\n\n  // We hold the DrainingReader during the pump. The pointer remains valid because\n  // the reader is created and owned during the pump loop lifetime.\n  kj::Maybe<kj::Own<DrainingReader>> maybeReader;\n\n  // Initialize the pump by releasing the default reader and creating a DrainingReader.\n  // This requires the isolate lock.\n  co_await active->ioContext.run(\n      [&active, &maybeReader](jsg::Lock& js) mutable -> kj::Promise<void> {\n    // Release the existing reader's lock so we can create a DrainingReader.\n    active->reader->releaseLock(js);\n\n    // Create the DrainingReader for the stream.\n    maybeReader = KJ_ASSERT_NONNULL(DrainingReader::create(js, *active->stream),\n        \"Failed to create DrainingReader - stream should not be locked\");\n    return kj::READY_NOW;\n  });\n\n  auto& reader = KJ_ASSERT_NONNULL(maybeReader);\n  kj::Maybe<kj::Exception> pendingException;\n\n  try {\n    while (true) {\n      // Perform a draining read to get all synchronously available data.\n      // Pass raw pointer to reader into the lambda - safe because we own it\n      // and keep it alive for the duration of the pump.\n      // The draining reader grabs all data currently available in the stream's\n      // queue, then tries to read more data up to a limit as long as the data\n      // can be provided synchronously. The idea is to drain off as much data\n      // from the stream as possible each time we are holding the isolate lock\n      // to minimize the number of times we need to re-enter the lock.\n      DrainingReader* readerPtr = reader.get();\n      DrainingReadResult result =\n          co_await active->ioContext.run([readerPtr](jsg::Lock& js) mutable {\n        auto& ioContext = IoContext::current();\n        // Use a 256KB limit to allow periodic yielding to the event loop,\n        // preventing a fast producer from monopolizing the thread. This limit\n        // only affects subsequent pump iterations after the initial buffer drain.\n        constexpr size_t kMaxReadPerCycle = 256 * 1024;\n        return ioContext.awaitJs(js, readerPtr->read(js, kMaxReadPerCycle));\n      });\n\n      // Write all the chunks we received using vectored write for efficiency.\n      if (result.chunks.size() > 0) {\n        KJ_ON_SCOPE_FAILURE(writeFailed = true);\n        // Convert Array<Array<byte>> to ArrayPtr<ArrayPtr<const byte>> for vectored write.\n        auto pieces =\n            KJ_MAP(chunk, result.chunks) -> kj::ArrayPtr<const kj::byte> { return chunk.asPtr(); };\n        co_await output.write(pieces);\n      }\n\n      // If the stream is done, end the output if needed and exit.\n      if (result.done) {\n        KJ_ON_SCOPE_FAILURE(writeFailed = true);\n        if (end) {\n          co_await output.end();\n        }\n        co_return;\n      }\n    }\n  } catch (...) {\n    auto exception = kj::getCaughtExceptionAsKj();\n    if (!writeFailed) {\n      // If we got an error and it wasn't the write that failed, abort the output.\n      output.abort(kj::cp(exception));\n    }\n    // Store the exception to handle after the catch block.\n    pendingException = kj::mv(exception);\n  }\n\n  // If there was an error, cancel the reader and propagate the exception.\n  KJ_IF_SOME(exception, pendingException) {\n    DrainingReader* readerPtr = reader.get();\n    co_await active->ioContext.run([readerPtr, ex = kj::cp(exception)](jsg::Lock& js) mutable {\n      auto& ioContext = IoContext::current();\n      auto error = js.exceptionToJsValue(kj::mv(ex));\n      return ioContext.awaitJs(js, readerPtr->cancel(js, error.getHandle(js)));\n    });\n    kj::throwFatalException(kj::mv(exception));\n  }\n}\n\nkj::Promise<DeferredProxy<void>> ReadableSourceKjAdapter::pumpTo(\n    WritableSink& output, EndAfterPump end) {\n  // The pumpTo operation continually reads from the stream and writes\n  // to the output until the stream is closed or an error occurs. Once\n  // the pump starts, the adapter transitions to the closed state and\n  // ownership of the underlying stream is transferred to the pump\n  // operation.\n\n  KJ_IF_SOME(exception, state.tryGetErrorUnsafe()) {\n    return kj::Promise<DeferredProxy<void>>(DeferredProxy<void>{kj::cp(exception)});\n  }\n\n  if (state.is<KjClosed>()) {\n    // Already closed, nothing to do.\n    return newNoopDeferredProxy();\n  }\n\n  auto& open = state.requireActiveUnsafe();\n  auto& active = *open.active;\n  // Per the contract for ReadableStreamSource::pumpTo, the pump operation\n  // will take over ownership of the underlying stream until it is complete,\n  // leaving the adapter itself in a closed state once the pump starts.\n  // Dropping the returned promise will cancel the pump operation.\n  // We do, however, need to first make sure that our active state is\n  // not already pending a read or terminal state change.\n  KJ_REQUIRE(!active.state.is<Active::Reading>(), \"Cannot have multiple concurrent reads.\");\n\n  if (active.state.is<Active::Done>()) {\n    // The previous read indicated that it was the last read by returning\n    // less than the minimum bytes requested, or the stream was fully\n    // canceled. We have to treat this as the stream being closed.\n    state.transitionTo<KjClosed>();\n    return newNoopDeferredProxy();\n  }\n\n  KJ_IF_SOME(exception, checkCancelingOrCanceled(active)) {\n    return kj::Promise<DeferredProxy<void>>(kj::mv(exception));\n  }\n\n  // The active state should be Readable of Idle here. Let's verify.\n  KJ_DASSERT(active.state.is<Active::Readable>() || active.state.is<Active::Idle>());\n\n  // The Active state will be transferred into the pumpImpl operation.\n  auto activeState = kj::mv(open.active);\n  state.transitionTo<KjClosed>();  // transition to closed immediately\n\n  // Because pumpToImpl is wrapping a JavaScript stream, it is not eligible\n  // for deferred proxying. We will return a noopDeferredProxy that wraps the\n  // promise from pumpToImpl();\n  return addNoopDeferredProxy(pumpToImpl(kj::mv(activeState), output, end));\n}\n\nReadableSource::Tee ReadableSourceKjAdapter::tee(size_t) {\n  KJ_UNIMPLEMENTED(\"Teeing a ReadableSourceKjAdapter is not supported.\");\n  // Explanation: Teeing a ReadableStream must be done under the isolate lock,\n  // as does creating a new ReadableSourceKjAdapter. However, when tee()\n  // is called we are not guaranteed to be under the isolate lock, nor can\n  // we acquire the lock here because this is a synchronous operation and\n  // acquiring the isolate lock requires waiting for a promise to resolve.\n  //\n  // Teeing here is unlikely to be necessary. If you do need a tee, it's\n  // necessary to tee the underlying ReadableStream directly and create\n  // two separate ReadableSourceKjAdapters, one for each branch of\n  // that tee while the lock is held.\n}\n\nkj::Promise<kj::Array<const kj::byte>> ReadableSourceKjAdapter::readAllBytes(size_t limit) {\n  co_return co_await readAllImpl<kj::byte>(limit);\n}\n\nkj::Promise<kj::String> ReadableSourceKjAdapter::readAllText(size_t limit) {\n  auto array = co_await readAllImpl<char>(limit);\n  co_return kj::String(kj::mv(array));\n}\n\ntemplate <typename T>\nkj::Promise<kj::Array<T>> ReadableSourceKjAdapter::readAllImpl(size_t limit) {\n  KJ_IF_SOME(exception, state.tryGetErrorUnsafe()) {\n    kj::throwFatalException(kj::cp(exception));\n  }\n\n  if (state.is<KjClosed>()) {\n    co_return kj::Array<T>();\n  }\n\n  auto& open = state.requireActiveUnsafe();\n  auto& active = *open.active;\n  KJ_REQUIRE(!active.state.is<Active::Reading>(), \"Cannot have multiple concurrent reads.\");\n\n  if (active.state.is<Active::Done>()) {\n    // The previous read indicated that it was the last read by returning\n    // less than the minimum bytes requested. We have to treat this as\n    // the stream being closed.\n    state.transitionTo<KjClosed>();\n    co_return kj::Array<T>();\n  }\n\n  throwIfCancelingOrCanceled(active);\n\n  // Our readAll operation will accumulate data into a buffer up to the\n  // specified limit. If the limit is exceeded, the returned promise will\n  // be rejected. Once the readAll operation starts, the adapter is moved\n  // into a closed state and ownership of the underlying stream is transferred\n  // to the readAll promise.\n  auto activeState = kj::mv(open.active);\n  state.transitionTo<KjClosed>();  // transition to closed immediately\n\n  KJ_DASSERT(activeState->state.is<Active::Readable>() || activeState->state.is<Active::Idle>());\n\n  // We do not use the canceler here. The adapter is closed and can be safely dropped.\n  // This promise, however, will keep the stream alive until the read is completed.\n  // If the returned promise is dropped, the readAll operation will be canceled.\n  CancelationToken cancelationToken;\n  co_return co_await IoContext::current().run(\n      [limit, active = kj::mv(activeState), cancelationToken = cancelationToken.getWeakRef()](\n          jsg::Lock& js) mutable -> kj::Promise<kj::Array<T>> {\n    kj::Vector<T> accumulated;\n    // If we know the length of the stream ahead of time, and it is within the limit,\n    // we can reserve that much space in the accumulator to avoid multiple allocations.\n    KJ_IF_SOME(length, active->stream->tryGetLength(StreamEncoding::IDENTITY)) {\n      if (length <= limit) {\n        accumulated.reserve(length);  // Pre-allocate\n      }\n    }\n\n    auto& ioContext = IoContext::current();\n    return ioContext.awaitJs(js,\n        readAllReadImpl(js, ioContext.addObject(kj::mv(active)), kj::mv(accumulated), limit,\n            kj::mv(cancelationToken)));\n  });\n}\n\ntemplate <typename T>\njsg::Promise<kj::Array<T>> ReadableSourceKjAdapter::readAllReadImpl(jsg::Lock& js,\n    IoOwn<Active> active,\n    kj::Vector<T> accumulated,\n    size_t limit,\n    kj::Rc<WeakRef<CancelationToken>> cancelationToken) {\n\n  // Check for cancelation. The cancelation token is a weak ref. If the promise\n  // that represents the readAll operation is dropped, the token will be invalidated.\n  // Since there is no way to directly cancel a JavaScript promise, this is the best\n  // we can do to interrupt the loop.\n  if (!cancelationToken->isValid()) {\n    return js.rejectedPromise<kj::Array<T>>(js.error(\"readAll operation was canceled.\"));\n  }\n\n  // First, drain any leftover data if the active state is in Readable mode.\n  KJ_IF_SOME(readable, active->state.tryGetUnsafe<Active::Readable>()) {\n    auto leftover = readable.view.asBytes();\n    if (leftover.size() > limit) {\n      auto error = js.rangeError(\"Memory limit would be exceeded before EOF.\");\n      return active->reader->cancel(js, error).then(\n          js, [ex = jsg::JsRef(js, error)](jsg::Lock& js) {\n        return js.rejectedPromise<kj::Array<T>>(ex.getHandle(js));\n      });\n    }\n    if constexpr (kj::isSameType<T, char>()) {\n      accumulated.addAll(leftover.asChars());\n    } else {\n      accumulated.addAll(leftover);\n    }\n    active->state.transitionTo<Active::Idle>();\n  }\n\n  return active->reader->read(js).then(js,\n      [active = kj::mv(active), accumulated = kj::mv(accumulated), limit,\n          cancelationToken = kj::mv(cancelationToken)](\n          jsg::Lock& js, ReadResult result) mutable -> jsg::Promise<kj::Array<T>> {\n    // Check for cancelation.\n    if (!cancelationToken->isValid()) {\n      return js.rejectedPromise<kj::Array<T>>(js.error(\"readAll operation was canceled.\"));\n    }\n\n    if (result.done || result.value == kj::none) {\n      // Stream ended. Return accumulated data.\n      // If we're reading text, add NUL terminator.\n      if constexpr (kj::isSameType<T, char>()) {\n        accumulated.add('\\0');\n      }\n      return js.resolvedPromise(accumulated.releaseAsArray());\n    }\n\n    auto& value = KJ_ASSERT_NONNULL(result.value);\n    auto jsval = jsg::JsValue(value.getHandle(js));\n\n    kj::ArrayPtr<const kj::byte> bytes;\n    kj::Maybe<kj::String> maybeOwnedString;\n\n    KJ_IF_SOME(str, jsval.tryCast<jsg::JsString>()) {\n      auto data = str.toUSVString(js);\n      bytes = data.asBytes();\n      maybeOwnedString = kj::mv(data);\n    } else KJ_IF_SOME(ab, jsval.tryCast<jsg::JsArrayBuffer>()) {\n      bytes = ab.asArrayPtr();\n    } else KJ_IF_SOME(view, jsval.tryCast<jsg::JsArrayBufferView>()) {\n      bytes = view.asArrayPtr();\n    } else {\n      auto error = js.typeError(\"ReadableStream provided a non-bytes value. Only ArrayBuffer, \"\n                                \"ArrayBufferView, or string are supported.\");\n      return active->reader->cancel(js, error).then(\n          js, [err = jsg::JsRef(js, error)](jsg::Lock& js) {\n        return js.rejectedPromise<kj::Array<T>>(err.getHandle(js));\n      });\n    }\n\n    if (accumulated.size() + bytes.size() > limit) {\n      auto error = js.rangeError(\"Memory limit would be exceeded before EOF.\");\n      return active->reader->cancel(js, error).then(\n          js, [err = jsg::JsRef(js, error)](jsg::Lock& js) {\n        return js.rejectedPromise<kj::Array<T>>(err.getHandle(js));\n      });\n    }\n\n    // Accumulate the bytes.\n    if constexpr (kj::isSameType<T, char>()) {\n      accumulated.addAll(bytes.asChars());\n    } else {\n      accumulated.addAll(bytes);\n    }\n\n    // Continue reading.\n    return readAllReadImpl(\n        js, kj::mv(active), kj::mv(accumulated), limit, kj::mv(cancelationToken));\n  });\n}\n\n}  // namespace workerd::api::streams\n"
  },
  {
    "path": "src/workerd/api/streams/readable-source-adapter.h",
    "content": "#include \"common.h\"\n#include \"readable-source.h\"\n#include \"readable.h\"\n\n#include <workerd/util/state-machine.h>\n\nnamespace workerd::api::streams {\n\n// We provide two utility adapters here: ReadableStreamSourceJsAdapter and\n// ReadableSourceKjAdapter.\n//\n// ReadableStreamSourceJsAdapter adapts a ReadableStreamSource to a JavaScript-friendly\n// interface. It provides methods that return JavaScript promises and use\n// JavaScript types. It is intended to be used by JavaScript code that wants\n// to read from a kj-backed stream. It takes ownership of the ReadableStreamSource\n// and holds it with an IoOwn, ensures that all operations are performed on the\n// correct IoContext, and safely cleans up after itself if the adapter is dropped.\n//\n//     ┌───────────────────────────────────────────┐\n//     │    ReadableStreamSourceJsAdapter          │\n//     │                                           │\n//     │  ┌─────────────────────────────────────┐  │\n//     │  │          JavaScript API             │  │\n//     │  │                                     │  │\n//     │  │  • read() → Promise<ReadResult>     │  │\n//     │  │  • readAllText() → Promise<string>  │  │\n//     │  │  • readAllBytes() → Promise<bytes>  │  │\n//     │  │  • close() → Promise<void>          │  │\n//     │  │  • cancel(reason)                   │  │\n//     │  │  • tryTee() → {branch1, branch2}    │  │\n//     │  └─────────────────────────────────────┘  │\n//     │                   │                       │\n//     │                   ▼                       │\n//     │  ┌─────────────────────────────────────┐  │\n//     │  │         State Management            │  │\n//     │  │                                     │  │\n//     │  │   Active ──► Closed                 │  │\n//     │  │     │          │                    │  │\n//     │  │     │          ▼                    │  │\n//     │  │     └─────► Canceled/Errored        │  │\n//     │  └─────────────────────────────────────┘  │\n//     │                   │                       │\n//     │                   ▼                       │\n//     │  ┌─────────────────────────────────────┐  │\n//     │  │        KJ Integration               │  │\n//     │  │                                     │  │\n//     │  │  IoOwn<ReadableStreamSource>        │  │\n//     │  │  WeakRef for safe references        │  │\n//     │  │  IoContext-aware operations         │  │\n//     │  └─────────────────────────────────────┘  │\n//     └───────────────────────────────────────────┘\n//                            │\n//                            ▼\n//     ┌───────────────────────────────────────────┐\n//     │       ReadableStreamSource                │\n//     │       (KJ Native Stream)                  │\n//     │                                           │\n//     │  • tryRead()                              │\n//     │  • pumpTo()                               │\n//     │  • tryGetLength()                         │\n//     │  • cancel()                               │\n//     └───────────────────────────────────────────┘\n//\n// The ReadableSourceKjAdapter adapts a ReadableStream to a KJ-friendly\n// ReadableStreamSource. It holds a strong reference to the ReadableStream and\n// locks it with a ReadableStreamDefaultReader. It is intended to be used by\n// KJ code that wants to read from a JavaScript-backed stream. It ensures that\n// all operations are performed on the correct IoContext, and safely cleans up\n// after itself if the adapter is dropped.\n//\n//     ┌───────────────────────────────────────────┐\n//     │         ReadableSourceKjAdapter           │\n//     │                                           │\n//     │  ┌─────────────────────────────────────┐  │\n//     │  │         KJ Native API               │  │\n//     │  │                                     │  │\n//     │  │  • tryRead(minBytes, maxBytes)      │  │\n//     │  │  • pumpTo(sink, end)                │  │\n//     │  │  • tryGetLength(encoding)           │  │\n//     │  │  • cancel(exception)                │  │\n//     │  │  • getPreferredEncoding()           │  │\n//     │  │  • tryTee() → none (unsupported)    │  │\n//     │  └─────────────────────────────────────┘  │\n//     │                   │                       │\n//     │                   ▼                       │\n//     │  ┌─────────────────────────────────────┐  │\n//     │  │       State Management              │  │\n//     │  │                                     │  │\n//     │  │   Active ──► Closed                 │  │\n//     │  │     │                               │  │\n//     │  │     └─────► Canceled/Errored        │  │\n//     │  └─────────────────────────────────────┘  │\n//     │                   │                       │\n//     │                   ▼                       │\n//     │  ┌─────────────────────────────────────┐  │\n//     │  │      JavaScript Integration         │  │\n//     │  │                                     │  │\n//     │  │  ReadableStreamDefaultReader        │  │\n//     │  │  WeakRef for safe references        │  │\n//     │  │  IoContext-aware JS operations      │  │\n//     │  │  Promise handling & async reads     │  │\n//     │  └─────────────────────────────────────┘  │\n//     └───────────────────────────────────────────┘\n//                            │\n//                            ▼\n//     ┌───────────────────────────────────────────┐\n//     │       JavaScript ReadableStream           │\n//     │                                           │\n//     │  • getReader()                            │\n//     │  • read() → Promise<{value, done}>        │\n//     │  • cancel(reason)                         │\n//     │  • locked, state properties               │\n//     └───────────────────────────────────────────┘\n\n// Adapts a ReadableStreamSource to a JavaScript-friendly interface.\nclass ReadableStreamSourceJsAdapter final {\n public:\n  ReadableStreamSourceJsAdapter(\n      jsg::Lock& js, IoContext& ioContext, kj::Own<ReadableSource> source);\n  KJ_DISALLOW_COPY_AND_MOVE(ReadableStreamSourceJsAdapter);\n  ~ReadableStreamSourceJsAdapter() noexcept(false);\n\n  // Returns true if the adapter is closed or canceled.\n  bool isClosed();\n\n  // If the adapter is canceled, returns the exception it was\n  // canceled with. Otherwise returns null.\n  kj::Maybe<const kj::Exception&> isCanceled() KJ_LIFETIMEBOUND;\n\n  // Cancels the underlying source if it is still active. If an\n  // exception is provided, the source will be errored with that.\n  // If no exception is provided, the source will be closed without\n  // error. All in-flight and pending read requests will be rejected.\n  // Unlike close(), the effect is immediate.\n  void cancel(kj::Exception exception);\n\n  // Like cancel() but with the error reason provided as a JS value.\n  void cancel(jsg::Lock& js, const jsg::JsValue& reason);\n\n  // Closes the stream immediatey without error if it is still\n  // active. All in-flight and pending read requests will be\n  // rejected with a cancelation error but the adapter will\n  // transition to the closed state rather than the errored state.\n  // If the adapter is already closed or canceled, this is a no-op.\n  void shutdown(jsg::Lock& js);\n\n  // Causes the adapter to enter the closing state. Any pending\n  // read requests will be allowed to complete but no new read requests\n  // will be accepted. The underlying source will be closed fully\n  // once all pending reads complete. If cancel() has already been\n  // called, this is a no-op and the returned promise resolves\n  // immediately. If cancel() is called while this is pending,\n  // the returned promise will reject with the same exception\n  // and the cancel will supersede the close.\n  jsg::Promise<void> close(jsg::Lock& js);\n\n  struct ReadOptions {\n    // The buffer to read into. The maximum number of bytes read\n    // is equal to the length of this buffer. The actual number of\n    // bytes read is indicated by the resolved value of the promise\n    // but will never exceed the length of this buffer.\n    jsg::BufferSource buffer;\n\n    // The optional minimum number of bytes to read. If not provided,\n    // the read will complete as soon as at least the mininum number\n    // of bytes to satisfy the minimum bytes-per-element of the input\n    // buffer is available.\n    // It is often more efficient to provide a minimum number of bytes\n    // because it allows to implementation to wait until larger chunks\n    // of data are available before completing the read.\n    kj::Maybe<size_t> minBytes;\n  };\n  struct ReadResult {\n    // The buffer containing the data that was read. The length\n    // of the buffer may be less than the length of the buffer\n    // provided in ReadOptions if fewer bytes were available.\n    // The identity of the underlying ArrayBuffer will be the same\n    // but the buffer itself will be a new type array view.\n    // of the same type as that provided in ReadOptions.\n    // If the read produced no data because the stream is\n    // closed, the type array will be zero length.\n    jsg::BufferSource buffer;\n\n    // True if the stream is now closed and no further reads\n    // are possible. If this is true, the buffer will be zero\n    // length.\n    bool done = false;\n  };\n\n  // Submit a read request. The returned promise resolves with a\n  // BufferSource containing the data that was read.\n  jsg::Promise<ReadResult> read(jsg::Lock& js, ReadOptions options);\n\n  // Utility function to read the entire stream as text. This is\n  // terminal in that once this is called, no further reads\n  // are possible. The entire stream will be read and concatenated\n  // and the resulting string returned. If the stream errors while\n  // reading, the promise will reject with the error.\n  // If there are pending reads when this is called, those reads\n  // will be allowed to complete first, and then the stream will\n  // be read to the end.\n  jsg::Promise<jsg::JsRef<jsg::JsString>> readAllText(jsg::Lock& js, uint64_t limit = kj::maxValue);\n\n  // Utility function to read the entire stream as bytes. This is\n  // terminal in that once this is called, no further reads\n  // are possible. The entire stream will be read and concatenated\n  // and the resulting bytes returned as a single BufferSource.\n  // If the stream errors while reading, the promise will reject\n  // with the error.\n  // If there are pending reads when this is called, those reads\n  // will be allowed to complete first, and then the stream will\n  // be read to the end.\n  jsg::Promise<jsg::BufferSource> readAllBytes(jsg::Lock& js, uint64_t limit = kj::maxValue);\n\n  // If the stream is still active, tries to get the total length,\n  // if known. If the length is not known, the encoding does not\n  // match the encoding of the underlying stream, or the stream is\n  // closed or errored, returns kj::none.\n  kj::Maybe<uint64_t> tryGetLength(StreamEncoding encoding);\n\n  struct Tee {\n    kj::Own<ReadableStreamSourceJsAdapter> branch1;\n    kj::Own<ReadableStreamSourceJsAdapter> branch2;\n  };\n  // Tees the stream into two branches. The returned Tee contains\n  // two new ReadableStreamSourceJsAdapter instances that will\n  // each receive the same data as this instance. Once this is called,\n  // this instance is no longer usable and all further operations\n  // on it will fail. Each branch operates independently; closing,\n  // canceling, or erroring one branch has no effect on the other branch.\n  // If this instance is already closed or canceled, or if there are\n  // in-flight or pending reads, this will throw.\n  kj::Maybe<Tee> tryTee(jsg::Lock& js, uint64_t limit = kj::maxValue);\n\n private:\n  struct Active;\n  struct Closed final {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"closed\"_kj;\n  };\n  struct Open {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"open\"_kj;\n    IoOwn<Active> active;\n  };\n\n  // State machine for tracking readable source adapter lifecycle:\n  //   Open -> Closed (normal close)\n  //   Open -> kj::Exception (error via cancel or read failure)\n  // Closed is terminal, kj::Exception is implicitly terminal via ErrorState.\n  using State = StateMachine<TerminalStates<Closed>,\n      ErrorState<kj::Exception>,\n      ActiveState<Open>,\n      Open,\n      Closed,\n      kj::Exception>;\n  State state;\n\n  kj::Rc<WeakRef<ReadableStreamSourceJsAdapter>> selfRef;\n};\n\n// ===============================================================================================\n\n// Adapts a ReadableStream to a KJ-friendly interface.\n// The adapter fully wraps and consumes the ReadableStream instance,\n// using a ReadableStreamDefaultReader to pull data from it.\n// When the adapter is destroyed or canceled, the reader is canceled\n// and both the reader and the stream references are dropped. Critically,\n// the stream is not usable after ownership is transferred to this adapter.\n// Initializing the adapter will fail if the stream is already locked or\n// disturbed.\n//\n// If the adapter is dropped, or canceled while there are pending reads,\n// the pending reads will be rejected with the same exception as the cancel.\n// Because JavaScript promises are not cancelable, reads that are in progress\n// won't be aborted immediately but the results will be ignored when they\n// complete and a best-effort will be made to interrupt the read as soon as\n// possible. If the stream is already closed, reads will complete immediately\n// with 0 bytes read. If the stream errors, reads will reject with the same\n// exception.\n//\n// The minRead contract is enforced. The adapter will attempt to read at\n// least minBytes on each read, under the isolate lock. If the stream ends\n// before minBytes can be satisfied, the read will complete with whatever\n// bytes were available and the adapter will remember that the stream is\n// closed.\n//\n// Concurrent/overlapping reads are not allowed. If a read is already\n// pending, further read attempts will be rejected.\n//\n// While the caller is expected to follow the ReadableStreamSource contract\n// and keep the adapter and buffer alive until the read promises resolve,\n// there are some protections in place to avoid use-after-free if the caller\n// drops the adapter. There's nothing we can do if the caller drops the\n// buffer, however, so that is still a hard requirement.\n// TODO(safety): This can be made safer by having read take a kj::Array\n// as input instead of a raw pointer and size, then having the read return\n// the filled in Array after the read completes, but that's a larger refactor.\nclass ReadableSourceKjAdapter final: public ReadableSource {\n public:\n  enum class MinReadPolicy {\n    // The read will complete as soon as at least minBytes have been read,\n    // even if more bytes are available and the buffer is not full. This\n    // may result in more read calls (keeping in mind that each read needs\n    // to acquire the isolate lock) but may keep the stream flowing more.\n    IMMEDIATE,\n    // The read will attempt to fill the entire buffer until either\n    // maxBytes, the stream ends, or we determine the buffer is \"full enough\".\n    // This will result in fewer read calls (and thus grabbing the isolate\n    // lock less often) but may result in higher latency for each read.\n    OPPORTUNISTIC,\n  };\n  struct Options {\n    MinReadPolicy minReadPolicy;\n  };\n\n  ReadableSourceKjAdapter(jsg::Lock& js,\n      IoContext& ioContext,\n      jsg::Ref<ReadableStream> stream,\n      Options options = {.minReadPolicy = MinReadPolicy::OPPORTUNISTIC});\n  ~ReadableSourceKjAdapter() noexcept(false);\n\n  // Attempts to read at least minBytes and up to maxBytes into the provided\n  // buffer. The returned promise resolves with the actual number of bytes read,\n  // which may be less than minBytes if the stream is fully consumed.\n  //\n  // If the stream is already closed, the returned promise resolves\n  // immediately with 0. If the stream is canceled or errors, the returned\n  // promise rejects with the same exception.\n  //\n  // minBytes must be less than or equal to maxBytes and greater than zero.\n  // If any values outside that range are provided, minBytes will be clamped\n  // to the range [1, maxBytes].\n  //\n  // Per the contact of tryRead, it is the caller's responsibility to ensure\n  // that both the buffer and this adapter remain alive until the returned\n  // promise resolves! It is also the caller's responsibility to ensure that\n  // buffer is at least maxBytes in length. However, there are some protections\n  // implemented to avoid use-after-free if the adapter is dropped while a read\n  // is in progress.\n  //\n  // The returned promise will never resolve with more than maxBytes.\n  kj::Promise<size_t> read(kj::ArrayPtr<kj::byte> buffer, size_t minBytes) override;\n\n  // Reads all remaining bytes from the stream and returns them.\n  kj::Promise<kj::Array<const kj::byte>> readAllBytes(size_t limit) override;\n\n  // Reads all remaining bytes from the stream and returns them as a string.\n  kj::Promise<kj::String> readAllText(size_t limit) override;\n\n  // Fully consume the stream and write it to the provided WritableStreamSink.\n  // If \"end\" is true, the output stream will be ended once the input\n  // stream is fully consumed.\n  // Per the contract of pumpTo, it is the caller's responsibility to ensure\n  // that both the WritableStreamSink and this adapter remain alive until\n  // the returned promise resolves!\n  kj::Promise<DeferredProxy<void>> pumpTo(WritableSink& output, EndAfterPump end) override;\n\n  // If the stream is still active, tries to get the total length,\n  // if known. If the length is not known, the encoding does not\n  // match the encoding of the underlying stream, or the stream is closed\n  // or errored, returns kj::none.\n  kj::Maybe<size_t> tryGetLength(StreamEncoding encoding) override;\n\n  // Cancels the underlying source if it is still active.\n  void cancel(kj::Exception reason) override;\n\n  StreamEncoding getEncoding() override {\n    // Our underlying ReadableStream produces non-encoded bytes (for now)\n    return StreamEncoding::IDENTITY;\n  };\n\n  Tee tee(size_t limit) override;\n\n  struct ReadContext;\n  KJ_DECLARE_NON_POLYMORPHIC(ReadContext);\n\n private:\n  struct Active;\n  KJ_DECLARE_NON_POLYMORPHIC(Active);\n  struct KjClosed {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"closed\"_kj;\n  };\n  struct KjOpen {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"open\"_kj;\n    kj::Own<Active> active;\n  };\n\n  // State machine for tracking readable source adapter lifecycle:\n  //   KjOpen -> KjClosed (normal close)\n  //   KjOpen -> kj::Exception (error via cancel or read failure)\n  // KjClosed is terminal, kj::Exception is implicitly terminal via ErrorState.\n  using KjState = StateMachine<TerminalStates<KjClosed>,\n      ErrorState<kj::Exception>,\n      ActiveState<KjOpen>,\n      KjOpen,\n      KjClosed,\n      kj::Exception>;\n  KjState state;\n  const Options options;\n  kj::Rc<WeakRef<ReadableSourceKjAdapter>> selfRef;\n\n  // Checks if the inner Active state is Canceling or Canceled.\n  // If so, transitions to error state and throws the exception.\n  void throwIfCancelingOrCanceled(Active& active);\n\n  // Checks if the inner Active state is Canceling or Canceled.\n  // If so, transitions to error state and returns the exception.\n  kj::Maybe<kj::Exception> checkCancelingOrCanceled(Active& active);\n\n  kj::Promise<size_t> readImpl(Active& active, kj::ArrayPtr<kj::byte> buffer, size_t minBytes);\n\n  static kj::Promise<void> pumpToImpl(\n      kj::Own<Active> active, WritableSink& output, EndAfterPump end);\n  static jsg::Promise<kj::Own<ReadContext>> readInternal(\n      jsg::Lock& js, kj::Own<ReadContext> context, MinReadPolicy minReadPolicy);\n\n  template <typename T>\n  kj::Promise<kj::Array<T>> readAllImpl(size_t limit);\n\n  struct CancelationToken final {\n    kj::Rc<WeakRef<CancelationToken>> selfRef;\n    inline CancelationToken()\n        : selfRef(kj::rc<WeakRef<CancelationToken>>(kj::Badge<CancelationToken>{}, *this)) {}\n    inline ~CancelationToken() {\n      selfRef->invalidate();\n    }\n    inline kj::Rc<WeakRef<CancelationToken>> getWeakRef() {\n      return selfRef.addRef();\n    }\n  };\n\n  template <typename T>\n  static jsg::Promise<kj::Array<T>> readAllReadImpl(jsg::Lock& js,\n      IoOwn<Active> active,\n      kj::Vector<T> accumulated,\n      size_t limit,\n      kj::Rc<WeakRef<CancelationToken>> cancelationToken);\n};\n\n}  // namespace workerd::api::streams\n"
  },
  {
    "path": "src/workerd/api/streams/readable-source-test.c++",
    "content": "#include \"readable-source.h\"\n\n#include <workerd/api/streams/writable-sink.h>\n#include <workerd/jsg/jsg-test.h>\n#include <workerd/tests/test-fixture.h>\n#include <workerd/util/own-util.h>\n#include <workerd/util/stream-utils.h>\n\n#include <kj/async-io.h>\n#include <kj/test.h>\n\n// We Thank Claude for Tests.\n\nnamespace workerd::api::streams {\nnamespace {\n\n// Mock WritableSink for testing pumpTo functionality\nclass MockWritableSink final: public WritableSink {\n public:\n  MockWritableSink() = default;\n  ~MockWritableSink() = default;\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::byte> buffer) override {\n    writeCallCount++;\n    totalBytesWritten += buffer.size();\n    writtenData.addAll(buffer);\n\n    if (shouldFailWrite) {\n      KJ_FAIL_REQUIRE(\"Expected failure\");\n    }\n\n    co_return;\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const kj::byte>> pieces) override {\n    multiWriteCallCount++;\n    for (auto piece: pieces) {\n      totalBytesWritten += piece.size();\n      writtenData.addAll(piece);\n    }\n\n    if (shouldFailWrite) {\n      return KJ_EXCEPTION(FAILED, \"Mock multi-write failure\");\n    }\n\n    return kj::READY_NOW;\n  }\n\n  kj::Promise<void> end() override {\n    endCallCount++;\n    isEnded = true;\n\n    if (shouldFailEnd) {\n      KJ_FAIL_REQUIRE(\"Expected failure\");\n    }\n\n    co_return;\n  }\n\n  void abort(kj::Exception reason) override {\n    abortCallCount++;\n    isAborted = true;\n    abortReason = kj::mv(reason);\n  }\n\n  rpc::StreamEncoding disownEncodingResponsibility() override {\n    disownCallCount++;\n    return encoding;\n  }\n\n  rpc::StreamEncoding getEncoding() override {\n    return encoding;\n  }\n\n  // Test state\n  uint32_t writeCallCount = 0;\n  uint32_t multiWriteCallCount = 0;\n  uint32_t endCallCount = 0;\n  uint32_t abortCallCount = 0;\n  uint32_t disownCallCount = 0;\n\n  size_t totalBytesWritten = 0;\n  bool isEnded = false;\n  bool isAborted = false;\n  kj::Maybe<kj::Exception> abortReason;\n\n  kj::Vector<kj::byte> writtenData;\n  rpc::StreamEncoding encoding = rpc::StreamEncoding::IDENTITY;\n\n  // Control behavior\n  bool shouldFailWrite = false;\n  bool shouldFailEnd = false;\n};\n\n// Memory-based AsyncInputStream for factory function tests\nclass MemoryAsyncInputStream: public kj::AsyncInputStream {\n public:\n  MemoryAsyncInputStream(kj::ArrayPtr<const kj::byte> data): data_(data) {}\n\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    auto dest = kj::arrayPtr(static_cast<kj::byte*>(buffer), maxBytes);\n    size_t amount = kj::min(dest.size(), data_.size());\n    dest.first(amount).copyFrom(data_.first(amount));\n    data_ = data_.slice(amount);\n    return amount;\n  }\n\n  kj::Maybe<uint64_t> tryGetLength() override {\n    return data_.size();\n  }\n\n private:\n  kj::ArrayPtr<const kj::byte> data_;\n};\n\nclass MemoryAsyncOutputStream final: public kj::AsyncOutputStream {\n public:\n  kj::Promise<void> write(kj::ArrayPtr<const kj::byte> buffer) override {\n    data.addAll(buffer);\n\n    if (writeShouldError) {\n      KJ_FAIL_REQUIRE(\"Expected failure\");\n    }\n\n    co_return;\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const kj::byte>> pieces) override {\n    for (auto piece: pieces) {\n      data.addAll(piece);\n    }\n\n    if (writeShouldError) {\n      KJ_FAIL_REQUIRE(\"Expected failure\");\n    }\n\n    co_return;\n  }\n\n  kj::Promise<void> whenWriteDisconnected() override {\n    return kj::NEVER_DONE;\n  }\n\n  bool writeShouldError = false;\n\n  kj::Vector<kj::byte> data;\n};\n\n// ======================================================================================\n// Core ReadableSource Interface Tests\n\nKJ_TEST(\"ReadableSource basic read operations (full)\") {\n  TestFixture fixture;\n  kj::byte testData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  KJ_ASSERT(source->getEncoding() == rpc::StreamEncoding::IDENTITY);\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    kj::FixedArray<kj::byte, 15> buffer;\n\n    KJ_ASSERT(KJ_ASSERT_NONNULL(source->tryGetLength(rpc::StreamEncoding::IDENTITY)) == 10);\n    KJ_ASSERT(source->tryGetLength(rpc::StreamEncoding::GZIP) == kj::none);\n\n    // Read at least 5 bytes, at most 15.\n    auto bytesRead = co_await source->read(buffer, 5);\n    KJ_ASSERT(bytesRead == 10);  // Should read all available data\n\n    KJ_ASSERT(buffer.asPtr().first(bytesRead) == testData);\n\n    // Next read should return nothing\n    bytesRead = co_await source->read(buffer, 1);\n    KJ_ASSERT(bytesRead == 0);  // EOF\n  });\n}\n\nKJ_TEST(\"ReadableSource basic read operations (partial)\") {\n  TestFixture fixture;\n  kj::byte testData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    kj::FixedArray<kj::byte, 5> buffer;\n\n    KJ_ASSERT(KJ_ASSERT_NONNULL(source->tryGetLength(rpc::StreamEncoding::IDENTITY)) == 10);\n    KJ_ASSERT(source->tryGetLength(rpc::StreamEncoding::GZIP) == kj::none);\n\n    // Read at most 5 bytes\n    auto bytesRead = co_await source->read(buffer, 5);\n    KJ_ASSERT(bytesRead == 5);  // Should read all available data\n\n    KJ_ASSERT(buffer.asPtr().first(bytesRead) == kj::arrayPtr(testData, 5));\n\n    // Next read should return 5 byte\n    bytesRead = co_await source->read(buffer, 1);\n    KJ_ASSERT(bytesRead == 5);  // EOF\n    KJ_ASSERT(buffer.asPtr().first(bytesRead) == kj::arrayPtr(testData + 5, 5));\n\n    // Next read should return nothing\n    bytesRead = co_await source->read(buffer, 1);\n    KJ_ASSERT(bytesRead == 0);  // EOF\n  });\n}\n\nKJ_TEST(\"ReadableSource concurrent reads forbidden\") {\n  TestFixture fixture;\n  kj::byte testData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    kj::FixedArray<kj::byte, 5> buffer;\n\n    auto readPromise1 = source->read(buffer, 5);\n    auto readPromise2 = source->read(buffer, 5);\n\n    try {\n      co_await readPromise2;\n      KJ_FAIL_REQUIRE(\"was expected to throw\");\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(exception.getDescription().contains(\"already being read\"));\n    }\n\n    // But the first read should still succeed.\n    auto bytesRead = co_await readPromise1;\n    KJ_ASSERT(bytesRead == 5);\n  });\n}\n\n// ======================================================================================\n// PumpTo Tests\n\nKJ_TEST(\"ReadableSource pumpTo with end\") {\n  TestFixture fixture;\n  kj::byte testData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  MockWritableSink sink;\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await environment.context.waitForDeferredProxy(source->pumpTo(sink, EndAfterPump::YES));\n    KJ_ASSERT(sink.totalBytesWritten == 10);\n    KJ_ASSERT(sink.isEnded);\n    KJ_ASSERT(sink.writtenData == testData);\n  });\n}\n\nKJ_TEST(\"ReadableSource pumpTo without end\") {\n  TestFixture fixture;\n  kj::byte testData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  MockWritableSink sink;\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await environment.context.waitForDeferredProxy(source->pumpTo(sink, EndAfterPump::NO));\n    KJ_ASSERT(sink.totalBytesWritten == 10);\n    KJ_ASSERT(!sink.isEnded);\n    KJ_ASSERT(sink.writtenData == testData);\n  });\n}\n\nKJ_TEST(\"ReadableSource large pumpTo with end\") {\n  TestFixture fixture;\n  auto testData = kj::heapArray<kj::byte>(52 * 1024);\n  testData.asPtr().fill(42);\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  MockWritableSink sink;\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await environment.context.waitForDeferredProxy(source->pumpTo(sink, EndAfterPump::YES));\n    KJ_ASSERT(sink.totalBytesWritten == 52 * 1024);\n    KJ_ASSERT(sink.isEnded);\n    KJ_ASSERT(sink.writtenData == testData);\n  });\n}\n\nKJ_TEST(\"ReadableSource large pumpTo canceled\") {\n  TestFixture fixture;\n  auto testData = kj::heapArray<kj::byte>(52 * 1024);\n  testData.asPtr().fill(42);\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  MockWritableSink sink;\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    auto promise =\n        environment.context.waitForDeferredProxy(source->pumpTo(sink, EndAfterPump::YES));\n    source->cancel(KJ_EXCEPTION(FAILED, \"test abort\"));\n    try {\n      co_await promise;\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(exception.getDescription().contains(\"test abort\"));\n    }\n  });\n}\n\nKJ_TEST(\"ReadableSource large pumpTo canceled before\") {\n  TestFixture fixture;\n  auto testData = kj::heapArray<kj::byte>(52 * 1024);\n  testData.asPtr().fill(42);\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  MockWritableSink sink;\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    source->cancel(KJ_EXCEPTION(FAILED, \"test abort\"));\n    auto promise =\n        environment.context.waitForDeferredProxy(source->pumpTo(sink, EndAfterPump::YES));\n    try {\n      co_await promise;\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(exception.getDescription().contains(\"test abort\"));\n    }\n  });\n}\n\nKJ_TEST(\"ReadableSource large pumpTo closed\") {\n  TestFixture fixture;\n  auto testData = kj::heapArray<kj::byte>(52 * 1024);\n  testData.asPtr().fill(42);\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  MockWritableSink sink;\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    auto& context = environment.context;\n    co_await source->readAllBytes(kj::maxValue);\n    co_await context.waitForDeferredProxy(source->pumpTo(sink, EndAfterPump::YES));\n    KJ_ASSERT(sink.totalBytesWritten == 0);\n  });\n}\n\nKJ_TEST(\"ReadableSource large pumpTo, concurrent read fails\") {\n  TestFixture fixture;\n  auto testData = kj::heapArray<kj::byte>(52 * 1024);\n  testData.asPtr().fill(42);\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  MockWritableSink sink;\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    auto promise =\n        environment.context.waitForDeferredProxy(source->pumpTo(sink, EndAfterPump::YES));\n\n    // Concurrent read should fail.\n    try {\n      co_await source->readAllBytes(kj::maxValue);\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(exception.getDescription().contains(\"already being read\"));\n    }\n\n    // But the pump should still succeed.\n    co_await promise;\n    KJ_ASSERT(sink.totalBytesWritten == 52 * 1024);\n\n    // And we can read again afterwards, but will be at EOF.\n    auto allBytes = co_await source->readAllBytes(kj::maxValue);\n    KJ_ASSERT(allBytes.size() == 0);\n  });\n}\n\n// ======================================================================================\n// Read all tests\n\nKJ_TEST(\"ReadableSource read all bytes (small)\") {\n  TestFixture fixture;\n  kj::byte testData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  KJ_ASSERT(source->getEncoding() == rpc::StreamEncoding::IDENTITY);\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    auto allBytes = co_await source->readAllBytes(100);\n    KJ_ASSERT(allBytes.size() == 10);\n    KJ_ASSERT(allBytes.asPtr() == kj::ArrayPtr<const kj::byte>(testData, 10));\n  });\n}\n\nKJ_TEST(\"ReadableSource read all bytes (large)\") {\n  TestFixture fixture;\n  auto testData = kj::heapArray<kj::byte>(52 * 1024);\n  testData.asPtr().fill(42);\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  KJ_ASSERT(source->getEncoding() == rpc::StreamEncoding::IDENTITY);\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    auto allBytes = co_await source->readAllBytes((52 * 1024) + 1);\n    KJ_ASSERT(allBytes.size() == 52 * 1024);\n    KJ_ASSERT(allBytes.asPtr() == testData);\n  });\n}\n\nKJ_TEST(\"ReadableSource read all text (small)\") {\n  TestFixture fixture;\n  kj::byte testData[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'};\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  KJ_ASSERT(source->getEncoding() == rpc::StreamEncoding::IDENTITY);\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    auto allText = co_await source->readAllBytes(100);\n    KJ_ASSERT(allText.size() == 10);\n    KJ_ASSERT(allText.asPtr() == kj::ArrayPtr<const kj::byte>(testData, 10));\n  });\n}\n\nKJ_TEST(\"ReadableSource read all text (large)\") {\n  TestFixture fixture;\n  auto testData = kj::heapArray<char>(52 * 1024);\n  testData.asPtr().fill('a');\n\n  MemoryAsyncInputStream input(testData.asBytes());\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  KJ_ASSERT(source->getEncoding() == rpc::StreamEncoding::IDENTITY);\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    auto allText = co_await source->readAllText((52 * 1024) + 1);\n    KJ_ASSERT(allText.size() == 52 * 1024);\n    KJ_ASSERT(allText.asPtr() == testData);\n  });\n}\n\nKJ_TEST(\"ReadableSource read all aborted (after read)\") {\n  TestFixture fixture;\n  auto testData = kj::heapArray<kj::byte>(52 * 1024);\n  testData.asPtr().fill(42);\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    auto promise = source->readAllBytes(52 * 1024);\n    source->cancel(KJ_EXCEPTION(FAILED, \"test abort\"));\n    try {\n      co_await promise;\n      KJ_FAIL_REQUIRE(\"was expected to throw\");\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(exception.getDescription().contains(\"test abort\"));\n    }\n  });\n}\n\nKJ_TEST(\"ReadableSource read all aborted (prior to read)\") {\n  TestFixture fixture;\n  auto testData = kj::heapArray<kj::byte>(52 * 1024);\n  testData.asPtr().fill(42);\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    source->cancel(KJ_EXCEPTION(FAILED, \"test abort\"));\n    auto promise = source->readAllBytes(52 * 1024);\n    try {\n      co_await promise;\n      KJ_FAIL_REQUIRE(\"was expected to throw\");\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(exception.getDescription().contains(\"test abort\"));\n    }\n  });\n}\n\nKJ_TEST(\"ReadableSource read all aborted (dropped)\") {\n  TestFixture fixture;\n  auto testData = kj::heapArray<kj::byte>(52 * 1024);\n  testData.asPtr().fill(42);\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    auto promise = source->readAllBytes(52 * 1024);\n    { auto freeme = kj::mv(source); }\n    try {\n      co_await promise;\n      KJ_FAIL_REQUIRE(\"was expected to throw\");\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(exception.getDescription().contains(\"stream was dropped\"));\n    }\n  });\n}\n\n// ======================================================================================\n// Tee tests\n\nKJ_TEST(\"ReadableSource tee (small, no limit)\") {\n  TestFixture fixture;\n  kj::byte testData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  auto tee = source->tee(200);\n  auto branch1 = kj::mv(tee.branch1);\n  auto branch2 = kj::mv(tee.branch2);\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    auto allBytes1 = co_await branch1->readAllBytes(100);\n    auto allBytes2 = co_await branch2->readAllBytes(100);\n    KJ_ASSERT(allBytes1.size() == 10);\n    KJ_ASSERT(allBytes1 == testData);\n    KJ_ASSERT(allBytes2.size() == 10);\n    KJ_ASSERT(allBytes2 == testData);\n\n    // Original source should be closed and return EOF\n    kj::FixedArray<kj::byte, 10> buffer;\n    auto bytesRead = co_await source->read(buffer, 1);\n    KJ_ASSERT(bytesRead == 0);  // EOF\n  });\n}\n\nKJ_TEST(\"ReadableSource tee (small, no limit, independent)\") {\n  TestFixture fixture;\n  kj::byte testData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  auto tee = source->tee(200);\n  auto branch1 = kj::mv(tee.branch1);\n  auto branch2 = kj::mv(tee.branch2);\n  branch2->cancel(KJ_EXCEPTION(FAILED, \"test abort\"));\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    auto allBytes1 = co_await branch1->readAllBytes(100);\n\n    try {\n      co_await branch2->readAllBytes(100);\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(exception.getDescription().contains(\"test abort\"));\n    }\n    KJ_ASSERT(allBytes1.size() == 10);\n    KJ_ASSERT(allBytes1.asPtr() == testData);\n\n    // Original source should be closed and return EOF\n    kj::FixedArray<kj::byte, 10> buffer;\n    auto bytesRead = co_await source->read(buffer, 1);\n    KJ_ASSERT(bytesRead == 0);  // EOF\n  });\n}\n\nKJ_TEST(\"ReadableSource tee (large, no limit)\") {\n  TestFixture fixture;\n  auto testData = kj::heapArray<kj::byte>(52 * 1024);\n  testData.asPtr().fill(42);\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  auto tee = source->tee(0xffffffff);\n  auto branch1 = kj::mv(tee.branch1);\n  auto branch2 = kj::mv(tee.branch2);\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    auto allBytes1 = co_await branch1->readAllBytes(kj::maxValue);\n    auto allBytes2 = co_await branch2->readAllBytes(kj::maxValue);\n    KJ_ASSERT(allBytes1.size() == 52 * 1024);\n    KJ_ASSERT(allBytes1 == testData);\n    KJ_ASSERT(allBytes2.size() == 52 * 1024);\n    KJ_ASSERT(allBytes2 == testData);\n\n    // Original source should be closed and return EOF\n    kj::FixedArray<kj::byte, 10> buffer;\n    auto bytesRead = co_await source->read(buffer, 1);\n    KJ_ASSERT(bytesRead == 0);  // EOF\n  });\n}\n\nKJ_TEST(\"ReadableSource tee (large, buffer limit)\") {\n  TestFixture fixture;\n  auto testData = kj::heapArray<kj::byte>(1024);\n  testData.asPtr().fill(42);\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  auto tee = source->tee(100);\n  auto branch1 = kj::mv(tee.branch1);\n  auto branch2 = kj::mv(tee.branch2);\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    try {\n      co_await branch1->readAllBytes(kj::maxValue);\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(exception.getDescription().contains(\"buffer limit exceeded\"));\n    }\n  });\n}\n\nKJ_TEST(\"ReadableSource after read\") {\n  TestFixture fixture;\n  auto testData = kj::heapArray<kj::byte>(1024);\n  testData.asPtr().fill(42);\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    kj::FixedArray<kj::byte, 512> buffer;\n    buffer.asPtr().first(512).fill(0);\n    buffer.asPtr().slice(512).fill(1);\n    auto bytesRead = co_await source->read(buffer, 1);\n    KJ_ASSERT(bytesRead == 512);\n    KJ_ASSERT(buffer.asPtr().first(bytesRead) == testData.asPtr().first(bytesRead));\n\n    auto tee = source->tee(0xffffffff);\n    auto branch1 = kj::mv(tee.branch1);\n    auto branch2 = kj::mv(tee.branch2);\n\n    // Each branch should get the remaining data\n    auto allBytes1 = co_await branch1->readAllBytes(kj::maxValue);\n    auto allBytes2 = co_await branch2->readAllBytes(kj::maxValue);\n    KJ_ASSERT(allBytes1.size() == 512);\n    KJ_ASSERT(allBytes1 == testData.asPtr().slice(512));\n    KJ_ASSERT(allBytes2.size() == 512);\n    KJ_ASSERT(allBytes2 == testData.asPtr().slice(512));\n  });\n}\n\n// ======================================================================================\n// ReadableSourceWrapper Tests\n\nKJ_TEST(\"ReadableSourceWrapper delegation\") {\n  TestFixture fixture;\n  kj::byte testData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  class TestWrapper: public ReadableSourceWrapper {\n   public:\n    TestWrapper(kj::Own<ReadableSource> inner): ReadableSourceWrapper(kj::mv(inner)) {}\n  };\n  auto wrapper = kj::heap<TestWrapper>(kj::mv(source));\n\n  KJ_ASSERT(wrapper->getEncoding() == rpc::StreamEncoding::IDENTITY);\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    kj::FixedArray<kj::byte, 15> buffer;\n\n    KJ_ASSERT(KJ_ASSERT_NONNULL(wrapper->tryGetLength(rpc::StreamEncoding::IDENTITY)) == 10);\n    KJ_ASSERT(wrapper->tryGetLength(rpc::StreamEncoding::GZIP) == kj::none);\n\n    // Read at least 5 bytes, at most 15.\n    auto bytesRead = co_await wrapper->read(buffer, 5);\n    KJ_ASSERT(bytesRead == 10);  // Should read all available data\n\n    KJ_ASSERT(buffer.asPtr().first(bytesRead) == testData);\n\n    // Next read should return nothing\n    bytesRead = co_await wrapper->read(buffer, 1);\n    KJ_ASSERT(bytesRead == 0);  // EOF\n  });\n}\n\nKJ_TEST(\"ReadableSourceWrapper release\") {\n  TestFixture fixture;\n  kj::byte testData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n\n  MemoryAsyncInputStream input(testData);\n  auto fakeOwn = kj::Own<MemoryAsyncInputStream>(&input, kj::NullDisposer::instance);\n  auto source = newReadableSource(kj::mv(fakeOwn));\n\n  class TestWrapper: public ReadableSourceWrapper {\n   public:\n    TestWrapper(kj::Own<ReadableSource> inner): ReadableSourceWrapper(kj::mv(inner)) {}\n  };\n  auto wrapper = kj::heap<TestWrapper>(kj::mv(source));\n\n  source = wrapper->release();\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    kj::FixedArray<kj::byte, 15> buffer;\n\n    // Using the wrapper shuld fail.\n    try {\n      co_await wrapper->read(buffer, 1);\n      KJ_FAIL_REQUIRE(\"was expected to throw\");\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(exception.getDescription().contains(\"inner != nullptr\"));\n    }\n\n    KJ_ASSERT(KJ_ASSERT_NONNULL(source->tryGetLength(rpc::StreamEncoding::IDENTITY)) == 10);\n    KJ_ASSERT(source->tryGetLength(rpc::StreamEncoding::GZIP) == kj::none);\n\n    // Read at least 5 bytes, at most 15.\n    auto bytesRead = co_await source->read(buffer, 5);\n    KJ_ASSERT(bytesRead == 10);  // Should read all available data\n\n    KJ_ASSERT(buffer.asPtr().first(bytesRead) == testData);\n\n    // Next read should return nothing\n    bytesRead = co_await source->read(buffer, 1);\n    KJ_ASSERT(bytesRead == 0);  // EOF\n  });\n}\n\n// ======================================================================================\n// Factory Function Tests\n\nKJ_TEST(\"newClosedReadableSource\") {\n  TestFixture fixture;\n  auto source = newClosedReadableSource();\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    kj::byte buffer[10];\n    auto bytesRead = co_await source->read(buffer, 1);\n    KJ_ASSERT(bytesRead == 0);  // EOF\n  });\n}\n\nKJ_TEST(\"newErroredReadableSource\") {\n  TestFixture fixture;\n  auto exception = KJ_EXCEPTION(FAILED, \"test error\");\n  auto source = newErroredReadableSource(kj::cp(exception));\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    kj::byte buffer[10];\n    try {\n      co_await source->read(buffer, 1);\n      KJ_FAIL_REQUIRE(\"was expected to throw\");\n    } catch (...) {\n      auto caught = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(caught.getDescription().contains(\"test error\"));\n    }\n  });\n}\n\nKJ_TEST(\"newReadableSourceFromBytes (copy)\") {\n  TestFixture fixture;\n  kj::byte testData[] = {1, 2, 3, 4, 5};\n  auto source = newReadableSourceFromBytes(kj::ArrayPtr<const kj::byte>(testData, 5));\n  testData[0] = 42;  // Modify original to ensure copy was made.\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    kj::FixedArray<kj::byte, 5> buffer;\n    auto bytesRead = co_await source->read(buffer, 1);\n    KJ_ASSERT(bytesRead == 5);\n    KJ_ASSERT(buffer[0] == 1);  // Original data\n\n    // Next read should return nothing\n    bytesRead = co_await source->read(buffer, 1);\n    KJ_ASSERT(bytesRead == 0);  // EOF\n  });\n}\n\nKJ_TEST(\"newReadableSourceFromBytes (owned)\") {\n  TestFixture fixture;\n  auto ownedData = kj::heapArray<kj::byte>(5);\n  ownedData.asPtr().fill(0);\n  auto ptr = ownedData.asPtr();\n  auto source = newReadableSourceFromBytes(ptr, kj::heap(kj::mv(ownedData)));\n  ptr[0] = 42;  // Modify original to ensure copy was made.\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    kj::FixedArray<kj::byte, 5> buffer;\n    auto bytesRead = co_await source->read(buffer, 1);\n    KJ_ASSERT(bytesRead == 5);\n    KJ_ASSERT(buffer[0] == 42);  // Modified data\n\n    // Next read should return nothing\n    bytesRead = co_await source->read(buffer, 1);\n    KJ_ASSERT(bytesRead == 0);  // EOF\n  });\n}\n\nKJ_TEST(\"newReadableSourceFromDelegate\") {\n  TestFixture fixture;\n  kj::byte testData[] = {1, 2, 3, 4, 5};\n  size_t position = 0;\n\n  auto producer = [&](kj::ArrayPtr<kj::byte> buffer, size_t minBytes) -> kj::Promise<size_t> {\n    if (position >= 5) {\n      return static_cast<size_t>(0);  // EOF\n    }\n\n    size_t available = 5 - position;\n    size_t toRead = kj::min(available, buffer.size());\n\n    if (toRead > 0) {\n      memcpy(buffer.begin(), testData + position, toRead);\n      position += toRead;\n    }\n\n    return toRead;\n  };\n\n  auto source = newReadableSourceFromProducer(kj::mv(producer), 5);\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    kj::FixedArray<kj::byte, 5> buffer;\n    auto bytesRead = co_await source->read(buffer, 1);\n    KJ_ASSERT(bytesRead == 5);\n    KJ_ASSERT(buffer.asPtr().first(bytesRead) == kj::ArrayPtr<const kj::byte>(testData, 5));\n\n    // Next read should return nothing\n    bytesRead = co_await source->read(buffer, 1);\n    KJ_ASSERT(bytesRead == 0);  // EOF\n  });\n}\n\nKJ_TEST(\"newReadableSourceFromDelegate (not enough bytes)\") {\n  TestFixture fixture;\n  kj::byte testData[] = {1, 2, 3, 4, 5};\n  size_t position = 0;\n\n  auto producer = [&](kj::ArrayPtr<kj::byte> buffer, size_t minBytes) -> kj::Promise<size_t> {\n    if (position >= 5) {\n      return static_cast<size_t>(0);  // EOF\n    }\n\n    size_t available = 5 - position;\n    size_t toRead = kj::min(available, buffer.size());\n\n    if (toRead > 0) {\n      memcpy(buffer.begin(), testData + position, toRead);\n      position += toRead;\n    }\n\n    return toRead;\n  };\n\n  auto source = newReadableSourceFromProducer(kj::mv(producer), 10);\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    kj::FixedArray<kj::byte, 5> buffer;\n    auto bytesRead = co_await source->read(buffer, 1);\n    KJ_ASSERT(bytesRead == 5);\n    KJ_ASSERT(buffer.asPtr().first(bytesRead) == kj::ArrayPtr<const kj::byte>(testData, 5));\n\n    // Next read should fail since producer did not produce the expected number of bytes\n    try {\n      co_await source->read(buffer, 1);\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(exception.getDescription().contains(\"ended stream early\"));\n    }\n  });\n}\n\n// ======================================================================================\n// Gzip encoding\n\nKJ_TEST(\"Gzip encoded stream\") {\n  TestFixture fixture;\n  static constexpr kj::byte data[] = {31, 139, 8, 0, 0, 0, 0, 0, 0, 3, 43, 206, 207, 77, 85, 72, 73,\n    44, 73, 84, 40, 201, 87, 72, 175, 202, 44, 0, 0, 40, 58, 113, 128, 17, 0, 0, 0};\n  auto inner = newMemoryInputStream(data);\n  auto source = newEncodedReadableSource(rpc::StreamEncoding::GZIP, kj::mv(inner));\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    // Should decompress on read all...\n    auto allBytes = co_await source->readAllBytes(kj::maxValue);\n    KJ_ASSERT(allBytes == \"some data to gzip\"_kjb);\n  });\n}\n\nKJ_TEST(\"Gzip encoded stream (pumpTo)\") {\n  TestFixture fixture;\n  static constexpr kj::byte data[] = {31, 139, 8, 0, 0, 0, 0, 0, 0, 3, 43, 206, 207, 77, 85, 72, 73,\n    44, 73, 84, 40, 201, 87, 72, 175, 202, 44, 0, 0, 40, 58, 113, 128, 17, 0, 0, 0};\n  auto inner = newMemoryInputStream(data);\n  auto source = newEncodedReadableSource(rpc::StreamEncoding::GZIP, kj::mv(inner));\n\n  MockWritableSink sink;\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await environment.context.waitForDeferredProxy(source->pumpTo(sink, EndAfterPump::YES));\n  });\n\n  KJ_ASSERT(sink.writtenData == \"some data to gzip\"_kjb);\n}\n\nKJ_TEST(\"Gzip encoded stream (pumpTo same encoding)\") {\n  TestFixture fixture;\n  static const kj::byte data[] = {31, 139, 8, 0, 0, 0, 0, 0, 0, 3, 43, 206, 207, 77, 85, 72, 73, 44,\n    73, 84, 40, 201, 87, 72, 175, 202, 44, 0, 0, 40, 58, 113, 128, 17, 0, 0, 0};\n  auto in = newMemoryInputStream(data);\n  auto source = newEncodedReadableSource(rpc::StreamEncoding::GZIP, kj::mv(in));\n\n  MemoryAsyncOutputStream inner;\n  auto fakeOwn = kj::Own<MemoryAsyncOutputStream>(&inner, kj::NullDisposer::instance);\n  auto sink = newEncodedWritableSink(rpc::StreamEncoding::GZIP, kj::mv(fakeOwn));\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await environment.context.waitForDeferredProxy(source->pumpTo(*sink, EndAfterPump::YES));\n  });\n\n  // The data should pass through unchanged.\n  KJ_ASSERT(inner.data == data);\n}\n\nKJ_TEST(\"Gzip encoded stream (pumpTo different encoding)\") {\n  TestFixture fixture;\n  static const kj::byte data[] = {31, 139, 8, 0, 0, 0, 0, 0, 0, 3, 43, 206, 207, 77, 85, 72, 73, 44,\n    73, 84, 40, 201, 87, 72, 175, 202, 44, 0, 0, 40, 58, 113, 128, 17, 0, 0, 0};\n  auto in = newMemoryInputStream(data);\n  auto source = newEncodedReadableSource(rpc::StreamEncoding::GZIP, kj::mv(in));\n\n  MemoryAsyncOutputStream inner;\n  auto fakeOwn = kj::Own<MemoryAsyncOutputStream>(&inner, kj::NullDisposer::instance);\n  auto sink = newEncodedWritableSink(rpc::StreamEncoding::BROTLI, kj::mv(fakeOwn));\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await environment.context.waitForDeferredProxy(source->pumpTo(*sink, EndAfterPump::YES));\n  });\n\n  // The data shuld be brotli compressed.\n  static const kj::byte expected[] = {\n    5, 8, 128, 115, 111, 109, 101, 32, 100, 97, 116, 97, 32, 116, 111, 32, 103, 122, 105, 112, 3};\n  KJ_ASSERT(inner.data == expected);\n}\n\n// ======================================================================================\n// Adaptive Pump Behavior Tests\n// These tests verify the adaptive pump heuristics without relying on timing.\n\n// Mock AsyncInputStream that tracks tryRead() calls and their parameters\nclass AdaptiveTestInputStream final: public kj::AsyncInputStream {\n public:\n  enum class FillBehavior {\n    ALWAYS_FILL_COMPLETELY,  // Always fill the buffer completely\n    PARTIAL_FILLS,           // Always return partial fills\n    MIXED,                   // Alternate between full and partial\n  };\n\n  AdaptiveTestInputStream(size_t totalSize, FillBehavior behavior, size_t chunkSize = 0)\n      : totalSize_(totalSize),\n        position_(0),\n        behavior_(behavior),\n        chunkSize_(chunkSize),\n        readCount_(0) {}\n\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    readCount_++;\n\n    // Track the minBytes parameter on each call\n    minBytesHistory_.add(minBytes);\n    maxBytesHistory_.add(maxBytes);\n\n    if (position_ >= totalSize_) {\n      co_return 0;  // EOF\n    }\n\n    size_t remaining = totalSize_ - position_;\n    size_t toRead = 0;\n\n    switch (behavior_) {\n      case FillBehavior::ALWAYS_FILL_COMPLETELY:\n        // Fill the buffer completely up to maxBytes\n        toRead = kj::min(remaining, maxBytes);\n        break;\n\n      case FillBehavior::PARTIAL_FILLS:\n        // Return partial fills - less than maxBytes but at least minBytes\n        // (unless at EOF). This simulates a stream with natural boundaries.\n        if (remaining >= minBytes) {\n          // We have enough data to satisfy minBytes\n          if (chunkSize_ > 0 && chunkSize_ >= minBytes) {\n            // Use chunkSize if it's large enough\n            toRead = kj::min(remaining, chunkSize_);\n          } else {\n            // Otherwise, use minBytes to avoid triggering EOF\n            toRead = minBytes;\n          }\n        } else {\n          // At the end, return what's left (even if less than minBytes)\n          toRead = remaining;\n        }\n        break;\n\n      case FillBehavior::MIXED:\n        // Alternate between full and partial fills\n        if (readCount_ % 2 == 1) {\n          toRead = kj::min(remaining, maxBytes);\n        } else {\n          toRead = kj::min(remaining, minBytes);\n        }\n        break;\n    }\n\n    // Fill buffer with predictable data\n    auto dest = static_cast<kj::byte*>(buffer);\n    for (size_t i = 0; i < toRead; i++) {\n      dest[i] = static_cast<kj::byte>((position_ + i) & 0xFF);\n    }\n\n    position_ += toRead;\n    co_return toRead;\n  }\n\n  kj::Maybe<uint64_t> tryGetLength() override {\n    return totalSize_;\n  }\n\n  // Test accessors\n  const kj::ArrayPtr<const size_t> getMinBytesHistory() const {\n    return minBytesHistory_.asPtr();\n  }\n  const kj::ArrayPtr<const size_t> getMaxBytesHistory() const {\n    return maxBytesHistory_.asPtr();\n  }\n  size_t getReadCount() const {\n    return readCount_;\n  }\n\n private:\n  size_t totalSize_;\n  size_t position_;\n  FillBehavior behavior_;\n  size_t chunkSize_;\n  size_t readCount_;\n  kj::Vector<size_t> minBytesHistory_;\n  kj::Vector<size_t> maxBytesHistory_;\n};\n\n// Mock WritableSink that tracks write patterns\nclass AdaptiveTestSink final: public WritableSink {\n public:\n  AdaptiveTestSink() = default;\n  ~AdaptiveTestSink() = default;\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::byte> buffer) override {\n    writeCallCount_++;\n    writeSizes_.add(buffer.size());\n    totalBytesWritten_ += buffer.size();\n    co_return;\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const kj::byte>> pieces) override {\n    KJ_FAIL_ASSERT(\"Should not be called in these tests\");\n  }\n\n  kj::Promise<void> end() override {\n    endCallCount_++;\n    co_return;\n  }\n\n  void abort(kj::Exception reason) override {\n    abortCallCount_++;\n  }\n\n  rpc::StreamEncoding disownEncodingResponsibility() override {\n    return rpc::StreamEncoding::IDENTITY;\n  }\n\n  rpc::StreamEncoding getEncoding() override {\n    return rpc::StreamEncoding::IDENTITY;\n  }\n\n  // Test accessors\n  uint32_t getWriteCallCount() const {\n    return writeCallCount_;\n  }\n  const kj::ArrayPtr<const size_t> getWriteSizes() const {\n    return writeSizes_.asPtr();\n  }\n  size_t getTotalBytesWritten() const {\n    return totalBytesWritten_;\n  }\n\n private:\n  uint32_t writeCallCount_ = 0;\n  uint32_t endCallCount_ = 0;\n  uint32_t abortCallCount_ = 0;\n  size_t totalBytesWritten_ = 0;\n  kj::Vector<size_t> writeSizes_;\n};\n\nKJ_TEST(\"Adaptive pump: verify mock stream is called\") {\n  TestFixture fixture;\n\n  // Simple test to verify the mock tracking actually works\n  AdaptiveTestInputStream input(\n      100 * 1024, AdaptiveTestInputStream::FillBehavior::ALWAYS_FILL_COMPLETELY);\n  auto fakeOwn = kj::Own<AdaptiveTestInputStream>(&input, kj::NullDisposer::instance);\n\n  auto source = newReadableSource(kj::mv(fakeOwn));\n  AdaptiveTestSink sink;\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await environment.context.waitForDeferredProxy(source->pumpTo(sink, EndAfterPump::YES));\n    KJ_ASSERT(sink.getTotalBytesWritten() == 100 * 1024);\n\n    // Verify that our mock was actually called\n    // Note: Vector tracking doesn't work reliably in coroutine context, but readCount does\n    KJ_ASSERT(input.getReadCount() > 0, \"Mock stream was never read from\");\n  });\n}\n\nKJ_TEST(\"Adaptive pump: buffer sizing for small stream (2KB)\") {\n  TestFixture fixture;\n\n  // Small stream should use a small buffer (power of 2, clamped to range)\n  // For 2KB, expect buffer size of 2KB (next power of 2), clamped to MED_BUFFER_SIZE (64KB)\n  AdaptiveTestInputStream input(\n      2 * 1024, AdaptiveTestInputStream::FillBehavior::ALWAYS_FILL_COMPLETELY);\n  auto fakeOwn = kj::Own<AdaptiveTestInputStream>(&input, kj::NullDisposer::instance);\n\n  auto source = newReadableSource(kj::mv(fakeOwn));\n  AdaptiveTestSink sink;\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await environment.context.waitForDeferredProxy(source->pumpTo(sink, EndAfterPump::YES));\n\n    // Verify the stream was read efficiently\n    KJ_ASSERT(sink.getTotalBytesWritten() == 2 * 1024);\n\n    // For small streams that fill completely, we should see efficient buffer usage\n    // The buffer should be sized appropriately (power of 2, at least MIN_BUFFER_SIZE)\n    auto maxBytesHistory = input.getMaxBytesHistory();\n    if (maxBytesHistory.size() > 0) {\n      // First read should use a buffer size that's a power of 2 and >= 2KB\n      size_t firstBufferSize = maxBytesHistory[0];\n      KJ_ASSERT(firstBufferSize >= 2 * 1024, firstBufferSize);\n      KJ_ASSERT(\n          (firstBufferSize & (firstBufferSize - 1)) == 0, \"Should be power of 2\", firstBufferSize);\n    }\n    // For very small streams, there might be optimizations that bypass our tracking\n  });\n}\n\nKJ_TEST(\"Adaptive pump: buffer sizing for medium stream (500KB)\") {\n  TestFixture fixture;\n\n  // Medium stream should be read efficiently in a reasonable number of chunks\n  AdaptiveTestInputStream input(\n      500 * 1024, AdaptiveTestInputStream::FillBehavior::ALWAYS_FILL_COMPLETELY);\n  auto fakeOwn = kj::Own<AdaptiveTestInputStream>(&input, kj::NullDisposer::instance);\n\n  auto source = newReadableSource(kj::mv(fakeOwn));\n  AdaptiveTestSink sink;\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await environment.context.waitForDeferredProxy(source->pumpTo(sink, EndAfterPump::YES));\n\n    KJ_ASSERT(sink.getTotalBytesWritten() == 500 * 1024);\n\n    // For a 500KB stream, with reasonable buffer sizing (likely 64KB),\n    // we should see around 8-10 reads\n    auto readCount = input.getReadCount();\n    KJ_ASSERT(readCount >= 4 && readCount <= 20, \"Expected 4-20 reads for 500KB stream\", readCount);\n  });\n}\n\nKJ_TEST(\"Adaptive pump: buffer sizing for large stream (2MB)\") {\n  TestFixture fixture;\n\n  // Large stream (>1MB) should use MAX_BUFFER_SIZE and complete efficiently\n  AdaptiveTestInputStream input(\n      2 * 1024 * 1024, AdaptiveTestInputStream::FillBehavior::ALWAYS_FILL_COMPLETELY);\n  auto fakeOwn = kj::Own<AdaptiveTestInputStream>(&input, kj::NullDisposer::instance);\n\n  auto source = newReadableSource(kj::mv(fakeOwn));\n  AdaptiveTestSink sink;\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await environment.context.waitForDeferredProxy(source->pumpTo(sink, EndAfterPump::YES));\n\n    KJ_ASSERT(sink.getTotalBytesWritten() == 2 * 1024 * 1024);\n\n    // For a 2MB stream with MAX_BUFFER_SIZE (128KB), we should see around 16-18 reads\n    auto readCount = input.getReadCount();\n    KJ_ASSERT(readCount >= 10 && readCount <= 30, \"Expected 10-30 reads for 2MB stream\", readCount);\n  });\n}\n\nKJ_TEST(\"Adaptive pump: fast-filling stream efficiency\") {\n  TestFixture fixture;\n\n  // Stream that always fills the buffer completely should be read efficiently\n  AdaptiveTestInputStream input(\n      200 * 1024, AdaptiveTestInputStream::FillBehavior::ALWAYS_FILL_COMPLETELY);\n  auto fakeOwn = kj::Own<AdaptiveTestInputStream>(&input, kj::NullDisposer::instance);\n\n  auto source = newReadableSource(kj::mv(fakeOwn));\n  AdaptiveTestSink sink;\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await environment.context.waitForDeferredProxy(source->pumpTo(sink, EndAfterPump::YES));\n\n    KJ_ASSERT(sink.getTotalBytesWritten() == 200 * 1024);\n\n    // Fast-filling streams should complete in relatively few iterations\n    auto readCount = input.getReadCount();\n    KJ_ASSERT(readCount >= 2 && readCount <= 10,\n        \"Expected 2-10 reads for 200KB fast-filling stream\", readCount);\n\n    // Write count should be close to read count (double buffering)\n    auto writeCount = sink.getWriteCallCount();\n    KJ_ASSERT(writeCount >= readCount - 2 && writeCount <= readCount + 2,\n        \"Write count should be close to read count\", writeCount, readCount);\n  });\n}\n\nKJ_TEST(\"Adaptive pump: partial-filling stream behavior\") {\n  TestFixture fixture;\n\n  // Stream that returns partial fills (32KB chunks)\n  // Should require more iterations than a fast-filling stream\n  AdaptiveTestInputStream input(\n      200 * 1024, AdaptiveTestInputStream::FillBehavior::PARTIAL_FILLS, 32 * 1024);\n  auto fakeOwn = kj::Own<AdaptiveTestInputStream>(&input, kj::NullDisposer::instance);\n\n  auto source = newReadableSource(kj::mv(fakeOwn));\n  AdaptiveTestSink sink;\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await environment.context.waitForDeferredProxy(source->pumpTo(sink, EndAfterPump::YES));\n\n    KJ_ASSERT(sink.getTotalBytesWritten() == 200 * 1024);\n\n    // Partial-filling streams require more reads than fast-filling streams\n    // 200KB / 32KB chunks = ~7 reads minimum\n    auto readCount = input.getReadCount();\n    KJ_ASSERT(readCount >= 5, \"Expected at least 5 reads for partial-fill stream\", readCount);\n  });\n}\n\nKJ_TEST(\"Adaptive pump: large stream efficiency\") {\n  TestFixture fixture;\n\n  // Large streams should complete efficiently with appropriate buffer sizing\n  AdaptiveTestInputStream input(\n      2 * 1024 * 1024, AdaptiveTestInputStream::FillBehavior::ALWAYS_FILL_COMPLETELY);\n  auto fakeOwn = kj::Own<AdaptiveTestInputStream>(&input, kj::NullDisposer::instance);\n\n  auto source = newReadableSource(kj::mv(fakeOwn));\n  AdaptiveTestSink sink;\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await environment.context.waitForDeferredProxy(source->pumpTo(sink, EndAfterPump::YES));\n\n    KJ_ASSERT(sink.getTotalBytesWritten() == 2 * 1024 * 1024);\n\n    // Should complete in a reasonable number of reads\n    // With 128KB buffers: 2MB / 128KB = ~16 reads\n    auto readCount = input.getReadCount();\n    KJ_ASSERT(readCount >= 10 && readCount <= 30, \"Expected 10-30 reads for 2MB stream\", readCount);\n  });\n}\n\nKJ_TEST(\"Adaptive pump: mixed behavior stream\") {\n  TestFixture fixture;\n\n  // Stream that alternates between full and partial fills\n  // Should still complete reasonably efficiently\n  AdaptiveTestInputStream input(1024 * 1024, AdaptiveTestInputStream::FillBehavior::MIXED);\n  auto fakeOwn = kj::Own<AdaptiveTestInputStream>(&input, kj::NullDisposer::instance);\n\n  auto source = newReadableSource(kj::mv(fakeOwn));\n  AdaptiveTestSink sink;\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await environment.context.waitForDeferredProxy(source->pumpTo(sink, EndAfterPump::YES));\n\n    KJ_ASSERT(sink.getTotalBytesWritten() == 1024 * 1024);\n\n    // Mixed behavior should still complete in a reasonable number of reads\n    auto readCount = input.getReadCount();\n    KJ_ASSERT(\n        readCount >= 5 && readCount <= 40, \"Expected 5-40 reads for 1MB mixed stream\", readCount);\n  });\n}\n\nKJ_TEST(\"Adaptive pump: double buffering behavior\") {\n  TestFixture fixture;\n\n  // Verify that the pump uses double buffering effectively\n  // We can observe this by checking write patterns match read patterns\n  AdaptiveTestInputStream input(\n      100 * 1024, AdaptiveTestInputStream::FillBehavior::ALWAYS_FILL_COMPLETELY);\n  auto fakeOwn = kj::Own<AdaptiveTestInputStream>(&input, kj::NullDisposer::instance);\n\n  auto source = newReadableSource(kj::mv(fakeOwn));\n  AdaptiveTestSink sink;\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await environment.context.waitForDeferredProxy(source->pumpTo(sink, EndAfterPump::YES));\n\n    KJ_ASSERT(sink.getTotalBytesWritten() == 100 * 1024);\n\n    // With double buffering, the number of writes should be close to the number of reads\n    // (minus one since the last read returns EOF)\n    auto readCount = input.getReadCount();\n    auto writeCount = sink.getWriteCallCount();\n\n    // Reads should be at least as many as writes (or equal for small streams)\n    KJ_ASSERT(readCount >= writeCount, readCount, writeCount);\n\n    // For properly pipelined operation, reads and writes should be close\n    // The difference should be small (typically 0-1 for good pipelining)\n    KJ_ASSERT(readCount - writeCount <= 2, \"Pipelining gap too large\", readCount, writeCount);\n  });\n}\n\nKJ_TEST(\"Adaptive pump: verify heuristics optimize for throughput\") {\n  TestFixture fixture;\n\n  // Large stream with consistent full fills should optimize for throughput\n  // by using large buffers and appropriate minBytes\n  AdaptiveTestInputStream input(\n      1024 * 1024, AdaptiveTestInputStream::FillBehavior::ALWAYS_FILL_COMPLETELY);\n  auto fakeOwn = kj::Own<AdaptiveTestInputStream>(&input, kj::NullDisposer::instance);\n\n  auto source = newReadableSource(kj::mv(fakeOwn));\n  AdaptiveTestSink sink;\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await environment.context.waitForDeferredProxy(source->pumpTo(sink, EndAfterPump::YES));\n\n    KJ_ASSERT(sink.getTotalBytesWritten() == 1024 * 1024);\n\n    auto writeSizes = sink.getWriteSizes();\n    auto readCount = input.getReadCount();\n\n    // For a 1MB stream with fast fills, we should see efficient large writes\n    // The number of iterations should be relatively small\n    KJ_ASSERT(readCount <= 20, \"Too many iterations for 1MB stream\", readCount);\n\n    // Most writes should be using the full buffer\n    size_t largeWrites = 0;\n    for (size_t size: writeSizes) {\n      if (size >= 32 * 1024) {  // Reasonably large writes\n        largeWrites++;\n      }\n    }\n\n    // Most writes should be large for throughput optimization\n    KJ_ASSERT(largeWrites >= writeSizes.size() / 2, \"Expected mostly large writes for throughput\",\n        largeWrites, writeSizes.size());\n  });\n}\n\nKJ_TEST(\"Adaptive pump: verify heuristics optimize for responsiveness\") {\n  TestFixture fixture;\n\n  // Stream with medium chunks should optimize for responsiveness\n  // Using 16KB chunks which will not fill larger buffers\n  AdaptiveTestInputStream input(\n      256 * 1024, AdaptiveTestInputStream::FillBehavior::PARTIAL_FILLS, 16 * 1024);\n  auto fakeOwn = kj::Own<AdaptiveTestInputStream>(&input, kj::NullDisposer::instance);\n\n  auto source = newReadableSource(kj::mv(fakeOwn));\n  AdaptiveTestSink sink;\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await environment.context.waitForDeferredProxy(source->pumpTo(sink, EndAfterPump::YES));\n\n    KJ_ASSERT(sink.getTotalBytesWritten() == 256 * 1024);\n\n    auto writeSizes = sink.getWriteSizes();\n\n    // For partial-fill streams, writes should match the stream's natural chunk size\n    // We should see multiple writes rather than trying to accumulate into large ones\n    KJ_ASSERT(writeSizes.size() >= 4, \"Expected multiple writes for partial-fill stream\",\n        writeSizes.size());\n\n    // The write pattern should reflect the stream's behavior\n    // Most writes should be around the chunk size (16KB) or minBytes\n    size_t mediumWrites = 0;\n    for (size_t size: writeSizes) {\n      if (size >= 8 * 1024 && size <= 32 * 1024) {  // Medium chunks\n        mediumWrites++;\n      }\n    }\n\n    // Should have multiple medium-sized writes reflecting the partial-fill pattern\n    KJ_ASSERT(mediumWrites >= 2, \"Expected some medium writes for responsive stream\", mediumWrites);\n  });\n}\n\n}  // namespace\n}  // namespace workerd::api::streams\n"
  },
  {
    "path": "src/workerd/api/streams/readable-source.c++",
    "content": "#include \"readable-source.h\"\n\n#include \"common.h\"\n#include \"writable-sink.h\"\n\n#include <workerd/api/util.h>\n#include <workerd/io/io-context.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/util/state-machine.h>\n#include <workerd/util/stream-utils.h>\n#include <workerd/util/string-buffer.h>\n#include <workerd/util/strong-bool.h>\n\n#include <kj/async-io.h>\n#include <kj/compat/brotli.h>\n#include <kj/compat/gzip.h>\n\n#include <bit>\n\nnamespace workerd::api::streams {\n\nnamespace {\n// Used to consume and collect all data from a ReadableSource up to a specified\n// limit. Throws if the limit is exceeded before EOF.\nclass AllReader final {\n public:\n  explicit AllReader(ReadableSource& input, size_t limit): input(input), limit(limit) {\n    JSG_REQUIRE(limit > 0, TypeError, \"Memory limit exceeded before EOF.\");\n    KJ_IF_SOME(length, input.tryGetLength(rpc::StreamEncoding::IDENTITY)) {\n      // Oh hey, we might be able to bail early.\n      JSG_REQUIRE(length <= limit, TypeError, \"Memory limit would be exceeded before EOF.\");\n    }\n  }\n  KJ_DISALLOW_COPY_AND_MOVE(AllReader);\n\n  kj::Promise<kj::Array<const kj::byte>> readAllBytes() {\n    co_return co_await read<kj::byte>();\n  }\n\n  kj::Promise<kj::String> readAllText() {\n    co_return kj::String(co_await read<char>(ReadOption::NULL_TERMINATE));\n  }\n\n private:\n  ReadableSource& input;\n  size_t limit;\n\n  enum class ReadOption {\n    NONE,\n    NULL_TERMINATE,\n  };\n\n  template <typename T>\n  kj::Promise<kj::Array<T>> read(ReadOption option = ReadOption::NONE) {\n    // Read in chunks and accumulate them. Use an exponential growth strategy\n    // to determine chunk sizes to minimize the number of iterations and\n    // allocations on large streams.\n    kj::Vector<kj::Array<T>> parts;\n    size_t runningTotal = 0;\n    // TODO(later): Make these configurable someday?\n    static constexpr size_t MIN_BUFFER_CHUNK = 1024;\n    static constexpr size_t DEFAULT_BUFFER_CHUNK = 4096;\n    // TODO(later): Consider increasing MAX_BUFFER_CHUNK, maybe up to 1 MB?\n    static constexpr size_t MAX_BUFFER_CHUNK = DEFAULT_BUFFER_CHUNK * 4;\n\n    // If we know in advance how much data we'll be reading, then we can attempt to optimize the\n    // loop here by setting the value specifically so we are only allocating at most twice. But,\n    // to be safe, let's enforce an upper bound on each allocation even if we do know the total.\n    kj::Maybe<size_t> maybeLength = input.tryGetLength(rpc::StreamEncoding::IDENTITY);\n\n    size_t amountToRead;\n    KJ_IF_SOME(length, maybeLength) {\n      if (length <= MAX_BUFFER_CHUNK) {\n        amountToRead = kj::min(limit, length);\n      } else {\n        amountToRead = DEFAULT_BUFFER_CHUNK;\n      }\n    } else {\n      amountToRead = MIN_BUFFER_CHUNK;\n    }\n\n    if (amountToRead != 0) {\n      while (true) {\n        auto bytes = kj::heapArray<T>(amountToRead);\n        size_t amount = co_await input.read(bytes.asBytes(), bytes.size());\n        KJ_DASSERT(amount <= bytes.size());\n        runningTotal += amount;\n        JSG_REQUIRE(runningTotal <= limit, TypeError, \"Memory limit exceeded before EOF.\");\n\n        if (amount == bytes.size()) {\n          parts.add(kj::mv(bytes));\n          // Adjust the next allocation size -- double it up to a maximum\n          amountToRead = kj::min(amountToRead * 2, kj::min(MAX_BUFFER_CHUNK, limit - runningTotal));\n        } else {\n          if (amount > 0) {\n            parts.add(bytes.first(amount).attach(kj::mv(bytes)));\n          }\n          break;\n        }\n      }\n    }\n\n    if (option == ReadOption::NULL_TERMINATE) {\n      auto out = kj::heapArray<T>(runningTotal + 1);\n      out[runningTotal] = '\\0';\n      copyInto<T>(out, parts);\n      co_return kj::mv(out);\n    }\n\n    // As an optimization, if there's only a single part in the list, we can avoid\n    // further copies.\n    if (parts.size() == 1) {\n      co_return kj::mv(parts[0]);\n    }\n\n    auto out = kj::heapArray<T>(runningTotal);\n    copyInto<T>(out, parts);\n    co_return kj::mv(out);\n  }\n\n  template <typename T>\n  void copyInto(kj::ArrayPtr<T> out, kj::ArrayPtr<kj::Array<T>> in) {\n    for (auto& part: in) {\n      KJ_DASSERT(part.size() <= out.size());\n      out.first(part.size()).copyFrom(part);\n      out = out.slice(part.size());\n    }\n  }\n};\n\n// An AsyncInputStream wrapper that translates tee-related kj::Exceptions from read\n// operations into jsg::Exceptions.\n// TODO(later): We might be able to get rid of this and use a KJ exception detail instead.\nclass TeeErrorAdapter final: public kj::AsyncInputStream {\n public:\n  static kj::Own<kj::AsyncInputStream> wrap(kj::Own<kj::AsyncInputStream> inner) {\n    // We make a best effort to avoid double-wrapping.\n    if (dynamic_cast<TeeErrorAdapter*>(inner.get()) == nullptr) {\n      return kj::heap<TeeErrorAdapter>(kj::mv(inner));\n    } else {\n      return kj::mv(inner);\n    }\n  }\n\n  explicit TeeErrorAdapter(kj::Own<AsyncInputStream> inner): inner(kj::mv(inner)) {}\n\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    return translateErrors([&] { return inner->tryRead(buffer, minBytes, maxBytes); });\n  }\n\n  kj::Maybe<uint64_t> tryGetLength() override {\n    return inner->tryGetLength();\n  };\n\n  kj::Promise<uint64_t> pumpTo(kj::AsyncOutputStream& output, uint64_t amount) override {\n    return translateErrors([&] { return inner->pumpTo(output, amount); });\n  }\n\n  kj::Maybe<kj::Own<kj::AsyncInputStream>> tryTee(uint64_t limit) override {\n    return inner->tryTee(limit);\n  }\n\n private:\n  kj::Own<AsyncInputStream> inner;\n\n  template <typename Func>\n  static auto translateErrors(Func&& f) -> decltype(kj::fwd<Func>(f)()) {\n    try {\n      co_return co_await f();\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      KJ_IF_SOME(translated,\n          translateKjException(exception,\n              {\n                {\"tee buffer size limit exceeded\"_kj,\n                  \"ReadableStream.tee() buffer limit exceeded. This error usually occurs \"\n                  \"when a Request or Response with a large body is cloned, then only one \"\n                  \"of the clones is read, forcing the Workers runtime to buffer the entire \"\n                  \"body in memory. To fix this issue, remove unnecessary calls to \"\n                  \"Request/Response.clone() and ReadableStream.tee(), and always read \"\n                  \"clones/tees in parallel.\"_kj},\n              })) {\n        kj::throwFatalException(kj::mv(translated));\n      } else {\n        kj::throwFatalException(kj::mv(exception));\n      }\n    }\n  }\n};\n\n// A kj::AsyncInputStream implementation that delegates to a provided function\n// to produce data on each read.\nclass InputStreamFromProducer final: public kj::AsyncInputStream {\n public:\n  using Producer = kj::Function<kj::Promise<size_t>(kj::ArrayPtr<kj::byte>, size_t)>;\n  InputStreamFromProducer(Producer producer, kj::Maybe<uint64_t> expectedLength)\n      : producer(kj::mv(producer)),\n        expectedLength(expectedLength) {}\n\n  KJ_DISALLOW_COPY_AND_MOVE(InputStreamFromProducer);\n\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    KJ_IF_SOME(p, producer) {\n      // If there is an expected length, we won't try to read more than whatever is remaining.\n      maxBytes = kj::min(maxBytes, expectedLength.orDefault(maxBytes));\n      minBytes = kj::min(minBytes, maxBytes);\n      auto amount = co_await p(kj::arrayPtr(static_cast<kj::byte*>(buffer), maxBytes), minBytes);\n      KJ_IF_SOME(length, expectedLength) {\n        KJ_DASSERT(amount <= length, \"Producer produced more data than expected.\");\n        length -= amount;\n      }\n      if (amount < minBytes) {\n        // The producer is indicating that we're done. Drop the producer.\n        // If the producer did not producer as much data as we expected, that's an error.\n        KJ_IF_SOME(length, expectedLength) {\n          KJ_REQUIRE(length == 0, \"jsg.Error: Producer ended stream early.\");\n        }\n        producer = kj::none;\n      }\n      co_return amount;\n    } else {\n      co_return 0;  // EOF\n    }\n  }\n\n  // Returns the expected number of bytes remaining to be read, if known.\n  kj::Maybe<uint64_t> tryGetLength() override {\n    return expectedLength;\n  }\n\n private:\n  kj::Maybe<kj::Function<kj::Promise<size_t>(kj::ArrayPtr<kj::byte>, size_t)>> producer;\n  kj::Maybe<uint64_t> expectedLength;\n};\n\nstruct Closed {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"closed\"_kj;\n};\n\nstruct Open {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"open\"_kj;\n  kj::Own<kj::AsyncInputStream> stream;\n};\n\n// State machine for tracking readable source lifecycle:\n//   Open -> Closed (normal close after EOF or pumpTo)\n//   Open -> kj::Exception (error via cancel() or read failure)\n// Closed is terminal, kj::Exception is implicitly terminal via ErrorState.\nusing ReadableSourceState = StateMachine<TerminalStates<Closed>,\n    ErrorState<kj::Exception>,\n    ActiveState<Open>,\n    Open,\n    Closed,\n    kj::Exception>;\n\n// A base class for ReadableSource implementations that provides default\n// implementations of some methods.\nclass ReadableSourceImpl: public ReadableSource {\n public:\n  ReadableSourceImpl(kj::Own<kj::AsyncInputStream> input,\n      rpc::StreamEncoding encoding = rpc::StreamEncoding::IDENTITY)\n      : state(ReadableSourceState::create<Open>(kj::mv(input))),\n        encoding(encoding) {}\n  ReadableSourceImpl(kj::Exception reason)\n      : state(ReadableSourceState::create<kj::Exception>(kj::mv(reason))),\n        encoding(rpc::StreamEncoding::IDENTITY) {}\n  ReadableSourceImpl()\n      : state(ReadableSourceState::create<Closed>()),\n        encoding(rpc::StreamEncoding::IDENTITY) {}\n  KJ_DISALLOW_COPY_AND_MOVE(ReadableSourceImpl);\n  virtual ~ReadableSourceImpl() noexcept(false) {\n    canceler.cancel(KJ_EXCEPTION(DISCONNECTED, \"stream was dropped\"));\n  }\n\n  kj::Promise<size_t> readInner(Open& open, kj::ArrayPtr<kj::byte> buffer, size_t minBytes = 1) {\n    try {\n      auto& stream = setStream(ensureIdentityEncoding(kj::mv(open.stream)));\n      minBytes = kj::max(minBytes, 1u);\n      auto amount = co_await readImpl(stream, buffer, minBytes);\n      if (amount < minBytes) {\n        setClosed();\n      }\n      co_return amount;\n    } catch (...) {\n      handleOperationException();\n    }\n  }\n\n  kj::Promise<size_t> read(kj::ArrayPtr<kj::byte> buffer, size_t minBytes = 1) override {\n    throwIfErrored();\n    if (state.is<Closed>()) {\n      co_return 0;\n    }\n    auto& open = state.requireActiveUnsafe();\n    KJ_REQUIRE(canceler.isEmpty(), \"jsg.Error: Stream is already being read\");\n    co_return co_await canceler.wrap(readInner(open, buffer, minBytes));\n    // If the source is dropped while a read is in progress, the canceler will\n    // trigger and abort the read. In such cases, we don't want to wrap this\n    // await in a try catch because it isn't safe to continue using the stream\n    // as it may no longer exist.\n  }\n\n  kj::Promise<DeferredProxy<void>> pumpTo(\n      WritableSink& output, EndAfterPump end = EndAfterPump::YES) override {\n    // By default, we assume the pump is eligible for deferred proxying.\n    KJ_CO_MAGIC BEGIN_DEFERRED_PROXYING;\n\n    if (!canceler.isEmpty()) {\n      kj::throwFatalException(KJ_EXCEPTION(FAILED, \"jsg.Error: Stream is already being read\"));\n    }\n\n    KJ_IF_SOME(errored, state.tryGetErrorUnsafe()) {\n      output.abort(kj::cp(errored));\n      kj::throwFatalException(kj::cp(errored));\n    }\n\n    if (state.is<Closed>()) {\n      if (end) {\n        co_await output.end();\n      }\n      co_return;\n    }\n\n    auto& open = state.requireActiveUnsafe();\n    // Ownership of the underlying inner stream is transferred to the pump operation,\n    // where it will be either fully consumed or errored out. In either case, this\n    // ReadableSource becomes closed and no longer usable once pumpTo() is called.\n    // Critically... it is important that just because the ReadableSource is closed here\n    // does NOT mean that the underlying stream has been fully consumed.\n    auto stream = kj::mv(open.stream);\n    setClosed();\n\n    if (output.getEncoding() != getEncoding()) {\n      // The target encoding is different from our current encoding.\n      // Let's ensure that our side is in identity encoding. The destination stream will\n      // take care of itself.\n      stream = ensureIdentityEncoding(kj::mv(stream));\n    } else {\n      // Since the encodings match, we can tell the output stream that it doesn't need to\n      // do any of the encoding work since we'll be providing data in the expected encoding.\n      KJ_ASSERT(getEncoding() == output.disownEncodingResponsibility());\n    }\n\n    // Note that because we are transferring ownership of the stream to the pump operation,\n    // and the pump itself should not rely on the ReadableSource for any state, it is\n    // safe to drop the ReadableSource once the pump operation begins.\n    co_return co_await pumpImpl(kj::mv(stream), output, end);\n  }\n\n  kj::Maybe<size_t> tryGetLength(rpc::StreamEncoding encoding) override {\n    if (encoding == rpc::StreamEncoding::IDENTITY) {\n      KJ_IF_SOME(open, state.tryGetActiveUnsafe()) {\n        return open.stream->tryGetLength();\n      }\n    }\n    return kj::none;\n  }\n\n  kj::Promise<kj::Array<const kj::byte>> readAllBytes(size_t limit) override {\n    throwIfErrored();\n    if (state.is<Closed>()) {\n      co_return kj::Array<const kj::byte>();\n    }\n    // Must be active\n    AllReader reader(*this, limit);\n    co_return co_await reader.readAllBytes();\n  }\n\n  kj::Promise<kj::String> readAllText(size_t limit) override {\n    throwIfErrored();\n    if (state.is<Closed>()) {\n      co_return kj::String();\n    }\n    // Must be active\n    AllReader reader(*this, limit);\n    co_return co_await reader.readAllText();\n  }\n\n  void cancel(kj::Exception reason) override {\n    canceler.cancel(kj::cp(reason));\n    setErrored(kj::mv(reason));\n  }\n\n  Tee tee(size_t limit) override {\n    KJ_IF_SOME(errored, state.tryGetErrorUnsafe()) {\n      return Tee{\n        .branch1 = newErroredReadableSource(kj::cp(errored)),\n        .branch2 = newErroredReadableSource(kj::cp(errored)),\n      };\n    }\n\n    if (state.is<Closed>()) {\n      return Tee{\n        .branch1 = newClosedReadableSource(),\n        .branch2 = newClosedReadableSource(),\n      };\n    }\n\n    auto& open = state.requireActiveUnsafe();\n    KJ_IF_SOME(result, tryTee(limit)) {\n      setClosed();\n      return kj::mv(result);\n    }\n\n    auto teeResult = kj::newTee(kj::mv(open.stream), limit);\n    setClosed();\n    return Tee{\n      .branch1 = newReadableSource(wrapTeeBranch(kj::mv(teeResult.branches[0]))),\n      .branch2 = newReadableSource(wrapTeeBranch(kj::mv(teeResult.branches[1]))),\n    };\n  }\n\n  rpc::StreamEncoding getEncoding() override {\n    return encoding;\n  }\n\n protected:\n  // Throws the stored exception if in error state.\n  void throwIfErrored() {\n    KJ_IF_SOME(exception, state.tryGetErrorUnsafe()) {\n      kj::throwFatalException(kj::cp(exception));\n    }\n  }\n\n  // Handles exceptions from read operations: stores the error and rethrows.\n  [[noreturn]] void handleOperationException() {\n    auto exception = kj::getCaughtExceptionAsKj();\n    setErrored(kj::cp(exception));\n    kj::throwFatalException(kj::mv(exception));\n  }\n\n  // Implementations really should override this to provide encoding support!\n  virtual kj::Own<kj::AsyncInputStream> ensureIdentityEncoding(\n      kj::Own<kj::AsyncInputStream>&& inner) {\n    // By default, we always use identity encoding so nothing to do here.\n    // It is up to subclasses to override this if they support other encodings.\n    KJ_DASSERT(encoding == rpc::StreamEncoding::IDENTITY);\n    return kj::mv(inner);\n  }\n\n  // Implementations should override to provide an alternative tee implementation.\n  // This will only be called when the state is known to be not closed or errored.\n  virtual kj::Maybe<Tee> tryTee(size_t limit) {\n    return kj::none;\n  }\n\n  ReadableSourceState& getState() {\n    return state;\n  }\n\n  void setClosed() {\n    state.transitionTo<Closed>();\n  }\n\n  void setErrored(kj::Exception reason) {\n    state.forceTransitionTo<kj::Exception>(kj::mv(reason));\n  }\n\n  kj::AsyncInputStream& setStream(kj::Own<kj::AsyncInputStream> stream) {\n    auto& inner = *stream;\n    state.getUnsafe<Open>().stream = kj::mv(stream);\n    return inner;\n  }\n\n  void setEncoding(rpc::StreamEncoding newEncoding) {\n    encoding = newEncoding;\n  }\n\n private:\n  ReadableSourceState state;\n  rpc::StreamEncoding encoding;\n  kj::Canceler canceler;\n\n  struct ReadOption {\n    bool identityEncoding;\n  };\n\n  // The default pumpTo() implementation which initiates a loop\n  // that reads a chunk from the input stream and writes it to the output\n  // stream until EOF is reached.\n  // The pump is canceled by dropping the returned promise.\n  static kj::Promise<void> pumpImpl(\n      kj::Own<kj::AsyncInputStream> stream, WritableSink& output, EndAfterPump end) {\n    // These are fairly arbitrary but reasonable buffer size choices.\n\n    // Note: this intentionally contains code that is similar to the\n    // ReadableSourceKjAdapter::pumpToImpl impl in readable-source-adapter.c++.\n    // The optimizations are generally the same but the targets are a bit different\n    // (ReadableStream vs. kj::AsyncInputStream).\n\n    static constexpr size_t DEFAULT_BUFFER_SIZE = 16384;\n    static constexpr size_t MIN_BUFFER_SIZE = 1024;\n    static constexpr size_t MED_BUFFER_SIZE = MIN_BUFFER_SIZE << 6;\n    static constexpr size_t MAX_BUFFER_SIZE = MIN_BUFFER_SIZE << 7;\n    static constexpr size_t MEDIUM_THRESHOLD = 1048576;\n    static_assert(MIN_BUFFER_SIZE < DEFAULT_BUFFER_SIZE);\n    static_assert(DEFAULT_BUFFER_SIZE < MED_BUFFER_SIZE);\n    static_assert(MED_BUFFER_SIZE < MAX_BUFFER_SIZE);\n    static_assert(MAX_BUFFER_SIZE < MEDIUM_THRESHOLD);\n\n    // Determine optimal buffer size based on stream length. If the stream does\n    // not report a length, use the default. The logic here is simple: use larger\n    // buffer sizes for larger streams to reduce the number of read/write iterations.\n    // and smaller buffer sizes for smaller streams to reduce memory usage.\n    // If the size is unknown, we defer to a reasonable default.\n    size_t bufferSize = DEFAULT_BUFFER_SIZE;\n    kj::Maybe<uint64_t> maybeRemaining = stream->tryGetLength();\n    KJ_IF_SOME(length, maybeRemaining) {\n      // Streams that advertise their length SHOULD always tell the truth.\n      // But... on the off change they don't, we'll still try to behave\n      // reasonably. At worst we will allocate a backing buffer and\n      // perform a single read. If this proves to be a performance issue,\n      // we can fall back to strictly enforcing the advertised length.\n      if (length <= MEDIUM_THRESHOLD) {\n        // When `length` is below the medium threshold, use\n        // the nearest power of 2 >= length within the range\n        // [MIN_BUFFER_SIZE, MED_BUFFER_SIZE].\n        bufferSize = kj::max(MIN_BUFFER_SIZE, std::bit_ceil(length));\n        bufferSize = kj::min(MED_BUFFER_SIZE, bufferSize);\n      } else {\n        // Otherwise, use the biggest buffer.\n        bufferSize = MAX_BUFFER_SIZE;\n      }\n    }\n\n    // We use a double-buffering/pipelining strategy here to try to keep both the read\n    // and write operations busy in parallel. While one buffer is being written to the\n    // output, the other buffer is being filled with data from the input stream. It does\n    // mean that we use a bit more memory in the process but should improve throughput on\n    // high-latency streams.\n    int currentReadBuf = 0;\n    kj::SmallArray<kj::byte, 4 * MIN_BUFFER_SIZE> backing(bufferSize * 2);\n    kj::ArrayPtr<kj::byte> buffer[] = {\n      backing.first(bufferSize),\n      backing.slice(bufferSize),\n    };\n\n    // We will use an adaptive minBytes value to try to optimize read sizes based on\n    // observed stream behavior. We start with a minBytes set to half the buffer size.\n    // As the stream is read, we will adjust minBytes up or down depending on whether\n    // the stream is consistently filling the buffer or not.\n    size_t minBytes = bufferSize >> 1;\n\n    auto readPromise = readImpl(*stream, buffer[currentReadBuf], minBytes);\n    size_t iterationCount = 0;\n    bool readFailed = false;\n\n    try {\n      while (true) {\n        // On each iteration, wait for the read to complete...\n        size_t amount;\n        {\n          KJ_ON_SCOPE_FAILURE(readFailed = true);\n          amount = co_await readPromise;\n        }\n        iterationCount++;\n\n        // If we read less than minBytes, assume EOF.\n        if (amount < minBytes) {\n          // If any bytes were read...\n          if (amount > 0) {\n            // Write our final chunk...\n            co_await output.write(buffer[currentReadBuf].first(amount));\n          }\n          // Then break out of the loop.\n          break;\n        }\n\n        // Set the write buffer to the one we just filled.\n        auto writeBuf = buffer[currentReadBuf];\n\n        // Then switch to the other buffer and start the next read.\n        currentReadBuf = 1 - currentReadBuf;\n\n        // Maybe adjust minBytes based on how much data we read this iteration.\n        if (iterationCount <= 3 || iterationCount % 10 == 0) {\n          if (amount == bufferSize) {\n            // Stream is filling buffer completely... Use smaller minBytes to\n            // increase responsiveness, should produce more reads with less data.\n            if (bufferSize >= 4 * DEFAULT_BUFFER_SIZE) {\n              // For large buffers (≥64KB), be more aggressive about responsiveness.\n              // 25% of a large buffer is still a substantial chunk (e.g., 32KB for 128KB).\n              minBytes = bufferSize >> 2;  // 25%\n            } else {\n              // For smaller buffers, 50% provides better balance, avoiding chunks\n              // that are too small for efficient processing (e.g., keeps 16KB → 8KB).\n              minBytes = bufferSize >> 1;  // 50%\n            }\n          } else {\n            // Stream didn't fill buffer - likely slower or at natural boundary.\n            // Use higher minBytes to accumulate larger chunks and reduce iteration overhead.\n            minBytes = (bufferSize >> 2) + (bufferSize >> 1);  // 75%\n          }\n        }\n\n        // Start our next read operation.\n        readPromise = readImpl(*stream, buffer[currentReadBuf], minBytes);\n\n        // Write out the chunk we just read in parallel with the next read.\n        // If the write fails, the exception will propagate and cancel the pump,\n        // including the read operation. If the read fails, it will be picked\n        // up at the start of the next loop iteration.\n        co_await output.write(writeBuf.first(amount));\n      }\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      if (readFailed) {\n        output.abort(kj::cp(exception));\n      }\n      kj::throwFatalException(kj::mv(exception));\n    }\n\n    if (end) {\n      co_await output.end();\n    }\n  }\n\n  static kj::Promise<size_t> readImpl(\n      kj::AsyncInputStream& inner, kj::ArrayPtr<kj::byte> buffer, size_t minBytes) {\n    KJ_ASSERT(minBytes <= buffer.size());\n    try {\n      // The read() method on AsyncInputStream will throw an exception on short reads,\n      // which is why we're using tryRead() here instead.\n      co_return co_await inner.tryRead(buffer.begin(), minBytes, buffer.size());\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      if (exception.getType() == kj::Exception::Type::DISCONNECTED) {\n        // Treat disconnects as EOF.\n        co_return 0;\n      }\n      kj::throwFatalException(kj::mv(exception));\n    }\n  }\n};\n\n// A ReadableSource wrapper that prevents deferred proxying. This is useful\n// when you expect that the IoContext will need to remain live for the duration\n// of the operations on the stream.\nclass NoDeferredProxySource final: public ReadableSourceWrapper {\n public:\n  NoDeferredProxySource(kj::Own<ReadableSource> inner, IoContext& ioctx)\n      : ReadableSourceWrapper(kj::mv(inner)),\n        ioctx(ioctx) {}\n\n  kj::Promise<size_t> read(kj::ArrayPtr<kj::byte> buffer, size_t minBytes = 1) override {\n    auto pending = ioctx.registerPendingEvent();\n    co_return co_await getInner().read(buffer, minBytes);\n  }\n\n  kj::Promise<DeferredProxy<void>> pumpTo(\n      WritableSink& output, EndAfterPump end = EndAfterPump::YES) override {\n    auto pending = ioctx.registerPendingEvent();\n    auto [proxyTask] = co_await getInner().pumpTo(output, end);\n    co_await proxyTask;\n  }\n\n  Tee tee(size_t limit) override {\n    auto tee = getInner().tee(limit);\n    return Tee{\n      .branch1 = kj::heap<NoDeferredProxySource>(kj::mv(tee.branch1), ioctx),\n      .branch2 = kj::heap<NoDeferredProxySource>(kj::mv(tee.branch2), ioctx),\n    };\n  }\n\n private:\n  IoContext& ioctx;\n};\n\n// A ReadableSource implementation that lazily wraps an innner Gzip or Brotli\n// encoded AsyncInputStream when the first read() is called, or when pumpTo is called,\n// the encoding will be selectively and lazily applied to the inner stream.\nclass EncodedAsyncInputStream final: public ReadableSourceImpl {\n public:\n  EncodedAsyncInputStream(kj::Own<kj::AsyncInputStream> inner, rpc::StreamEncoding encoding)\n      : ReadableSourceImpl(kj::mv(inner), encoding) {}\n\n  // Read bytes in identity encoding. If the stream is not already in identity encoding, it will be\n  // converted to identity encoding via an appropriate stream wrapper.\n  kj::Promise<size_t> read(kj::ArrayPtr<kj::byte> buffer, size_t minBytes) override {\n    try {\n      co_return co_await ReadableSourceImpl::read(buffer, minBytes);\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      KJ_IF_SOME(translated,\n          translateKjException(exception,\n              {\n                {\"gzip compressed stream ended prematurely\"_kj,\n                  \"Gzip compressed stream ended prematurely.\"_kj},\n                {\"gzip decompression failed\"_kj, \"Gzip decompression failed.\"},\n                {\"brotli state allocation failed\"_kj, \"Brotli state allocation failed.\"},\n                {\"invalid brotli window size\"_kj, \"Invalid brotli window size.\"},\n                {\"invalid brotli compression level\"_kj, \"Invalid brotli compression level.\"},\n                {\"brotli window size too big\"_kj, \"Brotli window size too big.\"},\n                {\"brotli decompression failed\"_kj, \"Brotli decompression failed.\"},\n                {\"brotli compression failed\"_kj, \"Brotli compression failed.\"},\n                {\"brotli compressed stream ended prematurely\"_kj,\n                  \"Brotli compressed stream ended prematurely.\"},\n              })) {\n        kj::throwFatalException(kj::mv(translated));\n      } else {\n        kj::throwFatalException(kj::mv(exception));\n      }\n    }\n  }\n\n  kj::Maybe<Tee> tryTee(size_t limit) override {\n    // Note that if we haven't called read() yet, then the inner stream is still\n    // in its original encoding. If read() has been called, however, then the inner\n    // stream will be wrapped and will be in identity encoding.\n    auto& open = KJ_ASSERT_NONNULL(getState().tryGetActiveUnsafe());\n    auto tee = kj::newTee(kj::mv(open.stream), limit);\n    return Tee{\n      .branch1 =\n          kj::heap<EncodedAsyncInputStream>(wrapTeeBranch(kj::mv(tee.branches[0])), getEncoding()),\n      .branch2 =\n          kj::heap<EncodedAsyncInputStream>(wrapTeeBranch(kj::mv(tee.branches[1])), getEncoding()),\n    };\n  }\n\n  kj::Own<kj::AsyncInputStream> ensureIdentityEncoding(\n      kj::Own<kj::AsyncInputStream>&& inner) override {\n    auto encoding = getEncoding();\n    if (encoding == rpc::StreamEncoding::IDENTITY) {\n      return kj::mv(inner);\n    }\n    setEncoding(rpc::StreamEncoding::IDENTITY);\n    return wrap(encoding, kj::mv(inner));\n  }\n\n private:\n  static kj::Own<kj::AsyncInputStream> wrap(\n      rpc::StreamEncoding encoding, kj::Own<kj::AsyncInputStream> inner) {\n    switch (encoding) {\n      case rpc::StreamEncoding::IDENTITY: {\n        return kj::mv(inner);\n      }\n      case rpc::StreamEncoding::GZIP: {\n        return kj::heap<kj::GzipAsyncInputStream>(*inner).attach(kj::mv(inner));\n      }\n      case rpc::StreamEncoding::BROTLI: {\n        return kj::heap<kj::BrotliAsyncInputStream>(*inner).attach(kj::mv(inner));\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n};\n\n}  // namespace\n\nkj::Own<ReadableSource> newReadableSourceFromBytes(\n    kj::ArrayPtr<const kj::byte> bytes, kj::Maybe<kj::Own<void>> maybeBacking) {\n  KJ_IF_SOME(backing, maybeBacking) {\n    return newReadableSource(newMemoryInputStream(bytes, kj::mv(backing)));\n  }\n\n  auto backing = kj::heapArray<kj::byte>(bytes);\n  auto ptr = backing.asPtr();\n  auto inner = newMemoryInputStream(ptr, kj::heap(kj::mv(backing)));\n  return newReadableSource(kj::mv(inner));\n}\n\nkj::Own<ReadableSource> newIoContextWrappedReadableSource(\n    IoContext& ioctx, kj::Own<ReadableSource> inner) {\n  return kj::heap<NoDeferredProxySource>(kj::mv(inner), ioctx);\n}\n\nkj::Own<ReadableSource> newReadableSourceFromProducer(\n    kj::Function<kj::Promise<size_t>(kj::ArrayPtr<kj::byte>, size_t)> producer,\n    kj::Maybe<uint64_t> expectedLength) {\n  return newReadableSource(kj::heap<InputStreamFromProducer>(kj::mv(producer), expectedLength));\n}\n\nkj::Own<ReadableSource> newClosedReadableSource() {\n  return kj::heap<ReadableSourceImpl>();\n}\n\nkj::Own<ReadableSource> newErroredReadableSource(kj::Exception exception) {\n  return kj::heap<ReadableSourceImpl>(kj::mv(exception));\n}\n\nkj::Own<ReadableSource> newReadableSource(kj::Own<kj::AsyncInputStream> inner) {\n  return kj::heap<ReadableSourceImpl>(kj::mv(inner));\n}\n\nkj::Own<ReadableSource> newEncodedReadableSource(\n    rpc::StreamEncoding encoding, kj::Own<kj::AsyncInputStream> inner) {\n  return kj::heap<EncodedAsyncInputStream>(kj::mv(inner), encoding);\n}\n\nkj::Own<kj::AsyncInputStream> wrapTeeBranch(kj::Own<kj::AsyncInputStream> branch) {\n  return TeeErrorAdapter::wrap(kj::mv(branch));\n}\n\n// =======================================================================================\n// MemoryInputStream\n\nnamespace {\n\n// A ReadableStreamSource backed by in-memory data that does NOT support deferred proxying.\n// This is critical when the backing memory may have V8 heap provenance - if we allowed\n// deferred proxying, the IoContext could complete and V8 GC could free the memory while\n// the deferred pump is still running, causing a use-after-free.\n//\n// TODO(soon): The expectation is that this will be update to implement ReadableSource instead\n// of ReadableStreamSource as we continue the transition.\nclass MemoryInputStream final: public ReadableStreamSource {\n public:\n  MemoryInputStream(kj::ArrayPtr<const kj::byte> bytes, kj::Maybe<kj::Own<void>> backing)\n      : unread(bytes),\n        backing(kj::mv(backing)) {}\n\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    size_t amount = kj::min(maxBytes, unread.size());\n    if (amount > 0) {\n      memcpy(buffer, unread.begin(), amount);\n      unread = unread.slice(amount, unread.size());\n    }\n    return amount;\n  }\n\n  kj::Maybe<uint64_t> tryGetLength(StreamEncoding encoding) override {\n    if (encoding == StreamEncoding::IDENTITY) {\n      return unread.size();\n    }\n    return kj::none;\n  }\n\n  kj::Promise<DeferredProxy<void>> pumpTo(WritableStreamSink& output, bool end) override {\n    // Explicitly NOT using KJ_CO_MAGIC BEGIN_DEFERRED_PROXYING here!\n    // The backing memory may be tied to V8 heap (e.g., jsg::BackingStore, Blob data),\n    // so we must complete all I/O before the IoContext can be released.\n    if (unread.size() > 0) {\n      auto data = unread;\n      unread = nullptr;\n      co_await output.write(data);\n    }\n    if (end) {\n      co_await output.end();\n    }\n    co_return;\n  }\n\n  void cancel(kj::Exception reason) override {\n    // Nothing to do - we're just reading from memory.\n    unread = nullptr;\n  }\n\n private:\n  kj::ArrayPtr<const kj::byte> unread;\n  kj::Maybe<kj::Own<void>> backing;\n};\n\n}  // namespace\n\nkj::Own<ReadableStreamSource> newMemorySource(\n    kj::ArrayPtr<const kj::byte> bytes, kj::Maybe<kj::Own<void>> maybeBacking) {\n  KJ_IF_SOME(backing, maybeBacking) {\n    return kj::heap<MemoryInputStream>(bytes, kj::mv(backing));\n  }\n  // No backing provided - make a copy of the bytes.\n  auto copy = kj::heapArray<kj::byte>(bytes);\n  auto ptr = copy.asPtr();\n  return kj::heap<MemoryInputStream>(ptr, kj::heap(kj::mv(copy)));\n}\n\n}  // namespace workerd::api::streams\n"
  },
  {
    "path": "src/workerd/api/streams/readable-source.h",
    "content": "#pragma once\n\n#include <workerd/io/worker-interface.capnp.h>\n#include <workerd/util/strong-bool.h>\n\n#include <kj/debug.h>\n\nnamespace kj {\nclass AsyncInputStream;\n}\n\nnamespace workerd {\n\nclass IoContext;\nnamespace api {\ntemplate <typename T>\nstruct DeferredProxy;\nclass ReadableStreamSource;\n}  // namespace api\n\nnamespace jsg {\nclass Lock;\n}  // namespace jsg\n\nnamespace api::streams {\n\nclass WritableSink;\n\nWD_STRONG_BOOL(EndAfterPump);\n\n// A ReadableSource is primarily intended to serve as a bridge between kj::AsyncInputStream\n// and the ReadableStream API. However, it can also be used directly by KJ-space code that needs\n// deferred proxying. While ReadableSource should probably have been a more JS-friendly\n// API, it's a bit too late to change that now. Use the ReadableSourceJsAdapter in the\n// readable-source-adapter.h file to wrap a ReadableSource for use from JavaScript.\n//\n// A ReadableSource must be treated like a KJ I/O object. Instances that are held\n// by any JS-heap objects must be held by an IoOwn.\n//\n// If the ReadableSource is canceled or dropped, all pending read() reads will be\n// canceled.\n//\n// Only one read() may be pending at a time. Attempting to initiate a second read()\n// while one is already pending will result in a rejected promise.\n//\n// Calling pumpTo initiates a sequence of read() calls until the stream is fully consumed.\n// Ownership of the underlying AsyncInputStream is transferred to the pumpTo operation and\n// the ReadableSource is put into a closed state. After calling pumpTo, no further\n// read() calls may be made directly on the ReadableSource. Dropping the returned\n// promise before it resolves will cancel the pump operation.\n//\n// It is **NOT** intended that you should implement this interface for general use.\n// It is only intended to be implemented by specific classes within workerd for the\n// purpose of bridging between kj/js streams. Streams that operate at the kj level\n// should implement the kj::Async*Stream interfaces, and streams that operate at\n// the JS level should implement to the UnderlyingSource interface. This is a\n// departure from what we've done previously but as part of the effort to simplify\n// the streams code, the goal is to reduce the number of different stream interfaces\n// that we implement to.\nclass ReadableSource {\n public:\n  // Read into the given buffer, returning a promise that resolves to the number of bytes read.\n  // The maximum number of bytes that will be read is the size of the buffer. The minimum number\n  // of bytes that will be read is minBytes. If at least minBytes cannot be read, the promise\n  // will be resolved with the number of bytes read and the stream will be closed.\n  virtual kj::Promise<size_t> read(kj::ArrayPtr<kj::byte> buffer, size_t minBytes = 1) = 0;\n\n  // If `end` is true, then `output.end()` will be called after pumping. Note that it's especially\n  // important to take advantage of this when using deferred proxying since calling `end()`\n  // directly might attempt to use the `IoContext` to call `registerPendingEvent()`.\n  // If the pump fails, the ReadableSource will be left in an errored state. The\n  // default implementation uses read() to read chunks of data and write them to the output\n  // using a 16KB buffer.\n  //\n  // Per the contract of pumpTo(), it is the caller's responsibility to ensure that both\n  // the WritableStreamSink and this ReadableSource remain alive until the returned\n  // promise resolves!\n  //\n  // It is the caller's responsibility to ensure that WritableStreamSink and this\n  // ReadableSource remain alive until the wrapped deferred proxy task resolves.\n  // The default implementation does arrange to make it safe to drop the source once\n  // the pump begins but that's only precautionary/defensive. It's still better/safer\n  // for the caller to keep the source alive until the pump completes.\n  virtual kj::Promise<DeferredProxy<void>> pumpTo(\n      WritableSink& output, EndAfterPump end = EndAfterPump::YES) = 0;\n\n  // If the stream is still active, and the encoding matches an encoding that the stream\n  // can provide, gets the total length, if known. If the length is not known, or the\n  // encoding does not match the encoding of the underlying stream, or the stream is closed\n  // or errored, returns kj::none.\n  virtual kj::Maybe<size_t> tryGetLength(rpc::StreamEncoding encoding) = 0;\n\n  // Fully consume the stream and return all of its data as a byte array. The limit\n  // parameter is the maximum number of bytes to read. If the stream contains more\n  // than this number of bytes, the promise will reject with an exception.\n  virtual kj::Promise<kj::Array<const kj::byte>> readAllBytes(size_t limit) = 0;\n\n  // Fully consume the stream and return all of its data as a string. The limit\n  // parameter is the maximum number of bytes to read. If the stream contains more\n  // than this number of bytes, the promise will reject with an exception.\n  virtual kj::Promise<kj::String> readAllText(size_t limit) = 0;\n\n  // Cancels the underlying source if it is still active. Must put the stream into an\n  // errored state. After calling this, all pending and future reads should fail.\n  // Dropping the ReadableSource without calling cancel() first should trigger\n  // cancel() with a generic exception.\n  virtual void cancel(kj::Exception reason) = 0;\n\n  struct Tee {\n    kj::Own<ReadableSource> branch1;\n    kj::Own<ReadableSource> branch2;\n  };\n\n  // Tees the stream into two branches. The returned Tee contains two new ReadableSource\n  // instances that will each receive the same data. Once this is called, this instance is no\n  // longer usable and will behave as if it has been closed.\n  // The limit parameter specifies the maximum buffer size to use when teeing.\n  virtual Tee tee(size_t limit) = 0;\n\n  // Gets the encoding of the stream.\n  virtual rpc::StreamEncoding getEncoding() = 0;\n};\n\n// Utility base class for ReadableSource wrappers that delegate all\n// operations to an inner ReadableSource while selectively overriding\n// some operations.\nclass ReadableSourceWrapper: public ReadableSource {\n public:\n  KJ_DISALLOW_COPY_AND_MOVE(ReadableSourceWrapper);\n  virtual ~ReadableSourceWrapper() noexcept(false) = default;\n\n  kj::Promise<size_t> read(kj::ArrayPtr<kj::byte> buffer, size_t minBytes = 1) override {\n    return getInner().read(buffer, minBytes);\n  }\n\n  kj::Promise<DeferredProxy<void>> pumpTo(\n      WritableSink& output, EndAfterPump end = EndAfterPump::YES) override {\n    return getInner().pumpTo(output, end);\n  }\n\n  kj::Promise<kj::Array<const kj::byte>> readAllBytes(size_t limit) override {\n    return getInner().readAllBytes(limit);\n  }\n\n  kj::Promise<kj::String> readAllText(size_t limit) override {\n    return getInner().readAllText(limit);\n  }\n\n  kj::Maybe<size_t> tryGetLength(rpc::StreamEncoding encoding) override {\n    return getInner().tryGetLength(encoding);\n  }\n\n  void cancel(kj::Exception reason) override {\n    getInner().cancel(kj::mv(reason));\n  }\n\n  Tee tee(size_t limit) override {\n    return getInner().tee(limit);\n  }\n\n  rpc::StreamEncoding getEncoding() override {\n    return getInner().getEncoding();\n  }\n\n  // Releases ownership of the inner ReadableSource. After calling this,\n  // this wrapper becomes unusable.\n  kj::Own<ReadableSource> release() {\n    auto ret = kj::mv(KJ_ASSERT_NONNULL(inner));\n    inner = kj::none;\n    return kj::mv(ret);\n  }\n\n protected:\n  ReadableSourceWrapper(kj::Own<ReadableSource> inner): inner(kj::mv(inner)) {}\n\n  ReadableSource& getInner() {\n    return *KJ_ASSERT_NONNULL(inner);\n  }\n\n private:\n  kj::Maybe<kj::Own<ReadableSource>> inner;\n};\n\n// Creates a ReadableSource that wraps the given kj::AsyncInputStream.\nkj::Own<ReadableSource> newReadableSource(kj::Own<kj::AsyncInputStream> inner);\n\n// Creates a ReadableSource that is already in the errored state.\nkj::Own<ReadableSource> newErroredReadableSource(kj::Exception exception);\n\n// Creates a ReadableSource that is already closed and will produce no data.\nkj::Own<ReadableSource> newClosedReadableSource();\n\n// Creates a ReadableSource that produces the given bytes and then closes.\n// The backing object, if any, is held alive until the stream is closed or canceled.\n// If the backing object is not provided, the bytes are copied.\nkj::Own<ReadableSource> newReadableSourceFromBytes(\n    kj::ArrayPtr<const kj::byte> bytes, kj::Maybe<kj::Own<void>> backing = kj::none);\n\n// Creates a ReadableSource that wraps the given source and prevents deferred proxying.\nkj::Own<ReadableSource> newIoContextWrappedReadableSource(\n    IoContext& ioctx, kj::Own<ReadableSource> inner);\n\n// Creates a ReadableSource that calls the given producer function to produce data\n// on each read (useful primarily for testing).\nkj::Own<ReadableSource> newReadableSourceFromProducer(\n    kj::Function<kj::Promise<size_t>(kj::ArrayPtr<kj::byte>, size_t)> producer,\n    kj::Maybe<uint64_t> expectedLength = kj::none);\n\n// Creates a ReadableSource that decodes the given stream according to the given encoding.\nkj::Own<ReadableSource> newEncodedReadableSource(\n    rpc::StreamEncoding encoding, kj::Own<kj::AsyncInputStream> inner);\n\n// Wraps a kj::AsyncInputStream returned from a tee() call to ensure that it translates\n// errors into equivalent JS exceptions. Typically this is used when customizing tee() on\n// a ReadableSource implementation.\nkj::Own<kj::AsyncInputStream> wrapTeeBranch(kj::Own<kj::AsyncInputStream> branch);\n\n// A ReadableStreamSource backed by in-memory data. Unlike newSystemStream() wrapping a\n// newMemoryInputStream(), this implementation does NOT support deferred proxying. This is\n// important when the backing memory has V8 heap provenance (e.g., jsg::BackingStore, Blob data,\n// kj::Array<kj::byte> with a v8::BackingStore attached, etc)\n// since the memory could be freed by GC after the IoContext completes.\n//\n// The `backing` parameter keeps the underlying memory alive for the lifetime of the stream.\n// If not provided, the bytes are copied.\n//\n// TODO(soon): Update to implement ReadableSource instead of ReadableStreamSource.\n// For now this is a ReadableStreamSource for compat with existing code. Once internal.h/c++\n// is updated to use ReadableSource, we will change this also.\n//\n// TODO(cleanup): It would be nice to eventually have some sort of stronger guarantee when\n// deferred proxying can or cannot be used with a stream. Right now it's a bit ad hoc and\n// error-prone. It requires the stream impl to keep track of whether it can be deferred-proxied\n// or not, but in this case, that may be entirely opaque behind the details of the backing memory\n// as is the case with kj::Array<kj::byte> instances that come from the type wrapper system.\nkj::Own<ReadableStreamSource> newMemorySource(\n    kj::ArrayPtr<const kj::byte> bytes, kj::Maybe<kj::Own<void>> backing = kj::none);\n\n}  // namespace api::streams\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/api/streams/readable.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"readable.h\"\n\n#include \"internal.h\"\n#include \"writable.h\"\n\n#include <workerd/api/system-streams.h>\n#include <workerd/api/worker-rpc.h>\n#include <workerd/io/features.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api {\n\nReaderImpl::ReaderImpl(ReadableStreamController::Reader& reader)\n    : ioContext(tryGetIoContext()),\n      reader(reader),\n      state(ReaderState::create<Initial>()) {}\n\nReaderImpl::~ReaderImpl() noexcept(false) {\n  KJ_IF_SOME(attached, state.tryGetActiveUnsafe()) {\n    attached.stream->getController().releaseReader(reader, kj::none);\n  }\n}\n\nvoid ReaderImpl::attach(ReadableStreamController& controller, jsg::Promise<void> closedPromise) {\n  KJ_ASSERT(state.is<Initial>());\n  state.transitionTo<Attached>(controller.addRef());\n  this->closedPromise = kj::mv(closedPromise);\n}\n\nvoid ReaderImpl::detach() {\n  // Only transition from Attached to Closed.\n  // All other states (Initial, Closed, Released) are no-ops.\n  if (state.isActive()) {\n    state.transitionTo<Closed>();\n  }\n}\n\njsg::Promise<void> ReaderImpl::cancel(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason) {\n  assertAttachedOrTerminal();\n  if (state.is<Released>()) {\n    return js.rejectedPromise<void>(\n        js.v8TypeError(\"This ReadableStream reader has been released.\"_kj));\n  }\n  if (state.is<Closed>()) {\n    return js.resolvedPromise();\n  }\n  auto& attached = state.requireActiveUnsafe();\n  // In some edge cases, this reader is the last thing holding a strong\n  // reference to the stream. Calling cancel might cause the readers strong\n  // reference to be cleared, so let's make sure we keep a reference to\n  // the stream at least until the call to cancel completes.\n  auto ref = attached.stream.addRef();\n  return attached.stream->getController().cancel(js, maybeReason);\n}\n\njsg::MemoizedIdentity<jsg::Promise<void>>& ReaderImpl::getClosed() {\n  // The closed promise should always be set after the object is created so this assert\n  // should always be safe.\n  return KJ_ASSERT_NONNULL(closedPromise);\n}\n\nvoid ReaderImpl::lockToStream(jsg::Lock& js, ReadableStream& stream) {\n  KJ_ASSERT(!stream.isLocked());\n  KJ_ASSERT(stream.getController().lockReader(js, reader));\n}\n\njsg::Promise<ReadResult> ReaderImpl::read(\n    jsg::Lock& js, kj::Maybe<ReadableStreamController::ByobOptions> byobOptions) {\n  assertAttachedOrTerminal();\n  if (state.is<Released>()) {\n    return js.rejectedPromise<ReadResult>(\n        js.v8TypeError(\"This ReadableStream reader has been released.\"_kj));\n  }\n  if (state.is<Closed>()) {\n    return js.rejectedPromise<ReadResult>(\n        js.v8TypeError(\"This ReadableStream has been closed.\"_kj));\n  }\n  auto& attached = state.requireActiveUnsafe();\n  KJ_IF_SOME(options, byobOptions) {\n    // Per the spec, we must perform these checks before disturbing the stream.\n    size_t atLeast = options.atLeast.orDefault(1);\n\n    if (options.byteLength == 0) {\n      return js.rejectedPromise<ReadResult>(\n          js.v8TypeError(\"You must call read() on a \\\"byob\\\" reader with a positive-sized \"\n                         \"TypedArray object.\"_kj));\n    }\n    if (atLeast == 0) {\n      return js.rejectedPromise<ReadResult>(js.v8TypeError(\n          kj::str(\"Requested invalid minimum number of bytes to read (\", atLeast, \").\")));\n    }\n    if (atLeast > options.byteLength) {\n      return js.rejectedPromise<ReadResult>(js.v8TypeError(kj::str(\"Minimum bytes to read (\",\n          atLeast, \") exceeds size of buffer (\", options.byteLength, \").\")));\n    }\n\n    jsg::BufferSource source(js, options.bufferView.getHandle(js));\n    options.atLeast = atLeast * source.getElementSize();\n  }\n\n  return KJ_ASSERT_NONNULL(attached.stream->getController().read(js, kj::mv(byobOptions)));\n}\n\nvoid ReaderImpl::releaseLock(jsg::Lock& js) {\n  // TODO(soon): Releasing the lock should cancel any pending reads. This is a recent\n  // modification to the spec that we have not yet implemented.\n  assertAttachedOrTerminal();\n  // Closed and Released states are no-ops.\n  KJ_IF_SOME(attached, state.tryGetActiveUnsafe()) {\n    // In some edge cases, this reader is the last thing holding a strong\n    // reference to the stream. Calling releaseLock might cause the readers strong\n    // reference to be cleared, so let's make sure we keep a reference to\n    // the stream at least until the call to releaseLock completes.\n    auto ref = attached.stream.addRef();\n    attached.stream->getController().releaseReader(reader, js);\n    state.transitionTo<Released>();\n  }\n}\n\nvoid ReaderImpl::visitForGc(jsg::GcVisitor& visitor) {\n  KJ_IF_SOME(attached, state.tryGetActiveUnsafe()) {\n    visitor.visit(attached.stream);\n  }\n  visitor.visit(closedPromise);\n}\n\n// ======================================================================================\n\nReadableStreamDefaultReader::ReadableStreamDefaultReader(): impl(*this) {}\n\njsg::Ref<ReadableStreamDefaultReader> ReadableStreamDefaultReader::constructor(\n    jsg::Lock& js, jsg::Ref<ReadableStream> stream) {\n  JSG_REQUIRE(\n      !stream->isLocked(), TypeError, \"This ReadableStream is currently locked to a reader.\");\n  auto reader = js.alloc<ReadableStreamDefaultReader>();\n  reader->lockToStream(js, *stream);\n  return kj::mv(reader);\n}\n\nvoid ReadableStreamDefaultReader::attach(\n    ReadableStreamController& controller, jsg::Promise<void> closedPromise) {\n  impl.attach(controller, kj::mv(closedPromise));\n}\n\njsg::Promise<void> ReadableStreamDefaultReader::cancel(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason) {\n  return impl.cancel(js, kj::mv(maybeReason));\n}\n\nvoid ReadableStreamDefaultReader::detach() {\n  impl.detach();\n}\n\njsg::MemoizedIdentity<jsg::Promise<void>>& ReadableStreamDefaultReader::getClosed() {\n  return impl.getClosed();\n}\n\nvoid ReadableStreamDefaultReader::lockToStream(jsg::Lock& js, ReadableStream& stream) {\n  impl.lockToStream(js, stream);\n}\n\njsg::Promise<ReadResult> ReadableStreamDefaultReader::read(jsg::Lock& js) {\n  return impl.read(js, kj::none);\n}\n\nvoid ReadableStreamDefaultReader::releaseLock(jsg::Lock& js) {\n  impl.releaseLock(js);\n}\n\nvoid ReadableStreamDefaultReader::visitForGc(jsg::GcVisitor& visitor) {\n  visitor.visit(impl);\n}\n\n// ======================================================================================\n\nReadableStreamBYOBReader::ReadableStreamBYOBReader(): impl(*this) {}\n\njsg::Ref<ReadableStreamBYOBReader> ReadableStreamBYOBReader::constructor(\n    jsg::Lock& js, jsg::Ref<ReadableStream> stream) {\n  JSG_REQUIRE(\n      !stream->isLocked(), TypeError, \"This ReadableStream is currently locked to a reader.\");\n\n  if (!stream->getController().isClosedOrErrored()) {\n    JSG_REQUIRE(stream->getController().isByteOriented(), TypeError,\n        \"This ReadableStream does not support BYOB reads.\");\n  }\n\n  auto reader = js.alloc<ReadableStreamBYOBReader>();\n  reader->lockToStream(js, *stream);\n  return kj::mv(reader);\n}\n\nvoid ReadableStreamBYOBReader::attach(\n    ReadableStreamController& controller, jsg::Promise<void> closedPromise) {\n  impl.attach(controller, kj::mv(closedPromise));\n}\n\njsg::Promise<void> ReadableStreamBYOBReader::cancel(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason) {\n  return impl.cancel(js, kj::mv(maybeReason));\n}\n\nvoid ReadableStreamBYOBReader::detach() {\n  impl.detach();\n}\n\njsg::MemoizedIdentity<jsg::Promise<void>>& ReadableStreamBYOBReader::getClosed() {\n  return impl.getClosed();\n}\n\nvoid ReadableStreamBYOBReader::lockToStream(jsg::Lock& js, ReadableStream& stream) {\n  impl.lockToStream(js, stream);\n}\n\njsg::Promise<ReadResult> ReadableStreamBYOBReader::read(jsg::Lock& js,\n    v8::Local<v8::ArrayBufferView> byobBuffer,\n    jsg::Optional<ReadableStreamBYOBReaderReadOptions> maybeOptions) {\n  static const ReadableStreamBYOBReaderReadOptions defaultOptions{};\n  auto options = ReadableStreamController::ByobOptions{\n    .bufferView = js.v8Ref(byobBuffer),\n    .byteOffset = byobBuffer->ByteOffset(),\n    .byteLength = byobBuffer->ByteLength(),\n    .atLeast = maybeOptions.orDefault(defaultOptions).min.orDefault(1),\n    .detachBuffer = FeatureFlags::get(js).getStreamsByobReaderDetachesBuffer(),\n  };\n  return impl.read(js, kj::mv(options));\n}\n\njsg::Promise<ReadResult> ReadableStreamBYOBReader::readAtLeast(\n    jsg::Lock& js, int minBytes, v8::Local<v8::ArrayBufferView> byobBuffer) {\n  auto options = ReadableStreamController::ByobOptions{\n    .bufferView = js.v8Ref(byobBuffer),\n    .byteOffset = byobBuffer->ByteOffset(),\n    .byteLength = byobBuffer->ByteLength(),\n    .atLeast = minBytes,\n    .detachBuffer = true,\n  };\n  return impl.read(js, kj::mv(options));\n}\n\nvoid ReadableStreamBYOBReader::releaseLock(jsg::Lock& js) {\n  impl.releaseLock(js);\n}\n\nvoid ReadableStreamBYOBReader::visitForGc(jsg::GcVisitor& visitor) {\n  visitor.visit(impl);\n}\n\n// ======================================================================================\n// DrainingReader implementation\n\nDrainingReader::DrainingReader(): ioContext(tryGetIoContext()) {}\n\nDrainingReader::~DrainingReader() noexcept(false) {\n  KJ_IF_SOME(stream, state.tryGet<Attached>()) {\n    stream->getController().releaseReader(*this, kj::none);\n  }\n}\n\nkj::Maybe<kj::Own<DrainingReader>> DrainingReader::create(jsg::Lock& js, ReadableStream& stream) {\n  if (stream.isLocked()) {\n    return kj::none;\n  }\n  auto reader = kj::heap<DrainingReader>();\n  if (!stream.getController().lockReader(js, *reader)) {\n    return kj::none;\n  }\n  return kj::mv(reader);\n}\n\nvoid DrainingReader::attach(\n    ReadableStreamController& controller, jsg::Promise<void> closedPromise) {\n  KJ_ASSERT(state.is<Initial>());\n  state = controller.addRef();\n  this->closedPromise = kj::mv(closedPromise);\n}\n\nvoid DrainingReader::detach() {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(i, Initial) {\n      return;\n    }\n    KJ_CASE_ONEOF(stream, Attached) {\n      state.init<StreamStates::Closed>();\n      return;\n    }\n    KJ_CASE_ONEOF(c, StreamStates::Closed) {\n      return;\n    }\n    KJ_CASE_ONEOF(r, Released) {\n      return;\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\njsg::Promise<DrainingReadResult> DrainingReader::read(jsg::Lock& js, size_t maxRead) {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(i, Initial) {\n      KJ_FAIL_ASSERT(\"this reader was never attached\");\n    }\n    KJ_CASE_ONEOF(stream, Attached) {\n      auto& controller = stream->getController();\n      KJ_IF_SOME(result, controller.drainingRead(js, maxRead)) {\n        return kj::mv(result);\n      }\n      return js.rejectedPromise<DrainingReadResult>(\n          js.v8TypeError(\"Unable to perform draining read on this stream.\"_kj));\n    }\n    KJ_CASE_ONEOF(r, Released) {\n      return js.rejectedPromise<DrainingReadResult>(\n          js.v8TypeError(\"This ReadableStream reader has been released.\"_kj));\n    }\n    KJ_CASE_ONEOF(c, StreamStates::Closed) {\n      return js.resolvedPromise(DrainingReadResult{\n        .chunks = kj::Array<kj::Array<kj::byte>>(),\n        .done = true,\n      });\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\njsg::Promise<void> DrainingReader::cancel(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason) {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(i, Initial) {\n      KJ_FAIL_ASSERT(\"this reader was never attached\");\n    }\n    KJ_CASE_ONEOF(stream, Attached) {\n      auto ref = stream.addRef();\n      return stream->getController().cancel(js, maybeReason);\n    }\n    KJ_CASE_ONEOF(r, Released) {\n      return js.rejectedPromise<void>(\n          js.v8TypeError(\"This ReadableStream reader has been released.\"_kj));\n    }\n    KJ_CASE_ONEOF(c, StreamStates::Closed) {\n      return js.resolvedPromise();\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid DrainingReader::releaseLock(jsg::Lock& js) {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(i, Initial) {\n      KJ_FAIL_ASSERT(\"this reader was never attached\");\n    }\n    KJ_CASE_ONEOF(stream, Attached) {\n      auto ref = stream.addRef();\n      stream->getController().releaseReader(*this, js);\n      state.init<Released>();\n      return;\n    }\n    KJ_CASE_ONEOF(c, StreamStates::Closed) {\n      return;\n    }\n    KJ_CASE_ONEOF(r, Released) {\n      return;\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nbool DrainingReader::isAttached() const {\n  return state.is<Attached>();\n}\n\nvoid DrainingReader::visitForGc(jsg::GcVisitor& visitor) {\n  KJ_IF_SOME(stream, state.tryGet<Attached>()) {\n    visitor.visit(stream);\n  }\n  visitor.visit(closedPromise);\n}\n\n// ======================================================================================\n\nReadableStream::ReadableStream(IoContext& ioContext, kj::Own<ReadableStreamSource> source)\n    : ReadableStream(newReadableStreamInternalController(ioContext, kj::mv(source))) {}\n\nReadableStream::ReadableStream(kj::Own<ReadableStreamController> controller)\n    : ioContext(tryGetIoContext()),\n      controller(kj::mv(controller)) {\n  getController().setOwnerRef(*this);\n}\n\nvoid ReadableStream::visitForGc(jsg::GcVisitor& visitor) {\n  visitor.visit(getController());\n  KJ_IF_SOME(pair, eofResolverPair) {\n    visitor.visit(pair.resolver);\n    visitor.visit(pair.promise);\n  }\n}\n\njsg::Ref<ReadableStream> ReadableStream::addRef() {\n  return JSG_THIS;\n}\n\nbool ReadableStream::isDisturbed() {\n  return getController().isDisturbed();\n}\n\nbool ReadableStream::isLocked() {\n  return getController().isLockedToReader();\n}\n\njsg::Promise<void> ReadableStream::onEof(jsg::Lock& js) {\n  eofResolverPair = js.newPromiseAndResolver<void>();\n  return kj::mv(KJ_ASSERT_NONNULL(eofResolverPair).promise);\n}\n\nvoid ReadableStream::signalEof(jsg::Lock& js) {\n  KJ_IF_SOME(pair, eofResolverPair) {\n    pair.resolver.resolve(js);\n  }\n}\n\nReadableStreamController& ReadableStream::getController() {\n  return *controller;\n}\n\njsg::Promise<void> ReadableStream::cancel(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason) {\n  if (isLocked()) {\n    return js.rejectedPromise<void>(\n        js.v8TypeError(\"This ReadableStream is currently locked to a reader.\"_kj));\n  }\n  return getController().cancel(js, maybeReason);\n}\n\nReadableStream::Reader ReadableStream::getReader(\n    jsg::Lock& js, jsg::Optional<GetReaderOptions> options) {\n  JSG_REQUIRE(!isLocked(), TypeError, \"This ReadableStream is currently locked to a reader.\");\n\n  bool isByob = false;\n  KJ_IF_SOME(o, options) {\n    KJ_IF_SOME(mode, o.mode) {\n      JSG_REQUIRE(\n          mode == \"byob\", TypeError, \"mode must be undefined or 'byob' in call to getReader().\");\n      // No need to check that the ReadableStream implementation is a byte stream: the first\n      // invocation of read() will do that for us and throw if necessary. Also, we should really\n      // just support reading non-byte streams with BYOB readers.\n      isByob = true;\n    }\n  }\n\n  if (isByob) {\n    return ReadableStreamBYOBReader::constructor(js, JSG_THIS);\n  }\n  return ReadableStreamDefaultReader::constructor(js, JSG_THIS);\n}\n\njsg::Ref<ReadableStream::ReadableStreamAsyncIterator> ReadableStream::values(\n    jsg::Lock& js, jsg::Optional<ValuesOptions> options) {\n  static const auto defaultOptions = ValuesOptions{};\n  return js.alloc<ReadableStreamAsyncIterator>(AsyncIteratorState{.ioContext = ioContext,\n    .reader = ReadableStreamDefaultReader::constructor(js, JSG_THIS),\n    .preventCancel = options.orDefault(defaultOptions).preventCancel.orDefault(false)});\n}\n\njsg::Ref<ReadableStream> ReadableStream::pipeThrough(\n    jsg::Lock& js, Transform transform, jsg::Optional<PipeToOptions> maybeOptions) {\n  auto& controller = getController();\n\n  auto& destination = transform.writable->getController();\n  JSG_REQUIRE(!isLocked(), TypeError, \"This ReadableStream is currently locked to a reader.\");\n  JSG_REQUIRE(!destination.isLockedToWriter(), TypeError,\n      \"This WritableStream is currently locked to a writer.\");\n\n  auto options = kj::mv(maybeOptions).orDefault({});\n  options.pipeThrough = true;\n  // The lambda intentionally captures self as a visitable reference, ensuring\n  // JSG_THIS stays alive until the pipe promise resolves.\n  controller.pipeTo(js, destination, kj::mv(options))\n      .then(js,\n          JSG_VISITABLE_LAMBDA(\n              (self = JSG_THIS), (self), (jsg::Lock& js) { return js.resolvedPromise(); }))\n      .markAsHandled(js);\n  return kj::mv(transform.readable);\n}\n\njsg::Promise<void> ReadableStream::pipeTo(jsg::Lock& js,\n    jsg::Ref<WritableStream> destination,\n    jsg::Optional<PipeToOptions> maybeOptions) {\n  if (isLocked()) {\n    return js.rejectedPromise<void>(\n        js.v8TypeError(\"This ReadableStream is currently locked to a reader.\"_kj));\n  }\n\n  if (destination->getController().isLockedToWriter()) {\n    return js.rejectedPromise<void>(\n        js.v8TypeError(\"This WritableStream is currently locked to a writer\"_kj));\n  }\n\n  auto options = kj::mv(maybeOptions).orDefault({});\n  return getController().pipeTo(js, destination->getController(), kj::mv(options));\n}\n\nkj::Array<jsg::Ref<ReadableStream>> ReadableStream::tee(jsg::Lock& js) {\n  JSG_REQUIRE(!isLocked(), TypeError, \"This ReadableStream is currently locked to a reader,\");\n  auto tee = getController().tee(js);\n  return kj::arr(kj::mv(tee.branch1), kj::mv(tee.branch2));\n}\n\njsg::JsString ReadableStream::inspectState(jsg::Lock& js) {\n  if (controller->isClosedOrErrored()) {\n    return js.strIntern(controller->isClosed() ? \"closed\"_kj : \"errored\"_kj);\n  } else {\n    return js.strIntern(\"readable\"_kj);\n  }\n}\n\nbool ReadableStream::inspectSupportsBYOB() {\n  return controller->isByteOriented();\n}\n\njsg::Optional<uint64_t> ReadableStream::inspectLength() {\n  return tryGetLength(StreamEncoding::IDENTITY);\n}\n\njsg::Promise<kj::Maybe<jsg::Value>> ReadableStream::nextFunction(\n    jsg::Lock& js, AsyncIteratorState& state) {\n  return state.reader->read(js).then(\n      js, [reader = state.reader.addRef()](jsg::Lock& js, ReadResult result) mutable {\n    if (result.done) {\n      reader->releaseLock(js);\n      return js.resolvedPromise(kj::Maybe<jsg::Value>(kj::none));\n    }\n    return js.resolvedPromise<kj::Maybe<jsg::Value>>(kj::mv(result.value));\n  });\n}\n\njsg::Promise<void> ReadableStream::returnFunction(\n    jsg::Lock& js, AsyncIteratorState& state, jsg::Optional<jsg::Value>& value) {\n  if (state.reader.get() != nullptr) {\n    auto reader = kj::mv(state.reader);\n    if (!state.preventCancel) {\n      auto promise = reader->cancel(js, value.map([&](jsg::Value& v) { return v.getHandle(js); }));\n      reader->releaseLock(js);\n      auto result = promise.then(js,\n          JSG_VISITABLE_LAMBDA((reader = kj::mv(reader)), (reader), (jsg::Lock& js) {\n            // Ensure that the reader is not garbage collected until the cancel promise resolves.\n            return js.resolvedPromise();\n          }));\n      // When the stream is already errored, cancel() returns a rejected promise\n      // that propagates through the .then() chain. Mark it as handled so V8 does\n      // not fire unhandledrejection events during iterator teardown.\n      result.markAsHandled(js);\n      return kj::mv(result);\n    }\n\n    reader->releaseLock(js);\n  }\n  return js.resolvedPromise();\n}\n\njsg::Ref<ReadableStream> ReadableStream::detach(jsg::Lock& js, bool ignoreDisturbed) {\n  JSG_REQUIRE(\n      !isDisturbed() || ignoreDisturbed, TypeError, \"The ReadableStream has already been read.\");\n  JSG_REQUIRE(!isLocked(), TypeError, \"The ReadableStream has been locked to a reader.\");\n  return js.alloc<ReadableStream>(getController().detach(js, ignoreDisturbed));\n}\n\nkj::Maybe<uint64_t> ReadableStream::tryGetLength(StreamEncoding encoding) {\n  return getController().tryGetLength(encoding);\n}\n\nkj::Promise<DeferredProxy<void>> ReadableStream::pumpTo(\n    jsg::Lock& js, kj::Own<WritableStreamSink> sink, bool end) {\n  JSG_REQUIRE(\n      IoContext::hasCurrent(), Error, \"Unable to consume this ReadableStream outside of a request\");\n  JSG_REQUIRE(!isLocked(), TypeError, \"The ReadableStream has been locked to a reader.\");\n  return getController().pumpTo(js, kj::mv(sink), end);\n}\n\njsg::Ref<ReadableStream> ReadableStream::constructor(jsg::Lock& js,\n    jsg::Optional<UnderlyingSource> underlyingSource,\n    jsg::Optional<StreamQueuingStrategy> queuingStrategy) {\n\n  JSG_REQUIRE(FeatureFlags::get(js).getStreamsJavaScriptControllers(), Error,\n      \"To use the new ReadableStream() constructor, enable the \"\n      \"streams_enable_constructors compatibility flag. \"\n      \"Refer to the docs for more information: https://developers.cloudflare.com/workers/platform/compatibility-dates/#compatibility-flags\");\n  // We account for the memory usage of the ReadableStream and its controller together because their\n  // lifetimes are identical and memory accounting itself has a memory overhead.\n  auto controller = newReadableStreamJsController();\n  auto stream = js.allocAccounted<ReadableStream>(\n      sizeof(ReadableStream) + controller->jsgGetMemorySelfSize(), kj::mv(controller));\n  stream->getController().setup(js, kj::mv(underlyingSource), kj::mv(queuingStrategy));\n  return kj::mv(stream);\n}\n\njsg::Optional<uint32_t> ByteLengthQueuingStrategy::size(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeValue) {\n  KJ_IF_SOME(value, maybeValue) {\n    if ((value)->IsArrayBuffer()) {\n      auto buffer = value.As<v8::ArrayBuffer>();\n      return buffer->ByteLength();\n    } else if ((value)->IsArrayBufferView()) {\n      auto view = value.As<v8::ArrayBufferView>();\n      return view->ByteLength();\n    } else {\n      // Per the WHATWG Streams spec, ByteLengthQueuingStrategy.size should return\n      // GetV(chunk, \"byteLength\"), which means getting the byteLength property\n      // from any object, not just ArrayBuffer/ArrayBufferView.\n      KJ_IF_SOME(obj, jsg::JsValue(value).tryCast<jsg::JsObject>()) {\n        auto byteLength = obj.get(js, \"byteLength\"_kj);\n        KJ_IF_SOME(num, byteLength.tryCast<jsg::JsNumber>()) {\n          KJ_IF_SOME(val, num.value(js)) {\n            return static_cast<uint32_t>(val);\n          }\n        }\n      }\n    }\n  }\n  return kj::none;\n}\n\nnamespace {\n\n// TODO(cleanup): These classes have been copied to external-pusher.c++. The copies here can be\n//   deleted as soon as we've switched from StreamSink to ExternalPusher and can delete all the\n//   StreamSink-related code. For now I'm not trying to avoid duplication.\n\n// HACK: We need as async pipe, like kj::newOneWayPipe(), except supporting explicit end(). So we\n//   wrap the two ends of the pipe in special adapters that track whether end() was called.\nclass ExplicitEndOutputPipeAdapter final: public capnp::ExplicitEndOutputStream {\n public:\n  ExplicitEndOutputPipeAdapter(\n      kj::Own<kj::AsyncOutputStream> inner, kj::Own<kj::RefcountedWrapper<bool>> ended)\n      : inner(kj::mv(inner)),\n        ended(kj::mv(ended)) {}\n\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override {\n    return KJ_REQUIRE_NONNULL(inner)->write(buffer);\n  }\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {\n    return KJ_REQUIRE_NONNULL(inner)->write(pieces);\n  }\n\n  kj::Maybe<kj::Promise<uint64_t>> tryPumpFrom(\n      kj::AsyncInputStream& input, uint64_t amount) override {\n    return KJ_REQUIRE_NONNULL(inner)->tryPumpFrom(input, amount);\n  }\n\n  kj::Promise<void> whenWriteDisconnected() override {\n    return KJ_REQUIRE_NONNULL(inner)->whenWriteDisconnected();\n  }\n\n  kj::Promise<void> end() override {\n    // Signal to the other side that end() was actually called.\n    ended->getWrapped() = true;\n    inner = kj::none;\n    return kj::READY_NOW;\n  }\n\n private:\n  kj::Maybe<kj::Own<kj::AsyncOutputStream>> inner;\n  kj::Own<kj::RefcountedWrapper<bool>> ended;\n};\n\nclass ExplicitEndInputPipeAdapter final: public kj::AsyncInputStream {\n public:\n  ExplicitEndInputPipeAdapter(kj::Own<kj::AsyncInputStream> inner,\n      kj::Own<kj::RefcountedWrapper<bool>> ended,\n      kj::Maybe<uint64_t> expectedLength)\n      : inner(kj::mv(inner)),\n        ended(kj::mv(ended)),\n        expectedLength(expectedLength) {}\n\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    size_t result = co_await inner->tryRead(buffer, minBytes, maxBytes);\n\n    KJ_IF_SOME(l, expectedLength) {\n      KJ_ASSERT(result <= l);\n      l -= result;\n      if (l == 0) {\n        // If we got all the bytes we expected, we treat this as a successful end, because the\n        // underlying KJ pipe is not actually going to wait for the other side to drop. This is\n        // consistent with the behavior of Content-Length in HTTP anyway.\n        ended->getWrapped() = true;\n      }\n    }\n\n    if (result < minBytes) {\n      // Verify that end() was called.\n      if (!ended->getWrapped()) {\n        JSG_FAIL_REQUIRE(Error, \"ReadableStream received over RPC disconnected prematurely.\");\n      }\n    }\n    co_return result;\n  }\n\n  kj::Maybe<uint64_t> tryGetLength() override {\n    return inner->tryGetLength();\n  }\n\n  kj::Promise<uint64_t> pumpTo(kj::AsyncOutputStream& output, uint64_t amount) override {\n    return inner->pumpTo(output, amount);\n  }\n\n private:\n  kj::Own<kj::AsyncInputStream> inner;\n  kj::Own<kj::RefcountedWrapper<bool>> ended;\n  kj::Maybe<uint64_t> expectedLength;\n};\n\n// Wrapper around ReadableStreamSource that prevents deferred proxying. We need this for RPC\n// streams because although they are \"system streams\", they become disconnected when the IoContext\n// is destroyed, due to the JsRpcCustomEvent being canceled.\n//\n// TODO(someday): Devise a better way for RPC streams to extend the lifetime of the RPC session\n//   beyond the destruction of the IoContext, if it is being used for deferred proxying.\nclass NoDeferredProxyReadableStream final: public ReadableStreamSource {\n public:\n  NoDeferredProxyReadableStream(kj::Own<ReadableStreamSource> inner, IoContext& ioctx)\n      : inner(kj::mv(inner)),\n        ioctx(ioctx) {}\n\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    return inner->tryRead(buffer, minBytes, maxBytes);\n  }\n\n  kj::Promise<DeferredProxy<void>> pumpTo(WritableStreamSink& output, bool end) override {\n    // Move the deferred proxy part of the task over to the non-deferred part. To do this,\n    // we use `ioctx.waitForDeferredProxy()`, which returns a single promise covering both parts\n    // (and, importantly, registering pending events where needed). Then, we add a noop deferred\n    // proxy to the end of that.\n    return addNoopDeferredProxy(ioctx.waitForDeferredProxy(inner->pumpTo(output, end)));\n  }\n\n  StreamEncoding getPreferredEncoding() override {\n    return inner->getPreferredEncoding();\n  }\n\n  kj::Maybe<uint64_t> tryGetLength(StreamEncoding encoding) override {\n    return inner->tryGetLength(encoding);\n  }\n\n  void cancel(kj::Exception reason) override {\n    return inner->cancel(kj::mv(reason));\n  }\n\n  kj::Maybe<Tee> tryTee(uint64_t limit) override {\n    return inner->tryTee(limit).map([&](Tee tee) {\n      return Tee{.branches = {\n                   kj::heap<NoDeferredProxyReadableStream>(kj::mv(tee.branches[0]), ioctx),\n                   kj::heap<NoDeferredProxyReadableStream>(kj::mv(tee.branches[1]), ioctx),\n                 }};\n    });\n  }\n\n private:\n  kj::Own<ReadableStreamSource> inner;\n  IoContext& ioctx;\n};\n\n}  // namespace\n\nvoid ReadableStream::serialize(jsg::Lock& js, jsg::Serializer& serializer) {\n  // Serialize by effectively creating a `JsRpcStub` around this object and serializing that.\n  // Except we don't actually want to do _exactly_ that, because we do not want to actually create\n  // a `JsRpcStub` locally. So do the important parts of `JsRpcStub::constructor()` followed by\n  // `JsRpcStub::serialize()`.\n\n  auto& handler = JSG_REQUIRE_NONNULL(serializer.getExternalHandler(), DOMDataCloneError,\n      \"ReadableStream can only be serialized for RPC.\");\n  auto externalHandler = dynamic_cast<RpcSerializerExternalHandler*>(&handler);\n  JSG_REQUIRE(externalHandler != nullptr, DOMDataCloneError,\n      \"ReadableStream can only be serialized for RPC.\");\n\n  // NOTE: We're counting on `pumpTo()`, below, to check that the stream is not locked or disturbed\n  //   and other common checks. It's important that we don't modify the stream in any way before\n  //   that call.\n\n  IoContext& ioctx = IoContext::current();\n\n  auto& controller = getController();\n  StreamEncoding encoding = controller.getPreferredEncoding();\n  auto expectedLength = controller.tryGetLength(encoding);\n\n  capnp::ByteStream::Client streamCap = [&]() {\n    KJ_IF_SOME(pusher, externalHandler->getExternalPusher()) {\n      auto req = pusher.pushByteStreamRequest(capnp::MessageSize{2, 0});\n      KJ_IF_SOME(el, expectedLength) {\n        req.setLengthPlusOne(el + 1);\n      }\n      auto pipeline = req.sendForPipeline();\n\n      externalHandler->write([encoding, expectedLength, source = pipeline.getSource()](\n                                 rpc::JsValue::External::Builder builder) mutable {\n        auto rs = builder.initReadableStream();\n        rs.setStream(kj::mv(source));\n        rs.setEncoding(encoding);\n      });\n\n      return pipeline.getSink();\n    } else {\n      return externalHandler\n          ->writeStream(\n              [encoding, expectedLength](rpc::JsValue::External::Builder builder) mutable {\n        auto rs = builder.initReadableStream();\n        rs.setEncoding(encoding);\n        KJ_IF_SOME(l, expectedLength) {\n          rs.getExpectedLength().setKnown(l);\n        }\n      }).castAs<capnp::ByteStream>();\n    }\n  }();\n\n  kj::Own<capnp::ExplicitEndOutputStream> kjStream =\n      ioctx.getByteStreamFactory().capnpToKjExplicitEnd(kj::mv(streamCap));\n\n  auto sink = newSystemStream(kj::mv(kjStream), encoding, ioctx);\n\n  ioctx.addTask(\n      ioctx.waitForDeferredProxy(pumpTo(js, kj::mv(sink), true)).catch_([](kj::Exception&& e) {\n    // Errors in pumpTo() are automatically propagated to the source and destination. We don't\n    // want to throw them from here since it'll cause an uncaught exception to be reported, even\n    // if the application actually does handle it!\n  }));\n}\n\njsg::Ref<ReadableStream> ReadableStream::deserialize(\n    jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer) {\n  auto& handler = KJ_REQUIRE_NONNULL(\n      deserializer.getExternalHandler(), \"got ReadableStream on non-RPC serialized object?\");\n  auto externalHandler = dynamic_cast<RpcDeserializerExternalHandler*>(&handler);\n  KJ_REQUIRE(externalHandler != nullptr, \"got ReadableStream on non-RPC serialized object?\");\n\n  auto reader = externalHandler->read();\n  KJ_REQUIRE(reader.isReadableStream(), \"external table slot type doesn't match serialization tag\");\n\n  auto rs = reader.getReadableStream();\n  auto encoding = rs.getEncoding();\n\n  KJ_REQUIRE(\n      static_cast<uint>(encoding) < capnp::Schema::from<StreamEncoding>().getEnumerants().size(),\n      \"unknown StreamEncoding received from peer\");\n\n  auto& ioctx = IoContext::current();\n\n  kj::Own<kj::AsyncInputStream> in;\n  if (rs.hasStream()) {\n    in = ioctx.getExternalPusher()->unwrapStream(rs.getStream());\n  } else {\n    kj::Maybe<uint64_t> expectedLength;\n    auto el = rs.getExpectedLength();\n    if (el.isKnown()) {\n      expectedLength = el.getKnown();\n    }\n\n    auto pipe = kj::newOneWayPipe(expectedLength);\n\n    auto endedFlag = kj::refcounted<kj::RefcountedWrapper<bool>>(false);\n\n    auto out = kj::heap<ExplicitEndOutputPipeAdapter>(kj::mv(pipe.out), kj::addRef(*endedFlag));\n    in = kj::heap<ExplicitEndInputPipeAdapter>(kj::mv(pipe.in), kj::mv(endedFlag), expectedLength);\n\n    externalHandler->setLastStream(ioctx.getByteStreamFactory().kjToCapnp(kj::mv(out)));\n  }\n\n  return js.alloc<ReadableStream>(ioctx,\n      kj::heap<NoDeferredProxyReadableStream>(newSystemStream(kj::mv(in), encoding, ioctx), ioctx));\n}\n\nkj::StringPtr ReaderImpl::jsgGetMemoryName() const {\n  return \"ReaderImpl\"_kjc;\n}\n\nsize_t ReaderImpl::jsgGetMemorySelfSize() const {\n  return sizeof(ReaderImpl);\n}\n\nvoid ReaderImpl::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n  KJ_IF_SOME(attached, state.tryGetActiveUnsafe()) {\n    tracker.trackField(\"stream\", attached.stream);\n  }\n  tracker.trackField(\"closedPromise\", closedPromise);\n}\n\nvoid ReadableStream::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"controller\", controller);\n  tracker.trackField(\"eofResolverPair\", eofResolverPair);\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/readable.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"common.h\"\n\n#include <kj/function.h>\n#include <workerd/util/state-machine.h>\n\nnamespace workerd::api {\n\nclass ReadableStreamDefaultReader;\nclass ReadableStreamBYOBReader;\n\nclass ReaderImpl final {\npublic:\n  ReaderImpl(ReadableStreamController::Reader& reader);\n\n  ~ReaderImpl() noexcept(false);\n\n  void attach(ReadableStreamController& controller, jsg::Promise<void> closedPromise);\n\n  jsg::Promise<void> cancel(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason);\n\n  void detach();\n\n  jsg::MemoizedIdentity<jsg::Promise<void>>& getClosed();\n\n  void lockToStream(jsg::Lock& js, ReadableStream& stream);\n\n  jsg::Promise<ReadResult> read(jsg::Lock& js,\n                                 kj::Maybe<ReadableStreamController::ByobOptions> byobOptions);\n\n  void releaseLock(jsg::Lock& js);\n\n  void visitForGc(jsg::GcVisitor& visitor);\n\n  kj::StringPtr jsgGetMemoryName() const;\n  size_t jsgGetMemorySelfSize() const;\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const;\n\nprivate:\n  struct Initial {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"initial\"_kj;\n  };\n  // While a Reader is attached to a ReadableStream, it holds a strong reference to the\n  // ReadableStream to prevent it from being GC'ed so long as the Reader is available.\n  // Once the reader is closed, released, or GC'ed the reference to the ReadableStream\n  // is cleared and the ReadableStream can be GC'ed if there are no other references to\n  // it being held anywhere. If the reader is still attached to the ReadableStream when\n  // it is destroyed, the ReadableStream's reference to the reader is cleared but the\n  // ReadableStream remains in the \"reader locked\" state, per the spec.\n  struct Attached {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"attached\"_kj;\n    jsg::Ref<ReadableStream> stream;\n  };\n  // Released: The user explicitly called releaseLock() to detach the reader from the stream.\n  // The stream remains usable and can be locked by a new reader.\n  struct Released {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"released\"_kj;\n  };\n  // Closed: The underlying stream ended (closed or errored) while the reader was attached.\n  // The stream is no longer usable.\n  struct Closed {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"closed\"_kj;\n  };\n\n  // State machine for ReaderImpl:\n  //   Initial -> Attached (attach() called)\n  //   Attached -> Closed (detach() called when stream closes)\n  //   Attached -> Released (releaseLock() called)\n  // Closed and Released are terminal states.\n  // Initial is not terminal but most methods assert if called in this state.\n  using ReaderState = StateMachine<TerminalStates<Closed, Released>,\n      ActiveState<Attached>,\n      Initial,\n      Attached,\n      Closed,\n      Released>;\n\n  kj::Maybe<IoContext&> ioContext;\n  ReadableStreamController::Reader& reader;\n\n  ReaderState state;\n\n  inline void assertAttachedOrTerminal() const {\n    KJ_ASSERT(!state.is<Initial>(), \"this reader was never attached\");\n  }\n  kj::Maybe<jsg::MemoizedIdentity<jsg::Promise<void>>> closedPromise;\n\n  friend class ReadableStreamDefaultReader;\n  friend class ReadableStreamBYOBReader;\n};\n\nclass ReadableStreamDefaultReader : public jsg::Object,\n                                    public ReadableStreamController::Reader {\npublic:\n  explicit ReadableStreamDefaultReader();\n\n  // JavaScript API\n\n  static jsg::Ref<ReadableStreamDefaultReader> constructor(\n      jsg::Lock& js, jsg::Ref<ReadableStream> stream);\n\n  jsg::MemoizedIdentity<jsg::Promise<void>>& getClosed();\n  jsg::Promise<void> cancel(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> reason);\n  jsg::Promise<ReadResult> read(jsg::Lock& js);\n  void releaseLock(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(ReadableStreamDefaultReader, CompatibilityFlags::Reader flags) {\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(closed, getClosed);\n    } else {\n      JSG_READONLY_INSTANCE_PROPERTY(closed, getClosed);\n    }\n    JSG_METHOD(cancel);\n    JSG_METHOD(read);\n    JSG_METHOD(releaseLock);\n\n    JSG_TS_OVERRIDE(<R = any> {\n      read(): Promise<ReadableStreamReadResult<R>>;\n    });\n  }\n\n  // Internal API\n\n  void attach(ReadableStreamController& controller, jsg::Promise<void> closedPromise) override;\n\n  void detach() override;\n\n  void lockToStream(jsg::Lock& js, ReadableStream& stream);\n\n  inline bool isByteOriented() const override { return false; }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"impl\", impl);\n  }\n\nprivate:\n  ReaderImpl impl;\n\n  void visitForGc(jsg::GcVisitor& visitor);\n};\n\nclass ReadableStreamBYOBReader: public jsg::Object,\n                                public ReadableStreamController::Reader {\npublic:\n  explicit ReadableStreamBYOBReader();\n\n  // JavaScript API\n\n  static jsg::Ref<ReadableStreamBYOBReader> constructor(\n      jsg::Lock& js,\n      jsg::Ref<ReadableStream> stream);\n\n  jsg::MemoizedIdentity<jsg::Promise<void>>& getClosed();\n  jsg::Promise<void> cancel(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> reason);\n\n  struct ReadableStreamBYOBReaderReadOptions {\n    jsg::Optional<int> min;\n    JSG_STRUCT(min);\n  };\n\n  jsg::Promise<ReadResult> read(jsg::Lock& js, v8::Local<v8::ArrayBufferView> byobBuffer,\n      jsg::Optional<ReadableStreamBYOBReaderReadOptions> options = kj::none);\n\n  // Non-standard extension so that reads can specify a minimum number of bytes to read. It's a\n  // struct so that we could eventually add things like timeouts if we need to. Since there's no\n  // existing spec that's a leading contender, this is behind a different method name to avoid\n  // conflicts with any changes to `read`. Fewer than `minBytes` may be returned if EOF is hit or\n  // the underlying stream is closed/errors out. In all cases the read result is either\n  // {value: theChunk, done: false} or {value: undefined, done: true} as with read.\n  // TODO(soon): Like fetch() and Cache.match(), readAtLeast() returns a promise for a V8 object.\n  jsg::Promise<ReadResult> readAtLeast(jsg::Lock& js,\n                                        int minBytes,\n                                        v8::Local<v8::ArrayBufferView> byobBuffer);\n\n  void releaseLock(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(ReadableStreamBYOBReader, CompatibilityFlags::Reader flags) {\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(closed, getClosed);\n    } else {\n      JSG_READONLY_INSTANCE_PROPERTY(closed, getClosed);\n    }\n    JSG_METHOD(cancel);\n    JSG_METHOD(read);\n    JSG_METHOD(releaseLock);\n\n    // Non-standard extension that should only apply to BYOB byte streams.\n    JSG_METHOD(readAtLeast);\n\n    JSG_TS_OVERRIDE(ReadableStreamBYOBReader {\n      read<T extends ArrayBufferView>(view: T): Promise<ReadableStreamReadResult<T>>;\n      readAtLeast<T extends ArrayBufferView>(minElements: number, view: T): Promise<ReadableStreamReadResult<T>>;\n    });\n  }\n\n  // Internal API\n\n  void attach(\n      ReadableStreamController& controller,\n      jsg::Promise<void> closedPromise) override;\n\n  void detach() override;\n\n  void lockToStream(jsg::Lock& js, ReadableStream& stream);\n\n  inline bool isByteOriented() const override { return true; }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"impl\", impl);\n  }\n\nprivate:\n  ReaderImpl impl;\n\n  void visitForGc(jsg::GcVisitor& visitor);\n};\n\n// DrainingReader is a C++ only reader (not exposed to JavaScript) that performs\n// draining reads. It locks the stream like standard readers but uses drainingRead()\n// instead of regular read() to drain all synchronously available data at once.\n// This is intended for optimized pipe operations.\nclass DrainingReader: public ReadableStreamController::Reader {\n public:\n  explicit DrainingReader();\n\n  // Factory method to create and lock to a stream. Returns nullptr if stream is locked.\n  static kj::Maybe<kj::Own<DrainingReader>> create(jsg::Lock& js, ReadableStream& stream);\n\n  virtual ~DrainingReader() noexcept(false);\n\n  // Performs a draining read, returning all synchronously available data as bytes.\n  // The maxRead parameter is a soft limit - see ReadableStreamController::drainingRead.\n  jsg::Promise<DrainingReadResult> read(jsg::Lock& js, size_t maxRead = kj::maxValue);\n\n  // Cancels the stream.\n  jsg::Promise<void> cancel(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason);\n\n  // Releases the lock on the stream.\n  void releaseLock(jsg::Lock& js);\n\n  // Returns whether this reader is still attached to a stream.\n  bool isAttached() const;\n\n  // ReadableStreamController::Reader interface\n  void attach(ReadableStreamController& controller, jsg::Promise<void> closedPromise) override;\n  void detach() override;\n  bool isByteOriented() const override { return false; }\n\n  void visitForGc(jsg::GcVisitor& visitor);\n\n private:\n  struct Initial {};\n  using Attached = jsg::Ref<ReadableStream>;\n  struct Released {};\n\n  kj::Maybe<IoContext&> ioContext;\n  kj::OneOf<Initial, Attached, StreamStates::Closed, Released> state = Initial();\n  kj::Maybe<jsg::MemoizedIdentity<jsg::Promise<void>>> closedPromise;\n};\n\nclass ReadableStream: public jsg::Object {\nprivate:\n\n  struct AsyncIteratorState {\n    kj::Maybe<IoContext&> ioContext;\n    jsg::Ref<ReadableStreamDefaultReader> reader;\n    bool preventCancel;\n  };\n\n  static jsg::Promise<kj::Maybe<jsg::Value>> nextFunction(\n      jsg::Lock& js,\n      AsyncIteratorState& state);\n\n  static jsg::Promise<void> returnFunction(\n      jsg::Lock& js,\n      AsyncIteratorState& state,\n      jsg::Optional<jsg::Value>& value);\n\npublic:\n  explicit ReadableStream(IoContext& ioContext,\n                          kj::Own<ReadableStreamSource> source);\n\n  explicit ReadableStream(kj::Own<ReadableStreamController> controller);\n\n  ReadableStreamController& getController();\n\n  jsg::Ref<ReadableStream> addRef();\n\n  bool isDisturbed();\n\n  // ---------------------------------------------------------------------------\n  // JS interface\n\n  // Creates a new JS-backed ReadableStream using the provided source and strategy.\n  // We use v8::Local<v8::Object>'s here instead of jsg structs because we need\n  // to preserve the object references within the implementation.\n  static jsg::Ref<ReadableStream> constructor(\n      jsg::Lock& js,\n      jsg::Optional<UnderlyingSource> underlyingSource,\n      jsg::Optional<StreamQueuingStrategy> queuingStrategy);\n\n  static jsg::Ref<ReadableStream> from(jsg::Lock& js, jsg::AsyncGenerator<jsg::Value> generator);\n\n  bool isLocked();\n\n  // Closes the stream. All present and future read requests are fulfilled with successful empty\n  // results. `reason` will be passed to the underlying source's cancel algorithm -- if this\n  // readable stream is one side of a transform stream, then its cancel algorithm causes the\n  // transform's writable side to become errored with `reason`.\n  jsg::Promise<void> cancel(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> reason);\n\n  using Reader = kj::OneOf<jsg::Ref<ReadableStreamDefaultReader>,\n                           jsg::Ref<ReadableStreamBYOBReader>>;\n\n  struct GetReaderOptions {\n    jsg::Optional<kj::String> mode;  // can be \"byob\" or undefined\n\n    JSG_STRUCT(mode);\n\n    JSG_STRUCT_TS_OVERRIDE({ mode: \"byob\" });\n    // Intentionally required, so we can use `GetReaderOptions` directly in the\n    // `ReadableStream#getReader()` overload.\n  };\n\n  Reader getReader(jsg::Lock& js, jsg::Optional<GetReaderOptions> options);\n\n  // Options specifically for the values() function.\n  struct ValuesOptions {\n    jsg::Optional<bool> preventCancel = false;\n    JSG_STRUCT(preventCancel);\n  };\n\n  JSG_ASYNC_ITERATOR_WITH_OPTIONS(ReadableStreamAsyncIterator,\n                                   values,\n                                   jsg::Value,\n                                   AsyncIteratorState,\n                                   nextFunction,\n                                   returnFunction,\n                                   ValuesOptions);\n  struct Transform {\n    jsg::Ref<ReadableStream> readable;\n    jsg::Ref<WritableStream> writable;\n\n    JSG_STRUCT(readable, writable);\n    JSG_STRUCT_TS_OVERRIDE(ReadableWritablePair<R = any, W = any> {\n      readable: ReadableStream<R>;\n      writable: WritableStream<W>;\n    });\n  };\n\n  jsg::Ref<ReadableStream> pipeThrough(\n      jsg::Lock& js,\n      Transform transform,\n      jsg::Optional<PipeToOptions> options);\n\n  jsg::Promise<void> pipeTo(\n      jsg::Lock& js,\n      jsg::Ref<WritableStream> destination,\n      jsg::Optional<PipeToOptions> options);\n\n  // Locks the stream and returns a pair of two new ReadableStreams, each of which read the same\n  // data as this ReadableStream would.\n  kj::Array<jsg::Ref<ReadableStream>> tee(jsg::Lock& js);\n\n  jsg::JsString inspectState(jsg::Lock& js);\n  bool inspectSupportsBYOB();\n  jsg::Optional<uint64_t> inspectLength();\n\n  JSG_RESOURCE_TYPE(ReadableStream, CompatibilityFlags::Reader flags) {\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(locked, isLocked);\n    } else {\n      JSG_READONLY_INSTANCE_PROPERTY(locked, isLocked);\n    }\n    JSG_METHOD(cancel);\n    JSG_METHOD(getReader);\n    JSG_METHOD(pipeThrough);\n    JSG_METHOD(pipeTo);\n    JSG_METHOD(tee);\n    JSG_METHOD(values);\n    JSG_STATIC_METHOD(from);\n\n    JSG_INSPECT_PROPERTY(state, inspectState);\n    JSG_INSPECT_PROPERTY(supportsBYOB, inspectSupportsBYOB);\n    JSG_INSPECT_PROPERTY(length, inspectLength);\n\n    JSG_ASYNC_ITERABLE(values);\n\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_TS_DEFINE(interface ReadableStream<R = any> {\n        get locked(): boolean;\n\n        cancel(reason?: any): Promise<void>;\n\n        getReader(): ReadableStreamDefaultReader<R>;\n        getReader(options: ReadableStreamGetReaderOptions): ReadableStreamBYOBReader;\n\n        pipeThrough<T>(transform: ReadableWritablePair<T, R>, options?: StreamPipeOptions): ReadableStream<T>;\n        pipeTo(destination: WritableStream<R>, options?: StreamPipeOptions): Promise<void>;\n\n        tee(): [ReadableStream<R>, ReadableStream<R>];\n\n        values(options?: ReadableStreamValuesOptions): AsyncIterableIterator<R>;\n        [Symbol.asyncIterator](options?: ReadableStreamValuesOptions): AsyncIterableIterator<R>;\n      });\n    } else {\n      JSG_TS_DEFINE(interface ReadableStream<R = any> {\n        readonly locked: boolean;\n\n        cancel(reason?: any): Promise<void>;\n\n        getReader(): ReadableStreamDefaultReader<R>;\n        getReader(options: ReadableStreamGetReaderOptions): ReadableStreamBYOBReader;\n\n        pipeThrough<T>(transform: ReadableWritablePair<T, R>, options?: StreamPipeOptions): ReadableStream<T>;\n        pipeTo(destination: WritableStream<R>, options?: StreamPipeOptions): Promise<void>;\n\n        tee(): [ReadableStream<R>, ReadableStream<R>];\n\n        values(options?: ReadableStreamValuesOptions): AsyncIterableIterator<R>;\n        [Symbol.asyncIterator](options?: ReadableStreamValuesOptions): AsyncIterableIterator<R>;\n      });\n    }\n    // Replace ReadableStream class with an interface and const, so we can have\n    // two constructors with differing type parameters for byte-oriented and\n    // value-oriented streams.\n    JSG_TS_OVERRIDE(const ReadableStream: {\n      prototype: ReadableStream;\n      new (underlyingSource: UnderlyingByteSource, strategy?: QueuingStrategy<Uint8Array>): ReadableStream<Uint8Array>;\n      new <R = any>(underlyingSource?: UnderlyingSource<R>, strategy?: QueuingStrategy<R>): ReadableStream<R>;\n    });\n  }\n\n  // Detaches this ReadableStream from its underlying controller state, returning a\n  // new ReadableStream instance that takes over the underlying state. This is used to\n  // support the \"create a proxy\" of a ReadableStream algorithm in the streams spec\n  // (see https://streams.spec.whatwg.org/#readablestream-create-a-proxy). In that\n  // algorithm, it says to create a proxy of a stream by creating a new TransformStream\n  // and piping the original through it. The readable side of the created transform\n  // becomes the proxy. That is quite inefficient so instead, we create a new\n  // ReadableStream that will take over ownership of the internal state of this one,\n  // leaving this ReadableStream locked and disturbed so that it is no longer usable.\n  // The name \"detach\" here is used in the sense of \"detaching the internal state\".\n  jsg::Ref<ReadableStream> detach(jsg::Lock& js, bool ignoreDisturbed=false);\n\n  kj::Maybe<uint64_t> tryGetLength(StreamEncoding encoding);\n\n  // A potentially optimized version of pipe that sends this stream's data to the given\n  // sink. The entire stream is consumed. The ReadableStream will be left locked and\n  // disturbed and the DeferredProxy returned will take over ownership of the internal\n  // state of the readable.\n  kj::Promise<DeferredProxy<void>> pumpTo(jsg::Lock& js,\n                                          kj::Own<WritableStreamSink> sink,\n                                          bool end);\n\n  // Initializes signalling mechanism for EOF detection. Returns a promise that will resolve when\n  // EOF is reached.\n  //\n  // This method should only be called once.\n  jsg::Promise<void> onEof(jsg::Lock& js);\n\n  // Used by ReadableStreamInternalController to signal EOF being reached. Can be called even if\n  // `onEof` wasn't called.\n  void signalEof(jsg::Lock& js);\n\n  void serialize(jsg::Lock& js, jsg::Serializer& serializer);\n  static jsg::Ref<ReadableStream> deserialize(\n      jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer);\n\n  JSG_SERIALIZABLE(rpc::SerializationTag::READABLE_STREAM);\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\nprivate:\n  kj::Maybe<IoContext&> ioContext;\n  kj::Own<ReadableStreamController> controller;\n\n  // Used to signal when this ReadableStream reads EOF. This signal is required for TCP sockets.\n  kj::Maybe<jsg::PromiseResolverPair<void>> eofResolverPair;\n\n  void visitForGc(jsg::GcVisitor& visitor);\n};\n\nstruct QueuingStrategyInit {\n  double highWaterMark;\n  JSG_STRUCT(highWaterMark);\n};\n\nusing QueuingStrategySizeFunction =\n    jsg::Optional<uint32_t>(jsg::Optional<v8::Local<v8::Value>>);\n\n// Utility class defined by the streams spec that uses byteLength to calculate\n// backpressure changes.\nclass ByteLengthQueuingStrategy: public jsg::Object {\npublic:\n  ByteLengthQueuingStrategy(QueuingStrategyInit init) : init(init) {}\n\n  static jsg::Ref<ByteLengthQueuingStrategy> constructor(jsg::Lock& js, QueuingStrategyInit init) {\n    return js.alloc<ByteLengthQueuingStrategy>(init);\n  }\n\n  double getHighWaterMark() const { return init.highWaterMark; }\n\n  jsg::Function<QueuingStrategySizeFunction> getSize() const { return &size; }\n\n  JSG_RESOURCE_TYPE(ByteLengthQueuingStrategy) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(highWaterMark, getHighWaterMark);\n    JSG_READONLY_PROTOTYPE_PROPERTY(size, getSize);\n\n    // QueuingStrategy requires the result of the size function to be defined\n    JSG_TS_OVERRIDE(implements QueuingStrategy<ArrayBufferView> {\n      get size(): (chunk?: any) => number;\n    });\n  }\n\nprivate:\n  static jsg::Optional<uint32_t> size(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>>);\n\n  QueuingStrategyInit init;\n};\n\n// Utility class defined by the streams spec that uses a fixed value of 1 to calculate\n// backpressure change\nclass CountQueuingStrategy: public jsg::Object {\npublic:\n  CountQueuingStrategy(QueuingStrategyInit init) : init(init) {}\n\n  static jsg::Ref<CountQueuingStrategy> constructor(jsg::Lock& js, QueuingStrategyInit init) {\n    return js.alloc<CountQueuingStrategy>(init);\n  }\n\n  double getHighWaterMark() const { return init.highWaterMark; }\n\n  jsg::Function<QueuingStrategySizeFunction> getSize() const { return &size; }\n\n  JSG_RESOURCE_TYPE(CountQueuingStrategy) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(highWaterMark, getHighWaterMark);\n    JSG_READONLY_PROTOTYPE_PROPERTY(size, getSize);\n\n    // QueuingStrategy requires the result of the size function to be defined\n    JSG_TS_OVERRIDE(implements QueuingStrategy {\n      get size(): (chunk?: any) => number;\n    });\n  }\n\nprivate:\n  static jsg::Optional<uint32_t> size(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>>) {\n    return 1;\n  }\n\n  QueuingStrategyInit init;\n};\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/standard-test.c++",
    "content": "#include \"readable.h\"\n#include \"standard.h\"\n#include \"writable.h\"\n\n#include <workerd/jsg/jsg-test.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/observer.h>\n#include <workerd/tests/test-fixture.h>\n\nnamespace workerd::api {\nnamespace {\n\nvoid preamble(auto callback) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) { callback(env.js); });\n}\n\nv8::Local<v8::Value> toBytes(jsg::Lock& js, kj::String str) {\n  return jsg::BackingStore::from(js, str.asBytes().attach(kj::mv(str))).createHandle(js);\n}\n\njsg::BufferSource toBufferSource(jsg::Lock& js, kj::String str) {\n  auto backing = jsg::BackingStore::from(js, str.asBytes().attach(kj::mv(str))).createHandle(js);\n  return jsg::BufferSource(js, kj::mv(backing));\n}\n\njsg::BufferSource toBufferSource(jsg::Lock& js, kj::Array<kj::byte> bytes) {\n  auto backing = jsg::BackingStore::from(js, kj::mv(bytes)).createHandle(js);\n  return jsg::BufferSource(js, kj::mv(backing));\n}\n\n// ======================================================================================\n// Happy Cases\n\nKJ_TEST(\"ReadableStream read all text (value readable)\") {\n  preamble([](jsg::Lock& js) {\n    uint checked = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        // Because we're using a value-based stream, two enqueue operations will\n        // require at least three reads to complete: one for the first chunk, 'hello, ',\n        // one for the second chunk, 'world!', and one to signal close.\n        KJ_SWITCH_ONEOF(controller) {\n          // Because we're using a value-based stream, two enqueue operations will\n          // require at least three reads to complete: one for the first chunk, 'hello, ',\n          // one for the second chunk, 'world!', and one to signal close.\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            checked++;\n            c->enqueue(js, toBytes(js, kj::str(\"Hello, \")));\n            c->enqueue(js, toBytes(js, kj::str(\"world!\")));\n            c->close(js);\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n      // Setting a highWaterMark of 0 means the pull function above will not be called\n      // immediately on creation of the stream, but only when the first read in the\n      // readall call below happens.\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    // Starts a read loop of javascript promises.\n    auto promise =\n        rs->getController().readAllText(js, 20).then(js, [&](jsg::Lock& js, kj::String&& text) {\n      KJ_ASSERT(text == \"Hello, world!\"_kjc);\n      checked++;\n    });\n\n    // Reading left the stream locked and disturbed\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n\n    // Run the microtasks to completion. This should resolve the promise and\n    // run it to completion. The test is buggy if it fails to do so.\n    js.runMicrotasks();\n    KJ_ASSERT(checked == 2);\n\n    // Reading everything successfully should cause the stream to close.\n    KJ_ASSERT(rs->getController().isClosed());\n\n    // Add we should still be locked and disturbed.\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n  });\n}\n\nKJ_TEST(\"ReadableStream read all text, rs ref held (value readable)\") {\n  preamble([](jsg::Lock& js) {\n    uint checked = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        // Because we're using a value-based stream, two enqueue operations will\n        // require at least three reads to complete: one for the first chunk, 'hello, ',\n        // one for the second chunk, 'world!', and one to signal close.\n        KJ_SWITCH_ONEOF(controller) {\n          // Because we're using a value-based stream, two enqueue operations will\n          // require at least three reads to complete: one for the first chunk, 'hello, ',\n          // one for the second chunk, 'world!', and one to signal close.\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            checked++;\n            c->enqueue(js, toBytes(js, kj::str(\"Hello, \")));\n            c->enqueue(js, toBytes(js, kj::str(\"world!\")));\n            c->close(js);\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n      // Setting a highWaterMark of 0 means the pull function above will not be called\n      // immediately on creation of the stream, but only when the first read in the\n      // readall call below happens.\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    // Starts a read loop of javascript promises.\n    auto promise =\n        rs->getController().readAllText(js, 20).then(js, [&](jsg::Lock& js, kj::String&& text) {\n      KJ_ASSERT(text == \"Hello, world!\"_kjc);\n      checked++;\n    });\n\n    // Reading left the stream locked and disturbed\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n\n    // Let's drop our ref to rs, things should still work as expected.\n    { auto drop = kj::mv(rs); }\n\n    // Run the microtasks to completion. This should resolve the promise and\n    // run it to completion. The test is buggy if it fails to do so.\n    js.runMicrotasks();\n    KJ_ASSERT(checked == 2);\n  });\n}\n\nKJ_TEST(\"ReadableStream read all text (byte readable)\") {\n  preamble([](jsg::Lock& js) {\n    uint checked = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .type = kj::str(\"bytes\"),\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        // Because we're using a value-based stream, two enqueue operations will\n        // require at least three reads to complete: one for the first chunk, 'hello, ',\n        // one for the second chunk, 'world!', and one to signal close.\n        KJ_SWITCH_ONEOF(controller) {\n          // Because we're using a value-based stream, two enqueue operations will\n          // require at least three reads to complete: one for the first chunk, 'hello, ',\n          // one for the second chunk, 'world!', and one to signal close.\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {\n            checked++;\n            c->enqueue(js, toBufferSource(js, kj::str(\"Hello, \")));\n            c->enqueue(js, toBufferSource(js, kj::str(\"world!\")));\n            c->close(js);\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n      // Setting a highWaterMark of 0 means the pull function above will not be called\n      // immediately on creation of the stream, but only when the first read in the\n      // readall call below happens.\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    // Starts a read loop of javascript promises.\n    auto promise =\n        rs->getController().readAllText(js, 20).then(js, [&](jsg::Lock& js, kj::String&& text) {\n      KJ_ASSERT(text == \"Hello, world!\"_kjc);\n      checked++;\n    });\n\n    // Reading left the stream locked and disturbed\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n\n    // Run the microtasks to completion. This should resolve the promise and\n    // run it to completion. The test is buggy if it fails to do so.\n    js.runMicrotasks();\n    KJ_ASSERT(checked == 2);\n\n    // Reading everything successfully should cause the stream to close.\n    KJ_ASSERT(rs->getController().isClosed());\n\n    // Add we should still be locked and disturbed.\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n  });\n}\n\nKJ_TEST(\"ReadableStream read all bytes (value readable)\") {\n  preamble([](jsg::Lock& js) {\n    uint checked = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        // Because we're using a value-based stream, two enqueue operations will\n        // require at least three reads to complete: one for the first chunk, 'hello, ',\n        // one for the second chunk, 'world!', and one to signal close.\n        KJ_SWITCH_ONEOF(controller) {\n          // Because we're using a value-based stream, two enqueue operations will\n          // require at least three reads to complete: one for the first chunk, 'hello, ',\n          // one for the second chunk, 'world!', and one to signal close.\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            checked++;\n            c->enqueue(js, toBytes(js, kj::str(\"Hello, \")));\n            c->enqueue(js, toBytes(js, kj::str(\"world!\")));\n            c->close(js);\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n      // Setting a highWaterMark of 0 means the pull function above will not be called\n      // immediately on creation of the stream, but only when the first read in the\n      // readall call below happens.\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    // Starts a read loop of javascript promises.\n    auto promise = rs->getController().readAllBytes(js, 20).then(\n        js, [&](jsg::Lock& js, jsg::BufferSource&& text) {\n      KJ_ASSERT(text.asArrayPtr() == \"Hello, world!\"_kjb);\n      checked++;\n    });\n\n    // Reading left the stream locked and disturbed\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n\n    // Run the microtasks to completion. This should resolve the promise and\n    // run it to completion. The test is buggy if it fails to do so.\n    js.runMicrotasks();\n    KJ_ASSERT(checked == 2);\n\n    // Reading everything successfully should cause the stream to close.\n    KJ_ASSERT(rs->getController().isClosed());\n\n    // Add we should still be locked and disturbed.\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n  });\n}\n\nKJ_TEST(\"ReadableStream read all bytes (byte readable)\") {\n  preamble([](jsg::Lock& js) {\n    uint checked = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .type = kj::str(\"bytes\"),\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        // Because we're using a value-based stream, two enqueue operations will\n        // require at least three reads to complete: one for the first chunk, 'hello, ',\n        // one for the second chunk, 'world!', and one to signal close.\n        KJ_SWITCH_ONEOF(controller) {\n          // Because we're using a value-based stream, two enqueue operations will\n          // require at least three reads to complete: one for the first chunk, 'hello, ',\n          // one for the second chunk, 'world!', and one to signal close.\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {\n            checked++;\n            c->enqueue(js, toBufferSource(js, kj::str(\"Hello, \")));\n            c->enqueue(js, toBufferSource(js, kj::str(\"world!\")));\n            c->close(js);\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n      // Setting a highWaterMark of 0 means the pull function above will not be called\n      // immediately on creation of the stream, but only when the first read in the\n      // readall call below happens.\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    // Starts a read loop of javascript promises.\n    auto promise = rs->getController().readAllBytes(js, 20).then(\n        js, [&](jsg::Lock& js, jsg::BufferSource&& text) {\n      KJ_ASSERT(text.asArrayPtr() == \"Hello, world!\"_kjb);\n      checked++;\n    });\n\n    // Reading left the stream locked and disturbed\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n\n    // Run the microtasks to completion. This should resolve the promise and\n    // run it to completion. The test is buggy if it fails to do so.\n    js.runMicrotasks();\n    KJ_ASSERT(checked == 2);\n\n    // Reading everything successfully should cause the stream to close.\n    KJ_ASSERT(rs->getController().isClosed());\n\n    // Add we should still be locked and disturbed.\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n  });\n}\n\nKJ_TEST(\"ReadableStream read all bytes (value readable, more reads)\") {\n  preamble([](jsg::Lock& js) {\n    uint checked = 0;\n    uint counter = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    auto chunks = kj::arr<kj::String>(kj::str(\"H\"), kj::str(\"e\"), kj::str(\"l\"), kj::str(\"l\"),\n        kj::str(\"o\"), kj::str(\",\"), kj::str(\" \"), kj::str(\"w\"), kj::str(\"o\"), kj::str(\"r\"),\n        kj::str(\"l\"), kj::str(\"d\"), kj::str(\"!\"));\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        // Because we're using a value-based stream, two enqueue operations will\n        // require at least three reads to complete: one for the first chunk, 'hello, ',\n        // one for the second chunk, 'world!', and one to signal close.\n        KJ_SWITCH_ONEOF(controller) {\n          // Because we're using a value-based stream, two enqueue operations will\n          // require at least three reads to complete: one for the first chunk, 'hello, ',\n          // one for the second chunk, 'world!', and one to signal close.\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            checked++;\n            c->enqueue(js, toBytes(js, kj::mv(chunks[counter++])));\n            if (counter == chunks.size()) {\n              c->close(js);\n            }\n\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n      // Setting a highWaterMark of 0 means the pull function above will not be called\n      // immediately on creation of the stream, but only when the first read in the\n      // readall call below happens.\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    // Starts a read loop of javascript promises.\n    auto promise = rs->getController().readAllBytes(js, 20).then(\n        js, [&](jsg::Lock& js, jsg::BufferSource&& text) {\n      KJ_ASSERT(text.asArrayPtr() == \"Hello, world!\"_kjb);\n      checked++;\n    });\n\n    // Reading left the stream locked and disturbed\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n\n    // Run the microtasks to completion. This should resolve the promise and\n    // run it to completion. The test is buggy if it fails to do so.\n    js.runMicrotasks();\n    KJ_ASSERT(checked == 14);\n\n    // Reading everything successfully should cause the stream to close.\n    KJ_ASSERT(rs->getController().isClosed());\n\n    // Add we should still be locked and disturbed.\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n  });\n}\n\nKJ_TEST(\"ReadableStream read all bytes (byte readable, more reads)\") {\n  preamble([](jsg::Lock& js) {\n    uint checked = 0;\n    uint counter = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    auto chunks = kj::arr<kj::String>(kj::str(\"H\"), kj::str(\"e\"), kj::str(\"l\"), kj::str(\"l\"),\n        kj::str(\"o\"), kj::str(\",\"), kj::str(\" \"), kj::str(\"w\"), kj::str(\"o\"), kj::str(\"r\"),\n        kj::str(\"l\"), kj::str(\"d\"), kj::str(\"!\"));\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .type = kj::str(\"bytes\"),\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        // Because we're using a value-based stream, two enqueue operations will\n        // require at least three reads to complete: one for the first chunk, 'hello, ',\n        // one for the second chunk, 'world!', and one to signal close.\n        KJ_SWITCH_ONEOF(controller) {\n          // Because we're using a value-based stream, two enqueue operations will\n          // require at least three reads to complete: one for the first chunk, 'hello, ',\n          // one for the second chunk, 'world!', and one to signal close.\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {\n            checked++;\n            c->enqueue(js, toBufferSource(js, kj::mv(chunks[counter++])));\n            if (counter == chunks.size()) {\n              c->close(js);\n            }\n\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n      // Setting a highWaterMark of 0 means the pull function above will not be called\n      // immediately on creation of the stream, but only when the first read in the\n      // readall call below happens.\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    // Starts a read loop of javascript promises.\n    auto promise = rs->getController().readAllBytes(js, 20).then(\n        js, [&](jsg::Lock& js, jsg::BufferSource&& text) {\n      KJ_ASSERT(text.asArrayPtr() == \"Hello, world!\"_kjb);\n      checked++;\n    });\n\n    // Reading left the stream locked and disturbed\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n\n    // Run the microtasks to completion. This should resolve the promise and\n    // run it to completion. The test is buggy if it fails to do so.\n    js.runMicrotasks();\n    KJ_ASSERT(checked == 14);\n\n    // Reading everything successfully should cause the stream to close.\n    KJ_ASSERT(rs->getController().isClosed());\n\n    // Add we should still be locked and disturbed.\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n  });\n}\n\nKJ_TEST(\"ReadableStream read all bytes (byte readable, large data)\") {\n  preamble([](jsg::Lock& js) {\n    uint checked = 0;\n    uint counter = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    static constexpr uint BASE = 4097;\n    auto chunks = kj::arr<kj::Array<kj::byte>>(kj::heapArray<kj::byte>(BASE),\n        kj::heapArray<kj::byte>(BASE * 2), kj::heapArray<kj::byte>(BASE * 4));\n    chunks[0].asPtr().fill('A');\n    chunks[1].asPtr().fill('B');\n    chunks[2].asPtr().fill('C');\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .type = kj::str(\"bytes\"),\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        // Because we're using a value-based stream, two enqueue operations will\n        // require at least three reads to complete: one for the first chunk, 'hello, ',\n        // one for the second chunk, 'world!', and one to signal close.\n        KJ_SWITCH_ONEOF(controller) {\n          // Because we're using a value-based stream, two enqueue operations will\n          // require at least three reads to complete: one for the first chunk, 'hello, ',\n          // one for the second chunk, 'world!', and one to signal close.\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {\n            checked++;\n            c->enqueue(js, toBufferSource(js, kj::mv(chunks[counter++])));\n            if (counter == chunks.size()) {\n              c->close(js);\n            }\n\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n      // Setting a highWaterMark of 0 means the pull function above will not be called\n      // immediately on creation of the stream, but only when the first read in the\n      // readall call below happens.\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    // Starts a read loop of javascript promises.\n    auto promise = rs->getController()\n                       .readAllBytes(js, (BASE * 7) + 1)\n                       .then(js, [&](jsg::Lock& js, jsg::BufferSource&& text) {\n      kj::byte check[BASE * 7]{};\n      kj::arrayPtr(check).first(BASE).fill('A');\n      kj::arrayPtr(check).slice(BASE).first(BASE * 2).fill('B');\n      kj::arrayPtr(check).slice(BASE * 3).fill('C');\n      KJ_ASSERT(text.size() == BASE * 7);\n      KJ_ASSERT(text.asArrayPtr() == check);\n      checked++;\n    });\n\n    // Reading left the stream locked and disturbed\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n\n    // Run the microtasks to completion. This should resolve the promise and\n    // run it to completion. The test is buggy if it fails to do so.\n    js.runMicrotasks();\n    KJ_ASSERT(checked == 4);\n\n    // Reading everything successfully should cause the stream to close.\n    KJ_ASSERT(rs->getController().isClosed());\n\n    // Add we should still be locked and disturbed.\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n  });\n}\n\n// ======================================================================================\n// Fail cases\n\nKJ_TEST(\"ReadableStream read all bytes (value readable, wrong type)\") {\n  preamble([](jsg::Lock& js) {\n    uint checked = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        // Because we're using a value-based stream, two enqueue operations will\n        // require at least three reads to complete: one for the first chunk, 'hello, ',\n        // one for the second chunk, 'world!', and one to signal close.\n        KJ_SWITCH_ONEOF(controller) {\n          // Because we're using a value-based stream, two enqueue operations will\n          // require at least three reads to complete: one for the first chunk, 'hello, ',\n          // one for the second chunk, 'world!', and one to signal close.\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            c->enqueue(js, js.str(\"wrong type\"_kjc));\n            checked++;\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      },\n      .cancel = [&](jsg::Lock& js, auto reason) -> jsg::Promise<void> {\n        KJ_ASSERT(kj::str(reason) == \"TypeError: This ReadableStream did not return bytes.\");\n        checked++;\n        return js.resolvedPromise();\n      }\n      // Setting a highWaterMark of 0 means the pull function above will not be called\n      // immediately on creation of the stream, but only when the first read in the\n      // readall call below happens.\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    // Starts a read loop of javascript promises.\n    auto promise = rs->getController().readAllBytes(js, 20).then(js,\n        [](jsg::Lock& js, jsg::BufferSource&& text) { KJ_UNREACHABLE; },\n        [&](jsg::Lock& js, jsg::Value&& exception) {\n      KJ_ASSERT(kj::str(exception.getHandle(js)) ==\n          \"TypeError: This ReadableStream did not return bytes.\");\n      checked++;\n    });\n\n    // Reading left the stream locked and disturbed\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n\n    // Run the microtasks to completion. This should resolve the promise and\n    // run it to completion. The test is buggy if it fails to do so.\n    js.runMicrotasks();\n    KJ_ASSERT(checked == 3);\n\n    KJ_ASSERT(rs->getController().isClosedOrErrored());\n\n    // Add we should still be locked and disturbed.\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n  });\n}\n\nKJ_TEST(\"ReadableStream read all bytes (value readable, to many bytes)\") {\n  preamble([](jsg::Lock& js) {\n    uint checked = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        // Because we're using a value-based stream, two enqueue operations will\n        // require at least three reads to complete: one for the first chunk, 'hello, ',\n        // one for the second chunk, 'world!', and one to signal close.\n        KJ_SWITCH_ONEOF(controller) {\n          // Because we're using a value-based stream, two enqueue operations will\n          // require at least three reads to complete: one for the first chunk, 'hello, ',\n          // one for the second chunk, 'world!', and one to signal close.\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            c->enqueue(js, toBytes(js, kj::str(\"123456789012345678901\")));\n            checked++;\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n      // Setting a highWaterMark of 0 means the pull function above will not be called\n      // immediately on creation of the stream, but only when the first read in the\n      // readall call below happens.\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    // Starts a read loop of javascript promises.\n    auto promise = rs->getController().readAllBytes(js, 20).then(js,\n        [](jsg::Lock& js, jsg::BufferSource&& text) { KJ_UNREACHABLE; },\n        [&](jsg::Lock& js, jsg::Value&& exception) {\n      KJ_ASSERT(kj::str(exception.getHandle(js)) == \"TypeError: Memory limit exceeded before EOF.\");\n      checked++;\n    });\n\n    // Reading left the stream locked and disturbed\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n\n    // Run the microtasks to completion. This should resolve the promise and\n    // run it to completion. The test is buggy if it fails to do so.\n    js.runMicrotasks();\n    KJ_ASSERT(checked == 2);\n\n    KJ_ASSERT(rs->getController().isClosedOrErrored());\n\n    // Add we should still be locked and disturbed.\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n  });\n}\n\nKJ_TEST(\"ReadableStream read all bytes (byte readable, to many bytes)\") {\n  preamble([](jsg::Lock& js) {\n    uint checked = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .type = kj::str(\"bytes\"),\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        // Because we're using a value-based stream, two enqueue operations will\n        // require at least three reads to complete: one for the first chunk, 'hello, ',\n        // one for the second chunk, 'world!', and one to signal close.\n        KJ_SWITCH_ONEOF(controller) {\n          // Because we're using a value-based stream, two enqueue operations will\n          // require at least three reads to complete: one for the first chunk, 'hello, ',\n          // one for the second chunk, 'world!', and one to signal close.\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {\n            c->enqueue(js, toBufferSource(js, kj::str(\"123456789012345678901\")));\n            checked++;\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n      // Setting a highWaterMark of 0 means the pull function above will not be called\n      // immediately on creation of the stream, but only when the first read in the\n      // readall call below happens.\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    // Starts a read loop of javascript promises.\n    auto promise = rs->getController().readAllBytes(js, 20).then(js,\n        [](jsg::Lock& js, jsg::BufferSource&& text) { KJ_UNREACHABLE; },\n        [&](jsg::Lock& js, jsg::Value&& exception) {\n      KJ_ASSERT(kj::str(exception.getHandle(js)) == \"TypeError: Memory limit exceeded before EOF.\");\n      checked++;\n    });\n\n    // Reading left the stream locked and disturbed\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n\n    // Run the microtasks to completion. This should resolve the promise and\n    // run it to completion. The test is buggy if it fails to do so.\n    js.runMicrotasks();\n    KJ_ASSERT(checked == 2);\n\n    KJ_ASSERT(rs->getController().isClosedOrErrored());\n\n    // Add we should still be locked and disturbed.\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n  });\n}\n\nKJ_TEST(\"ReadableStream read all bytes (byte readable, failed read)\") {\n  preamble([](jsg::Lock& js) {\n    uint checked = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .type = kj::str(\"bytes\"),\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        checked++;\n        return js.rejectedPromise<void>(js.error(\"boom\"));\n      }\n      // Setting a highWaterMark of 0 means the pull function above will not be called\n      // immediately on creation of the stream, but only when the first read in the\n      // readall call below happens.\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    // Starts a read loop of javascript promises.\n    auto promise = rs->getController().readAllBytes(js, 20).then(js,\n        [](jsg::Lock& js, jsg::BufferSource&& text) { KJ_UNREACHABLE; },\n        [&](jsg::Lock& js, jsg::Value&& exception) {\n      KJ_ASSERT(kj::str(exception.getHandle(js)) == \"Error: boom\");\n      checked++;\n    });\n\n    // Reading left the stream locked and disturbed\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n\n    // Run the microtasks to completion. This should resolve the promise and\n    // run it to completion. The test is buggy if it fails to do so.\n    js.runMicrotasks();\n    KJ_ASSERT(checked == 2);\n\n    KJ_ASSERT(rs->getController().isClosedOrErrored());\n\n    // Add we should still be locked and disturbed.\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n  });\n}\n\nKJ_TEST(\"ReadableStream read all bytes (value readable, failed read)\") {\n  preamble([](jsg::Lock& js) {\n    uint checked = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        checked++;\n        return js.rejectedPromise<void>(js.error(\"boom\"));\n      }\n      // Setting a highWaterMark of 0 means the pull function above will not be called\n      // immediately on creation of the stream, but only when the first read in the\n      // readall call below happens.\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    // Starts a read loop of javascript promises.\n    auto promise = rs->getController().readAllBytes(js, 20).then(js,\n        [](jsg::Lock& js, jsg::BufferSource&& text) { KJ_UNREACHABLE; },\n        [&](jsg::Lock& js, jsg::Value&& exception) {\n      KJ_ASSERT(kj::str(exception.getHandle(js)) == \"Error: boom\");\n      checked++;\n    });\n\n    // Reading left the stream locked and disturbed\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n\n    // Run the microtasks to completion. This should resolve the promise and\n    // run it to completion. The test is buggy if it fails to do so.\n    js.runMicrotasks();\n    KJ_ASSERT(checked == 2);\n\n    KJ_ASSERT(rs->getController().isClosedOrErrored());\n\n    // Add we should still be locked and disturbed.\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n  });\n}\n\nKJ_TEST(\"ReadableStream read all bytes (byte readable, failed start)\") {\n  preamble([](jsg::Lock& js) {\n    uint checked = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .type = kj::str(\"bytes\"),\n      .start = [&](jsg::Lock& js, UnderlyingSource::Controller controller) -> jsg::Promise<void> {\n        checked++;\n        return js.rejectedPromise<void>(js.error(\"boom\"));\n      }\n      // Setting a highWaterMark of 0 means the pull function above will not be called\n      // immediately on creation of the stream, but only when the first read in the\n      // readall call below happens.\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    // Starts a read loop of javascript promises.\n    auto promise = rs->getController().readAllBytes(js, 20).then(js,\n        [](jsg::Lock& js, jsg::BufferSource&& text) { KJ_UNREACHABLE; },\n        [&](jsg::Lock& js, jsg::Value&& exception) {\n      KJ_ASSERT(kj::str(exception.getHandle(js)) == \"Error: boom\");\n      checked++;\n    });\n\n    // Reading left the stream locked and disturbed\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n\n    // Run the microtasks to completion. This should resolve the promise and\n    // run it to completion. The test is buggy if it fails to do so.\n    js.runMicrotasks();\n    KJ_ASSERT(checked == 2);\n\n    KJ_ASSERT(rs->getController().isClosedOrErrored());\n\n    // Add we should still be locked and disturbed.\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n  });\n}\n\nKJ_TEST(\"ReadableStream read all bytes (byte readable, failed start 2)\") {\n  preamble([](jsg::Lock& js) {\n    uint checked = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .type = kj::str(\"bytes\"),\n      .start = [&](jsg::Lock& js, UnderlyingSource::Controller controller) -> jsg::Promise<void> {\n        checked++;\n        JSG_FAIL_REQUIRE(Error, \"boom\");\n      }\n      // Setting a highWaterMark of 0 means the pull function above will not be called\n      // immediately on creation of the stream, but only when the first read in the\n      // readall call below happens.\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    // Starts a read loop of javascript promises.\n    auto promise = rs->getController().readAllBytes(js, 20).then(js,\n        [](jsg::Lock& js, jsg::BufferSource&& text) { KJ_UNREACHABLE; },\n        [&](jsg::Lock& js, jsg::Value&& exception) {\n      KJ_ASSERT(kj::str(exception.getHandle(js)) == \"Error: boom\");\n      checked++;\n    });\n\n    // Reading left the stream locked and disturbed\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n\n    // Run the microtasks to completion. This should resolve the promise and\n    // run it to completion. The test is buggy if it fails to do so.\n    js.runMicrotasks();\n    KJ_ASSERT(checked == 2);\n\n    KJ_ASSERT(rs->getController().isClosedOrErrored());\n\n    // Add we should still be locked and disturbed.\n    KJ_ASSERT(rs->isLocked());\n    KJ_ASSERT(rs->isDisturbed());\n  });\n}\n\n// ======================================================================================\n// DrainingReader tests\n\nKJ_TEST(\"DrainingReader basic creation and locking (value stream)\") {\n  preamble([](jsg::Lock& js) {\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    rs->getController().setup(js, UnderlyingSource{}, StreamQueuingStrategy{.highWaterMark = 0});\n\n    // Stream should not be locked initially\n    KJ_ASSERT(!rs->isLocked());\n\n    // Create DrainingReader\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      // Stream should now be locked\n      KJ_ASSERT(rs->isLocked());\n      KJ_ASSERT(reader->isAttached());\n\n      // Release the lock\n      reader->releaseLock(js);\n      KJ_ASSERT(!rs->isLocked());\n      KJ_ASSERT(!reader->isAttached());\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader cannot be created on locked stream\") {\n  preamble([](jsg::Lock& js) {\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    rs->getController().setup(js, UnderlyingSource{}, StreamQueuingStrategy{.highWaterMark = 0});\n\n    // Create first reader to lock the stream\n    KJ_IF_SOME(reader1, DrainingReader::create(js, *rs)) {\n      KJ_ASSERT(rs->isLocked());\n\n      // Try to create another reader - should fail\n      auto maybeReader2 = DrainingReader::create(js, *rs);\n      KJ_ASSERT(maybeReader2 == kj::none);\n\n      reader1->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create first DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader read drains buffered data (value stream)\") {\n  preamble([](jsg::Lock& js) {\n    uint pullCount = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            pullCount++;\n            if (pullCount == 1) {\n              // First pull - enqueue multiple chunks\n              c->enqueue(js, toBytes(js, kj::str(\"Hello, \")));\n              c->enqueue(js, toBytes(js, kj::str(\"world!\")));\n            } else {\n              // Second pull - close the stream\n              c->close(js);\n            }\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      bool readCompleted = false;\n      auto promise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        // Should have drained both chunks\n        KJ_ASSERT(result.chunks.size() == 2);\n        KJ_ASSERT(kj::str(result.chunks[0].asChars()) == \"Hello, \");\n        KJ_ASSERT(kj::str(result.chunks[1].asChars()) == \"world!\");\n        KJ_ASSERT(!result.done);  // Stream not closed yet\n        readCompleted = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(readCompleted);\n      KJ_ASSERT(pullCount == 1);  // Only one pull needed\n\n      reader->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader read drains buffered data (byte stream)\") {\n  preamble([](jsg::Lock& js) {\n    uint pullCount = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .type = kj::str(\"bytes\"),\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {}\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {\n            pullCount++;\n            if (pullCount == 1) {\n              c->enqueue(js, toBufferSource(js, kj::str(\"Hello, \")));\n              c->enqueue(js, toBufferSource(js, kj::str(\"world!\")));\n            } else {\n              c->close(js);\n            }\n            return js.resolvedPromise();\n          }\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      bool readCompleted = false;\n      auto promise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        // Should have drained both chunks\n        KJ_ASSERT(result.chunks.size() == 2);\n        KJ_ASSERT(kj::str(result.chunks[0].asChars()) == \"Hello, \");\n        KJ_ASSERT(kj::str(result.chunks[1].asChars()) == \"world!\");\n        KJ_ASSERT(!result.done);\n        readCompleted = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(readCompleted);\n      KJ_ASSERT(pullCount == 1);\n\n      reader->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader read on closed stream returns done\") {\n  preamble([](jsg::Lock& js) {\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .start = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            c->close(js);\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    js.runMicrotasks();\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      bool readCompleted = false;\n      auto promise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_ASSERT(result.chunks.size() == 0);\n        KJ_ASSERT(result.done);\n        readCompleted = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(readCompleted);\n\n      reader->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader read after releaseLock rejects\") {\n  preamble([](jsg::Lock& js) {\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    rs->getController().setup(js, UnderlyingSource{}, StreamQueuingStrategy{.highWaterMark = 0});\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      reader->releaseLock(js);\n\n      bool readRejected = false;\n      auto promise =\n          reader->read(js).catch_(js, [&](jsg::Lock& js, jsg::Value reason) -> DrainingReadResult {\n        readRejected = true;\n        return DrainingReadResult{\n          .chunks = kj::Array<kj::Array<kj::byte>>(),\n          .done = true,\n        };\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(readRejected);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader sync data then async pull waits\") {\n  // Test case: pull enqueues some data synchronously, then returns a pending promise.\n  // The first draining read should get the sync data immediately.\n  // A second draining read should wait for the async pull to complete.\n  preamble([](jsg::Lock& js) {\n    uint pullCount = 0;\n    kj::Maybe<jsg::Promise<void>::Resolver> asyncResolver;\n    kj::Maybe<jsg::Ref<ReadableStreamDefaultController>> savedController;\n\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            pullCount++;\n            if (pullCount == 1) {\n              // First pull: enqueue data synchronously, but return async promise\n              c->enqueue(js, toBytes(js, kj::str(\"sync-chunk\")));\n              // Return a promise that resolves later\n              auto prp = js.newPromiseAndResolver<void>();\n              asyncResolver = kj::mv(prp.resolver);\n              savedController = c.addRef();\n              return kj::mv(prp.promise);\n            } else if (pullCount == 2) {\n              // Second pull after async resolution: enqueue more data\n              c->enqueue(js, toBytes(js, kj::str(\"async-chunk\")));\n              return js.resolvedPromise();\n            }\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      // First read - should get sync data immediately\n      bool firstReadCompleted = false;\n      auto promise1 = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_ASSERT(result.chunks.size() == 1);\n        KJ_ASSERT(kj::str(result.chunks[0].asChars()) == \"sync-chunk\");\n        KJ_ASSERT(!result.done);\n        firstReadCompleted = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(firstReadCompleted);\n      KJ_ASSERT(pullCount == 1);  // Only first pull happened\n\n      // Second read - should wait for async data\n      bool secondReadCompleted = false;\n      auto promise2 = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        // Should get the async chunk\n        KJ_ASSERT(result.chunks.size() >= 1);\n        KJ_ASSERT(kj::str(result.chunks[0].asChars()) == \"async-chunk\");\n        KJ_ASSERT(!result.done);\n        secondReadCompleted = true;\n      });\n\n      js.runMicrotasks();\n      // Second read should NOT complete yet - still waiting for async pull\n      KJ_ASSERT(!secondReadCompleted);\n\n      // Now resolve the async pull\n      KJ_ASSERT_NONNULL(asyncResolver).resolve(js);\n      js.runMicrotasks();\n\n      // Now second read should complete\n      KJ_ASSERT(secondReadCompleted);\n      KJ_ASSERT(pullCount == 2);\n\n      reader->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader with fully async pull\") {\n  // Test case: pull returns a promise without enqueueing anything synchronously.\n  // The draining read should wait for the pull to complete and then get the data.\n  preamble([](jsg::Lock& js) {\n    uint pullCount = 0;\n    kj::Maybe<jsg::Promise<void>::Resolver> asyncResolver;\n    kj::Maybe<jsg::Ref<ReadableStreamDefaultController>> savedController;\n\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            pullCount++;\n            // Return a promise and save the controller - will enqueue data when resolved\n            auto prp = js.newPromiseAndResolver<void>();\n            asyncResolver = kj::mv(prp.resolver);\n            savedController = c.addRef();\n            return kj::mv(prp.promise);\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      bool readCompleted = false;\n      auto promise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_ASSERT(result.chunks.size() == 1);\n        KJ_ASSERT(kj::str(result.chunks[0].asChars()) == \"async-data\");\n        KJ_ASSERT(!result.done);\n        readCompleted = true;\n      });\n\n      js.runMicrotasks();\n      // Read should NOT complete yet - waiting for async pull\n      KJ_ASSERT(!readCompleted);\n      KJ_ASSERT(pullCount == 1);\n\n      // Enqueue data and resolve the pull\n      KJ_ASSERT_NONNULL(savedController)->enqueue(js, toBytes(js, kj::str(\"async-data\")));\n      KJ_ASSERT_NONNULL(asyncResolver).resolve(js);\n      js.runMicrotasks();\n\n      // Now read should complete\n      KJ_ASSERT(readCompleted);\n\n      reader->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader byte stream with async pull\") {\n  // Test async behavior with byte streams\n  preamble([](jsg::Lock& js) {\n    uint pullCount = 0;\n    kj::Maybe<jsg::Promise<void>::Resolver> asyncResolver;\n    kj::Maybe<jsg::Ref<ReadableByteStreamController>> savedController;\n\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .type = kj::str(\"bytes\"),\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {}\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {\n            pullCount++;\n            if (pullCount == 1) {\n              // Enqueue sync data but return async\n              c->enqueue(js, toBufferSource(js, kj::str(\"sync-bytes\")));\n              auto prp = js.newPromiseAndResolver<void>();\n              asyncResolver = kj::mv(prp.resolver);\n              savedController = c.addRef();\n              return kj::mv(prp.promise);\n            }\n            return js.resolvedPromise();\n          }\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      // First read gets sync data\n      bool firstReadCompleted = false;\n      auto promise1 = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_ASSERT(result.chunks.size() == 1);\n        KJ_ASSERT(kj::str(result.chunks[0].asChars()) == \"sync-bytes\");\n        KJ_ASSERT(!result.done);\n        firstReadCompleted = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(firstReadCompleted);\n\n      // Resolve async pull to allow future pulls\n      KJ_ASSERT_NONNULL(asyncResolver).resolve(js);\n      js.runMicrotasks();\n\n      reader->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader multiple sync chunks then close\") {\n  // Test: Multiple sync chunks followed by close in the same pull\n  preamble([](jsg::Lock& js) {\n    uint pullCount = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            pullCount++;\n            // Enqueue multiple chunks then close\n            c->enqueue(js, toBytes(js, kj::str(\"chunk1\")));\n            c->enqueue(js, toBytes(js, kj::str(\"chunk2\")));\n            c->enqueue(js, toBytes(js, kj::str(\"chunk3\")));\n            c->close(js);\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      bool readCompleted = false;\n      auto promise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        // Should get all 3 chunks and done=true\n        KJ_ASSERT(result.chunks.size() == 3);\n        KJ_ASSERT(kj::str(result.chunks[0].asChars()) == \"chunk1\");\n        KJ_ASSERT(kj::str(result.chunks[1].asChars()) == \"chunk2\");\n        KJ_ASSERT(kj::str(result.chunks[2].asChars()) == \"chunk3\");\n        KJ_ASSERT(result.done);\n        readCompleted = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(readCompleted);\n      KJ_ASSERT(pullCount == 1);\n\n      reader->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader read from teed branches\") {\n  // Test: DrainingReader works correctly on both branches of a teed stream\n  preamble([](jsg::Lock& js) {\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            c->enqueue(js, toBytes(js, kj::str(\"chunk1\")));\n            c->enqueue(js, toBytes(js, kj::str(\"chunk2\")));\n            c->close(js);\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    // Tee the stream into two branches\n    auto branches = rs->tee(js);\n    KJ_ASSERT(branches.size() == 2);\n    auto& branch1 = branches[0];\n    auto& branch2 = branches[1];\n\n    // Create DrainingReader on branch1\n    KJ_IF_SOME(reader1, DrainingReader::create(js, *branch1)) {\n      bool read1Completed = false;\n      auto promise1 = reader1->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_ASSERT(result.chunks.size() == 2);\n        KJ_ASSERT(kj::str(result.chunks[0].asChars()) == \"chunk1\");\n        KJ_ASSERT(kj::str(result.chunks[1].asChars()) == \"chunk2\");\n        KJ_ASSERT(result.done);\n        read1Completed = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(read1Completed, \"Branch1 read should complete\");\n\n      reader1->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader for branch1\");\n    }\n\n    // Create DrainingReader on branch2 - should get the same data\n    KJ_IF_SOME(reader2, DrainingReader::create(js, *branch2)) {\n      bool read2Completed = false;\n      auto promise2 = reader2->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_ASSERT(result.chunks.size() == 2);\n        KJ_ASSERT(kj::str(result.chunks[0].asChars()) == \"chunk1\");\n        KJ_ASSERT(kj::str(result.chunks[1].asChars()) == \"chunk2\");\n        KJ_ASSERT(result.done);\n        read2Completed = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(read2Completed, \"Branch2 read should complete\");\n\n      reader2->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader for branch2\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader read from byte stream with BYOB support\") {\n  // Test: DrainingReader works correctly with byte streams (which support BYOB reads)\n  // even though DrainingReader itself uses a default reader. This tests that closing\n  // the controller synchronously during draining works correctly - the controller's\n  // close triggers doClose() which must be deferred while onConsumerWantsData is active.\n  preamble([](jsg::Lock& js) {\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .type = kj::str(\"bytes\"),\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {}\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {\n            // Enqueue multiple byte chunks - verifies DrainingReader handles\n            // byte stream chunks correctly and preserves order\n            c->enqueue(js, toBufferSource(js, kj::str(\"byob-chunk1\")));\n            c->enqueue(js, toBufferSource(js, kj::str(\"byob-chunk2\")));\n            c->enqueue(js, toBufferSource(js, kj::str(\"byob-chunk3\")));\n            // Close synchronously - this tests that the fix for use-after-free works.\n            // Without the fix, this would cause ByteReadable to be destroyed while\n            // onConsumerWantsData is still on the stack.\n            c->close(js);\n            return js.resolvedPromise();\n          }\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    // Use DrainingReader (which uses default reader) to drain the BYOB-capable byte stream\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      bool readCompleted = false;\n      auto promise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        // Should successfully drain all byte chunks in order\n        KJ_ASSERT(result.chunks.size() == 3, \"Should get 3 chunks\");\n        KJ_ASSERT(kj::str(result.chunks[0].asChars()) == \"byob-chunk1\");\n        KJ_ASSERT(kj::str(result.chunks[1].asChars()) == \"byob-chunk2\");\n        KJ_ASSERT(kj::str(result.chunks[2].asChars()) == \"byob-chunk3\");\n        KJ_ASSERT(result.done);  // Stream closed, so done should be true\n        readCompleted = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(readCompleted);\n\n      reader->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader error during pull in value stream\") {\n  // Test: Calling controller.error() synchronously inside pull during a draining\n  // read must not cause a use-after-free. The pull callback transitions the\n  // ConsumerImpl from Ready→Errored which destroys the Ready struct (and its\n  // RingBuffer). The drainingRead loop must detect this and stop.\n  preamble([](jsg::Lock& js) {\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            c->enqueue(js, toBytes(js, kj::str(\"before-error\")));\n            c->error(js, js.error(\"deliberate error\"));\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      bool readCompleted = false;\n      auto promise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_FAIL_ASSERT(\"Should have rejected, not resolved\");\n      }, [&](jsg::Lock& js, jsg::Value&& err) {\n        // The draining read should reject with the error from controller.error().\n        readCompleted = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(readCompleted, \"Read should have completed with rejection\");\n\n      reader->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader error during pull in byte stream\") {\n  // Test: Same as above but for byte streams (ByteQueue path).\n  preamble([](jsg::Lock& js) {\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .type = kj::str(\"bytes\"),\n      .pull = [](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {}\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {\n            c->enqueue(js, toBufferSource(js, kj::str(\"before-error\")));\n            c->error(js, js.error(\"deliberate error\"));\n            return js.resolvedPromise();\n          }\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      bool readCompleted = false;\n      auto promise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_FAIL_ASSERT(\"Should have rejected, not resolved\");\n      }, [&](jsg::Lock& js, jsg::Value&& err) { readCompleted = true; });\n\n      js.runMicrotasks();\n      KJ_ASSERT(readCompleted, \"Read should have completed with rejection\");\n\n      reader->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader read from stream with transform-like pattern\") {\n  // Test: DrainingReader works correctly with a stream that simulates the\n  // TransformStream pattern where data is written to writable and read from readable\n  preamble([](jsg::Lock& js) {\n    // Create a stream where the controller is stored and chunks are enqueued asynchronously\n    // (simulating how TransformStream's readable side receives transformed data)\n    kj::Maybe<jsg::Ref<ReadableStreamDefaultController>> savedController;\n    bool startResolved = false;\n\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .start = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            savedController = c.addRef();\n            startResolved = true;\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      },\n      .pull = [](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        // No-op pull - data comes from external enqueue calls (like transform writes)\n        return js.resolvedPromise();\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    js.runMicrotasks();\n    KJ_ASSERT(startResolved, \"Stream should have started\");\n\n    auto& controller = KJ_ASSERT_NONNULL(savedController, \"Controller should be saved\");\n\n    // Simulate TransformStream write->transform->enqueue pattern\n    // Enqueue transformed chunks (like what TransformStream's transform callback would do)\n    controller->enqueue(js, toBytes(js, kj::str(\"transformed-a\")));\n    controller->enqueue(js, toBytes(js, kj::str(\"transformed-b\")));\n\n    // Create DrainingReader to drain all buffered transformed data\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      bool readCompleted = false;\n      auto promise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        // Should drain all enqueued chunks\n        KJ_ASSERT(result.chunks.size() == 2);\n        KJ_ASSERT(kj::str(result.chunks[0].asChars()) == \"transformed-a\");\n        KJ_ASSERT(kj::str(result.chunks[1].asChars()) == \"transformed-b\");\n        KJ_ASSERT(!result.done);  // Stream not closed yet\n        readCompleted = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(readCompleted);\n\n      // Simulate more data being written/transformed\n      controller->enqueue(js, toBytes(js, kj::str(\"transformed-c\")));\n      controller->close(js);\n\n      bool finalReadCompleted = false;\n      auto finalPromise =\n          reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_ASSERT(result.chunks.size() == 1);\n        KJ_ASSERT(kj::str(result.chunks[0].asChars()) == \"transformed-c\");\n        KJ_ASSERT(result.done);  // Stream now closed\n        finalReadCompleted = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(finalReadCompleted);\n\n      reader->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader cancel while read is pending (value stream)\") {\n  // Test: Calling cancel() on the reader while a read() is pending should\n  // cause the pending read to reject and the stream to be canceled.\n  preamble([](jsg::Lock& js) {\n    kj::Maybe<jsg::Promise<void>::Resolver> asyncResolver;\n    bool cancelCalled = false;\n\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        // Return a pending promise to keep the read waiting\n        auto prp = js.newPromiseAndResolver<void>();\n        asyncResolver = kj::mv(prp.resolver);\n        return kj::mv(prp.promise);\n      },\n      .cancel = [&](jsg::Lock& js, auto reason) -> jsg::Promise<void> {\n        cancelCalled = true;\n        KJ_ASSERT(kj::str(reason) == \"canceled by reader\");\n        return js.resolvedPromise();\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      // Start a read that will be pending (waiting for async pull)\n      bool readRejected = false;\n      bool readResolved = false;\n      auto readPromise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        readResolved = true;\n      }, [&](jsg::Lock& js, jsg::Value&& reason) { readRejected = true; });\n\n      js.runMicrotasks();\n      // Read should still be pending - waiting for async pull\n      KJ_ASSERT(!readResolved);\n      KJ_ASSERT(!readRejected);\n\n      // Now cancel while read is pending\n      bool cancelResolved = false;\n      auto cancelPromise =\n          reader->cancel(js, js.str(\"canceled by reader\"_kjc)).then(js, [&](jsg::Lock& js) {\n        cancelResolved = true;\n      });\n\n      js.runMicrotasks();\n\n      // Cancel should have completed\n      KJ_ASSERT(cancelResolved, \"cancel() should resolve\");\n      KJ_ASSERT(cancelCalled, \"underlying source cancel should be called\");\n\n      // The pending read should have been rejected or resolved with done\n      KJ_ASSERT(readResolved || readRejected, \"pending read should complete after cancel\");\n\n      // Stream should be in closed/errored state\n      KJ_ASSERT(rs->getController().isClosedOrErrored());\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader cancel while read is pending (byte stream)\") {\n  // Test: Same as above but with byte stream\n  preamble([](jsg::Lock& js) {\n    kj::Maybe<jsg::Promise<void>::Resolver> asyncResolver;\n    bool cancelCalled = false;\n\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .type = kj::str(\"bytes\"),\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        // Return a pending promise to keep the read waiting\n        auto prp = js.newPromiseAndResolver<void>();\n        asyncResolver = kj::mv(prp.resolver);\n        return kj::mv(prp.promise);\n      },\n      .cancel = [&](jsg::Lock& js, auto reason) -> jsg::Promise<void> {\n        cancelCalled = true;\n        KJ_ASSERT(kj::str(reason) == \"canceled by reader\");\n        return js.resolvedPromise();\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      // Start a read that will be pending\n      bool readRejected = false;\n      bool readResolved = false;\n      auto readPromise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        readResolved = true;\n      }, [&](jsg::Lock& js, jsg::Value&& reason) { readRejected = true; });\n\n      js.runMicrotasks();\n      KJ_ASSERT(!readResolved);\n      KJ_ASSERT(!readRejected);\n\n      // Cancel while read is pending\n      bool cancelResolved = false;\n      auto cancelPromise =\n          reader->cancel(js, js.str(\"canceled by reader\"_kjc)).then(js, [&](jsg::Lock& js) {\n        cancelResolved = true;\n      });\n\n      js.runMicrotasks();\n\n      KJ_ASSERT(cancelResolved, \"cancel() should resolve\");\n      KJ_ASSERT(cancelCalled, \"underlying source cancel should be called\");\n      KJ_ASSERT(readResolved || readRejected, \"pending read should complete after cancel\");\n      KJ_ASSERT(rs->getController().isClosedOrErrored());\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader cancel while read is pending with buffered data\") {\n  // Test: Cancel while read is pending, but there's already some buffered data.\n  // The buffered data should be discarded and the stream canceled.\n  preamble([](jsg::Lock& js) {\n    kj::Maybe<jsg::Promise<void>::Resolver> asyncResolver;\n    kj::Maybe<jsg::Ref<ReadableStreamDefaultController>> savedController;\n    bool cancelCalled = false;\n\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            // Enqueue some data synchronously\n            c->enqueue(js, toBytes(js, kj::str(\"buffered-data\")));\n            savedController = c.addRef();\n            // But return a pending promise (more data coming)\n            auto prp = js.newPromiseAndResolver<void>();\n            asyncResolver = kj::mv(prp.resolver);\n            return kj::mv(prp.promise);\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      },\n      .cancel = [&](jsg::Lock& js, auto reason) -> jsg::Promise<void> {\n        cancelCalled = true;\n        return js.resolvedPromise();\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      // First read gets the buffered data\n      bool firstReadCompleted = false;\n      auto readPromise1 =\n          reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_ASSERT(result.chunks.size() == 1);\n        KJ_ASSERT(kj::str(result.chunks[0].asChars()) == \"buffered-data\");\n        KJ_ASSERT(!result.done);\n        firstReadCompleted = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(firstReadCompleted);\n\n      // Second read will be pending (waiting for async pull resolution)\n      bool secondReadRejected = false;\n      bool secondReadResolved = false;\n      auto readPromise2 = reader->read(js).then(js,\n          [&](jsg::Lock& js, DrainingReadResult&& result) { secondReadResolved = true; },\n          [&](jsg::Lock& js, jsg::Value&& reason) { secondReadRejected = true; });\n\n      js.runMicrotasks();\n      KJ_ASSERT(!secondReadResolved);\n      KJ_ASSERT(!secondReadRejected);\n\n      // Cancel while second read is pending\n      bool cancelResolved = false;\n      auto cancelPromise =\n          reader->cancel(js, js.str(\"cancel reason\"_kjc)).then(js, [&](jsg::Lock& js) {\n        cancelResolved = true;\n      });\n\n      js.runMicrotasks();\n\n      KJ_ASSERT(cancelResolved);\n      KJ_ASSERT(cancelCalled);\n      KJ_ASSERT(secondReadResolved || secondReadRejected);\n      KJ_ASSERT(rs->getController().isClosedOrErrored());\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader cancel while read pending - UAF safety (value stream)\") {\n  // This test specifically exercises the potential UAF scenario where:\n  // 1. A draining read creates a promise with lambdas capturing `this` (the Consumer)\n  // 2. Cancel is called, which rejects the pending read (scheduling the error lambda)\n  // 3. doClose() destroys the Consumer\n  // 4. The error lambda runs and must NOT access the destroyed Consumer\n  //\n  // The lambdas in ValueQueue::Consumer::drainingRead capture `this` to clear\n  // hasPendingDrainingRead. If the Consumer is destroyed before the lambda runs,\n  // this would be a use-after-free.\n  preamble([](jsg::Lock& js) {\n    kj::Maybe<jsg::Promise<void>::Resolver> asyncResolver;\n    bool cancelCalled = false;\n    bool pullCalled = false;\n\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        pullCalled = true;\n        // Return a pending promise - this keeps the read waiting\n        auto prp = js.newPromiseAndResolver<void>();\n        asyncResolver = kj::mv(prp.resolver);\n        return kj::mv(prp.promise);\n      },\n      .cancel = [&](jsg::Lock& js, auto reason) -> jsg::Promise<void> {\n        cancelCalled = true;\n        return js.resolvedPromise();\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      // Start a draining read - this will:\n      // 1. Call pull (which returns pending promise)\n      // 2. Queue a ReadRequest\n      // 3. Return a promise with lambdas capturing `this` (the Consumer)\n      bool readRejected = false;\n      bool readResolved = false;\n      auto readPromise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        readResolved = true;\n      }, [&](jsg::Lock& js, jsg::Value&& reason) {\n        // This error handler runs after cancel rejects the pending read.\n        // The lambda in drainingRead also runs to clear hasPendingDrainingRead.\n        // If that lambda accesses a destroyed Consumer, we have UAF.\n        readRejected = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(pullCalled, \"pull should have been called\");\n      KJ_ASSERT(!readResolved);\n      KJ_ASSERT(!readRejected);\n\n      // Now cancel. This will:\n      // 1. Call cancelPendingReads() which rejects the ReadRequest\n      // 2. The rejection schedules the error lambda as a microtask\n      // 3. doClose() runs (via KJ_DEFER) and may destroy the Consumer\n      // 4. Microtasks run - the error lambda in drainingRead accesses `this`\n      //\n      // If `this` is destroyed before the lambda runs, we have UAF.\n      bool cancelResolved = false;\n      auto cancelPromise =\n          reader->cancel(js, js.str(\"cancel for UAF test\"_kjc)).then(js, [&](jsg::Lock& js) {\n        cancelResolved = true;\n      });\n\n      // Run microtasks - this is where UAF would occur if the bug exists\n      js.runMicrotasks();\n\n      KJ_ASSERT(cancelResolved, \"cancel should resolve\");\n      KJ_ASSERT(cancelCalled, \"underlying source cancel should be called\");\n      KJ_ASSERT(readResolved || readRejected, \"read should complete after cancel\");\n      KJ_ASSERT(rs->getController().isClosedOrErrored(), \"stream should be closed/errored\");\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader cancel while read pending - UAF safety (byte stream)\") {\n  // Same test as above but for byte streams (ByteQueue::Consumer)\n  preamble([](jsg::Lock& js) {\n    kj::Maybe<jsg::Promise<void>::Resolver> asyncResolver;\n    bool cancelCalled = false;\n    bool pullCalled = false;\n\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .type = kj::str(\"bytes\"),\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        pullCalled = true;\n        auto prp = js.newPromiseAndResolver<void>();\n        asyncResolver = kj::mv(prp.resolver);\n        return kj::mv(prp.promise);\n      },\n      .cancel = [&](jsg::Lock& js, auto reason) -> jsg::Promise<void> {\n        cancelCalled = true;\n        return js.resolvedPromise();\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      bool readRejected = false;\n      bool readResolved = false;\n      auto readPromise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        readResolved = true;\n      }, [&](jsg::Lock& js, jsg::Value&& reason) { readRejected = true; });\n\n      js.runMicrotasks();\n      KJ_ASSERT(pullCalled);\n      KJ_ASSERT(!readResolved);\n      KJ_ASSERT(!readRejected);\n\n      bool cancelResolved = false;\n      auto cancelPromise =\n          reader->cancel(js, js.str(\"cancel for UAF test\"_kjc)).then(js, [&](jsg::Lock& js) {\n        cancelResolved = true;\n      });\n\n      js.runMicrotasks();\n\n      KJ_ASSERT(cancelResolved);\n      KJ_ASSERT(cancelCalled);\n      KJ_ASSERT(readResolved || readRejected);\n      KJ_ASSERT(rs->getController().isClosedOrErrored());\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\n// ======================================================================================\n// Execution Termination Tests\n\n// Test that execution termination during a read doesn't cause an assertion failure.\n// This tests the fix for the production error:\n// \"expected !js.v8Isolate->IsExecutionTerminating()\"\n//\n// Note: This test verifies that if IsExecutionTerminating() is true after readCallback()\n// returns, we handle it gracefully instead of asserting. However, actually triggering\n// the termination check in the exact right spot in a unit test is difficult because\n// V8 only checks the termination flag during JS execution, not during C++ callbacks.\n// The production scenario occurs when the termination happens during actual JS execution\n// inside readCallback() (e.g., user's pull function runs too long).\nKJ_TEST(\"ReadableStream handles execution termination during read\") {\n  preamble([](jsg::Lock& js) {\n    // We need to handle termination at this level because the context scope\n    // will propagate the termination exception.\n    v8::TryCatch outerTryCatch(js.v8Isolate);\n    bool testCompleted = false;\n\n    try {\n      v8::TryCatch tryCatch(js.v8Isolate);\n\n      auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n\n      // Set up a stream where the pull callback terminates execution\n      rs->getController().setup(js,\n          UnderlyingSource{\n            .pull =\n                [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        // Terminate execution - this simulates CPU time limit exceeded\n        js.terminateNextExecution();\n\n        // Force V8 to check the termination flag by doing JS work.\n        // This is similar to what terminateExecutionNow() does.\n        jsg::check(v8::JSON::Stringify(js.v8Context(), js.str(\"test\"_kj)));\n\n        // We shouldn't get here - termination should have been triggered\n        return js.resolvedPromise();\n      },\n          },\n          StreamQueuingStrategy{.highWaterMark = 0});\n\n      // Start a read - this will call pull which terminates execution\n      auto promise = rs->getController().readAllText(js, 100);\n\n      // Run microtasks - this will propagate the termination\n      js.runMicrotasks();\n\n      // If we get here without the assertion failing, the fix works\n      testCompleted = true;\n    } catch (jsg::JsExceptionThrown&) {\n      // Expected - execution was terminated\n      // The important thing is we didn't hit the assertion failure\n      testCompleted = true;\n    }\n\n    // Cancel termination so cleanup can proceed\n    if (js.v8Isolate->IsExecutionTerminating()) {\n      js.v8Isolate->CancelTerminateExecution();\n    }\n\n    // The test passes if we got here without an assertion failure\n    KJ_ASSERT(testCompleted, \"Test did not complete as expected\");\n  });\n}\n\n// ======================================================================================\n// WritableStream re-entrancy tests\n\nKJ_TEST(\"WritableStream close during abort algorithm returns rejected promise\") {\n  // Regression test for a bug where calling writer.close() re-entrantly from\n  // the underlying sink's abort algorithm would hit a KJ_ASSERT in WritableImpl::close.\n  //\n  // The issue: finishErroring transitions WritableImpl to Errored state, then runs\n  // the abort algorithm (user JS code). During that window, WritableStreamJsController\n  // is still in Controller state (doError hasn't propagated yet). If the abort\n  // algorithm calls writer.close(), the JsController delegates to WritableImpl::close\n  // which previously asserted isWritable()||isErroring() -- but state was Errored.\n  //\n  // The fix adds defensive checks in WritableImpl::close for Closed/Errored states\n  // that return rejected promises instead of asserting.\n  preamble([](jsg::Lock& js) {\n    bool abortCalled = false;\n    bool closeRejected = false;\n    bool abortResolved = false;\n\n    auto ws = js.alloc<WritableStream>(newWritableStreamJsController());\n\n    // We capture a raw pointer to the writer so the abort callback can call close().\n    WritableStreamDefaultWriter* writerPtr = nullptr;\n\n    // clang-format off\n    ws->getController().setup(js, UnderlyingSink{\n      .abort = [&](jsg::Lock& js, v8::Local<v8::Value> reason) -> jsg::Promise<void> {\n        abortCalled = true;\n        // Re-entrantly call close() on the writer during the abort algorithm.\n        // At this point, WritableImpl has already transitioned to Errored state\n        // but WritableStreamJsController hasn't been updated yet.\n        // This should return a rejected promise instead of crashing.\n        writerPtr->close(js).then(js,\n            [](jsg::Lock& js) {},\n            [&](jsg::Lock& js, jsg::Value reason) {\n              closeRejected = true;\n            });\n        return js.resolvedPromise();\n      }\n    }, StreamQueuingStrategy{});\n    // clang-format on\n\n    auto writer = js.alloc<WritableStreamDefaultWriter>();\n    writer->lockToStream(js, *ws);\n    writerPtr = &*writer;\n\n    // Abort the writer. This triggers:\n    // 1. WritableImpl::abort() sets pending abort, KJ_DEFER fires startErroring\n    // 2. startErroring -> finishErroring (no in-flight ops, stream is started)\n    // 3. finishErroring transitions WritableImpl to Errored\n    // 4. finishErroring calls the abort algorithm (our callback above)\n    // 5. The abort callback calls writer.close() while WritableImpl is Errored\n    //    but WritableStreamJsController still shows Controller state\n    writer->abort(js, kj::none).then(js, [&](jsg::Lock& js) {\n      abortResolved = true;\n    }, [&](jsg::Lock& js, jsg::Value reason) {});\n\n    js.runMicrotasks();\n\n    KJ_ASSERT(abortCalled, \"abort algorithm was not called\");\n    KJ_ASSERT(closeRejected, \"re-entrant close() should have been rejected\");\n    KJ_ASSERT(abortResolved, \"abort should have resolved successfully\");\n  });\n}\n\n// Regression test: DrainingReader with pull that synchronously closes the stream.\n// This exercises the case where the pull callback (triggered by onConsumerWantsData inside\n// consumer->drainingRead()) synchronously closes the controller, causing a deferred state\n// transition. Previously, ValueReadable::drainingRead() had its own beginOperation/endOperation\n// pair that would fire the deferred transition BEFORE the returned promise's .then() callbacks\n// (which capture `this` pointing at the Consumer) had a chance to run, causing use-after-free.\n// The fix moves beginOperation() to before consumer->drainingRead() at the controller level,\n// and endOperation() into the .then() callbacks, so the deferred transition only fires after\n// the Consumer's callbacks have run.\nKJ_TEST(\"DrainingReader: pull that synchronously closes does not UAF (value stream)\") {\n  preamble([](jsg::Lock& js) {\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            // Synchronously close without enqueuing any data.\n            // This triggers close -> onConsumerClose -> doClose -> deferTransitionTo<Closed>\n            // while the drainingRead's pending read is still in flight.\n            c->close(js);\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      bool readCompleted = false;\n      auto promise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        // Stream closed immediately, so we should get done=true.\n        KJ_ASSERT(result.done);\n        readCompleted = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(readCompleted);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader: pull that synchronously closes does not UAF (byte stream)\") {\n  preamble([](jsg::Lock& js) {\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .type = kj::str(\"bytes\"),\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {}\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {\n            c->close(js);\n            return js.resolvedPromise();\n          }\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      bool readCompleted = false;\n      auto promise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_ASSERT(result.done);\n        readCompleted = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(readCompleted);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\n// Test that a pull which synchronously errors the stream doesn't UAF either.\nKJ_TEST(\"DrainingReader: pull that synchronously errors does not UAF (value stream)\") {\n  preamble([](jsg::Lock& js) {\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            c->error(js, js.v8TypeError(\"test error\"_kj));\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      bool readRejected = false;\n      auto promise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_FAIL_ASSERT(\"Should have been rejected\");\n      }, [&](jsg::Lock& js, jsg::Value reason) { readRejected = true; });\n\n      js.runMicrotasks();\n      KJ_ASSERT(readRejected);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\n// Test drainingRead when pull enqueues data then closes on the next pull.\nKJ_TEST(\"DrainingReader: pull enqueues then closes on next pull (value stream)\") {\n  preamble([](jsg::Lock& js) {\n    uint pullCount = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            pullCount++;\n            if (pullCount == 1) {\n              c->enqueue(js, toBytes(js, kj::str(\"data\")));\n            } else {\n              // Second pull: close synchronously without enqueuing.\n              c->close(js);\n            }\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      // First read should get the data.\n      bool firstReadDone = false;\n      auto p1 = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_ASSERT(result.chunks.size() == 1);\n        KJ_ASSERT(kj::str(result.chunks[0].asChars()) == \"data\");\n        KJ_ASSERT(!result.done);\n        firstReadDone = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(firstReadDone);\n\n      // Second read triggers the close.\n      bool secondReadDone = false;\n      auto p2 = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_ASSERT(result.done);\n        secondReadDone = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(secondReadDone);\n\n      reader->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\n// Regression test: DrainingReader with pull that synchronously cancels the controller.\n// When cancel is called on the controller from within the pull callback, the controller's\n// doCancel() transitions its impl state to Closed and destroys the Queue. The QueueImpl\n// destructor detaches consumers (sets queue = kj::none) but does NOT cancel them — the\n// consumer state remains Active. Without the fix, drainingRead falls through to the\n// pending-read path and queues a ReadRequest that will never be fulfilled (promise hangs).\n// The fix detects the detached queue and returns done immediately.\nKJ_TEST(\"DrainingReader: pull that synchronously cancels does not hang (value stream)\") {\n  preamble([](jsg::Lock& js) {\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            // Synchronously cancel without enqueuing any data.\n            auto promise KJ_UNUSED = c->cancel(js, kj::none);\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      bool readCompleted = false;\n      auto promise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        // Stream canceled immediately with no data — should resolve with done=true.\n        KJ_ASSERT(result.done);\n        KJ_ASSERT(result.chunks.size() == 0);\n        readCompleted = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(readCompleted);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader: pull that synchronously cancels does not hang (byte stream)\") {\n  preamble([](jsg::Lock& js) {\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .type = kj::str(\"bytes\"),\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {}\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {\n            auto promise KJ_UNUSED = c->cancel(js, kj::none);\n            return js.resolvedPromise();\n          }\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      bool readCompleted = false;\n      auto promise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_ASSERT(result.done);\n        KJ_ASSERT(result.chunks.size() == 0);\n        readCompleted = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(readCompleted);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\n// Test drainingRead when pull enqueues data then cancels on the next pull.\n// The first read should deliver the enqueued data, and the second read\n// (which triggers the cancel) should resolve with done=true and no data.\nKJ_TEST(\"DrainingReader: pull enqueues then cancels on next pull (value stream)\") {\n  preamble([](jsg::Lock& js) {\n    uint pullCount = 0;\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            pullCount++;\n            if (pullCount == 1) {\n              c->enqueue(js, toBytes(js, kj::str(\"data\")));\n            } else {\n              // Second pull: cancel synchronously without enqueuing.\n              auto promise KJ_UNUSED = c->cancel(js, kj::none);\n            }\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      // First read should get the data.\n      bool firstReadDone = false;\n      auto p1 = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_ASSERT(result.chunks.size() == 1);\n        KJ_ASSERT(kj::str(result.chunks[0].asChars()) == \"data\");\n        KJ_ASSERT(!result.done);\n        firstReadDone = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(firstReadDone);\n\n      // Second read triggers the cancel — should resolve with done=true.\n      bool secondReadDone = false;\n      auto p2 = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_ASSERT(result.done);\n        secondReadDone = true;\n      });\n\n      js.runMicrotasks();\n      KJ_ASSERT(secondReadDone);\n\n      reader->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\n// Test that a pending error applied during wrapDrainingRead's endOperation() propagates\n// as a rejection rather than silently returning data. This exercises the defense-in-depth\n// fix in wrapDrainingRead where endOperation() applies a deferred Errored state.\n//\n// Scenario: Pull enqueues data synchronously but returns a rejected promise. The rejection\n// handler (which errors the stream) runs as a microtask between drainingRead's resolved\n// promise and wrapDrainingRead's .then() callback. The data collected by drainingRead should\n// be discarded because the error was applied during the operation.\nKJ_TEST(\"DrainingReader: pending error in endOperation rejects read (value stream)\") {\n  preamble([](jsg::Lock& js) {\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            // Enqueue data synchronously — drainingRead will collect it.\n            c->enqueue(js, toBytes(js, kj::str(\"should-be-discarded\")));\n            // Return rejected promise — the pull failure handler runs as a microtask\n            // and calls doError(), which defers the error because beginOperation() is\n            // active. When wrapDrainingRead's endOperation() fires, it applies the\n            // pending error and should throw rather than returning the data.\n            return js.rejectedPromise<void>(js.v8TypeError(\"pull failed\"_kj));\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      bool readRejected = false;\n      auto promise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_FAIL_ASSERT(\"Should have rejected, not resolved with data\");\n      }, [&](jsg::Lock& js, jsg::Value&& err) { readRejected = true; });\n\n      js.runMicrotasks();\n      KJ_ASSERT(readRejected, \"Read should have rejected due to pending error\");\n\n      reader->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader: pending error in endOperation rejects read (byte stream)\") {\n  preamble([](jsg::Lock& js) {\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .type = kj::str(\"bytes\"),\n      .pull = [](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {}\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {\n            c->enqueue(js, toBufferSource(js, kj::str(\"should-be-discarded\")));\n            return js.rejectedPromise<void>(js.v8TypeError(\"pull failed\"_kj));\n          }\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      bool readRejected = false;\n      auto promise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_FAIL_ASSERT(\"Should have rejected, not resolved with data\");\n      }, [&](jsg::Lock& js, jsg::Value&& err) { readRejected = true; });\n\n      js.runMicrotasks();\n      KJ_ASSERT(readRejected, \"Read should have rejected due to pending error\");\n\n      reader->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\n// Regression test: After drainingRead returns done=true because the stream was closed,\n// the controller-level close should also be finalized (not deferred until GC).\n//\n// When pull enqueues data AND closes in the same call, the sequence is:\n//   1. c->enqueue() pushes data to consumer buffer\n//   2. c->close() → ConsumerImpl::close() pushes Close marker, calls maybeDrainAndSetState()\n//   3. maybeDrainAndSetState() finds queueTotalSize > 0 (data still in buffer), can't finalize\n//   4. Back in drainingRead, drainBuffer() drains the data and sees the Close marker\n//   5. drainingRead returns done=true, but the consumer is still Active\n//\n// Without the fix, the consumer Close marker is never popped and maybeDrainAndSetState()\n// is never called again, so onConsumerClose never fires and the controller stays open.\n// The fix calls impl.maybeDrainAndSetState(js) after draining when isClosing is true.\nKJ_TEST(\"DrainingReader: controller closes promptly after drainingRead done (value stream)\") {\n  preamble([](jsg::Lock& js) {\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {\n            // Enqueue data and close in the same pull. This causes\n            // ConsumerImpl::close() → maybeDrainAndSetState() to find non-empty\n            // buffer, preventing immediate finalization.\n            c->enqueue(js, toBytes(js, kj::str(\"hello\")));\n            c->close(js);\n            return js.resolvedPromise();\n          }\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {}\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      bool readDone = false;\n      auto promise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        // Should get data with done=true (data + close in same batch).\n        KJ_ASSERT(result.done);\n        KJ_ASSERT(result.chunks.size() == 1);\n        KJ_ASSERT(kj::str(result.chunks[0].asChars()) == \"hello\");\n        readDone = true;\n      });\n      js.runMicrotasks();\n      KJ_ASSERT(readDone);\n\n      // The controller should now be in the Closed state — not deferred.\n      KJ_ASSERT(rs->getController().isClosed(),\n          \"controller should be closed after drainingRead returns done\");\n\n      reader->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\nKJ_TEST(\"DrainingReader: controller closes promptly after drainingRead done (byte stream)\") {\n  preamble([](jsg::Lock& js) {\n    auto rs = js.alloc<ReadableStream>(newReadableStreamJsController());\n    // clang-format off\n    rs->getController().setup(js, UnderlyingSource{\n      .type = kj::str(\"bytes\"),\n      .pull = [&](jsg::Lock& js, UnderlyingSource::Controller controller) {\n        KJ_SWITCH_ONEOF(controller) {\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableStreamDefaultController>) {}\n          KJ_CASE_ONEOF(c, jsg::Ref<ReadableByteStreamController>) {\n            // Enqueue data and close in the same pull.\n            c->enqueue(js, toBufferSource(js, kj::str(\"world\")));\n            c->close(js);\n            return js.resolvedPromise();\n          }\n        }\n        KJ_UNREACHABLE;\n      }\n    }, StreamQueuingStrategy{.highWaterMark = 0});\n    // clang-format on\n\n    KJ_IF_SOME(reader, DrainingReader::create(js, *rs)) {\n      bool readDone = false;\n      auto promise = reader->read(js).then(js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n        KJ_ASSERT(result.done);\n        KJ_ASSERT(result.chunks.size() == 1);\n        KJ_ASSERT(kj::str(result.chunks[0].asChars()) == \"world\");\n        readDone = true;\n      });\n      js.runMicrotasks();\n      KJ_ASSERT(readDone);\n\n      // The controller should now be in the Closed state — not deferred.\n      KJ_ASSERT(rs->getController().isClosed(),\n          \"controller should be closed after drainingRead returns done\");\n\n      reader->releaseLock(js);\n    } else {\n      KJ_FAIL_ASSERT(\"Failed to create DrainingReader\");\n    }\n  });\n}\n\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/standard.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"standard.h\"\n\n#include \"readable.h\"\n#include \"writable.h\"\n\n#include <workerd/io/features.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/util/autogate.h>\n#include <workerd/util/state-machine.h>\n#include <workerd/util/weak-refs.h>\n\n#include <kj/debug.h>\n#include <kj/vector.h>\n\nnamespace workerd::api {\n\nusing DefaultController = jsg::Ref<ReadableStreamDefaultController>;\nusing ByobController = jsg::Ref<ReadableByteStreamController>;\n\nnamespace {\nstruct ValueReadable;\nstruct ByteReadable;\n}  // namespace\n\n// =======================================================================================\n// The Unlocked, Locked, ReaderLocked, and WriterLocked structs\n// are used to track the current lock status of JavaScript-backed streams.\n// All readable and writable streams begin in the Unlocked state. When a\n// reader or writer are attached, the streams will transition into the\n// ReaderLocked or WriterLocked state. When the reader is released, those\n// will transition back to Unlocked.\n//\n// When a readable is piped to a writable, both will enter the PipeLocked state.\n// (PipeLocked is defined within the ReadableLockImpl and WritableLockImpl classes\n// below) When the pipe completes, both will transition back to Unlocked.\n//\n// When a ReadableStreamJsController is tee()'d, it will enter the locked state.\n\nnamespace {\n\n// A utility class used by ReadableStreamJsController\n// for implementing the reader lock in a consistent way (without duplicating any code).\ntemplate <typename Controller>\nclass ReadableLockImpl {\n public:\n  using PipeController = ReadableStreamController::PipeController;\n  using Reader = ReadableStreamController::Reader;\n\n  bool isLockedToReader() const {\n    return !state.template is<Unlocked>();\n  }\n\n  bool lockReader(jsg::Lock& js, Controller& self, Reader& reader);\n\n  // See the comment for releaseReader in common.h for details on the use of maybeJs\n  void releaseReader(Controller& self, Reader& reader, kj::Maybe<jsg::Lock&> maybeJs);\n\n  bool lock();\n\n  void onClose(jsg::Lock& js);\n  void onError(jsg::Lock& js, v8::Local<v8::Value> reason);\n\n  kj::Maybe<PipeController&> tryPipeLock(Controller& self);\n\n  void visitForGc(jsg::GcVisitor& visitor);\n\n  kj::StringPtr jsgGetMemoryName() const {\n    return \"ReadableLockImpl\"_kjc;\n  }\n  size_t jsgGetMemorySelfSize() const {\n    return sizeof(ReadableLockImpl);\n  }\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n    KJ_SWITCH_ONEOF(state) {\n      KJ_CASE_ONEOF(locked, Locked) {}\n      KJ_CASE_ONEOF(unlocked, Unlocked) {}\n      KJ_CASE_ONEOF(pipeLocked, PipeLocked) {}\n      KJ_CASE_ONEOF(readerLocked, ReaderLocked) {\n        tracker.trackField(\"readerLocked\", readerLocked);\n      }\n    }\n  }\n\n private:\n  class PipeLocked final: public PipeController {\n   public:\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"pipe-locked\"_kj;\n    explicit PipeLocked(Controller& inner): inner(inner) {}\n\n    bool isClosed() override {\n      return inner.state.template is<StreamStates::Closed>();\n    }\n\n    kj::Maybe<v8::Local<v8::Value>> tryGetErrored(jsg::Lock& js) override {\n      KJ_IF_SOME(errored, inner.state.template tryGetUnsafe<StreamStates::Errored>()) {\n        return errored.getHandle(js);\n      }\n      return kj::none;\n    }\n\n    void cancel(jsg::Lock& js, v8::Local<v8::Value> reason) override {\n      // Cancel here returns a Promise but we do not need to propagate it.\n      // We can safely drop it on the floor here.\n      auto promise KJ_UNUSED = inner.cancel(js, reason);\n    }\n\n    void close(jsg::Lock& js) override {\n      inner.doClose(js);\n    }\n\n    void error(jsg::Lock& js, v8::Local<v8::Value> reason) override {\n      inner.doError(js, reason);\n    }\n\n    void release(jsg::Lock& js, kj::Maybe<v8::Local<v8::Value>> maybeError = kj::none) override {\n      KJ_IF_SOME(error, maybeError) {\n        cancel(js, error);\n      }\n      inner.lock.state.template transitionTo<Unlocked>();\n    }\n\n    kj::Maybe<kj::Promise<void>> tryPumpTo(WritableStreamSink& sink, bool end) override;\n\n    jsg::Promise<ReadResult> read(jsg::Lock& js) override;\n\n   private:\n    Controller& inner;\n\n    friend Controller;\n  };\n\n  // State machine for ReadableLockImpl:\n  // All states can transition to any other state (no terminal states).\n  //   Unlocked -> Locked (lock() called for tee)\n  //   Unlocked -> ReaderLocked (lockReader() called)\n  //   Unlocked -> PipeLocked (tryPipeLock() called)\n  //   ReaderLocked -> Unlocked (releaseReader() called)\n  //   PipeLocked -> Unlocked (release() or onClose/onError called)\n  //   Locked -> (remains until stream is done)\n  using LockState = StateMachine<Locked, PipeLocked, ReaderLocked, Unlocked>;\n  LockState state = LockState::template create<Unlocked>();\n  friend Controller;\n};\n\n// A utility class used by WritableStreamJsController to implement the writer lock\n// mechanism. Extracted for consistency with ReadableStreamJsController and to\n// eventually allow it to be shared also with WritableStreamInternalController.\ntemplate <typename Controller>\nclass WritableLockImpl {\n public:\n  using Writer = WritableStreamController::Writer;\n\n  bool isLockedToWriter() const;\n\n  bool lockWriter(jsg::Lock& js, Controller& self, Writer& writer);\n\n  // See the comment for releaseWriter in common.h for details on the use of maybeJs\n  void releaseWriter(Controller& self, Writer& writer, kj::Maybe<jsg::Lock&> maybeJs);\n\n  void visitForGc(jsg::GcVisitor& visitor);\n\n  bool pipeLock(WritableStream& owner, jsg::Ref<ReadableStream> source, PipeToOptions& options);\n  void releasePipeLock();\n\n  JSG_MEMORY_INFO(WritableLockImpl) {\n    KJ_SWITCH_ONEOF(state) {\n      KJ_CASE_ONEOF(unlocked, Unlocked) {}\n      KJ_CASE_ONEOF(locked, Locked) {}\n      KJ_CASE_ONEOF(writerLocked, WriterLocked) {\n        tracker.trackField(\"writerLocked\", writerLocked);\n      }\n      KJ_CASE_ONEOF(pipeLocked, PipeLocked) {\n        tracker.trackField(\"pipeLocked\", pipeLocked);\n      }\n    }\n  }\n\n private:\n  struct PipeLocked {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"pipe-locked\"_kj;\n    ReadableStreamController::PipeController& source;\n    jsg::Ref<ReadableStream> readableStreamRef;\n\n    kj::Maybe<jsg::Ref<AbortSignal>> maybeSignal;\n\n    kj::Maybe<jsg::Promise<void>> checkSignal(jsg::Lock& js, Controller& self);\n\n    struct Flags {\n      uint8_t preventAbort : 1 = 0;\n      uint8_t preventCancel : 1 = 0;\n      uint8_t preventClose : 1 = 0;\n      uint8_t pipeThrough : 1 = 0;\n    };\n    Flags flags{};\n\n    JSG_MEMORY_INFO(PipeLocked) {\n      tracker.trackField(\"readableStreamRef\", readableStreamRef);\n      tracker.trackField(\"signal\", maybeSignal);\n    }\n  };\n\n  // State machine for WritableLockImpl:\n  // All states can transition to any other state (no terminal states).\n  //   Unlocked -> Locked (not currently used)\n  //   Unlocked -> WriterLocked (lockWriter() called)\n  //   Unlocked -> PipeLocked (pipeLock() called)\n  //   WriterLocked -> Unlocked (releaseWriter() called)\n  //   PipeLocked -> Unlocked (releasePipeLock() called)\n  using LockState = StateMachine<Unlocked, Locked, WriterLocked, PipeLocked>;\n  LockState state = LockState::template create<Unlocked>();\n\n  inline kj::Maybe<PipeLocked&> tryGetPipe() {\n    KJ_IF_SOME(locked, state.template tryGetUnsafe<PipeLocked>()) {\n      return locked;\n    }\n    return kj::none;\n  }\n\n  friend Controller;\n};\n\n// ======================================================================================\n\ntemplate <typename Controller>\nbool ReadableLockImpl<Controller>::lock() {\n  if (isLockedToReader()) {\n    return false;\n  }\n\n  state.template transitionTo<Locked>();\n  return true;\n}\n\ntemplate <typename Controller>\nbool ReadableLockImpl<Controller>::lockReader(jsg::Lock& js, Controller& self, Reader& reader) {\n  if (isLockedToReader()) {\n    return false;\n  }\n\n  auto prp = js.newPromiseAndResolver<void>();\n  prp.promise.markAsHandled(js);\n\n  auto lock = ReaderLocked(reader, kj::mv(prp.resolver));\n\n  if (self.state.template is<StreamStates::Closed>()) {\n    maybeResolvePromise(js, lock.getClosedFulfiller());\n  } else KJ_IF_SOME(errored, self.state.template tryGetUnsafe<StreamStates::Errored>()) {\n    maybeRejectPromise<void>(js, lock.getClosedFulfiller(), errored.getHandle(js));\n  }\n\n  state.template transitionTo<ReaderLocked>(kj::mv(lock));\n  reader.attach(self, kj::mv(prp.promise));\n  return true;\n}\n\ntemplate <typename Controller>\nvoid ReadableLockImpl<Controller>::releaseReader(\n    Controller& self, Reader& reader, kj::Maybe<jsg::Lock&> maybeJs) {\n  KJ_IF_SOME(locked, state.template tryGetUnsafe<ReaderLocked>()) {\n    KJ_ASSERT(&locked.getReader() == &reader);\n\n    KJ_IF_SOME(js, maybeJs) {\n      auto reason = js.typeError(\"This ReadableStream reader has been released.\"_kj);\n      KJ_SWITCH_ONEOF(self.state) {\n        KJ_CASE_ONEOF(initial, typename Controller::Initial) {}\n        KJ_CASE_ONEOF(closed, StreamStates::Closed) {}\n        KJ_CASE_ONEOF(errored, StreamStates::Errored) {}\n        KJ_CASE_ONEOF(consumer, kj::Own<ValueReadable>) {\n          consumer->cancelPendingReads(js, reason);\n        }\n        KJ_CASE_ONEOF(consumer, kj::Own<ByteReadable>) {\n          consumer->cancelPendingReads(js, reason);\n        }\n      }\n      maybeRejectPromise<void>(js, locked.getClosedFulfiller(), reason);\n    }\n\n    // Keep the locked.clear() after the isolate and hasPendingReadRequests check above.\n    // Clearing will release the references and we don't want to do that if the\n    // hasPendingReadRequests check fails.\n    locked.clear();\n\n    // When maybeJs is nullptr, that means releaseReader was called when the reader is\n    // being deconstructed and not as the result of explicitly calling releaseLock and\n    // we do not have an isolate lock. In that case, we don't want to change the lock\n    // state itself. Moving the lock above will free the lock state while keeping the\n    // ReadableStream marked as locked.\n    if (maybeJs != kj::none) {\n      state.template transitionTo<Unlocked>();\n    }\n  }\n}\n\ntemplate <typename Controller>\nkj::Maybe<ReadableStreamController::PipeController&> ReadableLockImpl<Controller>::tryPipeLock(\n    Controller& self) {\n  if (isLockedToReader()) {\n    return kj::none;\n  }\n  return state.template transitionTo<PipeLocked>(self);\n}\n\ntemplate <typename Controller>\nvoid ReadableLockImpl<Controller>::visitForGc(jsg::GcVisitor& visitor) {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(locked, Locked) {}\n    KJ_CASE_ONEOF(locked, Unlocked) {}\n    KJ_CASE_ONEOF(locked, PipeLocked) {}\n    KJ_CASE_ONEOF(locked, ReaderLocked) {\n      visitor.visit(locked);\n    }\n  }\n}\n\ntemplate <typename Controller>\nvoid ReadableLockImpl<Controller>::onClose(jsg::Lock& js) {\n  KJ_IF_SOME(locked, state.template tryGetUnsafe<ReaderLocked>()) {\n    try {\n      maybeResolvePromise(js, locked.getClosedFulfiller());\n    } catch (jsg::JsExceptionThrown&) {\n      // Resolving the promise could end up throwing an exception in some cases,\n      // causing a jsg::JsExceptionThrown to be thrown. At this point, however,\n      // we are already in the process of closing the stream and an error at this\n      // point is not recoverable. Log and move on.\n      LOG_NOSENTRY(ERROR, \"Error resolving ReadableStream reader closed promise\");\n    };\n  } else {\n    (void)state.template transitionFromTo<PipeLocked, Unlocked>();\n  }\n}\n\ntemplate <typename Controller>\nvoid ReadableLockImpl<Controller>::onError(jsg::Lock& js, v8::Local<v8::Value> reason) {\n  KJ_IF_SOME(locked, state.template tryGetUnsafe<ReaderLocked>()) {\n    try {\n      maybeRejectPromise<void>(js, locked.getClosedFulfiller(), reason);\n    } catch (jsg::JsExceptionThrown&) {\n      // Rejecting the promise could end up throwing an exception in some cases,\n      // causing a jsg::JsExceptionThrown to be thrown. At this point, however,\n      // we are already in the process of closing the stream and an error at this\n      // point is not recoverable. Log and move on.\n      LOG_NOSENTRY(ERROR, \"Error rejecting ReadableStream reader closed promise\");\n    }\n  } else {\n    (void)state.template transitionFromTo<PipeLocked, Unlocked>();\n  }\n}\n\ntemplate <typename Controller>\nkj::Maybe<kj::Promise<void>> ReadableLockImpl<Controller>::PipeLocked::tryPumpTo(\n    WritableStreamSink& sink, bool end) {\n  // We return nullptr here because this controller does not support kj's pumpTo.\n  return kj::none;\n}\n\ntemplate <typename Controller>\njsg::Promise<ReadResult> ReadableLockImpl<Controller>::PipeLocked::read(jsg::Lock& js) {\n  return KJ_ASSERT_NONNULL(inner.read(js, kj::none));\n}\n\n// ======================================================================================\n\ntemplate <typename Controller>\nbool WritableLockImpl<Controller>::isLockedToWriter() const {\n  return !state.template is<Unlocked>();\n}\n\ntemplate <typename Controller>\nbool WritableLockImpl<Controller>::lockWriter(jsg::Lock& js, Controller& self, Writer& writer) {\n  if (isLockedToWriter()) {\n    return false;\n  }\n\n  auto closedPrp = js.newPromiseAndResolver<void>();\n  closedPrp.promise.markAsHandled(js);\n  auto readyPrp = js.newPromiseAndResolver<void>();\n  readyPrp.promise.markAsHandled(js);\n\n  auto lock = WriterLocked(writer, kj::mv(closedPrp.resolver), kj::mv(readyPrp.resolver));\n\n  if (self.state.template is<StreamStates::Closed>()) {\n    maybeResolvePromise(js, lock.getClosedFulfiller());\n    maybeResolvePromise(js, lock.getReadyFulfiller());\n  } else KJ_IF_SOME(errored, self.state.template tryGetUnsafe<StreamStates::Errored>()) {\n    maybeRejectPromise<void>(js, lock.getClosedFulfiller(), errored.getHandle(js));\n    maybeRejectPromise<void>(js, lock.getReadyFulfiller(), errored.getHandle(js));\n  } else {\n    if (FeatureFlags::get(js).getWritableStreamSpecCompliantWriter()) {\n      // Per spec (SetUpWritableStreamDefaultWriter step 4), the ready promise\n      // is resolved when the stream is writable and not experiencing backpressure,\n      // regardless of whether the start algorithm has completed. The backpressure\n      // state is set synchronously during SetUpWritableStreamDefaultController.\n      KJ_IF_SOME(erroring, self.isErroring(js)) {\n        maybeRejectPromise<void>(js, lock.getReadyFulfiller(), erroring);\n      } else if (!self.hasBackpressure()) {\n        maybeResolvePromise(js, lock.getReadyFulfiller());\n      }\n    } else {\n      if (self.isStarted()) {\n        maybeResolvePromise(js, lock.getReadyFulfiller());\n      }\n    }\n  }\n\n  state.template transitionTo<WriterLocked>(kj::mv(lock));\n  writer.attach(js, self, kj::mv(closedPrp.promise), kj::mv(readyPrp.promise));\n  return true;\n}\n\ntemplate <typename Controller>\nvoid WritableLockImpl<Controller>::releaseWriter(\n    Controller& self, Writer& writer, kj::Maybe<jsg::Lock&> maybeJs) {\n  KJ_IF_SOME(locked, state.template tryGetUnsafe<WriterLocked>()) {\n    KJ_ASSERT(&locked.getWriter() == &writer);\n    KJ_IF_SOME(js, maybeJs) {\n      KJ_SWITCH_ONEOF(self.state) {\n        KJ_CASE_ONEOF(initial, typename Controller::Initial) {}\n        KJ_CASE_ONEOF(closed, StreamStates::Closed) {}\n        KJ_CASE_ONEOF(errored, StreamStates::Errored) {}\n        KJ_CASE_ONEOF(controller, jsg::Ref<WritableStreamDefaultController>) {\n          controller->cancelPendingWrites(\n              js, js.typeError(\"This WritableStream writer has been released.\"_kjc));\n        }\n      }\n\n      // Per spec (WritableStreamDefaultWriterRelease), both the ready and closed\n      // promises must be rejected when the writer is released.\n      auto releaseReason = js.v8TypeError(\"This WritableStream writer has been released.\"_kjc);\n      if (FeatureFlags::get(js).getWritableStreamSpecCompliantWriter()) {\n        if (locked.getReadyFulfiller() != kj::none) {\n          maybeRejectPromise<void>(js, locked.getReadyFulfiller(), releaseReason);\n        } else {\n          // The ready fulfiller was already consumed (promise was resolved).\n          // Per spec (WritableStreamDefaultWriterEnsureReadyPromiseRejected),\n          // we must replace it with a new rejected promise.\n          auto prp = js.newPromiseAndResolver<void>();\n          prp.promise.markAsHandled(js);\n          prp.resolver.reject(js, releaseReason);\n          locked.setReadyFulfiller(js, prp);\n        }\n      } else {\n        maybeRejectPromise<void>(js, locked.getReadyFulfiller(), releaseReason);\n      }\n      maybeRejectPromise<void>(js, locked.getClosedFulfiller(), releaseReason);\n    }\n    locked.clear();\n\n    // When maybeJs is nullptr, that means releaseWriter was called when the writer is\n    // being deconstructed and not as the result of explicitly calling releaseLock and\n    // we do not have an isolate lock. In that case, we don't want to change the lock\n    // state itself. Moving the lock above will free the lock state while keeping the\n    // WritableStream marked as locked.\n    if (maybeJs != kj::none) {\n      state.template transitionTo<Unlocked>();\n    }\n  }\n}\n\ntemplate <typename Controller>\nbool WritableLockImpl<Controller>::pipeLock(\n    WritableStream& owner, jsg::Ref<ReadableStream> source, PipeToOptions& options) {\n  if (isLockedToWriter()) {\n    return false;\n  }\n\n  auto& sourceLock = KJ_ASSERT_NONNULL(source->getController().tryPipeLock());\n\n  state.template transitionTo<PipeLocked>(PipeLocked{\n    .source = sourceLock,\n    .readableStreamRef = kj::mv(source),\n    .maybeSignal = kj::mv(options.signal),\n    .flags =\n        {\n          .preventAbort = options.preventAbort.orDefault(false),\n          .preventCancel = options.preventCancel.orDefault(false),\n          .preventClose = options.preventClose.orDefault(false),\n          .pipeThrough = options.pipeThrough,\n        },\n  });\n  return true;\n}\n\ntemplate <typename Controller>\nvoid WritableLockImpl<Controller>::releasePipeLock() {\n  if (state.template is<PipeLocked>()) {\n    state.template transitionTo<Unlocked>();\n  }\n}\n\ntemplate <typename Controller>\nvoid WritableLockImpl<Controller>::visitForGc(jsg::GcVisitor& visitor) {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(locked, Unlocked) {}\n    KJ_CASE_ONEOF(locked, Locked) {}\n    KJ_CASE_ONEOF(locked, WriterLocked) {\n      visitor.visit(locked);\n    }\n    KJ_CASE_ONEOF(locked, PipeLocked) {\n      visitor.visit(locked.readableStreamRef);\n      KJ_IF_SOME(signal, locked.maybeSignal) {\n        visitor.visit(signal);\n      }\n    }\n  }\n}\n\ntemplate <typename Controller>\nkj::Maybe<jsg::Promise<void>> WritableLockImpl<Controller>::PipeLocked::checkSignal(\n    jsg::Lock& js, Controller& self) {\n  KJ_IF_SOME(signal, maybeSignal) {\n    if (signal->getAborted(js)) {\n      auto reason = signal->getReason(js);\n      if (!flags.preventCancel) {\n        source.release(js, v8::Local<v8::Value>(reason));\n      } else {\n        source.release(js);\n      }\n      if (!flags.preventAbort) {\n        return self.abort(js, reason).then(js, JSG_VISITABLE_LAMBDA((this, reason = reason.addRef(js), ref = self.addRef()), (reason, ref), (jsg::Lock& js) {\n          return rejectedMaybeHandledPromise<void>(js, reason.getHandle(js), flags.pipeThrough);\n        }));\n      }\n      return rejectedMaybeHandledPromise<void>(js, reason, flags.pipeThrough);\n    }\n  }\n  return kj::none;\n}\n\nauto maybeAddFunctor(jsg::Lock& js, auto promise, auto onSuccess, auto onFailure) {\n  KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n    return promise.then(\n        js, ioContext.addFunctor(kj::mv(onSuccess)), ioContext.addFunctor(kj::mv(onFailure)));\n  } else {\n    return promise.then(js, kj::mv(onSuccess), kj::mv(onFailure));\n  }\n}\n\njsg::Promise<void> maybeRunAlgorithm(\n    jsg::Lock& js, auto& maybeAlgorithm, auto&& onSuccess, auto&& onFailure, auto&&... args) {\n  // The algorithm is a JavaScript function mapped through jsg::Function.\n  // It is expected to return a Promise mapped via jsg::Promise. If the\n  // function returns synchronously, the jsg::Promise wrapper ensures\n  // that it is properly mapped to a jsg::Promise, but if the Promise\n  // throws synchronously, we have to convert that synchronous throw\n  // into a proper rejected jsg::Promise.\n  KJ_IF_SOME(algorithm, maybeAlgorithm) {\n    // We need two layers of JSG_TRY here, unfortunately. The inner layer\n    // covers the algorithm implementation itself and is our typical error\n    // handling path. It ensures that if the algorithm throws an exception,\n    // that is properly converted in to a rejected promise that is *then*\n    // handled by the onFailure handler that is passed in. The outer JSG_TRY\n    // handles the rare and generally unexpected failure of the calls to\n    // .then() itself, which can throw JS exceptions synchronously in certain\n    // rare cases. For those we return a rejected promise but do not call the\n    // onFailure case since such errors are generally indicative of a fatal\n    // condition in the isolate (e.g. out of memory, other fatal exception, etc).\n    JSG_TRY(js) {\n      KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n        auto getInnerPromise = [&]() -> jsg::Promise<void> {\n          JSG_TRY(js) {\n            return algorithm(js, kj::fwd<decltype(args)>(args)...);\n          }\n          JSG_CATCH(exception) {\n            return js.rejectedPromise<void>(kj::mv(exception));\n          }\n        };\n        return getInnerPromise().then(\n            js, ioContext.addFunctor(kj::mv(onSuccess)), ioContext.addFunctor(kj::mv(onFailure)));\n      } else {\n        auto getInnerPromise = [&]() -> jsg::Promise<void> {\n          JSG_TRY(js) {\n            return algorithm(js, kj::fwd<decltype(args)>(args)...);\n          }\n          JSG_CATCH(exception) {\n            return js.rejectedPromise<void>(kj::mv(exception));\n          }\n        };\n        return getInnerPromise().then(js, kj::mv(onSuccess), kj::mv(onFailure));\n      }\n    }\n    JSG_CATCH(exception) {\n      return js.rejectedPromise<void>(kj::mv(exception));\n    }\n  }\n\n  // If the algorithm does not exist, we just handle it as a success and move on.\n  onSuccess(js);\n  return js.resolvedPromise();\n}\n\njsg::Promise<void> maybeRunAlgorithmAsync(\n    jsg::Lock& js, auto& maybeAlgorithm, auto&& onSuccess, auto&& onFailure, auto&&... args) {\n  // The algorithm is a JavaScript function mapped through jsg::Function.\n  // It is expected to return a Promise mapped via jsg::Promise. If the\n  // function returns synchronously, the jsg::Promise wrapper ensures\n  // that it is properly mapped to a jsg::Promise, but if the Promise\n  // throws synchronously, we have to convert that synchronous throw\n  // into a proper rejected jsg::Promise.\n  KJ_IF_SOME(algorithm, maybeAlgorithm) {\n    // We need two layers of tryCatch here, unfortunately. The inner layer\n    // covers the algorithm implementation itself and is our typical error\n    // handling path. It ensures that if the algorithm throws an exception,\n    // that is properly converted in to a rejected promise that is *then*\n    // handled by the onFailure handler that is passed in. The outer tryCatch\n    // handles the rare and generally unexpected failure of the calls to\n    // .then() itself, which can throw JS exceptions synchronously in certain\n    // rare cases. For those we return a rejected promise but do not call the\n    // onFailure case since such errors are generally indicative of a fatal\n    // condition in the isolate (e.g. out of memory, other fatal exception, etc).\n    return js.tryCatch([&] {\n      KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n        return js\n            .tryCatch([&] { return algorithm(js, kj::fwd<decltype(args)>(args)...); },\n                [&](jsg::Value&& exception) { return js.rejectedPromise<void>(kj::mv(exception)); })\n            .then(js, ioContext.addFunctor(kj::mv(onSuccess)),\n                ioContext.addFunctor(kj::mv(onFailure)));\n      } else {\n        return js\n            .tryCatch([&] { return algorithm(js, kj::fwd<decltype(args)>(args)...); },\n                [&](jsg::Value&& exception) {\n          return js.rejectedPromise<void>(kj::mv(exception));\n        }).then(js, kj::mv(onSuccess), kj::mv(onFailure));\n      }\n    }, [&](jsg::Value&& exception) { return js.rejectedPromise<void>(kj::mv(exception)); });\n  }\n\n  // If the algorithm does not exist, we handle it as a success but ensure\n  // it runs asynchronously by scheduling via a resolved promise.\n  KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n    return js.resolvedPromise().then(js, ioContext.addFunctor(kj::mv(onSuccess)));\n  } else {\n    return js.resolvedPromise().then(js, kj::mv(onSuccess));\n  }\n}\n\nint getHighWaterMark(\n    const UnderlyingSource& underlyingSource, const StreamQueuingStrategy& queuingStrategy) {\n  bool isBytes = underlyingSource.type.map([](auto& s) { return s == \"bytes\"; }).orDefault(false);\n  return queuingStrategy.highWaterMark.orDefault(isBytes ? 0 : 1);\n}\n\n}  // namespace\n\n// It is possible for the controller state to be released synchronously while\n// we are in the middle of a read. When that happens we need to defer the actual\n// close/error state change until the read call is complete. deferControllerStateChange\n// handles this for us by using the state machine's operation tracking to defer\n// pending close/error transitions until the read is complete.\ntemplate <typename Controller>\njsg::Promise<ReadResult> deferControllerStateChange(jsg::Lock& js,\n    Controller& controller,\n    kj::FunctionParam<jsg::Promise<ReadResult>()> readCallback) {\n  bool endOperation = true;\n  // The readCallback and the controller.doClose(..) and controller.doError(...)\n  // methods, as well as the methods can trigger JavaScript errors to be thrown\n  // synchronously in some cases. We want to make sure non-fatal errors cause the\n  // stream to error and only fatal cases bubble up.\n  return js.tryCatch([&] {\n    controller.state.beginOperation();\n    auto result = readCallback();\n    endOperation = false;\n\n    // endOperation() will automatically apply any pending state if this was the last operation.\n    // Returns true if a pending state was applied.\n    if (controller.state.endOperation()) {\n      // A pending state was applied. Call the appropriate callback.\n      // Skip callbacks if execution is being terminated (e.g., CPU time limit) since we can't\n      // safely execute JavaScript in that state.\n      if (!js.v8Isolate->IsExecutionTerminating()) {\n        if (controller.state.template is<StreamStates::Closed>()) {\n          controller.lock.onClose(js);\n        } else if (controller.state.template is<StreamStates::Errored>()) {\n          KJ_IF_SOME(err, controller.state.template tryGetUnsafe<StreamStates::Errored>()) {\n            controller.lock.onError(js, err.getHandle(js));\n          }\n        }\n      }\n    }\n\n    return kj::mv(result);\n  }, [&](jsg::Value exception) -> jsg::Promise<ReadResult> {\n    if (endOperation) {\n      // Clear any pending state since we're erroring\n      controller.state.clearPendingState();\n      (void)controller.state.endOperation();\n    }\n    controller.doError(js, exception.getHandle(js));\n    return js.rejectedPromise<ReadResult>(kj::mv(exception));\n  });\n}\n\n// The ReadableStreamJsController provides the implementation of custom\n// ReadableStreams backed by a user-code provided Underlying Source. The implementation\n// is fairly complicated and defined entirely by the streams specification.\n//\n// Another important thing to understand is that there are two types of JavaScript\n// backed ReadableStreams: value-oriented, and byte-oriented.\n//\n// When user code uses the `new ReadableStream(underlyingSource)` constructor, the\n// underlyingSource argument may have a `type` property, the value of which is either\n// `undefined`, the empty string, or the string value `'bytes'`. If the underlyingSource\n// argument is not given, the default value of `type` is `undefined`. If `type` is\n// `undefined` or the empty string, the ReadableStream is value-oriented. If `type` is\n// exactly equal to `'bytes'`, the ReadableStream is byte-oriented.\n//\n// For value-oriented streams, any JavaScript value can be pushed through the stream,\n// and the stream will only support use of the ReadableStreamDefaultReader to consume\n// the stream data.\n//\n// For byte-oriented streams, only byte data (as provided by `ArrayBufferView`s) can\n// be pushed through the stream. All byte-oriented streams support using both\n// ReadableStreamDefaultReader and ReadableStreamBYOBReader to consume the stream\n// data.\n//\n// When the ReadableStreamJsController::setup() method is called the type\n// of stream is determined, and the controller will create an instance of either\n// jsg::Ref<ReadableStreamDefaultController> or jsg::Ref<ReadableByteStreamController>.\n// These are the objects that are actually passed on to the user-code's Underlying Source\n// implementation.\nclass ReadableStreamJsController final: public ReadableStreamController {\n public:\n  using ReadableLockImpl = ReadableLockImpl<ReadableStreamJsController>;\n\n  KJ_DISALLOW_COPY_AND_MOVE(ReadableStreamJsController);\n\n  explicit ReadableStreamJsController();\n  explicit ReadableStreamJsController(StreamStates::Closed closed);\n  explicit ReadableStreamJsController(StreamStates::Errored errored);\n  explicit ReadableStreamJsController(jsg::Lock& js, ValueReadable& consumer);\n  explicit ReadableStreamJsController(jsg::Lock& js, ByteReadable& consumer);\n\n  jsg::Ref<ReadableStream> addRef() override;\n\n  void setup(jsg::Lock& js,\n      jsg::Optional<UnderlyingSource> maybeUnderlyingSource,\n      jsg::Optional<StreamQueuingStrategy> maybeQueuingStrategy) override;\n\n  // Signals that this ReadableStream is no longer interested in the underlying\n  // data source. Whether this cancels the underlying data source also depends\n  // on whether or not there are other ReadableStreams still attached to it.\n  // This operation is terminal. Once called, even while the returned Promise\n  // is still pending, the ReadableStream will be no longer usable and any\n  // data still in the queue will be dropped. Pending read requests will be\n  // rejected if a reason is given, or resolved with no data otherwise.\n  jsg::Promise<void> cancel(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> reason) override;\n\n  void doClose(jsg::Lock& js);\n\n  void doError(jsg::Lock& js, v8::Local<v8::Value> reason);\n\n  bool canCloseOrEnqueue();\n  bool hasBackpressure();\n\n  bool isByteOriented() const override;\n\n  bool isDisturbed() override;\n\n  bool isClosedOrErrored() const override;\n\n  bool isClosed() const override;\n\n  bool isLockedToReader() const override;\n\n  bool lockReader(jsg::Lock& js, Reader& reader) override;\n\n  kj::Maybe<v8::Local<v8::Value>> isErrored(jsg::Lock& js);\n\n  kj::Maybe<int> getDesiredSize();\n\n  jsg::Promise<void> pipeTo(\n      jsg::Lock& js, WritableStreamController& destination, PipeToOptions options) override;\n\n  kj::Promise<DeferredProxy<void>> pumpTo(\n      jsg::Lock& js, kj::Own<WritableStreamSink>, bool end) override;\n\n  kj::Maybe<jsg::Promise<ReadResult>> read(\n      jsg::Lock& js, kj::Maybe<ByobOptions> byobOptions) override;\n\n  kj::Maybe<jsg::Promise<DrainingReadResult>> drainingRead(\n      jsg::Lock& js, size_t maxRead = kj::maxValue) override;\n\n  // See the comment for releaseReader in common.h for details on the use of maybeJs\n  void releaseReader(Reader& reader, kj::Maybe<jsg::Lock&> maybeJs) override;\n\n  void setOwnerRef(ReadableStream& stream) override;\n\n  Tee tee(jsg::Lock& js) override;\n\n  kj::Maybe<PipeController&> tryPipeLock() override;\n\n  void visitForGc(jsg::GcVisitor& visitor) override;\n\n  kj::Maybe<kj::OneOf<DefaultController, ByobController>> getController();\n\n  jsg::Promise<jsg::BufferSource> readAllBytes(jsg::Lock& js, uint64_t limit) override;\n  jsg::Promise<kj::String> readAllText(jsg::Lock& js, uint64_t limit) override;\n\n  kj::Maybe<uint64_t> tryGetLength(StreamEncoding encoding) override;\n\n  kj::Own<ReadableStreamController> detach(jsg::Lock& js, bool ignoreDisturbed) override;\n\n  void setPendingClosure() override {\n    KJ_UNIMPLEMENTED(\"only implemented for WritableStreamInternalController\");\n  }\n\n  kj::StringPtr jsgGetMemoryName() const override;\n  size_t jsgGetMemorySelfSize() const override;\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override;\n\n private:\n  // If the stream was created within the scope of a request, we want to treat it as I/O\n  // and make sure it is not advanced from the scope of a different request.\n  kj::Maybe<IoContext&> ioContext;\n  kj::Maybe<ReadableStream&> owner;\n\n  // Initial state before setup() is called.\n  struct Initial {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"initial\"_kj;\n  };\n\n  // State machine for ReadableStreamJsController:\n  // Initial is the default state before setup() is called\n  // ValueReadable and ByteReadable are the active states (stream has data)\n  // Closed and Errored are terminal states (stream is done)\n  //   Initial -> ValueReadable or ByteReadable (setup() called)\n  //   Initial -> Closed (constructed with Closed)\n  //   Initial -> Errored (constructed with Errored)\n  //   ValueReadable -> Closed (doClose() or cancel() called)\n  //   ValueReadable -> Errored (doError() called)\n  //   ByteReadable -> Closed (doClose() or cancel() called)\n  //   ByteReadable -> Errored (doError() called)\n  // Note: No single ActiveState since there are two active variants.\n  // PendingStates allows Closed/Errored transitions to be deferred during reads.\n  using State = StateMachine<TerminalStates<StreamStates::Closed>,\n      ErrorState<StreamStates::Errored>,\n      PendingStates<StreamStates::Closed, StreamStates::Errored>,\n      Initial,\n      StreamStates::Closed,\n      StreamStates::Errored,\n      kj::Own<ValueReadable>,\n      kj::Own<ByteReadable>>;\n  State state = State::create<Initial>();\n\n  kj::Maybe<uint64_t> expectedLength = kj::none;\n  bool canceling = false;\n\n  // The lock state is separate because a closed or errored stream can still be locked.\n  ReadableLockImpl lock;\n\n  bool disturbed = false;\n\n  template <typename T>\n  jsg::Promise<T> readAll(jsg::Lock& js, uint64_t limit);\n\n  friend ReadableLockImpl;\n  friend ReadableLockImpl::PipeLocked;\n  friend struct ValueReadable;\n  friend struct ByteReadable;\n\n  template <typename Controller>\n  friend jsg::Promise<ReadResult> deferControllerStateChange(jsg::Lock& js,\n      Controller& controller,\n      kj::FunctionParam<jsg::Promise<ReadResult>()> readCallback);\n};\n\n// The WritableStreamJsController provides the implementation of custom\n// WritableStream's backed by a user-code provided Underlying Sink. The implementation\n// is fairly complicated and defined entirely by the streams specification.\nclass WritableStreamJsController final: public WritableStreamController {\n public:\n  using WritableLockImpl = WritableLockImpl<WritableStreamJsController>;\n\n  using Controller = jsg::Ref<WritableStreamDefaultController>;\n\n  explicit WritableStreamJsController();\n\n  explicit WritableStreamJsController(StreamStates::Closed closed);\n\n  explicit WritableStreamJsController(StreamStates::Errored errored);\n\n  ~WritableStreamJsController() noexcept(false);\n\n  KJ_DISALLOW_COPY_AND_MOVE(WritableStreamJsController);\n\n  jsg::Promise<void> abort(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> reason) override;\n\n  jsg::Ref<WritableStream> addRef() override;\n\n  jsg::Promise<void> close(jsg::Lock& js, bool markAsHandled = false) override;\n\n  jsg::Promise<void> flush(jsg::Lock& js, bool markAsHandled = false) override {\n    KJ_UNIMPLEMENTED(\"expected WritableStreamInternalController implementation to be enough\");\n  }\n\n  void doClose(jsg::Lock& js);\n\n  void doError(jsg::Lock& js, v8::Local<v8::Value> reason);\n\n  // Error through the underlying controller if available, going through the proper\n  // error transition (Erroring -> Errored).\n  void errorIfNeeded(jsg::Lock& js, v8::Local<v8::Value> reason);\n\n  kj::Maybe<int> getDesiredSize() override;\n\n  kj::Maybe<v8::Local<v8::Value>> isErroring(jsg::Lock& js) override;\n  kj::Maybe<v8::Local<v8::Value>> isErroredOrErroring(jsg::Lock& js);\n\n  bool isLocked() const;\n\n  bool isLockedToWriter() const override;\n\n  bool isStarted();\n\n  bool hasBackpressure();\n\n  inline bool isWritable() const {\n    return state.isActive();\n  }\n\n  bool lockWriter(jsg::Lock& js, Writer& writer) override;\n\n  void maybeRejectReadyPromise(jsg::Lock& js, v8::Local<v8::Value> reason);\n\n  void maybeResolveReadyPromise(jsg::Lock& js);\n\n  // See the comment for releaseWriter in common.h for details on the use of maybeJs\n  void releaseWriter(Writer& writer, kj::Maybe<jsg::Lock&> maybeJs) override;\n\n  kj::Maybe<kj::Own<WritableStreamSink>> removeSink(jsg::Lock& js) override;\n  void detach(jsg::Lock& js) override;\n\n  void setOwnerRef(WritableStream& stream) override;\n\n  void setup(jsg::Lock& js,\n      jsg::Optional<UnderlyingSink> maybeUnderlyingSink,\n      jsg::Optional<StreamQueuingStrategy> maybeQueuingStrategy) override;\n\n  kj::Maybe<jsg::Promise<void>> tryPipeFrom(\n      jsg::Lock& js, jsg::Ref<ReadableStream> source, PipeToOptions options) override;\n\n  void updateBackpressure(jsg::Lock& js, bool backpressure);\n\n  jsg::Promise<void> write(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> value) override;\n\n  void visitForGc(jsg::GcVisitor& visitor) override;\n\n  bool isClosedOrClosing() override;\n  bool isErrored() override;\n\n  inline bool isByteOriented() const override {\n    return false;\n  }\n\n  void setPendingClosure() override {\n    KJ_UNIMPLEMENTED(\"only implemented for WritableStreamInternalController\");\n  }\n\n  kj::StringPtr jsgGetMemoryName() const override;\n  size_t jsgGetMemorySelfSize() const override;\n  void jsgGetMemoryInfo(jsg::MemoryTracker& info) const override;\n\n private:\n  jsg::Promise<void> pipeLoop(jsg::Lock& js);\n\n  kj::Maybe<IoContext&> ioContext;\n  kj::Maybe<WritableStream&> owner;\n\n  // Initial state before setup() is called.\n  struct Initial {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"initial\"_kj;\n  };\n\n  // State machine for WritableStreamJsController:\n  // Initial is the default state before setup() is called\n  // Controller is the active state (stream is writable)\n  // Closed is terminal, Errored is implicitly terminal via ErrorState\n  using State = StateMachine<TerminalStates<StreamStates::Closed>,\n      ErrorState<StreamStates::Errored>,\n      ActiveState<Controller>,\n      Initial,\n      StreamStates::Closed,\n      StreamStates::Errored,\n      Controller>;\n  State state = State::create<Initial>();\n\n  WritableLockImpl lock;\n  kj::Maybe<jsg::Promise<void>> maybeAbortPromise;\n\n  friend WritableLockImpl;\n};\n\nkj::Own<ReadableStreamController> newReadableStreamJsController() {\n  return kj::heap<ReadableStreamJsController>();\n}\n\nkj::Own<WritableStreamController> newWritableStreamJsController() {\n  return kj::heap<WritableStreamJsController>();\n}\n\ntemplate <typename Self>\nReadableImpl<Self>::ReadableImpl(\n    UnderlyingSource underlyingSource, StreamQueuingStrategy queuingStrategy)\n    : state(State::template create<Queue>(getHighWaterMark(underlyingSource, queuingStrategy))),\n      algorithms(kj::mv(underlyingSource), kj::mv(queuingStrategy)) {}\n\ntemplate <typename Self>\nvoid ReadableImpl<Self>::start(jsg::Lock& js, jsg::Ref<Self> self) {\n  KJ_ASSERT(!flags.started && !flags.starting);\n  flags.starting = true;\n\n  // Per the streams spec, the size function should be called with `undefined` as `this`,\n  // not as a method on the strategy object.\n  KJ_IF_SOME(sizeFunc, algorithms.size) {\n    sizeFunc.setReceiver(jsg::Value(js.v8Isolate, js.v8Undefined()));\n  }\n\n  auto onSuccess = JSG_VISITABLE_LAMBDA((this, self = self.addRef()), (self), (jsg::Lock& js) {\n    flags.started = true;\n    flags.starting = false;\n    pullIfNeeded(js, kj::mv(self));\n  });\n\n  auto onFailure = JSG_VISITABLE_LAMBDA(\n      (this, self = self.addRef()), (self), (jsg::Lock& js, jsg::Value reason) {\n        flags.started = true;\n        flags.starting = false;\n        doError(js, kj::mv(reason));\n      });\n\n  maybeRunAlgorithm(js, algorithms.start, kj::mv(onSuccess), kj::mv(onFailure), kj::mv(self));\n  algorithms.start = kj::none;\n}\n\ntemplate <typename Self>\nsize_t ReadableImpl<Self>::consumerCount() {\n  return state.whenActiveOr([](Queue& q) { return q.getConsumerCount(); }, size_t{0});\n}\n\ntemplate <typename Self>\njsg::Promise<void> ReadableImpl<Self>::cancel(\n    jsg::Lock& js, jsg::Ref<Self> self, v8::Local<v8::Value> reason) {\n  if (state.template is<StreamStates::Closed>()) {\n    // We are already closed. There's nothing to cancel.\n    // This shouldn't happen but we handle the case anyway, just to be safe.\n    return js.resolvedPromise();\n  }\n  KJ_IF_SOME(errored, state.template tryGetUnsafe<StreamStates::Errored>()) {\n    // We are already errored. There's nothing to cancel.\n    // This shouldn't happen but we handle the case anyway, just to be safe.\n    return js.rejectedPromise<void>(errored.getHandle(js));\n  }\n\n  auto& queue = state.template getUnsafe<Queue>();\n  size_t consumerCount = queue.getConsumerCount();\n  if (consumerCount > 1) {\n    // If there is more than 1 consumer, then we just return here with an\n    // immediately resolved promise. The consumer will remove itself,\n    // canceling its interest in the underlying source but we do not yet\n    // want to cancel the underlying source since there are still other\n    // consumers that want data.\n    return js.resolvedPromise();\n  }\n\n  // Otherwise, there should be exactly one consumer at this point.\n  KJ_ASSERT(consumerCount == 1);\n  KJ_IF_SOME(pendingCancel, maybePendingCancel) {\n    // If we're already waiting for cancel to complete, just return the\n    // already existing pending promise.\n    // This shouldn't happen but we handle the case anyway, just to be safe.\n    return pendingCancel.promise.whenResolved(js);\n  }\n\n  auto prp = js.newPromiseAndResolver<void>();\n  maybePendingCancel = PendingCancel{\n    .fulfiller = kj::mv(prp.resolver),\n    .promise = kj::mv(prp.promise),\n  };\n  auto promise = KJ_ASSERT_NONNULL(maybePendingCancel).promise.whenResolved(js);\n  doCancel(js, kj::mv(self), reason);\n  return kj::mv(promise);\n}\n\ntemplate <typename Self>\nbool ReadableImpl<Self>::canCloseOrEnqueue() {\n  return state.isActive();\n}\n\n// doCancel() is triggered by cancel() being called, which is an explicit signal from\n// the ReadableStream that we don't care about the data this controller provides any\n// more. We don't need to notify the consumers because we presume they already know\n// that they called cancel. What we do want to do here, tho, is close the implementation\n// and trigger the cancel algorithm.\ntemplate <typename Self>\nvoid ReadableImpl<Self>::doCancel(jsg::Lock& js, jsg::Ref<Self> self, v8::Local<v8::Value> reason) {\n  state.template transitionTo<StreamStates::Closed>();\n\n  auto onSuccess = JSG_VISITABLE_LAMBDA((this, self = self.addRef()), (self), (jsg::Lock& js) {\n    doClose(js);\n    KJ_IF_SOME(pendingCancel, maybePendingCancel) {\n    maybeResolvePromise(js, pendingCancel.fulfiller);\n    } else {\n    // Else block to avert dangling else compiler warning.\n    }\n  });\n  auto onFailure = JSG_VISITABLE_LAMBDA(\n      (this, self = self.addRef()), (self), (jsg::Lock& js, jsg::Value reason) {\n        // We do not call doError() here because there's really no point. Everything\n        // that cares about the state of this controller impl has signaled that it\n        // no longer cares and has gone away.\n        doClose(js);\n        KJ_IF_SOME(pendingCancel, maybePendingCancel) {\n        maybeRejectPromise<void>(js, pendingCancel.fulfiller, reason.getHandle(js));\n        } else {\n        // Else block to avert dangling else compiler warning.\n        }\n      });\n\n  maybeRunAlgorithm(js, algorithms.cancel, kj::mv(onSuccess), kj::mv(onFailure), reason);\n}\n\ntemplate <typename Self>\nvoid ReadableImpl<Self>::enqueue(jsg::Lock& js, kj::Rc<Entry> entry, jsg::Ref<Self> self) {\n  JSG_REQUIRE(canCloseOrEnqueue(), TypeError, \"This ReadableStream is closed.\");\n  KJ_DEFER(pullIfNeeded(js, kj::mv(self)));\n  auto& queue = state.template getUnsafe<Queue>();\n  queue.push(js, kj::mv(entry));\n}\n\ntemplate <typename Self>\nvoid ReadableImpl<Self>::close(jsg::Lock& js) {\n  JSG_REQUIRE(canCloseOrEnqueue(), TypeError, \"This ReadableStream is closed.\");\n  auto& queue = state.template getUnsafe<Queue>();\n\n  if (queue.hasPartiallyFulfilledRead()) {\n    auto error =\n        js.v8Ref(js.v8TypeError(\"This ReadableStream was closed with a partial read pending.\"));\n    doError(js, error.addRef(js));\n    js.throwException(kj::mv(error));\n    return;\n  }\n\n  queue.close(js);\n\n  state.template transitionTo<StreamStates::Closed>();\n  doClose(js);\n}\n\ntemplate <typename Self>\nvoid ReadableImpl<Self>::doClose(jsg::Lock& js) {\n  // The state should have already been set to closed.\n  KJ_ASSERT(state.template is<StreamStates::Closed>());\n  algorithms.clear();\n}\n\ntemplate <typename Self>\nvoid ReadableImpl<Self>::doError(jsg::Lock& js, jsg::Value reason) {\n  // If already closed or errored, do nothing\n  if (state.isInactive()) {\n    return;\n  }\n\n  auto& queue = state.template getUnsafe<Queue>();\n  queue.error(js, reason.addRef(js));\n  state.template transitionTo<StreamStates::Errored>(kj::mv(reason));\n  algorithms.clear();\n}\n\ntemplate <typename Self>\nkj::Maybe<int> ReadableImpl<Self>::getDesiredSize() {\n  if (state.template is<StreamStates::Closed>()) {\n    return 0;\n  }\n  if (state.template is<StreamStates::Errored>()) {\n    return kj::none;\n  }\n  return state.template getUnsafe<Queue>().desiredSize();\n}\n\n// We should call pull if any of the consumers known to the queue have read requests or\n// we haven't yet signalled backpressure.\ntemplate <typename Self>\nbool ReadableImpl<Self>::shouldCallPull() {\n  return state.whenActiveOr(\n      [this](Queue& q) { return q.wantsRead() || getDesiredSize().orDefault(0) > 0; }, false);\n}\n\ntemplate <typename Self>\nvoid ReadableImpl<Self>::pullIfNeeded(jsg::Lock& js, jsg::Ref<Self> self) {\n  // Determining if we need to pull is fairly complicated. All of the following\n  // must hold true:\n  if (!shouldCallPull()) {\n    return;\n  }\n\n  if (flags.pulling) {\n    flags.pullAgain = true;\n    return;\n  }\n  KJ_ASSERT(!flags.pullAgain);\n  flags.pulling = true;\n\n  auto onSuccess = JSG_VISITABLE_LAMBDA((this, self = self.addRef()), (self), (jsg::Lock& js) {\n    flags.pulling = false;\n    if (flags.pullAgain) {\n    flags.pullAgain = false;\n    pullIfNeeded(js, kj::mv(self));\n    }\n  });\n\n  auto onFailure = JSG_VISITABLE_LAMBDA(\n      (this, self = self.addRef()), (self), (jsg::Lock& js, jsg::Value reason) {\n        flags.pulling = false;\n        doError(js, kj::mv(reason));\n      });\n\n  maybeRunAlgorithm(js, algorithms.pull, kj::mv(onSuccess), kj::mv(onFailure), self.addRef());\n}\n\ntemplate <typename Self>\nvoid ReadableImpl<Self>::forcePullIfNeeded(jsg::Lock& js, jsg::Ref<Self> self) {\n  // Like pullIfNeeded but bypasses the shouldCallPull() check. Used for draining reads\n  // which need to pull all available data regardless of backpressure settings.\n  if (!canCloseOrEnqueue()) {\n    return;\n  }\n\n  if (flags.pulling) {\n    flags.pullAgain = true;\n    return;\n  }\n  KJ_ASSERT(!flags.pullAgain);\n  flags.pulling = true;\n\n  auto onSuccess = JSG_VISITABLE_LAMBDA((this, self = self.addRef()), (self), (jsg::Lock& js) {\n    flags.pulling = false;\n    if (flags.pullAgain) {\n    flags.pullAgain = false;\n    // After a force pull, we go back to normal pullIfNeeded behavior.\n    pullIfNeeded(js, kj::mv(self));\n    }\n  });\n\n  auto onFailure = JSG_VISITABLE_LAMBDA(\n      (this, self = self.addRef()), (self), (jsg::Lock& js, jsg::Value reason) {\n        flags.pulling = false;\n        doError(js, kj::mv(reason));\n      });\n\n  maybeRunAlgorithm(js, algorithms.pull, kj::mv(onSuccess), kj::mv(onFailure), self.addRef());\n}\n\ntemplate <typename Self>\nvoid ReadableImpl<Self>::visitForGc(jsg::GcVisitor& visitor) {\n  state.visitForGc(visitor);\n  KJ_IF_SOME(pendingCancel, maybePendingCancel) {\n    visitor.visit(pendingCancel.fulfiller, pendingCancel.promise);\n  }\n  visitor.visit(algorithms);\n}\n\ntemplate <typename Self>\nkj::Own<typename ReadableImpl<Self>::Consumer> ReadableImpl<Self>::getConsumer(\n    kj::Maybe<ReadableImpl<Self>::StateListener&> listener) {\n  auto& queue = state.template getUnsafe<Queue>();\n  return kj::heap<typename ReadableImpl<Self>::Consumer>(queue, listener);\n}\n\n// ======================================================================================\n\ntemplate <typename Self>\nWritableImpl<Self>::WritableImpl(\n    jsg::Lock& js, WritableStream& owner, jsg::Ref<AbortSignal> abortSignal)\n    : owner(owner.addWeakRef()),\n      signal(kj::mv(abortSignal)) {\n  flags.pedanticWpt = FeatureFlags::get(js).getPedanticWpt();\n}\n\ntemplate <typename Self>\njsg::Promise<void> WritableImpl<Self>::abort(\n    jsg::Lock& js, jsg::Ref<Self> self, v8::Local<v8::Value> reason) {\n  // Per the spec, the signal.reason should be a DOMException with name 'AbortError'\n  // when no reason is provided, but the stored error should remain as the original reason.\n  auto signalReason = [&]() -> jsg::JsValue {\n    if (reason->IsUndefined() && FeatureFlags::get(js).getPedanticWpt()) {\n      auto ex = js.domException(\n          kj::str(\"AbortError\"), kj::str(\"This writable stream has been aborted.\"), kj::none);\n      return jsg::JsValue(KJ_ASSERT_NONNULL(ex.tryGetHandle(js)));\n    }\n    return jsg::JsValue(reason);\n  }();\n  signal->triggerAbort(js, signalReason);\n\n  // We have to check this again after the AbortSignal is triggered.\n  if (state.isTerminal()) {\n    return js.resolvedPromise();\n  }\n\n  KJ_IF_SOME(pendingAbort, maybePendingAbort) {\n    // Notice here that, per the spec, the reason given in this call of abort is\n    // intentionally ignored if there is already an abort pending.\n    return pendingAbort->whenResolved(js);\n  }\n\n  bool wasAlreadyErroring = false;\n  if (state.template is<StreamStates::Erroring>()) {\n    wasAlreadyErroring = true;\n    reason = js.v8Undefined();\n  }\n\n  KJ_DEFER(if (!wasAlreadyErroring) { startErroring(js, kj::mv(self), reason); });\n\n  maybePendingAbort = kj::heap<PendingAbort>(js, reason, wasAlreadyErroring);\n  return KJ_ASSERT_NONNULL(maybePendingAbort)->whenResolved(js);\n}\n\ntemplate <typename Self>\nkj::Maybe<WritableStreamJsController&> WritableImpl<Self>::tryGetOwner() {\n  KJ_IF_SOME(o, owner) {\n    return o->tryGet().map([](WritableStream& owner) -> WritableStreamJsController& {\n      return static_cast<WritableStreamJsController&>(owner.getController());\n    });\n  }\n  return kj::none;\n}\n\ntemplate <typename Self>\nssize_t WritableImpl<Self>::getDesiredSize() {\n  return highWaterMark - amountBuffered;\n}\n\ntemplate <typename Self>\nvoid WritableImpl<Self>::advanceQueueIfNeeded(jsg::Lock& js, jsg::Ref<Self> self) {\n  if (!flags.started || inFlightWrite != kj::none) {\n    return;\n  }\n  KJ_ASSERT(isWritable() || state.template is<StreamStates::Erroring>());\n\n  if (state.template is<StreamStates::Erroring>()) {\n    return finishErroring(js, kj::mv(self));\n  }\n\n  if (writeRequests.empty()) {\n    if (closeRequest != kj::none) {\n      KJ_ASSERT(inFlightClose == kj::none);\n      KJ_ASSERT_NONNULL(closeRequest);\n      inFlightClose = kj::mv(closeRequest);\n\n      auto onSuccess = JSG_VISITABLE_LAMBDA((this, self = self.addRef()), (self),\n          (jsg::Lock& js) { finishInFlightClose(js, kj::mv(self)); });\n\n      auto onFailure = JSG_VISITABLE_LAMBDA(\n          (this, self = self.addRef()), (self), (jsg::Lock& js, jsg::Value reason) {\n            finishInFlightClose(js, kj::mv(self), reason.getHandle(js));\n          });\n\n      // Per the spec, the close algorithm should always run asynchronously, even if\n      // there's no user-provided close handler. This ensures that releaseLock() can\n      // reject the closed promise before the close completes.\n      // The original maybeRunAlgorithm would call the onSuccess continuation\n      // synchronously if algorithms.close is not specified. maybeRunAlgorithmAsync\n      // always defers to a microtask.\n      if (FeatureFlags::get(js).getPedanticWpt()) {\n        maybeRunAlgorithmAsync(js, algorithms.close, kj::mv(onSuccess), kj::mv(onFailure));\n      } else {\n        maybeRunAlgorithm(js, algorithms.close, kj::mv(onSuccess), kj::mv(onFailure));\n      }\n    }\n    return;\n  }\n\n  KJ_ASSERT(inFlightWrite == kj::none);\n  auto req = dequeueWriteRequest();\n  auto value = req.value.addRef(js);\n  auto size = req.size;\n  inFlightWrite = kj::mv(req);\n\n  auto onSuccess =\n      JSG_VISITABLE_LAMBDA((this, self = self.addRef(), size), (self), (jsg::Lock& js) {\n        amountBuffered -= size;\n        finishInFlightWrite(js, self.addRef());\n        KJ_ASSERT(isWritable() || state.template is<StreamStates::Erroring>());\n        if (!isCloseQueuedOrInFlight() && isWritable()) {\n        updateBackpressure(js);\n        }\n        if (state.template is<StreamStates::Erroring>() || writeRequests.empty()) {\n        // In this case, we know advanceQueueIfNeeded won't recurse further, so we can\n        // avoid the extra microtask hop.\n        advanceQueueIfNeeded(js, kj::mv(self));\n        return js.resolvedPromise();\n        }\n        // Here, however, let's avoid potentially deep recursion by hopping to a new\n        // microtask to continue processing the queue.\n        return js.resolvedPromise().then(\n            js, JSG_VISITABLE_LAMBDA((this, self = kj::mv(self)), (self), (jsg::Lock & js) mutable {\n              if (isWritable() || state.template is<StreamStates::Erroring>()) {\n              advanceQueueIfNeeded(js, kj::mv(self));\n              }\n            }));\n      });\n\n  auto onFailure = JSG_VISITABLE_LAMBDA(\n      (this, self = self.addRef(), size), (self), (jsg::Lock& js, jsg::Value reason) {\n        amountBuffered -= size;\n        finishInFlightWrite(js, kj::mv(self), reason.getHandle(js));\n        return js.resolvedPromise();\n      });\n\n  // Per the spec, the write algorithm should always run asynchronously, even if\n  // there's no user-provided write handler. This ensures that backpressure changes\n  // from the write don't resolve the ready promise synchronously, preserving correct\n  // microtask ordering (e.g., ready rejects before closed on releaseLock).\n  if (FeatureFlags::get(js).getPedanticWpt()) {\n    maybeRunAlgorithmAsync(js, algorithms.write, kj::mv(onSuccess), kj::mv(onFailure),\n        value.getHandle(js), self.addRef());\n  } else {\n    maybeRunAlgorithm(js, algorithms.write, kj::mv(onSuccess), kj::mv(onFailure),\n        value.getHandle(js), self.addRef());\n  }\n}\n\ntemplate <typename Self>\njsg::Promise<void> WritableImpl<Self>::close(jsg::Lock& js, jsg::Ref<Self> self) {\n  if (state.template is<StreamStates::Closed>()) {\n    return js.rejectedPromise<void>(js.v8TypeError(\"This WritableStream has been closed.\"_kj));\n  }\n  KJ_IF_SOME(errored, state.template tryGetUnsafe<StreamStates::Errored>()) {\n    return js.rejectedPromise<void>(errored.addRef(js));\n  }\n  KJ_ASSERT(isWritable() || state.template is<StreamStates::Erroring>());\n  JSG_REQUIRE(\n      !isCloseQueuedOrInFlight(), TypeError, \"Cannot close a writer that is already being closed\");\n  auto prp = js.newPromiseAndResolver<void>();\n  closeRequest = kj::mv(prp.resolver);\n\n  if (flags.backpressure && isWritable()) {\n    KJ_IF_SOME(owner, tryGetOwner()) {\n      owner.maybeResolveReadyPromise(js);\n    }\n  }\n\n  advanceQueueIfNeeded(js, kj::mv(self));\n\n  return kj::mv(prp.promise);\n}\n\ntemplate <typename Self>\nvoid WritableImpl<Self>::dealWithRejection(\n    jsg::Lock& js, jsg::Ref<Self> self, v8::Local<v8::Value> reason) {\n  if (isWritable()) {\n    return startErroring(js, kj::mv(self), reason);\n  }\n  KJ_ASSERT(state.template is<StreamStates::Erroring>());\n  finishErroring(js, kj::mv(self));\n}\n\ntemplate <typename Self>\nWritableImpl<Self>::WriteRequest WritableImpl<Self>::dequeueWriteRequest() {\n  auto write = kj::mv(writeRequests.front());\n  writeRequests.pop_front();\n  return kj::mv(write);\n}\n\ntemplate <typename Self>\nvoid WritableImpl<Self>::doClose(jsg::Lock& js) {\n  KJ_ASSERT(closeRequest == kj::none);\n  KJ_ASSERT(inFlightClose == kj::none);\n  KJ_ASSERT(inFlightWrite == kj::none);\n  KJ_ASSERT(maybePendingAbort == kj::none);\n  KJ_ASSERT(writeRequests.empty());\n  // State should have already been transitioned to Closed\n  KJ_ASSERT(state.template is<StreamStates::Closed>());\n  algorithms.clear();\n\n  KJ_IF_SOME(owner, tryGetOwner()) {\n    owner.doClose(js);\n  }\n}\n\ntemplate <typename Self>\nvoid WritableImpl<Self>::doError(jsg::Lock& js, v8::Local<v8::Value> reason) {\n  KJ_ASSERT(closeRequest == kj::none);\n  KJ_ASSERT(inFlightClose == kj::none);\n  KJ_ASSERT(inFlightWrite == kj::none);\n  KJ_ASSERT(maybePendingAbort == kj::none);\n  KJ_ASSERT(writeRequests.empty());\n  // State should have already been transitioned to Errored\n  KJ_ASSERT(state.template is<StreamStates::Errored>());\n  algorithms.clear();\n\n  KJ_IF_SOME(owner, tryGetOwner()) {\n    owner.doError(js, reason);\n  }\n}\n\ntemplate <typename Self>\nvoid WritableImpl<Self>::error(jsg::Lock& js, jsg::Ref<Self> self, v8::Local<v8::Value> reason) {\n  if (isWritable()) {\n    algorithms.clear();\n    startErroring(js, kj::mv(self), reason);\n  }\n}\n\ntemplate <typename Self>\nvoid WritableImpl<Self>::finishErroring(jsg::Lock& js, jsg::Ref<Self> self) {\n  auto erroring = kj::mv(KJ_ASSERT_NONNULL(state.template tryGetUnsafe<StreamStates::Erroring>()));\n  auto reason = erroring.reason.getHandle(js);\n  KJ_ASSERT(inFlightWrite == kj::none);\n  KJ_ASSERT(inFlightClose == kj::none);\n  state.template transitionTo<StreamStates::Errored>(kj::mv(erroring.reason));\n\n  while (!writeRequests.empty()) {\n    dequeueWriteRequest().resolver.reject(js, reason);\n  }\n  KJ_ASSERT(writeRequests.empty());\n\n  KJ_IF_SOME(pendingAbort, maybePendingAbort) {\n    if (pendingAbort->reject) {\n      pendingAbort->fail(js, reason);\n      return rejectCloseAndClosedPromiseIfNeeded(js);\n    }\n\n    auto onSuccess = JSG_VISITABLE_LAMBDA((this, self = self.addRef()), (self), (jsg::Lock& js) {\n      auto& pendingAbort = KJ_ASSERT_NONNULL(maybePendingAbort);\n      pendingAbort->reject = false;\n      pendingAbort->complete(js);\n      rejectCloseAndClosedPromiseIfNeeded(js);\n    });\n\n    auto onFailure = JSG_VISITABLE_LAMBDA(\n        (this, self = self.addRef()), (self), (jsg::Lock& js, jsg::Value reason) {\n          auto& pendingAbort = KJ_ASSERT_NONNULL(maybePendingAbort);\n          pendingAbort->fail(js, reason.getHandle(js));\n          rejectCloseAndClosedPromiseIfNeeded(js);\n        });\n\n    maybeRunAlgorithm(js, algorithms.abort, kj::mv(onSuccess), kj::mv(onFailure), reason);\n    return;\n  }\n  rejectCloseAndClosedPromiseIfNeeded(js);\n}\n\ntemplate <typename Self>\nvoid WritableImpl<Self>::finishInFlightClose(\n    jsg::Lock& js, jsg::Ref<Self> self, kj::Maybe<v8::Local<v8::Value>> maybeReason) {\n  algorithms.clear();\n  KJ_ASSERT_NONNULL(inFlightClose);\n  KJ_ASSERT(isWritable() || state.template is<StreamStates::Erroring>());\n\n  KJ_IF_SOME(reason, maybeReason) {\n    maybeRejectPromise<void>(js, inFlightClose, reason);\n\n    KJ_IF_SOME(pendingAbort, PendingAbort::dequeue(maybePendingAbort)) {\n      pendingAbort->fail(js, reason);\n    }\n\n    return dealWithRejection(js, kj::mv(self), reason);\n  }\n\n  maybeResolvePromise(js, inFlightClose);\n\n  if (state.template is<StreamStates::Erroring>()) {\n    KJ_IF_SOME(pendingAbort, PendingAbort::dequeue(maybePendingAbort)) {\n      pendingAbort->reject = false;\n      pendingAbort->complete(js);\n    }\n  }\n  KJ_ASSERT(maybePendingAbort == kj::none);\n\n  state.template transitionTo<StreamStates::Closed>();\n  doClose(js);\n}\n\ntemplate <typename Self>\nvoid WritableImpl<Self>::finishInFlightWrite(\n    jsg::Lock& js, jsg::Ref<Self> self, kj::Maybe<v8::Local<v8::Value>> maybeReason) {\n  auto& write = KJ_ASSERT_NONNULL(inFlightWrite);\n\n  KJ_IF_SOME(reason, maybeReason) {\n    write.resolver.reject(js, reason);\n    inFlightWrite = kj::none;\n    KJ_ASSERT(isWritable() || state.template is<StreamStates::Erroring>());\n    return dealWithRejection(js, kj::mv(self), reason);\n  }\n\n  write.resolver.resolve(js);\n  inFlightWrite = kj::none;\n}\n\ntemplate <typename Self>\nbool WritableImpl<Self>::isCloseQueuedOrInFlight() {\n  return closeRequest != kj::none || inFlightClose != kj::none;\n}\n\ntemplate <typename Self>\nvoid WritableImpl<Self>::rejectCloseAndClosedPromiseIfNeeded(jsg::Lock& js) {\n  algorithms.clear();\n  auto reason =\n      KJ_ASSERT_NONNULL(state.template tryGetUnsafe<StreamStates::Errored>()).getHandle(js);\n  maybeRejectPromise<void>(js, closeRequest, reason);\n  PendingAbort::dequeue(maybePendingAbort);\n  doError(js, reason);\n}\n\ntemplate <typename Self>\nvoid WritableImpl<Self>::setup(jsg::Lock& js,\n    jsg::Ref<Self> self,\n    UnderlyingSink underlyingSink,\n    StreamQueuingStrategy queuingStrategy) {\n  KJ_ASSERT(!flags.started && !flags.starting);\n  flags.starting = true;\n\n  highWaterMark = queuingStrategy.highWaterMark.orDefault(1);\n  auto startAlgorithm = kj::mv(underlyingSink.start);\n  algorithms.write = kj::mv(underlyingSink.write);\n  algorithms.close = kj::mv(underlyingSink.close);\n  algorithms.abort = kj::mv(underlyingSink.abort);\n  algorithms.size = kj::mv(queuingStrategy.size);\n  // Per the streams spec, the size function should be called with `undefined` as `this`,\n  // not as a method on the strategy object.\n  KJ_IF_SOME(sizeFunc, algorithms.size) {\n    sizeFunc.setReceiver(jsg::Value(js.v8Isolate, js.v8Undefined()));\n  }\n\n  auto onSuccess = JSG_VISITABLE_LAMBDA((this, self = self.addRef()), (self), (jsg::Lock& js) {\n    KJ_ASSERT(isWritable() || state.template is<StreamStates::Erroring>());\n\n    if (isWritable()) {\n    // Only resolve the ready promise if an abort is not pending.\n    // It will have been rejected already.\n    KJ_IF_SOME(owner, tryGetOwner()) {\n    owner.maybeResolveReadyPromise(js);\n    } else {\n    // Else block to avert dangling else compiler warning.\n    }\n    }\n\n    flags.started = true;\n    flags.starting = false;\n    advanceQueueIfNeeded(js, kj::mv(self));\n  });\n\n  auto onFailure = JSG_VISITABLE_LAMBDA(\n      (this, self = self.addRef()), (self), (jsg::Lock& js, jsg::Value reason) {\n        auto handle = reason.getHandle(js);\n        KJ_ASSERT(isWritable() || state.template is<StreamStates::Erroring>());\n        KJ_IF_SOME(owner, tryGetOwner()) {\n        owner.maybeRejectReadyPromise(js, handle);\n        } else {\n        // Else block to avert dangling else compiler warning.\n        }\n        flags.started = true;\n        flags.starting = false;\n        dealWithRejection(js, kj::mv(self), handle);\n      });\n\n  flags.backpressure = getDesiredSize() <= 0;\n\n  maybeRunAlgorithm(js, startAlgorithm, kj::mv(onSuccess), kj::mv(onFailure), self.addRef());\n}\n\ntemplate <typename Self>\nvoid WritableImpl<Self>::startErroring(\n    jsg::Lock& js, jsg::Ref<Self> self, v8::Local<v8::Value> reason) {\n  KJ_ASSERT(isWritable());\n  KJ_IF_SOME(owner, tryGetOwner()) {\n    owner.maybeRejectReadyPromise(js, reason);\n  }\n  state.template transitionTo<StreamStates::Erroring>(js.v8Ref(reason));\n  if (inFlightWrite == kj::none && inFlightClose == kj::none && flags.started) {\n    finishErroring(js, kj::mv(self));\n  }\n}\n\ntemplate <typename Self>\nvoid WritableImpl<Self>::updateBackpressure(jsg::Lock& js) {\n  KJ_ASSERT(isWritable());\n  KJ_ASSERT(!isCloseQueuedOrInFlight());\n  bool bp = getDesiredSize() <= 0;\n\n  if (bp != flags.backpressure) {\n    flags.backpressure = bp;\n    KJ_IF_SOME(owner, tryGetOwner()) {\n      owner.updateBackpressure(js, flags.backpressure);\n    }\n  }\n}\n\ntemplate <typename Self>\njsg::Promise<void> WritableImpl<Self>::write(\n    jsg::Lock& js, jsg::Ref<Self> self, v8::Local<v8::Value> value) {\n\n  size_t size = 1;\n  KJ_IF_SOME(sizeFunc, algorithms.size) {\n    kj::Maybe<jsg::Value> failure;\n    JSG_TRY(js) {\n      size = sizeFunc(js, value);\n    }\n    JSG_CATCH(exception) {\n      startErroring(js, self.addRef(), exception.getHandle(js));\n      failure = kj::mv(exception);\n    }\n    KJ_IF_SOME(exception, failure) {\n      return js.rejectedPromise<void>(kj::mv(exception));\n    }\n  }\n\n  // Per spec (WritableStreamDefaultWriterWrite step 5), after calling the size\n  // algorithm, re-check that the stream is still locked to a writer. If\n  // releaseLock() was called from within strategy.size(), the write must be\n  // rejected. This check must occur before any state checks, as the stream\n  // state may still appear writable even after the writer was released.\n  if (FeatureFlags::get(js).getWritableStreamSpecCompliantWriter()) {\n    KJ_IF_SOME(owner, tryGetOwner()) {\n      if (!owner.isLockedToWriter()) {\n        return js.rejectedPromise<void>(\n            js.v8TypeError(\"This WritableStream writer has been released.\"_kjc));\n      }\n    }\n  }\n\n  KJ_IF_SOME(error, state.template tryGetUnsafe<StreamStates::Errored>()) {\n    return js.rejectedPromise<void>(error.addRef(js));\n  }\n\n  if (isCloseQueuedOrInFlight() || state.template is<StreamStates::Closed>()) {\n    return js.rejectedPromise<void>(js.v8TypeError(\"This ReadableStream is closed.\"_kj));\n  }\n\n  KJ_IF_SOME(erroring, state.template tryGetUnsafe<StreamStates::Erroring>()) {\n    return js.rejectedPromise<void>(erroring.reason.addRef(js));\n  }\n\n  KJ_ASSERT(isWritable());\n\n  auto prp = js.newPromiseAndResolver<void>();\n  writeRequests.push_back(WriteRequest{\n    .resolver = kj::mv(prp.resolver),\n    .value = js.v8Ref(value),\n    .size = size,\n  });\n  amountBuffered += size;\n\n  updateBackpressure(js);\n  advanceQueueIfNeeded(js, kj::mv(self));\n  return kj::mv(prp.promise);\n}\n\ntemplate <typename Self>\nvoid WritableImpl<Self>::visitForGc(jsg::GcVisitor& visitor) {\n  state.visitForGc(visitor);\n  visitor.visit(inFlightWrite, inFlightClose, closeRequest, algorithms, signal);\n  KJ_IF_SOME(pendingAbort, maybePendingAbort) {\n    visitor.visit(*pendingAbort);\n  }\n  visitor.visitAll(writeRequests);\n}\n\ntemplate <typename Self>\nbool WritableImpl<Self>::isWritable() const {\n  return state.isActive();\n}\n\ntemplate <typename Self>\nvoid WritableImpl<Self>::cancelPendingWrites(jsg::Lock& js, jsg::JsValue reason) {\n  for (auto& write: writeRequests) {\n    write.resolver.reject(js, reason);\n  }\n  writeRequests.clear();\n}\n\n// ======================================================================================\n\nnamespace {\ntemplate <typename Controller, typename Queue>\nstruct ReadableState {\n  Controller controller;\n  kj::Own<typename Queue::Consumer> consumer;\n  ReadableStreamJsController& owner;\n\n  ReadableState(Controller controller,\n      kj::Own<typename Queue::Consumer> consumer,\n      ReadableStreamJsController& owner)\n      : controller(kj::mv(controller)),\n        consumer(kj::mv(consumer)),\n        owner(owner) {}\n\n  ReadableState(Controller controller,\n      Queue::ConsumerImpl::StateListener& listener,\n      ReadableStreamJsController& owner)\n      : ReadableState(controller.addRef(), controller->getConsumer(listener), owner) {}\n\n  ReadableState clone(jsg::Lock& js,\n      Queue::ConsumerImpl::StateListener& listener,\n      ReadableStreamJsController& owner) {\n    return ReadableState(controller.addRef(), consumer->clone(js, listener), owner);\n  }\n};\n\nstruct ValueReadable final: private api::ValueQueue::ConsumerImpl::StateListener {\n\n  using State = ReadableState<DefaultController, ValueQueue>;\n  kj::Maybe<State> state;\n  bool reading = false;\n  bool pendingCancel = false;\n\n  JSG_MEMORY_INFO(ValueReadable) {\n    KJ_IF_SOME(s, state) {\n      tracker.trackField(\"controller\", s.controller);\n      tracker.trackField(\"consumer\", s.consumer);\n    }\n  }\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    KJ_IF_SOME(s, state) {\n      visitor.visit(s.controller, *s.consumer);\n    }\n  }\n\n  ValueReadable(DefaultController controller, ReadableStreamJsController& owner)\n      : state(State(kj::mv(controller), *this, owner)) {}\n\n  ValueReadable(jsg::Lock& js, ReadableStreamJsController& owner, ValueReadable& other)\n      : state(KJ_ASSERT_NONNULL(other.state).clone(js, *this, owner)) {}\n\n  KJ_DISALLOW_COPY_AND_MOVE(ValueReadable);\n\n  void cancelPendingReads(jsg::Lock& js, jsg::JsValue reason) {\n    KJ_IF_SOME(s, state) {\n      s.consumer->cancelPendingReads(js, reason);\n    }\n  }\n\n  kj::Own<ValueReadable> clone(jsg::Lock& js, ReadableStreamJsController& owner) {\n    // A single ReadableStreamDefaultController can have multiple consumers.\n    // When the ValueReadable constructor is used, the new consumer is added\n    // and starts to receive new data that becomes enqueued. When clone\n    // is used, any state currently held by this consumer is copied to the\n    // new consumer.\n    return kj::heap<ValueReadable>(js, owner, *this);\n  }\n\n  jsg::Promise<ReadResult> read(jsg::Lock& js) {\n    KJ_IF_SOME(s, state) {\n      auto prp = js.newPromiseAndResolver<ReadResult>();\n      reading = true;\n      s.consumer->read(js,\n          ValueQueue::ReadRequest{\n            .resolver = kj::mv(prp.resolver),\n          });\n      reading = false;\n      if (pendingCancel) {\n        // If we were canceled while reading, we need to drop our state now.\n        state = kj::none;\n        pendingCancel = false;\n      }\n      return kj::mv(prp.promise);\n    }\n\n    // We are canceled! There's nothing to do.\n    return js.resolvedPromise(ReadResult{.done = true});\n  }\n\n  jsg::Promise<DrainingReadResult> drainingRead(jsg::Lock& js, size_t maxRead) {\n    KJ_IF_SOME(s, state) {\n      // Note: We do NOT call beginOperation()/endOperation() here. The caller\n      // (ReadableStreamJsController::drainingRead) manages the operation scope\n      // around both this call and the returned promise's lifetime. If we added\n      // our own beginOperation/endOperation here, the endOperation would fire\n      // before the caller's wrapDrainingRead could set up its .then() callbacks,\n      // potentially destroying the Consumer while the returned promise still has\n      // dangling this-capturing callbacks from consumer->drainingRead().\n      return s.consumer->drainingRead(js, maxRead);\n    }\n\n    // We are canceled! Return done with empty chunks.\n    return js.resolvedPromise(DrainingReadResult{\n      .chunks = kj::Array<kj::Array<kj::byte>>(),\n      .done = true,\n    });\n  }\n\n  jsg::Promise<void> cancel(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason) {\n    // When a ReadableStream is canceled, the expected behavior is that the underlying\n    // controller is notified and the cancel algorithm on the underlying source is\n    // called. When there are multiple ReadableStreams sharing consumption of a\n    // controller, however, it should act as a shared pointer of sorts, canceling\n    // the underlying controller only when the last reader is canceled.\n    // Here, we rely on the controller implementing the correct behavior since it owns\n    // the queue that knows about all of the attached consumers.\n    if (pendingCancel) return js.resolvedPromise();\n    KJ_IF_SOME(s, state) {\n      // Check if there's a pending draining read before calling cancel, since cancel\n      // will resolve the pending read and we need to know if we should defer destruction.\n      bool hasPendingDrainingRead = s.consumer->hasPendingDrainingRead();\n      s.consumer->cancel(js, maybeReason);\n      auto promise = s.controller->cancel(js, kj::mv(maybeReason));\n      // If we're currently in a read (sync or draining), we need to wait for that to\n      // finish before dropping our state. For draining reads, the promise callbacks\n      // capture 'this' (the Consumer) to clear hasPendingDrainingRead. If we destroy\n      // the state now, those callbacks will UAF.\n      if (reading || hasPendingDrainingRead) {\n        pendingCancel = true;\n      } else {\n        state = kj::none;\n      }\n      return kj::mv(promise);\n    }\n\n    return js.resolvedPromise();\n  }\n\n  void onConsumerClose(jsg::Lock& js) override {\n    // Called by the consumer when a state change to closed happens.\n    // We need to notify the owner. Note that the owner may drop this\n    // readable in doClose so it is not safe to access anything on this\n    // after calling doClose.\n    KJ_IF_SOME(s, state) {\n      s.owner.doClose(js);\n    }\n  }\n\n  void onConsumerError(jsg::Lock& js, jsg::Value reason) override {\n    // Called by the consumer when a state change to errored happens.\n    // We need to notify the owner. Note that the owner may drop this\n    // readable in doClose so it is not safe to access anything on this\n    // after calling doError.\n    KJ_IF_SOME(s, state) {\n      s.owner.doError(js, reason.getHandle(js));\n    }\n  }\n\n  bool onConsumerWantsData(jsg::Lock& js) override {\n    // Called by the consumer when it has a queued pending read and needs\n    // data to be provided to fulfill it. We need to notify the controller\n    // to initiate pulling to provide the data.\n    // Returns true if the pull completed synchronously (meaning more pumping\n    // might yield additional synchronous data), false otherwise.\n    KJ_IF_SOME(s, state) {\n      // Save a reference to the owner before calling pull. The pull callback\n      // may trigger close/error which could destroy this ValueReadable. By\n      // using beginOperation(), we ensure doClose/doError defers the\n      // actual destruction until after we return.\n      ReadableStreamJsController& owner = s.owner;\n      owner.state.beginOperation();\n\n      // For draining reads, use forcePull to bypass backpressure checks.\n      // This ensures we pull all available data regardless of highWaterMark.\n      if (s.consumer->hasPendingDrainingRead()) {\n        s.controller->forcePull(js);\n      } else {\n        s.controller->pull(js);\n      }\n\n      // Check if state is still valid BEFORE calling endOperation(),\n      // because that call may destroy this ValueReadable if close was deferred.\n      bool result =\n          state.map([](State& s2) { return !s2.controller->isPulling(); }).orDefault(false);\n\n      // Process any deferred close/error. This may destroy this ValueReadable.\n      if (owner.state.endOperation()) {\n        // A pending state was applied. Call the appropriate callback.\n        if (owner.state.template is<StreamStates::Closed>()) {\n          owner.lock.onClose(js);\n        } else if (owner.state.template is<StreamStates::Errored>()) {\n          KJ_IF_SOME(err, owner.state.template tryGetUnsafe<StreamStates::Errored>()) {\n            owner.lock.onError(js, err.getHandle(js));\n          }\n        }\n      }\n\n      return result;\n    }\n    return false;\n  }\n\n  kj::Maybe<int> getDesiredSize() {\n    KJ_IF_SOME(s, state) {\n      return s.controller->getDesiredSize();\n    }\n    return kj::none;\n  }\n\n  bool canCloseOrEnqueue() {\n    return state.map([](State& s) { return s.controller->canCloseOrEnqueue(); }).orDefault(false);\n  }\n\n  kj::Maybe<DefaultController> getControllerRef() {\n    return state.map([](State& s) { return s.controller.addRef(); });\n  }\n};\n\nstruct ByteReadable final: private api::ByteQueue::ConsumerImpl::StateListener {\n\n  using State = ReadableState<ByobController, ByteQueue>;\n  kj::Maybe<State> state;\n  kj::Maybe<int> autoAllocateChunkSize;\n  bool pendingCancel = false;\n\n  JSG_MEMORY_INFO(ByteReadable) {\n    KJ_IF_SOME(s, state) {\n      tracker.trackField(\"controller\", s.controller);\n      tracker.trackField(\"consumer\", s.consumer);\n    }\n  }\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    KJ_IF_SOME(s, state) {\n      visitor.visit(s.controller, *s.consumer);\n    }\n  }\n\n  ByteReadable(ByobController controller,\n      ReadableStreamJsController& owner,\n      kj::Maybe<int> autoAllocateChunkSize)\n      : state(State(kj::mv(controller), *this, owner)),\n        autoAllocateChunkSize(autoAllocateChunkSize) {}\n\n  ByteReadable(jsg::Lock& js, ReadableStreamJsController& owner, ByteReadable& other)\n      : state(KJ_ASSERT_NONNULL(other.state).clone(js, *this, owner)),\n        autoAllocateChunkSize(other.autoAllocateChunkSize) {}\n\n  KJ_DISALLOW_COPY_AND_MOVE(ByteReadable);\n\n  void cancelPendingReads(jsg::Lock& js, jsg::JsValue reason) {\n    KJ_IF_SOME(s, state) {\n      s.consumer->cancelPendingReads(js, reason);\n    }\n  }\n\n  // A single ReadableByteStreamController can have multiple consumers.\n  // When the ByteReadable constructor is used, the new consumer is added\n  // and starts to receive new data that becomes enqueued. When clone\n  // is used, any state currently held by this consumer is copied to the\n  // new consumer.\n  kj::Own<ByteReadable> clone(jsg::Lock& js, ReadableStreamJsController& owner) {\n    return kj::heap<ByteReadable>(js, owner, *this);\n  }\n\n  jsg::Promise<ReadResult> read(\n      jsg::Lock& js, kj::Maybe<ReadableStreamController::ByobOptions> byobOptions) {\n    KJ_IF_SOME(s, state) {\n      auto prp = js.newPromiseAndResolver<ReadResult>();\n\n      KJ_IF_SOME(byob, byobOptions) {\n        jsg::BufferSource source(js, byob.bufferView.getHandle(js));\n        // If atLeast is not given, then by default it is the element size of the view\n        // that we were given. If atLeast is given, we make sure that it is aligned\n        // with the element size. No matter what, atLeast cannot be less than 1.\n        auto atLeast = kj::max(source.getElementSize(), byob.atLeast.orDefault(1));\n        atLeast = kj::max(1, atLeast - (atLeast % source.getElementSize()));\n        s.consumer->read(js,\n            ByteQueue::ReadRequest(kj::mv(prp.resolver),\n                {\n                  .store = jsg::BufferSource(js, source.detach(js)),\n                  .atLeast = atLeast,\n                  .type = ByteQueue::ReadRequest::Type::BYOB,\n                }));\n      } else KJ_IF_SOME(chunkSize, autoAllocateChunkSize) {\n        // autoAllocateChunkSize is set, so we allocate a buffer and do a BYOB read.\n        // This makes the buffer available to the underlying source via controller.byobRequest.\n        KJ_IF_SOME(store, jsg::BufferSource::tryAlloc(js, chunkSize)) {\n          // Ensure that the handle is created here so that the size of the buffer\n          // is accounted for in the isolate memory tracking.\n          s.consumer->read(js,\n              ByteQueue::ReadRequest(kj::mv(prp.resolver),\n                  {\n                    .store = kj::mv(store),\n                    .type = ByteQueue::ReadRequest::Type::BYOB,\n                  }));\n        } else {\n          prp.resolver.reject(js, js.v8Error(\"Failed to allocate buffer for read.\"));\n        }\n      } else {\n        // autoAllocateChunkSize is not set. Per spec, we do a DEFAULT read which means\n        // the underlying source's pull method won't get a byobRequest. It must use\n        // controller.enqueue() to provide data instead.\n        constexpr size_t kDefaultReadSize = 16384;  // 16KB default buffer\n        KJ_IF_SOME(store, jsg::BufferSource::tryAlloc(js, kDefaultReadSize)) {\n          s.consumer->read(js,\n              ByteQueue::ReadRequest(kj::mv(prp.resolver),\n                  {\n                    .store = kj::mv(store),\n                    .type = ByteQueue::ReadRequest::Type::DEFAULT,\n                  }));\n        } else {\n          prp.resolver.reject(js, js.v8Error(\"Failed to allocate buffer for read.\"));\n        }\n      }\n\n      return kj::mv(prp.promise);\n    }\n\n    // We are canceled! There's nothing else to do.\n    KJ_IF_SOME(byob, byobOptions) {\n      // If a BYOB buffer was given, we need to give it back wrapped in a TypedArray\n      // whose size is set to zero.\n      jsg::BufferSource source(js, byob.bufferView.getHandle(js));\n      auto store = source.detach(js);\n      store.consume(store.size());\n      return js.resolvedPromise(ReadResult{\n        .value = js.v8Ref(store.createHandle(js)),\n        .done = true,\n      });\n    } else {\n      return js.resolvedPromise(ReadResult{.done = true});\n    }\n  }\n\n  jsg::Promise<DrainingReadResult> drainingRead(jsg::Lock& js, size_t maxRead) {\n    KJ_IF_SOME(s, state) {\n      // Note: We do NOT call beginOperation()/endOperation() here. The caller\n      // (ReadableStreamJsController::drainingRead) manages the operation scope\n      // around both this call and the returned promise's lifetime. See the\n      // comment in ValueReadable::drainingRead for the detailed explanation.\n      return s.consumer->drainingRead(js, maxRead);\n    }\n\n    // We are canceled! Return done with empty chunks.\n    return js.resolvedPromise(DrainingReadResult{\n      .chunks = kj::Array<kj::Array<kj::byte>>(),\n      .done = true,\n    });\n  }\n\n  // When a ReadableStream is canceled, the expected behavior is that the underlying\n  // controller is notified and the cancel algorithm on the underlying source is\n  // called. When there are multiple ReadableStreams sharing consumption of a\n  // controller, however, it should act as a shared pointer of sorts, canceling\n  // the underlying controller only when the last reader is canceled.\n  // Here, we rely on the controller implementing the correct behavior since it owns\n  // the queue that knows about all of the attached consumers.\n  jsg::Promise<void> cancel(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason) {\n    if (pendingCancel) return js.resolvedPromise();\n    KJ_IF_SOME(s, state) {\n      // Check if there's a pending draining read before calling cancel, since cancel\n      // will resolve the pending read and we need to know if we should defer destruction.\n      bool hasPendingDrainingRead = s.consumer->hasPendingDrainingRead();\n      s.consumer->cancel(js, maybeReason);\n      auto promise = s.controller->cancel(js, kj::mv(maybeReason));\n      // If there's a pending draining read, we need to wait for it to finish before\n      // dropping our state. The draining read's promise callbacks capture 'this' (the\n      // Consumer) to clear hasPendingDrainingRead. If we destroy the state now, those\n      // callbacks will UAF.\n      if (hasPendingDrainingRead) {\n        pendingCancel = true;\n      } else {\n        state = kj::none;\n      }\n      return kj::mv(promise);\n    }\n\n    return js.resolvedPromise();\n  }\n\n  void onConsumerClose(jsg::Lock& js) override {\n    // Note that the owner may drop this readable in doClose so it\n    // is not safe to access anything on this after calling doClose.\n    KJ_IF_SOME(s, state) {\n      s.owner.doClose(js);\n    }\n  }\n\n  void onConsumerError(jsg::Lock& js, jsg::Value reason) override {\n    // Note that the owner may drop this readable in doClose so it\n    // is not safe to access anything on this after calling doError.\n    KJ_IF_SOME(s, state) {\n      s.owner.doError(js, reason.getHandle(js));\n    };\n  }\n\n  // Called by the consumer when it has a queued pending read and needs\n  // data to be provided to fulfill it. We need to notify the controller\n  // to initiate pulling to provide the data.\n  // Returns true if the pull completed synchronously (meaning more pumping\n  // might yield additional synchronous data), false otherwise.\n  bool onConsumerWantsData(jsg::Lock& js) override {\n    KJ_IF_SOME(s, state) {\n      // Save a reference to the owner before calling pull. The pull callback\n      // may trigger close/error which could destroy this ByteReadable. By\n      // using beginOperation(), we ensure doClose/doError defers the\n      // actual destruction until after we return.\n      ReadableStreamJsController& owner = s.owner;\n      owner.state.beginOperation();\n\n      // For draining reads, use forcePull to bypass backpressure checks.\n      // This ensures we pull all available data regardless of highWaterMark.\n      if (s.consumer->hasPendingDrainingRead()) {\n        s.controller->forcePull(js);\n      } else {\n        s.controller->pull(js);\n      }\n\n      // Check if state is still valid BEFORE calling endOperation(),\n      // because that call may destroy this ByteReadable if close was deferred.\n      bool result =\n          state.map([](State& s2) { return !s2.controller->isPulling(); }).orDefault(false);\n\n      // Process any deferred close/error. This may destroy this ByteReadable.\n      if (owner.state.endOperation()) {\n        // A pending state was applied. Call the appropriate callback.\n        if (owner.state.template is<StreamStates::Closed>()) {\n          owner.lock.onClose(js);\n        } else if (owner.state.template is<StreamStates::Errored>()) {\n          KJ_IF_SOME(err, owner.state.template tryGetUnsafe<StreamStates::Errored>()) {\n            owner.lock.onError(js, err.getHandle(js));\n          }\n        }\n      }\n\n      return result;\n    }\n    return false;\n  }\n\n  kj::Maybe<int> getDesiredSize() {\n    KJ_IF_SOME(s, state) {\n      return s.controller->getDesiredSize();\n    }\n    return kj::none;\n  }\n\n  bool canCloseOrEnqueue() {\n    return state.map([](State& s) { return s.controller->canCloseOrEnqueue(); }).orDefault(false);\n  }\n\n  kj::Maybe<ByobController> getControllerRef() {\n    return state.map([](State& state) { return state.controller.addRef(); });\n  }\n};\n}  // namespace\n\n// =======================================================================================\n\nReadableStreamDefaultController::ReadableStreamDefaultController(\n    UnderlyingSource underlyingSource, StreamQueuingStrategy queuingStrategy)\n    : ioContext(tryGetIoContext()),\n      impl(kj::mv(underlyingSource), kj::mv(queuingStrategy)) {}\n\nkj::Maybe<StreamStates::Errored> ReadableStreamDefaultController::getMaybeErrorState(\n    jsg::Lock& js) {\n  KJ_IF_SOME(errored, impl.state.tryGetUnsafe<StreamStates::Errored>()) {\n    return errored.addRef(js);\n  }\n  return kj::none;\n}\n\nvoid ReadableStreamDefaultController::start(jsg::Lock& js) {\n  impl.start(js, JSG_THIS);\n}\n\nbool ReadableStreamDefaultController::canCloseOrEnqueue() {\n  return impl.canCloseOrEnqueue();\n}\n\nbool ReadableStreamDefaultController::hasBackpressure() {\n  return !impl.shouldCallPull();\n}\n\nkj::Maybe<int> ReadableStreamDefaultController::getDesiredSize() {\n  return impl.getDesiredSize();\n}\n\nvoid ReadableStreamDefaultController::visitForGc(jsg::GcVisitor& visitor) {\n  visitor.visit(impl);\n}\n\njsg::Promise<void> ReadableStreamDefaultController::cancel(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason) {\n  return impl.cancel(js, JSG_THIS, maybeReason.orDefault([&] { return js.v8Undefined(); }));\n}\n\nvoid ReadableStreamDefaultController::close(jsg::Lock& js) {\n  impl.close(js);\n}\n\nvoid ReadableStreamDefaultController::enqueue(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> chunk) {\n  auto value = chunk.orDefault(js.undefined());\n\n  JSG_REQUIRE(impl.canCloseOrEnqueue(), TypeError, \"Unable to enqueue\");\n\n  size_t size = 1;\n  bool errored = false;\n  KJ_IF_SOME(sizeFunc, impl.algorithms.size) {\n    js.tryCatch([&] { size = sizeFunc(js, value); }, [&](jsg::Value exception) {\n      impl.doError(js, kj::mv(exception));\n      errored = true;\n    });\n  }\n\n  if (!errored) {\n    impl.enqueue(js, kj::rc<ValueQueue::Entry>(js.v8Ref(value), size), JSG_THIS);\n  }\n}\n\nvoid ReadableStreamDefaultController::error(jsg::Lock& js, v8::Local<v8::Value> reason) {\n  impl.doError(js, js.v8Ref(reason));\n}\n\n// When a consumer receives a read request, but does not have the data available to\n// fulfill the request, the consumer will call pull on the controller to pull that\n// data if needed.\nvoid ReadableStreamDefaultController::pull(jsg::Lock& js) {\n  impl.pullIfNeeded(js, JSG_THIS);\n}\n\nvoid ReadableStreamDefaultController::forcePull(jsg::Lock& js) {\n  impl.forcePullIfNeeded(js, JSG_THIS);\n}\n\nkj::Own<ValueQueue::Consumer> ReadableStreamDefaultController::getConsumer(\n    kj::Maybe<ValueQueue::ConsumerImpl::StateListener&> stateListener) {\n  return impl.getConsumer(stateListener);\n}\n\n// ======================================================================================\n\nReadableStreamBYOBRequest::Impl::Impl(jsg::Lock& js,\n    kj::Own<ByteQueue::ByobRequest> readRequest,\n    kj::Rc<WeakRef<ReadableByteStreamController>> controller)\n    : readRequest(kj::mv(readRequest)),\n      controller(kj::mv(controller)),\n      view(js.v8Ref(this->readRequest->getView(js))),\n      originalBufferByteLength(this->readRequest->getOriginalBufferByteLength(js)),\n      originalByteOffsetPlusBytesFilled(this->readRequest->getOriginalByteOffsetPlusBytesFilled()) {\n}\n\nvoid ReadableStreamBYOBRequest::Impl::updateView(jsg::Lock& js) {\n  jsg::check(view.getHandle(js)->Buffer()->Detach(v8::Local<v8::Value>()));\n  view = js.v8Ref(readRequest->getView(js));\n}\n\nvoid ReadableStreamBYOBRequest::visitForGc(jsg::GcVisitor& visitor) {\n  KJ_IF_SOME(impl, maybeImpl) {\n    visitor.visit(impl.view);\n  }\n}\n\nReadableStreamBYOBRequest::ReadableStreamBYOBRequest(jsg::Lock& js,\n    kj::Own<ByteQueue::ByobRequest> readRequest,\n    kj::Rc<WeakRef<ReadableByteStreamController>> controller)\n    : ioContext(tryGetIoContext()),\n      maybeImpl(Impl(js, kj::mv(readRequest), kj::mv(controller))) {}\n\nkj::Maybe<int> ReadableStreamBYOBRequest::getAtLeast() {\n  KJ_IF_SOME(impl, maybeImpl) {\n    return impl.readRequest->getAtLeast();\n  }\n  return kj::none;\n}\n\nkj::Maybe<jsg::V8Ref<v8::Uint8Array>> ReadableStreamBYOBRequest::getView(jsg::Lock& js) {\n  KJ_IF_SOME(impl, maybeImpl) {\n    return impl.view.addRef(js);\n  }\n  return kj::none;\n}\n\nvoid ReadableStreamBYOBRequest::invalidate(jsg::Lock& js) {\n  KJ_IF_SOME(impl, maybeImpl) {\n    // If the user code happened to have retained a reference to the view or\n    // the buffer, we need to detach it so that those references cannot be used\n    // to modify or observe modifications.\n    jsg::check(impl.view.getHandle(js)->Buffer()->Detach(v8::Local<v8::Value>()));\n    impl.controller->runIfAlive(\n        [](ReadableByteStreamController& controller) { controller.maybeByobRequest = kj::none; });\n  }\n  maybeImpl = kj::none;\n}\n\nvoid ReadableStreamBYOBRequest::respond(jsg::Lock& js, int bytesWritten) {\n  auto& impl = JSG_REQUIRE_NONNULL(\n      maybeImpl, TypeError, \"This ReadableStreamBYOBRequest has been invalidated.\");\n  JSG_REQUIRE(impl.controller->isValid(), Error, \"The ReadableStreamBYOBRequest is invalid.\");\n  JSG_REQUIRE(impl.view.getHandle(js)->ByteLength() > 0, TypeError,\n      \"Cannot respond with a zero-length or detached view\");\n  impl.controller->runIfAlive([&](ReadableByteStreamController& controller) {\n    if (!controller.canCloseOrEnqueue()) {\n      JSG_REQUIRE(bytesWritten == 0, TypeError,\n          \"The bytesWritten must be zero after the stream is closed.\");\n      KJ_ASSERT(impl.readRequest->isInvalidated());\n      invalidate(js);\n    } else {\n      bool shouldInvalidate = false;\n      if (impl.readRequest->isInvalidated() && controller.impl.consumerCount() >= 1) {\n        // While this particular request may be invalidated, there are still\n        // other branches we can push the data to. Let's do so.\n        jsg::BufferSource source(js, impl.view.getHandle(js));\n        auto entry = kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, source.detach(js)));\n        controller.impl.enqueue(js, kj::mv(entry), controller.getSelf());\n      } else {\n        JSG_REQUIRE(bytesWritten > 0, TypeError,\n            \"The bytesWritten must be more than zero while the stream is open.\");\n        if (impl.readRequest->respond(js, bytesWritten)) {\n          // The read request was fulfilled, we need to invalidate.\n          shouldInvalidate = true;\n        } else {\n          // The response did not fulfill the minimum requirements of the read.\n          // We do not want to invalidate the read request and we need to update the\n          // view so that on the next read the view will be properly adjusted.\n          impl.updateView(js);\n        }\n      }\n      controller.pull(js);\n      if (shouldInvalidate) {\n        invalidate(js);\n      }\n    }\n  });\n}\n\nvoid ReadableStreamBYOBRequest::respondWithNewView(jsg::Lock& js, jsg::BufferSource view) {\n  auto& impl = JSG_REQUIRE_NONNULL(\n      maybeImpl, TypeError, \"This ReadableStreamBYOBRequest has been invalidated.\");\n  JSG_REQUIRE(impl.controller->isValid(), Error, \"The ReadableStreamBYOBRequest is invalid.\");\n  impl.controller->runIfAlive([&](ReadableByteStreamController& controller) {\n    if (!controller.canCloseOrEnqueue()) {\n      JSG_REQUIRE(view.size() == 0, TypeError,\n          \"The view byte length must be zero after the stream is closed.\");\n\n      if (FeatureFlags::get(js).getPedanticWpt()) {\n        // Per the spec, when the stream is closed:\n        // 1. The view byte length must be zero (TypeError if not)\n        // 2. The underlying buffer must not be detached (TypeError)\n        // 3. The buffer byte length must not be zero (RangeError)\n        // 4. The buffer byte length must match the original (RangeError)\n        auto handle = view.getHandle(js);\n        auto buffer = handle->IsArrayBuffer() ? handle.As<v8::ArrayBuffer>()\n                                              : handle.As<v8::ArrayBufferView>()->Buffer();\n        JSG_REQUIRE(\n            !buffer->WasDetached(), TypeError, \"The underlying ArrayBuffer has been detached.\");\n\n        JSG_REQUIRE(view.canDetach(js), TypeError, \"Unable to use non-detachable ArrayBuffer.\");\n        // Use the stored values since the ByobRequest may have been invalidated during close.\n        auto actualBufferByteLength = buffer->ByteLength();\n        JSG_REQUIRE(\n            actualBufferByteLength != 0, RangeError, \"The underlying ArrayBuffer is zero-length.\");\n        JSG_REQUIRE(actualBufferByteLength == impl.originalBufferByteLength, RangeError,\n            \"The underlying ArrayBuffer is not the correct length.\");\n        // The view's byte offset must match the original byte offset plus bytes filled.\n        auto viewByteOffset =\n            handle->IsArrayBuffer() ? 0 : handle.As<v8::ArrayBufferView>()->ByteOffset();\n        JSG_REQUIRE(viewByteOffset == impl.originalByteOffsetPlusBytesFilled, RangeError,\n            \"The view has an invalid byte offset.\");\n      } else {\n        KJ_ASSERT(impl.readRequest->isInvalidated());\n      }\n\n      invalidate(js);\n    } else {\n      bool shouldInvalidate = false;\n      if (impl.readRequest->isInvalidated() && controller.impl.consumerCount() >= 1) {\n        // While this particular request may be invalidated, there are still\n        // other branches we can push the data to. Let's do so.\n        auto entry = kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, view.detach(js)));\n        controller.impl.enqueue(js, kj::mv(entry), controller.getSelf());\n      } else {\n        JSG_REQUIRE(view.size() > 0, TypeError,\n            \"The view byte length must be more than zero while the stream is open.\");\n        if (impl.readRequest->respondWithNewView(js, kj::mv(view))) {\n          // The read request was fulfilled, we need to invalidate.\n          shouldInvalidate = true;\n        } else {\n          // The response did not fulfill the minimum requirements of the read.\n          // We do not want to invalidate the read request and we need to update the\n          // view so that on the next read the view will be properly adjusted.\n          impl.updateView(js);\n        }\n      }\n\n      controller.pull(js);\n      if (shouldInvalidate) {\n        invalidate(js);\n      }\n    }\n  });\n}\n\nbool ReadableStreamBYOBRequest::isPartiallyFulfilled() {\n  KJ_IF_SOME(impl, maybeImpl) {\n    return impl.readRequest->isPartiallyFulfilled();\n  }\n  return false;\n}\n\n// ======================================================================================\n\nReadableByteStreamController::ReadableByteStreamController(\n    UnderlyingSource underlyingSource, StreamQueuingStrategy queuingStrategy)\n    : weakSelf(kj::rc<WeakRef<ReadableByteStreamController>>(\n          kj::Badge<ReadableByteStreamController>{}, *this)),\n      ioContext(tryGetIoContext()),\n      impl(kj::mv(underlyingSource), kj::mv(queuingStrategy)) {}\n\nReadableByteStreamController::~ReadableByteStreamController() noexcept(false) {\n  weakSelf->invalidate();\n}\n\nvoid ReadableByteStreamController::start(jsg::Lock& js) {\n  impl.start(js, JSG_THIS);\n}\n\nbool ReadableByteStreamController::canCloseOrEnqueue() {\n  return impl.canCloseOrEnqueue();\n}\n\nbool ReadableByteStreamController::hasBackpressure() {\n  return !impl.shouldCallPull();\n}\n\nkj::Maybe<int> ReadableByteStreamController::getDesiredSize() {\n  return impl.getDesiredSize();\n}\n\nvoid ReadableByteStreamController::visitForGc(jsg::GcVisitor& visitor) {\n  visitor.visit(maybeByobRequest, impl);\n}\n\njsg::Promise<void> ReadableByteStreamController::cancel(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason) {\n  KJ_IF_SOME(byobRequest, maybeByobRequest) {\n    if (impl.consumerCount() == 1) {\n      byobRequest->invalidate(js);\n    }\n  }\n  return impl.cancel(js, JSG_THIS, maybeReason.orDefault(js.undefined()));\n}\n\nvoid ReadableByteStreamController::close(jsg::Lock& js) {\n  KJ_IF_SOME(byobRequest, maybeByobRequest) {\n    JSG_REQUIRE(!byobRequest->isPartiallyFulfilled(), TypeError,\n        \"This ReadableStream was closed with a partial read pending.\");\n  } else if (FeatureFlags::get(js).getPedanticWpt()) {\n    // If maybeByobRequest is not set, check if there's a pending byob request.\n    // If so, materialize it before closing so it remains accessible after\n    // the state changes to Closed. This is required by the spec for proper\n    // respondWithNewView() error handling in the closed state.\n    // Only do this if the queue doesn't have a partially fulfilled read.\n    KJ_IF_SOME(queue, impl.state.tryGetUnsafe<ByteQueue>()) {\n      if (!queue.hasPartiallyFulfilledRead()) {\n        getByobRequest(js);\n      }\n    }\n  }\n  impl.close(js);\n}\n\nvoid ReadableByteStreamController::enqueue(jsg::Lock& js, jsg::BufferSource chunk) {\n  JSG_REQUIRE(chunk.size() > 0, TypeError, \"Cannot enqueue a zero-length ArrayBuffer.\");\n  JSG_REQUIRE(chunk.canDetach(js), TypeError, \"The provided ArrayBuffer must be detachable.\");\n  JSG_REQUIRE(impl.canCloseOrEnqueue(), TypeError, \"This ReadableByteStreamController is closed.\");\n\n  KJ_IF_SOME(byobRequest, maybeByobRequest) {\n    KJ_IF_SOME(view, byobRequest->getView(js)) {\n      JSG_REQUIRE(view.getHandle(js)->ByteLength() > 0, TypeError,\n          \"The byobRequest.view is zero-length or was detached\");\n    }\n    byobRequest->invalidate(js);\n  }\n\n  impl.enqueue(js, kj::rc<ByteQueue::Entry>(jsg::BufferSource(js, chunk.detach(js))), JSG_THIS);\n}\n\nvoid ReadableByteStreamController::error(jsg::Lock& js, v8::Local<v8::Value> reason) {\n  impl.doError(js, js.v8Ref(reason));\n}\n\nkj::Maybe<jsg::Ref<ReadableStreamBYOBRequest>> ReadableByteStreamController::getByobRequest(\n    jsg::Lock& js) {\n  if (maybeByobRequest == kj::none) {\n    KJ_IF_SOME(queue, impl.state.tryGetUnsafe<ByteQueue>()) {\n      KJ_IF_SOME(pendingByob, queue.nextPendingByobReadRequest()) {\n        maybeByobRequest =\n            js.alloc<ReadableStreamBYOBRequest>(js, kj::mv(pendingByob), weakSelf.addRef());\n      }\n    } else {\n      return kj::none;\n    }\n  }\n\n  return maybeByobRequest.map(\n      [&](jsg::Ref<ReadableStreamBYOBRequest>& req) { return req.addRef(); });\n}\n\n// When a consumer receives a read request, but does not have the data available to\n// fulfill the request, the consumer will call pull on the controller to pull that\n// data if needed.\nvoid ReadableByteStreamController::pull(jsg::Lock& js) {\n  impl.pullIfNeeded(js, JSG_THIS);\n}\n\nvoid ReadableByteStreamController::forcePull(jsg::Lock& js) {\n  impl.forcePullIfNeeded(js, JSG_THIS);\n}\n\nkj::Own<ByteQueue::Consumer> ReadableByteStreamController::getConsumer(\n    kj::Maybe<ByteQueue::ConsumerImpl::StateListener&> stateListener) {\n  return impl.getConsumer(stateListener);\n}\n\n// ======================================================================================\n\nReadableStreamJsController::ReadableStreamJsController(): ioContext(tryGetIoContext()) {}\n\nReadableStreamJsController::ReadableStreamJsController(StreamStates::Closed closed)\n    : ioContext(tryGetIoContext()) {\n  state.transitionTo<StreamStates::Closed>();\n}\n\nReadableStreamJsController::ReadableStreamJsController(StreamStates::Errored errored)\n    : ioContext(tryGetIoContext()) {\n  state.transitionTo<StreamStates::Errored>(kj::mv(errored));\n}\n\nReadableStreamJsController::ReadableStreamJsController(jsg::Lock& js, ValueReadable& consumer)\n    : ioContext(tryGetIoContext()) {\n  state.transitionTo<kj::Own<ValueReadable>>(consumer.clone(js, *this));\n}\n\nReadableStreamJsController::ReadableStreamJsController(jsg::Lock& js, ByteReadable& consumer)\n    : ioContext(tryGetIoContext()) {\n  state.transitionTo<kj::Own<ByteReadable>>(consumer.clone(js, *this));\n}\n\njsg::Ref<ReadableStream> ReadableStreamJsController::addRef() {\n  return KJ_REQUIRE_NONNULL(owner).addRef();\n}\n\njsg::Promise<void> ReadableStreamJsController::cancel(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason) {\n  disturbed = true;\n\n  const auto doCancel = [&](auto& consumer) {\n    auto reason = js.v8Ref(maybeReason.orDefault([&] { return js.v8Undefined(); }));\n    KJ_DEFER(doClose(js));\n    return consumer->cancel(js, reason.getHandle(js));\n  };\n\n  // Check for pending state first (deferred close/error during a read operation)\n  if (state.pendingStateIs<StreamStates::Closed>()) {\n    return js.resolvedPromise();\n  }\n  KJ_IF_SOME(pendingError, state.tryGetPendingStateUnsafe<StreamStates::Errored>()) {\n    return js.rejectedPromise<void>(pendingError.addRef(js));\n  }\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(initial, Initial) {\n      // Stream not yet set up, treat as closed.\n      return js.resolvedPromise();\n    }\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      return js.resolvedPromise();\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return js.rejectedPromise<void>(errored.addRef(js));\n    }\n    KJ_CASE_ONEOF(consumer, kj::Own<ValueReadable>) {\n      if (canceling) return js.resolvedPromise();\n      canceling = true;\n      return doCancel(consumer);\n    }\n    KJ_CASE_ONEOF(consumer, kj::Own<ByteReadable>) {\n      if (canceling) return js.resolvedPromise();\n      canceling = true;\n      return doCancel(consumer);\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\n// Finalizes the closed state of this ReadableStream. The connection to the underlying\n// controller is released with no further action. Importantly, this method is triggered\n// by the underlying controller as a result of that controller closing or being canceled.\n// We detach ourselves from the underlying controller by releasing the ValueReadable or\n// ByteReadable in the state and changing that to closed.\n// We also clean up other state here.\nvoid ReadableStreamJsController::doClose(jsg::Lock& js) {\n  // If already in a terminal state, nothing to do.\n  if (state.isTerminal()) return;\n\n  // deferTransitionTo will defer if an operation is in progress, otherwise transition immediately.\n  // Returns true if transition happened immediately.\n  if (state.deferTransitionTo<StreamStates::Closed>()) {\n    lock.onClose(js);\n  }\n  // If deferred, lock.onClose will be called when the pending state is applied\n  // via applyPendingState in deferControllerStateChange.\n}\n\n// As with doClose(), doError() finalizes the error state of this ReadableStream.\n// The connection to the underlying controller is released with no further action.\n// This method is triggered by the underlying controller as a result of that controller\n// erroring. We detach ourselves from the underlying controller by releasing the ValueReadable\n// or ByteReadable in the state and changing that to errored.\n// We also clean up other state here.\nvoid ReadableStreamJsController::doError(jsg::Lock& js, v8::Local<v8::Value> reason) {\n  // If already in a terminal state, nothing to do.\n  if (state.isTerminal()) return;\n\n  // deferTransitionTo will defer if an operation is in progress, otherwise transition immediately.\n  // Returns true if transition happened immediately.\n  if (state.deferTransitionTo<StreamStates::Errored>(js.v8Ref(reason))) {\n    lock.onError(js, reason);\n  }\n  // If deferred, lock.onError will be called when the pending state is applied\n  // via applyPendingState in deferControllerStateChange.\n}\n\nbool ReadableStreamJsController::isByteOriented() const {\n  return state.is<kj::Own<ByteReadable>>();\n}\n\nbool ReadableStreamJsController::isClosedOrErrored() const {\n  // Check if we're in a terminal state or have one pending\n  return state.isTerminal() || state.hasPendingState();\n}\n\nbool ReadableStreamJsController::isClosed() const {\n  // Check current state first, then pending state\n  if (state.is<StreamStates::Closed>()) return true;\n  return state.pendingStateIs<StreamStates::Closed>();\n}\n\nbool ReadableStreamJsController::isDisturbed() {\n  return disturbed;\n}\n\nbool ReadableStreamJsController::isLockedToReader() const {\n  return lock.isLockedToReader();\n}\n\nbool ReadableStreamJsController::lockReader(jsg::Lock& js, Reader& reader) {\n  return lock.lockReader(js, *this, reader);\n}\n\njsg::Promise<void> ReadableStreamJsController::pipeTo(\n    jsg::Lock& js, WritableStreamController& destination, PipeToOptions options) {\n  KJ_DASSERT(!isLockedToReader());\n  KJ_DASSERT(!destination.isLockedToWriter());\n\n  disturbed = true;\n  KJ_IF_SOME(promise, destination.tryPipeFrom(js, addRef(), kj::mv(options))) {\n    return kj::mv(promise);\n  }\n\n  return js.rejectedPromise<void>(\n      js.v8TypeError(\"This ReadableStream cannot be piped to this WritableStream\"_kj));\n}\n\nkj::Maybe<jsg::Promise<ReadResult>> ReadableStreamJsController::read(\n    jsg::Lock& js, kj::Maybe<ByobOptions> maybeByobOptions) {\n  disturbed = true;\n\n  KJ_IF_SOME(byobOptions, maybeByobOptions) {\n    byobOptions.detachBuffer = true;\n    auto view = byobOptions.bufferView.getHandle(js);\n    if (!view->Buffer()->IsDetachable()) {\n      return js.rejectedPromise<ReadResult>(\n          js.v8TypeError(\"Unabled to use non-detachable ArrayBuffer.\"_kj));\n    }\n\n    if (view->ByteLength() == 0 || view->Buffer()->ByteLength() == 0) {\n      return js.rejectedPromise<ReadResult>(\n          js.v8TypeError(\"Unable to use a zero-length ArrayBuffer.\"_kj));\n    }\n\n    // Check for pending error first (deferred error during a prior read operation)\n    KJ_IF_SOME(pendingError, state.tryGetPendingStateUnsafe<StreamStates::Errored>()) {\n      return js.rejectedPromise<ReadResult>(pendingError.addRef(js));\n    }\n\n    if (state.is<StreamStates::Closed>() || state.pendingStateIs<StreamStates::Closed>()) {\n      // If it is a BYOB read, then the spec requires that we return an empty\n      // view of the same type provided, that uses the same backing memory\n      // as that provided, but with zero-length.\n      auto source = jsg::BufferSource(js, byobOptions.bufferView.getHandle(js));\n      auto store = source.detach(js);\n      store.consume(store.size());\n      return js.resolvedPromise(ReadResult{\n        .value = js.v8Ref(store.createHandle(js)),\n        .done = true,\n      });\n    }\n  }\n\n  // Check for pending state (deferred close/error during a prior read operation)\n  if (state.pendingStateIs<StreamStates::Closed>()) {\n    // The closed state for BYOB reads is handled in the maybeByobOptions check above.\n    KJ_ASSERT(maybeByobOptions == kj::none);\n    return js.resolvedPromise(ReadResult{.done = true});\n  }\n  KJ_IF_SOME(pendingError, state.tryGetPendingStateUnsafe<StreamStates::Errored>()) {\n    return js.rejectedPromise<ReadResult>(pendingError.addRef(js));\n  }\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(initial, Initial) {\n      // Stream not yet set up, treat as closed.\n      KJ_ASSERT(maybeByobOptions == kj::none);\n      return js.resolvedPromise(ReadResult{.done = true});\n    }\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      // The closed state for BYOB reads is handled in the maybeByobOptions check above.\n      KJ_ASSERT(maybeByobOptions == kj::none);\n      return js.resolvedPromise(ReadResult{.done = true});\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return js.rejectedPromise<ReadResult>(errored.addRef(js));\n    }\n    KJ_CASE_ONEOF(consumer, kj::Own<ValueReadable>) {\n      // The ReadableStreamDefaultController does not support ByobOptions.\n      // It should never happen, but let's make sure.\n      KJ_ASSERT(maybeByobOptions == kj::none);\n      return deferControllerStateChange(js, *this, [&]() mutable { return consumer->read(js); });\n    }\n    KJ_CASE_ONEOF(consumer, kj::Own<ByteReadable>) {\n      return deferControllerStateChange(\n          js, *this, [&]() mutable { return consumer->read(js, kj::mv(maybeByobOptions)); });\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::Maybe<jsg::Promise<DrainingReadResult>> ReadableStreamJsController::drainingRead(\n    jsg::Lock& js, size_t maxRead) {\n  disturbed = true;\n\n  // Check for pending state first (deferred close/error during a prior read operation)\n  if (state.pendingStateIs<StreamStates::Closed>()) {\n    return js.resolvedPromise(DrainingReadResult{\n      .chunks = kj::Array<kj::Array<kj::byte>>(),\n      .done = true,\n    });\n  }\n  KJ_IF_SOME(pendingError, state.tryGetPendingStateUnsafe<StreamStates::Errored>()) {\n    return js.rejectedPromise<DrainingReadResult>(pendingError.addRef(js));\n  }\n\n  // Like deferControllerStateChange for regular reads, we need to prevent the controller\n  // state from being destroyed while a draining read's promise callbacks are pending.\n  // The drainingRead implementation captures `this` (the Consumer) in promise lambdas to\n  // clear hasPendingDrainingRead. If the state is changed (destroying the Consumer) before\n  // those callbacks run, we get a use-after-free.\n  //\n  // CRITICAL: state.beginOperation() MUST be called BEFORE consumer->drainingRead(), not\n  // after. The consumer->drainingRead() call may trigger onConsumerWantsData -> forcePull\n  // -> close/error, which calls deferTransitionTo. If no operation is in progress at that\n  // point, the transition fires immediately, destroying the Consumer while we're still\n  // inside its method and before the returned promise's .then() callbacks are set up.\n  // The endOperation() happens in the .then() callbacks below, ensuring the deferred\n  // state change only fires after the promise resolves/rejects and the Consumer's\n  // this-capturing callbacks have already run.\n  auto wrapDrainingRead =\n      [this](jsg::Lock& js,\n          jsg::Promise<DrainingReadResult> promise) -> jsg::Promise<DrainingReadResult> {\n    return promise.then(js, [this](jsg::Lock& js, DrainingReadResult result) {\n      if (state.endOperation()) {\n        // A pending state was applied. Call the appropriate callback.\n        if (state.template is<StreamStates::Closed>()) {\n          lock.onClose(js);\n        } else if (state.template is<StreamStates::Errored>()) {\n          KJ_IF_SOME(err, state.template tryGetUnsafe<StreamStates::Errored>()) {\n            lock.onError(js, err.getHandle(js));\n            // The error was applied during this operation — the data we collected\n            // may be invalid. Discard it and propagate the error rather than\n            // silently returning possibly-corrupt data.\n            js.throwException(err.addRef(js));\n          }\n        }\n      }\n      return kj::mv(result);\n    }, [this](jsg::Lock& js, jsg::Value exception) -> DrainingReadResult {\n      state.clearPendingState();\n      (void)state.endOperation();\n      js.throwException(kj::mv(exception));\n    });\n  };\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(initial, Initial) {\n      // Stream not yet set up, treat as closed.\n      return js.resolvedPromise(DrainingReadResult{\n        .chunks = kj::Array<kj::Array<kj::byte>>(),\n        .done = true,\n      });\n    }\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      return js.resolvedPromise(DrainingReadResult{\n        .chunks = kj::Array<kj::Array<kj::byte>>(),\n        .done = true,\n      });\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return js.rejectedPromise<DrainingReadResult>(errored.addRef(js));\n    }\n    KJ_CASE_ONEOF(consumer, kj::Own<ValueReadable>) {\n      // beginOperation MUST be before consumer->drainingRead() — see comment above.\n      state.beginOperation();\n      JSG_TRY(js) {\n        return wrapDrainingRead(js, consumer->drainingRead(js, maxRead));\n      }\n      JSG_CATCH(exception) {\n        state.clearPendingState();\n        (void)state.endOperation();\n        doError(js, exception.getHandle(js));\n        return js.rejectedPromise<DrainingReadResult>(kj::mv(exception));\n      };\n    }\n    KJ_CASE_ONEOF(consumer, kj::Own<ByteReadable>) {\n      // beginOperation MUST be before consumer->drainingRead() — see comment above.\n      state.beginOperation();\n      JSG_TRY(js) {\n        return wrapDrainingRead(js, consumer->drainingRead(js, maxRead));\n      }\n      JSG_CATCH(exception) {\n        state.clearPendingState();\n        (void)state.endOperation();\n        doError(js, exception.getHandle(js));\n        return js.rejectedPromise<DrainingReadResult>(kj::mv(exception));\n      };\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid ReadableStreamJsController::releaseReader(Reader& reader, kj::Maybe<jsg::Lock&> maybeJs) {\n  lock.releaseReader(*this, reader, maybeJs);\n}\n\nReadableStreamController::Tee ReadableStreamJsController::tee(jsg::Lock& js) {\n  JSG_REQUIRE(!isLockedToReader(), TypeError, \"This ReadableStream is locked to a reader.\");\n  lock.state.transitionTo<Locked>();\n  disturbed = true;\n\n  // This will leave this stream locked, disturbed, and closed.\n\n  // Check for pending state first (deferred close/error during a prior read operation)\n  if (state.pendingStateIs<StreamStates::Closed>()) {\n    return Tee{\n      .branch1 =\n          js.alloc<ReadableStream>(kj::heap<ReadableStreamJsController>(StreamStates::Closed())),\n      .branch2 =\n          js.alloc<ReadableStream>(kj::heap<ReadableStreamJsController>(StreamStates::Closed())),\n    };\n  }\n  KJ_IF_SOME(pendingError, state.tryGetPendingStateUnsafe<StreamStates::Errored>()) {\n    return Tee{\n      .branch1 =\n          js.alloc<ReadableStream>(kj::heap<ReadableStreamJsController>(pendingError.addRef(js))),\n      .branch2 =\n          js.alloc<ReadableStream>(kj::heap<ReadableStreamJsController>(pendingError.addRef(js))),\n    };\n  }\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(initial, Initial) {\n      // Stream not yet set up, treat as closed.\n      return Tee{\n        .branch1 =\n            js.alloc<ReadableStream>(kj::heap<ReadableStreamJsController>(StreamStates::Closed())),\n        .branch2 =\n            js.alloc<ReadableStream>(kj::heap<ReadableStreamJsController>(StreamStates::Closed())),\n      };\n    }\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      return Tee{\n        .branch1 =\n            js.alloc<ReadableStream>(kj::heap<ReadableStreamJsController>(StreamStates::Closed())),\n        .branch2 =\n            js.alloc<ReadableStream>(kj::heap<ReadableStreamJsController>(StreamStates::Closed())),\n      };\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return Tee{\n        .branch1 =\n            js.alloc<ReadableStream>(kj::heap<ReadableStreamJsController>(errored.addRef(js))),\n        .branch2 =\n            js.alloc<ReadableStream>(kj::heap<ReadableStreamJsController>(errored.addRef(js))),\n      };\n    }\n    KJ_CASE_ONEOF(consumer, kj::Own<ValueReadable>) {\n      KJ_DEFER(state.transitionTo<StreamStates::Closed>());\n      // We create two additional streams that clone this stream's consumer state,\n      // then close this stream's consumer.\n      return Tee{\n        .branch1 = js.alloc<ReadableStream>(kj::heap<ReadableStreamJsController>(js, *consumer)),\n        .branch2 = js.alloc<ReadableStream>(kj::heap<ReadableStreamJsController>(js, *consumer)),\n      };\n    }\n    KJ_CASE_ONEOF(consumer, kj::Own<ByteReadable>) {\n      KJ_DEFER(state.transitionTo<StreamStates::Closed>());\n      // We create two additional streams that clone this stream's consumer state,\n      // then close this stream's consumer.\n      return Tee{\n        .branch1 = js.alloc<ReadableStream>(kj::heap<ReadableStreamJsController>(js, *consumer)),\n        .branch2 = js.alloc<ReadableStream>(kj::heap<ReadableStreamJsController>(js, *consumer)),\n      };\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid ReadableStreamJsController::setOwnerRef(ReadableStream& stream) {\n  KJ_ASSERT(owner == kj::none);\n  owner = &stream;\n}\n\nvoid ReadableStreamJsController::setup(jsg::Lock& js,\n    jsg::Optional<UnderlyingSource> maybeUnderlyingSource,\n    jsg::Optional<StreamQueuingStrategy> maybeQueuingStrategy) {\n  auto underlyingSource = kj::mv(maybeUnderlyingSource).orDefault({});\n  auto queuingStrategy = kj::mv(maybeQueuingStrategy).orDefault({});\n  auto type = underlyingSource.type.map([](kj::StringPtr s) { return s; }).orDefault(\"\"_kj);\n\n  expectedLength = underlyingSource.expectedLength;\n\n  if (type == \"bytes\") {\n    // Per spec, autoAllocateChunkSize should only be set if the user explicitly provides it.\n    // If not set, the underlying source's pull method won't receive a byobRequest for\n    // non-BYOB reads and must use controller.enqueue() instead.\n    //\n    // However, our original implementation always defaulted to 4096, so we need a compat flag\n    // to control this behavior. Default to legacy behavior if flags aren't available.\n    bool useSpecCompliantBehavior = false;\n    KJ_IF_SOME(flags, FeatureFlags::tryGet(js)) {\n      useSpecCompliantBehavior = flags.getNoAutoAllocateChunkSize();\n    }\n\n    kj::Maybe<int> autoAllocateChunkSize;\n    if (useSpecCompliantBehavior) {\n      // Spec-compliant: only set if user explicitly provides it\n      autoAllocateChunkSize =\n          underlyingSource.autoAllocateChunkSize.map([](int size) { return size; });\n    } else {\n      // Legacy behavior: default to 4096 if not provided\n      autoAllocateChunkSize = underlyingSource.autoAllocateChunkSize.orDefault(\n          UnderlyingSource::DEFAULT_AUTO_ALLOCATE_CHUNK_SIZE);\n    }\n\n    auto controller =\n        js.alloc<ReadableByteStreamController>(kj::mv(underlyingSource), kj::mv(queuingStrategy));\n\n    KJ_IF_SOME(chunkSize, autoAllocateChunkSize) {\n      JSG_REQUIRE(chunkSize > 0, TypeError, \"The autoAllocateChunkSize option cannot be zero.\");\n    }\n\n    // We account for the memory usage of the ByteReadable and its controller together because\n    // their lifetimes are identical (in practice) and memory accounting itself has a memory\n    // overhead. The same applies to ValueReadable below.\n    state.transitionTo<kj::Own<ByteReadable>>(\n        kj::heap<ByteReadable>(controller.addRef(), *this, autoAllocateChunkSize)\n            .attach(js.getExternalMemoryAdjustment(\n                sizeof(ByteReadable) + sizeof(ReadableByteStreamController))));\n    controller->start(js);\n  } else {\n    JSG_REQUIRE(\n        type == \"\", TypeError, kj::str(\"\\\"\", type, \"\\\" is not a valid type of ReadableStream.\"));\n    auto controller = js.alloc<ReadableStreamDefaultController>(\n        kj::mv(underlyingSource), kj::mv(queuingStrategy));\n    state.transitionTo<kj::Own<ValueReadable>>(\n        kj::heap<ValueReadable>(controller.addRef(), *this)\n            .attach(js.getExternalMemoryAdjustment(\n                sizeof(ValueReadable) + sizeof(ReadableStreamDefaultController))));\n    controller->start(js);\n  }\n}\n\nkj::Maybe<ReadableStreamController::PipeController&> ReadableStreamJsController::tryPipeLock() {\n  return lock.tryPipeLock(*this);\n}\n\nvoid ReadableStreamJsController::visitForGc(jsg::GcVisitor& visitor) {\n  // Visit pending state if it's an error (Closed has no GC-traceable content)\n  KJ_IF_SOME(pendingError, state.tryGetPendingStateUnsafe<StreamStates::Errored>()) {\n    visitor.visit(pendingError);\n  }\n\n  // Note: We cannot use state.visitForGc(visitor) here because the state machine's\n  // visitForGc passes kj::Own<T>& to visitor.visit(), but GcVisitor expects T& for\n  // types with visitForGc methods. We must dereference kj::Own manually.\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(initial, Initial) {}\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {}\n    KJ_CASE_ONEOF(error, StreamStates::Errored) {\n      visitor.visit(error);\n    }\n    KJ_CASE_ONEOF(consumer, kj::Own<ValueReadable>) {\n      visitor.visit(*consumer);\n    }\n    KJ_CASE_ONEOF(consumer, kj::Own<ByteReadable>) {\n      visitor.visit(*consumer);\n    }\n  }\n  visitor.visit(lock);\n}\n\nkj::Maybe<int> ReadableStreamJsController::getDesiredSize() {\n  // If there's a pending state transition, return none\n  if (state.hasPendingState()) {\n    return kj::none;\n  }\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(initial, Initial) {\n      return kj::none;\n    }\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      return kj::none;\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return kj::none;\n    }\n    KJ_CASE_ONEOF(consumer, kj::Own<ValueReadable>) {\n      return consumer->getDesiredSize();\n    }\n    KJ_CASE_ONEOF(consumer, kj::Own<ByteReadable>) {\n      return consumer->getDesiredSize();\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::Maybe<v8::Local<v8::Value>> ReadableStreamJsController::isErrored(jsg::Lock& js) {\n  // Check for pending error first\n  KJ_IF_SOME(pendingError, state.tryGetPendingStateUnsafe<StreamStates::Errored>()) {\n    return pendingError.getHandle(js);\n  }\n  // Pending Closed means not errored, so we can just check current state\n  return state.tryGetUnsafe<StreamStates::Errored>().map(\n      [&](jsg::Value& reason) { return reason.getHandle(js); });\n}\n\nbool ReadableStreamJsController::canCloseOrEnqueue() {\n  // If there's a pending state transition, can't close or enqueue\n  if (state.hasPendingState()) {\n    return false;\n  }\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(initial, Initial) {\n      return false;\n    }\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      return false;\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return false;\n    }\n    KJ_CASE_ONEOF(consumer, kj::Own<ValueReadable>) {\n      return consumer->canCloseOrEnqueue();\n    }\n    KJ_CASE_ONEOF(consumer, kj::Own<ByteReadable>) {\n      return consumer->canCloseOrEnqueue();\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nbool ReadableStreamJsController::hasBackpressure() {\n  KJ_IF_SOME(size, getDesiredSize()) {\n    return size <= 0;\n  }\n  return false;\n}\n\nkj::Maybe<kj::OneOf<DefaultController, ByobController>> ReadableStreamJsController::\n    getController() {\n  // If there's a pending state transition, return none\n  if (state.hasPendingState()) {\n    return kj::none;\n  }\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(initial, Initial) {\n      return kj::none;\n    }\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      return kj::none;\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return kj::none;\n    }\n    KJ_CASE_ONEOF(consumer, kj::Own<ValueReadable>) {\n      return consumer->getControllerRef();\n    }\n    KJ_CASE_ONEOF(consumer, kj::Own<ByteReadable>) {\n      return consumer->getControllerRef();\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nnamespace {\n// Consumes all bytes from a stream, buffering in memory, with the purpose\n// of producing either a single concatenated kj::Array<byte> or kj::String.\nclass AllReader {\n public:\n  using PartList = kj::Array<kj::ArrayPtr<byte>>;\n\n  AllReader(jsg::Ref<ReadableStream> stream, uint64_t limit)\n      : state(State::create<jsg::Ref<ReadableStream>>(kj::mv(stream))),\n        limit(limit) {}\n  KJ_DISALLOW_COPY_AND_MOVE(AllReader);\n\n  jsg::Promise<jsg::BufferSource> allBytes(jsg::Lock& js) {\n    return loop(js).then(js, [this](auto& js, PartList&& partPtrs) -> jsg::BufferSource {\n      auto out = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, runningTotal);\n      copyInto(out.asArrayPtr(), partPtrs.asPtr());\n      return jsg::BufferSource(js, kj::mv(out));\n    });\n  }\n\n  jsg::Promise<kj::String> allText(\n      jsg::Lock& js, ReadAllTextOption option = ReadAllTextOption::NULL_TERMINATE) {\n    return loop(js).then(js, [this, option](auto& js, PartList&& partPtrs) {\n      // Strip UTF-8 BOM if requested\n      if ((option & ReadAllTextOption::STRIP_BOM) && partPtrs.size() > 0 &&\n          hasUtf8Bom(partPtrs[0])) {\n        partPtrs[0] = partPtrs[0].slice(UTF8_BOM_SIZE);\n        runningTotal -= UTF8_BOM_SIZE;\n      }\n\n      JSG_REQUIRE(runningTotal <= v8::String::kMaxLength, RangeError,\n          \"String length exceeds v8::String::kMaxLength.\");\n\n      auto out = kj::heapArray<char>(runningTotal + 1);\n      copyInto(out.first(out.size() - 1).asBytes(), partPtrs.asPtr());\n      out.back() = '\\0';\n      return kj::String(kj::mv(out));\n    });\n  }\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    state.visitForGc(visitor);\n  }\n\n private:\n  // State machine for AllReader:\n  // Closed is terminal, Errored is implicitly terminal via ErrorState.\n  // jsg::Ref<ReadableStream> is the active state (still reading).\n  using State = StateMachine<TerminalStates<StreamStates::Closed>,\n      ErrorState<StreamStates::Errored>,\n      ActiveState<jsg::Ref<ReadableStream>>,\n      StreamStates::Closed,\n      StreamStates::Errored,\n      jsg::Ref<ReadableStream>>;\n  State state;\n  uint64_t limit;\n  kj::Vector<jsg::BufferSource> parts;\n  uint64_t runningTotal = 0;\n\n  jsg::Promise<PartList> loop(jsg::Lock& js) {\n    KJ_SWITCH_ONEOF(state) {\n      KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n        return js.resolvedPromise(KJ_MAP(p, parts) { return p.asArrayPtr(); });\n      }\n      KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n        return js.template rejectedPromise<PartList>(errored.getHandle(js));\n      }\n      KJ_CASE_ONEOF(readable, jsg::Ref<ReadableStream>) {\n        // Note that these nested lambda retain references to `this` and `readable`\n        // and are passed into to promise returned by this method. It is the responsibility\n        // of the caller to ensure that the AllReader instance is kept alive until the\n        // promise is settled.\n        auto onSuccess = JSG_VISITABLE_LAMBDA((this, readable = readable.addRef()), (readable),\n            (jsg::Lock & js, ReadResult result) mutable->jsg::Promise<PartList> {\n              if (result.done) {\n              state.template transitionTo<StreamStates::Closed>();\n              return loop(js);\n              }\n\n              // If we're not done, the result value must be interpretable as\n              // bytes for the read to make any sense.\n              auto handle = KJ_ASSERT_NONNULL(result.value).getHandle(js);\n              if (!handle->IsArrayBufferView() && !handle->IsArrayBuffer()) {\n              auto error = js.v8TypeError(\"This ReadableStream did not return bytes.\");\n              state.template transitionTo<StreamStates::Errored>(js.v8Ref(error));\n              return readable->getController().cancel(js, error).then(\n                  js, [&](jsg::Lock& js) { return loop(js); });\n              }\n\n              jsg::BufferSource bufferSource(js, handle);\n\n              if (bufferSource.size() == 0) {\n              // Weird but allowed, we'll skip it.\n              return loop(js);\n              }\n\n              if ((runningTotal + bufferSource.size()) > limit) {\n              auto error = js.v8TypeError(\"Memory limit exceeded before EOF.\");\n              state.template transitionTo<StreamStates::Errored>(js.v8Ref(error));\n              return readable->getController().cancel(js, error).then(\n                  js, [&](jsg::Lock& js) { return loop(js); });\n              }\n\n              runningTotal += bufferSource.size();\n              parts.add(bufferSource.copy(js));\n              return loop(js);\n            });\n\n        auto onFailure = [this](auto& js, jsg::Value exception) -> jsg::Promise<PartList> {\n          // In this case the stream should already be errored.\n          state.template transitionTo<StreamStates::Errored>(js.v8Ref(exception.getHandle(js)));\n          return loop(js);\n        };\n\n        return maybeAddFunctor(js, KJ_ASSERT_NONNULL(readable->getController().read(js, kj::none)),\n            kj::mv(onSuccess), kj::mv(onFailure));\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  void copyInto(kj::ArrayPtr<byte> out, kj::ArrayPtr<kj::ArrayPtr<byte>> in) {\n    for (auto& part: in) {\n      KJ_ASSERT(part.size() <= out.size());\n      out.first(part.size()).copyFrom(part);\n      out = out.slice(part.size());\n    }\n  }\n};\n\n// PumpToReader implements the original JS promise-loop approach to pumping data from\n// a ReadableStream to a WritableStreamSink. It reads one chunk at a time using the\n// standard read() API, writes each chunk to the sink, and loops until done or errored.\n// This is the fallback path used when the ENABLE_DRAINING_READ_ON_STANDARD_STREAMS\n// autogate is not enabled.\nclass PumpToReader {\n public:\n  PumpToReader(jsg::Ref<ReadableStream> stream, kj::Own<WritableStreamSink> sink, bool end)\n      : ioContext(IoContext::current()),\n        state(State::create<jsg::Ref<ReadableStream>>(kj::mv(stream))),\n        sink(kj::mv(sink)),\n        self(kj::refcounted<WeakRef<PumpToReader>>(kj::Badge<PumpToReader>{}, *this)),\n        end(end) {}\n  KJ_DISALLOW_COPY_AND_MOVE(PumpToReader);\n\n  ~PumpToReader() noexcept(false) {\n    self->invalidate();\n    // Ensure that if a write promise is pending it is proactively canceled.\n    canceler.cancel(\"PumpToReader was destroyed\");\n  }\n\n  kj::Promise<void> pumpTo(jsg::Lock& js) {\n    ioContext.requireCurrentOrThrowJs();\n    KJ_SWITCH_ONEOF(state) {\n      KJ_CASE_ONEOF(stream, jsg::Ref<ReadableStream>) {\n        auto readable = stream.addRef();\n        state.template transitionTo<Pumping>();\n        return ioContext.awaitJs(\n            js, pumpLoop(js, ioContext, kj::mv(readable), ioContext.addObject(self->addRef())));\n      }\n      KJ_CASE_ONEOF(pumping, Pumping) {\n        return KJ_EXCEPTION(FAILED, \"pumping is already in progress\");\n      }\n      KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n        return KJ_EXCEPTION(FAILED, \"stream has already been consumed\");\n      }\n      KJ_CASE_ONEOF(errored, kj::Exception) {\n        return kj::cp(errored);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n private:\n  struct Pumping {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"pumping\"_kj;\n  };\n  IoContext& ioContext;\n\n  using State = StateMachine<TerminalStates<StreamStates::Closed>,\n      ErrorState<kj::Exception>,\n      Pumping,\n      StreamStates::Closed,\n      kj::Exception,\n      jsg::Ref<ReadableStream>>;\n  State state;\n  kj::Own<WritableStreamSink> sink;\n  kj::Own<WeakRef<PumpToReader>> self;\n  kj::Canceler canceler;\n  bool end;\n\n  bool isErroredOrClosed() {\n    return state.isTerminal();\n  }\n\n  jsg::Promise<void> pumpLoop(jsg::Lock& js,\n      IoContext& ioContext,\n      jsg::Ref<ReadableStream> readable,\n      IoOwn<WeakRef<PumpToReader>> pumpToReader) {\n    ioContext.requireCurrentOrThrowJs();\n\n    KJ_SWITCH_ONEOF(state) {\n      KJ_CASE_ONEOF(ready, jsg::Ref<ReadableStream>) {\n        KJ_UNREACHABLE;\n      }\n      KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n        return end ? ioContext.awaitIoLegacy(js, sink->end().attach(kj::mv(sink)))\n                   : js.resolvedPromise();\n      }\n      KJ_CASE_ONEOF(errored, kj::Exception) {\n        if (end) {\n          sink->abort(kj::cp(errored));\n        }\n        return js.rejectedPromise<void>(kj::cp(errored));\n      }\n      KJ_CASE_ONEOF(pumping, Pumping) {\n        using Result = kj::OneOf<Pumping, kj::Array<kj::byte>, StreamStates::Closed, jsg::Value>;\n\n        return KJ_ASSERT_NONNULL(readable->getController().read(js, kj::none))\n            .then(js,\n                ioContext.addFunctor([byteStream = readable->getController().isByteOriented()](\n                                         auto& js, ReadResult result) mutable -> Result {\n          if (result.done) {\n            return StreamStates::Closed();\n          }\n\n          auto handle = KJ_ASSERT_NONNULL(result.value).getHandle(js);\n          if (!handle->IsArrayBufferView() && !handle->IsArrayBuffer()) {\n            return js.v8Ref(js.v8TypeError(\"This ReadableStream did not return bytes.\"));\n          }\n\n          jsg::BufferSource bufferSource(js, handle);\n          if (bufferSource.size() == 0) {\n            return Pumping{};\n          }\n\n          if (byteStream) {\n            jsg::BackingStore backing = bufferSource.detach(js);\n            return backing.asArrayPtr().attach(kj::mv(backing));\n          }\n          return bufferSource.asArrayPtr().attach(kj::mv(bufferSource));\n        }),\n                [](auto& js, jsg::Value exception) mutable -> Result { return kj::mv(exception); })\n            .then(js, ioContext.addFunctor( JSG_VISITABLE_LAMBDA((readable = kj::mv(readable), pumpToReader = kj::mv(pumpToReader)), (readable), (jsg::Lock & js, Result result) mutable {\n              KJ_IF_SOME(reader, pumpToReader->tryGet()) {\n              reader.ioContext.requireCurrentOrThrowJs();\n              auto& ioContext = IoContext::current();\n              KJ_SWITCH_ONEOF(result) {\n              KJ_CASE_ONEOF(bytes, kj::Array<kj::byte>) {\n              auto promise = reader.sink->write(bytes).attach(kj::mv(bytes));\n              return ioContext.awaitIo(js, reader.canceler.wrap(kj::mv(promise)))\n                  .then(js,\n                      [](jsg::Lock& js) -> kj::Maybe<jsg::Value> {\n                return kj::Maybe<jsg::Value>(kj::none);\n              },\n                      [](jsg::Lock& js, jsg::Value exception) mutable -> kj::Maybe<jsg::Value> {\n                return kj::mv(exception);\n              })\n                  .then(js,\n                      ioContext.addFunctor(JSG_VISITABLE_LAMBDA(\n                          (readable = readable.addRef(), pumpToReader = kj::mv(pumpToReader)),\n                          (readable),\n                          (jsg::Lock & js, kj::Maybe<jsg::Value> maybeException) mutable {\n                            KJ_IF_SOME(reader, pumpToReader->tryGet()) {\n                            auto& ioContext = reader.ioContext;\n                            ioContext.requireCurrentOrThrowJs();\n                            KJ_IF_SOME(exception, maybeException) {\n                            if (!reader.isErroredOrClosed()) {\n                            reader.state.transitionTo<kj::Exception>(\n                                js.exceptionToKj(kj::mv(exception)));\n                            }\n                            } else {\n                            // Else block to avert dangling else compiler warning.\n                            }\n                            return reader.pumpLoop(\n                                js, ioContext, readable.addRef(), kj::mv(pumpToReader));\n                            } else {\n                            return readable->getController().cancel(js,\n                                maybeException.map(\n                                    [&](jsg::Value& ex) { return ex.getHandle(js); }));\n                            }\n                          })));\n              }\n              KJ_CASE_ONEOF(pumping, Pumping) {}\n              KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n              if (!reader.isErroredOrClosed()) {\n              reader.state.transitionTo<StreamStates::Closed>();\n              }\n              }\n              KJ_CASE_ONEOF(exception, jsg::Value) {\n              if (!reader.isErroredOrClosed()) {\n              reader.state.transitionTo<kj::Exception>(js.exceptionToKj(kj::mv(exception)));\n              }\n              }\n              }\n              return reader.pumpLoop(js, ioContext, readable.addRef(), kj::mv(pumpToReader));\n              } else {\n              KJ_SWITCH_ONEOF(result) {\n              KJ_CASE_ONEOF(bytes, kj::Array<kj::byte>) {\n              return readable->getController().cancel(js, kj::none);\n              }\n              KJ_CASE_ONEOF(pumping, Pumping) {\n              return readable->getController().cancel(js, kj::none);\n              }\n              KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n              return js.resolvedPromise();\n              }\n              KJ_CASE_ONEOF(exception, jsg::Value) {\n              return readable->getController().cancel(js, exception.getHandle(js));\n              }\n              }\n              }\n              KJ_UNREACHABLE;\n            })));\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n};\n\n// pumpToCoroutine uses a DrainingReader to efficiently pull all synchronously available\n// data from the stream in each iteration, then writes it to the sink using vectored\n// I/O. This minimizes isolate lock acquisitions by batching: each time the lock is\n// held, the stream's internal queue is fully drained and the JS pull callback is\n// pumped synchronously as many times as possible.\n//\n// The pump loop is a kj coroutine. Dropping the returned kj::Promise drops the\n// coroutine frame, which destroys the DrainingReader (releasing the stream lock)\n// and the sink. No WeakRef/IoOwn dance is needed because ownership is clear.\n// The coroutine that implements the pump loop takes ownership of the DrainingReader\n// and sink. The jsg::Ref<ReadableStream> is not passed into the coroutine because\n// jsg::Ref is disallowed in coroutine parameters; instead, the DrainingReader holds\n// a reference to the stream internally.\nkj::Promise<void> pumpToImpl(IoContext& ioContext,\n    kj::Own<DrainingReader> reader,\n    kj::Own<WritableStreamSink> sink,\n    bool end) {\n\n  bool writeFailed = false;\n\n  KJ_TRY {\n    while (true) {\n      // Perform a draining read to get all synchronously available data if possible\n      // or fall back to a regular read if not.\n      DrainingReadResult result = co_await ioContext.run([&reader](jsg::Lock& js) mutable {\n        auto& ioContext = IoContext::current();\n        // Use a 256KB limit to allow periodic yielding to the event loop,\n        // preventing a fast producer from monopolizing the thread.\n        constexpr size_t kMaxReadPerCycle = 256 * 1024;\n        return ioContext.awaitJs(js, reader->read(js, kMaxReadPerCycle));\n      });\n\n      // Write all the chunks we received using vectored write for efficiency.\n      if (result.chunks.size() > 0) {\n        KJ_ON_SCOPE_FAILURE(writeFailed = true);\n        auto pieces =\n            KJ_MAP(chunk, result.chunks) -> kj::ArrayPtr<const kj::byte> { return chunk.asPtr(); };\n        co_await sink->write(pieces);\n      }\n\n      // If the stream is done, end the output if needed and exit.\n      if (result.done) {\n        KJ_ON_SCOPE_FAILURE(writeFailed = true);\n        if (end) {\n          co_await sink->end();\n        }\n        co_return;\n      }\n    }\n  }\n  KJ_CATCH(exception) {\n    if (!writeFailed) {\n      sink->abort(kj::cp(exception));\n    }\n\n    co_await ioContext.run([&reader, ex = kj::cp(exception)](jsg::Lock& js) mutable {\n      auto& ioContext = IoContext::current();\n      auto error = js.exceptionToJsValue(kj::mv(ex));\n      return ioContext.awaitJs(js, reader->cancel(js, error.getHandle(js)));\n    });\n    kj::throwFatalException(kj::mv(exception));\n  }\n}\n}  // namespace\n\ntemplate <typename T>\njsg::Promise<T> ReadableStreamJsController::readAll(jsg::Lock& js, uint64_t limit) {\n  if (isLockedToReader()) {\n    return js.rejectedPromise<T>(KJ_EXCEPTION(\n        FAILED, \"jsg.TypeError: This ReadableStream is currently locked to a reader.\"));\n  }\n  disturbed = true;\n\n  bool stripBom = false;\n  KJ_IF_SOME(flags, FeatureFlags::tryGet(js)) {\n    stripBom = flags.getStripBomInReadAllText();\n  }\n\n  // This operation leaves the stream locked and disturbed. The loop will read until\n  // the stream is closed or errored. If the limit is reached, the loop will error.\n\n  const auto readAll = [this, limit, stripBom](auto& js) -> jsg::Promise<T> {\n    KJ_ASSERT(lock.lock());\n    // The AllReader will hold a traceable reference to the ReadableStream.\n    auto reader = kj::heap<AllReader>(addRef(), limit);\n\n    auto promise = ([&js, &reader, stripBom]() -> jsg::Promise<T> {\n      if constexpr (kj::isSameType<T, jsg::BufferSource>()) {\n        (void)stripBom;  // Unused in this branch.\n        return reader->allBytes(js);\n      } else {\n        auto option = ReadAllTextOption::NULL_TERMINATE;\n        if (stripBom) {\n          option |= ReadAllTextOption::STRIP_BOM;\n        }\n        return reader->allText(js, option);\n      }\n    })();\n\n    return maybeAddFunctor(js, kj::mv(promise),\n        // reader is a GC visitable type that holds a reference to either the stream\n        // or an error. Accordingly, we wrap it in a visitable lambda attached as a\n        // continuation on the promise to ensure that it is GC visited and kept alive until\n        // the promise settles.\n        JSG_VISITABLE_LAMBDA((reader = kj::mv(reader)), (reader),\n            (jsg::Lock & js, T result)->jsg::Promise<T> {\n              return js.resolvedPromise(kj::mv(result));\n            }),\n        [](jsg::Lock& js, jsg::Value exception) -> jsg::Promise<T> {\n      return js.rejectedPromise<T>(kj::mv(exception));\n    });\n  };\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(initial, Initial) {\n      // Stream not yet set up, treat as closed.\n      if constexpr (kj::isSameType<T, jsg::BufferSource>()) {\n        auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, 0);\n        return js.resolvedPromise(jsg::BufferSource(js, kj::mv(backing)));\n      } else {\n        return js.resolvedPromise(T());\n      }\n    }\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      if constexpr (kj::isSameType<T, jsg::BufferSource>()) {\n        auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, 0);\n        return js.resolvedPromise(jsg::BufferSource(js, kj::mv(backing)));\n      } else {\n        return js.resolvedPromise(T());\n      }\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return js.rejectedPromise<T>(errored.addRef(js));\n    }\n    KJ_CASE_ONEOF(valueReadable, kj::Own<ValueReadable>) {\n      return readAll(js);\n    }\n    KJ_CASE_ONEOF(byteReadable, kj::Own<ByteReadable>) {\n      return readAll(js);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\njsg::Promise<jsg::BufferSource> ReadableStreamJsController::readAllBytes(\n    jsg::Lock& js, uint64_t limit) {\n  return readAll<jsg::BufferSource>(js, limit);\n}\n\njsg::Promise<kj::String> ReadableStreamJsController::readAllText(jsg::Lock& js, uint64_t limit) {\n  return readAll<kj::String>(js, limit);\n}\n\nkj::Own<ReadableStreamController> ReadableStreamJsController::detach(\n    jsg::Lock& js, bool ignored /* unused */) {\n  KJ_ASSERT(!isLockedToReader());\n  KJ_ASSERT(!isDisturbed());\n  KJ_ASSERT(!state.hasOperationInProgress(), \"Unable to detach with read pending\");\n  auto controller = kj::heap<ReadableStreamJsController>();\n  controller->expectedLength = expectedLength;\n  disturbed = true;\n\n  // Clones this streams state into a new ReadableStreamController, leaving this stream\n  // locked, disturbed, and closed.\n\n  // The controller starts in Initial state by default, so we can use regular transitionTo.\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(initial, Initial) {\n      // Still in initial state, transition to closed\n      controller->state.transitionTo<StreamStates::Closed>();\n    }\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      controller->state.transitionTo<StreamStates::Closed>();\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      controller->state.transitionTo<StreamStates::Errored>(errored.addRef(js));\n    }\n    KJ_CASE_ONEOF(readable, kj::Own<ValueReadable>) {\n      KJ_ASSERT(lock.lock());\n      controller->state.transitionTo<kj::Own<ValueReadable>>(readable->clone(js, *controller));\n      state.transitionTo<StreamStates::Closed>();\n      lock.onClose(js);\n    }\n    KJ_CASE_ONEOF(readable, kj::Own<ByteReadable>) {\n      KJ_ASSERT(lock.lock());\n      controller->state.transitionTo<kj::Own<ByteReadable>>(readable->clone(js, *controller));\n      state.transitionTo<StreamStates::Closed>();\n      lock.onClose(js);\n    }\n  }\n\n  return kj::mv(controller);\n}\n\nkj::Maybe<uint64_t> ReadableStreamJsController::tryGetLength(StreamEncoding encoding) {\n  return expectedLength;\n}\n\nkj::Promise<DeferredProxy<void>> ReadableStreamJsController::pumpTo(\n    jsg::Lock& js, kj::Own<WritableStreamSink> sink, bool end) {\n  KJ_ASSERT(IoContext::hasCurrent(), \"Unable to consume this ReadableStream outside of a request\");\n  KJ_REQUIRE(!isLockedToReader(), \"This ReadableStream is currently locked to a reader.\");\n  disturbed = true;\n\n  // This operation will leave the ReadableStream locked and disturbed. It will consume\n  // the stream until it either closed or errors.\n  //\n  // When the ENABLE_DRAINING_READ_ON_STANDARD_STREAMS autogate is enabled, uses the new\n  // pumpToImpl coroutine with DrainingReader for batched reads and vectored writes.\n  // Otherwise, falls back to the original PumpToReader JS promise loop that reads one\n  // chunk at a time.\n\n  const auto handlePump = [&] {\n    if (util::Autogate::isEnabled(util::AutogateKey::ENABLE_DRAINING_READ_ON_STANDARD_STREAMS)) {\n      auto reader = KJ_ASSERT_NONNULL(DrainingReader::create(js, *this->addRef()),\n          \"Failed to create DrainingReader — stream should not be locked\");\n      auto& ioContext = IoContext::current();\n      return addNoopDeferredProxy(pumpToImpl(ioContext, kj::mv(reader), kj::mv(sink), end));\n    } else {\n      KJ_ASSERT(lock.lock());\n      auto reader = kj::heap<PumpToReader>(addRef(), kj::mv(sink), end);\n      return addNoopDeferredProxy(reader->pumpTo(js).attach(kj::mv(reader)));\n    }\n  };\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(initial, Initial) {\n      // Stream not yet set up, treat as closed.\n      return addNoopDeferredProxy(sink->end().attach(kj::mv(sink)));\n    }\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      return addNoopDeferredProxy(sink->end().attach(kj::mv(sink)));\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return js.exceptionToKj(errored.addRef(js));\n    }\n    KJ_CASE_ONEOF(readable, kj::Own<ValueReadable>) {\n      return handlePump();\n    }\n    KJ_CASE_ONEOF(readable, kj::Own<ByteReadable>) {\n      return handlePump();\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\n// ======================================================================================\n\nWritableStreamDefaultController::WritableStreamDefaultController(\n    jsg::Lock& js, WritableStream& owner, jsg::Ref<AbortSignal> abortSignal)\n    : ioContext(tryGetIoContext()),\n      impl(js, owner, kj::mv(abortSignal)) {}\n\njsg::Promise<void> WritableStreamDefaultController::abort(\n    jsg::Lock& js, v8::Local<v8::Value> reason) {\n  return impl.abort(js, JSG_THIS, reason);\n}\n\nvoid WritableStreamDefaultController::visitForGc(jsg::GcVisitor& visitor) {\n  visitor.visit(impl);\n}\n\njsg::Promise<void> WritableStreamDefaultController::close(jsg::Lock& js) {\n  return impl.close(js, JSG_THIS);\n}\n\nvoid WritableStreamDefaultController::error(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> reason) {\n  impl.error(js, JSG_THIS, reason.orDefault(js.undefined()));\n}\n\nkj::Maybe<ssize_t> WritableStreamDefaultController::getDesiredSize() {\n  // Per the spec, desiredSize should be null when the stream is erroring.\n  if (impl.flags.pedanticWpt && isErroring()) {\n    return kj::none;\n  }\n  return impl.getDesiredSize();\n}\n\njsg::Ref<AbortSignal> WritableStreamDefaultController::getSignal() {\n  return impl.signal.addRef();\n}\n\nkj::Maybe<v8::Local<v8::Value>> WritableStreamDefaultController::isErroring(jsg::Lock& js) {\n  KJ_IF_SOME(erroring, impl.state.tryGetUnsafe<StreamStates::Erroring>()) {\n    return erroring.reason.getHandle(js);\n  }\n  return kj::none;\n}\n\nvoid WritableStreamDefaultController::setup(\n    jsg::Lock& js, UnderlyingSink underlyingSink, StreamQueuingStrategy queuingStrategy) {\n  impl.setup(js, JSG_THIS, kj::mv(underlyingSink), kj::mv(queuingStrategy));\n}\n\njsg::Promise<void> WritableStreamDefaultController::write(\n    jsg::Lock& js, v8::Local<v8::Value> value) {\n  return impl.write(js, JSG_THIS, value);\n}\n\nvoid WritableStreamDefaultController::cancelPendingWrites(jsg::Lock& js, jsg::JsValue reason) {\n  impl.cancelPendingWrites(js, reason);\n}\n\nvoid WritableStreamDefaultController::clearAlgorithms() {\n  impl.algorithms.clear();\n}\n\nWritableStreamDefaultController::~WritableStreamDefaultController() noexcept(false) {\n  // Clear algorithms in destructor to break circular references\n  clearAlgorithms();\n}\n\n// ======================================================================================\nWritableStreamJsController::WritableStreamJsController(): ioContext(tryGetIoContext()) {}\n\nWritableStreamJsController::~WritableStreamJsController() noexcept(false) {\n  // Clear algorithms to break circular references during destruction\n  KJ_IF_SOME(controller, state.tryGetUnsafe<Controller>()) {\n    controller->clearAlgorithms();\n  }\n  // Clear the state to break the circular reference to the controller.\n  // During destruction, we force the transition since the current state doesn't matter.\n  state.forceTransitionTo<StreamStates::Closed>();\n  // Clear owner reference\n  owner = kj::none;\n  // Clear any pending abort promise\n  maybeAbortPromise = kj::none;\n}\n\nWritableStreamJsController::WritableStreamJsController(StreamStates::Closed closed)\n    : ioContext(tryGetIoContext()) {\n  state.transitionTo<StreamStates::Closed>();\n}\n\nWritableStreamJsController::WritableStreamJsController(StreamStates::Errored errored)\n    : ioContext(tryGetIoContext()) {\n  state.transitionTo<StreamStates::Errored>(kj::mv(errored));\n}\n\njsg::Promise<void> WritableStreamJsController::abort(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> reason) {\n  // The spec requires that if abort is called multiple times, it is supposed to return the same\n  // promise each time. That's a bit cumbersome here with jsg::Promise so we intentionally just\n  // return a continuation branch off the same promise.\n  KJ_IF_SOME(abortPromise, maybeAbortPromise) {\n    return abortPromise.whenResolved(js);\n  }\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(initial, Initial) {\n      // Stream hasn't been set up yet - treat like closed for abort purposes\n      maybeAbortPromise = js.resolvedPromise();\n      return KJ_ASSERT_NONNULL(maybeAbortPromise).whenResolved(js);\n    }\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      maybeAbortPromise = js.resolvedPromise();\n      return KJ_ASSERT_NONNULL(maybeAbortPromise).whenResolved(js);\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      // Per the spec, if the stream is errored, we are to return a resolved promise.\n      maybeAbortPromise = js.resolvedPromise();\n      return KJ_ASSERT_NONNULL(maybeAbortPromise).whenResolved(js);\n    }\n    KJ_CASE_ONEOF(controller, Controller) {\n      maybeAbortPromise = controller->abort(js, reason.orDefault(js.undefined()));\n      return KJ_ASSERT_NONNULL(maybeAbortPromise).whenResolved(js);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\njsg::Ref<WritableStream> WritableStreamJsController::addRef() {\n  return KJ_ASSERT_NONNULL(owner).addRef();\n}\n\nbool WritableStreamJsController::isClosedOrClosing() {\n  return state.is<StreamStates::Closed>();\n}\n\nbool WritableStreamJsController::isErrored() {\n  return state.isErrored();\n}\n\njsg::Promise<void> WritableStreamJsController::close(jsg::Lock& js, bool markAsHandled) {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(initial, Initial) {\n      return rejectedMaybeHandledPromise<void>(\n          js, js.v8TypeError(\"This WritableStream has been closed.\"_kj), markAsHandled);\n    }\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      return rejectedMaybeHandledPromise<void>(\n          js, js.v8TypeError(\"This WritableStream has been closed.\"_kj), markAsHandled);\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      if (FeatureFlags::get(js).getPedanticWpt()) {\n        return rejectedMaybeHandledPromise<void>(\n            js, js.v8TypeError(\"This WritableStream has been errored.\"_kj), markAsHandled);\n      }\n      return rejectedMaybeHandledPromise<void>(js, errored.getHandle(js), markAsHandled);\n    }\n    KJ_CASE_ONEOF(controller, Controller) {\n      return controller->close(js);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid WritableStreamJsController::doClose(jsg::Lock& js) {\n  // If already in a terminal state, nothing to do.\n  if (state.isTerminal()) return;\n\n  // Clear algorithms to break circular references before changing state\n  KJ_IF_SOME(controller, state.tryGetUnsafe<Controller>()) {\n    controller->clearAlgorithms();\n  }\n\n  state.transitionTo<StreamStates::Closed>();\n  KJ_IF_SOME(locked, lock.state.tryGetUnsafe<WriterLocked>()) {\n    maybeResolvePromise(js, locked.getClosedFulfiller());\n    maybeResolvePromise(js, locked.getReadyFulfiller());\n  } else {\n    (void)lock.state.transitionFromTo<WritableLockImpl::PipeLocked, Unlocked>();\n  }\n}\n\nvoid WritableStreamJsController::doError(jsg::Lock& js, v8::Local<v8::Value> reason) {\n  // If already in a terminal state, nothing to do.\n  if (state.isTerminal()) return;\n\n  // Clear algorithms to break circular references before changing state\n  KJ_IF_SOME(controller, state.tryGetUnsafe<Controller>()) {\n    controller->clearAlgorithms();\n  }\n\n  state.transitionTo<StreamStates::Errored>(js.v8Ref(reason));\n  KJ_IF_SOME(locked, lock.state.tryGetUnsafe<WriterLocked>()) {\n    maybeRejectPromise<void>(js, locked.getClosedFulfiller(), reason);\n    maybeResolvePromise(js, locked.getReadyFulfiller());\n  } else KJ_IF_SOME(pipeLocked, lock.state.tryGetUnsafe<WritableLockImpl::PipeLocked>()) {\n    // When the writable side of a pipe errors, we need to release the source stream.\n    // The pipeLoop may be waiting on a read from the source that will never complete,\n    // so we need to proactively release the source here.\n    if (!pipeLocked.flags.preventCancel) {\n      pipeLocked.source.release(js, reason);\n    } else {\n      pipeLocked.source.release(js);\n    }\n    lock.state.transitionTo<Unlocked>();\n  }\n}\n\nvoid WritableStreamJsController::errorIfNeeded(jsg::Lock& js, v8::Local<v8::Value> reason) {\n  // Error through the underlying controller if available, which goes through the proper\n  // error transition (Erroring -> Errored). This allows close() to be called while the\n  // stream is \"erroring\" and reject with the stored error.\n  KJ_IF_SOME(controller, state.tryGetUnsafe<Controller>()) {\n    controller->error(js, reason);\n  }\n  // If state is not Controller (already Closed or Errored), this is a no-op.\n}\n\nkj::Maybe<int> WritableStreamJsController::getDesiredSize() {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(initial, Initial) {\n      return 0;\n    }\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      return 0;\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return kj::none;\n    }\n    KJ_CASE_ONEOF(controller, Controller) {\n      return controller->getDesiredSize().map([](ssize_t size) -> int { return size; });\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::Maybe<v8::Local<v8::Value>> WritableStreamJsController::isErroring(jsg::Lock& js) {\n  KJ_IF_SOME(controller, state.tryGetUnsafe<Controller>()) {\n    return controller->isErroring(js);\n  }\n  return kj::none;\n}\n\nbool WritableStreamDefaultController::isErroring() const {\n  return impl.state.is<StreamStates::Erroring>();\n}\n\nkj::Maybe<v8::Local<v8::Value>> WritableStreamJsController::isErroredOrErroring(jsg::Lock& js) {\n  KJ_IF_SOME(err, state.tryGetErrorUnsafe()) {\n    return err.getHandle(js);\n  }\n  return isErroring(js);\n}\n\nbool WritableStreamJsController::isStarted() {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(initial, Initial) {\n      return false;\n    }\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      return true;\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return true;\n    }\n    KJ_CASE_ONEOF(controller, Controller) {\n      return controller->isStarted();\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nbool WritableStreamJsController::hasBackpressure() {\n  KJ_IF_SOME(controller, state.tryGetUnsafe<Controller>()) {\n    return controller->hasBackpressure();\n  }\n  return false;\n}\n\nbool WritableStreamJsController::isLocked() const {\n  return isLockedToWriter();\n}\n\nbool WritableStreamJsController::isLockedToWriter() const {\n  return !lock.state.is<Unlocked>();\n}\n\nbool WritableStreamJsController::lockWriter(jsg::Lock& js, Writer& writer) {\n  return lock.lockWriter(js, *this, writer);\n}\n\nvoid WritableStreamJsController::maybeRejectReadyPromise(\n    jsg::Lock& js, v8::Local<v8::Value> reason) {\n  KJ_IF_SOME(writerLock, lock.state.tryGetUnsafe<WriterLocked>()) {\n    if (writerLock.getReadyFulfiller() != kj::none) {\n      maybeRejectPromise<void>(js, writerLock.getReadyFulfiller(), reason);\n    } else {\n      auto prp = js.newPromiseAndResolver<void>();\n      prp.promise.markAsHandled(js);\n      prp.resolver.reject(js, reason);\n      writerLock.setReadyFulfiller(js, prp);\n    }\n  }\n}\n\nvoid WritableStreamJsController::maybeResolveReadyPromise(jsg::Lock& js) {\n  KJ_IF_SOME(writerLock, lock.state.tryGetUnsafe<WriterLocked>()) {\n    maybeResolvePromise(js, writerLock.getReadyFulfiller());\n  }\n}\n\nvoid WritableStreamJsController::releaseWriter(Writer& writer, kj::Maybe<jsg::Lock&> maybeJs) {\n  lock.releaseWriter(*this, writer, maybeJs);\n}\n\nkj::Maybe<kj::Own<WritableStreamSink>> WritableStreamJsController::removeSink(jsg::Lock& js) {\n  return kj::none;\n}\nvoid WritableStreamJsController::detach(jsg::Lock& js) {\n  KJ_UNIMPLEMENTED(\"WritableStreamJsController::detach is not implemented\");\n}\n\nvoid WritableStreamJsController::setOwnerRef(WritableStream& stream) {\n  owner = stream;\n}\n\nvoid WritableStreamJsController::setup(jsg::Lock& js,\n    jsg::Optional<UnderlyingSink> maybeUnderlyingSink,\n    jsg::Optional<StreamQueuingStrategy> maybeQueuingStrategy) {\n  auto underlyingSink = kj::mv(maybeUnderlyingSink).orDefault({});\n  auto queuingStrategy = kj::mv(maybeQueuingStrategy).orDefault({});\n\n  if (FeatureFlags::get(js).getPedanticWpt()) {\n    // Per the spec, the type property for WritableStream's underlying sink must be undefined.\n    // If it's anything else, throw a RangeError.\n    JSG_REQUIRE(underlyingSink.type == kj::none, RangeError,\n        \"Invalid underlying sink type. Only undefined is valid.\");\n  }\n\n  // We account for the memory usage of the WritableStreamDefaultController and AbortSignal together\n  // because their lifetimes are identical and memory accounting itself has a memory overhead.\n  auto controller = js.allocAccounted<WritableStreamDefaultController>(\n      sizeof(WritableStreamDefaultController) + sizeof(AbortSignal), js, KJ_ASSERT_NONNULL(owner),\n      js.alloc<AbortSignal>());\n  auto& controllerRef = *controller;\n  state.transitionTo<Controller>(kj::mv(controller));\n  controllerRef.setup(js, kj::mv(underlyingSink), kj::mv(queuingStrategy));\n}\n\nkj::Maybe<jsg::Promise<void>> WritableStreamJsController::tryPipeFrom(\n    jsg::Lock& js, jsg::Ref<ReadableStream> source, PipeToOptions options) {\n  JSG_REQUIRE_NONNULL(\n      ioContext, Error, \"Unable to pipe to a WritableStream created outside of a request\");\n\n  // The ReadableStream source here can be either a JavaScript-backed ReadableStream\n  // or ReadableStreamSource-backed. In either case, however, this WritableStream is\n  // JavaScript-based and must use a JavaScript promise-based data flow for piping data.\n  // We'll treat all ReadableStreams as if they are JavaScript-backed.\n  //\n  // This method will return a JavaScript promise that is resolved when the pipe operation\n  // completes, or is rejected if the pipe operation is aborted or errored.\n\n  // Let's also acquire the destination pipe lock.\n  lock.pipeLock(KJ_ASSERT_NONNULL(owner), kj::mv(source), options);\n\n  return pipeLoop(js).then(js, JSG_VISITABLE_LAMBDA((ref = addRef()), (ref), (auto& js){}));\n}\n\njsg::Promise<void> WritableStreamJsController::pipeLoop(jsg::Lock& js) {\n  auto maybePipeLock = lock.tryGetPipe();\n  if (maybePipeLock == kj::none) return js.resolvedPromise();\n  auto& pipeLock = KJ_REQUIRE_NONNULL(maybePipeLock);\n\n  auto preventAbort = pipeLock.flags.preventAbort;\n  auto preventCancel = pipeLock.flags.preventCancel;\n  auto preventClose = pipeLock.flags.preventClose;\n  auto pipeThrough = pipeLock.flags.pipeThrough;\n  auto& source = pipeLock.source;\n  // At the start of each pipe step, we check to see if either the source or\n  // the destination has closed or errored and propagate that on to the other.\n  KJ_IF_SOME(promise, pipeLock.checkSignal(js, *this)) {\n    lock.releasePipeLock();\n    return kj::mv(promise);\n  }\n\n  KJ_IF_SOME(errored, pipeLock.source.tryGetErrored(js)) {\n    source.release(js);\n    lock.releasePipeLock();\n    if (!preventAbort) {\n      auto onSuccess = JSG_VISITABLE_LAMBDA(\n          (pipeThrough, reason = js.v8Ref(errored)), (reason), (jsg::Lock& js) {\n            return rejectedMaybeHandledPromise<void>(js, reason.getHandle(js), pipeThrough);\n          });\n      auto promise = abort(js, errored);\n      KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n        return promise.then(js, ioContext.addFunctor(kj::mv(onSuccess)));\n      } else {\n        return promise.then(js, kj::mv(onSuccess));\n      }\n    }\n    return rejectedMaybeHandledPromise<void>(js, errored, pipeThrough);\n  }\n\n  KJ_IF_SOME(errored, state.tryGetUnsafe<StreamStates::Errored>()) {\n    lock.releasePipeLock();\n    auto reason = errored.getHandle(js);\n    if (!preventCancel) {\n      source.release(js, reason);\n    } else {\n      source.release(js);\n    }\n    return rejectedMaybeHandledPromise<void>(js, reason, pipeThrough);\n  }\n\n  KJ_IF_SOME(erroring, isErroring(js)) {\n    lock.releasePipeLock();\n    if (!preventCancel) {\n      source.release(js, erroring);\n    } else {\n      source.release(js);\n    }\n    return rejectedMaybeHandledPromise<void>(js, erroring, pipeThrough);\n  }\n\n  if (source.isClosed()) {\n    source.release(js);\n    lock.releasePipeLock();\n    if (!preventClose) {\n      auto promise = close(js);\n      if (pipeThrough) {\n        promise.markAsHandled(js);\n      }\n      return kj::mv(promise);\n    }\n    return js.resolvedPromise();\n  }\n\n  if (state.is<StreamStates::Closed>()) {\n    lock.releasePipeLock();\n    auto reason = js.v8TypeError(\"This destination writable stream is closed.\"_kj);\n    if (!preventCancel) {\n      source.release(js, reason);\n    } else {\n      source.release(js);\n    }\n\n    return rejectedMaybeHandledPromise<void>(js, reason, pipeThrough);\n  }\n\n  // Assuming we get by that, we perform a read on the source. If the read errors,\n  // we propagate the error to the destination, depending on options and reject\n  // the pipe promise. If the read is successful then we'll get a ReadResult\n  // back. If the ReadResult indicates done, then we close the destination\n  // depending on options and resolve the pipe promise. If the ReadResult is\n  // not done, we write the value on to the destination. If the write operation\n  // fails, we reject the pipe promise and propagate the error back to the\n  // source (again, depending on options). If the write operation is successful,\n  // we call pipeLoop again to move on to the next iteration.\n\n  auto onSuccess = JSG_VISITABLE_LAMBDA((this, ref = addRef(), preventCancel, pipeThrough), (ref),\n      (jsg::Lock & js, ReadResult result)->jsg::Promise<void> {\n        auto maybePipeLock = lock.tryGetPipe();\n        if (maybePipeLock == kj::none) return js.resolvedPromise();\n        auto& pipeLock = KJ_REQUIRE_NONNULL(maybePipeLock);\n\n        KJ_IF_SOME(promise, pipeLock.checkSignal(js, *this)) {\n          lock.releasePipeLock();\n          return kj::mv(promise);\n        } else {\n        }  // Trailing else() is squash compiler warning\n\n        if (result.done) {\n          // We'll handle the close at the start of the next iteration.\n          return pipeLoop(js);\n        }\n\n        auto onSuccess = JSG_VISITABLE_LAMBDA(\n        (this, ref=addRef()), (ref) , (jsg::Lock& js) {\n      return pipeLoop(js);\n    } );\n\n        auto onFailure = JSG_VISITABLE_LAMBDA(\n        (this, ref=addRef(), preventCancel, pipeThrough),\n        (ref) , (jsg::Lock& js, jsg::Value value) {\n      // The write failed. We need to release the source if the pipe lock still exists.\n      auto reason = value.getHandle(js);\n      KJ_IF_SOME(pipeLock, lock.tryGetPipe()) {\n        if (!preventCancel) {\n          pipeLock.source.release(js, reason);\n        } else {\n          pipeLock.source.release(js);\n        }\n      } else {}  // Trailing else() to squash compiler warning\n      return rejectedMaybeHandledPromise<void>(js, reason, pipeThrough);\n    } );\n\n        auto promise =\n            write(js, result.value.map([&](jsg::Value& value) { return value.getHandle(js); }));\n\n        return maybeAddFunctor(js, kj::mv(promise), kj::mv(onSuccess), kj::mv(onFailure));\n      });\n\n  auto onFailure =\n      JSG_VISITABLE_LAMBDA((this, ref = addRef()), (ref), (jsg::Lock& js, jsg::Value value) {\n        // The read failed. We will handle the error at the start of the next iteration.\n        return pipeLoop(js);\n      });\n\n  return maybeAddFunctor(js, pipeLock.source.read(js), kj::mv(onSuccess), kj::mv(onFailure));\n}\n\nvoid WritableStreamJsController::updateBackpressure(jsg::Lock& js, bool backpressure) {\n  KJ_IF_SOME(writerLock, lock.state.tryGetUnsafe<WriterLocked>()) {\n    if (backpressure) {\n      // Per the spec, when backpressure is updated and is true, we replace the existing\n      // ready promise on the writer with a new pending promise, regardless of whether\n      // the existing one is resolved or not.\n      auto prp = js.newPromiseAndResolver<void>();\n      prp.promise.markAsHandled(js);\n      return writerLock.setReadyFulfiller(js, prp);\n    }\n\n    // When backpressure is updated and is false, we resolve the ready promise on the writer\n    maybeResolvePromise(js, writerLock.getReadyFulfiller());\n  }\n}\n\njsg::Promise<void> WritableStreamJsController::write(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> value) {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(initial, Initial) {\n      return js.rejectedPromise<void>(js.v8TypeError(\"This WritableStream has been closed.\"_kj));\n    }\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {\n      return js.rejectedPromise<void>(js.v8TypeError(\"This WritableStream has been closed.\"_kj));\n    }\n    KJ_CASE_ONEOF(errored, StreamStates::Errored) {\n      return js.rejectedPromise<void>(errored.addRef(js));\n    }\n    KJ_CASE_ONEOF(controller, Controller) {\n      return controller->write(js, value.orDefault([&] { return js.undefined(); }));\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid WritableStreamJsController::visitForGc(jsg::GcVisitor& visitor) {\n  state.visitForGc(visitor);\n  visitor.visit(maybeAbortPromise, lock);\n}\n\n// =======================================================================================\n\nTransformStreamDefaultController::TransformStreamDefaultController(jsg::Lock& js)\n    : ioContext(tryGetIoContext()),\n      startPromise(js.newPromiseAndResolver<void>()) {}\n\nkj::Maybe<int> TransformStreamDefaultController::getDesiredSize() {\n  KJ_IF_SOME(readableController, tryGetReadableController()) {\n    return readableController.getDesiredSize();\n  }\n  return kj::none;\n}\n\nvoid TransformStreamDefaultController::enqueue(jsg::Lock& js, v8::Local<v8::Value> chunk) {\n  auto& readableController = JSG_REQUIRE_NONNULL(tryGetReadableController(), TypeError,\n      \"The readable side of this TransformStream is no longer readable.\");\n  JSG_REQUIRE(readableController.canCloseOrEnqueue(), TypeError,\n      \"The readable side of this TransformStream is no longer readable.\");\n  js.tryCatch([&] { readableController.enqueue(js, chunk); }, [&](jsg::Value exception) {\n    errorWritableAndUnblockWrite(js, exception.getHandle(js));\n    js.throwException(kj::mv(exception));\n  });\n\n  bool newBackpressure = readableController.hasBackpressure();\n  if (newBackpressure != backpressure) {\n    KJ_ASSERT(newBackpressure);\n    // Unfortunately the original implementation forgot to actually set the backpressure\n    // here so the backpressure signaling failed to work correctly. This is unfortunate\n    // because applying the backpressure here could break existing code, so we need to\n    // put the fix behind a compat flag. Doh!\n    if (FeatureFlags::get(js).getFixupTransformStreamBackpressure()) {\n      setBackpressure(js, true);\n    }\n  }\n}\n\nvoid TransformStreamDefaultController::error(jsg::Lock& js, v8::Local<v8::Value> reason) {\n  KJ_IF_SOME(readableController, tryGetReadableController()) {\n    readableController.error(js, reason);\n    readable = kj::none;\n  }\n  errorWritableAndUnblockWrite(js, reason);\n}\n\nvoid TransformStreamDefaultController::terminate(jsg::Lock& js) {\n  KJ_IF_SOME(readableController, tryGetReadableController()) {\n    readableController.close(js);\n    readable = kj::none;\n  }\n  errorWritableAndUnblockWrite(js, js.v8TypeError(\"The transform stream has been terminated\"_kj));\n}\n\njsg::Promise<void> TransformStreamDefaultController::write(\n    jsg::Lock& js, v8::Local<v8::Value> chunk) {\n  KJ_IF_SOME(writableController, tryGetWritableController()) {\n    KJ_IF_SOME(error, writableController.isErroredOrErroring(js)) {\n      return js.rejectedPromise<void>(error);\n    }\n\n    KJ_ASSERT(writableController.isWritable());\n\n    if (backpressure) {\n      auto chunkRef = js.v8Ref(chunk);\n      return KJ_ASSERT_NONNULL(maybeBackpressureChange).promise.whenResolved(js).then(js,\n          JSG_VISITABLE_LAMBDA((chunkRef = kj::mv(chunkRef), ref=JSG_THIS),\n                              (chunkRef, ref), (jsg::Lock& js) mutable -> jsg::Promise<void> {\n        KJ_IF_SOME(writableController, ref->tryGetWritableController()) {\n          KJ_IF_SOME(error, writableController.isErroring(js)) {\n            return js.rejectedPromise<void>(error);\n          } else {\n            // Else block to avert dangling else compiler warning.\n          }\n        } else {\n          // Else block to avert dangling else compiler warning.\n        }\n        return ref->performTransform(js, chunkRef.getHandle(js));\n      }));\n    }\n    return performTransform(js, chunk);\n  } else {\n    return js.rejectedPromise<void>(\n        KJ_EXCEPTION(FAILED, \"jsg.TypeError: Writing to the TransformStream failed.\"));\n  }\n}\n\njsg::Promise<void> TransformStreamDefaultController::abort(\n    jsg::Lock& js, v8::Local<v8::Value> reason) {\n  if (FeatureFlags::get(js).getPedanticWpt()) {\n    // If a finish operation is already in progress, return the existing promise\n    // or handle the case where we're being called synchronously from within another\n    // finish operation.\n    if (algorithms.finishStarted) {\n      KJ_IF_SOME(finish, algorithms.maybeFinish) {\n        return finish.whenResolved(js);\n      }\n      // finishStarted is true but maybeFinish is not set yet - this means we're being\n      // called synchronously from within another finish operation (like cancel).\n      // We need to error the stream with the abort reason so that both the current\n      // operation and this abort reject with the abort reason.\n      error(js, reason);\n      return js.rejectedPromise<void>(js.v8Ref(reason));\n    }\n\n    // Mark that we're starting a finish operation before running the algorithm.\n    algorithms.finishStarted = true;\n  } else {\n    KJ_IF_SOME(finish, algorithms.maybeFinish) {\n      return finish.whenResolved(js);\n    }\n  }\n\n  return algorithms.maybeFinish\n      .emplace(maybeRunAlgorithm(js, algorithms.cancel,\n          JSG_VISITABLE_LAMBDA(\n              (this, ref = JSG_THIS, reason = jsg::JsRef(js, jsg::JsValue(reason))), (ref, reason),\n              (jsg::Lock & js)->jsg::Promise<void> {\n                // If the readable side is errored, return a rejected promise with the stored error\n                {\n                KJ_IF_SOME(err, getReadableErrorState(js)) {\n                return js.rejectedPromise<void>(kj::mv(err));\n                } else {\n                // Else block to avert dangling else compiler warning.\n                }\n                }\n                // Otherwise... error with the given reason and resolve the abort promise\n                error(js, reason.getHandle(js));\n                return js.resolvedPromise();\n              }),\n          JSG_VISITABLE_LAMBDA((this, ref = JSG_THIS), (ref),\n              (jsg::Lock & js, jsg::Value reason)->jsg::Promise<void> {\n                error(js, reason.getHandle(js));\n                return js.rejectedPromise<void>(kj::mv(reason));\n              }),\n          jsg::JsValue(reason)))\n      .whenResolved(js);\n}\n\njsg::Promise<void> TransformStreamDefaultController::close(jsg::Lock& js) {\n  auto flags = FeatureFlags::get(js);\n  if (flags.getPedanticWpt()) {\n    // If a finish operation is already in progress (e.g., from cancel or abort),\n    // we should not run flush. Per the WHATWG streams spec, close/flush should\n    // coordinate with cancel to avoid calling both.\n    if (algorithms.finishStarted) {\n      KJ_IF_SOME(finish, algorithms.maybeFinish) {\n        return finish.whenResolved(js);\n      }\n      // finishStarted is true but maybeFinish is not set yet - this means we're being\n      // called synchronously from within another finish operation. If the stream was\n      // errored during that operation, return a rejected promise with the error.\n      KJ_IF_SOME(writableController, tryGetWritableController()) {\n        KJ_IF_SOME(err, writableController.isErroredOrErroring(js)) {\n          return js.rejectedPromise<void>(err);\n        }\n      }\n      KJ_IF_SOME(err, getReadableErrorState(js)) {\n        return js.rejectedPromise<void>(kj::mv(err));\n      }\n      return js.resolvedPromise();\n    }\n\n    // Mark that we're starting a finish operation before running the algorithm,\n    // since the algorithm may synchronously call other finish operations.\n    algorithms.finishStarted = true;\n  }\n\n  auto onSuccess =\n      JSG_VISITABLE_LAMBDA((ref = JSG_THIS), (ref), (jsg::Lock & js)->jsg::Promise<void> {\n        // If the stream was errored during the flush algorithm (e.g., by controller.error()\n        // or by a parallel cancel() calling abort()), we should reject with that error.\n        if (FeatureFlags::get(js).getPedanticWpt()) {\n        KJ_IF_SOME(err, ref->getReadableErrorState(js)) {\n        return js.rejectedPromise<void>(kj::mv(err));\n        } else {\n        // Else block to avert dangling else compiler warning.\n        }\n        }\n        // Allows for a graceful close of the readable side. Close will\n        // complete once all of the queued data is read or the stream\n        // errors. Only close if the stream can still be closed (e.g.,\n        // it wasn't closed by a cancel operation from within flush).\n        {\n        KJ_IF_SOME(readableController, ref->tryGetReadableController()) {\n        if (readableController.canCloseOrEnqueue()) {\n        readableController.close(js);\n        }\n        } else {\n        // Else block to avert dangling else compiler warning.\n        }\n        }\n        return js.resolvedPromise();\n      });\n\n  auto onFailure = JSG_VISITABLE_LAMBDA(\n      (ref = JSG_THIS), (ref), (jsg::Lock & js, jsg::Value reason)->jsg::Promise<void> {\n        ref->error(js, reason.getHandle(js));\n        return js.rejectedPromise<void>(kj::mv(reason));\n      });\n\n  if (flags.getPedanticWpt()) {\n    return algorithms.maybeFinish\n        .emplace(\n            maybeRunAlgorithm(js, algorithms.flush, kj::mv(onSuccess), kj::mv(onFailure), JSG_THIS))\n        .whenResolved(js);\n  }\n\n  return maybeRunAlgorithm(js, algorithms.flush, kj::mv(onSuccess), kj::mv(onFailure), JSG_THIS);\n}\n\njsg::Promise<void> TransformStreamDefaultController::pull(jsg::Lock& js) {\n  KJ_ASSERT(backpressure);\n  setBackpressure(js, false);\n  return KJ_ASSERT_NONNULL(maybeBackpressureChange).promise.whenResolved(js);\n}\n\njsg::Promise<void> TransformStreamDefaultController::cancel(\n    jsg::Lock& js, v8::Local<v8::Value> reason) {\n  if (FeatureFlags::get(js).getPedanticWpt()) {\n    // If a finish operation is already in progress, return the existing promise\n    // or check for errors if we're being called synchronously from within another\n    // finish operation.\n    if (algorithms.finishStarted) {\n      KJ_IF_SOME(finish, algorithms.maybeFinish) {\n        return finish.whenResolved(js);\n      }\n      // finishStarted is true but maybeFinish is not set yet - check if the stream\n      // was errored during that operation.\n      KJ_IF_SOME(err, getReadableErrorState(js)) {\n        return js.rejectedPromise<void>(kj::mv(err));\n      }\n      return js.resolvedPromise();\n    }\n\n    // Mark that we're starting a finish operation before running the algorithm.\n    algorithms.finishStarted = true;\n  }\n\n  return algorithms.maybeFinish\n      .emplace(maybeRunAlgorithm(js, algorithms.cancel,\n          JSG_VISITABLE_LAMBDA(\n              (this, ref = JSG_THIS, reason = jsg::JsRef(js, jsg::JsValue(reason))), (ref, reason),\n              (jsg::Lock & js)->jsg::Promise<void> {\n                // If the stream was errored during the cancel algorithm (e.g., by controller.error()\n                // or by a parallel abort()), we should reject with that error.\n                if (FeatureFlags::get(js).getPedanticWpt()) {\n                KJ_IF_SOME(err, getReadableErrorState(js)) {\n                readable = kj::none;\n                errorWritableAndUnblockWrite(js, reason.getHandle(js));\n                return js.rejectedPromise<void>(kj::mv(err));\n                } else {\n                // Else block to avert dangling else compiler warning.\n                }\n                }\n                readable = kj::none;\n                errorWritableAndUnblockWrite(js, reason.getHandle(js));\n                return js.resolvedPromise();\n              }),\n          JSG_VISITABLE_LAMBDA((this, ref = JSG_THIS), (ref),\n              (jsg::Lock & js, jsg::Value reason)->jsg::Promise<void> {\n                readable = kj::none;\n                errorWritableAndUnblockWrite(js, reason.getHandle(js));\n                return js.rejectedPromise<void>(kj::mv(reason));\n              }),\n          jsg::JsValue(reason)))\n      .whenResolved(js);\n}\n\njsg::Promise<void> TransformStreamDefaultController::performTransform(\n    jsg::Lock& js, v8::Local<v8::Value> chunk) {\n  if (algorithms.transform != kj::none) {\n    return maybeRunAlgorithm(js, algorithms.transform,\n        [](jsg::Lock& js) -> jsg::Promise<void> { return js.resolvedPromise(); },\n        JSG_VISITABLE_LAMBDA((ref = JSG_THIS), (ref),\n            (jsg::Lock & js, jsg::Value reason)->jsg::Promise<void> {\n              ref->error(js, reason.getHandle(js));\n              return js.rejectedPromise<void>(kj::mv(reason));\n            }),\n        chunk, JSG_THIS);\n  }\n  // If we got here, there is no transform algorithm. Per the spec, the default\n  // behavior then is to just pass along the value untransformed.\n  return js.tryCatch([&] {\n    enqueue(js, chunk);\n    return js.resolvedPromise();\n  }, [&](jsg::Value exception) { return js.rejectedPromise<void>(kj::mv(exception)); });\n}\n\nvoid TransformStreamDefaultController::setBackpressure(jsg::Lock& js, bool newBackpressure) {\n  KJ_ASSERT(newBackpressure != backpressure);\n  KJ_IF_SOME(prp, maybeBackpressureChange) {\n    prp.resolver.resolve(js);\n  }\n  maybeBackpressureChange = js.newPromiseAndResolver<void>();\n  KJ_ASSERT_NONNULL(maybeBackpressureChange).promise.markAsHandled(js);\n  backpressure = newBackpressure;\n}\n\nvoid TransformStreamDefaultController::errorWritableAndUnblockWrite(\n    jsg::Lock& js, v8::Local<v8::Value> reason) {\n  algorithms.clear();\n  KJ_IF_SOME(writableController, tryGetWritableController()) {\n    if (FeatureFlags::get(js).getPedanticWpt()) {\n      // Use errorIfNeeded which goes through the proper error transition (Erroring -> Errored).\n      // This allows close() to be called while the stream is \"erroring\" and reject with the\n      // stored error, which is the expected behavior per the WHATWG streams spec.\n      writableController.errorIfNeeded(js, reason);\n    } else if (writableController.isWritable()) {\n      writableController.doError(js, reason);\n    }\n    writable = kj::none;\n  }\n  if (backpressure) {\n    setBackpressure(js, false);\n  }\n}\n\nvoid TransformStreamDefaultController::visitForGc(jsg::GcVisitor& visitor) {\n  KJ_IF_SOME(backpressureChange, maybeBackpressureChange) {\n    visitor.visit(backpressureChange.promise, backpressureChange.resolver);\n  }\n  visitor.visit(writable, readable, startPromise.resolver, startPromise.promise, algorithms);\n}\n\nvoid TransformStreamDefaultController::init(jsg::Lock& js,\n    jsg::Ref<ReadableStream>& readable,\n    jsg::Ref<WritableStream>& writable,\n    jsg::Optional<Transformer> maybeTransformer) {\n  KJ_ASSERT(this->readable == kj::none);\n  KJ_ASSERT(this->writable == kj::none);\n\n  this->writable = writable.addRef();\n\n  // The TransformStreamDefaultController needs to have a reference to the underlying controller\n  // and not just the readable because if the readable is teed, or passed off to source, etc,\n  // the TransformStream has to make sure that it can continue to interface with the controller\n  // to push data into it.\n  auto& readableController = static_cast<ReadableStreamJsController&>(readable->getController());\n  auto readableRef = KJ_ASSERT_NONNULL(readableController.getController());\n  this->readable = KJ_ASSERT_NONNULL(readableRef.tryGet<DefaultController>()).addRef();\n\n  auto transformer = kj::mv(maybeTransformer).orDefault({});\n\n  // TODO(someday): The stream standard includes placeholders for supporting byte-oriented\n  // TransformStreams but does not yet define them. For now, we are limiting our implementation\n  // here to only support value-based transforms.\n  JSG_REQUIRE(transformer.readableType == kj::none, TypeError,\n      \"transformer.readableType must be undefined.\");\n  JSG_REQUIRE(transformer.writableType == kj::none, TypeError,\n      \"transformer.writableType must be undefined.\");\n\n  KJ_IF_SOME(transform, transformer.transform) {\n    algorithms.transform = kj::mv(transform);\n  }\n\n  KJ_IF_SOME(flush, transformer.flush) {\n    algorithms.flush = kj::mv(flush);\n  }\n\n  KJ_IF_SOME(cancel, transformer.cancel) {\n    algorithms.cancel = kj::mv(cancel);\n  }\n\n  setBackpressure(js, true);\n\n  maybeRunAlgorithm(js, transformer.start,\n      JSG_VISITABLE_LAMBDA(\n          (ref = JSG_THIS), (ref), (jsg::Lock& js) { ref->startPromise.resolver.resolve(js); }),\n      JSG_VISITABLE_LAMBDA((ref = JSG_THIS), (ref),\n          (jsg::Lock& js, jsg::Value reason) {\n            ref->startPromise.resolver.reject(js, reason.getHandle(js));\n          }),\n      JSG_THIS);\n}\n\nkj::Maybe<ReadableStreamDefaultController&> TransformStreamDefaultController::\n    tryGetReadableController() {\n  KJ_IF_SOME(controller, readable) {\n    return *controller;\n  }\n  return kj::none;\n}\n\nkj::Maybe<WritableStreamJsController&> TransformStreamDefaultController::\n    tryGetWritableController() {\n  KJ_IF_SOME(w, writable) {\n    return static_cast<WritableStreamJsController&>(w->getController());\n  }\n  return kj::none;\n}\n\nkj::Maybe<jsg::Value> TransformStreamDefaultController::getReadableErrorState(jsg::Lock& js) {\n  KJ_IF_SOME(controller, tryGetReadableController()) {\n    return controller.getMaybeErrorState(js);\n  }\n  return kj::none;\n}\n\ntemplate <class Self>\nkj::StringPtr WritableImpl<Self>::jsgGetMemoryName() const {\n  return \"WritableImpl\"_kjc;\n}\n\ntemplate <class Self>\nsize_t WritableImpl<Self>::jsgGetMemorySelfSize() const {\n  return sizeof(WritableImpl<Self>);\n}\n\ntemplate <class Self>\nvoid WritableImpl<Self>::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"signal\", signal);\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {}\n    KJ_CASE_ONEOF(error, StreamStates::Errored) {\n      tracker.trackField(\"error\", error);\n    }\n    KJ_CASE_ONEOF(erroring, StreamStates::Erroring) {\n      tracker.trackField(\"erroring\", erroring.reason);\n    }\n    KJ_CASE_ONEOF(writable, Writable) {}\n  }\n\n  tracker.trackField(\"abortAlgorithm\", algorithms.abort);\n  tracker.trackField(\"closeAlgorithm\", algorithms.close);\n  tracker.trackField(\"writeAlgorithm\", algorithms.write);\n  tracker.trackField(\"sizeAlgorithm\", algorithms.size);\n\n  for (auto& request: writeRequests) {\n    tracker.trackField(\"pendingWrite\", request);\n  }\n\n  tracker.trackField(\"inFlightWrite\", inFlightWrite);\n  tracker.trackField(\"inFlightClose\", inFlightClose);\n  tracker.trackField(\"closeRequest\", closeRequest);\n  tracker.trackField(\"maybePendingAbort\", maybePendingAbort);\n}\n\nkj::StringPtr WritableStreamJsController::jsgGetMemoryName() const {\n  return \"WritableStreamJsController\"_kjc;\n}\n\nsize_t WritableStreamJsController::jsgGetMemorySelfSize() const {\n  return sizeof(WritableStreamJsController);\n}\n\nvoid WritableStreamJsController::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(initial, Initial) {}\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {}\n    KJ_CASE_ONEOF(error, StreamStates::Errored) {\n      tracker.trackField(\"error\", error);\n    }\n    KJ_CASE_ONEOF(controller, Controller) {\n      tracker.trackField(\"controller\", controller);\n    }\n  }\n  tracker.trackField(\"lock\", lock);\n  tracker.trackField(\"maybeAbortPromise\", maybeAbortPromise);\n}\n\nvoid WritableStreamDefaultController::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"impl\", impl);\n}\n\nkj::StringPtr ReadableStreamJsController::jsgGetMemoryName() const {\n  return \"ReadableStreamJsController\"_kjc;\n}\n\nsize_t ReadableStreamJsController::jsgGetMemorySelfSize() const {\n  return sizeof(ReadableStreamJsController);\n}\n\nvoid ReadableStreamJsController::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(initial, Initial) {}\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {}\n    KJ_CASE_ONEOF(error, StreamStates::Errored) {\n      tracker.trackField(\"error\", error);\n    }\n    KJ_CASE_ONEOF(readable, kj::Own<ValueReadable>) {\n      tracker.trackField(\"readable\", readable);\n    }\n    KJ_CASE_ONEOF(readable, kj::Own<ByteReadable>) {\n      tracker.trackField(\"readable\", readable);\n    }\n  }\n\n  tracker.trackField(\"lock\", lock);\n\n  // Track pending error state if present (Closed has no trackable content)\n  KJ_IF_SOME(pendingError, state.tryGetPendingStateUnsafe<StreamStates::Errored>()) {\n    tracker.trackField(\"pendingError\", pendingError);\n  }\n}\n\ntemplate <class Self>\nkj::StringPtr ReadableImpl<Self>::jsgGetMemoryName() const {\n  return \"ReadableImpl\"_kjc;\n}\n\ntemplate <class Self>\nsize_t ReadableImpl<Self>::jsgGetMemorySelfSize() const {\n  return sizeof(ReadableImpl);\n}\n\ntemplate <class Self>\nvoid ReadableImpl<Self>::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(closed, StreamStates::Closed) {}\n    KJ_CASE_ONEOF(error, StreamStates::Errored) {\n      tracker.trackField(\"error\", error);\n    }\n    KJ_CASE_ONEOF(queue, Queue) {\n      tracker.trackField(\"queue\", queue);\n    }\n  }\n\n  tracker.trackField(\"startAlgorithm\", algorithms.start);\n  tracker.trackField(\"pullAlgorithm\", algorithms.pull);\n  tracker.trackField(\"cancelAlgorithm\", algorithms.cancel);\n  tracker.trackField(\"sizeAlgorithm\", algorithms.size);\n  tracker.trackField(\"pendingCancel\", maybePendingCancel);\n}\n\nvoid ReadableStreamBYOBRequest::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  KJ_IF_SOME(impl, maybeImpl) {\n    tracker.trackField(\"readRequest\", impl.readRequest);\n    tracker.trackField(\"view\", impl.view);\n  }\n}\n\nvoid TransformStreamDefaultController::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"startPromise\", startPromise);\n  tracker.trackField(\"maybeBackpressureChange\", maybeBackpressureChange);\n  tracker.trackField(\"transformAlgorithm\", algorithms.transform);\n  tracker.trackField(\"flushAlgorithm\", algorithms.flush);\n  tracker.trackField(\"writable\", writable);\n  tracker.trackField(\"readable\", readable);\n}\n\n// ======================================================================================\n\njsg::Ref<ReadableStream> ReadableStream::from(\n    jsg::Lock& js, jsg::AsyncGenerator<jsg::Value> generator) {\n\n  // AsyncGenerator is not a refcounted type, so we need to wrap it in a refcounted\n  // struct so that we can keep it alive through the various promise branches below.\n  auto rcGenerator =\n      kj::rc<kj::RefcountedWrapper<jsg::AsyncGenerator<jsg::Value>>>(kj::mv(generator));\n\n  // clang-format off\n  return constructor(js, UnderlyingSource{\n    .pull = [generator = rcGenerator.addRef()](jsg::Lock& js, auto controller) mutable {\n      auto& c = controller.template get<DefaultController>();\n      return generator->getWrapped().next(js).then(js,\n          JSG_VISITABLE_LAMBDA((controller = c.addRef(), generator = generator.addRef()),\n              (controller),\n              (jsg::Lock& js, kj::Maybe<jsg::Value> value) {\n                KJ_IF_SOME(v, value) {\n                  auto handle = v.getHandle(js);\n                  // Per the ReadableStream.from spec, if the value is a promise,\n                  // the stream should wait for it to resolve and enqueue the\n                  // resolved value...\n                  // ... yes, this means that ReadableStream.from where the inputs\n                  // are promises will be slow, but that's the spec.\n                  if (handle->IsPromise()) {\n                    return js.toPromise(handle.As<v8::Promise>()).then(js,\n                        JSG_VISITABLE_LAMBDA(\n                            (controller=controller.addRef()),\n                            (controller),\n                            (jsg::Lock& js, jsg::Value val) mutable {\n                      controller->enqueue(js, val.getHandle(js));\n                      return js.resolvedPromise();\n                    }));\n                  }\n                  controller->enqueue(js, v.getHandle(js));\n                } else {\n                  controller->close(js);\n                }\n                return js.resolvedPromise();\n              }),\n          JSG_VISITABLE_LAMBDA((controller = c.addRef(), generator = generator.addRef()),\n              (controller), (jsg::Lock& js, jsg::Value reason) {\n                controller->error(js, reason.getHandle(js));\n                return js.rejectedPromise<void>(kj::mv(reason));\n              }));\n    },\n    .cancel = [generator = rcGenerator.addRef()](jsg::Lock& js, auto reason) mutable {\n      return generator->getWrapped().return_(js, js.v8Ref(reason))\n          .then(js, [generator = kj::mv(generator)](auto& lock, auto) {\n        // The generator might produce a value on return and might even want to continue,\n        // but the stream has been canceled at this point, so we stop here.\n      });\n    },\n  }, StreamQueuingStrategy{ .highWaterMark = 0 });\n  // clang-format on\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/standard.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"common.h\"\n#include \"queue.h\"\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/util/ring-buffer.h>\n#include <workerd/util/state-machine.h>\n#include <workerd/util/weak-refs.h>\n\nnamespace workerd::api {\n\n// =======================================================================================\n// ReadableStreamJsController, WritableStreamJsController, and the rest here define the\n// implementation of JavaScript-backed ReadableStream and WritableStreams.\n//\n// A JavaScript-backed ReadableStream is backed by a ReadableStreamJsController that is either\n// Closed, Errored, or in a Readable state. When readable, the controller owns either a\n// ReadableStreamDefaultController or ReadableByteStreamController object that corresponds\n// to the identically named interfaces in the streams spec. These objects are responsible\n// for the bulk of the implementation detail, with the ReadableStreamJsController serving\n// only as a bridge between it and the ReadableStream object itself.\n//\n//  * ReadableStream -> ReadableStreamJsController -> jsg::Ref<ReadableStreamDefaultController>\n//  * ReadableStream -> ReadableStreamJsController -> jsg::Ref<ReadableByteStreamController>\n//\n// Contrast this with the implementation of internal streams using the\n// ReadableStreamInternalController:\n//\n//  * ReadableStream -> ReadableStreamInternalController -> IoOwn<ReadableStreamSource>\n//\n// When user-code creates a JavaScript-backed ReadableStream using the `ReadableStream`\n// object constructor, they pass along an object called an \"underlying source\" that provides\n// JavaScript functions the ReadableStream will call to either initialize, close, or source\n// the data for the stream:\n//\n//   const readable = new ReadableStream({\n//     async start(controller) {\n//       // Initialize the stream\n//     },\n//     async pull(controller) {\n//       // Provide the stream data\n//     },\n//     async cancel(reason) {\n//       // Cancel and de-initialize the stream\n//     }\n//   });\n//\n// By default, a JavaScript-backed ReadableStream is value-oriented -- that is, any JavaScript\n// type can be passed through the stream. It is not limited to bytes only. The implementation\n// of the pull method on the underlying source can push strings, booleans, numbers, even undefined\n// as values that can be read from the stream. In such streams, the `controller` used internally\n// (and owned by the ReadableStreamJsController) is the `ReadableStreamDefaultController`.\n//\n// To create a byte-oriented stream -- one that is capable only of working with bytes in the\n// form of ArrayBufferViews (e.g. `Uint8Array`, `Uint16Array`, `DataView`, etc), the underlying\n// source object passed into the `ReadableStream` constructor must have a property\n// `'type' = 'bytes'`.\n//\n//   const readable = new ReadableStream({\n//     type: 'bytes',\n//     async start(controller) {\n//       // Initialize the stream\n//     },\n//     async pull(controller) {\n//       // Provide the stream data\n//     },\n//     async cancel(reason) {\n//       // Cancel and de-initialize the stream\n//     }\n//   });\n//\n// From here on, we'll refer to these as either value streams or byte streams. And we'll refer to\n// ReadableStreamDefaultController as simply \"DefaultController\", and ReadableByteStreamController\n// as simply \"ByobController\".\n//\n// The DefaultController and ByobController each maintain an internal queue. When a read request\n// is received, if there is enough data in the internal queue to fulfill the read request, then\n// we do so. Otherwise, the controller will call the underlying source's pull method to ask it\n// to provide data to fulfill the read request.\n//\n// A critical aspect of the implementation here is that for JavaScript-backed streams, the entire\n// implementation never leaves the isolate lock, and we use JavaScript promises (via jsg::Promise)\n// instead of kj::Promise's to keep the implementation from having to bounce back and forth between\n// the two spaces. This means that with a JavaScript-backed ReadableStream, it is possible to read\n// and fully consume the stream entirely from within JavaScript without ever engaging the kj event\n// loop.\n//\n// When you tee() a JavaScript-backed ReadableStream, the stream is put into a locked state and\n// the data is funneled out through two separate \"branches\" (two new `ReadableStream`s).\n//\n// When anything reads from a tee branch, the underlying controller is asked to read from the\n// underlying source. When the underlying source responds to that read request, the\n// data is forwarded to all of the known branches.\n//\n// The story for JavaScript-backed writable streams is similar. User code passes what the\n// spec calls an \"underlying sink\" to the `WritableStream` object constructor. This provides\n// functions that are used to receive stream data.\n//\n// const writable = new WritableStream({\n//   async start(controller) {\n//     // initialize\n//   },\n//   async write(chunk, controller) {\n//     // process the written chunk\n//   },\n//   async abort(reason) {},\n//   async close(reason) {},\n// });\n//\n// It is important to note that JavaScript-backed WritableStream's are *always* value\n// oriented. It is up to the implementation of the underlying sink to determine if it is\n// capable of doing anything with whatever type of chunk it is given.\n//\n// JavaScript-backed WritableStreams are backed by the WritableStreamJsController and\n// WritableStreamDefaultController objects:\n//\n//  WritableStream -> WritableStreamJsController -> jsg::Ref<WritableStreamDefaultController>\n//\n// All write operations on a JavaScript-backed WritableStream are processed within the\n// isolate lock using JavaScript promises instead of kj::Promises.\n\nclass ReadableStreamJsController;\nclass WritableStreamJsController;\n\n// =======================================================================================\n// The ReadableImpl provides implementation that is common to both the\n// ReadableStreamDefaultController and the ReadableByteStreamController.\ntemplate <class Self>\nclass ReadableImpl {\n public:\n  using Consumer = Self::QueueType::Consumer;\n  using Entry = Self::QueueType::Entry;\n  using StateListener = Self::QueueType::ConsumerImpl::StateListener;\n\n  ReadableImpl(UnderlyingSource underlyingSource, StreamQueuingStrategy queuingStrategy);\n\n  // Invokes the start algorithm to initialize the underlying source.\n  void start(jsg::Lock& js, jsg::Ref<Self> self);\n\n  // If the readable is not already closed or errored, initiates a cancellation.\n  jsg::Promise<void> cancel(jsg::Lock& js, jsg::Ref<Self> self, v8::Local<v8::Value> maybeReason);\n\n  // True if the readable is not closed, not errored, and close has not already been requested.\n  bool canCloseOrEnqueue();\n\n  // Invokes the cancel algorithm to let the underlying source know that the\n  // readable has been canceled.\n  void doCancel(jsg::Lock& js, jsg::Ref<Self> self, v8::Local<v8::Value> reason);\n\n  // Close the queue if we are in a state where we can be closed.\n  void close(jsg::Lock& js);\n\n  // Push a chunk of data into the queue.\n  void enqueue(jsg::Lock& js, kj::Rc<Entry> entry, jsg::Ref<Self> self);\n\n  void doClose(jsg::Lock& js);\n\n  // If it isn't already errored or closed, errors the queue, causing all consumers to be errored\n  // and detached.\n  void doError(jsg::Lock& js, jsg::Value reason);\n\n  // When a negative number is returned, indicates that we are above the highwatermark\n  // and backpressure should be signaled.\n  kj::Maybe<int> getDesiredSize();\n\n  // Invokes the pull algorithm only if we're in a state where the queue the\n  // queue is below the watermark and we actually need data right now.\n  void pullIfNeeded(jsg::Lock& js, jsg::Ref<Self> self);\n\n  // Like pullIfNeeded but bypasses the shouldCallPull() check. Used for draining reads\n  // which need to pull all available data regardless of backpressure settings.\n  void forcePullIfNeeded(jsg::Lock& js, jsg::Ref<Self> self);\n\n  // True if the queue is current below the highwatermark.\n  bool shouldCallPull();\n\n  // True if a pull is currently in progress (the pull promise is pending).\n  // Used by draining reads to determine if pumping completed synchronously.\n  bool isPulling() const {\n    return flags.pulling;\n  }\n\n  // The consumer can be used to read from this readables queue so long as the queue\n  // is open. The consumer instance may outlive the readable but will be put into\n  // a closed state or errored state when the readable is destroyed.\n  kj::Own<Consumer> getConsumer(kj::Maybe<StateListener&> listener);\n\n  // The number of consumers that exist for this readable.\n  size_t consumerCount();\n\n  void visitForGc(jsg::GcVisitor& visitor);\n\n  kj::StringPtr jsgGetMemoryName() const;\n  size_t jsgGetMemorySelfSize() const;\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  struct Algorithms {\n    kj::Maybe<jsg::Function<UnderlyingSource::StartAlgorithm>> start;\n    kj::Maybe<jsg::Function<UnderlyingSource::PullAlgorithm>> pull;\n    kj::Maybe<jsg::Function<UnderlyingSource::CancelAlgorithm>> cancel;\n    kj::Maybe<jsg::Function<StreamQueuingStrategy::SizeAlgorithm>> size;\n\n    Algorithms(UnderlyingSource underlyingSource, StreamQueuingStrategy queuingStrategy)\n        : start(kj::mv(underlyingSource.start)),\n          pull(kj::mv(underlyingSource.pull)),\n          cancel(kj::mv(underlyingSource.cancel)),\n          size(kj::mv(queuingStrategy.size)) {}\n\n    Algorithms(Algorithms&& other) = default;\n    Algorithms& operator=(Algorithms&& other) = default;\n\n    void clear() {\n      start = kj::none;\n      pull = kj::none;\n      cancel = kj::none;\n      size = kj::none;\n    }\n\n    void visitForGc(jsg::GcVisitor& visitor) {\n      visitor.visit(start, pull, cancel, size);\n    }\n  };\n\n  using Queue = Self::QueueType;\n\n  // State machine for ReadableImpl:\n  // Queue is the active state where the stream can accept data\n  // Closed and Errored are terminal states (cannot transition back to Queue)\n  //   Queue -> Closed (close() or doCancel() called)\n  //   Queue -> Errored (doError() called)\n  using State = StateMachine<TerminalStates<StreamStates::Closed>,\n      ErrorState<StreamStates::Errored>,\n      ActiveState<Queue>,\n      StreamStates::Closed,\n      StreamStates::Errored,\n      Queue>;\n  State state;\n  Algorithms algorithms;\n\n  size_t highWaterMark = 1;\n\n  struct PendingCancel {\n    kj::Maybe<jsg::Promise<void>::Resolver> fulfiller;\n    jsg::Promise<void> promise;\n    JSG_MEMORY_INFO(PendingCancel) {\n      tracker.trackField(\"fulfiller\", fulfiller);\n      tracker.trackField(\"promise\", promise);\n    }\n  };\n  kj::Maybe<PendingCancel> maybePendingCancel;\n\n  struct Flags {\n    uint8_t pullAgain : 1 = 0;\n    uint8_t pulling : 1 = 0;\n    uint8_t started : 1 = 0;\n    uint8_t starting : 1 = 0;\n  };\n  Flags flags{};\n\n  friend Self;\n};\n\n// Utility that provides the core implementation of WritableStreamJsController,\n// separated out for consistency with ReadableStreamJsController/ReadableImpl and\n// to enable it to be more easily reused should new kinds of WritableStream\n// controllers be introduced.\ntemplate <class Self>\nclass WritableImpl {\n public:\n  using PendingAbort = WritableStreamController::PendingAbort;\n\n  struct WriteRequest {\n    jsg::Promise<void>::Resolver resolver;\n    jsg::Value value;\n    size_t size;\n\n    void visitForGc(jsg::GcVisitor& visitor) {\n      visitor.visit(resolver, value);\n    }\n\n    JSG_MEMORY_INFO(WriteRequest) {\n      tracker.trackField(\"resolver\", resolver);\n      tracker.trackField(\"value\", value);\n    }\n  };\n\n  WritableImpl(jsg::Lock& js, WritableStream& owner, jsg::Ref<AbortSignal> abortSignal);\n\n  jsg::Promise<void> abort(jsg::Lock& js, jsg::Ref<Self> self, v8::Local<v8::Value> reason);\n\n  void advanceQueueIfNeeded(jsg::Lock& js, jsg::Ref<Self> self);\n\n  jsg::Promise<void> close(jsg::Lock& js, jsg::Ref<Self> self);\n\n  void dealWithRejection(jsg::Lock& js, jsg::Ref<Self> self, v8::Local<v8::Value> reason);\n\n  WriteRequest dequeueWriteRequest();\n\n  void doClose(jsg::Lock& js);\n\n  void doError(jsg::Lock& js, v8::Local<v8::Value> reason);\n\n  void error(jsg::Lock& js, jsg::Ref<Self> self, v8::Local<v8::Value> reason);\n\n  void finishErroring(jsg::Lock& js, jsg::Ref<Self> self);\n\n  void finishInFlightClose(\n      jsg::Lock& js, jsg::Ref<Self> self, kj::Maybe<v8::Local<v8::Value>> reason = kj::none);\n\n  void finishInFlightWrite(\n      jsg::Lock& js, jsg::Ref<Self> self, kj::Maybe<v8::Local<v8::Value>> reason = kj::none);\n\n  ssize_t getDesiredSize();\n\n  bool isCloseQueuedOrInFlight();\n\n  void rejectCloseAndClosedPromiseIfNeeded(jsg::Lock& js);\n\n  kj::Maybe<WritableStreamJsController&> tryGetOwner();\n\n  void setup(jsg::Lock& js,\n      jsg::Ref<Self> self,\n      UnderlyingSink underlyingSink,\n      StreamQueuingStrategy queuingStrategy);\n\n  // Puts the writable into an erroring state. This allows any in flight write or\n  // close to complete before actually transitioning the writable.\n  void startErroring(jsg::Lock& js, jsg::Ref<Self> self, v8::Local<v8::Value> reason);\n\n  // Notifies the Writer of the current backpressure state. If the amount of data queued\n  // is equal to or above the highwatermark, then backpressure is applied.\n  void updateBackpressure(jsg::Lock& js);\n\n  // Writes a chunk to the Writable, possibly queuing the chunk in the internal buffer\n  // if there are already other writes pending.\n  jsg::Promise<void> write(jsg::Lock& js, jsg::Ref<Self> self, v8::Local<v8::Value> value);\n\n  // True if the writable is in a state where new chunks can be written\n  bool isWritable() const;\n\n  void cancelPendingWrites(jsg::Lock& js, jsg::JsValue reason);\n\n  void visitForGc(jsg::GcVisitor& visitor);\n\n  kj::StringPtr jsgGetMemoryName() const;\n  size_t jsgGetMemorySelfSize() const;\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  struct Algorithms {\n    kj::Maybe<jsg::Function<UnderlyingSink::AbortAlgorithm>> abort;\n    kj::Maybe<jsg::Function<UnderlyingSink::CloseAlgorithm>> close;\n    kj::Maybe<jsg::Function<UnderlyingSink::WriteAlgorithm>> write;\n    kj::Maybe<jsg::Function<StreamQueuingStrategy::SizeAlgorithm>> size;\n\n    Algorithms() {};\n    ~Algorithms() {\n      // Clear all algorithm references to break circular references\n      clear();\n    }\n    Algorithms(Algorithms&& other) = default;\n    Algorithms& operator=(Algorithms&& other) = default;\n\n    void clear() {\n      abort = kj::none;\n      close = kj::none;\n      size = kj::none;\n      write = kj::none;\n    }\n\n    void visitForGc(jsg::GcVisitor& visitor) {\n      visitor.visit(write, close, abort, size);\n    }\n  };\n\n  struct Writable {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"writable\"_kj;\n  };\n\n  // State machine for WritableImpl:\n  // Writable is the active state where the stream can accept writes\n  // Erroring is a transitional state - waiting for in-flight ops before erroring\n  // Closed and Errored are terminal states\n  //   Writable -> Erroring (startErroring() called)\n  //   Writable -> Closed (finishInFlightClose() succeeds)\n  //   Erroring -> Errored (finishErroring() called)\n  //   Erroring -> Closed (finishInFlightClose() succeeds - close wins)\n  using State = StateMachine<TerminalStates<StreamStates::Closed>,\n      ErrorState<StreamStates::Errored>,\n      ActiveState<Writable>,\n      StreamStates::Closed,\n      StreamStates::Errored,\n      StreamStates::Erroring,\n      Writable>;\n\n  // Sadly, we have to use a weak ref here rather than jsg::Ref. This is because\n  // the jsg::Ref<WritableStream> (via its internal WritableStreamJsController)\n  // holds a strong reference to the jsg::Ref<WritableStreamDefaultController> that\n  // uses this WritableImpl. This creates a strong circular reference between jsg::Refs\n  // that isn't allowed. GcTracing ends up with a stack overflow as the two jsg::Refs\n  // try tracing each other.\n  kj::Maybe<kj::Own<WeakRef<WritableStream>>> owner;\n  jsg::Ref<AbortSignal> signal;\n  State state = State::template create<Writable>();\n  Algorithms algorithms;\n\n  size_t highWaterMark = 1;\n  size_t amountBuffered = 0;\n\n  RingBuffer<WriteRequest, 8> writeRequests;\n\n  kj::Maybe<WriteRequest> inFlightWrite;\n  kj::Maybe<jsg::Promise<void>::Resolver> inFlightClose;\n  kj::Maybe<jsg::Promise<void>::Resolver> closeRequest;\n  kj::Maybe<kj::Own<PendingAbort>> maybePendingAbort;\n\n  struct Flags {\n    uint8_t started : 1 = 0;\n    uint8_t starting : 1 = 0;\n    uint8_t backpressure : 1 = 0;\n    uint8_t pedanticWpt : 1 = 0;\n  };\n  Flags flags{};\n\n  friend Self;\n};\n\n// =======================================================================================\n\n// ReadableStreamDefaultController is a JavaScript object defined by the streams specification.\n// It is capable of streaming any JavaScript value through it, including typed arrays and\n// array buffers, but treats all values as opaque. BYOB reads are not supported.\nclass ReadableStreamDefaultController: public jsg::Object {\n public:\n  using QueueType = ValueQueue;\n  using ReadableImpl = ReadableImpl<ReadableStreamDefaultController>;\n\n  ReadableStreamDefaultController(\n      UnderlyingSource underlyingSource, StreamQueuingStrategy queuingStrategy);\n\n  void start(jsg::Lock& js);\n\n  jsg::Promise<void> cancel(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason);\n\n  void close(jsg::Lock& js);\n\n  bool canCloseOrEnqueue();\n  bool hasBackpressure();\n  kj::Maybe<int> getDesiredSize();\n\n  void enqueue(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> chunk);\n\n  void error(jsg::Lock& js, v8::Local<v8::Value> reason);\n\n  void pull(jsg::Lock& js);\n\n  // Like pull(), but bypasses backpressure checks. Used for draining reads\n  // which need to pull all available data regardless of highWaterMark.\n  void forcePull(jsg::Lock& js);\n\n  // True if a pull is currently in progress (the pull promise is pending).\n  bool isPulling() const {\n    return impl.isPulling();\n  }\n\n  kj::Own<ValueQueue::Consumer> getConsumer(\n      kj::Maybe<ValueQueue::ConsumerImpl::StateListener&> stateListener);\n\n  JSG_RESOURCE_TYPE(ReadableStreamDefaultController) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(desiredSize, getDesiredSize);\n    JSG_METHOD(close);\n    JSG_METHOD(enqueue);\n    JSG_METHOD(error);\n\n    JSG_TS_OVERRIDE(<R = any> {\n      enqueue(chunk?: R): void;\n    });\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"impl\", impl);\n  }\n\n  kj::Maybe<StreamStates::Errored> getMaybeErrorState(jsg::Lock& js);\n\n private:\n  kj::Maybe<IoContext&> ioContext;\n  ReadableImpl impl;\n\n  void visitForGc(jsg::GcVisitor& visitor);\n};\n\n// The ReadableStreamBYOBRequest is provided by the ReadableByteStreamController\n// and is used by user code to fill a view provided by a BYOB read request.\n// Because we always support autoAllocateChunkSize in the ReadableByteStreamController,\n// there will always be a ReadableStreamBYOBRequest available when there is a pending\n// read.\n//\n// The ReadableStreamBYOBRequest is either in an attached or detached state.\n// The request is detached when invalidate() is called. Attempts to use the request\n// after it has been detached will fail.\n//\n// Note that the casing of the name (e.g. \"BYOB\" instead of the kj style \"Byob\") is\n// dictated by the streams specification since the class name is used as the exported\n// object name.\nclass ReadableStreamBYOBRequest: public jsg::Object {\n public:\n  ReadableStreamBYOBRequest(jsg::Lock& js,\n      kj::Own<ByteQueue::ByobRequest> readRequest,\n      kj::Rc<WeakRef<ReadableByteStreamController>> controller);\n\n  KJ_DISALLOW_COPY_AND_MOVE(ReadableStreamBYOBRequest);\n\n  // getAtLeast is a non-standard Workers-specific extension that specifies\n  // the minimum number of bytes the stream should fill into the view. It is\n  // added to support the readAtLeast extension on the ReadableStreamBYOBReader.\n  kj::Maybe<int> getAtLeast();\n\n  kj::Maybe<jsg::V8Ref<v8::Uint8Array>> getView(jsg::Lock& js);\n\n  void invalidate(jsg::Lock& js);\n\n  void respond(jsg::Lock& js, int bytesWritten);\n\n  void respondWithNewView(jsg::Lock& js, jsg::BufferSource view);\n\n  JSG_RESOURCE_TYPE(ReadableStreamBYOBRequest) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(view, getView);\n    JSG_METHOD(respond);\n    JSG_METHOD(respondWithNewView);\n\n    // atLeast is an Workers-specific extension used to support the\n    // readAtLeast API.\n    JSG_READONLY_PROTOTYPE_PROPERTY(atLeast, getAtLeast);\n  }\n\n  bool isPartiallyFulfilled();\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  struct Impl {\n    kj::Own<ByteQueue::ByobRequest> readRequest;\n    kj::Rc<WeakRef<ReadableByteStreamController>> controller;\n    jsg::V8Ref<v8::Uint8Array> view;\n\n    size_t originalBufferByteLength;\n    size_t originalByteOffsetPlusBytesFilled;\n\n    Impl(jsg::Lock& js,\n        kj::Own<ByteQueue::ByobRequest> readRequest,\n        kj::Rc<WeakRef<ReadableByteStreamController>> controller);\n\n    void updateView(jsg::Lock& js);\n  };\n\n  kj::Maybe<IoContext&> ioContext;\n  kj::Maybe<Impl> maybeImpl;\n\n  void visitForGc(jsg::GcVisitor& visitor);\n};\n\n// ReadableByteStreamController is a JavaScript object defined by the streams specification.\n// It is capable of only streaming byte data through it in the form of typed arrays.\n// BYOB reads are supported.\nclass ReadableByteStreamController: public jsg::Object {\n public:\n  using QueueType = ByteQueue;\n  using ReadableImpl = ReadableImpl<ReadableByteStreamController>;\n\n  ReadableByteStreamController(\n      UnderlyingSource underlyingSource, StreamQueuingStrategy queuingStrategy);\n  ~ReadableByteStreamController() noexcept(false);\n\n  jsg::Ref<ReadableByteStreamController> getSelf() {\n    return JSG_THIS;\n  }\n\n  void start(jsg::Lock& js);\n\n  jsg::Promise<void> cancel(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> maybeReason);\n\n  void close(jsg::Lock& js);\n\n  void enqueue(jsg::Lock& js, jsg::BufferSource chunk);\n\n  void error(jsg::Lock& js, v8::Local<v8::Value> reason);\n\n  bool canCloseOrEnqueue();\n  bool hasBackpressure();\n  kj::Maybe<int> getDesiredSize();\n\n  kj::Maybe<jsg::Ref<ReadableStreamBYOBRequest>> getByobRequest(jsg::Lock& js);\n\n  void pull(jsg::Lock& js);\n\n  // Like pull(), but bypasses backpressure checks. Used for draining reads\n  // which need to pull all available data regardless of highWaterMark.\n  void forcePull(jsg::Lock& js);\n\n  // True if a pull is currently in progress (the pull promise is pending).\n  bool isPulling() const {\n    return impl.isPulling();\n  }\n\n  kj::Own<ByteQueue::Consumer> getConsumer(\n      kj::Maybe<ByteQueue::ConsumerImpl::StateListener&> stateListener);\n\n  JSG_RESOURCE_TYPE(ReadableByteStreamController) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(byobRequest, getByobRequest);\n    JSG_READONLY_PROTOTYPE_PROPERTY(desiredSize, getDesiredSize);\n    JSG_METHOD(close);\n    JSG_METHOD(enqueue);\n    JSG_METHOD(error);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"impl\", impl);\n    tracker.trackField(\"maybeByobRequest\", maybeByobRequest);\n  }\n\n private:\n  kj::Rc<WeakRef<ReadableByteStreamController>> weakSelf;\n  kj::Maybe<IoContext&> ioContext;\n  ReadableImpl impl;\n  kj::Maybe<jsg::Ref<ReadableStreamBYOBRequest>> maybeByobRequest;\n\n  void visitForGc(jsg::GcVisitor& visitor);\n\n  friend class ReadableStreamBYOBRequest;\n  friend class ReadableStreamJsController;\n};\n\n// =======================================================================================\n\n// The WritableStreamDefaultController is an object defined by the stream specification.\n// Writable streams are always value oriented. It is up the underlying sink implementation\n// to determine whether it is capable of handling whatever type of JavaScript object it\n// is given.\nclass WritableStreamDefaultController: public jsg::Object {\n public:\n  using WritableImpl = WritableImpl<WritableStreamDefaultController>;\n\n  explicit WritableStreamDefaultController(\n      jsg::Lock& js, WritableStream& owner, jsg::Ref<AbortSignal> abortSignal);\n\n  ~WritableStreamDefaultController() noexcept(false);\n\n  jsg::Promise<void> abort(jsg::Lock& js, v8::Local<v8::Value> reason);\n\n  jsg::Promise<void> close(jsg::Lock& js);\n\n  void error(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> reason);\n\n  kj::Maybe<ssize_t> getDesiredSize();\n\n  jsg::Ref<AbortSignal> getSignal();\n\n  kj::Maybe<v8::Local<v8::Value>> isErroring(jsg::Lock& js);\n\n  // Returns true if the stream is in the erroring state. Unlike the overload\n  // that takes a lock, this method does not require a lock since it doesn't\n  // return the error reason.\n  bool isErroring() const;\n\n  bool isStarted() {\n    return impl.flags.started;\n  }\n\n  bool hasBackpressure() {\n    return impl.flags.backpressure;\n  }\n\n  void setup(jsg::Lock& js, UnderlyingSink underlyingSink, StreamQueuingStrategy queuingStrategy);\n\n  jsg::Promise<void> write(jsg::Lock& js, v8::Local<v8::Value> value);\n\n  JSG_RESOURCE_TYPE(WritableStreamDefaultController) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(signal, getSignal);\n    JSG_METHOD(error);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n  void cancelPendingWrites(jsg::Lock& js, jsg::JsValue reason);\n\n  // Clear algorithms to break circular references during destruction\n  void clearAlgorithms();\n\n private:\n  kj::Maybe<IoContext&> ioContext;\n  WritableImpl impl;\n\n  void visitForGc(jsg::GcVisitor& visitor);\n};\n\n// =======================================================================================\n\n// The relationship between the TransformStreamDefaultController and the\n// readable/writable streams associated with it can be complicated.\n// Strong references to the TransformStreamDefaultController are held by\n// the *algorithms* passed into the readable and writable streams using\n// JSG_VISITABLE_LAMBDAs. When those algorithms are cleared, the strong\n// references holding the TransformStreamDefaultController are freed.\n// However, user code can do silly things like hold the Transform controller\n// long after both the readable and writable sides have been GC'ed.\nclass TransformStreamDefaultController: public jsg::Object {\n public:\n  TransformStreamDefaultController(jsg::Lock& js);\n\n  void init(jsg::Lock& js,\n      jsg::Ref<ReadableStream>& readable,\n      jsg::Ref<WritableStream>& writable,\n      jsg::Optional<Transformer> maybeTransformer);\n\n  // The startPromise is used by both the readable and writable sides in their respective\n  // start algorithms. The promise itself is resolved within the init function when the\n  // transformers own start algorithm completes.\n  inline jsg::Promise<void> getStartPromise(jsg::Lock& js) {\n    return startPromise.promise.whenResolved(js);\n  }\n\n  kj::Maybe<int> getDesiredSize();\n\n  void enqueue(jsg::Lock& js, v8::Local<v8::Value> chunk);\n\n  void error(jsg::Lock& js, v8::Local<v8::Value> reason);\n\n  void terminate(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(TransformStreamDefaultController) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(desiredSize, getDesiredSize);\n    JSG_METHOD(enqueue);\n    JSG_METHOD(error);\n    JSG_METHOD(terminate);\n\n    JSG_TS_OVERRIDE(<O = any> {\n      enqueue(chunk?: O): void;\n    });\n  }\n\n  jsg::Promise<void> write(jsg::Lock& js, v8::Local<v8::Value> chunk);\n  jsg::Promise<void> abort(jsg::Lock& js, v8::Local<v8::Value> reason);\n  jsg::Promise<void> close(jsg::Lock& js);\n  jsg::Promise<void> pull(jsg::Lock& js);\n  jsg::Promise<void> cancel(jsg::Lock& js, v8::Local<v8::Value> reason);\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  struct Algorithms {\n    kj::Maybe<jsg::Function<Transformer::TransformAlgorithm>> transform;\n    kj::Maybe<jsg::Function<Transformer::FlushAlgorithm>> flush;\n    kj::Maybe<jsg::Function<Transformer::CancelAlgorithm>> cancel;\n\n    kj::Maybe<jsg::Promise<void>> maybeFinish = kj::none;\n    // This flag is set to true at the start of a finish operation (close/cancel/abort)\n    // before the algorithm runs. This is needed because emplace() evaluates its argument\n    // before setting maybeFinish, so if the algorithm calls another finish operation\n    // synchronously, maybeFinish wouldn't be set yet.\n    bool finishStarted = false;\n\n    Algorithms() {};\n    Algorithms(Algorithms&& other) = default;\n    Algorithms& operator=(Algorithms&& other) = default;\n\n    inline void clear() {\n      transform = kj::none;\n      flush = kj::none;\n      cancel = kj::none;\n    }\n\n    inline void visitForGc(jsg::GcVisitor& visitor) {\n      visitor.visit(transform, flush, cancel, maybeFinish);\n    }\n  };\n\n  void errorWritableAndUnblockWrite(jsg::Lock& js, v8::Local<v8::Value> reason);\n  jsg::Promise<void> performTransform(jsg::Lock& js, v8::Local<v8::Value> chunk);\n  void setBackpressure(jsg::Lock& js, bool newBackpressure);\n\n  kj::Maybe<IoContext&> ioContext;\n  jsg::PromiseResolverPair<void> startPromise;\n\n  kj::Maybe<ReadableStreamDefaultController&> tryGetReadableController();\n  kj::Maybe<WritableStreamJsController&> tryGetWritableController();\n\n  kj::Maybe<jsg::Value> getReadableErrorState(jsg::Lock& js);\n\n  // Currently, JS-backed transform streams only support value-oriented streams.\n  // In the future, that may change and this will need to become a kj::OneOf\n  // that includes a ReadableByteStreamController.\n  kj::Maybe<jsg::Ref<ReadableStreamDefaultController>> readable;\n  kj::Maybe<jsg::Ref<WritableStream>> writable;\n  Algorithms algorithms;\n  bool backpressure = false;\n  kj::Maybe<jsg::PromiseResolverPair<void>> maybeBackpressureChange;\n\n  void visitForGc(jsg::GcVisitor& visitor);\n};\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/streams-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport * as assert from 'node:assert';\nimport * as util from 'node:util';\n\nexport const partiallyReadStream = {\n  async test(ctrl, env, ctx) {\n    const enc = new TextEncoder();\n    const rs = new ReadableStream({\n      type: 'bytes',\n      start(controller) {\n        controller.enqueue(enc.encode('hello'));\n        controller.enqueue(enc.encode('world'));\n        controller.close();\n      },\n    });\n    const reader = rs.getReader({ mode: 'byob' });\n    await reader.read(new Uint8Array(5));\n    reader.releaseLock();\n\n    // Should not throw!\n    await env.KV.put('key', rs);\n  },\n};\n\nexport const arrayBufferOfReadable = {\n  async test() {\n    const cs = new CompressionStream('gzip');\n    const cw = cs.writable.getWriter();\n    await cw.write(new TextEncoder().encode('0123456789'.repeat(1000)));\n    await cw.close();\n    const data = await new Response(cs.readable).arrayBuffer();\n    assert.equal(66, data.byteLength);\n\n    const ds = new DecompressionStream('gzip');\n    const dw = ds.writable.getWriter();\n    await dw.write(data);\n    await dw.close();\n\n    const read = await new Response(ds.readable).arrayBuffer();\n    assert.equal(10_000, read.byteLength);\n  },\n};\n\nexport const inspect = {\n  async test() {\n    const inspectOpts = { breakLength: Infinity };\n\n    // Check with JavaScript regular ReadableStream\n    {\n      let pulls = 0;\n      const readableStream = new ReadableStream({\n        pull(controller) {\n          if (pulls === 0) controller.enqueue('hello');\n          if (pulls === 1) controller.close();\n          pulls++;\n        },\n      });\n      assert.strictEqual(\n        util.inspect(readableStream, inspectOpts),\n        \"ReadableStream { locked: false, [state]: 'readable', [supportsBYOB]: false, [length]: undefined }\"\n      );\n\n      const reader = readableStream.getReader();\n      assert.strictEqual(\n        util.inspect(readableStream, inspectOpts),\n        \"ReadableStream { locked: true, [state]: 'readable', [supportsBYOB]: false, [length]: undefined }\"\n      );\n\n      await reader.read();\n      assert.strictEqual(\n        util.inspect(readableStream, inspectOpts),\n        \"ReadableStream { locked: true, [state]: 'readable', [supportsBYOB]: false, [length]: undefined }\"\n      );\n\n      await reader.read();\n      assert.strictEqual(\n        util.inspect(readableStream, inspectOpts),\n        \"ReadableStream { locked: true, [state]: 'closed', [supportsBYOB]: false, [length]: undefined }\"\n      );\n    }\n\n    // Check with errored JavaScript regular ReadableStream\n    {\n      const readableStream = new ReadableStream({\n        start(controller) {\n          controller.error(new Error('Oops!'));\n        },\n      });\n      assert.strictEqual(\n        util.inspect(readableStream, inspectOpts),\n        \"ReadableStream { locked: false, [state]: 'errored', [supportsBYOB]: false, [length]: undefined }\"\n      );\n    }\n\n    // Check with JavaScript bytes ReadableStream\n    {\n      const readableStream = new ReadableStream({\n        type: 'bytes',\n        pull(controller) {\n          controller.enqueue(new Uint8Array([1]));\n        },\n      });\n      assert.strictEqual(\n        util.inspect(readableStream, inspectOpts),\n        \"ReadableStream { locked: false, [state]: 'readable', [supportsBYOB]: true, [length]: undefined }\"\n      );\n    }\n\n    // Check with JavaScript WritableStream\n    {\n      const writableStream = new WritableStream({\n        write(chunk, controller) {},\n      });\n      assert.strictEqual(\n        util.inspect(writableStream, inspectOpts),\n        \"WritableStream { locked: false, [state]: 'writable', [expectsBytes]: false }\"\n      );\n\n      const writer = writableStream.getWriter();\n      assert.strictEqual(\n        util.inspect(writableStream, inspectOpts),\n        \"WritableStream { locked: true, [state]: 'writable', [expectsBytes]: false }\"\n      );\n\n      await writer.write('chunk');\n      assert.strictEqual(\n        util.inspect(writableStream, inspectOpts),\n        \"WritableStream { locked: true, [state]: 'writable', [expectsBytes]: false }\"\n      );\n\n      await writer.close();\n      assert.strictEqual(\n        util.inspect(writableStream, inspectOpts),\n        \"WritableStream { locked: true, [state]: 'closed', [expectsBytes]: false }\"\n      );\n    }\n\n    // Check with errored JavaScript WritableStream\n    {\n      const writableStream = new WritableStream({\n        write(chunk, controller) {\n          controller.error(new Error('Oops!'));\n        },\n      });\n      assert.strictEqual(\n        util.inspect(writableStream, inspectOpts),\n        \"WritableStream { locked: false, [state]: 'writable', [expectsBytes]: false }\"\n      );\n\n      const writer = writableStream.getWriter();\n      const promise = writer.write('chunk');\n      assert.strictEqual(\n        util.inspect(writableStream, inspectOpts),\n        \"WritableStream { locked: true, [state]: 'erroring', [expectsBytes]: false }\"\n      );\n\n      await promise;\n      assert.strictEqual(\n        util.inspect(writableStream, inspectOpts),\n        \"WritableStream { locked: true, [state]: 'errored', [expectsBytes]: false }\"\n      );\n    }\n\n    // Check with internal known-length TransformStream\n    {\n      const inspectOpts = { breakLength: 100 };\n      const transformStream = new FixedLengthStream(5);\n      assert.strictEqual(\n        util.inspect(transformStream, inspectOpts),\n        `FixedLengthStream {\n  readable: ReadableStream { locked: false, [state]: 'readable', [supportsBYOB]: true, [length]: 5n },\n  writable: WritableStream { locked: false, [state]: 'writable', [expectsBytes]: true }\n}`\n      );\n\n      const { writable, readable } = transformStream;\n      const writer = writable.getWriter();\n      assert.strictEqual(\n        util.inspect(transformStream, inspectOpts),\n        `FixedLengthStream {\n  readable: ReadableStream { locked: false, [state]: 'readable', [supportsBYOB]: true, [length]: 5n },\n  writable: WritableStream { locked: true, [state]: 'writable', [expectsBytes]: true }\n}`\n      );\n\n      void writer.write(new Uint8Array([1, 2, 3]));\n      void writer.write(new Uint8Array([4, 5]));\n      assert.strictEqual(\n        util.inspect(transformStream, inspectOpts),\n        `FixedLengthStream {\n  readable: ReadableStream { locked: false, [state]: 'readable', [supportsBYOB]: true, [length]: 5n },\n  writable: WritableStream { locked: true, [state]: 'writable', [expectsBytes]: true }\n}`\n      );\n\n      void writer.close();\n      assert.strictEqual(\n        util.inspect(transformStream, inspectOpts),\n        `FixedLengthStream {\n  readable: ReadableStream { locked: false, [state]: 'readable', [supportsBYOB]: true, [length]: 5n },\n  writable: WritableStream { locked: true, [state]: 'closed', [expectsBytes]: true }\n}`\n      );\n\n      const reader = readable.getReader();\n      assert.strictEqual(\n        util.inspect(transformStream, inspectOpts),\n        `FixedLengthStream {\n  readable: ReadableStream { locked: true, [state]: 'readable', [supportsBYOB]: true, [length]: 5n },\n  writable: WritableStream { locked: true, [state]: 'closed', [expectsBytes]: true }\n}`\n      );\n\n      await reader.read();\n      assert.strictEqual(\n        util.inspect(transformStream, inspectOpts),\n        `FixedLengthStream {\n  readable: ReadableStream { locked: true, [state]: 'readable', [supportsBYOB]: true, [length]: 2n },\n  writable: WritableStream { locked: true, [state]: 'closed', [expectsBytes]: true }\n}`\n      );\n\n      await reader.read();\n      assert.strictEqual(\n        util.inspect(transformStream, inspectOpts),\n        `FixedLengthStream {\n  readable: ReadableStream { locked: true, [state]: 'readable', [supportsBYOB]: true, [length]: 0n },\n  writable: WritableStream { locked: true, [state]: 'closed', [expectsBytes]: true }\n}`\n      );\n\n      await reader.read();\n      assert.strictEqual(\n        util.inspect(transformStream, inspectOpts),\n        `FixedLengthStream {\n  readable: ReadableStream { locked: true, [state]: 'closed', [supportsBYOB]: true, [length]: 0n },\n  writable: WritableStream { locked: true, [state]: 'closed', [expectsBytes]: true }\n}`\n      );\n    }\n\n    // Check with errored internal TransformStream\n    {\n      const inspectOpts = { breakLength: 100 };\n      const transformStream = new IdentityTransformStream();\n      assert.strictEqual(\n        util.inspect(transformStream, inspectOpts),\n        `IdentityTransformStream {\n  readable: ReadableStream { locked: false, [state]: 'readable', [supportsBYOB]: true, [length]: undefined },\n  writable: WritableStream { locked: false, [state]: 'writable', [expectsBytes]: true }\n}`\n      );\n\n      const { writable, readable } = transformStream;\n      const writer = writable.getWriter();\n      void writer.abort(new Error('Oops!'));\n      assert.strictEqual(\n        util.inspect(transformStream, inspectOpts),\n        `IdentityTransformStream {\n  readable: ReadableStream { locked: false, [state]: 'readable', [supportsBYOB]: true, [length]: undefined },\n  writable: WritableStream { locked: true, [state]: 'errored', [expectsBytes]: true }\n}`\n      );\n\n      const reader = readable.getReader();\n      assert.strictEqual(\n        util.inspect(transformStream, inspectOpts),\n        `IdentityTransformStream {\n  readable: ReadableStream { locked: true, [state]: 'readable', [supportsBYOB]: true, [length]: undefined },\n  writable: WritableStream { locked: true, [state]: 'errored', [expectsBytes]: true }\n}`\n      );\n\n      await reader.read().catch(() => {});\n      assert.strictEqual(\n        util.inspect(transformStream, inspectOpts),\n        `IdentityTransformStream {\n  readable: ReadableStream { locked: true, [state]: 'errored', [supportsBYOB]: true, [length]: undefined },\n  writable: WritableStream { locked: true, [state]: 'errored', [expectsBytes]: true }\n}`\n      );\n    }\n  },\n};\n\n// Test for re-entrancy bug: when pushing to multiple consumers (via tee),\n// the transform function can directly cancel another consumer synchronously.\n// This should not crash - the cancelled consumer should gracefully ignore the push.\n// Before the fix, this would crash with:\n// \"expected state.template tryGet<Ready>() != nullptr; The consumer is either closed or errored.\"\n//\n// This test simulates the production scenario where:\n// 1. A TransformStream's readable is tee'd\n// 2. The transform function synchronously cancels one of the tee branches\n// 3. When enqueue is called, the push loop tries to push to the cancelled consumer\nexport const transformTeeReentrancySynchronousCancel = {\n  async test() {\n    let reader2;\n    let cancelledBranch2 = false;\n\n    // Create a TransformStream whose transform function cancels branch2\n    const ts = new TransformStream({\n      transform(chunk, controller) {\n        // First time through, cancel branch2 BEFORE enqueueing\n        // This simulates the production scenario where user code in the\n        // transform function affects another consumer\n        if (!cancelledBranch2 && reader2) {\n          reader2.cancel('cancelled synchronously in transform');\n          cancelledBranch2 = true;\n        }\n        controller.enqueue(chunk);\n      },\n    });\n\n    const writer = ts.writable.getWriter();\n    const [branch1, branch2] = ts.readable.tee();\n    const reader1 = branch1.getReader();\n    reader2 = branch2.getReader();\n\n    // Start pending reads on both branches\n    const read1Promise = reader1.read();\n    const read2Promise = reader2.read();\n\n    // Write to the transform - this triggers the transform function which:\n    // 1. Cancels branch2 (closing/erroring its consumer)\n    // 2. Calls controller.enqueue() which pushes to all consumers\n    // Before the fix, step 2 would crash when trying to push to cancelled branch2\n    await writer.write('test data');\n\n    // Verify branch1 got the data\n    const result1 = await read1Promise;\n    assert.strictEqual(result1.value, 'test data');\n\n    // branch2 was cancelled, so its read should complete (done or with data before cancel)\n    const result2 = await read2Promise;\n    assert.ok(result2 !== undefined);\n\n    await writer.close();\n    await reader1.cancel();\n  },\n};\n\n// Test with TransformStream to match the production stack trace more closely.\n// The production bug occurred during: TransformStream → enqueue → QueueImpl::push → consumer iteration\nexport const transformStreamTeeReentrancy = {\n  async test() {\n    const { readable, writable } = new TransformStream();\n    const writer = writable.getWriter();\n\n    const [branch1, branch2] = readable.tee();\n    const reader1 = branch1.getReader();\n    const reader2 = branch2.getReader();\n\n    // Start pending reads on both branches\n    const read1Promise = reader1.read();\n    const read2Promise = reader2.read();\n\n    // When read1 resolves, cancel branch2\n    // This simulates the production scenario where a .then() handler\n    // attached to the read promise cancels another branch\n    read1Promise.then(() => {\n      reader2.cancel('cancelled during transform');\n    });\n\n    // Write through the transform - this triggers enqueue on the readable side.\n    // Before the fix, this would crash when the push loop tried to push to\n    // the now-cancelled branch2 consumer.\n    await writer.write('transform data');\n\n    // Verify read1 succeeded\n    const result1 = await read1Promise;\n    assert.strictEqual(result1.value, 'transform data');\n\n    // read2 may have received data or be done - either is fine\n    // The important thing is no crash occurred\n    const result2 = await read2Promise;\n    assert.ok(result2 !== undefined);\n\n    await writer.close();\n    await reader1.cancel();\n  },\n};\n\n// Test that multiple writes through a tee'd ReadableStream work correctly\n// even when one branch is cancelled mid-stream\nexport const teeWithCancelMidStream = {\n  async test() {\n    let controller;\n    const stream = new ReadableStream({\n      start(c) {\n        controller = c;\n      },\n    });\n\n    const [branch1, branch2] = stream.tee();\n    const reader1 = branch1.getReader();\n    const reader2 = branch2.getReader();\n\n    // Start reads on both branches\n    let read1Promise = reader1.read();\n    let read2Promise = reader2.read();\n\n    // Enqueue first chunk - both branches should get it\n    controller.enqueue('chunk1');\n    const r1a = await read1Promise;\n    const r2a = await read2Promise;\n    assert.strictEqual(r1a.value, 'chunk1');\n    assert.strictEqual(r2a.value, 'chunk1');\n\n    // Now cancel branch2\n    await reader2.cancel('done with branch2');\n\n    // Start another read on branch1\n    read1Promise = reader1.read();\n\n    // Enqueue second chunk - only branch1 should get it\n    // This should not crash even though branch2's consumer is now closed\n    controller.enqueue('chunk2');\n    const r1b = await read1Promise;\n    assert.strictEqual(r1b.value, 'chunk2');\n\n    // Start another read and enqueue third chunk to confirm continued operation\n    read1Promise = reader1.read();\n    controller.enqueue('chunk3');\n    const r1c = await read1Promise;\n    assert.strictEqual(r1c.value, 'chunk3');\n\n    // Close and verify\n    read1Promise = reader1.read();\n    controller.close();\n    const r1d = await read1Promise;\n    assert.strictEqual(r1d.done, true);\n  },\n};\n\n// ============================================================================\n\nexport const testCancelPipethrough = {\n  async test() {\n    const enc = new TextEncoder();\n    const transform = new IdentityTransformStream();\n    const rs = new ReadableStream({\n      start(c) {\n        c.enqueue(enc.encode('hello'));\n      },\n    });\n    const readable = rs.pipeThrough(transform);\n\n    const reader = readable.getReader();\n\n    assert.ok(rs.locked);\n    assert.ok(transform.writable.locked);\n\n    reader.cancel(new Error('boom'));\n    reader.releaseLock();\n\n    // We've got to wait a tick to allow the cancel to propagate\n    await scheduler.wait(1);\n\n    assert.ok(!rs.locked);\n    assert.ok(!transform.readable.locked);\n    assert.ok(!transform.writable.locked);\n\n    // Our JavaScript ReadableStream should be closed (not errored).\n    // Cancel propagates back and closes the source stream.\n    const reader2 = rs.getReader();\n    const result = await reader2.read();\n    assert.ok(result.done);\n    assert.strictEqual(result.value, undefined);\n  },\n};\n\n// Same as testCancelPipethrough but uses a JavaScript-backed TransformStream\n// instead of IdentityTransformStream. The behavior should be the same.\nexport const testCancelPipethrough2 = {\n  async test() {\n    const enc = new TextEncoder();\n    const transform = new TransformStream();\n    const rs = new ReadableStream({\n      start(c) {\n        c.enqueue(enc.encode('hello'));\n      },\n    });\n    const readable = rs.pipeThrough(transform);\n\n    const reader = readable.getReader();\n\n    assert.ok(rs.locked);\n    assert.ok(transform.writable.locked);\n\n    reader.cancel(new Error('boom'));\n    reader.releaseLock();\n\n    // We've got to wait a tick to allow the cancel to propagate\n    await scheduler.wait(1);\n\n    assert.ok(!rs.locked);\n    assert.ok(!transform.readable.locked);\n    assert.ok(!transform.writable.locked);\n\n    // Our JavaScript ReadableStream should be closed (not errored).\n    // Cancel propagates back and closes the source stream.\n    const reader2 = rs.getReader();\n    const result = await reader2.read();\n    assert.ok(result.done);\n    assert.strictEqual(result.value, undefined);\n  },\n};\n\nexport const ResponseTextLargeBody = {\n  async test() {\n    const targetSize = 2147483648 + 1024 * 1024;\n    let sent = 0;\n    const chunkSize = 64 * 1024 * 1024;\n\n    const stream = new ReadableStream({\n      pull(controller) {\n        if (sent >= targetSize) {\n          controller.close();\n          return;\n        }\n        const size = Math.min(chunkSize, targetSize - sent);\n        controller.enqueue(new Uint8Array(size).fill(0x41));\n        sent += size;\n      },\n    });\n\n    await assert.rejects(\n      new Response(stream).text(),\n      (e) => e instanceof RangeError\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/streams/streams-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"streams-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"streams-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"streams_enable_constructors\", \"transformstream_enable_standard_constructor\", \"workers_api_getters_setters_on_prototype\"],\n        bindings = [ ( name = \"KV\", kvNamespace = \"kv\" ) ],\n      )\n    ),\n    ( name = \"kv\",\n      worker = (\n        modules = [\n          (name = \"kv\", esModule =\n            `export default {\n            `  async fetch(request, env, ctx) {\n            `    return new Response(await request.arrayBuffer());\n            `  }\n            `}\n          )\n        ],\n        compatibilityFlags = [\"nodejs_compat\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/streams/transform.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"transform.h\"\n\n#include \"identity-transform-stream.h\"\n#include \"standard.h\"\n\n#include <workerd/io/features.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::api {\n\nnamespace {\ntemplate <typename T>\njsg::Function<T> maybeAddFunctor(auto t) {\n  KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n    return jsg::Function<T>(ioContext.addFunctor(kj::mv(t)));\n  }\n  return jsg::Function<T>(kj::mv(t));\n}\n}  // namespace\n\njsg::Ref<TransformStream> TransformStream::constructor(jsg::Lock& js,\n    jsg::Optional<Transformer> maybeTransformer,\n    jsg::Optional<StreamQueuingStrategy> maybeWritableStrategy,\n    jsg::Optional<StreamQueuingStrategy> maybeReadableStrategy) {\n\n  if (FeatureFlags::get(js).getTransformStreamJavaScriptControllers()) {\n    // The standard implementation. Here the TransformStream is backed by readable\n    // and writable streams using the JavaScript-backed controllers. Data that is\n    // written to the writable side passes through the transform function that is\n    // given in maybeTransformer. If no transform function is given, then any value\n    // written is passed through unchanged.\n    //\n    // Per the standard specification, any JavaScript value can be written to and\n    // read from the transform stream, and the readable side does *not* support BYOB\n    // reads.\n    //\n    // Persistent references to the TransformStreamDefaultController are held by both\n    // the readable and writable sides. The actual TransformStream object can be dropped\n    // and allowed to be garbage collected.\n\n    auto controller = js.alloc<TransformStreamDefaultController>(js);\n    auto transformer = kj::mv(maybeTransformer).orDefault({});\n\n    // By default, let's signal backpressure on the readable side by setting the highWaterMark\n    // to zero if a strategy is not given. This effectively means that writes/reads will be\n    // one to one as long as the writer is respecting backpressure signals. If buffering\n    // occurs, it will happen in the writable side of the transform stream.\n    auto readableStrategy = kj::mv(maybeReadableStrategy)\n                                .orDefault(StreamQueuingStrategy{\n                                  .highWaterMark = 0,\n                                });\n\n    auto readable = ReadableStream::constructor(js,\n        UnderlyingSource{\n          .type = kj::none,\n          .autoAllocateChunkSize = kj::none,\n          .start = maybeAddFunctor<UnderlyingSource::StartAlgorithm>(\n              JSG_VISITABLE_LAMBDA((controller = controller.addRef()), (controller),\n                  (jsg::Lock & js, auto c) mutable { return controller->getStartPromise(js); })),\n          .pull = maybeAddFunctor<UnderlyingSource::PullAlgorithm>(\n              JSG_VISITABLE_LAMBDA((controller = controller.addRef()), (controller),\n                  (jsg::Lock & js, auto c) mutable { return controller->pull(js); })),\n          .cancel = maybeAddFunctor<UnderlyingSource::CancelAlgorithm>( JSG_VISITABLE_LAMBDA(\n              (controller = controller.addRef()), (controller),\n              (jsg::Lock & js, auto reason) mutable { return controller->cancel(js, reason); })),\n          .expectedLength = transformer.expectedLength.map(\n              [](uint64_t expectedLength) { return expectedLength; }),\n        },\n        kj::mv(readableStrategy));\n\n    auto writable = WritableStream::constructor(js,\n        UnderlyingSink{\n          .type = kj::none,\n          .start = maybeAddFunctor<UnderlyingSink::StartAlgorithm>(\n              JSG_VISITABLE_LAMBDA((controller = controller.addRef()), (controller),\n                  (jsg::Lock & js, auto c) mutable { return controller->getStartPromise(js); })),\n          .write = maybeAddFunctor<UnderlyingSink::WriteAlgorithm>(\n              JSG_VISITABLE_LAMBDA((controller = controller.addRef()), (controller),\n                  (jsg::Lock & js, auto chunk, auto c) mutable {\n                    return controller->write(js, chunk);\n                  })),\n          .abort = maybeAddFunctor<UnderlyingSink::AbortAlgorithm>(\n              JSG_VISITABLE_LAMBDA((controller = controller.addRef()), (controller),\n                  (jsg::Lock & js, auto reason) mutable { return controller->abort(js, reason); })),\n          .close = maybeAddFunctor<UnderlyingSink::CloseAlgorithm>(\n              JSG_VISITABLE_LAMBDA((controller = controller.addRef()), (controller),\n                  (jsg::Lock & js) mutable { return controller->close(js); })),\n        },\n        kj::mv(maybeWritableStrategy));\n\n    // The controller will store c++ references to both the readable and writable\n    // streams underlying controllers.\n    controller->init(js, readable, writable, kj::mv(transformer));\n\n    return js.alloc<TransformStream>(kj::mv(readable), kj::mv(writable));\n  }\n\n  // The old implementation just defers to IdentityTransformStream. If any of the arguments\n  // are specified we throw because it's most likely that they want the standard implementation\n  // but the compatibility flag is not set.\n  if (maybeTransformer != kj::none || maybeWritableStrategy != kj::none ||\n      maybeReadableStrategy != kj::none) {\n    IoContext::current().logWarningOnce(\n        \"To use the new TransformStream() constructor with a \"\n        \"custom transformer, enable the transformstream_enable_standard_constructor compatibility flag. \"\n        \"Refer to the docs for more information: https://developers.cloudflare.com/workers/platform/compatibility-dates/#compatibility-flags\");\n  }\n\n  return IdentityTransformStream::constructor(js);\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/transform.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"readable.h\"\n#include \"writable.h\"\n\nnamespace workerd::api {\n\n// A TransformStream is a readable, writable pair in which whatever is written to the writable\n// side can be read from the readable side, possibly transformed into a different type of value.\n//\n// The original version of TransformStream in Workers was nothing more than an identity\n// passthrough that only handled byte data. No actual transformation of the value was performed.\n// The original version did not conform to the streams standard. That original version has been\n// migrated into the IdentityTransformStream class. If the\n// transformstream_enable_standard_constructor compatibility flag is not enabled, then\n// TransformStream is just an alias for IdentityTransformStream and continues to implement the\n// non-standard behavior. With the transformstream_enable_standard_constructor flag set, however,\n// the TransformStream implements standardized behavior.\nclass TransformStream: public jsg::Object {\n public:\n  explicit TransformStream(jsg::Ref<ReadableStream> readable, jsg::Ref<WritableStream> writable)\n      : readable(kj::mv(readable)),\n        writable(kj::mv(writable)) {}\n\n  static jsg::Ref<TransformStream> constructor(jsg::Lock& js,\n      jsg::Optional<Transformer> maybeTransformer,\n      jsg::Optional<StreamQueuingStrategy> maybeWritableStrategy,\n      jsg::Optional<StreamQueuingStrategy> maybeReadableStrategy);\n\n  jsg::Ref<ReadableStream> getReadable() {\n    return readable.addRef();\n  }\n  jsg::Ref<WritableStream> getWritable() {\n    return writable.addRef();\n  }\n\n  JSG_RESOURCE_TYPE(TransformStream, CompatibilityFlags::Reader flags) {\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(readable, getReadable);\n      JSG_READONLY_PROTOTYPE_PROPERTY(writable, getWritable);\n\n      JSG_TS_OVERRIDE(<I = any, O = any> {\n        constructor(transformer?: Transformer<I, O>, writableStrategy?: QueuingStrategy<I>, readableStrategy?: QueuingStrategy<O>);\n        get readable(): ReadableStream<O>;\n        get writable(): WritableStream<I>;\n      });\n    } else {\n      JSG_READONLY_INSTANCE_PROPERTY(readable, getReadable);\n      JSG_READONLY_INSTANCE_PROPERTY(writable, getWritable);\n\n      JSG_TS_OVERRIDE(<I = any, O = any> {\n        constructor(transformer?: Transformer<I, O>, writableStrategy?: QueuingStrategy<I>, readableStrategy?: QueuingStrategy<O>);\n        readonly readable: ReadableStream<O>;\n        readonly writable: WritableStream<I>;\n      });\n    }\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"readable\", readable);\n    tracker.trackField(\"writable\", writable);\n  }\n\n private:\n  jsg::Ref<ReadableStream> readable;\n  jsg::Ref<WritableStream> writable;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(readable, writable);\n  }\n};\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/writable-sink-adapter-test.c++",
    "content": "#include \"standard.h\"\n#include \"writable-sink-adapter.h\"\n#include \"writable.h\"\n\n#include <workerd/api/system-streams.h>\n#include <workerd/jsg/jsg-test.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/tests/test-fixture.h>\n#include <workerd/util/own-util.h>\n#include <workerd/util/stream-utils.h>\n\nnamespace workerd::api::streams {\n\nnamespace {\nstruct SimpleEventRecordingSink final: public kj::AsyncOutputStream {\n  struct State {\n    size_t writeCalled = 0;\n  };\n  State state;\n\n  State& getState() {\n    return state;\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override {\n    state.writeCalled++;\n    return kj::READY_NOW;\n  }\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {\n    state.writeCalled++;\n    return kj::READY_NOW;\n  }\n\n  kj::Promise<void> whenWriteDisconnected() override {\n    return kj::NEVER_DONE;\n  }\n};\n\nstruct NeverReadySink final: public kj::AsyncOutputStream {\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override {\n    return kj::NEVER_DONE;\n  }\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {\n    return kj::NEVER_DONE;\n  }\n\n  kj::Promise<void> whenWriteDisconnected() override {\n    return kj::NEVER_DONE;\n  }\n};\n\nstruct ThrowingSink final: public kj::AsyncOutputStream {\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override {\n    KJ_FAIL_REQUIRE(\"worker_do_not_log; write() always throws\");\n    co_return;\n  }\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {\n    KJ_FAIL_REQUIRE(\"worker_do_not_log; write() always throws\");\n    co_return;\n  }\n\n  kj::Promise<void> whenWriteDisconnected() override {\n    return kj::NEVER_DONE;\n  }\n};\n}  // namespace\n\nKJ_TEST(\"Basic construction with default options\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto sink =\n        newIoContextWrappedWritableSink(env.context, newWritableSink(newNullOutputStream()));\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, kj::mv(sink));\n\n    KJ_ASSERT(!adapter->isClosed(), \"Adapter should not be closed upon construction\");\n    KJ_ASSERT(!adapter->isClosing(), \"Adapter should not be closing upon construction\");\n    KJ_ASSERT(adapter->isErrored() == kj::none, \"Adapter should not be errored upon construction\");\n    KJ_ASSERT(KJ_ASSERT_NONNULL(adapter->getDesiredSize()) == 16384,\n        \"Adapter should have default highWaterMark of 16384\");\n    auto& options = KJ_ASSERT_NONNULL(adapter->getOptions());\n    KJ_ASSERT(options.highWaterMark == 16384);\n    KJ_ASSERT(options.detachOnWrite == false);\n\n    auto readyPromise = adapter->getReady(env.js);\n    KJ_ASSERT(readyPromise.getState(env.js) == jsg::Promise<void>::State::FULFILLED,\n        \"Initial ready promise should be fulfilled\");\n  });\n}\n\nKJ_TEST(\"Construction with custom highWaterMark option\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto sink =\n        newIoContextWrappedWritableSink(env.context, newWritableSink(newNullOutputStream()));\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, kj::mv(sink),\n        WritableStreamSinkJsAdapter::Options{.highWaterMark = 100});\n\n    KJ_ASSERT(KJ_ASSERT_NONNULL(adapter->getDesiredSize()) == 100,\n        \"Adapter should have custom highWaterMark of 100\");\n  });\n}\n\nKJ_TEST(\"Construction with detachOnWrite=true option\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto sink =\n        newIoContextWrappedWritableSink(env.context, newWritableSink(newNullOutputStream()));\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, kj::mv(sink),\n        WritableStreamSinkJsAdapter::Options{\n          .detachOnWrite = true,\n        });\n    auto& options = KJ_ASSERT_NONNULL(adapter->getOptions());\n    KJ_ASSERT(options.detachOnWrite == true);\n  });\n}\n\nKJ_TEST(\"Construction with all custom options combined\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto sink =\n        newIoContextWrappedWritableSink(env.context, newWritableSink(newNullOutputStream()));\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, kj::mv(sink),\n        WritableStreamSinkJsAdapter::Options{\n          .highWaterMark = 100,\n          .detachOnWrite = true,\n        });\n    auto& options = KJ_ASSERT_NONNULL(adapter->getOptions());\n    KJ_ASSERT(options.highWaterMark == 100);\n    KJ_ASSERT(options.detachOnWrite == true);\n  });\n}\n\nKJ_TEST(\"Basic end() operation completes successfully\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto sink =\n        newIoContextWrappedWritableSink(env.context, newWritableSink(newNullOutputStream()));\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, kj::mv(sink));\n\n    auto endPromise = adapter->end(env.js);\n\n    KJ_ASSERT(endPromise.getState(env.js) == jsg::Promise<void>::State::PENDING,\n        \"End promise should be pending immediately after end() call\");\n    KJ_ASSERT(\n        adapter->isClosed() == false, \"Adapter should not be closed immediately after end() call\");\n    KJ_ASSERT(adapter->isClosing() == true,\n        \"Adapter should be in closing state immediately after end() call\");\n    KJ_ASSERT(adapter->isErrored() == kj::none, \"Adapter should not be errored after end() call\");\n\n    auto rejectedEnd = adapter->end(env.js);\n    KJ_ASSERT(rejectedEnd.getState(env.js) == jsg::Promise<void>::State::REJECTED,\n        \"Second end() call should be rejected\");\n\n    auto rejectedWrite = adapter->write(env.js, env.js.str(\"data\"_kj));\n    KJ_ASSERT(rejectedWrite.getState(env.js) == jsg::Promise<void>::State::REJECTED,\n        \"Write after end() call should be rejected\");\n\n    auto rejectedFlush = adapter->flush(env.js);\n    KJ_ASSERT(rejectedFlush.getState(env.js) == jsg::Promise<void>::State::REJECTED,\n        \"Flush after end() call should be rejected\");\n\n    return env.context\n        .awaitJs(env.js, endPromise.then(env.js, [&adapter = *adapter](jsg::Lock& js) {\n      KJ_ASSERT(\n          adapter.isClosed() == true, \"Adapter should be closed after end() promise resolves\");\n      KJ_ASSERT(adapter.isClosing() == false,\n          \"Adapter should not be in closing state after end() promise resolves\");\n      KJ_ASSERT(\n          adapter.isErrored() == kj::none, \"Adapter should not be errored after successful end()\");\n      KJ_ASSERT(adapter.getDesiredSize() == kj::none,\n          \"Desired size should be none after adapter is closed\");\n\n      auto rejectedEnd = adapter.end(js);\n      KJ_ASSERT(rejectedEnd.getState(js) == jsg::Promise<void>::State::FULFILLED,\n          \"Second end() call should be fulfilled\");\n\n      auto rejectedWrite = adapter.write(js, js.str(\"data\"_kj));\n      KJ_ASSERT(rejectedWrite.getState(js) == jsg::Promise<void>::State::REJECTED,\n          \"Write after end() call should be rejected\");\n\n      auto rejectedFlush = adapter.flush(js);\n      KJ_ASSERT(rejectedFlush.getState(js) == jsg::Promise<void>::State::REJECTED,\n          \"Flush after end() call should be rejected\");\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Basic abort() operation\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto sink =\n        newIoContextWrappedWritableSink(env.context, newWritableSink(newNullOutputStream()));\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, kj::mv(sink));\n\n    adapter->abort(env.js, env.js.str(\"Abort reason\"_kj));\n\n    KJ_ASSERT(adapter->isClosed() == false, \"Adapter should not be closed after abort()\");\n    KJ_ASSERT(adapter->isClosing() == false, \"Adapter should not be closing after abort()\");\n    auto exception =\n        KJ_ASSERT_NONNULL(adapter->isErrored(), \"Adapter should be in errored state after abort()\");\n    KJ_ASSERT(exception.getDescription().contains(\"Abort reason\"),\n        \"Adapter should be in errored state after abort()\");\n\n    KJ_ASSERT(adapter->getDesiredSize() == kj::none,\n        \"Desired size should be none after adapter is errored\");\n\n    auto rejectedWrite = adapter->write(env.js, env.js.str(\"data\"_kj));\n    KJ_ASSERT(rejectedWrite.getState(env.js) == jsg::Promise<void>::State::REJECTED,\n        \"Write after abort() call should be rejected\");\n\n    auto rejectedFlush = adapter->flush(env.js);\n    KJ_ASSERT(rejectedFlush.getState(env.js) == jsg::Promise<void>::State::REJECTED,\n        \"Flush after abort() call should be rejected\");\n\n    auto rejectedEnd = adapter->end(env.js);\n    KJ_ASSERT(rejectedEnd.getState(env.js) == jsg::Promise<void>::State::REJECTED,\n        \"End after abort() call should be rejected\");\n\n    adapter->abort(env.js, env.js.str(\"Abort reason 2\"_kj));\n    auto exception2 = KJ_ASSERT_NONNULL(\n        adapter->isErrored(), \"Adapter should still be in errored state after second abort()\");\n    KJ_ASSERT(exception2.getDescription().contains(\"Abort reason 2\"),\n        \"Adapter should reflect reason from second abort() call\");\n  });\n}\n\nKJ_TEST(\"Abort from closing state supersedes close\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto sink =\n        newIoContextWrappedWritableSink(env.context, newWritableSink(newNullOutputStream()));\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, kj::mv(sink));\n\n    auto endPromise = adapter->end(env.js);\n    KJ_ASSERT(endPromise.getState(env.js) == jsg::Promise<void>::State::PENDING,\n        \"End promise should be pending immediately after end() call\");\n    KJ_ASSERT(adapter->isClosing() == true,\n        \"Adapter should be in closing state immediately after end() call\");\n\n    adapter->abort(env.js, env.js.str(\"Abort reason\"_kj));\n\n    KJ_ASSERT(adapter->isClosed() == false, \"Adapter should not be closed after abort()\");\n    KJ_ASSERT(adapter->isClosing() == false, \"Adapter should not be closing after abort()\");\n    auto exception =\n        KJ_ASSERT_NONNULL(adapter->isErrored(), \"Adapter should be in errored state after abort()\");\n    KJ_ASSERT(exception.getDescription().contains(\"Abort reason\"),\n        \"Adapter should be in errored state after abort()\");\n\n    KJ_ASSERT(adapter->getDesiredSize() == kj::none,\n        \"Desired size should be none after adapter is errored\");\n\n    auto rejectedWrite = adapter->write(env.js, env.js.str(\"data\"_kj));\n    KJ_ASSERT(rejectedWrite.getState(env.js) == jsg::Promise<void>::State::REJECTED,\n        \"Write after abort() call should be rejected\");\n\n    auto rejectedFlush = adapter->flush(env.js);\n    KJ_ASSERT(rejectedFlush.getState(env.js) == jsg::Promise<void>::State::REJECTED,\n        \"Flush after abort() call should be rejected\");\n\n    auto rejectedEnd = adapter->end(env.js);\n    KJ_ASSERT(rejectedEnd.getState(env.js) == jsg::Promise<void>::State::REJECTED,\n        \"End after abort() call should be rejected\");\n\n    return env.context\n        .awaitJs(env.js, endPromise.then(env.js, [](jsg::Lock& js) {\n      return js.rejectedPromise<void>(js.error(\"End promise should not resolve after abort()\"));\n    }, [](jsg::Lock& js, jsg::Value exception) {\n      return js.resolvedPromise();\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Abort from closed state\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto sink =\n        newIoContextWrappedWritableSink(env.context, newWritableSink(newNullOutputStream()));\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, kj::mv(sink));\n\n    auto endPromise = adapter->end(env.js);\n    return env.context.awaitJs(env.js, kj::mv(endPromise))\n        .then([adapter = kj::mv(adapter)]() mutable {\n      KJ_ASSERT(adapter->isClosed() == true, \"Adapter should be closed after end()\");\n      adapter->abort(KJ_EXCEPTION(FAILED, \"Abort after closed should be no-op\"));\n      KJ_ASSERT(adapter->isClosed() == false,\n          \"Adapter switches to errored state after abort() from closed state\");\n      KJ_ASSERT_NONNULL(adapter->isErrored(),\n          \"Adapter should be in errored state after abort() from closed state\");\n    });\n  });\n}\n\nKJ_TEST(\"Abort rejects ready promise with abort reason\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto sink =\n        newIoContextWrappedWritableSink(env.context, newWritableSink(newNullOutputStream()));\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, kj::mv(sink),\n        WritableStreamSinkJsAdapter::Options{\n          .highWaterMark = 1,\n        });\n    adapter->write(env.js, env.js.str(\"data\"_kj));\n    adapter->write(env.js, env.js.str(\"data2\"_kj));\n\n    auto readyPromise = adapter->getReady(env.js);\n    KJ_ASSERT(readyPromise.getState(env.js) == jsg::Promise<void>::State::PENDING,\n        \"Teady promise should be pending\");\n\n    adapter->abort(env.js, env.js.str(\"Abort reason\"_kj));\n\n    return env.context\n        .awaitJs(env.js, readyPromise.then(env.js, [](jsg::Lock& js) {\n      return js.rejectedPromise<void>(js.error(\"Ready promise should not resolve after abort()\"));\n    }, [](jsg::Lock& js, jsg::Value exception) {\n      auto ex = jsg::JsValue(exception.getHandle(js));\n      KJ_ASSERT(ex.toString(js).contains(\"Abort reason\"),\n          \"Ready promise should be rejected with abort reason\");\n      return js.resolvedPromise();\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Abort aborts underlying sink\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    SimpleEventRecordingSink sink;\n    kj::Own<SimpleEventRecordingSink> fake(&sink, kj::NullDisposer::instance);\n    auto adapter =\n        kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, newWritableSink(kj::mv(fake)));\n    adapter->abort(env.js, env.js.str(\"Abort reason\"_kj));\n    KJ_ASSERT_NONNULL(adapter->isErrored(), \"Underlying sink's abort() should have been called\");\n  });\n}\n\nKJ_TEST(\"Abort rejects in-flight operations\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto neverDoneSink = newWritableSink(kj::heap<NeverReadySink>());\n    auto adapter =\n        kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, kj::mv(neverDoneSink));\n\n    auto writePromise = adapter->write(env.js, env.js.str(\"data\"_kj));\n    auto flushPromise = adapter->flush(env.js);\n    auto endPromise = adapter->end(env.js);\n\n    adapter->abort(env.js, env.js.str(\"Abort reason\"_kj));\n\n    return env.context\n        .awaitJs(env.js,\n            endPromise.then(env.js,\n                [](jsg::Lock& js) {\n      return js.rejectedPromise<void>(js.error(\"End promise should not resolve after abort()\"));\n    },\n                [writePromise = kj::mv(writePromise), flushPromise = kj::mv(flushPromise)](\n                    jsg::Lock& js, jsg::Value exception) mutable {\n      KJ_ASSERT(writePromise.getState(js) == jsg::Promise<void>::State::REJECTED,\n          \"Write promise should be rejected after abort()\");\n      KJ_ASSERT(flushPromise.getState(js) == jsg::Promise<void>::State::REJECTED,\n          \"Flush promise should be rejected after abort()\");\n\n      return js.resolvedPromise();\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"end() waits for all pending writes to complete\") {\n  TestFixture fixture;\n  SimpleEventRecordingSink sink;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<SimpleEventRecordingSink> fake(&sink, kj::NullDisposer::instance);\n    auto adapter =\n        kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, newWritableSink(kj::mv(fake)));\n\n    adapter->write(env.js, env.js.str(\"data1\"_kj));\n    adapter->write(env.js, env.js.str(\"data2\"_kj));\n    adapter->write(env.js, env.js.str(\"data3\"_kj));\n    adapter->write(env.js, env.js.str(\"data4\"_kj));\n    KJ_ASSERT(sink.getState().writeCalled == 1,\n        \"Underlying sink's write() should have been called four times\");\n\n    auto endPromise = adapter->end(env.js);\n\n    return env.context\n        .awaitJs(env.js, endPromise.then(env.js, [&state = sink.getState()](jsg::Lock& js) {\n      KJ_ASSERT(state.writeCalled == 4,\n          \"Underlying sink's write() should have been called four times before end() resolves\");\n      return js.resolvedPromise();\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"end() waits for all pending flushes to complete\") {\n  TestFixture fixture;\n  SimpleEventRecordingSink sink;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<SimpleEventRecordingSink> fake(&sink, kj::NullDisposer::instance);\n    auto adapter =\n        kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, newWritableSink(kj::mv(fake)));\n\n    auto flush1 = adapter->flush(env.js);\n    auto flush2 = adapter->flush(env.js);\n\n    auto endPromise = adapter->end(env.js);\n\n    return env.context\n        .awaitJs(env.js,\n            endPromise.then(env.js,\n                [&state = sink.getState(), flush1 = kj::mv(flush1), flush2 = kj::mv(flush2)](\n                    jsg::Lock& js) mutable {\n      KJ_ASSERT(flush1.getState(js) == jsg::Promise<void>::State::FULFILLED,\n          \"First flush() promise should be fulfilled before end() resolves\");\n      KJ_ASSERT(flush2.getState(js) == jsg::Promise<void>::State::FULFILLED,\n          \"Second flush() promise should be fulfilled before end() resolves\");\n      return js.resolvedPromise();\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"end() with large queue of pending operations\") {\n  TestFixture fixture;\n  SimpleEventRecordingSink sink;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<SimpleEventRecordingSink> fake(&sink, kj::NullDisposer::instance);\n    auto adapter =\n        kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, newWritableSink(kj::mv(fake)));\n\n    for (int i = 0; i < 1024; i++) {\n      adapter->write(env.js, env.js.str(\"data\"_kj));\n      adapter->flush(env.js);\n    }\n\n    auto endPromise = adapter->end(env.js);\n\n    return env.context\n        .awaitJs(env.js, endPromise.then(env.js, [&state = sink.getState()](jsg::Lock& js) {\n      KJ_ASSERT(state.writeCalled == 1024,\n          \"Underlying sink's write() should have been called four times before end() resolves\");\n      return js.resolvedPromise();\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"end() when underlyink sink.end() fails should error adapter\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto throwingSink = newWritableSink(kj::heap<ThrowingSink>());\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, kj::mv(throwingSink));\n\n    auto writePromise = adapter->write(env.js, env.js.str(\"hello\"_kj));\n\n    return env.context\n        .awaitJs(env.js, writePromise.then(env.js, [](jsg::Lock& js) {\n      return js.rejectedPromise<void>(js.error(\"Write promise should not resolve when sink fails\"));\n    }, [&adapter = *adapter](jsg::Lock& js, jsg::Value exception) {\n      auto err = jsg::JsValue(exception.getHandle(js));\n      KJ_ASSERT(err.toString(js).contains(\"internal error\"));\n      KJ_ASSERT(adapter.isErrored() != kj::none, \"Adapter should be in errored state\");\n      return js.resolvedPromise();\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"flush() completes after all prior writes\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto recordingSink = kj::heap<SimpleEventRecordingSink>();\n    auto& state = recordingSink->getState();\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(\n        env.js, env.context, newWritableSink(kj::mv(recordingSink)));\n\n    adapter->write(env.js, env.js.str(\"data1\"_kj));\n    adapter->write(env.js, env.js.str(\"data2\"_kj));\n    KJ_ASSERT(state.writeCalled == 1, \"Underlying sink's write() should have been called twice\");\n\n    auto flushPromise = adapter->flush(env.js);\n\n    return env.context\n        .awaitJs(env.js, flushPromise.then(env.js, [&state](jsg::Lock& js) {\n      KJ_ASSERT(state.writeCalled == 2,\n          \"Underlying sink's write() should have been called twice before flush() resolves\");\n      return js.resolvedPromise();\n    })).attach(kj::mv(state), kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"flush() with no writes completes immediately\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto recordingSink = kj::heap<SimpleEventRecordingSink>();\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(\n        env.js, env.context, newWritableSink(kj::mv(recordingSink)));\n\n    auto flushPromise = adapter->flush(env.js);\n\n    return env.context.awaitJs(env.js, kj::mv(flushPromise)).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"multiple sequential flush() calls\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto recordingSink = kj::heap<SimpleEventRecordingSink>();\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(\n        env.js, env.context, newWritableSink(kj::mv(recordingSink)));\n\n    auto flush1 = adapter->flush(env.js);\n    auto flush2 = adapter->flush(env.js);\n\n    return env.context\n        .awaitJs(env.js, flush1.then(env.js, [flush2 = kj::mv(flush2)](jsg::Lock& js) mutable {\n      return kj::mv(flush2);\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"write() when underlyink sink.write() fails should error adapter\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto throwingSink = newWritableSink(kj::heap<ThrowingSink>());\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, kj::mv(throwingSink));\n\n    auto writePromise = adapter->write(env.js, env.js.str(\"data\"_kj));\n    auto flushPromise = adapter->flush(env.js);\n\n    return env.context\n        .awaitJs(env.js,\n            writePromise.then(env.js,\n                [](jsg::Lock& js) {\n      return js.rejectedPromise<void>(js.error(\"Write promise should not resolve when sink fails\"));\n    },\n                [&adapter = *adapter, flushPromise = kj::mv(flushPromise)](\n                    jsg::Lock& js, jsg::Value exception) mutable {\n      auto err = jsg::JsValue(exception.getHandle(js));\n      KJ_ASSERT(err.toString(js).contains(\"internal error\"));\n      KJ_ASSERT(adapter.isErrored() != kj::none, \"Adapter should be in errored state\");\n      return flushPromise.then(js, [](jsg::Lock& js) {\n        return js.rejectedPromise<void>(\n            js.error(\"Flush promise should not resolve when sink fails\"));\n      }, [](jsg::Lock& js, jsg::Value exception) { return js.resolvedPromise(); });\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"multiple writes() should only error adapter once\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto throwingSink = newWritableSink(kj::heap<ThrowingSink>());\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, kj::mv(throwingSink));\n\n    auto write1 = adapter->write(env.js, env.js.str(\"data\"_kj));\n    auto write2 = adapter->write(env.js, env.js.str(\"data\"_kj));\n\n    return env.context\n        .awaitJs(env.js, write1.then(env.js, [](jsg::Lock& js) {\n      return js.rejectedPromise<void>(js.error(\"Write promise should not resolve when sink fails\"));\n    }, [&adapter = *adapter, write2 = kj::mv(write2)](jsg::Lock& js, jsg::Value exception) mutable {\n      auto err = jsg::JsValue(exception.getHandle(js));\n      KJ_ASSERT(err.toString(js).contains(\"internal error\"));\n      KJ_ASSERT(adapter.isErrored() != kj::none, \"Adapter should be in errored state\");\n      return write2.then(js, [](jsg::Lock& js) {\n        return js.rejectedPromise<void>(\n            js.error(\"Write promise should not resolve when sink fails\"));\n      }, [](jsg::Lock& js, jsg::Value exception) { return js.resolvedPromise(); });\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"zero-length writes are a non-op (string)\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto recordingSink = kj::heap<SimpleEventRecordingSink>();\n    auto& state = recordingSink->getState();\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(\n        env.js, env.context, newWritableSink(kj::mv(recordingSink)));\n\n    auto writePromise = adapter->write(env.js, env.js.str(\"\"_kj));\n    KJ_ASSERT(state.writeCalled == 0, \"Underlying sink's write() should not have been called\");\n\n    return env.context\n        .awaitJs(env.js, writePromise.then(env.js, [&state](jsg::Lock& js) {\n      KJ_ASSERT(state.writeCalled == 0, \"Underlying sink's write() should not have been called\");\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"zero-length writes are a non-op (ArrayBuffer)\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto recordingSink = kj::heap<SimpleEventRecordingSink>();\n    auto& state = recordingSink->getState();\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(\n        env.js, env.context, newWritableSink(kj::mv(recordingSink)));\n\n    auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(env.js, 0);\n    jsg::BufferSource source(env.js, kj::mv(backing));\n    jsg::JsValue handle(source.getHandle(env.js));\n\n    auto writePromise = adapter->write(env.js, handle);\n    KJ_ASSERT(state.writeCalled == 0, \"Underlying sink's write() should not have been called\");\n\n    return env.context\n        .awaitJs(env.js, writePromise.then(env.js, [&state](jsg::Lock& js) {\n      KJ_ASSERT(state.writeCalled == 0, \"Underlying sink's write() should not have been called\");\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"writing small ArrayBuffer\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto recordingSink = kj::heap<SimpleEventRecordingSink>();\n    auto& state = recordingSink->getState();\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context,\n        newWritableSink(kj::mv(recordingSink)),\n        WritableStreamSinkJsAdapter::Options{\n          .highWaterMark = 10,\n        });\n\n    auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(env.js, 10);\n    jsg::BufferSource source(env.js, kj::mv(backing));\n    jsg::JsValue handle(source.getHandle(env.js));\n\n    auto writePromise = adapter->write(env.js, handle);\n    KJ_ASSERT(state.writeCalled == 1, \"Underlying sink's write() should not have been called\");\n    KJ_ASSERT(KJ_ASSERT_NONNULL(adapter->getDesiredSize()) == 0,\n        \"Adapter's desired size should be 0 after writing highWaterMark bytes\");\n\n    return env.context\n        .awaitJs(env.js, writePromise.then(env.js, [&state, &adapter = *adapter](jsg::Lock& js) {\n      KJ_ASSERT(state.writeCalled == 1, \"Underlying sink's write() should not have been called\");\n      KJ_ASSERT(KJ_ASSERT_NONNULL(adapter.getDesiredSize()) == 10,\n          \"Back to initial desired size after write completes\");\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"writing medium ArrayBuffer\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto recordingSink = kj::heap<SimpleEventRecordingSink>();\n    auto& state = recordingSink->getState();\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context,\n        newWritableSink(kj::mv(recordingSink)),\n        WritableStreamSinkJsAdapter::Options{\n          .highWaterMark = 5 * 1024,\n        });\n\n    auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(env.js, 4 * 1024);\n    jsg::BufferSource source(env.js, kj::mv(backing));\n    jsg::JsValue handle(source.getHandle(env.js));\n\n    auto writePromise = adapter->write(env.js, handle);\n    KJ_ASSERT(state.writeCalled == 1, \"Underlying sink's write() should not have been called\");\n    KJ_ASSERT(KJ_ASSERT_NONNULL(adapter->getDesiredSize()) == 1024,\n        \"Adapter's desired size should be 1024 after writing 4 * 1024 bytes\");\n\n    return env.context\n        .awaitJs(env.js, writePromise.then(env.js, [&state, &adapter = *adapter](jsg::Lock& js) {\n      KJ_ASSERT(state.writeCalled == 1, \"Underlying sink's write() should not have been called\");\n      KJ_ASSERT(KJ_ASSERT_NONNULL(adapter.getDesiredSize()) == 5 * 1024,\n          \"Back to initial desired size after write completes\");\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"writing large ArrayBuffer\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto recordingSink = kj::heap<SimpleEventRecordingSink>();\n    auto& state = recordingSink->getState();\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context,\n        newWritableSink(kj::mv(recordingSink)),\n        WritableStreamSinkJsAdapter::Options{\n          .highWaterMark = 8 * 1024,\n        });\n\n    auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(env.js, 16 * 1024);\n    jsg::BufferSource source(env.js, kj::mv(backing));\n    jsg::JsValue handle(source.getHandle(env.js));\n\n    auto writePromise = adapter->write(env.js, handle);\n    KJ_ASSERT(state.writeCalled == 1, \"Underlying sink's write() should not have been called\");\n    KJ_ASSERT(KJ_ASSERT_NONNULL(adapter->getDesiredSize()) == -(8 * 1024),\n        \"Adapter's desired size should be negative after writing 16 * 1024 bytes\");\n\n    return env.context\n        .awaitJs(env.js, writePromise.then(env.js, [&state, &adapter = *adapter](jsg::Lock& js) {\n      KJ_ASSERT(state.writeCalled == 1, \"Underlying sink's write() should not have been called\");\n      KJ_ASSERT(KJ_ASSERT_NONNULL(adapter.getDesiredSize()) == 8 * 1024,\n          \"Back to initial desired size after write completes\");\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"writing the wrong types reject\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto recordingSink = kj::heap<SimpleEventRecordingSink>();\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(\n        env.js, env.context, newWritableSink(kj::mv(recordingSink)));\n\n    auto writeNull = adapter->write(env.js, env.js.null());\n    KJ_ASSERT(writeNull.getState(env.js) == jsg::Promise<void>::State::REJECTED,\n        \"Write of null should be rejected\");\n\n    auto writeUndefined = adapter->write(env.js, env.js.undefined());\n    KJ_ASSERT(writeUndefined.getState(env.js) == jsg::Promise<void>::State::REJECTED,\n        \"Write of undefined should be rejected\");\n\n    auto writeNumber = adapter->write(env.js, env.js.num(42));\n    KJ_ASSERT(writeNumber.getState(env.js) == jsg::Promise<void>::State::REJECTED,\n        \"Write of number should be rejected\");\n\n    auto writeBoolean = adapter->write(env.js, env.js.boolean(true));\n    KJ_ASSERT(writeBoolean.getState(env.js) == jsg::Promise<void>::State::REJECTED,\n        \"Write of boolean should be rejected\");\n\n    auto writeObject = adapter->write(env.js, env.js.obj());\n    KJ_ASSERT(writeObject.getState(env.js) == jsg::Promise<void>::State::REJECTED,\n        \"Write of plain object should be rejected\");\n  });\n}\n\nKJ_TEST(\"large number of large writes\") {\n  TestFixture fixture;\n  SimpleEventRecordingSink sink;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    kj::Own<SimpleEventRecordingSink> fake(&sink, kj::NullDisposer::instance);\n    auto adapter =\n        kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, newWritableSink(kj::mv(fake)));\n\n    for (int i = 0; i < 1000; i++) {\n      auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(env.js, 16 * 1024);\n      jsg::BufferSource source(env.js, kj::mv(backing));\n      jsg::JsValue handle(source.getHandle(env.js));\n\n      adapter->write(env.js, handle);\n    }\n    auto endPromise = adapter->end(env.js);\n\n    return env.context\n        .awaitJs(env.js,\n            endPromise.then(env.js, [&state = sink.getState(), &adapter = *adapter](jsg::Lock& js) {\n      KJ_ASSERT(state.writeCalled == 1000, \"Underlying sink's write() should have been called\");\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"ready promise signals backpressure correctly\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto recordingSink = kj::heap<SimpleEventRecordingSink>();\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context,\n        newWritableSink(kj::mv(recordingSink)),\n        WritableStreamSinkJsAdapter::Options{\n          .highWaterMark = 10,\n        });\n\n    auto readyPromise = adapter->getReady(env.js);\n    KJ_ASSERT(readyPromise.getState(env.js) == jsg::Promise<void>::State::FULFILLED,\n        \"Ready promise should be fulfilled when no backpressure\");\n\n    auto writePromise = adapter->write(env.js, env.js.str(\"12345678909876543210\"_kj));\n\n    readyPromise = adapter->getReady(env.js);\n    KJ_ASSERT(readyPromise.getState(env.js) == jsg::Promise<void>::State::PENDING,\n        \"Ready promise should be fulfilled when no backpressure\");\n\n    return env.context\n        .awaitJs(env.js, writePromise.then(env.js, [&adapter = *adapter](jsg::Lock& js) {\n      auto readyPromise = adapter.getReady(js);\n      KJ_ASSERT(readyPromise.getState(js) == jsg::Promise<void>::State::FULFILLED,\n          \"Ready promise should be fulfilled when no backpressure\");\n    })).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"detachOnWrite option detaches ArrayBuffer before write\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto recordingSink = kj::heap<SimpleEventRecordingSink>();\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context,\n        newWritableSink(kj::mv(recordingSink)),\n        WritableStreamSinkJsAdapter::Options{\n          .detachOnWrite = true,\n        });\n\n    auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(env.js, 10);\n    jsg::BufferSource source(env.js, kj::mv(backing));\n    KJ_ASSERT(!source.isDetached());\n    jsg::JsValue handle(source.getHandle(env.js));\n\n    auto writePromise = adapter->write(env.js, handle);\n\n    jsg::BufferSource source2(env.js, handle);\n    KJ_ASSERT(source2.size() == 0);\n\n    return env.context.awaitJs(env.js, kj::mv(writePromise)).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"detachOnWrite option detaches Uint8Array before write\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto recordingSink = kj::heap<SimpleEventRecordingSink>();\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context,\n        newWritableSink(kj::mv(recordingSink)),\n        WritableStreamSinkJsAdapter::Options{\n          .detachOnWrite = true,\n        });\n\n    auto backing = jsg::BackingStore::alloc<v8::Uint8Array>(env.js, 10);\n    jsg::BufferSource source(env.js, kj::mv(backing));\n    KJ_ASSERT(!source.isDetached());\n    jsg::JsValue handle(source.getHandle(env.js));\n\n    auto writePromise = adapter->write(env.js, handle);\n\n    jsg::BufferSource source2(env.js, handle);\n    KJ_ASSERT(source2.size() == 0);\n\n    return env.context.awaitJs(env.js, kj::mv(writePromise)).attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"Creating adapter and dropping it with pending operations\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto sink =\n        newIoContextWrappedWritableSink(env.context, newWritableSink(newNullOutputStream()));\n    auto adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, kj::mv(sink));\n\n    adapter->write(env.js, env.js.str(\"data\"_kj));\n    adapter->flush(env.js);\n    adapter->end(env.js);\n\n    // Dropping the adapter here should not crash or leak memory.\n  });\n}\n\nKJ_TEST(\"Dropping the IoContext with pending operations and using the adapter in another context\") {\n  TestFixture fixture;\n  kj::Maybe<kj::Own<WritableStreamSinkJsAdapter>> adapter;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto sink =\n        newIoContextWrappedWritableSink(env.context, newWritableSink(newNullOutputStream()));\n    adapter = kj::heap<WritableStreamSinkJsAdapter>(env.js, env.context, kj::mv(sink));\n    auto& adapterRef = *KJ_ASSERT_NONNULL(adapter);\n\n    adapterRef.write(env.js, env.js.str(\"data\"_kj));\n    adapterRef.flush(env.js);\n    adapterRef.end(env.js);\n\n    // Dropping the IoContext here should not crash or leak memory.\n  });\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& otherContext = KJ_ASSERT_NONNULL(adapter);\n    try {\n      otherContext->write(env.js, env.js.str(\"data2\"_kj));\n    } catch (...) {\n      auto ex = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(ex.getDescription().startsWith(\n          \"jsg.Error: Cannot perform I/O on behalf of a different request.\"));\n    }\n  });\n}\n\n// ================================================================================================\n\nnamespace {\nstruct WritableStreamContext {\n  kj::Vector<kj::Array<const kj::byte>> chunks;\n  bool closed = false;\n  kj::Maybe<jsg::JsRef<jsg::JsValue>> maybeAbort;\n};\n\njsg::Ref<WritableStream> createSimpleWritableStream(jsg::Lock& js, WritableStreamContext& context) {\n  return WritableStream::constructor(js,\n      UnderlyingSink{\n        .write =\n            [&context](jsg::Lock& js, auto chunk, auto) {\n    jsg::BufferSource source(js, chunk);\n    auto data = kj::heapArray<kj::byte>(source.asArrayPtr());\n    context.chunks.add(kj::mv(data));\n    return js.resolvedPromise();\n  },\n        .abort =\n            [&context](jsg::Lock& js, auto reason) {\n    context.maybeAbort = jsg::JsRef<jsg::JsValue>(js, jsg::JsValue(reason));\n    return js.resolvedPromise();\n  },\n        .close =\n            [&context](jsg::Lock& js) {\n    context.closed = true;\n    return js.resolvedPromise();\n  },\n      },\n      StreamQueuingStrategy{});\n}\n\njsg::Ref<WritableStream> createErroredStream(jsg::Lock& js) {\n  return WritableStream::constructor(js,\n      UnderlyingSink{\n        .write = [](jsg::Lock& js, auto chunk,\n                     auto) { return js.rejectedPromise<void>(js.error(\"Write error\")); },\n        .abort = [](jsg::Lock& js, auto reason) { return js.resolvedPromise(); },\n        .close = [](jsg::Lock& js) { return js.resolvedPromise(); },\n      },\n      StreamQueuingStrategy{});\n}\n\nstruct FiniteReadableStreamSource final: public ReadableStreamSource {\n  int counter = 0;\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    if (++counter < 5) {\n      kj::ArrayPtr<kj::byte> buf(static_cast<kj::byte*>(buffer), maxBytes);\n      buf.fill('a');\n      return maxBytes;\n    }\n    static constexpr size_t eof = 0;\n    return eof;\n  }\n};\n\nstruct ErroringStreamSource final: public ReadableStreamSource {\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    return KJ_EXCEPTION(FAILED, \"worker_do_not_log: Read error\");\n  }\n};\n}  // namespace\n\nKJ_TEST(\"WritableStreamSinkKjAdapter construction\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  WritableStreamContext context;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createSimpleWritableStream(env.js, context);\n    auto adapter = kj::heap<WritableStreamSinkKjAdapter>(env.js, env.context, kj::mv(stream));\n  });\n}\n\nKJ_TEST(\"WritableStreamSinkKjAdapter construction with locked stream\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  WritableStreamContext context;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createSimpleWritableStream(env.js, context);\n    auto writer = stream->getWriter(env.js);\n\n    try {\n      auto adapter = kj::heap<WritableStreamSinkKjAdapter>(env.js, env.context, kj::mv(stream));\n      KJ_FAIL_ASSERT(\"Construction with locked stream should have thrown\");\n    } catch (...) {\n      auto ex = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(ex.getDescription().contains(\"WritableStream is locked\"));\n    }\n  });\n}\n\nKJ_TEST(\"WritableStreamSinkKjAdapter construction with closed stream\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  WritableStreamContext context;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createSimpleWritableStream(env.js, context);\n    stream->close(env.js);\n\n    auto adapter = kj::heap<WritableStreamSinkKjAdapter>(env.js, env.context, kj::mv(stream));\n  });\n}\n\nKJ_TEST(\"WritableStreamSinkKjAdapter construction with errored stream\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  WritableStreamContext context;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createSimpleWritableStream(env.js, context);\n    stream->abort(env.js, env.js.str(\"Abort reason\"_kj));\n\n    auto adapter = kj::heap<WritableStreamSinkKjAdapter>(env.js, env.context, kj::mv(stream));\n  });\n}\n\nKJ_TEST(\"WritableStreamSinkKjAdapter construction with immediate end\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  WritableStreamContext context;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createSimpleWritableStream(env.js, context);\n    auto adapter = kj::heap<WritableStreamSinkKjAdapter>(env.js, env.context, kj::mv(stream));\n    return adapter->end().attach(kj::mv(adapter));\n  });\n}\n\nKJ_TEST(\"WritableStreamSinkKjAdapter construction with immediate abort\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  WritableStreamContext context;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createSimpleWritableStream(env.js, context);\n    auto adapter = kj::heap<WritableStreamSinkKjAdapter>(env.js, env.context, kj::mv(stream));\n    adapter->abort(KJ_EXCEPTION(DISCONNECTED, \"Abort reason\"));\n  });\n}\n\nKJ_TEST(\"WritableStreamSinkKjAdapter single write\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  WritableStreamContext context;\n  kj::FixedArray<kj::byte, 1024> buffer;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createSimpleWritableStream(env.js, context);\n    auto adapter = kj::heap<WritableStreamSinkKjAdapter>(env.js, env.context, kj::mv(stream));\n\n    buffer.fill('a');\n    return adapter->write(buffer.asPtr()).then([&adapter = *adapter]() {\n      return adapter.end();\n    }).attach(kj::mv(adapter));\n  });\n\n  KJ_ASSERT(context.chunks.size() == 1, \"Underlying stream should have received one chunk\");\n  KJ_ASSERT(context.chunks[0].size() == 1024, \"Underlying stream chunk should be 1024 bytes\");\n  KJ_ASSERT(context.chunks[0] == buffer, \"Underlying stream chunk should match written data\");\n  KJ_ASSERT(context.closed, \"Underlying stream should be closed\");\n  KJ_ASSERT(context.maybeAbort == kj::none, \"Underlying stream should not be aborted\");\n}\n\nKJ_TEST(\"WritableStreamSinkKjAdapter zero-length write\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  WritableStreamContext context;\n  kj::ArrayPtr<kj::byte> buffer = nullptr;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createSimpleWritableStream(env.js, context);\n    auto adapter = kj::heap<WritableStreamSinkKjAdapter>(env.js, env.context, kj::mv(stream));\n\n    return adapter->write(buffer).then([&adapter = *adapter]() {\n      return adapter.end();\n    }).attach(kj::mv(adapter));\n  });\n\n  KJ_ASSERT(context.chunks.size() == 0, \"Underlying stream should not have chunks\");\n  KJ_ASSERT(context.closed, \"Underlying stream should be closed\");\n  KJ_ASSERT(context.maybeAbort == kj::none, \"Underlying stream should not be aborted\");\n}\n\nKJ_TEST(\"WritableStreamSinkKjAdapter concurrent writes forbidden\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  WritableStreamContext context;\n  kj::FixedArray<kj::byte, 100> buffer;\n\n  try {\n    fixture.runInIoContext([&](const TestFixture::Environment& env) {\n      auto stream = createSimpleWritableStream(env.js, context);\n      auto adapter = kj::heap<WritableStreamSinkKjAdapter>(env.js, env.context, kj::mv(stream));\n\n      buffer.asPtr().fill('a');\n\n      auto p1 = adapter->write(buffer.asPtr());\n      // The second one should fail.\n\n      return adapter->write(buffer.asPtr()).attach(kj::mv(adapter));\n    });\n  } catch (...) {\n    auto ex = kj::getCaughtExceptionAsKj();\n    KJ_ASSERT(ex.getDescription().contains(\"Cannot have multiple concurrent writes\"));\n  }\n}\n\nKJ_TEST(\"WritableStreamSinkKjAdapter write after close\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  WritableStreamContext context;\n  kj::FixedArray<kj::byte, 100> buffer;\n\n  try {\n    fixture.runInIoContext([&](const TestFixture::Environment& env) {\n      auto stream = createSimpleWritableStream(env.js, context);\n      auto adapter = kj::heap<WritableStreamSinkKjAdapter>(env.js, env.context, kj::mv(stream));\n\n      buffer.asPtr().fill('a');\n\n      return adapter->end()\n          .then([&adapter = *adapter, &buffer]() {\n        return adapter.write(buffer.asPtr());\n      }).attach(kj::mv(adapter));\n    });\n  } catch (...) {\n    auto ex = kj::getCaughtExceptionAsKj();\n    KJ_ASSERT(ex.getDescription().contains(\"Cannot write after close\"));\n  }\n}\n\nKJ_TEST(\"WritableStreamSinkKjAdapter single errored\") {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n  kj::FixedArray<kj::byte, 1024> buffer;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto stream = createErroredStream(env.js);\n    auto adapter = kj::heap<WritableStreamSinkKjAdapter>(env.js, env.context, kj::mv(stream));\n\n    buffer.fill('a');\n    return adapter->write(buffer.asPtr())\n        .then([&adapter = *adapter]() { KJ_FAIL_ASSERT(\"Write should have failed\"); },\n            [](kj::Exception exception) {\n      KJ_ASSERT(exception.getDescription().contains(\"Write error\"),\n          \"Write should have failed with underlying stream error\");\n    }).attach(kj::mv(adapter));\n  });\n}\n}  // namespace workerd::api::streams\n"
  },
  {
    "path": "src/workerd/api/streams/writable-sink-adapter.c++",
    "content": "#include \"writable-sink-adapter.h\"\n\n#include \"writable.h\"\n\n#include <workerd/api/system-streams.h>\n#include <workerd/util/checked-queue.h>\n\nnamespace workerd::api::streams {\n\n// The Active state maintains a queue of tasks, such as write or flush operations. Each task\n// contains a promise-returning function object and a fulfiller. When the first task is\n// enqueued, the active state begins processing the queue asynchronously. Each function\n// is invoked in order, its promise awaited, and the result passed to the fulfiller. The\n// fulfiller notifies the code which enqueued the task that the task has completed. In\n// this way, read and close operations are safely executed in serial, even if one operation\n// is called before the previous completes. This mechanism satisfies KJ's restriction on\n// concurrent operations on streams.\nstruct WritableStreamSinkJsAdapter::Active final {\n  struct Task {\n    kj::Function<kj::Promise<void>()> task;\n    kj::Own<kj::PromiseFulfiller<void>> fulfiller;\n    kj::Maybe<kj::Promise<void>> maybeOutputLock;\n\n    Task(kj::Function<kj::Promise<void>()> task,\n        kj::Own<kj::PromiseFulfiller<void>> fulfiller,\n        kj::Maybe<kj::Promise<void>> maybeOutputLock = kj::none)\n        : task(kj::mv(task)),\n          fulfiller(kj::mv(fulfiller)),\n          maybeOutputLock(kj::mv(maybeOutputLock)) {}\n    KJ_DISALLOW_COPY_AND_MOVE(Task);\n  };\n  using TaskQueue = workerd::util::Queue<kj::Own<Task>>;\n\n  kj::Own<WritableSink> sink;\n  const Options options;\n  kj::Canceler canceler;\n  TaskQueue queue;\n  bool aborted = false;\n  bool running = false;\n  bool closePending = false;\n  size_t bytesInFlight = 0;\n  kj::Maybe<kj::Exception> pendingAbort;\n\n  Active(kj::Own<WritableSink> sink, Options options)\n      : sink(kj::mv(sink)),\n        options(kj::mv(options)) {\n    KJ_DASSERT(this->sink.get() != nullptr, \"WritableStreamSink cannot be null\");\n  }\n\n  KJ_DISALLOW_COPY_AND_MOVE(Active);\n  ~Active() noexcept(false) {\n    // When the Active is dropped, we cancel any remaining pending writes and\n    // abort the sink.\n    abort(KJ_EXCEPTION(FAILED, \"jsg.Error: Writable stream is canceled or closed.\"));\n\n    // Check invariants for safety.\n    // 1. Our canceler should be empty because we canceled it.\n    KJ_DASSERT(canceler.isEmpty());\n    // 2. The write queue should be empty.\n    KJ_DASSERT(queue.empty());\n  }\n\n  // Explicitly cancel all in-flight and pending tasks in the queue.\n  // This is a non-op if cancel has already been called.\n  void abort(kj::Exception&& exception) {\n    if (aborted) return;\n    aborted = true;\n    // 1. Cancel our in-flight \"runLoop\", if any.\n    pendingAbort = kj::cp(exception);\n    canceler.cancel(kj::cp(exception));\n    // 2. Drop our queue of pending tasks.\n    queue.drainTo(\n        [&exception](kj::Own<Task>&& task) { task->fulfiller->reject(kj::cp(exception)); });\n    // 3. Abort and drop the sink itself. We're done with it.\n    sink->abort(kj::mv(exception));\n    auto dropped KJ_UNUSED = kj::mv(sink);\n  }\n\n  // Get the desired size based on the configured high water mark and\n  // the number of bytes currently in flight.\n  ssize_t getDesiredSize() const {\n    return options.highWaterMark - bytesInFlight;\n  }\n\n  kj::Promise<void> enqueue(kj::Function<kj::Promise<void>()> task) {\n    KJ_DASSERT(!aborted, \"cannot enqueue tasks on an aborted queue\");\n    auto paf = kj::newPromiseAndFulfiller<void>();\n    auto& ioContext = IoContext::current();\n    queue.push(kj::heap<Task>(\n        kj::mv(task), kj::mv(paf.fulfiller), ioContext.waitForOutputLocksIfNecessary()));\n    if (!running) {\n      ioContext.addTask(canceler.wrap(run()));\n    }\n    return kj::mv(paf.promise);\n  }\n\n  kj::Promise<void> run() {\n    KJ_DEFER(running = false);\n    running = true;\n    while (!queue.empty() && !aborted) {\n      auto task = KJ_ASSERT_NONNULL(queue.pop());\n      KJ_DEFER({\n        if (task->fulfiller->isWaiting()) {\n          KJ_IF_SOME(pending, pendingAbort) {\n            task->fulfiller->reject(kj::mv(pending));\n          } else {\n            task->fulfiller->reject(KJ_EXCEPTION(DISCONNECTED, \"Task was canceled.\"));\n          }\n        }\n      });\n      bool taskFailed = false;\n      try {\n        KJ_IF_SOME(lock, task->maybeOutputLock) {\n          co_await lock;\n        }\n        co_await task->task();\n        task->fulfiller->fulfill();\n      } catch (...) {\n        auto ex = kj::getCaughtExceptionAsKj();\n        task->fulfiller->reject(kj::mv(ex));\n        taskFailed = true;\n      }\n      // If the task failed, we exit the loop. We're going to abort the\n      // entire remaining queue anyway so there's no point in continuing.\n      if (taskFailed) co_return;\n    }\n  }\n};\n\nWritableStreamSinkJsAdapter::WritableStreamSinkJsAdapter(\n    jsg::Lock& js, IoContext& ioContext, kj::Own<WritableSink> sink, kj::Maybe<Options> options)\n    : state(State::create<Open>(\n          ioContext.addObject(kj::heap<Active>(kj::mv(sink), kj::mv(options).orDefault({}))))),\n      backpressureState(newBackpressureState(js)),\n      selfRef(kj::rc<WeakRef<WritableStreamSinkJsAdapter>>(\n          kj::Badge<WritableStreamSinkJsAdapter>{}, *this)) {\n  // We want the initial backpressure state to be \"ready\".\n  backpressureState.release(js);\n}\n\nWritableStreamSinkJsAdapter::WritableStreamSinkJsAdapter(jsg::Lock& js,\n    IoContext& ioContext,\n    kj::Own<kj::AsyncOutputStream> stream,\n    StreamEncoding encoding,\n    kj::Maybe<Options> options)\n    : WritableStreamSinkJsAdapter(js,\n          ioContext,\n          newIoContextWrappedWritableSink(\n              ioContext, newEncodedWritableSink(encoding, kj::mv(stream))),\n          kj::mv(options)) {}\n\nWritableStreamSinkJsAdapter::~WritableStreamSinkJsAdapter() noexcept(false) {\n  selfRef->invalidate();\n}\n\nkj::Maybe<const kj::Exception&> WritableStreamSinkJsAdapter::isErrored() {\n  return state.tryGetErrorUnsafe();\n}\n\nbool WritableStreamSinkJsAdapter::isClosed() {\n  return state.is<Closed>();\n}\n\nbool WritableStreamSinkJsAdapter::isClosing() {\n  return state.whenActiveOr([](Open& open) { return open.active->closePending; }, false);\n}\n\nkj::Maybe<ssize_t> WritableStreamSinkJsAdapter::getDesiredSize() {\n  KJ_IF_SOME(open, state.tryGetActiveUnsafe()) {\n    return open.active->getDesiredSize();\n  }\n  return kj::none;\n}\n\njsg::Promise<void> WritableStreamSinkJsAdapter::write(jsg::Lock& js, const jsg::JsValue& value) {\n  KJ_IF_SOME(exc, state.tryGetErrorUnsafe()) {\n    // Really should not have been called if errored but just in case,\n    // return a rejected promise.\n    return js.rejectedPromise<void>(js.exceptionToJs(kj::cp(exc)));\n  }\n\n  if (state.is<Closed>()) {\n    // Really should not have been called if closed but just in case,\n    // return a rejected promise.\n    return js.rejectedPromise<void>(js.typeError(\"Write after close is not allowed\"));\n  }\n\n  auto& open = state.requireActiveUnsafe();\n  // Dereference the IoOwn once to get the active state.\n  auto& active = *open.active;\n\n  // If close is pending, we cannot accept any more writes.\n  if (active.closePending) {\n    auto exc = js.typeError(\"Write after close is not allowed\");\n    return js.rejectedPromise<void>(exc);\n  }\n\n  // Ok, we are in a writable state, there are no pending closes.\n  // Let's process our data and write it!\n  auto& ioContext = IoContext::current();\n\n  // We know that a WritableStreamSink only accepts bytes, so we need to\n  // verify that the value is a source of bytes. We accept three possible\n  // types: ArrayBuffer, ArrayBufferView, and String. If it is a string,\n  // we convert it to UTF-8 bytes. Anything else is an error.\n  if (value.isArrayBufferView() || value.isArrayBuffer() || value.isSharedArrayBuffer()) {\n    // We can just wrap the value with a jsg::BufferSource and write it.\n    jsg::BufferSource source(js, value);\n    if (active.options.detachOnWrite && source.canDetach(js)) {\n      // Detach from the original ArrayBuffer...\n      // ... and re-wrap it with a new BufferSource that we own.\n      source = jsg::BufferSource(js, source.detach(js));\n    }\n\n    // Zero-length writes are a no-op.\n    if (source.size() == 0) {\n      return js.resolvedPromise();\n    }\n\n    active.bytesInFlight += source.size();\n    maybeSignalBackpressure(js);\n    // Enqueue the actual write operation into the write queue. We pass in\n    // two lambdas, one that does the actual write, and one that handles\n    // errors. If the write fails, we need to transition the adapter to the\n    // errored state. If the write succeeds, we need to decrement the\n    // bytesInFlight counter.\n    //\n    // The promise returned by enqueue is not the actual write promise but\n    // a branch forked off of it. We wrap that with a JS promise that waits\n    // for it to complete. Once it does, we check if we can release backpressure.\n    // This has to be done within an Isolate lock because we need to be able\n    // to resolve or reject the JS promises. If the write fails, we instead\n    // abort the backpressure state.\n    //\n    // This slight indirection does mean that the backpressure state change\n    // may be slightly delayed after the actual write completes but that's\n    // ok.\n    //\n    // Capturing active by reference here is safe because the lambda is\n    // held by the write queue, which is itself held by Active. If active\n    // is destroyed, the write queue is destroyed along with the lambda.\n    auto promise =\n        active.enqueue(kj::coCapture([&active, source = kj::mv(source)]() -> kj::Promise<void> {\n      co_await active.sink->write(source.asArrayPtr());\n      active.bytesInFlight -= source.size();\n    }));\n    return ioContext\n        .awaitIo(js, kj::mv(promise), [self = selfRef.addRef()](jsg::Lock& js) {\n      // Why do we need a weak ref here? Well, because this is a JavaScript\n      // promise continuation. It is possible that the kj::Own holding our\n      // adapter can be dropped while we are waiting for the continuation\n      // to run. If that happens, we don't want to delay cleanup of the\n      // adapter just because of backpressure state management that would\n      // not be needed anymore, so we use a weak ref to update the backpressure\n      // state only if we are still alive.\n      self->runIfAlive(\n          [&](WritableStreamSinkJsAdapter& self) { self.maybeReleaseBackpressure(js); });\n    }).catch_(js, [self = selfRef.addRef()](jsg::Lock& js, jsg::Value exception) {\n      auto error = jsg::JsValue(exception.getHandle(js));\n      self->runIfAlive([&](WritableStreamSinkJsAdapter& self) {\n        self.abort(js, error);\n        self.backpressureState.abort(js, error);\n      });\n      js.throwException(kj::mv(exception));\n    });\n  } else if (value.isString()) {\n    // Also super easy! Let's just convert the string to UTF-8\n    auto str = value.toString(js);\n\n    // Zero-length writes are a no-op.\n    if (str.size() == 0) {\n      return js.resolvedPromise();\n    }\n\n    active.bytesInFlight += str.size();\n    // Make sure to account for the memory used by the string while the\n    // write is in-flight/pending\n    auto accounting = js.getExternalMemoryAdjustment(str.size());\n    maybeSignalBackpressure(js);\n    // Just like above, enqueue the write operation into the write queue,\n    // ensuring that we handle both the success and failure cases.\n    auto promise = active.enqueue(kj::coCapture(\n        [&active, str = kj::mv(str), accounting = kj::mv(accounting)]() -> kj::Promise<void> {\n      co_await active.sink->write(str.asBytes());\n      active.bytesInFlight -= str.size();\n    }));\n    return ioContext\n        .awaitIo(js, kj::mv(promise), [self = selfRef.addRef()](jsg::Lock& js) {\n      self->runIfAlive(\n          [&](WritableStreamSinkJsAdapter& self) { self.maybeReleaseBackpressure(js); });\n    }).catch_(js, [self = selfRef.addRef()](jsg::Lock& js, jsg::Value exception) {\n      auto error = jsg::JsValue(exception.getHandle(js));\n      self->runIfAlive([&](WritableStreamSinkJsAdapter& self) {\n        self.abort(js, error);\n        self.backpressureState.abort(js, error);\n      });\n      js.throwException(kj::mv(exception));\n    });\n  }\n\n  auto err = js.typeError(\"This WritableStream only supports writing byte types.\"_kj);\n  return js.rejectedPromise<void>(err);\n}\n\njsg::Promise<void> WritableStreamSinkJsAdapter::flush(jsg::Lock& js) {\n  KJ_IF_SOME(exc, state.tryGetErrorUnsafe()) {\n    // Really should not have been called if errored but just in case,\n    // return a rejected promise.\n    return js.rejectedPromise<void>(js.exceptionToJs(kj::cp(exc)));\n  }\n\n  if (state.is<Closed>()) {\n    // Really should not have been called if closed but just in case,\n    // return a rejected promise.\n    return js.rejectedPromise<void>(js.typeError(\"Flush after close is not allowed\"));\n  }\n\n  auto& open = state.requireActiveUnsafe();\n  // Dereference the IoOwn once to get the active state.\n  auto& active = *open.active;\n\n  // If close is pending, we cannot accept any more writes.\n  if (active.closePending) {\n    auto exc = js.typeError(\"Flush after close is not allowed\");\n    return js.rejectedPromise<void>(exc);\n  }\n\n  // Ok, we are in a writable state, there are no pending closes.\n  // Let's enqueue our flush signal.\n  auto& ioContext = IoContext::current();\n  // Flushing is really just a non-op write. We enqueue a no-op task\n  // into the write queue and wait for it to complete.\n  auto promise = active.enqueue([]() -> kj::Promise<void> {\n    // Non-op.\n    return kj::READY_NOW;\n  });\n  return ioContext.awaitIo(js, kj::mv(promise));\n}\n\n// Transitions the adapter into the closing state. Once the write queue\n// is empty, we will close the sink and transition to the closed state.\njsg::Promise<void> WritableStreamSinkJsAdapter::end(jsg::Lock& js) {\n  KJ_IF_SOME(exc, state.tryGetErrorUnsafe()) {\n    // Really should not have been called if errored but just in case,\n    // return a rejected promise.\n    return js.rejectedPromise<void>(js.exceptionToJs(kj::cp(exc)));\n  }\n\n  if (state.is<Closed>()) {\n    // We are already in a closed state. This is a no-op. This really\n    // should not have been called if closed but just in case, return\n    // a resolved promise.\n    return js.resolvedPromise();\n  }\n\n  auto& open = state.requireActiveUnsafe();\n  auto& ioContext = IoContext::current();\n  auto& active = *open.active;\n\n  if (active.closePending) {\n    return js.rejectedPromise<void>(js.typeError(\"Close already pending, cannot close again.\"));\n  }\n\n  active.closePending = true;\n  auto promise = active.enqueue(\n      kj::coCapture([&active]() -> kj::Promise<void> { co_await active.sink->end(); }));\n\n  return ioContext\n      .awaitIo(js, kj::mv(promise), [self = selfRef.addRef()](jsg::Lock& js) {\n    // While nothing at this point should be actually waiting on the ready promise,\n    // we should still resolve it just in case.\n    self->runIfAlive([&](WritableStreamSinkJsAdapter& self) {\n      self.state.transitionTo<Closed>();\n      self.maybeReleaseBackpressure(js);\n    });\n  }).catch_(js, [self = selfRef.addRef()](jsg::Lock& js, jsg::Value&& exception) {\n    // Likewise, while nothing should be waiting on the ready promise, we\n    // should still reject it just in case.\n    auto error = jsg::JsValue(exception.getHandle(js));\n    self->runIfAlive([&](WritableStreamSinkJsAdapter& self) {\n      self.abort(js, error);\n      self.backpressureState.abort(js, error);\n    });\n    js.throwException(kj::mv(exception));\n  });\n}\n\n// Transitions the adapter to the errored state, even if we are already closed.\nvoid WritableStreamSinkJsAdapter::abort(kj::Exception&& exception) {\n  // If we are in an active state, we need to cancel any in-flight and pending\n  // operations in the active write queue *before* we transition to the errored\n  // state. This ensures that any pending writes are interrupted and do not\n  // complete.\n  KJ_IF_SOME(open, state.tryGetActiveUnsafe()) {\n    open.active->abort(kj::cp(exception));\n  }\n  // Use forceTransitionTo because abort can be called from any state.\n  state.forceTransitionTo<kj::Exception>(kj::mv(exception));\n}\n\nvoid WritableStreamSinkJsAdapter::abort(jsg::Lock& js, const jsg::JsValue& reason) {\n  abort(js.exceptionToKj(reason));\n}\n\nvoid WritableStreamSinkJsAdapter::BackpressureState::abort(\n    jsg::Lock& js, const jsg::JsValue& reason) {\n  // Backpressure signaling is being aborted, likely because the adapter\n  // transitioned to the errored state. Reject the ready promise with\n  // the given reason.\n  KJ_IF_SOME(resolver, readyResolver) {\n    resolver.reject(js, reason);\n    readyResolver = kj::none;\n  }\n}\n\nvoid WritableStreamSinkJsAdapter::BackpressureState::release(jsg::Lock& js) {\n  // The backppressure has been released. Resolve the ready promise.\n  KJ_IF_SOME(resolver, readyResolver) {\n    resolver.resolve(js);\n    readyResolver = kj::none;\n  }\n}\n\nbool WritableStreamSinkJsAdapter::BackpressureState::isWaiting() const {\n  return readyResolver != kj::none;\n}\n\njsg::Promise<void> WritableStreamSinkJsAdapter::BackpressureState::getReady(jsg::Lock& js) {\n  return ready.whenResolved(js);\n}\n\njsg::MemoizedIdentity<jsg::Promise<void>>& WritableStreamSinkJsAdapter::BackpressureState::\n    getReadyStable() {\n  return readyWatcher;\n}\n\nWritableStreamSinkJsAdapter::BackpressureState::BackpressureState(\n    jsg::Promise<void>::Resolver&& resolver,\n    jsg::Promise<void>&& promise,\n    jsg::MemoizedIdentity<jsg::Promise<void>>&& watcher)\n    : readyResolver(kj::mv(resolver)),\n      ready(kj::mv(promise)),\n      readyWatcher(kj::mv(watcher)) {}\n\nvoid WritableStreamSinkJsAdapter::maybeSignalBackpressure(jsg::Lock& js) {\n  // We should only be signaling backpressure if we are in an active state.\n  state.requireActiveUnsafe();\n  // Indicate that backpressure is being applied. If we are already in a\n  // backpressure state (isWaiting() is true), this is a no-op.\n  if (!backpressureState.isWaiting()) {\n    // We signal backpressure by replacing the backpressure state.\n    // This replaces the JS promises and resolvers with a new set.\n    backpressureState = newBackpressureState(js);\n  }\n}\n\nvoid WritableStreamSinkJsAdapter::maybeReleaseBackpressure(jsg::Lock& js) {\n  KJ_IF_SOME(open, state.tryGetActiveUnsafe()) {\n    if (open.active->getDesiredSize() > 0) {\n      // The desired size is now > 0, so we can release backpressure.\n      // If backpressure is already released or aborted, this is a non-op.\n      backpressureState.release(js);\n    }\n  }\n}\n\nWritableStreamSinkJsAdapter::BackpressureState WritableStreamSinkJsAdapter::newBackpressureState(\n    jsg::Lock& js) {\n  jsg::PromiseResolverPair<void> pair = js.newPromiseAndResolver<void>();\n  pair.promise.markAsHandled(js);\n  auto watcher = jsg::MemoizedIdentity<jsg::Promise<void>>(pair.promise.whenResolved(js));\n  return BackpressureState(kj::mv(pair.resolver), kj::mv(pair.promise), kj::mv(watcher));\n}\n\njsg::Promise<void> WritableStreamSinkJsAdapter::getReady(jsg::Lock& js) {\n  return backpressureState.getReady(js);\n}\n\njsg::MemoizedIdentity<jsg::Promise<void>>& WritableStreamSinkJsAdapter::getReadyStable() {\n  return backpressureState.getReadyStable();\n}\n\nvoid WritableStreamSinkJsAdapter::visitForGc(jsg::GcVisitor& visitor) {\n  visitor.visit(\n      backpressureState.readyResolver, backpressureState.ready, backpressureState.readyWatcher);\n}\n\nvoid WritableStreamSinkJsAdapter::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"backpressureState.readyResolver\", backpressureState.readyResolver);\n  tracker.trackField(\"backpressureState.ready\", backpressureState.ready);\n  tracker.trackField(\"backpressureState.readyWatcher\", backpressureState.readyWatcher);\n}\n\nkj::Maybe<const WritableStreamSinkJsAdapter::Options&> WritableStreamSinkJsAdapter::getOptions() {\n  KJ_IF_SOME(open, state.tryGetActiveUnsafe()) {\n    return open.active->options;\n  } else {\n    return kj::none;\n  }\n}\n\n// ================================================================================================\n\nstruct WritableStreamSinkKjAdapter::Active {\n  IoContext& ioContext;\n  jsg::Ref<WritableStream> stream;\n  jsg::Ref<WritableStreamDefaultWriter> writer;\n  kj::Canceler canceler;\n\n  // The contract of WritableStreamSink is that there can only be one\n  // write in-flight at a time.\n  bool writePending = false;\n\n  bool closePending = false;\n  kj::Maybe<kj::Exception> pendingAbort;\n\n  // Prevent abort() from being called multiple times.\n  bool aborted = false;\n\n  Active(jsg::Lock& js, IoContext& ioContext, jsg::Ref<WritableStream> stream);\n  KJ_DISALLOW_COPY_AND_MOVE(Active);\n  ~Active() noexcept(false);\n\n  void abort(kj::Exception reason);\n};\n\nnamespace {\njsg::Ref<WritableStreamDefaultWriter> initWriter(jsg::Lock& js, jsg::Ref<WritableStream>& stream) {\n  JSG_REQUIRE(!stream->isLocked(), TypeError, \"WritableStream is locked.\");\n  return stream->getWriter(js);\n}\n}  // namespace\n\nWritableStreamSinkKjAdapter::Active::Active(\n    jsg::Lock& js, IoContext& ioContext, jsg::Ref<WritableStream> stream)\n    : ioContext(ioContext),\n      stream(kj::mv(stream)),\n      writer(initWriter(js, this->stream)) {}\n\nWritableStreamSinkKjAdapter::Active::~Active() noexcept(false) {\n  abort(KJ_EXCEPTION(DISCONNECTED, \"WritableStreamSinkKjAdapter is canceled.\"));\n}\n\nvoid WritableStreamSinkKjAdapter::Active::abort(kj::Exception reason) {\n  if (aborted) return;\n  aborted = true;\n  canceler.cancel(kj::cp(reason));\n  ioContext.addTask(ioContext.run([writable = kj::mv(stream), writer = kj::mv(writer),\n                                      exception = kj::cp(reason)](jsg::Lock& js) mutable {\n    auto& ioContext = IoContext::current();\n    auto error = js.exceptionToJsValue(kj::mv(exception));\n    auto promise = writer->abort(js, error.getHandle(js));\n    return ioContext.awaitJs(js, kj::mv(promise));\n  }));\n}\n\nWritableStreamSinkKjAdapter::WritableStreamSinkKjAdapter(\n    jsg::Lock& js, IoContext& ioContext, jsg::Ref<WritableStream> stream)\n    : state(KjState::create<KjOpen>(kj::heap<Active>(js, ioContext, kj::mv(stream)))),\n      selfRef(kj::rc<WeakRef<WritableStreamSinkKjAdapter>>(\n          kj::Badge<WritableStreamSinkKjAdapter>{}, *this)) {}\n\nWritableStreamSinkKjAdapter::~WritableStreamSinkKjAdapter() noexcept(false) {\n  selfRef->invalidate();\n}\n\nkj::Promise<void> WritableStreamSinkKjAdapter::write(kj::ArrayPtr<const byte> buffer) {\n  auto pieces = kj::arr(buffer);\n  co_await write(pieces);\n}\n\nkj::Promise<void> WritableStreamSinkKjAdapter::write(\n    kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) {\n  KJ_IF_SOME(exc, state.tryGetErrorUnsafe()) {\n    kj::throwFatalException(kj::cp(exc));\n  }\n\n  if (state.is<KjClosed>()) {\n    KJ_FAIL_REQUIRE(\"Cannot write after close.\");\n  }\n\n  auto& open = state.requireActiveUnsafe();\n  auto& active = *open.active;\n  KJ_REQUIRE(!active.writePending, \"Cannot have multiple concurrent writes.\");\n  KJ_IF_SOME(exception, active.pendingAbort) {\n    auto exc = kj::cp(exception);\n    state.forceTransitionTo<kj::Exception>(kj::cp(exc));\n    return kj::mv(exc);\n  }\n  if (active.closePending) {\n    state.transitionTo<KjClosed>();\n    KJ_FAIL_REQUIRE(\"Cannot write after close.\");\n  }\n  active.writePending = true;\n\n  return active.canceler\n      .wrap(active.ioContext.run([self = selfRef.addRef(), writer = active.writer.addRef(),\n                                     pieces = pieces](jsg::Lock& js) mutable -> kj::Promise<void> {\n    size_t totalAmount = 0;\n    for (auto piece: pieces) {\n      totalAmount += piece.size();\n    }\n    if (totalAmount == 0) {\n      return kj::READY_NOW;\n    }\n\n    // We collapse our pieces into a single ArrayBuffer for efficiency. The\n    // WritableStream API has no concept of a vector write, so each write\n    // would incur the overhead of a separate promise and microtask checkpoint.\n    // By collapsing into a single write we reduce that overhead.\n    auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, totalAmount);\n    auto ptr = backing.asArrayPtr();\n    for (auto piece: pieces) {\n      ptr.first(piece.size()).copyFrom(piece);\n      ptr = ptr.slice(piece.size());\n    }\n    jsg::BufferSource source(js, kj::mv(backing));\n\n    auto ready = KJ_ASSERT_NONNULL(writer->isReady(js));\n    auto promise =\n        ready.then(js, [writer = writer.addRef(), source = kj::mv(source)](jsg::Lock& js) mutable {\n      return writer->write(js, source.getHandle(js));\n    });\n    return IoContext::current().awaitJs(js, kj::mv(promise));\n  })).then([self = selfRef.addRef()]() {\n    self->runIfAlive([&](WritableStreamSinkKjAdapter& self) {\n      KJ_IF_SOME(open, self.state.tryGetActiveUnsafe()) {\n        open.active->writePending = false;\n      }\n    });\n  }, [self = selfRef.addRef()](kj::Exception exception) {\n    self->runIfAlive([&](WritableStreamSinkKjAdapter& self) {\n      KJ_IF_SOME(open, self.state.tryGetActiveUnsafe()) {\n        open.active->writePending = false;\n        open.active->pendingAbort = kj::cp(exception);\n      }\n    });\n    kj::throwFatalException(kj::mv(exception));\n  });\n}\n\nkj::Promise<void> WritableStreamSinkKjAdapter::end() {\n  KJ_IF_SOME(exc, state.tryGetErrorUnsafe()) {\n    return kj::cp(exc);\n  }\n\n  if (state.is<KjClosed>()) {\n    return kj::READY_NOW;\n  }\n\n  auto& open = state.requireActiveUnsafe();\n  auto& active = *open.active;\n  KJ_REQUIRE(!active.writePending, \"Cannot have multiple concurrent writes.\");\n  KJ_IF_SOME(exception, active.pendingAbort) {\n    auto exc = kj::mv(exception);\n    state.forceTransitionTo<kj::Exception>(kj::cp(exc));\n    return kj::mv(exc);\n  }\n  if (active.closePending) {\n    state.transitionTo<KjClosed>();\n    return kj::READY_NOW;\n  }\n  active.closePending = true;\n  return active.canceler\n      .wrap(active.ioContext.run(\n          [self = selfRef.addRef(), writer = active.writer.addRef()](jsg::Lock& js) mutable {\n    auto promise = writer->close(js);\n    return IoContext::current().awaitJs(js, kj::mv(promise));\n  })).catch_([self = selfRef.addRef()](kj::Exception exception) {\n    self->runIfAlive([&](WritableStreamSinkKjAdapter& self) {\n      KJ_IF_SOME(open, self.state.tryGetActiveUnsafe()) {\n        open.active->pendingAbort = kj::cp(exception);\n      }\n    });\n    kj::throwFatalException(kj::mv(exception));\n  });\n}\n\nvoid WritableStreamSinkKjAdapter::abort(kj::Exception reason) {\n  KJ_IF_SOME(open, state.tryGetActiveUnsafe()) {\n    open.active->abort(kj::cp(reason));\n  }\n  // Use forceTransitionTo because abort can be called from any state.\n  state.forceTransitionTo<kj::Exception>(kj::mv(reason));\n}\n\n}  // namespace workerd::api::streams\n"
  },
  {
    "path": "src/workerd/api/streams/writable-sink-adapter.h",
    "content": "#include \"common.h\"\n#include \"writable-sink.h\"\n\n#include <workerd/util/state-machine.h>\n#include <workerd/util/weak-refs.h>\n\nnamespace workerd::api::streams {\n// Wraps a WritableStreamSink with a more JS-friendly interface that implements\n// queued writes and backpressure signaling. This is arguably what WritableStreamSink\n// should have been in the first place. Eventually we might be able to replace\n// WritableStreamSink with this class directly, but for now we need to keep both.\n//\n// Instances of WritableStreamSinkJsAdapter are meant to be used from within the\n// isolate lock, when you have need to write data to a kj stream from JavaScript.\n// As such, it is not a jsg::Object itself, nor is it a kj I/O object, but it\n// sits between the two worlds. Internally it holds the WritableStreamSink within\n// an IoOwn so that correct IoContext usage is enforced. But the kj::Own for the\n// adapter itself is meant to be held in JS land.\n//\n// Once created, the adapter owns the underlying WritableStreamSink. It is not\n// possible to extract the sink from the adapter. This is because the adapter\n// needs to be able to enforce its own state machine and queued write mechanism.\n//\n// The adapter implements backpressure signaling based on a high water mark\n// configured at construction time. When the number of bytes in flight exceeds\n// the high water mark, we signal backpressure by causing the ready promise\n// to be reset to a new pending promise. When backpressure is released again,\n// the ready promise is resolved. The identity of the ready promise changes\n// whenever the backpressure state changes.\n//\n// The adapter also implements flush signaling. Flushing signals are checkpoints\n// that are inserted into the write queue, essentially like a no-op write. They\n// can be used as synchronization points to ensure that all prior writes have\n// completed. Flush signals do not affect backpressure or stream state.\n//\n// Dropping the adapter will cancel any in-flight and pending operations\n// immediately. Dropping the IoContext while the adapter is still active\n// will also cancel any in-flight and pending operations and cause the\n// adapter to be invalidated (the Active state is held with an IoOwn).\n//\n//     ┌───────────────────────────────────────────┐\n//     │         JavaScript Code                   │\n//     │                                           │\n//     │  • write(data) → Promise<void>            │\n//     │  • flush() → Promise<void>                │\n//     │  • end() → Promise<void>                  │\n//     │  • abort(reason)                          │\n//     │  • getReady() → Promise<void>             │\n//     └───────────────────────────────────────────┘\n//                            │\n//                            ▼\n//     ┌───────────────────────────────────────────┐\n//     │    WritableStreamSinkJsAdapter            │\n//     │                                           │\n//     │  ┌─────────────────────────────────────┐  │\n//     │  │       JavaScript API                │  │\n//     │  │                                     │  │\n//     │  │  • write(data) → Promise<void>      │  │\n//     │  │  • flush() → Promise<void>          │  │\n//     │  │  • end() → Promise<void>            │  │\n//     │  │  • abort(reason)                    │  │\n//     │  │  • getReady() → Promise<void>       │  │\n//     │  │  • getDesiredSize() → number        │  │\n//     │  └─────────────────────────────────────┘  │\n//     │                   │                       │\n//     │                   ▼                       │\n//     │  ┌─────────────────────────────────────┐  │\n//     │  │      Backpressure Management        │  │\n//     │  │                                     │  │\n//     │  │  • High water mark (16KB default)   │  │\n//     │  │  • Bytes in flight tracking         │  │\n//     │  │  • Ready promise signaling          │  │\n//     │  │  • Queue depth management           │  │\n//     │  └─────────────────────────────────────┘  │\n//     │                   │                       │\n//     │                   ▼                       │\n//     │  ┌─────────────────────────────────────┐  │\n//     │  │       Write Queue Management        │  │\n//     │  │                                     │  │\n//     │  │  • Queued writes with ordering      │  │\n//     │  │  • Flush checkpoints                │  │\n//     │  │  • Single in-flight write           │  │\n//     │  │  • Error propagation                │  │\n//     │  └─────────────────────────────────────┘  │\n//     │                   │                       │\n//     │                   ▼                       │\n//     │  ┌─────────────────────────────────────┐  │\n//     │  │         KJ Integration              │  │\n//     │  │                                     │  │\n//     │  │  IoOwn<WritableStreamSink>          │  │\n//     │  │  WeakRef for safe references        │  │\n//     │  │  IoContext-aware operations         │  │\n//     │  └─────────────────────────────────────┘  │\n//     └───────────────────────────────────────────┘\n//                            │\n//                            ▼\n//     ┌───────────────────────────────────────────┐\n//     │       WritableStreamSink                  │\n//     │       (KJ Native Sink)                    │\n//     │                                           │\n//     │  • write(buffer) → Promise<void>          │\n//     │  • end() → Promise<void>                  │\n//     │  • abort(reason)                          │\n//     └───────────────────────────────────────────┘\n//\nclass WritableStreamSinkJsAdapter final {\n public:\n  struct Options {\n    // While the WritableStreamSink interface, and kj streams in general, do\n    // not have a notion of backpressure, and instead generally require only\n    // one write to be in flight at a time, it's better for performance for\n    // us to be able to buffer a bit more data in flight. So we will implement\n    // a simple high water mark mechanism. The default is 16KB.\n    size_t highWaterMark = 16384;\n\n    // When detachOnWrite is true, and a write() is made with an ArrayBuffer,\n    // or ArrayBufferView, we will attempt to detach the underlying buffer\n    // before writing it to the sink. Detaching is required by the\n    // streams spec but our original implementation does not detach\n    // and it turns out there are old workers depending on that behavior.\n    bool detachOnWrite = false;\n  };\n\n  WritableStreamSinkJsAdapter(jsg::Lock& js,\n      IoContext& ioContext,\n      kj::Own<WritableSink> sink,\n      kj::Maybe<Options> options = kj::none);\n  WritableStreamSinkJsAdapter(jsg::Lock& js,\n      IoContext& ioContext,\n      kj::Own<kj::AsyncOutputStream> stream,\n      StreamEncoding encoding,\n      kj::Maybe<Options> options = kj::none);\n  KJ_DISALLOW_COPY_AND_MOVE(WritableStreamSinkJsAdapter);\n  ~WritableStreamSinkJsAdapter() noexcept(false);\n\n  // If we are in the errored state, returns the exception, otherwise kj::none.\n  kj::Maybe<const kj::Exception&> isErrored() KJ_LIFETIMEBOUND;\n\n  // Returns true if we are in the closed state.\n  bool isClosed();\n\n  // Returns true if close() has been called but we are not yet closed.\n  bool isClosing();\n\n  // If we are not in the closed or errored state, returns the desired\n  // size based on the configured high water mark and the number of\n  // bytes currently in flight. The desired size is the number of bytes\n  // that can be written before we exceed the high water mark. If the\n  // return value is <= 0 then backpressure is being signaled. If we are\n  // in the closed or errored states, returns kj::none.\n  kj::Maybe<ssize_t> getDesiredSize();\n\n  // Writes a chunk to the underlying sink via the queued write mechanism.\n  // The implementation ensures that only one write is in flight with the\n  // underlying sink at a time, while additional writes are queued up\n  // behind it. It is not necessary to await the returned promise before\n  // calling write() again, though doing so is not an error. If the write\n  // fails, the returned promise will reject with the failure reason.\n  // Also if the write fails, the adapter will be transitioned to the\n  // errored state and all subsequent queued writes will fail. Once\n  // close() has been called, no additional writes will be accepted\n  // and the returned promise will reject with an error. If the adapter\n  // is already in the closed or errored state, the returned promise will\n  // be rejected.\n  //\n  // Values written may be ArrayBuffer, ArrayBufferView, SharedArrayBuffer,\n  // or string. Other types will cause the returned promise to reject.\n  //\n  // Backpressure is signaled when the number of bytes in flight (i.e.\n  // the total number of bytes passed to write() calls that have not yet\n  // completed) exceeds the configured high water mark. When backpressure\n  // is signaled, additional writes are still accepted and queued up, but\n  // the caller really should wait for the ready promise to resolve before\n  // continuing to write more. This works exactly like a WritableStream's\n  // backpressure mechanism. Callers keep writing until backpressure is\n  // signaled, then wait for the ready promise to resolve before continuing,\n  // etc.\n  jsg::Promise<void> write(jsg::Lock& js, const jsg::JsValue& value);\n\n  // Inserts a flush signal into the write queue. The returned promise\n  // resolves once all prior writes have completed. This can be used\n  // as a synchronization point to ensure that all writes up to this\n  // point have been fully processed. If the adapter is in the closed\n  // or errored state, the returned promise will reject. If the stream\n  // errors while waiting for prior writes to complete, the returned\n  // promise will be rejected.\n  jsg::Promise<void> flush(jsg::Lock& js);\n\n  // Transitions the adapter into the closing state. Once the write queue\n  // is empty, we will close the sink and transition to the closed state.\n  // If the adapter is already in the closing state, a new promise is\n  // returned that will resolve when the adapter is fully closed. If the\n  // adapter is already closed, a resolved promise is returned. If the\n  // adapter is in the errored state, a rejected promise is returned.\n  // All pending writes in the queue will be processed before closing\n  // the sink and transitioning to the closed state. If any pending\n  // writes fail, the adapter will transition to the errored state, and\n  // all subsequent pending writes will be rejected along with the close\n  // promise.\n  jsg::Promise<void> end(jsg::Lock& js);\n\n  // Transitions the adapter to the errored state, even if we are already closed.\n  // All pending or in-flight writes, and a pending close, will all be rejected\n  // with the given exception. If we are already in the errored state, this\n  // is a no-op. This change is immediate. Once in the errored state, no\n  // further writes or closes are allowed.\n  void abort(kj::Exception&& exception);\n\n  // Transitions the adapter to the errored state, even if we are already closed.\n  // All pending or in-flight writes, and a pending close, will all be rejected\n  // with the given exception. If we are already in the errored state, this\n  // is a no-op. This change is immediate. Once in the errored state, no\n  // further writes or closes are allowed. This variant is for use when\n  // the exception is coming from JavaScript. It will be converted into a\n  // tunneled kj::Exception.\n  void abort(jsg::Lock& js, const jsg::JsValue& reason);\n\n  // Returns a promise that resolves when backpressure is released.\n  // Note that the identity of the returned promise will change as the\n  // backpressure state changes. Whenever backpressure is signaled, a new\n  // pending promise will be created, whenever backpressure is released\n  // again that promise will be resolved. As such, this promise should\n  // not be cached or stored. Instead, before every write() call, the\n  // caller should wait on the current getReady() promise.\n  jsg::Promise<void> getReady(jsg::Lock& js);\n\n  // Returns a memoized identity for the ready promise. This can be used\n  // to return a stable reference to the ready promise out to JavaScript\n  // that will not change identity between calls unless the backpressure\n  // state changes. Like the getReady() promise, this should not be cached\n  // or stored, but it is safe to return this from a getter multiple times\n  // to JavaScript as it will ensure that the same JS promise object is\n  // always returned until the backpressure state changes. This variation\n  // is not suitable for use within C++ code that needs to await on the\n  // ready promise because the internal jsg::Promise<void> object will\n  // no longer exist once the reference is passed out to JavaScript.\n  jsg::MemoizedIdentity<jsg::Promise<void>>& getReadyStable();\n\n  // Returns the options used to configure this adapter if the adapter\n  // is not closed or errored.\n  kj::Maybe<const Options&> getOptions();\n\n  void visitForGc(jsg::GcVisitor& visitor);\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  // Represents the active state of the adapter. Importantly, this state\n  // holds both the underlying WritableStreamSink and the write queue.\n  // It must be held within an IoOwn.\n  struct Active;\n\n  struct Closed final {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"closed\"_kj;\n  };\n\n  struct Open {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"open\"_kj;\n    IoOwn<Active> active;\n  };\n\n  // State machine for tracking writable sink adapter lifecycle:\n  //   Open -> Closed (normal close via end())\n  //   Open -> kj::Exception (error via abort() or write failure)\n  // Closed is terminal, kj::Exception is implicitly terminal via ErrorState.\n  using State = StateMachine<TerminalStates<Closed>,\n      ErrorState<kj::Exception>,\n      ActiveState<Open>,\n      Open,\n      Closed,\n      kj::Exception>;\n  State state;\n\n  // Used for backpressure signaling. When backpressure is indicated, the\n  // readyResolver, ready, and readyWatcher will be replaced with a new set.\n  // When backpressure is relieved, the readyResolver will be resolved.\n  // The adapter will start out in a ready state.\n  struct BackpressureState final {\n    // Note that if the BackpressureState is dropped while in a waiting state,\n    // the ready promise will be left unresolved. This is OK.\n    kj::Maybe<jsg::Promise<void>::Resolver> readyResolver;\n    jsg::Promise<void> ready;\n    jsg::MemoizedIdentity<jsg::Promise<void>> readyWatcher;\n\n    // Aborts backpressure signaling, likely because the adapter is being errored.\n    // Causes the ready promise to be rejected with the given reason.\n    void abort(jsg::Lock& js, const jsg::JsValue& reason);\n\n    // Releases backpressure, resolving the ready promise.\n    void release(jsg::Lock& js);\n\n    // Indicates that backpressure has been signaled and we are waiting\n    // for it to be released or aborted.\n    bool isWaiting() const;\n\n    // Returns a promise that resolves when backpressure is released.\n    // Note that every call to this returns a new jsg::Promise<void>\n    // instance. Callers that need a stable identity should use\n    // getReadyStable() instead (generally this is only the case when\n    // returning the promise to JavaScript via a getter).\n    jsg::Promise<void> getReady(jsg::Lock& js);\n\n    // Returns a memoized identity for the ready promise. This can be used\n    // to return a stable reference to the ready promise out to JavaScript\n    // that will not change identity between calls unless the backpressure\n    // state changes.\n    jsg::MemoizedIdentity<jsg::Promise<void>>& getReadyStable();\n    BackpressureState(jsg::Promise<void>::Resolver&& resolver,\n        jsg::Promise<void>&& promise,\n        jsg::MemoizedIdentity<jsg::Promise<void>>&& watcher);\n  };\n  BackpressureState backpressureState;\n  kj::Rc<WeakRef<WritableStreamSinkJsAdapter>> selfRef;\n\n  // Replaces the backpressure state with a new one, indicating that backpressure\n  // is being applied. If we are already in a backpressure state, this is a no-op.\n  // This will cause the ready promise (and its stable identity) to change.\n  void maybeSignalBackpressure(jsg::Lock& js);\n\n  // Conditionally releases backpressure if the desired size is now > 0.\n  void maybeReleaseBackpressure(jsg::Lock& js);\n\n  // Creates a new BackpressureState in the waiting state.\n  static BackpressureState newBackpressureState(jsg::Lock& js);\n};\n\n// ================================================================================\n\n// Adapts a WritableStream to a KJ-frendly interface.\n// The adapter fully wraps the WritableStream instance,\n// using a WritableStreamDefaultWriter to push data to it.\n// Then the adapter is destroyed or aborted, the writer is\n// aborted and both the writer and the stream references\n// are dropped. Critically, the stream is not usable after\n// ownership is transferred to this adapter. Initializing the adapter\n// will fail if the stream is already locked.\n//\n// If the adapter is dropped, or aborted while there are pending writes,\n// the pending writes will be rejected with the same exception as the abort.\n//\n// While WritableStream itself allows multiple writes to be in flight\n// at the same time, the WritableStreamSink interface does not, so\n// the adapter will ensure that only one write is in flight at a time.\n//\n// While the caller is expected to follow the WritableStreamSink contract\n// and keep the adapter alive until the write promises resolve, there\n// are some protections in place to avoid use-after-free if the caller\n// drops the adapter. There's nothing we can do if the caller drops the\n// buffer, however, so that is still a hard requirement.\n// TODO(safety): This can be made safer by having write take a kj::Array\n// as input instead of a kj::ArrayPtr but that's a larger refactor.\n//\n//     ┌───────────────────────────────────────────┐\n//     │         WritableStreamSink                │\n//     │                                           │\n//     │  • write(buffer)                          │\n//     │  • write(pieces[])                        │\n//     │  • end()                                  │\n//     │  • abort(reason)                          │\n//     └───────────────────────────────────────────┘\n//                            │\n//                            ▼\n//     ┌───────────────────────────────────────────┐\n//     │    WritableStreamSinkKjAdapter            │\n//     │                                           │\n//     │  ┌─────────────────────────────────────┐  │\n//     │  │         KJ Native API               │  │\n//     │  │                                     │  │\n//     │  │  • write(ArrayPtr<byte>)            │  │\n//     │  │  • write(ArrayPtr<ArrayPtr<byte>>)  │  │\n//     │  │  • end() → Promise<void>            │  │\n//     │  │  • abort(exception)                 │  │\n//     │  └─────────────────────────────────────┘  │\n//     │                   │                       │\n//     │                   ▼                       │\n//     │  ┌─────────────────────────────────────┐  │\n//     │  │       State Management              │  │\n//     │  │                                     │  │\n//     │  │   Active ──► Closed                 │  │\n//     │  │     │          │                    │  │\n//     │  │     │          ▼                    │  │\n//     │  │     └─────► Errored                 │  │\n//     │  └─────────────────────────────────────┘  │\n//     │                   │                       │\n//     │                   ▼                       │\n//     │  ┌─────────────────────────────────────┐  │\n//     │  │     JavaScript Integration          │  │\n//     │  │                                     │  │\n//     │  │  WritableStreamDefaultWriter        │  │\n//     │  │  WeakRef for safe references        │  │\n//     │  │  IoContext-aware JS operations      │  │\n//     │  │  Promise handling & async writes    │  │\n//     │  └─────────────────────────────────────┘  │\n//     └───────────────────────────────────────────┘\n//                            │\n//                            ▼\n//     ┌───────────────────────────────────────────┐\n//     │      JavaScript WritableStream            │\n//     │                                           │\n//     │  • getWriter()                            │\n//     │  • write(chunk) → Promise<void>           │\n//     │  • close() → Promise<void>                │\n//     │  • abort(reason) → Promise<void>          │\n//     │  • locked, state properties               │\n//     └───────────────────────────────────────────┘\n//\nclass WritableStreamSinkKjAdapter final: public WritableSink {\n public:\n  WritableStreamSinkKjAdapter(jsg::Lock& js, IoContext& ioContext, jsg::Ref<WritableStream> stream);\n  ~WritableStreamSinkKjAdapter() noexcept(false);\n\n  // Attempts to write the given buffer to the underlying stream.\n  // The returned promise resolves once the write has completed.\n  // If the stream is closed, the returned promise rejects with\n  // an exception. If the stream errors, the returned promise\n  // rejects with the same exception. If the write fails, the\n  // returned promise rejects with the failure reason.\n  //\n  // Per the contract of write, it is the caller's responsibility\n  // to ensure that the adapter and buffer remain alive until\n  // the returned promise resolves.\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override;\n\n  // Attempts to write the given pieces to the underlying stream.\n  // The returned promise resolves once the full write has completed.\n  // If the stream is closed, the returned promise rejects with\n  // an exception. If the stream errors, the returned promise\n  // rejects with the same exception. If the write fails, the\n  // returned promise rejects with the failure reason.\n  // Per the contract of write, it is the caller's responsibility\n  // to ensure that the adapter and buffers remain alive until\n  // the returned promise resolves.\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override;\n\n  // Closes the underlying stream. The returned promise resolves\n  // once the stream is fully closed. If the stream is already\n  // closed, the returned promise resolves immediately. If the\n  // stream errors, the returned promise rejects with the same\n  // exception. If the close fails, the returned promise rejects\n  // with the failure reason.\n  kj::Promise<void> end() override;\n\n  // Immediately interrupts existing pending writes and errors the stream.\n  // All pending or in-flight writes will be rejected with the given\n  // exception. If we are already in the errored state, this is a no-op\n  // and the exception is ignored. This change is immediate. Once in\n  // the errored state, no further writes or closes are allowed.\n  void abort(kj::Exception reason) override;\n\n  // A WritableStreamSinkKjAdapter always (currently) uses identity encoding.\n  rpc::StreamEncoding disownEncodingResponsibility() override {\n    return rpc::StreamEncoding::IDENTITY;\n  }\n\n  rpc::StreamEncoding getEncoding() override {\n    return rpc::StreamEncoding::IDENTITY;\n  }\n\n private:\n  struct Active;\n  KJ_DECLARE_NON_POLYMORPHIC(Active);\n\n  struct KjClosed {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"closed\"_kj;\n  };\n\n  struct KjOpen {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"open\"_kj;\n    kj::Own<Active> active;\n  };\n\n  // State machine for tracking writable sink adapter lifecycle:\n  //   KjOpen -> KjClosed (normal close via end())\n  //   KjOpen -> kj::Exception (error via abort() or write failure)\n  // KjClosed is terminal, kj::Exception is implicitly terminal via ErrorState.\n  using KjState = StateMachine<TerminalStates<KjClosed>,\n      ErrorState<kj::Exception>,\n      ActiveState<KjOpen>,\n      KjOpen,\n      KjClosed,\n      kj::Exception>;\n  KjState state;\n  kj::Rc<WeakRef<WritableStreamSinkKjAdapter>> selfRef;\n};\n\n}  // namespace workerd::api::streams\n"
  },
  {
    "path": "src/workerd/api/streams/writable-sink-test.c++",
    "content": "#include \"writable-sink.h\"\n\n#include <workerd/jsg/jsg-test.h>\n#include <workerd/tests/test-fixture.h>\n#include <workerd/util/own-util.h>\n#include <workerd/util/stream-utils.h>\n\n#include <kj/async-io.h>\n#include <kj/compat/gzip.h>\n#include <kj/test.h>\n\nnamespace workerd::api::streams {\nnamespace {\n\n// Mock WritableSink for testing wrapper functionality\nclass MockWritableSink final: public WritableSink {\n public:\n  MockWritableSink() = default;\n  ~MockWritableSink() = default;\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::byte> buffer) override {\n    writeCallCount++;\n    lastWriteSize = buffer.size();\n    totalBytesWritten += buffer.size();\n\n    if (shouldFailWrite) {\n      KJ_FAIL_REQUIRE(\"Expected failure\");\n    }\n\n    // Copy data for verification\n    writtenData.addAll(buffer);\n    co_return;\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const kj::byte>> pieces) override {\n    multiWriteCallCount++;\n    size_t totalSize = 0;\n\n    for (auto piece: pieces) {\n      totalSize += piece.size();\n      writtenData.addAll(piece);\n    }\n\n    totalBytesWritten += totalSize;\n    lastWriteSize = totalSize;\n\n    if (shouldFailWrite) {\n      KJ_FAIL_REQUIRE(\"Expected failure\");\n    }\n\n    co_return;\n  }\n\n  kj::Promise<void> end() override {\n    endCallCount++;\n    isEnded = true;\n\n    if (shouldFailEnd) {\n      KJ_FAIL_REQUIRE(\"Expected failure\");\n    }\n\n    co_return;\n  }\n\n  void abort(kj::Exception reason) override {\n    abortCallCount++;\n    abortReason = kj::mv(reason);\n  }\n\n  rpc::StreamEncoding disownEncodingResponsibility() override {\n    disownCallCount++;\n    auto prev = encoding;\n    encoding = rpc::StreamEncoding::IDENTITY;\n    return prev;\n  }\n\n  rpc::StreamEncoding getEncoding() override {\n    getEncodingCallCount++;\n    return encoding;\n  }\n\n  // Test state accessors\n  uint32_t writeCallCount = 0;\n  uint32_t multiWriteCallCount = 0;\n  uint32_t endCallCount = 0;\n  uint32_t abortCallCount = 0;\n  uint32_t disownCallCount = 0;\n  uint32_t getEncodingCallCount = 0;\n\n  size_t lastWriteSize = 0;\n  size_t totalBytesWritten = 0;\n  bool isEnded = false;\n  kj::Maybe<kj::Exception> abortReason;\n\n  kj::Vector<kj::byte> writtenData;\n  rpc::StreamEncoding encoding = rpc::StreamEncoding::IDENTITY;\n\n  // Control behavior for testing\n  bool shouldFailWrite = false;\n  bool shouldFailEnd = false;\n};\n\n// Test memory-based AsyncOutputStream for factory function tests\nclass MemoryAsyncOutputStream final: public kj::AsyncOutputStream {\n public:\n  kj::Promise<void> write(kj::ArrayPtr<const kj::byte> buffer) override {\n    data.addAll(buffer);\n\n    if (writeShouldError) {\n      KJ_FAIL_REQUIRE(\"Expected failure\");\n    }\n\n    co_return;\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const kj::byte>> pieces) override {\n    for (auto piece: pieces) {\n      data.addAll(piece);\n    }\n\n    if (writeShouldError) {\n      KJ_FAIL_REQUIRE(\"Expected failure\");\n    }\n\n    co_return;\n  }\n\n  kj::Promise<void> whenWriteDisconnected() override {\n    return kj::NEVER_DONE;\n  }\n\n  bool writeShouldError = false;\n\n  kj::Vector<kj::byte> data;\n};\n\nstruct MockEndable final: public EndableAsyncOutputStream {\n  bool isEnded = false;\n  kj::Vector<kj::byte> data;\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::byte> buffer) override {\n    data.addAll(buffer);\n    co_return;\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const kj::byte>> pieces) override {\n    for (auto piece: pieces) {\n      data.addAll(piece);\n    }\n    co_return;\n  }\n\n  kj::Promise<void> whenWriteDisconnected() override {\n    return kj::NEVER_DONE;\n  }\n\n  kj::Promise<void> end() override {\n    isEnded = true;\n    co_return;\n  }\n};\n\n// ======================================================================================\n// Core WritableSink Interface Tests\n\nKJ_TEST(\"WritableSink basic write operations\") {\n  TestFixture fixture;\n  MockWritableSink sink;\n  kj::byte testData[] = {1, 2, 3, 4, 5};\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    // Test single buffer write\n    kj::ArrayPtr<const kj::byte> buffer(testData, 5);\n\n    co_await sink.write(buffer);\n    co_await sink.end();\n  });\n\n  KJ_ASSERT(sink.writeCallCount == 1);\n  KJ_ASSERT(sink.lastWriteSize == 5);\n  KJ_ASSERT(sink.totalBytesWritten == 5);\n  KJ_ASSERT(sink.writtenData.size() == 5);\n  KJ_ASSERT(sink.writtenData == testData);\n  KJ_ASSERT(sink.isEnded);\n}\n\nKJ_TEST(\"WritableSink multi-piece write operations\") {\n  TestFixture fixture;\n  MockWritableSink sink;\n\n  // Test multi-piece write\n  kj::byte data1[] = {1, 2, 3};\n  kj::byte data2[] = {4, 5};\n  kj::byte data3[] = {6, 7, 8, 9};\n\n  kj::ArrayPtr<const kj::byte> pieces[] = {kj::ArrayPtr<const kj::byte>(data1, 3),\n    kj::ArrayPtr<const kj::byte>(data2, 2), kj::ArrayPtr<const kj::byte>(data3, 4)};\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await sink.write(kj::ArrayPtr<const kj::ArrayPtr<const kj::byte>>(pieces, 3));\n  });\n\n  KJ_ASSERT(sink.multiWriteCallCount == 1);\n  KJ_ASSERT(sink.lastWriteSize == 9);\n  KJ_ASSERT(sink.totalBytesWritten == 9);\n  KJ_ASSERT(sink.writtenData.size() == 9);\n\n  // Verify data order\n  kj::byte expected[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};\n  KJ_ASSERT(sink.writtenData == kj::ArrayPtr<const kj::byte>(expected, 9));\n}\n\nKJ_TEST(\"WritableSink end operation\") {\n  TestFixture fixture;\n  MockWritableSink sink;\n\n  fixture.runInIoContext(\n      [&](const auto& environment) -> kj::Promise<void> { co_await sink.end(); });\n\n  KJ_ASSERT(sink.endCallCount == 1);\n  KJ_ASSERT(sink.isEnded);\n}\n\nKJ_TEST(\"WritableSink abort operation\") {\n  MockWritableSink sink;\n\n  sink.abort(KJ_EXCEPTION(DISCONNECTED, \"Abort reason\"));\n\n  KJ_ASSERT(sink.abortCallCount == 1);\n  KJ_ASSERT(sink.abortReason != kj::none);\n}\n\nKJ_TEST(\"WritableSink encoding operations\") {\n  MockWritableSink sink;\n\n  auto encoding = sink.getEncoding();\n  KJ_ASSERT(encoding == rpc::StreamEncoding::IDENTITY);\n  KJ_ASSERT(sink.getEncodingCallCount == 1);\n\n  auto disownedEncoding = sink.disownEncodingResponsibility();\n  KJ_ASSERT(disownedEncoding == rpc::StreamEncoding::IDENTITY);\n  KJ_ASSERT(sink.disownCallCount == 1);\n}\n\n// ======================================================================================\n// WritableSinkWrapper Tests\n\nKJ_TEST(\"WritableSinkWrapper write/end delegation\") {\n  TestFixture fixture;\n  auto innerSink = kj::heap<MockWritableSink>();\n  auto& sink = *innerSink;\n  class TestWrapper: public WritableSinkWrapper {\n   public:\n    TestWrapper(kj::Own<WritableSink> inner): WritableSinkWrapper(kj::mv(inner)) {}\n  };\n  auto wrapper = kj::heap<TestWrapper>(kj::mv(innerSink));\n  kj::byte testData[] = {1, 2, 3};\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await wrapper->write(kj::ArrayPtr<const kj::byte>(testData, 3));\n    co_await wrapper->end();\n  });\n\n  KJ_ASSERT(sink.writeCallCount == 1);\n  KJ_ASSERT(sink.lastWriteSize == 3);\n  KJ_ASSERT(sink.totalBytesWritten == 3);\n  KJ_ASSERT(sink.writtenData.size() == 3);\n  KJ_ASSERT(sink.writtenData == kj::arrayPtr(testData, sizeof(testData)));\n  KJ_ASSERT(sink.endCallCount == 1);\n  KJ_ASSERT(sink.isEnded);\n}\n\nKJ_TEST(\"WritableSinkWrapper abort delegation\") {\n  TestFixture fixture;\n  auto innerSink = kj::heap<MockWritableSink>();\n  auto& sink = *innerSink;\n  class TestWrapper: public WritableSinkWrapper {\n   public:\n    TestWrapper(kj::Own<WritableSink> inner): WritableSinkWrapper(kj::mv(inner)) {}\n  };\n  auto wrapper = kj::heap<TestWrapper>(kj::mv(innerSink));\n  wrapper->abort(KJ_EXCEPTION(FAILED, \"test abort\"));\n  KJ_ASSERT(sink.abortCallCount == 1);\n  KJ_ASSERT(sink.abortReason != kj::none);\n}\n\nKJ_TEST(\"WritableSinkWrapper encoding delegation\") {\n  TestFixture fixture;\n  auto innerSink = kj::heap<MockWritableSink>();\n  auto& sink = *innerSink;\n  sink.encoding = rpc::StreamEncoding::GZIP;\n  class TestWrapper: public WritableSinkWrapper {\n   public:\n    TestWrapper(kj::Own<WritableSink> inner): WritableSinkWrapper(kj::mv(inner)) {}\n  };\n  auto wrapper = kj::heap<TestWrapper>(kj::mv(innerSink));\n\n  auto encoding = wrapper->getEncoding();\n  KJ_ASSERT(encoding == rpc::StreamEncoding::GZIP);\n  KJ_ASSERT(sink.getEncodingCallCount == 1);\n  auto disowned = wrapper->disownEncodingResponsibility();\n  KJ_ASSERT(disowned == rpc::StreamEncoding::GZIP);\n  KJ_ASSERT(wrapper->getEncoding() == rpc::StreamEncoding::IDENTITY);\n  KJ_ASSERT(sink.disownCallCount == 1);\n}\n\nKJ_TEST(\"WritableSinkWrapper release functionality\") {\n  auto innerSink = kj::heap<MockWritableSink>();\n  auto innerPtr = innerSink.get();\n\n  class TestWrapper: public WritableSinkWrapper {\n   public:\n    TestWrapper(kj::Own<WritableSink> inner): WritableSinkWrapper(kj::mv(inner)) {}\n  };\n\n  auto wrapper = kj::heap<TestWrapper>(kj::mv(innerSink));\n\n  // Release the inner sink\n  auto released = wrapper->release();\n  KJ_ASSERT(released.get() == innerPtr);\n\n  // Wrapper should no longer be usable\n  try {\n    wrapper->abort(KJ_EXCEPTION(FAILED, \"test\"));\n    KJ_FAIL_REQUIRE(\"Expected exception on using released wrapper\");\n  } catch (...) {\n    auto exception = kj::getCaughtExceptionAsKj();\n    KJ_EXPECT(exception.getDescription().contains(\"inner != nullptr\"));\n  }\n}\n\n// ======================================================================================\n// Factory Function Tests\n\nKJ_TEST(\"newWritableSink with AsyncOutputStream\") {\n  TestFixture fixture;\n  MemoryAsyncOutputStream inner;\n  auto fakeOwn = kj::Own<MemoryAsyncOutputStream>(&inner, kj::NullDisposer::instance);\n  auto sink = newWritableSink(kj::mv(fakeOwn));\n  kj::byte testData[] = {1, 2, 3, 4, 5};\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    // Test writing data\n    co_await sink->write(kj::ArrayPtr<const kj::byte>(testData, 5));\n    co_await sink->end();\n  });\n\n  KJ_ASSERT(inner.data.size() == 5);\n  KJ_ASSERT(inner.data == kj::arrayPtr(testData, sizeof(testData)));\n}\n\nKJ_TEST(\"newClosedWritableSink (write)\") {\n  TestFixture fixture;\n  auto sink = newClosedWritableSink();\n  kj::byte testData[] = {1, 2, 3};\n\n  try {\n    fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n      // Test write on closed sink\n      co_await sink->write(kj::ArrayPtr<const kj::byte>(testData, 3));\n    });\n    KJ_FAIL_REQUIRE(\"should have failed\");\n  } catch (...) {\n    auto ex = kj::getCaughtExceptionAsKj();\n    KJ_ASSERT(ex.getDescription().contains(\"closed stream\"));\n  }\n\n  // Should not throw, write should be a no-op\n}\n\nKJ_TEST(\"newClosedWritableSink (end)\") {\n  TestFixture fixture;\n  auto sink = newClosedWritableSink();\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    // Test write on closed sink\n    co_await sink->end();\n  });\n\n  // Should not throw, write should be a no-op\n}\n\nKJ_TEST(\"newClosedWritableSink (aborted)\") {\n  TestFixture fixture;\n  auto exception = KJ_EXCEPTION(FAILED, \"test error\");\n  auto sink = newClosedWritableSink();\n  sink->abort(kj::cp(exception));\n\n  try {\n    fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n      // Test write on closed sink\n      co_await sink->end();\n    });\n    KJ_FAIL_REQUIRE(\"should have failed\");\n  } catch (...) {\n    auto caught = kj::getCaughtExceptionAsKj();\n    KJ_ASSERT(caught.getDescription() == exception.getDescription());\n  }\n}\n\nKJ_TEST(\"newErroredWritableSink (write)\") {\n  TestFixture fixture;\n  auto exception = KJ_EXCEPTION(FAILED, \"test error\");\n  auto sink = newErroredWritableSink(kj::cp(exception));\n\n  try {\n    fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n      // Test write on errored sink\n      co_await sink->write(kj::ArrayPtr<const kj::byte>());\n    });\n    KJ_FAIL_REQUIRE(\"should have failed\");\n  } catch (...) {\n    auto caught = kj::getCaughtExceptionAsKj();\n    KJ_ASSERT(caught.getDescription() == exception.getDescription());\n  }\n}\n\nKJ_TEST(\"newErroredWritableSink (end)\") {\n  TestFixture fixture;\n  auto exception = KJ_EXCEPTION(FAILED, \"test error\");\n  auto sink = newErroredWritableSink(kj::cp(exception));\n\n  try {\n    fixture.runInIoContext(\n        [&](const auto& environment) -> kj::Promise<void> { co_await sink->end(); });\n    KJ_FAIL_REQUIRE(\"should have failed\");\n  } catch (...) {\n    auto caught = kj::getCaughtExceptionAsKj();\n    KJ_ASSERT(caught.getDescription() == exception.getDescription());\n  }\n}\n\nKJ_TEST(\"newNullWritableSink\") {\n  TestFixture fixture;\n  auto sink = newNullWritableSink();\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    // Test writing data\n    co_await sink->write(kj::ArrayPtr<const kj::byte>());\n    co_await sink->write(kj::ArrayPtr<const kj::byte>());\n    co_await sink->end();\n  });\n}\n\n// ======================================================================================\n// Error Handling Tests\n\nKJ_TEST(\"WritableSink error propagation\") {\n  TestFixture fixture;\n  MemoryAsyncOutputStream inner;\n  inner.writeShouldError = true;\n  auto fakeOwn = kj::Own<MemoryAsyncOutputStream>(&inner, kj::NullDisposer::instance);\n  auto sink = newWritableSink(kj::mv(fakeOwn));\n  kj::byte testData[] = {1, 2, 3, 4, 5};\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    try {\n      co_await sink->write(kj::ArrayPtr<const kj::byte>(testData, 5));\n      KJ_FAIL_REQUIRE(\"shuld have failed\");\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(exception.getDescription().contains(\"Expected failure\"));\n    }\n    // The write should have been recorded.\n    KJ_ASSERT(inner.data.size() == 5);\n    // Let's make sure the error state is persistent.\n    try {\n      co_await sink->write(kj::ArrayPtr<const kj::byte>(testData, 5));\n      KJ_FAIL_REQUIRE(\"shuld have failed\");\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      KJ_ASSERT(exception.getDescription().contains(\"Expected failure\"));\n    }\n    // No additional data should have been recorded.\n    KJ_ASSERT(inner.data.size() == 5);\n  });\n}\n\n// ======================================================================================\n// Encoding Tests\n\nKJ_TEST(\"WritableSink encoding responsibility transfer\") {\n  TestFixture fixture;\n  MemoryAsyncOutputStream inner;\n  inner.writeShouldError = true;\n  auto fakeOwn = kj::Own<MemoryAsyncOutputStream>(&inner, kj::NullDisposer::instance);\n  auto sink = newWritableSink(kj::mv(fakeOwn));\n  KJ_ASSERT(sink->getEncoding() == rpc::StreamEncoding::IDENTITY);\n  KJ_ASSERT(sink->disownEncodingResponsibility() == rpc::StreamEncoding::IDENTITY);\n  KJ_ASSERT(sink->getEncoding() == rpc::StreamEncoding::IDENTITY);\n}\n\n// ======================================================================================\n// Encoding-Aware WritableSink Implementation\n\nKJ_TEST(\"Gzip-encoding sink\") {\n  auto ctx = kj::setupAsyncIo();\n  TestFixture fixture({\n    .waitScope = ctx.waitScope,\n  });\n  MemoryAsyncOutputStream inner;\n  auto fakeOwn = kj::Own<MemoryAsyncOutputStream>(&inner, kj::NullDisposer::instance);\n  auto sink = newEncodedWritableSink(rpc::StreamEncoding::GZIP, kj::mv(fakeOwn));\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await sink->write(\"some data to gzip\"_kjb);\n    co_await sink->end();\n\n    auto mem = newMemoryInputStream(inner.data);\n    kj::GzipAsyncInputStream gunzip(*mem);\n    auto data = co_await gunzip.readAllText(kj::maxValue);\n    KJ_ASSERT(data == \"some data to gzip\"_kj);\n  });\n\n  auto memInput = newMemoryInputStream(inner.data);\n  kj::GzipAsyncInputStream gunzip(*memInput);\n  auto decompressed = gunzip.readAllBytes().wait(ctx.waitScope);\n\n  KJ_ASSERT(decompressed.asBytes() == \"some data to gzip\"_kjb);\n}\n\nKJ_TEST(\"Gzip-encoding sink (identity)\") {\n  TestFixture fixture;\n  MemoryAsyncOutputStream inner;\n  auto fakeOwn = kj::Own<MemoryAsyncOutputStream>(&inner, kj::NullDisposer::instance);\n  auto sink = newEncodedWritableSink(rpc::StreamEncoding::GZIP, kj::mv(fakeOwn));\n\n  static const kj::byte check[] = {31, 139, 8, 0, 0, 0, 0, 0, 0, 3, 43, 206, 207, 77, 85, 72, 73,\n    44, 73, 84, 40, 201, 87, 72, 175, 202, 44, 0, 0, 40, 58, 113, 128, 17, 0, 0, 0};\n\n  // When encoding is disowned, the data should be passed through unmodified.\n  sink->disownEncodingResponsibility();\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await sink->write(check);\n    co_await sink->end();\n  });\n\n  KJ_ASSERT(inner.data == kj::arrayPtr(check, sizeof(check)));\n}\n\n// ======================================================================================\n// IoContext-aware WritableSinkWrapper Tests\n\nKJ_TEST(\"IoContext aware wrapper\") {\n  TestFixture fixture;\n  MemoryAsyncOutputStream inner;\n  auto fakeOwn = kj::Own<MemoryAsyncOutputStream>(&inner, kj::NullDisposer::instance);\n  auto sink = newWritableSink(kj::mv(fakeOwn));\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    IoContext& ioContext = environment.context;\n    auto wrapper = newIoContextWrappedWritableSink(ioContext, kj::mv(sink));\n    co_await wrapper->write(\"some data\"_kjb);\n    co_await wrapper->end();\n  });\n\n  KJ_ASSERT(inner.data == \"some data\"_kjb);\n}\n\n// ======================================================================================\n// EndableAsyncOutputStream Tests\n\nKJ_TEST(\"EndableAsyncOutputStream\") {\n  TestFixture fixture;\n  MockEndable inner;\n  auto fakeOwn = kj::Own<MockEndable>(&inner, kj::NullDisposer::instance);\n  auto sink = newWritableSink(kj::mv(fakeOwn));\n\n  fixture.runInIoContext([&](const auto& environment) -> kj::Promise<void> {\n    co_await sink->write(\"some data\"_kjb);\n    co_await sink->end();\n  });\n\n  KJ_ASSERT(inner.data == \"some data\"_kjb);\n  KJ_ASSERT(inner.isEnded);\n}\n\n}  // namespace\n}  // namespace workerd::api::streams\n"
  },
  {
    "path": "src/workerd/api/streams/writable-sink.c++",
    "content": "#include \"writable-sink.h\"\n\n#include <workerd/io/io-context.h>\n#include <workerd/util/state-machine.h>\n#include <workerd/util/stream-utils.h>\n\n#include <capnp/compat/byte-stream.h>\n#include <kj/async-io.h>\n#include <kj/compat/brotli.h>\n#include <kj/compat/gzip.h>\n\nnamespace workerd::api::streams {\n\nnamespace {\nstruct Closed {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"closed\"_kj;\n};\n\nstruct Open {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"open\"_kj;\n  kj::Own<kj::AsyncOutputStream> stream;\n};\n\n// State machine for tracking writable sink lifecycle:\n//   Open -> Closed (normal close via end())\n//   Open -> kj::Exception (error via abort() or write failure)\n// Closed is terminal, kj::Exception is implicitly terminal via ErrorState.\nusing WritableSinkState = StateMachine<TerminalStates<Closed>,\n    ErrorState<kj::Exception>,\n    ActiveState<Open>,\n    Open,\n    Closed,\n    kj::Exception>;\n\n// The base implementation of WritableSink. This is not exposed publicly.\nclass WritableSinkImpl: public WritableSink {\n public:\n  WritableSinkImpl(kj::Own<kj::AsyncOutputStream> inner,\n      rpc::StreamEncoding encoding = rpc::StreamEncoding::IDENTITY)\n      : state(WritableSinkState::create<Open>(kj::mv(inner))),\n        encoding(encoding) {}\n  WritableSinkImpl()\n      : state(WritableSinkState::create<Closed>()),\n        encoding(rpc::StreamEncoding::IDENTITY) {}\n  WritableSinkImpl(kj::Exception reason)\n      : state(WritableSinkState::create<kj::Exception>(kj::mv(reason))),\n        encoding(rpc::StreamEncoding::IDENTITY) {}\n\n  KJ_DISALLOW_COPY_AND_MOVE(WritableSinkImpl);\n\n  virtual ~WritableSinkImpl() noexcept(false) {\n    if (!canceler.isEmpty()) {\n      canceler.cancel(KJ_EXCEPTION(DISCONNECTED, \"stream was dropped\"));\n    }\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override final {\n    throwIfErrored();\n    KJ_IF_SOME(open, state.tryGetActiveUnsafe()) {\n      KJ_REQUIRE(canceler.isEmpty(), \"jsg.Error: Stream is already being written to\");\n      try {\n        co_return co_await canceler.wrap(encodeAndWrite(prepareWrite(kj::mv(open.stream)), buffer));\n      } catch (...) {\n        handleOperationException();\n      }\n    }\n    // Must be closed\n    JSG_FAIL_REQUIRE(Error, \"Cannot write to a closed stream.\");\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override final {\n    throwIfErrored();\n    KJ_IF_SOME(open, state.tryGetActiveUnsafe()) {\n      KJ_REQUIRE(canceler.isEmpty(), \"jsg.Error: Stream is already being written to\");\n      try {\n        co_return co_await canceler.wrap(encodeAndWrite(prepareWrite(kj::mv(open.stream)), pieces));\n      } catch (...) {\n        handleOperationException();\n      }\n    }\n    // Must be closed\n    JSG_FAIL_REQUIRE(Error, \"Cannot write to a closed stream.\");\n  }\n\n  kj::Promise<void> end() override final {\n    throwIfErrored();\n    if (state.is<Closed>()) {\n      co_return;\n    }\n    auto& open = state.requireActiveUnsafe();\n    KJ_REQUIRE(canceler.isEmpty(), \"jsg.Error: Stream is already being written to\");\n    // The AsyncOutputStream interface does not yet have an end() method.\n    // Instead, we just drop it, signaling EOF. Eventually, it might get\n    // an end method, at which point we should use that instead.\n    try {\n      co_await canceler.wrap(endImpl(*open.stream));\n      setClosed();\n      co_return;\n    } catch (...) {\n      handleOperationException();\n    }\n  }\n\n  void abort(kj::Exception reason) override final {\n    canceler.cancel(kj::cp(reason));\n    setErrored(kj::mv(reason));\n  }\n\n  rpc::StreamEncoding disownEncodingResponsibility() override final {\n    auto prev = encoding;\n    encoding = rpc::StreamEncoding::IDENTITY;\n    return prev;\n  }\n\n  rpc::StreamEncoding getEncoding() override final {\n    return encoding;\n  }\n\n protected:\n  // Throws the stored exception if in error state.\n  void throwIfErrored() {\n    KJ_IF_SOME(exception, state.tryGetErrorUnsafe()) {\n      kj::throwFatalException(kj::cp(exception));\n    }\n  }\n\n  // Handles exceptions from write/end operations: stores the error and rethrows.\n  [[noreturn]] void handleOperationException() {\n    auto exception = kj::getCaughtExceptionAsKj();\n    setErrored(kj::cp(exception));\n    kj::throwFatalException(kj::mv(exception));\n  }\n\n  virtual kj::AsyncOutputStream& prepareWrite(kj::Own<kj::AsyncOutputStream>&& inner) {\n    return setStream(kj::mv(inner));\n  };\n\n  virtual kj::Promise<void> encodeAndWrite(\n      kj::AsyncOutputStream& output, kj::ArrayPtr<const kj::byte> data) {\n    co_await output.write(data);\n  }\n\n  virtual kj::Promise<void> encodeAndWrite(\n      kj::AsyncOutputStream& output, kj::ArrayPtr<const kj::ArrayPtr<const kj::byte>> pieces) {\n    co_await output.write(pieces);\n  }\n\n  virtual kj::Promise<void> endImpl(kj::AsyncOutputStream& output) {\n    // When using the default implementation, we assume IDENTITY encoding.\n    KJ_ASSERT(encoding == rpc::StreamEncoding::IDENTITY);\n    if (auto endable = dynamic_cast<EndableAsyncOutputStream*>(&output)) {\n      co_await endable->end();\n    } else if (auto endable = dynamic_cast<capnp::ExplicitEndOutputStream*>(&output)) {\n      co_await endable->end();\n    }\n    // By default there's nothing to flush.\n    co_return;\n  }\n\n  void setClosed() {\n    state.transitionTo<Closed>();\n  }\n\n  void setErrored(kj::Exception&& ex) {\n    // Use forceTransitionTo because setErrored may be called when already\n    // in an error state (e.g., from write error handling).\n    state.forceTransitionTo<kj::Exception>(kj::mv(ex));\n  }\n\n  kj::AsyncOutputStream& setStream(kj::Own<kj::AsyncOutputStream> inner) {\n    auto& ret = *inner;\n    // Update the stream in place without a state transition.\n    // This is called from prepareWrite() which may wrap/transform the stream.\n    state.getUnsafe<Open>().stream = kj::mv(inner);\n    return ret;\n  }\n\n  WritableSinkState& getState() {\n    return state;\n  }\n\n private:\n  WritableSinkState state;\n  rpc::StreamEncoding encoding;\n  kj::Canceler canceler;\n};\n\n// A wrapper around a native `kj::AsyncOutputStream` which knows the underlying encoding of the\n// stream and optimizes pumps from `EncodedAsyncInputStream`.\n//\n// The inner will be held on to right up until either end() or abort() is called.\n// This is important because some AsyncOutputStream implementations perform cleanup\n// operations equivalent to end() in their destructors (for instance HttpChunkedEntityWriter).\n// If we wait to clear the kj::Own when the EncodedAsyncOutputStream is destroyed, and the\n// EncodedAsyncOutputStream is owned (for instance) by an IoOwn, then the lifetime of the\n// inner may be extended past when it should. Eventually, kj::AsyncOutputStream should\n// probably have a distinct end() method of its own that we can defer to, but until it\n// does, it is important for us to release it as soon as end() or abort() are called.\nclass EncodedAsyncOutputStream final: public WritableSinkImpl {\n public:\n  explicit EncodedAsyncOutputStream(\n      kj::Own<kj::AsyncOutputStream> inner, rpc::StreamEncoding encoding)\n      : WritableSinkImpl(kj::mv(inner), encoding) {}\n\n  kj::Promise<void> endImpl(kj::AsyncOutputStream& output) override {\n    if (auto gzip = dynamic_cast<kj::GzipAsyncOutputStream*>(&output)) {\n      co_await gzip->end();\n    } else if (auto br = dynamic_cast<kj::BrotliAsyncOutputStream*>(&output)) {\n      co_await br->end();\n    } else if (auto endable = dynamic_cast<EndableAsyncOutputStream*>(&output)) {\n      co_await endable->end();\n    } else if (auto endable = dynamic_cast<capnp::ExplicitEndOutputStream*>(&output)) {\n      co_await endable->end();\n    }\n    // By default there's nothing to flush.\n  }\n\n  kj::AsyncOutputStream& prepareWrite(kj::Own<kj::AsyncOutputStream>&& inner) override {\n    switch (disownEncodingResponsibility()) {\n      case rpc::StreamEncoding::GZIP: {\n        return setStream(kj::heap<kj::GzipAsyncOutputStream>(*inner).attach(kj::mv(inner)));\n      }\n      case rpc::StreamEncoding::BROTLI: {\n        return setStream(kj::heap<kj::BrotliAsyncOutputStream>(*inner).attach(kj::mv(inner)));\n      }\n      case rpc::StreamEncoding::IDENTITY: {\n        return setStream(kj::mv(inner));\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n};\n\n// A wrapper around a WritableSink that registers pending events with an IoContext.\nclass IoContextWritableSinkWrapper: public WritableSinkWrapper {\n public:\n  IoContextWritableSinkWrapper(IoContext& ioContext, kj::Own<WritableSink> inner)\n      : WritableSinkWrapper(kj::mv(inner)),\n        ioContext(ioContext) {}\n\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override {\n    auto pending = ioContext.registerPendingEvent();\n    KJ_IF_SOME(p, ioContext.waitForOutputLocksIfNecessary()) {\n      co_await p;\n    }\n    co_await getInner().write(buffer);\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {\n    auto pending = ioContext.registerPendingEvent();\n    KJ_IF_SOME(p, ioContext.waitForOutputLocksIfNecessary()) {\n      co_await p;\n    }\n    co_await getInner().write(pieces);\n  }\n\n  kj::Promise<void> end() override {\n    auto pending = ioContext.registerPendingEvent();\n    KJ_IF_SOME(p, ioContext.waitForOutputLocksIfNecessary()) {\n      co_await p;\n    }\n    co_await getInner().end();\n  }\n\n private:\n  IoContext& ioContext;\n};\n}  // namespace\n\nkj::Own<WritableSink> newWritableSink(kj::Own<kj::AsyncOutputStream> inner) {\n  return kj::heap<WritableSinkImpl>(kj::mv(inner));\n}\n\nkj::Own<WritableSink> newClosedWritableSink() {\n  return kj::heap<WritableSinkImpl>();\n}\n\nkj::Own<WritableSink> newErroredWritableSink(kj::Exception reason) {\n  return kj::heap<WritableSinkImpl>(kj::mv(reason));\n}\n\nkj::Own<WritableSink> newNullWritableSink() {\n  return kj::heap<WritableSinkImpl>(newNullOutputStream());\n}\n\nkj::Own<WritableSink> newEncodedWritableSink(\n    rpc::StreamEncoding encoding, kj::Own<kj::AsyncOutputStream> inner) {\n  return kj::heap<EncodedAsyncOutputStream>(kj::mv(inner), encoding);\n}\n\nkj::Own<WritableSink> newIoContextWrappedWritableSink(\n    IoContext& ioContext, kj::Own<WritableSink> inner) {\n  return kj::heap<IoContextWritableSinkWrapper>(ioContext, kj::mv(inner));\n}\n\n}  // namespace workerd::api::streams\n"
  },
  {
    "path": "src/workerd/api/streams/writable-sink.h",
    "content": "#pragma once\n\n#include <workerd/io/worker-interface.capnp.h>\n\n#include <kj/debug.h>\n\nnamespace kj {\nclass AsyncOutputStream;\n}\n\nnamespace workerd {\n\nclass IoContext;\n\nnamespace api::streams {\n\n// A WritableSink is primarily intended to serve as a bridge between kj::AsyncOutputStream\n// and the WritableStream API. However, it can also be used directly by KJ-space code. While\n// WritableSink should probably have been a more JS-friendly API, it's a bit too late\n// to change that now. Use the WritableSinkJsAdapter in the writable-sink-adapter.h file\n// to wrap a WritableSink for use from JavaScript.\n//\n// Not all WritableSink implementations will be explicitly backed by a KJ stream;\n// some might be test implementations that discard data or accumulate it in memory, for\n// instance.\n//\n// A WritableSink must be treated like a KJ I/O object. Instances that are held\n// by any JS-heap objects must be held by an IoOwn.\n//\n// The sink permits only one write() or end() operation to be pending at a time. If\n// a second write() or end() is attempted while one is already pending, the promise\n// returned by the second call will be rejected with a jsg::Error. This is to\n// match the behavior of the kj::AsyncOutputStream interface.\n//\n// If the sink is aborted or dropped, any pending write() or end() operations will be\n// canceled.\nclass WritableSink {\n public:\n  // Write the given buffer to the stream, returning a promise that resolves when the write\n  // completes.\n  virtual kj::Promise<void> write(kj::ArrayPtr<const kj::byte> buffer) KJ_WARN_UNUSED_RESULT = 0;\n\n  // Write the given pieces to the stream, returning a promise that resolves when the write\n  // completes.\n  virtual kj::Promise<void> write(\n      kj::ArrayPtr<const kj::ArrayPtr<const kj::byte>> pieces) KJ_WARN_UNUSED_RESULT = 0;\n\n  // Ends the stream, transitioning it to the closed state. After this, no further writes\n  // will be accepted.\n  virtual kj::Promise<void> end() KJ_WARN_UNUSED_RESULT = 0;\n\n  // Aborts the stream, transitioning it to the errored state. After this, no further writes\n  // will be accepted.\n  virtual void abort(kj::Exception reason) = 0;\n\n  // Tells the sink that it is no longer to be responsible for encoding in the correct format.\n  // Instead, the caller takes responsibility. The expected encoding is returned; the caller\n  // promises that all future writes will use this encoding.\n  virtual rpc::StreamEncoding disownEncodingResponsibility() = 0;\n\n  // Return the encoding that this sink is using.\n  virtual rpc::StreamEncoding getEncoding() = 0;\n};\n\n// Utility base class for WritableSink wrappers that delegate all\n// operations to an inner WritableSink while selectively overriding\n// some operations.\nclass WritableSinkWrapper: public WritableSink {\n public:\n  virtual ~WritableSinkWrapper() noexcept(false) {\n    canceler.cancel(KJ_EXCEPTION(DISCONNECTED, \"Dropped\"));\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::byte> buffer) override {\n    return getInner().write(buffer);\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const kj::byte>> pieces) override {\n    return getInner().write(pieces);\n  }\n\n  kj::Promise<void> end() override {\n    return getInner().end();\n  }\n\n  void abort(kj::Exception reason) override {\n    getInner().abort(kj::mv(reason));\n  }\n\n  rpc::StreamEncoding disownEncodingResponsibility() override {\n    return getInner().disownEncodingResponsibility();\n  }\n\n  rpc::StreamEncoding getEncoding() override {\n    return getInner().getEncoding();\n  }\n\n  // Releases ownership of the inner WritableSink. After calling this,\n  // this instance is no longer usable.\n  kj::Own<WritableSink> release() {\n    auto ret = kj::mv(KJ_ASSERT_NONNULL(inner));\n    inner = kj::none;\n    canceler.cancel(KJ_EXCEPTION(DISCONNECTED, \"Released\"));\n    return kj::mv(ret);\n  }\n\n protected:\n  WritableSinkWrapper(kj::Own<WritableSink> inner): inner(kj::mv(inner)) {}\n  KJ_DISALLOW_COPY_AND_MOVE(WritableSinkWrapper);\n\n  WritableSink& getInner() {\n    return *KJ_ASSERT_NONNULL(inner);\n  }\n\n private:\n  kj::Canceler canceler;\n  kj::Maybe<kj::Own<WritableSink>> inner;\n};\n\n// Creates a WritableSink that wraps a kj::AsyncOutputStream.\nkj::Own<WritableSink> newWritableSink(kj::Own<kj::AsyncOutputStream> inner);\n\n// Creates a WritableSink that is in the closed state.\nkj::Own<WritableSink> newClosedWritableSink();\n\n// Creates a WritableSink that is permanently in the errored state.\nkj::Own<WritableSink> newErroredWritableSink(kj::Exception reason);\n\n// Creates a WritableSink that discards all data written to it.\nkj::Own<WritableSink> newNullWritableSink();\n\n// Creates a WritableSink that encodes data written to it.\nkj::Own<WritableSink> newEncodedWritableSink(\n    rpc::StreamEncoding encoding, kj::Own<kj::AsyncOutputStream> inner);\n\n// Wraps a WritableSink such that each write()/end() call on the returned sink will\n// register as a pending event on the IoContext.\nkj::Own<WritableSink> newIoContextWrappedWritableSink(\n    IoContext& ioContext, kj::Own<WritableSink> inner);\n\n}  // namespace api::streams\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/api/streams/writable.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"writable.h\"\n\n#include <workerd/api/system-streams.h>\n#include <workerd/api/worker-rpc.h>\n#include <workerd/io/features.h>\n\nnamespace workerd::api {\n\nWritableStreamDefaultWriter::WritableStreamDefaultWriter()\n    : ioContext(tryGetIoContext()),\n      state(WriterState::create<Initial>()) {}\n\nWritableStreamDefaultWriter::~WritableStreamDefaultWriter() noexcept(false) {\n  KJ_IF_SOME(attached, state.tryGetActiveUnsafe()) {\n    attached.stream->getController().releaseWriter(*this, kj::none);\n  }\n}\n\njsg::Ref<WritableStreamDefaultWriter> WritableStreamDefaultWriter::constructor(\n    jsg::Lock& js, jsg::Ref<WritableStream> stream) {\n  JSG_REQUIRE(\n      !stream->isLocked(), TypeError, \"This WritableStream is currently locked to a writer.\");\n  auto writer = js.alloc<WritableStreamDefaultWriter>();\n  writer->lockToStream(js, *stream);\n  return kj::mv(writer);\n}\n\njsg::Promise<void> WritableStreamDefaultWriter::abort(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> reason) {\n  assertAttachedOrTerminal();\n  if (state.is<Released>()) {\n    return js.rejectedPromise<void>(\n        js.v8TypeError(\"This WritableStream writer has been released.\"_kj));\n  }\n  if (state.is<Closed>()) {\n    return js.resolvedPromise();\n  }\n  auto& attached = state.requireActiveUnsafe();\n  // In some edge cases, this writer is the last thing holding a strong\n  // reference to the stream. Calling abort can cause the writers strong\n  // reference to be cleared, so let's make sure we keep a reference to\n  // the stream at least until the call to abort completes.\n  auto ref = attached.stream.addRef();\n  return attached.stream->getController().abort(js, reason);\n}\n\nvoid WritableStreamDefaultWriter::attach(jsg::Lock& js,\n    WritableStreamController& controller,\n    jsg::Promise<void> closedPromise,\n    jsg::Promise<void> readyPromise) {\n  KJ_ASSERT(state.is<Initial>());\n  state.transitionTo<Attached>(controller.addRef());\n  this->closedPromise = kj::mv(closedPromise);\n  replaceReadyPromise(js, kj::mv(readyPromise));\n}\n\njsg::Promise<void> WritableStreamDefaultWriter::close(jsg::Lock& js) {\n  assertAttachedOrTerminal();\n  if (state.is<Released>()) {\n    return js.rejectedPromise<void>(\n        js.v8TypeError(\"This WritableStream writer has been released.\"_kj));\n  }\n  if (state.is<Closed>()) {\n    return js.rejectedPromise<void>(js.v8TypeError(\"This WritableStream has been closed.\"_kj));\n  }\n  auto& attached = state.requireActiveUnsafe();\n  // In some edge cases, this writer is the last thing holding a strong\n  // reference to the stream. Calling close can cause the writers strong\n  // reference to be cleared, so let's make sure we keep a reference to\n  // the stream at least until the call to close completes.\n  auto ref = attached.stream.addRef();\n  return attached.stream->getController().close(js);\n}\n\nvoid WritableStreamDefaultWriter::detach() {\n  // Only transition from Attached to Closed.\n  // All other states (Initial, Closed, Released) are no-ops.\n  if (state.isActive()) {\n    state.transitionTo<Closed>();\n  }\n}\n\njsg::MemoizedIdentity<jsg::Promise<void>>& WritableStreamDefaultWriter::getClosed() {\n  return KJ_ASSERT_NONNULL(closedPromise, \"the writer was never attached to a stream\");\n}\n\nkj::Maybe<int> WritableStreamDefaultWriter::getDesiredSize() {\n  assertAttachedOrTerminal();\n  if (state.is<Released>()) {\n    JSG_FAIL_REQUIRE(TypeError, \"This WritableStream writer has been released.\");\n  }\n  if (state.is<Closed>()) {\n    return 0;\n  }\n  auto& attached = state.requireActiveUnsafe();\n  return attached.stream->getController().getDesiredSize();\n}\n\njsg::MemoizedIdentity<jsg::Promise<void>>& WritableStreamDefaultWriter::getReady() {\n  return KJ_ASSERT_NONNULL(readyPromise, \"the writer was never attached to a stream\");\n}\n\nkj::Maybe<jsg::Promise<void>> WritableStreamDefaultWriter::isReady(jsg::Lock& js) {\n  return readyPromisePending.map([&](jsg::Promise<void>& p) { return p.whenResolved(js); });\n}\n\nvoid WritableStreamDefaultWriter::lockToStream(jsg::Lock& js, WritableStream& stream) {\n  KJ_ASSERT(!stream.isLocked());\n  KJ_ASSERT(stream.getController().lockWriter(js, *this));\n}\n\nvoid WritableStreamDefaultWriter::releaseLock(jsg::Lock& js) {\n  // TODO(soon): Releasing the lock should cancel any pending writes.\n  assertAttachedOrTerminal();\n  // Closed and Released states are no-ops.\n  KJ_IF_SOME(attached, state.tryGetActiveUnsafe()) {\n    // In some edge cases, this writer is the last thing holding a strong\n    // reference to the stream. Calling releaseWriter can cause the writers\n    // strong reference to be cleared, so let's make sure we keep a reference\n    // to the stream at least until the call to releaseLock completes.\n    auto ref = attached.stream.addRef();\n    attached.stream->getController().releaseWriter(*this, js);\n    state.transitionTo<Released>();\n  }\n}\n\nvoid WritableStreamDefaultWriter::replaceReadyPromise(\n    jsg::Lock& js, jsg::Promise<void> readyPromise) {\n  this->readyPromisePending = kj::mv(readyPromise);\n  this->readyPromise = KJ_ASSERT_NONNULL(this->readyPromisePending).whenResolved(js);\n}\n\njsg::Promise<void> WritableStreamDefaultWriter::write(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> chunk) {\n  assertAttachedOrTerminal();\n  if (state.is<Released>()) {\n    return js.rejectedPromise<void>(\n        js.v8TypeError(\"This WritableStream writer has been released.\"_kj));\n  }\n  if (state.is<Closed>()) {\n    return js.rejectedPromise<void>(js.v8TypeError(\"This WritableStream has been closed.\"_kj));\n  }\n  auto& attached = state.requireActiveUnsafe();\n  return attached.stream->getController().write(js, chunk);\n}\n\njsg::JsString WritableStream::inspectState(jsg::Lock& js) {\n  if (controller->isErrored()) {\n    return js.strIntern(\"errored\");\n  } else if (controller->isErroring(js) != kj::none) {\n    return js.strIntern(\"erroring\");\n  } else if (controller->isClosedOrClosing()) {\n    return js.strIntern(\"closed\");\n  } else {\n    return js.strIntern(\"writable\");\n  }\n}\n\nbool WritableStream::inspectExpectsBytes() {\n  return controller->isByteOriented();\n}\n\nvoid WritableStreamDefaultWriter::visitForGc(jsg::GcVisitor& visitor) {\n  KJ_IF_SOME(attached, state.tryGetActiveUnsafe()) {\n    visitor.visit(attached.stream);\n  }\n  visitor.visit(closedPromise, readyPromise);\n}\n\n// ======================================================================================\n\nWritableStream::WritableStream(IoContext& ioContext,\n    kj::Own<WritableStreamSink> sink,\n    kj::Maybe<kj::Own<ByteStreamObserver>> maybeObserver,\n    kj::Maybe<uint64_t> maybeHighWaterMark,\n    kj::Maybe<jsg::Promise<void>> maybeClosureWaitable)\n    : WritableStream(newWritableStreamInternalController(ioContext,\n          kj::mv(sink),\n          kj::mv(maybeObserver),\n          maybeHighWaterMark,\n          kj::mv(maybeClosureWaitable))) {}\n\nWritableStream::WritableStream(kj::Own<WritableStreamController> controller)\n    : ioContext(tryGetIoContext()),\n      controller(kj::mv(controller)) {\n  getController().setOwnerRef(*this);\n}\n\njsg::Ref<WritableStream> WritableStream::addRef() {\n  return JSG_THIS;\n}\n\nvoid WritableStream::visitForGc(jsg::GcVisitor& visitor) {\n  visitor.visit(getController());\n}\n\nbool WritableStream::isLocked() {\n  return getController().isLockedToWriter();\n}\n\nWritableStreamController& WritableStream::getController() {\n  return *controller;\n}\n\nkj::Own<WritableStreamSink> WritableStream::removeSink(jsg::Lock& js) {\n  return JSG_REQUIRE_NONNULL(getController().removeSink(js), TypeError,\n      \"This WritableStream does not have a WritableStreamSink\");\n}\n\nvoid WritableStream::detach(jsg::Lock& js) {\n  getController().detach(js);\n}\n\njsg::Promise<void> WritableStream::abort(\n    jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> reason) {\n  if (isLocked()) {\n    return js.rejectedPromise<void>(\n        js.v8TypeError(\"This WritableStream is currently locked to a writer.\"_kj));\n  }\n  return getController().abort(js, reason);\n}\n\njsg::Promise<void> WritableStream::close(jsg::Lock& js) {\n  if (isLocked()) {\n    return js.rejectedPromise<void>(\n        js.v8TypeError(\"This WritableStream is currently locked to a writer.\"_kj));\n  }\n  return getController().close(js);\n}\n\njsg::Promise<void> WritableStream::flush(jsg::Lock& js) {\n  if (isLocked()) {\n    return js.rejectedPromise<void>(\n        js.v8TypeError(\"This WritableStream is currently locked to a writer.\"_kj));\n  }\n  return getController().flush(js);\n}\n\njsg::Ref<WritableStreamDefaultWriter> WritableStream::getWriter(jsg::Lock& js) {\n  return WritableStreamDefaultWriter::constructor(js, JSG_THIS);\n}\n\njsg::Ref<WritableStream> WritableStream::constructor(jsg::Lock& js,\n    jsg::Optional<UnderlyingSink> underlyingSink,\n    jsg::Optional<StreamQueuingStrategy> queuingStrategy) {\n  JSG_REQUIRE(FeatureFlags::get(js).getStreamsJavaScriptControllers(), Error,\n      \"To use the new WritableStream() constructor, enable the \"\n      \"streams_enable_constructors compatibility flag. \"\n      \"Refer to the docs for more information: https://developers.cloudflare.com/workers/platform/compatibility-dates/#compatibility-flags\");\n  auto controller = newWritableStreamJsController();\n  // We account for the memory usage of the WritableStream and its controller together because their\n  // lifetimes are identical and memory accounting itself has a memory overhead.\n  auto stream = js.allocAccounted<WritableStream>(\n      sizeof(WritableStream) + controller->jsgGetMemorySelfSize(), kj::mv(controller));\n  stream->getController().setup(js, kj::mv(underlyingSink), kj::mv(queuingStrategy));\n  return kj::mv(stream);\n}\n\nnamespace {\n\n// Wrapper around `WritableStreamSink` that makes it suitable for passing off to capnp RPC.\nclass WritableStreamRpcAdapter final: public capnp::ExplicitEndOutputStream {\n public:\n  WritableStreamRpcAdapter(kj::Own<WritableStreamSink> inner): inner(kj::mv(inner)) {}\n  ~WritableStreamRpcAdapter() noexcept(false) {\n    weakRef->invalidate();\n    doneFulfiller->fulfill();\n  }\n\n  // Returns a promise that resolves when the stream is dropped. If the promise is canceled before\n  // that, the stream is revoked.\n  kj::Promise<void> waitForCompletionOrRevoke() {\n    auto paf = kj::newPromiseAndFulfiller<void>();\n    doneFulfiller = kj::mv(paf.fulfiller);\n\n    return paf.promise.attach(kj::defer([weakRef = weakRef->addRef()]() mutable {\n      KJ_IF_SOME(obj, weakRef->tryGet()) {\n        // Stream is still alive, revoke it.\n        if (!obj.canceler.isEmpty()) {\n          obj.canceler.cancel(cancellationException());\n        }\n        obj.inner = kj::none;\n      }\n    }));\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override {\n    return canceler.wrap(getInner().write(buffer));\n  }\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {\n    return canceler.wrap(getInner().write(pieces));\n  }\n\n  // TODO(perf): We can't properly implement tryPumpFrom(), which means that Cap'n Proto will\n  //   be unable to perform path shortening if the underlying stream turns out to be another capnp\n  //   stream. This isn't a huge deal, but might be nice to enable someday. It may require\n  //   significant refactoring of streams.\n\n  kj::Promise<void> whenWriteDisconnected() override {\n    // TODO(someday): WritableStreamSink doesn't give us a way to implement this.\n    return kj::NEVER_DONE;\n  }\n\n  kj::Promise<void> end() override {\n    return canceler.wrap(getInner().end());\n  }\n\n private:\n  kj::Maybe<kj::Own<WritableStreamSink>> inner;\n  kj::Canceler canceler;\n  kj::Own<kj::PromiseFulfiller<void>> doneFulfiller;\n  kj::Own<WeakRef<WritableStreamRpcAdapter>> weakRef =\n      kj::refcounted<WeakRef<WritableStreamRpcAdapter>>(\n          kj::Badge<WritableStreamRpcAdapter>(), *this);\n\n  WritableStreamSink& getInner() {\n    return *KJ_UNWRAP_OR(inner, { kj::throwFatalException(cancellationException()); });\n  }\n\n  static kj::Exception cancellationException() {\n    return JSG_KJ_EXCEPTION(DISCONNECTED, Error,\n        \"WritableStream received over RPC was disconnected because the remote execution context \"\n        \"has endeded.\");\n  }\n};\n\n// In order to support JavaScript-backed WritableStreams that do not have a backing\n// WritableStreamSink, we need an alternative version of the WritableStreamRpcAdapter\n// that will arrange to acquire the isolate lock when necessary to perform writes\n// directly on the WritableStreamController. Note that this approach is necessarily\n// a lot slower\nclass WritableStreamJsRpcAdapter final: public capnp::ExplicitEndOutputStream {\n public:\n  WritableStreamJsRpcAdapter(IoContext& context, jsg::Ref<WritableStreamDefaultWriter> writer)\n      : context(context),\n        writer(kj::mv(writer)) {}\n\n  ~WritableStreamJsRpcAdapter() noexcept(false) {\n    weakRef->invalidate();\n    doneFulfiller->fulfill();\n\n    // If the stream was not explicitly ended and the writer still exists at this point,\n    // then we should trigger calling the abort algorithm on the stream. Sadly, there's a\n    // bit of an incompatibility with kj::AsyncOutputStream and the standard definition of\n    // WritableStream in that AsyncOutputStream has no specific way to explicitly signal that\n    // the stream is being aborted due to a particular reason.\n    //\n    // On the remote side, because it is using a WritableStreamSink implementation, when that\n    // side is aborted, all it does is record the reason and drop the stream. It does not\n    // propagate the reason back to this side. So, we have to do the best we can here. Our\n    // assumption is that once the stream is dropped, if it has not been explicitly ended and\n    // the writer still exists, then the writer should be aborted. This is not perfect because\n    // we cannot propagate the actual reason why it was aborted.\n    //\n    // Note also that there is no guarantee that the abort will actually run if the context\n    // is being torn down. Some WritableStream implementations might use the abort algorithm\n    // to clean things up or perform logging in the case of an error. Care needs to be taken\n    // in this situation or the user code might end up with bugs. Need to see if there's a\n    // better solution.\n    //\n    // TODO(someday): If the remote end can be updated to propagate the abort, then we can\n    // hopefully improve the situation here.\n    if (!ended) {\n      KJ_IF_SOME(writer, this->writer) {\n        context.addTask(context.run([writer = kj::mv(writer), exception = cancellationException()](\n                                        Worker::Lock& lock) mutable {\n          jsg::Lock& js = lock;\n          auto ex = js.exceptionToJs(kj::mv(exception));\n          return IoContext::current().awaitJs(lock, writer->abort(lock, ex.getHandle(js)));\n        }));\n      }\n    }\n  }\n\n  // Returns a promise that resolves when the stream is dropped. If the promise is canceled before\n  // that, the stream is revoked.\n  kj::Promise<void> waitForCompletionOrRevoke() {\n    auto paf = kj::newPromiseAndFulfiller<void>();\n    doneFulfiller = kj::mv(paf.fulfiller);\n\n    return paf.promise.attach(kj::defer([weakRef = weakRef->addRef()]() mutable {\n      KJ_IF_SOME(obj, weakRef->tryGet()) {\n        // Stream is still alive, revoke it.\n        if (!obj.canceler.isEmpty()) {\n          obj.canceler.cancel(cancellationException());\n        }\n        auto w = kj::mv(obj.writer);\n        KJ_IF_SOME(writer, w) {\n          obj.context.addTask(\n              obj.context.run([writer = kj::mv(writer), exception = cancellationException()](\n                                  Worker::Lock& lock) mutable {\n            jsg::Lock& js = lock;\n            auto ex = js.exceptionToJs(kj::mv(exception));\n            return IoContext::current().awaitJs(lock, writer->abort(lock, ex.getHandle(js)));\n          }));\n        }\n      }\n    }));\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override {\n    if (writer == kj::none) {\n      return KJ_EXCEPTION(FAILED, \"Write after stream has been closed.\");\n    }\n    if (buffer == nullptr) return kj::READY_NOW;\n    return canceler.wrap(context.run([this, buffer](Worker::Lock& lock) mutable {\n      auto& writer = getInner();\n      auto source = KJ_ASSERT_NONNULL(jsg::BufferSource::tryAlloc(lock, buffer.size()));\n      source.asArrayPtr().copyFrom(buffer);\n      return context.awaitJs(lock, writer.write(lock, source.getHandle(lock)));\n    }));\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {\n    if (writer == kj::none) {\n      return KJ_EXCEPTION(FAILED, \"Write after stream has been closed.\");\n    }\n    auto amount = 0;\n    for (auto& piece: pieces) {\n      amount += piece.size();\n    }\n    if (amount == 0) return kj::READY_NOW;\n    return canceler.wrap(context.run([this, amount, pieces](Worker::Lock& lock) mutable {\n      auto& writer = getInner();\n      // Sadly, we have to allocate and copy here. Our received set of buffers are only\n      // guaranteed to live until the returned promise is resolved, but the application code\n      // may hold onto the ArrayBuffer for longer. We need to make sure that the backing store\n      // for the ArrayBuffer remains valid.\n      auto source = KJ_ASSERT_NONNULL(jsg::BufferSource::tryAlloc(lock, amount));\n      auto ptr = source.asArrayPtr();\n      for (auto& piece: pieces) {\n        KJ_DASSERT(ptr.size() > 0);\n        KJ_DASSERT(piece.size() <= ptr.size());\n        if (piece.size() == 0) continue;\n        ptr.first(piece.size()).copyFrom(piece);\n        ptr = ptr.slice(piece.size());\n      }\n\n      return context.awaitJs(lock, writer.write(lock, source.getHandle(lock)));\n    }));\n  }\n\n  // TODO(perf): We can't properly implement tryPumpFrom(), which means that Cap'n Proto will\n  //   be unable to perform path shortening if the underlying stream turns out to be another capnp\n  //   stream. This isn't a huge deal, but might be nice to enable someday. It may require\n  //   significant refactoring of streams.\n\n  kj::Promise<void> whenWriteDisconnected() override {\n    // TODO(soon): We might be able to support this by following the writer.closed promise,\n    // which becomes resolved when the writer is used to close the stream, or rejects when\n    // the stream has errored. However, currently, we don't have an easy way to do this.\n    //\n    // The Writer's getClosed() method returns a jsg::MemoizedIdentity<jsg::Promise<void>>.\n    // jsg::MemoizedIdentity lazily converts the jsg::Promise into a v8::Promise once it\n    // passes through the type wrapper. It does not give us any way to consistently get\n    // at the underlying jsg::Promise<void> or the mapped v8::Promise. We would need to\n    // capture a TypeHandler in here and convert each time to one or the other, then\n    // attach our continuation. It's doable but a bit of a pain.\n    //\n    // For now, let's handle this the same as WritableStreamRpcAdapter and just return a\n    // never done.\n    return kj::NEVER_DONE;\n  }\n\n  kj::Promise<void> end() override {\n    if (writer == kj::none) {\n      return KJ_EXCEPTION(FAILED, \"End after stream has been closed.\");\n    }\n    ended = true;\n    return canceler.wrap(context.run([this](Worker::Lock& lock) mutable {\n      return context.awaitJs(lock, getInner().close(lock));\n    }));\n  }\n\n private:\n  IoContext& context;\n  kj::Maybe<jsg::Ref<WritableStreamDefaultWriter>> writer;\n  kj::Canceler canceler;\n  kj::Own<kj::PromiseFulfiller<void>> doneFulfiller;\n  kj::Own<WeakRef<WritableStreamJsRpcAdapter>> weakRef =\n      kj::refcounted<WeakRef<WritableStreamJsRpcAdapter>>(\n          kj::Badge<WritableStreamJsRpcAdapter>(), *this);\n  bool ended = false;\n\n  WritableStreamDefaultWriter& getInner() {\n    KJ_IF_SOME(inner, writer) {\n      return *inner;\n    }\n    kj::throwFatalException(cancellationException());\n  }\n\n  static kj::Exception cancellationException() {\n    return JSG_KJ_EXCEPTION(DISCONNECTED, Error,\n        \"WritableStream received over RPC was disconnected because the remote execution context \"\n        \"has endeded.\");\n  }\n};\n\n}  // namespace\n\nvoid WritableStream::serialize(jsg::Lock& js, jsg::Serializer& serializer) {\n  // Serialize by effectively creating a `JsRpcStub` around this object and serializing that.\n  // Except we don't actually want to do _exactly_ that, because we do not want to actually create\n  // a `JsRpcStub` locally. So do the important parts of `JsRpcStub::constructor()` followed by\n  // `JsRpcStub::serialize()`.\n\n  auto& handler = JSG_REQUIRE_NONNULL(serializer.getExternalHandler(), DOMDataCloneError,\n      \"WritableStream can only be serialized for RPC.\");\n  auto externalHandler = dynamic_cast<RpcSerializerExternalHandler*>(&handler);\n  JSG_REQUIRE(externalHandler != nullptr, DOMDataCloneError,\n      \"WritableStream can only be serialized for RPC.\");\n\n  IoContext& ioctx = IoContext::current();\n\n  // TODO(soon): Support JS-backed WritableStreams. Currently this only supports native streams\n  //   and IdentityTransformStream, since only they are backed by WritableStreamSink.\n\n  KJ_IF_SOME(sink, getController().removeSink(js)) {\n    // NOTE: We're counting on `removeSink()`, to check that the stream is not locked and other\n    //   common checks. It's important we don't modify the WritableStream before this call.\n    auto encoding = sink->disownEncodingResponsibility();\n    auto wrapper = kj::heap<WritableStreamRpcAdapter>(kj::mv(sink));\n\n    // Make sure this stream will be revoked if the IoContext ends.\n    ioctx.addTask(wrapper->waitForCompletionOrRevoke().attach(ioctx.registerPendingEvent()));\n\n    auto capnpStream = ioctx.getByteStreamFactory().kjToCapnp(kj::mv(wrapper));\n\n    externalHandler->write([capnpStream = kj::mv(capnpStream), encoding](\n                               rpc::JsValue::External::Builder builder) mutable {\n      auto ws = builder.initWritableStream();\n      ws.setByteStream(kj::mv(capnpStream));\n      ws.setEncoding(encoding);\n    });\n  } else {\n    // TODO(soon): Support disownEncodingResponsibility with JS-backed streams\n\n    // NOTE: We're counting on `getWriter()` to check that the stream is not locked and other\n    // common checks. It's important we don't modify the WritableStream before this call.\n    auto wrapper = kj::heap<WritableStreamJsRpcAdapter>(ioctx, getWriter(js));\n\n    // Make sure this stream will be revoked if the IoContext ends.\n    ioctx.addTask(wrapper->waitForCompletionOrRevoke().attach(ioctx.registerPendingEvent()));\n\n    auto capnpStream = ioctx.getByteStreamFactory().kjToCapnp(kj::mv(wrapper));\n\n    externalHandler->write(\n        [capnpStream = kj::mv(capnpStream)](rpc::JsValue::External::Builder builder) mutable {\n      auto ws = builder.initWritableStream();\n      ws.setByteStream(kj::mv(capnpStream));\n      ws.setEncoding(StreamEncoding::IDENTITY);\n    });\n  }\n}\n\njsg::Ref<WritableStream> WritableStream::deserialize(\n    jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer) {\n  auto& handler = KJ_REQUIRE_NONNULL(\n      deserializer.getExternalHandler(), \"got WritableStream on non-RPC serialized object?\");\n  auto externalHandler = dynamic_cast<RpcDeserializerExternalHandler*>(&handler);\n  KJ_REQUIRE(externalHandler != nullptr, \"got WritableStream on non-RPC serialized object?\");\n\n  auto reader = externalHandler->read();\n  KJ_REQUIRE(reader.isWritableStream(), \"external table slot type doesn't match serialization tag\");\n\n  auto ws = reader.getWritableStream();\n  auto encoding = ws.getEncoding();\n\n  KJ_REQUIRE(\n      static_cast<uint>(encoding) < capnp::Schema::from<StreamEncoding>().getEnumerants().size(),\n      \"unknown StreamEncoding received from peer\");\n\n  IoContext& ioctx = IoContext::current();\n  auto stream = ioctx.getByteStreamFactory().capnpToKjExplicitEnd(ws.getByteStream());\n  auto sink = newSystemStream(kj::mv(stream), encoding, ioctx);\n\n  return js.alloc<WritableStream>(\n      ioctx, kj::mv(sink), ioctx.getMetrics().tryCreateWritableByteStreamObserver());\n}\n\nvoid WritableStreamDefaultWriter::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  KJ_IF_SOME(attached, state.tryGetActiveUnsafe()) {\n    tracker.trackField(\"attached\", attached.stream);\n  }\n  tracker.trackField(\"closedPromise\", closedPromise);\n  tracker.trackField(\"readyPromise\", readyPromise);\n}\n\nvoid WritableStream::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"controller\", controller);\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams/writable.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"common.h\"\n\n#include <workerd/util/state-machine.h>\n#include <workerd/util/weak-refs.h>\n\nnamespace workerd::api {\n\nclass WritableStreamDefaultWriter: public jsg::Object, public WritableStreamController::Writer {\n public:\n  explicit WritableStreamDefaultWriter();\n\n  ~WritableStreamDefaultWriter() noexcept(false) override;\n\n  // JavaScript API\n\n  static jsg::Ref<WritableStreamDefaultWriter> constructor(\n      jsg::Lock& js, jsg::Ref<WritableStream> stream);\n\n  jsg::MemoizedIdentity<jsg::Promise<void>>& getClosed();\n  jsg::MemoizedIdentity<jsg::Promise<void>>& getReady();\n  kj::Maybe<int> getDesiredSize();\n\n  jsg::Promise<void> abort(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> reason);\n\n  // Closes the stream. All present write requests will complete, but future write requests will\n  // be rejected with a TypeError to the effect of \"This writable stream has been closed.\"\n  // `reason` will be passed to the underlying sink's close algorithm -- if this writable stream\n  // is one side of a transform stream, then its close algorithm causes the transform's readable\n  // side to become closed.\n  //\n  // Note: According to my reading of the Streams spec, if `writer.close()` is called on a\n  //   transform stream while the readable side has readable chunks in its queue, those chunks get\n  //   lost. This seems like a bug to me. Why would we wait for all present write requests to\n  //   complete on this side if we don't care that they're actually read?\n  jsg::Promise<void> close(jsg::Lock& js);\n\n  jsg::Promise<void> write(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> chunk);\n  void releaseLock(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(WritableStreamDefaultWriter, CompatibilityFlags::Reader flags) {\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(closed, getClosed);\n      JSG_READONLY_PROTOTYPE_PROPERTY(ready, getReady);\n      JSG_READONLY_PROTOTYPE_PROPERTY(desiredSize, getDesiredSize);\n    } else {\n      JSG_READONLY_INSTANCE_PROPERTY(closed, getClosed);\n      JSG_READONLY_INSTANCE_PROPERTY(ready, getReady);\n      JSG_READONLY_INSTANCE_PROPERTY(desiredSize, getDesiredSize);\n    }\n    JSG_METHOD(abort);\n    JSG_METHOD(close);\n    JSG_METHOD(write);\n    JSG_METHOD(releaseLock);\n\n    JSG_TS_OVERRIDE(<W = any> {\n      write(chunk?: W): Promise<void>;\n    });\n  }\n\n  // Internal API\n\n  void attach(jsg::Lock& js,\n      WritableStreamController& controller,\n      jsg::Promise<void> closedPromise,\n      jsg::Promise<void> readyPromise) override;\n\n  void detach() override;\n\n  void lockToStream(jsg::Lock& js, WritableStream& stream);\n\n  void replaceReadyPromise(jsg::Lock& js, jsg::Promise<void> readyPromise) override;\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n  kj::Maybe<jsg::Promise<void>> isReady(jsg::Lock& js);\n\n private:\n  struct Initial {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"initial\"_kj;\n  };\n  // While a Writer is attached to a WritableStream, it holds a strong reference to the\n  // WritableStream to prevent it from being GC'ed so long as the Writer is available.\n  // Once the writer is closed, released, or GC'ed the reference to the WritableStream\n  // is cleared and the WritableStream can be GC'ed if there are no other references to\n  // it being held anywhere. If the writer is still attached to the WritableStream when\n  // it is destroyed, the WritableStream's reference to the writer is cleared but the\n  // WritableStream remains in the \"writer locked\" state, per the spec.\n  struct Attached {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"attached\"_kj;\n    jsg::Ref<WritableStream> stream;\n  };\n  // Released: The user explicitly called releaseLock() to detach the writer from the stream.\n  // The stream remains usable and can be locked by a new writer.\n  struct Released {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"released\"_kj;\n  };\n  // Closed: The underlying stream ended (closed or errored) while the writer was attached.\n  // The stream is no longer usable.\n  struct Closed {\n    static constexpr kj::StringPtr NAME KJ_UNUSED = \"closed\"_kj;\n  };\n\n  // State machine for WritableStreamDefaultWriter:\n  //   Initial -> Attached (attach() called)\n  //   Attached -> Closed (detach() called when stream closes)\n  //   Attached -> Released (releaseLock() called)\n  // Closed and Released are terminal states.\n  // Initial is not terminal but most methods assert if called in this state.\n  using WriterState = StateMachine<TerminalStates<Closed, Released>,\n      ActiveState<Attached>,\n      Initial,\n      Attached,\n      Closed,\n      Released>;\n\n  kj::Maybe<IoContext&> ioContext;\n  WriterState state;\n\n  inline void assertAttachedOrTerminal() const {\n    KJ_ASSERT(!state.is<Initial>(), \"this writer was never attached\");\n  }\n\n  kj::Maybe<jsg::MemoizedIdentity<jsg::Promise<void>>> closedPromise;\n  kj::Maybe<jsg::MemoizedIdentity<jsg::Promise<void>>> readyPromise;\n  kj::Maybe<jsg::Promise<void>> readyPromisePending;\n\n  void visitForGc(jsg::GcVisitor& visitor);\n};\n\nclass WritableStream: public jsg::Object {\n public:\n  explicit WritableStream(IoContext& ioContext,\n      kj::Own<WritableStreamSink> sink,\n      kj::Maybe<kj::Own<ByteStreamObserver>> observer,\n      kj::Maybe<uint64_t> maybeHighWaterMark = kj::none,\n      kj::Maybe<jsg::Promise<void>> maybeClosureWaitable = kj::none);\n\n  explicit WritableStream(kj::Own<WritableStreamController> controller);\n  ~WritableStream() noexcept(false) {\n    weakRef->invalidate();\n  }\n\n  WritableStreamController& getController();\n\n  jsg::Ref<WritableStream> addRef();\n\n  // Remove and return the underlying implementation of this WritableStream. Throw a TypeError if\n  // this WritableStream is locked or closed, otherwise this WritableStream becomes immediately\n  // locked and closed. If this writable stream is errored, throw the stored error.\n  // TODO(cleanup): There are a couple of places where we need to convert to using detach()\n  // or the inner removeSink (on WritableStreamController) before we can remove this method.\n  virtual KJ_DEPRECATED(\"Use detach() instead\") kj::Own<WritableStreamSink> removeSink(\n      jsg::Lock& js);\n  virtual void detach(jsg::Lock& js);\n\n  // ---------------------------------------------------------------------------\n  // JS interface\n\n  static jsg::Ref<WritableStream> constructor(jsg::Lock& js,\n      jsg::Optional<UnderlyingSink> underlyingSink,\n      jsg::Optional<StreamQueuingStrategy> queuingStrategy);\n\n  bool isLocked();\n\n  // Errors the stream. All present and future read requests are rejected with a TypeError to the\n  // effect of \"This writable stream has been requested to abort.\" `reason` will be passed to the\n  // underlying sink's abort algorithm -- if this writable stream is one side of a transform stream,\n  // then its abort algorithm causes the transform's readable side to become errored with `reason`.\n  jsg::Promise<void> abort(jsg::Lock& js, jsg::Optional<v8::Local<v8::Value>> reason);\n\n  jsg::Promise<void> close(jsg::Lock& js);\n  jsg::Promise<void> flush(jsg::Lock& js);\n\n  jsg::Ref<WritableStreamDefaultWriter> getWriter(jsg::Lock& js);\n\n  jsg::JsString inspectState(jsg::Lock& js);\n  bool inspectExpectsBytes();\n\n  JSG_RESOURCE_TYPE(WritableStream, CompatibilityFlags::Reader flags) {\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(locked, isLocked);\n    } else {\n      JSG_READONLY_INSTANCE_PROPERTY(locked, isLocked);\n    }\n    JSG_METHOD(abort);\n    JSG_METHOD(close);\n    JSG_METHOD(getWriter);\n\n    JSG_INSPECT_PROPERTY(state, inspectState);\n    JSG_INSPECT_PROPERTY(expectsBytes, inspectExpectsBytes);\n\n    JSG_TS_OVERRIDE(<W = any> {\n      getWriter(): WritableStreamDefaultWriter<W>;\n    });\n  }\n\n  void serialize(jsg::Lock& js, jsg::Serializer& serializer);\n  static jsg::Ref<WritableStream> deserialize(\n      jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer);\n\n  JSG_SERIALIZABLE(rpc::SerializationTag::WRITABLE_STREAM);\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  kj::Maybe<IoContext&> ioContext;\n  kj::Own<WritableStreamController> controller;\n  kj::Own<WeakRef<WritableStream>> weakRef =\n      kj::refcounted<WeakRef<WritableStream>>(kj::Badge<WritableStream>(), *this);\n\n  kj::Own<WeakRef<WritableStream>> addWeakRef() {\n    return weakRef->addRef();\n  }\n\n  void visitForGc(jsg::GcVisitor& visitor);\n\n  template <typename T>\n  friend class WritableImpl;\n};\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams-test.c++",
    "content": "#include <workerd/api/streams/readable.h>\n#include <workerd/api/streams/standard.h>\n#include <workerd/tests/test-fixture.h>\n\n#include <kj/test.h>\n\nnamespace workerd::api {\n\nnamespace {\n\nclass FakeStreamSource final: public ReadableStreamSource {\n public:\n  FakeStreamSource(size_t length): length(length) {}\n\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    return kj::evalNow([this, maxBytes, buffer] {\n      auto amount = kj::min(maxBytes, length);\n      memset(buffer, 0, amount);\n      length -= amount;\n      return amount;\n    });\n  }\n\n private:\n  size_t length;\n};\n\nKJ_TEST(\"Streams tee stack overflow regression\") {\n  // Verify that deeply nested tee() chains don't cause a stack overflow.\n  // This is a regression test for a fix that removed deep recursion from tee().\n  static constexpr size_t teeDepth = 200 * 1024 / sizeof(void*);\n  TestFixture testFixture;\n  testFixture.runInIoContext([](const TestFixture::Environment& env) {\n    auto& js = jsg::Lock::from(env.isolate);\n    ReadableStream s(env.context, kj::heap<FakeStreamSource>(10 * 1024 * 1024));\n    auto readableStreams = s.tee(js);\n    for (size_t i = 0; i < teeDepth; i++) {\n      readableStreams = readableStreams[0]->tee(js);\n    }\n  });\n}\n\nKJ_TEST(\"Reading from default reader\") {\n  static constexpr size_t streamLength = 10 * 1024;\n  TestFixture testFixture;\n\n  testFixture.runInIoContext([](const TestFixture::Environment& env) -> kj::Promise<void> {\n    auto& js = jsg::Lock::from(env.isolate);\n    auto stream = js.alloc<ReadableStream>(env.context, kj::heap<FakeStreamSource>(streamLength));\n    auto reader = stream->getReader(js, {});\n    KJ_REQUIRE(reader.is<jsg::Ref<ReadableStreamDefaultReader>>());\n    auto& defaultReader = reader.get<jsg::Ref<ReadableStreamDefaultReader>>();\n\n    return env.context.awaitJs(js, defaultReader->read(js).then(js,\n            JSG_VISITABLE_LAMBDA((reader = defaultReader.addRef(), stream = stream.addRef()),\n                (reader, stream), (jsg::Lock& js, ReadResult readResult) {\n      KJ_ASSERT(!readResult.done);\n      auto& value = KJ_REQUIRE_NONNULL(readResult.value);\n      auto handle = value.getHandle(js);\n      KJ_ASSERT(handle->IsUint8Array());\n      KJ_ASSERT(4 * 1024 == handle.As<v8::Uint8Array>()->ByteLength());\n    })));\n  });\n}\n\nKJ_TEST(\"Reading from byob reader\") {\n  TestFixture testFixture;\n\n  struct TestData {\n    size_t streamLength = 10 * 1024;\n    size_t bufferSize;\n    bool expectDone;\n  };\n\n  TestData tests[] = {\n    {.streamLength = 10 * 1024, .bufferSize = 100},\n    {.streamLength = 10 * 1024, .bufferSize = 100 * 1024},\n    {.streamLength = 10, .bufferSize = 100},\n    {.streamLength = 1024, .bufferSize = 1024},\n  };\n\n  for (auto test: tests) {\n    testFixture.runInIoContext([&](const TestFixture::Environment& env) -> kj::Promise<void> {\n      auto& js = jsg::Lock::from(env.isolate);\n      auto stream =\n          js.alloc<ReadableStream>(env.context, kj::heap<FakeStreamSource>(test.streamLength));\n      ReadableStream::GetReaderOptions getReaderOptons = {.mode = kj::str(\"byob\")};\n      auto reader = stream->getReader(js, kj::mv(getReaderOptons));\n      KJ_REQUIRE(reader.is<jsg::Ref<ReadableStreamBYOBReader>>());\n      auto& byobReader = reader.get<jsg::Ref<ReadableStreamBYOBReader>>();\n\n      auto buffer = v8::Uint8Array::New(\n          v8::ArrayBuffer::New(js.v8Isolate, test.bufferSize), 0, test.bufferSize);\n\n      return env.context.awaitJs(js, byobReader->read(js, buffer, {}).then(js,\n                  JSG_VISITABLE_LAMBDA(\n                      (test, reader = byobReader.addRef(), stream = stream.addRef()),\n                      (reader, stream), (jsg::Lock& js, ReadResult readResult) {\n        KJ_ASSERT(!readResult.done);\n\n        auto& value = KJ_REQUIRE_NONNULL(readResult.value);\n        auto handle = value.getHandle(js);\n        KJ_ASSERT(handle->IsUint8Array());\n        auto view = handle.As<v8::Uint8Array>();\n        KJ_ASSERT(kj::min(test.streamLength, test.bufferSize) == view->ByteLength());\n        KJ_ASSERT(test.bufferSize == view->Buffer()->ByteLength());\n      })));\n      return kj::READY_NOW;\n    });\n  }\n}\n\nKJ_TEST(\"PumpToReader regression\") {\n  // If the promise holding the PumpToReader is dropped while the inner\n  // write to the sink is pending, the PumpToReader can free the sink.\n  // In some cases, this means that the sink can error because shutdownWrite\n  // is called while there is still a pending write promise. This test verifies\n  // that PumpToReader cancels any pending write promise when it is destroyed.\n\n  struct TestSink final: public WritableStreamSink {\n    kj::TwoWayPipe pipe;\n    kj::PromiseFulfillerPair<void> paf;\n    kj::Vector<kj::String>& events;\n    TestSink(kj::Vector<kj::String>& events)\n        : pipe(kj::newTwoWayPipe()),\n          paf(kj::newPromiseAndFulfiller<void>()),\n          events(events) {}\n\n    ~TestSink() {\n      events.add(kj::str(\"sink was destroyed\"));\n      pipe.ends[0]->shutdownWrite();\n    }\n\n    kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override {\n      events.add(kj::str(\"got the write\"));\n\n      paf.fulfiller->fulfill();\n\n      return pipe.ends[0]->write(buffer).attach(\n          kj::defer([this] { events.add(kj::str(\"write promise was dropped\")); }));\n    }\n\n    kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {\n      events.add(kj::str(\"got the write\"));\n      paf.fulfiller->fulfill();\n      // Concatenate pieces into a single buffer for the pipe write.\n      kj::Vector<byte> data;\n      for (auto& piece: pieces) {\n        data.addAll(piece);\n      }\n      auto arr = data.releaseAsArray();\n      return pipe.ends[0]->write(arr).attach(\n          kj::mv(arr), kj::defer([this] { events.add(kj::str(\"write promise was dropped\")); }));\n    }\n\n    kj::Promise<void> end() override {\n      return kj::READY_NOW;\n    }\n\n    void abort(kj::Exception reason) override {}\n  };\n\n  kj::Vector<kj::String> events;\n  capnp::MallocMessageBuilder flagsBuilder;\n  auto featureFlags = flagsBuilder.initRoot<CompatibilityFlags>();\n  featureFlags.setStreamsJavaScriptControllers(true);\n  TestFixture testFixture({.featureFlags = featureFlags.asReader()});\n\n  testFixture.runInIoContext([&](const TestFixture::Environment& env) -> kj::Promise<void> {\n    auto& js = jsg::Lock::from(env.isolate);\n    auto stream = ReadableStream::constructor(js,\n        UnderlyingSource{.start =\n                             [](jsg::Lock& js, auto controller) {\n      auto& c = KJ_REQUIRE_NONNULL(\n          controller.template tryGet<jsg::Ref<ReadableStreamDefaultController>>());\n      c->enqueue(js, v8::ArrayBuffer::New(js.v8Isolate, 10));\n      c->close(js);\n      return js.resolvedPromise();\n    }},\n        kj::none);\n\n    auto sink = kj::heap<TestSink>(events);\n    auto writePromise = kj::mv(sink->paf.promise);\n    auto promise = stream->pumpTo(js, kj::mv(sink), true);\n\n    return writePromise.attach(kj::mv(promise));\n  });\n\n  KJ_ASSERT(events.size() == 3);\n  KJ_ASSERT(events[0] == \"got the write\");\n  KJ_ASSERT(events[1] == \"write promise was dropped\");\n  KJ_ASSERT(events[2] == \"sink was destroyed\");\n}\n\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/streams.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// Streams standard: https://streams.spec.whatwg.org/\n//\n// This is the most over-engineered spec...\n\n#include <workerd/api/streams/compression.h>\n#include <workerd/api/streams/encoding.h>\n#include <workerd/api/streams/identity-transform-stream.h>\n#include <workerd/api/streams/readable.h>\n#include <workerd/api/streams/standard.h>\n#include <workerd/api/streams/transform.h>\n#include <workerd/api/streams/writable.h>\n\nnamespace workerd::api {\n\n#define EW_STREAMS_ISOLATE_TYPES                                                                   \\\n  api::StreamQueuingStrategy, api::UnderlyingSink, api::UnderlyingSource, api::Transformer,        \\\n      api::PipeToOptions, api::ReadResult, api::ReadableStream, api::ReadableStreamDefaultReader,  \\\n      api::ReadableStreamBYOBReader,                                                               \\\n      api::ReadableStreamBYOBReader::ReadableStreamBYOBReaderReadOptions,                          \\\n      api::ReadableStream::GetReaderOptions, api::ReadableStreamBYOBRequest,                       \\\n      api::ReadableStreamDefaultController, api::ReadableByteStreamController,                     \\\n      api::WritableStreamDefaultController, api::TransformStreamDefaultController,                 \\\n      api::ReadableStream::Transform, api::WritableStream, api::WritableStreamDefaultWriter,       \\\n      api::TransformStream, api::FixedLengthStream, api::IdentityTransformStream,                  \\\n      api::IdentityTransformStream::QueuingStrategy, api::ReadableStream::ValuesOptions,           \\\n      api::ReadableStream::ReadableStreamAsyncIterator,                                            \\\n      api::ReadableStream::ReadableStreamAsyncIterator::Next, api::CompressionStream,              \\\n      api::DecompressionStream, api::TextEncoderStream, api::TextDecoderStream,                    \\\n      api::TextDecoderStream::TextDecoderStreamInit, api::ByteLengthQueuingStrategy,               \\\n      api::CountQueuingStrategy, api::QueuingStrategyInit\n// The list of streams.h types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/sync-kv.c++",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"sync-kv.h\"\n\n#include \"actor-state.h\"\n\n#include <workerd/util/sqlite-kv.h>\n\nnamespace workerd::api {\n\njsg::JsValue SyncKvStorage::get(jsg::Lock& js, kj::String key) {\n  TraceContext traceContext =\n      IoContext::current().makeUserTraceSpan(\"durable_object_storage_kv_get\"_kjc);\n\n  SqliteKv& sqliteKv = getSqliteKv(js);\n\n  traceContext.setTag(\"db.system.name\"_kjc, \"cloudflare-durable-object-sql\"_kjc);\n  traceContext.setTag(\"db.operation.name\"_kjc, \"get\"_kjc);\n  traceContext.setTag(\"cloudflare.durable_object.kv.query.keys\"_kjc, key.asPtr());\n  traceContext.setTag(\"cloudflare.durable_object.kv.query.keys.count\"_kjc, static_cast<int64_t>(1));\n\n  kj::Maybe<jsg::JsValue> result;\n  if (sqliteKv.get(key,\n          [&](kj::ArrayPtr<const byte> value) { result = deserializeV8Value(js, key, value); })) {\n    return KJ_ASSERT_NONNULL(result);\n  } else {\n    return js.undefined();\n  }\n}\n\njsg::Ref<SyncKvStorage::ListIterator> SyncKvStorage::list(\n    jsg::Lock& js, jsg::Optional<ListOptions> maybeOptions) {\n  TraceContext traceContext =\n      IoContext::current().makeUserTraceSpan(\"durable_object_storage_kv_list\"_kjc);\n  SqliteKv& sqliteKv = getSqliteKv(js);\n\n  traceContext.setTag(\"db.system.name\"_kjc, \"cloudflare-durable-object-sql\"_kjc);\n  traceContext.setTag(\"db.operation.name\"_kjc, \"list\"_kjc);\n\n  KJ_IF_SOME(o, maybeOptions) {\n    KJ_IF_SOME(start, o.start) {\n      traceContext.setTag(\"cloudflare.durable_object.kv.query.start\"_kjc, start.asPtr());\n    }\n    KJ_IF_SOME(startAfter, o.startAfter) {\n      traceContext.setTag(\"cloudflare.durable_object.kv.query.startAfter\"_kjc, startAfter.asPtr());\n    }\n    KJ_IF_SOME(end, o.end) {\n      traceContext.setTag(\"cloudflare.durable_object.kv.query.end\"_kjc, end.asPtr());\n    }\n    KJ_IF_SOME(prefix, o.prefix) {\n      traceContext.setTag(\"cloudflare.durable_object.kv.query.prefix\"_kjc, prefix.asPtr());\n    }\n    KJ_IF_SOME(reverse, o.reverse) {\n      traceContext.setTag(\"cloudflare.durable_object.kv.query.reverse\"_kjc, reverse);\n    }\n    KJ_IF_SOME(limit, o.limit) {\n      traceContext.setTag(\n          \"cloudflare.durable_object.kv.query.limit\"_kjc, static_cast<int64_t>(limit));\n    }\n  }\n\n  // Convert our options to DurableObjectStorageOperations::ListOptions (which also have the\n  // `allowConcurrency` and `noCache` options, which are irrelevant in the sync interface).\n  auto asyncOptions = kj::mv(maybeOptions).map([&](ListOptions&& options) {\n    return DurableObjectStorageOperations::ListOptions{\n      .start = kj::mv(options.start),\n      .startAfter = kj::mv(options.startAfter),\n      .end = kj::mv(options.end),\n      .prefix = kj::mv(options).prefix,\n      .reverse = options.reverse,\n      .limit = options.limit,\n    };\n  });\n\n  auto [start, end, reverse, limit] =\n      KJ_UNWRAP_OR(DurableObjectStorageOperations::compileListOptions(asyncOptions), {\n        // Key range is empty. Return empty map.\n        return js.alloc<SyncKvStorage::ListIterator>(\n            IoContext::current().addObject(kj::heap<SqliteKv::ListCursor>(nullptr)));\n      });\n\n  auto cursor = sqliteKv.list(start, end, limit, reverse ? SqliteKv::REVERSE : SqliteKv::FORWARD)\n                    .attach(kj::mv(start), kj::mv(end));\n\n  return js.alloc<SyncKvStorage::ListIterator>(IoContext::current().addObject(kj::mv(cursor)));\n}\n\nkj::Maybe<jsg::JsArray> SyncKvStorage::listNext(jsg::Lock& js, IoOwn<SqliteKv::ListCursor>& state) {\n  auto& stateRef = *state;\n  KJ_IF_SOME(pair, stateRef.next()) {\n    return js.arr(js.str(pair.key), deserializeV8Value(js, pair.key, pair.value));\n  } else if (stateRef.wasCanceled()) {\n    JSG_FAIL_REQUIRE(Error,\n        \"kv.list() iterator was invalidated because a new call to kv.list() was started. Only one \"\n        \"kv.list() iterator can exist at a time.\");\n  } else {\n    return kj::none;\n  }\n}\n\nvoid SyncKvStorage::put(jsg::Lock& js, kj::String key, jsg::JsValue value) {\n  TraceContext traceContext =\n      IoContext::current().makeUserTraceSpan(\"durable_object_storage_kv_put\"_kjc);\n  SqliteKv& sqliteKv = getSqliteKv(js);\n\n  traceContext.setTag(\"db.system.name\"_kjc, \"cloudflare-durable-object-sql\"_kjc);\n  traceContext.setTag(\"db.operation.name\"_kjc, \"put\"_kjc);\n  traceContext.setTag(\"cloudflare.durable_object.kv.query.keys\"_kjc, key.asPtr());\n  traceContext.setTag(\"cloudflare.durable_object.kv.query.keys.count\"_kjc, static_cast<int64_t>(1));\n\n  sqliteKv.put(key, serializeV8Value(js, value));\n}\n\nkj::OneOf<bool, int> SyncKvStorage::delete_(jsg::Lock& js, kj::String key) {\n  TraceContext traceContext =\n      IoContext::current().makeUserTraceSpan(\"durable_object_storage_kv_delete\"_kjc);\n  SqliteKv& sqliteKv = getSqliteKv(js);\n\n  traceContext.setTag(\"db.system.name\"_kjc, \"cloudflare-durable-object-sql\"_kjc);\n  traceContext.setTag(\"db.operation.name\"_kjc, \"delete\"_kjc);\n  traceContext.setTag(\"cloudflare.durable_object.kv.query.keys\"_kjc, key.asPtr());\n  traceContext.setTag(\"cloudflare.durable_object.kv.query.keys.count\"_kjc, static_cast<int64_t>(1));\n\n  auto deleted = sqliteKv.delete_(key);\n\n  traceContext.setTag(\"cloudflare.durable_object.kv.response.deleted_count\"_kjc,\n      static_cast<int64_t>(deleted ? 1 : 0));\n\n  return deleted;\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/sync-kv.h",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"actor-state.h\"\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/iterator.h>\n#include <workerd/util/sqlite-kv.h>\n\nnamespace workerd::api {\n\n// Synchronous KV storage. Available as ctx.storage.kv on SQLite-backed DOs.\nclass SyncKvStorage: public jsg::Object {\n public:\n  SyncKvStorage(jsg::Ref<DurableObjectStorage> storage): storage(kj::mv(storage)) {}\n\n  struct ListOptions {\n    jsg::Optional<kj::String> start;\n    jsg::Optional<kj::String> startAfter;\n    jsg::Optional<kj::String> end;\n    jsg::Optional<kj::String> prefix;\n    jsg::Optional<bool> reverse;\n    jsg::Optional<int> limit;\n\n    JSG_STRUCT(start, startAfter, end, prefix, reverse, limit);\n    JSG_STRUCT_TS_OVERRIDE(SyncKvListOptions);  // Rename from SyncKvStorageListOptions\n  };\n\n  jsg::JsValue get(jsg::Lock& js, kj::String key);\n\n  JSG_ITERATOR_TYPE(ListIterator, jsg::JsArray, IoOwn<SqliteKv::ListCursor>, listNext);\n\n  jsg::Ref<ListIterator> list(jsg::Lock& js, jsg::Optional<ListOptions> options);\n\n  void put(jsg::Lock& js, kj::String key, jsg::JsValue value);\n\n  kj::OneOf<bool, int> delete_(jsg::Lock& js, kj::String key);\n\n  JSG_RESOURCE_TYPE(SyncKvStorage) {\n    JSG_METHOD(get);\n    JSG_METHOD(list);\n    JSG_METHOD(put);\n    JSG_METHOD_NAMED(delete, delete_);\n\n    JSG_TS_OVERRIDE({\n      get<T = unknown>(key: string): T | undefined;\n\n      list<T = unknown>(options?: SyncKvStorageListOptions): Iterable<[string, T]>;\n\n      put<T>(key: string, value: T): void;\n\n      delete(key: string): boolean;\n    });\n  }\n\n private:\n  jsg::Ref<DurableObjectStorage> storage;\n\n  SqliteKv& getSqliteKv(jsg::Lock& js) {\n    return storage->getSqliteKv(js);\n  }\n\n  static kj::Maybe<jsg::JsArray> listNext(jsg::Lock& js, IoOwn<SqliteKv::ListCursor>& state);\n};\n\n#define EW_SYNC_KV_ISOLATE_TYPES api::SyncKvStorage, api::SyncKvStorage::ListOptions,              \\\n    api::SyncKvStorage::ListIterator, api::SyncKvStorage::ListIterator::Next\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/system-streams-test.c++",
    "content": "// Copyright (c) 2017-2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"system-streams.h\"\n\n#include <workerd/io/io-context.h>\n#include <workerd/tests/test-fixture.h>\n\n#include <kj/test.h>\n\nnamespace workerd::api {\nnamespace {\n\nKJ_TEST(\"EncodedAsyncInputStream cancel with pending read on AsyncPipe\") {\n  // This test reproduces a use-after-free crash that occurred when:\n  // 1. A read operation is started on an EncodedAsyncInputStream backed by an AsyncPipe\n  // 2. The stream is cancelled (e.g., via Socket::close())\n  // 3. The AsyncPipe is destroyed while the read is still pending\n  //\n  // Without the fix (kj::Canceler in EncodedAsyncInputStream), the BlockedRead destructor\n  // would try to access the freed AsyncPipe, causing a use-after-free.\n\n  TestFixture fixture;\n  fixture.runInIoContext([](const TestFixture::Environment& env) -> kj::Promise<void> {\n    // Create an in-memory pipe (AsyncPipe)\n    auto pipe = kj::newTwoWayPipe();\n\n    // Create an EncodedAsyncInputStream wrapping one end of the pipe\n    kj::Own<kj::AsyncInputStream> inputStream = kj::mv(pipe.ends[0]);\n    auto stream = newSystemStream(kj::mv(inputStream), StreamEncoding::IDENTITY, env.context);\n\n    // Start a read operation - this will block because no data has been written to the pipe\n    kj::byte buffer[100];\n    auto readPromise = stream->tryRead(buffer, 1, sizeof(buffer));\n\n    // Cancel the stream - this simulates what Socket::close() does\n    stream->cancel(KJ_EXCEPTION(DISCONNECTED, \"stream cancelled\"));\n\n    // Now destroy the other end of the pipe - this destroys the AsyncPipe\n    // Without the fix, this would cause a use-after-free when the BlockedRead\n    // destructor tries to access the freed pipe.\n    pipe.ends[1] = nullptr;\n\n    // The read promise should be cancelled - try to wait for it\n    // It should reject with the cancellation exception\n    return readPromise.then(\n        [](size_t) { KJ_FAIL_ASSERT(\"read should have been cancelled\"); }, [](kj::Exception&& e) {\n      // Expected the read to be cancelled\n    });\n  });\n}\n\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/system-streams.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"system-streams.h\"\n\n#include \"util.h\"\n\n#include <kj/compat/brotli.h>\n#include <kj/compat/gzip.h>\n#include <kj/one-of.h>\n\nnamespace workerd::api {\n\n// =======================================================================================\n// EncodedAsyncInputStream\n\nnamespace {\n\n// A wrapper around a native `kj::AsyncInputStream` which knows the underlying encoding of the\n// stream and whether or not it requires pending event registration.\nclass EncodedAsyncInputStream final: public ReadableStreamSource {\n public:\n  explicit EncodedAsyncInputStream(\n      kj::Own<kj::AsyncInputStream> inner, StreamEncoding encoding, IoContext& context);\n\n  // Read bytes in identity encoding. If the stream is not already in identity encoding, it will be\n  // converted to identity encoding via an appropriate stream wrapper.\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override;\n\n  StreamEncoding getPreferredEncoding() override {\n    return encoding;\n  }\n\n  // Return the number of bytes, if known, which this input stream will produce if the sink is known\n  // to be of a particular encoding.\n  //\n  // It is likely an error to call this function without immediately following it with a pumpTo()\n  // to a EncodedAsyncOutputStream of that exact encoding.\n  kj::Maybe<uint64_t> tryGetLength(StreamEncoding outEncoding) override;\n\n  // Consume this stream and return two streams with the same encoding that read the exact same\n  // data.\n  //\n  // This implementation of `tryTee()` is not technically required for correctness, but prevents\n  // re-encoding (and converting Content-Length responses to chunk-encoded responses) gzip and\n  // brotli streams.\n  kj::Maybe<Tee> tryTee(uint64_t limit) override;\n\n  void cancel(kj::Exception reason) override;\n\n private:\n  friend class EncodedAsyncOutputStream;\n\n  void ensureIdentityEncoding();\n\n  kj::Own<kj::AsyncInputStream> inner;\n  StreamEncoding encoding;\n  kj::Canceler canceler;\n\n  IoContext& ioContext;\n};\n\nEncodedAsyncInputStream::EncodedAsyncInputStream(\n    kj::Own<kj::AsyncInputStream> inner, StreamEncoding encoding, IoContext& context)\n    : inner(kj::mv(inner)),\n      encoding(encoding),\n      ioContext(context) {}\n\nkj::Promise<size_t> EncodedAsyncInputStream::tryRead(\n    void* buffer, size_t minBytes, size_t maxBytes) {\n  ensureIdentityEncoding();\n\n  return kj::evalNow([&]() {\n    return canceler.wrap(inner->tryRead(buffer, minBytes, maxBytes))\n        .attach(ioContext.registerPendingEvent());\n  }).catch_([](kj::Exception&& exception) -> kj::Promise<size_t> {\n    KJ_IF_SOME(e,\n        translateKjException(exception,\n            {\n              {\"gzip compressed stream ended prematurely\"_kj,\n                \"Gzip compressed stream ended prematurely.\"_kj},\n              {\"gzip decompression failed\"_kj, \"Gzip decompression failed.\"},\n              {\"brotli state allocation failed\"_kj, \"Brotli state allocation failed.\"},\n              {\"invalid brotli window size\"_kj, \"Invalid brotli window size.\"},\n              {\"invalid brotli compression level\"_kj, \"Invalid brotli compression level.\"},\n              {\"brotli window size too big\"_kj, \"Brotli window size too big.\"},\n              {\"brotli decompression failed\"_kj, \"Brotli decompression failed.\"},\n              {\"brotli compression failed\"_kj, \"Brotli compression failed.\"},\n              {\"brotli compressed stream ended prematurely\"_kj,\n                \"Brotli compressed stream ended prematurely.\"},\n            })) {\n      return kj::mv(e);\n    }\n\n    // Let the original exception pass through, since it is likely already a jsg.TypeError.\n    return kj::mv(exception);\n  });\n}\n\nkj::Maybe<uint64_t> EncodedAsyncInputStream::tryGetLength(StreamEncoding outEncoding) {\n  if (outEncoding == encoding) {\n    return inner->tryGetLength();\n  } else {\n    // We have no idea what the length will be once encoded/decoded.\n    return kj::none;\n  }\n}\n\nkj::Maybe<ReadableStreamSource::Tee> EncodedAsyncInputStream::tryTee(uint64_t limit) {\n  // We tee the stream in its original encoding, because chances are highest that we'll be pumped\n  // to sinks that are of the same encoding, and only read in identity encoding no more than once.\n  //\n  // Additionally, we should propagate the fact that this stream is a native stream to the branches\n  // of the tee, so that branches which fall behind their siblings (and thus are reading from the\n  // tee buffer) still register pending events correctly.\n  auto tee = kj::newTee(kj::mv(inner), limit);\n\n  Tee result;\n  result.branches[0] = newSystemStream(newTeeErrorAdapter(kj::mv(tee.branches[0])), encoding);\n  result.branches[1] = newSystemStream(newTeeErrorAdapter(kj::mv(tee.branches[1])), encoding);\n  return kj::mv(result);\n}\n\nvoid EncodedAsyncInputStream::cancel(kj::Exception reason) {\n  // Cancel any pending read operations. This will cause the wrapped promises to be rejected\n  // with a cancellation exception, which properly cleans up the BlockedRead state in AsyncPipe\n  // before the pipe itself is destroyed.\n  canceler.cancel(kj::mv(reason));\n}\n\nvoid EncodedAsyncInputStream::ensureIdentityEncoding() {\n  // Decompression gets added to the stream here if needed based on the content encoding.\n  if (encoding == StreamEncoding::GZIP) {\n    inner = kj::heap<kj::GzipAsyncInputStream>(*inner).attach(kj::mv(inner));\n    encoding = StreamEncoding::IDENTITY;\n  } else if (encoding == StreamEncoding::BROTLI) {\n    inner = kj::heap<kj::BrotliAsyncInputStream>(*inner).attach(kj::mv(inner));\n    encoding = StreamEncoding::IDENTITY;\n  } else {\n    // We currently support gzip and brotli as non-identity content encodings.\n    KJ_ASSERT(encoding == StreamEncoding::IDENTITY);\n  }\n}\n\n// =======================================================================================\n// EncodedAsyncOutputStream\n\n// A wrapper around a native `kj::AsyncOutputStream` which knows the underlying encoding of the\n// stream and optimizes pumps from `EncodedAsyncInputStream`.\n//\n// The inner will be held on to right up until either end() or abort() is called.\n// This is important because some AsyncOutputStream implementations perform cleanup\n// operations equivalent to end() in their destructors (for instance HttpChunkedEntityWriter).\n// If we wait to clear the kj::Own when the EncodedAsyncOutputStream is destroyed, and the\n// EncodedAsyncOutputStream is owned (for instance) by an IoOwn, then the lifetime of the\n// inner may be extended past when it should. Eventually, kj::AsyncOutputStream should\n// probably have a distinct end() method of its own that we can defer to, but until it\n// does, it is important for us to release it as soon as end() or abort() are called.\nclass EncodedAsyncOutputStream final: public WritableStreamSink {\n public:\n  explicit EncodedAsyncOutputStream(\n      kj::Own<kj::AsyncOutputStream> inner, StreamEncoding encoding, IoContext& context);\n\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override;\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const kj::byte>> pieces) override;\n\n  kj::Maybe<kj::Promise<DeferredProxy<void>>> tryPumpFrom(\n      ReadableStreamSource& input, bool end) override;\n\n  kj::Promise<void> end() override;\n\n  void abort(kj::Exception reason) override;\n\n  StreamEncoding disownEncodingResponsibility() override;\n\n private:\n  void ensureIdentityEncoding();\n\n  // Unwrap `inner` as a `kj::AsyncOutputStream`.\n  kj::AsyncOutputStream& getInner();\n  // TODO(cleanup): Obviously this is polymorphism. We should be able to do better.\n\n  // A sentinel indicating that the EncodedOutputStream has ended and is no longer usable.\n  struct Ended {};\n\n  // I use a OneOf here rather than probing with downcasts because end() must be called for\n  // correctness rather than for optimization. I \"know\" this code will never be compiled w/o RTTI,\n  // but I'm paranoid.\n  kj::OneOf<kj::Own<kj::AsyncOutputStream>,\n      kj::Own<kj::GzipAsyncOutputStream>,\n      kj::Own<kj::BrotliAsyncOutputStream>,\n      Ended>\n      inner;\n\n  StreamEncoding encoding;\n\n  IoContext& ioContext;\n};\n\nEncodedAsyncOutputStream::EncodedAsyncOutputStream(\n    kj::Own<kj::AsyncOutputStream> inner, StreamEncoding encoding, IoContext& context)\n    : inner(kj::mv(inner)),\n      encoding(encoding),\n      ioContext(context) {}\n\nkj::Promise<void> EncodedAsyncOutputStream::write(kj::ArrayPtr<const byte> buffer) {\n  // Alternatively, we could throw here but this is erring on the side of leniency.\n  if (inner.is<Ended>()) return kj::READY_NOW;\n\n  ensureIdentityEncoding();\n\n  return getInner().write(buffer).attach(ioContext.registerPendingEvent());\n}\n\nkj::Promise<void> EncodedAsyncOutputStream::write(\n    kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) {\n  // Alternatively, we could throw here but this is erring on the side of leniency.\n  if (inner.is<Ended>()) return kj::READY_NOW;\n\n  ensureIdentityEncoding();\n\n  return getInner().write(pieces).attach(ioContext.registerPendingEvent());\n}\n\nkj::Maybe<kj::Promise<DeferredProxy<void>>> EncodedAsyncOutputStream::tryPumpFrom(\n    ReadableStreamSource& input, bool end) {\n\n  // If this output stream has already been ended, then there's nothing more to\n  // pump into it, just return an immediately resolved promise. Alternatively\n  // we could throw here.\n  if (inner.is<Ended>()) {\n    return kj::Promise<DeferredProxy<void>>(DeferredProxy<void>{kj::READY_NOW});\n  }\n\n  KJ_IF_SOME(nativeInput, kj::dynamicDowncastIfAvailable<EncodedAsyncInputStream>(input)) {\n    // We can avoid putting our inner streams into identity encoding if the input and output both\n    // have the same encoding. Since ReadableStreamSource/WritableStreamSink always pump everything\n    // (there is no `amount` parameter like in the KJ equivalents), we can assume that we will\n    // always stop at a valid endpoint.\n    //\n    // Note that even if we have to pump in identity encoding, there is no reason to return nullptr.\n    // We can still optimize the pump a little by registering only a single pending event rather\n    // than falling back to the heavier weight algorithm in ReadableStreamSource, which depends on\n    // tryRead() and write() registering their own individual events on every call.\n    if (nativeInput.encoding != encoding) {\n      ensureIdentityEncoding();\n      nativeInput.ensureIdentityEncoding();\n    }\n\n    auto promise = nativeInput.inner->pumpTo(getInner()).ignoreResult();\n    if (end) {\n      // TODO(cleanup): When KJ streams are refactored to have a general end(), this stupid switch\n      //   can go away.\n      KJ_SWITCH_ONEOF(inner) {\n        KJ_CASE_ONEOF(stream, kj::Own<kj::AsyncOutputStream>) {\n          KJ_IF_SOME(ee, kj::dynamicDowncastIfAvailable<capnp::ExplicitEndOutputStream>(*stream)) {\n            promise = promise.then([&ee = ee]() { return ee.end(); });\n          } else KJ_IF_SOME(aio, kj::dynamicDowncastIfAvailable<kj::AsyncIoStream>(*stream)) {\n            promise = promise.then([&aio = aio]() { aio.shutdownWrite(); });\n          }\n        }\n        KJ_CASE_ONEOF(gz, kj::Own<kj::GzipAsyncOutputStream>) {\n          promise = promise.then([&gz = gz]() { return gz->end(); });\n        }\n        KJ_CASE_ONEOF(br, kj::Own<kj::BrotliAsyncOutputStream>) {\n          promise = promise.then([&br = br]() { return br->end(); });\n        }\n        KJ_CASE_ONEOF(e, Ended) {}\n      }\n    }\n\n    // Since this is a system stream, the pump task is eligible to be deferred past IoContext\n    // lifetime!\n    return kj::Promise<DeferredProxy<void>>(DeferredProxy<void>{kj::mv(promise)});\n  }\n\n  return kj::none;\n}\n\nStreamEncoding EncodedAsyncOutputStream::disownEncodingResponsibility() {\n  StreamEncoding result = encoding;\n  encoding = StreamEncoding::IDENTITY;\n  return result;\n}\n\nkj::Promise<void> EncodedAsyncOutputStream::end() {\n  if (inner.is<Ended>()) return kj::READY_NOW;\n\n  kj::Promise<void> promise = kj::READY_NOW;\n\n  // TODO(cleanup): When KJ streams are refactored to have a general end(), this stupid switch\n  //   can go away.\n  KJ_SWITCH_ONEOF(inner) {\n    KJ_CASE_ONEOF(stream, kj::Own<kj::AsyncOutputStream>) {\n      KJ_IF_SOME(ee, kj::dynamicDowncastIfAvailable<capnp::ExplicitEndOutputStream>(*stream)) {\n        promise = ee.end().attach(kj::mv(stream));\n      } else KJ_IF_SOME(aio, kj::dynamicDowncastIfAvailable<kj::AsyncIoStream>(*stream)) {\n        aio.shutdownWrite();\n        promise = promise.attach(kj::mv(stream));\n      }\n    }\n    KJ_CASE_ONEOF(gz, kj::Own<kj::GzipAsyncOutputStream>) {\n      promise = gz->end().attach(kj::mv(gz));\n    }\n    KJ_CASE_ONEOF(br, kj::Own<kj::BrotliAsyncOutputStream>) {\n      promise = br->end().attach(kj::mv(br));\n    }\n    KJ_CASE_ONEOF(e, Ended) {}\n  }\n\n  inner.init<Ended>();\n\n  return promise.attach(ioContext.registerPendingEvent());\n}\n\nvoid EncodedAsyncOutputStream::abort(kj::Exception reason) {\n  KJ_SWITCH_ONEOF(inner) {\n    KJ_CASE_ONEOF(stream, kj::Own<kj::AsyncOutputStream>) {\n      stream->abortWrite(kj::mv(reason));\n    }\n    KJ_CASE_ONEOF(gz, kj::Own<kj::GzipAsyncOutputStream>) {\n      gz->abortWrite(kj::mv(reason));\n    }\n    KJ_CASE_ONEOF(br, kj::Own<kj::BrotliAsyncOutputStream>) {\n      br->abortWrite(kj::mv(reason));\n    }\n    KJ_CASE_ONEOF(e, Ended) {}\n  }\n  inner.init<Ended>();\n}\n\nvoid EncodedAsyncOutputStream::ensureIdentityEncoding() {\n  // Compression gets added to the stream here if needed based on the content encoding.\n  KJ_DASSERT(!inner.is<Ended>(), \"the EncodedAsyncOutputStream has been ended or aborted\");\n  if (encoding == StreamEncoding::GZIP) {\n    // This is safe because only a kj::AsyncOutputStream can have non-identity encoding.\n    auto& stream = inner.get<kj::Own<kj::AsyncOutputStream>>();\n\n    inner = kj::heap<kj::GzipAsyncOutputStream>(*stream).attach(kj::mv(stream));\n    encoding = StreamEncoding::IDENTITY;\n  } else if (encoding == StreamEncoding::BROTLI) {\n    auto& stream = inner.get<kj::Own<kj::AsyncOutputStream>>();\n\n    inner = kj::heap<kj::BrotliAsyncOutputStream>(*stream).attach(kj::mv(stream));\n    encoding = StreamEncoding::IDENTITY;\n  } else {\n    // We currently support gzip and brotli as non-identity content encodings.\n    KJ_ASSERT(encoding == StreamEncoding::IDENTITY);\n  }\n}\n\nkj::AsyncOutputStream& EncodedAsyncOutputStream::getInner() {\n  KJ_SWITCH_ONEOF(inner) {\n    KJ_CASE_ONEOF(stream, kj::Own<kj::AsyncOutputStream>) {\n      return *stream;\n    }\n    KJ_CASE_ONEOF(gz, kj::Own<kj::GzipAsyncOutputStream>) {\n      return *gz;\n    }\n    KJ_CASE_ONEOF(br, kj::Own<kj::BrotliAsyncOutputStream>) {\n      return *br;\n    }\n    KJ_CASE_ONEOF(ended, Ended) {\n      KJ_FAIL_ASSERT(\"the EncodedAsyncOutputStream has been ended or aborted.\");\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\n}  // namespace\n\nkj::Own<ReadableStreamSource> newSystemStream(\n    kj::Own<kj::AsyncInputStream> inner, StreamEncoding encoding, IoContext& context) {\n  return kj::heap<EncodedAsyncInputStream>(kj::mv(inner), encoding, context);\n}\nkj::Own<WritableStreamSink> newSystemStream(\n    kj::Own<kj::AsyncOutputStream> inner, StreamEncoding encoding, IoContext& context) {\n  return kj::heap<EncodedAsyncOutputStream>(kj::mv(inner), encoding, context);\n}\n\nSystemMultiStream newSystemMultiStream(\n    kj::RefcountedWrapper<kj::Own<kj::AsyncIoStream>>& stream, IoContext& context) {\n\n  return {.readable = kj::heap<EncodedAsyncInputStream>(\n              stream.addWrappedRef(), StreamEncoding::IDENTITY, context),\n    .writable = kj::heap<EncodedAsyncOutputStream>(\n        stream.addWrappedRef(), StreamEncoding::IDENTITY, context)};\n}\n\nContentEncodingOptions::ContentEncodingOptions(CompatibilityFlags::Reader flags)\n    : brotliEnabled(flags.getBrotliContentEncoding()) {}\n\nStreamEncoding getContentEncoding(IoContext& context,\n    const kj::HttpHeaders& headers,\n    Response::BodyEncoding bodyEncoding,\n    ContentEncodingOptions options) {\n  if (bodyEncoding == Response::BodyEncoding::MANUAL) {\n    return StreamEncoding::IDENTITY;\n  }\n  KJ_IF_SOME(encodingStr, headers.get(context.getHeaderIds().contentEncoding)) {\n    if (encodingStr == \"gzip\") {\n      return StreamEncoding::GZIP;\n    } else if (options.brotliEnabled && encodingStr == \"br\") {\n      return StreamEncoding::BROTLI;\n    }\n  }\n  return StreamEncoding::IDENTITY;\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/system-streams.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// Implementations of ReadableStreamSource / WritableStreamSink which wrap system streams (sockets),\n// handle encoding/decoding, and optimize pumping between them when possible.\n\n#include \"http.h\"\n\n#include <workerd/api/streams/common.h>  // for StreamEncoding, ...\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/io/io-context.h>\n\nnamespace workerd::api {\n\n// A ReadableStreamSource which automatically decodes its underlying stream. It does so lazily -- if\n// one of the `tryRead()` overloads is never called, then a `pumpTo()` to a WritableStreamSink\n// returned by `newSystemStream()` of the same encoding will not cause any decoding/encoding steps.\n//\n// NOTE: `inner` must be wholly-owned. In particular, it cannot contain references to JavaScript\n//   heap objects, as the stream is allowed to outlive the isolate, especially in the case of\n//   deferred proxying. If the inner stream for some reason contains JS references, you'll need\n//   to provide your own implementation of ReadableStreamSource.\nkj::Own<ReadableStreamSource> newSystemStream(kj::Own<kj::AsyncInputStream> inner,\n    StreamEncoding encoding,\n    IoContext& context = IoContext::current());\n\n// A WritableStreamSink which automatically encodes its underlying stream.\n//\n// NOTE: As with the other overload of newSystemStream(), `inner` must be wholly owned.\nkj::Own<WritableStreamSink> newSystemStream(kj::Own<kj::AsyncOutputStream> inner,\n    StreamEncoding encoding,\n    IoContext& context = IoContext::current());\n\nstruct SystemMultiStream {\n  kj::Own<ReadableStreamSource> readable;\n  kj::Own<WritableStreamSink> writable;\n};\n\n// A combo ReadableStreamSource and WritableStreamSink.\nSystemMultiStream newSystemMultiStream(kj::RefcountedWrapper<kj::Own<kj::AsyncIoStream>>& stream,\n    IoContext& context = IoContext::current());\n\nstruct ContentEncodingOptions {\n  bool brotliEnabled = false;\n  ContentEncodingOptions() = default;\n  ContentEncodingOptions(CompatibilityFlags::Reader flags);\n};\n\n// Get the Content-Encoding header from an HttpHeaders object as a StreamEncoding enum. Unsupported\n// encodings return IDENTITY.\nStreamEncoding getContentEncoding(IoContext& context,\n    const kj::HttpHeaders& headers,\n    Response::BodyEncoding bodyEncoding = Response::BodyEncoding::AUTO,\n    ContentEncodingOptions options = {});\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/tests/BUILD.bazel",
    "content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_binary\")\nload(\"//:build/wd_test.bzl\", \"wd_test\")\n\nwd_test(\n    src = \"settimeout-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"settimeout-test.js\"],\n)\n\nwd_test(\n    src = \"actor-alarms-delete-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"actor-alarms-delete-test.js\"],\n)\n\nwd_test(\n    src = \"delete-all-deletes-alarm-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"delete-all-deletes-alarm-test.js\"],\n)\n\nwd_test(\n    src = \"actor-alarms-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"actor-alarms-test.js\"],\n)\n\nwd_test(\n    src = \"tail-worker-test.wd-test\",\n    args = [\n        \"--experimental\",\n    ],\n    data = [\n        # To reduce complexity, tail-worker-test calls into other tests to get traces from them and\n        # includes several tail worker implementations.\n        \"actor-alarms-test.js\",\n        \"http-test.js\",\n        \"queue-test.js\",\n        \"tail-worker-test.js\",\n        \"tail-worker-test-receiver.js\",\n        \"tail-worker-test-dummy.js\",\n        \"tail-worker-test-invalid.js\",\n        \"tail-worker-test-jsrpc.js\",\n        \"websocket-hibernation.js\",\n    ],\n)\n\n# Test to validate timing semantics for JSRPC streaming responses.\n# This test verifies that Return events occur when the handler returns,\n# NOT when the stream is fully consumed.\nwd_test(\n    src = \"jsrpc-timing-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"jsrpc-timing-test.js\",\n        \"jsrpc-timing-test-tail.js\",\n    ],\n    tags = [\"no-coverage\"],\n)\n\n# Test for a worker that can log to itself using the isTracer parameter\nwd_test(\n    src = \"self-logger-test.wd-test\",\n    data = [\n        \"self-logger-test.js\",\n    ],\n)\n\nwd_test(\n    src = \"analytics-engine-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"analytics-engine-test.js\"],\n)\n\nwd_test(\n    src = \"http-standard-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"http-standard-test.js\"],\n)\n\nwd_test(\n    src = \"http-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"http-test.js\"],\n)\n\nwd_test(\n    src = \"ctx-props-test.wd-test\",\n    args = [\"--experimental\"],\n)\n\nwd_test(\n    src = \"cache-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"cache-instrumentation-test.js\",\n        \"cache-mock.js\",\n        \"cache-operations-test.js\",\n        \"instrumentation-tail-worker.js\",\n    ],\n)\n\nwd_test(\n    src = \"kv-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"instrumentation-tail-worker.js\",\n        \"kv-instrumentation-test.js\",\n        \"kv-test.js\",\n    ],\n)\n\nwd_test(\n    src = \"queue-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"queue-error-codes-test.js\",\n        \"queue-test.js\",\n    ],\n)\n\nwd_test(\n    src = \"r2-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"instrumentation-tail-worker.js\",\n        \"r2-instrumentation-test.js\",\n        \"r2-test.js\",\n    ],\n)\n\nwd_test(\n    src = \"rtti-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"rtti-test.js\"],\n)\n\nwd_test(\n    size = \"enormous\",\n    src = \"sql-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"instrumentation-tail-worker.js\",\n        \"sql-test.js\",\n        \"sql-test-tail.js\",\n    ],\n)\n\nwd_test(\n    src = \"sync-kv-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"instrumentation-tail-worker.js\",\n        \"sync-kv-instrumentation-test.js\",\n        \"sync-kv-test.js\",\n    ],\n)\n\nwd_test(\n    src = \"abort-internal-streams-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"abort-internal-streams-test.js\"],\n)\n\nwd_test(\n    src = \"abortable-fetch-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"abortable-fetch-test.js\"],\n)\n\nwd_test(\n    src = \"abortsignal-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"abortsignal-test.js\"],\n)\n\nwd_test(\n    src = \"actor-stub-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"actor-stub-test.js\"],\n)\n\nwd_test(\n    src = \"als-only-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"als-only-test.js\"],\n)\n\nwd_test(\n    src = \"als-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"als-test.js\"],\n)\n\nwd_test(\n    src = \"blob-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"blob-test.js\"],\n)\n\nwd_test(\n    src = \"blob2-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"blob2-test.js\"],\n)\n\nwd_test(\n    src = \"commonjs-module-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"commonjs-module-test.js\"],\n)\n\nwd_test(\n    src = \"crypto-extras-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"crypto-extras-test.js\"],\n)\n\nwd_test(\n    src = \"crypto-impl-asymmetric-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"crypto-impl-asymmetric-test.js\"],\n)\n\nwd_test(\n    src = \"crypto-streams-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"crypto-streams-test.js\"],\n)\n\nwd_test(\n    src = \"data-url-fetch-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"data-url-fetch-test.js\"],\n)\n\nwd_test(\n    src = \"encoding-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"encoding-test.js\"],\n)\n\nwd_test(\n    src = \"encoding-streams-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"encoding-streams-test.js\"],\n)\n\nwd_test(\n    src = \"events-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"events-test.js\"],\n)\n\nwd_test(\n    src = \"eventsource-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"eventsource-test.js\"],\n)\n\nwd_test(\n    src = \"form-data-legacy-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"form-data-legacy-test.js\"],\n)\n\nwd_test(\n    src = \"form-data-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"form-data-test.js\"],\n)\n\n# TODO(soon): Re-enable once it is determined why this test is failing\n# consistently in CI on Windows in all variant\n# wd_test(\n#     src = \"form-data-test-ts.wd-test\",\n#     args = [\"--experimental\"],\n#     data = [\"form-data-test-ts.ts\"],\n# )\n\nwd_test(\n    src = \"warnings-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"warnings-tail.js\",\n        \"warnings-test.js\",\n    ],\n)\n\nwd_test(\n    size = \"large\",\n    src = \"global-scope-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"global-scope-test.js\"],\n)\n\nwd_test(\n    size = \"large\",\n    src = \"unhandled-rejection-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"unhandled-rejection-test.js\"],\n)\n\nwd_test(\n    size = \"large\",\n    src = \"decompression-stream-unhandled-rejection-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"decompression-stream-unhandled-rejection-test.js\"],\n)\n\nwd_test(\n    size = \"large\",\n    src = \"htmlrewriter-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"htmlrewriter-test.js\"],\n    # TODO(soon): Test is excessively flaky under ASan, re-enable once fixed\n    tags = [\"no-asan\"],\n)\n\nwd_test(\n    src = \"js-rpc-flag.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"js-rpc-flag.js\"],\n)\n\nwd_test(\n    src = \"js-rpc-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"js-rpc-test.js\"],\n)\n\nwd_test(\n    src = \"js-rpc-params-ownership-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"js-rpc-params-ownership-test.js\"],\n)\n\nwd_test(\n    src = \"memory-cache-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"memory-cache-test.js\"],\n)\n\nwd_test(\n    src = \"module-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"module-test.js\"],\n)\n\nwd_test(\n    src = \"navigator-beacon-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"navigator-beacon-test.js\"],\n)\n\nwd_test(\n    src = \"navigator-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"navigator-test.js\"],\n)\n\nwd_test(\n    src = \"reporterror-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"reporterror-test.js\"],\n)\n\nwd_test(\n    src = \"response-json.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"response-json.js\"],\n)\n\nwd_test(\n    src = \"scheduler-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"scheduler-test.js\"],\n)\n\nwd_test(\n    src = \"streams-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"streams-test.js\"],\n)\n\nwd_test(\n    src = \"stub-storage-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"stub-storage-test.js\"],\n)\n\nwd_test(\n    src = \"compression-streams-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"compression-streams-test.js\"],\n)\n\nwd_test(\n    src = \"transform-streams-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"transform-streams-test.js\"],\n)\n\nwd_test(\n    src = \"pipe-streams-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"pipe-streams-test.js\"],\n)\n\nwd_test(\n    src = \"streams-r2-patterns-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"streams-r2-patterns-test.js\"],\n)\n\nwd_test(\n    src = \"streams-respond-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"streams-respond-test.js\"],\n)\n\nwd_test(\n    src = \"streams-no-auto-allocate-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"streams-no-auto-allocate-test.js\"],\n)\n\nwd_test(\n    src = \"streams-js-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"streams-js-test.js\"],\n)\n\nwd_test(\n    src = \"streams-iocontext-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"streams-iocontext-test.js\"],\n)\n\nwd_test(\n    src = \"streams-backpressure-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"streams-backpressure-test.js\"],\n)\n\nwd_test(\n    src = \"streams-error-edge-cases-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"streams-error-edge-cases-test.js\"],\n)\n\nwd_test(\n    src = \"streams-byob-edge-cases-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"streams-byob-edge-cases-test.js\"],\n)\n\nwd_test(\n    src = \"streams-tee-edge-cases-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"streams-tee-edge-cases-test.js\"],\n)\n\nwd_test(\n    src = \"streams-async-iterator-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"streams-async-iterator-test.js\"],\n)\n\nwd_test(\n    src = \"unsafe-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"unsafe-test.js\"],\n)\n\n# Test that verifies autogates are ENABLED - only runs in @all-autogates variant\nwd_test(\n    src = \"autogate-enabled-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"autogate-enabled-test.js\"],\n    generate_all_compat_flags_variant = False,\n    generate_default_variant = False,\n)\n\n# Test that verifies autogates are DISABLED - only runs in default variant\nwd_test(\n    src = \"autogate-disabled-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"autogate-disabled-test.js\"],\n    generate_all_autogates_variant = False,\n)\n\n# Test that verifies compat flags are ENABLED - only runs in @all-compat-flags variant\nwd_test(\n    src = \"compat-flag-enabled-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"compat-flag-enabled-test.js\"],\n    generate_all_autogates_variant = False,\n    generate_default_variant = False,\n)\n\n# Test that verifies compat flags are DISABLED - only runs in default variant\nwd_test(\n    src = \"compat-flag-disabled-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"compat-flag-disabled-test.js\"],\n    generate_all_autogates_variant = False,\n    generate_all_compat_flags_variant = False,\n)\n\nwd_test(\n    src = \"url-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"url-test.js\"],\n)\n\nwd_test(\n    src = \"websocket-allow-half-open-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"websocket-allow-half-open-test.js\"],\n)\n\nwd_test(\n    src = \"websocket-constructor-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"websocket-constructor-test.js\"],\n)\n\nwd_test(\n    src = \"websocket-hibernation.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"websocket-hibernation.js\"],\n)\n\njs_binary(\n    name = \"websocket-client-error-sidecar\",\n    entry_point = \"websocket-client-error-sidecar.js\",\n)\n\nwd_test(\n    src = \"websocket-client-error-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"websocket-client-error-test.js\",\n    ],\n    sidecar = \"websocket-client-error-sidecar\",\n    sidecar_port_bindings = [\n        \"BIG_MESSAGE_SERVER_PORT\",\n    ],\n)\n\njs_binary(\n    name = \"http-socket-server\",\n    entry_point = \"http-socket-server.js\",\n)\n\nwd_test(\n    src = \"http-socket-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"http-socket-test.js\",\n        \"starttls-server.pem\",\n    ],\n    sidecar = \"http-socket-server\",\n    sidecar_port_bindings = [\n        \"HTTP_SOCKET_SERVER_PORT\",\n        \"SOCKET_PARTIALLY_WRITTEN\",\n        \"STARTTLS_SOCKET\",\n        \"FLUSH_HELLO_SOCKET\",\n    ],\n)\n\nwd_test(\n    src = \"js-rpc-socket-test.wd-test\",\n    args = [\n        \"--experimental\",\n        \"--no-verbose\",\n    ],\n    data = [\"js-rpc-test.js\"],\n)\n\nwd_test(\n    src = \"http-test-ts.ts-wd-test\",\n    args = [\"--experimental\"],\n    data = [\"http-test-ts.ts\"],\n)\n\nwd_test(\n    src = \"new-module-registry-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"new-module-registry-test.js\",\n        \"test.wasm\",\n    ],\n)\n\nwd_test(\n    src = \"new-module-registry-ts-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"new-module-registry-ts-test.js\",\n        \"new-module-registry-ts-test-helper.ts\",\n    ],\n)\n\nwd_test(\n    src = \"new-module-registry-dns-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"new-module-registry-dns-test.js\",\n    ],\n)\n\nwd_test(\n    src = \"new-module-registry-node-filter-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"new-module-registry-node-filter-test.js\",\n    ],\n)\n\nwd_test(\n    src = \"cross-context-promise-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"cross-context-promise-test.js\"],\n)\n\nwd_test(\n    src = \"error-in-error-event-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"error-in-error-event-test.js\"],\n)\n\nwd_test(\n    src = \"no-to-string-tag-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"no-to-string-tag-test.js\"],\n    generate_all_compat_flags_variant = False,\n)\n\nwd_test(\n    src = \"fetch-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"fetch-test.js\"],\n)\n\nwd_test(\n    src = \"importable-env-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"importable-env-test.js\"],\n)\n\nwd_test(\n    src = \"disable-importable-env-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"disable-importable-env-test.js\"],\n)\n\nwd_test(\n    src = \"disable-importable-exports-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"disable-importable-exports-test.js\"],\n)\n\nwd_test(\n    src = \"importable-exports-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"importable-exports-test.js\"],\n)\n\nwd_test(\n    src = \"webfs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"webfs-test.js\"],\n)\n\nwd_test(\n    src = \"worker-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"worker-test.js\"],\n)\n\nwd_test(\n    src = \"request-clone-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"request-clone-test.js\"],\n)\n\nwd_test(\n    src = \"request-signal-enabled.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"request-signal-enabled.js\"],\n)\n\nwd_test(\n    src = \"request-signal-disabled.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"request-signal-disabled.js\"],\n)\n\nwd_test(\n    src = \"request-signal-passthrough.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"request-signal-passthrough.js\"],\n)\n\nwd_test(\n    size = \"large\",\n    src = \"worker-loader-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"worker-loader-test.js\"],\n    tags = [\"requires-network\"],\n)\n\nwd_test(\n    src = \"leak-fetch-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"leak-fetch-test.js\"],\n)\n\nwd_test(\n    src = \"messageport-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"messageport-test.js\"],\n)\n\njs_binary(\n    name = \"starttls-nodejs-server\",\n    entry_point = \"starttls-nodejs-server.js\",\n)\n\nwd_test(\n    src = \"starttls-nodejs-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"starttls-nodejs-test.js\",\n        \"starttls-server.pem\",\n    ],\n    sidecar = \"starttls-nodejs-server\",\n    sidecar_port_bindings = [\n        \"STARTTLS_CA_PORT\",\n    ],\n)\n\nwd_test(\n    src = \"streams-circ-ref-regression-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"streams-circ-ref-regression-test.js\"],\n)\n\nwd_test(\n    src = \"experimental-eval-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"experimental-eval-test.js\"],\n)\n\nwd_test(\n    src = \"fetch-redirect-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"fetch-redirect-test.js\"],\n)\n\nwd_test(\n    src = \"rpc-error-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"rpc-error-test.js\",\n        \"rpc-error-test.rpc.js\",\n    ],\n)\n\nwd_test(\n    src = \"actor-kv-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"actor-kv-test.js\",\n        \"actor-kv-test-tail.js\",\n        \"instrumentation-tail-worker.js\",\n    ],\n)\n\nwd_test(\n    src = \"headers-immutable-prototype-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"headers-immutable-prototype-test.js\"],\n)\n\nwd_test(\n    src = \"identity-transform-stream-state-machine-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"identity-transform-stream-state-machine-test.js\"],\n)\n\nwd_test(\n    src = \"response-used-body-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"response-used-body-test.js\"],\n)\n\nwd_test(\n    src = \"buffer-indexof-odd-offset-ucs2-test.wd-test\",\n    data = [\"buffer-indexof-odd-offset-ucs2-test.js\"],\n)\n\nwd_test(\n    src = \"textdecoder-utf16-odd-offset-test.wd-test\",\n    data = [\"textdecoder-utf16-odd-offset-test.js\"],\n)\n\nwd_test(\n    src = \"byob-reader-resize-pending-read-test.wd-test\",\n    data = [\"byob-reader-resize-pending-read-test.js\"],\n)\n\nwd_test(\n    src = \"pipe-write-special-buffer-test.wd-test\",\n    data = [\"pipe-write-special-buffer-test.js\"],\n)\n\nwd_test(\n    src = \"htmlrewriter-transform-cancel-test.wd-test\",\n    data = [\"htmlrewriter-transform-cancel-test.js\"],\n)\n"
  },
  {
    "path": "src/workerd/api/tests/abort-internal-streams-test.js",
    "content": "import { strictEqual } from 'assert';\n\nexport const abortInternalStreamsTest = {\n  async test() {\n    const { writable } = new IdentityTransformStream();\n\n    const writer = writable.getWriter();\n\n    const promise = writer.write(new Uint8Array(10));\n\n    await writer.abort();\n\n    // The write promise should abort proactively without waiting for a read,\n    // indicating that the queue was drained proactively when the abort was\n    // called.\n    try {\n      await promise;\n      throw new Error('The promise should have been rejected');\n    } catch (err) {\n      strictEqual(err, undefined);\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/abort-internal-streams-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"abort-internal-streams-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"abort-internal-streams-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat_v2\", \"internal_writable_stream_abort_clears_queue\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/abortable-fetch-test.js",
    "content": "import { strictEqual, ok, throws } from 'node:assert';\n\n// Test for the AbortSignal and AbortController standard Web API implementations.\n// The implementation for these are in api/basics.{h|c++}\n\nexport const abortControllerAlreadyAborted = {\n  async test(ctrl, env) {\n    const ac = new AbortController();\n    ac.abort();\n    try {\n      const result = await env.subrequest.fetch('http://example.org', {\n        signal: ac.signal,\n      });\n      throw new Error('should have failed');\n    } catch (err) {\n      strictEqual(err.message, 'The operation was aborted');\n    }\n  },\n};\n\nexport const alreadyAborted = {\n  async test(ctrl, env) {\n    const signal = AbortSignal.abort('boom');\n    try {\n      await env.subrequest.fetch('http://example.org', { signal });\n      throw new Error('should have failed');\n    } catch (err) {\n      strictEqual(err, 'boom');\n    }\n  },\n};\n\nexport const timedAbort = {\n  async test(ctrl, env) {\n    const signal = AbortSignal.timeout(100);\n    try {\n      await env.subrequest.fetch('http://example.org', { signal });\n      throw new Error('should have failed');\n    } catch (err) {\n      strictEqual(err.message, 'The operation was aborted due to timeout');\n    }\n  },\n};\n\nexport const abortControllerSyncAbort = {\n  async test(ctrl, env) {\n    const ac = new AbortController();\n    try {\n      const promise = env.subrequest.fetch('http://example.org', {\n        signal: ac.signal,\n      });\n      ac.abort();\n      await promise;\n      throw new Error('should have failed');\n    } catch (err) {\n      strictEqual(err.message, 'The operation was aborted');\n    }\n  },\n};\n\nexport const asyncSubrequest = {\n  async test(ctrl, env) {\n    try {\n      await env.subrequest.fetch('http://example.org/sub');\n      throw new Error('should have failed');\n    } catch (err) {\n      strictEqual(err.message, 'The operation was aborted due to timeout');\n    }\n  },\n};\n\nexport const syncSubrequest = {\n  async test(ctrl, env) {\n    try {\n      await env.subrequest.fetch('http://example.org/subsync');\n      throw new Error('should have failed');\n    } catch (err) {\n      strictEqual(err.message, 'The operation was aborted');\n    }\n  },\n};\n\nexport const requestAbortSignal = {\n  test() {\n    // The request object has an AbortSignal, even if never used.\n    const req1 = new Request('');\n    ok(Reflect.has(req1, 'signal'));\n\n    // request.signal should be the one passed in.\n    const ac = new AbortController();\n    const req2 = new Request('', { signal: ac.signal });\n    strictEqual(req2.signal, ac.signal);\n  },\n};\n\nexport default {\n  async fetch(request, env) {\n    if (request.url.endsWith('/sub')) {\n      // Verifies that a fetch subrequest returned as the response can be canceled\n      // asynchronously successfully.\n      const signal = AbortSignal.timeout(100);\n      return env.subrequest.fetch('http://example.org', { signal });\n    } else if (request.url.endsWith('/subsync')) {\n      // Verifies that a fetch subrequest returned as the response can be synchronously\n      // aborted.\n      const ac = new AbortController();\n      const resp = env.subrequest.fetch('http://example.org', {\n        signal: ac.signal,\n      });\n      ac.abort();\n      return resp;\n    } else {\n      await scheduler.wait(10000);\n      return new Response('ok');\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/abortable-fetch-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"abortable-fetch-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"abortable-fetch-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"url_original\", \"fetch_legacy_url\"],\n        bindings = [\n          (name = \"subrequest\", service = \"abortable-fetch-test\")\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/abortsignal-test.js",
    "content": "import { strictEqual, ok, throws, rejects } from 'node:assert';\nimport { WorkerEntrypoint, RpcTarget } from 'cloudflare:workers';\n\n// Test for the AbortSignal and AbortController standard Web API implementations.\n// The implementation for these are in api/basics.{h|c++}\n\nclass WrappedAbortSignal extends RpcTarget {\n  constructor() {\n    super();\n    this.ac = new AbortController();\n  }\n\n  forget() {\n    this.ac.signal.skipReleaseForTest();\n  }\n\n  getSignal() {\n    return this.ac.signal;\n  }\n}\n\nlet globalAbortController;\nexport class RpcRemoteEnd extends WorkerEntrypoint {\n  async echo(signal) {\n    return signal;\n  }\n\n  async countToInfinity(signal) {\n    let onAbortWasFired = false;\n\n    signal.onabort = () => {\n      onAbortWasFired = true;\n    };\n\n    for (let i = 0; ; i++) {\n      await scheduler.wait(50);\n      if (signal.aborted) {\n        return { counter: i, reason: signal.reason, onAbortWasFired };\n      }\n    }\n  }\n\n  async countToInfinityWithRequest(req) {\n    return this.countToInfinity(req.signal);\n  }\n\n  async countToInfinityWithTimeout(remoteSignal) {\n    let timeout = AbortSignal.timeout(1000);\n    let signal = AbortSignal.any([timeout, remoteSignal]);\n    return this.countToInfinity(signal);\n  }\n\n  async ignoreSignal(signal) {\n    let i = 0;\n\n    for (i = 0; i < 10; i++) {\n      await scheduler.wait(50);\n    }\n\n    return { counter: i, reason: signal.reason };\n  }\n\n  async chainReaction(signal) {\n    let onAbortWasFired = false;\n\n    signal.onabort = () => {\n      onAbortWasFired = true;\n    };\n\n    const inner = await this.env.RpcRemoteEnd.countToInfinity(signal);\n    return { inner, reason: signal.reason, onAbortWasFired };\n  }\n\n  async tryUsingGlobalAbortController() {\n    if (globalAbortController === undefined) {\n      globalAbortController = new AbortController();\n      await this.env.RpcRemoteEnd.echo(globalAbortController.signal); // send the signal over\n    } else {\n      globalAbortController.abort(new Error('boom?'));\n    }\n  }\n\n  async getWrappedSignal() {\n    return new WrappedAbortSignal();\n  }\n}\n\nexport const abortcontroller = {\n  test() {\n    // AbortSignal is not directly creatable\n    throws(() => new AbortSignal());\n\n    const ac = new AbortController();\n    ok(ac.signal instanceof AbortSignal);\n    strictEqual(ac.signal.aborted, false);\n\n    // every call to ac.signal should always be the same value.\n    strictEqual(ac.signal, ac.signal);\n\n    // signal is read only\n    throws(() => (ac.signal = 1));\n\n    let invoked = 0;\n    ac.signal.onabort = (event) => {\n      invoked++;\n      strictEqual(event.type, 'abort');\n    };\n\n    // Will not throw because the signal is not aborted\n    ac.signal.throwIfAborted();\n\n    // reason and aborted are read only\n    throws(() => (ac.signal.reason = 1));\n    throws(() => (ac.signal.aborted = 'foo'));\n\n    // trigger our abort with a default reason...\n    ac.abort();\n\n    // This one shouldn't get called since it is added after the abort\n    ac.signal.addEventListener('abort', () => {\n      throw new Error('should not have been called');\n    });\n\n    // Will throw because the signal is now aborted.\n    throws(() => ac.signal.throwIfAborted());\n\n    strictEqual(ac.signal.aborted, true);\n    strictEqual(ac.signal.reason.message, 'The operation was aborted');\n    strictEqual(ac.signal.reason.name, 'AbortError');\n\n    // Abort can be called multiple times with no effect.\n    ac.abort();\n\n    strictEqual(invoked, 1);\n  },\n};\n\nexport const abortcontrollerWithReason = {\n  test() {\n    const ac = new AbortController();\n    ok(ac.signal instanceof AbortSignal);\n    strictEqual(ac.signal.aborted, false);\n\n    let invoked = 0;\n\n    ac.signal.addEventListener('abort', (event) => {\n      invoked++;\n      strictEqual(ac.signal.reason, 'foo');\n    });\n\n    ac.abort('foo');\n    strictEqual(ac.signal.aborted, true);\n    strictEqual(ac.signal.reason, 'foo');\n\n    strictEqual(invoked, 1);\n  },\n};\n\nexport const alreadyAborted = {\n  test() {\n    const aborted = AbortSignal.abort();\n    strictEqual(aborted.aborted, true);\n    throws(() => aborted.throwIfAborted());\n\n    const abortedWithReason = AbortSignal.abort('foo');\n    strictEqual(abortedWithReason.aborted, true);\n    try {\n      abortedWithReason.throwIfAborted();\n      throw new Error('should have thrown');\n    } catch (err) {\n      strictEqual(err, 'foo');\n    }\n  },\n};\n\nexport const timedAbort = {\n  async test() {\n    const timed = AbortSignal.timeout(100);\n    let resolve;\n    const promise = new Promise((r) => (resolve = r));\n    let invoked = 0;\n    timed.onabort = () => {\n      invoked++;\n      resolve();\n    };\n    await promise;\n    strictEqual(invoked, 1);\n  },\n};\n\nexport const anyAbort = {\n  async test() {\n    // Set a timeout way in the future so this one doesn't happen first.\n    const timed = AbortSignal.timeout(1000000);\n    const ac = new AbortController();\n\n    // Creates an AbortSignal that will be triggered when either of the two\n    // given signals is triggered.\n    const any = AbortSignal.any([timed, ac.signal]);\n\n    let invoked = 0;\n    any.onabort = () => {\n      invoked++;\n    };\n\n    ac.abort();\n\n    strictEqual(invoked, 1);\n  },\n};\n\nexport const anyAbort2 = {\n  async test() {\n    const timed = AbortSignal.timeout(100);\n    const ac = new AbortController();\n    const any = AbortSignal.any([timed, ac.signal]);\n\n    let invoked = 0;\n    let resolve;\n    const promise = new Promise((r) => (resolve = r));\n\n    any.onabort = () => {\n      invoked++;\n      resolve();\n    };\n\n    await promise;\n\n    strictEqual(invoked, 1);\n  },\n};\n\nexport const anyAbort3 = {\n  async test() {\n    const timed = AbortSignal.timeout(1000000);\n    const aborted = AbortSignal.abort(123);\n    // If one of the signals is already abort, the any signal will be\n    // immediately aborted also.\n    const any = AbortSignal.any([timed, aborted]);\n    strictEqual(any.aborted, true);\n    strictEqual(any.reason, 123);\n  },\n};\n\nfunction initAny(signal, resolve) {\n  const any = AbortSignal.any([signal]);\n  any.onabort = () => {\n    resolve();\n  };\n}\n\nexport const anyAbort4 = {\n  async test() {\n    // Reproduces a failure seen under asan.\n    const ac = new AbortController();\n    ac.signal.addEventListener('abort', (event) => {});\n    const { promise, resolve } = Promise.withResolvers();\n\n    // Set up AbortSignal.any() to call \"resolve\" when ac.signal aborts.  We use a separate\n    // function to avoid accidentally capturing references in this scope.\n    initAny(ac.signal, resolve);\n\n    gc();\n    ac.abort();\n    await promise;\n  },\n};\n\nexport const onabortPrototypeProperty = {\n  test() {\n    const ac = new AbortController();\n    ok('onabort' in AbortSignal.prototype);\n    strictEqual(ac.signal.onabort, null);\n    delete ac.signal.onabort;\n    ok('onabort' in AbortSignal.prototype);\n    strictEqual(ac.signal.onabort, null);\n    let called = false;\n    ac.signal.onabort = () => {\n      called = true;\n    };\n    ac.abort();\n    ok(called);\n\n    // Setting the value to something other than a function or object\n    // should cause the value to become null.\n    [123, null, 'foo'].forEach((v) => {\n      ac.signal.onabort = () => {};\n      ac.signal.onabort = v;\n      strictEqual(ac.signal.onabort, null);\n    });\n\n    const handler = {};\n    ac.signal.onabort = handler;\n    strictEqual(ac.signal.onabort, handler);\n  },\n};\n\nexport const rpcUnusedSignal = {\n  async test(ctrl, env, ctx) {\n    const ac = new AbortController();\n    const responseSignal = await env.RpcRemoteEnd.echo(ac.signal);\n\n    ok(responseSignal instanceof AbortSignal);\n    strictEqual(responseSignal.aborted, false);\n    strictEqual(responseSignal.reason, undefined);\n  },\n};\n\nexport const rpcNeverAbortsSignal = {\n  async test(ctrl, env, ctx) {\n    const otherRequest = new Request('http://example.com');\n\n    const responseSignal = await env.RpcRemoteEnd.echo(otherRequest.signal);\n    ok(responseSignal instanceof AbortSignal);\n    strictEqual(responseSignal.aborted, false);\n    strictEqual(responseSignal.reason, undefined);\n  },\n};\n\nexport const rpcAbortSignalTimeout = {\n  async test(ctrl, env, ctx) {\n    const signal = AbortSignal.timeout(200);\n    const res = await env.RpcRemoteEnd.countToInfinity(signal);\n\n    // We don't care the exact value it got to, but at least 1 iteration should have happened\n    ok(res.counter >= 1);\n\n    // Make sure the reason was passed without being garbled\n    ok(res.reason instanceof DOMException);\n    strictEqual(res.reason.message, 'The operation was aborted due to timeout');\n\n    // Make sure an event was dispatched on the remote side\n    ok(res.onAbortWasFired);\n  },\n};\n\nexport const rpcAbortSignalAbort = {\n  async test(ctrl, env, ctx) {\n    // NB: AbortSignal.abort returns an abort signal that is already aborted\n    const expectedReason = \"just didn't feel like it\";\n    const signal = AbortSignal.abort(expectedReason);\n    const res = await env.RpcRemoteEnd.countToInfinity(signal);\n\n    // No iterations should have happened\n    strictEqual(res.counter, 0);\n\n    // Make sure the reason was passed without being garbled\n    strictEqual(res.reason, \"just didn't feel like it\");\n\n    // No event is dispatched on an already aborted signal\n    ok(!res.onAbortWasFired);\n  },\n};\n\nexport const rpcAbortControllerSignal = {\n  async test(ctrl, env, ctx) {\n    const ac = new AbortController();\n    const resPromise = env.RpcRemoteEnd.countToInfinity(ac.signal);\n\n    // Wait an arbitrary amount of time, then use the AbortController to abort the remote end.\n    await scheduler.wait(200);\n    const expectedReason = 'changed my mind';\n    ac.abort(expectedReason);\n\n    const res = await resPromise;\n\n    // We don't care the exact value it got to, but at least 1 iteration should have happened\n    ok(res.counter >= 1);\n\n    // Make sure the reason was passed without being garbled\n    strictEqual(res.reason, expectedReason);\n\n    // Make sure an event was dispatched on the remote side\n    ok(res.onAbortWasFired);\n  },\n};\n\nexport const rpcAbortControllerSignalNoReasonProvided = {\n  async test(ctrl, env, ctx) {\n    const ac = new AbortController();\n    const resPromise = env.RpcRemoteEnd.countToInfinity(ac.signal);\n\n    // Wait an arbitrary amount of time, then use the AbortController to abort the remote end.\n    await scheduler.wait(200);\n    ac.abort();\n\n    const res = await resPromise;\n\n    // We don't care the exact value it got to, but at least 1 iteration should have happened\n    ok(res.counter >= 1);\n\n    // Make sure the reason was passed without being garbled\n    ok(res.reason instanceof DOMException);\n    strictEqual(res.reason.message, 'The operation was aborted');\n\n    // Make sure an event was dispatched on the remote side\n    ok(res.onAbortWasFired);\n  },\n};\n\nexport const rpcAbortSignalFurtherCloned = {\n  async test(ctrl, env, ctx) {\n    const ac = new AbortController();\n    const resPromise = env.RpcRemoteEnd.chainReaction(ac.signal);\n\n    // Wait an arbitrary amount of time, then use the AbortController to abort the remote end.\n    await scheduler.wait(200);\n    const expectedReason = 'changed my mind';\n    ac.abort(expectedReason);\n\n    const res = await resPromise;\n\n    // We don't care the exact value it got to, but at least 1 iteration should have happened\n    ok(res.inner.counter >= 1);\n\n    // Make sure the reason was passed without being garbled\n    strictEqual(res.reason, expectedReason);\n    strictEqual(res.inner.reason, expectedReason);\n\n    // Make sure an event was dispatched on the remote side\n    ok(res.onAbortWasFired);\n    ok(res.inner.onAbortWasFired);\n  },\n};\n\nexport const rpcAbortSignalManyClients = {\n  async test(ctrl, env, ctx) {\n    const signal = AbortSignal.timeout(200);\n\n    const responses = await Promise.all(\n      Array.from({ length: 5 }, () => env.RpcRemoteEnd.countToInfinity(signal))\n    );\n    strictEqual(responses.length, 5);\n\n    for (const res of responses) {\n      // We don't care the exact value it got to, but at least 1 iteration should have happened\n      ok(res.counter >= 1);\n\n      // Make sure the reason was passed without being garbled\n      ok(res.reason instanceof DOMException);\n      strictEqual(\n        res.reason.message,\n        'The operation was aborted due to timeout'\n      );\n\n      // Make sure an event was dispatched on the remote side\n      ok(res.onAbortWasFired);\n    }\n  },\n};\n\nexport const rpcAbortSignalAny = {\n  async test(ctrl, env, ctx) {\n    const unusedAc = new AbortController();\n    const signal = AbortSignal.any([AbortSignal.timeout(200), unusedAc.signal]);\n    const res = await env.RpcRemoteEnd.countToInfinity(signal);\n\n    // We don't care the exact value it got to, but at least 1 iteration should have happened\n    ok(res.counter >= 1);\n\n    // Make sure the reason was passed without being garbled\n    ok(res.reason instanceof DOMException);\n    strictEqual(res.reason.message, 'The operation was aborted due to timeout');\n\n    // Make sure an event was dispatched on the remote side\n    ok(res.onAbortWasFired);\n  },\n};\n\nexport const rpcAbortSignalAnyOnRemoteEnd = {\n  async test(ctrl, env, ctx) {\n    const ac = new AbortController();\n    const resPromise = env.RpcRemoteEnd.countToInfinityWithTimeout(ac.signal);\n\n    // Wait an arbitrary amount of time, then use the AbortController to abort the remote end.\n    await scheduler.wait(200);\n    const expectedReason =\n      'our timeout triggered before the 1000ms timeout on the other side';\n    ac.abort(expectedReason);\n\n    const res = await resPromise;\n\n    // We don't care the exact value it got to, but at least 1 iteration should have happened\n    ok(res.counter >= 1);\n\n    // Make sure the reason was passed without being garbled\n    strictEqual(res.reason, expectedReason);\n\n    // Make sure an event was dispatched on the remote side\n    ok(res.onAbortWasFired);\n  },\n};\n\nexport const rpcRequestSignal = {\n  async test(ctrl, env, ctx) {\n    // Construct a request holding an AbortSignal, and then send this request to the other side\n    // Note that this signal isn't affected by the request_signal_passthrough compat flag, which\n    // only modifies the behaviour of the signal on the incoming request.\n    const req = new Request('http://example.com', {\n      signal: AbortSignal.timeout(200),\n    });\n\n    const res = await env.RpcRemoteEnd.countToInfinityWithRequest(req);\n\n    // We don't care the exact value it got to, but at least 1 iteration should have happened\n    ok(res.counter >= 1);\n\n    // Make sure the reason was passed without being garbled\n    ok(res.reason instanceof DOMException);\n    strictEqual(res.reason.message, 'The operation was aborted due to timeout');\n\n    // Make sure an event was dispatched on the remote side\n    ok(res.onAbortWasFired);\n  },\n};\n\nexport const rpcCrossRequestSignal = {\n  async test(ctrl, env, ctx) {\n    // Save an AbortController in the global scope\n    await env.RpcRemoteEnd.tryUsingGlobalAbortController();\n\n    // Try to use it again\n    await rejects(\n      async () => env.RpcRemoteEnd.tryUsingGlobalAbortController(),\n      {\n        name: 'Error',\n        message:\n          \"Cannot perform I/O on behalf of a different request. I/O objects (such as streams, request/response bodies, and others) created in the context of one request handler cannot be accessed from a different request's handler. This is a limitation of Cloudflare Workers which allows us to improve overall performance. (I/O type: RefcountedCanceler)\",\n      }\n    );\n  },\n};\n\nexport const rpcRemoteCanIgnoreSignal = {\n  async test(ctrl, env, ctx) {\n    const ac = new AbortController();\n    const resPromise = env.RpcRemoteEnd.ignoreSignal(ac.signal);\n\n    // Wait an arbitrary amount of time, then use the AbortController to abort the remote end.\n    await scheduler.wait(200);\n    const expectedReason = 'changed my mind';\n    ac.abort(expectedReason);\n\n    const res = await resPromise;\n\n    // Every iteration completes, the remote is not reacting to the abort\n    strictEqual(res.counter, 10);\n\n    // Make sure the reason was passed without being garbled\n    strictEqual(res.reason, expectedReason);\n  },\n};\n\nexport const rpcDestroySignalUnclean = {\n  async test(ctrl, env, ctx) {\n    // wrapper is a RPCTarget that just holds an AbortSignal\n    const wrapper = await env.RpcRemoteEnd.getWrappedSignal();\n\n    // Get our clone of the signal\n    const signal = await wrapper.getSignal();\n\n    // Tell the AbortSignal not to send a release message on disposal\n    await wrapper.forget();\n\n    // Destroy the wrapper\n    wrapper[Symbol.dispose]();\n\n    // No release message was sent, our clone will provide a message explaining the other side is\n    // gone.\n    ok(signal.aborted);\n    strictEqual(\n      signal.reason.message,\n      'An AbortSignal received over RPC was implicitly aborted because the connection back to its ' +\n        'trigger was lost.'\n    );\n  },\n};\n\nexport const rpcDestroySignalClean = {\n  async test(ctrl, env, ctx) {\n    // wrapper is a RPCTarget that just holds an AbortSignal\n    const wrapper = await env.RpcRemoteEnd.getWrappedSignal();\n\n    // Get our clone of the signal\n    const signal = await wrapper.getSignal();\n\n    // Destroy the wrapper\n    wrapper[Symbol.dispose]();\n\n    // A release message was sent, the signal will remain in an unaborted state\n    ok(!signal.aborted);\n    strictEqual(signal.reason, undefined);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/abortsignal-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"abortsignal-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"abortsignal-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"enable_abortsignal_rpc\", \"experimental\"],\n        bindings = [\n          (name = \"RpcRemoteEnd\", service = (name = \"abortsignal-test\", entrypoint = \"RpcRemoteEnd\")),\n        ]\n      )\n    ),\n  ],\n  v8Flags = [\"--expose-gc\"]\n);\n"
  },
  {
    "path": "src/workerd/api/tests/actor-alarms-delete-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// This test is aimed towards validating a correct deleteAlarm behavior in workerd.\n// Currently running alarms cannot be deleted within alarm() handler, but can delete it everywhere\n// else.\nimport * as assert from 'node:assert';\n\nexport class DurableObjectExample {\n  constructor(state) {\n    this.state = state;\n  }\n\n  async waitForAlarm(scheduledTime) {\n    let self = this;\n    const { promise, resolve, reject } = Promise.withResolvers();\n    self.resolve = resolve;\n    self.reject = reject;\n\n    try {\n      await promise;\n      if (Date.now() < scheduledTime.valueOf()) {\n        throw new Error(\n          `Date.now() is before scheduledTime! ${Date.now()} vs ${scheduledTime.valueOf()}`\n        );\n      }\n    } catch (e) {\n      throw new Error(\n        `error waiting for alarm at ${scheduledTime.valueOf()}: ${e}`\n      );\n    }\n\n    let alarm = await this.state.storage.getAlarm();\n    if (alarm != null) {\n      throw new Error(`alarm time not cleared when handler ends. ${alarm}`);\n    }\n  }\n\n  async alarm() {\n    try {\n      this.state.alarmsTriggered++;\n      let time = await this.state.storage.getAlarm();\n      if (time !== null) {\n        throw new Error(`time not null inside alarm handler ${time}`);\n      }\n      // Deleting an alarm inside `alarm()` will not have any effect, unless there's another queued alarm\n      // already.\n      await this.state.storage.deleteAlarm();\n\n      // On the other hand, if we have an alarm queued, it will be deleted. If this is working properly,\n      // we'll only have one alarm triggered.\n      await this.state.storage.setAlarm(Date.now() + 50);\n      await this.state.storage.deleteAlarm();\n\n      // All done inside `alarm()`.\n      this.resolve();\n    } catch (e) {\n      this.reject(e);\n    }\n  }\n\n  async fetch() {\n    this.state.alarmsTriggered = 0;\n    // We set an alarm that will never trigger because it gets deleted before running.\n    await this.state.storage.setAlarm(Date.now() + 500);\n    await this.state.storage.deleteAlarm();\n\n    // We set another alarm that will run in 0.5s to test that deleting an alarm inside its handler\n    // does not cause a crash.\n    const time = Date.now() + 500;\n    await this.state.storage.setAlarm(time);\n    assert.equal(await this.state.storage.getAlarm(), time);\n\n    // We should wait for all alarms the run before returning.\n    await this.waitForAlarm(time);\n\n    // We should have ran `alarm()` only once.\n    assert.equal(this.state.alarmsTriggered, 1);\n\n    // All done, return \"OK\" because if we reach this, everything worked as expected.\n    return new Response('OK');\n  }\n}\n\nexport default {\n  async test(ctrl, env, ctx) {\n    let id = env.ns.idFromName('A');\n    let obj = env.ns.get(id);\n    let res = await obj.fetch('http://foo/test');\n    let text = await res.text();\n    assert.equal(text, 'OK');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/actor-alarms-delete-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n    (name = \"TEST_TMPDIR\", disk = (writable = true)),\n  ],\n);\n\nconst mainWorker :Workerd.Worker = (\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n\n  modules = [\n    (name = \"worker\", esModule = embed \"actor-alarms-delete-test.js\"),\n  ],\n\n  durableObjectNamespaces = [\n    (className = \"DurableObjectExample\", uniqueKey = \"210bd0cbd803ef7883a1ee9d86cce06e\"),\n  ],\n\n  durableObjectStorage = (localDisk = \"TEST_TMPDIR\"),\n\n  bindings = [\n    (name = \"ns\", durableObjectNamespace = \"DurableObjectExample\"),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/actor-alarms-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport * as assert from 'node:assert';\n\nexport class DurableObjectExample {\n  constructor(state, env) {\n    this.state = state;\n  }\n\n  async waitForAlarm(scheduledTime) {\n    let self = this;\n    const { promise, resolve, reject } = Promise.withResolvers();\n    self.resolve = resolve;\n    self.reject = reject;\n\n    try {\n      await promise;\n      if (Date.now() < scheduledTime.valueOf()) {\n        throw new Error(\n          `Date.now() is before scheduledTime! ${Date.now()} vs ${scheduledTime.valueOf()}`\n        );\n      }\n    } catch (e) {\n      throw new Error(\n        `error waiting for alarm at ${scheduledTime.valueOf()}: ${e}`\n      );\n    }\n\n    let alarm = await this.state.storage.getAlarm();\n    if (alarm != null) {\n      throw new Error(`alarm time not cleared when handler ends. ${alarm}`);\n    }\n  }\n\n  async fetch() {\n    const time = Date.now() + 50;\n    await this.state.storage.setAlarm(time);\n    assert.equal(await this.state.storage.getAlarm(), time);\n\n    await this.waitForAlarm(time);\n\n    return new Response('OK');\n  }\n\n  async alarm() {\n    try {\n      let time = await this.state.storage.getAlarm();\n      if (time !== null) {\n        throw new Error(`time not null inside alarm handler ${time}`);\n      }\n      this.resolve();\n    } catch (e) {\n      this.reject(e);\n    }\n  }\n}\n\nexport const test = {\n  async test(ctrl, env, ctx) {\n    let id = env.ns.idFromName('A');\n    let obj = env.ns.get(id);\n    let res = await obj.fetch('http://foo/test');\n    let text = await res.text();\n    assert.equal(text, 'OK');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/actor-alarms-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n    (name = \"TEST_TMPDIR\", disk = (writable = true)),\n  ],\n);\n\nconst mainWorker :Workerd.Worker = (\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n\n  modules = [\n    (name = \"worker\", esModule = embed \"actor-alarms-test.js\"),\n  ],\n\n  durableObjectNamespaces = [\n    (className = \"DurableObjectExample\", uniqueKey = \"210bd0cbd803ef7883a1ee9d86cce06e\"),\n  ],\n\n  durableObjectStorage = (localDisk = \"TEST_TMPDIR\"),\n\n  bindings = [\n    (name = \"ns\", durableObjectNamespace = \"DurableObjectExample\"),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/actor-kv-test-tail.js",
    "content": "import * as assert from 'node:assert';\nimport {\n  invocationPromises,\n  spans,\n  testTailHandler,\n} from 'test:instrumentation-tail';\n\n// Use shared instrumentation test tail worker\nexport default testTailHandler;\n\nexport const test = {\n  async test(ctrl, env, ctx) {\n    const expected = [\n      { name: 'durable_object_storage_put', closed: true },\n      { name: 'durable_object_storage_put', closed: true },\n      { name: 'durable_object_storage_get', closed: true },\n      { name: 'durable_object_storage_get', closed: true },\n      { name: 'durable_object_storage_delete', closed: true },\n      { name: 'durable_object_storage_list', closed: true },\n      { name: 'durable_object_storage_deleteAll', closed: true },\n      { name: 'durable_object_storage_setAlarm', closed: true },\n      { name: 'durable_object_storage_getAlarm', closed: true },\n      { name: 'durable_object_storage_deleteAlarm', closed: true },\n      { name: 'durable_object_storage_transaction', closed: true },\n      { name: 'durable_object_storage_sync', closed: true },\n      {\n        name: 'durable_object_subrequest',\n        closed: true,\n        objectId:\n          'aa299662980ce671dbcb09a5d7ab26ab30e45465bcd12f263f2bdd7d5edd804a',\n      },\n    ];\n\n    await Promise.allSettled(invocationPromises);\n    let received = Array.from(spans.values());\n    assert.deepStrictEqual(received, expected);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/actor-kv-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport * as assert from 'node:assert';\nimport { DurableObject } from 'cloudflare:workers';\n\nasync function testKvOperations(state) {\n  const storage = state.storage;\n\n  // Test basic put/get operations\n  await storage.put('testKey1', 'testValue');\n  await storage.put({\n    foo1: 'bar1',\n    foo2: 'bar2',\n    foo3: 'bar3',\n    foo4: 'bar4',\n  });\n  await storage.get('testKey2');\n  await storage.get(['testKey1', 'testKey2']);\n  await storage.delete('testKey1');\n  await storage.list();\n  await storage.deleteAll();\n  await storage.setAlarm(Date.now() + 50);\n  await storage.getAlarm();\n  await storage.deleteAlarm();\n  await storage.transaction(() => {\n    return 'test';\n  });\n  await storage.sync();\n}\n\nexport class ActorKvTest extends DurableObject {\n  constructor(state, env) {\n    super(state, env);\n    this.state = state;\n  }\n\n  async fetch(req) {\n    const url = new URL(req.url);\n\n    if (url.pathname.endsWith('/kv-test')) {\n      await testKvOperations(this.state);\n      return Response.json({ ok: true, test: 'kv-operations' });\n    }\n\n    return Response.json(\n      { error: 'Unknown endpoint', path: url.pathname },\n      { status: 404 }\n    );\n  }\n\n  alarm() {}\n}\n\nexport default {\n  async test(ctrl, env, ctx) {\n    let id = env.ns.idFromName('actor-kv-test');\n    let obj = env.ns.get(id);\n\n    let doReq = async (path, init = {}) => {\n      let resp = await obj.fetch('http://test.example/' + path, init);\n      return await resp.json();\n    };\n\n    // Test KV operations\n    assert.deepEqual(await doReq('kv-test'), {\n      ok: true,\n      test: 'kv-operations',\n    });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/actor-kv-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n    (name = \"TEST_TMPDIR\", disk = (writable = true)),\n    (name = \"tail\", worker = .tailWorker),\n  ],\n  extensions = [ (\n    modules = [\n      ( name = \"test:instrumentation-tail\", esModule = embed \"instrumentation-tail-worker.js\" ),\n    ]\n  ) ]\n);\n\nconst mainWorker :Workerd.Worker = (\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n\n  streamingTails = [\"tail\"],\n\n  modules = [\n    (name = \"worker\", esModule = embed \"actor-kv-test.js\"),\n  ],\n\n  durableObjectNamespaces = [\n    (className = \"ActorKvTest\", uniqueKey = \"210bd0cbd803ef7883a1ee9d86cce06e\"),\n  ],\n\n  durableObjectStorage = (localDisk = \"TEST_TMPDIR\"),\n\n  bindings = [\n    (name = \"ns\", durableObjectNamespace = \"ActorKvTest\"),\n  ],\n);\n\nconst tailWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"actor-kv-test-tail.js\")\n  ],\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/actor-stub-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// We want to test the behavior of the Durable Object stub now that it uses name interception to\n// behave like a JS Proxy object.\nimport * as assert from 'node:assert';\n\nexport class DurableObjectExample {\n  constructor() {\n    this.check = true;\n  }\n\n  async fetch(request) {\n    return new Response('OK');\n  }\n\n  async foo() {\n    // Server side impl of foo.\n    return 'foo from remote';\n  }\n\n  async throw() {\n    throw 1;\n  }\n\n  thisCheck() {\n    if (this.check !== true) {\n      throw new Error('incorrect this within rpc function call');\n    }\n    return true;\n  }\n}\n\nasync function checkDurableObject(obj) {\n  assert.equal(await obj.foo(), 'foo from remote');\n  assert.equal(await obj.thisCheck(), true);\n  assert.deepStrictEqual(await obj.fetch('http://foo/'), new Response('OK'));\n  assert.rejects(obj.throw());\n}\n\nfunction checkNullishJurisdiction(env, jurisdiction) {\n  const id1 = env.ns.jurisdiction(jurisdiction).newUniqueId();\n  const stub1 = env.ns.get(id1);\n  checkDurableObject(stub1);\n  const id2 = env.ns.newUniqueId({ jurisdiction });\n  const stub2 = env.ns.get(id2);\n  checkDurableObject(stub2);\n}\n\nexport default {\n  async test(_request, env, _ctx) {\n    // This test verifies we can still use registered methods like `fetch()`, and also confirms that\n    // dynamic names (foo() in this case) can also be called.\n    //\n    // We should still be able to define names on the stub in JS and get the expected result.\n\n    // Check properties of DurableObjectId.\n    const id = env.ns.idFromName('foo');\n    assert.equal(id.name, 'foo');\n    assert.equal(id.jurisdiction, undefined);\n\n    {\n      // Check that no two DurableObjectId created via `newUniqueId()` are equal.\n      const id1 = env.ns.newUniqueId();\n      const id2 = env.ns.newUniqueId();\n      assert.equal(!id1.equals(id2), true);\n    }\n\n    // Check round tripping of DurableObjectId to string.\n    //\n    // Note: `name` property is dropped from `env.ns.idFromString(id.toString())`\n    assert.equal(env.ns.idFromString(id.toString()).toString(), id.toString());\n\n    // Check properties of DurableObject.\n    let obj = env.ns.get(id);\n    assert.equal(Object.keys(obj).length, 2);\n    assert.equal(obj.name, 'foo');\n    assert.equal(obj.id, id);\n    assert.equal(obj.id.name, 'foo');\n    assert.equal(obj.id.jurisdiction, undefined);\n\n    // Check that we can call methods on a DurableObject.\n    checkDurableObject(obj);\n\n    {\n      // Check that DurableObject constructed with `locationHint` is equivalent to `obj`.\n      let otherObj = env.ns.get(id, { locationHint: 'wnam' });\n      assert.deepStrictEqual(obj, otherObj);\n      checkDurableObject(otherObj);\n    }\n\n    {\n      // Check that DurableObject constructed via `getByName()` is equivalent to `obj`.\n      let otherObj = env.ns.getByName('foo');\n      assert.deepStrictEqual(obj, otherObj);\n      checkDurableObject(otherObj);\n    }\n\n    {\n      // Check that DurableObject constructed with `locationHint` and `getByName()` is equivalent\n      // to `obj`.\n      let otherObj = env.ns.getByName('foo', { locationHint: 'wnam' });\n      assert.deepStrictEqual(obj, otherObj);\n      checkDurableObject(otherObj);\n    }\n\n    {\n      // Check that nullish jurisdictions work in Workerd\n      checkNullishJurisdiction(env, null);\n      checkNullishJurisdiction(env, undefined);\n    }\n\n    // Check that methods can be defined on DurableObject and are callable.\n    obj.baz = () => {\n      return 'called baz';\n    };\n    assert.equal(Object.keys(obj).length, 3);\n    assert.equal(typeof obj.baz, 'function');\n    assert.equal(obj.baz(), 'called baz');\n\n    return new Response('OK');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/actor-stub-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n    (name = \"TEST_TMPDIR\", disk = (writable = true)),\n  ],\n);\n\nconst mainWorker :Workerd.Worker = (\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\", \"js_rpc\"],\n\n  modules = [\n    (name = \"worker\", esModule = embed \"actor-stub-test.js\"),\n  ],\n\n  durableObjectNamespaces = [\n    (className = \"DurableObjectExample\", uniqueKey = \"210bd0cbd803ef7883a1ee9d86cce06e\"),\n  ],\n\n  durableObjectStorage = (localDisk = \"TEST_TMPDIR\"),\n\n  bindings = [\n    (name = \"ns\", durableObjectNamespace = \"DurableObjectExample\"),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/als-only-test.js",
    "content": "import { AsyncLocalStorage } from 'node:async_hooks';\n\nexport const customthenable = {\n  // Test to ensure that async context is propagated into custom thenables.\n  async test() {\n    // This should just work\n    const als = new AsyncLocalStorage();\n    if (!(als instanceof AsyncLocalStorage)) {\n      throw new Error('Expected an instance of AsyncLocalStorage');\n    }\n\n    // This should not\n    try {\n      await import('node:assert');\n      throw new Error('Expected an error to be thrown');\n    } catch (err) {\n      if (err.message !== 'No such module \"node:assert\".') {\n        throw err;\n      }\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/als-only-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"als-only-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"als-only-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_als\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/als-test.js",
    "content": "import { strictEqual } from 'node:assert';\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\nexport const customthenable = {\n  // Test to ensure that async context is propagated into custom thenables.\n  async test() {\n    const als = new AsyncLocalStorage();\n    const result = await als.run(123, async () => {\n      return await {\n        then(done) {\n          done(als.getStore());\n        },\n      };\n    });\n    strictEqual(result, 123);\n\n    const result2 = await als.run(123, async () => {\n      return await new Promise((resolve) =>\n        resolve({\n          then(done) {\n            done(als.getStore());\n          },\n        })\n      );\n    });\n    strictEqual(result2, 123);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/als-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"als-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"als-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/analytics-engine-test.js",
    "content": "import * as assert from 'node:assert';\n\nlet written = false;\nasync function isWritten(timeout) {\n  const start = Date.now();\n  do {\n    if (written) return true;\n    await scheduler.wait(100);\n  } while (Date.now() - start < timeout);\n  throw new Error('Test never received request from analytics engine handler');\n}\n\nexport default {\n  async fetch(ctrl, env, ctx) {\n    written = true;\n    return new Response('');\n  },\n  async test(ctrl, env, ctx) {\n    env.aebinding.writeDataPoint({\n      blobs: ['TestBlob'],\n      doubles: [25],\n      indexes: ['testindex'],\n    });\n\n    assert.equal(await isWritten(5000), true);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/analytics-engine-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst analyticsWorker :Workerd.Worker = (\n  modules  = [\n    (name = \"worker\", esModule =\n      `import * as assert from 'node:assert';\n      `export default {\n      `  async fetch(request, env, ctx) {\n      `    let val = await request.json();\n      `    console.log(JSON.stringify(val));\n      `    assert.deepStrictEqual(val['dataset'], [97,110,97,108,121,116,105,99,115]);\n      `    assert.deepStrictEqual(val['double1'], 25);\n      `    assert.deepStrictEqual(val['blob1'], [84,101,115,116,66,108,111,98]);\n      `    await env.main.fetch(\"http://w/\");\n      `    return new Response('');\n      `  },\n      `};\n      ),\n    ],\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n  bindings = [ ( name = \"main\", service = \"main\" ) ]\n);\n\nconst mainWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"analytics-engine-test.js\"),\n  ],\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n\n  bindings = [ ( name = \"aebinding\", analyticsEngine = \"analytics\") ]\n);\n\nconst unitTests :Workerd.Config = (\n    services = [ (name = \"main\", worker = .mainWorker), (name = \"analytics\", worker = .analyticsWorker) ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/autogate-disabled-test.js",
    "content": "// Test that verifies autogates are DISABLED.\n// This test only runs in the default variant (all-autogates variant is disabled).\n\nimport { strictEqual } from 'node:assert';\nimport unsafe from 'workerd:unsafe';\n\nexport const autogateDisabledTest = {\n  test() {\n    // In the default variant, WORKERD_ALL_AUTOGATES env var is NOT set,\n    // so isTestAutogateEnabled() should return false.\n    const isEnabled = unsafe.isTestAutogateEnabled();\n    strictEqual(\n      isEnabled,\n      false,\n      'TEST_WORKERD autogate should be DISABLED in default variant'\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/autogate-disabled-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"autogate-disabled-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"autogate-disabled-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"unsafe_module\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/autogate-enabled-test.js",
    "content": "// Test that verifies autogates are ENABLED.\n// This test only runs in the @all-autogates variant (default variant is disabled).\n\nimport { strictEqual } from 'node:assert';\nimport unsafe from 'workerd:unsafe';\n\nexport const autogateEnabledTest = {\n  test() {\n    // In the @all-autogates variant, WORKERD_ALL_AUTOGATES env var is set,\n    // so isTestAutogateEnabled() should return true.\n    const isEnabled = unsafe.isTestAutogateEnabled();\n    strictEqual(\n      isEnabled,\n      true,\n      'TEST_WORKERD autogate should be ENABLED in @all-autogates variant'\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/autogate-enabled-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"autogate-enabled-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"autogate-enabled-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"unsafe_module\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/blob-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { strictEqual, throws } from 'node:assert';\nimport { inspect } from 'node:util';\n\nexport const test1 = {\n  async test(ctrl, env, ctx) {\n    let blob = new Blob(['foo', new TextEncoder().encode('bar'), 'baz']);\n    strictEqual(await blob.text(), 'foobarbaz');\n    strictEqual(\n      new TextDecoder().decode(await blob.arrayBuffer()),\n      'foobarbaz'\n    );\n    strictEqual(blob.type, '');\n\n    let blob2 = new Blob(['xx', blob, 'yy', blob], {\n      type: 'application/whatever',\n    });\n    strictEqual(await blob2.text(), 'xxfoobarbazyyfoobarbaz');\n    strictEqual(blob2.type, 'application/whatever');\n\n    let blob3 = new Blob();\n    strictEqual(await blob3.text(), '');\n\n    let slice = blob2.slice(5, 16);\n    strictEqual(await slice.text(), 'barbazyyfoo');\n    strictEqual(slice.type, '');\n\n    let slice2 = slice.slice(-5, 1234, 'type/type');\n    strictEqual(await slice2.text(), 'yyfoo');\n    strictEqual(slice2.type, 'type/type');\n\n    strictEqual(await blob2.slice(5).text(), 'barbazyyfoobarbaz');\n    strictEqual(await blob2.slice().text(), 'xxfoobarbazyyfoobarbaz');\n    strictEqual(await blob2.slice(3, 1).text(), '');\n\n    {\n      let stream = blob.stream();\n      let reader = stream.getReader();\n      let readResult = await reader.read();\n      strictEqual(readResult.done, false);\n      strictEqual(new TextDecoder().decode(readResult.value), 'foobarbaz');\n      readResult = await reader.read();\n      strictEqual(readResult.value, undefined);\n      strictEqual(readResult.done, true);\n      reader.releaseLock();\n    }\n\n    let before = Date.now();\n\n    let file = new File([blob, 'qux'], 'filename.txt');\n    strictEqual(file instanceof Blob, true);\n    strictEqual(await file.text(), 'foobarbazqux');\n    strictEqual(file.name, 'filename.txt');\n    strictEqual(file.type, '');\n    if (file.lastModified < before || file.lastModified > Date.now()) {\n      throw new Error('incorrect lastModified');\n    }\n\n    let file2 = new File(['corge', file], 'file2', {\n      type: 'text/foo',\n      lastModified: 123,\n    });\n    strictEqual(await file2.text(), 'corgefoobarbazqux');\n    strictEqual(file2.name, 'file2');\n    strictEqual(file2.type, 'text/foo');\n    strictEqual(file2.lastModified, 123);\n\n    try {\n      new Blob(['foo'], { endings: 'native' });\n      throw new Error(\"use of 'endings' should throw\");\n    } catch (err) {\n      if (\n        !err.message.includes(\n          \"The 'endings' field on 'Options' is not implemented.\"\n        )\n      ) {\n        throw err;\n      }\n    }\n\n    // Test type normalization.\n    strictEqual(new Blob([], { type: 'FoO/bAr' }).type, 'foo/bar');\n    strictEqual(new Blob([], { type: 'FoO\\u0019/bAr' }).type, '');\n    strictEqual(new Blob([], { type: 'FoO\\u0020/bAr' }).type, 'foo /bar');\n    strictEqual(new Blob([], { type: 'FoO\\u007e/bAr' }).type, 'foo\\u007e/bar');\n    strictEqual(new Blob([], { type: 'FoO\\u0080/bAr' }).type, '');\n    strictEqual(new File([], 'foo.txt', { type: 'FoO/bAr' }).type, 'foo/bar');\n    strictEqual(blob2.slice(1, 2, 'FoO/bAr').type, 'foo/bar');\n  },\n};\n\nexport const test2 = {\n  async test(ctrl, env, ctx) {\n    // This test verifies that a Blob created from a request/response properly reflects\n    // the content and content-type specified by the request/response.\n    const res = await env['request-blob'].fetch(\n      'http://example.org/blob-request',\n      {\n        method: 'POST',\n        body: 'abcd1234',\n        headers: { 'content-type': 'some/type' },\n      }\n    );\n\n    strictEqual(res.headers.get('content-type'), 'return/type');\n    strictEqual(await res.text(), 'foobarbaz');\n  },\n  async doTest(request) {\n    let b = await request.blob();\n    strictEqual(await b.text(), 'abcd1234');\n    strictEqual(b.type, 'some/type');\n\n    // Quick check that content-type header is correctly not set when the blob type is an empty\n    // string.\n    strictEqual(\n      new Response(new Blob(['typeful'], { type: 'foo' })).headers.has(\n        'Content-Type'\n      ),\n      true\n    );\n    strictEqual(\n      new Response(new Blob(['typeless'])).headers.has('Content-Type'),\n      false\n    );\n\n    return new Response(new Blob(['foobar', 'baz'], { type: 'return/type' }));\n  },\n};\n\nexport default {\n  async fetch(request) {\n    if (request.url.endsWith('/blob-request')) {\n      return test2.doTest(request);\n    } else {\n      throw new Error('Unexpected test request');\n    }\n  },\n};\n\nexport const testInspect = {\n  async test(ctrl, env, ctx) {\n    const blob = new Blob(['abc'], { type: 'text/plain' });\n    strictEqual(inspect(blob), \"Blob { size: 3, type: 'text/plain' }\");\n\n    const file = new File(['1'], 'file.txt', {\n      type: 'text/plain',\n      lastModified: 1000,\n    });\n    strictEqual(\n      inspect(file),\n      \"File { name: 'file.txt', lastModified: 1000, size: 1, type: 'text/plain' }\"\n    );\n  },\n};\n\nexport const overLarge = {\n  test() {\n    const blob1 = new Blob([new ArrayBuffer(128 * 1024 * 1024)]);\n\n    throws(\n      () => {\n        new Blob([new ArrayBuffer(128 * 1024 * 1024 + 1)]);\n      },\n      {\n        message: 'Blob size 134217729 exceeds limit 134217728',\n        name: 'RangeError',\n      }\n    );\n\n    throws(\n      () => {\n        new Blob([' ', blob1]);\n      },\n      {\n        message: 'Blob size 134217729 exceeds limit 134217728',\n        name: 'RangeError',\n      }\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/blob-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"blob-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"blob-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"set_tostring_tag\", \"workers_api_getters_setters_on_prototype\"],\n        bindings = [\n          (name = \"request-blob\", service = \"blob-test\")\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/blob2-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { ok, strictEqual, deepStrictEqual } from 'node:assert';\n\nexport const test = {\n  async test() {\n    const res = new Response('test', {\n      headers: {\n        'content-type': 'text/plain, x/y, text/html',\n      },\n    });\n\n    const blob = await res.blob();\n\n    // Without the blob_standard_mime_type compat flag, the blob.type would be\n    // the incorrect 'text/plain, x/y, text/html'\n\n    strictEqual(await blob.text(), 'test');\n    strictEqual(blob.type, 'text/html');\n  },\n};\n\nexport const bytes = {\n  async test() {\n    const check = new Uint8Array([116, 101, 115, 116]);\n    const blob = new Blob(['test']);\n    const u8 = await blob.bytes();\n    deepStrictEqual(u8, check);\n    ok(u8 instanceof Uint8Array);\n\n    const res = new Response('test');\n    const u8_2 = await res.bytes();\n    deepStrictEqual(u8_2, check);\n    ok(u8_2 instanceof Uint8Array);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/blob2-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"blob2-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"blob2-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"blob_standard_mime_type\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/buffer-indexof-odd-offset-ucs2-test.js",
    "content": "import { Buffer } from 'node:buffer';\nimport { strictEqual } from 'node:assert';\n\nexport const BufferIndexOfUnalignedUtf16 = {\n  async test() {\n    const ab = new ArrayBuffer(32);\n    const view = new Uint8Array(ab, 1, 30);\n\n    for (let i = 0; i < view.length; i++) {\n      view[i] = i % 2 === 0 ? 0x41 : 0x42;\n    }\n\n    const buf = Buffer.from(view.buffer, view.byteOffset, view.byteLength);\n    const idx = buf.indexOf('AB', 0, 'ucs2');\n    strictEqual(typeof idx, 'number');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/buffer-indexof-odd-offset-ucs2-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"buffer-indexof-odd-offset-ucs2-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"buffer-indexof-odd-offset-ucs2-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat_v2\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/byob-reader-resize-pending-read-test.js",
    "content": "import { strictEqual, ok } from 'node:assert';\n\nexport const ByobReaderResizePendingRead = {\n  async test() {\n    const ts = new IdentityTransformStream();\n    const reader = ts.readable.getReader({ mode: 'byob' });\n    const writer = ts.writable.getWriter();\n\n    const buffer = new ArrayBuffer(8192, { maxByteLength: 16384 });\n    const view = new Uint8Array(buffer, 4096, 1024);\n\n    const readPromise = reader.read(view);\n    buffer.resize(2048);\n\n    await writer.write(new Uint8Array(100).fill(0x41));\n    const result = await readPromise;\n\n    strictEqual(result.done, false);\n    strictEqual(result.value.byteLength, 0);\n\n    reader.releaseLock();\n    writer.releaseLock();\n  },\n};\n\nexport const ByobReaderResizableBufferTempLifetime = {\n  async test() {\n    const data = 'A'.repeat(1024);\n    const response = await fetch('data:text/plain;base64,' + btoa(data));\n    const reader = response.body.getReader({ mode: 'byob' });\n\n    const buffer = new ArrayBuffer(512, { maxByteLength: 1024 });\n    const view = new Uint8Array(buffer);\n\n    const result = await reader.read(view);\n    reader.releaseLock();\n\n    strictEqual(result.done, false);\n    ok(result.value.byteLength > 0);\n    const text = new TextDecoder().decode(result.value);\n    strictEqual(text, 'A'.repeat(result.value.byteLength));\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/byob-reader-resize-pending-read-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"byob-reader-resize-pending-read-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"byob-reader-resize-pending-read-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat_v2\", \"streams_byob_reader_does_not_detach_buffer\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/cache-instrumentation-test.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport * as assert from 'node:assert';\nimport {\n  invocationPromises,\n  spans,\n  testTailHandler,\n} from 'test:instrumentation-tail';\n\n// Use shared instrumentation test tail worker\nexport default testTailHandler;\n\nexport const test = {\n  async test() {\n    // Wait for all the tailStream executions to finish\n    await Promise.allSettled(invocationPromises);\n\n    // Recorded streaming tail worker events, in insertion order,\n    let received = Array.from(spans.values());\n\n    // spans emitted by cache-operations-test.js in execution order\n    let expected = [\n      {\n        name: 'cache_match',\n        'cache.request.url': 'https://example.com/conditional-etag',\n        'cache.request.method': 'GET',\n        'cache.request.header.if_none_match': 'abc123',\n        'cache.response.status_code': 504n,\n        'cache.response.body.size': 0n,\n        'cache.response.cache_status': 'MISS',\n        'cache.response.success': false,\n        closed: true,\n      },\n      {\n        name: 'cache_match',\n        'cache.request.url': 'https://example.com/conditional-last-modified',\n        'cache.request.method': 'GET',\n        'cache.request.header.if_modified_since':\n          'Wed, 21 Oct 2020 07:28:00 GMT',\n        'cache.response.status_code': 504n,\n        'cache.response.body.size': 0n,\n        'cache.response.cache_status': 'MISS',\n        'cache.response.success': false,\n        closed: true,\n      },\n      {\n        name: 'cache_delete',\n        'cache.request.url': 'https://example.com/delete-exists',\n        'cache.request.method': 'GET',\n        'cache.response.status_code': 200n,\n        'cache.response.success': true,\n        closed: true,\n      },\n      {\n        name: 'cache_delete',\n        'cache.request.url': 'https://example.com/delete-not-exists',\n        'cache.request.method': 'GET',\n        'cache.response.status_code': 404n,\n        'cache.response.success': false,\n        closed: true,\n      },\n      {\n        name: 'cache_delete',\n        'cache.request.ignore_method': true,\n        'cache.request.url': 'https://example.com/delete-with-options',\n        'cache.request.method': 'POST',\n        'cache.response.status_code': 404n,\n        'cache.response.success': false,\n        closed: true,\n      },\n      {\n        name: 'cache_put',\n        'cache.request.url': 'https://example.com/put-cache-tag',\n        'cache.request.method': 'GET',\n        'cache.request.payload.status_code': 200n,\n        'cache.request.payload.header.cache_control': 'public, max-age=3600',\n        'cache.request.payload.header.cache_tag': 'tag1,tag2,tag3',\n        'cache.request.payload.size': 143n,\n        'cache.response.success': true,\n        closed: true,\n      },\n      {\n        name: 'cache_put',\n        'cache.request.url': 'https://example.com/put-etag',\n        'cache.request.method': 'GET',\n        'cache.request.payload.status_code': 200n,\n        'cache.request.payload.header.cache_control': 'public, max-age=3600',\n        'cache.request.payload.header.etag': '\"abc123\"',\n        'cache.request.payload.size': 130n,\n        'cache.response.success': true,\n        closed: true,\n      },\n      {\n        name: 'cache_put',\n        'cache.request.url': 'https://example.com/put-expires',\n        'cache.request.method': 'GET',\n        'cache.request.payload.status_code': 200n,\n        'cache.request.payload.header.expires': 'Wed, 21 Oct 2025 07:28:00 GMT',\n        'cache.request.payload.size': 120n,\n        'cache.response.success': true,\n        closed: true,\n      },\n      {\n        name: 'cache_put',\n        'cache.request.url': 'https://example.com/put-last-modified',\n        'cache.request.method': 'GET',\n        'cache.request.payload.status_code': 200n,\n        'cache.request.payload.header.cache_control': 'public, max-age=3600',\n        'cache.request.payload.header.last_modified':\n          'Wed, 21 Oct 2020 07:28:00 GMT',\n        'cache.request.payload.size': 169n,\n        'cache.response.success': true,\n        closed: true,\n      },\n      {\n        name: 'cache_put',\n        'cache.request.url': 'https://example.com/put-set-cookie',\n        'cache.request.method': 'GET',\n        'cache.request.payload.status_code': 200n,\n        'cache.request.payload.header.cache_control': 'public, max-age=3600',\n        'cache.request.payload.size': 164n,\n        'cache.response.success': true,\n        closed: true,\n      },\n      {\n        name: 'cache_put',\n        'cache.request.url': 'https://example.com/put-set-cookie-private',\n        'cache.request.method': 'GET',\n        'cache.request.payload.status_code': 200n,\n        'cache.request.payload.header.cache_control':\n          'public, max-age=3600, private=Set-Cookie',\n        'cache.request.payload.size': 197n,\n        'cache.response.success': true,\n        closed: true,\n      },\n      {\n        name: 'cache_put',\n        'cache.request.url': 'https://example.com/put-must-revalidate',\n        'cache.request.method': 'GET',\n        'cache.request.payload.status_code': 200n,\n        'cache.request.payload.header.cache_control':\n          'public, max-age=3600, must-revalidate',\n        'cache.request.payload.size': 142n,\n        'cache.response.success': true,\n        closed: true,\n      },\n      {\n        name: 'cache_put',\n        'cache.request.url': 'https://example.com/put-proxy-revalidate',\n        'cache.request.method': 'GET',\n        'cache.request.payload.status_code': 200n,\n        'cache.request.payload.header.cache_control':\n          'public, max-age=3600, proxy-revalidate',\n        'cache.request.payload.size': 144n,\n        'cache.response.success': true,\n        closed: true,\n      },\n      {\n        name: 'cache_put',\n        'cache.request.url': 'https://example.com/put-private',\n        'cache.request.method': 'GET',\n        'cache.request.payload.status_code': 200n,\n        'cache.request.payload.header.cache_control': 'private, max-age=3600',\n        'cache.request.payload.size': 118n,\n        'cache.response.success': true,\n        closed: true,\n      },\n      {\n        name: 'cache_put',\n        'cache.request.url': 'https://example.com/put-no-cache',\n        'cache.request.method': 'GET',\n        'cache.request.payload.status_code': 200n,\n        'cache.request.payload.header.cache_control': 'no-cache, max-age=3600',\n        'cache.request.payload.size': 120n,\n        'cache.response.success': true,\n        closed: true,\n      },\n      {\n        name: 'cache_match',\n        'cache.request.url': 'https://example.com/cached-resource',\n        'cache.request.method': 'GET',\n        'cache.response.status_code': 200n,\n        'cache.response.body.size': 14n,\n        'cache.response.cache_status': 'HIT',\n        'cache.response.success': true,\n        closed: true,\n      },\n      {\n        name: 'cache_match',\n        'cache.request.url': 'https://example.com/not-cached',\n        'cache.request.method': 'GET',\n        'cache.response.status_code': 504n,\n        'cache.response.body.size': 0n,\n        'cache.response.cache_status': 'MISS',\n        'cache.response.success': false,\n        closed: true,\n      },\n      {\n        name: 'cache_match',\n        'cache.request.ignore_method': false,\n        'cache.request.url': 'https://example.com/cached-with-options',\n        'cache.request.method': 'GET',\n        'cache.response.status_code': 504n,\n        'cache.response.body.size': 0n,\n        'cache.response.cache_status': 'MISS',\n        'cache.response.success': false,\n        closed: true,\n      },\n      {\n        name: 'cache_put',\n        'cache.request.url': 'https://example.com/put-resource',\n        'cache.request.method': 'GET',\n        'cache.request.payload.status_code': 200n,\n        'cache.request.payload.header.cache_control': 'public, max-age=3600',\n        'cache.request.payload.size': 114n,\n        'cache.response.success': true,\n        closed: true,\n      },\n      {\n        name: 'cache_put',\n        'cache.request.url': 'https://example.com/put-no-store',\n        'cache.request.method': 'GET',\n        'cache.request.payload.status_code': 200n,\n        'cache.request.payload.header.cache_control': 'no-store',\n        'cache.request.payload.size': 106n,\n        'cache.response.success': true,\n        closed: true,\n      },\n      {\n        name: 'cache_put',\n        'cache.request.url': 'https://example.com/put-s-maxage',\n        'cache.request.method': 'GET',\n        'cache.request.payload.status_code': 200n,\n        'cache.request.payload.header.cache_control': 's-maxage=7200, public',\n        'cache.request.payload.size': 119n,\n        'cache.response.success': true,\n        closed: true,\n      },\n    ];\n\n    assert.equal(\n      received.length,\n      expected.length,\n      `Expected ${expected.length} received ${received.length} spans`\n    );\n    let errors = [];\n    for (let i = 0; i < received.length; i++) {\n      try {\n        assert.deepStrictEqual(received[i], expected[i]);\n      } catch (e) {\n        console.error(`value: ${i} does not match`);\n        console.log(e);\n        errors.push(e);\n      }\n    }\n    if (errors.length > 0) {\n      throw 'cache spans are incorrect';\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/cache-mock.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Mock cache backend - responds to GET (match), PUT (put), and PURGE (delete)\nexport default {\n  async fetch(request) {\n    const url = new URL(request.url);\n    const headers = new Headers();\n\n    // Handle cache.match() operations (GET requests)\n    if (request.method === 'GET') {\n      // Check if this is a cache.match() request (has only-if-cached)\n      const cacheControl = request.headers.get('cache-control');\n      if (cacheControl?.includes('only-if-cached')) {\n        // Simulate cache HIT\n        if (url.pathname.includes('cached-resource')) {\n          headers.set('CF-Cache-Status', 'HIT');\n          return new Response('Cached content', { status: 200, headers });\n        }\n        // Simulate cache MISS\n        headers.set('CF-Cache-Status', 'MISS');\n        return new Response(null, { status: 504, headers });\n      }\n    }\n\n    // Handle cache.put() operations (PUT requests)\n    if (request.method === 'PUT') {\n      // Read the body (which contains the serialized response to cache)\n      await request.text();\n\n      // Simulate successful cache write\n      return new Response(null, { status: 204 });\n    }\n\n    // Handle cache.delete() operations (PURGE requests)\n    if (request.method === 'PURGE') {\n      // Simulate successful deletion\n      if (url.pathname.includes('delete-exists')) {\n        return new Response(null, { status: 200 });\n      }\n      // Simulate not found\n      return new Response(null, { status: 404 });\n    }\n\n    return new Response('Not Found', { status: 404 });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/cache-operations-test.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Cache API operations for generating instrumentation telemetry.\n// The actual validation of telemetry spans happens in cache-instrumentation-test.js\n\nimport * as assert from 'node:assert';\n\n// Note that these aren't really tests of the cache API. We run through a bunch\n// of the cache API operations to emit telemetry for the tests in cache-instrumentation-test.js\n\nexport const matchOperations = {\n  async test(ctrl, env, ctx) {\n    const cache = caches.default;\n\n    // Test cache.match() - HIT case\n    const matchRequest1 = new Request('https://example.com/cached-resource', {\n      method: 'GET',\n    });\n    let matchResult = await cache.match(matchRequest1);\n    assert.ok(matchResult !== undefined, 'Should have a cache HIT');\n    assert.strictEqual(await matchResult.text(), 'Cached content');\n\n    // Test cache.match() - MISS case\n    const matchRequest2 = new Request('https://example.com/not-cached', {\n      method: 'GET',\n    });\n    matchResult = await cache.match(matchRequest2);\n    assert.strictEqual(matchResult, undefined, 'Should be cache MISS');\n\n    // Test cache.match() with options\n    const matchRequest3 = new Request(\n      'https://example.com/cached-with-options',\n      {\n        method: 'GET',\n      }\n    );\n    matchResult = await cache.match(matchRequest3, { ignoreMethod: false });\n    assert.strictEqual(\n      matchResult,\n      undefined,\n      'Should be cache MISS with options'\n    );\n  },\n};\n\nexport const putOperations = {\n  async test(ctrl, env, ctx) {\n    const cache = caches.default;\n\n    // Test cache.put() with max-age\n    const putRequest1 = new Request('https://example.com/put-resource', {\n      method: 'GET',\n    });\n    const putResponse1 = new Response('Test content', {\n      status: 200,\n      headers: {\n        'Content-Type': 'text/plain',\n        'Cache-Control': 'public, max-age=3600',\n      },\n    });\n    await cache.put(putRequest1, putResponse1);\n\n    // Test cache.put() with no-store\n    const putRequest2 = new Request('https://example.com/put-no-store', {\n      method: 'GET',\n    });\n    const putResponse2 = new Response('No store content', {\n      status: 200,\n      headers: {\n        'Content-Type': 'text/plain',\n        'Cache-Control': 'no-store',\n      },\n    });\n    await cache.put(putRequest2, putResponse2);\n\n    // Test cache.put() with s-maxage\n    const putRequest3 = new Request('https://example.com/put-s-maxage', {\n      method: 'GET',\n    });\n    const putResponse3 = new Response('S-maxage content', {\n      status: 200,\n      headers: {\n        'Content-Type': 'text/plain',\n        'Cache-Control': 's-maxage=7200, public',\n      },\n    });\n    await cache.put(putRequest3, putResponse3);\n  },\n};\n\nexport const deleteOperations = {\n  async test(ctrl, env, ctx) {\n    const cache = caches.default;\n\n    // Test cache.delete() - exists\n    const deleteRequest1 = new Request('https://example.com/delete-exists', {\n      method: 'GET',\n    });\n    let deleteResult = await cache.delete(deleteRequest1);\n    assert.strictEqual(\n      deleteResult,\n      true,\n      'Should return true for successful delete'\n    );\n\n    // Test cache.delete() - doesn't exist\n    const deleteRequest2 = new Request(\n      'https://example.com/delete-not-exists',\n      {\n        method: 'GET',\n      }\n    );\n    deleteResult = await cache.delete(deleteRequest2);\n    assert.strictEqual(\n      deleteResult,\n      false,\n      'Should return false when not found'\n    );\n\n    // Test cache.delete() with options\n    const deleteRequest3 = new Request(\n      'https://example.com/delete-with-options',\n      {\n        method: 'POST',\n      }\n    );\n    deleteResult = await cache.delete(deleteRequest3, { ignoreMethod: true });\n    assert.strictEqual(deleteResult, false, 'Should handle options');\n  },\n};\n\nexport const headersOperations = {\n  async test(ctrl, env, ctx) {\n    const cache = caches.default;\n\n    // Test cache.put() with Cache-Tag header\n    const putRequest1 = new Request('https://example.com/put-cache-tag', {\n      method: 'GET',\n    });\n    const putResponse1 = new Response('Tagged content', {\n      status: 200,\n      headers: {\n        'Content-Type': 'text/plain',\n        'Cache-Control': 'public, max-age=3600',\n        'Cache-Tag': 'tag1,tag2,tag3',\n      },\n    });\n    await cache.put(putRequest1, putResponse1);\n\n    // Test cache.put() with ETag header\n    const putRequest2 = new Request('https://example.com/put-etag', {\n      method: 'GET',\n    });\n    const putResponse2 = new Response('ETag content', {\n      status: 200,\n      headers: {\n        'Content-Type': 'text/plain',\n        'Cache-Control': 'public, max-age=3600',\n        ETag: '\"abc123\"',\n      },\n    });\n    await cache.put(putRequest2, putResponse2);\n\n    // Test cache.put() with Expires header\n    const putRequest3 = new Request('https://example.com/put-expires', {\n      method: 'GET',\n    });\n    const putResponse3 = new Response('Expires content', {\n      status: 200,\n      headers: {\n        'Content-Type': 'text/plain',\n        Expires: 'Wed, 21 Oct 2025 07:28:00 GMT',\n      },\n    });\n    await cache.put(putRequest3, putResponse3);\n\n    // Test cache.put() with Last-Modified header\n    const putRequest4 = new Request('https://example.com/put-last-modified', {\n      method: 'GET',\n    });\n    const putResponse4 = new Response('Last-Modified content', {\n      status: 200,\n      headers: {\n        'Content-Type': 'text/plain',\n        'Cache-Control': 'public, max-age=3600',\n        'Last-Modified': 'Wed, 21 Oct 2020 07:28:00 GMT',\n      },\n    });\n    await cache.put(putRequest4, putResponse4);\n\n    // Test cache.put() with Set-Cookie header (should not cache or require special handling)\n    const putRequest5 = new Request('https://example.com/put-set-cookie', {\n      method: 'GET',\n    });\n    const putResponse5 = new Response('Cookie content', {\n      status: 200,\n      headers: {\n        'Content-Type': 'text/plain',\n        'Cache-Control': 'public, max-age=3600',\n        'Set-Cookie': 'sessionid=abc123; Path=/; HttpOnly',\n      },\n    });\n    await cache.put(putRequest5, putResponse5);\n\n    // Test cache.put() with Set-Cookie and Cache-Control: private=Set-Cookie\n    const putRequest6 = new Request(\n      'https://example.com/put-set-cookie-private',\n      {\n        method: 'GET',\n      }\n    );\n    const putResponse6 = new Response('Cookie content with private', {\n      status: 200,\n      headers: {\n        'Content-Type': 'text/plain',\n        'Cache-Control': 'public, max-age=3600, private=Set-Cookie',\n        'Set-Cookie': 'sessionid=xyz789; Path=/; HttpOnly',\n      },\n    });\n    await cache.put(putRequest6, putResponse6);\n\n    // Test cache.put() with must-revalidate\n    const putRequest7 = new Request('https://example.com/put-must-revalidate', {\n      method: 'GET',\n    });\n    const putResponse7 = new Response('Must revalidate content', {\n      status: 200,\n      headers: {\n        'Content-Type': 'text/plain',\n        'Cache-Control': 'public, max-age=3600, must-revalidate',\n      },\n    });\n    await cache.put(putRequest7, putResponse7);\n\n    // Test cache.put() with proxy-revalidate\n    const putRequest8 = new Request(\n      'https://example.com/put-proxy-revalidate',\n      {\n        method: 'GET',\n      }\n    );\n    const putResponse8 = new Response('Proxy revalidate content', {\n      status: 200,\n      headers: {\n        'Content-Type': 'text/plain',\n        'Cache-Control': 'public, max-age=3600, proxy-revalidate',\n      },\n    });\n    await cache.put(putRequest8, putResponse8);\n\n    // Test cache.put() with private cache control\n    const putRequest9 = new Request('https://example.com/put-private', {\n      method: 'GET',\n    });\n    const putResponse9 = new Response('Private content', {\n      status: 200,\n      headers: {\n        'Content-Type': 'text/plain',\n        'Cache-Control': 'private, max-age=3600',\n      },\n    });\n    await cache.put(putRequest9, putResponse9);\n\n    // Test cache.put() with no-cache\n    const putRequest10 = new Request('https://example.com/put-no-cache', {\n      method: 'GET',\n    });\n    const putResponse10 = new Response('No-cache content', {\n      status: 200,\n      headers: {\n        'Content-Type': 'text/plain',\n        'Cache-Control': 'no-cache, max-age=3600',\n      },\n    });\n    await cache.put(putRequest10, putResponse10);\n  },\n};\n\nexport const conditionalRequestOperations = {\n  async test(ctrl, env, ctx) {\n    const cache = caches.default;\n\n    // Test cache.match() with If-None-Match (should work with ETag)\n    const matchRequest1 = new Request('https://example.com/conditional-etag', {\n      method: 'GET',\n      headers: {\n        'If-None-Match': 'abc123',\n      },\n    });\n    let matchResult = await cache.match(matchRequest1);\n\n    // Test cache.match() with If-Modified-Since (should work with Last-Modified)\n    const matchRequest2 = new Request(\n      'https://example.com/conditional-last-modified',\n      {\n        method: 'GET',\n        headers: {\n          'If-Modified-Since': 'Wed, 21 Oct 2020 07:28:00 GMT',\n        },\n      }\n    );\n    matchResult = await cache.match(matchRequest2);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/cache-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"cache-test\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"cache-operations-test.js\" )\n        ],\n        cacheApiOutbound = \"cache-backend\",\n        compatibilityFlags = [\"nodejs_compat\"],\n        streamingTails = [\"tail\"],\n      )\n    ),\n    ( name = \"cache-backend\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"cache-mock.js\" )\n        ],\n      )\n    ),\n    (name = \"tail\", worker = .tailWorker, ),\n  ],\n  extensions = [ (\n    modules = [\n      ( name = \"test:instrumentation-tail\", esModule = embed \"instrumentation-tail-worker.js\" ),\n    ]\n  ) ]\n);\n\nconst tailWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"cache-instrumentation-test.js\")\n  ],\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/commonjs-module-test.js",
    "content": "import { foo, bar, default as baz } from 'foo';\nimport * as fooModule from 'foo';\nimport { strictEqual } from 'node:assert';\n\nexport const test = {\n  test() {\n    strictEqual(fooModule.default, baz);\n    strictEqual(fooModule.foo, foo);\n    strictEqual(fooModule.bar, undefined);\n    strictEqual(foo, 1);\n    strictEqual(bar, undefined);\n    strictEqual(baz.foo, foo);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/commonjs-module-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"commonjs-module-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"commonjs-module-test.js\"),\n          (name = \"foo\",\n           commonJsModule = \"exports.foo = 1\",\n           namedExports = [\"foo\", \"bar\"])\n        ],\n        compatibilityFlags = [\"nodejs_compat\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/compat-flag-disabled-test.js",
    "content": "// Test that verifies compat flags are at OLDEST state (most disabled).\n// This test only runs in the default @ variant (@all-compat-flags variant is disabled).\n\nimport { strictEqual } from 'node:assert';\n\nexport const compatFlagDisabledTest = {\n  test() {\n    // With compat date 2000-01-01, formdata_parser_supports_files should be disabled\n    // (it was enabled on 2021-11-03).\n    const isEnabled =\n      Cloudflare.compatibilityFlags.formdata_parser_supports_files;\n    strictEqual(\n      isEnabled,\n      false,\n      'formdata_parser_supports_files should be DISABLED with oldest compat date'\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/compat-flag-disabled-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"compat-flag-disabled-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"compat-flag-disabled-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/compat-flag-enabled-test.js",
    "content": "// Test that verifies compat flags are at NEWEST state (all enabled).\n// This test only runs in the @all-compat-flags variant (default variant is disabled).\n\nimport { strictEqual } from 'node:assert';\n\nexport const compatFlagEnabledTest = {\n  test() {\n    // With compat date 2999-12-31, formdata_parser_supports_files should be enabled\n    // (it was enabled on 2021-11-03).\n    const isEnabled =\n      Cloudflare.compatibilityFlags.formdata_parser_supports_files;\n    strictEqual(\n      isEnabled,\n      true,\n      'formdata_parser_supports_files should be ENABLED with newest compat date'\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/compat-flag-enabled-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"compat-flag-enabled-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"compat-flag-enabled-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/compat-flags-test.js",
    "content": "import { ok, strictEqual, throws } from 'node:assert';\n\nconst { compatibilityFlags } = globalThis.Cloudflare;\nok(Object.isSealed(compatibilityFlags));\nok(Object.isFrozen(compatibilityFlags));\nok(!Object.isExtensible(compatibilityFlags));\n\n// It shuld be possible to shadow the Cloudflare global\nconst Cloudflare = 1;\nstrictEqual(Cloudflare, 1);\n\nexport const compatFlagsTest = {\n  test() {\n    // The compatibility flags object should be sealed, frozen, and not extensible.\n    throws(() => (compatibilityFlags.no_nodejs_compat_v2 = '...'));\n    throws(() => (compatibilityFlags.not_a_real_compat_flag = '...'));\n    throws(() => {\n      delete compatibilityFlags['nodejs_compat_v2'];\n    });\n\n    // The compatibility flags object should have no prototype.\n    strictEqual(Object.getPrototypeOf(compatibilityFlags), null);\n\n    // The compatibility flags object should have the expected properties...\n    // That is... the only keys that should appear on the compatibilityFlags\n    // object are the enable flags.\n    //\n    // If a key does not appear, it can mean one of three things:\n    // 1. It is a disable flag.\n    // 2. It is an experimental flag and the experimental option is not set.\n    // 3. The flag does not exist.\n    //\n    // At this level we make no attempt to differentiate between these cases\n    ok(compatibilityFlags['nodejs_compat_v2']);\n    ok(compatibilityFlags['url_standard']);\n\n    ok(!compatibilityFlags['no_nodejs_compat_v2']);\n    ok(!compatibilityFlags['url_original']);\n    strictEqual(compatibilityFlags['no_nodejs_compat_v2'], undefined);\n    strictEqual(compatibilityFlags['url_original'], undefined);\n\n    // Since we are not specifying the experimental flag, experimental flags should\n    // not be included in the output.\n    strictEqual(compatibilityFlags['durable_object_rename'], undefined);\n    strictEqual('durable_object_rename' in compatibilityFlags, false);\n\n    // If a flag does not exist, the value will be undefined.\n    strictEqual(compatibilityFlags['not-a-real-compat-flag'], undefined);\n    strictEqual('not-a-real-compat-flag' in compatibilityFlags, false);\n\n    // The compatibility flags object should have the expected keys.\n    const keys = Object.keys(compatibilityFlags);\n    ok(keys.includes('nodejs_compat_v2'));\n    ok(keys.includes('url_standard'));\n    ok(!keys.includes('url_original'));\n    ok(!keys.includes('not-a-real-compat-flag'));\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/compat-flags-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"compat-flags-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"compat-flags-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat_v2\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/compression-streams-test.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { strictEqual, rejects, ok } from 'node:assert';\n\n// Test CompressionStream/DecompressionStream with gzip\nexport const compressionGzip = {\n  async test() {\n    let output;\n    const testData = 'hello'.repeat(100);\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n\n    {\n      const { writable, readable } = new CompressionStream('gzip');\n\n      const writer = writable.getWriter();\n      const reader = readable.getReader();\n\n      await writer.write(enc.encode(testData));\n      await writer.close();\n\n      output = await reader.read();\n    }\n\n    {\n      const { writable, readable } = new DecompressionStream('gzip');\n\n      const writer = writable.getWriter();\n      const reader = readable.getReader();\n\n      await writer.write(output.value);\n      await writer.close();\n\n      const result = await reader.read();\n\n      strictEqual(dec.decode(result.value), testData);\n    }\n  },\n};\n\n// Test CompressionStream/DecompressionStream with deflate\nexport const compressionDeflate = {\n  async test() {\n    let output;\n    const testData = 'hello'.repeat(100);\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n\n    {\n      const { writable, readable } = new CompressionStream('deflate');\n\n      const writer = writable.getWriter();\n      const reader = readable.getReader();\n\n      await writer.write(enc.encode(testData));\n      await writer.close();\n\n      output = await reader.read();\n    }\n\n    {\n      const { writable, readable } = new DecompressionStream('deflate');\n\n      const writer = writable.getWriter();\n      const reader = readable.getReader();\n\n      await writer.write(output.value);\n      await writer.close();\n\n      const result = await reader.read();\n\n      strictEqual(dec.decode(result.value), testData);\n    }\n  },\n};\n\n// Test CompressionStream/DecompressionStream with deflate-raw\nexport const compressionDeflateRaw = {\n  async test() {\n    let output;\n    const testData = 'hello'.repeat(100);\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n\n    {\n      const { writable, readable } = new CompressionStream('deflate-raw');\n\n      const writer = writable.getWriter();\n      const reader = readable.getReader();\n\n      await writer.write(enc.encode(testData));\n      await writer.close();\n\n      output = await reader.read();\n    }\n\n    {\n      const { writable, readable } = new DecompressionStream('deflate-raw');\n\n      const writer = writable.getWriter();\n      const reader = readable.getReader();\n\n      await writer.write(output.value);\n      await writer.close();\n\n      const result = await reader.read();\n\n      strictEqual(dec.decode(result.value), testData);\n    }\n  },\n};\n\n// Test compression/decompression with pending read\nexport const compressionPendingRead = {\n  async test() {\n    const testData = 'hello';\n    const check = new Uint8Array([\n      0x78, 0x9c, 0xcb, 0x48, 0xcd, 0xc9, 0xc9, 0x07, 0x00, 0x06, 0x2c, 0x02,\n      0x15,\n    ]);\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n\n    {\n      const { writable, readable } = new CompressionStream('deflate');\n\n      const writer = writable.getWriter();\n      const reader = readable.getReader();\n\n      const read = reader.read();\n\n      await writer.write(enc.encode(testData));\n      await writer.close();\n\n      await read;\n    }\n\n    {\n      const { writable, readable } = new DecompressionStream('deflate');\n\n      const writer = writable.getWriter();\n      const reader = readable.getReader();\n\n      const read = reader.read();\n\n      await writer.write(check);\n      await writer.close();\n\n      const result = await read;\n\n      strictEqual(dec.decode(result.value), testData);\n    }\n  },\n};\n\n// Test cancel/abort behavior\nexport const compressionCancelAbort = {\n  async test() {\n    {\n      const { writable, readable } = new CompressionStream('deflate');\n      const reader = readable.getReader();\n      const writer = writable.getWriter();\n      const promise = reader.read();\n      writer.abort(new Error('boom'));\n      await rejects(promise, { message: 'boom' });\n    }\n\n    {\n      const { readable } = new CompressionStream('deflate');\n      const reader = readable.getReader();\n      const promise = reader.read();\n      reader.cancel(new Error('boom'));\n      await rejects(promise, { message: 'boom' });\n    }\n  },\n};\n\n// Test decompression error handling\nexport const decompressionError = {\n  async test() {\n    // The data is not compressed so DecompressionStream will fail.\n    const { writable, readable } = new DecompressionStream('deflate');\n\n    const writer = writable.getWriter();\n    const reader = readable.getReader();\n\n    // Write uncompressed data which should cause a decompression error\n    // The write itself may also reject, so we need to handle that too\n    const writePromise = writer\n      .write(new TextEncoder().encode('not compressed data'))\n      .catch(() => {});\n\n    // The read should fail with a TypeError\n    await rejects(reader.read(), TypeError);\n\n    // A second attempt to read also fails\n    await rejects(reader.read(), TypeError);\n\n    // Ensure the write operation completes before the test ends\n    await writePromise;\n  },\n};\n\n// Test decompression error via iteration\nexport const decompressionErrorIteration = {\n  async test() {\n    const { writable, readable } = new DecompressionStream('deflate');\n\n    const writer = writable.getWriter();\n\n    // Write uncompressed data which should cause a decompression error\n    writer.write(new TextEncoder().encode('not compressed data'));\n\n    const consume = async () => {\n      for await (const _ of readable) {\n        // Should not reach here\n      }\n    };\n\n    await rejects(consume(), TypeError);\n  },\n};\n\n// Test piped decompression with bad data does not hang\nexport const pipedDecompressionBadData = {\n  async test() {\n    async function consume(readable) {\n      const readAll = async () => {\n        for await (const _ of readable) {\n          // Should error\n        }\n      };\n      await rejects(readAll(), { message: 'Decompression failed.' });\n    }\n\n    async function doTest(transform) {\n      const { writable, readable } = new DecompressionStream('gzip');\n\n      const dest = readable.pipeThrough(transform);\n\n      const c = consume(dest);\n\n      const enc = new TextEncoder();\n      const writer = writable.getWriter();\n      await rejects(writer.write(enc.encode('hello world')), {\n        message: 'Decompression failed.',\n      });\n\n      await c;\n    }\n\n    await Promise.all([\n      doTest(new IdentityTransformStream()),\n      doTest(new TransformStream()),\n    ]);\n  },\n};\n\n// Test TransformStream pipeThrough CompressionStream\nexport const transformStreamThroughCompression = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n\n    // Create source data\n    const sourceData = 'hello world test data';\n\n    // Create a TransformStream as source\n    const { readable, writable } = new TransformStream();\n\n    // Pipe through compression\n    const compressed = readable.pipeThrough(new CompressionStream('gzip'));\n\n    // Pipe through decompression\n    const decompressed = compressed.pipeThrough(\n      new DecompressionStream('gzip')\n    );\n\n    // Write source data\n    const writer = writable.getWriter();\n    await writer.write(enc.encode(sourceData));\n    await writer.close();\n\n    // Read result\n    let result = '';\n    for await (const chunk of decompressed) {\n      result += dec.decode(chunk, { stream: true });\n    }\n    result += dec.decode();\n\n    strictEqual(result, sourceData);\n  },\n};\n\n// Test request.body pipeThrough DecompressionStream\n// This test requires a service binding that sends gzip-compressed data\nexport const requestBodyPipethrough = {\n  async test(_ctrl, env) {\n    // Fetch compressed data from the service\n    const response = await env.SERVICE.fetch('http://test/compressed');\n\n    // Pipe the response body through decompression\n    const decompressed = response.body.pipeThrough(\n      new DecompressionStream('gzip')\n    );\n\n    // Read all decompressed data\n    const dec = new TextDecoder();\n    let result = '';\n    for await (const chunk of decompressed) {\n      result += dec.decode(chunk, { stream: true });\n    }\n    result += dec.decode();\n\n    strictEqual(result, 'hello world '.repeat(100));\n  },\n};\n\n// Test transform roundtrip: request body → DecompressionStream → TransformStream → IdentityTransformStream\nexport const transformRoundtrip = {\n  async test(_ctrl, env) {\n    // Fetch compressed data from the service\n    const response = await env.SERVICE.fetch('http://test/compressed');\n\n    const decompression = new DecompressionStream('gzip');\n    const ts = new TransformStream({\n      transform(chunk, controller) {\n        controller.enqueue(chunk);\n      },\n    });\n    const { readable, writable } = new IdentityTransformStream();\n\n    // Test that piping from a JS-backed TransformStream through an\n    // IdentityTransformStream does not result in a hung pipeTo promise.\n    const pipePromise = response.body\n      .pipeThrough(decompression)\n      .pipeThrough(ts)\n      .pipeTo(writable);\n\n    // Read the result\n    const dec = new TextDecoder();\n    let result = '';\n    for await (const chunk of readable) {\n      result += dec.decode(chunk, { stream: true });\n    }\n    result += dec.decode();\n\n    await pipePromise;\n\n    strictEqual(result, 'hello world '.repeat(100));\n  },\n};\n\n// Test piping JS-backed stream to internal Response body.\n// Regression test for close signal propagation from JS ReadableStream to internal writable.\nexport const compressionPipeline = {\n  async test(_ctrl, env) {\n    const response = await env.SERVICE.fetch('http://test/compressionPipeline');\n    strictEqual(response.status, 200);\n\n    const decompressed = response.body.pipeThrough(\n      new DecompressionStream('gzip')\n    );\n\n    const dec = new TextDecoder();\n    let result = '';\n    for await (const chunk of decompressed) {\n      result += dec.decode(chunk, { stream: true });\n    }\n    result += dec.decode();\n\n    strictEqual(result, 'hello world '.repeat(100));\n  },\n};\n\n// Test DecompressionStream readable piped to internal Response body.\nexport const decompressionPipeline = {\n  async test(_ctrl, env) {\n    const response = await env.SERVICE.fetch(\n      'http://test/decompressionPipeline'\n    );\n    strictEqual(response.status, 200);\n\n    const dec = new TextDecoder();\n    let result = '';\n    for await (const chunk of response.body) {\n      result += dec.decode(chunk, { stream: true });\n    }\n    result += dec.decode();\n\n    strictEqual(result, 'hello world '.repeat(100));\n  },\n};\n\n// Test strictCompression: closing without finishing decompression should error\nexport const strictCompressionCloseWithoutFinish = {\n  async test() {\n    const ds = new DecompressionStream('gzip');\n    const writer = ds.writable.getWriter();\n    await rejects(writer.close(), TypeError);\n  },\n};\n\n// Test strictCompression: trailing data after valid gzip stream should error\nexport const strictCompressionTrailingData = {\n  async test() {\n    const ds = new DecompressionStream('gzip');\n    const writer = ds.writable.getWriter();\n\n    // Gzipped string \"FOOBAR\", plus a trailing 0xFF byte\n    const trailingStrm = new Uint8Array([\n      0x1f, 0x8b, 0x08, 0x00, 0xf9, 0x05, 0xb7, 0x59, 0x00, 0x03, 0x4b, 0xcb,\n      0xcf, 0x4f, 0x4a, 0x2c, 0x02, 0x00, 0x95, 0x1f, 0xf6, 0x9e, 0x06, 0x00,\n      0x00, 0x00, 0xff,\n    ]);\n\n    await rejects(writer.write(trailingStrm), TypeError);\n  },\n};\n\n// =============================================================================\n// Edge case tests for compression streams with chunked data.\n// These tests focus on scenarios where data arrives in multiple chunks,\n// large data handling, and error scenarios.\n//\n// Test inspirations:\n// - Bun: test/js/web/streams/compression.test.ts (round-trip tests, various formats)\n// - Deno: tests/unit/streams_test.ts (compression abort/cancel tests)\n// - Bun: test/js/web/fetch/fetch.stream.test.ts (corrupted data handling)\n// =============================================================================\n\n// Test compression with many small chunks (1 byte each)\n// Inspired by: Bun test/js/web/streams/compression.test.ts\nexport const compressionMultipleSmallChunks = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    const originalText = 'Hello, World! This is a test of chunked compression.';\n    const bytes = enc.encode(originalText);\n\n    const cs = new CompressionStream('gzip');\n    const writer = cs.writable.getWriter();\n\n    for (let i = 0; i < bytes.length; i++) {\n      await writer.write(new Uint8Array([bytes[i]]));\n    }\n    await writer.close();\n\n    const compressedChunks = [];\n    const reader = cs.readable.getReader();\n    while (true) {\n      const { value, done } = await reader.read();\n      if (done) break;\n      compressedChunks.push(value);\n    }\n\n    const ds = new DecompressionStream('gzip');\n    const dsWriter = ds.writable.getWriter();\n    for (const chunk of compressedChunks) {\n      await dsWriter.write(chunk);\n    }\n    await dsWriter.close();\n\n    let result = '';\n    const dsReader = ds.readable.getReader();\n    while (true) {\n      const { value, done } = await dsReader.read();\n      if (done) break;\n      result += dec.decode(value, { stream: true });\n    }\n    result += dec.decode();\n\n    strictEqual(result, originalText);\n  },\n};\n\n// Test decompression with chunked input (compressed data arrives in pieces)\n// Inspired by: Bun test/js/web/fetch/fetch.stream.test.ts\nexport const decompressionChunkedInput = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    const originalText = 'Test data for chunked decompression testing.';\n\n    const cs = new CompressionStream('deflate');\n    const csWriter = cs.writable.getWriter();\n    await csWriter.write(enc.encode(originalText));\n    await csWriter.close();\n\n    const compressedChunks = [];\n    const csReader = cs.readable.getReader();\n    while (true) {\n      const { value, done } = await csReader.read();\n      if (done) break;\n      compressedChunks.push(value);\n    }\n\n    const totalLength = compressedChunks.reduce(\n      (sum, chunk) => sum + chunk.length,\n      0\n    );\n    const compressed = new Uint8Array(totalLength);\n    let offset = 0;\n    for (const chunk of compressedChunks) {\n      compressed.set(chunk, offset);\n      offset += chunk.length;\n    }\n\n    const ds = new DecompressionStream('deflate');\n    const dsWriter = ds.writable.getWriter();\n\n    for (let i = 0; i < compressed.length; i += 2) {\n      const end = Math.min(i + 2, compressed.length);\n      await dsWriter.write(compressed.slice(i, end));\n    }\n    await dsWriter.close();\n\n    let result = '';\n    const dsReader = ds.readable.getReader();\n    while (true) {\n      const { value, done } = await dsReader.read();\n      if (done) break;\n      result += dec.decode(value, { stream: true });\n    }\n    result += dec.decode();\n\n    strictEqual(result, originalText);\n  },\n};\n\n// Test compression with large data (100KB+)\n// Inspired by: Bun bench/snippets/compression-streams.mjs\nexport const compressionLargeData = {\n  async test() {\n    const size = 100 * 1024;\n    const data = new Uint8Array(size);\n    for (let i = 0; i < size; i++) {\n      data[i] = i % 256;\n    }\n\n    const cs = new CompressionStream('gzip');\n    const csWriter = cs.writable.getWriter();\n    await csWriter.write(data);\n    await csWriter.close();\n\n    const compressedChunks = [];\n    const csReader = cs.readable.getReader();\n    while (true) {\n      const { value, done } = await csReader.read();\n      if (done) break;\n      compressedChunks.push(value);\n    }\n\n    const compressedLength = compressedChunks.reduce(\n      (sum, chunk) => sum + chunk.length,\n      0\n    );\n    ok(compressedLength < size, 'Compressed should be smaller than original');\n\n    const ds = new DecompressionStream('gzip');\n    const dsWriter = ds.writable.getWriter();\n    for (const chunk of compressedChunks) {\n      await dsWriter.write(chunk);\n    }\n    await dsWriter.close();\n\n    const decompressedChunks = [];\n    const dsReader = ds.readable.getReader();\n    while (true) {\n      const { value, done } = await dsReader.read();\n      if (done) break;\n      decompressedChunks.push(value);\n    }\n\n    const decompressedLength = decompressedChunks.reduce(\n      (sum, chunk) => sum + chunk.length,\n      0\n    );\n    strictEqual(decompressedLength, size);\n\n    const decompressed = new Uint8Array(decompressedLength);\n    let dOffset = 0;\n    for (const chunk of decompressedChunks) {\n      decompressed.set(chunk, dOffset);\n      dOffset += chunk.length;\n    }\n\n    for (let i = 0; i < size; i++) {\n      strictEqual(decompressed[i], i % 256, `Byte at ${i} should match`);\n    }\n  },\n};\n\n// Test decompression with completely invalid data should error\n// Inspired by: Bun test/js/web/fetch/fetch.stream.test.ts (corrupted data handling)\nexport const decompressionTruncated = {\n  async test() {\n    const invalidData = new Uint8Array([0x00, 0x01, 0x02, 0x03, 0x04, 0x05]);\n\n    const ds = new DecompressionStream('gzip');\n    const dsWriter = ds.writable.getWriter();\n    const dsReader = ds.readable.getReader();\n\n    await rejects(async () => {\n      await dsWriter.write(invalidData);\n      await dsWriter.close();\n\n      while (true) {\n        const { done } = await dsReader.read();\n        if (done) break;\n      }\n    });\n  },\n};\n\n// Test empty stream through compression\n// Inspired by: Bun test/js/web/streams/compression.test.ts\nexport const compressionEmptyStream = {\n  async test() {\n    const cs = new CompressionStream('deflate');\n    const writer = cs.writable.getWriter();\n    await writer.close();\n\n    const chunks = [];\n    const reader = cs.readable.getReader();\n    while (true) {\n      const { value, done } = await reader.read();\n      if (done) break;\n      chunks.push(value);\n    }\n\n    const ds = new DecompressionStream('deflate');\n    const dsWriter = ds.writable.getWriter();\n    for (const chunk of chunks) {\n      await dsWriter.write(chunk);\n    }\n    await dsWriter.close();\n\n    const dsReader = ds.readable.getReader();\n    const result = await dsReader.read();\n    ok(result.done);\n  },\n};\n\n// Test empty decompression\n// Inspired by: Bun test/js/web/streams/compression.test.ts\nexport const decompressionEmptyStream = {\n  async test() {\n    // Create valid empty compressed data first\n    const cs = new CompressionStream('gzip');\n    const csWriter = cs.writable.getWriter();\n    await csWriter.close();\n\n    const compressedChunks = [];\n    const csReader = cs.readable.getReader();\n    while (true) {\n      const { value, done } = await csReader.read();\n      if (done) break;\n      compressedChunks.push(value);\n    }\n\n    // Now decompress\n    const ds = new DecompressionStream('gzip');\n    const dsWriter = ds.writable.getWriter();\n    for (const chunk of compressedChunks) {\n      await dsWriter.write(chunk);\n    }\n    await dsWriter.close();\n\n    // Verify empty result\n    const dsReader = ds.readable.getReader();\n    const result = await dsReader.read();\n    ok(result.done);\n  },\n};\n\n// Test CompressionStream piped to IdentityTransformStream\n// Inspired by: workerd compression-streams-test.js\nexport const compressionPipeToIdentity = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    const originalText = 'Piping compression to identity transform.';\n\n    const source = new ReadableStream({\n      start(controller) {\n        controller.enqueue(enc.encode(originalText));\n        controller.close();\n      },\n    });\n\n    const compressed = source\n      .pipeThrough(new CompressionStream('deflate'))\n      .pipeThrough(new IdentityTransformStream());\n\n    const decompressed = compressed.pipeThrough(\n      new DecompressionStream('deflate')\n    );\n\n    let result = '';\n    for await (const chunk of decompressed) {\n      result += dec.decode(chunk, { stream: true });\n    }\n    result += dec.decode();\n\n    strictEqual(result, originalText);\n  },\n};\n\n// Test all supported formats work with chunked data\n// Inspired by: Bun test/js/web/streams/compression.test.ts\nexport const decompressionAllFormats = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    const formats = ['gzip', 'deflate', 'deflate-raw'];\n    const originalText = 'Testing all compression formats with chunked data!';\n\n    for (const format of formats) {\n      // Compress\n      const cs = new CompressionStream(format);\n      const csWriter = cs.writable.getWriter();\n\n      // Write in chunks\n      const bytes = enc.encode(originalText);\n      for (let i = 0; i < bytes.length; i += 5) {\n        await csWriter.write(bytes.slice(i, Math.min(i + 5, bytes.length)));\n      }\n      await csWriter.close();\n\n      // Collect compressed\n      const compressedChunks = [];\n      const csReader = cs.readable.getReader();\n      while (true) {\n        const { value, done } = await csReader.read();\n        if (done) break;\n        compressedChunks.push(value);\n      }\n\n      // Decompress\n      const ds = new DecompressionStream(format);\n      const dsWriter = ds.writable.getWriter();\n      for (const chunk of compressedChunks) {\n        await dsWriter.write(chunk);\n      }\n      await dsWriter.close();\n\n      // Verify\n      let result = '';\n      const dsReader = ds.readable.getReader();\n      while (true) {\n        const { value, done } = await dsReader.read();\n        if (done) break;\n        result += dec.decode(value, { stream: true });\n      }\n      result += dec.decode();\n\n      strictEqual(\n        result,\n        originalText,\n        `Format ${format} should round-trip correctly`\n      );\n    }\n  },\n};\n\nexport default {\n  async fetch(request, env) {\n    if (request.url.includes('/compressed')) {\n      const data = 'hello world '.repeat(100);\n      const enc = new TextEncoder();\n      const { readable, writable } = new CompressionStream('gzip');\n      const writer = writable.getWriter();\n      await writer.write(enc.encode(data));\n      await writer.close();\n      return new Response(readable, {\n        headers: { 'Content-Encoding': 'gzip' },\n      });\n    }\n    if (request.url.includes('/stream')) {\n      return new Response('hello world '.repeat(100));\n    }\n    if (request.url.includes('/compressionPipeline')) {\n      // Pipe through TransformStream → CompressionStream → Response body (internal writable)\n      const response = await env.SERVICE.fetch('http://test/stream');\n      const { readable, writable } = new TransformStream();\n      response.body.pipeTo(writable);\n      const compressed = readable.pipeThrough(new CompressionStream('gzip'));\n      return new Response(compressed, { encodeBody: 'manual' });\n    }\n    if (request.url.includes('/decompressionPipeline')) {\n      // Pipe DecompressionStream readable → Response body (internal writable)\n      const response = await env.SERVICE.fetch('http://test/compressed');\n      const decompressed = response.body.pipeThrough(\n        new DecompressionStream('gzip')\n      );\n      return new Response(decompressed);\n    }\n    return new Response('Not found', { status: 404 });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/compression-streams-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"compression-streams-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"compression-streams-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"streams_enable_constructors\", \"transformstream_enable_standard_constructor\", \"strict_compression_checks\"],\n        bindings = [\n          ( name = \"SERVICE\", service = \"compression-streams-test\" ),\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/cross-context-promise-test.js",
    "content": "import { match, rejects, strictEqual, throws } from 'assert';\nimport { AsyncLocalStorage } from 'async_hooks';\nimport { inspect } from 'util';\nimport { mock } from 'node:test';\n\nexport const crossContextResolveWorks = {\n  async test(_, env) {\n    // We're going to send two simultaneous requests to the same endpoint.\n    const results = await Promise.allSettled([\n      env.subrequest.fetch('http://example.org/resolve'),\n      env.subrequest.fetch('http://example.org/resolve'),\n    ]);\n    strictEqual(results[0].status, 'fulfilled');\n    strictEqual(results[1].status, 'fulfilled');\n    strictEqual(results[0].value.status, 200);\n    strictEqual(results[1].value.status, 200);\n    strictEqual(await results[0].value.text(), 'ok');\n    strictEqual(await results[1].value.text(), 'ok');\n  },\n};\n\nexport const crossContextRejectWorks = {\n  async test(_, env) {\n    // We're going to send two simultaneous requests to the same endpoint.\n    const results = await Promise.allSettled([\n      env.subrequest.fetch('http://example.org/reject'),\n      env.subrequest.fetch('http://example.org/reject'),\n    ]);\n    strictEqual(results[0].status, 'fulfilled');\n    strictEqual(results[1].status, 'fulfilled');\n    strictEqual(results[0].value.status, 200);\n    strictEqual(results[1].value.status, 200);\n    strictEqual(await results[0].value.text(), 'ok');\n    strictEqual(await results[1].value.text(), 'ok');\n  },\n};\n\nexport const crossContextStreamWorks = {\n  async test(_, env) {\n    // We're going to send two simultaneous requests to the same endpoint.\n    const results = await Promise.allSettled([\n      env.subrequest.fetch('http://example.org/stream'),\n      env.subrequest.fetch('http://example.org/stream'),\n    ]);\n    strictEqual(results[0].status, 'fulfilled');\n    strictEqual(results[1].status, 'fulfilled');\n    strictEqual(results[0].value.status, 200);\n    strictEqual(results[1].value.status, 200);\n    strictEqual(await results[0].value.text(), 'ok');\n    strictEqual(await results[1].value.text(), 'ok');\n  },\n};\n\nexport const customThenableWorks = {\n  async test(_, env) {\n    // We're going to send two simultaneous requests to the same endpoint.\n    const results = await Promise.allSettled([\n      env.subrequest.fetch('http://example.org/thenable'),\n      env.subrequest.fetch('http://example.org/thenable'),\n    ]);\n    strictEqual(results[0].status, 'fulfilled');\n    strictEqual(results[1].status, 'fulfilled');\n    strictEqual(results[0].value.status, 200);\n    strictEqual(results[1].value.status, 200);\n    strictEqual(await results[0].value.text(), 'ok');\n    strictEqual(await results[1].value.text(), 'ok');\n  },\n};\n\nexport const unhandledRejectionWorks = {\n  async test(_, env) {\n    // We're going to send two simultaneous requests to the same endpoint.\n    const results = await Promise.allSettled([\n      env.subrequest.fetch('http://example.org/unhandled'),\n      env.subrequest.fetch('http://example.org/unhandled'),\n    ]);\n    strictEqual(results[0].status, 'fulfilled');\n    strictEqual(results[1].status, 'fulfilled');\n    strictEqual(results[0].value.status, 200);\n    strictEqual(results[1].value.status, 200);\n    strictEqual(await results[0].value.text(), 'ok');\n    strictEqual(await results[1].value.text(), 'ok');\n  },\n};\n\nexport const expiredContextWorks = {\n  async test(_, env) {\n    // We're going to send two simultaneous requests to the same endpoint.\n    const results = await Promise.allSettled([\n      env.subrequest.fetch('http://example.org/expired'),\n      env.subrequest.fetch('http://example.org/expired'),\n    ]);\n    strictEqual(results[0].status, 'rejected');\n    strictEqual(results[1].status, 'fulfilled');\n    strictEqual(\n      results[0].reason.message,\n      \"The Workers runtime canceled this request because it detected that your Worker's code \" +\n        'had hung and would never generate a response. Refer to: ' +\n        'https://developers.cloudflare.com/workers/observability/errors/'\n    );\n    strictEqual(results[1].value.status, 200);\n    strictEqual(await results[1].value.text(), 'ok');\n    // Wait a tick for things to settle out before checking the global.\n    // We're just making sure here that the promise in the first request\n    // was canceled correctly.\n    await scheduler.wait(100);\n    strictEqual(globalThis.expiredRan, undefined);\n  },\n};\n\nexport const asyncIterWorks = {\n  async test(_, env) {\n    // We're going to send two simultaneous requests to the same endpoint.\n    const results = await Promise.allSettled([\n      env.subrequest.fetch('http://example.org/asynciter'),\n      env.subrequest.fetch('http://example.org/asynciter'),\n    ]);\n    strictEqual(results[0].status, 'fulfilled');\n    strictEqual(results[1].status, 'fulfilled');\n    strictEqual(results[0].value.status, 200);\n    strictEqual(results[1].value.status, 200);\n    strictEqual(await results[0].value.text(), 'ok');\n    strictEqual(await results[1].value.text(), 'ok');\n  },\n};\n\nexport const cyclicAwaitsWorks = {\n  async test(_, env) {\n    // We're going to send two simultaneous requests to the same endpoint.\n    const results = await Promise.allSettled([\n      env.subrequest.fetch('http://example.org/cyclic'),\n      env.subrequest.fetch('http://example.org/cyclic'),\n    ]);\n    strictEqual(results[0].status, 'rejected');\n    strictEqual(results[1].status, 'fulfilled');\n  },\n};\n\nexport default {\n  async fetch(req, env, ctx) {\n    if (req.url.endsWith('/resolve')) {\n      return resolveTest(req, env, ctx);\n    } else if (req.url.endsWith('/reject')) {\n      return rejectTest(req, env, ctx);\n    } else if (req.url.endsWith('/stream')) {\n      return crossRequestStream(req, env, ctx);\n    } else if (req.url.endsWith('/thenable')) {\n      return customThenable(req, env, ctx);\n    } else if (req.url.endsWith('/unhandled')) {\n      return unhandledRejection(req, env, ctx);\n    } else if (req.url.endsWith('/expired')) {\n      return expiredContext(req, env, ctx);\n    } else if (req.url.endsWith('/asynciter')) {\n      return asyncIterator(req, env, ctx);\n    } else if (req.url.endsWith('/cyclic')) {\n      return cyclicPromise(req, env, ctx);\n    }\n    throw new Error('Invalid URL');\n  },\n};\n\nfunction setupWaiter(ctx) {\n  const { promise, resolve } = Promise.withResolvers();\n  setTimeout(resolve, 1000);\n  ctx.waitUntil(promise);\n}\n\nasync function resolveTest(req, env, ctx) {\n  const als = new AsyncLocalStorage();\n  // This will be called twice. The first time in one request where we will\n  // create the promise and resolver. The second time for the second request\n  // where the promise will be resolved.\n  if (globalThis.request1 === undefined) {\n    setupWaiter(ctx);\n    const { promise, resolve } = Promise.withResolvers();\n    globalThis.request1 = { promise, resolve };\n    const ab = AbortSignal.abort();\n    strictEqual(ab.aborted, true);\n    await als.run(123, async () => {\n      await promise;\n      strictEqual(als.getStore(), 123);\n    });\n    // This part is the main test. It will not run until after the promise\n    // is resolved in the second request.\n    // We use an AbortSignal because it is bound to the IoContext and will\n    // throw an error if ab.aborted is checked from the wrong IoContext.\n    // If this line runes, it is proof that the promise continuation is\n    // running in the correct IoContext.\n    strictEqual(ab.aborted, true);\n    return new Response('ok');\n  }\n\n  // This is our second request. Here, all we do is resolve the promise.\n\n  // While we are deferring the continuations from the promise, the promise state\n  // change should happen immediately. Before calling resolve, the state should\n  // be pending. After calling resolve, the state should be resolved showing\n  // an undefined value. Updating the state of the promise immediately and\n  // synchronously is required by the language specification.\n  // See: https://tc39.es/ecma262/#sec-promise-resolve-functions\n  strictEqual(inspect(globalThis.request1.promise), 'Promise { <pending> }');\n  als.run('abc', () => globalThis.request1.resolve());\n  strictEqual(inspect(globalThis.request1.promise), 'Promise { undefined }');\n\n  const p = globalThis.request1.promise;\n\n  globalThis.request1 = undefined;\n\n  // We ought to be able to do a cross-request wait on the promise still.\n  await p;\n\n  return new Response('ok');\n}\n\nconst reason = new Error('boom');\n\nasync function rejectTest(req, env, ctx) {\n  if (globalThis.request2 === undefined) {\n    setupWaiter(ctx);\n    const { promise, reject } = Promise.withResolvers();\n    globalThis.request2 = { reject };\n    const ab = AbortSignal.abort();\n    strictEqual(ab.aborted, true);\n    try {\n      // The promise will be rejected from the other request.\n      await promise;\n      throw new Error('should not get here');\n    } catch (err) {\n      // The reason provided by the other request should be carried\n      // through here. If the ab.aborted check throws, then the continuation\n      // is running in the wrong IoContext, which is the main thing we are\n      // testing for here.\n      strictEqual(err, reason);\n      strictEqual(ab.aborted, true);\n    }\n    return new Response('ok');\n  }\n\n  // This is our second request. Here, all we do is reject the promise.\n  globalThis.request2.reject(reason);\n  globalThis.request2 = undefined;\n  return new Response('ok');\n}\n\nasync function crossRequestStream(req, env, ctx) {\n  // Here, we are going to create a stream that will be used across\n  // requests. The first request will create the stream and queue up\n  // a pending read. The second request will fulfill the read by providing\n  // data to the stream.\n  if (globalThis.stream === undefined) {\n    setupWaiter(ctx);\n    let controller;\n    const readable = new ReadableStream({\n      start(c) {\n        controller = c;\n      },\n    });\n    globalThis.stream = { controller };\n    const reader = readable.getReader();\n    const ab = AbortSignal.abort();\n    strictEqual(ab.aborted, true);\n    const read = await reader.read();\n    strictEqual(ab.aborted, true);\n    return new Response('ok');\n  }\n\n  // This is our second request. Here, all we do is provide data to the stream.\n  // This will cause our pending read to be fulfilled.\n  const enc = new TextEncoder();\n  globalThis.stream.controller.enqueue(enc.encode('hello'));\n  globalThis.stream = undefined;\n  return new Response('ok');\n}\n\nasync function customThenable(req, env, ctx) {\n  // This will be called twice. The first time in one request where we will\n  // create the promise and resolver. The second time for the second request\n  // where the promise will be resolved.\n  if (globalThis.thenable === undefined) {\n    setupWaiter(ctx);\n    const { promise, resolve } = Promise.withResolvers();\n    globalThis.thenable = { resolve };\n    const ab = AbortSignal.abort();\n    strictEqual(ab.aborted, true);\n\n    // We check to make sure the value provided by the custom thenable is\n    // property passed through to the promise resolution.\n\n    strictEqual(await promise, 1);\n    // This part is the main test. It will not run until after the promise\n    // is resolved in the second request.\n    // We use an AbortSignal because it is bound to the IoContext and will\n    // throw an error if ab.aborted is checked from the wrong IoContext.\n    // If this line runes, it is proof that the promise continuation is\n    // running in the correct IoContext.\n    strictEqual(ab.aborted, true);\n    return new Response('ok');\n  }\n\n  // This is our second request. Here, all we do is resolve the promise.\n  const ab = AbortSignal.abort();\n  strictEqual(ab.aborted, true);\n\n  const then = mock.fn((resolve) => {\n    // The thenable should be invoked in the second request's IoContext.\n    // If it is not, then the ab.aborted check below will fail.\n    strictEqual(ab.aborted, true);\n    resolve(1);\n  });\n\n  globalThis.thenable.resolve({ then });\n  globalThis.thenable = undefined;\n\n  // Confirm that the thenable was called exactly once in this request.\n  await Promise.resolve();\n  strictEqual(then.mock.calls.length, 1);\n\n  return new Response('ok');\n}\n\nasync function unhandledRejection(req, env, ctx) {\n  // This will be called twice. The first time in one request where we will\n  // create the promise and resolver. The second time for the second request\n  // where the promise will be rejected.\n  if (globalThis.unhandled === undefined) {\n    setupWaiter(ctx);\n    const { promise, reject } = Promise.withResolvers();\n    globalThis.unhandled = { reject };\n    const ab = AbortSignal.abort();\n    strictEqual(ab.aborted, true);\n\n    const rejectPromise = Promise.withResolvers();\n    globalThis.addEventListener(\n      'unhandledrejection',\n      (event) => {\n        // Here we have a gotcha! The unhandledrejection event is dispatched\n        // synchronously when the promise is rejected. It does not get deferred.\n        // so the IoContext here will be the second request's IoContext! This\n        // means that our ab.aborted check will fail!\n        throws(() => ab.aborted, {\n          message: /I\\/O type: RefcountedCanceler/,\n        });\n        strictEqual(event.reason, reason);\n        rejectPromise.resolve();\n      },\n      { once: true }\n    );\n    await rejectPromise.promise;\n\n    return new Response('ok');\n  }\n\n  // This is our second request. Here, all we do is reject the promise.\n  globalThis.unhandled.reject(reason);\n  globalThis.unhandled = undefined;\n  return new Response('ok');\n}\n\nasync function expiredContext(req, env, ctx) {\n  if (globalThis.expired === undefined) {\n    // We do not arrange for a waiter here. We want the request context\n    // to be canceled before the promise is resolved. In the error log\n    // (which we unfortunately cannot check here) we should see a message\n    // about the hanging promise being canceled and another message about\n    // a promise resolved across request contexts but the target request\n    // is not longer active. On the calling side, the first request\n    // should reject and the second request should resolve.\n    const { promise, resolve } = Promise.withResolvers();\n    globalThis.expired = { resolve };\n    promise.then(() => {\n      // This should never run...\n      globalThis.expiredRan = true;\n    });\n    await new Promise(() => {});\n    return new Response('ok');\n  }\n\n  // This is our second request. Here, all we do is resolve the promise.\n  // Let's wait a bit to make sure the other request has had time to\n  // be canceled and destroyed.\n  await scheduler.wait(100);\n  globalThis.expired.resolve();\n  globalThis.expired = undefined;\n  return new Response('ok');\n}\n\nasync function* gen(ab) {\n  let c = 0;\n  for (;;) {\n    await scheduler.wait(10);\n    strictEqual(ab.aborted, true);\n    yield c++;\n  }\n}\n\nasync function asyncIterator(req, env, ctx) {\n  if (globalThis.asynciter === undefined) {\n    const ab = AbortSignal.abort();\n    globalThis.asynciter = gen(ab);\n    globalThis.asyncIter2 = gen(ab);\n    return new Response('ok');\n  }\n\n  // Advancing the iterator should throw an I/O error because the generator\n  // is bound to the wrong IoContext here. This test just confirms that our\n  // promise context patch does not change the expected behavior of the async\n  // generator and async iterators. Those should still throw I/O errors when\n  // the generator is bound to the wrong IoContext via something that it captures.\n  await rejects(globalThis.asynciter.next(), {\n    message: /Cannot perform I\\/O/,\n  });\n\n  const foo = {\n    [Symbol.asyncIterator]() {\n      return globalThis.asyncIter2;\n    },\n  };\n\n  try {\n    for await (const _ of foo) {\n    }\n    throw new Error('should not get here');\n  } catch (err) {\n    match(err.message, /Cannot perform I\\/O/);\n  }\n\n  return new Response('ok');\n}\n\nasync function cyclicPromise(req, env, ctx) {\n  if (globalThis.cyclic === undefined) {\n    setupWaiter(ctx);\n    const { promise, resolve } = Promise.withResolvers();\n    globalThis.cyclic = { promise, resolve };\n    const ab = AbortSignal.abort();\n    strictEqual(ab.aborted, true);\n    await promise;\n    throw new Error('should never get here');\n  }\n\n  async function foo() {\n    await globalThis.cyclic.promise;\n    throw new Error('shnould never get here');\n  }\n\n  // The first request will hang because the promise is resolved\n  // with a promise that waits on the first, creating a hang\n  // condition that we can detect. The test log will contain\n  // a message about a hanging promise being canceled.\n  globalThis.cyclic.resolve(foo());\n  globalThis.cyclic = undefined;\n\n  return new Response('ok');\n}\n"
  },
  {
    "path": "src/workerd/api/tests/cross-context-promise-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"cross-context-promise-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"cross-context-promise-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat_v2\",\n          \"handle_cross_request_promise_resolution\",\n          \"streams_enable_constructors\",\n        ],\n        bindings = [\n          (name = \"subrequest\", service = \"cross-context-promise-test\")\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/crypto-extras-test.js",
    "content": "import { strictEqual, ok } from 'node:assert';\n\nexport const timingSafeEqual = {\n  test() {\n    // Note that this does not actually test that the equality check is,\n    // in fact, timing safe. It checks only the basic operation of the API\n\n    const enc = new TextEncoder();\n    [\n      [new ArrayBuffer(0), new ArrayBuffer(0)],\n      [new ArrayBuffer(1), new ArrayBuffer(1)],\n      [enc.encode('hello'), enc.encode('hello')],\n      [enc.encode('hellothere'), enc.encode('hellothere').buffer],\n    ].forEach(([a, b]) => {\n      if (!crypto.subtle.timingSafeEqual(a, b)) {\n        throw new Error('inputs should have been equal', a, b);\n      }\n    });\n\n    [\n      [enc.encode('hello'), enc.encode('there')],\n      [new Uint8Array([1, 2, 3, 4]), new Uint32Array([1])],\n    ].forEach(([a, b]) => {\n      if (crypto.subtle.timingSafeEqual(a, b)) {\n        throw new Error('inputs should not have been equal', a, b);\n      }\n    });\n\n    [\n      ['hello', 'there'],\n      [new ArrayBuffer(0), new ArrayBuffer(1)],\n    ].forEach(([a, b]) => {\n      try {\n        crypto.subtle.timingSafeEqual(a, b);\n        throw new Error('inputs should have caused an error', a, b);\n      } catch {}\n    });\n  },\n};\n\nexport const randomUuid = {\n  test() {\n    const pattern =\n      /[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[ab89][a-f0-9]{3}-[a-f0-9]{12}/;\n    // Loop through a bunch of generated UUID's to make sure we're consistently successful.\n    for (let n = 0; n < 100; n++) {\n      const uuid = crypto.randomUUID();\n      if (!pattern.test(uuid)) {\n        throw new Error(`${uuid} is not a valid random UUID`);\n      }\n    }\n  },\n};\n\nexport const cryptoGcmIvZeroLength = {\n  async test() {\n    const key = await crypto.subtle.generateKey(\n      {\n        name: 'AES-GCM',\n        length: 256,\n      },\n      true,\n      ['encrypt', 'decrypt']\n    );\n\n    for (const op of ['encrypt', 'decrypt']) {\n      await crypto.subtle[op](\n        {\n          name: 'AES-GCM',\n          iv: new ArrayBuffer(0),\n        },\n        key,\n        new ArrayBuffer(100)\n      ).then(\n        () => {\n          throw new Error('should not have resolved');\n        },\n        (err) => {\n          if (\n            err.constructor !== DOMException ||\n            err.message !== 'AES-GCM IV must not be empty.'\n          ) {\n            throw err;\n          }\n        }\n      );\n    }\n  },\n};\n\nexport const cryptoZeroLength = {\n  async test() {\n    function hex2Uint8Array(str) {\n      var v = str.match(/.{1,2}/g);\n      var buf = new Uint8Array((str.length + 1) / 2);\n      for (var i = 0; i < v.length; i++) {\n        buf[i] = parseInt(v[i], 16);\n      }\n      return buf;\n    }\n\n    function arrayBuffer2hex(arr) {\n      return Array.from(new Uint8Array(arr))\n        .map((i) => ('0' + i.toString(16)).slice(-2))\n        .join('');\n    }\n\n    // Try using a zero-length input to various crypto functions. This should be valid.\n    // At one point, encrypt() would sometimes fail on an empty input -- but, mysteriously,\n    // it depended on how exactly the ArrayBuffer was constructed! The problem turned out to\n    // be BoringSSL rejecting null pointers even if the size was 0.\n\n    const empty = new ArrayBuffer();\n\n    const DIGESTS = {\n      MD5: 'd41d8cd98f00b204e9800998ecf8427e',\n      'SHA-1': 'da39a3ee5e6b4b0d3255bfef95601890afd80709',\n      'SHA-256':\n        'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',\n      'SHA-512':\n        'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e',\n    };\n\n    for (const name in DIGESTS) {\n      const result = arrayBuffer2hex(await crypto.subtle.digest(name, empty));\n      if (result != DIGESTS[name]) {\n        throw new Error(\n          'for ' + name + ', expected ' + DIGESTS[name] + ' got ' + result\n        );\n      }\n    }\n\n    const ENCRYPTS = {\n      'AES-CBC': 'dd3eedef984211b98384dc5677bc728e',\n      'AES-GCM': 'fedbd1a722cb7c1a52f529e0469ee449',\n    };\n\n    for (const name in ENCRYPTS) {\n      const key = await crypto.subtle.importKey(\n        'raw',\n        new Uint8Array([\n          0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0, 1, 2,\n          3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,\n        ]),\n        name,\n        true,\n        ['encrypt']\n      );\n      const result = arrayBuffer2hex(\n        await crypto.subtle.encrypt(\n          { name, iv: new Uint8Array(16) },\n          key,\n          empty\n        )\n      );\n      if (result != ENCRYPTS[name]) {\n        throw new Error(\n          'for ' + name + ', expected ' + ENCRYPTS[name] + ' got ' + result\n        );\n      }\n    }\n  },\n};\n\nexport const deriveBitsNullLength = {\n  async test() {\n    // Tests that deriveBits can take a null or undefined length\n    // argument and still return the correct number of bits if\n    // the algorithm supports it. This is a recent spec change.\n\n    const pair = await crypto.subtle.generateKey(\n      {\n        name: 'ECDH',\n        namedCurve: 'P-384',\n      },\n      false,\n      ['deriveBits']\n    );\n\n    {\n      const bits = await crypto.subtle.deriveBits(\n        {\n          name: 'ECDH',\n          namedCurve: 'P-384',\n          public: pair.publicKey,\n        },\n        pair.privateKey,\n        undefined\n      );\n\n      strictEqual(bits.byteLength, 48);\n    }\n\n    {\n      const bits = await crypto.subtle.deriveBits(\n        {\n          name: 'ECDH',\n          namedCurve: 'P-384',\n          public: pair.publicKey,\n        },\n        pair.privateKey,\n        null\n      );\n\n      strictEqual(bits.byteLength, 48);\n    }\n\n    {\n      const bits = await crypto.subtle.deriveBits(\n        {\n          name: 'ECDH',\n          namedCurve: 'P-384',\n          public: pair.publicKey,\n        },\n        pair.privateKey\n      );\n\n      strictEqual(bits.byteLength, 48);\n    }\n  },\n};\n\nexport const aesCounterOverflowTest = {\n  async test() {\n    // Regression test: Check that the input counter is not modified when it overflows in the\n    // internal computation.\n    const key = await crypto.subtle.generateKey(\n      {\n        name: 'AES-CTR',\n        length: 128,\n      },\n      false,\n      ['encrypt']\n    );\n\n    // Maximum counter value, will overflow and require processing in two parts if there is more\n    // than one input data block.\n    const counter = new Uint8Array([\n      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,\n      255,\n    ]);\n    const counter2 = counter.slice();\n\n    await crypto.subtle.encrypt(\n      {\n        name: 'AES-CTR',\n        length: 128,\n        counter,\n      },\n      key,\n      new TextEncoder().encode('A'.repeat(2 * 16))\n    );\n    ok(crypto.subtle.timingSafeEqual(counter, counter2));\n  },\n};\n\n// Test that RSA JWK import with partially invalid fields throws a proper error\n// without leaking memory. The valid n/e/d fields cause BIGNUM allocations; the\n// invalid p field (bad base64) triggers a throw. Under ASAN this verifies the\n// BIGNUMs allocated for n/e/d are properly freed on the error path.\nexport const rsaJwkPartialImportFailure = {\n  async test() {\n    // A minimal RSA-2048 JWK with valid n, e, d but invalid CRT parameters.\n    // The n/e/d values are from a real key (public, not sensitive).\n    const jwk = {\n      kty: 'RSA',\n      // Valid base64url-encoded values for n and e (from an RSA-2048 test key)\n      n:\n        'sXchDaQebHnPiGvhGPEUBYNmRREkfWAz4CZV0FxTwtQq6R51mJk8qnnU_6DE_XJr' +\n        'T2JVNPB-bIXGFNnMLPOsTf5Q4r9Ks3h3S3tPzFqSd9Cjv0eRe-ZhWBYFkl-bLE1h' +\n        'ZGnmtQ--KfAiMvAtYNfRJwKL9cSKpGQTmqY6_0IbUqbZ0dXf_5D4rKCiZaQj-lTbm' +\n        'Eifn5JeRKnA2VY4dQvVQKhoQp_dEFwjOLGPOJ3yJhAFRrtFI3tzH7jSLNz2FA9gHk' +\n        'LaPrGxWF-bSNqlegYCr8CATCNfCAt9lDbCCHJiB5TQ5B-R40gM-y_M44zzX9nbZuA' +\n        'rSkBjQ',\n      e: 'AQAB',\n      d:\n        'VFCWOqXr8nvZNyaaJLXEnFBR3W45lj0nSjpUGSH-wOjK4p5_FDRlaL-eRa-VQvwjJ' +\n        '38BRJk9_0dKJPCMcuFVlj-B0FNpZ_gkBGC-jlLfCq3SBjRFBasVUR5vh4GGe_pFD3p' +\n        '0RWjwwl_6yPb_cCeI4XP4kK4JEWndHjvNmBcZI6PU0Lc_8-Fb_Z0-BTN3BA0DBkFS' +\n        'GCQN7G4dCdNQ3Onn3y2JBXB-pYlFkiHyR0j0o_GFoH_GE-WxQb7q0PjkNV-sMFQ8' +\n        '0ql44vEPg0Z8bZ0d1g_j8_Z3PuKCPJxJ6T3IGHPV1D-kBJyBvjJ-rlKr4XQ6XqvAp' +\n        'XWPQ',\n      // Invalid base64 in CRT parameters — should cause import to throw\n      p: '!!!not-valid-base64!!!',\n      q: '!!!not-valid-base64!!!',\n      dp: '!!!not-valid-base64!!!',\n      dq: '!!!not-valid-base64!!!',\n      qi: '!!!not-valid-base64!!!',\n    };\n\n    let threw = false;\n    try {\n      await crypto.subtle.importKey(\n        'jwk',\n        jwk,\n        { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },\n        true,\n        ['sign']\n      );\n    } catch (e) {\n      threw = true;\n      // Should get an error about invalid base64, not crash or silently succeed\n      ok(e instanceof Error, 'Expected an Error');\n    }\n    ok(threw, 'Import should have thrown for invalid CRT parameters');\n\n    // Also test with valid n/e but invalid d (earlier failure point)\n    const jwk2 = {\n      kty: 'RSA',\n      n: jwk.n,\n      e: jwk.e,\n      d: '!!!not-valid-base64!!!',\n      p: '!!!not-valid-base64!!!',\n      q: '!!!not-valid-base64!!!',\n      dp: '!!!not-valid-base64!!!',\n      dq: '!!!not-valid-base64!!!',\n      qi: '!!!not-valid-base64!!!',\n    };\n\n    threw = false;\n    try {\n      await crypto.subtle.importKey(\n        'jwk',\n        jwk2,\n        { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },\n        true,\n        ['sign']\n      );\n    } catch (e) {\n      threw = true;\n      ok(e instanceof Error, 'Expected an Error');\n    }\n    ok(threw, 'Import should have thrown for invalid private exponent');\n  },\n};\n\n// Test that operations on detached ArrayBuffers return empty results instead of\n// crashing. This exercises the WasDetached() checks in JsArrayBuffer, JsUint8Array,\n// JsArrayBufferView, and JsBufferSource.\nexport const detachedBufferHandling = {\n  async test() {\n    // Create a buffer, detach it by transferring, then use it with crypto APIs.\n    const buf = new ArrayBuffer(16);\n    const view = new Uint8Array(buf);\n\n    // Detach by transferring to a new ArrayBuffer via structuredClone\n    structuredClone(buf, { transfer: [buf] });\n\n    // buf is now detached — byteLength should be 0\n    strictEqual(buf.byteLength, 0);\n\n    // getRandomValues should handle the detached view gracefully\n    let threw = false;\n    try {\n      crypto.getRandomValues(view);\n    } catch (e) {\n      threw = true;\n    }\n    // The detached buffer has 0 length, which should be accepted (0 <= 65536)\n    // but the view reports 0 bytes so getRandomValues is effectively a no-op.\n    // Either succeeding with 0 bytes or throwing is acceptable behavior.\n\n    // timingSafeEqual with detached buffers\n    const detachedBuf2 = new ArrayBuffer(0);\n    ok(\n      crypto.subtle.timingSafeEqual(detachedBuf2, new ArrayBuffer(0)),\n      'Empty buffers should be timing-safe equal'\n    );\n\n    // Verify that encrypt with a detached IV throws rather than crashing\n    const key = await crypto.subtle.generateKey(\n      { name: 'AES-GCM', length: 128 },\n      false,\n      ['encrypt']\n    );\n    const detachedIv = new ArrayBuffer(12);\n    structuredClone(detachedIv, { transfer: [detachedIv] });\n    threw = false;\n    try {\n      await crypto.subtle.encrypt(\n        { name: 'AES-GCM', iv: detachedIv },\n        key,\n        new ArrayBuffer(0)\n      );\n    } catch (e) {\n      threw = true;\n    }\n    ok(threw, 'Encrypt with detached IV should throw');\n  },\n};\n\n// Test that EC JWK import with mismatched public/private key components is rejected.\n// EC_KEY_check_key validates that the private key d corresponds to the public key (x, y).\nexport const ecJwkKeyConsistencyCheck = {\n  async test() {\n    // Generate a valid P-256 key pair to get real x, y values\n    const keyPair = await crypto.subtle.generateKey(\n      { name: 'ECDSA', namedCurve: 'P-256' },\n      true,\n      ['sign', 'verify']\n    );\n    const validJwk = await crypto.subtle.exportKey('jwk', keyPair.privateKey);\n\n    // Corrupt the private key d while keeping x, y valid.\n    // This creates an inconsistency: d does not correspond to (x, y).\n    const corruptedJwk = {\n      ...validJwk,\n      d: validJwk.d.split('').reverse().join(''),\n    };\n\n    let threw = false;\n    try {\n      await crypto.subtle.importKey(\n        'jwk',\n        corruptedJwk,\n        { name: 'ECDSA', namedCurve: 'P-256' },\n        true,\n        ['sign']\n      );\n    } catch (e) {\n      threw = true;\n      ok(e instanceof Error, 'Expected an Error');\n    }\n    ok(threw, 'Import should have thrown for inconsistent EC JWK private key');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/crypto-extras-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"crypto-extras-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"crypto-extras-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/crypto-impl-asymmetric-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport * as assert from 'node:assert';\n\nexport const rsa_reject_infinite_loop_test = {\n  async test(ctrl, env, ctx) {\n    // Check that parameters that may cause an infinite loop in RSA key generation are rejected.\n    await assert.rejects(\n      crypto.subtle.generateKey(\n        {\n          name: 'RSASSA-PKCS1-v1_5',\n          hash: 'SHA-256',\n          modulusLength: 1024,\n          publicExponent: new Uint8Array([1]),\n        },\n        false,\n        ['sign', 'verify']\n      ),\n      { message: 'The \"publicExponent\" must be either 3 or 65537, but got 1.' }\n    );\n\n    await assert.rejects(\n      crypto.subtle.generateKey(\n        {\n          name: 'RSA-PSS',\n          hash: 'SHA-256',\n          modulusLength: 1024,\n          publicExponent: new Uint8Array([1]),\n        },\n        false,\n        ['sign', 'verify']\n      ),\n      { message: 'The \"publicExponent\" must be either 3 or 65537, but got 1.' }\n    );\n  },\n};\n\nexport const eddsa_test = {\n  async test(ctrl, env, ctx) {\n    // Test EDDSA ED25519 generateKey\n    const edKey = await crypto.subtle.generateKey(\n      {\n        name: 'NODE-ED25519',\n        namedCurve: 'NODE-ED25519',\n      },\n      false,\n      ['sign', 'verify']\n    );\n    assert.ok(edKey.publicKey.algorithm.name == 'NODE-ED25519');\n  },\n};\n\nexport const publicExponent_type_test = {\n  async test(ctrl, env, ctx) {\n    const key = await crypto.subtle.generateKey(\n      {\n        name: 'RSA-PSS',\n        hash: 'SHA-256',\n        modulusLength: 1024,\n        publicExponent: new Uint8Array([0x01, 0x00, 0x01]),\n      },\n      false,\n      ['sign', 'verify']\n    );\n\n    // Check that a Uint8Array is used for publicExponent. Without the\n    // crypto_preserve_public_exponent feature flag, this would incorrectly return an ArrayBuffer.\n    assert.ok(\n      key.publicKey.algorithm.publicExponent[Symbol.toStringTag] == 'Uint8Array'\n    );\n  },\n};\n\nexport const ecdhJwkTest = {\n  async test() {\n    const publicJwk = {\n      kty: 'EC',\n      crv: 'P-256',\n      alg: 'THIS CAN BE ANYTHING',\n      x: 'Ze2loSV3wrroKUN_4zhwGhCqo3Xhu1td4QjeQ5wIVR0',\n      y: 'HlLtdXARY_f55A3fnzQbPcm6hgr34Mp8p-nuzQCE0Zw',\n    };\n\n    // The import should succeed with no errors.\n    // Refs: https://github.com/cloudflare/workerd/issues/1403\n    await crypto.subtle.importKey(\n      'jwk',\n      publicJwk,\n      { name: 'ECDH', namedCurve: 'P-256' },\n      true,\n      []\n    );\n  },\n};\n\nexport const rsaAlgTest = {\n  async test() {\n    const v3 = { kty: 'RSA' };\n    Object.defineProperty(v3, 'alg', {\n      writable: true,\n      configurable: true,\n      value: 1024n,\n    });\n    const v7 = { name: 'RSA-OAEP', hash: 'SHA-1' };\n    const v8 = [];\n    await assert.rejects(crypto.subtle.importKey('jwk', v3, v7, 6, v8), {\n      message: /Unrecognized or unimplemented algorithm \"1024\"/,\n    });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/crypto-impl-asymmetric-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"crypto-impl-asymmetric-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"crypto-impl-asymmetric-test.js\")\n        ],\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\", \"crypto_preserve_public_exponent\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/crypto-streams-test.js",
    "content": "import { strictEqual, deepStrictEqual, rejects, throws } from 'node:assert';\nimport { Buffer } from 'node:buffer';\n\nexport const digeststream = {\n  async test() {\n    {\n      const check = new Uint8Array([\n        198, 247, 195, 114, 100, 29, 210, 94, 15, 221, 240, 33, 83, 117, 86, 31,\n      ]);\n\n      const stream = new crypto.DigestStream('md5');\n      const writer = stream.getWriter();\n      const enc = new TextEncoder();\n\n      writer.write(enc.encode('hello'));\n      writer.write(enc.encode('there'));\n      writer.close();\n\n      const digest = new Uint8Array(await stream.digest);\n\n      strictEqual(stream.bytesWritten, 10n);\n      deepStrictEqual(digest, check);\n    }\n\n    {\n      const stream = new crypto.DigestStream('md5');\n      const writer = stream.getWriter();\n      const enc = new TextEncoder();\n\n      writer.write(enc.encode('hello'));\n      writer.write(enc.encode('there'));\n      writer.abort(new Error('boom'));\n\n      await rejects(stream.digest);\n    }\n\n    {\n      // Creating for other known types works...\n      new crypto.DigestStream('SHA-256');\n      new crypto.DigestStream('SHA-384');\n      new crypto.DigestStream('SHA-512');\n      new crypto.DigestStream('crc32');\n\n      // But fails for unknown digest names...\n      throws(() => new crypto.DigestStream('foo'));\n    }\n\n    (async () => {\n      let digestPromise;\n      {\n        digestPromise = new crypto.DigestStream('md5').digest;\n      }\n      globalThis.gc();\n      await digestPromise;\n      throw new Error('The promise should not have resolved');\n    })();\n\n    {\n      const enc = new TextEncoder();\n      const check = new Uint8Array([\n        93, 65, 64, 42, 188, 75, 42, 118, 185, 113, 157, 145, 16, 23, 197, 146,\n      ]);\n      const digestStream = new crypto.DigestStream('md5');\n      const writer = digestStream.getWriter();\n      await writer.write(enc.encode('hello'));\n      await writer.close();\n      const digest = new Uint8Array(await digestStream.digest);\n      deepStrictEqual(digest, check);\n    }\n\n    {\n      const check = new Uint8Array([\n        93, 65, 64, 42, 188, 75, 42, 118, 185, 113, 157, 145, 16, 23, 197, 146,\n      ]);\n      const digestStream = new crypto.DigestStream('md5');\n      const writer = digestStream.getWriter();\n      await writer.write('hello');\n      await writer.close();\n      const digest = new Uint8Array(await digestStream.digest);\n      deepStrictEqual(digest, check);\n    }\n\n    {\n      const check = new Uint8Array([\n        70, 54, 153, 61, 62, 29, 164, 233, 214, 184, 248, 123, 121, 232, 247,\n        198, 208, 24, 88, 13, 82, 102, 25, 80, 234, 188, 56, 69, 197, 137, 122,\n        77,\n      ]);\n      const digestStream = new crypto.DigestStream('SHA-256');\n      const writer = digestStream.getWriter();\n      await writer.write(new Uint32Array([1, 2, 3]));\n      await writer.close();\n      const digest = new Uint8Array(await digestStream.digest);\n      deepStrictEqual(digest, check);\n    }\n\n    {\n      const check = new Uint8Array([\n        70, 54, 153, 61, 62, 29, 164, 233, 214, 184, 248, 123, 121, 232, 247,\n        198, 208, 24, 88, 13, 82, 102, 25, 80, 234, 188, 56, 69, 197, 137, 122,\n        77,\n      ]);\n      const digestStream = new crypto.DigestStream('SHA-256');\n      const writer = digestStream.getWriter();\n      // Ensures that byteOffset is correctly handled.\n      await writer.write(new Uint32Array([0, 1, 2, 3]).subarray(1));\n      await writer.close();\n      const digest = new Uint8Array(await digestStream.digest);\n      deepStrictEqual(digest, check);\n    }\n\n    {\n      const check = new Uint8Array([176, 224, 34, 147]);\n      const digestStream = new crypto.DigestStream('crc32');\n      const writer = digestStream.getWriter();\n      await writer.write(new Uint32Array([1, 2, 3]));\n      await writer.close();\n      const digest = new Uint8Array(await digestStream.digest);\n      deepStrictEqual(digest, check);\n    }\n\n    {\n      const digestStream = new crypto.DigestStream('md5');\n      const writer = digestStream.getWriter();\n\n      try {\n        await writer.write(123);\n        throw new Error('should have failed');\n      } catch (err) {\n        strictEqual(\n          err.message,\n          'DigestStream is a byte stream but received an object ' +\n            'of non-ArrayBuffer/ArrayBufferView/string type on its writable side.'\n        );\n      }\n    }\n\n    // AWS CRC tests, source:\n    // https://github.com/aws/aws-sdk-js-v3/blob/c3f3d0a1c652c88fef5859881c9a12cfc8df61c1/packages/middleware-flexible-checksums/src/middleware-flexible-checksums.integ.spec.ts#L21\n    const testCases = [\n      ['', 'crc32', 'AAAAAA=='],\n      ['abc', 'crc32', 'NSRBwg=='],\n      ['Hello world', 'crc32', 'i9aeUg=='],\n\n      ['', 'crc32c', 'AAAAAA=='],\n      ['abc', 'crc32c', 'Nks/tw=='],\n      ['Hello world', 'crc32c', 'crUfeA=='],\n\n      ['', 'crc64nvme', 'AAAAAAAAAAA='],\n      ['abc', 'crc64nvme', 'BeXKuz/B+us='],\n      ['Hello world', 'crc64nvme', 'OOJZ0D8xKts='],\n\n      ['', 'SHA-1', '2jmj7l5rSw0yVb/vlWAYkK/YBwk='],\n      ['abc', 'SHA-1', 'qZk+NkcGgWq6PiVxeFDCbJzQ2J0='],\n      ['Hello world', 'SHA-1', 'e1AsOh9IyGCa4hLN+2Od7jlnP14='],\n\n      ['', 'SHA-256', '47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='],\n      ['abc', 'SHA-256', 'ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0='],\n      [\n        'Hello world',\n        'SHA-256',\n        'ZOyIygCyaOW6GjVnihtTFtIS9PNmskdyMlNKiuyjfzw=',\n      ],\n    ];\n    {\n      for (const [body, algorithm, expected] of testCases) {\n        const digestStream = new crypto.DigestStream(algorithm);\n        const writer = digestStream.getWriter();\n        const enc = new TextEncoder();\n        writer.write(enc.encode(body));\n        writer.close();\n        const digest = await digestStream.digest;\n        deepStrictEqual(digest, Buffer.from(expected, 'base64').buffer);\n      }\n    }\n\n    // Creating and not using a digest stream doesn't crash\n    new crypto.DigestStream('SHA-1');\n  },\n};\n\nexport const digestStreamNoEnd = {\n  async test() {\n    const stream = new crypto.DigestStream('md5');\n    const writer = stream.getWriter();\n    const enc = new TextEncoder();\n\n    writer.write(enc.encode('hello'));\n    writer.write(enc.encode('there'));\n    // stream never ends, should not crash.\n  },\n};\n\nexport const digestStreamDisposable = {\n  async test() {\n    const enc = new TextEncoder();\n    const stream = new crypto.DigestStream('md5');\n    stream[Symbol.dispose]();\n\n    const writer = stream.getWriter();\n\n    try {\n      await writer.write(enc.encode('hello'));\n      throw new Error('should have failed');\n    } catch (err) {\n      strictEqual(err.message, 'The DigestStream was disposed.');\n    }\n\n    // Calling dispose again should have no impact\n    stream[Symbol.dispose]();\n  },\n};\n\nexport const digestStreamLargeChunks = {\n  async test() {\n    {\n      const check = new Uint8Array([0x13, 0xb3, 0xf0, 0x58]);\n      const digestStream = new crypto.DigestStream('crc32');\n      const writer = digestStream.getWriter();\n      await writer.write(Buffer.alloc(1024, 'a'));\n      await writer.write(Buffer.alloc(1024, 'b'));\n      await writer.write(Buffer.alloc(1024, 'c'));\n      await writer.write(Buffer.alloc(1024 * 1024, 'd'));\n      await writer.close();\n      const digest = new Uint8Array(await digestStream.digest);\n      deepStrictEqual(digest, check);\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/crypto-streams-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"crypto-streams-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"crypto-streams-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/ctx-props-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"ctx-props-test-disabled\",\n      worker = (\n        modules = [\n          (name = \"worker-props-disabled\", esModule =\n            `import assert from 'node:assert';\n            `export default {\n            ` async fetch(request, env, ctx) {\n            `   return new Response(JSON.stringify(ctx.props));\n            ` },\n            ` async test(request, env, ctx) {\n            `   const resp = await env.SERVICE.fetch(new Request(\"https://placeholder/hello\"));\n            `   const body = await resp.json();\n            `   assert.strictEqual(JSON.stringify(body), '{}');\n            ` },\n            `};\n          )\n        ],\n        bindings = [\n          ( name = \"SERVICE\", service = (name = \"ctx-props-test-disabled\", props = (json = \"false\"))),\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nonclass_entrypoint_reuses_ctx_across_invocations\"],\n      )\n    ),\n    ( name = \"ctx-props-test-enabled\",\n      worker = (\n        modules = [\n          (name = \"worker-props-enabled\", esModule =\n            `import assert from 'node:assert';\n            `export default {\n            ` async fetch(request, env, ctx) {\n            `   return new Response(JSON.stringify(ctx.props));\n            ` },\n            ` async test(request, env, ctx) {\n            `   const resp = await env.SERVICE.fetch(new Request(\"https://placeholder/hello\"));\n            `   const body = await resp.json();\n            `   assert.deepStrictEqual(body, {foo: 123});\n            ` },\n            `};\n          )\n        ],\n        bindings = [\n          ( name = \"SERVICE\", service = (name = \"ctx-props-test-enabled\", props = (json = \"{\\\"foo\\\" : 123}\"))),\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"unique_ctx_per_invocation\"],\n    ))\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/data-url-fetch-test.js",
    "content": "import { strictEqual } from 'node:assert';\n\nexport const dataUrl = {\n  async test() {\n    const resp = await fetch('data:text/plain,Hello%2C%20World!');\n    strictEqual(resp.status, 200);\n    strictEqual(resp.statusText, 'OK');\n    strictEqual(await resp.text(), 'Hello, World!');\n    strictEqual(resp.headers.get('content-type'), 'text/plain');\n  },\n};\n\nexport const base64DataUrl = {\n  async test() {\n    const resp = await fetch(\n      '  DATA:text/plain;a=\"b\";base64,\\t\\nSGVsbG8sIFdvcmxkIQ%3D%3D'\n    );\n    strictEqual(resp.status, 200);\n    strictEqual(resp.statusText, 'OK');\n    strictEqual(await resp.text(), 'Hello, World!');\n    strictEqual(resp.headers.get('content-type'), 'text/plain;a=b');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/data-url-fetch-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"data-url-fetch-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"data-url-fetch-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/decompression-stream-unhandled-rejection-test.js",
    "content": "// Regression test for https://github.com/cloudflare/workerd/issues/6061\n// DecompressionStream generates unhandled rejections internally when\n// streams are consumed via Array.fromAsync, even when the caller\n// properly handles errors.\n\nimport { strictEqual } from 'node:assert';\nimport { mock } from 'node:test';\nimport { setTimeout } from 'node:timers/promises';\n\nexport const decompessionStreamUnhandledRejection = {\n  async test() {\n    const rejectionFn = mock.fn();\n    const unhandledRejectionFn = mock.fn();\n    globalThis.addEventListener('rejectionhandled', rejectionFn);\n    globalThis.addEventListener('unhandledrejection', unhandledRejectionFn);\n\n    const valid = new Uint8Array([120, 156, 75, 4, 0, 0, 98, 0, 98]); // deflate('a')\n    const empty = new Uint8Array(1);\n    const invalid = new Uint8Array([...valid, ...empty]);\n    const double = new Uint8Array([...valid, ...valid]);\n    for (const chunks of [\n      [valid],\n      [invalid],\n      [valid, empty],\n      [valid, valid],\n      [double],\n    ]) {\n      try {\n        const stream = new Blob(chunks)\n          .stream()\n          .pipeThrough(new DecompressionStream('deflate'));\n        await Array.fromAsync(stream);\n      } catch (error) {\n        strictEqual(\n          error.message,\n          'Trailing bytes after end of compressed data'\n        );\n      }\n    }\n\n    await setTimeout(200);\n    globalThis.removeEventListener('rejectionhandled', rejectionFn);\n    globalThis.removeEventListener('unhandledrejection', unhandledRejectionFn);\n    strictEqual(\n      rejectionFn.mock.callCount(),\n      unhandledRejectionFn.mock.callCount()\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/decompression-stream-unhandled-rejection-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"decompression-stream-unhandled-rejection-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"decompression-stream-unhandled-rejection-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"strict_compression_checks\",\n          \"unhandled_rejection_after_microtask_checkpoint\",\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/delete-all-deletes-alarm-test.js",
    "content": "// Tests that the delete_all_deletes_alarm compat flag correctly controls whether\n// deleteAll() also deletes alarms. This file is used by two test services: one\n// with the flag enabled and one with it disabled. The EXPECT_ALARM_DELETED binding\n// tells this code which behavior to expect.\n\nimport * as assert from 'node:assert';\n\nexport class DurableObjectExample {\n  constructor(state) {\n    this.state = state;\n  }\n\n  async fetch(request) {\n    const url = new URL(request.url);\n    const expectDeleted = url.searchParams.get('expectDeleted') === 'true';\n\n    // Set an alarm for 10 minutes from now.\n    const alarmTime = Date.now() + 10 * 60 * 1000;\n    await this.state.storage.setAlarm(alarmTime);\n    assert.equal(await this.state.storage.getAlarm(), alarmTime);\n\n    // Also put some KV data so deleteAll has something to delete.\n    await this.state.storage.put('key', 'value');\n\n    // Call deleteAll().\n    await this.state.storage.deleteAll();\n\n    // KV data should always be deleted.\n    assert.equal(await this.state.storage.get('key'), undefined);\n\n    const alarmAfter = await this.state.storage.getAlarm();\n\n    if (expectDeleted) {\n      // With the compat flag enabled, the alarm should be deleted.\n      assert.equal(\n        alarmAfter,\n        null,\n        `Expected alarm to be null after deleteAll(), got ${alarmAfter}`\n      );\n    } else {\n      // Without the compat flag, the alarm should be preserved.\n      assert.equal(\n        alarmAfter,\n        alarmTime,\n        `Expected alarm to be preserved after deleteAll(), got ${alarmAfter}`\n      );\n      // Clean up the alarm so it doesn't fire during the test.\n      await this.state.storage.deleteAlarm();\n    }\n\n    return new Response('OK');\n  }\n\n  async alarm() {\n    // Should not be invoked during the test.\n    throw new Error('alarm handler unexpectedly invoked');\n  }\n}\n\nexport const test = {\n  async test(ctrl, env, ctx) {\n    let id = env.ns.idFromName('A');\n    let obj = env.ns.get(id);\n    let res = await obj.fetch(\n      `http://foo/test?expectDeleted=${env.EXPECT_ALARM_DELETED}`\n    );\n    let text = await res.text();\n    assert.equal(text, 'OK');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/delete-all-deletes-alarm-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"delete-all-deletes-alarm-enabled\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"delete-all-deletes-alarm-test.js\" )\n        ],\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\", \"delete_all_deletes_alarm\"],\n        durableObjectNamespaces = [\n          (className = \"DurableObjectExample\", uniqueKey = \"210bd0cbd803ef7883a1ee9d86cce06e\"),\n        ],\n        durableObjectStorage = (inMemory = void),\n        bindings = [\n          (name = \"ns\", durableObjectNamespace = \"DurableObjectExample\"),\n          (name = \"EXPECT_ALARM_DELETED\", text = \"true\"),\n        ],\n      )\n    ),\n    ( name = \"delete-all-preserves-alarm-disabled\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"delete-all-deletes-alarm-test.js\" )\n        ],\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\", \"delete_all_preserves_alarm\"],\n        durableObjectNamespaces = [\n          (className = \"DurableObjectExample\", uniqueKey = \"310bd0cbd803ef7883a1ee9d86cce06f\"),\n        ],\n        durableObjectStorage = (inMemory = void),\n        bindings = [\n          (name = \"ns\", durableObjectNamespace = \"DurableObjectExample\"),\n          (name = \"EXPECT_ALARM_DELETED\", text = \"false\"),\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/disable-importable-env-test.js",
    "content": "import { env, withEnv } from 'cloudflare:workers';\n\n// This test runs with the disallow-importable-env flag set, meaning that\n// the env imported from cloudflare:workers exists but is not-populated\n// with the primary environment. Using withEnv, however, would still work.\n\nimport { strictEqual, notDeepStrictEqual } from 'node:assert';\n\nstrictEqual(env.FOO, undefined);\n\nexport const test = {\n  async test(_, argEnv) {\n    strictEqual(env.FOO, undefined);\n    notDeepStrictEqual(argEnv, env);\n\n    // withEnv still works as expected tho\n    const { env: otherEnv2 } = await withEnv({ BAZ: 1 }, async () => {\n      await scheduler.wait(0);\n      return import('child');\n    });\n    strictEqual(otherEnv2.FOO, undefined);\n    strictEqual(otherEnv2.BAZ, 1);\n    strictEqual(argEnv.BAZ, undefined);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/disable-importable-env-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"disable-importable-env-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"disable-importable-env-test.js\"),\n          (name = \"child\", esModule = \"import {env as live} from 'cloudflare:workers'; export const env = {...live};\"),\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat_v2\",\n          \"disallow_importable_env\",\n        ],\n        bindings = [\n          (name = \"FOO\", text = \"BAR\"),\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/disable-importable-exports-test.js",
    "content": "import { exports, withExports } from 'cloudflare:workers';\nimport { strictEqual } from 'node:assert';\n\n// This test runs with the disallow-importable-env flag set, which should also\n// disable importable exports. The exports imported from cloudflare:workers\n// should not be populated. Using withExports, however, would still work.\n\nexport class MyService {\n  greet(name) {\n    return `Hello, ${name}!`;\n  }\n}\n\nexport const test = {\n  async test() {\n    // exports should be empty/undefined when disallow_importable_env is set\n    strictEqual(exports.MyService, undefined);\n    strictEqual(exports.test, undefined);\n\n    // withExports still works as expected with an object\n    const result = await withExports({ customExport: 'test' }, async () => {\n      await scheduler.wait(0);\n      const { exports: otherExports } = await import('child');\n      strictEqual(otherExports.customExport, 'test');\n      strictEqual(otherExports.MyService, undefined);\n      return otherExports.customExport;\n    });\n    strictEqual(result, 'test');\n\n    // withExports with non-object values should fall back to disabled behavior\n    const nonObjectValues = [null, 'string', 123, undefined];\n    for (const value of nonObjectValues) {\n      await withExports(value, async () => {\n        await scheduler.wait(0);\n        // When withExports gets a non-object, getCurrentExports falls through to the\n        // base behavior (which is disabled by disallow_importable_env), so exports are empty\n        strictEqual(exports.MyService, undefined);\n        strictEqual(exports.customExport, undefined);\n      });\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/disable-importable-exports-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"disable-importable-exports-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"disable-importable-exports-test.js\"),\n          (name = \"child\", esModule = \"import {exports as live} from 'cloudflare:workers'; export const exports = {...live};\"),\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"disallow_importable_env\",\n        ],\n        bindings = [],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/encoding-streams-test.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { strictEqual } from 'node:assert';\n\n// Test TextEncoderStream/TextDecoderStream pipeline\nexport const textEncoderDecoderPipeline = {\n  async test() {\n    // encIn accepts a string, outputs a Uint8Array.\n    // dec accepts a Uint8Array, outputs a string.\n    // encOut accepts a string, outputs a Uint8Array.\n    const encIn = new TextEncoderStream();\n    const dec = new TextDecoderStream();\n    const encOut = new TextEncoderStream();\n\n    const mid = encIn.readable.pipeThrough(dec);\n    const end = mid.pipeThrough(encOut);\n\n    const writer = encIn.writable.getWriter();\n    await writer.write('hello');\n    await writer.close();\n\n    const reader = end.getReader();\n    const result = await reader.read();\n\n    const decoder = new TextDecoder();\n    strictEqual(decoder.decode(result.value), 'hello');\n  },\n};\n\n// Test TextEncoderStream accepts anything that can be coerced to a string\nexport const textEncoderStreamTypes = {\n  async test() {\n    const decoder = new TextDecoder();\n    const enc = new TextEncoderStream();\n    const writer = enc.writable.getWriter();\n    const reader = enc.readable.getReader();\n\n    // Write all values then read them - need to interleave writes and reads properly\n    // by using Promise.all to avoid deadlocks from backpressure\n\n    // Test undefined\n    {\n      const [, result] = await Promise.all([\n        writer.write(undefined),\n        reader.read(),\n      ]);\n      strictEqual(decoder.decode(result.value), 'undefined');\n    }\n\n    // Test numbers\n    {\n      const [, result] = await Promise.all([writer.write(1), reader.read()]);\n      strictEqual(decoder.decode(result.value), '1');\n    }\n\n    // Test objects\n    {\n      const [, result] = await Promise.all([writer.write({}), reader.read()]);\n      strictEqual(decoder.decode(result.value), '[object Object]');\n    }\n\n    await writer.close();\n  },\n};\n\n// Test TextDecoderStream with Big5 encoding\nexport const textDecoderStreamBig5 = {\n  async test() {\n    const dec = new TextDecoderStream('big5');\n    const input = [0xa4, 0xa4, 0xb0, 0xea, 0xa4, 0x48];\n    const check = new Uint8Array([\n      0xe4, 0xb8, 0xad, 0xe5, 0x9c, 0x8b, 0xe4, 0xba, 0xba,\n    ]);\n\n    const writer = dec.writable.getWriter();\n\n    for (const byte of input) {\n      writer.write(new Uint8Array([byte]));\n    }\n    writer.close();\n\n    let result = '';\n    for await (const chunk of dec.readable) {\n      result += chunk;\n    }\n\n    strictEqual(result, '中國人');\n\n    const decoder = new TextDecoder('utf8');\n    strictEqual(result, decoder.decode(check));\n  },\n};\n\n// Test TextEncoderStream encoding property\nexport const textEncoderStreamEncoding = {\n  test() {\n    const enc = new TextEncoderStream();\n    strictEqual(enc.encoding, 'utf-8');\n  },\n};\n\n// Test TextDecoderStream with various encodings\nexport const textDecoderStreamEncodings = {\n  test() {\n    // Test UTF-16\n    const stream = new TextDecoderStream('utf-16', {\n      fatal: true,\n      ignoreBOM: true,\n    });\n    strictEqual(stream.encoding, 'utf-16le');\n    strictEqual(stream.fatal, true);\n    strictEqual(stream.ignoreBOM, true);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/encoding-streams-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"encoding-streams-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"encoding-streams-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"streams_enable_constructors\", \"transformstream_enable_standard_constructor\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/encoding-test.js",
    "content": "import { deepStrictEqual, strictEqual, throws, ok } from 'node:assert';\n\n// Test for the Encoding standard Web API implementation.\n// The implementation for these are in api/encoding.{h|c++}\n\nfunction decodeStreaming(decoder, input) {\n  // Test truncation behavior while streaming by feeding the decoder a single byte at a time.\n  // Note we don't try-catch here because we don't expect this ever to fail, because we're\n  // streaming text.\n  let x = '';\n  for (let i = 0; i < input.length; ++i) {\n    x += decoder.decode(input.slice(i, i + 1), { stream: true });\n  }\n  x += decoder.decode();\n  return x;\n}\n\n// From https://developer.mozilla.org/en-US/docs/Web/API/Encoding_API/Encodings\nconst windows1252Labels = [\n  'ansi_x3.4-1968',\n  'ascii',\n  'cp1252',\n  'cp819',\n  'csisolatin1',\n  'ibm819',\n  'iso-8859-1',\n  'iso-ir-100',\n  'iso8859-1',\n  'iso88591',\n  'iso_8859-1',\n  'iso_8859-1:1987',\n  'l1',\n  'latin1',\n  'us-ascii',\n  'windows-1252',\n  'x-cp1252',\n];\n\nconst utf8Labels = ['unicode-1-1-utf-8', 'utf-8', 'utf8'];\n\nexport const decodeStreamingTest = {\n  test() {\n    let results = [];\n\n    for (const label of windows1252Labels) {\n      ok(\n        new TextDecoder(`${label}`).encoding === 'windows-1252',\n        `TextDecoder constructed with '${label}' label to have 'windows-1252' encoding.`\n      );\n    }\n    for (const label of utf8Labels) {\n      ok(\n        new TextDecoder(`${label}`).encoding === 'utf-8',\n        `TextDecoder constructed with '${label}' label to have 'utf-8' encoding.`\n      );\n    }\n\n    const decoder = new TextDecoder();\n    const fatalDecoder = new TextDecoder('utf-8', { fatal: true });\n    const fatalIgnoreBomDecoder = new TextDecoder('utf-8', {\n      fatal: true,\n      ignoreBOM: true,\n    });\n\n    ok(decoder.encoding === 'utf-8', \"default encoding property to be 'utf-8'\");\n    ok(decoder.fatal === false, 'default fatal property to be false');\n    ok(decoder.ignoreBOM === false, 'default ignoreBOM property to be false');\n\n    const fooCat = new Uint8Array([102, 111, 111, 32, 240, 159, 152, 186]);\n\n    ok(decoder.decode().length === 0, 'decoded undefined array length to be 0');\n    ok(\n      decoder.decode(fooCat) === 'foo 😺',\n      'foo-cat from Uint8Buffer to be foo 😺'\n    );\n    ok(\n      decoder.decode(fooCat.buffer) === 'foo 😺',\n      'foo-cat from ArrayBuffer to be foo 😺'\n    );\n\n    const twoByteCodePoint = new Uint8Array([0xc2, 0xa2]); // cent sign\n    const threeByteCodePoint = new Uint8Array([0xe2, 0x82, 0xac]); // euro sign\n    const fourByteCodePoint = new Uint8Array([240, 159, 152, 186]); // cat emoji\n\n    [twoByteCodePoint, threeByteCodePoint, fourByteCodePoint].forEach(\n      (input) => {\n        // For each input sequence of code units, try decoding each subsequence of code units except the\n        // full code point itself.\n        for (let i = 1; i < input.length; ++i) {\n          const head = input.slice(0, input.length - i);\n          const tail = input.slice(input.length - i);\n\n          ok(\n            decoder.decode(head) === '�',\n            'code point fragment (head) to be replaced with replacement character'\n          );\n          ok(\n            decoder.decode(tail) === '�'.repeat(tail.length),\n            'code point fragment (tail) to be replaced with replacement character'\n          );\n\n          const errMsg = 'Failed to decode input.';\n\n          // Exception to be thrown decoding code point fragment (tail) in fatal mode\n          throws(() => fatalDecoder.decode(head));\n          throws(() => fatalDecoder.decode(tail));\n        }\n      }\n    );\n\n    // Test ASCII\n    const asciiDecoder = new TextDecoder('ascii');\n    ok(\n      asciiDecoder.decode().length === 0,\n      'decoded undefined array length to be 0'\n    );\n    ok(\n      asciiDecoder.decode(new Uint8Array([162, 174, 255])) === '¢®ÿ',\n      'decoded extended ascii correctly'\n    );\n\n    // Test streaming\n\n    ok(\n      decodeStreaming(fatalDecoder, twoByteCodePoint) === '¢',\n      '2-byte code point (cent sign) to be decoded correctly'\n    );\n    ok(\n      decodeStreaming(fatalDecoder, threeByteCodePoint) === '€',\n      '3-byte code point (euro sign) to be decoded correctly'\n    );\n    ok(\n      decodeStreaming(fatalDecoder, fourByteCodePoint) === '😺',\n      '4-byte code point (cat emoji) to be decoded correctly'\n    );\n\n    const bom = new Uint8Array([0xef, 0xbb, 0xbf]);\n    const bomBom = new Uint8Array([0xef, 0xbb, 0xbf, 0xef, 0xbb, 0xbf]);\n\n    ok(\n      decodeStreaming(fatalDecoder, bom) === '',\n      'BOM to be stripped by TextDecoder without ignoreBOM set'\n    );\n    ok(\n      decodeStreaming(fatalDecoder, bomBom) === '\\ufeff',\n      'first BOM to be stripped by TextDecoder without ignoreBOM set'\n    );\n\n    ok(\n      decodeStreaming(fatalIgnoreBomDecoder, bom) === '\\ufeff',\n      'BOM not to be stripped by TextDecoder with ignoreBOM set'\n    );\n    ok(\n      decodeStreaming(fatalIgnoreBomDecoder, bomBom) === '\\ufeff\\ufeff',\n      'first BOM not to be stripped by TextDecoder with ignoreBOM set'\n    );\n\n    const shiftJisDecoder = new TextDecoder('shift_jis');\n    let shiftJisResult = '';\n    shiftJisResult += shiftJisDecoder.decode(new Uint8Array([0x82]), {\n      stream: true,\n    });\n    shiftJisResult += shiftJisDecoder.decode(new Uint8Array(), {\n      stream: true,\n    });\n    shiftJisResult += shiftJisDecoder.decode(new Uint8Array([0xa0]), {\n      stream: true,\n    });\n    shiftJisResult += shiftJisDecoder.decode();\n    ok(\n      shiftJisResult === 'あ',\n      'streaming Shift_JIS should tolerate empty chunks without flushing state'\n    );\n  },\n};\n\nexport const textEncoderTest = {\n  test() {\n    const encoder = new TextEncoder();\n    strictEqual(encoder.encoding, 'utf-8');\n    strictEqual(encoder.encode().length, 0);\n    deepStrictEqual(\n      encoder.encode('foo 😺'),\n      new Uint8Array([102, 111, 111, 32, 240, 159, 152, 186])\n    );\n  },\n};\n\nexport const encodeWptTest = {\n  test() {\n    w3cTestEncode();\n    w3cTestEncodeInto();\n\n    function w3cTestEncode() {\n      const bad = [\n        {\n          input: '\\uD800',\n          expected: '\\uFFFD',\n          name: 'lone surrogate lead',\n        },\n        {\n          input: '\\uDC00',\n          expected: '\\uFFFD',\n          name: 'lone surrogate trail',\n        },\n        {\n          input: '\\uD800\\u0000',\n          expected: '\\uFFFD\\u0000',\n          name: 'unmatched surrogate lead',\n        },\n        {\n          input: '\\uDC00\\u0000',\n          expected: '\\uFFFD\\u0000',\n          name: 'unmatched surrogate trail',\n        },\n        {\n          input: '\\uDC00\\uD800',\n          expected: '\\uFFFD\\uFFFD',\n          name: 'swapped surrogate pair',\n        },\n        {\n          input: '\\uD834\\uDD1E',\n          expected: '\\uD834\\uDD1E',\n          name: 'properly encoded MUSICAL SYMBOL G CLEF (U+1D11E)',\n        },\n      ];\n\n      bad.forEach(function (t) {\n        const encoded = new TextEncoder().encode(t.input);\n        const decoded = new TextDecoder().decode(encoded);\n        strictEqual(decoded, t.expected);\n      });\n\n      strictEqual(new TextEncoder().encode().length, 0);\n    }\n\n    function w3cTestEncodeInto() {\n      [\n        {\n          input: 'Hi',\n          read: 0,\n          destinationLength: 0,\n          written: [],\n        },\n        {\n          input: 'A',\n          read: 1,\n          destinationLength: 10,\n          written: [0x41],\n        },\n        {\n          input: '\\u{1D306}', // \"\\uD834\\uDF06\"\n          read: 2,\n          destinationLength: 4,\n          written: [0xf0, 0x9d, 0x8c, 0x86],\n        },\n        {\n          input: '\\u{1D306}A',\n          read: 0,\n          destinationLength: 3,\n          written: [],\n        },\n        {\n          input: '\\uD834A\\uDF06A¥Hi',\n          read: 5,\n          destinationLength: 10,\n          written: [0xef, 0xbf, 0xbd, 0x41, 0xef, 0xbf, 0xbd, 0x41, 0xc2, 0xa5],\n        },\n        {\n          input: 'A\\uDF06',\n          read: 2,\n          destinationLength: 4,\n          written: [0x41, 0xef, 0xbf, 0xbd],\n        },\n        {\n          input: '¥¥',\n          read: 2,\n          destinationLength: 4,\n          written: [0xc2, 0xa5, 0xc2, 0xa5],\n        },\n      ].forEach((testData) => {\n        [\n          {\n            bufferIncrease: 0,\n            destinationOffset: 0,\n            filler: 0,\n          },\n          {\n            bufferIncrease: 10,\n            destinationOffset: 4,\n            filler: 0,\n          },\n          {\n            bufferIncrease: 0,\n            destinationOffset: 0,\n            filler: 0x80,\n          },\n          {\n            bufferIncrease: 10,\n            destinationOffset: 4,\n            filler: 0x80,\n          },\n          {\n            bufferIncrease: 0,\n            destinationOffset: 0,\n            filler: 'random',\n          },\n          {\n            bufferIncrease: 10,\n            destinationOffset: 4,\n            filler: 'random',\n          },\n        ].forEach((destinationData) => {\n          const bufferLength =\n            testData.destinationLength + destinationData.bufferIncrease;\n          const destinationOffset = destinationData.destinationOffset;\n          const destinationLength = testData.destinationLength;\n          const destinationFiller = destinationData.filler;\n          const encoder = new TextEncoder();\n          const buffer = new ArrayBuffer(bufferLength);\n          const view = new Uint8Array(\n            buffer,\n            destinationOffset,\n            destinationLength\n          );\n          const fullView = new Uint8Array(buffer);\n          const control = Array.from({ length: bufferLength }, () => 0);\n          let byte = destinationFiller;\n          for (let i = 0; i < bufferLength; i++) {\n            if (destinationFiller === 'random') {\n              byte = Math.floor(Math.random() * 256);\n            }\n            control[i] = byte;\n            fullView[i] = byte;\n          }\n\n          // It's happening\n          const result = encoder.encodeInto(testData.input, view);\n\n          // Basics\n          strictEqual(view.byteLength, destinationLength);\n          strictEqual(view.length, destinationLength);\n\n          // Remainder\n          strictEqual(result.read, testData.read);\n          strictEqual(result.written, testData.written.length);\n          for (let i = 0; i < bufferLength; i++) {\n            if (\n              i < destinationOffset ||\n              i >= destinationOffset + testData.written.length\n            ) {\n              strictEqual(fullView[i], control[i]);\n            } else {\n              strictEqual(fullView[i], testData.written[i - destinationOffset]);\n            }\n          }\n        });\n      });\n\n      [\n        DataView,\n        Int8Array,\n        Int16Array,\n        Int32Array,\n        Uint16Array,\n        Uint32Array,\n        Uint8ClampedArray,\n        Float16Array,\n        Float32Array,\n        Float64Array,\n      ].forEach((view) => {\n        const enc = new TextEncoder();\n        throws(() => enc.encodeInto('', new view(new ArrayBuffer(0))));\n      });\n\n      throws(() => enc.encodeInto('', new ArrayBuffer(0)));\n    }\n  },\n};\n\nexport const big5 = {\n  test() {\n    // Input is the Big5 encoding for the word 中國人 (meaning Chinese Person)\n    // Check is the UTF-8 encoding for the same word.\n    const input = new Uint8Array([0xa4, 0xa4, 0xb0, 0xea, 0xa4, 0x48]);\n    const check = new Uint8Array([\n      0xe4, 0xb8, 0xad, 0xe5, 0x9c, 0x8b, 0xe4, 0xba, 0xba,\n    ]);\n    const enc = new TextEncoder();\n    const dec = new TextDecoder('big5', { ignoreBOM: true });\n    const result = enc.encode(dec.decode(input));\n    strictEqual(result.length, check.length);\n    for (let n = 0; n < result.length; n++) {\n      strictEqual(result[n], check[n]);\n    }\n  },\n};\n\nexport const utf16leFastTrack = {\n  test() {\n    // Input is the UTF-16le encoding for the word Hello. This should trigger the fast-path\n    // which should handle the encoding with no problems. Here we only test that the results\n    // are expected. We cannot verify here that the fast track is actually used.\n    const input = new Uint8Array([\n      0x68, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00,\n    ]);\n    const dec = new TextDecoder('utf-16le');\n    strictEqual(dec.decode(input), 'hello');\n  },\n};\n\nexport const allTheDecoders = {\n  test() {\n    [\n      ['unicode-1-1-utf-8', 'utf-8'],\n      ['unicode11utf8', 'utf-8'],\n      ['unicode20utf8', 'utf-8'],\n      ['utf-8', 'utf-8'],\n      ['utf8', 'utf-8'],\n      ['x-unicode20utf8', 'utf-8'],\n      ['866', 'ibm866'],\n      ['cp866', 'ibm866'],\n      ['csibm866', 'ibm866'],\n      ['ibm866', 'ibm866'],\n      ['csisolatin2', 'iso-8859-2'],\n      ['iso-8859-2', 'iso-8859-2'],\n      ['iso-ir-101', 'iso-8859-2'],\n      ['iso8859-2', 'iso-8859-2'],\n      ['iso88592', 'iso-8859-2'],\n      ['iso_8859-2', 'iso-8859-2'],\n      ['iso_8859-2:1987', 'iso-8859-2'],\n      ['l2', 'iso-8859-2'],\n      ['latin2', 'iso-8859-2'],\n      ['csisolatin3', 'iso-8859-3'],\n      ['iso-8859-3', 'iso-8859-3'],\n      ['iso-ir-109', 'iso-8859-3'],\n      ['iso8859-3', 'iso-8859-3'],\n      ['iso88593', 'iso-8859-3'],\n      ['iso_8859-3', 'iso-8859-3'],\n      ['iso_8859-3:1988', 'iso-8859-3'],\n      ['l3', 'iso-8859-3'],\n      ['latin3', 'iso-8859-3'],\n      ['csisolatin4', 'iso-8859-4'],\n      ['iso-8859-4', 'iso-8859-4'],\n      ['iso-ir-110', 'iso-8859-4'],\n      ['iso8859-4', 'iso-8859-4'],\n      ['iso88594', 'iso-8859-4'],\n      ['iso_8859-4', 'iso-8859-4'],\n      ['iso_8859-4:1988', 'iso-8859-4'],\n      ['l4', 'iso-8859-4'],\n      ['latin4', 'iso-8859-4'],\n      ['csisolatincyrillic', 'iso-8859-5'],\n      ['cyrillic', 'iso-8859-5'],\n      ['iso-8859-5', 'iso-8859-5'],\n      ['iso-ir-144', 'iso-8859-5'],\n      ['iso8859-5', 'iso-8859-5'],\n      ['iso88595', 'iso-8859-5'],\n      ['iso_8859-5', 'iso-8859-5'],\n      ['iso_8859-5:1988', 'iso-8859-5'],\n      ['arabic', 'iso-8859-6'],\n      ['asmo-708', 'iso-8859-6'],\n      ['csiso88596e', 'iso-8859-6'],\n      ['csiso88596i', 'iso-8859-6'],\n      ['csisolatinarabic', 'iso-8859-6'],\n      ['ecma-114', 'iso-8859-6'],\n      ['iso-8859-6', 'iso-8859-6'],\n      ['iso-8859-6-e', 'iso-8859-6'],\n      ['iso-8859-6-i', 'iso-8859-6'],\n      ['iso-ir-127', 'iso-8859-6'],\n      ['iso8859-6', 'iso-8859-6'],\n      ['iso88596', 'iso-8859-6'],\n      ['iso_8859-6', 'iso-8859-6'],\n      ['iso_8859-6:1987', 'iso-8859-6'],\n      ['csisolatingreek', 'iso-8859-7'],\n      ['ecma-118', 'iso-8859-7'],\n      ['elot_928', 'iso-8859-7'],\n      ['greek', 'iso-8859-7'],\n      ['greek8', 'iso-8859-7'],\n      ['iso-8859-7', 'iso-8859-7'],\n      ['iso-ir-126', 'iso-8859-7'],\n      ['iso8859-7', 'iso-8859-7'],\n      ['iso88597', 'iso-8859-7'],\n      ['iso_8859-7', 'iso-8859-7'],\n      ['iso_8859-7:1987', 'iso-8859-7'],\n      ['sun_eu_greek', 'iso-8859-7'],\n      ['csiso88598e', 'iso-8859-8'],\n      ['csisolatinhebrew', 'iso-8859-8'],\n      ['hebrew', 'iso-8859-8'],\n      ['iso-8859-8', 'iso-8859-8'],\n      ['iso-8859-8-e', 'iso-8859-8'],\n      ['iso-ir-138', 'iso-8859-8'],\n      ['iso8859-8', 'iso-8859-8'],\n      ['iso88598', 'iso-8859-8'],\n      ['iso_8859-8', 'iso-8859-8'],\n      ['iso_8859-8:1988', 'iso-8859-8'],\n      ['visual', 'iso-8859-8'],\n      ['csiso88598i', 'iso-8859-8-i'],\n      ['iso-8859-8-i', 'iso-8859-8-i'],\n      ['logical', 'iso-8859-8-i'],\n      ['csisolatin6', 'iso-8859-10'],\n      ['iso-8859-10', 'iso-8859-10'],\n      ['iso-ir-157', 'iso-8859-10'],\n      ['iso8859-10', 'iso-8859-10'],\n      ['iso885910', 'iso-8859-10'],\n      ['l6', 'iso-8859-10'],\n      ['latin6', 'iso-8859-10'],\n      ['iso-8859-13', 'iso-8859-13'],\n      ['iso8859-13', 'iso-8859-13'],\n      ['iso885913', 'iso-8859-13'],\n      ['iso-8859-14', 'iso-8859-14'],\n      ['iso8859-14', 'iso-8859-14'],\n      ['iso885914', 'iso-8859-14'],\n      ['csisolatin9', 'iso-8859-15'],\n      ['iso-8859-15', 'iso-8859-15'],\n      ['iso8859-15', 'iso-8859-15'],\n      ['iso885915', 'iso-8859-15'],\n      ['iso_8859-15', 'iso-8859-15'],\n      ['l9', 'iso-8859-15'],\n      ['iso-8859-16', 'iso-8859-16'],\n      ['cskoi8r', 'koi8-r'],\n      ['koi', 'koi8-r'],\n      ['koi8', 'koi8-r'],\n      ['koi8-r', 'koi8-r'],\n      ['koi8_r', 'koi8-r'],\n      ['koi8-ru', 'koi8-u'],\n      ['koi8-u', 'koi8-u'],\n      ['csmacintosh', 'macintosh'],\n      ['mac', 'macintosh'],\n      ['macintosh', 'macintosh'],\n      ['x-mac-roman', 'macintosh'],\n      ['dos-874', 'windows-874'],\n      ['iso-8859-11', 'windows-874'],\n      ['iso8859-11', 'windows-874'],\n      ['iso885911', 'windows-874'],\n      ['tis-620', 'windows-874'],\n      ['windows-874', 'windows-874'],\n      ['cp1250', 'windows-1250'],\n      ['windows-1250', 'windows-1250'],\n      ['x-cp1250', 'windows-1250'],\n      ['cp1251', 'windows-1251'],\n      ['windows-1251', 'windows-1251'],\n      ['x-cp1251', 'windows-1251'],\n      ['ansi_x3.4-1968', 'windows-1252'],\n      ['ascii', 'windows-1252'],\n      ['cp1252', 'windows-1252'],\n      ['cp819', 'windows-1252'],\n      ['csisolatin1', 'windows-1252'],\n      ['ibm819', 'windows-1252'],\n      ['iso-8859-1', 'windows-1252'],\n      ['iso-ir-100', 'windows-1252'],\n      ['iso8859-1', 'windows-1252'],\n      ['iso88591', 'windows-1252'],\n      ['iso_8859-1', 'windows-1252'],\n      ['iso_8859-1:1987', 'windows-1252'],\n      ['l1', 'windows-1252'],\n      ['latin1', 'windows-1252'],\n      ['us-ascii', 'windows-1252'],\n      ['windows-1252', 'windows-1252'],\n      ['x-cp1252', 'windows-1252'],\n      ['cp1253', 'windows-1253'],\n      ['windows-1253', 'windows-1253'],\n      ['x-cp1253', 'windows-1253'],\n      ['cp1254', 'windows-1254'],\n      ['csisolatin5', 'windows-1254'],\n      ['iso-8859-9', 'windows-1254'],\n      ['iso-ir-148', 'windows-1254'],\n      ['iso8859-9', 'windows-1254'],\n      ['iso88599', 'windows-1254'],\n      ['iso_8859-9', 'windows-1254'],\n      ['iso_8859-9:1989', 'windows-1254'],\n      ['l5', 'windows-1254'],\n      ['latin5', 'windows-1254'],\n      ['windows-1254', 'windows-1254'],\n      ['x-cp1254', 'windows-1254'],\n      ['cp1255', 'windows-1255'],\n      ['windows-1255', 'windows-1255'],\n      ['x-cp1255', 'windows-1255'],\n      ['cp1256', 'windows-1256'],\n      ['windows-1256', 'windows-1256'],\n      ['x-cp1256', 'windows-1256'],\n      ['cp1257', 'windows-1257'],\n      ['windows-1257', 'windows-1257'],\n      ['x-cp1257', 'windows-1257'],\n      ['cp1258', 'windows-1258'],\n      ['windows-1258', 'windows-1258'],\n      ['x-cp1258', 'windows-1258'],\n      ['x-mac-cyrillic', 'x-mac-cyrillic'],\n      ['x-mac-ukrainian', 'x-mac-cyrillic'],\n      ['chinese', 'gbk'],\n      ['csgb2312', 'gbk'],\n      ['csiso58gb231280', 'gbk'],\n      ['gb2312', 'gbk'],\n      ['gb_2312', 'gbk'],\n      ['gb_2312-80', 'gbk'],\n      ['gbk', 'gbk'],\n      ['iso-ir-58', 'gbk'],\n      ['x-gbk', 'gbk'],\n      ['gb18030', 'gb18030'],\n      ['big5', 'big5'],\n      ['big5-hkscs', 'big5'],\n      ['cn-big5', 'big5'],\n      ['csbig5', 'big5'],\n      ['x-x-big5', 'big5'],\n      ['cseucpkdfmtjapanese', 'euc-jp'],\n      ['euc-jp', 'euc-jp'],\n      ['x-euc-jp', 'euc-jp'],\n      ['csiso2022jp', 'iso-2022-jp'],\n      ['iso-2022-jp', 'iso-2022-jp'],\n      ['csshiftjis', 'shift_jis'],\n      ['ms932', 'shift_jis'],\n      ['ms_kanji', 'shift_jis'],\n      ['shift-jis', 'shift_jis'],\n      ['shift_jis', 'shift_jis'],\n      ['sjis', 'shift_jis'],\n      ['windows-31j', 'shift_jis'],\n      ['x-sjis', 'shift_jis'],\n      ['cseuckr', 'euc-kr'],\n      ['csksc56011987', 'euc-kr'],\n      ['euc-kr', 'euc-kr'],\n      ['iso-ir-149', 'euc-kr'],\n      ['korean', 'euc-kr'],\n      ['ks_c_5601-1987', 'euc-kr'],\n      ['ks_c_5601-1989', 'euc-kr'],\n      ['ksc5601', 'euc-kr'],\n      ['ksc_5601', 'euc-kr'],\n      ['windows-949', 'euc-kr'],\n      ['csiso2022kr', undefined],\n      ['hz-gb-2312', undefined],\n      ['iso-2022-cn', undefined],\n      ['iso-2022-cn-ext', undefined],\n      ['iso-2022-kr', undefined],\n      ['replacement', undefined],\n      ['unicodefffe', 'utf-16be'],\n      ['utf-16be', 'utf-16be'],\n      ['csunicode', 'utf-16le'],\n      ['iso-10646-ucs-2', 'utf-16le'],\n      ['ucs-2', 'utf-16le'],\n      ['unicode', 'utf-16le'],\n      ['unicodefeff', 'utf-16le'],\n      ['utf-16', 'utf-16le'],\n      ['utf-16le', 'utf-16le'],\n      ['x-user-defined', 'x-user-defined'],\n      // Test that match is case-insensitive\n      ['UTF-8', 'utf-8'],\n      ['UtF-8', 'utf-8'],\n    ].forEach((pair) => {\n      const [label, key] = pair;\n      if (key === undefined) {\n        throws(() => new TextDecoder(label));\n      } else {\n        {\n          const dec = new TextDecoder(label);\n          strictEqual(dec.encoding, key);\n        }\n        {\n          // Whitespace leading and trailing the label will be ignored.\n          const dec = new TextDecoder(`\\t\\n\\r ${label}\\t\\n\\r`);\n          strictEqual(dec.encoding, key);\n        }\n      }\n    });\n  },\n};\n\n// Test that windows-1252 correctly decodes bytes 0x80-0x9F.\n// These bytes differ between windows-1252 and ISO-8859-1/Latin-1.\n// Per the WHATWG Encoding Standard, labels like \"ascii\", \"latin1\", and\n// \"iso-8859-1\" all map to windows-1252, so they must produce the\n// windows-1252 code points — not the raw byte values (C1 control chars).\n// See: https://encoding.spec.whatwg.org/#names-and-labels\nexport const windows1252Decode = {\n  test() {\n    // The windows-1252 mapping for bytes 0x80-0x9F per the WHATWG Encoding Standard.\n    // See: https://encoding.spec.whatwg.org/index-windows-1252.txt\n    // Bytes 0x81, 0x8D, 0x8F, 0x90, 0x9D map to their C1 control character\n    // code points (identity) rather than being undefined.\n    const win1252UpperTable = {\n      0x80: 0x20ac, // € Euro Sign\n      0x81: 0x0081, // <control>\n      0x82: 0x201a, // ‚ Single Low-9 Quotation Mark\n      0x83: 0x0192, // ƒ Latin Small Letter F With Hook\n      0x84: 0x201e, // „ Double Low-9 Quotation Mark\n      0x85: 0x2026, // … Horizontal Ellipsis\n      0x86: 0x2020, // † Dagger\n      0x87: 0x2021, // ‡ Double Dagger\n      0x88: 0x02c6, // ˆ Modifier Letter Circumflex Accent\n      0x89: 0x2030, // ‰ Per Mille Sign\n      0x8a: 0x0160, // Š Latin Capital Letter S With Caron\n      0x8b: 0x2039, // ‹ Single Left-Pointing Angle Quotation Mark\n      0x8c: 0x0152, // Œ Latin Capital Ligature OE\n      0x8d: 0x008d, // <control>\n      0x8e: 0x017d, // Ž Latin Capital Letter Z With Caron\n      0x8f: 0x008f, // <control>\n      0x90: 0x0090, // <control>\n      0x91: 0x2018, // ' Left Single Quotation Mark\n      0x92: 0x2019, // ' Right Single Quotation Mark\n      0x93: 0x201c, // \" Left Double Quotation Mark\n      0x94: 0x201d, // \" Right Double Quotation Mark\n      0x95: 0x2022, // • Bullet\n      0x96: 0x2013, // – En Dash\n      0x97: 0x2014, // — Em Dash\n      0x98: 0x02dc, // ˜ Small Tilde\n      0x99: 0x2122, // ™ Trade Mark Sign\n      0x9a: 0x0161, // š Latin Small Letter S With Caron\n      0x9b: 0x203a, // › Single Right-Pointing Angle Quotation Mark\n      0x9c: 0x0153, // œ Latin Small Ligature OE\n      0x9d: 0x009d, // <control>\n      0x9e: 0x017e, // ž Latin Small Letter Z With Caron\n      0x9f: 0x0178, // Ÿ Latin Capital Letter Y With Diaeresis\n    };\n\n    // Test with all aliases that map to windows-1252\n    for (const label of [\n      'windows-1252',\n      'ascii',\n      'latin1',\n      'iso-8859-1',\n      'us-ascii',\n    ]) {\n      const decoder = new TextDecoder(label);\n      for (const [byte, expectedCodePoint] of Object.entries(\n        win1252UpperTable\n      )) {\n        const byteVal = Number(byte);\n        const decoded = decoder.decode(Uint8Array.of(byteVal));\n        const actualCodePoint = decoded.codePointAt(0);\n        strictEqual(\n          actualCodePoint,\n          expectedCodePoint,\n          `${label}: byte 0x${byteVal.toString(16)} should decode to ` +\n            `U+${expectedCodePoint.toString(16).toUpperCase().padStart(4, '0')}, ` +\n            `got U+${actualCodePoint.toString(16).toUpperCase().padStart(4, '0')}`\n        );\n      }\n    }\n\n    // Verify that bytes outside 0x80-0x9F still work correctly\n    const decoder = new TextDecoder('windows-1252');\n    // ASCII range (0x00-0x7F) should be identity\n    strictEqual(decoder.decode(Uint8Array.of(0x41)).codePointAt(0), 0x41); // 'A'\n    strictEqual(decoder.decode(Uint8Array.of(0x7f)).codePointAt(0), 0x7f);\n    // 0xA0-0xFF range is identical between latin-1 and windows-1252\n    strictEqual(decoder.decode(Uint8Array.of(0xa2)).codePointAt(0), 0xa2); // ¢\n    strictEqual(decoder.decode(Uint8Array.of(0xff)).codePointAt(0), 0xff); // ÿ\n  },\n};\n\n// Per the WHATWG encoding spec (section 10.1.1), GBK's decoder is gb18030's decoder.\n// The .encoding property must still return \"gbk\", but decoding results must match gb18030.\n// https://encoding.spec.whatwg.org/#gbk-decoder\nexport const gbkDecoderIsGb18030Decoder = {\n  test() {\n    const gbk = new TextDecoder('gbk');\n    const gb18030 = new TextDecoder('gb18030');\n\n    // .encoding property must still distinguish the two\n    strictEqual(gbk.encoding, 'gbk');\n    strictEqual(gb18030.encoding, 'gb18030');\n\n    // Decoding results must be identical. These byte pairs exercise boundary\n    // conditions where gbk and gb18030 would diverge if the ICU converter\n    // for gbk were used directly instead of delegating to gb18030.\n    const testBytes = [\n      [0, 255],\n      [128, 255],\n      [129, 48],\n      [129, 255],\n      [254, 48],\n      [254, 255],\n      [255, 0],\n      [255, 255],\n    ];\n    for (const bytes of testBytes) {\n      const u8 = Uint8Array.from(bytes);\n      strictEqual(\n        gbk.decode(u8),\n        gb18030.decode(u8),\n        `gbk and gb18030 must decode [${bytes}] identically`\n      );\n    }\n  },\n};\n\nconst gbVersionAndRangesTest = (encoding) => {\n  const loose = new TextDecoder(encoding);\n  const checkAll = (...list) => list.forEach((x) => check(...x));\n  const check = (bytes, str, invalid = false) => {\n    const fatal = new TextDecoder(encoding, { fatal: true });\n    const u8 = Uint8Array.from(bytes);\n    strictEqual(loose.decode(u8), str);\n    if (!invalid) strictEqual(fatal.decode(u8), str);\n    if (invalid) throws(() => fatal.decode(u8));\n  };\n\n  check([0x84, 0x31, 0xa4, 0x36], '\\uFFFC');\n  check([0x84, 0x31, 0xa4, 0x37], '\\uFFFD');\n  check([0x84, 0x31, 0xa4, 0x38], '\\uFFFE');\n  check([0x84, 0x31, 0xa4, 0x39], '\\uFFFF');\n  check([0x84, 0x31, 0xa5, 0x30], '\\uFFFD', true);\n  check([0x8f, 0x39, 0xfe, 0x39], '\\uFFFD', true);\n  check([0x90, 0x30, 0x81, 0x30], String.fromCodePoint(0x1_00_00));\n  check([0x90, 0x30, 0x81, 0x31], String.fromCodePoint(0x1_00_01));\n\n  check([0xe3, 0x32, 0x9a, 0x35], String.fromCodePoint(0x10_ff_ff));\n  check([0xe3, 0x32, 0x9a, 0x36], '\\uFFFD', true);\n  check([0xe3, 0x32, 0x9a, 0x37], '\\uFFFD', true);\n\n  check([0xfe, 0x39, 0xfe, 0x39], '\\uFFFD', true);\n  check([0xff, 0x39, 0xfe, 0x39], '\\uFFFD9\\uFFFD', true);\n  check([0xfe, 0x40, 0xfe, 0x39], '\\uFA0C\\uFFFD', true);\n  check([0xfe, 0x39, 0xff, 0x39], '\\uFFFD9\\uFFFD9', true);\n  check([0xfe, 0x39, 0xfe, 0x40], '\\uFFFD9\\uFA0C', true);\n\n  checkAll(\n    [[0xa8, 0xbb], '\\u0251'],\n    [[0xa8, 0xbc], '\\u1E3F'],\n    [[0xa8, 0xbd], '\\u0144']\n  );\n  check([0x81, 0x35, 0xf4, 0x36], '\\u1E3E');\n  check([0x81, 0x35, 0xf4, 0x37], '\\uE7C7');\n  check([0x81, 0x35, 0xf4, 0x38], '\\u1E40');\n\n  checkAll(\n    [[0xa6, 0xd9], '\\uFE10'],\n    [[0xa6, 0xed], '\\uFE18'],\n    [[0xa6, 0xf3], '\\uFE19']\n  );\n  checkAll([[0xfe, 0x59], '\\u9FB4'], [[0xfe, 0xa0], '\\u9FBB']);\n};\n\nexport const gb18030VersionAndRanges = {\n  test() {\n    gbVersionAndRangesTest('gb18030');\n  },\n};\n\nexport const gbkVersionAndRanges = {\n  test() {\n    gbVersionAndRangesTest('gbk');\n  },\n};\n\n// Verify that the WHATWG-required mapping corrections also produce the\n// correct output when the corrected byte sequences appear inside a larger\n// buffer (surrounded by ASCII), not only when they are the entire input.\nexport const gb18030OverridesEmbedded = {\n  test() {\n    const d = new TextDecoder('gb18030');\n\n    // 0x80 → U+20AC (Euro sign) surrounded by ASCII\n    strictEqual(d.decode(Uint8Array.of(0x41, 0x80, 0x42)), 'A\\u20ACB');\n\n    // Two-byte mapping corrections surrounded by ASCII\n    strictEqual(d.decode(Uint8Array.of(0x41, 0xa8, 0xbb, 0x42)), 'A\\u0251B');\n    strictEqual(d.decode(Uint8Array.of(0x41, 0xa8, 0xbc, 0x42)), 'A\\u1E3FB');\n    strictEqual(d.decode(Uint8Array.of(0x41, 0xa8, 0xbd, 0x42)), 'A\\u0144B');\n\n    // Vertical form corrections surrounded by ASCII\n    strictEqual(d.decode(Uint8Array.of(0x41, 0xa6, 0xd9, 0x42)), 'A\\uFE10B');\n\n    // CJK extension corrections surrounded by ASCII\n    strictEqual(d.decode(Uint8Array.of(0x41, 0xfe, 0x59, 0x42)), 'A\\u9FB4B');\n  },\n};\n\nexport const replacementPushbackAsciiCharactersLoose = {\n  test() {\n    const vectors = {\n      big5: [\n        [[0x80], '\\uFFFD'],\n        [[0x81, 0x40], '\\uFFFD@'],\n        [[0x83, 0x5c], '\\uFFFD\\\\'],\n        [[0x87, 0x87, 0x40], '\\uFFFD@'],\n        [[0x81, 0x81], '\\uFFFD'],\n      ],\n      'iso-2022-jp': [\n        [[0x1b, 0x24], '\\uFFFD$'],\n        [[0x1b, 0x24, 0x40, 0x1b, 0x24], '\\uFFFD\\uFFFD'],\n      ],\n      'euc-jp': [\n        [[0x80], '\\uFFFD'],\n        [[0x8d, 0x8d], '\\uFFFD\\uFFFD'],\n        [[0x8e, 0x8e], '\\uFFFD'],\n      ],\n    };\n\n    for (const [encoding, list] of Object.entries(vectors)) {\n      const d = new TextDecoder(encoding);\n      for (const [bytes, text] of list) {\n        strictEqual(d.decode(Uint8Array.from(bytes)), text);\n      }\n    }\n  },\n};\n\nexport const stickyMultibyteStateIso2022JpLoose = {\n  test() {\n    const vectors = [\n      [[27], '\\uFFFD'],\n      [[27, 0x28], '\\uFFFD('],\n      [[0x1b, 0x28, 0x49], ''],\n    ];\n\n    const d = new TextDecoder('iso-2022-jp');\n    for (const [bytes, text] of vectors) {\n      strictEqual(d.decode(Uint8Array.of(0x40)), '@');\n      strictEqual(d.decode(Uint8Array.from(bytes)), text);\n      strictEqual(d.decode(Uint8Array.of(0x40)), '@');\n      strictEqual(d.decode(Uint8Array.of(0x2a)), '*');\n      strictEqual(d.decode(Uint8Array.of(0x42)), 'B');\n    }\n  },\n};\n\nexport const fatalStreamGb18030Gbk = {\n  test() {\n    for (const encoding of ['gb18030', 'gbk']) {\n      {\n        const d = new TextDecoder(encoding, { fatal: true });\n        strictEqual(d.decode(Uint8Array.of(0x80), { stream: true }), '\\u20AC');\n        throws(() =>\n          d.decode(Uint8Array.of(0x81, 0x30, 0x21, 0x21, 0x21), {\n            stream: true,\n          })\n        );\n        strictEqual(d.decode(Uint8Array.of(0x80)), '\\u20AC');\n      }\n\n      {\n        const d = new TextDecoder(encoding, { fatal: true });\n        strictEqual(d.decode(Uint8Array.of(0x80), { stream: true }), '\\u20AC');\n        throws(() =>\n          d.decode(Uint8Array.of(0x81, 0x30, 0x81, 0x42, 0x42), {\n            stream: true,\n          })\n        );\n        strictEqual(d.decode(Uint8Array.of(0x80)), '\\u20AC');\n      }\n    }\n  },\n};\n\nexport const textDecoderStream = {\n  test() {\n    const stream = new TextDecoderStream('utf-16', {\n      fatal: true,\n      ignoreBOM: true,\n    });\n    strictEqual(stream.encoding, 'utf-16le');\n    strictEqual(stream.fatal, true);\n    strictEqual(stream.ignoreBOM, true);\n\n    const enc = new TextEncoderStream();\n    strictEqual(enc.encoding, 'utf-8');\n  },\n};\n\n// Per WHATWG Big5 decoder step 1, when end-of-queue is reached with a\n// pending lead byte, the decoder must return error (U+FFFD in replacement\n// mode, throw in fatal mode). This tests the streaming case where a lead\n// byte is buffered in one call and then flushed without a trail byte.\nexport const big5OrphanedLeadOnFlush = {\n  test() {\n    // 0xA4 is a valid Big5 lead byte (e.g., first byte of 中 = 0xA4 0xA4).\n    // Streaming it alone, then flushing, must produce U+FFFD.\n    {\n      const dec = new TextDecoder('big5');\n      strictEqual(dec.decode(Uint8Array.of(0xa4), { stream: true }), '');\n      strictEqual(dec.decode(), '\\uFFFD');\n    }\n\n    // Fatal mode must throw on the orphaned lead.\n    {\n      const dec = new TextDecoder('big5', { fatal: true });\n      strictEqual(dec.decode(Uint8Array.of(0xa4), { stream: true }), '');\n      throws(() => dec.decode());\n    }\n\n    // Orphaned lead followed by an invalid trail byte on flush: the lead\n    // must produce U+FFFD. 0x20 (space) is not a valid Big5 trail byte\n    // (valid trails are 0x40-0x7E and 0xA1-0xFE).\n    {\n      const dec = new TextDecoder('big5');\n      strictEqual(dec.decode(Uint8Array.of(0xa4), { stream: true }), '');\n      const result = dec.decode(Uint8Array.of(0x20));\n      // The orphaned lead must produce at least one U+FFFD.\n      ok(\n        result.includes('\\uFFFD'),\n        `expected U+FFFD in output, got: ${JSON.stringify(result)}`\n      );\n      // The space byte must not be swallowed.\n      ok(\n        result.includes(' '),\n        `expected space in output, got: ${JSON.stringify(result)}`\n      );\n    }\n\n    // Streaming a complete pair across two calls must still work.\n    {\n      const dec = new TextDecoder('big5');\n      strictEqual(dec.decode(Uint8Array.of(0xa4), { stream: true }), '');\n      strictEqual(dec.decode(Uint8Array.of(0xa4)), '中');\n    }\n  },\n};\n\n// Test x-user-defined encoding per WHATWG spec\n// https://encoding.spec.whatwg.org/#x-user-defined-decoder\nexport const xUserDefinedDecode = {\n  test() {\n    const decoder = new TextDecoder('x-user-defined');\n    strictEqual(decoder.encoding, 'x-user-defined');\n    strictEqual(decoder.fatal, false);\n    strictEqual(decoder.ignoreBOM, false);\n\n    // Test ASCII bytes (0x00-0x7F) - identity mapping\n    strictEqual(decoder.decode(Uint8Array.of(0x41)), 'A');\n    strictEqual(decoder.decode(Uint8Array.of(0x00)), '\\u0000');\n    strictEqual(decoder.decode(Uint8Array.of(0x7f)), '\\u007F');\n\n    // Test high bytes (0x80-0xFF) - map to Private Use Area U+F780-U+F7FF\n    strictEqual(decoder.decode(Uint8Array.of(0x80)), '\\uF780');\n    strictEqual(decoder.decode(Uint8Array.of(0x81)), '\\uF781');\n    strictEqual(decoder.decode(Uint8Array.of(0xff)), '\\uF7FF');\n\n    // Test mixed sequence\n    const mixed = new Uint8Array([0x00, 0x7f, 0x80, 0x81, 0xff]);\n    strictEqual(decoder.decode(mixed), '\\u0000\\u007F\\uF780\\uF781\\uF7FF');\n\n    // Test empty input\n    strictEqual(decoder.decode(new Uint8Array([])), '');\n    strictEqual(decoder.decode(), '');\n\n    // Test pure ASCII input (fast path)\n    strictEqual(\n      decoder.decode(new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f])),\n      'Hello'\n    );\n\n    // Test streaming (x-user-defined is single-byte, streaming is trivial)\n    const streamDecoder = new TextDecoder('x-user-defined');\n    let result = '';\n    result += streamDecoder.decode(Uint8Array.of(0x41), { stream: true });\n    result += streamDecoder.decode(Uint8Array.of(0x80), { stream: true });\n    result += streamDecoder.decode(Uint8Array.of(0xff), { stream: true });\n    result += streamDecoder.decode();\n    strictEqual(result, 'A\\uF780\\uF7FF');\n  },\n};\n\n// Test x-user-defined with fatal option (all 256 bytes are valid)\nexport const xUserDefinedFatal = {\n  test() {\n    const decoder = new TextDecoder('x-user-defined', { fatal: true });\n    strictEqual(decoder.fatal, true);\n\n    // All 256 byte values are valid, fatal mode should never throw\n    for (let byte = 0; byte < 256; byte++) {\n      const decoded = decoder.decode(Uint8Array.of(byte));\n      if (byte < 0x80) {\n        strictEqual(decoded.codePointAt(0), byte);\n      } else {\n        strictEqual(decoded.codePointAt(0), 0xf700 + byte);\n      }\n    }\n  },\n};\n\n// Verify that streaming with zero-length input works for every legacy\n// encoding handled by the Rust LegacyDecoder. An empty chunk in streaming\n// mode must produce an empty string and leave the decoder in a valid state\n// for subsequent calls.\nexport const legacyStreamEmptyInput = {\n  test() {\n    const encodings = [\n      'big5',\n      'euc-jp',\n      'euc-kr',\n      'gb18030',\n      'gbk',\n      'iso-2022-jp',\n      'shift_jis',\n      'windows-1252',\n      'x-user-defined',\n    ];\n\n    const empty = new Uint8Array(0);\n\n    for (const label of encodings) {\n      for (const fatal of [false, true]) {\n        const dec = new TextDecoder(label, { fatal });\n\n        // Empty stream chunk must produce empty string.\n        strictEqual(\n          dec.decode(empty, { stream: true }),\n          '',\n          `${label} (fatal=${fatal}): empty stream chunk should be ''`\n        );\n\n        // A second empty stream chunk must also be fine.\n        strictEqual(\n          dec.decode(empty, { stream: true }),\n          '',\n          `${label} (fatal=${fatal}): second empty stream chunk should be ''`\n        );\n\n        // Final flush with no pending bytes must produce empty string.\n        strictEqual(\n          dec.decode(),\n          '',\n          `${label} (fatal=${fatal}): flush after empty chunks should be ''`\n        );\n\n        // Decoder must still work normally after the empty-stream sequence.\n        // Feed a single ASCII byte to verify.\n        strictEqual(\n          dec.decode(Uint8Array.of(0x41)),\n          'A',\n          `${label} (fatal=${fatal}): decode 'A' after empty stream should work`\n        );\n      }\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/encoding-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"encoding-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"encoding-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"text_decoder_cjk_decoder\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/error-in-error-event-test.js",
    "content": "import { strictEqual } from 'assert';\n\nexport const test = {\n  async test(_, env) {\n    const resp = await env.subrequest.fetch('http://example.org');\n    strictEqual(await resp.text(), 'ok');\n  },\n};\n\nexport default {\n  async fetch() {\n    // Throwing an error in the error event listener should not be catchable directly\n    // but should cause the IoContext to be aborted with the error.\n    addEventListener(\n      'error',\n      () => {\n        // This error is not going to be catchable. The best we can do is to log it.\n        // Unfortunately, workerd currently does not give us any mechanism to verify\n        // that it was logged in the test. Let's make sure the response is handled\n        // correctly at least.\n        throw new Error('boom (real)');\n      },\n      { once: true }\n    );\n    queueMicrotask(() => {\n      throw new Error('boom (unused)');\n    });\n    await scheduler.wait(10);\n    return new Response('ok');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/error-in-error-event-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"error-in-error-event-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"error-in-error-event-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat_v2\"],\n        bindings = [\n          (name = \"subrequest\", service = \"error-in-error-event-test\")\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/events-test.js",
    "content": "import { deepStrictEqual, strictEqual, throws, ok } from 'node:assert';\nimport { mock } from 'node:test';\n\n// Test for the Event and EventTarget standard Web API implementations.\n// The implementation for these are in api/basics.{h|c++}\n\nexport const event = {\n  test() {\n    // Any value that is not-stringifiable fails\n    throws(() => new Event(Symbol('test')));\n\n    // stringifiable values do work, however\n    strictEqual(new Event({}).type, '[object Object]');\n    strictEqual(new Event(null).type, 'null');\n    strictEqual(new Event(1).type, '1');\n\n    // Passing undefined explicitly works\n    strictEqual(new Event(undefined).type, 'undefined');\n\n    // But not passing a value for type fails\n    throws(() => new Event());\n\n    // We can create an Event object with no options and it works as expected.\n    const event = new Event('foo');\n    strictEqual(event.type, 'foo');\n    strictEqual(event.bubbles, false);\n    strictEqual(event.cancelable, false);\n    strictEqual(event.composed, false);\n    strictEqual(event.isTrusted, false);\n    strictEqual(event.defaultPrevented, false);\n    strictEqual(event.eventPhase, Event.NONE);\n    strictEqual(event.returnValue, true);\n    strictEqual(event.timeStamp, 0.0);\n    strictEqual(event.cancelBubble, false);\n    strictEqual(event.currentTarget, undefined);\n    deepStrictEqual(event.composedPath(), []);\n    event.stopImmediatePropagation();\n    event.stopPropagation();\n    event.preventDefault();\n    // Even tho we called preventDefault, because the event is not cancelable\n    // by default, defaultPrevented still is false.\n    strictEqual(event.defaultPrevented, false);\n    strictEqual(event.cancelBubble, true);\n  },\n};\n\nexport const eventWithOptions = {\n  test() {\n    // The options argument must be an object\n    throws(() => new Event('foo', 1));\n    throws(() => new Event('foo', 'bar'));\n\n    // We can create an Event object with no options and it works as expected.\n    const event = new Event('foo', {\n      cancelable: true,\n      bubbles: 'truthy values work also',\n      composed: true,\n    });\n    strictEqual(event.type, 'foo');\n    strictEqual(event.bubbles, true);\n    strictEqual(event.cancelable, true);\n    strictEqual(event.composed, true);\n    strictEqual(event.isTrusted, false);\n    strictEqual(event.defaultPrevented, false);\n    strictEqual(event.eventPhase, Event.NONE);\n    strictEqual(event.returnValue, true);\n    strictEqual(event.timeStamp, 0.0);\n    strictEqual(event.cancelBubble, false);\n    strictEqual(event.currentTarget, undefined);\n    deepStrictEqual(event.composedPath(), []);\n    event.stopImmediatePropagation();\n    event.stopPropagation();\n    event.preventDefault();\n    // Because the event is cancelable, defaultPrevented is true.\n    strictEqual(event.defaultPrevented, true);\n    strictEqual(event.returnValue, false);\n  },\n};\n\nexport const eventSubclass = {\n  test() {\n    class Foo extends Event {\n      constructor() {\n        super('foo');\n      }\n    }\n    const event = new Foo();\n    strictEqual(event.type, 'foo');\n    strictEqual(event.bubbles, false);\n    strictEqual(event.cancelable, false);\n    strictEqual(event.composed, false);\n    strictEqual(event.isTrusted, false);\n    strictEqual(event.defaultPrevented, false);\n    strictEqual(event.eventPhase, Event.NONE);\n    strictEqual(event.returnValue, true);\n    strictEqual(event.timeStamp, 0.0);\n    strictEqual(event.cancelBubble, false);\n    strictEqual(event.currentTarget, undefined);\n\n    // Everything except cancelBubble is read only and will throw\n    // if attempts are made to modify\n    throws(() => (event.type = 'foo'));\n    throws(() => (event.bubbles = false));\n    throws(() => (event.cancelable = false));\n    throws(() => (event.composed = false));\n    throws(() => (event.isTrusted = false));\n    throws(() => (event.defaultPrevented = false));\n    throws(() => (event.eventPhase = Event.NONE));\n    throws(() => (event.returnValue = true));\n    throws(() => (event.timeStamp = 0.0));\n    throws(() => (event.currentTarget = undefined));\n    event.cancelBubble = true;\n    strictEqual(event.cancelBubble, true);\n\n    // With the default compatibility flag set, the properties should\n    // exist on the prototype and not as own properties on the event itself.\n    strictEqual(\n      Reflect.getOwnPropertyDescriptor(event, 'cancelable'),\n      undefined\n    );\n\n    // Which means a subclass can replace the implementation successfully.\n    class Bar extends Event {\n      constructor() {\n        super('bar');\n      }\n      get bubbles() {\n        return 'hello';\n      }\n    }\n    const bar = new Bar();\n    strictEqual(bar.bubbles, 'hello');\n    strictEqual(bar.composed, false);\n  },\n};\n\nexport const extendableEventNotConstructable = {\n  test() {\n    // While the spec defines ExtendableEvent to be consructable, we do not support\n    // doing so. This is intentional because the only real use case of ExtendableEvent\n    // is to allow calling waitUntil, which only works on trusted events which can only\n    // originate from the runtime. That is, user code cannot create their own trusted\n    // events.\n    strictEqual(typeof ExtendableEvent, 'function');\n    throws(() => new ExtendableEvent('foo'));\n  },\n};\n\nexport const basicEventTarget = {\n  test() {\n    const target = new EventTarget();\n\n    const event = new Event('foo');\n    strictEqual(event.eventPhase, Event.NONE);\n    strictEqual(event.currentTarget, undefined);\n\n    let dispatchCount = 0;\n\n    const handler = (dispatched) => {\n      strictEqual(dispatched, event);\n      strictEqual(dispatched.eventPhase, Event.AT_TARGET);\n      strictEqual(dispatched.currentTarget, target);\n      deepStrictEqual(dispatched.composedPath(), [target]);\n      dispatchCount++;\n\n      // The event is already being dispatched so can't be again.\n      throws(() => target.dispatchEvent(dispatched));\n    };\n\n    const handlerObj = {\n      handleEvent: handler,\n    };\n\n    throws(() => target.addEventListener('foo', {}));\n    throws(() => target.addEventListener('foo', 'hello'));\n    throws(() => target.addEventListener('foo', []));\n    throws(() => target.addEventListener('foo', false));\n\n    // Event listener with no options\n    target.addEventListener('foo', handler);\n\n    // Same handler can be attached twice, but is only invoked once.\n    target.addEventListener('foo', handler);\n\n    target.addEventListener('foo', handlerObj);\n\n    let classCalled;\n    const foo = new (class Foo {\n      handleEvent(event) {\n        classCalled = true;\n      }\n    })();\n    target.addEventListener('foo', foo);\n\n    target.dispatchEvent(event);\n\n    strictEqual(classCalled, true);\n    strictEqual(event.eventPhase, Event.NONE);\n    strictEqual(event.currentTarget, target);\n\n    strictEqual(dispatchCount, 2);\n\n    target.removeEventListener('foo', handler);\n\n    target.dispatchEvent(event);\n\n    strictEqual(dispatchCount, 3);\n  },\n};\n\nexport const subclassedEventTarget = {\n  test() {\n    class MyEventTarget extends EventTarget {}\n    const event = new Event('foo');\n    const target = new MyEventTarget();\n    let dispatchCount = 0;\n    target.addEventListener('foo', (dispatched) => {\n      strictEqual(dispatched, event);\n      dispatchCount++;\n    });\n    target.dispatchEvent(event);\n    strictEqual(dispatchCount, 1);\n  },\n};\n\nexport const onceListener = {\n  test() {\n    const target = new EventTarget();\n    const event = new Event('foo');\n\n    let dispatchCount = 0;\n\n    target.addEventListener(\n      'foo',\n      () => {\n        dispatchCount++;\n      },\n      { once: true }\n    );\n\n    target.dispatchEvent(event);\n    target.dispatchEvent(event);\n\n    strictEqual(dispatchCount, 1);\n  },\n};\n\nexport const cancelableListener = {\n  test() {\n    const target = new EventTarget();\n    const event = new Event('foo');\n\n    let dispatchCount = 0;\n\n    const ac = new AbortController();\n\n    target.addEventListener(\n      'foo',\n      () => {\n        dispatchCount++;\n      },\n      { signal: ac.signal }\n    );\n\n    // Passing an already aborted signal just works as expected.\n    // No errors are thrown.\n    target.addEventListener(\n      'foo',\n      () => {\n        dispatchCount++;\n      },\n      { signal: AbortSignal.abort() }\n    );\n\n    ac.abort();\n\n    target.dispatchEvent(event);\n\n    strictEqual(dispatchCount, 0);\n  },\n};\n\nexport const cancelableListenerAbortPropagation = {\n  test() {\n    // TODO(bug): Cancelable event listeners should be removed by signal even when\n    // signal's abort event propagation is stopped. This is a safety measure to\n    // prevent certain kinds of memory leaks. We currently do not implement this\n    // protection.\n    // const controller = new AbortController();\n    // const { signal } = controller;\n    // signal.addEventListener('abort', (e) => e.stopImmediatePropagation(), { once: true });\n    // const et = new EventTarget();\n    // et.addEventListener('foo', () => {\n    //   console.log('....')\n    //   throw new Error('should not be called');\n    // }, { signal });\n    // controller.abort();\n    // et.dispatchEvent(new Event('foo'));\n  },\n};\n\nexport const passiveCaptureListener = {\n  test() {\n    const target = new EventTarget();\n    // capture and passive must be false. We do not support these but\n    // we allow them to be set for code portability reasons.\n    throws(() => {\n      target.addEventListener('foo', () => {}, {\n        capture: true,\n      });\n    });\n    throws(() => {\n      target.addEventListener('foo', () => {}, true);\n    });\n    throws(() => {\n      target.addEventListener('foo', () => {}, {\n        passive: true,\n      });\n    });\n    throws(() => {\n      target.removeEventListener('foo', () => {}, {\n        capture: true,\n      });\n    });\n    throws(() => {\n      target.removeEventListener('foo', () => {}, true);\n    });\n  },\n};\n\nexport const globalIsEventTarget = {\n  test() {\n    // TODO(bug): For some reason, even tho our globalThis does, in fact,\n    // extend EventTarget and inherits the dispatchEvent, addEventListener, etc,\n    // instanceof does not report that fact correctly. So we'll need to fix this.\n\n    // strictEqual(globalThis instanceof EventTarget);\n\n    strictEqual(typeof globalThis.dispatchEvent, 'function');\n    strictEqual(typeof globalThis.addEventListener, 'function');\n    strictEqual(typeof globalThis.removeEventListener, 'function');\n\n    const event = new Event('foo');\n    let dispatchCount = 0;\n    addEventListener(\n      'foo',\n      () => {\n        dispatchCount++;\n      },\n      { once: true }\n    );\n    dispatchEvent(event);\n    strictEqual(dispatchCount, 1);\n  },\n};\n\nexport const errorInHandler = {\n  test() {\n    // TODO(bug): Erroring in one event handler should not prevent others from being\n    // run but we currently do not implement this correctly.\n    const event = new Event('foo');\n    const target = new EventTarget();\n    let dispatchCount = 0;\n    target.addEventListener('foo', () => {\n      dispatchCount++;\n      throw new Error('boom');\n    });\n    target.addEventListener('foo', () => {\n      dispatchCount++;\n    });\n\n    throws(() => target.dispatchEvent(event));\n\n    // The dispatchCount here should be 2, but with the current bug, it's only 1\n    // strictEqual(dispatchCount, 2);\n    strictEqual(dispatchCount, 1);\n  },\n};\n\nexport const stopImmediatePropagation = {\n  test() {\n    const event = new Event('foo');\n    const target = new EventTarget();\n    let dispatchCount = 0;\n    target.addEventListener('foo', (event) => {\n      dispatchCount++;\n      // Calling stopImmediatePropagation should prevent the next listener\n      // from being invoked.\n      event.stopImmediatePropagation();\n    });\n    target.addEventListener('foo', (event) => {\n      throw new Error('should not have been invoked');\n    });\n    target.dispatchEvent(event);\n    strictEqual(dispatchCount, 1);\n  },\n};\n\nexport const nullUndefinedHandler = {\n  test() {\n    // TODO(bug): Odd as it may seem, the spec allows passing null and undefined\n    // to addEventListener. We currently do not handle these correctly.\n    const target = new EventTarget();\n    // target.addEventListener('foo', null);\n    // target.addEventListener('foo', undefined);\n  },\n};\n\nexport const customEvent = {\n  test() {\n    const event = new CustomEvent('foo', { detail: { a: 123 } });\n    ok(event instanceof Event);\n    strictEqual(event.type, 'foo');\n    deepStrictEqual(event.detail, { a: 123 });\n  },\n};\n\nexport const closeEvent = {\n  test() {\n    // The CloseEvent constructor second argument is optional. Our implementation\n    // had it as required. Let's make sure we can create it without the second arg.\n    new CloseEvent('foo');\n    new CloseEvent('foo', { code: 1000, reason: 'bye' });\n  },\n};\n\nexport const handlerThis = {\n  test() {\n    const et = new EventTarget();\n    const handler = mock.fn(function () {\n      strictEqual(this, et);\n    });\n    et.addEventListener('foo', handler);\n\n    const handlerObject = {\n      handleEvent: mock.fn(function () {\n        strictEqual(this, handlerObject);\n      }),\n    };\n    et.addEventListener('foo', handlerObject);\n\n    et.dispatchEvent(new Event('foo'));\n    strictEqual(handler.mock.callCount(), 1);\n    strictEqual(handlerObject.handleEvent.mock.callCount(), 1);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/events-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"events-nodejs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"events-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"set_event_target_this\", \"workers_api_getters_setters_on_prototype\", \"dont_substitute_null_on_type_error\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/eventsource-test.js",
    "content": "// Copyright (c) 2017-2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport { strictEqual, ok, throws } from 'node:assert';\n\nexport const acceptEventStreamTest = {\n  async test(ctrl, env) {\n    const eventsource = new EventSource(\n      'http://example.org/accept-event-stream',\n      { fetcher: env.subrequest }\n    );\n    ok(eventsource instanceof EventTarget);\n    strictEqual(eventsource.readyState, EventSource.CONNECTING);\n    const { promise, resolve } = Promise.withResolvers();\n    let opened = false;\n    eventsource.onopen = () => {\n      strictEqual(eventsource.readyState, EventSource.OPEN);\n      opened = true;\n    };\n    eventsource.onmessage = (event) => {\n      strictEqual(event.data, 'text/event-stream');\n      strictEqual(event.origin, 'http://example.org');\n      eventsource.close();\n      strictEqual(eventsource.readyState, EventSource.CLOSED);\n      resolve();\n    };\n    await promise;\n    ok(opened);\n  },\n};\n\nexport const cacheControlEventStreamTest = {\n  async test(ctrl, env) {\n    const eventsource = new EventSource(\n      'http://example.org/cache-control-event-stream',\n      { fetcher: env.subrequest }\n    );\n    const { promise, resolve } = Promise.withResolvers();\n    eventsource.onmessage = (event) => {\n      strictEqual(event.data, 'no-cache');\n      eventsource.close();\n      resolve();\n    };\n    await promise;\n  },\n};\n\nexport const lastEventIdTest = {\n  async test(ctrl, env) {\n    const eventsource = new EventSource('http://example.org/last-event-id', {\n      fetcher: env.subrequest,\n    });\n    const { promise, resolve } = Promise.withResolvers();\n    let first = true;\n    eventsource.onmessage = (event) => {\n      if (first) {\n        strictEqual(event.data, 'first');\n        first = false;\n      } else {\n        strictEqual(event.data, '1');\n        eventsource.close();\n        resolve();\n      }\n    };\n    await promise;\n  },\n};\n\nexport const eventIdPersistsTest = {\n  async test(ctrl, env) {\n    const eventsource = new EventSource(\n      'http://example.org/event-id-persists',\n      { fetcher: env.subrequest }\n    );\n    const { promise, resolve } = Promise.withResolvers();\n    eventsource.onmessage = (event) => {\n      switch (event.data) {\n        case 'first':\n          strictEqual(event.lastEventId, '1');\n          break;\n        case 'second':\n          strictEqual(event.lastEventId, '1');\n          break;\n        case 'third':\n          strictEqual(event.lastEventId, '2');\n          break;\n        case 'fourth':\n          strictEqual(event.lastEventId, '2');\n          eventsource.close();\n          resolve();\n          break;\n        default:\n          throw new Error(`Unexpected message: ${event.data}`);\n      }\n    };\n    await promise;\n  },\n};\n\nexport const eventIdResetsTest = {\n  async test(ctrl, env) {\n    const eventsource = new EventSource('http://example.org/event-id-resets', {\n      fetcher: env.subrequest,\n    });\n    const { promise, resolve } = Promise.withResolvers();\n    eventsource.onmessage = (event) => {\n      switch (event.data) {\n        case 'first':\n          strictEqual(event.lastEventId, '1');\n          break;\n        case 'second':\n          strictEqual(event.lastEventId, '');\n          eventsource.close();\n          resolve();\n          break;\n        default:\n          throw new Error(`Unexpected message: ${event.data}`);\n      }\n    };\n    await promise;\n  },\n};\n\nexport const eventIdResets2Test = {\n  async test(ctrl, env) {\n    const eventsource = new EventSource(\n      'http://example.org/event-id-resets-2',\n      { fetcher: env.subrequest }\n    );\n    const { promise, resolve } = Promise.withResolvers();\n    eventsource.onmessage = (event) => {\n      switch (event.data) {\n        case 'first':\n          strictEqual(event.lastEventId, '1');\n          break;\n        case 'second':\n          strictEqual(event.lastEventId, '');\n          eventsource.close();\n          resolve();\n          break;\n        default:\n          throw new Error(`Unexpected message: ${event.data}`);\n      }\n    };\n    await promise;\n  },\n};\n\nexport const messageTest = {\n  async test(ctrl, env) {\n    const eventsource = new EventSource('http://example.org/message', {\n      fetcher: env.subrequest,\n    });\n    const { promise, resolve } = Promise.withResolvers();\n    // We should get two messages...\n    let count = 0;\n    eventsource.onmessage = (event) => {\n      switch (count++) {\n        case 0: {\n          strictEqual(event.data, 'one\\ntwo');\n          break;\n        }\n        case 1: {\n          strictEqual(event.data, 'end');\n          eventsource.close();\n          resolve();\n          break;\n        }\n      }\n    };\n    await promise;\n  },\n};\n\nexport const reconnectFailTest = {\n  async test(ctrl, env) {\n    const eventsource = new EventSource('http://example.org/reconnect-fail', {\n      fetcher: env.subrequest,\n    });\n    const { promise, resolve } = Promise.withResolvers();\n    let count = 0;\n    eventsource.onmessage = (event) => {\n      switch (count++) {\n        case 0: {\n          strictEqual(event.data, 'opened');\n          break;\n        }\n        case 1: {\n          strictEqual(event.data, 'reconnected');\n          break;\n        }\n      }\n    };\n    // Should be called four times.\n    let errorCount = 0;\n    eventsource.onerror = (event) => {\n      if (errorCount < 3) {\n        strictEqual(eventsource.readyState, EventSource.CONNECTING);\n      }\n      if (++errorCount === 4) {\n        strictEqual(eventsource.readyState, EventSource.CLOSED);\n        resolve();\n      }\n    };\n    await promise;\n  },\n};\n\nexport const statusErrorTest = {\n  async test(ctrl, env) {\n    const eventsource = new EventSource('http://example.org/status-error', {\n      fetcher: env.subrequest,\n    });\n    const { promise, resolve } = Promise.withResolvers();\n    eventsource.onopen = () => {\n      throw new Error('should not be called');\n    };\n    eventsource.onerror = (event) => {\n      strictEqual(eventsource.readyState, EventSource.CLOSED);\n      resolve();\n    };\n    await promise;\n  },\n};\n\nexport const eventTest = {\n  async test(ctrl, env) {\n    const eventsource = new EventSource('http://example.org/event', {\n      fetcher: env.subrequest,\n    });\n    const { promise, resolve } = Promise.withResolvers();\n    let count = 0;\n    eventsource.ontest = (event) => {\n      switch (count++) {\n        case 0: {\n          strictEqual(event.data, 'first');\n          break;\n        }\n        case 1: {\n          strictEqual(event.data, 'second');\n          eventsource.close();\n          resolve();\n          break;\n        }\n      }\n    };\n    await promise;\n  },\n};\n\nexport const retryTest = {\n  async test(ctrl, env) {\n    const eventsource = new EventSource('http://example.org/retry', {\n      fetcher: env.subrequest,\n    });\n    const { promise, resolve } = Promise.withResolvers();\n    let count = 0;\n    eventsource.onmessage = (event) => {\n      switch (count++) {\n        case 0: {\n          strictEqual(event.data, 'first');\n          break;\n        }\n        case 1: {\n          strictEqual(event.data, 'second');\n          break;\n        }\n        case 2: {\n          strictEqual(event.data, 'first');\n          break;\n        }\n        case 3: {\n          strictEqual(event.data, 'second');\n          eventsource.close();\n          resolve();\n          break;\n        }\n      }\n    };\n    await promise;\n  },\n};\n\nexport const constructorTest = {\n  test(ctrl, env) {\n    throws(() => new EventSource('not a valid url'), {\n      name: 'SyntaxError',\n      message:\n        \"Cannot open an EventSource to 'not a valid url'. The URL is invalid.\",\n    });\n\n    throws(() => new EventSource(123), {\n      name: 'SyntaxError',\n      message: \"Cannot open an EventSource to '123'. The URL is invalid.\",\n    });\n\n    throws(\n      () => new EventSource('http://example.org', { withCredentials: true }),\n      {\n        name: 'NotSupportedError',\n        message:\n          'The init.withCredentials option is not supported. It must be false or undefined.',\n      }\n    );\n\n    // Doesn't throw\n    new EventSource('http://example.org/message', {\n      fetcher: env.subrequest,\n    }).close();\n    new EventSource('http://example.org/message', {\n      fetcher: env.subrequest,\n      withCredentials: false,\n    }).close();\n    new EventSource('http://example.org/message', {\n      fetcher: env.subrequest,\n      withCredentials: undefined,\n    }).close();\n  },\n};\n\nexport const eventSourceFromTest = {\n  async test() {\n    const enc = new TextEncoder();\n    const chunks = ['data: first\\n\\n', 'data: second\\n\\n', 'data: third\\n\\n'];\n    const rs = new ReadableStream({\n      async pull(c) {\n        await scheduler.wait(10);\n        c.enqueue(enc.encode(chunks.shift()));\n        if (chunks.length === 0) {\n          c.close();\n        }\n      },\n    });\n    const { promise, resolve } = Promise.withResolvers();\n    const eventsource = EventSource.from(rs);\n    // Should happen three times\n    let count = 0;\n    eventsource.onmessage = (event) => {\n      switch (count++) {\n        case 0: {\n          strictEqual(event.data, 'first');\n          break;\n        }\n        case 1: {\n          strictEqual(event.data, 'second');\n          break;\n        }\n        case 2: {\n          strictEqual(event.data, 'third');\n          eventsource.close();\n          resolve();\n          break;\n        }\n      }\n    };\n    await promise;\n  },\n};\n\nexport const eventSourceFromWithBOMTest = {\n  async test() {\n    const enc = new TextEncoder();\n    // The first chunk is going to include the UTF-8 BOM, which should\n    // be ignored and filtered out by the parser.\n    const chunks = [\n      '\\uFEFFdata: first\\n\\n',\n      'data: second\\n\\n',\n      'data: third\\n\\n',\n    ];\n    const rs = new ReadableStream({\n      async pull(c) {\n        await scheduler.wait(10);\n        c.enqueue(enc.encode(chunks.shift()));\n        if (chunks.length === 0) {\n          c.close();\n        }\n      },\n    });\n    const { promise, resolve } = Promise.withResolvers();\n    const eventsource = EventSource.from(rs);\n    // Should happen three times\n    let count = 0;\n    eventsource.onmessage = (event) => {\n      switch (count++) {\n        case 0: {\n          strictEqual(event.data, 'first');\n          break;\n        }\n        case 1: {\n          strictEqual(event.data, 'second');\n          break;\n        }\n        case 2: {\n          strictEqual(event.data, 'third');\n          eventsource.close();\n          resolve();\n          break;\n        }\n      }\n    };\n    await promise;\n  },\n};\n\nexport const prototypePropertyTest = {\n  test() {\n    strictEqual(EventSource.prototype.constructor, EventSource);\n    strictEqual(EventSource.prototype.CLOSED, 2);\n    strictEqual(EventSource.prototype.CONNECTING, 0);\n    strictEqual(EventSource.prototype.OPEN, 1);\n    ok('onopen' in EventSource.prototype);\n    ok('onmessage' in EventSource.prototype);\n    ok('onerror' in EventSource.prototype);\n    ok('close' in EventSource.prototype);\n    ok('readyState' in EventSource.prototype);\n    ok('url' in EventSource.prototype);\n    ok('withCredentials' in EventSource.prototype);\n  },\n};\n\nexport const disposable = {\n  test() {\n    // EventSource is not defined by the spec as being disposable using ERM, but\n    // it makes sense to do so. The dispose operation simply defers to close()\n    const rs = new ReadableStream();\n    const eventsource = EventSource.from(rs);\n    strictEqual(eventsource.readyState, EventSource.OPEN);\n    eventsource[Symbol.dispose]();\n    strictEqual(eventsource.readyState, EventSource.CLOSED);\n  },\n};\n\n// ======================================================================================\n\nconst handlers = {\n  '/accept-event-stream': acceptEventStream,\n  '/cache-control-event-stream': cacheControlEventStream,\n  '/last-event-id': lastEventId,\n  '/event-id-persists': eventIdPersists,\n  '/event-id-resets': eventIdResets,\n  '/event-id-resets-2': eventIdResets2,\n  '/message': message,\n  '/reconnect-fail': reconnectFail,\n  '/status-error': statusError,\n  '/event': event,\n  '/retry': retry,\n};\n\nasync function acceptEventStream(request) {\n  return new Response(`data: ${request.headers.get('accept')}\\n\\n`, {\n    headers: {\n      'Content-Type': 'text/event-stream',\n      'Cache-Control': 'no-cache',\n    },\n  });\n}\n\nasync function cacheControlEventStream(request) {\n  return new Response(`data: ${request.headers.get('cache-control')}\\n\\n`, {\n    headers: {\n      'Content-Type': 'text/event-stream',\n      'Cache-Control': 'no-cache',\n    },\n  });\n}\n\nasync function lastEventId(request) {\n  const lastEventId = request.headers.get('last-event-id');\n  if (lastEventId == null) {\n    return new Response('id: 1\\ndata: first\\n\\n', {\n      headers: {\n        'Content-Type': 'text/event-stream',\n        'Cache-Control': 'no-cache',\n      },\n    });\n  } else {\n    return new Response(`data: ${lastEventId}\\n\\n`, {\n      headers: {\n        'Content-Type': 'text/event-stream',\n        'Cache-Control': 'no-cache',\n      },\n    });\n  }\n}\n\nasync function eventIdPersists(request) {\n  return new Response(\n    'id: 1\\ndata: first\\n\\n' +\n      'data: second\\n\\n' +\n      'id: 2\\ndata: third\\n\\n' +\n      'data: fourth\\n\\n',\n    {\n      headers: {\n        'Content-Type': 'text/event-stream',\n        'Cache-Control': 'no-cache',\n      },\n    }\n  );\n}\n\nasync function eventIdResets(request) {\n  return new Response('id: 1\\ndata: first\\n\\n' + 'id: \\ndata: second\\n\\n', {\n    headers: {\n      'Content-Type': 'text/event-stream',\n      'Cache-Control': 'no-cache',\n    },\n  });\n}\n\nasync function eventIdResets2(request) {\n  return new Response('id: 1\\ndata: first\\n\\n' + 'id\\ndata: second\\n\\n', {\n    headers: {\n      'Content-Type': 'text/event-stream',\n      'Cache-Control': 'no-cache',\n    },\n  });\n}\n\nasync function message(request) {\n  // The response payload contains a couple of messages with different structures.\n  // including good messages, comments, and bad fields.\n  return new Response(\n    'data: one\\n' +\n      'data: two\\n\\n' +\n      ': comment' +\n      'falsefield:msg\\n\\n' +\n      'falsefield:msg\\n' +\n      'Data: data\\n\\n' +\n      'data\\n\\n' +\n      'data:end\\n\\n',\n    {\n      headers: {\n        'Content-Type': 'text/event-stream',\n        'Cache-Control': 'no-cache',\n      },\n    }\n  );\n}\n\nlet reconnectTestCount = 0;\nasync function reconnectFail(request) {\n  switch (reconnectTestCount++) {\n    case 0: {\n      return new Response('data: opened\\n\\n', {\n        headers: {\n          'Content-Type': 'text/event-stream',\n          'Cache-Control': 'no-cache',\n        },\n      });\n    }\n    case 1: {\n      return new Response('data: reconnected\\n\\n', {\n        headers: {\n          'Content-Type': 'text/event-stream',\n          'Cache-Control': 'no-cache',\n        },\n      });\n    }\n    case 2:\n    // Fall-through\n    case 3: {\n      return new Response(null, {\n        headers: {\n          'Content-Type': 'text/event-stream',\n          'Cache-Control': 'no-cache',\n        },\n        status: 204,\n      });\n    }\n  }\n}\n\nasync function statusError(request) {\n  return new Response(null, {\n    status: 500,\n  });\n}\n\nasync function event(request) {\n  return new Response(\n    'event: test\\n' + 'data: first\\n\\n' + 'event: test\\n' + 'data: second\\n\\n',\n    {\n      headers: {\n        'Content-Type': 'text/event-stream',\n        'Cache-Control': 'no-cache',\n      },\n    }\n  );\n}\n\nasync function retry(request) {\n  return new Response(\n    'retry: 3000\\n\\n' + 'data: first\\n\\n' + 'data: second\\n\\n',\n    {\n      headers: {\n        'Content-Type': 'text/event-stream',\n        'Cache-Control': 'no-cache',\n      },\n    }\n  );\n}\n\nexport default {\n  async fetch(request) {\n    const url = new URL(request.url, 'http://example.org/');\n    const handler = handlers[url.pathname];\n    if (handler === undefined) {\n      throw new Error('Not found');\n    }\n    return await handler(request);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/eventsource-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"eventsource-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"eventsource-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"experimental\", \"streams_enable_constructors\"],\n        bindings = [\n          (name = \"subrequest\", service = \"eventsource-test\")\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/experimental-eval-test.js",
    "content": "import { strictEqual } from 'node:assert';\n\nstrictEqual(eval('1 + 1'), 2);\n\nexport const evalTest = {\n  test(_ctrl, env) {\n    strictEqual(eval('1 + 1'), 2);\n\n    const fn = new Function('a', 'b', 'return a + b;');\n    strictEqual(fn(2, 3), 5);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/experimental-eval-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  v8Flags = [\"--expose-gc\"],\n  services = [\n    ( name = \"experimental-eval-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"experimental-eval-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"allow_insecure_inefficient_logged_eval\",\n          \"experimental\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/fetch-redirect-test.js",
    "content": "import { WorkerEntrypoint } from 'cloudflare:workers';\nimport assert from 'node:assert';\n\nexport class Server extends WorkerEntrypoint {\n  async fetch(req) {\n    const endpoint = new URL(req.url).pathname;\n    switch (endpoint) {\n      case '/echo-authorization':\n        return new Response(\n          req.headers.get('authorization') ?? '<not provided>'\n        );\n\n      case '/redirect-same-origin':\n        return Response.redirect('http://api.example.com/echo-authorization');\n\n      case '/redirect-cross-origin':\n        return Response.redirect(\n          'http://totallynotahacker.com/echo-authorization'\n        );\n    }\n  }\n}\n\nexport const sameOriginRedirectPreservesAuthorization = {\n  async test(ctrl, env, ctx) {\n    const res = await env.Server.fetch(\n      'http://api.example.com/redirect-same-origin',\n      { headers: { Authorization: 's00persecret' } }\n    );\n    assert.strictEqual(await res.text(), 's00persecret');\n  },\n};\n\nexport const crossOriginRedirectStripsAuthorization = {\n  async test(ctrl, env, ctx) {\n    const res = await env.Server.fetch(\n      'http://api.example.com/redirect-cross-origin',\n      { headers: { Authorization: 's00persecret' } }\n    );\n    assert.strictEqual(await res.text(), '<not provided>');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/fetch-redirect-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"fetch-redirect-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"fetch-redirect-test.js\" )\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"strip_authorization_on_cross_origin_redirect\", \"experimental\"],\n        bindings = [\n          (name = \"Server\", service = (name = \"fetch-redirect-test\", entrypoint = \"Server\")),\n          (name = \"defaultExport\", service = \"fetch-redirect-test\"),\n        ]\n      )\n    )\n  ]\n);\n\n"
  },
  {
    "path": "src/workerd/api/tests/fetch-test.js",
    "content": "import { strictEqual, throws } from 'assert';\n\n// Test depends on the setting of the upper_case_all_http_methods compatibility flag.\nstrictEqual(\n  globalThis.Cloudflare.compatibilityFlags['upper_case_all_http_methods'],\n  true\n);\n\nexport const test = {\n  test() {\n    // Verify that lower-cased method names are converted to upper-case.\n    // even though the Fetch API doesn't do this in general for all methods.\n    // Note that the upper_case_all_http_methods compat flag is intentionally\n    // diverging from the Fetch API here.\n    const req = new Request('https://example.com', { method: 'patch' });\n    strictEqual(req.method, 'PATCH');\n\n    // Unrecognized methods error as expected, with the error message\n    // showing the original-cased method name.\n    throws(() => new Request('http://example.org', { method: 'patchy' }), {\n      message: /^Invalid HTTP method string: patchy$/,\n    });\n  },\n};\n\nexport const fetchGen = {\n  async test() {\n    const enc = new TextEncoder();\n    async function* gen() {\n      yield enc.encode('Hello ');\n      yield enc.encode('World!');\n    }\n    const resp = new Response(gen());\n    strictEqual(await resp.text(), 'Hello World!');\n\n    const req = new Request('http://example.com', {\n      method: 'POST',\n      body: gen(),\n    });\n    strictEqual(await req.text(), 'Hello World!');\n\n    const resp2 = new Response([enc.encode('Hello '), enc.encode('World!')]);\n    strictEqual(await resp2.text(), 'Hello World!');\n\n    // Boxed strings work correctly.\n    const resp3 = new Response(new String('Hello'));\n    strictEqual(await resp3.text(), 'Hello');\n\n    // Regular strings work correctly.\n    const resp4 = new Response('Hello');\n    strictEqual(await resp4.text(), 'Hello');\n\n    const objIter = {\n      *[Symbol.iterator]() {\n        yield enc.encode('Hello ');\n        yield enc.encode('Object Iterator!');\n      },\n    };\n    const respIter = new Response(objIter);\n    strictEqual(await respIter.text(), 'Hello Object Iterator!');\n\n    // Custom toString prevents iterator treatment.\n    const obj = {\n      *[Symbol.iterator]() {\n        yield enc.encode('ignored');\n      },\n      toString() {\n        return 'Hello';\n      },\n    };\n    const resp5 = new Response(obj);\n    strictEqual(await resp5.text(), 'Hello');\n\n    class MyObj {\n      *[Symbol.iterator]() {\n        yield enc.encode('ignored');\n      }\n      toString() {\n        return 'Hello';\n      }\n    }\n    const resp6 = new Response(new MyObj());\n    strictEqual(await resp6.text(), 'Hello');\n\n    class Base {\n      toString() {\n        return 'Hello';\n      }\n    }\n    class SubObj extends Base {\n      *[Symbol.iterator]() {\n        yield enc.encode('ignored');\n      }\n    }\n    const resp7 = new Response(new SubObj());\n    strictEqual(await resp7.text(), 'Hello');\n\n    // Also custom toPrimitive prevents iterator treatment.\n    const objPrim = {\n      *[Symbol.iterator]() {\n        yield enc.encode('ignored');\n      },\n      [Symbol.toPrimitive]() {\n        return 'Hello';\n      },\n    };\n    const respPrim = new Response(objPrim);\n    strictEqual(await respPrim.text(), 'Hello');\n\n    // Unless it's specifically an async iterator.\n    const asyncIter = {\n      async *[Symbol.asyncIterator]() {\n        yield enc.encode('Hello ');\n        yield enc.encode('Async World!');\n      },\n      toString() {\n        return 'ignored';\n      },\n    };\n    const resp8 = new Response(asyncIter);\n    strictEqual(await resp8.text(), 'Hello Async World!');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/fetch-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"fetch-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"fetch-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat_v2\",\n          \"upper_case_all_http_methods\",\n          \"fetch_iterable_type_support\",\n          # Note that the override adjustment flag will be enabled by default\n          # when fetch_iterable_type_support is enabled after 2026-01-15.\n          \"fetch_iterable_type_support_override_adjustment\",\n          \"streams_enable_constructors\",\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/form-data-legacy-test.js",
    "content": "export const sendFilesInFormdata = {\n  async test() {\n    const INPUT = `--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\nContent-Disposition: form-data; name=\"foo\"; filename=\"foo.txt\"\nContent-Type: application/octet-stream\n\nfoo-content\n--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\nContent-Disposition: form-data; name=\"bar\"; filename=\"bar-renamed.txt\"\nContent-Type: application/octet-stream\n\nbar1-content\n--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\nContent-Disposition: form-data; name=\"bar\"; filename=\"bar2.txt\"\nContent-Type: text/bary\n\nbar2-content\n--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\nContent-Disposition: form-data; name=\"baz\"; filename=\"baz\"\nContent-Type: text/bazzy\n\nbaz-content\n--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\nContent-Disposition: form-data; name=\"qux\"; filename=\"qux%0A%22\\\\.txt\"\nContent-Type: application/octet-stream\n\nqux-content\n--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a--\n`;\n\n    const req = new Request('https://example.org', {\n      method: 'POST',\n      body: INPUT,\n      headers: {\n        'content-type':\n          'multipart/form-data;boundary=2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a',\n      },\n    });\n\n    const form = await req.formData();\n\n    form.set('foo', new File(['foo-content'], 'foo.txt'));\n    form.append(\n      'bar',\n      new File(['bar1-content'], 'bar1.txt'),\n      'bar-renamed.txt'\n    );\n    form.append(\n      'bar',\n      new File(['bar2-content'], 'bar2.txt', { type: 'text/bary' })\n    );\n    form.append('baz', new Blob(['baz-content'], { type: 'text/bazzy' }));\n    form.set('qux', new Blob(['qux-content']), 'qux\\n\"\\\\.txt');\n\n    {\n      let resp = new Response(form);\n      let text = await resp.text();\n      let roundtrip = await new Response(text, {\n        headers: resp.headers,\n      }).formData();\n      if (roundtrip.get('foo') != 'foo-content') {\n        throw new Error(\n          'expected round-trip turns into string (wrong, but backwards-compatible)'\n        );\n      }\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/form-data-legacy-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTestsLegacy :Workerd.Config = (\n  services = [\n    ( name = \"form-data-legacy-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"form-data-legacy-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"formdata_parser_converts_files_to_strings\"\n        ],\n      )\n    ),\n  ],\n);\n\n"
  },
  {
    "path": "src/workerd/api/tests/form-data-test-ts.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { strictEqual } from 'node:assert';\n\n// https://github.com/cloudflare/workerd/issues/5934\nexport const formDataUnionTypeOverloads = {\n  test() {\n    const formData = new FormData();\n\n    formData.append('stringKey', 'stringValue' as string | Blob);\n    strictEqual(formData.get('stringKey'), 'stringValue');\n\n    // We explicitly use string | Blob to test types.\n    const blob: string | Blob = new Blob(['blob content'], {\n      type: 'text/plain',\n    });\n    formData.append('blobKey', blob);\n    strictEqual(formData.get('blobKey') instanceof File, true);\n\n    const value: string | Blob = 'setValue';\n    formData.set('setStringKey', value);\n    strictEqual(formData.get('setStringKey'), 'setValue');\n\n    formData.set('setBlobKey', blob);\n    strictEqual(formData.get('setBlobKey') instanceof File, true);\n\n    const values: Array<{ key: string; value: string | Blob }> = [\n      { key: 'key1', value: 'string value' },\n      { key: 'key2', value: new Blob(['blob']) },\n    ];\n\n    const formData2 = new FormData();\n    for (const { key, value } of values) {\n      if (typeof value === 'string' || value instanceof Blob) {\n        formData2.append(key, value);\n      }\n    }\n\n    strictEqual(formData2.get('key1'), 'string value');\n    strictEqual(formData2.get('key2') instanceof File, true);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/form-data-test-ts.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"form-data-test-ts\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"form-data-test-ts.js\" )\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"formdata_parser_supports_files\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/form-data-test.js",
    "content": "import {\n  strictEqual,\n  deepStrictEqual,\n  notStrictEqual,\n  rejects,\n  throws,\n} from 'node:assert';\n\nexport const apiFormDataParse = {\n  async test(ctrl, env) {\n    const INPUT = `---\nContent-Disposition: form-data; name=\"field0\"\n\npart0\n---\nContent-Disposition: form-data; name=\"field1\"\n\npart1\n---\nContent-Disposition: form-data; name=\"field0\"\n\npart2\n---\nContent-Disposition: form-data; name=\"field1\"\n\npart3\n-----`;\n\n    const req = new Request('https://example.com', {\n      method: 'POST',\n      body: INPUT,\n      headers: {\n        'content-type': 'multipart/form-data; Boundary=\"-\"',\n      },\n    });\n\n    const formData = await req.formData();\n\n    deepStrictEqual(formData.getAll('field0'), ['part0', 'part2']);\n    deepStrictEqual(formData.getAll('field1'), ['part1', 'part3']);\n  },\n};\n\nexport const invalidFormdataContentDisposition = {\n  async test() {\n    const INPUT = `--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\nContent-Disposition: foobar\nContent-Type: application/octet-stream\n\nfoo-content\n--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a--\n`;\n\n    const req = new Request('https://example.org', {\n      method: 'POST',\n      body: INPUT,\n      headers: {\n        'content-type':\n          'multipart/form-data;boundary=2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a',\n      },\n    });\n\n    try {\n      await req.formData();\n      throw new Error('Parsing the form data should have thrown');\n    } catch (err) {\n      strictEqual(\n        err.message,\n        'Content-Disposition header for FormData part must ' +\n          'have the value \"form-data\", possibly followed by ' +\n          'parameters. Got: \"foobar\"'\n      );\n    }\n  },\n};\n\nexport const invalidFormData = {\n  async test() {\n    const form = new FormData();\n    form.set('foo', new File(['foo-content'], 'foo.txt\\\\'));\n    try {\n      new Request('http://example.org', {\n        method: 'POST',\n        body: form,\n      });\n      throw new Error('should have thrown');\n    } catch (err) {\n      strictEqual(err.message, \"Name or filename can't end with backslash\");\n    }\n  },\n};\n\nexport const formDataWithFilesBlobs = {\n  async test() {\n    const INPUT = `--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\nContent-Disposition: form-data; name=\"foo\"; filename=\"foo.txt\"\nContent-Type: application/octet-stream\n\nfoo-content\n--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\nContent-Disposition: form-data; name=\"bar\"; filename=\"bar-renamed.txt\"\nContent-Type: application/octet-stream\n\nbar1-content\n--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\nContent-Disposition: form-data; name=\"bar\"; filename=\"bar2.txt\"\nContent-Type: text/bary\n\nbar2-content\n--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\nContent-Disposition: form-data; name=\"baz\"; filename=\"baz\"\nContent-Type: text/bazzy\n\nbaz-content\n--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\nContent-Disposition: form-data; name=\"qux\"; filename=\"qux%0A%22\\\\\\\\.txt\"\nContent-Type: application/octet-stream\n\nqux-content\n--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a--\n`;\n\n    const req = new Request('https://example.org', {\n      method: 'POST',\n      body: INPUT,\n      headers: {\n        'content-type':\n          'multipart/form-data;boundary=2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a',\n      },\n    });\n\n    async function assertFile(file, name, type, content) {\n      if (!(file instanceof File)) {\n        throw new Error('not a File: ' + file);\n      }\n\n      strictEqual(name, file.name);\n      strictEqual(type, file.type);\n      strictEqual(content, await file.text());\n    }\n\n    const form = await req.formData();\n    await assertFile(\n      form.get('foo'),\n      'foo.txt',\n      'application/octet-stream',\n      'foo-content'\n    );\n    await assertFile(\n      form.getAll('bar')[0],\n      'bar-renamed.txt',\n      'application/octet-stream',\n      'bar1-content'\n    );\n    await assertFile(\n      form.getAll('bar')[1],\n      'bar2.txt',\n      'text/bary',\n      'bar2-content'\n    );\n    await assertFile(form.get('baz'), 'baz', 'text/bazzy', 'baz-content');\n    await assertFile(\n      form.get('qux'),\n      'qux%0A%22\\\\.txt',\n      'application/octet-stream',\n      'qux-content'\n    );\n  },\n};\n\nexport const sendFilesInFormdata = {\n  async test() {\n    const INPUT = `--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\nContent-Disposition: form-data; name=\"foo\"; filename=\"foo.txt\"\nContent-Type: application/octet-stream\n\nfoo-content\n--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\nContent-Disposition: form-data; name=\"bar\"; filename=\"bar-renamed.txt\"\nContent-Type: application/octet-stream\n\nbar1-content\n--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\nContent-Disposition: form-data; name=\"bar\"; filename=\"bar2.txt\"\nContent-Type: text/bary\n\nbar2-content\n--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\nContent-Disposition: form-data; name=\"baz\"; filename=\"baz\"\nContent-Type: text/bazzy\n\nbaz-content\n--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\nContent-Disposition: form-data; name=\"qux\"; filename=\"qux%0A%22\\\\.txt\"\nContent-Type: application/octet-stream\n\nqux-content\n--2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a--\n`;\n\n    const req = new Request('https://example.org', {\n      method: 'POST',\n      body: INPUT,\n      headers: {\n        'content-type':\n          'multipart/form-data;boundary=2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a',\n      },\n    });\n\n    const form = await req.formData();\n\n    form.set('foo', new File(['foo-content'], 'foo.txt'));\n    form.append(\n      'bar',\n      new File(['bar1-content'], 'bar1.txt'),\n      'bar-renamed.txt'\n    );\n    form.append(\n      'bar',\n      new File(['bar2-content'], 'bar2.txt', { type: 'text/bary' })\n    );\n    form.append('baz', new Blob(['baz-content'], { type: 'text/bazzy' }));\n    form.set('qux', new Blob(['qux-content']), 'qux\\n\"\\\\.txt');\n\n    if (!(form.get('foo') instanceof File)) {\n      throw new Error('expected file');\n    }\n    if (form.get('foo').name != 'foo.txt') {\n      throw new Error('expected file name foo.txt');\n    }\n    if (!(form.getAll('bar')[1] instanceof File)) {\n      throw new Error('expected files');\n    }\n  },\n};\n\nasync function parseFormData(contentType, text) {\n  const req = new Request('http://example.org', {\n    method: 'POST',\n    body: text,\n    headers: {\n      'content-type': contentType,\n    },\n  });\n  return await req.formData();\n}\n\nexport const testFormDataParser = {\n  async test() {\n    const successCases = [\n      {\n        // No parts. Note that Chrome throws a TypeError on this input, but it'll generate output that\n        // looks like this if you ask it to serialize an empty form.\n        contentType: 'multipart/form-data; boundary=\"+\"',\n        body: '--+--',\n        expected: '',\n        comment: 'Empty form is okay',\n      },\n      {\n        contentType: 'multipart/form-data; boundary=\"+\"',\n        body: [\n          // CRLF after boundary, CRLFCRLF after header, CRLF after message\n          '--+\\r\\n',\n          'Content-Disposition: form-data; name=\"field0\"\\r\\n',\n          '\\r\\n',\n          'part0\\r\\n',\n\n          // LF after boundary, CRLFLF after header, tabs in header, LF after message\n          '--+\\n',\n          'CONTENT-DISPOSITION:\\tform-data\\t;\\tname=\"field1\"\\r\\n',\n          '\\n',\n          'part1\\n',\n\n          // LFCRLF after header, no OWS in header, Content-Type header above disposition\n          '--+\\r\\n',\n          'content-disposition:form-data;name=\"field0\"\\n',\n          '\\r\\n',\n          'part2\\r\\n',\n\n          // LFCRLF after header\n          '--+\\r\\n',\n          'CoNTent-dIsposiTIOn: form-data; name=\"field1\"\\n',\n          '\\r\\n',\n          'part3\\r\\n',\n\n          '--+--',\n        ].join(''),\n        expected: 'field0=part0,field1=part1,field0=part2,field1=part3',\n        comment: 'Mixed CRLF and LF, case-insensitivity of header name',\n      },\n      {\n        contentType: 'multipart/form-data; boundary=\"+\"',\n        body: [\n          // CRLFCRLF after header, empty message with CRLF\n          '--+\\r\\n',\n          'Content-Disposition: form-data; name=\"empties\"\\r\\n',\n          '\\r\\n',\n          '\\r\\n',\n\n          // CRLFCRLF after header, empty message with LF\n          '--+\\r\\n',\n          'Content-Disposition: form-data; name=\"empties\"\\r\\n',\n          '\\r\\n',\n          '\\n',\n\n          // CRLFLF after header, empty message with CRLF\n          '--+\\r\\n',\n          'Content-Disposition: form-data; name=\"empties\"\\r\\n',\n          '\\n',\n          '\\r\\n',\n\n          // CRLFLF after header, empty message with LF\n          '--+\\r\\n',\n          'Content-Disposition: form-data; name=\"empties\"\\r\\n',\n          '\\n',\n          '\\n',\n\n          // LFCRLF after header, empty message with CRLF\n          '--+\\r\\n',\n          'Content-Disposition: form-data; name=\"empties\"\\n',\n          '\\r\\n',\n          '\\r\\n',\n\n          // LFCRLF after header, empty message with LF\n          '--+\\r\\n',\n          'Content-Disposition: form-data; name=\"empties\"\\n',\n          '\\r\\n',\n          '\\n',\n\n          // LFLF after header, empty message with CRLF\n          '--+\\r\\n',\n          'Content-Disposition: form-data; name=\"empties\"\\n',\n          '\\n',\n          '\\r\\n',\n\n          // LFLF after header, empty message with LF\n          '--+\\r\\n',\n          'Content-Disposition: form-data; name=\"empties\"\\n',\n          '\\n',\n          '\\n',\n\n          '--+--',\n        ].join(''),\n        expected:\n          'empties=,empties=,empties=,empties=,empties=,empties=,empties=,empties=',\n        comment: 'Mixed CRLF and LF with empty messages',\n      },\n      {\n        contentType: 'multipart/form-data; boundary=\"+\"',\n        body: [\n          '--+\\r\\n',\n          'Content-Type: text/plain; charset=utf-8\\r\\n',\n          'Content-Disposition: form-data; name=\"field0\"\\r\\n',\n          '\\r\\n',\n          'part0\\r\\n',\n\n          '--+\\r\\n',\n          'Content-Disposition: form-data; name=\"field1\"\\r\\n',\n          'Content-Type: text/plain; charset=utf-8\\r\\n',\n          '\\r\\n',\n          'part1\\r\\n',\n\n          '--+--',\n        ].join(''),\n        expected: 'field0=part0,field1=part1',\n        comment: 'Content-Type header should be okay',\n      },\n      {\n        contentType: 'application/x-www-form-urlencoded',\n        body: [\n          'field0=part0',\n          'field1=part1',\n          'field0=part2',\n          'field1=part3',\n        ].join('&'),\n        expected: 'field0=part0,field1=part1,field0=part2,field1=part3',\n        comment: 'Basic application/x-www-form-urlencoded parse works',\n      },\n      {\n        contentType: 'application/x-www-form-urlencoded',\n        body: ['field0=data+with+an+%26+in+it'].join('&'),\n        expected: 'field0=data with an & in it',\n        comment:\n          'application/x-www-form-urlencoded data gets percent-and-plus-decoded',\n      },\n      {\n        contentType: 'application/x-www-form-urlencoded',\n        body: ['', '=', 'field1', '=part2', 'field1='].join('&'),\n        expected: '=,field1=,=part2,field1=',\n        comment:\n          'application/x-www-form-urlencoded data with awkward &, = placement',\n      },\n    ];\n\n    for (let i = 0; i < successCases.length; ++i) {\n      const c = successCases[i];\n      const fd = await parseFormData(c.contentType, c.body);\n      const actual = [];\n      for (let [k, v] of fd) {\n        actual.push(`${k}=${v}`);\n      }\n      strictEqual(actual.join(','), c.expected);\n    }\n\n    let failureCases = [\n      {\n        contentType: 'multipart/form-data; boundary=\"+\"',\n        body: '',\n        comment: 'Empty body throws',\n      },\n      {\n        contentType: 'multipart/form-data; boundary=\"+\"',\n        body: '--asdf--',\n        comment: 'Bad boundary throws',\n      },\n      {\n        contentType: 'multipart/form-data; boundary=\"+\"',\n        body: '--+',\n        comment: 'Non-terminal boundary at end throws',\n      },\n      {\n        contentType: 'multipart/form-data; boundary=\"+\"',\n        body: [\n          '--+\\r\\n',\n          'Content-Disposition: form-data; name=\"field0\"\\r\\n',\n          '\\r\\n',\n          'part0\\r\\n',\n          '-+--',\n        ].join(''),\n        comment: 'Bad terminal delimiter',\n      },\n      {\n        contentType: 'multipart/form-data; boundary=\"+\"',\n        body: [\n          '--+\\r\\n',\n          'Bad-Content-Disposition: form-data; name=\"field0\"\\r\\n',\n          '\\r\\n',\n          'part0\\r\\n',\n          '--+--',\n        ].join(''),\n        comment: 'Bad Content-Disposition header',\n      },\n      {\n        contentType: 'multipart/form-data; boundary=\"+\"',\n        body: [\n          '--+\\r\\n',\n          'Content-Disposition-Bad: form-data; name=\"field0\"\\r\\n',\n          '\\r\\n',\n          'part0\\r\\n',\n          '--+--',\n        ].join(''),\n        comment: 'Bad Content-Disposition header',\n      },\n      {\n        contentType: 'multipart/form-data; boundary=\"+\"',\n        body: [\n          '--+\\r\\n',\n          'Content-Disposition: form-data; name=\"field0\"\\r\\n',\n          '\\r',\n          'part0\\r\\n',\n          '--+--',\n        ].join(''),\n        comment: 'No header termination CRLFCRLF',\n      },\n      {\n        contentType: 'multipart/form-data; boundary=\"+\"',\n        body: [\n          '--+\\r\\n',\n          'Content-Disposition: form-data; name=\"field0\"\\r\\n',\n          '\\r\\n',\n          'part0\\r\\n',\n        ].join(''),\n        comment: 'No subsequent boundary string',\n      },\n      {\n        contentType: 'multipart/form-data; boundary=\"+\"',\n        body: [\n          '--+\\r\\n',\n          'Content-Disposition: form-data; name=\"field0\"\\r\\n',\n          '\\r\\n',\n          'part0\\r\\n',\n          '--+\\r--',\n        ].join(''),\n        comment: \"Boundary was not succeeded by CRLF, LF, or '--'\",\n      },\n      {\n        contentType: 'multipart/form-data; boundary=',\n        body: '----',\n        comment: 'Empty boundary parameter in content-type',\n      },\n      {\n        contentType: 'application/x-www-form-urlencoded; charset=big5',\n        body: '--+--',\n        comment: 'Unsupported charset',\n      },\n    ];\n\n    for (let i = 0; i < failureCases.length; ++i) {\n      const c = failureCases[i];\n      await rejects(() => parseFormData(c.contentType, c.body));\n    }\n  },\n};\n\nexport const testFormDataSerializer = {\n  async test() {\n    // Test the serializer by making a round trip through our serializer and parser.\n\n    const expected = new FormData();\n    expected.append('field0', 'part0');\n    expected.append('field1', 'part1');\n    expected.append('field0', 'part2');\n    expected.append('field1', 'part3');\n    expected.append('field-with-a-\"-in-it', 'part4');\n\n    // Serialize the FormData.\n    const response = new Response(expected);\n\n    // Parse it back.\n    // This regex assumes an unquoted boundary, which is true for our serializer.\n    const boundary = /boundary=(.+)$/.exec(\n      response.headers.get('Content-Type')\n    )[1];\n    const actual = await parseFormData(\n      `multipart/form-data; boundary=\"${boundary}\"`,\n      await response.text()\n    );\n\n    const expectedData = [\n      'field0=part0',\n      'field1=part1',\n      'field0=part2',\n      'field1=part3',\n      'field-with-a-%22-in-it=part4',\n    ];\n    const actualData = [];\n    for (let [k, v] of actual) {\n      actualData.push(`${k}=${v}`);\n    }\n\n    strictEqual('' + actualData.join(','), '' + expectedData.join(','));\n  },\n};\n\nexport const testFormDataSet = {\n  test() {\n    const fd = new FormData();\n    fd.append('foo', '0');\n    fd.append('foo', '1');\n    fd.append('foo', '2');\n    fd.append('foo', '3');\n    fd.append('foo', '4');\n    fd.append('foo', '5');\n    fd.set('foo', '6');\n\n    strictEqual('' + fd.getAll('foo'), '6');\n  },\n};\n\nexport const testFormDataIterators = {\n  test() {\n    const fd = new FormData();\n    const entry = fd.entries();\n    const key = fd.keys();\n    const value = fd.values();\n    strictEqual(entry.next().value, undefined);\n    strictEqual(key.next().value, undefined);\n    strictEqual(value.next().value, undefined);\n\n    fd.append('key', '0');\n    fd.append('key', '1');\n    strictEqual('' + entry.next().value, 'key,0');\n    strictEqual('' + key.next().value, 'key');\n    strictEqual('' + value.next().value, '0');\n\n    fd.delete('key');\n    strictEqual(entry.next().value, undefined);\n    strictEqual(key.next().value, undefined);\n    strictEqual(value.next().value, undefined);\n  },\n};\n\nexport const testFormDataForeach = {\n  test() {\n    const fd = new FormData();\n\n    fd.forEach(function (v, k, t) {\n      throw new Error('should not be called on empty array');\n    });\n\n    let foreachOutput = [];\n    fd.append('key1', 'value1');\n    fd.append('key2', 'value2');\n\n    let i = 0;\n    fd.forEach(function (value, key, captureFd) {\n      notStrictEqual(value, '3'); // if this is true, then the test is useless\n      // updating the headers should affect them immediately when not called through forEach\n      captureFd.set(key, '3');\n      strictEqual(captureFd.get(key), '3');\n      // updating the headers should not affect `value`\n      notStrictEqual(value, '3');\n      foreachOutput.push(`${key}=${value}`);\n\n      captureFd.append('some-key', '4');\n      // console.log(\"appended\");\n      i += 1;\n    });\n\n    // appending keys within the loop should call the callback on the new items\n    strictEqual(i, 4);\n    strictEqual(\n      '' + foreachOutput.join('&'),\n      'key1=value1&key2=value2&some-key=4&some-key=4'\n    );\n    // `capture_headers.set` should affect the outer headers object\n    strictEqual(fd.get('key1'), '3');\n    strictEqual(fd.get('key2'), '3');\n    // `capture_headers.append` should affect the outer object\n    deepStrictEqual(fd.getAll('some-key'), ['3', '4']);\n\n    throws(() => fd.forEach());\n    throws(() => fd.forEach(1));\n\n    // `this` can be overridden by setting the second argument\n    fd.forEach(function () {\n      // NOTE: can't use `assert_equals` because `this` has type `object` which apparently it doesn't like\n      strictEqual(this, 1);\n    }, 1);\n\n    throws(() => {\n      fd.forEach(function () {\n        throw new Error('boo');\n      });\n    });\n\n    // forEach should not move the value\n    fd.set('key1', 'a');\n    fd.forEach(() => {});\n    strictEqual(fd.get('key1'), 'a');\n  },\n};\n\nexport const w3cTestFormDataAppend = {\n  test() {\n    function test_formdata(creator, verifier, description) {\n      let result = [];\n      for (let [k, v] of creator()) {\n        result.push(`${k}=${v}`);\n      }\n      verifier(result.join(','));\n    }\n\n    test_formdata(\n      function () {\n        var fd = new FormData();\n        fd.append('name', new String('value'));\n        return fd;\n      },\n      function (data) {\n        strictEqual(data, 'name=value');\n      },\n      'Passing a String object to FormData.append should work.'\n    );\n\n    strictEqual(create_formdata(['key', 'value1']).get('key'), 'value1');\n    strictEqual(\n      create_formdata(['key', 'value2'], ['key', 'value1']).get('key'),\n      'value2'\n    );\n    strictEqual(create_formdata(['key', undefined]).get('key'), 'undefined');\n    strictEqual(\n      create_formdata(['key', undefined], ['key', 'value1']).get('key'),\n      'undefined'\n    );\n    strictEqual(create_formdata(['key', null]).get('key'), 'null');\n    strictEqual(\n      create_formdata(['key', null], ['key', 'value1']).get('key'),\n      'null'\n    );\n\n    function create_formdata() {\n      var fd = new FormData();\n      for (var i = 0; i < arguments.length; i++) {\n        fd.append.apply(fd, arguments[i]);\n      }\n      return fd;\n    }\n  },\n};\n\nexport const w3cTestFormDataBlob = {\n  test() {\n    function create_formdata() {\n      var fd = new FormData();\n      for (var i = 0; i < arguments.length; i++) {\n        fd.append.apply(fd, arguments[i]);\n      }\n      return fd;\n    }\n\n    throws(() => create_formdata('a', 'b', 'c'));\n  },\n};\n\nexport const w3cTestFormDataDelete = {\n  test() {\n    {\n      var fd = create_formdata(['key', 'value1'], ['key', 'value2']);\n      fd.delete('key');\n      strictEqual(fd.get('key'), null);\n    }\n\n    {\n      var fd = create_formdata(['key', 'value1'], ['key', 'value2']);\n      fd.delete('nil');\n      strictEqual(fd.get('key'), 'value1');\n    }\n\n    {\n      var fd = create_formdata(['key1', 'value1'], ['key2', 'value2']);\n      fd.delete('key1');\n      strictEqual(fd.get('key1'), null);\n      strictEqual(fd.get('key2'), 'value2');\n    }\n\n    function create_formdata() {\n      var fd = new FormData();\n      for (var i = 0; i < arguments.length; i++) {\n        fd.append.apply(fd, arguments[i]);\n      }\n      return fd;\n    }\n  },\n};\n\nexport const w3cTestFormDataForeach = {\n  test() {\n    var fd = new FormData();\n    fd.append('n1', 'v1');\n    fd.append('n2', 'v2');\n    fd.append('n3', 'v3');\n    fd.append('n1', 'v4');\n    fd.append('n2', 'v5');\n    fd.append('n3', 'v6');\n    fd.delete('n2');\n    var expected_keys = ['n1', 'n3', 'n1', 'n3'];\n    var expected_values = ['v1', 'v3', 'v4', 'v6'];\n    // TODO(soon): Test with this File object.\n    //var file = new File(['hello'], \"hello.txt\");\n    //fd.append('f1', file);\n    //var expected_keys = ['n1', 'n3', 'n1', 'n3', 'f1'];\n    //var expected_values = ['v1', 'v3', 'v4', 'v6', file];\n\n    {\n      var mykeys = [],\n        myvalues = [];\n      for (var entry of fd) {\n        strictEqual(entry.length, 2);\n        mykeys.push(entry[0]);\n        myvalues.push(entry[1]);\n      }\n      deepStrictEqual(mykeys, expected_keys);\n      deepStrictEqual(myvalues, expected_values);\n    }\n\n    {\n      var mykeys = [],\n        myvalues = [];\n      for (var entry of fd.entries()) {\n        strictEqual(\n          entry.length,\n          2,\n          'entries() iterator should yield key/value pairs'\n        );\n        mykeys.push(entry[0]);\n        myvalues.push(entry[1]);\n      }\n      deepStrictEqual(\n        mykeys,\n        expected_keys,\n        'entries() iterator should see duplicate keys'\n      );\n      deepStrictEqual(\n        myvalues,\n        expected_values,\n        'entries() iterator should see non-deleted values'\n      );\n    }\n\n    {\n      var mykeys = [];\n      for (var entry of fd.keys()) mykeys.push(entry);\n      deepStrictEqual(mykeys, expected_keys);\n    }\n\n    {\n      var myvalues = [];\n      for (var entry of fd.values()) myvalues.push(entry);\n      deepStrictEqual(\n        myvalues,\n        expected_values,\n        'values() iterator should see non-deleted values'\n      );\n    }\n  },\n};\n\nexport const w3cTestFormDataGet = {\n  test() {\n    strictEqual(\n      create_formdata(['key', 'value1'], ['key', 'value2']).get('key'),\n      'value1'\n    );\n    strictEqual(\n      create_formdata(['key', 'value1'], ['key', 'value2']).get('nil'),\n      null\n    );\n    strictEqual(create_formdata().get('key'), null);\n    deepStrictEqual(\n      create_formdata(['key', 'value1'], ['key', 'value2']).getAll('key'),\n      ['value1', 'value2']\n    );\n    deepStrictEqual(\n      create_formdata(['key', 'value1'], ['key', 'value2']).getAll('nil'),\n      []\n    );\n    deepStrictEqual(create_formdata().getAll('key'), []);\n\n    function create_formdata() {\n      var fd = new FormData();\n      for (var i = 0; i < arguments.length; i++) {\n        fd.append.apply(fd, arguments[i]);\n      }\n      return fd;\n    }\n  },\n};\n\nexport const w3cTestFormDataHas = {\n  test() {\n    strictEqual(\n      create_formdata(['key', 'value1'], ['key', 'value2']).has('key'),\n      true\n    );\n    strictEqual(\n      create_formdata(['key', 'value1'], ['key', 'value2']).has('nil'),\n      false\n    );\n    strictEqual(create_formdata().has('key'), false);\n\n    function create_formdata() {\n      var fd = new FormData();\n      for (var i = 0; i < arguments.length; i++) {\n        fd.append.apply(fd, arguments[i]);\n      }\n      return fd;\n    }\n  },\n};\n\nexport const w3cTestFormDataSet = {\n  test() {\n    function test_formdata(creator, verifier, description) {\n      let result = [];\n      for (let [k, v] of creator()) {\n        result.push(`${k}=${v}`);\n      }\n      verifier(result.join(','));\n    }\n\n    test_formdata(\n      function () {\n        var fd = new FormData();\n        fd.set('name', new String('value'));\n        return fd;\n      },\n      function (data) {\n        strictEqual(data, 'name=value');\n      },\n      'Passing a String object to FormData.set should work'\n    );\n\n    strictEqual(create_formdata(['key', 'value1']).get('key'), 'value1');\n    strictEqual(\n      create_formdata(['key', 'value2'], ['key', 'value1']).get('key'),\n      'value1'\n    );\n    strictEqual(create_formdata(['key', undefined]).get('key'), 'undefined');\n    strictEqual(\n      create_formdata(['key', undefined], ['key', 'value1']).get('key'),\n      'value1'\n    );\n    strictEqual(create_formdata(['key', null]).get('key'), 'null');\n    strictEqual(\n      create_formdata(['key', null], ['key', 'value1']).get('key'),\n      'value1'\n    );\n\n    // TODO(conform): Support File/Blob.\n    //test(function () {\n    //  var fd = new FormData();\n    //  fd.set('key', new Blob([]), 'blank.txt');\n    //  var file = fd.get('key');\n    //  assert_true(file instanceof File);\n    //  assert_equals(file.name, 'blank.txt');\n    //}, 'testFormDataSetEmptyBlob');\n\n    function create_formdata() {\n      var fd = new FormData();\n      for (var i = 0; i < arguments.length; i++) {\n        fd.set.apply(fd, arguments[i]);\n      }\n      return fd;\n    }\n  },\n};\n\nexport const w3cTestFormData = {\n  test() {\n    function do_test(name, fd, expected) {\n      let result = [];\n      for (let [k, v] of fd) {\n        result.push(`${k}=${v}`);\n      }\n      strictEqual(result.join(','), expected, name);\n    }\n\n    function create_formdata() {\n      var fd = new FormData();\n      for (var i = 0; i < arguments.length; i++) {\n        fd.append.apply(fd, arguments[i]);\n      }\n      return fd;\n    }\n\n    do_test('empty formdata', new FormData(), '');\n    do_test(\n      'formdata with string',\n      create_formdata(['key', 'value']),\n      'key=value'\n    );\n    //do_test(\"formdata with named string\", create_formdata(['key', new Blob(['value'], {type: 'text/plain'}), 'kv.txt']), '\\nkey=kv.txt:text/plain:5,');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/form-data-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"form-data-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"form-data-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"formdata_parser_supports_files\"\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/global-scope-test.js",
    "content": "import {\n  deepStrictEqual,\n  strictEqual,\n  throws,\n  doesNotThrow,\n  notStrictEqual,\n  ok,\n} from 'node:assert';\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport util from 'node:util';\n\nexport const navigatorUserAgent = {\n  async test() {\n    strictEqual(navigator.userAgent, 'Cloudflare-Workers');\n  },\n};\n\nexport const timeoutClamping = {\n  async test() {\n    // p2 should resolve, p1 should never resolve.\n\n    const p1 = new Promise((resolve) => {\n      setTimeout(() => {\n        resolve('b');\n      }, 9223372036854775808); // Will be clamped to a max of 3153600000000\n    });\n\n    const p2 = new Promise((resolve) => {\n      setTimeout(() => {\n        resolve('a');\n      }, -100); // Will be clamped to 0\n    });\n\n    strictEqual(await Promise.race([p1, p2]), 'a');\n  },\n};\n\nexport const timeoutCount = {\n  test() {\n    for (let i = 0; i < 10000; ++i) {\n      setTimeout(() => {});\n    }\n    throws(() => setTimeout(() => {}), {\n      message:\n        'You have exceeded the number of active timeouts you may set. max active timeouts: 10000, current active timeouts: 10000, finished timeouts: 0',\n    });\n  },\n};\n\nexport const timeoutImplicitCancel = {\n  test() {\n    // Timeouts should be implicitly canceled after the IoContext is destroyed.\n    setTimeout(() => {\n      throw new Error('boom');\n    }, 100000);\n  },\n};\n\nexport const timeoutVarargs = {\n  async test() {\n    let resolve;\n    const promise = new Promise((a) => (resolve = a));\n    setTimeout(\n      (a, b, c) => {\n        resolve([a, b, c]);\n      },\n      10,\n      1,\n      2,\n      3\n    );\n    const results = await promise;\n    deepStrictEqual(results, [1, 2, 3]);\n  },\n};\n\nexport const intervalVarargs = {\n  async test() {\n    let resolve;\n    const promise = new Promise((a) => (resolve = a));\n\n    // prettier-ignore\n    const i = setInterval((a,b,c) => {\n      resolve([a,b,c]);\n      clearInterval(i);\n    }, 10, 1, 2, 3);\n\n    const results = await promise;\n    deepStrictEqual(results, [1, 2, 3]);\n  },\n};\n\nexport const mutableGlobals = {\n  async test() {\n    {\n      const oldCaches = globalThis.caches;\n      const oldCrypto = globalThis.crypto;\n      const oldSelf = globalThis.self;\n      const oldScheduler = globalThis.scheduler;\n      delete globalThis.caches;\n      delete globalThis.crypto;\n      delete globalThis.self;\n      delete globalThis.scheduler;\n      strictEqual(globalThis.caches, undefined);\n      strictEqual(globalThis.crypto, undefined);\n      strictEqual(globalThis.self, undefined);\n      strictEqual(globalThis.scheduler, undefined);\n      Object.defineProperties(globalThis, {\n        caches: { value: 'hello there', writable: true, configurable: true },\n        crypto: { value: 'we are', writable: true, configurable: true },\n        self: { value: 'mutable', writable: true, configurable: true },\n        scheduler: {\n          value: 'at global scope',\n          writable: true,\n          configurable: true,\n        },\n      });\n      strictEqual(globalThis.caches, 'hello there');\n      strictEqual(globalThis.crypto, 'we are');\n      strictEqual(globalThis.self, 'mutable');\n      strictEqual(globalThis.scheduler, 'at global scope');\n      globalThis.caches = oldCaches;\n      globalThis.crypto = oldCrypto;\n      globalThis.self = oldSelf;\n      globalThis.scheduler = oldScheduler;\n    }\n  },\n};\n\nexport const queueMicrotask = {\n  async test() {\n    [1, undefined, 'hello'].forEach((i) => {\n      throws(() => globalThis.queueMicrotask(i), {\n        message:\n          \"Failed to execute 'queueMicrotask' on 'ServiceWorkerGlobalScope': \" +\n          \"parameter 1 is not of type 'function'.\",\n      });\n    });\n    let resolve;\n    const promise = new Promise((a) => (resolve = a));\n    globalThis.queueMicrotask(resolve);\n    await promise;\n  },\n};\n\nexport const unhandledRejectionHandler = {\n  async test() {\n    let resolve;\n    const promise = new Promise((a) => (resolve = a));\n    addEventListener(\n      'unhandledrejection',\n      (event) => {\n        resolve();\n      },\n      { once: true }\n    );\n    // This should trigger the unhandledrejection handler\n    Promise.reject('boom');\n    await promise;\n  },\n};\n\nexport const unhandledRejectionHandler2 = {\n  async test() {\n    let resolve;\n    const promise = new Promise((a) => (resolve = a));\n    const handler = (event) => {\n      throw new Error('should not have fired');\n    };\n    addEventListener('unhandledrejection', handler);\n    try {\n      await Promise.reject('boom');\n    } catch {}\n    await Promise.reject('boom').catch(() => {});\n    removeEventListener('unhandledrejection', handler);\n  },\n};\n\nexport const unhandledRejectionHandler3 = {\n  async test() {\n    let resolve, resolve2;\n    const promise = new Promise((a) => (resolve = a));\n    const promise2 = new Promise((a) => (resolve2 = a));\n    addEventListener('unhandledrejection', (event) => {\n      event.promise.catch(() => {});\n      resolve();\n    });\n    addEventListener('rejectionhandled', (event) => {\n      resolve2();\n    });\n    Promise.reject('boom');\n    await Promise.all([promise, promise2]);\n  },\n};\n\nexport const unhandledRejectionHandler4 = {\n  async test() {\n    let resolve;\n    const promise = new Promise((a) => (resolve = a));\n    addEventListener('unhandledrejection', (event) => {\n      throw new Error('does not crash. safe to ignore in test logs');\n    });\n\n    Promise.reject('boom');\n  },\n};\n\nexport const structuredClone = {\n  test() {\n    {\n      strictEqual(globalThis.structuredClone('hello'), 'hello');\n    }\n\n    {\n      const a = {\n        b: {\n          c: [\n            {\n              d: 1,\n            },\n          ],\n        },\n      };\n      const clone = globalThis.structuredClone(a);\n      a.b.c[0].d = 2;\n      deepStrictEqual(clone.b.c[0].d, 1);\n    }\n\n    {\n      const enc = new TextEncoder();\n      const dec = new TextDecoder();\n      const u8 = enc.encode('hello');\n      const clone = globalThis.structuredClone(u8);\n      strictEqual(dec.decode(clone), 'hello');\n    }\n\n    {\n      const enc = new TextEncoder();\n      const dec = new TextDecoder();\n      const u8 = enc.encode('hello');\n      const clone = globalThis.structuredClone({ a: u8 });\n      u8[0] = 1;\n      strictEqual(dec.decode(u8), '\\u0001ello');\n      strictEqual(dec.decode(clone.a), 'hello');\n    }\n\n    {\n      const enc = new TextEncoder();\n      const dec = new TextDecoder();\n      const u8 = enc.encode('hello');\n      const clone = globalThis.structuredClone(\n        { a: u8 },\n        { transfer: [u8.buffer] }\n      );\n      strictEqual(u8.byteLength, 0);\n      strictEqual(dec.decode(clone.a), 'hello');\n    }\n\n    {\n      const u8 = new Uint8Array(new SharedArrayBuffer(1));\n      const clone = globalThis.structuredClone(u8);\n      notStrictEqual(clone.buffer, u8.buffer);\n      u8[0] = 1;\n      strictEqual(clone[0], 1);\n    }\n\n    {\n      const sab = new SharedArrayBuffer(2);\n      const a = {\n        b: new Uint8Array(sab),\n        c: new Uint16Array(sab),\n      };\n      const clone = globalThis.structuredClone(a);\n      ok(clone.b instanceof Uint8Array);\n      ok(clone.c instanceof Uint16Array);\n\n      clone.c[0] = 0xffff;\n\n      strictEqual(a.b[0], 255);\n      strictEqual(a.b[1], 255);\n    }\n\n    {\n      const u8 = new Uint8Array(1);\n      globalThis.structuredClone(u8, { transfer: [u8.buffer, u8.buffer] });\n    }\n\n    // Non-transferable objects throw\n    throws(() => globalThis.structuredClone({}, { transfer: {} }));\n\n    const memory = new WebAssembly.Memory({ initial: 2, maximum: 2 });\n    throws(() => globalThis.structuredClone(memory));\n\n    // This tests serialization of an API object. Only serializeable API objects (like `Headers`)\n    // can be cloned.\n    {\n      let orig = new Headers({ foo: 123, bar: 'abc' });\n      let cloned = globalThis.structuredClone(orig);\n      ok(cloned instanceof Headers);\n      strictEqual(cloned.get('foo'), '123');\n      strictEqual(cloned.get('bar'), 'abc');\n    }\n\n    // Verify that trying to serialize a non-serializable API type throws.\n    throws(() => globalThis.structuredClone(new TextEncoder()), {\n      name: 'DataCloneError',\n      code: DOMException.DATA_CLONE_ERR,\n      message:\n        'Could not serialize object of type \"TextEncoder\". This type does not support ' +\n        'serialization.',\n    });\n\n    // Test serialization of DOMException. This is technically an API object.\n    {\n      const de1 = new DOMException('hello', 'NotAllowedError');\n\n      const de2 = globalThis.structuredClone(de1);\n      ok(de2 instanceof DOMException);\n      strictEqual(de1.name, de2.name);\n      strictEqual(de1.message, de2.message);\n      strictEqual(de1.stack, de2.stack);\n      strictEqual(de1.code, de2.code);\n      notStrictEqual(de1, de2);\n    }\n  },\n};\n\nexport const SabStructuredCloneTransfer = {\n  async test() {\n    // Can't structureClone a a SharedArrayBuffer with transfer.\n    const sab = new SharedArrayBuffer(64);\n    const view = new Uint8Array(sab);\n\n    throws(() => globalThis.structuredClone(view, { transfer: [view] }), {\n      name: 'DataCloneError',\n    });\n  },\n};\n\nexport const base64 = {\n  test() {\n    function format_value(elem) {\n      return elem;\n    }\n    // Cloudflare note: The real format_value() is in testharness.js.\n\n    function generate_tests(f, cases) {\n      // Cloudflare note: the real generate_tests() is in the real testharness.js, and presumably it\n      // calls test(t[0], () => { f(t[1]) }), or something, for each `t` in `cases`. We can forgo the\n      // description element (t[0]) for our purposes.\n      cases.forEach(([description, testCase]) => {\n        f(testCase);\n      });\n    }\n\n    /**\n     * btoa() as defined by the HTML5 spec, which mostly just references RFC4648.\n     */\n    function mybtoa(s) {\n      // String conversion as required by WebIDL.\n      s = String(s);\n\n      // \"The btoa() method must throw an INVALID_CHARACTER_ERR exception if the\n      // method's first argument contains any character whose code point is\n      // greater than U+00FF.\"\n      for (var i = 0; i < s.length; i++) {\n        if (s.charCodeAt(i) > 255) {\n          return 'INVALID_CHARACTER_ERR';\n        }\n      }\n\n      var out = '';\n      for (var i = 0; i < s.length; i += 3) {\n        var groupsOfSix = [undefined, undefined, undefined, undefined];\n        groupsOfSix[0] = s.charCodeAt(i) >> 2;\n        groupsOfSix[1] = (s.charCodeAt(i) & 0x03) << 4;\n        if (s.length > i + 1) {\n          groupsOfSix[1] |= s.charCodeAt(i + 1) >> 4;\n          groupsOfSix[2] = (s.charCodeAt(i + 1) & 0x0f) << 2;\n        }\n        if (s.length > i + 2) {\n          groupsOfSix[2] |= s.charCodeAt(i + 2) >> 6;\n          groupsOfSix[3] = s.charCodeAt(i + 2) & 0x3f;\n        }\n        for (var j = 0; j < groupsOfSix.length; j++) {\n          if (typeof groupsOfSix[j] == 'undefined') {\n            out += '=';\n          } else {\n            out += btoaLookup(groupsOfSix[j]);\n          }\n        }\n      }\n      return out;\n    }\n\n    /**\n     * Lookup table for mybtoa(), which converts a six-bit number into the\n     * corresponding ASCII character.\n     */\n    function btoaLookup(idx) {\n      if (idx < 26) {\n        return String.fromCharCode(idx + 'A'.charCodeAt(0));\n      }\n      if (idx < 52) {\n        return String.fromCharCode(idx - 26 + 'a'.charCodeAt(0));\n      }\n      if (idx < 62) {\n        return String.fromCharCode(idx - 52 + '0'.charCodeAt(0));\n      }\n      if (idx == 62) {\n        return '+';\n      }\n      if (idx == 63) {\n        return '/';\n      }\n      // Throw INVALID_CHARACTER_ERR exception here -- won't be hit in the tests.\n    }\n\n    /**\n     * Implementation of atob() according to the HTML spec, except that instead of\n     * throwing INVALID_CHARACTER_ERR we return null.\n     */\n    function myatob(input) {\n      // WebIDL requires DOMStrings to just be converted using ECMAScript\n      // ToString, which in our case amounts to calling String().\n      input = String(input);\n\n      // \"Remove all space characters from input.\"\n      input = input.replace(/[ \\t\\n\\f\\r]/g, '');\n\n      // \"If the length of input divides by 4 leaving no remainder, then: if\n      // input ends with one or two U+003D EQUALS SIGN (=) characters, remove\n      // them from input.\"\n      if (input.length % 4 == 0 && /==?$/.test(input)) {\n        input = input.replace(/==?$/, '');\n      }\n\n      // \"If the length of input divides by 4 leaving a remainder of 1, throw an\n      // INVALID_CHARACTER_ERR exception and abort these steps.\"\n      //\n      // \"If input contains a character that is not in the following list of\n      // characters and character ranges, throw an INVALID_CHARACTER_ERR\n      // exception and abort these steps:\n      //\n      // U+002B PLUS SIGN (+)\n      // U+002F SOLIDUS (/)\n      // U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9)\n      // U+0041 LATIN CAPITAL LETTER A to U+005A LATIN CAPITAL LETTER Z\n      // U+0061 LATIN SMALL LETTER A to U+007A LATIN SMALL LETTER Z\"\n      if (input.length % 4 == 1 || !/^[+/0-9A-Za-z]*$/.test(input)) {\n        return null;\n      }\n\n      // \"Let output be a string, initially empty.\"\n      var output = '';\n\n      // \"Let buffer be a buffer that can have bits appended to it, initially\n      // empty.\"\n      //\n      // We append bits via left-shift and or.  accumulatedBits is used to track\n      // when we've gotten to 24 bits.\n      var buffer = 0;\n      var accumulatedBits = 0;\n\n      // \"While position does not point past the end of input, run these\n      // substeps:\"\n      for (var i = 0; i < input.length; i++) {\n        // \"Find the character pointed to by position in the first column of\n        // the following table. Let n be the number given in the second cell of\n        // the same row.\"\n        //\n        // \"Append to buffer the six bits corresponding to number, most\n        // significant bit first.\"\n        //\n        // atobLookup() implements the table from the spec.\n        buffer <<= 6;\n        buffer |= atobLookup(input[i]);\n\n        // \"If buffer has accumulated 24 bits, interpret them as three 8-bit\n        // big-endian numbers. Append the three characters with code points\n        // equal to those numbers to output, in the same order, and then empty\n        // buffer.\"\n        accumulatedBits += 6;\n        if (accumulatedBits == 24) {\n          output += String.fromCharCode((buffer & 0xff0000) >> 16);\n          output += String.fromCharCode((buffer & 0xff00) >> 8);\n          output += String.fromCharCode(buffer & 0xff);\n          buffer = accumulatedBits = 0;\n        }\n\n        // \"Advance position by one character.\"\n      }\n\n      // \"If buffer is not empty, it contains either 12 or 18 bits. If it\n      // contains 12 bits, discard the last four and interpret the remaining\n      // eight as an 8-bit big-endian number. If it contains 18 bits, discard the\n      // last two and interpret the remaining 16 as two 8-bit big-endian numbers.\n      // Append the one or two characters with code points equal to those one or\n      // two numbers to output, in the same order.\"\n      if (accumulatedBits == 12) {\n        buffer >>= 4;\n        output += String.fromCharCode(buffer);\n      } else if (accumulatedBits == 18) {\n        buffer >>= 2;\n        output += String.fromCharCode((buffer & 0xff00) >> 8);\n        output += String.fromCharCode(buffer & 0xff);\n      }\n\n      // \"Return output.\"\n      return output;\n    }\n\n    /**\n     * A lookup table for atob(), which converts an ASCII character to the\n     * corresponding six-bit number.\n     */\n    function atobLookup(chr) {\n      if (/[A-Z]/.test(chr)) {\n        return chr.charCodeAt(0) - 'A'.charCodeAt(0);\n      }\n      if (/[a-z]/.test(chr)) {\n        return chr.charCodeAt(0) - 'a'.charCodeAt(0) + 26;\n      }\n      if (/[0-9]/.test(chr)) {\n        return chr.charCodeAt(0) - '0'.charCodeAt(0) + 52;\n      }\n      if (chr == '+') {\n        return 62;\n      }\n      if (chr == '/') {\n        return 63;\n      }\n      // Throw exception; should not be hit in tests\n    }\n\n    function testBtoa(input) {\n      // \"The btoa() method must throw an INVALID_CHARACTER_ERR exception if the\n      // method's first argument contains any character whose code point is\n      // greater than U+00FF.\"\n      var normalizedInput = String(input);\n      for (var i = 0; i < normalizedInput.length; i++) {\n        if (normalizedInput.charCodeAt(i) > 255) {\n          throws(() => globalThis.btoa(input));\n          return;\n        }\n      }\n      strictEqual(globalThis.btoa(input), mybtoa(input));\n      strictEqual(globalThis.atob(globalThis.btoa(input)), String(input));\n    }\n\n    var tests = [\n      'עברית',\n      '',\n      'ab',\n      'abc',\n      'abcd',\n      'abcde',\n      // This one is thrown in because IE9 seems to fail atob(btoa()) on it.  Or\n      // possibly to fail btoa().  I actually can't tell what's happening here,\n      // but it doesn't hurt.\n      '\\xff\\xff\\xc0',\n      // Is your DOM implementation binary-safe?\n      '\\0a',\n      'a\\0b',\n      // WebIDL tests.\n      undefined,\n      null,\n      7,\n      12,\n      1.5,\n      true,\n      false,\n      NaN,\n      +Infinity,\n      -Infinity,\n      0,\n      -0,\n      {\n        toString: function () {\n          return 'foo';\n        },\n      },\n    ];\n    for (var i = 0; i < 258; i++) {\n      tests.push(String.fromCharCode(i));\n    }\n    tests.push(String.fromCharCode(10000));\n    tests.push(String.fromCharCode(65534));\n    tests.push(String.fromCharCode(65535));\n\n    // This is supposed to be U+10000.\n    tests.push(String.fromCharCode(0xd800, 0xdc00));\n    tests = tests.map(function (elem) {\n      var expected = mybtoa(elem);\n      if (expected === 'INVALID_CHARACTER_ERR') {\n        return [\n          'btoa(' + format_value(elem) + ') must raise INVALID_CHARACTER_ERR',\n          elem,\n        ];\n      }\n      return [\n        'btoa(' + format_value(elem) + ') == ' + format_value(mybtoa(elem)),\n        elem,\n      ];\n    });\n\n    var everything = '';\n    for (var i = 0; i < 256; i++) {\n      everything += String.fromCharCode(i);\n    }\n    tests.push(['btoa(first 256 code points concatenated)', everything]);\n\n    generate_tests(testBtoa, tests);\n\n    function testAtob(input) {\n      var expected = myatob(input);\n      if (expected === null) {\n        throws(() => atob(input));\n        return;\n      }\n\n      strictEqual(globalThis.atob(input), expected);\n    }\n\n    var tests = [\n      '',\n      'abcd',\n      ' abcd',\n      'abcd ',\n      ' abcd===',\n      'abcd=== ',\n      'abcd ===',\n      'a',\n      'ab',\n      'abc',\n      'abcde',\n      String.fromCharCode(0xd800, 0xdc00),\n      '=',\n      '==',\n      '===',\n      '====',\n      '=====',\n      'a=',\n      'a==',\n      'a===',\n      'a====',\n      'a=====',\n      'ab=',\n      'ab==',\n      'ab===',\n      'ab====',\n      'ab=====',\n      'abc=',\n      'abc==',\n      'abc===',\n      'abc====',\n      'abc=====',\n      'abcd=',\n      'abcd==',\n      'abcd===',\n      'abcd====',\n      'abcd=====',\n      'abcde=',\n      'abcde==',\n      'abcde===',\n      'abcde====',\n      'abcde=====',\n      '=a',\n      '=a=',\n      'a=b',\n      'a=b=',\n      'ab=c',\n      'ab=c=',\n      'abc=d',\n      'abc=d=',\n      // With whitespace\n      'ab\\tcd',\n      'ab\\ncd',\n      'ab\\fcd',\n      'ab\\rcd',\n      'ab cd',\n      'ab\\u00a0cd',\n      'ab\\t\\n\\f\\r cd',\n      ' \\t\\n\\f\\r ab\\t\\n\\f\\r cd\\t\\n\\f\\r ',\n      'ab\\t\\n\\f\\r =\\t\\n\\f\\r =\\t\\n\\f\\r ',\n      // Test if any bits are set at the end.  These should all be fine, since\n      // they end with A, which becomes 0:\n      'A',\n      '/A',\n      '//A',\n      '///A',\n      '////A',\n      // These are all bad, since they end in / (= 63, all bits set) but their\n      // length isn't a multiple of four characters, so they can't be output by\n      // btoa().  Thus one might expect some UAs to throw exceptions or otherwise\n      // object, since they could never be output by btoa(), so they're good to\n      // test.\n      '/',\n      'A/',\n      'AA/',\n      'AAAA/',\n      // But this one is possible:\n      'AAA/',\n      // Binary-safety tests\n      '\\0nonsense',\n      'abcd\\0nonsense',\n      // WebIDL tests\n      // TODO(conform): We need automatic stringification of arguments before these will work.\n      //undefined, null, 7, 12, 1.5, true, false, NaN, +Infinity, -Infinity, 0, -0,\n      //{ toString: function () { return \"foo\" } },\n      //{ toString: function () { return \"abcd\" } },\n    ];\n    tests = tests.map(function (elem) {\n      if (myatob(elem) === null) {\n        return [\n          'atob(' + format_value(elem) + ') must raise InvalidCharacterError',\n          elem,\n        ];\n      }\n      return [\n        'atob(' + format_value(elem) + ') == ' + format_value(myatob(elem)),\n        elem,\n      ];\n    });\n\n    generate_tests(testAtob, tests);\n  },\n};\n\nexport const webSocketPairIterable = {\n  test() {\n    const [a, b] = new WebSocketPair();\n    ok(a instanceof WebSocket);\n    ok(b instanceof WebSocket);\n  },\n};\n\nexport const queueMicrotaskError = {\n  async test() {\n    const als = new AsyncLocalStorage();\n    const { promise, resolve } = Promise.withResolvers();\n    const err = new Error('boom');\n    err.resolve = resolve;\n\n    addEventListener(\n      'error',\n      (event) => {\n        // Verify that async context propagates correctly\n        // in the error event.\n        strictEqual(als.getStore(), 123);\n        // We got the correct expected error.\n        strictEqual(event.error, err);\n        event.error.resolve();\n        // Returning true suppresses the default logging.\n        return true;\n      },\n      { once: true }\n    );\n\n    als.run(123, () =>\n      globalThis.queueMicrotask(() => {\n        // Throwing inside a queueMicrotask should trigger the error event.\n        strictEqual(als.getStore(), 123);\n        throw err;\n      })\n    );\n\n    await promise;\n  },\n};\n\nexport const toStringTag = {\n  test() {\n    strictEqual('Response', Response.prototype[Symbol.toStringTag]);\n    strictEqual('Request', Request.prototype[Symbol.toStringTag]);\n    strictEqual('Headers', Headers.prototype[Symbol.toStringTag]);\n    strictEqual(\n      'URLSearchParams',\n      URLSearchParams.prototype[Symbol.toStringTag]\n    );\n    strictEqual('CryptoKey', CryptoKey.prototype[Symbol.toStringTag]);\n    strictEqual(\n      toString.call(new URLSearchParams()),\n      '[object URLSearchParams]'\n    );\n\n    const internalFlag = Symbol.for('cloudflare:internal-class');\n    strictEqual(Headers.prototype[internalFlag], internalFlag);\n    strictEqual(new Headers()[internalFlag], internalFlag);\n  },\n};\nexport const validateGlobalThis = {\n  test() {\n    util.inspect(globalThis);\n  },\n};\n\nexport const webSocketUrlValidation = {\n  async test() {\n    // Username and password should have been set in Authorization header\n    // but we silently ignore it to match the fetch() implementation.\n    doesNotThrow(() => new WebSocket('ws://username@domain.com'));\n    // Empty values should be rejected.\n    throws(() => new WebSocket(''), {\n      name: 'SyntaxError',\n      message: /invalid/,\n    });\n  },\n};\n\nexport const reuseCtx = {\n  async test(controller, env, ctx) {\n    ctx.reused = true;\n    await ctx.exports.reuseCtx.check(null);\n    await ctx.exports.reuseCtx.check(null);\n  },\n\n  check(_, env, ctx) {\n    strictEqual(ctx.reused, undefined);\n    ctx.reused = true;\n  },\n};\n\nexport const structuredCloneError = {\n  test() {\n    // If it doesn't crash, we're good.\n    var globalProp = {\n      get p1() {\n        return this;\n      },\n      get trigger() {\n        function createSab() {\n          var sab = new SharedArrayBuffer(4096);\n          return sab;\n        }\n        var prop = {};\n        Object.defineProperty(prop, 'constructor', {\n          writable: true,\n          value: createSab,\n        });\n        return prop.constructor();\n      },\n      get p2() {\n        var gTCtor = globalThis.Intl.constructor;\n        var returnVal;\n        try {\n          returnVal = gTCtor.entries(this);\n        } catch (e) {}\n        return returnVal;\n      },\n    };\n    globalThis.structuredClone(globalProp);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/global-scope-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"global-scope-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"global-scope-test.js\")\n        ],\n        # \"experimental\" is for ctx.exports\n        compatibilityFlags = [\"nodejs_compat\", \"enable_ctx_exports\", \"global_navigator\", \"rpc\", \"set_tostring_tag\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/headers-immutable-prototype-test.js",
    "content": "import { strictEqual } from 'node:assert';\n\nexport const test = {\n  test() {\n    strictEqual(\n      Reflect.getOwnPropertyDescriptor(Headers, 'prototype').writable,\n      false\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/headers-immutable-prototype-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"headers-immutable-prototype-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"headers-immutable-prototype-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"immutable_api_prototypes\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/htmlrewriter-test.js",
    "content": "import {\n  strictEqual,\n  deepStrictEqual,\n  notStrictEqual,\n  rejects,\n  throws,\n} from 'node:assert';\n\nconst arrayIterator = [][Symbol.iterator]();\nconst arrayIteratorPrototype = Object.getPrototypeOf(\n  Object.getPrototypeOf(arrayIterator)\n);\n\nexport const passthroughWithContent = {\n  async test() {\n    const response = new Response('hello', {\n      headers: {\n        foo: 'bar',\n      },\n    });\n    strictEqual(response.headers.get('foo'), 'bar');\n\n    const rewriter = new HTMLRewriter();\n\n    const newResponse = rewriter.transform(response);\n\n    notStrictEqual(response, newResponse);\n    strictEqual(newResponse.headers.get('foo'), 'bar');\n    strictEqual(await newResponse.text(), 'hello');\n  },\n};\n\nexport const passthroughWithContentAndHandler = {\n  async test() {\n    const response = new Response('<h3>hello</h3>', {\n      headers: {\n        foo: 'bar',\n      },\n    });\n    strictEqual(response.headers.get('foo'), 'bar');\n\n    const rewriter = new HTMLRewriter().on('h3', {\n      element() {},\n      comments() {},\n      text() {},\n    });\n\n    const newResponse = rewriter.transform(response);\n\n    notStrictEqual(response, newResponse);\n    strictEqual(newResponse.headers.get('foo'), 'bar');\n    strictEqual(await newResponse.text(), '<h3>hello</h3>');\n  },\n};\n\nexport const passthroughWithContentAndAsyncHandler = {\n  async test() {\n    const response = new Response('<h3>hello</h3>', {\n      headers: {\n        foo: 'bar',\n      },\n    });\n    strictEqual(response.headers.get('foo'), 'bar');\n\n    const rewriter = new HTMLRewriter().on('h3', {\n      async test() {\n        await scheduler.wait(10);\n      },\n      async element() {\n        await scheduler.wait(10);\n      },\n      async comments() {\n        await scheduler.wait(10);\n      },\n    });\n\n    const newResponse = rewriter.transform(response);\n\n    notStrictEqual(response, newResponse);\n    strictEqual(newResponse.headers.get('foo'), 'bar');\n    strictEqual(await newResponse.text(), '<h3>hello</h3>');\n  },\n};\n\nexport const passthroughWithContentAndAsyncHandler2 = {\n  async test() {\n    const response = new Response('<h3>hello</h3>', {\n      headers: {\n        foo: 'bar',\n      },\n    });\n    strictEqual(response.headers.get('foo'), 'bar');\n\n    const { promise, resolve } = Promise.withResolvers();\n\n    const rewriter = new HTMLRewriter().on('h3', {\n      async test() {\n        await promise;\n      },\n      async element() {\n        await scheduler.wait(10);\n      },\n      async comments() {\n        await scheduler.wait(10);\n      },\n    });\n\n    const newResponse = rewriter.transform(response);\n\n    // We can resolve the promise after calling transform and the transform\n    // will complete as expected.\n    resolve();\n\n    notStrictEqual(response, newResponse);\n    strictEqual(newResponse.headers.get('foo'), 'bar');\n    strictEqual(await newResponse.text(), '<h3>hello</h3>');\n  },\n};\n\nexport const passthroughWithContentStream = {\n  async test() {\n    const { readable, writable } = new TransformStream();\n\n    const rewriter = new HTMLRewriter().on('h3', {\n      async text(content) {\n        await scheduler.wait(10);\n      },\n    });\n\n    const response = rewriter.transform(new Response(readable));\n\n    const writer = writable.getWriter();\n    const enc = new TextEncoder();\n    const results = await Promise.all([\n      response.text(),\n      writer.write(enc.encode('<h3>hello</h3>')),\n      writer.close(),\n    ]);\n\n    strictEqual(results[0], '<h3>hello</h3>');\n  },\n};\n\nexport const passthroughWithEmptyStream = {\n  async test() {\n    const { readable, writable } = new TransformStream();\n\n    const rewriter = new HTMLRewriter().on('h3', {\n      async text(content) {\n        await scheduler.wait(10);\n      },\n    });\n\n    const response = rewriter.transform(new Response(readable));\n\n    const writer = writable.getWriter();\n    const results = await Promise.all([response.text(), writer.close()]);\n\n    strictEqual(results[0], '');\n  },\n};\n\nexport const asyncElementHandler = {\n  async test() {\n    const rewriter = new HTMLRewriter().on('body', {\n      async element(e) {\n        await scheduler.wait(10);\n        e.setInnerContent('world');\n      },\n    });\n\n    const response = rewriter.transform(new Response('<body>hello</body>'));\n\n    strictEqual(await response.text(), '<body>world</body>');\n  },\n};\n\nexport const asyncCommentHandler = {\n  async test() {\n    const rewriter = new HTMLRewriter().on('body', {\n      async comments(comment) {\n        await scheduler.wait(10);\n        if (comment.text == 'hello') {\n          comment.text = 'world';\n        }\n      },\n    });\n\n    const response = rewriter.transform(\n      new Response('<body><!--hello--></body>')\n    );\n\n    strictEqual(await response.text(), '<body><!--world--></body>');\n  },\n};\n\nexport const objectHandlers = {\n  async test() {\n    class DocumentContentHandlers {\n      deadTokens = {};\n      doctypeCount = 0;\n      commentCount = 0;\n      textCount = 0;\n      expectedErrors = [];\n\n      doctype(token) {\n        if (!this.deadTokens.doctype) {\n          this.deadTokens.doctype = token;\n        }\n        ++this.doctypeCount;\n\n        this.sawDoctype = JSON.stringify(token);\n      }\n\n      comments(token) {\n        if (!this.deadTokens.comment) {\n          this.deadTokens.comment = token;\n        }\n        ++this.commentCount;\n      }\n\n      text(token) {\n        if (!this.deadTokens.text) {\n          this.deadTokens.text = token;\n        }\n        ++this.textCount;\n      }\n\n      end(token) {\n        this.reachedEnd = true;\n      }\n    }\n\n    class ElementContentHandlers {\n      deadTokens = {};\n      elementCount = 0;\n      commentCount = 0;\n      textCount = 0;\n      expectedErrors = [];\n\n      element(token) {\n        if (!this.deadTokens.element) {\n          this.deadTokens.element = token;\n        }\n        if (!this.deadTokens.attributesIterator) {\n          this.deadTokens.attributesIterator = token.attributes;\n        }\n        ++this.elementCount;\n\n        // Exercise all the different methods on Element.\n        if (\n          token.tagName === 'body' &&\n          token.hasAttribute('foo') &&\n          !token.hasAttribute('baz') &&\n          token.getAttribute('foo') === 'bar'\n        ) {\n          token.removeAttribute('foo');\n          token.setAttribute('baz', 'qux');\n\n          try {\n            token.tagName = 'should throw';\n            throw new Error('should have thrown');\n          } catch (e) {\n            this.expectedErrors.push(e.message);\n          }\n\n          token.tagName = 'tail';\n\n          // These will show up in order in the response body.\n          token.before('<1>');\n          token.before('<2>', { html: false });\n          token.before('<3>\\n', null);\n          token.before('<html>', { html: true });\n\n          // These will show up in reverse order in the response body.\n          token.prepend('<em>hello</em>, ', { html: true });\n          token.prepend('<6>\\n');\n          token.prepend('<5>', { html: false });\n          token.prepend('\\n<4>', null);\n\n          // Iterator tests.\n\n          this.sawAttributes = JSON.stringify([...token.attributes]);\n\n          let iterator = token.attributes;\n          let iteratorPrototype = Object.getPrototypeOf(\n            Object.getPrototypeOf(iterator)\n          );\n          if (iteratorPrototype !== arrayIteratorPrototype) {\n            throw new Error(\n              'attributes iterator does not have iterator prototype'\n            );\n          }\n\n          // Run the iterator down until it's done.\n          for (let [k, v] of iterator) {\n          }\n          // .next() should now be idempotent.\n          let result = iterator.next();\n          let result2 = iterator.next();\n          if (\n            result.done !== result2.done ||\n            result.value !== result2.value ||\n            !result.done ||\n            result.value\n          ) {\n            throw new Error(\n              'exhausted iterator should continually return done'\n            );\n          }\n        } else if (token.tagName === 'remove') {\n          let mode = token.getAttribute('mode');\n          if (mode === null) {\n            throw new Error(\"missing attribute on 'remove' element\");\n          }\n          if (token.removed) {\n            throw new Error('element should not have been removed yet');\n          }\n\n          if (mode === 'all') {\n            token.remove();\n          } else {\n            token.removeAndKeepContent();\n          }\n\n          if (!token.removed) {\n            throw new Error('element should have been removed now');\n          }\n        } else if (token.tagName === 'after') {\n          let isHtml = token.getAttribute('is-html');\n          let html = isHtml === 'true' ? true : false;\n          token.after('<after>', { html });\n        } else if (token.tagName === 'append') {\n          let isHtml = token.getAttribute('is-html');\n          let html = isHtml === 'true' ? true : false;\n          token.append('<append>', { html });\n        } else if (token.tagName === 'replace') {\n          let isHtml = token.getAttribute('is-html');\n          let html = isHtml === 'true' ? true : false;\n          token.replace('<replace>', { html });\n        } else if (token.tagName === 'set-inner-content') {\n          let isHtml = token.getAttribute('is-html');\n          let html = isHtml === 'true' ? true : false;\n          token.setInnerContent('<set-inner-content>', { html });\n        } else if (token.tagName === 'set-attribute') {\n          if (!token.hasAttribute('foo')) {\n            throw new Error('element should have had attribute');\n          }\n          let attr = token.getAttribute('foo');\n          if (attr !== '') {\n            throw new Error('element attribute should have been empty');\n          }\n          token.setAttribute('foo', 'bar');\n\n          if (token.getAttribute('nonexistent')) {\n            throw new Error('attribute should not exist');\n          }\n        }\n      }\n\n      comments(token) {\n        if (!this.deadTokens.comment) {\n          this.deadTokens.comment = token;\n        }\n        ++this.commentCount;\n\n        // Exercise all the different methods on Comment.\n        if (token.text === ' SET TEXT PROPERTY ') {\n          token.text = ' text property has been set ';\n        } else if (token.text === ' REMOVE ME ') {\n          if (token.removed) {\n            throw new Error(\"Shouldn't be removed yet\");\n          }\n\n          token.remove();\n\n          if (!token.removed) {\n            throw new Error('Should be removed now');\n          }\n        } else if (token.text === ' REPLACE ME ') {\n          if (token.removed) {\n            throw new Error(\"Shouldn't be removed yet\");\n          }\n\n          token.replace('this will get overwritten');\n\n          if (!token.removed) {\n            throw new Error('Should be removed now');\n          }\n\n          token.replace('<REPLACED>', null);\n\n          if (!token.removed) {\n            throw new Error('Should still be removed');\n          }\n\n          token.before('<!-- ', { html: true });\n          token.after(' -->', { html: true });\n        }\n      }\n\n      text(token) {\n        if (!this.deadTokens.text) {\n          this.deadTokens.text = token;\n        }\n        ++this.textCount;\n\n        if (token.lastInTextNode && token.text.length > 0) {\n          throw new Error('last text chunk has non-zero length');\n        } else if (!token.lastInTextNode && token.text.length === 0) {\n          throw new Error('non-last text chunk has zero length');\n        }\n\n        if (token.text === 'world') {\n          token.before('again, ');\n          token.after('...');\n\n          if (token.removed) {\n            throw new Error(\"Shouldn't be removed yet\");\n          }\n\n          token.replace('this will get overwritten');\n\n          if (!token.removed) {\n            throw new Error('Should be removed now');\n          }\n\n          token.replace('<WORLD>', { html: true });\n\n          if (!token.removed) {\n            throw new Error('Should still be removed');\n          }\n        } else if (token.text === 'REMOVE ME\\n') {\n          if (token.removed) {\n            throw new Error(\"Shouldn't be removed yet\");\n          }\n\n          token.remove();\n\n          if (!token.removed) {\n            throw new Error('Should be removed now');\n          }\n        }\n      }\n    }\n\n    let documentHandlers = new DocumentContentHandlers();\n    let elementHandlers = new ElementContentHandlers();\n\n    const rewriter = new HTMLRewriter()\n      .onDocument(documentHandlers)\n      .on('*', elementHandlers);\n\n    let count = 0;\n\n    const enc = new TextEncoder();\n    const kInput = [\n      '<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">',\n      '<!-- document-level comment -->',\n      'document-level text',\n      '<body foo=\"bar\" an-attr=\"ibute?\">world<p>REMOVE ME',\n      '<remove mode=\"all\">inner content</remove>',\n      '<remove mode=\"keep\">inner content</remove>',\n      '<after is-html=\"true\">inner content</after>',\n      '<after is-html=\"false\">inner content</after>',\n      '<append is-html=\"true\">inner content</append>',\n      '<append is-html=\"false\">inner content</append>',\n      '<replace is-html=\"true\">inner content</replace>',\n      '<replace is-html=\"false\">inner content</replace>',\n      '<set-inner-content is-html=\"true\">inner content</set-inner-content>',\n      '<set-inner-content is-html=\"false\">inner content</set-inner-content>',\n      '<set-attribute foo></set-attribute>',\n      '<set-attribute foo=></set-attribute>',\n      '<set-attribute foo=\"\"></set-attribute>',\n      '<!-- REMOVE ME -->',\n      '<!-- REPLACE ME -->',\n      '<!-- SET TEXT PROPERTY -->',\n      '</body>',\n    ];\n\n    const kResult = `<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"><!-- document-level comment -->document-level text&lt;1&gt;&lt;2&gt;&lt;3&gt;\n<html><tail an-attr=\"ibute?\" baz=\"qux\">\n&lt;4&gt;&lt;5&gt;&lt;6&gt;\n<em>hello</em>, again, <WORLD>...<p>REMOVE MEinner content<after is-html=\"true\">inner content</after><after><after is-html=\"false\">inner content</after>&lt;after&gt;<append is-html=\"true\">inner content<append></append><append is-html=\"false\">inner content&lt;append&gt;</append><replace>&lt;replace&gt;<set-inner-content is-html=\"true\"><set-inner-content></set-inner-content><set-inner-content is-html=\"false\">&lt;set-inner-content&gt;</set-inner-content><set-attribute foo=\"bar\"></set-attribute><set-attribute foo=\"bar\"></set-attribute><set-attribute foo=\"bar\"></set-attribute><!-- &lt;REPLACED&gt; --><!-- text property has been set --></tail>`;\n\n    const readable = new ReadableStream({\n      async pull(controller) {\n        await scheduler.wait(1);\n        if (kInput.length > 0) {\n          controller.enqueue(enc.encode(kInput.shift()));\n        } else {\n          controller.close();\n        }\n      },\n    });\n\n    const response = rewriter.transform(new Response(readable));\n\n    // At this point, we should not have seen any tokens.\n    strictEqual(elementHandlers.deadTokens.element, undefined);\n\n    strictEqual(await response.text(), kResult);\n\n    // Now we've seen tokens\n    notStrictEqual(elementHandlers.deadTokens.element, undefined);\n\n    // Verify that tokens are invalidated outside handler execution scope.\n    throws(() => documentHandlers.deadTokens.doctype.publicId, {\n      message:\n        'This content token is no longer valid. Content tokens are only ' +\n        'valid during the execution of the relevant content handler.',\n    });\n    throws(() => documentHandlers.deadTokens.comment.text, {\n      message:\n        'This content token is no longer valid. Content tokens are only ' +\n        'valid during the execution of the relevant content handler.',\n    });\n    throws(() => documentHandlers.deadTokens.text.text, {\n      message:\n        'This content token is no longer valid. Content tokens are only ' +\n        'valid during the execution of the relevant content handler.',\n    });\n    throws(() => elementHandlers.deadTokens.element.getAttribute('foo'), {\n      message:\n        'This content token is no longer valid. Content tokens are only ' +\n        'valid during the execution of the relevant content handler.',\n    });\n    throws(() => elementHandlers.deadTokens.attributesIterator.next(), {\n      message:\n        'This content token is no longer valid. Content tokens are only ' +\n        'valid during the execution of the relevant content handler.',\n    });\n    throws(() => elementHandlers.deadTokens.comment.text, {\n      message:\n        'This content token is no longer valid. Content tokens are only ' +\n        'valid during the execution of the relevant content handler.',\n    });\n    throws(() => elementHandlers.deadTokens.text.text, {\n      message:\n        'This content token is no longer valid. Content tokens are only ' +\n        'valid during the execution of the relevant content handler.',\n    });\n\n    strictEqual(documentHandlers.doctypeCount, 1);\n    strictEqual(documentHandlers.commentCount, 4);\n    strictEqual(documentHandlers.textCount, 26);\n    strictEqual(documentHandlers.reachedEnd, true);\n    strictEqual(elementHandlers.elementCount, 15);\n    strictEqual(elementHandlers.commentCount, 3);\n    strictEqual(elementHandlers.textCount, 24);\n    deepStrictEqual(elementHandlers.expectedErrors, [\n      'Parser error: ` ` character is forbidden in the tag name',\n    ]);\n  },\n};\n\nexport const manualWriting = {\n  async test() {\n    const { readable, writable } = new IdentityTransformStream();\n\n    const response = new HTMLRewriter()\n      .on('*', {\n        element(element) {\n          element.prepend('foo ');\n        },\n      })\n      .transform(new Response(readable));\n\n    const writer = writable.getWriter();\n    const encoder = new TextEncoder();\n\n    // Because this variation uses IdentityTransformStream, we must\n    // initiate the read before doing the writes.\n    const promise = response.text();\n\n    await writer.write(encoder.encode('<html>'));\n    await writer.write(encoder.encode('bar'));\n    await writer.write(encoder.encode('</html>'));\n    await writer.close();\n\n    strictEqual(await promise, '<html>foo bar</html>');\n  },\n};\n\nexport const manualWriting2 = {\n  async test() {\n    const { readable, writable } = new TransformStream();\n\n    const response = new HTMLRewriter()\n      .on('*', {\n        element(element) {\n          element.prepend('foo ');\n        },\n      })\n      .transform(new Response(readable));\n\n    const writer = writable.getWriter();\n    const encoder = new TextEncoder();\n\n    await writer.write(encoder.encode('<html>'));\n    await writer.write(encoder.encode('bar'));\n    await writer.write(encoder.encode('</html>'));\n    await writer.close();\n\n    // This variation uses the JavaScript TransformStream, so we can\n    // initiate the read after doing the writes.\n    const promise = response.text();\n\n    strictEqual(await promise, '<html>foo bar</html>');\n  },\n};\n\nexport const streamingReplacement = {\n  async test() {\n    const { readable, writable } = new TransformStream();\n\n    const response = new HTMLRewriter()\n      .on('*', {\n        async element(element) {\n          const dataStream = (\n            await fetch('data:,the quick brown fox jumped over the lazy dog%20')\n          ).body;\n          element.prepend(dataStream, { html: false });\n        },\n      })\n      .transform(new Response(readable));\n\n    const writer = writable.getWriter();\n    const encoder = new TextEncoder();\n\n    await writer.write(encoder.encode('<html>'));\n    await writer.write(encoder.encode('bar'));\n    await writer.write(encoder.encode('</html>'));\n    await writer.close();\n\n    // This variation uses the JavaScript TransformStream, so we can\n    // initiate the read after doing the writes.\n    const promise = response.text();\n    strictEqual(\n      await promise,\n      `<html>the quick brown fox jumped over the lazy dog bar</html>`\n    );\n  },\n};\n\nexport const streamingReplacementHTML = {\n  async test() {\n    const { readable, writable } = new TransformStream();\n\n    const response = new HTMLRewriter()\n      .on('*', {\n        async element(element) {\n          const dataStream = (\n            await fetch('data:,<b>such markup <i>much wow</i></b> ')\n          ).body;\n          element.prepend(dataStream, { html: true });\n        },\n      })\n      .transform(new Response(readable));\n\n    const writer = writable.getWriter();\n    const encoder = new TextEncoder();\n\n    await writer.write(encoder.encode('<html>'));\n    await writer.write(encoder.encode('bar'));\n    await writer.write(encoder.encode('</html>'));\n    await writer.close();\n\n    // This variation uses the JavaScript TransformStream, so we can\n    // initiate the read after doing the writes.\n    const promise = response.text();\n    strictEqual(\n      await promise,\n      `<html><b>such markup <i>much wow</i></b>bar</html>`\n    );\n  },\n};\n\nexport const streamingReplacementReplace = {\n  async test() {\n    const { readable, writable } = new TransformStream();\n\n    const response = new HTMLRewriter()\n      .on('.dinosaur', {\n        async element(element) {\n          const dataStream = (await fetch('data:,goodbye world')).body;\n          element.replace(dataStream, { html: false });\n        },\n      })\n      .transform(new Response(readable));\n\n    const writer = writable.getWriter();\n    const encoder = new TextEncoder();\n\n    await writer.write(encoder.encode('<html>'));\n    await writer.write(\n      encoder.encode('<div class=\"dinosaur\">hello world</div>')\n    );\n    await writer.write(encoder.encode('</html>'));\n    await writer.close();\n\n    // This variation uses the JavaScript TransformStream, so we can\n    // initiate the read after doing the writes.\n    const promise = response.text();\n    strictEqual(await promise, `<html>goodbye world</html>`);\n  },\n};\n\nexport const streamingReplacementMultiple = {\n  async test() {\n    const { readable, writable } = new TransformStream();\n\n    const response = new HTMLRewriter()\n      .on('*', {\n        async element(element) {\n          element.prepend(await fetch('data:,alpha%20'));\n          element.append(await fetch('data:,%20gamma'));\n        },\n      })\n      .transform(new Response(readable));\n\n    const writer = writable.getWriter();\n    const encoder = new TextEncoder();\n\n    await writer.write(encoder.encode('<html>'));\n    await writer.write(encoder.encode('beta'));\n    await writer.write(encoder.encode('</html>'));\n    await writer.close();\n\n    // This variation uses the JavaScript TransformStream, so we can\n    // initiate the read after doing the writes.\n    const promise = response.text();\n    strictEqual(await promise, `<html>alpha beta gamma</html>`);\n  },\n};\n\nexport const streamingReplacementBadUTF8 = {\n  async test() {\n    const { readable, writable } = new TransformStream();\n\n    const response = new HTMLRewriter()\n      .on('*', {\n        async element(element) {\n          element.prepend(await fetch('data:,garbage%e2%28%a1'));\n        },\n      })\n      .transform(new Response(readable));\n\n    const writer = writable.getWriter();\n    const encoder = new TextEncoder();\n\n    await writer.write(encoder.encode('<html>'));\n    await writer.write(encoder.encode('bar'));\n    await writer.write(encoder.encode('</html>'));\n    await writer.close();\n\n    // This variation uses the JavaScript TransformStream, so we can\n    // initiate the read after doing the writes.\n    await rejects(response.text(), { message: 'Parser error: Invalid UTF-8' });\n  },\n};\n\nexport const appendOnEnd = {\n  async test() {\n    const kInput =\n      '<!doctype html><html><head></head><body><!----></body></html>';\n    const kSuffix = '<!-- an end comment -->';\n    const result = new HTMLRewriter()\n      .onDocument({\n        end(end) {\n          end.append(kSuffix, { html: true });\n        },\n      })\n      .transform(new Response(kInput));\n    strictEqual(await result.text(), kInput + kSuffix);\n  },\n};\n\nexport const interleavedAsyncHandlers = {\n  async test() {\n    class OneTimeBarrier {\n      constructor(limit) {\n        this.limit = limit;\n        this.current = 0;\n        let resolve;\n        this.promise = new Promise((r) => (resolve = r));\n        this.resolver = resolve;\n      }\n\n      async wait() {\n        this.current += 1;\n        if (this.current >= this.limit) {\n          this.resolver();\n        } else {\n          await this.promise;\n        }\n      }\n    }\n\n    const barrier = new OneTimeBarrier(2);\n    const responses = await Promise.all([\n      new HTMLRewriter()\n        .on('body', {\n          async element(e) {\n            await barrier.wait();\n            e.setInnerContent('foo bar');\n          },\n        })\n        .transform(new Response('<body>body value</body>'))\n        .text(),\n      new HTMLRewriter()\n        .on('body', {\n          async element(e) {\n            await barrier.wait();\n            e.remove();\n          },\n        })\n        .transform(\n          new Response('<!doctype html><html><head></head><body></body></html>')\n        )\n        .arrayBuffer(),\n    ]);\n\n    const body = responses[0];\n    const blank = responses[1];\n\n    const inserted = new HTMLRewriter()\n      .on('html', {\n        element(e) {\n          e.append(body, { html: true });\n        },\n      })\n      .transform(new Response(blank));\n\n    strictEqual(\n      await inserted.text(),\n      '<!doctype html><html><head></head><body>foo bar</body></html>'\n    );\n  },\n};\n\nexport const exceptionInHandler = {\n  async test() {\n    const response = new HTMLRewriter()\n      .on('*', {\n        text() {\n          throw new Error('boom');\n        },\n      })\n      .transform(new Response('<body>hello</body>'));\n\n    await rejects(response.text(), {\n      message: 'boom',\n    });\n  },\n};\n\nexport const exceptionInAsyncHandler = {\n  async test() {\n    const response = new HTMLRewriter()\n      .on('*', {\n        async text() {\n          throw new Error('boom');\n        },\n      })\n      .transform(new Response('<body>hello</body>'));\n\n    await rejects(response.text(), {\n      message: 'boom',\n    });\n  },\n};\n\nexport const invalidEncoding = {\n  async test() {\n    const response = new Response('hello', {\n      headers: {\n        'content-type': 'text/html; charset=invalid',\n      },\n    });\n\n    throws(\n      () => {\n        new HTMLRewriter().on('*', {}).transform(response);\n      },\n      {\n        message: 'Parser error: Unknown character encoding has been provided.',\n      }\n    );\n  },\n};\n\nexport const exceptionPropagation = {\n  async test() {\n    const { readable, writable } = new IdentityTransformStream();\n    const response = new HTMLRewriter().transform(new Response(readable));\n    response.body.cancel(new Error('boom'));\n\n    const writer = writable.getWriter();\n\n    const enc = new TextEncoder();\n\n    await writer.write(enc.encode('test'));\n\n    rejects(writer.write(enc.encode('test')), {\n      message: 'boom',\n    });\n  },\n};\n\n// handled HTMLRewriter errors must not poison the IoContext.\nexport const continueAfterException = {\n  async test() {\n    const errorResponse = new HTMLRewriter()\n      .on('*', {\n        element() {\n          throw new Error('intentional error for testing');\n        },\n      })\n      .transform(new Response('<div>test</div>'));\n\n    await rejects(errorResponse.text(), {\n      message: 'intentional error for testing',\n    });\n\n    const successResponse = new HTMLRewriter()\n      .on('div', {\n        element(el) {\n          el.setInnerContent('success');\n        },\n      })\n      .transform(new Response('<div>original</div>'));\n\n    strictEqual(await successResponse.text(), '<div>success</div>');\n  },\n};\n\nexport const continueAfterException2 = {\n  async test() {\n    const errorResponse = new HTMLRewriter()\n      .on('*', {\n        element() {\n          throw new Error('intentional error for pipeTo testing');\n        },\n      })\n      .transform(new Response('<div>test</div>'));\n\n    const { writable } = new TransformStream();\n\n    await rejects(errorResponse.body.pipeTo(writable), {\n      message: 'intentional error for pipeTo testing',\n    });\n\n    const successResponse = new HTMLRewriter()\n      .on('div', {\n        element(el) {\n          el.setInnerContent('success after pipeTo');\n        },\n      })\n      .transform(new Response('<div>original</div>'));\n\n    strictEqual(\n      await successResponse.text(),\n      '<div>success after pipeTo</div>'\n    );\n  },\n};\n\nexport const sameToken = {\n  async test() {\n    const obj = {};\n    let element;\n\n    const r = new HTMLRewriter()\n      .on('*', {\n        element(e) {\n          element = e;\n          strictEqual(e.hi, undefined);\n\n          e.hi = 'test';\n          strictEqual(e.hi, 'test');\n\n          e.hi = 'hi';\n          e.obj = obj;\n\n          e.replace('foo');\n        },\n      })\n      .on('img', {\n        element(e) {\n          notStrictEqual(e, element);\n          notStrictEqual(e.hi, 'hi');\n          notStrictEqual(e.obj, obj);\n\n          // The HTMLRewriter creates a fresh new Element/Doctype/Text\n          // object for each handler, thus `e.hi` will yield undefined even if we\n          // assigned it in the first handler.\n          // See https://jira.cfdata.org/browse/EW-2200.\n          e.replace(e.hi);\n        },\n      })\n      .transform(new Response('<img />'))\n      .text();\n\n    await r;\n  },\n};\n\n// Regression test for VULN-122672: HTMLRewriter AttributesIterator UAF.\n// When element attributes are modified during iteration (adding new attributes\n// that cause the underlying Vec to reallocate), the iterator's stale pointers\n// would read from freed memory. The fix invalidates all live iterators when\n// setAttribute() or removeAttribute() is called.\nexport const attributesIteratorInvalidatedOnSetAttribute = {\n  async test() {\n    const html = `<div a=\"1\" b=\"2\" c=\"3\">test</div>`;\n\n    const rewriter = new HTMLRewriter().on('div', {\n      element(el) {\n        const iter = el.attributes[Symbol.iterator]();\n\n        // Read first attribute - valid.\n        const first = iter.next();\n        strictEqual(first.done, false);\n\n        // Mutate attributes — this must invalidate the iterator.\n        el.setAttribute('newattr', 'value');\n\n        // Subsequent next() must throw, not read freed memory.\n        throws(() => iter.next(), {\n          message:\n            'The attributes of this element have been modified during iteration. ' +\n            'You must create a new iterator after modifying attributes.',\n        });\n      },\n    });\n\n    await rewriter.transform(new Response(html)).text();\n  },\n};\n\nexport const attributesIteratorInvalidatedOnRemoveAttribute = {\n  async test() {\n    const html = `<div a=\"1\" b=\"2\" c=\"3\">test</div>`;\n\n    const rewriter = new HTMLRewriter().on('div', {\n      element(el) {\n        const iter = el.attributes[Symbol.iterator]();\n        iter.next(); // consume first\n\n        // removeAttribute also must invalidate the iterator.\n        el.removeAttribute('b');\n\n        throws(() => iter.next(), {\n          message:\n            'The attributes of this element have been modified during iteration. ' +\n            'You must create a new iterator after modifying attributes.',\n        });\n      },\n    });\n\n    await rewriter.transform(new Response(html)).text();\n  },\n};\n\n// After mutation, creating a fresh iterator must work normally.\nexport const attributesIteratorFreshAfterMutation = {\n  async test() {\n    const html = `<div a=\"1\">test</div>`;\n    let attrs = [];\n\n    const rewriter = new HTMLRewriter().on('div', {\n      element(el) {\n        el.setAttribute('b', '2');\n\n        // A new iterator created after mutation should work fine.\n        for (const [name, value] of el.attributes) {\n          attrs.push([name, value]);\n        }\n      },\n    });\n\n    await rewriter.transform(new Response(html)).text();\n\n    // Should see both original and newly-set attribute.\n    strictEqual(attrs.length, 2);\n    deepStrictEqual(attrs[0], ['a', '1']);\n    deepStrictEqual(attrs[1], ['b', '2']);\n  },\n};\n\n// Iterating without mutation must still work as before.\nexport const attributesIteratorNormalIteration = {\n  async test() {\n    const html = `<div x=\"1\" y=\"2\" z=\"3\">test</div>`;\n    let attrs = [];\n\n    const rewriter = new HTMLRewriter().on('div', {\n      element(el) {\n        for (const [name, value] of el.attributes) {\n          attrs.push([name, value]);\n        }\n      },\n    });\n\n    await rewriter.transform(new Response(html)).text();\n\n    deepStrictEqual(attrs, [\n      ['x', '1'],\n      ['y', '2'],\n      ['z', '3'],\n    ]);\n  },\n};\n\nexport const svgNamespace = {\n  async test() {\n    const response = new Response(`\n    <svg><a /></svg>\n  `);\n    let namespace;\n\n    await new HTMLRewriter()\n      .on('a', {\n        element(e) {\n          namespace = e.namespaceURI;\n        },\n      })\n      .transform(response)\n      .text();\n\n    strictEqual(namespace, 'http://www.w3.org/2000/svg');\n  },\n};\n\nexport const handlerPerformingManySmallWrites = {\n  async test() {\n    const promiseDepth = 128 * 100;\n    const numReads = 2;\n    const input = new Response('<div></div>');\n\n    const output = new HTMLRewriter()\n      .onDocument({\n        end(e) {\n          for (let i = 0; i < promiseDepth; i++) {\n            e.append('<div></div>', { html: true });\n          }\n        },\n      })\n      .transform(input);\n\n    // Begin reading but do not fully consume\n    const reader = output.body.getReader();\n    for (let i = 0; i < numReads; i++) {\n      await reader.read();\n    }\n  },\n};\n\nexport const hugeNumberOfHandlersForAnElement = {\n  // Many small handlers\n  async test() {\n    const promiseDepth = 128 * 100;\n    const numReads = promiseDepth;\n\n    const input = new Response('<div></div>'.repeat(promiseDepth));\n\n    const output = new HTMLRewriter()\n      .on('div', {\n        element(e) {\n          e.append('<div></div>', { html: true });\n        },\n      })\n      .transform(input);\n\n    // Begin reading but do not fully consume\n    const reader = output.body.getReader();\n    for (let i = 0; i < numReads; i++) {\n      await reader.read();\n    }\n  },\n};\n\n// Test HTMLRewriter with JS-backed ReadableStream\n// This test was moved from streams-respond-test.js because it triggers\n// a flaky ASAN failure related to V8's cppgc memory validation.\nexport const htmlRewriterStream = {\n  async test() {\n    const enc = new TextEncoder();\n\n    const readable = new ReadableStream({\n      pull(controller) {\n        controller.enqueue(enc.encode('Hello World!'));\n        controller.close();\n      },\n    });\n\n    let response = new Response(readable, {\n      status: 200,\n      headers: {\n        'Content-Type': 'text/html; charset=utf-8',\n      },\n    });\n\n    response = new HTMLRewriter().transform(response);\n\n    strictEqual(await response.text(), 'Hello World!');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/htmlrewriter-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"htmlrewriter-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"htmlrewriter-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"experimental\", \"streams_enable_constructors\", \"transformstream_enable_standard_constructor\", \"original-transform-stream-backpressure\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/htmlrewriter-transform-cancel-test.js",
    "content": "export const HtmlRewriterCancelBeforeRead = {\n  async test() {\n    for (let i = 0; i < 50; i++) {\n      const rewriter = new HTMLRewriter().on('div', {\n        element(element) {\n          element.setInnerContent('<span>x</span>', { html: true });\n        },\n      });\n\n      const inputStream = new ReadableStream({\n        start(controller) {\n          controller.enqueue(\n            new TextEncoder().encode(\n              '<html><body><div>content</div></body></html>'\n            )\n          );\n          controller.close();\n        },\n      });\n\n      const transformed = rewriter.transform(new Response(inputStream));\n      transformed.body.cancel();\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/htmlrewriter-transform-cancel-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"htmlrewriter-transform-cancel-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"htmlrewriter-transform-cancel-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"streams_enable_constructors\", \"original-transform-stream-backpressure\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/http-socket-server.js",
    "content": "// Copyright (c) 2017-2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// This file is used as a sidecar for the http-socket tests.\n// It creates an HTTP server that will respond to requests from the convertSocketToFetcher API.\nconst http = require('node:http');\nconst crypto = require('node:crypto');\nconst net = require('node:net');\nconst tls = require('node:tls');\nconst assert = require('node:assert');\n\n// Handle upgrade requests to switch from HTTP to WebSocket protocol\nfunction upgradeToWebSocketConnection(req, socket, head) {\n  // Check if it's a WebSocket upgrade request\n  if (req.headers['upgrade'] !== 'websocket') {\n    socket.end('HTTP/1.1 400 Bad Request');\n    return;\n  }\n\n  //console.log('WebSocket upgrade request received');\n  //console.log('WebSocket headers:', req.headers);\n\n  // Get the WebSocket key from the client\n  const webSocketKey = req.headers['sec-websocket-key'];\n  const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; // WebSocket protocol GUID\n\n  // Create the accept key by concatenating the key and GUID, then hashing\n  const acceptKey = crypto\n    .createHash('sha1')\n    .update(webSocketKey + GUID)\n    .digest('base64');\n\n  // Write WebSocket handshake response headers\n  socket.write(\n    'HTTP/1.1 101 Switching Protocols\\r\\n' +\n      'Upgrade: websocket\\r\\n' +\n      'Connection: Upgrade\\r\\n' +\n      `Sec-WebSocket-Accept: ${acceptKey}\\r\\n` +\n      '\\r\\n'\n  );\n\n  // Socket is now a WebSocket connection\n  handleWebSocketConnection(socket);\n}\n\n// Function to send a message to the client\nfunction sendMessage(socket, message) {\n  const payload = Buffer.from(message);\n  const payloadLength = payload.length;\n\n  // Create frame header\n  let header;\n\n  if (payloadLength <= 125) {\n    header = Buffer.alloc(2);\n    header[1] = payloadLength;\n  } else if (payloadLength <= 65535) {\n    header = Buffer.alloc(4);\n    header[1] = 126;\n    header.writeUInt16BE(payloadLength, 2);\n  } else {\n    header = Buffer.alloc(10);\n    header[1] = 127;\n    // Write length as 64-bit integer (simplified here)\n    header.writeBigUInt64BE(BigInt(payloadLength), 2);\n  }\n\n  // Set the first byte: FIN bit (0x80) + opcode 0x01 for text data\n  header[0] = 0x81;\n\n  // Combine header and payload\n  const frame = Buffer.concat([header, payload]);\n\n  // Send the frame\n  socket.write(frame);\n}\n\n// Parse WebSocket frames to extract message data\nfunction parseWebSocketFrame(buffer) {\n  if (buffer.length < 2) return null;\n\n  const isFinalFrame = !!(buffer[0] & 0x80);\n  const opcode = buffer[0] & 0x0f;\n  const isMasked = !!(buffer[1] & 0x80);\n  let payloadLength = buffer[1] & 0x7f;\n\n  let maskingKeyOffset = 2;\n  if (payloadLength === 126) {\n    payloadLength = buffer.readUInt16BE(2);\n    maskingKeyOffset = 4;\n  } else if (payloadLength === 127) {\n    payloadLength = Number(buffer.readBigUInt64BE(2));\n    maskingKeyOffset = 10;\n  }\n\n  if (!isMasked) {\n    // According to the spec, client messages must be masked\n    return {\n      opcode,\n      payload: Buffer.alloc(0),\n      isControl: (opcode & 0x8) !== 0,\n    };\n  }\n\n  const maskingKey = buffer.slice(maskingKeyOffset, maskingKeyOffset + 4);\n  const payloadOffset = maskingKeyOffset + 4;\n\n  if (buffer.length < payloadOffset + payloadLength) {\n    return null; // Not enough data\n  }\n\n  const payload = Buffer.alloc(payloadLength);\n  for (let i = 0; i < payloadLength; i++) {\n    payload[i] = buffer[payloadOffset + i] ^ maskingKey[i % 4];\n  }\n\n  return {\n    opcode,\n    payload,\n    isControl: (opcode & 0x8) !== 0,\n  };\n}\n\n// Create HTTP server\nconst server = http.createServer((req, res) => {\n  //console.log(`Received request: ${req.method} ${req.url}`);\n\n  if (req.url === '/ping') {\n    res.writeHead(200, { 'Content-Type': 'text/plain' });\n    res.end('pong');\n  } else if (req.url === '/json') {\n    res.writeHead(200, { 'Content-Type': 'application/json' });\n    res.end(JSON.stringify({ message: 'Hello from HTTP socket server' }));\n  } else if (req.url === '/echo' && req.method === 'POST') {\n    let body = '';\n    req.on('data', (chunk) => {\n      body += chunk.toString();\n    });\n    req.on('end', () => {\n      res.writeHead(200, { 'Content-Type': 'text/plain' });\n      res.end(body);\n    });\n  } else if (req.url === '/headers') {\n    const headers = {};\n    for (const [key, value] of Object.entries(req.headers)) {\n      headers[key] = value;\n    }\n    res.writeHead(200, { 'Content-Type': 'application/json' });\n    res.end(JSON.stringify(headers));\n  } else if (req.url === '/status/404') {\n    res.writeHead(404, { 'Content-Type': 'text/plain' });\n    res.end('Not Found');\n  } else if (req.url === '/status/500') {\n    res.writeHead(500, { 'Content-Type': 'text/plain' });\n    res.end('Internal Server Error');\n  } else if (req.url === '/drop') {\n    res.socket.drop();\n  } else if (req.url === '/destroy') {\n    res.socket.destroy();\n  } else if (req.url === '/redirect') {\n    res.writeHead(301, { Location: '/ping' });\n    res.end('Moved Permanently');\n  } else {\n    res.writeHead(404, { 'Content-Type': 'text/plain' });\n    res.end('Not Found');\n  }\n});\n\n// Handle WebSocket upgrade requests\nserver.on('upgrade', upgradeToWebSocketConnection);\n\n// Function to handle WebSocket connections\nfunction handleWebSocketConnection(socket) {\n  //console.log('WebSocket connection established');\n\n  // Send a welcome message\n  sendMessage(socket, 'Welcome to WebSocket server');\n\n  // Handle incoming data\n  let buffer = Buffer.alloc(0);\n  socket.on('data', (data) => {\n    buffer = Buffer.concat([buffer, data]);\n\n    // Process frames until we can't anymore\n    let frame;\n    while ((frame = parseWebSocketFrame(buffer))) {\n      // Handle different frame types\n      if (frame.isControl) {\n        if (frame.opcode === 0x8) {\n          // Close frame\n          socket.end();\n          return;\n        }\n        // Skip other control frames\n        continue;\n      }\n\n      // For text/binary frames, echo the message back\n      if (frame.opcode === 0x1) {\n        // Text frame\n        const message = frame.payload.toString('utf8');\n        //console.log('Received WebSocket message:', message);\n\n        // Echo the message back\n        sendMessage(socket, `Echo: ${message}`);\n      }\n\n      // Remove processed frame from buffer\n      const frameSize =\n        frame.payload.length + (frame.payload.length > 125 ? 4 : 2) + 4; // header + masking key + payload\n      buffer = buffer.slice(frameSize);\n    }\n  });\n\n  // Handle socket close\n  socket.on('end', () => {\n    //console.log('WebSocket connection closed');\n  });\n\n  socket.on('error', (error) => {\n    console.error('WebSocket error:', error);\n  });\n}\n\nserver.listen(process.env.HTTP_SOCKET_SERVER_PORT, () => {\n  console.info(\n    `HTTP Socket test server listening on port ${server.address().port}`\n  );\n});\n\n// This socket grabs connections and immediately drop them\nconst dropServer = net.createServer((socket) => {\n  socket.on('error', (err) => {\n    console.log('DROP: ' + err.name);\n    console.log('DROP: ' + err.message);\n  });\n  var ready = true;\n  // Repeatedly send a page of data till the socket is borked\n  const repeatedString = 'A'.repeat(4_096);\n  while (ready) {\n    ready = socket.write(repeatedString + '\\n');\n  }\n  socket.write(repeatedString + '\\n');\n});\n\ndropServer.listen(process.env.SOCKET_PARTIALLY_WRITTEN, () => {\n  console.info(\n    `Drop Socket test server listening on port ${dropServer.address().port}`\n  );\n});\n\n// Flush Hello Socket server that checks for hello message and responds with HTTP pong\nconst flushHelloServer = net.createServer((socket) => {\n  let receivedHello = false;\n  let buffer = Buffer.alloc(0);\n\n  socket.on('data', (data) => {\n    buffer = Buffer.concat([buffer, data]);\n    const message = buffer.toString().trim();\n\n    if (!receivedHello && message.includes('Hello')) {\n      receivedHello = true;\n      // Clear the buffer after processing hello\n      buffer = Buffer.alloc(0);\n      return;\n    }\n\n    if (receivedHello) {\n      // Respond with HTTP pong response\n      const httpResponse =\n        'HTTP/1.1 200 OK\\r\\nContent-Type: text/plain\\r\\nContent-Length: 4\\r\\n\\r\\npong';\n      socket.write(httpResponse);\n      socket.end();\n    }\n  });\n\n  socket.on('error', (err) => {\n    console.log('FLUSH_HELLO error:', err.message);\n  });\n});\n\nflushHelloServer.listen(process.env.FLUSH_HELLO_SOCKET, () => {\n  console.info(\n    `Flush Hello Socket server listening on port ${flushHelloServer.address().port}`\n  );\n});\n\n// Create a self-signed certificate for TLS with proper SAN extension\nfunction createSelfSignedCert() {\n  const key = `-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDIWfGy2tRsqANt\nJ1F/52bIDzMDxlmSkDpu3U3Ehq6TmH2hNBcLOWuWLvG8Np9artnzk8QnodfN8yEJ\n0HRzZ6mRjVIUHJOb3+L1+0ePOM8dtWvG0AOd95K0T0imJRLPR18UjHl5OLE7mMS9\nCGHa6mDTGKzTcdxtpkjiyoNgfdKKSKzLplga5if36leGJ2+mEhOAc/cV1kqOx+hP\nVGAOz5p3OnkgolC8hZ3WTsAFEYMU0QoPNs7jVCVNGH9t3qPiWV2/7XaNQoMnMHgq\nyyljcvDo0O0dw0HKtBVIMhz5xHHGZqJNM/R36MeHzO+cIYhJz+ncknu62+IlQOCY\neyHuxvpRAgMBAAECggEAB3SXYqsze+6doAZ2SS7se3XbVWDgbOyKjB0Wm4FShkIG\nrMTCNcP3fbF2A+W5dNesWxzM0Be87thFCrcz2iaJoBW07/QnRwXkDXjCDzGTPX0G\ni3GqrMptbmHD55DaHBYBEwPuMkVajQfwjEM/VvThUQGqTrz+MaNeM3hLPsA34Tbl\nwigHK+4tyAFLkYkvsxXYHs1F23ey2ubFUyBI8gvfayOvr4MOVfEbQZsmz3IB5jjk\noK5EaMJuhty65pb9Pi6ncSbfVQ2aciNgHjZs/is/WQfQPB2jYBPpmaFQbZc2pseZ\n8zTLvF+GKZng4hQE1F+F6DUf9sxb54Eu1XqzqGlrdQKBgQDkQc5fJj72rF9RkiBN\nzeEK0Ngihm9Jzsb544i7DT/4jPWwa1+dt+kNVqguDbcxi4tCi+P8BwTnfX5M31d2\n/Si9nDBpP+WLLRHjFq2AQf05JvFc1/7s4KvWBrfKbwiN+uxrstB2lDuFp386B20U\nstsCBu/nawjt5lYY7S0zPokkJQKBgQDgs9rTJNnRuaaEpUxqRdR6zR3z0Qe1B1Ga\ny6z8OqiX3N+wM9uAcrTzGOtPKvB4glcJZrrQp0NSxkJVsFBzPBCfUfjIV/IiOqS1\nnE/rrEKEG7ZVkxDUsdeS8KiVKBJjLch/hrT0udgv4vndXqeaJuNzbD1yfiKbPnY5\nyGC78uqvvQKBgGUGMx6twMRQekeSEzYcXvP4hxCQy4SxPiOvbv7K2HtbeApDG6ik\nk0NSDVGExIXrKxGi9J7BRIxoYJQJbZ6+YV+6VzreCuxUYExP5y6TBk5bTAw5lRym\nO6eYhZPVHMYqPqVUGSvCY629+nNmggLdPk1hYKDeIK+aeJTDtHOvw+b5AoGAZI74\nwf8+34WWyMv026ZuhZpf6ipEqbYhxgWaX7KcmoHFNWSvudcbtaMUQ3Sy8ytZaiKo\nPhJspZGGRDTIfBmIUtRrYrVA7iKSbZgLiCuqBNcmDTvoj1cbY24B8+Zf/DSUAsY1\nG0RERIHuUiw3E1yN86yf/yoFsLYOUKOk7teyQX0CgYBFUzUeVszODI/1NeKuGdoR\n1uJk2gt7hH4t7GdX4G78h3i6P5pa7qSyUXqBm53nXrYhEFRiVgDh5BoRnoKKWomj\nIDidHZX3fMoQvdKAT8sUbT/Q3KKWPFqGv7frdpQe+JM0DwwjjPrMtUWhuve455XW\nbCKgoxoaqEPUzY9CyI+RZg==\n-----END PRIVATE KEY-----`;\n\n  const cert = `-----BEGIN CERTIFICATE-----\nMIIDmTCCAoGgAwIBAgIUFdaq1aN7zHoSsmLp6ItxnM1VOPEwDQYJKoZIhvcNAQEL\nBQAwTjELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFRlc3QxDTALBgNVBAcMBFRlc3Qx\nDTALBgNVBAoMBFRlc3QxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yNTA3MjYwNjI3\nMDZaFw0yNjA3MjYwNjI3MDZaME4xCzAJBgNVBAYTAlVTMQ0wCwYDVQQIDARUZXN0\nMQ0wCwYDVQQHDARUZXN0MQ0wCwYDVQQKDARUZXN0MRIwEAYDVQQDDAlsb2NhbGhv\nc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIWfGy2tRsqANtJ1F/\n52bIDzMDxlmSkDpu3U3Ehq6TmH2hNBcLOWuWLvG8Np9artnzk8QnodfN8yEJ0HRz\nZ6mRjVIUHJOb3+L1+0ePOM8dtWvG0AOd95K0T0imJRLPR18UjHl5OLE7mMS9CGHa\n6mDTGKzTcdxtpkjiyoNgfdKKSKzLplga5if36leGJ2+mEhOAc/cV1kqOx+hPVGAO\nz5p3OnkgolC8hZ3WTsAFEYMU0QoPNs7jVCVNGH9t3qPiWV2/7XaNQoMnMHgqyylj\ncvDo0O0dw0HKtBVIMhz5xHHGZqJNM/R36MeHzO+cIYhJz+ncknu62+IlQOCYeyHu\nxvpRAgMBAAGjbzBtMB0GA1UdDgQWBBQ6R0NLwjufqWjT75cFdzHW9bh2DDAfBgNV\nHSMEGDAWgBQ6R0NLwjufqWjT75cFdzHW9bh2DDAPBgNVHRMBAf8EBTADAQH/MBoG\nA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAToR4\nCaI9HAfSSXE+6fPthp+qrwPmfx3rW0RskpjKuqemZmIK7ydU9pcYuGtc6CPen614\nRfFoaWtPltbNU0KV79P4zTRNYxqTKcEyhsjyAGbLA+bJtJE3hlDrfPGVyepZETXE\n7Ig4XPyXi4M+WmvLboAF2dHC+H1XoWp3agIN45VRnr5uPVNX19dTbr0gc3WxLEUH\nN839sKGVB9GVaQhF/4Z8ia0bluirf+6SNAaN/veJA40ixGEkHN3gqX4ZTZWl5rji\n3cLdth83wmueKxiBp8ov78ubmdPiBsyIVrsb8jEsxRPAKX8gEx09S/yIIs1ZEGi9\nwG73FlfFCc09zjmYww==\n-----END CERTIFICATE-----`;\n\n  return { key, cert };\n}\n\n// STARTTLS Socket server that implements proper handshake protocol\nconst startTlsSocketServer = net.createServer((s) => {\n  console.log('STARTTLS_SOCKET: New connection received');\n\n  // Send initial greeting\n  s.write('HELLO\\n');\n\n  // Wait for one response then upgrade to TLS\n  s.once('data', (data) => {\n    const response = data.toString().trim();\n    console.log('STARTTLS_SOCKET: Received response:', response);\n\n    if (response === 'HELLO_BACK') {\n      s.write('START_TLS\\n', () => {\n        console.log('STARTTLS_SOCKET: Sent START_TLS, upgrading to TLS');\n\n        // Small delay to ensure START_TLS is sent\n        console.log('STARTTLS_SOCKET: Creating TLS socket');\n        const tlsSocket = new tls.TLSSocket(s, {\n          isServer: true,\n          server: startTlsSocketServer,\n          secureContext: tls.createSecureContext(createSelfSignedCert()),\n          requestCert: false,\n          SNICallback: (hostname, callback) => {\n            console.log(\n              'STARTTLS_SOCKET: SNI callback for hostname:',\n              hostname\n            );\n            callback(null, null);\n          },\n        });\n\n        console.log('STARTTLS_SOCKET: Setting up TLS event handlers');\n\n        tlsSocket.on('secure', () => {\n          console.log('STARTTLS_SOCKET: TLS handshake complete');\n\n          // Handle TLS data\n          tlsSocket.on('data', (data) => {\n            const message = data.toString().trim();\n            console.log('STARTTLS_SOCKET: Received TLS message:', message);\n\n            if (message === 'ping') {\n              console.log('STARTTLS_SOCKET: Sending pong response');\n              tlsSocket.write('pong\\n', (err) => {\n                if (err) {\n                  console.log('STARTTLS_SOCKET: Error writing pong:', err);\n                } else {\n                  console.log('STARTTLS_SOCKET: Pong sent successfully');\n                }\n              });\n            } else if (message.includes('GET') || message.includes('POST')) {\n              // Handle HTTP requests over TLS\n              console.log('STARTTLS_SOCKET: TLS HTTP request:', message);\n              const httpResponse =\n                'HTTP/1.1 200 OK\\r\\nContent-Type: text/plain\\r\\nContent-Length: 4\\r\\n\\r\\npong';\n              tlsSocket.write(httpResponse);\n            }\n          });\n        });\n\n        tlsSocket.on('error', (err) => {\n          console.log('STARTTLS_SOCKET TLS error:', err.message);\n        });\n\n        tlsSocket.on('close', () => {\n          console.log('STARTTLS_SOCKET: TLS socket closed');\n        });\n\n        console.log(\n          'STARTTLS_SOCKET: TLS socket created, waiting for handshake'\n        );\n\n        // The TLS handshake should start when the client initiates it\n      });\n    }\n  });\n\n  s.on('error', (err) => {\n    console.log('STARTTLS_SOCKET socket error:', err.message);\n  });\n});\n\nstartTlsSocketServer.listen(process.env.STARTTLS_SOCKET, () => {\n  console.info(\n    `STARTTLS Socket server listening on port ${startTlsSocketServer.address().port}`\n  );\n});\n"
  },
  {
    "path": "src/workerd/api/tests/http-socket-test.js",
    "content": "// Copyright (c) 2017-2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { connect, internalNewHttpClient } from 'cloudflare:sockets';\nimport { strict as assert } from 'node:assert';\nimport { setTimeout as sleep } from 'node:timers/promises';\n\n// Basic connectivity and GET test\nexport const oneRequest = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.HTTP_SOCKET_SERVER_PORT}`);\n    const httpClient = await internalNewHttpClient(socket);\n\n    const response = await httpClient.fetch('https://example.com/ping');\n    assert.equal(response.status, 200);\n    const text = await response.text();\n    assert.equal(text, 'pong');\n  },\n};\n\nexport const jsonResponse = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.HTTP_SOCKET_SERVER_PORT}`);\n    const httpClient = await internalNewHttpClient(socket);\n\n    const response = await httpClient.fetch('https://example.com/json');\n    assert.equal(response.status, 200);\n    assert.equal(response.headers.get('content-type'), 'application/json');\n    const data = await response.json();\n    assert.deepEqual(data, { message: 'Hello from HTTP socket server' });\n  },\n};\n\nexport const postRequest = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.HTTP_SOCKET_SERVER_PORT}`);\n    const httpClient = await internalNewHttpClient(socket);\n\n    const postData = 'Hello, world!';\n    const response = await httpClient.fetch('https://example.com/echo', {\n      method: 'POST',\n      body: postData,\n    });\n    assert.equal(response.status, 200);\n    const text = await response.text();\n    assert.equal(text, postData);\n  },\n};\n\nexport const customHeaders = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.HTTP_SOCKET_SERVER_PORT}`);\n    const httpClient = await internalNewHttpClient(socket);\n\n    const response = await httpClient.fetch('https://example.com/headers', {\n      headers: {\n        'X-Custom-Header': 'custom-value',\n        'X-Another-Header': 'another-value',\n      },\n    });\n    assert.equal(response.status, 200);\n    const headers = await response.json();\n    assert.equal(headers['x-custom-header'], 'custom-value');\n    assert.equal(headers['x-another-header'], 'another-value');\n  },\n};\n\nexport const response404 = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.HTTP_SOCKET_SERVER_PORT}`);\n    const httpClient = await internalNewHttpClient(socket);\n\n    const response = await httpClient.fetch('https://example.com/status/404');\n    assert.equal(response.status, 404);\n    const text = await response.text();\n    assert.equal(text, 'Not Found');\n  },\n};\n\nexport const response500 = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.HTTP_SOCKET_SERVER_PORT}`);\n    const httpClient = await internalNewHttpClient(socket);\n\n    const response = await httpClient.fetch('https://example.com/status/500');\n    assert.equal(response.status, 500);\n    const text = await response.text();\n    assert.equal(text, 'Internal Server Error');\n  },\n};\n\nexport const redirect301 = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.HTTP_SOCKET_SERVER_PORT}`);\n    const httpClient = await internalNewHttpClient(socket);\n\n    // TODO(cleanup) when enabling multiple fetches we can only then do redirects.\n    const response = await httpClient.fetch('https://example.com/redirect');\n    assert.equal(response.status, 200);\n    const text = await response.text();\n    assert.equal(text, 'pong');\n  },\n};\n\nexport const multipleRequests = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.HTTP_SOCKET_SERVER_PORT}`);\n    const httpClient = await internalNewHttpClient(socket);\n\n    // First request\n    const response1 = await httpClient.fetch('https://example.com/ping');\n    assert.equal(response1.status, 200);\n    const text1 = await response1.text();\n    assert.equal(text1, 'pong');\n\n    // Second request on same connection\n    const response2 = await httpClient.fetch('https://example.com/json');\n    assert.equal(response2.status, 200);\n    const data = await response2.json();\n    assert.deepEqual(data, { message: 'Hello from HTTP socket server' });\n  },\n};\n\nexport const multipleConcurrentRequests = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.HTTP_SOCKET_SERVER_PORT}`);\n    const httpClient = await internalNewHttpClient(socket);\n\n    // TODO(cleanup) when multiple fetches are enabled make sure this message is changed\n    await assert.rejects(\n      Promise.all([\n        httpClient.fetch('https://example.com/ping', {\n          signal: AbortSignal.timeout(500),\n        }),\n        httpClient.fetch('https://example.com/json', {\n          signal: AbortSignal.timeout(500),\n        }),\n      ]),\n      {\n        name: 'Error',\n        message: /internal error/,\n      }\n    );\n  },\n};\n\n// Test that demonstrates socket is unusable after creating HTTP client\nexport const socketFetcherUnusable = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.HTTP_SOCKET_SERVER_PORT}`);\n    const httpClient = await internalNewHttpClient(socket);\n\n    // First, successfully use the HTTP client\n    const response = await httpClient.fetch('https://example.com/ping');\n    assert.equal(response.status, 200);\n\n    // Now try to use the socket directly - this should fail because the streams are detached\n    assert.throws(\n      () => {\n        socket.writable.getWriter();\n      },\n      {\n        name: 'TypeError',\n        message: 'This WritableStream is currently locked to a writer.',\n      }\n    );\n\n    assert.throws(\n      () => {\n        socket.readable.getReader();\n      },\n      {\n        name: 'TypeError',\n        message: 'This ReadableStream is currently locked to a reader.',\n      }\n    );\n  },\n};\n\n// Test that closing the socket does not affect the HTTP client\nexport const socketCloseThenFetch = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.HTTP_SOCKET_SERVER_PORT}`);\n    const httpClient = await internalNewHttpClient(socket);\n    // Now close the socket\n    await socket.close();\n\n    // Try to send a request with the HTTP client after closing the socket\n    // This should still work since the HTTP client should have its own stream\n    const response1 = await httpClient.fetch('https://example.com/ping');\n    assert.equal(response1.status, 200);\n  },\n};\n\n// Test that locking a reader before creating the stream has the right error.\nexport const lockReaderFail = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.HTTP_SOCKET_SERVER_PORT}`);\n    const reader = socket.readable.getReader();\n    await assert.rejects(internalNewHttpClient(socket), {\n      name: 'TypeError',\n      message: 'The ReadableStream has been locked to a reader.',\n    });\n  },\n};\n\n// Test that locking a writer before creating the stream has the right error.\nexport const lockWriterFail = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.HTTP_SOCKET_SERVER_PORT}`);\n    const writer = socket.writable.getWriter();\n    await assert.rejects(internalNewHttpClient(socket), {\n      name: 'TypeError',\n      message: 'This WritableStream is currently locked to a writer.',\n    });\n  },\n};\n\n/* TODO(soon) this test does not test the behavior properly\n// Test that queueing up writes before creating the stream throws an error.\nexport const writeThenConvertFlushes = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.FLUSH_HELLO_SOCKET}`);\n    const writer = socket.writable.getWriter();\n    const encoder = new TextEncoder();\n    await writer.write(encoder.encode('Hello'));\n    writer.releaseLock();\n    const httpClient = await internalNewHttpClient(socket);\n\n    const response = await httpClient.fetch('https://example.com/ping');\n    assert.equal(response.status, 200);\n    const text = await response.text();\n    assert.equal(text, 'pong');\n  },\n};\n*/\n\n/* TODO(soon) whenever we support inspecting the readable stream for data.\nexport const errorRemoteWrites = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.SOCKET_PARTIALLY_WRITTEN}`);\n    await sleep(2000);\n    const httpClient = await internalNewHttpClient(socket);\n    await assert.rejects(\n      httpClient.fetch('https://example.com/ping', {\n        signal: AbortSignal.timeout(500),\n      }),\n      {\n        name: 'Error',\n        message: \"Readable stream in http protocol starts empty, remote side wrote data that was not consumed\",\n      }\n    );\n  },\n};\n*/\n\n// Test WebSocket connection over the converted Socket\nexport const websockets = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.HTTP_SOCKET_SERVER_PORT}`);\n    const httpClient = await internalNewHttpClient(socket);\n\n    const res = await fetch(\n      `http://localhost:${env.HTTP_SOCKET_SERVER_PORT}/`,\n      {\n        headers: {\n          Connection: 'Upgrade',\n          Upgrade: 'websocket',\n        },\n      }\n    );\n\n    const ws = res.webSocket;\n\n    if (!ws) {\n      throw new Error('WebSocket upgrade failed');\n    }\n\n    ws.accept();\n    ws.send('Hello from test client');\n\n    // Test the WebSocket connection\n    await new Promise((resolve, reject) => {\n      // Set a timeout for the test\n      const timeout = setTimeout(() => {\n        reject(new Error('WebSocket test timed out after 5 seconds'));\n      }, 5000);\n\n      ws.addEventListener('message', (event) => {\n        // Verify we got the welcome message\n        if (event.data === 'Welcome to WebSocket server') {\n        } else if (event.data.startsWith('Echo:')) {\n          clearTimeout(timeout);\n          ws.close();\n          resolve();\n        }\n      });\n\n      ws.addEventListener('error', (error) => {\n        clearTimeout(timeout);\n        reject(\n          new Error(`WebSocket error: ${error.message || 'Unknown error'}`)\n        );\n      });\n\n      ws.addEventListener('close', (event) => {\n        if (event && !event.wasClean) {\n          clearTimeout(timeout);\n          reject(\n            new Error(\n              `WebSocket closed abnormally with code ${event.code || 'unknown'}`\n            )\n          );\n        }\n      });\n    });\n  },\n};\n\n// Test remote end destroys socket.\nexport const destroyRemote = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.HTTP_SOCKET_SERVER_PORT}`);\n    const httpClient = await internalNewHttpClient(socket);\n    await assert.rejects(httpClient.fetch('https://example.com/destroy'), {\n      name: 'Error',\n      message: 'Network connection lost.',\n    });\n  },\n};\n\n// Remote destroy first then await internalNewHttpClient\n// Check if connect freezes or if a socket doesn't\n\n// Test remote end drops socket\nexport const dropRemote = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.HTTP_SOCKET_SERVER_PORT}`);\n    const httpClient = await internalNewHttpClient(socket);\n    await assert.rejects(\n      httpClient.fetch('https://example.com/drop', {\n        signal: AbortSignal.timeout(500),\n      }),\n      {\n        name: 'TimeoutError',\n        message: 'The operation was aborted due to timeout',\n      }\n    );\n    /* TODO(cleanup) after supporting multiple fetches this should throw a recognizable error\n    response = await httpClient.fetch('https://example.com/ping');\n    */\n  },\n};\n\nexport const connectOnFetcherThrows = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.HTTP_SOCKET_SERVER_PORT}`);\n    const httpClient = await internalNewHttpClient(socket);\n\n    assert.throws(\n      () => {\n        httpClient.connect('localhost:8080');\n      },\n      {\n        name: 'TypeError',\n        message:\n          'connect is not something that can be done on a fetcher converted from a socket',\n      }\n    );\n  },\n};\n\nexport const startTlsSuccess = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.STARTTLS_SOCKET}`, {\n      secureTransport: 'starttls',\n    });\n\n    const writer = socket.writable.getWriter();\n    const reader = socket.readable.getReader();\n    const encoder = new TextEncoder();\n    const decoder = new TextDecoder();\n\n    try {\n      // Read HELLO greeting\n      const { value: greeting } = await reader.read();\n      const greetingText = decoder.decode(greeting).trim();\n      console.log('startTlsSuccess: Received greeting:', greetingText);\n\n      if (greetingText === 'HELLO') {\n        console.log('startTlsSuccess: Sending HELLO_BACK');\n        await writer.write(encoder.encode('HELLO_BACK\\n'));\n\n        // Read START_TLS signal\n        const { value: signal } = await reader.read();\n        const signalText = decoder.decode(signal).trim();\n        console.log('startTlsSuccess: Received signal:', signalText);\n\n        if (signalText === 'START_TLS') {\n          console.log('startTlsSuccess: Received START_TLS, upgrading to TLS');\n\n          // Release the reader and writer before upgrading\n          reader.releaseLock();\n          writer.releaseLock();\n\n          // Upgrade to TLS\n          console.log('startTlsSuccess: About to start TLS');\n          const tlsSocket = socket.startTls();\n          console.log('startTlsSuccess: Started TLS');\n\n          await tlsSocket.opened;\n          console.log(\n            'startTlsSuccess: TLS connection established successfully'\n          );\n\n          const httpClient = await internalNewHttpClient(tlsSocket);\n          const response = await httpClient.fetch('https://example.com/ping');\n          assert.equal(response.status, 200);\n          const text = await response.text();\n          assert.equal(text, 'pong');\n        }\n      }\n    } catch (err) {\n      console.log('startTlsSuccess: Error:', err.message);\n      throw err;\n    }\n  },\n};\n\nexport const startTlsEarlyConvert = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.STARTTLS_SOCKET}`, {\n      secureTransport: 'starttls',\n    });\n\n    const writer = socket.writable.getWriter();\n    const reader = socket.readable.getReader();\n    const encoder = new TextEncoder();\n    const decoder = new TextDecoder();\n\n    const { value: greeting } = await reader.read();\n    const greetingText = decoder.decode(greeting).trim();\n\n    if (greetingText !== 'HELLO') throw 'Wrong Handshake';\n    await writer.write(encoder.encode('HELLO_BACK\\n'));\n\n    // Read START_TLS signal\n    const { value: signal } = await reader.read();\n    const signalText = decoder.decode(signal).trim();\n    if (signalText !== 'START_TLS') throw 'Cannot Start TLS';\n\n    // Release the reader and writer before upgrading\n    reader.releaseLock();\n    writer.releaseLock();\n\n    const tlsSocket = socket.startTls();\n    await assert.rejects(internalNewHttpClient(socket), {\n      name: 'TypeError',\n      message: 'This WritableStream is currently locked to a writer.',\n    });\n    const httpClient = await internalNewHttpClient(tlsSocket);\n    await tlsSocket.opened;\n    const response = await httpClient.fetch('https://example.com/ping');\n    assert.equal(response.status, 200);\n    const text = await response.text();\n    assert.equal(text, 'pong');\n  },\n};\n\nexport const startTlsEarlySend = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.STARTTLS_SOCKET}`, {\n      secureTransport: 'starttls',\n    });\n\n    const writer = socket.writable.getWriter();\n    const reader = socket.readable.getReader();\n    const encoder = new TextEncoder();\n    const decoder = new TextDecoder();\n\n    const { value: greeting } = await reader.read();\n    const greetingText = decoder.decode(greeting).trim();\n\n    if (greetingText !== 'HELLO') throw 'Wrong Handshake';\n    await writer.write(encoder.encode('HELLO_BACK\\n'));\n\n    // Read START_TLS signal\n    const { value: signal } = await reader.read();\n    const signalText = decoder.decode(signal).trim();\n    if (signalText !== 'START_TLS') throw 'Cannot Start TLS';\n\n    // Release the reader and writer before upgrading\n    reader.releaseLock();\n    writer.releaseLock();\n\n    const tlsSocket = socket.startTls();\n    const httpClient = await internalNewHttpClient(tlsSocket);\n    const response = await httpClient.fetch('https://example.com/ping');\n    assert.equal(response.status, 200);\n    const text = await response.text();\n    assert.equal(text, 'pong');\n  },\n};\n\nexport const manualProtocolThenFetcher = {\n  async test(ctrl, env, ctx) {\n    const socket = connect(`localhost:${env.HTTP_SOCKET_SERVER_PORT}`);\n    const writer = socket.writable.getWriter();\n    const reader = socket.readable.getReader();\n    const encoder = new TextEncoder();\n    const decoder = new TextDecoder();\n\n    // Manually construct and send HTTP request\n    const httpRequest =\n      'GET /ping HTTP/1.1\\r\\n' +\n      'Host: example.com\\r\\n' +\n      'Connection: keep-alive\\r\\n' +\n      '\\r\\n';\n\n    await writer.write(encoder.encode(httpRequest));\n\n    // Read the HTTP response manually\n    let responseData = '';\n    let contentLength = 0;\n    let headersParsed = false;\n\n    while (true) {\n      const { value, done } = await reader.read();\n      if (done) break;\n\n      const chunk = decoder.decode(value, { stream: true });\n      responseData += chunk;\n\n      if (!headersParsed && responseData.includes('\\r\\n\\r\\n')) {\n        const [headers, body] = responseData.split('\\r\\n\\r\\n', 2);\n        const contentLengthMatch = headers.match(/content-length:\\s*(\\d+)/i);\n        if (contentLengthMatch) {\n          contentLength = parseInt(contentLengthMatch[1]);\n        }\n        headersParsed = true;\n\n        // Check if we have all the content\n        if (body.length >= contentLength) {\n          break;\n        }\n      }\n    }\n\n    // Verify the manual HTTP response\n    assert(responseData.includes('HTTP/1.1 200 OK'));\n    assert(responseData.includes('pong'));\n\n    // Release locks and convert to HTTP client\n    reader.releaseLock();\n    writer.releaseLock();\n\n    const httpClient = await internalNewHttpClient(socket);\n    const response = await httpClient.fetch('https://example.com/json');\n    assert.equal(response.status, 200);\n    const data = await response.json();\n    assert.deepEqual(data, { message: 'Hello from HTTP socket server' });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/http-socket-test.wd-test",
    "content": "# Copyright (c) 2017-2024 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (\n      name = \"main\",\n      worker = (\n        modules = [\n          (name = \"worker.js\", esModule = embed \"http-socket-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"experimental\"],\n        bindings = [\n          (name = \"HTTP_SOCKET_SERVER_PORT\", fromEnvironment = \"HTTP_SOCKET_SERVER_PORT\"),\n          (name = \"SOCKET_PARTIALLY_WRITTEN\", fromEnvironment = \"SOCKET_PARTIALLY_WRITTEN\"),\n          (name = \"FLUSH_HELLO_SOCKET\", fromEnvironment = \"FLUSH_HELLO_SOCKET\"),\n          (name = \"STARTTLS_SOCKET\", fromEnvironment = \"STARTTLS_SOCKET\"),\n        ],\n      )\n    ),\n    ( name = \"internet\", \n      network = ( \n        allow = [\"private\"],\n        tlsOptions = (\n          trustedCertificates = [\n            embed \"starttls-server.pem\",\n          ],\n        ),\n      ) \n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/http-standard-test.js",
    "content": "import { strictEqual, rejects, throws } from 'node:assert';\n\nexport default {\n  async fetch(req) {\n    if (req.url.endsWith('/')) {\n      return new Response(null, {\n        status: 302,\n        headers: {\n          location: ' /\\t2  ',\n        },\n      });\n    } else if (req.url.endsWith('/2')) {\n      return new Response('ok');\n    } else if (req.url.endsWith('/error')) {\n      const resp = Response.error();\n      strictEqual(resp.type, 'error');\n      strictEqual(resp.status, 0);\n      return resp;\n    }\n  },\n\n  foo() {\n    return Response.error();\n  },\n};\n\nexport const test = {\n  async test(ctrl, env) {\n    const resp = await env.SERVICE.fetch(\n      ' http://p\\tl\\na\\tc\\ne\\th\\no\\tl\\nd\\te\\nr\\t/ '\n    );\n\n    console.log(resp.url);\n\n    strictEqual(await resp.text(), 'ok');\n  },\n};\n\nexport const typeTest = {\n  test() {\n    const resp = new Response('ok');\n    strictEqual(resp.type, 'default');\n  },\n};\n\nexport const error = {\n  async test(_, env) {\n    await rejects(env.SERVICE.fetch('http://example/error'), {\n      message:\n        'Return value from serve handler must not be an error ' +\n        'response (like Response.error())',\n    });\n\n    const errorResp = Response.error();\n    throws(() => new Response('', errorResp), {\n      message:\n        'Responses may only be constructed with status codes in ' +\n        'the range 200 to 599, inclusive.',\n    });\n\n    const errorRespRpc = await env.SERVICE.foo(null);\n    strictEqual(errorRespRpc.type, 'error');\n    strictEqual(errorRespRpc.ok, false);\n  },\n};\n\n// Test that DNS-JSON content type does not trigger a warning when using text() method\n// Reference: https://github.com/cloudflare/workerd/issues/3326\nexport const dnsJsonContentType = {\n  async test() {\n    // Create a minimal response with application/dns-json mimetype\n    const dnsJsonResponse = new Response('{\"Status\":0}', {\n      headers: { 'Content-Type': 'application/dns-json' },\n    });\n\n    const text = await dnsJsonResponse.text();\n    strictEqual(typeof text, 'string', 'Response text should be a string');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/http-standard-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"http-standard-test\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"http-standard-test.js\" )\n        ],\n        bindings = [\n          ( name = \"SERVICE\", service = \"http-standard-test\" )\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"rpc\", \"url_standard\", \"fetch_standard_url\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/http-test-ts.ts",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'node:assert';\nimport util from 'node:util';\n\nexport default {\n  async fetch() {\n    return new Response(null, { status: 404 });\n  },\n};\n\nasync function assertRequestCacheThrowsError(\n  cacheHeader: RequestCache,\n  errorName: string = 'Error',\n  errorMessage: string = \"The 'cache' field on 'RequestInitializerDict' is not implemented.\"\n) {\n  assert.throws(\n    () => {\n      const header = { cache: cacheHeader };\n      const req: RequestInit = header;\n      new Request('https://example.org', req);\n    },\n    {\n      name: errorName,\n      message: errorMessage,\n    }\n  );\n}\n\nasync function assertFetchCacheRejectsError(\n  cacheHeader: RequestCache,\n  errorName: string = 'Error',\n  errorMessage: string = \"The 'cache' field on 'RequestInitializerDict' is not implemented.\"\n) {\n  await assert.rejects(\n    (async () => {\n      const header = { cache: cacheHeader };\n      const req: RequestInit = header;\n      await fetch('https://example.org', req);\n    })(),\n    {\n      name: errorName,\n      message: errorMessage,\n    }\n  );\n}\n\n// Test that the Request constructor accepts a Request object as the second parameter.\n// This validates the TypeScript type definitions are correct.\n// Reference: https://github.com/cloudflare/workerd/issues/5940\nexport const requestConstructorWithRequestAsInit = {\n  async test() {\n    // Create an original request\n    const originalRequest = new Request('https://example.com/original', {\n      method: 'POST',\n      headers: { 'X-Custom-Header': 'test-value' },\n    });\n\n    // TypeScript should allow passing a Request as the second parameter\n    const newRequest = new Request(\n      'https://example.com/new-url',\n      originalRequest\n    );\n\n    assert.strictEqual(newRequest.url, 'https://example.com/new-url');\n    assert.strictEqual(newRequest.method, 'POST');\n    assert.strictEqual(newRequest.headers.get('X-Custom-Header'), 'test-value');\n\n    // Also test with clone() which is a common pattern\n    const clonedAsInit = new Request(\n      'https://example.com/another-url',\n      originalRequest.clone()\n    );\n    assert.strictEqual(clonedAsInit.url, 'https://example.com/another-url');\n    assert.strictEqual(clonedAsInit.method, 'POST');\n  },\n};\n\nexport const cacheMode = {\n  async test(_ctrl: any, env: any, _ctx: any) {\n    const allowedCacheModes: Set<RequestCache> = new Set([\n      'default',\n      'force-cache',\n      'no-cache',\n      'no-store',\n      'only-if-cached',\n      'reload',\n    ]);\n    assert.strictEqual('cache' in Request.prototype, env.CACHE_ENABLED);\n    {\n      const req = new Request('https://example.org', {});\n      assert.strictEqual(req.cache, undefined);\n    }\n\n    var enabledCacheModes: Set<RequestCache> = new Set();\n\n    if (env.CACHE_ENABLED) {\n      enabledCacheModes.add('no-store');\n    }\n    if (env.NO_CACHE_ENABLED) {\n      enabledCacheModes.add('no-cache');\n    }\n    if (env.RELOAD_ENABLED) {\n      enabledCacheModes.add('reload');\n    }\n\n    const failureCacheModes = allowedCacheModes.difference(enabledCacheModes);\n    for (const cacheMode of failureCacheModes) {\n      if (!env.CACHE_ENABLED) {\n        await assertRequestCacheThrowsError(cacheMode);\n        await assertFetchCacheRejectsError(cacheMode);\n      } else {\n        await assertRequestCacheThrowsError(\n          cacheMode,\n          'TypeError',\n          'Unsupported cache mode: ' + cacheMode\n        );\n        await assertFetchCacheRejectsError(\n          cacheMode,\n          'TypeError',\n          'Unsupported cache mode: ' + cacheMode\n        );\n      }\n    }\n    for (const cacheMode of enabledCacheModes) {\n      {\n        const req = new Request('https://example.org', { cache: cacheMode });\n        assert.strictEqual(req.cache, cacheMode);\n      }\n      {\n        const response = await env.SERVICE.fetch(\n          'http://placeholder/not-found',\n          { cache: cacheMode }\n        );\n        assert.strictEqual(\n          util.inspect(response),\n          `Response {\n  status: 404,\n  statusText: 'Not Found',\n  headers: Headers(0) { [immutable]: true },\n  ok: false,\n  redirected: false,\n  url: 'http://placeholder/not-found',\n  webSocket: null,\n  cf: undefined,\n  type: 'default',\n  body: ReadableStream {\n    locked: false,\n    [state]: 'readable',\n    [supportsBYOB]: true,\n    [length]: 0n\n  },\n  bodyUsed: false\n}`\n        );\n      }\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/http-test-ts.ts-wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"http-test\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"http-test-ts.js\" )\n        ],\n        bindings = [\n          ( name = \"SERVICE\", service = \"http-test\" ),\n          ( name = \"CACHE_ENABLED\", json = \"false\" ),\n          ( name = \"NO_CACHE_ENABLED\", json = \"false\" ),\n          ( name = \"RELOAD_ENABLED\", json = \"false\" ),\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"service_binding_extra_handlers\", \"cache_option_disabled\"],\n      )\n    ),\n    ( name = \"http-test-cache-option-enabled\",\n      worker = (\n        modules = [\n          ( name = \"worker-cache-enabled\", esModule = embed \"http-test-ts.js\" )\n        ],\n        bindings = [\n          ( name = \"SERVICE\", service = \"http-test-cache-option-enabled\" ),\n          ( name = \"CACHE_ENABLED\", json = \"true\" ),\n          ( name = \"NO_CACHE_ENABLED\", json = \"false\" ),\n          ( name = \"RELOAD_ENABLED\", json = \"false\" ),\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"service_binding_extra_handlers\", \"cache_option_enabled\", \"workers_api_getters_setters_on_prototype\", \"cache_no_cache_disabled\", \"cache_reload_disabled\"],\n      )\n    ),\n    ( name = \"http-test-cache-no-cache\",\n      worker = (\n        modules = [\n          ( name = \"worker-cache-no-cache\", esModule = embed \"http-test-ts.js\" )\n        ],\n        bindings = [\n          ( name = \"SERVICE\", service = \"http-test-cache-no-cache\" ),\n          ( name = \"CACHE_ENABLED\", json = \"true\" ),\n          ( name = \"NO_CACHE_ENABLED\", json = \"true\" ),\n          ( name = \"RELOAD_ENABLED\", json = \"false\" ),\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"service_binding_extra_handlers\", \"cache_option_enabled\", \"cache_no_cache_enabled\", \"workers_api_getters_setters_on_prototype\", \"cache_reload_disabled\"],\n      )\n    ),\n    ( name = \"http-test-cache-reload\",\n      worker = (\n        modules = [\n          ( name = \"worker-reload\", esModule = embed \"http-test-ts.js\" )\n        ],\n        bindings = [\n          ( name = \"SERVICE\", service = \"http-test-cache-reload\" ),\n          ( name = \"CACHE_ENABLED\", json = \"true\" ),\n          ( name = \"NO_CACHE_ENABLED\", json = \"true\" ),\n          ( name = \"RELOAD_ENABLED\", json = \"true\" ),\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"service_binding_extra_handlers\",\n        \"cache_option_enabled\", \"cache_no_cache_enabled\", \"cache_reload_enabled\", \"workers_api_getters_setters_on_prototype\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/http-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'node:assert';\nimport util from 'node:util';\n\nlet scheduledLastCtrl;\n\nexport default {\n  async fetch(request, env, ctx) {\n    const { pathname } = new URL(request.url);\n    if (pathname === '/body-length') {\n      return Response.json(Object.fromEntries(request.headers));\n    }\n    if (pathname === '/web-socket') {\n      const pair = new WebSocketPair();\n      pair[0].addEventListener('message', (event) => {\n        pair[0].send(util.inspect(event));\n      });\n      pair[0].accept();\n      return new Response(null, {\n        status: 101,\n        webSocket: pair[1],\n      });\n    }\n    return new Response(null, { status: 404 });\n  },\n\n  async scheduled(ctrl, env, ctx) {\n    scheduledLastCtrl = ctrl;\n    if (ctrl.cron === '* * * * 30') ctrl.noRetry();\n  },\n\n  async test(ctrl, env, ctx) {\n    // Call `fetch()` with known body length\n    {\n      const body = new FixedLengthStream(3);\n      const writer = body.writable.getWriter();\n      void writer.write(new Uint8Array([1, 2, 3]));\n      void writer.close();\n      const response = await env.SERVICE.fetch(\n        'http://placeholder/body-length',\n        {\n          method: 'POST',\n          body: body.readable,\n        }\n      );\n      const headers = new Headers(await response.json());\n      assert.strictEqual(headers.get('Content-Length'), '3');\n      assert.strictEqual(headers.get('Transfer-Encoding'), null);\n    }\n\n    // Check `fetch()` with unknown body length\n    {\n      const body = new IdentityTransformStream();\n      const writer = body.writable.getWriter();\n      void writer.write(new Uint8Array([1, 2, 3]));\n      void writer.close();\n      const response = await env.SERVICE.fetch(\n        'http://placeholder/body-length',\n        {\n          method: 'POST',\n          body: body.readable,\n        }\n      );\n      const headers = new Headers(await response.json());\n      assert.strictEqual(headers.get('Content-Length'), null);\n      assert.strictEqual(headers.get('Transfer-Encoding'), 'chunked');\n    }\n\n    // Call `scheduled()` with no options\n    {\n      const result = await env.SERVICE.scheduled();\n      assert.strictEqual(result.outcome, 'ok');\n      assert(!result.noRetry);\n      assert(Math.abs(Date.now() - scheduledLastCtrl.scheduledTime) < 3_000);\n      assert.strictEqual(scheduledLastCtrl.cron, '');\n    }\n\n    // Call `scheduled()` with options, and noRetry()\n    {\n      const result = await env.SERVICE.scheduled({\n        scheduledTime: 1000,\n        cron: '* * * * 30',\n      });\n      assert.strictEqual(result.outcome, 'ok');\n      assert(result.noRetry);\n      assert.strictEqual(scheduledLastCtrl.scheduledTime, 1000);\n      assert.strictEqual(scheduledLastCtrl.cron, '* * * * 30');\n    }\n  },\n};\n\n// inspect tests\nexport const test = {\n  async test(ctrl, env, ctx) {\n    // Check URL with duplicate search param keys\n    const url = new URL('http://user:pass@placeholder:8787/path?a=1&a=2&b=3');\n    assert.strictEqual(\n      util.inspect(url),\n      `URL {\n  origin: 'http://placeholder:8787',\n  href: 'http://user:pass@placeholder:8787/path?a=1&a=2&b=3',\n  protocol: 'http:',\n  username: 'user',\n  password: 'pass',\n  host: 'placeholder:8787',\n  hostname: 'placeholder',\n  port: '8787',\n  pathname: '/path',\n  search: '?a=1&a=2&b=3',\n  hash: '',\n  searchParams: URLSearchParams(3) { 'a' => '1', 'a' => '2', 'b' => '3' }\n}`\n    );\n\n    // Check FormData with lower depth\n    const formData = new FormData();\n    formData.set('string', 'hello');\n    formData.set(\n      'blob',\n      new Blob(['<h1>BLOB</h1>'], {\n        type: 'text/html',\n      })\n    );\n    formData.set(\n      'file',\n      new File(['password123'], 'passwords.txt', {\n        type: 'text/plain',\n        lastModified: 1000,\n      })\n    );\n    assert.strictEqual(\n      util.inspect(formData, { depth: 0 }),\n      `FormData(3) { 'string' => 'hello', 'blob' => [File], 'file' => [File] }`\n    );\n\n    // Check request with mutable headers\n    const request = new Request('http://placeholder', {\n      method: 'POST',\n      body: 'message',\n      headers: { 'Content-Type': 'text/plain' },\n    });\n    if (env.CACHE_ENABLED) {\n      assert.strictEqual(\n        util.inspect(request),\n        `Request {\n  method: 'POST',\n  url: 'http://placeholder',\n  headers: Headers(1) { 'content-type' => 'text/plain', [immutable]: false },\n  redirect: 'follow',\n  fetcher: null,\n  signal: AbortSignal { aborted: false, reason: undefined, onabort: null },\n  cf: undefined,\n  integrity: '',\n  keepalive: false,\n  cache: undefined,\n  body: ReadableStream {\n    locked: false,\n    [state]: 'readable',\n    [supportsBYOB]: true,\n    [length]: 7n\n  },\n  bodyUsed: false\n}`\n      );\n    } else {\n      assert.strictEqual(\n        util.inspect(request),\n        `Request {\n  method: 'POST',\n  url: 'http://placeholder',\n  headers: Headers(1) { 'content-type' => 'text/plain', [immutable]: false },\n  redirect: 'follow',\n  fetcher: null,\n  signal: AbortSignal { aborted: false, reason: undefined, onabort: null },\n  cf: undefined,\n  integrity: '',\n  keepalive: false,\n  body: ReadableStream {\n    locked: false,\n    [state]: 'readable',\n    [supportsBYOB]: true,\n    [length]: 7n\n  },\n  bodyUsed: false\n}`\n      );\n    }\n\n    // Check response with immutable headers\n    const response = await env.SERVICE.fetch('http://placeholder/not-found');\n    assert.strictEqual(\n      util.inspect(response),\n      `Response {\n  status: 404,\n  statusText: 'Not Found',\n  headers: Headers(0) { [immutable]: true },\n  ok: false,\n  redirected: false,\n  url: 'http://placeholder/not-found',\n  webSocket: null,\n  cf: undefined,\n  type: 'default',\n  body: ReadableStream {\n    locked: false,\n    [state]: 'readable',\n    [supportsBYOB]: true,\n    [length]: 0n\n  },\n  bodyUsed: false\n}`\n    );\n\n    // Check `MessageEvent` with unimplemented properties\n    const webSocketResponse = await env.SERVICE.fetch(\n      'http://placeholder/web-socket',\n      {\n        headers: { Upgrade: 'websocket' },\n      }\n    );\n    const webSocket = webSocketResponse.webSocket;\n    assert.notStrictEqual(webSocket, null);\n    // The server-side WebSocketPair socket's binaryType depends on the compat flag.\n    const bt = new WebSocketPair()[0].binaryType;\n    const wsStr = `WebSocket {\\n    readyState: 1,\\n    url: null,\\n    protocol: '',\\n    extensions: '',\\n    binaryType: '${bt}'\\n  }`;\n    const messagePromise = new Promise((resolve) => {\n      webSocket.addEventListener('message', (event) => {\n        assert.strictEqual(\n          event.data,\n          `MessageEvent {\n  ports: [ [length]: 0 ],\n  source: null,\n  lastEventId: '',\n  origin: null,\n  data: 'data',\n  type: 'message',\n  eventPhase: 2,\n  composed: false,\n  bubbles: false,\n  cancelable: false,\n  defaultPrevented: false,\n  returnValue: true,\n  currentTarget: ${wsStr},\n  target: ${wsStr},\n  srcElement: ${wsStr},\n  timeStamp: 0,\n  isTrusted: true,\n  cancelBubble: false,\n  NONE: 0,\n  CAPTURING_PHASE: 1,\n  AT_TARGET: 2,\n  BUBBLING_PHASE: 3\n}`\n        );\n        resolve();\n      });\n    });\n    webSocket.accept();\n    webSocket.send('data');\n    webSocket.close();\n    await messagePromise;\n\n    // Test sending to oversized URL (bigger than MAX_TRACE_BYTES), relevant primarily for tail worker test.\n    await env.SERVICE.fetch('http://placeholder/' + '0'.repeat(2 ** 18));\n  },\n};\n\nasync function assertRequestCacheThrowsError(\n  cacheHeader,\n  errorName = 'Error',\n  errorMessage = \"The 'cache' field on 'RequestInitializerDict' is not implemented.\"\n) {\n  assert.throws(\n    () => {\n      new Request('https://example.org', { cache: cacheHeader });\n    },\n    {\n      name: errorName,\n      message: errorMessage,\n    }\n  );\n}\n\nasync function assertFetchCacheRejectsError(\n  cacheHeader,\n  errorName = 'Error',\n  errorMessage = \"The 'cache' field on 'RequestInitializerDict' is not implemented.\"\n) {\n  await assert.rejects(\n    (async () => {\n      await fetch('https://example.org', { cache: cacheHeader });\n    })(),\n    {\n      name: errorName,\n      message: errorMessage,\n    }\n  );\n}\n\nexport const cacheMode = {\n  async test(ctrl, env, ctx) {\n    var failureCases = [\n      'default',\n      'force-cache',\n      'no-cache',\n      'only-if-cached',\n      'reload',\n      'unsupported',\n    ];\n    assert.strictEqual('cache' in Request.prototype, env.CACHE_ENABLED);\n    {\n      const req = new Request('https://example.org', {});\n      assert.strictEqual(req.cache, undefined);\n    }\n    if (!env.CACHE_ENABLED) {\n      failureCases.push('no-store');\n      for (const cacheMode in failureCases) {\n        await assertRequestCacheThrowsError(cacheMode);\n        await assertFetchCacheRejectsError(cacheMode);\n      }\n    } else {\n      {\n        const req = new Request('https://example.org', { cache: 'no-store' });\n        assert.strictEqual(req.cache, 'no-store');\n      }\n      {\n        const response = await env.SERVICE.fetch(\n          'http://placeholder/not-found',\n          { cache: 'no-store' }\n        );\n        assert.strictEqual(\n          util.inspect(response),\n          `Response {\n  status: 404,\n  statusText: 'Not Found',\n  headers: Headers(0) { [immutable]: true },\n  ok: false,\n  redirected: false,\n  url: 'http://placeholder/not-found',\n  webSocket: null,\n  cf: undefined,\n  type: 'default',\n  body: ReadableStream {\n    locked: false,\n    [state]: 'readable',\n    [supportsBYOB]: true,\n    [length]: 0n\n  },\n  bodyUsed: false\n}`\n        );\n      }\n      for (const cacheMode in failureCases) {\n        await assertRequestCacheThrowsError(\n          cacheMode,\n          'TypeError',\n          'Unsupported cache mode: ' + cacheMode\n        );\n        await assertFetchCacheRejectsError(\n          cacheMode,\n          'TypeError',\n          'Unsupported cache mode: ' + cacheMode\n        );\n      }\n\n      // Test cache mode compatibility with cf.cacheTtl\n      // cache: 'no-store' with cf: { cacheTtl: -1 } should succeed because\n      // -1 is the NOCACHE_TTL value that no-store sets automatically.\n      {\n        const req = new Request('https://example.org', {\n          cache: 'no-store',\n          cf: { cacheTtl: -1 },\n        });\n        assert.strictEqual(req.cache, 'no-store');\n        // Fetch should succeed with compatible cacheTtl\n        await env.SERVICE.fetch('http://placeholder/not-found', {\n          cache: 'no-store',\n          cf: { cacheTtl: -1 },\n        });\n      }\n\n      // cache: 'no-store' with cf: { cacheTtl: 300 } should throw TypeError\n      // because 300 is incompatible with no-store (validation happens at fetch time)\n      {\n        const req = new Request('https://example.org', {\n          cache: 'no-store',\n          cf: { cacheTtl: 300 },\n        });\n        // Request construction succeeds\n        assert.strictEqual(req.cache, 'no-store');\n        // But fetch should fail with incompatible cacheTtl\n        await assert.rejects(\n          env.SERVICE.fetch('http://placeholder/not-found', {\n            cache: 'no-store',\n            cf: { cacheTtl: 300 },\n          }),\n          {\n            name: 'TypeError',\n            message: /is not compatible with cache/,\n          }\n        );\n      }\n\n      // cache: 'no-store' with cf: { cacheTtl: 0 } should also throw TypeError\n      {\n        await assert.rejects(\n          env.SERVICE.fetch('http://placeholder/not-found', {\n            cache: 'no-store',\n            cf: { cacheTtl: 0 },\n          }),\n          {\n            name: 'TypeError',\n            message: /is not compatible with cache/,\n          }\n        );\n      }\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/http-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"http-test\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"http-test.js\" )\n        ],\n        bindings = [\n          ( name = \"SERVICE\", service = \"http-test\" ),\n          ( name = \"CACHE_ENABLED\", json = \"false\" ),\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"service_binding_extra_handlers\", \"cache_option_disabled\", \"url_standard\", \"workers_api_getters_setters_on_prototype\", \"fetch_legacy_url\", \"web_socket_auto_reply_to_close\"],\n      )\n    ),\n    ( name = \"http-test-cache-option-enabled\",\n      worker = (\n        modules = [\n          ( name = \"worker-cache-enabled\", esModule = embed \"http-test.js\" )\n        ],\n        bindings = [\n          ( name = \"SERVICE\", service = \"http-test-cache-option-enabled\" ),\n          ( name = \"CACHE_ENABLED\", json = \"true\" ),\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"service_binding_extra_handlers\", \"cache_option_enabled\", \"url_standard\", \"workers_api_getters_setters_on_prototype\", \"fetch_legacy_url\", \"web_socket_auto_reply_to_close\"],\n    ))\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/identity-transform-stream-state-machine-test.js",
    "content": "import { strictEqual } from 'node:assert';\n\n// TODO(cleanup): This is a copy of an existing test in streams-test. Once the autogate is remvoed,\n// this separate test can be deleted.\nexport const test = {\n  async test() {\n    const its = new IdentityTransformStream();\n    const writer = its.writable.getWriter();\n    const input = new TextEncoder().encode('Hello, world!');\n    const writePromise = writer.write(input);\n    const closePromise = writer.close();\n    const reader = its.readable.getReader();\n    const { value, done } = await reader.read();\n    strictEqual(done, false);\n    strictEqual(value instanceof Uint8Array, true);\n    strictEqual(value.length, input.length);\n    for (let i = 0; i < input.length; i++) {\n      strictEqual(value[i], input[i]);\n    }\n    await Promise.all([writePromise, closePromise]);\n    const { done: doneAfterClose } = await reader.read();\n    strictEqual(doneAfterClose, true);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/identity-transform-stream-state-machine-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\n# TODO(cleanup): This is a copy of an existing test in streams-test. Once the autogate is remvoed,\n# this separate test can be deleted.\nconst unitTests :Workerd.Config = (\n  autogates = [\"workerd-autogate-identity-transform-stream-use-state-machine\"],\n  services = [\n    ( name = \"identity-transform-stream-state-machine-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"identity-transform-stream-state-machine-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"nodejs_compat_v2\", \"streams_enable_constructors\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/importable-env-test.js",
    "content": "import {\n  strictEqual,\n  deepStrictEqual,\n  notStrictEqual,\n  ok,\n  rejects,\n} from 'node:assert';\nimport { env, withEnv } from 'cloudflare:workers';\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nconst check = new AsyncLocalStorage();\n\n// The env is populated at the top level scope.\nstrictEqual(env.FOO, 'BAR');\n\n// Cache exists and is accessible.\nok(env.CACHE);\n// But fails when used because we're not in an io-context\nawait rejects(\n  env.CACHE.read('hello', async () => {}),\n  {\n    message: /Disallowed operation called within global scope./,\n  }\n);\n\nexport const importableEnv = {\n  async test(_, argEnv) {\n    // Accessing the cache initially at the global scope didn't break anything\n    const cached = await argEnv.CACHE.read('hello', async () => {\n      return {\n        value: 123,\n        expiration: Date.now() + 10000,\n      };\n    });\n    strictEqual(cached, 123);\n\n    // They aren't the same objects...\n    notStrictEqual(env, argEnv);\n    // But have all the same stuff...\n    deepStrictEqual(env, argEnv);\n\n    deepStrictEqual(Object.keys(env).sort(), ['CACHE', 'FOO', 'RPC']);\n\n    // It is populated inside a request\n    strictEqual(env.FOO, 'BAR');\n\n    // And following async operations.\n    await scheduler.wait(10);\n    strictEqual(env.FOO, 'BAR');\n\n    // Mutations to the env carry through as expected...\n    env.BAR = 123;\n    const { env: otherEnv } = await import('child');\n    strictEqual(otherEnv.FOO, 'BAR');\n    strictEqual(otherEnv.BAR, 123);\n    deepStrictEqual(argEnv, otherEnv);\n\n    // Using withEnv to replace the env...\n    const { env: otherEnv2 } = await withEnv({ BAZ: 1 }, async () => {\n      await scheduler.wait(0);\n      return import('child2');\n    });\n    strictEqual(otherEnv2.FOO, undefined);\n    strictEqual(otherEnv2.BAZ, 1);\n\n    // Original env is unmodified\n    strictEqual(env.BAZ, undefined);\n\n    // Verify that JSRPC calls appropriately see the environment.\n    const remote = await argEnv.RPC.rpcTarget(undefined);\n    await Promise.all([remote.test(), remote.test2()]);\n  },\n\n  get fetch() {\n    // It's a weird edge case, yes, but let's make sure that the env is\n    // available in getters when extracting the default handlers.\n    strictEqual(env.FOO, 'BAR');\n    return async () => {};\n  },\n\n  // Verifies that the environment is available and correctly propagated\n  // in custom events like jsrpc.\n  rpcTarget() {\n    strictEqual(env.FOO, 'BAR');\n    return withEnv({ FOO: 'BAZ' }, () => {\n      // Arguably, returned RPC targets should probably automatically capture\n      // the current async context and propagate that on subsequent calls,\n      // but they currently do not... therefore we have to manually capture\n      // the async context and be sure to enter it ourselves below.\n      const runInAsyncContext = AsyncLocalStorage.snapshot();\n      return {\n        test() {\n          // This one runs with the modified env\n          runInAsyncContext(() => strictEqual(env.FOO, 'BAZ'));\n        },\n        test2() {\n          // This one runs with the original env\n          strictEqual(env.FOO, 'BAR');\n        },\n      };\n    });\n  },\n};\n\nexport const nullableEnv = {\n  async test(_, argEnv) {\n    strictEqual(env.FOO, 'BAR');\n\n    await withEnv(null, async () => {\n      strictEqual(env.FOO, undefined);\n      strictEqual(env.CACHE, undefined);\n      strictEqual(typeof env, 'object');\n    });\n\n    await withEnv(undefined, async () => {\n      strictEqual(env.FOO, undefined);\n      strictEqual(env.CACHE, undefined);\n      strictEqual(typeof env, 'object');\n    });\n\n    strictEqual(env.FOO, 'BAR');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/importable-env-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"importable-env-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"importable-env-test.js\"),\n          (name = \"child\", esModule = \"import {env as live} from 'cloudflare:workers'; export const env = {...live};\"),\n          (name = \"child2\", esModule = \"import {env as live} from 'cloudflare:workers'; export const env = {...live};\"),\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat_v2\",\n          \"env_module_nullable_support\",\n          \"allow_importable_env\",\n          \"capture_async_api_throws\",\n          \"rpc\",\n        ],\n        bindings = [\n          (name = \"RPC\", service = ( name = \"importable-env-test\", entrypoint=\"importableEnv\")),\n          (name = \"FOO\", text = \"BAR\"),\n          (name = \"CACHE\", memoryCache = (\n            id = \"abc123\",\n            limits = (\n              maxKeys = 10,\n              maxValueSize = 1024,\n              maxTotalValueSize = 1024,\n            ),\n          ))\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/importable-exports-test.js",
    "content": "import { strictEqual, ok, deepStrictEqual } from 'node:assert';\nimport { DurableObject, exports, withExports } from 'cloudflare:workers';\n\nexport class MyService extends DurableObject {\n  greet(name) {\n    return `Hello, ${name}!`;\n  }\n}\n\nexport const importableExports = {\n  async test() {\n    ok(\n      exports.importableExports,\n      'exports.importableExports should be defined'\n    );\n    ok(exports.nullableExports, 'exports.nullableExports should be defined');\n    ok(exports.MyService, 'exports.MyService should be defined');\n\n    const keys = Object.keys(exports).sort();\n    deepStrictEqual(keys, [\n      'MyService',\n      'importableExports',\n      'nullableExports',\n    ]);\n\n    const id = exports.MyService.idFromName('test');\n    const stub = exports.MyService.get(id);\n    strictEqual(await stub.greet('World'), 'Hello, World!');\n\n    await scheduler.wait(10);\n    ok(\n      exports.importableExports,\n      'exports.importableExports should persist after async wait'\n    );\n\n    const customExports = { customValue: 'test', customNumber: 42 };\n    const result = await withExports(customExports, async () => {\n      await scheduler.wait(10);\n      strictEqual(exports.customValue, 'test');\n      strictEqual(exports.customNumber, 42);\n      strictEqual(\n        exports.importableExports,\n        undefined,\n        'base exports should be masked'\n      );\n      return exports.customValue;\n    });\n    strictEqual(result, 'test');\n\n    ok(\n      exports.importableExports,\n      'exports should be restored after withExports'\n    );\n    strictEqual(exports.customValue, undefined);\n\n    for (const value of ['string', 123]) {\n      await withExports(value, async () => {\n        await scheduler.wait(10);\n        ok(\n          exports.importableExports,\n          'non-object values should fall back to base exports'\n        );\n      });\n    }\n  },\n};\n\nexport const nullableExports = {\n  async test() {\n    await withExports(null, async () => {\n      strictEqual(exports.importableExports, undefined);\n      strictEqual(exports.nullableExports, undefined);\n      strictEqual(typeof exports, 'object');\n    });\n\n    await withExports(undefined, async () => {\n      strictEqual(exports.importableExports, undefined);\n      strictEqual(exports.nullableExports, undefined);\n      strictEqual(typeof exports, 'object');\n    });\n\n    ok(exports.importableExports, 'exports should be restored');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/importable-exports-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"importable-exports-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"importable-exports-test.js\"),\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"env_module_nullable_support\", \"rpc\"],\n        durableObjectNamespaces = [\n          (className = \"MyService\", uniqueKey = \"myservice\"),\n        ],\n        durableObjectStorage = (inMemory = void),\n        bindings = [],\n      )\n    ),\n  ]\n);\n"
  },
  {
    "path": "src/workerd/api/tests/instrumentation-tail-worker.js",
    "content": "// tailStream is going to be invoked multiple times, but we want to wait\n// to run the test until all executions are done. Collect promises for\n// each\nexport let spans = new Map();\nexport let invocationPromises = [];\n\n// tail stream handler function used in several STW instrumentation tests.\nexport const testTailHandler = {\n  tailStream(event, env, ctx) {\n    // Capture the top-level span ID from the onset event\n    const topLevelSpanId = event.event.spanId;\n\n    // For each \"onset\" event, store a promise which we will resolve when\n    // we receive the equivalent \"outcome\" event\n    let resolveFn;\n    invocationPromises.push(\n      new Promise((resolve, reject) => {\n        resolveFn = resolve;\n      })\n    );\n\n    return (event) => {\n      // For spanOpen events, the new span ID is in event.event.spanId\n      // For other events, they reference an existing span via event.spanContext.spanId\n      let spanKey = event.invocationId + event.spanContext.spanId;\n      switch (event.event.type) {\n        case 'spanOpen':\n          // spanOpen creates a new span with ID in event.event.spanId\n          spanKey = event.invocationId + event.event.spanId;\n          spans.set(spanKey, {\n            name: event.event.name,\n          });\n          break;\n        case 'attributes': {\n          let span = spans.get(spanKey);\n          if (event.spanContext.spanId === topLevelSpanId) {\n            // top-level JsRpc method name attribute – transform into a span with the given name\n            const rpcMethodName = event.event.info.find(\n              (item) => item['name'] === 'jsrpc.method'\n            ).value;\n            span = { name: rpcMethodName };\n            spans.set(spanKey, span);\n            break;\n          }\n\n          // attributes references an existing span via spanContext.spanId\n          if (!span) {\n            throw new Error(`Attributes event for unknown span: ${spanKey}`);\n          }\n          for (let { name, value } of event.event.info) {\n            span[name] = value;\n          }\n          break;\n        }\n        case 'spanClose': {\n          // spanClose references an existing span via spanContext.spanId\n          let span = spans.get(spanKey);\n          if (!span) {\n            throw new Error(`SpanClose event for unknown span: ${spanKey}`);\n          }\n          span['closed'] = true;\n          break;\n        }\n        case 'outcome':\n          resolveFn();\n          break;\n      }\n    };\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/js-rpc-flag.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'node:assert';\n\n// This class does not extend `DurableObject`, but because we have the `js_rpc` compat flag on,\n// it'll still accept RPC.\nexport class DurableObjectExample {\n  constructor() {}\n\n  foo() {\n    return 123;\n  }\n\n  fetch(req) {\n    return new Response(req.method + ' ' + req.url);\n  }\n}\n\nexport default {\n  async test(ctrl, env, ctx) {\n    let id = env.ns.idFromName('foo');\n    let obj = env.ns.get(id);\n    assert.strictEqual(await obj.foo(), 123);\n\n    // Test that the object has the old helper get() method that wraps fetch(). This is deprecated,\n    // but enabled because this test's compat date is before the date when these went away.\n    assert.strictEqual(await obj.get('http://foo'), 'GET http://foo');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/js-rpc-flag.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n    (name = \"TEST_TMPDIR\", disk = (writable = true)),\n  ],\n);\n\nconst mainWorker :Workerd.Worker = (\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\", \"js_rpc\", \"fetcher_has_get_put_delete\", \"rpc_params_transfer_stubs\", \"fetch_legacy_url\"],\n\n  modules = [\n    (name = \"worker\", esModule = embed \"js-rpc-flag.js\"),\n  ],\n\n  durableObjectNamespaces = [\n    (className = \"DurableObjectExample\", uniqueKey = \"210bd0cbd803ef7883a1ee9d86cce06e\"),\n  ],\n\n  durableObjectStorage = (localDisk = \"TEST_TMPDIR\"),\n\n  bindings = [\n    (name = \"ns\", durableObjectNamespace = \"DurableObjectExample\"),\n  ],\n);\n\n"
  },
  {
    "path": "src/workerd/api/tests/js-rpc-params-ownership-test.js",
    "content": "import assert from 'node:assert';\nimport { WorkerEntrypoint, RpcTarget, RpcStub } from 'cloudflare:workers';\n\nclass Counter extends RpcTarget {\n  count = { value: 0 };\n  dupCounts = { created: 0, disposed: 0 };\n  disposeCount = 0;\n\n  increment(amount = 1) {\n    this.count.value += amount;\n    return this.count.value;\n  }\n\n  [Symbol.dispose]() {\n    ++this.disposeCount;\n  }\n}\n\nclass DupableCounter extends Counter {\n  disposed = false;\n\n  dup() {\n    let result = new DupableCounter();\n    result.count = this.count;\n    result.dupCounts = this.dupCounts;\n    ++this.dupCounts.created;\n    return result;\n  }\n\n  [Symbol.dispose]() {\n    if (this.disposed) {\n      throw new Error('duplicate disposal');\n    }\n    this.disposed = true;\n    ++this.dupCounts.disposed;\n    ++this.disposeCount;\n  }\n}\n\nexport class TestService extends WorkerEntrypoint {\n  async increment(stub, i) {\n    await stub.increment(i);\n  }\n\n  async roundTrip(stub) {\n    return { stub: stub.dup() };\n  }\n}\n\n// Test that (with the rpc_params_dup_stubs compat flag) passing a stub in RPC params doesn't\n// transfer ownership of the stub.\nexport let rpcParamsDontTransferOwnership = {\n  async test(controller, env, ctx) {\n    let counter = new Counter();\n\n    {\n      using stub = new RpcStub(counter);\n\n      // Use the stub in params twice to prove that ownership isn't transferred away.\n      await ctx.exports.TestService.increment(stub, 2);\n      await ctx.exports.TestService.increment(stub, 3);\n\n      // Make extra-sure we can still call the stub.\n      await stub.increment();\n\n      assert.strictEqual(counter.count.value, 6);\n\n      // RpcTarget disposer should not have been called at all.\n      await scheduler.wait(0);\n      assert.strictEqual(counter.disposeCount, 0);\n    }\n\n    // Disposing a stub *asynchrconously* disposes the RpcTarget, so we have to spin the event\n    // loop to observe the disposal.\n    await scheduler.wait(0);\n    assert.strictEqual(counter.disposeCount, 1);\n  },\n};\n\n// Test that placing a plain RpcTarget in RPC params DOES \"take ownership\", that is, the disposer\n// will be called.\nexport let rpcParamsPlainTarget = {\n  async test(controller, env, ctx) {\n    let counter = new Counter();\n\n    await ctx.exports.TestService.increment(counter, 2);\n    await ctx.exports.TestService.increment(counter, 3);\n\n    assert.strictEqual(counter.count.value, 5);\n\n    // Each RPC invocation will have called the disposer.\n    await scheduler.wait(0);\n    assert.strictEqual(counter.disposeCount, 2);\n  },\n};\n\n// Test that placing an RpcTarget with a dup() method in RPC params causes the dup() method to be\n// called, and then the duplicate is later disposed.\nexport let rpcParamsDupTarget = {\n  async test(controller, env, ctx) {\n    let counter = new DupableCounter();\n\n    // If we directly pass `counter` to RPC params, it'll be dup()ed.\n    await ctx.exports.TestService.increment(counter, 2);\n    assert.strictEqual(counter.dupCounts.created, 1);\n    await ctx.exports.TestService.increment(counter, 3);\n    assert.strictEqual(counter.dupCounts.created, 2);\n\n    assert.strictEqual(counter.count.value, 5);\n\n    // Dups should have been disposed, but not original.\n    await scheduler.wait(0);\n    assert.strictEqual(counter.dupCounts.disposed, 2);\n    assert.strictEqual(counter.disposed, false);\n  },\n};\n\n// Like rpcParamsDupTarget but the target is wrapped in a Proxy. (This takes a different code\n// path.)\nexport let rpcParamsDupProxyTarget = {\n  async test(controller, env, ctx) {\n    let counter = new Proxy(new DupableCounter(), {});\n\n    // If we directly pass `counter` to RPC params, it'll be dup()ed.\n    await ctx.exports.TestService.increment(counter, 2);\n    assert.strictEqual(counter.dupCounts.created, 1);\n    await ctx.exports.TestService.increment(counter, 3);\n    assert.strictEqual(counter.dupCounts.created, 2);\n\n    assert.strictEqual(counter.count.value, 5);\n\n    // Dups should have been disposed, but not original.\n    await scheduler.wait(0);\n    assert.strictEqual(counter.dupCounts.disposed, 2);\n    assert.strictEqual(counter.disposed, false);\n  },\n};\n\n// Like rpcParamsDupTarget but the target is a function.\nexport let rpcParamsDupFunction = {\n  async test(controller, env, ctx) {\n    let count = 0;\n    let dupCount = 0;\n    let disposeCount = 0;\n\n    let increment = (i) => {\n      return (count += i);\n    };\n    increment.dup = () => {\n      ++dupCount;\n      return increment;\n    };\n    increment[Symbol.dispose] = function () {\n      ++disposeCount;\n    };\n\n    let counter = { increment };\n\n    // If we directly pass `counter` to RPC params, it'll be dup()ed.\n    await ctx.exports.TestService.increment(counter, 2);\n    assert.strictEqual(dupCount, 1);\n    await ctx.exports.TestService.increment(counter, 3);\n    assert.strictEqual(dupCount, 2);\n\n    assert.strictEqual(count, 5);\n\n    await scheduler.wait(0);\n    assert.strictEqual(disposeCount, 2);\n  },\n};\n\n// Test that returning a stub tansfers ownership of the stub, that is, the system later disposes\n// it.\nexport let rpcReturnsTransferOwnership = {\n  async test(controller, env, ctx) {\n    let counter = new Counter();\n\n    {\n      using stub = new RpcStub(counter);\n      using stub2 = (await ctx.exports.TestService.roundTrip(stub)).stub;\n\n      await scheduler.wait(0);\n      assert.strictEqual(counter.disposeCount, 0);\n    }\n\n    await scheduler.wait(0);\n    assert.strictEqual(counter.disposeCount, 1);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/js-rpc-params-ownership-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"js-rpc-params-ownership-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"js-rpc-params-ownership-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"rpc_params_dup_stubs\",\n          \"rpc\",\n          \"enable_ctx_exports\",\n        ],\n      )\n    ),\n  ],\n  v8Flags = [ \"--expose-gc\" ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/js-rpc-socket-test.wd-test",
    "content": "# Same as js-rpc-test.wd-test but seding RPC over a real socket.\n#\n# Note that in the BUILD file we explicitly disable info logging for this test because it's too\n# noisy.\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"js-rpc-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"js-rpc-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"fetcher_no_get_put_delete\",\n          \"enable_abortsignal_rpc\",\n          \"enhanced_error_serialization\",\n          \"enable_ctx_exports\",\n          \"experimental\",\n          \"streams_enable_constructors\",\n          \"http_headers_getsetcookie\",\n          \"fetch_legacy_url\",\n          \"rpc_params_transfer_stubs\"],\n        bindings = [\n          (name = \"self\", service = \"nonClass-loop\"),\n          (name = \"MyService\", service = \"MyService-loop\"),\n          (name = \"MyServiceProxy\", service = \"MyServiceProxy-loop\"),\n          (name = \"MyActor\", durableObjectNamespace = \"MyActor\"),\n          (name = \"ActorNoExtends\", durableObjectNamespace = \"ActorNoExtends\"),\n          (name = \"defaultExport\", service = \"default-loop\"),\n          (name = \"twelve\", json = \"12\"),\n          (name = \"GreeterFactory\", service = \"GreeterFactory-loop\"),\n        ],\n\n        durableObjectNamespaces = [\n          (className = \"MyActor\", uniqueKey = \"foo\"),\n          (className = \"ActorNoExtends\", uniqueKey = \"bar\"),\n        ],\n\n        durableObjectStorage = (inMemory = void),\n      )\n    ),\n    ( name = \"MyService-loop\",\n      external = (\n        address = \"loopback:MyService-loop\",\n        http = (capnpConnectHost = \"cappy\")\n      )\n    ),\n    ( name = \"MyServiceProxy-loop\",\n      external = (\n        address = \"loopback:MyServiceProxy-loop\",\n        http = (capnpConnectHost = \"cappy\")\n      )\n    ),\n    ( name = \"nonClass-loop\",\n      external = (\n        address = \"loopback:nonClass-loop\",\n        http = (capnpConnectHost = \"cappy\")\n      )\n    ),\n    ( name = \"default-loop\",\n      external = (\n        address = \"loopback:default-loop\",\n        http = (capnpConnectHost = \"cappy\")\n      )\n    ),\n    ( name = \"GreeterFactory-loop\",\n      external = (\n        address = \"loopback:GreeterFactory-loop\",\n        http = (capnpConnectHost = \"cappy\")\n      )\n    ),\n  ],\n  sockets = [\n    ( name = \"MyService-loop\",\n      address = \"loopback:MyService-loop\",\n      service = (name = \"js-rpc-test\", entrypoint = \"MyService\"),\n      http = (capnpConnectHost = \"cappy\")\n    ),\n    ( name = \"MyServiceProxy-loop\",\n      address = \"loopback:MyServiceProxy-loop\",\n      service = (name = \"js-rpc-test\", entrypoint = \"MyServiceProxy\"),\n      http = (capnpConnectHost = \"cappy\")\n    ),\n    ( name = \"nonClass-loop\",\n      address = \"loopback:nonClass-loop\",\n      service = (name = \"js-rpc-test\", entrypoint = \"nonClass\"),\n      http = (capnpConnectHost = \"cappy\")\n    ),\n    ( name = \"default-loop\",\n      address = \"loopback:default-loop\",\n      service = (name = \"js-rpc-test\"),\n      http = (capnpConnectHost = \"cappy\")\n    ),\n    ( name = \"GreeterFactory-loop\",\n      address = \"loopback:GreeterFactory-loop\",\n      service = (name = \"js-rpc-test\", entrypoint = \"GreeterFactory\"),\n      http = (capnpConnectHost = \"cappy\")\n    ),\n  ],\n  v8Flags = [ \"--expose-gc\" ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/js-rpc-test.js",
    "content": "import assert from 'node:assert';\nimport { waitUntil } from 'cloudflare:workers';\nimport {\n  WorkerEntrypoint,\n  DurableObject,\n  RpcPromise,\n  RpcProperty,\n  RpcStub,\n  RpcTarget,\n  ServiceStub,\n} from 'cloudflare:workers';\n\ntry {\n  waitUntil(null);\n  throw new Error('This should have thrown');\n} catch (error) {\n  assert.match(\n    error.message,\n    /Disallowed operation called within global scope./\n  );\n}\n\nclass MyCounter extends RpcTarget {\n  constructor(i = 0) {\n    super();\n    this.i = i;\n  }\n\n  async increment(j) {\n    this.i += j;\n    return this.i;\n  }\n\n  disposed = false;\n\n  #disposedResolver;\n  #onDisposedPromise = new Promise(\n    (resolve) => (this.#disposedResolver = resolve)\n  );\n\n  [Symbol.dispose]() {\n    this.disposed = true;\n    this.#disposedResolver();\n  }\n\n  onDisposed() {\n    return Promise.race([\n      this.#onDisposedPromise,\n      scheduler.wait(1000).then(() => {\n        throw new Error('timed out waiting for disposal');\n      }),\n    ]);\n  }\n\n  // Tests that `fetch()` is not special for RpcTargets.\n  async fetch(a, b, c) {\n    return `${this.i} ${a} ${b} ${c}`;\n  }\n}\n\nclass RpcBox extends RpcTarget {\n  #value;\n\n  constructor(value) {\n    super();\n    this.#value = value;\n  }\n\n  get value() {\n    return this.#value;\n  }\n}\n\nclass NonRpcClass {\n  foo() {\n    return 123;\n  }\n  get bar() {\n    return {\n      baz() {\n        return 456;\n      },\n    };\n  }\n\n  i = 0;\n  increment(i) {\n    this.i += i;\n  }\n}\n\nexport let nonClass = {\n  async noArgs(x, env, ctx) {\n    assert.strictEqual(typeof ctx.waitUntil, 'function');\n    return x === undefined ? env.twelve + 1 : 'param not undefined?';\n  },\n\n  async oneArg(i, env, ctx) {\n    assert.strictEqual(typeof ctx.waitUntil, 'function');\n    return env.twelve * i;\n  },\n\n  async oneArgOmitCtx(i, env) {\n    return env.twelve * i + 1;\n  },\n\n  async oneArgOmitEnvCtx(i) {\n    return 2 * i;\n  },\n\n  async twoArgs(i, j, env, ctx) {\n    assert.strictEqual(typeof ctx.waitUntil, 'function');\n    return i * j + env.twelve;\n  },\n\n  async fetch(req, env, ctx) {\n    // This is used in the stream test to fetch some gziped data.\n    if (req.url.endsWith('/gzip')) {\n      return new Response('this text was gzipped', {\n        headers: {\n          'Content-Encoding': 'gzip',\n        },\n      });\n    } else if (req.url.endsWith('/stream-from-rpc')) {\n      let stream = await env.MyService.returnReadableStream();\n      return new Response(stream);\n    } else {\n      throw new Error('unknown route');\n    }\n  },\n};\n\n// Globals used to test passing RPC promises or properties across I/O contexts (which is expected\n// to fail).\nlet globalRpcPromise;\n\n// Promise initialized by testWaitUntil() and then resolved shortly later, in a waitUntil task.\nlet globalWaitUntilPromise;\n\nexport class MyService extends WorkerEntrypoint {\n  constructor(ctx, env) {\n    super(ctx, env);\n\n    assert.strictEqual(this.ctx, ctx);\n    assert.strictEqual(this.env, env);\n\n    // This shouldn't be callable!\n    this.instanceMethod = () => {\n      return 'nope';\n    };\n\n    this.instanceObject = {\n      func: (a) => a * 5,\n    };\n  }\n\n  async noArgsMethod(x) {\n    return x === undefined ? this.env.twelve + 1 : 'param not undefined?';\n  }\n\n  async oneArgMethod(i) {\n    return this.env.twelve * i;\n  }\n\n  async twoArgsMethod(i, j) {\n    return i * j + this.env.twelve;\n  }\n\n  async makeCounter(i) {\n    return new MyCounter(i);\n  }\n\n  async incrementCounter(counter, i) {\n    return await counter.increment(i);\n  }\n\n  async getAnObject(i) {\n    return { foo: 123 + i, counter: new MyCounter(i) };\n  }\n\n  async getADeeperObject(i) {\n    return { foo: 123 + i, box: new RpcBox(new RpcStub(new MyCounter(i))) };\n  }\n\n  async getMap() {\n    let map = new Map();\n    map.set('foo', 123);\n    map.set('bar', 456);\n    return map;\n  }\n\n  async fetch(req, x) {\n    assert.strictEqual(x, undefined);\n    return new Response('method = ' + req.method + ', url = ' + req.url);\n  }\n\n  // Define a property to test behavior of property accessors.\n  get nonFunctionProperty() {\n    return { foo: 123 };\n  }\n\n  get functionProperty() {\n    return (a, b) => a - b;\n  }\n\n  get objectProperty() {\n    let nullPrototype = { foo: 123 };\n    nullPrototype.__proto__ = null;\n\n    return {\n      func: (a, b) => a * b,\n      deeper: {\n        deepFunc: (a, b) => a / b,\n      },\n      counter5: new MyCounter(5),\n      nonRpc: new NonRpcClass(),\n      nullPrototype,\n      someText: 'hello',\n    };\n  }\n\n  get promiseProperty() {\n    return scheduler.wait(10).then(() => 123);\n  }\n\n  get rejectingPromiseProperty() {\n    return Promise.reject(new Error('REJECTED'));\n  }\n\n  get throwingProperty() {\n    throw new Error('PROPERTY THREW');\n  }\n\n  throwingMethod() {\n    const err = new Error('METHOD THREW');\n    err.abc = 123;\n    throw err;\n  }\n\n  async neverReturn() {\n    await new Promise((resolve) => {});\n  }\n\n  async tryUseGlobalRpcPromise() {\n    return await globalRpcPromise;\n  }\n  async tryUseGlobalRpcPromisePipeline() {\n    return await globalRpcPromise.increment(1);\n  }\n\n  async getNonRpcClass() {\n    return { obj: new NonRpcClass() };\n  }\n\n  async getNullPrototypeObject() {\n    let obj = { foo: 123 };\n    obj.__proto__ = null;\n    return obj;\n  }\n\n  async getFunction() {\n    let func = (a, b) => a ^ b;\n    func.someProperty = 123;\n    return func;\n  }\n\n  getRpcPromise(callback) {\n    return callback();\n  }\n  getNestedRpcPromise(callback) {\n    return { value: callback() };\n  }\n  getRemoteNestedRpcPromise(callback) {\n    // Use a function as a cheap way to return a JsRpcStub that has a remote property `value` which\n    // itself is initialized as a JsRpcPromise.\n    let result = () => {};\n    result.value = callback();\n    return result;\n  }\n  getRpcProperty(callback) {\n    return callback.foo;\n  }\n  getNestedRpcProperty(callback) {\n    return { value: callback.foo };\n  }\n  getRemoteNestedRpcProperty(callback) {\n    // Use a function as a cheap way to return a JsRpcStub that has a remote property `value` which\n    // itself is initialized as a JsRpcProperty.\n    let result = () => {};\n\n    // We have to dup() the callback because otherwise it will be implicitly disposed when this\n    // function returns, but we want it to remain accessible as a property of the returned\n    // function.\n    let dupCallback = callback.dup();\n    result.value = dupCallback.foo;\n\n    // Not really\n    result[Symbol.dispose] = () => dupCallback[Symbol.dispose]();\n\n    return result;\n  }\n\n  get(a) {\n    return a + 1;\n  }\n  put(a, b) {\n    return a + b;\n  }\n  delete(a) {\n    return a - 1;\n  }\n\n  async testDispose(counter) {\n    // Prove that the counter works at this point.\n    let count = await counter.increment(5);\n\n    let counterDup = counter.dup();\n\n    return {\n      count,\n      counter: counter.dup(), // need to dup() for return\n      async incrementOriginal(n) {\n        // This will fail because after the call ends, the counter stub is disposed.\n        return await counter.increment(n);\n      },\n      async incrementDup(n) {\n        // This will succeed since we kept a dup.\n        return await counterDup.increment(n);\n      },\n      [Symbol.dispose]() {\n        counterDup[Symbol.dispose]();\n      },\n    };\n  }\n\n  async leak(stub) {\n    // Leak the input stub\n    stub.dup();\n\n    let ctx = this.ctx;\n\n    // Return something that contains stubs, holding the context open.\n    return {\n      noop() {},\n      abort() {\n        ctx.abort(new RangeError('foo bar abort reason'));\n      },\n    };\n  }\n\n  async leakButReturnPlainObject(stub) {\n    // Leak the input stub, so it will be disposed when the context is torn down.\n    stub.dup();\n\n    // Return a plain object (one with no stubs and no disposer). This should NOT\n    // hold the context open, so the stub should be dropped promptly.\n    return { foo: 123 };\n  }\n\n  async writeToStream(stream) {\n    let writer = stream.getWriter();\n    let enc = new TextEncoder();\n    await writer.write(enc.encode('foo, '));\n    await writer.write(enc.encode('bar, '));\n    await writer.write(enc.encode('baz!'));\n    await writer.close();\n  }\n\n  async writeToStreamExpectingError(stream) {\n    // We expect the writes to fail on the remote end. However, that won't happen\n    // right away. Due to backpressure and flow control, write does not wait until\n    // the remote end has received the data. It resolves as soon as there is space\n    // in the buffer to perform another write. Here we'll just keep writing until\n    // we get the expected error.\n    let writer = stream.getWriter();\n    let enc = new TextEncoder();\n    for (;;) {\n      await writer.write(enc.encode('foo, '));\n    }\n  }\n\n  async writeToStreamAbort(stream) {\n    // In this test, aborting the stream should propagate back to the remote\n    // side, causing the stream to be errored and the abort algorithm to be\n    // called with the provided error. Unfortunately the current implementation\n    // does not allow for that. The reason passed to abort is cached locally and\n    // is never communicated to the remote. Instead, the remote side will end up\n    // with a generic disconnect error. Sad face.\n    let writer = stream.getWriter();\n    writer.abort(new Error('boom'));\n  }\n\n  async readFromStream(stream) {\n    return await new Response(stream).text();\n  }\n\n  async returnReadableStream() {\n    let { readable, writable } = new IdentityTransformStream();\n    this.ctx.waitUntil(this.writeToStream(writable));\n    return readable;\n  }\n\n  async returnMultipleReadableStreams() {\n    let { readable, writable } = new IdentityTransformStream();\n    this.ctx.waitUntil(this.writeToStream(writable));\n\n    let pair2 = new IdentityTransformStream();\n    this.ctx.waitUntil(this.writeToStream(pair2.writable));\n    let readable2 = pair2.readable;\n\n    return [readable, readable2];\n  }\n\n  async roundTrip(value) {\n    return value;\n  }\n\n  async returnEmptyHeaders() {\n    return new Headers();\n  }\n\n  async returnHeaders() {\n    let result = new Headers();\n    result.append('foo', 'bar');\n    result.append('Set-Cookie', 'abc');\n    result.append('set-cookie', 'def');\n    result.append('corge', '!@#');\n    result.append('Content-Length', '123');\n    return result;\n  }\n\n  async returnRequest() {\n    return new Request('http://my-url.com', {\n      method: 'PUT',\n      headers: {\n        'Accept-Encoding': 'bazzip',\n        Foo: 'Bar',\n      },\n      redirect: 'manual',\n      body: 'Hello every body!',\n      cf: {\n        abc: 123,\n        hello: 'goodbye',\n      },\n    });\n  }\n\n  async returnResponse() {\n    return new Response('Response body!', {\n      status: 404,\n      headers: {\n        'Content-Type': 'abc',\n      },\n      cf: { foo: 123, bar: 'def' },\n    });\n  }\n\n  testWaitUntil() {\n    // Initialize globalWaitUntilPromise to a promise that will be resolved in a waitUntil task\n    // later on. We'll perform a cross-context wait to verify that the waitUntil task actually\n    // completes and resolves the promise.\n    let resolve;\n    globalWaitUntilPromise = new Promise((r) => {\n      resolve = r;\n    });\n\n    this.ctx.waitUntil(scheduler.wait(100).then(resolve));\n  }\n\n  testImportedWaitUntil() {\n    // Initialize globalWaitUntilPromise to a promise that will be resolved in a waitUntil task\n    // later on. We'll perform a cross-context wait to verify that the waitUntil task actually\n    // completes and resolves the promise.\n    let resolve;\n    globalWaitUntilPromise = new Promise((r) => {\n      resolve = r;\n    });\n\n    waitUntil(scheduler.wait(100).then(resolve));\n  }\n\n  async call(func, arg) {\n    return await func(arg);\n  }\n\n  async getProp(obj, prop) {\n    return await obj[prop];\n  }\n\n  // Useful to test pipelining.\n  async identity(x) {\n    return x;\n  }\n}\n\n// An entrypoint which forwards methods calls to MyService, thus acting as a proxy.\nexport class MyServiceProxy extends WorkerEntrypoint {\n  makeCounter(i) {\n    return this.env.MyService.makeCounter(i);\n  }\n\n  getAnObject(i) {\n    return this.env.MyService.getAnObject(i);\n  }\n\n  getADeeperObject(i) {\n    return this.env.MyService.getADeeperObject(i);\n  }\n}\n\nclass PostAbortCallTester extends RpcTarget {\n  constructor(ctx) {\n    super();\n    this.ctx = ctx;\n  }\n\n  ping() {\n    return 'pong';\n  }\n\n  hang() {\n    return new Promise((resolve) => {});\n  }\n\n  abort() {\n    this.ctx.abort('test aborted by abort()');\n  }\n\n  async failCriticalSection() {\n    await this.ctx.blockConcurrencyWhile(() => {\n      throw new Error('test broken critical section');\n    });\n  }\n}\n\nexport class MyActor extends DurableObject {\n  #counter = 0;\n\n  constructor(ctx, env) {\n    super(ctx, env);\n\n    assert.strictEqual(this.ctx, ctx);\n    assert.strictEqual(this.env, env);\n  }\n\n  async increment(amount) {\n    this.#counter += amount;\n    return this.#counter;\n  }\n\n  async doCallbackBlockingConcurrency() {\n    // Check that we can receive RPC callbacks during blockConcurrencyWhile(), if they are from\n    // an RPC running inside the block. This verifies that the critical section is captured\n    // correctly in IoContext::makeReentryCallback().\n    return this.ctx.blockConcurrencyWhile(async () => {\n      let func = () => {\n        return 12345;\n      };\n      return await this.env.MyService.getRpcPromise(func);\n    });\n  }\n\n  makePostAbortCallTester() {\n    return new PostAbortCallTester(this.ctx);\n  }\n}\n\nexport class ActorNoExtends {\n  async fetch(req) {\n    return new Response('from ActorNoExtends');\n  }\n\n  // This can't be called!\n  async foo() {\n    return 123;\n  }\n}\n\nexport default class DefaultService extends WorkerEntrypoint {\n  async fetch(req) {\n    // Test this.env here just to prove omitting the constructor entirely works.\n    return new Response('default service ' + this.env.twelve);\n  }\n}\n\nexport let basicServiceBinding = {\n  async test(controller, env, ctx) {\n    // Test service binding RPC.\n    assert.strictEqual(await env.self.oneArg(3), 36);\n    assert.strictEqual(await env.self.oneArgOmitCtx(3), 37);\n    assert.strictEqual(await env.self.oneArgOmitEnvCtx(3), 6);\n    await assert.rejects(() => env.self.twoArgs(123, 2), {\n      name: 'TypeError',\n      message:\n        'Cannot call handler function \"twoArgs\" over RPC because it has the wrong ' +\n        'number of arguments. A simple function handler can only be called over RPC if it has ' +\n        'exactly the arguments (arg, env, ctx), where only the first argument comes from the ' +\n        'client. To support multi-argument RPC functions, use class-based syntax (extending ' +\n        'WorkerEntrypoint) instead.',\n    });\n    await assert.rejects(() => env.self.noArgs(), {\n      name: 'TypeError',\n      message:\n        'Attempted to call RPC function \"noArgs\" with the wrong number of arguments. ' +\n        'When calling a top-level handler function that is not declared as part of a class, you ' +\n        'must always send exactly one argument. In order to support variable numbers of ' +\n        'arguments, the server must use class-based syntax (extending WorkerEntrypoint) ' +\n        'instead.',\n    });\n    await assert.rejects(() => env.self.oneArg(1, 2), {\n      name: 'TypeError',\n      message:\n        'Attempted to call RPC function \"oneArg\" with the wrong number of arguments. ' +\n        'When calling a top-level handler function that is not declared as part of a class, you ' +\n        'must always send exactly one argument. In order to support variable numbers of ' +\n        'arguments, the server must use class-based syntax (extending WorkerEntrypoint) ' +\n        'instead.',\n    });\n\n    // If we restore multi-arg support, remove the `rejects` checks above and un-comment these:\n    // assert.strictEqual(await env.self.noArgs(), 13);\n    // assert.strictEqual(await env.self.twoArgs(123, 2), 258);\n    // assert.strictEqual(await env.self.twoArgs(123, 2, \"foo\", \"bar\", \"baz\"), 258);\n  },\n};\n\nexport let extendingEntrypointClasses = {\n  async test(controller, env, ctx) {\n    // Verify that we can instantiate classes that inherit built-in classes.\n    let svc = new MyService(ctx, env);\n    assert.equal(svc instanceof WorkerEntrypoint, true);\n  },\n};\n\nexport let namedServiceBinding = {\n  async test(controller, env, ctx) {\n    assert.strictEqual(await env.MyService.noArgsMethod(), 13);\n    assert.strictEqual(await env.MyService.oneArgMethod(3), 36);\n    assert.strictEqual(await env.MyService.twoArgsMethod(123, 2), 258);\n\n    // Properties that return a function can actually be called.\n    assert.strictEqual(await env.MyService.functionProperty(19, 6), 13);\n\n    // Members of an object-typed property can be invoked.\n    assert.strictEqual(await env.MyService.objectProperty.func(6, 4), 24);\n    assert.strictEqual(\n      await env.MyService.objectProperty.deeper.deepFunc(6, 3),\n      2\n    );\n    assert.strictEqual(\n      await env.MyService.objectProperty.counter5.increment(3),\n      8\n    );\n\n    // Awaiting a property itself gets the value.\n    assert.strictEqual(\n      JSON.stringify(await env.MyService.nonFunctionProperty),\n      '{\"foo\":123}'\n    );\n    assert.strictEqual(await env.MyService.objectProperty.someText, 'hello');\n    {\n      let counter = await env.MyService.objectProperty.counter5;\n      assert.strictEqual(await counter.increment(3), 8);\n      assert.strictEqual(await counter.increment(7), 15);\n    }\n\n    {\n      let func = await env.MyService.objectProperty.func;\n      assert.strictEqual(await func(3, 7), 21);\n    }\n    {\n      let func = await env.MyService.getFunction();\n      assert.strictEqual(await func(3, 6), 5);\n      assert.strictEqual(await func.someProperty, 123);\n    }\n    {\n      // Pipeline the function call.\n      let func = env.MyService.getFunction();\n      assert.strictEqual(await func(3, 6), 5);\n      assert.strictEqual(await func.someProperty, 123);\n    }\n\n    // A property that returns a Promise will wait for the Promise.\n    assert.strictEqual(await env.MyService.promiseProperty, 123);\n\n    let sawFinally = false;\n    assert.strictEqual(\n      await env.MyService.promiseProperty.finally(() => {\n        sawFinally = true;\n      }),\n      123\n    );\n    assert.strictEqual(sawFinally, true);\n\n    // `fetch()` is special, the params get converted into a Request.\n    let resp = await env.MyService.fetch('http://foo/', { method: 'POST' });\n    assert.strictEqual(await resp.text(), 'method = POST, url = http://foo/');\n\n    await assert.rejects(() => env.MyService.instanceMethod(), {\n      name: 'TypeError',\n      message:\n        'The RPC receiver does not implement the method \"instanceMethod\".',\n    });\n\n    await assert.rejects(() => env.MyService.instanceObject.func(3), {\n      name: 'TypeError',\n      message:\n        'The RPC receiver does not implement the method \"instanceObject\".',\n    });\n\n    await assert.rejects(() => env.MyService.instanceObject, {\n      name: 'TypeError',\n      message:\n        'The RPC receiver does not implement the method \"instanceObject\".',\n    });\n\n    await assert.rejects(() => env.MyService.throwingProperty, {\n      name: 'Error',\n      message: 'PROPERTY THREW',\n    });\n    await assert.rejects(() => env.MyService.throwingMethod(), {\n      name: 'Error',\n      message: 'METHOD THREW',\n    });\n\n    await assert.rejects(() => env.MyService.rejectingPromiseProperty, {\n      name: 'Error',\n      message: 'REJECTED',\n    });\n    assert.strictEqual(\n      await env.MyService.rejectingPromiseProperty.catch((err) => {\n        assert.strictEqual(err.message, 'REJECTED');\n        return 234;\n      }),\n      234\n    );\n    assert.strictEqual(\n      await env.MyService.rejectingPromiseProperty.then(\n        () => 432,\n        (err) => {\n          assert.strictEqual(err.message, 'REJECTED');\n          return 234;\n        }\n      ),\n      234\n    );\n\n    let getByName = (name) => {\n      return env.MyService.getRpcMethodForTestOnly(name);\n    };\n\n    // Check getRpcMethodForTestOnly() actually works.\n    assert.strictEqual(await getByName('twoArgsMethod')(2, 3), 18);\n\n    // Check we cannot call reserved methods.\n    await assert.rejects(() => getByName('constructor')(), {\n      name: 'TypeError',\n      message:\n        \"'constructor' is a reserved method and cannot be called over RPC.\",\n    });\n    await assert.rejects(() => getByName('fetch')(), {\n      name: 'TypeError',\n      message: \"'fetch' is a reserved method and cannot be called over RPC.\",\n    });\n\n    // Check we cannot call methods of Object.\n    await assert.rejects(() => getByName('toString')(), {\n      name: 'TypeError',\n      message: 'The RPC receiver does not implement the method \"toString\".',\n    });\n    await assert.rejects(() => getByName('hasOwnProperty')(), {\n      name: 'TypeError',\n      message:\n        'The RPC receiver does not implement the method \"hasOwnProperty\".',\n    });\n\n    // Check we cannot access `env` or `ctx`.\n    await assert.rejects(() => getByName('env')(), {\n      name: 'TypeError',\n      message: 'The RPC receiver does not implement the method \"env\".',\n    });\n    await assert.rejects(() => getByName('ctx')(), {\n      name: 'TypeError',\n      message: 'The RPC receiver does not implement the method \"ctx\".',\n    });\n\n    // Check what happens if we access something that's actually declared as a property on the\n    // class. The difference in error message proves to us that `env` and `ctx` weren't visible at\n    // all, which is what we want.\n    await assert.rejects(() => getByName('nonFunctionProperty')(), {\n      name: 'TypeError',\n      message: '\"nonFunctionProperty\" is not a function.',\n    });\n    await assert.rejects(() => getByName('nonFunctionProperty').foo(), {\n      name: 'TypeError',\n      message: '\"nonFunctionProperty.foo\" is not a function.',\n    });\n\n    // Check that we can't access contents of a property that is a class but not derived from\n    // RpcTarget.\n    await assert.rejects(() => env.MyService.objectProperty.nonRpc.foo(), {\n      name: 'TypeError',\n      message: 'The RPC receiver does not implement the method \"nonRpc\".',\n    });\n    await assert.rejects(() => env.MyService.objectProperty.nonRpc.bar.baz(), {\n      name: 'TypeError',\n      message: 'The RPC receiver does not implement the method \"nonRpc\".',\n    });\n    await assert.rejects(() => env.MyService.objectProperty.nullPrototype.foo, {\n      name: 'TypeError',\n      message:\n        'The RPC receiver does not implement the method \"nullPrototype\".',\n    });\n\n    // Extra-paranoid check that we can't access methods on env or ctx.\n    await assert.rejects(\n      () => env.MyService.objectProperty.env.MyService.noArgsMethod(),\n      {\n        name: 'TypeError',\n        message: 'The RPC receiver does not implement the method \"env\".',\n      }\n    );\n    await assert.rejects(() => env.MyService.objectProperty.ctx.waitUntil(), {\n      name: 'TypeError',\n      message: 'The RPC receiver does not implement the method \"ctx\".',\n    });\n\n    // Can't serialize instances of classes that aren't derived from RpcTarget.\n    await assert.rejects(() => env.MyService.getNonRpcClass(), {\n      name: 'DataCloneError',\n      message:\n        'Could not serialize object of type \"NonRpcClass\". This type does not support ' +\n        'serialization.',\n    });\n    await assert.rejects(() => env.MyService.getNullPrototypeObject(), {\n      name: 'DataCloneError',\n      message:\n        'Could not serialize object of type \"Object\". This type does not support ' +\n        'serialization.',\n    });\n\n    // A stateless entryponit method that never returns should fail due to PendingEvent tracking.\n    await assert.rejects(() => env.MyService.neverReturn(), {\n      name: 'Error',\n      message:\n        \"The Workers runtime canceled this request because it detected that your Worker's code \" +\n        'had hung and would never generate a response. Refer to: ' +\n        'https://developers.cloudflare.com/workers/observability/errors/',\n    });\n\n    {\n      let map = await env.MyService.getMap();\n      assert.strictEqual(map.get('foo'), 123);\n      assert.strictEqual(map.get('bar'), 456);\n      assert.strictEqual(map.get('baz'), undefined);\n    }\n  },\n};\n\nexport let namedActorBinding = {\n  async test(controller, env, ctx) {\n    let id = env.MyActor.idFromName('foo');\n    let stub = env.MyActor.get(id);\n\n    assert.strictEqual(await stub.increment(5), 5);\n    assert.strictEqual(await stub.increment(2), 7);\n    assert.strictEqual(await stub.increment(8), 15);\n\n    assert.strictEqual(await stub.doCallbackBlockingConcurrency(), 12345);\n  },\n};\n\n// Test that if the actor class doesn't extend `DurableObject`, we don't allow RPC.\nexport let actorWithoutExtendsRejectsRpc = {\n  async test(controller, env, ctx) {\n    let id = env.ActorNoExtends.idFromName('foo');\n    let stub = env.ActorNoExtends.get(id);\n\n    // fetch() works.\n    let resp = await stub.fetch('http://foo');\n    assert.strictEqual(await resp.text(), 'from ActorNoExtends');\n\n    // RPC does not.\n    await assert.rejects(() => stub.foo(), {\n      name: 'TypeError',\n      message:\n        'The receiving Durable Object does not support RPC, because its class was not declared ' +\n        'with `extends DurableObject`. In order to enable RPC, make sure your class ' +\n        'extends the special class `DurableObject`, which can be imported from the module ' +\n        '\"cloudflare:workers\".',\n    });\n  },\n};\n\n// Test calling the default export when it is a class.\nexport let defaultExportClass = {\n  async test(controller, env, ctx) {\n    let resp = await env.defaultExport.fetch('http://foo');\n    assert.strictEqual(await resp.text(), 'default service 12');\n  },\n};\n\nexport let loopbackJsRpcTarget = {\n  async test(controller, env, ctx) {\n    {\n      let counter = new MyCounter(4);\n      let stub = new RpcStub(counter);\n      assert.strictEqual(await stub.increment(5), 9);\n      assert.strictEqual(await stub.increment(7), 16);\n\n      assert.strictEqual(await stub.fetch(true, 123, 'baz'), '16 true 123 baz');\n\n      assert.strictEqual(counter.disposed, false);\n      stub[Symbol.dispose]();\n\n      await assert.rejects(stub.increment(2), {\n        name: 'Error',\n        message: 'RPC stub used after being disposed.',\n      });\n\n      await counter.onDisposed();\n      assert.strictEqual(counter.disposed, true);\n\n      assert.strictEqual(stub instanceof RpcStub, true);\n      assert.strictEqual(stub.increment instanceof RpcProperty, true);\n      assert.strictEqual(stub.increment(1) instanceof RpcPromise, true);\n      assert.strictEqual(env.MyService instanceof ServiceStub, true);\n    }\n\n    // In fact, RpcStubs can be created from any old object.\n    {\n      let stub = new RpcStub({\n        sum(a, b) {\n          return a + b;\n        },\n      });\n\n      assert.strictEqual(await stub.sum(12, 34), 46);\n    }\n\n    // Or function.\n    {\n      let func = (a, b) => {\n        return a + b;\n      };\n      func.ownProperty = 'hello';\n      let stub = new RpcStub(func);\n\n      assert.strictEqual(await stub(12, 34), 46);\n      assert.strictEqual(await stub.ownProperty, 'hello');\n    }\n\n    // Or Proxy of an RpcTarget.\n    {\n      let counter = new MyCounter(4);\n      let stub = new RpcStub(new Proxy(counter, {}));\n      assert.strictEqual(await stub.increment(5), 9);\n    }\n  },\n};\n\nexport let sendStubOverRpc = {\n  async test(controller, env, ctx) {\n    let stub = new RpcStub(new MyCounter(4));\n    let stubDup = stub.dup();\n\n    assert.strictEqual(await env.MyService.incrementCounter(stub, 5), 9);\n\n    await assert.rejects(() => stub.increment(7), {\n      name: 'Error',\n      message: 'RPC stub used after being disposed.',\n    });\n\n    assert.strictEqual(await stubDup.increment(7), 16);\n  },\n};\n\nexport let receiveStubOverRpc = {\n  async test(controller, env, ctx) {\n    let stub = await env.MyService.makeCounter(17);\n    assert.strictEqual(await stub.increment(2), 19);\n    assert.strictEqual(await stub.increment(-10), 9);\n\n    // Do multiple concurrent calls, they should be delivered in the order in which they were made.\n    let promise1 = stub.increment(6);\n    let promise2 = stub.increment(4);\n    let promise3 = stub.increment(3);\n    assert.deepEqual(\n      await Promise.all([promise1, promise2, promise3]),\n      [15, 19, 22]\n    );\n  },\n};\n\nexport let promisePipelining = {\n  async test(controller, env, ctx) {\n    assert.strictEqual(await env.MyService.makeCounter(12).increment(3), 15);\n\n    assert.strictEqual(await env.MyService.getAnObject(5).foo, 128);\n    assert.strictEqual(\n      await env.MyService.getAnObject(5).counter.increment(7),\n      12\n    );\n\n    assert.rejects(() => env.MyService.oneArgMethod(5).foo(), {\n      name: 'TypeError',\n      message: 'The RPC receiver does not implement the method \"foo\".',\n    });\n\n    assert.rejects(() => env.MyService.getMap().foo(), {\n      name: 'TypeError',\n      message: 'The RPC receiver does not implement the method \"foo\".',\n    });\n  },\n};\n\n// Test promise pipelining through a proxy.\nexport let promisePipeliningProxy = {\n  async test(controller, env, ctx) {\n    // Pipeline on a proxied call that just returns a stub.\n    {\n      let counter = env.MyServiceProxy.makeCounter(12);\n      let promise1 = counter.increment(3);\n      let promise2 = counter.increment(5);\n      assert.strictEqual(await promise1, 15);\n      assert.strictEqual(await promise2, 20);\n    }\n\n    // Pipeline on a proxied call that returns an object containing a stub.\n    {\n      let counter = env.MyServiceProxy.getAnObject(12).counter;\n      let promise1 = counter.increment(3);\n      let promise2 = counter.increment(5);\n      assert.strictEqual(await promise1, 15);\n      assert.strictEqual(await promise2, 20);\n    }\n\n    // Pipeline on a proxied call that returns an object containing an object that contains a\n    // stub. (This ensures that pipelining can traverse JsRpcProperty values.)\n    {\n      let counter = env.MyServiceProxy.getADeeperObject(12).box.value;\n      let promise1 = counter.increment(3);\n      let promise2 = counter.increment(5);\n      assert.strictEqual(await promise1, 15);\n      assert.strictEqual(await promise2, 20);\n    }\n  },\n};\n\nexport let disposal = {\n  async test(controller, env, ctx) {\n    // Call function that returns plain stub. Dispose it.\n    {\n      let counter = await env.MyService.makeCounter(12);\n      assert.strictEqual(await counter.increment(3), 15);\n      counter[Symbol.dispose]();\n      await assert.rejects(counter.increment(2), {\n        name: 'Error',\n        message: 'RPC stub used after being disposed.',\n      });\n    }\n\n    // Call function that returns an object containing a stub. The object has a dispose() method\n    // added that disposes everything inside.\n    {\n      let obj = await env.MyService.getAnObject(5);\n      assert.strictEqual(await obj.counter.increment(7), 12);\n\n      obj[Symbol.dispose]();\n      await assert.rejects(obj.counter.increment(2), {\n        name: 'Error',\n        message: 'RPC stub used after being disposed.',\n      });\n    }\n\n    // Verify a capability passed as an RPC param receives the disposal callback.\n    {\n      let counter = new MyCounter(3);\n      assert.strictEqual(await env.MyService.incrementCounter(counter, 5), 8);\n      await counter.onDisposed();\n      assert.strictEqual(counter.disposed, true);\n    }\n\n    // A more complex case with testDispose().\n    {\n      let counter = new MyCounter(3);\n      let obj = await env.MyService.testDispose(counter);\n\n      // The counter was able to be incremented during the call.\n      assert.strictEqual(obj.count, 8);\n\n      // But after the call, the stub is disposed.\n      await assert.rejects(obj.incrementOriginal(), {\n        name: 'Error',\n        message: 'RPC stub used after being disposed.',\n      });\n\n      // The duplicate we created in the call still works though.\n      assert.strictEqual(await obj.incrementDup(7), 15);\n\n      // Also, the returned copy works.\n      assert.strictEqual(await obj.counter.increment(4), 19);\n\n      // But of course, disposing the return value overall breaks everything.\n      obj[Symbol.dispose]();\n      await assert.rejects(obj.counter.increment(2), {\n        name: 'Error',\n        message: 'RPC stub used after being disposed.',\n      });\n      await assert.rejects(obj.incrementDup(7), {\n        name: 'Error',\n        message: 'RPC stub used after being disposed.',\n      });\n\n      await counter.onDisposed();\n      assert.strictEqual(counter.disposed, true);\n    }\n\n    // Test a leak situation.\n    {\n      let counter = new MyCounter(3);\n      let obj = await env.MyService.leak(counter);\n\n      // Give a chance for disposal to happen.\n      await obj.noop();\n      await scheduler.wait(10);\n\n      // It should not have happened.\n      assert.strictEqual(counter.disposed, false);\n\n      // Even if we GC the leaked stub, still no disposal!\n      gc();\n      await scheduler.wait(10);\n      assert.strictEqual(counter.disposed, false);\n\n      // If we abort the server's I/O context, though, then the counter is disposed.\n      await assert.rejects(obj.abort(), {\n        name: 'RangeError',\n        message: 'foo bar abort reason',\n      });\n\n      await counter.onDisposed();\n      assert.strictEqual(counter.disposed, true);\n    }\n\n    // Test that a call which returns a plain object does not need to be disposed.\n    // Historically, the callee context would not be torn down promptly.\n    {\n      let counter = new MyCounter(3);\n      await env.MyService.leakButReturnPlainObject(counter);\n\n      // Give a chance for disposal to happen. When running with a real socket, this involves\n      // non-deterministic scheduling, and it can be relatively slower when running under ASAN,\n      // QEMU, etc. so we'll try to dynamically adjust how much time we wait, up to 10s.\n      for (let i = 0; i < 1000; i++) {\n        if (counter.disposed) break;\n        await scheduler.wait(10);\n      }\n\n      // It should have happened! A call that returns a plain object should NOT\n      // require disposal to clean up its context!\n      assert.strictEqual(counter.disposed, true);\n    }\n  },\n};\n\nexport let crossContextSharingDoesntWork = {\n  async test(controller, env, ctx) {\n    // Test what happens if a JsRpcPromise or JsRpcProperty is shared cross-context. This is not\n    // intended to work in general, but there are specific cases where it does work, and we should\n    // avoid breaking those with future changes.\n\n    // Sharing an RPC promise between contexts works as long as the promise returns a simple value\n    // (with no I/O objects), since JsRpcPromise wraps a simple JS promise and we support sharing\n    // JS promises.\n    globalRpcPromise = env.MyService.oneArgMethod(2);\n    assert.strictEqual(await env.MyService.tryUseGlobalRpcPromise(), 24);\n\n    // Sharing a property of a service binding works, because the service  binding itself is not\n    // tied to an I/O context. Awaiting the property actually initiates a new RPC session from\n    // whatever context performed the await.\n    globalRpcPromise = env.MyService.nonFunctionProperty;\n    assert.strictEqual(\n      JSON.stringify(await env.MyService.tryUseGlobalRpcPromise()),\n      '{\"foo\":123}'\n    );\n\n    // OK, now let's look at cases that do NOT work. These all produce the same error.\n    let expectedError = {\n      name: 'Error',\n      message:\n        'Cannot perform I/O on behalf of a different request. I/O objects (such as streams, ' +\n        'request/response bodies, and others) created in the context of one request handler ' +\n        \"cannot be accessed from a different request's handler. This is a limitation of \" +\n        'Cloudflare Workers which allows us to improve overall performance.',\n    };\n\n    // A promise which resolves to a value that contains a stub. The stub cannot be used from a\n    // different context.\n    //\n    // Note that the part that actually fails here is not awaiting the promise, but rather when\n    // tryUseGlobalRpcPromise() tries to return the result, it tries to serialize the stub, but\n    // it can't do that from the wrong context.\n    globalRpcPromise = env.MyService.makeCounter(12);\n\n    await assert.rejects(() => env.MyService.tryUseGlobalRpcPromise(), {\n      name: 'Error',\n      message:\n        'Cannot perform I/O on behalf of a different request. I/O objects (such as streams, ' +\n        'request/response bodies, and others) created in the context of one request handler ' +\n        \"cannot be accessed from a different request's handler. This is a limitation of \" +\n        'Cloudflare Workers which allows us to improve overall performance. (I/O type: Client)',\n    });\n\n    // Pipelining on someone else's promise straight-up doesn't work.\n    await assert.rejects(() => env.MyService.tryUseGlobalRpcPromisePipeline(), {\n      name: 'Error',\n      message:\n        'Cannot perform I/O on behalf of a different request. I/O objects (such as streams, ' +\n        'request/response bodies, and others) created in the context of one request handler ' +\n        \"cannot be accessed from a different request's handler. This is a limitation of \" +\n        'Cloudflare Workers which allows us to improve overall performance. ' +\n        '(I/O type: JsRpcPromise)',\n    });\n\n    // Now let's try accessing a JsRpcProperty, where the property is NOT a direct property of a\n    // top-level service binding. This works even less than a JsRpcPromise, since there's no inner\n    // JS promise, it tries to create one on-demand, which fails because the parent object is\n    // tied to the original I/O context.\n    globalRpcPromise = env.MyService.getAnObject(5).counter;\n\n    await assert.rejects(() => env.MyService.tryUseGlobalRpcPromise(), {\n      name: 'Error',\n      message:\n        'Cannot perform I/O on behalf of a different request. I/O objects (such as streams, ' +\n        'request/response bodies, and others) created in the context of one request handler ' +\n        \"cannot be accessed from a different request's handler. This is a limitation of \" +\n        'Cloudflare Workers which allows us to improve overall performance. (I/O type: Pipeline)',\n    });\n\n    await assert.rejects(() => env.MyService.tryUseGlobalRpcPromisePipeline(), {\n      name: 'Error',\n      message:\n        'Cannot perform I/O on behalf of a different request. I/O objects (such as streams, ' +\n        'request/response bodies, and others) created in the context of one request handler ' +\n        \"cannot be accessed from a different request's handler. This is a limitation of \" +\n        'Cloudflare Workers which allows us to improve overall performance. ' +\n        '(I/O type: JsRpcPromise)',\n    });\n  },\n};\n\nexport let waitUntilWorks = {\n  async test(controller, env, ctx) {\n    // Tests ctx.waitUntil\n    {\n      globalWaitUntilPromise = null;\n      await env.MyService.testWaitUntil();\n\n      assert.ok(globalWaitUntilPromise instanceof Promise);\n      await globalWaitUntilPromise;\n    }\n\n    // Tests `import { waitUntil } from 'cloudflare:workers` on WorkerEntrypoint\n    {\n      globalWaitUntilPromise = null;\n      await env.MyService.testImportedWaitUntil();\n\n      assert.ok(globalWaitUntilPromise instanceof Promise);\n      await globalWaitUntilPromise;\n    }\n  },\n};\n\nexport let serializeRpcPromiseOrProprety = {\n  async test(controller, env, ctx) {\n    // What happens if we actually try to serialize a JsRpcPromise or JsRpcProperty? Let's make\n    // sure these aren't, for instance, treated as functions because they are callable.\n\n    let func = () => {\n      return { x: 123 };\n    };\n    func.foo = { x: 456 };\n\n    // If we directly return returning a JsRpcPromise, the system automatically awaits it on the\n    // server side because it's a thenable.\n    assert.deepEqual(await env.MyService.getRpcPromise(func), {\n      x: 123,\n    });\n\n    // Pipelining also works.\n    assert.strictEqual(await env.MyService.getRpcPromise(func).x, 123);\n\n    // If a JsRpcPromise appears somewhere in the serialization tree, it'll just fail serialization.\n    // NOTE: We could choose to make this work later.\n    await assert.rejects(() => env.MyService.getNestedRpcPromise(func), {\n      name: 'DataCloneError',\n      message:\n        'Could not serialize object of type \"RpcPromise\". This type does not support ' +\n        'serialization.',\n    });\n    await assert.rejects(() => env.MyService.getNestedRpcPromise(func).value, {\n      name: 'DataCloneError',\n      message:\n        'Could not serialize object of type \"RpcPromise\". This type does not support ' +\n        'serialization.',\n    });\n    await assert.rejects(\n      () => env.MyService.getNestedRpcPromise(func).value.x,\n      {\n        name: 'DataCloneError',\n        message:\n          'Could not serialize object of type \"RpcPromise\". This type does not support ' +\n          'serialization.',\n      }\n    );\n\n    // Things get a little weird when we return a stub which itself has properties that reflect\n    // our RPC promise. If we await fetch the JsRpcPromise itself, this works, again because\n    // somewhere along the line V8 says \"oh look a thenable\" and awaits it, before it can be\n    // subject to serialization. That's fine.\n    assert.deepEqual(\n      await env.MyService.getRemoteNestedRpcPromise(func).value,\n      { x: 123 }\n    );\n    await assert.rejects(\n      () => env.MyService.getRemoteNestedRpcPromise(func).value.x,\n      {\n        name: 'TypeError',\n        message: 'The RPC receiver does not implement the method \"value\".',\n      }\n    );\n\n    // The story is similar for a JsRpcProperty -- though the implementation details differ.\n    assert.deepEqual(await env.MyService.getRpcProperty(func), {\n      x: 456,\n    });\n    assert.strictEqual(await env.MyService.getRpcProperty(func).x, 456);\n    await assert.rejects(() => env.MyService.getNestedRpcProperty(func), {\n      name: 'DataCloneError',\n      message:\n        'Could not serialize object of type \"RpcProperty\". This type does not support ' +\n        'serialization.',\n    });\n    await assert.rejects(() => env.MyService.getNestedRpcProperty(func).value, {\n      name: 'DataCloneError',\n      message:\n        'Could not serialize object of type \"RpcProperty\". This type does not support ' +\n        'serialization.',\n    });\n    await assert.rejects(\n      () => env.MyService.getNestedRpcProperty(func).value.x,\n      {\n        name: 'DataCloneError',\n        message:\n          'Could not serialize object of type \"RpcProperty\". This type does not support ' +\n          'serialization.',\n      }\n    );\n\n    assert.deepEqual(\n      await env.MyService.getRemoteNestedRpcProperty(func).value,\n      { x: 456 }\n    );\n    await assert.rejects(\n      () => env.MyService.getRemoteNestedRpcProperty(func).value.x,\n      {\n        name: 'TypeError',\n        message: 'The RPC receiver does not implement the method \"value\".',\n      }\n    );\n    await assert.rejects(\n      () => env.MyService.getRemoteNestedRpcProperty(func).value(),\n      {\n        name: 'TypeError',\n        message: '\"foo\" is not a function.',\n      }\n    );\n  },\n};\n\nexport let streams = {\n  async test(controller, env, ctx) {\n    // Send WritableStream.\n    {\n      let { readable, writable } = new IdentityTransformStream();\n      let promise = env.MyService.writeToStream(writable);\n      let text = await new Response(readable).text();\n      assert.strictEqual(text, 'foo, bar, baz!');\n      await promise;\n    }\n\n    {\n      const dec = new TextDecoder();\n      let result = '';\n      const { promise, resolve } = Promise.withResolvers();\n      const writable = new WritableStream({\n        write(chunk) {\n          result += dec.decode(chunk, { stream: true });\n        },\n        close() {\n          result += dec.decode();\n          resolve();\n        },\n      });\n      const p1 = env.MyService.writeToStream(writable);\n      await promise;\n      assert.strictEqual(result, 'foo, bar, baz!');\n      await p1;\n    }\n\n    {\n      // In this test, the remote side writes a chunk to the stream below, which throws\n      // an error. Ideally the error would propagate back to the calling side so that the\n      // remote knows the stream failed and can no longer be written to. The call to\n      // writeToStreamExpectingError should throw because the error should be propagated\n      // through the round trip.\n      const dec = new TextDecoder();\n      let result = '';\n      let writeCalled = 0;\n      const writable = new WritableStream({\n        write(chunk) {\n          writeCalled++;\n          throw new Error('boom');\n        },\n      });\n\n      try {\n        await env.MyService.writeToStreamExpectingError(writable);\n        throw new Error('should have thrown');\n      } catch (err) {\n        assert.strictEqual(err.message, 'boom');\n        // The write method should have been called once.\n        assert.strictEqual(writeCalled, 1);\n      }\n    }\n\n    {\n      // In this test, the remote side aborts the writable stream it receives.\n      // The abort should propagate such that the abort algorithm is called and the\n      // writeToStreamAbort call should succeed. The error passed on to abort(reason)\n      // should be the error that was given on the remote side when abort is called,\n      // but we currently do not propagate the abort reason through. What ends up\n      // happening is that the local stream is dropped with a generic cancelation\n      // error.\n      const dec = new TextDecoder();\n      const { promise, resolve } = Promise.withResolvers();\n      const writable = new WritableStream({\n        write(chunk) {},\n        abort(reason) {\n          resolve(reason);\n        },\n      });\n      await env.MyService.writeToStreamAbort(writable);\n      const reason = await promise;\n      // TODO(someday): The reason should be the error that was passed to abort on the\n      // remote side, but we currently do not propagate this. We end up with a generic\n      // disconnection error instead, which certainly not ideal.\n      assert.strictEqual(\n        reason.message,\n        'WritableStream received over RPC was disconnected because the remote execution ' +\n          'context has endeded.'\n      );\n    }\n\n    // TODO(someday): Is there any way to construct an encoded WritableStream? Only system\n    //   streams can be encoded, but there's no API that returns an encoded WritableStream I think.\n\n    // Send ReadableStream.\n    {\n      let { readable, writable } = new IdentityTransformStream();\n      let promise = env.MyService.readFromStream(readable);\n\n      let writer = writable.getWriter();\n      let enc = new TextEncoder();\n      await writer.write(enc.encode('foo, '));\n      await writer.write(enc.encode('bar, '));\n      await writer.write(enc.encode('baz!'));\n      await writer.close();\n\n      assert.strictEqual(await promise, 'foo, bar, baz!');\n    }\n\n    // Send a JS-backed ReadableStream.\n    {\n      let controller;\n      let readable = new ReadableStream({\n        start(c) {\n          controller = c;\n        },\n      });\n      let promise = env.MyService.readFromStream(readable);\n\n      let enc = new TextEncoder();\n      controller.enqueue(enc.encode('foo, '));\n      controller.enqueue(enc.encode('bar, '));\n      controller.enqueue(enc.encode('baz!'));\n      controller.close();\n\n      assert.strictEqual(await promise, 'foo, bar, baz!');\n    }\n\n    // Send streams that are locked.\n    {\n      let { readable, writable } = new IdentityTransformStream();\n\n      let writer = writable.getWriter();\n      let enc = new TextEncoder();\n      writer.write(enc.encode('foo'));\n\n      let reader = readable.getReader();\n\n      assert.rejects(env.MyService.writeToStream(writable), {\n        name: 'TypeError',\n        message: 'The WritableStream has been locked to a writer.',\n      });\n      assert.rejects(env.MyService.readFromStream(readable), {\n        name: 'TypeError',\n        message: 'The ReadableStream has been locked to a reader.',\n      });\n\n      // Verify the streams still work.\n      let dec = new TextDecoder();\n      assert.strictEqual(dec.decode((await reader.read()).value), 'foo');\n\n      writer.write(enc.encode('bar'));\n      assert.strictEqual(dec.decode((await reader.read()).value), 'bar');\n\n      writer.close();\n      assert.strictEqual((await reader.read()).done, true);\n    }\n\n    // Receive ReadableStream.\n    {\n      let readable = await env.MyService.returnReadableStream();\n      let text = await new Response(readable).text();\n      assert.strictEqual(text, 'foo, bar, baz!');\n    }\n\n    // Receive multiple ReadableStreams.\n    {\n      let readables = await env.MyService.returnMultipleReadableStreams();\n      assert.strictEqual(\n        await new Response(readables[0]).text(),\n        'foo, bar, baz!'\n      );\n      assert.strictEqual(\n        await new Response(readables[1]).text(),\n        'foo, bar, baz!'\n      );\n    }\n\n    // Send ReadableStream, but fail to fully write it.\n    {\n      let { readable, writable } = new IdentityTransformStream();\n      let promise = env.MyService.readFromStream(readable);\n\n      let writer = writable.getWriter();\n      let enc = new TextEncoder();\n      await writer.write(enc.encode('foo, '));\n      await writer.write(enc.encode('bar, '));\n      await writer.write(enc.encode('baz!'));\n      await writer.abort('foo');\n\n      await assert.rejects(promise, {\n        name: 'Error',\n        // TODO(someday): Propagate the actual error.\n        message: 'ReadableStream received over RPC disconnected prematurely.',\n      });\n    }\n\n    // Send fixed-length ReadableStream.\n    {\n      let { readable, writable } = new FixedLengthStream(\n        'foo, bar, baz!'.length\n      );\n      let promise = env.MyService.readFromStream(readable);\n\n      let writer = writable.getWriter();\n      let enc = new TextEncoder();\n      await writer.write(enc.encode('foo, '));\n      await writer.write(enc.encode('bar, '));\n      await writer.write(enc.encode('baz!'));\n      await writer.close();\n\n      assert.strictEqual(await promise, 'foo, bar, baz!');\n    }\n\n    // Send an encoded ReadableStream\n    {\n      let gzippedResp = await env.self.fetch('http://foo/gzip');\n\n      let text = await env.MyService.readFromStream(gzippedResp.body);\n\n      assert.strictEqual(text, 'this text was gzipped');\n    }\n\n    // Round trip streams.\n    {\n      let { readable, writable } = new IdentityTransformStream();\n\n      readable = await env.MyService.roundTrip(readable);\n      writable = await env.MyService.roundTrip(writable);\n\n      let readPromise = new Response(readable).text();\n\n      let writer = writable.getWriter();\n      let enc = new TextEncoder();\n      await writer.write(enc.encode('foo, '));\n      await writer.write(enc.encode('bar, '));\n      await writer.write(enc.encode('baz!'));\n      await writer.close();\n\n      assert.strictEqual(await readPromise, 'foo, bar, baz!');\n    }\n\n    // Perform an HTTP request whose response uses a ReadableStream obtained over RPC.\n    {\n      let resp = await env.self.fetch('http://foo/stream-from-rpc');\n\n      assert.strictEqual(await resp.text(), 'foo, bar, baz!');\n    }\n  },\n};\n\nexport let serializeHttpTypes = {\n  async test(controller, env, ctx) {\n    {\n      let headers = await env.MyService.returnEmptyHeaders();\n      assert.deepEqual([...headers], []);\n    }\n\n    {\n      let headers = await env.MyService.returnHeaders();\n      assert.strictEqual(headers instanceof Headers, true);\n\n      // Awkwardly, there's actually no API to get the non-lowercased header names.\n      assert.deepEqual(\n        [...headers],\n        [\n          ['content-length', '123'],\n          ['corge', '!@#'],\n          ['foo', 'bar'],\n          ['set-cookie', 'abc'],\n          ['set-cookie', 'def'],\n        ]\n      );\n\n      assert.deepEqual(headers.getSetCookie(), ['abc', 'def']);\n    }\n\n    {\n      let req = await env.MyService.returnRequest();\n\n      assert.strictEqual(req.url, 'http://my-url.com');\n      assert.strictEqual(req.method, 'PUT');\n      assert.strictEqual(req.headers.get('Accept-Encoding'), 'bazzip');\n      assert.strictEqual(req.headers.get('Foo'), 'Bar');\n      assert.strictEqual(req.redirect, 'manual');\n\n      assert.strictEqual(await req.text(), 'Hello every body!');\n\n      assert.deepEqual(req.cf, {\n        abc: 123,\n        hello: 'goodbye',\n      });\n    }\n\n    {\n      let req = await env.MyService.roundTrip(\n        new Request('http://foo', { signal: AbortSignal.timeout(100) })\n      );\n      assert.strictEqual(req.url, 'http://foo');\n      assert.ok(req.signal instanceof AbortSignal);\n    }\n\n    {\n      let req = await env.MyService.returnResponse();\n\n      assert.strictEqual(req.status, 404);\n      assert.strictEqual(req.statusText, 'Not Found');\n      assert.strictEqual(req.headers.get('Content-Type'), 'abc');\n      assert.deepEqual(req.cf, { foo: 123, bar: 'def' });\n\n      assert.strictEqual(await req.text(), 'Response body!');\n    }\n  },\n};\n\n// Test that exceptions thrown from async native functions have a proper stack trace. (This is\n// not specific to RPC but RPC is a convenient place to test it since we can easily define the\n// callee to throw an exception.)\n//\n// Note that it's only a *local* stack trace of the client-side stack leading up to the call. The\n// stack on the server side is not, at present, transmitted to the client.\nexport let testAsyncStackTrace = {\n  async test(controller, env, ctx) {\n    try {\n      await env.MyService.throwingMethod();\n    } catch (e) {\n      // check that the custom property made it through\n      assert.strictEqual(e.abc, 123);\n      // verify a local stack trace was produced\n      assert.strictEqual(e.stack.includes('at async Object.test'), true);\n    }\n  },\n};\n\n// Test that exceptions thrown over RPC have the .remote property.\nexport let testExceptionProperties = {\n  async test(controller, env, ctx) {\n    try {\n      await env.MyService.throwingMethod();\n    } catch (e) {\n      assert.strictEqual(e.abc, 123);\n      assert.strictEqual(e.remote, true);\n      assert.strictEqual(e.message, 'METHOD THREW');\n    }\n  },\n};\n\n// Test that get(), put(), and delete() are valid RPC method names, not hijacked by Fetcher.\nexport let canUseGetPutDelete = {\n  async test(controller, env, ctx) {\n    assert.strictEqual(await env.MyService.get(12), 13);\n    assert.strictEqual(await env.MyService.put(5, 7), 12);\n    assert.strictEqual(await env.MyService.delete(3), 2);\n  },\n};\n\n// Test that stubs can still be used after logging them.\nexport let logging = {\n  async test(controller, env, ctx) {\n    let counter = new MyCounter(0);\n    let stub = new RpcStub(counter);\n    assert.strictEqual(await stub.increment(1), 1);\n    assert.strictEqual(await stub.increment(1), 2);\n    console.log(stub);\n    assert.strictEqual(await stub.increment(1), 3);\n  },\n};\n\nexport let proxiedRpcTarget = {\n  async test(controller, env, ctx) {\n    // Proxy RPC target.\n    {\n      let counter = new MyCounter(0);\n      let proxy = new Proxy(counter, {\n        get(target, prop, receiver) {\n          if (prop == 'increment') {\n            return (i) => target.increment(i + 123);\n          } else {\n            let result = target[prop];\n            if (result instanceof Function) {\n              result = result.bind(target);\n            }\n            return result;\n          }\n        },\n      });\n\n      await env.MyService.incrementCounter(proxy, 1);\n\n      assert.strictEqual(counter.i, 124);\n    }\n\n    // Proxy plain object.\n    {\n      let counter = {\n        i: 0,\n        increment(i) {\n          this.i += i;\n        },\n      };\n\n      let proxy = new Proxy(counter, {\n        get(target, prop, receiver) {\n          if (prop == 'increment') {\n            return (i) => target.increment(i + 123);\n          } else {\n            let result = target[prop];\n            if (result instanceof Function) {\n              result = result.bind(target);\n            }\n            return result;\n          }\n        },\n      });\n\n      await env.MyService.incrementCounter(proxy, 1);\n\n      assert.strictEqual(counter.i, 124);\n    }\n\n    // Proxy function.\n    {\n      let func = (i) => {\n        return i * 3;\n      };\n      func.ownProp = 123;\n      let proxy = new Proxy(func, {\n        apply(target, thisArg, argumentsList) {\n          return target(...argumentsList) + 2;\n        },\n      });\n\n      assert.strictEqual(await env.MyService.call(proxy, 2), 8);\n      assert.strictEqual(await env.MyService.getProp(proxy, 'ownProp'), 123);\n\n      // Try pipelining.\n      assert.strictEqual(await env.MyService.identity(() => proxy)()(4), 14);\n      assert.strictEqual(\n        await env.MyService.identity(() => proxy)().ownProp,\n        123\n      );\n\n      assert.strictEqual(\n        await env.MyService.identity(() => ({ x: proxy }))().x(4),\n        14\n      );\n      assert.strictEqual(\n        await env.MyService.identity(() => ({ x: proxy }))().x.ownProp,\n        123\n      );\n    }\n\n    // Proxy RPC target that is callable.\n    {\n      let counter = new MyCounter(0);\n\n      // We make the proxy target be a function so that it is callable, but we implement\n      // getPrototypeOf() to make it appear to implement RpcTarget.\n      let func = (i) => i * 11;\n      func.ownProp = 123;\n      let proxy = new Proxy(func, {\n        get(target, prop, receiver) {\n          if (prop == 'increment') {\n            return (i) => counter.increment(i + 123);\n          } else if (prop == 'ownProp') {\n            return target.ownProp;\n          } else {\n            let result = counter[prop];\n            if (result instanceof Function) {\n              result = result.bind(counter);\n            }\n            return result;\n          }\n        },\n        has(target, prop) {\n          return prop in counter || prop === 'ownProp';\n        },\n        getPrototypeOf(target) {\n          return Object.getPrototypeOf(counter);\n        },\n      });\n\n      // We can call it.\n      assert.strictEqual(await env.MyService.call(proxy, 3), 33);\n\n      // We *cannot* access own properties of the function.\n      assert.rejects(() => env.MyService.getProp(proxy, 'ownProp'), {\n        name: 'TypeError',\n        message: 'The RPC receiver does not implement the method \"ownProp\".',\n      });\n\n      // We *can* access prototype properties, becaues it's an RpcTarget.\n      await env.MyService.incrementCounter(proxy, 1);\n      assert.strictEqual(counter.i, 124);\n\n      // Try pipelined calls.\n      assert.strictEqual(\n        await env.MyService.identity(() => proxy)().increment(3),\n        250\n      );\n      assert.strictEqual(\n        await env.MyService.identity(() => ({ p: proxy }))().p.increment(4),\n        377\n      );\n    }\n\n    // Can't proxy a class that doesn't extend `RpcTarget`.\n    {\n      let nonRpc = new NonRpcClass();\n      let proxy = new Proxy(nonRpc, {\n        get(target, prop, receiver) {\n          if (prop == 'increment') {\n            return (i) => target.increment(i + 123);\n          } else {\n            let result = target[prop];\n            if (result instanceof Function) {\n              result = result.bind(target);\n            }\n            return result;\n          }\n        },\n      });\n\n      await assert.rejects(() => env.MyService.incrementCounter(proxy, 1), {\n        name: 'DataCloneError',\n        message:\n          'Proxy could not be serialized because it is not a valid RPC receiver type. The ' +\n          'Proxy must emulate either a plain object or an RpcTarget, as indicated by the ' +\n          \"Proxy's prototype chain.\",\n      });\n    }\n\n    // Can't proxy an RpcTarget if we've overridden the prototype to say it's something else.\n    {\n      let counter = new MyCounter(0);\n      let proxy = new Proxy(counter, {\n        getPrototypeOf(target) {\n          return NonRpcClass.prototype;\n        },\n      });\n\n      await assert.rejects(() => env.MyService.incrementCounter(proxy, 1), {\n        name: 'DataCloneError',\n        message:\n          'Proxy could not be serialized because it is not a valid RPC receiver type. The ' +\n          'Proxy must emulate either a plain object or an RpcTarget, as indicated by the ' +\n          \"Proxy's prototype chain.\",\n      });\n    }\n\n    // CAN proxy a class that doesn't extend `RpcTarget` if we fake the prototype.\n    {\n      let nonRpc = new NonRpcClass();\n      let proxy = new Proxy(nonRpc, {\n        getPrototypeOf(target) {\n          return RpcTarget.prototype;\n        },\n      });\n\n      await env.MyService.incrementCounter(proxy, 321);\n      assert.strictEqual(nonRpc.i, 321);\n    }\n  },\n};\n\n// Test that we can construct a WorkerEntrypoint\nexport class MyEntrypoint extends WorkerEntrypoint {\n  rpcFunc() {\n    return 'hello from entrypoint';\n  }\n}\nfunction constructEntrypoint(cls, env) {\n  return new cls({ waitUntil: () => {} }, env);\n}\nexport let testConstructEntrypoint = {\n  async test(controller, env, ctx) {\n    const constructed = constructEntrypoint(MyEntrypoint, env);\n    assert.strictEqual(await constructed.rpcFunc(), 'hello from entrypoint');\n  },\n};\n\n// Test that calls to an RpcTarget made after the context is aborted don't get delivered.\nexport let portAbortCall = {\n  async test(controller, env, ctx) {\n    {\n      let id = env.MyActor.newUniqueId();\n      let actor = env.MyActor.get(id);\n      let stub = await actor.makePostAbortCallTester();\n\n      let hangPromise = stub.hang();\n      assert.strictEqual(await stub.ping(), 'pong');\n      let abortPromise = stub.abort();\n      let pingPromise = stub.ping();\n\n      await assert.rejects(abortPromise, {\n        name: 'Error',\n        message: 'test aborted by abort()',\n      });\n      await assert.rejects(pingPromise, {\n        name: 'Error',\n        message: 'test aborted by abort()',\n      });\n      await assert.rejects(hangPromise, {\n        name: 'Error',\n        message: 'test aborted by abort()',\n      });\n      // TODO(bug): This should propagate the abort reason.\n      await assert.rejects(stub.ping(), {\n        name: 'Error',\n        message:\n          'The execution context which hosts this callback is no longer running.',\n      });\n      await assert.rejects(actor.increment(2), {\n        name: 'Error',\n        message: 'test aborted by abort()',\n      });\n    }\n\n    // Start over with a new stub, this time use failCriticalSection() to break the actor. As of\n    // this writing, this differs significantly from plain `abort()` in that\n    // `IoContext::abortException` never gets set, since `IoContext::abort()` is not directly\n    // called, but instead the exception is joined into the on-abort promise.\n    {\n      let id = env.MyActor.newUniqueId();\n      let actor = env.MyActor.get(id);\n      let stub = await actor.makePostAbortCallTester();\n\n      let hangPromise = stub.hang();\n      assert.strictEqual(await stub.ping(), 'pong');\n      let failPromise = stub.failCriticalSection();\n      let pingPromise = stub.ping();\n\n      await assert.rejects(failPromise, {\n        name: 'Error',\n        message: 'test broken critical section',\n      });\n      await assert.rejects(pingPromise, {\n        name: 'Error',\n        message: 'test broken critical section',\n      });\n      await assert.rejects(hangPromise, {\n        name: 'Error',\n        message: 'test broken critical section',\n      });\n      // TODO(bug): This should propagate the abort reason.\n      await assert.rejects(stub.ping(), {\n        name: 'Error',\n        message:\n          'The execution context which hosts this callback is no longer running.',\n      });\n      await assert.rejects(actor.increment(2), {\n        name: 'Error',\n        message: 'test broken critical section',\n      });\n    }\n  },\n};\n\nexport class Greeter extends WorkerEntrypoint {\n  async greet(name) {\n    return `${this.ctx.props.greeting}, ${name}!`;\n  }\n}\n\nexport class GreeterFactory extends WorkerEntrypoint {\n  async makeGreeter(greeting) {\n    return this.ctx.exports.Greeter({ props: { greeting } });\n  }\n  async makeGreeterWrapped(greeting) {\n    return { greeter: this.ctx.exports.Greeter({ props: { greeting } }) };\n  }\n}\n\nexport let sendServiceStubOverRpc = {\n  async test(controller, env, ctx) {\n    {\n      let greeter = await env.GreeterFactory.makeGreeter('Yo');\n      assert.strictEqual(await greeter.greet('Alice'), 'Yo, Alice!');\n    }\n\n    // Test that we can pipeline on service stubs.\n    {\n      let greeter = env.GreeterFactory.makeGreeter('Yo');\n      assert.strictEqual(await greeter.greet('Alice'), 'Yo, Alice!');\n    }\n\n    // Pipelining works a little differently when the service stub is returned as the top-level\n    // value vs. an inner value, so test an inner value too.\n    {\n      let greeter = env.GreeterFactory.makeGreeterWrapped('Yo').greeter;\n      assert.strictEqual(await greeter.greet('Alice'), 'Yo, Alice!');\n    }\n  },\n};\n\n// Make sure that calls are delivered in e-order, even in the presence of pushed externals.\nexport let eOrderTest = {\n  async test(controller, env, ctx) {\n    let abortController = new AbortController();\n    let abortSignal = abortController.signal;\n\n    let readableController;\n    let readableStream = new ReadableStream({\n      start(c) {\n        readableController = c;\n      },\n    });\n\n    let stub = await env.MyService.makeCounter(0);\n\n    let promises = [];\n    promises.push(stub.increment(1));\n    promises.push(stub.increment(1));\n    promises.push(stub.increment(1, abortSignal));\n    promises.push(stub.increment(1));\n    promises.push(stub.increment(1, readableStream));\n    promises.push(stub.increment(1));\n\n    let results = await Promise.all(promises);\n\n    assert.deepEqual(results, [1, 2, 3, 4, 5, 6]);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/js-rpc-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"js-rpc-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"js-rpc-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"fetcher_no_get_put_delete\",\n          \"enable_abortsignal_rpc\",\n          \"enhanced_error_serialization\",\n          \"enable_ctx_exports\",\n          \"experimental\",\n          \"streams_enable_constructors\",\n          \"http_headers_getsetcookie\",\n          \"fetch_legacy_url\",\n          \"rpc_params_transfer_stubs\",\n        ],\n        bindings = [\n          (name = \"self\", service = (name = \"js-rpc-test\", entrypoint = \"nonClass\")),\n          (name = \"MyService\", service = (name = \"js-rpc-test\", entrypoint = \"MyService\")),\n          (name = \"MyServiceProxy\", service = (name = \"js-rpc-test\", entrypoint = \"MyServiceProxy\")),\n          (name = \"MyActor\", durableObjectNamespace = \"MyActor\"),\n          (name = \"ActorNoExtends\", durableObjectNamespace = \"ActorNoExtends\"),\n          (name = \"defaultExport\", service = \"js-rpc-test\"),\n          (name = \"twelve\", json = \"12\"),\n          (name = \"GreeterFactory\", service = (name = \"js-rpc-test\", entrypoint = \"GreeterFactory\")),\n        ],\n\n        durableObjectNamespaces = [\n          (className = \"MyActor\", uniqueKey = \"foo\"),\n          (className = \"ActorNoExtends\", uniqueKey = \"bar\"),\n        ],\n\n        durableObjectStorage = (inMemory = void),\n      )\n    ),\n  ],\n  v8Flags = [ \"--expose-gc\" ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/jsrpc-timing-test-tail.js",
    "content": "// Copyright (c) 2017-2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Streaming tail worker that captures timing information for JSRPC invocations.\n// This validates that Return events occur at the expected time.\n//\n// Expected timeline:\n//   T=0:      Onset (invocation starts)\n//   T=~500ms: Return (handler returns the stream)\n//   T=~950ms: Outcome (stream fully consumed)\n\nimport * as assert from 'node:assert';\n\n// Store events by invocation ID so we can analyze timing per-invocation\nlet invocationEvents = new Map();\n\nexport default {\n  tailStream(onsetEvent, env, ctx) {\n    const invocationId = onsetEvent.invocationId;\n    const baseTimestamp = onsetEvent.timestamp.getTime();\n\n    // Initialize event list for this invocation\n    const events = [\n      {\n        type: onsetEvent.event.type,\n        relativeTimeMs: 0,\n        info: onsetEvent.event.info?.type,\n        entrypoint: onsetEvent.event.entrypoint,\n      },\n    ];\n    invocationEvents.set(invocationId, { baseTimestamp, events });\n\n    return (event) => {\n      const relativeTimeMs = event.timestamp.getTime() - baseTimestamp;\n\n      const eventRecord = {\n        type: event.event.type,\n        relativeTimeMs,\n      };\n\n      // Capture additional info for specific event types\n      if (event.event.type === 'return') {\n        eventRecord.returnInfo = event.event.info;\n      } else if (event.event.type === 'outcome') {\n        eventRecord.outcome = event.event.outcome;\n        eventRecord.cpuTime = event.event.cpuTime;\n        eventRecord.wallTime = event.event.wallTime;\n      } else if (event.event.type === 'attributes') {\n        eventRecord.attributes = event.event.info;\n      }\n\n      events.push(eventRecord);\n    };\n  },\n};\n\nexport const test = {\n  async test() {\n    // Wait briefly for all tail events to be processed\n    await scheduler.wait(200);\n\n    // Find the StreamingService invocation (the JSRPC call)\n    let streamingServiceEvents = null;\n    for (const [invocationId, data] of invocationEvents.entries()) {\n      const onset = data.events[0];\n      if (onset.entrypoint === 'StreamingService' && onset.info === 'jsrpc') {\n        streamingServiceEvents = data.events;\n        break;\n      }\n    }\n\n    if (!streamingServiceEvents) {\n      console.log('Captured invocations:');\n      for (const [invocationId, data] of invocationEvents.entries()) {\n        console.log(`  ${invocationId}: ${JSON.stringify(data.events[0])}`);\n      }\n      throw new Error(\n        'Could not find StreamingService JSRPC invocation events'\n      );\n    }\n\n    console.log(\n      'StreamingService events:',\n      JSON.stringify(streamingServiceEvents, null, 2)\n    );\n\n    // Find the key events\n    const onset = streamingServiceEvents.find((e) => e.type === 'onset');\n    const returnEvent = streamingServiceEvents.find((e) => e.type === 'return');\n    const outcome = streamingServiceEvents.find((e) => e.type === 'outcome');\n\n    // Validate all events are present\n    assert.ok(onset, 'Missing onset event');\n    assert.ok(outcome, 'Missing outcome event');\n\n    if (!returnEvent) {\n      console.log('WARNING: No return event found for JSRPC invocation!');\n      console.log(\n        'This may indicate setReturn() is not being called for JSRPC.'\n      );\n      throw new Error('Return event not captured for JSRPC invocation');\n    }\n\n    const timeToReturn = returnEvent.relativeTimeMs;\n    const timeToOutcome = outcome.relativeTimeMs;\n    const gap = timeToOutcome - timeToReturn;\n\n    console.log(`timeToReturn: ${timeToReturn}ms`);\n    console.log(`timeToOutcome: ${timeToOutcome}ms`);\n    console.log(`Gap between Return and Outcome: ${gap}ms`);\n\n    // Key validation: Return must happen BEFORE Outcome\n    if (timeToReturn >= timeToOutcome) {\n      throw new Error(\n        `Return event (${timeToReturn}ms) should happen before Outcome (${timeToOutcome}ms). ` +\n          `This indicates Return is fired when stream ends, not when handler returns.`\n      );\n    }\n\n    // Return should happen at least 50ms after onset (handler sleeps for 500ms)\n    // NOTE: Using a lower threshold (50ms instead of 200ms) because Windows has\n    // significant timer jitter and scheduler.wait() timing assumptions don't hold\n    // reliably under load.\n    if (timeToReturn < 50) {\n      throw new Error(\n        `Return event (${timeToReturn}ms) happened too quickly. ` +\n          `Expected at least 50ms from onset (handler sleeps for 500ms before returning).`\n      );\n    }\n\n    // Return should be significantly before Outcome (at least 200ms gap for stream drain)\n    if (gap < 200) {\n      throw new Error(\n        `Gap between Return (${timeToReturn}ms) and Outcome (${timeToOutcome}ms) is only ${gap}ms. ` +\n          `Expected at least 200ms for stream consumption.`\n      );\n    }\n\n    console.log(`PASS: Return event occurs ${gap}ms before Outcome`);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/jsrpc-timing-test.js",
    "content": "// Copyright (c) 2017-2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Test to validate timing semantics for JSRPC invocations with streaming responses.\n// This test verifies that the Return event is emitted at the correct time relative\n// to Onset and Outcome events.\n//\n// Expected timeline:\n//   T=0:      Onset (invocation starts)\n//   T=~500ms: Return (handler returns the stream)\n//   T=~950ms: Outcome (stream fully consumed)\n\nimport { WorkerEntrypoint } from 'cloudflare:workers';\n\nexport class StreamingService extends WorkerEntrypoint {\n  async getStreamWithDelays() {\n    // Sleep 500ms before returning the stream\n    await scheduler.wait(500);\n\n    // Return a ReadableStream that takes ~450ms to drain\n    const encoder = new TextEncoder();\n    let chunksSent = 0;\n\n    const stream = new ReadableStream({\n      async pull(controller) {\n        if (chunksSent >= 3) {\n          controller.close();\n          return;\n        }\n        // Sleep 150ms between chunks (3 chunks = ~450ms to drain)\n        await scheduler.wait(150);\n        controller.enqueue(encoder.encode(`chunk${chunksSent++}\\n`));\n      },\n    });\n\n    return stream;\n  }\n}\n\nexport default {\n  async test(controller, env, ctx) {\n    // Call the streaming RPC method\n    const stream = await env.StreamingService.getStreamWithDelays();\n\n    // Consume the stream fully\n    const reader = stream.getReader();\n    let chunks = [];\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      chunks.push(new TextDecoder().decode(value));\n    }\n\n    // Verify we got all chunks\n    if (chunks.length !== 3) {\n      throw new Error(`Expected 3 chunks, got ${chunks.length}`);\n    }\n\n    // The actual timing validation is done in the tail worker's test() handler\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/jsrpc-timing-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\n# Test to validate timing semantics for JSRPC invocations with streaming responses.\n# This test verifies that the Return event is emitted when the handler returns,\n# NOT when the stream is fully consumed.\n#\n# Expected timeline:\n#   T=0:      Onset event (invocation starts)\n#   T=~500ms: Return event (handler returns ReadableStream after 500ms sleep)\n#   T=~950ms: Outcome event (stream fully consumed after ~450ms more)\n#\n# If Return event occurs at T=~950ms instead of T=~500ms, this indicates a bug\n# where setReturn() is called when capabilities are released rather than\n# when the handler actually returns.\n\nconst unitTests :Workerd.Config = (\n  services = [\n    (name = \"jsrpc-timing-test\", worker = .mainWorker),\n    (name = \"tail\", worker = .tailWorker),\n  ],\n);\n\nconst mainWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"jsrpc-timing-test.js\")\n  ],\n  compatibilityFlags = [\"nodejs_compat\", \"experimental\", \"precise_timers\", \"rpc\", \"streams_enable_constructors\"],\n  bindings = [\n    (name = \"StreamingService\", service = (name = \"jsrpc-timing-test\", entrypoint = \"StreamingService\")),\n  ],\n  streamingTails = [\"tail\"],\n);\n\nconst tailWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"jsrpc-timing-test-tail.js\")\n  ],\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\", \"precise_timers\"],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/kv-instrumentation-test.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport * as assert from 'node:assert';\nimport {\n  invocationPromises,\n  spans,\n  testTailHandler,\n} from 'test:instrumentation-tail';\n\n// Use shared instrumentation test tail worker\nexport default testTailHandler;\n\nexport const test = {\n  async test() {\n    // Wait for all the tailStream executions to finish\n    await Promise.allSettled(invocationPromises);\n\n    // Recorded streaming tail worker events, in insertion order, filtering spans not associated\n    // with KV, including jsRpc calls\n    let received = Array.from(spans.values()).filter((span) =>\n      span.name.match('kv')\n    );\n\n    // spans emitted by kv-test.js in execution order\n    let expected = [\n      {\n        name: 'kv_get',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'key1, key\"2',\n        'cloudflare.kv.query.keys.count': 2n,\n        'cloudflare.kv.response.size': 85n,\n        'cloudflare.kv.response.returned_rows': 2n,\n        closed: true,\n      },\n      {\n        name: 'kv_get',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'key1, key2',\n        'cloudflare.kv.query.keys.count': 2n,\n        'cloudflare.kv.response.size': 79n,\n        'cloudflare.kv.response.returned_rows': 2n,\n        closed: true,\n      },\n      {\n        name: 'kv_get',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys':\n          'key0, key1, key2, key3, key4, key5, key6, key7, key8, key9, key10, key11, key12, key13, key14, key15, key16, key17, key18, key19, key20, key21, key22, key23, key24, key25, key26, key27, key28, key29, key30, key31, key32, key33, key34, key35, key36, key37, key38, key39, key40, key41, key42, key43, key44, key45, key46, key47, key48, key49, key50, key51, key52, key53, key54, key55, key56, key57, key58, key59, key60, key61, key62, key63, key64, key65, key66, key67, key68, key69, key70, key71, key72, key73, k...',\n        'cloudflare.kv.query.keys.count': 100n,\n        'cloudflare.kv.response.size': 4081n,\n        'cloudflare.kv.response.returned_rows': 100n,\n        closed: true,\n      },\n      {\n        name: 'kv_get',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys':\n          'key0, key1, key2, key3, key4, key5, key6, key7, key8, key9, key10, key11, key12, key13, key14, key15, key16, key17, key18, key19, key20, key21, key22, key23, key24, key25, key26, key27, key28, key29, key30, key31, key32, key33, key34, key35, key36, key37, key38, key39, key40, key41, key42, key43, key44, key45, key46, key47, key48, key49, key50, key51, key52, key53, key54, key55, key56, key57, key58, key59, key60, key61, key62, key63, key64, key65, key66, key67, key68, key69, key70, key71, key72, key73, k...',\n        'cloudflare.kv.query.keys.count': 101n,\n        closed: true,\n      },\n      {\n        name: 'kv_get',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'key1, not-found',\n        'cloudflare.kv.query.keys.count': 2n,\n        'cloudflare.kv.query.cache_ttl': 100n,\n        'cloudflare.kv.response.size': 57n,\n        'cloudflare.kv.response.returned_rows': 2n,\n        closed: true,\n      },\n      {\n        name: 'kv_get',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': '',\n        'cloudflare.kv.query.keys.count': 0n,\n        closed: true,\n      },\n      {\n        name: 'kv_get',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'key1, key2',\n        'cloudflare.kv.query.keys.count': 2n,\n        'cloudflare.kv.query.type': 'json',\n        'cloudflare.kv.response.size': 67n,\n        'cloudflare.kv.response.returned_rows': 2n,\n        closed: true,\n      },\n      {\n        name: 'kv_get',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'key-not-json, key2',\n        'cloudflare.kv.query.keys.count': 2n,\n        'cloudflare.kv.query.type': 'json',\n        closed: true,\n      },\n      {\n        name: 'kv_get',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'key-not-json, key2',\n        'cloudflare.kv.query.keys.count': 2n,\n        'cloudflare.kv.query.type': 'arrayBuffer',\n        closed: true,\n      },\n      {\n        name: 'kv_get',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'key-not-json, key2',\n        'cloudflare.kv.query.keys.count': 2n,\n        'cloudflare.kv.query.type': 'banana',\n        closed: true,\n      },\n      {\n        name: 'kv_getWithMetadata',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'key1',\n        'cloudflare.kv.query.keys.count': 1n,\n        'cloudflare.kv.response.cache_status': 'HIT',\n        'cloudflare.kv.response.metadata': true,\n        'cloudflare.kv.response.size': 10n,\n        'cloudflare.kv.response.returned_rows': 1n,\n        closed: true,\n      },\n      {\n        name: 'kv_getWithMetadata',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'key1',\n        'cloudflare.kv.query.keys.count': 1n,\n        'cloudflare.kv.response.size': 80n,\n        'cloudflare.kv.response.returned_rows': 1n,\n        closed: true,\n      },\n      {\n        name: 'kv_getWithMetadata',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'key1',\n        'cloudflare.kv.query.keys.count': 1n,\n        'cloudflare.kv.query.type': 'json',\n        'cloudflare.kv.response.size': 74n,\n        'cloudflare.kv.response.returned_rows': 1n,\n        closed: true,\n      },\n      {\n        name: 'kv_getWithMetadata',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'key1, key2',\n        'cloudflare.kv.query.keys.count': 2n,\n        'cloudflare.kv.query.type': 'json',\n        'cloudflare.kv.response.size': 147n,\n        'cloudflare.kv.response.returned_rows': 2n,\n        closed: true,\n      },\n      {\n        name: 'kv_get',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'success',\n        'cloudflare.kv.query.keys.count': 1n,\n        'cloudflare.kv.response.cache_status': 'HIT',\n        'cloudflare.kv.response.metadata': true,\n        'cloudflare.kv.response.size': 13n,\n        'cloudflare.kv.response.returned_rows': 1n,\n        closed: true,\n      },\n      {\n        name: 'kv_get',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'fail-client',\n        'cloudflare.kv.query.keys.count': 1n,\n        closed: true,\n      },\n      {\n        name: 'kv_get',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'fail-server',\n        'cloudflare.kv.query.keys.count': 1n,\n        closed: true,\n      },\n      {\n        name: 'kv_get',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'get-json',\n        'cloudflare.kv.query.keys.count': 1n,\n        'cloudflare.kv.response.cache_status': 'HIT',\n        'cloudflare.kv.response.metadata': true,\n        'cloudflare.kv.response.size': 20n,\n        'cloudflare.kv.response.returned_rows': 1n,\n        closed: true,\n      },\n      {\n        name: 'kv_get',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'get-json',\n        'cloudflare.kv.query.keys.count': 1n,\n        'cloudflare.kv.query.type': 'json',\n        'cloudflare.kv.response.cache_status': 'HIT',\n        'cloudflare.kv.response.metadata': true,\n        'cloudflare.kv.response.size': 20n,\n        'cloudflare.kv.response.returned_rows': 1n,\n        closed: true,\n      },\n      {\n        name: 'kv_get',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'success',\n        'cloudflare.kv.query.keys.count': 1n,\n        'cloudflare.kv.query.type': 'stream',\n        'cloudflare.kv.response.cache_status': 'HIT',\n        'cloudflare.kv.response.metadata': true,\n        'cloudflare.kv.response.size': 13n,\n        'cloudflare.kv.response.returned_rows': 1n,\n        closed: true,\n      },\n      {\n        name: 'kv_get',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'get',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'success',\n        'cloudflare.kv.query.keys.count': 1n,\n        'cloudflare.kv.query.type': 'arrayBuffer',\n        'cloudflare.kv.response.cache_status': 'HIT',\n        'cloudflare.kv.response.metadata': true,\n        'cloudflare.kv.response.size': 13n,\n        'cloudflare.kv.response.returned_rows': 1n,\n        closed: true,\n      },\n      {\n        name: 'kv_list',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'list',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.prefix': 'te',\n        'cloudflare.kv.response.cache_status': 'HIT',\n        'cloudflare.kv.response.size': 291n,\n        'cloudflare.kv.response.list_complete': false,\n        'cloudflare.kv.response.cursor': '6Ck1la0VxJ0djhidm1MdX2FyD',\n        'cloudflare.kv.response.expiration': 1234n,\n        'cloudflare.kv.response.returned_rows': 3n,\n        closed: true,\n      },\n      {\n        name: 'kv_list',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'list',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.limit': 100n,\n        'cloudflare.kv.query.prefix': 'te',\n        'cloudflare.kv.query.cursor': '123',\n        'cloudflare.kv.response.cache_status': 'HIT',\n        'cloudflare.kv.response.size': 6939n,\n        'cloudflare.kv.response.list_complete': true,\n        'cloudflare.kv.response.expiration': 1234n,\n        'cloudflare.kv.response.returned_rows': 100n,\n        closed: true,\n      },\n      {\n        name: 'kv_list',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'list',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.prefix': 'not-found',\n        'cloudflare.kv.response.cache_status': 'HIT',\n        'cloudflare.kv.response.size': 32n,\n        'cloudflare.kv.response.list_complete': true,\n        'cloudflare.kv.response.returned_rows': 0n,\n        closed: true,\n      },\n      {\n        name: 'kv_put',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'put',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'foo_with_exp',\n        'cloudflare.kv.query.keys.count': 1n,\n        'cloudflare.kv.query.expiration': 10n,\n        'cloudflare.kv.query.value_type': 'text',\n        'cloudflare.kv.query.payload.size': 4n,\n        closed: true,\n      },\n      {\n        name: 'kv_put',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'put',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'foo_with_expTtl',\n        'cloudflare.kv.query.keys.count': 1n,\n        'cloudflare.kv.query.expiration_ttl': 15n,\n        'cloudflare.kv.query.value_type': 'text',\n        'cloudflare.kv.query.payload.size': 23n,\n        closed: true,\n      },\n      {\n        name: 'kv_put',\n        'db.system.name': 'cloudflare-kv',\n        'db.operation.name': 'put',\n        'cloudflare.binding.name': 'KV',\n        'cloudflare.binding.type': 'KV',\n        'cloudflare.kv.query.keys': 'foo_with_expTtl',\n        'cloudflare.kv.query.keys.count': 1n,\n        'cloudflare.kv.query.expiration_ttl': 15n,\n        'cloudflare.kv.query.value_type': 'ArrayBuffer',\n        'cloudflare.kv.query.payload.size': 256n,\n        closed: true,\n      },\n    ];\n\n    assert.equal(\n      received.length,\n      expected.length,\n      `Expected ${expected.length} received ${received.length} spans`\n    );\n    let errors = [];\n    for (let i = 0; i < received.length; i++) {\n      try {\n        assert.deepStrictEqual(received[i], expected[i]);\n      } catch (e) {\n        console.error(`value: ${i} does not match`);\n        console.log(e);\n        errors.push(e);\n      }\n    }\n    if (errors.length > 0) {\n      throw 'kv spans are incorrect';\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/kv-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'node:assert';\nimport { WorkerEntrypoint } from 'cloudflare:workers';\nexport default class KVTest extends WorkerEntrypoint {\n  // Request handler (from `env.NAMESPACE`)\n  async fetch(request, env, ctx) {\n    let result = 'example';\n    const { pathname, searchParams } = new URL(request.url);\n    const queryParameters = Object.fromEntries(searchParams);\n    const headers = new Headers();\n    headers.set('CF-Cache-Status', 'HIT');\n\n    if (pathname === '/fail-client') {\n      return new Response(null, { status: 404 });\n    } else if (pathname == '/fail-server') {\n      return new Response(null, { status: 500 });\n    } else if (pathname == '/get-json') {\n      result = JSON.stringify({ example: 'values' });\n    } else if (pathname == '/bulk/get') {\n      let r = '';\n      const decoder = new TextDecoder();\n      for await (const chunk of request.body) {\n        r += decoder.decode(chunk, { stream: true });\n      }\n      r += decoder.decode();\n      const parsedBody = JSON.parse(r);\n      const keys = parsedBody.keys;\n      if (keys.length < 1 || keys.length > 100) {\n        return new Response(null, { status: 400 });\n      }\n      result = {};\n      if (parsedBody.type == 'json') {\n        for (const key of keys) {\n          if (key == 'key-not-json') {\n            return new Response(null, { status: 500 });\n          }\n          const val = { example: `values-${key}` };\n          if (parsedBody.withMetadata) {\n            result[key] = { value: val, metadata: 'example-metadata' };\n          } else {\n            result[key] = val;\n          }\n        }\n      } else if (!parsedBody.type || parsedBody.type == 'text') {\n        for (const key of keys) {\n          const val = JSON.stringify({ example: `values-${key}` });\n          if (key == 'not-found') {\n            result[key] = null;\n          } else if (parsedBody.withMetadata) {\n            result[key] = { value: val, metadata: 'example-metadata' };\n          } else {\n            result[key] = val;\n          }\n        }\n      } else {\n        // invalid type requested\n        return new Response(null, { status: 500 });\n      }\n      result = JSON.stringify(result);\n    } else if (\n      pathname === '/foo_with_exp' ||\n      pathname === '/foo_with_expTtl'\n    ) {\n      console.log(pathname, searchParams, request.method, await request.text());\n      result = JSON.stringify({\n        keys: [],\n      });\n    } else if (pathname === '/') {\n      const limit = parseInt(queryParameters.key_count_limit ?? '3', 10);\n      const prefix = queryParameters.prefix;\n\n      switch (prefix) {\n        case 'te':\n          // [0, 1, 2, ... limit]\n          const range = [...Array(limit)].keys();\n          const keys = Array.from(range).map((index) => ({\n            name: `test${index}`,\n            metadata: { someMetadataKey: 'someMetadataValue' },\n          }));\n\n          result = JSON.stringify({\n            keys,\n            list_complete: limit === 100,\n            cursor: limit === 100 ? undefined : '6Ck1la0VxJ0djhidm1MdX2FyD',\n            expiration: 1234,\n          });\n          break;\n        case 'not-found':\n          result = JSON.stringify({\n            keys: [],\n            list_complete: true,\n          });\n      }\n    } else {\n      // generic success for get key\n      result = 'value-' + pathname.slice(1);\n    }\n    let response = new Response(result, {\n      status: 200,\n      headers: headers,\n    });\n    response.headers.set(\n      'CF-KV-Metadata',\n      '{\"someMetadataKey\":\"someMetadataValue\",\"someUnicodeMeta\":\"🤓\"}'\n    );\n    return response;\n  }\n\n  async delete(keys) {\n    if (keys === 'error') {\n      // invalid type requested\n      throw new KVInternalError('failed to delete a single key', 'DELETE');\n    }\n    if (Array.isArray(keys) && keys.length > 100) {\n      throw new BadClientRequestError(\n        'You can delete maximum of 100 keys per request',\n        'DELETE'\n      );\n    }\n    if (Array.isArray(keys) && keys.includes('error')) {\n      throw new KVInternalError(\n        'failed to delete a single key from batch',\n        'DELETE'\n      );\n    }\n    // Success otherwise\n  }\n}\n\nclass BadClientRequestError extends Error {\n  constructor(message, operation) {\n    super(`KV ${operation} failed: 400 ${message}`);\n  }\n}\n\nclass KVInternalError extends Error {\n  constructor(message, operation) {\n    super(`KV ${operation} failed: 500 ${message}`);\n  }\n}\n\nexport let getTest = {\n  async test(ctrl, env, ctx) {\n    // Test .get()\n    let response = await env.KV.get('success', {});\n    assert.strictEqual(response, 'value-success');\n\n    response = await env.KV.get('fail-client');\n    assert.strictEqual(response, null);\n    await assert.rejects(env.KV.get('fail-server'), {\n      message: 'KV GET failed: 500 Internal Server Error',\n    });\n\n    response = await env.KV.get('get-json');\n    assert.strictEqual(response, JSON.stringify({ example: 'values' }));\n\n    response = await env.KV.get('get-json', 'json');\n    assert.deepStrictEqual(response, { example: 'values' });\n\n    response = await env.KV.get('success', 'stream');\n    let result = '';\n    const decoder = new TextDecoder();\n    for await (const chunk of response) {\n      result += decoder.decode(chunk, { stream: true });\n    }\n    result += decoder.decode();\n    assert.strictEqual(result, 'value-success');\n\n    response = await env.KV.get('success', 'arrayBuffer');\n    assert.strictEqual(new TextDecoder().decode(response), 'value-success');\n  },\n};\n\nexport let getBulkTest = {\n  async test(ctrl, env, ctx) {\n    // // Testing .get bulk\n    let response = await env.KV.get(['key1', 'key\"2']);\n    let expected = new Map([\n      ['key1', '{\"example\":\"values-key1\"}'],\n      ['key\"2', '{\"example\":\"values-key\\\\\"2\"}'],\n    ]);\n    assert.deepStrictEqual(response, expected);\n\n    response = await env.KV.get(['key1', 'key2'], {});\n    expected = new Map([\n      ['key1', '{\"example\":\"values-key1\"}'],\n      ['key2', '{\"example\":\"values-key2\"}'],\n    ]);\n    assert.deepStrictEqual(response, expected);\n\n    let fullKeysArray = [];\n    let fullResponse = new Map();\n    for (let i = 0; i < 100; i++) {\n      fullKeysArray.push(`key` + i);\n      fullResponse.set(`key` + i, `{\"example\":\"values-key${i}\"}`);\n    }\n\n    response = await env.KV.get(fullKeysArray, {});\n    assert.deepStrictEqual(response, fullResponse);\n\n    //sending over 100 keys\n    fullKeysArray.push('key100');\n    await assert.rejects(env.KV.get(fullKeysArray), {\n      message: 'KV GET_BULK failed: 400 Bad Request',\n    });\n\n    response = await env.KV.get(['key1', 'not-found'], { cacheTtl: 100 });\n    expected = new Map([\n      ['key1', '{\"example\":\"values-key1\"}'],\n      ['not-found', null],\n    ]);\n    assert.deepStrictEqual(response, expected);\n\n    await assert.rejects(env.KV.get([]), {\n      message: 'KV GET_BULK failed: 400 Bad Request',\n    });\n\n    // // get bulk json\n    response = await env.KV.get(['key1', 'key2'], 'json');\n    expected = new Map([\n      ['key1', { example: 'values-key1' }],\n      ['key2', { example: 'values-key2' }],\n    ]);\n    assert.deepStrictEqual(response, expected);\n\n    // // get bulk json but it is not json - throws error\n    await assert.rejects(env.KV.get(['key-not-json', 'key2'], 'json'), {\n      message: 'KV GET_BULK failed: 500 Internal Server Error',\n    });\n\n    // // requested type is invalid for bulk get\n    await assert.rejects(env.KV.get(['key-not-json', 'key2'], 'arrayBuffer'), {\n      message: 'KV GET_BULK failed: 500 Internal Server Error',\n    });\n\n    await assert.rejects(\n      env.KV.get(['key-not-json', 'key2'], { type: 'banana' }),\n      {\n        message: 'KV GET_BULK failed: 500 Internal Server Error',\n      }\n    );\n\n    // // get with metadata\n    response = await env.KV.getWithMetadata('key1');\n    expected = {\n      value: 'value-key1',\n      metadata: { someMetadataKey: 'someMetadataValue', someUnicodeMeta: '🤓' },\n      cacheStatus: 'HIT',\n    };\n    assert.deepStrictEqual(response, expected);\n\n    response = await env.KV.getWithMetadata(['key1']);\n    expected = new Map([\n      [\n        'key1',\n        { metadata: 'example-metadata', value: '{\"example\":\"values-key1\"}' },\n      ],\n    ]);\n    assert.deepStrictEqual(response, expected);\n\n    response = await env.KV.getWithMetadata(['key1'], 'json');\n    expected = new Map([\n      [\n        'key1',\n        { metadata: 'example-metadata', value: { example: 'values-key1' } },\n      ],\n    ]);\n    assert.deepStrictEqual(response, expected);\n    response = await env.KV.getWithMetadata(['key1', 'key2'], 'json');\n    expected = new Map([\n      [\n        'key1',\n        { metadata: 'example-metadata', value: { example: 'values-key1' } },\n      ],\n      [\n        'key2',\n        { metadata: 'example-metadata', value: { example: 'values-key2' } },\n      ],\n    ]);\n    assert.deepStrictEqual(response, expected);\n  },\n};\n\nexport let deleteBulkTest = {\n  async test(ctrl, env, ctx) {\n    // Single key\n    let result = await env.KV.deleteBulk('success');\n    assert.strictEqual(result, undefined);\n\n    // Failure\n    await assert.rejects(env.KV.deleteBulk('error'), {\n      message: 'KV DELETE failed: 500 failed to delete a single key',\n    });\n\n    // Multiple keys\n    result = await env.KV.deleteBulk(['key1', 'key2', 'key3']);\n    assert.strictEqual(result, undefined);\n\n    // Too many keys\n    await assert.rejects(\n      env.KV.deleteBulk(\n        Array.from({ length: 101 }, (_, index) => `key${index + 1}`)\n      ),\n      {\n        message:\n          'KV DELETE failed: 400 You can delete maximum of 100 keys per request',\n      }\n    );\n\n    // Multiple keys with failure\n    await assert.rejects(env.KV.deleteBulk(['key1', 'error']), {\n      message: 'KV DELETE failed: 500 failed to delete a single key from batch',\n    });\n  },\n};\n\nexport let listTest = {\n  async test(ctrl, env, ctx) {\n    let result = await env.KV.list({ prefix: 'te' });\n    assert.strictEqual(result.keys.length, 3);\n    assert.strictEqual(result.list_complete, false);\n    assert.strictEqual(result.cursor, '6Ck1la0VxJ0djhidm1MdX2FyD');\n\n    result = await env.KV.list({ prefix: 'te', limit: 100, cursor: '123' });\n    assert.strictEqual(result.keys.length, 100);\n    assert.strictEqual(result.list_complete, true);\n\n    result = await env.KV.list({ prefix: 'not-found' });\n    assert.deepEqual(result, {\n      keys: [],\n      list_complete: true,\n      cacheStatus: 'HIT',\n    });\n  },\n};\n\nexport let putTest = {\n  async test(ctrl, env, ctx) {\n    await env.KV.put('foo_with_exp', 'bar1', { expiration: 10 });\n    await env.KV.put('foo_with_expTtl', 'bar2 a bit longer value', {\n      expirationTtl: 15,\n    });\n    const buf = new ArrayBuffer(256);\n    await env.KV.put('foo_with_expTtl', buf, { expirationTtl: 15 });\n    // assert.strictEqual(result.keys.length, 3);\n    // assert.strictEqual(result.list_complete, false);\n    // assert.strictEqual(result.cursor, '6Ck1la0VxJ0djhidm1MdX2FyD');\n\n    // result = await env.KV.list({ prefix: 'te', limit: 100, cursor: '123' });\n    // assert.strictEqual(result.keys.length, 100);\n    // assert.strictEqual(result.list_complete, true);\n\n    // result = await env.KV.list({ prefix: 'not-found' });\n    // assert.deepEqual(result, {\n    //   keys: [],\n    //   list_complete: true,\n    //   cacheStatus: null,\n    // });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/kv-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"kv-test\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"kv-test.js\" )\n        ],\n        bindings = [ ( name = \"KV\", kvNamespace = \"kv-test\" ), ],\n        # \"experimental\" flag is needed to test deleteBulk\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\", \"service_binding_extra_handlers\"],\n        streamingTails = [\"tail\"],\n      )\n    ),\n    (name = \"tail\", worker = .tailWorker, ),\n  ],\n  extensions = [ (\n    modules = [\n      ( name = \"test:instrumentation-tail\", esModule = embed \"instrumentation-tail-worker.js\" ),\n    ]\n  ) ]\n);\n\nconst tailWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"kv-instrumentation-test.js\")\n  ],\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/leak-fetch-test.js",
    "content": "// Test should not fail with a memory leak reported by ASAN.\n// Failures here would only occur in an ASAN build.\n\nexport const memoryLeak = {\n  async test(_ctrl, env) {\n    {\n      (await env.subrequest.fetch('http://example.org')).body.pipeTo(\n        new WritableStream()\n      );\n    }\n    globalThis.gc();\n    globalThis.gc();\n  },\n};\n\nexport const memoryLeak2 = {\n  async test(_ctrl, env) {\n    {\n      new ReadableStream().pipeTo(new WritableStream());\n    }\n    globalThis.gc();\n    globalThis.gc();\n  },\n};\n\nexport const memoryLeak3 = {\n  async test(_ctrl, env) {\n    {\n      new ReadableStream().pipeTo(new IdentityTransformStream().writable);\n    }\n    globalThis.gc();\n    globalThis.gc();\n  },\n};\n\nexport default {\n  async fetch() {\n    await scheduler.wait(1_000);\n    return new Response('ok');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/leak-fetch-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  v8Flags = [\"--expose-gc\"],\n  services = [\n    ( name = \"leak-fetch-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"leak-fetch-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"streams_enable_constructors\"],\n        bindings = [\n          (name = \"subrequest\", service = \"leak-fetch-test\")\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/memory-cache-test.js",
    "content": "import { strictEqual, ok } from 'node:assert';\n\nexport const basic = {\n  async test(ctrl, env) {\n    let fallbackCalled = 0;\n    const value = await env.CACHE.read('foo', async (key) => {\n      strictEqual(key, 'foo');\n      fallbackCalled++;\n      return { value: 'bar' };\n    });\n    const value2 = await env.CACHE.read('foo', async (key) => {\n      throw new Error('should not be called');\n    });\n    strictEqual(value, 'bar');\n    strictEqual(value, value2);\n    strictEqual(fallbackCalled, 1);\n  },\n};\n\nexport const keysTooLarge = {\n  async test(ctrl, env) {\n    // Keys that are fewer than 2048 bytes should work.\n    for (const key of ['', 'abc', 'a'.repeat(2048), 'ä'.repeat(1024)]) {\n      const val = await env.CACHE.read(key, async (k) => {\n        strictEqual(k, key);\n        return { value: `value for ${k}` };\n      });\n      strictEqual(val, `value for ${key}`);\n    }\n\n    // Keys longer that 2048 bytes should reject.\n    for (const key of ['a'.repeat(2049), 'ä'.repeat(1025)]) {\n      await env.CACHE.read(key, async (k) => {\n        throw new Error('should not be called');\n      }).then(\n        () => {\n          throw new Error('should have rejected');\n        },\n        (err) => {\n          strictEqual(err.message, 'Key too large.');\n        }\n      );\n    }\n  },\n};\n\nexport const valueNotSerializable = {\n  async test(ctrl, env) {\n    try {\n      await env.CACHE.read('foo', (key) => {\n        return { value: Symbol('foo') };\n      });\n      throw new Error('should have failed');\n    } catch (err) {\n      strictEqual(err.message, 'failed to serialize symbol');\n    }\n\n    try {\n      await env.CACHE.read('foo', (key) => {\n        return { value: () => {} };\n      });\n      throw new Error('should have failed');\n    } catch (err) {\n      strictEqual(err.message, 'failed to serialize function');\n    }\n  },\n};\n\nexport const evictionsHappen = {\n  async test(ctrl, env) {\n    // Our cache has a max of two items, let's fill it...\n    await env.CACHE2.read('foo', async (key) => {\n      return { value: 'foo' };\n    });\n    await env.CACHE2.read('bar', async (key) => {\n      return { value: 'bar' };\n    });\n    // Nothing should be evicted at this point.\n    // Let's make foo our most recently accessed item.\n    strictEqual(await env.CACHE2.read('bar'), 'bar');\n    strictEqual(await env.CACHE2.read('foo'), 'foo');\n    // Now, let's add a third item, baz.\n    await env.CACHE2.read('baz', async (key) => {\n      return { value: 'baz' };\n    });\n    // At this point, 'bar' should have been evicted.\n    strictEqual(await env.CACHE2.read('bar'), undefined);\n  },\n};\n\nexport const evictionsHappenValueSize = {\n  async test(ctrl, env) {\n    strictEqual(\n      await env.CACHE3.read('foo', async (key) => {\n        return { value: 'a'.repeat(495) };\n      }),\n      'a'.repeat(495)\n    );\n\n    strictEqual(await env.CACHE3.read('foo'), 'a'.repeat(495));\n\n    strictEqual(\n      await env.CACHE3.read('bar', async (key) => {\n        return { value: 'a'.repeat(500) };\n      }),\n      'a'.repeat(500)\n    );\n\n    strictEqual(await env.CACHE3.read('bar'), undefined);\n\n    for (let n = 0; n < 6; n++) {\n      await env.CACHE3.read(`${n}`, async (key) => {\n        return { value: 'a'.repeat(100) };\n      });\n      strictEqual(await env.CACHE3.read(`${n}`), 'a'.repeat(100));\n    }\n    strictEqual(await env.CACHE3.read('bar'), undefined);\n  },\n};\n\nexport const concurrentReads = {\n  async test(ctrl, env) {\n    const promises = [];\n    promises.push(\n      env.CACHE.read('qux', () => {\n        return { value: 'qux' };\n      })\n    );\n    promises.push(\n      env.CACHE.read('qux', () => {\n        throw new Error('should not have been called');\n      })\n    );\n    const results = await Promise.allSettled(promises);\n    strictEqual(results[0].value, 'qux');\n    strictEqual(results[1].value, 'qux');\n  },\n};\n\nexport const delayedFallback = {\n  async test(ctrl, env) {\n    const foo = await env.CACHE.read('123', async () => {\n      await scheduler.wait(100);\n      return { value: 123 };\n    });\n    strictEqual(foo, 123);\n  },\n};\n\nexport const expiredEviction = {\n  async test(ctrl, env) {\n    const ret = await env.CACHE.read('expires', async () => {\n      return { value: 'foo', expiration: Date.now() + 100 };\n    });\n    strictEqual(ret, 'foo');\n    await scheduler.wait(600);\n    strictEqual(await env.CACHE.read('expires'), undefined);\n  },\n};\n\nexport const fallbackThrows = {\n  async test(ctrl, env) {\n    try {\n      await env.CACHE.read('foo', () => {\n        throw new Error('foo');\n      });\n      throw new Error('should have thrown');\n    } catch (err) {\n      strictEqual(err.message, 'foo');\n    }\n\n    try {\n      await env.CACHE.read('foo', async () => {\n        throw new Error('foo');\n      });\n      throw new Error('should have thrown');\n    } catch (err) {\n      strictEqual(err.message, 'foo');\n    }\n\n    try {\n      await env.CACHE.read('foo', () => {\n        return Promise.reject(new Error('foo'));\n      });\n      throw new Error('should have thrown');\n    } catch (err) {\n      strictEqual(err.message, 'foo');\n    }\n  },\n};\n\nexport const fallbackQueueMicrotask = {\n  async test(ctrl, env) {\n    const ret = await env.CACHE.read('microtask', () => {\n      const { promise, resolve } = Promise.withResolvers();\n      globalThis.queueMicrotask(() => {\n        resolve({ value: 'xyz' });\n      });\n      return promise;\n    });\n    strictEqual(ret, 'xyz');\n  },\n};\n\nexport const fallbackChainingOnError = {\n  async test(ctrl, env) {\n    const promises = [];\n    promises.push(\n      env.CACHE.read('fallback', () => {\n        throw new Error('foo');\n      })\n    );\n    promises.push(\n      env.CACHE.read('fallback', () => {\n        return { value: 'bar' };\n      })\n    );\n    const results = await Promise.allSettled(promises);\n    // The first one failed.\n    strictEqual(results[0].reason.message, 'foo');\n    // The second one succeeded.\n    strictEqual(results[1].value, 'bar');\n  },\n};\n\nexport const fallbackNotLocked = {\n  async test(ctrl, env) {\n    // Test that one long running fallback does not block another one.\n    const promises = [];\n    promises.push(\n      env.CACHE.read('aaa', async () => {\n        await scheduler.wait(500);\n        return { value: 'aaa' };\n      })\n    );\n    promises.push(\n      env.CACHE.read('bbb', async () => {\n        return { value: 'bbb' };\n      })\n    );\n    const raced = await Promise.race(promises);\n    strictEqual(raced, 'bbb');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/memory-cache-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"memory-cache-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"memory-cache-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"],\n        bindings = [\n          (name = \"CACHE\", memoryCache = (\n            limits = (\n              maxKeys = 2,\n              maxValueSize = 1024,\n              maxTotalValueSize = 2056,\n            ),\n          )),\n          (name = \"CACHE2\", memoryCache = (\n            limits = (\n              maxKeys = 2,\n              maxValueSize = 1024,\n              maxTotalValueSize = 2056,\n            ),\n          )),\n          (name = \"CACHE3\", memoryCache = (\n            limits = (\n              maxKeys = 2,\n              maxValueSize = 500,\n              maxTotalValueSize = 600,\n            ),\n          ))\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/messageport-test.js",
    "content": "import { ok, strictEqual, throws } from 'node:assert';\n\nimport { mock } from 'node:test';\n\nexport const simple1 = {\n  async test() {\n    const { port1, port2 } = new MessageChannel();\n    ok(port1 instanceof MessagePort);\n    ok(port2 instanceof MessagePort);\n    port1.postMessage(1);\n    port2.postMessage(1);\n    const { promise, resolve } = Promise.withResolvers();\n    const handler = mock.fn((event) => {\n      strictEqual(event.data, 1);\n      strictEqual(event.isTrusted, true);\n      resolve();\n    });\n    port2.onmessage = handler;\n    port1.onmessage = handler;\n    await promise;\n    strictEqual(handler.mock.callCount(), 2);\n  },\n};\n\nexport const simple2 = {\n  async test() {\n    const { port1, port2 } = new MessageChannel();\n    ok(port1 instanceof MessagePort);\n    ok(port2 instanceof MessagePort);\n    const { promise, resolve } = Promise.withResolvers();\n    const handler = mock.fn((event) => {\n      strictEqual(event.data, 1);\n      resolve();\n    });\n    port2.onmessage = handler;\n    port1.onmessage = handler;\n    port1.postMessage(1);\n    port2.postMessage(1);\n    await promise;\n    strictEqual(handler.mock.callCount(), 2);\n  },\n};\n\nexport const simple3 = {\n  async test() {\n    const { port1, port2 } = new MessageChannel();\n\n    const closeHandler = mock.fn();\n    port1.onclose = closeHandler;\n    port2.onclose = closeHandler;\n\n    port1.close();\n    port2.onmessage = () => {\n      throw new Error('should not be called');\n    };\n    port1.postMessage('nope');\n    await scheduler.wait(10);\n    strictEqual(closeHandler.mock.callCount(), 2);\n  },\n};\n\nexport const simple4 = {\n  async test() {\n    const { port1, port2 } = new MessageChannel();\n    port2.close();\n    port2.onmessage = () => {\n      throw new Error('should not be called');\n    };\n    port1.onmessage = () => {\n      throw new Error('should not be called');\n    };\n    port1.postMessage('nope');\n    port2.postMessage('nope');\n    await scheduler.wait(10);\n  },\n};\n\nexport const simple5 = {\n  async test() {\n    const { port1, port2 } = new MessageChannel();\n    throws(() => port1.postMessage(1, [1]), {\n      message: 'Transfer list is not supported',\n    });\n    throws(() => port1.postMessage(1, { transfer: [1] }), {\n      message: 'Transfer list is not supported',\n    });\n    // If the lists are empty it is ok.\n    port1.postMessage(1, []);\n    port1.postMessage(1, { transfer: [] });\n\n    const handler = mock.fn((event) => {\n      strictEqual(event.data, 1);\n    });\n    port2.onmessage = handler;\n    await scheduler.wait(10);\n    strictEqual(handler.mock.callCount(), 2);\n  },\n};\n\n// The following are a selected subset of web platform tests for MessageChannel and MessagePort\n// that we know we pass. We don't support the full MessagePort spec so we're not going to run\n// the full WPT's for these yet.\n// Refs: https://github.com/web-platform-tests/wpt/blob/master/webmessaging/Channel_postMessage_Blob.any.js\n// Refs: https://github.com/web-platform-tests/wpt/blob/master/webmessaging/Channel_postMessage_DataCloneErr.any.js\n\nexport const postMessageBlob = {\n  test() {\n    // Per the spec, Blob is a serializable object, but we don't implement it as such currently.\n    // So attempting to post a blob should throw an error.\n    const { port1 } = new MessageChannel();\n    throws(() => port1.postMessage(new Blob([''])), {\n      code: 25, // DATA_CLONE_ERR,\n      name: 'DataCloneError',\n      message: /Could not serialize/,\n    });\n  },\n};\n\nexport const postMessageRpcTarget = {\n  async test() {\n    const { RpcTarget } = await import('cloudflare:workers');\n    class Foo extends RpcTarget {}\n\n    const { port1 } = new MessageChannel();\n    throws(() => port1.postMessage(new Foo()), {\n      code: 25, // DATA_CLONE_ERR,\n      name: 'DataCloneError',\n    });\n  },\n};\n\n// Subset of the Web Platform Tests we know we don't pass, listed for future reference:\n// Most the web messaging WPT's are set up to require a full implementation of MessagePort\n// with web workers and most of the tests are in html files. We'll come back to these\n// later.\n// * https://github.com/web-platform-tests/wpt/blob/master/webmessaging/Channel_postMessage_clone_port.any.js\n// * https://github.com/web-platform-tests/wpt/blob/master/webmessaging/Channel_postMessage_clone_port_error.any.js\n// * https://github.com/web-platform-tests/wpt/blob/master/webmessaging/Channel_postMessage_ports_readonly_array.any.js\n// * https://github.com/web-platform-tests/wpt/blob/master/webmessaging/Channel_postMessage_transfer_xsite_incoming_messages.window.js\n// * https://github.com/web-platform-tests/wpt/blob/master/webmessaging/Channel_postMessage_transfer_xsite_incoming_messages.window.js\n// * https://github.com/web-platform-tests/wpt/blob/master/webmessaging/Channel_postMessage_with_transfer_incoming_messages.any.js\n// * https://github.com/web-platform-tests/wpt/blob/master/webmessaging/Channel_postMessage_with_transfer_outgoing_messages.any.js\n\n// This one is a bit special, per the spec we're supposed to fire off the close event\n// on an entangled port when the other port is garbage collected. We don't do that yet\n// and we might not ever. Need to investigate this further but it's not blocking us\n// right now.\n// * https://github.com/web-platform-tests/wpt/blob/master/webmessaging/message-channels/close-event/garbage-collected.tentative.any.js\n"
  },
  {
    "path": "src/workerd/api/tests/messageport-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"messageport-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"messageport-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat_v2\", \"expose_global_message_channel\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/module-test.js",
    "content": "import * as assert from 'a/b/c';\nimport * as assert3 from 'node:assert';\n\nexport const basics = {\n  async test() {\n    const assert2 = await import('a/b/c');\n    if (assert !== assert2 && assert !== assert3) {\n      throw new Error('bad things happened');\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/module-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"module-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"module-test.js\"),\n          (name = \"a/b/c\",\n           esModule = \"import * as assert from 'node:assert'; await import('node:buffer'); export default assert;\"),\n        ],\n        compatibilityFlags = [\"nodejs_compat\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/navigator-beacon-test.js",
    "content": "import { ok, strictEqual } from 'node:assert';\n\nconst enc = new TextEncoder();\n\n// This one returns false with no error thrown because we're not in an IoContext\nok(!navigator.sendBeacon('http://example.org', 'does not work'));\n\nexport const navigatorBeaconTest = {\n  async test(ctrl, env, ctx) {\n    ok(navigator.sendBeacon('http://example.org', 'beacon'));\n    ok(\n      navigator.sendBeacon(\n        'http://example.org',\n        new ReadableStream({\n          start(c) {\n            c.enqueue(enc.encode('beacon'));\n            c.close();\n          },\n        })\n      )\n    );\n    ok(navigator.sendBeacon('http://example.org', enc.encode('beacon')));\n  },\n};\n\nexport default {\n  async fetch(req, env) {\n    strictEqual(await req.text(), 'beacon');\n    return new Response(null, { status: 204 });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/navigator-beacon-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst worker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"navigator-beacon-test.js\")\n  ],\n  compatibilityFlags = [\"nodejs_compat\", \"global_navigator\", \"streams_enable_constructors\"],\n);\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"navigator-beacon-test\", worker = .worker ),\n    ( name = \"internet\", worker = .worker )\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/navigator-test.js",
    "content": "import { strictEqual, deepStrictEqual } from 'node:assert';\n\nexport const testHardwareConcurrency = {\n  async test() {\n    strictEqual(navigator.hardwareConcurrency, 1);\n  },\n};\n\nexport const testUserAgent = {\n  async test() {\n    strictEqual(navigator.userAgent, 'Cloudflare-Workers');\n  },\n};\n\nexport const testLanguage = {\n  async test() {\n    strictEqual(navigator.language, 'en');\n    Object.defineProperty(navigator, 'language', { value: 'tr' });\n    strictEqual(navigator.language, 'tr');\n  },\n};\n\nexport const testLanguages = {\n  async test() {\n    deepStrictEqual(navigator.languages, ['en']);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/navigator-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"navigator-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"navigator-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"enable_navigator_language\", \"global_navigator\"],\n      ),\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/new-module-registry-dns-test.js",
    "content": "// Test that the node:dns module works correctly with the new module registry.\n// This exercises the Rust-implemented node-internal:dns module registration\n// path, which uses an adapter to bridge the Rust ModuleCallback into the\n// new BuiltinBuilder::addSynthetic API.\n\nimport { strictEqual, ok, deepStrictEqual } from 'node:assert';\nimport dns from 'node:dns';\nimport dnsPromises from 'node:dns/promises';\n\nexport const basicDnsApi = {\n  test() {\n    // getServers() is a synchronous function that returns the list of\n    // configured DNS servers. It exercises the node-internal:dns module.\n    const servers = dns.getServers();\n    ok(Array.isArray(servers));\n  },\n};\n\nexport const dnsPromisesApi = {\n  test() {\n    const resolver = new dnsPromises.Resolver();\n    ok(resolver);\n    const servers = resolver.getServers();\n    ok(Array.isArray(servers));\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/new-module-registry-dns-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"new-module-registry-dns-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"new-module-registry-dns-test.js\"),\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"nodejs_compat_v2\",\n          \"new_module_registry\",\n          \"experimental\",\n        ],\n      ),\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/new-module-registry-node-filter-test.js",
    "content": "// Test that per-module Node.js feature flag filtering works with the new\n// module registry. node:repl is behind enable_nodejs_repl_module which has\n// no enable date before 2026-03-17. With the compat flag\n// disable_nodejs_repl_module explicitly set, the module must not be available.\n\nimport { ok, rejects } from 'node:assert';\n\n// node:buffer should always be available when nodejs_compat is enabled.\nimport { Buffer } from 'node:buffer';\nok(Buffer);\n\nexport const gatedModuleNotAvailable = {\n  async test() {\n    // With disable_nodejs_repl_module set in the wd-test config, importing\n    // node:repl must fail regardless of compat date.\n    await rejects(() => import('node:repl'), {\n      message: /node:repl/,\n    });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/new-module-registry-node-filter-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"new-module-registry-node-filter-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"new-module-registry-node-filter-test.js\"),\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"nodejs_compat_v2\",\n          \"new_module_registry\",\n          \"experimental\",\n          \"disable_nodejs_repl_module\",\n        ],\n      ),\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/new-module-registry-test.js",
    "content": "import {\n  default as assert,\n  notStrictEqual,\n  ok,\n  rejects,\n  strictEqual,\n  deepStrictEqual,\n} from 'assert'; // Intentionally omit the 'node:' prefix\nimport { foo, default as def } from 'foo';\nimport { default as fs } from 'node:fs';\nimport { Buffer } from 'buffer'; // Intentionally omit the 'node:' prefix\nimport { foo as foo2, default as def2 } from 'bar';\nimport { createRequire } from 'module'; // Intentionally omit the 'node:' prefix\n\nimport * as workers from 'cloudflare:workers';\nstrictEqual(typeof workers, 'object');\nstrictEqual(typeof workers.DurableObject, 'function');\nstrictEqual(typeof workers.RpcPromise, 'function');\nstrictEqual(typeof workers.RpcProperty, 'function');\nstrictEqual(typeof workers.RpcStub, 'function');\nstrictEqual(typeof workers.RpcTarget, 'function');\nstrictEqual(typeof workers.ServiceStub, 'function');\nstrictEqual(typeof workers.WorkerEntrypoint, 'function');\nstrictEqual(typeof workers.WorkflowEntrypoint, 'function');\nstrictEqual(typeof workers.waitUntil, 'function');\nstrictEqual(typeof workers.withEnv, 'function');\nstrictEqual(typeof workers.env, 'object');\n\nawait rejects(import('cloudflare-internal:env'), {\n  message: /Module not found/,\n});\nawait rejects(import('cloudflare-internal:filesystem'));\n\n// Verify that import.meta.url is correct here.\nstrictEqual(import.meta.url, 'file:///bundle/worker');\n\n// Verify that import.meta.main is true here.\nok(import.meta.main);\n\n// When running in nodejs_compat_v2 mode, the globalThis.Buffer\n// and globalThis.process properties are resolved from the module\n// registry. Let's make sure we get good values here.\nstrictEqual(typeof globalThis.Buffer, 'function');\nstrictEqual(globalThis.Buffer, Buffer);\nok(globalThis.process);\nstrictEqual(typeof globalThis.process, 'object');\n\n// Verify that process.getBuiltinModule works correctly with\n// the new module registry.\nconst builtinBuffer1 = process.getBuiltinModule('buffer');\nconst builtinBuffer2 = process.getBuiltinModule('node:buffer');\nstrictEqual(builtinBuffer1, builtinBuffer2);\nstrictEqual(builtinBuffer1.Buffer, globalThis.Buffer);\nconst nonExistent = process.getBuiltinModule('non-existent');\nstrictEqual(nonExistent, undefined);\n\n// Our internal implementation of console.log depend on the module registry\n// to get the node-internal:internal_inspect module. This console.log makes\n// sure that works correctly without crashing when using the new module\n// registry implementation.\nconsole.log(import.meta);\n\n// Verify that import.meta.resolve provides correct results here.\n// The input should be interpreted as a URL and normalized according\n// to the rules in the WHATWG URL specification.\nstrictEqual(import.meta.resolve('./.././test/.././.%2e/foo'), 'file:///foo');\nstrictEqual(import.meta.resolve('foo'), 'file:///bundle/foo');\n\n// There are four tests at this top level... one for the import of the node:assert\n// module without the node: prefix specifier, two for the imports of the foo and\n// bar modules from the worker, and one for the aliases node:fs module from the\n// module worker.\n\nstrictEqual(foo, 1);\nstrictEqual(def, 2);\nstrictEqual(foo2, 1);\nstrictEqual(def2, 2);\nstrictEqual(fs, 'abc');\n\n// Equivalent to the above, but using the file: URL scheme.\nimport { foo as foo3, default as def3 } from 'file:///bundle/foo';\nstrictEqual(foo, foo3);\nstrictEqual(def, def3);\n\nimport * as text from 'text';\nstrictEqual(text.default, 'abc');\n\nimport * as data from 'data';\nstrictEqual(Buffer.from(data.default).toString(), 'abcdef');\n\nimport * as json from 'json';\ndeepStrictEqual(json.default, { foo: 1 });\n\n// Synchronously resolved promises can be awaited.\nawait Promise.resolve();\n\n// CommonJS modules can be imported and should work as expected.\nimport { default as cjs2 } from 'cjs2';\nstrictEqual(cjs2.foo, 1);\nstrictEqual(cjs2.bar, 2);\nstrictEqual(cjs2.filename, 'cjs1');\nstrictEqual(cjs2.dirname, '/bundle');\nstrictEqual(cjs2.assert, assert);\n\n// CommonJS modules can define named exports.\nimport { foo as cjs1foo, bar as cjs1bar } from 'cjs1';\nstrictEqual(cjs1foo, 1);\nstrictEqual(cjs1bar, 2);\n\n// The createRequire API works as expected.\nconst myRequire = createRequire(import.meta.url);\nconst customRequireCjs = myRequire('cjs1');\nstrictEqual(customRequireCjs.foo, cjs1foo);\nstrictEqual(customRequireCjs.bar, cjs1bar);\n\nconst customRequireCjs2 = myRequire(import.meta.resolve('cjs2'));\nstrictEqual(customRequireCjs2.foo, cjs2.foo);\n\n// When the module being imported throws an error during evaluation,\n// the error is propagated correctly.\nawait rejects(import('file:///bundle/cjs3'), {\n  message: 'boom',\n});\n\n// The modules cjs4 and cjs5 have a circular dependency on each other.\n// They should both load without error.\nimport { default as cjs4 } from 'cjs4';\nimport { default as cjs5 } from 'cjs5';\ndeepStrictEqual(cjs4, {});\ndeepStrictEqual(cjs5, {});\n\n// These dynamics imports can be top-level awaited because they\n// are immediately rejected with errors.\nawait rejects(import('invalid-json'), {\n  message: /Unexpected non-whitespace character after JSON/,\n});\n\nawait rejects(import('module-not-found'), {\n  message: /Module not found: file:\\/\\/\\/bundle\\/module-not-found/,\n});\n\nawait rejects(import('file:///outside'), {\n  message: /Module not found/,\n});\n\nawait import('file:///bundle/outside');\n\nconst abc123 = await import('abc123');\nstrictEqual(abc123.default, 1);\n\n// Full URLs can be used as module specifiers. These would not\n// actually resolve to network requests.\nconst mod = await import('https://example.com/mod');\nstrictEqual(mod.default, 'example');\n\n// Whitespace and leading ./, ../, and multiple slashes are\n// stripped and ignored when setting up the module registry,\n// so they should resolve as expected here as being relative\n// to the bundle base URL.\nconst mod2 = await import('   ./should/be/ok   ');\nstrictEqual(mod2.default, 1);\n\n// UTF-8 percent-encoded of 部品 (Japanese for \"component\")\nconst mod3 = await import('%E9%83%A8%E5%93%81');\nconst mod4 = await import('部品'); // Get's converted into UTF-8 bytes in source\nconst mod5 = await import('\\u90e8\\u54c1'); // Specifically UTF-16 code units in source\nconst mod6 = await import('\\xE9\\x83\\xA8\\xE5\\x93\\x81');\nimport { default as mod7 } from '部品';\nimport { default as mod8 } from '\\u90e8\\u54c1';\nimport { default as mod9 } from '\\xE9\\x83\\xA8\\xE5\\x93\\x81';\nimport { default as mod10 } from '%E9%83%A8%E5%93%81';\n\nstrictEqual(mod3.default, 1);\nstrictEqual(mod4.default, 1);\nstrictEqual(mod5.default, 1);\nstrictEqual(mod6.default, 1);\nstrictEqual(mod7, 1);\nstrictEqual(mod8, 1);\nstrictEqual(mod9, 1);\nstrictEqual(mod10, 1);\n\n// The percent-encoded UTF-16 form of 部品 should not work.\nawait rejects(import('%E8%90%C1%54'), {\n  message: /Module not found/,\n});\nawait rejects(import('%90%E8%54%C1'), {\n  message: /Module not found/,\n});\n\n// Verify that a module is unable to perform IO operations at the top level, even if\n// the dynamic import is initiated within the scope of an active IoContext.\nexport const noTopLevelIo = {\n  async test() {\n    await rejects(import('bad'), {\n      message: /^Disallowed operation called within global scope/,\n    });\n  },\n};\n\n// Verify that async local storage is propagated into dynamic imports.\nexport const alsPropagationDynamicImport = {\n  async test() {\n    const { AsyncLocalStorage } = await import('async_hooks');\n    globalThis.als = new AsyncLocalStorage();\n    const res = await globalThis.als.run(123, () => import('als'));\n    strictEqual(res.default, 123);\n    delete globalThis.als;\n  },\n};\n\n// Query strings and fragments create new instances of known modules.\nexport const queryAndFragment = {\n  async test() {\n    // Each resolves the same underlying module but creates a new instance.\n    // The exports should be the same but the module namespaces should be different.\n\n    const a = await import('foo?query');\n    const b = await import('foo#fragment');\n    const c = await import('foo?query#fragment');\n    const d = await import('foo');\n\n    strictEqual(a.default, 2);\n    strictEqual(a.foo, 1);\n    strictEqual(a.default, b.default);\n    strictEqual(a.default, c.default);\n    strictEqual(a.default, d.default);\n    strictEqual(a.foo, b.foo);\n    strictEqual(a.foo, c.foo);\n    strictEqual(a.foo, d.foo);\n\n    notStrictEqual(a, b);\n    notStrictEqual(a, c);\n    notStrictEqual(a, d);\n    notStrictEqual(b, c);\n    notStrictEqual(b, d);\n    notStrictEqual(c, d);\n\n    // The import.meta.url for each should match the specifier used to import the instance.\n    strictEqual(a.bar, 'file:///bundle/foo?query');\n    strictEqual(b.bar, 'file:///bundle/foo#fragment');\n    strictEqual(c.bar, 'file:///bundle/foo?query#fragment');\n    strictEqual(d.bar, 'file:///bundle/foo');\n  },\n};\n\n// We do not currently support import attributes. Per the recommendation\n// in the spec, we throw an error when they are encountered.\nexport const importAssertionsFail = {\n  async test() {\n    await rejects(import('ia'), {\n      message: /^Import attributes are not supported/,\n    });\n    await rejects(import('foo', { with: { a: 'abc' } }), {\n      message: /^Import attributes are not supported/,\n    });\n  },\n};\n\nexport const invalidUrlAsSpecifier = {\n  async test() {\n    await rejects(import('zebra: not a \\x00 valid URL'), {\n      message: /Module not found/,\n    });\n  },\n};\n\nexport const evalErrorsInEsmTopLevel = {\n  async test() {\n    await rejects(import('esm-error'), {\n      message: /boom/,\n    });\n    await rejects(import('esm-error-dynamic'), {\n      message: /boom/,\n    });\n  },\n};\n\nexport const wasmModuleTest = {\n  async test() {\n    const { default: wasm } = await import('wasm');\n    ok(wasm instanceof WebAssembly.Module);\n    await WebAssembly.instantiate(wasm, {});\n  },\n};\n\nimport source wasmSource from 'wasm';\nexport const wasmSourcePhaseTest = {\n  async test() {\n    ok(wasmSource instanceof WebAssembly.Module);\n    await WebAssembly.instantiate(wasmSource, {});\n  },\n};\n\nexport const wasmDynamicSourcePhaseTest = {\n  async test() {\n    const wasmSource = await import.source('wasm');\n    ok(wasmSource instanceof WebAssembly.Module);\n    await WebAssembly.instantiate(wasmSource, {});\n  },\n};\n\nexport const wasmDynamicSourcePhaseFailureTest = {\n  async test() {\n    await rejects(import.source('foo'), {\n      message:\n        'Source phase import not available for module: file:///bundle/foo',\n    });\n  },\n};\n\nexport const complexModuleTest = {\n  async test() {\n    const { abc } = await import('complex');\n    strictEqual(abc.foo, 1);\n    strictEqual(abc.def.bar, 2);\n\n    const { default: abc2 } = await import('abc');\n    strictEqual(abc2, 'file:///bundle/abc');\n  },\n};\n\n// TODO(now): Tests\n// * [x] Include tests for all known module types\n//   * [x] ESM\n//   * [x] CommonJS\n//   * [x] Text\n//   * [x] Data\n//   * [x] JSON\n//   * [x] WASM\n//   * [x] Python (works, but still needs to be fully tested)\n// * [x] IO is forbidden in top-level module scope\n// * [x] Async local storage context is propagated into dynamic imports\n// * [x] Static import correctly handles node: modules with/without the node: prefix\n// * [x] Dynamic import correctly handles node: modules with/without the node: prefix\n// * [x] Worker bundle can alias node: modules\n// * [x] modules not found are correctly reported as errors\n// * [x] Errors during ESM evaluation are correctly reported\n// * [x] Errors during dynamic import are correctly reported\n// * [x] Errors in JSON module parsing are correctly reported\n// * [x] Module specifiers are correctly handled as URLs\n//   * [x] Querys and fragments resolve new instances of known modules\n//   * [x] URL resolution works correctly\n//   * [x] Invalid URLs are correctly reported as errors\n// * [x] Import attributes should be rejected\n// * [x] require(...) Works in CommonJs Modules\n// * [x] require(...) correctly handles node: modules with/without the node: prefix\n// * [x] Circular dependencies are correctly handled\n// * [x] Errors during CommonJs evaluation are correctly reported\n// * [x] CommonJs modules correctly expose named exports\n// * [x] require('module').createRequire API works as expected\n// * [x] Entry point ESM with no default export is correctly reported as error\n// * [ ] Fallback service works as expected\n// * [x] console.log output correctly uses node-internal:inspect for output\n// ...\n"
  },
  {
    "path": "src/workerd/api/tests/new-module-registry-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"new-module-registry-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"new-module-registry-test.js\"),\n          (name = \"foo\", esModule = \"export const foo = 1; export default 2; export const bar = import.meta.url\"),\n          (name = \"bar\", esModule = \"export const foo = 1; export default 2;\"),\n          (name = \"node:fs\", esModule = \"export default 'abc'\"),\n\n          # Intentionally bad module to test error handling.\n          # Evaluation will error because i/o is not permitted at top-level scope.\n          (name = \"bad\", esModule = \"export default 1; setTimeout(() => {}, 10)\"),\n\n          # Ensure that async context is propagated into a dynamic import.\n          (name = \"als\", esModule = \"export default globalThis.als.getStore()\"),\n\n          # Import attributes are not supported currently\n          (name = \"ia\", esModule = \"import * as def from 'foo' with { a: 'test' }\"),\n\n          # Errors on ESM eval should be reported properly in both static and\n          # dynamic imports.\n          (name = \"esm-error\", esModule = \"export default 1; throw new Error('boom');\"),\n          (name = \"esm-error-dynamic\", esModule = \"export * as d from 'esm-error'\"),\n\n          # CommonJS modules work\n          (name = \"cjs1\",\n           namedExports = [\"foo\", \"bar\"],\n           commonJsModule = \"module.exports = { foo: 1, bar: 2, filename: __filename, dirname: __dirname, assert: require('assert') }\"),\n          (name = \"cjs2\", commonJsModule = \"module.exports = require('cjs1')\"),\n          (name = \"cjs3\", commonJsModule = \"throw new Error('boom')\"),\n          # Intentional circular dependency\n          (name = \"cjs4\", commonJsModule = \"module.exports = require('cjs5')\"),\n          (name = \"cjs5\", commonJsModule = \"module.exports = require('cjs4')\"),\n\n          # Other module types work\n          (name = \"text\", text = \"abc\"),\n          (name = \"data\", data = \"abcdef\"),\n          (name = \"json\", json = \"{ \\\"foo\\\": 1 }\"),\n          (name = \"invalid-json\", json = \"1n\"),\n\n          (name = \"wasm\", wasm = embed \"test.wasm\"),\n\n          (name = \"complex\", esModule = \"import * as abc from '././././././complex/complex2'; export { abc };\"),\n          (name = \"complex/complex2\", esModule = \"import * as def from '/bundle/complex/complex3'; export { def }; export const foo = 1;\"),\n          (name = \"complex/complex3\", esModule = \"export const bar = 2;\"),\n\n          # When the user bundle name begins with a slash, it is stripped before\n          # processing, ensuring that the module is still processed relative to\n          # the bundle base URL. Query strings and fragments are ignored.\n          (name = \"/%61bc?abc#123\", esModule = \"export default import.meta.url\"),\n\n          # A module name should not be able to escape the bundle base URL,\n          # and leading/trailing whitespace should be trimmed.\n          (name = \"    ..//////outside    \", esModule = \"export default 1;\"),\n\n          # Should resolve to \"abc123\" after normalization.\n          (name = \"/foo/../bar/../../../../////abc123\", esModule = \"export default 1;\"),\n\n          # It should be possible to have a module whose name is a valid URL.\n          (name = \"https://example.com/mod\", esModule = \"export default 'example';\"),\n\n          # Percent-encoded characters in the path should be normalized, and absolute\n          # file URLs are accepted as long as they are under the bundle base URL.\n          (name = \"file:///bundl%65/should/b%65/ok\", esModule = \"export default 1;\"),\n\n          # Unicode characters in the path should be handled as UTF-8 and supported.\n          (name = \"部品\", esModule = \"export default 1;\"),\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat_v2\",\n          \"new_module_registry\",\n          \"experimental\",\n        ],\n      ),\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/new-module-registry-ts-test-helper.ts",
    "content": "// A simple TypeScript module with type annotations that will be stripped\n// by the transpiler. This exercises the code path where EsModule source\n// is owned by a rust::String from the transpiler output.\n\nexport function add(a: number, b: number): number {\n  return a + b;\n}\n\nexport function greet(name: string): string {\n  return `hello, ${name}`;\n}\n\nexport const PI: number = 3.14159;\n"
  },
  {
    "path": "src/workerd/api/tests/new-module-registry-ts-test.js",
    "content": "// Test that TypeScript modules work correctly with the new module registry\n// when the typescript_strip_types compat flag is enabled. This exercises the\n// transpiler path where the source is owned by a rust::String that may have\n// shorter lifetime than the module registry.\n\nimport { strictEqual } from 'assert';\nimport { add, greet, PI } from 'ts-helper';\n\nstrictEqual(add(2, 3), 5);\nstrictEqual(greet('world'), 'hello, world');\nstrictEqual(PI, 3.14159);\n\nexport const dynamicImportTs = {\n  async test() {\n    const mod = await import('ts-helper');\n    strictEqual(mod.add(10, 20), 30);\n    strictEqual(mod.greet('test'), 'hello, test');\n    strictEqual(mod.PI, 3.14159);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/new-module-registry-ts-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"new-module-registry-ts-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"new-module-registry-ts-test.js\"),\n          (name = \"ts-helper\", esModule = embed \"new-module-registry-ts-test-helper.ts\"),\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat_v2\",\n          \"new_module_registry\",\n          \"typescript_strip_types\",\n          \"experimental\",\n        ],\n      ),\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/no-to-string-tag-test.js",
    "content": "import { strictEqual } from 'node:assert';\n\nexport default {\n  test() {\n    const h = new Headers();\n    strictEqual(h[Symbol.toStringTag], undefined);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/no-to-string-tag-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"no-to-string-tag-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"no-to-string-tag-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/opennextjs/BUILD.bazel",
    "content": "load(\"@bazel_skylib//rules:copy_file.bzl\", \"copy_file\")\nload(\"//:build/wd_test.bzl\", \"wd_test\")\n\ncopy_file(\n    name = \"opennext-ssr-worker\",\n    src = \"//src/workerd/api/tests/opennextjs/src:dist/worker.js\",\n    out = \"opennext-ssr-worker.js\",\n    tags = [\"no-downstream\"],\n    target_compatible_with = [\"@platforms//os:linux\"],\n)\n\nwd_test(\n    src = \"opennext-ssr-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"opennext-ssr-test.js\",\n        \":opennext-ssr-worker\",\n    ],\n    tags = [\"no-downstream\"],\n    target_compatible_with = [\"@platforms//os:linux\"],\n)\n"
  },
  {
    "path": "src/workerd/api/tests/opennextjs/README.md",
    "content": "# OpenNext SSR Test\n\nThis directory contains tests for running actual OpenNext Cloudflare bundled output in workerd.\nThe test verifies that workerd can correctly execute Next.js SSR applications built with the\n`@opennextjs/cloudflare` adapter.\n\n## Files\n\n- `opennext-ssr-test.js` - Test cases for API routes, SSR pages, streaming, RSC, etc.\n- `opennext-ssr-test.wd-test` - workerd test configuration\n- `src/` - Next.js application source files (JavaScript/JSX)\n\n## Running the Test\n\nThe worker is automatically generated from source before the test runs:\n\n```bash\nbazel test //src/workerd/api/tests/opennextjs:opennext-ssr-test@\n```\n\n**Note:** This test is Linux-only (`target_compatible_with = [\"@platforms//os:linux\"]`) due to\nthe reliance on the Next.js/OpenNext build toolchain.\n\n## How It Works\n\n1. **opennextjs-build**: Runs `@opennextjs/cloudflare build` to compile the Next.js app and\n   generate the OpenNext worker in `.open-next/`\n2. **opennextjs-worker**: Runs `wrangler deploy --dry-run --outdir=dist` to bundle the worker\n3. The output `dist/worker.js` is copied to `opennext-ssr-worker.js`\n4. The test runner loads the worker and executes test cases against it\n\n## Compatibility Flags\n\nThe test harness uses `nodejs_compat_v2` along with several additional Node.js module flags required (and defined in `src/wrangler.jsonc`)\nby the OpenNext runtime. These flags are configured in:\n\n- `src/wrangler.jsonc` - For the wrangler build\n- `opennext-ssr-test.wd-test` - For the workerd test runtime\n\nKey flags include:\n\n- `nodejs_compat_v2` - Enables Node.js compatibility layer (required due to test harness using oldest & latest compatibility dates)\n- `enable_nodejs_os_module` - Required for `node:os` module\n- `enable_nodejs_fs_module` - Required for `node:fs` module\n- `enable_nodejs_vm_module` - Required for `node:vm` module\n- `enable_nodejs_http_modules` - Required for `node:http` modules\n\n## Why JavaScript Instead of TypeScript?\n\nThe Next.js app uses JavaScript (`.js`/`.jsx`) instead of TypeScript because Next.js\nautomatically modifies `tsconfig.json` during builds. This conflicts with Bazel's sandbox,\nwhich treats source files as read-only. Using JavaScript with `jsconfig.json` avoids this issue.\n\n## Build Sandbox Note\n\nThe `opennextjs-build` target uses `execution_requirements = {\"no-sandbox\": \"1\"}` because\nNext.js needs to write various files during the build process (caches, generated files, etc.)\nwhich is incompatible with Bazel's default read-only sandbox.\n"
  },
  {
    "path": "src/workerd/api/tests/opennextjs/opennext-ssr-test.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { strictEqual, ok, deepStrictEqual } from 'node:assert';\nimport OpenNextWorker from 'opennext-ssr-worker';\n\nconst mockAssets = {\n  async fetch(request) {\n    const url = new URL(request.url);\n    if (url.pathname.startsWith('/_next/static/')) {\n      return new Response('/* mock js */', {\n        status: 200,\n        headers: { 'content-type': 'application/javascript' },\n      });\n    }\n    return new Response('Not Found', { status: 404 });\n  },\n};\n\nconst mockCtx = {\n  waitUntil() {},\n  passThroughOnException() {},\n};\n\nasync function fetchWorker(path, options = {}) {\n  const request = new Request(`http://localhost${path}`, {\n    method: options.method || 'GET',\n    headers: { accept: 'text/html', ...options.headers },\n    body: options.body,\n  });\n  return OpenNextWorker.fetch(request, { ASSETS: mockAssets }, mockCtx);\n}\n\nexport const workerInitialization = {\n  async test() {\n    ok(OpenNextWorker, 'OpenNext worker should be loaded');\n    ok(\n      typeof OpenNextWorker.fetch === 'function',\n      'Worker should have fetch handler'\n    );\n  },\n};\n\nexport const apiRouteGET = {\n  async test() {\n    const response = await fetchWorker('/api/data?foo=bar&baz=123', {\n      headers: { accept: 'application/json' },\n    });\n\n    strictEqual(response.status, 200);\n    strictEqual(response.headers.get('content-type'), 'application/json');\n\n    const data = await response.json();\n    ok(typeof data.timestamp === 'number', 'Should include timestamp');\n    strictEqual(data.message, 'API response');\n    strictEqual(data.method, 'GET');\n    ok(data.searchParams, 'Should include search params');\n  },\n};\n\nexport const apiRoutePOST = {\n  async test() {\n    const requestBody = { key: 'value', nested: { a: 1, b: [1, 2, 3] } };\n    const response = await fetchWorker('/api/data', {\n      method: 'POST',\n      headers: {\n        'content-type': 'application/json',\n        accept: 'application/json',\n      },\n      body: JSON.stringify(requestBody),\n    });\n\n    strictEqual(response.status, 200);\n    const data = await response.json();\n    deepStrictEqual(\n      data.received,\n      requestBody,\n      'Should echo back request body'\n    );\n    strictEqual(data.method, 'POST');\n  },\n};\n\nexport const apiRouteOPTIONS = {\n  async test() {\n    const response = await fetchWorker('/api/data', {\n      method: 'OPTIONS',\n      headers: {\n        origin: 'http://example.com',\n        'access-control-request-method': 'POST',\n      },\n    });\n\n    strictEqual(response.status, 204, 'OPTIONS should return 204');\n    ok(\n      response.headers.get('access-control-allow-methods'),\n      'Should have CORS headers'\n    );\n  },\n};\n\nexport const cookiesAPIGet = {\n  async test() {\n    const response = await fetchWorker('/api/cookies', {\n      headers: {\n        accept: 'application/json',\n        cookie: 'test-cookie=hello; another=world',\n      },\n    });\n\n    strictEqual(response.status, 200);\n    const data = await response.json();\n    ok(data.cookies, 'Should return cookies object');\n  },\n};\n\nexport const cookiesAPISet = {\n  async test() {\n    const response = await fetchWorker('/api/cookies', {\n      method: 'POST',\n      headers: {\n        'content-type': 'application/json',\n        accept: 'application/json',\n      },\n      body: JSON.stringify({\n        name: 'session',\n        value: 'abc123',\n        options: { httpOnly: true, path: '/' },\n      }),\n    });\n\n    strictEqual(response.status, 200);\n    const setCookie = response.headers.get('set-cookie');\n    ok(setCookie, 'Should have Set-Cookie header');\n    ok(setCookie.includes('session=abc123'), 'Should set the cookie value');\n  },\n};\n\nexport const cookiesAPIDelete = {\n  async test() {\n    const response = await fetchWorker('/api/cookies?name=session', {\n      method: 'DELETE',\n      headers: { accept: 'application/json' },\n    });\n\n    strictEqual(response.status, 200);\n    const data = await response.json();\n    strictEqual(data.deleted, 'session');\n  },\n};\n\nexport const indexPageSSR = {\n  async test() {\n    const response = await fetchWorker('/');\n\n    const contentType = response.headers.get('content-type');\n    ok(\n      contentType?.includes('text/html'),\n      `Should be HTML, got: ${contentType}`\n    );\n\n    const html = await response.text();\n    ok(\n      html.includes('<!DOCTYPE html>') || html.includes('<!doctype html>'),\n      'Should be HTML document'\n    );\n    ok(html.includes('<html'), 'Should have html tag');\n    ok(html.includes('SSR Test Page'), 'Should have page title');\n  },\n};\n\nexport const indexPageWithCookie = {\n  async test() {\n    const response = await fetchWorker('/', {\n      headers: { accept: 'text/html', cookie: 'test-cookie=from-request' },\n    });\n\n    strictEqual(response.status, 200);\n    const html = await response.text();\n    ok(html.includes('Cookie value:'), 'Should show cookie section');\n  },\n};\n\nexport const dynamicRouteBasic = {\n  async test() {\n    const response = await fetchWorker('/posts/123');\n\n    strictEqual(response.status, 200);\n    const html = await response.text();\n    ok(\n      html.includes('Post 123') || html.includes('123'),\n      'Should render post ID'\n    );\n  },\n};\n\nexport const dynamicRouteWithSpecialChars = {\n  async test() {\n    const response = await fetchWorker('/posts/hello-world-456');\n\n    strictEqual(response.status, 200);\n    const html = await response.text();\n    ok(html.includes('hello-world-456'), 'Should handle slug with hyphens');\n  },\n};\n\nexport const dynamicRouteNumeric = {\n  async test() {\n    const response = await fetchWorker('/posts/999');\n\n    strictEqual(response.status, 200);\n    const html = await response.text();\n    ok(html.includes('999'), 'Should handle numeric ID');\n  },\n};\n\nexport const streamingPageRenders = {\n  async test() {\n    const response = await fetchWorker('/streaming');\n\n    strictEqual(response.status, 200);\n    ok(\n      response.headers.get('content-type')?.includes('text/html'),\n      'Should be HTML'\n    );\n\n    const html = await response.text();\n    ok(html.includes('Streaming Test Page'), 'Should have page content');\n    ok(html.includes('Content chunk'), 'Should have streamed content');\n  },\n};\n\nexport const streamingResponseIsReadable = {\n  async test() {\n    const response = await fetchWorker('/streaming');\n\n    ok(response.body, 'Response should have a body');\n    ok(\n      typeof response.body.getReader === 'function',\n      'Body should be a ReadableStream'\n    );\n\n    const reader = response.body.getReader();\n    const chunks = [];\n    const decoder = new TextDecoder();\n    let totalBytes = 0;\n\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      chunks.push(value);\n      totalBytes += value.byteLength;\n    }\n\n    ok(chunks.length > 0, 'Should receive at least one chunk');\n    ok(totalBytes > 0, `Should receive bytes, got ${totalBytes}`);\n\n    const fullContent = chunks\n      .map((c) => decoder.decode(c, { stream: true }))\n      .join('');\n    ok(\n      fullContent.includes('<!DOCTYPE html>') ||\n        fullContent.includes('<!doctype html>'),\n      'Streamed content should be valid HTML'\n    );\n  },\n};\n\nexport const streamingMultipleChunks = {\n  async test() {\n    const response = await fetchWorker('/streaming');\n    const reader = response.body.getReader();\n\n    let chunkCount = 0;\n    let totalSize = 0;\n\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      chunkCount++;\n      totalSize += value.byteLength;\n    }\n\n    ok(chunkCount >= 1, `Should have chunks, got ${chunkCount}`);\n    ok(\n      totalSize > 1000,\n      `Should have substantial content, got ${totalSize} bytes`\n    );\n  },\n};\n\nexport const streamingConcurrentReads = {\n  async test() {\n    const responses = await Promise.all([\n      fetchWorker('/streaming'),\n      fetchWorker('/streaming'),\n      fetchWorker('/streaming'),\n    ]);\n\n    const results = await Promise.all(\n      responses.map(async (response) => {\n        const reader = response.body.getReader();\n        let size = 0;\n        while (true) {\n          const { done, value } = await reader.read();\n          if (done) break;\n          size += value.byteLength;\n        }\n        return size;\n      })\n    );\n\n    for (let i = 0; i < results.length; i++) {\n      ok(results[i] > 0, `Stream ${i} should have content`);\n    }\n  },\n};\n\nexport const redirectWithTarget = {\n  async test() {\n    const response = await fetchWorker(\n      '/redirect-test?target=/posts/redirected',\n      {\n        redirect: 'manual',\n      }\n    );\n\n    ok(\n      response.status === 307 ||\n        response.status === 308 ||\n        response.status === 302,\n      `Should redirect, got status ${response.status}`\n    );\n\n    const location = response.headers.get('location');\n    ok(location, 'Should have Location header');\n    ok(location.includes('/posts/redirected'), 'Should redirect to target');\n  },\n};\n\nexport const redirectPageWithoutTarget = {\n  async test() {\n    const response = await fetchWorker('/redirect-test');\n\n    strictEqual(response.status, 200, 'Should render page without redirect');\n    const html = await response.text();\n    ok(html.includes('Redirect Test'), 'Should show redirect test page');\n  },\n};\n\nexport const rscRequestBasic = {\n  async test() {\n    const response = await fetchWorker('/', {\n      headers: { accept: '*/*', RSC: '1' },\n    });\n\n    ok(\n      response.status >= 200 && response.status < 500,\n      `RSC request should not error, got ${response.status}`\n    );\n    const body = await response.text();\n    ok(body.length > 0, 'RSC response should not be empty');\n  },\n};\n\nexport const rscPrefetchRequest = {\n  async test() {\n    const response = await fetchWorker('/posts/456', {\n      headers: { accept: '*/*', RSC: '1', 'Next-Router-Prefetch': '1' },\n    });\n\n    ok(\n      response.status >= 200 && response.status < 500,\n      `RSC prefetch should work, got ${response.status}`\n    );\n  },\n};\n\nexport const notFoundPage = {\n  async test() {\n    const response = await fetchWorker('/nonexistent-page-xyz-123');\n\n    ok(\n      response.status === 404 || response.status === 200,\n      `Should handle 404, got: ${response.status}`\n    );\n  },\n};\n\nexport const notFoundAPIRoute = {\n  async test() {\n    const response = await fetchWorker('/api/nonexistent', {\n      headers: { accept: 'application/json' },\n    });\n\n    ok(\n      response.status === 404 || response.status >= 400,\n      `API 404 should return error status, got: ${response.status}`\n    );\n  },\n};\n\nexport const customHeadersForwarded = {\n  async test() {\n    const response = await fetchWorker('/api/data', {\n      headers: {\n        accept: 'application/json',\n        'x-custom-header': 'test-value',\n        'x-forwarded-for': '192.168.1.1',\n        'user-agent': 'workerd-test/1.0',\n      },\n    });\n\n    strictEqual(response.status, 200);\n    const data = await response.json();\n    ok(data.headers, 'Should return headers');\n    strictEqual(data.headers['x-custom-header'], 'test-value');\n  },\n};\n\nexport const acceptLanguageHeader = {\n  async test() {\n    const response = await fetchWorker('/api/data', {\n      headers: {\n        accept: 'application/json',\n        'accept-language': 'en-US,en;q=0.9,es;q=0.8',\n      },\n    });\n\n    strictEqual(response.status, 200);\n    const data = await response.json();\n    ok(data.headers['accept-language'], 'Should forward accept-language');\n  },\n};\n\nexport const headRequest = {\n  async test() {\n    const response = await fetchWorker('/api/data', {\n      method: 'HEAD',\n      headers: { accept: 'application/json' },\n    });\n\n    ok(response.status >= 200 && response.status < 500, 'HEAD should work');\n    const body = await response.text();\n    ok(body.length >= 0, 'HEAD response body handled');\n  },\n};\n\nexport const concurrentMixedRequests = {\n  async test() {\n    const responses = await Promise.all([\n      fetchWorker('/'),\n      fetchWorker('/api/data', { headers: { accept: 'application/json' } }),\n      fetchWorker('/posts/1'),\n      fetchWorker('/posts/2'),\n      fetchWorker('/streaming'),\n      fetchWorker('/api/cookies', { headers: { accept: 'application/json' } }),\n    ]);\n\n    for (let i = 0; i < responses.length; i++) {\n      ok(\n        responses[i].status >= 200 && responses[i].status < 500,\n        `Request ${i} should succeed`\n      );\n    }\n  },\n};\n\nexport const concurrentAPIRequests = {\n  async test() {\n    const responses = await Promise.all(\n      Array.from({ length: 10 }, (_, i) =>\n        fetchWorker(`/api/data?id=${i}`, {\n          headers: { accept: 'application/json' },\n        })\n      )\n    );\n\n    const bodies = await Promise.all(responses.map((r) => r.json()));\n    for (let i = 0; i < bodies.length; i++) {\n      ok(bodies[i].timestamp, `Request ${i} should have timestamp`);\n    }\n  },\n};\n\nexport const gracefulErrorHandling = {\n  async test() {\n    const paths = ['/500', '/../../../etc/passwd', '/api/data?error=true'];\n\n    for (const path of paths) {\n      const response = await fetchWorker(path);\n      ok(\n        typeof response.status === 'number',\n        `${path} should return a response`\n      );\n      ok(\n        response.status >= 200 && response.status < 600,\n        `${path} should have valid status`\n      );\n    }\n  },\n};\n\nexport const cacheControlHeaders = {\n  async test() {\n    const response = await fetchWorker('/');\n    const cacheControl = response.headers.get('cache-control');\n    ok(\n      cacheControl === null || typeof cacheControl === 'string',\n      'Cache-Control header should be readable'\n    );\n  },\n};\n\nexport const contentTypeHeaders = {\n  async test() {\n    const htmlResponse = await fetchWorker('/');\n    ok(\n      htmlResponse.headers.get('content-type')?.includes('text/html'),\n      'HTML page should have text/html'\n    );\n\n    const jsonResponse = await fetchWorker('/api/data', {\n      headers: { accept: 'application/json' },\n    });\n    ok(\n      jsonResponse.headers.get('content-type')?.includes('application/json'),\n      'API should have application/json'\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/opennextjs/opennext-ssr-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"opennext-ssr-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"opennext-ssr-test.js\"),\n          (name = \"opennext-ssr-worker\", esModule = embed \"opennext-ssr-worker.js\"),\n        ],\n        compatibilityFlags = [\n          \"experimental\",\n          \"nodejs_compat_v2\",\n          \"enable_nodejs_fs_module\",\n          \"enable_nodejs_os_module\",\n          \"enable_nodejs_vm_module\",\n          \"enable_nodejs_http_modules\",\n          \"streams_enable_constructors\",\n          \"transformstream_enable_standard_constructor\",\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/opennextjs/src/.gitignore",
    "content": ".next\n.open-next\n.wrangler\ndist\n"
  },
  {
    "path": "src/workerd/api/tests/opennextjs/src/BUILD.bazel",
    "content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_run_binary\")\nload(\"@npm//:defs.bzl\", \"npm_link_all_packages\")\nload(\"@npm//src/workerd/api/tests/opennextjs/src:@opennextjs/cloudflare/package_json.bzl\", opennext_bin = \"bin\")\nload(\"@npm//src/workerd/api/tests/opennextjs/src:wrangler/package_json.bzl\", wrangler_bin = \"bin\")\n\nnpm_link_all_packages(name = \"node_modules\")\n\nopennext_bin.opennextjs_cloudflare_binary(\n    name = \"opennextjs_cloudflare_bin\",\n)\n\nwrangler_bin.wrangler_binary(\n    name = \"wrangler_bin\",\n)\n\njs_run_binary(\n    name = \"opennextjs-build\",\n    srcs = [\n        \"jsconfig.json\",\n        \"next.config.mjs\",\n        \"open-next.config.mjs\",\n        \"package.json\",\n        \"wrangler.jsonc\",\n        \":node_modules/@opennextjs/cloudflare\",\n        \":node_modules/esbuild\",\n        \":node_modules/next\",\n        \":node_modules/react\",\n        \":node_modules/react-dom\",\n    ] + glob([\n        \"app/**/*.js\",\n        \"app/**/*.jsx\",\n    ]),\n    args = [\n        \"build\",\n        \"--openNextConfigPath\",\n        \"open-next.config.mjs\",\n    ],\n    chdir = package_name(),\n    execution_requirements = {\"no-sandbox\": \"1\"},\n    out_dirs = [\".open-next\"],\n    patch_node_fs = False,\n    tags = [\"no-downstream\"],\n    target_compatible_with = [\"@platforms//os:linux\"],\n    tool = \":opennextjs_cloudflare_bin\",\n    visibility = [\"//visibility:public\"],\n)\n\n# wrangler deploy --dry-run --outdir=dist\njs_run_binary(\n    name = \"opennextjs-worker\",\n    srcs = [\n        \"wrangler.jsonc\",\n        \":node_modules/@opennextjs/cloudflare\",\n        \":node_modules/esbuild\",\n        \":node_modules/wrangler\",\n        \":opennextjs-build\",\n    ],\n    outs = [\n        \"dist/README.md\",\n        \"dist/worker.js\",\n        \"dist/worker.js.map\",\n    ],\n    args = [\n        \"deploy\",\n        \"--dry-run\",\n        \"--outdir=dist\",\n    ],\n    chdir = package_name(),\n    execution_requirements = {\"no-sandbox\": \"1\"},\n    patch_node_fs = False,\n    tags = [\"no-downstream\"],\n    target_compatible_with = [\"@platforms//os:linux\"],\n    tool = \":wrangler_bin\",\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/workerd/api/tests/opennextjs/src/app/api/cookies/route.js",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { NextResponse } from 'next/server';\nimport { cookies } from 'next/headers';\n\nexport async function GET() {\n  const cookieStore = await cookies();\n  const allCookies = {};\n\n  cookieStore.getAll().forEach((cookie) => {\n    allCookies[cookie.name] = cookie.value;\n  });\n\n  return NextResponse.json({\n    message: 'Cookies API',\n    timestamp: Date.now(),\n    cookies: allCookies,\n  });\n}\n\nexport async function POST(request) {\n  const cookieStore = await cookies();\n  const body = await request.json().catch(() => ({}));\n  const { name, value, options } = body;\n\n  if (name && value) {\n    cookieStore.set(name, value, options || {});\n  }\n\n  return NextResponse.json({\n    message: 'Cookie set',\n    timestamp: Date.now(),\n    cookie: { name, value },\n  });\n}\n\nexport async function DELETE(request) {\n  const cookieStore = await cookies();\n  const name = request.nextUrl.searchParams.get('name');\n\n  if (name) {\n    cookieStore.delete(name);\n  }\n\n  return NextResponse.json({\n    message: 'Cookie deleted',\n    timestamp: Date.now(),\n    deleted: name,\n  });\n}\n"
  },
  {
    "path": "src/workerd/api/tests/opennextjs/src/app/api/data/route.js",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { NextResponse } from 'next/server';\n\nexport async function GET(request) {\n  const headers = {};\n  request.headers.forEach((value, key) => {\n    headers[key] = value;\n  });\n\n  const searchParams = {};\n  request.nextUrl.searchParams.forEach((value, key) => {\n    searchParams[key] = value;\n  });\n\n  return NextResponse.json({\n    message: 'API response',\n    timestamp: Date.now(),\n    method: 'GET',\n    headers,\n    searchParams,\n  });\n}\n\nexport async function POST(request) {\n  const body = await request.json().catch(() => ({}));\n\n  return NextResponse.json({\n    message: 'API response',\n    timestamp: Date.now(),\n    method: 'POST',\n    received: body,\n  });\n}\n\nexport async function OPTIONS() {\n  return new NextResponse(null, {\n    status: 204,\n    headers: {\n      'Access-Control-Allow-Origin': '*',\n      'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',\n    },\n  });\n}\n"
  },
  {
    "path": "src/workerd/api/tests/opennextjs/src/app/layout.jsx",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport const metadata = {\n  title: 'SSR Test App',\n};\n\nexport default function RootLayout({ children }) {\n  return (\n    <html lang=\"en\">\n      <body>{children}</body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "src/workerd/api/tests/opennextjs/src/app/page.jsx",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { Suspense } from 'react';\nimport { cookies } from 'next/headers';\n\nasync function AsyncSection({ id }) {\n  return <div id={`async-section-${id}`}>Loaded data for section-{id}</div>;\n}\n\nexport default async function Home() {\n  const cookieStore = await cookies();\n  const testCookie = cookieStore.get('test-cookie');\n\n  return (\n    <main>\n      <h1>SSR Test Page</h1>\n      <p id=\"server-time\">Server rendered at: {Date.now()}</p>\n      <p id=\"cookie-value\">Cookie value: {testCookie?.value ?? 'not set'}</p>\n      <section id=\"suspense-section\">\n        <h2>Suspense Boundaries</h2>\n        <Suspense fallback={<div>Loading 1...</div>}>\n          <AsyncSection id=\"1\" />\n        </Suspense>\n        <Suspense fallback={<div>Loading 2...</div>}>\n          <AsyncSection id=\"2\" />\n        </Suspense>\n        <Suspense fallback={<div>Loading 3...</div>}>\n          <AsyncSection id=\"3\" />\n        </Suspense>\n      </section>\n    </main>\n  );\n}\n"
  },
  {
    "path": "src/workerd/api/tests/opennextjs/src/app/posts/[id]/page.jsx",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport default async function PostPage({ params }) {\n  const { id } = await params;\n\n  return (\n    <main>\n      <h1>Post {id}</h1>\n      <p id=\"post-id\">Post ID: {id}</p>\n      <p id=\"render-time\">Rendered at: {Date.now()}</p>\n    </main>\n  );\n}\n"
  },
  {
    "path": "src/workerd/api/tests/opennextjs/src/app/redirect-test/page.jsx",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { redirect } from 'next/navigation';\n\nexport default function RedirectTestPage({ searchParams }) {\n  return <RedirectHandler searchParams={searchParams} />;\n}\n\nasync function RedirectHandler({ searchParams }) {\n  const params = await searchParams;\n  if (params.target) {\n    redirect(params.target);\n  }\n  return (\n    <main>\n      <h1>Redirect Test</h1>\n      <p>Add ?target=/path to redirect</p>\n    </main>\n  );\n}\n"
  },
  {
    "path": "src/workerd/api/tests/opennextjs/src/app/streaming/page.jsx",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport default function StreamingPage() {\n  return (\n    <main>\n      <h1>Streaming Test Page</h1>\n      <div id=\"large-content\">\n        {Array.from({ length: 100 }, (_, i) => (\n          <p key={i}>Content chunk {i + 1}</p>\n        ))}\n      </div>\n    </main>\n  );\n}\n"
  },
  {
    "path": "src/workerd/api/tests/opennextjs/src/jsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2017\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": false,\n    \"noEmit\": true,\n    \"module\": \"esnext\",\n    \"esModuleInterop\": true,\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\"\n  },\n  \"include\": [\"**/*.js\", \"**/*.jsx\"],\n  \"exclude\": [\"node_modules\", \".next\", \".open-next\"]\n}\n"
  },
  {
    "path": "src/workerd/api/tests/opennextjs/src/next.config.mjs",
    "content": "export default {};\n"
  },
  {
    "path": "src/workerd/api/tests/opennextjs/src/open-next.config.mjs",
    "content": "import { defineCloudflareConfig } from '@opennextjs/cloudflare';\n\nconst config = defineCloudflareConfig({});\n\n// Add buildCommand at the root level - this is used by @opennextjs/aws\n// to run the Next.js build. Using \"node --run build\" avoids needing pnpm.\n// --webpack is required because turbopack does not work under bazel.\n// See https://github.com/vercel/next.js/discussions/89549\nconfig.buildCommand = 'node --run build -- --webpack';\n\nexport default config;\n"
  },
  {
    "path": "src/workerd/api/tests/opennextjs/src/package.json",
    "content": "{\n  \"name\": \"opennextjs-ssr-test\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"build-with-opennext\": \"opennextjs-cloudflare build\",\n    \"bundle-with-wrangler\": \"wrangler deploy --dry-run --outdir=dist\"\n  },\n  \"dependencies\": {\n    \"@opennextjs/cloudflare\": \"1.16.3\",\n    \"next\": \"16.1.6\",\n    \"react\": \"19.2.4\",\n    \"react-dom\": \"19.2.4\",\n    \"wrangler\": \"4.63.0\"\n  },\n  \"devDependencies\": {\n    \"esbuild\": \"^0.27.2\"\n  }\n}\n"
  },
  {
    "path": "src/workerd/api/tests/opennextjs/src/wrangler.jsonc",
    "content": "/**\n * For more details on how to configure Wrangler, refer to:\n * https://developers.cloudflare.com/workers/wrangler/configuration/\n */\n{\n\t\"$schema\": \"node_modules/wrangler/config-schema.json\",\n\t\"name\": \"opennextjs-ssr-test\",\n\t\"main\": \".open-next/worker.js\",\n\t\"compatibility_date\": \"2025-12-01\",\n\t\"compatibility_flags\": [\n\t\t\"nodejs_compat\",\n\t\t\"global_fetch_strictly_public\"\n\t],\n\t\"assets\": {\n\t\t\"binding\": \"ASSETS\",\n\t\t\"directory\": \".open-next/assets\"\n\t},\n\t\"images\": {\n\t\t// Enable image optimization\n\t\t// see https://opennext.js.org/cloudflare/howtos/image\n\t\t\"binding\": \"IMAGES\"\n\t},\n\t\"services\": [\n\t\t{\n\t\t\t// Self-reference service binding, the service name must match the worker name\n\t\t\t// see https://opennext.js.org/cloudflare/caching\n\t\t\t\"binding\": \"WORKER_SELF_REFERENCE\",\n\t\t\t\"service\": \"opennextjs-ssr-test\"\n\t\t}\n\t],\n\t\"observability\": {\n\t\t\"enabled\": true\n\t}\n}\n"
  },
  {
    "path": "src/workerd/api/tests/pipe-streams-test.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { strictEqual, ok, rejects, deepStrictEqual, throws } from 'node:assert';\nimport { mock } from 'node:test';\n\n// Test pipeThrough from JavaScript readable to internal writable\nexport const pipeThroughJsToInternal = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    const chunks = [enc.encode('hello'), enc.encode('there'), 'hello'];\n    const rs = new ReadableStream({\n      pull(c) {\n        c.enqueue(chunks.shift());\n        if (chunks.length === 0) c.close();\n      },\n    });\n    const transform = new IdentityTransformStream();\n    const readable = rs.pipeThrough(transform);\n\n    const output = [];\n    async function consumeStream() {\n      for await (const chunk of readable) {\n        output.push(dec.decode(chunk));\n      }\n    }\n    // The 'hello' string at the end of chunks will cause an error to be thrown.\n    await rejects(consumeStream, {\n      message: 'This WritableStream only supports writing byte types.',\n    });\n\n    deepStrictEqual(output, ['hello', 'there']);\n  },\n};\n\n// Test pipeThrough error in JS Readable aborts Internal Writable when preventAbort = false\nexport const pipeThroughJsToInternalErroredSource = {\n  async test() {\n    const enc = new TextEncoder();\n    const rs = new ReadableStream({\n      async pull() {\n        throw new Error('boom');\n      },\n    });\n    const transform = new IdentityTransformStream();\n    const readable = rs.pipeThrough(transform);\n\n    ok(transform.writable.locked);\n\n    const reader = readable.getReader();\n\n    await rejects(reader.read(), { message: 'boom' });\n\n    ok(!transform.writable.locked);\n\n    // Attempts to use the writable from here on will fail with the same error.\n    const writer = transform.writable.getWriter();\n    await rejects(writer.write(enc.encode('hello')), { message: 'boom' });\n  },\n};\n\n// Test pipeTo error in JS Readable aborts Internal Writable when preventAbort = false\nexport const pipeToJsToInternalErroredSource = {\n  async test() {\n    const enc = new TextEncoder();\n    const rs = new ReadableStream({\n      async pull() {\n        throw new Error('boom');\n      },\n    });\n    const { readable, writable } = new IdentityTransformStream();\n    const pipe = rs.pipeTo(writable);\n\n    ok(writable.locked);\n\n    const reader = readable.getReader();\n\n    await rejects(reader.read(), { message: 'boom' });\n\n    ok(!writable.locked);\n\n    // Attempts to use the writable from here on will fail with the same error.\n    const writer = writable.getWriter();\n    await rejects(writer.write(enc.encode('hello')), { message: 'boom' });\n\n    await rejects(pipe, { message: 'boom' });\n  },\n};\n\n// Test pipeThrough error in JS Readable does not abort Internal Writable when preventAbort = true\nexport const pipeThroughJsToInternalErroredSourcePreventAbort = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    const transform = new IdentityTransformStream();\n    const rs = new ReadableStream({\n      async pull() {\n        throw new Error('boom');\n      },\n    });\n    const readable = rs.pipeThrough(transform, { preventAbort: true });\n\n    let reader = readable.getReader();\n\n    ok(rs.locked);\n    ok(transform.writable.locked);\n    ok(transform.readable.locked);\n\n    // Allow the piping algorithm to process the error from the pull.\n    await scheduler.wait(1);\n\n    reader.releaseLock();\n    ok(!rs.locked);\n    ok(!transform.readable.locked);\n    ok(!transform.writable.locked);\n\n    // We can still use the transform's readable and writable here.\n    const writer = transform.writable.getWriter();\n    reader = transform.readable.getReader();\n\n    await Promise.all([writer.write(enc.encode('hello')), reader.read()]);\n  },\n};\n\n// Test pipeTo error in JS Readable does not abort Internal Writable when preventAbort = true\nexport const pipeToJsToInternalErroredSourcePreventAbort = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    const { writable, readable } = new IdentityTransformStream();\n    const rs = new ReadableStream({\n      async pull() {\n        throw new Error('boom');\n      },\n    });\n    const pipe = rs.pipeTo(writable, { preventAbort: true });\n\n    let reader = readable.getReader();\n\n    ok(rs.locked);\n    ok(writable.locked);\n    ok(readable.locked);\n\n    // The pipe promise should be rejected here but the WritableStream\n    // destination should still be usable.\n    await rejects(pipe, { message: 'boom' });\n\n    reader.releaseLock();\n    ok(!rs.locked);\n    ok(!readable.locked);\n    ok(!writable.locked);\n\n    // We can still use the transform's readable and writable here.\n    const writer = writable.getWriter();\n    reader = readable.getReader();\n    writer.write(enc.encode('hello'));\n    writer.close();\n    const result = await reader.read();\n    strictEqual(dec.decode(result.value), 'hello');\n  },\n};\n\n// Test pipeThrough error in Writable cancels Readable when preventCancel = false\nexport const pipeThroughJsToInternalErroredDest = {\n  async test() {\n    const enc = new TextEncoder();\n    const transform = new IdentityTransformStream();\n    const rs = new ReadableStream({\n      start(c) {\n        c.enqueue(enc.encode('hello'));\n      },\n    });\n    const readable = rs.pipeThrough(transform);\n\n    const reader = readable.getReader();\n\n    ok(rs.locked);\n    ok(transform.writable.locked);\n\n    reader.cancel(new Error('boom'));\n    reader.releaseLock();\n\n    // Allow the cancel to propagate back to the source.\n    await scheduler.wait(1);\n\n    ok(!rs.locked);\n    ok(!transform.readable.locked);\n    ok(!transform.writable.locked);\n\n    // Our JavaScript ReadableStream should be closed (not errored).\n    // Cancel propagates back and closes the source stream.\n    const reader2 = rs.getReader();\n    const result = await reader2.read();\n    ok(result.done);\n    strictEqual(result.value, undefined);\n  },\n};\n\n// Test pipeTo error in Writable cancels Readable when preventCancel = false\nexport const pipeToJsToInternalErroredDest = {\n  async test() {\n    const enc = new TextEncoder();\n    const { readable, writable } = new IdentityTransformStream();\n    const rs = new ReadableStream({\n      start(c) {\n        c.enqueue(enc.encode('hello'));\n      },\n    });\n    const pipe = rs.pipeTo(writable);\n\n    const reader = readable.getReader();\n\n    ok(rs.locked);\n    ok(writable.locked);\n\n    reader.cancel(new Error('boom'));\n    reader.releaseLock();\n\n    await rejects(pipe, { message: 'boom' });\n\n    ok(!rs.locked);\n    ok(!readable.locked);\n    ok(!writable.locked);\n\n    // Our JavaScript ReadableStream should be closed (not errored).\n    // Cancel propagates back and closes the source stream.\n    const reader2 = rs.getReader();\n    const result = await reader2.read();\n    ok(result.done);\n    strictEqual(result.value, undefined);\n  },\n};\n\n// Test closing Readable closes Writable when preventClose = false\nexport const pipeThroughJsToInternalCloses = {\n  async test() {\n    const enc = new TextEncoder();\n    const chunks = [enc.encode('hello'), enc.encode('there')];\n    const rs = new ReadableStream({\n      pull(c) {\n        c.enqueue(chunks.shift());\n        if (chunks.length === 0) c.close();\n      },\n    });\n    const transform = new IdentityTransformStream();\n    const readable = rs.pipeThrough(transform);\n\n    for await (const chunk of readable) {\n      // consume all chunks\n    }\n\n    // The writable should be closed and locked by the pipe\n    throws(() => transform.writable.getWriter(), {\n      message: 'This WritableStream is currently locked to a writer.',\n    });\n  },\n};\n\n// Test closing Readable does not close Writable when preventClose = true\nexport const pipeThroughJsToInternalPreventClose = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    const rs = new ReadableStream({\n      start(c) {\n        c.enqueue(enc.encode('hello'));\n        c.close();\n      },\n    });\n    const transform = new IdentityTransformStream();\n    const readable = rs.pipeThrough(transform, { preventClose: true });\n\n    // Because the internal TransformStream here won't resolve the write\n    // promises until a read has been performed, we have to read, then\n    // wait a turn of the event loop before we can check that the writer\n    // is still in the correct state.\n    const reader = readable.getReader();\n    await reader.read();\n\n    await scheduler.wait(1);\n\n    // The WritableStream should not be closed and still usable.\n    const writer = transform.writable.getWriter();\n    writer.write(enc.encode('there'));\n    const read = await reader.read();\n    strictEqual(dec.decode(read.value), 'there');\n  },\n};\n\n// Test pipeThrough with BYOB ReadableStream works\nexport const pipeThroughJsByobToInternal = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    const chunks = [enc.encode('hello'), enc.encode('there')];\n    const rs = new ReadableStream({\n      type: 'bytes',\n      pull(c) {\n        c.enqueue(chunks.shift());\n        if (chunks.length === 0) c.close();\n      },\n    });\n    const transform = new IdentityTransformStream();\n    const readable = rs.pipeThrough(transform);\n\n    const output = [];\n    for await (const chunk of readable) {\n      output.push(dec.decode(chunk));\n    }\n\n    strictEqual(output[0], 'hello');\n    strictEqual(output[1], 'there');\n  },\n};\n\n// Test pipeTo with BYOB ReadableStream works\nexport const pipeToJsByobToInternal = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    const chunks = [enc.encode('hello'), enc.encode('there')];\n    const rs = new ReadableStream({\n      type: 'bytes',\n      pull(c) {\n        c.enqueue(chunks.shift());\n        if (chunks.length === 0) c.close();\n      },\n    });\n    const { readable, writable } = new IdentityTransformStream();\n    rs.pipeTo(writable);\n\n    const output = [];\n    for await (const chunk of readable) {\n      output.push(dec.decode(chunk));\n    }\n\n    strictEqual(output[0], 'hello');\n    strictEqual(output[1], 'there');\n  },\n};\n\n// Test simple pipeTo from internal readable to JavaScript writable\nexport const pipeToInternalToJsSimple = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n\n    const { readable, writable } = new IdentityTransformStream();\n\n    const chunks = [];\n    const ws = new WritableStream({\n      write(chunk) {\n        chunks.push(chunk);\n      },\n    });\n\n    const pipe = readable.pipeTo(ws);\n\n    const writer = writable.getWriter();\n    writer.write(enc.encode('hello'));\n    writer.write(enc.encode('there'));\n    writer.close();\n\n    await pipe;\n\n    strictEqual(dec.decode(chunks[0]), 'hello');\n    strictEqual(dec.decode(chunks[1]), 'there');\n\n    ok(!ws.locked);\n    ok(!readable.locked);\n\n    const writer2 = ws.getWriter();\n    await rejects(writer2.write('no'), {\n      message: 'This WritableStream has been closed.',\n    });\n  },\n};\n\n// Test pipeTo error in internal readable aborts JS writable when preventAbort = false\nexport const pipeToInternalToJsError = {\n  async test() {\n    const enc = new TextEncoder();\n\n    const { readable, writable } = new IdentityTransformStream();\n\n    const ws = new WritableStream({\n      write(chunk) {},\n    });\n\n    const pipe = readable.pipeTo(ws);\n\n    writable.abort(new Error('boom'));\n\n    await rejects(pipe, { message: 'boom' });\n\n    const writer = ws.getWriter();\n    await rejects(writer.write('hello'), { message: 'boom' });\n  },\n};\n\n// Test pipeTo error in internal readable does not abort JS writable when preventAbort = true\nexport const pipeToInternalToJsErrorPrevent = {\n  async test() {\n    const { readable, writable } = new IdentityTransformStream();\n\n    const ws = new WritableStream({\n      write(chunk) {},\n    });\n\n    const pipe = readable.pipeTo(ws, { preventAbort: true });\n\n    writable.abort(new Error('boom'));\n\n    await rejects(pipe, { message: 'boom' });\n\n    ok(!ws.locked);\n\n    const writer = ws.getWriter();\n    await writer.write('hello');\n  },\n};\n\n// Test pipeTo closing internal readable closes JS writable when preventClose = false\nexport const pipeToInternalToJsClose = {\n  async test() {\n    const { readable, writable } = new IdentityTransformStream();\n\n    const ws = new WritableStream({});\n\n    readable.pipeTo(ws);\n\n    writable.close();\n\n    // Allow the close to propagate through the pipe.\n    await scheduler.wait(1);\n\n    const writer = ws.getWriter();\n    await rejects(writer.write('hello'), {\n      message: 'This WritableStream has been closed.',\n    });\n  },\n};\n\n// Test pipeTo closing internal readable does not close JS writable when preventClose = true\nexport const pipeToInternalToJsClosePrevent = {\n  async test() {\n    const { readable, writable } = new IdentityTransformStream();\n\n    const ws = new WritableStream({});\n\n    readable.pipeTo(ws, { preventClose: true });\n\n    writable.close();\n\n    // Allow the pipe to finish without closing the destination.\n    await scheduler.wait(1);\n\n    const writer = ws.getWriter();\n    await writer.write('hello');\n  },\n};\n\n// Test simple pipeTo JS-to-JS\nexport const pipeToJsToJsSimple = {\n  async test() {\n    const chunks = [1, 2, 3];\n    const output = [];\n    const readable = new ReadableStream({\n      async pull(c) {\n        c.enqueue(chunks.shift());\n        if (chunks.length === 0) c.close();\n      },\n    });\n    const writable = new WritableStream({\n      write(chunk) {\n        output.push(chunk);\n      },\n    });\n\n    await readable.pipeTo(writable);\n\n    deepStrictEqual(output, [1, 2, 3]);\n  },\n};\n\n// Test pipeTo error in JS readable aborts JS writable when preventAbort = false\nexport const pipeToJsToJsErrorReadable = {\n  async test() {\n    const abortFn = mock.fn();\n    const readable = new ReadableStream({\n      async pull() {\n        throw new Error('boom');\n      },\n    });\n    const writable = new WritableStream({\n      abort: abortFn,\n    });\n\n    await rejects(readable.pipeTo(writable), { message: 'boom' });\n\n    strictEqual(abortFn.mock.callCount(), 1);\n  },\n};\n\n// Test pipeTo error in JS readable does not abort JS writable when preventAbort = true\nexport const pipeToJsToJsErrorReadablePrevent = {\n  async test() {\n    const abortFn = mock.fn();\n    const readable = new ReadableStream({\n      async pull() {\n        throw new Error('boom');\n      },\n    });\n    const writable = new WritableStream({\n      abort: abortFn,\n    });\n\n    const pipe = readable.pipeTo(writable, { preventAbort: true });\n\n    await rejects(pipe, { message: 'boom' });\n\n    strictEqual(abortFn.mock.callCount(), 0);\n  },\n};\n\n// Test pipeTo error in JS writable cancels JS readable when preventCancel = false\nexport const pipeToJsToJsErrorWritable = {\n  async test() {\n    const cancelFn = mock.fn();\n    const readable = new ReadableStream({\n      start(c) {\n        c.enqueue('hello');\n      },\n      cancel: cancelFn,\n    });\n    const writable = new WritableStream({\n      write() {\n        throw new Error('boom');\n      },\n    });\n\n    const pipe = readable.pipeTo(writable);\n\n    await rejects(pipe, { message: 'boom' });\n\n    strictEqual(cancelFn.mock.callCount(), 1);\n  },\n};\n\n// Test pipeTo error in JS writable does not cancel JS readable when preventCancel = true\nexport const pipeToJsToJsErrorWritablePrevent = {\n  async test() {\n    const chunks = [1, 2];\n    const cancelFn = mock.fn();\n    const readable = new ReadableStream({\n      pull(c) {\n        c.enqueue(chunks.shift());\n        if (chunks.length === 0) c.close();\n      },\n      cancel: cancelFn,\n    });\n    const writable = new WritableStream({\n      write() {\n        throw new Error('boom');\n      },\n    });\n\n    const pipe = readable.pipeTo(writable, { preventCancel: true });\n\n    await rejects(pipe, { message: 'boom' });\n\n    strictEqual(cancelFn.mock.callCount(), 0);\n\n    const reader = readable.getReader();\n    await reader.read();\n  },\n};\n\n// Test closing JS readable closes JS writable when preventClose = false\nexport const pipeToJsToJsCloseReadable = {\n  async test() {\n    const closeFn = mock.fn();\n    const readable = new ReadableStream({\n      start(c) {\n        c.close();\n      },\n    });\n    const writable = new WritableStream({\n      close: closeFn,\n    });\n\n    await readable.pipeTo(writable);\n\n    strictEqual(closeFn.mock.callCount(), 1);\n  },\n};\n\n// Test closing JS readable does not close JS writable when preventClose = true\nexport const pipeToJsToJsCloseReadablePrevent = {\n  async test() {\n    const closeFn = mock.fn();\n    const readable = new ReadableStream({\n      start(c) {\n        c.close();\n      },\n    });\n    const writable = new WritableStream({\n      close: closeFn,\n    });\n\n    await readable.pipeTo(writable, { preventClose: true });\n\n    strictEqual(closeFn.mock.callCount(), 0);\n  },\n};\n\n// Test pipeTo from a tee branch\nexport const pipeToJsToJsTee = {\n  async test() {\n    const readable = new ReadableStream({\n      start(c) {\n        c.enqueue('hello');\n        c.close();\n      },\n    });\n\n    const output = [];\n    const writable = new WritableStream({\n      write(chunk) {\n        output.push(chunk);\n      },\n    });\n\n    const [branch] = readable.tee();\n\n    await branch.pipeTo(writable);\n\n    strictEqual(output[0], 'hello');\n  },\n};\n\n// Test pipeTo with already-aborted signal (JS to JS)\nexport const pipeToJsToJsCancelAlready = {\n  async test() {\n    const signal = AbortSignal.abort(new Error('boom'));\n    const writeFn = mock.fn();\n    const readable = new ReadableStream({\n      start(c) {\n        c.enqueue('hello');\n        c.close();\n      },\n    });\n\n    const writable = new WritableStream({\n      write: writeFn,\n    });\n\n    await rejects(readable.pipeTo(writable, { signal }), { message: 'boom' });\n\n    strictEqual(writeFn.mock.callCount(), 0);\n  },\n};\n\n// Test pipeTo with already-aborted signal (JS to native)\nexport const pipeToJsToNativeCancelAlready = {\n  async test() {\n    const signal = AbortSignal.abort(new Error('boom'));\n    const source = new ReadableStream({\n      start(c) {\n        c.enqueue('hello');\n        c.close();\n      },\n    });\n\n    const { writable, readable } = new TransformStream();\n\n    await rejects(source.pipeTo(writable, { signal }), { message: 'boom' });\n  },\n};\n\n// Test pipeTo cancelable during operation (JS to JS)\nexport const pipeToJsToJsCancel = {\n  async test() {\n    const controller = new AbortController();\n\n    const readable = new ReadableStream({\n      start(c) {\n        c.enqueue('hello');\n      },\n    });\n\n    let output = '';\n    const writable = new WritableStream({\n      write(chunk) {\n        output += chunk;\n        controller.abort(new Error('boom'));\n      },\n    });\n\n    await rejects(readable.pipeTo(writable, { signal: controller.signal }), {\n      message: 'boom',\n    });\n\n    strictEqual(output, 'hello');\n  },\n};\n\n// Test pipeTo cancelable during operation (JS to native)\nexport const pipeToJsToNativeCancel = {\n  async test() {\n    const controller = new AbortController();\n    const enc = new TextEncoder();\n\n    let ready = false;\n\n    const source = new ReadableStream({\n      start(c) {\n        c.enqueue(enc.encode('hello'));\n      },\n      pull() {\n        if (ready) {\n          controller.abort(new Error('boom'));\n        }\n      },\n    });\n\n    const { readable, writable } = new TransformStream();\n\n    const reader = readable.getReader();\n\n    ready = true;\n\n    const promises = await Promise.allSettled([\n      source.pipeTo(writable, { signal: controller.signal }),\n      reader.read(),\n    ]);\n\n    strictEqual(promises[0].status, 'rejected');\n    strictEqual(promises[1].status, 'rejected');\n\n    strictEqual(promises[0].reason.message, 'boom');\n    strictEqual(promises[1].reason.message, 'boom');\n  },\n};\n\n// Default fetch handler for service binding requests\nexport default {\n  async fetch(request) {\n    if (request.url.includes('/stream')) {\n      const data = 'hello world '.repeat(100);\n      return new Response(data);\n    }\n    return new Response('Not found', { status: 404 });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/pipe-streams-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"pipe-streams-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"pipe-streams-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"streams_enable_constructors\", \"transformstream_enable_standard_constructor\"],\n        bindings = [\n          ( name = \"SERVICE\", service = \"pipe-streams-test\" ),\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/pipe-write-special-buffer-test.js",
    "content": "import { ok } from 'node:assert';\n\nexport const SabViewPipeThroughCompressionStream = {\n  async test() {\n    const sab = new SharedArrayBuffer(100);\n    const view = new Uint8Array(sab);\n    view.fill(0x41);\n\n    const readable = new ReadableStream({\n      start(controller) {\n        controller.enqueue(view);\n        controller.close();\n      },\n    });\n\n    const cs = new CompressionStream('gzip');\n    await readable.pipeTo(cs.writable);\n\n    const reader = cs.readable.getReader();\n    const { value, done } = await reader.read();\n    ok(!done);\n    ok(value.byteLength > 0, 'Expected compressed output');\n  },\n};\n\nexport const SabViewPipeThroughIdentityTransform = {\n  async test() {\n    const sab = new SharedArrayBuffer(1024);\n    const view = new Uint8Array(sab);\n    view.fill(0x42);\n\n    const readable = new ReadableStream({\n      start(controller) {\n        controller.enqueue(view);\n        controller.close();\n      },\n    });\n\n    const ts = new IdentityTransformStream();\n    const pipePromise = readable.pipeTo(ts.writable);\n\n    const reader = ts.readable.getReader();\n    const chunks = [];\n    while (true) {\n      const { value, done } = await reader.read();\n      if (done) break;\n      chunks.push(value);\n    }\n    await pipePromise;\n\n    const totalLength = chunks.reduce((sum, c) => sum + c.byteLength, 0);\n    ok(totalLength === 1024, `Expected 1024 bytes, got ${totalLength}`);\n  },\n};\n\nexport const ResizableBufferPipeThroughIdentityTransform = {\n  async test() {\n    const buffer = new ArrayBuffer(1024, { maxByteLength: 2048 });\n    const view = new Uint8Array(buffer);\n    view.fill(0x43);\n\n    const readable = new ReadableStream({\n      start(controller) {\n        controller.enqueue(view);\n        controller.close();\n      },\n    });\n\n    const ts = new IdentityTransformStream();\n    const pipePromise = readable.pipeTo(ts.writable);\n\n    const reader = ts.readable.getReader();\n    const chunks = [];\n    while (true) {\n      const { value, done } = await reader.read();\n      if (done) break;\n      chunks.push(value);\n    }\n    await pipePromise;\n\n    const totalLength = chunks.reduce((sum, c) => sum + c.byteLength, 0);\n    ok(totalLength === 1024, `Expected 1024 bytes, got ${totalLength}`);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/pipe-write-special-buffer-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"pipe-write-special-buffer-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"pipe-write-special-buffer-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat_v2\", \"streams_enable_constructors\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/queue-error-codes-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'node:assert';\nimport { Buffer } from 'node:buffer';\n\nexport default {\n  async fetch(request, env, ctx) {\n    const { pathname } = new URL(request.url);\n\n    if (pathname === '/message') {\n      const body = await request.text();\n\n      // Return error with headers defined\n      if (body.includes('with-headers')) {\n        return new Response('', {\n          status: 503,\n          headers: {\n            'CF-Queues-Error-Code': '10503',\n            'CF-Queues-Error-Cause': 'Service temporarily unavailable',\n          },\n        });\n      }\n\n      // Return error without headers\n      if (body.includes('no-headers')) {\n        return new Response('', { status: 503 });\n      }\n\n      // Default success\n      return new Response('');\n    }\n\n    if (pathname === '/batch') {\n      const body = await request.json();\n\n      if (body.messages.length > 0) {\n        const firstMessage = Buffer.from(\n          body.messages[0].body,\n          'base64'\n        ).toString();\n\n        // Return error with headers defined\n        if (firstMessage.includes('with-headers')) {\n          return new Response('', {\n            status: 503,\n            headers: {\n              'CF-Queues-Error-Code': '10503',\n              'CF-Queues-Error-Cause': 'Service temporarily unavailable',\n            },\n          });\n        }\n\n        // Return error without headers\n        if (firstMessage.includes('no-headers')) {\n          return new Response('', { status: 503 });\n        }\n      }\n\n      // Default success\n      return new Response('');\n    }\n\n    return new Response('Not Found', { status: 404 });\n  },\n\n  async test(ctrl, env, ctx) {\n    const flagEnabled = env.ERROR_CODES_FLAG;\n\n    // Test with error headers defined\n    try {\n      await env.QUEUE.send('with-headers', { contentType: 'text' });\n      assert.fail('Expected send() to throw');\n    } catch (error) {\n      if (flagEnabled) {\n        // Flag ON + Headers defined → detailed error with code\n        assert.strictEqual(\n          error.message,\n          'Service temporarily unavailable (10503)'\n        );\n      } else {\n        // Flag OFF + Headers defined → generic error (ignores headers)\n        assert.strictEqual(\n          error.message,\n          'Queue send failed: Service Unavailable'\n        );\n      }\n    }\n\n    // Test without error headers\n    try {\n      await env.QUEUE.send('no-headers', { contentType: 'text' });\n      assert.fail('Expected send() to throw');\n    } catch (error) {\n      if (flagEnabled) {\n        // Flag ON + Headers missing → default error with code 15000\n        assert.strictEqual(error.message, 'Unknown Internal Error (15000)');\n      } else {\n        // Flag OFF + Headers missing → generic error\n        assert.strictEqual(\n          error.message,\n          'Queue send failed: Service Unavailable'\n        );\n      }\n    }\n\n    // Test sendBatch with headers defined\n    try {\n      await env.QUEUE.sendBatch([\n        { body: 'with-headers', contentType: 'text' },\n      ]);\n      assert.fail('Expected sendBatch() to throw');\n    } catch (error) {\n      if (flagEnabled) {\n        // Flag ON + Headers defined → detailed error with code\n        assert.strictEqual(\n          error.message,\n          'Service temporarily unavailable (10503)'\n        );\n      } else {\n        // Flag OFF + Headers defined → generic error (ignores headers)\n        assert.strictEqual(\n          error.message,\n          'Queue sendBatch failed: Service Unavailable'\n        );\n      }\n    }\n\n    // Test sendBatch without headers\n    try {\n      await env.QUEUE.sendBatch([{ body: 'no-headers', contentType: 'text' }]);\n      assert.fail('Expected sendBatch() to throw');\n    } catch (error) {\n      if (flagEnabled) {\n        // Flag ON + Headers missing → default error with code 15000\n        assert.strictEqual(error.message, 'Unknown Internal Error (15000)');\n      } else {\n        // Flag OFF + Headers missing → generic error\n        assert.strictEqual(\n          error.message,\n          'Queue sendBatch failed: Service Unavailable'\n        );\n      }\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/queue-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'node:assert';\nimport { Buffer } from 'node:buffer';\n\nlet serializedBody;\n\nexport default {\n  // Producer receiver (from `env.QUEUE`)\n  async fetch(request, env, ctx) {\n    assert.strictEqual(request.method, 'POST');\n    const { pathname } = new URL(request.url);\n    if (pathname === '/message') {\n      const format = request.headers.get('X-Msg-Fmt') ?? 'v8';\n      if (format === 'text') {\n        assert.strictEqual(request.headers.get('X-Msg-Delay-Secs'), '2');\n        assert.strictEqual(await request.text(), 'abc');\n      } else if (format === 'bytes') {\n        const array = new Uint16Array(await request.arrayBuffer());\n        assert.deepStrictEqual(array, new Uint16Array([1, 2, 3]));\n      } else if (format === 'json') {\n        assert.deepStrictEqual(await request.json(), { a: 1 });\n      } else if (format === 'v8') {\n        // workerd doesn't provide V8 deserialization APIs, so just look for expected strings\n        const buffer = Buffer.from(await request.arrayBuffer());\n        assert(buffer.includes('key'));\n        assert(buffer.includes('value'));\n        serializedBody = buffer;\n      } else {\n        assert.fail(`Unexpected format: ${JSON.stringify(format)}`);\n      }\n    } else if (pathname === '/batch') {\n      assert.strictEqual(request.headers.get('X-Msg-Delay-Secs'), '2');\n\n      const body = await request.json();\n\n      assert.strictEqual(typeof body, 'object');\n      assert(Array.isArray(body?.messages));\n      assert.strictEqual(body.messages.length, 4);\n\n      assert.strictEqual(body.messages[0].contentType, 'text');\n      assert.strictEqual(\n        Buffer.from(body.messages[0].body, 'base64').toString(),\n        'def'\n      );\n\n      assert.strictEqual(body.messages[1].contentType, 'bytes');\n      assert.deepStrictEqual(\n        Buffer.from(body.messages[1].body, 'base64'),\n        Buffer.from([4, 5, 6])\n      );\n\n      assert.strictEqual(body.messages[2].contentType, 'json');\n      assert.deepStrictEqual(\n        JSON.parse(Buffer.from(body.messages[2].body, 'base64')),\n        [7, 8, { b: 9 }]\n      );\n\n      assert.strictEqual(body.messages[3].contentType, 'v8');\n      assert(Buffer.from(body.messages[3].body, 'base64').includes('value'));\n      assert.strictEqual(body.messages[3].delaySecs, 1);\n    } else {\n      assert.fail(`Unexpected pathname: ${JSON.stringify(pathname)}`);\n    }\n    return new Response();\n  },\n\n  // Consumer receiver (from `env.SERVICE`)\n  async queue(batch, env, ctx) {\n    assert.strictEqual(batch.queue, 'test-queue');\n    assert.strictEqual(batch.messages.length, 5);\n\n    assert.strictEqual(batch.messages[0].id, '#0');\n    assert.strictEqual(batch.messages[0].body, 'ghi');\n    assert.strictEqual(batch.messages[0].attempts, 1);\n\n    assert.strictEqual(batch.messages[1].id, '#1');\n    assert.deepStrictEqual(batch.messages[1].body, new Uint8Array([7, 8, 9]));\n    assert.strictEqual(batch.messages[1].attempts, 2);\n\n    assert.strictEqual(batch.messages[2].id, '#2');\n    assert.deepStrictEqual(batch.messages[2].body, { c: { d: 10 } });\n    assert.strictEqual(batch.messages[2].attempts, 3);\n    batch.messages[2].retry();\n\n    assert.strictEqual(batch.messages[3].id, '#3');\n    assert.deepStrictEqual(batch.messages[3].body, batch.messages[3].timestamp);\n    assert.strictEqual(batch.messages[3].attempts, 4);\n    batch.messages[3].retry({ delaySeconds: 2 });\n\n    assert.strictEqual(batch.messages[4].id, '#4');\n    assert.deepStrictEqual(batch.messages[4].body, new Map([['key', 'value']]));\n    assert.strictEqual(batch.messages[4].attempts, 5);\n\n    batch.ackAll();\n  },\n\n  async test(ctrl, env, ctx) {\n    await env.QUEUE.send('abc', { contentType: 'text', delaySeconds: 2 });\n    await env.QUEUE.send(new Uint16Array([1, 2, 3]), { contentType: 'bytes' });\n    await env.QUEUE.send({ a: 1 }, { contentType: 'json' });\n    await env.QUEUE.send(new Map([['key', 'value']]), { contentType: 'v8' });\n\n    await env.QUEUE.sendBatch(\n      [\n        { body: 'def', contentType: 'text' },\n        { body: new Uint8Array([4, 5, 6]), contentType: 'bytes' },\n        { body: [7, 8, { b: 9 }], contentType: 'json' },\n        { body: new Set(['value']), contentType: 'v8', delaySeconds: 1 },\n      ],\n      { delaySeconds: 2 }\n    );\n\n    const timestamp = new Date();\n    const response = await env.SERVICE.queue('test-queue', [\n      { id: '#0', timestamp, body: 'ghi', attempts: 1 },\n      { id: '#1', timestamp, body: new Uint8Array([7, 8, 9]), attempts: 2 },\n      { id: '#2', timestamp, body: { c: { d: 10 } }, attempts: 3 },\n      { id: '#3', timestamp, body: timestamp, attempts: 4 },\n      { id: '#4', timestamp, serializedBody, attempts: 5 },\n    ]);\n    assert.strictEqual(response.outcome, 'ok');\n    assert(!response.retryBatch.retry);\n    assert(response.ackAll);\n    assert.deepStrictEqual(response.retryMessages, [\n      { msgId: '#2' },\n      { msgId: '#3', delaySeconds: 2 },\n    ]);\n    assert.deepStrictEqual(response.explicitAcks, []);\n\n    await assert.rejects(\n      env.SERVICE.queue('test-queue', [{ id: '#0', timestamp, attempts: 1 }]),\n      {\n        name: 'TypeError',\n        message: 'Expected one of body or serializedBody for each message',\n      }\n    );\n    await assert.rejects(\n      env.SERVICE.queue('test-queue', [\n        { id: '#0', timestamp, body: '', serializedBody, attempts: 1 },\n      ]),\n      {\n        name: 'TypeError',\n        message: 'Expected one of body or serializedBody for each message',\n      }\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/queue-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"queue-test\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"queue-test.js\" )\n        ],\n        bindings = [\n          ( name = \"QUEUE\", queue = \"queue-test\" ),\n          ( name = \"SERVICE\", service = \"queue-test\" ),\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"service_binding_extra_handlers\", \"queues_json_messages\", \"rpc\", \"capture_async_api_throws\", \"disable_fast_jsg_struct\"],\n      )\n    ),\n    ( name = \"queue-error-codes-enabled\",\n      worker = (\n        modules = [\n          ( name = \"worker-error-codes-enabled\", esModule = embed \"queue-error-codes-test.js\" )\n        ],\n        bindings = [\n          ( name = \"QUEUE\", queue = \"queue-error-codes-enabled\" ),\n          ( name = \"ERROR_CODES_FLAG\", json = \"true\" ),\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"service_binding_extra_handlers\", \"queues_json_messages\", \"queue_expose_error_codes\", \"rpc\", \"capture_async_api_throws\", \"disable_fast_jsg_struct\"],\n      )\n    ),\n    ( name = \"queue-error-codes-disabled\",\n      worker = (\n        modules = [\n          ( name = \"worker-error-codes-disabled\", esModule = embed \"queue-error-codes-test.js\" )\n        ],\n        bindings = [\n          ( name = \"QUEUE\", queue = \"queue-error-codes-disabled\" ),\n          ( name = \"ERROR_CODES_FLAG\", json = \"false\" ),\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"service_binding_extra_handlers\", \"queues_json_messages\", \"no_queue_expose_error_codes\", \"rpc\", \"capture_async_api_throws\", \"disable_fast_jsg_struct\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/r2-instrumentation-test.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport * as assert from 'node:assert';\nimport {\n  invocationPromises,\n  spans,\n  testTailHandler,\n} from 'test:instrumentation-tail';\n\n// Use shared instrumentation test tail worker\nexport default testTailHandler;\n\nexport const test = {\n  async test() {\n    // Wait for all the tailStream executions to finish\n    await Promise.allSettled(invocationPromises);\n\n    // Recorded streaming tail worker events, in insertion order.\n    let received = Array.from(spans.values());\n\n    // spans emitted by r2-test.js in execution order\n    let expected = [\n      {\n        name: 'r2_put',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'PutObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'basicKey',\n        'cloudflare.r2.request.size': 7n,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_get',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'GetObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'basicKey',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_get',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'GetObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'basicKey',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_head',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'HeadObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'basicKey',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_createMultipartUpload',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'CreateMultipartUpload',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'basicKey',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.upload_id': 'multipartId',\n        closed: true,\n      },\n      {\n        name: 'r2_uploadPart',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'UploadPart',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.upload_id': 'multipartId',\n        'cloudflare.r2.request.part_number': 1n,\n        'cloudflare.r2.request.key': 'basicKey',\n        'cloudflare.r2.request.size': 7n,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'partEtag',\n        closed: true,\n      },\n      {\n        name: 'r2_abortMultipartUpload',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'AbortMultipartUpload',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.upload_id': 'multipartId',\n        'cloudflare.r2.request.key': 'basicKey',\n        'cloudflare.r2.response.success': true,\n        closed: true,\n      },\n      {\n        name: 'r2_completeMultipartUpload',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'CompleteMultipartUpload',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.upload_id': 'multipartId',\n        'cloudflare.r2.request.key': 'basicKey',\n        'cloudflare.r2.request.uploaded_parts': '1',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_list',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'ListObjects',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.limit': 1n,\n        'cloudflare.r2.request.prefix': 'basic',\n        'cloudflare.r2.request.cursor': 'ai',\n        'cloudflare.r2.request.delimiter': '/',\n        'cloudflare.r2.request.include.http_metadata': true,\n        'cloudflare.r2.request.include.custom_metadata': true,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.returned_objects': 1n,\n        'cloudflare.r2.response.delimited_prefixes': 0n,\n        'cloudflare.r2.response.truncated': true,\n        'cloudflare.r2.response.cursor': 'ai',\n        closed: true,\n      },\n      {\n        name: 'r2_delete',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'DeleteObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.keys': 'basicKey',\n        'cloudflare.r2.response.success': true,\n        closed: true,\n      },\n      {\n        name: 'r2_delete',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'DeleteObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.keys': 'basicKey, basicKey2',\n        'cloudflare.r2.response.success': true,\n        closed: true,\n      },\n      {\n        name: 'r2_get',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'GetObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'rangeOffLen',\n        'cloudflare.r2.request.range.offset': 1n,\n        'cloudflare.r2.request.range.length': 3n,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_put',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'PutObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'throwOnInvalidEtag',\n        closed: true,\n      },\n      {\n        name: 'r2_put',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'PutObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'throwOnInvalidEtag',\n        closed: true,\n      },\n      {\n        name: 'r2_get',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'GetObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'rangeSuff',\n        'cloudflare.r2.request.range.suffix': 2n,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_put',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'PutObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'onlyIfStrongEtag',\n        'cloudflare.r2.request.only_if.etag_matches': 'strongEtag',\n        'cloudflare.r2.request.only_if.etag_does_not_match': 'strongEtag',\n        'cloudflare.r2.request.only_if.uploaded_before':\n          '2000-01-01T00:00:00.000Z',\n        'cloudflare.r2.request.size': 7n,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_put',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'PutObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'onlyIfWildcard',\n        'cloudflare.r2.request.only_if.etag_matches': '*',\n        'cloudflare.r2.request.only_if.etag_does_not_match': '*',\n        'cloudflare.r2.request.only_if.uploaded_after':\n          '2000-01-01T00:00:00.000Z',\n        'cloudflare.r2.request.size': 7n,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_get',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'GetObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'onlyIfStrongEtag',\n        'cloudflare.r2.request.only_if.etag_matches': 'strongEtag',\n        'cloudflare.r2.request.only_if.etag_does_not_match': 'strongEtag',\n        'cloudflare.r2.request.only_if.uploaded_before':\n          '2000-01-01T00:00:00.000Z',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_get',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'GetObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'onlyIfWildcard',\n        'cloudflare.r2.request.only_if.etag_matches': '*',\n        'cloudflare.r2.request.only_if.etag_does_not_match': '*',\n        'cloudflare.r2.request.only_if.uploaded_after':\n          '2000-01-01T00:00:00.000Z',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_put',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'PutObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'httpMetadata',\n        'cloudflare.r2.request.http_metadata.content_type': 'text/plain',\n        'cloudflare.r2.request.http_metadata.content_encoding': 'utf-8',\n        'cloudflare.r2.request.http_metadata.content_disposition':\n          'attachment; filename = \"basicKey.txt\"',\n        'cloudflare.r2.request.http_metadata.content_language': 'en-US',\n        'cloudflare.r2.request.http_metadata.cache_control': 'no-store',\n        'cloudflare.r2.request.http_metadata.cache_expiry': 1000n,\n        'cloudflare.r2.request.size': 7n,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.content_type': 'text/plain',\n        'cloudflare.r2.response.content_encoding': 'utf-8',\n        'cloudflare.r2.response.content_disposition':\n          'attachment; filename = \"basicKey.txt\"',\n        'cloudflare.r2.response.content_language': 'en-US',\n        'cloudflare.r2.response.cache_control': 'no-store',\n        'cloudflare.r2.response.cache_expiry': '1970-01-01T00:00:01.000Z',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_head',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'HeadObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'httpMetadata',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.content_type': 'text/plain',\n        'cloudflare.r2.response.content_encoding': 'utf-8',\n        'cloudflare.r2.response.content_disposition':\n          'attachment; filename = \"basicKey.txt\"',\n        'cloudflare.r2.response.content_language': 'en-US',\n        'cloudflare.r2.response.cache_control': 'no-store',\n        'cloudflare.r2.response.cache_expiry': '1970-01-01T00:00:01.000Z',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_get',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'GetObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'httpMetadata',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.content_type': 'text/plain',\n        'cloudflare.r2.response.content_encoding': 'utf-8',\n        'cloudflare.r2.response.content_disposition':\n          'attachment; filename = \"basicKey.txt\"',\n        'cloudflare.r2.response.content_language': 'en-US',\n        'cloudflare.r2.response.cache_control': 'no-store',\n        'cloudflare.r2.response.cache_expiry': '1970-01-01T00:00:01.000Z',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_list',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'ListObjects',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.prefix': 'httpMeta',\n        'cloudflare.r2.request.include.http_metadata': true,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.returned_objects': 1n,\n        'cloudflare.r2.response.delimited_prefixes': 0n,\n        'cloudflare.r2.response.truncated': false,\n        closed: true,\n      },\n      {\n        name: 'r2_createMultipartUpload',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'CreateMultipartUpload',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'httpMetadata',\n        'cloudflare.r2.request.http_metadata.content_type': 'text/plain',\n        'cloudflare.r2.request.http_metadata.content_encoding': 'utf-8',\n        'cloudflare.r2.request.http_metadata.content_disposition':\n          'attachment; filename = \"basicKey.txt\"',\n        'cloudflare.r2.request.http_metadata.content_language': 'en-US',\n        'cloudflare.r2.request.http_metadata.cache_control': 'no-store',\n        'cloudflare.r2.request.http_metadata.cache_expiry': 1000n,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.upload_id': 'multipartId',\n        closed: true,\n      },\n      {\n        name: 'r2_completeMultipartUpload',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'CompleteMultipartUpload',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.upload_id': 'multipartId',\n        'cloudflare.r2.request.key': 'httpMetadata',\n        'cloudflare.r2.request.uploaded_parts': '',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.content_type': 'text/plain',\n        'cloudflare.r2.response.content_encoding': 'utf-8',\n        'cloudflare.r2.response.content_disposition':\n          'attachment; filename = \"basicKey.txt\"',\n        'cloudflare.r2.response.content_language': 'en-US',\n        'cloudflare.r2.response.cache_control': 'no-store',\n        'cloudflare.r2.response.cache_expiry': '1970-01-01T00:00:01.000Z',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_put',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'PutObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'httpMetadata',\n        'cloudflare.r2.request.http_metadata.content_type': 'text/plain',\n        'cloudflare.r2.request.http_metadata.content_encoding': 'utf-8',\n        'cloudflare.r2.request.http_metadata.content_disposition':\n          'attachment; filename = \"basicKey.txt\"',\n        'cloudflare.r2.request.http_metadata.content_language': 'en-US',\n        'cloudflare.r2.request.http_metadata.cache_control': 'no-store',\n        'cloudflare.r2.request.http_metadata.cache_expiry': 1000n,\n        'cloudflare.r2.request.size': 7n,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.content_type': 'text/plain',\n        'cloudflare.r2.response.content_encoding': 'utf-8',\n        'cloudflare.r2.response.content_disposition':\n          'attachment; filename = \"basicKey.txt\"',\n        'cloudflare.r2.response.content_language': 'en-US',\n        'cloudflare.r2.response.cache_control': 'no-store',\n        'cloudflare.r2.response.cache_expiry': '1970-01-01T00:00:01.000Z',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_head',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'HeadObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'httpMetadata',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.content_type': 'text/plain',\n        'cloudflare.r2.response.content_encoding': 'utf-8',\n        'cloudflare.r2.response.content_disposition':\n          'attachment; filename = \"basicKey.txt\"',\n        'cloudflare.r2.response.content_language': 'en-US',\n        'cloudflare.r2.response.cache_control': 'no-store',\n        'cloudflare.r2.response.cache_expiry': '1970-01-01T00:00:01.000Z',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_get',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'GetObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'httpMetadata',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.content_type': 'text/plain',\n        'cloudflare.r2.response.content_encoding': 'utf-8',\n        'cloudflare.r2.response.content_disposition':\n          'attachment; filename = \"basicKey.txt\"',\n        'cloudflare.r2.response.content_language': 'en-US',\n        'cloudflare.r2.response.cache_control': 'no-store',\n        'cloudflare.r2.response.cache_expiry': '1970-01-01T00:00:01.000Z',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_list',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'ListObjects',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.prefix': 'httpMeta',\n        'cloudflare.r2.request.include.http_metadata': true,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.returned_objects': 1n,\n        'cloudflare.r2.response.delimited_prefixes': 0n,\n        'cloudflare.r2.response.truncated': false,\n        closed: true,\n      },\n      {\n        name: 'r2_createMultipartUpload',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'CreateMultipartUpload',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'httpMetadata',\n        'cloudflare.r2.request.http_metadata.content_type': 'text/plain',\n        'cloudflare.r2.request.http_metadata.content_encoding': 'utf-8',\n        'cloudflare.r2.request.http_metadata.content_disposition':\n          'attachment; filename = \"basicKey.txt\"',\n        'cloudflare.r2.request.http_metadata.content_language': 'en-US',\n        'cloudflare.r2.request.http_metadata.cache_control': 'no-store',\n        'cloudflare.r2.request.http_metadata.cache_expiry': 1000n,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.upload_id': 'multipartId',\n        closed: true,\n      },\n      {\n        name: 'r2_completeMultipartUpload',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'CompleteMultipartUpload',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.upload_id': 'multipartId',\n        'cloudflare.r2.request.key': 'httpMetadata',\n        'cloudflare.r2.request.uploaded_parts': '',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.content_type': 'text/plain',\n        'cloudflare.r2.response.content_encoding': 'utf-8',\n        'cloudflare.r2.response.content_disposition':\n          'attachment; filename = \"basicKey.txt\"',\n        'cloudflare.r2.response.content_language': 'en-US',\n        'cloudflare.r2.response.cache_control': 'no-store',\n        'cloudflare.r2.response.cache_expiry': '1970-01-01T00:00:01.000Z',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_put',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'PutObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'customMetadata',\n        'cloudflare.r2.request.custom_metadata': true,\n        'cloudflare.r2.request.size': 7n,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_head',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'HeadObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'customMetadata',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_get',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'GetObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'customMetadata',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_list',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'ListObjects',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.prefix': 'customMeta',\n        'cloudflare.r2.request.include.custom_metadata': true,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.returned_objects': 1n,\n        'cloudflare.r2.response.delimited_prefixes': 0n,\n        'cloudflare.r2.response.truncated': false,\n        closed: true,\n      },\n      {\n        name: 'r2_createMultipartUpload',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'CreateMultipartUpload',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'customMetadata',\n        'cloudflare.r2.request.custom_metadata': true,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.upload_id': 'multipartId',\n        closed: true,\n      },\n      {\n        name: 'r2_completeMultipartUpload',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'CompleteMultipartUpload',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.upload_id': 'multipartId',\n        'cloudflare.r2.request.key': 'customMetadata',\n        'cloudflare.r2.request.uploaded_parts': '',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_put',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'PutObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'classDefault',\n        'cloudflare.r2.request.size': 7n,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_head',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'HeadObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'classDefault',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_get',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'GetObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'classDefault',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_createMultipartUpload',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'CreateMultipartUpload',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'classDefault',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.upload_id': 'multipartId',\n        closed: true,\n      },\n      {\n        name: 'r2_completeMultipartUpload',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'CompleteMultipartUpload',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.upload_id': 'multipartId',\n        'cloudflare.r2.request.key': 'classDefault',\n        'cloudflare.r2.request.uploaded_parts': '',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_put',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'PutObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'classStandard',\n        'cloudflare.r2.request.storage_class': 'Standard',\n        'cloudflare.r2.request.size': 7n,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_head',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'HeadObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'classStandard',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_get',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'GetObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'classStandard',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_createMultipartUpload',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'CreateMultipartUpload',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'classStandard',\n        'cloudflare.r2.request.storage_class': 'Standard',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.upload_id': 'multipartId',\n        closed: true,\n      },\n      {\n        name: 'r2_completeMultipartUpload',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'CompleteMultipartUpload',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.upload_id': 'multipartId',\n        'cloudflare.r2.request.key': 'classStandard',\n        'cloudflare.r2.request.uploaded_parts': '',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_put',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'PutObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'classInfrequentAccess',\n        'cloudflare.r2.request.storage_class': 'InfrequentAccess',\n        'cloudflare.r2.request.size': 7n,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'InfrequentAccess',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_head',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'HeadObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'classInfrequentAccess',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'InfrequentAccess',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_get',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'GetObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'classInfrequentAccess',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'InfrequentAccess',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_createMultipartUpload',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'CreateMultipartUpload',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'classInfrequentAccess',\n        'cloudflare.r2.request.storage_class': 'InfrequentAccess',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.upload_id': 'multipartId',\n        closed: true,\n      },\n      {\n        name: 'r2_completeMultipartUpload',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'CompleteMultipartUpload',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.upload_id': 'multipartId',\n        'cloudflare.r2.request.key': 'classInfrequentAccess',\n        'cloudflare.r2.request.uploaded_parts': '',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'InfrequentAccess',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_put',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'PutObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'ssec',\n        'cloudflare.r2.request.ssec_key': true,\n        'cloudflare.r2.request.size': 7n,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.ssec_key': true,\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_get',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'GetObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'ssec',\n        'cloudflare.r2.request.ssec_key': true,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.ssec_key': true,\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_head',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'HeadObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'ssec',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.ssec_key': true,\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_createMultipartUpload',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'CreateMultipartUpload',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'ssecMultipart',\n        'cloudflare.r2.request.ssec_key': true,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.upload_id': 'multipartId',\n        closed: true,\n      },\n      {\n        name: 'r2_uploadPart',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'UploadPart',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.upload_id': 'multipartId',\n        'cloudflare.r2.request.part_number': 1n,\n        'cloudflare.r2.request.key': 'ssecMultipart',\n        'cloudflare.r2.request.ssec_key': true,\n        'cloudflare.r2.request.size': 3n,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'partEtag',\n        closed: true,\n      },\n      {\n        name: 'r2_completeMultipartUpload',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'CompleteMultipartUpload',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.upload_id': 'multipartId',\n        'cloudflare.r2.request.key': 'ssecMultipart',\n        'cloudflare.r2.request.uploaded_parts': '1',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.ssec_key': true,\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_put',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'PutObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'ssec',\n        'cloudflare.r2.request.ssec_key': true,\n        'cloudflare.r2.request.size': 7n,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.ssec_key': true,\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_get',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'GetObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'ssec',\n        'cloudflare.r2.request.ssec_key': true,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.ssec_key': true,\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_head',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'HeadObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'ssec',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.ssec_key': true,\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_createMultipartUpload',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'CreateMultipartUpload',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'ssecMultipart',\n        'cloudflare.r2.request.ssec_key': true,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.upload_id': 'multipartId',\n        closed: true,\n      },\n      {\n        name: 'r2_uploadPart',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'UploadPart',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.upload_id': 'multipartId',\n        'cloudflare.r2.request.part_number': 1n,\n        'cloudflare.r2.request.key': 'ssecMultipart',\n        'cloudflare.r2.request.ssec_key': true,\n        'cloudflare.r2.request.size': 3n,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'partEtag',\n        closed: true,\n      },\n      {\n        name: 'r2_completeMultipartUpload',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'CompleteMultipartUpload',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.upload_id': 'multipartId',\n        'cloudflare.r2.request.key': 'ssecMultipart',\n        'cloudflare.r2.request.uploaded_parts': '1',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.ssec_key': true,\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_put',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'PutObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'multipleChecksums',\n        'cloudflare.r2.request.checksum.type': 'md5',\n        'cloudflare.r2.request.checksum.value':\n          '9a0364b9e99bb480dd25e1f0284c8555',\n        'cloudflare.r2.request.size': 7n,\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.checksum.md5':\n          '9a0364b9e99bb480dd25e1f0284c8555',\n        'cloudflare.r2.response.checksum.sha1':\n          '2a0364b9e99bb480dd25e1f0284c855511223344',\n        'cloudflare.r2.response.checksum.sha256':\n          '3a0364b9e99bb480dd25e1f0284c8555112233445566778899aabbccddeeff00',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n      {\n        name: 'r2_head',\n        'cloudflare.binding.type': 'r2',\n        'cloudflare.binding.name': 'BUCKET',\n        'cloudflare.r2.operation': 'HeadObject',\n        'cloudflare.r2.bucket': 'r2-test',\n        'cloudflare.r2.request.key': 'multipleChecksums',\n        'cloudflare.r2.response.success': true,\n        'cloudflare.r2.response.etag': 'objectEtag',\n        'cloudflare.r2.response.size': 123,\n        'cloudflare.r2.response.uploaded': '2024-08-27T14:00:57.918Z',\n        'cloudflare.r2.response.checksum.md5':\n          '9a0364b9e99bb480dd25e1f0284c8555',\n        'cloudflare.r2.response.checksum.sha1':\n          '2a0364b9e99bb480dd25e1f0284c855511223344',\n        'cloudflare.r2.response.checksum.sha256':\n          '3a0364b9e99bb480dd25e1f0284c8555112233445566778899aabbccddeeff00',\n        'cloudflare.r2.response.storage_class': 'Standard',\n        'cloudflare.r2.response.custom_metadata': true,\n        closed: true,\n      },\n    ];\n\n    assert.deepStrictEqual(received, expected);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/r2-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'node:assert';\n\nconst key = 'basicKey';\nconst body = 'content';\nconst httpMetaObj = {\n  contentType: 'text/plain',\n  contentLanguage: 'en-US',\n  contentDisposition: 'attachment; filename = \"basicKey.txt\"',\n  contentEncoding: 'utf-8',\n  cacheControl: 'no-store',\n  cacheExpiry: new Date(1e3),\n};\nconst httpFields = {\n  ...httpMetaObj,\n  cacheExpiry: '1000',\n};\nconst httpMetaHeaders = new Headers({\n  'content-type': httpMetaObj.contentType,\n  'content-language': httpMetaObj.contentLanguage,\n  'content-disposition': httpMetaObj.contentDisposition,\n  'content-encoding': httpMetaObj.contentEncoding,\n  'cache-control': httpMetaObj.cacheControl,\n  expires: httpMetaObj.cacheExpiry.toUTCString(),\n});\nconst customMetadata = {\n  foo: 'bar',\n  baz: 'qux',\n};\nconst customFields = Object.entries(customMetadata).map(([k, v]) => ({ k, v }));\nconst bufferKey = new Uint8Array([\n  185, 255, 145, 154, 120, 76, 122, 72, 191, 42, 8, 64, 86, 189, 185, 75, 105,\n  37, 155, 123, 165, 158, 4, 42, 222, 13, 135, 52, 87, 154, 181, 227,\n]);\nconst hexKey =\n  'b9ff919a784c7a48bf2a084056bdb94b69259b7ba59e042ade0d8734579ab5e3';\nconst keyMd5 = 'WGR5pEm07DroP3hYRAh8Yw==';\nconst conditionalDate = '946684800000';\n\n// Test checksums - known values for testing\nconst md5Buffer = new Uint8Array([\n  0x9a, 0x03, 0x64, 0xb9, 0xe9, 0x9b, 0xb4, 0x80, 0xdd, 0x25, 0xe1, 0xf0, 0x28,\n  0x4c, 0x85, 0x55,\n]);\n// Test SHA1 checksum\nconst sha1Buffer = new Uint8Array([\n  0x2a, 0x03, 0x64, 0xb9, 0xe9, 0x9b, 0xb4, 0x80, 0xdd, 0x25, 0xe1, 0xf0, 0x28,\n  0x4c, 0x85, 0x55, 0x11, 0x22, 0x33, 0x44,\n]);\n// Test SHA256 checksum\nconst sha256Buffer = new Uint8Array([\n  0x3a, 0x03, 0x64, 0xb9, 0xe9, 0x9b, 0xb4, 0x80, 0xdd, 0x25, 0xe1, 0xf0, 0x28,\n  0x4c, 0x85, 0x55, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa,\n  0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00,\n]);\nconst objResponse = {\n  name: key,\n  version: 'objectVersion',\n  size: '123',\n  etag: 'objectEtag',\n  uploaded: '1724767257918',\n  storageClass: 'Standard',\n};\nconst HeadObject = {\n  ssecKeyMd5: undefined,\n  storageClass: 'Standard',\n  range: undefined,\n  customMetadata: {},\n  httpMetadata: {},\n  uploaded: new Date(Number(objResponse.uploaded)),\n  checksums: {\n    sha512: undefined,\n    sha384: undefined,\n    sha256: undefined,\n    sha1: undefined,\n    md5: undefined,\n  },\n  httpEtag: '\"objectEtag\"',\n  etag: 'objectEtag',\n  size: 123,\n  version: 'objectVersion',\n  key,\n};\n\nfunction buildGetResponse({ head, body, isList } = {}) {\n  const encoder = new TextEncoder();\n  let meta;\n  if (!isList) {\n    meta = {\n      ...objResponse,\n    };\n  }\n  meta = {\n    ...meta,\n    ...head,\n  };\n  const metadata = encoder.encode(JSON.stringify(meta));\n  const responseBody = body\n    ? new ReadableStream({\n        start(controller) {\n          controller.enqueue(metadata);\n          controller.enqueue(encoder.encode(body));\n          controller.close();\n        },\n      })\n    : metadata;\n  return new Response(responseBody, {\n    headers: {\n      'cf-r2-metadata-size': metadata.length.toString(),\n      'content-length': metadata.length.toString(),\n    },\n  });\n}\nasync function compareResponse(res, { head, body } = {}, bytes) {\n  // Destructuring syntax looks ugly, but gets around needing to construct HeadResponse objects(somehow?)\n  const { ...obj } = await res;\n  obj.checksums = { ...obj.checksums };\n  assert.deepEqual(obj, {\n    ...HeadObject,\n    ...head,\n  });\n  if (body) {\n    if (bytes) {\n      const expected = new Uint8Array(new TextEncoder().encode(body));\n      const actual = await (await res).bytes();\n      assert.equal(expected.byteLength, actual.byteLength);\n      for (let i = 0; i < expected.byteLength; i++) {\n        assert.equal(expected[i], actual[i]);\n      }\n      return;\n    }\n    assert.strictEqual(await (await res).text(), body);\n  }\n}\n\nexport default {\n  // Handler for HTTP request binding makes to R2\n  async fetch(request, env, ctx) {\n    // We only expect PUT/Get\n    assert(['GET', 'PUT'].includes(request.method));\n\n    switch (request.method) {\n      case 'PUT': {\n        // Each request should have a metadata size header indicating how much\n        // we should read to understand what type of request this is\n        const metadataSizeString = request.headers.get('cf-r2-metadata-size');\n        assert.notStrictEqual(metadataSizeString, null);\n\n        const metadataSize = parseInt(metadataSizeString);\n        assert(!Number.isNaN(metadataSize));\n\n        const reader = request.body.getReader({ mode: 'byob' });\n        const jsonArray = new Uint8Array(metadataSize);\n        const { value } = await reader.readAtLeast(metadataSize, jsonArray);\n        reader.releaseLock();\n\n        const jsonRequest = JSON.parse(new TextDecoder().decode(value));\n\n        // Currently not using the body in these test so I'm going to just discard\n        for await (const _ of request.body) {\n        }\n\n        // Assert it's the correct version\n        assert((jsonRequest.version = 1));\n\n        if (jsonRequest.method === 'delete') {\n          if (jsonRequest.objects) {\n            assert.deepEqual(jsonRequest.objects, [key, key + '2']);\n          } else {\n            assert.deepEqual(jsonRequest.object, key);\n          }\n          return new Response();\n        }\n\n        switch (jsonRequest.object) {\n          case 'basicKey': {\n            switch (jsonRequest.method) {\n              case 'put': {\n                break;\n              }\n              case 'createMultipartUpload': {\n                return Response.json({\n                  uploadId: 'multipartId',\n                });\n              }\n              case 'uploadPart': {\n                return Response.json({\n                  etag: 'partEtag',\n                });\n              }\n              case 'abortMultipartUpload': {\n                return new Response();\n              }\n              case 'completeMultipartUpload': {\n                return Response.json(objResponse);\n              }\n            }\n            break;\n          }\n          case 'onlyIfStrongEtag': {\n            assert.deepStrictEqual(jsonRequest.onlyIf, {\n              etagMatches: [\n                {\n                  value: 'strongEtag',\n                  type: 'strong',\n                },\n              ],\n              etagDoesNotMatch: [\n                {\n                  value: 'strongEtag',\n                  type: 'strong',\n                },\n              ],\n              uploadedBefore: conditionalDate,\n            });\n            break;\n          }\n          case 'onlyIfWildcard': {\n            assert.deepStrictEqual(jsonRequest.onlyIf, {\n              etagMatches: [\n                {\n                  type: 'wildcard',\n                },\n              ],\n              etagDoesNotMatch: [\n                {\n                  type: 'wildcard',\n                },\n              ],\n              uploadedAfter: conditionalDate,\n            });\n            break;\n          }\n          case 'httpMetadata': {\n            if (jsonRequest.method !== 'completeMultipartUpload') {\n              assert.deepEqual(jsonRequest.httpFields, httpFields);\n            }\n            const head = {\n              ...objResponse,\n              httpFields,\n            };\n            switch (jsonRequest.method) {\n              case 'put':\n                return Response.json(head);\n              case 'createMultipartUpload':\n                return Response.json({\n                  uploadId: 'multipartId',\n                });\n              case 'completeMultipartUpload': {\n                return Response.json(head);\n              }\n            }\n          }\n          case 'customMetadata': {\n            if (jsonRequest.method !== 'completeMultipartUpload') {\n              assert.deepEqual(jsonRequest.customFields, customFields);\n            }\n            const head = {\n              ...objResponse,\n              customFields,\n            };\n            switch (jsonRequest.method) {\n              case 'put':\n                return Response.json(head);\n              case 'createMultipartUpload':\n                return Response.json({\n                  uploadId: 'multipartId',\n                });\n              case 'completeMultipartUpload':\n                return Response.json(head);\n            }\n          }\n          case 'classDefault': {\n            if (jsonRequest.method !== 'completeMultipartUpload') {\n              assert.strictEqual(jsonRequest.storageClass, undefined);\n            }\n            const head = objResponse;\n            switch (jsonRequest.method) {\n              case 'put':\n                return Response.json(head);\n              case 'createMultipartUpload':\n                return Response.json({\n                  uploadId: 'multipartId',\n                });\n              case 'completeMultipartUpload':\n                return Response.json(head);\n            }\n          }\n          case 'classStandard': {\n            if (jsonRequest.method !== 'completeMultipartUpload') {\n              assert.deepEqual(jsonRequest.storageClass, 'Standard');\n            }\n            const head = {\n              ...objResponse,\n              storageClass: 'Standard',\n            };\n            switch (jsonRequest.method) {\n              case 'put':\n                return Response.json(head);\n              case 'createMultipartUpload':\n                return Response.json({\n                  uploadId: 'multipartId',\n                });\n              case 'completeMultipartUpload':\n                return Response.json(head);\n            }\n          }\n          case 'classInfrequentAccess': {\n            if (jsonRequest.method !== 'completeMultipartUpload') {\n              assert.deepEqual(jsonRequest.storageClass, 'InfrequentAccess');\n            }\n            const head = {\n              ...objResponse,\n              storageClass: 'InfrequentAccess',\n            };\n            switch (jsonRequest.method) {\n              case 'put':\n                return Response.json(head);\n              case 'createMultipartUpload':\n                return Response.json({\n                  uploadId: 'multipartId',\n                });\n              case 'completeMultipartUpload':\n                return Response.json(head);\n            }\n          }\n          case 'ssec': {\n            assert.deepStrictEqual(jsonRequest.ssec, {\n              key: hexKey,\n            });\n            return Response.json({\n              ...objResponse,\n              ssec: {\n                algorithm: 'aes256',\n                keyMd5,\n              },\n            });\n          }\n          case 'ssecMultipart': {\n            if (jsonRequest.method === 'createMultipartUpload') {\n              assert.deepStrictEqual(jsonRequest.ssec, {\n                key: hexKey,\n              });\n              return Response.json({\n                uploadId: 'multipartId',\n              });\n            }\n            if (jsonRequest.method === 'uploadPart') {\n              assert.deepStrictEqual(jsonRequest.ssec, {\n                key: hexKey,\n              });\n              return Response.json({\n                etag: 'partEtag',\n                ssec: {\n                  algorithm: 'aes256',\n                  keyMd5,\n                },\n              });\n            }\n            if (jsonRequest.method === 'completeMultipartUpload') {\n              return Response.json({\n                ...objResponse,\n                ssec: {\n                  algorithm: 'aes256',\n                  keyMd5,\n                },\n              });\n            }\n          }\n          case 'multipleChecksums': {\n            const toHex = (buffer) =>\n              Array.from(buffer, (b) => b.toString(16).padStart(2, '0')).join(\n                ''\n              );\n            return Response.json({\n              ...objResponse,\n              checksums: {\n                0: toHex(md5Buffer), // md5\n                1: toHex(sha1Buffer), // sha1\n                2: toHex(sha256Buffer), // sha256\n              },\n            });\n          }\n        }\n        return Response.json(objResponse);\n      }\n      case 'GET': {\n        const rawHeader = request.headers.get('cf-r2-request');\n        const jsonRequest = JSON.parse(rawHeader);\n        assert((jsonRequest.version = 1));\n        if (jsonRequest.method === 'list') {\n          switch (jsonRequest.prefix) {\n            case 'basic': {\n              assert.deepEqual(jsonRequest, {\n                cursor: 'ai',\n                delimiter: '/',\n                include: [0, 1],\n                limit: 1,\n                method: 'list',\n                newRuntime: true,\n                prefix: 'basic',\n                version: 1,\n              });\n              return buildGetResponse({\n                head: {\n                  objects: [objResponse],\n                  truncated: true,\n                  cursor: 'ai',\n                  deliminatedPrefixes: [],\n                },\n                isList: true,\n              });\n            }\n            case 'httpMeta': {\n              assert.deepEqual(jsonRequest, {\n                include: [0],\n                method: 'list',\n                newRuntime: true,\n                prefix: 'httpMeta',\n                version: 1,\n              });\n\n              return buildGetResponse({\n                head: {\n                  objects: [{ ...objResponse, httpFields, customFields: [] }],\n                  truncated: false,\n                  deliminatedPrefixes: [],\n                },\n                isList: true,\n              });\n            }\n            case 'customMeta': {\n              assert.deepEqual(jsonRequest, {\n                include: [1],\n                method: 'list',\n                newRuntime: true,\n                prefix: 'customMeta',\n                version: 1,\n              });\n\n              return buildGetResponse({\n                head: {\n                  objects: [{ ...objResponse, httpFields: {}, customFields }],\n                  truncated: false,\n                  deliminatedPrefixes: [],\n                },\n                isList: true,\n              });\n            }\n          }\n        }\n        assert(['get', 'head'].includes(jsonRequest.method));\n        switch (jsonRequest.object) {\n          case 'basicKey': {\n            return buildGetResponse({ body });\n          }\n          case 'rangeOffLen': {\n            assert.deepEqual(jsonRequest.range, {\n              offset: '1',\n              length: '3',\n            });\n            return buildGetResponse({\n              head: {\n                range: jsonRequest.range,\n              },\n              body: 'ont',\n            });\n          }\n          case 'rangeSuff': {\n            assert.deepEqual(jsonRequest.range, {\n              suffix: '2',\n            });\n            return buildGetResponse({\n              head: {\n                range: {\n                  offset: '6',\n                  length: '2',\n                },\n              },\n              body: 'nt',\n            });\n          }\n          case 'onlyIfStrongEtag': {\n            assert.deepStrictEqual(jsonRequest.onlyIf, {\n              etagMatches: [\n                {\n                  value: 'strongEtag',\n                  type: 'strong',\n                },\n              ],\n              etagDoesNotMatch: [\n                {\n                  value: 'strongEtag',\n                  type: 'strong',\n                },\n              ],\n              uploadedBefore: conditionalDate,\n            });\n            return buildGetResponse({ body });\n          }\n          case 'onlyIfWildcard': {\n            assert.deepStrictEqual(jsonRequest.onlyIf, {\n              etagMatches: [\n                {\n                  type: 'wildcard',\n                },\n              ],\n              etagDoesNotMatch: [\n                {\n                  type: 'wildcard',\n                },\n              ],\n              uploadedAfter: conditionalDate,\n            });\n            return buildGetResponse({ body });\n          }\n          case 'httpMetadata': {\n            const head = {\n              httpFields,\n            };\n            switch (jsonRequest.method) {\n              case 'head':\n                return buildGetResponse({ head });\n              case 'get':\n                return buildGetResponse({ head, body });\n            }\n          }\n          case 'customMetadata': {\n            const head = {\n              customFields,\n            };\n            switch (jsonRequest.method) {\n              case 'head':\n                return buildGetResponse({ head });\n              case 'get':\n                return buildGetResponse({ head, body });\n            }\n          }\n          case 'classDefault':\n          case 'classStandard': {\n            const head = {\n              storageClass: 'Standard',\n            };\n            switch (jsonRequest.method) {\n              case 'head':\n                return buildGetResponse({ head });\n              case 'get':\n                return buildGetResponse({ head, body });\n            }\n          }\n          case 'classInfrequentAccess': {\n            const head = {\n              storageClass: 'InfrequentAccess',\n            };\n            switch (jsonRequest.method) {\n              case 'head':\n                return buildGetResponse({ head });\n              case 'get':\n                return buildGetResponse({ head, body });\n            }\n          }\n          case 'ssec': {\n            return buildGetResponse({\n              head: {\n                ssec: {\n                  algorithm: 'aes256',\n                  keyMd5,\n                },\n              },\n              body,\n            });\n          }\n          case 'multipleChecksums': {\n            const toHex = (buffer) =>\n              Array.from(buffer, (b) => b.toString(16).padStart(2, '0')).join(\n                ''\n              );\n            return buildGetResponse({\n              head: {\n                checksums: {\n                  0: toHex(md5Buffer), // md5\n                  1: toHex(sha1Buffer), // sha1\n                  2: toHex(sha256Buffer), // sha256\n                },\n              },\n              body: jsonRequest.method === 'get' ? body : undefined,\n            });\n          }\n        }\n        throw new Error('Unexpected GET');\n      }\n      default:\n        throw new Error('Unexpected HTTP Method');\n    }\n  },\n  async test(ctrl, env, ctx) {\n    // Basic Operations\n    {\n      // PutObject\n      await compareResponse(env.BUCKET.put(key, body));\n      // GetObject\n      await compareResponse(env.BUCKET.get(key), {\n        body,\n      });\n      // GetObject(.bytes())\n      await compareResponse(env.BUCKET.get(key), { body }, true);\n      // HeadObject\n      await compareResponse(env.BUCKET.head(key));\n      // MultipartUploads\n      {\n        // CreateMultipartUpload\n        const multi = await env.BUCKET.createMultipartUpload(key);\n        assert.equal(multi.uploadId, 'multipartId');\n        assert.equal(multi.key, key);\n        // UploadPart\n        const part = await multi.uploadPart(1, body);\n        assert.equal(part.etag, 'partEtag');\n        // Abort(doesn't quite make sense to abort **and** complete, but shouldn't matter)\n        await multi.abort();\n        // CompleteMultipartUpload\n        await compareResponse(\n          multi.complete([\n            {\n              partNumber: 1,\n              etag: 'partEtag',\n            },\n          ])\n        );\n      }\n      // ListObjects\n      {\n        const list = await env.BUCKET.list({\n          limit: 1,\n          prefix: 'basic',\n          cursor: 'ai',\n          delimiter: '/',\n          include: ['httpMetadata', 'customMetadata'],\n        });\n        list.objects[0] = { ...list.objects[0] };\n        list.objects[0].checksums = { ...list.objects[0].checksums };\n        assert.deepEqual(list, {\n          objects: [HeadObject],\n          truncated: true,\n          cursor: 'ai',\n          delimitedPrefixes: [],\n        });\n      }\n      // DeleteObject\n      {\n        await env.BUCKET.delete(key);\n        await env.BUCKET.delete([key, 'basicKey2']);\n      }\n    }\n    // Ranged Reads\n    {\n      // Offset/Length\n      {\n        const range = {\n          offset: 1,\n          length: 3,\n        };\n        await compareResponse(\n          env.BUCKET.get('rangeOffLen', {\n            range,\n          }),\n          {\n            head: { range },\n          },\n          'ont'\n        );\n      }\n      // Suffix\n      await compareResponse(\n        env.BUCKET.get('rangeSuff', {\n          range: {\n            suffix: 2,\n          },\n        }),\n        {\n          head: {\n            range: {\n              offset: 6,\n              length: 2,\n            },\n          },\n        },\n        'nt'\n      );\n    }\n    // Conditionals\n    {\n      try {\n        await env.BUCKET.put('throwOnInvalidEtag', body, {\n          onlyIf: new Headers({\n            'if-match': 'strongEtag',\n          }),\n        });\n        throw new Error('This should have thrown');\n      } catch {}\n      try {\n        await env.BUCKET.put('throwOnInvalidEtag', body, {\n          onlyIf: new Headers({\n            'if-none-match': 'strongEtag',\n          }),\n        });\n        throw new Error('This should have thrown');\n      } catch {}\n      await env.BUCKET.put('onlyIfStrongEtag', body, {\n        onlyIf: {\n          etagMatches: 'strongEtag',\n          etagDoesNotMatch: 'strongEtag',\n          uploadedBefore: new Date('0'),\n        },\n      });\n      await env.BUCKET.put('onlyIfWildcard', body, {\n        onlyIf: {\n          etagMatches: '*',\n          etagDoesNotMatch: '*',\n          uploadedAfter: new Date('0'),\n        },\n      });\n      await env.BUCKET.get('onlyIfStrongEtag', {\n        onlyIf: {\n          etagMatches: 'strongEtag',\n          etagDoesNotMatch: 'strongEtag',\n          uploadedBefore: new Date('0'),\n        },\n      });\n      await env.BUCKET.get('onlyIfWildcard', {\n        onlyIf: {\n          etagMatches: '*',\n          etagDoesNotMatch: '*',\n          uploadedAfter: new Date('0'),\n        },\n      });\n    }\n    // Metadata\n    {\n      // httpMetadata\n      for (const httpMetadata of [httpMetaObj, httpMetaHeaders]) {\n        const head = {\n          httpMetadata: httpMetaObj,\n        };\n        // PutObject\n        await compareResponse(\n          env.BUCKET.put('httpMetadata', body, {\n            httpMetadata,\n          }),\n          { head }\n        );\n        // HeadObject\n        await compareResponse(env.BUCKET.head('httpMetadata'), { head });\n        // GetObject\n        {\n          const objWithBody = await env.BUCKET.get('httpMetadata');\n          await compareResponse(objWithBody, { head, body });\n          // Hijacking this test to test `writeHttpMetadata` too\n          const createdHeaders = new Headers();\n          objWithBody.writeHttpMetadata(createdHeaders);\n          assert.deepEqual(\n            Object.fromEntries(createdHeaders.entries()),\n            Object.fromEntries(httpMetaHeaders.entries())\n          );\n        }\n        // ListObjects\n        {\n          const list = await env.BUCKET.list({\n            prefix: 'httpMeta',\n            include: ['httpMetadata'],\n          });\n          list.objects[0] = { ...list.objects[0] };\n          list.objects[0].checksums = { ...list.objects[0].checksums };\n          assert.deepEqual(list, {\n            delimitedPrefixes: [],\n            objects: [{ ...HeadObject, ...head }],\n            truncated: false,\n          });\n        }\n        // Multipart Upload\n        await compareResponse(\n          (\n            await env.BUCKET.createMultipartUpload('httpMetadata', {\n              httpMetadata,\n            })\n          ).complete([]),\n          { head }\n        );\n      }\n      // customMetadata\n      {\n        // PutObject\n        const head = {\n          customMetadata,\n        };\n        await compareResponse(\n          env.BUCKET.put('customMetadata', body, {\n            customMetadata,\n          }),\n          { head }\n        );\n        // HeadObject\n        await compareResponse(await env.BUCKET.head('customMetadata'), {\n          head,\n        });\n        // GetObject\n        await compareResponse(await env.BUCKET.get('customMetadata'), {\n          head,\n          body,\n        });\n        // ListObjects\n        {\n          const list = await env.BUCKET.list({\n            prefix: 'customMeta',\n            include: ['customMetadata'],\n          });\n          list.objects[0] = { ...list.objects[0] };\n          list.objects[0].checksums = { ...list.objects[0].checksums };\n          assert.deepEqual(list, {\n            delimitedPrefixes: [],\n            objects: [{ ...HeadObject, ...head }],\n            truncated: false,\n          });\n        }\n        // Multipart Upload\n        await compareResponse(\n          (\n            await env.BUCKET.createMultipartUpload('customMetadata', {\n              customMetadata,\n            })\n          ).complete([]),\n          { head }\n        );\n      }\n    }\n    // StorageClasses\n    {\n      for (const storageClassName of [\n        'Default',\n        'Standard',\n        'InfrequentAccess',\n      ]) {\n        const key = 'class' + storageClassName;\n        const storageClass =\n          storageClassName === 'Default' ? undefined : storageClassName;\n        const head = {\n          storageClass:\n            storageClassName === 'Default' ? 'Standard' : storageClassName,\n        };\n        // PutObject\n        await compareResponse(\n          env.BUCKET.put(key, body, {\n            storageClass,\n          }),\n          { head }\n        );\n        // HeadObject\n        await compareResponse(env.BUCKET.head(key), { head });\n        // GetObject\n        await compareResponse(env.BUCKET.get(key), { head, body });\n        // Multipart Upload\n        await compareResponse(\n          (\n            await env.BUCKET.createMultipartUpload(key, {\n              storageClass,\n            })\n          ).complete([]),\n          { head }\n        );\n      }\n    }\n    // SSEC\n    {\n      const head = {\n        ssecKeyMd5: keyMd5,\n      };\n      for (const ssecKey of [bufferKey, hexKey]) {\n        // PutObject\n        await compareResponse(\n          env.BUCKET.put('ssec', body, {\n            ssecKey,\n          }),\n          { head }\n        );\n        // GetObject\n        await compareResponse(\n          env.BUCKET.get('ssec', {\n            ssecKey,\n          }),\n          { head, body }\n        );\n        // HeadObject\n        await compareResponse(\n          env.BUCKET.head('ssec', {\n            ssecKey,\n          }),\n          { head }\n        );\n        // MultipartUpload\n        {\n          // CreateMultipartUpload\n          const multi = await env.BUCKET.createMultipartUpload(\n            'ssecMultipart',\n            {\n              ssecKey,\n            }\n          );\n          assert.equal(multi.uploadId, 'multipartId');\n          // UploadPart\n          const part = await multi.uploadPart(1, 'hey', {\n            ssecKey,\n          });\n          assert.equal(part.etag, 'partEtag');\n          // CompleteMultipartUpload\n          await compareResponse(\n            multi.complete([\n              {\n                partNumber: 1,\n                etag: 'partEtag',\n              },\n            ]),\n            { head }\n          );\n        }\n      }\n    }\n    // Checksums\n    {\n      // This tests the instrumentation with multiple checksums to ensure proper tag handling\n      let resp = await env.BUCKET.put('multipleChecksums', body, {\n        md5: md5Buffer,\n      });\n      assert.ok(resp);\n\n      // Also test HEAD operation to verify checksum tags\n      const headResp = await env.BUCKET.head('multipleChecksums');\n      assert.ok(headResp);\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/r2-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"r2-test\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"r2-test.js\" )\n        ],\n        bindings = [\n          ( name = \"BUCKET\", r2Bucket = \"r2-test\" ),\n          ( name = \"SERVICE\", service = \"r2-test\" ),\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"service_binding_extra_handlers\", \"streams_enable_constructors\", \"r2_list_honor_include\", \"disable_fast_jsg_struct\"],\n        streamingTails = [\"tail\"],\n      )\n    ),\n    (name = \"tail\", worker = .tailWorker, ),\n  ],\n  extensions = [ (\n    modules = [\n      ( name = \"test:instrumentation-tail\", esModule = embed \"instrumentation-tail-worker.js\" ),\n    ]\n  ) ]\n);\n\nconst tailWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"r2-instrumentation-test.js\")\n  ],\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\", \"disable_fast_jsg_struct\"],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/reporterror-test.js",
    "content": "import { mock } from 'node:test';\nimport { strictEqual, throws } from 'node:assert';\n\nconst boom = new Error('boom');\n\nconst handler = mock.fn((event) => {\n  if (event.error instanceof Error) {\n    strictEqual(event.message, 'Uncaught Error: boom');\n    strictEqual(event.colno, 13);\n    strictEqual(event.lineno, 4);\n    strictEqual(event.filename, 'worker');\n    strictEqual(event.error, boom);\n  } else {\n    strictEqual(event.message, 'Uncaught boom');\n    strictEqual(event.colno, 0);\n    strictEqual(event.lineno, 25);\n    strictEqual(event.filename, 'worker');\n    strictEqual(event.error, 'boom');\n  }\n  return true;\n});\n\naddEventListener('error', handler);\n\nreportError('boom');\n\nthrows(() => reportError(), {\n  message:\n    \"Failed to execute 'reportError' on 'ServiceWorkerGlobalScope': \" +\n    \"parameter 1 is not of type 'JsValue'.\",\n});\n\nexport const reportErrorTest = {\n  test() {\n    // TODO(soon): We are limited in what we can test here because we cannot\n    // inspect the log output and workerd does not implement that WorkerTracer\n    // used for collecting data for tail workers. The best we can currently do\n    // is make sure the basic API is working and that the mock fn was called.\n    reportError(boom);\n    strictEqual(handler.mock.calls.length, 2);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/reporterror-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"reporterror-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"reporterror-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/request-clone-test.js",
    "content": "import { deepStrictEqual } from 'node:assert';\n\n// This test validates tee'ing value-oriented js-backed readable streams\n// as there is no problem with byte-oriented systems.\nexport const clone = {\n  async test() {\n    function createReadableStreamFromString(str) {\n      const encoder = new TextEncoder();\n      const encodedData = encoder.encode(str);\n\n      return new ReadableStream({\n        start(controller) {\n          // Enqueue the data and close the stream\n          controller.enqueue(encodedData);\n          controller.close();\n        },\n      });\n    }\n\n    const cr = new Request(new URL('http://localhost/test'), {\n      method: 'POST',\n      body: createReadableStreamFromString('Hello stream'),\n      duplex: 'half',\n    });\n\n    const cr2 = cr.clone();\n\n    deepStrictEqual(await cr.text(), await cr2.text());\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/request-clone-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"request-clone-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"request-clone-test.js\" )\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"streams_enable_constructors\"],\n      )\n    )\n  ]\n);\n"
  },
  {
    "path": "src/workerd/api/tests/request-signal-disabled.js",
    "content": "import { WorkerEntrypoint } from 'cloudflare:workers';\nimport assert from 'node:assert';\nexport class OtherServer extends WorkerEntrypoint {\n  async fetch() {\n    await scheduler.wait(300);\n    return new Response('completed');\n  }\n\n  async rpcEcho(req) {\n    return req;\n  }\n}\nexport class Server extends WorkerEntrypoint {\n  async fetch(req) {\n    const resSameRequest = await this.env.OtherServer.rpcEcho(req);\n    const resNewRequest = await this.env.OtherServer.rpcEcho(new Request(req));\n    const resClonedRequest = await this.env.OtherServer.rpcEcho(req.clone());\n\n    return new Response('ok');\n  }\n}\n\nexport const incomingRequestSignalCanBeCloned = {\n  async test(ctrl, env, ctx) {\n    const req = env.Server.fetch('http://example.com/cloneIncomingRequest');\n    const res = await req;\n    assert.strictEqual(await res.text(), 'ok');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/request-signal-disabled.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"request-signal-disabled\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"request-signal-disabled.js\" )\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"disable_request_signal\", \"experimental\"],\n        bindings = [\n          (name = \"OtherServer\", service = (name = \"request-signal-disabled\", entrypoint = \"OtherServer\")),\n          (name = \"Server\", service = (name = \"request-signal-disabled\", entrypoint = \"Server\")),\n          (name = \"defaultExport\", service = \"request-signal-disabled\"),\n        ]\n      )\n    )\n  ]\n);\n\n"
  },
  {
    "path": "src/workerd/api/tests/request-signal-enabled.js",
    "content": "import { DurableObject, WorkerEntrypoint } from 'cloudflare:workers';\nimport assert from 'node:assert';\n\nexport class AbortTracker extends DurableObject {\n  async getAborted(key) {\n    return this.ctx.storage.get(key);\n  }\n  async setAborted(key, value) {\n    await this.ctx.storage.put(key, value);\n  }\n}\n\nlet reqSignalReason = null;\nlet rpcSignalReason = null;\nexport class OtherServer extends WorkerEntrypoint {\n  async fetch(req) {\n    await scheduler.wait(300);\n    return new Response('completed');\n  }\n\n  async echo(val) {\n    return val;\n  }\n\n  async saveReqReason(req) {\n    rpcSignalReason = req.signal.reason?.message;\n  }\n}\n\nexport class Server extends WorkerEntrypoint {\n  async fetch(req) {\n    const key = new URL(req.url).pathname.slice(1);\n    let abortTracker = this.env.AbortTracker.get(\n      this.env.AbortTracker.idFromName('AbortTracker')\n    );\n    await abortTracker.setAborted(key, false);\n\n    req.signal.onabort = () => {\n      this.ctx.waitUntil(abortTracker.setAborted(key, true));\n    };\n\n    return this[key](req);\n  }\n\n  async valid() {\n    return new Response('hello world');\n  }\n\n  async error() {\n    throw new Error('boom');\n  }\n\n  async hang() {\n    for (;;) {\n      await scheduler.wait(86400);\n    }\n  }\n\n  async hangAfterSendingSomeData() {\n    const { readable, writable } = new IdentityTransformStream();\n    this.ctx.waitUntil(this.sendSomeData(writable));\n\n    return new Response(readable);\n  }\n\n  async sendSomeData(writable) {\n    const writer = writable.getWriter();\n    const enc = new TextEncoder();\n    await writer.write(enc.encode('hello world'));\n    await this.hang();\n  }\n\n  async triggerSubrequest(req) {\n    this.ctx.waitUntil(this.callOtherServer(req));\n    await this.hang();\n  }\n\n  async callOtherServer(req) {\n    const key = 'subrequest';\n\n    let abortTracker = this.env.AbortTracker.get(\n      this.env.AbortTracker.idFromName('AbortTracker')\n    );\n\n    const passedThroughReq = new Request(req);\n    passedThroughReq.onabort = () => {\n      this.ctx.waitUntil(abortTracker.setAborted(key, true));\n    };\n\n    const res = await this.env.OtherServer.fetch(passedThroughReq);\n    const text = await res.text();\n\n    if (text == 'completed') {\n      await abortTracker.setAborted(key, false);\n    }\n  }\n\n  async passIncomingRequestOverRpc(req) {\n    // req.signal is in the \"this signal\" slot. It's an incoming request signal so it has the\n    // IGNORE_FOR_SUBREQUESTS flag set.\n    req.signal.onabort = () => {\n      // If we're here we know that the reason is filled in on req.signal\n      reqSignalReason = req.signal.reason.message;\n\n      // Ensure that the incoming request signal is in the \"signal\" slot, which is the one we\n      // actually serialize currently.\n      const newReq = req.clone();\n\n      // Since the signal is not included in serialization, OtherServer won't be able to read the\n      // reason.\n      this.ctx.waitUntil(this.env.OtherServer.saveReqReason(newReq));\n    };\n\n    // Just hang and wait for the client to give up\n    await scheduler.wait(86400);\n  }\n}\n\nexport const noAbortOnSimpleResponse = {\n  async test(ctrl, env, ctx) {\n    let abortTracker = env.AbortTracker.get(\n      env.AbortTracker.idFromName('AbortTracker')\n    );\n\n    const req = env.Server.fetch('http://example.com/valid');\n\n    const res = await req;\n    assert.strictEqual(await res.text(), 'hello world');\n    assert.strictEqual(await abortTracker.getAborted('valid'), false);\n  },\n};\n\nexport const noAbortIfServerThrows = {\n  async test(ctrl, env, ctx) {\n    let abortTracker = env.AbortTracker.get(\n      env.AbortTracker.idFromName('AbortTracker')\n    );\n\n    const req = env.Server.fetch('http://example.com/error');\n\n    await assert.rejects(() => req, { name: 'Error', message: 'boom' });\n    assert.strictEqual(await abortTracker.getAborted('error'), false);\n  },\n};\n\nexport const abortIfClientAbandonsRequest = {\n  async test(ctrl, env, ctx) {\n    let abortTracker = env.AbortTracker.get(\n      env.AbortTracker.idFromName('AbortTracker')\n    );\n\n    // This endpoint never generates a response, so we can timeout after an arbitrary time.\n    const req = env.Server.fetch('http://example.com/hang', {\n      signal: AbortSignal.timeout(500),\n    });\n\n    await assert.rejects(() => req, {\n      name: 'TimeoutError',\n      message: 'The operation was aborted due to timeout',\n    });\n    assert.strictEqual(await abortTracker.getAborted('hang'), true);\n  },\n};\n\nexport const abortIfClientCancelsReadingResponse = {\n  async test(ctrl, env, ctx) {\n    let abortTracker = env.AbortTracker.get(\n      env.AbortTracker.idFromName('AbortTracker')\n    );\n\n    // This endpoint begins generating a response but then hangs\n    const req = env.Server.fetch('http://example.com/hangAfterSendingSomeData');\n    const res = await req;\n    const reader = res.body.getReader();\n\n    const { value, done } = await reader.read();\n    assert.strictEqual(new TextDecoder().decode(value), 'hello world');\n    assert.ok(!done);\n\n    // Give up reading\n    await reader.cancel();\n\n    // Waste a bit of time so the server cleans up\n    await scheduler.wait(0);\n\n    assert.strictEqual(\n      await abortTracker.getAborted('hangAfterSendingSomeData'),\n      true\n    );\n  },\n};\n\nexport const abortedRequestDoesNotAbortSubrequest = {\n  async test(ctrl, env, ctx) {\n    let abortTracker = env.AbortTracker.get(\n      env.AbortTracker.idFromName('AbortTracker')\n    );\n\n    // This endpoint calls another endpoint that eventually completes after wasting 300 ms\n    // So, we abort the initial request quickly...\n    const req = env.Server.fetch('http://example.com/triggerSubrequest', {\n      signal: AbortSignal.timeout(100),\n    });\n\n    await assert.rejects(() => req, {\n      name: 'TimeoutError',\n      message: 'The operation was aborted due to timeout',\n    });\n    assert.strictEqual(\n      await abortTracker.getAborted('triggerSubrequest'),\n      true\n    );\n\n    // Then make sure that the subrequest wasn't also aborted\n    await scheduler.wait(500);\n    assert.strictEqual(await abortTracker.getAborted('subrequest'), false);\n  },\n};\n\nexport const requestPassedOverRpcDoesNotIncludeSignal = {\n  async test(ctrl, env, ctx) {\n    // This test covers the case when the `request_signal_passthrough` compat flag is not set.\n    // The incoming request's signal is not serialized.\n    // See request-signal-psssthrough.js for the corresponding test when the flag is set.\n\n    await assert.rejects(\n      () =>\n        env.Server.fetch('http://example.com/passIncomingRequestOverRpc', {\n          signal: AbortSignal.timeout(100),\n        }),\n      { message: 'The operation was aborted due to timeout' }\n    );\n\n    // Yield so the workers can write the abort reasons\n    await scheduler.wait(0);\n\n    assert.strictEqual(reqSignalReason, 'The client has disconnected');\n    assert.strictEqual(rpcSignalReason, undefined);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/request-signal-enabled.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"request-signal-enabled\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"request-signal-enabled.js\" )\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"enable_request_signal\", \"experimental\", \"rpc\", \"unwrap_custom_thenables\"],\n        durableObjectNamespaces = [\n          (className = \"AbortTracker\", uniqueKey = \"badbeef\"),\n        ],\n        durableObjectStorage = (inMemory = void),\n        bindings = [\n          (name = \"AbortTracker\", durableObjectNamespace = \"AbortTracker\"),\n          (name = \"OtherServer\", service = (name = \"request-signal-enabled\", entrypoint = \"OtherServer\")),\n          (name = \"Server\", service = (name = \"request-signal-enabled\", entrypoint = \"Server\")),\n          (name = \"defaultExport\", service = \"request-signal-enabled\"),\n        ]\n      )\n    )\n  ]\n);\n\n"
  },
  {
    "path": "src/workerd/api/tests/request-signal-passthrough.js",
    "content": "import { WorkerEntrypoint } from 'cloudflare:workers';\nimport assert from 'node:assert';\n\nlet reqSignalReason = null;\nlet rpcSignalReason = null;\n\nexport class OtherServer extends WorkerEntrypoint {\n  async echo(val) {\n    return val;\n  }\n\n  async saveReqReason(req) {\n    rpcSignalReason = req.signal.reason?.message;\n  }\n}\n\nexport class Server extends WorkerEntrypoint {\n  async fetch(req) {\n    // req.signal is in the \"this signal\" slot. Because request_signal_passthrough is set, there is\n    // no special handling.\n    req.signal.onabort = () => {\n      // If we're here we know that the reason is filled in on req.signal\n      reqSignalReason = req.signal.reason.message;\n\n      // Ensure that the incoming request signal is in the \"signal\" slot, which is the one we\n      // actually serialize currently.\n      const newReq = req.clone();\n\n      // And if the signal really is included in serialization, OtherServer will be able to read\n      // the reason.\n      this.ctx.waitUntil(this.env.OtherServer.saveReqReason(newReq));\n    };\n\n    // Just hang and wait for the client to give up\n    await scheduler.wait(86400);\n  }\n}\n\nexport const requestPassedOverRpcIncludesSignal = {\n  async test(ctrl, env, ctx) {\n    await assert.rejects(\n      () =>\n        env.Server.fetch('http://example.com', {\n          signal: AbortSignal.timeout(100),\n        }),\n      { message: 'The operation was aborted due to timeout' }\n    );\n\n    // Yield so the workers can write the abort reasons\n    await scheduler.wait(0);\n\n    assert.strictEqual(reqSignalReason, 'The client has disconnected');\n    assert.strictEqual(rpcSignalReason, 'The client has disconnected');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/request-signal-passthrough.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"request-signal-passthrough\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"request-signal-passthrough.js\" )\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"enable_request_signal\", \"request_signal_passthrough\", \"enable_abortsignal_rpc\", \"experimental\", \"rpc\", \"unwrap_custom_thenables\"],\n        bindings = [\n          (name = \"OtherServer\", service = (name = \"request-signal-passthrough\", entrypoint = \"OtherServer\")),\n          (name = \"Server\", service = (name = \"request-signal-passthrough\", entrypoint = \"Server\")),\n          (name = \"defaultExport\", service = \"request-signal-passthrough\"),\n        ]\n      )\n    )\n  ]\n);\n\n"
  },
  {
    "path": "src/workerd/api/tests/response-json.js",
    "content": "import { strictEqual, ok, deepStrictEqual, throws } from 'node:assert';\n\nexport const basics = {\n  async test() {\n    const response = Response.json({ a: 1 });\n    ok(response instanceof Response);\n    strictEqual(response.headers.get('content-type'), 'application/json');\n    strictEqual(await response.text(), '{\"a\":1}');\n  },\n};\n\nexport const headers = {\n  async test() {\n    const response = Response.json(\n      { a: 1 },\n      {\n        headers: [['content-type', 'abc']],\n      }\n    );\n    ok(response instanceof Response);\n    strictEqual(response.headers.get('content-type'), 'abc');\n    deepStrictEqual(await response.json(), { a: 1 });\n  },\n};\n\nexport const headers2 = {\n  async test() {\n    const headers = new Headers();\n    headers.set('content-type', 'abc');\n    const response = Response.json({ a: 1 }, { headers });\n    ok(response instanceof Response);\n    strictEqual(response.headers.get('content-type'), 'abc');\n    deepStrictEqual(await response.json(), { a: 1 });\n  },\n};\n\nexport const headers3 = {\n  async test() {\n    const other = new Response();\n    other.headers.set('content-type', 'abc');\n    const response = Response.json({ a: 1 }, other);\n    ok(response instanceof Response);\n    strictEqual(response.headers.get('content-type'), 'abc');\n    deepStrictEqual(await response.json(), { a: 1 });\n  },\n};\n\nexport const headers4 = {\n  async test() {\n    const response = Response.json({ a: 1 }, {});\n    ok(response instanceof Response);\n    strictEqual(response.headers.get('content-type'), 'application/json');\n    deepStrictEqual(await response.json(), { a: 1 });\n  },\n};\n\nexport const notJsonSerializable = {\n  async test() {\n    // Some values are not JSON serializable...\n    throws(() => Response.json({ a: 1n }));\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/response-json.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"response-json\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"response-json.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/response-used-body-test.js",
    "content": "import { rejects } from 'assert';\n\n// Ported from the internal respond.ew-test\n\nexport const abortInternalStreamsTest = {\n  async test(_, env) {\n    await rejects(env.subrequest.fetch('http://example.org'), {\n      message: /Body has already been used/,\n    });\n  },\n};\n\nexport default {\n  async fetch() {\n    const enc = new TextEncoder();\n    const rs = new ReadableStream({\n      pull(c) {\n        c.enqueue(enc.encode('hello'));\n      },\n    });\n    const response = new Response(rs);\n    rs.cancel(new Error('boom'));\n    return response;\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/response-used-body-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"response-used-body-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"response-used-body-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat_v2\", \"streams_enable_constructors\"],\n        bindings = [\n          (name = \"subrequest\", service = \"response-used-body-test\")\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/rpc-error-test.js",
    "content": "import { notStrictEqual, ok, strictEqual, throws } from 'assert';\n\nexport const test = {\n  async test(_, env) {\n    try {\n      await env.RPC.foo(null);\n      throw new Error('Expected error not thrown');\n    } catch (e) {\n      // The error returned by RPC will have a stack2 property that contains\n      // the original stack from the worker where the error was thrown.\n      notStrictEqual(e.stack2, undefined);\n      // Because we are not setting the trusted option when deserializing the\n      // error, the stack of the error reported here should be different from\n      // the original stack.\n      notStrictEqual(e.stack2, e.stack);\n    }\n\n    try {\n      const bar = await env.RPC.bar(null);\n      const baz = await bar.baz(null);\n      await baz.qux(null);\n      throw new Error('Expected error not thrown');\n    } catch (e) {\n      // The error returned by RPC will have a stack2 property that contains\n      // the original stack from the worker where the error was thrown.\n      notStrictEqual(e.stack2, undefined);\n      // Because we are not setting the trusted option when deserializing the\n      // error, the stack of the error reported here should be different from\n      // the original stack.\n      notStrictEqual(e.stack2, e.stack);\n    }\n\n    try {\n      await env.RPC.xyz(null);\n      throw new Error('Expected error not thrown');\n    } catch (e) {\n      // The error returned by RPC will have a stack2 property that contains\n      // the original stack from the worker where the error was thrown.\n      notStrictEqual(e.stack2, undefined);\n      // Because we are not setting the trusted option when deserializing the\n      // error, the stack of the error reported here should be different from\n      // the original stack.\n      notStrictEqual(e.stack2, e.stack);\n    }\n\n    {\n      const err = await env.RPC.abc(null).a;\n      // The error returned by RPC will have a stack2 property that contains\n      // the original stack from the worker where the error was thrown.\n      notStrictEqual(err.stack2, undefined);\n      // Because we are not setting the trusted option when deserializing the\n      // error, the stack of the error reported here should be different from\n      // the original stack.\n      notStrictEqual(err.stack2, err.stack);\n    }\n  },\n};\n\nexport let structuredCloneNotBroken = {\n  test(controller, env, ctx) {\n    // At one point, enhanced_error_serialization inadvertently broke structuredClone()'s support\n    // for host objects.\n    let orig = new Headers({ foo: '123', bar: 'abc' });\n    let cloned = structuredClone(orig);\n    ok(cloned instanceof Headers);\n    strictEqual(cloned.get('foo'), '123');\n    strictEqual(cloned.get('bar'), 'abc');\n\n    throws(() => structuredClone(new TextEncoder()), {\n      name: 'DataCloneError',\n      code: DOMException.DATA_CLONE_ERR,\n      message:\n        'Could not serialize object of type \"TextEncoder\". This type does not support ' +\n        'serialization.',\n    });\n  },\n};\n\n// DOMException is structured cloneable\nexport let domExceptionClone = {\n  test() {\n    const de1 = new DOMException('hello', 'NotAllowedError');\n\n    de1.foo = 'abc';\n\n    const de2 = structuredClone(de1);\n    ok(de2 instanceof DOMException);\n    strictEqual(de1.name, de2.name);\n    strictEqual(de1.message, de2.message);\n    strictEqual(de1.stack, de2.stack);\n    strictEqual(de1.code, de2.code);\n    notStrictEqual(de1, de2);\n\n    // TODO(bug): This is supposed to work, but currently doesn't because the \"native error\"\n    //   handling in jsg::Serializer does not kick in for DOMException. Instead, DOMException's\n    //   own serializer (in dom-exception.c++) is used, and it hasn't been updated to handle\n    //   serializing application properties.\n    // strictEqual(de1.foo, de2.foo);\n    // strictEqual(de2.foo, 'abc');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/rpc-error-test.rpc.js",
    "content": "import { RpcTarget } from 'cloudflare:workers';\n\nclass Baz extends RpcTarget {\n  async qux() {\n    await scheduler.wait(10);\n    const err = new Error('bork');\n    err.stack2 = err.stack;\n    throw err;\n  }\n}\n\nclass Bar extends RpcTarget {\n  async baz() {\n    await scheduler.wait(10);\n    return new Baz();\n  }\n}\n\nexport default {\n  foo() {\n    const err = new Error('boom');\n    err.stack2 = err.stack;\n    throw err;\n  },\n\n  async bar() {\n    await scheduler.wait(10);\n    return new Bar();\n  },\n\n  xyz() {\n    const err = new Error('bang');\n    err.stack2 = err.stack;\n    return Promise.reject(err);\n  },\n\n  abc() {\n    const err = new Error('boom');\n    err.stack2 = err.stack;\n    return { a: err };\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/rpc-error-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"rpc-error-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"rpc-error-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat_v2\", \"enhanced_error_serialization\", \"rpc\"],\n        bindings = [\n          ( name = \"RPC\", service = \"rpc-target\" ),\n        ]\n      )\n    ),\n    ( name = \"rpc-target\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"rpc-error-test.rpc.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat_v2\", \"enhanced_error_serialization\", \"rpc\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/rtti-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'node:assert';\nimport rtti from 'workerd:rtti';\n\nexport default {\n  async test(ctrl, env, ctx) {\n    const buffer = rtti.exportTypes('2023-05-18', ['nodejs_compat']);\n    assert(buffer.byteLength > 0);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/rtti-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"rtti-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"rtti-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"rtti_api\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/scheduler-test.js",
    "content": "import { deepStrictEqual, strictEqual, ok } from 'node:assert';\n\n// Test for the Event and EventTarget standard Web API implementations.\n// The implementation for these are in api/basics.{h|c++}\n\nexport const wait = {\n  async test(ctrl, env, ctx) {\n    strictEqual(typeof scheduler, 'object');\n    strictEqual(typeof scheduler.wait, 'function');\n\n    // regular wait...\n    await scheduler.wait(10);\n\n    // get's coerced to a number value (in this case 0)\n    await scheduler.wait('foo');\n\n    // waiting can be canceled\n    try {\n      await scheduler.wait(100000, { signal: AbortSignal.timeout(100) });\n      throw new Error('should have thrown');\n    } catch (err) {\n      strictEqual(err.message, 'The operation was aborted due to timeout');\n    }\n\n    try {\n      await scheduler.wait(100000, { signal: AbortSignal.abort() });\n      throw new Error('should have thrown');\n    } catch (err) {\n      strictEqual(err.message, 'The operation was aborted');\n    }\n\n    try {\n      const ac = new AbortController();\n      const promise = scheduler.wait(10000, { signal: ac.signal });\n      ac.abort();\n      await promise;\n      throw new Error('should have thrown');\n    } catch (err) {\n      strictEqual(err.message, 'The operation was aborted');\n    }\n\n    // Verify that ties are broken in the order the timeouts are scheduled\n    let order = [];\n    await Promise.all([\n      scheduler.wait(100).then(() => order.push(1)),\n      scheduler.wait(100).then(() => order.push(2)),\n      scheduler.wait(50).then(() => order.push(0)),\n      scheduler.wait(200).then(() => order.push(3)),\n    ]);\n    deepStrictEqual(order, [0, 1, 2, 3]);\n\n    // Verify that timeouts are properly canceled when IoContext is destroyed.\n    await env.subrequest.fetch('http://example.com');\n    await env.subrequest.fetch('http://example.com');\n\n    // globalThis.scheduler can be monkeypatched over...\n    scheduler = 'foo';\n    strictEqual(globalThis.scheduler, 'foo');\n  },\n};\n\nexport const sequentialAbort = {\n  async test(ctrl, env, ctx) {\n    // Sequentially wait a very long time 11k times, each time aborting via an\n    // AbortController. This exercises the abort path under heavy repetition.\n    // Test ensures that the underlying timer quota is not exceeded when the\n    // timeout is cancelled with an AbortSignal\n    const iterations = 11_000;\n    let completed = 0;\n    for (let i = 0; i < iterations; i++) {\n      const ac = new AbortController();\n      const promise = scheduler.wait(1_000_000, { signal: ac.signal });\n      ac.abort();\n      try {\n        await promise;\n        throw new Error('should have thrown');\n      } catch (err) {\n        strictEqual(err.message, 'The operation was aborted');\n      }\n      completed++;\n    }\n    strictEqual(\n      completed,\n      11_000,\n      `Expected 11000 iterations but only completed ${completed}`\n    );\n  },\n};\n\nexport default {\n  async fetch() {\n    // If globalThis.longWait exists, that means the timer fired after the\n    // IoContext was destroyed. Boo!\n    ok(!globalThis.longWait);\n    scheduler.wait(10).then(() => {\n      globalThis.longWait = true;\n    });\n    return new Response('not waiting');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/scheduler-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"scheduler-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"scheduler-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"],\n        bindings = [\n          (name = \"subrequest\", service = \"scheduler-test\")\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/self-logger-test.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport * as assert from 'node:assert';\n\n// Track if we've received logs in the tail handler\nlet receivedLogsInTail = false;\n\nexport default {\n  fetch(request, env) {\n    // Log some messages that should be captured by the tail handler\n    console.log('Self-logger test: Message 1');\n    console.log('Self-logger test: Message 2');\n    console.error(new Error('Self-logger test: Error message'));\n\n    return new Response('Self-logger test completed');\n  },\n\n  // Tail handler in the same worker that generates logs\n  tail(events) {\n    console.log(`Self-logger received ${events.length} events`);\n\n    // Check that we have the logs we expect\n    const logs = [];\n    for (const event of events) {\n      if (event.logs) {\n        for (const log of event.logs) {\n          logs.push(log.message[0]);\n        }\n      }\n    }\n\n    // Verify we're seeing the logs from our fetch handler\n    const foundMessage1 = logs.some((log) => log.includes('Message 1'));\n    const foundMessage2 = logs.some((log) => log.includes('Message 2'));\n    const foundError = logs.some((log) => log.includes('Error message'));\n\n    // Assert that we found our logs (this will throw if the test fails)\n    assert.ok(foundMessage1, 'Should have received \"Message 1\" in logs');\n    assert.ok(foundMessage2, 'Should have received \"Message 2\" in logs');\n    assert.ok(foundError, 'Should have received \"Error message\" in logs');\n    // If we get here, it means the self-logging functionality is working\n    receivedLogsInTail = true;\n  },\n};\n\nexport const test = {\n  async test(ctrl, env, ctx) {\n    // This test simply verifies that the self-logger worked\n    // If the tail handler was called, receivedLogsInTail will be true\n    const response = await env.SERVICE.fetch('http://example.com/');\n    const body = await response.text();\n    assert.strictEqual(body, 'Self-logger test completed');\n    await scheduler.wait(100);\n    assert.strictEqual(\n      receivedLogsInTail,\n      true,\n      'Tail handler should have been called'\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/self-logger-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    (name = \"self-logger\", worker = .selfLoggerWorker),\n  ],\n);\n\nconst selfLoggerWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"self-logger-test.js\")\n  ],\n  bindings = [\n    ( name = \"SERVICE\", service = \"self-logger\" ),\n  ],\n  compatibilityFlags = [\"nodejs_compat\"],\n  tails = [\"self-logger\"],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/settimeout-test.js",
    "content": "import { ok } from 'node:assert';\nimport timers from 'node:timers/promises';\n\n// Allow up to 10ms of jitter because the precise_timers compat flag\n// can introduce +/-3ms of variance in timer resolution, and coverage\n// builds add additional overhead.\nconst JITTER = 10;\n\n// The first setTimeout was firing too early because\n// kj::Timer::now() was stale after script compilation/startup.\n// Ref: https://github.com/cloudflare/workerd/issues/6019\nexport const basicAccuracy = {\n  async test() {\n    const t0 = Date.now();\n    await timers.setTimeout(100);\n    const t1 = Date.now();\n    ok(t1 - t0 >= 100 - JITTER, `Received a difference of ${t1 - t0}`);\n    await timers.setTimeout(100);\n    const t2 = Date.now();\n    ok(t2 - t1 >= 100 - JITTER, `Received a difference of ${t2 - t1}`);\n  },\n};\n\n// After CPU-heavy work between setTimeout calls,\n// subsequent consecutive setTimeouts must still each wait their full delay\n// rather than all firing at the same instant.\n// Ref: https://github.com/cloudflare/workerd/issues/6037\nexport const accuracyAfterCpuWork = {\n  async test() {\n    await timers.setTimeout(50);\n\n    // Let's burn some CPU\n    for (let j = 0; j < 1e9; j++);\n\n    const a = Date.now();\n    await timers.setTimeout(50);\n    const b = Date.now();\n    ok(\n      b - a >= 50 - JITTER,\n      `After CPU work, first sleep: expected ~50ms, got ${b - a}ms`\n    );\n\n    await timers.setTimeout(50);\n    const c = Date.now();\n    ok(\n      c - b >= 50 - JITTER,\n      `After CPU work, second sleep: expected ~50ms, got ${c - b}ms`\n    );\n\n    await timers.setTimeout(50);\n    const d = Date.now();\n    ok(\n      d - c >= 50 - JITTER,\n      `After CPU work, third sleep: expected ~50ms, got ${d - c}ms`\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/settimeout-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"settimeout-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"settimeout-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/sql-test-tail.js",
    "content": "import * as assert from 'node:assert';\n\nimport {\n  invocationPromises,\n  spans,\n  testTailHandler,\n} from 'test:instrumentation-tail';\n\nexport default testTailHandler;\n\nexport const test = {\n  async test(ctrl, env, ctx) {\n    await Promise.allSettled(invocationPromises);\n    let received = spans.values();\n\n    const reduced = received.reduce((acc, curr) => {\n      if (!acc[curr.name]) acc[curr.name] = 0;\n      acc[curr.name]++;\n      return acc;\n    }, {});\n    assert.deepStrictEqual(reduced, {\n      durable_object_storage_exec: 268,\n      durable_object_storage_ingest: 1030,\n      durable_object_storage_getDatabaseSize: 3,\n      durable_object_storage_put: 18,\n      durable_object_storage_get: 18,\n      durable_object_storage_transaction: 8,\n      durable_object_subrequest: 47,\n      durable_object_storage_deleteAll: 1,\n      createStringTable: 4,\n      runActorFunc: 4,\n      durable_object_storage_sync: 4,\n      getStringTableIds: 4,\n      testMultiStatement: 1,\n      testRollbackKvInit: 1,\n      testRollbackAlarmInit: 1,\n      durable_object_storage_setAlarm: 2,\n      durable_object_storage_getAlarm: 1,\n      testSessionsAPIBookmark: 20,\n      durable_object_storage_getCurrentBookmark: 20,\n      durable_object_storage_waitForBookmark: 19,\n    });\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/sql-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport * as assert from 'node:assert';\nimport { DurableObject } from 'cloudflare:workers';\n\n// A collection of functions that can be triggered by name from the DO, to make\n// it easier to keep the DO and test functions together in the test file:\nlet actorFuncs = {};\n\nasync function test(state) {\n  const storage = state.storage;\n  const sql = storage.sql;\n  // Test numeric results\n  const resultNumber = [...sql.exec('SELECT 123')];\n  assert.equal(resultNumber.length, 1);\n  assert.equal(resultNumber[0]['123'], 123);\n\n  // Test raw results\n  const resultNumberRaw = [...sql.exec('SELECT 123').raw()];\n  assert.equal(resultNumberRaw.length, 1);\n  assert.equal(resultNumberRaw[0].length, 1);\n  assert.equal(resultNumberRaw[0][0], 123);\n\n  sql.exec('SELECT 123');\n  sql.exec('SELECT 123');\n  sql.exec('SELECT 123');\n\n  // Test string results\n  const resultStr = [...sql.exec(\"SELECT 'hello'\")];\n  assert.equal(resultStr.length, 1);\n  assert.equal(resultStr[0][\"'hello'\"], 'hello');\n\n  // Test blob results\n  const resultBlob = [...sql.exec(\"SELECT x'ff' as blob\")];\n  assert.equal(resultBlob.length, 1);\n  const blob = new Uint8Array(resultBlob[0].blob);\n  assert.equal(blob.length, 1);\n  assert.equal(blob[0], 255);\n\n  {\n    // Test binding values\n    const result = [...sql.exec('SELECT ?', 456)];\n    assert.equal(result.length, 1);\n    assert.equal(result[0]['?'], 456);\n  }\n\n  {\n    // Test multiple binding values\n    const result = [...sql.exec('SELECT ? + ?', 123, 456)];\n    assert.equal(result.length, 1);\n    assert.equal(result[0]['? + ?'], 579);\n  }\n\n  {\n    // Test multiple rows\n    const result = [\n      ...sql.exec(\n        'SELECT 1 AS value\\n' +\n          'UNION ALL\\n' +\n          'SELECT 2 AS value\\n' +\n          'UNION ALL\\n' +\n          'SELECT 3 AS value;'\n      ),\n    ];\n    assert.equal(result.length, 3);\n    assert.equal(result[0]['value'], 1);\n    assert.equal(result[1]['value'], 2);\n    assert.equal(result[2]['value'], 3);\n  }\n\n  {\n    // Test multiple rows, manual iteration with next().\n    const cursor = sql.exec(\n      'SELECT 1 AS col\\n' +\n        'UNION ALL\\n' +\n        'SELECT \"foo\" AS col\\n' +\n        'UNION ALL\\n' +\n        'SELECT 3 AS col;'\n    );\n    assert.deepEqual(cursor.next(), { done: false, value: { col: 1 } });\n    assert.deepEqual(cursor.next(), { done: false, value: { col: 'foo' } });\n    assert.deepEqual(cursor.next(), { done: false, value: { col: 3 } });\n    assert.deepEqual(cursor.next(), { done: true });\n  }\n\n  {\n    // Test multiple rows using .toArray()\n    const cursor = sql.exec(\n      'SELECT 1 AS value\\n' +\n        'UNION ALL\\n' +\n        'SELECT \"foo\" AS value\\n' +\n        'UNION ALL\\n' +\n        'SELECT 3 AS value;'\n    );\n    assert.deepEqual(cursor.toArray(), [\n      { value: 1 },\n      { value: 'foo' },\n      { value: 3 },\n    ]);\n  }\n\n  {\n    // Test one row with .one()\n    let cursor = sql.exec('SELECT 123 AS foo, \"abc\" AS bar');\n    assert.deepEqual(cursor.one(), { foo: 123, bar: 'abc' });\n    // Cursor has been consumed.\n    assert.deepEqual([...cursor], []);\n\n    // Multiple results throws.\n    assert.throws(\n      () =>\n        sql\n          .exec(\n            'SELECT 1 AS value\\n' + 'UNION ALL\\n' + 'SELECT \"foo\" AS value;'\n          )\n          .one(),\n      /Expected exactly one result from SQL query, but got multiple results/\n    );\n\n    // No results throws.\n    sql.exec('CREATE TABLE IF NOT EXISTS empty (x INTEGER)');\n    assert.throws(\n      () => sql.exec('SELECT * from empty;').one(),\n      /Expected exactly one result from SQL query, but got no results/\n    );\n    sql.exec('DROP TABLE empty');\n  }\n\n  // Test partial query ingestion\n  assert.deepEqual(sql.ingest(`SELECT 123; SELECT 456;    `).remainder, '    ');\n  assert.deepEqual(sql.ingest(`SELECT 123; SELECT 456;`).remainder, '');\n  assert.deepEqual(\n    sql.ingest(`SELECT 123; SELECT 456`).remainder,\n    ' SELECT 456'\n  );\n  assert.deepEqual(sql.ingest(`SELECT 123; SELECT 45`).remainder, ' SELECT 45');\n  assert.deepEqual(sql.ingest(`SELECT 123; SELECT 4`).remainder, ' SELECT 4');\n  assert.deepEqual(sql.ingest(`SELECT 123; SELECT `).remainder, ' SELECT ');\n  assert.deepEqual(sql.ingest(`SELECT 123; SELECT`).remainder, ' SELECT');\n  assert.deepEqual(sql.ingest(`SELECT 123; SELEC`).remainder, ' SELEC');\n  assert.deepEqual(sql.ingest(`SELECT 123; SELE`).remainder, ' SELE');\n  assert.deepEqual(sql.ingest(`SELECT 123; SEL`).remainder, ' SEL');\n  assert.deepEqual(sql.ingest(`SELECT 123; SE`).remainder, ' SE');\n  assert.deepEqual(sql.ingest(`SELECT 123; S`).remainder, ' S');\n  assert.deepEqual(sql.ingest(`SELECT 123; `).remainder, ' ');\n  assert.deepEqual(sql.ingest(`SELECT 123;`).remainder, '');\n  assert.deepEqual(sql.ingest(`SELECT 123`).remainder, 'SELECT 123');\n  assert.deepEqual(sql.ingest(`SELECT 12`).remainder, 'SELECT 12');\n  assert.deepEqual(sql.ingest(`SELECT 1`).remainder, 'SELECT 1');\n\n  // Exec throws with trailing comments\n  assert.throws(\n    () => sql.exec('SELECT 123; SELECT 456; -- trailing comment'),\n    /SQL code did not contain a statement/\n  );\n  // Ingest does not\n  assert.deepEqual(\n    sql.ingest(`SELECT 123; SELECT 456; -- trailing comment`).remainder,\n    ' -- trailing comment'\n  );\n\n  // Ingest throws if statement looks \"complete\" but is actually a syntax error:\n  assert.throws(\n    () => sql.ingest(`SELECT * bunk;`),\n    /Error: near \"bunk\": syntax error at offset/\n  );\n  assert.throws(\n    () => sql.ingest(`INSER INTO xyz VALUES ('a'),('b');`),\n    /Error: near \"INSER\": syntax error/\n  );\n  assert.throws(\n    () => sql.ingest(`INSERT INTO xyz VALUES ('a')('b');`),\n    /Error: near \"\\(\": syntax error/\n  );\n\n  // Test execution of ingested queries by taking an input of 6 INSERT statements, that all\n  // add 6 rows of data, then splitting that into a bunch of chunks, then ingesting them all\n  {\n    sql.exec(`CREATE TABLE streaming(val TEXT);`);\n\n    // Convert to binary otherwise .split can cause corruption for multi-byte chars\n    const inputBytes = new TextEncoder().encode(INSERT_36_ROWS);\n    const decoder = new TextDecoder();\n\n    // Use a chunk size 1, 3, 9, 27, 81, ... bytes\n    for (let length = 1; length < inputBytes.length; length = length * 3) {\n      let totalRowsWritten = 0;\n      let totalSqlStatements = 0;\n      let buffer = '';\n      for (let offset = 0; offset < inputBytes.length; offset += length) {\n        // Simulate a single \"chunk\" arriving\n        const chunk = inputBytes.slice(offset, offset + length);\n\n        // Append the new chunk to the existing buffer\n        buffer += decoder.decode(chunk, { stream: true });\n\n        // Ingest any complete statements and snip those chars off the buffer\n        let result = sql.ingest(buffer);\n        buffer = result.remainder;\n        totalRowsWritten += result.rowsWritten;\n        totalSqlStatements += result.statementCount;\n\n        // Simulate awaiting next chunk\n        await scheduler.wait(1);\n      }\n\n      // Verify exactly 36 rows were added\n      assert.deepEqual(Array.from(sql.exec(`SELECT count(*) FROM streaming`)), [\n        { 'count(*)': 36 },\n      ]);\n\n      // Ensure our precious emoji were preserved, even if their bytes occur across split points\n      assert.deepEqual(\n        Array.from(sql.exec(`SELECT * FROM streaming WHERE val LIKE 'f%'`)),\n        [\n          { val: 'f: 😳' },\n          { val: 'f: 🫠' },\n          { val: 'f: 🙃' },\n          { val: 'f: 🤡' },\n          { val: 'f: 🥺' },\n          { val: 'f: 🔥😎🔥' },\n        ]\n      );\n\n      // Verify that all 36 rows we inserted were accounted for.\n      assert.equal(totalRowsWritten, 36);\n      assert.equal(totalSqlStatements, 6);\n\n      sql.exec(`DELETE FROM streaming`);\n      await scheduler.wait(1);\n    }\n    sql.exec(`DROP TABLE streaming;`);\n  }\n\n  // Test count\n  {\n    const result = [\n      ...sql.exec(\n        'SELECT count(value) from (SELECT 1 AS value\\n' +\n          'UNION ALL\\n' +\n          'SELECT 2 AS value\\n' +\n          'UNION ALL\\n' +\n          'SELECT 3 AS value);'\n      ),\n    ];\n    assert.equal(result.length, 1);\n    assert.equal(result[0]['count(value)'], 3);\n  }\n\n  // Test sum\n  {\n    const result = [\n      ...sql.exec(\n        'SELECT sum(value) from (SELECT 1 AS value\\n' +\n          'UNION ALL\\n' +\n          'SELECT 2 AS value\\n' +\n          'UNION ALL\\n' +\n          'SELECT 3 AS value);'\n      ),\n    ];\n    assert.equal(result.length, 1);\n    assert.equal(result[0]['sum(value)'], 6);\n  }\n\n  // Test math functions enabled\n  {\n    const result = [...sql.exec('SELECT cos(0)')];\n    assert.equal(result.length, 1);\n    assert.equal(result[0]['cos(0)'], 1);\n  }\n\n  // Empty statements\n  assert.throws(() => sql.exec(''), 'SQL code did not contain a statement');\n  assert.throws(() => sql.exec(';'), 'SQL code did not contain a statement');\n\n  // Invalid statements\n  assert.throws(() => sql.exec('SELECT ;'), /syntax error at offset 7/);\n  assert.throws(() => sql.exec('SELECT -;'), /syntax error at offset 8/);\n\n  // Data type mismatch\n  sql.exec(`CREATE TABLE test_error_codes (name TEXT);`);\n  assert.throws(\n    () =>\n      sql.exec(\n        `INSERT INTO test_error_codes(rowid, name) values ('yeah','nah');`\n      ),\n    /Error: datatype mismatch: SQLITE_MISMATCH/\n  );\n  sql.exec(`DROP TABLE test_error_codes;`);\n\n  // Incorrect number of binding values\n  assert.throws(\n    () => sql.exec('SELECT ?'),\n    'Error: Wrong number of parameter bindings for SQL query.'\n  );\n\n  // Prepared statement\n  const prepared = sql.prepare('SELECT 789');\n  const resultPrepared = [...prepared()];\n  assert.equal(resultPrepared.length, 1);\n  assert.equal(resultPrepared[0]['789'], 789);\n\n  // Running the same query twice, overlapping, works just fine.\n  let result1 = prepared();\n  let result2 = prepared();\n  // Iterate result2 before result1.\n  assert.equal([...result2][0]['789'], 789);\n  assert.equal([...result1][0]['789'], 789);\n\n  // That said if a cursor was already done before the statement was re-run, it's not considered\n  // canceled.\n  prepared();\n  assert.equal([...result2].length, 0);\n\n  // Prepared statement with binding values\n  const preparedWithBinding = sql.prepare('SELECT ?');\n  const resultPreparedWithBinding = [...preparedWithBinding(789)];\n  assert.equal(resultPreparedWithBinding.length, 1);\n  assert.equal(resultPreparedWithBinding[0]['?'], 789);\n\n  // Prepared statement (incorrect number of binding values)\n  assert.throws(\n    () => preparedWithBinding(),\n    'Error: Wrong number of parameter bindings for SQL query.'\n  );\n\n  // Prepared statement with whitespace\n  const whitespace = [' ', '\\t', '\\n', '\\r', '\\v', '\\f', '\\r\\n'];\n\n  for (const char of whitespace) {\n    const prepared = sql.prepare(`SELECT 1;${char}`);\n    const result = [...prepared()];\n\n    assert.equal(result.length, 1);\n  }\n\n  // Prepared statement with multiple statements\n  assert.deepEqual([...sql.prepare('SELECT 1; SELECT 2;')()], [{ 2: 2 }]);\n\n  // Accessing a hidden _cf_ table\n  assert.throws(\n    () => sql.exec('CREATE TABLE _cf_invalid (name TEXT)'),\n    /not authorized/\n  );\n  storage.put('blah', 123); // force creation of _cf_KV table\n  assert.throws(\n    () => sql.exec('SELECT * FROM _cf_KV'),\n    /access to _cf_KV.key is prohibited/\n  );\n\n  // Some pragmas are completely not allowed\n  assert.throws(\n    () => sql.exec('PRAGMA hard_heap_limit = 1024'),\n    /not authorized/\n  );\n\n  // Test reading read-only pragmas\n  {\n    const result = [...sql.exec('pragma data_version;')];\n    assert.equal(result.length, 1);\n    assert.equal(result[0]['data_version'], 2);\n  }\n\n  // Trying to write to read-only pragmas is not allowed\n  assert.throws(\n    () => sql.exec('PRAGMA data_version = 5'),\n    /not authorized: SQLITE_AUTH/\n  );\n  assert.throws(\n    () => sql.exec('PRAGMA max_page_count = 65536'),\n    /not authorized/\n  );\n  assert.throws(\n    () => sql.exec('PRAGMA page_size = 8192'),\n    /not authorized: SQLITE_AUTH/\n  );\n\n  // PRAGMA table_info and PRAGMA table_xinfo are allowed.\n  sql.exec('CREATE TABLE myTable (foo TEXT, bar INTEGER)');\n  {\n    let info = [...sql.exec('PRAGMA table_info(myTable)')];\n    assert.equal(info.length, 2);\n    assert.equal(info[0].name, 'foo');\n    assert.equal(info[1].name, 'bar');\n\n    let xInfo = [...sql.exec('PRAGMA table_xinfo(myTable)')];\n    assert.equal(xInfo.length, 2);\n    assert.equal(xInfo[0].name, 'foo');\n    assert.equal(xInfo[1].name, 'bar');\n  }\n\n  // Can't get table_info for _cf_KV.\n  assert.throws(() => sql.exec('PRAGMA table_info(_cf_KV)'), /not authorized/);\n\n  // Testing the three valid types of inputs for quick_check\n  assert.deepEqual(Array.from(sql.exec('pragma quick_check;')), [\n    { quick_check: 'ok' },\n  ]);\n  assert.deepEqual(Array.from(sql.exec('pragma quick_check(1);')), [\n    { quick_check: 'ok' },\n  ]);\n  assert.deepEqual(Array.from(sql.exec('pragma quick_check(100);')), [\n    { quick_check: 'ok' },\n  ]);\n  assert.deepEqual(Array.from(sql.exec('pragma quick_check(myTable);')), [\n    { quick_check: 'ok' },\n  ]);\n  // But that private tables are again restricted\n  assert.throws(() => sql.exec('PRAGMA quick_check(_cf_KV)'), /not authorized/);\n\n  // PRAGMA optimize and ANALYZE are allowed.\n  //\n  // The following sequence of calls is mentioned by https://www.sqlite.org/lang_analyze.html as how\n  // one might optmize the query planner.\n  {\n    // Dry-run.  SQLite's documentation uses -1 as the debug example.\n    assert.deepEqual(Array.from(sql.exec('PRAGMA optimize(-1);')), [\n      { optimize: 'ANALYZE \"main\".\"myTable\"' },\n      { optimize: 'ANALYZE \"main\".\"_cf_KV\"' },\n    ]);\n  }\n  {\n    // Dry-run.  This sets all of the bits except for the sign bit because SQLite's optimize\n    // function doesn't parse hex numbers into negative integers.\n    assert.deepEqual(Array.from(sql.exec('PRAGMA optimize(0x7fffffff);')), [\n      { optimize: 'ANALYZE \"main\".\"myTable\"' },\n      { optimize: 'ANALYZE \"main\".\"_cf_KV\"' },\n    ]);\n  }\n  {\n    let info = [...sql.exec('PRAGMA optimize=0x10002;')];\n    assert.equal(info.length, 0);\n  }\n  {\n    let info = [...sql.exec('ANALYZE;')];\n    assert.equal(info.length, 0);\n  }\n  {\n    let info = [...sql.exec('PRAGMA optimize;')];\n    assert.equal(info.length, 0);\n  }\n\n  // Basic functions like abs() work.\n  assert.equal([...sql.exec('SELECT abs(-123)').raw()][0][0], 123);\n\n  // We don't permit sqlite_*() functions.\n  assert.throws(\n    () => sql.exec('SELECT sqlite_version()'),\n    /not authorized to use function: sqlite_version/\n  );\n\n  // JSON -> operator works\n  const jsonResult = [\n    ...sql.exec('SELECT \\'{\"a\":2,\"c\":[4,5,{\"f\":7}]}\\' -> \\'$.c\\' AS value'),\n  ][0].value;\n  assert.equal(jsonResult, '[4,5,{\"f\":7}]');\n\n  // current_{date,time,timestamp} functions work\n  const resultDate = [...sql.exec('SELECT current_date')];\n  assert.equal(resultDate.length, 1);\n  // Should match results in the format \"2023-06-01\"\n  assert.match(resultDate[0]['current_date'], /^\\d{4}-\\d{2}-\\d{2}$/);\n\n  const resultTime = [...sql.exec('SELECT current_time')];\n  assert.equal(resultTime.length, 1);\n  // Should match results in the format \"15:30:03\"\n  assert.match(resultTime[0]['current_time'], /^\\d{2}:\\d{2}:\\d{2}$/);\n\n  const resultTimestamp = [...sql.exec('SELECT current_timestamp')];\n  assert.equal(resultTimestamp.length, 1);\n  // Should match results in the format \"2023-06-01 15:30:03\"\n  assert.match(\n    resultTimestamp[0]['current_timestamp'],\n    /^\\d{4}-\\d{2}-\\d{2}\\s{1}\\d{2}:\\d{2}:\\d{2}$/\n  );\n\n  // Validate that the SQLITE_LIMIT_COMPOUND_SELECT limit is enforced as expected\n  const compoundWithinLimits = [\n    ...sql.exec(\n      'SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5'\n    ),\n  ];\n  assert.equal(compoundWithinLimits.length, 5);\n  assert.throws(\n    () =>\n      sql.exec(\n        'SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6'\n      ),\n    /too many terms in compound SELECT/\n  );\n\n  // Can't start transactions or savepoints.\n  assert.throws(\n    () => sql.exec('BEGIN TRANSACTION'),\n    /please use the state.storage.transaction\\(\\) or state.storage.transactionSync\\(\\) APIs/\n  );\n  assert.throws(\n    () => sql.exec('SAVEPOINT foo'),\n    /please use the state.storage.transaction\\(\\) or state.storage.transactionSync\\(\\) APIs/\n  );\n\n  // Virtual tables\n  // Only fts5 and fts5vocab modules are allowed\n  assert.throws(\n    () => sql.exec(`CREATE VIRTUAL TABLE test_fts USING fts5abcd(id);`),\n    /not authorized/\n  );\n\n  // Full text search extension\n  sql.exec(`\n    CREATE TABLE documents (\n      id INTEGER PRIMARY KEY,\n      title TEXT NOT NULL,\n      content TEXT NOT NULL\n    );\n  `);\n\n  // Module names are case-insensitive\n  sql.exec(`\n    CREATE VIRTUAL TABLE documents_fts USING FtS5(id, title, content, tokenize = porter);\n  `);\n  sql.exec(`\n    CREATE VIRTUAL TABLE documents_fts_v_col USING fTs5VoCaB(documents_fts, col);\n  `);\n  sql.exec(`\n    CREATE VIRTUAL TABLE documents_fts_v_row USING FtS5vOcAb(documents_fts, row);\n  `);\n  sql.exec(`\n    CREATE VIRTUAL TABLE documents_fts_v_instance USING fTs5VoCaB(documents_fts, instance);\n  `);\n\n  sql.exec(`\n    CREATE TRIGGER documents_fts_insert\n    AFTER INSERT ON documents\n    BEGIN\n      INSERT INTO documents_fts(id, title, content)\n        VALUES(new.id, new.title, new.content);\n    END;\n  `);\n  sql.exec(`\n    CREATE TRIGGER documents_fts_update\n    AFTER UPDATE ON documents\n    BEGIN\n      UPDATE documents_fts SET title=new.title, content=new.content WHERE id=old.id;\n    END;\n  `);\n  sql.exec(`\n    CREATE TRIGGER documents_fts_delete\n    AFTER DELETE ON documents\n    BEGIN\n      DELETE FROM documents_fts WHERE id=old.id;\n    END;\n  `);\n  sql.exec(`\n    INSERT INTO documents (title, content) VALUES ('Document 1', 'This is the contents of document 1 (of 2).');\n  `);\n  sql.exec(`\n    INSERT INTO documents (title, content) VALUES ('Document 2', 'This is the content of document 2 (of 2).');\n  `);\n  // Porter stemming makes 'contents' and 'content' the same\n  {\n    let results = Array.from(\n      sql.exec(`\n      SELECT * FROM documents_fts WHERE documents_fts MATCH 'content' ORDER BY rank;\n    `)\n    );\n    assert.equal(results.length, 2);\n    assert.equal(results[0].id, 1); // Stemming makes doc 1 match first\n    assert.equal(results[1].id, 2);\n  }\n  // Ranking functions\n  {\n    let results = Array.from(\n      sql.exec(`\n      SELECT *, bm25(documents_fts) FROM documents_fts WHERE documents_fts MATCH '2' ORDER BY rank;\n    `)\n    );\n    assert.equal(results.length, 2);\n    assert.equal(\n      results[0]['bm25(documents_fts)'] < results[1]['bm25(documents_fts)'],\n      true\n    ); // Better matches have lower bm25 (since they're all negative\n    assert.equal(results[0].id, 2); // Doc 2 comes first (sorted by rank)\n    assert.equal(results[1].id, 1);\n  }\n  // highlight() function\n  {\n    let results = Array.from(\n      sql.exec(`\n        SELECT highlight(documents_fts, 2, '<b>', '</b>') as output FROM documents_fts WHERE documents_fts MATCH '2' ORDER BY rank;\n    `)\n    );\n    assert.equal(results.length, 2);\n    assert.equal(\n      results[0].output,\n      `This is the content of document <b>2</b> (of <b>2</b>).`\n    ); // two matches, two highlights\n    assert.equal(\n      results[1].output,\n      `This is the contents of document 1 (of <b>2</b>).`\n    );\n  }\n  // snippet() function\n  {\n    let results = Array.from(\n      sql.exec(`\n        SELECT snippet(documents_fts, 2, '<b>', '</b>', '...', 4) as output FROM documents_fts WHERE documents_fts MATCH '2' ORDER BY rank;\n    `)\n    );\n    assert.equal(results.length, 2);\n    assert.equal(results[0].output, `...document <b>2</b> (of <b>2</b>).`); // two matches, two highlights\n    assert.equal(results[1].output, `...document 1 (of <b>2</b>).`);\n  }\n\n  // Complex queries\n\n  // List table info\n  {\n    let result = [\n      ...sql.exec(`\n        SELECT name as tbl_name,\n               ncol as num_columns\n        FROM pragma_table_list\n        WHERE TYPE = \"table\"\n          AND tbl_name NOT LIKE \"sqlite_%\"\n          AND tbl_name NOT LIKE \"d1_%\"\n          AND tbl_name NOT LIKE \"_cf_%\"\n        ORDER BY tbl_name desc`),\n    ];\n    assert.equal(result.length, 2);\n    assert.equal(result[0].tbl_name, 'myTable');\n    assert.equal(result[0].num_columns, 2);\n    assert.equal(result[1].tbl_name, 'documents');\n    assert.equal(result[1].num_columns, 3);\n  }\n\n  // Similar query using JSON objects\n  {\n    const jsonResult = JSON.parse(\n      Array.from(\n        sql.exec(\n          `SELECT json_group_array(json_object(\n              'type', type,\n              'name', name,\n              'tbl_name', tbl_name,\n              'rootpage', rootpage,\n              'sql', sql,\n              'columns', (SELECT json_group_object(name, type) from pragma_table_info(tbl_name))\n          )) as data\n       FROM sqlite_master\n       WHERE type = \"table\" AND tbl_name != \"_cf_KV\";`\n        )\n      )[0].data\n    );\n    assert.equal(jsonResult.length, 12);\n    assert.equal(\n      jsonResult.map((r) => r.name).join(','),\n      'myTable,sqlite_stat1,documents,documents_fts,documents_fts_data,documents_fts_idx,documents_fts_content,documents_fts_docsize,documents_fts_config,documents_fts_v_col,documents_fts_v_row,documents_fts_v_instance'\n    );\n    assert.equal(jsonResult[0].columns.foo, 'TEXT');\n    assert.equal(jsonResult[0].columns.bar, 'INTEGER');\n    assert.equal(jsonResult[2].columns.id, 'INTEGER');\n    assert.equal(jsonResult[2].columns.title, 'TEXT');\n    assert.equal(jsonResult[2].columns.content, 'TEXT');\n  }\n\n  let assertValidBool = (name, val) => {\n    sql.exec('PRAGMA defer_foreign_keys = ' + name + ';');\n    assert.equal(\n      [...sql.exec('PRAGMA defer_foreign_keys;')][0].defer_foreign_keys,\n      val\n    );\n  };\n  let assertInvalidBool = (name, msg) => {\n    assert.throws(\n      () => sql.exec('PRAGMA defer_foreign_keys = ' + name + ';'),\n      msg || /not authorized/\n    );\n  };\n\n  assertValidBool('true', 1);\n  assertValidBool('false', 0);\n  assertValidBool('on', 1);\n  assertValidBool('off', 0);\n  assertValidBool('yes', 1);\n  assertValidBool('no', 0);\n  assertValidBool('1', 1);\n  assertValidBool('0', 0);\n\n  // case-insensitive\n  assertValidBool('tRuE', 1);\n  assertValidBool('NO', 0);\n\n  // quoted\n  assertValidBool(\"'true'\", 1);\n  assertValidBool('\"yes\"', 1);\n  assertValidBool('\"0\"', 0);\n\n  // whitespace is trimmed by sqlite before passing to authorizer\n  assertValidBool('  true    ', 1);\n\n  // Don't accept anything invalid...\n  assertInvalidBool('abcd');\n  assertInvalidBool('\"foo\"');\n  assertInvalidBool(\"'yes\", 'unrecognized token');\n\n  // Test database size interface.\n  assert.equal(sql.databaseSize, 40960);\n  sql.exec(`CREATE TABLE should_make_one_more_page(VALUE text);`);\n  assert.equal(sql.databaseSize, 40960 + 4096);\n  sql.exec(`DROP TABLE should_make_one_more_page;`);\n  assert.equal(sql.databaseSize, 40960);\n\n  storage.put('txnTest', 0);\n\n  // Try a transaction while no implicit transaction is open.\n  await scheduler.wait(1); // finish implicit txn\n  let txnResult = await storage.transaction(async () => {\n    storage.put('txnTest', 1);\n    assert.equal(await storage.get('txnTest'), 1);\n    return 'foo';\n  });\n  assert.equal(await storage.get('txnTest'), 1);\n  assert.equal(txnResult, 'foo');\n\n  // Try a transaction while an implicit transaction is open first.\n  storage.put('txnTest', 2);\n  await storage.transaction(async () => {\n    storage.put('txnTest', 3);\n    assert.equal(await storage.get('txnTest'), 3);\n  });\n  assert.equal(await storage.get('txnTest'), 3);\n\n  // Try a transaction that is explicitly rolled back.\n  await storage.transaction(async (txn) => {\n    storage.put('txnTest', 4);\n    assert.equal(await storage.get('txnTest'), 4);\n    txn.rollback();\n  });\n  assert.equal(await storage.get('txnTest'), 3);\n\n  // Try a transaction that is implicitly rolled back by throwing an exception.\n  try {\n    await storage.transaction(async (txn) => {\n      storage.put('txnTest', 5);\n      assert.equal(await storage.get('txnTest'), 5);\n      throw new Error('txn failure');\n    });\n    throw new Error('expected error');\n  } catch (err) {\n    assert.equal(err.message, 'txn failure');\n  }\n  assert.equal(await storage.get('txnTest'), 3);\n\n  // Try a nested transaction.\n  await storage.transaction(async (txn) => {\n    storage.put('txnTest', 6);\n    assert.equal(await storage.get('txnTest'), 6);\n    await storage.transaction(async (txn2) => {\n      storage.put('txnTest', 7);\n      assert.equal(await storage.get('txnTest'), 7);\n      // Let's even do an await in here for good measure.\n      await scheduler.wait(1);\n    });\n    assert.equal(await storage.get('txnTest'), 7);\n    txn.rollback();\n  });\n  assert.equal(await storage.get('txnTest'), 3);\n\n  // Test transactionSync, success\n  {\n    await scheduler.wait(1);\n    const result = storage.transactionSync(() => {\n      sql.exec('CREATE TABLE IF NOT EXISTS should_succeed (VALUE text);');\n      return 'some data';\n    });\n\n    assert.equal(result, 'some data');\n\n    const results = Array.from(\n      sql.exec(`\n      SELECT * FROM sqlite_master WHERE tbl_name = 'should_succeed'\n    `)\n    );\n    assert.equal(results.length, 1);\n  }\n\n  // Test transactionSync, failure\n  {\n    await scheduler.wait(1);\n\n    assert.throws(\n      () =>\n        storage.transactionSync(() => {\n          sql.exec('CREATE TABLE should_be_rolled_back (VALUE text);');\n          sql.exec('SELECT * FROM misspelled_table_name;');\n        }),\n      'Error: no such table: misspelled_table_name'\n    );\n\n    const results = Array.from(\n      sql.exec(`\n      SELECT * FROM sqlite_master WHERE tbl_name = 'should_be_rolled_back'\n    `)\n    );\n    assert.equal(results.length, 0);\n  }\n\n  // Test transactionSync, nested\n  {\n    sql.exec('CREATE TABLE txnTest (i INTEGER)');\n    sql.exec('INSERT INTO txnTest VALUES (1)');\n\n    let setI = sql.prepare('UPDATE txnTest SET i = ?');\n    let getIStmt = sql.prepare('SELECT i FROM txnTest');\n    let getI = () => [...getIStmt()][0].i;\n\n    assert.equal(getI(), 1);\n    storage.transactionSync(() => {\n      setI(2);\n      assert.equal(getI(), 2);\n\n      assert.throws(\n        () =>\n          storage.transactionSync(() => {\n            setI(3);\n            assert.equal(getI(), 3);\n            throw new Error('foo');\n          }),\n        'Error: foo'\n      );\n\n      assert.equal(getI(), 2);\n    });\n    assert.equal(getI(), 2);\n  }\n\n  // Test joining two tables with overlapping names\n  {\n    sql.exec(`CREATE TABLE abc (a INT, b INT, c INT);`);\n    sql.exec(`CREATE TABLE cde (c INT, d INT, e INT);`);\n    sql.exec(`INSERT INTO abc VALUES (1,2,3),(4,5,6);`);\n    sql.exec(`INSERT INTO cde VALUES (7,8,9),(1,2,3);`);\n\n    const stmt = sql.prepare(`SELECT * FROM abc, cde`);\n\n    // In normal iteration, data is lost\n    const objResults = Array.from(stmt());\n    assert.equal(Object.values(objResults[0]).length, 5); // duplicate column 'c' dropped\n    assert.equal(Object.values(objResults[1]).length, 5); // duplicate column 'c' dropped\n    assert.equal(Object.values(objResults[2]).length, 5); // duplicate column 'c' dropped\n    assert.equal(Object.values(objResults[3]).length, 5); // duplicate column 'c' dropped\n\n    assert.equal(objResults[0].c, 7); // Value of 'c' is the second in the join\n    assert.equal(objResults[1].c, 1); // Value of 'c' is the second in the join\n    assert.equal(objResults[2].c, 7); // Value of 'c' is the second in the join\n    assert.equal(objResults[3].c, 1); // Value of 'c' is the second in the join\n\n    // Iterator has a 'columnNames' property, with .raw() that lets us get the full data\n    const iterator = stmt();\n    assert.deepEqual(iterator.columnNames, ['a', 'b', 'c', 'c', 'd', 'e']);\n    const rawResults = Array.from(iterator.raw());\n    assert.equal(rawResults.length, 4);\n    assert.deepEqual(rawResults[0], [1, 2, 3, 7, 8, 9]);\n    assert.deepEqual(rawResults[1], [1, 2, 3, 1, 2, 3]);\n    assert.deepEqual(rawResults[2], [4, 5, 6, 7, 8, 9]);\n    assert.deepEqual(rawResults[3], [4, 5, 6, 1, 2, 3]);\n\n    // After an iterator is consumed, columnNames can still be accessed.\n    assert.deepEqual(iterator.columnNames, ['a', 'b', 'c', 'c', 'd', 'e']);\n\n    // Also works with cursors returned from .exec\n    const execIterator = sql.exec(`SELECT * FROM abc, cde`);\n    assert.deepEqual(execIterator.columnNames, ['a', 'b', 'c', 'c', 'd', 'e']);\n    assert.equal(Array.from(execIterator.raw())[0].length, 6);\n\n    // Execute some sort of statement that returns no results, check that we can read the column\n    // names (which is empty).\n    const oneIterator = sql.exec(`UPDATE abc SET a = 1 WHERE b = 123542`);\n    assert.deepEqual(oneIterator.columnNames, []);\n  }\n\n  await scheduler.wait(1);\n\n  // Test for bug where a cursor constructed from a prepared statement didn't have a strong ref\n  // to the statement object.\n  {\n    sql.exec('CREATE TABLE iteratorTest (i INTEGER)');\n    sql.exec('INSERT INTO iteratorTest VALUES (0), (1)');\n\n    let q = sql.prepare('SELECT * FROM iteratorTest')();\n    let i = 0;\n    for (let row of q) {\n      assert.equal(row.i, i++);\n      gc();\n    }\n  }\n\n  {\n    // Test binding blobs & nulls\n    sql.exec(`CREATE TABLE test_blob (id INTEGER PRIMARY KEY, data BLOB);`);\n    sql.prepare(\n      `INSERT INTO test_blob(data) VALUES(?),(ZEROBLOB(10)),(null),(?);`\n    )(crypto.getRandomValues(new Uint8Array(12)), null);\n    const results = Array.from(sql.exec(`SELECT * FROM test_blob`));\n    assert.equal(results.length, 4);\n    assert.equal(results[0].data instanceof ArrayBuffer, true);\n    assert.equal(results[0].data.byteLength, 12);\n    assert.equal(results[1].data instanceof ArrayBuffer, true);\n    assert.equal(results[1].data.byteLength, 10);\n    assert.equal(results[2].data, null);\n    assert.equal(results[3].data, null);\n  }\n\n  // Can rename tables\n  sql.exec(`\n    CREATE TABLE beforerename (\n      id INTEGER\n    );\n  `);\n  sql.exec(`\n    ALTER TABLE beforerename\n    RENAME TO afterrename;\n  `);\n\n  sql.exec(`\n    CREATE TABLE altercolumns (\n      meta TEXT\n     );\n  `);\n  // Can add columns\n  sql.exec(`\n    ALTER TABLE altercolumns\n    ADD COLUMN tobedeleted TEXT;\n  `);\n  // Can rename columns within a table\n  sql.exec(`\n    ALTER TABLE altercolumns\n    RENAME COLUMN meta TO metadata\n  `);\n  // Can drop columns\n  sql.exec(`\n    ALTER TABLE altercolumns\n    DROP COLUMN tobedeleted\n  `);\n\n  // Can add columns with a CHECK\n  sql.exec(`\n    ALTER TABLE altercolumns\n    ADD COLUMN checked_col TEXT CHECK(checked_col IN ('A','B'));\n  `);\n\n  // The CHECK is enforced unless `ignore_check_constraints` is on\n  sql.exec(`INSERT INTO altercolumns(checked_col) VALUES ('A')`);\n  assert.throws(\n    () => sql.exec(`INSERT INTO altercolumns(checked_col) VALUES ('C')`),\n    /Error: CHECK constraint failed: checked_col IN \\('A','B'\\)/\n  );\n\n  // Because there's already a row, adding another column with a CHECK\n  // but no default value will fail\n  assert.throws(\n    () =>\n      sql.exec(`\n        ALTER TABLE altercolumns\n        ADD COLUMN second_col TEXT CHECK(second_col IS NOT NULL);\n      `),\n    /Error: CHECK constraint failed/\n  );\n\n  // ignore_check_constraints lets us bypass this for adding bad data\n  sql.exec(`PRAGMA ignore_check_constraints=ON;`);\n  sql.exec(`INSERT INTO altercolumns(checked_col) VALUES ('C')`);\n  assert.deepEqual(\n    [...sql.exec(`SELECT * FROM altercolumns`)],\n    [\n      { checked_col: 'A', metadata: null },\n      { checked_col: 'C', metadata: null },\n    ]\n  );\n\n  // Or even adding columns that start broken (because second_col is NULL)\n  sql.exec(`\n    ALTER TABLE altercolumns\n    ADD COLUMN second_col TEXT CHECK(second_col IS NOT NULL);\n  `);\n\n  // Turning check constraints back on doesn't actually do any checking, eagerly\n  sql.exec(`PRAGMA ignore_check_constraints=OFF;`);\n\n  // But anything else that CHECKs that table will now fail, like adding another CHECK\n  assert.throws(\n    () =>\n      sql.exec(`\n        ALTER TABLE altercolumns\n        ADD COLUMN third_col TEXT DEFAULT 'E' CHECK(third_col IN ('E','F'));\n      `),\n    /Error: CHECK constraint failed/\n  );\n\n  // And we can use quick_check to list out that there are now errors\n  // (although these messages aren't great):\n  assert.deepEqual(\n    [...sql.exec(`PRAGMA quick_check;`)],\n    [\n      { quick_check: 'CHECK constraint failed in altercolumns' },\n      { quick_check: 'CHECK constraint failed in altercolumns' },\n    ]\n  );\n\n  // Can't create another temp table\n  assert.throws(\n    () =>\n      sql.exec(`\n    CREATE TEMP TABLE tempy AS\n      SELECT * FROM sqlite_master;\n  `),\n    'Error: not authorized'\n  );\n\n  // Assert foreign keys can be truly turned off, not just deferred\n  await state.blockConcurrencyWhile(async () => {\n    sql.exec(`PRAGMA foreign_keys = OFF;`);\n  });\n  storage.transactionSync(() => {\n    sql.exec(`\n      CREATE TABLE A (\n        id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n        bId INTEGER NOT NULL REFERENCES B (id) ON DELETE RESTRICT ON UPDATE CASCADE\n      );\n      INSERT INTO A VALUES(1,1); -- this would throw a parse error with foreign keys on\n      CREATE TABLE B (\n        id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT\n      );\n    `);\n  });\n\n  // Until we've inserted the row into B, we can detect our\n  // foreign key violation (even with foreign_keys=OFF)\n  assert.deepEqual(Array.from(sql.exec(`pragma foreign_key_check;`)), [\n    { table: 'A', rowid: 1, parent: 'B', fkid: 0 },\n  ]);\n  sql.exec(`INSERT INTO B VALUES (1);`);\n  assert.deepEqual(Array.from(sql.exec(`pragma foreign_key_check;`)), []);\n\n  // Restore foreign keys for the rest of the tests\n  await state.blockConcurrencyWhile(async () => {\n    sql.exec(`PRAGMA foreign_keys = ON;`);\n  });\n\n  // Verify caching.\n  {\n    let isCached = (q) => {\n      let cursor = sql.exec(q);\n      cursor.toArray();\n      return cursor.reusedCachedQueryForTest;\n    };\n\n    // Query based on literal string is cached.\n    assert.equal(false, isCached('SELECT 179321'));\n    assert.equal(true, isCached('SELECT 179321'));\n    assert.equal(true, isCached('SELECT 179321'));\n\n    // Query based on computed string is cached.\n    assert.equal(false, isCached('SELECT \"' + 'x'.repeat(4) + '\"'));\n    assert.equal(true, isCached('SELECT \"' + 'x'.repeat(4) + '\"'));\n    assert.equal(true, isCached('SELECT \"' + 'x'.repeat(4) + '\"'));\n  }\n\n  // Verify that if we alter a table, cached statements continue to work.\n  {\n    sql.exec('CREATE TABLE alterTableTest (a INTEGER)');\n    sql.exec('INSERT INTO alterTableTest VALUES (?)', 1);\n    assert.deepStrictEqual(sql.exec('SELECT * FROM alterTableTest').toArray(), [\n      { a: 1 },\n    ]);\n    sql.exec('ALTER TABLE alterTableTest ADD COLUMN b INTEGER').toArray();\n    assert.deepStrictEqual(sql.exec('SELECT * FROM alterTableTest').toArray(), [\n      { a: 1, b: null },\n    ]);\n    sql.exec('INSERT INTO alterTableTest VALUES (?, ?)', 2, 2);\n    assert.deepStrictEqual(sql.exec('SELECT * FROM alterTableTest').toArray(), [\n      { a: 1, b: null },\n      { a: 2, b: 2 },\n    ]);\n    sql.exec('INSERT INTO alterTableTest VALUES (?, ?)', 3, 3);\n    assert.deepStrictEqual(sql.exec('SELECT * FROM alterTableTest').toArray(), [\n      { a: 1, b: null },\n      { a: 2, b: 2 },\n      { a: 3, b: 3 },\n    ]);\n  }\n}\n\nasync function testIoStats(storage) {\n  const sql = storage.sql;\n\n  sql.exec(`CREATE TABLE tbl (id INTEGER PRIMARY KEY, value TEXT)`);\n  sql.exec(\n    `INSERT INTO tbl (id, value) VALUES (?, ?)`,\n    100000,\n    'arbitrary-initial-value'\n  );\n  await scheduler.wait(1);\n\n  // When writing, the rowsWritten count goes up.\n  {\n    const cursor = sql.exec(\n      `INSERT INTO tbl (id, value) VALUES (?, ?)`,\n      1,\n      'arbitrary-value'\n    );\n    Array.from(cursor); // Consume all the results\n    assert.equal(cursor.rowsWritten, 1);\n  }\n\n  // When reading, the rowsRead count goes up.\n  {\n    const cursor = sql.exec(`SELECT * FROM tbl`);\n    Array.from(cursor); // Consume all the results\n    assert.equal(cursor.rowsRead, 2);\n  }\n\n  // Each invocation of a prepared statement gets its own counters.\n  {\n    const id1 = 101;\n    const id2 = 202;\n\n    const prepared = sql.prepare(`INSERT INTO tbl (id, value) VALUES (?, ?)`);\n    const cursor123 = prepared(id1, 'value1');\n    Array.from(cursor123);\n    assert.equal(cursor123.rowsWritten, 1);\n\n    const cursor456 = prepared(id2, 'value2');\n    Array.from(cursor456);\n    assert.equal(cursor456.rowsWritten, 1);\n    assert.equal(cursor123.rowsWritten, 1); // remained unchanged\n  }\n\n  // Row counters are updated as you consume the cursor.\n  {\n    sql.exec(`DELETE FROM tbl`);\n    const prepared = sql.prepare(`INSERT INTO tbl (id, value) VALUES (?, ?)`);\n    for (let i = 1; i <= 10; i++) {\n      Array.from(prepared(i, 'value' + i));\n    }\n\n    const cursor = sql.exec(`SELECT * FROM tbl`);\n    const resultsIterator = cursor[Symbol.iterator]();\n    let rowsSeen = 0;\n    while (true) {\n      const result = resultsIterator.next();\n      if (result.done) {\n        assert.equal(10, cursor.rowsRead);\n        break;\n      }\n      // + 1 because the cursor is always one result ahead of what has been returned -- but there\n      // are only 10 rows total.\n      assert.equal(Math.min(++rowsSeen + 1, 10), cursor.rowsRead);\n    }\n  }\n\n  // Row counters can track interleaved cursors\n  {\n    const join = [];\n    const colCounts = [];\n    // In-JS joining of two tables should be possible:\n    const rows = sql.exec(`SELECT * FROM abc`);\n    for (let row of rows) {\n      const cols = sql.exec(`SELECT * FROM cde`);\n      for (let col of cols) {\n        join.push({ row, col });\n      }\n      colCounts.push(cols.rowsRead);\n    }\n    assert.deepEqual(join, [\n      { col: { c: 7, d: 8, e: 9 }, row: { a: 1, b: 2, c: 3 } },\n      { col: { c: 1, d: 2, e: 3 }, row: { a: 1, b: 2, c: 3 } },\n      { col: { c: 7, d: 8, e: 9 }, row: { a: 4, b: 5, c: 6 } },\n      { col: { c: 1, d: 2, e: 3 }, row: { a: 4, b: 5, c: 6 } },\n    ]);\n    assert.deepEqual(rows.rowsRead, 2);\n    assert.deepEqual(colCounts, [2, 2]);\n  }\n\n  // Temporary tables (i.e. for IN clauses) don't contribute to rowsWritten\n  {\n    const cursor = sql.exec(`SELECT * FROM abc WHERE a IN (1,2,3,4,5,6)`);\n    const rows = Array.from(cursor);\n    assert.deepEqual(cursor.rowsRead, 2);\n    assert.deepEqual(cursor.rowsWritten, 0);\n  }\n}\n\nasync function testForeignKeys(storage) {\n  const sql = storage.sql;\n\n  // Test defer_foreign_keys\n  {\n    sql.exec(`CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);`);\n    sql.exec(\n      `CREATE TABLE posts (id INTEGER PRIMARY KEY, user_id INTEGER, content TEXT, FOREIGN KEY(user_id) REFERENCES users(id));`\n    );\n\n    await scheduler.wait(1);\n\n    // By default, primary keys are enforced:\n    assert.throws(\n      () =>\n        sql.exec(\n          `INSERT INTO posts (user_id, content) VALUES (?, ?)`,\n          1,\n          'Post 1'\n        ),\n      /Error: FOREIGN KEY constraint failed/\n    );\n\n    // Transactions fail immediately too\n    let passed_first_statement = false;\n    assert.throws(\n      () =>\n        storage.transactionSync(() => {\n          sql.exec(\n            `INSERT INTO posts (user_id, content) VALUES (?, ?)`,\n            1,\n            'Post 1'\n          );\n          passed_first_statement = true;\n        }),\n      /Error: FOREIGN KEY constraint failed/\n    );\n    assert.equal(passed_first_statement, false);\n\n    await scheduler.wait(1);\n\n    // With defer_foreign_keys, we can insert things out-of-order within transactions,\n    // as long as the data is valid by the end.\n    storage.transactionSync(() => {\n      sql.exec(`PRAGMA defer_foreign_keys=ON;`);\n      sql.exec(\n        `INSERT INTO posts (user_id, content) VALUES (?, ?)`,\n        1,\n        'Post 1'\n      );\n      sql.exec(`INSERT INTO users VALUES (?, ?)`, 1, 'Alice');\n    });\n\n    await scheduler.wait(1);\n\n    // But if we use defer_foreign_keys but try to commit, it resets the DO\n    storage.transactionSync(() => {\n      sql.exec(`PRAGMA defer_foreign_keys=ON;`);\n      sql.exec(\n        `INSERT INTO posts (user_id, content) VALUES (?, ?)`,\n        2,\n        'Post 2'\n      );\n    });\n  }\n}\n\nasync function testStreamingIngestion(request, storage) {\n  const { sql } = storage;\n\n  sql.exec(`CREATE TABLE streaming(val TEXT);`);\n\n  await storage.transaction(async () => {\n    const stream = request.body.pipeThrough(new TextDecoderStream());\n    let buffer = '';\n\n    for await (const chunk of stream) {\n      // Append the new chunk to the existing buffer\n      buffer += chunk;\n\n      // Ingest any complete statements and snip those chars off the buffer\n      buffer = sql.ingest(buffer).remainder;\n    }\n  });\n\n  // Verify exactly 36 rows were added\n  assert.deepEqual(Array.from(sql.exec(`SELECT count(*) FROM streaming`)), [\n    { 'count(*)': 36 },\n  ]);\n  assert.deepEqual(\n    Array.from(sql.exec(`SELECT * FROM streaming WHERE val LIKE 'f%'`)),\n    [\n      { val: 'f: 😳' },\n      { val: 'f: 🫠' },\n      { val: 'f: 🙃' },\n      { val: 'f: 🤡' },\n      { val: 'f: 🥺' },\n      { val: 'f: 🔥😎🔥' },\n    ]\n  );\n}\n\nexport class DurableObjectExample extends DurableObject {\n  constructor(state, env) {\n    super(state, env);\n    this.state = state;\n  }\n\n  async fetch(req) {\n    if (req.url.endsWith('/sql-test')) {\n      await test(this.state);\n      return Response.json({ ok: true });\n    } else if (req.url.endsWith('/sql-test-foreign-keys')) {\n      await testForeignKeys(this.state.storage);\n      return Response.json({ ok: true });\n    } else if (req.url.endsWith('/increment')) {\n      let val = (await this.state.storage.get('counter')) || 0;\n      ++val;\n      this.state.storage.put('counter', val);\n      return Response.json(val);\n    } else if (req.url.endsWith('/break')) {\n      // This `put()` should be discarded due to the actor aborting immediately after.\n      this.state.storage.put('counter', 888);\n\n      // Abort the actor, which also cancels unflushed writes.\n      this.state.abort('test broken');\n\n      // abort() always throws.\n      throw new Error(\"can't get here\");\n    } else if (req.url.endsWith('/sql-test-io-stats')) {\n      await testIoStats(this.state.storage);\n      return Response.json({ ok: true });\n    } else if (req.url.endsWith('/streaming-ingestion')) {\n      await testStreamingIngestion(req, this.state.storage);\n      return Response.json({ ok: true });\n    } else if (req.url.endsWith('/deleteAll')) {\n      this.state.storage.put('counter', 888); // will be deleted\n      this.state.storage.deleteAll();\n      assert.strictEqual(await this.state.storage.get('counter'), undefined);\n      return Response.json({ ok: true });\n    }\n\n    throw new Error('unknown url: ' + req.url);\n  }\n\n  async testRollbackKvInit() {\n    // Test what happens if initialization of the _cf_KV table gets rolled back.\n\n    try {\n      this.state.storage.transactionSync(() => {\n        // Cause KV table to be initialized.\n        this.state.storage.put('foo', 123);\n\n        // Roll back the transaction by throwing.\n        throw new Error('bar');\n      });\n      throw new Error('expected error');\n    } catch (err) {\n      if (err.message != 'bar') throw err;\n    }\n\n    // Now try to put to KV again. This will create the `_cf_KV` table again.\n    await this.state.storage.put('foo', 456);\n  }\n\n  async testRollbackAlarmInit() {\n    // Much like testRollbackKvInit() but for alarms.\n\n    try {\n      this.state.storage.transactionSync(() => {\n        // Cause KV table to be initialized.\n        this.state.storage.setAlarm(Date.now() + 86400 * 365);\n\n        // Roll back the transaction by throwing.\n        throw new Error('bar');\n      });\n      throw new Error('expected error');\n    } catch (err) {\n      if (err.message != 'bar') throw err;\n    }\n\n    assert.strictEqual(await this.state.storage.getAlarm(), null);\n    await this.state.storage.setAlarm(Date.now() + 86400 * 365);\n  }\n\n  async alarm() {}\n\n  async testMultiStatement() {\n    // Performing this PRAGMA will cause sqlite to invalidate prepared statements and re-compile\n    // them the next time they are executed. (Probably, many other pragmas would have the same\n    // effect, but this is the one that we observed causing issues.)\n    //\n    // In particular, the prepared statement ActorSqlite::beginTxn, which is simply\n    // `BEGIN TRANSACTION`, will be invalidated and recompiled on the next invocation.\n    //\n    // When we perform our multi-statement exec below, the first line will invoke the\n    // `ActorSqlite::onWrite` callback, which will invoke `beginTxn`. Because `BEGIN TRANSACTION`\n    // must be recompiled, the SQLite authorizer callback will be invoked to check if it is\n    // authorized. But we use the authorizer callback to detect when SQLite has parsed a statement\n    // as a transaction statement. At one point, we had a bug where we incorrectly thought that\n    // the authorizer was being called on behalf of the statement we were trying to parse and\n    // execute, namely, `CREATE TABLE items...`. We therefore incorrectly made note that this\n    // statement was beginning a transaction. This led the transaction state tracking to become\n    // all wrong!\n    //\n    // This only turned out to be an issue when performing a multi-statement exec(), because in\n    // this case all statements except the last are executed inside the parse loop, which is why\n    // we misinterpreted the authorizer callback.\n    this.state.storage.sql.exec('PRAGMA case_sensitive_like = TRUE');\n\n    let cursor = this.state.storage.sql.exec(`\n      CREATE TABLE items(i INTEGER, s TEXT);\n      CREATE INDEX itemsIdx ON items(s);\n      INSERT INTO items VALUES (123, \"abc\");\n      INSERT INTO items VALUES (456, \"def\");\n      SELECT i FROM items WHERE s = \"abc\";\n    `);\n\n    assert.deepEqual([...cursor], [{ i: 123 }]);\n  }\n\n  async testSessionsAPIBookmark(previousBookmark) {\n    if (previousBookmark) {\n      await this.state.storage.waitForBookmark(previousBookmark);\n    }\n    let bookmark = await this.state.storage.getCurrentBookmark();\n    if (previousBookmark) {\n      assert.ok(previousBookmark < bookmark, \"new bookmark didn't advance!\");\n    }\n    return bookmark;\n  }\n\n  async createStringTable() {\n    this.state.storage.sql.exec(\n      'CREATE TABLE IF NOT EXISTS string_table (id INTEGER PRIMARY KEY, data BLOB)'\n    );\n  }\n\n  async getStringTableIds() {\n    return Array.from(\n      this.state.storage.sql.exec('SELECT id FROM string_table'),\n      (x) => x.id\n    );\n  }\n\n  async runActorFunc(name) {\n    return actorFuncs[name](this.state);\n  }\n}\n\nexport default {\n  async test(ctrl, env, ctx) {\n    let id = env.ns.idFromName('A');\n    let obj = env.ns.get(id);\n\n    // Now let's test persistence through breakage and atomic write coalescing.\n    let doReq = async (path, init = {}) => {\n      let resp = await obj.fetch('http://foo/' + path, init);\n      return await resp.json();\n    };\n\n    // Test SQL API\n    assert.deepEqual(await doReq('sql-test'), { ok: true });\n\n    // Test SQL IO stats\n    assert.deepEqual(await doReq('sql-test-io-stats'), { ok: true });\n\n    // Test SQL streaming ingestion\n    assert.deepEqual(\n      await doReq('streaming-ingestion', {\n        method: 'POST',\n        body: new ReadableStream({\n          async start(controller) {\n            const data = new TextEncoder().encode(INSERT_36_ROWS);\n\n            // Pick a value for chunkSize that splits the first emoji in half\n            const chunkSize = INSERT_36_ROWS.indexOf('😳') + 1;\n            assert.equal(chunkSize, 35); // Validate we're getting the value we expect\n\n            // Send each chunk with a wait of 1ms in between\n            for (\n              let offset = 0;\n              offset < data.length - 1;\n              offset += chunkSize\n            ) {\n              controller.enqueue(data.slice(offset, offset + chunkSize));\n              await scheduler.wait(1);\n            }\n\n            controller.close();\n          },\n        }),\n      }),\n      { ok: true }\n    );\n\n    // Test defer_foreign_keys (explodes the DO)\n    await assert.rejects(async () => {\n      await doReq('sql-test-foreign-keys');\n    }, /constraints were violated: FOREIGN KEY constraint failed: SQLITE_CONSTRAINT/);\n\n    // Since the DO was exploded, reusing the stub dosen't work.\n    await assert.rejects(async () => {\n      await doReq('increment');\n    }, /constraints were violated: FOREIGN KEY constraint failed: SQLITE_CONSTRAINT/);\n\n    // Get a new stub.\n    obj = env.ns.get(id);\n\n    // Some increments.\n    assert.equal(await doReq('increment'), 1);\n    assert.equal(await doReq('increment'), 2);\n\n    // Now induce a failure.\n    await assert.rejects(\n      async () => {\n        await doReq('break');\n      },\n      (err) => err.message === 'test broken' && err.durableObjectReset\n    );\n\n    // Get a new stub.\n    obj = env.ns.get(id);\n\n    // Everything's still consistent.\n    assert.equal(await doReq('increment'), 3);\n\n    // Delete all: increments start over\n    await doReq('deleteAll');\n    assert.equal(await doReq('increment'), 1);\n    assert.equal(await doReq('increment'), 2);\n  },\n};\n\nexport let testRollbackKvInit = {\n  async test(ctrl, env, ctx) {\n    let stub = env.ns.get(env.ns.idFromName('rollback-kv-test'));\n    await stub.testRollbackKvInit();\n    await stub.testRollbackAlarmInit();\n  },\n};\n\nexport let testMultiStatement = {\n  async test(ctrl, env, ctx) {\n    let stub = env.ns.get(env.ns.idFromName('multi-statement-test'));\n    await stub.testMultiStatement();\n  },\n};\n\nconst INSERT_36_ROWS = ['a', 'b', 'c', 'd', 'e', 'f']\n  .map(\n    (prefix) =>\n      `INSERT INTO streaming VALUES ${['😳', '🫠', '🙃', '🤡', '🥺', '🔥😎🔥']\n        .map((suffix) => `('${prefix}: ${suffix}')`)\n        .join(',')};`\n  )\n  .join(' ');\n\nexport let testSessionsAPIBookmark = {\n  async test(ctrl, env, ctx) {\n    let stub = env.ns.get(env.ns.idFromName('sessions-api-bookmark-test'));\n    let bookmark = undefined;\n    for (let i = 0; i < 20; ++i) {\n      bookmark = await stub.testSessionsAPIBookmark(bookmark);\n    }\n  },\n};\n\nexport let testAutoRollBackOnCriticalError = {\n  async test(ctrl, env, ctx) {\n    let id = env.ns.idFromName('auto-rollback-on-critical-error-test');\n    let stub = env.ns.get(id);\n    await stub.createStringTable();\n\n    // Even though the DO function catches and handles all exceptions, we still expect it to fail\n    // with the critical exception, due to the output gate being broken with it.\n    await assert.rejects(async () => {\n      await stub.runActorFunc('doAutoRollBackOnCriticalError');\n    }, /^Error: database or disk is full: SQLITE_FULL/);\n\n    // Get a new stub since the old stub is broken due to critical error\n    stub = env.ns.get(id);\n    // We expect only the first, committed row to be present:\n    assert.deepStrictEqual(await stub.getStringTableIds(), [1]);\n  },\n};\nactorFuncs.doAutoRollBackOnCriticalError = async (state) => {\n  // Limit size of db so we can trigger a SQLITE_FULL error\n  state.storage.sql.setMaxPageCountForTest(10);\n\n  // Add a row as part of an implicit transaction, and wait for it to commit.\n  state.storage.sql.exec('INSERT INTO string_table VALUES (?, ?)', 1, 'a');\n  await state.storage.sync();\n\n  // Add another row as part of a new implicit transaction\n  state.storage.sql.exec('INSERT INTO string_table VALUES (?, ?)', 2, 'a');\n\n  // Try to add a row that is too big for the database.  We expect this to fail with a critical\n  // error that rolls back the current implicit transaction and breaks the output gate:\n  assert.throws(() => {\n    state.storage.sql.exec(\n      'INSERT INTO string_table VALUES (?, ?)',\n      3,\n      'a'.repeat(1000000)\n    );\n  }, /^Error: database or disk is full: SQLITE_FULL/);\n\n  // Further storage ops are expected to fail because we've cached the critical error:\n  assert.throws(() => {\n    state.storage.sql.exec('INSERT INTO string_table VALUES (?, ?)', 4, 'a');\n  }, /^Error: database or disk is full: SQLITE_FULL/);\n};\n\nexport let testCriticalErrorOnTransactionSyncRollback = {\n  async test(ctrl, env, ctx) {\n    let id = env.ns.idFromName('critical-error-on-transaction-sync-rollback');\n    let stub = env.ns.get(id);\n    await stub.createStringTable();\n\n    await assert.rejects(async () => {\n      await stub.runActorFunc('doCriticalErrorOnTransactionSyncRollback');\n    }, /^Error: database or disk is full: SQLITE_FULL/);\n\n    // Get a new stub since the old stub is broken due to critical error\n    stub = env.ns.get(id);\n    // We expect only the first, committed row to be present:\n    assert.deepStrictEqual(await stub.getStringTableIds(), [1]);\n  },\n};\nactorFuncs.doCriticalErrorOnTransactionSyncRollback = async (state) => {\n  // Limit size of db so we can trigger a SQLITE_FULL error\n  state.storage.sql.setMaxPageCountForTest(10);\n\n  // Add a row as part of an implicit transaction, and wait for it to commit.\n  state.storage.sql.exec('INSERT INTO string_table VALUES (?, ?)', 1, 'a');\n  await state.storage.sync();\n\n  // Add another row as part of a new implicit transaction.  We expect this to also get rolled\n  // back when the subsequent transactionSync() fails.\n  state.storage.sql.exec('INSERT INTO string_table VALUES (?, ?)', 2, 'a');\n\n  // Try to add a row that is too big for the database, within an explicit synchronous\n  // transaction.  We expect this to fail with a critical error that rolls back the explicit\n  // transaction and breaks the output gate.  Earlier versions of the code failed here with\n  // an internal error \"no such savepoint: _cf_sync_savepoint_0\" when trying to roll back.\n  assert.throws(() => {\n    state.storage.transactionSync(() => {\n      assert.throws(() => {\n        state.storage.sql.exec(\n          'INSERT INTO string_table VALUES (?, ?)',\n          3,\n          'a'.repeat(1000000)\n        );\n      }, /^Error: database or disk is full: SQLITE_FULL/);\n\n      // Throw an exception to make transactionSync() attempt to roll back the transaction:\n      throw new Error('an_escaping_exception_to_trigger_rollback');\n    });\n  }, /^Error: an_escaping_exception_to_trigger_rollback/);\n};\n\nexport let testCriticalErrorOnTransactionSyncCommit = {\n  async test(ctrl, env, ctx) {\n    let id = env.ns.idFromName('critical-error-on-transaction-sync-commit');\n    let stub = env.ns.get(id);\n    await stub.createStringTable();\n\n    await assert.rejects(async () => {\n      await stub.runActorFunc('doCriticalErrorOnTransactionSyncCommit');\n    }, /^Error: database or disk is full: SQLITE_FULL/);\n\n    // Get a new stub since the old stub is broken due to critical error\n    stub = env.ns.get(id);\n    // We expect only the first, committed row to be present:\n    assert.deepStrictEqual(await stub.getStringTableIds(), [1]);\n  },\n};\nactorFuncs.doCriticalErrorOnTransactionSyncCommit = async (state) => {\n  // Limit size of db so we can trigger a SQLITE_FULL error\n  state.storage.sql.setMaxPageCountForTest(10);\n\n  // Add a row as part of an implicit transaction, and wait for it to commit.\n  state.storage.sql.exec('INSERT INTO string_table VALUES (?, ?)', 1, 'a');\n  await state.storage.sync();\n\n  // Add another row as part of a new implicit transaction.  We expect this to also get rolled\n  // back when the subsequent transactionSync() fails.\n  state.storage.sql.exec('INSERT INTO string_table VALUES (?, ?)', 2, 'a');\n\n  // Try to add a row that is too big for the database, within an explicit synchronous\n  // transaction.  We expect this to fail with a critical error that rolls back the explicit\n  // transaction and breaks the output gate.  Earlier versions of the code failed here with\n  // internal errors \"no such savepoint: _cf_sync_savepoint_0\" when trying to commit, then roll\n  // back.\n  assert.throws(() => {\n    state.storage.transactionSync(() => {\n      assert.throws(() => {\n        state.storage.sql.exec(\n          'INSERT INTO string_table VALUES (?, ?)',\n          3,\n          'a'.repeat(1000000)\n        );\n      }, /^Error: database or disk is full: SQLITE_FULL/);\n      // Because the lambda completes successfully, transactionSync() will still try to commit the\n      // transaction.\n    });\n  }, /^Error: Cannot commit transaction due to an earlier SQL critical error/);\n};\n\nexport let testCriticalErrorOnTransactionRollback = {\n  async test(ctrl, env, ctx) {\n    let id = env.ns.idFromName('critical-error-on-transaction-rollback');\n    let stub = env.ns.get(id);\n    await stub.createStringTable();\n\n    await assert.rejects(async () => {\n      await stub.runActorFunc('doCriticalErrorOnTransactionRollback');\n    }, /^Error: database or disk is full: SQLITE_FULL/);\n\n    // Get a new stub since the old stub is broken due to critical error\n    stub = env.ns.get(id);\n    // We expect only the first two committed rows to be present:\n    assert.deepStrictEqual(await stub.getStringTableIds(), [1, 2]);\n  },\n};\nactorFuncs.doCriticalErrorOnTransactionRollback = async (state) => {\n  // Limit size of db so we can trigger a SQLITE_FULL error\n  state.storage.sql.setMaxPageCountForTest(10);\n\n  // Add a row as part of an implicit transaction, and wait for it to commit.\n  state.storage.sql.exec('INSERT INTO string_table VALUES (?, ?)', 1, 'a');\n  await state.storage.sync();\n\n  // Add another row as part of a new implicit transaction.  We expect this to be committed\n  // prior to the failing explicit transaction.\n  state.storage.sql.exec('INSERT INTO string_table VALUES (?, ?)', 2, 'a');\n\n  // Try to add a row that is too big for the database, within an explicit asynchronous\n  // transaction.  We expect this to fail with a critical error that rolls back the explicit\n  // transaction and breaks the output gate.\n  await assert.rejects(async () => {\n    await state.storage.transaction(async (txn) => {\n      assert.throws(() => {\n        state.storage.sql.exec(\n          'INSERT INTO string_table VALUES (?, ?)',\n          3,\n          'a'.repeat(1000000)\n        );\n      }, /^Error: database or disk is full: SQLITE_FULL/);\n\n      // Explicitly roll back transaction.  In earlier versions of the code, this could throw\n      // \"no such savepoint: _cf_savepoint_0\" due to a missing brokenness check.\n      txn.rollback();\n    });\n  }, /^Error: database or disk is full: SQLITE_FULL/);\n};\n"
  },
  {
    "path": "src/workerd/api/tests/sql-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n    (name = \"TEST_TMPDIR\", disk = (writable = true)),\n    (name = \"tail\", worker = .tailWorker),\n  ],\n  extensions = [ (\n    modules = [\n      ( name = \"test:instrumentation-tail\", esModule = embed \"instrumentation-tail-worker.js\" ),\n    ]\n  ) ],\n  v8Flags = [ \"--expose-gc\" ],\n);\n\nconst mainWorker :Workerd.Worker = (\n\n  # \"experimental\" flag is needed to test `sql.ingest()` and `sql.prepare()`.\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\", \"streams_enable_constructors\", \"transformstream_enable_standard_constructor\", \"disable_fast_jsg_struct\"],\n\n  modules = [\n    (name = \"worker\", esModule = embed \"sql-test.js\"),\n  ],\n\n  durableObjectNamespaces = [\n    ( className = \"DurableObjectExample\",\n      uniqueKey = \"210bd0cbd803ef7883a1ee9d86cce06e\",\n      enableSql = true ),\n  ],\n\n  durableObjectStorage = (localDisk = \"TEST_TMPDIR\"),\n\n  bindings = [\n    (name = \"ns\", durableObjectNamespace = \"DurableObjectExample\"),\n  ],\n\n  streamingTails = [\"tail\"],\n);\n\n\nconst tailWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"sql-test-tail.js\")\n  ],\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\", \"disable_fast_jsg_struct\"],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/starttls-nodejs-server.js",
    "content": "// Copyright (c) 2017-2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n/*\n * This file serves a server that tries to upgrade tls connections and send messages.\n * This file is designed to run as a sidecar\n */\n\nconst net = require('node:net');\nconst tls = require('node:tls');\nconst assert = require('node:assert');\n\n// Create a self-signed certificate for TLS with proper SAN extension\nfunction createSelfSignedCert() {\n  const key = `-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDIWfGy2tRsqANt\nJ1F/52bIDzMDxlmSkDpu3U3Ehq6TmH2hNBcLOWuWLvG8Np9artnzk8QnodfN8yEJ\n0HRzZ6mRjVIUHJOb3+L1+0ePOM8dtWvG0AOd95K0T0imJRLPR18UjHl5OLE7mMS9\nCGHa6mDTGKzTcdxtpkjiyoNgfdKKSKzLplga5if36leGJ2+mEhOAc/cV1kqOx+hP\nVGAOz5p3OnkgolC8hZ3WTsAFEYMU0QoPNs7jVCVNGH9t3qPiWV2/7XaNQoMnMHgq\nyyljcvDo0O0dw0HKtBVIMhz5xHHGZqJNM/R36MeHzO+cIYhJz+ncknu62+IlQOCY\neyHuxvpRAgMBAAECggEAB3SXYqsze+6doAZ2SS7se3XbVWDgbOyKjB0Wm4FShkIG\nrMTCNcP3fbF2A+W5dNesWxzM0Be87thFCrcz2iaJoBW07/QnRwXkDXjCDzGTPX0G\ni3GqrMptbmHD55DaHBYBEwPuMkVajQfwjEM/VvThUQGqTrz+MaNeM3hLPsA34Tbl\nwigHK+4tyAFLkYkvsxXYHs1F23ey2ubFUyBI8gvfayOvr4MOVfEbQZsmz3IB5jjk\noK5EaMJuhty65pb9Pi6ncSbfVQ2aciNgHjZs/is/WQfQPB2jYBPpmaFQbZc2pseZ\n8zTLvF+GKZng4hQE1F+F6DUf9sxb54Eu1XqzqGlrdQKBgQDkQc5fJj72rF9RkiBN\nzeEK0Ngihm9Jzsb544i7DT/4jPWwa1+dt+kNVqguDbcxi4tCi+P8BwTnfX5M31d2\n/Si9nDBpP+WLLRHjFq2AQf05JvFc1/7s4KvWBrfKbwiN+uxrstB2lDuFp386B20U\nstsCBu/nawjt5lYY7S0zPokkJQKBgQDgs9rTJNnRuaaEpUxqRdR6zR3z0Qe1B1Ga\ny6z8OqiX3N+wM9uAcrTzGOtPKvB4glcJZrrQp0NSxkJVsFBzPBCfUfjIV/IiOqS1\nnE/rrEKEG7ZVkxDUsdeS8KiVKBJjLch/hrT0udgv4vndXqeaJuNzbD1yfiKbPnY5\nyGC78uqvvQKBgGUGMx6twMRQekeSEzYcXvP4hxCQy4SxPiOvbv7K2HtbeApDG6ik\nk0NSDVGExIXrKxGi9J7BRIxoYJQJbZ6+YV+6VzreCuxUYExP5y6TBk5bTAw5lRym\nO6eYhZPVHMYqPqVUGSvCY629+nNmggLdPk1hYKDeIK+aeJTDtHOvw+b5AoGAZI74\nwf8+34WWyMv026ZuhZpf6ipEqbYhxgWaX7KcmoHFNWSvudcbtaMUQ3Sy8ytZaiKo\nPhJspZGGRDTIfBmIUtRrYrVA7iKSbZgLiCuqBNcmDTvoj1cbY24B8+Zf/DSUAsY1\nG0RERIHuUiw3E1yN86yf/yoFsLYOUKOk7teyQX0CgYBFUzUeVszODI/1NeKuGdoR\n1uJk2gt7hH4t7GdX4G78h3i6P5pa7qSyUXqBm53nXrYhEFRiVgDh5BoRnoKKWomj\nIDidHZX3fMoQvdKAT8sUbT/Q3KKWPFqGv7frdpQe+JM0DwwjjPrMtUWhuve455XW\nbCKgoxoaqEPUzY9CyI+RZg==\n-----END PRIVATE KEY-----`;\n\n  const cert = `-----BEGIN CERTIFICATE-----\nMIIDmTCCAoGgAwIBAgIUFdaq1aN7zHoSsmLp6ItxnM1VOPEwDQYJKoZIhvcNAQEL\nBQAwTjELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFRlc3QxDTALBgNVBAcMBFRlc3Qx\nDTALBgNVBAoMBFRlc3QxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yNTA3MjYwNjI3\nMDZaFw0yNjA3MjYwNjI3MDZaME4xCzAJBgNVBAYTAlVTMQ0wCwYDVQQIDARUZXN0\nMQ0wCwYDVQQHDARUZXN0MQ0wCwYDVQQKDARUZXN0MRIwEAYDVQQDDAlsb2NhbGhv\nc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIWfGy2tRsqANtJ1F/\n52bIDzMDxlmSkDpu3U3Ehq6TmH2hNBcLOWuWLvG8Np9artnzk8QnodfN8yEJ0HRz\nZ6mRjVIUHJOb3+L1+0ePOM8dtWvG0AOd95K0T0imJRLPR18UjHl5OLE7mMS9CGHa\n6mDTGKzTcdxtpkjiyoNgfdKKSKzLplga5if36leGJ2+mEhOAc/cV1kqOx+hPVGAO\nz5p3OnkgolC8hZ3WTsAFEYMU0QoPNs7jVCVNGH9t3qPiWV2/7XaNQoMnMHgqyylj\ncvDo0O0dw0HKtBVIMhz5xHHGZqJNM/R36MeHzO+cIYhJz+ncknu62+IlQOCYeyHu\nxvpRAgMBAAGjbzBtMB0GA1UdDgQWBBQ6R0NLwjufqWjT75cFdzHW9bh2DDAfBgNV\nHSMEGDAWgBQ6R0NLwjufqWjT75cFdzHW9bh2DDAPBgNVHRMBAf8EBTADAQH/MBoG\nA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAToR4\nCaI9HAfSSXE+6fPthp+qrwPmfx3rW0RskpjKuqemZmIK7ydU9pcYuGtc6CPen614\nRfFoaWtPltbNU0KV79P4zTRNYxqTKcEyhsjyAGbLA+bJtJE3hlDrfPGVyepZETXE\n7Ig4XPyXi4M+WmvLboAF2dHC+H1XoWp3agIN45VRnr5uPVNX19dTbr0gc3WxLEUH\nN839sKGVB9GVaQhF/4Z8ia0bluirf+6SNAaN/veJA40ixGEkHN3gqX4ZTZWl5rji\n3cLdth83wmueKxiBp8ov78ubmdPiBsyIVrsb8jEsxRPAKX8gEx09S/yIIs1ZEGi9\nwG73FlfFCc09zjmYww==\n-----END CERTIFICATE-----`;\n\n  return { key, cert };\n}\n\nconst serverCA = net\n  .createServer((s) => {\n    console.log('ServerCA: New connection received');\n\n    // Send initial greeting\n    s.write('HELLO\\n');\n\n    // Wait for one response then upgrade to TLS\n    s.once('data', (data) => {\n      const response = data.toString().trim();\n      console.log('ServerCA: Received response:', response);\n\n      if (response === 'HELLO_BACK') {\n        s.write('START_TLS\\n', () => {\n          console.log('ServerCA: Sent START_TLS, upgrading to TLS');\n\n          // Small delay to ensure START_TLS is sent\n          console.log('serverCA: Creating TLS socket');\n          const tlsSocket = new tls.TLSSocket(s, {\n            isServer: true,\n            server: serverCA,\n            secureContext: tls.createSecureContext(createSelfSignedCert()),\n            requestCert: false,\n            SNICallback: (hostname, callback) => {\n              console.log('serverCA: SNI callback for hostname:', hostname);\n              assert.strictEqual(hostname, 'localhost');\n              callback(null, null);\n            },\n          });\n\n          console.log('serverCA: Setting up TLS event handlers');\n\n          tlsSocket.on('secure', () => {\n            console.log('serverCA: TLS handshake complete');\n\n            // Handle TLS data\n            tlsSocket.on('data', (data) => {\n              const message = data.toString().trim();\n              console.log('serverCA: Received TLS message:', message);\n\n              if (message === 'ping') {\n                console.log('serverCA: Sending pong response');\n                tlsSocket.write('pong\\n', (err) => {\n                  if (err) {\n                    console.log('serverCA: Error writing pong:', err);\n                  } else {\n                    console.log('serverCA: Pong sent successfully');\n                  }\n                });\n              }\n            });\n          });\n\n          tlsSocket.on('error', (err) => {\n            console.log('ServerCA TLS error:', err.message);\n          });\n\n          tlsSocket.on('close', () => {\n            console.log('serverCA: TLS socket closed');\n          });\n\n          console.log('serverCA: TLS socket created, waiting for handshake');\n\n          // The TLS handshake should start when the client initiates it\n        });\n      }\n    });\n\n    s.on('error', (err) => {\n      console.log('ServerCA socket error:', err.message);\n    });\n  })\n  .listen(process.env.STARTTLS_CA_PORT, () => {\n    console.info(`ServerCA listening on port ${serverCA.address().port}`);\n  });\n"
  },
  {
    "path": "src/workerd/api/tests/starttls-nodejs-test.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { connect } from 'cloudflare:sockets';\nimport { ok, strict as assert } from 'node:assert';\nimport { connect as tlsConnect } from 'node:tls';\nimport { connect as netConnect } from 'node:net';\n\nexport const checkPortsSetCorrectly = {\n  test(ctrl, env, ctx) {\n    const keys = ['STARTTLS_CA_PORT'];\n    for (const key of keys) {\n      assert.strictEqual(typeof env[key], 'string');\n      ok(env[key].length > 0);\n    }\n  },\n};\n\nexport const startTlsCATest = {\n  async test(ctrl, env, ctx) {\n    const opts = {\n      servername: 'localhost',\n      port: env.STARTTLS_CA_PORT,\n      rejectUnauthorized: true,\n    };\n\n    const socket = netConnect(opts.port);\n\n    // Wait for server's greeting then respond\n    socket.once('data', (data) => {\n      const greeting = data.toString().trim();\n      console.log('startTlsCATest: Received greeting:', greeting);\n\n      if (greeting === 'HELLO') {\n        console.log('startTlsCATest: Sending HELLO_BACK');\n        socket.write('HELLO_BACK\\n');\n\n        // Wait for START_TLS signal\n        socket.once('data', (data) => {\n          const signal = data.toString().trim();\n          console.log('startTlsCATest: Received signal:', signal);\n\n          if (signal === 'START_TLS') {\n            console.log('startTlsCATest: Received START_TLS, upgrading to TLS');\n\n            // Upgrade to TLS - pass socket in the options\n            const tlsSocket = tlsConnect(\n              {\n                ...opts,\n                socket: socket,\n              },\n              function () {\n                console.log(\n                  'startTlsCATest: TLS connection established successfully'\n                );\n\n                // Send ping message\n                console.log('startTlsCATest: Sending ping message');\n                this.write('ping\\n', (err) => {\n                  if (err) {\n                    console.log('startTlsCATest: Error writing ping:', err);\n                  } else {\n                    console.log('startTlsCATest: Ping sent successfully');\n                  }\n                });\n\n                // Wait for pong response\n                this.once('data', (data) => {\n                  const response = data.toString().trim();\n                  console.log('startTlsCATest: Received response:', response);\n\n                  // Assert response is 'pong'\n                  assert.strictEqual(\n                    response,\n                    'pong',\n                    'Expected pong response'\n                  );\n\n                  this.end();\n                });\n              }\n            );\n\n            tlsSocket.on('error', (err) => {\n              console.log('startTlsCATest: TLS connection error:', err.message);\n              throw err;\n            });\n          }\n        });\n      }\n    });\n\n    socket.on('error', (err) => {\n      console.log('startTlsCATest: Socket connection error:', err.message);\n      throw err;\n    });\n  },\n};\n\nexport const startTlsCloudflareTest = {\n  async test(ctrl, env, ctx) {\n    // Create a Cloudflare socket connection with STARTTLS\n    const socket = connect(`localhost:${env.STARTTLS_CA_PORT}`, {\n      secureTransport: 'starttls',\n    });\n\n    const writer = socket.writable.getWriter();\n    const reader = socket.readable.getReader();\n\n    try {\n      // Read HELLO greeting\n      const { value: greeting } = await reader.read();\n      const greetingText = new TextDecoder().decode(greeting).trim();\n      console.log('startTlsCfTest: Received greeting:', greetingText);\n\n      if (greetingText === 'HELLO') {\n        console.log('startTlsCfTest: Sending HELLO_BACK');\n        await writer.write(new TextEncoder().encode('HELLO_BACK\\n'));\n\n        // Read START_TLS signal\n        const { value: signal } = await reader.read();\n        const signalText = new TextDecoder().decode(signal).trim();\n        console.log('startTlsCfTest: Received signal:', signalText);\n\n        if (signalText === 'START_TLS') {\n          console.log('startTlsCfTest: Received START_TLS, upgrading to TLS');\n\n          // Release the reader and writer before upgrading\n          reader.releaseLock();\n          writer.releaseLock();\n\n          // Upgrade to TLS using Cloudflare socket's startTls\n          console.log('startTlsCfTest: About to start TLS');\n          const tlsSocket = socket.startTls();\n          console.log('startTlsCfTest: Started TLS');\n\n          await tlsSocket.opened;\n          console.log(\n            'startTlsCfTest: TLS connection established successfully'\n          );\n\n          // Get new writer and reader for TLS socket\n          const tlsWriter = tlsSocket.writable.getWriter();\n          const tlsReader = tlsSocket.readable.getReader();\n\n          // Send ping message\n          console.log('startTlsCfTest: Sending ping message');\n          await tlsWriter.write(new TextEncoder().encode('ping\\n'));\n\n          // Read pong response\n          const { value: response } = await tlsReader.read();\n          const responseText = new TextDecoder().decode(response).trim();\n          console.log('startTlsCfTest: Received response:', responseText);\n\n          // Assert response is 'pong'\n          assert.strictEqual(responseText, 'pong', 'Expected pong response');\n\n          // Close the socket\n          tlsReader.releaseLock();\n          tlsWriter.releaseLock();\n          await tlsSocket.close();\n        }\n      }\n    } catch (err) {\n      console.log('startTlsCfTest: Error:', err.message);\n      throw err;\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/starttls-nodejs-test.wd-test",
    "content": "# Copyright (c) 2017-2024 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\nusing Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (\n      name = \"main\",\n      worker = (\n        modules = [\n          (name = \"worker.js\", esModule = embed \"starttls-nodejs-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"experimental\"],\n        bindings = [\n          (name = \"STARTTLS_CA_PORT\", fromEnvironment = \"STARTTLS_CA_PORT\"),\n        ],\n      )\n    ),\n    ( name = \"internet\", \n      network = ( \n        allow = [\"private\"],\n        tlsOptions = (\n          trustedCertificates = [\n            embed \"starttls-server.pem\",\n          ],\n        ),\n      ) \n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/starttls-server.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDmTCCAoGgAwIBAgIUFdaq1aN7zHoSsmLp6ItxnM1VOPEwDQYJKoZIhvcNAQEL\nBQAwTjELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFRlc3QxDTALBgNVBAcMBFRlc3Qx\nDTALBgNVBAoMBFRlc3QxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yNTA3MjYwNjI3\nMDZaFw0yNjA3MjYwNjI3MDZaME4xCzAJBgNVBAYTAlVTMQ0wCwYDVQQIDARUZXN0\nMQ0wCwYDVQQHDARUZXN0MQ0wCwYDVQQKDARUZXN0MRIwEAYDVQQDDAlsb2NhbGhv\nc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIWfGy2tRsqANtJ1F/\n52bIDzMDxlmSkDpu3U3Ehq6TmH2hNBcLOWuWLvG8Np9artnzk8QnodfN8yEJ0HRz\nZ6mRjVIUHJOb3+L1+0ePOM8dtWvG0AOd95K0T0imJRLPR18UjHl5OLE7mMS9CGHa\n6mDTGKzTcdxtpkjiyoNgfdKKSKzLplga5if36leGJ2+mEhOAc/cV1kqOx+hPVGAO\nz5p3OnkgolC8hZ3WTsAFEYMU0QoPNs7jVCVNGH9t3qPiWV2/7XaNQoMnMHgqyylj\ncvDo0O0dw0HKtBVIMhz5xHHGZqJNM/R36MeHzO+cIYhJz+ncknu62+IlQOCYeyHu\nxvpRAgMBAAGjbzBtMB0GA1UdDgQWBBQ6R0NLwjufqWjT75cFdzHW9bh2DDAfBgNV\nHSMEGDAWgBQ6R0NLwjufqWjT75cFdzHW9bh2DDAPBgNVHRMBAf8EBTADAQH/MBoG\nA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAToR4\nCaI9HAfSSXE+6fPthp+qrwPmfx3rW0RskpjKuqemZmIK7ydU9pcYuGtc6CPen614\nRfFoaWtPltbNU0KV79P4zTRNYxqTKcEyhsjyAGbLA+bJtJE3hlDrfPGVyepZETXE\n7Ig4XPyXi4M+WmvLboAF2dHC+H1XoWp3agIN45VRnr5uPVNX19dTbr0gc3WxLEUH\nN839sKGVB9GVaQhF/4Z8ia0bluirf+6SNAaN/veJA40ixGEkHN3gqX4ZTZWl5rji\n3cLdth83wmueKxiBp8ov78ubmdPiBsyIVrsb8jEsxRPAKX8gEx09S/yIIs1ZEGi9\nwG73FlfFCc09zjmYww==\n-----END CERTIFICATE-----"
  },
  {
    "path": "src/workerd/api/tests/streams-async-iterator-test.js",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Tests for async iterator edge cases on ReadableStream.\n// These tests focus on iterator protocol methods (return, throw),\n// preventCancel option, and iteration over closed/errored streams.\n//\n// Test inspirations:\n// - Bun: test/js/web/streams/streams.test.js (Symbol.asyncIterator tests)\n// - Deno: tests/unit/streams_test.ts (async iterator tests)\n\nimport { strictEqual, ok, rejects, deepStrictEqual, throws } from 'node:assert';\n\n// Test that breaking out of for-await-of cancels the stream\n// Inspired by: Bun test/js/web/streams/streams.test.js\nexport const asyncIteratorBreakCancels = {\n  async test() {\n    let cancelled = false;\n    let cancelReason = null;\n\n    const rs = new ReadableStream({\n      pull(controller) {\n        controller.enqueue('chunk');\n      },\n      cancel(reason) {\n        cancelled = true;\n        cancelReason = reason;\n      },\n    });\n\n    const values = [];\n    for await (const chunk of rs) {\n      values.push(chunk);\n      if (values.length === 3) {\n        break;\n      }\n    }\n\n    strictEqual(values.length, 3);\n    ok(cancelled, 'stream should be cancelled after break');\n    strictEqual(cancelReason, undefined);\n  },\n};\n\n// Test calling return() explicitly on async iterator\n// Inspired by: Deno tests/unit/streams_test.ts\nexport const asyncIteratorReturnMethod = {\n  async test() {\n    let cancelled = false;\n\n    const rs = new ReadableStream({\n      pull(controller) {\n        controller.enqueue('chunk');\n      },\n      cancel() {\n        cancelled = true;\n      },\n    });\n\n    const iterator = rs[Symbol.asyncIterator]();\n\n    const first = await iterator.next();\n    strictEqual(first.value, 'chunk');\n    strictEqual(first.done, false);\n\n    const returnResult = await iterator.return('finished');\n    strictEqual(returnResult.done, true);\n\n    ok(cancelled, 'stream should be cancelled after return()');\n  },\n};\n\n// Test that return() followed by next() returns done\n// Inspired by: Deno tests/unit/streams_test.ts\nexport const asyncIteratorReturnThenNext = {\n  async test() {\n    const rs = new ReadableStream({\n      pull(controller) {\n        controller.enqueue('chunk');\n      },\n    });\n\n    const iterator = rs[Symbol.asyncIterator]();\n\n    await iterator.next();\n    await iterator.return();\n\n    const result = await iterator.next();\n    strictEqual(result.done, true);\n    strictEqual(result.value, undefined);\n  },\n};\n\n// Test values() with preventCancel: true\n// Inspired by: Bun test/js/web/streams/streams.test.js\nexport const asyncIteratorPreventCancel = {\n  async test() {\n    let cancelled = false;\n\n    const rs = new ReadableStream({\n      pull(controller) {\n        controller.enqueue('chunk');\n      },\n      cancel() {\n        cancelled = true;\n      },\n    });\n\n    const values = [];\n    for await (const chunk of rs.values({ preventCancel: true })) {\n      values.push(chunk);\n      if (values.length === 3) {\n        break;\n      }\n    }\n\n    strictEqual(values.length, 3);\n    ok(!cancelled, 'stream should NOT be cancelled with preventCancel: true');\n    ok(!rs.locked, 'stream should be unlocked');\n\n    const reader = rs.getReader();\n    const { value } = await reader.read();\n    strictEqual(value, 'chunk');\n    reader.releaseLock();\n  },\n};\n\n// Test iterating over an already-closed stream\n// Inspired by: Deno tests/unit/streams_test.ts\nexport const asyncIteratorOnClosedStream = {\n  async test() {\n    const rs = new ReadableStream({\n      start(controller) {\n        controller.enqueue('only-chunk');\n        controller.close();\n      },\n    });\n\n    const values = [];\n    for await (const chunk of rs) {\n      values.push(chunk);\n    }\n\n    deepStrictEqual(values, ['only-chunk']);\n  },\n};\n\n// Test iterating over an already-errored stream\n// Inspired by: Bun test/js/web/streams/streams.test.js\nexport const asyncIteratorOnErroredStream = {\n  async test() {\n    const rs = new ReadableStream({\n      start(controller) {\n        controller.error(new Error('Stream error'));\n      },\n    });\n\n    const values = [];\n    const iterate = async () => {\n      for await (const chunk of rs) {\n        values.push(chunk);\n      }\n    };\n\n    await rejects(iterate, { message: 'Stream error' });\n    strictEqual(values.length, 0);\n  },\n};\n\n// Test that getting an async iterator locks the stream\n// Inspired by: Bun test/js/web/streams/streams.test.js\nexport const asyncIteratorLocksStream = {\n  async test() {\n    const rs = new ReadableStream({\n      pull(controller) {\n        controller.enqueue('chunk');\n      },\n    });\n\n    ok(!rs.locked, 'stream should not be locked initially');\n\n    const iterator = rs[Symbol.asyncIterator]();\n\n    ok(rs.locked, 'stream should be locked after getting iterator');\n\n    throws(() => rs[Symbol.asyncIterator](), TypeError);\n\n    await iterator.return();\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/streams-async-iterator-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"streams-async-iterator-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"streams-async-iterator-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"streams_enable_constructors\", \"transformstream_enable_standard_constructor\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/streams-backpressure-test.js",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Tests for backpressure behavior in JS-backed streams.\n// These tests focus on desiredSize tracking, ready promise behavior,\n// and backpressure propagation through pipe chains.\n//\n// Test inspirations:\n// - Deno: tests/unit/streams_test.ts (parameterized count/delay tests)\n// - Deno: tests/node_compat/test-stream-readable-hwm-0.js (hwm=0 edge case)\n// - Bun: test/js/node/stream/node-stream.test.js (backpressure tests)\n// - Bun: test/js/node/http/node-http-backpressure.test.ts (HTTP-level backpressure)\n// - Bun: test/js/web/fetch/body-stream.test.ts (backpressure with various input lengths)\n\nimport { strictEqual, ok } from 'node:assert';\n\n// Test ReadableStream with highWaterMark = 0\n// Pull should only be called when there's an active read request\n// Inspired by: Deno tests/node_compat/test-stream-readable-hwm-0.js (hwm=0 edge case)\nexport const backpressureReadableHwmZero = {\n  async test() {\n    let pullCount = 0;\n    let controller;\n\n    const rs = new ReadableStream(\n      {\n        start(c) {\n          controller = c;\n        },\n        pull(c) {\n          pullCount++;\n          c.enqueue(pullCount);\n        },\n      },\n      { highWaterMark: 0 }\n    );\n\n    await scheduler.wait(10);\n    strictEqual(pullCount, 0, 'pull should not be called without read');\n    strictEqual(controller.desiredSize, 0, 'desiredSize should be 0');\n\n    const reader = rs.getReader();\n\n    const read1 = reader.read();\n    await scheduler.wait(1);\n    strictEqual(pullCount, 1, 'pull should be called once after read');\n\n    const result1 = await read1;\n    strictEqual(result1.value, 1);\n    strictEqual(result1.done, false);\n\n    strictEqual(controller.desiredSize, 0, 'desiredSize should remain 0');\n\n    const read2 = reader.read();\n    await scheduler.wait(1);\n    strictEqual(pullCount, 2, 'pull should be called again');\n\n    const result2 = await read2;\n    strictEqual(result2.value, 2);\n\n    reader.releaseLock();\n  },\n};\n\n// Test ReadableStream with highWaterMark = 1\n// Verify desiredSize transitions correctly\n// Inspired by: Deno tests/unit/streams_test.ts (parameterized hwm tests)\nexport const backpressureReadableHwmOne = {\n  async test() {\n    let pullCount = 0;\n\n    const rs = new ReadableStream(\n      {\n        start(c) {\n          strictEqual(c.desiredSize, 1, 'initial desiredSize should be 1');\n        },\n        pull(c) {\n          pullCount++;\n          c.enqueue(pullCount);\n        },\n      },\n      { highWaterMark: 1 }\n    );\n\n    const reader = rs.getReader();\n\n    const result1 = await reader.read();\n    strictEqual(result1.value, 1);\n    strictEqual(result1.done, false);\n\n    const result2 = await reader.read();\n    strictEqual(result2.value, 2);\n    strictEqual(result2.done, false);\n\n    reader.releaseLock();\n  },\n};\n\n// Test ReadableStream with highWaterMark = 64\n// Multiple chunks should be buffered\n// Inspired by: Bun test/js/web/streams/streams.test.js (large buffer tests)\nexport const backpressureReadableHwmLarge = {\n  async test() {\n    let pullCount = 0;\n    const MAX_PULLS = 100;\n\n    const rs = new ReadableStream(\n      {\n        pull(c) {\n          pullCount++;\n          if (pullCount <= MAX_PULLS) {\n            c.enqueue(pullCount);\n          } else {\n            c.close();\n          }\n        },\n      },\n      { highWaterMark: 64 }\n    );\n\n    const reader = rs.getReader();\n\n    const values = [];\n    while (true) {\n      const { value, done } = await reader.read();\n      if (done) break;\n      values.push(value);\n    }\n\n    strictEqual(values.length, MAX_PULLS);\n    strictEqual(values[0], 1);\n    strictEqual(values[99], 100);\n    ok(pullCount > MAX_PULLS, 'pull called enough times');\n  },\n};\n\n// Test WritableStream desiredSize tracking and ready promise\n// Inspired by: Bun test/js/web/fetch/body-stream.test.ts (backpressure with various input lengths)\nexport const backpressureWritableDesiredSize = {\n  async test() {\n    const written = [];\n\n    const ws = new WritableStream(\n      {\n        write(chunk) {\n          written.push(chunk);\n          // Simulate slow write\n          return new Promise((resolve) => setTimeout(resolve, 10));\n        },\n      },\n      { highWaterMark: 3 }\n    );\n\n    const writer = ws.getWriter();\n\n    strictEqual(writer.desiredSize, 3, 'initial desiredSize');\n\n    writer.write(1);\n    strictEqual(writer.desiredSize, 2, 'desiredSize after first write');\n\n    writer.write(2);\n    strictEqual(writer.desiredSize, 1, 'desiredSize after second write');\n\n    writer.write(3);\n    strictEqual(writer.desiredSize, 0, 'desiredSize at capacity');\n\n    const readyBefore = writer.ready;\n    ok(readyBefore instanceof Promise, 'ready should be a Promise');\n\n    writer.write(4);\n    strictEqual(writer.desiredSize, -1, 'desiredSize over capacity');\n\n    const readyAfter = writer.ready;\n    ok(readyAfter instanceof Promise, 'ready should still be a Promise');\n\n    await scheduler.wait(50);\n\n    ok(writer.desiredSize > -1, 'desiredSize recovered after writes complete');\n\n    const readyResolved = writer.ready;\n    await readyResolved;\n\n    await writer.close();\n    strictEqual(written.length, 4, 'all chunks written');\n  },\n};\n\n// Test WritableStream with slow sink causes backpressure\n// Inspired by: Bun test/js/bun/spawn/spawn-stdin-readable-stream-edge-cases.test.ts (slow consumer)\nexport const backpressureWritableSlowSink = {\n  async test() {\n    let writeCount = 0;\n\n    const ws = new WritableStream(\n      {\n        async write() {\n          writeCount++;\n          // Very slow write\n          await scheduler.wait(50);\n        },\n      },\n      { highWaterMark: 2 }\n    );\n\n    const writer = ws.getWriter();\n\n    strictEqual(writer.desiredSize, 2);\n\n    const w1 = writer.write('a');\n    strictEqual(writer.desiredSize, 1);\n\n    const w2 = writer.write('b');\n    strictEqual(writer.desiredSize, 0);\n\n    const initialReady = writer.ready;\n\n    const w3 = writer.write('c');\n    strictEqual(writer.desiredSize, -1);\n\n    // Backpressure can be signaled in two ways depending on implementation:\n    // 1. writer.ready returns a new pending promise (ready !== initialReady)\n    // 2. desiredSize drops to 0 or below, indicating the queue is full\n    // We accept either signal as valid backpressure indication.\n    ok(\n      writer.ready !== initialReady || writer.desiredSize <= 0,\n      'backpressure signal'\n    );\n\n    const results = await Promise.allSettled([w1, w2, w3]);\n    strictEqual(results[0].status, 'fulfilled', 'first write should succeed');\n    strictEqual(results[1].status, 'fulfilled', 'second write should succeed');\n    strictEqual(results[2].status, 'fulfilled', 'third write should succeed');\n    strictEqual(writeCount, 3);\n\n    await writer.close();\n  },\n};\n\n// Test TransformStream with both readable and writable strategies\n// Inspired by: Bun test/js/web/streams/streams.test.js (TransformStream tests)\nexport const backpressureTransformBothStrategies = {\n  async test() {\n    const ts = new TransformStream(\n      {\n        transform(chunk, controller) {\n          controller.enqueue(chunk);\n        },\n      },\n      { highWaterMark: 2 },\n      { highWaterMark: 4 }\n    );\n\n    const writer = ts.writable.getWriter();\n    const reader = ts.readable.getReader();\n\n    strictEqual(writer.desiredSize, 2, 'writable desiredSize');\n\n    writer.write(1);\n    writer.write(2);\n    writer.write(3);\n    writer.write(4);\n\n    const results = [];\n    for (let i = 0; i < 4; i++) {\n      const { value } = await reader.read();\n      results.push(value);\n    }\n\n    strictEqual(results.length, 4);\n    strictEqual(results[0], 1);\n    strictEqual(results[3], 4);\n\n    await writer.close();\n    const final = await reader.read();\n    ok(final.done);\n  },\n};\n\n// Test backpressure propagation through pipe chain\n// Inspired by: Bun test/js/web/streams/streams.test.js (pipeTo/pipeThrough tests)\nexport const backpressurePipeChain = {\n  async test() {\n    let sourcePullCount = 0;\n    const MAX_CHUNKS = 5;\n\n    const source = new ReadableStream(\n      {\n        pull(c) {\n          sourcePullCount++;\n          if (sourcePullCount <= MAX_CHUNKS) {\n            c.enqueue(sourcePullCount);\n          } else {\n            c.close();\n          }\n        },\n      },\n      { highWaterMark: 2 }\n    );\n\n    const transform = new TransformStream(\n      {\n        transform(chunk, controller) {\n          controller.enqueue(chunk * 2);\n        },\n      },\n      { highWaterMark: 1 },\n      { highWaterMark: 1 }\n    );\n\n    const chunks = [];\n    const dest = new WritableStream(\n      {\n        async write(chunk) {\n          chunks.push(chunk);\n          // Slow consumer\n          await scheduler.wait(5);\n        },\n      },\n      { highWaterMark: 1 }\n    );\n\n    await source.pipeThrough(transform).pipeTo(dest);\n\n    strictEqual(chunks.length, MAX_CHUNKS, 'all chunks received');\n    strictEqual(chunks[0], 2, 'first chunk transformed');\n    strictEqual(chunks[4], 10, 'last chunk transformed');\n  },\n};\n\n// Test byte stream highWaterMark is measured in bytes, not chunks\n// Inspired by: Bun test/js/node/test/parallel/test-whatwg-readablebytestream.js\nexport const backpressureByteStreamHwm = {\n  async test() {\n    let controller;\n    let pullCount = 0;\n\n    const rs = new ReadableStream(\n      {\n        type: 'bytes',\n        start(c) {\n          controller = c;\n          strictEqual(c.desiredSize, 10, 'initial desiredSize in bytes');\n        },\n        pull(c) {\n          pullCount++;\n          // Enqueue 3 bytes\n          c.enqueue(new Uint8Array([1, 2, 3]));\n        },\n      },\n      { highWaterMark: 10 }\n    );\n\n    await scheduler.wait(20);\n\n    ok(pullCount >= 3, 'pulled multiple times for byte count');\n    ok(controller.desiredSize <= 1, 'desiredSize accounts for bytes');\n\n    const reader = rs.getReader();\n\n    const { value } = await reader.read();\n    ok(value.byteLength >= 9, 'received buffered bytes');\n\n    reader.releaseLock();\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/streams-backpressure-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"streams-backpressure-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"streams-backpressure-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"streams_enable_constructors\", \"transformstream_enable_standard_constructor\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/streams-byob-edge-cases-test.js",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Tests for BYOB reader edge cases with various typed arrays.\n// These tests focus on different ArrayBufferView types beyond Uint8Array,\n// view offsets, and autoAllocateChunkSize variations.\n//\n// Test inspirations:\n// - Bun: test/js/node/test/parallel/test-whatwg-readablebytestreambyob.js (BYOB reader tests)\n// - Bun: test/js/node/test/parallel/test-whatwg-readablebytestream.js (ReadableByteStreamController)\n// - Deno: tests/unit_node/_fs/_fs_handle_test.ts (FileHandle BYOB reader)\n\nimport { strictEqual, ok } from 'node:assert';\n\n// Helper to create a byte stream that responds with data\nfunction createByteStreamWithData(data) {\n  return new ReadableStream({\n    type: 'bytes',\n    pull(controller) {\n      if (controller.byobRequest) {\n        const view = controller.byobRequest.view;\n        const bytesToCopy = Math.min(view.byteLength, data.length);\n        new Uint8Array(view.buffer, view.byteOffset, bytesToCopy).set(\n          data.subarray(0, bytesToCopy)\n        );\n        data = data.subarray(bytesToCopy);\n        controller.byobRequest.respond(bytesToCopy);\n        if (data.length === 0) {\n          controller.close();\n        }\n      } else {\n        controller.enqueue(data);\n        controller.close();\n      }\n    },\n  });\n}\n\n// Test BYOB read with Uint16Array view\n// Inspired by: Bun test/js/node/test/parallel/test-whatwg-readablebytestream.js\nexport const byobUint16Array = {\n  async test() {\n    // Create data that's properly aligned for Uint16Array (even number of bytes)\n    const data = new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05, 0x06]);\n    const rs = createByteStreamWithData(data);\n\n    const reader = rs.getReader({ mode: 'byob' });\n\n    // Read with Uint16Array (3 elements = 6 bytes)\n    const view = new Uint16Array(3);\n    const { value, done } = await reader.read(view);\n\n    ok(!done);\n    ok(value instanceof Uint16Array);\n    strictEqual(value.length, 3);\n    strictEqual(value.byteLength, 6);\n\n    // Verify the data (endianness-dependent)\n    const asBytes = new Uint8Array(\n      value.buffer,\n      value.byteOffset,\n      value.byteLength\n    );\n    strictEqual(asBytes[0], 0x01);\n    strictEqual(asBytes[1], 0x02);\n\n    reader.releaseLock();\n  },\n};\n\n// Test BYOB read with Uint32Array view\n// Inspired by: Bun test/js/node/test/parallel/test-whatwg-readablebytestream.js\nexport const byobUint32Array = {\n  async test() {\n    // Create data aligned for Uint32Array (multiple of 4 bytes)\n    const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);\n    const rs = createByteStreamWithData(data);\n\n    const reader = rs.getReader({ mode: 'byob' });\n\n    const view = new Uint32Array(2);\n    const { value, done } = await reader.read(view);\n\n    ok(!done);\n    ok(value instanceof Uint32Array);\n    strictEqual(value.length, 2);\n    strictEqual(value.byteLength, 8);\n\n    reader.releaseLock();\n  },\n};\n\n// Test BYOB read with Float32Array view\n// Inspired by: Bun test/js/node/test/parallel/test-whatwg-readablebytestream.js\nexport const byobFloat32Array = {\n  async test() {\n    // Create 8 bytes of data (2 Float32 values)\n    const data = new Uint8Array(8);\n    const floatView = new Float32Array(data.buffer);\n    floatView[0] = 3.14;\n    floatView[1] = 2.71;\n\n    const rs = createByteStreamWithData(data);\n    const reader = rs.getReader({ mode: 'byob' });\n\n    const view = new Float32Array(2);\n    const { value, done } = await reader.read(view);\n\n    ok(!done);\n    ok(value instanceof Float32Array);\n    strictEqual(value.length, 2);\n    ok(Math.abs(value[0] - 3.14) < 0.001);\n    ok(Math.abs(value[1] - 2.71) < 0.001);\n\n    reader.releaseLock();\n  },\n};\n\n// Test BYOB read with Float64Array view\n// Inspired by: Bun test/js/node/test/parallel/test-whatwg-readablebytestream.js\nexport const byobFloat64Array = {\n  async test() {\n    // Create 16 bytes of data (2 Float64 values)\n    const data = new Uint8Array(16);\n    const floatView = new Float64Array(data.buffer);\n    floatView[0] = Math.PI;\n    floatView[1] = Math.E;\n\n    const rs = createByteStreamWithData(data);\n    const reader = rs.getReader({ mode: 'byob' });\n\n    const view = new Float64Array(2);\n    const { value, done } = await reader.read(view);\n\n    ok(!done);\n    ok(value instanceof Float64Array);\n    strictEqual(value.length, 2);\n    ok(Math.abs(value[0] - Math.PI) < 0.0001);\n    ok(Math.abs(value[1] - Math.E) < 0.0001);\n\n    reader.releaseLock();\n  },\n};\n\n// Test BYOB read with DataView\n// Inspired by: Bun test/js/node/test/parallel/test-whatwg-readablebytestreambyob.js\nexport const byobDataView = {\n  async test() {\n    const data = new Uint8Array([0xde, 0xad, 0xbe, 0xef]);\n    const rs = createByteStreamWithData(data);\n\n    const reader = rs.getReader({ mode: 'byob' });\n\n    const view = new DataView(new ArrayBuffer(4));\n    const { value, done } = await reader.read(view);\n\n    ok(!done);\n    ok(value instanceof DataView);\n    strictEqual(value.byteLength, 4);\n    strictEqual(value.getUint8(0), 0xde);\n    strictEqual(value.getUint8(1), 0xad);\n    strictEqual(value.getUint8(2), 0xbe);\n    strictEqual(value.getUint8(3), 0xef);\n\n    reader.releaseLock();\n  },\n};\n\n// Test using different view types across consecutive reads\n// Inspired by: workerd streams-js-test.js readableStreamBytesMismatchedViewTypes\nexport const byobMixedViewTypes = {\n  async test() {\n    let pullCount = 0;\n    const rs = new ReadableStream({\n      type: 'bytes',\n      pull(controller) {\n        pullCount++;\n        if (controller.byobRequest) {\n          const view = controller.byobRequest.view;\n          const u8 = new Uint8Array(\n            view.buffer,\n            view.byteOffset,\n            view.byteLength\n          );\n          for (let i = 0; i < u8.length; i++) {\n            u8[i] = pullCount * 10 + i;\n          }\n          controller.byobRequest.respond(view.byteLength);\n        }\n      },\n    });\n\n    const reader = rs.getReader({ mode: 'byob' });\n\n    const result1 = await reader.read(new Uint8Array(4));\n    ok(result1.value instanceof Uint8Array);\n    strictEqual(result1.value.byteLength, 4);\n\n    const result2 = await reader.read(new Uint16Array(2));\n    ok(result2.value instanceof Uint16Array);\n    strictEqual(result2.value.byteLength, 4);\n\n    const result3 = await reader.read(new Uint32Array(1));\n    ok(result3.value instanceof Uint32Array);\n    strictEqual(result3.value.byteLength, 4);\n\n    strictEqual(pullCount, 3);\n    reader.releaseLock();\n  },\n};\n\n// Test BYOB read with ArrayBufferView that has a non-zero byteOffset\n// Inspired by: Bun test/js/node/test/parallel/test-whatwg-readablebytestream.js\nexport const byobViewOffset = {\n  async test() {\n    const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);\n    const rs = createByteStreamWithData(data);\n\n    const reader = rs.getReader({ mode: 'byob' });\n\n    const buffer = new ArrayBuffer(16);\n    const view = new Uint8Array(buffer, 4, 8); // Start at offset 4, length 8\n\n    strictEqual(view.byteOffset, 4);\n    strictEqual(view.byteLength, 8);\n\n    const { value, done } = await reader.read(view);\n\n    ok(!done);\n    ok(value instanceof Uint8Array);\n    strictEqual(value.byteLength, 8);\n    strictEqual(value.buffer.byteLength, 16);\n\n    reader.releaseLock();\n  },\n};\n\n// Test autoAllocateChunkSize with various values\n// Inspired by: Bun test/js/node/test/parallel/test-whatwg-readablebytestreambyob.js\nexport const byobAutoAllocateSizes = {\n  async test() {\n    const testSizes = [1, 1024, 65536];\n\n    for (const chunkSize of testSizes) {\n      let receivedSize = 0;\n\n      const rs = new ReadableStream({\n        type: 'bytes',\n        autoAllocateChunkSize: chunkSize,\n        pull(controller) {\n          if (controller.byobRequest) {\n            receivedSize = controller.byobRequest.view.byteLength;\n            controller.byobRequest.view[0] = 42;\n            controller.byobRequest.respond(1);\n            controller.close();\n          }\n        },\n      });\n\n      const reader = rs.getReader();\n      const { value } = await reader.read();\n\n      strictEqual(\n        receivedSize,\n        chunkSize,\n        `autoAllocateChunkSize=${chunkSize}`\n      );\n      ok(value instanceof Uint8Array);\n\n      reader.releaseLock();\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/streams-byob-edge-cases-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"streams-byob-edge-cases-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"streams-byob-edge-cases-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"streams_enable_constructors\", \"transformstream_enable_standard_constructor\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/streams-circ-ref-regression-test.js",
    "content": "import fs from 'node:fs';\nimport { Duplex } from 'node:stream';\n\n// If the test doesn't crash, it passes. There was a bug carried over from\n// the old original readable-streams module that didn't translate well from\n// the original CommonJS code to the ESM code, leading to a difference in\n// behavior when dealing with a circular reference depending on the order\n// in which the individual modules were imported.\n\nexport const circTest = {\n  test() {\n    const duplex = new Duplex();\n    duplex.destroy();\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/streams-circ-ref-regression-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"streams-circ-ref-regression-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"streams-circ-ref-regression-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"enable_nodejs_fs_module\", \"experimental\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/streams-error-edge-cases-test.js",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Tests for complex error scenarios in streams.\n// These tests focus on error type preservation, error propagation through\n// pipe chains, and race conditions between error and close operations.\n//\n// Test inspirations:\n// - Deno: tests/unit/streams_test.ts (cancel propagation, error type tests)\n// - Bun: test/js/web/streams/streams.test.js (pull rejection, error handling)\n// - Bun: test/js/web/fetch/fetch.stream.test.ts (corrupted data, socket close handling)\n// - Bun: test/js/bun/spawn/spawn-stdin-readable-stream-edge-cases.test.ts (exception in pull)\n\nimport { strictEqual, ok, rejects, deepStrictEqual } from 'node:assert';\n\n// Custom error class for testing error type preservation\nclass CustomStreamError extends Error {\n  constructor(message, code) {\n    super(message);\n    this.name = 'CustomStreamError';\n    this.code = code;\n  }\n}\n\n// Test error thrown after partial consumption of stream\n// Inspired by: Bun test/js/bun/spawn/spawn-stdin-readable-stream-edge-cases.test.ts (exception in pull)\nexport const errorDuringPartialConsumption = {\n  async test() {\n    let chunkCount = 0;\n\n    const rs = new ReadableStream({\n      pull(controller) {\n        chunkCount++;\n        if (chunkCount <= 3) {\n          controller.enqueue(chunkCount);\n        } else {\n          controller.error(new Error('Error after 3 chunks'));\n        }\n      },\n    });\n\n    const reader = rs.getReader();\n    const chunks = [];\n\n    for (let i = 0; i < 3; i++) {\n      const { value, done } = await reader.read();\n      ok(!done);\n      chunks.push(value);\n    }\n\n    deepStrictEqual(chunks, [1, 2, 3]);\n\n    await rejects(reader.read(), { message: 'Error after 3 chunks' });\n\n    await rejects(reader.read(), { message: 'Error after 3 chunks' });\n  },\n};\n\n// Test that custom error types are preserved through pipeTo\n// Inspired by: Deno tests/unit/streams_test.ts (cancel propagation with \"resource closed\" reason)\nexport const errorTypePreservationPipeTo = {\n  async test() {\n    const customError = new CustomStreamError('Custom error', 'ERR_CUSTOM');\n\n    const rs = new ReadableStream({\n      start(controller) {\n        controller.error(customError);\n      },\n    });\n\n    const ws = new WritableStream({\n      write() {},\n    });\n\n    await rejects(\n      async () => {\n        await rs.pipeTo(ws);\n      },\n      { message: 'Custom error' }\n    );\n  },\n};\n\n// Test that custom error types are preserved through pipeThrough\n// Inspired by: Deno tests/unit/streams_test.ts (error propagation tests)\nexport const errorTypePreservationPipeThrough = {\n  async test() {\n    const customError = new CustomStreamError('Pipe through error', 'ERR_PIPE');\n\n    const rs = new ReadableStream({\n      pull(controller) {\n        controller.error(customError);\n      },\n    });\n\n    const transform = new TransformStream();\n    const result = rs.pipeThrough(transform);\n\n    const reader = result.getReader();\n\n    await rejects(\n      async () => {\n        await reader.read();\n      },\n      { message: 'Pipe through error' }\n    );\n  },\n};\n\n// Test race between controller.error() and controller.close() on ReadableStream\n// Inspired by: Bun test/js/web/streams/streams.test.js (error handling edge cases)\nexport const errorRaceWithCloseReadable = {\n  async test() {\n    let controller;\n\n    const rs = new ReadableStream({\n      start(c) {\n        controller = c;\n      },\n    });\n\n    const reader = rs.getReader();\n    const readPromise = reader.read();\n\n    controller.error(new Error('Error wins'));\n    try {\n      controller.close();\n    } catch (_e) {\n      // May throw since stream is already errored\n    }\n\n    await rejects(readPromise, { message: 'Error wins' });\n  },\n};\n\n// Test race between writer.abort() and writer.close() on WritableStream\n// Inspired by: Bun test/js/web/streams/streams.test.js (abort/close race conditions)\nexport const errorRaceWithCloseWritable = {\n  async test() {\n    let writeStarted = false;\n\n    const ws = new WritableStream({\n      write() {\n        writeStarted = true;\n        // Simulate a slow write that can be aborted\n        return scheduler.wait(100);\n      },\n    });\n\n    const writer = ws.getWriter();\n\n    const writePromise = writer.write('data').catch((e) => e);\n\n    await scheduler.wait(5);\n    ok(writeStarted, 'Write should have started');\n\n    await writer.abort(new Error('Abort wins'));\n\n    const writeResult = await writePromise;\n    ok(\n      writeResult === undefined || writeResult instanceof Error,\n      'Write should complete or error'\n    );\n  },\n};\n\n// Test error thrown in TransformStream transform() callback using controller.error()\n// Inspired by: Bun test/js/web/streams/streams.test.js (TransformStream error handling)\nexport const errorInTransformFlush = {\n  async test() {\n    let transformController;\n\n    const ts = new TransformStream({\n      start(controller) {\n        transformController = controller;\n      },\n      transform(chunk, controller) {\n        controller.enqueue(chunk);\n      },\n    });\n\n    const reader = ts.readable.getReader();\n\n    transformController.error(new Error('Transform error'));\n\n    await rejects(\n      async () => {\n        await reader.read();\n      },\n      { message: 'Transform error' }\n    );\n  },\n};\n\n// Test error propagation through nested tee branches\n// Inspired by: Bun test/js/web/streams/streams.test.js (tee error handling)\nexport const errorPropagationTeeMultiBranch = {\n  async test() {\n    let controller;\n\n    const rs = new ReadableStream({\n      start(c) {\n        controller = c;\n      },\n    });\n\n    // Create nested tees: original -> [branch1, temp] -> [branch2, branch3]\n    const [branch1, temp] = rs.tee();\n    const [branch2, branch3] = temp.tee();\n\n    const reader1 = branch1.getReader();\n    const reader2 = branch2.getReader();\n    const reader3 = branch3.getReader();\n\n    // Start reads on all branches\n    const read1 = reader1.read();\n    const read2 = reader2.read();\n    const read3 = reader3.read();\n\n    // Error the source\n    controller.error(new Error('Source error'));\n\n    // All branches should receive the error\n    const results = await Promise.allSettled([read1, read2, read3]);\n\n    for (const result of results) {\n      strictEqual(result.status, 'rejected');\n      strictEqual(result.reason.message, 'Source error');\n    }\n  },\n};\n\n// Test AbortSignal cancellation during active pipeTo\n// Inspired by: Deno tests/unit/streams_test.ts (abort tests), Bun test/js/web/streams/streams.test.js\nexport const abortSignalDuringPipe = {\n  async test() {\n    const chunks = [];\n    let pullCount = 0;\n\n    const rs = new ReadableStream({\n      async pull(controller) {\n        pullCount++;\n        await scheduler.wait(10);\n        if (pullCount <= 10) {\n          controller.enqueue(pullCount);\n        } else {\n          controller.close();\n        }\n      },\n    });\n\n    const ws = new WritableStream({\n      write(chunk) {\n        chunks.push(chunk);\n      },\n    });\n\n    const abortController = new AbortController();\n\n    // Start piping\n    const pipePromise = rs.pipeTo(ws, { signal: abortController.signal });\n\n    // Wait for some chunks to flow\n    await scheduler.wait(50);\n\n    // Abort mid-pipe\n    abortController.abort(new Error('User cancelled'));\n\n    // Pipe should reject with an error (type may vary by implementation)\n    await rejects(async () => {\n      await pipePromise;\n    }, Error);\n\n    // Some chunks should have been written\n    ok(chunks.length > 0, 'Some chunks written before abort');\n    ok(chunks.length < 10, 'Not all chunks written due to abort');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/streams-error-edge-cases-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"streams-error-edge-cases-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"streams-error-edge-cases-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"streams_enable_constructors\", \"transformstream_enable_standard_constructor\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/streams-iocontext-test.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// Ported from edgeworker streams-iocontext.ew-test\n\nimport { strictEqual } from 'node:assert';\n\n// global-scope-readablestream\nconst enc1 = new TextEncoder();\nconst rs1 = new ReadableStream({\n  start(c) {\n    c.enqueue(enc1.encode('ok'));\n    c.close();\n  },\n});\n\nexport const globalScopeReadablestream = {\n  async test(ctrl, env) {\n    const response = await env.self.fetch('http://test/1');\n    const text = await response.text();\n    strictEqual(text, 'ok');\n  },\n};\n\n// global-scope-readablestream-2\nconst enc2 = new TextEncoder();\nconst dec2 = new TextDecoder();\nconst rs2 = new ReadableStream({\n  start(c) {\n    c.enqueue(enc2.encode('ok'));\n    c.close();\n  },\n});\nlet text2 = '';\nconst modulePromise2 = (async () => {\n  for await (const chunk of rs2) {\n    text2 += dec2.decode(chunk);\n  }\n  if (text2 !== 'ok') {\n    throw new Error('Stream should have been readable at global scope');\n  }\n})();\n\nexport const globalScopeReadablestream2 = {\n  async test(ctrl, env) {\n    await modulePromise2;\n    const response = await env.self.fetch('http://test/2');\n    const text = await response.text();\n    strictEqual(text, 'ok');\n  },\n};\n\n// global-scope-readablestream-3\nconst enc3 = new TextEncoder();\nconst dec3 = new TextDecoder();\nconst rs3 = new ReadableStream({\n  start(c) {\n    c.enqueue(enc3.encode('ok'));\n    c.close();\n  },\n});\n\nexport const globalScopeReadablestream3 = {\n  async test(ctrl, env) {\n    const response = await env.self.fetch('http://test/3');\n    const text = await response.text();\n    strictEqual(text, 'ok');\n  },\n};\n\n// global-scope-readablestream-4\nconst enc4 = new TextEncoder();\nconst rs4 = new ReadableStream({\n  start(c) {\n    c.enqueue(enc4.encode('ok'));\n    c.close();\n  },\n});\n\nexport const globalScopeReadablestream4 = {\n  async test(ctrl, env) {\n    const response = await env.self.fetch('http://test/4');\n    const text = await response.text();\n    strictEqual(text, 'ok');\n  },\n};\n\n// global-scope-readablestream-5\nconst enc5 = new TextEncoder();\nconst rs5 = new ReadableStream({\n  start(c) {\n    c.enqueue(enc5.encode('ok'));\n    c.close();\n  },\n});\nconst res5 = new Response(rs5);\n\nexport const globalScopeReadablestream5 = {\n  async test(ctrl, env) {\n    const response = await env.self.fetch('http://test/5');\n    const text = await response.text();\n    strictEqual(text, 'ok');\n  },\n};\n\n// global-scope-readablestream-6\nconst rs6 = new ReadableStream({\n  start(c) {\n    globalThis.controller6 = c;\n  },\n});\n\nexport const globalScopeReadablestream6 = {\n  async test(ctrl, env) {\n    const responseA = await env.self.fetch('http://test/6/a');\n    strictEqual(await responseA.text(), 'ok');\n\n    const responseB = await env.self.fetch('http://test/6/b');\n    strictEqual(await responseB.text(), 'madness');\n  },\n};\n\n// global-scope-readablestream-7\nlet resolve7;\nconst promise7 = new Promise((r) => (resolve7 = r));\nconst rs7 = new ReadableStream({\n  async pull(c) {\n    const pullFunction = await promise7;\n    return pullFunction(c);\n  },\n});\n\nexport const globalScopeReadablestream7 = {\n  async test(ctrl, env) {\n    const responseA = await env.self.fetch('http://test/7/a');\n    strictEqual(await responseA.text(), 'ok');\n\n    const responseB = await env.self.fetch('http://test/7/b');\n    strictEqual(await responseB.text(), 'madness');\n  },\n};\n\n// global-scope-readablestream-8\nlet resolve8;\nconst promise8 = new Promise((r) => (resolve8 = r));\nconst rs8 = new ReadableStream({\n  async start(c) {\n    globalThis.controller8 = c;\n    await promise8;\n  },\n});\n\nexport const globalScopeReadablestream8 = {\n  async test(ctrl, env) {\n    const responseA = await env.self.fetch('http://test/8/a');\n    strictEqual(await responseA.text(), 'ok');\n\n    const responseB = await env.self.fetch('http://test/8/b');\n    strictEqual(await responseB.text(), 'madness');\n  },\n};\n\nexport default {\n  async fetch(req) {\n    const url = new URL(req.url);\n\n    // global-scope-readablestream\n    if (url.pathname === '/1') {\n      return new Response(rs1);\n    }\n\n    // global-scope-readablestream-2\n    if (url.pathname === '/2') {\n      return new Response('ok');\n    }\n\n    // global-scope-readablestream-3\n    if (url.pathname === '/3') {\n      let text = '';\n      for await (const chunk of rs3) {\n        text += dec3.decode(chunk);\n      }\n      if (text !== 'ok') {\n        throw new Error('Global scope stream should have been readable');\n      }\n      return new Response('ok');\n    }\n\n    // global-scope-readablestream-4\n    if (url.pathname === '/4') {\n      return new Response(rs4.pipeThrough(new TransformStream()));\n    }\n\n    // global-scope-readablestream-5\n    if (url.pathname === '/5') {\n      return new Response('ok');\n    }\n\n    // global-scope-readablestream-6\n    if (url.pathname === '/6/a') {\n      const enc = new TextEncoder();\n      globalThis.controller6.enqueue(enc.encode('madness'));\n      globalThis.controller6.close();\n      return new Response('ok');\n    }\n    if (url.pathname === '/6/b') {\n      let text = '';\n      const dec = new TextDecoder();\n      for await (const chunk of rs6) {\n        text += dec.decode(chunk);\n      }\n      return new Response(text);\n    }\n\n    // global-scope-readablestream-7\n    if (url.pathname === '/7/a') {\n      const enc = new TextEncoder();\n      resolve7((c) => {\n        c.enqueue(enc.encode('madness'));\n        c.close();\n      });\n      return new Response('ok');\n    }\n    if (url.pathname === '/7/b') {\n      let text = '';\n      const dec = new TextDecoder();\n      for await (const chunk of rs7) {\n        text += dec.decode(chunk);\n      }\n      return new Response(text);\n    }\n\n    // global-scope-readablestream-8\n    if (url.pathname === '/8/a') {\n      const enc = new TextEncoder();\n      await scheduler.wait(10);\n      resolve8();\n      globalThis.controller8.enqueue(enc.encode('madness'));\n      globalThis.controller8.close();\n      return new Response('ok');\n    }\n    if (url.pathname === '/8/b') {\n      let text = '';\n      const dec = new TextDecoder();\n      for await (const chunk of rs8) {\n        text += dec.decode(chunk);\n      }\n      return new Response(text);\n    }\n\n    throw new Error('boom');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/streams-iocontext-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"streams-iocontext-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"streams-iocontext-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"streams_enable_constructors\",\n          \"transformstream_enable_standard_constructor\",\n        ],\n        bindings = [\n          (name = \"self\", service = \"streams-iocontext-test\")\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/streams-js-test.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Tests for JavaScript-backed streams (ReadableStream and WritableStream constructors)\n// Ported from edgeworker streams-js.ew-test\n\nimport { strictEqual, ok, throws, rejects } from 'node:assert';\n\n// Test that JS streams globals exist\nexport const userStreamsGlobalsExist = {\n  test() {\n    ok(ReadableStreamDefaultController !== undefined);\n    ok(ReadableByteStreamController !== undefined);\n    ok(ReadableStreamBYOBRequest !== undefined);\n    ok(WritableStreamDefaultController !== undefined);\n  },\n};\n\n// Test that JS streams objects are not directly constructable\nexport const jsStreamsObjectsNotConstructable = {\n  test() {\n    throws(() => new ReadableStreamDefaultController(), TypeError);\n    throws(() => new ReadableByteStreamController(), TypeError);\n    throws(() => new ReadableStreamBYOBRequest(), TypeError);\n    throws(() => new WritableStreamDefaultController(), TypeError);\n  },\n};\n\n// Test new ReadableStream() works\nexport const newReadableStream = {\n  test() {\n    new ReadableStream();\n    new ReadableStream({ type: 'bytes' });\n  },\n};\n\n// Test that underlying source algorithms are called\nexport const newReadableStreamAlgorithms = {\n  async test() {\n    // Sync algorithms\n    {\n      let started = false;\n      let pulled = false;\n      let canceled = false;\n      const rs = new ReadableStream({\n        start() {\n          started = true;\n        },\n        pull() {\n          pulled = true;\n        },\n        cancel() {\n          canceled = true;\n        },\n      });\n      ok(started);\n\n      await scheduler.wait(1);\n\n      rs.cancel();\n\n      ok(pulled);\n      ok(canceled);\n    }\n\n    // Byte stream sync algorithms\n    {\n      let started = false;\n      let pulled = false;\n      let canceled = false;\n      const rs = new ReadableStream(\n        {\n          type: 'bytes',\n          start() {\n            started = true;\n          },\n          pull() {\n            pulled = true;\n          },\n          cancel() {\n            canceled = true;\n          },\n        },\n        { highWaterMark: 1 }\n      );\n\n      ok(started);\n      await scheduler.wait(1);\n\n      rs.cancel();\n\n      ok(pulled);\n      ok(canceled);\n    }\n\n    // Async algorithms for value stream\n    {\n      let onStarted, onPulled, onCanceled;\n      let started = new Promise((resolve) => (onStarted = resolve));\n      let pulled = new Promise((resolve) => (onPulled = resolve));\n      let canceled = new Promise((resolve) => (onCanceled = resolve));\n\n      const rs = new ReadableStream({\n        async start() {\n          await scheduler.wait(1);\n          onStarted();\n        },\n        async pull() {\n          await scheduler.wait(1);\n          onPulled();\n        },\n        async cancel() {\n          onCanceled();\n        },\n      });\n\n      await Promise.allSettled([started, pulled]);\n      await scheduler.wait(1);\n      await Promise.allSettled([rs.cancel(), canceled]);\n    }\n\n    // Async algorithms for byte stream\n    {\n      let onStarted, onPulled, onCanceled;\n      let started = new Promise((resolve) => (onStarted = resolve));\n      let pulled = new Promise((resolve) => (onPulled = resolve));\n      let canceled = new Promise((resolve) => (onCanceled = resolve));\n\n      const rs = new ReadableStream(\n        {\n          type: 'bytes',\n          async start() {\n            await scheduler.wait(1);\n            onStarted();\n          },\n          async pull() {\n            await scheduler.wait(1);\n            onPulled();\n          },\n          async cancel() {\n            onCanceled();\n          },\n        },\n        { highWaterMark: 1 }\n      );\n\n      await Promise.allSettled([started, pulled]);\n      await scheduler.wait(1);\n      await Promise.allSettled([rs.cancel(), canceled]);\n    }\n  },\n};\n\n// Test that new ReadableStream creates the right kind of controller\nexport const newReadableStreamControllerType = {\n  test() {\n    new ReadableStream({\n      start(c) {\n        ok(c instanceof ReadableStreamDefaultController);\n      },\n      pull(c) {\n        ok(c instanceof ReadableStreamDefaultController);\n      },\n    });\n\n    new ReadableStream({\n      type: 'bytes',\n      start(c) {\n        ok(c instanceof ReadableByteStreamController);\n      },\n      pull(c) {\n        ok(c instanceof ReadableByteStreamController);\n        const byobRequest = c.byobRequest;\n        ok(byobRequest != null);\n        ok(byobRequest === c.byobRequest);\n        ok(byobRequest instanceof ReadableStreamBYOBRequest);\n        ok(byobRequest.view instanceof Uint8Array);\n      },\n    });\n  },\n};\n\n// Test sync algorithm errors are handled properly\nexport const newReadableStreamSyncAlgorithmErrorsHandled = {\n  async test() {\n    // Start error\n    {\n      const rs = new ReadableStream({\n        start() {\n          throw new Error('boom');\n        },\n      });\n\n      await rejects(rs.getReader().read(), { message: 'boom' });\n    }\n\n    // Pull error\n    {\n      const rs = new ReadableStream({\n        pull() {\n          throw new Error('boom');\n        },\n      });\n\n      await rejects(rs.getReader().read(), { message: 'boom' });\n    }\n\n    // Cancel error\n    {\n      const rs = new ReadableStream({\n        cancel() {\n          throw new Error('boom');\n        },\n      });\n      await rejects(rs.cancel(), { message: 'boom' });\n    }\n  },\n};\n\n// Test async algorithm errors are handled properly\nexport const newReadableStreamAsyncAlgorithmErrorsHandled = {\n  async test() {\n    // Async start error\n    {\n      const rs = new ReadableStream({\n        async start() {\n          throw new Error('boom');\n        },\n      });\n\n      await rejects(rs.getReader().read(), { message: 'boom' });\n    }\n\n    // Async pull error\n    {\n      const rs = new ReadableStream({\n        async pull() {\n          throw new Error('boom');\n        },\n      });\n\n      await rejects(rs.getReader().read(), { message: 'boom' });\n    }\n\n    // Async cancel error\n    {\n      const rs = new ReadableStream({\n        async cancel() {\n          throw new Error('boom');\n        },\n      });\n\n      await rejects(rs.cancel(), { message: 'boom' });\n    }\n  },\n};\n\n// Test size algorithm is called with correct value and errors handled\nexport const sizeAlgorithmCalled = {\n  async test() {\n    // Size algorithm called with correct value\n    {\n      let sizeCalled = false;\n      new ReadableStream(\n        {\n          pull(c) {\n            c.enqueue(1);\n          },\n        },\n        {\n          size(value) {\n            strictEqual(value, 1);\n            sizeCalled = true;\n          },\n        }\n      );\n\n      ok(sizeCalled);\n    }\n\n    // Size algorithm ignored in byte streams\n    {\n      let sizeCalled = false;\n      new ReadableStream(\n        {\n          type: 'bytes',\n          pull(c) {\n            c.enqueue(new Uint8Array(1));\n          },\n        },\n        {\n          size() {\n            sizeCalled = true;\n          },\n        }\n      );\n\n      ok(!sizeCalled);\n    }\n\n    // Size algorithm error handled\n    {\n      const rs = new ReadableStream(\n        {\n          pull(c) {\n            c.enqueue(1);\n          },\n        },\n        {\n          size() {\n            throw new Error('boom');\n          },\n        }\n      );\n\n      await rejects(rs.getReader().read(), { message: 'boom' });\n    }\n\n    // Async size algorithm not allowed\n    {\n      const rs = new ReadableStream(\n        {\n          pull(c) {\n            c.enqueue(1);\n          },\n        },\n        {\n          async size() {\n            return 1;\n          },\n        }\n      );\n\n      await rejects(rs.getReader().read(), {\n        message: 'The value cannot be converted because it is not an integer.',\n      });\n    }\n  },\n};\n\n// Test ReadableStream getDesiredSize is calculated correctly\nexport const readableGetDesiredSize = {\n  async test() {\n    // Value stream desiredSize\n    {\n      let controller;\n\n      const rs = new ReadableStream(\n        {\n          start(c) {\n            controller = c;\n            strictEqual(c.desiredSize, 2);\n            c.enqueue(1);\n            strictEqual(c.desiredSize, 1);\n            c.enqueue(2);\n            strictEqual(c.desiredSize, 0);\n            c.enqueue(3);\n            strictEqual(c.desiredSize, -1);\n          },\n        },\n        {\n          highWaterMark: 2,\n        }\n      );\n\n      await rs.getReader().read();\n      strictEqual(controller.desiredSize, 0);\n    }\n\n    // Enqueuing when there's an active read skips the queue\n    {\n      let controller;\n      const rs = new ReadableStream(\n        {\n          start(c) {\n            controller = c;\n          },\n        },\n        { highWaterMark: 2 }\n      );\n\n      const reader = rs.getReader();\n      strictEqual(controller.desiredSize, 2);\n      const read = reader.read();\n      controller.enqueue(1);\n      strictEqual(controller.desiredSize, 2);\n      strictEqual((await read).value, 1);\n    }\n\n    // Byte stream desiredSize\n    {\n      let controller;\n      const rs = new ReadableStream(\n        {\n          type: 'bytes',\n          start(c) {\n            controller = c;\n            strictEqual(c.desiredSize, 2);\n            c.enqueue(new Uint8Array(2));\n            strictEqual(c.desiredSize, 0);\n            c.enqueue(new Uint8Array(1));\n            strictEqual(c.desiredSize, -1);\n          },\n        },\n        {\n          highWaterMark: 2,\n        }\n      );\n\n      strictEqual((await rs.getReader().read()).value.byteLength, 3);\n      strictEqual(controller.desiredSize, 2);\n    }\n\n    // Byte stream enqueuing when there's an active read skips the queue\n    {\n      let controller;\n      const rs = new ReadableStream(\n        {\n          type: 'bytes',\n          start(c) {\n            controller = c;\n          },\n        },\n        { highWaterMark: 2 }\n      );\n\n      const reader = rs.getReader();\n      strictEqual(controller.desiredSize, 2);\n      const read = reader.read();\n      controller.enqueue(new Uint8Array(10));\n      strictEqual(controller.desiredSize, 2);\n      strictEqual((await read).value.byteLength, 10);\n    }\n  },\n};\n\n// Test ReadableStream controller.error() works as expected\nexport const readableStreamControllerError = {\n  async test() {\n    // Value stream\n    {\n      let controller;\n      const rs = new ReadableStream({\n        start(c) {\n          controller = c;\n        },\n      });\n      const reader = rs.getReader();\n      const read = reader.read();\n      controller.error(new Error('bang!'));\n      await rejects(read, { message: 'bang!' });\n    }\n\n    // Byte stream\n    {\n      let controller;\n      const rs = new ReadableStream({\n        type: 'bytes',\n        start(c) {\n          controller = c;\n        },\n      });\n      const reader = rs.getReader();\n      const read = reader.read();\n      controller.error(new Error('bang!'));\n      await rejects(read, { message: 'bang!' });\n    }\n  },\n};\n\n// Test ReadableStream autoAllocateChunkSize works as expected\nexport const readableStreamAutoAllocateChunkSize = {\n  async test() {\n    throws(() => {\n      new ReadableStream({\n        type: 'bytes',\n        autoAllocateChunkSize: 0,\n      });\n    }, TypeError);\n\n    throws(() => {\n      new ReadableStream({\n        type: 'bytes',\n        autoAllocateChunkSize: -1,\n      });\n    }, TypeError);\n\n    throws(() => {\n      new ReadableStream({\n        type: 'bytes',\n        autoAllocateChunkSize: 'a',\n      });\n    }, TypeError);\n\n    let pulled = false;\n    const rs = new ReadableStream({\n      type: 'bytes',\n      autoAllocateChunkSize: 10,\n      pull(c) {\n        pulled = true;\n        if (c.byobRequest) {\n          strictEqual(c.byobRequest.view.byteLength, 10);\n          c.byobRequest.respond(10);\n        }\n      },\n    });\n    await rs.getReader().read();\n    ok(pulled);\n  },\n};\n\n// Test ReadableStream byte stream respond() works appropriately\nexport const readableStreamByteRespond = {\n  async test() {\n    // Basic respond\n    {\n      const rs = new ReadableStream({\n        type: 'bytes',\n        pull(c) {\n          if (c.byobRequest) {\n            const req = c.byobRequest;\n            req.view[0] = 1;\n            req.view[1] = 2;\n            req.view[2] = 3;\n\n            throws(() => req.respond(10), RangeError);\n            throws(() => req.respond(0), TypeError);\n\n            req.respond(3);\n\n            // This will error the stream but won't be immediately\n            // apparent until the next read operation.\n            req.respond(3);\n          }\n        },\n      });\n\n      const reader = rs.getReader({ mode: 'byob' });\n      const u8 = new Uint8Array(3);\n      const read = reader.read(u8);\n      strictEqual(u8.byteLength, 0);\n\n      const { value } = await read;\n      strictEqual(value.byteLength, 3);\n\n      await rejects(reader.read(new Uint8Array(3)), {\n        message: 'This ReadableStreamBYOBRequest has been invalidated.',\n      });\n    }\n\n    // Respond with close\n    {\n      const rs = new ReadableStream({\n        type: 'bytes',\n        pull(c) {\n          if (c.byobRequest) {\n            c.close();\n            c.byobRequest.respond(0);\n          }\n        },\n      });\n\n      const reader = rs.getReader({ mode: 'byob' });\n\n      const u8 = new Uint8Array([1, 2, 3]);\n\n      const { done, value } = await reader.read(u8);\n\n      ok(done);\n      ok(value instanceof Uint8Array);\n      strictEqual(value.byteLength, 0);\n      strictEqual(value.buffer.byteLength, 3);\n      const u82 = new Uint8Array(value.buffer, 0, 3);\n      strictEqual(u82[0], 1);\n      strictEqual(u82[1], 2);\n      strictEqual(u82[2], 3);\n    }\n  },\n};\n\n// Test ReadableStream byte stream respondWithNewView works appropriately\nexport const readableStreamByteRespondWithNewView = {\n  async test() {\n    // Basic respondWithNewView\n    {\n      const rs = new ReadableStream({\n        type: 'bytes',\n        pull(c) {\n          if (c.byobRequest) {\n            const req = c.byobRequest;\n            const u8 = new Uint8Array(req.view.buffer);\n\n            u8[0] = 1;\n            u8[1] = 2;\n            u8[2] = 3;\n\n            // Can't respond with zero if we're not closed.\n            throws(() => req.respondWithNewView(new Uint8Array(0)), TypeError);\n\n            // Underlying buffer is too big.\n            throws(\n              () => req.respondWithNewView(new Uint8Array(10)),\n              RangeError\n            );\n\n            // Can't respond with a non-detachable ArrayBuffer.\n            throws(\n              () =>\n                req.respondWithNewView(\n                  new Uint8Array(new SharedArrayBuffer(10))\n                ),\n              TypeError\n            );\n\n            // New view has an invalid byte offset.\n            throws(\n              () => req.respondWithNewView(new Uint8Array(req.view.buffer, 1)),\n              RangeError\n            );\n\n            req.respondWithNewView(u8);\n\n            strictEqual(u8.byteLength, 0);\n\n            // This will error the stream but won't be immediately\n            // apparent until the next read operation.\n            req.respond(3);\n          }\n        },\n      });\n\n      const reader = rs.getReader({ mode: 'byob' });\n      const u8 = new Uint8Array(3);\n      const read = reader.read(u8);\n      strictEqual(u8.byteLength, 0);\n\n      const { value } = await read;\n      strictEqual(value.byteLength, 3);\n      strictEqual(value[0], 1);\n      strictEqual(value[1], 2);\n      strictEqual(value[2], 3);\n\n      await rejects(reader.read(new Uint8Array(3)), {\n        message: 'This ReadableStreamBYOBRequest has been invalidated.',\n      });\n    }\n\n    // RespondWithNewView with close\n    {\n      const rs = new ReadableStream({\n        type: 'bytes',\n        pull(c) {\n          if (c.byobRequest) {\n            c.close();\n            c.byobRequest.respondWithNewView(\n              new Uint8Array(c.byobRequest.view.buffer, 0, 0)\n            );\n          }\n        },\n      });\n\n      const reader = rs.getReader({ mode: 'byob' });\n\n      const { done, value } = await reader.read(new Uint8Array(3));\n\n      ok(done);\n      ok(value instanceof Uint8Array);\n      strictEqual(value.byteLength, 0);\n      strictEqual(value.buffer.byteLength, 3);\n    }\n  },\n};\n\n// Test ReadableStream JS controllers allow for multiple pending reads\nexport const readableStreamMultiplePendingReads = {\n  async test() {\n    // Value stream\n    {\n      let controller;\n      const rs = new ReadableStream({\n        start(c) {\n          controller = c;\n        },\n      });\n      const reader = rs.getReader();\n      const read1 = reader.read();\n      const read2 = reader.read();\n      controller.enqueue(1);\n      controller.enqueue(2);\n      const [res1, res2] = await Promise.all([read1, read2]);\n      strictEqual(res1.value, 1);\n      strictEqual(res2.value, 2);\n    }\n\n    // Byte stream\n    {\n      let controller;\n      const rs = new ReadableStream({\n        type: 'bytes',\n        start(c) {\n          controller = c;\n        },\n      });\n      const enc = new TextEncoder();\n      const dec = new TextDecoder();\n      const reader = rs.getReader();\n      const read1 = reader.read();\n      const read2 = reader.read();\n      controller.enqueue(enc.encode('hello'));\n      controller.enqueue(enc.encode('there'));\n      const [res1, res2] = await Promise.all([read1, read2]);\n      strictEqual(dec.decode(res1.value), 'hello');\n      strictEqual(dec.decode(res2.value), 'there');\n    }\n  },\n};\n\n// Test ReadableStream byte controller enqueue and reads with mismatched sizes works\nexport const readableStreamBytesMismatchedSizes = {\n  async test() {\n    const enc = new TextEncoder();\n    let pulls = 0;\n    const rs = new ReadableStream({\n      type: 'bytes',\n      start(c) {\n        c.enqueue(enc.encode('hello'));\n      },\n      pull(c) {\n        if (c.byobRequest) {\n          pulls++;\n          c.enqueue(enc.encode('there'));\n        }\n      },\n    });\n    const reader = rs.getReader({ mode: 'byob' });\n\n    await Promise.all(\n      [\n        enc.encode('he'),\n        enc.encode('ll'),\n        enc.encode('o'),\n        enc.encode('th'),\n        enc.encode('er'),\n        enc.encode('e'),\n      ].map(async (i) => {\n        const { done, value } = await reader.read(new Uint8Array(2));\n        ok(!done);\n        strictEqual(value.byteLength, i.byteLength);\n        for (let n = 0; n < value.byteLength; n++) {\n          strictEqual(value[n], i[n]);\n        }\n      })\n    );\n\n    strictEqual(pulls, 1);\n  },\n};\n\n// Test ReadableStream byte controller enqueue and reads with mismatched view types works\nexport const readableStreamBytesMismatchedViewTypes = {\n  async test() {\n    let pull = 0;\n    const rs = new ReadableStream({\n      type: 'bytes',\n      pull(c) {\n        if (c.byobRequest) {\n          const view = c.byobRequest.view;\n          switch (pull++) {\n            case 0: {\n              strictEqual(view.byteLength, 8);\n              strictEqual(view.byteOffset, 0);\n              view[0] = 1;\n              view[1] = 2;\n              view[2] = 3;\n              view[3] = 4;\n              view[4] = 5;\n              view[5] = 6;\n              view[6] = 7;\n              c.byobRequest.respond(7);\n              break;\n            }\n            case 1: {\n              strictEqual(view.byteLength, 5);\n              strictEqual(view.byteOffset, 3);\n              view[0] = 8;\n              c.byobRequest.respond(1);\n              c.close();\n              break;\n            }\n          }\n        }\n      },\n    });\n\n    const r = rs.getReader({ mode: 'byob' });\n\n    {\n      const { value } = await r.read(new Uint32Array(2));\n      ok(value instanceof Uint32Array);\n      strictEqual(value.length, 1);\n      strictEqual(value.byteLength, 4);\n      strictEqual(value.buffer.byteLength, 8);\n      const u8 = new Uint8Array(value.buffer, 0, value.byteLength);\n      strictEqual(u8[0], 1);\n      strictEqual(u8[1], 2);\n      strictEqual(u8[2], 3);\n      strictEqual(u8[3], 4);\n    }\n\n    {\n      const { value } = await r.read(new Uint32Array(2));\n      ok(value instanceof Uint32Array);\n      strictEqual(value.length, 1);\n      strictEqual(value.byteLength, 4);\n      strictEqual(value.buffer.byteLength, 8);\n      const u8 = new Uint8Array(value.buffer);\n      strictEqual(u8[0], 5);\n      strictEqual(u8[1], 6);\n      strictEqual(u8[2], 7);\n      strictEqual(u8[3], 8);\n    }\n  },\n};\n\n// Test ReadableStream byte controller enqueue subarray works\nexport const readableStreamBytesEnqueueSubarray = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    const rs = new ReadableStream({\n      type: 'bytes',\n      pull(c) {\n        const u8 = enc.encode('hello');\n        c.enqueue(u8.subarray(1, 4));\n        strictEqual(u8.byteLength, 0);\n        c.close();\n      },\n    });\n\n    const r = rs.getReader({ mode: 'byob' });\n\n    const { value } = await r.read(new Uint8Array(5));\n\n    strictEqual(dec.decode(value), 'ell');\n  },\n};\n\n// Test ReadableStream default and bytes controllers close promise works\nexport const readableStreamDefaultClosePromise = {\n  async test() {\n    // Value stream\n    {\n      let controller;\n      const rs = new ReadableStream({\n        start(c) {\n          controller = c;\n        },\n      });\n      const r = rs.getReader();\n      let closed = false;\n      r.closed.then(() => (closed = true));\n      controller.enqueue(1);\n      controller.close();\n      await r.read();\n      ok(closed);\n    }\n\n    // Byte stream default reader\n    {\n      let controller;\n      const rs = new ReadableStream({\n        type: 'bytes',\n        start(c) {\n          controller = c;\n        },\n      });\n\n      const r = rs.getReader();\n\n      let closed = false;\n      r.closed.then(() => (closed = true));\n      controller.enqueue(new Uint8Array(1));\n      controller.close();\n      await r.read();\n      await scheduler.wait(1);\n      ok(closed);\n    }\n\n    // Byte stream BYOB reader\n    {\n      let controller;\n      const rs = new ReadableStream({\n        type: 'bytes',\n        start(c) {\n          controller = c;\n        },\n      });\n\n      const r = rs.getReader({ mode: 'byob' });\n\n      let closed = false;\n      r.closed.then(() => (closed = true));\n      controller.enqueue(new Uint8Array(1));\n      controller.close();\n      await r.read(new Uint8Array(1));\n      await scheduler.wait(1);\n      ok(closed);\n    }\n  },\n};\n\n// Test ReadableStream default and bytes reads can be canceled\nexport const readableStreamCancelReads = {\n  async test() {\n    // Value stream\n    {\n      const rs = new ReadableStream();\n      const reader = rs.getReader();\n      const read = reader.read();\n      reader.cancel();\n\n      const { done, value } = await read;\n      ok(done);\n      strictEqual(value, undefined);\n    }\n\n    // Byte stream default reader\n    {\n      const rs = new ReadableStream({\n        type: 'bytes',\n      });\n      const reader = rs.getReader();\n      const read = reader.read();\n      reader.cancel();\n\n      const { done } = await read;\n      ok(done);\n    }\n\n    // Byte stream BYOB reader\n    {\n      const rs = new ReadableStream({\n        type: 'bytes',\n      });\n      const reader = rs.getReader({ mode: 'byob' });\n      const read = reader.read(new Uint8Array(1));\n      reader.cancel();\n\n      const { done } = await read;\n      ok(done);\n    }\n\n    // Byte stream BYOB reader with cancel reason\n    {\n      let cancelCalled = false;\n      const rs = new ReadableStream({\n        type: 'bytes',\n        cancel(reason) {\n          strictEqual(reason, 'boom');\n          cancelCalled = true;\n        },\n      });\n      const reader = rs.getReader({ mode: 'byob' });\n      const read = reader.read(new Uint8Array(1));\n      reader.cancel('boom');\n\n      const { done } = await read;\n      ok(done);\n      ok(cancelCalled);\n    }\n  },\n};\n\n// Test ReadableStream default and byte controller release lock work\nexport const readableStreamReleaseLock = {\n  async test() {\n    // With capture_async_api_throws, async methods (pipeTo) return rejected promises instead of throwing\n    // pipeThrough returns ReadableStream (not a promise), so it still throws synchronously\n    const captureAsyncThrows =\n      Cloudflare.compatibilityFlags.capture_async_api_throws;\n\n    // Value stream\n    {\n      const rs = new ReadableStream();\n      const reader = rs.getReader();\n      throws(() => rs.getReader(), TypeError);\n      throws(() => rs.tee(), TypeError);\n      if (captureAsyncThrows) {\n        await rejects(rs.pipeTo(), TypeError);\n      } else {\n        throws(() => rs.pipeTo(), TypeError);\n      }\n      throws(() => rs.pipeThrough(), TypeError);\n\n      reader.releaseLock();\n      rs.getReader();\n    }\n\n    // Byte stream default reader\n    {\n      const rs = new ReadableStream({\n        type: 'bytes',\n      });\n      const reader = rs.getReader();\n      throws(() => rs.getReader(), TypeError);\n      throws(() => rs.tee(), TypeError);\n      if (captureAsyncThrows) {\n        await rejects(rs.pipeTo(), TypeError);\n      } else {\n        throws(() => rs.pipeTo(), TypeError);\n      }\n      throws(() => rs.pipeThrough(), TypeError);\n\n      reader.releaseLock();\n      rs.getReader();\n    }\n\n    // Byte stream BYOB reader\n    {\n      const rs = new ReadableStream({\n        type: 'bytes',\n      });\n      const reader = rs.getReader({ mode: 'byob' });\n      throws(() => rs.getReader(), TypeError);\n      throws(() => rs.tee(), TypeError);\n      if (captureAsyncThrows) {\n        await rejects(rs.pipeTo(), TypeError);\n      } else {\n        throws(() => rs.pipeTo(), TypeError);\n      }\n      throws(() => rs.pipeThrough(), TypeError);\n\n      reader.releaseLock();\n      rs.getReader();\n    }\n  },\n};\n\n// Test ReadableStream default controller does not support BYOB reader\nexport const readableStreamDefaultNoByob = {\n  test() {\n    const rs = new ReadableStream();\n    throws(() => rs.getReader({ mode: 'byob' }), TypeError);\n    throws(() => new ReadableStreamBYOBReader(rs), TypeError);\n  },\n};\n\n// Test ReadableStream default controller tee() works\nexport const readableStreamDefaultTee = {\n  async test() {\n    // Tee an immediately closed ReadableStream\n    {\n      const rs = new ReadableStream({\n        start(c) {\n          c.close();\n        },\n      });\n\n      const [branch1, branch2] = rs.tee();\n\n      const reader1 = branch1.getReader();\n      const reader2 = branch2.getReader();\n\n      const [res1, res2] = await Promise.all([reader1.read(), reader2.read()]);\n\n      strictEqual(res1.done, true);\n      strictEqual(res2.done, true);\n    }\n\n    // Tee with data\n    {\n      const rs = new ReadableStream({\n        pull(c) {\n          c.enqueue(1);\n          c.close();\n        },\n      });\n\n      const [branch1, branch2] = rs.tee();\n\n      const reader1 = branch1.getReader();\n      const reader2 = branch2.getReader();\n\n      const [res1, res2] = await Promise.all([reader1.read(), reader2.read()]);\n\n      strictEqual(res1.value, 1);\n      strictEqual(res2.value, 1);\n\n      const [res3, res4] = await Promise.all([reader1.read(), reader2.read()]);\n\n      strictEqual(res3.done, true);\n      strictEqual(res4.done, true);\n    }\n\n    // Tee with multiple enqueues\n    {\n      let counter = 0;\n      const rs = new ReadableStream({\n        pull(c) {\n          c.enqueue(counter++);\n          if (counter == 2) {\n            c.close();\n          }\n        },\n      });\n\n      const [branch1, branch2] = rs.tee();\n\n      const reader1 = branch1.getReader();\n      const reader2 = branch2.getReader();\n\n      {\n        const [result1, result2] = await Promise.all([\n          reader1.read(),\n          reader2.read(),\n        ]);\n\n        ok(!result1.done);\n        ok(!result2.done);\n        strictEqual(result1.value, 0);\n        strictEqual(result2.value, 0);\n      }\n\n      {\n        const [result1, result2] = await Promise.all([\n          reader1.read(),\n          reader2.read(),\n        ]);\n        ok(!result1.done);\n        ok(!result2.done);\n        strictEqual(result1.value, 1);\n        strictEqual(result2.value, 1);\n      }\n\n      {\n        const [result1, result2] = await Promise.all([\n          reader1.read(),\n          reader2.read(),\n        ]);\n        ok(result1.done);\n        ok(result2.done);\n        strictEqual(result1.value, undefined);\n        strictEqual(result2.value, undefined);\n      }\n    }\n\n    // Canceling one branch does not impact the other\n    {\n      let counter = 0;\n      let canceled = false;\n      const rs = new ReadableStream({\n        pull(c) {\n          c.enqueue(counter++);\n          if (counter == 2) {\n            c.close();\n          }\n        },\n        cancel() {\n          canceled = true;\n        },\n      });\n\n      const [branch1, branch2] = rs.tee();\n\n      const reader1 = branch1.getReader();\n      const reader2 = branch2.getReader();\n\n      {\n        const [result1, result2] = await Promise.all([\n          reader1.read(),\n          reader2.read(),\n        ]);\n\n        ok(!result1.done);\n        ok(!result2.done);\n        strictEqual(result1.value, 0);\n        strictEqual(result2.value, 0);\n      }\n\n      reader2.cancel();\n\n      {\n        const [result1, result2] = await Promise.all([\n          reader1.read(),\n          reader2.read(),\n        ]);\n\n        ok(!canceled);\n\n        ok(!result1.done);\n        ok(result2.done);\n        strictEqual(result1.value, 1);\n        strictEqual(result2.value, undefined);\n      }\n\n      {\n        const [result1, result2] = await Promise.all([\n          reader1.read(),\n          reader2.read(),\n        ]);\n\n        ok(result1.done);\n        ok(result2.done);\n        strictEqual(result1.value, undefined);\n        strictEqual(result2.value, undefined);\n      }\n    }\n\n    // Canceling both tee branches cancels the underlying source\n    {\n      let canceled = false;\n      const rs = new ReadableStream({\n        start(c) {\n          c.enqueue(0);\n        },\n        cancel() {\n          canceled = true;\n        },\n      });\n\n      const [branch1, branch2] = rs.tee();\n\n      const reader1 = branch1.getReader();\n      const reader2 = branch2.getReader();\n\n      {\n        const [result1, result2] = await Promise.all([\n          reader1.read(),\n          reader2.read(),\n        ]);\n\n        ok(!result1.done);\n        ok(!result2.done);\n        strictEqual(result1.value, 0);\n        strictEqual(result2.value, 0);\n      }\n\n      await reader1.cancel();\n      ok(!canceled);\n\n      await reader2.cancel();\n      ok(canceled);\n    }\n\n    // Tee of a tee works\n    {\n      let controller;\n      const rs = new ReadableStream({\n        start(c) {\n          controller = c;\n        },\n      });\n\n      const [branch1, branch2] = rs.tee();\n      const [branch3, branch4] = branch2.tee();\n\n      throws(() => branch2.getReader(), TypeError);\n\n      {\n        const reader1 = branch1.getReader();\n        const reader3 = branch3.getReader();\n        const reader4 = branch4.getReader();\n\n        const read1 = reader1.read();\n        const read3 = reader3.read();\n        const read4 = reader4.read();\n\n        controller.enqueue(1);\n\n        const [result1, result3, result4] = await Promise.all([\n          read1,\n          read3,\n          read4,\n        ]);\n\n        strictEqual(result1.value, 1);\n        strictEqual(result3.value, 1);\n        strictEqual(result4.value, 1);\n      }\n    }\n\n    // Erroring the underlying source errors the branches\n    {\n      let controller;\n      const rs = new ReadableStream({\n        start(c) {\n          controller = c;\n        },\n      });\n\n      const [branch1, branch2] = rs.tee();\n\n      const reader1 = branch1.getReader();\n      const reader2 = branch2.getReader();\n\n      const read1 = reader1.read();\n      const read2 = reader2.read();\n\n      controller.error('boom');\n\n      (await Promise.allSettled([read1, read2])).forEach((i) => {\n        strictEqual(i.status, 'rejected');\n        strictEqual(i.reason, 'boom');\n      });\n    }\n\n    // Tee branches support BYOB reads\n    {\n      let controller;\n      const enc = new TextEncoder();\n      const dec = new TextDecoder();\n      const rs = new ReadableStream({\n        type: 'bytes',\n        start(c) {\n          controller = c;\n        },\n      });\n\n      const [branch1, branch2] = rs.tee();\n\n      const reader1 = branch1.getReader({ mode: 'byob' });\n      const reader2 = branch2.getReader({ mode: 'byob' });\n\n      const buf1 = new Uint8Array(2);\n      const buf2 = new Uint8Array(3);\n\n      const promises = [reader1.read(buf1), reader2.read(buf2)];\n\n      controller.enqueue(enc.encode('hello'));\n\n      const results = await Promise.all(promises);\n\n      strictEqual(dec.decode(results[0].value), 'he');\n      strictEqual(dec.decode(results[1].value), 'hel');\n    }\n  },\n};\n\n// =====================================================================================\n// WritableStream tests\n// =====================================================================================\n\n// Test new WritableStream() works\nexport const newWritableStream = {\n  test() {\n    new WritableStream();\n  },\n};\n\n// Test new WritableStream() with sink works\nexport const newWritableStreamWithSink = {\n  async test() {\n    // Sync sink with abort\n    {\n      let started = false;\n      let written = false;\n      let closed = false;\n      let aborted = false;\n      const ws = new WritableStream({\n        start(c) {\n          ok(c instanceof WritableStreamDefaultController);\n          started = true;\n        },\n        write(value, c) {\n          strictEqual(value, 1);\n          ok(c instanceof WritableStreamDefaultController);\n          written = true;\n        },\n        abort(reason) {\n          strictEqual(reason.message, 'boom');\n          aborted = true;\n        },\n        close() {\n          closed = true;\n        },\n      });\n\n      ok(started);\n\n      const writer = ws.getWriter();\n\n      await writer.write(1);\n      ok(written);\n\n      await writer.abort(new Error('boom'));\n      ok(aborted);\n      ok(!closed);\n\n      await rejects(writer.closed);\n    }\n\n    // Sync sink with close\n    {\n      let started = false;\n      let written = false;\n      let closed = false;\n      let aborted = false;\n      const ws = new WritableStream({\n        start(c) {\n          ok(c instanceof WritableStreamDefaultController);\n          started = true;\n        },\n        write(value, c) {\n          strictEqual(value, 1);\n          ok(c instanceof WritableStreamDefaultController);\n          written = true;\n        },\n        abort() {\n          aborted = true;\n        },\n        close() {\n          closed = true;\n        },\n      });\n\n      ok(started);\n\n      const writer = ws.getWriter();\n\n      await writer.write(1);\n      ok(written);\n\n      await Promise.all([writer.close(), writer.closed]);\n\n      ok(!aborted);\n      ok(closed);\n    }\n  },\n};\n\n// Test new WritableStream() with async sink works\nexport const newWritableStreamWithSinkAsync = {\n  async test() {\n    // Async sink with abort\n    {\n      let started = false;\n      let written = false;\n      let closed = false;\n      let aborted = false;\n      const ws = new WritableStream({\n        async start(c) {\n          ok(c instanceof WritableStreamDefaultController);\n          await scheduler.wait(10);\n          started = true;\n        },\n        async write(value, c) {\n          await scheduler.wait(10);\n          strictEqual(value, 1);\n          ok(c instanceof WritableStreamDefaultController);\n          written = true;\n        },\n        async abort(reason) {\n          await scheduler.wait(10);\n          strictEqual(reason.message, 'boom');\n          aborted = true;\n        },\n        async close() {\n          closed = true;\n        },\n      });\n\n      await scheduler.wait(15);\n      ok(started);\n\n      const writer = ws.getWriter();\n      await writer.ready;\n\n      await writer.write(1);\n      ok(written);\n\n      await writer.abort(new Error('boom'));\n      ok(aborted);\n      ok(!closed);\n\n      await rejects(writer.closed);\n    }\n\n    // Async sink with close\n    {\n      let started = false;\n      let written = false;\n      let closed = false;\n      let aborted = false;\n      const ws = new WritableStream({\n        async start(c) {\n          ok(c instanceof WritableStreamDefaultController);\n          await scheduler.wait(10);\n          started = true;\n        },\n        async write(value, c) {\n          await scheduler.wait(10);\n          strictEqual(value, 1);\n          ok(c instanceof WritableStreamDefaultController);\n          written = true;\n        },\n        async abort() {\n          await scheduler.wait(10);\n          aborted = true;\n        },\n        async close() {\n          await scheduler.wait(10);\n          closed = true;\n        },\n      });\n\n      await scheduler.wait(15);\n      ok(started);\n\n      const writer = ws.getWriter();\n      await writer.ready;\n\n      await writer.write(1);\n      ok(written);\n\n      await Promise.all([writer.close(), writer.closed]);\n      ok(!aborted);\n      ok(closed);\n    }\n  },\n};\n\n// Test new WritableStream() start algorithm error handled\nexport const newWritableStreamStartError = {\n  async test() {\n    // Sync start error\n    {\n      const ws = new WritableStream({\n        start() {\n          throw new Error('boom');\n        },\n      });\n\n      const writer = ws.getWriter();\n      await rejects(writer.write(1), { message: 'boom' });\n    }\n\n    // Async start error\n    {\n      const ws = new WritableStream({\n        async start() {\n          throw new Error('boom');\n        },\n      });\n\n      const writer = ws.getWriter();\n      await rejects(writer.write(1), { message: 'boom' });\n    }\n\n    // Start with controller.error\n    {\n      const ws = new WritableStream({\n        start(c) {\n          c.error(new Error('boom'));\n        },\n      });\n\n      const writer = ws.getWriter();\n      await rejects(writer.write(1), { message: 'boom' });\n    }\n  },\n};\n\n// Test new WritableStream() write algorithm error handled\nexport const newWritableStreamWriteError = {\n  async test() {\n    // Sync write error\n    {\n      const ws = new WritableStream({\n        write() {\n          throw new Error('boom');\n        },\n      });\n\n      const writer = ws.getWriter();\n      await rejects(writer.write(1), { message: 'boom' });\n    }\n\n    // Async write error\n    {\n      const ws = new WritableStream({\n        async write() {\n          throw new Error('boom');\n        },\n      });\n\n      const writer = ws.getWriter();\n      await rejects(writer.write(1), { message: 'boom' });\n    }\n\n    // Write with controller.error\n    {\n      const ws = new WritableStream({\n        write(value, c) {\n          strictEqual(value, 1);\n          c.error(new Error('boom'));\n        },\n      });\n\n      const writer = ws.getWriter();\n\n      // Should succeed\n      await writer.write(1);\n\n      await rejects(writer.closed, { message: 'boom' });\n    }\n  },\n};\n\n// Test new WritableStream() abort algorithm error handled\nexport const newWritableStreamAbortError = {\n  async test() {\n    // Sync abort error\n    {\n      const ws = new WritableStream({\n        abort(reason) {\n          strictEqual(reason, 1);\n          throw new Error('boom');\n        },\n      });\n\n      const writer = ws.getWriter();\n\n      await rejects(writer.abort(1), { message: 'boom' });\n\n      // Calling abort again returns the same rejected promise\n      await rejects(writer.abort(1), { message: 'boom' });\n    }\n\n    // Async abort error\n    {\n      const ws = new WritableStream({\n        async abort(reason) {\n          strictEqual(reason, 1);\n          throw new Error('boom');\n        },\n      });\n\n      const writer = ws.getWriter();\n      await rejects(writer.abort(1), { message: 'boom' });\n    }\n\n    // Abort with controller.error\n    {\n      let controller;\n      const ws = new WritableStream({\n        start(c) {\n          controller = c;\n        },\n        async abort(reason) {\n          strictEqual(reason, 1);\n          controller.error(new Error('ignored'));\n        },\n      });\n\n      const writer = ws.getWriter();\n\n      await writer.abort(1);\n\n      // The closed promise will use the abort reason, not the error\n      // reported in the controller\n      const results = await Promise.allSettled([writer.closed]);\n      strictEqual(results[0].status, 'rejected');\n      strictEqual(results[0].reason, 1);\n    }\n  },\n};\n\n// Test new WritableStream() close algorithm error handled\nexport const newWritableStreamCloseError = {\n  async test() {\n    // Sync close error\n    {\n      const ws = new WritableStream({\n        close() {\n          throw new Error('boom');\n        },\n      });\n\n      const writer = ws.getWriter();\n      await rejects(writer.close(), { message: 'boom' });\n    }\n\n    // Async close error\n    {\n      const ws = new WritableStream({\n        async close() {\n          throw new Error('boom');\n        },\n      });\n\n      const writer = ws.getWriter();\n      await rejects(writer.close(), { message: 'boom' });\n    }\n\n    // Close with controller.error (ignored)\n    {\n      let controller;\n      const ws = new WritableStream({\n        start(c) {\n          controller = c;\n        },\n        async close() {\n          controller.error(new Error('ignored'));\n        },\n      });\n\n      const writer = ws.getWriter();\n\n      // In this case, the error reported in the close algorithm is ignored\n      await Promise.all([writer.close(), writer.closed]);\n    }\n  },\n};\n\n// Test WritableStream multiple pending writes allowed\nexport const writableStreamMultiplePendingWrites = {\n  async test() {\n    const expectedWrites = ['hello', 'there'];\n    const ws = new WritableStream({\n      async write(value) {\n        await scheduler.wait(10);\n        strictEqual(value, expectedWrites.shift());\n      },\n    });\n\n    const writer = ws.getWriter();\n\n    await Promise.all([writer.write('hello'), writer.write('there')]);\n  },\n};\n\n// Test WritableStream writing a subarray works\nexport const writableStreamWriteSubarray = {\n  async test() {\n    const u8 = new Uint8Array([1, 2, 3, 4]);\n    const sub = u8.subarray(1, 3);\n\n    const ws = new WritableStream({\n      write(value) {\n        strictEqual(value, sub);\n      },\n    });\n\n    const writer = ws.getWriter();\n\n    await writer.write(sub);\n  },\n};\n\n// Test WritableStream writing any javascript value works\nexport const writableStreamWriteAny = {\n  async test() {\n    // Make a copy since we'll shift() from it\n    const expectedWrites = [\n      'hello',\n      true,\n      1,\n      1.1,\n      undefined,\n      NaN,\n      Infinity,\n      new Uint8Array(1),\n      {},\n      [],\n    ];\n    // Keep original for writing\n    const valuesToWrite = [...expectedWrites];\n\n    const ws = new WritableStream({\n      async write(value) {\n        await scheduler.wait(1);\n        const expected = expectedWrites.shift();\n        // Use Object.is for NaN and -0/+0 handling (same as testharness same_value)\n        ok(Object.is(value, expected), `expected ${expected} but got ${value}`);\n      },\n    });\n\n    const writer = ws.getWriter();\n\n    await Promise.all(valuesToWrite.map((i) => writer.write(i)));\n  },\n};\n\n// Test WritableStream desiredSize calculated correctly\nexport const writableStreamDesiredSize = {\n  async test() {\n    const ws = new WritableStream(\n      {\n        async write() {\n          await scheduler.wait(10);\n        },\n      },\n      {\n        highWaterMark: 2,\n      }\n    );\n\n    const writer = ws.getWriter();\n\n    const firstReady = writer.ready;\n    await firstReady;\n\n    strictEqual(writer.desiredSize, 2);\n    const write1 = writer.write(1);\n    strictEqual(writer.desiredSize, 1);\n\n    const write2 = writer.write(2);\n    const write3 = writer.write(3);\n\n    strictEqual(writer.desiredSize, -1);\n\n    await Promise.all([write1, write2, write3]);\n\n    ok(firstReady != writer.ready);\n    await writer.ready;\n  },\n};\n\n// Test WritableStream writes can be aborted\nexport const writableStreamWriteAbort = {\n  async test() {\n    let aborted = false;\n    const ws = new WritableStream({\n      start(c) {\n        c.signal.addEventListener(\n          'abort',\n          () => {\n            strictEqual(c.signal.reason.message, 'boom');\n            aborted = true;\n          },\n          { once: true }\n        );\n      },\n      async write() {\n        await scheduler.wait(10);\n      },\n    });\n\n    const writer = ws.getWriter();\n    const write1 = writer.write(1);\n    const write2 = writer.write(2);\n\n    writer.abort(new Error('boom'));\n\n    const [result1, result2] = await Promise.allSettled([write1, write2]);\n\n    // Both writes fail\n    strictEqual(result1.status, 'rejected');\n    strictEqual(result2.status, 'rejected');\n    strictEqual(result1.reason.message, 'boom');\n    strictEqual(result2.reason.message, 'boom');\n\n    ok(aborted);\n\n    // Aborting puts the stream into a persistent errored state\n    writer.releaseLock();\n    const writer2 = ws.getWriter();\n\n    await rejects(writer2.write('should not be allowed'), { message: 'boom' });\n  },\n};\n\n// Test WritableStream uses size algorithm correctly\nexport const writableStreamSizeAlgorithm = {\n  async test() {\n    // Size algorithm called\n    {\n      let sizeCalled = false;\n      const ws = new WritableStream(\n        {\n          async write() {\n            await scheduler.wait(10);\n          },\n        },\n        {\n          highWaterMark: 2,\n          size(value) {\n            sizeCalled = true;\n            strictEqual(value, 'hello');\n            return 2;\n          },\n        }\n      );\n\n      const writer = ws.getWriter();\n      strictEqual(writer.desiredSize, 2);\n      const write = writer.write('hello');\n      ok(sizeCalled);\n      strictEqual(writer.desiredSize, 0);\n      await write;\n    }\n\n    // Size algorithm error\n    {\n      const ws = new WritableStream(\n        {},\n        {\n          size() {\n            throw new Error('boom');\n          },\n        }\n      );\n\n      const writer = ws.getWriter();\n      await rejects(writer.write('hello'), { message: 'boom' });\n    }\n  },\n};\n\n// Test WritableStream aborting should replace ready promise\nexport const writableStreamAbortReadyRejected = {\n  async test() {\n    const ws = new WritableStream();\n    const writer = ws.getWriter();\n    const ready = writer.ready;\n\n    await ready;\n\n    writer.abort('boom');\n\n    ok(ready !== writer.ready);\n\n    const results = await Promise.allSettled([writer.ready]);\n    strictEqual(results[0].status, 'rejected');\n    strictEqual(results[0].reason, 'boom');\n  },\n};\n\n// Test WritableStream abort with no argument defaults to undefined\nexport const writableStreamAbortOptional = {\n  async test() {\n    const ws = new WritableStream();\n    const writer = ws.getWriter();\n    await writer.abort();\n\n    const results = await Promise.allSettled([writer.closed]);\n    strictEqual(results[0].status, 'rejected');\n    strictEqual(results[0].reason, undefined);\n  },\n};\n\n// Test WritableStream abort while starting rejects ready promise\nexport const writableStreamAbortWhileStarting = {\n  async test() {\n    const ws = new WritableStream({\n      async start() {},\n    });\n\n    const writer = ws.getWriter();\n    writer.abort('boom');\n\n    const results = await Promise.allSettled([writer.ready]);\n    strictEqual(results[0].status, 'rejected');\n    strictEqual(results[0].reason, 'boom');\n  },\n};\n\n// Test WritableStream abort error while writing rejects abort promise\nexport const writableStreamAbortWhileWriting = {\n  async test() {\n    const ws = new WritableStream({\n      async write() {\n        await scheduler.wait(10);\n      },\n\n      async abort() {\n        throw new Error('boom');\n      },\n    });\n\n    const writer = ws.getWriter();\n    const write = writer.write('test');\n\n    await rejects(writer.abort(), { message: 'boom' });\n\n    await write;\n\n    // The write should reject with undefined because the writer.abort() above\n    // specified undefined. The abort algorithm should not be called again.\n    await rejects(writer.write('should fail'), (err) => err === undefined);\n  },\n};\n\n// Test WritableStream releaseLock while aborting should reject closed promise\nexport const writableStreamReleaseLockWhileAborting = {\n  async test() {\n    const ws = new WritableStream({\n      async write() {\n        await scheduler.wait(10);\n      },\n    });\n\n    const writer = ws.getWriter();\n    writer.write('test');\n\n    writer.abort();\n    const closed = writer.closed;\n\n    writer.releaseLock();\n\n    await rejects(closed, TypeError);\n  },\n};\n\n// Test WritableStream throw during in flight close rejects abort and closed promise\nexport const writableStreamCloseThrowRejectsPromises = {\n  async test() {\n    const ws = new WritableStream({\n      async close() {\n        throw new Error('boom');\n      },\n    });\n\n    const writer = ws.getWriter();\n    const close = writer.close();\n    const abort = writer.abort();\n    const closed = writer.closed;\n\n    const res = await Promise.allSettled([close, abort, closed]);\n\n    strictEqual(res[0].status, 'rejected');\n    strictEqual(res[1].status, 'rejected');\n    strictEqual(res[2].status, 'rejected');\n\n    // The close and abort promises are rejected with the error thrown,\n    // the closed promise, however, should be rejected with the reason\n    // given in the abort(), which is defaulted to undefined.\n    strictEqual(res[0].reason.message, 'boom');\n    strictEqual(res[1].reason.message, 'boom');\n    strictEqual(res[2].reason, undefined);\n  },\n};\n\n// Test WritableStream sink abort not called while write or close in flight\nexport const writableStreamAbortTiming = {\n  async test() {\n    // Abort waits for start\n    {\n      let started = false;\n      const ws = new WritableStream({\n        async start() {\n          await scheduler.wait(10);\n          started = true;\n        },\n        abort() {\n          ok(started, 'The stream should have started first');\n        },\n      });\n\n      await ws.abort();\n    }\n\n    // Abort waits for write\n    {\n      let writeCompleted = false;\n      const ws = new WritableStream({\n        async write() {\n          await scheduler.wait(10);\n          writeCompleted = true;\n        },\n        abort() {\n          ok(writeCompleted, 'The write should have completed');\n        },\n      });\n\n      const writer = ws.getWriter();\n      const write = writer.write('hello');\n      const abort = writer.abort();\n\n      await Promise.allSettled([write, abort]);\n    }\n\n    // Abort waits for close\n    {\n      let closeCompleted = false;\n      const ws = new WritableStream({\n        async close() {\n          await scheduler.wait(10);\n          closeCompleted = true;\n        },\n        abort() {\n          ok(closeCompleted, 'The close should have completed');\n        },\n      });\n\n      const writer = ws.getWriter();\n      const close = writer.close();\n      const abort = writer.abort();\n\n      await Promise.allSettled([close, abort]);\n    }\n  },\n};\n\n// Test WritableStream abort during write should trigger abort algorithm with close pending\nexport const writableStreamAbortWriteClosePending = {\n  async test() {\n    let abortCalled = false;\n    const ws = new WritableStream({\n      async write() {\n        await scheduler.wait(10);\n      },\n      abort() {\n        abortCalled = true;\n      },\n    });\n\n    const writer = ws.getWriter();\n    const write = writer.write('hello');\n    const close = writer.close();\n    const abort = writer.abort();\n\n    const res = await Promise.allSettled([write, close, abort]);\n    ok(abortCalled);\n\n    strictEqual(res[0].status, 'fulfilled'); // Write finishes\n    strictEqual(res[1].status, 'rejected'); // Pending close is aborted\n    strictEqual(res[2].status, 'fulfilled'); // Abort finishes\n  },\n};\n\n// Test WritableStream ready promise rejects on controller error not waiting for in flight write\nexport const writableStreamErrorDuringInFlightWrite = {\n  async test() {\n    let controller;\n    const ws = new WritableStream({\n      start(c) {\n        controller = c;\n      },\n      async write() {\n        await scheduler.wait(10);\n      },\n    });\n\n    const writer = ws.getWriter();\n\n    const write = writer.write('hello').catch(() => {});\n\n    controller.error('boom');\n\n    await Promise.all([write, writer.ready.catch(() => {})]);\n  },\n};\n\n// Test WritableStream start errors after abort, close rejects\nexport const writableStreamStartErrorAfterAbort = {\n  async test() {\n    const ws = new WritableStream({\n      async start() {\n        await scheduler.wait(10);\n        throw new Error('boom');\n      },\n    });\n\n    ws.abort();\n\n    // close() should return a rejected promise. The abort was called\n    // before start finished, so the stream enters an errored state.\n    await ws.close().catch(() => {});\n\n    // Verify the stream is errored\n    strictEqual(ws.locked, false);\n  },\n};\n\n// Test WritableStream rejected sink write does not prevent sink abort\nexport const writableStreamRejectedWriteNoPreventAbort = {\n  async test() {\n    let abortCalled = false;\n    const ws = new WritableStream({\n      write() {\n        throw new Error('boom');\n      },\n      abort() {\n        abortCalled = true;\n      },\n    });\n\n    const writer = ws.getWriter();\n    const write = writer.write('hello');\n    const abort = writer.abort();\n\n    const res = await Promise.allSettled([write, abort]);\n    ok(abortCalled);\n    strictEqual(res[0].status, 'rejected');\n    strictEqual(res[1].status, 'fulfilled');\n  },\n};\n\n// Test WritableStream aborted twice\nexport const writableStreamAbortedTwice = {\n  async test() {\n    const ws = new WritableStream();\n    const abort1 = ws.abort();\n    const abort2 = ws.abort();\n\n    await Promise.all([abort1, abort2]);\n  },\n};\n\n// Test WritableStream aborting errored stream rejects with stored error\nexport const writableStreamAbortOnErroredResolves = {\n  async test() {\n    const ws = new WritableStream({\n      start(c) {\n        c.error(new Error('boom'));\n      },\n    });\n\n    // When aborting an already-errored stream, abort() rejects with the stored error\n    await rejects(ws.abort(), Error);\n  },\n};\n\n// Test WritableStream sink abort not called if stream errored before abort\nexport const writableStreamSinkAlgNoCallErrorBeforeAbort = {\n  test() {\n    let controller;\n    let abortCalled = false;\n    const ws = new WritableStream({\n      start(c) {\n        controller = c;\n      },\n      abort() {\n        abortCalled = true;\n      },\n    });\n\n    controller.error(new Error('boom'));\n    ws.abort(new Error('bang'));\n\n    ok(!abortCalled);\n  },\n};\n\n// Test WritableStream writer with pending abort, ready should reject\nexport const writableStreamWriterWithPendingAbort = {\n  async test() {\n    const ws = new WritableStream();\n    ws.abort(new Error('boom'));\n    const writer = ws.getWriter();\n\n    await rejects(writer.ready, Error);\n  },\n};\n\n// Test WritableStream promises resolved in order\nexport const writableStreamPromisesResolvedInOrder = {\n  async test() {\n    // Write before close\n    {\n      let closeFinished = false;\n      const ws = new WritableStream();\n\n      const writer = ws.getWriter();\n\n      const write = writer.write('hello').then(() => ok(!closeFinished));\n      const close = writer.close();\n      const closed = writer.closed.then(() => (closeFinished = true));\n\n      // Closed promise should not resolve before fulfilled write.\n      await Promise.allSettled([write, close, closed]);\n    }\n\n    // Rejected write before close\n    {\n      let closeFinished = false;\n      let writeFailed = false;\n      const ws = new WritableStream({\n        write() {\n          throw new Error('boom');\n        },\n      });\n\n      const writer = ws.getWriter();\n\n      const write = writer.write('hello').catch(() => {\n        ok(!closeFinished);\n        writeFailed = true;\n      });\n      const close = writer.close();\n      const closed = writer.closed.then(() => (closeFinished = true));\n\n      // Closed promise should not resolve before rejected write.\n      await Promise.allSettled([write, close, closed]);\n      ok(writeFailed);\n    }\n\n    // Writes resolved in order when aborting\n    {\n      const order = [];\n      const ws = new WritableStream({\n        async write() {\n          await scheduler.wait(10);\n        },\n      });\n\n      const writer = ws.getWriter();\n\n      const write1 = writer.write('hello').then(() => order.push(1));\n      const write2 = writer.write('hello').catch(() => order.push(2));\n      const write3 = writer.write('hello').catch(() => order.push(3));\n      const abort = writer.abort();\n\n      await Promise.allSettled([write1, write2, write3, abort]);\n\n      strictEqual(order[0], 1);\n      strictEqual(order[1], 2);\n      strictEqual(order[2], 3);\n    }\n  },\n};\n\n// =====================================================================================\n// Misc tests\n// =====================================================================================\n\n// Test highWaterMark validation\nexport const highWaterMarkValidated = {\n  test() {\n    [-1, -Infinity, NaN, {}, 'foo'].forEach((highWaterMark) => {\n      throws(() => new WritableStream(undefined, { highWaterMark }), TypeError);\n      throws(() => new ReadableStream(undefined, { highWaterMark }), TypeError);\n    });\n  },\n};\n\n// Test QueuingStrategy objects work\nexport const queuingStrategies = {\n  test() {\n    // ByteLengthQueuingStrategy\n    {\n      const strategy = new ByteLengthQueuingStrategy({ highWaterMark: 10 });\n\n      let startRan = false;\n\n      // Make sure we can create a stream using the strategy without error.\n      new ReadableStream(\n        {\n          start(c) {\n            strictEqual(c.desiredSize, 10);\n            c.enqueue(new Uint8Array(2));\n            strictEqual(c.desiredSize, 8);\n            startRan = true;\n          },\n        },\n        strategy\n      );\n\n      const { highWaterMark, size } = strategy;\n\n      ok(startRan);\n      strictEqual(highWaterMark, 10);\n      strictEqual(size('nothing'), undefined);\n      strictEqual(size(123), undefined);\n      strictEqual(size(undefined), undefined);\n      strictEqual(size(null), undefined);\n      strictEqual(size(), undefined);\n      strictEqual(size(new ArrayBuffer(10)), 10);\n      strictEqual(size(new Uint8Array(10)), 10);\n    }\n\n    // CountQueuingStrategy\n    {\n      const strategy = new CountQueuingStrategy({ highWaterMark: 9 });\n\n      let startRan = false;\n\n      // Make sure we can create a stream using the strategy without error.\n      new ReadableStream(\n        {\n          start(c) {\n            strictEqual(c.desiredSize, 9);\n            c.enqueue(new Uint8Array(2));\n            strictEqual(c.desiredSize, 8);\n            startRan = true;\n          },\n        },\n        strategy\n      );\n\n      const { highWaterMark, size } = strategy;\n\n      ok(startRan);\n      strictEqual(highWaterMark, 9);\n      strictEqual(size('nothing'), 1);\n      strictEqual(size(123), 1);\n      strictEqual(size(undefined), 1);\n      strictEqual(size(null), 1);\n      strictEqual(size(), 1);\n      strictEqual(size(new ArrayBuffer(10)), 1);\n      strictEqual(size(new Uint8Array(10)), 1);\n    }\n  },\n};\n\n// Test proper default highwater mark\nexport const hwmDefault = {\n  async test() {\n    let pulled = 0;\n    new ReadableStream({\n      start(c) {\n        strictEqual(c.desiredSize, 1);\n      },\n      pull() {\n        pulled++;\n      },\n    });\n\n    new ReadableStream({\n      type: 'bytes',\n      start(c) {\n        strictEqual(c.desiredSize, 0);\n      },\n      pull() {\n        pulled += 2;\n      },\n    });\n\n    await scheduler.wait(1);\n    strictEqual(pulled, 1);\n  },\n};\n\n// Test byobreader regression\nexport const byobreaderRegression = {\n  async test() {\n    function newReadableStream(chunks) {\n      chunks = chunks.filter((ch) => ch !== 0);\n      return new ReadableStream({\n        type: 'bytes',\n        start(c) {\n          if (chunks.length === 0) {\n            c.close();\n          }\n        },\n        pull(c) {\n          c.enqueue(new Uint8Array(chunks.shift()));\n          if (chunks.length === 0) {\n            c.close();\n          }\n        },\n      });\n    }\n\n    const rs = newReadableStream([]);\n    // Ensure that getting a byob reader on a closed byte stream works correctly.\n    const reader = rs.getReader({ mode: 'byob' });\n    const { done } = await reader.read(new Uint8Array(10));\n    ok(done);\n  },\n};\n\n// Test writer double close\nexport const writerDoubleClose = {\n  async test() {\n    const ws = new WritableStream({\n      write() {},\n    });\n    const writer = ws.getWriter();\n\n    writer.write(123);\n\n    writer.close();\n    // With capture_async_api_throws, async methods return rejected promises instead of throwing\n    if (Cloudflare.compatibilityFlags.capture_async_api_throws) {\n      await rejects(writer.close(), TypeError);\n    } else {\n      throws(() => writer.close(), TypeError);\n    }\n  },\n};\n\n// =====================================================================================\n// GC tests (these require --expose-gc v8 flag)\n// =====================================================================================\n\n// Test ReadableStream object references are held through gc\nexport const readableStreamReferencesHold = {\n  async test() {\n    let controller;\n    let reader;\n    let read;\n\n    // Byte stream\n    {\n      const rs = new ReadableStream({\n        type: 'bytes',\n        start(c) {\n          controller = c;\n        },\n      });\n\n      reader = rs.getReader({ mode: 'byob' });\n    }\n\n    await scheduler.wait(10);\n    gc();\n\n    {\n      read = reader.read(new Uint8Array(1));\n      reader = undefined;\n    }\n\n    await scheduler.wait(10);\n    gc();\n\n    {\n      controller.enqueue(new Uint8Array([1]));\n      controller = undefined;\n      const { value, done } = await read;\n      ok(!done);\n      strictEqual(value[0], 1);\n    }\n\n    // Value stream\n    {\n      let controller;\n      let reader;\n      let read;\n\n      {\n        const rs = new ReadableStream({\n          start(c) {\n            controller = c;\n          },\n        });\n        reader = rs.getReader();\n      }\n\n      await scheduler.wait(10);\n      gc();\n\n      {\n        read = reader.read();\n        reader = undefined;\n      }\n\n      await scheduler.wait(10);\n      gc();\n\n      {\n        controller.enqueue('hello');\n        controller = undefined;\n        const { value, done } = await read;\n        ok(!done);\n        strictEqual(value, 'hello');\n      }\n    }\n  },\n};\n\n// Test WritableStream object references are held through gc\nexport const writableStreamGc = {\n  async test() {\n    let controller;\n    let writer;\n    let write;\n\n    {\n      const ws = new WritableStream({\n        start(c) {\n          controller = c;\n        },\n      });\n      writer = ws.getWriter();\n    }\n\n    await scheduler.wait(10);\n    gc();\n\n    {\n      write = writer.write(1);\n      writer = undefined;\n    }\n\n    await scheduler.wait(10);\n    gc();\n\n    {\n      await write;\n      strictEqual(controller.signal.aborted, false);\n    }\n  },\n};\n\n// Test ReadableStream with async iterator gc works\nexport const asyncIteratorGc = {\n  async test() {\n    // This test verifies that the ReadableStream and its async iterator\n    // are properly handled through gc\n    function getNextPromise() {\n      let values = new ReadableStream({\n        async pull(controller) {\n          await scheduler.wait(50);\n          controller.enqueue('A');\n          controller.close();\n        },\n      }).values();\n      values.next();\n      const promise = values.next();\n      values = undefined;\n      return promise;\n    }\n\n    let promise = getNextPromise();\n    gc();\n    strictEqual((await promise).done, true);\n    promise = undefined;\n    gc();\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/streams-js-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  v8Flags = [\"--expose-gc\"],\n  services = [\n    ( name = \"streams-js-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"streams-js-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"streams_enable_constructors\",\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/streams-no-auto-allocate-test.js",
    "content": "// Test for streams_no_default_auto_allocate_chunk_size compat flag\nimport { strictEqual, ok, deepStrictEqual } from 'node:assert';\n\nexport const byobRequestIsNullWithoutAutoAllocate = {\n  async test() {\n    // When autoAllocateChunkSize is NOT set, byobRequest should be null\n    // for non-BYOB reads (spec-compliant behavior)\n    let byobRequestWasNull = false;\n    let pullCalled = false;\n\n    const rs = new ReadableStream({\n      type: 'bytes',\n      pull(controller) {\n        pullCalled = true;\n        byobRequestWasNull = controller.byobRequest === null;\n        // Must use enqueue() since byobRequest is null\n        controller.enqueue(new Uint8Array([1, 2, 3, 4]));\n        controller.close();\n      },\n    });\n\n    const reader = rs.getReader();\n    const result = await reader.read();\n\n    ok(pullCalled, 'pull should have been called');\n    ok(\n      byobRequestWasNull,\n      'byobRequest should be null when autoAllocateChunkSize is not set'\n    );\n    strictEqual(result.done, false);\n    deepStrictEqual([...result.value], [1, 2, 3, 4]);\n  },\n};\n\nexport const byobRequestAvailableWithAutoAllocate = {\n  async test() {\n    // When autoAllocateChunkSize IS set, byobRequest should be available\n    let byobRequestWasAvailable = false;\n    let pullCalled = false;\n\n    const rs = new ReadableStream({\n      type: 'bytes',\n      autoAllocateChunkSize: 1024,\n      pull(controller) {\n        pullCalled = true;\n        byobRequestWasAvailable = controller.byobRequest !== null;\n        if (controller.byobRequest) {\n          // Use the BYOB request\n          const view = controller.byobRequest.view;\n          new Uint8Array(view.buffer, view.byteOffset, 4).set([5, 6, 7, 8]);\n          controller.byobRequest.respond(4);\n        } else {\n          controller.enqueue(new Uint8Array([5, 6, 7, 8]));\n        }\n        controller.close();\n      },\n    });\n\n    const reader = rs.getReader();\n    const result = await reader.read();\n\n    ok(pullCalled, 'pull should have been called');\n    ok(\n      byobRequestWasAvailable,\n      'byobRequest should be available when autoAllocateChunkSize is set'\n    );\n    strictEqual(result.done, false);\n    deepStrictEqual([...result.value], [5, 6, 7, 8]);\n  },\n};\n\nexport const byobReadStillWorks = {\n  async test() {\n    // BYOB reads should still work regardless of autoAllocateChunkSize\n    let byobRequestWasAvailable = false;\n\n    const rs = new ReadableStream({\n      type: 'bytes',\n      // No autoAllocateChunkSize set\n      pull(controller) {\n        byobRequestWasAvailable = controller.byobRequest !== null;\n        if (controller.byobRequest) {\n          const view = controller.byobRequest.view;\n          new Uint8Array(view.buffer, view.byteOffset, 4).set([9, 10, 11, 12]);\n          controller.byobRequest.respond(4);\n        }\n        controller.close();\n      },\n    });\n\n    // Use BYOB reader\n    const reader = rs.getReader({ mode: 'byob' });\n    const buffer = new Uint8Array(16);\n    const result = await reader.read(buffer);\n\n    ok(\n      byobRequestWasAvailable,\n      'byobRequest should be available for BYOB reads'\n    );\n    strictEqual(result.done, false);\n    deepStrictEqual([...result.value], [9, 10, 11, 12]);\n  },\n};\n\nexport const multipleReadsWithEnqueue = {\n  async test() {\n    // Test that multiple reads work correctly with enqueue()\n    let pullCount = 0;\n\n    const rs = new ReadableStream({\n      type: 'bytes',\n      pull(controller) {\n        pullCount++;\n        if (pullCount <= 3) {\n          controller.enqueue(new Uint8Array([pullCount]));\n        } else {\n          controller.close();\n        }\n      },\n    });\n\n    const reader = rs.getReader();\n    const chunks = [];\n\n    while (true) {\n      const { value, done } = await reader.read();\n      if (done) break;\n      chunks.push([...value]);\n    }\n\n    strictEqual(chunks.length, 3);\n    deepStrictEqual(chunks[0], [1]);\n    deepStrictEqual(chunks[1], [2]);\n    deepStrictEqual(chunks[2], [3]);\n  },\n};\n\nexport const pipeToWorksWithoutAutoAllocate = {\n  async test() {\n    // Test that pipeTo works correctly without autoAllocateChunkSize\n    const allBytes = [];\n\n    const rs = new ReadableStream({\n      type: 'bytes',\n      start(controller) {\n        controller.enqueue(new Uint8Array([1, 2]));\n        controller.enqueue(new Uint8Array([3, 4]));\n        controller.close();\n      },\n    });\n\n    const ws = new WritableStream({\n      write(chunk) {\n        allBytes.push(...chunk);\n      },\n    });\n\n    await rs.pipeTo(ws);\n\n    // Verify all bytes were received (chunks may be combined)\n    deepStrictEqual(allBytes, [1, 2, 3, 4]);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/streams-no-auto-allocate-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"streams-no-auto-allocate-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"streams-no-auto-allocate-test.js\")\n        ],\n        compatibilityFlags = [\n          \"experimental\",\n          \"nodejs_compat\",\n          \"streams_enable_constructors\",\n          \"streams_no_default_auto_allocate_chunk_size\",\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/streams-r2-patterns-test.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { strictEqual, ok } from 'node:assert';\n\n// Test BYOB readAtLeast with automatic atLeast handling\nexport const byobReadAtLeastAutomatic = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    const chunks = ['hello', 'there'];\n    const rs = new ReadableStream({\n      type: 'bytes',\n      pull(c) {\n        // When using enqueue, the stream impl will take care of properly handling the\n        // at least requirement...\n        c.enqueue(enc.encode(chunks.shift()));\n        if (chunks.length === 0) c.close();\n      },\n    });\n\n    const reader = rs.getReader({ mode: 'byob' });\n\n    const res = await reader.readAtLeast(100, new Uint8Array(100));\n\n    strictEqual(dec.decode(res.value), 'hellothere');\n  },\n};\n\n// Test BYOB readAtLeast with manual atLeast handling\nexport const byobReadAtLeastManual = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    const chunks = ['hello', 'there'];\n    const expectedAtLeasts = [100, 95];\n    const rs = new ReadableStream({\n      type: 'bytes',\n      pull(c) {\n        if (chunks.length === 0) {\n          c.close();\n          c.byobRequest.respond(0);\n        } else {\n          // The respond() can partially fulfill the minRead requirement over\n          // multiple calls to pull.\n          strictEqual(c.byobRequest.atLeast, expectedAtLeasts.shift());\n\n          enc.encodeInto(chunks.shift(), c.byobRequest.view);\n          c.byobRequest.respond(5);\n        }\n      },\n    });\n\n    const reader = rs.getReader({ mode: 'byob' });\n\n    const res = await reader.readAtLeast(100, new Uint8Array(100));\n\n    strictEqual(dec.decode(res.value), 'hellothere');\n  },\n};\n\n// Test IdentityTransformStream with readAtLeast incremental writes\nexport const identityTransformReadAtLeast = {\n  async test() {\n    const { readable, writable } = new IdentityTransformStream();\n\n    const reader = readable.getReader({ mode: 'byob' });\n    const writer = writable.getWriter();\n\n    // There's been a latent bug in IdentityTransformStream ever since\n    // readAtLeast was introduced that caused it to mishandle the atLeast\n    // calculation when individual writes were < atLeast.\n\n    for (let n = 0; n < 8; n++) {\n      writer.write(new Uint8Array(1));\n    }\n    writer.write(new Uint8Array([0x1]));\n    writer.write(new Uint8Array([0x2]));\n    writer.write(new Uint8Array([0x3]));\n    writer.write(new Uint8Array([0x4]));\n\n    const res = await reader.readAtLeast(8, new Uint8Array(8));\n    strictEqual(res.value.byteLength, 8);\n\n    const res2 = await reader.readAtLeast(2, new Uint8Array(4));\n    const res3 = await reader.readAtLeast(2, new Uint8Array(4));\n\n    strictEqual(res2.value.byteLength, 2);\n    strictEqual(res2.value[0], 0x1);\n    strictEqual(res2.value[1], 0x2);\n\n    strictEqual(res3.value.byteLength, 2);\n    strictEqual(res3.value[0], 0x3);\n    strictEqual(res3.value[1], 0x4);\n  },\n};\n\n// Test FixedLengthStream with readAtLeast incremental writes\nexport const fixedLengthStreamReadAtLeast = {\n  async test() {\n    const { readable, writable } = new FixedLengthStream(12);\n\n    const reader = readable.getReader({ mode: 'byob' });\n    const writer = writable.getWriter();\n\n    // There's been a latent bug in IdentityTransformStream ever since\n    // readAtLeast was introduced that caused it to mishandle the atLeast\n    // calculation when individual writes were < atLeast.\n\n    for (let n = 0; n < 8; n++) {\n      writer.write(new Uint8Array(1));\n    }\n    writer.write(new Uint8Array([0x1]));\n    writer.write(new Uint8Array([0x2]));\n    writer.write(new Uint8Array([0x3]));\n    writer.write(new Uint8Array([0x4]));\n\n    const res = await reader.readAtLeast(8, new Uint8Array(8));\n    strictEqual(res.value.byteLength, 8);\n\n    const res2 = await reader.readAtLeast(2, new Uint8Array(4));\n    const res3 = await reader.readAtLeast(2, new Uint8Array(4));\n\n    strictEqual(res2.value.byteLength, 2);\n    strictEqual(res2.value[0], 0x1);\n    strictEqual(res2.value[1], 0x2);\n\n    strictEqual(res3.value.byteLength, 2);\n    strictEqual(res3.value[0], 0x3);\n    strictEqual(res3.value[1], 0x4);\n  },\n};\n\n// Test BYOB stream tee closed on start with waitUntil\n// Tests that a teed BYOB stream that closes immediately after enqueuing\n// still works correctly when one branch is consumed via waitUntil\nexport const closedByobTeeOnStart = {\n  async test(ctrl, env, ctx) {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n\n    async function consume(rs) {\n      const reader = rs.getReader({ mode: 'byob' });\n      let result = '';\n      for (;;) {\n        const res = await reader.readAtLeast(10, new Uint8Array(10));\n        if (res.done) break;\n        result += dec.decode(res.value, { stream: true });\n      }\n      result += dec.decode();\n      if (result !== 'hello') throw new Error('Incorrect result in branch');\n      return result;\n    }\n\n    const rs = new ReadableStream({\n      type: 'bytes',\n      start(c) {\n        c.enqueue(enc.encode('hello'));\n        c.close();\n      },\n    });\n\n    const [b1, b2] = rs.tee();\n\n    const branch2Promise = consume(b2);\n    ctx.waitUntil(branch2Promise);\n\n    const result1 = await consume(b1);\n    strictEqual(result1, 'hello');\n\n    const result2 = await branch2Promise;\n    strictEqual(result2, 'hello');\n  },\n};\n\n// Test IdentityTransformStream properly handles readAtLeast\nexport const identityTransformStreamReadAtLeast = {\n  async test() {\n    const { readable, writable } = new IdentityTransformStream();\n\n    const reader = readable.getReader({ mode: 'byob' });\n    const writer = writable.getWriter();\n\n    const expectedReads = [100, 100, 1, 0];\n\n    async function consume(reader) {\n      const res = await reader.readAtLeast(100, new Uint8Array(100));\n      if (!res.done) {\n        strictEqual(res.value.byteLength, expectedReads.shift());\n        return consume(reader);\n      }\n    }\n\n    await Promise.all([\n      consume(reader),\n      writer.write(new Uint8Array(100)),\n      writer.write(new Uint8Array(1)),\n      writer.write(new Uint8Array(100)),\n      writer.close(),\n    ]);\n  },\n};\n\n// Test BYOB readAtLeast partially filled\nexport const partiallyFilledByobAtLeast = {\n  async test() {\n    const { readable, writable } = new IdentityTransformStream();\n    const reader = readable.getReader({ mode: 'byob' });\n    const rs = new ReadableStream({\n      type: 'bytes',\n      async pull(controller) {\n        const chunk = await reader.readAtLeast(100, new Uint8Array(100));\n        if (!chunk.done) {\n          controller.enqueue(chunk.value);\n        } else {\n          controller.close();\n        }\n      },\n    });\n\n    async function consume(readable) {\n      let ab = new ArrayBuffer(102);\n      const dec = new TextDecoder();\n      let ret = '';\n      const reader = readable.getReader({ mode: 'byob' });\n      for (;;) {\n        const read = await reader.readAtLeast(102, new Uint8Array(ab));\n        if (!read.done) {\n          ret += dec.decode(read.value);\n          ab = read.value.buffer;\n          continue;\n        } else {\n          break;\n        }\n      }\n      strictEqual(ret, 'hello'.repeat(1000));\n      return ret.length;\n    }\n\n    const p = consume(rs);\n\n    const enc = new TextEncoder();\n    const writer = writable.getWriter();\n    writer.write(enc.encode('hello'.repeat(1000)));\n    writer.close();\n\n    strictEqual(await p, 5000);\n  },\n};\n\n// Test BYOB readAtLeast with tee\nexport const byobReadAtLeastTee = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    const chunks = ['hello', 'there'];\n    const expectedAtLeasts = [100, 95];\n    const rs = new ReadableStream({\n      type: 'bytes',\n      pull(c) {\n        if (chunks.length === 0) {\n          c.close();\n          c.byobRequest.respond(0);\n        } else {\n          strictEqual(c.byobRequest.atLeast, expectedAtLeasts.shift());\n          enc.encodeInto(chunks.shift(), c.byobRequest.view);\n          c.byobRequest.respond(5);\n        }\n      },\n    });\n\n    const [branch1, branchB] = rs.tee();\n    const [branch2, branch3] = branchB.tee();\n\n    const reader = branch1.getReader({ mode: 'byob' });\n    const reader2 = branch2.getReader({ mode: 'byob' });\n    const reader3 = branch3.getReader({ mode: 'byob' });\n\n    const p1 = reader.readAtLeast(100, new Uint8Array(100));\n    const p2 = reader2.readAtLeast(5, new Uint8Array(100));\n    const p3 = reader3.readAtLeast(3, new Uint8Array(3));\n\n    const res = await Promise.all([p1, p2, p3]);\n\n    strictEqual(dec.decode(res[0].value), 'hellothere');\n    strictEqual(dec.decode(res[1].value), 'hello');\n    strictEqual(dec.decode(res[2].value), 'hel');\n\n    const res2 = await reader2.readAtLeast(5, new Uint8Array(100));\n    strictEqual(dec.decode(res2.value), 'there');\n\n    const res3 = await reader3.readAtLeast(4, new Uint8Array(4));\n    strictEqual(dec.decode(res3.value), 'loth');\n\n    const res4 = await reader.readAtLeast(100, new Uint8Array(100));\n    strictEqual(res4.done, true);\n\n    const res5 = await reader2.readAtLeast(5, new Uint8Array(100));\n    strictEqual(res5.done, true);\n\n    const res6 = await reader3.readAtLeast(4, new Uint8Array(4));\n    strictEqual(dec.decode(res6.value), 'ere');\n\n    const res7 = await reader2.readAtLeast(5, new Uint8Array(100));\n    strictEqual(res7.done, true);\n  },\n};\n\n// Test BYOB readAtLeast with tee complex variant 1\nexport const byobReadAtLeastTeeComplex1 = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    const chunks = ['helloth', 'ere'];\n    let previousByobRequest;\n    const rs = new ReadableStream({\n      type: 'bytes',\n      pull(c) {\n        const req = c.byobRequest;\n        if (chunks.length === 0) {\n          c.close();\n          req.respond(0);\n        } else {\n          ok(!(req === previousByobRequest));\n          const chunk = chunks.shift();\n          enc.encodeInto(chunk, req.view);\n          req.respond(chunk.length);\n        }\n      },\n    });\n\n    const [branch1, branchB] = rs.tee();\n    const [branch2, branch3] = branchB.tee();\n\n    const reader1 = branch1.getReader({ mode: 'byob' });\n    const reader2 = branch2.getReader({ mode: 'byob' });\n    const reader3 = branch3.getReader({ mode: 'byob' });\n\n    const res1 = await reader1.readAtLeast(5, new Uint8Array(10));\n    strictEqual(dec.decode(res1.value), 'helloth');\n    const res2 = await reader2.readAtLeast(10, new Uint8Array(10));\n    strictEqual(dec.decode(res2.value), 'hellothere');\n\n    const res3 = await reader1.readAtLeast(5, new Uint8Array(10));\n    strictEqual(dec.decode(res3.value), 'ere');\n\n    const res4 = await reader3.readAtLeast(2, new Uint8Array(12));\n    strictEqual(dec.decode(res4.value), 'hellothere');\n  },\n};\n\n// Test BYOB readAtLeast with tee complex variant 2\nexport const byobReadAtLeastTeeComplex2 = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    const chunks = ['helloth', 'ere'];\n    let previousByobRequest;\n    const rs = new ReadableStream({\n      type: 'bytes',\n      pull(c) {\n        if (chunks.length === 0) {\n          c.close();\n          c.byobRequest.respond(0);\n        } else {\n          ok(!(c.byobRequest === previousByobRequest));\n          const chunk = chunks.shift();\n          enc.encodeInto(chunk, c.byobRequest.view);\n          c.byobRequest.respond(chunk.length);\n        }\n      },\n    });\n\n    const [branch1, branchB] = rs.tee();\n    const [branch2, branch3] = branchB.tee();\n\n    const reader1 = branch1.getReader({ mode: 'byob' });\n    const reader2 = branch2.getReader({ mode: 'byob' });\n    const reader3 = branch3.getReader({ mode: 'byob' });\n\n    const res1 = await reader1.readAtLeast(5, new Uint8Array(10));\n    strictEqual(dec.decode(res1.value), 'helloth');\n    const res2 = await reader2.readAtLeast(10, new Uint8Array(10));\n    strictEqual(dec.decode(res2.value), 'hellothere');\n\n    const res3 = await reader1.readAtLeast(5, new Uint8Array(10));\n    strictEqual(dec.decode(res3.value), 'ere');\n\n    const res4 = await reader3.readAtLeast(2, new Uint8Array(12));\n    strictEqual(dec.decode(res4.value), 'hellothere');\n  },\n};\n\n// Test BYOB readAtLeast with tee complex variant 3 (typed arrays)\nexport const byobReadAtLeastTeeComplex3 = {\n  async test() {\n    const chunks = [\n      new Uint8Array([0x01]),\n      new Uint8Array([0x02]),\n      new Uint8Array([0x03]),\n      new Uint8Array([0x04]),\n      new Uint8Array([0x05, 0x06]),\n    ];\n\n    const rs = new ReadableStream({\n      type: 'bytes',\n      pull(c) {\n        if (chunks.length === 0) {\n          c.close();\n          c.byobRequest.respond(0);\n        } else {\n          const view = c.byobRequest.view;\n          const chunk = chunks.shift();\n          for (let n = 0; n < chunk.length; n++) {\n            view[n] = chunk[n];\n          }\n          c.byobRequest.respond(chunk.length);\n        }\n      },\n    });\n\n    const [branch1, branch2] = rs.tee();\n\n    const reader1 = branch1.getReader({ mode: 'byob' });\n    const reader2 = branch2.getReader({ mode: 'byob' });\n\n    const [res1, res2, res3, res4] = await Promise.all([\n      reader1.readAtLeast(2, new Uint16Array(2)),\n      reader1.readAtLeast(2, new Uint8Array(2)),\n      reader2.readAtLeast(2, new Uint8Array(2)),\n      reader2.readAtLeast(1, new Uint32Array(1)),\n    ]);\n\n    strictEqual(res1.value instanceof Uint16Array, true);\n    strictEqual(res2.value instanceof Uint8Array, true);\n    strictEqual(res1.value[0], 0x0201);\n    strictEqual(res1.value[1], 0x0403);\n    strictEqual(res2.value[0], 0x05);\n    strictEqual(res2.value[1], 0x06);\n\n    strictEqual(res3.value instanceof Uint8Array, true);\n    strictEqual(res4.value instanceof Uint32Array, true);\n    strictEqual(res3.value[0], 0x1);\n    strictEqual(res3.value[1], 0x2);\n    strictEqual(res4.value[0], 0x06050403);\n  },\n};\n\nexport const requestCloneByob = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    const chunks = [\n      enc.encode('hello'),\n      enc.encode('there'),\n      enc.encode('!!!!!'),\n    ];\n    const rs = new ReadableStream({\n      type: 'bytes',\n      pull(c) {\n        c.enqueue(chunks.shift());\n        if (chunks.length === 0) c.close();\n      },\n    });\n\n    const newRequest = new Request('http://example.org', {\n      method: 'POST',\n      body: rs,\n    });\n    const reader = newRequest.clone().body.getReader({ mode: 'byob' });\n\n    strictEqual(\n      dec.decode((await reader.readAtLeast(10, new Uint8Array(10))).value),\n      'hellothere'\n    );\n  },\n};\n\nexport const textDecoderStreamRequest = {\n  async test() {\n    const enc = new TextEncoder();\n    const rs = new ReadableStream({\n      type: 'bytes',\n      start(c) {\n        c.enqueue(enc.encode('hello'));\n        c.close();\n      },\n    });\n\n    const request = new Request('http://example.org', {\n      method: 'POST',\n      body: rs,\n    });\n\n    const reader = request.body\n      .pipeThrough(new TextDecoderStream('utf-8'))\n      .getReader();\n    strictEqual(typeof (await reader.read()).value, 'string');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/streams-r2-patterns-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"streams-r2-patterns-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"streams-r2-patterns-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"streams_enable_constructors\", \"transformstream_enable_standard_constructor\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/streams-respond-test.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { strictEqual, rejects, ok } from 'node:assert';\n\n// Test Response body methods with JS-backed BYOB ReadableStream\nexport const responseBodyMethodsJsByob = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n\n    {\n      const rs = new ReadableStream({\n        type: 'bytes',\n        async pull(c) {\n          if (c.byobRequest) {\n            enc.encodeInto('hello', c.byobRequest.view);\n            c.byobRequest.respond(5);\n            c.close();\n          }\n        },\n      });\n\n      const resp = new Response(rs);\n\n      strictEqual(dec.decode(await resp.arrayBuffer()), 'hello');\n    }\n\n    {\n      const rs = new ReadableStream({\n        type: 'bytes',\n        async pull(c) {\n          if (c.byobRequest) {\n            enc.encodeInto('hello', c.byobRequest.view);\n            c.byobRequest.respond(5);\n            c.close();\n          }\n        },\n      });\n\n      const resp = new Response(rs);\n\n      strictEqual(await resp.text(), 'hello');\n    }\n  },\n};\n\n// Test Request body methods with JS-backed ReadableStream\nexport const requestBodyMethodsJsByob = {\n  async test() {\n    const enc = new TextEncoder();\n    const wrapped = new ReadableStream({\n      type: 'bytes',\n      async pull(c) {\n        c.enqueue(enc.encode('hello'));\n        c.close();\n      },\n    });\n\n    const req = new Request('http://example.com', {\n      method: 'POST',\n      body: wrapped,\n    });\n    const text = await req.text();\n\n    strictEqual(text, 'hello');\n  },\n};\n\n// Test basic JS ReadableStream as Response body\nexport const jsSource = {\n  async test() {\n    const enc = new TextEncoder();\n    const rs = new ReadableStream({\n      start(c) {\n        c.enqueue(enc.encode('hello'));\n        c.close();\n      },\n    });\n\n    const response = new Response(rs);\n    strictEqual(await response.text(), 'hello');\n  },\n};\n\n// Test JS ReadableStream with async pull as Response body\nexport const jsSourceAsyncPull = {\n  async test() {\n    const enc = new TextEncoder();\n    const chunks = [enc.encode('hello'), enc.encode('there')];\n    const rs = new ReadableStream({\n      async pull(c) {\n        await scheduler.wait(10);\n        if (chunks.length > 0) c.enqueue(chunks.shift());\n        if (chunks.length === 0) c.close();\n      },\n    });\n\n    const response = new Response(rs);\n    strictEqual(await response.text(), 'hellothere');\n  },\n};\n\n// Test BYOB ReadableStream as Response body\nexport const jsByteSource = {\n  async test() {\n    const enc = new TextEncoder();\n    const rs = new ReadableStream({\n      type: 'bytes',\n      pull(c) {\n        const request = c.byobRequest;\n        if (request != null) {\n          enc.encodeInto('hello', request.view);\n          request.respond(5);\n          c.close();\n        }\n      },\n    });\n\n    const response = new Response(rs);\n    strictEqual(await response.text(), 'hello');\n  },\n};\n\n// Test BYOB ReadableStream with multiple chunks\nexport const jsByteSourceMultipleChunks = {\n  async test() {\n    const enc = new TextEncoder();\n    const chunks = ['hello', 'there', 'this', 'is', 'a', 'test'];\n\n    const rs = new ReadableStream({\n      type: 'bytes',\n      async pull(c) {\n        await scheduler.wait(10);\n        const request = c.byobRequest;\n        if (request != null) {\n          const chunk = chunks.shift();\n          if (chunk !== undefined) {\n            const { written } = enc.encodeInto(chunk, request.view);\n            request.respond(written);\n          } else {\n            c.close();\n            request.respond(0);\n          }\n        }\n      },\n    });\n\n    const response = new Response(rs);\n    strictEqual(await response.text(), 'hellotherethisisatest');\n  },\n};\n\n// Test teed JS ReadableStream as Response body\nexport const jsTeeSource = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n    const readable = new ReadableStream({\n      start(c) {\n        c.enqueue(enc.encode('hello'));\n        c.close();\n      },\n    });\n\n    const [branch1, branch2] = readable.tee();\n    const reader = branch2.getReader();\n\n    // By reading first, we ensure that the branch1 will have queued\n    // read data before the Response is actually transmitted. Both tee\n    // branches should still resolve to the same data.\n\n    const result = await reader.read();\n    strictEqual(dec.decode(result.value), 'hello');\n\n    const response = new Response(branch1);\n    strictEqual(await response.text(), 'hello');\n  },\n};\n\n// Test closed teed BYOB stream\nexport const jsTeeClose = {\n  async test() {\n    const rs = new ReadableStream({\n      type: 'bytes',\n      async pull(c) {\n        if (c.byobRequest) {\n          c.close();\n          c.byobRequest.respond(0);\n        }\n      },\n    });\n\n    const [branch] = rs.tee();\n    const response = new Response(branch);\n    strictEqual(await response.text(), '');\n  },\n};\n\n// Test large enqueue with synchronous close (default stream)\nexport const bigEnqueue = {\n  async test() {\n    const a = 'a'.repeat(4096 * 2);\n    const enc = new TextEncoder();\n    const rs = new ReadableStream({\n      start(c) {\n        c.enqueue(enc.encode(a));\n        c.close();\n      },\n    });\n\n    const response = new Response(rs);\n    const text = await response.text();\n    strictEqual(text.length, 8192);\n    strictEqual(text, a);\n  },\n};\n\n// Test large enqueue with synchronous close (bytes stream)\nexport const bigEnqueueBytes = {\n  async test() {\n    const a = 'a'.repeat(4096 * 2);\n    const enc = new TextEncoder();\n    const rs = new ReadableStream({\n      type: 'bytes',\n      start(c) {\n        c.enqueue(enc.encode(a));\n        c.close();\n      },\n    });\n\n    const response = new Response(rs);\n    const text = await response.text();\n    strictEqual(text.length, 8192);\n    strictEqual(text, a);\n  },\n};\n\n// Test large enqueue via IdentityTransformStream (sync close)\nexport const bigEnqueueViaIdentityTransform = {\n  async test() {\n    const a = 'a'.repeat(4096 * 2 + 345);\n    const enc = new TextEncoder();\n\n    const rs = new ReadableStream({\n      start(c) {\n        c.enqueue(enc.encode(a));\n        c.close();\n      },\n    });\n\n    const transform = new IdentityTransformStream();\n    const response = new Response(rs.pipeThrough(transform));\n\n    const text = await response.text();\n    strictEqual(text.length, 8537);\n    strictEqual(text, a);\n  },\n};\n\n// Test large enqueue via IdentityTransformStream (async close)\nexport const bigEnqueueViaIdentityTransformAsync = {\n  async test() {\n    const a = 'a'.repeat(4096 * 2 + 345);\n    const enc = new TextEncoder();\n\n    const rs = new ReadableStream({\n      async start(c) {\n        c.enqueue(enc.encode(a));\n        await scheduler.wait(10);\n        c.close();\n      },\n    });\n\n    const transform = new IdentityTransformStream();\n    const response = new Response(rs.pipeThrough(transform));\n\n    const text = await response.text();\n    strictEqual(text.length, 8537);\n    strictEqual(text, a);\n  },\n};\n\n// Test large enqueue via JS TransformStream (sync close)\nexport const bigEnqueueViaJsTransform = {\n  async test() {\n    const a = 'a'.repeat(4096 * 2 + 345);\n    const enc = new TextEncoder();\n\n    const rs = new ReadableStream({\n      start(c) {\n        c.enqueue(enc.encode(a));\n        c.close();\n      },\n    });\n\n    const transform = new TransformStream();\n    const response = new Response(rs.pipeThrough(transform));\n\n    const text = await response.text();\n    strictEqual(text.length, 8537);\n    strictEqual(text, a);\n  },\n};\n\n// Test large enqueue via JS TransformStream (async close)\nexport const bigEnqueueViaJsTransformAsync = {\n  async test() {\n    const a = 'a'.repeat(4096 * 2 + 345);\n    const enc = new TextEncoder();\n\n    const rs = new ReadableStream({\n      async start(c) {\n        c.enqueue(enc.encode(a));\n        await scheduler.wait(10);\n        c.close();\n      },\n    });\n\n    const transform = new TransformStream();\n    const response = new Response(rs.pipeThrough(transform));\n\n    const text = await response.text();\n    strictEqual(text.length, 8537);\n    strictEqual(text, a);\n  },\n};\n\nexport const bigEnqueueViaJsTransformSplit = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n\n    const rs = new ReadableStream({\n      start(c) {\n        c.enqueue(enc.encode('a'.repeat(4090)));\n      },\n      pull(c) {\n        c.enqueue(enc.encode('a'.repeat(4096 + 345)));\n        c.close();\n      },\n    });\n\n    const transform = new TransformStream({\n      transform(chunk, controller) {\n        controller.enqueue(enc.encode(dec.decode(chunk).toUpperCase()));\n      },\n    });\n\n    const response = new Response(rs.pipeThrough(transform));\n\n    const text = await response.text();\n    strictEqual(text.length, 4090 + 4096 + 345);\n    strictEqual(text, 'A'.repeat(4090 + 4096 + 345));\n  },\n};\n\n// Test enqueue same chunk multiple times (default stream)\nexport const enqueueChunkMultipleTimes = {\n  async test() {\n    const chunk = new TextEncoder().encode('ping!');\n    const rs = new ReadableStream({\n      start(controller) {\n        controller.enqueue(chunk);\n        controller.enqueue(chunk);\n        controller.enqueue(chunk);\n        controller.enqueue(chunk);\n        controller.close();\n      },\n    });\n\n    const response = new Response(rs);\n    strictEqual(await response.text(), 'ping!ping!ping!ping!');\n  },\n};\n\n// Test enqueue same chunk multiple times errors in bytes stream\nexport const enqueueChunkMultipleTimesBytes = {\n  async test() {\n    const chunk = new TextEncoder().encode('ping!');\n    const rs = new ReadableStream({\n      type: 'bytes',\n      start(controller) {\n        controller.enqueue(chunk);\n        try {\n          controller.enqueue(chunk);\n          throw new Error('this should have failed because chunk is size 0');\n        } catch (err) {\n          if (err.message !== 'Cannot enqueue a zero-length ArrayBuffer.') {\n            throw new Error('Incorrect error: ' + err.message);\n          }\n          controller.close();\n        }\n      },\n    });\n\n    const response = new Response(rs);\n    strictEqual(await response.text(), 'ping!');\n  },\n};\n\nexport const bigEnqueueOddSize = {\n  async test() {\n    const a = 'a'.repeat(4096 * 2 + 345);\n    const enc = new TextEncoder();\n    const rs = new ReadableStream({\n      start(c) {\n        c.enqueue(enc.encode(a));\n        c.close();\n      },\n    });\n\n    const response = new Response(rs);\n    const text = await response.text();\n    strictEqual(text.length, 8537);\n    strictEqual(text, a);\n  },\n};\n\n// In this test, we write data into an IdentityTransformStream\n// We then read from that in a JS ReadableStream\n// We then pipe that through a JS TransformStream\n// We then respond with the TransformStream's readable.\n// We use parallel writes to simulate waitUntil() behavior.\nexport const multistepTransform = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n\n    const { readable, writable } = new IdentityTransformStream();\n    const reader = readable.getReader({ mode: 'byob' });\n\n    const rs = new ReadableStream({\n      type: 'bytes',\n      start(c) {\n        c.enqueue(enc.encode('bbbb'));\n      },\n      async pull(c) {\n        const buffer = new Uint8Array(4096);\n        const result = await reader.read(buffer);\n        if (result.done) {\n          c.enqueue(enc.encode('bye'));\n          c.close();\n        } else {\n          const view = c.byobRequest.view;\n          const toCopy = Math.min(result.value.byteLength, view.byteLength);\n          new Uint8Array(view.buffer, view.byteOffset, toCopy).set(\n            result.value.subarray(0, toCopy)\n          );\n          c.byobRequest.respond(toCopy);\n        }\n      },\n    });\n\n    const transform = new TransformStream({\n      transform(chunk, controller) {\n        controller.enqueue(enc.encode(dec.decode(chunk).toUpperCase()));\n      },\n    });\n\n    const response = new Response(rs.pipeThrough(transform));\n\n    const writer = writable.getWriter();\n    const writePromise = (async () => {\n      await writer.write(enc.encode('a'.repeat(4090)));\n      await writer.write(enc.encode('a'.repeat(4096)));\n      await writer.write(enc.encode('a'.repeat(345)));\n      await writer.close();\n    })();\n\n    const [text] = await Promise.all([response.text(), writePromise]);\n\n    strictEqual(text.startsWith('BBBB'), true);\n    strictEqual(text.endsWith('BYE'), true);\n    strictEqual(text.length, 4 + 4090 + 4096 + 345 + 3);\n  },\n};\n\n// In this test, we write data into an IdentityTransformStream\n// We then read from that in a JS ReadableStream\n// We then pipe that through a JS TransformStream with preventClose = true\n// When the pipe is done, we write a final chunk to the JS TransformStream\n// We then respond with the TransformStream's readable.\nexport const multistepTransformPreventClose = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n\n    const { readable, writable } = new IdentityTransformStream();\n    const reader = readable.getReader({ mode: 'byob' });\n\n    const detachesBuffer =\n      Cloudflare.compatibilityFlags.streams_byob_reader_detaches_buffer;\n\n    const rs = new ReadableStream({\n      type: 'bytes',\n      start(c) {\n        c.enqueue(enc.encode('bbbb'));\n      },\n      async pull(c) {\n        if (detachesBuffer) {\n          const buffer = new Uint8Array(4096);\n          const result = await reader.read(buffer);\n          if (result.done) {\n            c.enqueue(enc.encode('bye'));\n            c.close();\n          } else {\n            const view = c.byobRequest.view;\n            const toCopy = Math.min(result.value.byteLength, view.byteLength);\n            new Uint8Array(view.buffer, view.byteOffset, toCopy).set(\n              result.value.subarray(0, toCopy)\n            );\n            c.byobRequest.respond(toCopy);\n          }\n        } else {\n          const result = await reader.read(c.byobRequest.view);\n          if (result.done) {\n            c.enqueue(enc.encode('bye'));\n            c.close();\n          } else {\n            c.byobRequest.respondWithNewView(result.value);\n          }\n        }\n      },\n    });\n\n    const transform = new TransformStream({\n      transform(chunk, controller) {\n        controller.enqueue(enc.encode(dec.decode(chunk).toUpperCase()));\n      },\n    });\n\n    const promise = rs.pipeTo(transform.writable, { preventClose: true });\n\n    const writer = writable.getWriter();\n    const writePromise = (async () => {\n      await writer.write(enc.encode('a'.repeat(4090)));\n      await writer.write(enc.encode('a'.repeat(4096)));\n      await writer.write(enc.encode('a'.repeat(345)));\n      await writer.close();\n    })();\n\n    async function finishChunk() {\n      await promise;\n      const transformWriter = transform.writable.getWriter();\n      await transformWriter.write(enc.encode('all done'));\n      await transformWriter.close();\n    }\n\n    const response = new Response(transform.readable);\n\n    const [text] = await Promise.all([\n      response.text(),\n      writePromise,\n      finishChunk(),\n    ]);\n\n    strictEqual(text.startsWith('BBBB'), true);\n    strictEqual(text.endsWith('ALL DONE'), true);\n    // 4 (BBBB) + 4090 + 4096 + 345 (A's) + 3 (BYE) + 8 (ALL DONE)\n    strictEqual(text.length, 4 + 4090 + 4096 + 345 + 3 + 8);\n  },\n};\n\nexport const jsSourceError = {\n  async test() {\n    const rs = new ReadableStream({\n      start(c) {\n        throw new Error('boom');\n      },\n    });\n\n    const response = new Response(rs);\n    await rejects(response.text(), { name: 'Error', message: 'boom' });\n  },\n};\n\nexport const jsSourceErrorAsync = {\n  async test() {\n    const rs = new ReadableStream({\n      async pull(c) {\n        await scheduler.wait(10);\n        throw new Error('boom');\n      },\n    });\n\n    const response = new Response(rs);\n    await rejects(response.text(), { name: 'Error', message: 'boom' });\n  },\n};\n\nexport const jsErroredSourceAsync = {\n  async test() {\n    const rs = new ReadableStream({\n      async start(c) {\n        throw new Error('boom');\n      },\n    });\n\n    const response = new Response(rs);\n    await rejects(response.text(), { name: 'Error', message: 'boom' });\n  },\n};\n\nexport const jsErroredSourceAsyncDelayed = {\n  async test() {\n    const rs = new ReadableStream({\n      async start(c) {\n        await scheduler.wait(10);\n        throw new Error('boom');\n      },\n    });\n\n    const response = new Response(rs);\n    await rejects(response.text(), { name: 'Error', message: 'boom' });\n  },\n};\n\nexport const jsNotBytesInPull = {\n  async test() {\n    const rs = new ReadableStream({\n      pull(c) {\n        c.enqueue('hello');\n        c.close();\n      },\n    });\n\n    const response = new Response(rs);\n    await rejects(response.text(), TypeError);\n  },\n};\n\nexport const jsNotBytesInStart = {\n  async test() {\n    const rs = new ReadableStream({\n      start(c) {\n        c.enqueue('hello');\n        c.close();\n      },\n    });\n\n    const response = new Response(rs);\n    await rejects(response.text(), TypeError);\n  },\n};\n\nexport const jsTeeError = {\n  async test() {\n    const rs = new ReadableStream({\n      pull(c) {\n        throw new Error('boom');\n      },\n    });\n\n    const [branch] = rs.tee();\n    const response = new Response(branch);\n    await rejects(response.text(), { name: 'Error', message: 'boom' });\n  },\n};\n\nexport const jsTeeErrorByob = {\n  async test() {\n    const rs = new ReadableStream({\n      type: 'bytes',\n      async pull(c) {\n        if (c.byobRequest) throw new Error('boom');\n      },\n    });\n\n    const [branch] = rs.tee();\n    const response = new Response(branch);\n    await rejects(response.text(), { name: 'Error', message: 'boom' });\n  },\n};\n\nexport const jsSourceTeed = {\n  async test() {\n    const enc = new TextEncoder();\n\n    const readable = new ReadableStream({\n      start(c) {\n        c.enqueue(enc.encode('hello'));\n      },\n      async pull(c) {\n        c.enqueue(enc.encode('a'.repeat(100)));\n        c.enqueue(enc.encode('b'.repeat(200)));\n        c.enqueue(enc.encode('c'.repeat(300)));\n        c.enqueue(enc.encode('d'.repeat(400)));\n        c.enqueue(enc.encode('e'.repeat(500)));\n        c.enqueue(enc.encode('f'.repeat(600)));\n        c.enqueue(enc.encode('g'.repeat(700)));\n        c.enqueue(enc.encode('h'.repeat(800)));\n        await scheduler.wait(10);\n        c.enqueue(enc.encode('i'.repeat(900)));\n        c.enqueue(enc.encode('j'.repeat(1000)));\n        c.enqueue(enc.encode('k'.repeat(1100)));\n        c.close();\n      },\n    });\n\n    const tee = readable.tee();\n\n    async function consume(branch) {\n      for await (const chunk of branch) {\n      }\n    }\n\n    const consumePromise = consume(tee[1]);\n\n    const response = new Response(tee[0]);\n    const text = await response.text();\n\n    await consumePromise;\n\n    strictEqual(text.startsWith('hello'), true);\n    strictEqual(text.endsWith('k'.repeat(1100)), true);\n    strictEqual(\n      text.length,\n      5 + 100 + 200 + 300 + 400 + 500 + 600 + 700 + 800 + 900 + 1000 + 1100\n    );\n  },\n};\n\nexport const jsByteSourceLargeData = {\n  async test() {\n    const largeData = 'x'.repeat(100000);\n    const enc = new TextEncoder();\n\n    const sourceReadable = new ReadableStream({\n      type: 'bytes',\n      start(c) {\n        c.enqueue(enc.encode(largeData));\n        c.close();\n      },\n    });\n\n    const reader = sourceReadable.getReader({ mode: 'byob' });\n\n    const rs = new ReadableStream({\n      type: 'bytes',\n      async pull(c) {\n        const request = c.byobRequest;\n        if (request != null) {\n          const chunk = await reader.read(request.view);\n          if (!chunk.done) {\n            request.respondWithNewView(chunk.value);\n          } else {\n            c.close();\n          }\n        }\n      },\n    });\n\n    const response = new Response(rs);\n    const text = await response.text();\n    strictEqual(text.length, 100000);\n    strictEqual(text, largeData);\n  },\n};\n\nexport const jsByteSourceLargeDataEnqueue = {\n  async test() {\n    const largeData = 'x'.repeat(100000);\n    const enc = new TextEncoder();\n\n    const sourceReadable = new ReadableStream({\n      type: 'bytes',\n      start(c) {\n        c.enqueue(enc.encode(largeData));\n        c.close();\n      },\n    });\n\n    const rs = new ReadableStream({\n      type: 'bytes',\n      async pull(c) {\n        for await (const chunk of sourceReadable) {\n          c.enqueue(chunk);\n        }\n        c.close();\n      },\n    });\n\n    const response = new Response(rs);\n    const text = await response.text();\n    strictEqual(text.length, 100000);\n    strictEqual(text, largeData);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/streams-respond-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"streams-respond-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"streams-respond-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"streams_enable_constructors\",\n          \"transformstream_enable_standard_constructor\",\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/streams-tee-edge-cases-test.js",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Tests for tee() edge cases with asymmetric consumption patterns.\n// These tests focus on scenarios where tee branches are consumed at\n// different rates or only partially consumed.\n//\n// Test inspirations:\n// - Bun: test/js/web/streams/streams.test.js (tee for default and direct streams)\n// - Deno: tests/unit/streams_test.ts (tee tests)\n\nimport { strictEqual, ok, deepStrictEqual } from 'node:assert';\n\n// Test consuming only one branch of a tee completely\n// Inspired by: Bun test/js/web/streams/streams.test.js (tee tests)\nexport const teeConsumeOneBranchFully = {\n  async test() {\n    let pullCount = 0;\n    const rs = new ReadableStream({\n      pull(controller) {\n        pullCount++;\n        if (pullCount <= 5) {\n          controller.enqueue(pullCount);\n        } else {\n          controller.close();\n        }\n      },\n    });\n\n    const [branch1, branch2] = rs.tee();\n\n    // Only consume branch1 fully\n    const reader1 = branch1.getReader();\n    const values = [];\n\n    while (true) {\n      const { value, done } = await reader1.read();\n      if (done) break;\n      values.push(value);\n    }\n\n    deepStrictEqual(values, [1, 2, 3, 4, 5]);\n\n    // branch2 should still be readable (though branch1 consumed the data)\n    ok(!branch2.locked);\n\n    // Now consume branch2\n    const reader2 = branch2.getReader();\n    const values2 = [];\n\n    while (true) {\n      const { value, done } = await reader2.read();\n      if (done) break;\n      values2.push(value);\n    }\n\n    deepStrictEqual(values2, [1, 2, 3, 4, 5]);\n  },\n};\n\n// Test tee with different read rates on branches\n// Inspired by: Deno tests/unit/streams_test.ts (async stream tests)\nexport const teeDifferentReadRates = {\n  async test() {\n    let counter = 0;\n    const rs = new ReadableStream({\n      pull(controller) {\n        counter++;\n        if (counter <= 10) {\n          controller.enqueue(counter);\n        } else {\n          controller.close();\n        }\n      },\n    });\n\n    const [branch1, branch2] = rs.tee();\n\n    const reader1 = branch1.getReader();\n    const reader2 = branch2.getReader();\n\n    const results1 = [];\n    const results2 = [];\n\n    // Read from branch1 fast, branch2 slow\n    for (let i = 0; i < 10; i++) {\n      // Read 2 from branch1\n      results1.push((await reader1.read()).value);\n      if (i % 2 === 1) {\n        // Read 1 from branch2 every other iteration\n        results2.push((await reader2.read()).value);\n      }\n    }\n\n    // Finish reading branch2\n    while (true) {\n      const { value, done } = await reader2.read();\n      if (done) break;\n      results2.push(value);\n    }\n\n    // Finish reading branch1\n    while (true) {\n      const { value, done } = await reader1.read();\n      if (done) break;\n      results1.push(value);\n    }\n\n    // Both branches should have received all values\n    deepStrictEqual(results1, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);\n    deepStrictEqual(results2, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);\n  },\n};\n\n// Test canceling the slower branch mid-stream\n// Inspired by: Bun test/js/web/streams/streams.test.js (cancel tests)\nexport const teeCancelSlowBranch = {\n  async test() {\n    let counter = 0;\n    let sourceCancelled = false;\n\n    const rs = new ReadableStream({\n      pull(controller) {\n        counter++;\n        if (counter <= 20) {\n          controller.enqueue(counter);\n        } else {\n          controller.close();\n        }\n      },\n      cancel() {\n        sourceCancelled = true;\n      },\n    });\n\n    const [branch1, branch2] = rs.tee();\n\n    const reader1 = branch1.getReader();\n    const reader2 = branch2.getReader();\n\n    // Read 5 from both branches\n    for (let i = 0; i < 5; i++) {\n      await reader1.read();\n      await reader2.read();\n    }\n\n    // Cancel branch2 (the \"slow\" one)\n    await reader2.cancel('No longer needed');\n\n    // Source should NOT be cancelled yet (branch1 still active)\n    ok(!sourceCancelled);\n\n    // Continue reading branch1 to completion\n    const remaining = [];\n    while (true) {\n      const { value, done } = await reader1.read();\n      if (done) break;\n      remaining.push(value);\n    }\n\n    // Branch1 should have received remaining values\n    strictEqual(remaining.length, 15);\n    strictEqual(remaining[0], 6);\n    strictEqual(remaining[14], 20);\n  },\n};\n\n// Test tee with byte stream using default readers\n// Inspired by: workerd streams-js-test.js (byte stream tee tests)\nexport const teeByteStreamDefaultReaders = {\n  async test() {\n    const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);\n    let offset = 0;\n\n    const rs = new ReadableStream({\n      type: 'bytes',\n      pull(controller) {\n        if (offset < data.length) {\n          const chunk = data.slice(offset, offset + 2);\n          offset += 2;\n          controller.enqueue(chunk);\n        } else {\n          controller.close();\n        }\n      },\n    });\n\n    const [branch1, branch2] = rs.tee();\n\n    // Use default readers (not BYOB)\n    const reader1 = branch1.getReader();\n    const reader2 = branch2.getReader();\n\n    const bytes1 = [];\n    const bytes2 = [];\n\n    // Read all from both branches, collecting individual bytes\n    while (true) {\n      const { value, done } = await reader1.read();\n      if (done) break;\n      for (const b of value) bytes1.push(b);\n    }\n\n    while (true) {\n      const { value, done } = await reader2.read();\n      if (done) break;\n      for (const b of value) bytes2.push(b);\n    }\n\n    // Both branches should have received all 8 bytes with same values\n    deepStrictEqual(bytes1, [1, 2, 3, 4, 5, 6, 7, 8]);\n    deepStrictEqual(bytes2, [1, 2, 3, 4, 5, 6, 7, 8]);\n  },\n};\n\n// Test tee with byte stream using mixed reader types\n// Inspired by: workerd streams-js-test.js (BYOB tee tests)\nexport const teeByteStreamMixedReaders = {\n  async test() {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n\n    let controller;\n    const rs = new ReadableStream({\n      type: 'bytes',\n      start(c) {\n        controller = c;\n      },\n    });\n\n    const [branch1, branch2] = rs.tee();\n\n    // Use BYOB reader on branch1, default reader on branch2\n    const reader1 = branch1.getReader({ mode: 'byob' });\n    const reader2 = branch2.getReader();\n\n    // Start reads\n    const read1Promise = reader1.read(new Uint8Array(5));\n    const read2Promise = reader2.read();\n\n    // Enqueue data\n    controller.enqueue(enc.encode('hello'));\n    controller.close();\n\n    const [result1, result2] = await Promise.all([read1Promise, read2Promise]);\n\n    // Both should receive the data\n    strictEqual(dec.decode(result1.value), 'hello');\n    strictEqual(dec.decode(result2.value), 'hello');\n  },\n};\n\n// Test tee with large number of chunks\n// Inspired by: Deno tests/unit/streams_test.ts (large stream tests)\nexport const teeLargeChunkCount = {\n  async test() {\n    const CHUNK_COUNT = 1000;\n    let counter = 0;\n\n    const rs = new ReadableStream({\n      pull(controller) {\n        if (counter < CHUNK_COUNT) {\n          controller.enqueue(counter++);\n        } else {\n          controller.close();\n        }\n      },\n    });\n\n    const [branch1, branch2] = rs.tee();\n\n    // Read both branches in parallel\n    async function consumeBranch(branch) {\n      const reader = branch.getReader();\n      let count = 0;\n      let sum = 0;\n      while (true) {\n        const { value, done } = await reader.read();\n        if (done) break;\n        count++;\n        sum += value;\n      }\n      return { count, sum };\n    }\n\n    const [result1, result2] = await Promise.all([\n      consumeBranch(branch1),\n      consumeBranch(branch2),\n    ]);\n\n    strictEqual(result1.count, CHUNK_COUNT);\n    strictEqual(result2.count, CHUNK_COUNT);\n    strictEqual(result1.sum, result2.sum);\n    // Sum of 0 to 999 = 999 * 1000 / 2 = 499500\n    strictEqual(result1.sum, 499500);\n  },\n};\n\n// Test tee after partial read from original stream\n// Inspired by: Bun test/js/web/streams/streams.test.js\nexport const teeAfterPartialRead = {\n  async test() {\n    let counter = 0;\n    const rs = new ReadableStream({\n      pull(controller) {\n        counter++;\n        if (counter <= 10) {\n          controller.enqueue(counter);\n        } else {\n          controller.close();\n        }\n      },\n    });\n\n    // Read some values before tee\n    const originalReader = rs.getReader();\n    const firstValue = await originalReader.read();\n    strictEqual(firstValue.value, 1);\n\n    // Release lock and tee\n    originalReader.releaseLock();\n\n    const [branch1, branch2] = rs.tee();\n\n    // Both branches should start from where original left off\n    const reader1 = branch1.getReader();\n    const reader2 = branch2.getReader();\n\n    const value1 = await reader1.read();\n    const value2 = await reader2.read();\n\n    // Both should get value 2 (first value after the pre-tee read)\n    strictEqual(value1.value, 2);\n    strictEqual(value2.value, 2);\n\n    reader1.releaseLock();\n    reader2.releaseLock();\n  },\n};\n\n// Test that cancel reason is passed through tee\n// Inspired by: Deno tests/unit/streams_test.ts (cancel reason tests)\nexport const teeCancelReason = {\n  async test() {\n    let receivedReason = null;\n    let cancelCalled = false;\n\n    const rs = new ReadableStream({\n      pull(controller) {\n        controller.enqueue('data');\n      },\n      cancel(reason) {\n        cancelCalled = true;\n        receivedReason = reason;\n      },\n    });\n\n    const [branch1, branch2] = rs.tee();\n\n    const reader1 = branch1.getReader();\n    const reader2 = branch2.getReader();\n\n    // Read one value from each\n    await reader1.read();\n    await reader2.read();\n\n    // Cancel both with specific reasons\n    await reader1.cancel('Reason from branch 1');\n    await reader2.cancel('Reason from branch 2');\n\n    // The source cancel should be called when both branches are cancelled\n    ok(cancelCalled, 'Source cancel should be called');\n    // The reason format may vary by implementation - it could be:\n    // - An array of reasons\n    // - The first reason\n    // - The second reason\n    // - A composite reason\n    // Just verify cancel was called with some reason\n    ok(\n      receivedReason !== null && receivedReason !== undefined,\n      'Cancel reason should be provided'\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/streams-tee-edge-cases-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"streams-tee-edge-cases-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"streams-tee-edge-cases-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"streams_enable_constructors\", \"transformstream_enable_standard_constructor\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/streams-test.js",
    "content": "import { strictEqual, ok, deepStrictEqual, rejects, throws } from 'node:assert';\n\nconst enc = new TextEncoder();\n\nexport const rs = {\n  async test(ctrl, env) {\n    const resp = await env.subrequest.fetch('http://example.org', {\n      method: 'POST',\n      body: new ReadableStream({\n        expectedLength: 10,\n        start(c) {\n          c.enqueue(enc.encode('hellohello'));\n          c.close();\n        },\n      }),\n    });\n    for await (const _ of resp.body) {\n    }\n  },\n};\n\nexport const ts = {\n  async test(ctrl, env) {\n    const { readable, writable } = new TransformStream({\n      expectedLength: 10,\n    });\n    const writer = writable.getWriter();\n    writer.write(enc.encode('hellohello'));\n    writer.close();\n    const resp = await env.subrequest.fetch('http://example.org', {\n      method: 'POST',\n      body: readable,\n    });\n    for await (const _ of resp.body) {\n    }\n  },\n};\n\n// Regression test for https://github.com/cloudflare/workerd/issues/5113\nexport const rsRequest = {\n  async test(ctrl, env) {\n    const resp = await env.subrequest.fetch(\n      new Request('http://example.org', {\n        method: 'POST',\n        body: new ReadableStream({\n          expectedLength: 10,\n          start(c) {\n            c.enqueue(enc.encode('hellohello'));\n            c.close();\n          },\n        }),\n      })\n    );\n    for await (const _ of resp.body) {\n    }\n  },\n};\n\n// Regression test for https://github.com/cloudflare/workerd/issues/5113\nexport const tsRequest = {\n  async test(ctrl, env) {\n    const { readable, writable } = new TransformStream({\n      expectedLength: 10,\n    });\n    const writer = writable.getWriter();\n    writer.write(enc.encode('hellohello'));\n    writer.close();\n    const resp = await env.subrequest.fetch(\n      new Request('http://example.org', {\n        method: 'POST',\n        body: readable,\n      })\n    );\n    for await (const _ of resp.body) {\n    }\n  },\n};\n\nexport const byobMin = {\n  async test() {\n    let controller;\n    const rs = new ReadableStream({\n      type: 'bytes',\n      start(c) {\n        controller = c;\n      },\n    });\n\n    async function handleRead(readable) {\n      const reader = rs.getReader({ mode: 'byob' });\n      const result = await reader.read(new Uint8Array(10), { min: 10 });\n      strictEqual(result.done, false);\n      strictEqual(result.value.byteLength, 10);\n    }\n\n    async function handlePush(controller) {\n      for (let n = 0; n < 10; n++) {\n        controller.enqueue(new Uint8Array(1));\n        await scheduler.wait(10);\n      }\n    }\n\n    const results = await Promise.allSettled([\n      handleRead(rs),\n      handlePush(controller),\n    ]);\n\n    strictEqual(results[0].status, 'fulfilled');\n    strictEqual(results[1].status, 'fulfilled');\n  },\n};\n\nexport const cancelReadsOnReleaseLock = {\n  async test() {\n    const rs = new ReadableStream();\n    const reader = rs.getReader();\n    const read = reader.read();\n\n    const result = await Promise.allSettled([read, reader.releaseLock()]);\n    strictEqual(result[0].status, 'rejected');\n    strictEqual(\n      result[0].reason.message,\n      'This ReadableStream reader has been released.'\n    );\n    strictEqual(result[1].status, 'fulfilled');\n\n    // Make sure we can still get another reader\n    const reader2 = rs.getReader();\n  },\n};\n\nexport const cancelWriteOnReleaseLock = {\n  async test() {\n    const ws = new WritableStream({\n      write() {\n        return new Promise(() => {});\n      },\n    });\n    const writer = ws.getWriter();\n    // This first write is just to start the write queue so that the\n    // next write becomes pending in the queue. This first write will\n    // never be fulfilled since it is in-progress but the queue will\n    // be rejected.\n    writer.write('ignored');\n    const results = await Promise.allSettled([\n      writer.write('hello'),\n      writer.releaseLock(),\n    ]);\n    strictEqual(results[0].status, 'rejected');\n    strictEqual(\n      results[0].reason.message,\n      'This WritableStream writer has been released.'\n    );\n    strictEqual(results[1].status, 'fulfilled');\n\n    // Make sure we can still get another writer\n    const writer2 = ws.getWriter();\n  },\n};\n\nexport const readAllTextRequestSmall = {\n  async test() {\n    const rs = new ReadableStream({\n      pull(c) {\n        c.enqueue(enc.encode('hello '));\n        c.enqueue(enc.encode('world!'));\n        c.close();\n      },\n    });\n    const request = new Request('http://example.org', {\n      method: 'POST',\n      body: rs,\n    });\n    const text = await request.text();\n    strictEqual(text, 'hello world!');\n  },\n};\n\nexport const readAllTextResponseSmall = {\n  async test() {\n    const rs = new ReadableStream({\n      pull(c) {\n        c.enqueue(enc.encode('hello '));\n        c.enqueue(enc.encode('world!'));\n        c.close();\n      },\n    });\n    const response = new Response(rs);\n    const text = await response.text();\n    strictEqual(text, 'hello world!');\n  },\n};\n\nexport const readAllTextRequestBig = {\n  async test() {\n    const chunks = [\n      'a'.repeat(4097),\n      'b'.repeat(4097 * 2),\n      'c'.repeat(4097 * 4),\n    ];\n    let check = '';\n    const enc = new TextEncoder();\n\n    const rs = new ReadableStream({\n      pull(c) {\n        if (chunks.length === 0) {\n          c.close();\n          return;\n        }\n        const chunk = chunks.shift();\n        check += chunk;\n        c.enqueue(enc.encode(chunk));\n      },\n    });\n    const request = new Request('http://example.org', {\n      method: 'POST',\n      body: rs,\n    });\n    const text = await request.text();\n    strictEqual(text.length, check.length);\n    strictEqual(text, check);\n  },\n};\n\nexport const readAllTextResponseBig = {\n  async test() {\n    const chunks = [\n      'a'.repeat(4097),\n      'b'.repeat(4097 * 2),\n      'c'.repeat(4097 * 4),\n    ];\n    let check = '';\n    const enc = new TextEncoder();\n\n    const rs = new ReadableStream({\n      async pull(c) {\n        await scheduler.wait(10);\n        if (chunks.length === 0) {\n          c.close();\n          return;\n        }\n        const chunk = chunks.shift();\n        check += chunk;\n        c.enqueue(enc.encode(chunk));\n      },\n    });\n    const response = new Response(rs);\n    const promise = response.text();\n    const text = await promise;\n    strictEqual(text.length, check.length);\n    strictEqual(text, check);\n  },\n};\n\nexport const readAllTextFailedPull = {\n  async test() {\n    const rs = new ReadableStream({\n      async pull(c) {\n        await scheduler.wait(10);\n        throw new Error('boom');\n      },\n    });\n    const response = new Response(rs);\n    await rejects(response.text(), { message: 'boom' });\n  },\n};\n\nexport const readAllTextFailedStart = {\n  async test() {\n    const rs = new ReadableStream({\n      async start(c) {\n        await scheduler.wait(10);\n        throw new Error('boom');\n      },\n    });\n    const response = new Response(rs);\n    await rejects(response.text(), { message: 'boom' });\n  },\n};\n\nexport const readAllTextFailed = {\n  async test() {\n    const rs = new ReadableStream({\n      async start(c) {\n        await scheduler.wait(10);\n        c.error(new Error('boom'));\n      },\n    });\n    const response = new Response(rs);\n    ok(!rs.locked);\n    const promise = response.text();\n    ok(rs.locked);\n    await rejects(promise, { message: 'boom' });\n  },\n};\n\nexport const tsCancel = {\n  async test() {\n    // Verify that a TransformStream's cancel function is called when the\n    // readable is canceled or the writable is aborted. Verify also that\n    // errors thrown by the cancel function are propagated.\n    {\n      let cancelCalled = false;\n      const { readable } = new TransformStream({\n        async cancel(reason) {\n          strictEqual(reason, 'boom');\n          await scheduler.wait(10);\n          cancelCalled = true;\n        },\n      });\n      ok(!cancelCalled);\n      await readable.cancel('boom');\n      ok(cancelCalled);\n    }\n\n    {\n      let cancelCalled = false;\n      const { writable } = new TransformStream({\n        async cancel(reason) {\n          strictEqual(reason, 'boom');\n          await scheduler.wait(10);\n          cancelCalled = true;\n        },\n      });\n      ok(!cancelCalled);\n      await writable.abort('boom');\n      ok(cancelCalled);\n    }\n\n    {\n      const { writable } = new TransformStream({\n        async cancel(reason) {\n          throw new Error('boomy');\n        },\n      });\n      await rejects(writable.abort('boom'), { message: 'boomy' });\n    }\n  },\n};\n\nexport const writableStreamGcTraceFinishes = {\n  test() {\n    // TODO(soon): We really need better testing for GC visitation.\n    const ws = new WritableStream();\n    gc();\n  },\n};\n\nexport const readableStreamFromAsyncGenerator = {\n  async test() {\n    async function* gen() {\n      await scheduler.wait(10);\n      yield 'hello';\n      await scheduler.wait(10);\n      yield 'world';\n    }\n    const rs = ReadableStream.from(gen());\n    const chunks = [];\n    for await (const chunk of rs) {\n      chunks.push(chunk);\n    }\n    deepStrictEqual(chunks, ['hello', 'world']);\n  },\n};\n\nexport const readableStreamFromSyncGenerator = {\n  async test() {\n    const rs = ReadableStream.from(['hello', 'world']);\n    const chunks = [];\n    for await (const chunk of rs) {\n      chunks.push(chunk);\n    }\n    deepStrictEqual(chunks, ['hello', 'world']);\n  },\n};\n\nexport const readableStreamFromSyncGenerator2 = {\n  async test() {\n    function* gen() {\n      yield 'hello';\n      yield 'world';\n    }\n    const rs = ReadableStream.from(gen());\n    const chunks = [];\n    for await (const chunk of rs) {\n      chunks.push(chunk);\n    }\n    deepStrictEqual(chunks, ['hello', 'world']);\n  },\n};\n\nexport const readableStreamFromAsyncCanceled = {\n  async test() {\n    async function* gen() {\n      let count = 0;\n      try {\n        count++;\n        yield 'hello';\n        count++;\n        yield 'world';\n      } finally {\n        strictEqual(count, 1);\n      }\n    }\n    const rs = ReadableStream.from(gen());\n    const chunks = [];\n    for await (const chunk of rs) {\n      chunks.push(chunk);\n      return;\n    }\n    deepStrictEqual(chunks, ['hello']);\n  },\n};\n\nexport const readableStreamFromThrowingAsyncGen = {\n  async test() {\n    async function* gen() {\n      yield 'hello';\n      throw new Error('boom');\n    }\n    const rs = ReadableStream.from(gen());\n    const chunks = [];\n    async function consumeStream() {\n      for await (const chunk of rs) {\n        chunks.push(chunk);\n      }\n    }\n    await rejects(consumeStream, { message: 'boom' });\n    deepStrictEqual(chunks, ['hello']);\n  },\n};\n\nexport const readableStreamFromNoopAsyncGen = {\n  async test() {\n    async function* gen() {}\n    const rs = ReadableStream.from(gen());\n    const chunks = [];\n    for await (const chunk of rs) {\n      chunks.push(chunk);\n    }\n    deepStrictEqual(chunks, []);\n  },\n};\n\n// Tests for ReadableStream.from() cancel behavior per WPT spec\nexport const readableStreamFromCancelRejectsWhenReturnRejects = {\n  async test() {\n    const rejectError = new Error('return error');\n    const iterable = {\n      async next() {\n        return { value: undefined, done: true };\n      },\n      async return() {\n        throw rejectError;\n      },\n      [Symbol.asyncIterator]() {\n        return this;\n      },\n    };\n\n    const rs = ReadableStream.from(iterable);\n    const reader = rs.getReader();\n\n    await rejects(reader.cancel(), rejectError);\n  },\n};\n\nexport const readableStreamFromCancelRejectsWhenReturnThrows = {\n  async test() {\n    const throwError = new Error('return throws');\n    const iterable = {\n      async next() {\n        return { value: undefined, done: true };\n      },\n      return() {\n        throw throwError;\n      },\n      [Symbol.asyncIterator]() {\n        return this;\n      },\n    };\n\n    const rs = ReadableStream.from(iterable);\n    const reader = rs.getReader();\n\n    await rejects(reader.cancel(), (err) => err === throwError);\n  },\n};\n\nexport const readableStreamFromCancelRejectsWhenReturnNotMethod = {\n  async test() {\n    const iterable = {\n      async next() {\n        return { value: undefined, done: true };\n      },\n      return: 42, // exists but not callable\n      [Symbol.asyncIterator]() {\n        return this;\n      },\n    };\n\n    const rs = ReadableStream.from(iterable);\n    const reader = rs.getReader();\n\n    await rejects(reader.cancel(), {\n      name: 'TypeError',\n      message: /return/,\n    });\n  },\n};\n\nexport const readableStreamFromCancelRejectsWhenReturnNonObject = {\n  async test() {\n    const iterable = {\n      async next() {\n        return { value: undefined, done: true };\n      },\n      async return() {\n        return 42; // fulfills with non-object\n      },\n      [Symbol.asyncIterator]() {\n        return this;\n      },\n    };\n\n    const rs = ReadableStream.from(iterable);\n    const reader = rs.getReader();\n\n    await rejects(reader.cancel(), {\n      name: 'TypeError',\n    });\n  },\n};\n\nexport const readableStreamFromCancelResolvesWhenReturnMissing = {\n  async test() {\n    const iterable = {\n      async next() {\n        return { value: undefined, done: true };\n      },\n      // no return method\n      [Symbol.asyncIterator]() {\n        return this;\n      },\n    };\n\n    const rs = ReadableStream.from(iterable);\n    const reader = rs.getReader();\n\n    // Should resolve without error when return() is missing\n    await Promise.all([reader.cancel(), reader.closed]);\n  },\n};\n\nexport const abortWriterAfterGc = {\n  async test() {\n    function getWriter() {\n      const { writable } = new IdentityTransformStream();\n      return writable.getWriter();\n    }\n\n    const writer = getWriter();\n    gc();\n    await writer.abort();\n  },\n};\n\nexport const finalReadOnInternalStreamReturnsBuffer = {\n  async test() {\n    const { readable, writable } = new IdentityTransformStream();\n    const writer = writable.getWriter();\n    await writer.close();\n\n    const reader = readable.getReader({ mode: 'byob' });\n    let result = await reader.read(new Uint8Array(10));\n    strictEqual(result.done, true);\n    ok(result.value instanceof Uint8Array);\n    strictEqual(result.value.byteLength, 0);\n    strictEqual(result.value.buffer.byteLength, 10);\n\n    result = await reader.read(new Uint8Array(10));\n    strictEqual(result.done, true);\n    ok(result.value instanceof Uint8Array);\n    strictEqual(result.value.byteLength, 0);\n    strictEqual(result.value.buffer.byteLength, 10);\n  },\n};\n\n// Test that canceling a stream rejects body consume function\nexport const cancelStreamRejectsBodyConsume = {\n  async test() {\n    const response = new Response('foo bar');\n    const stream = response.body;\n\n    stream.cancel(new Error('a good reason'));\n\n    await rejects(response.text(), TypeError);\n  },\n};\n\n// Test that canceling a reader resolves closed promise\nexport const cancelReaderResolvesClosedPromise = {\n  async test() {\n    const response = new Response('foo bar');\n    const stream = response.body;\n    const reader = stream.getReader();\n\n    reader.cancel();\n    const closed = await reader.closed;\n    strictEqual(typeof closed, 'undefined');\n    reader.releaseLock();\n\n    await rejects(response.text(), TypeError);\n  },\n};\n\n// Test that getReader with bad mode throws\nexport const getReaderBadModeThrows = {\n  test() {\n    const response = new Response('foo bar');\n    const stream = response.body;\n\n    throws(() => stream.getReader({ mode: 'nope' }), TypeError);\n  },\n};\n\n// Test that stream is locked after getReader() called\nexport const streamLockedAfterGetReader = {\n  test() {\n    const response = new Response('foo bar');\n    const stream = response.body;\n\n    const reader = stream.getReader();\n\n    ok(stream.locked);\n\n    throws(() => stream.getReader(), TypeError);\n\n    reader.releaseLock();\n    ok(!stream.locked);\n    reader.releaseLock(); // Second time should be a no-op\n  },\n};\n\n// Test BYOB reader constraints\nexport const byobReaderConstraints = {\n  async test() {\n    const response = new Response('foo bar');\n    const stream = response.body;\n    const reader = stream.getReader({ mode: 'byob' });\n    // Start a read - this will consume part of the stream\n    reader.read(new Uint8Array(32)).catch(() => {}); // Ignore the result\n\n    // We use rejects() with async wrapper instead of throws() because the error\n    // is thrown synchronously without streams_enable_constructors but returned as\n    // a rejected promise when that flag is enabled. The async wrapper handles both.\n\n    // Cannot BYOB with a zero-length buffer\n    await rejects(async () => reader.read(new Uint8Array(0)), TypeError);\n\n    // Cannot BYOB an ArrayBuffer, only an ArrayBufferView\n    await rejects(async () => reader.read(new ArrayBuffer(32)), TypeError);\n\n    // Cannot use BYOB reader as a non-BYOB reader\n    await rejects(async () => reader.read(), TypeError);\n  },\n};\n\n// Test cancel error type propagation\nexport const cancelErrorTypePropagation = {\n  async test() {\n    class ExampleError extends Error {\n      constructor() {\n        super('foo bar');\n        this.name = 'ExampleError';\n      }\n    }\n\n    const cancelErrorTests = [\n      {\n        cancelWith: new Error('test'),\n        expectError: 'Error: test',\n      },\n      {\n        cancelWith: 'test',\n        expectError: 'Error: test',\n      },\n      {\n        cancelWith: 'jsg.Error: test',\n        expectError: 'Error: jsg.Error: test',\n      },\n      {\n        cancelWith: new TypeError('Problems!'),\n        expectError: 'TypeError: Problems!',\n        errorType: TypeError,\n      },\n      {\n        cancelWith: new RangeError('Problems!'),\n        expectError: 'RangeError: Problems!',\n        errorType: RangeError,\n      },\n      {\n        cancelWith: new SyntaxError('The semicolons are bad'),\n        expectError: 'SyntaxError: The semicolons are bad',\n        errorType: SyntaxError,\n      },\n      {\n        cancelWith: new ReferenceError(\"Didn't find it\"),\n        expectError: \"ReferenceError: Didn't find it\",\n        errorType: ReferenceError,\n      },\n      {\n        cancelWith: undefined,\n        expectError: 'Error: Stream was cancelled.',\n      },\n      {\n        cancelWith: new ExampleError(),\n        expectError: 'Error: ExampleError: foo bar',\n        errorType: Error,\n      },\n    ];\n\n    for (const testCase of cancelErrorTests) {\n      const ts = new IdentityTransformStream();\n\n      const writer = ts.writable.getWriter();\n      const reader = ts.readable.getReader();\n      const writePromise = writer.write(new TextEncoder().encode('a'));\n      const writerActualClosed = writer.close();\n      await reader.cancel(testCase.cancelWith);\n\n      for (const promise of [writePromise, writerActualClosed]) {\n        await rejects(promise, (e) => {\n          strictEqual(String(e), testCase.expectError);\n          if (testCase.errorType) {\n            ok(e instanceof testCase.errorType);\n          }\n          return true;\n        });\n      }\n    }\n  },\n};\n\n// Test IdentityTransformStream write before read\nexport const identityTransformWriteBeforeRead = {\n  async test() {\n    const MAX_RW = 10;\n    const { readable, writable } = new IdentityTransformStream();\n    const writer = writable.getWriter();\n    const reader = readable.getReader();\n\n    const writePromises = [];\n    for (let i = 0; i < MAX_RW; i++) {\n      writePromises.push(writer.write(new Uint8Array([i])));\n    }\n\n    const chunks = [];\n    for (let i = 0; i < MAX_RW; i++) {\n      chunks.push(await reader.read());\n    }\n\n    await Promise.all(writePromises);\n\n    for (let i = 0; i < chunks.length; i++) {\n      deepStrictEqual([...chunks[i].value], [i]);\n      strictEqual(chunks[i].done, false);\n    }\n\n    const writeClosePromise = writer.close();\n    const chunk = await reader.read();\n    await writeClosePromise;\n\n    strictEqual(chunk.done, true);\n\n    await writer.closed;\n    await reader.closed;\n  },\n};\n\n// Test IdentityTransformStream read before write\nexport const identityTransformReadBeforeWrite = {\n  async test() {\n    const MAX_RW = 10;\n    const { readable, writable } = new IdentityTransformStream();\n    const writer = writable.getWriter();\n    const reader = readable.getReader();\n\n    // IdentityTransformStream only supports one pending read at a time,\n    // so we test read-before-write by starting each read before its write\n    const chunks = [];\n    for (let i = 0; i < MAX_RW; i++) {\n      const readPromise = reader.read();\n      await writer.write(new Uint8Array([i]));\n      chunks.push(await readPromise);\n    }\n\n    for (let i = 0; i < chunks.length; i++) {\n      deepStrictEqual([...chunks[i].value], [i]);\n      strictEqual(chunks[i].done, false);\n    }\n\n    const readClosePromise = reader.read();\n    await writer.close();\n    const chunk = await readClosePromise;\n\n    strictEqual(chunk.done, true);\n\n    await writer.closed;\n    await reader.closed;\n  },\n};\n\n// Test closed promise under lock release\nexport const closedPromiseUnderLockRelease = {\n  async test() {\n    const { readable, writable } = new IdentityTransformStream();\n\n    const writer = writable.getWriter();\n    const reader = readable.getReader();\n\n    const writerClosed = writer.closed;\n    const readerClosed = reader.closed;\n\n    writer.releaseLock();\n\n    await rejects(writerClosed, TypeError);\n\n    reader.releaseLock();\n\n    await rejects(readerClosed, TypeError);\n  },\n};\n\n// Test closed promise under writer abort\nexport const closedPromiseUnderWriterAbort = {\n  async test() {\n    const { readable, writable } = new IdentityTransformStream();\n\n    const writer = writable.getWriter();\n    const reader = readable.getReader();\n\n    const writerClosed = writer.closed;\n    const readerClosed = reader.closed;\n\n    const readPromise = reader.read();\n    await writer.abort(new Error('Some arbitrary, capricious reason.'));\n\n    await rejects(writerClosed, Error);\n    await rejects(readPromise, Error);\n    await rejects(readerClosed, Error);\n  },\n};\n\n// Test FixedLengthStream constructor preconditions\nexport const fixedLengthStreamPreconditions = {\n  test() {\n    // Can construct with negative zero\n    new FixedLengthStream(-0.0);\n\n    // Can construct with fraction (coerced to 0)\n    new FixedLengthStream(0.00001);\n\n    // Can construct with MAX_SAFE_INTEGER\n    new FixedLengthStream(Number.MAX_SAFE_INTEGER);\n\n    // Cannot construct with unsafe integer\n    throws(() => new FixedLengthStream(Number.MAX_SAFE_INTEGER + 1), TypeError);\n\n    // Cannot construct with negative integer\n    throws(() => new FixedLengthStream(-1), TypeError);\n  },\n};\n\n// Test non-standard readAtLeast() extension with default reader (should throw)\nexport const readAtLeastDefaultReaderThrows = {\n  async test() {\n    const rs = new ReadableStream({\n      type: 'bytes',\n      pull(c) {\n        c.enqueue(enc.encode('hello'));\n        c.close();\n      },\n    });\n\n    const reader = rs.getReader();\n    throws(() => reader.readAtLeast(1), TypeError);\n    reader.releaseLock();\n\n    // Consume the stream to clean up\n    for await (const _ of rs) {\n    }\n  },\n};\n\n// Test non-standard readAtLeast() extension with BYOB reader\n// Note: The original ew-test expected value=undefined on done, which was the legacy\n// behavior of internal streams. With `internal_stream_byob_return_view` compat flag\n// (enabled since 2024-05-13), the spec-compliant behavior returns an empty view.\nexport const readAtLeastByobReader = {\n  async test(ctrl, env) {\n    // Use service binding to get chunked response\n    const response = await env.subrequest.fetch('http://test/chunked');\n    const reader = response.body.getReader({ mode: 'byob' });\n\n    // First readAtLeast: request min 4 bytes\n    // Server sends: 'foo' (3) + 'bar' (3) = 6 bytes, first chunk 'foo' only 3 bytes\n    // so readAtLeast(4) should wait for more data\n    let result = await reader.readAtLeast(4, new Uint8Array(20));\n    let value = new TextDecoder().decode(result.value);\n    strictEqual(result.done, false);\n    strictEqual(value.length, 6);\n    strictEqual(value, 'foobar');\n\n    // Regular read\n    result = await reader.read(new Uint8Array(20));\n    value = new TextDecoder().decode(result.value);\n    strictEqual(value.length, 1);\n    strictEqual(value, 'b');\n    strictEqual(result.done, false);\n\n    // Second readAtLeast: request min 4 bytes, only 'az' (2 bytes) remain\n    // Server sends: 'a' (1) + 'z' (1) = 2 bytes, then closes\n    result = await reader.readAtLeast(4, new Uint8Array(20));\n    value = new TextDecoder().decode(result.value);\n    strictEqual(value.length, 2);\n    strictEqual(value, 'az');\n    strictEqual(result.done, false);\n\n    // Final read should be done - spec requires empty view, not undefined\n    result = await reader.readAtLeast(4, new Uint8Array(20));\n    strictEqual(result.done, true);\n    ok(result.value instanceof Uint8Array);\n    strictEqual(result.value.byteLength, 0);\n  },\n};\n\nexport const writeSubarray = {\n  async test() {\n    const { readable, writable } = new IdentityTransformStream();\n\n    const u8 = new Uint8Array([1, 2, 3, 4]);\n\n    const writer = writable.getWriter();\n    const reader = readable.getReader();\n\n    writer.write(u8.subarray(1, 3));\n    writer.close();\n\n    const { value } = await reader.read();\n\n    strictEqual(value.length, 2);\n    strictEqual(value[0], u8[1]);\n    strictEqual(value[1], u8[2]);\n  },\n};\n\nexport const writableStreamWriterConstructor = {\n  test() {\n    const t = new IdentityTransformStream();\n    new WritableStreamDefaultWriter(t.writable);\n  },\n};\n\nexport const readableStreamDefaultReaderConstructor = {\n  test() {\n    const t = new IdentityTransformStream();\n    new ReadableStreamDefaultReader(t.readable);\n  },\n};\n\nexport const readableStreamByobReaderConstructor = {\n  test() {\n    const t = new IdentityTransformStream();\n    new ReadableStreamBYOBReader(t.readable);\n  },\n};\n\nexport const byobReaderDetachesBuffer = {\n  async test() {\n    const ts = new IdentityTransformStream();\n    const view = new Uint8Array(10);\n    const buffer = view.buffer;\n    const writer = ts.writable.getWriter();\n    const reader = ts.readable.getReader({ mode: 'byob' });\n    strictEqual(view.byteLength, 10);\n    strictEqual(view.buffer.byteLength, 10);\n    const res = await Promise.all([\n      writer.write(new Uint8Array(10)),\n      reader.read(view),\n    ]);\n\n    strictEqual(view.byteLength, 0);\n    strictEqual(view.buffer.byteLength, 0);\n\n    ok(res[1].value.buffer instanceof ArrayBuffer);\n    ok(res[1].value.buffer !== buffer);\n\n    await rejects(async () => reader.read(view), TypeError);\n\n    // Using a non-detachable ArrayBuffer must fail with a rejection\n    const memory = new WebAssembly.Memory({\n      initial: 10,\n      maximum: 10,\n      shared: true,\n    });\n    await rejects(\n      async () => reader.read(new Uint8Array(memory.buffer)),\n      TypeError\n    );\n\n    await rejects(\n      async () => reader.read(new Uint8Array(new SharedArrayBuffer(10))),\n      TypeError\n    );\n  },\n};\n\nexport const captureSyncThrows = {\n  async test() {\n    const { readable } = new IdentityTransformStream();\n    const reader = readable.getReader({ mode: 'byob' });\n    // Without the captureThrowsAsRejections flag enabled, this would throw synchronously.\n    // With the flag enabled, however, the synchronous throw is changed into a promise rejection.\n    await rejects(async () => reader.read(new ArrayBuffer(10)), TypeError);\n  },\n};\n\nexport const teeFixedLengthStreamNoHang = {\n  async test() {\n    const ts = new FixedLengthStream(11);\n    const writer = ts.writable.getWriter();\n    writer.write(new TextEncoder().encode('foo bar baz'));\n    writer.close();\n    const [left, right] = ts.readable.tee();\n    const response = new Response(left);\n    strictEqual(await response.text(), 'foo bar baz');\n  },\n};\n\nexport const transformStreamReadAllBytes = {\n  async test() {\n    const { readable, writable } = new IdentityTransformStream();\n    const response = new Response(readable);\n    const writer = writable.getWriter();\n\n    const N = 8;\n    const M = 5000;\n\n    const writePromise = (async () => {\n      for (let i = 0; i < N; i++) {\n        const chunk = new Uint8Array(M);\n        chunk.fill(i + 1);\n        await writer.write(chunk);\n      }\n      await writer.close();\n    })();\n\n    const body = new Uint8Array(await response.arrayBuffer());\n    strictEqual(body.byteLength, N * M);\n    for (let i = 0; i < body.length; i++) {\n      strictEqual(body[i], Math.floor(i / M) + 1);\n    }\n    await writePromise;\n  },\n};\n\nexport const transformStreamReadAllText = {\n  async test() {\n    const { readable, writable } = new IdentityTransformStream();\n    const response = new Response(readable);\n    const writer = writable.getWriter();\n\n    const lowerCaseA = 97;\n    const N = 8;\n    const M = 5000;\n\n    const writePromise = (async () => {\n      for (let i = 0; i < N; i++) {\n        const chunk = new Uint8Array(M);\n        chunk.fill(i + lowerCaseA);\n        await writer.write(chunk);\n      }\n      await writer.close();\n    })();\n\n    const body = await response.text();\n    strictEqual(body.length, N * M);\n    for (let i = 0; i < N; i++) {\n      const expected = String.fromCharCode(i + lowerCaseA).repeat(M);\n      strictEqual(body.slice(i * M, i * M + M), expected);\n    }\n    await writePromise;\n  },\n};\n\nexport const concurrentReadsRejected = {\n  async test() {\n    const { readable } = new IdentityTransformStream();\n    const reader = readable.getReader();\n    const p0 = reader.read();\n    await rejects(reader.read(), TypeError);\n  },\n};\n\nexport default {\n  async fetch(request, env) {\n    const url = new URL(request.url);\n\n    // Endpoint for chunked data for readAtLeast tests\n    if (url.pathname === '/chunked') {\n      const rs = new ReadableStream({\n        type: 'bytes',\n        async pull(controller) {\n          // Simulate chunked input: foo, bar, b, a, z\n          const chunks = [\n            enc.encode('foo'),\n            enc.encode('bar'),\n            enc.encode('b'),\n            enc.encode('a'),\n            enc.encode('z'),\n          ];\n          for (const chunk of chunks) {\n            controller.enqueue(chunk);\n            await scheduler.wait(1);\n          }\n          controller.close();\n        },\n      });\n      return new Response(rs);\n    }\n\n    strictEqual(request.headers.get('content-length'), '10');\n    return new Response(request.body);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/streams-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  v8Flags = [\"--expose-gc\"],\n  services = [\n    ( name = \"streams-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"streams-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"internal_stream_byob_return_view\", \"streams_enable_constructors\", \"transformstream_enable_standard_constructor\", \"streams_byob_reader_detaches_buffer\"],\n        bindings = [\n          (name = \"subrequest\", service = \"streams-test\")\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/stub-storage-test.js",
    "content": "import { WorkerEntrypoint, DurableObject } from 'cloudflare:workers';\nimport assert from 'node:assert';\n\n// This test verifies that we can store ServiceStubs (Fetchers) and DurableObjectClasses in DO KV\n// storage.\n\nexport class MyActor extends DurableObject {\n  async get(name) {\n    return await this.ctx.storage.get(name);\n  }\n  async put(name, value) {\n    await this.ctx.storage.put(name, value);\n  }\n\n  async useFacet(name) {\n    let facet = this.ctx.facets.get(name, async () => {\n      let cls = await this.ctx.storage.get(name);\n      return { class: cls, id: name };\n    });\n\n    await facet.setName('Bob');\n    return await facet.greet();\n  }\n}\n\nexport class Greeter extends WorkerEntrypoint {\n  async greet(name) {\n    return `${this.ctx.props.greeting}, ${name}!`;\n  }\n}\n\nexport class MyFacet extends DurableObject {\n  async setName(name) {\n    await this.ctx.storage.put('name', name);\n  }\n\n  async greet() {\n    let name = await this.ctx.storage.get('name');\n    return `${this.ctx.props.greeting}, ${name}?`;\n  }\n}\n\nexport let storeServiceBinding = {\n  async test(controller, env, ctx) {\n    let id = ctx.exports.MyActor.idFromName('foo');\n    let stub = ctx.exports.MyActor.get(id);\n\n    {\n      await stub.put('foo', ctx.exports.Greeter({ props: { greeting: 'Yo' } }));\n      let greeter = await stub.get('foo');\n      assert.strictEqual(await greeter.greet('Alice'), 'Yo, Alice!');\n    }\n\n    {\n      await stub.put(\n        'bar',\n        ctx.exports.MyFacet({ props: { greeting: 'Hiya' } })\n      );\n\n      assert.strictEqual(await stub.useFacet('bar'), 'Hiya, Bob?');\n    }\n  },\n};\n\n// Test that service stubs and actor classes can both be encoded into `props`, the stub with the\n// props can be sent over RPC, and it all still works.\nexport class UsePropsTest extends WorkerEntrypoint {\n  async run() {\n    let id = this.ctx.exports.MyActor.idFromName('bar');\n    let stub = this.ctx.exports.MyActor.get(id);\n\n    {\n      await stub.put('foo', this.ctx.props.Greeter);\n      let greeter = await stub.get('foo');\n      assert.strictEqual(await greeter.greet('Alice'), 'Yo, Alice!');\n    }\n\n    {\n      await stub.put('bar', this.ctx.props.MyFacet);\n\n      assert.strictEqual(await stub.useFacet('bar'), 'Hiya, Bob?');\n    }\n  }\n}\n\nexport let bindingsInProps = {\n  async test(controller, env, ctx) {\n    let stub = ctx.exports.UsePropsTest({\n      props: {\n        Greeter: ctx.exports.Greeter({ props: { greeting: 'Yo' } }),\n        MyFacet: ctx.exports.MyFacet({ props: { greeting: 'Hiya' } }),\n      },\n    });\n\n    // Send stub over RPC so the props get encoded into a channel token with nested channel\n    // tokens.\n    await ctx.exports.bindingsInProps.run(stub);\n  },\n\n  async run(stub) {\n    await stub.run();\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/stub-storage-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"stub-storage-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"stub-storage-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"allow_irrevocable_stub_storage\",\n          \"experimental\",\n          \"rpc\",\n          \"enable_ctx_exports\",\n          \"service_binding_extra_handlers\",\n          \"fetcher_no_get_put_delete\",\n        ],\n        durableObjectNamespaces = [\n          (className = \"MyActor\", uniqueKey = \"foo\", enableSql = true),\n        ],\n        durableObjectStorage = (inMemory = void),\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/sync-kv-instrumentation-test.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport * as assert from 'node:assert';\nimport {\n  invocationPromises,\n  spans,\n  testTailHandler,\n} from 'test:instrumentation-tail';\n\nexport default testTailHandler;\n\nexport const test = {\n  async test() {\n    await Promise.allSettled(invocationPromises);\n    // filter out non-KV spans including jsRpc calls\n    let received = Array.from(spans.values()).filter((span) =>\n      span.name.match('kv')\n    );\n\n    assert.deepStrictEqual(received, expectedSpans);\n  },\n};\n\nconst expectedSpans = [\n  {\n    name: 'durable_object_storage_kv_get',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'get',\n    'cloudflare.durable_object.kv.query.keys': 'foo',\n    'cloudflare.durable_object.kv.query.keys.count': 1n,\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_put',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'put',\n    'cloudflare.durable_object.kv.query.keys': 'foo',\n    'cloudflare.durable_object.kv.query.keys.count': 1n,\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_get',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'get',\n    'cloudflare.durable_object.kv.query.keys': 'foo',\n    'cloudflare.durable_object.kv.query.keys.count': 1n,\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_get',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'get',\n    'cloudflare.durable_object.kv.query.keys': 'bar',\n    'cloudflare.durable_object.kv.query.keys.count': 1n,\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_put',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'put',\n    'cloudflare.durable_object.kv.query.keys': 'bar',\n    'cloudflare.durable_object.kv.query.keys.count': 1n,\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_get',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'get',\n    'cloudflare.durable_object.kv.query.keys': 'bar',\n    'cloudflare.durable_object.kv.query.keys.count': 1n,\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_list',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'list',\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_list',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'list',\n    'cloudflare.durable_object.kv.query.reverse': true,\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_list',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'list',\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_list',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'list',\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_put',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'put',\n    'cloudflare.durable_object.kv.query.keys': 'baz',\n    'cloudflare.durable_object.kv.query.keys.count': 1n,\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_list',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'list',\n    'cloudflare.durable_object.kv.query.prefix': 'ba',\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_list',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'list',\n    'cloudflare.durable_object.kv.query.limit': 1n,\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_list',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'list',\n    'cloudflare.durable_object.kv.query.reverse': true,\n    'cloudflare.durable_object.kv.query.limit': 1n,\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_list',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'list',\n    'cloudflare.durable_object.kv.query.start': 'b',\n    'cloudflare.durable_object.kv.query.end': 'c',\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_list',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'list',\n    'cloudflare.durable_object.kv.query.start': 'b',\n    'cloudflare.durable_object.kv.query.end': 'baz',\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_list',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'list',\n    'cloudflare.durable_object.kv.query.start': 'bar',\n    'cloudflare.durable_object.kv.query.end': 'c',\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_list',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'list',\n    'cloudflare.durable_object.kv.query.startAfter': 'bar',\n    'cloudflare.durable_object.kv.query.end': 'c',\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_delete',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'delete',\n    'cloudflare.durable_object.kv.query.keys': 'qux',\n    'cloudflare.durable_object.kv.query.keys.count': 1n,\n    'cloudflare.durable_object.kv.response.deleted_count': 0n,\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_delete',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'delete',\n    'cloudflare.durable_object.kv.query.keys': 'bar',\n    'cloudflare.durable_object.kv.query.keys.count': 1n,\n    'cloudflare.durable_object.kv.response.deleted_count': 1n,\n    closed: true,\n  },\n  {\n    name: 'durable_object_storage_kv_list',\n    'db.system.name': 'cloudflare-durable-object-sql',\n    'db.operation.name': 'list',\n    closed: true,\n  },\n];\n"
  },
  {
    "path": "src/workerd/api/tests/sync-kv-test.js",
    "content": "import { DurableObject } from 'cloudflare:workers';\nimport assert from 'node:assert';\n\nexport class MyActor extends DurableObject {\n  run() {\n    let kv = this.ctx.storage.kv;\n\n    assert.strictEqual(kv.get('foo'), undefined);\n    kv.put('foo', 123);\n    assert.strictEqual(kv.get('foo'), 123);\n\n    assert.strictEqual(kv.get('bar'), undefined);\n    kv.put('bar', 'abc');\n    assert.strictEqual(kv.get('bar'), 'abc');\n\n    // Iterates in alphabetical order.\n    assert.deepEqual(\n      [...kv.list()],\n      [\n        ['bar', 'abc'],\n        ['foo', 123],\n      ]\n    );\n\n    assert.deepEqual(\n      [...kv.list({ reverse: true })],\n      [\n        ['foo', 123],\n        ['bar', 'abc'],\n      ]\n    );\n\n    // A new call to kv.list() invalidates any previous iterator.\n    {\n      let cursor1 = kv.list();\n      let cursor2 = kv.list();\n\n      assert.throws(() => [...cursor1], {\n        name: 'Error',\n        message:\n          'kv.list() iterator was invalidated because a new call to kv.list() was started. ' +\n          'Only one kv.list() iterator can exist at a time.',\n      });\n\n      assert.deepEqual(\n        [...cursor2],\n        [\n          ['bar', 'abc'],\n          ['foo', 123],\n        ]\n      );\n    }\n\n    kv.put('baz', false);\n\n    // Try a prefix.\n    assert.deepEqual(\n      [...kv.list({ prefix: 'ba' })],\n      [\n        ['bar', 'abc'],\n        ['baz', false],\n      ]\n    );\n\n    // Try a limit.\n    assert.deepEqual([...kv.list({ limit: 1 })], [['bar', 'abc']]);\n\n    // Try a reverse limit.\n    assert.deepEqual([...kv.list({ limit: 1, reverse: true })], [['foo', 123]]);\n\n    // Try a range.\n    assert.deepEqual(\n      [...kv.list({ start: 'b', end: 'c' })],\n      [\n        ['bar', 'abc'],\n        ['baz', false],\n      ]\n    );\n\n    // End is exclusive.\n    assert.deepEqual(\n      [...kv.list({ start: 'b', end: 'baz' })],\n      [['bar', 'abc']]\n    );\n\n    // Start is inclusive.\n    assert.deepEqual(\n      [...kv.list({ start: 'bar', end: 'c' })],\n      [\n        ['bar', 'abc'],\n        ['baz', false],\n      ]\n    );\n\n    // Except when it's not.\n    assert.deepEqual(\n      [...kv.list({ startAfter: 'bar', end: 'c' })],\n      [['baz', false]]\n    );\n\n    // Test delete.\n    assert.strictEqual(kv.delete('qux'), false);\n    assert.strictEqual(kv.delete('bar'), true);\n    assert.deepEqual(\n      [...kv.list()],\n      [\n        ['baz', false],\n        ['foo', 123],\n      ]\n    );\n  }\n}\n\nexport let testAutoRollBackOnCriticalError = {\n  async test(ctrl, env, ctx) {\n    await ctx.exports.MyActor.getByName('foo').run();\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/sync-kv-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n    (name = \"TEST_TMPDIR\", disk = (writable = true)),\n    (name = \"tail\", worker= .tailWorker)\n  ],\n  extensions = [ (\n    modules = [\n      ( name = \"test:instrumentation-tail\", esModule = embed \"instrumentation-tail-worker.js\" ),\n    ]\n  ) ]\n);\n\nconst mainWorker :Workerd.Worker = (\n\n  compatibilityFlags = [\"enable_ctx_exports\", \"nodejs_compat\", \"rpc\"],\n\n  streamingTails = [\"tail\"],\n\n  modules = [\n    (name = \"worker\", esModule = embed \"sync-kv-test.js\"),\n  ],\n\n  durableObjectNamespaces = [\n    ( className = \"MyActor\",\n      uniqueKey = \"c4ca24b6066547c9a3c847b6f6bf169a\",\n      enableSql = true ),\n  ],\n\n  durableObjectStorage = (localDisk = \"TEST_TMPDIR\"),\n);\n\nconst tailWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"sync-kv-instrumentation-test.js\")\n  ],\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/tail-worker-test-dummy.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nexport const test = {\n  // https://developers.cloudflare.com/workers/observability/logs/tail-workers/\n  tail(event, env, ctx) {\n    return (event) => {};\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/tail-worker-test-invalid.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport default {\n  // https://developers.cloudflare.com/workers/observability/logs/tail-workers/\n  tailStream(event, env, ctx) {\n    // Return an invalid handler\n    return 42;\n  },\n};\n\nexport const test = {\n  async test() {\n    await scheduler.wait(50);\n    // Tests for a bug where we tried to report an outcome event to a stream after setting up the\n    // stream handler with the onset event failed – with an invalid tail handler, we should not be\n    // failing the entire test but only return an error for the affected worker.\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/tail-worker-test-jsrpc.js",
    "content": "import assert from 'node:assert';\nimport { WorkerEntrypoint, DurableObject } from 'cloudflare:workers';\n\nexport class MyService extends WorkerEntrypoint {\n  // Define a property to test behavior of property accessors.\n  get nonFunctionProperty() {\n    // As a regression test for EW-9282, check that logging in a getter invoked via JSRPC is\n    // reflected in traces does not result in missing onset errors. This works through\n    // tail-worker-test, which calls into other tests to get traces from them and includes several\n    // tail worker implementations. See tail-worker-test.js for test output.\n    console.log('foo');\n    return { foo: 123 };\n  }\n\n  // Test method that returns a transient object with methods.\n  // This tests that setJsRpcInfo is only called once for the entrypoint method,\n  // not again for transient object methods.\n  getCounter() {\n    console.log('getCounter called');\n    return {\n      increment() {\n        console.log('increment called on transient');\n        return 1;\n      },\n      getValue() {\n        console.log('getValue called on transient');\n        return 42;\n      },\n    };\n  }\n\n  constructor(ctx, env) {\n    // As a regression test for EW-9282, check that logging in the constructor does not result in\n    // missing onset errors. This requires setting the onset event early on, before getting a\n    // handler to the entrypoint.\n    console.log('bar');\n    super(ctx, env);\n  }\n}\n\nexport class MyActor extends DurableObject {\n  async functionProperty() {}\n\n  constructor(ctx, env) {\n    // As a regression test for EW-9282, check that logging in the constructor does not result in\n    // missing onset errors – for DOs, we need to report the onset even earlier.\n    super(ctx, env);\n    this.ctx.blockConcurrencyWhile(async () => {\n      console.log('baz');\n    });\n  }\n}\n\nexport default {\n  async test(controller, env, ctx) {\n    let getByName = (name) => {\n      return env.MyService.getRpcMethodForTestOnly(name);\n    };\n\n    // Check what happens if we access something that's actually declared as a property on the\n    // class. The difference in error message proves to us that `env` and `ctx` weren't visible at\n    // all, which is what we want.\n    await assert.rejects(() => getByName('nonFunctionProperty')(), {\n      name: 'TypeError',\n      message: '\"nonFunctionProperty\" is not a function.',\n    });\n\n    let id = env.MyActor.idFromName('foo');\n    let stub = env.MyActor.get(id);\n    await stub.functionProperty();\n\n    // Test transient object methods - this should only report jsrpc.method for the initial\n    // getCounter() call, not for the subsequent increment() and getValue() calls on the\n    // returned transient object.\n    let counter = await env.MyService.getCounter();\n    let result1 = await counter.increment();\n    let result2 = await counter.getValue();\n    assert.strictEqual(result1, 1);\n    assert.strictEqual(result2, 42);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/tail-worker-test-receiver.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport * as assert from 'node:assert';\n\nlet resposeMap = new Map();\n\nexport default {\n  // https://developers.cloudflare.com/workers/observability/logs/tail-workers/\n  tailStream(event, env, ctx) {\n    // Onset event, must be singleton\n\n    // Set inner spanId to zero to have deterministic output.\n    event.event.spanId = '0000000000000000';\n\n    resposeMap.set(event.spanContext.traceId, JSON.stringify(event.event));\n    return (event) => {\n      let cons = resposeMap.get(event.spanContext.traceId);\n      resposeMap.set(\n        event.spanContext.traceId,\n        cons + JSON.stringify(event.event)\n      );\n    };\n  },\n};\n\nexport const test = {\n  async test() {\n    // HACK: The prior tests terminates once the scheduled() invocation has returned a response, but\n    // propagating the outcome of the invocation may take longer. Wait briefly so this can go ahead.\n    await scheduler.wait(50);\n\n    // The shared tail worker we configured only produces onset and outcome events, so every trace is identical here.\n    // Number of traces based on how often main tail worker is invoked from previous tests\n    let numTraces = 28;\n    let basicTrace =\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"scriptTags\":[],\"info\":{\"type\":\"trace\",\"traces\":[]}}{\"type\":\"return\"}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}';\n    assert.deepStrictEqual(\n      Array.from(resposeMap.values()),\n      Array.from({ length: numTraces }, () => basicTrace)\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/tail-worker-test.js",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport * as assert from 'node:assert';\n\nlet responseMap = new Map();\n\n// JSON.stringify() does not support BigInt by default - manually add that here.\nBigInt.prototype.toJSON = function () {\n  return this.toString();\n};\n\nexport default {\n  // https://developers.cloudflare.com/workers/observability/logs/tail-workers/\n  tailStream(event, env, ctx) {\n    // Onset event, must be singleton\n\n    // The Onset spanContext does not have a spanId defined (once span context propagation is\n    // implemented, it will be defined if a trigger context is available).\n    assert.strictEqual(event.spanContext.spanId, undefined);\n    let spanIdSet = new Set();\n\n    // For scheduled and alarm tests, override scheduledTime to make this test deterministic.\n    if (\n      event.event.info.type == 'scheduled' ||\n      event.event.info.type == 'alarm'\n    ) {\n      event.event.info.scheduledTime = '1970-01-01T00:00:00.000Z';\n    }\n\n    const topLevelSpanId = event.event.spanId;\n    // Set inner spanId to zero to have deterministic output, but save it first.\n    event.event.spanId = '0000000000000000';\n\n    responseMap.set(event.spanContext.traceId, JSON.stringify(event.event));\n    return (event) => {\n      // The spanContext always has a non-zero spanId available for events other than Onset.\n      assert.notStrictEqual(event.spanContext.spanId, undefined);\n      assert.notStrictEqual(event.spanContext.spanId, '0000000000000000');\n\n      // For outcome events (and all other tail events except for nested spans) the reported spanId\n      // must match the span opened by the Onset event.\n      if (event.event.type == 'outcome') {\n        assert.strictEqual(event.spanContext.spanId, topLevelSpanId);\n      } else if (event.event.type == 'spanOpen') {\n        // Every SpanOpen eveny must have another span or the Onset as its parent, as described by\n        // the SpanContext spanId.\n        assert.ok(\n          event.spanContext.spanId == topLevelSpanId ||\n            spanIdSet.has(event.spanContext.spanId)\n        );\n        // Add the newly opened span to the set of spanIds.\n        spanIdSet.add(event.event.spanId);\n      } else if (\n        event.event.type == 'attributes' ||\n        event.event.type == 'spanClose'\n      ) {\n        // Every SpanClose/attributes event must point to the spanId of a previously opened span\n        // OR the top-level span created by the onset event.\n        assert.ok(\n          spanIdSet.has(event.spanContext.spanId) ||\n            event.spanContext.spanId === topLevelSpanId\n        );\n      }\n\n      let cons = responseMap.get(event.spanContext.traceId);\n      responseMap.set(\n        event.spanContext.traceId,\n        cons + JSON.stringify(event.event)\n      );\n    };\n  },\n};\n\nexport const test = {\n  async test() {\n    // HACK: The prior tests terminates once the scheduled() invocation has returned a response, but\n    // propagating the outcome of the invocation may take longer. Wait briefly so this can go ahead.\n    await scheduler.wait(50);\n    // This test verifies that we're able to receive tail stream events for various handlers.\n\n    // Recorded streaming tail worker events, in insertion order.\n    let response = Array.from(responseMap.values());\n\n    let expected = [\n      // http-test.js: fetch and scheduled events get reported correctly.\n      // First event is emitted by the test() event, which allows us to get some coverage for span tracing.\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"scriptTags\":[],\"info\":{\"type\":\"custom\"}}{\"type\":\"spanOpen\",\"name\":\"fetch\",\"spanId\":\"0000000000000001\"}{\"type\":\"attributes\",\"info\":[{\"name\":\"network.protocol.name\",\"value\":\"http\"},{\"name\":\"network.protocol.version\",\"value\":\"HTTP/1.1\"},{\"name\":\"http.request.method\",\"value\":\"POST\"},{\"name\":\"url.full\",\"value\":\"http://placeholder/body-length\"},{\"name\":\"http.request.body.size\",\"value\":\"3\"},{\"name\":\"http.response.status_code\",\"value\":\"200\"},{\"name\":\"http.response.body.size\",\"value\":\"22\"}]}{\"type\":\"spanClose\",\"outcome\":\"ok\"}{\"type\":\"spanOpen\",\"name\":\"fetch\",\"spanId\":\"0000000000000002\"}{\"type\":\"attributes\",\"info\":[{\"name\":\"network.protocol.name\",\"value\":\"http\"},{\"name\":\"network.protocol.version\",\"value\":\"HTTP/1.1\"},{\"name\":\"http.request.method\",\"value\":\"POST\"},{\"name\":\"url.full\",\"value\":\"http://placeholder/body-length\"},{\"name\":\"http.response.status_code\",\"value\":\"200\"},{\"name\":\"http.response.body.size\",\"value\":\"31\"}]}{\"type\":\"spanClose\",\"outcome\":\"ok\"}{\"type\":\"spanOpen\",\"name\":\"scheduled\",\"spanId\":\"0000000000000003\"}{\"type\":\"spanClose\",\"outcome\":\"ok\"}{\"type\":\"spanOpen\",\"name\":\"scheduled\",\"spanId\":\"0000000000000004\"}{\"type\":\"spanClose\",\"outcome\":\"ok\"}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"scriptTags\":[],\"info\":{\"type\":\"fetch\",\"method\":\"POST\",\"url\":\"http://placeholder/body-length\",\"headers\":[]}}{\"type\":\"return\",\"info\":{\"type\":\"fetch\",\"statusCode\":200}}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"scriptTags\":[],\"info\":{\"type\":\"fetch\",\"method\":\"POST\",\"url\":\"http://placeholder/body-length\",\"headers\":[]}}{\"type\":\"return\",\"info\":{\"type\":\"fetch\",\"statusCode\":200}}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"scriptTags\":[],\"info\":{\"type\":\"scheduled\",\"scheduledTime\":\"1970-01-01T00:00:00.000Z\",\"cron\":\"\"}}{\"type\":\"return\"}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"scriptTags\":[],\"info\":{\"type\":\"scheduled\",\"scheduledTime\":\"1970-01-01T00:00:00.000Z\",\"cron\":\"* * * * 30\"}}{\"type\":\"return\"}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"entrypoint\":\"cacheMode\",\"scriptTags\":[],\"info\":{\"type\":\"custom\"}}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"scriptTags\":[],\"info\":{\"type\":\"fetch\",\"method\":\"GET\",\"url\":\"http://placeholder/not-found\",\"headers\":[]}}{\"type\":\"return\",\"info\":{\"type\":\"fetch\",\"statusCode\":404}}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"scriptTags\":[],\"info\":{\"type\":\"fetch\",\"method\":\"GET\",\"url\":\"http://placeholder/web-socket\",\"headers\":[{\"name\":\"upgrade\",\"value\":\"websocket\"}]}}{\"type\":\"return\"}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n\n      // Test that when the onset event would be larger than MAX_TRACE_BYTES, we still send the event but with variable size fields counted to the size being empty.\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"scriptTags\":[],\"info\":{\"type\":\"fetch\",\"method\":\"GET\",\"url\":\"\",\"headers\":[]}}{\"type\":\"return\",\"info\":{\"type\":\"fetch\",\"statusCode\":404}}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n\n      // queue-test.js: queue events\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"scriptTags\":[],\"info\":{\"type\":\"custom\"}}{\"type\":\"spanOpen\",\"name\":\"queue_send\",\"spanId\":\"0000000000000001\"}{\"type\":\"spanClose\",\"outcome\":\"ok\"}{\"type\":\"spanOpen\",\"name\":\"queue_send\",\"spanId\":\"0000000000000002\"}{\"type\":\"spanClose\",\"outcome\":\"ok\"}{\"type\":\"spanOpen\",\"name\":\"queue_send\",\"spanId\":\"0000000000000003\"}{\"type\":\"spanClose\",\"outcome\":\"ok\"}{\"type\":\"spanOpen\",\"name\":\"queue_send\",\"spanId\":\"0000000000000004\"}{\"type\":\"spanClose\",\"outcome\":\"ok\"}{\"type\":\"spanOpen\",\"name\":\"queue_send\",\"spanId\":\"0000000000000005\"}{\"type\":\"spanClose\",\"outcome\":\"ok\"}{\"type\":\"spanOpen\",\"name\":\"queue\",\"spanId\":\"0000000000000006\"}{\"type\":\"spanClose\",\"outcome\":\"ok\"}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"scriptTags\":[],\"info\":{\"type\":\"fetch\",\"method\":\"POST\",\"url\":\"https://fake-host/message\",\"headers\":[{\"name\":\"content-type\",\"value\":\"application/octet-stream\"},{\"name\":\"x-msg-delay-secs\",\"value\":\"2\"},{\"name\":\"x-msg-fmt\",\"value\":\"text\"}]}}{\"type\":\"return\",\"info\":{\"type\":\"fetch\",\"statusCode\":200}}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"scriptTags\":[],\"info\":{\"type\":\"fetch\",\"method\":\"POST\",\"url\":\"https://fake-host/message\",\"headers\":[{\"name\":\"content-type\",\"value\":\"application/octet-stream\"},{\"name\":\"x-msg-fmt\",\"value\":\"bytes\"}]}}{\"type\":\"return\",\"info\":{\"type\":\"fetch\",\"statusCode\":200}}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"scriptTags\":[],\"info\":{\"type\":\"fetch\",\"method\":\"POST\",\"url\":\"https://fake-host/message\",\"headers\":[{\"name\":\"content-type\",\"value\":\"application/octet-stream\"},{\"name\":\"x-msg-fmt\",\"value\":\"json\"}]}}{\"type\":\"return\",\"info\":{\"type\":\"fetch\",\"statusCode\":200}}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"scriptTags\":[],\"info\":{\"type\":\"fetch\",\"method\":\"POST\",\"url\":\"https://fake-host/message\",\"headers\":[{\"name\":\"content-type\",\"value\":\"application/octet-stream\"},{\"name\":\"x-msg-fmt\",\"value\":\"v8\"}]}}{\"type\":\"return\",\"info\":{\"type\":\"fetch\",\"statusCode\":200}}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"scriptTags\":[],\"info\":{\"type\":\"fetch\",\"method\":\"POST\",\"url\":\"https://fake-host/batch\",\"headers\":[{\"name\":\"cf-queue-batch-bytes\",\"value\":\"31\"},{\"name\":\"cf-queue-batch-count\",\"value\":\"4\"},{\"name\":\"cf-queue-largest-msg\",\"value\":\"13\"},{\"name\":\"content-type\",\"value\":\"application/json\"},{\"name\":\"x-msg-delay-secs\",\"value\":\"2\"}]}}{\"type\":\"return\",\"info\":{\"type\":\"fetch\",\"statusCode\":200}}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"scriptTags\":[],\"info\":{\"type\":\"queue\",\"queueName\":\"test-queue\",\"batchSize\":5}}{\"type\":\"return\"}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n\n      // actor-alarms-test.js: alarm events\n      '{\"type\":\"onset\",\"executionModel\":\"durableObject\",\"spanId\":\"0000000000000000\",\"entrypoint\":\"DurableObjectExample\",\"scriptTags\":[],\"info\":{\"type\":\"fetch\",\"method\":\"GET\",\"url\":\"http://foo/test\",\"headers\":[]}}{\"type\":\"spanOpen\",\"name\":\"durable_object_storage_setAlarm\",\"spanId\":\"0000000000000001\"}{\"type\":\"spanClose\",\"outcome\":\"ok\"}{\"type\":\"spanOpen\",\"name\":\"durable_object_storage_getAlarm\",\"spanId\":\"0000000000000002\"}{\"type\":\"spanClose\",\"outcome\":\"ok\"}{\"type\":\"return\",\"info\":{\"type\":\"fetch\",\"statusCode\":200}}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"durableObject\",\"spanId\":\"0000000000000000\",\"entrypoint\":\"DurableObjectExample\",\"scriptTags\":[],\"info\":{\"type\":\"alarm\",\"scheduledTime\":\"1970-01-01T00:00:00.000Z\"}}{\"type\":\"spanOpen\",\"name\":\"durable_object_storage_getAlarm\",\"spanId\":\"0000000000000001\"}{\"type\":\"spanClose\",\"outcome\":\"ok\"}{\"type\":\"spanOpen\",\"name\":\"durable_object_storage_getAlarm\",\"spanId\":\"0000000000000002\"}{\"type\":\"spanClose\",\"outcome\":\"ok\"}{\"type\":\"return\"}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n\n      // buffered tail worker, triggered via alarm test. It would appear that these being recorded\n      // after the onsets above is not guaranteed, but since the streaming tail worker is invoked\n      // when the main invocation starts whereas the buffered tail worker is only invoked when it ends\n      // this should be fine in practice.\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"scriptTags\":[],\"info\":{\"type\":\"trace\",\"traces\":[\"\"]}}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"scriptTags\":[],\"info\":{\"type\":\"trace\",\"traces\":[\"\"]}}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n\n      // tests/websocket-hibernation.js: hibernatableWebSocket events\n      '{\"type\":\"onset\",\"executionModel\":\"durableObject\",\"spanId\":\"0000000000000000\",\"entrypoint\":\"DurableObjectExample\",\"scriptTags\":[],\"info\":{\"type\":\"fetch\",\"method\":\"GET\",\"url\":\"http://example.com/\",\"headers\":[{\"name\":\"upgrade\",\"value\":\"websocket\"}]}}{\"type\":\"return\"}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"durableObject\",\"spanId\":\"0000000000000000\",\"entrypoint\":\"DurableObjectExample\",\"scriptTags\":[],\"info\":{\"type\":\"fetch\",\"method\":\"GET\",\"url\":\"http://example.com/hibernation\",\"headers\":[{\"name\":\"upgrade\",\"value\":\"websocket\"}]}}{\"type\":\"return\"}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"durableObject\",\"spanId\":\"0000000000000000\",\"entrypoint\":\"DurableObjectExample\",\"scriptTags\":[],\"info\":{\"type\":\"hibernatableWebSocket\",\"info\":{\"type\":\"message\"}}}{\"type\":\"return\"}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"durableObject\",\"spanId\":\"0000000000000000\",\"entrypoint\":\"DurableObjectExample\",\"scriptTags\":[],\"info\":{\"type\":\"hibernatableWebSocket\",\"info\":{\"type\":\"close\",\"code\":1000,\"wasClean\":true}}}{\"type\":\"return\"}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n\n      // tail-worker-test-jsrpc: Regression test for EW-9282 (missing onset event with\n      // JsRpcSessionCustomEvent). This is derived from tests/js-rpc-test.js.\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"scriptTags\":[],\"info\":{\"type\":\"custom\"}}{\"type\":\"spanOpen\",\"name\":\"durable_object_subrequest\",\"spanId\":\"0000000000000002\"}{\"type\":\"attributes\",\"info\":[{\"name\":\"objectId\",\"value\":\"af6dd8b6678e07bac992dae1bbbb3f385af19ebae7e5ea8c66d6341b246d3328\"}]}{\"type\":\"spanClose\",\"outcome\":\"ok\"}{\"type\":\"spanOpen\",\"name\":\"jsRpcSession\",\"spanId\":\"0000000000000003\"}{\"type\":\"spanClose\",\"outcome\":\"ok\"}{\"type\":\"spanOpen\",\"name\":\"jsRpcSession\",\"spanId\":\"0000000000000001\"}{\"type\":\"spanClose\",\"outcome\":\"ok\"}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"entrypoint\":\"MyService\",\"scriptTags\":[],\"info\":{\"type\":\"jsrpc\"}}{\"type\":\"attributes\",\"info\":[{\"name\":\"jsrpc.method\",\"value\":\"nonFunctionProperty\"}]}{\"type\":\"log\",\"level\":\"log\",\"message\":[\"bar\"]}{\"type\":\"log\",\"level\":\"log\",\"message\":[\"foo\"]}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      '{\"type\":\"onset\",\"executionModel\":\"durableObject\",\"spanId\":\"0000000000000000\",\"entrypoint\":\"MyActor\",\"scriptTags\":[],\"info\":{\"type\":\"jsrpc\"}}{\"type\":\"log\",\"level\":\"log\",\"message\":[\"baz\"]}{\"type\":\"attributes\",\"info\":[{\"name\":\"jsrpc.method\",\"value\":\"functionProperty\"}]}{\"type\":\"return\"}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n      // Test for transient objects - getCounter returns an object with methods\n      // All transient calls happen in a single trace event, with only the entrypoint method reported\n      '{\"type\":\"onset\",\"executionModel\":\"stateless\",\"spanId\":\"0000000000000000\",\"entrypoint\":\"MyService\",\"scriptTags\":[],\"info\":{\"type\":\"jsrpc\"}}{\"type\":\"attributes\",\"info\":[{\"name\":\"jsrpc.method\",\"value\":\"getCounter\"}]}{\"type\":\"log\",\"level\":\"log\",\"message\":[\"bar\"]}{\"type\":\"log\",\"level\":\"log\",\"message\":[\"getCounter called\"]}{\"type\":\"return\"}{\"type\":\"log\",\"level\":\"log\",\"message\":[\"increment called on transient\"]}{\"type\":\"log\",\"level\":\"log\",\"message\":[\"getValue called on transient\"]}{\"type\":\"outcome\",\"outcome\":\"ok\",\"cpuTime\":0,\"wallTime\":0}',\n    ];\n\n    assert.deepStrictEqual(response, expected);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/tail-worker-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    # Embedded tests we get traces from\n    ( name = \"http-test\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"http-test.js\" )\n        ],\n        bindings = [\n          ( name = \"SERVICE\", service = \"http-test\" ),\n          ( name = \"CACHE_ENABLED\", json = \"false\" ),\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"service_binding_extra_handlers\", \"cache_option_disabled\", \"queues_json_messages\", \"url_standard\", \"workers_api_getters_setters_on_prototype\", \"fetch_legacy_url\", \"web_socket_auto_reply_to_close\"],\n        streamingTails = [\"log\", \"log-invalid\"],\n      ),\n    ),\n    ( name = \"queue-test\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"queue-test.js\" )\n        ],\n        bindings = [\n          ( name = \"QUEUE\", queue = \"queue-test\" ),\n          ( name = \"SERVICE\", service = \"queue-test\" ),\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"service_binding_extra_handlers\", \"queues_json_messages\", \"rpc\", \"capture_async_api_throws\", \"disable_fast_jsg_struct\"],\n        streamingTails = [\"log\"],\n      )\n    ),\n    (name = \"alarms\", worker = .alarmsWorker),\n    (name = \"hiber\", worker = .hiberWorker),\n    (name = \"js-rpc-test\", worker = .jsRpcWorker),\n    (name = \"TEST_TMPDIR\", disk = (writable = true)),\n    # Dummy buffered tail worker (gets traces from alarms worker and produces trace for main tracer)\n    (name = \"buffered\", worker = .logBuffered, ),\n    # Receives trace events from STW, used to check that STW produces trace events properly\n    (name = \"receiver\", worker = .logReceiver, ),\n    # Unified tail worker with tests\n    # tests are executed in order, so logWorker needs to be last to have all traces available\n    (name = \"log\", worker = .logWorker, ),\n    # tail worker that fails to return a valid handler, used to confirm that this is handled\n    # gracefully, i.e. only results in a user-level error.\n    ( name = \"log-invalid\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"tail-worker-test-invalid.js\")\n        ],\n      ),\n    )\n  ],\n);\n\nconst alarmsWorker :Workerd.Worker = (\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n\n  modules = [\n    (name = \"worker\", esModule = embed \"actor-alarms-test.js\"),\n  ],\n\n  durableObjectNamespaces = [\n    (className = \"DurableObjectExample\", uniqueKey = \"210bd0cbd803ef7883a1ee9d86cce06e\"),\n  ],\n\n  durableObjectStorage = (localDisk = \"TEST_TMPDIR\"),\n\n  bindings = [\n    (name = \"ns\", durableObjectNamespace = \"DurableObjectExample\"),\n  ],\n  # tailed by the main tail worker and the dummy buffered tail worker, to get traces for it too.\n  tails = [\"buffered\"],\n  streamingTails = [\"log\"],\n);\n\nconst hiberWorker :Workerd.Worker = (\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n\n  modules = [\n    (name = \"worker\", esModule = embed \"websocket-hibernation.js\"),\n  ],\n\n  durableObjectNamespaces = [\n    (className = \"DurableObjectExample\", uniqueKey = \"210bd0cbd803ef7883a1ee9d86cce06f\"),\n  ],\n\n  durableObjectStorage = (localDisk = \"TEST_TMPDIR\"),\n\n  bindings = [\n    (name = \"ns\", durableObjectNamespace = \"DurableObjectExample\"),\n  ],\n  streamingTails = [\"log\"],\n);\n\nconst jsRpcWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"tail-worker-test-jsrpc.js\")\n  ],\n  compatibilityFlags = [\"nodejs_compat\", \"experimental\"],\n\n  bindings = [\n    (name = \"MyService\", service = (name = \"js-rpc-test\", entrypoint = \"MyService\")),\n    (name = \"MyActor\", durableObjectNamespace = \"MyActor\"),\n  ],\n  durableObjectNamespaces = [\n    (className = \"MyActor\", uniqueKey = \"foo\"),\n  ],\n  durableObjectStorage = (inMemory = void),\n\n  streamingTails = [\"log\"],\n);\n\nconst logWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"tail-worker-test.js\")\n  ],\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n  streamingTails = [\"receiver\"],\n);\n\nconst logReceiver :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"tail-worker-test-receiver.js\")\n  ],\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n);\n\nconst logBuffered :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"tail-worker-test-dummy.js\")\n  ],\n  compatibilityFlags = [\"streaming_tail_worker\"],\n  streamingTails = [\"log\"],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/textdecoder-utf16-odd-offset-test.js",
    "content": "import { strictEqual } from 'node:assert';\n\nexport const TextDecoderUtf16OddOffset = {\n  async test() {\n    const decoder = new TextDecoder('utf-16le');\n    const buffer = new ArrayBuffer(200);\n    const view = new Uint8Array(buffer, 3, 20);\n    for (let i = 0; i < 20; i += 2) {\n      view[i] = 0x42;\n      view[i + 1] = 0x00;\n    }\n    const result = decoder.decode(view);\n    strictEqual(result, 'BBBBBBBBBB');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/textdecoder-utf16-odd-offset-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"textdecoder-utf16-odd-offset-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"textdecoder-utf16-odd-offset-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat_v2\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/transform-streams-test.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { strictEqual, ok } from 'node:assert';\n\nasync function consume(readable) {\n  let data = '';\n  for await (const chunk of readable) {\n    data += chunk;\n  }\n  return data;\n}\n\n// Test default identity transform\nexport const defaultIdentityTransform = {\n  async test() {\n    const transform = new TransformStream();\n    ok(!(transform instanceof IdentityTransformStream));\n\n    const { readable, writable } = transform;\n\n    const writer = writable.getWriter();\n    const reader = readable.getReader();\n\n    const after = await Promise.allSettled([\n      writer.write('hello'),\n      reader.read(),\n    ]);\n\n    strictEqual(after[0].value, undefined);\n    strictEqual(after[1].value.value, 'hello');\n  },\n};\n\n// Test simple transform with start/transform/flush\nexport const simpleTransform = {\n  async test() {\n    const transform = new TransformStream({\n      start(controller) {\n        controller.enqueue('<');\n      },\n      transform(value, controller) {\n        controller.enqueue(value.toUpperCase());\n      },\n      flush(controller) {\n        controller.enqueue('>');\n      },\n    });\n    ok(!(transform instanceof IdentityTransformStream));\n\n    const { readable, writable } = transform;\n\n    const writer = writable.getWriter();\n\n    const res = await Promise.allSettled([\n      writer.write('hello'),\n      writer.write('there'),\n      writer.close(),\n      consume(readable),\n    ]);\n\n    strictEqual(res[3].value, '<HELLOTHERE>');\n  },\n};\n\n// Test async transform with delays\nexport const delayTransform = {\n  async test() {\n    const transform = new TransformStream({\n      async start(controller) {\n        await scheduler.wait(1);\n        controller.enqueue('<');\n      },\n      async transform(value, controller) {\n        await scheduler.wait(1);\n        controller.enqueue(value.toUpperCase());\n      },\n      async flush(controller) {\n        await scheduler.wait(1);\n        controller.enqueue('>');\n      },\n    });\n    ok(!(transform instanceof IdentityTransformStream));\n\n    const { readable, writable } = transform;\n\n    const writer = writable.getWriter();\n\n    const res = await Promise.allSettled([\n      writer.write('hello'),\n      writer.write('there'),\n      writer.close(),\n      consume(readable),\n    ]);\n\n    strictEqual(res[3].value, '<HELLOTHERE>');\n  },\n};\n\n// Test transform with different types (string input, Uint8Array output)\nexport const differentTypesTransform = {\n  async test() {\n    const enc = new TextEncoder();\n\n    async function consumeBytes(readable) {\n      const dec = new TextDecoder();\n      let data = '';\n      for await (const chunk of readable) {\n        data += dec.decode(chunk, { stream: true });\n      }\n      data += dec.decode();\n      return data;\n    }\n\n    const transform = new TransformStream({\n      async start(controller) {\n        await scheduler.wait(1);\n        controller.enqueue(enc.encode('<'));\n      },\n      async transform(value, controller) {\n        await scheduler.wait(1);\n        controller.enqueue(enc.encode(value.toUpperCase()));\n      },\n      async flush(controller) {\n        await scheduler.wait(1);\n        controller.enqueue(enc.encode('>'));\n      },\n    });\n    ok(!(transform instanceof IdentityTransformStream));\n\n    const { readable, writable } = transform;\n\n    const writer = writable.getWriter();\n\n    const res = await Promise.allSettled([\n      writer.write('hello'),\n      writer.write('there'),\n      writer.close(),\n      consumeBytes(readable),\n    ]);\n\n    strictEqual(res[3].value, '<HELLOTHERE>');\n  },\n};\n\n// Test sync error during start\nexport const syncErrorDuringStart = {\n  async test() {\n    const transform = new TransformStream({\n      start() {\n        throw new Error('boom');\n      },\n    });\n    ok(!(transform instanceof IdentityTransformStream));\n\n    const { readable, writable } = transform;\n\n    const writer = writable.getWriter();\n\n    const res = await Promise.allSettled([\n      writer.write('hello'),\n      writer.write('there'),\n      writer.close(),\n      consume(readable),\n    ]);\n\n    strictEqual(res[0].status, 'rejected');\n    strictEqual(res[1].status, 'rejected');\n    strictEqual(res[2].status, 'rejected');\n    strictEqual(res[0].reason.message, 'boom');\n    strictEqual(res[1].reason.message, 'boom');\n    strictEqual(res[2].reason.message, 'boom');\n  },\n};\n\n// Test async error during start\nexport const asyncErrorDuringStart = {\n  async test() {\n    const transform = new TransformStream({\n      async start() {\n        throw new Error('boom');\n      },\n    });\n    ok(!(transform instanceof IdentityTransformStream));\n\n    const { readable, writable } = transform;\n\n    const writer = writable.getWriter();\n\n    const res = await Promise.allSettled([\n      writer.write('hello'),\n      writer.write('there'),\n      writer.close(),\n      consume(readable),\n    ]);\n\n    strictEqual(res[0].status, 'rejected');\n    strictEqual(res[1].status, 'rejected');\n    strictEqual(res[2].status, 'rejected');\n    strictEqual(res[3].status, 'rejected');\n    strictEqual(res[0].reason.message, 'boom');\n    strictEqual(res[1].reason.message, 'boom');\n    strictEqual(res[2].reason.message, 'boom');\n    strictEqual(res[3].reason.message, 'boom');\n  },\n};\n\n// Test sync error during transform\nexport const syncErrorDuringTransform = {\n  async test() {\n    const transform = new TransformStream({\n      transform() {\n        throw new Error('boom');\n      },\n    });\n    ok(!(transform instanceof IdentityTransformStream));\n\n    const { readable, writable } = transform;\n\n    const writer = writable.getWriter();\n\n    const res = await Promise.allSettled([\n      writer.write('hello'),\n      writer.write('there'),\n      writer.close(),\n      consume(readable),\n    ]);\n\n    strictEqual(res[0].status, 'rejected');\n    strictEqual(res[1].status, 'rejected');\n    strictEqual(res[2].status, 'rejected');\n    strictEqual(res[3].status, 'rejected');\n    strictEqual(res[0].reason.message, 'boom');\n    strictEqual(res[1].reason.message, 'boom');\n    strictEqual(res[2].reason.message, 'boom');\n    strictEqual(res[3].reason.message, 'boom');\n  },\n};\n\n// Test async error during transform\nexport const asyncErrorDuringTransform = {\n  async test() {\n    const transform = new TransformStream({\n      async transform() {\n        await scheduler.wait(1);\n        throw new Error('boom');\n      },\n    });\n    ok(!(transform instanceof IdentityTransformStream));\n\n    const { readable, writable } = transform;\n\n    const writer = writable.getWriter();\n\n    const res = await Promise.allSettled([\n      writer.write('hello'),\n      writer.write('there'),\n      writer.close(),\n      consume(readable),\n    ]);\n\n    strictEqual(res[0].status, 'rejected');\n    strictEqual(res[1].status, 'rejected');\n    strictEqual(res[2].status, 'rejected');\n    strictEqual(res[3].status, 'rejected');\n    strictEqual(res[0].reason.message, 'boom');\n    strictEqual(res[1].reason.message, 'boom');\n    strictEqual(res[2].reason.message, 'boom');\n    strictEqual(res[3].reason.message, 'boom');\n  },\n};\n\n// Test sync error during flush\nexport const syncErrorDuringFlush = {\n  async test() {\n    const transform = new TransformStream({\n      flush() {\n        throw new Error('boom');\n      },\n    });\n    ok(!(transform instanceof IdentityTransformStream));\n\n    const { readable, writable } = transform;\n\n    const writer = writable.getWriter();\n\n    const res = await Promise.allSettled([\n      writer.write('hello'),\n      writer.write('there'),\n      writer.close(),\n      consume(readable),\n    ]);\n\n    // The two writes will succeed.\n    strictEqual(res[0].status, 'fulfilled');\n    strictEqual(res[1].status, 'fulfilled');\n\n    // The close and the consume will reject.\n    strictEqual(res[2].status, 'rejected');\n    strictEqual(res[3].status, 'rejected');\n    strictEqual(res[2].reason.message, 'boom');\n    strictEqual(res[3].reason.message, 'boom');\n  },\n};\n\n// Test async error during flush\nexport const asyncErrorDuringFlush = {\n  async test() {\n    const transform = new TransformStream({\n      async flush() {\n        await scheduler.wait(1);\n        throw new Error('boom');\n      },\n    });\n    ok(!(transform instanceof IdentityTransformStream));\n\n    const { readable, writable } = transform;\n\n    const writer = writable.getWriter();\n\n    const res = await Promise.allSettled([\n      writer.write('hello'),\n      writer.write('there'),\n      writer.close(),\n      consume(readable),\n    ]);\n\n    // The two writes will succeed.\n    strictEqual(res[0].status, 'fulfilled');\n    strictEqual(res[1].status, 'fulfilled');\n\n    // The close and the consume will reject.\n    strictEqual(res[2].status, 'rejected');\n    strictEqual(res[3].status, 'rejected');\n    strictEqual(res[2].reason.message, 'boom');\n    strictEqual(res[3].reason.message, 'boom');\n  },\n};\n\n// Test write backpressure\nexport const writeBackpressure = {\n  async test() {\n    let expectedReadSize = 2;\n    const transform = new TransformStream(\n      {\n        transform(chunk, controller) {\n          strictEqual(controller.desiredSize, expectedReadSize--);\n          controller.enqueue(chunk);\n        },\n      },\n      { highWaterMark: 2 },\n      { highWaterMark: 2 }\n    );\n\n    const writer = transform.writable.getWriter();\n    strictEqual(writer.desiredSize, 2);\n\n    const promises = [writer.write('hello'), writer.write('there')];\n\n    strictEqual(writer.desiredSize, 0);\n\n    await Promise.allSettled(promises);\n\n    strictEqual(writer.desiredSize, 2);\n  },\n};\n\n// Test that piping from a JS-backed TransformStream through an\n// IdentityTransformStream does not result in a hung pipeTo promise.\nexport const transformRoundtrip = {\n  async test(ctrl, env, ctx) {\n    const enc = new TextEncoder();\n    const dec = new TextDecoder();\n\n    const testData = 'hello world test data';\n    const compressedStream = new ReadableStream({\n      start(controller) {\n        controller.enqueue(enc.encode(testData));\n        controller.close();\n      },\n    }).pipeThrough(new CompressionStream('gzip'));\n\n    const compressedChunks = [];\n    const compressedReader = compressedStream.getReader();\n    for (;;) {\n      const { done, value } = await compressedReader.read();\n      if (done) break;\n      compressedChunks.push(value);\n    }\n    const compressedData = new Uint8Array(\n      compressedChunks.reduce((acc, chunk) => acc + chunk.length, 0)\n    );\n    let offset = 0;\n    for (const chunk of compressedChunks) {\n      compressedData.set(chunk, offset);\n      offset += chunk.length;\n    }\n\n    const inputStream = new ReadableStream({\n      start(controller) {\n        controller.enqueue(compressedData);\n        controller.close();\n      },\n    });\n\n    const decompression = new DecompressionStream('gzip');\n    const ts = new TransformStream({\n      transform(chunk, controller) {\n        controller.enqueue(chunk);\n      },\n    });\n    const { readable, writable } = new IdentityTransformStream();\n\n    ctx.waitUntil(\n      inputStream.pipeThrough(decompression).pipeThrough(ts).pipeTo(writable)\n    );\n\n    const outputChunks = [];\n    const reader = readable.getReader();\n    for (;;) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      outputChunks.push(value);\n    }\n\n    const output = dec.decode(\n      new Uint8Array(\n        outputChunks.reduce((acc, chunk) => [...acc, ...chunk], [])\n      )\n    );\n    strictEqual(output, testData);\n  },\n};\n\n// Regression test: iterating over globalThis properties should not crash.\n// This tests that constructing and inspecting various global objects works correctly.\n//\n// Note: We skip `process` in the iteration because when nodejs_compat is enabled,\n// the iteration logic may call process.exit() which terminates the test.\nexport const transformCrashRegression = {\n  test() {\n    function iterate(obj, depth = 0, results = {}, originalKey) {\n      for (const key in obj) {\n        if (depth > 100) return results;\n        if (\n          key === 'parent' ||\n          key === 'globalThis' ||\n          key === 'self' ||\n          key === 'ServiceWorkerGlobalScope' ||\n          key === 'global' ||\n          key === 'process'\n        )\n          continue;\n        if (typeof obj[key] === 'object') {\n          results[key] = iterate(obj[key], ++depth, results[key] ?? {}, key);\n        } else {\n          const properties = new Set([\n            ...Object.getOwnPropertyNames(obj[key]),\n            ...Object.getOwnPropertySymbols(obj[key]),\n            ...Object.keys(obj[key]),\n          ]);\n          for (const prop in obj[key]) {\n            properties.add(prop);\n          }\n          if (properties.size > 0) {\n            results[key] = {\n              __proto: typeof obj[key].__proto__,\n            };\n            for (const property of properties) {\n              if (\n                property === 'caller' ||\n                property === 'callee' ||\n                property === 'arguments' ||\n                property === 'constructor'\n              )\n                continue;\n              let writeProp =\n                property === 'prototype' ? '_prototype' : property;\n              try {\n                if (typeof obj[key][property] === 'object') {\n                  results[key][writeProp] = iterate(\n                    obj[key][property],\n                    ++depth,\n                    results[key][property] ?? {},\n                    key\n                  );\n                } else {\n                  results[key][writeProp] = typeof obj[key][property];\n                }\n              } catch (err) {}\n              let instance;\n              try {\n                if (property === 'prototype') {\n                  try {\n                    instance = new obj[key]();\n                  } catch (internalConstructError) {\n                    if (\n                      internalConstructError.message.includes(\n                        'Failed to construct'\n                      ) ||\n                      internalConstructError.message.includes(\n                        'Illegal constructor'\n                      )\n                    ) {\n                      instance = new obj[key]('');\n                    } else {\n                      throw internalConstructError;\n                    }\n                  }\n                } else {\n                  instance = new obj[key][property]();\n                }\n                const instanceProperties = new Set([\n                  ...Object.getOwnPropertyNames(instance),\n                  ...Object.getOwnPropertySymbols(instance),\n                  ...Object.keys(instance),\n                  ...(instance.constructor\n                    ? Object.getOwnPropertyNames(instance.constructor)\n                    : []),\n                  ...(instance.constructor\n                    ? Object.getOwnPropertySymbols(instance.constructor)\n                    : []),\n                  ...(instance.constructor\n                    ? Object.keys(instance.constructor)\n                    : []),\n                ]);\n                for (const prop in instance) {\n                  instanceProperties.add(prop);\n                }\n                if (instanceProperties.size > 0) {\n                  if (results[key][writeProp] === undefined) {\n                    results[key][writeProp] = {};\n                  }\n                  for (const instanceProperty of instanceProperties) {\n                    try {\n                      if (\n                        results[key][writeProp][instanceProperty] === undefined\n                      ) {\n                        results[key][writeProp][instanceProperty] =\n                          typeof instance[instanceProperty];\n                      }\n                    } catch {}\n                  }\n                } else {\n                  if (results[key][writeProp] === undefined) {\n                    results[key][writeProp] = typeof instance;\n                  }\n                }\n              } catch (e) {\n                if (results[key][writeProp] === undefined) {\n                  results[key][writeProp] = typeof obj[key][property];\n                }\n                continue;\n              }\n            }\n          } else {\n            results[key] = typeof obj[key];\n          }\n        }\n      }\n      return results;\n    }\n\n    function order(obj) {\n      if (typeof obj === 'object') {\n        const ordered = {};\n        Object.keys(obj)\n          .sort()\n          .forEach(function (key) {\n            ordered[key] = order(obj[key]);\n          });\n        return ordered;\n      } else {\n        return obj;\n      }\n    }\n\n    const iterated = iterate(globalThis);\n    const ordered = order(iterated);\n    const toWrite = JSON.stringify(ordered);\n\n    // Test passes if we get here without crashing\n    ok(toWrite.length > 0);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/transform-streams-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"transform-streams-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"transform-streams-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"streams_enable_constructors\",\n          \"transformstream_enable_standard_constructor\",\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../../tools/base.tsconfig.json\",\n  \"compilerOptions\": {\n    \"lib\": [\"esnext\", \"webworker\"],\n    \"paths\": {\n      \"@workerd/*\": [\"../../../bazel-bin/src/workerd/*\"]\n    },\n    \"skipLibCheck\": true,\n    \"allowJs\": false,\n    \"types\": [\"@types/node\"]\n  },\n  \"include\": [\"**/*.ts\"]\n}\n"
  },
  {
    "path": "src/workerd/api/tests/unhandled-rejection-test.js",
    "content": "// Regression tests for https://github.com/cloudflare/workerd/issues/6020\n// Unhandled rejection should NOT fire for promises that are handled through\n// multi-tick promise chains.\n\nimport { strictEqual, ok, rejects } from 'node:assert';\nimport { mock } from 'node:test';\n\nconst asyncFunction = async (name) => {\n  throw new Error(`this function rejects: ${name}`);\n};\n\n// Verifies assert.rejects handles rejections without unhandledrejection.\nexport const assertRejects = {\n  async test() {\n    const handler = mock.fn();\n    addEventListener('unhandledrejection', handler);\n    try {\n      await rejects(async () => asyncFunction('A'));\n      strictEqual(\n        handler.mock.callCount(),\n        0,\n        'unhandledrejection should not fire for assert.rejects'\n      );\n    } finally {\n      removeEventListener('unhandledrejection', handler);\n    }\n  },\n};\n\n// Verifies chained .then().catch() handling avoids unhandledrejection.\nexport const promiseChainCatch = {\n  async test() {\n    const handler = mock.fn();\n    addEventListener('unhandledrejection', handler);\n    try {\n      const error = await Promise.resolve()\n        .then(() => asyncFunction('B'))\n        .then(() => null)\n        .catch((e) => e);\n      ok(error instanceof Error);\n      strictEqual(error.message, 'this function rejects: B');\n      strictEqual(\n        handler.mock.callCount(),\n        0,\n        'unhandledrejection should not fire for .catch() chain'\n      );\n    } finally {\n      removeEventListener('unhandledrejection', handler);\n    }\n  },\n};\n\n// Verifies try/catch around awaited chain avoids unhandledrejection.\nexport const tryCatchAwait = {\n  async test() {\n    const handler = mock.fn();\n    addEventListener('unhandledrejection', handler);\n    try {\n      try {\n        await Promise.resolve('C').then(asyncFunction);\n      } catch (error) {\n        ok(error instanceof Error);\n        strictEqual(error.message, 'this function rejects: C');\n      }\n      strictEqual(\n        handler.mock.callCount(),\n        0,\n        'unhandledrejection should not fire for try/catch'\n      );\n    } finally {\n      removeEventListener('unhandledrejection', handler);\n    }\n  },\n};\n\n// Verifies a truly unhandled rejection still emits unhandledrejection.\nexport const genuineUnhandledRejectionStillFires = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n    const handler = mock.fn(() => resolve());\n    addEventListener('unhandledrejection', handler, { once: true });\n    Promise.reject('boom');\n    await promise;\n    strictEqual(\n      handler.mock.callCount(),\n      1,\n      'unhandledrejection should fire for genuinely unhandled rejection'\n    );\n  },\n};\n\n// Verifies unhandledrejection fires after a Promise.resolve tick.\nexport const unhandledRejectionAfterPromiseResolve = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n    const handler = mock.fn(() => resolve());\n    addEventListener('unhandledrejection', handler, { once: true });\n    Promise.reject('boom');\n    await Promise.resolve();\n    await promise;\n    strictEqual(\n      handler.mock.callCount(),\n      1,\n      'unhandledrejection should fire after Promise.resolve'\n    );\n  },\n};\n\n// Verifies unhandledrejection followed by rejectionhandled on late catch.\nexport const lateHandlerTriggersRejectionhandled = {\n  async test() {\n    const { promise: unhandledPromise, resolve: resolveUnhandled } =\n      Promise.withResolvers();\n    const { promise: handledPromise, resolve: resolveHandled } =\n      Promise.withResolvers();\n    let unhandledReason;\n    let handledReason;\n    const unhandledHandler = mock.fn((event) => {\n      unhandledReason = event.reason;\n      resolveUnhandled();\n    });\n    const handledHandler = mock.fn((event) => {\n      handledReason = event.reason;\n      resolveHandled();\n    });\n    addEventListener('unhandledrejection', unhandledHandler, { once: true });\n    addEventListener('rejectionhandled', handledHandler, { once: true });\n    try {\n      const error = new Error('late');\n      const promise = Promise.reject(error);\n      await unhandledPromise;\n      promise.catch(() => {});\n      await handledPromise;\n      strictEqual(\n        unhandledHandler.mock.callCount(),\n        1,\n        'unhandledrejection should fire once before late handler'\n      );\n      strictEqual(\n        handledHandler.mock.callCount(),\n        1,\n        'rejectionhandled should fire after late handler'\n      );\n      ok(unhandledReason instanceof Error);\n      strictEqual(unhandledReason.message, 'late');\n      strictEqual(\n        handledReason,\n        undefined,\n        'rejectionhandled reason should be undefined'\n      );\n    } finally {\n      removeEventListener('unhandledrejection', unhandledHandler);\n      removeEventListener('rejectionhandled', handledHandler);\n    }\n  },\n};\n\n// Verifies unhandledrejection handler can trigger another unhandled rejection.\nexport const handlerTriggeredUnhandledRejection = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n    const timeout = new Promise((resolveTimeout) => {\n      setTimeout(resolveTimeout, 25);\n    });\n    const reasons = [];\n    let callCount = 0;\n    const handler = mock.fn((event) => {\n      reasons.push(event.reason);\n      callCount += 1;\n      if (callCount === 1) {\n        queueMicrotask(() => Promise.reject(new Error('second')));\n      }\n      if (callCount === 2) {\n        resolve();\n      }\n    });\n    addEventListener('unhandledrejection', handler);\n    try {\n      Promise.reject(new Error('first'));\n      await Promise.race([promise, timeout]);\n      strictEqual(\n        handler.mock.callCount(),\n        2,\n        'unhandledrejection should fire for rejection triggered by handler'\n      );\n      strictEqual(reasons.length, 2);\n      ok(reasons[0] instanceof Error);\n      strictEqual(reasons[0].message, 'first');\n      ok(reasons[1] instanceof Error);\n      strictEqual(reasons[1].message, 'second');\n    } finally {\n      removeEventListener('unhandledrejection', handler);\n    }\n  },\n};\n\n// Verifies each unhandled rejection emits its own event.\nexport const multipleUnhandledRejections = {\n  async test() {\n    const { promise, resolve } = Promise.withResolvers();\n    const timeout = new Promise((resolveTimeout) => {\n      setTimeout(resolveTimeout, 25);\n    });\n    const handler = mock.fn(() => {\n      if (handler.mock.callCount() === 2) {\n        resolve();\n      }\n    });\n    addEventListener('unhandledrejection', handler);\n    try {\n      Promise.reject(new Error('one'));\n      Promise.reject(new Error('two'));\n      await Promise.race([promise, timeout]);\n      strictEqual(\n        handler.mock.callCount(),\n        2,\n        'unhandledrejection should fire for each unhandled rejection'\n      );\n    } finally {\n      removeEventListener('unhandledrejection', handler);\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/unhandled-rejection-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"unhandled-rejection-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"unhandled-rejection-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"unhandled_rejection_after_microtask_checkpoint\",\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/unsafe-test.js",
    "content": "import { strictEqual, ok, throws } from 'node:assert';\n\nexport const basics = {\n  test(ctx, env) {\n    strictEqual(env.unsafe.eval('1'), 1);\n\n    // eval does not capture outer scope.\n    let m = 1;\n    throws(() => env.unsafe.eval('m'));\n\n    throws(() => env.unsafe.eval(' throw new Error(\"boom\"); ', 'foo'), {\n      message: 'boom',\n      stack: /at foo/,\n    });\n\n    // Regular dynamic eval is still not allowed\n    throws(() => eval(''));\n  },\n};\n\nexport const newFunction = {\n  test(ctx, env) {\n    const fn = env.unsafe.newFunction('return m', 'bar', 'm');\n    strictEqual(fn.length, 1);\n    strictEqual(fn.name, 'bar');\n    strictEqual(fn(), undefined);\n    strictEqual(fn(1), 1);\n    strictEqual(fn(fn), fn);\n  },\n};\n\nexport const newAsyncFunction = {\n  async test(ctx, env) {\n    const fn = env.unsafe.newAsyncFunction('return await m', 'bar', 'm');\n    strictEqual(fn.length, 1);\n    strictEqual(fn.name, 'bar');\n    strictEqual(await fn(), undefined);\n    strictEqual(await fn(1), 1);\n    strictEqual(await fn(fn), fn);\n    strictEqual(await fn(Promise.resolve(1)), 1);\n  },\n};\n\nexport const newAsyncFunction2 = {\n  async test(ctx, env) {\n    const fn = env.unsafe.newAsyncFunction('return await arguments[0]');\n    strictEqual(fn.length, 0);\n    strictEqual(fn.name, 'anonymous');\n    strictEqual(await fn(), undefined);\n    strictEqual(await fn(1), 1);\n    strictEqual(await fn(fn), fn);\n    strictEqual(await fn(Promise.resolve(1)), 1);\n  },\n};\n\nexport const newWasmModule = {\n  async test(ctx, env) {\n    throws(\n      () => env.unsafe.newWasmModule([]),\n      new TypeError(\n        \"Failed to execute 'newWasmModule' on 'UnsafeEval': parameter 1 is not of type 'ArrayBuffer or ArrayBufferView'.\"\n      )\n    );\n    // Fails to construct: missing magic number\n    throws(() => env.unsafe.newWasmModule(new Uint8Array([])));\n    // Test that we can successfully construct a minimal valid Wasm module: magic\n    // number 0asm + version\n    const result = env.unsafe.newWasmModule(\n      new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0])\n    );\n    strictEqual(result.constructor, WebAssembly.Module);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/unsafe-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"unsafe-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"unsafe-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"experimental\"],\n        bindings = [\n          (name = \"unsafe\", unsafeEval = void )\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/url-test.js",
    "content": "import { deepStrictEqual, strictEqual, ok, fail, throws } from 'node:assert';\n\nexport const constructAndGet = {\n  test() {\n    const cases = [\n      {\n        input: 'https://capnproto.org',\n        pathname: '/',\n        comment: 'special URL, 0-component path',\n      },\n      {\n        input: 'https://capnproto.org/',\n        pathname: '/',\n        comment: 'special URL, 1-component path with empty terminal component',\n      },\n      {\n        input: 'https://capnproto.org/foo',\n        pathname: '/foo',\n        comment: 'special URL, 1-component path',\n      },\n      {\n        input: 'https://capnproto.org/foo/',\n        pathname: '/foo/',\n        comment: 'special URL, 2-component path with empty terminal component',\n      },\n      {\n        input: 'https://capnproto.org/%2F',\n        pathname: '/%2F',\n        comment: 'encoded slash can be a path component',\n      },\n      {\n        input: 'https://capnproto.org//',\n        pathname: '//',\n        comment: '// in path is not collapsed',\n      },\n      {\n        input: 'https://capnproto.org/./',\n        pathname: '/',\n        comment: '/./ in path is collapsed',\n      },\n      {\n        input: 'https://capnproto.org/?<>',\n        search: '?%3C%3E',\n        comment: 'angle brackets in search get percent-encoded',\n      },\n      {\n        input: 'https://capnproto.org/??',\n        search: '??',\n        comment: 'question mark in search does not get percent-encoded',\n      },\n      {\n        input: 'https://capnproto.org/?foo',\n        search: '?foo',\n        href: 'https://capnproto.org/?foo',\n        comment:\n          'No-valued/empty-valued query parameters are preserved round-trip',\n      },\n      {\n        input: 'https://capnproto.org/?foo=',\n        search: '?foo=',\n        href: 'https://capnproto.org/?foo=',\n        comment:\n          'No-valued/empty-valued query parameters are preserved round-trip',\n      },\n      {\n        input: 'https://capnproto.org/?foo=&bar',\n        search: '?foo=&bar',\n        href: 'https://capnproto.org/?foo=&bar',\n        comment:\n          'No-valued/empty-valued query parameters are preserved round-trip',\n      },\n      {\n        input: 'https://capnproto.org/?foo&bar=',\n        search: '?foo&bar=',\n        href: 'https://capnproto.org/?foo&bar=',\n        comment:\n          'No-valued/empty-valued query parameters are preserved round-trip',\n      },\n      {\n        input: 'https://capnproto.org/?foo+bar=baz+qux',\n        search: '?foo+bar=baz+qux',\n        comment: 'pluses in search do not get percent-encoded',\n      },\n      {\n        input: 'https://capnproto.org/?😺',\n        search: '?%F0%9F%98%BA',\n        comment: 'cat emoji in search gets percent-encoded',\n      },\n      {\n        input: 'https://capnproto.org/#😺',\n        hash: '#%F0%9F%98%BA',\n        comment: 'cat emoji in hash gets percent-encoded',\n      },\n      {\n        input: 'https://capnproto.org:443/',\n        href: 'https://capnproto.org/',\n        comment:\n          'Parsing a URL with an explicit scheme-default port ignores the port',\n      },\n      {\n        input: 'https://capnproto.org:/',\n        href: 'https://capnproto.org/',\n        comment: 'Parsing a URL with a \":\" but no port ignores port',\n      },\n      {\n        input: 'http://foo/bar;baz@qux',\n        href: 'http://foo/bar;baz@qux',\n        comment:\n          'URL path components are encoded with the path percent encode set',\n      },\n      {\n        input: 'https://jam_com.helpusersvote.net/',\n        href: 'https://jam_com.helpusersvote.net/',\n        comment: 'Underscores are allowed in hostnames',\n      },\n      {\n        input:\n          'https://foo%25bar:baz%25qux@capnproto.org/foo%25bar?foo%25bar=baz%25qux#foo%25bar',\n        href: 'https://foo%25bar:baz%25qux@capnproto.org/foo%25bar?foo%25bar=baz%25qux#foo%25bar',\n        comment:\n          'Encoded percent signs in userinfo, path, query, and fragment get round-tripped',\n      },\n    ];\n\n    cases.forEach((test) => {\n      ['pathname', 'search', 'hash', 'href'].forEach((attribute) => {\n        const url = new URL(test.input);\n        if (test[attribute] !== undefined) {\n          strictEqual(\n            url[attribute],\n            test[attribute],\n            `${test.input}: ${test.comment}`\n          );\n        }\n      });\n    });\n  },\n};\n\nexport const constructSetAndGet = {\n  test() {\n    const cases = {\n      protocol: [\n        {\n          href: 'https://example.com/',\n          new_value: 'http',\n          expected: {\n            href: 'http://example.com/',\n          },\n          comment: 'Setting scheme on URL with null port does not change port',\n        },\n        {\n          href: 'https://example.com:80/',\n          new_value: 'http',\n          expected: {\n            href: 'http://example.com/',\n          },\n          comment:\n            'Setting scheme on URL with port that is the new scheme-default port nulls out port',\n        },\n        {\n          href: 'https://example.com:1234/',\n          new_value: 'http',\n          expected: {\n            href: 'http://example.com:1234/',\n          },\n          comment:\n            'Setting scheme on URL with non-scheme-default port preserves port',\n        },\n      ],\n      search: [\n        {\n          href: 'https://capnproto.org/',\n          new_value: '#',\n          expected: {\n            href: 'https://capnproto.org/?%23',\n            search: '?%23',\n          },\n        },\n        {\n          href: 'https://capnproto.org/',\n          new_value: '#=#',\n          expected: {\n            href: 'https://capnproto.org/?%23=%23',\n            search: '?%23=%23',\n          },\n        },\n      ],\n      host: [\n        {\n          href: 'https://example.com/',\n          new_value: 'foo.example.com:',\n          expected: {\n            href: 'https://foo.example.com/',\n          },\n          comment: 'Setting host with \":\" but no port ignores port',\n        },\n        {\n          href: 'https://example.com/',\n          new_value: 'foo.example.com:80',\n          expected: {\n            href: 'https://foo.example.com:80/',\n          },\n          comment: 'Setting host with non-scheme-default port sets port',\n        },\n        {\n          href: 'https://example.com:1234/',\n          new_value: 'foo.example.com:443',\n          expected: {\n            href: 'https://foo.example.com/',\n          },\n          comment: 'Setting host with scheme-default port nulls out port',\n        },\n        {\n          href: 'https://example.com/',\n          new_value: 'foo.example.com:443',\n          expected: {\n            href: 'https://foo.example.com/',\n          },\n          comment: 'Setting host with scheme-default port nulls out port',\n        },\n      ],\n      port: [\n        {\n          href: 'https://example.com/',\n          new_value: '443',\n          expected: {\n            href: 'https://example.com/',\n          },\n          comment: 'Setting port to scheme-default port is a no-op',\n        },\n        {\n          href: 'https://example.com:1234/',\n          new_value: '443',\n          expected: {\n            href: 'https://example.com/',\n          },\n          comment: 'Setting port to scheme-default port nulls out port',\n        },\n        {\n          href: 'https://example.com/',\n          new_value: '1234',\n          expected: {\n            href: 'https://example.com:1234/',\n          },\n          comment: 'Setting port to non-scheme-default port adopts port',\n        },\n        {\n          href: 'https://example.com:80/',\n          new_value: '1234',\n          expected: {\n            href: 'https://example.com:1234/',\n          },\n          comment: 'Setting port to non-scheme-default port adopts port',\n        },\n        {\n          href: 'https://example.com:1234/',\n          new_value: '',\n          expected: {\n            href: 'https://example.com/',\n          },\n          comment: 'Setting port to the empty string nulls out port',\n        },\n      ],\n      hostname: [\n        {\n          href: 'http://example.com/path/to/something?query=foo#hash',\n          new_value: 'newexample.com',\n          expected: {\n            href: 'http://newexample.com/path/to/something?query=foo#hash',\n          },\n        },\n      ],\n    };\n\n    for (const attribute in cases) {\n      cases[attribute].forEach((test) => {\n        const url = new URL(test.href);\n        url[attribute] = test.new_value;\n        for (const a in test.expected) {\n          strictEqual(\n            url[a],\n            test.expected[a],\n            `${test.href}: ${test.comment}`\n          );\n        }\n      });\n    }\n  },\n};\n\nexport const urlSearchParamsStringifyBehavior = {\n  test() {\n    const url = new URL('https://example.com/?foo&bar=&baz=qux');\n    strictEqual(url.href, 'https://example.com/?foo&bar=&baz=qux');\n    strictEqual(url.search, '?foo&bar=&baz=qux');\n    strictEqual(url.searchParams.toString(), 'foo=&bar=&baz=qux');\n  },\n};\n\nexport const urlSearchParamsForEach = {\n  test() {\n    let searchParams = new URLSearchParams('');\n    searchParams.forEach(() => {\n      fail('Should not have been called');\n    });\n\n    const foreachOutput = [];\n    searchParams = new URLSearchParams('key1=value1&key2=value2');\n    strictEqual(searchParams.size, 2);\n    let pushed = false;\n    searchParams.forEach((val, key) => {\n      foreachOutput.push([key, val]);\n      if (!pushed) {\n        // We can add params within this loop but it's not really safe\n        // because it will cause the loop to run forever if we're not\n        // careful.\n        pushed = true;\n        searchParams.set('key3', 'value3');\n      }\n    });\n    deepStrictEqual(foreachOutput, [\n      ['key1', 'value1'],\n      ['key2', 'value2'],\n      ['key3', 'value3'],\n    ]);\n    strictEqual(searchParams.size, 3);\n\n    // Calling forEach with no argument throws\n    throws(() => searchParams.forEach());\n\n    // Calling forEach with wrong arguments throws\n    throws(() => searchParams.forEach(1));\n\n    // This can be overridden by the second argument\n    searchParams.forEach(function () {\n      strictEqual(this, 1);\n    }, 1);\n\n    // Propagates errors\n    throws(() =>\n      searchParams.forEach(() => {\n        throw new Error('boom');\n      })\n    );\n  },\n};\n\nexport const urlSearchParamsInit1 = {\n  test() {\n    const search1 = new URLSearchParams('a=b');\n    const search2 = new URLSearchParams(search1);\n    strictEqual(search1.toString(), search2.toString());\n  },\n};\n\nexport const urlSearchParamsInit2 = {\n  test() {\n    const search1 = new URLSearchParams('a=b');\n    search1[Symbol.iterator] = function* () {\n      yield ['y', 'z'];\n    };\n    const search2 = new URLSearchParams(search1);\n    ok(!search2.has('a'));\n    ok(search2.has('y'));\n    strictEqual(search2.get('y'), 'z');\n  },\n};\n\nexport const urlSearchParamsInit3 = {\n  test() {\n    // If the initializer has a deleted iterator, then its\n    // contents are ignored but can still be interpreted as\n    // a dictionary.\n    const search1 = new URLSearchParams('a=b');\n    search1[Symbol.iterator] = undefined;\n    search1.y = 'z';\n    const search2 = new URLSearchParams(search1);\n    ok(!search2.has('a'));\n    ok(search2.has('y'));\n    strictEqual(search2.get('y'), 'z');\n  },\n};\n\nexport const urlSearchParamsInit4 = {\n  test() {\n    const formdata = new FormData();\n    formdata.append('a', 'b');\n    ok(formdata.has('a'));\n    const search2 = new URLSearchParams(formdata);\n    ok(search2.has('a'));\n    strictEqual(search2.get('a'), 'b');\n  },\n};\n\nexport const urlSearchParamsInit5 = {\n  test() {\n    const formdata = new FormData();\n    formdata.append('a', 'b');\n    formdata[Symbol.iterator] = undefined;\n    const search2 = new URLSearchParams(formdata);\n    ok(!search2.has('a'));\n  },\n};\n\nexport const urlToJson = {\n  test() {\n    const url = new URL('https://example.com');\n    strictEqual(JSON.stringify(url), '\"https://example.com/\"');\n  },\n};\n\nexport const urlSearchParamsSet = {\n  test() {\n    const url = new URL('https://example.com?c=d');\n    url.searchParams.set('a', 'b');\n    strictEqual(url.searchParams.size, 2);\n    url.searchParams.delete('c');\n    strictEqual(url.searchParams.size, 1);\n    ok(url.searchParams.has('a'));\n    ok(!url.searchParams.has('c'));\n    ok(url.searchParams.has('a', 'b'));\n    ok(!url.searchParams.has('a', 'c'));\n    url.searchParams.delete('a', 'c');\n    ok(url.searchParams.has('a'));\n  },\n};\n\nexport const urlConstructorTests = {\n  test() {\n    const cases = [\n      '# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/segments.js',\n      {\n        input: 'http://example\\t.\\norg',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'C|/',\n        base: 'file://host/dir/file',\n        href: 'file://host/C:/',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: 'host',\n        hostname: 'host',\n        port: '',\n        pathname: '/C:/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'C|\\n/',\n        base: 'file://host/dir/file',\n        href: 'file://host/C:/',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: 'host',\n        hostname: 'host',\n        port: '',\n        pathname: '/C:/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'https://:@test',\n        base: 'about:blank',\n        href: 'https://test/',\n        origin: 'https://test',\n        protocol: 'https:',\n        username: '',\n        password: '',\n        host: 'test',\n        hostname: 'test',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'non-special://test:@test/x',\n        base: 'about:blank',\n        href: 'non-special://test@test/x',\n        origin: 'null',\n        protocol: 'non-special:',\n        username: 'test',\n        password: '',\n        host: 'test',\n        hostname: 'test',\n        port: '',\n        pathname: '/x',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'non-special://:@test/x',\n        base: 'about:blank',\n        href: 'non-special://test/x',\n        origin: 'null',\n        protocol: 'non-special:',\n        username: '',\n        password: '',\n        host: 'test',\n        hostname: 'test',\n        port: '',\n        pathname: '/x',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http:foo.com',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/foo.com',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/foo.com',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '\\t   :foo.com   \\n',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/:foo.com',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/:foo.com',\n        search: '',\n        hash: '',\n      },\n      {\n        input: ' foo.com  ',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/foo.com',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/foo.com',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'a:\\t foo.com',\n        base: 'http://example.org/foo/bar',\n        href: 'a: foo.com',\n        origin: 'null',\n        protocol: 'a:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: ' foo.com',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://f:21/ b ? d # e ',\n        base: 'http://example.org/foo/bar',\n        href: 'http://f:21/%20b%20?%20d%20#%20e',\n        origin: 'http://f:21',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'f:21',\n        hostname: 'f',\n        port: '21',\n        pathname: '/%20b%20',\n        search: '?%20d%20',\n        hash: '#%20e',\n      },\n      {\n        input: 'lolscheme:x x#x x',\n        base: 'about:blank',\n        href: 'lolscheme:x x#x%20x',\n        protocol: 'lolscheme:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'x x',\n        search: '',\n        hash: '#x%20x',\n      },\n      {\n        input: 'http://f:/c',\n        base: 'http://example.org/foo/bar',\n        href: 'http://f/c',\n        origin: 'http://f',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'f',\n        hostname: 'f',\n        port: '',\n        pathname: '/c',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://f:0/c',\n        base: 'http://example.org/foo/bar',\n        href: 'http://f:0/c',\n        origin: 'http://f:0',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'f:0',\n        hostname: 'f',\n        port: '0',\n        pathname: '/c',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://f:00000000000000/c',\n        base: 'http://example.org/foo/bar',\n        href: 'http://f:0/c',\n        origin: 'http://f:0',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'f:0',\n        hostname: 'f',\n        port: '0',\n        pathname: '/c',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://f:00000000000000000000080/c',\n        base: 'http://example.org/foo/bar',\n        href: 'http://f/c',\n        origin: 'http://f',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'f',\n        hostname: 'f',\n        port: '',\n        pathname: '/c',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://f:b/c',\n        base: 'http://example.org/foo/bar',\n        failure: true,\n      },\n      {\n        input: 'http://f: /c',\n        base: 'http://example.org/foo/bar',\n        failure: true,\n      },\n      {\n        input: 'http://f:\\n/c',\n        base: 'http://example.org/foo/bar',\n        href: 'http://f/c',\n        origin: 'http://f',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'f',\n        hostname: 'f',\n        port: '',\n        pathname: '/c',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://f:fifty-two/c',\n        base: 'http://example.org/foo/bar',\n        failure: true,\n      },\n      {\n        input: 'http://f:999999/c',\n        base: 'http://example.org/foo/bar',\n        failure: true,\n      },\n      {\n        input: 'non-special://f:999999/c',\n        base: 'http://example.org/foo/bar',\n        failure: true,\n      },\n      {\n        input: 'http://f: 21 / b ? d # e ',\n        base: 'http://example.org/foo/bar',\n        failure: true,\n      },\n      {\n        input: '',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/bar',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/bar',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '  \\t',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/bar',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/bar',\n        search: '',\n        hash: '',\n      },\n      {\n        input: ':foo.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/:foo.com/',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/:foo.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: ':foo.com\\\\',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/:foo.com/',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/:foo.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: ':',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/:',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/:',\n        search: '',\n        hash: '',\n      },\n      {\n        input: ':a',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/:a',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/:a',\n        search: '',\n        hash: '',\n      },\n      {\n        input: ':/',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/:/',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/:/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: ':\\\\',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/:/',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/:/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: ':#',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/:#',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/:',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '#',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/bar#',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/bar',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '#/',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/bar#/',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/bar',\n        search: '',\n        hash: '#/',\n      },\n      {\n        input: '#\\\\',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/bar#\\\\',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/bar',\n        search: '',\n        hash: '#\\\\',\n      },\n      {\n        input: '#;?',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/bar#;?',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/bar',\n        search: '',\n        hash: '#;?',\n      },\n      {\n        input: '?',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/bar?',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/bar',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '/',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: ':23',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/:23',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/:23',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '/:23',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/:23',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/:23',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '::',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/::',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/::',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '::23',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/::23',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/::23',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'foo://',\n        base: 'http://example.org/foo/bar',\n        href: 'foo://',\n        origin: 'null',\n        protocol: 'foo:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://a:b@c:29/d',\n        base: 'http://example.org/foo/bar',\n        href: 'http://a:b@c:29/d',\n        origin: 'http://c:29',\n        protocol: 'http:',\n        username: 'a',\n        password: 'b',\n        host: 'c:29',\n        hostname: 'c',\n        port: '29',\n        pathname: '/d',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http::@c:29',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/:@c:29',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/:@c:29',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://&a:foo(b]c@d:2/',\n        base: 'http://example.org/foo/bar',\n        href: 'http://&a:foo(b%5Dc@d:2/',\n        origin: 'http://d:2',\n        protocol: 'http:',\n        username: '&a',\n        password: 'foo(b%5Dc',\n        host: 'd:2',\n        hostname: 'd',\n        port: '2',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://::@c@d:2',\n        base: 'http://example.org/foo/bar',\n        href: 'http://:%3A%40c@d:2/',\n        origin: 'http://d:2',\n        protocol: 'http:',\n        username: '',\n        password: '%3A%40c',\n        host: 'd:2',\n        hostname: 'd',\n        port: '2',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://foo.com:b@d/',\n        base: 'http://example.org/foo/bar',\n        href: 'http://foo.com:b@d/',\n        origin: 'http://d',\n        protocol: 'http:',\n        username: 'foo.com',\n        password: 'b',\n        host: 'd',\n        hostname: 'd',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://foo.com/\\\\@',\n        base: 'http://example.org/foo/bar',\n        href: 'http://foo.com//@',\n        origin: 'http://foo.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'foo.com',\n        hostname: 'foo.com',\n        port: '',\n        pathname: '//@',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http:\\\\\\\\foo.com\\\\',\n        base: 'http://example.org/foo/bar',\n        href: 'http://foo.com/',\n        origin: 'http://foo.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'foo.com',\n        hostname: 'foo.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http:\\\\\\\\a\\\\b:c\\\\d@foo.com\\\\',\n        base: 'http://example.org/foo/bar',\n        href: 'http://a/b:c/d@foo.com/',\n        origin: 'http://a',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'a',\n        hostname: 'a',\n        port: '',\n        pathname: '/b:c/d@foo.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'foo:/',\n        base: 'http://example.org/foo/bar',\n        href: 'foo:/',\n        origin: 'null',\n        protocol: 'foo:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'foo:/bar.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'foo:/bar.com/',\n        origin: 'null',\n        protocol: 'foo:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/bar.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'foo://///////',\n        base: 'http://example.org/foo/bar',\n        href: 'foo://///////',\n        origin: 'null',\n        protocol: 'foo:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '///////',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'foo://///////bar.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'foo://///////bar.com/',\n        origin: 'null',\n        protocol: 'foo:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '///////bar.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'foo:////://///',\n        base: 'http://example.org/foo/bar',\n        href: 'foo:////://///',\n        origin: 'null',\n        protocol: 'foo:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '//://///',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'c:/foo',\n        base: 'http://example.org/foo/bar',\n        href: 'c:/foo',\n        origin: 'null',\n        protocol: 'c:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/foo',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '//foo/bar',\n        base: 'http://example.org/foo/bar',\n        href: 'http://foo/bar',\n        origin: 'http://foo',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'foo',\n        hostname: 'foo',\n        port: '',\n        pathname: '/bar',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://foo/path;a??e#f#g',\n        base: 'http://example.org/foo/bar',\n        href: 'http://foo/path;a??e#f#g',\n        origin: 'http://foo',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'foo',\n        hostname: 'foo',\n        port: '',\n        pathname: '/path;a',\n        search: '??e',\n        hash: '#f#g',\n      },\n      {\n        input: 'http://foo/abcd?efgh?ijkl',\n        base: 'http://example.org/foo/bar',\n        href: 'http://foo/abcd?efgh?ijkl',\n        origin: 'http://foo',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'foo',\n        hostname: 'foo',\n        port: '',\n        pathname: '/abcd',\n        search: '?efgh?ijkl',\n        hash: '',\n      },\n      {\n        input: 'http://foo/abcd#foo?bar',\n        base: 'http://example.org/foo/bar',\n        href: 'http://foo/abcd#foo?bar',\n        origin: 'http://foo',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'foo',\n        hostname: 'foo',\n        port: '',\n        pathname: '/abcd',\n        search: '',\n        hash: '#foo?bar',\n      },\n      {\n        input: '[61:24:74]:98',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/[61:24:74]:98',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/[61:24:74]:98',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http:[61:27]/:foo',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/[61:27]/:foo',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/[61:27]/:foo',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://[1::2]:3:4',\n        base: 'http://example.org/foo/bar',\n        failure: true,\n      },\n      {\n        input: 'http://2001::1',\n        base: 'http://example.org/foo/bar',\n        failure: true,\n      },\n      {\n        input: 'http://2001::1]',\n        base: 'http://example.org/foo/bar',\n        failure: true,\n      },\n      {\n        input: 'http://2001::1]:80',\n        base: 'http://example.org/foo/bar',\n        failure: true,\n      },\n      {\n        input: 'http://[2001::1]',\n        base: 'http://example.org/foo/bar',\n        href: 'http://[2001::1]/',\n        origin: 'http://[2001::1]',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '[2001::1]',\n        hostname: '[2001::1]',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://[::127.0.0.1]',\n        base: 'http://example.org/foo/bar',\n        href: 'http://[::7f00:1]/',\n        origin: 'http://[::7f00:1]',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '[::7f00:1]',\n        hostname: '[::7f00:1]',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://[0:0:0:0:0:0:13.1.68.3]',\n        base: 'http://example.org/foo/bar',\n        href: 'http://[::d01:4403]/',\n        origin: 'http://[::d01:4403]',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '[::d01:4403]',\n        hostname: '[::d01:4403]',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://[2001::1]:80',\n        base: 'http://example.org/foo/bar',\n        href: 'http://[2001::1]/',\n        origin: 'http://[2001::1]',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '[2001::1]',\n        hostname: '[2001::1]',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http:/example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/example.com/',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'ftp:/example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'ftp://example.com/',\n        origin: 'ftp://example.com',\n        protocol: 'ftp:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'https:/example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'https://example.com/',\n        origin: 'https://example.com',\n        protocol: 'https:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'madeupscheme:/example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'madeupscheme:/example.com/',\n        origin: 'null',\n        protocol: 'madeupscheme:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file:/example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'file:///example.com/',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file://example:1/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'file://example:test/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'file://example%/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'file://[example]/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'ftps:/example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'ftps:/example.com/',\n        origin: 'null',\n        protocol: 'ftps:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'gopher:/example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'gopher:/example.com/',\n        protocol: 'gopher:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'ws:/example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'ws://example.com/',\n        origin: 'ws://example.com',\n        protocol: 'ws:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'wss:/example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'wss://example.com/',\n        origin: 'wss://example.com',\n        protocol: 'wss:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'data:/example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'data:/example.com/',\n        origin: 'null',\n        protocol: 'data:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'javascript:/example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'javascript:/example.com/',\n        origin: 'null',\n        protocol: 'javascript:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'mailto:/example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'mailto:/example.com/',\n        origin: 'null',\n        protocol: 'mailto:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http:example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/example.com/',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'ftp:example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'ftp://example.com/',\n        origin: 'ftp://example.com',\n        protocol: 'ftp:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'https:example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'https://example.com/',\n        origin: 'https://example.com',\n        protocol: 'https:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'madeupscheme:example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'madeupscheme:example.com/',\n        origin: 'null',\n        protocol: 'madeupscheme:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'ftps:example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'ftps:example.com/',\n        origin: 'null',\n        protocol: 'ftps:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'gopher:example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'gopher:example.com/',\n        protocol: 'gopher:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'ws:example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'ws://example.com/',\n        origin: 'ws://example.com',\n        protocol: 'ws:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'wss:example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'wss://example.com/',\n        origin: 'wss://example.com',\n        protocol: 'wss:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'data:example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'data:example.com/',\n        origin: 'null',\n        protocol: 'data:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'javascript:example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'javascript:example.com/',\n        origin: 'null',\n        protocol: 'javascript:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'mailto:example.com/',\n        base: 'http://example.org/foo/bar',\n        href: 'mailto:example.com/',\n        origin: 'null',\n        protocol: 'mailto:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '/a/b/c',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/a/b/c',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/a/b/c',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '/a/ /c',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/a/%20/c',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/a/%20/c',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '/a%2fc',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/a%2fc',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/a%2fc',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '/a/%2f/c',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/a/%2f/c',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/a/%2f/c',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '#β',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/bar#%CE%B2',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/bar',\n        search: '',\n        hash: '#%CE%B2',\n      },\n      {\n        input: 'data:text/html,test#test',\n        base: 'http://example.org/foo/bar',\n        href: 'data:text/html,test#test',\n        origin: 'null',\n        protocol: 'data:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'text/html,test',\n        search: '',\n        hash: '#test',\n      },\n      {\n        input: 'tel:1234567890',\n        base: 'http://example.org/foo/bar',\n        href: 'tel:1234567890',\n        origin: 'null',\n        protocol: 'tel:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '1234567890',\n        search: '',\n        hash: '',\n      },\n      '# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/file.html',\n      {\n        input: 'file:c:\\\\foo\\\\bar.html',\n        base: 'file:///tmp/mock/path',\n        href: 'file:///c:/foo/bar.html',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/c:/foo/bar.html',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '  File:c|////foo\\\\bar.html',\n        base: 'file:///tmp/mock/path',\n        href: 'file:///c:////foo/bar.html',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/c:////foo/bar.html',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'C|/foo/bar',\n        base: 'file:///tmp/mock/path',\n        href: 'file:///C:/foo/bar',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/C:/foo/bar',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '/C|\\\\foo\\\\bar',\n        base: 'file:///tmp/mock/path',\n        href: 'file:///C:/foo/bar',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/C:/foo/bar',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '//C|/foo/bar',\n        base: 'file:///tmp/mock/path',\n        href: 'file:///C:/foo/bar',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/C:/foo/bar',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '//server/file',\n        base: 'file:///tmp/mock/path',\n        href: 'file://server/file',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: 'server',\n        hostname: 'server',\n        port: '',\n        pathname: '/file',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '\\\\\\\\server\\\\file',\n        base: 'file:///tmp/mock/path',\n        href: 'file://server/file',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: 'server',\n        hostname: 'server',\n        port: '',\n        pathname: '/file',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '/\\\\server/file',\n        base: 'file:///tmp/mock/path',\n        href: 'file://server/file',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: 'server',\n        hostname: 'server',\n        port: '',\n        pathname: '/file',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file:///foo/bar.txt',\n        base: 'file:///tmp/mock/path',\n        href: 'file:///foo/bar.txt',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/foo/bar.txt',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file:///home/me',\n        base: 'file:///tmp/mock/path',\n        href: 'file:///home/me',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/home/me',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '//',\n        base: 'file:///tmp/mock/path',\n        href: 'file:///',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '///',\n        base: 'file:///tmp/mock/path',\n        href: 'file:///',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '///test',\n        base: 'file:///tmp/mock/path',\n        href: 'file:///test',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/test',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file://test',\n        base: 'file:///tmp/mock/path',\n        href: 'file://test/',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: 'test',\n        hostname: 'test',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file://localhost',\n        base: 'file:///tmp/mock/path',\n        href: 'file:///',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file://localhost/',\n        base: 'file:///tmp/mock/path',\n        href: 'file:///',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file://localhost/test',\n        base: 'file:///tmp/mock/path',\n        href: 'file:///test',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/test',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'test',\n        base: 'file:///tmp/mock/path',\n        href: 'file:///tmp/mock/test',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/tmp/mock/test',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file:test',\n        base: 'file:///tmp/mock/path',\n        href: 'file:///tmp/mock/test',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/tmp/mock/test',\n        search: '',\n        hash: '',\n      },\n      '# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/path.js',\n      {\n        input: 'http://example.com/././foo',\n        base: 'about:blank',\n        href: 'http://example.com/foo',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foo',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/./.foo',\n        base: 'about:blank',\n        href: 'http://example.com/.foo',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/.foo',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo/.',\n        base: 'about:blank',\n        href: 'http://example.com/foo/',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foo/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo/./',\n        base: 'about:blank',\n        href: 'http://example.com/foo/',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foo/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo/bar/..',\n        base: 'about:blank',\n        href: 'http://example.com/foo/',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foo/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo/bar/../',\n        base: 'about:blank',\n        href: 'http://example.com/foo/',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foo/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo/..bar',\n        base: 'about:blank',\n        href: 'http://example.com/foo/..bar',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foo/..bar',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo/bar/../ton',\n        base: 'about:blank',\n        href: 'http://example.com/foo/ton',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foo/ton',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo/bar/../ton/../../a',\n        base: 'about:blank',\n        href: 'http://example.com/a',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/a',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo/../../..',\n        base: 'about:blank',\n        href: 'http://example.com/',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo/../../../ton',\n        base: 'about:blank',\n        href: 'http://example.com/ton',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/ton',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo/%2e',\n        base: 'about:blank',\n        href: 'http://example.com/foo/',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foo/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo/%2e%2',\n        base: 'about:blank',\n        href: 'http://example.com/foo/%2e%2',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foo/%2e%2',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar',\n        base: 'about:blank',\n        href: 'http://example.com/%2e.bar',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/%2e.bar',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com////../..',\n        base: 'about:blank',\n        href: 'http://example.com//',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '//',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo/bar//../..',\n        base: 'about:blank',\n        href: 'http://example.com/foo/',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foo/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo/bar//..',\n        base: 'about:blank',\n        href: 'http://example.com/foo/bar/',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foo/bar/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo',\n        base: 'about:blank',\n        href: 'http://example.com/foo',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foo',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/%20foo',\n        base: 'about:blank',\n        href: 'http://example.com/%20foo',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/%20foo',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo%',\n        base: 'about:blank',\n        href: 'http://example.com/foo%',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foo%',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo%2',\n        base: 'about:blank',\n        href: 'http://example.com/foo%2',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foo%2',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo%2zbar',\n        base: 'about:blank',\n        href: 'http://example.com/foo%2zbar',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foo%2zbar',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo%2Â©zbar',\n        base: 'about:blank',\n        href: 'http://example.com/foo%2%C3%82%C2%A9zbar',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foo%2%C3%82%C2%A9zbar',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo%41%7a',\n        base: 'about:blank',\n        href: 'http://example.com/foo%41%7a',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foo%41%7a',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo\\t\\u0091%91',\n        base: 'about:blank',\n        href: 'http://example.com/foo%C2%91%91',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foo%C2%91%91',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo%00%51',\n        base: 'about:blank',\n        href: 'http://example.com/foo%00%51',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foo%00%51',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/(%28:%3A%29)',\n        base: 'about:blank',\n        href: 'http://example.com/(%28:%3A%29)',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/(%28:%3A%29)',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/%3A%3a%3C%3c',\n        base: 'about:blank',\n        href: 'http://example.com/%3A%3a%3C%3c',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/%3A%3a%3C%3c',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/foo\\tbar',\n        base: 'about:blank',\n        href: 'http://example.com/foobar',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/foobar',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com\\\\\\\\foo\\\\\\\\bar',\n        base: 'about:blank',\n        href: 'http://example.com//foo//bar',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '//foo//bar',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd',\n        base: 'about:blank',\n        href: 'http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/%7Ffp3%3Eju%3Dduvgw%3Dd',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/@asdf%40',\n        base: 'about:blank',\n        href: 'http://example.com/@asdf%40',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/@asdf%40',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/你好你好',\n        base: 'about:blank',\n        href: 'http://example.com/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/‥/foo',\n        base: 'about:blank',\n        href: 'http://example.com/%E2%80%A5/foo',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/%E2%80%A5/foo',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/﻿/foo',\n        base: 'about:blank',\n        href: 'http://example.com/%EF%BB%BF/foo',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/%EF%BB%BF/foo',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.com/‮/foo/‭/bar',\n        base: 'about:blank',\n        href: 'http://example.com/%E2%80%AE/foo/%E2%80%AD/bar',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/%E2%80%AE/foo/%E2%80%AD/bar',\n        search: '',\n        hash: '',\n      },\n      '# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/relative.js',\n      {\n        input: 'http://www.google.com/foo?bar=baz#',\n        base: 'about:blank',\n        href: 'http://www.google.com/foo?bar=baz#',\n        origin: 'http://www.google.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.google.com',\n        hostname: 'www.google.com',\n        port: '',\n        pathname: '/foo',\n        search: '?bar=baz',\n        hash: '',\n      },\n      {\n        input: 'http://www.google.com/foo?bar=baz# »',\n        base: 'about:blank',\n        href: 'http://www.google.com/foo?bar=baz#%20%C2%BB',\n        origin: 'http://www.google.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.google.com',\n        hostname: 'www.google.com',\n        port: '',\n        pathname: '/foo',\n        search: '?bar=baz',\n        hash: '#%20%C2%BB',\n      },\n      {\n        input: 'data:test# »',\n        base: 'about:blank',\n        href: 'data:test#%20%C2%BB',\n        origin: 'null',\n        protocol: 'data:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'test',\n        search: '',\n        hash: '#%20%C2%BB',\n      },\n      {\n        input: 'http://www.google.com',\n        base: 'about:blank',\n        href: 'http://www.google.com/',\n        origin: 'http://www.google.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.google.com',\n        hostname: 'www.google.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://192.0x00A80001',\n        base: 'about:blank',\n        href: 'http://192.168.0.1/',\n        origin: 'http://192.168.0.1',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '192.168.0.1',\n        hostname: '192.168.0.1',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://www/foo%2Ehtml',\n        base: 'about:blank',\n        href: 'http://www/foo%2Ehtml',\n        origin: 'http://www',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www',\n        hostname: 'www',\n        port: '',\n        pathname: '/foo%2Ehtml',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://www/foo/%2E/html',\n        base: 'about:blank',\n        href: 'http://www/foo/html',\n        origin: 'http://www',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www',\n        hostname: 'www',\n        port: '',\n        pathname: '/foo/html',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://user:pass@/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'http://%25DOMAIN:foobar@foodomain.com/',\n        base: 'about:blank',\n        href: 'http://%25DOMAIN:foobar@foodomain.com/',\n        origin: 'http://foodomain.com',\n        protocol: 'http:',\n        username: '%25DOMAIN',\n        password: 'foobar',\n        host: 'foodomain.com',\n        hostname: 'foodomain.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http:\\\\\\\\www.google.com\\\\foo',\n        base: 'about:blank',\n        href: 'http://www.google.com/foo',\n        origin: 'http://www.google.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.google.com',\n        hostname: 'www.google.com',\n        port: '',\n        pathname: '/foo',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://foo:80/',\n        base: 'about:blank',\n        href: 'http://foo/',\n        origin: 'http://foo',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'foo',\n        hostname: 'foo',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://foo:81/',\n        base: 'about:blank',\n        href: 'http://foo:81/',\n        origin: 'http://foo:81',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'foo:81',\n        hostname: 'foo',\n        port: '81',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'httpa://foo:80/',\n        base: 'about:blank',\n        href: 'httpa://foo:80/',\n        origin: 'null',\n        protocol: 'httpa:',\n        username: '',\n        password: '',\n        host: 'foo:80',\n        hostname: 'foo',\n        port: '80',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://foo:-80/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'https://foo:443/',\n        base: 'about:blank',\n        href: 'https://foo/',\n        origin: 'https://foo',\n        protocol: 'https:',\n        username: '',\n        password: '',\n        host: 'foo',\n        hostname: 'foo',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'https://foo:80/',\n        base: 'about:blank',\n        href: 'https://foo:80/',\n        origin: 'https://foo:80',\n        protocol: 'https:',\n        username: '',\n        password: '',\n        host: 'foo:80',\n        hostname: 'foo',\n        port: '80',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'ftp://foo:21/',\n        base: 'about:blank',\n        href: 'ftp://foo/',\n        origin: 'ftp://foo',\n        protocol: 'ftp:',\n        username: '',\n        password: '',\n        host: 'foo',\n        hostname: 'foo',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'ftp://foo:80/',\n        base: 'about:blank',\n        href: 'ftp://foo:80/',\n        origin: 'ftp://foo:80',\n        protocol: 'ftp:',\n        username: '',\n        password: '',\n        host: 'foo:80',\n        hostname: 'foo',\n        port: '80',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'gopher://foo:70/',\n        base: 'about:blank',\n        href: 'gopher://foo:70/',\n        protocol: 'gopher:',\n        username: '',\n        password: '',\n        host: 'foo:70',\n        hostname: 'foo',\n        port: '70',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'gopher://foo:443/',\n        base: 'about:blank',\n        href: 'gopher://foo:443/',\n        protocol: 'gopher:',\n        username: '',\n        password: '',\n        host: 'foo:443',\n        hostname: 'foo',\n        port: '443',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'ws://foo:80/',\n        base: 'about:blank',\n        href: 'ws://foo/',\n        origin: 'ws://foo',\n        protocol: 'ws:',\n        username: '',\n        password: '',\n        host: 'foo',\n        hostname: 'foo',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'ws://foo:81/',\n        base: 'about:blank',\n        href: 'ws://foo:81/',\n        origin: 'ws://foo:81',\n        protocol: 'ws:',\n        username: '',\n        password: '',\n        host: 'foo:81',\n        hostname: 'foo',\n        port: '81',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'ws://foo:443/',\n        base: 'about:blank',\n        href: 'ws://foo:443/',\n        origin: 'ws://foo:443',\n        protocol: 'ws:',\n        username: '',\n        password: '',\n        host: 'foo:443',\n        hostname: 'foo',\n        port: '443',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'ws://foo:815/',\n        base: 'about:blank',\n        href: 'ws://foo:815/',\n        origin: 'ws://foo:815',\n        protocol: 'ws:',\n        username: '',\n        password: '',\n        host: 'foo:815',\n        hostname: 'foo',\n        port: '815',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'wss://foo:80/',\n        base: 'about:blank',\n        href: 'wss://foo:80/',\n        origin: 'wss://foo:80',\n        protocol: 'wss:',\n        username: '',\n        password: '',\n        host: 'foo:80',\n        hostname: 'foo',\n        port: '80',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'wss://foo:81/',\n        base: 'about:blank',\n        href: 'wss://foo:81/',\n        origin: 'wss://foo:81',\n        protocol: 'wss:',\n        username: '',\n        password: '',\n        host: 'foo:81',\n        hostname: 'foo',\n        port: '81',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'wss://foo:443/',\n        base: 'about:blank',\n        href: 'wss://foo/',\n        origin: 'wss://foo',\n        protocol: 'wss:',\n        username: '',\n        password: '',\n        host: 'foo',\n        hostname: 'foo',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'wss://foo:815/',\n        base: 'about:blank',\n        href: 'wss://foo:815/',\n        origin: 'wss://foo:815',\n        protocol: 'wss:',\n        username: '',\n        password: '',\n        host: 'foo:815',\n        hostname: 'foo',\n        port: '815',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http:/example.com/',\n        base: 'about:blank',\n        href: 'http://example.com/',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'ftp:/example.com/',\n        base: 'about:blank',\n        href: 'ftp://example.com/',\n        origin: 'ftp://example.com',\n        protocol: 'ftp:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'https:/example.com/',\n        base: 'about:blank',\n        href: 'https://example.com/',\n        origin: 'https://example.com',\n        protocol: 'https:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'madeupscheme:/example.com/',\n        base: 'about:blank',\n        href: 'madeupscheme:/example.com/',\n        origin: 'null',\n        protocol: 'madeupscheme:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file:/example.com/',\n        base: 'about:blank',\n        href: 'file:///example.com/',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'ftps:/example.com/',\n        base: 'about:blank',\n        href: 'ftps:/example.com/',\n        origin: 'null',\n        protocol: 'ftps:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'gopher:/example.com/',\n        base: 'about:blank',\n        href: 'gopher:/example.com/',\n        protocol: 'gopher:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'ws:/example.com/',\n        base: 'about:blank',\n        href: 'ws://example.com/',\n        origin: 'ws://example.com',\n        protocol: 'ws:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'wss:/example.com/',\n        base: 'about:blank',\n        href: 'wss://example.com/',\n        origin: 'wss://example.com',\n        protocol: 'wss:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'data:/example.com/',\n        base: 'about:blank',\n        href: 'data:/example.com/',\n        origin: 'null',\n        protocol: 'data:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'javascript:/example.com/',\n        base: 'about:blank',\n        href: 'javascript:/example.com/',\n        origin: 'null',\n        protocol: 'javascript:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'mailto:/example.com/',\n        base: 'about:blank',\n        href: 'mailto:/example.com/',\n        origin: 'null',\n        protocol: 'mailto:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http:example.com/',\n        base: 'about:blank',\n        href: 'http://example.com/',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'ftp:example.com/',\n        base: 'about:blank',\n        href: 'ftp://example.com/',\n        origin: 'ftp://example.com',\n        protocol: 'ftp:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'https:example.com/',\n        base: 'about:blank',\n        href: 'https://example.com/',\n        origin: 'https://example.com',\n        protocol: 'https:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'madeupscheme:example.com/',\n        base: 'about:blank',\n        href: 'madeupscheme:example.com/',\n        origin: 'null',\n        protocol: 'madeupscheme:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'ftps:example.com/',\n        base: 'about:blank',\n        href: 'ftps:example.com/',\n        origin: 'null',\n        protocol: 'ftps:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'gopher:example.com/',\n        base: 'about:blank',\n        href: 'gopher:example.com/',\n        protocol: 'gopher:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'ws:example.com/',\n        base: 'about:blank',\n        href: 'ws://example.com/',\n        origin: 'ws://example.com',\n        protocol: 'ws:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'wss:example.com/',\n        base: 'about:blank',\n        href: 'wss://example.com/',\n        origin: 'wss://example.com',\n        protocol: 'wss:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'data:example.com/',\n        base: 'about:blank',\n        href: 'data:example.com/',\n        origin: 'null',\n        protocol: 'data:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'javascript:example.com/',\n        base: 'about:blank',\n        href: 'javascript:example.com/',\n        origin: 'null',\n        protocol: 'javascript:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'example.com/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'mailto:example.com/',\n        base: 'about:blank',\n        href: 'mailto:example.com/',\n        origin: 'null',\n        protocol: 'mailto:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'example.com/',\n        search: '',\n        hash: '',\n      },\n      '# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/segments-userinfo-vs-host.html',\n      {\n        input: 'http:@www.example.com',\n        base: 'about:blank',\n        href: 'http://www.example.com/',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http:/@www.example.com',\n        base: 'about:blank',\n        href: 'http://www.example.com/',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://@www.example.com',\n        base: 'about:blank',\n        href: 'http://www.example.com/',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http:a:b@www.example.com',\n        base: 'about:blank',\n        href: 'http://a:b@www.example.com/',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: 'a',\n        password: 'b',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http:/a:b@www.example.com',\n        base: 'about:blank',\n        href: 'http://a:b@www.example.com/',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: 'a',\n        password: 'b',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://a:b@www.example.com',\n        base: 'about:blank',\n        href: 'http://a:b@www.example.com/',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: 'a',\n        password: 'b',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://@pple.com',\n        base: 'about:blank',\n        href: 'http://pple.com/',\n        origin: 'http://pple.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'pple.com',\n        hostname: 'pple.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http::b@www.example.com',\n        base: 'about:blank',\n        href: 'http://:b@www.example.com/',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: '',\n        password: 'b',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http:/:b@www.example.com',\n        base: 'about:blank',\n        href: 'http://:b@www.example.com/',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: '',\n        password: 'b',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://:b@www.example.com',\n        base: 'about:blank',\n        href: 'http://:b@www.example.com/',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: '',\n        password: 'b',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http:/:@/www.example.com',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'http://user@/www.example.com',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'http:@/www.example.com',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'http:/@/www.example.com',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'http://@/www.example.com',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'https:@/www.example.com',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'http:a:b@/www.example.com',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'http:/a:b@/www.example.com',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'http://a:b@/www.example.com',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'http::@/www.example.com',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'http:a:@www.example.com',\n        base: 'about:blank',\n        href: 'http://a@www.example.com/',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: 'a',\n        password: '',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http:/a:@www.example.com',\n        base: 'about:blank',\n        href: 'http://a@www.example.com/',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: 'a',\n        password: '',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://a:@www.example.com',\n        base: 'about:blank',\n        href: 'http://a@www.example.com/',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: 'a',\n        password: '',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://www.@pple.com',\n        base: 'about:blank',\n        href: 'http://www.@pple.com/',\n        origin: 'http://pple.com',\n        protocol: 'http:',\n        username: 'www.',\n        password: '',\n        host: 'pple.com',\n        hostname: 'pple.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http:@:www.example.com',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'http:/@:www.example.com',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'http://@:www.example.com',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'http://:@www.example.com',\n        base: 'about:blank',\n        href: 'http://www.example.com/',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      '# Others',\n      {\n        input: '/',\n        base: 'http://www.example.com/test',\n        href: 'http://www.example.com/',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '/test.txt',\n        base: 'http://www.example.com/test',\n        href: 'http://www.example.com/test.txt',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/test.txt',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '.',\n        base: 'http://www.example.com/test',\n        href: 'http://www.example.com/',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '..',\n        base: 'http://www.example.com/test',\n        href: 'http://www.example.com/',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'test.txt',\n        base: 'http://www.example.com/test',\n        href: 'http://www.example.com/test.txt',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/test.txt',\n        search: '',\n        hash: '',\n      },\n      {\n        input: './test.txt',\n        base: 'http://www.example.com/test',\n        href: 'http://www.example.com/test.txt',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/test.txt',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '../test.txt',\n        base: 'http://www.example.com/test',\n        href: 'http://www.example.com/test.txt',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/test.txt',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '../aaa/test.txt',\n        base: 'http://www.example.com/test',\n        href: 'http://www.example.com/aaa/test.txt',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/aaa/test.txt',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '../../test.txt',\n        base: 'http://www.example.com/test',\n        href: 'http://www.example.com/test.txt',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/test.txt',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '中/test.txt',\n        base: 'http://www.example.com/test',\n        href: 'http://www.example.com/%E4%B8%AD/test.txt',\n        origin: 'http://www.example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.example.com',\n        hostname: 'www.example.com',\n        port: '',\n        pathname: '/%E4%B8%AD/test.txt',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://www.example2.com',\n        base: 'http://www.example.com/test',\n        href: 'http://www.example2.com/',\n        origin: 'http://www.example2.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.example2.com',\n        hostname: 'www.example2.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '//www.example2.com',\n        base: 'http://www.example.com/test',\n        href: 'http://www.example2.com/',\n        origin: 'http://www.example2.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.example2.com',\n        hostname: 'www.example2.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file:...',\n        base: 'http://www.example.com/test',\n        href: 'file:///...',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/...',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file:..',\n        base: 'http://www.example.com/test',\n        href: 'file:///',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file:a',\n        base: 'http://www.example.com/test',\n        href: 'file:///a',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/a',\n        search: '',\n        hash: '',\n      },\n      '# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/host.html',\n      'Basic canonicalization, uppercase should be converted to lowercase',\n      {\n        input: 'http://ExAmPlE.CoM',\n        base: 'http://other.com/',\n        href: 'http://example.com/',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example example.com',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      {\n        input: 'http://Goo%20 goo%7C|.com',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      {\n        input: 'http://[]',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      {\n        input: 'http://[:]',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      'U+3000 is mapped to U+0020 (space) which is disallowed',\n      {\n        input: 'http://GOO\\u00a0\\u3000goo.com',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      'Other types of space (no-break, zero-width, zero-width-no-break) are name-prepped away to nothing. U+200B, U+2060, and U+FEFF, are ignored',\n      {\n        input: 'http://GOO\\u200b\\u2060\\ufeffgoo.com',\n        base: 'http://other.com/',\n        href: 'http://googoo.com/',\n        origin: 'http://googoo.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'googoo.com',\n        hostname: 'googoo.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      'Leading and trailing C0 control or space',\n      {\n        input: '\\u0000\\u001b\\u0004\\u0012 http://example.com/\\u001f \\u000d ',\n        base: 'about:blank',\n        href: 'http://example.com/',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      'Ideographic full stop (full-width period for Chinese, etc.) should be treated as a dot. U+3002 is mapped to U+002E (dot)',\n      {\n        input: 'http://www.foo。bar.com',\n        base: 'http://other.com/',\n        href: 'http://www.foo.bar.com/',\n        origin: 'http://www.foo.bar.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'www.foo.bar.com',\n        hostname: 'www.foo.bar.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      'Invalid Unicode characters should fail... U+FDD0 is disallowed; %ef%b7%90 is U+FDD0',\n      {\n        input: 'http://\\ufdd0zyx.com',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      'This is the same as previous but escaped',\n      {\n        input: 'http://%ef%b7%90zyx.com',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      'U+FFFD',\n      {\n        input: 'https://\\ufffd',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'https://%EF%BF%BD',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'https://x/\\ufffd?\\ufffd#\\ufffd',\n        base: 'about:blank',\n        href: 'https://x/%EF%BF%BD?%EF%BF%BD#%EF%BF%BD',\n        origin: 'https://x',\n        protocol: 'https:',\n        username: '',\n        password: '',\n        host: 'x',\n        hostname: 'x',\n        port: '',\n        pathname: '/%EF%BF%BD',\n        search: '?%EF%BF%BD',\n        hash: '#%EF%BF%BD',\n      },\n      \"Test name prepping, fullwidth input should be converted to ASCII and NOT IDN-ized. This is 'Go' in fullwidth UTF-8/UTF-16.\",\n      {\n        input: 'http://Ｇｏ.com',\n        base: 'http://other.com/',\n        href: 'http://go.com/',\n        origin: 'http://go.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'go.com',\n        hostname: 'go.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      'URL spec forbids the following. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24257',\n      {\n        input: 'http://％４１.com',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      {\n        input: 'http://%ef%bc%85%ef%bc%94%ef%bc%91.com',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      '...%00 in fullwidth should fail (also as escaped UTF-8 input)',\n      {\n        input: 'http://％００.com',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      {\n        input: 'http://%ef%bc%85%ef%bc%90%ef%bc%90.com',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      'Basic IDN support, UTF-8 and UTF-16 input should be converted to IDN',\n      {\n        input: 'http://你好你好',\n        base: 'http://other.com/',\n        href: 'http://xn--6qqa088eba/',\n        origin: 'http://xn--6qqa088eba',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'xn--6qqa088eba',\n        hostname: 'xn--6qqa088eba',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'https://faß.ExAmPlE/',\n        base: 'about:blank',\n        href: 'https://xn--fa-hia.example/',\n        origin: 'https://xn--fa-hia.example',\n        protocol: 'https:',\n        username: '',\n        password: '',\n        host: 'xn--fa-hia.example',\n        hostname: 'xn--fa-hia.example',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'sc://faß.ExAmPlE/',\n        base: 'about:blank',\n        href: 'sc://fa%C3%9F.ExAmPlE/',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: 'fa%C3%9F.ExAmPlE',\n        hostname: 'fa%C3%9F.ExAmPlE',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      'Invalid escaped characters should fail and the percents should be escaped. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24191',\n      {\n        input: 'http://%zz%66%a.com',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      'If we get an invalid character that has been escaped.',\n      {\n        input: 'http://%25',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      {\n        input: 'http://hello%00',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      'Escaped numbers should be treated like IP addresses if they are.',\n      {\n        input: 'http://%30%78%63%30%2e%30%32%35%30.01',\n        base: 'http://other.com/',\n        href: 'http://192.168.0.1/',\n        origin: 'http://192.168.0.1',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '192.168.0.1',\n        hostname: '192.168.0.1',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://%30%78%63%30%2e%30%32%35%30.01%2e',\n        base: 'http://other.com/',\n        href: 'http://192.168.0.1/',\n        origin: 'http://192.168.0.1',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '192.168.0.1',\n        hostname: '192.168.0.1',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://192.168.0.257',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      'Invalid escaping in hosts causes failure',\n      {\n        input: 'http://%3g%78%63%30%2e%30%32%35%30%2E.01',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      'A space in a host causes failure',\n      {\n        input: 'http://192.168.0.1 hello',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      {\n        input: 'https://x x:12',\n        base: 'about:blank',\n        failure: true,\n      },\n      'Fullwidth and escaped UTF-8 fullwidth should still be treated as IP',\n      {\n        input: 'http://０Ｘｃ０．０２５０．０１',\n        base: 'http://other.com/',\n        href: 'http://192.168.0.1/',\n        origin: 'http://192.168.0.1',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '192.168.0.1',\n        hostname: '192.168.0.1',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      'Domains with empty labels',\n      {\n        input: 'http://./',\n        base: 'about:blank',\n        href: 'http://./',\n        origin: 'http://.',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '.',\n        hostname: '.',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://../',\n        base: 'about:blank',\n        href: 'http://../',\n        origin: 'http://..',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '..',\n        hostname: '..',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      'Broken IPv6',\n      {\n        input: 'http://[www.google.com]/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'http://[google.com]',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      {\n        input: 'http://[::1.2.3.4x]',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      {\n        input: 'http://[::1.2.3.]',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      {\n        input: 'http://[::1.2.]',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      {\n        input: 'http://[::1.]',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      'Misc Unicode',\n      {\n        input: 'http://foo:💩@example.com/bar',\n        base: 'http://other.com/',\n        href: 'http://foo:%F0%9F%92%A9@example.com/bar',\n        origin: 'http://example.com',\n        protocol: 'http:',\n        username: 'foo',\n        password: '%F0%9F%92%A9',\n        host: 'example.com',\n        hostname: 'example.com',\n        port: '',\n        pathname: '/bar',\n        search: '',\n        hash: '',\n      },\n      '# resolving a fragment against any scheme succeeds',\n      {\n        input: '#',\n        base: 'test:test',\n        href: 'test:test#',\n        origin: 'null',\n        protocol: 'test:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'test',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '#x',\n        base: 'mailto:x@x.com',\n        href: 'mailto:x@x.com#x',\n        origin: 'null',\n        protocol: 'mailto:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'x@x.com',\n        search: '',\n        hash: '#x',\n      },\n      {\n        input: '#x',\n        base: 'data:,',\n        href: 'data:,#x',\n        origin: 'null',\n        protocol: 'data:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: ',',\n        search: '',\n        hash: '#x',\n      },\n      {\n        input: '#x',\n        base: 'about:blank',\n        href: 'about:blank#x',\n        origin: 'null',\n        protocol: 'about:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'blank',\n        search: '',\n        hash: '#x',\n      },\n      {\n        input: '#',\n        base: 'test:test?test',\n        href: 'test:test?test#',\n        origin: 'null',\n        protocol: 'test:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'test',\n        search: '?test',\n        hash: '',\n      },\n      '# multiple @ in authority state',\n      {\n        input: 'https://@test@test@example:800/',\n        base: 'http://doesnotmatter/',\n        href: 'https://%40test%40test@example:800/',\n        origin: 'https://example:800',\n        protocol: 'https:',\n        username: '%40test%40test',\n        password: '',\n        host: 'example:800',\n        hostname: 'example',\n        port: '800',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'https://@@@example',\n        base: 'http://doesnotmatter/',\n        href: 'https://%40%40@example/',\n        origin: 'https://example',\n        protocol: 'https:',\n        username: '%40%40',\n        password: '',\n        host: 'example',\n        hostname: 'example',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      'non-az-09 characters',\n      {\n        input: 'http://`{}:`{}@h/`{}?`{}',\n        base: 'http://doesnotmatter/',\n        href: 'http://%60%7B%7D:%60%7B%7D@h/%60%7B%7D?`{}',\n        origin: 'http://h',\n        protocol: 'http:',\n        username: '%60%7B%7D',\n        password: '%60%7B%7D',\n        host: 'h',\n        hostname: 'h',\n        port: '',\n        pathname: '/%60%7B%7D',\n        search: '?`{}',\n        hash: '',\n      },\n      '# Credentials in base',\n      {\n        input: '/some/path',\n        base: 'http://user@example.org/smth',\n        href: 'http://user@example.org/some/path',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: 'user',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/some/path',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '',\n        base: 'http://user:pass@example.org:21/smth',\n        href: 'http://user:pass@example.org:21/smth',\n        origin: 'http://example.org:21',\n        protocol: 'http:',\n        username: 'user',\n        password: 'pass',\n        host: 'example.org:21',\n        hostname: 'example.org',\n        port: '21',\n        pathname: '/smth',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '/some/path',\n        base: 'http://user:pass@example.org:21/smth',\n        href: 'http://user:pass@example.org:21/some/path',\n        origin: 'http://example.org:21',\n        protocol: 'http:',\n        username: 'user',\n        password: 'pass',\n        host: 'example.org:21',\n        hostname: 'example.org',\n        port: '21',\n        pathname: '/some/path',\n        search: '',\n        hash: '',\n      },\n      '# a set of tests designed by zcorpan for relative URLs with unknown schemes',\n      {\n        input: 'i',\n        base: 'sc:sd',\n        failure: true,\n      },\n      {\n        input: 'i',\n        base: 'sc:sd/sd',\n        failure: true,\n      },\n      {\n        input: 'i',\n        base: 'sc:/pa/pa',\n        href: 'sc:/pa/i',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/pa/i',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'i',\n        base: 'sc://ho/pa',\n        href: 'sc://ho/i',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: 'ho',\n        hostname: 'ho',\n        port: '',\n        pathname: '/i',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'i',\n        base: 'sc:///pa/pa',\n        href: 'sc:///pa/i',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/pa/i',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '../i',\n        base: 'sc:sd',\n        failure: true,\n      },\n      {\n        input: '../i',\n        base: 'sc:sd/sd',\n        failure: true,\n      },\n      {\n        input: '../i',\n        base: 'sc:/pa/pa',\n        href: 'sc:/i',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/i',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '../i',\n        base: 'sc://ho/pa',\n        href: 'sc://ho/i',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: 'ho',\n        hostname: 'ho',\n        port: '',\n        pathname: '/i',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '../i',\n        base: 'sc:///pa/pa',\n        href: 'sc:///i',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/i',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '/i',\n        base: 'sc:sd',\n        failure: true,\n      },\n      {\n        input: '/i',\n        base: 'sc:sd/sd',\n        failure: true,\n      },\n      {\n        input: '/i',\n        base: 'sc:/pa/pa',\n        href: 'sc:/i',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/i',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '/i',\n        base: 'sc://ho/pa',\n        href: 'sc://ho/i',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: 'ho',\n        hostname: 'ho',\n        port: '',\n        pathname: '/i',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '/i',\n        base: 'sc:///pa/pa',\n        href: 'sc:///i',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/i',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '?i',\n        base: 'sc:sd',\n        failure: true,\n      },\n      {\n        input: '?i',\n        base: 'sc:sd/sd',\n        failure: true,\n      },\n      {\n        input: '?i',\n        base: 'sc:/pa/pa',\n        href: 'sc:/pa/pa?i',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/pa/pa',\n        search: '?i',\n        hash: '',\n      },\n      {\n        input: '?i',\n        base: 'sc://ho/pa',\n        href: 'sc://ho/pa?i',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: 'ho',\n        hostname: 'ho',\n        port: '',\n        pathname: '/pa',\n        search: '?i',\n        hash: '',\n      },\n      {\n        input: '?i',\n        base: 'sc:///pa/pa',\n        href: 'sc:///pa/pa?i',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/pa/pa',\n        search: '?i',\n        hash: '',\n      },\n      {\n        input: '#i',\n        base: 'sc:sd',\n        href: 'sc:sd#i',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'sd',\n        search: '',\n        hash: '#i',\n      },\n      {\n        input: '#i',\n        base: 'sc:sd/sd',\n        href: 'sc:sd/sd#i',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'sd/sd',\n        search: '',\n        hash: '#i',\n      },\n      {\n        input: '#i',\n        base: 'sc:/pa/pa',\n        href: 'sc:/pa/pa#i',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/pa/pa',\n        search: '',\n        hash: '#i',\n      },\n      {\n        input: '#i',\n        base: 'sc://ho/pa',\n        href: 'sc://ho/pa#i',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: 'ho',\n        hostname: 'ho',\n        port: '',\n        pathname: '/pa',\n        search: '',\n        hash: '#i',\n      },\n      {\n        input: '#i',\n        base: 'sc:///pa/pa',\n        href: 'sc:///pa/pa#i',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/pa/pa',\n        search: '',\n        hash: '#i',\n      },\n      '# make sure that relative URL logic works on known typically non-relative schemes too',\n      {\n        input: 'about:/../',\n        base: 'about:blank',\n        href: 'about:/',\n        origin: 'null',\n        protocol: 'about:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'data:/../',\n        base: 'about:blank',\n        href: 'data:/',\n        origin: 'null',\n        protocol: 'data:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'javascript:/../',\n        base: 'about:blank',\n        href: 'javascript:/',\n        origin: 'null',\n        protocol: 'javascript:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'mailto:/../',\n        base: 'about:blank',\n        href: 'mailto:/',\n        origin: 'null',\n        protocol: 'mailto:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      '# unknown schemes and their hosts',\n      {\n        input: 'sc://ñ.test/',\n        base: 'about:blank',\n        href: 'sc://%C3%B1.test/',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '%C3%B1.test',\n        hostname: '%C3%B1.test',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'sc://\\u0000/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'sc:// /',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'sc://%/',\n        base: 'about:blank',\n        href: 'sc://%/',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '%',\n        hostname: '%',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'sc://@/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'sc://te@s:t@/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'sc://:/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'sc://:12/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'sc://[/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'sc://\\\\/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'sc://]/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'x',\n        base: 'sc://ñ',\n        href: 'sc://%C3%B1/x',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '%C3%B1',\n        hostname: '%C3%B1',\n        port: '',\n        pathname: '/x',\n        search: '',\n        hash: '',\n      },\n      '# unknown schemes and backslashes',\n      {\n        input: 'sc:\\\\../',\n        base: 'about:blank',\n        href: 'sc:\\\\../',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '\\\\../',\n        search: '',\n        hash: '',\n      },\n      '# unknown scheme with path looking like a password',\n      {\n        input: 'sc::a@example.net',\n        base: 'about:blank',\n        href: 'sc::a@example.net',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: ':a@example.net',\n        search: '',\n        hash: '',\n      },\n      '# unknown scheme with bogus percent-encoding',\n      {\n        input: 'wow:%NBD',\n        base: 'about:blank',\n        href: 'wow:%NBD',\n        origin: 'null',\n        protocol: 'wow:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '%NBD',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'wow:%1G',\n        base: 'about:blank',\n        href: 'wow:%1G',\n        origin: 'null',\n        protocol: 'wow:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '%1G',\n        search: '',\n        hash: '',\n      },\n      '# Hosts and percent-encoding',\n      {\n        input: 'ftp://example.com%80/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'ftp://example.com%A0/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'https://example.com%80/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'https://example.com%A0/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'ftp://%e2%98%83',\n        base: 'about:blank',\n        href: 'ftp://xn--n3h/',\n        origin: 'ftp://xn--n3h',\n        protocol: 'ftp:',\n        username: '',\n        password: '',\n        host: 'xn--n3h',\n        hostname: 'xn--n3h',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'https://%e2%98%83',\n        base: 'about:blank',\n        href: 'https://xn--n3h/',\n        origin: 'https://xn--n3h',\n        protocol: 'https:',\n        username: '',\n        password: '',\n        host: 'xn--n3h',\n        hostname: 'xn--n3h',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      '# tests from jsdom/whatwg-url designed for code coverage',\n      {\n        input: 'http://127.0.0.1:10100/relative_import.html',\n        base: 'about:blank',\n        href: 'http://127.0.0.1:10100/relative_import.html',\n        origin: 'http://127.0.0.1:10100',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '127.0.0.1:10100',\n        hostname: '127.0.0.1',\n        port: '10100',\n        pathname: '/relative_import.html',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://facebook.com/?foo=%7B%22abc%22',\n        base: 'about:blank',\n        href: 'http://facebook.com/?foo=%7B%22abc%22',\n        origin: 'http://facebook.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'facebook.com',\n        hostname: 'facebook.com',\n        port: '',\n        pathname: '/',\n        search: '?foo=%7B%22abc%22',\n        hash: '',\n      },\n      {\n        input: 'https://localhost:3000/jqueryui@1.2.3',\n        base: 'about:blank',\n        href: 'https://localhost:3000/jqueryui@1.2.3',\n        origin: 'https://localhost:3000',\n        protocol: 'https:',\n        username: '',\n        password: '',\n        host: 'localhost:3000',\n        hostname: 'localhost',\n        port: '3000',\n        pathname: '/jqueryui@1.2.3',\n        search: '',\n        hash: '',\n      },\n      '# tab/LF/CR',\n      {\n        input:\n          'h\\tt\\nt\\rp://h\\to\\ns\\rt:9\\t0\\n0\\r0/p\\ta\\nt\\rh?q\\tu\\ne\\rry#f\\tr\\na\\rg',\n        base: 'about:blank',\n        href: 'http://host:9000/path?query#frag',\n        origin: 'http://host:9000',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'host:9000',\n        hostname: 'host',\n        port: '9000',\n        pathname: '/path',\n        search: '?query',\n        hash: '#frag',\n      },\n      '# Stringification of URL.searchParams',\n      {\n        input: '?a=b&c=d',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/bar?a=b&c=d',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/bar',\n        search: '?a=b&c=d',\n        searchParams: 'a=b&c=d',\n        hash: '',\n      },\n      {\n        input: '??a=b&c=d',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/bar??a=b&c=d',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/bar',\n        search: '??a=b&c=d',\n        searchParams: '%3Fa=b&c=d',\n        hash: '',\n      },\n      '# Scheme only',\n      {\n        input: 'http:',\n        base: 'http://example.org/foo/bar',\n        href: 'http://example.org/foo/bar',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/foo/bar',\n        search: '',\n        searchParams: '',\n        hash: '',\n      },\n      {\n        input: 'http:',\n        base: 'https://example.org/foo/bar',\n        failure: true,\n      },\n      {\n        input: 'sc:',\n        base: 'https://example.org/foo/bar',\n        href: 'sc:',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '',\n        search: '',\n        searchParams: '',\n        hash: '',\n      },\n      '# Percent encoding of fragments',\n      {\n        input: 'http://foo.bar/baz?qux#foo\\bbar',\n        base: 'about:blank',\n        href: 'http://foo.bar/baz?qux#foo%08bar',\n        origin: 'http://foo.bar',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'foo.bar',\n        hostname: 'foo.bar',\n        port: '',\n        pathname: '/baz',\n        search: '?qux',\n        searchParams: 'qux=',\n        hash: '#foo%08bar',\n      },\n      '# IPv4 parsing (via https://github.com/nodejs/node/pull/10317)',\n      {\n        input: 'http://192.168.257',\n        base: 'http://other.com/',\n        href: 'http://192.168.1.1/',\n        origin: 'http://192.168.1.1',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '192.168.1.1',\n        hostname: '192.168.1.1',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://192.168.257.com',\n        base: 'http://other.com/',\n        href: 'http://192.168.257.com/',\n        origin: 'http://192.168.257.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '192.168.257.com',\n        hostname: '192.168.257.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://256',\n        base: 'http://other.com/',\n        href: 'http://0.0.1.0/',\n        origin: 'http://0.0.1.0',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '0.0.1.0',\n        hostname: '0.0.1.0',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://256.com',\n        base: 'http://other.com/',\n        href: 'http://256.com/',\n        origin: 'http://256.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '256.com',\n        hostname: '256.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://999999999',\n        base: 'http://other.com/',\n        href: 'http://59.154.201.255/',\n        origin: 'http://59.154.201.255',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '59.154.201.255',\n        hostname: '59.154.201.255',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://999999999.com',\n        base: 'http://other.com/',\n        href: 'http://999999999.com/',\n        origin: 'http://999999999.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '999999999.com',\n        hostname: '999999999.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://10000000000',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      {\n        input: 'http://10000000000.com',\n        base: 'http://other.com/',\n        href: 'http://10000000000.com/',\n        origin: 'http://10000000000.com',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '10000000000.com',\n        hostname: '10000000000.com',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://4294967295',\n        base: 'http://other.com/',\n        href: 'http://255.255.255.255/',\n        origin: 'http://255.255.255.255',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '255.255.255.255',\n        hostname: '255.255.255.255',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://4294967296',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      {\n        input: 'http://0xffffffff',\n        base: 'http://other.com/',\n        href: 'http://255.255.255.255/',\n        origin: 'http://255.255.255.255',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '255.255.255.255',\n        hostname: '255.255.255.255',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://0xffffffff1',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      {\n        input: 'http://256.256.256.256',\n        base: 'http://other.com/',\n        failure: true,\n      },\n      {\n        input: 'https://0x.0x.0',\n        base: 'about:blank',\n        href: 'https://0.0.0.0/',\n        origin: 'https://0.0.0.0',\n        protocol: 'https:',\n        username: '',\n        password: '',\n        host: '0.0.0.0',\n        hostname: '0.0.0.0',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      'More IPv4 parsing (via https://github.com/jsdom/whatwg-url/issues/92)',\n      {\n        input: 'https://0x100000000/test',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'https://256.0.0.1/test',\n        base: 'about:blank',\n        failure: true,\n      },\n      \"# file URLs containing percent-encoded Windows drive letters (shouldn't work)\",\n      {\n        input: 'file:///C%3A/',\n        base: 'about:blank',\n        href: 'file:///C%3A/',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/C%3A/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file:///C%7C/',\n        base: 'about:blank',\n        href: 'file:///C%7C/',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/C%7C/',\n        search: '',\n        hash: '',\n      },\n      '# file URLs relative to other file URLs (via https://github.com/jsdom/whatwg-url/pull/60)',\n      {\n        input: 'pix/submit.gif',\n        base: 'file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/anchor.html',\n        href: 'file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname:\n          '/C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '..',\n        base: 'file:///C:/',\n        href: 'file:///C:/',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/C:/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '..',\n        base: 'file:///',\n        href: 'file:///',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      '# More file URL tests by zcorpan and annevk',\n      {\n        input: '/',\n        base: 'file:///C:/a/b',\n        href: 'file:///C:/',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/C:/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '//d:',\n        base: 'file:///C:/a/b',\n        href: 'file:///d:',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/d:',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '//d:/..',\n        base: 'file:///C:/a/b',\n        href: 'file:///d:/',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/d:/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '..',\n        base: 'file:///ab:/',\n        href: 'file:///',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '..',\n        base: 'file:///1:/',\n        href: 'file:///',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '',\n        base: 'file:///test?test#test',\n        href: 'file:///test?test',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/test',\n        search: '?test',\n        hash: '',\n      },\n      {\n        input: 'file:',\n        base: 'file:///test?test#test',\n        href: 'file:///test?test',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/test',\n        search: '?test',\n        hash: '',\n      },\n      {\n        input: '?x',\n        base: 'file:///test?test#test',\n        href: 'file:///test?x',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/test',\n        search: '?x',\n        hash: '',\n      },\n      {\n        input: 'file:?x',\n        base: 'file:///test?test#test',\n        href: 'file:///test?x',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/test',\n        search: '?x',\n        hash: '',\n      },\n      {\n        input: '#x',\n        base: 'file:///test?test#test',\n        href: 'file:///test?test#x',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/test',\n        search: '?test',\n        hash: '#x',\n      },\n      {\n        input: 'file:#x',\n        base: 'file:///test?test#test',\n        href: 'file:///test?test#x',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/test',\n        search: '?test',\n        hash: '#x',\n      },\n      '# File URLs and many (back)slashes',\n      {\n        input: 'file:\\\\\\\\//',\n        base: 'about:blank',\n        href: 'file:////',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '//',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file:\\\\\\\\\\\\\\\\',\n        base: 'about:blank',\n        href: 'file:////',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '//',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file:\\\\\\\\\\\\\\\\?fox',\n        base: 'about:blank',\n        href: 'file:////?fox',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '//',\n        search: '?fox',\n        hash: '',\n      },\n      {\n        input: 'file:\\\\\\\\\\\\\\\\#guppy',\n        base: 'about:blank',\n        href: 'file:////#guppy',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '//',\n        search: '',\n        hash: '#guppy',\n      },\n      {\n        input: 'file://spider///',\n        base: 'about:blank',\n        href: 'file://spider///',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: 'spider',\n        hostname: 'spider',\n        port: '',\n        pathname: '///',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file:\\\\\\\\localhost//',\n        base: 'about:blank',\n        href: 'file:////',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '//',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file:///localhost//cat',\n        base: 'about:blank',\n        href: 'file:///localhost//cat',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/localhost//cat',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file://\\\\/localhost//cat',\n        base: 'about:blank',\n        href: 'file:////localhost//cat',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '//localhost//cat',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file://localhost//a//../..//',\n        base: 'about:blank',\n        href: 'file://///',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '///',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '/////mouse',\n        base: 'file:///elephant',\n        href: 'file://///mouse',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '///mouse',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '\\\\//pig',\n        base: 'file://lion/',\n        href: 'file:///pig',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/pig',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '\\\\/localhost//pig',\n        base: 'file://lion/',\n        href: 'file:////pig',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '//pig',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '//localhost//pig',\n        base: 'file://lion/',\n        href: 'file:////pig',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '//pig',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '/..//localhost//pig',\n        base: 'file://lion/',\n        href: 'file://lion//localhost//pig',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: 'lion',\n        hostname: 'lion',\n        port: '',\n        pathname: '//localhost//pig',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file://',\n        base: 'file://ape/',\n        href: 'file:///',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      '# File URLs with non-empty hosts',\n      {\n        input: '/rooibos',\n        base: 'file://tea/',\n        href: 'file://tea/rooibos',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: 'tea',\n        hostname: 'tea',\n        port: '',\n        pathname: '/rooibos',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '/?chai',\n        base: 'file://tea/',\n        href: 'file://tea/?chai',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: 'tea',\n        hostname: 'tea',\n        port: '',\n        pathname: '/',\n        search: '?chai',\n        hash: '',\n      },\n      \"# Windows drive letter handling with the 'file:' base URL\",\n      {\n        input: 'C|',\n        base: 'file://host/dir/file',\n        href: 'file://host/C:',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: 'host',\n        hostname: 'host',\n        port: '',\n        pathname: '/C:',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'C|#',\n        base: 'file://host/dir/file',\n        href: 'file://host/C:#',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: 'host',\n        hostname: 'host',\n        port: '',\n        pathname: '/C:',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'C|?',\n        base: 'file://host/dir/file',\n        href: 'file://host/C:?',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: 'host',\n        hostname: 'host',\n        port: '',\n        pathname: '/C:',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'C|/',\n        base: 'file://host/dir/file',\n        href: 'file://host/C:/',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: 'host',\n        hostname: 'host',\n        port: '',\n        pathname: '/C:/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'C|\\n/',\n        base: 'file://host/dir/file',\n        href: 'file://host/C:/',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: 'host',\n        hostname: 'host',\n        port: '',\n        pathname: '/C:/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'C|\\\\',\n        base: 'file://host/dir/file',\n        href: 'file://host/C:/',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: 'host',\n        hostname: 'host',\n        port: '',\n        pathname: '/C:/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'C',\n        base: 'file://host/dir/file',\n        href: 'file://host/dir/C',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: 'host',\n        hostname: 'host',\n        port: '',\n        pathname: '/dir/C',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'C|a',\n        base: 'file://host/dir/file',\n        href: 'file://host/dir/C|a',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: 'host',\n        hostname: 'host',\n        port: '',\n        pathname: '/dir/C|a',\n        search: '',\n        hash: '',\n      },\n      '# Windows drive letter quirk with not empty host',\n      {\n        input: 'file://example.net/C:/',\n        base: 'about:blank',\n        href: 'file://example.net/C:/',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: 'example.net',\n        hostname: 'example.net',\n        port: '',\n        pathname: '/C:/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file://1.2.3.4/C:/',\n        base: 'about:blank',\n        href: 'file://1.2.3.4/C:/',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '1.2.3.4',\n        hostname: '1.2.3.4',\n        port: '',\n        pathname: '/C:/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file://[1::8]/C:/',\n        base: 'about:blank',\n        href: 'file://[1::8]/C:/',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '[1::8]',\n        hostname: '[1::8]',\n        port: '',\n        pathname: '/C:/',\n        search: '',\n        hash: '',\n      },\n      '# Windows drive letter quirk (no host)',\n      {\n        input: 'file:/C|/',\n        base: 'about:blank',\n        href: 'file:///C:/',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/C:/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file://C|/',\n        base: 'about:blank',\n        href: 'file:///C:/',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/C:/',\n        search: '',\n        hash: '',\n      },\n      '# file URLs without base URL by Rimas Misevičius',\n      {\n        input: 'file:',\n        base: 'about:blank',\n        href: 'file:///',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'file:?q=v',\n        base: 'about:blank',\n        href: 'file:///?q=v',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/',\n        search: '?q=v',\n        hash: '',\n      },\n      {\n        input: 'file:#frag',\n        base: 'about:blank',\n        href: 'file:///#frag',\n        protocol: 'file:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '#frag',\n      },\n      '# IPv6 tests',\n      {\n        input: 'http://[1:0::]',\n        base: 'http://example.net/',\n        href: 'http://[1::]/',\n        origin: 'http://[1::]',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '[1::]',\n        hostname: '[1::]',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://[0:1:2:3:4:5:6:7:8]',\n        base: 'http://example.net/',\n        failure: true,\n      },\n      {\n        input: 'https://[0::0::0]',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'https://[0:.0]',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'https://[0:0:]',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'https://[0:1:2:3:4:5:6:7.0.0.0.1]',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'https://[0:1.00.0.0.0]',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'https://[0:1.290.0.0.0]',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'https://[0:1.23.23]',\n        base: 'about:blank',\n        failure: true,\n      },\n      '# Empty host',\n      {\n        input: 'http://?',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'http://#',\n        base: 'about:blank',\n        failure: true,\n      },\n      '# Non-special-URL path tests',\n      {\n        input: 'sc://ñ',\n        base: 'about:blank',\n        href: 'sc://%C3%B1',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '%C3%B1',\n        hostname: '%C3%B1',\n        port: '',\n        pathname: '',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'sc://ñ?x',\n        base: 'about:blank',\n        href: 'sc://%C3%B1?x',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '%C3%B1',\n        hostname: '%C3%B1',\n        port: '',\n        pathname: '',\n        search: '?x',\n        hash: '',\n      },\n      {\n        input: 'sc://ñ#x',\n        base: 'about:blank',\n        href: 'sc://%C3%B1#x',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '%C3%B1',\n        hostname: '%C3%B1',\n        port: '',\n        pathname: '',\n        search: '',\n        hash: '#x',\n      },\n      {\n        input: '#x',\n        base: 'sc://ñ',\n        href: 'sc://%C3%B1#x',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '%C3%B1',\n        hostname: '%C3%B1',\n        port: '',\n        pathname: '',\n        search: '',\n        hash: '#x',\n      },\n      {\n        input: '?x',\n        base: 'sc://ñ',\n        href: 'sc://%C3%B1?x',\n        origin: 'null',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '%C3%B1',\n        hostname: '%C3%B1',\n        port: '',\n        pathname: '',\n        search: '?x',\n        hash: '',\n      },\n      {\n        input: 'sc://?',\n        base: 'about:blank',\n        href: 'sc://?',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'sc://#',\n        base: 'about:blank',\n        href: 'sc://#',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '///',\n        base: 'sc://x/',\n        href: 'sc:///',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '////',\n        base: 'sc://x/',\n        href: 'sc:////',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '//',\n        search: '',\n        hash: '',\n      },\n      {\n        input: '////x/',\n        base: 'sc://x/',\n        href: 'sc:////x/',\n        protocol: 'sc:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '//x/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'tftp://foobar.com/someconfig;mode=netascii',\n        base: 'about:blank',\n        href: 'tftp://foobar.com/someconfig;mode=netascii',\n        origin: 'null',\n        protocol: 'tftp:',\n        username: '',\n        password: '',\n        host: 'foobar.com',\n        hostname: 'foobar.com',\n        port: '',\n        pathname: '/someconfig;mode=netascii',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'telnet://user:pass@foobar.com:23/',\n        base: 'about:blank',\n        href: 'telnet://user:pass@foobar.com:23/',\n        origin: 'null',\n        protocol: 'telnet:',\n        username: 'user',\n        password: 'pass',\n        host: 'foobar.com:23',\n        hostname: 'foobar.com',\n        port: '23',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'ut2004://10.10.10.10:7777/Index.ut2',\n        base: 'about:blank',\n        href: 'ut2004://10.10.10.10:7777/Index.ut2',\n        origin: 'null',\n        protocol: 'ut2004:',\n        username: '',\n        password: '',\n        host: '10.10.10.10:7777',\n        hostname: '10.10.10.10',\n        port: '7777',\n        pathname: '/Index.ut2',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'redis://foo:bar@somehost:6379/0?baz=bam&qux=baz',\n        base: 'about:blank',\n        href: 'redis://foo:bar@somehost:6379/0?baz=bam&qux=baz',\n        origin: 'null',\n        protocol: 'redis:',\n        username: 'foo',\n        password: 'bar',\n        host: 'somehost:6379',\n        hostname: 'somehost',\n        port: '6379',\n        pathname: '/0',\n        search: '?baz=bam&qux=baz',\n        hash: '',\n      },\n      {\n        input: 'rsync://foo@host:911/sup',\n        base: 'about:blank',\n        href: 'rsync://foo@host:911/sup',\n        origin: 'null',\n        protocol: 'rsync:',\n        username: 'foo',\n        password: '',\n        host: 'host:911',\n        hostname: 'host',\n        port: '911',\n        pathname: '/sup',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'git://github.com/foo/bar.git',\n        base: 'about:blank',\n        href: 'git://github.com/foo/bar.git',\n        origin: 'null',\n        protocol: 'git:',\n        username: '',\n        password: '',\n        host: 'github.com',\n        hostname: 'github.com',\n        port: '',\n        pathname: '/foo/bar.git',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'irc://myserver.com:6999/channel?passwd',\n        base: 'about:blank',\n        href: 'irc://myserver.com:6999/channel?passwd',\n        origin: 'null',\n        protocol: 'irc:',\n        username: '',\n        password: '',\n        host: 'myserver.com:6999',\n        hostname: 'myserver.com',\n        port: '6999',\n        pathname: '/channel',\n        search: '?passwd',\n        hash: '',\n      },\n      {\n        input: 'dns://fw.example.org:9999/foo.bar.org?type=TXT',\n        base: 'about:blank',\n        href: 'dns://fw.example.org:9999/foo.bar.org?type=TXT',\n        origin: 'null',\n        protocol: 'dns:',\n        username: '',\n        password: '',\n        host: 'fw.example.org:9999',\n        hostname: 'fw.example.org',\n        port: '9999',\n        pathname: '/foo.bar.org',\n        search: '?type=TXT',\n        hash: '',\n      },\n      {\n        input: 'ldap://localhost:389/ou=People,o=JNDITutorial',\n        base: 'about:blank',\n        href: 'ldap://localhost:389/ou=People,o=JNDITutorial',\n        origin: 'null',\n        protocol: 'ldap:',\n        username: '',\n        password: '',\n        host: 'localhost:389',\n        hostname: 'localhost',\n        port: '389',\n        pathname: '/ou=People,o=JNDITutorial',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'git+https://github.com/foo/bar',\n        base: 'about:blank',\n        href: 'git+https://github.com/foo/bar',\n        origin: 'null',\n        protocol: 'git+https:',\n        username: '',\n        password: '',\n        host: 'github.com',\n        hostname: 'github.com',\n        port: '',\n        pathname: '/foo/bar',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'urn:ietf:rfc:2648',\n        base: 'about:blank',\n        href: 'urn:ietf:rfc:2648',\n        origin: 'null',\n        protocol: 'urn:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'ietf:rfc:2648',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'tag:joe@example.org,2001:foo/bar',\n        base: 'about:blank',\n        href: 'tag:joe@example.org,2001:foo/bar',\n        origin: 'null',\n        protocol: 'tag:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'joe@example.org,2001:foo/bar',\n        search: '',\n        hash: '',\n      },\n      '# percent encoded hosts in non-special-URLs',\n      {\n        input: 'non-special://%E2%80%A0/',\n        base: 'about:blank',\n        href: 'non-special://%E2%80%A0/',\n        protocol: 'non-special:',\n        username: '',\n        password: '',\n        host: '%E2%80%A0',\n        hostname: '%E2%80%A0',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'non-special://H%4fSt/path',\n        base: 'about:blank',\n        href: 'non-special://H%4fSt/path',\n        protocol: 'non-special:',\n        username: '',\n        password: '',\n        host: 'H%4fSt',\n        hostname: 'H%4fSt',\n        port: '',\n        pathname: '/path',\n        search: '',\n        hash: '',\n      },\n      '# IPv6 in non-special-URLs',\n      {\n        input: 'non-special://[1:2:0:0:5:0:0:0]/',\n        base: 'about:blank',\n        href: 'non-special://[1:2:0:0:5::]/',\n        protocol: 'non-special:',\n        username: '',\n        password: '',\n        host: '[1:2:0:0:5::]',\n        hostname: '[1:2:0:0:5::]',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'non-special://[1:2:0:0:0:0:0:3]/',\n        base: 'about:blank',\n        href: 'non-special://[1:2::3]/',\n        protocol: 'non-special:',\n        username: '',\n        password: '',\n        host: '[1:2::3]',\n        hostname: '[1:2::3]',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'non-special://[1:2::3]:80/',\n        base: 'about:blank',\n        href: 'non-special://[1:2::3]:80/',\n        protocol: 'non-special:',\n        username: '',\n        password: '',\n        host: '[1:2::3]:80',\n        hostname: '[1:2::3]',\n        port: '80',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'non-special://[:80/',\n        base: 'about:blank',\n        failure: true,\n      },\n      {\n        input: 'blob:https://example.com:443/',\n        base: 'about:blank',\n        href: 'blob:https://example.com:443/',\n        origin: 'https://example.com',\n        protocol: 'blob:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'https://example.com:443/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'blob:d3958f5c-0777-0845-9dcf-2cb28783acaf',\n        base: 'about:blank',\n        href: 'blob:d3958f5c-0777-0845-9dcf-2cb28783acaf',\n        protocol: 'blob:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: 'd3958f5c-0777-0845-9dcf-2cb28783acaf',\n        search: '',\n        hash: '',\n      },\n      'Invalid IPv4 radix digits',\n      {\n        input: 'http://0x7f.0.0.0x7g',\n        base: 'about:blank',\n        href: 'http://0x7f.0.0.0x7g/',\n        origin: 'http://0x7f.0.0.0x7g',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '0x7f.0.0.0x7g',\n        hostname: '0x7f.0.0.0x7g',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://0X7F.0.0.0X7G',\n        base: 'about:blank',\n        href: 'http://0x7f.0.0.0x7g/',\n        origin: 'http://0x7f.0.0.0x7g',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '0x7f.0.0.0x7g',\n        hostname: '0x7f.0.0.0x7g',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      'Invalid IPv4 portion of IPv6 address',\n      {\n        input: 'http://[::127.0.0.0.1]',\n        base: 'about:blank',\n        failure: true,\n      },\n      'Uncompressed IPv6 addresses with 0',\n      {\n        input: 'http://[0:1:0:1:0:1:0:1]',\n        base: 'about:blank',\n        href: 'http://[0:1:0:1:0:1:0:1]/',\n        origin: 'http://[0:1:0:1:0:1:0:1]',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '[0:1:0:1:0:1:0:1]',\n        hostname: '[0:1:0:1:0:1:0:1]',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://[1:0:1:0:1:0:1:0]',\n        base: 'about:blank',\n        href: 'http://[1:0:1:0:1:0:1:0]/',\n        origin: 'http://[1:0:1:0:1:0:1:0]',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: '[1:0:1:0:1:0:1:0]',\n        hostname: '[1:0:1:0:1:0:1:0]',\n        port: '',\n        pathname: '/',\n        search: '',\n        hash: '',\n      },\n      'Percent-encoded query and fragment',\n      {\n        input: 'http://example.org/test?\\u0022',\n        base: 'about:blank',\n        href: 'http://example.org/test?%22',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/test',\n        search: '?%22',\n        hash: '',\n      },\n      {\n        input: 'http://example.org/test?\\u0023',\n        base: 'about:blank',\n        href: 'http://example.org/test?#',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/test',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'http://example.org/test?\\u003C',\n        base: 'about:blank',\n        href: 'http://example.org/test?%3C',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/test',\n        search: '?%3C',\n        hash: '',\n      },\n      {\n        input: 'http://example.org/test?\\u003E',\n        base: 'about:blank',\n        href: 'http://example.org/test?%3E',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/test',\n        search: '?%3E',\n        hash: '',\n      },\n      {\n        input: 'http://example.org/test?\\u2323',\n        base: 'about:blank',\n        href: 'http://example.org/test?%E2%8C%A3',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/test',\n        search: '?%E2%8C%A3',\n        hash: '',\n      },\n      {\n        input: 'http://example.org/test?%23%23',\n        base: 'about:blank',\n        href: 'http://example.org/test?%23%23',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/test',\n        search: '?%23%23',\n        hash: '',\n      },\n      {\n        input: 'http://example.org/test?%GH',\n        base: 'about:blank',\n        href: 'http://example.org/test?%GH',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/test',\n        search: '?%GH',\n        hash: '',\n      },\n      {\n        input: 'http://example.org/test?a#%EF',\n        base: 'about:blank',\n        href: 'http://example.org/test?a#%EF',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/test',\n        search: '?a',\n        hash: '#%EF',\n      },\n      {\n        input: 'http://example.org/test?a#%GH',\n        base: 'about:blank',\n        href: 'http://example.org/test?a#%GH',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/test',\n        search: '?a',\n        hash: '#%GH',\n      },\n      'Bad bases',\n      {\n        input: 'test-a.html',\n        base: 'a',\n        failure: true,\n      },\n      {\n        input: 'test-a-slash.html',\n        base: 'a/',\n        failure: true,\n      },\n      {\n        input: 'test-a-slash-slash.html',\n        base: 'a//',\n        failure: true,\n      },\n      {\n        input: 'test-a-colon.html',\n        base: 'a:',\n        failure: true,\n      },\n      {\n        input: 'test-a-colon-slash.html',\n        base: 'a:/',\n        href: 'a:/test-a-colon-slash.html',\n        protocol: 'a:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/test-a-colon-slash.html',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'test-a-colon-slash-slash.html',\n        base: 'a://',\n        href: 'a:///test-a-colon-slash-slash.html',\n        protocol: 'a:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/test-a-colon-slash-slash.html',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'test-a-colon-b.html',\n        base: 'a:b',\n        failure: true,\n      },\n      {\n        input: 'test-a-colon-slash-b.html',\n        base: 'a:/b',\n        href: 'a:/test-a-colon-slash-b.html',\n        protocol: 'a:',\n        username: '',\n        password: '',\n        host: '',\n        hostname: '',\n        port: '',\n        pathname: '/test-a-colon-slash-b.html',\n        search: '',\n        hash: '',\n      },\n      {\n        input: 'test-a-colon-slash-slash-b.html',\n        base: 'a://b',\n        href: 'a://b/test-a-colon-slash-slash-b.html',\n        protocol: 'a:',\n        username: '',\n        password: '',\n        host: 'b',\n        hostname: 'b',\n        port: '',\n        pathname: '/test-a-colon-slash-slash-b.html',\n        search: '',\n        hash: '',\n      },\n      'Null code point in fragment',\n      {\n        input: 'http://example.org/test?a#b\\u0000c',\n        base: 'about:blank',\n        href: 'http://example.org/test?a#b%00c',\n        origin: 'http://example.org',\n        protocol: 'http:',\n        username: '',\n        password: '',\n        host: 'example.org',\n        hostname: 'example.org',\n        port: '',\n        pathname: '/test',\n        search: '?a',\n        hash: '#b%00c',\n      },\n    ];\n\n    cases.forEach((test) => {\n      if (typeof test === 'string') return;\n      if (test.failure) {\n        throws(() => {\n          new URL(test.input, test.base || 'about:blank');\n        });\n        return;\n      }\n      const url = new URL(test.input, test.base || 'about:blank');\n      strictEqual(url.href, test.href);\n      strictEqual(url.protocol, test.protocol);\n      strictEqual(url.username, test.username);\n      strictEqual(url.password, test.password);\n      strictEqual(url.host, test.host);\n      strictEqual(url.hostname, test.hostname);\n      strictEqual(url.port, test.port);\n      strictEqual(url.pathname, test.pathname);\n      strictEqual(url.search, test.search);\n      if ('searchParams' in test) {\n        strictEqual(url.searchParams.toString(), test.searchParams);\n      }\n      strictEqual(url.hash, test.hash);\n      if (test.origin == null) {\n        strictEqual(url.origin, 'null');\n      } else {\n        strictEqual(url.origin, test.origin);\n      }\n    });\n  },\n};\n\nexport const wptTestURLSearchParamsConstructor = {\n  test() {\n    let params = new URLSearchParams();\n    strictEqual(params + '', '');\n    params = new URLSearchParams('');\n    strictEqual(params + '', '');\n    params = new URLSearchParams('a=b');\n    strictEqual(params + '', 'a=b');\n    params = new URLSearchParams(params);\n    strictEqual(params + '', 'a=b');\n\n    {\n      const params = new URLSearchParams();\n      strictEqual(params.toString(), '');\n    }\n\n    {\n      const params = new URLSearchParams(DOMException);\n      strictEqual(\n        params.toString(),\n        'INDEX_SIZE_ERR=1&DOMSTRING_SIZE_ERR=2&HIERARCHY_REQUEST_ERR=3&WRONG_DOCUMENT_ERR=4&INVALID_CHARACTER_ERR=5&NO_DATA_ALLOWED_ERR=6&NO_MODIFICATION_ALLOWED_ERR=7&NOT_FOUND_ERR=8&NOT_SUPPORTED_ERR=9&INUSE_ATTRIBUTE_ERR=10&INVALID_STATE_ERR=11&SYNTAX_ERR=12&INVALID_MODIFICATION_ERR=13&NAMESPACE_ERR=14&INVALID_ACCESS_ERR=15&VALIDATION_ERR=16&TYPE_MISMATCH_ERR=17&SECURITY_ERR=18&NETWORK_ERR=19&ABORT_ERR=20&URL_MISMATCH_ERR=21&QUOTA_EXCEEDED_ERR=22&TIMEOUT_ERR=23&INVALID_NODE_TYPE_ERR=24&DATA_CLONE_ERR=25'\n      );\n    }\n\n    {\n      const params = new URLSearchParams('');\n      strictEqual(params.__proto__, URLSearchParams.prototype);\n    }\n\n    {\n      const params = new URLSearchParams({});\n      strictEqual(params + '', '');\n    }\n\n    {\n      let params = new URLSearchParams('a=b');\n      ok(params.has('a'));\n      ok(!params.has('b'));\n      params = new URLSearchParams('a=b&c');\n      ok(params.has('a'));\n      ok(params.has('c'));\n      params = new URLSearchParams('&a&&& &&&&&a+b=& c&m%c3%b8%c3%b8');\n      ok(params.has('a'));\n      ok(params.has('a b'));\n      ok(params.has(' '));\n      ok(!params.has('c'));\n      ok(params.has(' c'));\n      ok(params.has('møø'));\n    }\n\n    {\n      const seed = new URLSearchParams('a=b&c=d');\n      const params = new URLSearchParams(seed);\n      strictEqual(params.get('a'), 'b');\n      strictEqual(params.get('c'), 'd');\n      ok(!params.has('d'));\n      // The name-value pairs are copied when created; later updates\n      // should not be observable.\n      seed.append('e', 'f');\n      ok(!params.has('e'));\n      params.append('g', 'h');\n      ok(!seed.has('g'));\n    }\n\n    {\n      let params = new URLSearchParams('a=b+c');\n      strictEqual(params.get('a'), 'b c');\n      params = new URLSearchParams('a+b=c');\n      strictEqual(params.get('a b'), 'c');\n    }\n\n    {\n      const testValue = '+15555555555';\n      const params = new URLSearchParams();\n      params.set('query', testValue);\n      const newParams = new URLSearchParams(params.toString());\n\n      strictEqual(params.toString(), 'query=%2B15555555555');\n      strictEqual(params.get('query'), testValue);\n      strictEqual(newParams.get('query'), testValue);\n    }\n\n    {\n      let params = new URLSearchParams('a=b c');\n      strictEqual(params.get('a'), 'b c');\n      params = new URLSearchParams('a b=c');\n      strictEqual(params.get('a b'), 'c');\n    }\n\n    {\n      let params = new URLSearchParams('a=b%20c');\n      strictEqual(params.get('a'), 'b c');\n      params = new URLSearchParams('a%20b=c');\n      strictEqual(params.get('a b'), 'c');\n    }\n\n    {\n      let params = new URLSearchParams('a=b\\0c');\n      strictEqual(params.get('a'), 'b\\0c');\n      params = new URLSearchParams('a\\0b=c');\n      strictEqual(params.get('a\\0b'), 'c');\n    }\n\n    {\n      let params = new URLSearchParams('a=b%00c');\n      strictEqual(params.get('a'), 'b\\0c');\n      params = new URLSearchParams('a%00b=c');\n      strictEqual(params.get('a\\0b'), 'c');\n    }\n\n    {\n      let params = new URLSearchParams('a=b\\u2384');\n      strictEqual(params.get('a'), 'b\\u2384');\n      params = new URLSearchParams('a\\u2384b=c');\n      strictEqual(params.get('a\\u2384b'), 'c');\n    }\n\n    {\n      let params = new URLSearchParams('a=b%e2%8e%84');\n      strictEqual(params.get('a'), 'b\\u2384');\n      params = new URLSearchParams('a%e2%8e%84b=c');\n      strictEqual(params.get('a\\u2384b'), 'c');\n    }\n\n    {\n      let params = new URLSearchParams('a=b\\uD83D\\uDCA9c');\n      strictEqual(params.get('a'), 'b\\uD83D\\uDCA9c');\n      params = new URLSearchParams('a\\uD83D\\uDCA9b=c');\n      strictEqual(params.get('a\\uD83D\\uDCA9b'), 'c');\n    }\n\n    {\n      let params = new URLSearchParams('a=b%f0%9f%92%a9c');\n      strictEqual(params.get('a'), 'b\\uD83D\\uDCA9c');\n      params = new URLSearchParams('a%f0%9f%92%a9b=c');\n      strictEqual(params.get('a\\uD83D\\uDCA9b'), 'c');\n    }\n\n    {\n      let params = new URLSearchParams([]);\n      params = new URLSearchParams([\n        ['a', 'b'],\n        ['c', 'd'],\n      ]);\n      strictEqual(params.get('a'), 'b');\n      strictEqual(params.get('c'), 'd');\n      throws(() => new URLSearchParams([[1]]));\n      throws(() => new URLSearchParams([[1, 2, 3]]));\n    }\n\n    {\n      let params = new URLSearchParams('a=a/b~');\n      strictEqual(params.toString(), 'a=a%2Fb%7E');\n\n      let url = new URL('https://example.org?a=a/b~');\n      strictEqual(url.search, '?a=a/b~');\n      strictEqual(url.searchParams.toString(), 'a=a%2Fb%7E');\n    }\n\n    [\n      { input: { '+': '%C2' }, output: [['+', '%C2']], name: 'object with +' },\n      {\n        input: { c: 'x', a: '?' },\n        output: [\n          ['c', 'x'],\n          ['a', '?'],\n        ],\n        name: 'object with two keys',\n      },\n      {\n        input: [\n          ['c', 'x'],\n          ['a', '?'],\n        ],\n        output: [\n          ['c', 'x'],\n          ['a', '?'],\n        ],\n        name: 'array with two keys',\n      },\n    ].forEach((val) => {\n      let params = new URLSearchParams(val.input);\n      let i = 0;\n      for (let param of params) {\n        deepStrictEqual(param, val.output[i]);\n        i++;\n      }\n    });\n\n    {\n      const params = new URLSearchParams();\n      params[Symbol.iterator] = function* () {\n        yield ['a', 'b'];\n      };\n      let params2 = new URLSearchParams(params);\n      strictEqual(params2.get('a'), 'b');\n    }\n  },\n};\n\nexport const urlSetSearch = {\n  test() {\n    const url = new URL('http://example.com?foo=bar&baz=qux');\n    url.search = '?quux=corge&grault=garply';\n    const result = [];\n    for (let param of url.searchParams) {\n      result.push(param.join(':'));\n    }\n    deepStrictEqual(result, ['quux:corge', 'grault:garply']);\n  },\n};\n\nexport const urlSegfaultRegression = {\n  test() {\n    let url = new URL('http://example.org/?foo=bar&baz=qux');\n    // Setting certain properties would once zero out an internal reference count, causing a\n    // nondeterministic crash during URL/URLSearchParams destruction. It triggered an assertion\n    // under debug mode when the `searchParams` property was accessed, which this test is designed\n    // to express.\n    url.protocol = 'http:';\n    strictEqual(url.searchParams.toString(), 'foo=bar&baz=qux');\n  },\n};\n\n// ======================================================================================\n\nexport const urlPatternBasics = {\n  test() {\n    const urlPattern = new URLPattern();\n\n    // The standard attributes exist as they should and are readonly\n    strictEqual(urlPattern.protocol, '*');\n    strictEqual(urlPattern.username, '*');\n    strictEqual(urlPattern.password, '*');\n    strictEqual(urlPattern.hostname, '*');\n    strictEqual(urlPattern.port, '*');\n    strictEqual(urlPattern.pathname, '*');\n    strictEqual(urlPattern.search, '*');\n    strictEqual(urlPattern.hash, '*');\n\n    strictEqual(typeof urlPattern.exec, 'function');\n    strictEqual(typeof urlPattern.test, 'function');\n\n    {\n      // URLPattern can be correctly subclassed.\n      let count = 0;\n      class MyURLPattern extends URLPattern {\n        get protocol() {\n          count++;\n          return super.protocol;\n        }\n        get username() {\n          count++;\n          return super.username;\n        }\n        get password() {\n          count++;\n          return super.password;\n        }\n        get hostname() {\n          count++;\n          return super.hostname;\n        }\n        get port() {\n          count++;\n          return super.port;\n        }\n        get pathname() {\n          count++;\n          return super.pathname;\n        }\n        get search() {\n          count++;\n          return super.search;\n        }\n        get hash() {\n          count++;\n          return super.hash;\n        }\n        test() {\n          return true;\n        }\n        exec() {\n          return true;\n        }\n      }\n\n      const myPattern = new MyURLPattern();\n      strictEqual(myPattern.protocol, '*');\n      strictEqual(myPattern.username, '*');\n      strictEqual(myPattern.password, '*');\n      strictEqual(myPattern.hostname, '*');\n      strictEqual(myPattern.port, '*');\n      strictEqual(myPattern.pathname, '*');\n      strictEqual(myPattern.search, '*');\n      strictEqual(myPattern.hash, '*');\n      strictEqual(myPattern.exec(), true);\n      strictEqual(myPattern.test(), true);\n      strictEqual(count, 8);\n    }\n  },\n};\n\nexport const urlParseStatic = {\n  test() {\n    const url = URL.parse('http://example.org');\n    strictEqual(url.protocol, 'http:');\n    strictEqual(url.host, 'example.org');\n\n    const url2 = URL.parse('foo', 'http://example.org');\n    strictEqual(url2.protocol, 'http:');\n    strictEqual(url2.host, 'example.org');\n    strictEqual(url2.pathname, '/foo');\n\n    // Unlike `new URL(...)` the `URL.parse(...)` function does not throw on invalid URLs\n    // (which is the key point)\n    strictEqual(URL.parse('not valid'), null);\n  },\n};\n\nexport const urlSearchParamsPipe = {\n  test() {\n    const url = new URL('http://example.com/?a=b%7Cc');\n    strictEqual(url.search, '?a=b%7Cc');\n    strictEqual(url.searchParams.toString(), 'a=b%7Cc');\n  },\n};\n\nexport const urlPatternFun = {\n  test() {\n    const v3 = new Uint16Array(57578);\n    const v4 = {};\n    v4.username = v3;\n    new URLPattern(v4);\n  },\n};\n\nexport const urlPatternExecWithNestedRegexGroups = {\n  test() {\n    // Regression test: a pattern whose value contains its own capturing groups\n    // (e.g. a named group like (?<foo>x)) would cause an out-of-bounds access\n    // in execRegex because the regex match array is larger than the nameList.\n    // The unnamed regex group gets auto-named \"0\" by the URLPattern parser.\n    const pattern = new URLPattern({ pathname: '/:a/((?<foo>x))' });\n    const result = pattern.exec({ pathname: '/1/x' });\n    strictEqual(result !== null, true);\n\n    const groups = result.pathname.groups;\n    strictEqual(groups.a, '1');\n    strictEqual(groups['0'], 'x');\n    // Should only have the two named parts from the URL pattern (not regex named groups).\n    deepStrictEqual(Object.keys(groups), ['0', 'a']);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/url-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"url-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"url-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"urlsearchparams_delete_has_value_arg\",\n          \"urlpattern_original\",\n          \"url_standard\",\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/warnings-tail.js",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport * as assert from 'node:assert';\n\n// ================================================================================================\n// Expected warnings registry.\n//\n// Add new entries here when introducing logWarning() calls. Each entry is a substring that must\n// appear in at least one warn-level tail event's message. The trigger worker (warnings-test.js)\n// must exercise a code path that produces each warning.\n//\n// NOTE: Some logWarning() calls go through jsg::Lock::logWarning() instead of\n// IoContext::logWarning(). The jsg path does not currently emit to the tracer, so those warnings\n// are not observable via tail workers and cannot be tested here (e.g. .text() on non-text body).\nconst EXPECTED_WARNINGS = [\n  // From Body::Body (http.c++) — FormData body with a custom Content-Type header.\n  'FormData body was provided',\n  // From Response constructor (http.c++) — null-body status with zero-length body.\n  'Constructing a Response with a null body status',\n];\n// ================================================================================================\n\nlet allEvents = [];\n\nexport default {\n  tailStream(event) {\n    allEvents.push(event.event);\n    return (event) => {\n      allEvents.push(event.event);\n    };\n  },\n};\n\nexport const test = {\n  async test() {\n    // HACK: Tail events from the trigger worker's fetch invocation are delivered asynchronously\n    // across service boundaries. There is no JS-level synchronization primitive to await their\n    // arrival — a plain `await promise` doesn't drive the I/O loop that delivers them. We must\n    // use scheduler.wait() to yield while the runtime processes pending I/O.\n    const TIMEOUT_MS = 5000;\n    const POLL_MS = 10;\n    const deadline = Date.now() + TIMEOUT_MS;\n\n    const unseen = () =>\n      EXPECTED_WARNINGS.filter(\n        (substring) =>\n          !allEvents.some(\n            (e) =>\n              e.type === 'log' &&\n              e.level === 'warn' &&\n              e.message?.[0]?.includes(substring)\n          )\n      );\n\n    while (unseen().length > 0 && Date.now() < deadline) {\n      await scheduler.wait(POLL_MS);\n    }\n\n    const missing = unseen();\n    assert.strictEqual(\n      missing.length,\n      0,\n      `${missing.length} expected warning(s) were not observed after ${TIMEOUT_MS}ms:\\n` +\n        missing.map((s) => `  - \"${s}\"`).join('\\n')\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/warnings-test.js",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport default {\n  async fetch(request) {\n    // --- FormData body with custom Content-Type (http.c++) ---\n    // The custom Content-Type will lack the boundary parameter that the FormData serializer\n    // generates, preventing the recipient from parsing the body.\n    const formData = new FormData();\n    formData.append('key', 'value');\n    const req = new Request('http://example.com', {\n      method: 'POST',\n      body: formData,\n      headers: { 'Content-Type': 'multipart/form-data' },\n    });\n\n    // --- Null-body status with zero-length body (http.c++) ---\n    // Constructing a Response with a null-body status (204) and a non-null, zero-length body\n    // is technically incorrect and should warn.\n    const resp = new Response('', { status: 204 });\n\n    return new Response('ok');\n  },\n};\n\nexport const test = {\n  async test(ctrl, env) {\n    // Invoke via service binding so the fetch() handler runs in a traced invocation\n    // (test() handlers are intentionally not traced by the test runner).\n    await env.SELF.fetch('http://dummy');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/warnings-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"trigger\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"warnings-test.js\" )\n        ],\n        bindings = [\n          ( name = \"SELF\", service = \"trigger\" ),\n        ],\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n        streamingTails = [\"tail\"],\n      ),\n    ),\n    ( name = \"tail\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"warnings-tail.js\" )\n        ],\n        compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n      ),\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/webfs-test.js",
    "content": "import {\n  ok,\n  strictEqual,\n  deepStrictEqual,\n  notDeepStrictEqual,\n  rejects,\n  throws,\n} from 'node:assert';\n\nok(navigator.storage instanceof StorageManager);\n\nconst dir = await navigator.storage.getDirectory();\nok(dir instanceof FileSystemDirectoryHandle);\nstrictEqual(dir.name, '');\n\nconst bundle = await dir.getDirectoryHandle('bundle');\nconst temp = await dir.getDirectoryHandle('tmp');\n\nstrictEqual(bundle.name, 'bundle');\nstrictEqual(temp.name, 'tmp');\n\nlet count = 0;\nfor await (const node of dir) {\n  // The node is an Array of [name, handle]\n  strictEqual(typeof node[0], 'string');\n  ok(node[1] instanceof FileSystemDirectoryHandle);\n  count++;\n}\nstrictEqual(count, 3);\n\nconst names = await Array.fromAsync(dir.keys());\ndeepStrictEqual(names, ['bundle', 'tmp', 'dev']);\n\nexport const webfsTest = {\n  async test() {\n    const file = await temp.getFileHandle('foo.txt', { create: true });\n    ok(file instanceof FileSystemFileHandle);\n    const writable = await file.createWritable();\n    ok(writable instanceof FileSystemWritableFileStream);\n    const enc = new TextEncoder();\n    await writable.write(enc.encode('hello world'));\n\n    // The original file contents remain unchanged until the writable is closed.\n    let f = await file.getFile();\n    strictEqual(await f.text(), '');\n    await writable.close();\n\n    // Once closed, however, the file contents are updated.\n    f = await file.getFile();\n    strictEqual(await f.text(), 'hello world');\n\n    const file2 = await temp.getFileHandle('foo.txt');\n    ok(file2 instanceof FileSystemFileHandle);\n    const fileStream = await file2.getFile();\n    ok(fileStream instanceof File);\n    const text = await fileStream.text();\n    strictEqual(text, 'hello world');\n\n    await rejects(temp.getFileHandle('does-not-exist.txt', { create: false }), {\n      message: 'Not found',\n    });\n  },\n};\n\n// TODO(node-fs): Rework this test now that createSyncAccessHandle has been removed.\n// We can still test this using the the stream API but it needs to be structured\n// a little differently.\n// export const deviceTest = {\n//   async test() {\n//     // Test /dev/null\n//     const dev = await dir.getDirectoryHandle('dev');\n//     const devNull = await dev.getFileHandle('null');\n//     ok(devNull instanceof FileSystemFileHandle);\n//     const sync = await devNull.createSyncAccessHandle();\n//     const enc = new TextEncoder();\n//     strictEqual(sync.getSize(), 0);\n//     sync.write(enc.encode('hello world'));\n//     strictEqual(sync.getSize(), 0);\n//     strictEqual(sync.read(new Uint8Array(11)), 0);\n\n//     // Test /dev/zero\n//     const devZero = await dev.getFileHandle('zero');\n//     ok(devZero instanceof FileSystemFileHandle);\n//     const syncZero = await devZero.createSyncAccessHandle();\n//     strictEqual(syncZero.getSize(), 0);\n//     syncZero.write(enc.encode('hello world'));\n//     strictEqual(syncZero.getSize(), 0);\n//     const u8 = new Buffer.from([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);\n//     const zeroes = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);\n//     strictEqual(syncZero.read(u8), u8.byteLength);\n//     deepStrictEqual(u8, zeroes);\n\n//     // Test /dev/full\n//     const devFull = await dev.getFileHandle('full');\n//     ok(devFull instanceof FileSystemFileHandle);\n//     const syncFull = await devFull.createSyncAccessHandle();\n//     strictEqual(syncFull.getSize(), 0);\n//     throws(() => syncFull.write(enc.encode('hello world')), {\n//       message: 'Operation not permitted',\n//       name: 'NotAllowedError',\n//     });\n//     strictEqual(syncFull.getSize(), 0);\n//     const u8Full = new Buffer.from([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);\n//     strictEqual(syncFull.read(u8Full), 11);\n//     deepStrictEqual(u8Full, zeroes);\n\n//     // Test /dev/random\n//     const devRandom = await dev.getFileHandle('random');\n//     ok(devRandom instanceof FileSystemFileHandle);\n//     const syncRandom = await devRandom.createSyncAccessHandle();\n//     strictEqual(syncRandom.getSize(), 0);\n//     throws(() => syncRandom.write(enc.encode('hello world')), {\n//       message: 'Operation not permitted',\n//       name: 'NotAllowedError',\n//     });\n//     strictEqual(syncRandom.getSize(), 0);\n//     const u8Random = new Buffer.from([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);\n//     const check = new Buffer.from([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);\n//     strictEqual(syncRandom.read(u8Random), 11);\n//     notDeepStrictEqual(u8Random, check);\n//   },\n// };\n\nexport const asyncIteratorTest = {\n  async test() {\n    const dir = await navigator.storage.getDirectory();\n\n    let count = 0;\n    for await (const node of dir) {\n      // The node is an Array of [name, handle]\n      ok(node[1] instanceof FileSystemDirectoryHandle);\n      count++;\n    }\n    strictEqual(count, 3);\n\n    const names = await Array.fromAsync(dir.keys());\n    deepStrictEqual(names, ['bundle', 'tmp', 'dev']);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/webfs-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"webfs-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"webfs-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"enable_web_file_system\",\n          \"experimental\",\n          \"global_navigator\",\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/websocket-allow-half-open-test.js",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { strictEqual, doesNotThrow } from 'node:assert';\n\n// Test that when allowHalfOpen is false (default with compat flag), a server-initiated\n// close sets readyState to CLOSED.\nexport const autoCloseReplyWhenNotHalfOpen = {\n  async test() {\n    const pair = new WebSocketPair();\n    const [client, server] = Object.values(pair);\n\n    // accept() without options — allowHalfOpen defaults to false with the compat flag.\n    client.accept();\n    server.accept();\n\n    const closePromise = new Promise((resolve) => {\n      client.addEventListener('close', (event) => {\n        // When allowHalfOpen is false, the runtime auto-sends a close reply,\n        // so both closedIncoming and closedOutgoing are true — readyState should be CLOSED.\n        resolve({\n          readyState: client.readyState,\n          code: event.code,\n          wasClean: event.wasClean,\n        });\n      });\n    });\n\n    // Server initiates close.\n    server.close(1000, 'server closing');\n\n    const result = await closePromise;\n    strictEqual(result.readyState, WebSocket.CLOSED);\n    strictEqual(result.code, 1000);\n    strictEqual(result.wasClean, true);\n  },\n};\n\n// Test that calling close() inside the close event handler after the automatic close\n// handshake is silently ignored (doesn't throw). This is the realistic pattern for\n// users who are already manually replying to close frames today — when they update\n// their compat date and get web_socket_auto_reply_to_close, their existing close()\n// call must not break.\nexport const closeInsideHandlerAfterAutoCloseIsIgnored = {\n  async test() {\n    const pair = new WebSocketPair();\n    const [client, server] = Object.values(pair);\n\n    client.accept();\n    server.accept();\n\n    const closePromise = new Promise((resolve) => {\n      client.addEventListener('close', (event) => {\n        // This is what existing user code typically looks like: replying to close\n        // inside the close handler. With the auto-reply already sent, closedOutgoing\n        // is true but the native state is still Accepted (tryReleaseNative hasn't\n        // run yet), so this exercises the closedOutgoing early-return in close().\n        doesNotThrow(() => {\n          client.close(1000, 'manual reply inside handler');\n        });\n\n        resolve({\n          readyState: client.readyState,\n          code: event.code,\n          wasClean: event.wasClean,\n        });\n      });\n    });\n\n    // Server initiates close; the runtime auto-replies because allowHalfOpen is false.\n    server.close(1000, 'server closing');\n\n    const result = await closePromise;\n    strictEqual(result.readyState, WebSocket.CLOSED);\n    strictEqual(result.code, 1000);\n    strictEqual(result.wasClean, true);\n  },\n};\n\n// Same as above, but calling close() after the handler has returned and the native\n// WebSocket has been released. This exercises the state.is<Released>() early-return\n// in close(), which is a different code path from the closedOutgoing check above.\nexport const closeAfterAutoCloseAndReleaseIsIgnored = {\n  async test() {\n    const pair = new WebSocketPair();\n    const [client, server] = Object.values(pair);\n\n    client.accept();\n    server.accept();\n\n    const closePromise = new Promise((resolve) => {\n      client.addEventListener('close', (event) => {\n        resolve({\n          readyState: client.readyState,\n          code: event.code,\n          wasClean: event.wasClean,\n        });\n      });\n    });\n\n    // Server initiates close; the runtime auto-replies because allowHalfOpen is false.\n    server.close(1000, 'server closing');\n\n    const result = await closePromise;\n    strictEqual(result.readyState, WebSocket.CLOSED);\n\n    // By now tryReleaseNative has run and the state is Released.\n    // close() should still be silently ignored.\n    doesNotThrow(() => {\n      client.close(1000, 'manual reply after release');\n    });\n\n    strictEqual(client.readyState, WebSocket.CLOSED);\n  },\n};\n\n// Test that when allowHalfOpen is true via accept(), a server-initiated close sets\n// readyState to CLOSING.\nexport const halfOpenCloseKeepsClosingState = {\n  async test() {\n    const pair = new WebSocketPair();\n    const [client, server] = Object.values(pair);\n\n    // Opt into half-open mode via accept() options.\n    client.accept({ allowHalfOpen: true });\n    server.accept();\n\n    const closePromise = new Promise((resolve) => {\n      client.addEventListener('close', (event) => {\n        // When allowHalfOpen is true, no auto-reply is sent, so only closedIncoming\n        // is true — readyState should be CLOSING.\n        resolve({\n          readyState: client.readyState,\n          code: event.code,\n          wasClean: event.wasClean,\n        });\n      });\n    });\n\n    // Server initiates close.\n    server.close(1000, 'server closing');\n\n    const result = await closePromise;\n    strictEqual(result.readyState, WebSocket.CLOSING);\n    strictEqual(result.code, 1000);\n    strictEqual(result.wasClean, true);\n\n    // The client must manually close to complete the handshake.\n    client.close(1000, 'client reply');\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/websocket-allow-half-open-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"websocket-allow-half-open-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"websocket-allow-half-open-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"web_socket_auto_reply_to_close\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/websocket-client-error-sidecar.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nconst http = require('http');\nconst crypto = require('crypto');\n\n// Handle upgrade requests to switch from HTTP to WebSocket protocol\nfunction upgradeToWebSocketConnection(req, socket, head) {\n  // Check if it's a WebSocket upgrade request\n  if (req.headers['upgrade'] !== 'websocket') {\n    socket.end('HTTP/1.1 400 Bad Request');\n    return;\n  }\n\n  // Get the WebSocket key from the client\n  const webSocketKey = req.headers['sec-websocket-key'];\n  const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; // WebSocket protocol GUID\n\n  // Create the accept key by concatenating the key and GUID, then hashing\n  const acceptKey = crypto\n    .createHash('sha1')\n    .update(webSocketKey + GUID)\n    .digest('base64');\n\n  // Write WebSocket handshake response headers\n  socket.write(\n    'HTTP/1.1 101 Switching Protocols\\r\\n' +\n      'Upgrade: websocket\\r\\n' +\n      'Connection: Upgrade\\r\\n' +\n      `Sec-WebSocket-Accept: ${acceptKey}\\r\\n` +\n      '\\r\\n'\n  );\n\n  // Socket is now a WebSocket connection\n  handleWebSocketConnection(socket);\n}\n\n// Function to send a message to the client\nfunction sendMessage(socket, message) {\n  const payload = Buffer.from(message);\n  const payloadLength = payload.length;\n\n  // Create frame header\n  let header;\n\n  if (payloadLength <= 125) {\n    header = Buffer.alloc(2);\n    header[1] = payloadLength;\n  } else if (payloadLength <= 65535) {\n    header = Buffer.alloc(4);\n    header[1] = 126;\n    header.writeUInt16BE(payloadLength, 2);\n  } else {\n    header = Buffer.alloc(10);\n    header[1] = 127;\n    // Write length as 64-bit integer (simplified here)\n    header.writeBigUInt64BE(BigInt(payloadLength), 2);\n  }\n\n  // Set the first byte: FIN bit (0x80) + opcode 0x01 for text data\n  header[0] = 0x81;\n\n  // Combine header and payload\n  const frame = Buffer.concat([header, payload]);\n\n  // Send the frame\n  socket.write(frame);\n}\n\n// Get the port and host from environment variables\nconst port = process.env.BIG_MESSAGE_SERVER_PORT;\nconst host = process.env.SIDECAR_HOSTNAME;\n\nfunction reportAddress(server) {\n  const address = server.address();\n  console.info(`Listening on ${address.address}:${address.port}`);\n}\n\n// Create HTTP server to handle the WebSocket handshake\nconst server = http.createServer((req, res) => {\n  res.writeHead(200, { 'Content-Type': 'text/plain' });\n  res.end('WebSocket server is running');\n});\nserver.on('upgrade', upgradeToWebSocketConnection);\nserver.on('error', (event) => {\n  console.log(event);\n  console.log(event.message);\n});\n// Start the server\nserver.listen(port, host, () => reportAddress(server));\n\n// Function to handle WebSocket connections\nfunction handleWebSocketConnection(socket) {\n  // Send a welcome message\n  sendMessage(socket, new Uint8Array(33 * 1024 * 1024));\n}\n"
  },
  {
    "path": "src/workerd/api/tests/websocket-client-error-test.js",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// This test verifies that WebSocket protocol errors are properly converted to JavaScript errors\n// using the JsgifyWebSocketErrors handler.\n\nexport default {\n  async test(controller, env) {\n    const ws = new WebSocket(\n      `ws://${env.SIDECAR_HOSTNAME}:${env.BIG_MESSAGE_SERVER_PORT}/`\n    );\n    let receivedError = false;\n\n    // Set up a promise to wait for errors\n    const errorPromise = new Promise((resolve, reject) => {\n      // Set a timeout\n      const timeoutId = setTimeout(() => {\n        reject({\n          source: 'timeout',\n          message: 'No error received within timeout',\n        });\n      }, 5000);\n\n      // Handle errors\n      ws.addEventListener('error', (event) => {\n        clearTimeout(timeoutId);\n        receivedError = true;\n\n        if (\n          event.message ===\n          'Uncaught Error: WebSocket protocol error; protocolError.statusCode = 1009; protocolError.description = Message is too large: 34603008 > 33554432'\n        ) {\n          resolve({\n            source: 'client-error',\n            message: event.message,\n          });\n        } else {\n          reject({\n            source: 'client-error-malformed',\n            message: event.message,\n          });\n        }\n      });\n\n      ws.addEventListener('open', () => {});\n      // Handle close\n      ws.addEventListener('close', (event) => {\n        if (!(receivedError && event.code === 1009)) {\n          clearTimeout(timeoutId);\n          reject({\n            source: 'client-close',\n            code: event.code,\n            reason: event.reason,\n          });\n        }\n      });\n    });\n\n    // Wait for an error or timeout\n    await errorPromise;\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/websocket-client-error-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n    (name = \"internet\", network = (allow = [\"private\"])),\n  ],\n);\n\nconst mainWorker :Workerd.Worker = (\n  modules = [\n    (name = \"worker\", esModule = embed \"websocket-client-error-test.js\"),\n  ],\n  bindings = [\n    (name = \"SIDECAR_HOSTNAME\", fromEnvironment = \"SIDECAR_HOSTNAME\"),\n    (name = \"BIG_MESSAGE_SERVER_PORT\", fromEnvironment = \"BIG_MESSAGE_SERVER_PORT\"),\n  ]\n);\n"
  },
  {
    "path": "src/workerd/api/tests/websocket-constructor-test.js",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport {\n  deepStrictEqual,\n  doesNotThrow,\n  strictEqual,\n  throws,\n} from 'node:assert';\n\n// Test that WebSocket constructor handles empty protocols array correctly.\n// Per the WebSocket spec, an empty protocols array should be valid and equivalent\n// to not specifying any protocols.\n// See: https://github.com/cloudflare/workerd/issues/5822\nexport const emptyProtocolsArray = {\n  async test() {\n    const ws = new WebSocket('wss://example.com/', []);\n    strictEqual(ws.url, 'wss://example.com/');\n    strictEqual(ws.protocol, '');\n    strictEqual(ws.readyState, WebSocket.CONNECTING);\n    ws.close();\n  },\n};\n\n// Test that a single protocol string works\nexport const singleProtocolString = {\n  async test() {\n    const ws = new WebSocket('wss://example.com/', 'chat');\n    strictEqual(ws.url, 'wss://example.com/');\n    ws.close();\n  },\n};\n\n// Test that invalid protocol tokens still throw SyntaxError\nexport const invalidProtocolToken = {\n  async test() {\n    throws(\n      () => new WebSocket('wss://example.com/', 'invalid protocol with spaces'),\n      {\n        name: 'SyntaxError',\n      }\n    );\n  },\n};\n\n// Test that duplicate valid protocols throw SyntaxError\nexport const duplicateProtocols = {\n  async test() {\n    throws(() => new WebSocket('wss://example.com/', ['chat', 'chat']), {\n      name: 'SyntaxError',\n    });\n  },\n};\n\n// Test that close() throws SyntaxError when reason exceeds 123 bytes (UTF-8).\n// Per WHATWG WebSocket spec and RFC 6455 Section 5.5, the close frame body\n// must not exceed 125 bytes (2-byte code + up to 123 bytes of reason).\nexport const closeReasonTooLong = {\n  async test() {\n    const ws = new WebSocket('wss://example.com/');\n    // 124 ASCII bytes => 124 UTF-8 bytes, which exceeds the 123-byte limit.\n    const longReason = 'a'.repeat(124);\n    throws(() => ws.close(1000, longReason), {\n      name: 'SyntaxError',\n    });\n    ws.close();\n  },\n};\n\n// Test that close() accepts a reason of exactly 123 bytes.\nexport const closeReasonExact123Bytes = {\n  async test() {\n    const ws = new WebSocket('wss://example.com/');\n    const reason123 = 'a'.repeat(123);\n    doesNotThrow(() => ws.close(1000, reason123));\n  },\n};\n\n// Test that close() counts UTF-8 bytes, not characters.\n// U+00E9 (é) is 2 bytes in UTF-8. 62 of them = 124 bytes > 123 limit.\nexport const closeReasonMultibyteExceeds = {\n  async test() {\n    const ws = new WebSocket('wss://example.com/');\n    const multibyteReason = '\\u00e9'.repeat(62); // 62 chars × 2 bytes = 124 bytes\n    throws(() => ws.close(1000, multibyteReason), {\n      name: 'SyntaxError',\n    });\n    ws.close();\n  },\n};\n\n// Test that close() replaces lone surrogates with U+FFFD per the USVString spec.\n// This is tested end-to-end by the WPT Close-reason-unpaired-surrogates.any.js test.\n// Here we verify the CloseEvent constructor also applies USVString conversion.\nexport const closeReasonUSVString = {\n  async test() {\n    // CloseEvent.reason is typed as USVString per the HTML spec,\n    // so lone surrogates must be replaced with U+FFFD.\n    const evt = new CloseEvent('close', { code: 1000, reason: '\\uD807' });\n    strictEqual(\n      evt.reason,\n      '\\uFFFD',\n      'CloseEvent reason should replace lone surrogate with U+FFFD'\n    );\n\n    // Multiple surrogates in a string.\n    const evt2 = new CloseEvent('close', {\n      code: 1000,\n      reason: 'hello\\uD800world\\uDC00end',\n    });\n    strictEqual(\n      evt2.reason,\n      'hello\\uFFFDworld\\uFFFDend',\n      'CloseEvent reason should replace each lone surrogate with U+FFFD'\n    );\n\n    // Properly paired surrogates should pass through unchanged.\n    const evt3 = new CloseEvent('close', {\n      code: 1000,\n      reason: '\\uD83D\\uDE00',\n    });\n    strictEqual(\n      evt3.reason,\n      '\\uD83D\\uDE00',\n      'Properly paired surrogates should not be replaced'\n    );\n  },\n};\n\n// Test that close() with code 1000 succeeds (Normal Closure).\nexport const closeCode1000Succeeds = {\n  async test() {\n    const ws = new WebSocket('wss://example.com/');\n    doesNotThrow(() => ws.close(1000));\n  },\n};\n\n// Test that close() with codes in the 3000-4999 range succeeds.\nexport const closeCode3000To4999Succeeds = {\n  async test() {\n    for (const code of [3000, 3500, 4000, 4999]) {\n      const ws = new WebSocket('wss://example.com/');\n      doesNotThrow(() => ws.close(code));\n    }\n  },\n};\n\n// Test that close() rejects codes outside the spec-allowed set.\n// Per WHATWG WebSocket spec, only 1000 and 3000-4999 are valid.\nexport const closeCodeSpecInvalid = {\n  async test() {\n    for (const code of [999, 1001, 1004, 1005, 1006, 1015, 2999, 5000]) {\n      const ws = new WebSocket('wss://example.com/');\n      throws(\n        () => ws.close(code),\n        { name: 'InvalidAccessError' },\n        `close(${code}) should throw InvalidAccessError`\n      );\n      ws.close();\n    }\n  },\n};\n\n// Test that binaryType defaults to \"blob\" when websocket_standard_binary_type flag is on.\nexport const binaryTypeDefaultsToBlob = {\n  async test() {\n    const ws = new WebSocket('wss://example.com/');\n    strictEqual(ws.binaryType, 'blob');\n    ws.close();\n  },\n};\n\n// Test that binaryType on WebSocketPair also defaults to \"blob\".\nexport const binaryTypeWebSocketPairDefault = {\n  async test() {\n    const pair = new WebSocketPair();\n    strictEqual(pair[0].binaryType, 'blob');\n    strictEqual(pair[1].binaryType, 'blob');\n    // WebSocketPair sockets require accept() before close(), but we only\n    // need to verify the default value so no cleanup is needed.\n  },\n};\n\n// Test that binaryType setter accepts \"arraybuffer\" and \"blob\".\nexport const binaryTypeSetterValid = {\n  async test() {\n    const ws = new WebSocket('wss://example.com/');\n    strictEqual(ws.binaryType, 'blob');\n\n    ws.binaryType = 'arraybuffer';\n    strictEqual(ws.binaryType, 'arraybuffer');\n\n    ws.binaryType = 'blob';\n    strictEqual(ws.binaryType, 'blob');\n    ws.close();\n  },\n};\n\n// Test that binaryType setter silently ignores invalid values per spec.\nexport const binaryTypeSetterInvalid = {\n  async test() {\n    const ws = new WebSocket('wss://example.com/');\n    strictEqual(ws.binaryType, 'blob');\n\n    ws.binaryType = 'notBlobOrArrayBuffer';\n    strictEqual(ws.binaryType, 'blob');\n\n    ws.binaryType = '';\n    strictEqual(ws.binaryType, 'blob');\n\n    ws.binaryType = 'arraybuffer';\n    strictEqual(ws.binaryType, 'arraybuffer');\n\n    ws.binaryType = 'BLOB';\n    strictEqual(ws.binaryType, 'arraybuffer');\n    ws.close();\n  },\n};\n\n// Test that binary messages are delivered as Blob when binaryType is \"blob\".\nexport const binaryMessageDeliveredAsBlob = {\n  async test() {\n    const pair = new WebSocketPair();\n    const [client, server] = pair;\n    server.accept();\n\n    const data = new Uint8Array([1, 2, 3, 4, 5]);\n    const received = new Promise((resolve) => {\n      server.addEventListener('message', (event) => resolve(event.data));\n    });\n\n    client.accept();\n    client.send(data);\n\n    const msg = await received;\n    strictEqual(msg instanceof Blob, true);\n    deepStrictEqual(new Uint8Array(await msg.arrayBuffer()), data);\n\n    client.close();\n    server.close();\n  },\n};\n\n// Test that binary messages are delivered as ArrayBuffer when binaryType is \"arraybuffer\".\nexport const binaryMessageDeliveredAsArrayBuffer = {\n  async test() {\n    const pair = new WebSocketPair();\n    const [client, server] = pair;\n    server.accept();\n    server.binaryType = 'arraybuffer';\n\n    const data = new Uint8Array([10, 20, 30]);\n    const received = new Promise((resolve) => {\n      server.addEventListener('message', (event) => resolve(event.data));\n    });\n\n    client.accept();\n    client.send(data);\n\n    const msg = await received;\n    strictEqual(msg instanceof ArrayBuffer, true);\n    deepStrictEqual(new Uint8Array(msg), data);\n\n    client.close();\n    server.close();\n  },\n};\n\n// Test that switching binaryType mid-stream changes delivery format.\nexport const binaryTypeSwitchMidStream = {\n  async test() {\n    const pair = new WebSocketPair();\n    const [client, server] = pair;\n    server.accept();\n    client.accept();\n\n    // Default is \"blob\" with the compat flag.\n    strictEqual(server.binaryType, 'blob');\n\n    // First message: delivered as Blob.\n    {\n      const received = new Promise((resolve) => {\n        server.addEventListener('message', (event) => resolve(event.data), {\n          once: true,\n        });\n      });\n      client.send(new Uint8Array([1]));\n      const msg = await received;\n      strictEqual(msg instanceof Blob, true);\n    }\n\n    // Switch to arraybuffer.\n    server.binaryType = 'arraybuffer';\n\n    // Second message: delivered as ArrayBuffer.\n    {\n      const received = new Promise((resolve) => {\n        server.addEventListener('message', (event) => resolve(event.data), {\n          once: true,\n        });\n      });\n      client.send(new Uint8Array([2]));\n      const msg = await received;\n      strictEqual(msg instanceof ArrayBuffer, true);\n      deepStrictEqual(new Uint8Array(msg), new Uint8Array([2]));\n    }\n\n    client.close();\n    server.close();\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/websocket-constructor-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"websocket-constructor-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"websocket-constructor-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"websocket_close_reason_byte_limit\", \"pedantic_wpt\", \"websocket_standard_binary_type\"]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/websocket-hibernation.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// A simple test to confirm we can close() a websocket from the close handler.\nexport class DurableObjectExample {\n  constructor(state) {\n    this.reset = true;\n    this.state = state;\n  }\n\n  async fetch(request) {\n    // Confirm this is a websocket request.\n    const upgradeHeader = request.headers.get('Upgrade');\n    if (!upgradeHeader || upgradeHeader !== 'websocket') {\n      return new Response('Expected Upgrade: websocket', { status: 426 });\n    }\n\n    let pair = new WebSocketPair();\n    let server = pair[0];\n    if (request.url.endsWith('/hibernation')) {\n      this.state.acceptWebSocket(server);\n    } else {\n      server.accept();\n      server.addEventListener('message', () => {\n        server.send('regular message from DO');\n      });\n      server.addEventListener('close', () => {\n        server.close(1000, 'regular close from DO');\n      });\n    }\n\n    return new Response(null, {\n      status: 101,\n      webSocket: pair[1],\n    });\n  }\n\n  webSocketMessage(ws) {\n    ws.send(`Hibernatable message from DO.`);\n  }\n\n  webSocketClose(ws, code, reason, wasClean) {\n    ws.close(1000, 'Hibernatable close from DO');\n  }\n}\n\nexport const test = {\n  async test(ctrl, env, ctx) {\n    let id = env.ns.idFromName('foo');\n    let obj = env.ns.get(id);\n\n    // Test to make sure that we can call ws.close() from the DO's close handler.\n    let webSocketTest = async (obj, url, expected) => {\n      let req = await obj.fetch(url, {\n        headers: {\n          Upgrade: 'websocket',\n        },\n      });\n\n      let ws = req.webSocket;\n      if (!ws) {\n        return new Error('Failed to get ws');\n      }\n      ws.accept();\n      let prom = new Promise((resolve, reject) => {\n        ws.addEventListener('close', (close) => {\n          if ((close.code != 1000) & (close.reason != expected)) {\n            reject(`got ${close.reason}`);\n          }\n          resolve();\n        });\n      });\n\n      ws.send('Hi from Worker!');\n      ws.close(1000, 'bye from Worker!');\n\n      await prom;\n    };\n\n    // Normal websocket\n    await webSocketTest(obj, 'http://example.com/', 'regular close from DO');\n    // Hibernatable Websocket.\n    await webSocketTest(\n      obj,\n      'http://example.com/hibernation',\n      'Hibernatable close from DO'\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/websocket-hibernation.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n    (name = \"TEST_TMPDIR\", disk = (writable = true)),\n  ],\n);\n\nconst mainWorker :Workerd.Worker = (\n  compatibilityFlags = [\"experimental\", \"nodejs_compat\"],\n\n  modules = [\n    (name = \"worker\", esModule = embed \"websocket-hibernation.js\"),\n  ],\n\n  durableObjectNamespaces = [\n    (className = \"DurableObjectExample\", uniqueKey = \"210bd0cbd803ef7883a1ee9d86cce06e\"),\n  ],\n\n  durableObjectStorage = (localDisk = \"TEST_TMPDIR\"),\n\n  bindings = [\n    (name = \"ns\", durableObjectNamespace = \"DurableObjectExample\"),\n  ],\n);\n\n"
  },
  {
    "path": "src/workerd/api/tests/worker-loader-test.js",
    "content": "import { WorkerEntrypoint, DurableObject } from 'cloudflare:workers';\nimport assert from 'node:assert';\n\nexport class GreeterLoopback extends WorkerEntrypoint {\n  async greet(name) {\n    return `${this.ctx.props.greeting}, ${name}!`;\n  }\n}\n\nexport let basics = {\n  async test(ctrl, env, ctx) {\n    let worker = env.loader.get('basics', () => {\n      return {\n        compatibilityDate: '2025-01-01',\n        mainModule: 'foo.js',\n        modules: {\n          'foo.js': `\n            import {WorkerEntrypoint} from \"cloudflare:workers\";\n            export default {\n              greet(name) { return \"Hello, \" + name; }\n            }\n            export let alternate = {\n              greet(name, env, ctx) { return \\`\\${ctx.props.greeting}, \\${name}\\`; }\n            }\n            export class FancyPropsEntrypoint extends WorkerEntrypoint {\n              async run() {\n                let greet1 = await this.ctx.props.greeter.greet(\"Dave\");\n                let greet2 = await this.ctx.props.greeter2.greet(\"Eve\");\n                return [greet1, greet2].join(\"\\\\n\");\n              }\n            }\n          `,\n        },\n      };\n    });\n\n    {\n      let result = await worker.getEntrypoint().greet('Alice');\n      assert.strictEqual(result, 'Hello, Alice');\n    }\n\n    let greeter = worker.getEntrypoint('alternate', {\n      props: { greeting: 'Welcome' },\n    });\n    let greeter2 = worker.getEntrypoint('alternate', {\n      props: { greeting: 'Howdy' },\n    });\n\n    {\n      let result = await greeter.greet('Bob');\n      assert.strictEqual(result, 'Welcome, Bob');\n    }\n\n    {\n      let result = await greeter2.greet('Carol');\n      assert.strictEqual(result, 'Howdy, Carol');\n    }\n\n    let fancyProps = await worker.getEntrypoint('FancyPropsEntrypoint', {\n      props: {\n        greeter: ctx.exports.GreeterLoopback({ props: { greeting: \"G'day\" } }),\n        greeter2: ctx.exports.GreeterLoopback({ props: { greeting: 'Sup' } }),\n      },\n    });\n\n    {\n      let result = await fancyProps.run();\n      assert.strictEqual(result, \"G'day, Dave!\\nSup, Eve!\");\n    }\n\n    // Note that we CANNOT transfer a dynamic worker entrypoint.\n    assert.rejects(\n      () =>\n        worker.getEntrypoint('FancyPropsEntrypoint', {\n          props: { greeter, greeter2 },\n        }),\n      {\n        name: 'DataCloneError',\n        message:\n          'Entrypoints to dynamically-loaded workers cannot be transferred to other Workers, ' +\n          'because the system does not know how to reload this Worker from scratch. Instead, ' +\n          'have the parent Worker expose an entrypoint which constructs the dynamic worker ' +\n          'and forwards to it.',\n      }\n    );\n\n    // Let's quickly verify that if we use ctx.exports.GreeterLoopback *without* invoking it, then\n    // we get an error that it isn't serializable. (This isn't really testing worker-loader, it's\n    // more testing LoopbackServiceStub and that serializability is not inherited.)\n    assert.rejects(\n      () =>\n        worker.getEntrypoint('FancyPropsEntrypoint', {\n          props: {\n            greeter: ctx.exports.GreeterLoopback,\n            greeter2: ctx.exports.GreeterLoopback,\n          },\n        }),\n      {\n        name: 'DataCloneError',\n        message:\n          'Could not serialize object of type \"LoopbackServiceStub\". This type does not support ' +\n          'serialization.',\n      }\n    );\n  },\n};\n\nexport let pythonBasics = {\n  async test(ctrl, env, ctx) {\n    let worker = env.loader.get('pythonBasics', () => {\n      return {\n        compatibilityDate: '2025-01-01',\n        mainModule: 'foo.py',\n        compatibilityFlags: ['python_workers', 'python_no_global_handlers'],\n        modules: {\n          'foo.py': `\nfrom workers import WorkerEntrypoint\nclass Default(WorkerEntrypoint):\n  async def greet(self, name):\n    return \"Hello, \" + name\nclass alternate(WorkerEntrypoint):\n  async def greet(self, name):\n    return \"Welcome, \" + name\n          `,\n        },\n      };\n    });\n\n    {\n      let result = await worker.getEntrypoint().greet('Stacey');\n      assert.strictEqual(result, 'Hello, Stacey');\n    }\n\n    {\n      let result = await worker.getEntrypoint('alternate').greet('Anthony');\n      assert.strictEqual(result, 'Welcome, Anthony');\n    }\n  },\n};\n\n// Test supplying a basic `env` object.\nexport let passEnv = {\n  async test(ctrl, env, ctx) {\n    let worker = env.loader.get('passEnv', () => {\n      return {\n        compatibilityDate: '2025-01-01',\n        mainModule: 'foo.js',\n        modules: {\n          'foo.js': `\n            export default {\n              fetch(req, env, ctx) {\n                return new Response(\"env.hello = \" + env.hello);\n              },\n            }\n          `,\n        },\n        env: {\n          hello: 123,\n        },\n      };\n    });\n\n    let resp = await worker.getEntrypoint().fetch('https://example.com');\n    assert.strictEqual(await resp.text(), 'env.hello = 123');\n  },\n};\n\nexport let passEnvCaps = {\n  async test(ctrl, env, ctx) {\n    let worker = env.loader.get('passEnvCaps', () => {\n      return {\n        compatibilityDate: '2025-01-01',\n        mainModule: 'foo.js',\n        modules: {\n          'foo.js': `\n            export default {\n              async fetch(req, env, ctx) {\n                let greet1 = await env.greeter.greet(\"Alice\");\n                let greet2 = await env.greeter2.greet(\"Bob\");\n                return new Response([greet1, greet2].join(\"\\\\n\"));\n              },\n            }\n          `,\n        },\n        env: {\n          greeter: ctx.exports.GreeterLoopback({\n            props: { greeting: 'Hello' },\n          }),\n          greeter2: ctx.exports.GreeterLoopback({\n            props: { greeting: 'Welcome' },\n          }),\n        },\n      };\n    });\n\n    let resp = await worker.getEntrypoint().fetch('https://example.com');\n    assert.strictEqual(await resp.text(), 'Hello, Alice!\\nWelcome, Bob!');\n  },\n};\n\nexport let testOutbound = {\n  async fetch(req, env, ctx) {\n    return new Response('hello from testOutbound');\n  },\n};\n\n// Test overriding globalOutbound\nexport let overrideGlobalOutbound = {\n  async test(ctrl, env, ctx) {\n    let worker = env.loader.get('overrideGlobalOutbound', () => {\n      return {\n        compatibilityDate: '2025-01-01',\n        mainModule: 'foo.js',\n        modules: {\n          'foo.js': `\n            export default {\n              fetch(req, env, ctx) {\n                return fetch(req);\n              },\n            }\n          `,\n        },\n\n        // Set globalOutbound to redirect to our own `testOutbound` entrypoint.\n        globalOutbound: ctx.exports.testOutbound,\n      };\n    });\n\n    let resp = await worker.getEntrypoint().fetch('https://example.com');\n    assert.strictEqual(await resp.text(), 'hello from testOutbound');\n  },\n};\n\n// This test's own `globalOutbound` is configured to loop back to this entrypoint.\nexport let defaultOutbound = {\n  async fetch(req, env, ctx) {\n    return new Response('hello from defaultOutbound');\n  },\n};\n\n// Test that globalOutbound is inherited if not specified.\nexport let inheritGlobalOutbound = {\n  async test(ctrl, env, ctx) {\n    let worker = env.loader.get('inheritGlobalOutbound', () => {\n      return {\n        compatibilityDate: '2025-01-01',\n        mainModule: 'foo.js',\n        modules: {\n          'foo.js': `\n            export default {\n              fetch(req, env, ctx) {\n                return fetch(req);\n              },\n            }\n          `,\n        },\n\n        // Don't set `globalOutbound` at all.\n      };\n    });\n\n    let resp = await worker.getEntrypoint().fetch('https://example.com');\n    assert.strictEqual(await resp.text(), 'hello from defaultOutbound');\n  },\n};\n\n// Test setting `globalOutbound` to null.\nexport let nullGlobalOutbound = {\n  async test(ctrl, env, ctx) {\n    let worker = env.loader.get('nullGlobalOutbound', () => {\n      return {\n        compatibilityDate: '2025-01-01',\n        mainModule: 'foo.js',\n        modules: {\n          'foo.js': `\n            export default {\n              fetch(req, env, ctx) {\n                return fetch(req);\n              },\n            }\n          `,\n        },\n\n        globalOutbound: null,\n      };\n    });\n\n    await assert.rejects(worker.getEntrypoint().fetch('https://example.com'), {\n      name: 'Error',\n      message:\n        'This worker is not permitted to access the internet via global functions like fetch(). ' +\n        \"It must use capabilities (such as bindings in 'env') to talk to the outside world.\",\n    });\n  },\n};\n\n// We need to bounce the tail event through an actor to receive it back in the execution context\n// that set up the dynamic tail. `RendezvousActor` helps with that.\nexport class RendezvousActor extends DurableObject {\n  promiseAndResolvers = Promise.withResolvers();\n\n  resolve(value) {\n    this.promiseAndResolvers.resolve(value);\n  }\n\n  wait() {\n    return this.promiseAndResolvers.promise;\n  }\n}\n\nexport class TestTail extends WorkerEntrypoint {\n  async tail(event) {\n    // HACK: Currently, tail events are not serializable over RPC. :(\n    event = JSON.parse(JSON.stringify(event));\n\n    await this.ctx.exports.RendezvousActor.getByName('tails').resolve({\n      props: this.ctx.props,\n      event,\n    });\n  }\n}\n\n// Test tail worker on dynamically-loaded worker.\nexport let tails = {\n  async test(ctrl, env, ctx) {\n    let worker = env.loader.get('tails', () => {\n      return {\n        compatibilityDate: '2025-01-01',\n        mainModule: 'foo.js',\n        modules: {\n          'foo.js': `\n            export default {\n              fetch(req, env, ctx) {\n                console.log(\"hello, tail\");\n                return new Response(\"OK\");\n              },\n            }\n          `,\n        },\n\n        tails: [ctx.exports.TestTail({ props: { foo: 123 } })],\n      };\n    });\n\n    let resp = await worker.getEntrypoint().fetch('https://example.com');\n    assert.strictEqual(await resp.text(), 'OK');\n\n    let { props, event } =\n      await ctx.exports.RendezvousActor.getByName('tails').wait();\n    assert.deepEqual(props, { foo: 123 });\n    assert.strictEqual(event[0].logs[0].message[0], 'hello, tail');\n  },\n};\n\nexport class GreeterFacet extends DurableObject {\n  async greet(name) {\n    return `${this.ctx.props.greeting}, ${name}?`;\n  }\n}\n\nexport class FacetTestActor extends DurableObject {\n  async doTest() {\n    let worker = this.env.loader.get('facets', () => {\n      return {\n        compatibilityDate: '2025-01-01',\n        compatibilityFlags: ['experimental'],\n        allowExperimental: true,\n        mainModule: 'foo.js',\n        modules: {\n          'foo.js': `\n            import {DurableObject} from \"cloudflare:workers\";\n            export class MyActor extends DurableObject {\n              i = 0;\n\n              increment(j = 1) {\n                this.i += j;\n                return this.i;\n              }\n              myProps() {\n                let result = {...this.ctx.props};\n                delete result.propsGreeter;\n                return result;\n              }\n              async greets() {\n                let greeter1 = this.ctx.facets.get(\"greeter1\", () => {\n                  return { class: this.env.envGreeter };\n                });\n                let greeter2 = this.ctx.facets.get(\"greeter2\", () => {\n                  return { class: this.ctx.props.propsGreeter };\n                });\n                let greet1 = await greeter1.greet(\"Alice\");\n                let greet2 = await greeter2.greet(\"Bob\");\n                return [greet1, greet2].join(\"\\\\n\");\n              }\n            }\n          `,\n        },\n\n        // Set globalOutbound to redirect to our own `testOutbound` entrypoint.\n        globalOutbound: this.ctx.exports.testOutbound,\n\n        env: {\n          envGreeter: this.ctx.exports.GreeterFacet({\n            props: { greeting: 'Hello' },\n          }),\n        },\n      };\n    });\n\n    let cls = worker.getDurableObjectClass('MyActor', {\n      props: {\n        foo: 123,\n        bar: 456,\n        propsGreeter: this.ctx.exports.GreeterFacet({\n          props: { greeting: 'Welcome' },\n        }),\n      },\n    });\n\n    let facet = this.ctx.facets.get('bar', () => {\n      return { class: cls };\n    });\n\n    assert.strictEqual(await facet.increment(), 1);\n    assert.strictEqual(await facet.increment(4), 5);\n\n    assert.deepEqual(await facet.myProps(), { foo: 123, bar: 456 });\n\n    assert.strictEqual(await facet.greets(), 'Hello, Alice?\\nWelcome, Bob?');\n  }\n}\n\nexport let facets = {\n  async test(ctrl, env, ctx) {\n    let id = ctx.exports.FacetTestActor.idFromName('foo');\n    let stub = ctx.exports.FacetTestActor.get(id);\n    await stub.doTest();\n  },\n};\n\n// Test isolate uniqueness when loading the same name multiple times.\nexport let isolateUniqueness = {\n  async test(ctrl, env, ctx) {\n    let loadCount = 0;\n    let loadCodeCallback = () => {\n      ++loadCount;\n      return {\n        compatibilityDate: '2025-01-01',\n        mainModule: 'foo.js',\n        modules: {\n          'foo.js': `\n            import {WorkerEntrypoint} from \"cloudflare:workers\";\n            let i = 0;\n            export default class extends WorkerEntrypoint {\n              increment() {\n                return i++;\n              }\n            }\n          `,\n        },\n      };\n    };\n\n    let assertNotCalled = () => {\n      throw new Error(\n        'Code loader callback called when it should have reused an existing isolate.'\n      );\n    };\n\n    let name = 'isolateUniqueness';\n    let name2 = 'isolateUniquenessOtherName';\n\n    // Load the same name from all four of our loaders:\n    // - Two loaders with the same ID.\n    // - One with a unique ID.\n    // - One with no ID (anonymous).\n    let shared1 = env.sharedLoader1.get(name, loadCodeCallback).getEntrypoint();\n    let shared2 = env.sharedLoader2.get(name, assertNotCalled).getEntrypoint();\n    let unique = env.uniqueLoader.get(name, loadCodeCallback).getEntrypoint();\n    let anonymous = env.loader.get(name, loadCodeCallback).getEntrypoint();\n\n    // Also try loading a different name from various loaders.\n    let anonymousOtherName = env.loader\n      .get(name2, loadCodeCallback)\n      .getEntrypoint();\n    let sharedOtherName = env.sharedLoader1\n      .get(name2, loadCodeCallback)\n      .getEntrypoint();\n\n    // Same name from loaders with the same ID should go to the same isolate.\n    assert.strictEqual(await shared1.increment(), 0);\n    assert.strictEqual(await shared1.increment(), 1);\n    assert.strictEqual(await shared2.increment(), 2);\n    assert.strictEqual(await shared2.increment(), 3);\n\n    // Different IDs -> different isolates.\n    assert.strictEqual(await anonymous.increment(), 0);\n    assert.strictEqual(await anonymous.increment(), 1);\n    assert.strictEqual(await unique.increment(), 0);\n    assert.strictEqual(await unique.increment(), 1);\n\n    // Different names -> different isolates.\n    assert.strictEqual(await anonymousOtherName.increment(), 0);\n    assert.strictEqual(await anonymousOtherName.increment(), 1);\n    assert.strictEqual(await sharedOtherName.increment(), 0);\n    assert.strictEqual(await sharedOtherName.increment(), 1);\n\n    // Load the same name from the same loader -> same isolate.\n    let anonymousAgain = env.loader.get(name, assertNotCalled).getEntrypoint();\n    assert.strictEqual(await anonymousAgain.increment(), 2);\n    assert.strictEqual(await anonymousAgain.increment(), 3);\n\n    // Test that null/undefined name parameter creates unique isolates each time\n    // Each call should generate a unique name internally.\n    let noName1 = env.loader.get(null, loadCodeCallback).getEntrypoint();\n    let noName2 = env.loader.get(undefined, loadCodeCallback).getEntrypoint();\n    let noName3 = env.loader.get(undefined, loadCodeCallback).getEntrypoint();\n\n    // Each should be a different isolate\n    assert.strictEqual(await noName1.increment(), 0);\n    assert.strictEqual(await noName1.increment(), 1);\n    assert.strictEqual(await noName2.increment(), 0);\n    assert.strictEqual(await noName2.increment(), 1);\n    assert.strictEqual(await noName3.increment(), 0);\n    assert.strictEqual(await noName3.increment(), 1);\n\n    assert.strictEqual(loadCount, 8);\n  },\n};\n\n// Test non-string module types\nexport let moduleTypes = {\n  async test(ctrl, env, ctx) {\n    let worker = env.loader.get('moduleTypes', () => {\n      return {\n        compatibilityDate: '2025-01-01',\n        mainModule: 'main.js',\n        modules: {\n          'main.js': {\n            js: `\n              import {WorkerEntrypoint} from \"cloudflare:workers\";\n              import textModule from './text.txt';\n              import jsonModule from './data.json';\n              import cjsModule from './cjs.cjs';\n              import dataModule from './binary.dat';\n\n              export default class extends WorkerEntrypoint {\n                getTextModule() { return textModule; }\n                getJsonModule() { return jsonModule; }\n                getCjsModule() { return cjsModule.greet('Alice'); }\n                getDataModule() { return dataModule; }\n              }\n            `,\n          },\n          'text.txt': {\n            text: 'Hello from text module!',\n          },\n          'data.json': {\n            json: { message: 'Hello from JSON module!', value: 42 },\n          },\n          'cjs.cjs': {\n            cjs: `\n              module.exports = {\n                greet: function(name) {\n                  return 'Hello from CommonJS, ' + name;\n                }\n              };\n            `,\n          },\n          'binary.dat': {\n            data: new TextEncoder().encode('Hello from binary data!'),\n          },\n        },\n      };\n    });\n\n    let entrypoint = worker.getEntrypoint();\n\n    assert.strictEqual(\n      await entrypoint.getTextModule(),\n      'Hello from text module!'\n    );\n\n    let jsonData = await entrypoint.getJsonModule();\n    assert.strictEqual(jsonData.message, 'Hello from JSON module!');\n    assert.strictEqual(jsonData.value, 42);\n\n    assert.strictEqual(\n      await entrypoint.getCjsModule(),\n      'Hello from CommonJS, Alice'\n    );\n\n    let dataModule = await entrypoint.getDataModule();\n    let decoder = new TextDecoder();\n    assert.strictEqual(decoder.decode(dataModule), 'Hello from binary data!');\n  },\n};\n\n// Test WASM module support\nexport let wasmModules = {\n  async test(ctrl, env, ctx) {\n    // Create a simple WASM module that exports an add function\n    // (module (func (export \"add\") (param i32 i32) (result i32) (local.get 0) (local.get 1) (i32.add)))\n    const wasmBytes = new Uint8Array([\n      0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x60,\n      0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, 0x07, 0x01,\n      0x03, 0x61, 0x64, 0x64, 0x00, 0x00, 0x0a, 0x09, 0x01, 0x07, 0x00, 0x20,\n      0x00, 0x20, 0x01, 0x6a, 0x0b,\n    ]);\n\n    let worker = env.loader.get('wasmModules', () => {\n      return {\n        compatibilityDate: '2025-01-01',\n        mainModule: 'main.js',\n        modules: {\n          'main.js': {\n            js: `\n              import {WorkerEntrypoint} from \"cloudflare:workers\";\n              import wasmModule from './math.wasm';\n\n              export default class extends WorkerEntrypoint {\n                async getWasmAdd(a, b) {\n                  const instance = await WebAssembly.instantiate(wasmModule);\n                  return instance.exports.add(a, b);\n                }\n              }\n            `,\n          },\n          'math.wasm': {\n            wasm: wasmBytes,\n          },\n        },\n      };\n    });\n\n    let entrypoint = worker.getEntrypoint();\n\n    let result = await entrypoint.getWasmAdd(5, 7);\n    assert.strictEqual(result, 12);\n\n    result = await entrypoint.getWasmAdd(100, 42);\n    assert.strictEqual(result, 142);\n  },\n};\n\n// Test setting compat date / flags works\nexport let compatDateFlags = {\n  async test(ctrl, env, ctx) {\n    let getCode = (compatibilityDate, compatibilityFlags) => {\n      return {\n        compatibilityDate,\n        compatibilityFlags,\n        allowExperimental: true,\n        mainModule: 'main.js',\n        modules: {\n          'main.js': `\n            import {WorkerEntrypoint} from \"cloudflare:workers\";\n            export default class extends WorkerEntrypoint {\n              hasNodeCompat() {\n                return typeof process !== 'undefined';\n              }\n              canUseRequestCache() {\n                try {\n                  new Request(\"https://foo\", {cache: \"no-store\"});\n                  return true;\n                } catch {\n                  return false;\n                }\n              }\n            }\n          `,\n        },\n      };\n    };\n\n    // Test old date (2023), with node compat turned on.\n    {\n      let worker = env.loader.get('compatDateFlags1', () =>\n        getCode('2023-01-01', ['nodejs_compat', 'nodejs_compat_v2'])\n      );\n      let entrypoint = await worker.getEntrypoint();\n      assert.strictEqual(await entrypoint.hasNodeCompat(), true);\n      assert.strictEqual(await entrypoint.canUseRequestCache(), false);\n    }\n\n    // Test newer date (2025), with node compat not enabled.\n    {\n      let worker = env.loader.get('compatDateFlags2', () =>\n        getCode('2025-01-01', [])\n      );\n      let entrypoint = await worker.getEntrypoint();\n      assert.strictEqual(await entrypoint.hasNodeCompat(), false);\n      assert.strictEqual(await entrypoint.canUseRequestCache(), true);\n    }\n  },\n};\n\n// Test code loader callback that does something asynchronous\nexport let asyncCodeLoader = {\n  async test(ctrl, env, ctx) {\n    let worker = env.loader.get('asyncCodeLoader', async () => {\n      // Simulate async work\n      await new Promise((resolve) => setTimeout(resolve, 1));\n\n      return {\n        compatibilityDate: '2025-01-01',\n        mainModule: 'main.js',\n        modules: {\n          'main.js': `\n            import {WorkerEntrypoint} from \"cloudflare:workers\";\n            export default class extends WorkerEntrypoint {\n              getMessage() {\n                return 'Hello from async loaded worker!';\n              }\n            }\n          `,\n        },\n      };\n    });\n\n    let result = await worker.getEntrypoint().getMessage();\n    assert.strictEqual(result, 'Hello from async loaded worker!');\n  },\n};\n\n// Test what happens if the code getter callback throws an exception\nexport let codeLoaderException = {\n  async test(ctrl, env, ctx) {\n    let worker = env.loader.get('codeLoaderException', () => {\n      throw new Error('Code loader failed!');\n    });\n\n    let ep = worker.getEntrypoint();\n    try {\n      await ep.someMethod();\n      assert.fail('Expected exception to be thrown');\n    } catch (error) {\n      assert.strictEqual(error.message, 'Code loader failed!');\n    }\n  },\n};\n\n// Test that ctx.exports works in dynamically-loaded workers.\nexport let ctxExports = {\n  async test(ctrl, env, ctx) {\n    let worker = env.loader.get('ctxExports', () => {\n      return {\n        compatibilityDate: '2025-01-01',\n        compatibilityFlags: ['enable_ctx_exports'],\n        mainModule: 'foo.js',\n        allowExperimental: true,\n        modules: {\n          'foo.js': `\n            import {WorkerEntrypoint} from \"cloudflare:workers\";\n            export default class extends WorkerEntrypoint {\n              async greet(name) {\n                let greeting = await this.ctx.exports.AltEntrypoint.greet(name);\n                return greeting + \"!\";\n              }\n            }\n            export class AltEntrypoint extends WorkerEntrypoint {\n              greet(name) { return \"Hello, \" + name; }\n            }\n          `,\n        },\n      };\n    });\n\n    let result = await worker.getEntrypoint().greet('Alice');\n    assert.strictEqual(result, 'Hello, Alice!');\n  },\n};\n\nconst mixedModules = {\n  compatibilityDate: '2025-01-01',\n  modules: {\n    'foo.py': `\nfrom workers import WorkerEntrypoint\nclass Default(WorkerEntrypoint):\nasync def greet(self, name):\nreturn \"Hello, \" + name\n    `,\n    'foo.js': `\n      export default {\n        greet(name) { return \"Hello, \" + name; }\n      }\n    `,\n  },\n};\n\nexport let noMixedJsPythonModules = {\n  async test(ctrl, env, ctx) {\n    let worker = env.loader.get('noMixedJsPythonModules', () => {\n      return {\n        ...mixedModules,\n        mainModule: 'foo.py',\n      };\n    });\n\n    await assert.rejects(worker.getEntrypoint().greet('Alice'), {\n      name: 'TypeError',\n      message:\n        'Module \"foo.js\" is a JS module, but the main module is a Python module.',\n    });\n  },\n};\n\nexport let noMixedJsPythonModules2 = {\n  async test(ctrl, env, ctx) {\n    let worker = env.loader.get('noMixedJsPythonModules2', () => {\n      return {\n        ...mixedModules,\n        mainModule: 'foo.js',\n      };\n    });\n\n    await assert.rejects(worker.getEntrypoint().greet('Alice'), {\n      name: 'TypeError',\n      message:\n        'Module \"foo.py\" is a Python module, but the main module isn\\'t a Python module.',\n    });\n  },\n};\n\n// Test that startup exceptions show proper error messages, not internal errors.\nexport let startupException = {\n  async test(ctrl, env, ctx) {\n    let worker = env.loader.get('startupException', () => {\n      return {\n        compatibilityDate: '2025-01-01',\n        mainModule: 'main.js',\n        modules: {\n          'main.js': `\n            import {WorkerEntrypoint} from \"cloudflare:workers\";\n\n            // This will throw during startup (global scope)\n            throw new Error('intentional startup error');\n\n            export default class extends WorkerEntrypoint {\n              greet(name) {\n                return \"Hello, \" + name;\n              }\n            }\n          `,\n        },\n      };\n    });\n\n    let ep = worker.getEntrypoint();\n    try {\n      await ep.greet('Alice');\n      assert.fail('Expected exception to be thrown');\n    } catch (error) {\n      assert(\n        error.message.includes('intentional startup error'),\n        `Expected error message to contain 'intentional startup error', but got: ${error.message}`\n      );\n\n      // The error should NOT be a generic internal error\n      assert(\n        !error.message.includes('internal error; reference'),\n        `Error should not be a generic internal error, got: ${error.message}`\n      );\n    }\n  },\n};\n\nexport let justLoad = {\n  async test(ctrl, env, ctx) {\n    let worker = env.loader.load({\n      compatibilityDate: '2025-01-01',\n      mainModule: 'foo.js',\n      modules: {\n        'foo.js': `\n          import {WorkerEntrypoint} from \"cloudflare:workers\";\n          export default {\n            greet(name) { return \"Hello, \" + name; }\n          }\n          export let alternate = {\n            greet(name, env, ctx) { return \\`\\${ctx.props.greeting}, \\${name}\\`; }\n          }\n          export class FancyPropsEntrypoint extends WorkerEntrypoint {\n            async run() {\n              let greet1 = await this.ctx.props.greeter.greet(\"Dave\");\n              let greet2 = await this.ctx.props.greeter2.greet(\"Eve\");\n              return [greet1, greet2].join(\"\\\\n\");\n            }\n          }\n        `,\n      },\n    });\n\n    {\n      let result = await worker.getEntrypoint().greet('Alice');\n      assert.strictEqual(result, 'Hello, Alice');\n    }\n\n    let greeter = worker.getEntrypoint('alternate', {\n      props: { greeting: 'Welcome' },\n    });\n    let greeter2 = worker.getEntrypoint('alternate', {\n      props: { greeting: 'Howdy' },\n    });\n\n    {\n      let result = await greeter.greet('Bob');\n      assert.strictEqual(result, 'Welcome, Bob');\n    }\n\n    {\n      let result = await greeter2.greet('Carol');\n      assert.strictEqual(result, 'Howdy, Carol');\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/worker-loader-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"worker-loader-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"worker-loader-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\",\"experimental\",\"enable_ctx_exports\"],\n        bindings = [\n          (name = \"loader\", workerLoader = ()),\n          (name = \"sharedLoader1\", workerLoader = (id = \"shared\")),\n          (name = \"sharedLoader2\", workerLoader = (id = \"shared\")),\n          (name = \"uniqueLoader\", workerLoader = (id = \"nonshared\")),\n        ],\n        durableObjectNamespaces = [\n          (className = \"FacetTestActor\", uniqueKey = \"FacetTestActor\"),\n          (className = \"RendezvousActor\", uniqueKey = \"RendezvousActor\"),\n        ],\n        durableObjectStorage = (inMemory = void),\n        globalOutbound = (name = \"worker-loader-test\", entrypoint = \"defaultOutbound\"),\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/tests/worker-test.js",
    "content": "import { throws, rejects, strictEqual } from 'node:assert';\nimport { mock } from 'node:test';\n\n// We allow eval and new Function() during startup (global scope evaluation).\nstrictEqual(eval('1+1'), 2); // should work\nconst StartupFunction = new Function('return 1+1');\nstrictEqual(StartupFunction(), 2); // should work\nawait Promise.resolve();\nstrictEqual(eval('1+1'), 2); // should still work\nconst StartupFunction2 = new Function('return 2+2');\nstrictEqual(StartupFunction2(), 4); // should still work\nstrictEqual((await import('module-does-eval')).default, 2); // should work\n\nexport const testStartupEval = {\n  async test() {\n    throws(() => eval('console.log(\"This should not work\")'), {\n      message: /Code generation from strings disallowed for this context/,\n    });\n\n    throws(() => new Function('console.log(\"This should not work\")'), {\n      message: /Code generation from strings disallowed for this context/,\n    });\n\n    await rejects(() => import('another-module-does-eval'), {\n      message: /Code generation from strings disallowed for this context/,\n    });\n\n    // eval() with no args or a non-string arg is allowed (V8 returns the value\n    // as-is per spec since there is no string to compile).\n    strictEqual(eval(), undefined);\n    strictEqual(eval(undefined), undefined);\n\n    // new Function() with params and a string body is still blocked.\n    throws(() => new Function('a', 'b', 'return a + b'), {\n      message: /Code generation from strings disallowed for this context/,\n    });\n\n    // new Function() with params and undefined body is also blocked (the body\n    // becomes the string \"undefined\" via ToString).\n    throws(() => new Function('a', 'b', undefined), {\n      message: /Code generation from strings disallowed for this context/,\n    });\n\n    // new Function() with no arguments is allowed (the synthesized source\n    // matches the known empty-body, no-parameter pattern).\n    const emptyFn = new Function();\n    strictEqual(typeof emptyFn, 'function');\n\n    // Extending Function with super() and no arguments is also allowed.\n    class Foo extends Function {\n      constructor() {\n        super();\n      }\n    }\n    const foo = new Foo();\n    strictEqual(typeof foo, 'function');\n  },\n};\n\n// Test verifies that explicit resource management is, in fact, enabled.\nexport const disposeTest = {\n  test() {\n    const fn = mock.fn();\n    {\n      using _ = {\n        [Symbol.dispose]() {\n          fn();\n        },\n      };\n    }\n    strictEqual(fn.mock.callCount(), 1);\n  },\n};\n\nexport const asyncDisposeTest = {\n  async test() {\n    const fn = mock.fn(async () => {});\n    {\n      await using _ = {\n        async [Symbol.asyncDispose]() {\n          await fn();\n        },\n      };\n    }\n    strictEqual(fn.mock.callCount(), 1);\n  },\n};\n"
  },
  {
    "path": "src/workerd/api/tests/worker-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"worker-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"worker-test.js\"),\n          (name = \"module-does-eval\", esModule = \"export default eval('1+1')\"),\n          (name = \"another-module-does-eval\", esModule = \"export default eval('2+2')\"),\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"allow_eval_during_startup\",\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/api/trace.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"trace.h\"\n\n#include <workerd/api/global-scope.h>\n#include <workerd/api/http.h>\n#include <workerd/api/util.h>\n#include <workerd/io/io-context.h>\n#include <workerd/io/tracer.h>\n#include <workerd/jsg/ser.h>\n#include <workerd/util/own-util.h>\n#include <workerd/util/thread-scopes.h>\n#include <workerd/util/uncaught-exception-source.h>\n#include <workerd/util/uuid.h>\n\n#include <capnp/schema.h>\n#include <kj/encoding.h>\n\nnamespace workerd::api {\n\nTailEvent::TailEvent(\n    jsg::Lock& js, kj::LiteralStringConst type, kj::ArrayPtr<kj::Own<Trace>> events)\n    : ExtendableEvent(type),\n      events(KJ_MAP(e, events) -> jsg::Ref<TraceItem> { return js.alloc<TraceItem>(js, *e); }) {}\n\nkj::Array<jsg::Ref<TraceItem>> TailEvent::getEvents() {\n  return KJ_MAP(e, events) -> jsg::Ref<TraceItem> { return e.addRef(); };\n}\n\nnamespace {\nkj::Maybe<double> getTraceTimestamp(const Trace& trace) {\n  if (trace.eventTimestamp == kj::UNIX_EPOCH) {\n    return kj::none;\n  }\n  if (isPredictableModeForTest()) {\n    return 0.0;\n  }\n  return (trace.eventTimestamp - kj::UNIX_EPOCH) / kj::MILLISECONDS;\n}\n\ndouble getTraceLogTimestamp(const tracing::Log& log) {\n  if (isPredictableModeForTest()) {\n    return 0;\n  } else {\n    return (log.timestamp - kj::UNIX_EPOCH) / kj::MILLISECONDS;\n  }\n}\n\ndouble getTraceDiagnosticChannelEventTimestamp(const tracing::DiagnosticChannelEvent& event) {\n  if (isPredictableModeForTest()) {\n    return 0;\n  } else {\n    return (event.timestamp - kj::UNIX_EPOCH) / kj::MILLISECONDS;\n  }\n}\n\nkj::LiteralStringConst getTraceLogLevel(const tracing::Log& log) {\n  switch (log.logLevel) {\n    case LogLevel::DEBUG_:\n      return \"debug\"_kjc;\n    case LogLevel::INFO:\n      return \"info\"_kjc;\n    case LogLevel::LOG:\n      return \"log\"_kjc;\n    case LogLevel::WARN:\n      return \"warn\"_kjc;\n    case LogLevel::ERROR:\n      return \"error\"_kjc;\n  }\n  KJ_UNREACHABLE;\n}\n\njsg::V8Ref<v8::Object> getTraceLogMessage(jsg::Lock& js, const tracing::Log& log) {\n  return js.parseJson(log.message).cast<v8::Object>(js);\n}\n\nkj::Array<jsg::Ref<TraceLog>> getTraceLogs(jsg::Lock& js, const Trace& trace) {\n  return KJ_MAP(x, trace.logs) -> jsg::Ref<TraceLog> { return js.alloc<TraceLog>(js, trace, x); };\n}\n\nkj::Array<jsg::Ref<TraceDiagnosticChannelEvent>> getTraceDiagnosticChannelEvents(\n    jsg::Lock& js, const Trace& trace) {\n  return KJ_MAP(x, trace.diagnosticChannelEvents) -> jsg::Ref<TraceDiagnosticChannelEvent> {\n    return js.alloc<TraceDiagnosticChannelEvent>(trace, x);\n  };\n}\n\nkj::Maybe<ScriptVersion> getTraceScriptVersion(const Trace& trace) {\n  return trace.scriptVersion.map([](const auto& version) { return ScriptVersion(*version); });\n}\n\ndouble getTraceExceptionTimestamp(const tracing::Exception& ex) {\n  if (isPredictableModeForTest()) {\n    return 0;\n  } else {\n    return (ex.timestamp - kj::UNIX_EPOCH) / kj::MILLISECONDS;\n  }\n}\n\nkj::Array<jsg::Ref<TraceException>> getTraceExceptions(jsg::Lock& js, const Trace& trace) {\n  return KJ_MAP(x, trace.exceptions) -> jsg::Ref<TraceException> { return js.alloc<TraceException>(trace, x); };\n}\n\njsg::Optional<kj::Array<kj::String>> getTraceScriptTags(const Trace& trace) {\n  if (trace.scriptTags.size() > 0) {\n    return KJ_MAP(t, trace.scriptTags) -> kj::String { return kj::str(t); };\n  } else {\n    return kj::none;\n  }\n}\n\nTraceItem::TailAttributeValue getTraceTailAttributeValue(const tracing::Attribute& tag) {\n  KJ_REQUIRE(tag.value.size() == 1, \"tail attributes must contain exactly one value\");\n\n  KJ_SWITCH_ONEOF(tag.value[0]) {\n    KJ_CASE_ONEOF(boolean, bool) {\n      return TraceItem::TailAttributeValue(boolean);\n    }\n    KJ_CASE_ONEOF(number, double) {\n      return TraceItem::TailAttributeValue(number);\n    }\n    KJ_CASE_ONEOF(integer, int64_t) {\n      return TraceItem::TailAttributeValue(static_cast<double>(integer));\n    }\n    KJ_CASE_ONEOF(string, kj::ConstString) {\n      return TraceItem::TailAttributeValue(kj::str(string));\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::Own<TraceItem::FetchEventInfo::Request::Detail> getFetchRequestDetail(\n    jsg::Lock& js, const Trace& trace, const tracing::FetchEventInfo& eventInfo) {\n  const auto getCf = [&]() -> jsg::Optional<jsg::V8Ref<v8::Object>> {\n    const auto& cfJson = eventInfo.cfJson;\n    if (cfJson.size() > 0) {\n      return js.parseJson(cfJson).cast<v8::Object>(js);\n    }\n    return kj::none;\n  };\n\n  const auto getHeaders = [&]() -> kj::Array<tracing::FetchEventInfo::Header> {\n    return KJ_MAP(header, eventInfo.headers) {\n      return tracing::FetchEventInfo::Header(kj::str(header.name), kj::str(header.value));\n    };\n  };\n\n  return kj::refcounted<TraceItem::FetchEventInfo::Request::Detail>(\n      getCf(), getHeaders(), kj::str(eventInfo.method), kj::str(eventInfo.url));\n}\n\nkj::Maybe<TraceItem::EventInfo> getTraceEvent(jsg::Lock& js, const Trace& trace) {\n  KJ_IF_SOME(e, trace.eventInfo) {\n    KJ_SWITCH_ONEOF(e) {\n      KJ_CASE_ONEOF(fetch, tracing::FetchEventInfo) {\n        return kj::Maybe(\n            js.alloc<TraceItem::FetchEventInfo>(js, trace, fetch, trace.fetchResponseInfo));\n      }\n      KJ_CASE_ONEOF(jsRpc, tracing::JsRpcEventInfo) {\n        return kj::Maybe(js.alloc<TraceItem::JsRpcEventInfo>(trace, jsRpc));\n      }\n      KJ_CASE_ONEOF(scheduled, tracing::ScheduledEventInfo) {\n        return kj::Maybe(js.alloc<TraceItem::ScheduledEventInfo>(trace, scheduled));\n      }\n      KJ_CASE_ONEOF(alarm, tracing::AlarmEventInfo) {\n        return kj::Maybe(js.alloc<TraceItem::AlarmEventInfo>(trace, alarm));\n      }\n      KJ_CASE_ONEOF(queue, tracing::QueueEventInfo) {\n        return kj::Maybe(js.alloc<TraceItem::QueueEventInfo>(trace, queue));\n      }\n      KJ_CASE_ONEOF(email, tracing::EmailEventInfo) {\n        return kj::Maybe(js.alloc<TraceItem::EmailEventInfo>(trace, email));\n      }\n      KJ_CASE_ONEOF(tracedTrace, tracing::TraceEventInfo) {\n        return kj::Maybe(js.alloc<TraceItem::TailEventInfo>(js, trace, tracedTrace));\n      }\n      KJ_CASE_ONEOF(hibWs, tracing::HibernatableWebSocketEventInfo) {\n        KJ_SWITCH_ONEOF(hibWs.type) {\n          KJ_CASE_ONEOF(message, tracing::HibernatableWebSocketEventInfo::Message) {\n            return kj::Maybe(\n                js.alloc<TraceItem::HibernatableWebSocketEventInfo>(js, trace, message));\n          }\n          KJ_CASE_ONEOF(close, tracing::HibernatableWebSocketEventInfo::Close) {\n            return kj::Maybe(js.alloc<TraceItem::HibernatableWebSocketEventInfo>(js, trace, close));\n          }\n          KJ_CASE_ONEOF(error, tracing::HibernatableWebSocketEventInfo::Error) {\n            return kj::Maybe(js.alloc<TraceItem::HibernatableWebSocketEventInfo>(js, trace, error));\n          }\n        }\n        KJ_UNREACHABLE;\n      }\n      KJ_CASE_ONEOF(custom, tracing::CustomEventInfo) {\n        return kj::Maybe(js.alloc<TraceItem::CustomEventInfo>(trace, custom));\n      }\n    }\n  }\n  return kj::none;\n}\n}  // namespace\n\nTraceItem::TraceItem(jsg::Lock& js, const Trace& trace)\n    : eventInfo(getTraceEvent(js, trace)),\n      eventTimestamp(getTraceTimestamp(trace)),\n      logs(getTraceLogs(js, trace)),\n      exceptions(getTraceExceptions(js, trace)),\n      diagnosticChannelEvents(getTraceDiagnosticChannelEvents(js, trace)),\n      scriptName(mapCopyString(trace.scriptName)),\n      entrypoint(mapCopyString(trace.entrypoint)),\n      scriptVersion(getTraceScriptVersion(trace)),\n      dispatchNamespace(mapCopyString(trace.dispatchNamespace)),\n      scriptTags(getTraceScriptTags(trace)),\n      tailAttributes(trace.tailAttributes.map(\n          [](auto& tags) { return KJ_MAP(tag, tags) { return tag.clone(); }; })),\n      durableObjectId(mapCopyString(trace.durableObjectId)),\n      executionModel(kj::str(trace.executionModel)),\n      outcome(kj::str(trace.outcome)),\n      cpuTime(trace.cpuTime / kj::MILLISECONDS),\n      wallTime(trace.wallTime / kj::MILLISECONDS),\n      truncated(trace.truncated) {}\n\nkj::Maybe<TraceItem::EventInfo> TraceItem::getEvent(jsg::Lock& js) {\n  return eventInfo.map([](auto& info) -> TraceItem::EventInfo {\n    KJ_SWITCH_ONEOF(info) {\n      KJ_CASE_ONEOF(info, jsg::Ref<FetchEventInfo>) {\n        return info.addRef();\n      }\n      KJ_CASE_ONEOF(info, jsg::Ref<JsRpcEventInfo>) {\n        return info.addRef();\n      }\n      KJ_CASE_ONEOF(info, jsg::Ref<ScheduledEventInfo>) {\n        return info.addRef();\n      }\n      KJ_CASE_ONEOF(info, jsg::Ref<AlarmEventInfo>) {\n        return info.addRef();\n      }\n      KJ_CASE_ONEOF(info, jsg::Ref<QueueEventInfo>) {\n        return info.addRef();\n      }\n      KJ_CASE_ONEOF(info, jsg::Ref<EmailEventInfo>) {\n        return info.addRef();\n      }\n      KJ_CASE_ONEOF(info, jsg::Ref<TailEventInfo>) {\n        return info.addRef();\n      }\n      KJ_CASE_ONEOF(info, jsg::Ref<HibernatableWebSocketEventInfo>) {\n        return info.addRef();\n      }\n      KJ_CASE_ONEOF(info, jsg::Ref<CustomEventInfo>) {\n        return info.addRef();\n      }\n    }\n    KJ_UNREACHABLE;\n  });\n}\n\nkj::Maybe<double> TraceItem::getEventTimestamp() {\n  return eventTimestamp;\n}\n\nkj::ArrayPtr<jsg::Ref<TraceLog>> TraceItem::getLogs() {\n  return logs;\n}\n\nkj::ArrayPtr<jsg::Ref<TraceException>> TraceItem::getExceptions() {\n  return exceptions;\n}\n\nkj::ArrayPtr<jsg::Ref<TraceDiagnosticChannelEvent>> TraceItem::getDiagnosticChannelEvents() {\n  return diagnosticChannelEvents;\n}\n\nkj::Maybe<kj::StringPtr> TraceItem::getScriptName() {\n  return scriptName.map([](auto& name) -> kj::StringPtr { return name; });\n}\n\njsg::Optional<kj::StringPtr> TraceItem::getEntrypoint() {\n  return entrypoint;\n}\n\njsg::Optional<ScriptVersion> TraceItem::getScriptVersion() {\n  return scriptVersion;\n}\n\njsg::Optional<kj::StringPtr> TraceItem::getDispatchNamespace() {\n  return dispatchNamespace.map([](auto& ns) -> kj::StringPtr { return ns; });\n}\n\njsg::Optional<kj::Array<kj::StringPtr>> TraceItem::getScriptTags() {\n  return scriptTags.map(\n      [](kj::Array<kj::String>& tags) { return KJ_MAP(t, tags) -> kj::StringPtr { return t; }; });\n}\n\njsg::Optional<jsg::Dict<TraceItem::TailAttributeValue>> TraceItem::getTailAttributes() {\n  return tailAttributes.map([](kj::Array<tracing::Attribute>& tags) {\n    return jsg::Dict<TraceItem::TailAttributeValue>{\n      .fields =\n          KJ_MAP(tag, tags) {\n      return jsg::Dict<TraceItem::TailAttributeValue>::Field{\n        .name = kj::str(tag.name),\n        .value = getTraceTailAttributeValue(tag),\n      };\n    },\n    };\n  });\n}\n\njsg::Optional<kj::StringPtr> TraceItem::getDurableObjectId() {\n  return durableObjectId.map([](auto& id) -> kj::StringPtr { return id; });\n}\n\nkj::StringPtr TraceItem::getExecutionModel() {\n  return executionModel;\n}\n\nkj::StringPtr TraceItem::getOutcome() {\n  return outcome;\n}\n\nbool TraceItem::getTruncated() {\n  return truncated;\n}\n\nuint TraceItem::getCpuTime() {\n  return cpuTime;\n}\n\nuint TraceItem::getWallTime() {\n  return wallTime;\n}\n\nTraceItem::FetchEventInfo::FetchEventInfo(jsg::Lock& js,\n    const Trace& trace,\n    const tracing::FetchEventInfo& eventInfo,\n    kj::Maybe<const tracing::FetchResponseInfo&> responseInfo)\n    : request(js.alloc<Request>(js, trace, eventInfo)),\n      response(responseInfo.map([&](auto& info) { return js.alloc<Response>(trace, info); })) {}\n\nTraceItem::FetchEventInfo::Request::Detail::Detail(jsg::Optional<jsg::V8Ref<v8::Object>> cf,\n    kj::Array<tracing::FetchEventInfo::Header> headers,\n    kj::String method,\n    kj::String url)\n    : cf(kj::mv(cf)),\n      headers(kj::mv(headers)),\n      method(kj::mv(method)),\n      url(kj::mv(url)) {}\n\njsg::Ref<TraceItem::FetchEventInfo::Request> TraceItem::FetchEventInfo::getRequest() {\n  return request.addRef();\n}\n\njsg::Optional<jsg::Ref<TraceItem::FetchEventInfo::Response>> TraceItem::FetchEventInfo::\n    getResponse() {\n  return response.map([](auto& ref) mutable -> jsg::Ref<TraceItem::FetchEventInfo::Response> {\n    return ref.addRef();\n  });\n}\n\nTraceItem::FetchEventInfo::Request::Request(\n    jsg::Lock& js, const Trace& trace, const tracing::FetchEventInfo& eventInfo)\n    : detail(getFetchRequestDetail(js, trace, eventInfo)) {}\n\nTraceItem::FetchEventInfo::Request::Request(Detail& detail, bool redacted)\n    : redacted(redacted),\n      detail(kj::addRef(detail)) {}\n\njsg::Optional<jsg::V8Ref<v8::Object>> TraceItem::FetchEventInfo::Request::getCf(jsg::Lock& js) {\n  return detail->cf.map([&](jsg::V8Ref<v8::Object>& obj) { return obj.addRef(js); });\n}\n\njsg::Dict<kj::String, kj::String> TraceItem::FetchEventInfo::Request::getHeaders(jsg::Lock& js) {\n  auto shouldRedact = [](kj::StringPtr name) {\n    return (\n        //(name == \"authorization\"_kj) || // covered below\n        (name == \"cookie\"_kj) || (name == \"set-cookie\"_kj) || name.contains(\"auth\"_kjc) ||\n        name.contains(\"jwt\"_kjc) || name.contains(\"key\"_kjc) || name.contains(\"secret\"_kjc) ||\n        name.contains(\"token\"_kjc));\n  };\n\n  using HeaderDict = jsg::Dict<kj::String, kj::String>;\n  auto builder = kj::heapArrayBuilder<HeaderDict::Field>(detail->headers.size());\n  for (const auto& header: detail->headers) {\n    auto v = (redacted && shouldRedact(header.name)) ? \"REDACTED\"_kj : header.value;\n    builder.add(HeaderDict::Field{kj::str(header.name), kj::str(v)});\n  }\n\n  // TODO(conform): Better to return a frozen JS Object?\n  return HeaderDict{builder.finish()};\n}\n\nkj::StringPtr TraceItem::FetchEventInfo::Request::getMethod() {\n  return detail->method;\n}\n\nkj::String TraceItem::FetchEventInfo::Request::getUrl() {\n  return (redacted ? redactUrl(detail->url) : kj::str(detail->url));\n}\n\njsg::Ref<TraceItem::FetchEventInfo::Request> TraceItem::FetchEventInfo::Request::getUnredacted(\n    jsg::Lock& js) {\n  return js.alloc<Request>(*detail, false /* details are not redacted */);\n}\n\nTraceItem::FetchEventInfo::Response::Response(\n    const Trace& trace, const tracing::FetchResponseInfo& responseInfo)\n    : status(responseInfo.statusCode) {}\n\nuint16_t TraceItem::FetchEventInfo::Response::getStatus() {\n  return status;\n}\n\nTraceItem::JsRpcEventInfo::JsRpcEventInfo(\n    const Trace& trace, const tracing::JsRpcEventInfo& eventInfo)\n    : rpcMethod(kj::str(eventInfo.methodName)) {}\n\nkj::StringPtr TraceItem::JsRpcEventInfo::getRpcMethod() {\n  return rpcMethod;\n}\n\nTraceItem::ScheduledEventInfo::ScheduledEventInfo(\n    const Trace& trace, const tracing::ScheduledEventInfo& eventInfo)\n    : scheduledTime(eventInfo.scheduledTime),\n      cron(kj::str(eventInfo.cron)) {}\n\ndouble TraceItem::ScheduledEventInfo::getScheduledTime() {\n  return scheduledTime;\n}\nkj::StringPtr TraceItem::ScheduledEventInfo::getCron() {\n  return cron;\n}\n\nTraceItem::AlarmEventInfo::AlarmEventInfo(\n    const Trace& trace, const tracing::AlarmEventInfo& eventInfo)\n    : scheduledTime(eventInfo.scheduledTime) {}\n\nkj::Date TraceItem::AlarmEventInfo::getScheduledTime() {\n  return scheduledTime;\n}\n\nTraceItem::QueueEventInfo::QueueEventInfo(\n    const Trace& trace, const tracing::QueueEventInfo& eventInfo)\n    : queueName(kj::str(eventInfo.queueName)),\n      batchSize(eventInfo.batchSize) {}\n\nkj::StringPtr TraceItem::QueueEventInfo::getQueueName() {\n  return queueName;\n}\n\nuint32_t TraceItem::QueueEventInfo::getBatchSize() {\n  return batchSize;\n}\n\nTraceItem::EmailEventInfo::EmailEventInfo(\n    const Trace& trace, const tracing::EmailEventInfo& eventInfo)\n    : mailFrom(kj::str(eventInfo.mailFrom)),\n      rcptTo(kj::str(eventInfo.rcptTo)),\n      rawSize(eventInfo.rawSize) {}\n\nkj::StringPtr TraceItem::EmailEventInfo::getMailFrom() {\n  return mailFrom;\n}\n\nkj::StringPtr TraceItem::EmailEventInfo::getRcptTo() {\n  return rcptTo;\n}\n\nuint32_t TraceItem::EmailEventInfo::getRawSize() {\n  return rawSize;\n}\n\nkj::Array<jsg::Ref<TraceItem::TailEventInfo::TailItem>> getConsumedEventsFromEventInfo(\n    jsg::Lock& js, const tracing::TraceEventInfo& eventInfo) {\n  return KJ_MAP(t, eventInfo.traces) -> jsg::Ref<TraceItem::TailEventInfo::TailItem> {\n    return js.alloc<TraceItem::TailEventInfo::TailItem>(t);\n  };\n}\n\nTraceItem::TailEventInfo::TailEventInfo(\n    jsg::Lock& js, const Trace& trace, const tracing::TraceEventInfo& eventInfo)\n    : consumedEvents(getConsumedEventsFromEventInfo(js, eventInfo)) {}\n\nkj::Array<jsg::Ref<TraceItem::TailEventInfo::TailItem>> TraceItem::TailEventInfo::\n    getConsumedEvents() {\n  return KJ_MAP(consumedEvent, consumedEvents) -> jsg::Ref<TailEventInfo::TailItem> {\n    return consumedEvent.addRef();\n  };\n}\n\nTraceItem::TailEventInfo::TailItem::TailItem(const tracing::TraceEventInfo::TraceItem& traceItem)\n    : scriptName(mapCopyString(traceItem.scriptName)) {}\n\nkj::Maybe<kj::StringPtr> TraceItem::TailEventInfo::TailItem::getScriptName() {\n  return scriptName;\n}\n\nTraceDiagnosticChannelEvent::TraceDiagnosticChannelEvent(\n    const Trace& trace, const tracing::DiagnosticChannelEvent& eventInfo)\n    : timestamp(getTraceDiagnosticChannelEventTimestamp(eventInfo)),\n      channel(kj::heapString(eventInfo.channel)),\n      message(kj::heapArray<kj::byte>(eventInfo.message)) {}\n\nkj::StringPtr TraceDiagnosticChannelEvent::getChannel() {\n  return channel;\n}\n\njsg::JsValue TraceDiagnosticChannelEvent::getMessage(jsg::Lock& js) {\n  if (message.size() == 0) return js.undefined();\n  jsg::Deserializer des(js, message.asPtr());\n  return des.readValue(js);\n}\n\ndouble TraceDiagnosticChannelEvent::getTimestamp() {\n  return timestamp;\n}\n\nScriptVersion::ScriptVersion(workerd::ScriptVersion::Reader version)\n    : id{[&]() -> kj::Maybe<kj::String> {\n        return UUID::fromUpperLower(version.getId().getUpper(), version.getId().getLower())\n            .map([](const auto& uuid) { return uuid.toString(); });\n      }()},\n      tag{[&]() -> kj::Maybe<kj::String> {\n        if (version.hasTag()) {\n          return kj::str(version.getTag());\n        }\n        return kj::none;\n      }()},\n      message{[&]() -> kj::Maybe<kj::String> {\n        if (version.hasMessage()) {\n          return kj::str(version.getMessage());\n        }\n        return kj::none;\n      }()} {}\n\nScriptVersion::ScriptVersion(const ScriptVersion& other)\n    : id{mapCopyString(other.id)},\n      tag{mapCopyString(other.tag)},\n      message{mapCopyString(other.message)} {}\n\nTraceItem::CustomEventInfo::CustomEventInfo(\n    const Trace& trace, const tracing::CustomEventInfo& eventInfo)\n    : eventInfo(eventInfo) {}\n\nTraceItem::HibernatableWebSocketEventInfo::HibernatableWebSocketEventInfo(jsg::Lock& js,\n    const Trace& trace,\n    const tracing::HibernatableWebSocketEventInfo::Message eventInfo)\n    : eventType(js.alloc<TraceItem::HibernatableWebSocketEventInfo::Message>(trace, eventInfo)) {}\n\nTraceItem::HibernatableWebSocketEventInfo::HibernatableWebSocketEventInfo(jsg::Lock& js,\n    const Trace& trace,\n    const tracing::HibernatableWebSocketEventInfo::Close eventInfo)\n    : eventType(js.alloc<TraceItem::HibernatableWebSocketEventInfo::Close>(trace, eventInfo)) {}\n\nTraceItem::HibernatableWebSocketEventInfo::HibernatableWebSocketEventInfo(jsg::Lock& js,\n    const Trace& trace,\n    const tracing::HibernatableWebSocketEventInfo::Error eventInfo)\n    : eventType(js.alloc<TraceItem::HibernatableWebSocketEventInfo::Error>(trace, eventInfo)) {}\n\nTraceItem::HibernatableWebSocketEventInfo::Type TraceItem::HibernatableWebSocketEventInfo::\n    getEvent() {\n  KJ_SWITCH_ONEOF(eventType) {\n    KJ_CASE_ONEOF(m, jsg::Ref<TraceItem::HibernatableWebSocketEventInfo::Message>) {\n      return m.addRef();\n    }\n    KJ_CASE_ONEOF(c, jsg::Ref<TraceItem::HibernatableWebSocketEventInfo::Close>) {\n      return c.addRef();\n    }\n    KJ_CASE_ONEOF(e, jsg::Ref<TraceItem::HibernatableWebSocketEventInfo::Error>) {\n      return e.addRef();\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nuint16_t TraceItem::HibernatableWebSocketEventInfo::Close::getCode() {\n  return eventInfo.code;\n}\n\nbool TraceItem::HibernatableWebSocketEventInfo::Close::getWasClean() {\n  return eventInfo.wasClean;\n}\n\nTraceLog::TraceLog(jsg::Lock& js, const Trace& trace, const tracing::Log& log)\n    : timestamp(getTraceLogTimestamp(log)),\n      level(getTraceLogLevel(log)),\n      message(getTraceLogMessage(js, log)) {}\n\ndouble TraceLog::getTimestamp() {\n  return timestamp;\n}\n\nkj::StringPtr TraceLog::getLevel() {\n  return level;\n}\n\njsg::V8Ref<v8::Object> TraceLog::getMessage(jsg::Lock& js) {\n  return message.addRef(js);\n}\n\nTraceException::TraceException(const Trace& trace, const tracing::Exception& exception)\n    : timestamp(getTraceExceptionTimestamp(exception)),\n      name(kj::str(exception.name)),\n      message(kj::str(exception.message)),\n      stack(mapCopyString(exception.stack)) {}\n\ndouble TraceException::getTimestamp() {\n  return timestamp;\n}\n\nkj::StringPtr TraceException::getMessage() {\n  return message;\n}\n\nkj::StringPtr TraceException::getName() {\n  return name;\n}\n\njsg::Optional<kj::StringPtr> TraceException::getStack(jsg::Lock& js) {\n  return stack;\n}\n\nTraceMetrics::TraceMetrics(uint cpuTime, uint wallTime): cpuTime(cpuTime), wallTime(wallTime) {}\n\njsg::Ref<TraceMetrics> UnsafeTraceMetrics::fromTrace(jsg::Lock& js, jsg::Ref<TraceItem> item) {\n  return js.alloc<TraceMetrics>(item->getCpuTime(), item->getWallTime());\n}\n\nnamespace {\nkj::Promise<void> sendTracesToExportedHandler(kj::Own<IoContext::IncomingRequest> incomingRequest,\n    kj::Maybe<kj::StringPtr> entrypointNamePtr,\n    kj::Maybe<Worker::VersionInfo> versionInfo,\n    Frankenvalue props,\n    kj::ArrayPtr<kj::Own<Trace>> traces) {\n  // Mark the request as delivered because we're about to run some JS.\n  incomingRequest->delivered();\n\n  auto& context = incomingRequest->getContext();\n  auto& metrics = incomingRequest->getMetrics();\n\n  auto nonEmptyTraces = kj::Vector<kj::Own<Trace>>(kj::size(traces));\n  for (auto& trace: traces) {\n    if (trace->eventInfo != kj::none) {\n      nonEmptyTraces.add(kj::addRef(*trace));\n    }\n  }\n\n  // Add the actual JS as a wait until because the handler may be an event listener which can't\n  // wait around for async resolution. We're relying on `drain()` below to persist `incomingRequest`\n  // and its members until this task completes.\n  auto entrypointName = mapCopyString(entrypointNamePtr);\n  try {\n    co_await context.run(\n        [&context, nonEmptyTraces = nonEmptyTraces.asPtr(), entrypointName = kj::mv(entrypointName),\n            versionInfo = kj::mv(versionInfo), props = kj::mv(props)](Worker::Lock& lock) mutable {\n      jsg::AsyncContextFrame::StorageScope traceScope = context.makeAsyncTraceScope(lock);\n\n      auto handler = lock.getExportedHandler(\n          entrypointName, kj::mv(versionInfo), kj::mv(props), context.getActor());\n      return lock.getGlobalScope().sendTraces(nonEmptyTraces, lock, handler);\n    });\n  } catch (kj::Exception& e) {\n    // TODO(someday): We only report sendTraces() as failed for metrics/logging if the initial\n    //   event handler throws an exception; we do not consider waitUntil(). But all async work done\n    //   in a trace handler has to be done using waitUntil(). So, this seems wrong. Should we\n    //   change it so any waitUntil() failure counts as an error? For that matter, arguably *all*\n    //   event types should report failure if a waitUntil() throws?\n    metrics.reportFailure(e);\n\n    // Log JS exceptions (from the initial sendTraces() call) to the JS console, if fiddle is\n    // attached. This also has the effect of logging internal errors to syslog. (Note that\n    // exceptions that occur asynchronously while waiting for the context to drain will be\n    // logged elsewhere.)\n    context.logUncaughtExceptionAsync(UncaughtExceptionSource::TRACE_HANDLER, kj::mv(e));\n  };\n\n  co_await incomingRequest->drain();\n}\n}  // namespace\n\ntracing::EventInfo TraceCustomEvent::getEventInfo() const {\n  return tracing::TraceEventInfo(traces);\n}\n\nauto TraceCustomEvent::run(kj::Own<IoContext::IncomingRequest> incomingRequest,\n    kj::Maybe<kj::StringPtr> entrypointNamePtr,\n    kj::Maybe<Worker::VersionInfo> versionInfo,\n    Frankenvalue props,\n    kj::TaskSet& waitUntilTasks) -> kj::Promise<Result> {\n  // Don't bother to wait around for the handler to run, just hand it off to the waitUntil tasks.\n  waitUntilTasks.add(sendTracesToExportedHandler(\n      kj::mv(incomingRequest), entrypointNamePtr, kj::mv(versionInfo), kj::mv(props), traces));\n\n  // Reporting a proper outcome and return event here would be nice, but for that we'd need to await\n  // running the tail handler...\n  return Result{\n    .outcome = EventOutcome::OK,\n  };\n}\n\nauto TraceCustomEvent::sendRpc(capnp::HttpOverCapnpFactory& httpOverCapnpFactory,\n    capnp::ByteStreamFactory& byteStreamFactory,\n    workerd::rpc::EventDispatcher::Client dispatcher) -> kj::Promise<Result> {\n  auto req = dispatcher.sendTracesRequest();\n  auto out = req.initTraces(traces.size());\n  for (auto i: kj::indices(traces)) {\n    traces[i]->copyTo(out[i]);\n  }\n\n  auto resp = co_await req.send();\n  auto respResult = resp.getResult();\n  co_return WorkerInterface::CustomEvent::Result{\n    .outcome = respResult.getOutcome(),\n  };\n}\n\nvoid TailEvent::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  for (const auto& event: events) {\n    tracker.trackField(nullptr, event);\n  }\n}\n\nvoid TraceItem::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  KJ_IF_SOME(event, eventInfo) {\n    KJ_SWITCH_ONEOF(event) {\n      KJ_CASE_ONEOF(info, jsg::Ref<FetchEventInfo>) {\n        tracker.trackField(\"eventInfo\", info);\n      }\n      KJ_CASE_ONEOF(info, jsg::Ref<JsRpcEventInfo>) {\n        tracker.trackField(\"eventInfo\", info);\n      }\n      KJ_CASE_ONEOF(info, jsg::Ref<ScheduledEventInfo>) {\n        tracker.trackField(\"eventInfo\", info);\n      }\n      KJ_CASE_ONEOF(info, jsg::Ref<AlarmEventInfo>) {\n        tracker.trackField(\"eventInfo\", info);\n      }\n      KJ_CASE_ONEOF(info, jsg::Ref<QueueEventInfo>) {\n        tracker.trackField(\"eventInfo\", info);\n      }\n      KJ_CASE_ONEOF(info, jsg::Ref<EmailEventInfo>) {\n        tracker.trackField(\"eventInfo\", info);\n      }\n      KJ_CASE_ONEOF(info, jsg::Ref<TailEventInfo>) {\n        tracker.trackField(\"eventInfo\", info);\n      }\n      KJ_CASE_ONEOF(info, jsg::Ref<CustomEventInfo>) {\n        tracker.trackField(\"eventInfo\", info);\n      }\n      KJ_CASE_ONEOF(info, jsg::Ref<HibernatableWebSocketEventInfo>) {\n        tracker.trackField(\"eventInfo\", info);\n      }\n    }\n  }\n  for (const auto& log: logs) {\n    tracker.trackField(\"log\", log);\n  }\n  for (const auto& exception: exceptions) {\n    tracker.trackField(\"exception\", exception);\n  }\n  for (const auto& event: diagnosticChannelEvents) {\n    tracker.trackField(\"diagnosticChannelEvent\", event);\n  }\n  tracker.trackField(\"scriptName\", scriptName);\n  tracker.trackField(\"scriptVersion\", scriptVersion);\n  tracker.trackField(\"dispatchNamespace\", dispatchNamespace);\n  KJ_IF_SOME(tags, scriptTags) {\n    for (const auto& tag: tags) {\n      tracker.trackField(\"scriptTag\", tag);\n    }\n  }\n  KJ_IF_SOME(tags, tailAttributes) {\n    for (const auto& tag: tags) {\n      tracker.trackFieldWithSize(\"tailAttributeName\", tag.name.size());\n      for (const auto& value: tag.value) {\n        KJ_IF_SOME(string, value.tryGet<kj::ConstString>()) {\n          tracker.trackFieldWithSize(\"tailAttributeValue\", string.size());\n        }\n      }\n    }\n  }\n  tracker.trackField(\"outcome\", outcome);\n}\n\nvoid TraceItem::FetchEventInfo::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"request\", request);\n  tracker.trackField(\"response\", response);\n}\n\nvoid TraceItem::TailEventInfo::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  for (const auto& event: consumedEvents) {\n    tracker.trackField(nullptr, event);\n  }\n}\n\nvoid TraceItem::HibernatableWebSocketEventInfo::visitForMemoryInfo(\n    jsg::MemoryTracker& tracker) const {\n  KJ_SWITCH_ONEOF(eventType) {\n    KJ_CASE_ONEOF(message, jsg::Ref<Message>) {\n      tracker.trackField(\"message\", message);\n    }\n    KJ_CASE_ONEOF(close, jsg::Ref<Close>) {\n      tracker.trackField(\"close\", close);\n    }\n    KJ_CASE_ONEOF(error, jsg::Ref<Error>) {\n      tracker.trackField(\"error\", error);\n    }\n  }\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/trace.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n/**\n * Bindings for trace worker (ie. to support running wrangler tail).\n */\n\n#include <workerd/api/basics.h>\n#include <workerd/io/io-context.h>\n#include <workerd/io/trace.h>\n#include <workerd/jsg/jsg.h>\n\n#include <kj/async.h>\n\nnamespace workerd::api {\n\nclass TraceItem;\nclass TraceException;\nclass TraceLog;\nclass TraceDiagnosticChannelEvent;\n\nclass TailEvent final: public ExtendableEvent {\n public:\n  explicit TailEvent(\n      jsg::Lock& js, kj::LiteralStringConst type, kj::ArrayPtr<kj::Own<Trace>> events);\n\n  static jsg::Ref<TailEvent> constructor(kj::String type) = delete;\n  // TODO(soon): constructor?\n\n  kj::Array<jsg::Ref<TraceItem>> getEvents();\n\n  JSG_RESOURCE_TYPE(TailEvent) {\n    JSG_INHERIT(ExtendableEvent);\n\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(events, getEvents);\n    // Deprecated. Please, use `events` instead.\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(traces, getEvents);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  kj::Array<jsg::Ref<TraceItem>> events;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visitAll(events);\n  }\n};\n\nstruct ScriptVersion {\n  explicit ScriptVersion(workerd::ScriptVersion::Reader version);\n  ScriptVersion(const ScriptVersion&);\n\n  jsg::Optional<kj::String> id;\n  jsg::Optional<kj::String> tag;\n  jsg::Optional<kj::String> message;\n\n  JSG_STRUCT(id, tag, message);\n\n  JSG_MEMORY_INFO(ScriptVersion) {\n    tracker.trackField(\"id\", id);\n    tracker.trackField(\"tag\", tag);\n    tracker.trackField(\"message\", message);\n  }\n};\n\nclass TraceItem final: public jsg::Object {\n public:\n  using TailAttributeValue = kj::OneOf<bool, double, kj::String>;\n\n  class FetchEventInfo;\n  class JsRpcEventInfo;\n  class ScheduledEventInfo;\n  class AlarmEventInfo;\n  class QueueEventInfo;\n  class EmailEventInfo;\n  class TailEventInfo;\n  class HibernatableWebSocketEventInfo;\n  class CustomEventInfo;\n\n  explicit TraceItem(jsg::Lock& js, const Trace& trace);\n\n  using EventInfo = kj::OneOf<jsg::Ref<FetchEventInfo>,\n      jsg::Ref<JsRpcEventInfo>,\n      jsg::Ref<ScheduledEventInfo>,\n      jsg::Ref<AlarmEventInfo>,\n      jsg::Ref<QueueEventInfo>,\n      jsg::Ref<EmailEventInfo>,\n      jsg::Ref<TailEventInfo>,\n      jsg::Ref<CustomEventInfo>,\n      jsg::Ref<HibernatableWebSocketEventInfo>>;\n  kj::Maybe<EventInfo> getEvent(jsg::Lock& js);\n  kj::Maybe<double> getEventTimestamp();\n\n  kj::ArrayPtr<jsg::Ref<TraceLog>> getLogs();\n  kj::ArrayPtr<jsg::Ref<TraceException>> getExceptions();\n  kj::ArrayPtr<jsg::Ref<TraceDiagnosticChannelEvent>> getDiagnosticChannelEvents();\n  kj::Maybe<kj::StringPtr> getScriptName();\n  jsg::Optional<kj::StringPtr> getEntrypoint();\n  jsg::Optional<ScriptVersion> getScriptVersion();\n  jsg::Optional<kj::StringPtr> getDispatchNamespace();\n  jsg::Optional<kj::Array<kj::StringPtr>> getScriptTags();\n  jsg::Optional<jsg::Dict<TailAttributeValue>> getTailAttributes();\n  jsg::Optional<kj::StringPtr> getDurableObjectId();\n  kj::StringPtr getExecutionModel();\n  kj::StringPtr getOutcome();\n\n  uint getCpuTime();\n  uint getWallTime();\n  bool getTruncated();\n\n  JSG_RESOURCE_TYPE(TraceItem, CompatibilityFlags::Reader flags) {\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(event, getEvent);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(eventTimestamp, getEventTimestamp);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(logs, getLogs);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(exceptions, getExceptions);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(diagnosticsChannelEvents, getDiagnosticChannelEvents);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(scriptName, getScriptName);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(entrypoint, getEntrypoint);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(scriptVersion, getScriptVersion);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(dispatchNamespace, getDispatchNamespace);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(scriptTags, getScriptTags);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(tailAttributes, getTailAttributes);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(durableObjectId, getDurableObjectId);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(outcome, getOutcome);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(executionModel, getExecutionModel);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(truncated, getTruncated);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(cpuTime, getCpuTime);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(wallTime, getWallTime);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  kj::Maybe<EventInfo> eventInfo;\n  kj::Maybe<double> eventTimestamp;\n  kj::Array<jsg::Ref<TraceLog>> logs;\n  kj::Array<jsg::Ref<TraceException>> exceptions;\n  kj::Array<jsg::Ref<TraceDiagnosticChannelEvent>> diagnosticChannelEvents;\n  kj::Maybe<kj::String> scriptName;\n  kj::Maybe<kj::String> entrypoint;\n  kj::Maybe<ScriptVersion> scriptVersion;\n  kj::Maybe<kj::String> dispatchNamespace;\n  jsg::Optional<kj::Array<kj::String>> scriptTags;\n  kj::Maybe<kj::Array<tracing::Attribute>> tailAttributes;\n  kj::Maybe<kj::String> durableObjectId;\n  kj::String executionModel;\n  kj::String outcome;\n  uint cpuTime;\n  uint wallTime;\n  bool truncated;\n};\n\n// When adding a new TraceItem eventInfo type, it is important not to\n// try keeping a reference to the Trace and tracing::*EventInfo inputs.\n// They are kj heap objects that have a lifespan that is managed independently\n// of the TraceItem object. Each of the implementations here extract the\n// necessary detail on creation and use LAZY instance properties to minimize\n// copying and allocation necessary when accessing these values.\n// TODO(cleanup): Later we can further optimize by creating the JS objects\n// immediately on creation.\n\n// While this class is named FetchEventInfo, it encapsulates both the actual\n// FetchEventInfo as well as the FetchResponseInfo, which is an (optional)\n// sibling field (see worker.capnp). The internal FetchEventInfo (and\n// EventInfo in general) only represents the original event, not any\n// subsequent results such as the HTTP response. Internally, FetchEventInfo is\n// populated as soon as a request comes in, whereas the FetchResponseInfo is\n// only set once the request has finished entirely (along with the outcome,\n// see TraceItem::getOutcome).\nclass TraceItem::FetchEventInfo final: public jsg::Object {\n public:\n  class Request;\n  class Response;\n\n  explicit FetchEventInfo(jsg::Lock& js,\n      const Trace& trace,\n      const tracing::FetchEventInfo& eventInfo,\n      kj::Maybe<const tracing::FetchResponseInfo&> responseInfo);\n\n  jsg::Ref<Request> getRequest();\n  jsg::Optional<jsg::Ref<Response>> getResponse();\n\n  // TODO(cleanup) Use struct types more?\n  JSG_RESOURCE_TYPE(FetchEventInfo) {\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(response, getResponse);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(request, getRequest);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  jsg::Ref<Request> request;\n  jsg::Optional<jsg::Ref<Response>> response;\n};\n\nclass TraceItem::FetchEventInfo::Request final: public jsg::Object {\n public:\n  struct Detail: public kj::Refcounted {\n    jsg::Optional<jsg::V8Ref<v8::Object>> cf;\n    kj::Array<tracing::FetchEventInfo::Header> headers;\n    kj::String method;\n    kj::String url;\n\n    Detail(jsg::Optional<jsg::V8Ref<v8::Object>> cf,\n        kj::Array<tracing::FetchEventInfo::Header> headers,\n        kj::String method,\n        kj::String url);\n\n    JSG_MEMORY_INFO(Detail) {\n      tracker.trackField(\"cf\", cf);\n      for (const auto& header: headers) {\n        tracker.trackField(nullptr, header);\n      }\n      tracker.trackField(\"method\", method);\n      tracker.trackField(\"url\", url);\n    }\n  };\n\n  explicit Request(jsg::Lock& js, const Trace& trace, const tracing::FetchEventInfo& eventInfo);\n\n  // Creates a possibly unredacted instance that shared a ref of the Detail\n  explicit Request(Detail& detail, bool redacted = true);\n\n  jsg::Optional<jsg::V8Ref<v8::Object>> getCf(jsg::Lock& js);\n  jsg::Dict<kj::String, kj::String> getHeaders(jsg::Lock& js);\n  kj::StringPtr getMethod();\n  kj::String getUrl();\n\n  jsg::Ref<Request> getUnredacted(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(Request) {\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(cf, getCf);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(headers, getHeaders);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(method, getMethod);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(url, getUrl);\n\n    JSG_METHOD(getUnredacted);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"detail\", detail);\n  }\n\n private:\n  bool redacted = true;\n  kj::Own<Detail> detail;\n};\n\nclass TraceItem::FetchEventInfo::Response final: public jsg::Object {\n public:\n  explicit Response(const Trace& trace, const tracing::FetchResponseInfo& responseInfo);\n\n  uint16_t getStatus();\n\n  JSG_RESOURCE_TYPE(Response) {\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(status, getStatus);\n  }\n\n private:\n  uint16_t status;\n};\n\nclass TraceItem::JsRpcEventInfo final: public jsg::Object {\n public:\n  explicit JsRpcEventInfo(const Trace& trace, const tracing::JsRpcEventInfo& eventInfo);\n\n  // We call this `rpcMethod` to make clear this is an RPC event, since some tail workers rely\n  // on duck-typing EventInfo based on the properties present. (`methodName` might be ambiguous\n  // since HTTP also has methods.)\n  //\n  // TODO(someday): Clearly there should be a better way to distinguish event types?\n  kj::StringPtr getRpcMethod();\n\n  JSG_RESOURCE_TYPE(JsRpcEventInfo) {\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(rpcMethod, getRpcMethod);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"rpcMethod\", rpcMethod);\n  }\n\n private:\n  kj::String rpcMethod;\n};\n\nclass TraceItem::ScheduledEventInfo final: public jsg::Object {\n public:\n  explicit ScheduledEventInfo(const Trace& trace, const tracing::ScheduledEventInfo& eventInfo);\n\n  double getScheduledTime();\n  kj::StringPtr getCron();\n\n  JSG_RESOURCE_TYPE(ScheduledEventInfo) {\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(scheduledTime, getScheduledTime);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(cron, getCron);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"cron\", cron);\n  }\n\n private:\n  double scheduledTime;\n  kj::String cron;\n};\n\nclass TraceItem::AlarmEventInfo final: public jsg::Object {\n public:\n  explicit AlarmEventInfo(const Trace& trace, const tracing::AlarmEventInfo& eventInfo);\n\n  kj::Date getScheduledTime();\n\n  JSG_RESOURCE_TYPE(AlarmEventInfo) {\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(scheduledTime, getScheduledTime);\n  }\n\n private:\n  kj::Date scheduledTime;\n};\n\nclass TraceItem::QueueEventInfo final: public jsg::Object {\n public:\n  explicit QueueEventInfo(const Trace& trace, const tracing::QueueEventInfo& eventInfo);\n\n  kj::StringPtr getQueueName();\n  uint32_t getBatchSize();\n  // TODO(now): Add something about the timestamp(s) of the newest/oldest message(s) in the batch?\n\n  JSG_RESOURCE_TYPE(QueueEventInfo) {\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(queue, getQueueName);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(batchSize, getBatchSize);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"queueName\", queueName);\n  }\n\n private:\n  kj::String queueName;\n  uint32_t batchSize;\n};\n\nclass TraceItem::EmailEventInfo final: public jsg::Object {\n public:\n  explicit EmailEventInfo(const Trace& trace, const tracing::EmailEventInfo& eventInfo);\n\n  kj::StringPtr getMailFrom();\n  kj::StringPtr getRcptTo();\n  uint32_t getRawSize();\n\n  JSG_RESOURCE_TYPE(EmailEventInfo) {\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(mailFrom, getMailFrom);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(rcptTo, getRcptTo);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(rawSize, getRawSize);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"mailFrom\", mailFrom);\n    tracker.trackField(\"rcptTo\", rcptTo);\n  }\n\n private:\n  kj::String mailFrom;\n  kj::String rcptTo;\n  uint32_t rawSize;\n};\n\nclass TraceItem::TailEventInfo final: public jsg::Object {\n public:\n  class TailItem;\n\n  explicit TailEventInfo(\n      jsg::Lock& js, const Trace& trace, const tracing::TraceEventInfo& eventInfo);\n\n  kj::Array<jsg::Ref<TailItem>> getConsumedEvents();\n\n  JSG_RESOURCE_TYPE(TailEventInfo) {\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(consumedEvents, getConsumedEvents);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  kj::Array<jsg::Ref<TailItem>> consumedEvents;\n};\n\nclass TraceItem::TailEventInfo::TailItem final: public jsg::Object {\n public:\n  explicit TailItem(const tracing::TraceEventInfo::TraceItem& traceItem);\n\n  kj::Maybe<kj::StringPtr> getScriptName();\n\n  JSG_RESOURCE_TYPE(TailItem) {\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(scriptName, getScriptName);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"scriptName\", scriptName);\n  }\n\n private:\n  kj::Maybe<kj::String> scriptName;\n};\n\nclass TraceItem::HibernatableWebSocketEventInfo final: public jsg::Object {\n public:\n  class Message;\n  class Close;\n  class Error;\n\n  explicit HibernatableWebSocketEventInfo(jsg::Lock& js,\n      const Trace& trace,\n      tracing::HibernatableWebSocketEventInfo::Message eventInfo);\n  explicit HibernatableWebSocketEventInfo(\n      jsg::Lock& js, const Trace& trace, tracing::HibernatableWebSocketEventInfo::Close eventInfo);\n  explicit HibernatableWebSocketEventInfo(\n      jsg::Lock& js, const Trace& trace, tracing::HibernatableWebSocketEventInfo::Error eventInfo);\n\n  using Type = kj::OneOf<jsg::Ref<Message>, jsg::Ref<Close>, jsg::Ref<Error>>;\n\n  Type getEvent();\n\n  JSG_RESOURCE_TYPE(HibernatableWebSocketEventInfo) {\n    JSG_READONLY_INSTANCE_PROPERTY(getWebSocketEvent, getEvent);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  Type eventType;\n};\n\nclass TraceItem::HibernatableWebSocketEventInfo::Message final: public jsg::Object {\n public:\n  explicit Message(const Trace& trace, tracing::HibernatableWebSocketEventInfo::Message eventInfo)\n      : eventInfo(eventInfo) {}\n\n  static constexpr kj::StringPtr webSocketEventType = \"message\"_kj;\n  kj::StringPtr getWebSocketEventType() {\n    return webSocketEventType;\n  }\n\n  JSG_RESOURCE_TYPE(Message) {\n    JSG_READONLY_INSTANCE_PROPERTY(webSocketEventType, getWebSocketEventType);\n  }\n\n private:\n  const tracing::HibernatableWebSocketEventInfo::Message eventInfo;\n};\n\nclass TraceItem::HibernatableWebSocketEventInfo::Close final: public jsg::Object {\n public:\n  explicit Close(const Trace& trace, tracing::HibernatableWebSocketEventInfo::Close eventInfo)\n      : eventInfo(eventInfo) {}\n\n  static constexpr kj::StringPtr webSocketEventType = \"close\"_kj;\n  kj::StringPtr getWebSocketEventType() {\n    return webSocketEventType;\n  }\n\n  uint16_t getCode();\n  bool getWasClean();\n\n  JSG_RESOURCE_TYPE(Close) {\n    JSG_READONLY_INSTANCE_PROPERTY(webSocketEventType, getWebSocketEventType);\n    JSG_READONLY_INSTANCE_PROPERTY(code, getCode);\n    JSG_READONLY_INSTANCE_PROPERTY(wasClean, getWasClean);\n  }\n\n private:\n  const tracing::HibernatableWebSocketEventInfo::Close eventInfo;\n};\n\nclass TraceItem::HibernatableWebSocketEventInfo::Error final: public jsg::Object {\n public:\n  explicit Error(const Trace& trace, tracing::HibernatableWebSocketEventInfo::Error eventInfo)\n      : eventInfo(eventInfo) {}\n\n  static constexpr kj::StringPtr webSocketEventType = \"error\"_kj;\n  kj::StringPtr getWebSocketEventType() {\n    return webSocketEventType;\n  }\n\n  JSG_RESOURCE_TYPE(Error) {\n    JSG_READONLY_INSTANCE_PROPERTY(webSocketEventType, getWebSocketEventType);\n  }\n\n private:\n  const tracing::HibernatableWebSocketEventInfo::Error eventInfo;\n};\n\nclass TraceItem::CustomEventInfo final: public jsg::Object {\n public:\n  explicit CustomEventInfo(const Trace& trace, const tracing::CustomEventInfo& eventInfo);\n\n  JSG_RESOURCE_TYPE(CustomEventInfo) {}\n\n private:\n  const tracing::CustomEventInfo& eventInfo;\n};\n\nclass TraceDiagnosticChannelEvent final: public jsg::Object {\n public:\n  explicit TraceDiagnosticChannelEvent(\n      const Trace& trace, const tracing::DiagnosticChannelEvent& eventInfo);\n\n  double getTimestamp();\n  kj::StringPtr getChannel();\n  jsg::JsValue getMessage(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(TraceDiagnosticChannelEvent) {\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(timestamp, getTimestamp);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(channel, getChannel);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(message, getMessage);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"channel\", channel);\n    tracker.trackFieldWithSize(\"message\", message.size());\n  }\n\n private:\n  double timestamp;\n  kj::String channel;\n  kj::Array<kj::byte> message;\n};\n\nclass TraceLog final: public jsg::Object {\n public:\n  TraceLog(jsg::Lock& js, const Trace& trace, const tracing::Log& log);\n\n  double getTimestamp();\n  kj::StringPtr getLevel();\n  jsg::V8Ref<v8::Object> getMessage(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(TraceLog) {\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(timestamp, getTimestamp);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(level, getLevel);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(message, getMessage);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"message\", message);\n  }\n\n private:\n  double timestamp;\n  kj::LiteralStringConst level;\n  jsg::V8Ref<v8::Object> message;\n};\n\nclass TraceException final: public jsg::Object {\n public:\n  TraceException(const Trace& trace, const tracing::Exception& exception);\n\n  double getTimestamp();\n  kj::StringPtr getName();\n  kj::StringPtr getMessage();\n  jsg::Optional<kj::StringPtr> getStack(jsg::Lock& js);\n\n  JSG_RESOURCE_TYPE(TraceException) {\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(timestamp, getTimestamp);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(message, getMessage);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(name, getName);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(stack, getStack);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"name\", name);\n    tracker.trackField(\"message\", message);\n  }\n\n private:\n  double timestamp;\n  kj::String name;\n  kj::String message;\n  kj::Maybe<kj::String> stack;\n};\n\nclass TraceMetrics final: public jsg::Object {\n public:\n  explicit TraceMetrics(uint cpuTime, uint wallTime);\n\n  uint getCPUTime() {\n    return cpuTime;\n  };\n  uint getWallTime() {\n    return wallTime;\n  };\n\n  JSG_RESOURCE_TYPE(TraceMetrics) {\n    JSG_READONLY_INSTANCE_PROPERTY(cpuTime, getCPUTime);\n    JSG_READONLY_INSTANCE_PROPERTY(wallTime, getWallTime);\n\n    JSG_TS_ROOT();\n  }\n\n private:\n  uint cpuTime;\n  uint wallTime;\n};\n\nclass UnsafeTraceMetrics final: public jsg::Object {\n public:\n  jsg::Ref<TraceMetrics> fromTrace(jsg::Lock& js, jsg::Ref<TraceItem> item);\n\n  JSG_RESOURCE_TYPE(UnsafeTraceMetrics) {\n    JSG_METHOD(fromTrace);\n\n    JSG_TS_ROOT();\n  }\n};\n\nclass TraceCustomEvent final: public WorkerInterface::CustomEvent {\n public:\n  TraceCustomEvent(uint16_t typeId, kj::Array<kj::Own<Trace>> traces)\n      : typeId(typeId),\n        traces(kj::mv(traces)) {}\n\n  kj::Promise<Result> run(kj::Own<IoContext::IncomingRequest> incomingRequest,\n      kj::Maybe<kj::StringPtr> entrypointName,\n      kj::Maybe<Worker::VersionInfo> versionInfo,\n      Frankenvalue props,\n      kj::TaskSet& waitUntilTasks) override;\n\n  kj::Promise<Result> sendRpc(capnp::HttpOverCapnpFactory& httpOverCapnpFactory,\n      capnp::ByteStreamFactory& byteStreamFactory,\n      rpc::EventDispatcher::Client dispatcher) override;\n\n  uint16_t getType() override {\n    return typeId;\n  }\n\n  tracing::EventInfo getEventInfo() const override;\n\n  kj::Promise<Result> notSupported() override {\n    KJ_UNIMPLEMENTED(\"trace event not supported\");\n  }\n\n  static constexpr uint16_t TYPE = 2;\n\n private:\n  uint16_t typeId;\n  kj::Array<kj::Own<workerd::Trace>> traces;\n};\n\n#define EW_TRACE_ISOLATE_TYPES                                                                     \\\n  api::ScriptVersion, api::TailEvent, api::TraceItem, api::TraceItem::AlarmEventInfo,              \\\n      api::TraceItem::CustomEventInfo, api::TraceItem::ScheduledEventInfo,                         \\\n      api::TraceItem::QueueEventInfo, api::TraceItem::EmailEventInfo,                              \\\n      api::TraceItem::TailEventInfo, api::TraceItem::TailEventInfo::TailItem,                      \\\n      api::TraceItem::FetchEventInfo, api::TraceItem::FetchEventInfo::Request,                     \\\n      api::TraceItem::FetchEventInfo::Response, api::TraceItem::JsRpcEventInfo,                    \\\n      api::TraceItem::HibernatableWebSocketEventInfo,                                              \\\n      api::TraceItem::HibernatableWebSocketEventInfo::Message,                                     \\\n      api::TraceItem::HibernatableWebSocketEventInfo::Close,                                       \\\n      api::TraceItem::HibernatableWebSocketEventInfo::Error, api::TraceLog, api::TraceException,   \\\n      api::TraceDiagnosticChannelEvent, api::TraceMetrics, api::UnsafeTraceMetrics\n// The list of trace.h types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/tracing-module.c++",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"tracing-module.h\"\n\nnamespace workerd::api {\n\nJsSpan::JsSpan(kj::Maybe<IoOwn<TraceContext>> span): span(kj::mv(span)) {}\n\nJsSpan::~JsSpan() noexcept(false) {\n  end();\n}\n\nvoid JsSpan::end() {\n  span = kj::none;\n}\n\nvoid JsSpan::setAttribute(\n    jsg::Lock& js, kj::String key, jsg::Optional<kj::OneOf<bool, double, kj::String>> maybeValue) {\n  KJ_IF_SOME(s, span) {\n    KJ_IF_SOME(value, maybeValue) {\n      // JavaScript numbers (double) are stored as-is, not converted to int64_t\n      s->setTag(kj::ConstString(kj::mv(key)), kj::mv(value));\n    }\n    // If value is undefined/none, we simply don't set the attribute\n  }\n}\n\njsg::Ref<JsSpan> TracingModule::startSpan(jsg::Lock& js, kj::String name) {\n  KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n    TraceContext traceContext = ioContext.makeUserTraceSpan(kj::ConstString(kj::mv(name)));\n    auto ownedSpan = ioContext.addObject(kj::heap(kj::mv(traceContext)));\n    return js.alloc<JsSpan>(kj::mv(ownedSpan));\n  } else {\n    // When no IoContext is available, create a no-op span\n    return js.alloc<JsSpan>(kj::none);\n  }\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/tracing-module.h",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/io/io-context.h>\n\nnamespace workerd::api {\n\n// JavaScript-accessible span class that manages span ownership through IoContext\nclass JsSpan: public jsg::Object {\n public:\n  JsSpan(kj::Maybe<IoOwn<TraceContext>> span);\n  ~JsSpan() noexcept(false);\n\n  // Ends the span, marking its completion. Once ended, the span cannot be modified.\n  // If the span is not explicitly ended, it will be automatically ended when the\n  // JsSpan object is destroyed.\n  void end();\n  // Sets an attribute on the span. Values can be string, number, boolean, or undefined.\n  // If undefined is passed, the attribute is not set (allows optional chaining).\n  // Note: We intentionally don't support BigInt/int64_t. JavaScript numbers (doubles)\n  // are sufficient for most tracing use cases, and BigInt conversion to int64_t would\n  // require handling truncation for values outside the int64_t range.\n  void setAttribute(\n      jsg::Lock& js, kj::String key, jsg::Optional<kj::OneOf<bool, double, kj::String>> value);\n\n  JSG_RESOURCE_TYPE(JsSpan) {\n    JSG_METHOD(end);\n    JSG_METHOD(setAttribute);\n    JSG_DISPOSE(end);\n  }\n\n private:\n  kj::Maybe<IoOwn<TraceContext>> span;\n};\n\n// Module that provides tracing capabilities for Workers.\n// This module is available as \"cloudflare-internal:tracing\" and provides\n// functionality to create and manage tracing spans.\nclass TracingModule: public jsg::Object {\n public:\n  TracingModule() = default;\n  TracingModule(jsg::Lock&, const jsg::Url&) {}\n\n  // Creates a new tracing span with the given name.\n  // The span will be associated with the current IoContext and will track\n  // the execution of the code within its lifetime.\n  // If no IoContext is available (e.g., during initialization), a no-op span\n  // is returned that safely ignores all operations.\n  //\n  // Example usage:\n  //   const span = tracing.startSpan(\"my-operation\");\n  //   try {\n  //     // ... perform operation ...\n  //   } finally {\n  //     span.end();\n  //   }\n  jsg::Ref<JsSpan> startSpan(jsg::Lock& js, kj::String name);\n\n  JSG_RESOURCE_TYPE(TracingModule) {\n    JSG_METHOD(startSpan);\n\n    JSG_NESTED_TYPE(JsSpan);\n  }\n};\n\ntemplate <class Registry>\nvoid registerTracingModule(Registry& registry, CompatibilityFlags::Reader flags) {\n  registry.template addBuiltinModule<TracingModule>(\n      \"cloudflare-internal:tracing\", workerd::jsg::ModuleRegistry::Type::INTERNAL);\n}\n\ntemplate <typename TypeWrapper>\nkj::Own<jsg::modules::ModuleBundle> getInternalTracingModuleBundle(auto featureFlags) {\n  jsg::modules::ModuleBundle::BuiltinBuilder builder(\n      jsg::modules::ModuleBundle::BuiltinBuilder::Type::BUILTIN_ONLY);\n  static const auto kSpecifier = \"cloudflare-internal:tracing\"_url;\n  builder.addObject<TracingModule, TypeWrapper>(kSpecifier);\n  return builder.finish();\n}\n};  // namespace workerd::api\n\n#define EW_TRACING_MODULE_ISOLATE_TYPES api::TracingModule, api::JsSpan\n"
  },
  {
    "path": "src/workerd/api/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tools/base.tsconfig.json\",\n  \"compilerOptions\": {\n    \"lib\": [\"esnext\", \"webworker\"],\n    \"sourceMap\": true,\n    \"paths\": {\n      \"@workerd/*\": [\"../../bazel-bin/src/workerd/*\"]\n    },\n    \"declaration\": true,\n    \"skipLibCheck\": true,\n    \"types\": [\"@types/node\"]\n  },\n  \"include\": [\"**/*.ts\"]\n}\n"
  },
  {
    "path": "src/workerd/api/unsafe.c++",
    "content": "#include \"unsafe.h\"\n\n#include <workerd/io/io-context.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/script.h>\n#include <workerd/util/autogate.h>\n\nnamespace workerd::api {\n\nnamespace {\nstatic constexpr auto EVAL_STR = \"eval\"_kjc;\nstatic constexpr auto ANON_STR = \"anonymous\"_kjc;\nstatic constexpr auto ASYNC_FN_PREFIX = \"async function \"_kjc;\nstatic constexpr auto ASYNC_FN_ARG_OPEN = \"(\"_kjc;\nstatic constexpr auto ASYNC_FN_ARG_CLOSE = \") {\"_kjc;\nstatic constexpr auto ASYNC_FN_SUFFIX = \"}\"_kjc;\n\ninline kj::StringPtr getName(jsg::Optional<kj::String>& name, kj::StringPtr def) {\n  return name.map([](kj::String& str) { return str.asPtr(); }).orDefault(def);\n}\n}  // namespace\n\n#ifdef WORKERD_FUZZILLI\nvoid Stdin::reprl(jsg::Lock& js) {\n  js.setAllowEval(true);\n  /*\n  cov_init_builtins_edges(static_cast<uint32_t>(\n      v8::internal::BasicBlockProfiler::Get()\n          ->GetCoverageBitmap(reinterpret_cast<v8::Isolate*>(js.v8Isolate))\n          .size()));\n  */\n\n  char helo[] = \"HELO\";\n  if (write(REPRL_CWFD, helo, 4) != 4 || read(REPRL_CRFD, helo, 4) != 4) {\n    printf(\"Invalid HELO response from parent\\n\");\n  }\n\n  if (memcmp(helo, \"HELO\", 4) != 0) {\n    printf(\"Invalid response from parent\\n\");\n  }\n\n  do {\n    v8::HandleScope handle_scope(js.v8Isolate);\n    v8::TryCatch try_catch(js.v8Isolate);\n    try_catch.SetVerbose(true);\n\n    size_t script_size = 0;\n    unsigned action = 0;\n    ssize_t nread = read(REPRL_CRFD, &action, 4);\n    fflush(0);\n    fflush(stderr);\n    if (nread != 4 || action != 0x63657865) {  // 'exec'\n      fprintf(stderr, \"Unknown action: %x\\n\", action);\n      exit(-1);\n    }\n\n    CHECK(read(REPRL_CRFD, &script_size, 8) == 8);\n\n    char* script_ = (char*)malloc(script_size + 1);\n    CHECK(script_ != nullptr);\n\n    char* source_buffer_tail = script_;\n    ssize_t remaining = (ssize_t)script_size;\n\n    while (remaining > 0) {\n      ssize_t rv = read(REPRL_DRFD, source_buffer_tail, (size_t)remaining);\n      if (rv <= 0) {\n        fprintf(stderr, \"Failed to load script\\n\");\n        exit(-1);\n      }\n      remaining -= rv;\n      source_buffer_tail += rv;\n    }\n\n    script_[script_size] = '\\0';\n\n    int status = 0;\n    unsigned res_val = 0;\n    const kj::String script = kj::str(script_);\n    const kj::String wrapped = kj::str(\"{\", script_, \"}\");\n    auto compiled = jsg::NonModuleScript::compile(js, wrapped, \"reprl\"_kj);\n    try {\n      auto result = compiled.runAndReturn(js);\n      res_val = jsg::check(v8::Local<v8::Value>(result)->Int32Value(js.v8Context()));\n      // if we reach that point execution was successful -> return 0\n      res_val = 0;\n    } catch (jsg::JsExceptionThrown&) {\n      res_val = 11;\n      if (try_catch.HasCaught()) {\n        auto str = workerd::jsg::check(try_catch.Message()->Get()->ToDetailString(js.v8Context()));\n        v8::String::Utf8Value utf8String(js.v8Isolate, str);\n        fflush(stdout);\n      }\n    }\n\n    fflush(stdout);\n    fflush(stderr);\n    status = (res_val & 0xFF) << 8;\n    CHECK(write(REPRL_CWFD, &status, 4) == 4);\n    __sanitizer_cov_reset_edgeguards();\n    free(script_);\n    //cleanup context\n\n  } while (true);\n}\n#endif\n\njsg::JsValue UnsafeEval::eval(jsg::Lock& js, kj::String script, jsg::Optional<kj::String> name) {\n  js.setAllowEval(true);\n  KJ_DEFER(js.setAllowEval(false));\n  auto compiled = jsg::NonModuleScript::compile(js, script, getName(name, EVAL_STR));\n  return compiled.runAndReturn(js);\n}\n\nUnsafeEval::UnsafeEvalFunction UnsafeEval::newFunction(jsg::Lock& js,\n    jsg::JsString script,\n    jsg::Optional<kj::String> name,\n    jsg::Arguments<jsg::JsRef<jsg::JsString>> args,\n    const jsg::TypeHandler<UnsafeEvalFunction>& handler) {\n  js.setAllowEval(true);\n  KJ_DEFER(js.setAllowEval(false));\n\n  auto nameStr = js.str(getName(name, ANON_STR));\n  v8::ScriptOrigin origin(nameStr);\n  v8::ScriptCompiler::Source source(script, origin);\n\n  v8::LocalVector<v8::String> argNames(js.v8Isolate);\n  argNames.reserve(args.size());\n  for (auto& arg: args) {\n    argNames.push_back(arg.getHandle(js));\n  }\n\n  auto fn = jsg::check(v8::ScriptCompiler::CompileFunction(\n      js.v8Context(), &source, argNames.size(), argNames.data(), 0, nullptr));\n  fn->SetName(nameStr);\n\n  return KJ_ASSERT_NONNULL(handler.tryUnwrap(js, fn));\n}\n\nUnsafeEval::UnsafeEvalFunction UnsafeEval::newAsyncFunction(jsg::Lock& js,\n    jsg::JsString script,\n    jsg::Optional<kj::String> name,\n    jsg::Arguments<jsg::JsRef<jsg::JsString>> args,\n    const jsg::TypeHandler<UnsafeEvalFunction>& handler) {\n  js.setAllowEval(true);\n  KJ_DEFER(js.setAllowEval(false));\n\n  auto nameStr = js.str(getName(name, ANON_STR));\n\n  // This case is sadly a bit more complicated than the newFunction variant\n  // because v8 currently (surprisingly) does not actually expose a way\n  // CompileAsyncFunction variant (silly v8). What we end up doing here is\n  // building a string that wraps the script provided by the caller:\n  //\n  //   async function {name}({args}) { {script} }; {name}\n  //\n  // Where {name} is the name of the function as provided by the user or\n  // \"anonymous\" by default, {args} is the list of args provided by the\n  // caller, if any. We end the constructed string with the name of the\n  // function so that the result of running the compiled script is a reference\n  // to the compiled function.\n\n  auto prepared = v8::String::Concat(js.v8Isolate, js.strIntern(ASYNC_FN_PREFIX), nameStr);\n  prepared = v8::String::Concat(js.v8Isolate, prepared, js.strIntern(ASYNC_FN_ARG_OPEN));\n\n  for (auto& arg: args) {\n    prepared = v8::String::Concat(js.v8Isolate, prepared, arg.getHandle(js));\n    prepared = v8::String::Concat(js.v8Isolate, prepared, js.strIntern(\",\"));\n  }\n  prepared = v8::String::Concat(js.v8Isolate, prepared, js.strIntern(ASYNC_FN_ARG_CLOSE));\n  prepared = v8::String::Concat(js.v8Isolate, prepared, script);\n  prepared = v8::String::Concat(js.v8Isolate, prepared, js.strIntern(ASYNC_FN_SUFFIX));\n  prepared = v8::String::Concat(js.v8Isolate, prepared, js.strIntern(\";\"));\n  prepared = v8::String::Concat(js.v8Isolate, prepared, nameStr);\n\n  v8::ScriptOrigin origin(nameStr);\n  v8::ScriptCompiler::Source source(prepared, origin);\n\n  auto compiled = jsg::check(v8::ScriptCompiler::Compile(js.v8Context(), &source));\n  auto result = jsg::check(compiled->Run(js.v8Context()));\n\n  KJ_REQUIRE(result->IsAsyncFunction());\n\n  return KJ_ASSERT_NONNULL(handler.tryUnwrap(js, result.As<v8::Function>()));\n}\n\njsg::JsValue UnsafeEval::newWasmModule(jsg::Lock& js, kj::Array<kj::byte> src) {\n  js.setAllowEval(true);\n  KJ_DEFER(js.setAllowEval(false));\n\n  auto maybeWasmModule = v8::WasmModuleObject::Compile(\n      js.v8Isolate, v8::MemorySpan<const uint8_t>(src.begin(), src.size()));\n  return jsg::JsValue(jsg::check(maybeWasmModule));\n}\n\njsg::Promise<void> UnsafeModule::abortAllDurableObjects(jsg::Lock& js) {\n  auto& context = IoContext::current();\n\n  auto exception = JSG_KJ_EXCEPTION(FAILED, Error, \"Application called abortAllDurableObjects().\");\n  context.abortAllActors(exception);\n\n  // We used to perform the abort asynchronously, but that became no longer necessary when\n  // `Worker::Actor`'s destructor stopped requiring taking the isolate lock.\n  return js.resolvedPromise();\n}\n\nbool UnsafeModule::isTestAutogateEnabled() {\n  return util::Autogate::isEnabled(util::AutogateKey::TEST_WORKERD);\n}\n\n#ifdef WORKERD_FUZZILLI\nvoid Fuzzilli::fuzzilli(jsg::Lock& js, jsg::Arguments<jsg::Value> args) {\n  // Delegate to the fuzzilli_handler in fuzzilli.c++\n  fuzzilli_handler(js, args);\n}\n#endif\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/unsafe.h",
    "content": "#pragma once\n\n#include <workerd/io/io-context.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/modules-new.h>\n#include <workerd/jsg/script.h>\n#include <workerd/jsg/url.h>\n\n#include <csignal>\n#include <iostream>\n\n#ifdef _WIN32\n#include <io.h>\n#else\n#include <unistd.h>\n#endif\n\n#include <workerd/api/fuzzilli.h>\n\nnamespace workerd::api {\n\n// A special binding object that allows for dynamic evaluation.\nclass UnsafeEval: public jsg::Object {\n public:\n  UnsafeEval() = default;\n  UnsafeEval(jsg::Lock&, const jsg::Url&) {}\n\n  // A non-capturing eval. Compile and evaluates the given script, returning whatever\n  // value is returned by the script. This version of eval intentionally does not\n  // capture any part of the outer scope other than globalThis and globally scoped\n  // variables. The optional `name` will appear in stack traces for any errors thrown.\n  //\n  // console.log(env.unsafe.eval('1 + 1'));  // prints 2\n  //\n  jsg::JsValue eval(jsg::Lock& js, kj::String script, jsg::Optional<kj::String> name);\n\n  using UnsafeEvalFunction = jsg::Function<jsg::Value(jsg::Arguments<jsg::Value>)>;\n\n  // Compiles and returns a new Function using the given script. The function does not\n  // capture any part of the outer scope other than globalThis and globally scoped\n  // variables. The optional `name` will be set as the name of the function and will\n  // appear in stack traces for any errors thrown. An optional list of argument names\n  // can be passed in.\n  //\n  // const fn = env.unsafe.newFunction('return m', 'foo', 'm');\n  // console.log(fn(1));  // prints 1\n  //\n  UnsafeEvalFunction newFunction(jsg::Lock& js,\n      jsg::JsString script,\n      jsg::Optional<kj::String> name,\n      jsg::Arguments<jsg::JsRef<jsg::JsString>> args,\n      const jsg::TypeHandler<UnsafeEvalFunction>& handler);\n\n  // Compiles and returns a new Async Function using the given script. The function\n  // does not capture any part of the outer scope other than globalThis and globally\n  // scoped variables. The optional `name` will be set as the name of the function\n  // and will appear in stack traces for any errors thrown. An optional list of\n  // arguments names can be passed in. If your function needs to use the await\n  // key, use this instead of newFunction.\n  UnsafeEvalFunction newAsyncFunction(jsg::Lock& js,\n      jsg::JsString script,\n      jsg::Optional<kj::String> name,\n      jsg::Arguments<jsg::JsRef<jsg::JsString>> args,\n      const jsg::TypeHandler<UnsafeEvalFunction>& handler);\n\n  jsg::JsValue newWasmModule(jsg::Lock& js, kj::Array<kj::byte> src);\n\n  JSG_RESOURCE_TYPE(UnsafeEval) {\n    JSG_METHOD(eval);\n    JSG_METHOD(newFunction);\n    JSG_METHOD(newAsyncFunction);\n    JSG_METHOD(newWasmModule);\n  }\n};\n\n// A special binding that allows access to stdin. Used for REPL.\nclass Stdin: public jsg::Object {\n public:\n  Stdin() = default;\n\n  void reprl(jsg::Lock& js);\n\n  kj::String getline(jsg::Lock& js) {\n    std::string res;\n    std::getline(std::cin, res);\n    return kj::heapString(res.c_str());\n  }\n\n  JSG_RESOURCE_TYPE(Stdin) {\n    JSG_METHOD(getline);\n#ifdef WORKERD_FUZZILLI\n    JSG_METHOD(reprl);\n#endif\n  }\n};\n\nclass UnsafeModule: public jsg::Object {\n public:\n  UnsafeModule() = default;\n  UnsafeModule(jsg::Lock&, const jsg::Url&) {}\n  jsg::Promise<void> abortAllDurableObjects(jsg::Lock& js);\n\n  // Returns true if the TEST_WORKERD autogate is enabled.\n  // This is used to verify that the all-autogates test variant is working correctly.\n  bool isTestAutogateEnabled();\n\n  JSG_RESOURCE_TYPE(UnsafeModule) {\n    JSG_METHOD(abortAllDurableObjects);\n    JSG_METHOD(isTestAutogateEnabled);\n  }\n};\n\n#ifdef WORKERD_FUZZILLI\n// Fuzzilli fuzzing support for triggering crashes and printing debug output\nclass Fuzzilli: public jsg::Object {\n public:\n  Fuzzilli() = default;\n  Fuzzilli(jsg::Lock&, const jsg::Url&) {}\n\n  // Fuzzilli function for triggering crashes or printing debug output\n  // fuzzilli('FUZZILLI_CRASH', type: number): Triggers a crash based on type\n  // fuzzilli('FUZZILLI_PRINT', message: string): Prints message to fuzzer output\n  void fuzzilli(jsg::Lock& js, jsg::Arguments<jsg::Value> args);\n\n  JSG_RESOURCE_TYPE(Fuzzilli) {\n    JSG_METHOD(fuzzilli);\n  }\n};\n#endif\n\ntemplate <class Registry>\nvoid registerUnsafeModule(Registry& registry) {\n  registry.template addBuiltinModule<UnsafeModule>(\n      \"workerd:unsafe\", workerd::jsg::ModuleRegistry::Type::BUILTIN);\n  registry.template addBuiltinModule<UnsafeEval>(\n      \"workerd:unsafe-eval\", workerd::jsg::ModuleRegistry::Type::BUILTIN);\n}\n\n#ifdef WORKERD_FUZZILLI\n#define EW_UNSAFE_ISOLATE_TYPES api::UnsafeEval, api::UnsafeModule, api::Stdin, api::Fuzzilli\n#else\n#define EW_UNSAFE_ISOLATE_TYPES api::UnsafeEval, api::UnsafeModule, api::Stdin\n#endif\n\ntemplate <class Registry>\nvoid registerUnsafeModules(Registry& registry, auto featureFlags) {\n  registry.template addBuiltinModule<UnsafeEval>(\n      \"internal:unsafe-eval\", workerd::jsg::ModuleRegistry::Type::INTERNAL);\n#ifdef WORKERD_FUZZILLI\n  registry.template addBuiltinModule<Stdin>(\n      \"workerd:stdin\", workerd::jsg::ModuleRegistry::Type::BUILTIN);\n\n  if (featureFlags.getWorkerdExperimental()) {\n    registry.template addBuiltinModule<Fuzzilli>(\n        \"workerd:fuzzilli\", workerd::jsg::ModuleRegistry::Type::BUILTIN);\n  }\n#endif\n}\n\ntemplate <typename TypeWrapper>\nkj::Own<jsg::modules::ModuleBundle> getInternalUnsafeModuleBundle(auto featureFlags) {\n  jsg::modules::ModuleBundle::BuiltinBuilder builder(\n      jsg::modules::ModuleBundle::BuiltinBuilder::Type::BUILTIN_ONLY);\n  static const auto kSpecifier = \"internal:unsafe-eval\"_url;\n  builder.addObject<UnsafeEval, TypeWrapper>(kSpecifier);\n  return builder.finish();\n}\n\ntemplate <typename TypeWrapper>\nkj::Own<jsg::modules::ModuleBundle> getExternalUnsafeModuleBundle(auto featureFlags) {\n  jsg::modules::ModuleBundle::BuiltinBuilder builder(\n      jsg::modules::ModuleBundle::BuiltinBuilder::Type::BUILTIN);\n  static const auto kSpecifier = \"workerd:unsafe-eval\"_url;\n  builder.addObject<UnsafeEval, TypeWrapper>(kSpecifier);\n\n  static const auto kUnsafeSpecifier = \"workerd:unsafe\"_url;\n  builder.addObject<UnsafeModule, TypeWrapper>(kUnsafeSpecifier);\n\n#ifdef WORKERD_FUZZILLI\n  {\n    static const auto kStdinSpecifier = \"workerd:stdin\"_url;\n    builder.addSynthetic(kStdinSpecifier,\n        jsg::modules::Module::newJsgObjectModuleHandler<Stdin, TypeWrapper>(\n            [](jsg::Lock& js) { return js.alloc<Stdin>(); }));\n  }\n\n  if (featureFlags.getWorkerdExperimental()) {\n    static const auto kFuzzilliSpecifier = \"workerd:fuzzilli\"_url;\n    builder.addSynthetic(kFuzzilliSpecifier,\n        jsg::modules::Module::newJsgObjectModuleHandler<Fuzzilli, TypeWrapper>(\n            [](jsg::Lock& js) { return js.alloc<Fuzzilli>(); }));\n  }\n#endif\n\n  return builder.finish();\n}\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/url-standard.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"url-standard.h\"\n\n#include \"blob.h\"\n#include \"util.h\"\n\n#include <workerd/io/features.h>\n\n#include <unicode/uchar.h>\n#include <unicode/uidna.h>\n#include <unicode/ustring.h>\n#include <unicode/utf8.h>\n\n#include <kj/array.h>\n\n#include <algorithm>\n#include <cmath>\n#include <cstring>\n#include <map>\n#include <numeric>\n\n#if _WIN32\n#include <ws2tcpip.h>\n#undef RELATIVE\n#else\n#include <arpa/inet.h>\n#endif\n\nnamespace workerd::api::url {\n\nnamespace {\njsg::Url parseImpl(kj::StringPtr url, kj::Maybe<kj::StringPtr> maybeBase) {\n  return JSG_REQUIRE_NONNULL(jsg::Url::tryParse(url, maybeBase), TypeError, \"Invalid URL string.\");\n}\n}  // namespace\n\njsg::Ref<URL> URL::constructor(\n    jsg::Lock& js, jsg::USVString url, jsg::Optional<jsg::USVString> base) {\n  return js.alloc<URL>(kj::mv(url), base.map([](jsg::USVString& base) { return base.asPtr(); }));\n}\n\nURL::URL(kj::StringPtr url, kj::Maybe<kj::StringPtr> base): inner(parseImpl(url, base)) {}\n\nURL::~URL() noexcept(false) {\n  KJ_IF_SOME(searchParams, maybeSearchParams) {\n    searchParams->maybeUrl = kj::none;\n  }\n}\n\nbool URL::canParse(jsg::USVString url, jsg::Optional<jsg::USVString> maybeBase) {\n  return jsg::Url::canParse(url, maybeBase.map([](jsg::USVString& str) { return str.asPtr(); }));\n}\n\njsg::JsString URL::createObjectURL(\n    jsg::Lock& js, kj::OneOf<jsg::Ref<File>, jsg::Ref<Blob>> object) {\n  // TODO(soon): Implement this\n  JSG_FAIL_REQUIRE(Error, \"URL.createObjectURL() is not implemented\"_kj);\n}\n\nvoid URL::revokeObjectURL(jsg::Lock& js, jsg::USVString object_url) {\n  // TODO(soon): Implement this\n  JSG_FAIL_REQUIRE(Error, \"URL.revokeObjectURL() is not implemented\"_kj);\n}\n\njsg::Ref<URLSearchParams> URL::getSearchParams(jsg::Lock& js) {\n  KJ_IF_SOME(searchParams, maybeSearchParams) {\n    return searchParams.addRef();\n  }\n  return maybeSearchParams.emplace(js.alloc<URLSearchParams>(inner.getSearch(), *this)).addRef();\n}\n\nkj::Array<const char> URL::getOrigin() {\n  return inner.getOrigin();\n}\n\nkj::ArrayPtr<const char> URL::getHref() {\n  return inner.getHref();\n}\n\nvoid URL::setHref(jsg::Lock& js, jsg::USVString value) {\n  // Href setter is the only place in URL parser that is allowed to throw except the constructor.\n  if (!inner.setHref(value)) {\n    auto context = jsg::TypeErrorContext::setterArgument(typeid(URL), \"href\");\n    jsg::throwTypeError(js.v8Isolate, context, \"valid URL string\");\n  }\n  KJ_IF_SOME(searchParams, maybeSearchParams) {\n    searchParams->reset();\n  }\n}\n\nkj::ArrayPtr<const char> URL::getProtocol() {\n  return inner.getProtocol();\n}\n\nvoid URL::setProtocol(jsg::USVString value) {\n  inner.setProtocol(value);\n}\n\nkj::ArrayPtr<const char> URL::getUsername() {\n  return inner.getUsername();\n}\n\nvoid URL::setUsername(jsg::USVString value) {\n  inner.setUsername(value);\n}\n\nkj::ArrayPtr<const char> URL::getPassword() {\n  return inner.getPassword();\n}\n\nvoid URL::setPassword(jsg::USVString value) {\n  inner.setPassword(value);\n}\n\nkj::ArrayPtr<const char> URL::getHost() {\n  return inner.getHost();\n}\n\nvoid URL::setHost(jsg::USVString value) {\n  inner.setHost(value);\n}\n\nkj::ArrayPtr<const char> URL::getHostname() {\n  return inner.getHostname();\n}\n\nvoid URL::setHostname(jsg::USVString value) {\n  inner.setHostname(value);\n}\n\nkj::ArrayPtr<const char> URL::getPort() {\n  return inner.getPort();\n}\n\nvoid URL::setPort(jsg::USVString value) {\n  inner.setPort(kj::Maybe(value.asPtr()));\n}\n\nkj::ArrayPtr<const char> URL::getPathname() {\n  return inner.getPathname();\n}\n\nvoid URL::setPathname(jsg::USVString value) {\n  inner.setPathname(value);\n}\n\nkj::ArrayPtr<const char> URL::getSearch() {\n  return inner.getSearch();\n}\n\nvoid URL::setSearch(jsg::USVString value) {\n  inner.setSearch(kj::Maybe(value.asPtr()));\n  KJ_IF_SOME(searchParams, maybeSearchParams) {\n    searchParams->reset();\n  }\n}\n\nkj::ArrayPtr<const char> URL::getHash() {\n  return inner.getHash();\n}\n\nvoid URL::setHash(jsg::USVString hash) {\n  inner.setHash(kj::Maybe(hash.asPtr()));\n}\n\nvoid URL::visitForGc(jsg::GcVisitor& visitor) {\n  visitor.visit(maybeSearchParams);\n}\n\n// ======================================================================================\nnamespace {\njsg::UrlSearchParams initSearchParams(const URLSearchParams::Initializer& init) {\n  KJ_SWITCH_ONEOF(init) {\n    KJ_CASE_ONEOF(pairs, URLSearchParams::StringPairs) {\n      jsg::UrlSearchParams params;\n      for (auto& item: pairs) {\n        JSG_REQUIRE(item.size() == 2, TypeError, \"Invalid URL search params sequence.\");\n        params.append(item[0], item[1]);\n      }\n      return kj::mv(params);\n    }\n    KJ_CASE_ONEOF(dict, jsg::Dict<jsg::USVString, jsg::USVString>) {\n      kj::HashMap<kj::StringPtr, kj::StringPtr> paramsMap;\n      for (auto& item: dict.fields) {\n        paramsMap.upsert(item.name, item.value);\n      }\n\n      jsg::UrlSearchParams params;\n      for (auto& item: paramsMap) {\n        params.append(item.key, item.value);\n      }\n      return kj::mv(params);\n    }\n    KJ_CASE_ONEOF(str, jsg::USVString) {\n      return JSG_REQUIRE_NONNULL(\n          jsg::UrlSearchParams::tryParse(str), TypeError, \"Invalid URL search params string.\");\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\njsg::UrlSearchParams initFromSearch(kj::Maybe<kj::ArrayPtr<const char>>& maybeQuery) {\n  KJ_IF_SOME(query, maybeQuery) {\n    return JSG_REQUIRE_NONNULL(\n        jsg::UrlSearchParams::tryParse(query), TypeError, \"Invalid URL search params string.\");\n  }\n  return jsg::UrlSearchParams();\n}\n}  // namespace\n\njsg::Ref<URLSearchParams> URLSearchParams::constructor(\n    jsg::Lock& js, jsg::Optional<Initializer> init) {\n  return js.alloc<URLSearchParams>(kj::mv(init).orDefault(jsg::USVString()));\n}\n\nURLSearchParams::URLSearchParams(Initializer initializer): inner(initSearchParams(initializer)) {}\n\nURLSearchParams::URLSearchParams(kj::Maybe<kj::ArrayPtr<const char>> maybeQuery, URL& url)\n    : inner(initFromSearch(maybeQuery)),\n      maybeUrl(url) {}\n\nuint URLSearchParams::getSize() {\n  return inner.size();\n}\n\nvoid URLSearchParams::update() {\n  KJ_IF_SOME(url, maybeUrl) {\n    auto serialized = toString();\n    url.inner.setSearch(kj::Maybe(serialized.asPtr()));\n  }\n}\n\nvoid URLSearchParams::reset() {\n  KJ_IF_SOME(url, maybeUrl) {\n    auto search = kj::Maybe(url.inner.getSearch());\n    inner.reset(search);\n  }\n}\n\nvoid URLSearchParams::append(jsg::USVString name, jsg::USVString value) {\n  inner.append(name, value);\n  update();\n}\n\nvoid URLSearchParams::delete_(\n    jsg::Lock& js, jsg::USVString name, jsg::Optional<jsg::USVString> value) {\n  KJ_ON_SCOPE_SUCCESS(update());\n  if (FeatureFlags::get(js).getUrlSearchParamsDeleteHasValueArg()) {\n    // The whatwg url spec was updated to add a second optional argument to delete()\n    // and has(). While it was determined that it likely didn't break browser users,\n    // the change could break at least a couple existing deployed workers so we have\n    // to gate support behind a compat flag.\n    KJ_IF_SOME(v, value) {\n      inner.delete_(name, kj::Maybe(v.asPtr()));\n      return;\n    }\n  }\n  inner.delete_(name);\n}\n\nkj::Maybe<kj::ArrayPtr<const char>> URLSearchParams::get(jsg::USVString name) {\n  return inner.get(name);\n}\n\nkj::Array<kj::ArrayPtr<const char>> URLSearchParams::getAll(jsg::USVString name) {\n  return inner.getAll(name);\n}\n\nbool URLSearchParams::has(jsg::Lock& js, jsg::USVString name, jsg::Optional<jsg::USVString> value) {\n  if (FeatureFlags::get(js).getUrlSearchParamsDeleteHasValueArg()) {\n    // The whatwg url spec was updated to add a second optional argument to delete()\n    // and has(). While it was determined that it likely didn't break browser users,\n    // the change could break at least a couple existing deployed workers so we have\n    // to gate support behind a compat flag.\n    KJ_IF_SOME(v, value) {\n      return inner.has(name, kj::Maybe(v.asPtr()));\n    }\n  }\n  return inner.has(name);\n}\n\nvoid URLSearchParams::set(jsg::USVString name, jsg::USVString value) {\n  inner.set(name, value);\n  update();\n}\n\nvoid URLSearchParams::sort() {\n  inner.sort();\n  update();\n}\n\njsg::Ref<URLSearchParams::EntryIterator> URLSearchParams::entries(jsg::Lock& js) {\n  return js.alloc<URLSearchParams::EntryIterator>(\n      IteratorState<jsg::UrlSearchParams::EntryIterator>(JSG_THIS, inner.getEntries()));\n}\n\njsg::Ref<URLSearchParams::KeyIterator> URLSearchParams::keys(jsg::Lock& js) {\n  return js.alloc<URLSearchParams::KeyIterator>(\n      IteratorState<jsg::UrlSearchParams::KeyIterator>(JSG_THIS, inner.getKeys()));\n}\n\njsg::Ref<URLSearchParams::ValueIterator> URLSearchParams::values(jsg::Lock& js) {\n  return alloc<URLSearchParams::ValueIterator>(\n      IteratorState<jsg::UrlSearchParams::ValueIterator>(JSG_THIS, inner.getValues()));\n}\n\nkj::Maybe<kj::Array<kj::ArrayPtr<const char>>> URLSearchParams::entryIteratorNext(\n    jsg::Lock& js, URLSearchParams::IteratorState<jsg::UrlSearchParams::EntryIterator>& state) {\n  return state.inner.next().map([](const jsg::UrlSearchParams::EntryIterator::Entry& entry) {\n    return kj::arr(entry.key, entry.value);\n  });\n}\n\nkj::Maybe<kj::ArrayPtr<const char>> URLSearchParams::keyIteratorNext(\n    jsg::Lock& js, URLSearchParams::IteratorState<jsg::UrlSearchParams::KeyIterator>& state) {\n  return state.inner.next();\n}\n\nkj::Maybe<kj::ArrayPtr<const char>> URLSearchParams::valueIteratorNext(\n    jsg::Lock& js, URLSearchParams::IteratorState<jsg::UrlSearchParams::ValueIterator>& state) {\n  return state.inner.next();\n}\n\nvoid URLSearchParams::forEach(jsg::Lock& js,\n    jsg::Function<void(kj::StringPtr, kj::StringPtr, jsg::Ref<URLSearchParams>)> callback,\n    jsg::Optional<jsg::JsValue> thisArg) {\n  auto receiver = js.undefined();\n  KJ_IF_SOME(arg, thisArg) {\n    if (!arg.isNullOrUndefined()) {\n      receiver = arg;\n    }\n  }\n  callback.setReceiver(js.v8Ref<v8::Value>(receiver));\n\n  auto entries = inner.getEntries();\n  while (entries.hasNext()) {\n    auto next = KJ_ASSERT_NONNULL(entries.next());\n    kj::String value(\n        const_cast<char*>(next.value.begin()), next.value.size(), kj::NullArrayDisposer::instance);\n    kj::String key(\n        const_cast<char*>(next.key.begin()), next.key.size(), kj::NullArrayDisposer::instance);\n    callback(js, value, key, JSG_THIS);\n  }\n}\n\nkj::String URLSearchParams::toString() {\n  return kj::str(inner);\n}\n\n}  // namespace workerd::api::url\n"
  },
  {
    "path": "src/workerd/api/url-standard.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/one-of.h>\n#include <workerd/api/blob.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/url.h>\n#include <workerd/io/compatibility-date.capnp.h>\n\nnamespace workerd::api {\n// The original URL implementation based on kj::Url is not compliant with the\n// WHATWG URL standard, but we can't get rid of it. This is an alternate\n// implementation that is based on the spec. It can be enabled using a\n// configuration flag. We put it in its own namespace to keep its classes\n// from conflicting with the old implementation.\nnamespace url {\n\nclass URL;\n\n// The URLSearchParams object is a wrapper for application/x-www-form-urlencoded\n// data. It can be used by itself or with URL (every URL object has a searchParams\n// attribute that is kept in sync).\nclass URLSearchParams: public jsg::Object {\n  template <typename T>\n  struct IteratorState {\n    jsg::Ref<URLSearchParams> self;\n    T inner;\n    IteratorState(jsg::Ref<URLSearchParams> self, T t) : self(kj::mv(self)), inner(kj::mv(t)) {}\n    void visitForGc(jsg::GcVisitor& visitor) {\n      visitor.visit(self);\n    }\n  };\npublic:\n  using StringPair = jsg::Sequence<jsg::USVString>;\n  using StringPairs = jsg::Sequence<StringPair>;\n\n  using Initializer = kj::OneOf<StringPairs,\n                                jsg::Dict<jsg::USVString, jsg::USVString>,\n                                jsg::USVString>;\n\n  // Constructor called by the static constructor method.\n  URLSearchParams(Initializer init);\n\n  // Constructor called by the URL class when created.\n  URLSearchParams(kj::Maybe<kj::ArrayPtr<const char>> maybeQuery, URL& url);\n\n  static jsg::Ref<URLSearchParams> constructor(jsg::Lock& js, jsg::Optional<Initializer> init);\n\n  void append(jsg::USVString name, jsg::USVString value);\n  void delete_(jsg::Lock& js, jsg::USVString name, jsg::Optional<jsg::USVString> value);\n  kj::Maybe<kj::ArrayPtr<const char>> get(jsg::USVString name);\n  kj::Array<kj::ArrayPtr<const char>> getAll(jsg::USVString name);\n  bool has(jsg::Lock& js, jsg::USVString name, jsg::Optional<jsg::USVString> value);\n  void set(jsg::USVString name, jsg::USVString value);\n  void sort();\n\n  JSG_ITERATOR(EntryIterator, entries,\n               kj::Array<kj::ArrayPtr<const char>>,\n               IteratorState<jsg::UrlSearchParams::EntryIterator>,\n               entryIteratorNext)\n  JSG_ITERATOR(KeyIterator, keys,\n               kj::ArrayPtr<const char>,\n               IteratorState<jsg::UrlSearchParams::KeyIterator>,\n               keyIteratorNext)\n  JSG_ITERATOR(ValueIterator, values,\n               kj::ArrayPtr<const char>,\n               IteratorState<jsg::UrlSearchParams::ValueIterator>,\n               valueIteratorNext)\n\n  void forEach(jsg::Lock&,\n               jsg::Function<void(kj::StringPtr, kj::StringPtr, jsg::Ref<URLSearchParams>)>,\n               jsg::Optional<jsg::JsValue>);\n\n  kj::String toString();\n\n  uint getSize();\n\n  JSG_RESOURCE_TYPE(URLSearchParams, CompatibilityFlags::Reader flags) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(size, getSize);\n    JSG_METHOD(append);\n    JSG_METHOD_NAMED(delete, delete_);\n    JSG_METHOD(get);\n    JSG_METHOD(getAll);\n    JSG_METHOD(has);\n    JSG_METHOD(set);\n    JSG_METHOD(sort);\n    JSG_METHOD(entries);\n    JSG_METHOD(keys);\n    JSG_METHOD(values);\n    JSG_METHOD(forEach);\n    JSG_METHOD(toString);\n    JSG_ITERABLE(entries);\n\n    if (!flags.getSpecCompliantUrl()) {\n      // This is a hack. The spec-compliant URLSearchParams type is used in the Body constructor,\n      // see https://github.com/cloudflare/workerd/blob/v1.20241127.0/src/workerd/api/http.h#L255\n      // This means that when the TypeScript generation scripts are visiting root types for\n      // inclusion, we'll always visit the spec-compliant type even if we have the \"url-standard\"\n      // flag disabled. Rather than updating those usages based on which flags are enabled, we just\n      // delete the spec compliant declaration in an override if \"url-standard\" is disabled.\n      // We do the same for the non-spec-compliant URLSearchParams\n      // (https://github.com/cloudflare/workerd/blob/v1.20241127.0/src/workerd/api/url.h#L219).\n      JSG_TS_OVERRIDE(type URLSearchParams = never);\n    } else if (flags.getUrlSearchParamsDeleteHasValueArg()) {\n      JSG_TS_OVERRIDE(URLSearchParams {\n        entries(): IterableIterator<[key: string, value: string]>;\n        [Symbol.iterator](): IterableIterator<[key: string, value: string]>;\n\n        forEach<This = unknown>(callback: (this: This, value: string, key: string, parent: URLSearchParams) => void, thisArg?: This): void;\n      });\n    } else {\n      JSG_TS_OVERRIDE(URLSearchParams {\n        delete(name: string): void;\n        has(name: string): boolean;\n\n        entries(): IterableIterator<[key: string, value: string]>;\n        [Symbol.iterator](): IterableIterator<[key: string, value: string]>;\n\n        forEach<This = unknown>(callback: (this: This, value: string, key: string, parent: URLSearchParams) => void, thisArg?: This): void;\n      });\n    }\n    // Rename from urlURLSearchParams\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"inner\", inner);\n    tracker.trackField(\"url\", maybeUrl);\n  }\n\nprivate:\n  jsg::UrlSearchParams inner;\n  kj::Maybe<URL&> maybeUrl;\n\n  // Updates the associated URL (if any) with the serialized contents of this URLSearchParam\n  void update();\n\n  // Updates the contents of this URLSearchParam with the current contents of the associated\n  // URLs search component.\n  void reset();\n\n  static kj::Maybe<kj::Array<kj::ArrayPtr<const char>>> entryIteratorNext(\n      jsg::Lock& js,\n      IteratorState<jsg::UrlSearchParams::EntryIterator>& state);\n  static kj::Maybe<kj::ArrayPtr<const char>> keyIteratorNext(\n      jsg::Lock& js,\n      IteratorState<jsg::UrlSearchParams::KeyIterator>& state);\n  static kj::Maybe<kj::ArrayPtr<const char>> valueIteratorNext(\n      jsg::Lock& js,\n      IteratorState<jsg::UrlSearchParams::ValueIterator>& state);\n\n  friend class URL;\n};\n\n// The humble URL object, in all its spec-compliant glory.\n// The majority of the implementation is covered by jsg::Url.\nclass URL: public jsg::Object {\npublic:\n  URL(kj::StringPtr url, kj::Maybe<kj::StringPtr> base = kj::none);\n  ~URL() noexcept(false) override;\n\n  static jsg::Ref<URL> constructor(jsg::Lock& js, jsg::USVString url, jsg::Optional<jsg::USVString> base);\n\n  static kj::Maybe<jsg::Ref<URL>> parse(jsg::Lock& js, jsg::USVString url, jsg::Optional<jsg::USVString> base) {\n    // Method should not throw if the parse fails\n    return js.tryCatch([&]() -> kj::Maybe<jsg::Ref<URL>> {\n      return constructor(js, kj::mv(url), kj::mv(base));\n    }, [](auto) -> kj::Maybe<jsg::Ref<URL>> { return kj::none; });\n  }\n\n  kj::ArrayPtr<const char> getHref();\n  void setHref(jsg::Lock& js, jsg::USVString value);\n\n  kj::Array<const char> getOrigin();\n\n  kj::ArrayPtr<const char> getProtocol();\n  void setProtocol(jsg::USVString value);\n\n  kj::ArrayPtr<const char> getUsername();\n  void setUsername(jsg::USVString value);\n\n  kj::ArrayPtr<const char> getPassword();\n  void setPassword(jsg::USVString value);\n\n  kj::ArrayPtr<const char> getHost();\n  void setHost(jsg::USVString value);\n\n  kj::ArrayPtr<const char> getHostname();\n  void setHostname(jsg::USVString value);\n\n  kj::ArrayPtr<const char> getPort();\n  void setPort(jsg::USVString value);\n\n  kj::ArrayPtr<const char> getPathname();\n  void setPathname(jsg::USVString value);\n\n  kj::ArrayPtr<const char> getSearch();\n  void setSearch(jsg::USVString value);\n\n  kj::ArrayPtr<const char> getHash();\n  void setHash(jsg::USVString value);\n\n  jsg::Ref<URLSearchParams> getSearchParams(jsg::Lock& js);\n\n  // Standard utility that returns true if the given input can be\n  // successfully parsed as a URL. This is useful for validating\n  // URL inputs without incurring the additional cost of constructing\n  // and throwing an error. For example:\n  //\n  // const urls = [\n  //   'https://example.org/good',\n  //   'not a url'\n  // ].filter((test) => URL.canParse(test));\n  //\n  static bool canParse(jsg::USVString url, jsg::Optional<jsg::USVString> base = kj::none);\n  static jsg::JsString createObjectURL(jsg::Lock& js, kj::OneOf<jsg::Ref<File>, jsg::Ref<Blob>> object);\n  static void revokeObjectURL(jsg::Lock& js, jsg::USVString object_url);\n\n  JSG_RESOURCE_TYPE(URL) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(origin, getOrigin);\n    JSG_PROTOTYPE_PROPERTY(href, getHref, setHref);\n    JSG_PROTOTYPE_PROPERTY(protocol, getProtocol, setProtocol);\n    JSG_PROTOTYPE_PROPERTY(username, getUsername, setUsername);\n    JSG_PROTOTYPE_PROPERTY(password, getPassword, setPassword);\n    JSG_PROTOTYPE_PROPERTY(host, getHost, setHost);\n    JSG_PROTOTYPE_PROPERTY(hostname, getHostname, setHostname);\n    JSG_PROTOTYPE_PROPERTY(port, getPort, setPort);\n    JSG_PROTOTYPE_PROPERTY(pathname, getPathname, setPathname);\n    JSG_PROTOTYPE_PROPERTY(search, getSearch, setSearch);\n    JSG_PROTOTYPE_PROPERTY(hash, getHash, setHash);\n    JSG_READONLY_PROTOTYPE_PROPERTY(searchParams, getSearchParams);\n    JSG_METHOD_NAMED(toJSON, getHref);\n    JSG_METHOD_NAMED(toString, getHref);\n    JSG_STATIC_METHOD(canParse);\n    JSG_STATIC_METHOD(parse);\n    JSG_STATIC_METHOD(createObjectURL);\n    JSG_STATIC_METHOD(revokeObjectURL);\n\n    JSG_TS_OVERRIDE(URL {\n      constructor(url: string | URL, base?: string | URL);\n    });\n    // Rename from urlURL, and allow URLs which get coerced to strings in either\n    // constructor parameter\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"inner\", inner);\n    tracker.trackField(\"searchParams\", maybeSearchParams);\n  }\n\n  operator const jsg::Url&() const { return inner; }\n  operator jsg::Url() { return inner.clone(); }\n\nprivate:\n  jsg::Url inner;\n  kj::Maybe<jsg::Ref<URLSearchParams>> maybeSearchParams;\n\n  void visitForGc(jsg::GcVisitor& visitor);\n\n  friend class URLSearchParams;\n};\n\n#define EW_URL_STANDARD_ISOLATE_TYPES              \\\n  api::url::URL,                                   \\\n  api::url::URLSearchParams,                       \\\n  api::url::URLSearchParams::EntryIterator,        \\\n  api::url::URLSearchParams::EntryIterator::Next,  \\\n  api::url::URLSearchParams::KeyIterator,          \\\n  api::url::URLSearchParams::KeyIterator::Next,    \\\n  api::url::URLSearchParams::ValueIterator,        \\\n  api::url::URLSearchParams::ValueIterator::Next\n\n}  // namespace url\n}  // namespace api\n\n"
  },
  {
    "path": "src/workerd/api/url.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"url.h\"\n\n#include \"util.h\"\n\n#include <kj/encoding.h>\n#include <kj/parse/char.h>\n#include <kj/string-tree.h>\n\n#include <algorithm>\n#include <map>\n#include <set>\n\nnamespace workerd::api {\n\nnamespace {\n\n// Helper functions for the origin, pathname, and search getters and setters.\n\n// The folowing two lists needs to be kept in sync since the length and the order\n// of them are needed to properly calculate the hash/index.\nconstexpr kj::StringPtr is_special_list[] = {\n  \"http\"_kj, \" \"_kj, \"https\"_kj, \"ws\"_kj, \"ftp\"_kj, \"wss\"_kj, \"file\"_kj, \" \"_kj};\nconstexpr kj::StringPtr special_ports[] = {\n  \"80\"_kj, \"\"_kj, \"443\"_kj, \"80\"_kj, \"21\"_kj, \"443\"_kj, \"\"_kj, \"\"_kj};\n\n// Taken from Ada URL library.\n// Ref: https://github.com/ada-url/ada/blob/b431670699cf4f3ebb2e2c394c23a89850bb6f3f/include/ada/scheme-inl.h#L49\nbool isSpecialScheme(kj::StringPtr scheme) noexcept {\n  if (scheme.size() == 0) {\n    return false;\n  }\n  // Depending on the first character and the size of the input, this line calculates\n  // the index from the list above.\n  //\n  // Generate a simple hash value that will always be between 0 and 7 (inclusive),\n  // regardless of the input. This is because the bitwise AND with 7 ensures that\n  // only the last 3 bits of the result are kept.\n  int hash_value = (2 * scheme.size() + static_cast<unsigned>(scheme[0])) & 7;\n  const auto target = is_special_list[hash_value];\n  return (target[0] == scheme[0]) && (target.slice(1) == scheme.slice(1));\n}\n\n// Taken from Ada URL library.\n// Ref: https://github.com/ada-url/ada/blob/b431670699cf4f3ebb2e2c394c23a89850bb6f3f/include/ada/scheme-inl.h#L57\nkj::Maybe<kj::StringPtr> defaultPortForScheme(kj::StringPtr scheme) noexcept {\n  if (scheme.size() == 0) {\n    return kj::none;\n  }\n  int hash_value = (2 * scheme.size() + static_cast<unsigned>(scheme[0])) & 7;\n  const auto target = is_special_list[hash_value];\n  if ((target[0] == scheme[0]) && (target.slice(1) == scheme.slice(1))) {\n    auto port = special_ports[hash_value];\n    if (port.size() == 0) {\n      return kj::none;\n    }\n    return port;\n  }\n\n  return kj::none;\n}\n\nvoid normalizePort(kj::Url& url) {\n  // Remove trailing ':', and remove ':xxx' if xxx is the scheme-default port.\n\n  KJ_IF_SOME(colon, url.host.findFirst(':')) {\n    if (url.host.size() == colon + 1) {\n      // Remove trailing ':'.\n      url.host = kj::str(url.host.first(colon));\n    } else KJ_IF_SOME(defaultPort, defaultPortForScheme(url.scheme)) {\n      if (defaultPort == url.host.slice(colon + 1)) {\n        // Remove scheme-default port.\n        url.host = kj::str(url.host.first(colon));\n      }\n    }\n  }\n}\n\nkj::Maybe<kj::ArrayPtr<const char>> trySplit(kj::ArrayPtr<const char>& text, char c) {\n  // TODO(cleanup): Code duplication with kj/compat/url.c++.\n\n  for (auto i: kj::indices(text)) {\n    if (text[i] == c) {\n      kj::ArrayPtr<const char> result = text.first(i);\n      text = text.slice(i + 1, text.size());\n      return result;\n    }\n  }\n  return kj::none;\n}\n\nkj::ArrayPtr<const char> split(kj::StringPtr& text, const kj::parse::CharGroup_& chars) {\n  // TODO(cleanup): Code duplication with kj/compat/url.c++.\n\n  for (auto i: kj::indices(text)) {\n    if (chars.contains(text[i])) {\n      kj::ArrayPtr<const char> result = text.first(i);\n      text = text.slice(i);\n      return result;\n    }\n  }\n  auto result = text.asArray();\n  text = \"\";\n  return result;\n}\n\nkj::String percentDecode(kj::ArrayPtr<const char> text, bool& hadErrors) {\n  // TODO(cleanup): Code duplication with kj/compat/url.c++.\n\n  auto result = kj::decodeUriComponent(text);\n  if (result.hadErrors) hadErrors = true;\n  return kj::mv(result);\n}\n\nkj::String percentDecodeQuery(kj::ArrayPtr<const char> text, bool& hadErrors) {\n  // TODO(cleanup): Code duplication with kj/compat/url.c++.\n\n  auto result = kj::decodeWwwForm(text);\n  if (result.hadErrors) hadErrors = true;\n  return kj::mv(result);\n}\n\n// Use this instead of calling kj::Url::toString() directly.\nkj::String kjUrlToString(const kj::Url& url) {\n  kj::String result;\n  KJ_IF_SOME(exception, kj::runCatchingExceptions([&]() {\n    result = url.toString();\n    // TODO(soon): This stringifier does not append trailing slashes to the pathname conformantly.\n    //   For example, this equality currently does not hold true:\n    //\n    //     new URL('https://capnproto.org?query').href === 'https://capnproto.org/?query'\n    //\n    //   Fixing this bug would enable a plurality of the W3C test cases which currently fail. I.e.,\n    //   it's the lowest hanging fruit. ;)\n  })) {\n    // TODO(conform): toString() really shouldn't be throwing anything, because it shouldn't be\n    //   possible to get the URL object in a state where it has any invalid component. However, a\n    //   variety of bugs conspire to make it possible (notably, EW-962 and EW-1731), and we're stuck\n    //   with the situation for now. Rather than expose these errors to the user as opaque internal\n    //   errors (and nag us via Sentry), we get our hands dirty with some string matching, in the\n    //   hopes of helping users work around the bugs.\n    KJ_IF_SOME(e,\n        translateKjException(exception,\n            {\n              {\"invalid hostname when stringifying URL\"_kj,\n                \"Invalid hostname when stringifying URL.\"_kj},\n              {\"invalid name in URL path\"_kj, \"Invalid pathname when stringifying URL.\"_kj},\n            })) {\n      kj::throwFatalException(kj::mv(e));\n    }\n\n    // This is either an error we should know about and expect, or an \"internal error\". Either way,\n    // squawk about it.\n    KJ_LOG(ERROR, exception);\n    JSG_FAIL_REQUIRE(TypeError, \"Error stringifying URL.\");\n  }\n\n  return kj::mv(result);\n}\n\n}  // namespace\n\n// =======================================================================================\n// URL\n\njsg::Ref<URL> URL::constructor(jsg::Lock& js, kj::String url, jsg::Optional<kj::String> base) {\n  KJ_IF_SOME(b, base) {\n    auto baseUrl =\n        JSG_REQUIRE_NONNULL(kj::Url::tryParse(kj::mv(b)), TypeError, \"Invalid base URL string.\");\n    return js.alloc<URL>(JSG_REQUIRE_NONNULL(\n        baseUrl.tryParseRelative(kj::mv(url)), TypeError, \"Invalid relative URL string.\"));\n  }\n  return js.alloc<URL>(\n      JSG_REQUIRE_NONNULL(kj::Url::tryParse(kj::mv(url)), TypeError, \"Invalid URL string.\"));\n}\n\nURL::URL(kj::Url&& u): url(kj::refcounted<RefcountedUrl>(kj::mv(u))) {\n  normalizePort(*url);\n}\n\n// Setters and Getters\n//\n// When possible, getters just pull out the corresponding attribute from kj::Url and return it.\n// Sometimes we need to modify the output a bit, e.g. to get the hostname and port separately.\n//\n// Setters need to set and validate new input. To accomplish this without reimplementing validation\n// code that ought to live in kj::Url, I have implemented setters using the following general\n// strategy:\n//\n// 1. Pre-process the input, if necessary. E.g., we drop anything after a ':' when setting protocol.\n// 2. Clone the kj::Url object.\n// 3. Replace the cloned component in question with the new value.\n// 4. Stringify and parse the clone. If this succeeds, the clone is the new kj::Url object.\n//\n// Notably, we do little to no validation in this wrapper class. As validation checks are added to\n// kj::Url's parser, more and more unit tests for this wrapper class should start passing without\n// modification.\n//\n// TODO(perf): Pre-processing input, cloning, stringifying, and parsing the cloned URL is an awfully\n//   heavyweight operation when all we want to do is validly replace a URL component. A couple\n//   attributes, pathname and search, are able to take advantage of the kj::Url parser's context\n//   argument: we can parse a pathname using the HTTP_REQUEST context, for instance. The WHATWG URL\n//   spec defines a parser state machine allowing for the state to be overridden to parse only\n//   specific components of a URL. This is more or less a generalization of kj::Url's parser\n//   context, and offers an obvious path forward to both conformance and performance.\n\nkj::String URL::getHref() {\n  return toString();\n}\nvoid URL::setHref(jsg::Lock& js, kj::String value) {\n  KJ_IF_SOME(u, kj::Url::tryParse(kj::mv(value))) {\n    url->kj::Url::operator=(kj::mv(u));\n  } else {\n    auto context = jsg::TypeErrorContext::setterArgument(typeid(URL), \"href\");\n    jsg::throwTypeError(js.v8Isolate, context, \"valid URL string\");\n    // href's is the only setter which is allowed to throw on invalid input, according to the spec.\n  }\n}\n\nkj::String URL::getOrigin() {\n  // TODO(cleanup): Move this logic into kj::Url.\n\n  if (isSpecialScheme(url->scheme) && url->scheme != \"file\") {\n    return kj::str(url->scheme, \"://\", url->host);\n  } else if (url->scheme == \"file\") {\n    return kj::str(\"null\");\n  } else if (url->scheme == \"blob\") {\n    // TODO(soon): Parse url->path[0] and return that if it has an origin.\n    return kj::str(\"null\");\n  }\n  return kj::str(\"null\");\n}\n\nkj::String URL::getProtocol() {\n  return kj::str(url->scheme, ':');\n}\nvoid URL::setProtocol(kj::String value) {\n  KJ_IF_SOME(colon, value.findFirst(':')) {\n    value = kj::str(value.first(colon));\n  }\n\n  auto copy = url->clone();\n  copy.scheme = kj::mv(value);\n\n  KJ_IF_SOME(u, kj::Url::tryParse(kjUrlToString(copy))) {\n    url->kj::Url::operator=(kj::mv(u));\n  }\n\n  normalizePort(*url);\n}\n\nkj::String URL::getUsername() {\n  KJ_IF_SOME(userInfo, url->userInfo) {\n    return kj::encodeUriUserInfo(userInfo.username);\n  }\n  return {};\n}\nvoid URL::setUsername(kj::String value) {\n  auto copy = url->clone();\n  KJ_IF_SOME(ui, copy.userInfo) {\n    ui.username = kj::mv(value);\n  } else {\n    copy.userInfo = kj::Url::UserInfo{.username = kj::mv(value)};\n  }\n\n  KJ_IF_SOME(u, kj::Url::tryParse(kjUrlToString(copy))) {\n    url->kj::Url::operator=(kj::mv(u));\n  }\n}\n\nkj::String URL::getPassword() {\n  KJ_IF_SOME(userInfo, url->userInfo) {\n    KJ_IF_SOME(password, userInfo.password) {\n      return kj::encodeUriUserInfo(password);\n    }\n  }\n  return {};\n}\nvoid URL::setPassword(kj::String value) {\n  auto copy = url->clone();\n  KJ_IF_SOME(ui, copy.userInfo) {\n    // We already have userInfo. Set the password if we were given a non-empty string, otherwise\n    // reset the password Maybe.\n    if (value.size() > 0) {\n      ui.password = kj::mv(value);\n    } else {\n      ui.password = kj::none;\n    }\n  } else if (value.size() > 0) {\n    copy.userInfo = kj::Url::UserInfo{.password = kj::mv(value)};\n  }\n\n  KJ_IF_SOME(u, kj::Url::tryParse(kjUrlToString(copy))) {\n    url->kj::Url::operator=(kj::mv(u));\n  }\n}\n\nkj::String URL::getHost() {\n  return kj::str(url->host);\n}\nvoid URL::setHost(kj::String value) {\n  // The spec provides the following helpful note:\n  //\n  //   If the given value for the host attribute’s setter lacks a port, context object’s url’s port\n  //   will not change. This can be unexpected as host attribute’s getter does return a URL-port\n  //   string so one might have assumed the setter to always \"reset\" both.\n\n  // If the new host value lacks a port, copy the current one over to the new value, if any. We can\n  // assume that if the current one has a port, it must not be the default port for this URL's\n  // scheme.\n  KJ_IF_SOME(colon, url->host.findFirst(':')) {\n    KJ_IF_SOME(newHostColon, value.findFirst(':')) {\n      if (value.size() == newHostColon + 1) {\n        // The new host has a colon but nothing after it. Adopt the current port.\n        value = kj::str(kj::mv(value), url->host.slice(colon + 1));\n      } else {\n        // The new host has a port, so we don't copy the current one over.\n      }\n    } else {\n      // The new host has no port. Adopt the current port.\n      value = kj::str(kj::mv(value), url->host.slice(colon));\n    }\n  }\n\n  // TODO(soon): Validate the new host string. kj::Url::isValidHost(value)?\n  url->host = kj::mv(value);\n\n  normalizePort(*url);\n}\n\nkj::String URL::getHostname() {\n  KJ_IF_SOME(colon, url->host.findFirst(':')) {\n    return kj::str(url->host.first(colon));\n  }\n  return kj::str(url->host);\n}\nvoid URL::setHostname(kj::String value) {\n  // In contrast to the host setter, the hostname setter explicitly ignores any new port. We take\n  // the hostname from the new value and the port from the old value.\n  auto hostnameString = value.first(value.findFirst(':').orDefault(value.size()));\n  auto portString = url->host.slice(url->host.findFirst(':').orDefault(url->host.size()));\n\n  url->host = kj::str(hostnameString, portString);\n}\n\nkj::String URL::getPort() {\n  KJ_IF_SOME(colon, url->host.findFirst(':')) {\n    return kj::str(url->host.slice(colon + 1));\n  }\n  return {};\n}\nvoid URL::setPort(kj::String value) {\n  KJ_IF_SOME(colon, url->host.findFirst(':')) {\n    // Our url's host already has a port. Replace it.\n    value = kj::str(url->host.first(colon + 1), kj::mv(value));\n  } else {\n    value = kj::str(url->host, ':', kj::mv(value));\n  }\n\n  url->host = kj::mv(value);\n\n  normalizePort(*url);\n}\n\nkj::String URL::getPathname() {\n  if (!url->path.empty()) {\n    auto components =\n        KJ_MAP(component, url->path) { return kj::str('/', kj::encodeUriPath(component)); };\n    return kj::str(kj::strArray(components, \"\"), url->hasTrailingSlash ? \"/\" : \"\");\n  } else if (url->hasTrailingSlash || isSpecialScheme(url->scheme)) {\n    // Special URLs have non-empty paths by definition, regardless of the value of hasTrailingSlash.\n    return kj::str('/');\n  }\n  return {};\n}\nvoid URL::setPathname(kj::String value) {\n  decltype(url->path) newPath;\n  bool newHasTrailingSlash;\n\n  auto text = value.slice(0);\n  bool err = false;\n\n  // TODO(cleanup): Code duplication with kj/compat/url.c++.\n\n  auto addPart = [&]() {\n    // We only look for / to end path components in this setter, not ? and # like in\n    // kj::Url::tryParse().\n    constexpr auto END_PATH_PART = kj::parse::anyOfChars(\"/\");\n    auto part = split(text, END_PATH_PART);\n    if (part.size() == 2 && part[0] == '.' && part[1] == '.') {\n      if (!newPath.empty()) {\n        newPath.removeLast();\n      }\n      newHasTrailingSlash = true;\n    } else if (part.size() == 0 || (part.size() == 1 && part[0] == '.')) {\n      // Collapse consecutive slashes and \"/./\".\n      newHasTrailingSlash = true;\n    } else {\n      newPath.add(percentDecode(part, err));\n      newHasTrailingSlash = false;\n    }\n  };\n\n  // Unlike kj::Url::tryParse(), the pathname being set doesn't have to begin with a slash.\n  if (!text.startsWith(\"/\")) {\n    addPart();\n  }\n\n  while (text.startsWith(\"/\")) {\n    text = text.slice(1);\n    addPart();\n  }\n\n  if (!err) {\n    url->hasTrailingSlash = newHasTrailingSlash;\n    url->path = newPath.releaseAsArray();\n  }\n}\n\nkj::String URL::getSearch() {\n  auto query = KJ_MAP(q, url->query) {\n    // TODO(soon): We shouldn't be performing any encoding here, because our setSearch() (and URL\n    //   constructor) shouldn't be performing application/x-www-form-urlencoded decoding on the\n    //   query string themselves -- that's for URLSearchParams to do.\n    if (q.value.begin() != nullptr) {\n      return kj::str(kj::encodeWwwForm(q.name), '=', kj::encodeWwwForm(q.value));\n    }\n    return kj::str(kj::encodeWwwForm(q.name));\n  };\n\n  if (query.size() > 0) {\n    return kj::str('?', kj::strArray(query, \"&\"));\n  }\n  return {};\n}\nvoid URL::setSearch(kj::String value) {\n  decltype(url->query) newQuery;\n\n  auto text = value.slice(value.startsWith(\"?\") ? 1 : 0);\n  bool err = false;\n\n  // TODO(cleanup): Code duplication with kj/compat/url.c++.\n\n  for (;;) {\n    // We only look for & to end path components in this setter, not # like in kj::Url::tryParse().\n    constexpr auto END_QUERY_PART = kj::parse::anyOfChars(\"&\");\n    auto part = split(text, END_QUERY_PART);\n\n    if (part.size() > 0) {\n      // TODO(soon): We shouldn't be performing any decoding here. Rather, the spec dictates that we\n      //   should actually be percent-*encoding* with a very specific character set. Note that this\n      //   also applies to URL's constructor as well.\n      //\n      //   See step 1.3.1 of https://url.spec.whatwg.org/#query-state\n      KJ_IF_SOME(key, trySplit(part, '=')) {\n        newQuery.add(\n            kj::Url::QueryParam{percentDecodeQuery(key, err), percentDecodeQuery(part, err)});\n      } else {\n        newQuery.add(kj::Url::QueryParam{percentDecodeQuery(part, err), nullptr});\n      }\n    }\n\n    if (!text.startsWith(\"&\")) break;\n    text = text.slice(1);\n  }\n\n  if (!err) {\n    url->query = newQuery.releaseAsArray();\n  }\n}\n\njsg::Ref<URLSearchParams> URL::getSearchParams(jsg::Lock& js) {\n  KJ_IF_SOME(usp, searchParams) {\n    return usp.addRef();\n  } else {\n    searchParams.emplace(js.alloc<URLSearchParams>(kj::addRef(*url)));\n    return KJ_ASSERT_NONNULL(searchParams).addRef();\n  }\n}\n\nkj::String URL::getHash() {\n  KJ_IF_SOME(fragment, url->fragment) {\n    if (fragment.size() > 0) {\n      return kj::str('#', kj::encodeUriFragment(fragment));\n    }\n  }\n  return {};\n}\nvoid URL::setHash(kj::String value) {\n  // Omit any starting '#'.\n  url->fragment = kj::decodeUriComponent(value.slice(value.startsWith(\"#\") ? 1 : 0));\n}\n\nkj::String URL::toString() {\n  return kjUrlToString(*url);\n}\nkj::String URL::toJSON() {\n  return toString();\n}\n\n// =======================================================================================\n// URLSearchParams\n\nURLSearchParams::URLSearchParams(kj::Own<URL::RefcountedUrl> url): url(kj::mv(url)) {}\n\njsg::Ref<URLSearchParams> URLSearchParams::constructor(\n    jsg::Lock& js, jsg::Optional<URLSearchParams::Initializer> init) {\n  auto searchParams = js.alloc<URLSearchParams>(kj::refcounted<URL::RefcountedUrl>());\n\n  KJ_IF_SOME(i, init) {\n    KJ_SWITCH_ONEOF(i) {\n      KJ_CASE_ONEOF(usp, jsg::Ref<URLSearchParams>) {\n        searchParams->url->kj::Url::operator=(usp->url->clone());\n      }\n      KJ_CASE_ONEOF(queryString, kj::String) {\n        parseQueryString(searchParams->url->query, kj::mv(queryString), true);\n      }\n      KJ_CASE_ONEOF(dict, jsg::Dict<kj::String>) {\n        searchParams->url->query = KJ_MAP(entry, dict.fields) {\n          return kj::Url::QueryParam{kj::mv(entry.name), kj::mv(entry.value)};\n        };\n      }\n      KJ_CASE_ONEOF(arrayOfArrays, kj::Array<kj::Array<kj::String>>) {\n        searchParams->url->query = KJ_MAP(entry, arrayOfArrays) {\n          JSG_REQUIRE(entry.size() == 2, TypeError,\n              \"To initialize a URLSearchParams object \"\n              \"from an array-of-arrays, each inner array must have exactly two elements.\");\n          return kj::Url::QueryParam{kj::mv(entry[0]), kj::mv(entry[1])};\n        };\n      }\n    }\n  }\n\n  return searchParams;\n}\n\nvoid URLSearchParams::append(kj::String name, kj::String value) {\n  url->query.add(kj::Url::QueryParam{kj::mv(name), kj::mv(value)});\n}\n\nvoid URLSearchParams::delete_(kj::String name) {\n  auto pivot = std::remove_if(\n      url->query.begin(), url->query.end(), [&name](const auto& kv) { return kv.name == name; });\n  url->query.truncate(pivot - url->query.begin());\n}\n\nkj::Maybe<kj::String> URLSearchParams::get(kj::String name) {\n  for (auto& [k, v]: url->query) {\n    if (k == name) {\n      return kj::str(v);\n    }\n  }\n  return kj::none;\n}\n\nkj::Array<kj::String> URLSearchParams::getAll(kj::String name) {\n  kj::Vector<kj::String> result;\n  for (auto& [k, v]: url->query) {\n    if (k == name) {\n      result.add(kj::str(v));\n    }\n  }\n  return result.releaseAsArray();\n}\n\nbool URLSearchParams::has(kj::String name) {\n  for (auto& [k, v]: url->query) {\n    if (k == name) {\n      return true;\n    }\n  }\n  return false;\n}\n\n// Set the first element named `name` to `value`, then remove all the rest matching that name.\nvoid URLSearchParams::set(kj::String name, kj::String value) {\n  const auto predicate = [name = name.slice(0)](const auto& kv) { return kv.name == name; };\n  auto firstFound = std::find_if(url->query.begin(), url->query.end(), predicate);\n  if (firstFound != url->query.end()) {\n    firstFound->value = kj::mv(value);\n    auto pivot = std::remove_if(++firstFound, url->query.end(), predicate);\n    url->query.truncate(pivot - url->query.begin());\n  } else {\n    append(kj::mv(name), kj::mv(value));\n  }\n}\n\n// Sort by UTF-16 code unit, preserving order of equal elements.\nvoid URLSearchParams::sort() {\n  // TODO(perf): This UTF-16 business is sad. The WPT points out the specific example 🌈 < ﬃ,\n  //   because the rainbow is lexicographically less than the ligature in UTF-16 code units. In\n  //   UTF-8 code units, their order is the opposite.\n  //\n  //       UTF-8       |   UTF-16\n  //   ﬃ   ef ac 83    |  fb03\n  //   🌈  f0 9f 8c 88 |  d83c df08\n\n  std::stable_sort(url->query.begin(), url->query.end(), [](const auto& left, const auto& right) {\n    auto leftUtf16 = fastEncodeUtf16(left.name.asArray());\n    auto rightUtf16 = fastEncodeUtf16(right.name.asArray());\n    return std::lexicographical_compare(\n        leftUtf16.begin(), leftUtf16.end(), rightUtf16.begin(), rightUtf16.end());\n  });\n}\n\nvoid URLSearchParams::forEach(jsg::Lock& js,\n    jsg::Function<void(kj::StringPtr, kj::StringPtr, jsg::Ref<URLSearchParams>)> callback,\n    jsg::Optional<jsg::Value> thisArg) {\n  auto receiver = js.v8Undefined();\n  KJ_IF_SOME(arg, thisArg) {\n    auto handle = arg.getHandle(js);\n    if (!handle->IsNullOrUndefined()) {\n      receiver = handle;\n    }\n  }\n  callback.setReceiver(js.v8Ref(receiver));\n\n  // On each iteration of the for loop, a JavaScript callback is invoked. If a new\n  // item is appended to the this->url->query within that function, the loop must pick\n  // it up. Using the classic for (;;) syntax here allows for that. However, this does\n  // mean that it's possible for a user to trigger an infinite loop here if new items\n  // are added to the search params unconditionally on each iteration.\n  // Silence clang-tidy warning, using an iterator would not work correctly if callback\n  // increases the size of data.\n  // NOLINTNEXTLINE(modernize-loop-convert)\n  for (size_t i = 0; i < this->url->query.size(); i++) {\n    auto& [key, value] = this->url->query[i];\n    callback(js, value, key, JSG_THIS);\n  }\n}\n\njsg::Ref<URLSearchParams::EntryIterator> URLSearchParams::entries(jsg::Lock& js) {\n  return js.alloc<EntryIterator>(IteratorState{JSG_THIS});\n}\n\njsg::Ref<URLSearchParams::KeyIterator> URLSearchParams::keys(jsg::Lock& js) {\n  return js.alloc<KeyIterator>(IteratorState{JSG_THIS});\n}\n\njsg::Ref<URLSearchParams::ValueIterator> URLSearchParams::values(jsg::Lock& js) {\n  return js.alloc<ValueIterator>(IteratorState{JSG_THIS});\n}\n\nkj::String URLSearchParams::toString() {\n  kj::Vector<char> chars(128);\n\n  bool first = true;\n  for (auto& param: url->query) {\n    if (!first) chars.add('&');\n    first = false;\n    chars.addAll(kj::encodeWwwForm(param.name));\n    // This *intentionally* differs from the behavior in URL::getSearch() and kj::Url::toString()!\n    // URLSearchParams has no concept of \"null-valued\" query parameters -- they get coerced to\n    // empty-valued query parameters, so we unconditionally add the '=' sign.\n    chars.add('=');\n    chars.addAll(kj::encodeWwwForm(param.value));\n  }\n\n  chars.add('\\0');\n  return kj::String(chars.releaseAsArray());\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/url.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/io/compatibility-date.capnp.h>\n#include <kj/refcount.h>\n#include <kj/compat/url.h>\n\nnamespace workerd::api {\n\nclass URLSearchParams;\n\n// Implements the URL interface as prescribed by: https://url.spec.whatwg.org/#api\n// This is the legacy, non-standard implementation.\nclass URL: public jsg::Object {\npublic:\n  static jsg::Ref<URL> constructor(jsg::Lock& js, kj::String url, jsg::Optional<kj::String> base);\n\n  // Href is the only setter that throws. All others ignore errors, leaving their values\n  // unchanged.\n  kj::String getHref();\n\n  // Href is the only setter that throws. All others ignore errors, leaving their values\n  // unchanged.\n  void setHref(jsg::Lock& js, kj::String value);\n\n  kj::String getOrigin();\n\n  kj::String getProtocol();\n  void setProtocol(kj::String value);\n\n  kj::String getUsername();\n  void setUsername(kj::String value);\n\n  kj::String getPassword();\n  void setPassword(kj::String value);\n\n  kj::String getHost();\n  void setHost(kj::String value);\n\n  kj::String getHostname();\n  void setHostname(kj::String value);\n\n  kj::String getPort();\n  void setPort(kj::String value);\n\n  kj::String getPathname();\n  void setPathname(kj::String value);\n\n  kj::String getSearch();\n  void setSearch(kj::String value);\n\n  jsg::Ref<URLSearchParams> getSearchParams(jsg::Lock& js);\n\n  kj::String getHash();\n  void setHash(kj::String value);\n\n  kj::String toString();\n  kj::String toJSON();\n\n  JSG_RESOURCE_TYPE(URL, CompatibilityFlags::Reader flags) {\n    // Previously, we were setting all properties as instance properties,\n    // which broke the ability to subclass the URL object. With the\n    // compatibility flag set, we instead attach the properties to the\n    // prototype.\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_PROTOTYPE_PROPERTY(href, getHref, setHref);\n      JSG_READONLY_PROTOTYPE_PROPERTY(origin, getOrigin);\n      JSG_PROTOTYPE_PROPERTY(protocol, getProtocol, setProtocol);\n      JSG_PROTOTYPE_PROPERTY(username, getUsername, setUsername);\n      JSG_PROTOTYPE_PROPERTY(password, getPassword, setPassword);\n      JSG_PROTOTYPE_PROPERTY(host, getHost, setHost);\n      JSG_PROTOTYPE_PROPERTY(hostname, getHostname, setHostname);\n      JSG_PROTOTYPE_PROPERTY(port, getPort, setPort);\n      JSG_PROTOTYPE_PROPERTY(pathname, getPathname, setPathname);\n      JSG_PROTOTYPE_PROPERTY(search, getSearch, setSearch);\n      JSG_READONLY_PROTOTYPE_PROPERTY(searchParams, getSearchParams);\n      JSG_PROTOTYPE_PROPERTY(hash, getHash, setHash);\n    } else {\n      JSG_INSTANCE_PROPERTY(href, getHref, setHref);\n      JSG_READONLY_INSTANCE_PROPERTY(origin, getOrigin);\n      JSG_INSTANCE_PROPERTY(protocol, getProtocol, setProtocol);\n      JSG_INSTANCE_PROPERTY(username, getUsername, setUsername);\n      JSG_INSTANCE_PROPERTY(password, getPassword, setPassword);\n      JSG_INSTANCE_PROPERTY(host, getHost, setHost);\n      JSG_INSTANCE_PROPERTY(hostname, getHostname, setHostname);\n      JSG_INSTANCE_PROPERTY(port, getPort, setPort);\n      JSG_INSTANCE_PROPERTY(pathname, getPathname, setPathname);\n      JSG_INSTANCE_PROPERTY(search, getSearch, setSearch);\n      JSG_READONLY_INSTANCE_PROPERTY(searchParams, getSearchParams);\n      JSG_INSTANCE_PROPERTY(hash, getHash, setHash);\n    }\n\n    JSG_METHOD(toString);\n    JSG_METHOD(toJSON);\n\n    JSG_TS_OVERRIDE({\n      constructor(url: string | URL, base?: string | URL);\n    });\n    // Allow URLs which get coerced to strings in either constructor parameter\n  }\n\n  // Treat as private -- needs to be public for js.alloc<T>()...\n  explicit URL(kj::Url&& u);\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    size_t size = 0;\n    size += url->scheme.size();\n    KJ_IF_SOME(ui, url->userInfo) {\n      size += ui.username.size();\n      KJ_IF_SOME(pwd, ui.password) {\n        size += pwd.size();\n      }\n    }\n    size += url->host.size();\n    for (const auto& p: url->path) {\n      size += p.size();\n    }\n    for (const auto& param : url->query) {\n      size += param.name.size();\n      size += param.value.size();\n    }\n    KJ_IF_SOME(frag, url->fragment) {\n      size += frag.size();\n    }\n    tracker.trackFieldWithSize(\"inner\", size);\n    tracker.trackField(\"searchParams\", searchParams);\n  }\n\nprivate:\n  friend class URLSearchParams;\n\n  struct RefcountedUrl: kj::Refcounted, kj::Url {\n    template <typename... Args>\n    RefcountedUrl(Args&&... args): kj::Url(kj::fwd<Args>(args)...) {}\n  };\n\n  kj::Own<RefcountedUrl> url;\n  kj::Maybe<jsg::Ref<URLSearchParams>> searchParams;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(searchParams);\n  }\n};\n\nclass URLSearchParams: public jsg::Object {\n  // TODO(cleanup): Combine implementation with FormData?\nprivate:\n  struct IteratorState {\n    jsg::Ref<URLSearchParams> parent;\n    uint index = 0;\n\n    void visitForGc(jsg::GcVisitor& visitor) {\n      visitor.visit(parent);\n    }\n  };\n\npublic:\n  explicit URLSearchParams(kj::Own<URL::RefcountedUrl> url);\n\n  using Initializer = kj::OneOf<jsg::Ref<URLSearchParams>, kj::String, jsg::Dict<kj::String>,\n                                kj::Array<kj::Array<kj::String>>>;\n\n  static jsg::Ref<URLSearchParams> constructor(jsg::Lock& js, jsg::Optional<Initializer> init);\n\n  void append(kj::String name, kj::String value);\n  void delete_(kj::String name);\n  kj::Maybe<kj::String> get(kj::String name);\n  kj::Array<kj::String> getAll(kj::String name);\n  bool has(kj::String name);\n  void set(kj::String name, kj::String value);\n\n  void sort();\n\n  JSG_ITERATOR(EntryIterator, entries,\n                kj::Array<kj::String>,\n                IteratorState,\n                entryIteratorNext)\n  JSG_ITERATOR(KeyIterator, keys,\n                kj::StringPtr,\n                IteratorState,\n                keyIteratorNext)\n  JSG_ITERATOR(ValueIterator, values,\n                kj::StringPtr,\n                IteratorState,\n                valueIteratorNext)\n\n  void forEach(jsg::Lock&,\n               jsg::Function<void(kj::StringPtr, kj::StringPtr, jsg::Ref<URLSearchParams>)>,\n               jsg::Optional<jsg::Value>);\n\n  kj::String toString();\n\n  inline uint getSize() { return url->query.size(); }\n\n  JSG_RESOURCE_TYPE(URLSearchParams, CompatibilityFlags::Reader flags) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(size, getSize);\n    JSG_METHOD(append);\n    JSG_METHOD_NAMED(delete, delete_);\n    JSG_METHOD(get);\n    JSG_METHOD(getAll);\n    JSG_METHOD(has);\n    JSG_METHOD(set);\n\n    JSG_METHOD(sort);\n\n    JSG_METHOD(entries);\n    JSG_METHOD(keys);\n    JSG_METHOD(values);\n    JSG_METHOD(forEach);\n\n    JSG_ITERABLE(entries);\n\n    JSG_METHOD(toString);\n\n    if (flags.getSpecCompliantUrl()) {\n      // This is a hack. The non-spec-compliant URLSearchParams type is used in\n      // the Response and Request constructors. This means that when the\n      // TypeScript generation scripts are visiting root types for inclusion,\n      // we'll always visit the non-spec-compliant type even if we have the\n      // \"url-standard\" flag enabled. Rather than updating those usages based\n      // on which flags are enabled, we just delete the non-spec complaint\n      // declaration in an override if \"url-standard\" is enabled.\n      JSG_TS_OVERRIDE(type URLSearchParams = never);\n    } else {\n      JSG_TS_OVERRIDE({\n        constructor(init?: URLSearchParams | string | Record<string, string> | [key: string, value: string][]);\n\n        entries(): IterableIterator<[key: string, value: string]>;\n        [Symbol.iterator](): IterableIterator<[key: string, value: string]>;\n\n        forEach<This = unknown>(callback: (this: This, value: string, key: string, parent: URLSearchParams) => void, thisArg?: This): void;\n      });\n    }\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackFieldWithSize(\"url\", url->toString().size());\n  }\n\nprivate:\n  kj::Own<URL::RefcountedUrl> url;\n\n  static kj::Maybe<kj::Array<kj::String>> entryIteratorNext(jsg::Lock& js, IteratorState& state) {\n    if (state.index >= state.parent->url->query.size()) {\n      return kj::none;\n    }\n    auto& [key, value] = state.parent->url->query[state.index++];\n    return kj::arr(kj::str(key), kj::str(value));\n  }\n\n  static kj::Maybe<kj::StringPtr> keyIteratorNext(jsg::Lock& js, IteratorState& state) {\n    if (state.index >= state.parent->url->query.size()) {\n      return kj::none;\n    }\n    auto& [key, value] = state.parent->url->query[state.index++];\n    return key.asPtr();\n  }\n\n  static kj::Maybe<kj::StringPtr> valueIteratorNext(jsg::Lock& js, IteratorState& state) {\n    if (state.index >= state.parent->url->query.size()) {\n      return kj::none;\n    }\n    auto& [key, value] = state.parent->url->query[state.index++];\n    return value.asPtr();\n  }\n};\n\n#define EW_URL_ISOLATE_TYPES                  \\\n  api::URL,                                   \\\n  api::URLSearchParams,                       \\\n  api::URLSearchParams::EntryIterator,        \\\n  api::URLSearchParams::EntryIterator::Next,  \\\n  api::URLSearchParams::KeyIterator,          \\\n  api::URLSearchParams::KeyIterator::Next,    \\\n  api::URLSearchParams::ValueIterator,        \\\n  api::URLSearchParams::ValueIterator::Next\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/urlpattern-standard.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"urlpattern-standard.h\"\n\n#include \"ada.h\"\n\nnamespace workerd::api::urlpattern {\nstd::optional<URLPattern::URLPatternRegexEngine::regex_type> URLPattern::URLPatternRegexEngine::\n    create_instance(std::string_view pattern, bool ignore_case) {\n  auto& js = jsg::Lock::current();\n  jsg::Lock::RegExpFlags flags = jsg::Lock::RegExpFlags::kUNICODE_SETS;\n  if (ignore_case) {\n    flags = static_cast<jsg::Lock::RegExpFlags>(\n        flags | static_cast<int>(jsg::Lock::RegExpFlags::kIGNORE_CASE));\n  }\n\n  // std::string_view is not guaranteed to be null-terminated, but kj::StringPtr requires it.\n  // We need to create a null-terminated copy.\n  auto str = kj::str(kj::arrayPtr(pattern.data(), pattern.size()));\n  JSG_TRY(js) {\n    return jsg::JsRef(js, js.regexp(str, flags));\n  }\n  JSG_CATCH(_) {\n    return std::nullopt;\n  }\n}\n\nbool URLPattern::URLPatternRegexEngine::regex_match(\n    std::string_view input, const regex_type& pattern) {\n  auto& js = jsg::Lock::current();\n  // std::string_view is not guaranteed to be null-terminated, but kj::StringPtr requires it.\n  // We need to create a null-terminated copy.\n  auto str = kj::str(kj::arrayPtr(input.data(), input.size()));\n  return pattern.getHandle(js).match(js, str);\n}\n\nstd::optional<std::vector<std::optional<std::string>>> URLPattern::URLPatternRegexEngine::\n    regex_search(std::string_view input, const regex_type& pattern) {\n  auto& js = jsg::Lock::current();\n  // std::string_view is not guaranteed to be null-terminated, but kj::StringPtr requires it.\n  // We need to create a null-terminated copy.\n  auto str = kj::str(kj::arrayPtr(input.data(), input.size()));\n  KJ_IF_SOME(matches, pattern.getHandle(js)(js, str)) {\n    std::vector<std::optional<std::string>> results(matches.size() - 1);\n    // The first value is always the input of the exec() command. Therefore\n    // we should avoid it while constructing the returning vector.\n    for (size_t i = 1; i < matches.size(); i++) {\n      auto value = matches.get(js, i);\n      if (value.isUndefined()) {\n        results[i - 1] = std::nullopt;\n      } else {\n        KJ_DASSERT(value.isString());\n        auto str = value.toString(js);\n        results[i - 1] = std::string(str.cStr(), str.size());\n      }\n    }\n    return kj::mv(results);\n  }\n  return std::nullopt;\n}\n\nada::url_pattern_options URLPattern::URLPatternOptions::toAdaType() const {\n  return {.ignore_case = ignoreCase.orDefault(false)};\n}\n\nada::url_pattern_init URLPattern::URLPatternInit::toAdaType() const {\n  ada::url_pattern_init init{};\n#define V(_, name)                                                                                 \\\n  KJ_IF_SOME(v, name) {                                                                            \\\n    init.name = std::string(v.cStr(), v.size());                                                   \\\n  }\n  URL_PATTERN_COMPONENTS(V)\n#undef V\n  KJ_IF_SOME(b, baseURL) {\n    init.base_url = std::string(b.cStr(), b.size());\n  }\n  return init;\n}\n\nURLPattern::URLPatternInit URLPattern::createURLPatternInit(\n    jsg::Lock& js, const ada::url_pattern_init& other) {\n  // By converting to USVString we are asserting that these values are always valid UTF-8 data,\n  // with no unpaired surrogates, etc. This is true because these values are derived from user\n  // input that was already a USVString, and ada-url will not introduce invalid UTF-8 data when\n  // it processes input.\n\n  URLPatternInit result{};\n#define V(_, name)                                                                                 \\\n  if (auto v = other.name) {                                                                       \\\n    result.name = jsg::USVString(kj::str(kj::ArrayPtr(v->c_str(), v->size())));                    \\\n  }\n  URL_PATTERN_COMPONENTS(V)\n#undef V\n\n  if (auto v = other.base_url) {\n    result.baseURL = jsg::USVString(kj::str(kj::ArrayPtr(v->c_str(), v->size())));\n  }\n  return result;\n}\n\nURLPattern::URLPatternComponentResult URLPattern::createURLPatternComponentResult(\n    jsg::Lock& js, const ada::url_pattern_component_result& other) {\n  auto result = URLPatternComponentResult{\n    .input = jsg::JsRef(js, js.str(kj::ArrayPtr(other.input.c_str(), other.input.size()))),\n    .groups = jsg::JsRef(js, js.obj()),\n  };\n\n  for (auto& [key, value]: other.groups) {\n    auto k = js.str(kj::ArrayPtr(key.c_str(), key.size()));\n\n    if (value) {\n      result.groups.getHandle(js).set(js, k, js.str(kj::ArrayPtr(value->c_str(), value->size())));\n    } else {\n      result.groups.getHandle(js).set(js, k, js.undefined());\n    }\n  }\n  return result;\n}\n\nbool URLPattern::getHasRegExpGroups() const {\n  return inner.has_regexp_groups();\n}\n\nURLPattern::URLPatternResult URLPattern::createURLPatternResult(\n    jsg::Lock& js, const ada::url_pattern_result& other) {\n  URLPatternResult result{\n#define V(_, name) .name = URLPattern::createURLPatternComponentResult(js, other.name),\n    URL_PATTERN_COMPONENTS(V)\n#undef V\n  };\n\n  auto vecInputs =\n      kj::heapArray<kj::OneOf<jsg::JsRef<jsg::JsString>, URLPatternInit>>(other.inputs.size());\n  size_t i = 0;\n  for (const auto& input: other.inputs) {\n    if (std::holds_alternative<std::string_view>(input)) {\n      auto raw = std::get<std::string_view>(input);\n      vecInputs[i] = jsg::JsRef(js, js.str(kj::ArrayPtr(raw.data(), raw.size())));\n    } else {\n      KJ_DASSERT(std::holds_alternative<ada::url_pattern_init>(input));\n      auto obj = std::get<ada::url_pattern_init>(input);\n      vecInputs[i] = createURLPatternInit(js, obj);\n    }\n    i++;\n  }\n  result.inputs = mv(vecInputs);\n  return result;\n}\n\n#define DEFINE_GETTER(uppercase, lowercase)                                                        \\\n  kj::StringPtr URLPattern::get##uppercase() const {                                               \\\n    auto value = inner.get_##lowercase();                                                          \\\n    return kj::StringPtr(value.data(), value.size());                                              \\\n  }\nURL_PATTERN_COMPONENTS(DEFINE_GETTER)\n#undef DEFINE_GETTER\n\njsg::Ref<URLPattern> URLPattern::constructor(jsg::Lock& js,\n    jsg::Optional<kj::OneOf<jsg::USVString, URLPatternInit>> maybeInput,\n    jsg::Optional<kj::OneOf<jsg::USVString, URLPatternOptions>> maybeBase,\n    jsg::Optional<URLPatternOptions> maybeOptions) {\n  ada::url_pattern_input input;\n  std::optional<std::string_view> base{};\n  std::optional<ada::url_pattern_options> options{};\n\n  KJ_IF_SOME(mi, maybeInput) {\n    KJ_SWITCH_ONEOF(mi) {\n      KJ_CASE_ONEOF(str, jsg::USVString) {\n        input = std::string_view(str.begin(), str.size());\n      }\n      KJ_CASE_ONEOF(init, URLPatternInit) {\n        input = init.toAdaType();\n      }\n    }\n  } else {\n    input = ada::url_pattern_init{};\n  }\n\n  KJ_IF_SOME(b, maybeBase) {\n    KJ_SWITCH_ONEOF(b) {\n      KJ_CASE_ONEOF(str, jsg::USVString) {\n        base = std::string_view(str.begin(), str.size());\n      }\n      KJ_CASE_ONEOF(o, URLPatternOptions) {\n        options = o.toAdaType();\n      }\n    }\n  }\n\n  if (!options.has_value()) {\n    KJ_IF_SOME(o, maybeOptions) {\n      options = o.toAdaType();\n    }\n  }\n\n  std::string_view* base_opt = base ? &base.value() : nullptr;\n  ada::url_pattern_options* options_opt = options ? &options.value() : nullptr;\n  auto result = ada::parse_url_pattern<URLPatternRegexEngine>(kj::mv(input), base_opt, options_opt);\n  JSG_REQUIRE(result.has_value(), TypeError, \"Failed to construct URLPattern\"_kj);\n  return js.alloc<URLPattern>(std::move(*result));\n}\n\nbool URLPattern::test(jsg::Optional<kj::OneOf<jsg::USVString, URLPatternInit>> maybeInput,\n    jsg::Optional<jsg::USVString> maybeBase) {\n  ada::result<bool> result;\n  std::optional<std::string_view> base{};\n\n  KJ_IF_SOME(b, maybeBase) {\n    base = std::string_view(b.begin(), b.size());\n  }\n\n  std::string_view* base_ptr = base ? &base.value() : nullptr;\n\n  KJ_IF_SOME(mi, maybeInput) {\n    KJ_SWITCH_ONEOF(mi) {\n      KJ_CASE_ONEOF(str, jsg::USVString) {\n        result = inner.test(std::string_view(str.begin(), str.size()), base_ptr);\n      }\n      KJ_CASE_ONEOF(pi, URLPattern::URLPatternInit) {\n        result = inner.test(pi.toAdaType(), base_ptr);\n      }\n    }\n  } else {\n    result = inner.test(ada::url_pattern_init{}, base_ptr);\n  }\n\n  JSG_REQUIRE(result.has_value(), TypeError, \"Failed to test URLPattern\");\n\n  return *result;\n}\n\nkj::Maybe<URLPattern::URLPatternResult> URLPattern::exec(jsg::Lock& js,\n    jsg::Optional<kj::OneOf<jsg::USVString, URLPatternInit>> maybeInput,\n    jsg::Optional<jsg::USVString> maybeBase) {\n  ada::result<std::optional<ada::url_pattern_result>> result;\n  std::optional<std::string_view> base_url{};\n\n  KJ_IF_SOME(b, maybeBase) {\n    base_url = std::string_view(b.cStr(), b.size());\n  }\n\n  KJ_IF_SOME(mi, maybeInput) {\n    KJ_SWITCH_ONEOF(mi) {\n      KJ_CASE_ONEOF(str, jsg::USVString) {\n        result = inner.exec(\n            std::string_view(str.begin(), str.size()), base_url ? &base_url.value() : nullptr);\n      }\n      KJ_CASE_ONEOF(pi, URLPattern::URLPatternInit) {\n        result = inner.exec(pi.toAdaType(), base_url ? &base_url.value() : nullptr);\n      }\n    }\n  } else {\n    result = inner.exec(ada::url_pattern_init{}, base_url ? &base_url.value() : nullptr);\n  }\n\n  // If result does not exist, we should throw.\n  JSG_REQUIRE(result.has_value(), TypeError, \"Failed to exec URLPattern\"_kj);\n\n  if (result->has_value()) {\n    return createURLPatternResult(js, **result);\n  }\n\n  // Return null\n  return kj::none;\n}\n}  // namespace workerd::api::urlpattern\n"
  },
  {
    "path": "src/workerd/api/urlpattern-standard.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"ada.h\"\n\n#include <workerd/jsg/jsg.h>\n\n#include <kj/array.h>\n#include <kj/one-of.h>\n#include <kj/string.h>\n\n#include <optional>\n#include <string>\n#include <string_view>\n#include <vector>\n\nnamespace workerd::api {\n\nnamespace urlpattern {\n#define URL_PATTERN_COMPONENTS(V)                                                                  \\\n  V(Protocol, protocol)                                                                            \\\n  V(Username, username)                                                                            \\\n  V(Password, password)                                                                            \\\n  V(Hostname, hostname)                                                                            \\\n  V(Port, port)                                                                                    \\\n  V(Pathname, pathname)                                                                            \\\n  V(Search, search)                                                                                \\\n  V(Hash, hash)\n\n// URLPattern is a Web Platform standard API for matching URLs against a\n// pattern syntax (think of it as a regular expression for URLs). It is\n// defined in https://wicg.github.io/urlpattern.\n// More information about the URL Pattern syntax can be found at\n// https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API\nclass URLPattern final: public jsg::Object {\n public:\n  class URLPatternRegexEngine {\n   public:\n    URLPatternRegexEngine() = default;\n    using regex_type = jsg::JsRef<jsg::JsRegExp>;\n    static std::optional<regex_type> create_instance(std::string_view pattern, bool ignore_case);\n    static std::optional<std::vector<std::optional<std::string>>> regex_search(\n        std::string_view input, const regex_type& pattern);\n    static bool regex_match(std::string_view input, const regex_type& pattern);\n  };\n\n  // A structure providing matching patterns for individual components\n  // of a URL. When a URLPattern is created, or when a URLPattern is\n  // used to match or test against a URL, the input can be given as\n  // either a string or a URLPatternInit struct. If a string is given,\n  // it will be parsed to create a URLPatternInit. The URLPatternInit\n  // API is defined as part of the URLPattern specification.\n  struct URLPatternInit {\n#define V(_, name) jsg::Optional<jsg::USVString> name;\n    URL_PATTERN_COMPONENTS(V)\n#undef V\n    jsg::Optional<jsg::USVString> baseURL;\n\n    JSG_STRUCT(protocol, username, password, hostname, port, pathname, search, hash, baseURL);\n    JSG_STRUCT_TS_OVERRIDE(URLPatternInit);\n\n    ada::url_pattern_init toAdaType() const;\n  };\n\n  // A struct providing the URLPattern matching results for a single\n  // URL component. The URLPatternComponentResult is only ever used\n  // as a member attribute of a URLPatternResult struct. The\n  // URLPatternComponentResult API is defined as part of the URLPattern\n  // specification.\n  struct URLPatternComponentResult final {\n    jsg::JsRef<jsg::JsString> input;\n    jsg::JsRef<jsg::JsObject> groups;\n\n    JSG_STRUCT(input, groups);\n    JSG_STRUCT_TS_OVERRIDE(URLPatternComponentResult {\n                  input: string;\n                  groups: Record<string, string>;\n                });\n  };\n\n  // A struct providing the URLPattern matching results for all\n  // components of a URL. The URLPatternResult API is defined as\n  // part of the URLPattern specification.\n  struct URLPatternResult final {\n    kj::Array<kj::OneOf<jsg::JsRef<jsg::JsString>, URLPatternInit>> inputs;\n#define V(_, name) URLPatternComponentResult name;\n    URL_PATTERN_COMPONENTS(V)\n#undef V\n\n    JSG_STRUCT(inputs, protocol, username, password, hostname, port, pathname, search, hash);\n    JSG_STRUCT_TS_OVERRIDE(URLPatternResult);\n  };\n\n  struct URLPatternOptions final {\n    jsg::Optional<bool> ignoreCase;\n\n    JSG_STRUCT(ignoreCase);\n    JSG_STRUCT_TS_OVERRIDE(URLPatternOptions);\n\n    ada::url_pattern_options toAdaType() const;\n  };\n\n  explicit URLPattern(ada::url_pattern<URLPatternRegexEngine> i): inner(kj::mv(i)) {};\n\n  static jsg::Ref<URLPattern> constructor(jsg::Lock& js,\n      jsg::Optional<kj::OneOf<jsg::USVString, URLPatternInit>> input,\n      jsg::Optional<kj::OneOf<jsg::USVString, URLPatternOptions>> baseURL,\n      jsg::Optional<URLPatternOptions> patternOptions);\n\n  kj::Maybe<URLPatternResult> exec(jsg::Lock& js,\n      jsg::Optional<kj::OneOf<jsg::USVString, URLPatternInit>> input,\n      jsg::Optional<jsg::USVString> baseURL);\n\n  bool test(jsg::Optional<kj::OneOf<jsg::USVString, URLPatternInit>> input,\n      jsg::Optional<jsg::USVString> baseURL);\n\n  bool getHasRegExpGroups() const;\n\n#define V(name, _) kj::StringPtr get##name() const;\n  URL_PATTERN_COMPONENTS(V)\n#undef V\n\n  JSG_RESOURCE_TYPE(URLPattern) {\n#define V(Name, name) JSG_READONLY_PROTOTYPE_PROPERTY(name, get##Name);\n    URL_PATTERN_COMPONENTS(V)\n#undef V\n    JSG_READONLY_PROTOTYPE_PROPERTY(hasRegExpGroups, getHasRegExpGroups);\n    JSG_METHOD(test);\n    JSG_METHOD(exec);\n  }\n\n private:\n  ada::url_pattern<URLPatternRegexEngine> inner;\n\n  static URLPatternInit createURLPatternInit(jsg::Lock& js, const ada::url_pattern_init& other);\n  static URLPatternComponentResult createURLPatternComponentResult(\n      jsg::Lock& js, const ada::url_pattern_component_result& other);\n  static URLPatternResult createURLPatternResult(\n      jsg::Lock& js, const ada::url_pattern_result& other);\n};\n}  // namespace urlpattern\n#define EW_URLPATTERN_STANDARD_ISOLATE_TYPES                                                       \\\n  api::urlpattern::URLPattern, api::urlpattern::URLPattern::URLPatternInit,                        \\\n      api::urlpattern::URLPattern::URLPatternComponentResult,                                      \\\n      api::urlpattern::URLPattern::URLPatternResult,                                               \\\n      api::urlpattern::URLPattern::URLPatternOptions\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/urlpattern.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"urlpattern.h\"\n\n#include <workerd/util/own-util.h>\n\n#include <kj/vector.h>\n\nnamespace workerd::api {\n\nnamespace {\njsg::JsRef<jsg::JsRegExp> compileRegex(\n    jsg::Lock& js, const jsg::UrlPattern::Component& component, bool ignoreCase) {\n  JSG_TRY(js) {\n    jsg::Lock::RegExpFlags flags = jsg::Lock::RegExpFlags::kUNICODE;\n    if (ignoreCase) {\n      flags = static_cast<jsg::Lock::RegExpFlags>(\n          flags | static_cast<int>(jsg::Lock::RegExpFlags::kIGNORE_CASE));\n    }\n    return jsg::JsRef<jsg::JsRegExp>(js, js.regexp(component.getRegex(), flags));\n  }\n  JSG_CATCH(_) {\n    JSG_FAIL_REQUIRE(TypeError, \"Invalid regular expression syntax.\");\n  }\n}\n\njsg::Ref<URLPattern> create(jsg::Lock& js, jsg::UrlPattern pattern) {\n  bool ignoreCase = pattern.getIgnoreCase();\n\n  // Might look a bit confusing here. The URL_PATTERN_COMPONENTS macro\n  // is used also to define the constructor for URLPattern so to make\n  // sure things line up right we reuse that pattern here also. Because\n  // we are moving the pattern into the constructor, we need to make sure\n  // the regex patterns are compiled first so we use the macro twice.\n#define V(Name, var) auto var = compileRegex(js, pattern.get##Name(), ignoreCase);\n  URL_PATTERN_COMPONENTS(V)\n#undef V\n\n#define V(_, var) , kj::mv(var)\n  return js.alloc<URLPattern>(kj::mv(pattern) URL_PATTERN_COMPONENTS(V));\n#undef V\n}\n\nkj::Maybe<URLPattern::URLPatternComponentResult> execRegex(jsg::Lock& js,\n    jsg::JsRef<jsg::JsRegExp>& regex,\n    kj::ArrayPtr<const kj::String> nameList,\n    kj::StringPtr input) {\n  using Groups = jsg::Dict<kj::String, kj::String>;\n\n  KJ_IF_SOME(array, regex.getHandle(js)(js, input)) {\n    // Per the URLPattern spec, we iterate over nameList rather than the regex\n    // result array. The regex may contain more capturing groups than there are\n    // named parts (e.g. when a part's value itself contains capturing groups\n    // like \"(?<foo>x)\"), so using the regex array length would cause an\n    // out-of-bounds access on nameList.\n    uint32_t length = nameList.size();\n    kj::Vector<Groups::Field> fields(length);\n\n    for (uint32_t index = 0; index < length; index++) {\n      auto value = array.get(js, index + 1);\n      fields.add(Groups::Field{\n        .name = kj::str(nameList[index]),\n        .value = value.isUndefined() ? kj::String() : kj::str(value),\n      });\n    }\n\n    return URLPattern::URLPatternComponentResult{\n      .input = kj::str(input),\n      .groups = Groups{.fields = fields.releaseAsArray()},\n    };\n  }\n\n  return kj::none;\n}\n}  // namespace\n\nURLPattern::URLPattern(jsg::UrlPattern inner,\n    jsg::JsRef<jsg::JsRegExp> protocolRegex,\n    jsg::JsRef<jsg::JsRegExp> usernameRegex,\n    jsg::JsRef<jsg::JsRegExp> passwordRegex,\n    jsg::JsRef<jsg::JsRegExp> hostnameRegex,\n    jsg::JsRef<jsg::JsRegExp> portRegex,\n    jsg::JsRef<jsg::JsRegExp> pathnameRegex,\n    jsg::JsRef<jsg::JsRegExp> searchRegex,\n    jsg::JsRef<jsg::JsRegExp> hashRegex)\n    : inner(kj::mv(inner)),\n      protocolRegex(kj::mv(protocolRegex)),\n      usernameRegex(kj::mv(usernameRegex)),\n      passwordRegex(kj::mv(passwordRegex)),\n      hostnameRegex(kj::mv(hostnameRegex)),\n      portRegex(kj::mv(portRegex)),\n      pathnameRegex(kj::mv(pathnameRegex)),\n      searchRegex(kj::mv(searchRegex)),\n      hashRegex(kj::mv(hashRegex)) {}\n\nvoid URLPattern::visitForGc(jsg::GcVisitor& visitor) {\n  visitor.visit(protocolRegex, usernameRegex, passwordRegex, hostnameRegex, portRegex,\n      pathnameRegex, searchRegex, hashRegex);\n}\n\nkj::StringPtr URLPattern::getProtocol() {\n  return inner.getProtocol().getPattern();\n}\nkj::StringPtr URLPattern::getUsername() {\n  return inner.getUsername().getPattern();\n}\nkj::StringPtr URLPattern::getPassword() {\n  return inner.getPassword().getPattern();\n}\nkj::StringPtr URLPattern::getHostname() {\n  return inner.getHostname().getPattern();\n}\nkj::StringPtr URLPattern::getPort() {\n  return inner.getPort().getPattern();\n}\nkj::StringPtr URLPattern::getPathname() {\n  return inner.getPathname().getPattern();\n}\nkj::StringPtr URLPattern::getSearch() {\n  return inner.getSearch().getPattern();\n}\nkj::StringPtr URLPattern::getHash() {\n  return inner.getHash().getPattern();\n}\n\nURLPattern::URLPatternInit::operator jsg::UrlPattern::Init() {\n  return {\n    .protocol = mapCopyString(this->protocol),\n    .username = mapCopyString(this->username),\n    .password = mapCopyString(this->password),\n    .hostname = mapCopyString(this->hostname),\n    .port = mapCopyString(this->port),\n    .pathname = mapCopyString(this->pathname),\n    .search = mapCopyString(this->search),\n    .hash = mapCopyString(this->hash),\n    .baseUrl = mapCopyString(this->baseURL),\n  };\n}\n\njsg::Ref<URLPattern> URLPattern::constructor(jsg::Lock& js,\n    jsg::Optional<URLPatternInput> input,\n    jsg::Optional<kj::OneOf<kj::String, URLPatternOptions>> baseURL,\n    jsg::Optional<URLPatternOptions> patternOptions) {\n  kj::Maybe<kj::String> base;\n  kj::Maybe<bool> ignoreCase;\n\n  KJ_IF_SOME(b, baseURL) {\n    KJ_SWITCH_ONEOF(b) {\n      KJ_CASE_ONEOF(str, kj::String) {\n        base = kj::str(str);\n      }\n      KJ_CASE_ONEOF(o, URLPatternOptions) {\n        ignoreCase = o.ignoreCase.orDefault(false);\n      }\n    }\n  }\n\n  if (ignoreCase == kj::none) {\n    KJ_IF_SOME(o, patternOptions) {\n      ignoreCase = o.ignoreCase.orDefault(false);\n    }\n  }\n\n  KJ_SWITCH_ONEOF(kj::mv(input).orDefault(URLPatternInit{})) {\n    KJ_CASE_ONEOF(str, kj::String) {\n      KJ_SWITCH_ONEOF(jsg::UrlPattern::tryCompile(str.asPtr(),\n                          jsg::UrlPattern::CompileOptions{\n                            .baseUrl = base.map([](kj::String& str) { return str.asPtr(); }),\n                            .ignoreCase = ignoreCase.orDefault(false),\n                          })) {\n        KJ_CASE_ONEOF(err, kj::String) {\n          JSG_FAIL_REQUIRE(TypeError, kj::mv(err));\n        }\n        KJ_CASE_ONEOF(pattern, jsg::UrlPattern) {\n          return create(js, kj::mv(pattern));\n        }\n      }\n    }\n    KJ_CASE_ONEOF(init, URLPatternInit) {\n      KJ_SWITCH_ONEOF(jsg::UrlPattern::tryCompile(init,\n                          jsg::UrlPattern::CompileOptions{\n                            .baseUrl = base.map([](kj::String& str) { return str.asPtr(); }),\n                            .ignoreCase = ignoreCase.orDefault(false),\n                          })) {\n        KJ_CASE_ONEOF(err, kj::String) {\n          JSG_FAIL_REQUIRE(TypeError, kj::mv(err));\n        }\n        KJ_CASE_ONEOF(pattern, jsg::UrlPattern) {\n          return create(js, kj::mv(pattern));\n        }\n      }\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nbool URLPattern::test(\n    jsg::Lock& js, jsg::Optional<URLPatternInput> input, jsg::Optional<kj::String> baseURL) {\n  return exec(js, kj::mv(input), kj::mv(baseURL)) != kj::none;\n}\n\nkj::Maybe<URLPattern::URLPatternResult> URLPattern::exec(\n    jsg::Lock& js, jsg::Optional<URLPatternInput> maybeInput, jsg::Optional<kj::String> maybeBase) {\n  auto input = kj::mv(maybeInput).orDefault(URLPattern::URLPatternInit());\n  kj::Vector<URLPattern::URLPatternInput> inputs(2);\n\n  kj::String protocol = nullptr;\n  kj::String username = nullptr;\n  kj::String password = nullptr;\n  kj::String hostname = nullptr;\n  kj::String port = nullptr;\n  kj::String pathname = nullptr;\n  kj::String search = nullptr;\n  kj::String hash = nullptr;\n\n  KJ_SWITCH_ONEOF(input) {\n    KJ_CASE_ONEOF(string, kj::String) {\n      KJ_IF_SOME(url, jsg::Url::tryParse(string.asPtr(), maybeBase.map([](kj::String& s) {\n        return s.asPtr();\n      }))) {\n        auto p = url.getProtocol();\n        protocol = kj::str(p.first(p.size() - 1));\n        username = kj::str(url.getUsername());\n        password = kj::str(url.getPassword());\n        hostname = kj::str(url.getHostname());\n        port = kj::str(url.getPort());\n        pathname = kj::str(url.getPathname());\n        search = url.getSearch().size() > 0 ? kj::str(url.getSearch().slice(1)) : kj::String();\n        hash = url.getHash().size() > 0 ? kj::str(url.getHash().slice(1)) : kj::String();\n      } else {\n        return kj::none;\n      }\n      inputs.add(kj::mv(string));\n      KJ_IF_SOME(base, maybeBase) {\n        inputs.add(kj::mv(base));\n      }\n    }\n    KJ_CASE_ONEOF(i, URLPattern::URLPatternInit) {\n      JSG_REQUIRE(\n          maybeBase == kj::none, TypeError, \"A baseURL is not allowed when input is an object.\");\n      inputs.add(URLPattern::URLPatternInit{\n        .protocol = mapCopyString(i.protocol),\n        .username = mapCopyString(i.username),\n        .password = mapCopyString(i.password),\n        .hostname = mapCopyString(i.hostname),\n        .port = mapCopyString(i.port),\n        .pathname = mapCopyString(i.pathname),\n        .search = mapCopyString(i.search),\n        .hash = mapCopyString(i.hash),\n        .baseURL = mapCopyString(i.baseURL),\n      });\n\n      jsg::UrlPattern::Init init = {\n        .protocol = kj::mv(i.protocol),\n        .username = kj::mv(i.username),\n        .password = kj::mv(i.password),\n        .hostname = kj::mv(i.hostname),\n        .port = kj::mv(i.port),\n        .pathname = kj::mv(i.pathname),\n        .search = kj::mv(i.search),\n        .hash = kj::mv(i.hash),\n        .baseUrl = kj::mv(i.baseURL),\n      };\n\n      jsg::UrlPattern::ProcessInitOptions options = {\n        .mode = jsg::UrlPattern::ProcessInitOptions::Mode::URL};\n\n      KJ_SWITCH_ONEOF(jsg::UrlPattern::processInit(kj::mv(init), kj::mv(options))) {\n        KJ_CASE_ONEOF(err, kj::String) {\n          JSG_FAIL_REQUIRE(TypeError, kj::mv(err));\n        }\n        KJ_CASE_ONEOF(init, jsg::UrlPattern::Init) {\n          protocol = kj::mv(init.protocol).orDefault(kj::String());\n          username = kj::mv(init.username).orDefault(kj::String());\n          password = kj::mv(init.password).orDefault(kj::String());\n          hostname = kj::mv(init.hostname).orDefault(kj::String());\n          port = kj::mv(init.port).orDefault(kj::String());\n          pathname = kj::mv(init.pathname).orDefault(kj::String());\n          search = kj::mv(init.search).orDefault(kj::String());\n          hash = kj::mv(init.hash).orDefault(kj::String());\n        }\n      }\n    }\n  }\n\n  auto protocolExecResult = execRegex(js, protocolRegex, inner.getProtocol().getNames(), protocol);\n  auto usernameExecResult = execRegex(js, usernameRegex, inner.getUsername().getNames(), username);\n  auto passwordExecResult = execRegex(js, passwordRegex, inner.getPassword().getNames(), password);\n  auto hostnameExecResult = execRegex(js, hostnameRegex, inner.getHostname().getNames(), hostname);\n  auto portExecResult = execRegex(js, portRegex, inner.getPort().getNames(), port);\n  auto pathnameExecResult = execRegex(js, pathnameRegex, inner.getPathname().getNames(), pathname);\n  auto searchExecResult = execRegex(js, searchRegex, inner.getSearch().getNames(), search);\n  auto hashExecResult = execRegex(js, hashRegex, inner.getHash().getNames(), hash);\n\n  if (protocolExecResult == kj::none || usernameExecResult == kj::none ||\n      passwordExecResult == kj::none || hostnameExecResult == kj::none ||\n      portExecResult == kj::none || pathnameExecResult == kj::none ||\n      searchExecResult == kj::none || hashExecResult == kj::none) {\n    return kj::none;\n  }\n\n  return URLPattern::URLPatternResult{\n    .inputs = inputs.releaseAsArray(),\n    .protocol = kj::mv(KJ_REQUIRE_NONNULL(protocolExecResult)),\n    .username = kj::mv(KJ_REQUIRE_NONNULL(usernameExecResult)),\n    .password = kj::mv(KJ_REQUIRE_NONNULL(passwordExecResult)),\n    .hostname = kj::mv(KJ_REQUIRE_NONNULL(hostnameExecResult)),\n    .port = kj::mv(KJ_REQUIRE_NONNULL(portExecResult)),\n    .pathname = kj::mv(KJ_REQUIRE_NONNULL(pathnameExecResult)),\n    .search = kj::mv(KJ_REQUIRE_NONNULL(searchExecResult)),\n    .hash = kj::mv(KJ_REQUIRE_NONNULL(hashExecResult)),\n  };\n}\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/urlpattern.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/url.h>\n\nnamespace workerd::api {\n\n#define URL_PATTERN_COMPONENTS(V)                                                                  \\\n  V(Protocol, protocol)                                                                            \\\n  V(Username, username)                                                                            \\\n  V(Password, password)                                                                            \\\n  V(Hostname, hostname)                                                                            \\\n  V(Port, port)                                                                                    \\\n  V(Pathname, pathname)                                                                            \\\n  V(Search, search)                                                                                \\\n  V(Hash, hash)\n\n// URLPattern is a Web Platform standard API for matching URLs against a\n// pattern syntax (think of it as a regular expression for URLs). It is\n// defined in https://wicg.github.io/urlpattern.\n// More information about the URL Pattern syntax can be found at\n// https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API\nclass URLPattern final: public jsg::Object {\n public:\n  // A structure providing matching patterns for individual components\n  // of a URL. When a URLPattern is created, or when a URLPattern is\n  // used to match or test against a URL, the input can be given as\n  // either a string or a URLPatternInit struct. If a string is given,\n  // it will be parsed to create a URLPatternInit. The URLPatternInit\n  // API is defined as part of the URLPattern specification.\n  struct URLPatternInit final {\n#define V(_, name) jsg::Optional<kj::String> name;\n    URL_PATTERN_COMPONENTS(V)\n#undef V\n    jsg::Optional<kj::String> baseURL;\n\n    JSG_STRUCT(protocol, username, password, hostname, port, pathname, search, hash, baseURL);\n\n    operator jsg::UrlPattern::Init();\n  };\n\n  using URLPatternInput = kj::OneOf<kj::String, URLPatternInit>;\n\n  // A struct providing the URLPattern matching results for a single\n  // URL component. The URLPatternComponentResult is only ever used\n  // as a member attribute of a URLPatternResult struct. The\n  // URLPatternComponentResult API is defined as part of the URLPattern\n  // specification.\n  struct URLPatternComponentResult final {\n    kj::String input;\n    jsg::Dict<kj::String, kj::String> groups;\n\n    JSG_STRUCT(input, groups);\n  };\n\n  // A struct providing the URLPattern matching results for all\n  // components of a URL. The URLPatternResult API is defined as\n  // part of the URLPattern specification.\n  struct URLPatternResult final {\n    kj::Array<URLPatternInput> inputs;\n#define V(_, name) URLPatternComponentResult name;\n    URL_PATTERN_COMPONENTS(V)\n#undef V\n\n    JSG_STRUCT(inputs, protocol, username, password, hostname, port, pathname, search, hash);\n  };\n\n  struct URLPatternOptions final {\n    jsg::Optional<bool> ignoreCase;\n\n    JSG_STRUCT(ignoreCase);\n  };\n\n  explicit URLPattern(jsg::UrlPattern inner\n#define V(_, name) , jsg::JsRef<jsg::JsRegExp> name##Regex\n          URL_PATTERN_COMPONENTS(V)\n#undef V\n  );\n\n  static jsg::Ref<URLPattern> constructor(jsg::Lock& js,\n      jsg::Optional<URLPatternInput> input,\n      jsg::Optional<kj::OneOf<kj::String, URLPatternOptions>> baseURL,\n      jsg::Optional<URLPatternOptions> patternOptions);\n\n  kj::Maybe<URLPatternResult> exec(\n      jsg::Lock& js, jsg::Optional<URLPatternInput> input, jsg::Optional<kj::String> baseURL);\n\n  bool test(jsg::Lock& js, jsg::Optional<URLPatternInput> input, jsg::Optional<kj::String> baseURL);\n\n#define V(name, _) kj::StringPtr get##name();\n  URL_PATTERN_COMPONENTS(V)\n#undef V\n\n  JSG_RESOURCE_TYPE(URLPattern) {\n#define V(Name, name) JSG_READONLY_PROTOTYPE_PROPERTY(name, get##Name);\n    URL_PATTERN_COMPONENTS(V)\n#undef V\n    JSG_METHOD(test);\n    JSG_METHOD(exec);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"inner\", inner);\n  }\n\n private:\n  jsg::UrlPattern inner;\n#define V(_, name) jsg::JsRef<jsg::JsRegExp> name##Regex;\n  URL_PATTERN_COMPONENTS(V)\n#undef V\n\n  void visitForGc(jsg::GcVisitor& visitor);\n};\n\n#define EW_URLPATTERN_ISOLATE_TYPES                                                                \\\n  api::URLPattern, api::URLPattern::URLPatternInit, api::URLPattern::URLPatternComponentResult,    \\\n      api::URLPattern::URLPatternResult, api::URLPattern::URLPatternOptions\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/util-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"util.h\"\n\n#include <kj/test.h>\n\nnamespace workerd::api {\nnamespace {\n\nvoid expectRedacted(kj::StringPtr input, kj::StringPtr expected) {\n  KJ_EXPECT(redactUrl(input) == expected, redactUrl(input), expected);\n}\nvoid expectUnredacted(kj::StringPtr input) {\n  KJ_EXPECT(redactUrl(input) == input, redactUrl(input), input);\n}\n\nvoid expectContentTypeParameter(kj::StringPtr input, kj::StringPtr param, kj::StringPtr expected) {\n  auto value = KJ_ASSERT_NONNULL(readContentTypeParameter(input, param));\n  KJ_EXPECT(value == expected);\n}\n\nKJ_TEST(\"redactUrl can detect hex ids\") {\n  // no id:\n  expectUnredacted(\"\"_kj);\n  expectUnredacted(\"https://domain/path?a=1&b=2\"_kj);\n\n  expectRedacted(\n      \"https://domain/0123456789abcdef0123456789abcdef/x\"_kj, \"https://domain/REDACTED/x\"_kj);\n  expectRedacted(\n      \"https://domain/0123456789abcdef-0123456789abcdef/x\"_kj, \"https://domain/REDACTED/x\"_kj);\n\n  // not long enough:\n  expectUnredacted(\"https://domain/0123456789abcdef0123456789abcde/x\"_kj);\n  expectUnredacted(\"https://domain/0123456789-abcdef-0123456789-abcde/x\"_kj);\n  expectUnredacted(\"https://domain/0123456789ABCDEF0123456789ABCDE/x\"_kj);\n  expectUnredacted(\"https://domain/0123456789_ABCDEF_0123456789_ABCDE/x\"_kj);\n\n  // contains non-hex character:\n  expectUnredacted(\"https://domain/0123456789abcdef0123456789abcdefg/x\"_kj);\n}\n\nKJ_TEST(\"redactUrl can detect base64 ids\") {\n  expectRedacted(\"https://domain/01234567890123456azAZ/x\"_kj, \"https://domain/REDACTED/x\"_kj);\n\n  // not long enough:\n  expectUnredacted(\"https://domain/0123456789012345azAZ/x\"_kj);\n\n  // not enough lowercase:\n  expectUnredacted(\"https://domain/012345678901234567zAZ/x\"_kj);\n\n  // not enough uppercase:\n  expectUnredacted(\"https://domain/012345678901234567azZ/x\"_kj);\n\n  // not enough digits:\n  expectUnredacted(\"https://domain/IThinkIShallNeverSee0/x\"_kj);\n}\n\nKJ_TEST(\"readContentTypeParameter can fetch boundary parameter\") {\n\n  // normal\n  expectContentTypeParameter(\n      \"multipart/form-data; boundary=\\\"__boundary__\\\"\"_kj, \"boundary\"_kj, \"__boundary__\"_kj);\n\n  // multiple params\n  expectContentTypeParameter(\"multipart/form-data; charset=utf-8; boundary=\\\"__boundary__\\\"\"_kj,\n      \"boundary\"_kj, \"__boundary__\"_kj);\n\n  // param name inside value of other param\n  expectContentTypeParameter(\n      \"multipart/form-data; charset=\\\"boundary=;\\\"; boundary=\\\"__boundary__\\\"\"_kj, \"boundary\"_kj,\n      \"__boundary__\"_kj);\n\n  // ensure param is not found\n  KJ_ASSERT(readContentTypeParameter(\n                \"multipart/form-data; charset=\\\"boundary=;\\\"; boundary=\\\"__boundary__\\\"\"_kj,\n                \"boundary1\"_kj) == kj::none);\n\n  // no quotes\n  expectContentTypeParameter(\n      \"multipart/form-data; charset=\\\"boundary=;\\\"; boundary=__boundary__\"_kj, \"boundary\"_kj,\n      \"__boundary__\"_kj);\n\n  // attribute names are case-insensitive, but values are not\n  expectContentTypeParameter(\n      \"multipart/form-data; charset=\\\"boundary=;\\\"; boundary=__Boundary__\"_kj, \"Boundary\"_kj,\n      \"__Boundary__\"_kj);\n\n  // different order\n  expectContentTypeParameter(\"multipart/form-data; boundary=\\\"__boundary__\\\"; charset=utf-8\"_kj,\n      \"boundary\"_kj, \"__boundary__\"_kj);\n\n  // bogus parameter\n  expectContentTypeParameter(\"multipart/form-data; foo=123; boundary=\\\"__boundary__\\\"\"_kj,\n      \"boundary\"_kj, \"__boundary__\"_kj);\n\n  // quoted-string\n  expectContentTypeParameter(\n      R\"(multipart/form-data; foo=\"\\\"boundary=bar\\\"\"; boundary=\"realboundary\")\", \"boundary\"_kj,\n      \"realboundary\"_kj);\n\n  // handle non-closing quotes\n  expectContentTypeParameter(\n      R\"(multipart/form-data; charset=\"boundary=;\\\"; boundary=\"__boundary__\")\", \"boundary\"_kj,\n      \"__boundary__\"_kj);\n\n  expectContentTypeParameter(\n      R\"(multipart/form-data; charset=\"boundary=;\"; boundary=\"__boundary__\\\")\", \"boundary\"_kj,\n      \"__boundary__\"_kj);\n\n  expectContentTypeParameter(\n      R\"(multipart/form-data; charset=\\\"boundary=;\\\"; boundary=\\\"__boundary__\\\")\", \"boundary\"_kj,\n      R\"(\\\"__boundary__\\\")\");\n\n  // spurious whitespace before ;\n  expectContentTypeParameter(\n      \"multipart/form-data; boundary=asdf ;foo=bar\"_kj, \"boundary\"_kj, \"asdf\"_kj);\n\n  // spurious whitespace before ; with quotes\n  expectContentTypeParameter(\n      \"multipart/form-data; boundary=\\\"asdf\\\" ;foo=bar\"_kj, \"boundary\"_kj, \"asdf\"_kj);\n\n  // all whitespace\n  KJ_ASSERT(readContentTypeParameter(\"multipart/form-data; boundary= ;foo=bar\"_kj, \"boundary\"_kj) ==\n      kj::none);\n\n  // all whitespace with quotes\n  KJ_ASSERT(readContentTypeParameter(\"multipart/form-data; boundary=\"\n                                     \" ;foo=bar\"_kj,\n                \"boundary\"_kj) == kj::none);\n\n  // terminal escape character after quote\n  KJ_ASSERT(readContentTypeParameter(R\"(multipart/form-data; foo=\"\\)\", \"boundary\"_kj) == kj::none);\n\n  // space before value\n  expectContentTypeParameter(\"multipart/form-data; boundary= a\"_kj, \"boundary\"_kj, \" a\"_kj);\n\n  // space before value with quotes\n  expectContentTypeParameter(\"multipart/form-data; boundary=\\\" a\\\"\"_kj, \"boundary\"_kj, \" a\"_kj);\n\n  // space before ; on another param\n  expectContentTypeParameter(\n      \"multipart/form-data; foo=\\\"bar\\\" ;boundary=asdf\"_kj, \"boundary\"_kj, \"asdf\"_kj);\n\n  // space before ; on another param with quotes\n  expectContentTypeParameter(\n      \"multipart/form-data; foo=\\\"bar\\\" ;boundary=\\\"asdf\\\"\"_kj, \"boundary\"_kj, \"asdf\"_kj);\n\n  // space before ; on another param no quotes\n  expectContentTypeParameter(\n      \"multipart/form-data; foo=bar ;boundary=asdf\"_kj, \"boundary\"_kj, \"asdf\"_kj);\n\n  // space before ; on another param quotes on wanted param\n  expectContentTypeParameter(\n      \"multipart/form-data; foo=bar ;boundary=\\\"asdf\\\"\"_kj, \"boundary\"_kj, \"asdf\"_kj);\n}\n\n}  // namespace\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/util.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"util.h\"\n\n#include \"simdutf.h\"\n\n#include <workerd/util/mimetype.h>\n#include <workerd/util/strings.h>\n\n#include <kj/encoding.h>\n\nnamespace workerd::api {\nnamespace {\n\nkj::ArrayPtr<const char> split(kj::ArrayPtr<const char>& text, char c) {\n  // TODO(cleanup): Modified version of split() found in kj/compat/url.c++.\n\n  for (auto i: kj::indices(text)) {\n    if (text[i] == c) {\n      kj::ArrayPtr<const char> result = text.first(i);\n      text = text.slice(i + 1, text.size());\n      return result;\n    }\n  }\n  auto result = text;\n  text = {};\n  return result;\n}\n\n}  // namespace\n\nvoid parseQueryString(kj::Vector<kj::Url::QueryParam>& query,\n    kj::ArrayPtr<const char> text,\n    bool skipLeadingQuestionMark) {\n  if (skipLeadingQuestionMark && text.size() > 0 && text[0] == '?') {\n    text = text.slice(1, text.size());\n  }\n\n  while (text.size() > 0) {\n    auto value = split(text, '&');\n    if (value.size() == 0) continue;\n    auto name = split(value, '=');\n    query.add(kj::Url::QueryParam{kj::decodeWwwForm(name), kj::decodeWwwForm(value)});\n  }\n}\n\nkj::Maybe<kj::String> readContentTypeParameter(kj::StringPtr contentType, kj::StringPtr param) {\n  KJ_IF_SOME(parsed, MimeType::tryParse(contentType)) {\n    return parsed.params().find(toLower(param)).map([](auto& value) { return kj::str(value); });\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::Exception> translateKjException(\n    const kj::Exception& exception, std::initializer_list<ErrorTranslation> translations) {\n  for (auto& t: translations) {\n    if (exception.getDescription().contains(t.kjDescription)) {\n      return kj::Exception(kj::Exception::Type::FAILED, __FILE__, __LINE__,\n          kj::str(JSG_EXCEPTION(TypeError) \": \", t.jsDescription));\n    }\n  }\n\n  return kj::none;\n}\n\nnamespace {\n\ntemplate <typename Func>\nauto translateTeeErrors(Func&& f) -> decltype(kj::fwd<Func>(f)()) {\n  try {\n    co_return co_await f();\n  } catch (...) {\n    auto exception = kj::getCaughtExceptionAsKj();\n    KJ_IF_SOME(e,\n        translateKjException(exception,\n            {\n              {\"tee buffer size limit exceeded\"_kj,\n                \"ReadableStream.tee() buffer limit exceeded. This error usually occurs when a Request or \"\n                \"Response with a large body is cloned, then only one of the clones is read, forcing \"\n                \"the Workers runtime to buffer the entire body in memory. To fix this issue, remove \"\n                \"unnecessary calls to Request/Response.clone() and ReadableStream.tee(), and always read \"\n                \"clones/tees in parallel.\"_kj},\n            })) {\n      kj::throwFatalException(kj::mv(e));\n    }\n    kj::throwFatalException(kj::mv(exception));\n  }\n}\n\n}  // namespace\n\nkj::Own<kj::AsyncInputStream> newTeeErrorAdapter(kj::Own<kj::AsyncInputStream> inner) {\n  class Adapter final: public kj::AsyncInputStream {\n   public:\n    explicit Adapter(kj::Own<AsyncInputStream> inner): inner(kj::mv(inner)) {}\n\n    kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n      return translateTeeErrors([&] { return inner->tryRead(buffer, minBytes, maxBytes); });\n    }\n\n    kj::Maybe<uint64_t> tryGetLength() override {\n      return inner->tryGetLength();\n    };\n\n    kj::Promise<uint64_t> pumpTo(kj::AsyncOutputStream& output, uint64_t amount) override {\n      return translateTeeErrors([&] { return inner->pumpTo(output, amount); });\n    }\n\n    kj::Maybe<kj::Own<kj::AsyncInputStream>> tryTee(uint64_t limit) override {\n      return inner->tryTee(limit);\n    }\n\n   private:\n    kj::Own<AsyncInputStream> inner;\n  };\n\n  if (dynamic_cast<Adapter*>(inner.get()) != nullptr) {\n    // HACK: Don't double-wrap. This can otherwise happen if we tee a tee.\n    return kj::mv(inner);\n  } else {\n    return kj::heap<Adapter>(kj::mv(inner));\n  }\n}\n\nkj::String redactUrl(kj::StringPtr url) {\n  kj::Vector<char> redacted(url.size() + 1);\n  const char* spanStart = url.begin();\n  bool sawNonHexChar = false;\n  uint digitCount = 0;\n  uint upperCount = 0;\n  uint lowerCount = 0;\n  uint hexDigitCount = 0;\n\n  auto maybeRedactSpan = [&](kj::ArrayPtr<const char> span) {\n    bool isHexId = (hexDigitCount >= 32 && !sawNonHexChar);\n    bool probablyBase64Id =\n        (span.size() >= 21 && digitCount >= 2 && upperCount >= 2 && lowerCount >= 2);\n\n    if (isHexId || probablyBase64Id) {\n      redacted.addAll(\"REDACTED\"_kj);\n    } else {\n      redacted.addAll(span);\n    }\n  };\n\n  for (const char& c: url) {\n    uint8_t lookup = kCharLookupTable[static_cast<const kj::byte>(c)];\n    bool isSep = lookup & CharAttributeFlag::SEPARATOR;\n    bool isAlphaUpper = lookup & CharAttributeFlag::UPPER_CASE;\n    bool isAlphaLower = lookup & CharAttributeFlag::LOWER_CASE;\n    bool isDigit = lookup & CharAttributeFlag::DIGIT;\n    bool isHex = lookup & CharAttributeFlag::HEX;\n\n    // These extra characters are used in the regular and url-safe versions of\n    // base64, but might also be used for GUID-style separators in hex ids.\n    // Regular base64 also includes '/', which we don't try to match here due\n    // to its prevalence in URLs.  Likewise, we ignore the base64 \"=\" padding\n    // character.\n\n    if (isAlphaUpper || isAlphaLower || isDigit || isSep) {\n      if (isHex) {\n        hexDigitCount++;\n      }\n      if (!isHex && !isSep) {\n        sawNonHexChar = true;\n      }\n      if (isAlphaUpper) {\n        upperCount++;\n      }\n      if (isAlphaLower) {\n        lowerCount++;\n      }\n      if (isDigit) {\n        digitCount++;\n      }\n    } else {\n      maybeRedactSpan(kj::ArrayPtr<const char>(spanStart, &c));\n      redacted.add(c);\n      spanStart = &c + 1;\n      hexDigitCount = 0;\n      digitCount = 0;\n      upperCount = 0;\n      lowerCount = 0;\n      sawNonHexChar = false;\n    }\n  }\n  maybeRedactSpan(kj::ArrayPtr<const char>(spanStart, url.end()));\n  redacted.add('\\0');\n\n  return kj::String(redacted.releaseAsArray());\n}\n\nkj::Maybe<jsg::V8Ref<v8::Object>> cloneRequestCf(\n    jsg::Lock& js, kj::Maybe<jsg::V8Ref<v8::Object>> maybeCf) {\n  KJ_IF_SOME(cf, maybeCf) {\n    return cf.deepClone(js);\n  }\n  return kj::none;\n}\n\nvoid maybeWarnIfNotText(jsg::Lock& js, kj::StringPtr str) {\n  KJ_IF_SOME(parsed, MimeType::tryParse(str)) {\n    if (MimeType::isText(parsed)) return;\n  }\n  // A common mistake is to call .text() on non-text content, e.g. because you're implementing a\n  // search-and-replace across your whole site and you forgot that it'll apply to images too.\n  // When running in the fiddle, let's warn the developer if they do this.\n  js.logWarning(\n      kj::str(\"Called .text() on an HTTP body which does not appear to be text. The body's \"\n              \"Content-Type is \\\"\",\n          str,\n          \"\\\". The result will probably be corrupted. Consider \"\n          \"checking the Content-Type header before interpreting entities as text.\"));\n}\n\nkj::String fastEncodeBase64Url(kj::ArrayPtr<const byte> bytes) {\n  if (KJ_UNLIKELY(bytes.size() == 0)) {\n    return {};\n  }\n  auto expected_length = simdutf::base64_length_from_binary(bytes.size(), simdutf::base64_url);\n  auto output = kj::heapArray<char>(expected_length + 1);\n  auto actual_length = simdutf::binary_to_base64(\n      bytes.asChars().begin(), bytes.size(), output.asChars().begin(), simdutf::base64_url);\n  output[actual_length] = '\\0';\n  return kj::String(kj::mv(output));\n}\n\nkj::Array<char16_t> fastEncodeUtf16(kj::ArrayPtr<const char> bytes) {\n  if (KJ_UNLIKELY(bytes.size() == 0)) {\n    return {};\n  }\n  auto expected_length = simdutf::utf16_length_from_utf8(bytes.asChars().begin(), bytes.size());\n  auto output = kj::heapArray<char16_t>(expected_length);\n  auto actual_length =\n      simdutf::convert_utf8_to_utf16(bytes.asChars().begin(), bytes.size(), output.begin());\n  return output.first(actual_length).attach(kj::mv(output));\n}\n\n// URI-encode control characters and spaces.\nkj::String uriEncodeControlChars(kj::ArrayPtr<const byte> bytes) {\n  // TODO(cleanup): Once this is deployed, update open-source KJ HTTP to do this automatically.\n  const char HEX_DIGITS_URI[] = \"0123456789ABCDEF\";\n\n  kj::Vector<char> result(bytes.size() + 1);\n  for (byte b: bytes) {\n    if (b > 0x20) {\n      result.add(b);\n    } else {\n      result.add('%');\n      result.add(HEX_DIGITS_URI[b / 16]);\n      result.add(HEX_DIGITS_URI[b % 16]);\n    }\n  }\n  result.add('\\0');\n  return kj::String(result.releaseAsArray());\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/util.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/jsg/jsg.h>\n\n#include <v8.h>\n\n#include <kj/async-io.h>\n#include <kj/compat/url.h>\n#include <kj/string.h>\n\nnamespace workerd::api {\n\n// =======================================================================================\n\n#if _MSC_VER\n#define strcasecmp _stricmp\n#endif\n\n// Case-insensitive comparator for use with std::set/map.\nstruct CiLess {\n  bool operator()(kj::StringPtr lhs, kj::StringPtr rhs) const {\n    return strcasecmp(lhs.begin(), rhs.begin()) < 0;\n  }\n};\n\n// Parse `rawText` as application/x-www-form-urlencoded name/value pairs and store in `query`. If\n// `skipLeadingQuestionMark` is true, any initial '?' will be ignored. Otherwise, it will be\n// interpreted as part of the first URL-encoded field.\nvoid parseQueryString(kj::Vector<kj::Url::QueryParam>& query,\n    kj::ArrayPtr<const char> rawText,\n    bool skipLeadingQuestionMark = false);\n// TODO(cleanup): Would be really nice to move this to kj-url.\n\n// Given the value of a Content-Type header, returns the value of a single expected parameter.\n// For example:\n//\n//   readContentTypeParameter(\"application/x-www-form-urlencoded; charset=\\\"foobar\\\"\", \"charset\")\n//\n// would return \"foobar\" (without the quotes).\n//\n// Assumptions:\n//   - `contentType` has a semi-colon followed by OWS before the parameters.\n//   - If the wanted parameter uses quoted-string values, the correct\n//     value may not be returned.\nkj::Maybe<kj::String> readContentTypeParameter(kj::StringPtr contentType, kj::StringPtr param);\n// TODO(cleanup): Replace this function with a full kj::MimeType parser.\n\n// =======================================================================================\n\nstruct ErrorTranslation {\n  // A snippet of a KJ API exception description to be searched for.\n  kj::StringPtr kjDescription;\n\n  // A cleaned up exception description suitable for exposing to JavaScript. There is no need to\n  // prefix it with jsg.TypeError.\n  kj::StringPtr jsDescription;\n};\n\n// HACK: In some cases, KJ APIs throw exceptions with essential details that we want to expose to\n// the user, but also sensitive details or poor formatting which we'd prefer not to expose to the\n// user. While crude, we can string match to provide cleaned up exception messages. This O(n)\n// function helps you do that.\nkj::Maybe<kj::Exception> translateKjException(\n    const kj::Exception& exception, std::initializer_list<ErrorTranslation> translations);\n\n// =======================================================================================\n\n// Wrap the given stream in an adapter which translates kj::newTee()-specific exceptions into\n// JS-visible exceptions.\nkj::Own<kj::AsyncInputStream> newTeeErrorAdapter(kj::Own<kj::AsyncInputStream> inner);\n\n// Redacts potential secret keys from a given URL using a couple heuristics:\n//   - Any run of hex characters of 32 or more digits, ignoring potential \"+-_\" separators\n//   - Any run of base64 characters of 21 or more digits, including at least\n//     two each of digits, capital letters, and lowercase letters.\n// Such ids are replaced with the text \"REDACTED\".\nkj::String redactUrl(kj::StringPtr url);\n\n// =======================================================================================\n\nvoid maybeWarnIfNotText(jsg::Lock& js, kj::StringPtr str);\n\nkj::String fastEncodeBase64Url(kj::ArrayPtr<const byte> bytes);\nkj::Array<char16_t> fastEncodeUtf16(kj::ArrayPtr<const char> bytes);\n\nkj::String uriEncodeControlChars(kj::ArrayPtr<const byte> bytes);\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/web-socket.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"web-socket.h\"\n\n#include \"blob.h\"\n#include \"events.h\"\n#include \"messagechannel.h\"\n#include \"util.h\"\n\n#include <workerd/io/features.h>\n#include <workerd/io/io-context.h>\n#include <workerd/io/worker.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/ser.h>\n#include <workerd/util/autogate.h>\n#include <workerd/util/sentry.h>\n\n#include <kj/compat/url.h>\n\nnamespace workerd::api {\n\nkj::StringPtr KJ_STRINGIFY(const WebSocket::NativeState& state) {\n  // TODO(someday) We might care more about this `OneOf` than its which, that probably means\n  // returning a kj::String instead.\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(ac, WebSocket::AwaitingConnection) return \"AwaitingConnection\";\n    KJ_CASE_ONEOF(aaoc, WebSocket::AwaitingAcceptanceOrCoupling)\n      return \"AwaitingAcceptanceOrCoupling\";\n    KJ_CASE_ONEOF(a, WebSocket::Accepted) return \"Accepted\";\n    KJ_CASE_ONEOF(r, WebSocket::Released) return \"Released\";\n  }\n  KJ_UNREACHABLE;\n}\n\nIoOwn<WebSocket::Native> WebSocket::initNative(IoContext& ioContext,\n    kj::WebSocket& ws,\n    kj::Array<kj::StringPtr> tags,\n    bool closedOutgoingConn) {\n  auto nativeObj = kj::heap<Native>();\n  nativeObj->state.init<Accepted>(\n      Accepted::Hibernatable{.ws = ws, .tagsRef = kj::mv(tags)}, *nativeObj, ioContext);\n  // We might have called `close()` when this WebSocket was previously active.\n  // If so, we want to prevent any future calls to `send()`.\n  nativeObj->closedOutgoing = closedOutgoingConn;\n  autoResponseStatus.isClosed = nativeObj->closedOutgoing;\n  return ioContext.addObject(kj::mv(nativeObj));\n}\n\nWebSocket::WebSocket(\n    jsg::Lock& js, IoContext& ioContext, kj::WebSocket& ws, HibernationPackage package)\n    : weakRef(kj::refcounted<WeakRef<WebSocket>>(kj::Badge<WebSocket>{}, *this)),\n      url(kj::mv(package.url)),\n      protocol(kj::mv(package.protocol)),\n      extensions(kj::mv(package.extensions)),\n      binaryType_(FeatureFlags::get(js).getWebsocketBinaryTypeDefault() ? BinaryType::BLOB\n                                                                        : BinaryType::ARRAYBUFFER),\n      serializedAttachment(kj::mv(package.serializedAttachment)),\n      allowHalfOpen(package.allowHalfOpen),\n      farNative(initNative(ioContext,\n          ws,\n          kj::mv(KJ_REQUIRE_NONNULL(package.maybeTags)),\n          package.closedOutgoingConnection)),\n      outgoingMessages(IoContext::current().addObject(kj::heap<OutgoingMessagesMap>())) {}\n// This constructor is used when reinstantiating a websocket that had been hibernating, which is\n// why we can go straight to the Accepted state. However, note that we are actually in the\n// `Hibernatable` \"sub-state\"!\n\njsg::Ref<WebSocket> WebSocket::hibernatableFromNative(\n    jsg::Lock& js, kj::WebSocket& ws, HibernationPackage package) {\n  return js.alloc<WebSocket>(js, IoContext::current(), ws, kj::mv(package));\n}\n\nWebSocket::WebSocket(jsg::Lock& js, kj::Own<kj::WebSocket> native)\n    : weakRef(kj::refcounted<WeakRef<WebSocket>>(kj::Badge<WebSocket>{}, *this)),\n      url(kj::none),\n      binaryType_(FeatureFlags::get(js).getWebsocketBinaryTypeDefault() ? BinaryType::BLOB\n                                                                        : BinaryType::ARRAYBUFFER),\n      allowHalfOpen(!FeatureFlags::get(js).getWebSocketAutoReplyToClose()),\n      farNative(nullptr),\n      outgoingMessages(IoContext::current().addObject(kj::heap<OutgoingMessagesMap>())) {\n  auto nativeObj = kj::heap<Native>();\n  nativeObj->state.init<AwaitingAcceptanceOrCoupling>(kj::mv(native));\n  farNative = IoContext::current().addObject(kj::mv(nativeObj));\n}\n\nWebSocket::WebSocket(jsg::Lock& js, kj::String url)\n    : weakRef(kj::refcounted<WeakRef<WebSocket>>(kj::Badge<WebSocket>{}, *this)),\n      url(kj::mv(url)),\n      binaryType_(FeatureFlags::get(js).getWebsocketBinaryTypeDefault() ? BinaryType::BLOB\n                                                                        : BinaryType::ARRAYBUFFER),\n      allowHalfOpen(!FeatureFlags::get(js).getWebSocketAutoReplyToClose()),\n      farNative(nullptr),\n      outgoingMessages(IoContext::current().addObject(kj::heap<OutgoingMessagesMap>())) {\n  auto nativeObj = kj::heap<Native>();\n  nativeObj->state.init<AwaitingConnection>();\n  farNative = IoContext::current().addObject(kj::mv(nativeObj));\n}\n\nvoid WebSocket::initConnection(jsg::Lock& js, kj::Promise<PackedWebSocket> prom) {\n\n  auto& canceler = KJ_ASSERT_NONNULL(farNative->state.tryGet<AwaitingConnection>()).canceler;\n\n  IoContext::current()\n      .awaitIo(js, canceler.wrap(kj::mv(prom)),\n          [this, self = JSG_THIS](jsg::Lock& js, PackedWebSocket packedSocket) mutable {\n    auto& native = *farNative;\n    KJ_IF_SOME(pending, native.state.tryGet<AwaitingConnection>()) {\n      // We've successfully established our web socket, we do not need to cancel anything.\n      pending.canceler.release();\n    }\n\n    native.state.init<AwaitingAcceptanceOrCoupling>(\n        AwaitingAcceptanceOrCoupling{IoContext::current().addObject(kj::mv(packedSocket.ws))});\n\n    // both `protocol` and `extensions` start off as empty strings.\n    // They become null if the connection is established and no protocol/extension was chosen.\n    // https://html.spec.whatwg.org/multipage/web-sockets.html#dom-websocket-protocol\n    KJ_IF_SOME(proto, packedSocket.proto) {\n      protocol = kj::mv(proto);\n    } else {\n      protocol = kj::none;\n    }\n\n    KJ_IF_SOME(ext, packedSocket.extensions) {\n      extensions = kj::mv(ext);\n    } else {\n      extensions = kj::none;\n    }\n\n    // Fire open event.\n    internalAccept(js, IoContext::current().getCriticalSection());\n    dispatchOpen(js);\n  }).catch_(js, [this, self = JSG_THIS](jsg::Lock& js, jsg::Value&& e) mutable {\n    // Fire error event.\n    // Sets readyState to CLOSING.\n    farNative->closedIncoming = true;\n\n    // Sets readyState to CLOSED.\n    reportError(js, jsg::JsValue(e.getHandle(js)).addRef(js));\n\n    dispatchEventImpl(\n        js, js.alloc<CloseEvent>(1006, kj::str(\"Failed to establish websocket connection\"), false));\n  });\n  // Note that in this attach we pass a strong reference to the WebSocket. The reference will be\n  // dropped when either the connection promise completes or the IoContext is torn down,\n  // whichever comes first.\n}\n\nnamespace {\n\n// See item 10 of https://datatracker.ietf.org/doc/html/rfc6455#section-4.1\nbool validProtoToken(const kj::StringPtr protocol) {\n  if (kj::size(protocol) == 0) {\n    return false;\n  }\n\n  for (auto& c: protocol) {\n    // Note that this also includes separators 0x20 (SP) and 0x09 (HT), so we don't need to check\n    // for them below.\n    if (c < 0x21 || 0x7E < c) {\n      return false;\n    }\n\n    switch (c) {\n      case '(':\n      case ')':\n      case '<':\n      case '>':\n      case '@':\n      case ',':\n      case ';':\n      case ':':\n      case '\\\\':\n      case '/':\n      case '[':\n      case ']':\n      case '?':\n      case '=':\n      case '{':\n      case '}':\n        return false;\n      default:\n        break;\n    }\n  }\n  return true;\n}\n\n}  // namespace\n\njsg::Ref<WebSocket> WebSocket::constructor(jsg::Lock& js,\n    kj::String url,\n    jsg::Optional<kj::OneOf<kj::Array<kj::String>, kj::String>> protocols) {\n\n  auto& context = IoContext::current();\n\n  // Check if we have a valid URL\n  constexpr auto urlOptions = kj::Url::Options{.percentDecode = false, .allowEmpty = true};\n  constexpr auto wsErr = \"WebSocket Constructor: \"_kj;\n\n  // To be compatible with fetch() implementation:\n  // - First parse the URL with REMOTE_HREF which requires hostname.\n  // - Then stringify the URL with HTTP_PROXY_REQUEST which omits the userinfo.\n  kj::Url urlRecord = JSG_REQUIRE_NONNULL(kj::Url::tryParse(url, kj::Url::REMOTE_HREF, urlOptions),\n      DOMSyntaxError, wsErr, \"The url is invalid.\");\n\n  JSG_REQUIRE(urlRecord.scheme == \"ws\" || urlRecord.scheme == \"wss\", DOMSyntaxError, wsErr,\n      \"The url scheme must be ws or wss.\");\n  // We want the caller to pass `ws/wss` as per the spec, but FL would treat these as http in\n  // `X-Forwarded-Proto`, so we want to ensure that `wss` results in `https`, not `http`.\n  if (urlRecord.scheme == \"ws\") {\n    urlRecord.scheme = kj::str(\"http\");\n  } else if (urlRecord.scheme == \"wss\") {\n    urlRecord.scheme = kj::str(\"https\");\n  }\n\n  JSG_REQUIRE(\n      urlRecord.fragment == kj::none, DOMSyntaxError, wsErr, \"The url fragment must be empty.\");\n\n  kj::HttpHeaders headers(context.getHeaderTable());\n\n  // Set protocols header if necessary.\n  KJ_IF_SOME(variant, protocols) {\n    // String consisting of the protocol(s) we send to the server.\n    kj::Maybe<kj::String> maybeProtoString;\n\n    KJ_SWITCH_ONEOF(variant) {\n      KJ_CASE_ONEOF(proto, kj::String) {\n        JSG_REQUIRE(\n            validProtoToken(proto), DOMSyntaxError, wsErr, \"The protocol header token is invalid.\");\n        maybeProtoString = kj::mv(proto);\n      }\n      KJ_CASE_ONEOF(protoArr, kj::Array<kj::String>) {\n        // Per the WebSocket spec, an empty protocols array is valid and equivalent to not\n        // specifying any protocols - we simply don't set the Sec-WebSocket-Protocol header.\n        if (protoArr.size() > 0) {\n          // Search for duplicates by checking for their presence in the set.\n          kj::HashSet<kj::String> present;\n\n          for (const auto& proto: protoArr) {\n            JSG_REQUIRE(validProtoToken(proto), DOMSyntaxError, wsErr,\n                \"One of the protocol header tokens is invalid.\");\n            JSG_REQUIRE(!present.contains(proto), DOMSyntaxError, wsErr,\n                \"The protocols header cannot have repeating values.\");\n\n            present.insert(kj::str(proto));\n          }\n          constexpr auto delim = \", \"_kj;\n          maybeProtoString = kj::str(kj::delimited(protoArr, delim));\n        }\n      }\n    }\n    KJ_IF_SOME(protoString, maybeProtoString) {\n      auto protoHeaderId = context.getHeaderIds().secWebSocketProtocol;\n      headers.set(protoHeaderId, kj::mv(protoString));\n    }\n  }\n\n  // Any userinfo, username and/or password, should be removed.\n  // Users should use Authorization header for this purpose.\n  kj::String connUrl =\n      uriEncodeControlChars(urlRecord.toString(kj::Url::HTTP_PROXY_REQUEST).asBytes());\n  auto ws = js.alloc<WebSocket>(js, kj::mv(url));\n\n  headers.set(kj::HttpHeaderId::SEC_WEBSOCKET_EXTENSIONS, kj::str(\"permessage-deflate\"));\n  // By default, browsers set the compression extension header for `new WebSocket()`.\n\n  if (!FeatureFlags::get(js).getWebSocketCompression()) {\n    // If we haven't enabled the websocket compression compatibility flag, strip the header from the\n    // subrequest.\n    headers.unset(kj::HttpHeaderId::SEC_WEBSOCKET_EXTENSIONS);\n  }\n\n  auto client = context.getHttpClient(0, false, kj::none, \"websocket_open\"_kjc);\n  auto prom =\n      ([](auto& context, auto connUrl, auto headers, auto client) -> kj::Promise<PackedWebSocket> {\n    auto response = co_await client->openWebSocket(connUrl, headers);\n\n    JSG_REQUIRE(response.statusCode == 101, TypeError,\n        \"Failed to establish the WebSocket connection: expected server to reply with HTTP \"\n        \"status code 101 (switching protocols), but received \",\n        response.statusCode, \" instead.\");\n\n    KJ_SWITCH_ONEOF(response.webSocketOrBody) {\n      KJ_CASE_ONEOF(webSocket, kj::Own<kj::WebSocket>) {\n        auto maybeProtoPtr = response.headers->get(context.getHeaderIds().secWebSocketProtocol);\n        auto maybeExtensionsPtr = response.headers->get(kj::HttpHeaderId::SEC_WEBSOCKET_EXTENSIONS);\n\n        kj::Maybe<kj::String> maybeProto;\n        kj::Maybe<kj::String> maybeExtensions;\n\n        KJ_IF_SOME(proto, maybeProtoPtr) {\n          maybeProto = kj::str(proto);\n        }\n\n        KJ_IF_SOME(extensions, maybeExtensionsPtr) {\n          maybeExtensions = kj::str(extensions);\n        }\n\n        co_return PackedWebSocket{.ws = webSocket.attach(kj::mv(client)),\n          .proto = kj::mv(maybeProto),\n          .extensions = kj::mv(maybeExtensions)};\n      }\n      KJ_CASE_ONEOF(body, kj::Own<kj::AsyncInputStream>) {\n        JSG_FAIL_REQUIRE(\n            TypeError, \"Worker received body in a response to a request for a WebSocket.\");\n      }\n    }\n    KJ_UNREACHABLE\n  })(context, kj::mv(connUrl), kj::mv(headers), kj::mv(client));\n\n  ws->initConnection(js, kj::mv(prom));\n\n  return ws;\n}\n\nkj::Promise<DeferredProxy<void>> WebSocket::couple(\n    kj::Own<kj::WebSocket> other, RequestObserver& request) {\n  auto& native = *farNative;\n  JSG_REQUIRE(!native.state.is<AwaitingConnection>(), TypeError,\n      \"Can't return WebSocket in a Response if it was created with `new WebSocket()`\");\n  JSG_REQUIRE(!native.state.is<Released>(), TypeError,\n      \"Can't return WebSocket that was already used in a response.\");\n  KJ_IF_SOME(state, native.state.tryGet<Accepted>()) {\n    if (state.isHibernatable()) {\n      JSG_FAIL_REQUIRE(\n          TypeError, \"Can't return WebSocket in a Response after calling acceptWebSocket().\");\n    } else {\n      JSG_FAIL_REQUIRE(TypeError, \"Can't return WebSocket in a Response after calling accept().\");\n    }\n  }\n\n  // Tear down the IoOwn since we now need to extend the WebSocket to a `DeferredProxy` promise.\n  // This works because the `DeferredProxy` ends on the same event loop, but after the request\n  // context goes away.\n  kj::Own<kj::WebSocket> self =\n      kj::mv(KJ_ASSERT_NONNULL(native.state.tryGet<AwaitingAcceptanceOrCoupling>()).ws);\n  native.state.init<Released>();\n\n  auto& context = IoContext::current();\n\n  auto upstream = other->pumpTo(*self);\n  auto downstream = self->pumpTo(*other);\n\n  auto tryGetPeer = [&]() -> kj::Maybe<WebSocket&> {\n    KJ_IF_SOME(p, peer) {\n      return p->tryGet();\n    }\n    return kj::none;\n  };\n  auto isHibernatable = [&](workerd::api::WebSocket& ws) {\n    KJ_IF_SOME(state, ws.farNative->state.tryGet<Accepted>()) {\n      return state.isHibernatable();\n    }\n    return false;\n  };\n  KJ_IF_SOME(p, tryGetPeer()) {\n    // We're terminating the WebSocket in this worker, so the upstream promise (which pumps\n    // messages from the client to this worker) counts as something the request is waiting for.\n    upstream = upstream.attach(context.registerPendingEvent());\n\n    // We can observe websocket traffic in both directions by attaching an observer to the peer\n    // websocket which terminates in the worker.\n    KJ_IF_SOME(observer, request.tryCreateWebSocketObserver()) {\n      p.observer = kj::mv(observer);\n    }\n  }\n\n  // We need to use `eagerlyEvaluate()` on both inputs to `joinPromises` to work around the awkward\n  // behavior of `joinPromises` lazily-evaluating tail continuations.\n  auto promise = kj::joinPromises(\n      kj::arr(upstream.eagerlyEvaluate(nullptr), downstream.eagerlyEvaluate(nullptr)))\n                     .attach(kj::mv(self), kj::mv(other));\n\n  KJ_IF_SOME(peer, tryGetPeer()) {\n    // Since the WebSocket is terminated locally, we generally want the request and associated\n    // IoContext to stay alive until the WebSocket connection has terminated.\n    //\n    // However, there is one exception to this: when the WebSocket is hibernatable, we don't want\n    // the existence of this connection to prevent the actor from being evicted, so we fall through\n    // to deferred proxying in this case.\n    if (!isHibernatable(peer)) {\n      co_await promise;\n      co_return;\n    }\n  }\n\n  // Either:\n  // 1. This websocket is just proxying through, in which case we can allow the IoContext to go\n  // away while still being able to successfully pump the websocket connection.\n  // 2. This is a hibernatable websocket and we are falling through to deferred proxying to\n  // potentially allow for hibernation to occur.\n\n  // To begin deferred proxying, we can use this magic `KJ_CO_MAGIC` expression, which fulfills\n  // our outer promise for a DeferredProxy<void>, which wraps a promise for the rest of this\n  // coroutine.\n  KJ_CO_MAGIC BEGIN_DEFERRED_PROXYING;\n\n  co_return co_await promise;\n}\n\nvoid WebSocket::accept(jsg::Lock& js, jsg::Optional<AcceptOptions> options) {\n  auto& native = *farNative;\n  JSG_REQUIRE(!native.state.is<AwaitingConnection>(), TypeError,\n      \"Websockets obtained from the 'new WebSocket()' constructor cannot call accept\");\n  JSG_REQUIRE(!native.state.is<Released>(), TypeError,\n      \"Can't accept() WebSocket that was already used in a response.\");\n\n  KJ_IF_SOME(accepted, native.state.tryGet<Accepted>()) {\n    JSG_REQUIRE(!accepted.isHibernatable(), TypeError,\n        \"Can't accept() WebSocket after enabling hibernation.\");\n    // Technically, this means it's OK to invoke `accept()` once a `new WebSocket()` resolves to\n    // an established connection. This is probably okay? It might spare the worker devs a class of\n    // errors they do not care care about.\n    return;\n  }\n\n  KJ_IF_SOME(opts, options) {\n    KJ_IF_SOME(value, opts.allowHalfOpen) {\n      allowHalfOpen = AllowHalfOpen(value);\n    }\n  }\n\n  internalAccept(js, IoContext::current().getCriticalSection());\n}\n\nvoid WebSocket::internalAccept(jsg::Lock& js, kj::Maybe<kj::Own<InputGate::CriticalSection>> cs) {\n  auto& native = *farNative;\n  auto nativeWs = kj::mv(KJ_ASSERT_NONNULL(native.state.tryGet<AwaitingAcceptanceOrCoupling>()).ws);\n  native.state.init<Accepted>(kj::mv(nativeWs), native, IoContext::current());\n  return startReadLoop(js, kj::mv(cs));\n}\n\nWebSocket::Accepted::Accepted(kj::Own<kj::WebSocket> wsParam, Native& native, IoContext& context)\n    : ws(kj::mv(wsParam)),\n      whenAbortedTask(createAbortTask(native, context)) {\n  KJ_IF_SOME(a, context.getActor()) {\n    auto& metrics = a.getMetrics();\n    metrics.webSocketAccepted();\n\n    // Save the metrics object for the destructor since the IoContext may not be accessible\n    // there.\n    actorMetrics = kj::addRef(metrics);\n  }\n}\n\nWebSocket::Accepted::Accepted(Hibernatable wsParam, Native& native, IoContext& context)\n    : ws(kj::mv(wsParam)),\n      whenAbortedTask(createAbortTask(native, context)) {\n  KJ_IF_SOME(a, context.getActor()) {\n    auto& metrics = a.getMetrics();\n    metrics.webSocketAccepted();\n\n    // Save the metrics object for the destructor since the IoContext may not be accessible\n    // there.\n    actorMetrics = kj::addRef(metrics);\n  }\n}\n\nkj::Promise<void> WebSocket::Accepted::createAbortTask(Native& native, IoContext& context) {\n  try {\n    // whenAborted() is theoretically not supposed to throw, but some code paths, like\n    // AbortableWebSocket and Cap'n Proto disconnects, may end up throwing DISCONNECTED. Treat\n    // exceptions the same as if `whenAborted()` finished normally -- but log in catch catch\n    // block if it's not DISCONNECTED.\n    co_await ws->whenAborted();\n\n    // Other end disconnected prematurely. We may be able to clean up our state.\n    native.outgoingAborted = true;\n    if (!native.isPumping && native.closedIncoming) {\n      // We can safely destroy the underlying WebSocket as it is no longer in use.\n      // HACK: Replacing the state will delete `whenAbortedTask`, which is the task that is\n      //   currently executing, which will crash. We know we're at the end of the task here\n      //   so detach it as a work-around.\n      whenAbortedTask.detach([](auto&&) {});\n      native.state.init<Released>();\n    } else {\n      // Either we haven't received the incoming disconnect yet, or there are writes\n      // in-flight. In either case, we need to wait for those to happen before we destroy the\n      // underlying object, or we might have a UAF situation. Those other operations should\n      // fail shortly and notice the `outgoingAborted` flag when they do.\n    }\n  } catch (...) {\n    auto ex = kj::getCaughtExceptionAsKj();\n    if (ex.getType() != kj::Exception::Type::DISCONNECTED) {\n      LOG_EXCEPTION(\"webSocketWhenAborted\", ex);\n    }\n  }\n}\n\nWebSocket::Accepted::~Accepted() noexcept(false) {\n  KJ_IF_SOME(a, actorMetrics) {\n    a.get()->webSocketClosed();\n  }\n}\n\n// Default max WebSocket message size limit. Note that kj-http's own default is 1MiB\n// (`kj::WebSocket::SUGGESTED_MAX_MESSAGE_SIZE`). We've found this to be too small for many commmon\n// use cases, such as proxying Chrome Devtools Protocol messages.\n//\n// JS-RPC messages are size-limited to 32MiB, and it seems to be working well, so we're setting the\n// WebSocket default max message size to match that.\nstatic constexpr size_t WEBSOCKET_MAX_MESSAGE_SIZE = 32u << 20;\n\nvoid WebSocket::startReadLoop(jsg::Lock& js, kj::Maybe<kj::Own<InputGate::CriticalSection>> cs) {\n  size_t maxMessageSize = WEBSOCKET_MAX_MESSAGE_SIZE;\n  if (FeatureFlags::get(js).getIncreaseWebsocketMessageSize()) {\n    maxMessageSize = 128u << 20;\n  }\n\n  // If the kj::WebSocket happens to be an AbortableWebSocket (see util/abortable.h), then\n  // calling readLoop here could throw synchronously if the canceler has already been tripped.\n  // Using kj::evalNow() here let's us capture that and handle correctly.\n  //\n  // We catch exceptions and return Maybe<Exception> instead since we want to handle the exceptions\n  // in awaitIo() below, but we don't want the KJ exception converted to JavaScript before we can\n  // examine it.\n  kj::Promise<kj::Maybe<kj::Exception>> promise = readLoop(kj::mv(cs), maxMessageSize);\n\n  auto& context = IoContext::current();\n\n  auto hasLocalPeer = [&]() {\n    KJ_IF_SOME(p, peer) {\n      if (p->isValid()) {\n        return true;\n      }\n    }\n    return false;\n  };\n  if (!hasLocalPeer()) {\n    promise = promise.attach(context.registerPendingEvent());\n  }\n\n  // We put the read loop in a `waitUntil`, since there would otherwise be a race condition between\n  // delivering the final close message and the request being canceled due to client disconnect.\n  // This `waitUntil` will not significantly extend the lifetime of the request in practice, as the\n  // request otherwise ends when the client disconnects, and the read loop will also end when the\n  // client disconnects -- we just want to ensure that they happen in the right order.\n  //\n  // TODO(bug): Using waitUntil() for this purpose is only correct for WebSockets originating from\n  //   the eyeball. For an outgoing WebSocket, we should just do addTask(). Alternatively, perhaps\n  //   we need to adjust the cancellation logic to wait for whenThreadIdle() before cancelling,\n  //   which would then allow close messages to be delivered from eyeball connections without any\n  //   use of waitUntil().\n  //\n  // TODO(cleanup): We have to use awaitIoLegacy() so that we can handle registerPendingEvent()\n  //   manually. Ideally, we'd refactor things such that a WebSocketPair where both ends are\n  //   accepted locally is implemented completely in JavaScript space, using jsg::Promise instead\n  //   of kj::Promise, and then only use awaitIo() on truly remote WebSockets.\n  // TODO(cleanup): Should addWaitUntil() take jsg::Promise instead of kj::Promise?\n  context.addWaitUntil(context.awaitJs(js,\n      context.awaitIoLegacy(js, kj::mv(promise))\n          .then(js,\n              [this, thisHandle = JSG_THIS](\n                  jsg::Lock& js, kj::Maybe<kj::Exception>&& maybeError) mutable {\n    auto& native = *farNative;\n    KJ_IF_SOME(e, maybeError) {\n      if (!native.closedIncoming && e.getType() == kj::Exception::Type::DISCONNECTED) {\n        // Report premature disconnect or cancel as a close event.\n        dispatchEventImpl(js,\n            js.alloc<CloseEvent>(\n                1006, kj::str(\"WebSocket disconnected without sending Close frame.\"), false));\n        native.closedIncoming = true;\n        // If there are no further messages to send, so we can discard the underlying connection.\n        tryReleaseNative(js);\n      } else {\n        native.closedIncoming = true;\n        reportError(js, kj::cp(e));\n      }\n    }\n  })));\n}\n\nvoid WebSocket::send(jsg::Lock& js, kj::OneOf<kj::Array<byte>, kj::String> message) {\n  auto& native = *farNative;\n  JSG_REQUIRE(!native.closedOutgoing, TypeError, \"Can't call WebSocket send() after close().\");\n  if (native.outgoingAborted || native.state.is<Released>()) {\n    // Per the spec, we should silently ignore send()s that happen after the connection is closed.\n    // NOTE: The spec claims send() should also silently ignore messages sent after a close message\n    //   has been sent or received cleanly. We ignore this advice:\n    // * If close has been sent, i.e. close() has been called, then calling send() is clearly a\n    //   bug, and we'd like to help people debug, so we throw an exception above. (This point is\n    //   debatable, we could change it.)\n    // * It makes no sense that *receiving* a close message should prevent further calls to send().\n    //   The spec seems broken here. What if you need to send a couple final messages for a clean\n    //   shutdown?\n    return;\n  } else if (awaitingHibernatableError()) {\n    // Ready for the hibernatable error event state, after encountering an error, the websocket\n    // isn't able to send outbound messages; let's release it.\n    tryReleaseNative(js);\n    return;\n  }\n\n  JSG_REQUIRE(native.state.is<Accepted>(), TypeError,\n      \"You must call one of accept() or state.acceptWebSocket() on this WebSocket before sending \"\n      \"messages.\");\n\n  auto maybeOutputLock = IoContext::current().waitForOutputLocksIfNecessary();\n  auto msg = [&]() -> kj::WebSocket::Message {\n    KJ_SWITCH_ONEOF(message) {\n      KJ_CASE_ONEOF(text, kj::String) {\n        return kj::mv(text);\n        break;\n      }\n      KJ_CASE_ONEOF(data, kj::Array<byte>) {\n        return kj::mv(data);\n        break;\n      }\n    }\n\n    KJ_UNREACHABLE;\n  }();\n\n  outgoingMessages->insert(\n      GatedMessage{kj::mv(maybeOutputLock), kj::mv(msg), getPendingAutoResponseCount()});\n\n  ensurePumping(js);\n}\n\nvoid WebSocket::close(\n    jsg::Lock& js, jsg::Optional<int> code, jsg::Optional<jsg::USVString> reason) {\n  auto& native = *farNative;\n\n  // Per the spec, close code and reason validation must happen before any readyState checks.\n  // See https://websockets.spec.whatwg.org/#dom-websocket-close step 1.\n  KJ_IF_SOME(c, code) {\n    if (FeatureFlags::get(js).getPedanticWpt()) {\n      // The WHATWG WebSocket spec allows only 1000 (Normal Closure) or the range 3000-4999\n      // (reserved for libraries/frameworks and private use, per RFC 6455 Section 7.4.2).\n      JSG_REQUIRE(c == 1000 || (c >= 3000 && c <= 4999), DOMInvalidAccessError,\n          \"Invalid WebSocket close code: \", c, \".\");\n    } else {\n      // Legacy behavior: accept the full 1000-4999 range, only rejecting four codes that\n      // RFC 6455 Section 7.4.1 reserves and forbids endpoints from sending in a Close frame:\n      //   1004 - Reserved (no defined meaning)\n      //   1005 - No Status Rcvd (only used internally to indicate no code was present)\n      //   1006 - Abnormal Closure (only used internally when connection drops without a Close frame)\n      //   1015 - TLS Handshake failure (only used internally, never sent over the wire)\n      JSG_REQUIRE(c >= 1000 && c < 5000 && c != 1004 && c != 1005 && c != 1006 && c != 1015,\n          DOMInvalidAccessError, \"Invalid WebSocket close code: \", c, \".\");\n    }\n  }\n\n  // Per the WHATWG WebSocket spec and RFC 6455 Section 5.5, the close frame body must not exceed\n  // 125 bytes (2-byte status code + up to 123 bytes of reason). Throw a SyntaxError if the\n  // reason's UTF-8 encoding is longer than 123 bytes.\n  if (FeatureFlags::get(js).getWebsocketCloseReasonByteLimit()) {\n    KJ_IF_SOME(r, reason) {\n      JSG_REQUIRE(r.size() <= 123, DOMSyntaxError,\n          \"WebSocket close reason must not be longer than 123 bytes when UTF-8 encoded.\");\n    }\n  }\n\n  // The default code of 1005 cannot have a reason, per the standard, so if a reason is specified\n  // then there must be a code, too.\n  JSG_REQUIRE(reason == kj::none || code != kj::none, DOMInvalidAccessError,\n      \"If you specify a WebSocket close reason, you must also specify a code.\");\n\n  // Handle close before connection is established for websockets obtained through `new WebSocket()`.\n  KJ_IF_SOME(pending, native.state.tryGet<AwaitingConnection>()) {\n    pending.canceler.cancel(kj::str(\"Called close before connection was established.\"));\n\n    // Strictly speaking, we might not be all the way released by now, but we definitely shouldn't\n    // worry about canceling again.\n    native.state.init<Released>();\n    return;\n  }\n\n  if (native.closedOutgoing || native.outgoingAborted || native.state.is<Released>()) {\n    // See comments in send(), above, which also apply here. Note that we opt to ignore a\n    // double-close() per spec, whereas send()-after-close() throws (off-spec).\n\n    return;\n  } else if (awaitingHibernatableError()) {\n    // Ready for the hibernatable error event state, after encountering an error, the websocket\n    // isn't able to send outbound messages; let's release it.\n    tryReleaseNative(js);\n    return;\n  }\n  JSG_REQUIRE(native.state.is<Accepted>(), TypeError,\n      \"You must call one of accept() or state.acceptWebSocket() on this WebSocket before sending \"\n      \"messages.\");\n\n  assertNoError(js);\n\n  outgoingMessages->insert(GatedMessage{IoContext::current().waitForOutputLocksIfNecessary(),\n    kj::WebSocket::Close{\n      // Code 1005 actually translates to sending a close message with no body on the wire.\n      static_cast<uint16_t>(code.orDefault(1005)),\n      kj::mv(reason).orDefault(jsg::USVString(kj::str())),\n    },\n    getPendingAutoResponseCount()});\n\n  native.closedOutgoing = true;\n  closedOutgoingForHib = true;\n  ensurePumping(js);\n}\n\nint WebSocket::getReadyState() {\n  auto& native = *farNative;\n  if ((native.closedIncoming && native.closedOutgoing) || error != kj::none) {\n    return READY_STATE_CLOSED;\n  } else if (native.closedIncoming || native.closedOutgoing) {\n    // Bizarrely, the spec uses the same state for a close message having been sent *or* received,\n    // even though these are very different states from the point of view of the application.\n    return READY_STATE_CLOSING;\n  } else if (native.state.is<AwaitingConnection>()) {\n    return READY_STATE_CONNECTING;\n  }\n  return READY_STATE_OPEN;\n}\n\nbool WebSocket::isAccepted() {\n  return farNative->state.is<Accepted>();\n}\n\nbool WebSocket::isReleased() {\n  return farNative->state.is<Released>();\n}\n\nkj::Maybe<kj::String> WebSocket::getPreferredExtensions(kj::WebSocket::ExtensionsContext ctx) {\n  KJ_SWITCH_ONEOF(farNative->state) {\n    KJ_CASE_ONEOF(ws, AwaitingConnection) {\n      return kj::none;\n    }\n    KJ_CASE_ONEOF(container, AwaitingAcceptanceOrCoupling) {\n      return container.ws->getPreferredExtensions(ctx);\n    }\n    KJ_CASE_ONEOF(container, Accepted) {\n      return container.ws->getPreferredExtensions(ctx);\n    }\n    KJ_CASE_ONEOF(container, Released) {\n      return kj::none;\n    }\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::StringPtr> WebSocket::getUrl() {\n  return url.map([](kj::StringPtr value) { return value; });\n}\n\nkj::Maybe<kj::StringPtr> WebSocket::getProtocol() {\n  return protocol.map([](kj::StringPtr value) { return value; });\n}\n\nkj::Maybe<kj::StringPtr> WebSocket::getExtensions() {\n  return extensions.map([](kj::StringPtr value) { return value; });\n}\n\nkj::Maybe<jsg::JsValue> WebSocket::deserializeAttachment(jsg::Lock& js) {\n  return serializedAttachment.map([&](kj::ArrayPtr<byte> attachment) -> jsg::JsValue {\n    jsg::Deserializer deserializer(js, attachment, kj::none, kj::none,\n        jsg::Deserializer::Options{\n          .version = 15,\n          .readHeader = true,\n        });\n\n    return deserializer.readValue(js);\n  });\n}\n\nvoid WebSocket::serializeAttachment(jsg::Lock& js, jsg::JsValue attachment) {\n  jsg::Serializer serializer(js,\n      jsg::Serializer::Options{\n        .version = 15,\n        .omitHeader = false,\n      });\n  serializer.write(js, attachment);\n  auto released = serializer.release();\n  JSG_REQUIRE(released.data.size() <= MAX_ATTACHMENT_SIZE, Error,\n      \"A WebSocket 'attachment' cannot be larger than \", MAX_ATTACHMENT_SIZE,\n      \" bytes.\"\n      \"'attachment' was \",\n      released.data.size(), \" bytes.\");\n  serializedAttachment = kj::mv(released.data);\n}\n\nvoid WebSocket::setAutoResponseStatus(\n    kj::Maybe<kj::Date> time, kj::Promise<void> autoResponsePromise) {\n  autoResponseTimestamp = time;\n  autoResponseStatus.ongoingAutoResponse = kj::mv(autoResponsePromise);\n}\n\nkj::Maybe<kj::Date> WebSocket::getAutoResponseTimestamp() {\n  return autoResponseTimestamp;\n}\n\nvoid WebSocket::dispatchOpen(jsg::Lock& js) {\n  dispatchEventImpl(js, js.alloc<Event>(\"open\"));\n}\n\nvoid WebSocket::ensurePumping(jsg::Lock& js) {\n  auto& native = *farNative;\n  if (!native.isPumping) {\n    auto& context = IoContext::current();\n    auto& accepted = KJ_ASSERT_NONNULL(native.state.tryGet<Accepted>());\n    auto promise = kj::evalNow([&]() {\n      return accepted.canceler.wrap(\n          pump(context, *outgoingMessages, *accepted.ws, native, autoResponseStatus, observer));\n    });\n\n    // TODO(cleanup): We use awaitIoLegacy() here because we don't want this to count as a pending\n    //   event if this is a WebSocketPair with the other end being handled in the same isolate.\n    //   In that case, the pump can hang if accept() is never called on the other end. Ideally,\n    //   this scenario would be handled in-isolate using jsg::Promise, but that would take some\n    //   refactoring.\n    context.awaitIoLegacy(js, kj::mv(promise))\n        .then(js, [this, thisHandle = JSG_THIS](jsg::Lock& js) {\n      auto& native = *farNative;\n      if (native.outgoingAborted) {\n        if (awaitingHibernatableRelease()) {\n          // We have a hibernatable websocket -- we don't want to dispatch a regular error event.\n          tryReleaseNative(js);\n        } else {\n          // Apparently, the peer stopped accepting messages (probably, disconnected entirely), but\n          // this didn't cause our writes to fail, maybe due to timing. Let's set the error now.\n          reportError(js, KJ_EXCEPTION(DISCONNECTED, \"WebSocket peer disconnected\"));\n        }\n      } else if (native.closedIncoming && native.closedOutgoing) {\n        if (awaitingHibernatableRelease()) {\n          // TODO(someday): These async races can be pretty complicated, and while it's good to have\n          // tests to make sure we're not broken, it would be nice to refactor this code eventually.\n\n          // Hibernatable WebSockets had a subtle race condition where one pump() promise would\n          // start right after a previous pump() completed, but before this continuation ran.\n          //\n          // This race prevented close messages from being sent from inside the webSocketClose()\n          // handler because prior to the CLOSE getting sent in the second pump(), the promise\n          // continuation following the first pump() would transition us from Accepted to Released,\n          // triggering the canceler and cancelling the outgoing CLOSE of the second pump() promise.\n          //\n          // For a more detailed explanation, see https://github.com/cloudflare/workerd/pull/1535.\n          tryReleaseNative(js);\n        } else if (native.state.is<Accepted>()) {\n          // Native WebSocket no longer needed; release.\n          native.state.init<Released>();\n        } else if (native.state.is<Released>()) {\n          // While we were awaiting the jsg::Promise, someone else released our state. That's fine.\n        } else {\n          KJ_FAIL_ASSERT(\"Unexpected native web socket state\", native.state);\n        }\n      }\n    }, [this, thisHandle = JSG_THIS](jsg::Lock& js, jsg::Value&& exception) mutable {\n      if (awaitingHibernatableRelease()) {\n        // We have a hibernatable websocket -- we don't want to dispatch a regular error event.\n        tryReleaseNative(js);\n      } else {\n        reportError(js, jsg::JsValue(exception.getHandle(js)).addRef(js));\n      }\n    });\n  }\n}\n\nkj::Promise<void> WebSocket::sendAutoResponse(kj::String message, kj::WebSocket& ws) {\n  if (autoResponseStatus.isPumping) {\n    autoResponseStatus.pendingAutoResponseDeque.push(kj::mv(message));\n  } else if (!autoResponseStatus.isClosed) {\n    auto p = ws.send(message).fork();\n    autoResponseStatus.ongoingAutoResponse = p.addBranch();\n    co_await p;\n    autoResponseStatus.ongoingAutoResponse = kj::READY_NOW;\n  }\n}\n\nnamespace {\n\nsize_t countBytesFromMessage(const kj::WebSocket::Message& message) {\n  // This does not count the extra data of the RPC frame or the savings from any compression.\n  // We're incentivizing customers to use reasonably sized messages, not trying to get an exact\n  // count of how many bytes went over the wire.\n\n  KJ_SWITCH_ONEOF(message) {\n    KJ_CASE_ONEOF(s, kj::String) {\n      return s.size();\n    }\n    KJ_CASE_ONEOF(a, kj::Array<byte>) {\n      return a.size();\n    }\n    KJ_CASE_ONEOF(c, kj::WebSocket::Close) {\n      // If we include the size of the close code, that could incentivize our customers to omit\n      // sending Close frames when appropriate. The same cannot be said for the close reason since\n      // someone could encapsulate their final message in it to save costs.\n      return c.reason.size();\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\n}  // namespace\n\nkj::Promise<void> WebSocket::pump(IoContext& context,\n    OutgoingMessagesMap& outgoingMessages,\n    kj::WebSocket& ws,\n    Native& native,\n    AutoResponse& autoResponse,\n    kj::Maybe<kj::Own<WebSocketObserver>>& observer) {\n  KJ_ASSERT(!native.isPumping);\n  native.isPumping = true;\n  autoResponse.isPumping = true;\n  bool completed = false;\n  KJ_DEFER({\n    // We use a KJ_DEFER to set native.isPumping = false to ensure that it happens -- we had a bug\n    // in the past where this was handled by the caller of WebSocket::pump() and it allowed for\n    // messages to get stuck in `outgoingMessages` until the pump task was restarted.\n    native.isPumping = false;\n\n    // Either we were already through all our outgoing messages or we experienced failure/\n    // cancellation and cannot send these anyway.\n    outgoingMessages.clear();\n\n    autoResponse.isPumping = false;\n\n    autoResponse.pendingAutoResponseDeque.clear();\n\n    if (!completed) {\n      // We didn't make it to `completed = true` at the end of this function, so either an\n      // exception was thrown or the task was canceled. Either way, we cannot send any further\n      // messages, because the connection is in a broken state and will just throw more exceptions.\n      // Setting `outgoingAborted` stops us from even trying to queue any more messages.\n      native.outgoingAborted = true;\n    }\n  });\n\n  // If we have a ongoingAutoResponse, we must co_await it here because there's a ws.send()\n  // in progress. Otherwise there can occur ws.send() race problems.\n  co_await autoResponse.ongoingAutoResponse;\n  autoResponse.ongoingAutoResponse = kj::READY_NOW;\n\n  do {\n    while (outgoingMessages.size() > 0) {\n      GatedMessage gatedMessage = outgoingMessages.release(*outgoingMessages.ordered().begin());\n      KJ_IF_SOME(promise, gatedMessage.outputLock) {\n        co_await promise;\n      }\n\n      auto size = countBytesFromMessage(gatedMessage.message);\n\n      while (gatedMessage.pendingAutoResponses > 0) {\n        auto message = KJ_ASSERT_NONNULL(autoResponse.pendingAutoResponseDeque.pop());\n        gatedMessage.pendingAutoResponses--;\n        autoResponse.queuedAutoResponses--;\n        co_await ws.send(message);\n      }\n\n      KJ_SWITCH_ONEOF(gatedMessage.message) {\n        KJ_CASE_ONEOF(text, kj::String) {\n          co_await ws.send(text);\n          break;\n        }\n        KJ_CASE_ONEOF(data, kj::Array<byte>) {\n          co_await ws.send(data);\n          break;\n        }\n        KJ_CASE_ONEOF(close, kj::WebSocket::Close) {\n          co_await ws.close(close.code, close.reason);\n          autoResponse.isClosed = true;\n          break;\n        }\n      }\n\n      KJ_IF_SOME(o, observer) {\n        o->sentMessage(size);\n      }\n\n      KJ_IF_SOME(a, context.getActor()) {\n        a.getMetrics().sentWebSocketMessage(size);\n      }\n    }\n\n    // If there are any auto-responses left to process, we should do it now.\n    // We should also check if the last sent message was a close. Shouldn't happen.\n    while (!autoResponse.pendingAutoResponseDeque.empty() && !autoResponse.isClosed) {\n      auto message = KJ_ASSERT_NONNULL(autoResponse.pendingAutoResponseDeque.pop());\n      co_await ws.send(message);\n    }\n\n    // While we were `co_await`ing the auto-response send, more messages could have been queued\n    // into `outgoingMessages`. If so we'll need to start over, otherwise these messages would be\n    // discarded in our KJ_DEFER block!\n  } while (outgoingMessages.size() > 0);\n\n  completed = true;\n}\n\nsize_t WebSocket::getPendingAutoResponseCount() {\n  auto count =\n      autoResponseStatus.pendingAutoResponseDeque.size() - autoResponseStatus.queuedAutoResponses;\n  autoResponseStatus.queuedAutoResponses = autoResponseStatus.pendingAutoResponseDeque.size();\n  return count;\n}\n\nvoid WebSocket::tryReleaseNative(jsg::Lock& js) {\n  // If the native WebSocket is no longer needed (the connection closed) and there are no more\n  // messages to send, we can discard the underlying connection.\n  auto& native = *farNative;\n  if ((native.closedOutgoing || native.outgoingAborted) && !native.isPumping) {\n    // Native WebSocket no longer needed; release.\n    KJ_ASSERT(native.state.is<Accepted>());\n    native.state.init<Released>();\n  }\n}\n\nkj::Array<kj::StringPtr> WebSocket::getHibernatableTags() {\n  auto& accepted = JSG_REQUIRE_NONNULL(farNative->state.tryGet<Accepted>(), Error,\n      \"you must call 'acceptWebSocket()' before attempting to access the tags of a WebSocket.\");\n  JSG_REQUIRE(accepted.isHibernatable(), Error, \"only hibernatable websockets can have tags.\");\n  return accepted.ws.getHibernatableTags();\n}\n\nkj::Promise<kj::Maybe<kj::Exception>> WebSocket::readLoop(\n    kj::Maybe<kj::Own<InputGate::CriticalSection>> cs, size_t maxMessageSize) {\n  try {\n    // Note that we'll throw if the websocket has enabled hibernation.\n    auto& ws = *KJ_REQUIRE_NONNULL(\n        KJ_ASSERT_NONNULL(farNative->state.tryGet<Accepted>()).ws.getIfNotHibernatable());\n    auto& context = IoContext::current();\n    while (true) {\n      auto message = co_await ws.receive(maxMessageSize);\n\n      auto size = countBytesFromMessage(message);\n      KJ_IF_SOME(o, observer) {\n        o->receivedMessage(size);\n      }\n\n      context.getLimitEnforcer().topUpActor();\n      KJ_IF_SOME(a, context.getActor()) {\n        a.getMetrics().receivedWebSocketMessage(size);\n      }\n\n      // Re-enter the context with context.run(). This is arguably a bit unusual compared to other\n      // I/O which is delivered by return from context.awaitIo(), but the difference here is that we\n      // have a long stream of events over time. It makes sense to use context.run() each time a new\n      // event arrives.\n      // TODO(cleanup): The way context.run is defined, a capturing lambda is required here, which\n      // is a bit unfortunate. We could simply things somewhat with a variation that would allow\n      // something like context.run(handleMessage, *this, kj::mv(message)) where the acquired lock,\n      // and the additional arguments are passed into handleMessage, avoiding the need for the\n      // lambda here entirely.\n      auto result = co_await context.run([this, message = kj::mv(message)](auto& wLock) mutable {\n        auto& native = *farNative;\n        jsg::Lock& js = wLock;\n        KJ_SWITCH_ONEOF(message) {\n          KJ_CASE_ONEOF(text, kj::String) {\n            dispatchEventImpl(js, js.alloc<MessageEvent>(js, js.str(text)));\n          }\n          KJ_CASE_ONEOF(data, kj::Array<byte>) {\n            if (binaryType_ == BinaryType::BLOB) {\n              // Per the WHATWG spec, deliver binary messages as Blob when binaryType is \"blob\".\n              auto bufferSource = jsg::BufferSource(js, jsg::BackingStore::from(js, kj::mv(data)));\n              auto blob = js.alloc<Blob>(js, kj::mv(bufferSource), kj::str());\n              dispatchEventImpl(js, js.alloc<MessageEvent>(js, kj::str(\"message\"), kj::mv(blob)));\n            } else {\n              auto ab = js.arrayBuffer(kj::mv(data)).getHandle(js);\n              dispatchEventImpl(js, js.alloc<MessageEvent>(js, jsg::JsValue(ab)));\n            }\n          }\n          KJ_CASE_ONEOF(close, kj::WebSocket::Close) {\n            native.closedIncoming = true;\n            if (!allowHalfOpen.toBool() && !native.closedOutgoing && !native.outgoingAborted &&\n                !native.state.is<Released>()) {\n              // When allowHalfOpen is false (the spec-compliant default with the\n              // web_socket_auto_reply_to_close compat flag), automatically send a reciprocal\n              // Close frame through the outgoing message pump so that readyState is CLOSED (3)\n              // when the close event fires. Skip if a close frame was already sent (e.g. the\n              // application called close() before the server sent its Close), or if the outgoing\n              // side is otherwise unusable.\n              outgoingMessages->insert(\n                  GatedMessage{IoContext::current().waitForOutputLocksIfNecessary(),\n                    kj::WebSocket::Close{close.code, kj::str(close.reason)},\n                    getPendingAutoResponseCount()});\n\n              native.closedOutgoing = true;\n              closedOutgoingForHib = true;\n              ensurePumping(js);\n            }\n            dispatchEventImpl(js, js.alloc<CloseEvent>(close.code, kj::mv(close.reason), true));\n            // Native WebSocket no longer needed; release.\n            tryReleaseNative(js);\n            return false;\n          }\n        }\n\n        return true;\n      }, mapAddRef(cs));\n\n      if (!result) co_return kj::none;\n    }\n    KJ_UNREACHABLE;\n  } catch (...) {\n    co_return kj::getCaughtExceptionAsKj();\n  }\n}\n\njsg::Ref<WebSocketPair> WebSocketPair::constructor(jsg::Lock& js) {\n  auto pipe = kj::newWebSocketPipe();\n  auto pair = js.alloc<WebSocketPair>(\n      js.alloc<WebSocket>(js, kj::mv(pipe.ends[0])), js.alloc<WebSocket>(js, kj::mv(pipe.ends[1])));\n  auto first = pair->getFirst();\n  auto second = pair->getSecond();\n\n  first->setPeer(second->addWeakRef());\n  second->setPeer(first->addWeakRef());\n  return kj::mv(pair);\n}\n\njsg::Ref<WebSocketPair::PairIterator> WebSocketPair::entries(jsg::Lock& js) {\n  return js.alloc<PairIterator>(IteratorState{\n    .pair = JSG_THIS,\n    .index = 0,\n  });\n}\n\nvoid WebSocket::reportError(jsg::Lock& js, kj::Exception&& e) {\n  reportError(js, js.exceptionToJsValue(kj::cp(e)));\n}\n\nvoid WebSocket::reportError(jsg::Lock& js, jsg::JsRef<jsg::JsValue> err) {\n  // If this is the first error, raise the error event.\n  if (error == kj::none) {\n    auto msg = kj::str(v8::Exception::CreateMessage(js.v8Isolate, err.getHandle(js))->Get());\n    error = err.addRef(js);\n\n    dispatchEventImpl(js,\n        js.alloc<ErrorEvent>(\n            ErrorEvent::ErrorEventInit{.message = kj::mv(msg), .error = kj::mv(err)}));\n\n    // After an error we don't allow further send()s. If the receive loop has also ended then we\n    // can destroy the connection. Note that we don't set closedOutgoing = true because that flag\n    // is specifically to indicate that `close()` has been called, and it causes `send()` to throw\n    // an exception complaining specifically that `close()` was called, which would be\n    // inappropriate in this case.\n    auto& native = *farNative;\n    native.outgoingAborted = true;\n    if (native.closedIncoming && !native.isPumping) {\n      KJ_IF_SOME(pending, native.state.tryGet<AwaitingConnection>()) {\n        // Nothing worth canceling if we're reporting an error from the connection establishment\n        // continuations.\n        pending.canceler.release();\n      }\n\n      // We're no longer pumping so let's make sure we release the native connection here.\n      native.state.init<Released>();\n    }\n  }\n}\n\nvoid WebSocket::assertNoError(jsg::Lock& js) {\n  KJ_IF_SOME(e, error) {\n    js.throwException(e.addRef(js));\n  }\n}\n\nvoid WebSocket::setPeer(kj::Own<WeakRef<WebSocket>> other) {\n  peer = kj::mv(other);\n}\n\nkj::Own<kj::WebSocket> WebSocket::acceptAsHibernatable(kj::Array<kj::StringPtr> tags) {\n  KJ_IF_SOME(hibernatable, farNative->state.tryGet<AwaitingAcceptanceOrCoupling>()) {\n    // We can only request hibernation if we have not called accept.\n    auto ws = kj::mv(hibernatable.ws);\n    // We pass a reference to the kj::WebSocket for the api::WebSocket to refer to when calling\n    // `send()` or `close()`.\n    farNative->state.init<Accepted>(Accepted::Hibernatable{.ws = *ws, .tagsRef = kj::mv(tags)},\n        *farNative, IoContext::current());\n    return kj::mv(ws);\n  }\n  JSG_FAIL_REQUIRE(TypeError,\n      \"Tried to make an api::WebSocket hibernatable when it was in an incompatible state.\");\n}\n\nvoid WebSocket::initiateHibernatableRelease(jsg::Lock& js,\n    kj::Own<kj::WebSocket> ws,\n    kj::Array<kj::String> tags,\n    HibernatableReleaseState releaseState) {\n  // TODO(soon): We probably want this to be an assert, since this is meant to be called once\n  // at the end of a websocket connection>\n  KJ_IF_SOME(state, farNative->state.tryGet<Accepted>()) {\n    KJ_REQUIRE(state.isHibernatable(),\n        \"tried to initiate hibernatable release but websocket wasn't hibernatable\");\n    state.ws.initiateHibernatableRelease(js, kj::mv(ws), kj::mv(tags), releaseState);\n    farNative->closedIncoming = true;\n  } else {\n    KJ_LOG(WARNING, \"Unexpected Hibernatable WebSocket state on release\", farNative->state);\n  }\n}\n\nbool WebSocket::awaitingHibernatableError() {\n  KJ_IF_SOME(accepted, farNative->state.tryGet<Accepted>()) {\n    return (accepted.ws.isAwaitingError());\n  }\n  return false;\n}\n\nbool WebSocket::awaitingHibernatableRelease() {\n  KJ_IF_SOME(accepted, farNative->state.tryGet<Accepted>()) {\n    return (accepted.ws.isAwaitingRelease());\n  }\n  return false;\n}\n\nbool WebSocket::peerIsAwaitingCoupling() {\n  bool answer = false;\n  KJ_IF_SOME(p, peer) {\n    p->runIfAlive([&answer](WebSocket& ws) {\n      answer = ws.farNative->state.is<AwaitingAcceptanceOrCoupling>();\n    });\n  }\n  return answer;\n}\n\nWebSocket::HibernationPackage WebSocket::buildPackageForHibernation() {\n  // TODO(cleanup): It would be great if we could limit this so only the HibernationManager\n  // (or a derived class) could call it.\n  return HibernationPackage{\n    .url = kj::mv(url),\n    .protocol = kj::mv(protocol),\n    .extensions = kj::mv(extensions),\n    .serializedAttachment = kj::mv(serializedAttachment),\n    .maybeTags = kj::none,\n    .closedOutgoingConnection = closedOutgoingForHib,\n    .allowHalfOpen = allowHalfOpen,\n  };\n}\n\nWebSocket::Accepted::WrappedWebSocket::WrappedWebSocket(Hibernatable ws) {\n  inner.init<Hibernatable>(kj::mv(ws));\n}\n\nWebSocket::Accepted::WrappedWebSocket::WrappedWebSocket(kj::Own<kj::WebSocket> ws) {\n  inner.init<kj::Own<kj::WebSocket>>(kj::mv(ws));\n}\n\nkj::WebSocket* WebSocket::Accepted::WrappedWebSocket::operator->() {\n  KJ_SWITCH_ONEOF(inner) {\n    KJ_CASE_ONEOF(owned, kj::Own<kj::WebSocket>) {\n      return owned.get();\n    }\n    KJ_CASE_ONEOF(hibernatable, Hibernatable) {\n      return &hibernatable.ws;\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::WebSocket& WebSocket::Accepted::WrappedWebSocket::operator*() {\n  KJ_SWITCH_ONEOF(inner) {\n    KJ_CASE_ONEOF(owned, kj::Own<kj::WebSocket>) {\n      return *owned;\n    }\n    KJ_CASE_ONEOF(hibernatable, Hibernatable) {\n      return hibernatable.ws;\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::Maybe<kj::Own<kj::WebSocket>&> WebSocket::Accepted::WrappedWebSocket::getIfNotHibernatable() {\n  // The implication of getting nullptr is that this websocket is hibernatable. This is useful\n  // if the caller only ever expects to get a regular websocket, for example, if they are in\n  // any method that should be inaccessible to hibernatable websockets (ex. readLoop).\n  return inner.tryGet<kj::Own<kj::WebSocket>>();\n}\n\nkj::Maybe<WebSocket::Accepted::Hibernatable&> WebSocket::Accepted::WrappedWebSocket::\n    getIfHibernatable() {\n  return inner.tryGet<Hibernatable>();\n}\n\nkj::Array<kj::StringPtr> WebSocket::Accepted::WrappedWebSocket::getHibernatableTags() {\n  KJ_SWITCH_ONEOF(KJ_REQUIRE_NONNULL(inner.tryGet<Hibernatable>()).tagsRef) {\n    KJ_CASE_ONEOF(ref, kj::Array<kj::StringPtr>) {\n      // Tags are still owned by the HibernationManager\n      return kj::heapArray<kj::StringPtr>(ref);\n    }\n    KJ_CASE_ONEOF(arr, kj::Array<kj::String>) {\n      // We have the array already, let's copy it and return.\n      auto cpy = kj::heapArray<kj::StringPtr>(arr.size());\n      for (auto& i: kj::indices(arr)) {\n        cpy[i] = arr[i].asPtr();\n      }\n      return cpy;\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid WebSocket::Accepted::WrappedWebSocket::initiateHibernatableRelease(jsg::Lock& js,\n    kj::Own<kj::WebSocket> ws,\n    kj::Array<kj::String> tags,\n    HibernatableReleaseState state) {\n  auto& hibernatable = KJ_REQUIRE_NONNULL(getIfHibernatable());\n  hibernatable.releaseState = state;\n  // Note that we move the owned kj::WebSocket here.\n  hibernatable.attachedForClose = kj::mv(ws);\n  hibernatable.tagsRef.init<kj::Array<kj::String>>(kj::mv(tags));\n}\n\nbool WebSocket::Accepted::WrappedWebSocket::isAwaitingRelease() {\n  KJ_IF_SOME(ws, getIfHibernatable()) {\n    return (ws.releaseState != HibernatableReleaseState::NONE);\n  }\n  return false;\n}\n\nbool WebSocket::Accepted::WrappedWebSocket::isAwaitingError() {\n  KJ_IF_SOME(ws, getIfHibernatable()) {\n    return (ws.releaseState == HibernatableReleaseState::ERROR);\n  }\n  return false;\n}\n\nbool WebSocket::Accepted::isHibernatable() {\n  return ws.getIfNotHibernatable() == kj::none;\n}\n\nvoid WebSocketPair::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(nullptr, sockets[0]);\n  tracker.trackField(nullptr, sockets[1]);\n}\n\nkj::StringPtr WebSocket::getBinaryType() {\n  return binaryType_ == BinaryType::BLOB ? \"blob\"_kj : \"arraybuffer\"_kj;\n}\n\nvoid WebSocket::setBinaryType(kj::String value) {\n  if (value == \"blob\") {\n    binaryType_ = BinaryType::BLOB;\n  } else if (value == \"arraybuffer\") {\n    binaryType_ = BinaryType::ARRAYBUFFER;\n  }\n  // Per the spec, invalid values are silently ignored — the existing binaryType is retained.\n}\n\nvoid WebSocket::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"url\", url);\n  tracker.trackField(\"protocol\", protocol);\n  tracker.trackField(\"extensions\", extensions);\n  KJ_IF_SOME(attachment, serializedAttachment) {\n    tracker.trackFieldWithSize(\"attachment\", attachment.size());\n  }\n  tracker.trackFieldWithSize(\"IoOwn<Native>\", sizeof(IoOwn<Native>));\n  tracker.trackField(\"error\", error);\n  tracker.trackFieldWithSize(\"IoOwn<OutgoingMessagesMap>\", sizeof(IoOwn<OutgoingMessagesMap>));\n  tracker.trackField(\"autoResponseStatus\", autoResponseStatus);\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/web-socket.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"basics.h\"\n#include \"events.h\"\n\n#include <workerd/io/io-gate.h>\n#include <workerd/io/observer.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/util/checked-queue.h>\n#include <workerd/util/strong-bool.h>\n#include <workerd/util/weak-refs.h>\n\n#include <kj/compat/http.h>\n\n#include <cstdlib>\n#include <list>\n\nnamespace workerd {\nclass ActorObserver;\n}\n\nnamespace workerd::api {\n\nclass Blob;\n\ntemplate <typename T>\nstruct DeferredProxy;\n\nclass CloseEvent: public Event {\n public:\n  CloseEvent(uint code, kj::String reason, bool clean)\n      : Event(\"close\"),\n        code(code),\n        reason(kj::mv(reason)),\n        clean(clean) {}\n  CloseEvent(kj::String type, int code, kj::String reason, bool clean)\n      : Event(kj::mv(type)),\n        code(code),\n        reason(kj::mv(reason)),\n        clean(clean) {}\n\n  struct Initializer {\n    jsg::Optional<int> code;\n    jsg::Optional<jsg::USVString> reason;\n    jsg::Optional<bool> wasClean;\n\n    JSG_STRUCT(code, reason, wasClean);\n    JSG_STRUCT_TS_OVERRIDE(CloseEventInit);\n  };\n  static jsg::Ref<CloseEvent> constructor(\n      jsg::Lock& js, kj::String type, jsg::Optional<Initializer> initializer) {\n    Initializer init = kj::mv(initializer).orDefault({});\n    return js.alloc<CloseEvent>(kj::mv(type), init.code.orDefault(0),\n        kj::mv(init.reason).orDefault(jsg::USVString(kj::str())), init.wasClean.orDefault(false));\n  }\n\n  int getCode() {\n    return code;\n  }\n  kj::StringPtr getReason() {\n    return reason;\n  }\n  bool getWasClean() {\n    return clean;\n  }\n\n  JSG_RESOURCE_TYPE(CloseEvent) {\n    JSG_INHERIT(Event);\n\n    JSG_READONLY_INSTANCE_PROPERTY(code, getCode);\n    JSG_READONLY_INSTANCE_PROPERTY(reason, getReason);\n    JSG_READONLY_INSTANCE_PROPERTY(wasClean, getWasClean);\n\n    JSG_TS_ROOT();\n    // CloseEvent will be referenced from the `WebSocketEventMap` define\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"reason\", reason);\n  }\n\n private:\n  int code;\n  kj::String reason;\n  bool clean;\n};\n\nWD_STRONG_BOOL(AllowHalfOpen);\n\n// The forward declaration is necessary so we can make some\n// WebSocket methods accessible to WebSocketPair via friend declaration.\nclass WebSocket;\n\nclass WebSocketPair: public jsg::Object {\n private:\n  struct IteratorState final {\n    jsg::Ref<WebSocketPair> pair;\n    size_t index = 0;\n\n    void visitForGc(jsg::GcVisitor& visitor) {\n      visitor.visit(pair);\n    }\n\n    JSG_MEMORY_INFO(IteratorState) {\n      tracker.trackField(\"pair\", pair);\n    }\n  };\n\n public:\n  WebSocketPair(jsg::Ref<WebSocket> first, jsg::Ref<WebSocket> second)\n      : sockets{kj::mv(first), kj::mv(second)} {}\n\n  static jsg::Ref<WebSocketPair> constructor(jsg::Lock& js);\n\n  jsg::Ref<WebSocket> getFirst() {\n    return sockets[0].addRef();\n  }\n  jsg::Ref<WebSocket> getSecond() {\n    return sockets[1].addRef();\n  }\n\n  JSG_ITERATOR(PairIterator, entries, jsg::Ref<WebSocket>, IteratorState, iteratorNext);\n\n  JSG_RESOURCE_TYPE(WebSocketPair) {\n    // TODO(soon): These really should be using an indexed property handler rather\n    // than named instance properties but jsg does not yet have support for that.\n    JSG_READONLY_INSTANCE_PROPERTY(0, getFirst);\n    JSG_READONLY_INSTANCE_PROPERTY(1, getSecond);\n    JSG_ITERABLE(entries);\n\n    JSG_TS_OVERRIDE(const WebSocketPair: {\n      new (): { 0: WebSocket; 1: WebSocket };\n    });\n    // Ensure correct typing with `Object.values()`.\n    // Without this override, the generated definition will look like:\n    //\n    // ```ts\n    // declare class WebSocketPair {\n    //   constructor();\n    //   readonly 0: WebSocket;\n    //   readonly 1: WebSocket;\n    // }\n    // ```\n    //\n    // Trying to call `Object.values(new WebSocketPair())` will result\n    // in the following `any` typed values:\n    //\n    // ```ts\n    // const [one, two] = Object.values(new WebSocketPair());\n    //       // ^? const one: any\n    // ```\n    //\n    // With this override in place, `one` and `two` will be typed `WebSocket`.\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n private:\n  jsg::Ref<WebSocket> sockets[2];\n\n  static kj::Maybe<jsg::Ref<WebSocket>> iteratorNext(jsg::Lock& js, IteratorState& state) {\n    if (state.index >= 2) {\n      return kj::none;\n    }\n    return state.pair->sockets[state.index++].addRef();\n  }\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(sockets[0]);\n    visitor.visit(sockets[1]);\n  }\n};\n\nclass WebSocket: public EventTarget {\n private:\n  // Forward declarations.\n  struct PackedWebSocket;\n  struct Native;\n\n public:\n  // WebSocket ready states.\n  static constexpr int READY_STATE_CONNECTING = 0;\n  static constexpr int READY_STATE_OPEN = 1;\n  static constexpr int READY_STATE_CLOSING = 2;\n  static constexpr int READY_STATE_CLOSED = 3;\n\n  // Creates the Native object when we recreate the WebSocket when waking from hibernation.\n  IoOwn<Native> initNative(IoContext& ioContext,\n      kj::WebSocket& ws,\n      kj::Array<kj::StringPtr> tags,\n      bool closedOutgoingConn);\n\n  // Some properties of the `api::WebSocket` that need to survive hibernation. When we initiate\n  // the hibernation process, we want to move these properties out of the `api::WebSocket`.\n  // When we recreate the websocket due to activity, we move the properties back in.\n  struct HibernationPackage {\n    kj::Maybe<kj::String> url;\n    kj::Maybe<kj::String> protocol;\n    kj::Maybe<kj::String> extensions;\n    kj::Maybe<kj::Array<byte>> serializedAttachment;\n\n    // `maybeTags` is only non-empty when we're recreating the api::WebSocket.\n    // We don't need to populate it when hibernating because the tags are already\n    // stored in the HibernationManager.\n    kj::Maybe<kj::Array<kj::StringPtr>> maybeTags;\n\n    // True forever once the JS WebSocket calls `close()`.\n    bool closedOutgoingConnection = false;\n\n    // Whether the WebSocket allows half-open close state.\n    AllowHalfOpen allowHalfOpen = AllowHalfOpen::YES;\n  };\n\n  ~WebSocket() noexcept(false) {\n    weakRef->invalidate();\n  }\n\n  // This WebSocket constructor is only used when WebSockets wake up from hibernation.\n  // It will immediately set the `state` to `Accepted`, but it limits the behavior by specifying it\n  // as `Hibernatable` -- thereby making most api::WebSocket methods inaccessible.\n  WebSocket(jsg::Lock& js, IoContext& ioContext, kj::WebSocket& ws, HibernationPackage package);\n\n  // Similar to how the JS `constructor()` creates a WebSocket, when waking from hibernation\n  // we want to be able to recreate WebSockets from C++ that will be delivered to JS code.\n  static jsg::Ref<WebSocket> hibernatableFromNative(\n      jsg::Lock& js, kj::WebSocket& ws, HibernationPackage package);\n\n  // The JS WebSocket constructor needs to initiate a connection, but we need to return the\n  // WebSocket object to the caller in Javascript immediately. We will defer the connection logic\n  // to the `initConnection` method.\n  WebSocket(jsg::Lock& js, kj::Own<kj::WebSocket> native);\n\n  // The JS WebSocket constructor needs to initiate a connection, but we need to return the\n  // WebSocket object to the caller in Javascript immediately. We will defer the connection logic\n  // to the `initConnection` method.\n  WebSocket(jsg::Lock& js, kj::String url);\n\n  // We initiate a `new WebSocket()` connection and set up a continuation that handles the\n  // response once it's available. This includes assigning the native websocket and dispatching the\n  // relevant `open`/`error` events.\n  void initConnection(jsg::Lock& js, kj::Promise<PackedWebSocket>);\n\n  // Pumps messages from this WebSocket to `other`, and from `other` to this, making sure to\n  // register pending events as appropriate. Used to connect a websocket to a client via an HTTP\n  // response.\n  //\n  // Only one of this or accept() is allowed to be invoked.\n  //\n  // As an exception to the usual KJ convention, it is not necessary for the JavaScript `WebSocket`\n  // object to be kept live while waiting for the promise returned by couple() to complete. Instead,\n  // the promise takes direct ownership of the underlying KJ-native WebSocket (as well as `other`).\n  kj::Promise<DeferredProxy<void>> couple(kj::Own<kj::WebSocket> other, RequestObserver& request);\n\n  // Extract the kj::WebSocket from this api::WebSocket (if applicable). The kj::WebSocket will be\n  // owned elsewhere, but the api::WebSocket will retain a reference.\n  kj::Own<kj::WebSocket> acceptAsHibernatable(kj::Array<kj::StringPtr> tags);\n\n  void tryReleaseNative(jsg::Lock& js);\n\n  // Accesses the tags of the hibernatable websocket.\n  kj::Array<kj::StringPtr> getHibernatableTags();\n\n  enum class HibernatableReleaseState {\n    // The way we release Hibernatable WebSockets slightly differs from regular WebSockets.\n    // We can't access the isolate after the event runs. `NONE` indicates we are not releasing.\n    NONE,\n    CLOSE,\n    ERROR\n  };\n\n  // Called when a Hibernatable WebSocket wants to dispatch a close/error event, this modifies\n  // our `Accepted` state to prepare the state to transition to `Released`.\n  void initiateHibernatableRelease(jsg::Lock& js,\n      kj::Own<kj::WebSocket> ws,\n      kj::Array<kj::String> tags,\n      HibernatableReleaseState releaseState);\n\n  bool awaitingHibernatableError();\n\n  bool awaitingHibernatableRelease();\n\n  // Should only be called on one end of a WebSocketPair.\n  // Relevant for WebSocket Hibernation: the end we return in the Response must be in the\n  // AwaitingAcceptanceOrCoupling state.\n  bool peerIsAwaitingCoupling();\n\n  HibernationPackage buildPackageForHibernation();\n\n  // ---------------------------------------------------------------------------\n  // JS API.\n\n  struct AcceptOptions {\n    jsg::Optional<bool> allowHalfOpen;\n\n    JSG_STRUCT(allowHalfOpen);\n  };\n\n  // Creates a new outbound WebSocket.\n  static jsg::Ref<WebSocket> constructor(jsg::Lock& js,\n      kj::String url,\n      jsg::Optional<kj::OneOf<kj::Array<kj::String>, kj::String>> protocols);\n\n  // Begin delivering events locally.\n  void accept(jsg::Lock& js, jsg::Optional<AcceptOptions> options);\n\n  // Same as accept(), but websockets that are created with `new WebSocket()` in JS cannot call\n  // accept(). Instead, we only permit the C++ constructor to call this \"internal\" version of accept()\n  // so that the websocket can start processing messages once the connection has been established.\n  void internalAccept(jsg::Lock& js, kj::Maybe<kj::Own<InputGate::CriticalSection>> cs);\n\n  // We defer the actual logic of accept() and internalAccept() to this method, since they largely\n  // share code.\n  void startReadLoop(jsg::Lock& js, kj::Maybe<kj::Own<InputGate::CriticalSection>> cs);\n\n  void send(jsg::Lock& js, kj::OneOf<kj::Array<byte>, kj::String> message);\n  void close(jsg::Lock& js, jsg::Optional<int> code, jsg::Optional<jsg::USVString> reason);\n\n  // Used to get/set the attachment for hibernation.\n  // If the object isn't serialized, it will not survive hibernation.\n  void serializeAttachment(jsg::Lock& js, jsg::JsValue attachment);\n\n  // Used to get/set the attachment for hibernation.\n  // If the object isn't serialized, it will not survive hibernation.\n  kj::Maybe<jsg::JsValue> deserializeAttachment(jsg::Lock& js);\n\n  // Used to get/store the last auto request/response timestamp for this WebSocket.\n  // These methods are c++ only and are not exposed to our js interface.\n  // Also used to track hibernatable websockets auto-response sends.\n  void setAutoResponseStatus(kj::Maybe<kj::Date> time, kj::Promise<void> autoResponsePromise);\n\n  // Used to get/store the last auto request/response timestamp for this WebSocket.\n  // These methods are c++ only and are not exposed to our js interface.\n  kj::Maybe<kj::Date> getAutoResponseTimestamp();\n\n  kj::Promise<void> sendAutoResponse(kj::String message, kj::WebSocket& ws);\n\n  int getReadyState();\n\n  bool isAccepted();\n  bool isReleased();\n\n  // For internal use only.\n  // We need to access the underlying KJ WebSocket so we can determine the compression configuration\n  // it uses (if any).\n  kj::Maybe<kj::String> getPreferredExtensions(kj::WebSocket::ExtensionsContext ctx);\n\n  kj::Maybe<kj::StringPtr> getUrl();\n  kj::Maybe<kj::StringPtr> getProtocol();\n  kj::Maybe<kj::StringPtr> getExtensions();\n\n  kj::StringPtr getBinaryType();\n  void setBinaryType(kj::String value);\n\n  JSG_RESOURCE_TYPE(WebSocket, CompatibilityFlags::Reader flags) {\n    JSG_INHERIT(EventTarget);\n    JSG_METHOD(accept);\n    JSG_METHOD(send);\n    JSG_METHOD(close);\n    JSG_METHOD(serializeAttachment);\n    JSG_METHOD(deserializeAttachment);\n\n    JSG_STATIC_CONSTANT(READY_STATE_CONNECTING);\n    JSG_STATIC_CONSTANT_NAMED(CONNECTING, WebSocket::READY_STATE_CONNECTING);\n\n    JSG_STATIC_CONSTANT(READY_STATE_OPEN);\n    JSG_STATIC_CONSTANT_NAMED(OPEN, WebSocket::READY_STATE_OPEN);\n\n    JSG_STATIC_CONSTANT(READY_STATE_CLOSING);\n    JSG_STATIC_CONSTANT_NAMED(CLOSING, WebSocket::READY_STATE_CLOSING);\n\n    JSG_STATIC_CONSTANT(READY_STATE_CLOSED);\n    JSG_STATIC_CONSTANT_NAMED(CLOSED, WebSocket::READY_STATE_CLOSED);\n\n    // Previously, we were setting all properties as instance properties,\n    // which broke the ability to subclass the Event object. With the\n    // compatibility flag set, we instead attach the properties to the\n    // prototype.\n    if (flags.getJsgPropertyOnPrototypeTemplate()) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(readyState, getReadyState);\n      JSG_READONLY_PROTOTYPE_PROPERTY(url, getUrl);\n      JSG_READONLY_PROTOTYPE_PROPERTY(protocol, getProtocol);\n      JSG_READONLY_PROTOTYPE_PROPERTY(extensions, getExtensions);\n      JSG_PROTOTYPE_PROPERTY(binaryType, getBinaryType, setBinaryType);\n    } else {\n      JSG_READONLY_INSTANCE_PROPERTY(readyState, getReadyState);\n      JSG_READONLY_INSTANCE_PROPERTY(url, getUrl);\n      JSG_READONLY_INSTANCE_PROPERTY(protocol, getProtocol);\n      JSG_READONLY_INSTANCE_PROPERTY(extensions, getExtensions);\n      JSG_INSTANCE_PROPERTY(binaryType, getBinaryType, setBinaryType);\n    }\n\n    JSG_TS_DEFINE(type WebSocketEventMap = {\n      close: CloseEvent;\n      message: MessageEvent;\n      open: Event;\n      error: ErrorEvent;\n    });\n    JSG_TS_OVERRIDE(extends EventTarget<WebSocketEventMap> {\n      get binaryType(): \"blob\" | \"arraybuffer\";\n      set binaryType(value: \"blob\" | \"arraybuffer\");\n    });\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n  kj::Own<WeakRef<WebSocket>> addWeakRef() {\n    return weakRef->addRef();\n  }\n\n private:\n  kj::Own<WeakRef<WebSocket>> weakRef;\n  kj::Maybe<kj::String> url;\n  kj::Maybe<kj::String> protocol = kj::String();\n  kj::Maybe<kj::String> extensions = kj::String();\n  // The binaryType attribute per the WHATWG WebSocket spec. Defaults to \"blob\" when the\n  // websocket_standard_binary_type compat flag is enabled, \"arraybuffer\" otherwise.\n  enum class BinaryType { BLOB, ARRAYBUFFER };\n  BinaryType binaryType_ = BinaryType::ARRAYBUFFER;\n\n  kj::Maybe<kj::Date> autoResponseTimestamp;\n  // All WebSockets have this property. It starts out null but can\n  // be assigned to any serializable value. The property will survive hibernation.\n  // We have to serialize each time we call the setter so we can determine if the size limit\n  // has been breached.\n  kj::Maybe<kj::Array<byte>> serializedAttachment;\n\n  // Tracks farNative->closedOutgoing, but we need to access it when we trigger Hibernation so it\n  // cannot be `IoOwn`ed as `farNative` is. This informs the HibernatableWebSocket if we called\n  // `close()`, thereby preventing calls to `send()` even after we wake from hibernation.\n  bool closedOutgoingForHib = false;\n\n  // When YES, a server-initiated close does NOT automatically send a reciprocal close frame,\n  // leaving readyState as CLOSING (2) when the close event fires. The application is then\n  // responsible for calling close() explicitly. When NO (spec-compliant default with the\n  // web_socket_auto_reply_to_close compat flag), a close reply is sent automatically and\n  // readyState is CLOSED (3) when the close event fires.\n  // Default is YES (legacy behavior); overridden from the compat flag at construction time.\n  AllowHalfOpen allowHalfOpen = AllowHalfOpen::YES;\n\n  // Maximum allowed size for WebSocket messages\n  inline static const size_t SUGGESTED_MAX_MESSAGE_SIZE = 1u << 20;\n\n  // Maximum size of a WebSocket attachment.\n  inline static const size_t MAX_ATTACHMENT_SIZE = 1024 * 16;\n\n  struct AwaitingConnection {\n    // A canceler associated with the pending websocket connection for `new Websocket()`.\n    kj::Canceler canceler;\n  };\n  struct AwaitingAcceptanceOrCoupling {\n    explicit AwaitingAcceptanceOrCoupling(kj::Own<kj::WebSocket> ws): ws(kj::mv(ws)) {}\n    kj::Own<kj::WebSocket> ws;\n  };\n  struct Accepted {\n    // A `Hibernatable` WebSocket shares a sub-set of behavior that's already implemented for an\n    // `Accepted` WebSocket, so we can think of it a sub-state.\n    struct Hibernatable {\n      kj::WebSocket& ws;\n      // If we have initiated a hibernatable error/close event, we need to take back ownership of\n      // the kj::WebSocket so any final queued messages will deliver. We store this owned websocket\n      // in `attachedForClose`. Since the `ws` reference is still valid, we prevent usage of\n      // `attachedForClose` directly in favor of using continuing to use `ws` directly.\n      kj::Maybe<kj::Own<void>> attachedForClose;\n\n      // We can't move the state to Released after the Hibernatable Close/Error event runs, since\n      // we don't have a request on the thread by the time the event completes.\n      //\n      // If we are \"releasing\", we may prevent the websocket from doing certain things like calling\n      // send/close. We're more restrictive if we're delivering an Error than delivering a Close.\n      HibernatableReleaseState releaseState = HibernatableReleaseState::NONE;\n\n      // There are two possible states for tagsRef:\n      //  1. kj::Array<kj::StringPtr>\n      //      - Tags are owned by the HibernationManager, we just reference them to save memory.\n      //  2. kj::Array<kj::String>\n      //      - We're going to be dispatching a Close or an Error event, i.e. the\n      //        HibernatableWebSocket is free to go away. We can no longer rely on tags stored in\n      //        the HibernationManager, so instead we copy the data into the api::WebSocket.\n      //\n      // We could just copy all tags into api::WebSocet every time we reactivate/wake from\n      // hibernation, but it could add up to 2.56KB of memory for each websocket.\n      // With a maximum of 32k websockets, that could put a lot of memory pressure on the DO.\n      kj::OneOf<kj::Array<kj::StringPtr>, kj::Array<kj::String>> tagsRef;\n    };\n\n    explicit Accepted(kj::Own<kj::WebSocket> ws, Native& native, IoContext& context);\n    explicit Accepted(Hibernatable ws, Native& native, IoContext& context);\n\n    ~Accepted() noexcept(false);\n\n    // A simple wrapper to make it easier to access the underlying kj::WebSocket.\n    class WrappedWebSocket {\n     public:\n      explicit WrappedWebSocket(Hibernatable ws);\n      explicit WrappedWebSocket(kj::Own<kj::WebSocket> ws);\n\n      kj::WebSocket* operator->();\n\n      kj::WebSocket& operator*();\n\n      kj::Maybe<kj::Own<kj::WebSocket>&> getIfNotHibernatable();\n      kj::Maybe<Hibernatable&> getIfHibernatable();\n      kj::Array<kj::StringPtr> getHibernatableTags();\n\n      // Transitions our Hibernatable websocket to a \"Releasing\" state.\n      // The websocket will transition to `Released` when convenient.\n      void initiateHibernatableRelease(jsg::Lock& js,\n          kj::Own<kj::WebSocket> ws,\n          kj::Array<kj::String> tags,\n          HibernatableReleaseState state);\n\n      bool isAwaitingRelease();\n      bool isAwaitingError();\n\n     private:\n      kj::OneOf<kj::Own<kj::WebSocket>, Hibernatable> inner;\n    };\n\n    WrappedWebSocket ws;\n\n    bool isHibernatable();\n\n    kj::Promise<void> createAbortTask(Native& native, IoContext& context);\n    // Listens for ws->whenAborted() and possibly triggers a proactive shutdown.\n    kj::Promise<void> whenAbortedTask = nullptr;\n\n    kj::Maybe<kj::Own<ActorObserver>> actorMetrics;\n\n    // This canceler wraps the pump loop as a precaution to make sure we can't exit the Accepted\n    // state with a pump task still happening asynchronously. In practice the canceler should usually\n    // be empty when destroyed because we do not leave the Accepted state if we're still pumping.\n    // Even in the case of IoContext premature cancellation, the pump task should be canceled\n    // by the IoContext before the Canceler is destroyed.\n    kj::Canceler canceler;\n  };\n\n  struct Released {};\n  using NativeState =\n      kj::OneOf<AwaitingConnection, AwaitingAcceptanceOrCoupling, Accepted, Released>;\n  friend kj::StringPtr KJ_STRINGIFY(const NativeState&);\n\n  struct Native {\n    NativeState state;\n\n    // Is there currently a task running to pump outgoing messages?\n    bool isPumping = false;\n\n    // Has a Close message been enqueued for send? (It may still be in outgoingMessages. Check\n    // closedOutgoing && !isPumping to check if it has gone out.)\n    bool closedOutgoing = false;\n\n    // Has a Close message been received, or has a premature disconnection occurred?\n    bool closedIncoming = false;\n\n    // Have we detected that the peer has stopped accepting messages? We may want to clean up more\n    // proactively in this case.\n    bool outgoingAborted = false;\n  };\n\n  // The underlying native WebSocket (or a promise that will emplace one).\n  //\n  // The state transitions look like so:\n  // - Starts as `AwaitingConnection` if the `WebSocket(url ...)` ctor is used.\n  // - Starts as `AwaitingAcceptanceOrCoupling` if the `WebSocket(native)` ctor is used.\n  // - Transitions from `AwaitingConnection` to `AwaitingAcceptanceOrCoupling` when the native\n  //   connection is established and to `Accepted` once the read loop starts.\n  // - Transitions from `AwaitingConnection` to `Released` when connection establishment fails.\n  // - Transitions from `AwaitingAcceptanceOrCoupling` to `Accepted` when it is accepted.\n  // - Transitions from `AwaitingAcceptanceOrCoupling` to `Released` when it is coupled to another\n  //   web socket.\n  // - Transitions from `Accepted` to `Released` when outgoing pump is done and either both\n  //   directions have seen \"close\" messages or an error has occurred.\n  IoOwn<Native> farNative;\n\n  // If any error has occurred.\n  kj::Maybe<jsg::JsRef<jsg::JsValue>> error;\n\n  struct GatedMessage {\n    kj::Maybe<kj::Promise<void>> outputLock;  // must wait for this before actually sending\n    kj::WebSocket::Message message;\n    size_t pendingAutoResponses = 0;\n  };\n  using OutgoingMessagesMap = kj::Table<GatedMessage, kj::InsertionOrderIndex>;\n  // Queue of messages to be sent. This is wrapped in an IoOwn so that the pump loop can safely\n  // access the map without locking the isolate.\n  IoOwn<OutgoingMessagesMap> outgoingMessages;\n\n  // Keep track of current hibernatable websockets auto-response status to avoid racing\n  // between regular websocket messages, and auto-responses.\n  struct AutoResponse {\n    kj::Promise<void> ongoingAutoResponse = kj::READY_NOW;\n    workerd::util::Queue<kj::String> pendingAutoResponseDeque;\n    size_t queuedAutoResponses = 0;\n    bool isPumping = false;\n    bool isClosed = false;\n\n    JSG_MEMORY_INFO(AutoResponse) {\n      tracker.trackFieldWithSize(\"ongoingAutoResponse\", sizeof(kj::Promise<void>));\n      pendingAutoResponseDeque.forEach(\n          [&](const kj::String& message) { tracker.trackField(nullptr, message); });\n    }\n  };\n\n  AutoResponse autoResponseStatus;\n\n  kj::Maybe<kj::Own<WebSocketObserver>> observer;\n\n  // Contains a websocket and possibly some data from the WebSocketResponse headers.\n  struct PackedWebSocket {\n    kj::Own<kj::WebSocket> ws;\n    kj::Maybe<kj::String> proto;\n    kj::Maybe<kj::String> extensions;\n  };\n\n  // So that each end of a WebSocketPair can keep track of its peer.\n  // We use a weak ref to track the peer to avoid having a strong ref cycle\n  // between the two WebSocket instances that would cause them to leak. This\n  // can mean, however, that it's possible for one of the peers to be garbage\n  // collected while the other still exists. This should be fairly unusual tho.\n  kj::Maybe<kj::Own<WeakRef<WebSocket>>> peer;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(error);\n  }\n\n  void setPeer(kj::Own<WeakRef<WebSocket>> peer);\n\n  friend jsg::Ref<WebSocketPair> WebSocketPair::constructor(jsg::Lock&);\n\n  void dispatchOpen(jsg::Lock& js);\n\n  void ensurePumping(jsg::Lock& js);\n\n  // Returns the number of pending auto-responses that should be sent before the next outgoing\n  // message, and advances the queuedAutoResponses counter. Called each time a GatedMessage is\n  // inserted into outgoingMessages to guarantee auto-response ordering.\n  size_t getPendingAutoResponseCount();\n\n  // Write messages from `outgoingMessages` into `ws`.\n  //\n  // These are not necessarily called under isolate lock, but they are called on the given\n  // context's thread. They are declared `static` to prove they don't access the JavaScript\n  // object's members in a thread-unsafe way. `outgoingMessages` and `ws` are both `IoOwn`ed\n  // objects so are safe to access from the thread without the isolate lock. The whole task is\n  // owned by the `IoContext` so it'll be canceled if the `IoContext` is destroyed.\n  static kj::Promise<void> pump(IoContext& context,\n      OutgoingMessagesMap& outgoingMessages,\n      kj::WebSocket& ws,\n      Native& native,\n      AutoResponse& autoResponse,\n      kj::Maybe<kj::Own<WebSocketObserver>>& observer);\n\n  kj::Promise<kj::Maybe<kj::Exception>> readLoop(\n      kj::Maybe<kj::Own<InputGate::CriticalSection>> cs, size_t maxMessageSize);\n\n  void reportError(jsg::Lock& js, kj::Exception&& e);\n  void reportError(jsg::Lock& js, jsg::JsRef<jsg::JsValue> err);\n\n  void assertNoError(jsg::Lock& js);\n};\n\n#define EW_WEBSOCKET_ISOLATE_TYPES                                                                 \\\n  api::CloseEvent, api::CloseEvent::Initializer, api::WebSocket, api::WebSocket::AcceptOptions,    \\\n      api::WebSocketPair, api::WebSocketPair::PairIterator,                                        \\\n      api::WebSocketPair::PairIterator::                                                           \\\n          Next  // The list of websocket.h types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/worker-loader.c++",
    "content": "#include \"worker-loader.h\"\n\n#include <workerd/api/actor.h>\n#include <workerd/api/http.h>\n#include <workerd/io/compatibility-date.h>\n#include <workerd/io/features.h>\n#include <workerd/io/io-context.h>\n\n#include <capnp/message.h>\n\nnamespace workerd::api {\n\njsg::Ref<Fetcher> WorkerStub::getEntrypoint(jsg::Lock& js,\n    jsg::Optional<kj::Maybe<kj::String>> name,\n    jsg::Optional<EntrypointOptions> options) {\n  Frankenvalue props;\n\n  KJ_IF_SOME(o, options) {\n    KJ_IF_SOME(p, o.props) {\n      props = Frankenvalue::fromJs(js, p.getHandle(js));\n    }\n  }\n\n  kj::Maybe<kj::String> entrypointName;\n  KJ_IF_SOME(n, name) {\n    KJ_IF_SOME(n2, n) {\n      if (n2 != \"default\"_kj) {\n        entrypointName = kj::mv(n2);\n      }\n    }\n  }\n\n  auto subreqChannel = channel->getEntrypoint(kj::mv(entrypointName), kj::mv(props));\n  return js.alloc<Fetcher>(IoContext::current().addObject(kj::mv(subreqChannel)));\n}\n\njsg::Ref<DurableObjectClass> WorkerStub::getDurableObjectClass(jsg::Lock& js,\n    jsg::Optional<kj::Maybe<kj::String>> name,\n    jsg::Optional<EntrypointOptions> options) {\n  Frankenvalue props;\n\n  KJ_IF_SOME(o, options) {\n    KJ_IF_SOME(p, o.props) {\n      props = Frankenvalue::fromJs(js, p.getHandle(js));\n    }\n  }\n\n  kj::Maybe<kj::String> entrypointName;\n  KJ_IF_SOME(n, name) {\n    KJ_IF_SOME(n2, n) {\n      if (n2 != \"default\"_kj) {\n        entrypointName = kj::mv(n2);\n      }\n    }\n  }\n\n  return js.alloc<DurableObjectClass>(IoContext::current().addObject(\n      channel->getActorClass(kj::mv(entrypointName), kj::mv(props))));\n}\n\njsg::Ref<WorkerStub> WorkerLoader::get(\n    jsg::Lock& js, kj::Maybe<kj::String> name, jsg::Function<jsg::Promise<WorkerCode>()> getCode) {\n  auto& ioctx = IoContext::current();\n\n  auto reenterAndGetCode = ioctx.makeReentryCallback(\n      [&ioctx, getCode = kj::mv(getCode), compatDateValidation = compatDateValidation](\n          jsg::Lock& js) mutable {\n    return getCode(js).then(\n        js, [&ioctx, compatDateValidation](jsg::Lock& js, WorkerCode code) -> DynamicWorkerSource {\n      return toDynamicWorkerSource(js, ioctx, compatDateValidation, kj::mv(code));\n    });\n  });\n\n  auto isolateChannel =\n      ioctx.getIoChannelFactory().loadIsolate(channel, kj::mv(name), kj::mv(reenterAndGetCode));\n\n  return js.alloc<WorkerStub>(ioctx.addObject(kj::mv(isolateChannel)));\n}\n\njsg::Ref<WorkerStub> WorkerLoader::load(jsg::Lock& js, WorkerCode code) {\n  auto& ioctx = IoContext::current();\n\n  auto source = toDynamicWorkerSource(js, ioctx, compatDateValidation, kj::mv(code));\n\n  // Annoyingly, the callback we pass to `loadIsolate()` technically may be called any number of\n  // times. Yes, even though we aren't providing an ID. The runtime can actually evict the isolate\n  // while a stub still exists, as long as there is no active request on the stub, and then\n  // recreate the isolate on the next request. Moreover, it may ultimately destroy the `ownContent`\n  // in another thread, so we need to use atomic refcounting on it. Ugh!\n  struct OwnContentWrapper: public kj::AtomicRefcounted {\n    kj::Own<void> content;\n    OwnContentWrapper(kj::Own<void> content): content(kj::mv(content)) {}\n  };\n  auto ownContentWrapper = kj::atomicRefcounted<OwnContentWrapper>(kj::mv(source.ownContent));\n\n  auto isolateChannel = ioctx.getIoChannelFactory().loadIsolate(channel, kj::none,\n      [source = kj::mv(source), ownContentWrapper = kj::mv(ownContentWrapper)]() mutable {\n    return source.clone(kj::atomicAddRef(*ownContentWrapper));\n  });\n\n  return js.alloc<WorkerStub>(ioctx.addObject(kj::mv(isolateChannel)));\n}\n\nDynamicWorkerSource WorkerLoader::toDynamicWorkerSource(jsg::Lock& js,\n    IoContext& ioctx,\n    CompatibilityDateValidation compatDateValidation,\n    WorkerCode code) {\n  auto extractedSource = extractSource(js, code);\n  auto ownCompatFlags = extractCompatFlags(js, code, compatDateValidation);\n  CompatibilityFlags::Reader compatFlags = *ownCompatFlags;\n\n  Frankenvalue env;\n  KJ_IF_SOME(codeEnv, code.env) {\n    env = Frankenvalue::fromJs(js, codeEnv.getHandle(js));\n  }\n\n  kj::Maybe<kj::Own<IoChannelFactory::SubrequestChannel>> globalOutbound;\n  KJ_IF_SOME(maybeOut, code.globalOutbound) {\n    KJ_IF_SOME(out, maybeOut) {\n      auto channel = out->getSubrequestChannel(ioctx);\n      channel->requireAllowsTransfer();\n      globalOutbound = kj::mv(channel);\n    } else {\n      // Application passed `null` to disable internet access. Leave `globalOutbound` as\n      // `kj::none`.\n    }\n  } else {\n    // Inherit the calling worker's global outbound channel.\n    //\n    // Note we don't need to enforce transferrability in this case because if it was the global\n    // outbound of the parent, it must be OK to be the global outbound of the child.\n    globalOutbound =\n        ioctx.getIoChannelFactory().getSubrequestChannel(IoContext::NULL_CLIENT_CHANNEL);\n  }\n\n  kj::Array<kj::Own<IoChannelFactory::SubrequestChannel>> tailChannels;\n  KJ_IF_SOME(tails, code.tails) {\n    tailChannels = KJ_MAP(tail, tails) {\n      auto channel = tail->getSubrequestChannel(ioctx);\n      channel->requireAllowsTransfer();\n      return kj::mv(channel);\n    };\n  }\n\n  kj::Array<kj::Own<IoChannelFactory::SubrequestChannel>> streamingTailChannels;\n  KJ_IF_SOME(streamingTails, code.streamingTails) {\n    JSG_REQUIRE(code.allowExperimental.orDefault(false), Error,\n        \"Streaming tail workers are experimental. You must pass the option \"\n        \"'allowExperimental: true' to the worker loader to use them\");\n\n    streamingTailChannels = KJ_MAP(tail, streamingTails) {\n      auto channel = tail->getSubrequestChannel(ioctx);\n      channel->requireAllowsTransfer();\n      return kj::mv(channel);\n    };\n  }\n\n  return {.source = kj::mv(extractedSource),\n    .compatibilityFlags = compatFlags,\n    .env = kj::mv(env),\n    .globalOutbound = kj::mv(globalOutbound),\n    .tails = kj::mv(tailChannels),\n    .streamingTails = kj::mv(streamingTailChannels),\n    .ownContent = ownCompatFlags.attach(kj::mv(code.modules), kj::mv(code.mainModule)),\n    .ownContentIsRpcResponse = false};\n}\n\nWorker::Script::Source WorkerLoader::extractSource(jsg::Lock& js, WorkerCode& code) {\n  JSG_REQUIRE(code.modules.fields.size() > 0, TypeError,\n      \"Dynamic Worker code must contain at least one module.\");\n\n  auto modules = KJ_MAP(entry, code.modules.fields) -> Worker::Script::Module {\n    KJ_SWITCH_ONEOF(entry.value) {\n      KJ_CASE_ONEOF(text, kj::String) {\n        if (entry.name.endsWith(\".py\"_kj)) {\n          return {\n            .name = entry.name,\n            .content = Worker::Script::PythonModule{.body = text},\n          };\n        }\n\n        if (entry.name.endsWith(\".js\"_kj)) {\n          return {\n            .name = entry.name,\n            .content = Worker::Script::EsModule{.body = text},\n          };\n        }\n\n        JSG_FAIL_REQUIRE(TypeError,\n            \"Module name must end with '.js' or '.py' (or the content must be an object \",\n            \"indicating the type explicitly). Got: \", entry.name);\n      }\n      KJ_CASE_ONEOF(module, Module) {\n        uint fieldCount = (module.js != kj::none) + (module.cjs != kj::none) +\n            (module.text != kj::none) + (module.data != kj::none) + (module.json != kj::none) +\n            (module.py != kj::none) + (module.wasm != kj::none);\n        JSG_REQUIRE(fieldCount == 1, TypeError,\n            \"Each module must contain exactly one of 'js', 'cjs', 'text', 'data', 'json', 'py', or 'wasm'. \"\n            \"Module '\",\n            entry.name, \"' contained \", fieldCount, \" properties.\");\n\n        return {.name = entry.name, .content = [&]() -> Worker::Script::ModuleContent {\n          KJ_IF_SOME(js, module.js) {\n            // TODO: this might need typescript transpilation too.\n            return Worker::Script::EsModule{.body = js};\n          } else KJ_IF_SOME(cjs, module.cjs) {\n            return Worker::Script::CommonJsModule{.body = cjs};\n          } else KJ_IF_SOME(text, module.text) {\n            return Worker::Script::TextModule{.body = text};\n          } else KJ_IF_SOME(data, module.data) {\n            return Worker::Script::DataModule{.body = data};\n          } else KJ_IF_SOME(json, module.json) {\n            kj::StringPtr serialized =\n                module.serializedJson.emplace(js.serializeJson(kj::mv(json)));\n            return Worker::Script::JsonModule{.body = serialized};\n          } else KJ_IF_SOME(py, module.py) {\n            return Worker::Script::PythonModule{.body = py};\n          } else KJ_IF_SOME(wasm, module.wasm) {\n            return Worker::Script::WasmModule{.body = wasm};\n          } else {\n            KJ_UNREACHABLE;\n          }\n        }()};\n      }\n    }\n    KJ_UNREACHABLE;\n  };\n\n  bool isPython = code.mainModule.endsWith(\".py\"_kj);\n  // Disallow Python modules when the main module is a JS module, and vice versa.\n  for (auto& module: modules) {\n    auto isJsModule = module.content.is<Worker::Script::EsModule>() ||\n        module.content.is<Worker::Script::CommonJsModule>();\n    if (isPython && isJsModule) {\n      JSG_FAIL_REQUIRE(TypeError, \"Module \\\"\", module.name,\n          \"\\\" is a JS module, but the main module is a Python module.\");\n    }\n    auto isPythonModule = module.content.is<Worker::Script::PythonModule>();\n    if (!isPython && isPythonModule) {\n      JSG_FAIL_REQUIRE(TypeError, \"Module \\\"\", module.name,\n          \"\\\" is a Python module, but the main module isn't a Python module.\");\n    }\n  }\n\n  return Worker::Script::ModulesSource{\n    .mainModule = code.mainModule,\n    .modules = kj::mv(modules),\n    .isPython = isPython,\n  };\n}\n\nkj::Own<CompatibilityFlags::Reader> WorkerLoader::extractCompatFlags(\n    jsg::Lock& js, WorkerCode& code, CompatibilityDateValidation compatDateValidation) {\n  bool allowExperimental = code.allowExperimental.orDefault(false);\n  if (!FeatureFlags::get(js).getWorkerdExperimental()) {\n    JSG_REQUIRE(!allowExperimental, Error,\n        \"'allowExperimental' is only allowed when the calling worker has the 'experimental' \"\n        \"compat flag set.\");\n  }\n\n  kj::ArrayPtr<const kj::String> compatFlags;\n  KJ_IF_SOME(f, code.compatibilityFlags) {\n    compatFlags = f;\n  }\n\n  capnp::word scratch[capnp::sizeInWords<CompatibilityFlags>() + 4]{};\n  capnp::MallocMessageBuilder compatFlagsMessage(scratch);\n  auto compatFlagsBuilder = compatFlagsMessage.getRoot<CompatibilityFlags>();\n\n  SimpleWorkerErrorReporter errorReporter;\n\n  compileCompatibilityFlags(code.compatibilityDate, compatFlags, compatFlagsBuilder, errorReporter,\n      allowExperimental, compatDateValidation);\n\n  if (!errorReporter.errors.empty()) {\n    JSG_FAIL_REQUIRE(Error, errorReporter.errors.front());\n  }\n\n  return capnp::clone(compatFlagsBuilder.asReader());\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/worker-loader.h",
    "content": "#pragma once\n\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/io/compatibility-date.h>\n#include <workerd/io/io-channels.h>\n#include <workerd/io/io-own.h>\n#include <workerd/io/worker.h>\n#include <workerd/jsg/setup.h>\n\nnamespace workerd::api {\n\nclass Fetcher;\nclass DurableObjectClass;\n\n// JS stub pointing to a remote Worker loaded using WorkerLoader. This is not a stub for a specific\n// entrypoint, but instead the entire Worker, allowing the caller to call any entrypoint (and\n// specify arbitrary props).\nclass WorkerStub: public jsg::Object {\n public:\n  WorkerStub(IoOwn<WorkerStubChannel> channel): channel(kj::mv(channel)) {}\n\n  struct EntrypointOptions {\n    jsg::Optional<jsg::JsRef<jsg::JsObject>> props;\n\n    JSG_STRUCT(props);\n  };\n\n  jsg::Ref<Fetcher> getEntrypoint(jsg::Lock& js,\n      jsg::Optional<kj::Maybe<kj::String>> name,\n      jsg::Optional<EntrypointOptions> options);\n  jsg::Ref<DurableObjectClass> getDurableObjectClass(jsg::Lock& js,\n      jsg::Optional<kj::Maybe<kj::String>> name,\n      jsg::Optional<EntrypointOptions> options);\n\n  JSG_RESOURCE_TYPE(WorkerStub, CompatibilityFlags::Reader flags) {\n    JSG_METHOD(getEntrypoint);\n\n    if (flags.getWorkerdExperimental()) {\n      // Facets are experimental.\n      JSG_METHOD(getDurableObjectClass);\n\n      JSG_TS_OVERRIDE({\n        getEntrypoint<T extends Rpc.WorkerEntrypointBranded | undefined>(\n            name?: string, options?: WorkerStubEntrypointOptions): Fetcher<T>;\n        getDurableObjectClass<T extends Rpc.DurableObjectBranded | undefined>(\n            name?: string, options?: WorkerStubEntrypointOptions): DurableObjectClass<T>;\n      });\n    } else {\n      JSG_TS_OVERRIDE({\n        getEntrypoint<T extends Rpc.WorkerEntrypointBranded | undefined>(\n            name?: string, options?: WorkerStubEntrypointOptions): Fetcher<T>;\n      });\n    }\n  }\n\n private:\n  IoOwn<WorkerStubChannel> channel;\n};\n\n// JS interface for worker loader binding.\nclass WorkerLoader: public jsg::Object {\n public:\n  // Create a WorkerLoader backed by the given I/O channel.\n  //\n  // `compatDateValidation` will differ between workerd vs. production.\n  explicit WorkerLoader(uint channel, CompatibilityDateValidation compatDateValidation)\n      : channel(channel),\n        compatDateValidation(compatDateValidation) {}\n\n  struct Module {\n    // Exactly one must be filled in.\n    jsg::Optional<kj::String> js;               // ES module\n    jsg::Optional<kj::String> cjs;              // Common JS module\n    jsg::Optional<kj::String> text;             // text blob, imports as a string\n    jsg::Optional<kj::Array<const byte>> data;  // byte blob, imports as ArrayBuffer\n    jsg::Optional<jsg::Value> json;             // arbitrary JS value, will be serialized to JSON\n                                                // and then parsed again when imported\n    jsg::Optional<kj::String> py;               // Python module\n    jsg::Optional<kj::Array<const byte>> wasm;  // compiled WASM module\n\n    JSG_STRUCT(js, cjs, text, data, json, py, wasm);\n\n    // HACK: When we serialize the JSON in extractSource() we need to place the owned kj::String\n    //   somewhere since Worker::Script::Source only gets a kj::StringPtr.\n    kj::Maybe<kj::String> serializedJson;\n  };\n\n  struct WorkerCode {\n    kj::String compatibilityDate;\n    jsg::Optional<kj::Array<kj::String>> compatibilityFlags;\n    jsg::Optional<bool> allowExperimental = false;\n\n    kj::String mainModule;\n\n    // Modules are specified as an object mapping names to content. If the content is just a\n    // string, an ES module is assumed. If it's an object, the type of module is determined\n    // based on which property is set.\n    jsg::Dict<kj::OneOf<Module, kj::String>> modules;\n\n    // Any RPC-serializable value!\n    jsg::Optional<jsg::JsRef<jsg::JsObject>> env;\n\n    // `Fetcher` (e.g. service binding) representing the loaded worker's global outbound.\n    //\n    // If omitted, inherit the current worker's global outbound.\n    //\n    // If `null`, block the global outbound (all requests throw errors).\n    jsg::Optional<kj::Maybe<jsg::Ref<Fetcher>>> globalOutbound;\n\n    // Specify tail workers.\n    jsg::Optional<kj::Array<jsg::Ref<Fetcher>>> tails;\n    jsg::Optional<kj::Array<jsg::Ref<Fetcher>>> streamingTails;\n\n    // TODO(someday): cache API outbound?\n\n    JSG_STRUCT(compatibilityDate,\n        compatibilityFlags,\n        allowExperimental,\n        mainModule,\n        modules,\n        env,\n        globalOutbound,\n        tails,\n        streamingTails);\n  };\n\n  jsg::Ref<WorkerStub> get(\n      jsg::Lock& js, kj::Maybe<kj::String> name, jsg::Function<jsg::Promise<WorkerCode>()> getCode);\n\n  // Shortcut for `get(null, () => code)`.\n  jsg::Ref<WorkerStub> load(jsg::Lock& js, WorkerCode code);\n\n  JSG_RESOURCE_TYPE(WorkerLoader) {\n    JSG_METHOD(get);\n    JSG_METHOD(load);\n\n    JSG_TS_ROOT();\n  }\n\n private:\n  uint channel;\n  CompatibilityDateValidation compatDateValidation;\n\n  static DynamicWorkerSource toDynamicWorkerSource(jsg::Lock& js,\n      IoContext& ioctx,\n      CompatibilityDateValidation compatDateValidation,\n      WorkerCode code);\n\n  static Worker::Script::Source extractSource(jsg::Lock& js, WorkerCode& code);\n  static kj::Own<CompatibilityFlags::Reader> extractCompatFlags(\n      jsg::Lock& js, WorkerCode& code, CompatibilityDateValidation compatDateValidation);\n\n  kj::Promise<kj::Own<const Worker>> startWorker(\n      Worker::Script::Source extractedSource, CompatibilityFlags::Reader compatibilityFlags);\n};\n\n#define EW_WORKER_LOADER_ISOLATE_TYPES                                                             \\\n  api::WorkerStub, api::WorkerStub::EntrypointOptions, api::WorkerLoader,                          \\\n      api::WorkerLoader::Module, api::WorkerLoader::WorkerCode\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/worker-rpc.c++",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/api/actor-state.h>\n#include <workerd/api/global-scope.h>\n#include <workerd/api/worker-rpc.h>\n#include <workerd/io/features.h>\n#include <workerd/io/tracer.h>\n#include <workerd/jsg/ser.h>\n#include <workerd/util/autogate.h>\n#include <workerd/util/completion-membrane.h>\n\n#include <capnp/membrane.h>\n\nnamespace workerd::api {\n\nnamespace {\n\nusing StreamSinkFulfiller = kj::Own<kj::PromiseFulfiller<rpc::JsValue::StreamSink::Client>>;\n\n}  // namespace\n\n// Implementation of StreamSink RPC interface. The stream sender calls `startStream()` when\n// serializing each stream, and the recipient calls `setSlot()` when deserializing streams to\n// provide the appropriate destination capability. This class is designed to allow these two\n// calls to happen in either order for each slot.\nclass StreamSinkImpl final: public rpc::JsValue::StreamSink::Server, public kj::Refcounted {\n public:\n  ~StreamSinkImpl() noexcept(false) {\n    for (auto& slot: table) {\n      KJ_IF_SOME(f, slot.tryGet<StreamFulfiller>()) {\n        f->reject(KJ_EXCEPTION(FAILED, \"expected startStream() was never received\"));\n      }\n    }\n  }\n\n  void setSlot(uint i, capnp::Capability::Client stream) {\n    if (table.size() <= i) table.resize(i + 1);\n\n    if (table[i] == nullptr) {\n      table[i] = kj::mv(stream);\n    } else KJ_SWITCH_ONEOF(table[i]) {\n      KJ_CASE_ONEOF(stream, capnp::Capability::Client) {\n        KJ_FAIL_REQUIRE(\"setSlot() tried to set the same slot twice\", i);\n      }\n      KJ_CASE_ONEOF(fulfiller, StreamFulfiller) {\n        fulfiller->fulfill(kj::mv(stream));\n        table[i] = Consumed();\n      }\n      KJ_CASE_ONEOF(_, Consumed) {\n        KJ_FAIL_REQUIRE(\"setSlot() tried to set the same slot twice\", i);\n      }\n    }\n  }\n\n  kj::Promise<void> startStream(StartStreamContext context) override {\n    uint i = context.getParams().getExternalIndex();\n\n    if (table.size() <= i) {\n      // guard against ridiculous table allocation\n      JSG_REQUIRE(i < 1024, Error, \"Too many streams in one message.\");\n      table.resize(i + 1);\n    }\n\n    if (table[i] == nullptr) {\n      auto paf = kj::newPromiseAndFulfiller<capnp::Capability::Client>();\n      table[i] = kj::mv(paf.fulfiller);\n      context.getResults(capnp::MessageSize{4, 1}).setStream(kj::mv(paf.promise));\n    } else KJ_SWITCH_ONEOF(table[i]) {\n      KJ_CASE_ONEOF(stream, capnp::Capability::Client) {\n        context.getResults(capnp::MessageSize{4, 1}).setStream(kj::mv(stream));\n        table[i] = Consumed();\n      }\n      KJ_CASE_ONEOF(fulfiller, StreamFulfiller) {\n        KJ_FAIL_REQUIRE(\"startStream() tried to start the same stream twice\", i);\n      }\n      KJ_CASE_ONEOF(_, Consumed) {\n        KJ_FAIL_REQUIRE(\"startStream() tried to start the same stream twice\", i);\n      }\n    }\n\n    return kj::READY_NOW;\n  }\n\n private:\n  using StreamFulfiller = kj::Own<kj::PromiseFulfiller<capnp::Capability::Client>>;\n  struct Consumed {};\n\n  // Each slot starts out null (uninitialized). It becomes a Capability::Client if setSlot() is\n  // called first, or a StreamFulfiller if startStream() is called first. It becomes `Consumed`\n  // when the other method is called.\n  // HACK: Slots in the table take advantage of the little-known fact that OneOf has a \"null\"\n  //   value, which is the value a OneOf has when default-initialized. This is useful because we\n  //   don't want to explicitly initialize skipped slots. Maybe<OneOf> would be another option\n  //   here, but would add 8 bytes to every slot just to store a boolean... feels bloated. There\n  //   are only two methods in this class so I think it's OK.\n  using Slot = kj::OneOf<capnp::Capability::Client, StreamFulfiller, Consumed>;\n\n  kj::Vector<Slot> table;\n};\n\nkj::Maybe<rpc::JsValue::ExternalPusher::Client> RpcSerializerExternalHandler::getExternalPusher() {\n  KJ_IF_SOME(ep, externalPusher) {\n    return ep;\n  } else KJ_IF_SOME(func, getStreamHandlerFunc.tryGet<GetExternalPusherFunc>()) {\n    // First call, set up ExternalPusher.\n    return externalPusher.emplace(func());\n  } else {\n    // Using StreamSink.\n    return kj::none;\n  }\n}\n\ncapnp::Capability::Client RpcSerializerExternalHandler::writeStream(BuilderCallback callback) {\n  rpc::JsValue::StreamSink::Client* streamSinkPtr;\n  KJ_IF_SOME(ss, streamSink) {\n    streamSinkPtr = &ss;\n  } else {\n    // First stream written, set up the StreamSink.\n    auto& func = KJ_REQUIRE_NONNULL(getStreamHandlerFunc.tryGet<GetStreamSinkFunc>(),\n        \"this serialization is not using StreamSink; use getExternalPusher() instead\");\n    streamSinkPtr = &streamSink.emplace(func());\n  }\n\n  auto result = ({\n    auto req = streamSinkPtr->startStreamRequest(capnp::MessageSize{4, 0});\n    req.setExternalIndex(externals.size());\n    req.send().getStream();\n  });\n\n  write(kj::mv(callback));\n\n  return result;\n}\n\ncapnp::Orphan<capnp::List<rpc::JsValue::External>> RpcSerializerExternalHandler::build(\n    capnp::Orphanage orphanage) {\n  auto result = orphanage.newOrphan<capnp::List<rpc::JsValue::External>>(externals.size());\n  auto builder = result.get();\n  for (auto i: kj::indices(externals)) {\n    externals[i](builder[i]);\n  }\n  return result;\n}\n\nRpcDeserializerExternalHandler::~RpcDeserializerExternalHandler() noexcept(false) {\n  if (!unwindDetector.isUnwinding()) {\n    KJ_ASSERT(i == externals.size(), \"deserialization did not consume all of the externals\");\n  }\n}\n\nrpc::JsValue::External::Reader RpcDeserializerExternalHandler::read() {\n  KJ_ASSERT(i < externals.size());\n  return externals[i++];\n}\n\nvoid RpcDeserializerExternalHandler::setLastStream(capnp::Capability::Client stream) {\n  KJ_IF_SOME(ss, streamSink) {\n    ss.setSlot(i - 1, kj::mv(stream));\n  } else {\n    auto ss = kj::refcounted<StreamSinkImpl>();\n    ss->setSlot(i - 1, kj::mv(stream));\n    streamSink = *ss;\n    streamSinkCap = rpc::JsValue::StreamSink::Client(kj::mv(ss));\n  }\n}\n\nnamespace {\n\n// Call to construct an `rpc::JsValue` from a JS value.\n//\n// `makeBuilder` is a function which takes a capnp::MessageSize hint and returns the\n// rpc::JsValue::Builder to fill in.\ntemplate <typename Func>\nvoid serializeJsValue(jsg::Lock& js,\n    jsg::JsValue value,\n    RpcSerializerExternalHandler& externalHandler,\n    Func makeBuilder) {\n  jsg::Serializer serializer(js,\n      jsg::Serializer::Options{\n        .version = 15,\n        .omitHeader = false,\n        .treatClassInstancesAsPlainObjects = false,\n        .externalHandler = externalHandler,\n      });\n  serializer.write(js, value);\n  kj::Array<const byte> data = serializer.release().data;\n  JSG_ASSERT(data.size() <= MAX_JS_RPC_MESSAGE_SIZE, Error,\n      \"Serialized RPC arguments or return values are limited to 32MiB, but the size of this value \"\n      \"was: \",\n      data.size(), \" bytes.\");\n\n  capnp::MessageSize hint{0, 0};\n  hint.wordCount += (data.size() + sizeof(capnp::word) - 1) / sizeof(capnp::word);\n  hint.wordCount += capnp::sizeInWords<rpc::JsValue>();\n  hint.wordCount += externalHandler.size() * capnp::sizeInWords<rpc::JsValue::External>();\n  hint.capCount += externalHandler.size();\n\n  rpc::JsValue::Builder builder = makeBuilder(hint);\n\n  // TODO(perf): It would be nice if we could serialize directly into the capnp message to avoid\n  // a redundant copy of the bytes here. Maybe we could even cancel serialization early if it\n  // goes over the size limit.\n  builder.setV8Serialized(data);\n\n  if (externalHandler.size() > 0) {\n    builder.adoptExternals(\n        externalHandler.build(capnp::Orphanage::getForMessageContaining(builder)));\n  }\n}\n\nstruct DeserializeResult {\n  jsg::JsValue value;\n  kj::Own<RpcStubDisposalGroup> disposalGroup;\n  kj::Maybe<rpc::JsValue::StreamSink::Client> streamSink;\n};\n\n// Call to construct a JS value from an `rpc::JsValue`.\nDeserializeResult deserializeJsValue(\n    jsg::Lock& js, rpc::JsValue::Reader reader, kj::Maybe<StreamSinkImpl&> streamSink = kj::none) {\n  auto disposalGroup = kj::heap<RpcStubDisposalGroup>();\n\n  RpcDeserializerExternalHandler externalHandler(reader.getExternals(), *disposalGroup, streamSink);\n\n  jsg::Deserializer deserializer(js, reader.getV8Serialized(), kj::none, kj::none,\n      jsg::Deserializer::Options{\n        .version = 15,\n        .readHeader = true,\n        // Previously, while these are passing over an RPC boundary, we preserved stack\n        // traces in errors that happened to get passed through rather than thrown.\n        // This was mainly due, I believe, to a misunderstanding about whether or not\n        // v8 serialization preserved the stacks or not. When enhanced error serialization\n        // is disabled, stacks are preserved and this flag has no effect. When enhanced\n        // error serialization is enabled, then we'll switch to not preserving stacks in\n        // passed-through errors.\n        .preserveStackInErrors = false,\n        .externalHandler = externalHandler,\n      });\n\n  return {\n    .value = deserializer.readValue(js),\n    .disposalGroup = kj::mv(disposalGroup),\n    .streamSink = externalHandler.getStreamSink(),\n  };\n}\n\n// Does deserializeJsValue() and then adds a `dispose()` method to the returned object (if it is\n// an object) which disposes all stubs therein.\njsg::JsValue deserializeRpcReturnValue(jsg::Lock& js,\n    rpc::JsRpcTarget::CallResults::Reader callResults,\n    kj::Maybe<StreamSinkImpl&> streamSink) {\n  auto [value, disposalGroup, ss] = deserializeJsValue(js, callResults.getResult(), streamSink);\n\n  if (streamSink == kj::none) {\n    KJ_REQUIRE(ss == kj::none,\n        \"RPC returned result using StreamSink even though ExternalPusher was provided\");\n  }\n\n  // If the object had a disposer on the callee side, it will run when we discard the callPipeline,\n  // so attach that to the disposal group on the caller side. If the returned object did NOT have\n  // a disposer then we should discard callPipeline so that we don't hold open the callee's\n  // context for no reason.\n  if (callResults.getHasDisposer()) {\n    disposalGroup->setCallPipeline(\n        IoContext::current().addObject(kj::heap(callResults.getCallPipeline())));\n  }\n\n  KJ_IF_SOME(obj, value.tryCast<jsg::JsObject>()) {\n    if (obj.isInstanceOf<JsRpcStub>(js)) {\n      // We're returning a plain stub. We don't need to override its `dispose` method.\n      disposalGroup->disownAll();\n    } else {\n      // Add a dispose method to the return object that disposes the DisposalGroup.\n      v8::Local<v8::Value> func = js.wrapSimpleFunction(js.v8Context(),\n          [disposalGroup = kj::mv(disposalGroup)](jsg::Lock&,\n              const v8::FunctionCallbackInfo<v8::Value>&) mutable { disposalGroup->disposeAll(); });\n      obj.setNonEnumerable(js, js.symbolDispose(), jsg::JsValue(func));\n    }\n  } else {\n    // Result wasn't an object, so it must not contain any stubs.\n    KJ_ASSERT(disposalGroup->empty());\n  }\n\n  return value;\n}\n\n// A membrane which attaches some object until it is destroyed.\n//\n// TODO(cleanup): This is generally useful, should it be part of capnp?\nclass AttachmentMembrane final: public capnp::MembranePolicy, public kj::Refcounted {\n public:\n  explicit AttachmentMembrane(kj::Own<void> attachment): attachment(kj::mv(attachment)) {}\n\n  kj::Maybe<capnp::Capability::Client> inboundCall(\n      uint64_t interfaceId, uint16_t methodId, capnp::Capability::Client target) override {\n    return kj::none;\n  }\n\n  kj::Maybe<capnp::Capability::Client> outboundCall(\n      uint64_t interfaceId, uint16_t methodId, capnp::Capability::Client target) override {\n    return kj::none;\n  }\n\n  kj::Own<MembranePolicy> addRef() override {\n    return kj::addRef(*this);\n  }\n\n private:\n  kj::Own<void> attachment;\n};\n\n// Given a value, check if it has a dispose method and, if so, invoke it.\nvoid tryCallDisposeMethod(jsg::Lock& js, jsg::JsValue value) {\n  js.withinHandleScope([&]() {\n    KJ_IF_SOME(obj, value.tryCast<jsg::JsObject>()) {\n      auto dispose = obj.get(js, js.symbolDispose());\n      if (dispose.isFunction()) {\n        jsg::check(v8::Local<v8::Value>(dispose).As<v8::Function>()->Call(\n            js.v8Context(), value, 0, nullptr));\n      }\n    }\n  });\n}\n\n}  // namespace\n\nJsRpcPromise::JsRpcPromise(jsg::JsRef<jsg::JsPromise> inner,\n    kj::Own<WeakRef> weakRefParam,\n    IoOwn<rpc::JsRpcTarget::CallResults::Pipeline> pipeline)\n    : inner(kj::mv(inner)),\n      weakRef(kj::mv(weakRefParam)),\n      state(Pending{kj::mv(pipeline)}) {\n  KJ_REQUIRE(weakRef->ref == kj::none);\n  weakRef->ref = *this;\n}\nJsRpcPromise::~JsRpcPromise() noexcept(false) {\n  weakRef->ref = kj::none;\n}\n\nvoid JsRpcPromise::resolve(jsg::Lock& js, jsg::JsValue result) {\n  if (state.is<Pending>()) {\n    state = Resolved{\n      .result = jsg::Value(js.v8Isolate, result),\n      .ctxCheck = IoContext::current().addObject(*this),\n    };\n  } else {\n    // We'd better dispose this.\n    tryCallDisposeMethod(js, result);\n  }\n}\n\nvoid JsRpcPromise::dispose(jsg::Lock& js) {\n  KJ_IF_SOME(resolved, state.tryGet<Resolved>()) {\n    // Disposing the promise implies disposing the final result.\n    tryCallDisposeMethod(js, jsg::JsValue(resolved.result.getHandle(js)));\n  }\n\n  state = Disposed();\n  weakRef->disposed = true;\n}\n\n// See comment at call site for explanation.\nstatic rpc::JsRpcTarget::Client makeJsRpcTargetForSingleLoopbackCall(\n    jsg::Lock& js, jsg::JsObject obj);\n\nrpc::JsRpcTarget::Client JsRpcPromise::getClientForOneCall(\n    jsg::Lock& js, kj::Vector<kj::StringPtr>& path) {\n  // (Don't extend `path` because we're the root.)\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(pending, Pending) {\n      return pending.pipeline->getCallPipeline();\n    }\n    KJ_CASE_ONEOF(resolved, Resolved) {\n      // Dereference `ctxCheck` just to verify we're running in the correct context. (If not,\n      // this will throw.)\n      *resolved.ctxCheck;\n\n      // A value was already returned, and we closed the original RPC pipeline. But the application\n      // kept the promise around and is still trying to pipeline on it. What do we do?\n      //\n      // A naive answer would be: We just return the actual value that was returned originally.\n      // Like if someone asked for `promise.foo.bar`, we just give them `returnValue.foo.bar`.\n      //\n      // That doesn't quite work, for a couple reasons:\n      // * If the caller is awaiting a property, they expect the result will have a `dispose()`\n      //   method added to it, and that any stubs in the result will be independently disposable.\n      //   This essentially means we need to clone the value so that we can dup() all the stubs and\n      //   modify the result.\n      // * If the caller is trying to make a pipelined RPC call, they expect this call to go\n      //   through all the usual RPC machinery. They do NOT expect that this is going to be a local\n      //   call.\n      //\n      // The easiest way to make this all just work is... to actually wrap the value in a one-off\n      // RPC stub, and make a real RPC on it.\n\n      return js.withinHandleScope([&]() -> rpc::JsRpcTarget::Client {\n        auto value = jsg::JsValue(resolved.result.getHandle(js));\n\n        KJ_IF_SOME(obj, value.tryCast<jsg::JsObject>()) {\n          KJ_IF_SOME(stub, obj.tryUnwrapAs<JsRpcStub>(js)) {\n            // Oh, the return value is actually a stub itself. Just use it.\n            return stub->getClient();\n          } else {\n            // Must be a plain object.\n            return makeJsRpcTargetForSingleLoopbackCall(js, obj);\n          }\n        } else {\n          JSG_FAIL_REQUIRE(TypeError, \"Can't pipeline on RPC that did not return an object.\");\n        }\n      });\n    }\n    KJ_CASE_ONEOF(disposed, Disposed) {\n      return JSG_KJ_EXCEPTION(FAILED, Error, \"RPC promise used after being disposed.\");\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nrpc::JsRpcTarget::Client JsRpcProperty::getClientForOneCall(\n    jsg::Lock& js, kj::Vector<kj::StringPtr>& path) {\n  auto result = parent->getClientForOneCall(js, path);\n  path.add(name);\n  return result;\n}\n\nnamespace {\n\nstruct JsRpcPromiseAndPipeline {\n  jsg::JsPromise promise;\n  kj::Own<JsRpcPromise::WeakRef> weakRef;\n  rpc::JsRpcTarget::CallResults::Pipeline pipeline;\n\n  jsg::Ref<JsRpcPromise> asJsRpcPromise(jsg::Lock& js) && {\n    return js.alloc<JsRpcPromise>(jsg::JsRef<jsg::JsPromise>(js, promise), kj::mv(weakRef),\n        IoContext::current().addObject(kj::heap(kj::mv(pipeline))));\n  }\n};\n\n// Core implementation of making an RPC call, reusable for many cases below.\nJsRpcPromiseAndPipeline callImpl(jsg::Lock& js,\n    JsRpcClientProvider& parent,\n    kj::Maybe<const kj::String&> name,\n    // If `maybeArgs` is provided, this is a call, otherwise it is a property access.\n    kj::Maybe<const v8::FunctionCallbackInfo<v8::Value>&> maybeArgs) {\n  // Note: We used to enforce that RPC methods had to be called with the correct `this`. That is,\n  // we prevented people from doing:\n  //\n  //   let obj = {foo: someRpcStub.foo};\n  //   obj.foo();\n  //\n  // This would throw \"Illegal invocation\", as is the norm when pulling methods of a native object.\n  // That worked as long as RPC methods were implemented as `jsg::Function`. However, when we\n  // switched to RPC methods being implemented as callable objects (JsRpcProperty), this became\n  // impossible, because V8's SetCallAsFunctionHandler() arranges that `this` is bound to the\n  // callable object itself, regardless of how it was invoked. So now we cannot detect the\n  // situation above, because V8 never tells us about `obj` at all.\n  //\n  // Oh well. It's not a big deal. Just annoying that we have to forever support tearing RPC\n  // methods off their source object, even if we change implementations to something where that's\n  // less convenient.\n\n  try {\n    return js.tryCatch([&]() -> JsRpcPromiseAndPipeline {\n      // `path` will be filled in with the path of property names leading from the stub represented by\n      // `client` to the specific property / method that we're trying to invoke.\n      kj::Vector<kj::StringPtr> path;\n      auto client = parent.getClientForOneCall(js, path);\n\n      auto& ioContext = IoContext::current();\n\n      KJ_IF_SOME(lock, ioContext.waitForOutputLocksIfNecessary()) {\n        // Replace the client with a promise client that will delay the call until the output gate\n        // is open.\n        client = lock.then([client = kj::mv(client)]() mutable { return kj::mv(client); });\n      }\n\n      auto builder = client.callRequest();\n\n      // This code here is slightly overcomplicated in order to avoid pushing anything to the\n      // kj::Vector in the common case that the parent path is empty. I'm probably trying too hard\n      // but oh well.\n      if (path.empty()) {\n        KJ_IF_SOME(n, name) {\n          builder.setMethodName(n);\n        } else {\n          // No name and no path, must be directly calling a stub.\n          builder.initMethodPath(0);\n        }\n      } else {\n        auto pathBuilder = builder.initMethodPath(path.size() + (name != kj::none));\n        for (auto i: kj::indices(path)) {\n          pathBuilder.set(i, path[i]);\n        }\n        KJ_IF_SOME(n, name) {\n          pathBuilder.set(path.size(), n);\n        }\n      }\n\n      kj::Maybe<StreamSinkFulfiller> paramsStreamSinkFulfiller;\n\n      bool useExternalPusher =\n          util::Autogate::isEnabled(util::AutogateKey::RPC_USE_EXTERNAL_PUSHER);\n\n      KJ_IF_SOME(args, maybeArgs) {\n        // If we have arguments, serialize them.\n        // Note that we may fail to serialize some element, in which case this will throw back to\n        // JS.\n        if (args.Length() > 0) {\n          // This is a function call with arguments.\n          v8::LocalVector<v8::Value> argv(js.v8Isolate, args.Length());\n          for (int n = 0; n < args.Length(); n++) {\n            argv[n] = args[n];\n          }\n          auto arr = v8::Array::New(js.v8Isolate, argv.data(), argv.size());\n\n          auto stubOwnership = FeatureFlags::get(js).getRpcParamsDupStubs()\n              ? RpcSerializerExternalHandler::DUPLICATE\n              : RpcSerializerExternalHandler::TRANSFER;\n\n          RpcSerializerExternalHandler::GetStreamHandlerFunc getStreamHandlerFunc;\n          if (useExternalPusher) {\n            getStreamHandlerFunc.init<RpcSerializerExternalHandler::GetExternalPusherFunc>(\n                [&]() -> rpc::JsValue::ExternalPusher::Client { return client; });\n          } else {\n            getStreamHandlerFunc.init<RpcSerializerExternalHandler::GetStreamSinkFunc>([&]() {\n              // A stream was encountered in the params, so we must expect the response to contain\n              // paramsStreamSink. But we don't have the response yet. So, we need to set up a\n              // temporary promise client, which we hook to the response a little bit later.\n              auto paf = kj::newPromiseAndFulfiller<rpc::JsValue::StreamSink::Client>();\n              paramsStreamSinkFulfiller = kj::mv(paf.fulfiller);\n              return kj::mv(paf.promise);\n            });\n          }\n\n          RpcSerializerExternalHandler externalHandler(stubOwnership, kj::mv(getStreamHandlerFunc));\n          serializeJsValue(js, jsg::JsValue(arr), externalHandler, [&](capnp::MessageSize hint) {\n            // TODO(perf): Actually use the size hint.\n            return builder.getOperation().initCallWithArgs();\n          });\n        }\n      } else {\n        // This is a property access.\n        builder.getOperation().setGetProperty();\n      }\n\n      kj::Maybe<kj::Own<StreamSinkImpl>> resultStreamSink;\n      if (useExternalPusher) {\n        // Unfortunately, we always have to send the ExternalPusher since we don't know whether the\n        // call will return any streams (or other pushed externals). Luckily, it's a\n        // one-per-IoContext object, not a big deal. (It'll take a slot on the capnp export table\n        // though.)\n        builder.getResultsStreamHandler().setExternalPusher(ioContext.getExternalPusher());\n      } else {\n        // Unfortunately, we always have to send a `resultsStreamSink` because we don't know until\n        // after the call completes whether or not it will return any streams. If it's unused,\n        // though, it should only be a couple allocations.\n        builder.getResultsStreamHandler().setStreamSink(\n            kj::addRef(*resultStreamSink.emplace(kj::refcounted<StreamSinkImpl>())));\n      }\n\n      auto callResult = builder.send();\n\n      KJ_IF_SOME(ssf, paramsStreamSinkFulfiller) {\n        ssf->fulfill(callResult.getParamsStreamSink());\n      }\n\n      // We need to arrange that our JsRpcPromise will updated in-place with the final settlement\n      // of this RPC promise. However, we can't actually construct the JsRpcPromise until we have\n      // the final promise to give it. To resolve the cycle, we only create a JsRpcPromise::WeakRef\n      // here, which is filled in later on to point at the JsRpcPromise, if and when one is created.\n      auto weakRef = kj::atomicRefcounted<JsRpcPromise::WeakRef>();\n\n      // HACK: Make sure that any calls to the ExternalPusher get to us before we try to\n      // deserialize the result. A weird quirk of Cap'n Proto is that return values arrive faster\n      // than calls by 1 turn of the event loop, so if we just insert a turn here we should be OK.\n      //\n      // Note that key to this working is the fact that the continuation returns a Promise, even\n      // though it is initialized with an immediate value. This forces the extra turn.\n      auto promise = callResult.then(\n          [](auto resp) -> kj::Promise<capnp::Response<rpc::JsRpcTarget::CallResults>> {\n        return kj::mv(resp);\n      });\n\n      // RemotePromise lets us consume its pipeline and promise portions independently; we consume\n      // the promise here and we consume the pipeline below, both via kj::mv().\n      auto jsPromise = ioContext.awaitIo(js, kj::mv(promise),\n          [weakRef = kj::atomicAddRef(*weakRef), resultStreamSink = kj::mv(resultStreamSink)](\n              jsg::Lock& js,\n              capnp::Response<rpc::JsRpcTarget::CallResults> response) mutable -> jsg::Value {\n        auto jsResult = deserializeRpcReturnValue(js, response, resultStreamSink);\n\n        if (weakRef->disposed) {\n          // The promise was explicitly disposed before it even resolved. This means we must dispose\n          // the returned object as well.\n          tryCallDisposeMethod(js, jsResult);\n        } else {\n          KJ_IF_SOME(r, weakRef->ref) {\n            r.resolve(js, jsResult);\n          }\n        }\n\n        return jsg::Value(js.v8Isolate, jsResult);\n      });\n\n      return {\n        .promise = jsg::JsPromise(js.wrapSimplePromise(kj::mv(jsPromise))),\n        .weakRef = kj::mv(weakRef),\n        .pipeline = kj::mv(callResult),\n      };\n    }, [&](jsg::Value error) -> JsRpcPromiseAndPipeline {\n      // Probably a serialization error. Need to convert to an async error since we never throw\n      // synchronously from async functions.\n      auto jsError = jsg::JsValue(error.getHandle(js));\n      auto pipeline = capnp::newBrokenPipeline(js.exceptionToKj(jsError));\n      return {.promise = js.rejectedJsPromise(jsError),\n        .weakRef = kj::atomicRefcounted<JsRpcPromise::WeakRef>(),\n        .pipeline =\n            rpc::JsRpcTarget::CallResults::Pipeline(capnp::AnyPointer::Pipeline(kj::mv(pipeline)))};\n    });\n  } catch (jsg::JsExceptionThrown&) {\n    // This must be a termination exception, or we would have caught it above.\n    throw;\n  } catch (...) {\n    // Catch KJ exceptions and make them async, since we don't want async calls to throw\n    // synchronously.\n    auto e = kj::getCaughtExceptionAsKj();\n    auto pipeline = capnp::newBrokenPipeline(kj::cp(e));\n    return {\n      .promise = jsg::JsPromise(js.wrapSimplePromise(js.rejectedPromise<jsg::Value>(kj::mv(e)))),\n      .weakRef = kj::atomicRefcounted<JsRpcPromise::WeakRef>(),\n      .pipeline =\n          rpc::JsRpcTarget::CallResults::Pipeline(capnp::AnyPointer::Pipeline(kj::mv(pipeline)))};\n  }\n}\n\n}  // namespace\n\njsg::Ref<JsRpcPromise> JsRpcProperty::call(const v8::FunctionCallbackInfo<v8::Value>& args) {\n  jsg::Lock& js = jsg::Lock::from(args.GetIsolate());\n\n  return callImpl(js, *parent, name, args).asJsRpcPromise(js);\n}\n\njsg::Ref<JsRpcPromise> JsRpcStub::call(const v8::FunctionCallbackInfo<v8::Value>& args) {\n  jsg::Lock& js = jsg::Lock::from(args.GetIsolate());\n\n  return callImpl(js, *this, kj::none, args).asJsRpcPromise(js);\n}\n\njsg::Ref<JsRpcPromise> JsRpcPromise::call(const v8::FunctionCallbackInfo<v8::Value>& args) {\n  jsg::Lock& js = jsg::Lock::from(args.GetIsolate());\n\n  return callImpl(js, *this, kj::none, args).asJsRpcPromise(js);\n}\n\nnamespace {\n\njsg::JsValue thenImpl(jsg::Lock& js,\n    v8::Local<v8::Promise> promise,\n    v8::Local<v8::Function> handler,\n    jsg::Optional<v8::Local<v8::Function>> errorHandler) {\n  KJ_IF_SOME(e, errorHandler) {\n    // Note that we intentionally propagate any exception from promise->Then() synchronously since\n    // if V8's native Promise threw synchronously from `then()`, we might as well too. Anyway it's\n    // probably a termination exception.\n    return jsg::JsPromise(jsg::check(promise->Then(js.v8Context(), handler, e)));\n  } else {\n    return jsg::JsPromise(jsg::check(promise->Then(js.v8Context(), handler)));\n  }\n}\n\njsg::JsValue catchImpl(\n    jsg::Lock& js, v8::Local<v8::Promise> promise, v8::Local<v8::Function> errorHandler) {\n  return jsg::JsPromise(jsg::check(promise->Catch(js.v8Context(), errorHandler)));\n}\n\njsg::JsValue finallyImpl(\n    jsg::Lock& js, v8::Local<v8::Promise> promise, v8::Local<v8::Function> onFinally) {\n  // HACK: `finally()` is not exposed as a C++ API, so we have to manually read it from JS.\n  jsg::JsObject obj(promise);\n  auto func = obj.get(js, \"finally\");\n  KJ_ASSERT(func.isFunction());\n  v8::Local<v8::Value> param = onFinally;\n  return jsg::JsValue(jsg::check(\n      v8::Local<v8::Value>(func).As<v8::Function>()->Call(js.v8Context(), obj, 1, &param)));\n}\n\n}  // namespace\n\njsg::JsValue JsRpcProperty::then(jsg::Lock& js,\n    v8::Local<v8::Function> handler,\n    jsg::Optional<v8::Local<v8::Function>> errorHandler) {\n  auto promise = callImpl(js, *parent, name, kj::none).promise;\n\n  return thenImpl(js, promise, handler, errorHandler);\n}\n\njsg::JsValue JsRpcProperty::catch_(jsg::Lock& js, v8::Local<v8::Function> errorHandler) {\n  auto promise = callImpl(js, *parent, name, kj::none).promise;\n\n  return catchImpl(js, promise, errorHandler);\n}\n\njsg::JsValue JsRpcProperty::finally(jsg::Lock& js, v8::Local<v8::Function> onFinally) {\n  auto promise = callImpl(js, *parent, name, kj::none).promise;\n\n  return finallyImpl(js, promise, onFinally);\n}\n\njsg::JsValue JsRpcPromise::then(jsg::Lock& js,\n    v8::Local<v8::Function> handler,\n    jsg::Optional<v8::Local<v8::Function>> errorHandler) {\n  return thenImpl(js, inner.getHandle(js), handler, errorHandler);\n}\n\njsg::JsValue JsRpcPromise::catch_(jsg::Lock& js, v8::Local<v8::Function> errorHandler) {\n  return catchImpl(js, inner.getHandle(js), errorHandler);\n}\n\njsg::JsValue JsRpcPromise::finally(jsg::Lock& js, v8::Local<v8::Function> onFinally) {\n  return finallyImpl(js, inner.getHandle(js), onFinally);\n}\n\nkj::Maybe<jsg::Ref<JsRpcProperty>> JsRpcProperty::getProperty(jsg::Lock& js, kj::String name) {\n  return js.alloc<JsRpcProperty>(JSG_THIS, kj::mv(name));\n}\n\nkj::Maybe<jsg::Ref<JsRpcProperty>> JsRpcPromise::getProperty(jsg::Lock& js, kj::String name) {\n  return js.alloc<JsRpcProperty>(JSG_THIS, kj::mv(name));\n}\n\nJsRpcStub::JsRpcStub(IoOwn<rpc::JsRpcTarget::Client> capnpClient,\n    RpcStubDisposalGroup& disposalGroup,\n    jsg::ExternalMemoryAdjustment externalMemoryAdjustment)\n    : capnpClient(kj::mv(capnpClient)),\n      disposalGroup(disposalGroup),\n      externalMemoryAdjustment(kj::mv(externalMemoryAdjustment)) {\n  disposalGroup.list.add(*this);\n}\n\nJsRpcStub::~JsRpcStub() noexcept(false) {\n  KJ_IF_SOME(d, disposalGroup) {\n    d.list.remove(*this);\n  }\n\n  KJ_IF_SOME(c, capnpClient) {\n    // The app failed to dispose the stub; it leaked. We'd rather not make GC observable, so we\n    // must pass the capnp capability off to the I/O context to be dropped when the I/O context\n    // itself shuts down.\n    kj::mv(c).deferGcToContext();\n\n    // In preview, let's try to warn the developer about the problem.\n    //\n    // TODO(cleanup): Instead of logging this warning at GC time, it would be better if we logged\n    //   it at the time that the client is destroyed, i.e. when the IoContext is torn down,\n    //   which is usually sooner (and more deterministic). But logging a warning during\n    //   IoContext tear-down is problematic since logWarningOnce() is a method on\n    //   IoContext...\n    KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n      ioContext.logWarningOnce(\n          \"An RPC stub was not disposed properly. You must call dispose() on all stubs in order to \"\n          \"let the other side know that you are no longer using them. You cannot rely on \"\n          \"the garbage collector for this because it may take arbitrarily long before actually \"\n          \"collecting unreachable objects. As a shortcut, calling dispose() on the result of \"\n          \"an RPC call disposes all stubs within it.\"_kj);\n    }\n  }\n}\n\nRpcStubDisposalGroup::~RpcStubDisposalGroup() noexcept(false) {\n  if (jsg::isInGcDestructor()) {\n    // If the disposal group was dropped as a result of garbage collection, we should NOT actually\n    // dispose any stubs. In particular:\n    // * If an application never invokes dispose() on an RPC result and the result is GC'ed, the\n    //   app could still be holding onto stubs that came from that result. We don't want to\n    //   dispose those unexpectedly.\n    // * If an incoming RPC call does something like `await new Promise(() => {})` to hang\n    //   forever, the promise reaction can be GC'ed even though the call didn't really complete.\n    //   We don't want to dispose param stubs in this case.\n    disownAll();\n\n    // If we have a `callPipeline`, it means we called an RPC that returned an object, and that\n    // object had a dispose method defined on the server side. We don't want it to observe GC,\n    // so we'll defer dropping the pipeline until the IoContext is destroyed.\n    //\n    // (We don't do this as part of disownAll() because the one other call site of disownAll()\n    // is only invoked in cases where there shouldn't be a `callPipeline` anyway...)\n    KJ_IF_SOME(c, callPipeline) {\n      kj::mv(c).deferGcToContext();\n\n      // In preview, let's try to warn the developer about the problem.\n      //\n      // TODO(cleanup): Same comment as in ~JsRpcStub().\n      KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n        ioContext.logWarningOnce(\n            \"An RPC result was not disposed properly. One of the RPC calls you made expects you \"\n            \"to call dispose() on the return value, but you didn't do so. You cannot rely on \"\n            \"the garbage collector for this because it may take arbitrarily long before actually \"\n            \"collecting unreachable objects.\"_kj);\n      }\n    }\n  } else {\n    // However, if we're destroying the RpcStubDisposalGroup NOT as a result of GC, this probably\n    // means one of:\n    // * This is the disposal group for an incoming RPC call, and that call completed. The group\n    //   was attached to the completion continuation, which executed, and is now being destroyed.\n    //   This is the normal completion case, and we should dispose all the param stubs.\n    // * An exception was thrown in the RPC implementation before stubs could be passed to\n    //   JavaScript in the first place, resulting in the disposal group being destroyed during\n    //   exception unwind. The stubs should be disposed proactively since they were never\n    //   received.\n    disposeAll();\n  }\n}\n\nrpc::JsRpcTarget::Client JsRpcStub::getClient() {\n  KJ_IF_SOME(c, capnpClient) {\n    return *c;\n  } else {\n    // TODO(soon): Improve the error message to describe why it was disposed.\n    return JSG_KJ_EXCEPTION(FAILED, Error, \"RPC stub used after being disposed.\");\n  }\n}\n\nrpc::JsRpcTarget::Client JsRpcStub::getClientForOneCall(\n    jsg::Lock& js, kj::Vector<kj::StringPtr>& path) {\n  // (Don't extend `path` because we're the root.)\n  return getClient();\n}\n\njsg::Ref<JsRpcStub> JsRpcStub::dup(jsg::Lock& js) {\n  return js.alloc<JsRpcStub>(IoContext::current().addObject(kj::heap(getClient())));\n}\n\nvoid JsRpcStub::dispose() {\n  capnpClient = kj::none;\n  externalMemoryAdjustment = kj::none;\n  KJ_IF_SOME(d, disposalGroup) {\n    d.list.remove(*this);\n    disposalGroup = kj::none;\n  }\n}\n\nvoid RpcStubDisposalGroup::disownAll() {\n  for (auto& stub: list) {\n    stub.disposalGroup = kj::none;\n    list.remove(stub);\n  }\n}\n\nvoid RpcStubDisposalGroup::disposeAll() {\n  for (auto& stub: list) {\n    stub.dispose();\n  }\n  callPipeline = kj::none;\n\n  // Each stub should have removed itself.\n  KJ_ASSERT(list.empty());\n}\n\nkj::Maybe<jsg::Ref<JsRpcProperty>> JsRpcStub::getRpcMethod(jsg::Lock& js, kj::String name) {\n  // Do not return a method for `then`, otherwise JavaScript decides this is a thenable, i.e. a\n  // custom Promise, which will mean a Promise that resolves to this object will attempt to chain\n  // with it, which is not what you want!\n  if (name == \"then\"_kj) return kj::none;\n\n  return js.alloc<JsRpcProperty>(JSG_THIS, kj::mv(name));\n}\n\nvoid JsRpcStub::serialize(jsg::Lock& js, jsg::Serializer& serializer) {\n  auto& handler = JSG_REQUIRE_NONNULL(serializer.getExternalHandler(), DOMDataCloneError,\n      \"Remote RPC references can only be serialized for RPC.\");\n  auto externalHandler = dynamic_cast<RpcSerializerExternalHandler*>(&handler);\n  JSG_REQUIRE(externalHandler != nullptr, DOMDataCloneError,\n      \"Remote RPC references can only be serialized for RPC.\");\n\n  // We may be forwarding a stub that points to some other isolate. Consider the case where we\n  // are returning the stub to our client. The RPC session remains live as long as the client is\n  // holding any remaining stubs obtained from this session, due to CompletionMembrane. However, if\n  // the only remaining stubs point on to different isolates, and we don't have anything left to\n  // do in this IoContext, then the pending event mechanism would abort the IoContext early with\n  // \"The script will never generate a response.\" To avoid that, we need to attach a pending event\n  // to this stub, using a membrane.\n  //\n  // TODO(someday): Ideally, we would not need to keep the IoContext live just because stubs pass\n  //   through it. It would be nice to implement a sort of \"deferred proxying\" for RPC, where we\n  //   shut down the IoContext when it has nothing left to do. Note, though, that if the IoContext\n  //   is explicitly *aborted*, we probably should revoke all capabilities obtained through it.\n  //   That actually doesn't quite happen today: aborting the IoContext is likely to cancel all\n  //   subrequests which probably has the effect of breaking any stubs obtained from them, but\n  //   not necessarily (the subrequests could use waitUntil() to extend themselves). Anyway, this\n  //   will be trickier to get right, so I'm punting with this work-around for now.\n  auto cap = capnp::membrane(\n      getClient(), kj::refcounted<AttachmentMembrane>(IoContext::current().registerPendingEvent()));\n\n  externalHandler->write([cap = kj::mv(cap)](rpc::JsValue::External::Builder builder) mutable {\n    builder.setRpcTarget(kj::mv(cap));\n  });\n\n  if (externalHandler->getStubOwnership() == RpcSerializerExternalHandler::TRANSFER) {\n    // Instead of disposing the stub immediately, we add a disposer to the serializer\n    // that will be executed when the pipeline is finished. This ensures the stub\n    // remains valid for the duration of any pipelined operations.\n    externalHandler->addStubDisposer(\n        kj::heap(kj::defer([self = JSG_THIS]() mutable { self->dispose(); })));\n  }\n}\n\njsg::Ref<JsRpcStub> JsRpcStub::deserialize(\n    jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer) {\n  auto& handler = KJ_REQUIRE_NONNULL(\n      deserializer.getExternalHandler(), \"got JsRpcStub on non-RPC serialized object?\");\n  auto externalHandler = dynamic_cast<RpcDeserializerExternalHandler*>(&handler);\n  KJ_REQUIRE(externalHandler != nullptr, \"got JsRpcStub on non-RPC serialized object?\");\n\n  auto reader = externalHandler->read();\n  KJ_REQUIRE(reader.isRpcTarget(), \"external table slot type doesn't match serialization tag\");\n\n  auto& ioctx = IoContext::current();\n\n  // Account for membrane/promise memory in the KJ heap (~1600 bytes per stub from profiling).\n  static constexpr size_t ESTIMATED_EXTERNAL_MEMORY_PER_STUB = 1600;\n  auto externalMemory = js.getExternalMemoryAdjustment(ESTIMATED_EXTERNAL_MEMORY_PER_STUB);\n\n  return js.alloc<JsRpcStub>(ioctx.addObject(kj::heap(reader.getRpcTarget())),\n      externalHandler->getDisposalGroup(), kj::mv(externalMemory));\n}\n\nstatic bool isFunctionForRpc(jsg::Lock& js, v8::Local<v8::Function> func) {\n  jsg::JsObject obj(func);\n  if (obj.isInstanceOf<JsRpcProperty>(js) || obj.isInstanceOf<JsRpcPromise>(js)) {\n    // Don't allow JsRpcProperty or JsRpcPromise to be treated as plain functions, even though they\n    // are technically callable. These types need to be treated specially (if we decide to let\n    // them be passed over RPC at all).\n    return false;\n  }\n  return true;\n}\n\nstatic bool isFunctionForRpc(jsg::Lock& js, jsg::JsValue value) {\n  if (!value.isFunction()) return false;\n  return isFunctionForRpc(js, v8::Local<v8::Value>(value).As<v8::Function>());\n}\n\n// `makeCallPipeline()` has a bit of a complicated result type..\nnamespace MakeCallPipeline {\n// The value is an object, which may have stubs inside it.\nstruct Object {\n  rpc::JsRpcTarget::Client cap;\n\n  // Was the value a plain JavaScript object which had a custom dispose() method?\n  bool hasDispose;\n};\n\n// The value was something that should serialize to a single stub (e.g. it was an RpcTarget, a\n// plain function, or already a stub). The callPipeline should simply be a copy of that stub.\nstruct SingleStub {};\n\n// The value is not a type that supports pipelining. It may still be serializable, and it could\n// even contain stubs (e.g. in a Map).\nstruct NonPipelinable {\n  // callPipeline to return just for error-handling purposes.\n  rpc::JsRpcTarget::Client errorPipeline;\n};\n\nusing Result = kj::OneOf<Object, SingleStub, NonPipelinable>;\n};  // namespace MakeCallPipeline\n\ntemplate <typename Func>\nMakeCallPipeline::Result serializeJsValueWithPipeline(jsg::Lock& js,\n    jsg::JsValue value,\n    Func makeBuilder,\n    RpcSerializerExternalHandler::GetStreamHandlerFunc getStreamSinkFunc);\n\n// Callee-side implementation of JsRpcTarget.\n//\n// Most of the implementation is in this base class. There are subclasses specializing for the case\n// of a top-level entrypoint vs. a transient object introduced by a previous RPC in the same\n// session.\nclass JsRpcTargetBase: public rpc::JsRpcTarget::Server {\n public:\n  struct MayOutliveIncomingRequest {};\n  struct CantOutliveIncomingRequest {};\n\n  // Constructor used by TransientJsRpcTarget, which does not own the context. It needs to use\n  // makeReentryCallback() to guard against the possibility that the IoContext is canceled before\n  // or during a call.\n  JsRpcTargetBase(IoContext& ctx, MayOutliveIncomingRequest)\n      : enterIsolateAndCall(ctx.makeReentryCallback<IoContext::TOP_UP>(\n            [this, &ctx](Worker::Lock& lock, CallContext callContext) {\n              return callImpl(lock, ctx, callContext);\n            })),\n        externalPusher(ctx.getExternalPusher()) {}\n\n  // Constructor use by EntrypointJsRpcTarget, which is revoked and destroyed before the IoContext\n  // can possibly be canceled. It can just use ctx.run().\n  JsRpcTargetBase(IoContext& ctx, CantOutliveIncomingRequest)\n      : enterIsolateAndCall([this, &ctx](CallContext callContext) {\n          // Note: No need to topUpActor() since this is the start of a top-level request, so the\n          // actor will already have been topped up by IncomingRequest::delivered().\n          return ctx.run([this, &ctx, callContext](Worker::Lock& lock) mutable {\n            return callImpl(lock, ctx, callContext);\n          });\n        }),\n        externalPusher(ctx.getExternalPusher()) {}\n\n  struct EnvCtx {\n    v8::Local<v8::Value> env;\n    jsg::JsObject ctx;\n  };\n\n  struct TargetInfo {\n    // The object on which the RPC method should be invoked.\n    jsg::JsObject target;\n\n    // If `env` and `ctx` need to be delivered as arguments to the method, these are the values\n    // to deliver.\n    kj::Maybe<EnvCtx> envCtx;\n\n    bool allowInstanceProperties;\n  };\n\n  // Get the object on which the method is to be invoked. This is virtual so that we can have\n  // separate subclasses handling the case of an entrypoint vs. a transient RPC object.\n  virtual TargetInfo getTargetInfo(Worker::Lock& lock, IoContext& ioCtx) = 0;\n\n  // Handles the delivery of JS RPC method calls.\n  kj::Promise<void> call(CallContext callContext) override {\n    co_await kj::yield();\n\n    // Try to execute the requested method.\n    co_return co_await enterIsolateAndCall(callContext).catch_([](kj::Exception&& e) {\n      if (jsg::isTunneledException(e.getDescription())) {\n        // Annotate exceptions in RPC worker calls as remote exceptions.\n        auto description = jsg::stripRemoteExceptionPrefix(e.getDescription());\n        if (!description.startsWith(\"remote.\")) {\n          // If we already were annotated as remote from some other worker entrypoint, no point\n          // adding an additional prefix.\n          e.setDescription(kj::str(\"remote.\", description));\n        }\n      }\n      kj::throwFatalException(kj::mv(e));\n    });\n  }\n\n  // Implements ExternalPusher by forwarding to the shared implementation.\n  //\n  // Note JsRpcTarget has to implement `ExternalPusher` directly rather than providing a method\n  // like `getExternalPusher()` because it's important that the pushes arrive before the call, and\n  // the ordering can only be guaranteed if they're on the same object.\n  kj::Promise<void> pushByteStream(PushByteStreamContext context) override {\n    return externalPusher->pushByteStream(context);\n  }\n  kj::Promise<void> pushAbortSignal(PushAbortSignalContext context) override {\n    return externalPusher->pushAbortSignal(context);\n  }\n\n  KJ_DISALLOW_COPY_AND_MOVE(JsRpcTargetBase);\n\n private:\n  virtual void maybeSetJsRpcInfo(IoContext& ctx, const kj::ConstString& methodNameForTrace) = 0;\n\n  // Function which enters the isolate lock and IoContext and then invokes callImpl(). Created\n  // using IoContext::makeReentryCallback().\n  kj::Function<kj::Promise<void>(CallContext callContext)> enterIsolateAndCall;\n\n  kj::Rc<ExternalPusherImpl> externalPusher;\n\n  // Returns true if the given name cannot be used as a method on this type.\n  virtual bool isReservedName(kj::StringPtr name) = 0;\n\n  kj::Promise<void> callImpl(Worker::Lock& lock, IoContext& ctx, CallContext callContext) {\n    jsg::Lock& js = lock;\n    auto params = callContext.getParams();\n    // Method name suitable for use in trace and error messages. May be a pointer into the RPC\n    // params reader.\n    kj::ConstString methodNameForTrace;\n\n    // Retrieve the method name and report onset event info if tracing is enabled.\n    switch (params.which()) {\n      case rpc::JsRpcTarget::CallParams::METHOD_NAME: {\n        methodNameForTrace = kj::ConstString(kj::str(params.getMethodName()));\n        break;\n      }\n      case rpc::JsRpcTarget::CallParams::METHOD_PATH: {\n        auto path = params.getMethodPath();\n        auto n = path.size();\n\n        if (n == 0) {\n          // Call the target itself as a function.\n          methodNameForTrace = \"(this)\"_kjc;\n        } else {\n          methodNameForTrace = kj::ConstString(kj::strArray(path, \".\"));\n        }\n        break;\n      }\n    }\n\n    maybeSetJsRpcInfo(ctx, methodNameForTrace);\n\n    auto targetInfo = getTargetInfo(lock, ctx);\n\n    // We will try to get the function, if we can't we'll throw an error to the client.\n    auto [propHandle, thisArg] =\n        tryGetProperty(lock, targetInfo.target, params, targetInfo.allowInstanceProperties, ctx);\n\n    auto op = params.getOperation();\n\n    auto handleResult = [&](InvocationResult&& invocationResult) {\n      // Given a handle for the result, if it's a promise, await the promise, then serialize the\n      // final result for return.\n\n      RpcSerializerExternalHandler::GetStreamHandlerFunc getResultsStreamHandlerFunc;\n      auto resultStreamHandler = params.getResultsStreamHandler();\n      switch (resultStreamHandler.which()) {\n        case rpc::JsRpcTarget::CallParams::ResultsStreamHandler::EXTERNAL_PUSHER:\n          getResultsStreamHandlerFunc.init<RpcSerializerExternalHandler::GetExternalPusherFunc>(\n              [cap = resultStreamHandler.getExternalPusher()]() mutable { return kj::mv(cap); });\n          break;\n        case rpc::JsRpcTarget::CallParams::ResultsStreamHandler::STREAM_SINK:\n          getResultsStreamHandlerFunc.init<RpcSerializerExternalHandler::GetStreamSinkFunc>(\n              [cap = resultStreamHandler.getStreamSink()]() mutable { return kj::mv(cap); });\n          break;\n      }\n\n      kj::Maybe<kj::Own<kj::PromiseFulfiller<rpc::JsRpcTarget::Client>>> callPipelineFulfiller;\n\n      // We need another ref to this fulfiller for the error callback. It can rely on being\n      // destroyed at the same time as the success callback.\n      kj::Maybe<kj::PromiseFulfiller<rpc::JsRpcTarget::Client>&> callPipelineFulfillerRef;\n\n      KJ_IF_SOME(ss, invocationResult.streamSink) {\n        // Since we have a StreamSink, it's important that we hook up the pipeline for that\n        // immediately. Annoyingly, that also means we need to hook up a pipeline for\n        // callPipeline, which we don't actually have yet, so we need to promise-ify it.\n\n        // If the caller requested using ExternalPusher for the results, then it should also use\n        // ExternalPusher for the params. (Theoretically we could support mix-and-match but...\n        // let's keep it simple.)\n        KJ_REQUIRE(resultStreamHandler.isStreamSink(),\n            \"RPC params used StreamSink when result is supposed to use ExternalPusher\");\n\n        auto paf = kj::newPromiseAndFulfiller<rpc::JsRpcTarget::Client>();\n        callPipelineFulfillerRef = *paf.fulfiller;\n        callPipelineFulfiller = kj::mv(paf.fulfiller);\n\n        capnp::PipelineBuilder<rpc::JsRpcTarget::CallResults> builder(16);\n        builder.setCallPipeline(kj::mv(paf.promise));\n        builder.setParamsStreamSink(ss);\n        callContext.setPipeline(builder.build());\n      }\n\n      // HACK: Cap'n Proto call contexts are documented as being pointer-like types where the\n      // backing object's lifetime is that of the RPC call, but in reality they are refcounted\n      // under the hood. Since we'll be executing the call in the JS microtask queue, we have no\n      // ability to actually cancel execution if a cancellation arrives over RPC, and at the end of\n      // that execution we're going to access the call context to write the results. We could\n      // invent some complicated way to skip initializing results in the case the call has been\n      // canceled, but it's easier and safer to just grab a refcount on the call context object\n      // itself, which fully protects us. So... do that.\n      auto ownCallContext = capnp::CallContextHook::from(callContext).addRef();\n\n      auto result = ctx.awaitJs(js,\n          js.toPromise(invocationResult.returnValue)\n              .then(js,\n                  ctx.addFunctor(\n                      // Warning: Be careful about captures here! If the incoming RPC is canceled,\n                      // this continuation will still execute, sice it's a JS promise continuation.\n                      // But `this` could have been destroyed in the meantime. So all our captures\n                      // must take full ownership.\n                      [callContext, ownCallContext = kj::mv(ownCallContext),\n                          paramDisposalGroup = kj::mv(invocationResult.paramDisposalGroup),\n                          paramsStreamSink = kj::mv(invocationResult.streamSink),\n                          getResultsStreamHandlerFunc = kj::mv(getResultsStreamHandlerFunc),\n                          callPipelineFulfiller = kj::mv(callPipelineFulfiller)](\n                          jsg::Lock& js, jsg::Value value) mutable {\n        jsg::JsValue resultValue(value.getHandle(js));\n\n        rpc::JsRpcTarget::CallResults::Builder results = nullptr;\n        auto maybePipeline =\n            serializeJsValueWithPipeline(js, resultValue, [&](capnp::MessageSize hint) {\n          hint.wordCount += capnp::sizeInWords<rpc::JsRpcTarget::CallResults>();\n          hint.capCount += 1;  // for callPipeline\n          results = callContext.initResults(hint);\n          return results.initResult();\n        }, kj::mv(getResultsStreamHandlerFunc));\n\n        KJ_SWITCH_ONEOF(maybePipeline) {\n          KJ_CASE_ONEOF(obj, MakeCallPipeline::Object) {\n            results.setCallPipeline(kj::mv(obj.cap));\n\n            // Note that hasDisposer is ONLY meant to indicate the presence of an\n            // application-level disposer. It need not be true if we only have stub disposers.\n            results.setHasDisposer(obj.hasDispose);\n          }\n          KJ_CASE_ONEOF(obj, MakeCallPipeline::SingleStub) {\n            // Serialization should have produced a single stub. We can use that same stub as\n            // the callPipeline.\n            auto externals = results.asReader().getResult().getExternals();\n            KJ_ASSERT(externals.size() == 1);\n            auto external = externals[0];\n            KJ_ASSERT(external.isRpcTarget());\n            results.setCallPipeline(external.getRpcTarget());\n          }\n          KJ_CASE_ONEOF(nonPipelinable, MakeCallPipeline::NonPipelinable) {\n            results.setCallPipeline(kj::mv(nonPipelinable.errorPipeline));\n            // leave hasDisposer false\n          }\n        }\n\n        KJ_IF_SOME(cpf, callPipelineFulfiller) {\n          cpf->fulfill(results.getCallPipeline());\n        }\n\n        KJ_IF_SOME(ss, paramsStreamSink) {\n          results.setParamsStreamSink(kj::mv(ss));\n        }\n\n        // paramDisposalGroup will be destroyed when we return (or when this lambda is destroyed\n        // as a result of the promise being rejected). This will implicitly dispose the param\n        // stubs.\n      }),\n                  ctx.addFunctor([callPipelineFulfillerRef](jsg::Lock& js, jsg::Value&& error) {\n        // If we set up a `callPipeline` early, we have to make sure it propagates the error.\n        // (Otherwise we get a PromiseFulfiller error instead, which is pretty useless...)\n        KJ_IF_SOME(cpf, callPipelineFulfillerRef) {\n          cpf.reject(js.exceptionToKj(error.addRef(js)));\n        }\n        js.throwException(kj::mv(error));\n      })));\n\n      if (ctx.hasOutputGate()) {\n        // Note: If `ctx` is destroyed, the entire call to `callImpl()` will be canceled\n        // (makeReentryCallback() ensures this). This does NOT cancel the JavaScript (because JS\n        // promises are not RAII-cancelable), but it will cancel this trailing .then(), which is\n        // why it's safe to capture `&ctx` here.\n        return result.then([&ctx]() mutable { return ctx.waitForOutputLocks(); });\n      } else {\n        return result;\n      }\n    };\n\n    switch (op.which()) {\n      case rpc::JsRpcTarget::CallParams::Operation::CALL_WITH_ARGS: {\n        // Note that using isFunctionForRpc(js, propHandle) here would be incorrect, since that\n        // decides whether it is a function *that can be serialized as a stub*. JsRpcProperty\n        // is (at present) considered non-serializable in itself, but when traversing the\n        // pipeline path, we may have descended into a stub and its properties, thus we could\n        // actually be invoking a JsRpcProperty here. As long as it is in fact callable, we will\n        // allow it.\n        JSG_REQUIRE(propHandle->IsFunction(), TypeError,\n            kj::str(\"\\\"\", methodNameForTrace, \"\\\" is not a function.\"));\n        auto fn = propHandle.As<v8::Function>();\n\n        kj::Maybe<rpc::JsValue::Reader> args;\n        if (op.hasCallWithArgs()) {\n          args = op.getCallWithArgs();\n        }\n\n        InvocationResult invocationResult;\n        KJ_IF_SOME(envCtx, targetInfo.envCtx) {\n          invocationResult = invokeFnInsertingEnvCtx(\n              js, methodNameForTrace, fn, thisArg, args, envCtx.env, envCtx.ctx);\n        } else {\n          invocationResult = invokeFn(js, fn, thisArg, args);\n        }\n\n        // We have a function, so let's call it and serialize the result for RPC.\n        // If the function returns a promise we will wait for the promise to finish so we can\n        // serialize the result.\n        return handleResult(kj::mv(invocationResult));\n      }\n\n      case rpc::JsRpcTarget::CallParams::Operation::GET_PROPERTY:\n        return handleResult({.returnValue = propHandle});\n    }\n\n    KJ_FAIL_ASSERT(\"unknown JsRpcTarget::CallParams::Operation\", (uint)op.which());\n  }\n\n  struct GetPropResult {\n    v8::Local<v8::Value> handle;\n    v8::Local<v8::Object> thisArg;\n  };\n\n  [[noreturn]] static void failLookup(kj::StringPtr kjName) {\n    JSG_FAIL_REQUIRE(\n        TypeError, kj::str(\"The RPC receiver does not implement the method \\\"\", kjName, \"\\\".\"));\n  }\n\n  GetPropResult tryGetProperty(jsg::Lock& js,\n      jsg::JsObject object,\n      rpc::JsRpcTarget::CallParams::Reader callParams,\n      bool allowInstanceProperties,\n      IoContext& ctx) {\n    auto prototypeOfObject = KJ_ASSERT_NONNULL(js.obj().getPrototype(js).tryCast<jsg::JsObject>());\n\n    // Get the named property of `object`.\n    auto getProperty = [&](kj::StringPtr kjName) {\n      JSG_REQUIRE(!isReservedName(kjName), TypeError,\n          kj::str(\"'\", kjName, \"' is a reserved method and cannot be called over RPC.\"));\n\n      jsg::JsValue jsName = js.strIntern(kjName);\n\n      if (allowInstanceProperties) {\n        // This is a simple object. Its own properties are considered to be accessible over RPC, but\n        // inherited properties (i.e. from Object.prototype) are not.\n        if (!object.has(js, jsName, jsg::JsObject::HasOption::OWN)) {\n          failLookup(kjName);\n        }\n        return object.get(js, jsName);\n      } else {\n        // This is an instance of a valid RPC target class.\n        if (object.has(js, jsName, jsg::JsObject::HasOption::OWN)) {\n          // We do NOT allow own properties, only class properties.\n          failLookup(kjName);\n        }\n\n        auto value = object.get(js, jsName);\n        if (value == prototypeOfObject.get(js, jsName)) {\n          // This property is inherited from the prototype of `Object`. Don't allow.\n          failLookup(kjName);\n        }\n\n        return value;\n      }\n    };\n\n    kj::Maybe<jsg::JsValue> result;\n\n    switch (callParams.which()) {\n      case rpc::JsRpcTarget::CallParams::METHOD_NAME: {\n        result = getProperty(callParams.getMethodName());\n        break;\n      }\n\n      case rpc::JsRpcTarget::CallParams::METHOD_PATH: {\n        auto path = callParams.getMethodPath();\n        auto n = path.size();\n\n        if (n == 0) {\n          // Call the target itself as a function.\n          result = object;\n        } else {\n          bool inStub = false;\n          for (auto i: kj::zeroTo(n - 1)) {\n            // For each property name except the last, look up the property and replace `object`\n            // with it.\n            kj::StringPtr name = path[i];\n            auto next = getProperty(name);\n\n            KJ_IF_SOME(o, next.tryCast<jsg::JsObject>()) {\n              object = o;\n            } else {\n              // Not an object, doesn't have further properties.\n              failLookup(name);\n            }\n\n            // If the object is a Proxy, then `isInstanceOf<JsRpcTarget>()` won't actually work,\n            // because the Proxy is not an instance of any native type. But for our purposes,\n            // RpcTarget is only a marker used to indicate what semantics are desired.\n            bool isProxyOfRpcTarget = false;\n            if (jsg::JsValue(object).isProxy()) {\n              // Unfortunatley in this case we need to follow the prototype chain manually, looking\n              // for `JsRpcTarget`.\n              js.withinHandleScope([&]() {\n                auto proto = object.getPrototype(js);\n                auto prototypeOfRpcTarget = js.getPrototypeFor<JsRpcTarget>();\n\n                for (;;) {\n                  auto objProto = KJ_UNWRAP_OR(proto.tryCast<jsg::JsObject>(), break);\n                  if (objProto == prototypeOfRpcTarget) {\n                    isProxyOfRpcTarget = true;\n                    break;\n                  }\n                  proto = objProto.getPrototype(js);\n                }\n              });\n            }\n\n            // Decide whether the new object is a suitable RPC target.\n            if (object.getPrototype(js) == prototypeOfObject) {\n              // Yes. It's a simple object.\n              allowInstanceProperties = true;\n            } else if (isProxyOfRpcTarget || object.isInstanceOf<JsRpcTarget>(js)) {\n              // Yes. It's a JsRpcTarget.\n              allowInstanceProperties = false;\n            } else if (object.isInstanceOf<JsRpcStub>(js) || object.isInstanceOf<Fetcher>(js) ||\n                (inStub && object.isInstanceOf<JsRpcProperty>(js))) {\n              // Yes. It's a JsRpcStub or Fetcher. We should allow descending into the stub.\n              // Note that the wildcard property of a stub is a prototype property, not an instance\n              // property, so setting allowInstanceProperties = false here gets the behavior we\n              // want.\n              // TODO(someday): We'll need to support JsRpcPromise here if someday we allow it to\n              //    be serialized.\n              allowInstanceProperties = false;\n\n              // We will only traverse JsRpcProperty if we got there by descending through a\n              // JsRpcStub. At present you can't just pull a property of a stub and return it.\n              inStub = true;\n            } else if (isFunctionForRpc(js, object)) {\n              // Yes. It's a function.\n              allowInstanceProperties = true;\n            } else {\n              failLookup(name);\n            }\n          }\n\n          result = getProperty(path[n - 1]);\n        }\n\n        break;\n      }\n    }\n\n    return {\n      .handle = KJ_ASSERT_NONNULL(result, \"unknown CallParams type\", (uint)callParams.which()),\n      .thisArg = object,\n    };\n  }\n\n  struct InvocationResult {\n    v8::Local<v8::Value> returnValue;\n    kj::Maybe<kj::Own<RpcStubDisposalGroup>> paramDisposalGroup;\n    kj::Maybe<rpc::JsValue::StreamSink::Client> streamSink;\n  };\n\n  // Deserializes the arguments and passes them to the given function.\n  static InvocationResult invokeFn(jsg::Lock& js,\n      v8::Local<v8::Function> fn,\n      v8::Local<v8::Object> thisArg,\n      kj::Maybe<rpc::JsValue::Reader> args) {\n    // We received arguments from the client, deserialize them back to JS.\n    KJ_IF_SOME(a, args) {\n      auto [value, disposalGroup, streamSink] = deserializeJsValue(js, a);\n      auto args = KJ_REQUIRE_NONNULL(\n          value.tryCast<jsg::JsArray>(), \"expected JsArray when deserializing arguments.\");\n      // Call() expects a `Local<Value> []`... so we populate an array.\n\n      v8::LocalVector<v8::Value> arguments(js.v8Isolate, args.size());\n      for (size_t i = 0; i < args.size(); ++i) {\n        arguments[i] = args.get(js, i);\n      }\n\n      InvocationResult result{\n        .returnValue =\n            jsg::check(fn->Call(js.v8Context(), thisArg, arguments.size(), arguments.data())),\n        .streamSink = kj::mv(streamSink),\n      };\n      if (!disposalGroup->empty()) {\n        result.paramDisposalGroup = kj::mv(disposalGroup);\n      }\n      return result;\n    } else {\n      return {.returnValue = jsg::check(fn->Call(js.v8Context(), thisArg, 0, nullptr))};\n    }\n  };\n\n  // Like `invokeFn`, but inject the `env` and `ctx` values between the first and second\n  // parameters. Used for service bindings that use functional syntax.\n  static InvocationResult invokeFnInsertingEnvCtx(jsg::Lock& js,\n      kj::StringPtr methodName,\n      v8::Local<v8::Function> fn,\n      v8::Local<v8::Object> thisArg,\n      kj::Maybe<rpc::JsValue::Reader> args,\n      v8::Local<v8::Value> env,\n      jsg::JsObject ctx) {\n    // Determine the function arity (how many parameters it was declared to accept) by reading the\n    // `.length` attribute.\n    auto arity = js.withinHandleScope([&]() {\n      auto length = jsg::check(fn->Get(js.v8Context(), js.strIntern(\"length\")));\n      return jsg::check(length->IntegerValue(js.v8Context()));\n    });\n\n    // Avoid excessive allocation from a maliciously-set `length`.\n    JSG_REQUIRE(arity >= 0 && arity < 256, TypeError,\n        \"RPC function has unreasonable length attribute: \", arity);\n\n    if (arity < 3) {\n      // If a function has fewer than three arguments, reproduce the historical behavior where\n      // we'd pass the main argument followed by `env` and `ctx` and the undeclared parameters\n      // would just be truncated.\n      arity = 3;\n    }\n\n    kj::Maybe<kj::Own<RpcStubDisposalGroup>> paramDisposalGroup;\n    kj::Maybe<rpc::JsValue::StreamSink::Client> streamSink;\n\n    // We're going to pass all the arguments from the client to the function, but we are going to\n    // insert `env` and `ctx`. We assume the last two arguments that the function declared are\n    // `env` and `ctx`, so we can determine where to insert them based on the function's arity.\n    kj::Maybe<jsg::JsArray> argsArrayFromClient;\n    size_t argCountFromClient = 0;\n    KJ_IF_SOME(a, args) {\n      auto [value, disposalGroup, ss] = deserializeJsValue(js, a);\n      streamSink = kj::mv(ss);\n\n      auto array = KJ_REQUIRE_NONNULL(\n          value.tryCast<jsg::JsArray>(), \"expected JsArray when deserializing arguments.\");\n      argCountFromClient = array.size();\n      argsArrayFromClient = kj::mv(array);\n\n      if (!disposalGroup->empty()) {\n        paramDisposalGroup = kj::mv(disposalGroup);\n      }\n    }\n\n    // For now, we are disallowing multiple arguments with bare function syntax, due to a footgun:\n    // if you forget to add `env, ctx` to your arg list, then the last arguments from the client\n    // will be replaced with `env` and `ctx`. Probably this would be quickly noticed in testing,\n    // but if you were to accidentally reflect `env` back to the client, it would be a severe\n    // security flaw.\n    JSG_REQUIRE(arity == 3, TypeError, \"Cannot call handler function \\\"\", methodName,\n        \"\\\" over RPC because it has the wrong \"\n        \"number of arguments. A simple function handler can only be called over RPC if it has \"\n        \"exactly the arguments (arg, env, ctx), where only the first argument comes from the \"\n        \"client. To support multi-argument RPC functions, use class-based syntax (extending \"\n        \"WorkerEntrypoint) instead.\");\n    JSG_REQUIRE(argCountFromClient == 1, TypeError, \"Attempted to call RPC function \\\"\", methodName,\n        \"\\\" with the wrong number of arguments. \"\n        \"When calling a top-level handler function that is not declared as part of a class, you \"\n        \"must always send exactly one argument. In order to support variable numbers of \"\n        \"arguments, the server must use class-based syntax (extending WorkerEntrypoint) \"\n        \"instead.\");\n\n    v8::LocalVector<v8::Value> arguments(js.v8Isolate, kj::max(argCountFromClient + 2, arity));\n\n    for (auto i: kj::zeroTo(arity - 2)) {\n      if (argCountFromClient > i) {\n        arguments[i] = KJ_ASSERT_NONNULL(argsArrayFromClient).get(js, i);\n      } else {\n        arguments[i] = js.undefined();\n      }\n    }\n\n    arguments[arity - 2] = env;\n    arguments[arity - 1] = ctx;\n\n    KJ_IF_SOME(a, argsArrayFromClient) {\n      for (size_t i = arity - 2; i < argCountFromClient; ++i) {\n        arguments[i + 2] = a.get(js, i);\n      }\n    }\n\n    return {\n      .returnValue =\n          jsg::check(fn->Call(js.v8Context(), thisArg, arguments.size(), arguments.data())),\n      .paramDisposalGroup = kj::mv(paramDisposalGroup),\n      .streamSink = kj::mv(streamSink),\n    };\n  };\n};\n\nclass TransientJsRpcTarget final: public JsRpcTargetBase {\n public:\n  TransientJsRpcTarget(\n      jsg::Lock& js, IoContext& ioCtx, jsg::JsObject object, bool allowInstanceProperties = false)\n      : JsRpcTargetBase(ioCtx, MayOutliveIncomingRequest()),\n        handles(ioCtx.addObjectReverse(kj::heap<Handles>(js, object))),\n        allowInstanceProperties(allowInstanceProperties) {\n    // Check for the existence of a dispose function now so that the destructor doesn't have to\n    // take an isolate lock if there isn't one.\n    auto getResult = object.get(js, js.symbolDispose());\n    if (getResult.isFunction()) {\n      auto dispose = jsg::V8Ref<v8::Function>(\n          js.v8Isolate, v8::Local<v8::Value>(getResult).As<v8::Function>());\n      disposeFulfiller = addDisposeTask(js, ioCtx, object, kj::mv(dispose), {});\n    }\n  }\n\n  // Use this version of the constructor to pass the dispose function separately.\n  TransientJsRpcTarget(jsg::Lock& js,\n      IoContext& ioCtx,\n      jsg::JsObject object,\n      kj::Maybe<jsg::V8Ref<v8::Function>> dispose,\n      kj::Vector<kj::Own<void>> stubDisposers,\n      bool allowInstanceProperties = false)\n      : JsRpcTargetBase(ioCtx, MayOutliveIncomingRequest()),\n        handles(ioCtx.addObjectReverse(kj::heap<Handles>(js, object))),\n        disposeFulfiller(addDisposeTask(js, ioCtx, object, kj::mv(dispose), kj::mv(stubDisposers))),\n        allowInstanceProperties(allowInstanceProperties) {}\n\n  ~TransientJsRpcTarget() noexcept(false) {\n    KJ_IF_SOME(f, kj::mv(disposeFulfiller)) {\n      f->fulfill();\n    }\n  }\n\n  TargetInfo getTargetInfo(Worker::Lock& lock, IoContext& ioCtx) override {\n    return {\n      .target = handles->object.getHandle(lock),\n      .envCtx = kj::none,\n      .allowInstanceProperties = allowInstanceProperties,\n    };\n  }\n\n private:\n  struct Handles {\n    jsg::JsRef<jsg::JsObject> object;\n\n    Handles(jsg::Lock& js, jsg::JsObject object): object(js, object) {}\n  };\n\n  // This object could outlive the IoContext (that's why `JsRpcTargetBase` holds a `WeakRef` to the\n  // context). That means hypothetically it could also outlive the isolate. We therefore need to\n  // place these handles in a `ReverseIoOwn` so that if the `IoContext` dies before we do, they are\n  // dropped at that point.\n  ReverseIoOwn<Handles> handles;\n\n  // When fulfilled, calls the original object's dispose function.\n  kj::Maybe<kj::Own<kj::PromiseFulfiller<void>>> disposeFulfiller;\n\n  static kj::Maybe<kj::Own<kj::PromiseFulfiller<void>>> addDisposeTask(jsg::Lock& js,\n      IoContext& ctx,\n      jsg::JsObject object,\n      kj::Maybe<jsg::V8Ref<v8::Function>> dispose,\n      kj::Vector<kj::Own<void>> stubDisposers) {\n    if (dispose == kj::none && stubDisposers.empty()) {\n      // Don't bother scheduling disposal if we have neither.\n      return kj::none;\n    }\n\n    auto obj = jsg::JsRef<jsg::JsObject>(js, object);\n    auto [promise, fulfiller] = kj::newPromiseAndFulfiller<void>();\n    auto jsPromise = ctx.awaitIo(js, kj::mv(promise),\n        [obj = kj::mv(obj), dispose = kj::mv(dispose), stubDiposers = kj::mv(stubDisposers)](\n            jsg::Lock& js) {\n      KJ_IF_SOME(d, dispose) {\n        jsg::check(d.getHandle(js)->Call(js.v8Context(), obj.getHandle(js), 0, nullptr));\n      }\n\n      // Our stub disposers are dropped at the end of this task.\n    });\n    ctx.addTask(ctx.awaitJs(js, kj::mv(jsPromise)));\n    return kj::mv(fulfiller);\n  }\n\n  bool allowInstanceProperties;\n\n  bool isReservedName(kj::StringPtr name) override {\n    if (  // dup() is reserved to duplicate the stub itself, pointing to the same object.\n        name == \"dup\" ||\n\n        // All JS classes define a method `constructor` on the prototype, but we don't actually\n        // want this to be callable over RPC!\n        name == \"constructor\") {\n      return true;\n    }\n    return false;\n  }\n\n  void maybeSetJsRpcInfo(IoContext& ctx, const kj::ConstString& methodNameForTrace) override {}\n};\n\n// See comment at call site for explanation.\nstatic rpc::JsRpcTarget::Client makeJsRpcTargetForSingleLoopbackCall(\n    jsg::Lock& js, jsg::JsObject obj) {\n  // We intentionally do not want to hook up the disposer here since we're not taking ownership\n  // of the object.\n  return rpc::JsRpcTarget::Client(kj::heap<TransientJsRpcTarget>(\n      js, IoContext::current(), obj, kj::none, kj::Vector<kj::Own<void>>(), true));\n}\n\ntemplate <typename Func>\nMakeCallPipeline::Result serializeJsValueWithPipeline(jsg::Lock& js,\n    jsg::JsValue value,\n    Func makeBuilder,\n    RpcSerializerExternalHandler::GetStreamHandlerFunc getStreamHandlerFunc) {\n  auto maybeDispose = js.withinHandleScope([&]() -> kj::Maybe<jsg::V8Ref<v8::Function>> {\n    jsg::JsObject obj = KJ_UNWRAP_OR(value.tryCast<jsg::JsObject>(), { return kj::none; });\n\n    if (obj.getPrototype(js) == js.obj().getPrototype(js)) {\n      // It's a plain object.\n      jsg::JsValue disposeProperty = obj.get(js, js.symbolDispose());\n\n      // We don't want the disposer to be serialized, so delete it from the object. (Remember\n      // that a new `dispose()` method will always be added on the client side).\n      obj.delete_(js, js.symbolDispose());\n\n      if (disposeProperty.isFunction()) {\n        auto localDispose = v8::Local<v8::Value>(disposeProperty).As<v8::Function>();\n        return jsg::V8Ref<v8::Function>(js.v8Isolate, localDispose);\n      }\n    }\n\n    return kj::none;\n  });\n  auto hasDispose = maybeDispose != kj::none;\n\n  // Now that we've extracted our dispose function, we can serialize our value.\n  RpcSerializerExternalHandler externalHandler(\n      RpcSerializerExternalHandler::TRANSFER, kj::mv(getStreamHandlerFunc));\n  serializeJsValue(js, value, externalHandler, kj::mv(makeBuilder));\n\n  auto stubDisposers = externalHandler.releaseStubDisposers();\n\n  return js.withinHandleScope([&]() -> MakeCallPipeline::Result {\n    jsg::JsObject obj = KJ_UNWRAP_OR(value.tryCast<jsg::JsObject>(), {\n      // Primitive value. Return a fake pipeline just so that we get nice errors if someone tries\n      // to pipeline on it. (If we return null, we'll get \"called null capability\" out of\n      // Cap'n Proto, which will be treated as an internal error.)\n      return MakeCallPipeline::NonPipelinable{\n        .errorPipeline = rpc::JsRpcTarget::Client(kj::heap<TransientJsRpcTarget>(\n            js, IoContext::current(), js.obj(), kj::none, kj::Vector<kj::Own<void>>(), true))};\n    });\n\n    if (obj.getPrototype(js) == js.obj().getPrototype(js)) {\n      // It's a plain object.\n      auto pipeline = kj::heap<TransientJsRpcTarget>(\n          js, IoContext::current(), obj, kj::mv(maybeDispose), kj::mv(stubDisposers), true);\n\n      return MakeCallPipeline::Object{\n        .cap = rpc::JsRpcTarget::Client(kj::mv(pipeline)), .hasDispose = hasDispose};\n    } else if (obj.isInstanceOf<JsRpcStub>(js)) {\n      // It's just a stub. It'll serialize as a single stub, obviously.\n      return MakeCallPipeline::SingleStub();\n    } else if (obj.isInstanceOf<JsRpcTarget>(js)) {\n      // It's an RPC target. It will be serialized as a single stub.\n      return MakeCallPipeline::SingleStub();\n    } else if (isFunctionForRpc(js, obj)) {\n      // It's a plain function. It will be serialized as a single stub.\n      return MakeCallPipeline::SingleStub();\n    } else if (obj.isInstanceOf<Fetcher>(js)) {\n      // It's a plain fetcher. We want to allow pipelining on it, but we also actually need to\n      // serialize it, so we can't use `SingleStub()`. Note we set `allowInstanceProperties` to\n      // `false` here because the wildcard property of a `Fetcher` is a prototype property, and\n      // that's what we want to expose for pipelining.\n      auto pipeline = kj::heap<TransientJsRpcTarget>(\n          js, IoContext::current(), obj, kj::mv(maybeDispose), kj::mv(stubDisposers), false);\n\n      return MakeCallPipeline::Object{\n        .cap = rpc::JsRpcTarget::Client(kj::mv(pipeline)), .hasDispose = hasDispose};\n    } else {\n      // Not an RPC object. Could be a String or other serializable types that derive from Object.\n      // Similar to primitive types, we return a fake pipeline for error-handling reasons.\n      // TODO(soon): What if someone returns e.g. a Map with a disposer on it? Should we honor that\n      //   disposer?\n      return MakeCallPipeline::NonPipelinable{\n        .errorPipeline = rpc::JsRpcTarget::Client(kj::heap<TransientJsRpcTarget>(\n            js, IoContext::current(), js.obj(), kj::none, kj::Vector<kj::Own<void>>(), true))};\n    }\n  });\n}\n\n// RpcStub are allowed to wrap:\n// * RpcTargets\n// * Functions\n// * Plain objects (only when created explicitly via `new RpcStub`)\n//\n// This function checks for these and returns:\n// * kj::none if it's not a valid type to be wrapped in as tub.\n// * The value for allowInstanceProperties if it is.\nkj::Maybe<bool> checkStubType(jsg::Lock& js, jsg::JsObject handle) {\n  return js.withinHandleScope([&]() -> kj::Maybe<bool> {\n    // TODO(perf): We should really cache `prototypeOfObject` somewhere so we don't have to create\n    //   an object to get it. (We do this other places in this file, too...)\n    auto prototypeOfObject = KJ_ASSERT_NONNULL(js.obj().getPrototype(js).tryCast<jsg::JsObject>());\n    auto prototypeOfRpcTarget = js.getPrototypeFor<JsRpcTarget>();\n    auto proto = handle.getPrototype(js);\n    if (proto == prototypeOfObject) {\n      // A regular object. Allow access to instance properties.\n      return true;\n    } else {\n      // Walk the prototype chain looking for RpcTarget.\n      //\n      // (Note we can't simply use handle.isInstanceOf<JsRpcTarget>() because that doesn't work\n      // correctly for proxies. Since RpcTarget is only used as a marker, we don't really need\n      // the object to be an instance of it -- we just care if it's in the prototype chain, even\n      // if the prototype chain is faked by the Proxy.)\n      //\n      // TODO(someday): Consider whether `new RpcStub(obj)` should work on arbitrary types. This\n      //   could be a useful way to say: \"I am explicitly opting into treating this like an\n      //   RpcTarget even though I do not have the ability to make its type extend RpcTarget.\"\n      for (;;) {\n        if (proto == prototypeOfRpcTarget) {\n          // An RpcTarget, don't allow instance properties.\n          return false;\n        }\n\n        KJ_IF_SOME(protoObj, proto.tryCast<jsg::JsObject>()) {\n          proto = protoObj.getPrototype(js);\n        } else if (isFunctionForRpc(js, handle)) {\n          // This is NOT an RpcTarget, but it IS callable as a function, so treat it as such.\n          return true;\n        } else {\n          // End of prototype chain, and didn't find RpcTarget.\n          return kj::none;\n        }\n      }\n    }\n  });\n}\n\njsg::Ref<JsRpcStub> JsRpcStub::constructor(jsg::Lock& js, jsg::JsObject object) {\n  auto& ioctx = IoContext::current();\n\n  bool allowInstanceProperties = JSG_REQUIRE_NONNULL(checkStubType(js, object), TypeError,\n      \"RpcStubs can only wrap plain objects, functions, and RpcTarget derivatives.\");\n\n  rpc::JsRpcTarget::Client cap =\n      kj::heap<TransientJsRpcTarget>(js, ioctx, object, allowInstanceProperties);\n\n  return js.alloc<JsRpcStub>(ioctx.addObject(kj::heap(kj::mv(cap))));\n}\n\nvoid JsRpcTarget::serialize(jsg::Lock& js, jsg::Serializer& serializer) {\n  // Serialize by effectively creating a `JsRpcStub` around this object and serializing that.\n  // Except we don't actually want to do _exactly_ that, because we do not want to actually create\n  // a `JsRpcStub` locally. So do the important parts of `JsRpcStub::constructor()` followed by\n  // `JsRpcStub::serialize()`.\n\n  auto& handler = JSG_REQUIRE_NONNULL(serializer.getExternalHandler(), DOMDataCloneError,\n      \"Remote RPC references can only be serialized for RPC.\");\n  auto externalHandler = dynamic_cast<RpcSerializerExternalHandler*>(&handler);\n  JSG_REQUIRE(externalHandler != nullptr, DOMDataCloneError,\n      \"Remote RPC references can only be serialized for RPC.\");\n\n  // Handle can't possibly be missing during serialization, it's how we got here.\n  auto handle = jsg::JsObject(KJ_ASSERT_NONNULL(JSG_THIS.tryGetHandle(js)));\n\n  if (externalHandler->getStubOwnership() == RpcSerializerExternalHandler::DUPLICATE) {\n    // This message isn't supposed to take ownership of stubs. What does that mean for an\n    // RpcTarget? You might argue that it means we should never call the disposer. But that's not\n    // really enough: what if the real owner *does* call the disposer, before our stub is done\n    // with it? How do we make sure the RpcTarget stays alive?\n    //\n    // Things get clearer if we look at a real use case: pure-JS Cap'n Web stubs. We don't see\n    // them as stubs (since they are not instances of JsRpcStub). Instead, we see them as\n    // RpcTargets. But we need the semantics to come out the same: when passed as a parameter\n    // to a native RPC call, we need to duplicate the stub, because the original copy might very\n    // well be disposed before we use it.\n    //\n    // How do we duplicate this non-native stub? Well... proper way to duplicate a pure-JS Cap'n\n    // Web stub is, of course, to call its `dup()` method.\n    //\n    // So how about we just do that? If the target has a `dup()` method, we call it, and we take\n    // ownership of the result, instead of taking ownership of the original object.\n    auto dup = handle.get(js, \"dup\");\n    KJ_IF_SOME(dupFunc, dup.tryCast<jsg::JsFunction>()) {\n      auto replacement = dupFunc.call(js, handle);\n      bool replaced = false;\n\n      // We got a duplicate. Is it still an RpcTarget?\n      KJ_IF_SOME(replacementObj, replacement.tryCast<jsg::JsObject>()) {\n        if (replacementObj.isInstanceOf<JsRpcTarget>(js)) {\n          // It is! Let's replace our handle with the duplicate!\n          handle = replacementObj;\n          replaced = true;\n        }\n      }\n\n      JSG_REQUIRE(replaced, DOMDataCloneError,\n          \"Couldn't create a stub for the RcpTarget because it has a dup() method which did not \"\n          \"return another RpcTarget. Either remove the dup() method or make sure it returns an \"\n          \"RpcTarget.\");\n    } else {\n      // If no dup() method was present, then what?\n      //\n      // The pedantic argument would say: we need to throw an exception. But that would lead to a\n      // pretty poor development experience as people would have to fiddle with adding dup()\n      // methods to all their RpcTargets.\n      //\n      // Another argument might say: we should just use the RpcTarget but never call the disposer\n      // since we don't own it. But that would probably be confusing. People would wonder why their\n      // disposers are never called.\n      //\n      // If someone passes an RpcTarget with no dup() method, but which does have a disposer, as\n      // the argument to an RPC method, *probably* they just want the disposer to be called when\n      // the callee is done with the object. That is, they want us to take ownership after all. If\n      // that is *not* what they want, then they can always implement a dup() method to make it\n      // clear.\n      //\n      // So, we will just \"take ownership\" of the target after all, and call its disposer.\n    }\n  }\n\n  rpc::JsRpcTarget::Client cap = kj::heap<TransientJsRpcTarget>(js, IoContext::current(), handle);\n\n  externalHandler->write([cap = kj::mv(cap)](rpc::JsValue::External::Builder builder) mutable {\n    builder.setRpcTarget(kj::mv(cap));\n  });\n}\n\nvoid RpcSerializerExternalHandler::serializeFunction(\n    jsg::Lock& js, jsg::Serializer& serializer, v8::Local<v8::Function> func) {\n  serializer.writeRawUint32(static_cast<uint>(rpc::SerializationTag::JS_RPC_STUB));\n\n  auto handle = jsg::JsObject(func);\n\n  // Similar to JsRpcTarget::serialize(), we may need to dup() the function.\n  if (stubOwnership == RpcSerializerExternalHandler::DUPLICATE) {\n    auto dup = handle.get(js, \"dup\");\n    KJ_IF_SOME(dupFunc, dup.tryCast<jsg::JsFunction>()) {\n      auto replacement = dupFunc.call(js, handle);\n      bool replaced = false;\n\n      // We got a duplicate. Is it still a Function?\n      KJ_IF_SOME(replacementObj, replacement.tryCast<jsg::JsObject>()) {\n        if (isFunctionForRpc(js, replacementObj)) {\n          // It is! Let's replace our handle with the duplicate!\n          handle = replacementObj;\n          replaced = true;\n        }\n      }\n\n      JSG_REQUIRE(replaced, DOMDataCloneError,\n          \"Couldn't create a stub for the function because it has a dup() method which did not \"\n          \"return another function. Either remove the dup() method or make sure it returns a \"\n          \"function.\");\n    }\n  }\n\n  rpc::JsRpcTarget::Client cap =\n      kj::heap<TransientJsRpcTarget>(js, IoContext::current(), handle, true);\n  write([cap = kj::mv(cap)](rpc::JsValue::External::Builder builder) mutable {\n    builder.setRpcTarget(kj::mv(cap));\n  });\n}\n\nvoid RpcSerializerExternalHandler::serializeProxy(\n    jsg::Lock& js, jsg::Serializer& serializer, v8::Local<v8::Proxy> proxy) {\n  auto handle = jsg::JsObject(proxy);\n\n  // Proxies are allowed to present themselves as anything that you could pass to `new RpcStub`.\n  //\n  // Note there's an intentional quirk here: If the Proxy presents itself as a plain object, we\n  // wrap it in a stub, rather than serialize the object. This enables the Proxy to continue\n  // intercepting property accesses when they happen, rather than have all the properties accessed\n  // and serialized upfront. However, in retrospect, this may haev been a bad choice, as it means a\n  // Proxy on a plain object cannot have exactly the same behavior as a plain object would have.\n  // Note that apps which explicitly want to prevent a plain object from being serialized over\n  // RPC can simply use `new RpcStub(object)` to explicitly wrap it in a stub -- no need to use\n  // a Proxy for that.\n  auto allowInstanceProperties = JSG_REQUIRE_NONNULL(checkStubType(js, handle), DOMDataCloneError,\n      \"Proxy could not be serialized because it is not a valid RPC receiver type. The \"\n      \"Proxy must emulate either a plain object or an RpcTarget, as indicated by the \"\n      \"Proxy's prototype chain.\");\n\n  // Similar to JsRpcTarget::serialize(), we may need to dup() the proxy.\n  if (stubOwnership == RpcSerializerExternalHandler::DUPLICATE) {\n    auto dup = handle.get(js, \"dup\");\n    KJ_IF_SOME(dupFunc, dup.tryCast<jsg::JsFunction>()) {\n      auto replacement = dupFunc.call(js, handle);\n      bool replaced = false;\n\n      // We got a duplicate. Is it still the same type?\n      KJ_IF_SOME(replacementObj, replacement.tryCast<jsg::JsObject>()) {\n        KJ_IF_SOME(stubType, checkStubType(js, replacementObj)) {\n          if (stubType == allowInstanceProperties) {\n            // It is! Let's replace our handle with the duplicate!\n            handle = replacementObj;\n            replaced = true;\n          }\n        }\n      }\n\n      JSG_REQUIRE(replaced, DOMDataCloneError,\n          \"Couldn't create a stub for the Proxy because it has a dup() method which did not \"\n          \"return the same underlying type (RpcTarget or Function) as the Proxy itself represents. \"\n          \"Either remove the dup() method or make sure it returns an RpcTarget.\");\n    }\n  }\n\n  // Great, we've concluded we can indeed point a stub at this proxy.\n  serializer.writeRawUint32(static_cast<uint>(rpc::SerializationTag::JS_RPC_STUB));\n\n  rpc::JsRpcTarget::Client cap =\n      kj::heap<TransientJsRpcTarget>(js, IoContext::current(), handle, allowInstanceProperties);\n  write([cap = kj::mv(cap)](rpc::JsValue::External::Builder builder) mutable {\n    builder.setRpcTarget(kj::mv(cap));\n  });\n}\n\n// JsRpcTarget implementation specific to entrypoints. This is used to deliver the first, top-level\n// call of an RPC session.\nclass EntrypointJsRpcTarget final: public JsRpcTargetBase {\n public:\n  EntrypointJsRpcTarget(IoContext& ioCtx,\n      kj::Maybe<kj::StringPtr> entrypointName,\n      kj::Maybe<Worker::VersionInfo> versionInfo,\n      Frankenvalue props,\n      kj::Maybe<kj::String> wrapperModule,\n      kj::Maybe<kj::Own<BaseTracer>> tracer)\n      : JsRpcTargetBase(ioCtx, CantOutliveIncomingRequest()),\n        ioCtx(ioCtx),\n        // Most of the time we don't really have to clone this but it's hard to fully prove, so\n        // let's be safe.\n        entrypointName(entrypointName.map([](kj::StringPtr s) { return kj::str(s); })),\n        versionInfo(kj::mv(versionInfo)),\n        props(kj::mv(props)),\n        wrapperModule(kj::mv(wrapperModule)),\n        tracer(kj::mv(tracer)) {}\n\n  // Override call() to emit the Return event when the top-level RPC call completes.\n  // This marks when the handler returned a value, NOT when all data has been streamed or all\n  // capabilities released.\n  kj::Promise<void> call(CallContext callContext) override {\n    return JsRpcTargetBase::call(kj::mv(callContext)).then([this]() {\n      KJ_IF_SOME(t, ioCtx.getWorkerTracer()) {\n        t.setReturn(ioCtx.now());\n      }\n    });\n  }\n\n  TargetInfo getTargetInfo(Worker::Lock& lock, IoContext& ioCtx) override {\n    jsg::Lock& js = lock;\n\n    auto handler = KJ_REQUIRE_NONNULL(lock.getExportedHandler(entrypointName, kj::mv(versionInfo),\n                                          kj::mv(props), ioCtx.getActor()),\n        \"Failed to get handler to worker.\");\n\n    if (handler->missingSuperclass && wrapperModule == kj::none) {\n      // JS RPC is not enabled on the server side, we cannot call any methods.\n      JSG_REQUIRE(FeatureFlags::get(js).getJsRpc(), TypeError,\n          \"The receiving Durable Object does not support RPC, because its class was not declared \"\n          \"with `extends DurableObject`. In order to enable RPC, make sure your class \"\n          \"extends the special class `DurableObject`, which can be imported from the module \"\n          \"\\\"cloudflare:workers\\\".\");\n    }\n\n    auto target = jsg::JsObject(handler->self.getHandle(lock));\n\n    KJ_IF_SOME(moduleName, wrapperModule) {\n      // We've been asked to apply a wrapper module to the handler. This is a builtin module whose\n      // default export is a class. The class is constructed with the constructor arguments being\n      // the ctx and env objects and the original DO instance.\n\n      // This mechanism probably won't work very well on anything other than Durable Objects, so\n      // block such usage for now. We could reconsider this if we have a use case in the future.\n      auto& actor = JSG_REQUIRE_NONNULL(\n          ioCtx.getActor(), Error, \"Wrapper modules can only be applied to Durable Objects.\");\n\n      auto module = JSG_REQUIRE_NONNULL(\n          js.resolveInternalModule(moduleName), Error, \"Unknown internal module: \", moduleName);\n      v8::Local<v8::Value> defaultExport = module.get(js, \"default\"_kj);\n      JSG_REQUIRE(defaultExport->IsFunction(), TypeError,\n          \"Internal module's default export is not a function.\");\n      auto func = defaultExport.As<v8::Function>();\n\n      v8::Local<v8::Value> args[3] = {actor.getCtx(js), actor.getEnv(js), target};\n      auto jsContext = js.v8Context();\n      v8::Local<v8::Value> result = jsg::check(func->NewInstance(jsContext, 3, args));\n      JSG_REQUIRE(result->IsObject(), TypeError,\n          \"Internal module wrapper function did not return an object.\");\n      target = jsg::JsObject(result.As<v8::Object>());\n    }\n\n    // clang-format off\n    TargetInfo targetInfo{\n      .target = target,\n      .envCtx = handler->ctx.map([&](jsg::Ref<ExecutionContext>& execCtx) -> EnvCtx {\n        return {\n          .env = handler->env.getHandle(js),\n          .ctx = lock.getWorker().getIsolate().getApi().wrapExecutionContext(js, execCtx.addRef()),\n        };\n      })\n    };\n    // clang-format on\n\n    // `targetInfo.envCtx` is present when we're invoking a freestanding function, and therefore\n    // `env` and `ctx` need to be passed as parameters. In that case, we our method lookup\n    // should obviously permit instance properties, since we expect the export is a plain object.\n    // Otherwise, though, the export is a class. In that case, we have set the rule that we will\n    // only allow class properties (aka prototype properties) to be accessed, to avoid\n    // programmers shooting themselves in the foot by forgetting to make their members private.\n    targetInfo.allowInstanceProperties = targetInfo.envCtx != kj::none;\n\n    return targetInfo;\n  }\n\n private:\n  IoContext& ioCtx;\n  kj::Maybe<kj::String> entrypointName;\n  kj::Maybe<Worker::VersionInfo> versionInfo;\n  Frankenvalue props;\n  kj::Maybe<kj::String> wrapperModule;\n  kj::Maybe<kj::Own<BaseTracer>> tracer;\n\n  bool isReservedName(kj::StringPtr name) override {\n    if (  // \"fetch\" and \"connect\" are treated specially on entrypoints.\n        name == \"fetch\" || name == \"connect\" ||\n\n        // These methods are reserved by the Durable Objects implementation.\n        // TODO(someday): Should they be reserved only for Durable Objects, not WorkerEntrypoint?\n        name == \"alarm\" || name == \"webSocketMessage\" || name == \"webSocketClose\" ||\n        name == \"webSocketError\" ||\n\n        // dup() is reserved to duplicate the stub itself, pointing to the same object.\n        name == \"dup\" ||\n\n        // All JS classes define a method `constructor` on the prototype, but we don't actually\n        // want this to be callable over RPC!\n        name == \"constructor\") {\n      return true;\n    }\n    return false;\n  }\n\n  void maybeSetJsRpcInfo(IoContext& ctx, const kj::ConstString& methodNameForTrace) override {\n    KJ_IF_SOME(tracer, ctx.getWorkerTracer()) {\n      tracer.setJsRpcInfo(ctx.getInvocationSpanContext(), ctx.now(), methodNameForTrace);\n    }\n  }\n};\n\n// A membrane which wraps the top-level JsRpcTarget of an RPC session on the server side. The\n// purpose of this membrane is to allow only a single top-level call, which then gets a\n// `CompletionMembrane` wrapped around it. Note that we can't just wrap `CompletionMembrane` around\n// the top-level object directly because that capability will not be dropped until the RPC session\n// completes, since it is actually returned as the result of the top-level RPC call, but that\n// call doesn't return until the `CompletionMembrane` says all capabilities were dropped, so this\n// would create a cycle.\nclass JsRpcSessionCustomEvent::ServerTopLevelMembrane final: public capnp::MembranePolicy,\n                                                             public kj::Refcounted {\n public:\n  explicit ServerTopLevelMembrane(kj::Own<kj::PromiseFulfiller<void>> doneFulfiller)\n      : completionMembrane(kj::refcounted<CompletionMembrane>(kj::mv(doneFulfiller))) {}\n\n  ~ServerTopLevelMembrane() noexcept(false) {\n    KJ_IF_SOME(cm, completionMembrane) {\n      cm->reject(\n          KJ_EXCEPTION(DISCONNECTED, \"JS RPC session canceled without calling an RPC method.\"));\n    }\n  }\n\n  kj::Maybe<capnp::Capability::Client> inboundCall(\n      uint64_t interfaceId, uint16_t methodId, capnp::Capability::Client target) override {\n    if (interfaceId == capnp::typeId<rpc::JsRpcTarget>()) {\n      // JsRpcTarget::call()\n      auto cm = kj::mv(JSG_REQUIRE_NONNULL(\n          completionMembrane, Error, \"Only one RPC method call is allowed on this object.\"));\n      completionMembrane = kj::none;\n      return capnp::membrane(kj::mv(target), kj::mv(cm));\n    } else if (interfaceId == capnp::typeId<rpc::JsValue::ExternalPusher>()) {\n      // ExternalPusher methods\n      //\n      // It's important that we use the same membrane that we'll use for call(), so that\n      // capabilities returned by the ExternalPusher will be wrapped in the membrane, hence they\n      // will be unwrapped when passed back through the membrane again to call().\n      auto& cm = *JSG_REQUIRE_NONNULL(\n          completionMembrane, Error, \"getExternalPusher() must be called before call()\");\n      return capnp::membrane(kj::mv(target), kj::addRef(cm));\n    } else {\n      KJ_FAIL_ASSERT(\"unkown interface ID for JsRpcTarget\");\n    }\n  }\n\n  kj::Maybe<capnp::Capability::Client> outboundCall(\n      uint64_t interfaceId, uint16_t methodId, capnp::Capability::Client target) override {\n    KJ_FAIL_ASSERT(\"ServerTopLevelMembrane shouldn't have outgoing capabilities\");\n  }\n\n  kj::Own<MembranePolicy> addRef() override {\n    return kj::addRef(*this);\n  }\n\n private:\n  kj::Maybe<kj::Own<CompletionMembrane>> completionMembrane;\n};\n\nkj::Promise<WorkerInterface::CustomEvent::Result> JsRpcSessionCustomEvent::run(\n    kj::Own<IoContext::IncomingRequest> incomingRequest,\n    kj::Maybe<kj::StringPtr> entrypointName,\n    kj::Maybe<Worker::VersionInfo> versionInfo,\n    Frankenvalue props,\n    kj::TaskSet& waitUntilTasks) {\n  IoContext& ioctx = incomingRequest->getContext();\n\n  incomingRequest->delivered();\n\n  KJ_DEFER({\n    // waitUntil() should allow extending execution on the server side even when the client\n    // disconnects.\n    waitUntilTasks.add(incomingRequest->drain().attach(kj::mv(incomingRequest)));\n  });\n\n  EntrypointJsRpcTarget target(ioctx, entrypointName, kj::mv(versionInfo), kj::mv(props),\n      kj::mv(wrapperModule), mapAddRef(incomingRequest->getWorkerTracer()));\n  capnp::RevocableServer<rpc::JsRpcTarget> revcableTarget(target);\n\n  try {\n    auto [donePromise, doneFulfiller] = kj::newPromiseAndFulfiller<void>();\n    capFulfiller->fulfill(capnp::membrane(\n        revcableTarget.getClient(), kj::refcounted<ServerTopLevelMembrane>(kj::mv(doneFulfiller))));\n\n    // `donePromise` resolves once there are no longer any capabilities pointing between the client\n    // and server as part of this session.\n    co_await donePromise.exclusiveJoin(ioctx.onAbort());\n\n    co_return WorkerInterface::CustomEvent::Result{.outcome = EventOutcome::OK};\n  } catch (...) {\n    // Make sure the top-level capability is revoked with the same exception that `run()` is\n    // throwing, rather than some generic revocation exception.\n    auto e = kj::getCaughtExceptionAsKj();\n    revcableTarget.revoke(kj::cp(e));\n    kj::throwFatalException(kj::mv(e));\n  }\n}\n\nkj::Promise<WorkerInterface::CustomEvent::Result> JsRpcSessionCustomEvent::sendRpc(\n    capnp::HttpOverCapnpFactory& httpOverCapnpFactory,\n    capnp::ByteStreamFactory& byteStreamFactory,\n    rpc::EventDispatcher::Client dispatcher) {\n  // We arrange to revoke all capabilities in this session as soon as `sendRpc()` completes or is\n  // canceled. Normally, the server side doesn't return if any capabilities still exist, so this\n  // only makes a difference in the case that some sort of an error occurred. We don't strictly\n  // have to revoke the capabilities as they are probably already broken anyway, but revoking them\n  // helps to ensure that the underlying transport isn't \"held open\" waiting for the JS garbage\n  // collector to actually collect the JsRpcStub objects.\n  auto revokePaf = kj::newPromiseAndFulfiller<void>();\n\n  KJ_DEFER({\n    if (revokePaf.fulfiller->isWaiting()) {\n      revokePaf.fulfiller->reject(KJ_EXCEPTION(DISCONNECTED, \"JS-RPC session canceled\"));\n    }\n  });\n\n  auto req = dispatcher.jsRpcSessionRequest();\n  auto sent = req.send();\n\n  rpc::JsRpcTarget::Client cap = sent.getTopLevel();\n\n  cap = capnp::membrane(kj::mv(cap), kj::refcounted<RevokerMembrane>(kj::mv(revokePaf.promise)));\n\n  // When no more capabilities exist on the connection, we want to proactively cancel the RPC.\n  // This is needed in particular for the case where the client is dropped without making any calls\n  // at all, e.g. because serializing the arguments failed. Unfortunately, simply dropping the\n  // capability obtained through `sent.getTopLevel()` above will not be detected by the server,\n  // because this is a pipeline capability on a call that is still running. So, if we don't\n  // actually cancel the connection client-side, the server will hang open waiting for the initial\n  // top-level call to arrive, and the event will appear never to complete at our end.\n  //\n  // TODO(cleanup): It feels like there's something wrong with the design here. Can we make this\n  //   less ugly?\n  auto completionPaf = kj::newPromiseAndFulfiller<void>();\n  cap = capnp::membrane(\n      kj::mv(cap), kj::refcounted<CompletionMembrane>(kj::mv(completionPaf.fulfiller)));\n\n  this->capFulfiller->fulfill(kj::mv(cap));\n\n  try {\n    co_await sent.ignoreResult().exclusiveJoin(kj::mv(completionPaf.promise));\n  } catch (...) {\n    auto e = kj::getCaughtExceptionAsKj();\n    if (revokePaf.fulfiller->isWaiting()) {\n      revokePaf.fulfiller->reject(kj::cp(e));\n    }\n    kj::throwFatalException(kj::mv(e));\n  }\n\n  co_return WorkerInterface::CustomEvent::Result{.outcome = EventOutcome::OK};\n}\n\n};  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/worker-rpc.h",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// Classes for calling a remote Worker/Durable Object's methods from the stub over RPC.\n// This file contains the generic stub object (JsRpcStub), as well as classes for sending and\n// delivering the RPC event.\n//\n// `JsRpcStub` specifically represents a capability that was introduced as part of some\n// broader RPC session. `Fetcher`, on the other hand, also supports RPC methods, where each method\n// call begins a new session (by dispatching a `jsRpcSession` custom event). Service bindings and\n// Durable Object stubs both extend from `Fetcher`, and so allow such calls.\n//\n// See worker-interface.capnp for the underlying protocol.\n\n#include <workerd/io/io-context.h>\n#include <workerd/io/trace.h>\n#include <workerd/io/worker-interface.capnp.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/modules-new.h>\n#include <workerd/jsg/ser.h>\n#include <workerd/jsg/url.h>\n\nnamespace workerd::api {\n\n// The 32MB limit is based on the fact that Cap'n Proto's default total message size limit is 64MB,\n// and we want to stay clear of that.\n// Additionally, considering total memory of the isolate is limited to 128MB a significantly larger\n// memory might cause unwarrented condemnations and terminations.\n// Applications which need to move large amounts of data should split the data into several smaller\n// chunks transmitted through separate calls.\nconstexpr size_t MAX_JS_RPC_MESSAGE_SIZE = 1u << 25;\n\n// ExternalHandler used when serializing RPC messages. Serialization functions with which to\n// handle RPC specially should use this.\nclass RpcSerializerExternalHandler final: public jsg::Serializer::ExternalHandler {\n public:\n  using GetStreamSinkFunc = kj::Function<rpc::JsValue::StreamSink::Client()>;\n  using GetExternalPusherFunc = kj::Function<rpc::JsValue::ExternalPusher::Client()>;\n  using GetStreamHandlerFunc = kj::OneOf<GetStreamSinkFunc, GetExternalPusherFunc>;\n\n  enum StubOwnership { TRANSFER, DUPLICATE };\n\n  // `getStreamSinkFunc` will be called at most once, the first time a stream is encountered in\n  // serialization, to get the StreamSink that should be used.\n  RpcSerializerExternalHandler(\n      StubOwnership stubOwnership, GetStreamHandlerFunc getStreamHandlerFunc)\n      : stubOwnership(stubOwnership),\n        getStreamHandlerFunc(kj::mv(getStreamHandlerFunc)) {}\n\n  inline StubOwnership getStubOwnership() {\n    return stubOwnership;\n  }\n\n  using BuilderCallback = kj::Function<void(rpc::JsValue::External::Builder)>;\n\n  // Returns the ExternalPusher for the remote side. Returns kj::none if this serialization is\n  // using the older StreamSink approach, in which case you need to call `writeStream()` instead.\n  kj::Maybe<rpc::JsValue::ExternalPusher::Client> getExternalPusher();\n\n  // Add an external. The value is a callback which will be invoked later to fill in the\n  // JsValue::External in the Cap'n Proto structure. The external array cannot be allocated until\n  // the number of externals are known, which is only after all calls to `add()` have completed,\n  // hence the need for a callback.\n  void write(BuilderCallback callback) {\n    externals.add(kj::mv(callback));\n  }\n\n  // Like write(), but use this when there is also a stream associated with the external, i.e.\n  // using StreamSink. This returns a capability which will eventually resolve to the stream.\n  //\n  // StreamSink is being replaced by ExternalPusher. You should only call writeStream() if\n  // getExternalPusher() returns kj::none. If ExternalPusher is available, this method will throw.\n  capnp::Capability::Client writeStream(BuilderCallback callback);\n\n  // Build the final list.\n  capnp::Orphan<capnp::List<rpc::JsValue::External>> build(capnp::Orphanage orphanage);\n\n  size_t size() {\n    return externals.size();\n  }\n\n  // Add an object that will be released once the serialized value is no longer needed to handle\n  // pipelined calls (i.e. when we are serializing a return value). In particular, for each stub\n  // that we found while serializing, we need to make sure its disposer is run later, so the\n  // Own<void>'s destructor runs said disposer.\n  //\n  // NOTE: These are called \"stub disposers\" because they are most commonly used to dispose stubs\n  //   that were part of the serialized value, but other kinds of serialized objects could use\n  //   this as well.\n  void addStubDisposer(kj::Own<void> disposer) {\n    stubDisposers.add(kj::mv(disposer));\n  }\n\n  // Get the list of disposers to be attached to the pipeline\n  kj::Vector<kj::Own<void>> releaseStubDisposers() {\n    return kj::mv(stubDisposers);\n  }\n\n  // We serialize functions by turning them into RPC stubs.\n  void serializeFunction(\n      jsg::Lock& js, jsg::Serializer& serializer, v8::Local<v8::Function> func) override;\n\n  // We can serialize a Proxy if it happens to wrap RpcTarget.\n  void serializeProxy(\n      jsg::Lock& js, jsg::Serializer& serializer, v8::Local<v8::Proxy> proxy) override;\n\n private:\n  StubOwnership stubOwnership;\n  GetStreamHandlerFunc getStreamHandlerFunc;\n\n  kj::Vector<BuilderCallback> externals;\n  kj::Vector<kj::Own<void>> stubDisposers;\n\n  kj::Maybe<rpc::JsValue::StreamSink::Client> streamSink;\n  kj::Maybe<rpc::JsValue::ExternalPusher::Client> externalPusher;\n};\n\nclass RpcStubDisposalGroup;\nclass StreamSinkImpl;\n\n// ExternalHandler used when deserializing RPC messages. Deserialization functions with which to\n// handle RPC specially should use this.\nclass RpcDeserializerExternalHandler final: public jsg::Deserializer::ExternalHandler {\n public:\n  // The `streamSink` parameter should be provided if a StreamSink already exists, e.g. when\n  // deserializing results. If omitted, it will be constructed on-demand.\n  RpcDeserializerExternalHandler(capnp::List<rpc::JsValue::External>::Reader externals,\n      RpcStubDisposalGroup& disposalGroup,\n      kj::Maybe<StreamSinkImpl&> streamSink)\n      : externals(externals),\n        disposalGroup(disposalGroup),\n        streamSink(streamSink) {}\n  ~RpcDeserializerExternalHandler() noexcept(false);\n\n  // Read and return the next external.\n  rpc::JsValue::External::Reader read();\n\n  // Call immediately after `read()` when reading an external that is associated with a stream.\n  // `stream` is published back to the sender via StreamSink.\n  void setLastStream(capnp::Capability::Client stream);\n\n  // All stubs deserialized as part of a particular parameter or result set are placed in a\n  // common disposal group so that they can be disposed together.\n  RpcStubDisposalGroup& getDisposalGroup() {\n    return disposalGroup;\n  }\n\n  // Call after serialization is complete to get the StreamSink that should handle streams found\n  // while deserializing. Returns none if there were no streams. This should only be called if\n  // a `streamSink` was NOT passed to the constructor.\n  kj::Maybe<rpc::JsValue::StreamSink::Client> getStreamSink() {\n    return kj::mv(streamSinkCap);\n  }\n\n private:\n  capnp::List<rpc::JsValue::External>::Reader externals;\n  uint i = 0;\n\n  kj::UnwindDetector unwindDetector;\n  RpcStubDisposalGroup& disposalGroup;\n\n  kj::Maybe<StreamSinkImpl&> streamSink;\n  kj::Maybe<rpc::JsValue::StreamSink::Client> streamSinkCap;\n};\n\n// Base class for objects which can be sent over RPC, but doing so actually sends a stub which\n// makes RPCs back to the original object.\nclass JsRpcTarget: public jsg::Object {\n public:\n  static jsg::Ref<JsRpcTarget> constructor(jsg::Lock& js) {\n    return js.alloc<JsRpcTarget>();\n  }\n\n  JSG_RESOURCE_TYPE(JsRpcTarget) {}\n\n  // Serializes to JsRpcStub.\n  void serialize(jsg::Lock& js, jsg::Serializer& serializer);\n  JSG_ONEWAY_SERIALIZABLE(rpc::SerializationTag::JS_RPC_STUB);\n};\n\n// Common superclass of JsRpcStub and Fetcher, the two types that may serve as the basis for\n// RPC calls.\n//\n// This class is NOT part of the JavaScript class hierarchy (it has no JSG_RESOURCE_TYPE block),\n// it's only a C++ class used to abstract how to get a capnp client out of the object.\nclass JsRpcClientProvider: public jsg::Object {\n public:\n  // Get a capnp client that can be used to dispatch one call.\n  //\n  // If this isn't the root object (i.e. this is a JsRpcProperty), the property path starting from\n  // the root object will be appended to `path`.\n  virtual rpc::JsRpcTarget::Client getClientForOneCall(\n      jsg::Lock& js, kj::Vector<kj::StringPtr>& path) = 0;\n};\n\nclass JsRpcProperty;\n\n// Represents the promise returned by calling an RPC method. We don't use a regular Promise object,\n// but rather our own custom thenable, so that we can support pipelining on it.\nclass JsRpcPromise: public JsRpcClientProvider {\n public:\n  // A weak reference to this JsRpcPromise. Unlike the usual WeakRef pattern, though, this ref is\n  // allocated before the promise itself is actually created, and filled in later. This is needed\n  // to solve cyclic initialization challenges in `callImpl()`.\n  struct WeakRef: public kj::AtomicRefcounted {\n    // Note: The contents of `WeakRef` can only be accessed under isolate lock, but `WeakRef`'s\n    // refcount is not protected by any lock, hence why it is AtomicRefcounted. This also implies\n    // that it can be destroyed without a lock.\n\n    kj::Maybe<JsRpcPromise&> ref;\n\n    // This is set true if the JsRpcPromise's dispose() method was explicitly called, in which\n    // case the final result should be considered pre-disposed.\n    bool disposed = false;\n  };\n\n  JsRpcPromise(jsg::JsRef<jsg::JsPromise> inner,\n      kj::Own<WeakRef> weakRef,\n      IoOwn<rpc::JsRpcTarget::CallResults::Pipeline> pipeline);\n  ~JsRpcPromise() noexcept(false);\n\n  void resolve(jsg::Lock& js, jsg::JsValue result);\n  void dispose(jsg::Lock& js);\n\n  rpc::JsRpcTarget::Client getClientForOneCall(\n      jsg::Lock& js, kj::Vector<kj::StringPtr>& path) override;\n\n  // Expect that the call is itself going to return a function... and call that.\n  jsg::Ref<JsRpcPromise> call(const v8::FunctionCallbackInfo<v8::Value>& args);\n\n  // Implement standard Promise interface, especially `then()` so that this works as a custom\n  // thenable.\n  //\n  // Note that we intentionally return jsg::JsValue rather than jsg::JsPromise because we actually\n  // do not want the JSG glue to recognize we're returning a promise triggering behavior that pins\n  // the JsRpcPromise in memory until it resolves. It's actually fine if the JsRpcPromise is GC'ed\n  // before the inner promise resolves, because it's just a thin wrapper that delegates to the\n  // inner promise. The inner promise will keep running until it completes, and will invoke all\n  // the continuations then.\n  jsg::JsValue then(jsg::Lock& js,\n      v8::Local<v8::Function> handler,\n      jsg::Optional<v8::Local<v8::Function>> errorHandler);\n  jsg::JsValue catch_(jsg::Lock& js, v8::Local<v8::Function> errorHandler);\n  jsg::JsValue finally(jsg::Lock& js, v8::Local<v8::Function> onFinally);\n\n  // Get a nested property, using pipelining.\n  kj::Maybe<jsg::Ref<JsRpcProperty>> getProperty(jsg::Lock& js, kj::String name);\n\n  JSG_RESOURCE_TYPE(JsRpcPromise) {\n    JSG_DISPOSE(dispose);\n    JSG_CALLABLE(call);\n    JSG_WILDCARD_PROPERTY(getProperty);\n    JSG_METHOD(then);\n    JSG_METHOD_NAMED(catch, catch_);\n    JSG_METHOD(finally);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"inner\", inner);\n  }\n\n private:\n  jsg::JsRef<jsg::JsPromise> inner;\n  kj::Own<WeakRef> weakRef;\n\n  struct Pending {\n    IoOwn<rpc::JsRpcTarget::CallResults::Pipeline> pipeline;\n  };\n  struct Resolved {\n    jsg::Value result;\n\n    // Dummy IoPtr to self, used only to verify that we're running in the correct context.\n    // (Dereferencing from the wrong context would throw an exception.)\n    // Note: Can't use IoContext::WeakRef here because it's not thread-safe (it's only intended to\n    //   be held from KJ I/O objects, but this is a JSG object).\n    IoPtr<JsRpcPromise> ctxCheck;\n  };\n  struct Disposed {};\n\n  // Note we don't have a \"rejected\" state because it works fine to just leave the state as\n  // \"Pending\" -- calls to `pipeline` will rethrow the same exception, and holding the pipeline\n  // open won't actually hold anything open on the server.\n  kj::OneOf<Pending, Resolved, Disposed> state;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(inner);\n    KJ_SWITCH_ONEOF(state) {\n      KJ_CASE_ONEOF(pending, Pending) {}\n      KJ_CASE_ONEOF(resolved, Resolved) {\n        visitor.visit(resolved.result);\n      }\n      KJ_CASE_ONEOF(disposed, Disposed) {}\n    }\n  }\n};\n\n// Represents a property -- possibly, a method -- of a remote RPC object.\nclass JsRpcProperty: public JsRpcClientProvider {\n public:\n  JsRpcProperty(jsg::Ref<JsRpcClientProvider> parent, kj::String name)\n      : parent(kj::mv(parent)),\n        name(kj::mv(name)) {}\n\n  rpc::JsRpcTarget::Client getClientForOneCall(\n      jsg::Lock& js, kj::Vector<kj::StringPtr>& path) override;\n\n  // Call the property as a method.\n  jsg::Ref<JsRpcPromise> call(const v8::FunctionCallbackInfo<v8::Value>& args);\n\n  // Treat the property as a promise to obtain the value.\n  //\n  // Note that we intentionally return jsg::JsValue rather than jsg::JsPromise because we actually\n  // do not want the JSG glue to recognize we're returning a promise triggering behavior that pins\n  // the JsRpcProperty in memory until it resolves. It's actually fine if the JsRpcProperty is GC'ed\n  // before the promise resolves, since the property is just an API stub. The underlying Cap'n Proto\n  // RPCs it starts will keep running; Cap'n Proto refcounts all the necessary resources internally.\n  jsg::JsValue then(jsg::Lock& js,\n      v8::Local<v8::Function> handler,\n      jsg::Optional<v8::Local<v8::Function>> errorHandler);\n  jsg::JsValue catch_(jsg::Lock& js, v8::Local<v8::Function> errorHandler);\n  jsg::JsValue finally(jsg::Lock& js, v8::Local<v8::Function> onFinally);\n\n  // Get a nested property, using pipelining.\n  kj::Maybe<jsg::Ref<JsRpcProperty>> getProperty(jsg::Lock& js, kj::String name);\n\n  JSG_RESOURCE_TYPE(JsRpcProperty) {\n    // You can call the property as a function. We'll assume it is a method in this case.\n    JSG_CALLABLE(call);\n\n    // You can access further nested properties. We'll assume the property is an object in this\n    // case.\n    JSG_WILDCARD_PROPERTY(getProperty);\n\n    // You can treat the property as a promise. This returns the value of the property.\n    JSG_METHOD(then);\n    JSG_METHOD_NAMED(catch, catch_);\n    JSG_METHOD(finally);\n  }\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackField(\"parent\", parent);\n    tracker.trackField(\"name\", name);\n  }\n\n private:\n  // The parent object from which this property was obtained.\n  jsg::Ref<JsRpcClientProvider> parent;\n\n  // Name of this property within its immediate parent.\n  kj::String name;\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(parent);\n  }\n};\n\n// A JsRpcStub object forwards JS method calls to the remote Worker/Durable Object over RPC.\n// Since methods are not known until runtime, JsRpcStub doesn't define any JS methods.\n// Instead, we use JSG_WILDCARD_PROPERTY to intercept property accesses of names that are not known\n// at compile time.\n//\n// JsRpcStub only supports method calls. You cannot, for instance, access a property of a\n// Durable Object over RPC.\n//\n// The `JsRpcStub` type is used to represent capabilities passed across some previous JS RPC\n// call. It is NOT the type of a Durable Object stub nor a service binding. Those are instances of\n// `Fetcher`, which has a `getRpcMethod()` call of its own that mostly delegates to\n// `JsRpcStub::sendJsRpc()`.\nclass JsRpcStub: public JsRpcClientProvider {\n public:\n  // Only deserialize() needs ExternalMemoryAdjustment — it extracts a new capability from an RPC\n  // response, creating MembraneHook + forked promise allocations. The other callers (dup() and\n  // constructor()) just refcount existing capabilities or create local loopbacks.\n  JsRpcStub(IoOwn<rpc::JsRpcTarget::Client> capnpClient): capnpClient(kj::mv(capnpClient)) {}\n  JsRpcStub(IoOwn<rpc::JsRpcTarget::Client> capnpClient,\n      RpcStubDisposalGroup& disposalGroup,\n      jsg::ExternalMemoryAdjustment externalMemoryAdjustment);\n  ~JsRpcStub() noexcept(false);\n\n  rpc::JsRpcTarget::Client getClient();\n\n  rpc::JsRpcTarget::Client getClientForOneCall(\n      jsg::Lock& js, kj::Vector<kj::StringPtr>& path) override;\n\n  jsg::Ref<JsRpcStub> dup(jsg::Lock& js);\n  void dispose();\n\n  // Given a JsRpcTarget, make an RPC stub from it.\n  //\n  // Usually, applications won't use this constructor directly. Rather, they will define types\n  // that extend `JsRpcTarget` and then they will simply return those. The serializer will\n  // automatically handle `JsRpcTarget` by wrapping it in `JsRpcStub`. However, it can be useful\n  // for testing to be able to construct a loopback stub.\n  static jsg::Ref<JsRpcStub> constructor(jsg::Lock& js, jsg::JsObject object);\n\n  // Call the stub itself as a function.\n  jsg::Ref<JsRpcPromise> call(const v8::FunctionCallbackInfo<v8::Value>& args);\n\n  kj::Maybe<jsg::Ref<JsRpcProperty>> getRpcMethod(jsg::Lock& js, kj::String name);\n\n  JSG_RESOURCE_TYPE(JsRpcStub) {\n    JSG_METHOD(dup);\n    JSG_DISPOSE(dispose);\n    JSG_CALLABLE(call);\n    JSG_WILDCARD_PROPERTY(getRpcMethod);\n  }\n\n  void serialize(jsg::Lock& js, jsg::Serializer& serializer);\n  static jsg::Ref<JsRpcStub> deserialize(\n      jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer);\n\n  JSG_SERIALIZABLE(rpc::SerializationTag::JS_RPC_STUB);\n\n private:\n  // Nulled out upon dispose().\n  kj::Maybe<IoOwn<rpc::JsRpcTarget::Client>> capnpClient;\n\n  kj::Maybe<RpcStubDisposalGroup&> disposalGroup;\n  kj::ListLink<JsRpcStub> disposalGroupLink;\n  kj::Maybe<jsg::ExternalMemoryAdjustment> externalMemoryAdjustment;\n\n  friend class RpcStubDisposalGroup;\n};\n\nclass RpcStubDisposalGroup {\n public:\n  ~RpcStubDisposalGroup() noexcept(false);\n\n  // Release all the stubs in the group without disposing them. They will have to be disposed\n  // individually by calling their disposers directly.\n  void disownAll();\n\n  // Call dispose() on every stub in the group.\n  void disposeAll();\n\n  bool empty() {\n    return list.empty();\n  }\n\n  // When creating a disposal group representing an RPC response, we may also attach the\n  // `callPipeline` from the response, to control when the server-side `dispose()` method is\n  // invoked. This isn't part of any stub, it's just discarded upon disposal.\n  void setCallPipeline(IoOwn<rpc::JsRpcTarget::Client> value) {\n    callPipeline = kj::mv(value);\n  }\n\n private:\n  kj::List<JsRpcStub, &JsRpcStub::disposalGroupLink> list;\n  kj::Maybe<IoOwn<rpc::JsRpcTarget::Client>> callPipeline;\n  friend class JsRpcStub;\n};\n\n// `jsRpcSession` returns a capability that provides the client a way to call remote methods\n// over RPC. We drain the IncomingRequest after the capability is used to run the relevant JS.\nclass JsRpcSessionCustomEvent final: public WorkerInterface::CustomEvent {\n public:\n  JsRpcSessionCustomEvent(uint16_t typeId,\n      kj::Maybe<kj::String> wrapperModule = kj::none,\n      kj::PromiseFulfillerPair<rpc::JsRpcTarget::Client> paf =\n          kj::newPromiseAndFulfiller<rpc::JsRpcTarget::Client>())\n      : capFulfiller(kj::mv(paf.fulfiller)),\n        clientCap(kj::mv(paf.promise)),\n        typeId(typeId),\n        wrapperModule(kj::mv(wrapperModule)) {}\n\n  ~JsRpcSessionCustomEvent() noexcept(false) {\n    if (capFulfiller->isWaiting()) {\n      capFulfiller->reject(\n          KJ_EXCEPTION(DISCONNECTED, \"JsRpcSessionCustomEvent was destroyed before completion\"));\n    }\n  }\n\n  kj::Promise<Result> run(kj::Own<IoContext::IncomingRequest> incomingRequest,\n      kj::Maybe<kj::StringPtr> entrypointName,\n      kj::Maybe<Worker::VersionInfo> versionInfo,\n      Frankenvalue props,\n      kj::TaskSet& waitUntilTasks) override;\n\n  kj::Promise<Result> sendRpc(capnp::HttpOverCapnpFactory& httpOverCapnpFactory,\n      capnp::ByteStreamFactory& byteStreamFactory,\n      rpc::EventDispatcher::Client dispatcher) override;\n\n  uint16_t getType() override {\n    return typeId;\n  }\n\n  tracing::EventInfo getEventInfo() const override {\n    return tracing::JsRpcEventInfo(nullptr);\n  }\n\n  rpc::JsRpcTarget::Client getCap() {\n    auto result = kj::mv(KJ_ASSERT_NONNULL(clientCap, \"can only call getCap() once\"));\n    clientCap = kj::none;\n    return result;\n  }\n\n  kj::Promise<Result> notSupported() override {\n    JSG_FAIL_REQUIRE(TypeError, \"The receiver is not an RPC object\");\n  }\n\n  void failed(const kj::Exception& e) override {\n    capFulfiller->reject(kj::cp(e));\n  }\n\n  // Event ID for jsRpcSession.\n  //\n  // Similar to WebSocket hibernation, we define this event ID in the internal codebase, but since\n  // we don't create JsRpcSessionCustomEvent from our internal code, we can't pass the event\n  // type in -- so we hardcode it here.\n  static constexpr uint16_t WORKER_RPC_EVENT_TYPE = 9;\n\n private:\n  kj::Own<kj::PromiseFulfiller<workerd::rpc::JsRpcTarget::Client>> capFulfiller;\n\n  // We need to set the client/server capability on the event itself to get around CustomEvent's\n  // limited return type.\n  kj::Maybe<rpc::JsRpcTarget::Client> clientCap;\n  uint16_t typeId;\n\n  kj::Maybe<kj::String> wrapperModule;\n\n  class ServerTopLevelMembrane;\n};\n\n#define EW_WORKER_RPC_ISOLATE_TYPES                                                                \\\n  api::JsRpcPromise, api::JsRpcProperty, api::JsRpcStub, api::JsRpcTarget\n\n};  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/workers-module.c++",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"workers-module.h\"\n\n#include <workerd/api/actor-state.h>\n#include <workerd/api/global-scope.h>\n\nnamespace workerd::api {\n\njsg::Ref<WorkerEntrypoint> WorkerEntrypoint::constructor(\n    const v8::FunctionCallbackInfo<v8::Value>& args, jsg::JsObject ctx, jsg::JsObject env) {\n  // HACK: We take `FunctionCallbackInfo` mostly so that we can set properties directly on\n  //   `This()`. There ought to be a better way to get access to `this` in a constructor.\n  //   We *also* declare `ctx` and `env` params more explicitly just for the sake of type checking.\n  jsg::Lock& js = jsg::Lock::from(args.GetIsolate());\n\n  jsg::JsObject self(args.This());\n  self.set(js, \"ctx\", jsg::JsValue(args[0]));\n  self.set(js, \"env\", jsg::JsValue(args[1]));\n  return js.alloc<WorkerEntrypoint>();\n}\n\njsg::Ref<DurableObjectBase> DurableObjectBase::constructor(\n    const v8::FunctionCallbackInfo<v8::Value>& args,\n    jsg::Ref<DurableObjectState> ctx,\n    jsg::JsObject env) {\n  // HACK: We take `FunctionCallbackInfo` mostly so that we can set properties directly on\n  //   `This()`. There ought to be a better way to get access to `this` in a constructor.\n  //   We *also* declare `ctx` and `env` params more explicitly just for the sake of type checking.\n  jsg::Lock& js = jsg::Lock::from(args.GetIsolate());\n\n  jsg::JsObject self(args.This());\n  self.set(js, \"ctx\", jsg::JsValue(args[0]));\n  self.set(js, \"env\", jsg::JsValue(args[1]));\n  return js.alloc<DurableObjectBase>();\n}\n\njsg::Ref<WorkflowEntrypoint> WorkflowEntrypoint::constructor(\n    const v8::FunctionCallbackInfo<v8::Value>& args,\n    jsg::Ref<ExecutionContext> ctx,\n    jsg::JsObject env) {\n  // HACK: We take `FunctionCallbackInfo` mostly so that we can set properties directly on\n  //   `This()`. There ought to be a better way to get access to `this` in a constructor.\n  //   We *also* declare `ctx` and `env` params more explicitly just for the sake of type checking.\n  jsg::Lock& js = jsg::Lock::from(args.GetIsolate());\n\n  jsg::JsObject self(args.This());\n  self.set(js, \"ctx\", jsg::JsValue(args[0]));\n  self.set(js, \"env\", jsg::JsValue(args[1]));\n  return js.alloc<WorkflowEntrypoint>();\n}\n\nvoid EntrypointsModule::waitUntil(kj::Promise<void> promise) {\n  // No need to check if IoContext::hasCurrent since current() will throw\n  // if there is no active request.\n  IoContext::current().addWaitUntil(kj::mv(promise));\n}\n\n}  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/api/workers-module.h",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/api/http.h>\n#include <workerd/api/worker-rpc.h>\n\nnamespace workerd::api {\n\n// Base class for exported RPC services.\n//\n// When the worker's top-level module exports a class that extends this class, it means that it\n// is a stateless service.\n//\n//     import {WorkerEntrypoint} from \"cloudflare:workers\";\n//     export class MyService extends WorkerEntrypoint {\n//       async fetch(req) { ... }\n//       async someRpcMethod(a, b) { ... }\n//     }\n//\n// `env` and `ctx` are automatically available as `this.env` and `this.ctx`, without the need to\n// define a constructor.\nclass WorkerEntrypoint: public jsg::Object {\n public:\n  static jsg::Ref<WorkerEntrypoint> constructor(\n      const v8::FunctionCallbackInfo<v8::Value>& args, jsg::JsObject ctx, jsg::JsObject env);\n\n  JSG_RESOURCE_TYPE(WorkerEntrypoint) {}\n};\n\n// Like WorkerEntrypoint, but this is the base class for Durable Object classes.\n//\n// Note that the name of this class as seen by JavaScript is `DurableObject`, but using that name\n// in C++ would conflict with the type name currently used by DO stubs.\n// TODO(cleanup): Rename DO stubs to `DurableObjectStub`?\n//\n// Historically, DO classes were not expected to inherit anything. However, this made it impossible\n// to tell whether an exported class was intended to be a DO class vs. something else. Originally\n// there were no other kinds of exported classes so this was fine. Going forward, we encourage\n// everyone to be explicit by inheriting this, and we require it if you want to use RPC.\nclass DurableObjectBase: public jsg::Object {\n public:\n  static jsg::Ref<DurableObjectBase> constructor(const v8::FunctionCallbackInfo<v8::Value>& args,\n      jsg::Ref<DurableObjectState> ctx,\n      jsg::JsObject env);\n\n  JSG_RESOURCE_TYPE(DurableObjectBase) {}\n};\n\n// Base class for Workflows\n//\n// When the worker's top-level module exports a class that extends this class, it means that it\n// is a Workflow.\n//\n//     import { WorkflowEntrypoint } from \"cloudflare:workers\";\n//     export class MyWorkflow extends WorkflowEntrypoint {\n//       async run(batch, fns) { ... }\n//     }\n//\n// `env` and `ctx` are automatically available as `this.env` and `this.ctx`, without the need to\n// define a constructor.\nclass WorkflowEntrypoint: public jsg::Object {\n public:\n  static jsg::Ref<WorkflowEntrypoint> constructor(const v8::FunctionCallbackInfo<v8::Value>& args,\n      jsg::Ref<ExecutionContext> ctx,\n      jsg::JsObject env);\n\n  JSG_RESOURCE_TYPE(WorkflowEntrypoint) {}\n};\n\n// The \"cloudflare:workers\" module, which exposes the WorkerEntrypoint, WorkflowEntrypoint and DurableObject types\n// for extending.\nclass EntrypointsModule: public jsg::Object {\n public:\n  EntrypointsModule() = default;\n  EntrypointsModule(jsg::Lock&, const jsg::Url&) {}\n\n  void waitUntil(kj::Promise<void> promise);\n\n  JSG_RESOURCE_TYPE(EntrypointsModule) {\n    JSG_NESTED_TYPE(WorkerEntrypoint);\n    JSG_NESTED_TYPE(WorkflowEntrypoint);\n    JSG_NESTED_TYPE_NAMED(DurableObjectBase, DurableObject);\n    JSG_NESTED_TYPE_NAMED(JsRpcPromise, RpcPromise);\n    JSG_NESTED_TYPE_NAMED(JsRpcProperty, RpcProperty);\n    JSG_NESTED_TYPE_NAMED(JsRpcStub, RpcStub);\n    JSG_NESTED_TYPE_NAMED(JsRpcTarget, RpcTarget);\n    JSG_NESTED_TYPE_NAMED(Fetcher, ServiceStub);\n\n    JSG_METHOD(waitUntil);\n  }\n};\n\n#define EW_WORKERS_MODULE_ISOLATE_TYPES                                                            \\\n  api::WorkerEntrypoint, api::WorkflowEntrypoint, api::DurableObjectBase, api::EntrypointsModule\n\ntemplate <class Registry>\nvoid registerWorkersModule(Registry& registry, CompatibilityFlags::Reader flags) {\n  registry.template addBuiltinModule<EntrypointsModule>(\n      \"cloudflare-internal:workers\", workerd::jsg::ModuleRegistry::Type::INTERNAL);\n}\n\ntemplate <typename TypeWrapper>\nkj::Own<jsg::modules::ModuleBundle> getInternalRpcModuleBundle(auto featureFlags) {\n  jsg::modules::ModuleBundle::BuiltinBuilder builder(\n      jsg::modules::ModuleBundle::BuiltinBuilder::Type::BUILTIN_ONLY);\n  static const auto kSpecifier = \"cloudflare-internal:workers\"_url;\n  builder.addObject<EntrypointsModule, TypeWrapper>(kSpecifier);\n  return builder.finish();\n}\n};  // namespace workerd::api\n"
  },
  {
    "path": "src/workerd/io/AGENTS.md",
    "content": "# src/workerd/io/\n\n## OVERVIEW\n\nI/O lifecycle, per-request context, worker/isolate management, actor storage, consistency gates, and compatibility flags.\n\n## KEY CLASSES\n\n| Class                                       | File                   | Role                                                                                        |\n| ------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------- |\n| `IoContext`                                 | `io-context.{h,c++}`   | Per-request god object; thread-local via `IoContext::current()`                             |\n| `IoContext::IncomingRequest`                | `io-context.h`         | Tracks one inbound request for metrics/tracing; actors have many per IoContext              |\n| `Worker`                                    | `worker.{h,c++}`       | Ref-counted worker instance; owns Script + Isolate refs                                     |\n| `Worker::Isolate`                           | `worker.h:337`         | V8 isolate wrapper; shared across workers with same config                                  |\n| `Worker::Script`                            | `worker.h:250`         | Compiled script bound to an Isolate                                                         |\n| `Worker::Lock`                              | `worker.h:677`         | Synchronous V8 isolate lock (must hold to touch JS heap)                                    |\n| `Worker::AsyncLock`                         | `worker.h:799`         | Fair async queue for acquiring `Worker::Lock`                                               |\n| `Worker::Actor`                             | `worker.h:819`         | Durable Object instance; owns gates + cache + hibernation state                             |\n| `ActorCache`                                | `actor-cache.{h,c++}`  | LRU write-back cache over RPC storage; `ActorCacheOps` base                                 |\n| `ActorSqlite`                               | `actor-sqlite.{h,c++}` | SQLite-backed `ActorCacheOps` implementation                                                |\n| `InputGate` / `OutputGate`                  | `io-gate.{h,c++}`      | Consistency primitives for DO concurrent request handling                                   |\n| `IoOwn<T>` / `IoPtr<T>` / `ReverseIoOwn<T>` | `io-own.{h,c++}`       | Cross-heap smart pointers preventing KJ↔JS ref leaks                                       |\n\n## WHERE TO LOOK\n\n| Task                            | File(s)                                                                           |\n| ------------------------------- | --------------------------------------------------------------------------------- |\n| Promise bridging KJ↔JS         | `io-context.h` — `awaitIo()`, `awaitJs()`                                         |\n| Request lifecycle / subrequests | `io-context.{h,c++}`, `worker-entrypoint.{h,c++}`                                 |\n| Actor storage ops               | `actor-cache.h` (`ActorCacheOps`), `actor-sqlite.h`, `actor-storage.capnp`        |\n| DO gate semantics               | `io-gate.{h,c++}` — `InputGate::CriticalSection`, `OutputGate::lockWhile()`       |\n| Worker/isolate creation         | `worker.{h,c++}`, `worker-modules.{h,c++}`                                        |\n| Metrics/logging hooks           | `observer.h` — `RequestObserver`, `IsolateObserver`, `ActorObserver`              |\n| Tracing                         | `trace.{h,c++,capnp}`, `trace-stream.{h,c++}`, `tracer.{h,c++}`                   |\n| Resource limits                 | `limit-enforcer.h` (abstract interface)                                           |\n| Timer scheduling                | `io-timers.{h,c++}`                                                               |\n| Hibernatable WebSockets         | `hibernation-manager.{h,c++}`                                                     |\n| Cap'n Proto schemas             | `worker-interface.capnp`, `actor-storage.capnp`, `container.capnp`, `trace.capnp` |\n\n## CONVENTIONS\n\n- `IoContext::current()` — ambient thread-local access; only valid inside a request\n- `awaitIo(js, kjPromise, func)` bridges KJ→JS; `func` runs under V8 lock. `awaitIo(js, kjPromise)` for identity\n- `awaitJs(js, jsPromise)` bridges JS→KJ\n- `addObject(kj::Own<T>)` returns `IoOwn<T>` — the **only** safe way to store KJ I/O objects reachable from JS heap\n- Two-phase locking: `Worker::AsyncLock` (fair queue) → `Worker::Lock` (V8 isolate lock)\n- `ActorCacheOps` methods return `kj::OneOf<Result, kj::Promise<Result>>` — sync when cached, async otherwise\n- `OutputGate::lockWhile(promise)` blocks outgoing responses until the promise resolves\n- `InputGate::CriticalSection` must succeed or permanently breaks the gate\n- Observer classes (`RequestObserver`, `IsolateObserver`, etc.) have no-op defaults; all methods optional\n\n## ANTI-PATTERNS\n\n- **NEVER** use `awaitIoLegacy()` in new code — use `awaitIo()` with continuation\n- `awaitIoImpl` parameter ordering (promise by-value, func by-ref) is **critical** for exception safety\n- `abortWhen()` promises must **never** enter the V8 isolate\n- Cross-request I/O object access throws by design (IoOwn prevents this)\n"
  },
  {
    "path": "src/workerd/io/BUILD.bazel",
    "content": "load(\"//:build/kj_test.bzl\", \"kj_test\")\nload(\"//:build/wd_capnp_library.bzl\", \"wd_capnp_library\")\nload(\"//:build/wd_cc_embed.bzl\", \"wd_cc_embed\")\nload(\"//:build/wd_cc_library.bzl\", \"wd_cc_library\")\nload(\"//:build/wd_test.bzl\", \"wd_test\")\n\n# TODO(cleanup): Split up into smaller targets, although this target is already relatively small and\n# not encumbered with many dependencies.\nwd_cc_library(\n    name = \"io-helpers\",\n    srcs = [\n        \"io-thread-context.c++\",\n        \"io-timers.c++\",\n        \"request-tracker.c++\",\n    ],\n    hdrs = [\n        \"io-thread-context.h\",\n        \"io-timers.h\",\n        \"request-tracker.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/jsg\",\n        \"@capnp-cpp//src/capnp/compat:http-over-capnp\",\n    ],\n)\n\nwd_cc_library(\n    name = \"io\",\n    # HACK: Currently, the `io` and `api` packages are interdependent. We fold all the sources\n    #   from `api` into `io`. In principle, it should be possible to pull them apart so `api`\n    #   depends on `io` but not vice versa. In practice, this appears very difficult due to the\n    # IoContext -> Worker -> ServiceWorkerGlobalScope -> (various api targets) dependency chain.\n    # TODO(cleanup): Fix this.\n    srcs = [\n        \"compatibility-date.c++\",\n        \"external-pusher.c++\",\n        \"features.c++\",\n        \"hibernation-manager.c++\",\n        \"io-channels.c++\",\n        \"io-context.c++\",\n        \"io-own.c++\",\n        \"io-util.c++\",\n        \"trace-stream.c++\",\n        \"tracer.c++\",\n        \"worker.c++\",\n        \"worker-fs.c++\",\n    ] + [\"//src/workerd/api:srcs\"],\n    hdrs = [\n        \"compatibility-date.h\",\n        \"external-pusher.h\",\n        \"hibernation-manager.h\",\n        \"io-channels.h\",\n        \"io-context.h\",\n        \"io-own.h\",\n        \"io-util.h\",\n        \"trace-stream.h\",\n        \"tracer.h\",\n        \"worker.h\",\n        \"worker-fs.h\",\n    ] + [\"//src/workerd/api:hdrs\"],\n    implementation_deps = [\n        \"//src/rust/jsg\",\n        \"//src/workerd/api:crypto-crc-impl\",\n        \"//src/workerd/api:data-url\",\n        \"//src/workerd/api/node:exceptions\",\n        \"//src/workerd/util:completion-membrane\",\n        \"//src/workerd/util:entropy\",\n        \"//src/workerd/util:perfetto\",\n        \"//src/workerd/util:string-buffer\",\n        \"//src/workerd/util:strings\",\n        \"//src/workerd/util:uuid\",\n        \"@capnp-cpp//src/kj/compat:kj-brotli\",\n        \"@capnp-cpp//src/kj/compat:kj-gzip\",\n        \"@nbytes\",\n        \"@simdutf\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":actor\",\n        \":actor-id\",\n        \":actor-storage_capnp\",\n        \":cdp_capnp\",\n        \":container_capnp\",\n        \":features\",\n        \":frankenvalue\",\n        \":io-gate\",\n        \":io-helpers\",\n        \":limit-enforcer\",\n        \":maximum-compatibility-date\",\n        \":observer\",\n        \":release-version\",\n        \":trace\",\n        \":wasm-instantiate-shim\",\n        \":worker-interface\",\n        \":worker-source\",\n        \"//src/rust/cxx-integration\",\n        \"//src/workerd/api:analytics-engine_capnp\",\n        \"//src/workerd/api:deferred-proxy\",\n        \"//src/workerd/api:encoding\",\n        \"//src/workerd/api:fuzzilli\",\n        \"//src/workerd/api:hibernation-event-params\",\n        \"//src/workerd/api:url\",\n        \"//src/workerd/jsg\",\n        \"//src/workerd/jsg:inspector\",\n        \"//src/workerd/jsg:script\",\n        \"//src/workerd/util:checked-queue\",\n        \"//src/workerd/util:exception\",\n        \"//src/workerd/util:header-validation\",\n        \"//src/workerd/util:immediate-crash\",\n        \"//src/workerd/util:ring-buffer\",\n        \"//src/workerd/util:small-set\",\n        \"//src/workerd/util:sqlite\",\n        \"//src/workerd/util:state-machine\",\n        \"//src/workerd/util:strong-bool\",\n        \"@capnp-cpp//src/capnp:capnp-rpc\",\n        \"@capnp-cpp//src/capnp/compat:http-over-capnp\",\n        \"@capnp-cpp//src/kj:kj-async\",\n        \"@ncrypto\",\n        \"@ssl\",\n    ],\n)\n\n# Headers only target to avoid having circular dependencies.\nwd_cc_library(\n    name = \"features\",\n    hdrs = [\"features.h\"],\n    implementation_deps = [\n        \"//src/workerd/jsg\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":compatibility-date_capnp\",\n    ],\n)\n\nwd_cc_library(\n    name = \"worker-modules\",\n    srcs = [\"worker-modules.c++\"],\n    hdrs = [\"worker-modules.h\"],\n    implementation_deps = [\n        \"//src/pyodide:pyodide_static\",\n        \"//src/workerd/api:pyodide\",\n        \"//src/workerd/api/node\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":io\",\n        \"//src/pyodide:python-entrypoint\",\n        \"//src/workerd/api:commonjs\",\n        \"//src/workerd/api:rtti\",\n        \"//src/workerd/jsg\",\n        \"//src/workerd/util:strong-bool\",\n    ],\n)\n\nwd_cc_library(\n    name = \"bundle-fs\",\n    srcs = [\"bundle-fs.c++\"],\n    hdrs = [\"bundle-fs.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":io\",\n    ],\n)\n\nwd_cc_library(\n    name = \"promise-wrapper\",\n    hdrs = [\"promise-wrapper.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":io\",\n        \"//src/workerd/jsg\",\n    ],\n)\n\nwd_cc_library(\n    name = \"worker-source\",\n    hdrs = [\"worker-source.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/rust/cxx-integration\",\n        \"@capnp-cpp//src/capnp\",\n    ],\n)\n\n# TODO(cleanup): Split this up further.\nwd_cc_library(\n    name = \"actor\",\n    srcs = [\n        \"actor-cache.c++\",\n        \"actor-sqlite.c++\",\n        \"actor-storage.c++\",\n    ],\n    hdrs = [\n        \"actor-cache.h\",\n        \"actor-sqlite.h\",\n        \"actor-storage.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":actor-storage_capnp\",\n        \":io-gate\",\n        \":trace\",\n        \"//src/workerd/jsg:exception\",\n        \"//src/workerd/util:autogate\",\n        \"//src/workerd/util:duration-exceeded-logger\",\n        \"//src/workerd/util:sqlite\",\n        \"@capnp-cpp//src/capnp:capnp-rpc\",\n        \"@capnp-cpp//src/kj:kj-async\",\n    ],\n)\n\nwd_cc_library(\n    name = \"frankenvalue\",\n    srcs = [\"frankenvalue.c++\"],\n    hdrs = [\"frankenvalue.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":frankenvalue_capnp\",\n        \"//src/workerd/jsg\",\n        \"@capnp-cpp//src/capnp:capnpc\",\n    ],\n)\n\nwd_cc_library(\n    name = \"trace\",\n    srcs = [\"trace.c++\"],\n    hdrs = [\"trace.h\"],\n    implementation_deps = [\n        \"//src/workerd/util:entropy\",\n        \"//src/workerd/util:thread-scopes\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":trace_capnp\",\n        \":worker-interface_capnp\",\n        \"//src/workerd/jsg:memory-tracker\",\n        \"//src/workerd/util:own-util\",\n        \"@capnp-cpp//src/capnp:capnp-rpc\",\n        \"@capnp-cpp//src/capnp:capnpc\",\n        \"@capnp-cpp//src/kj/compat:kj-http\",\n        \"@ssl\",\n    ],\n)\n\nwd_cc_library(\n    name = \"observer\",\n    srcs = [\"observer.c++\"],\n    hdrs = [\"observer.h\"],\n    implementation_deps = [\n        \":worker-interface\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":features_capnp\",\n        \":trace\",\n        \"//src/workerd/jsg:observer\",\n        \"//src/workerd/util:sqlite\",\n    ],\n)\n\nwd_cc_library(\n    name = \"io-gate\",\n    srcs = [\"io-gate.c++\"],\n    hdrs = [\"io-gate.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":trace\",\n        \"@capnp-cpp//src/kj\",\n        \"@capnp-cpp//src/kj:kj-async\",\n    ],\n)\n\nwd_cc_library(\n    name = \"limit-enforcer\",\n    srcs = [\"tracked-wasm-instance.c++\"],\n    hdrs = [\n        \"limit-enforcer.h\",\n        \"tracked-wasm-instance.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":outcome_capnp\",\n        \"//src/workerd/jsg\",\n    ],\n)\n\nwd_cc_library(\n    name = \"worker-entrypoint\",\n    srcs = [\"worker-entrypoint.c++\"],\n    hdrs = [\"worker-entrypoint.h\"],\n    implementation_deps = [\n        \"//src/workerd/util:perfetto\",\n        \"//src/workerd/util:strings\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":io\",\n    ],\n)\n\nwd_cc_library(\n    name = \"worker-interface\",\n    srcs = [\"worker-interface.c++\"],\n    hdrs = [\"worker-interface.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":frankenvalue_capnp\",\n        \":trace\",\n        \":worker-interface_capnp\",\n        \"//src/workerd/util\",\n        \"@capnp-cpp//src/capnp:capnp-rpc\",\n        \"@capnp-cpp//src/capnp:capnpc\",\n        \"@capnp-cpp//src/capnp/compat:http-over-capnp\",\n    ],\n)\n\nwd_cc_library(\n    name = \"actor-id\",\n    hdrs = [\"actor-id.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\"@capnp-cpp//src/kj\"],\n)\n\ngenrule(\n    name = \"trimmed-release-version-gen\",\n    srcs = [\"release-version.txt\"],\n    outs = [\"trimmed-release-version.txt\"],\n    cmd = \"tr -d '\\n' < $(location release-version.txt) > $(location trimmed-release-version.txt)\",\n    cmd_ps = \"(Get-Content $(location release-version.txt) -Raw -Encoding Ascii).TrimEnd() | Set-Content $(location trimmed-release-version.txt) -NoNewLine -Encoding Ascii\",\n    visibility = [\"//visibility:public\"],\n)\n\ngenrule(\n    name = \"trimmed-maximum-compatibility-date-gen\",\n    srcs = [\"maximum-compatibility-date.txt\"],\n    outs = [\"trimmed-maximum-compatibility-date.txt\"],\n    cmd = \"tr -d '\\n' < $(location maximum-compatibility-date.txt) > $(location trimmed-maximum-compatibility-date.txt)\",\n    cmd_ps = \"(Get-Content $(location maximum-compatibility-date.txt) -Raw -Encoding Ascii).TrimEnd() | Set-Content $(location trimmed-maximum-compatibility-date.txt) -NoNewLine -Encoding Ascii\",\n    visibility = [\"//visibility:public\"],\n)\n\n# The date of this release. Used for the version string and the npm compatibilityDate export.\nwd_cc_embed(\n    name = \"release-version\",\n    src = \":trimmed-release-version.txt\",\n    base_name = \"release-version\",\n)\n\n# Maximum compatibility date that can safely be set using code compiled from this repo. Set to\n# 7 days beyond the release date so that users can set their compat date to today even if\n# they're running a workerd release from a few days ago.\n#\n# Note that the production Cloudflare Workers upload API always accepts any date up to the current\n# date regardless of this constant, on the assumption that Cloudflare is always running the latest\n# version of the code. This constant is more to protect users who are self-hosting the runtime and\n# could be running an older version.\nwd_cc_embed(\n    name = \"maximum-compatibility-date\",\n    src = \":trimmed-maximum-compatibility-date.txt\",\n    base_name = \"maximum-compatibility-date\",\n)\n\nwd_cc_embed(\n    name = \"wasm-instantiate-shim\",\n    src = \"wasm-instantiate-shim.js\",\n    base_name = \"wasm-instantiate-shim\",\n)\n\nwd_capnp_library(src = \"cdp.capnp\")\n\nwd_capnp_library(\n    src = \"worker-interface.capnp\",\n    deps = [\n        \":frankenvalue_capnp\",\n        \":outcome_capnp\",\n        \":script-version_capnp\",\n        \":trace_capnp\",\n        \"@capnp-cpp//src/capnp/compat:http-over-capnp_capnp\",\n    ],\n)\n\nwd_capnp_library(src = \"actor-storage.capnp\")\n\nwd_capnp_library(src = \"outcome.capnp\")\n\nwd_capnp_library(src = \"script-version.capnp\")\n\nwd_capnp_library(src = \"trace.capnp\")\n\nwd_capnp_library(src = \"compatibility-date.capnp\")\n\nwd_capnp_library(src = \"features.capnp\")\n\nwd_capnp_library(src = \"frankenvalue.capnp\")\n\nwd_capnp_library(\n    src = \"container.capnp\",\n    deps = [\n        \":compatibility-date_capnp\",\n        \"@capnp-cpp//src/capnp/compat:byte-stream_capnp\",\n    ],\n)\n\nkj_test(\n    src = \"io-gate-test.c++\",\n    deps = [\n        \":io-gate\",\n    ],\n)\n\nkj_test(\n    src = \"actor-cache-test.c++\",\n    deps = [\n        \":actor\",\n        \":io-gate\",\n        \"//src/workerd/util:test\",\n        \"//src/workerd/util:test-util\",\n    ],\n)\n\nkj_test(\n    src = \"actor-sqlite-test.c++\",\n    deps = [\n        \":actor\",\n        \":io-gate\",\n        \"//src/workerd/util:test\",\n        \"//src/workerd/util:test-util\",\n        \"@sqlite3\",\n    ],\n)\n\nkj_test(\n    src = \"promise-wrapper-test.c++\",\n    # Omitting coverage because it takes a really long time executing it.\n    tags = [\"no-coverage\"],\n    deps = [\n        \":io\",\n        \":promise-wrapper\",\n    ],\n)\n\nkj_test(\n    src = \"compatibility-date-test.c++\",\n    deps = [\n        \":io\",\n        \"@capnp-cpp//src/capnp:capnpc\",\n    ],\n)\n\nkj_test(\n    src = \"observer-test.c++\",\n    deps = [\n        \":observer\",\n        \"//src/workerd/io:worker-interface\",\n        \"//src/workerd/util:strings\",\n        \"@capnp-cpp//src/capnp:capnpc\",\n    ],\n)\n\nkj_test(\n    src = \"trace-test.c++\",\n    deps = [\n        \":trace\",\n        \"//src/workerd/util:thread-scopes\",\n    ],\n)\n\nkj_test(\n    src = \"frankenvalue-test.c++\",\n    deps = [\n        \":frankenvalue\",\n    ],\n)\n\nkj_test(\n    src = \"worker-fs-test.c++\",\n    deps = [\n        \":io\",\n    ],\n)\n\nwd_test(\n    src = \"io-context-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"io-context-test.js\"],\n)\n\nkj_test(\n    src = \"tracked-wasm-instance-test.c++\",\n    deps = [\n        \":limit-enforcer\",\n    ],\n)\n\nwd_test(\n    src = \"tracked-wasm-instance-js-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"tracked-wasm-instance-test.js\",\n        \"//src/workerd/io/wasm:signal-basic.wasm\",\n        \"//src/workerd/io/wasm:signal-bounds-check-edge.wasm\",\n        \"//src/workerd/io/wasm:signal-bounds-check-overflow.wasm\",\n        \"//src/workerd/io/wasm:signal-bounds-check-valid.wasm\",\n        \"//src/workerd/io/wasm:signal-decoy-memory.wasm\",\n        \"//src/workerd/io/wasm:signal-externref-memory.wasm\",\n        \"//src/workerd/io/wasm:signal-imported-memory.wasm\",\n        \"//src/workerd/io/wasm:signal-memory-reclaim.wasm\",\n        \"//src/workerd/io/wasm:signal-no-globals.wasm\",\n        \"//src/workerd/io/wasm:signal-partial-exports.wasm\",\n        \"//src/workerd/io/wasm:signal-preinit.wasm\",\n        \"//src/workerd/io/wasm:signal-terminated-only.wasm\",\n    ],\n    # The WebAssembly.instantiate shim is behind the WASM_SHUTDOWN_SIGNAL_SHIM autogate,\n    # so this test only works when all autogates are enabled.\n    generate_all_compat_flags_variant = False,\n    generate_default_variant = False,\n)\n\nkj_test(\n    src = \"bundle-fs-test.c++\",\n    deps = [\n        \":bundle-fs\",\n        \":io\",\n        \"//src/workerd/tests:test-fixture\",\n    ],\n)\n"
  },
  {
    "path": "src/workerd/io/actor-cache-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"actor-cache.h\"\n#include \"io-gate.h\"\n\n#include <workerd/util/capnp-mock.h>\n#include <workerd/util/test.h>\n\n#include <capnp/dynamic.h>\n#include <kj/debug.h>\n#include <kj/list.h>\n#include <kj/source-location.h>\n#include <kj/test.h>\n#include <kj/thread.h>\n\nnamespace workerd {\nnamespace {\n\n// =======================================================================================\n// Test helpers specific to ActorCache test.\n\ntemplate <typename T>\nkj::Promise<T> eagerlyReportExceptions(kj::Promise<T> promise, kj::SourceLocation location = {}) {\n  // TODO(cleanup): Move to KJ somewhere?\n  return promise.eagerlyEvaluate([location](kj::Exception&& e) -> T {\n    KJ_LOG_AT(ERROR, location, e);\n    kj::throwFatalException(kj::mv(e));\n  });\n}\n\ntemplate <typename T>\nkj::Promise<T> expectUncached(\n    kj::OneOf<T, kj::Promise<T>> result, kj::SourceLocation location = {}) {\n  // Expect that a result returned by get()/list()/delete() was not served entirely from cache,\n  // and return the promise.\n  KJ_SWITCH_ONEOF(result) {\n    KJ_CASE_ONEOF(promise, kj::Promise<T>) {\n      return eagerlyReportExceptions(kj::mv(promise), location);\n    }\n    KJ_CASE_ONEOF(value, T) {\n      KJ_FAIL_ASSERT_AT(location, \"result was unexpectedly cached\");\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\ntemplate <typename T>\nT expectCached(kj::OneOf<T, kj::Promise<T>> result, kj::SourceLocation location = {}) {\n  // Expect that a result returned by get()/list()/delete() was served entirely from cache, and\n  // return that.\n  KJ_SWITCH_ONEOF(result) {\n    KJ_CASE_ONEOF(promise, kj::Promise<T>) {\n      KJ_FAIL_ASSERT_AT(location, \"result was unexpectedly uncached\");\n    }\n    KJ_CASE_ONEOF(value, T) {\n      return kj::mv(value);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nstruct KeyValuePtr {\n  kj::StringPtr key;\n  kj::StringPtr value;\n\n  inline bool operator==(const KeyValuePtr& other) const {\n    return key == other.key && value == other.value;\n  }\n  inline kj::String toString() const {\n    return kj::str(key, \": \", value);\n  }\n};\n\nstruct KeyValue {\n  kj::String key;\n  kj::String value;\n\n  inline bool operator==(const KeyValuePtr& other) const {\n    return key == other.key && value == other.value;\n  }\n  inline bool operator==(const KeyValue& other) const {\n    return key == other.key && value == other.value;\n  }\n  inline kj::String toString() const {\n    return kj::str(key, \": \", value);\n  }\n};\n\nkj::ArrayPtr<const KeyValuePtr> kvs(kj::ArrayPtr<const KeyValuePtr> a) {\n  return a;\n}\n// We want to be able to write checks like:\n//\n//    KJ_ASSERT(results == {{\"bar\", \"456\"}, {\"foo\", \"123\"}});\n//\n// Unfortunately, the compiler is not smart enough to figure out how to interpret the braced list\n// the right of the comparison. So, we give it a little help by wrapping it in `kvs()`, like:\n//\n//    KJ_ASSERT(results == kvs({{\"bar\", \"456\"}, {\"foo\", \"123\"}}));\n\n// stringifyValues() is a convenience function that turns byte-array values returned by ActorCache\n// into strings, for a variety of different return types.\n\nkj::String stringifyValues(ActorCache::ValuePtr value) {\n  return kj::str(value.asChars());\n}\nKeyValue stringifyValues(ActorCache::KeyValuePtrPair kv) {\n  return {kj::str(kv.key), stringifyValues(kv.value)};\n}\nkj::Array<KeyValue> stringifyValues(const ActorCache::GetResultList& list) {\n  return KJ_MAP(e, list) { return stringifyValues(e); };\n}\n\ntemplate <typename T>\nauto stringifyValues(const kj::Maybe<T>& values) {\n  return values.map([](auto& v) { return stringifyValues(v); });\n}\n\ntemplate <typename T>\nkj::OneOf<decltype(stringifyValues(kj::instance<T>())),\n    kj::Promise<decltype(stringifyValues(kj::instance<T>()))>>\nstringifyValues(kj::OneOf<T, kj::Promise<T>> result) {\n  KJ_SWITCH_ONEOF(result) {\n    KJ_CASE_ONEOF(promise, kj::Promise<T>) {\n      return promise.then([](T result) { return stringifyValues(kj::mv(result)); });\n    }\n    KJ_CASE_ONEOF(value, T) {\n      return stringifyValues(kj::mv(value));\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nstruct ActorCacheConvenienceWrappers {\n  // Convenience methods to make test more concise by handling value conversions to/from strings,\n  // and allowing parameters to be string literals instead of owned strings.\n  //\n  // This is formulated as a mixin inherited by ActorCacheTest, below, so that it can be reused\n  // for transactions as well.\n\n  ActorCacheConvenienceWrappers(ActorCacheOps& target): target(target) {}\n\n  auto get(kj::StringPtr key, ActorCache::ReadOptions options = {}) {\n    return stringifyValues(target.get(kj::str(key), options));\n  }\n  auto get(kj::ArrayPtr<const kj::StringPtr> keys, ActorCache::ReadOptions options = {}) {\n    return stringifyValues(target.get(KJ_MAP(k, keys) { return kj::str(k); }, options));\n  }\n  auto getAlarm(ActorCache::ReadOptions options = {}) {\n    return target.getAlarm(options);\n  }\n\n  auto list(kj::StringPtr begin,\n      kj::StringPtr end,\n      kj::Maybe<uint> limit = kj::none,\n      ActorCache::ReadOptions options = {}) {\n    return stringifyValues(target.list(kj::str(begin), kj::str(end), limit, options));\n  }\n  auto listReverse(kj::StringPtr begin,\n      kj::StringPtr end,\n      kj::Maybe<uint> limit = kj::none,\n      ActorCache::ReadOptions options = {}) {\n    return stringifyValues(target.listReverse(kj::str(begin), kj::str(end), limit, options));\n  }\n\n  auto list(kj::StringPtr begin,\n      decltype(nullptr),\n      kj::Maybe<uint> limit = kj::none,\n      ActorCache::ReadOptions options = {}) {\n    return stringifyValues(target.list(kj::str(begin), kj::none, limit, options));\n  }\n  auto listReverse(kj::StringPtr begin,\n      decltype(nullptr),\n      kj::Maybe<uint> limit = kj::none,\n      ActorCache::ReadOptions options = {}) {\n    return stringifyValues(target.listReverse(kj::str(begin), kj::none, limit, options));\n  }\n\n  auto put(kj::StringPtr key, kj::StringPtr value, ActorCache::WriteOptions options = {}) {\n    return target.put(kj::str(key), kj::heapArray(value.asBytes()), options, nullptr);\n  }\n  auto put(kj::ArrayPtr<const KeyValuePtr> kvs, ActorCache::WriteOptions options = {}) {\n    return target.put(KJ_MAP(kv, kvs) {\n      return ActorCache::KeyValuePair{kj::str(kv.key), kj::heapArray(kv.value.asBytes())};\n    }, options, nullptr);\n  }\n  auto setAlarm(kj::Maybe<kj::Date> newTime, ActorCache::WriteOptions options = {}) {\n    return target.setAlarm(newTime, options, nullptr);\n  }\n\n  auto delete_(kj::StringPtr key, ActorCache::WriteOptions options = {}) {\n    return target.delete_(kj::str(key), options, nullptr);\n  }\n  auto delete_(kj::ArrayPtr<const kj::StringPtr> keys, ActorCache::WriteOptions options = {}) {\n    return target.delete_(KJ_MAP(k, keys) { return kj::str(k); }, options, nullptr);\n  }\n\n private:\n  ActorCacheOps& target;\n};\n\nstruct ActorCacheTestOptions {\n  bool monitorOutputGate = true;\n  size_t softLimit = 512 * 1024;\n  size_t hardLimit = 1024 * 1024;\n  kj::Duration staleTimeout = 1 * kj::SECONDS;\n  size_t dirtyListByteLimit = 64 * 1024;\n  size_t maxKeysPerRpc = 128;\n  bool noCache = false;\n  bool neverFlush = false;\n};\n\nstruct ActorCacheTest: public ActorCacheConvenienceWrappers {\n  // Common test setup code and helpers used in many test cases.\n\n  kj::EventLoop loop;\n  kj::WaitScope ws;\n  kj::Own<MockServer> mockStorage;\n\n  ActorCache::SharedLru lru;\n  OutputGate gate;\n  ActorCache cache;\n\n  kj::Promise<void> gateBrokenPromise;\n\n  kj::UnwindDetector unwindDetector;\n\n  ActorCacheTest(ActorCacheTestOptions options = {},\n      MockServer::Pair<rpc::ActorStorage::Stage> mockPair =\n          MockServer::make<rpc::ActorStorage::Stage>())\n      : ActorCacheConvenienceWrappers(cache),\n        ws(loop),\n        mockStorage(kj::mv(mockPair.mock)),\n        lru({options.softLimit, options.hardLimit, options.staleTimeout, options.dirtyListByteLimit,\n          options.maxKeysPerRpc, options.noCache, options.neverFlush}),\n        cache(kj::mv(mockPair.client), lru, gate),\n        gateBrokenPromise(options.monitorOutputGate ? eagerlyReportExceptions(gate.onBroken())\n                                                    : kj::Promise<void>(kj::READY_NOW)) {}\n\n  ~ActorCacheTest() noexcept(false) {\n    // Make sure if the output gate has been broken, the exception was reported. This is important\n    // to report errors thrown inside flush(), since those won't otherwise propagate into the test\n    // body.\n    gateBrokenPromise.poll(ws);\n\n    if (!unwindDetector.isUnwinding()) {\n      // On successful test completion, also check that there were no extra calls to the mock.\n      mockStorage->expectNoActivity(ws);\n\n      cache.verifyConsistencyForTest();\n    }\n  }\n};\n\nKJ_TEST(\"ActorCache single-key basics\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Get value that is present on disk.\n  {\n    auto promise = expectUncached(test.get(\"foo\"));\n\n    mockStorage->expectCall(\"get\", ws)\n        .withParams(CAPNP(key = \"foo\"))\n        .thenReturn(CAPNP(value = \"bar\"));\n\n    auto result = KJ_ASSERT_NONNULL(promise.wait(ws));\n    KJ_EXPECT(result == \"bar\");\n  }\n\n  // Get value that is absent on disk.\n  {\n    auto promise = expectUncached(test.get(\"bar\"));\n\n    mockStorage->expectCall(\"get\", ws).withParams(CAPNP(key = \"bar\")).thenReturn(CAPNP());\n\n    auto result = promise.wait(ws);\n    KJ_EXPECT(result == kj::none);\n  }\n\n  // Get cached.\n  {\n    auto result = KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\")));\n    KJ_EXPECT(result == \"bar\");\n  }\n  {\n    auto result = expectCached(test.get(\"bar\"));\n    KJ_EXPECT(result == kj::none);\n  }\n\n  // Overwrite with a put().\n  {\n    test.put(\"foo\", \"baz\");\n\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"foo\", value = \"baz\")]))\n        .thenReturn(CAPNP());\n  }\n\n  {\n    auto result = KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\")));\n    KJ_EXPECT(result == \"baz\");\n  }\n\n  {\n    KJ_ASSERT(expectCached(test.delete_(\"foo\")));\n\n    mockStorage->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [\"foo\"]))\n        .thenReturn(CAPNP(numDeleted = 1));\n  }\n\n  {\n    auto result = expectCached(test.get(\"foo\"));\n    KJ_EXPECT(result == kj::none);\n  }\n}\n\nKJ_TEST(\"ActorCache multi-key basics\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    // Request four keys, but only return two. The others should be marked empty. Note we\n    // intentionally make sure that, in alphabetical order, the keys alternate between present\n    // and absent, with the last one being absent, for maximum code coverage.\n    auto promise = expectUncached(test.get({\"foo\"_kj, \"bar\"_kj, \"baz\"_kj, \"qux\"_kj}));\n\n    mockStorage->expectCall(\"getMultiple\", ws)\n        .withParams(CAPNP(keys = [ \"bar\", \"baz\", \"foo\", \"qux\" ]), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"bar\", value = \"456\"),\n                          // baz absent\n                          (key = \"foo\", value = \"123\"),\n                          // qux absent\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    auto results = promise.wait(ws);\n    KJ_ASSERT(results == kvs({{\"bar\", \"456\"}, {\"foo\", \"123\"}}));\n  }\n\n  {\n    auto results = expectCached(test.get({\"foo\"_kj, \"bar\"_kj, \"baz\"_kj, \"qux\"_kj}));\n    KJ_ASSERT(results == kvs({{\"bar\", \"456\"}, {\"foo\", \"123\"}}));\n  }\n\n  {\n    test.put({{\"foo\", \"321\"}, {\"bar\", \"654\"}});\n\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [ (key = \"foo\", value = \"321\"), (key = \"bar\", value = \"654\") ]))\n        .thenReturn(CAPNP());\n  }\n\n  {\n    auto results = expectCached(test.get({\"foo\"_kj, \"bar\"_kj}));\n    KJ_ASSERT(results == kvs({{\"bar\", \"654\"}, {\"foo\", \"321\"}}));\n  }\n\n  {\n    KJ_ASSERT(expectCached(test.delete_({\"foo\"_kj, \"bar\"_kj, \"baz\"_kj, \"qux\"_kj})) == 2);\n\n    mockStorage->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [ \"foo\", \"bar\" ]))\n        .thenReturn(CAPNP(numDeleted = 2));\n  }\n\n  {\n    auto results = expectCached(test.get({\"foo\"_kj, \"bar\"_kj}));\n    KJ_ASSERT(results == kvs({}));\n  }\n}\n\n// =======================================================================================\n\nKJ_TEST(\"ActorCache more puts\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    test.put(\"foo\", \"bar\");\n\n    // Value is immediately in cache.\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"bar\");\n\n    auto inProgressFlush = mockStorage->expectCall(\"put\", ws).withParams(\n        CAPNP(entries = [(key = \"foo\", value = \"bar\")]));\n\n    // Still in cache during flush.\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"bar\");\n\n    kj::mv(inProgressFlush).thenReturn(CAPNP());\n  }\n\n  // Still in cache after transaction completion.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"bar\");\n\n  // Putting the exact same value is redundant, so doesn't do an RPC.\n  {\n    test.put(\"foo\", \"bar\");\n    mockStorage->expectNoActivity(ws);\n  }\n\n  // Putting a different value is not redundant.\n  {\n    test.put(\"foo\", \"baz\");\n\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"foo\", value = \"baz\")]))\n        .thenReturn(CAPNP());\n  }\n\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"baz\");\n}\n\nKJ_TEST(\"ActorCache more deletes\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.delete_(\"foo\"));\n\n    // Value is immediately in cache.\n    KJ_ASSERT(expectCached(test.get(\"foo\")) == kj::none);\n\n    auto mockDelete = mockStorage->expectCall(\"delete\", ws).withParams(CAPNP(keys = [\"foo\"]));\n\n    // Still in cache during flush.\n    KJ_ASSERT(!promise.poll(ws));\n    KJ_ASSERT(expectCached(test.get(\"foo\")) == kj::none);\n\n    kj::mv(mockDelete).thenReturn(CAPNP(numDeleted = 1));\n\n    // Delete call returned true due to numDeleted = 1.\n    KJ_ASSERT(promise.wait(ws));\n  }\n\n  // Still in cache after transaction completion.\n  KJ_ASSERT(expectCached(test.get(\"foo\")) == kj::none);\n\n  // Try a case where the key isn't on disk.\n  {\n    auto promise = expectUncached(test.delete_(\"bar\"));\n\n    mockStorage->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [\"bar\"]))\n        .thenReturn(CAPNP(numDeleted = 0));\n\n    // Delete call returned false due to numDeleted = 0.\n    KJ_ASSERT(!promise.wait(ws));\n  }\n\n  // Deleting an already-deleted key is redundant, so doesn't do an RPC.\n  {\n    KJ_ASSERT(!expectCached(test.delete_(\"foo\")));\n    KJ_ASSERT(!expectCached(test.delete_(\"bar\")));\n\n    mockStorage->expectNoActivity(ws);\n  }\n\n  // Putting over the deleted key is not redundant.\n  {\n    test.put(\"foo\", \"baz\");\n\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"foo\", value = \"baz\")]))\n        .thenReturn(CAPNP());\n  }\n\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"baz\");\n\n  // Deleting it again is not redundant.\n  {\n    KJ_ASSERT(expectCached(test.delete_(\"foo\")));\n\n    mockStorage->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [\"foo\"]))\n        .thenReturn(CAPNP(numDeleted = 1));\n  }\n\n  KJ_ASSERT(expectCached(test.get(\"foo\")) == kj::none);\n}\n\nKJ_TEST(\"ActorCache more multi-puts\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Create a scenario where we have several cached and uncached keys.\n  // foo, bar = cached with values\n  // baz, qux = cached as absent\n  // corge, grault = not cached\n  {\n    auto promise = expectUncached(test.get({\"foo\", \"bar\", \"baz\", \"qux\"}));\n\n    mockStorage->expectCall(\"getMultiple\", ws)\n        .withParams(CAPNP(keys = [ \"bar\", \"baz\", \"foo\", \"qux\" ]), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"bar\", value = \"456\"), (key = \"foo\", value = \"123\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    auto results = promise.wait(ws);\n    KJ_ASSERT(results == kvs({{\"bar\", \"456\"}, {\"foo\", \"123\"}}));\n  }\n\n  {\n    test.put({{\"foo\", \"321\"}, {\"bar\", \"456\"}, {\"baz\", \"654\"}, {\"corge\", \"987\"}});\n\n    // Values are immediately in cache.\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"321\");\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"bar\"))) == \"456\");\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"baz\"))) == \"654\");\n    KJ_ASSERT(expectCached(test.get(\"qux\")) == nullptr);\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"corge\"))) == \"987\");\n\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries =\n                              [\n                                (key = \"foo\", value = \"321\"),\n                                // bar omitted because it was redundant\n                                (key = \"baz\", value = \"654\"), (key = \"corge\", value = \"987\")\n                              ]))\n        .thenReturn(CAPNP());\n  }\n\n  // Fetch everything again for good measure.\n  {\n    auto promise =\n        expectUncached(test.get({\"foo\"_kj, \"bar\"_kj, \"baz\"_kj, \"qux\"_kj, \"corge\"_kj, \"grault\"_kj}));\n\n    // Only \"grault\" is not cached.\n    mockStorage->expectCall(\"getMultiple\", ws)\n        .withParams(CAPNP(keys = [\"grault\"]), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    auto results = promise.wait(ws);\n    KJ_ASSERT(results ==\n        kvs({\n          {\"bar\", \"456\"},\n          {\"baz\", \"654\"},\n          {\"corge\", \"987\"},\n          {\"foo\", \"321\"},\n        }));\n  }\n}\n\nKJ_TEST(\"ActorCache more multi-deletes\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Create a scenario where we have several cached and uncached keys.\n  // foo, bar = cached with values\n  // baz, qux = cached as absent\n  // corge, grault = not cached\n  {\n    auto promise = expectUncached(test.get({\"foo\", \"bar\", \"baz\", \"qux\"}));\n\n    mockStorage->expectCall(\"getMultiple\", ws)\n        .withParams(CAPNP(keys = [ \"bar\", \"baz\", \"foo\", \"qux\" ]), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"bar\", value = \"456\"), (key = \"foo\", value = \"123\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    auto results = promise.wait(ws);\n    KJ_ASSERT(results == kvs({{\"bar\", \"456\"}, {\"foo\", \"123\"}}));\n  }\n\n  {\n    auto promise = expectUncached(test.delete_({\"bar\"_kj, \"qux\"_kj, \"corge\"_kj, \"grault\"_kj}));\n\n    // Values are immediately in cache.\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n    KJ_ASSERT(expectCached(test.get(\"bar\")) == nullptr);\n    KJ_ASSERT(expectCached(test.get(\"baz\")) == nullptr);\n    KJ_ASSERT(expectCached(test.get(\"qux\")) == nullptr);\n    KJ_ASSERT(expectCached(test.get(\"corge\")) == nullptr);\n    KJ_ASSERT(expectCached(test.get(\"grault\")) == nullptr);\n\n    auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [ \"corge\", \"grault\" ]))\n        .thenReturn(CAPNP(numDeleted = 1));\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [\"bar\"]))\n        .thenReturn(CAPNP(numDeleted = 65382));  // count is ignored\n    mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n    mockTxn->expectDropped(ws);\n\n    KJ_ASSERT(promise.wait(ws) == 2);\n  }\n\n  // Fetch everything again for good measure.\n  {\n    auto promise = expectUncached(\n        test.get({\"foo\"_kj, \"bar\"_kj, \"baz\"_kj, \"qux\"_kj, \"corge\"_kj, \"grault\"_kj, \"garply\"_kj}));\n\n    // Only \"garply\" is not cached.\n    mockStorage->expectCall(\"getMultiple\", ws)\n        .withParams(CAPNP(keys = [\"garply\"]), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"garply\", value = \"abcd\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    auto results = promise.wait(ws);\n    KJ_ASSERT(results == kvs({{\"foo\", \"123\"}, {\"garply\", \"abcd\"}}));\n  }\n}\n\nKJ_TEST(\"ActorCache batching due to maxKeysPerRpc\") {\n  ActorCacheTest test({.maxKeysPerRpc = 2});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Do 5 puts and 3 deletes and expect a transaction that is batched accordingly given we set the\n  // batch size to 2.\n  test.put({{\"foo\", \"123\"}, {\"bar\", \"456\"}, {\"baz\", \"789\"}});\n  test.put(\"qux\", \"555\");\n  test.put(\"corge\", \"999\");\n\n  // Note that because we drop the returned promises from these deletes, they end up as \"muted\"\n  // deletes, so the resulting batches don't have to match the original calls.\n  test.delete_(\"grault\");\n  test.delete_({\"garply\"_kj, \"waldo\"_kj});\n\n  // We keep these promises, so they should not be \"muted\". Specifically, \"count4\" should be its own\n  // batch despite fitting in a batch with \"count3\" because it's a separate delete.\n  auto deleteProm1 = expectUncached(test.delete_({\"count1\"_kj, \"count2\"_kj, \"count3\"_kj}));\n  auto deleteProm2 = expectUncached(test.delete_({\"count4\"_kj}));\n  auto deleteProm3 = expectUncached(test.delete_({\"count5\"_kj, \"count6\"_kj}));\n\n  auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n  mockTxn->expectCall(\"delete\", ws)\n      .withParams(CAPNP(keys = [ \"count1\", \"count2\" ]))\n      .thenReturn(CAPNP(numDeleted = 1));  // Treat one of this batch as present, 2 total.\n  mockTxn->expectCall(\"delete\", ws)\n      .withParams(CAPNP(keys = [\"count3\"]))\n      .thenReturn(CAPNP(numDeleted = 1));  // Treat one of this batch as present, 2 total.\n  mockTxn->expectCall(\"delete\", ws)\n      .withParams(CAPNP(keys = [\"count4\"]))\n      .thenReturn(CAPNP(numDeleted = 0));  // Treat this batch as absent.\n  mockTxn->expectCall(\"delete\", ws)\n      .withParams(CAPNP(keys = [ \"count5\", \"count6\" ]))\n      .thenReturn(CAPNP(numDeleted = 2));  // Treat all of this batch as present.\n  mockTxn->expectCall(\"delete\", ws)\n      .withParams(CAPNP(keys = [ \"grault\", \"garply\" ]))\n      .thenReturn(CAPNP(numDeleted = 1));\n  mockTxn->expectCall(\"delete\", ws)\n      .withParams(CAPNP(keys = [\"waldo\"]))\n      .thenReturn(CAPNP(numDeleted = 1));\n  mockTxn->expectCall(\"put\", ws)\n      .withParams(CAPNP(entries = [ (key = \"foo\", value = \"123\"), (key = \"bar\", value = \"456\") ]))\n      .thenReturn(CAPNP());\n  mockTxn->expectCall(\"put\", ws)\n      .withParams(CAPNP(entries = [ (key = \"baz\", value = \"789\"), (key = \"qux\", value = \"555\") ]))\n      .thenReturn(CAPNP());\n  mockTxn->expectCall(\"put\", ws)\n      .withParams(CAPNP(entries = [(key = \"corge\", value = \"999\")]))\n      .thenReturn(CAPNP());\n  mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n  mockTxn->expectDropped(ws);\n\n  KJ_EXPECT(deleteProm1.wait(ws) == 2);\n  KJ_EXPECT(deleteProm2.wait(ws) == 0);\n  KJ_EXPECT(deleteProm3.wait(ws) == 2);\n}\n\nKJ_TEST(\"ActorCache batching due to max storage RPC words\") {\n  ActorCacheTest test({.hardLimit = 128 * 1024 * 1024});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Doing 128 puts with 128 KiB values should exceed the 16 MiB limit enforced on storage RPCs.\n  auto bigVal = kj::heapArray<const byte>(128 * 1024);\n  for (int i = 0; i < 128; ++i) {\n    test.cache.put(kj::str(i),\n        kj::Array<const byte>(bigVal.begin(), bigVal.size(), kj::NullArrayDisposer::instance),\n        ActorCache::WriteOptions(), nullptr);\n  }\n\n  auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n  mockTxn->expectCall(\"put\", ws).thenReturn(CAPNP());\n  mockTxn->expectCall(\"put\", ws).thenReturn(CAPNP());\n  mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n  mockTxn->expectDropped(ws);\n}\n\nKJ_TEST(\"ActorCache deleteAll()\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Populate the cache with some stuff.\n  {\n    auto promise = expectUncached(test.get({\"qux\"_kj, \"corge\"_kj}));\n\n    mockStorage->expectCall(\"getMultiple\", ws)\n        .withParams(CAPNP(keys = [ \"corge\", \"qux\" ]), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"corge\", value = \"555\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    auto results = promise.wait(ws);\n    KJ_ASSERT(results == kvs({{\"corge\", \"555\"}}));\n  }\n\n  test.put(\"foo\", \"123\");  // plain put\n  auto deletePromise = expectUncached(test.delete_({\"bar\"_kj, \"baz\"_kj, \"grault\"_kj}));\n  test.put(\"baz\", \"789\");  // overwrites a counted delete\n  test.delete_(\"garply\");  // uncounted delete\n\n  auto deleteAll = test.cache.deleteAll({}, nullptr);\n\n  // Post-deleteAll writes.\n  test.put(\"grault\", \"12345\");\n  test.put(\"garply\", \"54321\");\n  test.put(\"waldo\", \"99999\");\n\n  // Alarms are not affected by deleteAll, so this alarm set should actually end up in\n  // the pre-deleteAll flush.\n  test.setAlarm(12345 * kj::MILLISECONDS + kj::UNIX_EPOCH);\n\n  KJ_ASSERT(expectCached(test.get(\"foo\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"baz\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"corge\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"a\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"z\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"\")) == nullptr);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"grault\"))) == \"12345\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"garply\"))) == \"54321\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"waldo\"))) == \"99999\");\n\n  {\n    auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [ \"bar\", \"baz\", \"grault\" ]))\n        .thenReturn(CAPNP(numDeleted = 2));\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [\"garply\"]))\n        .thenReturn(CAPNP(numDeleted = 2));\n    mockTxn->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [ (key = \"foo\", value = \"123\"), (key = \"baz\", value = \"789\") ]))\n        .thenReturn(CAPNP());\n    mockTxn->expectCall(\"setAlarm\", ws)\n        .withParams(CAPNP(scheduledTimeMs = 12345))\n        .thenReturn(CAPNP());\n    mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n    mockTxn->expectDropped(ws);\n  }\n\n  mockStorage->expectCall(\"deleteAll\", ws).thenReturn(CAPNP(numDeleted = 2));\n\n  KJ_ASSERT(deleteAll.count.wait(ws) == 2);\n\n  // Post-deleteAll writes in a new flush.\n  {\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries =\n                              [\n                                (key = \"grault\", value = \"12345\"),\n                                (key = \"garply\", value = \"54321\"), (key = \"waldo\", value = \"99999\")\n                              ]))\n        .thenReturn(CAPNP());\n  }\n\n  KJ_ASSERT(deletePromise.wait(ws) == 2);\n\n  KJ_ASSERT(expectCached(test.get(\"foo\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"baz\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"corge\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"a\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"z\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"\")) == nullptr);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"grault\"))) == \"12345\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"garply\"))) == \"54321\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"waldo\"))) == \"99999\");\n}\n\nKJ_TEST(\"ActorCache deleteAll() during transaction commit\") {\n  // This tests a race condition that existed previously in the code.\n\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Get a transaction going, and then issue a deleteAll() in the middle of it.\n  test.put(\"foo\", \"123\");\n\n  {\n    auto inProgressFlush = mockStorage->expectCall(\"put\", ws).withParams(\n        CAPNP(entries = [(key = \"foo\", value = \"123\")]));\n\n    // Issue a put and a deleteAll() here!\n    test.put(\"bar\", \"456\");\n    test.cache.deleteAll({}, nullptr);\n\n    kj::mv(inProgressFlush).thenReturn(CAPNP());\n  }\n\n  // We should see a new flush happen for the pre-deleteAll() write.\n  {\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"bar\", value = \"456\")]))\n        .thenReturn(CAPNP());\n  }\n\n  // Now the deleteAll() actually happens.\n  mockStorage->expectCall(\"deleteAll\", ws).thenReturn(CAPNP());\n}\n\nKJ_TEST(\"ActorCache deleteAll() again when previous one isn't done yet\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Populate the cache with some stuff.\n  {\n    auto promise = expectUncached(test.get({\"qux\"_kj, \"corge\"_kj}));\n\n    mockStorage->expectCall(\"getMultiple\", ws)\n        .withParams(CAPNP(keys = [ \"corge\", \"qux\" ]), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"corge\", value = \"555\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    auto results = promise.wait(ws);\n    KJ_ASSERT(results == kvs({{\"corge\", \"555\"}}));\n  }\n\n  test.put(\"foo\", \"123\");  // plain put\n  auto deletePromise = expectUncached(test.delete_({\"bar\"_kj, \"baz\"_kj, \"grault\"_kj}));\n  test.put(\"baz\", \"789\");  // overwrites a counted delete\n  test.delete_(\"garply\");  // uncounted delete\n\n  auto deleteAllA = test.cache.deleteAll({}, nullptr);\n\n  // Post-deleteAll writes.\n  test.put(\"grault\", \"12345\");\n  test.put(\"garply\", \"54321\");\n  test.put(\"waldo\", \"99999\");\n\n  {\n    auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [ \"bar\", \"baz\", \"grault\" ]))\n        .thenReturn(CAPNP(numDeleted = 2));\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [\"garply\"]))\n        .thenReturn(CAPNP(numDeleted = 2));\n    mockTxn->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [ (key = \"foo\", value = \"123\"), (key = \"baz\", value = \"789\") ]))\n        .thenReturn(CAPNP());\n    mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n    mockTxn->expectDropped(ws);\n  }\n\n  // Do another deleteAll() before the first one is done.\n  auto deleteAllB = test.cache.deleteAll({}, nullptr);\n\n  // And a write after that.\n  test.put(\"fred\", \"2323\");\n\n  // Now finish it.\n  mockStorage->expectCall(\"deleteAll\", ws).thenReturn(CAPNP(numDeleted = 2));\n  KJ_ASSERT(deleteAllA.count.wait(ws) == 2);\n  KJ_ASSERT(deleteAllB.count.wait(ws) == 0);\n\n  // The deleteAll()s were coalesced, so only the final write is committed.\n  {\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"fred\", value = \"2323\")]))\n        .thenReturn(CAPNP());\n  }\n  KJ_ASSERT(deletePromise.wait(ws) == 2);\n}\n\nKJ_TEST(\"ActorCache coalescing\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Create a scenario where we have several cached and uncached keys.\n  // foo, bar = cached with values\n  // baz, qux = cached as absent\n  // corge, grault, others = not cached\n  {\n    auto promise = expectUncached(test.get({\"foo\", \"bar\", \"baz\", \"qux\"}));\n\n    mockStorage->expectCall(\"getMultiple\", ws)\n        .withParams(CAPNP(keys = [ \"bar\", \"baz\", \"foo\", \"qux\" ]), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"bar\", value = \"456\"), (key = \"foo\", value = \"123\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    auto results = promise.wait(ws);\n    KJ_ASSERT(results == kvs({{\"bar\", \"456\"}, {\"foo\", \"123\"}}));\n  }\n\n  // Now do several puts and deletes that overwrite each other, and make sure they coalesce\n  // properly.\n  {\n    test.put({{\"bar\", \"654\"}, {\"qux\", \"555\"}, {\"corge\", \"789\"}});\n    test.put(\"corge\", \"987\");\n    auto promise1 = expectUncached(test.delete_({\"bar\"_kj, \"grault\"_kj}));\n    KJ_ASSERT(expectCached(test.delete_(\"foo\")));\n    auto promise2 = expectUncached(test.delete_({\"garply\"_kj, \"waldo\"_kj, \"fred\"_kj}));\n\n    // Note this final put undoes a delete. However, the delete was of a key not in cache, so it\n    // still has to be performed in order to produce the deletion count.\n    test.put(\"waldo\", \"odlaw\");\n\n    auto values = expectCached(test.get({\"foo\"_kj, \"bar\"_kj, \"baz\"_kj, \"qux\"_kj, \"corge\"_kj,\n      \"grault\"_kj, \"garply\"_kj, \"waldo\"_kj, \"fred\"_kj}));\n    KJ_EXPECT(values ==\n        kvs({\n          {\"corge\", \"987\"},\n          {\"qux\", \"555\"},\n          {\"waldo\", \"odlaw\"},\n        }));\n\n    auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [\"grault\"]))\n        .thenReturn(CAPNP(numDeleted = 0));\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [ \"garply\", \"waldo\", \"fred\" ]))\n        .thenReturn(CAPNP(numDeleted = 2));\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [ \"bar\", \"foo\" ]))\n        .thenReturn(CAPNP(numDeleted = 65382));  // count is ignored\n    mockTxn->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries =\n                              [\n                                (key = \"qux\", value = \"555\"), (key = \"corge\", value = \"987\"),\n                                (key = \"waldo\", value = \"odlaw\")\n                              ]))\n        .thenReturn(CAPNP());\n    mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n    mockTxn->expectDropped(ws);\n\n    KJ_ASSERT(promise1.wait(ws) == 1);\n    KJ_ASSERT(promise2.wait(ws) == 2);\n  }\n}\n\nKJ_TEST(\"ActorCache canceled deletes are coalesced\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // A bunch of deletes where we immediately drop the returned promises.\n  (void)expectUncached(test.delete_(\"foo\"));\n  (void)expectUncached(test.delete_({\"bar\"_kj, \"baz\"_kj}));\n  (void)expectUncached(test.delete_(\"qux\"));\n\n  // Keep one promise.\n  auto promise = expectUncached(test.delete_(\"corge\"));\n\n  // Overwrite one of them.\n  test.put(\"qux\", \"blah\");\n\n  // The deletes where the caller stopped listening will be coalesced into one, or dropped entirely\n  // if overwritten by a later put().\n  {\n    auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [\"corge\"]))\n        .thenReturn(CAPNP(numDeleted = 0));\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [ \"foo\", \"bar\", \"baz\" ]))\n        .thenReturn(CAPNP(numDeleted = 1234));  // count ignored\n    mockTxn->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"qux\", value = \"blah\")]))\n        .thenReturn(CAPNP());\n    mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n    mockTxn->expectDropped(ws);\n  }\n\n  KJ_ASSERT(!promise.wait(ws));\n}\n\nKJ_TEST(\"ActorCache get-put ordering\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Initiate a get, followed by a put and a delete that affect the same keys. Since the get()\n  // started first, its final results later should not reflect the put and delete.\n  auto promise1 = expectUncached(test.get({\"foo\"_kj, \"bar\"_kj, \"baz\"_kj}));\n  test.put(\"foo\", \"123\");\n  auto deletePromise = expectUncached(test.delete_(\"bar\"));\n\n  // Verify cache content.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n  KJ_ASSERT(expectCached(test.get(\"bar\")) == nullptr);\n\n  // Start another get. This time, \"foo\" and \"bar\" will be served from cache, but \"baz\" is still\n  // on disk. This means this get won't complete immediately. We'll then overwrite the value of\n  // \"bar\", but hope that the get() has already picked up the cached value for consistency.\n  auto promise2 = expectUncached(test.get({\"foo\"_kj, \"bar\"_kj, \"baz\"_kj}));\n  test.put(\"bar\", \"456\");\n\n  // Verify cache content.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"bar\"))) == \"456\");\n\n  // Expect to receive the storage gets. But, don't return from them yet!\n  KJ_ASSERT(!promise1.poll(ws));\n  auto mockGet1 = mockStorage->expectCall(\"getMultiple\", ws)\n                      .withParams(CAPNP(keys = [ \"bar\", \"baz\", \"foo\" ]), \"stream\"_kj);\n\n  KJ_ASSERT(!promise2.poll(ws));\n  auto mockGet2 =\n      mockStorage->expectCall(\"getMultiple\", ws).withParams(CAPNP(keys = [\"baz\"]), \"stream\"_kj);\n\n  // No writes will be done until our reads finish!\n  mockStorage->expectNoActivity(ws);\n\n  // Let's have the second read complete first.\n  kj::mv(mockGet2)\n      .useCallback(\"stream\", [&](MockClient stream) {\n    stream.call(\"values\", CAPNP(list = [(key = \"baz\", value = \"987\")])).expectReturns(CAPNP(), ws);\n    stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n  }).expectCanceled();\n\n  // The completed read returns cached results as of when it was called, merged with what it\n  // read from disk.\n  KJ_ASSERT(promise2.wait(ws) == kvs({{\"baz\", \"987\"}, {\"foo\", \"123\"}}));\n\n  // The completed read brought \"baz\" into cache but didn't change \"foo\" or \"bar\".\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"bar\"))) == \"456\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"baz\"))) == \"987\");\n\n  // Still no writes because the first read is still outstanding.\n  mockStorage->expectNoActivity(ws);\n\n  // Finally, have the first read complete.\n  kj::mv(mockGet1)\n      .useCallback(\"stream\", [&](MockClient stream) {\n    stream\n        .call(\"values\",\n            CAPNP(list =\n                      [\n                        (key = \"bar\", value = \"654\"), (key = \"baz\", value = \"987\"),\n                        (key = \"foo\", value = \"321\")\n                      ]))\n        .expectReturns(CAPNP(), ws);\n    stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n  }).expectCanceled();\n\n  // This returns exactly what came off disk, not reflecting any later writes.\n  KJ_ASSERT(promise1.wait(ws) == kvs({{\"bar\", \"654\"}, {\"baz\", \"987\"}, {\"foo\", \"321\"}}));\n\n  // The completed read didn't mess with the cache.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"bar\"))) == \"456\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"baz\"))) == \"987\");\n\n  // Next up, the flush transaction proceeds.\n  auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n  mockTxn->expectCall(\"delete\", ws)\n      .withParams(CAPNP(keys = [\"bar\"]))\n      .thenReturn(CAPNP(numDeleted = 1));\n  mockTxn->expectCall(\"put\", ws)\n      .withParams(CAPNP(entries = [ (key = \"foo\", value = \"123\"), (key = \"bar\", value = \"456\") ]))\n      .thenReturn(CAPNP());\n  mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n  mockTxn->expectDropped(ws);\n\n  // Our delete finally finished.\n  KJ_ASSERT(deletePromise.wait(ws) == 1);\n\n  // Cache is still good.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"bar\"))) == \"456\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"baz\"))) == \"987\");\n}\n\nKJ_TEST(\"ActorCache put during flush\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  test.put({{\"foo\", \"123\"}, {\"bar\", \"456\"}});\n\n  {\n    auto inProgressFlush = mockStorage->expectCall(\"put\", ws).withParams(\n        CAPNP(entries = [ (key = \"foo\", value = \"123\"), (key = \"bar\", value = \"456\") ]));\n\n    // We're in the middle of flushing... do a put. Should be fine.\n    test.put(\"bar\", \"654\");\n\n    kj::mv(inProgressFlush).thenReturn(CAPNP());\n  }\n\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"bar\"))) == \"654\");\n\n  {\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"bar\", value = \"654\")]))\n        .thenReturn(CAPNP());\n  }\n\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"bar\"))) == \"654\");\n}\n\nKJ_TEST(\"ActorCache flush retry\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  test.put({{\"foo\", \"123\"}, {\"bar\", \"456\"}, {\"baz\", \"789\"}});\n  auto promise1 = expectUncached(test.delete_({\"qux\"_kj, \"quux\"_kj}));\n  auto promise2 = expectUncached(test.delete_({\"corge\"_kj, \"grault\"_kj}));\n\n  {\n    auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n    // One delete succeeds, the other throws (later).\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [ \"qux\", \"quux\" ]))\n        .thenReturn(CAPNP(numDeleted = 1));\n    auto mockDelete =\n        mockTxn->expectCall(\"delete\", ws).withParams(CAPNP(keys = [ \"corge\", \"grault\" ]));\n    mockTxn->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries =\n                              [\n                                (key = \"foo\", value = \"123\"), (key = \"bar\", value = \"456\"),\n                                (key = \"baz\", value = \"789\")\n                              ]))\n        .thenReturn(CAPNP());\n\n    // While the transaction is outstanding, some more puts and deletes mess with things...\n    test.put(\"bar\", \"654\");\n    KJ_ASSERT(expectCached(test.delete_(\"baz\")));\n    test.put(\"qux\", \"987\");\n    test.put(\"corge\", \"555\");\n\n    kj::mv(mockDelete).thenThrow(KJ_EXCEPTION(DISCONNECTED, \"delete failed\"));\n    mockTxn->expectCall(\"commit\", ws).thenThrow(KJ_EXCEPTION(DISCONNECTED, \"flush failed\"));\n    mockTxn->expectDropped(ws);\n  }\n\n  // Verify cache.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"bar\"))) == \"654\");\n  KJ_ASSERT(expectCached(test.get(\"baz\")) == nullptr);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"qux\"))) == \"987\");\n  KJ_ASSERT(expectCached(test.get(\"quux\")) == nullptr);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"corge\"))) == \"555\");\n  KJ_ASSERT(expectCached(test.get(\"grault\")) == nullptr);\n\n  // Although the counted delete succeeded, the promise will not resolve until our flush succeeds!\n  KJ_ASSERT(!promise1.poll(ws));\n  // The second delete failed and is also still outstanding until a flush succeeds.\n  KJ_ASSERT(!promise2.poll(ws));\n\n  // The transaction will be retried, with the updated puts and deletes.\n  {\n    auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n    // Note that \"corge\" is still the subject of a delete, even though it has since been\n    // overwritten by a put, because we still need to count the delete. \"qux\", on the other\n    // hand, no longer needs counting, and has also been overwritten by a put(), so it doesn't\n    // need to be deleted anymore. \"quux\" is still deleted, even though the count was returned\n    // last time, because it hasn't been further overwritten, and that delete from last time\n    // wasn't actually committed.\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [\"quux\"]))\n        .thenReturn(CAPNP());  // count ignored because we got it on the first try!\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [ \"corge\", \"grault\" ]))\n        .thenReturn(CAPNP(numDeleted = 2));\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [\"baz\"]))\n        .thenReturn(CAPNP(numDeleted = 1234));  // count ignored\n    mockTxn->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries =\n                              [\n                                (key = \"foo\", value = \"123\"), (key = \"bar\", value = \"654\"),\n                                (key = \"qux\", value = \"987\"), (key = \"corge\", value = \"555\")\n                              ]))\n        .thenReturn(CAPNP());\n    mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n    mockTxn->expectDropped(ws);\n  }\n\n  // Verify cache.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"bar\"))) == \"654\");\n  KJ_ASSERT(expectCached(test.get(\"baz\")) == nullptr);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"qux\"))) == \"987\");\n  KJ_ASSERT(expectCached(test.get(\"quux\")) == nullptr);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"corge\"))) == \"555\");\n  KJ_ASSERT(expectCached(test.get(\"grault\")) == nullptr);\n\n  // Second delete finished this time.\n  KJ_ASSERT(promise2.wait(ws) == 2);\n\n  // Our flush has succeeded and we've obtained our count!\n  KJ_ASSERT(promise1.wait(ws) == 1);\n}\n\nKJ_TEST(\"ActorCache output gate blocked during flush\") {\n  ActorCacheTest test({.monitorOutputGate = false});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(ws));\n\n  // Do a put.\n  test.put(\"foo\", \"123\");\n  test.delete_(\"bar\");\n\n  // Now it is blocked.\n  auto gatePromise = test.gate.wait(nullptr);\n  KJ_ASSERT(!gatePromise.poll(ws));\n\n  // Complete the transaction.\n  {\n    auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [\"bar\"]))\n        .thenReturn(CAPNP(numDeleted = 0));\n    mockTxn->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"foo\", value = \"123\")]))\n        .thenReturn(CAPNP());\n    auto commitCall = mockTxn->expectCall(\"commit\", ws);\n\n    // Still blocked until commit completes.\n    KJ_ASSERT(!gatePromise.poll(ws));\n\n    kj::mv(commitCall).thenReturn(CAPNP());\n    mockTxn->expectDropped(ws);\n  }\n\n  KJ_ASSERT(gatePromise.poll(ws));\n  gatePromise.wait(ws);\n}\n\nKJ_TEST(\"ActorCache output gate bypass\") {\n  ActorCacheTest test({.monitorOutputGate = false});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Gate is currently not blocked.\n  test.gate.wait(nullptr).wait(ws);\n\n  // Do a put.\n  test.put(\"foo\", \"123\", {.allowUnconfirmed = true});\n\n  // Gate still isn't blocked, because we set `allowUnconfirmed`.\n  test.gate.wait(nullptr).wait(ws);\n\n  // Complete the transaction.\n  {\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"foo\", value = \"123\")]))\n        .thenReturn(CAPNP());\n  }\n\n  test.gate.wait(nullptr).wait(ws);\n}\n\nKJ_TEST(\"ActorCache output gate bypass on one put but not the next\") {\n  ActorCacheTest test({.monitorOutputGate = false});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Gate is currently not blocked.\n  test.gate.wait(nullptr).wait(ws);\n\n  // Do two puts, only bypassing on the first. The net result should be that the output gate is\n  // in effect.\n  test.put(\"foo\", \"123\", {.allowUnconfirmed = true});\n  test.put(\"bar\", \"456\");\n\n  // Now it is blocked.\n  auto gatePromise = test.gate.wait(nullptr);\n  KJ_ASSERT(!gatePromise.poll(ws));\n\n  // Complete the transaction.\n  {\n    auto inProgressFlush = mockStorage->expectCall(\"put\", ws).withParams(\n        CAPNP(entries = [ (key = \"foo\", value = \"123\"), (key = \"bar\", value = \"456\") ]));\n\n    // Still blocked until the flush completes.\n    KJ_ASSERT(!gatePromise.poll(ws));\n\n    kj::mv(inProgressFlush).thenReturn(CAPNP());\n  }\n\n  KJ_ASSERT(gatePromise.poll(ws));\n  gatePromise.wait(ws);\n}\n\nKJ_TEST(\"ActorCache flush hard failure\") {\n  ActorCacheTest test({.monitorOutputGate = false});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto promise = test.gate.onBroken();\n\n  test.put(\"foo\", \"123\");\n\n  KJ_ASSERT(!promise.poll(ws));\n\n  {\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"foo\", value = \"123\")]))\n        .thenThrow(KJ_EXCEPTION(FAILED, \"jsg.Error: flush failed hard\"));\n  }\n\n  KJ_EXPECT_THROW_MESSAGE(\n      \"broken.outputGateBroken; jsg.Error: flush failed hard\", promise.wait(ws));\n\n  // Further writes won't even try to start any new transactions because the failure killed them all.\n  test.put(\"bar\", \"456\");\n}\n\nKJ_TEST(\"ActorCache flush hard failure with output gate bypass\") {\n  ActorCacheTest test({.monitorOutputGate = false});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto promise = test.gate.onBroken();\n\n  test.put(\"foo\", \"123\", {.allowUnconfirmed = true});\n\n  // The output gate is not applied.\n  test.gate.wait(nullptr).wait(ws);\n  KJ_ASSERT(!promise.poll(ws));\n\n  {\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"foo\", value = \"123\")]))\n        .thenThrow(KJ_EXCEPTION(FAILED, \"jsg.Error: flush failed hard\"));\n  }\n\n  // The failure was still propagated to the output gate.\n  KJ_EXPECT_THROW_MESSAGE(\"flush failed hard\", promise.wait(ws));\n  KJ_EXPECT_THROW_MESSAGE(\"flush failed hard\", test.gate.wait(nullptr).wait(ws));\n\n  // Further writes won't even try to start any new transactions because the failure killed them all.\n  test.put(\"bar\", \"456\");\n}\n\nKJ_TEST(\"ActorCache read retry\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto promise = expectUncached(test.get(\"foo\"));\n  test.put(\"bar\", \"456\");\n  test.delete_(\"baz\");\n\n  // Expect the get, but don't resolve yet.\n  auto mockGet = mockStorage->expectCall(\"get\", ws).withParams(CAPNP(key = \"foo\"));\n\n  // No activity because reads are outstanding.\n  mockStorage->expectNoActivity(ws);\n\n  // Fail out the read with a disconnect.\n  kj::mv(mockGet).thenThrow(KJ_EXCEPTION(DISCONNECTED, \"read failed\"));\n\n  // It will be retried.\n  auto mockGet2 = mockStorage->expectCall(\"get\", ws).withParams(CAPNP(key = \"foo\"));\n\n  // Still no activity because of the read.\n  mockStorage->expectNoActivity(ws);\n\n  // Finish it.\n  kj::mv(mockGet2).thenReturn(CAPNP(value = \"123\"));\n\n  // Now the transaction starts actually writing (and completes).\n  auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n  mockTxn->expectCall(\"delete\", ws)\n      .withParams(CAPNP(keys = [\"baz\"]))\n      .thenReturn(CAPNP(numDeleted = 0));\n  mockTxn->expectCall(\"put\", ws)\n      .withParams(CAPNP(entries = [(key = \"bar\", value = \"456\")]))\n      .thenReturn(CAPNP());\n  mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n  mockTxn->expectDropped(ws);\n\n  // And the read finishes.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(promise.wait(ws)) == \"123\");\n}\n\nKJ_TEST(\"ActorCache read retry on flush containing only puts\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto promise = expectUncached(test.get(\"foo\"));\n  test.put(\"bar\", \"456\");\n\n  // Expect the get, but don't resolve yet.\n  auto mockGet = mockStorage->expectCall(\"get\", ws).withParams(CAPNP(key = \"foo\"));\n\n  // No activity on the flush yet (not even starting a txn), because reads are outstanding.\n  mockStorage->expectNoActivity(ws);\n\n  // Fail out the read with a disconnect.\n  kj::mv(mockGet).thenThrow(KJ_EXCEPTION(DISCONNECTED, \"read failed\"));\n\n  // It will be retried.\n  auto mockGet2 = mockStorage->expectCall(\"get\", ws).withParams(CAPNP(key = \"foo\"));\n\n  // Still no transaction activity.\n  mockStorage->expectNoActivity(ws);\n\n  // Finish it.\n  kj::mv(mockGet2).thenReturn(CAPNP(value = \"123\"));\n\n  // Now the transaction starts actually writing (and completes).\n  mockStorage->expectCall(\"put\", ws)\n      .withParams(CAPNP(entries = [(key = \"bar\", value = \"456\")]))\n      .thenReturn(CAPNP());\n\n  // And the read finishes.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(promise.wait(ws)) == \"123\");\n}\n\nKJ_TEST(\"ActorCache read hard fail\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Don't use expectCached() this time because we don't want eagerlyReportExceptions(), because\n  // we actually expect an exception.\n  auto promise = test.get(\"foo\").get<kj::Promise<kj::Maybe<kj::String>>>();\n  test.put(\"bar\", \"456\");\n  test.delete_(\"baz\");\n\n  // Expect the get, but don't resolve yet.\n  auto mockGet = mockStorage->expectCall(\"get\", ws).withParams(CAPNP(key = \"foo\"));\n\n  // We won't write anything until the read completes.\n  mockStorage->expectNoActivity(ws);\n\n  // Fail out the read with non-disconnect.\n  kj::mv(mockGet).thenThrow(KJ_EXCEPTION(FAILED, \"read failed\"));\n\n  // The read propagates the error.\n  KJ_EXPECT_THROW_MESSAGE(\"read failed\", promise.wait(ws));\n\n  // The read is NOT retried, so expect the transaction to run now.\n  auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n  mockTxn->expectCall(\"delete\", ws)\n      .withParams(CAPNP(keys = [\"baz\"]))\n      .thenReturn(CAPNP(numDeleted = 0));\n  mockTxn->expectCall(\"put\", ws)\n      .withParams(CAPNP(entries = [(key = \"bar\", value = \"456\")]))\n      .thenReturn(CAPNP());\n  mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n  mockTxn->expectDropped(ws);\n\n  // The read is NOT retried.\n  mockStorage->expectNoActivity(ws);\n}\n\nKJ_TEST(\"ActorCache read cancel\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto promise = expectUncached(test.get(\"foo\"));\n  test.put(\"bar\", \"456\");\n  test.delete_(\"baz\");\n\n  // Expect the get, but intentionally don't resolve it.\n  auto mockGet = mockStorage->expectCall(\"get\", ws).withParams(CAPNP(key = \"foo\"));\n\n  // We won't write anything until the read completes.\n  mockStorage->expectNoActivity(ws);\n\n  // Cancel the read.\n  promise = nullptr;\n  mockGet.expectCanceled();\n\n  // The transaction proceeds.\n  auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n  mockTxn->expectCall(\"delete\", ws)\n      .withParams(CAPNP(keys = [\"baz\"]))\n      .thenReturn(CAPNP(numDeleted = 0));\n  mockTxn->expectCall(\"put\", ws)\n      .withParams(CAPNP(entries = [(key = \"bar\", value = \"456\")]))\n      .thenReturn(CAPNP());\n\n  mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n  mockTxn->expectDropped(ws);\n}\n\nKJ_TEST(\"ActorCache get-multiple multiple blocks\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto promise = expectUncached(test.get({\"foo\"_kj, \"bar\"_kj, \"baz\"_kj, \"qux\"_kj, \"corge\"_kj}));\n\n  mockStorage->expectCall(\"getMultiple\", ws)\n      .withParams(CAPNP(keys = [ \"bar\", \"baz\", \"corge\", \"foo\", \"qux\" ]), \"stream\"_kj)\n      .useCallback(\"stream\", [&](MockClient stream) {\n    stream.call(\"values\", CAPNP(list = [(key = \"baz\", value = \"456\")])).expectReturns(CAPNP(), ws);\n\n    // At this point, \"bar\" and \"baz\" are considered cached.\n    KJ_ASSERT(expectCached(test.get(\"bar\")) == nullptr);\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"baz\"))) == \"456\");\n    (void)expectUncached(test.get(\"corge\"));\n    (void)expectUncached(test.get(\"foo\"));\n    (void)expectUncached(test.get(\"qux\"));\n\n    stream.call(\"values\", CAPNP(list = [(key = \"foo\", value = \"789\")])).expectReturns(CAPNP(), ws);\n\n    // At this point, everything except \"qux\" is cached.\n    KJ_ASSERT(expectCached(test.get(\"bar\")) == nullptr);\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"baz\"))) == \"456\");\n    KJ_ASSERT(expectCached(test.get(\"corge\")) == nullptr);\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"789\");\n    (void)expectUncached(test.get(\"qux\"));\n\n    stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n\n    // Now it's all cached.\n    KJ_ASSERT(expectCached(test.get(\"bar\")) == nullptr);\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"baz\"))) == \"456\");\n    KJ_ASSERT(expectCached(test.get(\"corge\")) == nullptr);\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"789\");\n    KJ_ASSERT(expectCached(test.get(\"qux\")) == nullptr);\n  }).expectCanceled();\n\n  KJ_ASSERT(promise.wait(ws) == kvs({{\"baz\", \"456\"}, {\"foo\", \"789\"}}));\n}\n\nKJ_TEST(\"ActorCache get-multiple partial retry\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto promise = expectUncached(test.get({\"foo\"_kj, \"bar\"_kj, \"baz\"_kj, \"qux\"_kj}));\n\n  mockStorage->expectCall(\"getMultiple\", ws)\n      .withParams(CAPNP(keys = [ \"bar\", \"baz\", \"foo\", \"qux\" ]), \"stream\"_kj)\n      .useCallback(\"stream\", [&](MockClient stream) {\n    stream.call(\"values\", CAPNP(list = [(key = \"baz\", value = \"456\")])).expectReturns(CAPNP(), ws);\n  }).thenThrow(KJ_EXCEPTION(DISCONNECTED, \"read failed\"));\n\n  mockStorage\n      ->expectCall(\"getMultiple\", ws)\n      // Since \"baz\" was received, the caller knows that it only has to retry keys after that.\n      .withParams(CAPNP(keys = [ \"foo\", \"qux\" ]), \"stream\"_kj)\n      .useCallback(\"stream\", [&](MockClient stream) {\n    stream.call(\"values\", CAPNP(list = [(key = \"qux\", value = \"789\")])).expectReturns(CAPNP(), ws);\n    stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n  }).expectCanceled();\n\n  KJ_ASSERT(promise.wait(ws) == kvs({{\"baz\", \"456\"}, {\"qux\", \"789\"}}));\n}\n\n// =======================================================================================\n// OK... time for hard mode. Let's test list().\n\nKJ_TEST(\"ActorCache list()\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"bar\", value = \"456\"), (key = \"baz\", value = \"789\"),\n                          (key = \"foo\", value = \"123\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"123\"}}));\n  }\n\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"bar\"))) == \"456\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"baz\"))) == \"789\");\n\n  // Stuff in range that wasn't reported is cached as absent.\n  KJ_ASSERT(expectCached(test.get(\"bara\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"corge\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"quw\")) == nullptr);\n\n  // Listing the same range again is fully cached.\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\")) ==\n      kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"123\"}}));\n\n  // Limits can be applied to the cached results.\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\", 0u)) == kvs({}));\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\", 1)) == kvs({{\"bar\", \"456\"}}));\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\", 2)) == kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}}));\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\", 3)) ==\n      kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"123\"}}));\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\", 4)) ==\n      kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"123\"}}));\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\", 1000)) ==\n      kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"123\"}}));\n\n  // The endpoint of the list is not cached.\n  {\n    auto promise = expectUncached(test.get(\"qux\"));\n\n    mockStorage->expectCall(\"get\", ws)\n        .withParams(CAPNP(key = \"qux\"))\n        .thenReturn(CAPNP(value = \"555\"));\n\n    auto result = KJ_ASSERT_NONNULL(promise.wait(ws));\n    KJ_EXPECT(result == \"555\");\n  }\n}\n\nKJ_TEST(\"ActorCache list() all\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.list(nullptr, nullptr));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"bar\", value = \"456\"), (key = \"baz\", value = \"789\"),\n                          (key = \"foo\", value = \"123\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"123\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.get(\"\")) == nullptr);\n  KJ_ASSERT(expectCached(test.list(nullptr, nullptr)) ==\n      kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"123\"}}));\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\")) ==\n      kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"123\"}}));\n  KJ_ASSERT(expectCached(test.list(nullptr, nullptr)) ==\n      kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"123\"}}));\n  KJ_ASSERT(expectCached(test.list(\"baz\", nullptr)) == kvs({{\"baz\", \"789\"}, {\"foo\", \"123\"}}));\n  KJ_ASSERT(expectCached(test.list(nullptr, \"foo\")) == kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}}));\n}\n\nKJ_TEST(\"ActorCache list() with limit\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"qux\", 3));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\", limit = 3), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"bar\", value = \"456\"), (key = \"baz\", value = \"789\"),\n                          (key = \"foo\", value = \"123\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"123\"}}));\n  }\n\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"bar\"))) == \"456\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"baz\"))) == \"789\");\n\n  // Stuff in range that wasn't reported is cached as absent -- but not past the last reported\n  // value, which was \"foo\".\n  KJ_ASSERT(expectCached(test.get(\"bara\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"corge\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"fon\")) == nullptr);\n\n  // Stuff after the last key is not in cache.\n  (void)expectUncached(test.get(\"fooa\"));\n\n  // Listing the same range again, with the same limit or lower, is fully cached.\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\", 3)) ==\n      kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"123\"}}));\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\", 2)) == kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}}));\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\", 1)) == kvs({{\"bar\", \"456\"}}));\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\", 0u)) == kvs({}));\n\n  // But a larger limit won't be cached.\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"qux\", 4));\n\n    // The new list will start at \"foo\\0\" with a limit of 1, so that it won't redundantly list foo\n    // itself and will only get the one remaining key that it needs.\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"foo\\0\", end = \"qux\", limit = 1), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"garply\", value = \"54321\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) ==\n        kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"123\"}, {\"garply\", \"54321\"}}));\n  }\n\n  // Cached if we try it again though.\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\", 4)) ==\n      kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"123\"}, {\"garply\", \"54321\"}}));\n}\n\nKJ_TEST(\"ActorCache list() with limit around negative entries\") {\n  // This checks for a bug where the initial scan through cache for list() applies the limit to\n  // the total number of entries seen (positive or negative), when it really needs to apply only\n  // to positive entries.\n\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Set up a bunch of negative entries and a positive one after them.\n  test.delete_({\"bar1\"_kj, \"bar2\"_kj, \"bar3\"_kj, \"bar4\"_kj});\n  test.put(\"baz\", \"789\");\n\n  // Now do a list through them. It should see the positive entry in cache.\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"qux\", 3));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\", limit = 7), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"bar\", value = \"456\"), (key = \"bar1\", value = \"xxx\"),\n                          (key = \"bar3\", value = \"yyy\"), (key = \"foo\", value = \"123\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"123\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\", 4)) ==\n      kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"123\"}}));\n\n  // Acknowledge the transaction.\n  {\n    auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n    mockTxn->expectCall(\"delete\", ws).thenReturn(CAPNP());\n    mockTxn->expectCall(\"put\", ws).thenReturn(CAPNP());\n    mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n    mockTxn->expectDropped(ws);\n  }\n}\n\nKJ_TEST(\"ActorCache list() start point is not present\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"baz\", value = \"789\"), (key = \"foo\", value = \"123\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"baz\", \"789\"}, {\"foo\", \"123\"}}));\n  }\n\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n\n  KJ_ASSERT(expectCached(test.get(\"bar\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"bara\")) == nullptr);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"baz\"))) == \"789\");\n  KJ_ASSERT(expectCached(test.get(\"baza\")) == nullptr);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n  KJ_ASSERT(expectCached(test.get(\"fooa\")) == nullptr);\n}\n\nKJ_TEST(\"ActorCache list() multiple ranges\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.list(\"a\", \"c\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"a\", end = \"c\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [ (key = \"a\", value = \"1\"), (key = \"b\", value = \"2\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"a\", \"1\"}, {\"b\", \"2\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.list(\"a\", \"c\")) == kvs({{\"a\", \"1\"}, {\"b\", \"2\"}}));\n\n  {\n    auto promise = expectUncached(test.list(\"x\", \"z\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"x\", end = \"z\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"y\", value = \"9\")])).expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"y\", \"9\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.list(\"a\", \"c\")) == kvs({{\"a\", \"1\"}, {\"b\", \"2\"}}));\n  KJ_ASSERT(expectCached(test.list(\"x\", \"z\")) == kvs({{\"y\", \"9\"}}));\n\n  (void)expectUncached(test.get(\"w\"));\n  (void)expectUncached(test.get(\"d\"));\n  (void)expectUncached(test.get(\"c\"));\n}\n\nKJ_TEST(\"ActorCache list() with some already-cached keys in range\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Initialize cache with some clean entries, both positive and negative.\n  {\n    auto promise1 = expectUncached(test.get(\"bbb\"));\n    auto promise2 = expectUncached(test.get(\"ccc\"));\n\n    mockStorage->expectCall(\"get\", ws).withParams(CAPNP(key = \"bbb\")).thenReturn(CAPNP());\n    mockStorage->expectCall(\"get\", ws)\n        .withParams(CAPNP(key = \"ccc\"))\n        .thenReturn(CAPNP(value = \"cval\"));\n\n    KJ_ASSERT(promise1.wait(ws) == nullptr);\n    KJ_ASSERT(KJ_ASSERT_NONNULL(promise2.wait(ws)) == \"cval\");\n  }\n\n  // Also some newly-written entries, positive and negative.\n  test.put(\"ddd\", \"dval\");\n  auto deletePromise = expectUncached(test.delete_(\"eee\"));\n\n  // Now list the range. Explicitly produce results that contradict the recent writes.\n  {\n    auto promise = expectUncached(test.list(\"aaa\", \"fff\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"aaa\", end = \"fff\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"ccc\", value = \"cval\"), (key = \"eee\", value = \"eval\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"ccc\", \"cval\"}, {\"ddd\", \"dval\"}}));\n  }\n\n  {\n    auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [\"eee\"]))\n        .thenReturn(CAPNP(numDeleted = 1));\n    mockTxn->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"ddd\", value = \"dval\")]))\n        .thenReturn(CAPNP());\n    mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n    mockTxn->expectDropped(ws);\n  }\n\n  KJ_ASSERT(deletePromise.wait(ws) == 1);\n}\n\nKJ_TEST(\"ActorCache list() with seemingly-redundant dirty entries\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Write some stuff.\n  auto deletePromise = expectUncached(test.delete_(\"bbb\"));\n  test.put(\"ccc\", \"cval\");\n\n  // Initiate a list operation, but don't complete it yet.\n  auto listPromise = expectUncached(test.list(\"aaa\", \"fff\"));\n  auto listCall = mockStorage->expectCall(\"list\", ws)\n                      .withParams(CAPNP(start = \"aaa\", end = \"fff\"), \"stream\"_kj);\n\n  // The delete won't do any work until the list completes.\n  mockStorage->expectNoActivity(ws);\n\n  // Now write some contradictory values.\n  test.put(\"bbb\", \"bval\");\n  KJ_ASSERT(expectCached(test.delete_(\"ccc\")) == 1);\n\n  // Now let the list complete in a way that matches what was just written.\n  kj::mv(listCall)\n      .useCallback(\"stream\", [&](MockClient stream) {\n    stream.call(\"values\", CAPNP(list = [(key = \"bbb\", value = \"bval\")])).expectReturns(CAPNP(), ws);\n    stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n  }).expectCanceled();\n\n  // The list produces results consistent with when it started.\n  KJ_ASSERT(listPromise.wait(ws) == kvs({{\"ccc\", \"cval\"}}));\n\n  // But the later writes are still there in cache.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"bbb\"))) == \"bval\");\n  KJ_ASSERT(expectCached(test.get(\"ccc\")) == nullptr);\n\n  // Now the transaction runs, notably containing only the original writes, not the later writes,\n  // despite our flush being delayed by the reads.\n  auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n  mockTxn->expectCall(\"delete\", ws)\n      .withParams(CAPNP(keys = [\"bbb\"]))\n      .thenReturn(CAPNP(numDeleted = 1));\n  mockTxn->expectCall(\"put\", ws)\n      .withParams(CAPNP(entries = [(key = \"ccc\", value = \"cval\")]))\n      .thenReturn(CAPNP());\n  mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n  mockTxn->expectDropped(ws);\n  KJ_ASSERT(deletePromise.wait(ws) == 1);\n\n  // And then there's a new transaction to write things back to the original values.\n  // This is NOT REDUNDANT, even though the list results seemed to match the current cached values!\n  // (I wrote this test to prove to myself that a DIRTY entry can't be marked CLEAN just because a\n  // read result from disk came back with the same value.)\n  mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n  mockTxn->expectCall(\"delete\", ws)\n      .withParams(CAPNP(keys = [\"ccc\"]))\n      .thenReturn(CAPNP(numDeleted = 1));\n  mockTxn->expectCall(\"put\", ws)\n      .withParams(CAPNP(entries = [(key = \"bbb\", value = \"bval\")]))\n      .thenReturn(CAPNP());\n  mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n  mockTxn->expectDropped(ws);\n\n  // For good measure, verify list result can be served from cache.\n  KJ_ASSERT(expectCached(test.list(\"aaa\", \"fff\")) == kvs({{\"bbb\", \"bval\"}}));\n}\n\nKJ_TEST(\"ActorCache list() starting from known value\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    test.put(\"bar\", \"123\");\n\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"bar\", value = \"123\")]))\n        .thenReturn(CAPNP());\n  }\n\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\\0\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"baz\", value = \"456\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"bar\", \"123\"}, {\"baz\", \"456\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\")) == kvs({{\"bar\", \"123\"}, {\"baz\", \"456\"}}));\n}\n\nKJ_TEST(\"ActorCache list() starting from unknown value\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    test.put(\"baz\", \"456\");\n\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"baz\", value = \"456\")]))\n        .thenReturn(CAPNP());\n  }\n\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"baz\", value = \"456\"), (key = \"foo\", value = \"123\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"baz\", \"456\"}, {\"foo\", \"123\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\")) == kvs({{\"baz\", \"456\"}, {\"foo\", \"123\"}}));\n}\n\nKJ_TEST(\"ActorCache list() consecutively, absent midpoint\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"corge\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"corge\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"baz\", value = \"456\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"baz\", \"456\"}}));\n  }\n\n  {\n    auto promise = expectUncached(test.list(\"corge\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"corge\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"foo\", value = \"123\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"123\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\")) == kvs({{\"baz\", \"456\"}, {\"foo\", \"123\"}}));\n}\n\nKJ_TEST(\"ActorCache list() consecutively reverse, absent midpoint\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.list(\"corge\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"corge\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"foo\", value = \"123\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"123\"}}));\n  }\n\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"corge\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"corge\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"baz\", value = \"456\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"baz\", \"456\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\")) == kvs({{\"baz\", \"456\"}, {\"foo\", \"123\"}}));\n}\n\nKJ_TEST(\"ActorCache list() consecutively, present midpoint\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"corge\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"corge\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"baz\", value = \"456\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"baz\", \"456\"}}));\n  }\n\n  {\n    auto promise = expectUncached(test.list(\"corge\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"corge\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"corge\", value = \"789\"), (key = \"foo\", value = \"123\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"corge\", \"789\"}, {\"foo\", \"123\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\")) ==\n      kvs({{\"baz\", \"456\"}, {\"corge\", \"789\"}, {\"foo\", \"123\"}}));\n}\n\nKJ_TEST(\"ActorCache list() consecutively reverse, present midpoint\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.list(\"corge\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"corge\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"corge\", value = \"789\"), (key = \"foo\", value = \"123\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"corge\", \"789\"}, {\"foo\", \"123\"}}));\n  }\n\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"corge\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"corge\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"baz\", value = \"456\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"baz\", \"456\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\")) ==\n      kvs({{\"baz\", \"456\"}, {\"corge\", \"789\"}, {\"foo\", \"123\"}}));\n}\n\nKJ_TEST(\"ActorCache list() starting in known-empty gap\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Create a known-empty gap between \"bar\" and \"corge\".\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"corge\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"corge\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({}));\n  }\n\n  // Now list from \"baz\" to \"qux\", which starts in the gap.\n  {\n    auto promise = expectUncached(test.list(\"baz\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"corge\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"foo\", value = \"123\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"123\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\")) == kvs({{\"foo\", \"123\"}}));\n}\n\nKJ_TEST(\"ActorCache list() ending in known-empty gap\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Create a known-empty gap between \"corge\" and \"qux\".\n  {\n    auto promise = expectUncached(test.list(\"corge\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"corge\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({}));\n  }\n\n  // Now list from \"bar\" to \"foo\", which ends in the gap.\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"foo\"));\n\n    // Note that the implementation of `list()` only looks for a prefix that it can skip, not a\n    // suffix. Hence, the underlying list() call will go all the way to \"foo\", even though the\n    // range from \"qux\" to \"foo\" is entirely in cache and hence in theory could be skipped. This\n    // optimization is missing  because the code is complex enough already and it doesn't seem like\n    // it would be a win that often.\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"foo\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"baz\", value = \"123\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"baz\", \"123\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\")) == kvs({{\"baz\", \"123\"}}));\n}\n\nKJ_TEST(\"ActorCache list() with limit and dirty puts that end up past the limit\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  test.put(\"corge\", \"123\");\n  test.put(\"grault\", \"321\");\n\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"qux\", 3));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\", limit = 3), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"bar\", value = \"456\"), (key = \"baz\", value = \"654\"),\n                          (key = \"foo\", value = \"789\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"bar\", \"456\"}, {\"baz\", \"654\"}, {\"corge\", \"123\"}}));\n  }\n\n  // Although we only requested 3 results above, we actually listed through \"foo\" at least, so\n  // now we can list 4 results and they'll all come from cache.\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\", 4)) ==\n      kvs({{\"bar\", \"456\"}, {\"baz\", \"654\"}, {\"corge\", \"123\"}, {\"foo\", \"789\"}}));\n\n  // Acknowledge the transaction.\n  mockStorage->expectCall(\"put\", ws).thenReturn(CAPNP());\n}\n\nKJ_TEST(\"ActorCache list() overwrite endpoint\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.list(\"corge\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"corge\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"corge\", value = \"789\"), (key = \"foo\", value = \"123\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"corge\", \"789\"}, {\"foo\", \"123\"}}));\n  }\n\n  test.put(\"qux\", \"456\");\n\n  KJ_ASSERT(expectCached(test.list(\"corge\", \"xyzzy\", 3)) ==\n      kvs({{\"corge\", \"789\"}, {\"foo\", \"123\"}, {\"qux\", \"456\"}}));\n\n  // Acknowledge the transaction.\n  mockStorage->expectCall(\"put\", ws).thenReturn(CAPNP());\n}\n\nKJ_TEST(\"ActorCache list() delete endpoint\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.list(\"corge\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"corge\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"corge\", value = \"789\"), (key = \"foo\", value = \"123\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"corge\", \"789\"}, {\"foo\", \"123\"}}));\n  }\n\n  auto deletePromise = expectUncached(test.delete_(\"qux\"));\n\n  // Acknowledge the delete transaction.\n  {\n    mockStorage->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [\"qux\"]))\n        .thenReturn(CAPNP(numDeleted = 1));\n  }\n\n  KJ_ASSERT(deletePromise.wait(ws) == 1);\n\n  // Do another list() through the deleted entry to make sure it didn't cause confusion. We apply\n  // a limit to this list to check for a bug where negative entries in the fully-cached prefix\n  // were incorrectly counted against the limit; only positive entries should be.\n  {\n    auto promise = expectUncached(test.list(\"corge\", \"xyzzy\", 4));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"qux\\0\", end = \"xyzzy\", limit = 2), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"waldo\", value = \"555\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"corge\", \"789\"}, {\"foo\", \"123\"}, {\"waldo\", \"555\"}}));\n  }\n}\n\nKJ_TEST(\"ActorCache list() delete endpoint empty range\") {\n  // Same as last test except the listed range is totally empty.\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.list(\"corge\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"corge\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [])).expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({}));\n  }\n\n  auto deletePromise = expectUncached(test.delete_(\"qux\"));\n\n  // Acknowledge the delete transaction.\n  {\n    mockStorage->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [\"qux\"]))\n        .thenReturn(CAPNP(numDeleted = 1));\n  }\n\n  KJ_ASSERT(deletePromise.wait(ws) == 1);\n\n  // Do another list() through the deleted entry to make sure it didn't cause confusion. We apply\n  // a limit to this list to check for a bug where negative entries in the fully-cached prefix\n  // were incorrectly counted against the limit; only positive entries should be.\n  {\n    auto promise = expectUncached(test.list(\"corge\", \"xyzzy\", 4));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"qux\\0\", end = \"xyzzy\", limit = 4), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"qux\", value = \"555\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({}));\n  }\n}\n\nKJ_TEST(\"ActorCache list() interleave streaming with other ops\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto promise = expectUncached(test.list(\"bar\", \"qux\"));\n\n  mockStorage->expectCall(\"list\", ws)\n      .withParams(CAPNP(start = \"bar\", end = \"qux\"), \"stream\"_kj)\n      .useCallback(\"stream\", [&](MockClient stream) {\n    stream\n        .call(\"values\",\n            CAPNP(list = [ (key = \"bar\", value = \"123\"), (key = \"corge\", value = \"456\") ]))\n        .expectReturns(CAPNP(), ws);\n\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"bar\"))) == \"123\");\n    KJ_ASSERT(expectCached(test.get(\"baz\")) == nullptr);\n    auto promise2 = expectUncached(test.get(\"grault\"));\n    mockStorage->expectCall(\"get\", ws).withParams(CAPNP(key = \"grault\")).thenReturn(CAPNP());\n    KJ_ASSERT(promise2.wait(ws) == nullptr);\n\n    test.put(\"foo\", \"987\");\n\n    stream\n        .call(\"values\",\n            CAPNP(list = [ (key = \"foo\", value = \"789\"), (key = \"garply\", value = \"555\") ]))\n        .expectReturns(CAPNP(), ws);\n\n    KJ_ASSERT(expectCached(test.delete_(\"garply\")) == 1);\n\n    stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n  }).expectCanceled();\n\n  KJ_ASSERT(promise.wait(ws) ==\n      kvs({{\"bar\", \"123\"}, {\"corge\", \"456\"}, {\"foo\", \"789\"}, {\"garply\", \"555\"}}));\n\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\")) ==\n      kvs({{\"bar\", \"123\"}, {\"corge\", \"456\"}, {\"foo\", \"987\"}}));\n\n  // There will be two flushes waiting since the put of \"foo\" will have started before the\n  // delete of \"garply\"\n  {\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"foo\", value = \"987\")]))\n        .thenReturn(CAPNP());\n  }\n  {\n    mockStorage->expectCall(\"delete\", ws).withParams(CAPNP(keys = [\"garply\"])).thenReturn(CAPNP());\n  }\n}\n\nKJ_TEST(\"ActorCache list() end of first block deleted at inopportune time\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Do a delete, wait for the commit... and then hold it open.\n  auto deletePromise = expectUncached(test.delete_(\"corge\"));\n\n  auto mockDelete = mockStorage->expectCall(\"delete\", ws).withParams(CAPNP(keys = [\"corge\"]));\n\n  // Now do a list.\n  auto promise = expectUncached(test.list(\"bar\", \"qux\"));\n\n  mockStorage->expectCall(\"list\", ws)\n      .withParams(CAPNP(start = \"bar\", end = \"qux\"), \"stream\"_kj)\n      .useCallback(\"stream\", [&](MockClient stream) {\n    // First block ends at the deleted entry.\n    stream\n        .call(\"values\",\n            CAPNP(list = [ (key = \"bar\", value = \"123\"), (key = \"corge\", value = \"456\") ]))\n        .expectReturns(CAPNP(), ws);\n\n    // Let the delete finish. So now the last key in the first block is cached as a negative\n    // clean entry.\n    kj::mv(mockDelete).thenReturn(CAPNP());\n\n    // Continue on.\n    stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n  }).expectCanceled();\n\n  KJ_ASSERT(promise.wait(ws) == kvs({{\"bar\", \"123\"}}));\n\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\")) == kvs({{\"bar\", \"123\"}}));\n}\n\nKJ_TEST(\"ActorCache list() retry on failure\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"qux\", 4));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\", limit = 4), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"bar\", value = \"456\"), (key = \"baz\", value = \"789\") ]))\n          .expectReturns(CAPNP(), ws);\n    }).thenThrow(KJ_EXCEPTION(DISCONNECTED, \"oops\"));\n\n    // Retry starts from `baz`.\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"baz\\0\", end = \"qux\", limit = 2), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      // Duplicates of earlier keys will be ignored.\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"bar\", value = \"IGNORE\"), (key = \"baz\", value = \"IGNORE\"),\n                          (key = \"foo\", value = \"123\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"123\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\")) ==\n      kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"123\"}}));\n}\n\nKJ_TEST(\"ActorCache get() of endpoint of previous list() returning negative is cached correctly\") {\n  // This tests for a bug that once existed in ActorCache::addReadResultToCache() where we compared\n  // against a moved-away value.\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"qux\", 4));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\", limit = 4), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({}));\n  }\n\n  {\n    auto promise = expectUncached(test.get(\"qux\"));\n    mockStorage->expectCall(\"get\", ws).withParams(CAPNP(key = \"qux\")).thenReturn(CAPNP());\n    KJ_ASSERT(promise.wait(ws) == nullptr);\n  }\n\n  KJ_ASSERT(expectCached(test.get(\"qux\")) == nullptr);\n}\n\n// =======================================================================================\n// And now... listReverse()... needs all its own tests...\n\nKJ_TEST(\"ActorCache listReverse()\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.listReverse(\"bar\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"foo\", value = \"123\"), (key = \"baz\", value = \"789\"),\n                          (key = \"bar\", value = \"456\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}, {\"bar\", \"456\"}}));\n  }\n\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"bar\"))) == \"456\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"baz\"))) == \"789\");\n\n  // Stuff in range that wasn't reported is cached as absent.\n  KJ_ASSERT(expectCached(test.get(\"bara\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"corge\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"quw\")) == nullptr);\n\n  // Listing the same range again is fully cached.\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\")) ==\n      kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}, {\"bar\", \"456\"}}));\n\n  // Limits can be applied to the cached results.\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\", 0u)) == kvs({}));\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\", 1)) == kvs({{\"foo\", \"123\"}}));\n  KJ_ASSERT(\n      expectCached(test.listReverse(\"bar\", \"qux\", 2)) == kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}}));\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\", 3)) ==\n      kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}, {\"bar\", \"456\"}}));\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\", 4)) ==\n      kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}, {\"bar\", \"456\"}}));\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\", 1000)) ==\n      kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}, {\"bar\", \"456\"}}));\n\n  // The endpoint of the list is not cached.\n  {\n    auto promise = expectUncached(test.get(\"qux\"));\n\n    mockStorage->expectCall(\"get\", ws)\n        .withParams(CAPNP(key = \"qux\"))\n        .thenReturn(CAPNP(value = \"555\"));\n\n    auto result = KJ_ASSERT_NONNULL(promise.wait(ws));\n    KJ_EXPECT(result == \"555\");\n  }\n}\n\nKJ_TEST(\"ActorCache listReverse() all\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.listReverse(nullptr, nullptr));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"foo\", value = \"123\"), (key = \"baz\", value = \"789\"),\n                          (key = \"bar\", value = \"456\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}, {\"bar\", \"456\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.get(\"\")) == nullptr);\n  KJ_ASSERT(expectCached(test.list(nullptr, nullptr)) ==\n      kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"123\"}}));\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\")) ==\n      kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}, {\"bar\", \"456\"}}));\n  KJ_ASSERT(expectCached(test.listReverse(nullptr, nullptr)) ==\n      kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}, {\"bar\", \"456\"}}));\n  KJ_ASSERT(\n      expectCached(test.listReverse(\"baz\", nullptr)) == kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}}));\n  KJ_ASSERT(\n      expectCached(test.listReverse(nullptr, \"foo\")) == kvs({{\"baz\", \"789\"}, {\"bar\", \"456\"}}));\n}\n\nKJ_TEST(\"ActorCache listReverse() with limit\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.listReverse(\"abc\", \"qux\", 3));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"abc\", end = \"qux\", limit = 3, reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"foo\", value = \"123\"), (key = \"baz\", value = \"789\"),\n                          (key = \"bar\", value = \"456\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}, {\"bar\", \"456\"}}));\n  }\n\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"bar\"))) == \"456\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"baz\"))) == \"789\");\n\n  // Stuff in range that wasn't reported is cached as absent -- but not past the last reported\n  // value, which was \"foo\".\n  KJ_ASSERT(expectCached(test.get(\"bara\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"corge\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"fon\")) == nullptr);\n\n  // Stuff before the first key is not in cache.\n  (void)expectUncached(test.get(\"baq\"));\n\n  // Listing the same range again, with the same limit or lower, is fully cached.\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\", 3)) ==\n      kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}, {\"bar\", \"456\"}}));\n  KJ_ASSERT(\n      expectCached(test.listReverse(\"bar\", \"qux\", 2)) == kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}}));\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\", 1)) == kvs({{\"foo\", \"123\"}}));\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\", 0u)) == kvs({}));\n\n  // But a larger limit won't be cached.\n  {\n    auto promise = expectUncached(test.listReverse(\"abc\", \"qux\", 4));\n\n    // The new list will end at \"bar\" with a limit of 1, so that it will only get the one remaining\n    // key that it needs.\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"abc\", end = \"bar\", limit = 1, reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"baa\", value = \"xyz\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(\n        promise.wait(ws) == kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}, {\"bar\", \"456\"}, {\"baa\", \"xyz\"}}));\n  }\n\n  // Cached if we try it again though.\n  KJ_ASSERT(expectCached(test.listReverse(\"abc\", \"qux\", 4)) ==\n      kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}, {\"bar\", \"456\"}, {\"baa\", \"xyz\"}}));\n}\n\nKJ_TEST(\"ActorCache listReverse() with limit around negative entries\") {\n  // This checks for a bug where the initial scan through cache for list() applies the limit to\n  // the total number of entries seen (positive or negative), when it really needs to apply only\n  // to positive entries.\n\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Set up a bunch of negative entries and a positive one after them.\n  test.delete_({\"bar1\"_kj, \"bar2\"_kj, \"bar3\"_kj, \"bar4\"_kj});\n  test.put(\"bar\", \"456\");\n\n  // Now do a list through them. It should see the positive entry in cache.\n  {\n    auto promise = expectUncached(test.listReverse(\"bar\", \"qux\", 3));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\", limit = 7, reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"foo\", value = \"123\"), (key = \"baz\", value = \"789\"),\n                          (key = \"bar3\", value = \"yyy\"), (key = \"bar1\", value = \"xxx\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}, {\"bar\", \"456\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\", 3)) ==\n      kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}, {\"bar\", \"456\"}}));\n\n  // Acknowledge the transaction.\n  {\n    auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n    mockTxn->expectCall(\"delete\", ws).thenReturn(CAPNP());\n    mockTxn->expectCall(\"put\", ws).thenReturn(CAPNP());\n    mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n    mockTxn->expectDropped(ws);\n  }\n}\n\nKJ_TEST(\"ActorCache listReverse() start point is not present\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.listReverse(\"bar\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"foo\", value = \"123\"), (key = \"baz\", value = \"789\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}}));\n  }\n\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n\n  KJ_ASSERT(expectCached(test.get(\"bar\")) == nullptr);\n  KJ_ASSERT(expectCached(test.get(\"bara\")) == nullptr);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"baz\"))) == \"789\");\n  KJ_ASSERT(expectCached(test.get(\"baza\")) == nullptr);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n  KJ_ASSERT(expectCached(test.get(\"fooa\")) == nullptr);\n}\n\nKJ_TEST(\"ActorCache listReverse() multiple ranges\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.listReverse(\"a\", \"c\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"a\", end = \"c\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [ (key = \"b\", value = \"2\"), (key = \"a\", value = \"1\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"b\", \"2\"}, {\"a\", \"1\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.listReverse(\"a\", \"c\")) == kvs({{\"b\", \"2\"}, {\"a\", \"1\"}}));\n\n  {\n    auto promise = expectUncached(test.listReverse(\"x\", \"z\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"x\", end = \"z\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"y\", value = \"9\")])).expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"y\", \"9\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.listReverse(\"a\", \"c\")) == kvs({{\"b\", \"2\"}, {\"a\", \"1\"}}));\n  KJ_ASSERT(expectCached(test.listReverse(\"x\", \"z\")) == kvs({{\"y\", \"9\"}}));\n\n  (void)expectUncached(test.get(\"w\"));\n  (void)expectUncached(test.get(\"d\"));\n  (void)expectUncached(test.get(\"c\"));\n}\n\nKJ_TEST(\"ActorCache listReverse() with some already-cached keys in range\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Initialize cache with some clean entries, both positive and negative.\n  {\n    auto promise1 = expectUncached(test.get(\"bbb\"));\n    auto promise2 = expectUncached(test.get(\"ccc\"));\n\n    mockStorage->expectCall(\"get\", ws).withParams(CAPNP(key = \"bbb\")).thenReturn(CAPNP());\n    mockStorage->expectCall(\"get\", ws)\n        .withParams(CAPNP(key = \"ccc\"))\n        .thenReturn(CAPNP(value = \"cval\"));\n\n    KJ_ASSERT(promise1.wait(ws) == nullptr);\n    KJ_ASSERT(KJ_ASSERT_NONNULL(promise2.wait(ws)) == \"cval\");\n  }\n\n  // Also some newly-written entries, positive and negative.\n  test.put(\"ddd\", \"dval\");\n  auto deletePromise = expectUncached(test.delete_(\"eee\"));\n\n  // Now list the range. Explicitly produce results that contradict the recent writes.\n  {\n    auto promise = expectUncached(test.listReverse(\"aaa\", \"fff\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"aaa\", end = \"fff\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"eee\", value = \"eval\"), (key = \"ccc\", value = \"cval\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"ddd\", \"dval\"}, {\"ccc\", \"cval\"}}));\n  }\n\n  {\n    auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [\"eee\"]))\n        .thenReturn(CAPNP(numDeleted = 1));\n    mockTxn->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"ddd\", value = \"dval\")]))\n        .thenReturn(CAPNP());\n    mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n    mockTxn->expectDropped(ws);\n  }\n\n  KJ_ASSERT(deletePromise.wait(ws) == 1);\n}\n\nKJ_TEST(\"ActorCache listReverse() with seemingly-redundant dirty entries\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Write some stuff.\n  auto deletePromise = expectUncached(test.delete_(\"bbb\"));\n  test.put(\"ccc\", \"cval\");\n\n  // Initiate a list operation, but don't complete it yet.\n  auto listPromise = expectUncached(test.listReverse(\"aaa\", \"fff\"));\n  auto listCall = mockStorage->expectCall(\"list\", ws)\n                      .withParams(CAPNP(start = \"aaa\", end = \"fff\", reverse = true), \"stream\"_kj);\n\n  // We won't do any work while the list is outstanding.\n  mockStorage->expectNoActivity(ws);\n\n  // Now write some contradictory values.\n  test.put(\"bbb\", \"bval\");\n  KJ_ASSERT(expectCached(test.delete_(\"ccc\")) == 1);\n\n  // Now let the list complete in a way that matches what was just written.\n  kj::mv(listCall)\n      .useCallback(\"stream\", [&](MockClient stream) {\n    stream.call(\"values\", CAPNP(list = [(key = \"bbb\", value = \"bval\")])).expectReturns(CAPNP(), ws);\n    stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n  }).expectCanceled();\n\n  // The list produces results consistent with when it started.\n  KJ_ASSERT(listPromise.wait(ws) == kvs({{\"ccc\", \"cval\"}}));\n\n  // But the later writes are still there in cache.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"bbb\"))) == \"bval\");\n  KJ_ASSERT(expectCached(test.get(\"ccc\")) == nullptr);\n\n  // The transaction completes now.\n  auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n  mockTxn->expectCall(\"delete\", ws)\n      .withParams(CAPNP(keys = [\"bbb\"]))\n      .thenReturn(CAPNP(numDeleted = 1));\n  mockTxn->expectCall(\"put\", ws)\n      .withParams(CAPNP(entries = [(key = \"ccc\", value = \"cval\")]))\n      .thenReturn(CAPNP());\n  mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n  mockTxn->expectDropped(ws);\n  KJ_ASSERT(deletePromise.wait(ws) == 1);\n\n  // And then there's a new transaction to write things back to the original values.\n  // This is NOT REDUNDANT, even though the list results seemed to match the current cached values!\n  // (I wrote this test to prove to myself that a DIRTY entry can't be marked CLEAN just because a\n  // read result from disk came back with the same value.)\n  mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n  mockTxn->expectCall(\"delete\", ws)\n      .withParams(CAPNP(keys = [\"ccc\"]))\n      .thenReturn(CAPNP(numDeleted = 1));\n  mockTxn->expectCall(\"put\", ws)\n      .withParams(CAPNP(entries = [(key = \"bbb\", value = \"bval\")]))\n      .thenReturn(CAPNP());\n  mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n  mockTxn->expectDropped(ws);\n\n  // For good measure, verify list result can be served from cache.\n  KJ_ASSERT(expectCached(test.listReverse(\"aaa\", \"fff\")) == kvs({{\"bbb\", \"bval\"}}));\n}\n\nKJ_TEST(\"ActorCache listReverse() starting from known value\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    test.put(\"bar\", \"123\");\n\n    mockStorage->expectCall(\"put\", ws).thenReturn(CAPNP());\n  }\n\n  {\n    auto promise = expectUncached(test.listReverse(\"bar\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"baz\", value = \"456\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"baz\", \"456\"}, {\"bar\", \"123\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\")) == kvs({{\"baz\", \"456\"}, {\"bar\", \"123\"}}));\n}\n\nKJ_TEST(\"ActorCache listReverse() starting from unknown value\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    test.put(\"baz\", \"456\");\n\n    mockStorage->expectCall(\"put\", ws).thenReturn(CAPNP());\n  }\n\n  {\n    auto promise = expectUncached(test.listReverse(\"bar\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"foo\", value = \"123\"), (key = \"baz\", value = \"456\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"123\"}, {\"baz\", \"456\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\")) == kvs({{\"foo\", \"123\"}, {\"baz\", \"456\"}}));\n}\n\nKJ_TEST(\"ActorCache listReverse() consecutively, absent midpoint\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.listReverse(\"bar\", \"corge\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"corge\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"baz\", value = \"456\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"baz\", \"456\"}}));\n  }\n\n  {\n    auto promise = expectUncached(test.listReverse(\"corge\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"corge\", end = \"qux\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"foo\", value = \"123\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"123\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\")) == kvs({{\"foo\", \"123\"}, {\"baz\", \"456\"}}));\n}\n\nKJ_TEST(\"ActorCache listReverse() consecutively reverse, absent midpoint\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.listReverse(\"corge\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"corge\", end = \"qux\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"foo\", value = \"123\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"123\"}}));\n  }\n\n  {\n    auto promise = expectUncached(test.listReverse(\"bar\", \"corge\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"corge\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"baz\", value = \"456\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"baz\", \"456\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\")) == kvs({{\"foo\", \"123\"}, {\"baz\", \"456\"}}));\n}\n\nKJ_TEST(\"ActorCache listReverse() consecutively, present midpoint\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.listReverse(\"bar\", \"corge\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"corge\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"baz\", value = \"456\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"baz\", \"456\"}}));\n  }\n\n  {\n    auto promise = expectUncached(test.listReverse(\"corge\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"corge\", end = \"qux\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"foo\", value = \"123\"), (key = \"corge\", value = \"789\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"123\"}, {\"corge\", \"789\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\")) ==\n      kvs({{\"foo\", \"123\"}, {\"corge\", \"789\"}, {\"baz\", \"456\"}}));\n}\n\nKJ_TEST(\"ActorCache listReverse() consecutively reverse, present midpoint\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.listReverse(\"corge\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"corge\", end = \"qux\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"foo\", value = \"123\"), (key = \"corge\", value = \"789\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"123\"}, {\"corge\", \"789\"}}));\n  }\n\n  {\n    auto promise = expectUncached(test.listReverse(\"bar\", \"corge\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"corge\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"baz\", value = \"456\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"baz\", \"456\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\")) ==\n      kvs({{\"foo\", \"123\"}, {\"corge\", \"789\"}, {\"baz\", \"456\"}}));\n}\n\nKJ_TEST(\"ActorCache listReverse() starting in known-empty gap\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Create a known-empty gap between \"bar\" and \"corge\".\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"corge\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"corge\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({}));\n  }\n\n  // Now list from \"baz\" to \"qux\", which starts in the gap.\n  {\n    auto promise = expectUncached(test.listReverse(\"baz\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"baz\", end = \"qux\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"foo\", value = \"123\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"123\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\")) == kvs({{\"foo\", \"123\"}}));\n}\n\nKJ_TEST(\"ActorCache listReverse() ending in known-empty gap\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Create a known-empty gap between \"corge\" and \"qux\".\n  {\n    auto promise = expectUncached(test.list(\"corge\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"corge\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({}));\n  }\n\n  // Now list from \"bar\" to \"foo\", which ends in the gap.\n  {\n    auto promise = expectUncached(test.listReverse(\"bar\", \"foo\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"corge\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"baz\", value = \"123\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"baz\", \"123\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\")) == kvs({{\"baz\", \"123\"}}));\n}\n\nKJ_TEST(\"ActorCache listReverse() with limit and dirty puts that end up past the limit\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  test.put(\"corge\", \"123\");\n  test.put(\"bar\", \"321\");\n\n  {\n    auto promise = expectUncached(test.listReverse(\"bar\", \"qux\", 3));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\", limit = 3, reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"grault\", value = \"456\"), (key = \"foo\", value = \"654\"),\n                          (key = \"baz\", value = \"789\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"grault\", \"456\"}, {\"foo\", \"654\"}, {\"corge\", \"123\"}}));\n  }\n\n  // Although we only requested 3 results above, we actually listed through \"baz\" at least, so\n  // now we can list 4 results and they'll all come from cache.\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\", 4)) ==\n      kvs({{\"grault\", \"456\"}, {\"foo\", \"654\"}, {\"corge\", \"123\"}, {\"baz\", \"789\"}}));\n\n  // Acknowledge the transaction.\n  mockStorage->expectCall(\"put\", ws).thenReturn(CAPNP());\n}\n\nKJ_TEST(\"ActorCache listReverse() overwrite endpoint\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.listReverse(\"corge\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"corge\", end = \"qux\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"foo\", value = \"123\"), (key = \"corge\", value = \"789\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"123\"}, {\"corge\", \"789\"}}));\n  }\n\n  test.put(\"qux\", \"456\");\n\n  KJ_ASSERT(expectCached(test.list(\"corge\", \"xyzzy\", 3)) ==\n      kvs({{\"corge\", \"789\"}, {\"foo\", \"123\"}, {\"qux\", \"456\"}}));\n\n  // Acknowledge the transaction.\n  mockStorage->expectCall(\"put\", ws).thenReturn(CAPNP());\n}\n\nKJ_TEST(\"ActorCache listReverse() delete endpoint\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.listReverse(\"corge\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"corge\", end = \"qux\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"foo\", value = \"123\"), (key = \"corge\", value = \"789\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"123\"}, {\"corge\", \"789\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.delete_(\"corge\")) == 1);\n\n  // Acknowledge the delete transaction.\n  {\n    mockStorage->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [\"corge\"]))\n        .thenReturn(CAPNP(numDeleted = 1));\n  }\n\n  {\n    auto promise = expectUncached(test.listReverse(\"bar\", \"qux\", 4));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"corge\", limit = 3, reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"baz\", value = \"555\")]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"123\"}, {\"baz\", \"555\"}}));\n  }\n}\n\nKJ_TEST(\"ActorCache listReverse() interleave streaming with other ops\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto promise = expectUncached(test.listReverse(\"baa\", \"qux\"));\n\n  mockStorage->expectCall(\"list\", ws)\n      .withParams(CAPNP(start = \"baa\", end = \"qux\", reverse = true), \"stream\"_kj)\n      .useCallback(\"stream\", [&](MockClient stream) {\n    stream\n        .call(\"values\",\n            CAPNP(list = [ (key = \"garply\", value = \"555\"), (key = \"foo\", value = \"789\") ]))\n        .expectReturns(CAPNP(), ws);\n\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"garply\"))) == \"555\");\n    KJ_ASSERT(expectCached(test.get(\"grault\")) == nullptr);\n    KJ_ASSERT(expectCached(test.get(\"gah\")) == nullptr);\n    auto promise2 = expectUncached(test.get(\"baz\"));\n    mockStorage->expectCall(\"get\", ws).withParams(CAPNP(key = \"baz\")).thenReturn(CAPNP());\n    KJ_ASSERT(promise2.wait(ws) == nullptr);\n\n    test.put(\"corge\", \"987\");\n\n    stream\n        .call(\"values\",\n            CAPNP(list = [ (key = \"corge\", value = \"456\"), (key = \"bar\", value = \"123\") ]))\n        .expectReturns(CAPNP(), ws);\n\n    KJ_ASSERT(expectCached(test.delete_(\"bar\")) == 1);\n\n    stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n  }).expectCanceled();\n\n  KJ_ASSERT(promise.wait(ws) ==\n      kvs({{\"garply\", \"555\"}, {\"foo\", \"789\"}, {\"corge\", \"456\"}, {\"bar\", \"123\"}}));\n\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\")) ==\n      kvs({{\"garply\", \"555\"}, {\"foo\", \"789\"}, {\"corge\", \"987\"}}));\n\n  // There will be two flushes waiting since the put of \"foo\" will have started before the\n  // delete of \"garply\"\n  {\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"corge\", value = \"987\")]))\n        .thenReturn(CAPNP());\n  }\n  { mockStorage->expectCall(\"delete\", ws).withParams(CAPNP(keys = [\"bar\"])).thenReturn(CAPNP()); }\n}\n\nKJ_TEST(\"ActorCache listReverse() end of first block deleted at inopportune time\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Do a delete, wait for the commit... and then hold it open.\n  auto deletePromise = expectUncached(test.delete_(\"corge\"));\n\n  auto mockDelete = mockStorage->expectCall(\"delete\", ws).withParams(CAPNP(keys = [\"corge\"]));\n\n  // Now do a list.\n  auto promise = expectUncached(test.listReverse(\"bar\", \"qux\"));\n\n  mockStorage->expectCall(\"list\", ws)\n      .withParams(CAPNP(start = \"bar\", end = \"qux\", reverse = true), \"stream\"_kj)\n      .useCallback(\"stream\", [&](MockClient stream) {\n    // First block ends at the deleted entry.\n    stream\n        .call(\"values\",\n            CAPNP(list = [ (key = \"foo\", value = \"456\"), (key = \"corge\", value = \"123\") ]))\n        .expectReturns(CAPNP(), ws);\n\n    // Let the delete finish. So now the last key in the first block is cached as a negative\n    // clean entry.\n    kj::mv(mockDelete).thenReturn(CAPNP());\n\n    // Continue on.\n    stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n  }).expectCanceled();\n\n  KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"456\"}}));\n\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\")) == kvs({{\"foo\", \"456\"}}));\n}\n\nKJ_TEST(\"ActorCache listReverse() retry on failure\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.listReverse(\"bar\", \"qux\", 4));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\", limit = 4, reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"foo\", value = \"123\"), (key = \"baz\", value = \"789\") ]))\n          .expectReturns(CAPNP(), ws);\n    }).thenThrow(KJ_EXCEPTION(DISCONNECTED, \"oops\"));\n\n    // Retry starts from `baz`.\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"baz\", limit = 2, reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      // Duplicates of earlier keys will be ignored.\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"foo\", value = \"IGNORE\"), (key = \"baz\", value = \"IGNORE\"),\n                          (key = \"bar\", value = \"456\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}, {\"bar\", \"456\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.listReverse(\"bar\", \"qux\")) ==\n      kvs({{\"foo\", \"123\"}, {\"baz\", \"789\"}, {\"bar\", \"456\"}}));\n}\n\n// =======================================================================================\n// LRU purge\n\nconstexpr size_t ENTRY_SIZE = 120;\nKJ_TEST(\"ActorCache LRU purge\") {\n  ActorCacheTest test({.softLimit = 1 * ENTRY_SIZE});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto promise = expectUncached(test.get(\"foo\"));\n  mockStorage->expectCall(\"get\", ws)\n      .withParams(CAPNP(key = \"foo\"))\n      .thenReturn(CAPNP(value = \"123\"));\n\n  KJ_ASSERT(KJ_ASSERT_NONNULL(promise.wait(ws)) == \"123\");\n\n  // Still cached.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n\n  promise = expectUncached(test.get(\"bar\"));\n  mockStorage->expectCall(\"get\", ws)\n      .withParams(CAPNP(key = \"bar\"))\n      .thenReturn(CAPNP(value = \"456\"));\n\n  KJ_ASSERT(KJ_ASSERT_NONNULL(promise.wait(ws)) == \"456\");\n\n  // Still cached.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"bar\"))) == \"456\");\n\n  // But foo was evicted.\n  (void)expectUncached(test.get(\"foo\"));\n}\n\nKJ_TEST(\"ActorCache LRU purge ordering\") {\n  ActorCacheTest test({.softLimit = 4 * ENTRY_SIZE});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  test.put(\"foo\", \"123\");\n  test.put(\"bar\", \"456\");\n  test.put(\"baz\", \"789\");\n  test.put(\"qux\", \"555\");\n\n  // Let the flush of the puts complete.\n  mockStorage->expectCall(\"put\", ws).thenReturn(CAPNP());\n\n  // Ensure the flush actually completes (marking dirty entries as clean) before continuing.\n  test.gate.wait(nullptr).wait(ws);\n\n  // Touch foo.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n\n  // Write two new values to push things out.\n  test.put(\"xxx\", \"aaa\");\n  test.put(\"yyy\", \"bbb\");\n\n  // More puts flushing.\n  mockStorage->expectCall(\"put\", ws).thenReturn(CAPNP());\n\n  // Foo and qux live, bar and baz evicted.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n  (void)expectUncached(test.get(\"bar\"));\n  (void)expectUncached(test.get(\"baz\"));\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"qux\"))) == \"555\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"xxx\"))) == \"aaa\");\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"yyy\"))) == \"bbb\");\n}\n\nKJ_TEST(\"ActorCache LRU purge larger\") {\n  ActorCacheTest test({.softLimit = 32 * ENTRY_SIZE});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto kilobyte = kj::str(kj::repeat('x', 1024));\n\n  auto promise = expectUncached(test.get(\"foo\"));\n  mockStorage->expectCall(\"get\", ws)\n      .withParams(CAPNP(key = \"foo\"))\n      .thenReturn(CAPNP(value = \"123\"));\n\n  KJ_ASSERT(KJ_ASSERT_NONNULL(promise.wait(ws)) == \"123\");\n\n  test.put(\"bar\", kilobyte);\n  test.put(\"baz\", kilobyte);\n  test.put(\"qux\", kilobyte);\n\n  // Still cached.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n\n  test.put(\"corge\", kilobyte);\n\n  // Dropped from cache, because the puts are in-flight and so cannot be dropped. This read gets\n  // sent off before the puts above because the event loop hasn't been yielded yet.\n  // TODO(cleanup): We hold onto the promise here (even though in theory it'd be fine to drop)\n  // because the capnp-mock framework doesn't handle dropped client promises well (capnp destructs\n  // the ReceivedCall before waitForEvent resolves and hands control back to expectCall, leaving\n  // receivedPromises empty in expectCall).\n  promise = expectUncached(test.get(\"foo\"));\n\n  test.put(\"grault\", kilobyte);\n  test.put(\"garply\", kilobyte);\n\n  // Everything dirty is still in cache despite exceeding cache bounds.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"bar\"))) == kilobyte);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"baz\"))) == kilobyte);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"qux\"))) == kilobyte);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"corge\"))) == kilobyte);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"grault\"))) == kilobyte);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"garply\"))) == kilobyte);\n\n  {\n    // We have to wait for the get before the flush since capnp-mock doesn't continue waiting\n    // after receiving the first call, and in this case the first call received will be the get.\n    mockStorage->expectCall(\"get\", ws)\n        .withParams(CAPNP(key = \"foo\"))\n        .thenReturn(CAPNP(value = \"123\"));\n    mockStorage->expectCall(\"put\", ws).thenReturn(CAPNP());\n    // Ensure the flush actually completes (marking dirty entries as clean) before continuing.\n    test.gate.wait(nullptr).wait(ws);\n  }\n\n  (void)expectUncached(test.get(\"bar\"));\n  (void)expectUncached(test.get(\"baz\"));\n  (void)expectUncached(test.get(\"qux\"));\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"corge\"))) == kilobyte);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"grault\"))) == kilobyte);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"garply\"))) == kilobyte);\n}\n\nKJ_TEST(\"ActorCache LRU purge\") {\n  ActorCacheTest test({.softLimit = 1});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto promise = expectUncached(test.get({\"foo\"_kj, \"bar\"_kj, \"baz\"_kj}));\n  mockStorage->expectCall(\"getMultiple\", ws)\n      .withParams(CAPNP(keys = [ \"bar\", \"baz\", \"foo\" ]), \"stream\"_kj)\n      .useCallback(\"stream\", [&](MockClient stream) {\n    stream\n        .call(\"values\",\n            CAPNP(list =\n                      [\n                        (key = \"bar\", value = \"456\"), (key = \"baz\", value = \"789\"),\n                        (key = \"foo\", value = \"123\")\n                      ]))\n        .expectReturns(CAPNP(), ws);\n    stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n  }).expectCanceled();\n\n  KJ_ASSERT(promise.wait(ws) == kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"123\"}}));\n\n  // Nothing was cached, because nothing fit in the LRU.\n  KJ_ASSERT(test.lru.currentSize() == 0);\n  (void)expectUncached(test.get(\"foo\"));\n  (void)expectUncached(test.get(\"bar\"));\n  (void)expectUncached(test.get(\"baz\"));\n}\n\nKJ_TEST(\"ActorCache evict on timeout\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto timePoint = kj::UNIX_EPOCH;\n  KJ_ASSERT(test.cache.evictStale(timePoint) == kj::none);\n\n  auto ackFlush = [&]() {\n    mockStorage->expectCall(\"put\", ws).thenReturn(CAPNP());\n    // Ensure the flush actually completes (marking dirty entries as clean) before continuing.\n    test.gate.wait(nullptr).wait(ws);\n  };\n\n  test.put(\"foo\", \"123\");\n  test.put(\"bar\", \"456\");\n  ackFlush();\n\n  KJ_ASSERT(test.cache.evictStale(timePoint + 100 * kj::MILLISECONDS) == kj::none);\n  KJ_ASSERT(test.cache.evictStale(timePoint + 200 * kj::MILLISECONDS) == kj::none);\n  KJ_ASSERT(test.cache.evictStale(timePoint + 500 * kj::MILLISECONDS) == kj::none);\n\n  expectCached(test.get(\"foo\"));\n  expectCached(test.get(\"bar\"));\n\n  KJ_ASSERT(test.cache.evictStale(timePoint + 1000 * kj::MILLISECONDS) == nullptr);\n  // foo and bar are now stale\n\n  // add baz\n  test.put(\"baz\", \"789\");\n  ackFlush();\n\n  // don't check foo because we want it to be evicted, but touch bar\n  expectCached(test.get(\"bar\"));\n\n  KJ_ASSERT(test.cache.evictStale(timePoint + 2000 * kj::MILLISECONDS) == nullptr);\n  // Now foo should be evicted and bar and baz stale.\n\n  // Verify foo is evicted.\n  (void)expectUncached(test.get(\"foo\"));\n\n  // Touch bar.\n  expectCached(test.get(\"bar\"));\n\n  KJ_ASSERT(test.cache.evictStale(timePoint + 3000 * kj::MILLISECONDS) == nullptr);\n  // Now baz should have been evicted, but bar is still here because we keep touching it.\n\n  expectCached(test.get(\"bar\"));\n  (void)expectUncached(test.get(\"baz\"));\n}\n\nKJ_TEST(\"ActorCache backpressure due to dirtyPressureThreshold\") {\n  ActorCacheTest test({.dirtyListByteLimit = 2 * ENTRY_SIZE});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto timePoint = kj::UNIX_EPOCH;\n  KJ_ASSERT(test.cache.evictStale(timePoint) == nullptr);\n\n  KJ_ASSERT(test.put(\"foo\", \"123\") == nullptr);\n  KJ_ASSERT(test.put(\"bar\", \"456\") == nullptr);\n  auto promise1 = KJ_ASSERT_NONNULL(test.put(\"baz\", \"789\"));\n  auto promise2 = KJ_ASSERT_NONNULL(test.put(\"qux\", \"555\"));\n\n  // These deletes are actually cached, BUT backpressure will apply to make them return a promise.\n  auto promise3 = expectUncached(test.delete_(\"baz\"));\n  auto promise4 = expectUncached(test.delete_({\"qux\"_kj}));\n\n  // A delete of an unknown keys will also apply backpressure, of course.\n  auto promise5 = expectUncached(test.delete_(\"corge\"));\n  auto promise6 = expectUncached(test.delete_({\"grault\"_kj}));\n\n  // Let the write transaction complete.\n  {\n    auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n    mockTxn->expectCall(\"delete\", ws).thenReturn(CAPNP(numDeleted = 0));\n    mockTxn->expectCall(\"delete\", ws).thenReturn(CAPNP(numDeleted = 0));\n    mockTxn->expectCall(\"delete\", ws).thenReturn(CAPNP(numDeleted = 0));\n    mockTxn->expectCall(\"put\", ws).thenReturn(CAPNP());\n\n    // Test for bogus `KJ_ASSERT(flushScheduled)` in `ActorCache::getBackpressure()`.\n    auto promise7 = KJ_ASSERT_NONNULL(test.cache.evictStale(timePoint));\n\n    KJ_ASSERT(!promise1.poll(ws));\n    KJ_ASSERT(!promise2.poll(ws));\n    KJ_ASSERT(!promise3.poll(ws));\n    KJ_ASSERT(!promise4.poll(ws));\n    KJ_ASSERT(!promise5.poll(ws));\n    KJ_ASSERT(!promise6.poll(ws));\n    KJ_ASSERT(!promise7.poll(ws));\n\n    mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n\n    promise1.wait(ws);\n    promise2.wait(ws);\n    KJ_ASSERT(promise3.wait(ws));\n    KJ_ASSERT(promise4.wait(ws) == 1);\n    KJ_ASSERT(!promise5.wait(ws));\n    KJ_ASSERT(promise6.wait(ws) == 0);\n    promise7.wait(ws);\n\n    mockTxn->expectDropped(ws);\n  }\n}\n\nKJ_TEST(\"ActorCache lru evict entry with known-empty gaps\") {\n  ActorCacheTest test({.softLimit = 5 * ENTRY_SIZE});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Populate cache.\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"bar\", value = \"456\"), (key = \"baz\", value = \"789\"),\n                          (key = \"corge\", value = \"555\"), (key = \"foo\", value = \"123\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) ==\n        kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"corge\", \"555\"}, {\"foo\", \"123\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\")) ==\n      kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"corge\", \"555\"}, {\"foo\", \"123\"}}));\n\n  // touch some stuff so that \"corge\" is the oldest entry.\n  expectCached(test.list(\"foo\", \"qux\"));\n  expectCached(test.get(\"bar\"));\n  expectCached(test.get(\"baz\"));\n\n  // do a put() to force an eviction.\n  {\n    test.put(\"xyzzy\", \"x\");\n\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"xyzzy\", value = \"x\")]))\n        .thenReturn(CAPNP());\n  }\n\n  // The ranges before and after \"corge\" are missing, but everything else is still in cache.\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"baz\")) == kvs({{\"bar\", \"456\"}}));\n  KJ_ASSERT(expectCached(test.get(\"bay\")) == nullptr);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"baz\"))) == \"789\");\n  KJ_ASSERT(expectCached(test.list(\"foo\", \"qux\")) == kvs({{\"foo\", \"123\"}}));\n  KJ_ASSERT(expectCached(test.get(\"fooa\")) == nullptr);\n\n  (void)expectUncached(test.get(\"baza\"));\n  (void)expectUncached(test.get(\"corge\"));\n  (void)expectUncached(test.get(\"fo\"));\n}\n\nKJ_TEST(\"ActorCache lru evict gap entry with known-empty gaps\") {\n  ActorCacheTest test({.softLimit = 5 * ENTRY_SIZE});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Populate cache.\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"bar\", value = \"456\"), (key = \"baz\", value = \"789\"),\n                          (key = \"corge\", value = \"555\"), (key = \"foo\", value = \"123\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) ==\n        kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"corge\", \"555\"}, {\"foo\", \"123\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\")) ==\n      kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"corge\", \"555\"}, {\"foo\", \"123\"}}));\n\n  // touch some stuff so that \"qux\" is the oldest entry.\n  expectCached(test.get(\"bar\"));\n  expectCached(test.get(\"baz\"));\n  expectCached(test.get(\"corge\"));\n  expectCached(test.get(\"foo\"));\n\n  // We still have a cached gap between \"foo\" and \"qux\".\n  KJ_ASSERT(expectCached(test.get(\"foo+1\")) == nullptr);\n\n  // do a put() to force an eviction.\n  {\n    test.put(\"xyzzy\", \"x\");\n\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"xyzzy\", value = \"x\")]))\n        .thenReturn(CAPNP());\n  }\n\n  // Okay, that gap is gone now.\n  (void)expectUncached(test.get(\"foo+1\"));\n}\n\nKJ_TEST(\"ActorCache lru evict entry with trailing known-empty gap (followed by END_GAP)\") {\n  ActorCacheTest test({.softLimit = 5 * ENTRY_SIZE});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Populate cache.\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"bar\", value = \"456\"), (key = \"baz\", value = \"789\"),\n                          (key = \"corge\", value = \"555\"), (key = \"foo\", value = \"123\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) ==\n        kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"corge\", \"555\"}, {\"foo\", \"123\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\")) ==\n      kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"corge\", \"555\"}, {\"foo\", \"123\"}}));\n\n  // touch some stuff so that \"foo\" is the oldest entry.\n  expectCached(test.get(\"bar\"));\n  expectCached(test.get(\"baz\"));\n  expectCached(test.get(\"corge\"));\n\n  // do a put() to force an eviction.\n  {\n    test.put(\"xyzzy\", \"x\");\n\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"xyzzy\", value = \"x\")]))\n        .thenReturn(CAPNP());\n  }\n\n  // The range after \"foo\" is missing, but everything else is still in cache.\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"corge\")) == kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}}));\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"corge\"))) == \"555\");\n\n  (void)expectUncached(test.get(\"corgf\"));\n  (void)expectUncached(test.get(\"foo\"));\n  (void)expectUncached(test.get(\"quw\"));\n  (void)expectUncached(test.get(\"qux\"));\n  (void)expectUncached(test.get(\"quy\"));\n}\n\nKJ_TEST(\"ActorCache timeout entry with known-empty gaps\") {\n  ActorCacheTest test({.softLimit = 5 * ENTRY_SIZE});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto startTime = kj::UNIX_EPOCH;\n  test.cache.evictStale(startTime);\n\n  // Populate cache.\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"bar\", value = \"456\"), (key = \"baz\", value = \"789\"),\n                          (key = \"corge\", value = \"555\"), (key = \"foo\", value = \"123\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) ==\n        kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"corge\", \"555\"}, {\"foo\", \"123\"}}));\n  }\n\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"qux\")) ==\n      kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"corge\", \"555\"}, {\"foo\", \"123\"}}));\n\n  // Make all entries STALE.\n  test.cache.evictStale(startTime + 1 * kj::SECONDS);\n\n  // touch some stuff so that \"corge\" is the only STALE entry.\n  expectCached(test.list(\"foo\", \"qux\"));\n  expectCached(test.get(\"bar\"));\n  expectCached(test.get(\"baz\"));\n\n  // Time out \"corge\".\n  test.cache.evictStale(startTime + 2 * kj::SECONDS);\n\n  // The ranges before and after \"corge\" are missing, but everything else is still in cache.\n  KJ_ASSERT(expectCached(test.list(\"bar\", \"baz\")) == kvs({{\"bar\", \"456\"}}));\n  KJ_ASSERT(expectCached(test.get(\"bay\")) == nullptr);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"baz\"))) == \"789\");\n  KJ_ASSERT(expectCached(test.list(\"foo\", \"qux\")) == kvs({{\"foo\", \"123\"}}));\n  KJ_ASSERT(expectCached(test.get(\"fooa\")) == nullptr);\n\n  (void)expectUncached(test.get(\"baza\"));\n  (void)expectUncached(test.get(\"corge\"));\n  (void)expectUncached(test.get(\"fo\"));\n}\n\nKJ_TEST(\"ActorCache evictStale entire list with end marker\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto timePoint = kj::UNIX_EPOCH;\n  KJ_ASSERT(test.cache.evictStale(timePoint) == nullptr);\n\n  {\n    // Populate a decent list.\n    auto promise = expectUncached(test.list(\"bar\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"bar\", value = \"456\"), (key = \"baz\", value = \"789\"),\n                          (key = \"corge\", value = \"555\"), (key = \"foo\", value = \"123\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) ==\n        kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"corge\", \"555\"}, {\"foo\", \"123\"}}));\n  }\n\n  KJ_EXPECT(test.lru.currentSize() > 0);\n\n  // First mark the entire cache as stale.\n  timePoint += 1 * kj::SECONDS;\n  KJ_ASSERT(test.cache.evictStale(timePoint) == nullptr);\n  KJ_EXPECT(test.lru.currentSize() > 0);\n\n  // Evict the entire cache.\n  timePoint += 1 * kj::SECONDS;\n  KJ_ASSERT(test.cache.evictStale(timePoint) == nullptr);\n  KJ_EXPECT(test.lru.currentSize() == 0);\n}\n\nKJ_TEST(\"ActorCache purge everything while listing\") {\n  ActorCacheTest test({.softLimit = 1});  // evict everything immediately\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"bar\", value = \"456\"), (key = \"baz\", value = \"789\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"corge\", value = \"555\"), (key = \"foo\", value = \"123\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) ==\n        kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"corge\", \"555\"}, {\"foo\", \"123\"}}));\n  }\n\n  (void)expectUncached(test.get(\"bar\"));\n  (void)expectUncached(test.get(\"baz\"));\n  (void)expectUncached(test.get(\"corge\"));\n  (void)expectUncached(test.get(\"foo\"));\n}\n\nKJ_TEST(\"ActorCache purge everything while listing; has previous entry\") {\n  ActorCacheTest test({.softLimit = 1});  // evict everything immediately\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // This is the same as the previous test, except we put an entry into cache first that appears\n  // before the list range. This exercises a slightly different code path in markGapsEmpty().\n  test.put(\"a\", \"x\");\n\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"qux\"));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"bar\", value = \"456\"), (key = \"baz\", value = \"789\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"corge\", value = \"555\"), (key = \"foo\", value = \"123\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) ==\n        kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"corge\", \"555\"}, {\"foo\", \"123\"}}));\n  }\n\n  // Acknowledge the flush.\n  mockStorage->expectCall(\"put\", ws).thenReturn(CAPNP());\n}\n\nKJ_TEST(\"ActorCache exceed hard limit on read\") {\n  ActorCacheTest test(\n      {.monitorOutputGate = false, .softLimit = 2 * ENTRY_SIZE, .hardLimit = 2 * ENTRY_SIZE});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto brokenPromise = test.gate.onBroken();\n\n  {\n    // Don't use expectUncached() since it will log exceptions as test failures.\n    auto promise = test.list(\"bar\"_kj, \"qux\"_kj).get<kj::Promise<kj::Array<KeyValue>>>();\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"bar\", value = \"456\"), (key = \"baz\", value = \"789\") ]))\n          .expectReturns(CAPNP(), ws);\n      KJ_ASSERT(!brokenPromise.poll(ws));\n\n      // The next value delivered overflows the cache.\n      stream.call(\"values\", CAPNP(list = [(key = \"corge\", value = \"555\")]))\n          .expectThrows(kj::Exception::Type::OVERLOADED,\n              \"exceeded its memory limit due to overflowing the storage cache\", ws);\n\n      KJ_ASSERT(brokenPromise.poll(ws));\n\n      // The exception propagates to further calls do to capnp streaming semantics.\n      stream.call(\"values\", CAPNP(list = [(key = \"foo\", value = \"123\")]))\n          .expectThrows(kj::Exception::Type::OVERLOADED,\n              \"exceeded its memory limit due to overflowing the storage cache\", ws);\n      stream.call(\"end\", CAPNP())\n          .expectThrows(kj::Exception::Type::OVERLOADED,\n              \"exceeded its memory limit due to overflowing the storage cache\", ws);\n\n      // The call will actually have been canceled when the first call failed.\n    }).expectCanceled();\n\n    KJ_EXPECT_THROW_MESSAGE(\n        \"exceeded its memory limit due to overflowing the storage cache\", promise.wait(ws));\n  }\n\n  KJ_EXPECT_THROW_MESSAGE(\n      \"exceeded its memory limit due to overflowing the storage cache\", brokenPromise.wait(ws));\n}\n\nKJ_TEST(\"ActorCache exceed hard limit on write\") {\n  ActorCacheTest test(\n      {.monitorOutputGate = false, .softLimit = 2 * ENTRY_SIZE, .hardLimit = 2 * ENTRY_SIZE});\n  auto& ws = test.ws;\n\n  auto brokenPromise = test.gate.onBroken();\n\n  test.put(\"foo\", \"123\");\n  test.put(\"bar\", \"456\");\n  KJ_EXPECT_THROW_MESSAGE(\n      \"exceeded its memory limit due to overflowing the storage cache\", test.put(\"baz\", \"789\"));\n\n  KJ_ASSERT(brokenPromise.poll(ws));\n  KJ_EXPECT_THROW_MESSAGE(\n      \"exceeded its memory limit due to overflowing the storage cache\", brokenPromise.wait(ws));\n}\n\n// =======================================================================================\n\nKJ_TEST(\"ActorCache skip cache\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Read a value.\n  {\n    auto promise = expectUncached(test.get(\"foo\", {.noCache = true}));\n\n    mockStorage->expectCall(\"get\", ws)\n        .withParams(CAPNP(key = \"foo\"))\n        .thenReturn(CAPNP(value = \"bar\"));\n\n    auto result = KJ_ASSERT_NONNULL(promise.wait(ws));\n    KJ_EXPECT(result == \"bar\");\n  }\n\n  // Read it again -- not in cache!\n  {\n    auto promise = expectUncached(test.get(\"foo\", {.noCache = true}));\n\n    mockStorage->expectCall(\"get\", ws)\n        .withParams(CAPNP(key = \"foo\"))\n        .thenReturn(CAPNP(value = \"baz\"));\n\n    auto result = KJ_ASSERT_NONNULL(promise.wait(ws));\n    KJ_EXPECT(result == \"baz\");\n  }\n\n  // Put a value.\n  {\n    test.put(\"foo\", \"qux\", {.noCache = true});\n\n    // If we read it right now, it's in cache.\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\", {.noCache = true}))) == \"qux\");\n\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"foo\", value = \"qux\")]))\n        .thenReturn(CAPNP());\n  }\n\n  // Wait on the output gate to make sure the flush is actually done.\n  test.gate.wait(nullptr).wait(test.ws);\n\n  // After the put completes, it's not in cache anymore.\n  {\n    auto promise = expectUncached(test.get(\"foo\", {.noCache = true}));\n\n    mockStorage->expectCall(\"get\", ws)\n        .withParams(CAPNP(key = \"foo\"))\n        .thenReturn(CAPNP(value = \"baz\"));\n\n    auto result = KJ_ASSERT_NONNULL(promise.wait(ws));\n    KJ_EXPECT(result == \"baz\");\n  }\n\n  // Do it again. This time, though, the read that happens while dirty doesn't have .noCache.\n  {\n    test.put(\"foo\", \"qux\", {.noCache = true});\n\n    // If we read it right now, it's in cache.\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"qux\");\n\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"foo\", value = \"qux\")]))\n        .thenReturn(CAPNP());\n  }\n\n  // Wait on the output gate to make sure the flush is actually done.\n  test.gate.wait(nullptr).wait(test.ws);\n\n  // This time it stayed in cache!\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"qux\");\n\n  // Do an uncached list.\n  {\n    auto promise = expectUncached(test.list(\"bar\", \"qux\", kj::none, {.noCache = true}));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\"), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"bar\", value = \"456\"), (key = \"baz\", value = \"789\"),\n                          (key = \"foo\", value = \"123\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"bar\", \"456\"}, {\"baz\", \"789\"}, {\"foo\", \"qux\"}}));\n  }\n\n  // `foo` is still cached.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"qux\");\n\n  // The other things that were returned weren't cached.\n  (void)expectUncached(test.get(\"bar\"));\n  (void)expectUncached(test.get(\"baz\"));\n\n  // No gaps were cached as empty either.\n  (void)expectUncached(test.get(\"corge\"));\n  (void)expectUncached(test.get(\"grault\"));\n\n  // Again, but reverse list.\n  {\n    auto promise = expectUncached(test.listReverse(\"bar\", \"qux\", kj::none, {.noCache = true}));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"bar\", end = \"qux\", reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"foo\", value = \"123\"), (key = \"baz\", value = \"789\"),\n                          (key = \"bar\", value = \"456\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"foo\", \"qux\"}, {\"baz\", \"789\"}, {\"bar\", \"456\"}}));\n  }\n\n  // `foo` is still cached.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"qux\");\n\n  // The other things that were returned weren't cached.\n  (void)expectUncached(test.get(\"bar\"));\n  (void)expectUncached(test.get(\"baz\"));\n\n  // No gaps were cached as empty either.\n  (void)expectUncached(test.get(\"corge\"));\n  (void)expectUncached(test.get(\"grault\"));\n}\n\n// =======================================================================================\n\nKJ_TEST(\"ActorCache transaction read-through\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  ActorCache::Transaction txn(test.cache);\n  ActorCacheConvenienceWrappers eztxn(txn);\n\n  {\n    auto promise = expectUncached(eztxn.get(\"foo\"));\n    mockStorage->expectCall(\"get\", ws)\n        .withParams(CAPNP(key = \"foo\"))\n        .thenReturn(CAPNP(value = \"123\"));\n    KJ_ASSERT(KJ_ASSERT_NONNULL(promise.wait(ws)) == \"123\");\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(eztxn.get(\"foo\"))) == \"123\");\n  }\n\n  {\n    auto promise = expectUncached(eztxn.get(\"bar\"));\n    mockStorage->expectCall(\"get\", ws).withParams(CAPNP(key = \"bar\")).thenReturn(CAPNP());\n    KJ_ASSERT(promise.wait(ws) == nullptr);\n    KJ_ASSERT(expectCached(eztxn.get(\"bar\")) == nullptr);\n  }\n\n  {\n    auto promise = expectUncached(eztxn.get({\"baz\"_kj, \"qux\"_kj, \"corge\"_kj}));\n\n    mockStorage->expectCall(\"getMultiple\", ws)\n        .withParams(CAPNP(keys = [ \"baz\", \"corge\", \"qux\" ]), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list = [ (key = \"baz\", value = \"456\"), (key = \"qux\", value = \"789\") ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"baz\", \"456\"}, {\"qux\", \"789\"}}));\n\n    KJ_ASSERT(expectCached(eztxn.get({\"foo\"_kj, \"bar\"_kj, \"baz\"_kj, \"qux\"_kj, \"corge\"_kj})) ==\n        kvs({{\"baz\", \"456\"}, {\"foo\", \"123\"}, {\"qux\", \"789\"}}));\n  }\n\n  {\n    auto promise = expectUncached(eztxn.list(\"a\", \"z\", 10));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"a\", end = \"z\", limit = 10), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"baz\", value = \"456\"), (key = \"foo\", value = \"123\"),\n                          (key = \"grault\", value = \"555\"), (key = \"qux\", value = \"789\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) ==\n        kvs({{\"baz\", \"456\"}, {\"foo\", \"123\"}, {\"grault\", \"555\"}, {\"qux\", \"789\"}}));\n\n    KJ_ASSERT(expectCached(eztxn.list(\"a\", \"z\", 10)) ==\n        kvs({{\"baz\", \"456\"}, {\"foo\", \"123\"}, {\"grault\", \"555\"}, {\"qux\", \"789\"}}));\n\n    KJ_ASSERT(expectCached(eztxn.listReverse(\"a\", \"z\")) ==\n        kvs({{\"qux\", \"789\"}, {\"grault\", \"555\"}, {\"foo\", \"123\"}, {\"baz\", \"456\"}}));\n  }\n}\n\nKJ_TEST(\"ActorCache transaction overlay changes\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  ActorCache::Transaction txn(test.cache);\n  ActorCacheConvenienceWrappers eztxn(txn);\n\n  eztxn.put(\"foo\", \"321\");\n  eztxn.put({{\"bar\", \"654\"}, {\"qux\", \"987\"}});\n  auto deletePromise1 = expectUncached(eztxn.delete_(\"grault\"));\n  auto deletePromise2 = expectUncached(eztxn.delete_({\"baz\"_kj, \"garply\"_kj}));\n\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(eztxn.get(\"foo\"))) == \"321\");\n  KJ_ASSERT(expectCached(eztxn.get(\"baz\")) == nullptr);\n  KJ_ASSERT(expectCached(eztxn.get({\"bar\"_kj, \"baz\"_kj, \"qux\"_kj})) ==\n      kvs({{\"bar\", \"654\"}, {\"qux\", \"987\"}}));\n\n  // The deletes will force reads in order to compute counts.\n  mockStorage->expectCall(\"get\", ws)\n      .withParams(CAPNP(key = \"grault\"))\n      .thenReturn(CAPNP(value = \"555\"));\n  mockStorage->expectCall(\"getMultiple\", ws)\n      .withParams(CAPNP(keys = [ \"baz\", \"garply\" ]), \"stream\"_kj)\n      .useCallback(\"stream\", [&](MockClient stream) {\n    stream.call(\"values\", CAPNP(list = [(key = \"baz\", value = \"456\")])).expectReturns(CAPNP(), ws);\n    stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n  }).expectCanceled();\n\n  KJ_ASSERT(deletePromise1.wait(ws));\n  KJ_ASSERT(deletePromise2.wait(ws) == 1);\n\n  {\n    auto promise = expectUncached(eztxn.get({\"baz\"_kj, \"qux\"_kj, \"corge\"_kj}));\n\n    mockStorage->expectCall(\"getMultiple\", ws)\n        .withParams(CAPNP(keys = [\"corge\"]), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [])).expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"qux\", \"987\"}}));\n\n    KJ_ASSERT(expectCached(eztxn.get({\"foo\"_kj, \"bar\"_kj, \"baz\"_kj, \"qux\"_kj, \"corge\"_kj})) ==\n        kvs({{\"bar\", \"654\"}, {\"foo\", \"321\"}, {\"qux\", \"987\"}}));\n  }\n\n  {\n    auto promise = expectUncached(eztxn.list(\"a\", \"z\", 10));\n\n    mockStorage\n        ->expectCall(\"list\", ws)\n        // limit is adjusted by 3 because it could return values that have already been deleted\n        // in the transaction.\n        .withParams(CAPNP(start = \"a\", end = \"z\", limit = 13), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"baz\", value = \"456\"), (key = \"foo\", value = \"123\"),\n                          (key = \"grault\", value = \"555\"), (key = \"qux\", value = \"789\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) == kvs({{\"bar\", \"654\"}, {\"foo\", \"321\"}, {\"qux\", \"987\"}}));\n\n    KJ_ASSERT(expectCached(eztxn.list(\"a\", \"z\", 10)) ==\n        kvs({{\"bar\", \"654\"}, {\"foo\", \"321\"}, {\"qux\", \"987\"}}));\n\n    KJ_ASSERT(expectCached(eztxn.listReverse(\"a\", \"z\")) ==\n        kvs({{\"qux\", \"987\"}, {\"foo\", \"321\"}, {\"bar\", \"654\"}}));\n  }\n\n  mockStorage->expectNoActivity(ws);\n\n  txn.commit();\n\n  {\n    auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [ \"grault\", \"baz\" ]))\n        .thenReturn(CAPNP(numDeleted = 2));\n    mockTxn->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries =\n                              [\n                                (key = \"foo\", value = \"321\"), (key = \"bar\", value = \"654\"),\n                                (key = \"qux\", value = \"987\")\n                              ]))\n        .thenReturn(CAPNP());\n    mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n    mockTxn->expectDropped(ws);\n  }\n}\n\nKJ_TEST(\"ActorCache transaction overlay changes precached\") {\n  // Like previous test, but have the range cached in the underlying cache before the transaction\n  // touches it.\n\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto promise = expectUncached(test.listReverse(\"a\", \"z\", 10));\n\n    mockStorage->expectCall(\"list\", ws)\n        .withParams(CAPNP(start = \"a\", end = \"z\", limit = 10, reverse = true), \"stream\"_kj)\n        .useCallback(\"stream\", [&](MockClient stream) {\n      stream\n          .call(\"values\",\n              CAPNP(list =\n                        [\n                          (key = \"qux\", value = \"789\"), (key = \"grault\", value = \"555\"),\n                          (key = \"foo\", value = \"123\"), (key = \"baz\", value = \"456\")\n                        ]))\n          .expectReturns(CAPNP(), ws);\n      stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n    }).expectCanceled();\n\n    KJ_ASSERT(promise.wait(ws) ==\n        kvs({{\"qux\", \"789\"}, {\"grault\", \"555\"}, {\"foo\", \"123\"}, {\"baz\", \"456\"}}));\n  }\n\n  ActorCache::Transaction txn(test.cache);\n  ActorCacheConvenienceWrappers eztxn(txn);\n\n  eztxn.put(\"foo\", \"321\");\n  eztxn.put({{\"bar\", \"654\"}, {\"qux\", \"987\"}});\n  KJ_ASSERT(expectCached(eztxn.delete_(\"grault\")));\n  KJ_ASSERT(expectCached(eztxn.delete_({\"baz\"_kj, \"garply\"_kj})) == 1);\n\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectCached(eztxn.get(\"foo\"))) == \"321\");\n  KJ_ASSERT(expectCached(eztxn.get(\"baz\")) == nullptr);\n  KJ_ASSERT(expectCached(eztxn.get({\"bar\"_kj, \"baz\"_kj, \"qux\"_kj})) ==\n      kvs({{\"bar\", \"654\"}, {\"qux\", \"987\"}}));\n\n  KJ_ASSERT(expectCached(eztxn.get({\"foo\"_kj, \"bar\"_kj, \"baz\"_kj, \"qux\"_kj, \"corge\"_kj})) ==\n      kvs({{\"bar\", \"654\"}, {\"foo\", \"321\"}, {\"qux\", \"987\"}}));\n  KJ_ASSERT(expectCached(eztxn.list(\"a\", \"z\", 10)) ==\n      kvs({{\"bar\", \"654\"}, {\"foo\", \"321\"}, {\"qux\", \"987\"}}));\n  KJ_ASSERT(expectCached(eztxn.listReverse(\"a\", \"z\")) ==\n      kvs({{\"qux\", \"987\"}, {\"foo\", \"321\"}, {\"bar\", \"654\"}}));\n\n  mockStorage->expectNoActivity(ws);\n\n  txn.commit();\n\n  {\n    auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n    mockTxn->expectCall(\"delete\", ws)\n        .withParams(CAPNP(keys = [ \"grault\", \"baz\" ]))\n        .thenReturn(CAPNP(numDeleted = 2));\n    mockTxn->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries =\n                              [\n                                (key = \"foo\", value = \"321\"), (key = \"bar\", value = \"654\"),\n                                (key = \"qux\", value = \"987\")\n                              ]))\n        .thenReturn(CAPNP());\n    mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n    mockTxn->expectDropped(ws);\n  }\n}\n\nKJ_TEST(\"ActorCache transaction output gate blocked during flush\") {\n  ActorCacheTest test({.monitorOutputGate = false});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Gate is currently not blocked.\n  test.gate.wait(nullptr).wait(ws);\n\n  // Do a transaction with a put.\n  ActorCache::Transaction txn(test.cache);\n  ActorCacheConvenienceWrappers eztxn(txn);\n  eztxn.put(\"foo\", \"123\");\n  txn.commit();\n\n  // Now it is blocked.\n  auto gatePromise = test.gate.wait(nullptr);\n  KJ_ASSERT(!gatePromise.poll(ws));\n\n  // Complete the transaction.\n  {\n    auto inProgressFlush = mockStorage->expectCall(\"put\", ws).withParams(\n        CAPNP(entries = [(key = \"foo\", value = \"123\")]));\n\n    // Still blocked until the flush completes.\n    KJ_ASSERT(!gatePromise.poll(ws));\n\n    kj::mv(inProgressFlush).thenReturn(CAPNP());\n  }\n\n  KJ_ASSERT(gatePromise.poll(ws));\n  gatePromise.wait(ws);\n}\n\nKJ_TEST(\"ActorCache transaction output gate bypass\") {\n  ActorCacheTest test({.monitorOutputGate = false});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Gate is currently not blocked.\n  test.gate.wait(nullptr).wait(ws);\n\n  // Do a transaction with a put.\n  ActorCache::Transaction txn(test.cache);\n  ActorCacheConvenienceWrappers eztxn(txn);\n  eztxn.put(\"foo\", \"123\", {.allowUnconfirmed = true});\n  txn.commit();\n\n  // Gate still isn't blocked, because we set `allowUnconfirmed`.\n  test.gate.wait(nullptr).wait(ws);\n\n  // Complete the transaction with a flush.\n  mockStorage->expectCall(\"put\", ws)\n      .withParams(CAPNP(entries = [(key = \"foo\", value = \"123\")]))\n      .thenReturn(CAPNP());\n\n  test.gate.wait(nullptr).wait(ws);\n}\n\nKJ_TEST(\"ActorCache transaction output gate bypass on one put but not the next\") {\n  ActorCacheTest test({.monitorOutputGate = false});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Gate is currently not blocked.\n  test.gate.wait(nullptr).wait(ws);\n\n  // Do a transaction with two puts, only bypassing on the first. The net result should be that\n  // the output gate is in effect.\n  ActorCache::Transaction txn(test.cache);\n  ActorCacheConvenienceWrappers eztxn(txn);\n  eztxn.put(\"foo\", \"123\", {.allowUnconfirmed = true});\n  eztxn.put(\"bar\", \"456\");\n  txn.commit();\n\n  // Now it is blocked.\n  auto gatePromise = test.gate.wait(nullptr);\n  KJ_ASSERT(!gatePromise.poll(ws));\n\n  // Complete the transaction.\n  {\n    auto inProgressFlush = mockStorage->expectCall(\"put\", ws).withParams(\n        CAPNP(entries = [ (key = \"foo\", value = \"123\"), (key = \"bar\", value = \"456\") ]));\n\n    // Still blocked until the flush completes.\n    KJ_ASSERT(!gatePromise.poll(ws));\n\n    kj::mv(inProgressFlush).thenReturn(CAPNP());\n  }\n\n  KJ_ASSERT(gatePromise.poll(ws));\n  gatePromise.wait(ws);\n}\n\nKJ_TEST(\"ActorCache transaction multiple put batches\") {\n  ActorCacheTest test({.maxKeysPerRpc = 2});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Do a transaction with enough puts to batch.\n  ActorCache::Transaction txn(test.cache);\n  ActorCacheConvenienceWrappers eztxn(txn);\n  eztxn.put({{\"foo\", \"123\"}, {\"bar\", \"456\"}, {\"baz\", \"789\"}});\n\n  // Poll the wait scope to make sure we haven't slipped through to the cache already.\n  ws.poll();\n\n  eztxn.put({{\"qux\", \"555\"}, {\"corge\", \"999\"}});\n  txn.commit();\n\n  auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n  mockTxn->expectCall(\"put\", ws)\n      .withParams(CAPNP(entries = [ (key = \"foo\", value = \"123\"), (key = \"bar\", value = \"456\") ]))\n      .thenReturn(CAPNP());\n  mockTxn->expectCall(\"put\", ws)\n      .withParams(CAPNP(entries = [ (key = \"baz\", value = \"789\"), (key = \"qux\", value = \"555\") ]))\n      .thenReturn(CAPNP());\n  mockTxn->expectCall(\"put\", ws)\n      .withParams(CAPNP(entries = [(key = \"corge\", value = \"999\")]))\n      .thenReturn(CAPNP());\n  mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n  mockTxn->expectDropped(ws);\n}\n\nKJ_TEST(\"ActorCache transaction multiple counted delete batches\") {\n  // Do a transaction with a big counted delete. The rpc getMultiple and delete should batch\n  // according to maxKeysPerRpc.\n\n  ActorCacheTest test({.maxKeysPerRpc = 2});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  ActorCache::Transaction txn(test.cache);\n  ActorCacheConvenienceWrappers eztxn(txn);\n\n  {\n    // Load one of our values to delete into the cache itself which will avoid rpc deletes for\n    // counting.\n    test.put(\"count2\", \"2\");\n    mockStorage->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"count2\", value = \"2\")]))\n        .thenReturn(CAPNP());\n  }\n\n  {\n    // Load one of our values to delete into the transaction which will avoid even talking to the\n    // cache.\n    eztxn.put(\"count3\", \"3\");\n  }\n\n  auto deletePromise =\n      eztxn.delete_({\"count1\"_kj, \"count2\"_kj, \"count3\"_kj, \"count4\"_kj, \"count5\"_kj})\n          .get<kj::Promise<uint>>();\n\n  mockStorage\n      ->expectCall(\"getMultiple\", ws)\n      // Note that this batch is smaller because \"count2\" was known to the actor cache.\n      .withParams(CAPNP(keys = [\"count1\"]), \"stream\"_kj)\n      .useCallback(\"stream\", [&](MockClient stream) {\n    // Pretend that \"count1\" already exists but was not in the cache.\n    stream.call(\"values\", CAPNP(list = [(key = \"count1\", value = \"1\")])).expectReturns(CAPNP(), ws);\n    stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n  }).expectCanceled();\n  mockStorage->expectCall(\"getMultiple\", ws)\n      .withParams(CAPNP(keys = [ \"count4\", \"count5\" ]), \"stream\"_kj)\n      .useCallback(\"stream\", [&](MockClient stream) {\n    stream.call(\"end\", CAPNP()).expectReturns(CAPNP(), ws);\n  }).expectCanceled();\n\n  // For hacky reasons, we are able to observe the counted delete before we submit the\n  // transaction.\n  KJ_EXPECT(deletePromise.wait(ws) == 3);\n\n  txn.commit();\n\n  auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n  mockTxn\n      ->expectCall(\"delete\", ws)\n      // \"count3\" comes first because it entered the transaction first.\n      .withParams(CAPNP(keys = [ \"count3\", \"count1\" ]))\n      .thenReturn(CAPNP(numDeleted = 1));\n  mockTxn\n      ->expectCall(\"delete\", ws)\n      // Neither \"count4\" or \"count5\" are deleted because we observed them in the get.\n      .withParams(CAPNP(keys = [\"count2\"]))\n      .thenReturn(CAPNP(numDeleted = 1));\n  mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n  mockTxn->expectDropped(ws);\n}\n\nKJ_TEST(\"ActorCache transaction negative list range returns nothing\") {\n  ActorCacheTest test({.monitorOutputGate = false});\n\n  ActorCache::Transaction txn(test.cache);\n  ActorCacheConvenienceWrappers eztxn(txn);\n\n  eztxn.put(\"foo\", \"123\");\n\n  KJ_ASSERT(expectCached(eztxn.list(\"qux\", \"bar\")) == kvs({}));\n  KJ_ASSERT(expectCached(eztxn.listReverse(\"qux\", \"bar\")) == kvs({}));\n}\n\n// =======================================================================================\n\nKJ_TEST(\"ActorCache list stream cancellation\") {\n  // Test for cases where implementations of ListStream might stay alive longer than expected, due\n  // to capabilities being held remotely.\n  //\n  // We can't use ActorCacheTest in this test as we need to manage allocation and destruction to\n  // set up the problematic circumstances.\n\n  kj::EventLoop loop;\n  kj::WaitScope ws(loop);\n\n  auto mockPair = MockServer::make<rpc::ActorStorage::Stage>();\n  kj::Own<MockServer> mockStorage = kj::mv(mockPair.mock);\n  auto mockClient = kj::mv(mockPair.client);\n\n  ActorCacheTestOptions options;\n\n  kj::Maybe<MockServer::ExpectedCall> call;\n  kj::Maybe<MockClient> listClient;\n\n  // Try get-multiple.\n  {\n    // We allocate `lru` on the heap to assist Valgrind in being able to detect when it is used\n    // after free.\n    auto lru = kj::heap<ActorCache::SharedLru>(ActorCache::SharedLru::Options{options.softLimit,\n      options.hardLimit, options.staleTimeout, options.dirtyListByteLimit, options.maxKeysPerRpc});\n    OutputGate gate;\n    ActorCache cache(mockClient, *lru, gate);\n\n    ActorCacheConvenienceWrappers ezCache(cache);\n\n    auto promise = expectUncached(ezCache.get({\"foo\"_kj, \"bar\"_kj}));\n\n    call = mockStorage->expectCall(\"getMultiple\", ws)\n               .withParams(CAPNP(keys = [ \"bar\", \"foo\" ]), \"stream\"_kj)\n               .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"bar\", value = \"123\")]))\n          .expectReturns(CAPNP(), ws);\n      listClient = kj::mv(stream);\n    });\n\n    // Now we're going to cancel the promise and destroy the cache while the call is still\n    // outstanding, with unreported entries in it!\n  }\n\n  KJ_ASSERT_NONNULL(listClient)\n      .call(\"values\", CAPNP(list = [(key = \"foo\", value = \"456\")]))\n      .expectThrows(kj::Exception::Type::DISCONNECTED, \"canceled\", ws);\n  KJ_ASSERT_NONNULL(kj::mv(call)).expectCanceled();\n\n  // Try list().\n  {\n    // We allocate `lru` on the heap to assist Valgrind in being able to detect when it is used\n    // after free.\n    auto lru = kj::heap<ActorCache::SharedLru>(ActorCache::SharedLru::Options{options.softLimit,\n      options.hardLimit, options.staleTimeout, options.dirtyListByteLimit, options.maxKeysPerRpc});\n    OutputGate gate;\n    ActorCache cache(mockClient, *lru, gate);\n\n    ActorCacheConvenienceWrappers ezCache(cache);\n\n    auto promise = expectUncached(ezCache.list(\"bar\", \"qux\"));\n\n    call = mockStorage->expectCall(\"list\", ws)\n               .withParams(CAPNP(start = \"bar\", end = \"qux\"), \"stream\"_kj)\n               .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"bar\", value = \"123\")]))\n          .expectReturns(CAPNP(), ws);\n      listClient = kj::mv(stream);\n    });\n\n    // Now we're going to cancel the promise and destroy the cache while the call is still\n    // outstanding, with unreported entries in it!\n  }\n\n  KJ_ASSERT_NONNULL(listClient)\n      .call(\"values\", CAPNP(list = [(key = \"foo\", value = \"456\")]))\n      .expectThrows(kj::Exception::Type::DISCONNECTED, \"canceled\", ws);\n  KJ_ASSERT_NONNULL(kj::mv(call)).expectCanceled();\n\n  // Try listReverse().\n  {\n    // We allocate `lru` on the heap to assist Valgrind in being able to detect when it is used\n    // after free.\n    auto lru = kj::heap<ActorCache::SharedLru>(ActorCache::SharedLru::Options{options.softLimit,\n      options.hardLimit, options.staleTimeout, options.dirtyListByteLimit, options.maxKeysPerRpc});\n    OutputGate gate;\n    ActorCache cache(mockClient, *lru, gate);\n\n    ActorCacheConvenienceWrappers ezCache(cache);\n\n    auto promise = expectUncached(ezCache.listReverse(\"bar\", \"qux\"));\n\n    call = mockStorage->expectCall(\"list\", ws)\n               .withParams(CAPNP(start = \"bar\", end = \"qux\", reverse = true), \"stream\"_kj)\n               .useCallback(\"stream\", [&](MockClient stream) {\n      stream.call(\"values\", CAPNP(list = [(key = \"foo\", value = \"123\")]))\n          .expectReturns(CAPNP(), ws);\n      listClient = kj::mv(stream);\n    });\n\n    // Now we're going to cancel the promise and destroy the cache while the call is still\n    // outstanding, with unreported entries in it!\n  }\n\n  KJ_ASSERT_NONNULL(listClient)\n      .call(\"values\", CAPNP(list = [(key = \"bar\", value = \"456\")]))\n      .expectThrows(kj::Exception::Type::DISCONNECTED, \"canceled\", ws);\n  KJ_ASSERT_NONNULL(kj::mv(call)).expectCanceled();\n}\n\nKJ_TEST(\"ActorCache never-flush\") {\n  ActorCacheTest test({.neverFlush = true});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  // Puts don't start a transaction.\n  KJ_EXPECT(test.put(\"foo\", \"123\") == nullptr);\n  KJ_EXPECT(test.cache.onNoPendingFlush(nullptr) == nullptr);\n  mockStorage->expectNoActivity(ws);\n\n  // Gets still see the put() value.\n  KJ_EXPECT(KJ_ASSERT_NONNULL(expectCached(test.get(\"foo\"))) == \"123\");\n\n  // Uncached reads work normally.\n  {\n    auto promise = expectUncached(test.get(\"bar\"));\n\n    mockStorage->expectCall(\"get\", ws)\n        .withParams(CAPNP(key = \"bar\"))\n        .thenReturn(CAPNP(value = \"456\"));\n\n    auto result = KJ_ASSERT_NONNULL(promise.wait(ws));\n    KJ_EXPECT(result == \"456\");\n  }\n}\n\nKJ_TEST(\"ActorCache alarm get/put\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  {\n    auto time = expectUncached(test.getAlarm());\n\n    mockStorage->expectCall(\"getAlarm\", ws).thenReturn(CAPNP(scheduledTimeMs = 0));\n\n    KJ_ASSERT(time.wait(ws) == kj::none);\n  }\n\n  {\n    auto time = expectCached(test.getAlarm());\n\n    KJ_ASSERT(time == kj::none);\n  }\n\n  auto oneMs = 1 * kj::MILLISECONDS + kj::UNIX_EPOCH;\n  auto twoMs = 2 * kj::MILLISECONDS + kj::UNIX_EPOCH;\n  // Used as the \"current time\" parameter for armAlarmHandler in tests.\n  auto testCurrentTime = kj::UNIX_EPOCH;\n  {\n    // Test alarm writes happen transactionally with storage ops\n    test.setAlarm(oneMs);\n    test.put(\"foo\", \"bar\");\n\n    auto mockTxn = mockStorage->expectCall(\"txn\", ws).returnMock(\"transaction\");\n    mockTxn->expectCall(\"put\", ws)\n        .withParams(CAPNP(entries = [(key = \"foo\", value = \"bar\")]))\n        .thenReturn(CAPNP());\n    mockTxn->expectCall(\"setAlarm\", ws).withParams(CAPNP(scheduledTimeMs = 1)).thenReturn(CAPNP());\n    mockTxn->expectCall(\"commit\", ws).thenReturn(CAPNP());\n    mockTxn->expectDropped(ws);\n  }\n\n  {\n    auto time = expectCached(test.getAlarm());\n\n    KJ_ASSERT(time == oneMs);\n  }\n\n  {\n    // Test clearing alarm\n    test.setAlarm(kj::none);\n\n    // When there are no other storage operations to be flushed, alarm modifications can be flushed\n    // without a wrapping txn.\n    mockStorage->expectCall(\"deleteAlarm\", ws)\n        .withParams(CAPNP(timeToDeleteMs = 0))\n        .thenReturn(CAPNP(deleted = true));\n    // Wait on the output gate to make sure the flush is actually done before checking the cache.\n    test.gate.wait(nullptr).wait(test.ws);\n  }\n\n  {\n    auto time = expectCached(test.getAlarm());\n\n    KJ_ASSERT(time == kj::none);\n  }\n\n  {\n    // we have a cached time == nullptr, so we should not attempt to run an alarm\n    auto armResult =\n        test.cache.armAlarmHandler(10 * kj::SECONDS + kj::UNIX_EPOCH, nullptr, testCurrentTime);\n    KJ_ASSERT(armResult.is<ActorCache::CancelAlarmHandler>());\n    auto cancelResult = kj::mv(armResult.get<ActorCache::CancelAlarmHandler>());\n    KJ_ASSERT(cancelResult.waitBeforeCancel.poll(ws));\n    cancelResult.waitBeforeCancel.wait(ws);\n  }\n\n  {\n    test.setAlarm(oneMs);\n\n    mockStorage->expectCall(\"setAlarm\", ws)\n        .withParams(CAPNP(scheduledTimeMs = 1))\n        .thenReturn(CAPNP());\n  }\n\n  {\n    // Test that alarm handler handle clears alarm when dropped with no writes\n    {\n      auto armResult = test.cache.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n      KJ_ASSERT(armResult.is<ActorCache::RunAlarmHandler>());\n    }\n    mockStorage->expectCall(\"deleteAlarm\", ws)\n        .withParams(CAPNP(timeToDeleteMs = 1))\n        .thenReturn(CAPNP(deleted = true));\n  }\n\n  {\n    test.setAlarm(oneMs);\n\n    // Test that alarm handler handle does not clear alarm when dropped with writes\n    {\n      auto armResult = test.cache.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n      KJ_ASSERT(armResult.is<ActorCache::RunAlarmHandler>());\n      test.setAlarm(twoMs);\n    }\n    mockStorage->expectCall(\"setAlarm\", ws)\n        .withParams(CAPNP(scheduledTimeMs = 2))\n        .thenReturn(CAPNP());\n  }\n\n  {\n    test.setAlarm(oneMs);\n\n    // Test that alarm handler handle does not cache delete when it fails\n    {\n      auto armResult = test.cache.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n      KJ_ASSERT(armResult.is<ActorCache::RunAlarmHandler>());\n    }\n    mockStorage->expectCall(\"deleteAlarm\", ws)\n        .withParams(CAPNP(timeToDeleteMs = 1))\n        .thenReturn(CAPNP(deleted = false));\n    test.gate.wait(nullptr).wait(test.ws);\n  }\n\n  {\n    // Test that alarm handler handle does not cache alarm delete when noCache == true\n    {\n      auto armResult = test.cache.armAlarmHandler(twoMs, nullptr, testCurrentTime, true);\n      KJ_ASSERT(armResult.is<ActorCache::RunAlarmHandler>());\n    }\n    mockStorage->expectCall(\"deleteAlarm\", ws)\n        .withParams(CAPNP(timeToDeleteMs = 2))\n        .thenReturn(CAPNP(deleted = true));\n    test.gate.wait(nullptr).wait(test.ws);\n  }\n\n  {\n    auto time = expectUncached(test.getAlarm());\n\n    mockStorage->expectCall(\"getAlarm\", ws).thenReturn(CAPNP(scheduledTimeMs = 0));\n\n    KJ_ASSERT(time.wait(ws) == kj::none);\n  }\n}\n\nKJ_TEST(\"ActorCache uncached nonnull alarm get\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto time = expectUncached(test.getAlarm());\n  auto oneMs = 1 * kj::MILLISECONDS + kj::UNIX_EPOCH;\n\n  mockStorage->expectCall(\"getAlarm\", ws).thenReturn(CAPNP(scheduledTimeMs = 1));\n\n  KJ_ASSERT(time.wait(ws) == oneMs);\n}\n\nKJ_TEST(\"ActorCache alarm delete when flush fails\") {\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto oneMs = 1 * kj::MILLISECONDS + kj::UNIX_EPOCH;\n  auto testCurrentTime = kj::UNIX_EPOCH;\n\n  {\n    auto time = expectUncached(test.getAlarm());\n\n    mockStorage->expectCall(\"getAlarm\", ws).thenReturn(CAPNP(scheduledTimeMs = 1));\n\n    KJ_ASSERT(time.wait(ws) == oneMs);\n  }\n\n  {\n    auto time = KJ_ASSERT_NONNULL(expectCached(test.getAlarm()));\n    KJ_ASSERT(time == oneMs);\n  }\n\n  // we want to test that even if a flush is retried\n  // that the post-delete actions for a checked delete happen.\n  {\n    auto handle = test.cache.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n\n    auto time = expectCached(test.getAlarm());\n    KJ_ASSERT(time == kj::none);\n  }\n\n  for (auto i = 0; i < 2; i++) {\n    mockStorage->expectCall(\"deleteAlarm\", ws)\n        .withParams(CAPNP(timeToDeleteMs = 1))\n        .thenThrow(KJ_EXCEPTION(DISCONNECTED, \"foo\"));\n  }\n\n  {\n    mockStorage->expectCall(\"deleteAlarm\", ws)\n        .withParams(CAPNP(timeToDeleteMs = 1))\n        .thenReturn(CAPNP(deleted = false));\n    // Wait on the output gate to make sure the flush is actually done.\n    test.gate.wait(nullptr).wait(test.ws);\n  }\n\n  {\n    auto time = expectUncached(test.getAlarm());\n\n    mockStorage->expectCall(\"getAlarm\", ws).thenReturn(CAPNP(scheduledTimeMs = 10));\n\n    KJ_ASSERT(time.wait(ws) == 10 * kj::MILLISECONDS + kj::UNIX_EPOCH);\n  }\n}\n\nKJ_TEST(\"ActorCache deleteAll() with deleteAlarm deletes alarm after deleteAll succeeds\") {\n  // Tests that deleteAll() with deleteAlarm=true deletes the alarm in the post-deleteAll\n  // flush, ensuring the alarm is only deleted after the deleteAll RPC succeeds.\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto oneMs = 1 * kj::MILLISECONDS + kj::UNIX_EPOCH;\n\n  // First, set an alarm so we have something to delete.\n  test.setAlarm(oneMs);\n\n  mockStorage->expectCall(\"setAlarm\", ws)\n      .withParams(CAPNP(scheduledTimeMs = 1))\n      .thenReturn(CAPNP());\n\n  {\n    auto time = expectCached(test.getAlarm());\n    KJ_ASSERT(time == oneMs);\n  }\n\n  // Call deleteAll() with deleteAlarm=true.\n  auto deleteAll = test.cache.deleteAll({}, nullptr, {.deleteAlarm = true});\n\n  // The deleteAll RPC goes through first.\n  mockStorage->expectCall(\"deleteAll\", ws).thenReturn(CAPNP(numDeleted = 0));\n\n  KJ_ASSERT(deleteAll.count.wait(ws) == 0);\n\n  // After deleteAll succeeds, the alarm deletion is flushed in the post-deleteAll flush.\n  mockStorage->expectCall(\"deleteAlarm\", ws)\n      .withParams(CAPNP(timeToDeleteMs = 0))\n      .thenReturn(CAPNP(deleted = true));\n  test.gate.wait(nullptr).wait(test.ws);\n\n  {\n    auto time = expectCached(test.getAlarm());\n    KJ_ASSERT(time == kj::none);\n  }\n}\n\nKJ_TEST(\"ActorCache deleteAll() without deleteAlarm preserves alarm\") {\n  // Tests that deleteAll() without deleteAlarm (the default) does not delete the alarm.\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto oneMs = 1 * kj::MILLISECONDS + kj::UNIX_EPOCH;\n\n  // First, set an alarm.\n  test.setAlarm(oneMs);\n\n  mockStorage->expectCall(\"setAlarm\", ws)\n      .withParams(CAPNP(scheduledTimeMs = 1))\n      .thenReturn(CAPNP());\n\n  {\n    auto time = expectCached(test.getAlarm());\n    KJ_ASSERT(time == oneMs);\n  }\n\n  // Call deleteAll() without deleteAlarm (default).\n  auto deleteAll = test.cache.deleteAll({}, nullptr);\n\n  // The deleteAll RPC goes through.\n  mockStorage->expectCall(\"deleteAll\", ws).thenReturn(CAPNP(numDeleted = 0));\n\n  KJ_ASSERT(deleteAll.count.wait(ws) == 0);\n\n  // Wait for the output gate to complete.\n  test.gate.wait(nullptr).wait(test.ws);\n\n  // The alarm should still be set.\n  {\n    auto time = expectCached(test.getAlarm());\n    KJ_ASSERT(time == oneMs);\n  }\n}\n\nKJ_TEST(\"ActorCache deleteAll() with deleteAlarm does not overwrite subsequent setAlarm\") {\n  // Tests that if setAlarm() is called after deleteAll({.deleteAlarm = true}) but before the\n  // deleteAll RPC completes, the new alarm is preserved rather than being clobbered by the\n  // post-deleteAll alarm deletion.\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto oneMs = 1 * kj::MILLISECONDS + kj::UNIX_EPOCH;\n  auto twoMs = 2 * kj::MILLISECONDS + kj::UNIX_EPOCH;\n\n  // First, set an alarm so we have something to delete.\n  test.setAlarm(oneMs);\n\n  mockStorage->expectCall(\"setAlarm\", ws)\n      .withParams(CAPNP(scheduledTimeMs = 1))\n      .thenReturn(CAPNP());\n\n  {\n    auto time = expectCached(test.getAlarm());\n    KJ_ASSERT(time == oneMs);\n  }\n\n  // Call deleteAll() with deleteAlarm=true.\n  auto deleteAll = test.cache.deleteAll({}, nullptr, {.deleteAlarm = true});\n\n  // Hold the deleteAll RPC in flight.\n  auto mockDeleteAll = mockStorage->expectCall(\"deleteAll\", ws);\n\n  // While the deleteAll RPC is in flight, set a new alarm. This simulates the user's JS code\n  // calling setAlarm() after deleteAll() but before the deleteAll RPC has completed.\n  test.setAlarm(twoMs);\n\n  // Now complete the deleteAll RPC.\n  kj::mv(mockDeleteAll).thenReturn(CAPNP(numDeleted = 0));\n\n  KJ_ASSERT(deleteAll.count.wait(ws) == 0);\n\n  // The post-deleteAll flush should send setAlarm for the new time, not deleteAlarm.\n  mockStorage->expectCall(\"setAlarm\", ws)\n      .withParams(CAPNP(scheduledTimeMs = 2))\n      .thenReturn(CAPNP());\n  test.gate.wait(nullptr).wait(test.ws);\n\n  // Verify the alarm is set to the new time.\n  {\n    auto time = expectCached(test.getAlarm());\n    KJ_ASSERT(time == twoMs);\n  }\n}\n\nKJ_TEST(\"ActorCache deleteAll() with deleteAlarm during alarm handler cancels deferred delete\") {\n  // Tests that calling deleteAll() with deleteAlarm=true while an alarm handler is running\n  // overwrites the DeferredAlarmDelete state, so when the handler finishes and the\n  // DeferredAlarmDeleter fires, it does nothing (since currentAlarmTime is no longer\n  // DeferredAlarmDelete). The alarm deletion instead happens via the normal post-deleteAll\n  // flush path.\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto oneMs = 1 * kj::MILLISECONDS + kj::UNIX_EPOCH;\n  auto testCurrentTime = kj::UNIX_EPOCH;\n\n  // Set an alarm so we have something to delete.\n  test.setAlarm(oneMs);\n\n  mockStorage->expectCall(\"setAlarm\", ws)\n      .withParams(CAPNP(scheduledTimeMs = 1))\n      .thenReturn(CAPNP());\n\n  {\n    auto time = expectCached(test.getAlarm());\n    KJ_ASSERT(time == oneMs);\n  }\n\n  {\n    // Arm the alarm handler, putting us in DeferredAlarmDelete state.\n    auto armResult = test.cache.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n    KJ_ASSERT(armResult.is<ActorCache::RunAlarmHandler>());\n\n    // During the handler, getAlarm() should return none (deferred delete is active).\n    auto time = expectCached(test.getAlarm());\n    KJ_ASSERT(time == kj::none);\n\n    // Call deleteAll() with deleteAlarm=true while the handler is running. This overwrites\n    // currentAlarmTime from DeferredAlarmDelete to KnownAlarmTime{CLEAN, none}.\n    auto deleteAll = test.cache.deleteAll({}, nullptr, {.deleteAlarm = true});\n\n    // getAlarm() should still return none.\n    {\n      auto time = expectCached(test.getAlarm());\n      KJ_ASSERT(time == kj::none);\n    }\n\n    // Drop the RunAlarmHandler. Since currentAlarmTime is no longer DeferredAlarmDelete,\n    // the DeferredAlarmDeleter does nothing.\n  }\n\n  // The deleteAll RPC goes through.\n  mockStorage->expectCall(\"deleteAll\", ws).thenReturn(CAPNP(numDeleted = 0));\n\n  // After deleteAll succeeds, the alarm deletion is flushed in the post-deleteAll flush.\n  mockStorage->expectCall(\"deleteAlarm\", ws)\n      .withParams(CAPNP(timeToDeleteMs = 0))\n      .thenReturn(CAPNP(deleted = true));\n  test.gate.wait(nullptr).wait(test.ws);\n\n  {\n    auto time = expectCached(test.getAlarm());\n    KJ_ASSERT(time == kj::none);\n  }\n}\n\nKJ_TEST(\n    \"ActorCache deleteAll() without deleteAlarm during alarm handler preserves deferred delete\") {\n  // Tests that calling deleteAll() without deleteAlarm while an alarm handler is running\n  // leaves the DeferredAlarmDelete state intact. When the handler finishes, the deferred\n  // deletion fires normally and the alarm is deleted via the pre-deleteAll flush.\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto oneMs = 1 * kj::MILLISECONDS + kj::UNIX_EPOCH;\n  auto testCurrentTime = kj::UNIX_EPOCH;\n\n  // Set an alarm so we have something to delete.\n  test.setAlarm(oneMs);\n\n  mockStorage->expectCall(\"setAlarm\", ws)\n      .withParams(CAPNP(scheduledTimeMs = 1))\n      .thenReturn(CAPNP());\n\n  {\n    auto time = expectCached(test.getAlarm());\n    KJ_ASSERT(time == oneMs);\n  }\n\n  {\n    // Arm the alarm handler, putting us in DeferredAlarmDelete state.\n    auto armResult = test.cache.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n    KJ_ASSERT(armResult.is<ActorCache::RunAlarmHandler>());\n\n    // During the handler, getAlarm() should return none (deferred delete is active).\n    auto time = expectCached(test.getAlarm());\n    KJ_ASSERT(time == kj::none);\n\n    // Call deleteAll() without deleteAlarm while the handler is running.\n    // This does NOT touch currentAlarmTime, so DeferredAlarmDelete is preserved.\n    auto deleteAll = test.cache.deleteAll({}, nullptr);\n\n    // getAlarm() still returns none because DeferredAlarmDelete is still active.\n    {\n      auto time = expectCached(test.getAlarm());\n      KJ_ASSERT(time == kj::none);\n    }\n\n    // Drop the RunAlarmHandler. DeferredAlarmDeleter fires, sets status to READY,\n    // and calls ensureFlushScheduled().\n  }\n\n  // The deferred alarm delete is flushed in the pre-deleteAll flush, using the original\n  // alarm time as the timeToDeleteMs.\n  mockStorage->expectCall(\"deleteAlarm\", ws)\n      .withParams(CAPNP(timeToDeleteMs = 1))\n      .thenReturn(CAPNP(deleted = true));\n\n  // Then the deleteAll RPC goes through.\n  mockStorage->expectCall(\"deleteAll\", ws).thenReturn(CAPNP(numDeleted = 0));\n\n  test.gate.wait(nullptr).wait(test.ws);\n\n  {\n    auto time = expectCached(test.getAlarm());\n    KJ_ASSERT(time == kj::none);\n  }\n}\n\nKJ_TEST(\"ActorCache deleteAll() failure with deleteAlarm does not delete alarm\") {\n  // Tests that if the deleteAll RPC fails, the alarm deletion RPC is never sent.\n  ActorCacheTest test({.monitorOutputGate = false});\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  auto brokenPromise = test.gate.onBroken();\n\n  auto oneMs = 1 * kj::MILLISECONDS + kj::UNIX_EPOCH;\n\n  // First, set an alarm so we have something to delete.\n  test.setAlarm(oneMs);\n\n  mockStorage->expectCall(\"setAlarm\", ws)\n      .withParams(CAPNP(scheduledTimeMs = 1))\n      .thenReturn(CAPNP());\n\n  {\n    auto time = expectCached(test.getAlarm());\n    KJ_ASSERT(time == oneMs);\n  }\n\n  // Call deleteAll() with deleteAlarm=true.\n  auto deleteAll = test.cache.deleteAll({}, nullptr, {.deleteAlarm = true});\n\n  // The deleteAll RPC fails.\n  mockStorage->expectCall(\"deleteAll\", ws)\n      .thenThrow(KJ_EXCEPTION(FAILED, \"jsg.Error: deleteAll failed\"));\n\n  // The output gate should be broken due to the failure.\n  KJ_EXPECT_THROW_MESSAGE(\"deleteAll failed\", brokenPromise.wait(ws));\n\n  // No deleteAlarm RPC should have been sent since the deleteAll failed.\n  mockStorage->expectNoActivity(ws);\n}\n\nKJ_TEST(\"ActorCache can wait for flush\") {\n  // This test confirms that `onNoPendingFlush()` will return a promise that resolves when any\n  // scheduled or in-flight flush completes.\n\n  ActorCacheTest test;\n  auto& ws = test.ws;\n  auto& mockStorage = test.mockStorage;\n\n  struct InFlightRequest {\n    workerd::MockServer::ExpectedCall op;\n    kj::Maybe<kj::Own<workerd::MockServer>> maybeTxn;\n  };\n\n  // There is no pending flush since nothing has been done!\n  KJ_ASSERT(test.cache.onNoPendingFlush(nullptr) == nullptr);\n\n  struct VerifyOptions {\n    bool skipSecondOperation;\n  };\n  size_t secondaryPutIndex = 0;\n  auto verify = [&](auto receiveRequest, auto sendResponse, VerifyOptions options) {\n    // We haven't sent our request yet, but we should have a promise now.\n    auto scheduledPromise = KJ_ASSERT_NONNULL(test.cache.onNoPendingFlush(nullptr));\n\n    // We have sent our request, but it hasn't responded yet. We should still have a promise.\n    auto req = receiveRequest();\n    auto inFlightPromise = KJ_ASSERT_NONNULL(test.cache.onNoPendingFlush(nullptr));\n\n    // Do an additional put to make a separate flush.\n    struct SecondOperation {\n      kj::String key;\n      kj::Promise<void> scheduledPromise;\n    };\n    kj::Maybe<SecondOperation> maybeSecondOperation;\n    if (!options.skipSecondOperation) {\n      auto key = kj::str(\"foo-\", secondaryPutIndex++);\n      test.put(key, \"bar\");\n      auto secondPromise = KJ_ASSERT_NONNULL(test.cache.onNoPendingFlush(nullptr));\n      KJ_ASSERT(!secondPromise.poll(ws));\n      maybeSecondOperation.emplace(SecondOperation{\n        .key = kj::mv(key),\n        .scheduledPromise = kj::mv(secondPromise),\n      });\n    }\n\n    // No promise should have resolved yet.\n    KJ_ASSERT(!scheduledPromise.poll(ws) && !inFlightPromise.poll(ws));\n\n    // Resolve the operations and confirm that the promises resolve.\n    sendResponse(kj::mv(req));\n    scheduledPromise.wait(ws);\n    inFlightPromise.wait(ws);\n\n    KJ_IF_SOME(secondOperation, maybeSecondOperation) {\n      // This promise is for a later flush, so it should not have resolved yet.\n      KJ_ASSERT(!secondOperation.scheduledPromise.poll(ws));\n\n      // Finish our secondary put and observe the second flush resolving.\n      auto params =\n          kj::str(R\"((entries = [(key = \")\", secondOperation.key, R\"(\", value = \"bar\")]))\");\n      mockStorage->expectCall(\"put\", ws).withParams(params).thenReturn(CAPNP());\n\n      secondOperation.scheduledPromise.wait(ws);\n    }\n\n    // We finished our flush, nothing left to do.\n    KJ_ASSERT(test.cache.onNoPendingFlush(nullptr) == nullptr);\n  };\n\n  {\n    // Join in on a simple put.\n    test.put(\"foo\", \"bar\");\n\n    verify(\n        [&]() {\n      return InFlightRequest{\n        .op = mockStorage->expectCall(\"put\", ws).withParams(\n            CAPNP(entries = [(key = \"foo\", value = \"bar\")])),\n      };\n    }, [&](auto req) { kj::mv(req.op).thenReturn(CAPNP()); },\n        {\n          .skipSecondOperation = false,\n        });\n  }\n\n  {\n    // Join in on a delete.\n    test.delete_(\"foo\");\n\n    verify(\n        [&]() {\n      return InFlightRequest{\n        .op = mockStorage->expectCall(\"delete\", ws).withParams(CAPNP(keys = [\"foo\"])),\n      };\n    }, [&](auto req) { kj::mv(req.op).thenReturn(CAPNP(numDeleted = 1)); },\n        {\n          .skipSecondOperation = false,\n        });\n  }\n\n  {\n    // Join in on a simple put with allowUnconfirmed.\n    test.put(\"foo\", \"baz\", ActorCacheWriteOptions{.allowUnconfirmed = true});\n\n    verify(\n        [&]() {\n      return InFlightRequest{\n        .op = mockStorage->expectCall(\"put\", ws).withParams(\n            CAPNP(entries = [(key = \"foo\", value = \"baz\")])),\n      };\n    }, [&](auto req) { kj::mv(req.op).thenReturn(CAPNP()); },\n        {\n          .skipSecondOperation = false,\n        });\n  }\n\n  {\n    // Join in on a delete with allowUnconfirmed.\n    test.delete_(\"foo\", ActorCacheWriteOptions{.allowUnconfirmed = true});\n\n    verify(\n        [&]() {\n      return InFlightRequest{\n        .op = mockStorage->expectCall(\"delete\", ws).withParams(CAPNP(keys = [\"foo\"])),\n      };\n    }, [&](auto req) { kj::mv(req.op).thenReturn(CAPNP(numDeleted = 1)); },\n        {\n          .skipSecondOperation = false,\n        });\n  }\n\n  {\n    // Join in on a scheduled setAlarm.\n    test.setAlarm(1 * kj::MILLISECONDS + kj::UNIX_EPOCH);\n\n    verify(\n        [&]() {\n      return InFlightRequest{\n        .op = mockStorage->expectCall(\"setAlarm\", ws).withParams(CAPNP(scheduledTimeMs = 1)),\n      };\n    }, [&](auto req) { kj::mv(req.op).thenReturn(CAPNP()); },\n        {\n          .skipSecondOperation = false,\n        });\n  }\n\n  {\n    // Join in on a scheduled setAlarm with allowUnconfirmed.\n    test.setAlarm(\n        2 * kj::MILLISECONDS + kj::UNIX_EPOCH, ActorCacheWriteOptions{.allowUnconfirmed = true});\n\n    verify(\n        [&]() {\n      return InFlightRequest{\n        .op = mockStorage->expectCall(\"setAlarm\", ws).withParams(CAPNP(scheduledTimeMs = 2)),\n      };\n    }, [&](auto req) { kj::mv(req.op).thenReturn(CAPNP()); },\n        {\n          .skipSecondOperation = false,\n        });\n  }\n\n  {\n    // Join in on a scheduled deleteAll.\n    test.cache.deleteAll(ActorCacheWriteOptions{.allowUnconfirmed = false}, nullptr);\n\n    verify(\n        [&]() {\n      return InFlightRequest{\n        .op = mockStorage->expectCall(\"deleteAll\", ws).withParams(CAPNP()),\n      };\n    }, [&](auto req) { kj::mv(req.op).thenReturn(CAPNP()); },\n        {\n          // We can't test the second operation because deleteAll immediately follows up with any puts\n          // that happened while it was in flight. This means that we invoke the mock twice in the same\n          // promise chain without being able to set up exceptions in time.\n          .skipSecondOperation = true,\n        });\n  }\n\n  {\n    // Join in on a scheduled deleteAll with allowUnconfirmed.\n    test.cache.deleteAll(ActorCacheWriteOptions{.allowUnconfirmed = true}, nullptr);\n\n    verify(\n        [&]() {\n      return InFlightRequest{\n        .op = mockStorage->expectCall(\"deleteAll\", ws).withParams(CAPNP()),\n      };\n    }, [&](auto req) { kj::mv(req.op).thenReturn(CAPNP()); },\n        {\n          .skipSecondOperation = true,\n        });\n  }\n}\n\nKJ_TEST(\"ActorCache can shutdown\") {\n  // This test confirms that `shutdown()` stops scheduled flushes but does not stop in-flight\n  // flushes. It also confirms that `shutdown()` prevents future operations.\n\n  struct InFlightRequest {\n    MockServer::ExpectedCall op;\n    kj::Promise<void> promise;\n  };\n\n  struct BeforeShutdownResult {\n    kj::Maybe<InFlightRequest> maybeReq;\n    bool shouldBreakOutputGate;\n  };\n\n  struct VerifyOptions {\n    kj::Maybe<const kj::Exception&> maybeError;\n  };\n  auto verifyWithOptions = [&](auto&& beforeShutdown, auto&& afterShutdown, VerifyOptions options) {\n    auto test = ActorCacheTest({.monitorOutputGate = false});\n    auto& ws = test.ws;\n\n    BeforeShutdownResult res = beforeShutdown(test);\n\n    // Shutdown and observe the pending flush to break the io gate.\n    test.cache.shutdown(options.maybeError);\n    auto maybeShutdownPromise = test.cache.onNoPendingFlush(nullptr);\n\n    afterShutdown(test, kj::mv(res.maybeReq));\n\n    auto error = options.maybeError.map([](const kj::Exception& e) {\n      return kj::cp(e);\n    }).orDefault(KJ_EXCEPTION(DISCONNECTED, kj::str(ActorCache::SHUTDOWN_ERROR_MESSAGE)));\n\n    if (res.shouldBreakOutputGate) {\n      // We expected the output gate to break async after shutdown.\n      auto& shutdownPromise = KJ_REQUIRE_NONNULL(maybeShutdownPromise);\n      WD_EXPECT_THROW(error, shutdownPromise.wait(ws));\n      KJ_EXPECT(test.cache.onNoPendingFlush(nullptr) == nullptr);\n      WD_EXPECT_THROW(error, test.gate.wait(nullptr).wait(ws));\n    } else KJ_IF_SOME(promise, maybeShutdownPromise) {\n      // The in-flight flush should resolve cleanly without any follow on or breaking the output\n      // gate.\n      promise.wait(ws);\n      KJ_EXPECT(test.cache.onNoPendingFlush(nullptr) == nullptr);\n      test.gate.wait(nullptr).wait(ws);\n    }\n\n    // Puts and deletes, even with allowedUnconfirmed, should throw.\n    WD_EXPECT_THROW(error, test.put(\"foo\", \"baz\"));\n    WD_EXPECT_THROW(error, test.put(\"foo\", \"bat\", {.allowUnconfirmed = true}));\n    WD_EXPECT_THROW(error, test.delete_(\"foo\"));\n    WD_EXPECT_THROW(error, test.delete_(\"foo\", {.allowUnconfirmed = true}));\n\n    if (!res.shouldBreakOutputGate) {\n      // We tried to use storage after shutdown, we should now be breaking the output gate.\n      auto afterShutdownPromise = KJ_ASSERT_NONNULL(test.cache.onNoPendingFlush(nullptr));\n      WD_EXPECT_THROW(error, afterShutdownPromise.wait(ws));\n      KJ_EXPECT(test.cache.onNoPendingFlush(nullptr) == nullptr);\n      WD_EXPECT_THROW(error, test.gate.wait(nullptr).wait(ws));\n    }\n  };\n\n  auto verify = [&](auto&& beforeShutdown, auto&& afterShutdown) {\n    verifyWithOptions(beforeShutdown, afterShutdown, {.maybeError = kj::none});\n    verifyWithOptions(beforeShutdown, afterShutdown, {.maybeError = KJ_EXCEPTION(FAILED, \"Nope.\")});\n  };\n\n  verify([](ActorCacheTest& test) {\n    // Do nothing and expect nothing!\n    return BeforeShutdownResult{\n      .maybeReq = kj::none,\n      .shouldBreakOutputGate = false,\n    };\n  }, [](ActorCacheTest& test, kj::Maybe<InFlightRequest>) {\n    // Nothing should have made it to storage.\n    test.mockStorage->expectNoActivity(test.ws);\n  });\n\n  verify([](ActorCacheTest& test) {\n    // Do a confirmed put (which schedules a flush).\n    test.put(\"foo\", \"bar\", {.allowUnconfirmed = false});\n\n    // Expect the put to be cancelled and break the gate.\n    return BeforeShutdownResult{\n      .maybeReq = kj::none,\n      .shouldBreakOutputGate = true,\n    };\n  }, [](ActorCacheTest& test, kj::Maybe<InFlightRequest>) {\n    // Nothing should have made it to storage.\n    test.mockStorage->expectNoActivity(test.ws);\n  });\n\n  verify([](ActorCacheTest& test) {\n    // Do an unconfirmed put (which schedules a flush).\n    test.put(\"foo\", \"bar\", {.allowUnconfirmed = true});\n\n    // Expect the put to be cancelled and break the gate.\n    return BeforeShutdownResult{\n      .maybeReq = kj::none,\n      .shouldBreakOutputGate = true,\n    };\n  }, [](ActorCacheTest& test, kj::Maybe<InFlightRequest>) {\n    // Nothing should have made it to storage.\n    test.mockStorage->expectNoActivity(test.ws);\n  });\n\n  verify([](ActorCacheTest& test) {\n    // Do a confirmed put and wait for it to be in-flight.\n    test.put(\"foo\", \"bar\", {.allowUnconfirmed = false});\n\n    auto op = test.mockStorage->expectCall(\"put\", test.ws)\n                  .withParams(CAPNP(entries = [(key = \"foo\", value = \"bar\")]));\n    auto promise = KJ_REQUIRE_NONNULL(test.cache.onNoPendingFlush(nullptr));\n    KJ_EXPECT(!promise.poll(test.ws));\n\n    return BeforeShutdownResult{\n      .maybeReq =\n          InFlightRequest{\n            .op = kj::mv(op),\n            .promise = kj::mv(promise),\n          },\n      .shouldBreakOutputGate = false,\n    };\n  }, [](ActorCacheTest& test, kj::Maybe<InFlightRequest> maybeReq) {\n    // Finish the storage response and wait to see our pre-shutdown in-flight flush finish.\n    auto req = KJ_ASSERT_NONNULL(kj::mv(maybeReq));\n    kj::mv(req.op).thenReturn(CAPNP());\n    req.promise.wait(test.ws);\n\n    // Nothing else should have made it to storage.\n    test.mockStorage->expectNoActivity(test.ws);\n  });\n\n  verify([](ActorCacheTest& test) {\n    // Do an unconfirmed put and wait for it to be in-flight.\n    test.put(\"foo\", \"bar\", {.allowUnconfirmed = true});\n\n    auto op = test.mockStorage->expectCall(\"put\", test.ws)\n                  .withParams(CAPNP(entries = [(key = \"foo\", value = \"bar\")]));\n    auto promise = KJ_REQUIRE_NONNULL(test.cache.onNoPendingFlush(nullptr));\n    KJ_EXPECT(!promise.poll(test.ws));\n\n    return BeforeShutdownResult{\n      .maybeReq =\n          InFlightRequest{\n            .op = kj::mv(op),\n            .promise = kj::mv(promise),\n          },\n      .shouldBreakOutputGate = false,\n    };\n  }, [](ActorCacheTest& test, kj::Maybe<InFlightRequest> maybeReq) {\n    // Finish the storage response and wait to see our pre-shutdown in-flight flush finish.\n    auto req = KJ_ASSERT_NONNULL(kj::mv(maybeReq));\n    kj::mv(req.op).thenReturn(CAPNP());\n    req.promise.wait(test.ws);\n\n    // Nothing else should have made it to storage.\n    test.mockStorage->expectNoActivity(test.ws);\n  });\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/actor-cache.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"actor-cache.h\"\n\n#include <workerd/io/actor-storage.h>\n#include <workerd/io/io-gate.h>\n#include <workerd/jsg/exception.h>\n#include <workerd/util/duration-exceeded-logger.h>\n#include <workerd/util/sentry.h>\n\n#include <kj/debug.h>\n\n#include <algorithm>\n\nnamespace workerd {\n\n// Max size, in words, of a storage RPC request. Set to 16MiB because our storage backend has a\n// hard limit of 16MiB per operation.\n//\n// (Also, at 64MiB we'd hit the Cap'n Proto message size limit.)\n//\n// Note that in practice, the key size limit (options.maxKeysPerRpc) will kick in long before we\n// hit this limit, so this is just a sanity check.\nstatic constexpr size_t MAX_ACTOR_STORAGE_RPC_WORDS = (16u << 20) / sizeof(capnp::word);\n\nconst ActorCache::Hooks ActorCache::Hooks::DEFAULT;\n\nnamespace {\n\n// Utility functions for recording latency metrics via a one-liner in the callers below.\nauto recordStorageRead(ActorCache::Hooks& hooks, const kj::MonotonicClock& clock) {\n  auto start = clock.now();\n  return kj::defer([start, &hooks, &clock]() { hooks.storageReadCompleted(clock.now() - start); });\n}\nauto recordStorageWrite(ActorCache::Hooks& hooks, const kj::MonotonicClock& clock) {\n  auto start = clock.now();\n  return kj::defer([start, &hooks, &clock]() { hooks.storageWriteCompleted(clock.now() - start); });\n}\n\n}  // namespace\n\nActorCache::ActorCache(\n    rpc::ActorStorage::Stage::Client storage, const SharedLru& lru, OutputGate& gate, Hooks& hooks)\n    : storage(kj::mv(storage)),\n      lru(lru),\n      gate(gate),\n      hooks(hooks),\n      clock(kj::systemPreciseMonotonicClock()),\n      currentValues(lru.cleanList.lockExclusive()) {}\n\nActorCache::~ActorCache() noexcept(false) {\n  // Need to remove all entries from any lists they might be in.\n  auto lock = lru.cleanList.lockExclusive();\n  clear(lock);\n}\n\nvoid ActorCache::clear(Lock& lock) {\n  for (auto& entry: currentValues.get(lock)) {\n    removeEntry(lock, *entry);\n  }\n  currentValues.get(lock).clear();\n}\n\nActorCache::Entry::Entry(ActorCache& cache, Key key, Value value)\n    : maybeCache(cache),\n      key(kj::mv(key)),\n      value(kj::mv(value)),\n      valueStatus(EntryValueStatus::PRESENT) {\n  KJ_IF_SOME(c, maybeCache) {\n    c.lru.size.fetch_add(size(), std::memory_order_relaxed);\n  }\n}\n\nActorCache::Entry::Entry(ActorCache& cache, Key key, EntryValueStatus valueStatus)\n    : maybeCache(cache),\n      key(kj::mv(key)),\n      valueStatus(valueStatus) {\n  KJ_IASSERT(valueStatus != EntryValueStatus::PRESENT,\n      \"Pass a serialized empty v8 value if you want a present but empty entry!\");\n  KJ_IF_SOME(c, maybeCache) {\n    c.lru.size.fetch_add(size(), std::memory_order_relaxed);\n  }\n}\n\nActorCache::Entry::Entry(Key key, Value value)\n    : key(kj::mv(key)),\n      value(kj::mv(value)),\n      valueStatus(EntryValueStatus::PRESENT) {}\nActorCache::Entry::Entry(Key key, EntryValueStatus valueStatus)\n    : key(kj::mv(key)),\n      valueStatus(valueStatus) {}\n\nActorCache::Entry::~Entry() noexcept(false) {\n  KJ_IF_SOME(c, maybeCache) {\n    size_t size = this->size();\n\n    size_t before = c.lru.size.fetch_sub(size, std::memory_order_relaxed);\n\n    if (KJ_UNLIKELY(before < size)) {\n      // underflow -- shouldn't happen, but just in case, let's fix\n      KJ_LOG(ERROR, \"SharedLru size tracking inconsistency detected\", before, size,\n          kj::getStackTrace());\n      c.lru.size.store(0, std::memory_order_relaxed);\n    }\n\n    if (link.isLinked()) {\n      switch (getSyncStatus()) {\n        case EntrySyncStatus::CLEAN: {\n          KJ_LOG(WARNING, \"Entry destructed while still in the clean list\");\n          break;\n        }\n        case EntrySyncStatus::DIRTY: {\n          // Ah, we don't need a lock so we can just unlink ourselves. This is safe because we will\n          // only destruct a DIRTY entry on the actor's event loop. (We can destruct a CLEAN entry\n          // as part of evicting entries from the shared lru on a different event loop.)\n          c.dirtyList.remove(*this);\n          break;\n        }\n        case EntrySyncStatus::NOT_IN_CACHE: {\n          KJ_LOG(WARNING, \"Entry with sync status NOT_IN_CACHE still in a list\");\n          break;\n        }\n      }\n    }\n  }\n}\n\nActorCache::SharedLru::SharedLru(Options options): options(options) {}\n\nActorCache::SharedLru::~SharedLru() noexcept(false) {\n  KJ_REQUIRE(cleanList.getWithoutLock().empty(),\n      \"ActorCache::SharedLru destroyed while an ActorCache still exists?\");\n  if (size.load(std::memory_order_relaxed) != 0) {\n    KJ_LOG(ERROR,\n        \"SharedLru destroyed while cache entries still exist, \"\n        \"this will lead to use-after-free\");\n  }\n}\n\nkj::Maybe<kj::Promise<void>> ActorCache::evictStale(kj::Date now) {\n  int64_t nowNs = (now - kj::UNIX_EPOCH) / kj::NANOSECONDS;\n  int64_t oldValue = lru.nextStaleCheckNs.load(std::memory_order_relaxed);\n\n  if (nowNs >= oldValue) {\n    int64_t newValue = nowNs + lru.options.staleTimeout / kj::NANOSECONDS;\n    if (lru.nextStaleCheckNs.compare_exchange_strong(oldValue, newValue)) {\n      auto lock = lru.cleanList.lockExclusive();\n      for (auto& entry: *lock) {\n        if (entry.isStale) {\n          auto& cache = KJ_ASSERT_NONNULL(entry.maybeCache);\n          cache.removeEntry(lock, entry);\n          cache.evictEntry(lock, entry);\n        } else {\n          entry.isStale = true;\n        }\n      }\n    }\n  }\n\n  // Apply backpressure if we're over the soft limit.\n  return getBackpressure();\n}\n\nkj::OneOf<ActorCache::CancelAlarmHandler, ActorCache::RunAlarmHandler> ActorCache::armAlarmHandler(\n    kj::Date scheduledTime,\n    SpanParent parentSpan,\n    kj::Date currentTime KJ_UNUSED,\n    bool noCache,\n    kj::StringPtr actorId) {\n  noCache = noCache || lru.options.noCache;\n\n  KJ_ASSERT(!currentAlarmTime.is<DeferredAlarmDelete>());\n  bool alarmDeleteNeeded = true;\n  KJ_IF_SOME(t, currentAlarmTime.tryGet<KnownAlarmTime>()) {\n    if (t.time != scheduledTime) {\n      if (t.status == KnownAlarmTime::Status::CLEAN) {\n        // If there's a clean scheduledTime that is different from ours, this run should be\n        // canceled.\n        LOG_WARNING_PERIODICALLY(\"NOSENTRY CRDB alarm handler canceled.\", scheduledTime,\n            t.time.orDefault(kj::UNIX_EPOCH), actorId);\n        return CancelAlarmHandler{.waitBeforeCancel = kj::READY_NOW};\n      } else {\n        // There's a alarm write that hasn't been set yet pending for a time different than ours --\n        // We won't cancel the alarm because it hasn't been confirmed, but we shouldn't delete\n        // the pending write.\n        alarmDeleteNeeded = false;\n      }\n    }\n  }\n\n  if (alarmDeleteNeeded) {\n    currentAlarmTime = DeferredAlarmDelete{\n      .status = DeferredAlarmDelete::Status::WAITING,\n      .timeToDelete = scheduledTime,\n      .noCache = noCache,\n      .traceSpan = kj::mv(parentSpan),\n    };\n  }\n  static const DeferredAlarmDeleter disposer;\n  return RunAlarmHandler{.deferredDelete = kj::Own<void>(this, disposer)};\n}\n\nvoid ActorCache::cancelDeferredAlarmDeletion() {\n  KJ_IF_SOME(deferredDelete, currentAlarmTime.tryGet<DeferredAlarmDelete>()) {\n    currentAlarmTime = KnownAlarmTime{.status = KnownAlarmTime::Status::CLEAN,\n      .time = deferredDelete.timeToDelete,\n      .noCache = deferredDelete.noCache};\n  }\n}\n\nkj::Maybe<kj::Promise<void>> ActorCache::getBackpressure() {\n  if (dirtyList.sizeInBytes() > lru.options.dirtyListByteLimit && !lru.options.neverFlush) {\n    // Wait for dirty entries to be flushed.\n    return lastFlush.addBranch().then([this]() -> kj::Promise<void> {\n      KJ_IF_SOME(p, getBackpressure()) {\n        return kj::mv(p);\n      } else {\n        return kj::READY_NOW;\n      }\n    });\n  }\n\n  // At one point, we tried applying backpressure if the total cache size was greater than\n  // `softLimit`. This turned out to be a bad idea. If the cache is over the limit due to dirty\n  // entries waiting to be flushed, then `dirtyListByteLimit` will actually kick in first (since\n  // it's by default 8MB of data). So if the cache is over the soft limit (which is typically more\n  // like 16MB), it could only be because a very large read operation has loaded a bunch of entries\n  // into memory but hasn't delivered them to the app yet. In this case, if we apply backpressure,\n  // then the app cannot make progress and therefore cannot receive the result of these reads! So it\n  // will just deadlock.\n  //\n  // Hence, it only makes sense to wait for dirty entries to be flushed, not to wait for overall\n  // size to go down.\n  return kj::none;\n}\n\nvoid ActorCache::requireNotTerminal(SpanParent traceSpan) {\n  KJ_IF_SOME(e, maybeTerminalException) {\n    if (!gate.isBroken()) {\n      // We've tried to use storage after shutdown, break the output gate via `flushImpl()` so that\n      // we don't let the worker return stale state. This isn't strictly necessary but it does\n      // mirror previous behavior wherein we would use disabled storage via `flushImpl()` and break\n      // the output gate.\n      ensureFlushScheduled({}, kj::mv(traceSpan));\n    }\n\n    kj::throwFatalException(kj::cp(e));\n  }\n}\n\nvoid ActorCache::evictOrOomIfNeeded(Lock& lock) {\n  if (lru.evictIfNeeded(lock)) {\n    auto exception = KJ_EXCEPTION(OVERLOADED,\n        \"broken.exceededMemory; jsg.Error: Durable Object's isolate exceeded its memory limit due to overflowing the \"\n        \"storage cache. This could be due to writing too many values to storage without stopping \"\n        \"to wait for writes to complete, or due to reading too many values in a single operation \"\n        \"(e.g. a large list()). All objects in the isolate were reset.\");\n\n    // Add trace info sufficient to tell us which operation caused the failure.\n    exception.addTraceHere();\n    exception.addTrace(__builtin_return_address(0));\n    // We know this exception happens due to user error. Let's add an exception detail so we can\n    // parse it later.\n    exception.setDetail(jsg::EXCEPTION_IS_USER_ERROR, kj::heapArray<byte>(0));\n\n    if (maybeTerminalException == kj::none) {\n      maybeTerminalException.emplace(kj::cp(exception));\n    } else {\n      // We've already experienced a terminal exception either from shutdown or OOM. Note that we\n      // still schedule the flush since shutdown does not.\n    }\n\n    clear(lock);\n    oomCanceler.cancel(exception);\n\n    if (!gate.isBroken()) {\n      // We want to break the OutputGate. We can't quite just do `gate.lockWhile(exception)` because\n      // that returns a promise which we'd then have to put somewhere so that we don't immediately\n      // cancel it. Instead, we can ensure that a flush has been scheduled. `flushImpl()`, when\n      // called, will throw an exception which breaks the gate.\n      ensureFlushScheduled(WriteOptions(), nullptr);\n    }\n\n    kj::throwFatalException(kj::mv(exception));\n  }\n}\n\nbool ActorCache::SharedLru::evictIfNeeded(Lock& lock) const {\n  for (;;) {\n    size_t current = size.load(std::memory_order_relaxed);\n    if (current <= options.softLimit) {\n      // All good.\n      return false;\n    }\n\n    // We're over the limit, let's evict stuff.\n    if (lock->empty()) {\n      // Nothing to evict.\n      return current > options.hardLimit;\n    }\n\n    Entry& entry = lock->front();\n    auto& cache = KJ_ASSERT_NONNULL(entry.maybeCache);\n    cache.removeEntry(lock, entry);\n    cache.evictEntry(lock, entry);\n  }\n}\n\nvoid ActorCache::touchEntry(Lock& lock, Entry& entry) {\n  if (entry.getSyncStatus() == EntrySyncStatus::CLEAN) {\n    entry.isStale = false;\n    lock->remove(entry);\n    addToCleanList(lock, entry);\n  }\n\n  // We only call `touchEntry` when the operation or the LRU has !noCache, so we want to cache this.\n  //\n  // If this is a dirty entry previously marked no-cache, remove that mark. This results in the\n  // same end state as if the entry had been flushed and evicted before the read -- it would have\n  // been read back, and then into cache.\n  entry.noCache = false;\n}\n\nvoid ActorCache::removeEntry(Lock& lock, Entry& entry) {\n  switch (entry.getSyncStatus()) {\n    case EntrySyncStatus::DIRTY: {\n      dirtyList.remove(entry);\n      break;\n    }\n    case EntrySyncStatus::CLEAN: {\n      lock->remove(entry);\n      break;\n    }\n    case EntrySyncStatus::NOT_IN_CACHE: {\n      // Nothing to do!\n      break;\n    }\n  }\n\n  entry.setNotInCache();\n}\n\nvoid ActorCache::evictEntry(Lock& lock, Entry& entry) {\n  auto& map = currentValues.get(lock);\n  auto ordered = map.ordered();\n  auto iter = map.seek(entry.key);\n\n  KJ_ASSERT(iter != ordered.end() && iter->get() == &entry);\n\n  // If the previous entry has gapIsKnownEmpty, we need to set that false, because when we delete\n  // this entry, the previous entry's \"gap\" will now extend to the *next* entry. We definitely know\n  // that that the new gap is non-empty because we're evicting an entry inside that very gap.\n  //\n  // TODO(perf): Maybe we should instead replace the evicted entry with an UNKNOWN entry in this\n  //   case? The problem is, when the app accesses a key in the gap, the LRU time of the previous\n  //   entry gets bumped, but the _next_ entry does not get bumped. Hence these accesses won't\n  //   prevent the next entry from being evicted, and when it is, the gap effectively gets evicted\n  //   too, leading to a cache miss on a key that had been recently accessed. This is a pretty\n  //   obscure scenario, though, and after one cache miss the key would then be in cache again.\n  if (iter != ordered.begin()) {\n    auto prev = iter;\n    --prev;\n    prev->get()->gapIsKnownEmpty = false;\n  }\n\n  map.erase(*iter);\n}\n\nvoid ActorCache::verifyConsistencyForTest() {\n  auto lock = lru.cleanList.lockExclusive();\n  currentValues.get(lock).verify();  // verify the table's BTreeIndex\n  bool prevGapIsKnownEmpty = false;\n  kj::Maybe<kj::StringPtr> prevKey = kj::none;\n  for (auto& entry: currentValues.get(lock).ordered()) {\n    KJ_IF_SOME(p, prevKey) {\n      KJ_ASSERT(entry->key > p, \"keys out of order?\", p, entry->key);\n    }\n    prevKey = entry->key;\n    auto& key = entry->key;\n    switch (entry->getValueStatus()) {\n      case EntryValueStatus::ABSENT: {\n        KJ_ASSERT(!prevGapIsKnownEmpty || !entry->gapIsKnownEmpty,\n            \"clean negative entry in the middle of a known-empty gap is redundant\", key);\n        break;\n      }\n      case EntryValueStatus::PRESENT: {\n        // Nothing to do for PRESENT!\n        break;\n      }\n      case EntryValueStatus::UNKNOWN: {\n        KJ_ASSERT(!entry->gapIsKnownEmpty, \"entry can't be followed by known-empty gap\", key);\n        break;\n      }\n    }\n\n    KJ_ASSERT(entry->getSyncStatus() != EntrySyncStatus::NOT_IN_CACHE,\n        \"entry should not appear in map\", entry->key);\n    KJ_ASSERT(entry->link.isLinked());\n\n    prevGapIsKnownEmpty = entry->gapIsKnownEmpty;\n  }\n}\n\n// =======================================================================================\n// read operations\n\nkj::OneOf<kj::Maybe<ActorCache::Value>, kj::Promise<kj::Maybe<ActorCache::Value>>> ActorCache::get(\n    Key key, ReadOptions options) {\n  ActorStorageLimits::checkMaxKeySize(key);\n\n  options.noCache = options.noCache || lru.options.noCache;\n  requireNotTerminal(nullptr);\n\n  auto lock = lru.cleanList.lockExclusive();\n  auto entry = findInCache(lock, kj::mv(key), options);\n  switch (entry->getValueStatus()) {\n    case EntryValueStatus::PRESENT:\n    case EntryValueStatus::ABSENT: {\n      return entry->getValue();\n    }\n    case EntryValueStatus::UNKNOWN: {\n      return getImpl(kj::mv(entry), options);\n    }\n  }\n}\n\nauto ActorCache::getImpl(\n    kj::Own<Entry> entry, ReadOptions options) -> kj::Promise<kj::Maybe<Value>> {\n  auto response = co_await scheduleStorageRead(\n      [key = entry->key.asBytes()](rpc::ActorStorage::Operations::Client client) {\n    auto req = client.getRequest(capnp::MessageSize{4 + key.size() / sizeof(capnp::word), 0});\n    req.setKey(key);\n    return req.send().dropPipeline();\n  });\n\n  kj::Maybe<capnp::Data::Reader> value;\n  if (response.hasValue()) {\n    value = response.getValue();\n  }\n  auto lock = lru.cleanList.lockExclusive();\n  auto newEntry = addReadResultToCache(lock, cloneKey(entry->key), value, options);\n  evictOrOomIfNeeded(lock);\n  co_return newEntry->getValue();\n}\n\nclass ActorCache::GetMultiStreamImpl final: public rpc::ActorStorage::ListStream::Server {\n public:\n  GetMultiStreamImpl(ActorCache& cache,\n      kj::Vector<kj::Own<Entry>> cachedEntries,\n      kj::Vector<Key> keysToFetchParam,\n      kj::Own<kj::PromiseFulfiller<GetResultList>> fulfiller,\n      const ReadOptions& options)\n      : cache(cache),\n        cachedEntries(kj::mv(cachedEntries)),\n        keysToFetch(kj::mv(keysToFetchParam)),\n        nextExpectedKey(keysToFetch.begin()),\n        fulfiller(kj::mv(fulfiller)),\n        options(options) {}\n\n  kj::Promise<void> values(ValuesContext context) override {\n    if (!fulfiller->isWaiting()) {\n      // The original caller stopped listening. Try to cancel the stream by throwing.\n      return KJ_EXCEPTION(DISCONNECTED, \"canceled\");\n    }\n\n    auto lock = cache.lru.cleanList.lockExclusive();\n    auto params = context.getParams();\n    kj::String prevKey;\n    for (auto kv: params.getList()) {\n      KJ_ASSERT(kv.hasValue());  // values that don't exist aren't listed!\n      KJ_ASSERT(nextExpectedKey != keysToFetch.end());\n\n      // TODO(perf): This copy of the key is not really needed, we use the key from `keysToFetch`\n      //   instead. But the capnp representation is a byte array which isn't null-terminated\n      //   which would make the code difficult below.\n      auto key = kj::str(kv.getKey().asChars());\n\n      KJ_ASSERT(key >= prevKey, \"storage returned keys in non-sorted order?\");\n\n      // Find matching key in keysToFetch, possibly marking missing keys as absent.\n      for (;;) {\n        if (nextExpectedKey == keysToFetch.end() || key < *nextExpectedKey) {\n          // This may be a duplicate due to a retry. Ignore it.\n          break;\n        } else if (key == *nextExpectedKey) {\n          fetchedEntries.add(\n              cache.addReadResultToCache(lock, kj::mv(*nextExpectedKey), kv.getValue(), options));\n          ++nextExpectedKey;\n          break;\n        }\n\n        // It seems the list results have moved past `nextExpectedKey`, meaning it wasn't present\n        // on disk. Write a negative cache entry.\n        cache.addReadResultToCache(lock, kj::mv(*nextExpectedKey), kj::none, options);\n        ++nextExpectedKey;\n      }\n\n      if (nextExpectedKey == keysToFetch.end()) {\n        fulfill();\n      }\n\n      prevKey = kj::mv(key);\n    }\n    cache.evictOrOomIfNeeded(lock);\n    return kj::READY_NOW;\n  }\n\n  kj::Promise<void> end(EndContext context) override {\n    if (!fulfiller->isWaiting()) {\n      // Just ignore end() if we've already stopped waiting.\n      return kj::READY_NOW;\n    }\n\n    if (nextExpectedKey < keysToFetch.end()) {\n      // Some trailing keys weren't seen, better mark them as not present.\n      auto lock = cache.lru.cleanList.lockExclusive();\n      while (nextExpectedKey < keysToFetch.end()) {\n        cache.addReadResultToCache(lock, kj::mv(*nextExpectedKey++), kj::none, options);\n      }\n      cache.evictOrOomIfNeeded(lock);\n    }\n\n    fulfill();\n\n    return kj::READY_NOW;\n  }\n\n  void fulfill() {\n    // We return results in sorted order. You might argue that it could make sense to return\n    // results in the same order as the keys were originally specified. Even though we return\n    // a `Map` in JavaScript, the iteration order of a `Map` is defined to be the order of\n    // insertion, therefore the order in which we return results here is actually observable by\n    // the application. Trying to match the input order, however, almost certainly wouldn't be\n    // useful to apps. The only plausible way it could be useful is if the app could do e.g.\n    // `[...map.values()]` and end up with an array of values that exactly corresponds to the\n    // input array of keys. However, it won't exactly correspond for two reasons:\n    // - Keys that weren't present on disk aren't listed at all. To meaningfully change this,\n    //   we would need to say that the Map object returned to JavaScript would contain entries\n    //   even for missing keys, where the value is explicitly set to `undefined`. However,\n    //   changing that would be a breaking change.\n    // - Keys that were listed twice in the input list won't be reported twice. This is an\n    //   inherent limitation of the fact that we return a `Map`.\n    //\n    // Hence, applications that tried to depend on this ordering would be shooting themselves\n    // in the foot. We do, however, want to produce a consistent ordering for reproducibility's\n    // sake, but any consistent ordering will due. Sorted order is as good as anything else, and\n    // happens to be nice and easy for us.\n    fulfiller->fulfill(\n        GetResultList(kj::mv(cachedEntries), kj::mv(fetchedEntries), GetResultList::FORWARD));\n  }\n\n  // Indicates that the operation is being canceled. Proactively drops all entries. This\n  // is important because the destructor of an `Entry` updates the cache's accounting of memory\n  // usage, so it's important that an `Entry` cannot be held beyond the lifetime of the cache\n  // itself.\n  void cancel() {\n    KJ_ASSERT(!fulfiller->isWaiting());  // proves further RPCs will be ignored\n    cachedEntries.clear();\n    fetchedEntries.clear();\n  }\n\n  ActorCache& cache;\n  kj::Vector<kj::Own<Entry>> cachedEntries;\n  kj::Vector<kj::Own<Entry>> fetchedEntries;\n  kj::Vector<Key> keysToFetch;\n  Key* nextExpectedKey;\n  kj::Own<kj::PromiseFulfiller<GetResultList>> fulfiller;\n  ReadOptions options;\n};\n\nkj::OneOf<ActorCache::GetResultList, kj::Promise<ActorCache::GetResultList>> ActorCache::get(\n    kj::Array<Key> keys, ReadOptions options) {\n  ActorStorageLimits::checkMaxPairsCount(keys.size());\n\n  options.noCache = options.noCache || lru.options.noCache;\n  requireNotTerminal(nullptr);\n\n  std::sort(keys.begin(), keys.end());\n\n  kj::Vector<kj::Own<Entry>> cachedEntries(keys.size());\n  // Entries satisfying the requested keys.\n\n  kj::Vector<Key> keysToFetch(keys.size());\n  // Keys that were not satisfied from cache.\n\n  capnp::MessageSize sizeHint{4, 1};\n\n  {\n    auto lock = lru.cleanList.lockExclusive();\n    for (auto& key: keys) {\n      auto entry = findInCache(lock, key, options);\n      switch (entry->getValueStatus()) {\n        case EntryValueStatus::PRESENT:\n        case EntryValueStatus::ABSENT: {\n          cachedEntries.add(kj::mv(entry));\n          break;\n        }\n        case EntryValueStatus::UNKNOWN: {\n          // +1 word for padding, +1 word for the pointer in the key list.\n          sizeHint.wordCount += key.size() / sizeof(capnp::word) + 2;\n          keysToFetch.add(kj::mv(key));\n        }\n      }\n    }\n  }\n\n  if (keysToFetch.empty()) {\n    // All satisfied, return early.\n    return GetResultList(kj::mv(cachedEntries), {}, GetResultList::FORWARD);\n  }\n\n  auto paf = kj::newPromiseAndFulfiller<GetResultList>();\n  auto streamServer = kj::heap<GetMultiStreamImpl>(\n      *this, kj::mv(cachedEntries), kj::mv(keysToFetch), kj::mv(paf.fulfiller), options);\n  auto& streamServerRef = *streamServer;\n\n  rpc::ActorStorage::ListStream::Client streamClient = kj::mv(streamServer);\n\n  auto sendPromise = scheduleStorageRead(\n      [sizeHint, streamClient, &streamServerRef](\n          rpc::ActorStorage::Operations::Client client) mutable -> kj::Promise<void> {\n    if (streamServerRef.nextExpectedKey == streamServerRef.keysToFetch.end()) {\n      // No more keys expected, must have finished listing on a previous try.\n      return kj::READY_NOW;\n    }\n    auto req = client.getMultipleRequest(sizeHint);\n    auto keysToFetch =\n        kj::arrayPtr(streamServerRef.nextExpectedKey, streamServerRef.keysToFetch.end());\n    auto list = req.initKeys(keysToFetch.size());\n    for (auto i: kj::indices(keysToFetch)) {\n      list.set(i, keysToFetch[i].asBytes());\n    }\n    req.setStream(streamClient);\n    return req.sendIgnoringResult();\n  });\n\n  // Wait on the RPC only until stream.end() is called, then report the results. We prevent\n  // `stream` from being destroyed until we have a result so that if the RPC throws an exception,\n  // we don't accidentally report \"PromiseFulfiller not fulfilled\" instead of the exception.\n  auto promise = sendPromise.then([&streamServerRef]() -> kj::Promise<ActorCache::GetResultList> {\n    if (streamServerRef.fulfiller->isWaiting()) {\n      return KJ_EXCEPTION(FAILED, \"getMultiple() never called stream.end()\");\n    } else {\n      // We'll be canceled momentarily...\n      return kj::NEVER_DONE;\n    }\n  });\n  return paf.promise.exclusiveJoin(kj::mv(promise))\n      .attach(kj::defer(\n          [client = kj::mv(streamClient), &streamServerRef]() { streamServerRef.cancel(); }));\n}\n\nkj::OneOf<kj::Maybe<kj::Date>, kj::Promise<kj::Maybe<kj::Date>>> ActorCache::getAlarm(\n    ReadOptions options) {\n  options.noCache = options.noCache || lru.options.noCache;\n\n  // If in cache return time\n  // Else schedule alarm read\n  KJ_SWITCH_ONEOF(currentAlarmTime) {\n    KJ_CASE_ONEOF(entry, ActorCache::DeferredAlarmDelete) {\n      // An alarm handler is currently running, and a new alarm time has not been set yet.\n      // We need to return that there is no alarm.\n      return kj::Maybe<kj::Date>(kj::none);\n    }\n    KJ_CASE_ONEOF(entry, ActorCache::KnownAlarmTime) {\n      return entry.time;\n    }\n    KJ_CASE_ONEOF(_, ActorCache::UnknownAlarmTime) {\n      return scheduleStorageRead([](rpc::ActorStorage::Operations::Client client) {\n        auto req = client.getAlarmRequest();\n        return req.send().dropPipeline();\n      })\n          .then([this, options](capnp::Response<rpc::ActorStorage::Operations::GetAlarmResults>\n                        response) mutable -> kj::Maybe<kj::Date> {\n        auto scheduledTimeMs = response.getScheduledTimeMs();\n        auto result = [&]() -> kj::Maybe<kj::Date> {\n          if (scheduledTimeMs == 0) {\n            return kj::none;\n          } else {\n            return scheduledTimeMs * kj::MILLISECONDS + kj::UNIX_EPOCH;\n          }\n        }();\n\n        if (!options.noCache && currentAlarmTime.is<UnknownAlarmTime>()) {\n          // If we don't end up in this branch, the time that's already in currentAlarmTime must\n          // be at least as fresh as the one we just read.\n          //\n          // If it was created by a setAlarm(), then it is actually fresher. If it was created\n          // by a concurrent getAlarm(), then it should be exactly the same time.\n\n          currentAlarmTime =\n              ActorCache::KnownAlarmTime{ActorCache::KnownAlarmTime::Status::CLEAN, result};\n        }\n\n        return result;\n      });\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\n// -----------------------------------------------------------------------------\n\nnamespace {\n// To simplify the handling of Maybe<Key> representing the end point of a list range, we define\n// these operators to allow comparison between a Key and a Maybe<Key>, where a null Maybe<Key>\n// sorts after all other keys.\n\ninline bool operator==(const ActorCache::Key& a, const kj::Maybe<ActorCache::Key>& b) {\n  KJ_IF_SOME(bb, b) {\n    return a == bb;\n  } else {\n    return false;\n  }\n}\ninline bool operator<(const ActorCache::Key& a, const kj::Maybe<ActorCache::Key>& b) {\n  KJ_IF_SOME(bb, b) {\n    return a < bb;\n  } else {\n    return true;\n  }\n}\ninline bool operator>=(const ActorCache::Key& a, const kj::Maybe<ActorCache::Key>& b) {\n  KJ_IF_SOME(bb, b) {\n    return a >= bb;\n  } else {\n    return false;\n  }\n}\ninline bool operator>(const ActorCache::Key& a, const kj::Maybe<ActorCache::KeyPtr>& b) {\n  KJ_IF_SOME(bb, b) {\n    return a > bb;\n  } else {\n    return false;\n  }\n}\n\ninline auto seekOrEnd(auto& map, kj::Maybe<ActorCache::KeyPtr> key) {\n  KJ_IF_SOME(k, key) {\n    return map.seek(k);\n  } else {\n    return map.ordered().end();\n  }\n}\n\n}  // namespace\n\nclass ActorCache::ForwardListStreamImpl final: public rpc::ActorStorage::ListStream::Server {\n public:\n  ForwardListStreamImpl(ActorCache& cache,\n      Key beginKey,\n      kj::Maybe<Key> endKey,\n      kj::Vector<kj::Own<Entry>> cachedEntries,\n      kj::Own<kj::PromiseFulfiller<GetResultList>> fulfiller,\n      kj::Maybe<uint> originalLimit,\n      kj::Maybe<uint> adjustedLimit,\n      bool beginKeyIsKnown,\n      const ReadOptions& options)\n      : cache(cache),\n        beginKey(kj::mv(beginKey)),\n        endKey(kj::mv(endKey)),\n        cachedEntries(kj::mv(cachedEntries)),\n        fulfiller(kj::mv(fulfiller)),\n        originalLimit(originalLimit),\n        adjustedLimit(adjustedLimit),\n        beginKeyIsKnown(beginKeyIsKnown),\n        options(options) {}\n\n  kj::Promise<void> values(ValuesContext context) override {\n    if (!fulfiller->isWaiting()) {\n      // The original caller stopped listening. Try to cancel the stream by throwing.\n      return KJ_EXCEPTION(DISCONNECTED, \"canceled\");\n    }\n\n    {\n      auto lock = cache.lru.cleanList.lockExclusive();\n      auto list = context.getParams().getList();\n\n      bool insertedAny = false;\n\n      for (auto kv: list) {\n        Key key = kj::str(kv.getKey().asChars());\n\n        if (!beginKeyIsKnown) {\n          if (key != beginKey) {\n            // This is the first set of results we've received, and it does not include the start\n            // point of the list. Therefore, we should insert an entry with a null value, to make\n            // sure the whole range can be marked as empty. We'll end up marking this entry as\n            // part of markGapsEmpty(), later.\n            markBeginAsEmpty(lock);\n          }\n        } else {\n          if (key <= beginKey) {\n            // Out-of-order result. This is probably the result of restarting the list operation\n            // due to a disconnect. We assume this is actually a duplicate of a result we\n            // received earlier. Ignore it.\n            continue;\n          }\n        }\n\n        KJ_ASSERT(kv.hasValue());  // values that don't exist aren't listed!\n        auto entry = cache.addReadResultToCache(lock, kj::mv(key), kv.getValue(), options);\n        fetchedEntries.add(kj::mv(entry));\n        insertedAny = true;\n      }\n\n      if (insertedAny) {\n        // Update `gapIsKnownEmpty` on the whole range.\n        cache.markGapsEmpty(lock, beginKey, fetchedEntries.back()->key.asPtr(), options);\n        beginKey = cloneKey(fetchedEntries.back()->key);\n        beginKeyIsKnown = true;\n      }\n\n      cache.evictOrOomIfNeeded(lock);\n    }\n\n    if (fetchedEntries.size() >= adjustedLimit.orDefault(kj::maxValue)) {\n      // Oh we're already done.\n      fulfill();\n    }\n    return kj::READY_NOW;\n  }\n\n  kj::Promise<void> end(EndContext context) override {\n    if (!fulfiller->isWaiting()) {\n      // Just ignore end() if we've already stopped waiting. In particular this happens in\n      // limit requests that reach the limit -- the last call to values() will have already\n      // fulfilled the fulfiller.\n      return kj::READY_NOW;\n    }\n\n    // Mark the rest of the range as empty.\n    {\n      auto lock = cache.lru.cleanList.lockExclusive();\n\n      if (!beginKeyIsKnown) {\n        // We received no results at all, so the start of the list is definitely not in storage.\n        markBeginAsEmpty(lock);\n      }\n\n      if (fetchedEntries.size() < adjustedLimit.orDefault(kj::maxValue)) {\n        // We didn't reach the limit, so the rest of the range must be empty.\n        cache.markGapsEmpty(lock, beginKey, endKey, options);\n      }\n\n      cache.evictOrOomIfNeeded(lock);\n    }\n\n    fulfill();\n\n    return kj::READY_NOW;\n  }\n\n  void fulfill() {\n    fulfiller->fulfill(GetResultList(\n        kj::mv(cachedEntries), kj::mv(fetchedEntries), GetResultList::FORWARD, originalLimit));\n  };\n\n  // Mark the start of the list operation will a null entry, because we did not see it listed.\n  //\n  // Note that this insertion attempt will be ignored in two cases:\n  // 1. An entry already exists with this key, perhaps as the result of a put(). This is\n  //    fine, because the existing entry means we have something to mark.\n  // 2. The entry doesn't exist, but the previous entry has `gapIsKnownEmpty = true`, and\n  //    so the insertion of a new null entry is ignored for being redundant. This case is\n  //    fine too, as the gap is already marked. Our markGapsEmpty() call will start with the\n  //    following entry.\n  void markBeginAsEmpty(Lock& lock) {\n    cache.addReadResultToCache(lock, cloneKey(beginKey), kj::none, options);\n  }\n\n  // Indicates that the operation is being canceled. Proactively drops all entries. This\n  // is important because the destructor of an `Entry` updates the cache's accounting of memory\n  // usage, so it's important that an `Entry` cannot be held beyond the lifetime of the cache\n  // itself.\n  void cancel() {\n    KJ_ASSERT(!fulfiller->isWaiting());  // proves further RPCs will be ignored\n    cachedEntries.clear();\n    fetchedEntries.clear();\n  }\n\n  ActorCache& cache;\n\n  // Either:\n  // - No prefix of the list is known yet, and `beginKey` is the original begin point passed to\n  //   list().\n  // - Some prefix is already satisfied, either from cache or from a previous batch of results\n  //   streamed from storage, and `beginKey` is the key of the last known entry in this prefix.\n  Key beginKey;\n\n  // The end of the list range, as originally passed to list().\n  kj::Maybe<Key> endKey;\n\n  // Entries we gathered from cache.\n  kj::Vector<kj::Own<Entry>> cachedEntries;\n\n  // Entries that have streamed in from disk.\n  kj::Vector<kj::Own<Entry>> fetchedEntries;\n\n  // Fulfiller for the final results.\n  kj::Own<kj::PromiseFulfiller<GetResultList>> fulfiller;\n\n  // The original requested limit, if any.\n  kj::Maybe<uint> originalLimit;\n\n  // The limit we sent to storage.\n  kj::Maybe<uint> adjustedLimit;\n\n  // Does `beginKey` point to a key where we already know the associated value? This is\n  // especially true when `beginKey` points to the last entry of a previous batch received via\n  // a call to `values()`.\n  bool beginKeyIsKnown;\n\n  ReadOptions options;\n};\n\nkj::OneOf<ActorCache::GetResultList, kj::Promise<ActorCache::GetResultList>> ActorCache::list(\n    Key beginKey, kj::Maybe<Key> endKey, kj::Maybe<uint> limit, ReadOptions options) {\n  options.noCache = options.noCache || lru.options.noCache;\n  requireNotTerminal(nullptr);\n\n  // We start by scanning the cache for entries satisfying the list range. If we can fully satisfy\n  // the list using these, then we're done! Otherwise, we make a storage request to get the rest.\n  // When the storage request produces results, we must discard any that conflict with what was\n  // in cache before hand, since what's in cache could have come from a put() that wasn't flushed\n  // yet. However, we need to be careful NOT to use any entries that were put() *after* the list()\n  // operation started.\n\n  kj::Vector<kj::Own<Entry>> cachedEntries;\n  size_t positiveCount = 0;  // number of positive entries in `cachedEntries`\n  if (limit.orDefault(kj::maxValue) == 0 || beginKey >= endKey) {\n    // No results in these cases, just return.\n    return ActorCache::GetResultList(kj::mv(cachedEntries), {}, GetResultList::FORWARD);\n  }\n\n  uint limitAdjustment = 0;\n  // When requesting to storage, we need to adjust the limit to increase it by the number of cached\n  // negative entries in the range, since each of those negative entries could potentially negate a\n  // positive entry read from disk.\n\n  auto lock = lru.cleanList.lockExclusive();\n  auto& map = currentValues.get(lock);\n  auto ordered = map.ordered();\n\n  kj::Maybe<KeyPtr> storageListStart;\n  // If we must do a storage operation, what key shall it start at?\n  //\n  // Note that we never do more than one storage operation, even if we have a patchwork of cache\n  // entries matching different subsets of the list. Trying to split the operation into multiple\n  // smaller list operations to avoid re-listing things we already know seems like too much work to\n  // be worth it. So, we only track the first key which we know needs to be listed, and then we\n  // list the rest of the space from there.\n\n  bool storageListStartIsKnown = false;\n  // Does `storageListStart` point to a key for which we already know the value? If so we can\n  // avoid listing that key specifically.\n\n  uint knownPrefixSize = 0;\n  // How many keys were matched from cache before (and not including) `storageListStart`? We will\n  // use this to reduce the `limit` we pass in the storage op (if there is one).\n\n  // Let's iterate over the cache starting from `beginKey`.\n  auto iter = map.seek(beginKey);\n\n  // We need some special logic to handle the starting point with regard to gaps.\n  if (iter != ordered.end() && iter->get()->key == beginKey) {\n    // There is an entry specifically for `beginKey`, so we'll start there.\n  } else {\n    // `beginKey` does not match an entry, but we can check if it is in a known-empty gap.\n    if (iter == ordered.begin()) {\n      // No, because there is no previous entry. Oh well. We will have to start the storage list\n      // from `beginKey`.\n      storageListStart = beginKey;\n      storageListStartIsKnown = false;\n    } else {\n      // There is a previous key in cache, let's take a look.\n      auto prev = iter;\n      --prev;\n      if (prev->get()->gapIsKnownEmpty) {\n        // `beginKey` is in a known-empty gap, so we know that this key simply doesn't exist in\n        // storage.\n      } else {\n        // We don't know if `beginKey` exists in storage so we'll have to start the storage list\n        // there.\n        storageListStart = beginKey;\n        storageListStartIsKnown = false;\n      }\n    }\n  }\n\n  // Now we can start scanning normally. We need to scan entries within the list range to build\n  // a list of possible results, as well as to determine whether we need to do a storage request.\n  // Even if we end up having to go to disk to find more data, we don't need to scan more than\n  // `limit` entries from cache because any entries beyond that couldn't possibly end up in the\n  // final results anyway.\n  //\n  // Note that we must keep scanning the cache *even if* we've seen an empty gap and\n  // `storageListStart` is non-null. This is because our results must include recent put()s, which\n  // may still be DIRTY so won't be returned when we list the database. Later on we'll merge the\n  // entries we find in cache with those we get from disk.\n  for (; iter != ordered.end() && iter->get()->key < endKey &&\n       positiveCount < limit.orDefault(kj::maxValue);\n       ++iter) {\n    Entry& entry = **iter;\n\n    if (!options.noCache) {\n      touchEntry(lock, entry);\n    }\n\n    switch (entry.getValueStatus()) {\n      case EntryValueStatus::ABSENT: {\n        cachedEntries.add(kj::atomicAddRef(entry));\n        if (storageListStart != kj::none && entry.isDirty()) {\n          // This negative entry could negate something read from storage later, so we need to\n          // increase the storage list limit.\n          ++limitAdjustment;\n        }\n        break;\n      }\n      case EntryValueStatus::PRESENT: {\n        cachedEntries.add(kj::atomicAddRef(entry));\n        ++positiveCount;\n        if (storageListStart == kj::none) {\n          ++knownPrefixSize;\n        }\n        break;\n      }\n      case EntryValueStatus::UNKNOWN: {\n        // Ignore entry that exists only to mark a previous list range.\n        break;\n      }\n    }\n\n    if (storageListStart == kj::none && !entry.gapIsKnownEmpty) {\n      // The gap after this entry is not cached so we'll have to start our list operation here.\n      storageListStart = entry.key;\n      storageListStartIsKnown = entry.getValueStatus() != EntryValueStatus::UNKNOWN;\n    }\n  }\n\n  if (iter != ordered.end() && iter->get()->key == endKey) {\n    // We have an entry exactly at our end, it might even be a previously inserted UNKNOWN. Let's\n    // touch it for freshness.\n    if (!options.noCache) {\n      touchEntry(lock, **iter);\n    }\n  }\n\n  if (storageListStart == kj::none || knownPrefixSize >= limit.orDefault(kj::maxValue)) {\n    // We fully satisfied the list operation from cache.\n    return GetResultList(kj::mv(cachedEntries), {}, GetResultList::FORWARD, limit);\n  }\n\n  auto adjustedLimit =\n      limit.map([&](uint orig) { return orig + limitAdjustment - knownPrefixSize; });\n\n  auto paf = kj::newPromiseAndFulfiller<GetResultList>();\n  auto streamServer = kj::heap<ForwardListStreamImpl>(*this,\n      cloneKey(KJ_ASSERT_NONNULL(storageListStart)), kj::mv(endKey), kj::mv(cachedEntries),\n      kj::mv(paf.fulfiller), limit, adjustedLimit, storageListStartIsKnown, options);\n  auto& streamServerRef = *streamServer;\n\n  rpc::ActorStorage::ListStream::Client streamClient = kj::mv(streamServer);\n\n  auto sendPromise = scheduleStorageRead(\n      [&streamServerRef, streamClient](\n          rpc::ActorStorage::Operations::Client client) mutable -> kj::Promise<void> {\n    auto req = client.listRequest(\n        capnp::MessageSize{8 + streamServerRef.beginKey.size() / sizeof(capnp::word) +\n              streamServerRef.endKey.map([](KeyPtr k) {\n      return k.size() / sizeof(capnp::word);\n    }).orDefault(0),\n          1});\n\n    if (streamServerRef.beginKeyIsKnown) {\n      // `streamServerRef.beginKey` points to a key for which we already know the value, either\n      // because it was already in cache when we started, or because we are retrying and a previous\n      // call to `values()` produced this key. Querying it again would be redundant. But, list\n      // operations are inclusive of the start key. So, we compute the successor of the start key,\n      // which is the key with a zero byte appended.\n      auto buffer = req.initStart(streamServerRef.beginKey.size() + 1);\n      memcpy(buffer.begin(), streamServerRef.beginKey.begin(), buffer.size() - 1);\n      // Technically capnp is zero-initialized so this is redundant, but just for safety and\n      // clarity...\n      buffer[buffer.size() - 1] = 0;\n    } else {\n      if (streamServerRef.beginKey.size() > 0) {\n        req.setStart(streamServerRef.beginKey.asBytes());\n      }\n    }\n\n    KJ_IF_SOME(e, streamServerRef.endKey) {\n      req.setEnd(e.asBytes());\n    }\n\n    KJ_IF_SOME(l, streamServerRef.adjustedLimit) {\n      if (streamServerRef.fetchedEntries.size() >= l) {\n        // Oh it turns out we actually satisfied the limit already so we don't actually have to\n        // retry. The fulfiller would have already been fulfilled.\n        return kj::READY_NOW;\n      }\n      req.setLimit(l - streamServerRef.fetchedEntries.size());\n    }\n\n    req.setStream(streamClient);\n    return req.sendIgnoringResult();\n  });\n\n  // Wait on the RPC only until stream.end() is called, then report the results. We prevent\n  // `stream` from being destroyed until we have a result so that if the RPC throws an exception,\n  // we don't accidentally report \"PromiseFulfiller not fulfilled\" instead of the exception.\n  auto promise = sendPromise.then([&streamServerRef]() -> kj::Promise<ActorCache::GetResultList> {\n    if (streamServerRef.fulfiller->isWaiting()) {\n      return KJ_EXCEPTION(FAILED, \"list() never called stream.end()\");\n    } else {\n      // We'll be canceled momentarily...\n      return kj::NEVER_DONE;\n    }\n  });\n\n  return paf.promise.exclusiveJoin(kj::mv(promise))\n      .attach(kj::defer(\n          [client = kj::mv(streamClient), &streamServerRef]() { streamServerRef.cancel(); }));\n}\n\n// -----------------------------------------------------------------------------\n\nclass ActorCache::ReverseListStreamImpl final: public rpc::ActorStorage::ListStream::Server {\n public:\n  ReverseListStreamImpl(ActorCache& cache,\n      Key beginKey,\n      kj::Maybe<Key> endKey,\n      kj::Vector<kj::Own<Entry>> cachedEntries,\n      kj::Own<kj::PromiseFulfiller<GetResultList>> fulfiller,\n      kj::Maybe<uint> originalLimit,\n      kj::Maybe<uint> adjustedLimit,\n      ReadOptions options)\n      : cache(cache),\n        beginKey(kj::mv(beginKey)),\n        endKey(kj::mv(endKey)),\n        cachedEntries(kj::mv(cachedEntries)),\n        fulfiller(kj::mv(fulfiller)),\n        originalLimit(originalLimit),\n        adjustedLimit(adjustedLimit),\n        options(options) {}\n\n  kj::Promise<void> values(ValuesContext context) override {\n    if (!fulfiller->isWaiting()) {\n      // The original caller stopped listening. Try to cancel the stream by throwing.\n      return KJ_EXCEPTION(DISCONNECTED, \"canceled\");\n    }\n\n    {\n      auto lock = cache.lru.cleanList.lockExclusive();\n      auto list = context.getParams().getList();\n\n      bool insertedAny = false;\n\n      for (auto kv: list) {\n        Key key = kj::str(kv.getKey().asChars());\n\n        if (key >= endKey) {\n          // Out-of-order result. This is probably the result of restarting the list operation\n          // due to a disconnect. We assume this is actually a duplicate of a result we\n          // received earlier. Ignore it.\n          continue;\n        }\n\n        KJ_ASSERT(kv.hasValue());  // values that don't exist aren't listed!\n        auto entry = cache.addReadResultToCache(lock, kj::mv(key), kv.getValue(), options);\n        fetchedEntries.add(kj::mv(entry));\n        insertedAny = true;\n      }\n\n      if (insertedAny) {\n        // Update `gapIsKnownEmpty` on the whole range.\n        cache.markGapsEmpty(lock, fetchedEntries.back()->key, endKey, options);\n        endKey = cloneKey(fetchedEntries.back()->key);\n      }\n\n      cache.evictOrOomIfNeeded(lock);\n    }\n\n    if (fetchedEntries.size() >= adjustedLimit.orDefault(kj::maxValue) || beginKey == endKey) {\n      // Oh we're already done.\n      fulfill();\n    }\n    return kj::READY_NOW;\n  }\n\n  kj::Promise<void> end(EndContext context) override {\n    if (!fulfiller->isWaiting()) {\n      // Just ignore end() if we've already stopped waiting. In particular this happens in\n      // limit requests that reach the limit, or when we see an entry matching the beginning\n      // key of the list range -- in both cases, the last call to values() will have already\n      // fulfilled the fulfiller.\n      return kj::READY_NOW;\n    }\n\n    // Mark the rest of the range as empty.\n    {\n      auto lock = cache.lru.cleanList.lockExclusive();\n\n      if (fetchedEntries.size() < adjustedLimit.orDefault(kj::maxValue)) {\n        // We didn't reach the limit, so the rest of the range must be empty.\n\n        // We may need to insert a negative entry at the beginning of the list range, since we\n        // didn't see it, implying it's not present on disk. addResultToCache() will conveniently\n        // avoid adding anything if it turns out this is already in a known-empty gap.\n        auto beginEntry = cache.addReadResultToCache(lock, cloneKey(beginKey), kj::none, options);\n\n        // And we need to mark gaps empty from there to the final entry we actually saw.\n        cache.markGapsEmpty(lock, beginEntry->key, endKey, options);\n      }\n\n      cache.evictOrOomIfNeeded(lock);\n    }\n\n    fulfill();\n\n    return kj::READY_NOW;\n  }\n\n  void fulfill() {\n    fulfiller->fulfill(GetResultList(\n        kj::mv(cachedEntries), kj::mv(fetchedEntries), GetResultList::REVERSE, originalLimit));\n  }\n\n  // Indicates that the operation is being canceled. Proactively drops all entries. This\n  // is important because the destructor of an `Entry` updates the cache's accounting of memory\n  // usage, so it's important that an `Entry` cannot be held beyond the lifetime of the cache\n  // itself.\n  void cancel() {\n    KJ_ASSERT(!fulfiller->isWaiting());  // proves further RPCs will be ignored\n    cachedEntries.clear();\n    fetchedEntries.clear();\n  }\n\n  ActorCache& cache;\n\n  // The beginning of the list range, as originally passed to list().\n  Key beginKey;\n\n  // Either:\n  // - No suffix of the list is known yet, and `endKey` is the original end point passed to\n  //   list().\n  // - Some suffix is already satisfied , either from cache or from a previous batch of results\n  //   streamed from storage, and `endKey` is the key of the first known entry in this suffix.\n  kj::Maybe<Key> endKey;\n\n  // Entries we gathered from cache.\n  kj::Vector<kj::Own<Entry>> cachedEntries;\n\n  // Entries that have streamed in from disk.\n  kj::Vector<kj::Own<Entry>> fetchedEntries;\n\n  // Fulfiller for the final results.\n  kj::Own<kj::PromiseFulfiller<GetResultList>> fulfiller;\n\n  // The original requested limit, if any.\n  kj::Maybe<uint> originalLimit;\n\n  // The limit we sent to storage.\n  kj::Maybe<uint> adjustedLimit;\n\n  ReadOptions options;\n};\n\nkj::OneOf<ActorCache::GetResultList, kj::Promise<ActorCache::GetResultList>> ActorCache::\n    listReverse(Key beginKey, kj::Maybe<Key> endKey, kj::Maybe<uint> limit, ReadOptions options) {\n  options.noCache = options.noCache || lru.options.noCache;\n  requireNotTerminal(nullptr);\n\n  // Alas, everything needs to be done slightly differently when listing in reverse. This function\n  // is an adjusted version of the previous function.\n\n  kj::Vector<kj::Own<Entry>> cachedEntries;\n  size_t positiveCount = 0;  // number of positive entries in `cachedEntries`\n  if (limit.orDefault(kj::maxValue) == 0 || beginKey >= endKey) {\n    // No results in these cases, just return.\n    return ActorCache::GetResultList(kj::mv(cachedEntries), {}, GetResultList::REVERSE);\n  }\n\n  uint limitAdjustment = 0;\n  // When requesting to storage, we need to adjust the limit to increase it by the number of cached\n  // negative entries in the range, since each of those negative entries could potentially negate a\n  // positive entry read from disk.\n\n  auto lock = lru.cleanList.lockExclusive();\n  auto& map = currentValues.get(lock);\n  auto ordered = map.ordered();\n\n  kj::Maybe<KeyPtr> storageListEnd;\n  // If we must do a storage operation, what key shall it end at?\n  //\n  // As an extra hack, if the Maybe is non-null but the KeyPtr is null, this indicates there is\n  // no end. It's impossible for storageListEnd to point at a null key and intend this to mean that\n  // the end should be the empty-string key because this would suggest an empty list range.\n\n  uint knownSuffixSize = 0;\n  // How many keys were matched from cache after (and including) `storageListEnd`? We will\n  // use this to reduce the `limit` we pass in the storage op (if there is one).\n\n  // Let's iterate backwards over the cache starting from `endKey`. Iterating backwards is a\n  // bit mind-bendy.\n  //\n  // Note that we must keep scanning the cache *even if* we've seen an empty gap and\n  // `storageListEnd` is non-null. This is because our results must include recent put()s, which\n  // may still be DIRTY so won't be returned when we list the database. Later on we'll merge the\n  // entries we find in cache with those we get from disk.\n  KeyPtr nextKey = endKey.orDefault({});  // \"the last key we saw in backwards order\"\n  auto iter = seekOrEnd(map, endKey);\n  if (iter != map.ordered().end() && iter->get()->key == endKey) {\n    // We have an entry exactly at our end, it might even be a previously inserted UNKNOWN. Let's\n    // touch it for freshness.\n    if (!options.noCache) {\n      touchEntry(lock, **iter);\n    }\n  }\n  for (; positiveCount < limit.orDefault(kj::maxValue);) {\n    if (iter == ordered.begin()) {\n      // No earlier entries, treat same as if previous entry were before beginKey and had\n      // gapIsKnownEmpty = false.\n      if (storageListEnd == kj::none) storageListEnd = nextKey;\n      break;\n    }\n\n    // Step backwards.\n    --iter;\n    auto& entry = **iter;\n\n    // If the gap after this entry is not known empty, then we've exhausted our known-suffix and\n    // will need to cover this gap using a storage RPC.\n    if (storageListEnd == kj::none && !entry.gapIsKnownEmpty) {\n      storageListEnd = nextKey;\n    }\n\n    if (entry.key < beginKey) {\n      // We've traversed past the beginning of our range so exit the loop here.\n      break;\n    }\n\n    if (!options.noCache) {\n      touchEntry(lock, entry);\n    }\n\n    // Note that we need to add even negative entries to `cachedEntries` so that they override\n    // whatever we read from storage later. However, they should not count against the limit.\n    switch (entry.getValueStatus()) {\n      case EntryValueStatus::ABSENT: {\n        cachedEntries.add(kj::atomicAddRef(entry));\n        if (storageListEnd != kj::none && entry.isDirty()) {\n          // This negative entry could negate something read from storage later, so we need to\n          // increase the storage list limit.\n          ++limitAdjustment;\n        }\n        break;\n      }\n      case EntryValueStatus::PRESENT: {\n        cachedEntries.add(kj::atomicAddRef(entry));\n        ++positiveCount;\n        if (storageListEnd == kj::none) {\n          ++knownSuffixSize;\n        }\n        break;\n      }\n      case EntryValueStatus::UNKNOWN: {\n        // Ignore entry that exists only to mark a previous list range.\n        break;\n      }\n    }\n\n    if (entry.key == beginKey) {\n      // We've traversed through the beginning of our range so exit the loop here.\n      break;\n    }\n\n    nextKey = entry.key;\n  }\n\n  if (storageListEnd == kj::none || knownSuffixSize >= limit.orDefault(kj::maxValue)) {\n    // We fully satisfied the list operation from cache.\n    return GetResultList(kj::mv(cachedEntries), {}, GetResultList::REVERSE, limit);\n  }\n\n  {\n    KeyPtr k = KJ_ASSERT_NONNULL(storageListEnd);\n    if (k.size() == 0) {\n      // Empty string inside non-null storageListEnd means that our endpoint is the end of the\n      // keyspace. (It couldn't possibly mean that our endpoint is the *beginning* of the keyspace,\n      // because that would mean that we're listing a zero-sized range, in which case we would have\n      // returned earlier.)\n      endKey = kj::none;\n    } else {\n      endKey = cloneKey(k);\n    }\n  }\n\n  auto adjustedLimit =\n      limit.map([&](uint orig) { return orig + limitAdjustment - knownSuffixSize; });\n\n  auto paf = kj::newPromiseAndFulfiller<GetResultList>();\n  auto streamServer = kj::heap<ReverseListStreamImpl>(*this, kj::mv(beginKey), kj::mv(endKey),\n      kj::mv(cachedEntries), kj::mv(paf.fulfiller), limit, adjustedLimit, options);\n  auto& streamServerRef = *streamServer;\n\n  rpc::ActorStorage::ListStream::Client streamClient = kj::mv(streamServer);\n\n  auto sendPromise = scheduleStorageRead(\n      [&streamServerRef, streamClient](\n          rpc::ActorStorage::Operations::Client client) mutable -> kj::Promise<void> {\n    auto req = client.listRequest(\n        capnp::MessageSize{8 + streamServerRef.beginKey.size() / sizeof(capnp::word) +\n              streamServerRef.endKey.map([](KeyPtr k) {\n      return k.size() / sizeof(capnp::word);\n    }).orDefault(0),\n          1});\n    if (streamServerRef.beginKey.size() > 0) {\n      req.setStart(streamServerRef.beginKey.asBytes());\n    }\n    KJ_IF_SOME(e, streamServerRef.endKey) {\n      req.setEnd(e.asBytes());\n    }\n    req.setReverse(true);\n    KJ_IF_SOME(l, streamServerRef.adjustedLimit) {\n      if (streamServerRef.fetchedEntries.size() >= l) {\n        // Oh it turns out we actually satisfied the limit already so we don't actually have to\n        // retry. The fulfiller would have already been fulfilled.\n        return kj::READY_NOW;\n      }\n      req.setLimit(l - streamServerRef.fetchedEntries.size());\n    }\n    req.setStream(streamClient);\n    return req.sendIgnoringResult();\n  });\n\n  // Wait on the RPC only until stream.end() is called, then report the results. We prevent\n  // `stream` from being destroyed until we have a result so that if the RPC throws an exception,\n  // we don't accidentally report \"PromiseFulfiller not fulfilled\" instead of the exception.\n  auto promise = sendPromise.then([&streamServerRef]() -> kj::Promise<ActorCache::GetResultList> {\n    if (streamServerRef.fulfiller->isWaiting()) {\n      return KJ_EXCEPTION(FAILED, \"list() never called stream.end()\");\n    } else {\n      // We'll be canceled momentarily...\n      return kj::NEVER_DONE;\n    }\n  });\n\n  return paf.promise.exclusiveJoin(kj::mv(promise))\n      .attach(kj::defer(\n          [client = kj::mv(streamClient), &streamServerRef]() { streamServerRef.cancel(); }));\n}\n\n// -----------------------------------------------------------------------------\n// Helpers for read operations\n\nkj::Own<ActorCache::Entry> ActorCache::findInCache(\n    Lock& lock, KeyPtr key, const ReadOptions& options) {\n  auto& map = currentValues.get(lock);\n  auto iter = map.seek(key);\n  auto ordered = map.ordered();\n\n  if (iter != ordered.end() && iter->get()->key == key) {\n    // Found exact matching entry.\n    Entry& entry = **iter;\n    if (!options.noCache) {\n      touchEntry(lock, entry);\n    }\n    return kj::atomicAddRef(entry);\n  } else {\n    // Key is not in the map, but we have to check for outstanding list() operations by checking\n    // the previous entry's gapState.\n\n    if (iter != ordered.begin()) {\n      Entry& prev = **--iter;\n      if (prev.gapIsKnownEmpty) {\n        // A previous list() operation covered this section of the key space and did not find this\n        // key, so we know it's not present. Return a dummy entry saying this.\n        return kj::atomicRefcounted<ActorCache::Entry>(cloneKey(key), EntryValueStatus::ABSENT);\n      }\n    }\n\n    // We don't know whether this key exists in storage.\n    return kj::atomicRefcounted<ActorCache::Entry>(cloneKey(key), EntryValueStatus::UNKNOWN);\n  }\n}\n\nkj::Own<ActorCache::Entry> ActorCache::addReadResultToCache(\n    Lock& lock, Key key, kj::Maybe<capnp::Data::Reader> maybeReader, const ReadOptions& options) {\n  if (options.noCache) {\n    // We don't actually want to add this to the cache, just return the entry.\n    KJ_IF_SOME(reader, maybeReader) {\n      return kj::atomicRefcounted<Entry>(kj::mv(key), kj::heapArray(reader));\n    } else {\n      return kj::atomicRefcounted<Entry>(kj::mv(key), EntryValueStatus::ABSENT);\n    }\n  }\n\n  auto& map = currentValues.get(lock);\n\n  kj::Own<Entry> entry;\n  KJ_IF_SOME(reader, maybeReader) {\n    entry = kj::atomicRefcounted<Entry>(*this, kj::mv(key), kj::heapArray(reader));\n  } else {\n    // Inserting a negative entry. Let's check if the new insertion is redundant due to the\n    // previous entry having `gapIsKnownEmpty`.\n    auto iter = map.seek(key);\n    auto ordered = map.ordered();\n    if ((iter == ordered.end() || iter->get()->key != key) && iter != ordered.begin()) {\n      // We did not find an exact match for the key, so we got an iterator pointing to the next\n      // entry after the key. It's not the first entry, so we can back it up one to get the\n      // entry before the key.\n      --iter;\n\n      if (iter->get()->gapIsKnownEmpty) {\n        // This entry is redundant, so we won't insert it.\n        return kj::atomicRefcounted<Entry>(kj::mv(key), EntryValueStatus::ABSENT);\n        ;\n      }\n    }\n\n    entry = kj::atomicRefcounted<Entry>(*this, kj::mv(key), EntryValueStatus::ABSENT);\n    // TODO(perf): It's a little sad that we are going to do a findOrCreate() below that is going\n    //   to repeat the same lookup that produced `iter`. Maybe we could extend kj::Table with a\n    //   way to provide an existing iterator as a hint when inserting?\n  }\n\n  // At this point, we know we definitely want there to exist an entry matching this key. So now\n  // try to insert it.\n  auto& slot = map.findOrCreate(entry->key, [&]() {\n    // No existing entry has this key, so insert our new entry.\n    //\n    // Note that it's definitely guaranteed that the entry *before* the one we're inserting cannot\n    // possibly have `gapIsKnownEmpty = true`, because:\n    // 1. If our new entry has a null value, then we could have returned early above in this case.\n    // 2. If our new entry has a non-null value, then it would be inconsistent for a previous\n    //    entry to claim that the gap is empty -- this new entry proves it was not! Remember that\n    //    we are inserting an entry that was the result of reading from disk, so it *must* be\n    //    consistent with any existing knowledge about the state of disk -- unless we have a bug in\n    //    the caching logic.\n    //\n    // Because of this, we know it is correct to leave `gapIsKnownEmpty = false` on our new entry.\n    addToCleanList(lock, *entry);\n    return kj::atomicAddRef(*entry);\n  });\n\n  if (slot.get() != entry.get()) {\n    // There was a pre-existing entry with the key, so ours wasn't inserted.\n    switch (slot->getValueStatus()) {\n      case EntryValueStatus::UNKNOWN: {\n        // Oh, it's just a marker for the end of a list range. Go ahead and insert our new entry\n        // into the same slot.\n        KJ_ASSERT(!slot->gapIsKnownEmpty);  // UNKNOWN entry should never have gapIsKnownEmpty.\n        removeEntry(lock, *slot);\n\n        addToCleanList(lock, *entry);\n        slot = kj::atomicAddRef(*entry);\n        break;\n      }\n      case EntryValueStatus::PRESENT:\n      case EntryValueStatus::ABSENT: {\n        // The entry that's already in the map must be at least as fresh as the one we just created.\n        // If it was created by a put() or delete(), then it is actually fresher. If it was created\n        // by a concurrent get() or list() that fetched the same key, then it should be exactly the\n        // same value. So, either way, our new entry isn't needed. We mark it NOT_IN_CACHE since it\n        // won't be placed in the map.\n        //\n        // NOTE: You might be tempted to say that if the existing entry is DIRTY, but its value\n        //   matches the value that we just read off disk, then we can cancel the write, because\n        //   we've discovered it is redundant. Unfortunately, this is NOT true, because it's possible\n        //   something else has been written in between. Specifically, we could currently be in the\n        //   process of building a transaction that wrote some other value to this specific key, but\n        //   hasn't been committed yet, probably because it is waiting for this read operation to\n        //   complete. Meanwhile, another put() or delete() could have just been performed\n        //   momentarily ago that changed the flushing entry back to DIRTY and changed its value to\n        //   one that coincidentally matches what we pulled off disk. However, the open transaction\n        //   is still going to be committed, writing the intermediate value, so we still need to plan\n        //   to write this value again in the next transaction.\n        touchEntry(lock, *slot);\n        break;\n      }\n    }\n  }\n\n  return kj::mv(entry);\n}\n\n// Set `gapIsKnownEmpty` across the range covered by a new batch of entries arriving from\n// storage via a list() operation. Since we just listed this range, we know that all the gaps\n// between entries in this range can now be marked as empty.\n//\n// You might ask: \"But what if an entry was evicted from the cache between when list() was\n// called and now, creating a gap?\"\n//\n// There are two possibilities:\n// 1. The evicted entry was clean at the time list() was called. In this case, the list()\n//    operation will have returned it, so it would have been re-added to the cache just\n//    before this method call.\n// 2. The evicted entry was dirty at the time list() was called. This can't cause a problem\n//    because we ensure that any flush is ordered after all previous read operations, so such\n//    entries could not possibly be marked clean until after the list operation completes.\n//    And, they cannot be evicted until they are marked clean. So these entries could not\n//    have been evicted yet.\nvoid ActorCache::markGapsEmpty(\n    Lock& lock, KeyPtr beginKey, kj::Maybe<KeyPtr> endKey, const ReadOptions& options) {\n  if (options.noCache) {\n    // Oops, never mind. We're not caching the list() results, so we can't mark anything\n    // known-empty.\n    return;\n  }\n\n  auto& map = currentValues.get(lock);\n\n  auto endIter = seekOrEnd(map, endKey);\n  {\n    auto ordered = map.ordered();\n    if (endIter == ordered.end() || endIter->get()->key > endKey) {\n      // The key that we're marking up *to* is not in the map.\n      if (endIter == ordered.begin()) {\n        // Whoops, it appears we don't actually have any entries in the marking range. This could\n        // happen during a forward list() due to entries from previous values() calls having\n        // already been evicted before end() was called. In this case, nothing would actually be\n        // marked below. But then our UNKNOWN entry would be inconsistent, so we'd better not\n        // insert it at all.\n        //\n        // Note that this does NOT happen as a result of a list() returning no results, because\n        // in that case the list operation would have inserted a negative entry at the beginning\n        // of the range. The only reason why we wouldn't have found that negative entry here is\n        // because it has since been evicted.\n        return;\n      }\n\n      --endIter;\n      if (endIter->get()->key < beginKey) {\n        // Same as above, it appears we have no suitable entries to mark, so we can't insert an\n        // UNKNOWN.\n        return;\n      }\n\n      if (endIter->get()->gapIsKnownEmpty) {\n        // The end key is in an already-known-empty gap, so there's no need to insert an UNKNOWN.\n        // We intentionally leave `endIter` pointing to the start of the gap even though it's not\n        // the end of our list range, because we know the stuff from there to the end of the range\n        // is already marked.\n      } else {\n        // We must insert an UNKNOWN entry to cap our range.\n        KJ_IF_SOME(k, endKey) {\n          auto entry = kj::atomicRefcounted<Entry>(*this, cloneKey(k), EntryValueStatus::UNKNOWN);\n          addToCleanList(lock, *entry);\n          map.insert(kj::mv(entry));\n        } else {\n          // No UNKNOWN needed since the end is actually the end of the key space.\n        }\n\n        // Oops, that invalidated our iterator, so find it again.\n        endIter = seekOrEnd(map, endKey);\n      }\n    }\n  }\n\n  kj::Vector<KeyPtr> keysToErase;\n  auto beginIter = map.seek(beginKey);\n  auto mapEnd = map.ordered().end();\n  for (auto iter = beginIter; iter != mapEnd; ++iter) {\n    auto& entry = **iter;\n\n    if (entry.getValueStatus() != EntryValueStatus::PRESENT && !entry.isDirty() &&\n        (iter != endIter || iter->get()->gapIsKnownEmpty)) {\n      // Either:\n      // (a) This is an UNKNOWN entry.\n      // (b) This is a clean negative entry.\n      //\n      // And either:\n      // (a) This is not the last entry, so we're about to set `gapIsKnownEmpty` on it.\n      // (b) It is the last entry, and it is already `gapIsKnownEmpty`.\n      //\n      // Either way, if the *previous* entry also has `gapIsKnownEmpty`, then *this* entry\n      // becomes redundant. In that case we need to delete it instead.\n      //\n      // Note that a negative entry that is DIRTY is not necessarily redundant, because it\n      // could be that a different value was written to that entry and then deleted between\n      // when the list() was initiated and the current state of the cache. A negative DIRTY\n      // entry will become redundant once it becomes CLEAN, so we'll have to deal with it then.\n\n      bool prevGapIsEmpty;\n      if (iter == beginIter) {\n        // This is the first entry in the range, so we have to check if the previous entry\n        // was marked.\n        if (iter == map.ordered().begin()) {\n          prevGapIsEmpty = false;\n        } else {\n          auto prev = iter;\n          --prev;\n          prevGapIsEmpty = prev->get()->gapIsKnownEmpty;\n        }\n      } else {\n        // This isn't the first entry we've iterated over so we must have marked the previous\n        // one with gapIsKnownEmpty.\n        prevGapIsEmpty = true;\n      }\n\n      if (prevGapIsEmpty) {\n        // Unfortunately erasing from the map will invalidate our iterator, so we need to make\n        // a second pass to erase, below.\n        keysToErase.add(entry.key);\n      }\n    }\n\n    if (iter == endIter) {\n      // We didn't check for `iter == endIter` earlier because the conditional above -- which\n      // potentially deletes redundant entries -- can actually apply to the end of the range, even\n      // though that entry itself isn't considered part of the range. Marking the range could cause\n      // the entry immediately after the end to become redundant.\n      //\n      // We do want to break here, though, because we do not want to mark an entry that is past\n      // the end of the range.\n      break;\n    }\n\n    entry.gapIsKnownEmpty = true;\n  }\n\n  for (auto& key: keysToErase) {\n    auto& entry = KJ_ASSERT_NONNULL(map.find(key));\n    removeEntry(lock, *entry);\n    map.erase(entry);\n  }\n}\n\nActorCache::GetResultList::GetResultList(kj::Vector<KeyValuePair> contents)\n    : entries(contents.size()),\n      cacheStatuses(contents.size()) {\n  // TODO(perf): Allocating an `Entry` object for every key/value pair is lame but to avoid it\n  //   we'd have to make the common case worse...\n  for (auto& kv: contents) {\n    entries.add(kj::atomicRefcounted<Entry>(kj::mv(kv.key), kj::mv(kv.value)));\n    cacheStatuses.add(CacheStatus::UNCACHED);\n  }\n}\n\n// Merges `cachedEntries` and `fetchedEntries`, which should each already be sorted in the\n// given order. If a key exists in both, `cachedEntries` is preferred.\n//\n// After merging, if an entry's value is null, it is dropped.\n//\n// The final result is truncated to `limit`, if any.\n//\n// The idea is that `cachedEntries` is the set of entries that were loaded from cache while\n// `fetchedEntries` is the set read from storage.\nActorCache::GetResultList::GetResultList(kj::Vector<kj::Own<Entry>> cachedEntries,\n    kj::Vector<kj::Own<Entry>> fetchedEntries,\n    Order order,\n    kj::Maybe<uint> maybeLimit) {\n  uint limit = maybeLimit.orDefault(kj::maxValue);\n  entries.reserve(kj::min(cachedEntries.size() + fetchedEntries.size(), limit));\n\n  auto cachedIter = cachedEntries.begin();\n  auto fetchedIter = fetchedEntries.begin();\n\n  auto add = [&](kj::Own<ActorCache::Entry>&& entry, CacheStatus status) {\n    // Remove null values.\n    if (entry->getValueStatus() == ActorCache::EntryValueStatus::PRESENT) {\n      entries.add(kj::mv(entry));\n      cacheStatuses.add(status);\n    }\n  };\n\n  while ((cachedIter != cachedEntries.end() || fetchedIter != fetchedEntries.end()) &&\n      entries.size() < limit) {\n    if (cachedIter == cachedEntries.end()) {\n      add(kj::mv(*fetchedIter++), CacheStatus::UNCACHED);\n    } else if (fetchedIter == fetchedEntries.end()) {\n      add(kj::mv(*cachedIter++), CacheStatus::CACHED);\n    } else if (order == REVERSE ? cachedIter->get()->key > fetchedIter->get()->key\n                                : cachedIter->get()->key < fetchedIter->get()->key) {\n      add(kj::mv(*cachedIter++), CacheStatus::CACHED);\n    } else if (cachedIter->get()->key == fetchedIter->get()->key) {\n      // Same key in both. Prefer the cached entry because it will reflect the state as of when the\n      // operation began.\n      // Uncached status because we still fetched from disk.\n      add(kj::mv(*cachedIter++), CacheStatus::UNCACHED);\n      ++fetchedIter;\n    } else {\n      add(kj::mv(*fetchedIter++), CacheStatus::UNCACHED);\n    }\n  }\n\n#ifdef KJ_DEBUG\n  // Verify sort.\n  kj::Maybe<KeyPtr> prev;\n  for (auto& entry: entries) {\n    KJ_IF_SOME(p, prev) {\n      if (order == REVERSE) {\n        KJ_ASSERT(entry->key < p);\n      } else {\n        KJ_ASSERT(entry->key > p);\n      }\n    }\n    prev = entry->key;\n  }\n#endif\n}\n\ntemplate <typename Func>\nkj::PromiseForResult<Func, rpc::ActorStorage::Operations::Client> ActorCache::scheduleStorageRead(\n    Func&& function) {\n  // This is basically kj::retryOnDisconnect() except that we make the first call synchronously.\n  // For our use case, this is safe, and I wanted to make sure reads get sent concurrently with\n  // further JavaScript execution if possible.\n  auto promise = kj::evalNow(\n      [&]() mutable { return function(storage).attach(recordStorageRead(hooks, clock)); });\n  return oomCanceler.wrap(\n      promise\n          .catch_([this, function = kj::mv(function)](kj::Exception&& e) mutable\n              -> kj::PromiseForResult<Func, rpc::ActorStorage::Operations::Client> {\n    if (e.getType() == kj::Exception::Type::DISCONNECTED) {\n      return function(storage).attach(recordStorageRead(hooks, clock));\n    } else {\n      return kj::mv(e);\n    }\n  }).attach(kj::addRef(*readCompletionChain)));\n}\n\nkj::Promise<void> ActorCache::waitForPastReads() {\n  if (!readCompletionChain->isShared()) {\n    // No reads are in flight right now.\n    return kj::READY_NOW;\n  }\n\n  // Create a new chain link.\n  auto next = kj::refcounted<ReadCompletionChain>();\n\n  // Update previous chain so that when it is destroyed, it'll fulfill us and also drop its\n  // reference on the next link.\n  auto paf = kj::newPromiseAndFulfiller<void>();\n  readCompletionChain->fulfiller = kj::mv(paf.fulfiller);\n  readCompletionChain->next = kj::addRef(*next);\n\n  // Make `next` the current link.\n  readCompletionChain = kj::mv(next);\n\n  return kj::mv(paf.promise);\n}\n\nActorCache::ReadCompletionChain::~ReadCompletionChain() noexcept(false) {\n  KJ_IF_SOME(f, fulfiller) {\n    f->fulfill();\n  }\n}\n\n// =======================================================================================\n// write operations\n\nkj::Maybe<kj::Promise<void>> ActorCache::put(\n    Key key, Value value, WriteOptions options, SpanParent traceSpan) {\n  ActorStorageLimits::checkMaxKeySize(key);\n  ActorStorageLimits::checkMaxValueSize(key, value);\n\n  options.noCache = options.noCache || lru.options.noCache;\n  requireNotTerminal(traceSpan.addRef());\n  {\n    auto lock = lru.cleanList.lockExclusive();\n    kj::Maybe<CountedDelete> maybeCountedDelete;\n    auto entry = kj::atomicRefcounted<Entry>(*this, kj::mv(key), kj::mv(value));\n    putImpl(lock, kj::mv(entry), options, maybeCountedDelete, kj::mv(traceSpan));\n    evictOrOomIfNeeded(lock);\n  }\n  return getBackpressure();\n}\n\nkj::Maybe<kj::Promise<void>> ActorCache::put(\n    kj::Array<KeyValuePair> pairs, WriteOptions options, SpanParent traceSpan) {\n  for (auto& pair: pairs) {\n    // We check limits in a separate loop to fail the whole operation when any pair fails a check\n    ActorStorageLimits::checkMaxKeySize(pair.key);\n    ActorStorageLimits::checkMaxValueSize(pair.key, pair.value);\n  }\n\n  options.noCache = options.noCache || lru.options.noCache;\n  requireNotTerminal(traceSpan.addRef());\n  {\n    auto lock = lru.cleanList.lockExclusive();\n    for (auto& pair: pairs) {\n      kj::Maybe<CountedDelete> maybeCountedDelete;\n      auto entry = kj::atomicRefcounted<Entry>(*this, kj::mv(pair.key), kj::mv(pair.value));\n      putImpl(lock, kj::mv(entry), options, maybeCountedDelete, traceSpan.addRef());\n    }\n    evictOrOomIfNeeded(lock);\n  }\n  return getBackpressure();\n}\n\nkj::Maybe<kj::Promise<void>> ActorCache::setAlarm(\n    kj::Maybe<kj::Date> newAlarmTime, WriteOptions options, SpanParent traceSpan) {\n  options.noCache = options.noCache || lru.options.noCache;\n  KJ_IF_SOME(time, currentAlarmTime.tryGet<KnownAlarmTime>()) {\n    // If we're in the alarm handler and haven't set the time yet,\n    // we can't perform this optimization as currentAlarmTime will be equal\n    // to the currently running time but we indicate to the actor in getAlarm() that there\n    // is no alarm set, therefore we need to act like that in setAlarm().\n    //\n    // After the first write in the handler occurs, which would set KnownAlarmTime,\n    // the logic here is correct again as currentAlarmTime would match what we are reporting\n    // to the user from getAlarm().\n    //\n    // So, we only apply this for KnownAlarmTime.\n\n    if (time.time == newAlarmTime) {\n      // No change! May as well skip the storage operation.\n      return kj::none;\n    }\n  }\n\n  currentAlarmTime = ActorCache::KnownAlarmTime{\n    ActorCache::KnownAlarmTime::Status::DIRTY, newAlarmTime, options.noCache};\n\n  ensureFlushScheduled(options, kj::mv(traceSpan));\n\n  return getBackpressure();\n}\n\nnamespace {\ntemplate <typename F>\nkj::OneOf<std::invoke_result_t<F>, kj::PromiseForResult<F, void>> mapPromise(\n    kj::Maybe<kj::Promise<void>> maybePromise, F&& f) {\n  KJ_IF_SOME(promise, maybePromise) {\n    return promise.then(kj::fwd<F>(f));\n  } else {\n    return kj::fwd<F>(f)();\n  }\n}\n}  // namespace\n\nkj::OneOf<bool, kj::Promise<bool>> ActorCache::delete_(\n    Key key, WriteOptions options, SpanParent traceSpan) {\n  ActorStorageLimits::checkMaxKeySize(key);\n\n  options.noCache = options.noCache || lru.options.noCache;\n  requireNotTerminal(traceSpan.addRef());\n\n  auto countedDelete = kj::refcounted<CountedDelete>();\n  {\n    auto lock = lru.cleanList.lockExclusive();\n    auto entry = kj::atomicRefcounted<Entry>(*this, kj::mv(key), EntryValueStatus::ABSENT);\n    putImpl(lock, kj::mv(entry), options, *countedDelete, kj::mv(traceSpan));\n    evictOrOomIfNeeded(lock);\n  }\n\n  auto waiter = kj::heap<CountedDeleteWaiter>(*this, kj::addRef(*countedDelete));\n  kj::Maybe<kj::Promise<void>> maybePromise;\n  KJ_IF_SOME(p, getBackpressure()) {\n    // This might be more than one flush but that's OK as long as our state gets taken care of.\n    maybePromise = countedDelete->forgiveIfFinished(kj::mv(p));\n  } else if (!countedDelete->entries.empty()) {\n    maybePromise = countedDelete->forgiveIfFinished(lastFlush.addBranch());\n  }\n  return mapPromise(kj::mv(maybePromise),\n      [waiter = kj::mv(waiter)]() { return waiter->getCountedDelete().countDeleted > 0; });\n}\n\nkj::OneOf<uint, kj::Promise<uint>> ActorCache::delete_(\n    kj::Array<Key> keys, WriteOptions options, SpanParent traceSpan) {\n  for (auto& key: keys) {\n    ActorStorageLimits::checkMaxKeySize(key);\n  }\n\n  options.noCache = options.noCache || lru.options.noCache;\n  requireNotTerminal(traceSpan.addRef());\n\n  auto countedDelete = kj::refcounted<CountedDelete>();\n  {\n    auto lock = lru.cleanList.lockExclusive();\n    for (auto& key: keys) {\n      auto entry = kj::atomicRefcounted<Entry>(*this, kj::mv(key), EntryValueStatus::ABSENT);\n      putImpl(lock, kj::mv(entry), options, *countedDelete, traceSpan.addRef());\n    }\n    evictOrOomIfNeeded(lock);\n  }\n\n  auto waiter = kj::heap<CountedDeleteWaiter>(*this, kj::addRef(*countedDelete));\n  kj::Maybe<kj::Promise<void>> maybePromise;\n  KJ_IF_SOME(p, getBackpressure()) {\n    // This might be more than one flush but that's OK as long as our state gets taken care of.\n    maybePromise = countedDelete->forgiveIfFinished(kj::mv(p));\n  } else if (!countedDelete->entries.empty()) {\n    maybePromise = countedDelete->forgiveIfFinished(lastFlush.addBranch());\n  }\n  return mapPromise(kj::mv(maybePromise),\n      [waiter = kj::mv(waiter)]() { return waiter->getCountedDelete().countDeleted; });\n}\n\nkj::Own<ActorCacheInterface::Transaction> ActorCache::startTransaction() {\n  return kj::heap<Transaction>(*this);\n}\n\nActorCache::DeleteAllResults ActorCache::deleteAll(\n    WriteOptions options, SpanParent traceSpan, DeleteAllOptions deleteAllOptions) {\n  // Since deleteAll() cannot be performed as part of another transaction, in order to maintain\n  // our ordering guarantees, we will have to complete all writes that occurred prior to the\n  // deleteAll(), then submit the deleteAll(), then do any writes afterwards. Conveniently, though,\n  // a deleteAll() invalidates the whole map. So, we can take all the dirty entries out and place\n  // them off to the side for the moment, so that overwrites won't affect them. (Otherwise, an\n  // overwritten entry would be moved to the end of the dirty list, which might mean it is\n  // committed in the wrong order with respect to the deleteAll().)\n\n  options.noCache = options.noCache || lru.options.noCache;\n  requireNotTerminal(traceSpan.addRef());\n\n  kj::Promise<uint> result{static_cast<uint>(0)};\n\n  {\n    auto lock = lru.cleanList.lockExclusive();\n    auto& map = currentValues.get(lock);\n\n    kj::Vector<kj::Own<Entry>> deletedDirty;\n    for (auto& entry: dirtyList) {\n      // We will be removing all entries from their respective lists soon, so let's preserve the\n      // dirty list so we can run it before our delete all.\n      deletedDirty.add(kj::atomicAddRef(entry));\n    };\n\n    // Clear out the entire map.\n    for (auto& entry: map) {\n      removeEntry(lock, *entry);\n    }\n    map.clear();\n\n    // Insert a dummy entry with an ABSENT key and gapIsKnownEmpty = true to indicate that\n    // everything is empty.\n    map.findOrCreate(Key{}, [&]() {\n      Key key;\n      auto entry = kj::atomicRefcounted<Entry>(*this, kj::mv(key), EntryValueStatus::ABSENT);\n      addToCleanList(lock, *entry);\n      entry->gapIsKnownEmpty = true;\n      return entry;\n    });\n\n    KJ_IF_SOME(existing, requestedDeleteAll) {\n      // A previous deleteAll() was scheduled and hasn't been committed yet. This means that we\n      // can actually coalesce the two, and there's no need to commit any writes that happened\n      // between them. So we can throw away `deletedDirty`.\n      // We also don't want to double-bill for a coalesced deleteAll, so we don't update\n      // result in this branch. We do ensure the alarm is deleted if requested, though.\n      existing.deleteAlarm = existing.deleteAlarm || deleteAllOptions.deleteAlarm;\n    } else {\n      // If no previous deleteAll() was scheduled, then schedule this one.\n      auto paf = kj::newPromiseAndFulfiller<uint>();\n      result = kj::mv(paf.promise);\n      requestedDeleteAll = DeleteAllState{\n        .deletedDirty = kj::mv(deletedDirty),\n        .countFulfiller = kj::mv(paf.fulfiller),\n        .deleteAlarm = deleteAllOptions.deleteAlarm,\n      };\n      ensureFlushScheduled(options, kj::mv(traceSpan));\n    }\n\n    if (deleteAllOptions.deleteAlarm) {\n      // Update the in-memory alarm state immediately so that getAlarm() returns null right away.\n      // The actual alarm deletion from storage is deferred to flushImplDeleteAll(), which sets\n      // currentAlarmTime to DIRTY after the deleteAll RPC succeeds, causing the post-deleteAll\n      // flush to send the deleteAlarm RPC.\n      currentAlarmTime = KnownAlarmTime{\n        .status = KnownAlarmTime::Status::CLEAN,\n        .time = kj::none,\n      };\n    }\n\n    // This is called for consistency, but deleteAll() strictly reduces cache usage, so it's not\n    // entirely necessary.\n    evictOrOomIfNeeded(lock);\n  }\n\n  return DeleteAllResults{.backpressure = getBackpressure(), .count = kj::mv(result)};\n}\n\nvoid ActorCache::putImpl(Lock& lock,\n    kj::Own<Entry> newEntry,\n    const WriteOptions& options,\n    kj::Maybe<CountedDelete&> maybeCountedDelete,\n    SpanParent traceSpan) {\n  auto& map = currentValues.get(lock);\n  auto ordered = map.ordered();\n\n  // This gets a little complicated because we want to avoid redundant insertions.\n\n  newEntry->noCache = options.noCache;\n\n  auto iter = map.seek(newEntry->key);\n  if (iter != ordered.end() && iter->get()->key == newEntry->key) {\n    // Exact same entry already exists.\n    auto& slot = *iter;\n\n    switch (slot->getValueStatus()) {\n      case EntryValueStatus::PRESENT: {\n        if (slot->getValuePtr() == newEntry->getValuePtr()) {\n          // No change! The entry already had this value. Might as well skip the whole storage\n          // operation.\n          return;\n        }\n\n        KJ_IF_SOME(c, maybeCountedDelete) {\n          // Overwrote an entry that was in cache, so we can count it now. Note that because we\n          // are PRESENT, we will not be added to the CountedDelete's `entries`, since we only\n          // do this for UNKNOWN entries! Instead, we'll be part of a regular delete.\n          ++c.countDeleted;\n        }\n        break;\n      }\n      case EntryValueStatus::ABSENT: {\n        if (slot->getValuePtr() == newEntry->getValuePtr()) {\n          // No change! The entry already had this value. Might as well skip the whole storage\n          // operation.\n          return;\n        }\n\n        if (slot->isCountedDelete) {\n          // We are overwriting an entry that is slated for a counted delete operation.\n          // There may be a situation where all the entries associated with a counted delete are\n          // actually successfully deleted (and we get the count), but the transaction the deletes\n          // execute within fails.\n          //\n          // Since we are currently overwriting the Entry, we might as well inform the\n          // `CountedDelete` that this Entry has since been overwritten. Then, if we hit the case\n          // described above, we won't need to include this Entry in a subsequent counted delete\n          // retry, since we already have the count AND the Entry has been overwritten.\n          //\n          // For more details, see how we filter the entries to be deleted for a CountedDeleteFlush\n          // as part of a flush.\n          slot->overwritingCountedDelete = true;\n        }\n        // We don't have to worry about the counted delete since we were already deleted.\n        break;\n      }\n      case EntryValueStatus::UNKNOWN: {\n        // This was a list end marker, we should just overwrite it.\n\n        KJ_IF_SOME(c, maybeCountedDelete) {\n          // Despite an entry being present, we don't know if the key exists, because it's just an\n          // UNKNOWN entry. So we will still have to arrange to count the delete later.\n          newEntry->isCountedDelete = true;\n          c.entries.add(kj::atomicAddRef(*newEntry));\n        }\n        break;\n      }\n    }\n\n    KJ_DASSERT(slot->key == newEntry->key);\n\n    // Inherit gap state.\n    newEntry->gapIsKnownEmpty = slot->gapIsKnownEmpty;\n\n    // Swap in the new entry.\n    removeEntry(lock, *slot);\n\n    slot = kj::mv(newEntry);\n    addToDirtyList(*slot);\n  } else {\n    // No exact matching entry exists, insert a new one.\n\n    // Does the previous entry have a known-empty gap?\n    bool previousGapKnownEmpty = false;\n    if (iter != ordered.begin()) {\n      --iter;\n      previousGapKnownEmpty = iter->get()->gapIsKnownEmpty;\n    }\n    if (previousGapKnownEmpty && newEntry->getValueStatus() == EntryValueStatus::ABSENT) {\n      // No change! The entry is already known not to exist, and we're trying to delete it. Might\n      // as well skip the whole storage operation.\n      return;\n    }\n\n    // Create the new entry.\n    // TODO(perf): Extend kj::TreeIndex to allow supplying the existing iterator as a hint when\n    //   inserting a new entry, to avoid repeating the lookup.\n    auto& slot = map.insert(kj::mv(newEntry));\n    slot->gapIsKnownEmpty = previousGapKnownEmpty;\n    KJ_IF_SOME(c, maybeCountedDelete) {\n      slot->isCountedDelete = true;\n      c.entries.add(kj::atomicAddRef(*slot));\n    }\n    addToDirtyList(*slot);\n  }\n\n  ensureFlushScheduled(options, kj::mv(traceSpan));\n}\n\nvoid ActorCache::ensureFlushScheduled(const WriteOptions& options, SpanParent traceSpan) {\n  if (lru.options.neverFlush) {\n    // Skip all flushes. Used for preview sessions where data is strictly kept in memory.\n\n    // Handle deleteAll state that would normally be processed during flushImplDeleteAll().\n    KJ_IF_SOME(deleteAllState, requestedDeleteAll) {\n      deleteAllState.countFulfiller->fulfill(0);\n      if (deleteAllState.deleteAlarm) {\n        currentAlarmTime = KnownAlarmTime{\n          .status = KnownAlarmTime::Status::DIRTY,\n          .time = kj::none,\n        };\n      }\n      requestedDeleteAll = kj::none;\n    }\n\n    // Also, we need to handle scheduling or canceling any alarm changes locally.\n    KJ_SWITCH_ONEOF(currentAlarmTime) {\n      KJ_CASE_ONEOF(knownAlarmTime, ActorCache::KnownAlarmTime) {\n        if (knownAlarmTime.status == KnownAlarmTime::Status::DIRTY) {\n          knownAlarmTime.status = KnownAlarmTime::Status::CLEAN;\n          hooks.updateAlarmInMemory(knownAlarmTime.time);\n        }\n      }\n      KJ_CASE_ONEOF(deferredDelete, ActorCache::DeferredAlarmDelete) {\n        if (deferredDelete.status == DeferredAlarmDelete::Status::READY) {\n          currentAlarmTime = KnownAlarmTime{\n            .status = KnownAlarmTime::Status::CLEAN,\n            .time = kj::none,\n          };\n          hooks.updateAlarmInMemory(kj::none);\n        }\n      }\n      KJ_CASE_ONEOF(_, UnknownAlarmTime) {}\n    }\n\n    return;\n  }\n\n  if (!flushScheduled) {\n    flushScheduled = true;\n    // Capture the trace span from the first write in this flush batch.\n    currentFlushSpan = kj::mv(traceSpan);\n\n    auto flushPromise = lastFlush.addBranch()\n                            .attach(kj::defer([this]() {\n      flushScheduled = false;\n      flushScheduledWithOutputGate = false;\n      // Reset the flush span for the next batch\n      currentFlushSpan = nullptr;\n    })).then([this]() {\n      ++flushesEnqueued;\n      return kj::evalNow([this]() {\n        // `flushImpl()` can throw, so we need to wrap it in `evalNow()` to observe all pathways.\n        return flushImpl();\n      }).attach(kj::defer([this]() { --flushesEnqueued; }));\n    });\n\n    if (options.allowUnconfirmed) {\n      // Don't apply output gate. But, if an exception is thrown, we still want to break the gate,\n      // so arrange for that.\n      flushPromise = flushPromise.catch_([this](kj::Exception&& e) {\n        return gate.lockWhile(kj::Promise<void>(kj::mv(e)), nullptr);\n      });\n    } else {\n      flushPromise = gate.lockWhile(kj::mv(flushPromise), currentFlushSpan.addRef());\n      flushScheduledWithOutputGate = true;\n    }\n\n    lastFlush = flushPromise.fork();\n  } else if (!flushScheduledWithOutputGate && !options.allowUnconfirmed) {\n    // The flush has already been scheduled without the output gate, but we want to upgrade it to\n    // use the output gate now. The span was already captured when the flush was first scheduled.\n    lastFlush = gate.lockWhile(lastFlush.addBranch(), currentFlushSpan.addRef()).fork();\n    flushScheduledWithOutputGate = true;\n  }\n}\n\n// This function returns a Maybe<Promise> because a falsy maybe allows the jsg interface to make\n// a resolved jsg::Promise. This is meaningfully different from a ready kj::Promise because it\n// allows the next continuation to run immediately on the microtask queue instead of returning to\n// the kj event loop and fulfilling a resolver that enqueues the continuation.\nkj::Maybe<kj::Promise<void>> ActorCache::onNoPendingFlush(SpanParent parentSpan) {\n  if (lru.options.neverFlush) {\n    // We won't ever flush (usually because we're a preview session), so return a falsy maybe.\n    return kj::none;\n  }\n\n  if (flushScheduled) {\n    // There is a flush that is currently scheduled but not yet running, we need to wait for that\n    // flush to complete before resolving the jsg::Promise.\n    return lastFlush.addBranch();\n  }\n\n  if (flushesEnqueued > 0) {\n    // There is no flush that is scheduled but there is one running, we need to wait for that flush\n    // to complete before resolving the jsg::Promise.\n    return lastFlush.addBranch();\n  }\n\n  // There are no scheduled or in-flight flushes (and there may never have been any), we can return\n  // a false Maybe.\n  return kj::none;\n}\n\nvoid ActorCache::shutdown(kj::Maybe<const kj::Exception&> maybeException) {\n  if (maybeTerminalException == kj::none) {\n    auto exception = [&]() {\n      KJ_IF_SOME(e, maybeException) {\n        // We were given an exception, use it.\n        return kj::cp(e);\n      }\n\n      // Use the direct constructor so that we can reuse the constexpr message variable for testing.\n      auto exception = kj::Exception(kj::Exception::Type::DISCONNECTED, __FILE__, __LINE__,\n          kj::heapString(SHUTDOWN_ERROR_MESSAGE));\n\n      // Add trace info sufficient to tell us which operation caused the failure.\n      exception.addTraceHere();\n      exception.addTrace(__builtin_return_address(0));\n      return exception;\n    }();\n\n    // Any scheduled flushes will fail once `flushImpl()` is invoked and notices that\n    // `maybeTerminalException` has a value. Any in-flight flushes will continue to run in the\n    // background. Remember that these in-flight flushes may or may not be awaited by the worker,\n    // but they still hold the output lock as long as `allowUnconfirmed` wasn't used.\n    maybeTerminalException.emplace(kj::mv(exception));\n\n    // We explicitly do not schedule a flush to break the output gate. This means that if a request\n    // is ongoing after the actor cache is shutting down, the output gate is only broken if they\n    // had to send a flush after shutdown, either from a scheduled flush or a retry after failure.\n  } else {\n    // We've already experienced a terminal exception either from shutdown or OOM, there should\n    // already be a flush scheduled that will break the output gate.\n  }\n}\n\nconstexpr size_t bytesToWordsRoundUp(size_t bytes) {\n  return (bytes + sizeof(capnp::word) - 1) / sizeof(capnp::word);\n}\n\nnamespace {\nusing RpcPutRequest = capnp::Request<rpc::ActorStorage::Operations::PutParams,\n    rpc::ActorStorage::Operations::PutResults>;\n\nusing RpcDeleteRequest = capnp::Request<rpc::ActorStorage::Operations::DeleteParams,\n    rpc::ActorStorage::Operations::DeleteResults>;\n}  // namespace\n\nkj::Promise<void> ActorCache::startFlushTransaction() {\n  // Whenever we flush, we MUST write ALL dirty entries in a single transaction. This is necessary\n  // because our cache design doesn't necessarily remember the order in which writes were\n  // originally initiated, and thus it's not possible to choose a consistent prefix of writes\n  // to transact at once. In particular, when two writes occur on the same key with no (successful)\n  // flush in between, the first value is thrown away and never written at all. If we then wanted\n  // to perform a partial write that brings storage up-to-date with some point in time between the\n  // first and second puts, we wouldn't be able to, because we don't have the old value.\n  //\n  // Perhaps this would be possible to fix by adding more complex logic. But, it doesn't seem\n  // like a big deal to require all flushes to be complete flushes.\n\n  // We don't take a lock on `lru.cleanList` here, because we don't need it. We only access\n  // `dirtyList`, which is only ever accessed within the actor's thread, so it's safe. We know\n  // that `SharedLru` will only ever mess with CLEAN entries, which we don't look at here.\n\n  // We have three kinds of writes: Puts, counted deletes, and muted deletes. Counted deletes are\n  // delete operations for which the application still wants to know exactly how many keys are\n  // actually deleted. We must make a separate RPC call for each counted delete, in order to get\n  // the counts back. But we may also have deletes where the application doesn't need to know the\n  // count, either because it discarded the promise already, or because we were able to determine\n  // the count based on cache. We call these \"muted\" deletes, and we can batch them all together.\n  // We can also batch all the puts together, because applications don't expect puts to return\n  // anything.\n  //\n  // There's another wrinkle, which is that we don't want to send more than 128 keys per batch.\n  // This per-batch limit is historically enforced by our storage back-end\n  // (supervisor/actor-storage.c++). Truth be told, the limit is artificial and the original\n  // motivations for it don't apply anymore. However, splitting huge batches into smaller ones is\n  // beneficial to avoid writing overly large capnp messages and other reasons. So, for puts and\n  // muted deletes, we go ahead and construct batches of no more than 128 keys. They all end up\n  // being part of the same transaction in the end, though.\n  //\n  // TODO(perf): Currently we send all the batches at the same time. If the batches are large,\n  //   it could be worth spacing them out a bit so we don't saturate the connection. However, we\n  //   still need to make sure that the whole transaction represents a consistent snapshot in time,\n  //   so getting this right, without making a copy of everything upfront, could get complicated.\n  //   Punting for now.\n\n  PutFlush putFlush;\n  MutedDeleteFlush mutedDeleteFlush;\n\n  auto includeInCurrentBatch = [this](kj::Vector<FlushBatch>& batches, size_t words) {\n    KJ_ASSERT(words < MAX_ACTOR_STORAGE_RPC_WORDS);\n\n    if (batches.empty()) {\n      // This is the first one, let's just set up a current batch.\n      batches.add(FlushBatch{});\n    } else if (auto& tailBatch = batches.back(); tailBatch.pairCount >= lru.options.maxKeysPerRpc ||\n               ((tailBatch.wordCount + words) > MAX_ACTOR_STORAGE_RPC_WORDS)) {\n      // We've filled this batch, add a new one.\n      batches.add(FlushBatch{});\n    }\n\n    auto& batch = batches.back();\n    ++batch.pairCount;\n    batch.wordCount += words;\n  };\n\n  kj::Vector<CountedDeleteFlush> countedDeleteFlushes(countedDeletes.size());\n  for (auto countedDelete: countedDeletes) {\n    if (countedDelete->isFinished) {\n      // This countedDelete has already be executed, but we haven't delivered the final count to\n      // the waiter yet. We'll skip it here since the destructor of CountedDeleteWaiter should\n      // eventually remove this entry from `countedDeletes`.\n      continue;\n    }\n\n    // We might have successfully deleted these entries, but had the broader transaction fail.\n    // In that case, we might have entries that have since been overwritten, and which no longer\n    // need to be scheduled for deletion.\n    kj::Vector<kj::Own<Entry>> entriesToDelete(countedDelete->entries.size());\n    for (auto& entry: countedDelete->entries) {\n      if (entry->overwritingCountedDelete && countedDelete->completedInTransaction) {\n        // Not only is this a retry, but we have since modified the entry with a put().\n        // Since we already have the delete count, we don't need to delete this entry again.\n        continue;\n      }\n      entriesToDelete.add(kj::mv(entry));\n    }\n\n    // We will skip this CountedDelete if there are no entries that need to be deleted.\n    // It will be removed from `countedDeletes` by the next flush.\n    if (entriesToDelete.empty()) {\n      continue;\n    }\n\n    auto& countedDeleteFlush = countedDeleteFlushes.add(CountedDeleteFlush{\n      .countedDelete = kj::addRef(*countedDelete),\n    });\n    // Now that we've filtered our entries down to only those that need to be deleted,\n    // we need to overwrite the CountedDelete's `entries`.\n    countedDelete->entries = kj::mv(entriesToDelete);\n    for (auto& entry: countedDelete->entries) {\n      // A delete() call on this key is waiting to find out if the key existed in storage. Since\n      // each delete() call needs to return the count of keys deleted, we must issue\n      // corresponding delete calls to storage with the same batching, so that storage returns\n      // the right counts to us. We can't batch all the deletes into a single delete operation\n      // since then we'd only get a single count back and we wouldn't know how to split that up\n      // to satisfy all the callers.\n      //\n      // Note that a subsequent put() call could have set entry.value to non-null, but we still\n      // have to perform the delete first in order to determine the count that the delete() call\n      // should return.\n      //\n      // There is a minor quirk here because the counted delete set does not distinguish between\n      // before and after a delete all. That's actually okay because we should be able to\n      // immediately resolve counted deletes requested after a delete all (either the values are\n      // absent or they have a dirty put). This might also be an issue if we respected noCache for\n      // delete all's dummy value, but we do not.\n      entry->flushStarted = true;\n\n      auto keySizeInWords = bytesToWordsRoundUp(entry->key.size());\n      auto words = keySizeInWords + 1;\n      includeInCurrentBatch(countedDeleteFlush.batches, words);\n    }\n  }\n\n  auto countEntry = [&](Entry& entry) {\n    // Counts up the number of operations and RPC message sizes we'll need to cover this entry.\n\n    if (entry.isCountedDelete) {\n      // We should have already put this entry into a batch, so just skip it.\n      KJ_ASSERT(entry.flushStarted);\n      return;\n    }\n\n    entry.flushStarted = true;\n\n    auto keySizeInWords = bytesToWordsRoundUp(entry.key.size());\n\n    KJ_IF_SOME(v, entry.getValuePtr()) {\n      auto words = keySizeInWords + bytesToWordsRoundUp(v.size()) +\n          capnp::sizeInWords<rpc::ActorStorage::KeyValue>();\n      includeInCurrentBatch(putFlush.batches, words);\n      putFlush.entries.add(kj::atomicAddRef(entry));\n    } else {\n      auto words = keySizeInWords + 1;\n      includeInCurrentBatch(mutedDeleteFlush.batches, words);\n      mutedDeleteFlush.entries.add(kj::atomicAddRef(entry));\n    }\n  };\n\n  MaybeAlarmChange maybeAlarmChange = CleanAlarm{};\n  KJ_SWITCH_ONEOF(currentAlarmTime) {\n    KJ_CASE_ONEOF(knownAlarmTime, ActorCache::KnownAlarmTime) {\n      if (knownAlarmTime.status == KnownAlarmTime::Status::DIRTY ||\n          knownAlarmTime.status == KnownAlarmTime::Status::FLUSHING) {\n        knownAlarmTime.status = KnownAlarmTime::Status::FLUSHING;\n        maybeAlarmChange = DirtyAlarm{knownAlarmTime.time};\n      }\n    }\n    KJ_CASE_ONEOF(deferredDelete, ActorCache::DeferredAlarmDelete) {\n      if (deferredDelete.status == DeferredAlarmDelete::Status::READY ||\n          deferredDelete.status == DeferredAlarmDelete::Status::FLUSHING) {\n        deferredDelete.status = DeferredAlarmDelete::Status::FLUSHING;\n        maybeAlarmChange = DirtyAlarm{kj::none};\n      }\n    }\n    KJ_CASE_ONEOF(_, UnknownAlarmTime) {}\n  }\n\n  // We have to remember _before_ waiting for the flush whether or not it was a pre-deleteAll()\n  // flush. Otherwise, if it wasn't, but someone calls deleteAll() while we're flushing, then\n  // `requestedDeleteAll` might be non-null afterwards, but that would not indicate that we were\n  // ready to issue the delete-all.\n  KJ_IF_SOME(r, requestedDeleteAll) {\n    for (auto& entry: r.deletedDirty) {\n      countEntry(*entry);\n    }\n  } else {\n    for (auto& entry: dirtyList) {\n      countEntry(entry);\n    }\n  }\n\n  // We don't want to write anything until we know that any past reads have completed, because one\n  // or more of those reads could have been on the previous value of a key that was then overwritten\n  // by a put() that we're about to flush, and we don't want it to be possible for that read to end\n  // up receiving a value that was written later (especially if the read retries due to a\n  // disconnect).\n  //\n  // In practice, most code probably will not have any reads in flight when a flush occurs.\n  //\n  // Note that we have cached strong references to all entries we intend to mutate above. This means\n  // that we can be confident that flushing the cached set will not conflict with future reads\n  // because:\n  // - All our cached entries are dirty.\n  // - Dirty entries can only be removed from the cache map if replaced by a new dirty entry.\n  // - Thus all new read requests for our cached entries keys will be served from cache.\n  co_await waitForPastReads();\n\n  // Actually flush out the changes.\n  auto useTransactionToFlush = [&]() {\n    return flushImplUsingTxn(kj::mv(putFlush), kj::mv(mutedDeleteFlush),\n        countedDeleteFlushes.releaseAsArray(), kj::mv(maybeAlarmChange));\n  };\n\n  uint typesOfDataToFlush = 0;\n  if (!putFlush.batches.empty()) {\n    ++typesOfDataToFlush;\n  }\n  if (!mutedDeleteFlush.batches.empty()) {\n    ++typesOfDataToFlush;\n  }\n  if (!countedDeleteFlushes.empty()) {\n    ++typesOfDataToFlush;\n  }\n  if (maybeAlarmChange.is<DirtyAlarm>()) {\n    ++typesOfDataToFlush;\n  }\n\n  if (typesOfDataToFlush == 0) {\n    // Oh, nothing to do.\n  } else if (typesOfDataToFlush > 1) {\n    // We have multiple types of operations, so we have to use a transaction.\n    co_await useTransactionToFlush();\n  } else if (maybeAlarmChange.is<DirtyAlarm>()) {\n    // We only had an alarm, we can skip the transaction.\n    co_await flushImplAlarmOnly(maybeAlarmChange.get<DirtyAlarm>());\n  } else if (putFlush.batches.size() == 1) {\n    // As an optimization for the common case where there are only puts and they all fit in a\n    // single batch, just send a simple put rather than complicating things with a transaction.\n    co_await flushImplUsingSinglePut(kj::mv(putFlush));\n  } else if (mutedDeleteFlush.batches.size() == 1) {\n    // Same as for puts, but for muted deletes.\n    co_await flushImplUsingSingleMutedDelete(kj::mv(mutedDeleteFlush));\n  } else if (countedDeleteFlushes.size() == 1 && countedDeleteFlushes[0].batches.size() == 1) {\n    // Same as for puts, but for muted deletes.\n    co_await flushImplUsingSingleCountedDelete(kj::mv(countedDeleteFlushes[0]));\n  } else {\n    // None of the special cases above triggered. Default to using a transaction in all other cases,\n    // such as when there are so many keys to be flushed that they don't fit into a single batch.\n    co_await useTransactionToFlush();\n  }\n}\n\nkj::Promise<void> ActorCache::flushImpl(uint retryCount) {\n  KJ_IF_SOME(e, maybeTerminalException) {\n    // If we have a terminal exception, throw here to break the output gate and prevent any calls\n    // to storage. This does not use `requireNotTerminal()` so that we don't recursively schedule\n    // flushes.\n    kj::throwFatalException(kj::cp(e));\n  }\n\n  auto flushProm = startFlushTransaction();\n\n  bool flushingBeforeDeleteAll = requestedDeleteAll != kj::none;\n  return oomCanceler.wrap(kj::mv(flushProm))\n      .then([this, flushingBeforeDeleteAll]() -> kj::Promise<void> {\n    // We need to process the alarm result before we (potentially) start the delete all because if\n    // we did not our alarm state can't know if it need to flush a new time or not after the delete\n    // all. This might be another reason why delete all should not be considered truly deleting the\n    // durable object: alarms are not cleared by a delete all.\n    KJ_SWITCH_ONEOF(currentAlarmTime) {\n      KJ_CASE_ONEOF(knownAlarmTime, ActorCache::KnownAlarmTime) {\n        if (knownAlarmTime.status == KnownAlarmTime::Status::FLUSHING) {\n          if (knownAlarmTime.noCache) {\n            currentAlarmTime = UnknownAlarmTime{};\n          } else {\n            knownAlarmTime.status = KnownAlarmTime::Status::CLEAN;\n          }\n        }\n      }\n      KJ_CASE_ONEOF(deferredDelete, ActorCache::DeferredAlarmDelete) {\n        if (deferredDelete.status == DeferredAlarmDelete::Status::FLUSHING) {\n          bool wasDeleted = KJ_ASSERT_NONNULL(deferredDelete.wasDeleted);\n          if (deferredDelete.noCache || !wasDeleted) {\n            currentAlarmTime = UnknownAlarmTime{};\n          } else {\n            currentAlarmTime = KnownAlarmTime{.status = KnownAlarmTime::Status::CLEAN,\n              .time = kj::none,\n              .noCache = deferredDelete.noCache};\n          }\n        }\n      }\n      KJ_CASE_ONEOF(_, ActorCache::UnknownAlarmTime) {}\n    }\n    if (flushingBeforeDeleteAll) {\n      // The writes we flushed were writes that had occurred before a deleteAll. Now that they are\n      // written, we must perform the deleteAll() itself.\n      return flushImplDeleteAll();\n    }\n\n    auto lock = lru.cleanList.lockExclusive();\n\n    KJ_IF_SOME(r, requestedDeleteAll) {\n      // It would appear that all dirty entries were moved into `requestedDeleteAll` during the\n      // time that we were waiting for the flushImpl(). We want to remove the flushing entries\n      // from that vector now.\n      // TODO(cleanup): kj::Vector<T>::filter() would be nice to have here.\n      auto dst = r.deletedDirty.begin();\n      for (auto src = r.deletedDirty.begin(); src != r.deletedDirty.end(); ++src) {\n        if (!src->get()->flushStarted) {\n          if (dst != src) *dst = kj::mv(*src);\n          ++dst;\n        }\n      }\n      r.deletedDirty.resize(dst - r.deletedDirty.begin());\n    } else {\n      // Mark all flushing entries as `CLEAN`. Note that we know that all flushing entries must\n      // form a prefix of `dirtyList` since any new entries would have been added to the end.\n      for (auto& entry: dirtyList) {\n        if (!entry.flushStarted) {\n          // Completed all flushing entries.\n          break;\n        }\n\n        KJ_ASSERT(entry.flushStarted);\n\n        // We know all `countedDelete` operations were satisfied so we can remove this if it's\n        // present. The `CountedDeleteWaiters` will resolve once the flush is finished, and will\n        // remove the `CountedDelete`s from `countedDeletes`. Even if it doesn't happen by the\n        // next flush, each `CountedDelete` should have `isFinished` set so even if we encounter it\n        // next flush we won't attempt to delete again.\n        entry.isCountedDelete = false;\n\n        dirtyList.remove(entry);\n        if (entry.noCache) {\n          entry.setNotInCache();\n          evictEntry(lock, entry);\n        } else {\n          if (entry.gapIsKnownEmpty && entry.getValueStatus() == EntryValueStatus::ABSENT) {\n            // This is a negative entry, and is followed by a known-empty gap. If the previous entry\n            // also has `gapIsKnownEmpty`, then this entry is entirely redundant.\n            auto& map = currentValues.get(lock);\n            auto entryIter = map.seek(entry.key);\n            KJ_ASSERT(entryIter->get() == &entry);\n\n            if (entryIter != map.ordered().begin()) {\n              auto prevIter = entryIter;\n              --prevIter;\n              if (prevIter->get()->gapIsKnownEmpty) {\n                // Yep!\n                entry.setNotInCache();\n                map.erase(*entryIter);\n                // WARNING: We might have just deleted `entry`.\n                continue;\n              }\n            }\n          }\n\n          addToCleanList(lock, entry);\n        }\n      }\n    }\n\n    evictOrOomIfNeeded(lock);\n\n    return kj::READY_NOW;\n  }, [this, retryCount](kj::Exception&& e) -> kj::Promise<void> {\n    static const size_t MAX_RETRIES = 4;\n    if (e.getType() == kj::Exception::Type::DISCONNECTED && retryCount < MAX_RETRIES) {\n      return flushImpl(retryCount + 1);\n    } else if (jsg::isTunneledException(e.getDescription()) ||\n        jsg::isDoNotLogException(e.getDescription())) {\n      // Before passing along the exception, give it the proper brokenness reason.\n      // We were overriding any exception that came through here by ioGateBroken (now outputGateBroken).\n      // without checking for previous brokenness reasons we would be unable to throw\n      // exceededConcurrentStorageOps at all.\n      auto msg = jsg::stripRemoteExceptionPrefix(e.getDescription());\n      if (!(msg.startsWith(\"broken.\"))) {\n        e.setDescription(kj::str(\"broken.outputGateBroken; \", msg));\n      }\n      return kj::mv(e);\n    } else {\n      if (isInterestingException(e)) {\n        LOG_EXCEPTION(\"actorCacheFlush\", e);\n      } else {\n        LOG_NOSENTRY(ERROR, \"actor cache flush failed\", e);\n      }\n      // Pass through exception type to convey appropriate retry behavior.\n      return kj::Exception(e.getType(), __FILE__, __LINE__,\n          kj::str(\"broken.outputGateBroken; jsg.Error: Internal error in Durable \"\n                  \"Object storage write caused object to be reset.\"));\n    }\n  });\n}\n\nkj::Promise<void> ActorCache::flushImplUsingSinglePut(PutFlush putFlush) {\n  KJ_ASSERT(putFlush.batches.size() == 1);\n  auto& batch = putFlush.batches[0];\n\n  KJ_ASSERT(batch.wordCount < MAX_ACTOR_STORAGE_RPC_WORDS);\n  KJ_ASSERT(batch.pairCount == putFlush.entries.size());\n\n  auto request = storage.putRequest(capnp::MessageSize{4 + batch.wordCount, 0});\n  auto list = request.initEntries(batch.pairCount);\n  auto entryIt = putFlush.entries.begin();\n  for (auto kv: list) {\n    auto& entry = **(entryIt++);\n    auto v = KJ_ASSERT_NONNULL(entry.getValuePtr());\n    kv.setKey(entry.key.asBytes());\n    kv.setValue(v);\n  }\n\n  // We're done with the batching instructions, free them before we go async.\n  putFlush.entries.clear();\n  putFlush.batches.clear();\n  {\n    auto writeObserver = recordStorageWrite(hooks, clock);\n    util::DurationExceededLogger logger(\n        clock, 1 * kj::SECONDS, \"storage operation took longer than expected: single put\");\n    co_await request.sendIgnoringResult();\n  }\n}\n\nkj::Promise<void> ActorCache::flushImplUsingSingleMutedDelete(MutedDeleteFlush mutedFlush) {\n  KJ_ASSERT(mutedFlush.batches.size() == 1);\n  auto& batch = mutedFlush.batches[0];\n\n  KJ_ASSERT(batch.wordCount < MAX_ACTOR_STORAGE_RPC_WORDS);\n  KJ_ASSERT(batch.pairCount == mutedFlush.entries.size());\n\n  auto request = storage.deleteRequest(capnp::MessageSize{4 + batch.wordCount, 0});\n  auto listBuilder = request.initKeys(batch.pairCount);\n  auto entryIt = mutedFlush.entries.begin();\n  for (size_t i = 0; i < batch.pairCount; ++i) {\n    auto& entry = **(entryIt++);\n    listBuilder.set(i, entry.key.asBytes());\n  }\n\n  // We're done with the batching instructions, free them before we go async.\n  mutedFlush.entries.clear();\n  mutedFlush.batches.clear();\n\n  {\n    auto writeObserver = recordStorageWrite(hooks, clock);\n    util::DurationExceededLogger logger(\n        clock, 1 * kj::SECONDS, \"storage operation took longer than expected: muted delete\");\n    co_await request.sendIgnoringResult();\n  }\n}\n\nkj::Promise<void> ActorCache::flushImplUsingSingleCountedDelete(CountedDeleteFlush countedFlush) {\n  KJ_ASSERT(countedFlush.batches.size() == 1);\n  auto& batch = countedFlush.batches[0];\n\n  auto countedDelete = kj::mv(countedFlush.countedDelete);\n  KJ_ASSERT(batch.wordCount < MAX_ACTOR_STORAGE_RPC_WORDS);\n  KJ_ASSERT(batch.pairCount == countedDelete->entries.size());\n\n  auto request = storage.deleteRequest(capnp::MessageSize{4 + batch.wordCount, 0});\n  auto listBuilder = request.initKeys(batch.pairCount);\n  auto entryIt = countedDelete->entries.begin();\n  for (size_t i = 0; i < batch.pairCount; ++i) {\n    auto& entry = **(entryIt++);\n    listBuilder.set(i, entry.key.asBytes());\n  }\n\n  // We're done with the batching instructions, free them before we go async.\n  countedFlush.batches.clear();\n\n  auto writeObserver = recordStorageWrite(hooks, clock);\n  util::DurationExceededLogger logger(\n      clock, 1 * kj::SECONDS, \"storage operation took longer than expected: counted delete\");\n  auto response = co_await request.send();\n  countedDelete->countDeleted += response.getNumDeleted();\n  countedDelete->isFinished = true;\n}\n\nkj::Promise<void> ActorCache::flushImplAlarmOnly(DirtyAlarm dirty) {\n  auto writeObserver = recordStorageWrite(hooks, clock);\n  util::DurationExceededLogger logger(\n      clock, 1 * kj::SECONDS, \"storage operation took longer than expected: set/delete alarm\");\n\n  // TODO(someday) This could be templated to reuse the same code for this and the transaction case.\n  // Handle alarm writes first, since they're simplest.\n  KJ_IF_SOME(newTime, dirty.newTime) {\n    auto req = storage.setAlarmRequest();\n    req.setScheduledTimeMs((newTime - kj::UNIX_EPOCH) / kj::MILLISECONDS);\n    co_await req.sendIgnoringResult();\n    co_return;\n  } else {\n    // Alarm deletes are a bit trickier because we have to take DeferredAlarmDeletes into account.\n    auto req = storage.deleteAlarmRequest();\n    KJ_IF_SOME(deferredDelete, currentAlarmTime.tryGet<DeferredAlarmDelete>()) {\n      if (deferredDelete.status == DeferredAlarmDelete::Status::FLUSHING) {\n        req.setTimeToDeleteMs((deferredDelete.timeToDelete - kj::UNIX_EPOCH) / kj::MILLISECONDS);\n        auto response = co_await req.send();\n        KJ_IF_SOME(deferredDelete, currentAlarmTime.tryGet<DeferredAlarmDelete>()) {\n          if (deferredDelete.status == DeferredAlarmDelete::Status::FLUSHING) {\n            // We always update wasDeleted regardless of whether or not it is true\n            // because this continuation can succeed even if the greater transaction fails,\n            // and so we want to make sure we end up with the correct value if the first\n            // attempt succeeds to delete, the txn fails, and the retry fails to delete.\n            // The early update is OK because we don't actually use the incorrect state until\n            // the transaction succeeds in the .then() below.\n            deferredDelete.wasDeleted = response.getDeleted();\n          }\n        }\n      } else {\n        // Not sending a delete request for WAITING or READY is intentional. The WAITING\n        // state refers to when the alarm run has started but has not completed successfully,\n        // and READY is set when the run completes -- only FLUSHING indicates we actually\n        // need to send a request.\n      }\n    } else {\n      co_await req.send();\n    }\n  }\n}\n\nkj::Promise<void> ActorCache::flushImplUsingTxn(PutFlush putFlush,\n    MutedDeleteFlush mutedDeleteFlush,\n    CountedDeleteFlushes countedDeleteFlushes,\n    MaybeAlarmChange maybeAlarmChange) {\n  auto txnProm = storage.txnRequest(capnp::MessageSize{4, 0}).send();\n  auto txn = txnProm.getTransaction();\n\n  struct RpcCountedDelete {\n    kj::Own<CountedDelete> countedDelete;\n    kj::Array<RpcDeleteRequest> rpcDeletes;\n  };\n  auto rpcCountedDeletes = kj::heapArrayBuilder<RpcCountedDelete>(countedDeleteFlushes.size());\n  auto rpcMutedDeletes = kj::heapArrayBuilder<RpcDeleteRequest>(mutedDeleteFlush.batches.size());\n  auto rpcPuts = kj::heapArrayBuilder<RpcPutRequest>(putFlush.batches.size());\n\n  for (auto& flush: countedDeleteFlushes) {\n    auto countedDelete = kj::mv(flush.countedDelete);\n    auto entryIt = countedDelete->entries.begin();\n    kj::Vector<RpcDeleteRequest> rpcDeletes;\n    for (auto& batch: flush.batches) {\n      KJ_ASSERT(batch.wordCount < MAX_ACTOR_STORAGE_RPC_WORDS);\n\n      auto request = txn.deleteRequest(capnp::MessageSize{4 + batch.wordCount, 0});\n      auto listBuilder = request.initKeys(batch.pairCount);\n      for (size_t i = 0; i < batch.pairCount; ++i) {\n        KJ_ASSERT(entryIt != countedDelete->entries.end());\n        auto& entry = **(entryIt++);\n        listBuilder.set(i, entry.key.asBytes());\n      }\n\n      rpcDeletes.add(kj::mv(request));\n    }\n    KJ_ASSERT(entryIt == countedDelete->entries.end());\n    rpcCountedDeletes.add(RpcCountedDelete{\n      .countedDelete = kj::mv(countedDelete),\n      .rpcDeletes = rpcDeletes.releaseAsArray(),\n    });\n  }\n  countedDeleteFlushes = nullptr;\n\n  {\n    auto entryIt = mutedDeleteFlush.entries.begin();\n    for (auto& batch: mutedDeleteFlush.batches) {\n      KJ_ASSERT(batch.wordCount < MAX_ACTOR_STORAGE_RPC_WORDS);\n\n      auto request = txn.deleteRequest(capnp::MessageSize{4 + batch.wordCount, 0});\n      auto listBuilder = request.initKeys(batch.pairCount);\n      for (size_t i = 0; i < batch.pairCount; ++i) {\n        KJ_ASSERT(entryIt != mutedDeleteFlush.entries.end());\n        auto& entry = **(entryIt++);\n        listBuilder.set(i, entry.key.asBytes());\n      }\n      rpcMutedDeletes.add(kj::mv(request));\n    }\n    KJ_ASSERT(entryIt == mutedDeleteFlush.entries.end());\n  }\n  mutedDeleteFlush.entries.clear();\n  mutedDeleteFlush.batches.clear();\n\n  {\n    auto entryIt = putFlush.entries.begin();\n    for (auto& batch: putFlush.batches) {\n      KJ_ASSERT(batch.wordCount < MAX_ACTOR_STORAGE_RPC_WORDS);\n\n      auto request = txn.putRequest(capnp::MessageSize{4 + batch.wordCount, 0});\n      auto listBuilder = request.initEntries(batch.pairCount);\n      for (auto kv: listBuilder) {\n        KJ_ASSERT(entryIt != putFlush.entries.end());\n        auto& entry = **(entryIt++);\n        auto v = KJ_ASSERT_NONNULL(entry.getValuePtr());\n        kv.setKey(entry.key.asBytes());\n        kv.setValue(v);\n      }\n      rpcPuts.add(kj::mv(request));\n    }\n    KJ_ASSERT(entryIt == putFlush.entries.end());\n  }\n  putFlush.entries.clear();\n  putFlush.batches.clear();\n\n  // Send all the RPCs. It's important that counted deletes are sent first since they can overlap\n  // with puts. Specifically this can happen if someone does a delete() immediately followed by a\n  // put() on the same key. These two writes may have been coalesced into a single flush.\n  // Unfortunately, we can't just skip the delete because we still need to count it. So we issue\n  // a delete, followed by a put, in the same transaction.\n  // The constant extra 2 promises are those added outside of the rpc batches, currently one\n  // to work around a bug in capnp::autoreconnect, and one to actually commit the flush txn\n  // A 3rd promise may be added to write the alarm time if necessary.\n  auto promises = kj::heapArrayBuilder<kj::Promise<void>>(rpcPuts.size() + rpcMutedDeletes.size() +\n      rpcCountedDeletes.size() + 2 + !maybeAlarmChange.is<CleanAlarm>());\n\n  auto joinCountedDelete = [](RpcCountedDelete& rpcCountedDelete) -> kj::Promise<void> {\n    auto promises = KJ_MAP(request, rpcCountedDelete.rpcDeletes) {\n      return request.send().then(\n          [](capnp::Response<rpc::ActorStorage::Operations::DeleteResults>&& response) mutable\n          -> uint { return response.getNumDeleted(); });\n    };\n\n    size_t recordsDeleted = 0;\n    for (auto& promise: promises) {\n      recordsDeleted += co_await promise;\n    }\n\n    // This may be a retry following a successful counted delete within a failed transaction.\n    // In that case, we don't want to update the count again, since we've already considered it.\n    if (!rpcCountedDelete.countedDelete->completedInTransaction) {\n      // We only increment our `countDeleted` if *ALL* the delete batches succeeded.\n      rpcCountedDelete.countedDelete->countDeleted += recordsDeleted;\n    }\n\n    // This delete succeeded, but we may need to retry it in some cases, ex. if the transaction fails.\n    // If we *do* retry after a successful counted delete, we won't want to update our\n    // `countDeleted` since we already got it.\n    rpcCountedDelete.countedDelete->completedInTransaction = true;\n  };\n\n  for (auto& rpcCountedDelete: rpcCountedDeletes) {\n    promises.add(joinCountedDelete(rpcCountedDelete));\n  }\n\n  for (auto& request: rpcMutedDeletes) {\n    promises.add(request.sendIgnoringResult());\n  }\n\n  for (auto& request: rpcPuts) {\n    promises.add(request.sendIgnoringResult());\n  }\n\n  KJ_SWITCH_ONEOF(maybeAlarmChange) {\n    KJ_CASE_ONEOF(dirty, DirtyAlarm) {\n      KJ_IF_SOME(newTime, dirty.newTime) {\n        auto req = txn.setAlarmRequest();\n        req.setScheduledTimeMs((newTime - kj::UNIX_EPOCH) / kj::MILLISECONDS);\n        promises.add(req.sendIgnoringResult());\n      } else {\n        auto req = txn.deleteAlarmRequest();\n        KJ_IF_SOME(deferredDelete, currentAlarmTime.tryGet<DeferredAlarmDelete>()) {\n          if (deferredDelete.status == DeferredAlarmDelete::Status::FLUSHING) {\n            req.setTimeToDeleteMs(\n                (deferredDelete.timeToDelete - kj::UNIX_EPOCH) / kj::MILLISECONDS);\n            auto prom = req.send().then([this](auto response) {\n              KJ_IF_SOME(deferredDelete, currentAlarmTime.tryGet<DeferredAlarmDelete>()) {\n                if (deferredDelete.status == DeferredAlarmDelete::Status::FLUSHING) {\n                  // We always update wasDeleted regardless of whether or not it is true\n                  // because this continuation can succeed even if the greater transaction fails,\n                  // and so we want to make sure we end up with the correct value if the first\n                  // attempt succeeds to delete, the txn fails, and the retry fails to delete.\n                  // The early update is OK because we don't actually use the incorrect state until\n                  // the transaction succeeds in the .then() below.\n                  deferredDelete.wasDeleted = response.getDeleted();\n                }\n              }\n            });\n            promises.add(kj::mv(prom));\n          }\n          // Not sending a delete request for WAITING or READY is intentional. The WAITING\n          // state refers to when the alarm run has started but has not completed successfully,\n          // and READY is set when the run completes -- only FLUSHING indicates we actually\n          // need to send a request.\n        } else {\n          promises.add(req.sendIgnoringResult());\n        }\n      }\n    }\n    KJ_CASE_ONEOF(_, CleanAlarm) {}\n  }\n\n  // We have to wait on the transaction promise so we don't cancel the catch_ branch that triggers\n  // our autoReconnect logic on storage failures.\n  // TODO(cleanup): We should probably fix ReconnectHook so the catch_ doesn't get canceled\n  // if the promise is dropped but the pipeline stays alive.\n  promises.add(txnProm.ignoreResult());\n\n  {\n    auto writeObserver = recordStorageWrite(hooks, clock);\n    util::DurationExceededLogger logger(clock, 1 * kj::SECONDS,\n        \"storage operation took longer than expected: commit flush transaction\");\n    promises.add(txn.commitRequest(capnp::MessageSize{4, 0}).sendIgnoringResult());\n\n    co_await kj::joinPromises(promises.finish());\n    for (auto& rpcCountedDelete: rpcCountedDeletes) {\n      // Now that the transaction has successfully completed, we can mark all our CountedDeletes\n      // as having completed as well.\n      rpcCountedDelete.countedDelete->isFinished = true;\n    }\n  }\n}\n\nkj::Promise<void> ActorCache::flushImplDeleteAll(uint retryCount) {\n  // By this point, we've completed any writes that had originally been performed before\n  // deleteAll() was called, and we're ready to perform the deleteAll() itself.\n  //\n  // Note that we intentionally don't time deleteAll() with hooks.startStorageWrite() because it's\n  // expected to be much slower than all other storage operations, taking linear time with respect\n  // to how much data is stored in the actor.\n\n  KJ_ASSERT(requestedDeleteAll != kj::none);\n\n  return storage.deleteAllRequest(capnp::MessageSize{2, 0})\n      .send()\n      .then(\n          [this](capnp::Response<rpc::ActorStorage::Operations::DeleteAllResults> results)\n              -> kj::Promise<void> {\n    auto& deleteAllState = KJ_ASSERT_NONNULL(requestedDeleteAll);\n    deleteAllState.countFulfiller->fulfill(results.getNumDeleted());\n    bool shouldDeleteAlarm = deleteAllState.deleteAlarm;\n\n    // Success! We can now null out `requestedDeleteAll`. Note that we don't have to worry about\n    // `requestedDeleteAll` having changed since we flushed it earlier, because it can't change\n    // until it is first nulled out. If deleteAll() is called multiple times before the first one\n    // finishes, subsequent ones see `requestedDeleteAll` is already non-null and they don't change\n    // it. Instead, the writes that occurred between the deleteAll()s are simply discarded, as if\n    // the two deleteAll()s had been coalesced into a single one.\n    requestedDeleteAll = kj::none;\n\n    if (shouldDeleteAlarm) {\n      // The deleteAll() was requested with deleteAlarm=true. Now that the deleteAll RPC has\n      // succeeded, we dirty the alarm to kj::none so that it will be flushed in the post-deleteAll\n      // flushImpl() call below. This ordering ensures the alarm is only deleted after KV data has\n      // been successfully deleted.\n      //\n      // However, if currentAlarmTime is already DIRTY, that means a subsequent setAlarm() call\n      // was made after the deleteAll() was initiated, and that newer alarm value should take\n      // precedence over the deletion.\n      bool alreadyDirty = false;\n      KJ_IF_SOME(known, currentAlarmTime.tryGet<KnownAlarmTime>()) {\n        alreadyDirty = known.status == KnownAlarmTime::Status::DIRTY;\n      }\n      if (!alreadyDirty) {\n        currentAlarmTime = ActorCache::KnownAlarmTime{\n          .status = ActorCache::KnownAlarmTime::Status::DIRTY,\n          .time = kj::none,\n        };\n      }\n    }\n\n    {\n      auto lock = lru.cleanList.lockExclusive();\n      evictOrOomIfNeeded(lock);\n    }\n\n    // Now we must flush any writes that happened after the deleteAll(). (If there are none, this\n    // will complete quickly.)\n    // TODO(soon) This will use the write options for the deleteAll() even if the options for future\n    // operations differ. This can mean that we will not wait for the output gate when we were asked\n    // to do so. We should fix this.\n    return flushImpl();\n  },\n          [this, retryCount](kj::Exception&& e) -> kj::Promise<void> {\n    static const size_t MAX_RETRIES = 4;\n    if (e.getType() == kj::Exception::Type::DISCONNECTED && retryCount < MAX_RETRIES) {\n      return flushImplDeleteAll(retryCount + 1);\n    } else if (jsg::isTunneledException(e.getDescription()) ||\n        jsg::isDoNotLogException(e.getDescription())) {\n      // Before passing along the exception, give it the proper brokenness reason.\n      auto msg = jsg::stripRemoteExceptionPrefix(e.getDescription());\n      e.setDescription(kj::str(\"broken.outputGateBroken; \", msg));\n      return kj::mv(e);\n    } else {\n      if (isInterestingException(e)) {\n        LOG_EXCEPTION(\"actorCacheDeleteAll\", e);\n      } else {\n        LOG_NOSENTRY(ERROR, \"actorCacheDeleteAll failed\", e);\n      }\n      // Pass through exception type to convey appropriate retry behavior.\n      return kj::Exception(e.getType(), __FILE__, __LINE__,\n          kj::str(\n              \"broken.outputGateBroken; jsg.Error: Internal error in Durable Object storage deleteAll() caused object to be reset.\"));\n    }\n  });\n}\n\n// =======================================================================================\n// ActorCache::Transaction\n\nActorCache::Transaction::Transaction(ActorCache& cache): cache(cache) {}\nActorCache::Transaction::~Transaction() noexcept(false) {\n  // If not commit()ed... we don't have to do anything in particular here, just drop the changes.\n}\n\nkj::Maybe<kj::Promise<void>> ActorCache::Transaction::commit() {\n  {\n    auto lock = cache.lru.cleanList.lockExclusive();\n    for (auto& change: entriesToWrite) {\n      cache.putImpl(lock, kj::mv(change.entry), change.options, kj::none, commitSpan.addRef());\n    }\n    entriesToWrite.clear();\n    cache.evictOrOomIfNeeded(lock);\n  }\n\n  KJ_IF_SOME(change, alarmChange) {\n    cache.setAlarm(change.newTime, change.options, commitSpan.addRef());\n  }\n  alarmChange = kj::none;\n\n  return cache.getBackpressure();\n}\n\nkj::Promise<void> ActorCache::Transaction::rollback() {\n  entriesToWrite.clear();\n  alarmChange = kj::none;\n  return kj::READY_NOW;\n}\n\n// -----------------------------------------------------------------------------\n// transaction reads\n\nkj::OneOf<kj::Maybe<ActorCache::Value>, kj::Promise<kj::Maybe<ActorCache::Value>>> ActorCache::\n    Transaction::get(Key key, ReadOptions options) {\n  options.noCache = options.noCache || cache.lru.options.noCache;\n  KJ_IF_SOME(change, entriesToWrite.find(key)) {\n    return change.entry->getValue();\n  } else {\n    return cache.get(kj::mv(key), options);\n  }\n}\n\nkj::OneOf<ActorCache::GetResultList, kj::Promise<ActorCache::GetResultList>> ActorCache::\n    Transaction::get(kj::Array<Key> keys, ReadOptions options) {\n  options.noCache = options.noCache || cache.lru.options.noCache;\n\n  kj::Vector<kj::Own<Entry>> changedEntries;\n  kj::Vector<Key> keysToFetch;\n\n  for (auto& key: keys) {\n    KJ_IF_SOME(change, entriesToWrite.find(key)) {\n      changedEntries.add(kj::atomicAddRef(*change.entry));\n    } else {\n      keysToFetch.add(kj::mv(key));\n    }\n  }\n\n  std::sort(changedEntries.begin(), changedEntries.end(),\n      [](auto& a, auto& b) { return a.get()->key < b.get()->key; });\n\n  return merge(kj::mv(changedEntries), cache.get(keysToFetch.releaseAsArray(), options),\n      GetResultList::FORWARD);\n}\n\nkj::OneOf<kj::Maybe<kj::Date>, kj::Promise<kj::Maybe<kj::Date>>> ActorCache::Transaction::getAlarm(\n    ReadOptions options) {\n  options.noCache = options.noCache || cache.lru.options.noCache;\n  KJ_IF_SOME(a, alarmChange) {\n    return a.newTime;\n  } else {\n    return cache.getAlarm(options);\n  }\n}\n\nkj::OneOf<ActorCache::GetResultList, kj::Promise<ActorCache::GetResultList>> ActorCache::\n    Transaction::list(Key begin, kj::Maybe<Key> end, kj::Maybe<uint> limit, ReadOptions options) {\n  options.noCache = options.noCache || cache.lru.options.noCache;\n  kj::Vector<kj::Own<Entry>> changedEntries;\n  if (limit.orDefault(kj::maxValue) == 0 || begin >= end) {\n    // No results in these cases, just return.\n    return ActorCache::GetResultList(kj::mv(changedEntries), {}, GetResultList::REVERSE);\n  }\n  auto beginIter = entriesToWrite.seek(begin);\n  auto endIter = seekOrEnd(entriesToWrite, end);\n  uint positiveCount = 0;\n  // TODO(cleanup): Add `iterRange()` to KJ's public interface.\n  for (auto& change: kj::_::iterRange(beginIter, endIter)) {\n    changedEntries.add(kj::atomicAddRef(*change.entry));\n    if (change.entry->getValueStatus() == EntryValueStatus::PRESENT) {\n      ++positiveCount;\n    }\n    if (positiveCount == limit.orDefault(kj::maxValue)) break;\n  }\n\n  // Increase limit to make sure it can't be underrun by negative entries negating it.\n  limit = limit.map([&](uint n) { return n + (changedEntries.size() - positiveCount); });\n\n  return merge(kj::mv(changedEntries), cache.list(kj::mv(begin), kj::mv(end), limit, options),\n      GetResultList::FORWARD);\n}\n\nkj::OneOf<ActorCache::GetResultList, kj::Promise<ActorCache::GetResultList>> ActorCache::\n    Transaction::listReverse(\n        Key begin, kj::Maybe<Key> end, kj::Maybe<uint> limit, ReadOptions options) {\n  options.noCache = options.noCache || cache.lru.options.noCache;\n  kj::Vector<kj::Own<Entry>> changedEntries;\n  if (limit.orDefault(kj::maxValue) == 0 || begin >= end) {\n    // No results in these cases, just return.\n    return ActorCache::GetResultList(kj::mv(changedEntries), {}, GetResultList::REVERSE);\n  }\n  auto beginIter = entriesToWrite.seek(begin);\n  auto endIter = seekOrEnd(entriesToWrite, end);\n  uint positiveCount = 0;\n  for (auto iter = endIter; iter != beginIter;) {\n    --iter;\n    changedEntries.add(kj::atomicAddRef(*iter->entry));\n    if (iter->entry->getValueStatus() == EntryValueStatus::PRESENT) {\n      ++positiveCount;\n    }\n    if (positiveCount == limit.orDefault(kj::maxValue)) break;\n  }\n\n  // Increase limit to make sure it can't be underrun by negative entries negating it.\n  limit = limit.map([&](uint n) { return n + (changedEntries.size() - positiveCount); });\n\n  return merge(kj::mv(changedEntries),\n      cache.listReverse(kj::mv(begin), kj::mv(end), limit, options), GetResultList::REVERSE);\n}\n\nkj::OneOf<ActorCache::GetResultList, kj::Promise<ActorCache::GetResultList>> ActorCache::\n    Transaction::merge(kj::Vector<kj::Own<Entry>> changedEntries,\n        kj::OneOf<GetResultList, kj::Promise<GetResultList>> cacheRead,\n        GetResultList::Order order) {\n  KJ_SWITCH_ONEOF(cacheRead) {\n    KJ_CASE_ONEOF(results, GetResultList) {\n      return GetResultList(kj::mv(changedEntries), kj::mv(results.entries), order);\n    }\n    KJ_CASE_ONEOF(promise, kj::Promise<GetResultList>) {\n      return promise.then(\n          [changedEntries = kj::mv(changedEntries), order](GetResultList results) mutable {\n        return GetResultList(kj::mv(changedEntries), kj::mv(results.entries), order);\n      });\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\n// -----------------------------------------------------------------------------\n// transaction writes\n\nkj::Maybe<kj::Promise<void>> ActorCache::Transaction::put(\n    Key key, Value value, WriteOptions options, SpanParent traceSpan) {\n  options.noCache = options.noCache || cache.lru.options.noCache;\n  auto lock = cache.lru.cleanList.lockExclusive();\n  auto entry = kj::atomicRefcounted<Entry>(cache, kj::mv(key), kj::mv(value));\n  putImpl(lock, kj::mv(entry), options);\n\n  // Capture span for use at commit time\n  commitSpan = kj::mv(traceSpan);\n\n  // Don't apply backpressure because transactions can't be flushed anyway.\n  return kj::none;\n}\n\nkj::Maybe<kj::Promise<void>> ActorCache::Transaction::put(\n    kj::Array<KeyValuePair> pairs, WriteOptions options, SpanParent traceSpan) {\n  options.noCache = options.noCache || cache.lru.options.noCache;\n  auto lock = cache.lru.cleanList.lockExclusive();\n\n  for (auto& pair: pairs) {\n    auto entry = kj::atomicRefcounted<Entry>(cache, kj::mv(pair.key), kj::mv(pair.value));\n    putImpl(lock, kj::mv(entry), options);\n  }\n\n  // Capture span for use at commit time\n  commitSpan = kj::mv(traceSpan);\n\n  // Don't apply backpressure because transactions can't be flushed anyway.\n  return kj::none;\n}\n\nkj::Maybe<kj::Promise<void>> ActorCache::Transaction::setAlarm(\n    kj::Maybe<kj::Date> newTime, WriteOptions options, SpanParent traceSpan) {\n  options.noCache = options.noCache || cache.lru.options.noCache;\n  alarmChange = DirtyAlarmWithOptions{DirtyAlarm{newTime}, options};\n\n  // Capture span for use at commit time\n  commitSpan = kj::mv(traceSpan);\n\n  return kj::none;\n}\n\nkj::OneOf<bool, kj::Promise<bool>> ActorCache::Transaction::delete_(\n    Key key, WriteOptions options, SpanParent traceSpan) {\n  options.noCache = options.noCache || cache.lru.options.noCache;\n\n  uint count = 0;\n  kj::Maybe<KeyPtr> keyToCount;\n\n  {\n    auto lock = cache.lru.cleanList.lockExclusive();\n    auto entry = kj::atomicRefcounted<Entry>(cache, kj::mv(key), EntryValueStatus::ABSENT);\n    keyToCount = putImpl(lock, kj::mv(entry), options, count);\n  }\n\n  // Capture span for use at commit time\n  commitSpan = kj::mv(traceSpan);\n\n  KJ_IF_SOME(k, keyToCount) {\n    // Unfortunately, to find out the count, we have to do a read.\n    KJ_SWITCH_ONEOF(cache.get(cloneKey(k), {})) {\n      KJ_CASE_ONEOF(value, kj::Maybe<ActorCache::Value>) {\n        return value != kj::none;\n      }\n      KJ_CASE_ONEOF(promise, kj::Promise<kj::Maybe<ActorCache::Value>>) {\n        return promise.then([](kj::Maybe<ActorCache::Value> value) { return value != kj::none; });\n      }\n    }\n    KJ_UNREACHABLE;\n  } else {\n    return count > 0;\n  }\n}\n\nkj::OneOf<uint, kj::Promise<uint>> ActorCache::Transaction::delete_(\n    kj::Array<Key> keys, WriteOptions options, SpanParent traceSpan) {\n  options.noCache = options.noCache || cache.lru.options.noCache;\n\n  if (keys.size() == 0) {\n    return 0u;\n  }\n\n  uint count = 0;\n  kj::Vector<kj::Vector<Key>> keysToCount;\n  auto startNewBatch = [&]() { return &keysToCount.add(); };\n  auto currentBatch = startNewBatch();\n\n  {\n    auto lock = cache.lru.cleanList.lockExclusive();\n    for (auto& key: keys) {\n      auto entry = kj::atomicRefcounted<Entry>(cache, kj::mv(key), EntryValueStatus::ABSENT);\n      KJ_IF_SOME(keyToCount, putImpl(lock, kj::mv(entry), options, count)) {\n        if (currentBatch->size() >= cache.lru.options.maxKeysPerRpc) {\n          currentBatch = startNewBatch();\n        }\n        currentBatch->add(cloneKey(keyToCount));\n      }\n    }\n  }\n\n  // Capture span for use at commit time\n  commitSpan = kj::mv(traceSpan);\n\n  if (keysToCount.empty()) {\n    return count;\n  } else {\n    // HACK: Since we allow deletes of larger than our maxKeysPerRpc but these deletes can provoke\n    // gets, we need to batch said gets. This all would be much simpler if our default get behavior\n    // did batching/sync.\n    kj::Maybe<kj::Promise<uint>> maybeTotalPromise;\n    for (auto& batch: keysToCount) {\n      // Unfortunately, to find out the count, we have to do a read. Note that even returning this\n      // value separate from a committed transaction means that non-transaction storage ops can make\n      // the value incorrect.\n      KJ_SWITCH_ONEOF(cache.get(batch.releaseAsArray(), {})) {\n        KJ_CASE_ONEOF(results, GetResultList) {\n          count = count + results.size();\n        }\n        KJ_CASE_ONEOF(promise, kj::Promise<GetResultList>) {\n          if (maybeTotalPromise == kj::none) {\n            // We had to do a remote get, start a promise\n            maybeTotalPromise.emplace(0);\n          }\n          maybeTotalPromise = KJ_ASSERT_NONNULL(maybeTotalPromise)\n                                  .then([promise = kj::mv(promise)](\n                                            uint previousResult) mutable -> kj::Promise<uint> {\n            return promise.then([previousResult](GetResultList results) mutable -> uint {\n              return previousResult + kj::implicitCast<uint>(results.size());\n            });\n          });\n        }\n      }\n    }\n\n    KJ_IF_SOME(totalPromise, maybeTotalPromise) {\n      return totalPromise.then([count](uint result) { return count + result; });\n    } else {\n      return count;\n    }\n  }\n}\n\nkj::Maybe<ActorCache::KeyPtr> ActorCache::Transaction::putImpl(\n    Lock& lock, kj::Own<Entry> entry, const WriteOptions& options, kj::Maybe<uint&> count) {\n  Change change{\n    .entry = kj::mv(entry),\n    .options = options,\n  };\n  bool replaced = false;\n  auto& slot = entriesToWrite.upsert(kj::mv(change), [&](auto& existing, auto&& replacement) {\n    replaced = true;\n    KJ_IF_SOME(c, count) {\n      c += existing.entry->getValueStatus() == EntryValueStatus::PRESENT;\n    }\n    existing = kj::mv(replacement);\n  });\n  if (replaced) {\n    // Already counted.\n    return kj::none;\n  } else {\n    return KeyPtr(slot.entry->key);\n  }\n}\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/actor-cache.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/io/actor-storage.capnp.h>\n#include <workerd/io/trace.h>\n#include <workerd/jsg/exception.h>\n\n#include <kj/async.h>\n#include <kj/debug.h>\n#include <kj/list.h>\n#include <kj/map.h>\n#include <kj/mutex.h>\n#include <kj/one-of.h>\n#include <kj/time.h>\n\n#include <atomic>\n\nnamespace workerd {\n\nusing kj::byte;\nusing kj::uint;\nclass OutputGate;\nclass SqliteDatabase;\nclass SqliteKv;\n\nstruct ActorCacheReadOptions {\n  // If the entry is not already in cache and has to be read from disk, don't store the result in\n  // cache, only return it to the caller.\n  //\n  // If there is already a matching entry in cache, that value will be returned as normal. Hence,\n  // `noCache` does not affect consistency, only performance.\n  bool noCache = false;\n};\n\nstruct ActorCacheWriteOptions {\n  // Instructs that the output gate should not wait for this write to be confirmed on disk. Write\n  // failures will still break the output gate -- but the application could potentially return a\n  // result before the failure is observed, leading to a prematurely confirmed write.\n  bool allowUnconfirmed = false;\n\n  // Once the value has been confirmed written to disk, immediately evict it from the cache.\n  //\n  // Until the value is safely on disk, the dirty value will be used to fulfill reads for the same\n  // key.  Hence, `noCache` does not affect consistency, only performance.\n  bool noCache = false;\n};\n\nstruct DeleteAllOptions {\n  // When true, deleteAll() will also delete any scheduled alarm. The alarm deletion is\n  // guaranteed to take effect only after the deleteAll() itself succeeds, so that we never\n  // end up in a state where the alarm is deleted but KV data remains.\n  bool deleteAlarm = false;\n};\n\n// Common interface between ActorCache and ActorCache::Transaction.\nclass ActorCacheOps {\n public:\n  using Key = kj::String;\n  using KeyPtr = kj::StringPtr;\n  // Keys are text for now, but we could also change this to `Array<const byte>`.\n  static inline Key cloneKey(KeyPtr ptr) {\n    return kj::str(ptr);\n  }\n\n  // Values are raw bytes.\n  using Value = kj::Array<const byte>;\n  using ValuePtr = kj::ArrayPtr<const byte>;\n\n  struct KeyValuePair {\n    Key key;\n    Value value;\n  };\n  struct KeyValuePtrPair {\n    KeyPtr key;\n    ValuePtr value;\n\n    KeyValuePtrPair(const KeyValuePair& other): key(other.key), value(other.value) {}\n    KeyValuePtrPair(KeyPtr key, ValuePtr value): key(key), value(value) {}\n  };\n\n  struct KeyRename {\n    Key oldKey;\n    Key newKey;\n  };\n\n  enum class CacheStatus { CACHED, UNCACHED };\n\n  struct KeyValuePtrPairWithCache: public KeyValuePtrPair {\n    CacheStatus status;\n\n    KeyValuePtrPairWithCache(const KeyValuePtrPair& other, CacheStatus status)\n        : KeyValuePtrPair(other),\n          status(status) {}\n    KeyValuePtrPairWithCache(const KeyValuePtrPairWithCache& other)\n        : KeyValuePtrPair(other.key, other.value),\n          status(other.status) {}\n    KeyValuePtrPairWithCache(KeyPtr key, ValuePtr value, CacheStatus status)\n        : KeyValuePtrPair(key, value),\n          status(status) {}\n  };\n\n  // An iterable type where each element is a KeyValuePtrPair.\n  class GetResultList;\n\n  struct CleanAlarm {};\n\n  struct DirtyAlarm {\n    kj::Maybe<kj::Date> newTime;\n  };\n\n  using MaybeAlarmChange = kj::OneOf<CleanAlarm, DirtyAlarm>;\n\n  struct DirtyAlarmWithOptions: public DirtyAlarm {\n    ActorCacheWriteOptions options;\n  };\n\n  using ReadOptions = ActorCacheReadOptions;\n\n  // Get the values for some key, keys, or range of keys.\n  //\n  // Returns a Maybe<Value> or GetResultList if the result is immediately available from cache,\n  // otherwise returns a Promise and fetches the results from storage.\n  //\n  // `listReverse()` lists in reverse, which turns out to require a subtly different implementation\n  // of pretty much the entire algorithm.\n  //\n  // Passing a null key for `end` means list through the last key in the actor. For `begin`, you\n  // can pass an empty string to list from the first key in the actor, since the empty string is\n  // the first possible key.\n  virtual kj::OneOf<kj::Maybe<Value>, kj::Promise<kj::Maybe<Value>>> get(\n      Key key, ReadOptions options) = 0;\n  virtual kj::OneOf<GetResultList, kj::Promise<GetResultList>> get(\n      kj::Array<Key> keys, ReadOptions options) = 0;\n  virtual kj::OneOf<kj::Maybe<kj::Date>, kj::Promise<kj::Maybe<kj::Date>>> getAlarm(\n      ReadOptions options) = 0;\n  virtual kj::OneOf<GetResultList, kj::Promise<GetResultList>> list(\n      Key begin, kj::Maybe<Key> end, kj::Maybe<uint> limit, ReadOptions options) = 0;\n  virtual kj::OneOf<GetResultList, kj::Promise<GetResultList>> listReverse(\n      Key begin, kj::Maybe<Key> end, kj::Maybe<uint> limit, ReadOptions options) = 0;\n\n  using WriteOptions = ActorCacheWriteOptions;\n\n  // Writes a key/value into cache and schedules it to be flushed to disk later.\n  //\n  // The cache will automatically arrange to flush changes to disk, adding the flush to the\n  // `OutputGate` passed to the constructor.\n  //\n  // This returns a promise for backpressure. If a promise is returned, the application should\n  // delay further puts until the promise resolves. This happens when too much data is pinned in\n  // cache because writes haven't been flushed to disk yet. Dropping this promise will not cancel\n  // the put.\n  //\n  // The traceSpan parameter is used for output gate lock hold tracing. It is captured only by\n  // the first write that starts a new flush batch.\n  virtual kj::Maybe<kj::Promise<void>> put(\n      Key key, Value value, WriteOptions options, SpanParent traceSpan) = 0;\n  virtual kj::Maybe<kj::Promise<void>> put(\n      kj::Array<KeyValuePair> pairs, WriteOptions options, SpanParent traceSpan) = 0;\n\n  // Writes a new alarm time into cache and schedules it to be flushed to disk later, same as put().\n  virtual kj::Maybe<kj::Promise<void>> setAlarm(\n      kj::Maybe<kj::Date> newTime, WriteOptions options, SpanParent traceSpan) = 0;\n\n  // Delete the given keys.\n  //\n  // Returns a `bool` or `uint` if it can be immediately determined from cache how many keys were\n  // present before the call. Otherwise, returns a promise which resolves after getting a response\n  // from underlying storage. The promise also applies backpressure if needed, as with put().\n  //\n  // The traceSpan parameter is used for output gate lock hold tracing.\n  virtual kj::OneOf<bool, kj::Promise<bool>> delete_(\n      Key key, WriteOptions options, SpanParent traceSpan) = 0;\n  virtual kj::OneOf<uint, kj::Promise<uint>> delete_(\n      kj::Array<Key> keys, WriteOptions options, SpanParent traceSpan) = 0;\n};\n\n// Abstract interface that is implemented by ActorCache as well as ActorSqlite.\n//\n// This extends ActorCacheOps and adds some methods that don't make sense as part of\n// ActorCache::Transaction.\nclass ActorCacheInterface: public ActorCacheOps {\n public:\n  // If the actor's storage is backed by SQLite, return the underlying database.\n  virtual kj::Maybe<SqliteDatabase&> getSqliteDatabase() = 0;\n\n  // If the actor's storage is backed by SQLite, return the SqliteKv object which provides a\n  // synchronous interface to KV storage. This is only available for SQLite-basked DOs because\n  // old-style DOs have asyncronous storage.\n  virtual kj::Maybe<SqliteKv&> getSqliteKv() = 0;\n\n  class Transaction: public ActorCacheOps {\n   public:\n    // Write all changes to the underlying ActorCache.\n    //\n    // If commit() is not called before the Transaction is destroyed, nothing is written.\n    //\n    // Returns a promise if backpressure needs to be applied (like ActorCache::put()).\n    //\n    // This will NOT detect conflicts, it will always just write blindly, because conflicts\n    // inherently cannot happen.\n    virtual kj::Maybe<kj::Promise<void>> commit() = 0;\n\n    virtual kj::Promise<void> rollback() = 0;\n  };\n\n  virtual kj::Own<Transaction> startTransaction() = 0;\n\n  // We split these up so client code that doesn't need the count doesn't have to\n  // wait for it just to account for backpressure\n  struct DeleteAllResults {\n    kj::Maybe<kj::Promise<void>> backpressure;\n    kj::Promise<uint> count;\n  };\n\n  // Delete everything in the actor's storage. This is not part of ActorCacheOps because it\n  // is not supported as part of a transaction.\n  //\n  // The returned count only includes keys that were actually deleted from storage, not keys in\n  // cache -- we only use the returned deleteAll count for billing, and not counting deletes of\n  // entries that are only in cache is no problem for billing, those deletes don't cost us anything.\n  virtual DeleteAllResults deleteAll(\n      WriteOptions options, SpanParent traceSpan, DeleteAllOptions deleteAllOptions = {}) = 0;\n\n  // Call each time the isolate lock is taken to evict stale entries. If this returns a promise,\n  // then the caller must hold off on JavaScript execution until the promise resolves -- this\n  // creates back pressure when the write queue is too deep.\n  //\n  // (This takes a Date rather than a TimePoint because it is based on Date.now(), to avoid\n  // bypassing Spectre mitigations.)\n  virtual kj::Maybe<kj::Promise<void>> evictStale(kj::Date now) = 0;\n\n  virtual void shutdown(kj::Maybe<const kj::Exception&> maybeException) = 0;\n\n  // Possible armAlarmHandler() return values:\n  //\n  // Alarm should be canceled without retry (because alarm state has changed such that the\n  // requested alarm time is no longer valid).\n  struct CancelAlarmHandler {\n    // Caller should wait for this promise to complete before canceling.\n    kj::Promise<void> waitBeforeCancel;\n  };\n  // Alarm should be run.\n  struct RunAlarmHandler {\n    // RAII object to delete the alarm, if object is destroyed before setAlarm() or\n    // cancelDeferredAlarmDeletion() are called.  Caller should attach it to a promise\n    // representing the alarm handler's execution.\n    kj::Own<void> deferredDelete;\n  };\n\n  // Call when entering the alarm handler.\n  //\n  // `currentTime` is used to determine if an overdue alarm should run immediately even when\n  // the local alarm state differs from the scheduled time (to avoid blocking on storage sync).\n  virtual kj::OneOf<CancelAlarmHandler, RunAlarmHandler> armAlarmHandler(kj::Date scheduledTime,\n      SpanParent parentSpan,\n      kj::Date currentTime,\n      bool noCache = false,\n      kj::StringPtr actorId = \"\") = 0;\n\n  virtual void cancelDeferredAlarmDeletion() = 0;\n\n  virtual kj::Maybe<kj::Promise<void>> onNoPendingFlush(SpanParent parentSpan) = 0;\n\n  // Implements the respective PITR API calls. The default implementations throw JSG errors saying\n  // PITR is not implemented. These methods are meant to be implemented internally.\n  virtual kj::Promise<kj::String> getCurrentBookmark(SpanParent parentSpan) {\n    JSG_FAIL_REQUIRE(\n        Error, \"This Durable Object's storage back-end does not implement point-in-time recovery.\");\n  }\n\n  virtual kj::Promise<kj::String> getBookmarkForTime(kj::Date timestamp) {\n    JSG_FAIL_REQUIRE(\n        Error, \"This Durable Object's storage back-end does not implement point-in-time recovery.\");\n  }\n\n  virtual kj::Promise<kj::String> onNextSessionRestoreBookmark(kj::StringPtr bookmark) {\n    JSG_FAIL_REQUIRE(\n        Error, \"This Durable Object's storage back-end does not implement point-in-time recovery.\");\n  }\n\n  virtual kj::Promise<void> waitForBookmark(kj::StringPtr bookmark, SpanParent parentSpan) {\n    JSG_FAIL_REQUIRE(\n        Error, \"This Durable Object's storage back-end does not implement point-in-time recovery.\");\n  }\n\n  virtual void ensureReplicas() {\n    JSG_FAIL_REQUIRE(Error, \"This Durable Object's storage back-end does not support replication.\");\n  }\n\n  virtual void disableReplicas() {\n    JSG_FAIL_REQUIRE(Error, \"This Durable Object's storage back-end does not support replication.\");\n  }\n};\n\n// An in-memory caching layer on top of ActorStorage.Stage RPC interface.\n//\n// This cache assumes that it is the only client of the underlying storage -- which is, of\n// course, true for actors.\n//\n// Writes complete \"instantly\" -- but the OutputGate is told to block output until the write is\n// confirmed durable.\n//\n// Ordering is carefully preserved. A read will always return results consistent with the time\n// when it was called, never reflecting later writes -- even writes that are performed before\n// the read actually completes. Writes are never committed out-of-order (this is accomplished by\n// brute force -- the cache always performs a transaction committing all dirty keys at once).\n//\n// The cache implements LRU eviction triggered by both time and memory pressure. Memory usage is\n// accounted across many actors (typically, all actors in the same isolate), so that the cache\n// size limit can be set based on the per-isolate memory limit.\nclass ActorCache final: public ActorCacheInterface {\n public:\n  // Shared LRU for a whole isolate.\n  class SharedLru;\n\n  // Hooks that can be used to customize ActorCache behavior or report statistics.\n  class Hooks {\n   public:\n    // Called when the alarm time is dirty when neverFlush is set and ensureFlushScheduled is called.\n    virtual void updateAlarmInMemory(kj::Maybe<kj::Date> newAlarmTime) {};\n\n    // Used to track metrics of read and write operation latencies from the isolate's perspective.\n    virtual void storageReadCompleted(kj::Duration latency) {}\n    virtual void storageWriteCompleted(kj::Duration latency) {}\n\n    static const Hooks DEFAULT;\n  };\n\n  static constexpr auto SHUTDOWN_ERROR_MESSAGE =\n      \"broken.ignored; jsg.Error: \"\n      \"Durable Object storage is no longer accessible.\"_kj;\n\n  ActorCache(rpc::ActorStorage::Stage::Client storage,\n      const SharedLru& lru,\n      OutputGate& gate,\n      // Hooks has no member variables, so const_cast is acceptable.\n      Hooks& hooks = const_cast<Hooks&>(Hooks::DEFAULT));\n  ~ActorCache() noexcept(false);\n\n  kj::Maybe<SqliteDatabase&> getSqliteDatabase() override {\n    return kj::none;\n  }\n  kj::Maybe<SqliteKv&> getSqliteKv() override {\n    return kj::none;\n  }\n  kj::OneOf<kj::Maybe<Value>, kj::Promise<kj::Maybe<Value>>> get(\n      Key key, ReadOptions options) override;\n  kj::OneOf<GetResultList, kj::Promise<GetResultList>> get(\n      kj::Array<Key> keys, ReadOptions options) override;\n  kj::OneOf<kj::Maybe<kj::Date>, kj::Promise<kj::Maybe<kj::Date>>> getAlarm(\n      ReadOptions options) override;\n  kj::OneOf<GetResultList, kj::Promise<GetResultList>> list(\n      Key begin, kj::Maybe<Key> end, kj::Maybe<uint> limit, ReadOptions options) override;\n  kj::OneOf<GetResultList, kj::Promise<GetResultList>> listReverse(\n      Key begin, kj::Maybe<Key> end, kj::Maybe<uint> limit, ReadOptions options) override;\n  kj::Maybe<kj::Promise<void>> put(\n      Key key, Value value, WriteOptions options, SpanParent traceSpan) override;\n  kj::Maybe<kj::Promise<void>> put(\n      kj::Array<KeyValuePair> pairs, WriteOptions options, SpanParent traceSpan) override;\n  kj::OneOf<bool, kj::Promise<bool>> delete_(\n      Key key, WriteOptions options, SpanParent traceSpan) override;\n  kj::OneOf<uint, kj::Promise<uint>> delete_(\n      kj::Array<Key> keys, WriteOptions options, SpanParent traceSpan) override;\n  kj::Maybe<kj::Promise<void>> setAlarm(\n      kj::Maybe<kj::Date> newAlarmTime, WriteOptions options, SpanParent traceSpan) override;\n  // See ActorCacheOps.\n\n  kj::Own<ActorCacheInterface::Transaction> startTransaction() override;\n  DeleteAllResults deleteAll(\n      WriteOptions options, SpanParent traceSpan, DeleteAllOptions deleteAllOptions = {}) override;\n  kj::Maybe<kj::Promise<void>> evictStale(kj::Date now) override;\n  void shutdown(kj::Maybe<const kj::Exception&> maybeException) override;\n\n  kj::OneOf<CancelAlarmHandler, RunAlarmHandler> armAlarmHandler(kj::Date scheduledTime,\n      SpanParent parentSpan,\n      kj::Date currentTime,\n      bool noCache = false,\n      kj::StringPtr actorId = \"\") override;\n  void cancelDeferredAlarmDeletion() override;\n  kj::Maybe<kj::Promise<void>> onNoPendingFlush(SpanParent parentSpan) override;\n  // See ActorCacheInterface\n\n  class Transaction;\n  // Check for inconsistencies in the cache, e.g. redundant entries.\n  void verifyConsistencyForTest();\n\n private:\n  // Backs the `kj::Own<void>` returned by `armAlarmHandler()`.\n  class DeferredAlarmDeleter: public kj::Disposer {\n   public:\n    // The `Own<void>` returned by `armAlarmHandler()` is actually set up to point to the\n    // `ActorCache` itself, but with an alternate disposer that deletes the alarm rather than\n    // the whole object.\n    void disposeImpl(void* pointer) const override {\n      auto p = reinterpret_cast<ActorCache*>(pointer);\n      KJ_IF_SOME(d, p->currentAlarmTime.tryGet<DeferredAlarmDelete>()) {\n        d.status = DeferredAlarmDelete::Status::READY;\n        p->ensureFlushScheduled(WriteOptions{.noCache = d.noCache}, kj::mv(d.traceSpan));\n      }\n    }\n  };\n\n  enum class EntrySyncStatus : int8_t {\n    // The value was set by the app via put() or delete(), and we have not yet initiated a write\n    // to disk. The entry is appended to `dirtyList` whenever entering this state.\n    //\n    // Next state: CLEAN (if the flush succeeds) or NOT_IN_CACHE (if a new put()/delete()\n    // overwrites this entry first).\n    DIRTY,\n\n    // The entry matches what is currently on disk. The entry is currently present in the LRU\n    // queue.\n    //\n    // Next state: NOT_IN_CACHE (if a new put()/delete() overwrites the entry), or deleted (if\n    // evicted due to memory pressure).\n    CLEAN,\n\n    // This entry is not currently in the cache -- it is an orphaned object. This happens e.g. if\n    // a put() or delete() overwrote the entry, in which case the `Entry` object is removed from\n    // the map and replaced with a new object. The old object may continue to exist if it is still\n    // the subject of an outstanding get() which was initiated before the entry was overwritten.\n    // This is not the only use of NOT_IN_CACHE, but in general, any Entry which is not in the\n    // cache's `currentValues` map must have this state.\n    //\n    // Next state: deleted (Entry will be destroyed when the refcount reaches zero), or any\n    //   other state if the entry is inserted into the cache.\n    NOT_IN_CACHE\n  };\n\n  enum class EntryValueStatus : uint8_t {\n    // This entry has a known value. (Note that while there is nothing wrong per say with a value\n    // size of zero, v8 serialized data will always have a greater size.)\n    PRESENT,\n\n    // This entry is known to be absent.\n    ABSENT,\n\n    // This entry has not been fetched into cache yet, but the previous entry has `gapIsKnownEmpty =\n    // true`. Such entries are created as a result of list() operations, to mark the endpoint of the\n    // list range. List ranges are exclusive of their endpoint, hence the value associated with this\n    // key is commonly unknown.\n    UNKNOWN,\n  };\n\n  struct CountedDelete;\n\n  struct Entry: public kj::AtomicRefcounted {\n    // A cache entry.\n    //\n    // Entries are refcounted so that an operation which cares about a particular entry can keep\n    // it live even after it has been evicted or overwritten. In particular, because read\n    // operations are consistent with the time when read() was called, they may need to hold\n    // strong references to the entries they are reading, so that if the entries are overwritten,\n    // the read operation still has the original value from when it was called.\n    //\n    // The mutable content of an `Entry` is protected by the same mutex that protects\n    // `lru.cleanList`. `key` and `value` are declared `const` so that they can safely be used\n    // without a lock.\n\n    Entry(ActorCache& cache, Key key, Value value);\n    Entry(ActorCache& cache, Key key, EntryValueStatus status);\n    Entry(Key key, Value value);\n    Entry(Key key, EntryValueStatus status);\n    ~Entry() noexcept(false);\n    KJ_DISALLOW_COPY_AND_MOVE(Entry);\n\n    kj::Maybe<ActorCache&> maybeCache;\n    const Key key;\n\n   private:\n    // The value associated with this key. If our `valueStatus` below is `ABSENT` or `UNKNOWN`, it\n    // will have size 0.\n    //\n    // `value` cannot change after the `Entry` is constructed. When a key is overwritten, the\n    // existing `Entry` is removed from the map and replaced with a new one, so that `value` does\n    // not need to be modified. This allows us to avoid copying `Entry` objects by refcounting\n    // them instead, especially in the case of a read operation which is only partially fulfilled\n    // from cache and needs to remember the original cached values even if they are overwritten\n    // before the read completes.\n    const Value value;\n    EntryValueStatus valueStatus;\n\n    // This enum indicates how synchronized this entry is with storage.\n    EntrySyncStatus syncStatus = EntrySyncStatus::NOT_IN_CACHE;\n\n   public:\n    EntryValueStatus getValueStatus() const {\n      return valueStatus;\n    }\n\n    inline EntrySyncStatus getSyncStatus() const {\n      return syncStatus;\n    }\n\n    kj::Maybe<ValuePtr> getValuePtr() const {\n      if (valueStatus == EntryValueStatus::PRESENT) {\n        return value.asPtr();\n      } else {\n        return kj::none;\n      }\n    }\n    kj::Maybe<Value> getValue() const {\n      KJ_IF_SOME(ptr, getValuePtr()) {\n        return ptr.attach(kj::atomicAddRef(*this));\n      } else {\n        return kj::none;\n      }\n    }\n\n    void setNotInCache() {\n      syncStatus = EntrySyncStatus::NOT_IN_CACHE;\n    }\n\n    // Avoid using setClean() and setDirty() directly. If you want to set the status to CLEAN or\n    // DIRTY, consider using the addToCleanList() and addToDirtyList() methods. This helps us keep\n    // the state transitions manageable.\n    void setClean() {\n      syncStatus = EntrySyncStatus::CLEAN;\n    }\n\n    void setDirty() {\n      syncStatus = EntrySyncStatus::DIRTY;\n    }\n\n    bool isDirty() const {\n      switch (getSyncStatus()) {\n        case EntrySyncStatus::DIRTY: {\n          return true;\n        }\n        case EntrySyncStatus::CLEAN: {\n          return false;\n        }\n        case EntrySyncStatus::NOT_IN_CACHE: {\n          KJ_FAIL_ASSERT(\"NOT_IN_CACHE entries should not be in the map or flushing\");\n        }\n      }\n    }\n\n    bool isStale = false;\n    bool flushStarted = false;\n\n    // If true, then a past list() operation covered the space between this entry and the following\n    // entry, meaning that we know for sure that there are no other keys on disk between them.\n    bool gapIsKnownEmpty = false;\n\n    // If true, then this entry should be evicted from cache immediately when it becomes CLEAN.\n    // The entry still needs to reside in cache while DIRTY since we need to store it\n    // somewhere, and so we might as well serve cache hits based on it in the meantime.\n    bool noCache = false;\n\n    // In the DIRTY state, if this entry was originally created as the result of a\n    // `delete()` call, and as such the caller needs to receive a count of deletions, then this\n    // tracks that need. Note that only one caller could ever be waiting on this, because\n    // subsequent delete() calls can be counted based on the cache content. This can be false\n    // if no delete operations need a count from this entry.\n    bool isCountedDelete = false;\n\n    // This Entry is part of a CountedDelete, but has since been overwritten via a put().\n    // This is really only useful in determining if we need to retry the deletion of this entry from\n    // storage, since we're interested in the number of deleted records. If we already got the count,\n    // we won't include this entry as part of our retried delete.\n    bool overwritingCountedDelete = false;\n\n    // If CLEAN, the entry will be in the SharedLru's `cleanList`.\n    //\n    // If DIRTY, the entry will be in `dirtyList`.\n    kj::ListLink<Entry> link;\n\n    size_t size() const {\n      return sizeof(*this) + key.size() + value.size();\n    }\n  };\n\n  // Callbacks for a kj::TreeIndex for a kj::Table<kj::Own<Entry>>.\n  class EntryTableCallbacks {\n   public:\n    inline KeyPtr keyForRow(const kj::Own<Entry>& row) const {\n      return row->key;\n    }\n\n    inline bool isBefore(const kj::Own<Entry>& row, KeyPtr key) const {\n      return row->key < key;\n    }\n    inline bool isBefore(const kj::Own<Entry>& a, const kj::Own<Entry>& b) const {\n      return a->key < b->key;\n    }\n\n    inline bool matches(const kj::Own<Entry>& row, KeyPtr key) const {\n      return row->key == key;\n    }\n  };\n\n  // When delete() is called with one or more keys that aren't in cache, we will need to get\n  // feedback from the database in order to report a count of deletions back to the application.\n  // Entries that were originally added to the cache as part of such a `delete()` will reference\n  // a `CountedDelete`.\n  //\n  // This object can only be manipulated in the thread that owns the specific actor that made\n  // the request. That works out fine since CountedDelete only ever exists for dirty entries,\n  // which won't be touched cross-thread by the LRU.\n  struct CountedDelete final: public kj::Refcounted {\n    CountedDelete() = default;\n    KJ_DISALLOW_COPY_AND_MOVE(CountedDelete);\n\n    kj::Promise<void> forgiveIfFinished(kj::Promise<void> promise) {\n      try {\n        co_await promise;\n      } catch (...) {\n        if (isFinished) {\n          // We already flushed, so it's OK that the promise threw.\n          co_return;\n        } else {\n          throw;\n        }\n      }\n    }\n\n    // Running count of entries that existed before the delete.\n    uint countDeleted = 0;\n\n    // Did this particular counted delete succeed within a transaction? In other words, did we\n    // already get the count? Even if we got the count, we may need to retry if the transaction\n    // itself failed, though we won't need to get the count again.\n    bool completedInTransaction = false;\n\n    // Did this particular counted delete succeed? Note that this can be true even if the flush\n    // failed on a different batch of operations.\n    bool isFinished = false;\n\n    // The entries are associated with this counted delete.\n    kj::Vector<kj::Own<Entry>> entries;\n  };\n\n  class CountedDeleteWaiter {\n   public:\n    explicit CountedDeleteWaiter(ActorCache& cache, kj::Own<CountedDelete> state)\n        : cache(cache),\n          state(kj::mv(state)) {\n      // Register this operation so that we can batch it properly during flush.\n      cache.countedDeletes.insert(this->state.get());\n    }\n    KJ_DISALLOW_COPY_AND_MOVE(CountedDeleteWaiter);\n    ~CountedDeleteWaiter() noexcept(false) {\n      for (auto& entry: state->entries) {\n        // Let each entry associated with this counted delete know that we aren't waiting anymore.\n        entry->isCountedDelete = false;\n      }\n\n      // Since the count of deleted pairs is no longer required, we don't need to batch the ops.\n      // Note that we're doing eraseMatch since the pointer is a temporary literal.\n      cache.countedDeletes.eraseMatch(state.get());\n    }\n\n    const CountedDelete& getCountedDelete() const {\n      return *state;\n    }\n\n   private:\n    ActorCache& cache;\n    kj::Own<CountedDelete> state;\n  };\n\n  kj::HashSet<CountedDelete*> countedDeletes;\n\n  rpc::ActorStorage::Stage::Client storage;\n  const SharedLru& lru;\n  OutputGate& gate;\n  Hooks& hooks;\n  const kj::MonotonicClock& clock;\n\n  // Wrapper around kj::List that keeps track of the total size of all elements.\n  class DirtyList {\n   public:\n    void add(Entry& entry) {\n      inner.add(entry);\n      innerSize += entry.size();\n    }\n\n    void remove(Entry& entry) {\n      inner.remove(entry);\n      innerSize -= entry.size();\n    }\n\n    size_t sizeInBytes() {\n      return innerSize;\n    }\n\n    auto begin() {\n      return inner.begin();\n    }\n    auto end() {\n      return inner.end();\n    }\n\n   private:\n    kj::List<Entry, &Entry::link> inner;\n    size_t innerSize = 0;\n  };\n\n  // List of entries in DIRTY state. New dirty entries are added to the end. If any\n  // flushing entries are present, they always appear strictly before non-flushing entries.\n  DirtyList dirtyList;\n\n  // Map of current known values for keys. Searchable by key, including ordered iteration.\n  //\n  // This map is protected by the same lock as lru.cleanList. ExternalMutexGuarded helps enforce\n  // this.\n  kj::ExternalMutexGuarded<kj::Table<kj::Own<Entry>, kj::TreeIndex<EntryTableCallbacks>>>\n      currentValues;\n\n  struct UnknownAlarmTime {};\n  struct KnownAlarmTime {\n    enum class Status { CLEAN, DIRTY, FLUSHING } status;\n    kj::Maybe<kj::Date> time;\n    bool noCache = false;\n  };\n\n  // Used by armAlarmHandler to know if a write needs to happen after the handler finishes\n  // to clear the alarm time.\n  struct DeferredAlarmDelete {\n    enum class Status { WAITING, READY, FLUSHING } status;\n\n    // Set to a time to pass as `timeToDelete` when making the delete call.\n    kj::Date timeToDelete;\n\n    // When the delete finishes, set to whether or not it succeeded.\n    kj::Maybe<bool> wasDeleted;\n\n    bool noCache = false;\n\n    // Trace span for the alarm handler, used when scheduling the deferred alarm deletion flush.\n    SpanParent traceSpan = nullptr;\n  };\n\n  kj::OneOf<UnknownAlarmTime, KnownAlarmTime, DeferredAlarmDelete> currentAlarmTime =\n      UnknownAlarmTime{};\n\n  struct ReadCompletionChain: public kj::Refcounted {\n    kj::Maybe<kj::Own<ReadCompletionChain>> next;\n    kj::Maybe<kj::Own<kj::PromiseFulfiller<void>>> fulfiller;\n    ReadCompletionChain() = default;\n    ~ReadCompletionChain() noexcept(false);\n    KJ_DISALLOW_COPY_AND_MOVE(ReadCompletionChain);\n  };\n  // Used to implement waitForPastReads(). See that function to understand how it works...\n  kj::Own<ReadCompletionChain> readCompletionChain = kj::refcounted<ReadCompletionChain>();\n\n  // True if ensureFlushScheduled() has been called but the flush has not started yet.\n  bool flushScheduled = false;\n\n  // When flushScheduled is true, indicates whether the output gate is already waiting on said\n  // flush. The first write that does *not* set `allowUnconfirmed` causes the output gate to be\n  // applied.\n  bool flushScheduledWithOutputGate = false;\n\n  // Trace span for the current flush operation, captured from the first write that triggers\n  // a flush batch. Used for the output gate lock hold trace.\n  SpanParent currentFlushSpan = nullptr;\n\n  // The count of the number of flushes that have been queued without yet resolving.\n  size_t flushesEnqueued = 0;\n\n  struct DeleteAllState {\n    // If deleteAll() was called since the last flush, these are all the dirty entries that existed\n    // in the cache immediately before the deleteAll(). Since deleteAll() cannot be part of a\n    // transaction, in order to maintain ordering guarantees, we'll need to flush these entries\n    // first, then perform the deleteAll(), then flush any entries that were dirtied after the\n    // deleteAll().\n    kj::Vector<kj::Own<Entry>> deletedDirty;\n    kj::Own<kj::PromiseFulfiller<uint>> countFulfiller;\n\n    // If true, the alarm should also be deleted after the deleteAll() RPC succeeds.\n    bool deleteAlarm = false;\n  };\n\n  kj::Maybe<DeleteAllState> requestedDeleteAll;\n\n  // Promise for the completion of the previous flush. We can only execute one flushImpl() at a time\n  // because we can't allow out-of-order writes.\n  kj::ForkedPromise<void> lastFlush = kj::Promise<void>(kj::READY_NOW).fork();\n  // TODO(perf): If we could rely on e-order on the ActorStorage API, we could pipeline additional\n  //   writes and not have to worry about this. However, at present, ActorStorage has automatic\n  //   reconnect behavior at the supervisor layer which violates e-order.\n\n  // Did we hit a problem that makes the ActorCache unusable? If so this is the exception that\n  // describes the problem.\n  kj::Maybe<kj::Exception> maybeTerminalException;\n\n  // Will be canceled if and when `oomException` becomes non-null.\n  kj::Canceler oomCanceler;\n\n  // Type of a lock on `SharedLru::cleanList`. We use the same lock to protect `currentValues`.\n  using Lock = kj::Locked<kj::List<Entry, &Entry::link>>;\n\n  // Add this entry to the clean list and set its status to CLEAN.\n  // This doesn't do much, but it makes it easier to track what's going on.\n  void addToCleanList(Lock& listLock, Entry& entryRef) {\n    entryRef.setClean();\n    listLock->add(entryRef);\n  }\n\n  // Add this entry to the dirty list and set its status to DIRTY.\n  // This doesn't do much, but it makes it easier to track what's going on.\n  void addToDirtyList(Entry& entryRef) {\n    entryRef.setDirty();\n    dirtyList.add(entryRef);\n  }\n\n  // Indicate that an entry was observed by a read operation and so should be moved to the end of\n  // the LRU queue.\n  void touchEntry(Lock& lock, Entry& entry);\n\n  // TODO(soon) This function mostly belongs on the SharedLru, not the ActorCache. Notably,\n  // `removeEntry()` has to do with the shared clean list but `evictEntry()` has to do with\n  // the non-shared map. It is like this for now because generalizing the SharedLru into an\n  // IsolateCache is bigger work.\n  void removeEntry(Lock& lock, Entry& entry);\n\n  // Look for a key in cache, returning a strong reference on the matching entry.\n  //\n  // Note that the returned entry could have `EntryValueStatus::UNKNOWN` which means we do not know\n  // if it is in storage or `EntryValueStatus::ABSENT` which means we know it is not in storage.\n  kj::Own<Entry> findInCache(Lock& lock, KeyPtr key, const ReadOptions& options);\n\n  // Add an entry to the cache, where the entry was the result of reading from storage. If another\n  // entry with the same key has been inserted in the meantime, then the new entry will not be\n  // inserted and will instead immediately have state NOT_IN_CACHE.\n  //\n  // Either way, a strong reference to the entry is returned.\n  kj::Own<Entry> addReadResultToCache(\n      Lock& lock, Key key, kj::Maybe<capnp::Data::Reader> value, const ReadOptions& readOptions);\n\n  // Mark all gaps empty between the begin and end key.\n  void markGapsEmpty(Lock& lock, KeyPtr begin, kj::Maybe<KeyPtr> end, const ReadOptions& options);\n\n  // Implements put() or delete(). Multi-key variants call this for each key.\n  void putImpl(Lock& lock,\n      kj::Own<Entry> newEntry,\n      const WriteOptions& options,\n      kj::Maybe<CountedDelete&> counted,\n      SpanParent traceSpan);\n\n  kj::Promise<kj::Maybe<Value>> getImpl(kj::Own<Entry> entry, ReadOptions options);\n\n  // Ensure that we will flush dirty entries soon.\n  // The traceSpan is captured only on the first call that starts a new flush batch.\n  void ensureFlushScheduled(const WriteOptions& options, SpanParent traceSpan);\n\n  // Schedule a read RPC. The given function will be invoked and provided with an\n  // ActorStorage::Operations::Client on which the read operation should be performed. The function\n  // might be called multiple times. The first call may be synchronous.\n  //\n  // This method has two purposes:\n  // - Retry operations that fail due to disconnects.\n  // - Ensure that reads cannot be re-ordered after writes that were originally scheduled later.\n  //\n  // Note that `function()` must return a plain `Promise`, not a `capnp::RemotePromise`, because\n  // it is necessary to `.attach()` something to it. Use `.dropPipeline()` to convert a\n  // `RemotePromise` to a plain `Promise`.\n  template <typename Func>\n  kj::PromiseForResult<Func, rpc::ActorStorage::Operations::Client> scheduleStorageRead(\n      Func&& function);\n\n  // Wait until all read operations that are currently in-flight have completed or failed\n  // (including exhausting all retries). Does not propagate the read exception, if any. This is\n  // used for ordering, to make sure a write is not committed too early such that it interferes\n  // with a previous read.\n  kj::Promise<void> waitForPastReads();\n\n  kj::Promise<void> flushImpl(uint retryCount = 0);\n  kj::Promise<void> flushImplDeleteAll(uint retryCount = 0);\n\n  struct FlushBatch {\n    size_t pairCount = 0;\n    size_t wordCount = 0;\n  };\n  struct PutFlush {\n    kj::Vector<kj::Own<Entry>> entries;\n    kj::Vector<FlushBatch> batches;\n  };\n  struct MutedDeleteFlush {\n    kj::Vector<kj::Own<Entry>> entries;\n    kj::Vector<FlushBatch> batches;\n  };\n  struct CountedDeleteFlush {\n    kj::Own<CountedDelete> countedDelete;\n    kj::Vector<FlushBatch> batches;\n  };\n  using CountedDeleteFlushes = kj::Array<CountedDeleteFlush>;\n  kj::Promise<void> startFlushTransaction();\n  kj::Promise<void> flushImplUsingSinglePut(PutFlush putFlush);\n  kj::Promise<void> flushImplUsingSingleMutedDelete(MutedDeleteFlush mutedFlush);\n  kj::Promise<void> flushImplUsingSingleCountedDelete(CountedDeleteFlush countedFlush);\n  kj::Promise<void> flushImplAlarmOnly(DirtyAlarm dirty);\n  kj::Promise<void> flushImplUsingTxn(PutFlush putFlush,\n      MutedDeleteFlush mutedDeleteFlush,\n      CountedDeleteFlushes countedDeleteFlushes,\n      MaybeAlarmChange maybeAlarmChange);\n\n  // Carefully remove a clean entry from `currentValues`, making sure to update gaps.\n  void evictEntry(Lock& lock, Entry& entry);\n\n  // Drop the entire cache. Called during destructor and on OOM.\n  void clear(Lock& lock);\n\n  // Throws OOM exception if `oom` is true.\n  void requireNotTerminal(SpanParent traceSpan);\n\n  // Evict cache entries as needed to reach the target memory usage. If the cache has exceeded the\n  // hard limit, trigger an OOM, canceling all RPCs and breaking the output gate.\n  void evictOrOomIfNeeded(Lock& lock);\n\n  // If the LRU is currently over the soft limit, returns a promise that resolves when it is\n  // back under the limit.\n  kj::Maybe<kj::Promise<void>> getBackpressure();\n\n  class GetMultiStreamImpl;\n  class ForwardListStreamImpl;\n  class ReverseListStreamImpl;\n  friend class ActorCacheOps::GetResultList;\n};\n\nclass ActorCacheOps::GetResultList {\n  using Entry = ActorCache::Entry;\n\n public:\n  class Iterator {\n   public:\n    KeyValuePtrPairWithCache operator*() {\n      KJ_IREQUIRE(ptr->get()->getValueStatus() == ActorCache::EntryValueStatus::PRESENT);\n      return {ptr->get()->key, ptr->get()->getValuePtr().orDefault({}), *statusPtr};\n    }\n    Iterator& operator++() {\n      ++ptr;\n      ++statusPtr;\n      return *this;\n    }\n    Iterator operator++(int) {\n      auto copy = *this;\n      ++ptr;\n      ++statusPtr;\n      return copy;\n    }\n    bool operator==(const Iterator& other) const {\n      return ptr == other.ptr && statusPtr == other.statusPtr;\n    }\n\n   private:\n    const kj::Own<Entry>* ptr;\n    const CacheStatus* statusPtr;\n\n    explicit Iterator(const kj::Own<Entry>* ptr, const CacheStatus* statusPtr)\n        : ptr(ptr),\n          statusPtr(statusPtr) {}\n    friend class GetResultList;\n  };\n\n  Iterator begin() const {\n    return Iterator(entries.begin(), cacheStatuses.begin());\n  }\n  Iterator end() const {\n    return Iterator(entries.end(), cacheStatuses.end());\n  }\n  size_t size() const {\n    return entries.size();\n  }\n\n  // Construct a simple GetResultList from key-value pairs.\n  explicit GetResultList(kj::Vector<KeyValuePair> contents);\n\n private:\n  kj::Vector<kj::Own<Entry>> entries;\n  kj::Vector<CacheStatus> cacheStatuses;\n\n  enum Order { FORWARD, REVERSE };\n\n  // Merges `cachedEntries` and `fetchedEntries`, which should each already be sorted in the\n  // given order. If a key exists in both, `cachedEntries` is preferred.\n  //\n  // After merging, if an entry's value is null, it is dropped.\n  //\n  // The final result is truncated to `limit`, if any.\n  //\n  // The idea is that `cachedEntries` is the set of entries that were loaded from cache while\n  // `fetchedEntries` is the set read from storage.\n  explicit GetResultList(kj::Vector<kj::Own<Entry>> cachedEntries,\n      kj::Vector<kj::Own<Entry>> fetchedEntries,\n      Order order,\n      kj::Maybe<uint> limit = kj::none);\n\n  friend class ActorCache;\n};\n\n// Options to ActorCache::SharedLru's constructor. Declared at top level so that it can be\n// forward-declared elsewhere.\nstruct ActorCacheSharedLruOptions {\n  // Memory usage that the LRU will try to stay under by evicting clean values.\n  size_t softLimit;\n\n  // Memory usage at which operations should start failing and actors should be killed for\n  // exceeding memory limits.\n  size_t hardLimit;\n\n  // Time period after which a value that hasn't been accessed at all should be evicted even if\n  // the total cache size is below `softLimit`.\n  kj::Duration staleTimeout;\n\n  // How many bytes in a particular ActorCache can be dirty before backpressure is applied on the\n  // app.\n  size_t dirtyListByteLimit;\n\n  // Maximum number of keys in a single RPC message during a flush. If a message would be larger\n  // than this, it'll be split into multiple calls.\n  //\n  // This should typically be set to ActorStorageClientImpl::MAX_KEYS from\n  // supervisor/actor-storage.h.\n  size_t maxKeysPerRpc;\n\n  // If true, assume `noCache` for all operations.\n  bool noCache = false;\n\n  // If true, don't actually flush anything. This is used in preview sessions, since they keep\n  // state strictly in memory.\n  bool neverFlush = false;\n};\n\nclass ActorCache::SharedLru {\n public:\n  using Options = ActorCacheSharedLruOptions;\n\n  explicit SharedLru(Options options);\n\n  ~SharedLru() noexcept(false);\n  KJ_DISALLOW_COPY_AND_MOVE(SharedLru);\n\n  // Mostly for testing.\n  size_t currentSize() const {\n    return size.load(std::memory_order_relaxed);\n  }\n\n private:\n  const Options options;\n\n  // List of clean values, across all caches, ordered from least-recently-used to\n  // most-recently-used.\n  kj::MutexGuarded<kj::List<Entry, &Entry::link>> cleanList;\n\n  // Total byte size of everything that is cached, including dirty values that aren't in `cleanList`.\n  mutable std::atomic<size_t> size = 0;\n\n  // TimePoint when we should next evict stale entries. Represented as an int64_t of nanoseconds\n  // instead of kj::TimePoint to allow for atomic operations.\n  mutable std::atomic<int64_t> nextStaleCheckNs = 0;\n\n  // Evict cache entries as needed according to the cache limits. Returns true if the hard limit\n  // is exceeded and nothing can be evicted, in which case the caller should fail out in the\n  // appropriate way for the kind of operation being performed.\n  bool evictIfNeeded(Lock& lock) const KJ_WARN_UNUSED_RESULT;\n\n  friend class ActorCache;\n};\n\n// A transaction represents a set of writes that haven't been committed. The transaction can be\n// discarded without committing.\n//\n// ActorCache::Transaction intentionally does NOT detect conflicts with concurrent transactions.\n// It is up to a higher layer to make sure that only one transaction occurs at a time, perhaps\n// using critical sections.\nclass ActorCache::Transaction final: public ActorCacheInterface::Transaction {\n public:\n  Transaction(ActorCache& cache);\n  ~Transaction() noexcept(false);\n\n  kj::OneOf<kj::Maybe<Value>, kj::Promise<kj::Maybe<Value>>> get(\n      Key key, ReadOptions options) override;\n  kj::OneOf<GetResultList, kj::Promise<GetResultList>> get(\n      kj::Array<Key> keys, ReadOptions options) override;\n  kj::OneOf<kj::Maybe<kj::Date>, kj::Promise<kj::Maybe<kj::Date>>> getAlarm(\n      ReadOptions options) override;\n  kj::OneOf<GetResultList, kj::Promise<GetResultList>> list(\n      Key begin, kj::Maybe<Key> end, kj::Maybe<uint> limit, ReadOptions options) override;\n  kj::OneOf<GetResultList, kj::Promise<GetResultList>> listReverse(\n      Key begin, kj::Maybe<Key> end, kj::Maybe<uint> limit, ReadOptions options) override;\n  kj::Maybe<kj::Promise<void>> put(\n      Key key, Value value, WriteOptions options, SpanParent traceSpan) override;\n  kj::Maybe<kj::Promise<void>> put(\n      kj::Array<KeyValuePair> pairs, WriteOptions options, SpanParent traceSpan) override;\n  kj::OneOf<bool, kj::Promise<bool>> delete_(\n      Key key, WriteOptions options, SpanParent traceSpan) override;\n  kj::OneOf<uint, kj::Promise<uint>> delete_(\n      kj::Array<Key> keys, WriteOptions options, SpanParent traceSpan) override;\n  kj::Maybe<kj::Promise<void>> setAlarm(\n      kj::Maybe<kj::Date> newAlarmTime, WriteOptions options, SpanParent traceSpan) override;\n  // Same interface as ActorCache.\n  //\n  // Read ops will reflect the previous writes made to the transaction even though they aren't\n  // committed yet.\n\n  kj::Maybe<kj::Promise<void>> commit() override;\n  kj::Promise<void> rollback() override;\n  // Implements ActorCacheInterface::Transaction.\n\n private:\n  ActorCache& cache;\n\n  struct Change {\n    kj::Own<Entry> entry;\n    WriteOptions options;\n  };\n\n  // Callbacks for a kj::TreeIndex for a kj::Table<Change>.\n  class ChangeTableCallbacks {\n   public:\n    inline KeyPtr keyForRow(const Change& row) const {\n      return row.entry->key;\n    }\n\n    inline bool isBefore(const Change& row, KeyPtr key) const {\n      return row.entry->key < key;\n    }\n    inline bool matches(const Change& row, KeyPtr key) const {\n      return row.entry->key == key;\n    }\n  };\n\n  kj::Table<Change, kj::TreeIndex<ChangeTableCallbacks>> entriesToWrite;\n\n  kj::Maybe<DirtyAlarmWithOptions> alarmChange;\n\n  // Trace span captured from each write, to be used when commit() flushes changes.\n  SpanParent commitSpan = nullptr;\n\n  // Merge the changes in the transaction with the results from reading from the underlying\n  // ActorCache.\n  kj::OneOf<GetResultList, kj::Promise<GetResultList>> merge(\n      kj::Vector<kj::Own<Entry>> changedEntries,\n      kj::OneOf<GetResultList, kj::Promise<GetResultList>> cacheRead,\n      GetResultList::Order order);\n\n  // Adds the given key/value pair to `changes`. If an existing entry is replaced, *count is\n  // incremented if it was a positive entry. If no existing entry is replaced, then the key\n  // is returned, indicating that if a count is needed, we'll need to inspect cache/disk.\n  kj::Maybe<KeyPtr> putImpl(Lock& lock,\n      kj::Own<Entry> entry,\n      const WriteOptions& options,\n      kj::Maybe<uint&> count = kj::none);\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/actor-id.h",
    "content": "#pragma once\n\n#include <kj/common.h>\n#include <kj/string.h>\n\nnamespace workerd {\n\n// Behavior mode for getting an actor\nenum class ActorGetMode {\n  // Creates the actor if it does not already exist, otherwise gets the existing actor.\n  GET_OR_CREATE,\n\n  // Get an already-created actor, throwing an error if it does not exist.\n  GET_EXISTING\n};\n\n// Routing mode for actor requests when replicas are available.\nenum class ActorRoutingMode {\n  // Use default routing behavior (may route to replicas if available).\n  DEFAULT,\n\n  // Always route to the primary, even if replicas exist.\n  PRIMARY_ONLY\n};\n\n// Version information for an actor. Used to specify cohort.\nstruct ActorVersion {\n  kj::Maybe<kj::String> cohort;\n};\n\n// An abstract class that implements generation of global actor IDs in a particular namespace.\n//\n// This is NOT at I/O type. Each global actor namespace binding holds one instance of this which\n// it may call from any thread.\nclass ActorIdFactory {\n public:\n  // Abstract actor ID.\n  //\n  // This is NOT an I/O type. An ActorId created in one IoContext can be used in other\n  // IoContexts. `ActorChannel` and `Actor`, however, are context-specific I/O types. It is\n  // expected that an ActorChannel's get() method can accept any ActorId generated for the same\n  // worker (by the IoChannelFactory for any IoContext), but will detect if the ID is not valid\n  // for the specific namespace.\n  class ActorId {\n   public:\n    // Get the string that could be passed to `idFromString()` to recreate this ID.\n    virtual kj::String toString() const = 0;\n\n    // If the ActorId was created using `idFromName()`, return a copy of the name that was passed\n    // to it. Otherwise, returns null.\n    virtual kj::Maybe<kj::StringPtr> getName() const = 0;\n\n    // Get the jurisdiction that was used when creating this ID.\n    virtual kj::Maybe<kj::StringPtr> getJurisdiction() const = 0;\n\n    // Compare with another ID.\n    //\n    // This is allowed to assume the other ID was created by some other ActorIdFactory passed to\n    // one of the worker's other bindings, i.e. if all factories produce the same ID type, then\n    // this can downcast to that without a dynamic check.\n    virtual bool equals(const ActorId& other) const = 0;\n\n    virtual kj::Own<ActorId> clone() const = 0;\n  };\n\n  virtual kj::Own<ActorId> newUniqueId(kj::Maybe<kj::StringPtr> jurisdiction) = 0;\n  virtual kj::Own<ActorId> idFromName(kj::String name) = 0;\n  virtual kj::Own<ActorId> idFromString(kj::String str) = 0;\n  virtual bool matchesJurisdiction(const ActorId& id) = 0;\n  virtual kj::Own<ActorIdFactory> cloneWithJurisdiction(\n      kj::Maybe<kj::StringPtr> maybeJurisdiction) = 0;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/actor-sqlite-test.c++",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"actor-sqlite.h\"\n#include \"io-gate.h\"\n\n#include <workerd/util/autogate.h>\n#include <workerd/util/capnp-mock.h>\n#include <workerd/util/test.h>\n\n#include <sqlite3.h>\n\n#include <kj/debug.h>\n#include <kj/test.h>\n\nnamespace workerd {\nnamespace {\n\nstatic constexpr kj::Date oneMs = 1 * kj::MILLISECONDS + kj::UNIX_EPOCH;\nstatic constexpr kj::Date twoMs = 2 * kj::MILLISECONDS + kj::UNIX_EPOCH;\nstatic constexpr kj::Date threeMs = 3 * kj::MILLISECONDS + kj::UNIX_EPOCH;\nstatic constexpr kj::Date fourMs = 4 * kj::MILLISECONDS + kj::UNIX_EPOCH;\nstatic constexpr kj::Date fiveMs = 5 * kj::MILLISECONDS + kj::UNIX_EPOCH;\n// Used as the \"current time\" parameter for armAlarmHandler in tests.\n// Set to epoch (before all test alarm times) so existing tests aren't affected by\n// the overdue alarm check.\nstatic constexpr kj::Date testCurrentTime = kj::UNIX_EPOCH;\n\ntemplate <typename T>\nkj::Promise<T> eagerlyReportExceptions(kj::Promise<T> promise, kj::SourceLocation location = {}) {\n  return promise.eagerlyEvaluate([location](kj::Exception&& e) -> T {\n    KJ_LOG_AT(ERROR, location, e);\n    kj::throwFatalException(kj::mv(e));\n  });\n}\n\n// Expect that a synchronous result is returned.\ntemplate <typename T>\nT expectSync(kj::OneOf<T, kj::Promise<T>> result, kj::SourceLocation location = {}) {\n  KJ_SWITCH_ONEOF(result) {\n    KJ_CASE_ONEOF(promise, kj::Promise<T>) {\n      KJ_FAIL_ASSERT_AT(location, \"result was unexpectedly asynchronous\");\n    }\n    KJ_CASE_ONEOF(value, T) {\n      return kj::mv(value);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nstruct ActorSqliteTestOptions final {\n  bool monitorOutputGate = true;\n};\n\nstruct ActorSqliteTest final {\n  kj::EventLoop loop;\n  kj::WaitScope ws;\n\n  OutputGate gate;\n  kj::Own<const kj::Directory> vfsDir;\n  SqliteDatabase::Vfs vfs;\n  SqliteDatabase db;\n\n  struct Call final {\n    kj::String desc;\n    kj::Own<kj::PromiseFulfiller<void>> fulfiller;\n  };\n  kj::Vector<Call> calls;\n\n  struct ActorSqliteTestHooks final: public ActorSqlite::Hooks {\n   public:\n    explicit ActorSqliteTestHooks(ActorSqliteTest& parent): parent(parent) {}\n\n    kj::Promise<void> scheduleRun(\n        kj::Maybe<kj::Date> newAlarmTime, kj::Promise<void> priorTask) override {\n      KJ_IF_SOME(h, parent.scheduleRunHandler) {\n        return h(newAlarmTime);\n      }\n      auto desc = newAlarmTime.map([](auto& t) {\n        return kj::str(\"scheduleRun(\", t, \")\");\n      }).orDefault(kj::str(\"scheduleRun(none)\"));\n      auto [promise, fulfiller] = kj::newPromiseAndFulfiller<void>();\n      parent.calls.add(Call{kj::mv(desc), kj::mv(fulfiller)});\n      return kj::mv(promise);\n    }\n\n    ActorSqliteTest& parent;\n  };\n  kj::Maybe<kj::Function<kj::Promise<void>(kj::Maybe<kj::Date>)>> scheduleRunHandler;\n  ActorSqliteTestHooks hooks = ActorSqliteTestHooks(*this);\n\n  ActorSqlite actor;\n\n  kj::Promise<void> gateBrokenPromise;\n  kj::UnwindDetector unwindDetector;\n\n  explicit ActorSqliteTest(ActorSqliteTestOptions options = {})\n      : ws(loop),\n        vfsDir(kj::newInMemoryDirectory(kj::nullClock())),\n        vfs(*vfsDir),\n        db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY),\n        actor(kj::attachRef(db), gate, KJ_BIND_METHOD(*this, commitCallback), hooks),\n        gateBrokenPromise(options.monitorOutputGate ? eagerlyReportExceptions(gate.onBroken())\n                                                    : kj::Promise<void>(kj::READY_NOW)) {\n    db.afterReset([this](SqliteDatabase& cbDb) {\n      KJ_ASSERT(&db == &cbDb);\n      KJ_ASSERT(actor.isCommitScheduled(),\n          \"actor should have a commit scheduled during afterReset() callback\");\n    });\n  }\n\n  ~ActorSqliteTest() noexcept(false) {\n    if (!unwindDetector.isUnwinding()) {\n      // Make sure if the output gate has been broken, the exception was reported. This is\n      // important to report errors thrown inside flush(), since those won't otherwise propagate\n      // into the test body.\n      gateBrokenPromise.poll(ws);\n\n      // Make sure there's no outstanding async work we haven't considered:\n      pollAndExpectCalls({}, \"unexpected calls at end of test\");\n    }\n  }\n\n  kj::Promise<void> commitCallback(SpanParent) {\n    auto [promise, fulfiller] = kj::newPromiseAndFulfiller<void>();\n    calls.add(Call{kj::str(\"commit\"), kj::mv(fulfiller)});\n    return kj::mv(promise);\n  }\n\n  // Polls the event loop, then asserts that the description of calls up to this point match the\n  // expectation and returns their fulfillers.  Also clears the call log.\n  //\n  // TODO(cleanup): Is there a better way to do mocks?  capnp-mock looks nice, but seems a bit\n  // heavyweight for this test.\n  kj::Vector<kj::Own<kj::PromiseFulfiller<void>>> pollAndExpectCalls(\n      std::initializer_list<kj::StringPtr> expCallDescs,\n      kj::StringPtr message = \"\"_kj,\n      kj::SourceLocation location = {}) {\n    ws.poll();\n    auto callDescs = KJ_MAP(c, calls) { return kj::str(c.desc); };\n    KJ_ASSERT_AT(callDescs == heapArray(expCallDescs), location, kj::str(message));\n    auto fulfillers = KJ_MAP(c, calls) { return kj::mv(c.fulfiller); };\n    calls.clear();\n    return kj::mv(fulfillers);\n  }\n\n  // A few driver methods for convenience.\n  auto get(kj::StringPtr key, ActorCache::ReadOptions options = {}) {\n    return actor.get(kj::str(key), options);\n  }\n  auto getAlarm(ActorCache::ReadOptions options = {}) {\n    return actor.getAlarm(options);\n  }\n  auto put(kj::StringPtr key, kj::StringPtr value, ActorCache::WriteOptions options = {}) {\n    return actor.put(kj::str(key), kj::heapArray(value.asBytes()), options, nullptr);\n  }\n  auto putMultiple(\n      kj::Array<ActorCache::KeyValuePair> pairs, ActorCache::WriteOptions options = {}) {\n    return actor.put(kj::mv(pairs), options, nullptr);\n  }\n  auto putMultipleExplicitTxn(\n      kj::Array<ActorCache::KeyValuePair> pairs, ActorCache::WriteOptions options = {}) {\n    auto txn = actor.startTransaction();\n    txn->put(kj::mv(pairs), options, nullptr);\n    return txn->commit();\n  }\n  auto deleteMultiple(kj::Array<kj::String> keys, ActorCache::WriteOptions options = {}) {\n    return actor.delete_(kj::mv(keys), options, nullptr);\n  }\n  auto setAlarm(kj::Maybe<kj::Date> newTime, ActorCache::WriteOptions options = {}) {\n    return actor.setAlarm(newTime, options, nullptr);\n  }\n  auto sync() {\n    return actor.onNoPendingFlush(nullptr);\n  }\n};\n\nKJ_TEST(\"initial alarm value is unset\") {\n  ActorSqliteTest test;\n\n  KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n}\n\nKJ_TEST(\"can set and get alarm\") {\n  ActorSqliteTest test;\n\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n}\n\nKJ_TEST(\"check put multiple wraps operations in a transaction\") {\n  ActorSqliteTest test;\n\n  // Let's deinit the autogate. This will enforce the old behavior where putMultiple would commit\n  // some puts, until a single put failed.\n  util::Autogate::deinitAutogate();\n\n  kj::Vector<ActorCache::KeyValuePair> putKVs;\n  putKVs.add(ActorCache::KeyValuePair{kj::str(\"foo\"), kj::heapArray(kj::str(\"bar\").asBytes())});\n\n  // NoTxn test\n  {\n    // Check that we're in a NoTxn\n    KJ_ASSERT(!test.actor.isCommitScheduled());\n    test.putMultiple(putKVs.releaseAsArray());\n    // During write, all NoTxn operations are wrapped in an ImplicitTxn.\n    auto commitFulfiller = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n    commitFulfiller->fulfill();\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo\"))) == kj::str(\"bar\").asBytes());\n  }\n\n  // ExplicitTxn test\n  {\n    putKVs.add(ActorCache::KeyValuePair{kj::str(\"foo2\"), kj::heapArray(kj::str(\"bar2\").asBytes())});\n    KJ_ASSERT(!test.actor.isCommitScheduled());\n    // Similar to the previous putMultiple, but wrapped in a transactionSync (ExplicitTxn)\n    test.putMultipleExplicitTxn(putKVs.releaseAsArray());\n    auto commitFulfiller = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n    commitFulfiller->fulfill();\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo2\"))) == kj::str(\"bar2\").asBytes());\n  }\n\n  // ImplicitTxn test\n  {\n    // A single put will create an ImplicitTxn that we can use to wrap our putMultiple into.\n    KJ_ASSERT(!test.actor.isCommitScheduled());\n    test.put(\"baz\", \"bat\");\n\n    // By now, we should check there's a commit scheduled in a ImplicitTxn.\n    KJ_ASSERT(test.actor.isCommitScheduled());\n    putKVs.add(ActorCache::KeyValuePair{kj::str(\"foo3\"), kj::heapArray(kj::str(\"bar3\").asBytes())});\n    test.putMultiple(putKVs.releaseAsArray());\n\n    auto commitFulfiller = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"baz\"))) == kj::str(\"bat\").asBytes());\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo3\"))) == kj::str(\"bar3\").asBytes());\n    commitFulfiller->fulfill();\n  }\n}\n\nKJ_TEST(\"check put multiple wraps operations in a transaction\") {\n  ActorSqliteTest test;\n\n  kj::Vector<ActorCache::KeyValuePair> putKVs;\n  putKVs.add(ActorCache::KeyValuePair{kj::str(\"foo\"), kj::heapArray(kj::str(\"bar\").asBytes())});\n\n  // NoTxn test\n  {\n    // Check that we're in a NoTxn\n    KJ_ASSERT(!test.actor.isCommitScheduled());\n    test.putMultiple(putKVs.releaseAsArray());\n    // During write, all NoTxn operations are wrapped in an ImplicitTxn.\n    auto commitFulfiller = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n    commitFulfiller->fulfill();\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo\"))) == kj::str(\"bar\").asBytes());\n  }\n\n  // ExplicitTxn test\n  {\n    putKVs.add(ActorCache::KeyValuePair{kj::str(\"foo2\"), kj::heapArray(kj::str(\"bar2\").asBytes())});\n    KJ_ASSERT(!test.actor.isCommitScheduled());\n    // Similar to the previous putMultiple, but wrapped in a transactionSync (ExplicitTxn)\n    test.putMultipleExplicitTxn(putKVs.releaseAsArray());\n    auto commitFulfiller = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n    commitFulfiller->fulfill();\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo2\"))) == kj::str(\"bar2\").asBytes());\n  }\n\n  // ImplicitTxn test\n  {\n    // A single put will create an ImplicitTxn that we can use to wrap our putMultiple into.\n    KJ_ASSERT(!test.actor.isCommitScheduled());\n    test.put(\"baz\", \"bat\");\n\n    // By now, we should check there's a commit scheduled in a ImplicitTxn.\n    KJ_ASSERT(test.actor.isCommitScheduled());\n    putKVs.add(ActorCache::KeyValuePair{kj::str(\"foo3\"), kj::heapArray(kj::str(\"bar3\").asBytes())});\n    test.putMultiple(putKVs.releaseAsArray());\n\n    auto commitFulfiller = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"baz\"))) == kj::str(\"bat\").asBytes());\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo3\"))) == kj::str(\"bar3\").asBytes());\n    commitFulfiller->fulfill();\n  }\n}\n\nKJ_TEST(\"check put multiple wraps operations in a transaction and rollback on error\") {\n  ActorSqliteTest test;\n\n  // We expect that putMultiple is all or nothing, rolling back if a single put fails.\n\n  kj::Vector<ActorCache::KeyValuePair> putKVs;\n\n  // Add some regular key-value pairs that we know are supported\n  putKVs.add(ActorCache::KeyValuePair{kj::str(\"foo\"), kj::heapArray(kj::str(\"bar\").asBytes())});\n  putKVs.add(ActorCache::KeyValuePair{kj::str(\"foo2\"), kj::heapArray(kj::str(\"bar2\").asBytes())});\n  putKVs.add(ActorCache::KeyValuePair{kj::str(\"foo3\"), kj::heapArray(kj::str(\"bar3\").asBytes())});\n\n  // Now create a key that's too large. Should fail with  string or blob too big: SQLITE_TOOBIG\n  auto tooLongKey = kj::heapString(2200000);\n  tooLongKey.asArray().fill('a');\n  // Add it to our KV array\n  putKVs.add(\n      ActorCache::KeyValuePair{kj::str(tooLongKey), kj::heapArray(kj::str(\"bar\").asBytes())});\n\n  // NoTxn test\n  {\n    // Check that we're in a NoTxn\n    KJ_ASSERT(!test.actor.isCommitScheduled());\n    try {\n      test.putMultiple(putKVs.releaseAsArray());\n      // During write, all NoTxn operations are wrapped in an ImplicitTxn.\n      auto commitFulfiller = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n      commitFulfiller->fulfill();\n      KJ_UNREACHABLE;\n    } catch (kj::Exception& e) {\n      KJ_ASSERT(\n          e.getDescription() == \"expected false; jsg.Error: string or blob too big: SQLITE_TOOBIG\");\n    }\n    KJ_ASSERT(expectSync(test.get(kj::str(\"foo\"))) == nullptr);\n    KJ_ASSERT(expectSync(test.get(kj::str(\"foo2\"))) == nullptr);\n    KJ_ASSERT(expectSync(test.get(kj::str(\"foo3\"))) == nullptr);\n  }\n\n  // Reset the transaction state by going async, which will cause the ImplicitTxn to commit.\n  {\n    auto commitFulfiller = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n    commitFulfiller->fulfill();\n  }\n\n  // ExplicitTxn test\n  {\n    KJ_ASSERT(!test.actor.isCommitScheduled());\n    // Similar to the previous putMultiple, but wrapped in a transactionSync (ExplicitTxn)\n    test.putMultipleExplicitTxn(putKVs.releaseAsArray());\n    auto commitFulfiller = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n    commitFulfiller->fulfill();\n    KJ_ASSERT(expectSync(test.get(kj::str(\"foo\"))) == nullptr);\n    KJ_ASSERT(expectSync(test.get(kj::str(\"foo2\"))) == nullptr);\n    KJ_ASSERT(expectSync(test.get(kj::str(\"foo3\"))) == nullptr);\n  }\n\n  // ImplicitTxn test\n  {\n    // A single put will create an ImplicitTxn that we can use to wrap our putMultiple into.\n    KJ_ASSERT(!test.actor.isCommitScheduled());\n    test.put(\"baz\", \"bat\");\n\n    // By now, we should check there's a commit scheduled in a ImplicitTxn.\n    KJ_ASSERT(test.actor.isCommitScheduled());\n    test.putMultiple(putKVs.releaseAsArray());\n\n    auto commitFulfiller = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n    // The single put succeeded, but the putMultiple did not.\n    KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"baz\"))) == kj::str(\"bat\").asBytes());\n    KJ_ASSERT(expectSync(test.get(kj::str(\"foo\"))) == nullptr);\n    KJ_ASSERT(expectSync(test.get(kj::str(\"foo2\"))) == nullptr);\n    KJ_ASSERT(expectSync(test.get(kj::str(\"foo3\"))) == nullptr);\n    commitFulfiller->fulfill();\n  }\n}\n\nKJ_TEST(\"alarm write happens transactionally with storage ops\") {\n  ActorSqliteTest test;\n\n  test.setAlarm(oneMs);\n  test.put(\"foo\", \"bar\");\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo\"))) == kj::str(\"bar\").asBytes());\n}\n\nKJ_TEST(\"storage op without alarm change does not wait on scheduler\") {\n  ActorSqliteTest test;\n\n  test.put(\"foo\", \"bar\");\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo\"))) == kj::str(\"bar\").asBytes());\n  KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n}\n\nKJ_TEST(\"alarm scheduling starts synchronously before implicit local db commit\") {\n  ActorSqliteTest test;\n\n  // In workerd (unlike edgeworker), there is no remote storage, so there is no work done in\n  // commitCallback(); the local db is considered durably stored after the synchronous sqlite\n  // commit() call returns.  If a commit includes an alarm state change that requires scheduling\n  // before the commit call, it needs to happen synchronously.  Since workerd synchronously\n  // schedules alarms, we just need to ensure that the database is in a pre-commit state when\n  // scheduleRun() is called.\n\n  // Initialize alarm state to 2ms.\n  test.setAlarm(twoMs);\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n\n  bool startedScheduleRun = false;\n  test.scheduleRunHandler = [&](kj::Maybe<kj::Date>) -> kj::Promise<void> {\n    startedScheduleRun = true;\n\n    KJ_EXPECT_THROW_MESSAGE(\n        \"cannot start a transaction within a transaction\", test.db.run(\"BEGIN TRANSACTION\"));\n\n    return kj::READY_NOW;\n  };\n\n  test.setAlarm(oneMs);\n  KJ_ASSERT(!startedScheduleRun);\n  test.ws.poll();\n  KJ_ASSERT(startedScheduleRun);\n\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n}\n\nKJ_TEST(\"alarm scheduling starts synchronously before explicit local db commit\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 2ms.\n  test.setAlarm(twoMs);\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n\n  bool startedScheduleRun = false;\n  test.scheduleRunHandler = [&](kj::Maybe<kj::Date>) -> kj::Promise<void> {\n    startedScheduleRun = true;\n\n    // Not sure if there is a good way to detect savepoint presence without mutating the db state,\n    // but this is sufficient to verify the test properties:\n\n    // Verify that we are not within a nested savepoint.\n    KJ_EXPECT_THROW_MESSAGE(\n        \"no such savepoint: _cf_savepoint_1\", test.db.run(\"RELEASE _cf_savepoint_1\"));\n\n    // Verify that we are within the root savepoint.\n    test.db.run(\"RELEASE _cf_savepoint_0\");\n    KJ_EXPECT_THROW_MESSAGE(\n        \"no such savepoint: _cf_savepoint_0\", test.db.run(\"RELEASE _cf_savepoint_0\"));\n\n    // We don't actually care what happens in the test after this point, but it's slightly simpler\n    // to re-add the savepoint to allow the test to complete cleanly:\n    test.db.run(\"SAVEPOINT _cf_savepoint_0\");\n\n    return kj::READY_NOW;\n  };\n\n  {\n    auto txn = test.actor.startTransaction();\n    txn->setAlarm(oneMs, {}, nullptr);\n\n    KJ_ASSERT(!startedScheduleRun);\n    txn->commit();\n    KJ_ASSERT(startedScheduleRun);\n\n    test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  }\n\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n}\n\nKJ_TEST(\"alarm scheduling does not start synchronously before nested explicit local db commit\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 2ms.\n  test.setAlarm(twoMs);\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n\n  bool startedScheduleRun = false;\n  test.scheduleRunHandler = [&](kj::Maybe<kj::Date>) -> kj::Promise<void> {\n    startedScheduleRun = true;\n    return kj::READY_NOW;\n  };\n\n  {\n    auto txn1 = test.actor.startTransaction();\n\n    {\n      auto txn2 = test.actor.startTransaction();\n      txn2->setAlarm(oneMs, {}, nullptr);\n\n      txn2->commit();\n      KJ_ASSERT(!startedScheduleRun);\n    }\n\n    txn1->commit();\n    KJ_ASSERT(startedScheduleRun);\n\n    test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  }\n\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n}\n\nKJ_TEST(\"synchronous alarm scheduling failure causes local db commit to throw synchronously\") {\n  ActorSqliteTest test({.monitorOutputGate = false});\n  auto promise = test.gate.onBroken();\n\n  auto getLocalAlarm = [&]() -> kj::Maybe<kj::Date> {\n    auto query = test.db.run(\"SELECT value FROM _cf_METADATA WHERE key = 1\");\n    if (query.isDone() || query.isNull(0)) {\n      return kj::none;\n    } else {\n      return kj::UNIX_EPOCH + query.getInt64(0) * kj::NANOSECONDS;\n    }\n  };\n\n  // Initialize alarm state to 2ms.\n  test.setAlarm(twoMs);\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n\n  // Override scheduleRun handler with one that throws synchronously.\n  bool startedScheduleRun = false;\n  test.scheduleRunHandler = [&](kj::Maybe<kj::Date>) -> kj::Promise<void> {\n    startedScheduleRun = true;\n    // Must throw synchronously; returning an exception is insufficient.\n    kj::throwFatalException(KJ_EXCEPTION(FAILED, \"a_sync_fail\"));\n  };\n\n  KJ_ASSERT(!promise.poll(test.ws));\n  test.setAlarm(oneMs);\n\n  // Expect that polling will attempt to commit the implicit transaction, which should\n  // synchronously fail when attempting to call scheduleRun() before the db commit, and roll back the\n  // local db state to the 2ms alarm.\n  KJ_ASSERT(!startedScheduleRun);\n  KJ_ASSERT(KJ_REQUIRE_NONNULL(getLocalAlarm()) == oneMs);\n  test.ws.poll();\n  KJ_ASSERT(startedScheduleRun);\n  KJ_ASSERT(KJ_REQUIRE_NONNULL(getLocalAlarm()) == twoMs);\n\n  KJ_ASSERT(promise.poll(test.ws));\n  KJ_EXPECT_THROW_MESSAGE(\"a_sync_fail\", promise.wait(test.ws));\n}\n\nKJ_TEST(\"can clear alarm\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  test.setAlarm(kj::none);\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"scheduleRun(none)\"})[0]->fulfill();\n\n  KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n}\n\nKJ_TEST(\"can set alarm twice\") {\n  ActorSqliteTest test;\n\n  test.setAlarm(oneMs);\n  test.setAlarm(twoMs);\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  KJ_ASSERT(expectSync(test.getAlarm()) == twoMs);\n}\n\nKJ_TEST(\"setting duplicate alarm is no-op\") {\n  ActorSqliteTest test;\n\n  test.setAlarm(kj::none);\n  test.pollAndExpectCalls({});\n\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({});\n}\n\nKJ_TEST(\"tells alarm handler to cancel when committed alarm is empty\") {\n  ActorSqliteTest test;\n\n  {\n    auto armResult = test.actor.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n    // We expect armAlarmHandler() to tell us to cancel the alarm.\n    KJ_ASSERT(armResult.is<ActorCache::CancelAlarmHandler>());\n    auto waitPromise = kj::mv(armResult.get<ActorCache::CancelAlarmHandler>().waitBeforeCancel);\n\n    // We also expect the alarm cancellation to contain a scheduling request to delete the alarm,\n    // to handle cases where alarm deletion was durably committed to the database, but a failure\n    // occurred before the alarm deletion was conveyed to the alarm scheduler.\n    KJ_ASSERT(!waitPromise.poll(test.ws));\n    test.pollAndExpectCalls({\"scheduleRun(none)\"})[0]->fulfill();\n    KJ_ASSERT(waitPromise.poll(test.ws));\n    waitPromise.wait(test.ws);\n  }\n}\n\nKJ_TEST(\"tells alarm handler to reschedule when handler alarm is later than committed alarm\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  // Request handler run at 2ms.  Expect cancellation with rescheduling.\n  auto armResult = test.actor.armAlarmHandler(twoMs, nullptr, testCurrentTime);\n  KJ_ASSERT(armResult.is<ActorSqlite::CancelAlarmHandler>());\n  auto cancelResult = kj::mv(armResult.get<ActorSqlite::CancelAlarmHandler>());\n\n  // Expect rescheduling was requested and that returned promise resolves after fulfillment.\n  auto waitBeforeCancel = kj::mv(cancelResult.waitBeforeCancel);\n  auto rescheduleFulfiller = kj::mv(test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]);\n  KJ_ASSERT(!waitBeforeCancel.poll(test.ws));\n  rescheduleFulfiller->fulfill();\n  KJ_ASSERT(waitBeforeCancel.poll(test.ws));\n  waitBeforeCancel.wait(test.ws);\n}\n\nKJ_TEST(\"tells alarm handler to reschedule when handler alarm is earlier than committed alarm\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 2ms.\n  test.setAlarm(twoMs);\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == twoMs);\n\n  // Expect that armAlarmHandler() tells caller to cancel after rescheduling completes.\n  auto armResult = test.actor.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n  KJ_ASSERT(armResult.is<ActorSqlite::CancelAlarmHandler>());\n  auto cancelResult = kj::mv(armResult.get<ActorSqlite::CancelAlarmHandler>());\n\n  // Expect rescheduling was requested and that returned promise resolves after fulfillment.\n  auto waitBeforeCancel = kj::mv(cancelResult.waitBeforeCancel);\n  auto rescheduleFulfiller = kj::mv(test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]);\n  KJ_ASSERT(!waitBeforeCancel.poll(test.ws));\n  rescheduleFulfiller->fulfill();\n  KJ_ASSERT(waitBeforeCancel.poll(test.ws));\n  waitBeforeCancel.wait(test.ws);\n}\n\nKJ_TEST(\"runs overdue alarm immediately when local alarm time is in the past\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 2ms.\n  test.setAlarm(twoMs);\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == twoMs);\n\n  // The local state says the alarm is due to fire at 2ms, but we're saying the AlarmManager has 1ms,\n  // usually this would result in a rescheduling of the alarm, but since our currentTime is 5ms, we\n  // will just run the alarm now since it's already overdue.\n  {\n    auto overdueCurrentTime = fiveMs;\n    auto armResult = test.actor.armAlarmHandler(oneMs, nullptr, overdueCurrentTime);\n\n    // Should run the handler immediately instead of canceling/rescheduling.\n    KJ_ASSERT(armResult.is<ActorSqlite::RunAlarmHandler>());\n  }\n\n  // commit and delete the alarm after we drop the alarm handler (this is a deferred delete).\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"scheduleRun(none)\"})[0]->fulfill();\n}\n\nKJ_TEST(\"does not cancel handler when local db alarm state is later than scheduled alarm\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  test.setAlarm(twoMs);\n  {\n    auto armResult = test.actor.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n    KJ_ASSERT(armResult.is<ActorSqlite::RunAlarmHandler>());\n  }\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->fulfill();\n}\n\nKJ_TEST(\"does not cancel handler when local db alarm state is earlier than scheduled alarm\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 2ms.\n  test.setAlarm(twoMs);\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == twoMs);\n\n  test.setAlarm(oneMs);\n  {\n    auto armResult = test.actor.armAlarmHandler(twoMs, nullptr, testCurrentTime);\n    KJ_ASSERT(armResult.is<ActorSqlite::RunAlarmHandler>());\n  }\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n}\n\nKJ_TEST(\"getAlarm() returns null during handler\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  {\n    auto armResult = test.actor.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n    KJ_ASSERT(armResult.is<ActorSqlite::RunAlarmHandler>());\n    test.pollAndExpectCalls({});\n\n    KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n  }\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"scheduleRun(none)\"})[0]->fulfill();\n}\n\nKJ_TEST(\"alarm handler handle clears alarm when dropped with no writes\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  {\n    auto armResult = test.actor.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n    KJ_ASSERT(armResult.is<ActorSqlite::RunAlarmHandler>());\n  }\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"scheduleRun(none)\"})[0]->fulfill();\n  KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n}\n\nKJ_TEST(\"alarm deleter does not clear alarm when dropped with writes\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  {\n    auto armResult = test.actor.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n    KJ_ASSERT(armResult.is<ActorSqlite::RunAlarmHandler>());\n    test.setAlarm(twoMs);\n  }\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->fulfill();\n\n  KJ_ASSERT(expectSync(test.getAlarm()) == twoMs);\n}\n\nKJ_TEST(\"can cancel deferred alarm deletion during handler\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  {\n    auto armResult = test.actor.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n    KJ_ASSERT(armResult.is<ActorSqlite::RunAlarmHandler>());\n    test.actor.cancelDeferredAlarmDeletion();\n  }\n\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n}\n\nKJ_TEST(\"canceling deferred alarm deletion outside handler has no effect\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  {\n    auto armResult = test.actor.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n    KJ_ASSERT(armResult.is<ActorSqlite::RunAlarmHandler>());\n  }\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"scheduleRun(none)\"})[0]->fulfill();\n\n  test.actor.cancelDeferredAlarmDeletion();\n\n  KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n}\n\nKJ_TEST(\"canceling deferred alarm deletion outside handler edge case\") {\n  // Presumably harmless to cancel deletion if the client requests it after the handler ends but\n  // before the event loop runs the commit code?  Trying to cancel deletion outside the handler is\n  // a bit of a contract violation anyway -- maybe we should just assert against it?\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  {\n    auto armResult = test.actor.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n    KJ_ASSERT(armResult.is<ActorSqlite::RunAlarmHandler>());\n  }\n  test.actor.cancelDeferredAlarmDeletion();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"scheduleRun(none)\"})[0]->fulfill();\n\n  KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n}\n\nKJ_TEST(\"canceling deferred alarm deletion is idempotent\") {\n  // Not sure if important, but matches ActorCache behavior.\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  {\n    auto armResult = test.actor.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n    KJ_ASSERT(armResult.is<ActorSqlite::RunAlarmHandler>());\n    test.actor.cancelDeferredAlarmDeletion();\n    test.actor.cancelDeferredAlarmDeletion();\n  }\n\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n}\n\nKJ_TEST(\"alarm handler cleanup succeeds when output gate is broken\") {\n  auto runWithSetup = [](auto testFunc) {\n    ActorSqliteTest test({.monitorOutputGate = false});\n    auto promise = test.gate.onBroken();\n\n    // Initialize alarm state to 1ms.\n    test.setAlarm(oneMs);\n    test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n    test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n    test.pollAndExpectCalls({});\n    KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n    auto armResult = test.actor.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n    KJ_ASSERT(armResult.is<ActorSqlite::RunAlarmHandler>());\n    auto deferredDelete = kj::mv(armResult.get<ActorSqlite::RunAlarmHandler>().deferredDelete);\n\n    // Break gate\n    test.put(\"foo\", \"bar\");\n    test.pollAndExpectCalls({\"commit\"})[0]->reject(KJ_EXCEPTION(FAILED, \"a_rejected_commit\"));\n    KJ_EXPECT_THROW_MESSAGE(\"a_rejected_commit\", promise.wait(test.ws));\n    // Ensure taskFailed handler runs and notices brokenness:\n    test.ws.poll();\n\n    testFunc(test, kj::mv(deferredDelete));\n  };\n\n  // Here, we test that the deferred deleter destructor doesn't throw, both in the case when the\n  // caller cancels deletion and when it does not cancel it:\n\n  runWithSetup([](ActorSqliteTest& test, kj::Own<void> deferredDelete) {\n    // In the case where the handler fails, we assume the caller will explicitly cancel deferred\n    // alarm deletion:\n    test.actor.cancelDeferredAlarmDeletion();\n\n    // Dropping the DeferredAlarmDeleter should succeed -- that is, it should not throw here:\n    { auto drop = kj::mv(deferredDelete); }\n  });\n\n  runWithSetup([](ActorSqliteTest& test, kj::Own<void> deferredDelete) {\n    // In the case where the handler succeeds, the caller will not cancel deferred deletion before\n    // dropping the DeferredAlarmDeleter.  Dropping the DeferredAlarmDeleter should still succeed,\n    // even if the output gate happens to already be broken:\n    { auto drop = kj::mv(deferredDelete); }\n  });\n}\n\nKJ_TEST(\"handler alarm is not deleted when commit fails\") {\n  ActorSqliteTest test({.monitorOutputGate = false});\n\n  auto promise = test.gate.onBroken();\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  {\n    auto armResult = test.actor.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n    KJ_ASSERT(armResult.is<ActorSqlite::RunAlarmHandler>());\n\n    KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n  }\n  test.pollAndExpectCalls({\"commit\"})[0]->reject(KJ_EXCEPTION(FAILED, \"a_rejected_commit\"));\n\n  KJ_EXPECT_THROW_MESSAGE(\"a_rejected_commit\", promise.wait(test.ws));\n}\n\nKJ_TEST(\"setting earlier alarm persists alarm scheduling before db\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 2ms.\n  test.setAlarm(twoMs);\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == twoMs);\n\n  // Update alarm to be earlier.  We expect the alarm scheduling to be persisted before the db.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n}\n\nKJ_TEST(\"setting later alarm persists db before alarm scheduling\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  // Update alarm to be later.  We expect the db to be persisted before the alarm scheduling.\n  test.setAlarm(twoMs);\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->fulfill();\n\n  KJ_ASSERT(expectSync(test.getAlarm()) == twoMs);\n}\n\nKJ_TEST(\"multiple set-earlier in-flight alarms wait for earliest before committing db\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 5ms.\n  test.setAlarm(fiveMs);\n  test.pollAndExpectCalls({\"scheduleRun(5ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == fiveMs);\n\n  // Gate is not blocked.\n  auto gateWaitBefore = test.gate.wait(nullptr);\n  KJ_ASSERT(gateWaitBefore.poll(test.ws));\n\n  // Update alarm to be earlier (4ms).  We expect the alarm scheduling to start.\n  test.setAlarm(fourMs);\n  auto fulfiller4Ms = kj::mv(test.pollAndExpectCalls({\"scheduleRun(4ms)\"})[0]);\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == fourMs);\n\n  // Gate as-of 4ms update is blocked.\n  auto gateWait4ms = test.gate.wait(nullptr);\n  KJ_ASSERT(!gateWait4ms.poll(test.ws));\n\n  // While 4ms scheduling request is in-flight, update alarm to be even earlier (3ms).  We expect\n  // the 4ms request to block the 3ms scheduling request.\n  test.setAlarm(threeMs);\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == threeMs);\n\n  // Gate as-of 3ms update is blocked.\n  auto gateWait3ms = test.gate.wait(nullptr);\n  KJ_ASSERT(!gateWait3ms.poll(test.ws));\n\n  // Update alarm to be even earlier (2ms).  We expect scheduling requests to still be blocked.\n  test.setAlarm(twoMs);\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == twoMs);\n\n  // Gate as-of 2ms update is blocked.\n  auto gateWait2ms = test.gate.wait(nullptr);\n  KJ_ASSERT(!gateWait2ms.poll(test.ws));\n\n  // Fulfill the 4ms request.  We expect the 2ms scheduling to start, because that is the current\n  // alarm value.\n  fulfiller4Ms->fulfill();\n  auto fulfiller2Ms = kj::mv(test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]);\n  test.pollAndExpectCalls({});\n\n  // While waiting for 2ms request, update alarm time to be 1ms.  Expect scheduling to be blocked.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  // Gate as-of 1ms update is blocked.\n  auto gateWait1ms = test.gate.wait(nullptr);\n  KJ_ASSERT(!gateWait1ms.poll(test.ws));\n\n  // Fulfill the 2ms request.  We expect the 1ms scheduling to start.\n  fulfiller2Ms->fulfill();\n  auto fulfiller1Ms = kj::mv(test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]);\n  test.pollAndExpectCalls({});\n\n  // Fulfill the 1ms request.  We expect a single db commit to start (coalescing all previous db\n  // commits together).\n  fulfiller1Ms->fulfill();\n  auto commitFulfiller = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n  test.pollAndExpectCalls({});\n\n  // We expect all earlier gates to be blocked until commit completes.\n  KJ_ASSERT(!gateWait4ms.poll(test.ws));\n  KJ_ASSERT(!gateWait3ms.poll(test.ws));\n  KJ_ASSERT(!gateWait2ms.poll(test.ws));\n  KJ_ASSERT(!gateWait1ms.poll(test.ws));\n  commitFulfiller->fulfill();\n  KJ_ASSERT(gateWait4ms.poll(test.ws));\n  KJ_ASSERT(gateWait3ms.poll(test.ws));\n  KJ_ASSERT(gateWait2ms.poll(test.ws));\n  KJ_ASSERT(gateWait1ms.poll(test.ws));\n\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n}\n\nKJ_TEST(\"setting later alarm times does scheduling after db commit\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  // Gate is not blocked.\n  auto gateWaitBefore = test.gate.wait(nullptr);\n  KJ_ASSERT(gateWaitBefore.poll(test.ws));\n\n  // Set alarm to 2ms.  Expect 2ms db commit to start.\n  test.setAlarm(twoMs);\n  auto commit2MsFulfiller = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n  test.pollAndExpectCalls({});\n\n  // Gate as-of 2ms update is blocked.\n  auto gateWait2Ms = test.gate.wait(nullptr);\n  KJ_ASSERT(!gateWait2Ms.poll(test.ws));\n\n  // Set alarm to 3ms.  Expect 3ms db commit to start. The 2ms scheduleRun will never happen now\n  // that we've overwritten it while it was persisting to SQLite (before we send an update to the\n  // alarm manager).\n  test.setAlarm(threeMs);\n  auto commit3MsFulfiller = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n  test.pollAndExpectCalls({});\n\n  // Gate as-of 3ms update is blocked.\n  auto gateWait3Ms = test.gate.wait(nullptr);\n  KJ_ASSERT(!gateWait3Ms.poll(test.ws));\n\n  // Expect 2ms gate to be unblocked once the commit finishes, but don't expect a scheduleRun(2ms).\n  KJ_ASSERT(!gateWait2Ms.poll(test.ws));\n  commit2MsFulfiller->fulfill();\n  KJ_ASSERT(gateWait2Ms.poll(test.ws));\n  test.pollAndExpectCalls({});\n\n  // Fulfill 3ms db commit.  Expect 3ms alarm to be scheduled and 3ms gate to be unblocked.\n  KJ_ASSERT(!gateWait3Ms.poll(test.ws));\n\n  commit3MsFulfiller->fulfill();\n  KJ_ASSERT(gateWait3Ms.poll(test.ws));\n\n  auto fulfiller3Ms = kj::mv(test.pollAndExpectCalls({\"scheduleRun(3ms)\"})[0]);\n  test.pollAndExpectCalls({});\n\n  fulfiller3Ms->fulfill();\n}\n\nKJ_TEST(\"rejected move-earlier alarm scheduling request breaks gate\") {\n  ActorSqliteTest test({.monitorOutputGate = false});\n\n  auto promise = test.gate.onBroken();\n\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->reject(\n      KJ_EXCEPTION(FAILED, \"a_rejected_scheduleRun\"));\n\n  KJ_EXPECT_THROW_MESSAGE(\"a_rejected_scheduleRun\", promise.wait(test.ws));\n}\n\nKJ_TEST(\"rejected move-later alarm scheduling request does not break gate\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  // Update alarm to be later.  We expect the db to be persisted before the alarm scheduling.\n  // We simulate a failure during the alarm rescheduling, but expect it to not break the output\n  // gate.\n  test.setAlarm(twoMs);\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->reject(\n      KJ_EXCEPTION(FAILED, \"a_rejected_scheduleRun\"));\n\n  // Subsequent kv put succeeds.  In an earlier version of the code, this failed, due to capturing\n  // the scheduling failure as if it had broke the output gate, without actually breaking the\n  // output gate.\n  test.put(\"foo\", \"bar\");\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n}\n\nKJ_TEST(\"an exception thrown during merged commits does not hang\") {\n  ActorSqliteTest test({.monitorOutputGate = false});\n\n  auto promise = test.gate.onBroken();\n\n  // Initialize alarm state to 5ms.\n  test.setAlarm(fiveMs);\n  test.pollAndExpectCalls({\"scheduleRun(5ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == fiveMs);\n\n  // Update alarm to be earlier (4ms).  We expect the alarm scheduling to start.\n  test.setAlarm(fourMs);\n  auto fulfiller4Ms = kj::mv(test.pollAndExpectCalls({\"scheduleRun(4ms)\"})[0]);\n  auto gateWait4ms = test.gate.wait(nullptr);\n\n  // While 4ms scheduling request is in-flight, update alarm to be earlier (3ms).  We expect\n  // the two commit requests to merge and be blocked on the alarm scheduling request.\n  test.setAlarm(threeMs);\n  test.pollAndExpectCalls({});\n  auto gateWait3ms = test.gate.wait(nullptr);\n\n  // Reject the 4ms request.  We expect both gate waiting promises to unblock with exceptions.\n  KJ_ASSERT(!gateWait4ms.poll(test.ws));\n  KJ_ASSERT(!gateWait3ms.poll(test.ws));\n  fulfiller4Ms->reject(KJ_EXCEPTION(FAILED, \"a_rejected_scheduleRun\"));\n  KJ_ASSERT(gateWait4ms.poll(test.ws));\n  KJ_ASSERT(gateWait3ms.poll(test.ws));\n\n  KJ_EXPECT_THROW_MESSAGE(\"a_rejected_scheduleRun\", gateWait4ms.wait(test.ws));\n  KJ_EXPECT_THROW_MESSAGE(\"a_rejected_scheduleRun\", gateWait3ms.wait(test.ws));\n  KJ_EXPECT_THROW_MESSAGE(\"a_rejected_scheduleRun\", promise.wait(test.ws));\n}\n\nKJ_TEST(\"getAlarm/setAlarm check for brokenness\") {\n  ActorSqliteTest test({.monitorOutputGate = false});\n\n  auto promise = test.gate.onBroken();\n\n  // Break gate\n  test.put(\"foo\", \"bar\");\n  test.pollAndExpectCalls({\"commit\"})[0]->reject(KJ_EXCEPTION(FAILED, \"a_rejected_commit\"));\n\n  KJ_EXPECT_THROW_MESSAGE(\"a_rejected_commit\", promise.wait(test.ws));\n\n  // Apparently we don't actually set brokenness until the taskFailed handler runs, but presumably\n  // this is OK?\n  test.getAlarm();\n\n  // Ensure taskFailed handler runs and notices brokenness:\n  test.ws.poll();\n\n  KJ_EXPECT_THROW_MESSAGE(\"a_rejected_commit\", test.getAlarm());\n  KJ_EXPECT_THROW_MESSAGE(\"a_rejected_commit\", test.setAlarm(kj::none));\n  test.pollAndExpectCalls({});\n}\n\nKJ_TEST(\"calling deleteAll() preserves alarm state if alarm is set\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  {\n    KJ_ASSERT(!test.actor.isCommitScheduled());\n    ActorCache::DeleteAllResults results = test.actor.deleteAll({}, nullptr);\n    KJ_ASSERT(test.actor.isCommitScheduled());\n    KJ_ASSERT(results.backpressure == kj::none);\n    KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n    auto commitFulfiller = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n    KJ_ASSERT(results.count.wait(test.ws) == 0);\n    KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n    commitFulfiller->fulfill();\n    KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n    test.pollAndExpectCalls({});\n    KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n  }\n\n  {\n    // Should be fine to call deleteAll() a few times in succession, too:\n    KJ_ASSERT(!test.actor.isCommitScheduled());\n    ActorCache::DeleteAllResults results1 = test.actor.deleteAll({}, nullptr);\n    ActorCache::DeleteAllResults results2 = test.actor.deleteAll({}, nullptr);\n    KJ_ASSERT(test.actor.isCommitScheduled());\n    KJ_ASSERT(results1.backpressure == kj::none);\n    KJ_ASSERT(results2.backpressure == kj::none);\n    KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n    // Presumably fine to be performing the alarm state restoration after each db reset:\n    auto commitFulfillers = test.pollAndExpectCalls({\"commit\", \"commit\"});\n    KJ_ASSERT(results1.count.wait(test.ws) == 0);\n    KJ_ASSERT(results2.count.wait(test.ws) == 0);\n    KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n    commitFulfillers[0]->fulfill();\n    KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n    commitFulfillers[1]->fulfill();\n    KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n    test.pollAndExpectCalls({});\n    KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n  }\n}\n\nKJ_TEST(\"calling deleteAll() preserves alarm state if alarm is not set\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to empty value in metadata table.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n  test.setAlarm(kj::none);\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"scheduleRun(none)\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n\n  {\n    KJ_ASSERT(!test.actor.isCommitScheduled());\n    ActorCache::DeleteAllResults results = test.actor.deleteAll({}, nullptr);\n    KJ_ASSERT(test.actor.isCommitScheduled());\n    KJ_ASSERT(results.backpressure == kj::none);\n    KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n\n    auto commitFulfiller = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n    KJ_ASSERT(results.count.wait(test.ws) == 0);\n    KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n\n    commitFulfiller->fulfill();\n    KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n\n    // We can also assert that we leave the database empty, in case that turns out to be useful later:\n    auto q =\n        test.db.run(\"SELECT name FROM sqlite_master WHERE type='table' AND name='_cf_METADATA'\");\n    KJ_ASSERT(q.isDone());\n  }\n\n  {\n    // Should be fine to call deleteAll() a few times in succession, too:\n    KJ_ASSERT(!test.actor.isCommitScheduled());\n    ActorCache::DeleteAllResults results1 = test.actor.deleteAll({}, nullptr);\n    ActorCache::DeleteAllResults results2 = test.actor.deleteAll({}, nullptr);\n    KJ_ASSERT(test.actor.isCommitScheduled());\n    KJ_ASSERT(results1.backpressure == kj::none);\n    KJ_ASSERT(results2.backpressure == kj::none);\n    KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n\n    // Presumably fine that the deletion commits coalesce:\n    auto commitFulfiller = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n    KJ_ASSERT(results1.count.wait(test.ws) == 0);\n    KJ_ASSERT(results2.count.wait(test.ws) == 0);\n    KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n\n    commitFulfiller->fulfill();\n    KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n\n    auto q =\n        test.db.run(\"SELECT name FROM sqlite_master WHERE type='table' AND name='_cf_METADATA'\");\n    KJ_ASSERT(q.isDone());\n  }\n}\n\nKJ_TEST(\"calling deleteAll() during an implicit transaction preserves alarm state\") {\n  ActorSqliteTest test;\n\n  KJ_ASSERT(!test.actor.isCommitScheduled());\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n\n  ActorCache::DeleteAllResults results = test.actor.deleteAll({}, nullptr);\n  KJ_ASSERT(test.actor.isCommitScheduled());\n  KJ_ASSERT(results.backpressure == kj::none);\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n\n  auto commitFulfiller = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n  KJ_ASSERT(results.count.wait(test.ws) == 0);\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  commitFulfiller->fulfill();\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n}\n\nKJ_TEST(\"deleteAll with deleteAlarm option deletes alarm\") {\n  // Tests that deleteAll() with deleteAlarm=true deletes the alarm along with KV data,\n  // instead of preserving the alarm as it does by default.\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  // Call deleteAll() with deleteAlarm=true.\n  ActorCache::DeleteAllResults results = test.actor.deleteAll({}, nullptr, {.deleteAlarm = true});\n\n  // The alarm should now be deleted.\n  KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n\n  // Commit should include scheduling the alarm cancellation.\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"scheduleRun(none)\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n\n  KJ_ASSERT(results.count.wait(test.ws) == 0);\n  KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n}\n\nKJ_TEST(\"deleteAll without deleteAlarm option preserves alarm\") {\n  // Tests that deleteAll() without deleteAlarm (the default) preserves the alarm,\n  // which is the existing behavior.\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  // Call deleteAll() without deleteAlarm (default behavior).\n  ActorCache::DeleteAllResults results = test.actor.deleteAll({}, nullptr);\n\n  // The alarm should be preserved.\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n\n  KJ_ASSERT(results.count.wait(test.ws) == 0);\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n}\n\nKJ_TEST(\"deleteAll with deleteAlarm during alarm handler cancels deferred delete\") {\n  // Tests that calling deleteAll() with deleteAlarm=true while an alarm handler is running\n  // correctly deletes the alarm and cancels the deferred alarm deletion (haveDeferredDelete).\n  // When the handler's DeferredAlarmDeleter is dropped, it should NOT write a null alarm row\n  // since deleteAll already handled the deletion.\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  {\n    auto armResult = test.actor.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n    KJ_ASSERT(armResult.is<ActorSqlite::RunAlarmHandler>());\n\n    // During the handler, getAlarm() should return none (deferred delete is active).\n    KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n\n    // Call deleteAll() with deleteAlarm=true while the handler is running.\n    auto results = test.actor.deleteAll({}, nullptr, {.deleteAlarm = true});\n\n    // getAlarm() should still return none.\n    KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n\n    // Drop the DeferredAlarmDeleter (simulating handler success). Since deleteAll already\n    // cleared haveDeferredDelete, this should NOT write to the metadata table.\n  }\n\n  // The deleteAll commit should go through commitImpl(), which detects the alarm moved to none\n  // and schedules the cancellation.\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"scheduleRun(none)\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n\n  KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n}\n\nKJ_TEST(\"deleteAll without deleteAlarm during alarm handler still has deferred delete\") {\n  // Tests that calling deleteAll() without deleteAlarm while an alarm handler is running\n  // restores the alarm in metadata but leaves haveDeferredDelete active. When the handler\n  // finishes, the deferred deletion deletes the restored alarm.\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  {\n    auto armResult = test.actor.armAlarmHandler(oneMs, nullptr, testCurrentTime);\n    KJ_ASSERT(armResult.is<ActorSqlite::RunAlarmHandler>());\n\n    // During the handler, getAlarm() should return none (deferred delete is active).\n    KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n\n    // Call deleteAll() without deleteAlarm while the handler is running.\n    // This restores the alarm in metadata, but haveDeferredDelete is still true.\n    test.actor.deleteAll({}, nullptr);\n\n    // getAlarm() still returns none because haveDeferredDelete is still active.\n    KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n\n    // Drop the DeferredAlarmDeleter (simulating handler success). This triggers\n    // maybeDeleteDeferredAlarm() which deletes the restored alarm.\n  }\n\n  // The deleteAll commit goes first, then the deferred alarm deletion triggers its own commit\n  // with alarm scheduling.\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"scheduleRun(none)\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n\n  KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n}\n\nKJ_TEST(\"deleteAll deleteAlarm does not schedule alarm cancellation if setAlarm interleaves\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  // Start deleteAll with deleteAlarm=true and hold the commit.\n  test.actor.deleteAll({}, nullptr, {.deleteAlarm = true});\n  auto deleteAllCommit = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n\n  // While deleteAll commit is in-flight, set a later alarm.\n  test.setAlarm(twoMs);\n  auto setAlarmCommit = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == twoMs);\n\n  // Completing the deleteAll commit should NOT schedule a cancel because setAlarm interleaved.\n  deleteAllCommit->fulfill();\n  test.pollAndExpectCalls({});\n\n  // Completing the setAlarm commit should schedule the new alarm time.\n  setAlarmCommit->fulfill();\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n\n  KJ_ASSERT(expectSync(test.getAlarm()) == twoMs);\n}\n\nKJ_TEST(\"rolling back transaction leaves alarm in expected state\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 2ms.\n  test.setAlarm(twoMs);\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == twoMs);\n\n  {\n    auto txn = test.actor.startTransaction();\n    KJ_ASSERT(expectSync(txn->getAlarm({})) == twoMs);\n    txn->setAlarm(oneMs, {}, nullptr);\n    KJ_ASSERT(expectSync(txn->getAlarm({})) == oneMs);\n    // Dropping transaction without committing; should roll back.\n  }\n  KJ_ASSERT(expectSync(test.getAlarm()) == twoMs);\n}\n\nKJ_TEST(\"rolling back transaction leaves deferred alarm deletion in expected state\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 2ms.\n  test.setAlarm(twoMs);\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == twoMs);\n\n  {\n    auto armResult = test.actor.armAlarmHandler(twoMs, nullptr, testCurrentTime);\n    KJ_ASSERT(armResult.is<ActorSqlite::RunAlarmHandler>());\n\n    auto txn = test.actor.startTransaction();\n    KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n    test.setAlarm(oneMs);\n    KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n    txn->rollback().wait(test.ws);\n\n    // After rollback, getAlarm() still returns the deferred deletion result.\n    KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n\n    // After rollback, no changes committed, no change in scheduled alarm.\n    test.pollAndExpectCalls({});\n  }\n\n  // After handler, 2ms alarm is deleted.\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"scheduleRun(none)\"})[0]->fulfill();\n  KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n}\n\nKJ_TEST(\"committing transaction leaves deferred alarm deletion in expected state\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 2ms.\n  test.setAlarm(twoMs);\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == twoMs);\n\n  {\n    auto armResult = test.actor.armAlarmHandler(twoMs, nullptr, testCurrentTime);\n    KJ_ASSERT(armResult.is<ActorSqlite::RunAlarmHandler>());\n\n    auto txn = test.actor.startTransaction();\n    KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n    test.setAlarm(oneMs);\n    KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n    txn->commit();\n\n    // After commit, getAlarm() returns the committed value.\n    KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n    test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n    test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n    test.pollAndExpectCalls({});\n  }\n\n  // Alarm not deleted\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n}\n\nKJ_TEST(\"rolling back nested transaction leaves deferred alarm deletion in expected state\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 2ms.\n  test.setAlarm(twoMs);\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == twoMs);\n\n  {\n    auto armResult = test.actor.armAlarmHandler(twoMs, nullptr, testCurrentTime);\n    KJ_ASSERT(armResult.is<ActorSqlite::RunAlarmHandler>());\n\n    auto txn1 = test.actor.startTransaction();\n    KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n    {\n      // Rolling back nested transaction change leaves deferred deletion in place.\n      auto txn2 = test.actor.startTransaction();\n      KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n      test.setAlarm(oneMs);\n      KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n      txn2->rollback().wait(test.ws);\n      KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n    }\n    KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n    {\n      // Committing nested transaction changes parent transaction state to dirty.\n      auto txn3 = test.actor.startTransaction();\n      KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n      test.setAlarm(oneMs);\n      KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n      txn3->commit();\n      KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n    }\n    KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n    {\n      // Nested transaction of dirty transaction is dirty, rollback has no effect.\n      auto txn4 = test.actor.startTransaction();\n      KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n      txn4->rollback().wait(test.ws);\n      KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n    }\n    KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n    txn1->rollback().wait(test.ws);\n\n    // After root transaction rollback, getAlarm() still returns the deferred deletion result.\n    KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n\n    // After rollback, no changes committed, no change in scheduled alarm.\n    test.pollAndExpectCalls({});\n  }\n\n  // After handler, 2ms alarm is deleted.\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"scheduleRun(none)\"})[0]->fulfill();\n  KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n}\n\nKJ_TEST(\"database write operations check for brokenness\") {\n  ActorSqliteTest test({.monitorOutputGate = false});\n\n  auto promise = test.gate.onBroken();\n\n  // Break gate\n  test.put(\"foo\", \"bar\");\n  test.pollAndExpectCalls({\"commit\"})[0]->reject(KJ_EXCEPTION(FAILED, \"a_rejected_commit\"));\n\n  KJ_EXPECT_THROW_MESSAGE(\"a_rejected_commit\", promise.wait(test.ws));\n\n  // We don't actually set ActorSqlite's brokenness until the taskFailed handler runs...\n  test.ws.poll();\n\n  // Try making a write operation to the database, expecting it to throw the broken message via\n  // the onWrite handler:\n  KJ_EXPECT_THROW_MESSAGE(\n      \"a_rejected_commit\", test.db.run(\"CREATE TABLE IF NOT EXISTS counter (count INTEGER)\"));\n  test.pollAndExpectCalls({});\n}\n\nKJ_TEST(\"allowUnconfirmed put does not block output gate\") {\n  ActorSqliteTest test;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Do an unconfirmed put\n  test.put(\"foo\", \"bar\", {.allowUnconfirmed = true});\n\n  // Gate still isn't blocked, because we set `allowUnconfirmed`.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the transaction.\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should still not be blocked after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify data was written\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo\"))) == kj::str(\"bar\").asBytes());\n}\n\nKJ_TEST(\"confirmed put blocks output gate\") {\n  ActorSqliteTest test;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Do a confirmed put (default behavior)\n  test.put(\"foo\", \"bar\", {.allowUnconfirmed = false});\n\n  // Now it should be blocked.\n  KJ_ASSERT(!test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the transaction.\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should unblock after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify data was written\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo\"))) == kj::str(\"bar\").asBytes());\n}\n\nKJ_TEST(\"mixed confirmed and unconfirmed writes in same transaction use output gate\") {\n  ActorSqliteTest test;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Do an unconfirmed put followed by a confirmed put in the same transaction batch\n  test.put(\"foo\", \"bar\", {.allowUnconfirmed = true});\n  test.put(\"baz\", \"quux\", {.allowUnconfirmed = false});\n\n  // Since any write in the batch needs confirmation, the entire batch should use output gate\n  KJ_ASSERT(!test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the transaction.\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should unblock after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Both writes should be committed\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo\"))) == kj::str(\"bar\").asBytes());\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"baz\"))) == kj::str(\"quux\").asBytes());\n}\n\nKJ_TEST(\"allowUnconfirmed delete does not block output gate\") {\n  ActorSqliteTest test;\n\n  // First set up some data\n  test.put(\"foo\", \"bar\");\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should be unblocked after setup\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Perform an unconfirmed delete - need to add delete helper method or use actor directly\n  expectSync(test.actor.delete_(kj::str(\"foo\"), {.allowUnconfirmed = true}, nullptr));\n\n  // Gate still isn't blocked, because we set `allowUnconfirmed`.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the transaction.\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should still not be blocked after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Data should be deleted\n  KJ_ASSERT(expectSync(test.get(\"foo\")) == kj::none);\n}\n\nKJ_TEST(\"allowUnconfirmed putMultiple does not block output gate\") {\n  ActorSqliteTest test;\n\n  // Gate should be unblocked at start\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Create multiple key-value pairs for the test\n  kj::Vector<ActorCache::KeyValuePair> putKVs;\n  putKVs.add(ActorCache::KeyValuePair{kj::str(\"foo\"), kj::heapArray(kj::str(\"bar\").asBytes())});\n  putKVs.add(ActorCache::KeyValuePair{kj::str(\"baz\"), kj::heapArray(kj::str(\"qux\").asBytes())});\n  putKVs.add(ActorCache::KeyValuePair{kj::str(\"key3\"), kj::heapArray(kj::str(\"value3\").asBytes())});\n\n  // Perform an unconfirmed putMultiple within the implicit transaction\n  test.putMultiple(putKVs.releaseAsArray(), {.allowUnconfirmed = true});\n\n  // Gate still isn't blocked, because we set `allowUnconfirmed`.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the transaction.\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should still not be blocked after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify all data was written correctly\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo\"))) == kj::str(\"bar\").asBytes());\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"baz\"))) == kj::str(\"qux\").asBytes());\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"key3\"))) == kj::str(\"value3\").asBytes());\n}\n\nKJ_TEST(\"allowUnconfirmed deleteMultiple does not block output gate\") {\n  ActorSqliteTest test;\n\n  // First set up some data\n  kj::Vector<ActorCache::KeyValuePair> putKVs;\n  putKVs.add(ActorCache::KeyValuePair{kj::str(\"foo\"), kj::heapArray(kj::str(\"bar\").asBytes())});\n  putKVs.add(ActorCache::KeyValuePair{kj::str(\"baz\"), kj::heapArray(kj::str(\"qux\").asBytes())});\n  putKVs.add(ActorCache::KeyValuePair{kj::str(\"key3\"), kj::heapArray(kj::str(\"value3\").asBytes())});\n\n  test.putMultiple(putKVs.releaseAsArray());\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should be unblocked after setup\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Create array of keys to delete\n  kj::Vector<kj::String> deleteKeys;\n  deleteKeys.add(kj::str(\"foo\"));\n  deleteKeys.add(kj::str(\"baz\"));\n  deleteKeys.add(kj::str(\"key3\"));\n\n  // Perform an unconfirmed deleteMultiple\n  KJ_EXPECT(expectSync(\n                test.deleteMultiple(deleteKeys.releaseAsArray(), {.allowUnconfirmed = true})) == 3);\n\n  // Gate still isn't blocked, because we set `allowUnconfirmed`.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the transaction.\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should still not be blocked after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify all data was deleted\n  KJ_ASSERT(expectSync(test.get(\"foo\")) == kj::none);\n  KJ_ASSERT(expectSync(test.get(\"baz\")) == kj::none);\n  KJ_ASSERT(expectSync(test.get(\"key3\")) == kj::none);\n}\n\nKJ_TEST(\"unconfirmed write failure still breaks output gate\") {\n  ActorSqliteTest test({.monitorOutputGate = false});\n\n  auto promise = test.gate.onBroken();\n\n  // Do an unconfirmed put\n  test.put(\"foo\", \"bar\", {.allowUnconfirmed = true});\n\n  // The output gate is not applied initially.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n  KJ_ASSERT(!promise.poll(test.ws));\n\n  // Reject the commit to simulate failure\n  test.pollAndExpectCalls({\"commit\"})[0]->reject(KJ_EXCEPTION(FAILED, \"flush failed hard\"));\n\n  // Gate should be broken due to commit failure\n  KJ_EXPECT_THROW_MESSAGE(\"flush failed hard\", promise.wait(test.ws));\n}\n\nKJ_TEST(\"Direct SQL queries are confirmed writes\") {\n  ActorSqliteTest test;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  auto& db = KJ_ASSERT_NONNULL(test.actor.getSqliteDatabase());\n\n  db.run(\"CREATE TABLE myTable (i INTEGER PRIMARY KEY, s TEXT)\");\n  db.run(\"INSERT INTO myTable VALUES (1, \\\"a\\\")\");\n\n  // Now the gate should be blocked.\n  KJ_ASSERT(!test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the transaction.\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should unblock after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Make sure that the write actually succeeded.\n  {\n    auto query = db.run(\"SELECT * FROM myTable\");\n    KJ_ASSERT(!query.isDone());\n    KJ_EXPECT(query.getInt64(0) == 1);\n    KJ_EXPECT(query.getText(1) == \"a\");\n    query.nextRow();\n    KJ_ASSERT(query.isDone());\n  }\n}\n\nKJ_TEST(\"An unconfirmed put followed by a direct SQL queries requires the output gate\") {\n  ActorSqliteTest test;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  test.put(\"foo\", \"bar\", {.allowUnconfirmed = true});\n  auto& db = KJ_ASSERT_NONNULL(test.actor.getSqliteDatabase());\n  db.run(\"CREATE TABLE myTable (i INTEGER PRIMARY KEY, s TEXT)\");\n  db.run(\"INSERT INTO myTable VALUES (1, \\\"a\\\")\");\n\n  // Now the gate should be blocked.\n  KJ_ASSERT(!test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the transaction.\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should unblock after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Make sure that the write actually succeeded.\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo\"))) == kj::str(\"bar\").asBytes());\n  {\n    auto query = db.run(\"SELECT * FROM myTable\");\n    KJ_ASSERT(!query.isDone());\n    KJ_EXPECT(query.getInt64(0) == 1);\n    KJ_EXPECT(query.getText(1) == \"a\");\n    query.nextRow();\n    KJ_ASSERT(query.isDone());\n  }\n}\n\nKJ_TEST(\"sync() returns immediately when no writes are pending\") {\n  ActorSqliteTest test;\n\n  // When there are no pending writes, sync() should return a resolved promise\n  auto syncResult = test.sync();\n  auto syncPromise = kj::mv(KJ_ASSERT_NONNULL(syncResult));\n  KJ_ASSERT(syncPromise.poll(test.ws));\n}\n\nKJ_TEST(\"sync() waits for confirmed writes to complete\") {\n  ActorSqliteTest test;\n\n  // Do a confirmed write (default behavior)\n  test.put(\"foo\", \"bar\", {.allowUnconfirmed = false});\n\n  // sync() should return a promise that blocks until the commit completes\n  auto syncResult = test.sync();\n  KJ_ASSERT(syncResult != kj::none);\n\n  auto syncPromise = kj::mv(KJ_ASSERT_NONNULL(syncResult));\n\n  // The sync promise should not be ready yet\n  KJ_ASSERT(!syncPromise.poll(test.ws));\n\n  // Complete the commit\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Now the sync promise should be ready\n  KJ_ASSERT(syncPromise.poll(test.ws));\n  syncPromise.wait(test.ws);\n\n  // Verify data was written\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo\"))) == kj::str(\"bar\").asBytes());\n}\n\nKJ_TEST(\"sync() waits for unconfirmed writes to complete\") {\n  ActorSqliteTest test;\n\n  // Do an unconfirmed write\n  test.put(\"foo\", \"bar\", {.allowUnconfirmed = true});\n\n  // sync() should still return a promise that blocks until the commit completes\n  auto syncResult = test.sync();\n  KJ_ASSERT(syncResult != kj::none);\n\n  auto syncPromise = kj::mv(KJ_ASSERT_NONNULL(syncResult));\n\n  // The sync promise should not be ready yet\n  KJ_ASSERT(!syncPromise.poll(test.ws));\n\n  // Complete the commit\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Now the sync promise should be ready\n  KJ_ASSERT(syncPromise.poll(test.ws));\n  syncPromise.wait(test.ws);\n\n  // Verify data was written\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo\"))) == kj::str(\"bar\").asBytes());\n}\n\nKJ_TEST(\"sync() waits for multiple unconfirmed writes in a row\") {\n  ActorSqliteTest test;\n\n  // Do multiple unconfirmed writes - they should batch into a single transaction\n  test.put(\"foo\", \"bar\", {.allowUnconfirmed = true});\n  test.put(\"baz\", \"qux\", {.allowUnconfirmed = true});\n  test.put(\"key3\", \"value3\", {.allowUnconfirmed = true});\n\n  // sync() should wait for the batched commit\n  auto syncResult = test.sync();\n  KJ_ASSERT(syncResult != kj::none);\n  auto syncPromise = kj::mv(KJ_ASSERT_NONNULL(syncResult));\n\n  // The sync promise should not be ready yet\n  KJ_ASSERT(!syncPromise.poll(test.ws));\n\n  // Complete the single batched commit\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Now the sync promise should be ready\n  KJ_ASSERT(syncPromise.poll(test.ws));\n  syncPromise.wait(test.ws);\n\n  // Verify all writes were committed\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo\"))) == kj::str(\"bar\").asBytes());\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"baz\"))) == kj::str(\"qux\").asBytes());\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"key3\"))) == kj::str(\"value3\").asBytes());\n}\n\nKJ_TEST(\"sync() only waits for writes before it was called\") {\n  ActorSqliteTest test;\n\n  // First write\n  test.put(\"foo\", \"bar\", {.allowUnconfirmed = true});\n\n  // Call sync for the first write\n  auto syncResult = test.sync();\n  KJ_ASSERT(syncResult != kj::none);\n  auto syncPromise = kj::mv(KJ_ASSERT_NONNULL(syncResult));\n\n  // Complete first commit\n  auto firstCommit = kj::mv(test.pollAndExpectCalls({\"commit\"})[0]);\n  firstCommit->fulfill();\n\n  // First sync should complete\n  KJ_ASSERT(syncPromise.poll(test.ws));\n  syncPromise.wait(test.ws);\n\n  // Second write after sync was called\n  test.put(\"baz\", \"qux\", {.allowUnconfirmed = true});\n\n  // The original sync should still be complete (doesn't wait for new write)\n  // To verify this, let's get a new sync that should wait for the second write\n  auto syncResult2 = test.sync();\n  KJ_ASSERT(syncResult2 != kj::none);\n  auto syncPromise2 = kj::mv(KJ_ASSERT_NONNULL(syncResult2));\n\n  // Second sync should not be ready\n  KJ_ASSERT(!syncPromise2.poll(test.ws));\n\n  // Complete second commit\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Now second sync should complete\n  KJ_ASSERT(syncPromise2.poll(test.ws));\n  syncPromise2.wait(test.ws);\n}\n\nKJ_TEST(\"sync() propagates commit errors\") {\n  ActorSqliteTest test({.monitorOutputGate = false});\n\n  auto promise = test.gate.onBroken();\n\n  // Do an unconfirmed write\n  test.put(\"foo\", \"bar\", {.allowUnconfirmed = true});\n\n  // Call sync\n  auto syncResult = test.sync();\n  KJ_ASSERT(syncResult != kj::none);\n  auto syncPromise = kj::mv(KJ_ASSERT_NONNULL(syncResult));\n\n  // The sync promise should not be ready yet\n  KJ_ASSERT(!syncPromise.poll(test.ws));\n\n  // Reject the commit to simulate failure\n  test.pollAndExpectCalls({\"commit\"})[0]->reject(KJ_EXCEPTION(FAILED, \"commit failed\"));\n\n  // sync promise should become ready with an exception\n  KJ_ASSERT(syncPromise.poll(test.ws));\n  KJ_EXPECT_THROW_MESSAGE(\"commit failed\", syncPromise.wait(test.ws));\n\n  // Gate should also be broken\n  KJ_EXPECT_THROW_MESSAGE(\"commit failed\", promise.wait(test.ws));\n}\n\nKJ_TEST(\"sync() with mixed confirmed and unconfirmed writes\") {\n  ActorSqliteTest test;\n\n  // Do an unconfirmed write followed by a confirmed write\n  test.put(\"foo\", \"bar\", {.allowUnconfirmed = true});\n  test.put(\"baz\", \"qux\", {.allowUnconfirmed = false});\n\n  // sync() should wait for both writes\n  auto syncResult = test.sync();\n  KJ_ASSERT(syncResult != kj::none);\n  auto syncPromise = kj::mv(KJ_ASSERT_NONNULL(syncResult));\n\n  // The sync promise should not be ready yet\n  KJ_ASSERT(!syncPromise.poll(test.ws));\n\n  // Complete the commit (both writes are in the same transaction)\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Now the sync promise should be ready\n  KJ_ASSERT(syncPromise.poll(test.ws));\n  syncPromise.wait(test.ws);\n\n  // Both writes should be committed\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo\"))) == kj::str(\"bar\").asBytes());\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"baz\"))) == kj::str(\"qux\").asBytes());\n}\n\nKJ_TEST(\"multiple sync() calls for same commit\") {\n  ActorSqliteTest test;\n\n  // Do a write\n  test.put(\"foo\", \"bar\", {.allowUnconfirmed = true});\n\n  // Call sync multiple times - they should all wait for the same commit\n  auto syncResult1 = test.sync();\n  auto syncResult2 = test.sync();\n  auto syncResult3 = test.sync();\n\n  KJ_ASSERT(syncResult1 != kj::none);\n  KJ_ASSERT(syncResult2 != kj::none);\n  KJ_ASSERT(syncResult3 != kj::none);\n\n  auto syncPromise1 = kj::mv(KJ_ASSERT_NONNULL(syncResult1));\n  auto syncPromise2 = kj::mv(KJ_ASSERT_NONNULL(syncResult2));\n  auto syncPromise3 = kj::mv(KJ_ASSERT_NONNULL(syncResult3));\n\n  // None should be ready yet\n  KJ_ASSERT(!syncPromise1.poll(test.ws));\n  KJ_ASSERT(!syncPromise2.poll(test.ws));\n  KJ_ASSERT(!syncPromise3.poll(test.ws));\n\n  // Complete the commit\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // All sync promises should become ready\n  KJ_ASSERT(syncPromise1.poll(test.ws));\n  KJ_ASSERT(syncPromise2.poll(test.ws));\n  KJ_ASSERT(syncPromise3.poll(test.ws));\n\n  syncPromise1.wait(test.ws);\n  syncPromise2.wait(test.ws);\n  syncPromise3.wait(test.ws);\n}\n\nKJ_TEST(\"allowUnconfirmed setAlarm does not block output gate\") {\n  ActorSqliteTest test;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Do an unconfirmed setAlarm\n  test.setAlarm(oneMs, {.allowUnconfirmed = true});\n\n  // Gate still isn't blocked, because we set `allowUnconfirmed`.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the transaction - alarm scheduling happens before commit\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should still not be blocked after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify alarm was set\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n}\n\nKJ_TEST(\"confirmed setAlarm blocks output gate\") {\n  ActorSqliteTest test;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Do a confirmed setAlarm (default behavior)\n  test.setAlarm(oneMs, {.allowUnconfirmed = false});\n\n  // Gate should be blocked after scheduling starts\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  KJ_ASSERT(!test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the transaction.\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should unblock after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify alarm was set\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n}\n\nKJ_TEST(\"allowUnconfirmed setAlarm then confirmed put uses output gate\") {\n  ActorSqliteTest test;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Do an unconfirmed setAlarm followed by a confirmed put in the same transaction batch\n  test.setAlarm(oneMs, {.allowUnconfirmed = true});\n  test.put(\"foo\", \"bar\", {.allowUnconfirmed = false});\n\n  // Since any write in the batch needs confirmation, the entire batch should use output gate\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  KJ_ASSERT(!test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the transaction.\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should unblock after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Both operations should be committed\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo\"))) == kj::str(\"bar\").asBytes());\n}\n\nKJ_TEST(\"allowUnconfirmed setAlarm with storage ops\") {\n  ActorSqliteTest test;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Do unconfirmed setAlarm with unconfirmed storage writes\n  test.setAlarm(oneMs, {.allowUnconfirmed = true});\n  test.put(\"foo\", \"bar\", {.allowUnconfirmed = true});\n\n  // Gate still isn't blocked since both operations are unconfirmed\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the transaction\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should still not be blocked\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify both alarm and storage writes committed\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo\"))) == kj::str(\"bar\").asBytes());\n}\n\nKJ_TEST(\"allowUnconfirmed setAlarm updating existing alarm\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 2ms.\n  test.setAlarm(twoMs);\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == twoMs);\n\n  // Gate should be unblocked after setup\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Update alarm to earlier time with allowUnconfirmed\n  test.setAlarm(oneMs, {.allowUnconfirmed = true});\n\n  // Gate still isn't blocked\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the transaction - when moving alarm earlier, schedule happens first\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should still not be blocked\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify alarm was updated\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n}\n\nKJ_TEST(\"allowUnconfirmed setAlarm to later time\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  // Gate should be unblocked after setup\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Update alarm to later time with allowUnconfirmed\n  test.setAlarm(twoMs, {.allowUnconfirmed = true});\n\n  // Gate still isn't blocked\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the transaction - when moving alarm later, commit happens first\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"scheduleRun(2ms)\"})[0]->fulfill();\n\n  // Gate should still not be blocked\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify alarm was updated\n  KJ_ASSERT(expectSync(test.getAlarm()) == twoMs);\n}\n\nKJ_TEST(\"allowUnconfirmed setAlarm to clear alarm\") {\n  ActorSqliteTest test;\n\n  // Initialize alarm state to 1ms.\n  test.setAlarm(oneMs);\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({});\n  KJ_ASSERT(expectSync(test.getAlarm()) == oneMs);\n\n  // Gate should be unblocked after setup\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Clear alarm with allowUnconfirmed\n  test.setAlarm(kj::none, {.allowUnconfirmed = true});\n\n  // Gate still isn't blocked\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the transaction\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"scheduleRun(none)\"})[0]->fulfill();\n\n  // Gate should still not be blocked\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify alarm was cleared\n  KJ_ASSERT(expectSync(test.getAlarm()) == kj::none);\n}\n\nKJ_TEST(\"unconfirmed setAlarm failure still breaks output gate\") {\n  ActorSqliteTest test({.monitorOutputGate = false});\n\n  auto promise = test.gate.onBroken();\n\n  // Do an unconfirmed setAlarm\n  test.setAlarm(oneMs, {.allowUnconfirmed = true});\n\n  // The output gate is not applied initially.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n  KJ_ASSERT(!promise.poll(test.ws));\n\n  // Fulfill scheduleRun but reject the commit to simulate failure\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->reject(KJ_EXCEPTION(FAILED, \"alarm commit failed\"));\n\n  // Gate should be broken due to commit failure\n  KJ_EXPECT_THROW_MESSAGE(\"alarm commit failed\", promise.wait(test.ws));\n}\n\nKJ_TEST(\"sync() throws after critical error in explicit transaction\") {\n  ActorSqliteTest test({.monitorOutputGate = false});\n  auto heapLimit = [&]() {\n    auto row = test.db.run(\"PRAGMA hard_heap_limit\");\n    return row.getInt(0);\n  }();\n  KJ_DBG(heapLimit);\n  KJ_DEFER(sqlite3_hard_heap_limit64(heapLimit););\n\n  // Start an explicit transaction\n  auto txn = test.actor.startTransaction();\n\n  // Do a write within the transaction\n  txn->put(kj::str(\"foo\"), kj::heapArray(kj::str(\"bar\").asBytes()), {}, nullptr);\n\n  // Trigger a critical error using SQLITE_NOMEM by setting a very low heap limit\n  // and then trying to insert a large value.\n  try {\n    // Set SQLite's memory limit very low to trigger SQLITE_NOMEM\n    test.db.run(\"PRAGMA hard_heap_limit=8192\");  // 8KB limit\n\n    // Create data that will exceed the memory limit\n    auto largeData = kj::heapArray<byte>(50000, 'X');  // 50KB\n\n    // This should trigger SQLITE_NOMEM, causing SQLite to auto-rollback the transaction,\n    // which will trigger the critical error handler.\n    //\n    // We have to copy it again in order to convert to a Array<const byte> from an Array<byte>.\n    txn->put(kj::str(\"large_key\"), kj::heapArray<const byte>(largeData.asBytes()), /*options=*/{},\n        nullptr);\n    KJ_FAIL_ASSERT(\"Query should have failed with SQLITE_NOMEM\");\n  } catch (kj::Exception& e) {\n    // Expected: out of memory error. We catch and ignore this to continue the test.\n    KJ_ASSERT(e.getDescription().contains(\"out of memory\"));\n  }\n\n  // sync() should also throw an exception because the storage is now broken\n  auto syncResult = test.sync();\n  KJ_ASSERT(syncResult != kj::none);\n  auto syncPromise = kj::mv(KJ_ASSERT_NONNULL(syncResult));\n  KJ_EXPECT_THROW_MESSAGE(\"broken\", syncPromise.wait(test.ws));\n\n  // The transaction is now in a broken state due to the critical error.\n  // Attempting to commit should fail.\n  KJ_EXPECT_THROW_MESSAGE(\"broken\", txn->commit());\n}\n\nKJ_TEST(\"allowUnconfirmed put in explicit transaction does not block output gate\") {\n  ActorSqliteTest test;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Start an explicit transaction\n  auto txn = test.actor.startTransaction();\n\n  // Do an unconfirmed put within the transaction\n  txn->put(\n      kj::str(\"foo\"), kj::heapArray(kj::str(\"bar\").asBytes()), {.allowUnconfirmed = true}, nullptr);\n\n  // Gate still isn't blocked during the transaction, because we set `allowUnconfirmed`.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Commit the transaction\n  txn->commit();\n\n  // Gate should still not be blocked during commit because all writes were unconfirmed\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the commit\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should still not be blocked after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify data was written\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo\"))) == kj::str(\"bar\").asBytes());\n}\n\nKJ_TEST(\"confirmed put in explicit transaction blocks output gate on commit\") {\n  ActorSqliteTest test;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Start an explicit transaction\n  auto txn = test.actor.startTransaction();\n\n  // Do a confirmed put (default behavior)\n  txn->put(kj::str(\"foo\"), kj::heapArray(kj::str(\"bar\").asBytes()), {.allowUnconfirmed = false},\n      nullptr);\n\n  // Gate should still not be blocked during the transaction - explicit txns only lock on commit\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Commit the transaction\n  txn->commit();\n\n  // Now the gate should be blocked because we're committing a confirmed write\n  KJ_ASSERT(!test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the commit\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should unblock after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify data was written\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo\"))) == kj::str(\"bar\").asBytes());\n}\n\nKJ_TEST(\"mixed confirmed and unconfirmed puts in explicit transaction use output gate\") {\n  ActorSqliteTest test;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Start an explicit transaction\n  auto txn = test.actor.startTransaction();\n\n  // Do an unconfirmed put followed by a confirmed put\n  txn->put(\n      kj::str(\"foo\"), kj::heapArray(kj::str(\"bar\").asBytes()), {.allowUnconfirmed = true}, nullptr);\n  txn->put(kj::str(\"baz\"), kj::heapArray(kj::str(\"quux\").asBytes()), {.allowUnconfirmed = false},\n      nullptr);\n\n  // Gate should still not be blocked during the transaction\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Commit the transaction\n  txn->commit();\n\n  // Since any write in the transaction needs confirmation, commit should use output gate\n  KJ_ASSERT(!test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the commit\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should unblock after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Both writes should be committed\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo\"))) == kj::str(\"bar\").asBytes());\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"baz\"))) == kj::str(\"quux\").asBytes());\n}\n\nKJ_TEST(\"allowUnconfirmed delete in explicit transaction does not block output gate\") {\n  ActorSqliteTest test;\n\n  // First set up some data\n  test.put(\"foo\", \"bar\");\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should be unblocked after setup\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Start an explicit transaction\n  auto txn = test.actor.startTransaction();\n\n  // Perform an unconfirmed delete\n  expectSync(txn->delete_(kj::str(\"foo\"), {.allowUnconfirmed = true}, nullptr));\n\n  // Gate still isn't blocked during the transaction\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Commit the transaction\n  txn->commit();\n\n  // Gate should still not be blocked during commit because the delete was unconfirmed\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the commit\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should still not be blocked after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify data was deleted\n  KJ_ASSERT(expectSync(test.get(\"foo\")) == kj::none);\n}\n\nKJ_TEST(\"allowUnconfirmed putMultiple in explicit transaction does not block output gate\") {\n  ActorSqliteTest test;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Start an explicit transaction\n  auto txn = test.actor.startTransaction();\n\n  // Do an unconfirmed putMultiple\n  auto pairs = kj::heapArrayBuilder<ActorCacheOps::KeyValuePair>(2);\n  pairs.add(ActorCacheOps::KeyValuePair{kj::str(\"foo\"), kj::heapArray(kj::str(\"bar\").asBytes())});\n  pairs.add(ActorCacheOps::KeyValuePair{kj::str(\"baz\"), kj::heapArray(kj::str(\"quux\").asBytes())});\n  txn->put(pairs.finish(), {.allowUnconfirmed = true}, nullptr);\n\n  // Gate still isn't blocked during the transaction\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Commit the transaction\n  txn->commit();\n\n  // Gate should still not be blocked during commit\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the commit\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should still not be blocked after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify data was written\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"foo\"))) == kj::str(\"bar\").asBytes());\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"baz\"))) == kj::str(\"quux\").asBytes());\n}\n\nKJ_TEST(\"allowUnconfirmed deleteMultiple in explicit transaction does not block output gate\") {\n  ActorSqliteTest test;\n\n  // First set up some data\n  test.put(\"foo\", \"bar\");\n  test.put(\"baz\", \"quux\");\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should be unblocked after setup\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Start an explicit transaction\n  auto txn = test.actor.startTransaction();\n\n  // Perform an unconfirmed deleteMultiple\n  auto keys = kj::heapArrayBuilder<ActorCacheOps::Key>(2);\n  keys.add(kj::str(\"foo\"));\n  keys.add(kj::str(\"baz\"));\n  expectSync(txn->delete_(keys.finish(), {.allowUnconfirmed = true}, nullptr));\n\n  // Gate still isn't blocked during the transaction\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Commit the transaction\n  txn->commit();\n\n  // Gate should still not be blocked during commit\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the commit\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should still not be blocked after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify data was deleted\n  KJ_ASSERT(expectSync(test.get(\"foo\")) == kj::none);\n  KJ_ASSERT(expectSync(test.get(\"baz\")) == kj::none);\n}\n\nKJ_TEST(\"allowUnconfirmed setAlarm in explicit transaction does not block output gate\") {\n  ActorSqliteTest test;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Start an explicit transaction\n  auto txn = test.actor.startTransaction();\n\n  // Set an alarm with allowUnconfirmed\n  txn->setAlarm(oneMs, {.allowUnconfirmed = true}, nullptr);\n\n  // Gate still isn't blocked during the transaction\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Commit the transaction\n  txn->commit();\n\n  // Gate should still not be blocked during commit\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the scheduleRun and commit\n  test.pollAndExpectCalls({\"scheduleRun(1ms)\"})[0]->fulfill();\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should still not be blocked after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify alarm was set\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.getAlarm())) == oneMs);\n}\n\nKJ_TEST(\"nested transaction: unconfirmed child commit does not block output gate\") {\n  ActorSqliteTest test;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Start a parent transaction\n  auto parentTxn = test.actor.startTransaction();\n\n  // Do an unconfirmed put in the parent\n  parentTxn->put(kj::str(\"parent\"), kj::heapArray(kj::str(\"data\").asBytes()),\n      {.allowUnconfirmed = true}, nullptr);\n\n  {\n    // Start a nested child transaction\n    auto childTxn = test.actor.startTransaction();\n\n    // Do an unconfirmed put in the child\n    childTxn->put(kj::str(\"child\"), kj::heapArray(kj::str(\"data\").asBytes()),\n        {.allowUnconfirmed = true}, nullptr);\n\n    // Gate still isn't blocked\n    KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n    // Commit the child transaction\n    childTxn->commit();\n  }\n\n  // Gate should still not be blocked after child commit\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Commit the parent transaction\n  parentTxn->commit();\n\n  // Gate should still not be blocked during parent commit because all writes were unconfirmed\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the commit\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should still not be blocked after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify both writes were committed\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"parent\"))) == kj::str(\"data\").asBytes());\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"child\"))) == kj::str(\"data\").asBytes());\n}\n\nKJ_TEST(\"nested transaction: confirmed child propagates to parent commit\") {\n  ActorSqliteTest test;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Start a parent transaction with unconfirmed write\n  auto parentTxn = test.actor.startTransaction();\n  parentTxn->put(kj::str(\"parent\"), kj::heapArray(kj::str(\"data\").asBytes()),\n      {.allowUnconfirmed = true}, nullptr);\n\n  {\n    // Start a nested child transaction\n    auto childTxn = test.actor.startTransaction();\n\n    // Do a confirmed put in the child\n    childTxn->put(kj::str(\"child\"), kj::heapArray(kj::str(\"data\").asBytes()),\n        {.allowUnconfirmed = false}, nullptr);\n\n    // Gate still isn't blocked during the transaction\n    KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n    // Commit the child transaction - this should propagate someWriteConfirmed to parent\n    childTxn->commit();\n  }\n\n  // Gate should still not be blocked after child commit (no real commit yet)\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Commit the parent transaction\n  parentTxn->commit();\n\n  // Now the gate should be blocked because the child had a confirmed write\n  KJ_ASSERT(!test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the commit\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should unblock after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify both writes were committed\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"parent\"))) == kj::str(\"data\").asBytes());\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"child\"))) == kj::str(\"data\").asBytes());\n}\n\nKJ_TEST(\"nested transaction: confirmed parent with unconfirmed child blocks output gate\") {\n  ActorSqliteTest test;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Start a parent transaction with confirmed write\n  auto parentTxn = test.actor.startTransaction();\n  parentTxn->put(kj::str(\"parent\"), kj::heapArray(kj::str(\"data\").asBytes()),\n      {.allowUnconfirmed = false}, nullptr);\n\n  {\n    // Start a nested child transaction\n    auto childTxn = test.actor.startTransaction();\n\n    // Do an unconfirmed put in the child\n    childTxn->put(kj::str(\"child\"), kj::heapArray(kj::str(\"data\").asBytes()),\n        {.allowUnconfirmed = true}, nullptr);\n\n    // Gate still isn't blocked during the transaction\n    KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n    // Commit the child transaction\n    childTxn->commit();\n  }\n\n  // Gate should still not be blocked after child commit\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Commit the parent transaction\n  parentTxn->commit();\n\n  // Now the gate should be blocked because the parent had a confirmed write\n  KJ_ASSERT(!test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the commit\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should unblock after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify both writes were committed\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"parent\"))) == kj::str(\"data\").asBytes());\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"child\"))) == kj::str(\"data\").asBytes());\n}\n\nKJ_TEST(\"nested transaction: deeply nested confirmed write propagates to root\") {\n  ActorSqliteTest test;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Start a parent transaction with unconfirmed write\n  auto txn1 = test.actor.startTransaction();\n  txn1->put(kj::str(\"level1\"), kj::heapArray(kj::str(\"data\").asBytes()), {.allowUnconfirmed = true},\n      nullptr);\n\n  {\n    // Start a second level nested transaction with unconfirmed write\n    auto txn2 = test.actor.startTransaction();\n    txn2->put(kj::str(\"level2\"), kj::heapArray(kj::str(\"data\").asBytes()),\n        {.allowUnconfirmed = true}, nullptr);\n\n    {\n      // Start a third level nested transaction with confirmed write\n      auto txn3 = test.actor.startTransaction();\n      txn3->put(kj::str(\"level3\"), kj::heapArray(kj::str(\"data\").asBytes()),\n          {.allowUnconfirmed = false}, nullptr);\n\n      // Gate still isn't blocked during the transaction\n      KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n      // Commit level 3 - should propagate someWriteConfirmed to level 2\n      txn3->commit();\n    }\n\n    KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n    // Commit level 2 - should propagate someWriteConfirmed to level 1\n    txn2->commit();\n  }\n\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Commit level 1 (root transaction)\n  txn1->commit();\n\n  // Now the gate should be blocked because level 3 had a confirmed write\n  KJ_ASSERT(!test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the commit\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should unblock after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify all writes were committed\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"level1\"))) == kj::str(\"data\").asBytes());\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"level2\"))) == kj::str(\"data\").asBytes());\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"level3\"))) == kj::str(\"data\").asBytes());\n}\n\nKJ_TEST(\"nested transaction: rollback resets someWriteConfirmed flag\") {\n  ActorSqliteTest test;\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Start a parent transaction with unconfirmed write\n  auto parentTxn = test.actor.startTransaction();\n  parentTxn->put(kj::str(\"parent\"), kj::heapArray(kj::str(\"data\").asBytes()),\n      {.allowUnconfirmed = true}, nullptr);\n\n  {\n    // Start a nested child transaction\n    auto childTxn = test.actor.startTransaction();\n\n    // Do a confirmed put in the child\n    childTxn->put(kj::str(\"child\"), kj::heapArray(kj::str(\"data\").asBytes()),\n        {.allowUnconfirmed = false}, nullptr);\n\n    // Gate still isn't blocked during the transaction\n    KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n    // Rollback the child transaction instead of committing\n    childTxn->rollback().wait(test.ws);\n  }\n\n  // Gate should still not be blocked\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Commit the parent transaction\n  parentTxn->commit();\n\n  // Gate should still not be blocked because child was rolled back\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Complete the commit\n  test.pollAndExpectCalls({\"commit\"})[0]->fulfill();\n\n  // Gate should still not be blocked after commit completes\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Verify only parent write was committed\n  KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get(\"parent\"))) == kj::str(\"data\").asBytes());\n  KJ_ASSERT(expectSync(test.get(\"child\")) == kj::none);\n}\n\nKJ_TEST(\"explicit transaction: commit failure breaks output gate even for unconfirmed writes\") {\n  ActorSqliteTest test({.monitorOutputGate = false});\n\n  auto promise = test.gate.onBroken();\n\n  // Gate is currently not blocked.\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Start an explicit transaction\n  auto txn = test.actor.startTransaction();\n\n  // Do an unconfirmed put\n  txn->put(\n      kj::str(\"foo\"), kj::heapArray(kj::str(\"bar\").asBytes()), {.allowUnconfirmed = true}, nullptr);\n\n  // Commit the transaction\n  txn->commit();\n\n  // Gate should not be blocked yet because write was unconfirmed\n  KJ_ASSERT(test.gate.wait(nullptr).poll(test.ws));\n\n  // Reject the commit to simulate failure\n  test.pollAndExpectCalls({\"commit\"})[0]->reject(KJ_EXCEPTION(FAILED, \"commit failed\"));\n\n  // Gate should now be broken due to commit failure, even though write was unconfirmed\n  KJ_EXPECT_THROW_MESSAGE(\"commit failed\", promise.wait(test.ws));\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/actor-sqlite.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"actor-sqlite.h\"\n\n#include \"io-gate.h\"\n\n#include <workerd/jsg/exception.h>\n#include <workerd/util/autogate.h>\n#include <workerd/util/sentry.h>\n\n#include <kj/exception.h>\n#include <kj/function.h>\n\n#include <algorithm>\n\nnamespace workerd {\n\nnamespace {\n\n// Returns true if a given (set or unset) alarm will fire earlier than another.\nstatic bool willFireEarlier(kj::Maybe<kj::Date> alarm1, kj::Maybe<kj::Date> alarm2) {\n  // Intuitively, an unset alarm is effectively indistinguishable from an alarm set at infinity.\n  return alarm1.orDefault(kj::maxValue) < alarm2.orDefault(kj::maxValue);\n}\n\n// Helper to make kj::Maybe<kj::Date> loggable - returns the date or kj::maxValue for logging\nstatic kj::Date logDate(kj::Maybe<kj::Date> maybeDate) {\n  return maybeDate.orDefault(kj::maxValue);\n}\n\n// Set options.allowUnconfirmed to false and log a reason why.\nvoid disableAllowUnconfirmed(ActorCacheOps::WriteOptions& options, kj::StringPtr reason) {\n  if (options.allowUnconfirmed) {\n    KJ_LOG(WARNING, \"NOSENTRY allowUnconfirmed disabled\", reason);\n    options.allowUnconfirmed = false;\n  }\n}\n\n}  // namespace\n\nActorSqlite::ActorSqlite(kj::Own<SqliteDatabase> dbParam,\n    OutputGate& outputGate,\n    kj::Function<kj::Promise<void>(SpanParent)> commitCallback,\n    Hooks& hooks,\n    bool debugAlarmSyncParam)\n    : db(kj::mv(dbParam)),\n      outputGate(outputGate),\n      commitCallback(kj::mv(commitCallback)),\n      hooks(hooks),\n      kv(*db),\n      metadata(*db),\n      commitTasks(*this),\n      debugAlarmSync(debugAlarmSyncParam) {\n  db->onWrite(KJ_BIND_METHOD(*this, onWrite));\n  db->onCriticalError(KJ_BIND_METHOD(*this, onCriticalError));\n  lastConfirmedAlarmDbState = metadata.getAlarm();\n\n  // Because we preserve an invariant that scheduled alarms are always at or earlier than\n  // persisted db alarm state, it should be OK to populate our idea of the latest scheduled alarm\n  // using the current db alarm state.  At worst, it may perform one unnecessary scheduling\n  // request in cases where a previous alarm-state-altering transaction failed.\n  alarmScheduledNoLaterThan = metadata.getAlarm();\n}\n\nActorSqlite::ImplicitTxn::ImplicitTxn(ActorSqlite& parent): parent(parent) {\n  KJ_REQUIRE(parent.currentTxn.is<NoTxn>());\n  parent.beginTxn.run();\n  parent.currentTxn = this;\n}\nActorSqlite::ImplicitTxn::~ImplicitTxn() noexcept(false) {\n  KJ_IF_SOME(c, parent.currentTxn.tryGet<ImplicitTxn*>()) {\n    if (c == this) {\n      parent.currentTxn.init<NoTxn>();\n    }\n  }\n  if (!committed && parent.broken == kj::none) {\n    // Failed to commit, so roll back.\n    //\n    // This should only happen in cases of catastrophic error. Since this is rarely actually\n    // executed, we don't prepare a statement for it.\n    parent.db->run(\"ROLLBACK TRANSACTION\");\n  }\n}\n\nvoid ActorSqlite::ImplicitTxn::commit() {\n  // Ignore redundant commit()s.\n  if (!committed) {\n    parent.commitTxn.run();\n    committed = true;\n  }\n}\n\nvoid ActorSqlite::ImplicitTxn::rollback() {\n  // Ignore redundant commit()s.\n  if (!committed) {\n    // As of this writing, rollback() is only called when the database is about to be reset.\n    // Preparing a statement for it would be a waste since that statement would never be executed\n    // more than once, since resetting requires repreparing all statements anyway. So we don't\n    // bother.\n    parent.db->run(\"ROLLBACK TRANSACTION\");\n    committed = true;\n  }\n}\n\nvoid ActorSqlite::ImplicitTxn::setSomeWriteConfirmed(bool someWriteConfirmed) {\n  this->someWriteConfirmed = someWriteConfirmed;\n}\n\nbool ActorSqlite::ImplicitTxn::isSomeWriteConfirmed() const {\n  return someWriteConfirmed;\n}\n\nActorSqlite::ExplicitTxn::ExplicitTxn(ActorSqlite& actorSqlite): actorSqlite(actorSqlite) {\n  KJ_SWITCH_ONEOF(actorSqlite.currentTxn) {\n    KJ_CASE_ONEOF(_, NoTxn) {}\n    KJ_CASE_ONEOF(implicit, ImplicitTxn*) {\n      // An implicit transaction is open, commit it now because it would be weird if writes\n      // performed before the explicit transaction started were postponed until the transaction\n      // completes. Note that this isn't violating any atomicity guarantees because the transaction\n      // API is async, and atomicity is only guaranteed over synchronous code.\n      implicit->commit();\n    }\n    KJ_CASE_ONEOF(exp, ExplicitTxn*) {\n      KJ_REQUIRE(!exp->hasChild,\n          \"critical section should have blocked creation of more than one child at a time\");\n      parent = kj::addRef(*exp);\n      exp->hasChild = true;\n      depth = exp->depth + 1;\n      alarmDirty = exp->alarmDirty;\n      someWriteConfirmed = exp->someWriteConfirmed;\n    }\n  }\n  actorSqlite.currentTxn = this;\n\n  // To support nested transactions, we assign each savepoint a name based on its nesting depth.\n  // Unfortunately this means we cannot prepare the statement, unless we prepare a series of\n  // statements for each depth. (Actually, it could be reasonable to prepare statements for\n  // depth 0 specifically, but I'm not going to try it for now.)\n  actorSqlite.db->run(\n      {.regulator = SqliteDatabase::TRUSTED}, kj::str(\"SAVEPOINT _cf_savepoint_\", depth));\n}\nActorSqlite::ExplicitTxn::~ExplicitTxn() noexcept(false) {\n  KJ_DEFER([&]() noexcept {\n    // We'd better crash if any of this state update fails, otherwise dangling pointers.\n\n    // This is wrapped in a KJ_DEFER because we want it to run no matter what *after* the rollback\n    // fix and KJ_DEFER seemed like the cleanest way to do this.\n\n    KJ_ASSERT(!hasChild);\n    auto cur = KJ_ASSERT_NONNULL(actorSqlite.currentTxn.tryGet<ExplicitTxn*>());\n    KJ_ASSERT(cur == this);\n    KJ_IF_SOME(p, parent) {\n      p.get()->hasChild = false;\n      actorSqlite.currentTxn = p.get();\n    } else {\n      actorSqlite.currentTxn.init<NoTxn>();\n    }\n  }(););\n\n  if (!committed && actorSqlite.broken == kj::none) {\n    // Assume rollback if not committed.\n    rollbackImpl();\n  }\n}\n\nbool ActorSqlite::ExplicitTxn::getAlarmDirty() {\n  return alarmDirty;\n}\n\nvoid ActorSqlite::ExplicitTxn::setAlarmDirty() {\n  alarmDirty = true;\n}\n\nvoid ActorSqlite::ExplicitTxn::setSomeWriteConfirmed(bool someWriteConfirmed) {\n  this->someWriteConfirmed = someWriteConfirmed;\n}\n\nbool ActorSqlite::ExplicitTxn::isSomeWriteConfirmed() const {\n  return someWriteConfirmed;\n}\n\nkj::Maybe<kj::Promise<void>> ActorSqlite::ExplicitTxn::commit() {\n  actorSqlite.requireNotBroken();\n  KJ_REQUIRE(!hasChild,\n      \"critical sections should have prevented committing transaction while \"\n      \"nested txn is outstanding\");\n\n  // Start the schedule request before root transaction commit(), for correctness in workerd.\n  kj::Maybe<PrecommitAlarmState> precommitAlarmState;\n  if (parent == kj::none) {\n    precommitAlarmState = actorSqlite.startPrecommitAlarmScheduling();\n  }\n\n  actorSqlite.db->run(\n      {.regulator = SqliteDatabase::TRUSTED}, kj::str(\"RELEASE _cf_savepoint_\", depth));\n  committed = true;\n\n  KJ_IF_SOME(p, parent) {\n    if (alarmDirty) {\n      p->alarmDirty = true;\n    }\n    if (someWriteConfirmed) {\n      p->someWriteConfirmed = true;\n    }\n  } else {\n    if (alarmDirty) {\n      actorSqlite.haveDeferredDelete = false;\n    }\n\n    // We committed the root transaction, so it's time to signal any replication layer and lock\n    // the output gate in the meantime.\n\n    // Unlike ImplicitTxn, which locks the output gate at the start of the first write that requires\n    // confirmation, ExplicitTxn only locks when we're going to confirm the commit.  I think this\n    // makes since given the explicit commit call.\n    auto commitPromise = kj::evalNow([this, &precommitAlarmState]() {\n      return actorSqlite.commitImpl(\n          kj::mv(KJ_ASSERT_NONNULL(precommitAlarmState)), actorSqlite.currentCommitSpan.addRef());\n    })\n                             .catch_([outputGate = &actorSqlite.outputGate,\n                                         spanParent = actorSqlite.currentCommitSpan.addRef()](\n                                         kj::Exception&& e) mutable {\n      // Unconditionally break the output gate if commit threw an error, no matter whether the\n      // commit was confirmed or unconfirmed.\n      return outputGate->lockWhile(kj::Promise<void>(kj::mv(e)), kj::mv(spanParent));\n    });\n    if (someWriteConfirmed) {\n      commitPromise = actorSqlite.outputGate.lockWhile(\n          kj::mv(commitPromise), actorSqlite.currentCommitSpan.addRef());\n    }\n    auto forkedPromise = commitPromise.fork();\n    actorSqlite.commitTasks.add(forkedPromise.addBranch());\n    actorSqlite.lastCommit = kj::mv(forkedPromise);\n  }\n\n  // No backpressure for SQLite.\n  return kj::none;\n}\n\nkj::Promise<void> ActorSqlite::ExplicitTxn::rollback() {\n  actorSqlite.requireNotBroken();\n  JSG_REQUIRE(!hasChild, Error,\n      \"Cannot roll back an outer transaction while a nested transaction is still running.\");\n  if (!committed) {\n    rollbackImpl();\n    committed = true;\n  }\n  return kj::READY_NOW;\n}\n\nvoid ActorSqlite::ExplicitTxn::rollbackImpl() noexcept(false) {\n  actorSqlite.db->run(\n      {.regulator = SqliteDatabase::TRUSTED}, kj::str(\"ROLLBACK TO _cf_savepoint_\", depth));\n  actorSqlite.db->run(\n      {.regulator = SqliteDatabase::TRUSTED}, kj::str(\"RELEASE _cf_savepoint_\", depth));\n  KJ_IF_SOME(p, parent) {\n    alarmDirty = p->alarmDirty;\n    someWriteConfirmed = p->someWriteConfirmed;\n  } else {\n    alarmDirty = false;\n    someWriteConfirmed = false;\n  }\n}\n\nvoid ActorSqlite::onCriticalError(\n    kj::StringPtr errorMessage, kj::Maybe<kj::Exception> maybeException) {\n  // If we have already experienced a terminal exception, no need to replace it\n  if (broken == kj::none) {\n    kj::Exception exception = kj::mv(maybeException).orDefault([&]() {\n      return JSG_KJ_EXCEPTION(FAILED, Error, errorMessage);\n    });\n    exception.setDescription(kj::str(\"broken.outputGateBroken; \", exception.getDescription()));\n    broken.emplace(kj::cp(exception));\n\n    // Also ensure output gate is explicitly broken.\n    commitTasks.add(\n        outputGate.lockWhile(kj::Promise<void>(kj::mv(exception)), currentCommitSpan.addRef()));\n  }\n}\n\nvoid ActorSqlite::startImplicitTxn() {\n  auto txn = kj::heap<ImplicitTxn>(*this);\n\n  // We implement the magic of accumulating all of the writes between JavaScript awaits in one\n  // transaction by evaluating by wrapping the commit function with kj::evalLater, which runs the\n  // function on the next turn of the event loop\n  auto commitPromise =\n      kj::evalLater([this, txn = kj::mv(txn)]() mutable -> kj::Promise<void> {\n    // Don't commit if shutdown() has been called.\n    requireNotBroken();\n\n    // Start the schedule request before commit(), for correctness in workerd.\n    auto precommitAlarmState = startPrecommitAlarmScheduling();\n\n    try {\n      txn->commit();\n    } catch (...) {\n      // HACK: If we became broken during `COMMIT TRANSACTION` then throw the broken exception\n      // instead of whatever SQLite threw.\n      requireNotBroken();\n\n      // No, we're not broken, so propagate the exception as-is.\n      throw;\n    }\n\n    // The callback is only expected to commit writes up until this point. Any new writes that\n    // occur while the callback is in progress are NOT included, therefore require a new commit\n    // to be scheduled. So, we should drop `txn` to cause `currentTxn` to become NoTxn now,\n    // rather than after the callback.\n    { auto drop = kj::mv(txn); }\n\n    // Move the commit span out immediately so new writes can capture a fresh span.\n    return commitImpl(kj::mv(precommitAlarmState), kj::mv(currentCommitSpan));\n  })\n          // Unconditionally break the output gate if commit threw an error, no matter whether the\n          // commit was confirmed or unconfirmed.\n          .catch_([this](kj::Exception&& e) {\n    return outputGate.lockWhile(kj::Promise<void>(kj::mv(e)), nullptr);\n  })\n          // We need to wait for this in commitTasks and in lastCommit.\n          .fork();\n\n  commitTasks.add(commitPromise.addBranch());\n\n  // Commits must be executed in order, so we only have to track the most recent commit promise.\n  lastCommit = kj::mv(commitPromise);\n}\n\nvoid ActorSqlite::onWrite(bool allowUnconfirmed) {\n  requireNotBroken();\n  if (currentTxn.is<NoTxn>()) {\n    startImplicitTxn();\n  }\n\n  // Update the status of the current transaction.\n  KJ_SWITCH_ONEOF(currentTxn) {\n    KJ_CASE_ONEOF(_, NoTxn) {\n      KJ_FAIL_REQUIRE(\"we must have a transaction at this point\");\n    }\n    KJ_CASE_ONEOF(implicitTxn, ImplicitTxn*) {\n      if (!implicitTxn->isSomeWriteConfirmed() && !allowUnconfirmed) {\n        // This is adding a must-confirm write to the transaction, so we must ensure the outputGate\n        // locks for remainder of this transaction.\n        implicitTxn->setSomeWriteConfirmed(!allowUnconfirmed);\n        commitTasks.add(outputGate.lockWhile(lastCommit.addBranch(), currentCommitSpan.addRef()));\n      }\n    }\n    KJ_CASE_ONEOF(explicitTxn, ExplicitTxn*) {\n      if (!explicitTxn->isSomeWriteConfirmed() && !allowUnconfirmed) {\n        // This is adding a must-confirm write to the transaction, so we must ensure the outputGate\n        // locks for remainder of this transaction.\n        explicitTxn->setSomeWriteConfirmed(!allowUnconfirmed);\n        // ExplicitTxns don't have a pending commit and don't lock the output gate during the\n        // transaction, so there's nothing to do here.\n      }\n    }\n  }\n}\n\nkj::Promise<void> ActorSqlite::requestScheduledAlarm(\n    kj::Maybe<kj::Date> requestedTime, kj::Promise<void> priorTask) {\n  // Not using coroutines here, because it's important for correctness in workerd that a\n  // synchronously thrown exception in scheduleRun() can escape synchronously to the caller.\n\n  bool movingAlarmLater = willFireEarlier(alarmScheduledNoLaterThan, requestedTime);\n  if (movingAlarmLater) {\n    // Since we are setting the alarm to be later, we can update alarmScheduledNoLaterThan\n    // immediately and still preserve the invariant that the scheduled alarm time is equal to or\n    // earlier than the persisted db alarm value.  Doing the immediate update ensures that\n    // subsequent invocations of commitImpl() will compare against the correct value in their\n    // precommit alarm checks, even if other later-setting requests are still in-flight, without\n    // needing to wait for them to complete.\n    alarmScheduledNoLaterThan = requestedTime;\n  }\n\n  return hooks.scheduleRun(requestedTime, kj::mv(priorTask))\n      .then([this, movingAlarmLater, requestedTime]() {\n    if (!movingAlarmLater) {\n      alarmScheduledNoLaterThan = requestedTime;\n    }\n  });\n}\n\nActorSqlite::PrecommitAlarmState ActorSqlite::startPrecommitAlarmScheduling() {\n  PrecommitAlarmState state;\n  if (pendingCommit == kj::none &&\n      willFireEarlier(metadata.getAlarm(), alarmScheduledNoLaterThan)) {\n    // We must wait on the `alarmLaterChain` here, otherwise, if there is a pending \"move later\"\n    // alarm task and it fails, our \"move earlier\" alarm might interleave, succeed, and be followed\n    // by a retry of the \"move later\" alarm. This happens because \"move later\" alarms complete after\n    // we commit to local SQLite.\n    //\n    // By waiting on any pending \"move later\" alarm, we correctly serialize our `scheduleRun()`\n    // calls to the alarm manager.\n    state.schedulingPromise =\n        requestScheduledAlarm(metadata.getAlarm(), alarmLaterChain.addBranch());\n  }\n  return kj::mv(state);\n}\n\nkj::Promise<void> ActorSqlite::commitImpl(\n    ActorSqlite::PrecommitAlarmState precommitAlarmState, SpanParent parentSpan) {\n  auto commitSpan = parentSpan.newChild(\"actor_sqlite_commit\"_kjc);\n\n  // We assume that exceptions thrown during commit will propagate to the caller, such that they\n  // will ensure cancelDeferredAlarmDeletion() is called, if necessary.\n\n  bool haveAlarmForDebug = false;\n\n  if (debugAlarmSync) {\n    KJ_LOG(WARNING, \"NOSENTRY DEBUG_ALARM: commitImpl entered\", (pendingCommit != kj::none),\n        alarmVersion, logDate(metadata.getAlarm()));\n  }\n\n  KJ_IF_SOME(pending, pendingCommit) {\n    // If an earlier commitImpl() invocation is already in the process of updating precommit\n    // alarms but has not yet made the commitCallback() call, it should be OK to wait on it to\n    // perform the precommit alarm update and db commit for this invocation, too.\n    kj::Maybe<kj::Date> alarmBeforeMerge;\n    if (debugAlarmSync) {\n      alarmBeforeMerge = metadata.getAlarm();\n      KJ_LOG(WARNING, \"NOSENTRY DEBUG_ALARM: Commit merge waiting\", logDate(alarmBeforeMerge),\n          alarmVersion);\n    }\n    commitSpan.setTag(\"merged_with_pending_commit\"_kjc, true);\n    co_await pending.addBranch();\n    if (debugAlarmSync) {\n      auto alarmAfterMerge = metadata.getAlarm();\n      KJ_LOG(WARNING, \"NOSENTRY DEBUG_ALARM: Commit merge resumed\", logDate(alarmBeforeMerge),\n          logDate(alarmAfterMerge), alarmVersion);\n    }\n    co_return;\n  }\n\n  // There are no pending commits in-flight, so we set up a forked promise that other callers can\n  // wait on, to perform the alarm scheduling and database persistence work for all of them.  Note\n  // that the fulfiller is owned by this coroutine context, so if an exception is thrown below,\n  // the fulfiller's destructor will detect that the stack is unwinding and will automatically\n  // propagate the thrown exception to the other waiters.\n  auto [promise, fulfiller] = kj::newPromiseAndFulfiller<void>();\n  pendingCommit = promise.fork();\n\n  // Wait for the first precommit alarm scheduling request to complete, if any.  This was set up\n  // in startPrecommitAlarmScheduling() and is essentially the first iteration of the below\n  // while() loop, but needed to be initiated synchronously before the local database commit to\n  // ensure correctness in workerd.\n  KJ_IF_SOME(p, precommitAlarmState.schedulingPromise) {\n    auto alarmSpan = commitSpan.newChild(\"actor_sqlite_alarm_sync\"_kjc);\n    haveAlarmForDebug = true;\n    co_await p;\n  }\n\n  // While the local db state requires an earlier alarm than is known might be scheduled, issue an\n  // alarm update request for the earlier time and wait for it to complete.  This helps ensure\n  // that the successfully scheduled alarm time is always earlier or equal to the alarm state in\n  // the successfully persisted db.\n  int syncIterations = 0;\n  auto startAlarmState = metadata.getAlarm();\n  while (willFireEarlier(metadata.getAlarm(), alarmScheduledNoLaterThan)) {\n    auto alarmSpan = commitSpan.newChild(\"actor_sqlite_alarm_sync\"_kjc);\n    if (debugAlarmSync) {\n      haveAlarmForDebug = true;\n      auto currentAlarmState = metadata.getAlarm();\n      KJ_LOG(WARNING, \"NOSENTRY DEBUG_ALARM: Move earlier loop iteration\", syncIterations,\n          logDate(currentAlarmState), logDate(alarmScheduledNoLaterThan), alarmVersion);\n    }\n    // Note that we do not pass alarmLaterChain here. We don't need to for the following reasons:\n    //\n    //  1. We already waited for the chain in the precommitAlarmState promise above.\n    //  2. We set the `pendingCommit` prior to yielding to the event loop earlier, so any subsequent\n    //     commits have to wait for us to fulfill the pendingCommit promise. In short, no one could\n    //     have added another \"move-later\" alarm to the chain, not until we finish.\n    //\n    // While we *could* pass the alarmLaterChain promise (it wouldn't be incorrect), when calling\n    // addBranch() on a resolved ForkedPromise, the continuation would be evaluated on a future turn\n    // of the event loop. That means we're going to suspend, even if the promise is ready, which\n    // means we'd take a performance hit.\n    co_await requestScheduledAlarm(metadata.getAlarm(), kj::READY_NOW);\n    syncIterations++;\n  }\n  if (debugAlarmSync && syncIterations > 0) {\n    KJ_LOG(WARNING, \"NOSENTRY DEBUG_ALARM: Move earlier loop complete\", logDate(startAlarmState),\n        \"ended_with\", logDate(metadata.getAlarm()), \"iterations\", syncIterations, alarmVersion);\n  }\n\n  // Issue the commitCallback() request to persist the db state, then synchronously clear the\n  // pending commit so that the next commitImpl() invocation starts its own set of precommit alarm\n  // updates and db commit.\n  auto alarmStateForCommit = metadata.getAlarm();\n\n  // Capture the alarm version before going async to detect concurrent alarm changes. If the\n  // alarmVersion changes while we are in-flight, we should skip attempting any move-later alarm\n  // update.\n  auto alarmVersionBeforeAsync = alarmVersion;\n\n  if (debugAlarmSync) {\n    KJ_LOG(WARNING, \"NOSENTRY DEBUG_ALARM: Captured state before persisting to SQLite async\",\n        logDate(alarmStateForCommit), alarmVersionBeforeAsync);\n  }\n\n  auto commitCallbackPromise = commitCallback(SpanParent(commitSpan));\n  pendingCommit = kj::none;\n\n  // Wait for the db to persist.\n  co_await commitCallbackPromise;\n  lastConfirmedAlarmDbState = alarmStateForCommit;\n\n  if (debugAlarmSync && haveAlarmForDebug) {\n    KJ_LOG(WARNING, \"NOSENTRY DEBUG_ALARM: Persisted in SQLite\", \"sqlite_has\",\n        logDate(alarmStateForCommit), \"alarmScheduledNoLaterThan\",\n        logDate(alarmScheduledNoLaterThan), alarmVersion);\n  }\n\n  // Notify any merged commitImpl() requests that the db persistence completed.\n  fulfiller->fulfill();\n\n  if (debugAlarmSync) {\n    KJ_LOG(WARNING, \"NOSENTRY DEBUG_ALARM: Version check\", alarmVersionBeforeAsync, alarmVersion,\n        \"match\", (alarmVersion == alarmVersionBeforeAsync));\n  }\n  // If another commit modified the alarm while we were async, skip post-commit alarm sync.\n  //\n  // We do this for a few reasons:\n  //  1. The other commit will handle its own alarm sync\n  //  2. Post-commit syncs are inherently optional (the alarm will self-correct)\n  //  3. This coalesces redundant alarm updates for better performance\n  //  4. This avoids race conditions where a later commit moved the alarm earlier, requiring a\n  //     pre-commit alarm update, and this update may have already been made before we get here.\n  if (alarmVersion == alarmVersionBeforeAsync) {\n    // No intervening alarm changes, it is safe to schedule a move-later alarm update if needed.\n    if (willFireEarlier(alarmScheduledNoLaterThan, alarmStateForCommit)) {\n      if (debugAlarmSync) {\n        KJ_LOG(WARNING, \"NOSENTRY DEBUG_ALARM: Moving alarm later\", \"sqlite_has\",\n            logDate(alarmStateForCommit), logDate(alarmScheduledNoLaterThan), alarmVersion);\n      }\n      // We need to extend our alarmLaterChain now that we're adding a new \"move-later\" alarm task.\n      //\n      // Technically, we don't need serialize our \"move-later\" alarms since SQLite has the later\n      // time committed locally. We could just set the `alarmLaterChain` and pass a `kj::READY_NOW`\n      // to requestScheduledAlarm, and so if we have a partial failure we would just recover when\n      // the alarm runs early. That said, it doesn't hurt to serialize on the client-side.\n      alarmLaterChain = requestScheduledAlarm(alarmStateForCommit, alarmLaterChain.addBranch())\n                            .attach(commitSpan.newChild(\"actor_sqlite_alarm_sync\"_kjc))\n                            .catch_([](kj::Exception&& e) {\n        // If an exception occurs when scheduling the alarm later, it's OK -- the alarm will\n        // eventually fire at the earlier time, and the rescheduling will be retried.\n        // We catch here to prevent the chain from breaking on errors.\n        LOG_WARNING_PERIODICALLY(\"NOSENTRY SQLite reschedule later alarm failed\", e);\n      }).fork();\n    }\n  }\n}\n\nvoid ActorSqlite::taskFailed(kj::Exception&& exception) {\n  // The output gate should already have been broken since it wraps all commit tasks that can\n  // throw. So, we don't have to report anything here, the exception will already propagate\n  // elsewhere. We should block further operations, though.\n  if (broken == kj::none) {\n    broken = kj::mv(exception);\n    if (!outputGate.isBroken()) {\n      LOG_PERIODICALLY(\n          ERROR, \"SQLite actor recorded broken exception without breaking output gate\");\n    }\n  }\n}\n\nvoid ActorSqlite::requireNotBroken() {\n  KJ_IF_SOME(e, broken) {\n    kj::throwFatalException(kj::cp(e));\n  }\n}\n\nvoid ActorSqlite::maybeDeleteDeferredAlarm() {\n  if (!inAlarmHandler) {\n    // Pretty sure this can't happen.\n    LOG_WARNING_ONCE(\"expected to be in alarm handler when trying to delete alarm\");\n  }\n  inAlarmHandler = false;\n\n  if (haveDeferredDelete) {\n    // If we have reached this point, the client is destroying its DeferredAlarmDeleter at the end\n    // of an alarm handler run, and deletion hasn't been cancelled, indicating that the handler\n    // returned success.\n    //\n    // If the output gate has somehow broken in the interim, attempting to write the deletion here\n    // will cause the DeferredAlarmDeleter destructor to throw, which the caller probably isn't\n    // expecting.  So we'll skip the deletion attempt, and let the caller detect the gate\n    // brokenness through other means.\n    if (broken == kj::none) {\n      // Use the span captured in armAlarmHandler() for this internal write, since\n      // metadata.setAlarm() doesn't go through the regular write path with a traceSpan parameter.\n      currentCommitSpan = kj::mv(deferredAlarmSpan);\n      // the safe thing to do is to require confirmation.\n      if (metadata.setAlarm(kj::none, /*allowUnconfirmed=*/false)) {\n        ++alarmVersion;\n        if (debugAlarmSync) {\n          KJ_LOG(WARNING, \"NOSENTRY DEBUG_ALARM: maybeDeleteDeferredAlarm cleared alarm\",\n              alarmVersion);\n        }\n      }\n    }\n    haveDeferredDelete = false;\n    deferredAlarmSpan = nullptr;\n  }\n}\n\n// =======================================================================================\n// ActorCacheInterface implementation\n\nkj::OneOf<kj::Maybe<ActorCacheOps::Value>, kj::Promise<kj::Maybe<ActorCacheOps::Value>>>\nActorSqlite::get(Key key, ReadOptions options) {\n  requireNotBroken();\n\n  kj::Maybe<ActorCacheOps::Value> result;\n  kv.get(key, [&](ValuePtr value) { result = kj::heapArray(value); });\n  return result;\n}\n\nkj::OneOf<ActorCacheOps::GetResultList, kj::Promise<ActorCacheOps::GetResultList>> ActorSqlite::get(\n    kj::Array<Key> keys, ReadOptions options) {\n  requireNotBroken();\n\n  kj::Vector<KeyValuePair> results;\n  for (auto& key: keys) {\n    kv.get(\n        key, [&](ValuePtr value) { results.add(KeyValuePair{kj::mv(key), kj::heapArray(value)}); });\n  }\n  std::sort(results.begin(), results.end(), [](auto& a, auto& b) { return a.key < b.key; });\n  return GetResultList(kj::mv(results));\n}\n\nkj::OneOf<kj::Maybe<kj::Date>, kj::Promise<kj::Maybe<kj::Date>>> ActorSqlite::getAlarm(\n    ReadOptions options) {\n  requireNotBroken();\n\n  bool transactionAlarmDirty = false;\n  KJ_IF_SOME(exp, currentTxn.tryGet<ExplicitTxn*>()) {\n    transactionAlarmDirty = exp->getAlarmDirty();\n  }\n\n  if (haveDeferredDelete && !transactionAlarmDirty) {\n    // If an alarm handler is currently running, and a new alarm time has not been set yet, We\n    // need to return that there is no alarm.\n    return kj::Maybe<kj::Date>(kj::none);\n  } else {\n    return metadata.getAlarm();\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::OneOf<ActorCacheOps::GetResultList, kj::Promise<ActorCacheOps::GetResultList>> ActorSqlite::\n    list(Key begin, kj::Maybe<Key> end, kj::Maybe<uint> limit, ReadOptions options) {\n  requireNotBroken();\n\n  kj::Vector<KeyValuePair> results;\n  kv.list(begin, end, limit, SqliteKv::FORWARD, [&](KeyPtr key, ValuePtr value) {\n    results.add(KeyValuePair{kj::str(key), kj::heapArray(value)});\n  });\n\n  // Already guaranteed sorted.\n  return GetResultList(kj::mv(results));\n}\n\nkj::OneOf<ActorCacheOps::GetResultList, kj::Promise<ActorCacheOps::GetResultList>> ActorSqlite::\n    listReverse(Key begin, kj::Maybe<Key> end, kj::Maybe<uint> limit, ReadOptions options) {\n  requireNotBroken();\n\n  kj::Vector<KeyValuePair> results;\n  kv.list(begin, end, limit, SqliteKv::REVERSE, [&](KeyPtr key, ValuePtr value) {\n    results.add(KeyValuePair{kj::str(key), kj::heapArray(value)});\n  });\n\n  // Already guaranteed sorted (reversed).\n  return GetResultList(kj::mv(results));\n}\n\nkj::Maybe<kj::Promise<void>> ActorSqlite::put(\n    Key key, Value value, WriteOptions options, SpanParent traceSpan) {\n  requireNotBroken();\n  // Capture trace span for the output gate lock hold trace.\n  currentCommitSpan = kj::mv(traceSpan);\n  kv.put(key, value, {.allowUnconfirmed = options.allowUnconfirmed});\n  return kj::none;\n}\n\nkj::Maybe<kj::Promise<void>> ActorSqlite::put(\n    kj::Array<KeyValuePair> pairs, WriteOptions options, SpanParent traceSpan) {\n  requireNotBroken();\n  // Capture trace span for the output gate lock hold trace.\n  currentCommitSpan = kj::mv(traceSpan);\n  if (currentTxn.is<NoTxn>()) {\n    // If we are not in a transaction, start an ImplicitTxn since that's what would happen on the\n    // first write anyway.\n    startImplicitTxn();\n  }\n\n  KJ_ASSERT(!currentTxn.is<NoTxn>());\n\n  kv.put(pairs, {.allowUnconfirmed = options.allowUnconfirmed});\n\n  return kj::none;\n}\n\nkj::OneOf<bool, kj::Promise<bool>> ActorSqlite::delete_(\n    Key key, WriteOptions options, SpanParent traceSpan) {\n  requireNotBroken();\n  // Capture trace span for the output gate lock hold trace.\n  currentCommitSpan = kj::mv(traceSpan);\n\n  return kv.delete_(key, {.allowUnconfirmed = options.allowUnconfirmed});\n}\n\nkj::OneOf<uint, kj::Promise<uint>> ActorSqlite::delete_(\n    kj::Array<Key> keys, WriteOptions options, SpanParent traceSpan) {\n  requireNotBroken();\n  // Capture trace span for the output gate lock hold trace.\n  currentCommitSpan = kj::mv(traceSpan);\n\n  uint count = 0;\n  for (auto& key: keys) {\n    count += kv.delete_(key, {.allowUnconfirmed = options.allowUnconfirmed});\n  }\n  return count;\n}\n\nkj::Maybe<kj::Promise<void>> ActorSqlite::setAlarm(\n    kj::Maybe<kj::Date> newAlarmTime, WriteOptions options, SpanParent traceSpan) {\n  requireNotBroken();\n  // Capture trace span for the output gate lock hold trace.\n  currentCommitSpan = kj::mv(traceSpan);\n\n  // TODO(someday): When deleting alarm data in an otherwise empty database, clear the database to\n  // free up resources?\n\n  // Only increment version counter if the alarm value actually changed. This is important because\n  // if the value didn't change, no SQLite write occurs, so no implicit transaction is started,\n  // and we don't want to invalidate in-flight commits without a replacement commit.\n  if (metadata.setAlarm(newAlarmTime, options.allowUnconfirmed)) {\n    ++alarmVersion;\n    if (debugAlarmSync) {\n      KJ_LOG(WARNING, \"NOSENTRY DEBUG_ALARM: setAlarm called\", logDate(newAlarmTime), alarmVersion);\n    }\n  }\n\n  KJ_IF_SOME(exp, currentTxn.tryGet<ExplicitTxn*>()) {\n    exp->setAlarmDirty();\n  } else {\n    haveDeferredDelete = false;\n  }\n\n  return kj::none;\n}\n\nkj::Own<ActorCacheInterface::Transaction> ActorSqlite::startTransaction() {\n  requireNotBroken();\n\n  return kj::refcounted<ExplicitTxn>(*this);\n}\n\nActorCacheInterface::DeleteAllResults ActorSqlite::deleteAll(\n    WriteOptions options, SpanParent traceSpan, DeleteAllOptions deleteAllOptions) {\n  requireNotBroken();\n  disableAllowUnconfirmed(options, \"deleteAll is not supported\");\n\n  // Capture trace span for the output gate lock (deleteAll always requires confirmation).\n  currentCommitSpan = kj::mv(traceSpan);\n\n  // kv.deleteAll() clears the database, so we need to save and possibly restore alarm state in\n  // the metadata table to maintain behavior from before the deleteAllDeletesAlarm compat flag.\n  auto localAlarmState = metadata.getAlarm();\n\n  // deleteAll() cannot be part of a transaction because it deletes the database altogether. So,\n  // we have to close our transactions or fail.\n  KJ_SWITCH_ONEOF(currentTxn) {\n    KJ_CASE_ONEOF(_, NoTxn) {\n      // good\n    }\n    KJ_CASE_ONEOF(implicit, ImplicitTxn*) {\n      // Whatever the implicit transaction did, it's about to be blown away anyway. Roll it back\n      // so we don't waste time flushing these writes anywhere.\n      implicit->rollback();\n      currentTxn = NoTxn();\n    }\n    KJ_CASE_ONEOF(exp, ExplicitTxn*) {\n      // Keep in mind:\n      //\n      //   ctx.storage.transaction(txn => {\n      //     txn.deleteAll();          // calls `DurableObjectTransaction::deleteAll()`\n      //     ctx.storage.deleteAll();  // calls this method, `ActorSqlite::deleteAll()`\n      //   });\n      //\n      // `DurableObjectTransaction::deleteAll()` throws this exception, since `deleteAll()` is not\n      // supported inside a transaction. Under the new SQLite-backed storage system, directly\n      // calling `cxt.storage` inside a transaction (as opposed to using the `txn` object) should\n      // still be treated as part of the transaction, and so should throw the same thing.\n      JSG_FAIL_REQUIRE(Error, \"Cannot call deleteAll() within a transaction\");\n    }\n  }\n\n  if (!deleteAllCommitScheduled) {\n    // Make sure a commit callback is queued for the deleteAll().\n    commitTasks.add(outputGate.lockWhile(kj::evalLater([this]() mutable -> kj::Promise<void> {\n      // Don't commit if shutdown() has been called.\n      requireNotBroken();\n\n      deleteAllCommitScheduled = false;\n      if (currentTxn.is<ImplicitTxn*>()) {\n        // An implicit transaction is already scheduled, so we'll count on it to perform a commit when it's\n        // done. This is particularly important for the case where deleteAll() was called while an alarm\n        // is outstanding; resetting the alarm state (below) starts an implicit transaction.\n        // We don't want to commit the deletion without that transaction.\n        return kj::READY_NOW;\n      } else {\n        // Use commitImpl() rather than commitCallback() so that alarm scheduling is handled.\n        // This is important when deleteAll() deletes an alarm: commitImpl() detects that\n        // metadata.getAlarm() moved to kj::none and notifies the scheduler via\n        // requestScheduledAlarm(kj::none, ...).\n        auto precommitAlarmState = startPrecommitAlarmScheduling();\n        return commitImpl(kj::mv(precommitAlarmState), currentCommitSpan.addRef());\n      }\n    }),\n        currentCommitSpan.addRef()));\n    deleteAllCommitScheduled = true;\n  }\n\n  uint count = kv.deleteAll();\n\n  // Reset alarm state, if necessary.  If no alarm is set, OK to just leave metadata table\n  // uninitialized.\n  if (localAlarmState != kj::none) {\n    if (deleteAllOptions.deleteAlarm) {\n      // The caller wants the alarm deleted along with KV data. Since kv.deleteAll() already\n      // wiped the database (including the alarm metadata), metadata.getAlarm() will naturally\n      // return kj::none without creating any tables or rows. Increment alarmVersion so in‑flight\n      // commits don’t perform stale post‑commit alarm scheduling, and the deleteAll commit can sync\n      // cancellation.\n      ++alarmVersion;\n      haveDeferredDelete = false;\n    } else {\n      // TODO(correctness): Since workerd doesn't have a separate durability step, in the unlikely\n      // event of a failure here, between deleteAll() and setAlarm(), we could theoretically lose the\n      // current alarm state when running under workerd.  Not sure if there's a practical way to avoid\n      // this.\n      if (metadata.setAlarm(localAlarmState, options.allowUnconfirmed)) {\n        ++alarmVersion;\n        if (debugAlarmSync) {\n          KJ_LOG(WARNING, \"NOSENTRY DEBUG_ALARM: deleteAll restored alarm\",\n              logDate(localAlarmState), alarmVersion);\n        }\n      }\n    }\n  }\n\n  return {\n    .backpressure = kj::none,\n    .count = count,\n  };\n}\n\nkj::Maybe<kj::Promise<void>> ActorSqlite::evictStale(kj::Date now) {\n  // This implementation never needs to apply backpressure.\n  return kj::none;\n}\n\nvoid ActorSqlite::shutdown(kj::Maybe<const kj::Exception&> maybeException) {\n  // TODO(cleanup): Logic copied from ActorCache::shutdown(). Should they share somehow?\n\n  if (broken == kj::none) {\n    auto exception = [&]() {\n      KJ_IF_SOME(e, maybeException) {\n        // We were given an exception, use it.\n        return kj::cp(e);\n      }\n\n      // Use the direct constructor so that we can reuse the constexpr message variable for testing.\n      auto exception = kj::Exception(kj::Exception::Type::DISCONNECTED, __FILE__, __LINE__,\n          kj::heapString(ActorCache::SHUTDOWN_ERROR_MESSAGE));\n\n      // Add trace info sufficient to tell us which operation caused the failure.\n      exception.addTraceHere();\n      exception.addTrace(__builtin_return_address(0));\n      return exception;\n    }();\n\n    // Any scheduled flushes will fail once `flushImpl()` is invoked and notices that\n    // `maybeTerminalException` has a value. Any in-flight flushes will continue to run in the\n    // background. Remember that these in-flight flushes may or may not be awaited by the worker,\n    // but they still hold the output lock as long as `allowUnconfirmed` wasn't used.\n    broken.emplace(kj::mv(exception));\n\n    // We explicitly do not schedule a flush to break the output gate. This means that if a request\n    // is ongoing after the actor cache is shutting down, the output gate is only broken if they\n    // had to send a flush after shutdown, either from a scheduled flush or a retry after failure.\n  } else {\n    // We've already experienced a terminal exception either from shutdown or OOM, there should\n    // already be a flush scheduled that will break the output gate.\n  }\n}\n\nkj::OneOf<ActorSqlite::CancelAlarmHandler, ActorSqlite::RunAlarmHandler> ActorSqlite::\n    armAlarmHandler(kj::Date scheduledTime,\n        SpanParent parentSpan,\n        kj::Date currentTime,\n        bool noCache,\n        kj::StringPtr actorId) {\n  KJ_ASSERT(!inAlarmHandler);\n\n  if (haveDeferredDelete) {\n    // Unlikely to happen, unless caller is starting new alarm handler before previous alarm\n    // handler cleanup has completed.\n    LOG_WARNING_ONCE(\"expected previous alarm handler to be cleaned up\");\n  }\n\n  auto localAlarmState = metadata.getAlarm();\n  if (localAlarmState != scheduledTime) {\n    if (localAlarmState == lastConfirmedAlarmDbState) {\n      // If the local alarm time is already in the past, just run the handler now. This avoids\n      // blocking alarm execution on the AlarmManager sync when storage is overloaded. The alarm\n      // will either delete itself on success or reschedule on failure.\n      if ((willFireEarlier(localAlarmState, currentTime))) {\n        auto localAlarmTime = KJ_ASSERT_NONNULL(localAlarmState);\n        LOG_WARNING_PERIODICALLY(\n            \"NOSENTRY SQLite alarm overdue, running despite AlarmManager mismatch\", scheduledTime,\n            localAlarmTime, currentTime, actorId);\n        haveDeferredDelete = true;\n        inAlarmHandler = true;\n        deferredAlarmSpan = kj::mv(parentSpan);\n        static const DeferredAlarmDeleter disposer;\n        return RunAlarmHandler{.deferredDelete = kj::Own<void>(this, disposer)};\n      }\n\n      // If there's a clean db time that differs from the requested handler's scheduled time, this\n      // run should be canceled.\n      if (willFireEarlier(scheduledTime, localAlarmState)) {\n        // If the handler's scheduled time is earlier than the clean scheduled time, we may be\n        // recovering from a failed db commit or scheduling request, so we need to request that\n        // the alarm be rescheduled for the current db time, and tell the caller to wait for\n        // successful rescheduling before cancelling the current handler invocation.\n        //\n        // TODO(perf): If we already have such a rescheduling request in-flight, might want to\n        // coalesce with the existing request?\n        LOG_WARNING_PERIODICALLY(\n            \"NOSENTRY SQLite alarm handler canceled with requestScheduledAlarm.\", scheduledTime,\n            localAlarmState.orDefault(kj::UNIX_EPOCH), actorId);\n\n        // Since we're requesting to move the alarm time to later, we need to add to our\n        // `alarmLaterChain`. Note that for the chain, we want to make sure any scheduling failure\n        // does not break us, but for the `CancelAlarmHandler`, we want the caller to receive the\n        // exception normally, so we do not consume the exception.\n        auto schedulingPromise =\n            requestScheduledAlarm(localAlarmState, alarmLaterChain.addBranch()).fork();\n        alarmLaterChain = schedulingPromise.addBranch()\n                              .catch_([](kj::Exception&& e) {\n          // If an exception occurs when scheduling the alarm later, it's OK -- the alarm will\n          // eventually fire at the earlier time, and the rescheduling will be retried.\n          // We catch here to prevent the chain from breaking on errors.\n          LOG_WARNING_PERIODICALLY(\"NOSENTRY SQLite reschedule later alarm failed\", e);\n        }).fork();\n        return CancelAlarmHandler{.waitBeforeCancel = schedulingPromise.addBranch()};\n      } else {\n        // We have a clean local alarm time that is earlier than the handler's scheduled time,\n        // which suggests that either the alarm manager is working with stale data or that local\n        // alarm time has somehow gotten out of sync with the scheduled alarm time.\n\n        // We know localAlarmState has a value here because we're in the branch where it's earlier\n        // than scheduledTime (not equal, and not later).\n        auto localTime = KJ_ASSERT_NONNULL(localAlarmState);\n\n        // Only log if the alarm manager is significantly late (>10 seconds behind SQLite)\n        if (scheduledTime - localTime > 10 * kj::SECONDS) {\n          LOG_WARNING_PERIODICALLY(\n              \"NOSENTRY SQLite alarm handler canceled.\", scheduledTime, actorId, localTime);\n        }\n\n        // Tell the caller to wait for successful rescheduling before cancelling the current\n        // handler invocation.\n        //\n        // We pass kj::READY_NOW because being in this branch (SQLite is ahead of the alarm manager)\n        // means there's no recent move-later operation to wait for, so no need for alarmLaterChain.\n        return CancelAlarmHandler{\n          .waitBeforeCancel = requestScheduledAlarm(localAlarmState, kj::READY_NOW)};\n      }\n    } else {\n      // There's a alarm write that hasn't been set yet pending for a time different than ours --\n      // We won't cancel the alarm because it hasn't been confirmed, but we shouldn't delete\n      // the pending write.\n      haveDeferredDelete = false;\n    }\n  } else {\n    haveDeferredDelete = true;\n    deferredAlarmSpan = kj::mv(parentSpan);\n  }\n  inAlarmHandler = true;\n\n  static const DeferredAlarmDeleter disposer;\n  return RunAlarmHandler{.deferredDelete = kj::Own<void>(this, disposer)};\n}\n\nvoid ActorSqlite::cancelDeferredAlarmDeletion() {\n  if (!inAlarmHandler) {\n    // Pretty sure this can't happen.\n    LOG_WARNING_ONCE(\"expected to be in alarm handler when trying to cancel deleted alarm\");\n  }\n  haveDeferredDelete = false;\n}\n\nkj::Maybe<kj::Promise<void>> ActorSqlite::onNoPendingFlush(SpanParent parentSpan) {\n  // This implements sync().\n  //\n  // sync() should wait for ALL writes (both confirmed and unconfirmed) that are outstanding at the\n  // time sync() is called. We use lastCommit which keeps track of the most recent commit to be\n  // formed. We join with the outputGate because there are a lot of edge cases where we break the\n  // output gate and it's easiest to catch all of those instances here rather than updating\n  // everything to also break lastCommit.\n  return kj::joinPromisesFailFast(\n      kj::arr(lastCommit.addBranch(), outputGate.wait(kj::mv(parentSpan))));\n}\n\nkj::Promise<kj::String> ActorSqlite::getCurrentBookmark(SpanParent parentSpan) {\n  // This is an ersatz implementation that's good enough for local dev with D1's Session API.\n  //\n  // The returned bookmark satisfies the properties that D1 cares about:\n  //\n  // * Later bookmarks sort after earlier bookmarks.  We implement this by incrementing the bookmark\n  // * whenever getCurrentBookmark() is called.\n  //\n  // * Bookmarks from the current workerd session sort after bookmarks from previous sessions.  We\n  //   implement this by saving an ersatz bookmark in the SqliteMetadata table.\n\n  requireNotBroken();\n  uint64_t bookmark = 0;\n  KJ_IF_SOME(b, metadata.getLocalDevelopmentBookmark()) {\n    bookmark = b + 1;\n  }\n  metadata.setLocalDevelopmentBookmark(bookmark);\n\n  // TODO(cleanup): Left-padded number stringification should maybe be in KJ?\n  auto paddedHex = [](uint32_t n) {\n    kj::FixedArray<char, 8> result;\n    for (auto i = 0; i < result.size(); i++) {\n      char digit = n % 16;\n      n /= 16;\n      digit += digit < 10 ? '0' : ('a' - 10);\n      result[result.size() - 1 - i] = digit;\n    }\n    return result;\n  };\n\n  // Turn the bookmark into a format matching what Cloudflare's production returns.\n  constexpr uint32_t uint32_max = kj::maxValue;\n  kj::FixedArray<char, 32> pad;\n  pad.fill('0');\n  return kj::str(paddedHex(bookmark / uint32_max), '-', paddedHex(bookmark % uint32_max), '-',\n      paddedHex(0), '-', pad);\n}\n\nkj::Promise<void> ActorSqlite::waitForBookmark(kj::StringPtr bookmark, SpanParent parentSpan) {\n  // This is an ersatz implementation that's good enough for local dev with D1's Session API.\n  requireNotBroken();\n  return kj::READY_NOW;\n}\n\nvoid ActorSqlite::TxnCommitRegulator::onError(\n    kj::Maybe<int> sqliteErrorCode, kj::StringPtr message) const {\n  KJ_IF_SOME(c, sqliteErrorCode) {\n    // We cannot `#include <sqlite3.h>` in the same compilation unit as `#include\n    // <workerd/io/trace.h>` because the latter includes v8 and v8 seems to conflict with sqlite.\n    // So we copy the value of SQLITE_CONSTRAINT from sqlite3.h\n    constexpr int SQLITE_CONSTRAINT = 19;\n    if (c == SQLITE_CONSTRAINT) {\n      JSG_ASSERT(false, Error,\n          \"Durable Object was reset and rolled back to its last known good state because the \"\n          \"application left the database in a state where constraints were violated: \",\n          message);\n    }\n  }\n\n  // For any other type of error, fall back to the default behavior (throwing a non-JSG exception)\n  // as we don't know for sure that the problem is the application's fault.\n}\n\nconst ActorSqlite::Hooks ActorSqlite::Hooks::DEFAULT = ActorSqlite::Hooks{};\n\nkj::Promise<void> ActorSqlite::Hooks::scheduleRun(\n    kj::Maybe<kj::Date> newAlarmTime, kj::Promise<void> priorTask) {\n  JSG_FAIL_REQUIRE(Error, \"alarms are not yet implemented for SQLite-backed Durable Objects\");\n}\n\nkj::OneOf<kj::Maybe<ActorCacheOps::Value>, kj::Promise<kj::Maybe<ActorCacheOps::Value>>>\nActorSqlite::ExplicitTxn::get(Key key, ReadOptions options) {\n  return actorSqlite.get(kj::mv(key), options);\n}\nkj::OneOf<ActorCacheOps::GetResultList, kj::Promise<ActorCacheOps::GetResultList>> ActorSqlite::\n    ExplicitTxn::get(kj::Array<Key> keys, ReadOptions options) {\n  return actorSqlite.get(kj::mv(keys), options);\n}\nkj::OneOf<kj::Maybe<kj::Date>, kj::Promise<kj::Maybe<kj::Date>>> ActorSqlite::ExplicitTxn::getAlarm(\n    ReadOptions options) {\n  return actorSqlite.getAlarm(options);\n}\nkj::OneOf<ActorCacheOps::GetResultList, kj::Promise<ActorCacheOps::GetResultList>> ActorSqlite::\n    ExplicitTxn::list(Key begin, kj::Maybe<Key> end, kj::Maybe<uint> limit, ReadOptions options) {\n  return actorSqlite.list(kj::mv(begin), kj::mv(end), limit, options);\n}\nkj::OneOf<ActorCacheOps::GetResultList, kj::Promise<ActorCacheOps::GetResultList>> ActorSqlite::\n    ExplicitTxn::listReverse(\n        Key begin, kj::Maybe<Key> end, kj::Maybe<uint> limit, ReadOptions options) {\n  return actorSqlite.listReverse(kj::mv(begin), kj::mv(end), limit, options);\n}\nkj::Maybe<kj::Promise<void>> ActorSqlite::ExplicitTxn::put(\n    Key key, Value value, WriteOptions options, SpanParent traceSpan) {\n  return actorSqlite.put(kj::mv(key), kj::mv(value), options, kj::mv(traceSpan));\n}\nkj::Maybe<kj::Promise<void>> ActorSqlite::ExplicitTxn::put(\n    kj::Array<KeyValuePair> pairs, WriteOptions options, SpanParent traceSpan) {\n  return actorSqlite.put(kj::mv(pairs), options, kj::mv(traceSpan));\n}\nkj::OneOf<bool, kj::Promise<bool>> ActorSqlite::ExplicitTxn::delete_(\n    Key key, WriteOptions options, SpanParent traceSpan) {\n  return actorSqlite.delete_(kj::mv(key), options, kj::mv(traceSpan));\n}\nkj::OneOf<uint, kj::Promise<uint>> ActorSqlite::ExplicitTxn::delete_(\n    kj::Array<Key> keys, WriteOptions options, SpanParent traceSpan) {\n  return actorSqlite.delete_(kj::mv(keys), options, kj::mv(traceSpan));\n}\nkj::Maybe<kj::Promise<void>> ActorSqlite::ExplicitTxn::setAlarm(\n    kj::Maybe<kj::Date> newAlarmTime, WriteOptions options, SpanParent traceSpan) {\n  return actorSqlite.setAlarm(newAlarmTime, options, kj::mv(traceSpan));\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/actor-sqlite.h",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"actor-cache.h\"\n\n#include <workerd/io/trace.h>\n#include <workerd/util/sqlite-kv.h>\n#include <workerd/util/sqlite-metadata.h>\n\nnamespace workerd {\n\n// An implementation of ActorCacheOps that is backed by SqliteKv.\nclass ActorSqlite final: public ActorCacheInterface, private kj::TaskSet::ErrorHandler {\n  // TODO(perf): This interface is not designed ideally for wrapping SqliteKv. In particular, we\n  //   end up allocating extra copies of all the results. It would be nicer if we could actually\n  //   parse the V8-serialized values directly from the blob pointers that SQLite spits out.\n  //   However, that probably requires rewriting `DurableObjectStorageOperations`. For now, hooking\n  //   here is easier and not too costly.\n\n public:\n  // Hooks to configure ActorSqlite behavior, right now only used to allow plugging in a backend\n  // for alarm operations.\n  class Hooks {\n   public:\n    // Makes a request to the alarm manager to run the alarm handler at the given time, returning\n    // a promise that resolves when the scheduling has succeeded. `priorTask` is any work we must\n    // wait on prior to scheduling the new request, as of this writing, this would be the\n    // alarmLaterChain, which holds promises to move the alarm time \"later\" than is currently set.\n    virtual kj::Promise<void> scheduleRun(\n        kj::Maybe<kj::Date> newAlarmTime, kj::Promise<void> priorTask);\n\n    static const Hooks DEFAULT;\n\n    static constexpr inline Hooks& getDefaultHooks() {\n      // Hooks has no member variables, so const_cast is acceptable.\n      return const_cast<Hooks&>(Hooks::DEFAULT);\n    }\n  };\n\n  // Constructs ActorSqlite, arranging to honor the output gate, that is, any writes to the\n  // database which occur without any `await`s in between will automatically be combined into a\n  // single atomic write. This is accomplished using transactions. In addition to ensuring\n  // atomicity, this tends to improve performance, as SQLite is able to coalesce writes across\n  // statements that modify the same page.\n  //\n  // `commitCallback` will be invoked after committing a transaction. The output gate will block on\n  // the returned promise. This can be used e.g. when the database needs to be replicated to other\n  // machines before being considered durable.\n  explicit ActorSqlite(kj::Own<SqliteDatabase> dbParam,\n      OutputGate& outputGate,\n      kj::Function<kj::Promise<void>(SpanParent)> commitCallback,\n      Hooks& hooks = Hooks::getDefaultHooks(),\n      bool debugAlarmSync = false);\n\n  bool isCommitScheduled() {\n    return !currentTxn.is<NoTxn>() || deleteAllCommitScheduled;\n  }\n\n  kj::Maybe<SqliteDatabase&> getSqliteDatabase() override {\n    return *db;\n  }\n\n  kj::Maybe<SqliteKv&> getSqliteKv() override {\n    requireNotBroken();\n    return kv;\n  }\n\n  kj::OneOf<kj::Maybe<Value>, kj::Promise<kj::Maybe<Value>>> get(\n      Key key, ReadOptions options) override;\n  kj::OneOf<GetResultList, kj::Promise<GetResultList>> get(\n      kj::Array<Key> keys, ReadOptions options) override;\n  kj::OneOf<kj::Maybe<kj::Date>, kj::Promise<kj::Maybe<kj::Date>>> getAlarm(\n      ReadOptions options) override;\n  kj::OneOf<GetResultList, kj::Promise<GetResultList>> list(\n      Key begin, kj::Maybe<Key> end, kj::Maybe<uint> limit, ReadOptions options) override;\n  kj::OneOf<GetResultList, kj::Promise<GetResultList>> listReverse(\n      Key begin, kj::Maybe<Key> end, kj::Maybe<uint> limit, ReadOptions options) override;\n  kj::Maybe<kj::Promise<void>> put(\n      Key key, Value value, WriteOptions options, SpanParent traceSpan) override;\n  kj::Maybe<kj::Promise<void>> put(\n      kj::Array<KeyValuePair> pairs, WriteOptions options, SpanParent traceSpan) override;\n  kj::OneOf<bool, kj::Promise<bool>> delete_(\n      Key key, WriteOptions options, SpanParent traceSpan) override;\n  kj::OneOf<uint, kj::Promise<uint>> delete_(\n      kj::Array<Key> keys, WriteOptions options, SpanParent traceSpan) override;\n  kj::Maybe<kj::Promise<void>> setAlarm(\n      kj::Maybe<kj::Date> newAlarmTime, WriteOptions options, SpanParent traceSpan) override;\n  // See ActorCacheOps.\n\n  kj::Own<ActorCacheInterface::Transaction> startTransaction() override;\n  DeleteAllResults deleteAll(\n      WriteOptions options, SpanParent traceSpan, DeleteAllOptions deleteAllOptions = {}) override;\n  kj::Maybe<kj::Promise<void>> evictStale(kj::Date now) override;\n  void shutdown(kj::Maybe<const kj::Exception&> maybeException) override;\n  kj::OneOf<CancelAlarmHandler, RunAlarmHandler> armAlarmHandler(kj::Date scheduledTime,\n      SpanParent parentSpan,\n      kj::Date currentTime,\n      bool noCache = false,\n      kj::StringPtr actorId = \"\") override;\n  void cancelDeferredAlarmDeletion() override;\n  kj::Maybe<kj::Promise<void>> onNoPendingFlush(SpanParent parentSpan) override;\n  kj::Promise<kj::String> getCurrentBookmark(SpanParent parentSpan) override;\n  kj::Promise<void> waitForBookmark(kj::StringPtr bookmark, SpanParent parentSpan) override;\n  // See ActorCacheInterface\n\n private:\n  kj::Own<SqliteDatabase> db;\n  OutputGate& outputGate;\n  kj::Function<kj::Promise<void>(SpanParent)> commitCallback;\n  Hooks& hooks;\n  SqliteKv kv;\n  SqliteMetadata metadata;\n\n  // Define a SqliteDatabase::Regulator that is similar to TRUSTED but turns certain SQLite errors\n  // into application errors as appropriate when committing an implicit transaction.\n  class TxnCommitRegulator: public SqliteDatabase::Regulator {\n   public:\n    void onError(kj::Maybe<int> sqliteErrorCode, kj::StringPtr message) const override;\n  };\n  static constexpr TxnCommitRegulator TRUSTED_TXN_COMMIT;\n\n  SqliteDatabase::Statement beginTxn = db->prepare(\"BEGIN TRANSACTION\");\n  SqliteDatabase::Statement commitTxn = db->prepare(TRUSTED_TXN_COMMIT, \"COMMIT TRANSACTION\");\n\n  kj::Maybe<kj::Exception> broken;\n\n  struct NoTxn {};\n\n  class ImplicitTxn {\n   public:\n    explicit ImplicitTxn(ActorSqlite& parent);\n    ~ImplicitTxn() noexcept(false);\n    KJ_DISALLOW_COPY_AND_MOVE(ImplicitTxn);\n\n    void commit();\n    void rollback();\n\n    void setSomeWriteConfirmed(bool someWriteConfirmed);\n    bool isSomeWriteConfirmed() const;\n\n   private:\n    ActorSqlite& parent;\n\n    bool committed = false;\n\n    // True if any of the writes in this commit are confirmed writes.\n    bool someWriteConfirmed = false;\n  };\n\n  class ExplicitTxn: public ActorCacheInterface::Transaction, public kj::Refcounted {\n   public:\n    ExplicitTxn(ActorSqlite& actorSqlite);\n    ~ExplicitTxn() noexcept(false);\n    KJ_DISALLOW_COPY_AND_MOVE(ExplicitTxn);\n\n    bool getAlarmDirty();\n    void setAlarmDirty();\n\n    void setSomeWriteConfirmed(bool someWriteConfirmed);\n    bool isSomeWriteConfirmed() const;\n\n    kj::Maybe<kj::Promise<void>> commit() override;\n    kj::Promise<void> rollback() override;\n    // Implements ActorCacheInterface::Transaction.\n\n    kj::OneOf<kj::Maybe<Value>, kj::Promise<kj::Maybe<Value>>> get(\n        Key key, ReadOptions options) override;\n    kj::OneOf<GetResultList, kj::Promise<GetResultList>> get(\n        kj::Array<Key> keys, ReadOptions options) override;\n    kj::OneOf<kj::Maybe<kj::Date>, kj::Promise<kj::Maybe<kj::Date>>> getAlarm(\n        ReadOptions options) override;\n    kj::OneOf<GetResultList, kj::Promise<GetResultList>> list(\n        Key begin, kj::Maybe<Key> end, kj::Maybe<uint> limit, ReadOptions options) override;\n    kj::OneOf<GetResultList, kj::Promise<GetResultList>> listReverse(\n        Key begin, kj::Maybe<Key> end, kj::Maybe<uint> limit, ReadOptions options) override;\n    kj::Maybe<kj::Promise<void>> put(\n        Key key, Value value, WriteOptions options, SpanParent traceSpan) override;\n    kj::Maybe<kj::Promise<void>> put(\n        kj::Array<KeyValuePair> pairs, WriteOptions options, SpanParent traceSpan) override;\n    kj::OneOf<bool, kj::Promise<bool>> delete_(\n        Key key, WriteOptions options, SpanParent traceSpan) override;\n    kj::OneOf<uint, kj::Promise<uint>> delete_(\n        kj::Array<Key> keys, WriteOptions options, SpanParent traceSpan) override;\n    kj::Maybe<kj::Promise<void>> setAlarm(\n        kj::Maybe<kj::Date> newAlarmTime, WriteOptions options, SpanParent traceSpan) override;\n    // Implements ActorCacheOps. These will all forward to the ActorSqlite instance.\n\n   private:\n    ActorSqlite& actorSqlite;\n    kj::Maybe<kj::Own<ExplicitTxn>> parent;\n    uint depth = 0;\n    bool hasChild = false;\n    bool committed = false;\n    bool alarmDirty = false;\n    // True if any of the writes in this commit are confirmed writes.\n    bool someWriteConfirmed = false;\n\n    void rollbackImpl();\n  };\n\n  // When set to NoTxn, there is no transaction outstanding.\n  //\n  // When set to `ImplicitTxn*`, an implicit transaction is currently open, owned by `commitTasks`.\n  // If there is a need to commit this early, e.g. to start an explicit transaction, that can be\n  // done through this reference.\n  //\n  // When set to `ExplicitTxn*`, an explicit transaction is currently open, so no implicit\n  // transactions should be used in the meantime.\n  kj::OneOf<NoTxn, ImplicitTxn*, ExplicitTxn*> currentTxn = NoTxn();\n\n  // If true, then a commit is scheduled as a result of deleteAll() having been called.\n  bool deleteAllCommitScheduled = false;\n\n  // State for tracking completion of all commits (both confirmed and unconfirmed) for implementing\n  // sync() in onNoPendingFlush.\n  kj::ForkedPromise<void> lastCommit = kj::Promise<void>(kj::READY_NOW).fork();\n\n  // Backs the `kj::Own<void>` returned by `armAlarmHandler()`.\n  class DeferredAlarmDeleter: public kj::Disposer {\n   public:\n    // The `Own<void>` returned by `armAlarmHandler()` is actually set up to point to the\n    // `ActorSqlite` itself, but with an alternate disposer that deletes the alarm rather than\n    // the whole object.\n    void disposeImpl(void* pointer) const override {\n      reinterpret_cast<ActorSqlite*>(pointer)->maybeDeleteDeferredAlarm();\n    }\n  };\n\n  // We need to track some additional alarm state to guarantee at-least-once alarm delivery:\n  // Within an alarm handler, we want the observable alarm state to look like the running alarm\n  // was deleted at the start of the handler (when armAlarmHandler() is called), but we don't\n  // actually want to persist that deletion until after the handler has successfully completed.\n  bool haveDeferredDelete = false;\n\n  // Trace span for the deferred alarm deletion, captured from armAlarmHandler and used when\n  // the alarm is actually deleted. This is separate from currentCommitSpan because the alarm\n  // deletion is an internal write (via metadata.setAlarm) that doesn't go through the regular\n  // write methods with a traceSpan parameter. If the alarm handler does no other writes,\n  // currentCommitSpan would be null, so we need this saved span for the output gate lock trace.\n  SpanParent deferredAlarmSpan = nullptr;\n\n  // Some state only used for tracking calling invariants.\n  bool inAlarmHandler = false;\n\n  // The alarm state for which we last received confirmation that the db was durably stored.\n  kj::Maybe<kj::Date> lastConfirmedAlarmDbState;\n\n  // The latest time we'd expect a scheduled alarm to fire, given the current set of in-flight\n  // scheduling requests, without yet knowing if any of them succeeded or failed.  We use this\n  // value to maintain the invariant that the scheduled alarm is always equal to or earlier than\n  // the alarm value in the persisted database state.\n  kj::Maybe<kj::Date> alarmScheduledNoLaterThan;\n\n  // A promise for an in-progress alarm notification update and database commit.\n  kj::Maybe<kj::ForkedPromise<void>> pendingCommit;\n\n  kj::TaskSet commitTasks;\n\n  // Trace span for the current commit operation. Captured from each write and used\n  // for the output gate lock hold trace when a non-allowUnconfirmed write occurs.\n  SpanParent currentCommitSpan = nullptr;\n\n  // Promise chain for serializing \"move alarm later\" operations to prevent races\n  // at the alarm manager. Each update waits for the previous one to complete.\n  kj::ForkedPromise<void> alarmLaterChain = kj::Promise<void>(kj::READY_NOW).fork();\n\n  // Version counter that increments on every alarm change. Used to detect if another commit\n  // modified the alarm while we were async, allowing us to skip redundant post-commit alarm\n  // syncs. This provides automatic coalescing of rapid alarm changes.\n  uint64_t alarmVersion = 0;\n\n  // Debug flag for tracing alarm synchronization issues for specific namespaces\n  bool debugAlarmSync = false;\n\n  void startImplicitTxn();\n\n  void onWrite(bool allowUnconfirmed);\n\n  void onCriticalError(kj::StringPtr errorMessage, kj::Maybe<kj::Exception> maybeException);\n\n  // Issues a request to the alarm scheduler for the given time, returning a promise that resolves\n  // when the request is confirmed.\n  kj::Promise<void> requestScheduledAlarm(\n      kj::Maybe<kj::Date> requestedTime, kj::Promise<void> priorTask);\n\n  struct PrecommitAlarmState {\n    // Promise for the completion of precommit alarm scheduling\n    kj::Maybe<kj::Promise<void>> schedulingPromise;\n  };\n\n  // To be called just before committing the local sqlite db, to synchronously start any necessary\n  // alarm scheduling:\n  PrecommitAlarmState startPrecommitAlarmScheduling();\n\n  // Performs the rest of the asynchronous commit, to be waited on after committing the local\n  // sqlite db.  Should be called in the same turn of the event loop as\n  // startPrecommitAlarmScheduling() and passed the state that it returned.\n  kj::Promise<void> commitImpl(PrecommitAlarmState precommitAlarmState, SpanParent parentSpan);\n\n  void taskFailed(kj::Exception&& exception) override;\n\n  void requireNotBroken();\n\n  // Called when DeferredAlarmDeleter is destroyed, to delete alarm if not reset or cancelled\n  // during handler.\n  void maybeDeleteDeferredAlarm();\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/actor-storage.c++",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"actor-storage.h\"\n\n#include <workerd/io/actor-storage.capnp.h>\n#include <workerd/jsg/exception.h>\n\nnamespace workerd {\nvoid ActorStorageLimits::checkMaxKeySize(kj::StringPtr key) {\n  // It's tempting to put the key in this message, but that key could be surprisingly large so let's\n  // return a simple message.\n  JSG_REQUIRE(key.size() <= MAX_KEY_SIZE, RangeError,\n      kj::str(\"Keys cannot be larger than \", MAX_KEY_SIZE, \" bytes. \", \"A key of size \", key.size(),\n          \" was provided.\"));\n}\n\nvoid ActorStorageLimits::checkMaxValueSize(kj::StringPtr, kj::ArrayPtr<const kj::byte> value) {\n  // It's tempting to put the key in this message, but that key could be surprisingly large so let's\n  // return a simple message.\n  JSG_REQUIRE(value.size() <= ENFORCED_MAX_VALUE_SIZE, RangeError,\n      kj::str(\"Values cannot be larger than \", ADVERTISED_MAX_VALUE_SIZE, \" bytes. \",\n          \"A value of size \", value.size(), \" was provided.\"));\n}\n\nvoid ActorStorageLimits::checkMaxPairsCount(size_t count) {\n  JSG_REQUIRE(count <= rpc::ActorStorage::MAX_KEYS, RangeError,\n      kj::str(\"Maximum number of key value pairs is \", rpc::ActorStorage::MAX_KEYS, \". \", count,\n          \" pairs were provided.\"));\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/actor-storage.capnp",
    "content": "# Copyright (c) 2017-2022 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n@0xb200a391b94343f1;\n\nusing Cxx = import \"/capnp/c++.capnp\";\n$Cxx.namespace(\"workerd::rpc\");\n$Cxx.allowCancellation;\n\ninterface ActorStorage @0xd7759d7fc87c08e4 {\n  struct KeyValue {\n    key @0 :Data;\n    value @1 :Data;\n  }\n\n  struct KeyRename {\n    oldKey @0 :Data;\n    newKey @1 :Data;\n  }\n\n  getStage @0 (stableId :Text) -> (stage :Stage);\n  # Get the storage capability for the given stage of the pipeline, identified by its stable ID.\n\n  interface Operations @0xb512f2ce1f544439 {\n    get @0 (key :Data) -> (value :Data);\n    list @3 (start :Data, end :Data, limit :Int32, reverse :Bool, stream :ListStream, prefix :Data);\n    put @1 (entries :List(KeyValue));\n    delete @2 (keys :List(Data)) -> (numDeleted :Int32);\n\n    getMultiple @4 (keys :List(Data), stream :ListStream);\n    deleteAll @5 () -> (numDeleted :Int32);\n\n    rename @9 (entries :List(KeyRename)) -> (renamed :List(Data));\n\n    getAlarm @6 () -> (scheduledTimeMs :Int64);\n    setAlarm @7 (scheduledTimeMs :Int64);\n    deleteAlarm @8 (timeToDeleteMs :Int64) -> (deleted :Bool);\n  }\n\n  struct DbSettings {\n    enum Priority {\n      default @0;\n      low @1;\n    }\n    priority @0 :Priority;\n    asOfTimeMs @1 :Int64;\n  }\n\n  interface Stage @0xdc35f52864c57550 extends(Operations) {\n    txn @0 (settings :DbSettings) -> (transaction :Transaction);\n\n    interface Transaction extends(Operations) {\n      commit @0 ();\n      rollback @1 ();\n    }\n  }\n\n  interface ListStream {\n    values @0 (list :List(KeyValue)) -> stream;\n    end @1 ();\n  }\n\n  const maxKeys :UInt32 = 128;\n  # The maximum number of keys that clients should be allowed to modify in a single storage\n  # operation. This should be enforced for operations that access or modify multiple keys. This\n  # limit will not be enforced upon the total count of keys involved in explicit transactions.\n\n  const renameLimit :UInt32 = 1000;\n  # The maximum number of keys in a rename() operation.\n}\n"
  },
  {
    "path": "src/workerd/io/actor-storage.h",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/common.h>\n#include <kj/string.h>\n\nnamespace workerd {\n// This class wraps common values and functions for interacting durable object (actor) storage.\nclass ActorStorageLimits {\n public:\n  // We grant some extra cushion on top of the advertised max size in order\n  // to avoid penalizing people for pushing right up against the advertised size.\n  // The v8 serialization method we use can add a few extra bytes for its type tag\n  // and other metadata, such as the length of a string or number of items in an\n  // array. The most important cases (where users are most likely to try to\n  // intentionally run right up against the limit) are Strings and ArrayBuffers,\n  // which each get 4 bytes of metadata attached when encoded. We throw a little\n  // extra on just for future proofing and an abundance of caution.\n  //\n  // If you're curious why we add 34 bytes of cushion -- we used to add 32, but\n  // then started writing v8 serialization headers, which are 2 bytes, and didn't\n  // want to stop accepting values that we accepted before writing headers.\n\n  static constexpr size_t ADVERTISED_MAX_VALUE_SIZE = 128 * 1024;\n  static constexpr size_t ENFORCED_MAX_VALUE_SIZE = ADVERTISED_MAX_VALUE_SIZE + 34;\n\n  static constexpr size_t MAX_KEY_SIZE = 2048;\n\n  static void checkMaxKeySize(kj::StringPtr key);\n  static void checkMaxValueSize(kj::StringPtr key, kj::ArrayPtr<const kj::byte> value);\n  static void checkMaxPairsCount(size_t count);\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/bundle-fs-test.c++",
    "content": "// Copyright (c) 2024-2029 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/io/bundle-fs.h>\n#include <workerd/tests/test-fixture.h>\n\n#include <capnp/message.h>\n#include <kj/debug.h>\n#include <kj/encoding.h>\n#include <kj/test.h>\n\nnamespace workerd {\nnamespace {\nworkerd::WorkerSource readConfig() {\n\n  kj::Vector<WorkerSource::Module> modules(8);\n\n  modules.add(WorkerSource::Module{.name = \"a/esModule\"_kj,\n    .content = WorkerSource::EsModule{.body = \"this is an esm module\"_kj}});\n\n  modules.add(WorkerSource::Module{.name = \"a/commonJsModule\"_kj,\n    .content = WorkerSource::CommonJsModule{.body = \"this is a commonjs module\"_kj}});\n\n  modules.add(WorkerSource::Module{\n    .name = \"b/text\"_kj, .content = WorkerSource::TextModule{.body = \"this is a text module\"_kj}});\n\n  modules.add(WorkerSource::Module{.name = \"b/data\"_kj,\n    .content = WorkerSource::DataModule{.body = \"this is a data module\"_kj.asArray().asBytes()}});\n\n  modules.add(WorkerSource::Module{.name = \"c/wasm\"_kj,\n    .content = WorkerSource::WasmModule{.body = \"this is a wasm module\"_kj.asArray().asBytes()}});\n\n  modules.add(WorkerSource::Module{\n    .name = \"c/json\"_kj, .content = WorkerSource::JsonModule{.body = \"this is a json module\"_kj}});\n\n  modules.add(WorkerSource::Module{.name = \"a/pythonModule\"_kj,\n    .content = WorkerSource::PythonModule{.body = \"this is a python module\"_kj}});\n\n  return workerd::WorkerSource(workerd::WorkerSource::ModulesSource{\n    .mainModule = \"worker\"_kj, .modules = modules.releaseAsArray()});\n}\n\nKJ_TEST(\"The BundleDirectoryDelegate works\") {\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto config = readConfig();\n    auto dir = getBundleDirectory(config);\n\n    KJ_REQUIRE_NONNULL(dir->tryOpen(env.js, kj::Path({\"a\"})));\n    KJ_REQUIRE_NONNULL(dir->tryOpen(env.js, kj::Path({\"a\", \"esModule\"})));\n    KJ_REQUIRE_NONNULL(dir->tryOpen(env.js, kj::Path({\"a\", \"commonJsModule\"})));\n    KJ_REQUIRE_NONNULL(dir->tryOpen(env.js, kj::Path({\"b\"})));\n    KJ_REQUIRE_NONNULL(dir->tryOpen(env.js, kj::Path({\"b\", \"text\"})));\n    KJ_REQUIRE_NONNULL(dir->tryOpen(env.js, kj::Path({\"b\", \"data\"})));\n    KJ_REQUIRE_NONNULL(dir->tryOpen(env.js, kj::Path({\"c\"})));\n    KJ_REQUIRE_NONNULL(dir->tryOpen(env.js, kj::Path({\"c\", \"wasm\"})));\n    KJ_REQUIRE_NONNULL(dir->tryOpen(env.js, kj::Path({\"c\", \"json\"})));\n    KJ_EXPECT(dir->tryOpen(env.js, kj::Path({\"a\", \"foo\"})) == kj::none);\n    KJ_EXPECT(dir->tryOpen(env.js, kj::Path({\"zzz\", \"yyy\"})) == kj::none);\n\n    // Iterating over the directory should work.\n    size_t counter = 0;\n    for (auto& _ KJ_UNUSED: *dir.get()) {\n      counter++;\n    }\n    KJ_EXPECT(counter, 3);\n\n    KJ_EXPECT(dir->count(env.js) == 3);\n\n    auto maybeEsModule = dir->tryOpen(env.js, kj::Path({\"a\", \"esModule\"}));\n    auto& esmodule = KJ_ASSERT_NONNULL(maybeEsModule).get<kj::Rc<File>>();\n    auto stat = esmodule->stat(env.js);\n    KJ_EXPECT(stat.type == FsType::FILE);\n    KJ_EXPECT(stat.size == 21);\n\n    auto maybeFile = dir->tryOpen(env.js, kj::Path({\"a\", \"commonJsModule\"}));\n    auto& file = KJ_ASSERT_NONNULL(maybeFile).get<kj::Rc<File>>();\n\n    auto readText = file->readAllText(env.js).get<jsg::JsString>();\n    KJ_EXPECT(readText == env.js.str(\"this is a commonjs module\"_kj));\n\n    auto readBytes = file->readAllBytes(env.js).get<jsg::BufferSource>();\n    KJ_EXPECT(readBytes.asArrayPtr() == \"this is a commonjs module\"_kjb);\n\n    // Reading five bytes from offset 20 should return \"odule\".\n    kj::byte buffer[5]{};\n    size_t r = file->read(env.js, 20, buffer);\n    KJ_EXPECT(r == 5);\n    KJ_EXPECT(kj::ArrayPtr<kj::byte>(buffer, r) == \"odule\"_kjb);\n\n    // Attempt to read beyond EOF return nothing.\n    KJ_EXPECT(file->read(env.js, 100, buffer) == 0);\n\n    // Attempts to modify anything should fail.\n    auto error = dir->remove(env.js, kj::Path({\"a\", \"esModule\"})).get<FsError>();\n    KJ_EXPECT(error == FsError::READ_ONLY);\n\n    // Attempting to create a file should fail.\n    auto maybeErr = dir->tryOpen(env.js, kj::Path({\"a\", \"something\", \"else\"}),\n        Directory::OpenOptions{\n          .createAs = FsType::FILE,\n        });\n    KJ_EXPECT(KJ_ASSERT_NONNULL(maybeErr).get<FsError>() == FsError::READ_ONLY);\n  });\n}\n\nKJ_TEST(\"Guarding against circular symlinks works\") {\n  // This isn't the best location for this particular test since it is\n  // not specific to bundle-fs. However, the test needs the TestFixture\n  // and it's a bit of a pain to move it to the other test file so for\n  // now it will live here.\n  TestFixture fixture;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    // We don't need to set up a TmpDirStorageScope since we are running\n    // within a test fixture that sets up an IoContext...\n    auto vfs = newVirtualFileSystem(kj::heap<FsMap>(), getTmpDirectoryImpl());\n\n    // Set up circular symlinks\n    auto maybeTemp = KJ_ASSERT_NONNULL(vfs->resolve(env.js, \"file:///\"_url));\n    auto& tempDir = maybeTemp.get<kj::Rc<Directory>>();\n\n    KJ_EXPECT(tempDir->add(env.js, \"a\", vfs->newSymbolicLink(env.js, \"file:///b\"_url)) == kj::none);\n    KJ_EXPECT(tempDir->add(env.js, \"b\", vfs->newSymbolicLink(env.js, \"file:///c\"_url)) == kj::none);\n    KJ_EXPECT(tempDir->add(env.js, \"c\", vfs->newSymbolicLink(env.js, \"file:///a\"_url)) == kj::none);\n    KJ_EXPECT(tempDir->add(env.js, \"d\", vfs->newSymbolicLink(env.js, \"file:///e\"_url)) == kj::none);\n\n    // This symlink goes no where. A kj::none should be returned.\n    KJ_EXPECT(vfs->resolve(env.js, \"file:///d\"_url) == kj::none);\n\n    auto resolved = KJ_ASSERT_NONNULL(vfs->resolve(env.js, \"file:///a\"_url));\n    KJ_EXPECT(resolved.get<workerd::FsError>() == workerd::FsError::SYMLINK_DEPTH_EXCEEDED);\n\n    auto resolvedStat = KJ_ASSERT_NONNULL(vfs->resolveStat(env.js, \"file:///a\"_url));\n    KJ_EXPECT(resolvedStat.get<workerd::FsError>() == workerd::FsError::SYMLINK_DEPTH_EXCEEDED);\n\n    resolved = KJ_ASSERT_NONNULL(vfs->resolve(env.js, \"file:///b\"_url));\n    KJ_EXPECT(resolved.get<workerd::FsError>() == workerd::FsError::SYMLINK_DEPTH_EXCEEDED);\n\n    resolved = KJ_ASSERT_NONNULL(vfs->resolve(env.js, \"file:///c\"_url));\n    KJ_EXPECT(resolved.get<workerd::FsError>() == workerd::FsError::SYMLINK_DEPTH_EXCEEDED);\n\n    // And while we're at it, let's check that a symlink can be removed\n    KJ_EXPECT(tempDir->remove(env.js, kj::Path({\"a\"})).get<bool>());\n    // Removing the symlink means that the cycle is broken and a resolve for\n    // c or b should return kj::none.\n    KJ_EXPECT(vfs->resolve(env.js, \"file:///a\"_url) == kj::none);\n    KJ_EXPECT(vfs->resolve(env.js, \"file:///b\"_url) == kj::none);\n    KJ_EXPECT(vfs->resolve(env.js, \"file:///c\"_url) == kj::none);\n  });\n}\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/bundle-fs.c++",
    "content": "#include \"bundle-fs.h\"\n\nnamespace workerd {\nkj::Rc<Directory> getBundleDirectory(const WorkerSource& conf) {\n  // Note that we are using a lazy directory here. That means we won't actually\n  // build the directory structure out until it is actually accessed in order\n  // to avoid unnecessary operations in the case a worker never actually uses\n  // this part of the filesystem.\n\n  // Importantly, the WorkerSource we get here won't be sticking around.\n  // We need to copy the details we need out of it now...Critically, however,\n  // the caller needs to arrange to keep the original source alive for the\n  // lifetime of the directory since the directory only contains pointers.\n  struct Entry {\n    kj::StringPtr name;\n    kj::ArrayPtr<const kj::byte> data;\n  };\n  kj::Vector<Entry> entries;\n  KJ_SWITCH_ONEOF(conf.variant) {\n    KJ_CASE_ONEOF(script, WorkerSource::ScriptSource) {\n      entries.add(Entry{\n        .name = script.mainScriptName,\n        .data = script.mainScript.asBytes(),\n      });\n    }\n    KJ_CASE_ONEOF(modules, WorkerSource::ModulesSource) {\n      for (auto& module: modules.modules) {\n        KJ_SWITCH_ONEOF(module.content) {\n          KJ_CASE_ONEOF(esModule, WorkerSource::EsModule) {\n            entries.add(Entry{\n              .name = module.name,\n              .data = esModule.body.asBytes(),\n            });\n          }\n          KJ_CASE_ONEOF(commonJsModule, WorkerSource::CommonJsModule) {\n            entries.add(Entry{\n              .name = module.name,\n              .data = commonJsModule.body.asBytes(),\n            });\n          }\n          KJ_CASE_ONEOF(textModule, WorkerSource::TextModule) {\n            entries.add(Entry{\n              .name = module.name,\n              .data = textModule.body.asBytes(),\n            });\n          }\n          KJ_CASE_ONEOF(dataModule, WorkerSource::DataModule) {\n            entries.add(Entry{\n              .name = module.name,\n              .data = dataModule.body,\n            });\n          }\n          KJ_CASE_ONEOF(wasmModule, WorkerSource::WasmModule) {\n            entries.add(Entry{\n              .name = module.name,\n              .data = wasmModule.body,\n            });\n          }\n          KJ_CASE_ONEOF(jsonModule, WorkerSource::JsonModule) {\n            entries.add(Entry{\n              .name = module.name,\n              .data = jsonModule.body.asBytes(),\n            });\n          }\n          KJ_CASE_ONEOF(pythonModule, WorkerSource::PythonModule) {\n            entries.add(Entry{\n              .name = module.name,\n              .data = pythonModule.body.asBytes(),\n            });\n          }\n          KJ_CASE_ONEOF(pythonRequirement, WorkerSource::PythonRequirement) {\n            // Just ignore it.\n          }\n          KJ_CASE_ONEOF(capnpModule, WorkerSource::CapnpModule) {\n            // Capnp modules are not supported in the bundle.\n            // Just ignore it.\n          }\n        }\n      }\n    }\n  }\n\n  return getLazyDirectoryImpl([entries = entries.releaseAsArray()] {\n    Directory::Builder builder;\n    kj::Path kRoot{};\n    for (auto& entry: entries) {\n      auto url = KJ_ASSERT_NONNULL(jsg::Url::tryParse(entry.name, \"file:///\"_kj));\n      // If the name is not a valid file URL path, ignore it.\n      if (url.getProtocol() != \"file:\"_kj) {\n        continue;\n      }\n      auto pathStr = kj::str(url.getPathname().slice(1));\n      auto path = kRoot.eval(pathStr);\n      builder.addPath(path, File::newReadable(entry.data));\n    }\n    return builder.finish();\n  });\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/bundle-fs.h",
    "content": "#pragma once\n\n#include <workerd/io/worker-fs.h>\n#include <workerd/io/worker-source.h>\n\nnamespace workerd {\n\n// Create a VirtualFileSystem directory from the bundle configuration. Each type of\n// module in the bundle is represented as a file. The directory structure is created\n// based on the module names. For example, if the bundle contains a module with the\n// name \"foo/bar/baz\", it will be represented as a directory \"foo\" with a subdirectory\n// \"bar\" and a file \"baz\" inside it. The directory structure and files are read-only.\n// All timestamps are set to the Unix epoch.\n//\n// Callers are expected to ensure that the pointers held by the WorkerSource remain\n// valid for the lifetime of the returned Directory.\nkj::Rc<Directory> getBundleDirectory(const WorkerSource& source);\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/cdp.capnp",
    "content": "# Copyright (c) 2017-2022 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n@0x9ac3aeb51d4b6d95;\n# capnp structures describing parts of Chrome DevTools Protocol that we need to handle manually.\n# These were initially autogenerated, but later tweaked manually so script is not included.\n\nusing Cxx = import \"/capnp/c++.capnp\";\nusing Json = import \"/capnp/compat/json.capnp\";\n\n$Cxx.namespace(\"workerd::cdp\");\n$Cxx.allowCancellation;\n\nenum LogType {\n  log @0;\n  debug @1 $Cxx.name(\"debug_\");  # avoid collision with macro on Apple platforms\n  info @2;\n  error @3;\n  warning @4;\n}\n\nstruct Runtime {\n  # Runtime domain exposes JavaScript runtime by means of remote evaluation and mirror objects. Evaluation results are returned as mirror object that expose object type, string representation and unique identifier that can be used for further object reference. Original objects are maintained in memory unless they are either explicitly released or are released along with the other objects in their object group.\n  using ScriptId = Text; # Unique script identifier.\n  struct CallFrame {\n    # Stack entry for runtime errors and assertions.\n    functionName @0 :Text; # JavaScript function name.\n    scriptId @1 :ScriptId; # JavaScript script id.\n    url @2 :Text; # JavaScript script name or url.\n    lineNumber @3 :Int32; # JavaScript script line number (0-based).\n    columnNumber @4 :Int32; # JavaScript script column number (0-based).\n  }\n  struct StackTrace {\n    # Call frames for assertions or error messages.\n    description @0 :Text; # String label of this stack trace. For async traces this may be a name of the function that initiated the async call.\n    callFrames @1 :List(CallFrame); # JavaScript function name.\n    parent @2 :StackTrace; # Asynchronous JavaScript stack trace that preceded this stack, if available.\n    parentId @3 :StackTraceId; # Asynchronous JavaScript stack trace that preceded this stack, if available.\n  }\n  using UniqueDebuggerId = Text; # Unique identifier of current debugger.\n  struct StackTraceId {\n    # If `debuggerId` is set stack trace comes from another debugger and can be resolved there. This allows to track cross-debugger calls. See `Runtime.StackTrace` and `Debugger.paused` for usages.\n    id @0 :Text;\n    debuggerId @1 :UniqueDebuggerId;\n  }\n\n  struct Event {\n    struct ConsoleApiCalled {\n      type @0 :LogType;\n\n      args @1 :List(Arg);\n      struct Arg $Json.discriminator(name = \"type\") {\n        union {\n          undefined @0 :Void;\n          string :group $Json.flatten() {\n            value @1 :Text;\n          }\n          # TODO(someday): Other types.\n        }\n      }\n\n      executionContextId @2 :UInt32;\n      timestamp @3 :Float64;\n      stackTrace @4 :StackTrace;\n    }\n  }\n}\nstruct Page {\n  # Actions and events related to the inspected page belong to the page domain.\n  enum ResourceType {\n    # Resource type as it was perceived by the rendering engine.\n    document @0 $Json.name(\"Document\");\n    stylesheet @1 $Json.name(\"Stylesheet\");\n    image @2 $Json.name(\"Image\");\n    media @3 $Json.name(\"Media\");\n    font @4 $Json.name(\"Font\");\n    script @5 $Json.name(\"Script\");\n    textTrack @6 $Json.name(\"TextTrack\");\n    xhr @7 $Json.name(\"XHR\");\n    fetch @8 $Json.name(\"Fetch\");\n    eventSource @9 $Json.name(\"EventSource\");\n    webSocket @10 $Json.name(\"WebSocket\");\n    manifest @11 $Json.name(\"Manifest\");\n    other @12 $Json.name(\"Other\");\n  }\n  using FrameId = Text; # Unique frame identifier.\n}\nstruct Security {\n  # Security\n  using CertificateId = Int32; # An internal certificate ID value.\n  enum MixedContentType {\n    # A description of mixed content (HTTP resources on HTTPS pages), as defined by https://www.w3.org/TR/mixed-content/#categories\n    blockable @0;\n    optionallyBlockable @1 $Json.name(\"optionally-blockable\");\n    none @2;\n  }\n  enum SecurityState {\n    # The security level of a page or resource.\n    unknown @0;\n    neutral @1;\n    insecure @2;\n    secure @3;\n    info @4;\n  }\n}\nstruct Network {\n  # Network domain allows tracking network activities of the page. It exposes information about http, file, data and other requests and responses, their headers, bodies, timing, etc.\n  using LoaderId = Text; # Unique loader identifier.\n  using RequestId = Text; # Unique request identifier.\n  using TimeSinceEpoch = Float64; # UTC time in seconds, counted from January 1, 1970.\n  using MonotonicTime = Float64; # Monotonically increasing time in seconds since an arbitrary point in the past.\n  using Headers = Json.Value; # Request / response headers as keys / values of JSON object.\n  struct ResourceTiming {\n    # Timing information for the request.\n    requestTime @0 :Float64; # Timing's requestTime is a baseline in seconds, while the other numbers are ticks in milliseconds relatively to this requestTime.\n    proxyStart @1 :Float64; # Started resolving proxy.\n    proxyEnd @2 :Float64; # Finished resolving proxy.\n    dnsStart @3 :Float64; # Started DNS address resolve.\n    dnsEnd @4 :Float64; # Finished DNS address resolve.\n    connectStart @5 :Float64; # Started connecting to the remote host.\n    connectEnd @6 :Float64; # Connected to the remote host.\n    sslStart @7 :Float64; # Started SSL handshake.\n    sslEnd @8 :Float64; # Finished SSL handshake.\n    workerStart @9 :Float64; # Started running ServiceWorker.\n    workerReady @10 :Float64; # Finished Starting ServiceWorker.\n    sendStart @11 :Float64; # Started sending request.\n    sendEnd @12 :Float64; # Finished sending request.\n    pushStart @13 :Float64; # Time the server started pushing request.\n    pushEnd @14 :Float64; # Time the server finished pushing request.\n    receiveHeadersEnd @15 :Float64; # Finished receiving response headers.\n  }\n  enum ResourcePriority {\n    # Loading priority of a resource request.\n    veryLow @0 $Json.name(\"VeryLow\");\n    low @1 $Json.name(\"Low\");\n    medium @2 $Json.name(\"Medium\");\n    high @3 $Json.name(\"High\");\n    veryHigh @4 $Json.name(\"VeryHigh\");\n  }\n  struct Request {\n    # HTTP request data.\n    url @0 :Text; # Request URL.\n    method @1 :Text; # HTTP request method.\n    headers @2 :Headers; # HTTP request headers.\n    postData @3 :Text; # HTTP POST request data.\n    mixedContentType @4 :Security.MixedContentType; # The mixed content type of the request.\n    initialPriority @5 :ResourcePriority; # Priority of the resource request at the time request is sent.\n    enum ReferrerPolicy {\n      # The referrer policy of the request, as defined in https://www.w3.org/TR/referrer-policy/\n      unsafeUrl @0 $Json.name(\"unsafe-url\");\n      noReferrerWhenDowngrade @1 $Json.name(\"no-referrer-when-downgrade\");\n      noReferrer @2 $Json.name(\"no-referrer\");\n      origin @3;\n      originWhenCrossOrigin @4 $Json.name(\"origin-when-cross-origin\");\n      sameOrigin @5 $Json.name(\"same-origin\");\n      strictOrigin @6 $Json.name(\"strict-origin\");\n      strictOriginWhenCrossOrigin @7 $Json.name(\"strict-origin-when-cross-origin\");\n    }\n    referrerPolicy @6 :ReferrerPolicy; # The referrer policy of the request, as defined in https://www.w3.org/TR/referrer-policy/\n    isLinkPreload @7 :Bool; # Whether is loaded via link preload.\n  }\n  struct SignedCertificateTimestamp {\n    # Details of a signed certificate timestamp (SCT).\n    status @0 :Text; # Validation status.\n    origin @1 :Text; # Origin.\n    logDescription @2 :Text; # Log name / description.\n    logId @3 :Text; # Log ID.\n    timestamp @4 :TimeSinceEpoch; # Issuance date.\n    hashAlgorithm @5 :Text; # Hash algorithm.\n    signatureAlgorithm @6 :Text; # Signature algorithm.\n    signatureData @7 :Text; # Signature data.\n  }\n  struct SecurityDetails {\n    # Security details about a request.\n    protocol @0 :Text; # Protocol name (e.g. \"TLS 1.2\" or \"QUIC\").\n    keyExchange @1 :Text; # Key Exchange used by the connection, or the empty string if not applicable.\n    keyExchangeGroup @2 :Text; # (EC)DH group used by the connection, if applicable.\n    cipher @3 :Text; # Cipher name.\n    mac @4 :Text; # TLS MAC. Note that AEAD ciphers do not have separate MACs.\n    certificateId @5 :Security.CertificateId; # Certificate ID value.\n    subjectName @6 :Text; # Certificate subject name.\n    sanList @7 :List(Text); # Subject Alternative Name (SAN) DNS names and IP addresses.\n    issuer @8 :Text; # Name of the issuing CA.\n    validFrom @9 :TimeSinceEpoch; # Certificate valid from date.\n    validTo @10 :TimeSinceEpoch; # Certificate valid to (expiration) date\n    signedCertificateTimestampList @11 :List(SignedCertificateTimestamp); # List of signed certificate timestamps (SCTs).\n  }\n  struct Response {\n    # HTTP response data.\n    url @0 :Text; # Response URL. This URL can be different from CachedResource.url in case of redirect.\n    status @1 :Int32; # HTTP response status code.\n    statusText @2 :Text; # HTTP response status text.\n    headers @3 :Headers; # HTTP response headers.\n    headersText @4 :Text; # HTTP response headers text.\n    mimeType @5 :Text; # Resource mimeType as determined by the browser.\n    requestHeaders @6 :Headers; # Refined HTTP request headers that were actually transmitted over the network.\n    requestHeadersText @7 :Text; # HTTP request headers text.\n    connectionReused @8 :Bool; # Specifies whether physical connection was actually reused for this request.\n    connectionId @9 :Float64; # Physical connection id that was actually used for this request.\n    remoteIPAddress @10 :Text; # Remote IP address.\n    remotePort @11 :Int32; # Remote port.\n    fromDiskCache @12 :Bool; # Specifies that the request was served from the disk cache.\n    fromServiceWorker @13 :Bool; # Specifies that the request was served from the ServiceWorker.\n    encodedDataLength @14 :Float64; # Total number of bytes received for this request so far.\n    timing @15 :ResourceTiming; # Timing information for the given request.\n    protocol @16 :Text; # Protocol used to fetch this request.\n    securityState @17 :Security.SecurityState; # Security state of the request resource.\n    securityDetails @18 :SecurityDetails; # Security details for the request.\n  }\n  struct Initiator {\n    # Information about the request initiator.\n    enum Type {\n      # Type of this initiator.\n      parser @0;\n      script @1;\n      preload @2;\n      other @3;\n    }\n    type @0 :Type; # Type of this initiator.\n    stack @1 :Runtime.StackTrace; # Initiator JavaScript stack trace, set for Script only.\n    url @2 :Text; # Initiator URL, set for Parser type or for Script type (when script is importing module).\n    lineNumber @3 :Float64; # Initiator line number, set for Parser type or for Script type (when script is importing module) (0-based).\n  }\n  struct Command {\n    struct Enable {\n      # Enables network tracking, network events will now be delivered to the client.\n      struct Params {\n        maxTotalBufferSize @0 :Int32; # Buffer size in bytes to use when preserving network payloads (XHRs, etc).\n        maxResourceBufferSize @1 :Int32; # Per-resource buffer size in bytes to use when preserving network payloads (XHRs, etc).\n      }\n      struct Result {}\n    }\n    struct Disable {\n      # Disables network tracking, prevents network events from being sent to the client.\n      struct Params {}\n      struct Result {}\n    }\n    struct GetResponseBody {\n      # Returns content served for the given request.\n      struct Params {\n        requestId @0 :RequestId; # Identifier of the network request to get content for.\n      }\n      struct Result {\n        body @0 :Text; # Response body.\n        base64Encoded @1 :Bool; # True, if content was sent as base64.\n      }\n    }\n  }\n  struct Event {\n    struct RequestWillBeSent {\n      # Fired when page is about to send HTTP request.\n      requestId @0 :RequestId; # Request identifier.\n      loaderId @1 :LoaderId; # Loader identifier. Empty string if the request is fetched from worker.\n      documentURL @2 :Text; # URL of the document this request is loaded for.\n      request @3 :Request; # Request data.\n      timestamp @4 :MonotonicTime; # Timestamp.\n      wallTime @5 :TimeSinceEpoch; # Timestamp.\n      initiator @6 :Initiator; # Request initiator.\n      redirectResponse @7 :Response; # Redirect response data.\n      type @8 :Page.ResourceType; # Type of this resource.\n      frameId @9 :Page.FrameId; # Frame identifier.\n    }\n    struct ResponseReceived {\n      # Fired when HTTP response is available.\n      requestId @0 :RequestId; # Request identifier.\n      loaderId @1 :LoaderId; # Loader identifier. Empty string if the request is fetched from worker.\n      timestamp @2 :MonotonicTime; # Timestamp.\n      type @3 :Page.ResourceType; # Resource type.\n      response @4 :Response; # Response data.\n      frameId @5 :Page.FrameId; # Frame identifier.\n    }\n    struct DataReceived {\n      # Fired when data chunk was received over the network.\n      requestId @0 :RequestId; # Request identifier.\n      timestamp @1 :MonotonicTime; # Timestamp.\n      dataLength @2 :Int32; # Data chunk length.\n      encodedDataLength @3 :Int32; # Actual bytes received (might be less than dataLength for compressed encodings).\n    }\n    struct LoadingFinished {\n      # Fired when HTTP request has finished loading.\n      requestId @0 :RequestId; # Request identifier.\n      timestamp @1 :MonotonicTime; # Timestamp.\n      encodedDataLength @2 :Float64; # Total number of bytes received for this request.\n      cfResponse @3 :Command.GetResponseBody.Result; # Custom extension to send response body immediately to the client.\n    }\n  }\n}\n\nstruct Profiler {\n\n  struct PositionTickInfo {\n    line @0 :Int32;\n    ticks @1 :Int32;\n  }\n\n  struct ProfileNode {\n    id @0 :Int32;\n    callFrame @1 :Runtime.CallFrame;\n    hitCount @2 :Int32;\n    children @3 :List(Int32);\n    deoptReason @4 :Text;\n    positionTicks @5 :List(PositionTickInfo);\n  }\n\n  struct Profile {\n    nodes @0 :List(ProfileNode);\n    startTime @1 :Float64;\n    endTime @2 :Float64;\n    samples @3 :List(Int32);\n    timeDeltas @4 :List(Int32);\n  }\n\n  struct Command {\n    struct Enable {\n      struct Params {}\n      struct Result {}\n    }\n    struct SetSamplingInterval {\n      struct Params {\n        interval @0 :Int32;\n      }\n      struct Result {}\n    }\n    struct Start {\n      struct Params {}\n      struct Result {}\n    }\n    struct Stop {\n      struct Params {}\n      struct Result {\n        profile @0 :Profile;\n      }\n    }\n  }\n}\n\nstruct HeapProfiler {\n  struct Command {\n    struct TakeHeapSnapshot {\n      struct Params {\n        reportProgress @0 : Bool;\n        captureNumericValue @1 : Bool;\n        exposeInternals @2 : Bool;\n      }\n      struct Result {}\n    }\n  }\n\n  struct Event {\n    struct AddHeapSnapshotChunk {\n      chunk @0 :Text;\n    }\n    struct ReportHeapSnapshotProgress {\n      done @0 : UInt32;\n      total @1 : UInt32;\n      finished @2 : Bool;\n    }\n  }\n}\n\nstruct Error {\n  code @0 :Int32;\n  message @1 :Text;\n}\n\nstruct Method(Params, Result) {\n  union {\n    params @0 :Params;\n    result @1 :Result;\n    error @2 :Error;\n  }\n}\n\nstruct Command $Json.discriminator(name = \"method\") {\n  id @0 :Int32;\n  union {\n    unknown @1 :Void;\n    networkEnable @2 :Method(Network.Command.Enable.Params, Network.Command.Enable.Result) $Json.name(\"Network.enable\") $Json.flatten();\n    networkDisable @3 :Method(Network.Command.Disable.Params, Network.Command.Disable.Result) $Json.name(\"Network.disable\") $Json.flatten();\n    networkGetResponseBody @4 :Method(Network.Command.GetResponseBody.Params, Network.Command.GetResponseBody.Result) $Json.name(\"Network.getResponseBody\") $Json.flatten();\n    profilerSetSamplingInterval @5 :Method(Profiler.Command.SetSamplingInterval.Params, Profiler.Command.SetSamplingInterval.Result) $Json.name(\"Profiler.setSamplingInterval\") $Json.flatten();\n    profilerEnable @6 :Method(Profiler.Command.Enable.Params, Profiler.Command.Enable.Result) $Json.name(\"Profiler.enable\") $Json.flatten();\n    profilerStart @7 :Method(Profiler.Command.Start.Params, Profiler.Command.Start.Result) $Json.name(\"Profiler.start\") $Json.flatten();\n    profilerStop @8 :Method(Profiler.Command.Stop.Params, Profiler.Command.Stop.Result) $Json.name(\"Profiler.stop\") $Json.flatten();\n    takeHeapSnapshot @9 : Method(HeapProfiler.Command.TakeHeapSnapshot.Params, HeapProfiler.Command.TakeHeapSnapshot.Result) $Json.name(\"HeapProfiler.takeHeapSnapshot\") $Json.flatten();\n  }\n}\n\nstruct Event $Json.discriminator(name = \"method\", valueName = \"params\") {\n  union {\n    networkRequestWillBeSent @0 :Network.Event.RequestWillBeSent $Json.name(\"Network.requestWillBeSent\"); # Fired when page is about to send HTTP request.\n    networkResponseReceived @1 :Network.Event.ResponseReceived $Json.name(\"Network.responseReceived\"); # Fired when HTTP response is available.\n    networkDataReceived @2 :Network.Event.DataReceived $Json.name(\"Network.dataReceived\"); # Fired when data chunk was received over the network.\n    networkLoadingFinished @3 :Network.Event.LoadingFinished $Json.name(\"Network.loadingFinished\"); # Fired when HTTP request has finished loading.\n\n    runtimeConsoleApiCalled @4 :Runtime.Event.ConsoleApiCalled $Json.name(\"Runtime.consoleAPICalled\");\n\n    addHeapSnapshotChunk @5 :HeapProfiler.Event.AddHeapSnapshotChunk $Json.name(\"HeapProfiler.addHeapSnapshotChunk\");\n    reportHeapSnapshotProgress @6 :HeapProfiler.Event.ReportHeapSnapshotProgress $Json.name(\"HeapProfiler.reportHeapSnapshotProgress\");\n  }\n}\n"
  },
  {
    "path": "src/workerd/io/compatibility-date-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"compatibility-date.h\"\n\n#include <workerd/io/maximum-compatibility-date.embed.h>\n\n#include <capnp/message.h>\n#include <capnp/serialize-text.h>\n#include <kj/debug.h>\n#include <kj/test.h>\n\n#include <chrono>\n#include <format>\n\nnamespace workerd {\nnamespace {\n\nKJ_TEST(\"compatibility date parsing\") {\n  auto expectParseTo = [](kj::StringPtr input, kj::StringPtr expected) {\n    KJ_IF_SOME(actual, normalizeCompatDate(input)) {\n      KJ_EXPECT(actual == expected);\n    } else {\n      KJ_FAIL_EXPECT(\"couldn't parse\", input);\n    }\n  };\n\n  auto expectNoParse = [](kj::StringPtr input) {\n    KJ_IF_SOME(actual, normalizeCompatDate(input)) {\n      KJ_FAIL_EXPECT(\"expected couldn't parse\", input, actual);\n    }\n  };\n\n  expectParseTo(\"2021-05-17\", \"2021-05-17\");\n  expectParseTo(\"2021-05-01\", \"2021-05-01\");\n  expectParseTo(\"2000-01-01\", \"2000-01-01\");\n  expectParseTo(\"2999-12-31\", \"2999-12-31\");\n  expectParseTo(\"2024-02-29\", \"2024-02-29\");\n  expectParseTo(\"2112-04-01\", \"2112-04-01\");\n\n  // Alas, strptime() accepts February 30 as a perfectly valid date.\n  //expectNoParse(\"2024-2-30\");\n  //expectNoParse(\"2023-2-29\");\n\n  expectNoParse(\"2024-2-32\");\n  expectNoParse(\"3000-01-01\");\n  expectNoParse(\"1999-12-31\");\n  expectNoParse(\"123-01-01\");\n  expectNoParse(\"2021-13-01\");\n  expectNoParse(\"2021-12-32\");\n  expectNoParse(\"2021-00-01\");\n  expectNoParse(\"2021-01-00\");\n\n  expectNoParse(\" 2021-05-17\");\n  expectNoParse(\"2021 -05-17\");\n  expectNoParse(\"2021- 05-17\");\n  expectNoParse(\"2021-05 -17\");\n  expectNoParse(\"2021-05- 17\");\n  expectNoParse(\"2021-05-17 \");\n  expectNoParse(\"2021/05/17\");\n  expectNoParse(\"2021_05_17\");\n\n  expectNoParse(\"2021-5-07\");\n  expectNoParse(\"2021-05-7\");\n  expectNoParse(\"202-05-07\");\n}\n\nKJ_TEST(\"compatibility flag parsing\") {\n  auto expectCompileCompatibilityFlags =\n      [](kj::StringPtr compatDate, kj::ArrayPtr<const kj::StringPtr> featureFlags,\n          kj::StringPtr expectedOutput, kj::ArrayPtr<const kj::StringPtr> expectedErrors = nullptr,\n          CompatibilityDateValidation dateValidation = CompatibilityDateValidation::FUTURE_FOR_TEST,\n          bool r2InternalBetaApiSet = false, bool experimental = false) {\n    capnp::MallocMessageBuilder message;\n    auto orphanage = message.getOrphanage();\n\n    auto flagListOrphan = orphanage.newOrphan<capnp::List<capnp::Text>>(featureFlags.size());\n    auto flagList = flagListOrphan.get();\n    for (auto i: kj::indices(featureFlags)) {\n      flagList.set(i, featureFlags.begin()[i]);\n    }\n\n    auto outputOrphan = orphanage.newOrphan<CompatibilityFlags>();\n    auto output = outputOrphan.get();\n\n    SimpleWorkerErrorReporter errorReporter;\n    compileCompatibilityFlags(\n        compatDate, flagList.asReader(), output, errorReporter, experimental, dateValidation);\n\n    capnp::TextCodec codec;\n    auto parsedExpectedOutput = codec.decode<CompatibilityFlags>(expectedOutput, orphanage);\n\n    if (!r2InternalBetaApiSet) {\n      // The r2PublicBetaApi is always expected by default regardless of compat date unless\n      // explicitly disabled.\n      parsedExpectedOutput.get().setR2PublicBetaApi(true);\n    }\n\n    // If errors are expected, then the output is irrelevant.\n    if (expectedErrors.size() == 0) {\n      KJ_EXPECT(kj::str(output) == kj::str(parsedExpectedOutput.getReader()));\n    }\n    KJ_EXPECT(kj::strArray(errorReporter.errors, \"\\n\") == kj::strArray(expectedErrors, \"\\n\"));\n  };\n\n  expectCompileCompatibilityFlags(\"2021-05-17\", {}, \"()\");\n\n  expectCompileCompatibilityFlags(\"2021-11-02\", {}, \"(formDataParserSupportsFiles = false)\");\n  expectCompileCompatibilityFlags(\"2021-11-03\", {}, \"(formDataParserSupportsFiles = true)\");\n  expectCompileCompatibilityFlags(\"2021-11-04\", {}, \"(formDataParserSupportsFiles = true)\");\n  expectCompileCompatibilityFlags(\"2021-11-03\", {\"formdata_parser_converts_files_to_strings\"},\n      \"(formDataParserSupportsFiles = false)\");\n\n  // Test compatibility flag overrides.\n  expectCompileCompatibilityFlags(\n      \"2021-05-17\", {\"formdata_parser_supports_files\"_kj}, \"(formDataParserSupportsFiles = true)\");\n  expectCompileCompatibilityFlags(\"2021-05-17\", {\"fetch_refuses_unknown_protocols\"_kj},\n      \"(fetchRefusesUnknownProtocols = true)\");\n  expectCompileCompatibilityFlags(\"2021-05-17\",\n      {\"formdata_parser_supports_files\"_kj, \"fetch_refuses_unknown_protocols\"_kj},\n      \"(formDataParserSupportsFiles = true, fetchRefusesUnknownProtocols = true)\");\n  expectCompileCompatibilityFlags(\"2021-11-04\", {\"fetch_refuses_unknown_protocols\"_kj},\n      \"(formDataParserSupportsFiles = true, fetchRefusesUnknownProtocols = true)\");\n\n  // Test errors.\n  expectCompileCompatibilityFlags(\"abcd\", {}, \"()\", {\"Invalid compatibility date: abcd\"});\n  expectCompileCompatibilityFlags(\"2021-05-17\",\n      {\"formdata_parser_supports_files\"_kj, \"formdata_parser_supports_files\"_kj},\n      \"(formDataParserSupportsFiles = true)\",\n      {\"Compatibility flag specified multiple times: formdata_parser_supports_files\"});\n  expectCompileCompatibilityFlags(\"2021-05-17\",\n      {\"formdata_parser_supports_files\"_kj, \"formdata_parser_converts_files_to_strings\"_kj},\n      \"(formDataParserSupportsFiles = true)\",\n      {\"Compatibility flags are mutually contradictory: \"\n       \"formdata_parser_supports_files vs formdata_parser_converts_files_to_strings\"});\n  expectCompileCompatibilityFlags(\"2021-11-04\", {\"formdata_parser_supports_files\"_kj},\n      \"(formDataParserSupportsFiles = true)\",\n      {\"The compatibility flag formdata_parser_supports_files became the default as of \"\n       \"2021-11-03 so does not need to be specified anymore.\"},\n      CompatibilityDateValidation::CURRENT_DATE_FOR_CLOUDFLARE);\n  expectCompileCompatibilityFlags(\n      \"2021-05-17\", {\"unknown_feature\"_kj}, \"()\", {\"No such compatibility flag: unknown_feature\"});\n\n  expectCompileCompatibilityFlags(\"2252-04-01\", {}, \"()\",\n      {\"Can't set compatibility date in the future: 2252-04-01\"},\n      CompatibilityDateValidation::CURRENT_DATE_FOR_CLOUDFLARE);\n\n  expectCompileCompatibilityFlags(\"2252-04-01\", {}, \"()\",\n      {kj::str(\"This Worker requires compatibility date \\\"2252-04-01\\\", but the newest date \"\n               \"supported by this server binary is \\\"\",\n           MAXIMUM_COMPATIBILITY_DATE, \"\\\".\"),\n        kj::str(\"Can't set compatibility date in the future: \\\"2252-04-01\\\". Today's date \"\n                \"(UTC) is \\\"\",\n            currentDateStr(), \"\\\".\")},\n      CompatibilityDateValidation::CODE_VERSION);\n\n  // Test experimental requirement using durable_object_rename as it is obsolete\n  expectCompileCompatibilityFlags(\"2020-01-01\", {\"durable_object_rename\"_kj}, \"(obsolete19 = true)\",\n      {\"The compatibility flag durable_object_rename is experimental and may break or be removed \"\n       \"in a future version of workerd. To use this flag, you must pass --experimental on the \"\n       \"command line.\"_kj},\n      CompatibilityDateValidation::CODE_VERSION, false, false);\n  expectCompileCompatibilityFlags(\"2020-01-01\", {\"durable_object_rename\"_kj}, \"(obsolete19 = true)\",\n      {}, CompatibilityDateValidation::CODE_VERSION, false, true);\n\n  // Test experimental requirement using the durable_object_alarms flag since we know this flag\n  // is obsolete and will never have a date set. (Should always pass, even if experimental flags\n  // aren't allowed)\n  expectCompileCompatibilityFlags(\"2020-01-01\", {\"durable_object_alarms\"_kj}, \"(obsolete14 = true)\",\n      {}, CompatibilityDateValidation::CODE_VERSION, false, false);\n  expectCompileCompatibilityFlags(\"2020-01-01\", {\"durable_object_alarms\"_kj}, \"(obsolete14 = true)\",\n      {}, CompatibilityDateValidation::CODE_VERSION, false, true);\n\n  // Multiple errors.\n  expectCompileCompatibilityFlags(\"abcd\",\n      {\"formdata_parser_supports_files\"_kj, \"fetch_refuses_unknown_protocols\"_kj,\n        \"unknown_feature\"_kj, \"fetch_refuses_unknown_protocols\"_kj, \"another_feature\"_kj,\n        \"formdata_parser_supports_files\"_kj},\n      \"(formDataParserSupportsFiles = true, fetchRefusesUnknownProtocols = true)\",\n      {\"Compatibility flag specified multiple times: fetch_refuses_unknown_protocols\",\n        \"Compatibility flag specified multiple times: formdata_parser_supports_files\",\n        \"Invalid compatibility date: abcd\", \"No such compatibility flag: another_feature\",\n        \"No such compatibility flag: unknown_feature\"});\n\n  // Can explicitly disable flag that's enabled for all dates.s\n  expectCompileCompatibilityFlags(\"2021-05-17\", {\"r2_internal_beta_bindings\"}, \"()\", {},\n      CompatibilityDateValidation::FUTURE_FOR_TEST, true, false);\n\n  // nodejs_compat implies nodejs_compat_v2 on or after 2024-09-23\n  expectCompileCompatibilityFlags(\"2024-09-23\", {\"nodejs_compat\"},\n      \"(formDataParserSupportsFiles = true,\"\n      \" fetchRefusesUnknownProtocols = true,\"\n      \" esiIncludeIsVoidTag = false,\"\n      \" obsolete3 = false,\"\n      \" durableObjectFetchRequiresSchemeAuthority = true,\"\n      \" streamsByobReaderDetachesBuffer = true,\"\n      \" streamsJavaScriptControllers = true,\"\n      \" jsgPropertyOnPrototypeTemplate = true,\"\n      \" minimalSubrequests = true,\"\n      \" noCotsOnExternalFetch = true,\"\n      \" specCompliantUrl = true,\"\n      \" globalNavigator = true,\"\n      \" captureThrowsAsRejections = true,\"\n      \" r2PublicBetaApi = true,\"\n      \" obsolete14 = false,\"\n      \" noSubstituteNull = true,\"\n      \" transformStreamJavaScriptControllers = true,\"\n      \" r2ListHonorIncludeFields = true,\"\n      \" exportCommonJsDefaultNamespace = true,\"\n      \" obsolete19 = false,\"\n      \" webSocketCompression = true,\"\n      \" nodeJsCompat = true,\"\n      \" obsolete22 = false,\"\n      \" specCompliantResponseRedirect = true,\"\n      \" workerdExperimental = false,\"\n      \" durableObjectGetExisting = false,\"\n      \" httpHeadersGetSetCookie = true,\"\n      \" dispatchExceptionTunneling = true,\"\n      \" serviceBindingExtraHandlers = false,\"\n      \" noCfBotManagementDefault = true,\"\n      \" urlSearchParamsDeleteHasValueArg = true,\"\n      \" strictCompression = true,\"\n      \" brotliContentEncoding = true,\"\n      \" strictCrypto = true,\"\n      \" rttiApi = false,\"\n      \" obsolete35 = false,\"\n      \" cryptoPreservePublicExponent = true,\"\n      \" vectorizeQueryMetadataOptional = true,\"\n      \" unsafeModule = false,\"\n      \" jsRpc = false,\"\n      \" noImportScripts = true,\"\n      \" nodeJsAls = false,\"\n      \" queuesJsonMessages = true,\"\n      \" pythonWorkers = false,\"\n      \" fetcherNoGetPutDelete = true,\"\n      \" unwrapCustomThenables = true,\"\n      \" fetcherRpc = true,\"\n      \" internalStreamByobReturn = true,\"\n      \" blobStandardMimeType = true,\"\n      \" fetchStandardUrl = true,\"\n      \" nodeJsCompatV2 = true,\"\n      \" globalFetchStrictlyPublic = false,\"\n      \" newModuleRegistry = false,\"\n      \" allowCustomPorts = true,\"\n      \" internalWritableStreamAbortClearsQueue = true,\"\n      \" nodeJsZlib = true)\",\n      {}, CompatibilityDateValidation::FUTURE_FOR_TEST, false, false);\n  expectCompileCompatibilityFlags(\"2024-09-22\", {\"nodejs_compat\"},\n      \"(formDataParserSupportsFiles = true,\"\n      \" fetchRefusesUnknownProtocols = true,\"\n      \" esiIncludeIsVoidTag = false,\"\n      \" obsolete3 = false,\"\n      \" durableObjectFetchRequiresSchemeAuthority = true,\"\n      \" streamsByobReaderDetachesBuffer = true,\"\n      \" streamsJavaScriptControllers = true,\"\n      \" jsgPropertyOnPrototypeTemplate = true,\"\n      \" minimalSubrequests = true,\"\n      \" noCotsOnExternalFetch = true,\"\n      \" specCompliantUrl = true,\"\n      \" globalNavigator = true,\"\n      \" captureThrowsAsRejections = true,\"\n      \" r2PublicBetaApi = true,\"\n      \" obsolete14 = false,\"\n      \" noSubstituteNull = true,\"\n      \" transformStreamJavaScriptControllers = true,\"\n      \" r2ListHonorIncludeFields = true,\"\n      \" exportCommonJsDefaultNamespace = true,\"\n      \" obsolete19 = false,\"\n      \" webSocketCompression = true,\"\n      \" nodeJsCompat = true,\"\n      \" obsolete22 = false,\"\n      \" specCompliantResponseRedirect = true,\"\n      \" workerdExperimental = false,\"\n      \" durableObjectGetExisting = false,\"\n      \" httpHeadersGetSetCookie = true,\"\n      \" dispatchExceptionTunneling = true,\"\n      \" serviceBindingExtraHandlers = false,\"\n      \" noCfBotManagementDefault = true,\"\n      \" urlSearchParamsDeleteHasValueArg = true,\"\n      \" strictCompression = true,\"\n      \" brotliContentEncoding = true,\"\n      \" strictCrypto = true,\"\n      \" rttiApi = false,\"\n      \" obsolete35 = false,\"\n      \" cryptoPreservePublicExponent = true,\"\n      \" vectorizeQueryMetadataOptional = true,\"\n      \" unsafeModule = false,\"\n      \" jsRpc = false,\"\n      \" noImportScripts = true,\"\n      \" nodeJsAls = false,\"\n      \" queuesJsonMessages = true,\"\n      \" pythonWorkers = false,\"\n      \" fetcherNoGetPutDelete = true,\"\n      \" unwrapCustomThenables = true,\"\n      \" fetcherRpc = true,\"\n      \" internalStreamByobReturn = true,\"\n      \" blobStandardMimeType = true,\"\n      \" fetchStandardUrl = true,\"\n      \" nodeJsCompatV2 = false,\"\n      \" globalFetchStrictlyPublic = false,\"\n      \" newModuleRegistry = false,\"\n      \" cacheOptionEnabled = false,\"\n      \" kvDirectBinding = false,\"\n      \" allowCustomPorts = true,\"\n      \" increaseWebsocketMessageSize = false,\"\n      \" internalWritableStreamAbortClearsQueue = true,\"\n      \" pythonWorkersDevPyodide = false,\"\n      \" nodeJsZlib = false)\",\n      {}, CompatibilityDateValidation::FUTURE_FOR_TEST, false, false);\n}\n\nKJ_TEST(\"encode to flag list for FL\") {\n  capnp::MallocMessageBuilder message;\n  auto orphanage = message.getOrphanage();\n\n  auto compileOwnFeatureFlags =\n      [&](kj::StringPtr compatDate, kj::ArrayPtr<const kj::StringPtr> featureFlags,\n          CompatibilityDateValidation dateValidation = CompatibilityDateValidation::FUTURE_FOR_TEST,\n          bool experimental = false) {\n    auto flagListOrphan = orphanage.newOrphan<capnp::List<capnp::Text>>(featureFlags.size());\n    auto flagList = flagListOrphan.get();\n    for (auto i: kj::indices(featureFlags)) {\n      flagList.set(i, featureFlags.begin()[i]);\n    }\n\n    auto outputOrphan = orphanage.newOrphan<CompatibilityFlags>();\n    auto output = outputOrphan.get();\n\n    SimpleWorkerErrorReporter errorReporter;\n\n    compileCompatibilityFlags(\n        compatDate, flagList.asReader(), output, errorReporter, experimental, dateValidation);\n    KJ_ASSERT(errorReporter.errors.empty());\n\n    return kj::mv(outputOrphan);\n  };\n\n  {\n    // Disabled by date.\n    auto featureFlagsOrphan = compileOwnFeatureFlags(\"2021-05-17\", {});\n    auto featureFlags = featureFlagsOrphan.get();\n    auto strings = decompileCompatibilityFlagsForFl(featureFlags);\n    KJ_EXPECT(strings.size() == 0);\n  }\n\n  {\n    // Disabled by date, enabled by flag.\n    auto featureFlagsOrphan = compileOwnFeatureFlags(\"2021-05-17\", {\"minimal_subrequests\"_kj});\n    auto featureFlags = featureFlagsOrphan.get();\n    auto strings = decompileCompatibilityFlagsForFl(featureFlags);\n    KJ_EXPECT(strings.size() == 1);\n    KJ_EXPECT(strings[0] == \"minimal_subrequests\"_kj);\n  }\n\n  {\n    // Enabled by date.\n    auto featureFlagsOrphan = compileOwnFeatureFlags(\"2022-07-01\", {});\n    auto featureFlags = featureFlagsOrphan.get();\n    auto strings = decompileCompatibilityFlagsForFl(featureFlags);\n    KJ_EXPECT(strings.size() == 2);\n    KJ_EXPECT(strings[0] == \"minimal_subrequests\"_kj);\n    KJ_EXPECT(strings[1] == \"no_cots_on_external_fetch\"_kj);\n  }\n\n  {\n    // Enabled by date, disabled by flag.\n    auto featureFlagsOrphan = compileOwnFeatureFlags(\"2022-07-01\", {\"cots_on_external_fetch\"});\n    auto featureFlags = featureFlagsOrphan.get();\n    auto strings = decompileCompatibilityFlagsForFl(featureFlags);\n    KJ_EXPECT(strings.size() == 1);\n    KJ_EXPECT(strings[0] == \"minimal_subrequests\"_kj);\n  }\n}\n\nKJ_TEST(\"compatibility dates must be Tuesday, Wednesday, or Thursday\") {\n  // List of specific flags that are allowed to use non-conformant dates\n  // (already deployed and can't be changed for compatibility reasons)\n  kj::HashSet<kj::StringPtr> allowedFlagExceptions;\n  allowedFlagExceptions.insertAll(std::initializer_list<kj::StringPtr>{\n    // Existing non-conformant dates that are already deployed\n    \"jsgPropertyOnPrototypeTemplate\"_kj,           // 2022-01-31 (Monday)\n    \"specCompliantUrl\"_kj,                         // 2022-10-31 (Monday)\n    \"globalNavigator\"_kj,                          // 2022-03-21 (Monday)\n    \"captureThrowsAsRejections\"_kj,                // 2022-10-31 (Monday)\n    \"exportCommonJsDefaultNamespace\"_kj,           // 2022-10-31 (Monday)\n    \"urlSearchParamsDeleteHasValueArg\"_kj,         // 2023-07-01 (Saturday)\n    \"brotliContentEncoding\"_kj,                    // 2024-04-29 (Monday)\n    \"cryptoPreservePublicExponent\"_kj,             // 2023-12-01 (Friday)\n    \"noImportScripts\"_kj,                          // 2024-03-04 (Monday)\n    \"queuesJsonMessages\"_kj,                       // 2024-03-18 (Monday)\n    \"unwrapCustomThenables\"_kj,                    // 2024-04-01 (Monday)\n    \"internalStreamByobReturn\"_kj,                 // 2024-05-13 (Monday)\n    \"blobStandardMimeType\"_kj,                     // 2024-06-03 (Monday)\n    \"fetchStandardUrl\"_kj,                         // 2024-06-03 (Monday)\n    \"cacheOptionEnabled\"_kj,                       // 2024-11-11 (Monday)\n    \"allowCustomPorts\"_kj,                         // 2024-09-02 (Monday)\n    \"internalWritableStreamAbortClearsQueue\"_kj,   // 2024-09-02 (Monday)\n    \"handleCrossRequestPromiseResolution\"_kj,      // 2024-10-14 (Monday)\n    \"upperCaseAllHttpMethods\"_kj,                  // 2024-10-14 (Monday)\n    \"noTopLevelAwaitInRequire\"_kj,                 // 2024-12-02 (Monday)\n    \"fixupTransformStreamBackpressure\"_kj,         // 2024-12-16 (Monday)\n    \"obsolete74\"_kj,                               // 2025-03-10 (Monday)\n    \"cacheApiRequestCfOverridesCacheRules\"_kj,     // 2025-05-19 (Monday)\n    \"cacheApiCompatFlags\"_kj,                      // 2025-04-19 (Saturday)\n    \"jsWeakRef\"_kj,                                // 2025-05-05 (Monday)\n    \"enableNavigatorLanguage\"_kj,                  // 2025-05-19 (Monday)\n    \"allowEvalDuringStartup\"_kj,                   // 2025-06-01 (Sunday)\n    \"bindAsyncLocalStorageSnapshot\"_kj,            // 2025-06-16 (Monday)\n    \"throwOnUnrecognizedImportAssertion\"_kj,       // 2025-06-16 (Monday)\n    \"setEventTargetThis\"_kj,                       // 2025-08-01 (Friday)\n    \"enableForwardableEmailFullHeaders\"_kj,        // 2025-08-01 (Friday)\n    \"exposeGlobalMessageChannel\"_kj,               // 2025-08-15 (Friday)\n    \"pythonWorkersForceNewVendorPath\"_kj,          // 2025-08-11 (Monday)\n    \"enableWorkflowScriptValidation\"_kj,           // 2025-09-20 (Saturday)\n    \"stripAuthorizationOnCrossOriginRedirect\"_kj,  // 2025-09-01 (Monday)\n    \"enableCtxExports\"_kj,                         // 2025-11-17 (Monday)\n\n    // Non-conformant dates via impliedByAfterDate\n    \"pythonWorkers\"_kj,                  // 2000-01-01 (Saturday) via impliedByAfterDate\n    \"nodeJsCompatV2\"_kj,                 // 2024-09-23 (Monday) via impliedByAfterDate\n    \"nodeJsZlib\"_kj,                     // 2024-09-23 (Monday) via impliedByAfterDate\n    \"enableNodejsHttpModules\"_kj,        // 2025-08-15 (Friday) via impliedByAfterDate\n    \"enableNodejsHttpServerModules\"_kj,  // 2025-09-01 (Monday) via impliedByAfterDate\n    \"removeNodejsCompatEOL\"_kj,          // 2025-09-01 (Monday) via impliedByAfterDate\n    \"enableNodeJsHttp2Module\"_kj,        // 2025-09-01 (Monday) via impliedByAfterDate\n    \"removeNodejsCompatEOLv23\"_kj,       // 2025-09-01 (Monday) via impliedByAfterDate\n    \"enableNodeJsProcessV2\"_kj,          // 2025-09-15 (Monday) via impliedByAfterDate\n    \"enableNodeJsFsModule\"_kj,           // 2025-09-15 (Monday) via impliedByAfterDate\n    \"enableNodeJsOsModule\"_kj,           // 2025-09-15 (Monday) via impliedByAfterDate\n    \"pythonWorkflows\"_kj,                // 2025-09-20 (Saturday) via impliedByAfterDate\n    \"enableNodeJsConsoleModule\"_kj,      // 2025-09-21 (Sunday) via impliedByAfterDate\n    \"pythonWorkers20250116\"_kj,          // 2025-09-29 (Monday) via impliedByAfterDate\n    \"removeNodejsCompatEOLv22\"_kj,       // 2027-04-30 (Friday) via impliedByAfterDate\n    \"removeNodejsCompatEOLv24\"_kj,       // 2028-04-30 (Sunday) via impliedByAfterDate\n  });\n\n  // Helper function to suggest the next valid date (Tuesday, Wednesday, or Thursday)\n  auto suggestNextValidDate =\n      [](const std::chrono::year_month_day& ymd) -> std::chrono::year_month_day {\n    auto currentDate = std::chrono::sys_days{ymd};\n    auto wd = std::chrono::weekday{currentDate};\n\n    // Calculate days until next Tuesday (2), Wednesday (3), or Thursday (4)\n    int currentDay = wd.c_encoding();  // 0 = Sunday, 1 = Monday, etc.\n    int daysToAdd;\n\n    if (currentDay <= 1) {\n      // Sunday (0) or Monday (1): next valid day is Tuesday\n      daysToAdd = 2 - currentDay;\n    } else if (currentDay >= 5) {\n      // Friday (5) or Saturday (6): next valid day is Tuesday of next week\n      daysToAdd = (7 - currentDay) + 2;\n    } else {\n      // Already Tuesday/Wednesday/Thursday - this shouldn't happen\n      daysToAdd = 0;\n    }\n\n    return std::chrono::year_month_day{currentDate + std::chrono::days{daysToAdd}};\n  };\n\n  // Helper function to parse and validate date\n  auto parseDate = [](kj::StringPtr dateStr) -> kj::Maybe<std::chrono::year_month_day> {\n    // First validate the date using normalizeCompatDate\n    KJ_IF_SOME(normalized, normalizeCompatDate(dateStr)) {\n      // Parse the validated date string\n      int year, month, day;\n      KJ_ASSERT(sscanf(normalized.cStr(), \"%d-%d-%d\", &year, &month, &day) == 3);\n\n      // Create year_month_day\n      auto ymd = std::chrono::year_month_day{\n        std::chrono::year{year},\n        std::chrono::month{static_cast<unsigned>(month)},\n        std::chrono::day{static_cast<unsigned>(day)},\n      };\n      KJ_ASSERT(ymd.ok());\n\n      return ymd;\n    } else {\n      return kj::none;\n    }\n  };\n\n  // Check all compatibility flag fields\n  auto schema = capnp::Schema::from<CompatibilityFlags>();\n  auto fields = schema.getFields();\n\n  kj::Vector<kj::String> violations;\n\n  for (auto field: fields) {\n    auto fieldName = field.getProto().getName();\n\n    // Skip if this specific flag is in the allowed exceptions list\n    if (allowedFlagExceptions.contains(fieldName)) {\n      continue;\n    }\n\n    for (auto annotation: field.getProto().getAnnotations()) {\n      kj::Maybe<kj::StringPtr> maybeDateStr;\n\n      if (annotation.getId() == COMPAT_ENABLE_DATE_ANNOTATION_ID) {\n        maybeDateStr = annotation.getValue().getText();\n      } else if (annotation.getId() == IMPLIED_BY_AFTER_DATE_ANNOTATION_ID) {\n        auto value = annotation.getValue();\n        auto s = value.getStruct().getAs<workerd::ImpliedByAfterDate>();\n        maybeDateStr = s.getDate();\n      }\n\n      KJ_IF_SOME(dateStr, maybeDateStr) {\n        auto ymd = KJ_REQUIRE_NONNULL(\n            parseDate(dateStr), \"Invalid compatibility flag date format: \", dateStr);\n        auto suggestedYmd = suggestNextValidDate(ymd);\n\n        // If suggestNextValidDate returns a different date, the original date was invalid\n        if (ymd != suggestedYmd) {\n          static const char* dayNames[] = {\n            \"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"};\n\n          auto wd = std::chrono::weekday{std::chrono::sys_days{ymd}};\n          int dayOfWeek = wd.c_encoding();\n\n          auto suggestedWd = std::chrono::weekday{std::chrono::sys_days{suggestedYmd}};\n          int suggestedDayOfWeek = suggestedWd.c_encoding();\n\n          auto suggestedDateStr = std::format(\"{:%F}\", suggestedYmd);\n          violations.add(kj::str(\"Field '\", fieldName, \"' has date \", dateStr, \" which is a \",\n              dayNames[dayOfWeek], \". Dates must be Tuesday, Wednesday, or Thursday. \",\n              \"Suggestion: use \", suggestedDateStr.c_str(), \" (\", dayNames[suggestedDayOfWeek],\n              \") instead.\"));\n        }\n      }\n    }\n  }\n\n  if (!violations.empty()) {\n    KJ_FAIL_ASSERT(\"Compatibility date violations found:\\n\", kj::strArray(violations, \"\\n\"));\n  }\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/compatibility-date.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"compatibility-date.h\"\n\n#include \"time.h\"\n\n#include <workerd/io/maximum-compatibility-date.embed.h>\n#include <workerd/io/release-version.embed.h>\n\n#include <capnp/dynamic.h>\n#include <capnp/schema.h>\n#include <kj/debug.h>\n#include <kj/map.h>\n#include <kj/vector.h>\n\n#include <cstdio>\n\nnamespace workerd {\n\nusing kj::uint;\n\nnamespace {\n\nstruct CompatDate {\n  uint year;\n  uint month;\n  uint day;\n\n  inline bool operator==(const CompatDate& other) const {\n    return year == other.year && month == other.month && day == other.day;\n  }\n  inline bool operator<(const CompatDate& other) const {\n    if (year < other.year) return true;\n    if (year > other.year) return false;\n    if (month < other.month) return true;\n    if (month > other.month) return false;\n    return day < other.day;\n  }\n  inline bool operator>=(const CompatDate& other) const {\n    return !(*this < other);\n  }\n\n  static kj::Maybe<CompatDate> parse(kj::StringPtr text) {\n    // Basic sanity check that years are 4-digit in the [2000,2999] range. If it is the year 3000\n    // and this code broke, all I can say is: haha, take that robots, humans screwed you over yet\n    // again, you can Roko's basilisk me all you want I don't care.\n    if (!text.startsWith(\"2\")) return kj::none;\n    // Force 4-digit year, 2-digit month, and 2-digit day.\n    if (text.size() != 10 || text[4] != '-' || text[7] != '-') {\n      return kj::none;\n    }\n    // Validate the date contains only digits and dashes.\n    for (char c: text) {\n      if ((c < '0' || '9' < c) && c != '-') return kj::none;\n    }\n    uint year, month, day;\n    // TODO(someday): use `kj::parse` here instead\n    auto result = sscanf(text.cStr(), \"%d-%d-%d\", &year, &month, &day);\n    if (result == EOF || result < 3) return kj::none;\n    // Basic validation, notably this will happily accept invalid dates like 2022-02-30\n    if (year < 2000 || year >= 3000) return kj::none;\n    if (month < 1 || month > 12) return kj::none;\n    if (day < 1 || day > 31) return kj::none;\n    return CompatDate{year, month, day};\n  }\n\n  static CompatDate parse(kj::StringPtr text, Worker::ValidationErrorReporter& errorReporter) {\n    static constexpr CompatDate DEFAULT_DATE{2021, 5, 1};\n    KJ_IF_SOME(v, parse(text)) {\n      return v;\n    } else {\n      errorReporter.addError(kj::str(\"Invalid compatibility date: \", text));\n      return DEFAULT_DATE;\n    }\n  }\n\n  static CompatDate today() {\n    time_t now = time(nullptr);\n#if _MSC_VER\n    // `gmtime` is thread-safe on Windows: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/gmtime-gmtime32-gmtime64?view=msvc-170#return-value\n    auto t = *gmtime(&now);\n#else\n    struct tm t;\n    KJ_ASSERT(gmtime_r(&now, &t) == &t);\n#endif\n    return {static_cast<uint>(t.tm_year + 1900), static_cast<uint>(t.tm_mon + 1),\n      static_cast<uint>(t.tm_mday)};\n  }\n\n  kj::String toString() {\n    return kj::str(year, '-', month < 10 ? \"0\" : \"\", month, '-', day < 10 ? \"0\" : \"\", day);\n  }\n};\n}  // namespace\n\nkj::String currentDateStr() {\n  return CompatDate::today().toString();\n}\n\nstatic void compileCompatibilityFlags(kj::StringPtr compatDate,\n    kj::HashSet<kj::String> flagSet,\n    CompatibilityFlags::Builder output,\n    Worker::ValidationErrorReporter& errorReporter,\n    bool allowExperimentalFeatures,\n    CompatibilityDateValidation dateValidation) {\n  auto parsedCompatDate = CompatDate::parse(compatDate, errorReporter);\n\n  switch (dateValidation) {\n    case CompatibilityDateValidation::CODE_VERSION:\n      if (KJ_ASSERT_NONNULL(CompatDate::parse(MAXIMUM_COMPATIBILITY_DATE)) < parsedCompatDate) {\n        errorReporter.addError(\n            kj::str(\"This Worker requires compatibility date \\\"\", parsedCompatDate,\n                \"\\\", but the newest \"\n                \"date supported by this server binary is \\\"\",\n                MAXIMUM_COMPATIBILITY_DATE, \"\\\".\"));\n      }\n      // workerd is built with MAXIMUM_COMPATIBILITY_DATE set a little bit into the future, so\n      // that the build can support setting the compat date to today until the next release is\n      // ready. But we don't want people to actually set their compat date in the future, so let's\n      // check against the clock time as well.\n      if (CompatDate::today() < parsedCompatDate) {\n        errorReporter.addError(kj::str(\"Can't set compatibility date in the future: \\\"\",\n            parsedCompatDate, \"\\\". Today's date (UTC) is \\\"\", CompatDate::today(), \"\\\".\"));\n      }\n      break;\n\n    case CompatibilityDateValidation::CURRENT_DATE_FOR_CLOUDFLARE:\n      if (CompatDate::today() < parsedCompatDate) {\n        errorReporter.addError(\n            kj::str(\"Can't set compatibility date in the future: \", parsedCompatDate));\n      }\n      break;\n\n    case CompatibilityDateValidation::FUTURE_FOR_TEST:\n      // No validation.\n      break;\n  }\n\n  auto schema = capnp::Schema::from<CompatibilityFlags>();\n  auto dynamicOutput = capnp::toDynamic(output);\n\n  // For each item added to this list, the flag identified by field will be\n  // enabled if the flag identified by other is enabled.\n  struct ImpliedBy {\n    capnp::StructSchema::Field field;\n    capnp::StructSchema::Field other;\n  };\n  kj::Vector<ImpliedBy> impliedByList(schema.getFields().size());\n\n  for (auto field: schema.getFields()) {\n    bool enableByDate = false;\n    bool enableByFlag = false;\n    bool disableByFlag = false;\n    bool isExperimental = false;\n\n    kj::Maybe<CompatDate> enableDate;\n    kj::StringPtr enableFlagName;\n    kj::StringPtr disableFlagName;\n    kj::Vector<ImpliedBy> impliedByVector;\n\n    for (auto annotation: field.getProto().getAnnotations()) {\n      if (annotation.getId() == COMPAT_ENABLE_FLAG_ANNOTATION_ID) {\n        enableFlagName = annotation.getValue().getText();\n        KJ_IF_SOME(entry, flagSet.find(enableFlagName)) {\n          enableByFlag = true;\n          flagSet.erase(entry);\n        }\n      } else if (annotation.getId() == COMPAT_DISABLE_FLAG_ANNOTATION_ID) {\n        disableFlagName = annotation.getValue().getText();\n        KJ_IF_SOME(entry, flagSet.find(disableFlagName)) {\n          disableByFlag = true;\n          flagSet.erase(entry);\n        }\n      } else if (annotation.getId() == COMPAT_ENABLE_DATE_ANNOTATION_ID) {\n        auto parsedDate = KJ_ASSERT_NONNULL(CompatDate::parse(annotation.getValue().getText()));\n        enableDate = parsedDate;\n        enableByDate = parsedCompatDate >= parsedDate;\n      } else if (annotation.getId() == COMPAT_ENABLE_ALL_DATES_ANNOTATION_ID) {\n        enableByDate = true;\n      } else if (annotation.getId() == EXPERIMENTAl_ANNOTATION_ID) {\n        isExperimental = true;\n      } else if (annotation.getId() == IMPLIED_BY_AFTER_DATE_ANNOTATION_ID) {\n        auto value = annotation.getValue();\n        auto s = value.getStruct().getAs<workerd::ImpliedByAfterDate>();\n        auto parsedDate = KJ_ASSERT_NONNULL(CompatDate::parse(s.getDate()));\n        // This flag will be marked as enabled if the flag identified by\n        // s.getName() is enabled, but only on or after the specified date.\n        if (parsedCompatDate >= parsedDate && !disableByFlag) {\n          if (s.hasName()) {\n            impliedByVector.add(ImpliedBy{\n              .field = field,\n              .other = schema.getFieldByName(s.getName()),\n            });\n          } else if (s.hasNames()) {\n            for (auto name: s.getNames()) {\n              impliedByVector.add(ImpliedBy{\n                .field = field,\n                .other = schema.getFieldByName(name),\n              });\n            }\n          }\n        }\n      }\n    }\n    for (auto& impliedBy: impliedByVector) {\n      // We only want to add the implied by flag if it is not explicitly disabled.\n      if (!disableByFlag) {\n        impliedByList.add(kj::mv(impliedBy));\n      }\n    }\n\n    // Check for conflicts.\n    if (enableByFlag && disableByFlag) {\n      errorReporter.addError(kj::str(\"Compatibility flags are mutually contradictory: \",\n          enableFlagName, \" vs \", disableFlagName));\n    }\n    if (enableByFlag && enableByDate &&\n        dateValidation != CompatibilityDateValidation::FUTURE_FOR_TEST) {\n      // Skip this error for FUTURE_FOR_TEST since tests may need to explicitly specify flags\n      // for the default variant (which uses an old compat date) while the all-compat-flags\n      // variant enables all flags by date.\n      KJ_IF_SOME(d, enableDate) {\n        errorReporter.addError(kj::str(\"The compatibility flag \", enableFlagName,\n            \" became the default as of \", d, \" so does not need to be specified anymore.\"));\n      } else {\n        errorReporter.addError(kj::str(\"The compatibility flag \", enableFlagName,\n            \" is the default, so does not need to be specified anymore.\"));\n      }\n    }\n    if (disableByFlag && !enableByDate) {\n      // We don't consider it an error to specify a disable flag when the compatibility date makes\n      // it redundant, because at a future date it won't be redundant, and someone could want to\n      // set the flag early to make sure they don't forget later.\n    }\n    if (enableByFlag && isExperimental && !allowExperimentalFeatures) {\n      if (dateValidation == CompatibilityDateValidation::CURRENT_DATE_FOR_CLOUDFLARE) {\n        errorReporter.addError(kj::str(\"The compatibility flag \", enableFlagName,\n            \" is experimental and cannot yet be used in Workers deployed to Cloudflare.\"));\n      } else {\n        errorReporter.addError(kj::str(\"The compatibility flag \", enableFlagName,\n            \" is experimental and may break or be \"\n            \"removed in a future version of workerd. To use this flag, you must pass --experimental \"\n            \"on the command line.\"));\n      }\n    }\n\n    dynamicOutput.set(field, enableByFlag || (enableByDate && !disableByFlag));\n  }\n\n  for (auto& implied: impliedByList) {\n    if (capnp::toDynamic(output).get(implied.other).as<bool>()) {\n      dynamicOutput.set(implied.field, true);\n    }\n  }\n\n  for (auto& flag: flagSet) {\n    errorReporter.addError(kj::str(\"No such compatibility flag: \", flag));\n  }\n}\n\nvoid compileCompatibilityFlags(kj::StringPtr compatDate,\n    capnp::List<capnp::Text>::Reader compatFlags,\n    CompatibilityFlags::Builder output,\n    Worker::ValidationErrorReporter& errorReporter,\n    bool allowExperimentalFeatures,\n    CompatibilityDateValidation dateValidation) {\n  kj::HashSet<kj::String> flagSet;\n  flagSet.reserve(compatFlags.size());\n  for (auto flag: compatFlags) {\n    flagSet.upsert(kj::str(flag), [&](auto& existing, auto&& newValue) {\n      errorReporter.addError(kj::str(\"Compatibility flag specified multiple times: \", flag));\n    });\n  }\n\n  return compileCompatibilityFlags(compatDate, kj::mv(flagSet), output, errorReporter,\n      allowExperimentalFeatures, dateValidation);\n}\n\nvoid compileCompatibilityFlags(kj::StringPtr compatDate,\n    kj::ArrayPtr<const kj::String> compatFlags,\n    CompatibilityFlags::Builder output,\n    Worker::ValidationErrorReporter& errorReporter,\n    bool allowExperimentalFeatures,\n    CompatibilityDateValidation dateValidation) {\n  kj::HashSet<kj::String> flagSet;\n  flagSet.reserve(compatFlags.size());\n  for (auto& flag: compatFlags) {\n    flagSet.upsert(kj::str(flag), [&](auto& existing, auto&& newValue) {\n      errorReporter.addError(kj::str(\"Compatibility flag specified multiple times: \", flag));\n    });\n  }\n\n  return compileCompatibilityFlags(compatDate, kj::mv(flagSet), output, errorReporter,\n      allowExperimentalFeatures, dateValidation);\n}\n\nnamespace {\n\nstruct ParsedField {\n  kj::StringPtr enableFlag;\n  capnp::StructSchema::Field field;\n};\n\nkj::Array<const ParsedField> makeFieldTable(capnp::StructSchema::FieldList fields) {\n  kj::Vector<ParsedField> table(fields.size());\n\n  for (auto field: fields) {\n    kj::Maybe<kj::StringPtr> enableFlag;\n    bool neededByFl = false;\n\n    for (auto annotation: field.getProto().getAnnotations()) {\n      if (annotation.getId() == COMPAT_ENABLE_FLAG_ANNOTATION_ID) {\n        enableFlag = annotation.getValue().getText();\n      } else if (annotation.getId() == NEEDED_BY_FL) {\n        neededByFl = true;\n      }\n    }\n\n    if (neededByFl) {\n      table.add(ParsedField{\n        .enableFlag = KJ_REQUIRE_NONNULL(enableFlag),\n        .field = field,\n      });\n    }\n  }\n\n  return table.releaseAsArray();\n}\n\n}  // namespace\n\nkj::Array<kj::StringPtr> decompileCompatibilityFlagsForFl(CompatibilityFlags::Reader input) {\n  static const auto fieldTable =\n      makeFieldTable(capnp::Schema::from<CompatibilityFlags>().getFields());\n\n  kj::Vector<kj::StringPtr> enableFlags;\n  enableFlags.reserve(fieldTable.size());\n  for (auto field: fieldTable) {\n    if (capnp::toDynamic(input).get(field.field).as<bool>()) {\n      enableFlags.add(field.enableFlag);\n    }\n  }\n\n  return enableFlags.releaseAsArray();\n}\n\nkj::Maybe<kj::String> normalizeCompatDate(kj::StringPtr date) {\n  return CompatDate::parse(date).map([](auto v) { return v.toString(); });\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/compatibility-date.capnp",
    "content": "# Copyright (c) 2017-2022 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n@0x8b3d4aaa36221ec8;\n\nusing Cxx = import \"/capnp/c++.capnp\";\n$Cxx.namespace(\"workerd\");\n$Cxx.allowCancellation;\n\nstruct ImpliedByAfterDate @0x8f8c1b68151b6cff {\n  # Annotates a compatibility flag to indicate that it is implied by the enablement\n  # of the named flag(s) after the specified date.\n  union {\n    name @0 :Text;\n    names @2 :List(Text);\n  }\n  date @1 :Text;\n}\n\nstruct CompatibilityFlags @0x8f8c1b68151b6cef {\n  # Flags that change the basic behavior of the runtime API, especially for\n  # backwards-compatibility with old bugs.\n  #\n  # Note: At one time, this was called \"FeatureFlags\", and many places in the codebase still call\n  #   it that. We could do a mass-rename but there's some tricky spots involving JSON\n  #   communications with other systems... I'm leaving it for now.\n\n  annotation compatEnableFlag @0xb6dabbc87cd1b03e (field) :Text;\n  annotation compatDisableFlag @0xd145cf1adc42577c (field) :Text;\n  # Compatibility flag names which enable or disable this feature, overriding what the worker's\n  # compatibility date would otherwise set.\n  #\n  # An enable-flag is used to enable the feature before it becomes the default, probably for\n  # testing purposes. All features probably should have an enable-flag defined first, then get\n  # a date assigned once testing is done.\n  #\n  # A disable-flag is used when a worker needs to keep long-term backwards compatibility with one\n  # bug but doesn't want to hold back everything else. This is hopefully rare! Most features\n  # should have a disable-flag defined.\n\n  annotation compatEnableDate @0x91a5d5d7244cf6d0 (field) :Text;\n  # The compatibility date (date string, like \"2021-05-17\") after which this flag should always\n  # be enabled.\n\n  annotation compatEnableAllDates @0x9a1d37c8030d9418 (field) :Void;\n  # All compatibility dates should start using the flag as enabled.\n  # NOTE: This is almost NEVER what you actually want because you're most likely breaking back\n  # compat. Note that workers uploaded with the flag will fail validation, so this will break\n  # uploads for anyone still using the flag.\n  #\n  # However, this is useful in some cases where the back compat you're breaking is for\n  # pre-release users who you've communicated this in advance to. Turning this on for a field\n  # will also cause test failures in compatibility-date-test.c++ and validation-test.ekam-rule\n  # because the default set of flags that are enabled changes from being the empty set to including\n  # this flag (at some point presumably it also means you're removing the compat flags at some point\n  # and you'll have to repeat this exercise).\n\n  annotation neededByFl @0xbd23aff9deefc308 (field) :Void;\n  # A tag to tell us which fields we'll need to propagate to FL on subrequests and responses.\n  #\n  # (\"FL\" refers to Cloudflare's HTTP proxy stack which is used for all outbound requests. Except\n  # for `brotliContentEncoding`, flags with this annotation have no effect when `workerd` is used\n  # outside of Cloudflare.)\n\n  annotation experimental @0xe3e5a63e76284d88 (field):Void;\n  # Flags with this annotation can only be used when workerd is run with the --experimental flag.\n  # These flags may be subject to change or even removal in the future with no warning -- they are\n  # not covered by Workers' usual backwards-compatibility promise. Experimental flags cannot be\n  # used in Workers deployed on Cloudflare except by test accounts belonging to Cloudflare team\n  # members.\n\n  annotation impliedByAfterDate @0xe3e5a63e76284d89 (field) :ImpliedByAfterDate;\n\n  annotation pythonSnapshotRelease @0xef74c0cc5d18cc0c (field) :Void;\n  # This annotation marks a compat flag as introducing a potentially breaking change to Python\n  # memory snapshots. See the doc comment for the `PythonSnapshotRelease` struct above for more\n  # details.\n\n  formDataParserSupportsFiles @0 :Bool\n      $compatEnableFlag(\"formdata_parser_supports_files\")\n      $compatEnableDate(\"2021-11-03\")\n      $compatDisableFlag(\"formdata_parser_converts_files_to_strings\");\n  # Our original implementations of FormData made the mistake of turning files into strings.\n  # We hadn't implemented `File` yet, and we forgot.\n\n  fetchRefusesUnknownProtocols @1 :Bool\n      $compatEnableFlag(\"fetch_refuses_unknown_protocols\")\n      $compatEnableDate(\"2021-11-10\")\n      $compatDisableFlag(\"fetch_treats_unknown_protocols_as_http\");\n  # Our original implementation of fetch() incorrectly accepted URLs with any scheme (protocol).\n  # It happily sent any scheme to FL in the `X-Forwarded-Proto` header. FL would ignore any\n  # scheme it didn't recgonize, which effectively meant it treated all schemes other than `https`\n  # as `http`.\n\n  esiIncludeIsVoidTag @2 :Bool\n      $compatEnableFlag(\"html_rewriter_treats_esi_include_as_void_tag\");\n  # Our original implementation of `esi:include` treated it as needing an end tag.\n  # We're worried that fixing this could break existing workers.\n\n  obsolete3 @3 :Bool;\n\n  durableObjectFetchRequiresSchemeAuthority @4 :Bool\n      $compatEnableFlag(\"durable_object_fetch_requires_full_url\")\n      $compatEnableDate(\"2021-11-10\")\n      $compatDisableFlag(\"durable_object_fetch_allows_relative_url\");\n  # Our original implementation allowed URLs without schema and/or authority components and\n  # interpreted them relative to \"https://fake-host/\".\n\n  streamsByobReaderDetachesBuffer @5 :Bool\n      $compatEnableFlag(\"streams_byob_reader_detaches_buffer\")\n      $compatEnableDate(\"2021-11-10\")\n      $compatDisableFlag(\"streams_byob_reader_does_not_detach_buffer\");\n  # The streams specification dictates that ArrayBufferViews that are passed in to the\n  # read() operation of a ReadableStreamBYOBReader are detached, making it impossible\n  # for user code to modify or observe the data as it is being read. Our original\n  # implementation did not do that.\n\n  streamsJavaScriptControllers @6 :Bool\n      $compatEnableFlag(\"streams_enable_constructors\")\n      $compatEnableDate(\"2022-11-30\")\n      $compatDisableFlag(\"streams_disable_constructors\");\n  # Controls the availability of the work in progress new ReadableStream() and\n  # new WritableStream() constructors backed by JavaScript underlying sources\n  # and sinks.\n\n  jsgPropertyOnPrototypeTemplate @7 :Bool\n      $compatEnableFlag(\"workers_api_getters_setters_on_prototype\")\n      $compatEnableDate(\"2022-01-31\")\n      $compatDisableFlag(\"workers_api_getters_setters_on_instance\");\n  # Originally, JSG_PROPERTY registered getter/setters on an objects *instance*\n  # template as opposed to its prototype template. This broke subclassing at\n  # the JavaScript layer, preventing a subclass from correctly overriding the\n  # superclasses getters/setters. This flag controls the breaking change made\n  # to set those getters/setters on the prototype template instead.\n\n  minimalSubrequests @8 :Bool\n      $compatEnableFlag(\"minimal_subrequests\")\n      $compatEnableDate(\"2022-04-05\")\n      $compatDisableFlag(\"no_minimal_subrequests\")\n      $neededByFl;\n  # Turns off a bunch of redundant features for outgoing subrequests from Cloudflare. Historically,\n  # a number of standard Cloudflare CDN features unrelated to Workers would sometimes run on\n  # Workers subrequests despite not making sense there. For example, Cloudflare might automatically\n  # apply gzip compression when the origin server responds without it but the client supports it.\n  # This doesn't make sense to do when the response is going to be consumed by Workers, since the\n  # Workers Runtime is typically running on the same machine. When Workers itself returns a\n  # response to the client, Cloudflare's stack will have another chance to apply gzip, and it\n  # makes much more sense to do there.\n\n  noCotsOnExternalFetch @9 :Bool\n      $compatEnableFlag(\"no_cots_on_external_fetch\")\n      $compatEnableDate(\"2022-03-08\")\n      $compatDisableFlag(\"cots_on_external_fetch\")\n      $neededByFl;\n  # Tells FL not to use the Custom Origin Trust Store for grey-cloud / external subrequests. Fixes\n  # EW-5299.\n\n  specCompliantUrl @10 :Bool\n      $compatEnableFlag(\"url_standard\")\n      $compatEnableDate(\"2022-10-31\")\n      $compatDisableFlag(\"url_original\");\n  # The original URL implementation based on kj::Url is not compliant with the\n  # WHATWG URL Standard, leading to a number of issues reported by users. Unfortunately,\n  # making it spec compliant is a breaking change. This flag controls the availability\n  # of the new spec-compliant URL implementation.\n\n  globalNavigator @11 :Bool\n      $compatEnableFlag(\"global_navigator\")\n      $compatEnableDate(\"2022-03-21\")\n      $compatDisableFlag(\"no_global_navigator\");\n\n  captureThrowsAsRejections @12 :Bool\n      $compatEnableFlag(\"capture_async_api_throws\")\n      $compatEnableDate(\"2022-10-31\")\n      $compatDisableFlag(\"do_not_capture_async_api_throws\");\n  # Many worker APIs that return JavaScript promises currently throw synchronous errors\n  # when exceptions occur. Per the Web Platform API specs, async functions should never\n  # throw synchronously. This flag changes the behavior so that async functions return\n  # rejections instead of throwing.\n\n  r2PublicBetaApi @13 :Bool\n      $compatEnableFlag(\"r2_public_beta_bindings\")\n      $compatDisableFlag(\"r2_internal_beta_bindings\")\n      $compatEnableAllDates;\n  # R2 public beta bindings are the default.\n  # R2 internal beta bindings is back-compat.\n\n  obsolete14 @14 :Bool\n      $compatEnableFlag(\"durable_object_alarms\");\n  # Used to gate access to durable object alarms to authorized workers, no longer used (alarms\n  # are now enabled for everyone).\n\n  noSubstituteNull @15 :Bool\n      $compatEnableFlag(\"dont_substitute_null_on_type_error\")\n      $compatEnableDate(\"2022-06-01\")\n      $compatDisableFlag(\"substitute_null_on_type_error\");\n  # There is a bug in the original implementation of the kj::Maybe<T> type wrapper\n  # that had it inappropriately interpret a nullptr result as acceptable as opposed\n  # to it being a type error. Enabling this flag switches on the correct behavior.\n  # Disabling the flag switches back to the original broken behavior.\n\n  transformStreamJavaScriptControllers @16 :Bool\n      $compatEnableFlag(\"transformstream_enable_standard_constructor\")\n      $compatEnableDate(\"2022-11-30\")\n      $compatDisableFlag(\"transformstream_disable_standard_constructor\");\n  # Controls whether the TransformStream constructor conforms to the stream standard or not.\n  # Must be used in combination with the streamsJavaScriptControllers flag.\n\n  r2ListHonorIncludeFields @17 :Bool\n      $compatEnableFlag(\"r2_list_honor_include\")\n      $compatEnableDate(\"2022-08-04\");\n  # Controls if R2 bucket.list honors the `include` field as intended. It previously didn't\n  # and by default would result in http & custom metadata for an object always being returned\n  # in the list.\n\n  exportCommonJsDefaultNamespace @18 :Bool\n      $compatEnableFlag(\"export_commonjs_default\")\n      $compatEnableDate(\"2022-10-31\")\n      $compatDisableFlag(\"export_commonjs_namespace\");\n  # Unfortunately, when the CommonJsModule type was implemented, it mistakenly exported the\n  # module namespace (an object like `{default: module.exports}`) rather than exporting only\n  # the module.exports. When this flag is enabled, the export is fixed.\n\n  obsolete19 @19 :Bool\n      $compatEnableFlag(\"durable_object_rename\")\n      $experimental;\n  # Obsolete flag. Has no effect.\n\n  webSocketCompression @20 :Bool\n      $compatEnableFlag(\"web_socket_compression\")\n      $compatEnableDate(\"2023-08-15\")\n      $compatDisableFlag(\"no_web_socket_compression\");\n  # Enables WebSocket compression. Without this flag, all attempts to negotiate compression will\n  # be refused for scripts prior to the compat date, so WebSockets will never use compression.\n  # With this flag, the system will automatically negotiate the use of the permessage-deflate\n  # extension where appropriate. The Worker can also request specific compression settings by\n  # specifying a valid Sec-WebSocket-Extensions header, or setting the header to the empty string\n  # to explicitly request that no compression be used.\n\n  nodeJsCompat @21 :Bool\n      $compatEnableFlag(\"nodejs_compat\")\n      $compatDisableFlag(\"no_nodejs_compat\");\n  # Enables nodejs compat imports in the application.\n\n  obsolete22 @22 :Bool\n      $compatEnableFlag(\"tcp_sockets_support\");\n  # Used to enables TCP sockets in workerd.\n\n  specCompliantResponseRedirect @23 :Bool\n      $compatEnableDate(\"2023-03-14\")\n      $compatEnableFlag(\"response_redirect_url_standard\")\n      $compatDisableFlag(\"response_redirect_url_original\");\n  # The original URL implementation based on kj::Url is not compliant with the\n  # WHATWG URL Standard, leading to a number of issues reported by users. Unfortunately,\n  # the specCompliantUrl flag did not contemplate the redirect usage. This flag is\n  # specifically about the usage in a redirect().\n\n  workerdExperimental @24 :Bool\n      $compatEnableFlag(\"experimental\")\n      $experimental;\n  # Experimental, do not use.\n  # This is a catch-all compatibility flag for experimental development within workerd\n  # that is not covered by another more-specific compatibility flag. It is the intention\n  # of this flag to always have the $experimental attribute.\n  # This is intended to guard new features that do not introduce any backwards-compatibility\n  # concerns (e.g. they only add a new API), and therefore do not need a compat flag of their own\n  # in the long term, but where we still want to guard access to the feature while it is in\n  # development. Don't use this for backwards-incompatible changes; give them their own flag.\n  # WARNING: Any feature blocked by this flag is subject to change at any time, including\n  # removal. Do not ignore this warning.\n\n  durableObjectGetExisting @25 :Bool\n      $compatEnableFlag(\"durable_object_get_existing\")\n      $experimental;\n  # Experimental, allows getting a durable object stub that ensures the object already exists.\n  # This is currently a work in progress mechanism that is not yet available for use in workerd.\n\n  httpHeadersGetSetCookie @26 :Bool\n      $compatEnableFlag(\"http_headers_getsetcookie\")\n      $compatDisableFlag(\"no_http_headers_getsetcookie\")\n      $compatEnableDate(\"2023-03-01\");\n  # Enables the new headers.getSetCookie() API and the corresponding changes in behavior for\n  # the Header objects keys() and entries() iterators.\n\n  dispatchExceptionTunneling @27 :Bool\n      $compatEnableDate(\"2023-03-01\")\n      $compatEnableFlag(\"dynamic_dispatch_tunnel_exceptions\")\n      $compatDisableFlag(\"dynamic_dispatch_treat_exceptions_as_500\");\n  # Enables the tunneling of exceptions from a dynamic dispatch callee back into the caller.\n  # Previously any uncaught exception in the callee would be returned to the caller as an empty\n  # HTTP 500 response.\n\n  serviceBindingExtraHandlers @28 :Bool\n      $compatEnableFlag(\"service_binding_extra_handlers\")\n      $experimental;\n  # Allows service bindings to call additional event handler methods on the target Worker.\n  # Initially only includes support for calling the queue() handler.\n  # WARNING: this flag exposes the V8 deserialiser to users via `Fetcher#queue()` `serializedBody`.\n  # Historically, this has required a trusted environment to be safe. If we decide to make this\n  # flag non-experimental, we must ensure we take appropriate precuations.\n\n  noCfBotManagementDefault @29 :Bool\n      $compatEnableFlag(\"no_cf_botmanagement_default\")\n      $compatDisableFlag(\"cf_botmanagement_default\")\n      $compatEnableDate(\"2023-08-01\");\n  # This one operates a bit backwards. With the flag *enabled* no default cfBotManagement\n  # data will be included. The the flag *disable*, default cfBotManagement data will be\n  # included in the request.cf if the field is not present.\n\n  urlSearchParamsDeleteHasValueArg @30 :Bool\n      $compatEnableFlag(\"urlsearchparams_delete_has_value_arg\")\n      $compatDisableFlag(\"no_urlsearchparams_delete_has_value_arg\")\n      $compatEnableDate(\"2023-07-01\");\n  # When enabled, the delete() and has() methods of the standard URLSearchParams object\n  # (see url-standard.h) will have the recently added second value argument enabled.\n\n  strictCompression @31 :Bool\n      $compatEnableFlag(\"strict_compression_checks\")\n      $compatDisableFlag(\"no_strict_compression_checks\")\n      $compatEnableDate(\"2023-08-01\");\n  # Perform additional error checking in the Web Compression API and throw an error if a\n  # DecompressionStream has trailing data or gets closed before the full compressed data has been\n  # provided.\n\n  brotliContentEncoding @32 :Bool\n      $compatEnableFlag(\"brotli_content_encoding\")\n      $compatEnableDate(\"2024-04-29\")\n      $compatDisableFlag(\"no_brotli_content_encoding\")\n      $neededByFl;\n  # Enables compression/decompression support for the brotli compression algorithm.\n  # With the flag enabled workerd will support the \"br\" content encoding in the Request and\n  # Response APIs and compress or decompress data accordingly as with gzip.\n\n  strictCrypto @33 :Bool\n      $compatEnableFlag(\"strict_crypto_checks\")\n      $compatDisableFlag(\"no_strict_crypto_checks\")\n      $compatEnableDate(\"2023-08-01\");\n  # Perform additional error checking in the Web Crypto API to conform with the specification as\n  # well as reject key parameters that may be unsafe based on key length or public exponent.\n\n  rttiApi @34 :Bool\n      $compatEnableFlag(\"rtti_api\")\n      $experimental;\n  # Enables the `workerd:rtti` module for querying runtime-type-information from JavaScript.\n\n  obsolete35 @35 :Bool\n      $compatEnableFlag(\"webgpu\")\n      $experimental;\n  # The experimental webgpu API was removed.\n\n  cryptoPreservePublicExponent @36 :Bool\n      $compatEnableFlag(\"crypto_preserve_public_exponent\")\n      $compatDisableFlag(\"no_crypto_preserve_public_exponent\")\n      $compatEnableDate(\"2023-12-01\");\n  # In the WebCrypto API, the `publicExponent` field of the algorithm of RSA keys would previously\n  # be an ArrayBuffer. Using this flag, publicExponent is a Uint8Array as mandated by the\n  # specification.\n\n  vectorizeQueryMetadataOptional @37 :Bool\n      $compatEnableFlag(\"vectorize_query_metadata_optional\")\n      $compatEnableDate(\"2023-11-08\")\n      $compatDisableFlag(\"vectorize_query_original\");\n  # Vectorize query option change to allow returning of metadata to be optional. Accompanying this:\n  # a return format change to move away from a nested object with the VectorizeVector.\n\n  unsafeModule @38 :Bool\n      $compatEnableFlag(\"unsafe_module\")\n      $experimental;\n  # Enables the `workerd:unsafe` module for performing dangerous operations from JavaScript.\n  # Intended for local development and testing use cases. Currently just supports aborting all\n  # Durable Objects running in a `workerd` process.\n\n  jsRpc @39 :Bool\n      $compatEnableFlag(\"js_rpc\")\n      $experimental;\n  # Enables JS RPC on the server side for Durable Object classes that do not explicitly extend\n  # `DurableObjects`.\n  #\n  # This flag is obsolete but supported temporarily to avoid breaking people who used it. All code\n  # should switch to using `extends DurableObject` as the way to enable RPC.\n  #\n  # As of this writing, it is still necessary to enable the general `experimental` flag to use RPC\n  # on both the client and server sides.\n\n  noImportScripts @40 :Bool\n      $compatEnableFlag(\"no_global_importscripts\")\n      $compatDisableFlag(\"global_importscripts\")\n      $compatEnableDate(\"2024-03-04\");\n  # Removes the non-implemented importScripts() function from the global scope.\n\n  nodeJsAls @41 :Bool\n      $compatEnableFlag(\"nodejs_als\")\n      $compatDisableFlag(\"no_nodejs_als\");\n  # Enables the availability of the Node.js AsyncLocalStorage API independently of the full\n  # node.js compatibility option.\n\n  queuesJsonMessages @42 :Bool\n      $compatEnableFlag(\"queues_json_messages\")\n      $compatDisableFlag(\"no_queues_json_messages\")\n      $compatEnableDate(\"2024-03-18\");\n  # Queues bindings serialize messages to JSON format by default (the previous default was v8 format)\n\n  pythonWorkers @43 :Bool\n      $compatEnableFlag(\"python_workers\")\n      $pythonSnapshotRelease\n      $impliedByAfterDate(name = \"pythonWorkersDevPyodide\", date = \"2000-01-01\");\n  # Enables Python Workers. Access to this flag is not restricted, instead bundles containing\n  # Python modules are restricted in EWC.\n  #\n  # WARNING: Python Workers are still an experimental feature and thus subject to change.\n\n  fetcherNoGetPutDelete @44 :Bool\n      $compatEnableFlag(\"fetcher_no_get_put_delete\")\n      $compatDisableFlag(\"fetcher_has_get_put_delete\")\n      $compatEnableDate(\"2024-03-26\");\n  # Historically, the `Fetcher` type -- which is the type of Service Bindings, and also the parent\n  # type of Durable Object stubs -- had special methods `get()`, `put()`, and `delete()`, which\n  # were shortcuts for calling `fetch()` with the corresponding HTTP method. These methods were\n  # never documented.\n  #\n  # To make room for people to define their own RPC methods with these names, this compat flag\n  # makes them no longer defined.\n\n  unwrapCustomThenables @45 :Bool\n      $compatEnableFlag(\"unwrap_custom_thenables\")\n      $compatDisableFlag(\"no_unwrap_custom_thenables\")\n      $compatEnableDate(\"2024-04-01\");\n\n  fetcherRpc @46 :Bool\n      $compatEnableFlag(\"rpc\")\n      $compatDisableFlag(\"no_rpc\")\n      $compatEnableDate(\"2024-04-03\");\n  # Whether the type `Fetcher` type -- which is the type of Service Bindings, and also the parent\n  # type of Durable Object stubs -- support RPC. If so, this type will have a wildcard method, so\n  # it will appear that all possible property names are present on any fetcher instance. This could\n  # break code that tries to infer types based on the presence or absence of methods.\n\n  internalStreamByobReturn @47 :Bool\n      $compatEnableFlag(\"internal_stream_byob_return_view\")\n      $compatDisableFlag(\"internal_stream_byob_return_undefined\")\n      $compatEnableDate(\"2024-05-13\");\n  # Sadly, the original implementation of ReadableStream (now called \"internal\" streams), did not\n  # properly implement the result of ReadableStreamBYOBReader's read method. When done = true,\n  # per the spec, the result `value` must be an empty ArrayBufferView whose underlying ArrayBuffer\n  # is the same as the one passed to the read method. Our original implementation returned\n  # undefined instead. This flag changes the behavior to match the spec and to match the behavior\n  # implemented by the JS-backed ReadableStream implementation.\n\n  blobStandardMimeType @48 :Bool\n      $compatEnableFlag(\"blob_standard_mime_type\")\n      $compatDisableFlag(\"blob_legacy_mime_type\")\n      $compatEnableDate(\"2024-06-03\");\n  # The original implementation of the Blob mime type normalization when extracting a blob\n  # from the Request or Response body is not compliant with the standard. Unfortunately,\n  # making it compliant is a breaking change. This flag controls the availability of the\n  # new spec-compliant Blob mime type normalization.\n\n  fetchStandardUrl @49 :Bool\n    $compatEnableFlag(\"fetch_standard_url\")\n    $compatDisableFlag(\"fetch_legacy_url\")\n    $compatEnableDate(\"2024-06-03\");\n  # Ensures that WHATWG standard URL parsing is used in the fetch API implementation.\n\n  nodeJsCompatV2 @50 :Bool\n      $compatEnableFlag(\"nodejs_compat_v2\")\n      $compatDisableFlag(\"no_nodejs_compat_v2\")\n      $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2024-09-23\");\n  # Implies nodeJSCompat with the following additional modifications:\n  # * Node.js Compat built-ins may be imported/required with or without the node: prefix\n  # * Node.js Compat the globals Buffer and process are available everywhere\n\n  globalFetchStrictlyPublic @51 :Bool\n      $compatEnableFlag(\"global_fetch_strictly_public\")\n      $compatDisableFlag(\"global_fetch_private_origin\");\n  # Controls what happens when a Worker hosted on Cloudflare uses the global `fetch()` function to\n  # request a hostname that is within the Worker's own Cloudflare zone (domain).\n  #\n  # Historically, such requests would be routed to the zone's origin server, ignoring any Workers\n  # mapped to the URL and also bypassing Cloudflare security settings. This behavior made sense\n  # when Workers was first introduced as a way to rewrite requests before passing them along to\n  # the origin, and bindings didn't exist: the only way to forward the request to origin was to\n  # use global fetch(), and if it didn't bypass Workers, you'd end up looping back to the same\n  # Worker.\n  #\n  # However, this behavior has a problem: it opens the door for SSRF attacks. Imagine a Worker is\n  # designed to fetch a resource from a user-provided URL. An attacker could provide a URL that\n  # points back to the Worker's own zone, and possibly cause the Worker to fetch a resource from\n  # origin that isn't meant to be reachable by the public.\n  #\n  # Traditionally, this kind of attack is considered a bug in the application: an application that\n  # fetches untrusted URLs must verify that the URL doesn't refer to a private resource that only\n  # the application itself is meant to access. However, applications can easily get this wrong.\n  # Meanwhile, by using bindings, we can make this class of problem go away.\n  #\n  # When global_fetch_strictly_public is enabled, the global `fetch()` function (when invoked on\n  # Cloudflare Workers) will strictly route requests as if they were made on the public internet.\n  # Thus, requests to a Worker's own zone will loop back to the \"front door\" of Cloudflare and\n  # will be treated like a request from the internet, possibly even looping back to the same Worker\n  # again. If an application wishes to send requests to its origin, it must configure an \"origin\n  # binding\". An origin binding behaves like a service binding (it has a `fetch()` method) but\n  # sends requests to the zone's origin servers, bypassing Cloudflare. E.g. the Worker would write\n  # `env.ORIGIN.fetch(req)` to send a request to its origin.\n  #\n  # Note: This flag only impacts behavior on Cloudflare. It has no effect when using workerd.\n  # Under workerd, the config file can control where global `fetch()` goes by configuring the\n  # worker's `globalOutbound` implicit binding. By default, under workerd, global `fetch()` has\n  # always been configured to accept publicly-routable internet hosts only; hostnames which map\n  # to private IP addresses (as defined in e.g. RFC 1918) will be rejected. Thus, workerd has\n  # always been SSRF-safe by default.\n\n  newModuleRegistry @52 :Bool\n      $compatEnableFlag(\"new_module_registry\")\n      $compatDisableFlag(\"legacy_module_registry\")\n      $experimental;\n  # Enables of the new module registry implementation.\n\n  cacheOptionEnabled @53 :Bool\n    $compatEnableFlag(\"cache_option_enabled\")\n    $compatDisableFlag(\"cache_option_disabled\")\n    $compatEnableDate(\"2024-11-11\");\n  # Enables the use of no-store headers from requests\n\n  kvDirectBinding @54 :Bool\n      $compatEnableFlag(\"kv_direct_binding\")\n      $experimental;\n  # Enables bypassing FL by translating pipeline tunnel configuration to subpipeline.\n  # This flag is used only by the internal repo and not directly by workerd.\n\n  allowCustomPorts @55 :Bool\n      $compatEnableFlag(\"allow_custom_ports\")\n      $compatDisableFlag(\"ignore_custom_ports\")\n      $compatEnableDate(\"2024-09-02\")\n      $neededByFl;\n  # Enables fetching hosts with a custom port from workers.\n  # For orange clouded sites only standard ports are allowed (https://developers.cloudflare.com/fundamentals/reference/network-ports/#network-ports-compatible-with-cloudflares-proxy).\n  # For grey clouded sites all ports are allowed.\n\n  increaseWebsocketMessageSize @56 :Bool\n      $compatEnableFlag(\"increase_websocket_message_size\")\n      $experimental;\n  # For local development purposes only, increase the message size limit to 128MB.\n  # This is not expected ever to be made available in production, as large messages are inefficient.\n\n  internalWritableStreamAbortClearsQueue @57 :Bool\n      $compatEnableFlag(\"internal_writable_stream_abort_clears_queue\")\n      $compatDisableFlag(\"internal_writable_stream_abort_does_not_clear_queue\")\n      $compatEnableDate(\"2024-09-02\");\n  # When using the original WritableStream implementation (\"internal\" streams), the\n  # abort() operation would be handled lazily, meaning that the queue of pending writes\n  # would not be cleared until the next time the queue was processed. This behavior leads\n  # to a situtation where the stream can hang if the consumer stops consuming. When set,\n  # this flag changes the behavior to clear the queue immediately upon abort.\n\n  pythonWorkersDevPyodide @58 :Bool\n    $compatEnableFlag(\"python_workers_development\")\n    $pythonSnapshotRelease\n    $experimental;\n  # Enables Python Workers and uses the bundle from the Pyodide source directory directly. For testing only.\n  #\n  # Note that the baseline snapshot hash here refers to the one used in\n  # `baseline-from-gcs.ew-test-bin.c++`. We don't intend to ever load it in production.\n\n  nodeJsZlib @59 :Bool\n      $compatEnableFlag(\"nodejs_zlib\")\n      $compatDisableFlag(\"no_nodejs_zlib\")\n      $impliedByAfterDate(names = [\"nodeJsCompat\", \"nodeJsCompatV2\"], date = \"2024-09-23\");\n  # Enables node:zlib implementation while it is in-development.\n  # Once the node:zlib implementation is complete, this will be automatically enabled when\n  # nodejs_compat or nodejs_compat_v2 are enabled.\n\n  replicaRouting @60 :Bool\n      $compatEnableFlag(\"replica_routing\")\n      $experimental;\n  # Enables routing to a replica on the client-side.\n  # Doesn't mean requests *will* be routed to a replica, only that they can be.\n\n  obsolete61 @61 :Bool\n      $compatEnableFlag(\"enable_d1_with_sessions_api\")\n      $experimental;\n  # Was used to enable the withSession(bookmarkOrConstraint) method that allows users\n  # to use read-replication Sessions API for D1. This is now enabled for everyone.\n\n  handleCrossRequestPromiseResolution @62 :Bool\n      $compatEnableFlag(\"handle_cross_request_promise_resolution\")\n      $compatDisableFlag(\"no_handle_cross_request_promise_resolution\")\n      $compatEnableDate(\"2024-10-14\");\n  # Historically, it has been possible to resolve a promise from an incorrect request\n  # IoContext. This leads to issues with promise continuations being scheduled to run\n  # in the wrong IoContext leading to errors and difficult to diagnose bugs. With this\n  # compatibility flag we arrange to have such promise continuations scheduled to run\n  # in the correct IoContext if it is still alive, or dropped on the floor with a warning\n  # if the correct IoContext is not still alive.\n  obsolete63 @63 :Bool\n      $experimental;\n\n  setToStringTag @64 :Bool\n      $compatEnableFlag(\"set_tostring_tag\")\n      $compatDisableFlag(\"do_not_set_tostring_tag\")\n      $compatEnableDate(\"2024-09-26\");\n  # A change was made that set the Symbol.toStringTag on all jsg::Objects in order to\n  # fix several spec compliance bugs. Unfortunately it turns out that was more breaking\n  # than expected. This flag restores the original behavior for compat dates before\n  # 2024-09-26\n\n  upperCaseAllHttpMethods @65 :Bool\n      $compatEnableFlag(\"upper_case_all_http_methods\")\n      $compatDisableFlag(\"no_upper_case_all_http_methods\")\n      $compatEnableDate(\"2024-10-14\");\n  # HTTP methods are expected to be upper-cased. Per the fetch spec, if the methods\n  # is specified as `get`, `post`, `put`, `delete`, `head`, or `options`, implementations\n  # are expected to uppercase the method. All other method names would generally be\n  # expected to throw as unrecognized (e.g. `patch` would be an error while `PATCH` is\n  # accepted). This is a bit restrictive, even if it is in the spec. This flag modifies\n  # the behavior to uppercase all methods prior to parsing to that the method is always\n  # recognized if it is a known method.\n\n  obsolete66 @66 :Bool\n      $compatEnableFlag(\"python_external_packages\");\n\n  noTopLevelAwaitInRequire @67 :Bool\n      $compatEnableFlag(\"disable_top_level_await_in_require\")\n      $compatDisableFlag(\"enable_top_level_await_in_require\")\n      $compatEnableDate(\"2024-12-02\");\n  # When enabled, use of top-level await syntax in require() calls will be disallowed.\n  # The ecosystem and runtimes are moving to a state where top level await in modules\n  # is being strongly discouraged.\n\n  fixupTransformStreamBackpressure @68 :Bool\n      $compatEnableFlag(\"fixup-transform-stream-backpressure\")\n      $compatDisableFlag(\"original-transform-stream-backpressure\")\n      $compatEnableDate(\"2024-12-16\");\n  # A bug in the original implementation of TransformStream failed to apply backpressure\n  # correctly. The fix, however, can break existing implementations that don't account\n  # for the bug so we need to put the fix behind a compat flag.\n\n  obsolete69 @69 :Bool\n      $compatEnableFlag(\"tail_worker_user_spans\")\n      $experimental;\n\n  cacheNoCache @70 :Bool\n      $compatEnableFlag(\"cache_no_cache_enabled\")\n      $compatDisableFlag(\"cache_no_cache_disabled\")\n      $impliedByAfterDate(name = \"cacheOptionEnabled\", date = \"2025-08-07\");\n  # Enables the use of cache: no-cache in the fetch api.\n\n  pythonWorkers20250116 @71 :Bool\n      $compatEnableFlag(\"python_workers_20250116\")\n      $compatDisableFlag(\"no_python_workers_20250116\")\n      $impliedByAfterDate(name = \"pythonWorkers\", date = \"2025-09-29\")\n      $pythonSnapshotRelease;\n\n  requestCfOverridesCacheRules @72 :Bool\n      $compatEnableFlag(\"request_cf_overrides_cache_rules\")\n      $compatDisableFlag(\"no_request_cf_overrides_cache_rules\")\n      $compatEnableDate(\"2025-04-02\")\n      $neededByFl;\n  # Enables cache settings specified request in fetch api cf object to override cache rules. (only for user owned or grey-clouded sites)\n\n  memoryCacheDelete @73 :Bool\n      $compatEnableFlag(\"memory_cache_delete\")\n      $experimental;\n  # Enables delete operations on memory cache if enabled.\n\n  obsolete74 @74: Bool\n      $compatEnableFlag(\"unique_ctx_per_invocation\")\n      $compatEnableDate(\"2025-03-10\");\n  # Creates a unique ExportedHandler for each call to `export default` thus allowing a unique ctx\n  # per invocation.\n  #\n  # OBSOLETE: We decided to apply this change even to old workers as we've found some old workers\n  #   that assumed this behavior all along, and so this change actually fixes bugs for them.\n  #   It seems unlikely to break anyone because there's no way a Worker could depend on any two\n  #   requests hitting the same isolate, so how could they depend on any two requests having the\n  #   same `ctx` object? At worst they might store some sort of cache on it which becomes\n  #   ineffective, but their Worker would still work, and anyway this would be a very weird thing\n  #   for someone to do.\n\n  reuseCtxAcrossNonclassEvents @92: Bool\n      $compatEnableFlag(\"nonclass_entrypoint_reuses_ctx_across_invocations\");\n  # Just in case someone somewhere somehow actually relied on every event receiving the same `ctx`\n  # object, this restores the original behavior. We do not recommend this.\n\n  queueConsumerNoWaitForWaitUntil @75 :Bool\n      $compatEnableFlag(\"queue_consumer_no_wait_for_wait_until\")\n      $compatDisableFlag(\"queue_consumer_wait_for_wait_until\");\n  # If enabled, does not require all waitUntil'ed promises to resolve successfully before reporting\n  # succeeded/failed messages/batches back from a queue consumer to the Queues service. This\n  # prevents a slow waitUntil'ed promise from slowing down consumption of messages from a queue,\n  # which has been a recurring problem for the prior behavior (which did wait for all waitUntil'ed\n  # tasks to complete.\n  # This intentionally doesn't have a compatEnableDate yet until so we can let some users opt-in to\n  # try it before enabling it for all new scripts, but will eventually need one.\n\n  populateProcessEnv @76 :Bool\n      $compatEnableFlag(\"nodejs_compat_populate_process_env\")\n      $compatDisableFlag(\"nodejs_compat_do_not_populate_process_env\")\n      $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2025-04-01\");\n  # Automatically populate process.env from text bindings only\n  # when nodejs_compat is being used.\n\n  cacheApiRequestCfOverridesCacheRules @77 :Bool\n      $compatEnableFlag(\"cache_api_request_cf_overrides_cache_rules\")\n      $compatDisableFlag(\"no_cache_api_request_cf_overrides_cache_rules\")\n      $compatEnableDate(\"2025-05-19\")\n      $neededByFl;\n  # Enables cache settings specified request in cache api cf object to override cache rules. (only for user owned or grey-clouded sites)\n\n  disableImportableEnv @78 :Bool\n      $compatEnableFlag(\"disallow_importable_env\")\n      $compatDisableFlag(\"allow_importable_env\");\n  # When allowed, `import { env, exports } from 'cloudflare:workers'` will provide access\n  # to the per-request environment/bindings. This flag also disables importable exports\n  # (the exports proxy) since both features are conceptually related.\n\n  assetsSecFetchModeNavigateHeaderPrefersAssetServing @79 :Bool\n      $compatEnableFlag(\"assets_navigation_prefers_asset_serving\")\n      $compatDisableFlag(\"assets_navigation_has_no_effect\")\n      $compatEnableDate(\"2025-04-01\");\n  # Enables routing to asset-worker over a user worker when an appropriate\n  # `assets.not_found_handling` configuration option is set and `Sec-Fetch-Mode: navigate` header\n  # is present. This flag is used only by @cloudflare/workers-shared (within workers-sdk) and not\n  #  directly by workerd.\n\n  cacheApiCompatFlags @80 :Bool\n      $compatEnableFlag(\"cache_api_compat_flags\")\n      $compatDisableFlag(\"no_cache_api_compat_flags\")\n      $compatEnableDate(\"2025-04-19\");\n  # when enabled, exports compability flags for FL to Cache API requests.\n\n  obsolete81 @81 :Bool\n      $compatEnableFlag(\"python_workers_durable_objects\")\n      $experimental;\n  # when enabled, enables Durable Object support for Python Workers.\n\n  obsolete82 @82 :Bool\n      $compatEnableFlag(\"streaming_tail_worker\")\n      $experimental;\n  # Obsolete flag. Has no effect.\n\n  specCompliantUrlpattern @83 :Bool\n    $compatEnableFlag(\"urlpattern_standard\")\n    $compatEnableDate(\"2025-05-01\")\n    $compatDisableFlag(\"urlpattern_original\");\n  # The original URLPattern implementation is not compliant with the\n  # WHATWG URLPattern Standard, leading to a number of issues reported by users. Unfortunately,\n  # making it spec compliant is a breaking change. This flag controls the availability\n  # of the new spec-compliant URLPattern implementation.\n\n  jsWeakRef @84 :Bool\n      $compatEnableFlag(\"enable_weak_ref\")\n      $compatEnableDate(\"2025-05-05\")\n      $compatDisableFlag(\"disable_weak_ref\");\n  # Enables WeakRefs and FinalizationRegistry API.\n  # WebAssembly based projects often rely on this API for wasm memory cleanup\n\n  requestSignalPassthrough @85 :Bool\n      $compatEnableFlag(\"request_signal_passthrough\")\n      $compatDisableFlag(\"no_request_signal_passthrough\");\n  # When enabled, the AbortSignal of the incoming request is not passed through to subrequests.\n  # As a result, outgoing subrequests will not be cancelled when the incoming request is.\n\n  enableNavigatorLanguage @86 :Bool\n      $compatEnableFlag(\"enable_navigator_language\")\n      $compatEnableDate(\"2025-05-19\")\n      $compatDisableFlag(\"disable_navigator_language\");\n  # Enables Navigator.language API.\n\n  webFileSystem @87 :Bool\n      $compatEnableFlag(\"enable_web_file_system\")\n      $experimental;\n  # Enables the experimental Web File System API.\n  # WARNING: This API is still in development and may change or be removed in the future.\n\n  abortSignalRpc @88 :Bool\n      $compatEnableFlag(\"enable_abortsignal_rpc\")\n      $experimental;\n  # Enables experimental support for passing AbortSignal over RPC.\n\n  allowEvalDuringStartup @89 :Bool\n      $compatEnableFlag(\"allow_eval_during_startup\")\n      $compatEnableDate(\"2025-06-01\")\n      $compatDisableFlag(\"disallow_eval_during_startup\");\n  # Enables eval() and new Function() during startup.\n\n  enableRequestSignal @90 :Bool\n    $compatEnableFlag(\"enable_request_signal\")\n    $compatDisableFlag(\"disable_request_signal\");\n  # Enables Request.signal for incoming requests.\n  # This feature is still experimental and the compat flag has no default enable date.\n\n  connectPassThrough @91 :Bool\n    $compatEnableFlag(\"connect_pass_through\")\n    $experimental;\n  # Causes the Worker to handle incoming connect events by simply passing them through to the\n  # Worker's globalOutbound (typically, the internet).\n  #\n  # As of this writing, Workers cannot yet receive raw socket connections, because no API has been\n  # defined for doing so. But a Worker can be configured to be the `globalOutbound` for another\n  # Worker, causing the first Worker to intercept all outbound network requests that the second\n  # Worker makes by calling `fetch()` or `connect()`. Since there's no way for the first Worker\n  # to actually handle the `connect()` requests, this implies the second Worker cannot make any\n  # raw TCP connections in this configuration. This is intended: often, the first Worker\n  # implements some sort of security rules governing what kinds of requests the second Worker can\n  # send to the internet, and if `connect()` requests were allowed to go directly to the internet,\n  # that could be used to bypass said security checks.\n  #\n  # However, sometimes outbound workers are used for reasons other than security, and in fact the\n  # outbound Worker does not really care to block `connect()` requests. Until such a time as we\n  # create an actual API for proxying connections, such outbound workers can set this compat flag\n  # to opt into allowing connect requests to pass through.\n\n  # NOTE: `reuseCtxAcrossNonclassEvents @92` was declared earlier in the file. Next ordinal is\n  #   @93.\n\n  bindAsyncLocalStorageSnapshot @93 :Bool\n      $compatEnableFlag(\"bind_asynclocalstorage_snapshot_to_request\")\n      $compatDisableFlag(\"do_not_bind_asynclocalstorage_snapshot_to-request\")\n      $compatEnableDate(\"2025-06-16\");\n  # The AsyncLocalStorage frame can capture values that are bound to the\n  # current IoContext. This is not always in the users control since we use\n  # the ALS storage frame to propagate internal trace spans as well as\n  # user-provided values. This flag, when set, binds the snapshot / bound\n  # functions to the current IoContext and will throw an error if the bound\n  # functions are called outside of the IoContext in which they were created.\n\n  throwOnUnrecognizedImportAssertion @94 :Bool\n      $compatEnableFlag(\"throw_on_unrecognized_import_assertion\")\n      $compatDisableFlag(\"ignore_unrecognized_import_assertion\")\n      $compatEnableDate(\"2025-06-16\");\n  # In the original module registry implementation, import attributes that are not recognized\n  # would be ignored. This is not compliant with the spec which strongly recommends that runtimes\n  # throw an error when unknown import attributes are encountered. In the new module registry\n  # implementation the recommended behavior is what is implemented. With this compat flag\n  # enabled, the original module registry implementation will follow the recommended behavior.\n\n  pythonWorkflows @95 :Bool\n    $compatEnableFlag(\"python_workflows\")\n    $compatDisableFlag(\"disable_python_workflows\")\n    $impliedByAfterDate(name = \"pythonWorkers\", date = \"2025-09-20\");\n  # Enables support for Python workflows.\n  # This is still in development and may change in the future.\n\n  unsupportedProcessActualPlatform @96 :Bool\n      $compatEnableFlag(\"unsupported_process_actual_platform\")\n      $experimental;\n  # By default, Workerd will always expose \"linux\" as the process.platform.\n  # This flag enables support for process.platform to expose the actual system platform.\n  # This is unsupported, as this feature will never ever be supported as non-experimental and is a\n  # temporary WPT test path only.\n\n  enableNodeJsProcessV2 @97 :Bool\n      $compatEnableFlag(\"enable_nodejs_process_v2\")\n      $compatDisableFlag(\"disable_nodejs_process_v2\")\n      $impliedByAfterDate(name = \"nodeJsCompat\", date=\"2025-09-15\");\n  # Switches from the partial process implementation with only \"nextTick\", \"env\", \"exit\",\n  # \"getBuiltinModule\", \"platform\" and \"features\" property implementations, to the full-featured\n  # Node.js-compatibile process implementation with all process properties either stubbed or\n  # implemented. It is required to use this flag with nodejs_compat (or nodejs_compat_v2).\n\n  setEventTargetThis @98 :Bool\n      $compatEnableFlag(\"set_event_target_this\")\n      $compatDisableFlag(\"no_set_event_target_this\")\n      $compatEnableDate(\"2025-08-01\");\n  # The original implementation of EventTarget was not correctly setting the `this` value\n  # for event handlers. This flag enables the correct behavior, which is compliant with the spec.\n\n  enableForwardableEmailFullHeaders @99 :Bool\n      $compatEnableFlag(\"set_forwardable_email_full_headers\")\n      $compatDisableFlag(\"set_forwardable_email_single_headers\")\n      $compatEnableDate(\"2025-08-01\");\n  # The original version of the headers sent to edgeworker were truncated to a single\n  # value for specific header names, such as To and Cc. With this compat flag we will send\n  # the full header values to the worker script.\n\n  enableNodejsHttpModules @100 :Bool\n      $compatEnableFlag(\"enable_nodejs_http_modules\")\n      $compatDisableFlag(\"disable_nodejs_http_modules\")\n      $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2025-08-15\");\n  # Enables Node.js http related modules such as node:http and node:https\n\n  pedanticWpt @101 :Bool\n      $compatEnableFlag(\"pedantic_wpt\")\n      $compatDisableFlag(\"non_pedantic_wpt\");\n  # Enables a \"pedantic mode\" for WPT compliance. Multiple changes are grouped under\n  # this flag that are known to be required to pass more web platform tests but which\n  # otherwise are likely not to be strictly necessary for most users.\n\n  exposeGlobalMessageChannel @102 :Bool\n      $compatEnableFlag(\"expose_global_message_channel\")\n      $compatDisableFlag(\"no_expose_global_message_channel\")\n      $compatEnableDate(\"2025-08-15\");\n  # Enables exposure of the MessagePort and MessageChannel classes on the global scope.\n\n  enableNodejsHttpServerModules @103 :Bool\n      $compatEnableFlag(\"enable_nodejs_http_server_modules\")\n      $compatDisableFlag(\"disable_nodejs_http_server_modules\")\n      $impliedByAfterDate(name = \"enableNodejsHttpModules\", date = \"2025-09-01\");\n  # Enables Node.js http server related modules such as node:_http_server\n  # It is required to use this flag with `enable_nodejs_http_modules` since\n  # it enables the usage of http related node.js modules, and this flag enables\n  # the methods exposed by the node.js http modules.\n  # Regarding the recommendation for using import { env, waitUntil } from 'cloudflare:workers';\n  # `disallow_importable_env` compat flag should not be set if you are using this\n  # and need access to the env since that will prevent access.\n\n  pythonNoGlobalHandlers @104 :Bool\n      $compatEnableFlag(\"python_no_global_handlers\")\n      $compatDisableFlag(\"disable_python_no_global_handlers\")\n      $compatEnableDate(\"2025-08-14\");\n  # Disables the global handlers for Python workers and enforces their use via default entrypoint\n  # classes.\n\n  enableNodeJsFsModule @105 :Bool\n    $compatEnableFlag(\"enable_nodejs_fs_module\")\n    $compatDisableFlag(\"disable_nodejs_fs_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2025-09-15\");\n  # Enables the Node.js fs module. It is required to use this flag with\n  # nodejs_compat (or nodejs_compat_v2).\n\n  enableNodeJsOsModule @106 :Bool\n    $compatEnableFlag(\"enable_nodejs_os_module\")\n    $compatDisableFlag(\"disable_nodejs_os_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2025-09-15\");\n  # Enables the Node.js os module. It is required to use this flag with\n  # nodejs_compat (or nodejs_compat_v2).\n\n  pythonWorkersForceNewVendorPath @107 :Bool\n      $compatEnableFlag(\"python_workers_force_new_vendor_path\")\n      $compatEnableDate(\"2025-08-11\");\n  # Disables adding `/session/metadata/vendor` to the Python Worker's sys.path. So Workers using\n  # this flag will have to place their vendored modules in a `python_modules` directory.\n\n  removeNodejsCompatEOL @108 :Bool\n    $compatEnableFlag(\"remove_nodejs_compat_eol\")\n    $compatDisableFlag(\"add_nodejs_compat_eol\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2025-09-01\");\n  # Removes the Node.js compatibility layer for EOL versions of Node.js.\n  # When the flag is enabled, APIs that have reached End-of-Life in Node.js\n  # will be removed for workers. When disabled, the APIs are present (but\n  # might still be non-functional stubs)\n  # This flag is intended to be a roll-up flag. That is, as additional APIs\n  # reach EOL, new compat flags will be added for those that will have\n  # `impliedByAfterDate(name = \"removeNodeJsCompatEOL\", ...` annotations.\n\n  enableWorkflowScriptValidation @109 :Bool\n      $compatEnableFlag(\"enable_validate_workflow_entrypoint\")\n      $compatDisableFlag(\"disable_validate_workflow_entrypoint\")\n      $compatEnableDate(\"2025-09-20\");\n  # This flag enables additional checks in the control plane to validate that workflows are\n  # defined and used correctly\n\n  pythonDedicatedSnapshot @110 :Bool\n      $compatEnableFlag(\"python_dedicated_snapshot\")\n      $compatDisableFlag(\"disable_python_dedicated_snapshot\")\n      $impliedByAfterDate(name = \"pythonWorkers20250116\", date = \"2025-10-16\");\n  # Enables the generation of dedicated snapshots on Python Worker upload. The snapshot will be\n  # stored inside the resulting WorkerBundle of the Worker. The snapshot will be taken after the\n  # top-level execution of the Worker.\n\n  typescriptStripTypes @111 :Bool\n    $compatEnableFlag(\"typescript_strip_types\")\n    $experimental;\n  # Strips all Typescript types from loaded files.\n  # If loaded files contain unsupported typescript construct beyond type annotations (e.g. enums),\n  # or is not a syntactically valid Typescript, the worker will fail to load.\n\n  enableNodeJsHttp2Module @112 :Bool\n    $compatEnableFlag(\"enable_nodejs_http2_module\")\n    $compatDisableFlag(\"disable_nodejs_http2_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2025-09-01\");\n  # Enables the Node.js http2 module stubs.\n\n  experimentalAllowEvalAlways @113 :Bool\n      $compatEnableFlag(\"allow_insecure_inefficient_logged_eval\")\n      $experimental;\n  # Enables eval() and new Function() always, even during request handling.\n  # ***This flag should *never* be enabled by default.***\n  # The name of the enable flag is intentionally long and scary-sounding to\n  # discourage casual use.\n  #  * \"insecure\" because code-gen during request handling can lead to security issues.\n  #  * \"inefficient\" because repeated code-gen during request handling can be slow.\n  #  * \"logged\" because each use would likely be logged in production for security\n  #    auditing so users should avoid including PII and other sensitive data in dynamically\n  #    generated and evaluated code.\n  # This flag is experimental and may be removed in the future. It is added for\n  # testing purposes.\n\n  stripAuthorizationOnCrossOriginRedirect @114 :Bool\n      $compatEnableFlag(\"strip_authorization_on_cross_origin_redirect\")\n      $compatDisableFlag(\"retain_authorization_on_cross_origin_redirect\")\n      $compatEnableDate(\"2025-09-01\");\n  # This flag specifies that when automatic redirects are enabled, and a redirect points to a URL\n  # at a different origin, if the original request contained an Authorization header, that header\n  # is removed before following the redirect. This behavior is required by the current version of\n  # the Fetch API specification.\n  #\n  # This requirement was added to the Fetch spec in 2022, well after Cloudflare Workers\n  # originally implemented it. Hence, Workers did not originally implement this requirement. This\n  # requirement is backwards-incompatible, and so the new behavior is guarded by a compatibility\n  # flag.\n  #\n  # Note that the old behavior was not inherently insecure, and indeed could be desirable in many\n  # circumstances. For example, if an API that requires authorization wishes to change its\n  # hostname, it might wish to redirect to the new hostname while having the client send along\n  # their credentials. Under the new fetch behavior, such a redirect will break clients, and this\n  # has legitimately broken real use cases. However, it's true that the old behavior could be a\n  # \"gotcha\" leading to security problems when combined with other mistakes. Hence, the spec was\n  # changed, and Workers must follow the spec.\n\n  enhancedErrorSerialization @115 :Bool\n      $compatEnableFlag(\"enhanced_error_serialization\")\n      $compatDisableFlag(\"legacy_error_serialization\")\n      $experimental;\n  # Enables enhanced error serialization for errors serialized using structuredClone /\n  # v8 serialization. More error types are supported, and own properties are included.\n  # Note that when enabled, deserialization of the errors will not preserve the original\n  # stack by default.\n\n  emailSendingQueuing @116 :Bool\n      $compatEnableFlag(\"enable_email_sending_queuing\")\n      $compatDisableFlag(\"disable_email_sending_queuing\");\n  # Enables Queuing on the `.send(message: EmailMessage)` function on send_email binding if there's\n  # a temporary error on email delivery.\n  # Note that by enabling this, user-provided Message-IDs are stripped and\n  # Email Workers will generate and use its own.\n\n  removeNodejsCompatEOLv22 @117 :Bool\n      $compatEnableFlag(\"remove_nodejs_compat_eol_v22\")\n      $compatDisableFlag(\"add_nodejs_compat_eol_v22\")\n      $impliedByAfterDate(name = \"removeNodejsCompatEOL\", date = \"2027-04-30\");\n  # Removes APIs that reached end-of-life in Node.js 22.x. When using the\n  # removeNodejsCompatEOL flag, this will default enable on/after 2027-04-30.\n\n  removeNodejsCompatEOLv23 @118 :Bool\n      $compatEnableFlag(\"remove_nodejs_compat_eol_v23\")\n      $compatDisableFlag(\"add_nodejs_compat_eol_v23\")\n      $impliedByAfterDate(name = \"removeNodejsCompatEOLv24\", date = \"2025-09-01\");\n  # Removes APIs that reached end-of-life in Node.js 23.x. This will default\n  # enable when the removeNodejsCompatEOLv24 flag is enabled after 2025-09-01.\n  # Went EOL on 2025-06-01\n\n  removeNodejsCompatEOLv24 @119 :Bool\n      $compatEnableFlag(\"remove_nodejs_compat_eol_v24\")\n      $compatDisableFlag(\"add_nodejs_compat_eol_v24\")\n      $impliedByAfterDate(name = \"removeNodejsCompatEOL\", date = \"2028-04-30\");\n  # Removes APIs that reached end-of-life in Node.js 24.x. When using the\n\t# removeNodejsCompatEOL flag, this will default enable on/after 2028-04-30.\n\n  enableNodeJsConsoleModule @120 :Bool\n    $compatEnableFlag(\"enable_nodejs_console_module\")\n    $compatDisableFlag(\"disable_nodejs_console_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2025-09-21\");\n  # Enables the Node.js console module. It is required to use this flag with\n  # nodejs_compat (or nodejs_compat_v2).\n\n  enableNodeJsVmModule @121 :Bool\n    $compatEnableFlag(\"enable_nodejs_vm_module\")\n    $compatDisableFlag(\"disable_nodejs_vm_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2025-10-01\");\n  # Enables the Node.js non-functional stub vm module. It is required to use this flag with\n  # nodejs_compat (or nodejs_compat_v2).\n\n  enableNodeJsPerfHooksModule @122 :Bool\n     $compatEnableFlag(\"enable_nodejs_perf_hooks_module\")\n     $compatDisableFlag(\"disable_nodejs_perf_hooks_module\")\n     $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2026-03-17\");\n   # Enables the Node.js perf_hooks module. It is required to use this flag with\n   # nodejs_compat (or nodejs_compat_v2). This flag also implicitly enables the\n   # global Performance classes (PerformanceEntry, PerformanceMark, etc.).\n\n  enableGlobalPerformanceClasses @123 :Bool\n     $compatEnableFlag(\"enable_global_performance_classes\")\n     $compatDisableFlag(\"disable_global_performance_classes\")\n     $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2026-03-17\");\n   # Enables PerformanceEntry, PerformanceMark, PerformanceMeasure, PerformanceResourceTiming,\n   # PerformanceObserver and PerformanceObserverEntryList global classes.\n   # These are also implicitly enabled by enableNodeJsPerfHooksModule.\n\n  enableNodeJsDomainModule @124 :Bool\n    $compatEnableFlag(\"enable_nodejs_domain_module\")\n    $compatDisableFlag(\"disable_nodejs_domain_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2025-12-04\");\n  # Enables the Node.js non-functional stub domain module. It is required to use this flag with\n  # nodejs_compat (or nodejs_compat_v2).\n\n  enableNodeJsV8Module @125 :Bool\n    $compatEnableFlag(\"enable_nodejs_v8_module\")\n    $compatDisableFlag(\"disable_nodejs_v8_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2026-03-17\");\n  # Enables the Node.js non-functional stub v8 module. It is required to use this flag with\n  # nodejs_compat (or nodejs_compat_v2).\n\n  enableNodeJsTtyModule @126 :Bool\n    $compatEnableFlag(\"enable_nodejs_tty_module\")\n    $compatDisableFlag(\"disable_nodejs_tty_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2026-03-17\");\n  # Enables the Node.js non-functional stub tty module. It is required to use this flag with\n  # nodejs_compat (or nodejs_compat_v2).\n\n  enableNodeJsPunycodeModule @127 :Bool\n    $compatEnableFlag(\"enable_nodejs_punycode_module\")\n    $compatDisableFlag(\"disable_nodejs_punycode_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2025-12-04\");\n  # Enables the Node.js deprecated punycode module. It is required to use this flag with\n  # nodejs_compat (or nodejs_compat_v2).\n\n  enableNodeJsClusterModule @128 :Bool\n    $compatEnableFlag(\"enable_nodejs_cluster_module\")\n    $compatDisableFlag(\"disable_nodejs_cluster_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2025-12-04\");\n  # Enables the Node.js non-functional stub cluster module. It is required to use this flag with\n  # nodejs_compat (or nodejs_compat_v2).\n\n  enableNodeJsChildProcessModule @129 :Bool\n    $compatEnableFlag(\"enable_nodejs_child_process_module\")\n    $compatDisableFlag(\"disable_nodejs_child_process_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2026-03-17\");\n  # Enables the Node.js non-functional stub child_process module. It is required to use this\n  # flag with nodejs_compat (or nodejs_compat_v2).\n\n  enableNodeJsWorkerThreadsModule @130 :Bool\n    $compatEnableFlag(\"enable_nodejs_worker_threads_module\")\n    $compatDisableFlag(\"disable_nodejs_worker_threads_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2026-03-17\");\n  # Enables the Node.js non-functional stub worker_threads module. It is required to use this\n  # flag with nodejs_compat (or nodejs_compat_v2).\n\n  enableNodeJsStreamWrapModule @131 :Bool\n    $compatEnableFlag(\"enable_nodejs_stream_wrap_module\")\n    $compatDisableFlag(\"disable_nodejs_stream_wrap_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2026-01-29\");\n  # Enables the Node.js non-functional stub _stream_wrap module. It is required to use this\n  # flag with nodejs_compat (or nodejs_compat_v2).\n\n  enableNodeJsWasiModule @132 :Bool\n    $compatEnableFlag(\"enable_nodejs_wasi_module\")\n    $compatDisableFlag(\"disable_nodejs_wasi_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2025-12-04\");\n  # Enables the Node.js non-functional stub wasi module. It is required to use this\n  # flag with nodejs_compat (or nodejs_compat_v2).\n\n  enableNodeJsDgramModule @133 :Bool\n    $compatEnableFlag(\"enable_nodejs_dgram_module\")\n    $compatDisableFlag(\"disable_nodejs_dgram_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2026-01-29\");\n  # Enables the Node.js non-functional stub dgram module. It is required to use this\n  # flag with nodejs_compat (or nodejs_compat_v2).\n\n  enableNodeJsInspectorModule @134 :Bool\n    $compatEnableFlag(\"enable_nodejs_inspector_module\")\n    $compatDisableFlag(\"disable_nodejs_inspector_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2026-01-29\");\n  # Enables the Node.js non-functional stub inspector module. It is required to use this\n  # flag with nodejs_compat (or nodejs_compat_v2).\n\n  enableNodeJsTraceEventsModule @135 :Bool\n    $compatEnableFlag(\"enable_nodejs_trace_events_module\")\n    $compatDisableFlag(\"disable_nodejs_trace_events_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2025-12-04\");\n  # Enables the Node.js non-functional stub trace_events module. It is required to use this\n  # flag with nodejs_compat (or nodejs_compat_v2).\n\n  enableNodeJsReadlineModule @136 :Bool\n    $compatEnableFlag(\"enable_nodejs_readline_module\")\n    $compatDisableFlag(\"disable_nodejs_readline_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2026-03-17\");\n  # Enables the Node.js non-functional stub readline module. It is required to use this\n  # flag with nodejs_compat (or nodejs_compat_v2).\n\n  enableNodeJsReplModule @137 :Bool\n    $compatEnableFlag(\"enable_nodejs_repl_module\")\n    $compatDisableFlag(\"disable_nodejs_repl_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2026-03-17\");\n  # Enables the Node.js non-functional stub repl module. It is required to use this\n  # flag with nodejs_compat (or nodejs_compat_v2).\n\n  enableNodeJsSqliteModule @138 :Bool\n    $compatEnableFlag(\"enable_nodejs_sqlite_module\")\n    $compatDisableFlag(\"disable_nodejs_sqlite_module\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2026-01-29\");\n  # Enables the Node.js non-functional stub sqlite module. It is required to use this\n  # flag with nodejs_compat (or nodejs_compat_v2).\n\n  enableCtxExports @139 :Bool\n    $compatEnableFlag(\"enable_ctx_exports\")\n    $compatDisableFlag(\"disable_ctx_exports\")\n    $compatEnableDate(\"2025-11-17\");\n  # Enable the ctx.exports API.\n\n  pythonExternalSDK @140 :Bool\n    $compatEnableFlag(\"enable_python_external_sdk\")\n    $compatDisableFlag(\"disable_python_external_sdk\")\n    $compatEnableDate(\"2026-04-21\");\n  # Don't include the Python sdk from the runtime, use a vendored copy.\n\n  fastJsgStruct @141 :Bool\n    $compatEnableFlag(\"enable_fast_jsg_struct\")\n    $compatDisableFlag(\"disable_fast_jsg_struct\")\n    $compatEnableDate(\"2025-12-03\");\n  # Enables the fast jsg::Struct optimization. With this enabled, JSG_STRUCTS\n  # will use a more efficient creation pattern that reduces construction time.\n  # However, optional fields will be explicitly set to undefined rather than\n  # being omitted, which is an observable behavior change.\n\n  cacheReload @142 :Bool\n      $compatEnableFlag(\"cache_reload_enabled\")\n      $compatDisableFlag(\"cache_reload_disabled\")\n      $experimental;\n  # Enables the use of cache: reload in the fetch api.\n\n  streamsNodejsV24Compat @143 :Bool\n    $compatEnableFlag(\"enable_streams_nodejs_v24_compat\")\n    $compatDisableFlag(\"disable_streams_nodejs_v24_compat\");\n  # Enables breaking changes to Node.js streams done with the release of Node.js v24.\n\n  pythonCheckRngState @144 : Bool\n    $compatEnableFlag(\"python_check_rng_state\")\n    $compatDisableFlag(\"disable_python_check_rng_state\")\n    $impliedByAfterDate(name = \"pythonWorkers\", date = \"2025-12-16\");\n\n  shouldSetImmutablePrototype @145 :Bool\n    $compatEnableFlag(\"immutable_api_prototypes\")\n    $compatDisableFlag(\"mutable_api_prototypes\");\n  # When set, tells JSG to make the prototype of all jsg::Objects immutable.\n  # TODO(soon): Add the default on date once the flag is verified to be\n  # generally safe.\n\n  fetchIterableTypeSupport @146 :Bool\n    $compatEnableFlag(\"fetch_iterable_type_support\")\n    $compatDisableFlag(\"no_fetch_iterable_type_support\")\n    $compatEnableDate(\"2026-02-19\");\n  # Enables passing sync and async iterables as the body of fetch Request or Response.\n  # Previously, sync iterables like Arrays would be accepted but stringified, and async\n  # iterables would be treated as regular objects and not iterated over at all. With this\n  # flag enabled, sync and async iterables will be properly iterated over and their values\n  # used as the body of the request or response.\n  # The actual compat flag enables the specific AsyncGeneratorIgnoringStrings type wrapper\n  # that allows this behavior and allows sync Generator and AsyncGenerator objects to be\n  # included in kj::OneOf declarations safely with strings and other types. When enabled,\n  # strings are ignored but Arrays will be treated as iterables and not stringified as before.\n  # Also see fetchIterableTypeSupportOverrideAdjustment.\n\n  envModuleNullableSupport @147 :Bool\n    $compatEnableFlag(\"env_module_nullable_support\")\n    $compatDisableFlag(\"no_env_module_nullable_support\");\n  # Enables support for null and undefined values in EnvModule's getCurrentEnv() and\n  # getCurrentExports() methods. When enabled, if the async context contains null or\n  # undefined values, they will be returned instead of falling through to the worker\n  # env/exports. This allows more explicit control over env and exports values in async\n  # contexts.\n\n  preciseTimers @148 :Bool\n    $compatEnableFlag(\"precise_timers\")\n    $compatDisableFlag(\"no_precise_timers\")\n    $experimental;\n  # Enables precise timers with 3ms granularity. This provides more accurate timing for performance\n  # measurements and time-sensitive operations.\n\n  fetchIterableTypeSupportOverrideAdjustment @149 :Bool\n    $compatEnableFlag(\"fetch_iterable_type_support_override_adjustment\")\n    $compatDisableFlag(\"no_fetch_iterable_type_support_override_adjustment\")\n    $impliedByAfterDate(name = \"fetchIterableTypeSupport\", date = \"2026-01-15\");\n  # Further adapts the fetch iterable type support to adjust for toString/toPrimitive\n  # overrides on sync iterable objects. Specifically, if an object passed as the body\n  # of a fetch Request or Response is sync iterable but has a custom toString or\n  # toPrimitive method, we will skip treating it as a sync iterable and instead allow\n  # it to fall through to being handled as a stringified object.\n\n  stripBomInReadAllText @150 :Bool\n    $compatEnableFlag(\"strip_bom_in_read_all_text\")\n    $compatDisableFlag(\"do_not_strip_bom_in_read_all_text\")\n    $compatEnableDate(\"2026-01-13\")\n    $impliedByAfterDate(name = \"pedanticWpt\", date = \"2026-01-13\");\n  # Instructs the readAllText method in streams to strip the leading UTF8 BOM if present.\n\n  allowIrrevocableStubStorage @151 :Bool\n    $compatEnableFlag(\"allow_irrevocable_stub_storage\")\n    $experimental;\n  # Permits various stub types (e.g. ServiceStub aka Fetcher, DurableObjectClass) to be stored in\n  # long-term Durable Object storage without any mechanism for the stub target to audit or revoke\n  # incoming connections.\n  #\n  # This feature exists for experimental use only, and will be removed once we have a properly\n  # auditable and revocable storage mechanism.\n\n  rpcParamsDupStubs @152 :Bool\n    $compatEnableFlag(\"rpc_params_dup_stubs\")\n    $compatDisableFlag(\"rpc_params_transfer_stubs\")\n    $compatEnableDate(\"2026-01-20\");\n  # Changes the ownership semantics of RPC stubs embedded in the parameters of an RPC call.\n  #\n  # When the RPC system was first introduced, RPC stubs that were embedded in the params or return\n  # value of some other call had their ownership transferred. That is, the original stub was\n  # implicitly disposed, with a duplicate stub being delivered to the destination.\n  #\n  # This turns out to compose poorly with another rule: in the callee, any stubs received in the\n  # params of a call are automatically disposed when the call returns. These two rules combine to\n  # mean that if you proxy a call -- i.e. the implementation of an RPC just makes another RPC call\n  # passing along the same params -- then any stubs in the params get disposed twice. Worse, if\n  # the eventual recipient of the stub wants to keep a duplicate past the end of the call, this\n  # may not work because the copy of the stub in the proxy layer gets disposed anyway, breaking the\n  # connection.\n  #\n  # For this reason, the pure-JS implementation of Cap'n Web switched to saying that stubs in params\n  # do NOT transfer ownership -- they are simply duplicated. This compat flag fixes the Workers\n  # Runtime built-in RPC to match Cap'n Web behavior.\n  #\n  # In particular, this fixes: https://github.com/cloudflare/capnweb/issues/110\n\n  enableNodejsGlobalTimers @153 :Bool\n    $compatEnableFlag(\"enable_nodejs_global_timers\")\n    $compatDisableFlag(\"no_nodejs_global_timers\")\n    $impliedByAfterDate(name = \"nodeJsCompat\", date = \"2026-02-10\");\n  # When enabled, setTimeout, setInterval, clearTimeout, and clearInterval\n  # are available on globalThis as Node.js-compatible versions from node:timers.\n  # setTimeout and setInterval return Timeout objects with methods like\n  # refresh(), ref(), unref(), and hasRef().\n  # This flag requires nodejs_compat or nodejs_compat_v2 to be enabled.\n\n  noAutoAllocateChunkSize @154 :Bool\n    $compatEnableFlag(\"streams_no_default_auto_allocate_chunk_size\")\n    $compatDisableFlag(\"streams_auto_allocate_chunk_size_default\")\n    $experimental;\n  # Per the WHATWG Streams spec, byte streams should only have autoAllocateChunkSize\n  # set when the user explicitly provides it. When not set, non-BYOB reads on byte\n  # streams won't have a byobRequest available in the pull() callback, and the\n  # underlying source must use controller.enqueue() to provide data instead.\n  #\n  # Our original implementation always defaulted autoAllocateChunkSize to 4096,\n  # which meant all byte stream reads behaved as BYOB reads. This flag enables\n  # the spec-compliant behavior where autoAllocateChunkSize is only set when\n  # explicitly provided by the user.\n  #\n  # Code relying on the old behavior that accesses controller.byobRequest without\n  # checking for null will break. To migrate, either:\n  # 1. Add a null check: if (controller.byobRequest) { ... }\n  # 2. Explicitly set autoAllocateChunkSize when creating the stream\n\n  pythonWorkflowsImplicitDeps @155 :Bool\n    $compatEnableFlag(\"python_workflows_implicit_dependencies\")\n    $compatDisableFlag(\"no_python_workflows_implicit_dependencies\")\n    $impliedByAfterDate(name = \"pythonWorkers\", date = \"2026-02-25\");\n  # replaces depends param on steps to an implicit approach with step callables passed as params\n  # these steps are called internally and act as dependencies\n\n  requireReturnsDefaultExport @156 :Bool\n    $compatEnableFlag(\"require_returns_default_export\")\n    $compatDisableFlag(\"require_returns_namespace\")\n    $compatEnableDate(\"2026-01-22\");\n  # When enabled, require() will return the default export of a module if it exists.\n  # If the default export does not exist, it falls back to returning a mutable\n  # copy of the module namespace object. This matches the behavior that Node.js\n  # uses for require(esm) where the default export is returned when available.\n  # This flag is useful for frameworks like Next.js that expect to patch module exports.\n  #\n  # TODO(later): Once this is no longer experimental, this flag should be implied by\n  # exportCommonJsDefaultNamespace (or vice versa) for consistency.\n\n  pythonRequestHeadersPreserveCommas @157 :Bool\n    $compatEnableFlag(\"python_request_headers_preserve_commas\")\n    $compatDisableFlag(\"disable_python_request_headers_preserve_commas\")\n    $compatEnableDate(\"2026-02-17\");\n  # Preserve commas in Python Request headers rather than treating them as separators,\n  # while still exposing multiple Set-Cookie headers as distinct values.\n\n  queueExposeErrorCodes @158 :Bool\n    $compatEnableFlag(\"queue_expose_error_codes\")\n    $compatDisableFlag(\"no_queue_expose_error_codes\")\n    $compatEnableDate(\"2026-03-12\");\n  # When enabled, queue operations will include detailed error information (error code and cause)\n\n  textDecoderReplaceSurrogates @159 :Bool\n    $compatEnableFlag(\"text_decoder_replace_surrogates\")\n    $compatDisableFlag(\"disable_text_decoder_replace_surrogates\")\n    $compatEnableDate(\"2026-02-24\")\n    $impliedByAfterDate(name = \"pedanticWpt\", date = \"2026-02-24\");\n  # When enabled, the UTF-16le TextDecoder will replace lone surrogates with U+FFFD\n  # (the Unicode replacement character) as required by the spec. Previously, lone\n  # surrogates were passed through unchanged, producing non-well-formed strings.\n\n  containersPidNamespace @160 :Bool\n    $compatEnableFlag(\"containers_pid_namespace\")\n    $compatDisableFlag(\"no_containers_pid_namespace\")\n    $compatEnableDate(\"2026-04-01\");\n  # When enabled, containers attached to Durable Objects do NOT share the host PID namespace\n  # (they get their own isolated PID namespace). When disabled (the default), containers share\n  # the host PID namespace.\n\n  deleteAllDeletesAlarm @161 :Bool\n    $compatEnableFlag(\"delete_all_deletes_alarm\")\n    $compatDisableFlag(\"delete_all_preserves_alarm\")\n    $compatEnableDate(\"2026-02-24\");\n  # When enabled, calling storage.deleteAll() on a Durable Object also deletes\n  # any scheduled alarm, in addition to deleting all stored key-value data.\n  #\n  # Previously, deleteAll() preserved the alarm state. This was surprising\n  # behavior since the intent of deleteAll() is to clear all state.\n\n  unhandledRejectionAfterMicrotaskCheckpoint @162 :Bool\n    $compatEnableFlag(\"unhandled_rejection_after_microtask_checkpoint\")\n    $compatDisableFlag(\"no_unhandled_rejection_after_microtask_checkpoint\")\n    $compatEnableDate(\"2026-03-03\");\n  # When enabled, unhandledrejection processing is deferred until the microtask\n  # checkpoint completes, avoiding misfires on multi-tick promise chains.\n\n  textDecoderCjkDecoder @163 :Bool\n    $compatEnableFlag(\"text_decoder_cjk_decoder\")\n    $compatDisableFlag(\"disable_text_decoder_cjk_decoder\")\n    $compatEnableDate(\"2026-03-03\");\n  # Enables the dedicated CJK TextDecoder implementation for overrides and\n  # Big5 lead-byte handling instead of the legacy ICU-only path.\n\n  websocketCloseReasonByteLimit @164 :Bool\n      $compatEnableFlag(\"websocket_close_reason_byte_limit\")\n      $compatDisableFlag(\"no_websocket_close_reason_byte_limit\")\n      $compatEnableDate(\"2026-03-03\");\n  # When enabled, WebSocket close() throws a SyntaxError DOMException if the\n  # reason string exceeds 123 bytes when UTF-8 encoded, as required by the\n  # WHATWG WebSocket spec and RFC 6455 Section 5.5. Previously, workerd allowed\n  # arbitrarily long close reasons without validation.\n\n  enableVersionApi @165 :Bool\n    $compatEnableFlag(\"enable_version_api\")\n    $experimental;\n  # Enables version-related APIs. This currently only enables the `version` option in loopback\n  # bindings to specify a requested version and exposes `ctx.version`. The behaviour of this flag\n  # will change in the future.\n\n  websocketBinaryTypeDefault @166 :Bool\n      $compatEnableFlag(\"websocket_standard_binary_type\")\n      $compatDisableFlag(\"no_websocket_standard_binary_type\")\n      $compatEnableDate(\"2026-03-17\");\n  # Per the WHATWG WebSocket spec, the binaryType attribute defaults to \"blob\"\n  # and binary messages are delivered as Blob objects. Previously, workerd did\n  # not expose the binaryType property at all and always delivered binary\n  # messages as ArrayBuffer. When this flag is enabled, binaryType defaults to\n  # \"blob\" and binary messages are delivered as Blob. Workers that set\n  # binaryType to \"arraybuffer\" will continue to receive ArrayBuffer regardless\n  # of this flag.\n  #\n  # Note: This flag has no effect on the Durable Object hibernatable WebSocket\n  # message handler (webSocketMessage). That handler always receives binary data\n  # as ArrayBuffer, since it operates outside the normal WebSocket read loop.\n\n  writableStreamSpecCompliantWriter @167 :Bool\n      $compatEnableFlag(\"writable_stream_spec_compliant_writer\")\n      $compatDisableFlag(\"no_writable_stream_spec_compliant_writer\")\n      $compatEnableDate(\"2026-03-24\");\n  # Fixes several WritableStream spec compliance issues around writer\n  # lock/release behavior.\n\n  encoderStreamSpecCompliantBackpressure @168 :Bool\n      $compatEnableFlag(\"encoder_stream_spec_compliant_backpressure\")\n      $compatDisableFlag(\"no_encoder_stream_spec_compliant_backpressure\")\n      $compatEnableDate(\"2026-03-24\");\n  # Fixes TextEncoderStream and TextDecoderStream to use the correct readable\n  # side high water mark of 0 (per the WHATWG Encoding spec), instead of 1.\n  # With HWM=0 the readable side starts with backpressure, so writes correctly\n  # block until a reader pulls. Previously HWM defaulted to 1, which caused\n  # pull() to fire at startup, clearing backpressure before any write.\n\n  specCompliantPropertyAttributes @169 :Bool\n      $compatEnableFlag(\"spec_compliant_property_attributes\")\n      $compatDisableFlag(\"no_spec_compliant_property_attributes\")\n      $experimental;\n  # Fixes several Web IDL compliance issues on property attributes:\n  #  - Constructor .length reflects the number of required arguments instead\n  #    of always being 0.\n  #  - Method and static method .length reflects the number of required\n  #    arguments instead of always being 0.\n  #  - Getter .length is explicitly 0 and setter .length is 1.\n  #  - Getter .name is \"get <name>\" and setter .name is \"set <name>\" per the\n  #    Web IDL spec, instead of empty strings.\n  #  - Constants gain DontDelete (non-configurable) on both the constructor\n  #    and prototype, matching { writable: false, enumerable: true,\n  #    configurable: false } per Web IDL.\n  #  - Interface objects (nested types) become own properties of globalThis\n  #    with { writable: true, enumerable: false, configurable: true }, instead\n  #    of being inherited from the prototype chain.\n\n  workflowsPreserveNonRetryableErrorMessage @170 :Bool\n      $compatEnableFlag(\"workflows_preserve_non_retryable_error_message\")\n      $experimental;\n  # When enabled, if a Workflow step throws a NonRetryableError, the error message\n  # and name are preserved on the thrown exception instead of being replaced with\n  # a generic \"NonRetryableError\" string.\n\n  webSocketAutoReplyToClose @171 :Bool\n    $compatEnableFlag(\"web_socket_auto_reply_to_close\")\n    $compatDisableFlag(\"web_socket_manual_reply_to_close\")\n    $compatEnableDate(\"2026-04-07\");\n  # When enabled, a reciprocal Close frame is automatically sent through the\n  # outgoing message pump when a server-initiated close is received, and the\n  # WebSocket readyState is CLOSED (3) when the close event fires. Previously,\n  # no close reply was sent and readyState was CLOSING (2). The WebSocket spec\n  # requires readyState to be CLOSED when the close event is dispatched.\n\n  enableCtxVersionMetadata @172 :Bool\n    $compatEnableFlag(\"enable_ctx_version_metadata\")\n    $experimental;\n  # When paired with `enable_version_api`, also exposes `ctx.version.metadata`. This is a separate\n  # flag as we haven't decided on the exact behaviour of `ctx.version.metadata`, but the rest of\n  # `ctx.version` is much more well defined. The behaviour of this flag will change in the future.\n}\n"
  },
  {
    "path": "src/workerd/io/compatibility-date.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/io/worker.h>\n\nnamespace workerd {\n\nenum class CompatibilityDateValidation {\n  // Allow dates up through the date specified by `supportedCompatibilityDate` in\n  // `compatibility-date.capnp`.\n  CODE_VERSION,\n\n  // Allow dates up to through the current date.\n  //\n  // This should ONLY be used by Cloudflare. If Cloudflare fails to deploy a compatibility flag\n  // before its default-on date passes, then the flag's default-on date needs to be pushed back.\n  CURRENT_DATE_FOR_CLOUDFLARE,\n  // TODO(someday): We may want to consider eliminating this and using CODE_VERSION even for\n  //   Cloudflare. Once people are testing their code using the open source runtime in wrangler,\n  //   they will be forced to set a date no later than `supportedCompatibilityDate` anyway. If\n  //   we make sure Cloudflare is updated before the runtime used locally by Wrangler is updated,\n  //   then there's no real need for Cloudflare to accept newer dates. This would then allow us\n  //   to avoid the situation where a release misses the deadline implied by its compat flag\n  //   default-on dates.\n\n  // Allow any future date. This should only be used to test `compileCompatibilityFlags` itself.\n  FUTURE_FOR_TEST\n};\n\nvoid compileCompatibilityFlags(kj::StringPtr compatDate,\n    capnp::List<capnp::Text>::Reader compatFlags,\n    CompatibilityFlags::Builder output,\n    Worker::ValidationErrorReporter& errorReporter,\n    bool allowExperimentalFeatures,\n    CompatibilityDateValidation dateValidation);\nvoid compileCompatibilityFlags(kj::StringPtr compatDate,\n    kj::ArrayPtr<const kj::String> compatFlags,\n    CompatibilityFlags::Builder output,\n    Worker::ValidationErrorReporter& errorReporter,\n    bool allowExperimentalFeatures,\n    CompatibilityDateValidation dateValidation);\n\n// Return an array of compatibility enable-flags which express the given FeatureFlags. The returned\n// StringPtrs point to FeatureFlags annotation parameters, which live in static storage.\nkj::Array<kj::StringPtr> decompileCompatibilityFlagsForFl(CompatibilityFlags::Reader input);\n// TODO(soon): Introduce a codec which can minimize the number of flags generated by choosing a good\n//   compatibility date.\n\n// Exposed to unit test the parser.\nkj::Maybe<kj::String> normalizeCompatDate(kj::StringPtr date);\n\n// Returns the current date as a string formatted by CompatDate.\nkj::String currentDateStr();\n\n// These values come from src/workerd/io/compatibility-date.capnp\nstatic constexpr uint64_t COMPAT_ENABLE_FLAG_ANNOTATION_ID = 0xb6dabbc87cd1b03eull;\nstatic constexpr uint64_t COMPAT_DISABLE_FLAG_ANNOTATION_ID = 0xd145cf1adc42577cull;\nstatic constexpr uint64_t COMPAT_ENABLE_DATE_ANNOTATION_ID = 0x91a5d5d7244cf6d0ull;\nstatic constexpr uint64_t COMPAT_ENABLE_ALL_DATES_ANNOTATION_ID = 0x9a1d37c8030d9418;\nstatic constexpr uint64_t EXPERIMENTAl_ANNOTATION_ID = 0xe3e5a63e76284d88;\nstatic constexpr uint64_t IMPLIED_BY_AFTER_DATE_ANNOTATION_ID = 0xe3e5a63e76284d89;\nstatic constexpr uint64_t NEEDED_BY_FL = 0xbd23aff9deefc308ull;\nstatic constexpr uint64_t PYTHON_SNAPSHOT_RELEASE_ANNOTATION_ID = 0xef74c0cc5d18cc0cull;\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/container.capnp",
    "content": "@0xcb7be0e1be835084;\n\nusing Cxx = import \"/capnp/c++.capnp\";\n$Cxx.namespace(\"workerd::rpc\");\n$Cxx.allowCancellation;\n\nusing import \"/capnp/compat/byte-stream.capnp\".ByteStream;\nusing CompatibilityFlags = import \"/workerd/io/compatibility-date.capnp\".CompatibilityFlags;\n\ninterface Container @0x9aaceefc06523bca {\n  # RPC interface to talk to a container, for containers attached to Durable Objects.\n  #\n  # When the actor shuts down, workerd will drop the `Container` capability, at which point\n  # the container engine should implicitly destroy the container.\n\n  status @0 () -> (running :Bool);\n  # Returns the container's current status. The runtime will always call this at DO startup.\n\n  start @1 StartParams -> ();\n  # Start the container. It's an error to call this if the container is already running.\n\n  struct StartParams {\n    entrypoint @0 :List(Text);\n    # Specifies the command to run as the root process of the container. If null, the container\n    # image's default command is used.\n\n    enableInternet @1 :Bool = false;\n    # Set true to enable the container to talk directly to the public internet. Otherwise, the\n    # public internet will not be accessible -- but it's still possible to intercept connection\n    # attempts and handle them in the DO, using the `listenTcp()` method below.\n\n    environmentVariables @2 :List(Text);\n    # Specifies the environment variables of the container.\n    # It will spread over the existing defined environment variables of the container image.\n    # If null, the container will start with the environment variables defined in its image.\n    # The format is defined as a list of `NAME=VALUE`.\n    # The container runtime should validate the environment variables input.\n\n    hardTimeoutMs @3 :Int64;\n    # Configures an absolute timeout that starts when the container starts and never resets.\n    # The container will be forcefully terminated when this timeout expires, regardless of activity.\n    # Unlike inactivity timeout, this is a hard deadline from container startup.\n    # If 0 (default), no hard timeout is applied.\n\n    compatibilityFlags @4 :CompatibilityFlags;\n    # Compatibility flags for this worker\n\n    labels @5 :List(Label);\n    # Optional key-value metadata labels for metrics/observability.\n  }\n\n  struct Label {\n    name @0 :Text;\n    value @1 :Text;\n  }\n\n  monitor @2 () -> (exitCode: Int32);\n  # Waits for the container to shut down.\n  #\n  # If the container shuts down because the root process exited with a success status, or because\n  # the client invoked `destroy()`, then `monitor()` completes without an error. If it shuts down\n  # for any other reason, `monitor()` throws an exception describing what happened. (This exception\n  # may or may not be a JSG exception depending on whether it is an application error or a system\n  # error.)\n\n  destroy @3 ();\n  # Immediately and abruptly stops the container and tears it down. The application is not given\n  # any warning, it simply stops immediately. Upon successful return from destroy(), the container\n  # is no longer running. If a call to `monitor()` is waiting when `destroy()` is invoked,\n  # `monitor()` will also return (with no error). If the container is not running when `destroy()`\n  # is invoked, `destroy()` silently returns with no error.\n\n  signal @4 (signo :UInt32);\n  # Sends the given Linux signal number to the root process.\n\n  getTcpPort @5 (port :UInt16) -> (port :Port);\n  # Obtains an object which can be used to connect to the application inside the container on the\n  # given TCP port (the application must be listening on this port).\n\n  interface Port {\n    # Represents a port to which connections can be made.\n\n    connect @0 (down :ByteStream) -> (up :ByteStream);\n    # Forms a raw socket connection to the port.\n    #\n    # Note that when the Durable Object application uses the HTTP-oriented APIs, workerd will\n    # take care of speaking the HTTP protocol on top of the raw socket. So, the container engine\n    # need only implement raw connections.\n  }\n\n  listenTcp @6 (filter :IpFilter, handler :TcpHandler) -> (handle :Capability);\n  # Arranges to intercept outgoing TCP connections from the container and redirect them to the\n  # given `handler`.\n\n  struct IpFilter {\n    # Specifies a range of IP addresses and/or port numbers which should be intercepted when the\n    # application in the container tries to connect to them.\n\n    addr @0 :Text;\n    # null = all addresses\n    # TODO(someday): Support CIDR? (e.g. \"192.168.0.0/16\")\n\n    port @1 :UInt16 = 0;\n    # 0 = all ports\n  }\n\n  interface TcpHandler {\n    # Interface which intercepts outgoing connections from a container.\n\n    connect @0 (addr :Text, port :UInt16, down :ByteStream) -> (up :ByteStream);\n    # Like Port.connect() but also receives the address and port number to which the container was\n    # attempting to connect.\n  }\n\n  setInactivityTimeout @7 (durationMs  :Int64);\n  # Configures the duration where the runtime should shutdown the container after there is\n  # no connections or activity to the Container.\n  #\n  # After a capability disconnect, the runtime should signal the container\n  # at the configured duration.\n  #\n  # Note that if there is an open connection to the container, the runtime must not shutdown the container.\n  # If there is no activity timeout duration configured and no container connection, it's up to the runtime\n  # to decide when to signal the container to exit.\n\n  setEgressHttp @8 (hostPort :Text, channelToken :Data);\n  # Configures egress HTTP routing for the container. When the container attempts to connect to the\n  # specified host:port, the connection should be routed back to the Workers runtime using the channel token.\n  # The format of hostPort can be '<ip|cidr|hostnameGlob>[':'<port>]'. If the host part is not an\n  # IP or CIDR, it is treated as a hostname glob.\n  # If port is omitted, it's assumed to only cover port 80.\n  # This method does not support HTTPs yet.\n\n\n  # TODO: setEgressTcp\n}\n"
  },
  {
    "path": "src/workerd/io/external-pusher.c++",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/io/external-pusher.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd {\n\n// =======================================================================================\n// ReadableStream handling\n\nnamespace {\n\n// TODO(cleanup): These classes have been copied from streams/readable.c++. The copies there can be\n//   deleted as soon as we've switched from StreamSink to ExternalPusher and can delete all the\n//   StreamSink-related code. For now I'm not trying to avoid duplication.\n\n// HACK: We need as async pipe, like kj::newOneWayPipe(), except supporting explicit end(). So we\n//   wrap the two ends of the pipe in special adapters that track whether end() was called.\nclass ExplicitEndOutputPipeAdapter final: public capnp::ExplicitEndOutputStream {\n public:\n  ExplicitEndOutputPipeAdapter(\n      kj::Own<kj::AsyncOutputStream> inner, kj::Own<kj::RefcountedWrapper<bool>> ended)\n      : inner(kj::mv(inner)),\n        ended(kj::mv(ended)) {}\n\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override {\n    return KJ_REQUIRE_NONNULL(inner)->write(buffer);\n  }\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {\n    return KJ_REQUIRE_NONNULL(inner)->write(pieces);\n  }\n\n  kj::Maybe<kj::Promise<uint64_t>> tryPumpFrom(\n      kj::AsyncInputStream& input, uint64_t amount) override {\n    return KJ_REQUIRE_NONNULL(inner)->tryPumpFrom(input, amount);\n  }\n\n  kj::Promise<void> whenWriteDisconnected() override {\n    return KJ_REQUIRE_NONNULL(inner)->whenWriteDisconnected();\n  }\n\n  kj::Promise<void> end() override {\n    // Signal to the other side that end() was actually called.\n    ended->getWrapped() = true;\n    inner = kj::none;\n    return kj::READY_NOW;\n  }\n\n private:\n  kj::Maybe<kj::Own<kj::AsyncOutputStream>> inner;\n  kj::Own<kj::RefcountedWrapper<bool>> ended;\n};\n\nclass ExplicitEndInputPipeAdapter final: public kj::AsyncInputStream {\n public:\n  ExplicitEndInputPipeAdapter(kj::Own<kj::AsyncInputStream> inner,\n      kj::Own<kj::RefcountedWrapper<bool>> ended,\n      kj::Maybe<uint64_t> expectedLength)\n      : inner(kj::mv(inner)),\n        ended(kj::mv(ended)),\n        expectedLength(expectedLength) {}\n\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    size_t result = co_await inner->tryRead(buffer, minBytes, maxBytes);\n\n    KJ_IF_SOME(l, expectedLength) {\n      KJ_ASSERT(result <= l);\n      l -= result;\n      if (l == 0) {\n        // If we got all the bytes we expected, we treat this as a successful end, because the\n        // underlying KJ pipe is not actually going to wait for the other side to drop. This is\n        // consistent with the behavior of Content-Length in HTTP anyway.\n        ended->getWrapped() = true;\n      }\n    }\n\n    if (result < minBytes) {\n      // Verify that end() was called.\n      if (!ended->getWrapped()) {\n        JSG_FAIL_REQUIRE(Error, \"ReadableStream received over RPC disconnected prematurely.\");\n      }\n    }\n    co_return result;\n  }\n\n  kj::Maybe<uint64_t> tryGetLength() override {\n    return inner->tryGetLength();\n  }\n\n  kj::Promise<uint64_t> pumpTo(kj::AsyncOutputStream& output, uint64_t amount) override {\n    return inner->pumpTo(output, amount);\n  }\n\n private:\n  kj::Own<kj::AsyncInputStream> inner;\n  kj::Own<kj::RefcountedWrapper<bool>> ended;\n  kj::Maybe<uint64_t> expectedLength;\n};\n\n}  // namespace\n\nclass ExternalPusherImpl::InputStreamImpl final: public ExternalPusher::InputStream::Server {\n public:\n  InputStreamImpl(kj::Own<kj::AsyncInputStream> stream): stream(kj::mv(stream)) {}\n\n  kj::Maybe<kj::Own<kj::AsyncInputStream>> stream;\n};\n\nkj::Promise<void> ExternalPusherImpl::pushByteStream(PushByteStreamContext context) {\n  kj::Maybe<uint64_t> expectedLength;\n  auto lp1 = context.getParams().getLengthPlusOne();\n  if (lp1 > 0) {\n    expectedLength = lp1 - 1;\n  }\n\n  auto pipe = kj::newOneWayPipe(expectedLength);\n\n  auto endedFlag = kj::refcounted<kj::RefcountedWrapper<bool>>(false);\n\n  auto out = kj::heap<ExplicitEndOutputPipeAdapter>(kj::mv(pipe.out), kj::addRef(*endedFlag));\n  auto in =\n      kj::heap<ExplicitEndInputPipeAdapter>(kj::mv(pipe.in), kj::mv(endedFlag), expectedLength);\n\n  auto results = context.initResults(capnp::MessageSize{4, 2});\n\n  results.setSource(inputStreamSet.add(kj::heap<InputStreamImpl>(kj::mv(in))));\n  results.setSink(byteStreamFactory.kjToCapnp(kj::mv(out)));\n  return kj::READY_NOW;\n}\n\nkj::Own<kj::AsyncInputStream> ExternalPusherImpl::unwrapStream(\n    ExternalPusher::InputStream::Client cap) {\n  auto& unwrapped = KJ_REQUIRE_NONNULL(\n      inputStreamSet.tryGetLocalServerSync(cap), \"pushed external is not a byte stream\");\n\n  return KJ_REQUIRE_NONNULL(kj::mv(kj::downcast<InputStreamImpl>(unwrapped).stream),\n      \"pushed byte stream has already been consumed\");\n}\n\n// =======================================================================================\n// AbortSignal handling\n\nnamespace {\n\n// The jsrpc handler that receives aborts from the remote and triggers them locally\n//\n// TODO(cleanup): This class has been copied from external-pusher.c++. The copy there can be\n//   deleted as soon as we've switched from StreamSink to ExternalPusher and can delete all the\n//   StreamSink-related code. For now I'm not trying to avoid duplication.\nclass AbortTriggerRpcServer final: public rpc::AbortTrigger::Server {\n public:\n  AbortTriggerRpcServer(kj::Own<kj::PromiseFulfiller<void>> fulfiller,\n      kj::Own<ExternalPusherImpl::PendingAbortReason>&& pendingReason)\n      : fulfiller(kj::mv(fulfiller)),\n        pendingReason(kj::mv(pendingReason)) {}\n\n  kj::Promise<void> abort(AbortContext abortCtx) override {\n    auto params = abortCtx.getParams();\n    auto reason = params.getReason().getV8Serialized();\n\n    pendingReason->getWrapped() = kj::heapArray(reason.asBytes());\n    fulfiller->fulfill();\n    return kj::READY_NOW;\n  }\n\n  kj::Promise<void> release(ReleaseContext releaseCtx) override {\n    released = true;\n    return kj::READY_NOW;\n  }\n\n  ~AbortTriggerRpcServer() noexcept(false) {\n    if (pendingReason->getWrapped() != nullptr) {\n      // Already triggered\n      return;\n    }\n\n    if (!released) {\n      pendingReason->getWrapped() = JSG_KJ_EXCEPTION(FAILED, DOMAbortError,\n          \"An AbortSignal received over RPC was implicitly aborted because the connection back to \"\n          \"its trigger was lost.\");\n    }\n\n    // Always fulfill the promise in case the AbortSignal was waiting\n    fulfiller->fulfill();\n  }\n\n private:\n  kj::Own<kj::PromiseFulfiller<void>> fulfiller;\n  kj::Own<ExternalPusherImpl::PendingAbortReason> pendingReason;\n  bool released = false;\n};\n\n}  // namespace\n\nclass ExternalPusherImpl::AbortSignalImpl final: public ExternalPusher::AbortSignal::Server {\n public:\n  AbortSignalImpl(AbortSignal content): content(kj::mv(content)) {}\n\n  kj::Maybe<AbortSignal> content;\n};\n\nkj::Promise<void> ExternalPusherImpl::pushAbortSignal(PushAbortSignalContext context) {\n  auto paf = kj::newPromiseAndFulfiller<void>();\n  auto pendingReason = kj::refcounted<PendingAbortReason>();\n\n  auto results = context.initResults(capnp::MessageSize{4, 2});\n\n  results.setTrigger(\n      kj::heap<AbortTriggerRpcServer>(kj::mv(paf.fulfiller), kj::addRef(*pendingReason)));\n  results.setSignal(abortSignalSet.add(\n      kj::heap<AbortSignalImpl>(AbortSignal{kj::mv(paf.promise), kj::mv(pendingReason)})));\n\n  return kj::READY_NOW;\n}\n\nExternalPusherImpl::AbortSignal ExternalPusherImpl::unwrapAbortSignal(\n    ExternalPusher::AbortSignal::Client cap) {\n  auto& unwrapped = KJ_REQUIRE_NONNULL(\n      abortSignalSet.tryGetLocalServerSync(cap), \"pushed external is not an AbortSignal\");\n\n  return KJ_REQUIRE_NONNULL(kj::mv(kj::downcast<AbortSignalImpl>(unwrapped).content),\n      \"pushed AbortSignal has already been consumed\");\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/external-pusher.h",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/io/worker-interface.capnp.h>\n\n#include <capnp/compat/byte-stream.h>\n#include <kj/async-io.h>\n\nnamespace workerd {\n\nusing kj::byte;\n\n// Implements JsValue.ExternalPusher from worker-interface.capnp.\n//\n// ExternalPusher allows a remote peer to \"push\" certain kinds of objects into our address space\n// so that they can then be embedded in `JsValue` as `External` values.\nclass ExternalPusherImpl: public rpc::JsValue::ExternalPusher::Server, public kj::Refcounted {\n public:\n  ExternalPusherImpl(capnp::ByteStreamFactory& byteStreamFactory)\n      : byteStreamFactory(byteStreamFactory) {}\n\n  using ExternalPusher = rpc::JsValue::ExternalPusher;\n\n  kj::Own<kj::AsyncInputStream> unwrapStream(ExternalPusher::InputStream::Client cap);\n\n  // Box which holds the reason why an AbortSignal was aborted. May be either:\n  // - A serialized V8 value if the signal was aborted from JavaScript.\n  // - A KJ exception if the connection from the trigger was lost.\n  using PendingAbortReason = kj::RefcountedWrapper<kj::OneOf<kj::Array<byte>, kj::Exception>>;\n\n  struct AbortSignal {\n    // Resolves when `reason` has been filled in.\n    kj::Promise<void> signal;\n\n    // The abort reason box, will be uninitialized until `signal` resolves.\n    kj::Own<PendingAbortReason> reason;\n  };\n\n  AbortSignal unwrapAbortSignal(ExternalPusher::AbortSignal::Client cap);\n\n  kj::Promise<void> pushByteStream(PushByteStreamContext context) override;\n  kj::Promise<void> pushAbortSignal(PushAbortSignalContext context) override;\n\n private:\n  capnp::ByteStreamFactory& byteStreamFactory;\n\n  capnp::CapabilityServerSet<ExternalPusher::InputStream> inputStreamSet;\n  capnp::CapabilityServerSet<ExternalPusher::AbortSignal> abortSignalSet;\n\n  class InputStreamImpl;\n  class AbortSignalImpl;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/features.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"features.h\"\n\n#include \"worker.h\"\n\nnamespace workerd {\n\nCompatibilityFlags::Reader FeatureFlags::get(jsg::Lock&) {\n  // Note that the jsg::Lock& argument here is not actually used. We require\n  // that a jsg::Lock reference is passed in as proof that current() is called\n  // from within a valid isolate lock so that the Worker::Api::current()\n  // call below will work as expected.\n  // TODO(later): Use of Worker::Api::current() here implies that there\n  // is only one set of compatibility flags relevant at a time within each thread\n  // context. For now that holds true. Later it is possible that may not be the\n  // case which will require us to further adapt this model.\n  return Worker::Api::current().getFeatureFlags();\n}\n\nkj::Maybe<CompatibilityFlags::Reader> FeatureFlags::tryGet(jsg::Lock& lock) {\n  return Worker::Api::tryCurrent().map(\n      [](const Worker::Api& api) { return api.getFeatureFlags(); });\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/features.capnp",
    "content": "# Copyright (c) 2017-2022 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n@0x8b3d4aaa36221ec9;\n\nusing Cxx = import \"/capnp/c++.capnp\";\n$Cxx.namespace(\"workerd\");\n$Cxx.allowCancellation;\n\nenum Features {\n  test @0;\n  # A test feature that should never be used in production code.\n\n  # Due to a number of practical limitations on the metrics collection,\n  # we do not really want the list of features to grow unbounded over\n  # time. At any given point in time we shouldn't be trying to track\n  # more than 50 features at a time.\n  #\n  # Features we are no longer needing to track can and should be removed,\n  # just be careful to adjust the index ordinals of the remaining features\n  # correctly. In code, be sure to never rely on the ordinal value and\n  # instead always use the features enum to ensure that things won't break.\n\n  # We want to determine how users typically read the data from a Blob.\n  # The reason is so that we can determine how best to optimize the Blob\n  # implementation.\n  blobAsArrayBuffer @1;\n  blobAsText @2;\n  blobAsStream @3;\n  blobGetData @4;\n}\n"
  },
  {
    "path": "src/workerd/io/features.h",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd {\n\nstruct FeatureFlags {\n  FeatureFlags() = delete;\n\n  // Get the feature flags that are relevant for the current jsg::Lock or\n  // throw if we are not currently executing JavaScript.\n  static CompatibilityFlags::Reader get(jsg::Lock&);\n\n  // Alternative to get() that returns kj::none if the flags are not available.\n  // This is typically only the case in certain tests where we may only partially\n  // initialize the JS environment.\n  static kj::Maybe<CompatibilityFlags::Reader> tryGet(jsg::Lock&);\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/frankenvalue-test.c++",
    "content": "#include \"frankenvalue.h\"\n\n#include <workerd/jsg/jsg-test.h>\n\n#include <capnp/message.h>\n#include <kj/debug.h>\n#include <kj/test.h>\n\nnamespace workerd {\nnamespace {\n\njsg::V8System v8System;\nclass ContextGlobalObject: public jsg::Object, public jsg::ContextGlobal {};\n\nenum class TestSerializationTag { TEST_SERIALIZABLE };\n\nstruct TestCapEntry final: public Frankenvalue::CapTableEntry {\n  int value;\n\n  TestCapEntry(int value): value(value) {}\n  kj::Own<CapTableEntry> clone() override {\n    return kj::heap<TestCapEntry>(value);\n  }\n};\n\nclass TestSerializableCap: public jsg::Object {\n public:\n  TestSerializableCap(int value): value(value) {}\n\n  int toJSON() {\n    return value;\n  }\n\n  JSG_RESOURCE_TYPE(TestSerializableCap) {\n    JSG_METHOD(toJSON);\n  }\n\n  void serialize(jsg::Lock& js, jsg::Serializer& serializer) {\n    auto& handler = KJ_ASSERT_NONNULL(serializer.getExternalHandler());\n    auto externalHandler = dynamic_cast<Frankenvalue::CapTableBuilder*>(&handler);\n    KJ_ASSERT(externalHandler != nullptr);\n    serializer.writeRawUint32(externalHandler->add(kj::heap<TestCapEntry>(value)));\n  }\n\n  static jsg::Ref<TestSerializableCap> deserialize(\n      jsg::Lock& js, TestSerializationTag tag, jsg::Deserializer& deserializer) {\n    auto& handler = KJ_REQUIRE_NONNULL(deserializer.getExternalHandler());\n    auto externalHandler = dynamic_cast<Frankenvalue::CapTableReader*>(&handler);\n    KJ_REQUIRE(externalHandler != nullptr);\n\n    auto& cap = KJ_REQUIRE_NONNULL(externalHandler->get(deserializer.readRawUint32()));\n    auto typedCap = dynamic_cast<TestCapEntry*>(&cap);\n    KJ_REQUIRE(typedCap != nullptr);\n\n    return js.alloc<TestSerializableCap>(typedCap->value);\n  }\n\n  JSG_SERIALIZABLE(TestSerializationTag::TEST_SERIALIZABLE);\n\n private:\n  int value;\n};\n\nstruct TestContext: public ContextGlobalObject {\n  JSG_RESOURCE_TYPE(TestContext) {}\n};\nJSG_DECLARE_ISOLATE_TYPE(TestIsolate, TestContext, TestSerializableCap);\n\nKJ_TEST(\"Frankenvalue\") {\n  jsg::test::Evaluator<TestContext, TestIsolate> e(v8System);\n\n  // Silence compiler warning.\n  (void)TestSerializableCap::jsgSerializeOneway;\n  (void)TestSerializableCap::jsgSerializeOldTags;\n\n  e.run([&](auto& lock) {\n    jsg::Lock& js = lock;\n    auto makeSerializableCap = [&](int i) {\n      return jsg::JsValue(lock.wrap(js.v8Context(), js.alloc<TestSerializableCap>(i)));\n    };\n\n    // Create a value based on JSON.\n    Frankenvalue value = Frankenvalue::fromJson(kj::str(R\"({\"baz\": 321, \"qux\": \"xyz\"})\"_kj));\n\n    // prop1 is empty.\n    value.setProperty(kj::str(\"prop1\"), {});\n\n    // prop2 is a V8-serialized value.\n    value.setProperty(kj::str(\"prop2\"), [&]() {\n      auto obj = js.obj();\n      obj.set(js, \"foo\", js.num(123));\n      obj.set(js, \"bar\", js.str(\"abc\"_kj));\n      obj.set(js, \"baz\", makeSerializableCap(234));\n      obj.set(js, \"qux\", makeSerializableCap(345));\n\n      auto result = Frankenvalue::fromJs(js, obj);\n\n      result.setProperty(kj::str(\"nested\"), [&]() {\n        auto nested = js.obj();\n        nested.set(js, \"corge\", js.num(222));\n        nested.set(js, \"grault\",\n            jsg::JsValue(lock.wrap(js.v8Context(), js.alloc<TestSerializableCap>(333))));\n        return Frankenvalue::fromJs(js, nested);\n      }());\n      result.setProperty(kj::str(\"nested2\"), [&]() {\n        auto nested = js.obj();\n        nested.set(js, \"garply\", js.num(444));\n        nested.set(js, \"waldo\",\n            jsg::JsValue(lock.wrap(js.v8Context(), js.alloc<TestSerializableCap>(555))));\n        return Frankenvalue::fromJs(js, nested);\n      }());\n\n      return result;\n    }());\n\n    KJ_ASSERT(value.getCapTable().size() == 4);\n    KJ_EXPECT(kj::downcast<TestCapEntry>(*value.getCapTable()[0]).value == 234);\n    KJ_EXPECT(kj::downcast<TestCapEntry>(*value.getCapTable()[1]).value == 345);\n    KJ_EXPECT(kj::downcast<TestCapEntry>(*value.getCapTable()[2]).value == 333);\n    KJ_EXPECT(kj::downcast<TestCapEntry>(*value.getCapTable()[3]).value == 555);\n\n    // Round trip through capnp.\n    {\n      capnp::MallocMessageBuilder message;\n      auto builder = message.initRoot<rpc::Frankenvalue>();\n      value.toCapnp(builder);\n\n      kj::Vector<kj::Own<Frankenvalue::CapTableEntry>> newCapTable;\n      for (auto& entry: value.getCapTable()) {\n        newCapTable.add(entry->clone());\n      }\n\n      value = Frankenvalue::fromCapnp(builder.asReader(), kj::mv(newCapTable));\n    }\n\n    // Use clone().\n    value = value.clone();\n\n    // Back to JS, then JSON, then check that nothing was lost.\n    KJ_EXPECT(js.serializeJson(value.toJs(js)) ==\n        R\"({\"baz\":321,\"qux\":\"xyz\",\"prop1\":{},\"prop2\":{\"foo\":123,\"bar\":\"abc\",\"baz\":234,\"qux\":345)\"_kj\n        R\"(,\"nested\":{\"corge\":222,\"grault\":333},\"nested2\":{\"garply\":444,\"waldo\":555}}})\"_kj);\n  });\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/frankenvalue.c++",
    "content": "#include \"frankenvalue.h\"\n\n#include <workerd/jsg/ser.h>\n\nnamespace workerd {\n\nFrankenvalue Frankenvalue::cloneImpl() const {\n  Frankenvalue result;\n\n  KJ_SWITCH_ONEOF(value) {\n    KJ_CASE_ONEOF(_, EmptyObject) {\n      result.value = EmptyObject();\n    }\n    KJ_CASE_ONEOF(json, Json) {\n      result.value = Json{kj::str(json.json)};\n    }\n    KJ_CASE_ONEOF(v8Serialized, V8Serialized) {\n      result.value = V8Serialized{kj::heapArray(v8Serialized.data.asPtr())};\n    }\n  }\n\n  if (!properties.empty()) {\n    result.properties.reserve(properties.size());\n\n    for (auto& property: properties) {\n      KJ_ASSERT(property.value.capTable.empty());\n      result.properties.add(Property{\n        .name = kj::str(property.name),\n        .value = property.value.cloneImpl(),\n        .capTableOffset = property.capTableOffset,\n        .capTableSize = property.capTableSize,\n      });\n    }\n  }\n\n  return result;\n}\n\nFrankenvalue Frankenvalue::clone() {\n  auto result = cloneImpl();\n\n  if (!capTable.empty()) {\n    result.capTable.reserve(capTable.size());\n    for (auto& entry: capTable) {\n      result.capTable.add(entry->clone());\n    }\n  }\n\n  return result;\n}\n\nFrankenvalue Frankenvalue::threadSafeClone() const {\n  auto result = cloneImpl();\n\n  if (!capTable.empty()) {\n    result.capTable.reserve(capTable.size());\n    for (auto& entry: capTable) {\n      result.capTable.add(entry->threadSafeClone());\n    }\n  }\n\n  return result;\n}\n\nkj::Own<Frankenvalue::CapTableEntry> Frankenvalue::CapTableEntry::threadSafeClone() const {\n  KJ_FAIL_REQUIRE(\"a capability in the Frankenvalue does not implement threadSafeClone()\");\n}\n\nvoid Frankenvalue::toCapnp(rpc::Frankenvalue::Builder builder) {\n  toCapnpImpl(builder, capTable.size());\n}\n\nvoid Frankenvalue::toCapnpImpl(rpc::Frankenvalue::Builder builder, uint capTableSize) {\n  KJ_SWITCH_ONEOF(value) {\n    KJ_CASE_ONEOF(_, EmptyObject) {\n      builder.setEmptyObject();\n    }\n    KJ_CASE_ONEOF(json, Json) {\n      builder.setJson(json.json);\n    }\n    KJ_CASE_ONEOF(v8Serialized, V8Serialized) {\n      builder.setV8Serialized(v8Serialized.data);\n    }\n  }\n\n  if (properties.empty()) {\n    builder.setCapTableSize(capTableSize);\n  } else {\n    uint capTablePos = properties[0].capTableOffset;\n    builder.setCapTableSize(capTablePos);\n\n    auto listBuilder = builder.initProperties(properties.size());\n\n    for (auto i: kj::indices(properties)) {\n      KJ_ASSERT(properties[i].capTableOffset == capTablePos);\n      capTablePos += properties[i].capTableSize;\n      auto elemBuilder = listBuilder[i];\n      elemBuilder.setName(properties[i].name);\n      properties[i].value.toCapnpImpl(elemBuilder, properties[i].capTableSize);\n    }\n    KJ_ASSERT(capTablePos == capTableSize);\n  }\n}\n\nFrankenvalue Frankenvalue::fromCapnp(\n    rpc::Frankenvalue::Reader reader, kj::Vector<kj::Own<CapTableEntry>> capTable) {\n  Frankenvalue result;\n\n  uint capCount = 0;\n  result.fromCapnpImpl(reader, capCount);\n\n  KJ_REQUIRE(capTable.size() == capCount);\n  result.capTable = kj::mv(capTable);\n\n  return result;\n}\n\nvoid Frankenvalue::fromCapnpImpl(rpc::Frankenvalue::Reader reader, uint& capCount) {\n  switch (reader.which()) {\n    case rpc::Frankenvalue::EMPTY_OBJECT:\n      this->value = EmptyObject();\n      break;\n    case rpc::Frankenvalue::JSON:\n      this->value = Json{kj::str(reader.getJson())};\n      break;\n    case rpc::Frankenvalue::V8_SERIALIZED:\n      this->value = V8Serialized{kj::heapArray(reader.getV8Serialized())};\n      break;\n  }\n\n  capCount += reader.getCapTableSize();\n\n  auto properties = reader.getProperties();\n  if (properties.size() > 0) {\n    this->properties.reserve(properties.size());\n\n    for (auto property: properties) {\n      Property result{\n        .name = kj::str(property.getName()),\n        .capTableOffset = capCount,\n      };\n      result.value.fromCapnpImpl(property, capCount);\n      result.capTableSize = capCount - result.capTableOffset;\n      this->properties.add(kj::mv(result));\n    }\n  }\n}\n\njsg::JsValue Frankenvalue::toJs(jsg::Lock& js) {\n  return toJsImpl(js, capTable);\n}\n\njsg::JsValue Frankenvalue::toJsImpl(jsg::Lock& js, kj::ArrayPtr<kj::Own<CapTableEntry>> capTable) {\n  return js.withinHandleScope([&]() -> jsg::JsValue {\n    jsg::JsValue result = [&]() -> jsg::JsValue {\n      KJ_SWITCH_ONEOF(value) {\n        KJ_CASE_ONEOF(_, EmptyObject) {\n          return js.obj();\n        }\n        KJ_CASE_ONEOF(json, Json) {\n          // TODO(cleanup): Make jsg::Lock::parseJson() not return a persistent handle.\n          return jsg::JsValue(jsg::check(v8::JSON::Parse(js.v8Context(), js.str(json.json))));\n        }\n        KJ_CASE_ONEOF(v8Serialized, V8Serialized) {\n          CapTableReader capTableReader(\n              properties.empty() ? capTable : capTable.slice(0, properties[0].capTableOffset));\n\n          jsg::Deserializer deser(js, v8Serialized.data, kj::none, kj::none,\n              jsg::Deserializer::Options{\n                .externalHandler = capTableReader,\n              });\n          return deser.readValue(js);\n        }\n      }\n      KJ_UNREACHABLE;\n    }();\n\n    if (!properties.empty()) {\n      jsg::JsObject obj = KJ_REQUIRE_NONNULL(\n          result.tryCast<jsg::JsObject>(), \"non-object Frankenvalue can't have properties\");\n\n      for (auto& property: properties) {\n        obj.set(js, property.name,\n            property.value.toJsImpl(js,\n                capTable.slice(\n                    property.capTableOffset, property.capTableOffset + property.capTableSize)));\n      }\n    }\n\n    return result;\n  });\n}\n\nvoid Frankenvalue::populateJsObject(jsg::Lock& js, jsg::JsObject target) {\n  if (!empty()) {\n    js.withinHandleScope([&]() {\n      auto sourceObj = KJ_REQUIRE_NONNULL(toJs(js).tryCast<jsg::JsObject>(),\n          \"Frankenvalue must be an object for populateJsObject()\");\n      auto props = sourceObj.getPropertyNames(js, jsg::KeyCollectionFilter::OWN_ONLY,\n          jsg::PropertyFilter::ONLY_ENUMERABLE, jsg::IndexFilter::INCLUDE_INDICES);\n      auto propCount = props.size();\n      for (auto i: kj::zeroTo(propCount)) {\n        auto prop = props.get(js, i);\n        target.set(js, prop, sourceObj.get(js, prop));\n      }\n    });\n  }\n}\n\nFrankenvalue Frankenvalue::fromJs(jsg::Lock& js, jsg::JsValue value) {\n  Frankenvalue result;\n\n  js.withinHandleScope([&]() {\n    CapTableBuilder capTableBuilder(result);\n    jsg::Serializer ser(js,\n        {\n          .treatClassInstancesAsPlainObjects = false,\n          .externalHandler = capTableBuilder,\n        });\n    ser.write(js, value);\n    result.value = V8Serialized{ser.release().data};\n  });\n\n  return result;\n}\n\nFrankenvalue Frankenvalue::fromJson(kj::String json) {\n  Frankenvalue result;\n  result.value = Json{kj::mv(json)};\n  return result;\n}\n\nvoid Frankenvalue::setProperty(kj::String name, Frankenvalue value) {\n  // We need to merge the value's cap table into ours.\n  uint capTableOffset = capTable.size();\n  uint capTableSize = value.capTable.size();\n\n  capTable.reserve(capTable.size() + value.capTable.size());\n  for (auto& cap: value.capTable) {\n    capTable.add(kj::mv(cap));\n  }\n\n  // Drop the value's capTable. Overwrite it rather than use clear() so that the backing buffer\n  // is actually freed.\n  value.capTable = {};\n\n  properties.add(Property{\n    .name = kj::mv(name),\n    .value = kj::mv(value),\n    .capTableOffset = capTableOffset,\n    .capTableSize = capTableSize,\n  });\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/frankenvalue.capnp",
    "content": "@0xcc3b225cb3101aba;\n\nusing Cxx = import \"/capnp/c++.capnp\";\n$Cxx.namespace(\"workerd::rpc\");\n$Cxx.allowCancellation;\n\nstruct Frankenvalue {\n  # Represents a JavaScript value that has been stitched together from multiple sources outside of\n  # a JavaScript evaluation context. The Frankevalue can be evaluated down to a JS value as soon\n  # as it has a JS execution environment in which to be evaluated.\n  #\n  # This is used in particular to represent `ctx.props`.\n\n  union {\n    emptyObject @0 :Void;\n    # Just an object with no properties.\n\n    json @1 :Text;\n    # Parse this JSON-formatted text to compute the value.\n\n    v8Serialized @2 :Data;\n    # Parse these V8-serialized bytes to compute the value.\n  }\n\n  properties @3 :List(Frankenvalue);\n  # Additional properties to add to the value. The base value (specified by the union above) must\n  # be an object. Each property in this list must have a `name`. They will be added as properties\n  # of the object.\n  #\n  # If a property in the list conflicts with a property that already exists in the base value,\n  # the property is overwritten with the value from the `properties` list.\n\n  name @4 :Text;\n  # Property name. Used only when this `Frankenvalue` represents a property, that is, it is an\n  # element within the `properties` list of some other `Frankenvalue`. If this is the root value,\n  # then `name` must be null.\n\n  capTableSize @5 :UInt32 = 0;\n  # How large is this value's cap table, not counting `properties`.\n  #\n  # The final cap table contains this many base caps (referenced by the union above), followed by\n  # the caps for each property, in order.\n\n  capTable @6 :AnyPointer;\n  # Some sort of representation of the cap table. The exact format is different for different\n  # contexts. Frankenvalue::toCapnp() and fromCapnp() don't handle this at all -- the caller is\n  # expected to deal with it.\n  #\n  # TODO(cleanup): Consider making `Frankenvalue` a generic over the capTable type? Maybe even make\n  #   the C++ class a template over the CapTableEntry type?\n}\n"
  },
  {
    "path": "src/workerd/io/frankenvalue.h",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/io/frankenvalue.capnp.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd {\n\n// C++ class mirroring `Frankenvalue` as defined in `frankenvalue.capnp`.\n//\n// Represents a JavaScript value that has been stitched together from multiple sources outside of\n// a JavaScript evaluation context. The Frankevalue can be evaluated down to a JS value as soon\n// as it has a JS execution environment in which to be evaluated.\n//\n// This is used in particular to represent `ctx.props`.\nclass Frankenvalue {\n public:\n  Frankenvalue(): value(EmptyObject()) {}\n\n  bool empty() const {\n    return value.is<EmptyObject>() && properties.empty();\n  }\n\n  Frankenvalue clone();\n\n  // This method only works if the `CapTableEntry`s in this `Frankenvalue` all implement\n  // `threadSafeClone()`.\n  Frankenvalue threadSafeClone() const;\n\n  class CapTableEntry;\n\n  // Convert to/from capnp format.\n  //\n  // The CapTable, if any, is expected to be handled separately, as different use cases call for\n  // very different handling of the cap table.\n  void toCapnp(rpc::Frankenvalue::Builder builder);\n  static Frankenvalue fromCapnp(\n      rpc::Frankenvalue::Reader reader, kj::Vector<kj::Own<CapTableEntry>> capTable = {});\n\n  // Convert to/from JavaScript values. Note that round trips here don't produce the exact same\n  // Frankenvalue representation: toJs() puts all the contents together into a single value, and\n  // fromJs() always returns a Frankenvalue containing a single V8-serialized value.\n  jsg::JsValue toJs(jsg::Lock& js);\n  static Frankenvalue fromJs(jsg::Lock& js, jsg::JsValue value);\n\n  // Like toJs() but add the properties to an existing object. Throws if the `Frankenvalue` does\n  // not represent an object. This is used to populate `env` in particular.\n  void populateJsObject(jsg::Lock& js, jsg::JsObject target);\n\n  // Construct a Frankenvalue from JSON.\n  //\n  // (It's not possible to convert a Frankenvalue back to JSON, except by evaluating it in JS and\n  // then JSON-stringifying from there.)\n  static Frankenvalue fromJson(kj::String json);\n\n  // Add a property to the value, represented as another Frankenvalue. This is how you \"stitch\n  // together\" values!\n  //\n  // This is called `set` because the new property will override any existing property with the\n  // same name, but note that this strictly appends content. The replacement happens only when the\n  // Frankenvalue is finally converted to JS.\n  void setProperty(kj::String name, Frankenvalue value);\n\n  // ---------------------------------------------------------------------------\n  // Capability handling\n  //\n  // A Frankenvalue can contain capabilities (typically ServiceStubs). When serializing from\n  // JavaScript, these will be encoded as integer indexes into a separate table -- the CapTable.\n\n  // The Frankenvalue itself doesn't know how these \"capabilities\" are implemneted, so leaves this\n  // up to a higher layer. It simply maintains a table of `CapTableEntry` objects. `CapTableEntry`\n  // serves as a generic base class for multiple representations which serializers and\n  // deserializers for specific types will need to support through downcasting.\n  //\n  // In particular:\n  // - Typically, the type is `IoChannelFactory::SubrequestChannel`.\n  // - When a Frankenvalue is being used to initialize the `env` of a dynamically-loaded isolate,\n  //   each CapTableEntry may simply contain an I/O channel number.\n  // - In some environments, a CapTableEntry might be some sort of description of how to load a\n  //   Worker that implements the capability.\n  class CapTableEntry {\n   public:\n    // Clone the entry, used when `Frankenvalue::clone()` is called. Many implementations may\n    // implement this using addRef().\n    virtual kj::Own<CapTableEntry> clone() = 0;\n\n    // Like `clone()` but works on const values. Used only when `Frankenvalue::threadSafeClone()`\n    // is called. The default implementation throws an exception.\n    virtual kj::Own<CapTableEntry> threadSafeClone() const;\n  };\n\n  kj::ArrayPtr<kj::Own<CapTableEntry>> getCapTable() {\n    return capTable;\n  }\n\n  // Rewrite all the caps in the table by calling the `rewrite()` callback on each one.\n  template <typename Func>\n  void rewriteCaps(Func&& rewrite) {\n    for (auto& slot: capTable) {\n      slot = rewrite(kj::mv(slot));\n    }\n  }\n\n  // When deserializing a JS value, the jsg::Deserializer's ExternalHandler will have this type.\n  class CapTableReader final: public jsg::Deserializer::ExternalHandler {\n   public:\n    kj::Maybe<CapTableEntry&> get(uint index) {\n      if (index < table.size()) {\n        return *table[index];\n      } else {\n        return kj::none;\n      }\n    }\n\n   private:\n    kj::ArrayPtr<kj::Own<CapTableEntry>> table;\n    CapTableReader(kj::ArrayPtr<kj::Own<CapTableEntry>> table): table(table) {}\n    friend class Frankenvalue;\n  };\n\n  // When serializing a JS value, the jsg::Serializer's ExternalHandler will have this type.\n  class CapTableBuilder final: public jsg::Serializer::ExternalHandler {\n   public:\n    uint add(kj::Own<CapTableEntry> entry) {\n      uint result = target.capTable.size();\n      target.capTable.add(kj::mv(entry));\n      return result;\n    }\n\n   private:\n    Frankenvalue& target;\n    CapTableBuilder(Frankenvalue& target): target(target) {}\n    friend class Frankenvalue;\n  };\n\n private:\n  struct EmptyObject {};\n  struct Json {\n    kj::String json;\n  };\n  struct V8Serialized {\n    kj::Array<byte> data;\n  };\n  kj::OneOf<EmptyObject, Json, V8Serialized> value;\n\n  struct Property;\n  kj::Vector<Property> properties;\n\n  kj::Vector<kj::Own<CapTableEntry>> capTable;\n\n  Frankenvalue cloneImpl() const;\n  void fromCapnpImpl(rpc::Frankenvalue::Reader reader, uint& capTablePos);\n  void toCapnpImpl(rpc::Frankenvalue::Builder builder, uint capTableSize);\n  jsg::JsValue toJsImpl(jsg::Lock& js, kj::ArrayPtr<kj::Own<CapTableEntry>> capTable);\n};\n\n// Can't be defined inline since `Frankenvalue` is still incomplete there.\nstruct Frankenvalue::Property {\n  kj::String name;\n  Frankenvalue value;\n\n  // `value.capTable` is always empty. Instead, these two values specify the slice of the parent's\n  // capTable which this Frankenvalue refers into.\n  uint capTableOffset = 0;\n  uint capTableSize = 0;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/hibernation-manager.c++",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"hibernation-manager.h\"\n\n#include \"io-channels.h\"\n\n#include <workerd/util/uuid.h>\n\nnamespace workerd {\n\nHibernationManagerImpl::HibernatableWebSocket::HibernatableWebSocket(\n    jsg::Ref<api::WebSocket> websocket,\n    kj::ArrayPtr<kj::String> tags,\n    HibernationManagerImpl& manager)\n    : tagItems(kj::heapArray<TagListItem>(tags.size())),\n      activeOrPackage(kj::mv(websocket)),\n      // The `ws` starts off empty because we need to set up our tagging infrastructure before\n      // calling api::WebSocket::acceptAsHibernatable(). We will transfer ownership of the\n      // kj::WebSocket prior to starting the readLoop.\n      ws(kj::none),\n      manager(manager) {}\n\nHibernationManagerImpl::HibernatableWebSocket::~HibernatableWebSocket() noexcept(false) {\n  // We expect this dtor to be called when we're removing a HibernatableWebSocket\n  // from our `allWs` collection in the HibernationManager.\n\n  // This removal is fast because we have direct access to each kj::List, as well as direct\n  // access to each TagListItem we want to remove.\n  for (auto& item: tagItems) {\n    KJ_IF_SOME(list, item.list) {\n      // The list reference is non-null, so we still have a valid reference to this\n      // TagListItem in the list, which we will now remove.\n      list.remove(item);\n      if (list.empty()) {\n        // Remove the bucket in tagToWs if the tag has no more websockets.\n        manager.tagToWs.erase(kj::mv(item.tag));\n      }\n    }\n    item.hibWS = kj::none;\n    item.list = kj::none;\n  }\n}\n\nkj::Array<kj::StringPtr> HibernationManagerImpl::HibernatableWebSocket::getTags() {\n  auto tags = kj::heapArray<kj::StringPtr>(tagItems.size());\n  for (auto i: kj::indices(tagItems)) {\n    tags[i] = tagItems[i].tag;\n  }\n  return tags;\n}\n\nkj::Array<kj::String> HibernationManagerImpl::HibernatableWebSocket::cloneTags() {\n  auto tags = kj::heapArray<kj::String>(tagItems.size());\n  for (auto i: kj::indices(tagItems)) {\n    tags[i] = kj::str(tagItems[i].tag);\n  }\n  return tags;\n}\n\njsg::Ref<api::WebSocket> HibernationManagerImpl::HibernatableWebSocket::getActiveOrUnhibernate(\n    jsg::Lock& js) {\n  KJ_IF_SOME(package, activeOrPackage.tryGet<api::WebSocket::HibernationPackage>()) {\n    // Recreate our tags array for the api::WebSocket.\n    package.maybeTags = getTags();\n\n    // Now that we unhibernated the WebSocket, we can set the last received autoResponse timestamp\n    // that was stored in the corresponding HibernatableWebSocket. We also move autoResponsePromise\n    // from the hibernation manager to api::websocket to prevent possible ws.send races.\n    activeOrPackage\n        .init<jsg::Ref<api::WebSocket>>(\n            api::WebSocket::hibernatableFromNative(js, *KJ_REQUIRE_NONNULL(ws), kj::mv(package)))\n        ->setAutoResponseStatus(autoResponseTimestamp, kj::mv(autoResponsePromise));\n    autoResponsePromise = kj::READY_NOW;\n  }\n  return activeOrPackage.get<jsg::Ref<api::WebSocket>>().addRef();\n}\n\nHibernationManagerImpl::HibernationManagerImpl(\n    kj::Own<Worker::Actor::Loopback> loopback, uint16_t hibernationEventType)\n    : loopback(kj::mv(loopback)),\n      hibernationEventType(hibernationEventType),\n      onDisconnect(DisconnectHandler{}),\n      readLoopTasks(onDisconnect) {}\n\nHibernationManagerImpl::~HibernationManagerImpl() noexcept(false) {\n  // Drop our outstanding tasks, the `readLoopTasks` have weak references to the\n  // `HibernatableWebSockets` in `allWs`, and since we're about to drop all of those WebSockets,\n  // we can't allow any more events to be delivered.\n  readLoopTasks.clear();\n\n  // Note that the HibernatableWebSocket destructor handles removing any references to itself in\n  // `tagToWs`, and even removes the hashmap entry if there are no more entries in the bucket.\n  allWs.clear();\n  KJ_ASSERT(tagToWs.size() == 0, \"tagToWs hashmap wasn't cleared.\");\n}\n\nkj::Own<Worker::Actor::HibernationManager> HibernationManagerImpl::addRef() {\n  return kj::addRef(*this);\n}\n\nvoid HibernationManagerImpl::acceptWebSocket(\n    jsg::Ref<api::WebSocket> ws, kj::ArrayPtr<kj::String> tags) {\n  // First, we create the HibernatableWebSocket and add it to the collection where it'll stay\n  // until it's destroyed.\n\n  JSG_REQUIRE(allWs.size() < ACTIVE_CONNECTION_LIMIT, Error, \"only \", ACTIVE_CONNECTION_LIMIT,\n      \" websockets can be accepted on a single Durable Object instance\");\n\n  auto hib = kj::heap<HibernatableWebSocket>(kj::mv(ws), tags, *this);\n  HibernatableWebSocket& refToHibernatable = *hib.get();\n  allWs.push_front(kj::mv(hib));\n  refToHibernatable.node = allWs.begin();\n\n  // If the `tags` array is empty (i.e. user did not provide a tag), we skip the population of the\n  // `tagToWs` HashMap below and go straight to initiating the readLoop.\n\n  // It is the caller's responsibility to ensure all elements of `tags` are unique.\n  // TODO(cleanup): Maybe we could enforce uniqueness by using an immutable type that\n  // can only be constructed if the elements in the collection are distinct, ex. \"DistinctArray\".\n  //\n  // We need to add the HibernatableWebSocket to each bucket in `tagToWs` corresponding to its tags.\n  //  1. Create the entry if it doesn't exist\n  //  2. Fill the TagListItem in the HibernatableWebSocket's tagItems array\n  size_t position = 0;\n  for (auto tag = tags.begin(); tag < tags.end(); tag++, position++) {\n    auto& tagCollection = tagToWs.findOrCreate(*tag, [&tag]() {\n      auto item = kj::heap<TagCollection>(\n          kj::mv(*tag), kj::heap<kj::List<TagListItem, &TagListItem::link>>());\n      return decltype(tagToWs)::Entry{item->tag, kj::mv(item)};\n    });\n    // This TagListItem sits in the HibernatableWebSocket's tagItems array.\n    auto& tagListItem = refToHibernatable.tagItems[position];\n    tagListItem.hibWS = refToHibernatable;\n    tagListItem.tag = tagCollection->tag.asPtr();\n\n    auto& list = tagCollection->list;\n    list->add(tagListItem);\n    // We also give the TagListItem a reference to the list it was added to so the\n    // HibernatableWebSocket can quickly remove itself from the list without doing a lookup\n    // in `tagToWs`.\n    tagListItem.list = *list.get();\n  }\n\n  // Before starting the readLoop, we need to move the kj::Own<kj::WebSocket> from the\n  // api::WebSocket into the HibernatableWebSocket and accept the api::WebSocket as \"hibernatable\".\n  refToHibernatable.ws =\n      refToHibernatable.activeOrPackage.get<jsg::Ref<api::WebSocket>>()->acceptAsHibernatable(\n          refToHibernatable.getTags());\n\n  // Finally, we initiate the readloop for this HibernatableWebSocket and\n  // give the task to the HibernationManager so it lives long.\n  readLoopTasks.add(handleReadLoop(refToHibernatable).catch_([](kj::Exception&& e) {\n    if (isInterestingException(e)) {\n      LOG_EXCEPTION_IF_INTERNAL(\"HibernationManagerImpl::handleReadLoop\", e);\n    }\n  }));\n}\n\nkj::Promise<void> HibernationManagerImpl::handleReadLoop(HibernatableWebSocket& refToHibernatable) {\n  kj::Maybe<kj::Exception> maybeException;\n  try {\n    co_await readLoop(refToHibernatable);\n  } catch (...) {\n    maybeException = kj::getCaughtExceptionAsKj();\n  }\n  co_await handleSocketTermination(refToHibernatable, maybeException);\n}\n\nkj::Vector<jsg::Ref<api::WebSocket>> HibernationManagerImpl::getWebSockets(\n    jsg::Lock& js, kj::Maybe<kj::StringPtr> maybeTag) {\n  kj::Vector<jsg::Ref<api::WebSocket>> matches;\n  KJ_IF_SOME(tag, maybeTag) {\n    KJ_IF_SOME(item, tagToWs.find(tag)) {\n      auto& list = *((item)->list);\n      for (auto& entry: list) {\n        auto& hibWS = KJ_REQUIRE_NONNULL(entry.hibWS);\n        matches.add(hibWS.getActiveOrUnhibernate(js));\n      }\n    }\n  } else {\n    // Add all websockets!\n    for (auto& hibWS: allWs) {\n      matches.add(hibWS->getActiveOrUnhibernate(js));\n    }\n  }\n  return kj::mv(matches);\n}\n\nvoid HibernationManagerImpl::setWebSocketAutoResponse(\n    kj::Maybe<kj::StringPtr> request, kj::Maybe<kj::StringPtr> response) {\n  KJ_IF_SOME(req, request) {\n    // If we have a request, we must also have a response. If response is kj::none, we'll throw.\n    autoResponsePair->request = kj::str(req);\n    autoResponsePair->response = kj::str(KJ_REQUIRE_NONNULL(response));\n    return;\n  }\n  // If we don't have a request, we must unset both request and response.\n  autoResponsePair->request = kj::none;\n  autoResponsePair->response = kj::none;\n}\n\nkj::Maybe<jsg::Ref<api::WebSocketRequestResponsePair>> HibernationManagerImpl::\n    getWebSocketAutoResponse(jsg::Lock& js) {\n  KJ_IF_SOME(req, autoResponsePair->request) {\n    // When getting the currently set auto-response pair, if we have a request we must have a response\n    // set. If not, we'll throw.\n    return api::WebSocketRequestResponsePair::constructor(\n        js, kj::str(req), kj::str(KJ_REQUIRE_NONNULL(autoResponsePair->response)));\n  }\n  return kj::none;\n}\n\nvoid HibernationManagerImpl::setTimerChannel(TimerChannel& timerChannel) {\n  timer = timerChannel;\n}\n\nvoid HibernationManagerImpl::hibernateWebSockets(Worker::Lock& lock) {\n  JSG_WITHIN_CONTEXT_SCOPE(lock, lock.getContext(), [&](jsg::Lock& js) {\n    for (auto& ws: allWs) {\n      KJ_IF_SOME(active, ws->activeOrPackage.tryGet<jsg::Ref<api::WebSocket>>()) {\n        // Transfers ownership of properties from api::WebSocket to HibernatableWebSocket via the\n        // HibernationPackage.\n        ws->activeOrPackage.init<api::WebSocket::HibernationPackage>(\n            active.get()->buildPackageForHibernation());\n      } else {\n      }  // Here to quash compiler warning\n    }\n  });\n}\n\nvoid HibernationManagerImpl::setEventTimeout(kj::Maybe<uint32_t> timeoutMs) {\n  eventTimeoutMs = timeoutMs;\n}\n\nkj::Maybe<uint32_t> HibernationManagerImpl::getEventTimeout() {\n  return eventTimeoutMs;\n}\n\nvoid HibernationManagerImpl::dropHibernatableWebSocket(HibernatableWebSocket& hib) {\n  removeFromAllWs(hib);\n}\n\ninline void HibernationManagerImpl::removeFromAllWs(HibernatableWebSocket& hib) {\n  auto& node = KJ_REQUIRE_NONNULL(hib.node);\n  allWs.erase(node);\n}\n\nkj::Promise<void> HibernationManagerImpl::handleSocketTermination(\n    HibernatableWebSocket& hib, kj::Maybe<kj::Exception>& maybeError) {\n  kj::Maybe<kj::Promise<void>> event;\n  KJ_IF_SOME(error, maybeError) {\n    auto websocketId = randomUUID(kj::none);\n    webSocketsForEventHandler.insert(kj::str(websocketId), &hib);\n    kj::Maybe<api::HibernatableSocketParams> params;\n    if (!hib.hasDispatchedClose && (error.getType() == kj::Exception::Type::DISCONNECTED)) {\n      // If premature disconnect/cancel, dispatch a close event if we haven't already.\n      hib.hasDispatchedClose = true;\n      params = api::HibernatableSocketParams(1006,\n          kj::str(\"WebSocket disconnected without sending Close frame.\"), false,\n          kj::mv(websocketId));\n    } else {\n      // Otherwise, we need to dispatch an error event!\n      params = api::HibernatableSocketParams(kj::mv(error), kj::mv(websocketId));\n    }\n\n    KJ_REQUIRE_NONNULL(params).setTimeout(eventTimeoutMs);\n    // Dispatch the event.\n    auto workerInterface = loopback->getWorker(IoChannelFactory::SubrequestMetadata{});\n    event = workerInterface\n                ->customEvent(kj::heap<api::HibernatableWebSocketCustomEvent>(\n                    hibernationEventType, kj::mv(KJ_REQUIRE_NONNULL(params)), *this))\n                .ignoreResult()\n                .attach(kj::mv(workerInterface));\n  }\n\n  // Returning the event promise will store it in readLoopTasks.\n  // After the task completes, we want to drop the websocket since we've closed the connection.\n  KJ_IF_SOME(promise, event) {\n    co_await promise;\n  }\n\n  dropHibernatableWebSocket(hib);\n}\n\nkj::Promise<void> HibernationManagerImpl::readLoop(HibernatableWebSocket& hib) {\n  // Like the api::WebSocket readLoop(), but we dispatch different types of events.\n  auto& ws = *KJ_REQUIRE_NONNULL(hib.ws);\n  while (true) {\n    kj::WebSocket::Message message = co_await ws.receive();\n    // Note that errors are handled by the callee of `readLoop`, since we throw from `receive()`.\n\n    auto skip = false;\n\n    // If we have a request != kj::none, we can compare it the received message. This also implies\n    // that we have a response set in autoResponsePair.\n    KJ_IF_SOME(req, autoResponsePair->request) {\n      KJ_SWITCH_ONEOF(message) {\n        KJ_CASE_ONEOF(text, kj::String) {\n          if (text == req) {\n            // If the received message matches the one set for auto-response, we must\n            // short-circuit readLoop, store the current timestamp and and automatically respond\n            // with the expected response.\n            TimerChannel& timerChannel = KJ_REQUIRE_NONNULL(timer);\n            // This should count as a new IO event, hence we should call syncTime\n            // otherwise the autoResponseTimestamp wouldn't be accurate.\n            timerChannel.syncTime();\n            // We should have set the timerChannel previously in the hibernation manager.\n            // If we haven't, we aren't able to get the current time.\n            hib.autoResponseTimestamp = timerChannel.now();\n            // We'll store the current timestamp in the HibernatableWebSocket to assure it gets\n            // stored even if the WebSocket is currently hibernating. In that scenario, the timestamp\n            // value will be loaded into the WebSocket during unhibernation.\n            KJ_SWITCH_ONEOF(hib.activeOrPackage) {\n              KJ_CASE_ONEOF(apiWs, jsg::Ref<api::WebSocket>) {\n                // If the actor is not hibernated/If the WebSocket is active, we need to update\n                // autoResponseTimestamp on the active websocket.\n                apiWs->setAutoResponseStatus(hib.autoResponseTimestamp, kj::READY_NOW);\n                // Since we had a request set, we must have and response that's sent back using the\n                // same websocket here. The sending of response is managed in web-socket to avoid\n                // possible racing problems with regular websocket messages.\n                co_await apiWs->sendAutoResponse(\n                    kj::str(KJ_REQUIRE_NONNULL(autoResponsePair->response).asArray()), ws);\n              }\n              KJ_CASE_ONEOF(package, api::WebSocket::HibernationPackage) {\n                if (!package.closedOutgoingConnection) {\n                  // We need to store the autoResponsePromise because we may instantiate an api::websocket\n                  // If we do that, we have to provide it with the promise to avoid races. This can\n                  // happen if we have a websocket hibernating, that unhibernates and sends a\n                  // message while ws.send() for auto-response is also sending.\n                  auto p = ws.send(KJ_REQUIRE_NONNULL(autoResponsePair->response).asArray()).fork();\n                  hib.autoResponsePromise = p.addBranch();\n                  co_await p;\n                  hib.autoResponsePromise = kj::READY_NOW;\n                }\n              }\n            }\n            // If we've sent an auto response message, we should not unhibernate or deliver the\n            // received message to the actor\n            skip = true;\n          }\n        }\n        KJ_CASE_ONEOF_DEFAULT {}\n      }\n    }\n\n    if (skip) {\n      continue;\n    }\n\n    auto websocketId = randomUUID(kj::none);\n    webSocketsForEventHandler.insert(kj::str(websocketId), &hib);\n\n    // Build the event params depending on what type of message we got.\n    kj::Maybe<api::HibernatableSocketParams> maybeParams;\n    KJ_SWITCH_ONEOF(message) {\n      KJ_CASE_ONEOF(text, kj::String) {\n        maybeParams.emplace(kj::mv(text), kj::mv(websocketId));\n      }\n      KJ_CASE_ONEOF(data, kj::Array<kj::byte>) {\n        maybeParams.emplace(kj::mv(data), kj::mv(websocketId));\n      }\n      KJ_CASE_ONEOF(close, kj::WebSocket::Close) {\n        maybeParams.emplace(close.code, kj::mv(close.reason), true, kj::mv(websocketId));\n        // We'll dispatch the close event, so let's mark our websocket as having done so to\n        // prevent a situation where we dispatch it twice.\n        hib.hasDispatchedClose = true;\n      }\n    }\n\n    auto params = kj::mv(KJ_REQUIRE_NONNULL(maybeParams));\n    params.setTimeout(eventTimeoutMs);\n    auto isClose = params.isCloseEvent();\n    // Dispatch the event.\n    auto workerInterface = loopback->getWorker(IoChannelFactory::SubrequestMetadata{});\n    co_await workerInterface->customEvent(kj::heap<api::HibernatableWebSocketCustomEvent>(\n        hibernationEventType, kj::mv(params), *this));\n    if (isClose) {\n      co_return;\n    }\n  }\n}\n\n};  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/hibernation-manager.h",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/api/actor-state.h>\n#include <workerd/api/hibernatable-web-socket.h>\n#include <workerd/api/web-socket.h>\n#include <workerd/jsg/jsg.h>\n\n#include <kj/exception.h>\n\n#include <list>\n\nnamespace workerd {\n\n// Implements the HibernationManager class.\nclass HibernationManagerImpl final: public Worker::Actor::HibernationManager {\n public:\n  HibernationManagerImpl(kj::Own<Worker::Actor::Loopback> loopback, uint16_t hibernationEventType);\n  ~HibernationManagerImpl() noexcept(false);\n\n  // Tells the HibernationManager to create a new HibernatableWebSocket with the associated tags\n  // and to initiate the `readLoop()` for this websocket. The `tags` array *must* contain only\n  // unique elements.\n  void acceptWebSocket(jsg::Ref<api::WebSocket> ws, kj::ArrayPtr<kj::String> tags) override;\n\n  // Gets a collection of websockets associated with the given tag. Any hibernating websockets will\n  // be woken up. If no tag is provided, we return all accepted websockets.\n  kj::Vector<jsg::Ref<api::WebSocket>> getWebSockets(\n      jsg::Lock& js, kj::Maybe<kj::StringPtr> tag) override;\n\n  // Hibernates all the websockets held by the HibernationManager.\n  // This converts our activeOrPackage from an api::WebSocket to a HibernationPackage.\n  void hibernateWebSockets(Worker::Lock& lock) override;\n\n  void setWebSocketAutoResponse(\n      kj::Maybe<kj::StringPtr> request, kj::Maybe<kj::StringPtr> response) override;\n  kj::Maybe<jsg::Ref<api::WebSocketRequestResponsePair>> getWebSocketAutoResponse(\n      jsg::Lock& js) override;\n  void setTimerChannel(TimerChannel& timerChannel) override;\n\n  kj::Own<HibernationManager> addRef() override;\n\n  friend class api::HibernatableWebSocketEvent;\n\n  // Sets/Unset the maximum time in milliseconds that an hibernatable websocket event can run for.\n  // If the timeout is reached, event is canceled.\n  void setEventTimeout(kj::Maybe<uint32_t> timeoutMs) override;\n\n  // Gets the event timeout if set.\n  kj::Maybe<uint32_t> getEventTimeout() override;\n\n private:\n  class HibernatableWebSocket;\n\n  kj::Promise<void> handleReadLoop(HibernatableWebSocket& refToHibernatable);\n\n  // Each HibernatableWebSocket can have multiple tags, so we want to store a reference\n  // in our kj::List.\n  struct TagListItem {\n    kj::Maybe<HibernatableWebSocket&> hibWS;\n    kj::ListLink<TagListItem> link;\n    kj::StringPtr tag;\n    // The List that refers to this TagListItem.\n    // If `list` is null, we've already removed this item from the list.\n    kj::Maybe<kj::List<TagListItem, &TagListItem::link>&> list;\n  };\n\n  // api::WebSockets cannot survive hibernation, but kj::WebSockets do. This class helps us\n  // manage the transition of an api::WebSocket from its active state to a hibernated state\n  // and vice versa.\n  //\n  // Some properties of the JS websocket object need to be retained throughout hibernation,\n  // such as `attachment`, `url`, `extensions`, etc. These properties are only read/modified\n  // when initiating, or waking from hibernation.\n  class HibernatableWebSocket {\n   public:\n    HibernatableWebSocket(jsg::Ref<api::WebSocket> websocket,\n        kj::ArrayPtr<kj::String> tags,\n        HibernationManagerImpl& manager);\n    ~HibernatableWebSocket() noexcept(false);\n    KJ_DISALLOW_COPY_AND_MOVE(HibernatableWebSocket);\n\n    // Returns the tags associated with this HibernatableWebSocket.\n    kj::Array<kj::StringPtr> getTags();\n\n    // Returns the tags associated with this HibernatableWebSocket.\n    // Note that this returns an array of Strings, unlike `getTags()`.\n    // Copying the strings each time tags are requested would be expensive,\n    // so we only do it when we're delivering a close/error event because\n    // we will be destroying the HibernatableWebSocket object,\n    // which the tags need to outlive.\n    kj::Array<kj::String> cloneTags();\n\n    // Returns a reference to the active websocket. If the websocket is currently hibernating,\n    // we have to unhibernate it first. The process moves values from the HibernatableWebSocket\n    // to the api::WebSocket.\n    jsg::Ref<api::WebSocket> getActiveOrUnhibernate(jsg::Lock& js);\n\n    kj::ListLink<HibernatableWebSocket> link;\n\n    // An array of all the items/nodes that refer to this HibernatableWebSocket.\n    // Keeping track of these items allows us to quickly remove every reference from `tagToWs`\n    // once the websocket disconnects -- rather than iterating through each relevant tag in the\n    // hashmap and removing it from each kj::List.\n    kj::Array<TagListItem> tagItems;\n\n    // If active, we have an api::WebSocket reference, otherwise, we're hibernating, so we retain\n    // the websocket's properties in a HibernationPackage until it's time to wake up.\n    kj::OneOf<jsg::Ref<api::WebSocket>, api::WebSocket::HibernationPackage> activeOrPackage;\n\n    // This is an owned websocket that we extract from the api::WebSocket after accepting as\n    // hibernatable. It becomes null once we dispatch a close or error event because we want its\n    // lifetime to be managed by IoContext's DeleteQueue. This helps prevent a situation where the\n    // HibernationManager drops the websocket before all queued messages have sent.\n    kj::Maybe<kj::Own<kj::WebSocket>> ws;\n\n    HibernationManagerImpl& manager;\n    // TODO(someday): We (currently) only use the HibernationManagerImpl reference to refer to\n    // `tagToWs` when running the dtor for `HibernatableWebSocket`. This feels a bit excessive,\n    // I would rather have the HibernationManager deal with its collections than have the\n    // HibernatableWebSocket do so. Maybe come back to this at some point?\n\n    // Reference to the Node in `allWs` that allows us to do fast deletion on disconnect.\n    kj::Maybe<std::list<kj::Own<HibernatableWebSocket>>::iterator> node;\n\n    // True once we have dispatched the close event.\n    // This prevents us from dispatching it if we have already done so.\n    bool hasDispatchedClose = false;\n\n    // Stores the last received autoResponseRequest timestamp.\n    kj::Maybe<kj::Date> autoResponseTimestamp;\n\n    // Keeps track of the currently ongoing websocket auto-response send promise. This promise may\n    // be moved to api::websocket if an hibernating websocket unhibernates.\n    kj::Promise<void> autoResponsePromise = kj::READY_NOW;\n\n    friend HibernationManagerImpl;\n  };\n\n  // Removes a HibernatableWebSocket from the HibernationManager's various collections.\n  void dropHibernatableWebSocket(HibernatableWebSocket& hib);\n\n  // Removes the HibernatableWebSocket from `allWs`.\n  inline void removeFromAllWs(HibernatableWebSocket& hib);\n\n  // Handles the termination of the websocket. If termination was not clean, we might try to\n  // dispatch a close event (if we haven't already), or an error event.\n  // We will also remove the HibernatableWebSocket from the HibernationManager's collections.\n  kj::Promise<void> handleSocketTermination(\n      HibernatableWebSocket& hib, kj::Maybe<kj::Exception>& maybeError) KJ_WARN_UNUSED_RESULT;\n\n  // Like the api::WebSocket readLoop(), but we dispatch different types of events.\n  kj::Promise<void> readLoop(HibernatableWebSocket& hib);\n\n  // This struct is held by the `tagToWs` hashmap. The key is a StringPtr to tag, and the value\n  // is this struct itself.\n  struct TagCollection {\n    kj::String tag;\n    kj::Own<kj::List<TagListItem, &TagListItem::link>> list;\n\n    TagCollection(kj::String tag, decltype(list) list): tag(kj::mv(tag)), list(kj::mv(list)) {}\n    TagCollection(TagCollection&& other) = default;\n  };\n\n  // This structure will hold the request and corresponding response for hibernatable websockets\n  // auto-response feature. Although we store 2 kj::Maybe strings, if we don't have a request set\n  // we can't have a response, and vice versa.\n  // TODO(cleanup): Remove kj::Maybe from request and response strings.\n  struct AutoRequestResponsePair {\n    kj::Maybe<kj::String> request = kj::none;\n    kj::Maybe<kj::String> response = kj::none;\n  };\n\n  // A hashmap of tags to HibernatableWebSockets associated with the tag.\n  // We use a kj::List so we can quickly remove websockets that have disconnected.\n  // Also note that we box the keys and values such that in the event of a hashmap resizing we don't\n  // move the underlying data (thereby keeping any references intact).\n  kj::HashMap<kj::StringPtr, kj::Own<TagCollection>> tagToWs;\n\n  // We store all of our HibernatableWebSockets in a doubly linked-list.\n  std::list<kj::Own<HibernatableWebSocket>> allWs;\n\n  // Used to obtain the worker so we can dispatch Hibernatable websocket events.\n  kj::Own<Worker::Actor::Loopback> loopback;\n\n  // Passed to HibernatableWebSocket custom event as the typeId.\n  uint16_t hibernationEventType;\n\n  // A map of { ID -> HibernatableWebSocket } that allows the event handler that is currently\n  // running to access the HibernatableWebSocket that it needs to execute.\n  //\n  // Dispatching events tends to result in races when events are received on different websockets\n  // around the same time. Suppose there are two websockets that disconnect at the same time.\n  // It is possible that both of them will be added to the map (i.e. their `receive()`\n  // will throw) before the first event is dispatched and manages to obtain its associated websocket.\n  kj::HashMap<kj::String, HibernatableWebSocket*> webSocketsForEventHandler;\n\n  // The maximum number of Hibernatable WebSocket connections a single HibernationManagerImpl\n  // instance can manage.\n  const size_t ACTIVE_CONNECTION_LIMIT = 1024 * 32;\n\n  class DisconnectHandler: public kj::TaskSet::ErrorHandler {\n   public:\n    // We don't need to do anything here; we already handle disconnects in the callee of readLoop().\n    void taskFailed(kj::Exception&& exception) override {};\n  };\n  DisconnectHandler onDisconnect;\n  kj::TaskSet readLoopTasks;\n  kj::Own<AutoRequestResponsePair> autoResponsePair = kj::heap<AutoRequestResponsePair>();\n  kj::Maybe<TimerChannel&> timer;\n  kj::Maybe<uint32_t> eventTimeoutMs;\n};\n};  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/io-channels.c++",
    "content": "#include \"io-channels.h\"\n\nnamespace workerd {\n\nkj::Array<byte> IoChannelFactory::SubrequestChannel::getToken(ChannelTokenUsage usage) {\n  JSG_FAIL_REQUIRE(DOMDataCloneError, \"This ServiceStub cannot be serialized.\");\n}\n\nkj::Array<byte> IoChannelFactory::ActorClassChannel::getToken(ChannelTokenUsage usage) {\n  JSG_FAIL_REQUIRE(DOMDataCloneError, \"This Durable Object class cannot be serialized.\");\n}\n\nkj::Own<IoChannelFactory::SubrequestChannel> IoChannelFactory::subrequestChannelFromToken(\n    ChannelTokenUsage usage, kj::ArrayPtr<const byte> token) {\n  JSG_FAIL_REQUIRE(DOMDataCloneError, \"This Worker is not able to deserialize ServiceStubs.\");\n}\n\nkj::Own<IoChannelFactory::ActorClassChannel> IoChannelFactory::actorClassFromToken(\n    ChannelTokenUsage usage, kj::ArrayPtr<const byte> token) {\n  JSG_FAIL_REQUIRE(\n      DOMDataCloneError, \"This Worker is not able to deserialize Durable Object class stubs.\");\n}\n\nvoid IoChannelFactory::ActorChannel::requireAllowsTransfer() {\n  JSG_FAIL_REQUIRE(DOMDataCloneError,\n      \"Durable Object stubs cannot (yet) be transferred between Workers. This will change in \"\n      \"a future version.\");\n}\n\nuint IoChannelCapTableEntry::getChannelNumber(Type expectedType) {\n  // A type mismatch shouldn't be possible as long as attackers cannot tamper with the\n  // serialization, but we do the check to catch bugs.\n  KJ_REQUIRE(type == expectedType,\n      \"IoChannelCapTableEntry type didn't match serialized JavaScript API type.\");\n\n  return channel;\n}\n\nkj::Own<Frankenvalue::CapTableEntry> IoChannelCapTableEntry::clone() {\n  return kj::heap<IoChannelCapTableEntry>(type, channel);\n}\n\nkj::Own<Frankenvalue::CapTableEntry> IoChannelCapTableEntry::threadSafeClone() const {\n  return kj::heap<IoChannelCapTableEntry>(type, channel);\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/io-channels.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/io/actor-id.h>\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/io/frankenvalue.h>\n#include <workerd/io/io-util.h>\n#include <workerd/io/trace.h>\n#include <workerd/io/worker-source.h>\n\n#include <capnp/capability.h>  // for Capability\n#include <kj/debug.h>\n#include <kj/string.h>\n\nnamespace kj {\nclass HttpClient;\nclass Network;\n}  // namespace kj\n\nnamespace workerd {\n\nclass WorkerInterface;\n\n// Interface for talking to the Cache API. Needs to be declared here so that IoContext can\n// contain it.\nclass CacheClient {\n public:\n  struct SubrequestMetadata {\n    // The `request.cf` blob, JSON-encoded.\n    kj::Maybe<kj::String> cfBlobJson;\n\n    // Specifies the parent span for the subrequest for tracing purposes.\n    SpanParent parentSpan;\n\n    // Serialized JSON value to pass in ew_compat field of control header to FL. This has the same\n    // semantics as the field in IoChannelFactory::SubrequestMetadata.\n    kj::Maybe<kj::String> featureFlagsForFl;\n  };\n\n  // Get the default namespace, i.e. the one that fetch() will use for caching.\n  //\n  // The returned client is intended to be used for one request.\n  virtual kj::Own<kj::HttpClient> getDefault(SubrequestMetadata metadata) = 0;\n\n  // Get an HttpClient for the given cache namespace.\n  virtual kj::Own<kj::HttpClient> getNamespace(kj::StringPtr name, SubrequestMetadata metadata) = 0;\n};\n\n// A timer instance, used to back Date.now(), setTimeout(), etc. This object may implement\n// Spectre mitigations.\nclass TimerChannel {\n public:\n  // Call each time control enters the isolate to set up the clock.\n  virtual void syncTime() = 0;\n\n  // Return the current time. `nextTimeout` is the time at which the next setTimeout() callback\n  // is scheduled; implementations performing Spectre mitigations should clamp to this value so\n  // that Date.now() never goes backwards or reveals timing side channels.\n  virtual kj::Date now(kj::Maybe<kj::Date> nextTimeout = kj::none) = 0;\n\n  // Returns a promise that resolves once `now() >= when`.\n  virtual kj::Promise<void> atTime(kj::Date when) = 0;\n\n  // Returns a promise that resolves after some time. This is intended to be used for implementing\n  // time limits on some sort of operation, not for implementing application-driven timing, as it does\n  // not implement any Spectre mitigations.\n  virtual kj::Promise<void> afterLimitTimeout(kj::Duration t) = 0;\n};\n\nclass WorkerStubChannel;\nstruct DynamicWorkerSource;\n\n// Each IoContext has a set of \"channels\" on which outgoing I/O can be initiated. All outgoing\n// I/O occurs through these channels. Think of these kind of like file descriptors. They are\n// often associated with bindings.\n//\n// For example, any call to fetch() uses a subrequest channel. The global fetch() specifically\n// uses subrequest channel zero. Each service binding (aka worker-to-worker binding) is assigned\n// a unique subrequest channel number, and calling `binding.fetch()` sends the request to the\n// given channel.\n//\n// While most channels are SubrequestChannels, other channel types exist to handle I/O that is\n// not subrequest-shaped. For example, a Workers Analytics Engine binding uses a logging channel.\n//\n// Note that each type of channel has its own number space. That is, subrequest channel 5 and\n// logging channel 5 are not related.\n//\n// The reason we have channels, rather than binding API objects directly holding the I/O objects,\n// is because binding API objects live across multiple requests, but the I/O objects may differ\n// from request to request.\n//\n// This class encapsulates all outgoing I/O that a Worker can perform. It does not cover incoming\n// I/O, i.e. the event that started the Worker. If IoChannelFactory is implemented such that\n// all methods throw exceptions, then the Worker will be completely unable to communicate with\n// anything in the world except for the client -- this is a useful property for sandboxing!\nclass IoChannelFactory {\n public:\n  // Contains metadata attached to an outgoing subrequest from a worker, independent of the type\n  // of request.\n  struct SubrequestMetadata {\n    // The `request.cf` blob, JSON-encoded.\n    kj::Maybe<kj::String> cfBlobJson;\n\n    // Specifies the parent span for the subrequest for tracing purposes.\n    SpanParent parentSpan = SpanParent(nullptr);\n\n    // Serialized JSON value to pass in ew_compat field of control header to FL. If this subrequest\n    // does not go directly to FL, this value is ignored. Flags marked with `$neededByFl` in\n    // `compatibility-date.capnp` end up here.\n    kj::Maybe<kj::String> featureFlagsForFl;\n\n    // Timestamp for when a subrequest is started. (ms since the Unix Epoch)\n    double startTime = dateNow();\n  };\n\n  // Parameters that can influence the version of a worker that is used to serve a subrequest.\n  struct VersionRequest {\n    // Request a version within the given cohort.\n    kj::Maybe<kj::String> cohort;\n\n    VersionRequest clone() const {\n      return {\n        .cohort = cohort.map([](const kj::String& s) { return kj::str(s); }),\n      };\n    }\n  };\n\n  virtual kj::Own<WorkerInterface> startSubrequest(uint channel, SubrequestMetadata metadata) = 0;\n\n  // Get a Cap'n Proto RPC capability. Various binding types are backed by capabilities.\n  //\n  // Note that some other channel types, like actor channels, may actually be wrappers around\n  // capability channels, and so may share the same channel number space, but this shouldn't be\n  // assumed.\n  virtual capnp::Capability::Client getCapability(uint channel) = 0;\n\n  // Get a CacheClient, used to implement the Cache API.\n  virtual kj::Own<CacheClient> getCache() = 0;\n\n  // Get the singleton timer instance, used to back Date.now(), setTimeout(), etc. This object\n  // may implement Spectre mitigations.\n  virtual TimerChannel& getTimer() = 0;\n\n  // Write a log message to a logfwdr channel. Each log binding has its own channel number.\n  //\n  // The IoChannelFactory already knows which member of the overall message union is expected to\n  // be filled in for this channel. That member will be initialized as a pointer, and then\n  // `buildMessage` will be invoked to fill in the pointer's content. The callback is always\n  // executed immediately, before `writeLogfwdr()` returns a promise.\n  virtual kj::Promise<void> writeLogfwdr(\n      uint channel, kj::FunctionParam<void(capnp::AnyPointer::Builder)> buildMessage) = 0;\n\n  enum ChannelTokenUsage {\n    // Token is to be sent over RPC and hence will be converted back into a SubrequestChannel\n    // soon. Such tokens have limited lifetime but are otherwise irrevocable.\n    RPC,\n\n    // Token is to be stored in long-term storage. At present this must only be allowed to be\n    // used in workers that have the allow_irrevocable_stub_storage compat flag (checked by the\n    // caller). In the future the format for such tokens will change.\n    STORAGE,\n  };\n\n  // Object representing somehere where generic workers subrequests can be sent. Multiple requests\n  // may be sent. This is an I/O type so it is only valid within the `IoContext` where it was\n  // created.\n  class SubrequestChannel: public kj::Refcounted, public Frankenvalue::CapTableEntry {\n   public:\n    // Start a new request to this target.\n    //\n    // Note that not all `metadata` properties make sense here, but it didn't seem worth defining\n    // a new struct type. `cfBlobJson` and `parentSpan` make sense, but `featureFlagsForFl` and\n    // `dynamicDispatchTarget` do not.\n    //\n    // Note that the caller is expected to keep the SubrequestChannel alive until it is done with\n    // the returned WorkerInterface.\n    virtual kj::Own<WorkerInterface> startRequest(SubrequestMetadata metadata) = 0;\n\n    kj::Own<CapTableEntry> clone() override final {\n      return kj::addRef(*this);\n    }\n\n    // Throws a JSG error if a Fetcher backed by this channel should not be serialized and passed\n    // to other workers. The default implementation throws a generic error, but subclasses may\n    // specialize with better errror messages -- or override to just return in order to permit the\n    // serialization.\n    //\n    // This check is necessary especially in workerd in order to block serialization of types that,\n    // in production, would be difficult or impossible to serialize. In particular,\n    // dynamically-loaded workers cannot be serialized because the system does not know how to\n    // reconstruct a dynamically-loaded worker from scratch.\n    virtual void requireAllowsTransfer() = 0;\n\n    // Get a token representing this SubrequestChannel which can be converted back into a\n    // SubrequestChannel using subrequestChannelFromToken(). Default implementation throws a\n    // TypeError.\n    virtual kj::Array<byte> getToken(ChannelTokenUsage usage);\n  };\n\n  // Obtain an object representing a particular subrequest channel.\n  //\n  // getSubrequestChannel(i).startRequest(meta) is exactly equivalent to startSubrequest(i, meta).\n  // The reason to use this instead is when the channel is not necessarily going to be used to\n  // start a subrequest immediately, but instead is going to be passed around as a capability.\n  //\n  // `props` and `versionRequest` can only be specified if this is a loopback channel (i.e. from\n  // ctx.exports). For any other channel, they will throw.\n  //\n  // TODO(cleanup): Consider getting rid of `startSubrequest()` in favor of this.\n  virtual kj::Own<SubrequestChannel> getSubrequestChannel(uint channel,\n      kj::Maybe<Frankenvalue> props = kj::none,\n      kj::Maybe<VersionRequest> versionRequest = kj::none) = 0;\n\n  // Stub for a remote actor. Allows sending requests to the actor.\n  class ActorChannel: public SubrequestChannel {\n   public:\n    // At present there are no methods beyond what `SubrequestChannel` defines. However, it's\n    // easy to imagine that actor stubs may have more functionality than just sending requests\n    // someday, so we keep this as a separate type.\n\n    // For now, actor stubs are not transferrable -- but we do intend to change that at some point.\n    void requireAllowsTransfer() override final;\n  };\n\n  // Get an actor stub from the given namespace for the actor with the given ID.\n  //\n  // `id` must have been constructed using one of the `ActorIdFactory` instances corresponding to\n  // one of the worker's bindings, however it doesn't necessarily have to be from the the correct\n  // `ActorIdFactory` -- if it's from some other factory, the method will throw an appropriate\n  // exception.\n  virtual kj::Own<ActorChannel> getGlobalActor(uint channel,\n      const ActorIdFactory::ActorId& id,\n      kj::Maybe<kj::String> locationHint,\n      ActorGetMode mode,\n      bool enableReplicaRouting,\n      ActorRoutingMode routingMode,\n      SpanParent parentSpan,\n      kj::Maybe<ActorVersion> version) = 0;\n\n  // Get an actor stub from the given namespace for the actor with the given name.\n  virtual kj::Own<ActorChannel> getColoLocalActor(\n      uint channel, kj::StringPtr id, SpanParent parentSpan) = 0;\n\n  // ActorClassChannel is a reference to an actor class in another worker. This class acts as a\n  // token which can be passed into other interfaces that might use the actor class, particularly\n  // Worker::Actor::FacetManager.\n  class ActorClassChannel: public kj::Refcounted, public Frankenvalue::CapTableEntry {\n   public:\n    kj::Own<CapTableEntry> clone() override final {\n      return kj::addRef(*this);\n    }\n\n    // Same as the corresponding methods on SubrequestChannel.\n    virtual void requireAllowsTransfer() = 0;\n    virtual kj::Array<byte> getToken(ChannelTokenUsage usage);\n\n    // This class has no functional methods, since it serves as a token to be passed to other\n    // interfaces (namely the facets API).\n  };\n\n  // Get an actor class binding corresponding to the given channel number.\n  //\n  // `props` can only be specified if this is a loopback channel (i.e. from ctx.exports). For any\n  // other channel, it will throw.\n  virtual kj::Own<ActorClassChannel> getActorClass(\n      uint channel, kj::Maybe<Frankenvalue> props = kj::none) {\n    // TODO(cleanup): Remove this once the production runtime has implemented this.\n    KJ_UNIMPLEMENTED(\"This runtime doesn't support actor class channels.\");\n  }\n\n  // Aborts all actors except those in namespaces marked with `preventEviction`.\n  virtual void abortAllActors(kj::Maybe<kj::Exception&> reason) {\n    KJ_UNIMPLEMENTED(\"Only implemented by single-tenant workerd runtime\");\n  }\n\n  // Use a dynamic Worker loader binding to obtain an Worker by name. If name is null, or if the named Worker doesn't already exist, the callback will be called to fetch the source code from which the Worker should be created.\n  virtual kj::Own<WorkerStubChannel> loadIsolate(uint loaderChannel,\n      kj::Maybe<kj::String> name,\n      kj::Function<kj::Promise<DynamicWorkerSource>()> fetchSource) {\n    JSG_FAIL_REQUIRE(Error, \"Dynamic worker loading is not supported by this runtime.\");\n  }\n\n  // Get the network for connecting to workerd debug ports.\n  // This is used by the workerdDebugPort binding to connect to remote workerd instances.\n  virtual kj::Network& getWorkerdDebugPortNetwork() {\n    JSG_FAIL_REQUIRE(Error, \"WorkerdDebugPort bindings are not supported by this runtime.\");\n  }\n\n  // Converts a token created with {SubrequestChannel,ActorClassChannel}::getToken() back into a\n  // live channel. Default implementations throw.\n  virtual kj::Own<SubrequestChannel> subrequestChannelFromToken(\n      ChannelTokenUsage usage, kj::ArrayPtr<const byte> token);\n  virtual kj::Own<ActorClassChannel> actorClassFromToken(\n      ChannelTokenUsage usage, kj::ArrayPtr<const byte> token);\n};\n\n// Represents a dynamically-loaded Worker to which requests can be sent.\n//\n// This object is returned before the Worker actually loads, so if any errors occur while loading,\n// any requests sent to the Worker will fail, propagating the exception.\nclass WorkerStubChannel {\n public:\n  virtual kj::Own<IoChannelFactory::SubrequestChannel> getEntrypoint(\n      kj::Maybe<kj::String> name, Frankenvalue props) = 0;\n\n  virtual kj::Own<IoChannelFactory::ActorClassChannel> getActorClass(\n      kj::Maybe<kj::String> name, Frankenvalue props) = 0;\n\n  // TODO(someday): Allow caller to enumerate entrypoints?\n};\n\n// Source code needed to dynamically load a Worker.\nstruct DynamicWorkerSource {\n  WorkerSource source;\n  CompatibilityFlags::Reader compatibilityFlags;\n\n  // `env` object to pass to the loaded worker. Can contain anything that can be serialized to\n  // a `Frankenvalue` (which should eventually include all binding types, RPC stubs, etc.).\n  Frankenvalue env;\n\n  // Where should global fetch() (and connect()) be sent?\n  kj::Maybe<kj::Own<IoChannelFactory::SubrequestChannel>> globalOutbound;\n\n  // Tail workers that should receive tail events for invocations of the dynamic worker.\n  kj::Array<kj::Own<IoChannelFactory::SubrequestChannel>> tails;\n  kj::Array<kj::Own<IoChannelFactory::SubrequestChannel>> streamingTails;\n\n  // Owns any data structures pointed into by the other members. (E.g. `source` contains a lot of\n  // `StringPtr`s; `ownContent` owns the backing buffer for them.)\n  kj::Own<void> ownContent;\n\n  // Indicates whether ownContent is holding onto a Cap'n Proto RPC response. This is important\n  // to know because such an RPC response must be destroyed on the same thread where it was\n  //  created, and generally should be destroyed \"relatively soon\", not kept around forever. If\n  //  this is false, then it is perfectly safe to transfer ownership of ownContent between threads\n  //  and keep it alive indefinitely long.\n  bool ownContentIsRpcResponse = true;\n\n  // Clone the DynamicWorkerSource. Caller must provide a new reference to use as `ownContent`,\n  // which must be a refcount on the same content since the pointers will not be updated. Note\n  // that if `ownContentIsRpcResponse` is false, then `ownContent` could be passed off to other\n  // threads and as such the refcount had better be atomic.\n  DynamicWorkerSource clone(kj::Own<void> newOwnContent) {\n    return {\n      .source = source.clone(),\n      .compatibilityFlags = compatibilityFlags,\n      .env = env.clone(),\n      .globalOutbound = mapAddRef(globalOutbound),\n      .tails = KJ_MAP(t, tails) { return kj::addRef(*t); },\n      .streamingTails = KJ_MAP(t, streamingTails) { return kj::addRef(*t); },\n      .ownContent = kj::mv(newOwnContent),\n      .ownContentIsRpcResponse = ownContentIsRpcResponse,\n    };\n  }\n};\n\n// A Frankenvalue::CapTableEntry which directly references a numbered I/O channel. This is ONLY\n// valid to use when the `Frankenvalue` is being deserialized as the `env` object of an isolate.\n// The caller should use frankenvalue.rewriteCaps() to rewrite the cap table entries into\n// IoChannelCapTableEntry, building the I/O channel table as it goes.\nclass IoChannelCapTableEntry final: public Frankenvalue::CapTableEntry {\n public:\n  enum Type {\n    SUBREQUEST,\n    ACTOR_CLASS,\n    // TODO(someday): Other channel types, maybe.\n  };\n\n  IoChannelCapTableEntry(Type type, uint channel): type(type), channel(channel) {}\n\n  Type getType() const {\n    return type;\n  }\n\n  // Throws if type doesn't match.\n  uint getChannelNumber(Type expectedType);\n\n  kj::Own<CapTableEntry> clone() override;\n  kj::Own<CapTableEntry> threadSafeClone() const override;\n\n private:\n  Type type;\n  uint channel;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/io-context-test.js",
    "content": "import assert from 'node:assert';\n\nexport let testHangingPromise = {\n  async test(controller, env, ctx) {\n    await assert.rejects(\n      ctx.exports.testHangingPromise.fetch('http://example.com'),\n      {\n        name: 'Error',\n        message:\n          \"The Workers runtime canceled this request because it detected that your Worker's \" +\n          'code had hung and would never generate a response. Refer to: ' +\n          'https://developers.cloudflare.com/workers/observability/errors/',\n      }\n    );\n  },\n\n  async fetch(req, env, ctx) {\n    await new Promise((resolve) => {});\n  },\n};\n\nlet abortCalled = false;\nlet abortReturned = false;\n\nexport let testAbort = {\n  async test(controller, env, ctx) {\n    await assert.rejects(ctx.exports.testAbort.fetch('http://example.com'), {\n      name: 'Error',\n      message: 'test abort reason',\n    });\n\n    assert.strictEqual(abortCalled, true);\n    assert.strictEqual(abortReturned, false);\n  },\n\n  async fetch(req, env, ctx) {\n    abortCalled = true;\n    ctx.abort(new Error('test abort reason'));\n    abortReturned = true; // shouldn't get here!\n  },\n};\n"
  },
  {
    "path": "src/workerd/io/io-context-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"io-context-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"io-context-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",  # for assert\n          \"experimental\",   # for ctx.abort\n          \"enable_ctx_exports\",\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/io/io-context.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"io-context.h\"\n\n#include <workerd/io/io-gate.h>\n#include <workerd/io/tracer.h>\n#include <workerd/io/worker.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/setup.h>\n#include <workerd/util/own-util.h>\n#include <workerd/util/sentry.h>\n#include <workerd/util/uncaught-exception-source.h>\n\n#include <kj/debug.h>\n\n#include <cmath>\n#include <map>\n\nnamespace workerd {\n\nstatic thread_local IoContext* threadLocalRequest = nullptr;\n\nSuppressIoContextScope::SuppressIoContextScope(): cached(threadLocalRequest) {\n  threadLocalRequest = nullptr;\n}\n\nSuppressIoContextScope::~SuppressIoContextScope() noexcept(false) {\n  threadLocalRequest = cached;\n}\n\nstatic const kj::EventLoopLocal<int> threadId;\n\nstatic void* getThreadId() {\n  return threadId.get();\n}\n\nclass IoContext::TimeoutManagerImpl final: public TimeoutManager {\n public:\n  class TimeoutState;\n  using Map = std::map<TimeoutId, TimeoutState>;\n  using Iterator = Map::iterator;\n\n  TimeoutManagerImpl() = default;\n  KJ_DISALLOW_COPY_AND_MOVE(TimeoutManagerImpl);\n\n  TimeoutId setTimeout(\n      IoContext& context, TimeoutId::Generator& generator, TimeoutParameters params) override {\n    // Verify the generator is from the correct ServiceWorkerGlobalScope. If we have been passed a\n    // different `timeoutIdGenerator`, then that means this IoContext is active at a time when\n    // JavaScript in a different V8 context is executing. This _should_ be impossible, but we're\n    // occasionally seeing timeout ID collision assertion failures in `addState()`, and one possible\n    // explanation is that an IoContext is somehow current for a different V8 context.\n    //\n    // TODO(cleanup): Find a more general way to assert that the JS API surface is being used under\n    //   the correct IoContext, get rid of this function's `generator` parameter, and instead rely\n    //   on the IoContext to provide the generator.\n    KJ_ASSERT(&generator == &context.getCurrentLock().getTimeoutIdGenerator(),\n        \"TimeoutId Generator mismatch - using a generator from wrong ServiceWorkerGlobalScope\");\n\n    auto [id, it] = addState(generator, kj::mv(params));\n    setTimeoutImpl(context, it);\n    return id;\n  }\n\n  void clearTimeout(IoContext&, TimeoutId id) override;\n\n  size_t getTimeoutCount() const override {\n    return timeoutsStarted - timeoutsFinished;\n  }\n\n  kj::Maybe<kj::Date> getNextTimeout() const override {\n    if (timeoutTimes.size() == 0) {\n      return kj::none;\n    } else {\n      return timeoutTimes.begin()->key.when;\n    }\n  }\n\n  void cancelAll() override {\n    timerTask = nullptr;\n    timeouts.clear();\n    timeoutTimes.clear();\n  }\n\n private:\n  struct IdAndIterator {\n    TimeoutId id;\n    Iterator it;\n  };\n  IdAndIterator addState(TimeoutId::Generator& generator, TimeoutParameters params);\n\n  void setTimeoutImpl(IoContext& context, Iterator it);\n\n  // A pair of a Date and a numeric ID, used as entry in timeoutTimes set, below.\n  struct TimeoutTime {\n    kj::Date when;\n    uint tiebreaker;  // Unique number, in case two timeouts target same time.\n\n    inline bool operator<(const TimeoutTime& other) const {\n      if (when < other.when) return true;\n      if (when > other.when) return false;\n      return tiebreaker < other.tiebreaker;\n    }\n    inline bool operator==(const TimeoutTime& other) const {\n      return when == other.when && tiebreaker == other.tiebreaker;\n    }\n  };\n\n  // Tracks registered timeouts sorted by the next time the timeout is expected to fire.\n  //\n  // The associated fulfiller should be fulfilled when the time has been reached AND all previous\n  // timeouts have completed.\n  kj::TreeMap<TimeoutTime, kj::Own<kj::PromiseFulfiller<void>>> timeoutTimes;\n  uint timeoutTimesTiebreakerCounter = 0;\n\n  uint timeoutsStarted = 0;\n  uint timeoutsFinished = 0;\n  Map timeouts;\n\n  // Promise that is waiting for the closest timeout, and will fulfill its fulfiller. We only ever\n  // actually wait on the next timeout in `timeoutTasks`, so that we can't fulfill timer callbacks\n  // out-of-order. This task gets replaced each time the lead timeout changes.\n  kj::Promise<void> timerTask = nullptr;\n\n  // Must be called any time timeoutTimes.begin() changes.\n  void resetTimerTask(TimerChannel& timerChannel);\n};\n\nclass IoContext::TimeoutManagerImpl::TimeoutState {\n public:\n  TimeoutState(TimeoutManagerImpl& manager, TimeoutParameters params);\n  ~TimeoutState();\n\n  void trigger(Worker::Lock& lock);\n  void cancel();\n\n  TimeoutManagerImpl& manager;\n  TimeoutParameters params;\n\n  bool isCanceled = false;\n  bool isRunning = false;\n\n  kj::Maybe<kj::Promise<void>> maybePromise;\n};\n\nIoContext::IoContext(ThreadContext& thread,\n    kj::Own<const Worker> workerParam,\n    kj::Maybe<Worker::Actor&> actorParam,\n    kj::Own<LimitEnforcer> limitEnforcerParam)\n    : thread(thread),\n      worker(kj::mv(workerParam)),\n      actor(actorParam),\n      limitEnforcer(kj::mv(limitEnforcerParam)),\n      threadId(getThreadId()),\n      deleteQueue(kj::arc<DeleteQueue>()),\n      cachePutSerializer(kj::READY_NOW),\n      timeoutManager(kj::heap<TimeoutManagerImpl>()),\n      waitUntilTasks(*this),\n      tasks(*this),\n      deleteQueueSignalTask(startDeleteQueueSignalTask(this)) {\n  kj::PromiseFulfillerPair<void> paf = kj::newPromiseAndFulfiller<void>();\n  abortFulfiller = kj::mv(paf.fulfiller);\n  abortPromise = paf.promise.fork();\n\n  // Arrange to complain if execution resource limits (CPU/memory) are exceeded.\n  auto makeLimitsPromise = [this]() {\n    auto promise = limitEnforcer->onLimitsExceeded();\n    if (isInspectorEnabled()) {\n      // Arrange to report the problem to the inspector in addition to aborting.\n      // TODO(cleanup): This is weird. Should it go somewhere else?\n      promise = (kj::coCapture([this, promise = kj::mv(promise)]() mutable -> kj::Promise<void> {\n        kj::Maybe<kj::Exception> maybeException;\n        try {\n          co_await promise;\n        } catch (...) {\n          // Just capture the exception here, we'll handle is below since we cannot have\n          // a co_await in the body of a catch clause.\n          maybeException = kj::getCaughtExceptionAsKj();\n        }\n\n        KJ_IF_SOME(exception, maybeException) {\n          Worker::AsyncLock asyncLock = co_await worker->takeAsyncLockWithoutRequest(nullptr);\n          worker->runInLockScope(asyncLock, [&](Worker::Lock& lock) {\n            lock.logUncaughtException(\n                jsg::extractTunneledExceptionDescription(exception.getDescription()));\n            kj::throwFatalException(kj::mv(exception));\n          });\n        }\n      }))();\n    }\n\n    return promise;\n  };\n  KJ_IF_SOME(cb, this->worker->getIsolate().getCpuLimitNearlyExceededCallback()) {\n    limitEnforcer->setCpuLimitNearlyExceededCallback(kj::mv(cb));\n  }\n\n  // Arrange to abort when limits expire.\n  abortWhen(makeLimitsPromise());\n\n  KJ_IF_SOME(a, actor) {\n    // Arrange to complain if the input gate is broken, which indicates a critical section failed\n    // and the actor can no longer be used.\n    abortWhen(a.getInputGate().onBroken());\n\n    // Also complain if the output gate is broken, which indicates a critical storage failure that\n    // means we cannot continue execution. (In fact, we need to retroactively pretend that previous\n    // execution didn't happen, but that is taken care of elsewhere.)\n    abortWhen(a.getOutputGate().onBroken());\n  }\n}\n\nIoContext::IncomingRequest::IoContext_IncomingRequest(kj::Own<IoContext> contextParam,\n    kj::Own<IoChannelFactory> ioChannelFactoryParam,\n    kj::Own<RequestObserver> metricsParam,\n    kj::Maybe<kj::Own<BaseTracer>> workerTracer,\n    kj::Maybe<tracing::InvocationSpanContext> maybeTriggerInvocationSpan)\n    : context(kj::mv(contextParam)),\n      metrics(kj::mv(metricsParam)),\n      workerTracer(kj::mv(workerTracer)),\n      ioChannelFactory(kj::mv(ioChannelFactoryParam)),\n      maybeTriggerInvocationSpan(kj::mv(maybeTriggerInvocationSpan)) {}\n\ntracing::InvocationSpanContext& IoContext::IncomingRequest::getInvocationSpanContext() {\n  // Creating a new InvocationSpanContext can be a bit expensive since it needs to\n  // generate random IDs, so we only create it lazily when requested, which should\n  // only be when tracing is enabled and we need to record spans.\n  KJ_IF_SOME(ctx, invocationSpanContext) {\n    return ctx;\n  }\n\n  invocationSpanContext = tracing::InvocationSpanContext::newForInvocation(\n      maybeTriggerInvocationSpan.map(\n          [](auto& trigger) -> tracing::InvocationSpanContext& { return trigger; }),\n      context->getEntropySource());\n  return KJ_ASSERT_NONNULL(invocationSpanContext);\n}\n\n// A call to delivered() implies a promise to call drain() later (or one of the other methods\n// that sets waitedForWaitUntil). So, we can now safely add the request to\n// context->incomingRequests, which implies taking responsibility for draining on the way out.\nvoid IoContext::IncomingRequest::delivered(kj::SourceLocation location) {\n  KJ_REQUIRE(!wasDelivered, \"delivered() can only be called once\");\n  if (!context->incomingRequests.empty()) {\n    // There is already an IncomingRequest running in this context, and we're going to make it no\n    // longer current. Make sure to attribute accumulated CPU time to it.\n    auto& oldFront = context->incomingRequests.front();\n    context->limitEnforcer->reportMetrics(*oldFront.metrics);\n\n    KJ_IF_SOME(f, oldFront.drainFulfiller) {\n      // Allow the previous current IncomingRequest to finish draining, because the new request\n      // will take over responsibility for completing any tasks that aren't done yet.\n      f.get()->fulfill();\n    }\n  }\n\n  context->incomingRequests.addFront(*this);\n  wasDelivered = true;\n  deliveredLocation = location;\n  metrics->delivered();\n\n  KJ_IF_SOME(workerTracer, workerTracer) {\n    currentUserTraceSpan = workerTracer->makeUserRequestSpan();\n  }\n\n  KJ_IF_SOME(a, context->actor) {\n    // Re-synchronize the timer and top up limits for every new incoming request to an actor.\n    ioChannelFactory->getTimer().syncTime();\n    context->limitEnforcer->topUpActor();\n\n    // Run the Actor's constructor if it hasn't been run already.\n    a.ensureConstructed(*context);\n\n    // Record a new incoming request to actor metrics.\n    a.getMetrics().startRequest();\n  }\n}\n\nkj::Date IoContext::IncomingRequest::now(kj::Maybe<kj::Date> nextTimeout) {\n  metrics->clockRead();\n  return ioChannelFactory->getTimer().now(kj::mv(nextTimeout));\n}\n\nIoContext::IncomingRequest::~IoContext_IncomingRequest() noexcept(false) {\n  if (!wasDelivered) {\n    KJ_IF_SOME(w, workerTracer) {\n      w->markUnused();\n    }\n    // Request was never added to context->incomingRequests in the first place.\n    return;\n  }\n\n  // Hack: We need to report an accurate time stamps for the STW outcome event, but the timer may\n  // not be available when the outcome event gets reported. Define the outcome event time as the\n  // time when the incoming request shuts down.\n  KJ_IF_SOME(w, workerTracer) {\n    w->recordTimestamp(now());\n  }\n\n  if (&context->incomingRequests.front() == this) {\n    // We're the current request, make sure to consume CPU time attribution.\n    context->limitEnforcer->reportMetrics(*metrics);\n    context->lastDeliveredLocation = deliveredLocation;\n\n    if (!waitedForWaitUntil && !context->waitUntilTasks.isEmpty()) {\n      KJ_LOG(WARNING, \"failed to invoke drain() on IncomingRequest before destroying it\",\n          kj::getStackTrace());\n    }\n  }\n\n  KJ_IF_SOME(a, context->actor) {\n    a.getMetrics().endRequest();\n  }\n  context->worker->getIsolate().completedRequest();\n  metrics->jsDone();\n\n  if (context->isShared()) {\n    // This context is not about to be destroyed when we drop it, but if it was aborted, we would\n    // prefer for it to get cleaned up promptly.\n\n    KJ_IF_SOME(e, context->abortException) {\n      // The context was aborted. It's possible that the event ended with background work still\n      // scheduled, because `drain()` ends early on abort. We should cancel that background work\n      // now.\n      //\n      // We couldn't do this in abort() because it can be called from inside a task that could\n      // be canceled, and a self-cancellation would lead to a crash.\n\n      if (!context->canceler.isEmpty()) {\n        context->canceler.cancel(e);\n      }\n      context->timeoutManager->cancelAll();\n      context->tasks.clear();\n      context->waitUntilTasks.clear();\n    }\n  }\n\n  // Remove incoming request after canceling waitUntil tasks, which may have spans attached that\n  // require accessing a timer from the active request.\n  context->incomingRequests.remove(*this);\n}\n\nInputGate::Lock IoContext::getInputLock() {\n  return KJ_ASSERT_NONNULL(currentInputLock, \"no input lock available in this context\")\n      .addRef(getCurrentTraceSpan());\n}\n\nkj::Maybe<kj::Own<InputGate::CriticalSection>> IoContext::getCriticalSection() {\n  KJ_IF_SOME(l, currentInputLock) {\n    return l.getCriticalSection().map(\n        [](InputGate::CriticalSection& cs) { return kj::addRef(cs); });\n  } else {\n    return kj::none;\n  }\n}\n\nkj::Promise<void> IoContext::waitForOutputLocks() {\n  KJ_IF_SOME(p, waitForOutputLocksIfNecessary()) {\n    return kj::mv(p);\n  } else {\n    return kj::READY_NOW;\n  }\n}\n\nbool IoContext::hasOutputGate() {\n  return actor != kj::none;\n}\n\nkj::Maybe<kj::Promise<void>> IoContext::waitForOutputLocksIfNecessary() {\n  return actor.map(\n      [this](Worker::Actor& actor) { return actor.getOutputGate().wait(getCurrentTraceSpan()); });\n}\n\nkj::Maybe<IoOwn<kj::Promise<void>>> IoContext::waitForOutputLocksIfNecessaryIoOwn() {\n  return waitForOutputLocksIfNecessary().map(\n      [this](kj::Promise<void> promise) { return addObject(kj::heap(kj::mv(promise))); });\n}\n\nbool IoContext::isOutputGateBroken() {\n  KJ_IF_SOME(a, actor) {\n    return a.getOutputGate().isBroken();\n  } else {\n    return false;\n  }\n}\n\nbool IoContext::isInspectorEnabled() {\n  return worker->getIsolate().isInspectorEnabled();\n}\n\nbool IoContext::isFiddle() {\n  return thread.isFiddle();\n}\n\nbool IoContext::hasWarningHandler() {\n  return isInspectorEnabled() || getWorkerTracer() != kj::none ||\n      ::kj::_::Debug::shouldLog(::kj::LogSeverity::INFO);\n}\n\nvoid IoContext::logWarning(kj::StringPtr description) {\n  KJ_REQUIRE_NONNULL(currentLock).logWarning(description);\n}\n\nvoid IoContext::logWarningOnce(kj::StringPtr description) {\n  KJ_REQUIRE_NONNULL(currentLock).logWarningOnce(description);\n}\n\nvoid IoContext::logErrorOnce(kj::StringPtr description) {\n  KJ_REQUIRE_NONNULL(currentLock).logErrorOnce(description);\n}\n\nvoid IoContext::logUncaughtException(kj::StringPtr description) {\n  KJ_REQUIRE_NONNULL(currentLock).logUncaughtException(description);\n}\n\nvoid IoContext::logUncaughtException(\n    UncaughtExceptionSource source, const jsg::JsValue& exception, const jsg::JsMessage& message) {\n  KJ_REQUIRE_NONNULL(currentLock).logUncaughtException(source, exception, message);\n}\n\nvoid IoContext::logUncaughtExceptionAsync(\n    UncaughtExceptionSource source, kj::Exception&& exception) {\n  if (getWorkerTracer() == kj::none && !worker->getIsolate().isInspectorEnabled()) {\n    // We don't need to take the isolate lock as neither inspecting nor tracing is enabled. We\n    // do still want to syslog if relevant, but we can do that without a lock.\n    if (!jsg::isTunneledException(exception.getDescription()) &&\n        !jsg::isDoNotLogException(exception.getDescription()) &&\n        // TODO(soon): Figure out why client disconnects are getting logged here if we don't\n        // ignore DISCONNECTED. If we fix that, do we still want to filter these?\n        exception.getType() != kj::Exception::Type::DISCONNECTED) {\n      LOG_EXCEPTION(\"jsgInternalError\", exception);\n    } else {\n      KJ_LOG(INFO, \"uncaught exception\", exception);  // Run with --verbose to see exception logs.\n    }\n    return;\n  }\n\n  struct RunnableImpl: public Runnable {\n    UncaughtExceptionSource source;\n    kj::Exception exception;\n\n    RunnableImpl(UncaughtExceptionSource source, kj::Exception&& exception)\n        : source(source),\n          exception(kj::mv(exception)) {}\n    void run(Worker::Lock& lock) override {\n      // TODO(soon): Add logUncaughtException to jsg::Lock.\n      lock.logUncaughtException(source, kj::mv(exception));\n    }\n  };\n\n  // Make sure this is logged even if another exception occurs trying to log it to the devtools inspector,\n  // e.g. if `runImpl` throws before calling logUncaughtException.\n  // This is useful for tests (and in fact only affects tests, since it's logged at an INFO level).\n  KJ_ON_SCOPE_FAILURE({ KJ_LOG(INFO, \"uncaught exception\", source, exception); });\n  RunnableImpl runnable(source, kj::mv(exception));\n  // TODO(perf): Is it worth using an async lock here? The only case where it really matters is\n  //   when a trace worker is active, but maybe they'll be more common in the future. To take an\n  //   async lock here, we'll probably have to update all the call sites of this method... ick.\n  kj::Maybe<RequestObserver&> metrics;\n  if (!incomingRequests.empty()) metrics = getMetrics();\n  runImpl(\n      runnable, Worker::Lock::TakeSynchronously(metrics), kj::none, Runnable::Exceptional(true));\n}\n\nvoid IoContext::abort(kj::Exception&& e) {\n  if (abortException != kj::none) {\n    return;\n  }\n  abortException = kj::cp(e);\n  KJ_IF_SOME(a, actor) {\n    // Stop the ActorCache from flushing any scheduled write operations to prevent any unnecessary\n    // or unintentional async work\n    a.shutdownActorCache(kj::cp(e));\n  }\n  abortFulfiller->reject(kj::mv(e));\n}\n\nvoid IoContext::abortWhen(kj::Promise<void> promise) {\n  // Unlike addTask(), abortWhen() always uses `tasks`, even in actors, because we do not want\n  // these tasks to block hibernation.\n  if (abortException == kj::none) {\n    tasks.add(promise.catch_([this](kj::Exception&& e) { abort(kj::mv(e)); }));\n  }\n}\n\nvoid IoContext::addTask(kj::Promise<void> promise) {\n  ++addTaskCounter;\n\n  // In Actors, we treat all tasks as wait-until tasks, because it's perfectly legit to start a\n  // task under one request and then expect some other request to handle it later.\n  if (actor != kj::none) {\n    addWaitUntil(kj::mv(promise));\n    return;\n  }\n\n  if (actor == kj::none) {\n    // This metric won't work correctly in actors since it's being tracked per-request, but tasks\n    // are not tied to requests in actors. So we just skip it in actors. (Actually this code path\n    // is not even executed in the actor case but I'm leaving the check in just in case that ever\n    // changes.)\n    auto& metrics = getMetrics();\n    if (metrics.getSpan().isObserved()) {\n      promise = promise.attach(metrics.addedContextTask());\n    }\n  }\n\n  tasks.add(kj::mv(promise));\n}\n\nvoid IoContext::addWaitUntil(kj::Promise<void> promise) {\n  if (actor == kj::none) {\n    // This metric won't work correctly in actors since it's being tracked per-request, but tasks\n    // are not tied to requests in actors. So we just skip it in actors.\n    auto& metrics = getMetrics();\n    if (metrics.getSpan().isObserved()) {\n      promise = promise.attach(metrics.addedWaitUntilTask());\n    }\n  }\n\n  if (incomingRequests.empty()) {\n    DEBUG_FATAL_RELEASE_LOG(WARNING, \"Adding task to IoContext with no current IncomingRequest\",\n        lastDeliveredLocation, kj::getStackTrace());\n  }\n\n  waitUntilTasks.add(kj::mv(promise));\n}\n\n// Mark ourselves so we know that we made a best effort attempt to wait for waitUntilTasks.\nkj::Promise<void> IoContext::IncomingRequest::drain() {\n  waitedForWaitUntil = true;\n\n  if (&context->incomingRequests.front() != this) {\n    // A newer request was received, so draining isn't our job.\n    return kj::READY_NOW;\n  }\n\n  kj::Promise<void> timeoutPromise = nullptr;\n  KJ_IF_SOME(a, context->actor) {\n    // For actors, all promises are canceled on actor shutdown, not on a fixed timeout,\n    // because work doesn't necessarily happen on a per-request basis in actors and we don't want\n    // work being unexpectedly canceled based on which request initiated it.\n    timeoutPromise = a.onShutdown();\n\n    // Also arrange to cancel the drain if a new request arrives, since it will take over\n    // responsibility for background tasks.\n    auto drainPaf = kj::newPromiseAndFulfiller<void>();\n    drainFulfiller = kj::mv(drainPaf.fulfiller);\n    timeoutPromise = timeoutPromise.exclusiveJoin(kj::mv(drainPaf.promise));\n  } else {\n    // For non-actor requests, apply the configured soft timeout, typically 30 seconds.\n    auto timeoutLogPromise = [this]() -> kj::Promise<void> {\n      return context->run([this](Worker::Lock&) {\n        context->logWarning(\n            \"waitUntil() tasks did not complete within the allowed time after invocation end and have been cancelled. \"\n            \"See: https://developers.cloudflare.com/workers/runtime-apis/context/#waituntil\");\n      });\n    };\n    timeoutPromise = context->limitEnforcer->limitDrain().then(kj::mv(timeoutLogPromise));\n  }\n  return context->waitUntilTasks.onEmpty()\n      .exclusiveJoin(kj::mv(timeoutPromise))\n      .exclusiveJoin(context->onAbort().catch_([](kj::Exception&&) {}));\n}\n\nkj::Promise<IoContext_IncomingRequest::FinishScheduledResult> IoContext::IncomingRequest::\n    finishScheduled() {\n  // TODO(someday): In principle we should be able to support delivering the \"scheduled\" event type\n  //   to an actor, and this may be important if we open up the whole of WorkerInterface to be\n  //   callable from any stub. However, the logic around async tasks would have to be different. We\n  //   cannot assume that just because an async task fails while the scheduled event is running,\n  //   that the scheduled event itself failed -- the failure could have been a task initiated by\n  //   an unrelated concurrent event.\n  KJ_ASSERT(context->actor == kj::none,\n      \"this code isn't designed to allow scheduled events to be delivered to actors\");\n\n  // Mark ourselves so we know that we made a best effort attempt to wait for waitUntilTasks.\n  KJ_ASSERT(context->incomingRequests.size() == 1);\n  context->incomingRequests.front().waitedForWaitUntil = true;\n\n  auto timeoutPromise = context->limitEnforcer->limitScheduled().then(\n      [] { return IoContext_IncomingRequest::FinishScheduledResult::TIMEOUT; });\n  return context->waitUntilTasks.onEmpty()\n      .then([]() { return IoContext_IncomingRequest::FinishScheduledResult::COMPLETED; })\n      .exclusiveJoin(kj::mv(timeoutPromise))\n      .exclusiveJoin(context->onAbort().then([] {\n    return IoContext_IncomingRequest::FinishScheduledResult::ABORTED;\n  }, [](kj::Exception&&) { return IoContext_IncomingRequest::FinishScheduledResult::ABORTED; }));\n}\n\nclass IoContext::PendingEvent: public kj::Refcounted {\n public:\n  explicit PendingEvent(IoContext& context): maybeContext(context) {}\n  ~PendingEvent() noexcept(false);\n  KJ_DISALLOW_COPY_AND_MOVE(PendingEvent);\n\n  kj::Maybe<IoContext&> maybeContext;\n};\n\nIoContext::~IoContext() noexcept(false) {\n  if (!canceler.isEmpty()) {\n    KJ_IF_SOME(e, abortException) {\n      // Assume the abort exception is why we are canceling.\n      canceler.cancel(e);\n    } else {\n      canceler.cancel(JSG_KJ_EXCEPTION(\n          FAILED, Error, \"The execution context responding to this call was canceled.\"));\n    }\n  }\n\n  // Detach the PendingEvent if it still exists.\n  KJ_IF_SOME(pe, pendingEvent) {\n    pe.maybeContext = kj::none;\n  }\n\n  // Kill the sentinel so that no weak references can refer to this IoContext anymore.\n  selfRef->invalidate();\n}\n\nIoContext::PendingEvent::~PendingEvent() noexcept(false) {\n  IoContext& context = KJ_UNWRAP_OR(maybeContext, {\n    // IoContext must have been destroyed before the PendingEvent was.\n    return;\n  });\n\n  context.pendingEvent = kj::none;\n\n  // We can't abort just yet. We need to run the event loop to see if any queued\n  // events come back into JavaScript. If registerPendingEvent() is called in the meantime, this\n  // will be canceled.\n  context.abortFromHangTask = Worker::AsyncLock::whenThreadIdle()\n                                  .then([&context = context]() noexcept {\n    // We have nothing left to do and no PendingEvent has been registered. Abort now.\n    return context.worker->takeAsyncLock(context.getMetrics())\n        .then([&context](Worker::AsyncLock asyncLock) { context.abortFromHang(asyncLock); });\n  }).eagerlyEvaluate(nullptr);\n}\n\nkj::Own<void> IoContext::registerPendingEvent() {\n  if (actor != kj::none) {\n    // Actors don't use the pending event system, because different requests to the same Actor are\n    // explicitly allowed to resolve each other's promises.\n    return {};\n  }\n\n  KJ_IF_SOME(pe, pendingEvent) {\n    return kj::addRef(pe);\n  } else {\n    KJ_IF_SOME(e, abortException) {\n      kj::throwFatalException(kj::cp(e));\n    }\n\n    // Cancel any already-scheduled finalization.\n    abortFromHangTask = kj::none;\n\n    auto result = kj::refcounted<PendingEvent>(*this);\n    pendingEvent = *result;\n    return result;\n  }\n}\n\nIoContext::TimeoutManagerImpl::TimeoutState::TimeoutState(\n    TimeoutManagerImpl& manager, TimeoutParameters params)\n    : manager(manager),\n      params(kj::mv(params)) {\n  ++manager.timeoutsStarted;\n}\n\nIoContext::TimeoutManagerImpl::TimeoutState::~TimeoutState() {\n  KJ_ASSERT(!isRunning);\n  if (!isCanceled) {\n    ++manager.timeoutsFinished;\n  }\n}\n\nvoid IoContext::TimeoutManagerImpl::TimeoutState::trigger(Worker::Lock& lock) {\n  isRunning = true;\n  auto cleanupGuard = kj::defer([&] { isRunning = false; });\n\n  // Now it's safe to call the user's callback.\n  KJ_IF_SOME(function, params.function) {\n    (function)(lock);\n  }\n}\n\nvoid IoContext::TimeoutManagerImpl::TimeoutState::cancel() {\n  if (isCanceled) {\n    return;\n  }\n\n  auto wasCanceled = isCanceled;\n  isCanceled = true;\n\n  if (!isRunning && !wasCanceled) {\n    params.function = kj::none;\n    maybePromise = kj::none;\n  }\n\n  ++manager.timeoutsFinished;\n}\n\nauto IoContext::TimeoutManagerImpl::addState(\n    TimeoutId::Generator& generator, TimeoutParameters params) -> IdAndIterator {\n  JSG_REQUIRE(getTimeoutCount() < MAX_TIMEOUTS, DOMQuotaExceededError,\n      \"You have exceeded the number of active timeouts you may set.\",\n      \" max active timeouts: \", MAX_TIMEOUTS, \", current active timeouts: \", getTimeoutCount(),\n      \", finished timeouts: \", timeoutsFinished);\n\n  auto id = generator.getNext();\n  auto [it, wasEmplaced] = timeouts.try_emplace(id, *this, kj::mv(params));\n  if (!wasEmplaced) {\n    // We shouldn't have reached here because the `TimeoutId::Generator` throws if it reaches\n    // Number.MAX_SAFE_INTEGER, much less wraps around the uint64_t number space. Let's throw with\n    // as many details as possible.\n    auto& state = it->second;\n    auto delay = state.params.msDelay;\n    auto repeat = state.params.repeat;\n    KJ_FAIL_ASSERT(\"Saw a timeout id collision\", getTimeoutCount(), timeoutsStarted, id.toNumber(),\n        delay, repeat);\n  }\n\n  return {id, it};\n}\n\nvoid IoContext::TimeoutManagerImpl::setTimeoutImpl(IoContext& context, Iterator it) {\n  auto& state = it->second;\n\n  auto stateGuard = kj::defer([&]() {\n    if (state.maybePromise == kj::none) {\n      // Something threw, erase the state.\n      timeouts.erase(it);\n    }\n  });\n\n  auto paf = kj::newPromiseAndFulfiller<void>();\n\n  // Schedule relative to Date.now() so the delay appears exact to the application.\n  auto when = context.now() + state.params.msDelay * kj::MILLISECONDS;\n  // TODO(cleanup): The manual use of run() here (including carrying over the critical section) is\n  //   kind of ugly, but using awaitIo() doesn't work here because we need the ability to cancel\n  //   the timer, so we don't want to addTask() it, which awaitIo() does implicitly.\n  auto promise =\n      paf.promise.then([this, &context, it, cs = context.getCriticalSection()]() mutable {\n    return context.run([this, &context, it](Worker::Lock& lock) mutable {\n      auto& state = it->second;\n\n      auto stateGuard = kj::defer([&] {\n        if (state.maybePromise == kj::none) {\n          // At the end of this block, there was no new timeout, so we should remove the state.\n          // Note that this can happen from cancelTimeout or a non-repeating timeout.\n          timeouts.erase(it);\n        }\n      });\n\n      if (state.isCanceled) {\n        // We've been canceled before running. Nothing more to do.\n        KJ_ASSERT(state.maybePromise == kj::none);\n        return;\n      }\n\n      KJ_IF_SOME(promise, state.maybePromise) {\n        // We could KJ_ASSERT_NONNULL(iter->second) instead if we are sure clearTimeout() couldn't\n        // race us. However, I'm not sure about that.\n\n        // First, move our timeout promise to the task set so it's safe to call clearInterval()\n        // inside the user's callback. We don't yet null out the Maybe<Promise>, because we need to\n        // be able to detect whether the user does call clearInterval(). We leave the actual map\n        // entry in place because this aids in reporting cross-request-context timeout cancellation\n        // errors to the user.\n        context.addTask(kj::mv(promise));\n\n        // Because Promise has an underspecified move ctor, we need to explicitly nullify the Maybe\n        // to indicate that we've consumed the promise.\n        state.maybePromise = kj::none;\n\n        // The user's callback might throw, but we need to at least attempt to reschedule interval\n        // callbacks even if they throw. This deferred action takes care of that. Note that we don't\n        // run the user's callback directly in this->run(), because that function throws a fatal\n        // exception if a JS exception is thrown, which complicates our logic here.\n        //\n        // TODO(perf): If we can guarantee that `timeout->second = nullptr` will never throw, it\n        //   might be worthwhile having an early-out path for non-interval timeouts.\n        kj::UnwindDetector unwindDetector;\n        KJ_DEFER(unwindDetector.catchExceptionsIfUnwinding([&] {\n          if (state.isCanceled) {\n            // The user's callback has called clearInterval(), nothing more to do.\n            KJ_ASSERT(state.maybePromise == kj::none);\n            return;\n          }\n\n          // If this is an interval task and the script has CPU time left, reschedule the task;\n          // otherwise leave the dead map entry in place.\n          if (state.params.repeat && context.limitEnforcer->getLimitsExceeded() == kj::none) {\n            setTimeoutImpl(context, it);\n          }\n        }););\n\n        state.trigger(lock);\n      }\n    }, kj::mv(cs));\n  }, [](kj::Exception&&) {});\n\n  promise = promise.attach(context.registerPendingEvent());\n\n  // Add an entry to the timeoutTimes map, to track when the nearest timeout is. Arrange for it\n  // to be removed when the promise completes.\n  TimeoutTime timeoutTimesKey{when, timeoutTimesTiebreakerCounter++};\n  timeoutTimes.insert(timeoutTimesKey, kj::mv(paf.fulfiller));\n  auto deferredTimeoutTimeRemoval = kj::defer([this, &context, timeoutTimesKey]() {\n    // If the promise is being destroyed due to IoContext teardown then IoChannelFactory may\n    // no longer be available, but we can just skip starting a new timer in that case as it'd be\n    // canceled anyway. Similarly we should skip rescheduling if the context has been aborted since\n    // there's no way the events can run anyway (and we'll cause trouble if `cancelAll()` is being\n    // called in ~IoContext_IncomingRequest).\n    if (context.selfRef->isValid() && context.abortException == kj::none) {\n      bool isNext = timeoutTimes.begin()->key == timeoutTimesKey;\n      timeoutTimes.erase(timeoutTimesKey);\n      if (isNext) resetTimerTask(context.getIoChannelFactory().getTimer());\n    }\n  });\n\n  if (timeoutTimes.begin()->key == timeoutTimesKey) {\n    resetTimerTask(context.getIoChannelFactory().getTimer());\n  }\n  promise = promise.attach(kj::mv(deferredTimeoutTimeRemoval));\n\n  if (context.actor != kj::none) {\n    // Add a wait-until task which resolves when this timer completes. This ensures that\n    // `IncomingRequest::drain()` waits until all timers finish.\n    auto paf = kj::newPromiseAndFulfiller<void>();\n    promise = promise.attach(\n        kj::defer([fulfiller = kj::mv(paf.fulfiller)]() mutable { fulfiller->fulfill(); }));\n    context.addWaitUntil(kj::mv(paf.promise));\n  }\n\n  state.maybePromise = promise.eagerlyEvaluate(nullptr);\n}\n\nvoid IoContext::TimeoutManagerImpl::resetTimerTask(TimerChannel& timerChannel) {\n  if (timeoutTimes.size() == 0) {\n    // Not waiting for any timer, clear the existing timer task.\n    timerTask = nullptr;\n  } else {\n    // Wait for the first timer.\n    auto& entry = *timeoutTimes.begin();\n    timerTask = timerChannel.atTime(entry.key.when)\n                    .then([this, key = entry.key]() {\n      auto& newEntry = *timeoutTimes.begin();\n      KJ_ASSERT(newEntry.key == key,\n          \"front of timeoutTimes changed without calling resetTimerTask(), we probably missed \"\n          \"a timeout!\");\n      newEntry.value->fulfill();\n    }).eagerlyEvaluate([](kj::Exception&& e) { KJ_LOG(ERROR, e); });\n  }\n}\n\nvoid IoContext::TimeoutManagerImpl::clearTimeout(IoContext& context, TimeoutId timeoutId) {\n  auto timeout = timeouts.find(timeoutId);\n  if (timeout == timeouts.end()) {\n    // We can't find this timeout, thus we act as if it was already canceled.\n    return;\n  }\n\n  // Cancel the timeout.\n  timeout->second.cancel();\n}\n\nTimeoutId IoContext::setTimeoutImpl(\n    TimeoutId::Generator& generator, bool repeat, jsg::Function<void()> function, double msDelay) {\n  static constexpr int64_t max = 3153600000000;  // Milliseconds in 100 years\n  // Clamp the range on timers to [0, 3153600000000] (inclusive). The specs\n  // do not indicate a clear maximum range for setTimeout/setInterval so the\n  // limit here is fairly arbitrary. 100 years max should be plenty safe.\n  int64_t delay = msDelay <= 0 || std::isnan(msDelay) ? 0\n      : msDelay >= static_cast<double>(max)           ? max\n                                                      : static_cast<int64_t>(msDelay);\n  auto params = TimeoutManager::TimeoutParameters(repeat, delay, kj::mv(function));\n  return timeoutManager->setTimeout(*this, generator, kj::mv(params));\n}\n\nvoid IoContext::clearTimeoutImpl(TimeoutId id) {\n  timeoutManager->clearTimeout(*this, id);\n}\n\nsize_t IoContext::getTimeoutCount() {\n  return timeoutManager->getTimeoutCount();\n}\n\nkj::Date IoContext::now(IncomingRequest& incomingRequest) {\n  if (getWorker().getScript().getIsolate().getApi().getFeatureFlags().getPreciseTimers()) {\n    auto now = kj::systemPreciseCalendarClock().now();\n    // Round to 3ms granularity\n    int64_t ms = (now - kj::UNIX_EPOCH) / kj::MILLISECONDS;\n    int64_t roundedMs = (ms / 3) * 3;\n    return kj::UNIX_EPOCH + roundedMs * kj::MILLISECONDS;\n  }\n\n  // Let TimerChannel decide whether to clamp to the next timeout time. This is how Spectre\n  // mitigations ensure Date.now() inside a callback returns exactly the scheduled time.\n  return incomingRequest.now(timeoutManager->getNextTimeout());\n}\n\nkj::Date IoContext::now() {\n  return now(getCurrentIncomingRequest());\n}\n\nkj::Rc<ExternalPusherImpl> IoContext::getExternalPusher() {\n  KJ_IF_SOME(ep, externalPusher) {\n    return ep.addRef();\n  } else {\n    return externalPusher.emplace(kj::rc<ExternalPusherImpl>(getByteStreamFactory())).addRef();\n  }\n}\n\nkj::Own<WorkerInterface> IoContext::getSubrequestNoChecks(\n    kj::FunctionParam<kj::Own<WorkerInterface>(TraceContext&, IoChannelFactory&)> func,\n    SubrequestOptions options) {\n  TraceContext tracing;\n  KJ_IF_SOME(n, options.operationName) {\n    tracing = makeUserTraceSpan(n.clone());\n  }\n\n  kj::Own<WorkerInterface> ret;\n  KJ_IF_SOME(existing, options.existingTraceContext) {\n    ret = func(existing, getIoChannelFactory());\n  } else {\n    ret = func(tracing, getIoChannelFactory());\n  }\n\n  if (options.wrapMetrics) {\n    auto& metrics = getMetrics();\n    ret = metrics.wrapSubrequestClient(kj::mv(ret));\n    ret = worker->getIsolate().wrapSubrequestClient(\n        kj::mv(ret), getHeaderIds().contentEncoding, metrics);\n  }\n\n  if (tracing.isObserved()) {\n    auto ioOwnedSpan = addObject(kj::heap(kj::mv(tracing)));\n    ret = ret.attach(kj::mv(ioOwnedSpan));\n  }\n\n  // Subrequests use a lot of unaccounted C++ memory, so we adjust V8's external memory counter to\n  // pressure the GC and protect against OOMs. We apply this adjustment to ALL subrequests (not\n  // just fetch). We only apply this when the JS lock is held (i.e., when JS code initiated the\n  // subrequest); infrastructure paths that bypass JS don't need it.\n  KJ_IF_SOME(lock, currentLock) {\n    jsg::Lock& js = lock;\n    ret = ret.attach(js.getExternalMemoryAdjustment(8 * 1024));\n  }\n\n  return kj::mv(ret);\n}\n\nkj::Own<WorkerInterface> IoContext::getSubrequest(\n    kj::FunctionParam<kj::Own<WorkerInterface>(TraceContext&, IoChannelFactory&)> func,\n    SubrequestOptions options) {\n  limitEnforcer->newSubrequest(options.inHouse);\n  return getSubrequestNoChecks(kj::mv(func), kj::mv(options));\n}\n\nkj::Own<WorkerInterface> IoContext::getSubrequestChannel(\n    uint channel, bool isInHouse, kj::Maybe<kj::String> cfBlobJson, kj::ConstString operationName) {\n  return getSubrequest(\n      [&](TraceContext& tracing, IoChannelFactory& channelFactory) {\n    return getSubrequestChannelImpl(\n        channel, isInHouse, kj::mv(cfBlobJson), tracing, channelFactory);\n  },\n      SubrequestOptions{\n        .inHouse = isInHouse,\n        .wrapMetrics = !isInHouse,\n        .operationName = kj::mv(operationName),\n      });\n}\n\nkj::Own<WorkerInterface> IoContext::getSubrequestChannel(\n    uint channel, bool isInHouse, kj::Maybe<kj::String> cfBlobJson, TraceContext& traceContext) {\n  return getSubrequest(\n      [&](TraceContext& tracing, IoChannelFactory& channelFactory) {\n    return getSubrequestChannelImpl(\n        channel, isInHouse, kj::mv(cfBlobJson), tracing, channelFactory);\n  },\n      SubrequestOptions{\n        .inHouse = isInHouse,\n        .wrapMetrics = !isInHouse,\n        .existingTraceContext = traceContext,\n      });\n}\n\nkj::Own<WorkerInterface> IoContext::getSubrequestChannelNoChecks(uint channel,\n    bool isInHouse,\n    kj::Maybe<kj::String> cfBlobJson,\n    kj::Maybe<kj::ConstString> operationName) {\n  return getSubrequestNoChecks(\n      [&](TraceContext& tracing, IoChannelFactory& channelFactory) {\n    return getSubrequestChannelImpl(\n        channel, isInHouse, kj::mv(cfBlobJson), tracing, channelFactory);\n  },\n      SubrequestOptions{\n        .inHouse = isInHouse,\n        .wrapMetrics = !isInHouse,\n        .operationName = kj::mv(operationName),\n      });\n}\n\nkj::Own<WorkerInterface> IoContext::getSubrequestChannelImpl(uint channel,\n    bool isInHouse,\n    kj::Maybe<kj::String> cfBlobJson,\n    TraceContext& tracing,\n    IoChannelFactory& channelFactory) {\n  IoChannelFactory::SubrequestMetadata metadata{\n    .cfBlobJson = kj::mv(cfBlobJson),\n    .parentSpan = tracing.getInternalSpanParent(),\n    .featureFlagsForFl = mapCopyString(worker->getIsolate().getFeatureFlagsForFl()),\n  };\n\n  auto client = channelFactory.startSubrequest(channel, kj::mv(metadata));\n\n  return client;\n}\n\nkj::Own<kj::HttpClient> IoContext::getHttpClient(\n    uint channel, bool isInHouse, kj::Maybe<kj::String> cfBlobJson, kj::ConstString operationName) {\n  return asHttpClient(\n      getSubrequestChannel(channel, isInHouse, kj::mv(cfBlobJson), kj::mv(operationName)));\n}\n\nkj::Own<kj::HttpClient> IoContext::getHttpClient(\n    uint channel, bool isInHouse, kj::Maybe<kj::String> cfBlobJson, TraceContext& traceContext) {\n  return asHttpClient(getSubrequestChannel(channel, isInHouse, kj::mv(cfBlobJson), traceContext));\n}\n\nkj::Own<CacheClient> IoContext::getCacheClient() {\n  // TODO(someday): Should Cache API requests be considered in-house? They are already not counted\n  //   as subrequests in metrics and logs (like in-house requests aren't), but historically the\n  //   subrequest limit still applied. Since I can't currently think of a use case for more than 50\n  //   cache API requests per request, I'm leaving it as-is for now.\n  limitEnforcer->newSubrequest(false);\n  auto ret = getIoChannelFactory().getCache();\n\n  // Apply external memory adjustment for Cache API subrequests (same as other subrequests in\n  // getSubrequestNoChecks).\n  KJ_IF_SOME(lock, currentLock) {\n    jsg::Lock& js = lock;\n    ret = ret.attach(js.getExternalMemoryAdjustment(8 * 1024));\n  }\n\n  return kj::mv(ret);\n}\n\njsg::AsyncContextFrame::StorageScope IoContext::makeAsyncTraceScope(\n    Worker::Lock& lock, kj::Maybe<SpanParent> spanParentOverride) {\n  static const SpanParent dummySpanParent = nullptr;\n\n  jsg::Lock& js = lock;\n  kj::Own<SpanParent> spanParent;\n  KJ_IF_SOME(spo, kj::mv(spanParentOverride)) {\n    spanParent = kj::heap(kj::mv(spo));\n  } else {\n    // TODO(cleanup): Can we also elide the other memory allocations for the (unused) storage\n    // scope if tracing is disabled?\n    SpanParent metricsSpan = getMetrics().getSpan();\n    if (!metricsSpan.isObserved()) {\n      // const_cast is ok: There's no state that could be changed in a non-observed span parent.\n      spanParent = kj::Own<SpanParent>(\n          &const_cast<SpanParent&>(dummySpanParent), kj::NullDisposer::instance);\n    } else {\n      spanParent = kj::heap(kj::mv(metricsSpan));\n    }\n  }\n  auto ioOwnSpanParent = IoContext::current().addObject(kj::mv(spanParent));\n  auto spanHandle = jsg::wrapOpaque(js.v8Context(), kj::mv(ioOwnSpanParent));\n  return jsg::AsyncContextFrame::StorageScope(\n      js, lock.getTraceAsyncContextKey(), js.v8Ref(spanHandle));\n}\n\nSpanParent IoContext::getCurrentTraceSpan() {\n  // If called while lock is held, try to use the trace info stored in the async context.\n  KJ_IF_SOME(lock, currentLock) {\n    KJ_IF_SOME(frame, jsg::AsyncContextFrame::current(lock)) {\n      KJ_IF_SOME(value, frame.get(lock.getTraceAsyncContextKey())) {\n        auto handle = value.getHandle(lock);\n        jsg::Lock& js = lock;\n        auto& spanParent = jsg::unwrapOpaqueRef<IoOwn<SpanParent>>(js.v8Isolate, handle);\n        return spanParent->addRef();\n      }\n    }\n  }\n\n  // If async context is unavailable (unset, or JS lock is not held), fall back to heuristic of\n  // using the trace info from the most recent active request.\n  return getMetrics().getSpan();\n}\n\nSpanParent IoContext::getCurrentUserTraceSpan() {\n  if (incomingRequests.empty()) {\n    return SpanParent(nullptr);\n  } else {\n    return getCurrentIncomingRequest().getCurrentUserTraceSpan();\n  }\n}\n\nSpanParent IoContext_IncomingRequest::getCurrentUserTraceSpan() {\n  return currentUserTraceSpan.addRef();\n}\n\nSpanBuilder IoContext::makeTraceSpan(kj::ConstString operationName) {\n  return getCurrentTraceSpan().newChild(kj::mv(operationName));\n}\n\nTraceContext IoContext::makeUserTraceSpan(kj::ConstString operationName) {\n  auto span = makeTraceSpan(operationName.clone());\n  auto userSpan = getCurrentUserTraceSpan().newChild(kj::mv(operationName));\n  return TraceContext(kj::mv(span), kj::mv(userSpan));\n}\n\nvoid IoContext::taskFailed(kj::Exception&& exception) {\n  if (waitUntilStatusValue == EventOutcome::OK) {\n    KJ_IF_SOME(status, limitEnforcer->getLimitsExceeded()) {\n      waitUntilStatusValue = status;\n    } else {\n      waitUntilStatusValue = EventOutcome::EXCEPTION;\n    }\n  }\n\n  // If `taskFailed()` throws the whole event loop blows up... let's be careful not to let that\n  // happen.\n  KJ_IF_SOME(e, kj::runCatchingExceptions([&]() {\n    logUncaughtExceptionAsync(UncaughtExceptionSource::ASYNC_TASK, kj::mv(exception));\n  })) {\n    KJ_LOG(ERROR, \"logUncaughtExceptionAsync() threw an exception?\", e);\n  }\n}\n\nvoid IoContext::requireCurrent() {\n  KJ_REQUIRE(threadLocalRequest == this, \"request is not current in this thread\");\n}\n\nvoid IoContext::checkFarGet(const DeleteQueue& expectedQueue, const std::type_info& type) {\n  requireCurrent();\n\n  if (&expectedQueue == deleteQueue.queue.get()) {\n    // same request or same actor, success\n  } else {\n    throwNotCurrentJsError(type);\n  }\n}\n\nWorker::Actor& IoContext::getActorOrThrow() {\n  return KJ_ASSERT_NONNULL(actor, \"not an actor request\");\n}\n\nvoid IoContext::runInContextScope(Worker::LockType lockType,\n    kj::Maybe<InputGate::Lock> inputLock,\n    kj::Function<void(Worker::Lock&)> func) {\n  // The previously-current context, before we entered this scope. We have to allow opening\n  // multiple nested scopes especially to support destructors: destroying objects related to a\n  // subrequest in one worker could transitively destroy resources belonging to the next worker in\n  // the pipeline. We can't delay destruction to a future turn of the event loop because it's\n  // common for child objects to contain pointers back to stuff owned by the parent that could\n  // then be dangling.\n  KJ_REQUIRE(threadId == getThreadId(), \"IoContext cannot switch threads\");\n  SuppressIoContextScope previousRequest;\n  threadLocalRequest = this;\n\n  worker->runInLockScope(lockType, [&](Worker::Lock& lock) {\n    KJ_REQUIRE(currentInputLock == kj::none);\n    KJ_REQUIRE(currentLock == kj::none);\n    KJ_DEFER(currentLock = kj::none; currentInputLock = kj::none);\n    currentInputLock = kj::mv(inputLock);\n    currentLock = lock;\n\n    JSG_WITHIN_CONTEXT_SCOPE(lock, lock.getContext(), [&](jsg::Lock& js) {\n      v8::Isolate::PromiseContextScope promiseContextScope(\n          lock.getIsolate(), getPromiseContextTag(lock));\n\n      {\n        // Handle any pending deletions that arrived while the worker was processing a different\n        // request.\n        auto l = deleteQueue.queue->crossThreadDeleteQueue.lockExclusive();\n        auto& state = KJ_ASSERT_NONNULL(*l);\n        for (auto& object: state.queue) {\n          OwnedObjectList::unlink(*object);\n        }\n        state.queue.clear();\n      }\n\n      func(lock);\n    });\n  });\n}\n\nvoid IoContext::runImpl(Runnable& runnable,\n    Worker::LockType lockType,\n    kj::Maybe<InputGate::Lock> inputLock,\n    Runnable::Exceptional exceptional) {\n  KJ_IF_SOME(l, inputLock) {\n    KJ_REQUIRE(l.isFor(KJ_ASSERT_NONNULL(actor).getInputGate()));\n  }\n\n  getIoChannelFactory().getTimer().syncTime();\n\n  runInContextScope(lockType, kj::mv(inputLock), [&](Worker::Lock& workerLock) {\n    kj::Own<void> event;\n    if (!exceptional) {\n      workerLock.requireNoPermanentException();\n      // Prevent prematurely detecting a hang while we're still executing JavaScript.\n      // TODO(cleanup): Is this actually still needed or is this vestigial? Seems like it should\n      //   not be necessary.\n      event = registerPendingEvent();\n    }\n\n    auto limiterScope = limitEnforcer->enterJs(workerLock, *this);\n\n    bool gotTermination = false;\n\n    KJ_DEFER({\n      // Always clear out all pending V8 events before leaving the scope. This ensures that\n      // there's never any unfinished work waiting to run when we return to the event loop.\n      //\n      // Alternatively, we could use kj::evalLater() to queue a callback which runs the microtasks.\n      // This would perhaps prevent a microtask loop from blocking incoming I/O events. However,\n      // in practice this seems like a dubious scenario. A script that does while(1) will always\n      // block I/O, so why should a script in a promise loop not? If scripts want to use 100% of\n      // CPU but also receive I/O as it arrives, we should offer some API to explicitly request\n      // polling for I/O.\n      jsg::Lock& js = workerLock;\n\n      if (gotTermination) {\n        // We already consumed the termination pseudo-exception, so if we call RunMicrotasks() now,\n        // they will run with no limit. But if we call terminateNextExecution() again now, it will\n        // conveniently cause RunMicrotasks() to terminate _right after_ dequeuing the contents of\n        // the task queue, which is perfect, because it effectively cancels them all.\n        js.terminateNextExecution();\n      }\n\n      // Run microtask checkpoint with an active IoContext\n      {\n        // Running the microtask queue can itself trigger a pending exception in the isolate.\n        v8::TryCatch tryCatch(workerLock.getIsolate());\n\n        js.runMicrotasks();\n\n        if (tryCatch.HasCaught()) {\n          // It really shouldn't be possible for microtasks to throw regular exceptions.\n          // so if we got here it should be a terminal condition.\n          KJ_ASSERT(tryCatch.HasTerminated());\n          // If we do not reset here we end up with a dangling exception in the isolate that\n          // leads to an assert in v8 when the Lock is destroyed.\n          tryCatch.Reset();\n          // Ensure we don't pump the message loop in this case\n          gotTermination = true;\n        }\n      }\n\n      // Run FinalizationRegistry cleanup tasks without an IoContext\n      {\n        SuppressIoContextScope noIoCtxt;\n        while (!gotTermination && js.pumpMsgLoop()) {\n          // Check if FinalizationRegistry cleanup callbacks have not breached our limits\n          if (limitEnforcer->getLimitsExceeded() != kj::none) {\n            // We can potentially log this, but due to a lack of IoContext we cannot notify\n            // the worker\n            break;\n          }\n\n          // It is possible that a microtask got enqueued during pumpMsgLoop execution\n          // Microtasks enqueued by FinalizationRegistry cleanup tasks should also run\n          // without an active IoContext\n          v8::TryCatch tryCatch(workerLock.getIsolate());\n\n          js.runMicrotasks();\n\n          if (tryCatch.HasCaught()) {\n            // It really shouldn't be possible for microtasks to throw regular exceptions.\n            // so if we got here it should be a terminal condition.\n            KJ_ASSERT(tryCatch.HasTerminated());\n            // If we do not reset here we end up with a dangling exception in the isolate that\n            // leads to an assert in v8 when the Lock is destroyed.\n            tryCatch.Reset();\n            // Ensure we don't pump the message loop in this case\n            gotTermination = true;\n          }\n        }\n      }\n    });\n\n    v8::TryCatch tryCatch(workerLock.getIsolate());\n    try {\n      runnable.run(workerLock);\n    } catch (const jsg::JsExceptionThrown&) {\n      if (tryCatch.HasTerminated()) {\n        gotTermination = true;\n        limiterScope = nullptr;\n\n        // Check if we hit a limit.\n        limitEnforcer->requireLimitsNotExceeded();\n\n        // Check if we were aborted. TerminateExecution() may be called after abort() in order\n        // to prevent any more JavaScript from executing.\n        KJ_IF_SOME(e, abortException) {\n          kj::throwFatalException(kj::cp(e));\n        }\n\n        // That should have thrown, so we shouldn't get here.\n        KJ_FAIL_ASSERT(\"script terminated for unknown reasons\");\n      } else {\n        if (tryCatch.Message().IsEmpty()) {\n          // Should never happen, but check for it because otherwise V8 will crash.\n          KJ_LOG(ERROR, \"tryCatch.Message() was empty even when not HasTerminated()??\",\n              kj::getStackTrace());\n          JSG_FAIL_REQUIRE(Error, \"(JavaScript exception with no message)\");\n        } else {\n          auto jsException = tryCatch.Exception();\n\n          // TODO(someday): We log \"uncaught exception\" here whenever throwing from JS to C++.\n          //   However, the C++ code calling us may still catch the exception and do its own logging,\n          //   or may even tunnel it back to JavaScript, making this log line redundant or maybe even\n          //   wrong (if the exception is in fact caught later). But, it's difficult to be sure that\n          //   all C++ consumers log properly, and even if they do, the stack trace is lost once the\n          //   exception has been tunneled into a KJ exception, so the later logging won't be as\n          //   useful. We should improve the tunneling to include stack traces and ensure that all\n          //   consumers do in fact log exceptions, then we can remove this.\n          workerLock.logUncaughtException(UncaughtExceptionSource::INTERNAL,\n              jsg::JsValue(jsException), jsg::JsMessage(tryCatch.Message()));\n\n          jsg::throwTunneledException(workerLock.getIsolate(), jsException);\n        }\n      }\n    }\n  });\n}\n\nstatic constexpr auto kAsyncIoErrorMessage =\n    \"Disallowed operation called within global scope. Asynchronous I/O \"\n    \"(ex: fetch() or connect()), setting a timeout, and generating random \"\n    \"values are not allowed within global scope. To fix this error, perform this \"\n    \"operation within a handler. \"\n    \"https://developers.cloudflare.com/workers/runtime-apis/handlers/\";\n\nIoContext& IoContext::current() {\n  if (threadLocalRequest == nullptr) {\n    v8::Isolate* isolate = v8::Isolate::TryGetCurrent();\n    KJ_REQUIRE(isolate != nullptr, \"there is no current request on this thread\");\n    isolate->ThrowError(jsg::v8StrIntern(isolate, kAsyncIoErrorMessage));\n    throw jsg::JsExceptionThrown();\n  } else {\n    return *threadLocalRequest;\n  }\n}\n\nkj::Maybe<IoContext&> IoContext::tryCurrent() {\n  if (threadLocalRequest == nullptr) {\n    return kj::none;\n  } else {\n    return *threadLocalRequest;\n  }\n}\n\nbool IoContext::hasCurrent() {\n  return threadLocalRequest != nullptr;\n}\n\nbool IoContext::isCurrent() {\n  return this == threadLocalRequest;\n}\n\nauto IoContext::tryGetWeakRefForCurrent() -> kj::Maybe<kj::Own<WeakRef>> {\n  KJ_IF_SOME(ioContext, tryCurrent()) {\n    return ioContext.getWeakRef();\n  } else {\n    return kj::none;\n  }\n}\n\nvoid IoContext::abortFromHang(Worker::AsyncLock& asyncLock) {\n  KJ_ASSERT(actor == kj::none);  // we don't perform hang detection on actor requests\n\n  // Don't bother aborting if limits were exceeded because in that case the abort promise will be\n  // fulfilled shortly anyway.\n  if (limitEnforcer->getLimitsExceeded() == kj::none) {\n    abort(JSG_KJ_EXCEPTION(FAILED, Error,\n        \"The Workers runtime canceled this request because it detected that your Worker's code \"\n        \"had hung and would never generate a response. Refer to: \"\n        \"https://developers.cloudflare.com/workers/observability/errors/\"));\n  }\n}\n\nnamespace {\n\nclass CacheSerializedInputStream final: public kj::AsyncInputStream {\n public:\n  CacheSerializedInputStream(\n      kj::Own<kj::AsyncInputStream> inner, kj::Own<kj::PromiseFulfiller<void>> fulfiller)\n      : inner(kj::mv(inner)),\n        fulfiller(kj::mv(fulfiller)) {}\n\n  ~CacheSerializedInputStream() noexcept(false) {\n    fulfiller->fulfill();\n  }\n\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    return inner->tryRead(buffer, minBytes, maxBytes);\n  }\n\n  kj::Maybe<uint64_t> tryGetLength() override {\n    return inner->tryGetLength();\n  }\n\n  kj::Promise<uint64_t> pumpTo(kj::AsyncOutputStream& output, uint64_t amount) override {\n    return inner->pumpTo(output, amount);\n  }\n\n private:\n  kj::Own<kj::AsyncInputStream> inner;\n  kj::Own<kj::PromiseFulfiller<void>> fulfiller;\n};\n\n}  // namespace\n\njsg::Promise<IoOwn<kj::AsyncInputStream>> IoContext::makeCachePutStream(\n    jsg::Lock& js, kj::Own<kj::AsyncInputStream> stream) {\n  auto paf = kj::newPromiseAndFulfiller<void>();\n\n  KJ_DEFER(cachePutSerializer = kj::mv(paf.promise));\n\n  return awaitIo(js,\n      cachePutSerializer.then(\n          [fulfiller = kj::mv(paf.fulfiller),\n              stream = kj::mv(stream)]() mutable -> kj::Own<kj::AsyncInputStream> {\n    if (stream->tryGetLength() != kj::none) {\n      // PUT with Content-Length. We can just return immediately, allowing the next PUT to start.\n      KJ_DEFER(fulfiller->fulfill());\n      return kj::mv(stream);\n    } else {\n      // TODO(later): With Cache streams no longer having a size limit enforced by the runtime,\n      // explore if we can clean up stream serialization too.\n      // PUT with Transfer-Encoding: chunked. We have no idea how big this request body is going to\n      // be, so wrap the stream that only unblocks the next PUT after this one is complete.\n      return kj::heap<CacheSerializedInputStream>(kj::mv(stream), kj::mv(fulfiller));\n    }\n  }),\n      [this](\n          jsg::Lock&, kj::Own<kj::AsyncInputStream> result) { return addObject(kj::mv(result)); });\n}\n\nvoid IoContext::writeLogfwdr(\n    uint channel, kj::FunctionParam<void(capnp::AnyPointer::Builder)> buildMessage) {\n  addWaitUntil(getIoChannelFactory()\n                   .writeLogfwdr(channel, kj::mv(buildMessage))\n                   .attach(registerPendingEvent()));\n}\n\nvoid IoContext::requireCurrentOrThrowJs() {\n  if (!isCurrent()) {\n    throwNotCurrentJsError();\n  }\n}\n\nvoid IoContext::requireCurrentOrThrowJs(WeakRef& weak) {\n  KJ_IF_SOME(ctx, weak.tryGet()) {\n    if (ctx.isCurrent()) {\n      return;\n    }\n  }\n  throwNotCurrentJsError();\n}\n\nvoid IoContext::throwNotCurrentJsError(kj::Maybe<const std::type_info&> maybeType) {\n  auto type = maybeType\n                  .map([](const std::type_info& type) {\n    return kj::str(\" (I/O type: \", jsg::typeName(type), \")\");\n  }).orDefault(kj::String());\n\n  if (threadLocalRequest != nullptr && threadLocalRequest->actor != kj::none) {\n    JSG_FAIL_REQUIRE(Error,\n        kj::str(\n            \"Cannot perform I/O on behalf of a different Durable Object. I/O objects \"\n            \"(such as streams, request/response bodies, and others) created in the context of one \"\n            \"Durable Object cannot be accessed from a different Durable Object in the same isolate. \"\n            \"This is a limitation of Cloudflare Workers which allows us to improve overall \"\n            \"performance.\",\n            type));\n  } else {\n    JSG_FAIL_REQUIRE(Error,\n        kj::str(\n            \"Cannot perform I/O on behalf of a different request. I/O objects (such as \"\n            \"streams, request/response bodies, and others) created in the context of one request \"\n            \"handler cannot be accessed from a different request's handler. This is a limitation \"\n            \"of Cloudflare Workers which allows us to improve overall performance.\",\n            type));\n  }\n}\n\njsg::JsObject IoContext::getPromiseContextTag(jsg::Lock& js) {\n  if (promiseContextTag == kj::none) {\n    auto deferral = kj::heap<IoCrossContextExecutor>(deleteQueue.queue.addRef());\n    promiseContextTag = jsg::JsRef(js, js.opaque(kj::mv(deferral)));\n  }\n  return KJ_REQUIRE_NONNULL(promiseContextTag).getHandle(js);\n}\n\nkj::Promise<void> IoContext::startDeleteQueueSignalTask(IoContext* context) {\n  // The promise that is returned is held by the IoContext itself, so when the\n  // IoContext is destroyed, the promise will be canceled and the loop will\n  // end. On each iteration of the loop we want to reset the cross thread\n  // signal in the delete queue, then wait on the promise. Once the promise\n  // is fulfilled, we will run an empty task to prompt the IoContext to drain\n  // the DeleteQueue.\n  try {\n    for (;;) {\n      co_await context->deleteQueue.queue->resetCrossThreadSignal();\n      co_await context->run([](auto& lock) {\n        auto& context = IoContext::current();\n        auto l = context.deleteQueue.queue->crossThreadDeleteQueue.lockExclusive();\n        auto& state = KJ_ASSERT_NONNULL(*l);\n        for (auto& action: state.actions) {\n          action(lock);\n        }\n        state.actions.clear();\n      });\n    }\n  } catch (...) {\n    context->abort(kj::getCaughtExceptionAsKj());\n  }\n}\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/io-context.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"io-own.h\"\n#include \"worker.h\"\n\n#include <workerd/api/deferred-proxy.h>\n#include <workerd/io/actor-id.h>\n#include <workerd/io/external-pusher.h>\n#include <workerd/io/io-channels.h>\n#include <workerd/io/io-gate.h>\n#include <workerd/io/io-thread-context.h>\n#include <workerd/io/io-timers.h>\n#include <workerd/io/limit-enforcer.h>\n#include <workerd/io/trace.h>\n#include <workerd/io/worker-fs.h>\n#include <workerd/jsg/async-context.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/util/exception.h>\n#include <workerd/util/uncaught-exception-source.h>\n#include <workerd/util/weak-refs.h>\n\n#include <capnp/dynamic.h>\n#include <kj/async-io.h>\n#include <kj/compat/http.h>\n#include <kj/function.h>\n#include <kj/mutex.h>\n\nnamespace workerd {\nclass WorkerTracer;\nclass BaseTracer;\n}  // namespace workerd\n\nnamespace workerd {\nclass LimitEnforcer;\n}\n\nnamespace capnp {\nclass HttpOverCapnpFactory;\n}\n\nnamespace workerd {\n\n// This wishes it were IoContext::Runnable::Exceptional.\nWD_STRONG_BOOL(IoContext_Runnable_Exceptional);\n\n[[noreturn]] void throwExceededMemoryLimit(bool isActor);\n\nclass IoContext;\n\n// Represents one incoming request being handled by a IoContext. In non-actor scenarios,\n// there is only ever one IncomingRequest per IoContext, but with actors there could be many.\n//\n// This should normally be referenced as IoContext::IncomingRequest, but it has been pulled\n// out of the nested scope to allow forward-declaration.\n//\n// The purpose of tracking IncomingRequests at all is so that we can perform metrics, logging,\n// and tracing on a \"per-request basis\", e.g. we can log that a particular incoming request\n// generated N subrequests, and traces can trace through them. But this concept falls apart\n// a bit when actors are in play, because we can't really say which incoming request \"caused\"\n// any particular subrequest, especially when multiple incoming requests overlap. As a\n// heuristic approximation, we attribute each subrequest (and all other forms of resource\n// usage) to the \"current\" incoming request, which is defined as the newest request that hasn't\n// already completed.\nclass IoContext_IncomingRequest final {\n public:\n  IoContext_IncomingRequest(kj::Own<IoContext> context,\n      kj::Own<IoChannelFactory> ioChannelFactory,\n      kj::Own<RequestObserver> metrics,\n      kj::Maybe<kj::Own<BaseTracer>> workerTracer,\n      kj::Maybe<tracing::InvocationSpanContext> maybeTriggerInvocationSpan);\n  KJ_DISALLOW_COPY_AND_MOVE(IoContext_IncomingRequest);\n  ~IoContext_IncomingRequest() noexcept(false);\n\n  IoContext& getContext() {\n    return *context;\n  }\n\n  // Invoked when the request is actually delivered.\n  //\n  // If, for some reason, this is not invoked before the object is destroyed, this indicate that\n  // the event was canceled for some reason before delivery. No JavaScript was invoked.\n  //\n  // This method invokes metrics->delivered() and also makes this IncomingRequest \"current\" for\n  // the IoContext.\n  //\n  // If delivered() is never called, then drain() need not be called.\n  void delivered(kj::SourceLocation = kj::SourceLocation());\n\n  // Waits until the request is \"done\". For non-actor requests this means waiting until\n  // all \"waitUntil\" tasks finish, applying the \"soft timeout\" time limit from WorkerLimits.\n  //\n  // For actor requests, this means waiting until either all tasks have finished (not just\n  // waitUntil, all tasks), or a new incoming request has been received (which then takes over\n  // responsibility for waiting for tasks), or the actor is shut down.\n  kj::Promise<void> drain();\n\n  // Waits for all \"waitUntil\" tasks to finish, up to the time limit for scheduled events, as\n  // defined by `scheduledTimeoutMs` in `WorkerLimits`. Returns an enum indicating if the\n  // event completed successfully, hit a timeout, or was aborted.\n  //\n  // Note that, while this is similar in some ways to `drain()`, `finishScheduled()` is intended\n  // to be called synchronously during request handling, i.e. where a client is waiting for the\n  // result, and the operation will be canceled if the client disconnects. `drain()` is intended\n  // to be called after the client has received a response or disconnected.\n  //\n  // This method is also used by some custom event handlers (see WorkerInterface::CustomEvent) that\n  // need similar behavior, as well as the test handler. TODO(cleanup): Rename to something more\n  // generic?\n  enum class FinishScheduledResult { COMPLETED, ABORTED, TIMEOUT };\n  kj::Promise<FinishScheduledResult> finishScheduled();\n\n  // Access the event loop's current time point. This will remain constant between ticks. This is\n  // used to implement IoContext::now(), which should be preferred so that time can be adjusted\n  // based on setTimeout() when needed.\n  kj::Date now(kj::Maybe<kj::Date> nextTimeout = kj::none);\n\n  RequestObserver& getMetrics() {\n    return *metrics;\n  }\n\n  kj::Maybe<BaseTracer&> getWorkerTracer() {\n    return workerTracer;\n  }\n\n  SpanParent getCurrentUserTraceSpan();\n\n  // The invocation span context is a unique identifier for a specific\n  // worker invocation.\n  tracing::InvocationSpanContext& getInvocationSpanContext();\n\n private:\n  kj::Own<IoContext> context;\n  kj::Own<RequestObserver> metrics;\n  kj::Maybe<kj::Own<BaseTracer>> workerTracer;\n  kj::Own<IoChannelFactory> ioChannelFactory;\n\n  SpanParent currentUserTraceSpan = nullptr;\n\n  // The invocation span context identifies the trace id, invocation id, and root\n  // span for the current request. Every invocation of a worker function always\n  // has a root span, even if it is not explicitly traced.\n  kj::Maybe<tracing::InvocationSpanContext> maybeTriggerInvocationSpan;\n  kj::Maybe<tracing::InvocationSpanContext> invocationSpanContext;\n\n  bool wasDelivered = false;\n\n  // Used for debugging, tracks whether we properly called drain() or some other mechanism to\n  // wait for waitUntil tasks.\n  bool waitedForWaitUntil = false;\n\n  // If drain() was already called, this is non-null and fulfilling it will cancel the drain.\n  // This is used in particular when a new IncomingRequest starts while the drain is being\n  // awaited.\n  kj::Maybe<kj::Own<kj::PromiseFulfiller<void>>> drainFulfiller;\n\n  // Used by IoContext::incomingRequests.\n  kj::ListLink<IoContext_IncomingRequest> link;\n\n  // Tracks the location where delivered() was called for debugging.\n  kj::Maybe<kj::SourceLocation> deliveredLocation;\n\n  friend class IoContext;\n};\n\n// IoContext holds state associated with a single I/O context. For stateless requests, each\n// incoming request runs in a unique I/O context. For actors, each actor runs in a unique I/O\n// context (but all requests received by that actor run in the same context).\n//\n// The IoContext serves as a bridge between JavaScript objects and I/O objects. I/O\n// objects are strongly tied to the KJ event loop, and thus must live on a single thread. The\n// JS isolate, however, can move between threads, bringing all garbage-collected heap objects\n// with it. So, when a GC'ed object holds a reference to I/O objects or tasks (KJ promises), it\n// needs help from IoContext manage this.\n//\n// Whenever JavaScript is executing, the current IoContext can be obtained via\n// `IoContext::current()`, and this can then be used to manage I/O, such as outgoing\n// subrequests. When the IoContext is destroyed, all outstanding I/O objects and tasks\n// created through it are destroyed immediately, even if objects on the JS heap still refer to\n// them. Any attempt to access an I/O object from the wrong context will throw.\n//\n// This has an observable side-effect for workers: if a worker saves the request objects\n// associated with one request into its global state and then attempts to access those objects\n// within callbacks associated with some other request, an exception will be thrown. We actually\n// like this. We don't want people leaking heavy objects or allowing simultaneous requests to\n// interfere with each other.\nclass IoContext final: public kj::Refcounted, private kj::TaskSet::ErrorHandler {\n public:\n  class TimeoutManagerImpl;\n\n  // Construct a new IoContext. Before using it, you must also create an IncomingRequest.\n  IoContext(ThreadContext& thread,\n      kj::Own<const Worker> worker,\n      kj::Maybe<Worker::Actor&> actor,\n      kj::Own<LimitEnforcer> limitEnforcer);\n\n  // On destruction, all outstanding tasks associated with this request are canceled.\n  ~IoContext() noexcept(false);\n\n  using IncomingRequest = IoContext_IncomingRequest;\n\n  const Worker& getWorker() {\n    return *worker;\n  }\n  Worker::Lock& getCurrentLock() {\n    return KJ_REQUIRE_NONNULL(currentLock);\n  }\n\n  kj::Maybe<Worker::Actor&> getActor() {\n    return actor;\n  }\n\n  // Gets the actor, throwing if there isn't one.\n  Worker::Actor& getActorOrThrow();\n\n  RequestObserver& getMetrics() {\n    return *getCurrentIncomingRequest().metrics;\n  }\n\n  kj::Maybe<BaseTracer&> getWorkerTracer() {\n    if (incomingRequests.empty()) return kj::none;\n    return getCurrentIncomingRequest().getWorkerTracer();\n  }\n\n  LimitEnforcer& getLimitEnforcer() {\n    return *limitEnforcer;\n  }\n\n  // Get the current input lock. Throws an exception if no input lock is held (e.g. because this is\n  // not an actor request).\n  InputGate::Lock getInputLock();\n\n  // Get the current CriticalSection, if there is one, or returns null if not.\n  kj::Maybe<kj::Own<InputGate::CriticalSection>> getCriticalSection();\n\n  // Runs `callback` within its own critical section, returning its final result. If `callback`\n  // throws, the input lock will break, resetting the actor.\n  //\n  // This can only be called when I/O gates are active, i.e. in an actor.\n  template <typename Func>\n  jsg::PromiseForResult<Func, void, true> blockConcurrencyWhile(jsg::Lock& js, Func&& callback);\n\n  // Returns true if output lock gating is necessary.\n  // Can be used in optimizations to bypass wait* calls altogether.\n  bool hasOutputGate();\n\n  // Wait until all outstanding output locks have been unlocked. Does not wait for future output\n  // locks, even if they are created before past locks are unlocked.\n  //\n  // This is used in actors to block output while some storage writes are uncommitted. For\n  // non-actor requests, this always completes immediately.\n  kj::Promise<void> waitForOutputLocks();\n\n  // Like waitForOutputLocks() but, as an optimization, returns null in (some) cases where no\n  // wait is needed, such as when the request is not an actor request.\n  //\n  // Use the ...IoOwn() overload if you need to store this promise in a JS API object.\n  kj::Maybe<kj::Promise<void>> waitForOutputLocksIfNecessary();\n  kj::Maybe<IoOwn<kj::Promise<void>>> waitForOutputLocksIfNecessaryIoOwn();\n\n  // Check if the output gate (only used by actors) is currently broken. This indicates that there\n  // was a problem with committing storage writes.\n  //\n  // For non-actor requests, this always returns false.\n  bool isOutputGateBroken();\n\n  // Lock output until the given promise completes.\n  //\n  // It is an error to call this outside of actors.\n  template <typename T>\n  kj::Promise<T> lockOutputWhile(kj::Promise<T> promise);\n\n  bool isInspectorEnabled();\n  bool isFiddle();\n\n  // Returns true if there is something listening for warnings — the Chrome DevTools inspector,\n  // a streaming tail worker tracer, or --verbose stderr logging. Use this to guard expensive\n  // warning-message construction that should be skipped when nobody would see the result.\n  bool hasWarningHandler();\n\n  // Log a warning. Emits to the Chrome DevTools inspector (if connected), stderr, and to the\n  // streaming tail worker tracer (if active).\n  void logWarning(kj::StringPtr description);\n\n  // Log a warning, deduplicating so that each unique message is only logged once for the lifetime\n  // of an isolate. Emits to the same destinations as logWarning().\n  void logWarningOnce(kj::StringPtr description);\n\n  // Log an internal error message. Deduplicates log messages such that a single unique message will\n  // only be logged once for the lifetime of an isolate.\n  void logErrorOnce(kj::StringPtr description);\n\n  void logUncaughtException(kj::StringPtr description);\n  void logUncaughtException(UncaughtExceptionSource source,\n      const jsg::JsValue& exception,\n      const jsg::JsMessage& message = jsg::JsMessage());\n\n  // Log an uncaught exception from an asynchronous context, i.e. when the IoContext is not\n  // \"current\".\n  void logUncaughtExceptionAsync(UncaughtExceptionSource source, kj::Exception&& e);\n\n  // Returns a promise that will reject with an exception if and when the request should be\n  // aborted, e.g. because its CPU time expired. This should be joined with any promises for\n  // incoming tasks.\n  kj::Promise<void> onAbort() {\n    return abortPromise.addBranch();\n  }\n\n  // Force context abort now.\n  //\n  // Note that abort() is safe to call while the IoContext is current. Becaues of this, it cannot\n  // cancel any tasks synchronously, as this might cancel the current promise, leading to a crash.\n  void abort(kj::Exception&& e);\n\n  // Await the given promise and, if it throws, call `abort()` with the exception. The promise\n  // given here should just be a monitoring promise, it should not represent any sort of background\n  // work beyond monitoring. In particular, it must not be a task that attempts to enter the\n  // isolate by calling context.run().\n  void abortWhen(kj::Promise<void> promise);\n\n  // Has event.passThroughOnException() been called?\n  bool isFailOpen() {\n    return failOpen;\n  }\n\n  // Called by event.passThroughOnException().\n  void setFailOpen() {\n    failOpen = true;\n  }\n\n  // -----------------------------------------------------------------\n  // Tracking thread-local request\n\n  // Asynchronously execute a callback inside the context.\n  //\n  // We don't use a \"scope\" class because this might actually switch to a larger stack for the\n  // duration of the callback.\n  //\n  // If `inputLock` is not provided, and this is an actor context, an input lock will be obtained\n  // before executing the callback.\n  template <typename Func>\n  kj::PromiseForResult<Func, Worker::Lock&> run(\n      Func&& func, kj::Maybe<InputGate::Lock> inputLock = kj::none) KJ_WARN_UNUSED_RESULT;\n\n  // Like run() but executes within the given critical section, if it is non-null. If\n  // `criticalSection` is null, then this just forwards to the other run() (with null inputLock).\n  template <typename Func>\n  kj::PromiseForResult<Func, Worker::Lock&> run(Func&& func,\n      kj::Maybe<kj::Own<InputGate::CriticalSection>> criticalSection) KJ_WARN_UNUSED_RESULT;\n\n  // Returns the current IoContext for the thread.\n  // Throws an exception if there is no current context (see hasCurrent() below).\n  static IoContext& current();\n\n  // Like current(), but returns kj::none if there is no current context.\n  static kj::Maybe<IoContext&> tryCurrent();\n\n  // True if there is a current IoContext for the thread (current() will not throw).\n  static bool hasCurrent();\n\n  // True if this is the IoContext for the current thread (same as `hasCurrent() && tcx == current()`).\n  bool isCurrent();\n\n  // Check if a current request is available. Used to provide better diagnostics when this is\n  // unexpectedly absent when reporting a user span.\n  // TODO(cleanup): This is a hack, remove after addressing the underlying issue.\n  bool hasCurrentIncomingRequest() {\n    return !incomingRequests.empty();\n  }\n\n  // Like requireCurrent() but throws a JS error if this IoContext is not the current.\n  void requireCurrentOrThrowJs();\n\n  // A WeakRef is a weak reference to a IoContext. Note that because IoContext is not\n  // itself ref-counted, we cannot follow the usual pattern of a weak reference that potentially\n  // converts to a strong reference. Instead, intended usage looks like so:\n  // ```\n  // auto& context = IoContext::current();\n  // return canOutliveContext().then([contextWeakRef = context.getWeakRef()]() mutable {\n  //   auto hadContext = contextWeakRef.runIfAlive([&](IoContext& context){\n  //     useContextFinally(context);\n  //   });\n  //   if (!hadContext) {\n  //     doWhatMustBeDone();\n  //   }\n  // });\n  // ```\n  using WeakRef = workerd::WeakRef<IoContext>;\n\n  kj::Own<WeakRef> getWeakRef() {\n    return kj::addRef(*selfRef);\n  }\n\n  // If there is a current IoContext, return its WeakRef.\n  static kj::Maybe<kj::Own<WeakRef>> tryGetWeakRefForCurrent();\n\n  // Like requireCurrentOrThrowJs() but works on a WeakRef.\n  static void requireCurrentOrThrowJs(WeakRef& weak);\n\n  // Just throw the error that requireCurrentOrThrowJs() would throw on failure.\n  [[noreturn]] static void throwNotCurrentJsError(\n      kj::Maybe<const std::type_info&> maybeType = kj::none);\n\n  // -----------------------------------------------------------------\n  // Task scheduling and object storage\n\n  // Arrange for the given promise to execute as part of this request. It will be canceled if the\n  // request is canceled.\n  void addTask(kj::Promise<void> promise);\n\n  template <typename T, typename Func>\n  jsg::PromiseForResult<Func, T, true> awaitIo(jsg::Lock& js, kj::Promise<T> promise, Func&& func);\n\n  // Attach the objects to the promise by creating a continuation that holds them.\n  // This ensures the attachments stay alive until the promise resolves.\n  // This should ONLY be used with TraceContext or SpanBuilder objects.\n  template <typename T, typename... Attachments>\n  jsg::Promise<T> attachSpans(jsg::Lock& js, jsg::Promise<T> promise, Attachments&&... attachments)\n    requires(... &&\n        (kj::isSameType<Attachments, SpanBuilder>() || kj::isSameType<Attachments, TraceContext>()))\n  {\n    return attachSpansInternalOnly(js, kj::mv(promise), kj::fwd<Attachments>(attachments)...);\n  }\n\n  // public for tests\n  template <typename T, typename... Attachments>\n  jsg::Promise<T> attachSpansInternalOnly(\n      jsg::Lock& js, jsg::Promise<T> promise, Attachments&&... attachments) {\n    auto attachmentTuple = addObject(kj::heap(kj::tuple(kj::fwd<Attachments>(attachments)...)));\n\n    if constexpr (kj::isSameType<T, void>()) {\n      return promise.then(js, [attachmentTuple = kj::mv(attachmentTuple)](jsg::Lock&) {\n        // The attachments are kept alive in this lambda's capture\n      });\n    } else {\n      return promise.then(js, [attachmentTuple = kj::mv(attachmentTuple)](jsg::Lock&, T result) {\n        // The attachments are kept alive in this lambda's capture\n        return result;\n      });\n    }\n  }\n\n  // Waits for some background I/O to complete, then executes `func` on the result, returning a\n  // JavaScript promise for the result of that. If no `func` is provided, no transformation is\n  // applied.\n  //\n  // If the IoContext is canceled, the I/O promise will be canceled, `func` will be destroyed\n  // without being called, and the JS promise will never resolve.\n  //\n  // You might wonder why this function takes a continuation function as a parameter, rather than\n  // taking a single `kj::Promise<T>`, returning `jsg::Promise<T>`, and leaving it up to you to\n  // call `.then()` on the result. The answer is that `func` provides stronger guarantees about the\n  // context where it runs, which avoids the need for `IoOwn`s:\n  // - `func` itself can safely capture I/O objects without IoOwn, because the function itself\n  //   is attached to the IoContext. (If the IoContext is canceled, `func` is destroyed.)\n  // - Similarly, the result of `promise` can be an I/O object without needing to be wrapped in\n  //   IoOwn, because `func` is guaranteed to be called in this IoContext.\n  //\n  // Conversely, you might wonder why you wouldn't use `awaitIo(promise.then(func))` instead, which\n  // would also avoid the need for `IoOwn` since `func` would run as part of the KJ event loop.\n  // But, in this version, `func` cannot access any JavaScript objects, because it would not run\n  // with the isolate lock.\n  //\n  // Historically, we solved this with something called `capctx`. You would write something like:\n  // `awaitIo(promise.then(capctx(func)))`. This provided both properties: `func()` ran both in\n  // the KJ event loop and with the isolate lock held. However, this had the problem that it\n  // required returning to the KJ event loop between running func() and running whatever\n  // JavaScript code was waiting on it. This implies releasing the isolate lock just to\n  // immediately acquire it again, which was wasteful. Passing `func` as a parameter to `awaitIo()`\n  // allows it to run under the same isolate lock that then runs the awaiting JavaScript.\n  //\n  // Note that awaitIo() automatically implies registering a pending event while waiting for the\n  // promise (no need to call registerPendingEvent()).\n  template <typename T>\n  jsg::Promise<T> awaitIo(jsg::Lock& js, kj::Promise<T> promise);\n\n  // Waits for the given I/O while holding the input lock, so that all other I/O is blocked from\n  // completing in the meantime (unless it is also holding the same input lock).\n  template <typename T>\n  jsg::Promise<T> awaitIoWithInputLock(jsg::Lock& js, kj::Promise<T> promise);\n\n  template <typename T, typename Func>\n  jsg::PromiseForResult<Func, T, true> awaitIoWithInputLock(\n      jsg::Lock& js, kj::Promise<T> promise, Func&& func);\n\n  // DEPRECATED: Like awaitIo() but:\n  // - Does not have a continuation function, so suffers from the problems described in\n  //   `awaitIo()`'s doc comment.\n  // - Does not automatically register a pending event.\n  //\n  // This is used to implement the historical KJ-oriented PromiseWrapper behavior in terms of the\n  // new `awaitIo()` implementation. This should go away once all API implementations are\n  // refactored to use `awaitIo()`.\n  template <typename T>\n  jsg::Promise<T> awaitIoLegacy(jsg::Lock& js, kj::Promise<T> promise);\n\n  // DEPRECATED: Like awaitIo() but:\n  // - Does not have a continuation function, so suffers from the problems described in\n  //   `awaitIo()`'s doc comment.\n  // - Does not automatically register a pending event.\n  //\n  // This is used to implement the historical KJ-oriented PromiseWrapper behavior in terms of the\n  // new `awaitIo()` implementation. This should go away once all API implementations are\n  // refactored to use `awaitIo()`.\n  template <typename T>\n  jsg::Promise<T> awaitIoLegacyWithInputLock(jsg::Lock& js, kj::Promise<T> promise);\n\n  // Returns a KJ promise that resolves when a particular JavaScript promise completes.\n  //\n  // The JS promise must complete within this IoContext. The KJ promise will reject\n  // immediately if any of these happen:\n  // - The JS promise is GC'ed without resolving.\n  // - The JS promise is resolved from the wrong context.\n  // - The system detects that no further progress will be made in this context (because there is no\n  //   more JavaScript to run, and there is no outstanding I/O scheduled with awaitIo()).\n  //\n  // If `T` is `IoOwn<U>`, it will be unwrapped to just `U` in the result. If `U` is in turn\n  // `kj::Promise<V>`, then the promises will be chained as usual, so the final result is\n  // `kj::Promise<V>`.\n  template <typename T>\n  kj::_::ReducePromises<RemoveIoOwn<T>> awaitJs(jsg::Lock& js, jsg::Promise<T> promise);\n\n  enum TopUpFlag { NO_TOP_UP, TOP_UP };\n\n  // Make a kj::Function which, when called, re-enters this IoContext to run some code.\n  //\n  // `func` is a function with a signature similar to:\n  //\n  //     template <typename... Params, typename Result>\n  //     jsg::Promise<Result> func(jsg::Lock& js, Params&&... params);\n  //\n  // (Optionally, the `jsg::Promise<Result>` can just be `Result` instead.)\n  //\n  // The returned lambda will a signature like:\n  //\n  //     kj::Promise<Result> func(Params&&...);\n  //\n  // This function can be invoked without holding the isolate lock.\n  //\n  // You might think that all this does is set up a lambda that captures the IoContext and calls\n  // ctx.run(). But, it turns out getting this right is a lot more complicated.\n  // - What if the IoContext has been canceled / destroyed, or is destroyed during the callback?\n  // - What if it still exists, but it's an actor and there's no longer an IncomingRequest?\n  // - How do you prevent \"the script will never generate a response\" if the callback is the\n  //   only thing being waited for?\n  // - What if the call was made within blockConcurrencyWhile()? The callback will be blocked until\n  //   the critical section ends, which could lead to deadlock if the critical section code is\n  //   waiting on it?\n  //\n  // This solves all that:\n  // - If the IoContext is destroyed, the callback throws an exception.\n  // - However, as long as the callback itself exists, it is treated as if a task were added using\n  //   addTask(). In actors, this blocks hibernation and keeps the IncomingRequest live.\n  // - Additionally, the calback counts as a PendingEvent.\n  // - The callback is allowed to run within the critical section (blockConcurrencyWhile()) from\n  //   which it was called.\n  //\n  // In short, you should almost never use ctx.run() to re-enter an existing context. You almost\n  // always want either awaitIo() (to re-enter the context after some KJ promise completes) or\n  // makeReentryCallback() (to re-enter the context on a callback).\n  //\n  // The returned function can be called multiple times.\n  //\n  // Note that when invoking the returned function, the function object itself must outlive the\n  // Promise it returns -- just like a coroutine lambda that has a capture. This should, of course,\n  // be assumed of all functions that return promises, but classically kj::Promise's own `.then()`\n  // does not keep its input continuation functions live in this way. If you want to pass the\n  // callback to `.then()`, you can wrap it in `kj::coCapture()`, but note that this means it can\n  // only be called once.\n  //\n  // Use `makeReentryCallback<IoContext::TOP_UP>(func)` to cause\n  // `ctx.getLimitEnforcer().topUpActor()` to be called each time the callback is invoked. This is\n  // useful because `topUpActor()` must be called before entering the isolate lock, so it can't be\n  // part of the body of the given callback function.\n  template <TopUpFlag topUp = NO_TOP_UP, typename Func>\n  auto makeReentryCallback(Func func);\n\n  // Returns the number of times addTask() has been called (even if the tasks have completed).\n  uint taskCount() {\n    return addTaskCounter;\n  }\n\n  // Indicates that the script has requested that it stay active until the given promise resolves.\n  // drain() waits until all such promises have completed.\n  void addWaitUntil(kj::Promise<void> promise);\n\n  // Returns the status of waitUntil promises. If a promise fails, this sets the status to the\n  // one corresponding to the exception type.\n  EventOutcome waitUntilStatus() const {\n    return waitUntilStatusValue;\n  }\n\n  // DO NOT USE, use `addWaitUntil()` instead.\n  kj::TaskSet& getWaitUntilTasks() {\n    // TODO(cleanup): This is only needed for use with RpcWorkerInterface, but we can eliminate\n    //   that class's need for waitUntilTasks if we change the signature of sendTraces() to return\n    //   a promise, I think.\n    return waitUntilTasks;\n  }\n\n  // Wraps a reference in a wrapper which:\n  // 1. Will throw an exception if dereferenced while the IoContext is not current for the\n  //    thread.\n  // 2. Can be safely destroyed from any thread.\n  // 3. Invalidates itself when the request ends (such that dereferencing throws).\n  template <typename T>\n  IoOwn<T> addObject(kj::Own<T> obj);\n\n  // Wraps a reference in a wrapper which:\n  // 1. Will throw an exception if dereferenced while the IoContext is not current for the\n  //    thread.\n  // 2. Can be safely destroyed from any thread.\n  // 3. Invalidates itself when the request ends (such that dereferencing throws).\n  template <typename T>\n  IoPtr<T> addObject(T& obj);\n\n  // Like addObject() but takes a functor, returning a functor with the same signature but which\n  // holds the original functor under a `IoOwn`, and so will stop working if the IoContext\n  // is no longer valid. This is particularly useful for passing to `jsg::Promise::then()` when\n  // you need the continuation to run in the correct context.\n  template <typename Func>\n  auto addFunctor(Func&& func);\n\n  // Attach an object to the IoContext such that it will be destroyed when either the returned\n  // reference is dropped OR the IoContext itself is destroyed. In the latter case, further\n  // attempts to access the returned reference will throw. The reference can only be used and\n  // destroyed within the same thread as the IoContext lives.\n  template <typename T>\n  ReverseIoOwn<T> addObjectReverse(kj::Own<T> obj);\n\n  // Call this to indicate that the caller expects to call into JavaScript in this IoContext\n  // at some point in the future, in response to some *external* event that the caller is waiting\n  // for. Then, hold on to the returned handle until that time. This prevents finalizers from being\n  // called in the meantime.\n  kj::Own<void> registerPendingEvent();\n  // TODO(cleanup): awaitIo() automatically applies this. Is the public method needed anymore?\n\n  // When you want to perform a task that returns Promise<DeferredProxy<T>> and the application\n  // JavaScript is waiting for the result, use `context.waitForDeferredProxy(promise)` to turn it\n  // into a regular `Promise<T>`, including registering pending events as needed.\n  template <typename T>\n  kj::Promise<T> waitForDeferredProxy(kj::Promise<api::DeferredProxy<T>>&& promise) {\n    return promise.then([this](api::DeferredProxy<T> deferredProxy) {\n      return deferredProxy.proxyTask.attach(registerPendingEvent());\n    });\n  }\n\n  // Like awaitIo(), but handles the specific case of Promise<DeferredProxy>. This is special\n  // because the convention is that the outer promise is NOT treated as a pending I/O event; it\n  // may actually be waiting for something to happen in JavaScript land. Once the outer promise\n  // resolves, the inner promise (the DeferredProxy<T>) is treated as external I/O.\n  template <typename T>\n  jsg::Promise<T> awaitDeferredProxy(jsg::Lock& js, kj::Promise<api::DeferredProxy<T>>&& promise) {\n    return awaitIoImpl(\n        js, waitForDeferredProxy(kj::mv(promise)), getCriticalSection(), IdentityFunc<T>());\n  }\n\n  // Called by ScheduledEvent\n  void setNoRetryScheduled() {\n    retryScheduled = false;\n  }\n\n  // Called by ServiceWorkerGlobalScope::runScheduled\n  bool shouldRetryScheduled() {\n    return retryScheduled;\n  }\n\n  // -----------------------------------------------------------------\n  // Access to I/O\n\n  // Used to implement setTimeout(). We don't expose the timer directly because the\n  // promises it returns need to live in this I/O context, anyway.\n  TimeoutId setTimeoutImpl(\n      TimeoutId::Generator& generator, bool repeat, jsg::Function<void()> function, double msDelay);\n\n  // Used to implement clearTimeout(). We don't expose the timer directly because the\n  // promises it returns need to live in this I/O context, anyway.\n  void clearTimeoutImpl(TimeoutId key);\n\n  size_t getTimeoutCount();\n\n  // Access the event loop's current time point. This will remain constant between ticks.\n  kj::Date now(IncomingRequest& incomingRequest);\n\n  // Access the event loop's current time point. This will remain constant between ticks.\n  kj::Date now();\n\n  TmpDirStoreScope& getTmpDirStoreScope() {\n    KJ_IF_SOME(scope, tmpDirStoreScope) {\n      return *scope;\n    }\n    return *tmpDirStoreScope.emplace(TmpDirStoreScope::create());\n  }\n\n  // Returns a promise that resolves once `now() >= when`.\n  kj::Promise<void> atTime(kj::Date when) {\n    return getIoChannelFactory().getTimer().atTime(when);\n  }\n\n  // Returns a promise that resolves after some time. This is intended to be used for implementing\n  // time limits on some sort of operation, not for implementing application-driven timing, as it\n  // does not maintain consistency with the clock as observed through Date.now(), e.g. when it\n  // comes to Spectre mitigations.\n  kj::Promise<void> afterLimitTimeout(kj::Duration t) {\n    return getIoChannelFactory().getTimer().afterLimitTimeout(t);\n  }\n\n  // Provide access to the system CSPRNG.\n  kj::EntropySource& getEntropySource() {\n    return thread.getEntropySource();\n  }\n\n  capnp::HttpOverCapnpFactory& getHttpOverCapnpFactory() {\n    return thread.getHttpOverCapnpFactory();\n  }\n\n  capnp::ByteStreamFactory& getByteStreamFactory() {\n    return thread.getByteStreamFactory();\n  }\n\n  const kj::HttpHeaderTable& getHeaderTable() {\n    return thread.getHeaderTable();\n  }\n  const ThreadContext::HeaderIdBundle& getHeaderIds() {\n    return thread.getHeaderIds();\n  }\n\n  kj::Rc<ExternalPusherImpl> getExternalPusher();\n\n  // Subrequest channel numbers for the two special channels.\n  // NULL = The channel used by global fetch() when the Request has no fetcher attached.\n  // NEXT = DEPRECATED: The fetcher attached to Requests delivered by a FetchEvent, so that we can\n  //     detect when an incoming request is passed through to `fetch()` (perhaps with rewrites)\n  //     and treat that case differently. In practice this has proven too confusing, so we don't\n  //     plan to treat NEXT and NULL differently going forward.\n  static constexpr uint NULL_CLIENT_CHANNEL = 0;\n  static constexpr uint NEXT_CLIENT_CHANNEL = 1;\n\n  // Number of subrequest channels that have special meaning (and so won't appear in any binding).\n  static constexpr uint SPECIAL_SUBREQUEST_CHANNEL_COUNT = 2;\n\n  struct SubrequestOptions final {\n    // When inHouse is true, the subrequest is to an API provided internally. For example calls\n    // to KV. This primarily affects metrics and limits.\n    bool inHouse;\n\n    // When true, the client is wrapped by metrics.wrapSubrequestClient() ensuring appropriate\n    // metrics collection.\n    bool wrapMetrics;\n\n    // The name to use for the request's span if tracing is turned on.\n    kj::Maybe<kj::ConstString> operationName;\n\n    // The tracing context to use for the subrequest if tracing is enabled.\n    kj::Maybe<TraceContext&> existingTraceContext;\n  };\n\n  kj::Own<WorkerInterface> getSubrequestNoChecks(\n      kj::FunctionParam<kj::Own<WorkerInterface>(TraceContext&, IoChannelFactory&)> func,\n      SubrequestOptions options);\n\n  // If creating a new subrequest is permitted, calls the given factory function synchronously to\n  // create one.\n  // If operationName is specified within options and tracing is enabled, this will add a child span\n  // to the current trace span for both tracing formats.\n  // TODO(o11y): In the future we may need to change the interface to support having different span\n  // names and enforce that only documented spans can be emitted.\n  kj::Own<WorkerInterface> getSubrequest(\n      kj::FunctionParam<kj::Own<WorkerInterface>(TraceContext&, IoChannelFactory&)> func,\n      SubrequestOptions options);\n\n  // Get WorkerInterface objects to use for subrequests.\n  //\n  // `channel` specifies which outgoing channel to use. The special channel 0 refers to the \"null\"\n  // binding (used for fetches where `request.fetcher` is not set), and channel 1 refers to the\n  // \"next\" binding (used when request.fetcher is carried over from the incoming request).\n  // Named bindings, e.g. Worker2Worker bindings, will have indices starting from 2. Fetcher\n  // bindings declared via Worker::Global::Fetcher have a corresponding `channel` property to refer\n  // to these outgoing bindings.\n  //\n  // `isInHouse` is true if this client represents an \"in house\" endpoint, i.e. some API provided\n  // by the Workers platform. For example, KV namespaces are in-house. This primarily affects\n  // metrics and limits:\n  // - In-house requests do not count as \"subrequests\" for metrics and logging purposes.\n  // - In-house requests are not subject to the same limits on the number of subrequests per\n  //   request.\n  // - In preview, in-house requests do not show up in the network tab.\n  //\n  // `operationName` is the name to use for the request's span, if tracing is turned on.\n  kj::Own<WorkerInterface> getSubrequestChannel(uint channel,\n      bool isInHouse,\n      kj::Maybe<kj::String> cfBlobJson,\n      kj::ConstString operationName);\n\n  // Get WorkerInterface objects to use for subrequests.\n  //\n  // `channel` specifies which outgoing channel to use. The special channel 0 refers to the \"null\"\n  // binding (used for fetches where `request.fetcher` is not set), and channel 1 refers to the\n  // \"next\" binding (used when request.fetcher is carried over from the incoming request).\n  // Named bindings, e.g. Worker2Worker bindings, will have indices starting from 2. Fetcher\n  // bindings declared via Worker::Global::Fetcher have a corresponding `channel` property to refer\n  // to these outgoing bindings.\n  //\n  // `isInHouse` is true if this client represents an \"in house\" endpoint, i.e. some API provided\n  // by the Workers platform. For example, KV namespaces are in-house. This primarily affects\n  // metrics and limits:\n  // - In-house requests do not count as \"subrequests\" for metrics and logging purposes.\n  // - In-house requests are not subject to the same limits on the number of subrequests per\n  //   request.\n  // - In preview, in-house requests do not show up in the network tab.\n  //\n  // `traceContext` is the trace context to use for the subrequest, if tracing is turned on.\n  kj::Own<WorkerInterface> getSubrequestChannel(\n      uint channel, bool isInHouse, kj::Maybe<kj::String> cfBlobJson, TraceContext& traceContext);\n\n  // Like getSubrequestChannel() but doesn't enforce limits. Use for trusted paths only.\n  kj::Own<WorkerInterface> getSubrequestChannelNoChecks(uint channel,\n      bool isInHouse,\n      kj::Maybe<kj::String> cfBlobJson,\n      kj::Maybe<kj::ConstString> operationName = kj::none);\n\n  // Convenience methods that call getSubrequest*() and adapt the returned WorkerInterface objects\n  // to HttpClient.\n  kj::Own<kj::HttpClient> getHttpClient(uint channel,\n      bool isInHouse,\n      kj::Maybe<kj::String> cfBlobJson,\n      kj::ConstString operationName);\n\n  kj::Own<kj::HttpClient> getHttpClient(\n      uint channel, bool isInHouse, kj::Maybe<kj::String> cfBlobJson, TraceContext& traceContext);\n  // TODO(cleanup): Make it the caller's job to call asHttpClient() on the result of\n  //   getSubrequest*().\n\n  capnp::Capability::Client getCapnpChannel(uint channel) {\n    return getIoChannelFactory().getCapability(channel);\n  }\n\n  kj::Own<IoChannelFactory::ActorChannel> getGlobalActorChannel(uint channel,\n      const ActorIdFactory::ActorId& id,\n      kj::Maybe<kj::String> locationHint,\n      ActorGetMode mode,\n      bool enableReplicaRouting,\n      ActorRoutingMode routingMode,\n      SpanParent parentSpan,\n      kj::Maybe<ActorVersion> version) {\n    return getIoChannelFactory().getGlobalActor(channel, id, kj::mv(locationHint), mode,\n        enableReplicaRouting, routingMode, kj::mv(parentSpan), kj::mv(version));\n  }\n  kj::Own<IoChannelFactory::ActorChannel> getColoLocalActorChannel(\n      uint channel, kj::StringPtr id, SpanParent parentSpan) {\n    return getIoChannelFactory().getColoLocalActor(channel, id, kj::mv(parentSpan));\n  }\n\n  void abortAllActors(kj::Maybe<kj::Exception&> reason) {\n    getIoChannelFactory().abortAllActors(reason);\n  }\n\n  // Get an HttpClient to use for Cache API subrequests.\n  kj::Own<CacheClient> getCacheClient();\n\n  // Returns an object that ensures an async JS operation started in the current scope captures the\n  // given trace span, or the current request's trace span, if no span is given.\n  jsg::AsyncContextFrame::StorageScope makeAsyncTraceScope(\n      Worker::Lock& lock, kj::Maybe<SpanParent> spanParent = kj::none) KJ_WARN_UNUSED_RESULT;\n\n  // Returns the current span being recorded.  If called while the JS lock is held, uses the trace\n  // information from the current async context, if available.\n  SpanParent getCurrentTraceSpan();\n  SpanParent getCurrentUserTraceSpan();\n\n  tracing::InvocationSpanContext& getInvocationSpanContext() {\n    return getCurrentIncomingRequest().getInvocationSpanContext();\n  }\n\n  // Returns a builder for recording tracing spans (or a no-op builder if tracing is inactive).\n  // If called while the JS lock is held, uses the trace information from the current async\n  // context, if available.\n  [[nodiscard]] SpanBuilder makeTraceSpan(kj::ConstString operationName);\n  // Returns both an internal and a user tracing span, this ensures that all user spans are\n  // available in internal tracing.\n  [[nodiscard]] TraceContext makeUserTraceSpan(kj::ConstString operationName);\n\n  // Implement per-IoContext rate limiting for Cache.put(). Pass the body of a Cache API PUT\n  // request and get a possibly wrapped stream back.\n  //\n  // If the stream has an unknown length, you will get a wrapped stream back that is used to\n  // serialize PUT requests.\n  jsg::Promise<IoOwn<kj::AsyncInputStream>> makeCachePutStream(\n      jsg::Lock& js, kj::Own<kj::AsyncInputStream> stream);\n  // TODO(cleanup): Factor this into getCacheClient() somehow so it's not opt-in.\n\n  // Gets a CapabilityServerSet representing the capnp capabilities hosted by this request or\n  // actor context. This allows us to implement the CapnpCapability::unwrap() method on\n  // capabilities which allows the application to get at the underlying server object, when the\n  // capability points to a local object.\n  capnp::CapabilityServerSet<capnp::DynamicCapability>& getLocalCapSet() {\n    return localCapSet;\n  }\n\n  void writeLogfwdr(uint channel, kj::FunctionParam<void(capnp::AnyPointer::Builder)> buildMessage);\n\n  jsg::JsObject getPromiseContextTag(jsg::Lock& js);\n\n  // The IoChannelFactory must be accessed through the\n  // currentIncomingRequest because it has some tracing context built in.\n  //\n  // TODO(later): this is made public for Python Workers. It should be possible to make this private\n  // again later.\n  IoChannelFactory& getIoChannelFactory() {\n    return *getCurrentIncomingRequest().ioChannelFactory;\n  }\n\n  void pumpMessageLoop();\n\n private:\n  ThreadContext& thread;\n\n  kj::Own<WeakRef> selfRef = kj::refcounted<WeakRef>(kj::Badge<IoContext>(), *this);\n\n  kj::Maybe<kj::Own<TmpDirStoreScope>> tmpDirStoreScope;\n\n  kj::Own<const Worker> worker;\n  kj::Maybe<Worker::Actor&> actor;\n  kj::Own<LimitEnforcer> limitEnforcer;\n\n  // List of active IncomingRequests, ordered from most-recently-started to least-recently-started.\n  kj::List<IncomingRequest, &IncomingRequest::link> incomingRequests;\n\n  kj::Maybe<kj::SourceLocation> lastDeliveredLocation;\n\n  capnp::CapabilityServerSet<capnp::DynamicCapability> localCapSet;\n\n  bool failOpen = false;\n\n  // For debug checks.\n  void* threadId;\n\n  // For scheduled workers noRetry calls\n  bool retryScheduled = true;\n\n  kj::Maybe<Worker::Lock&> currentLock;\n  kj::Maybe<InputGate::Lock> currentInputLock;\n\n  DeleteQueuePtr deleteQueue;\n\n  kj::Maybe<kj::Exception> abortException;\n  kj::Own<kj::PromiseFulfiller<void>> abortFulfiller;\n  kj::ForkedPromise<void> abortPromise = nullptr;\n\n  class PendingEvent;\n\n  kj::Maybe<PendingEvent&> pendingEvent;\n  kj::Maybe<kj::Promise<void>> abortFromHangTask;\n\n  // Objects pointed to by IoOwn<T>s.\n  // NOTE: This must live below `deleteQueue`, as some of these OwnedObjects may own attachctx()'ed\n  //   objects which reference `deleteQueue` in their destructors.\n  OwnedObjectList ownedObjects;\n\n  kj::Maybe<kj::Rc<ExternalPusherImpl>> externalPusher;\n\n  // Implementation detail of makeCachePutStream().\n\n  // TODO: Used for Cache PUT serialization.\n  kj::Promise<void> cachePutSerializer;\n\n  // The timeout manager needs to live below `deleteQueue` because the promises may refer to\n  // objects in the queue.\n  //\n  // ATTENTION: `timeoutManager` MUST be declared before both `waitUntilTasks` and `tasks` so it\n  // outlives them. During TaskSet destruction, deferred callbacks (e.g. the one in Scheduler::wait\n  // that clears the timer slot via clearTimeoutImpl) still need a live timeoutManager. C++ destroys\n  // members in reverse declaration order, so declaring timeoutManager first ensures it is destroyed\n  // last among these three.\n  kj::Own<TimeoutManager> timeoutManager;\n\n  kj::TaskSet waitUntilTasks;\n  EventOutcome waitUntilStatusValue = EventOutcome::OK;\n\n  void setTimeoutImpl(TimeoutId timeoutId,\n      bool repeat,\n      jsg::V8Ref<v8::Function> function,\n      double msDelay,\n      kj::Array<jsg::Value> args);\n\n  uint addTaskCounter = 0;\n  kj::TaskSet tasks;\n\n  // This canceler will be canceled when the IoContext is destroyed. Use it to wrap promises that\n  // need to be held externally but which should error if the IoContext is canceled. This is used\n  // for `makeReentryCallback()` in particular.\n  kj::Canceler canceler;\n\n  kj::Own<WorkerInterface> getSubrequestChannelImpl(uint channel,\n      bool isInHouse,\n      kj::Maybe<kj::String> cfBlobJson,\n      TraceContext& tracing,\n      IoChannelFactory& channelFactory);\n\n  friend class IoContext_IncomingRequest;\n  template <typename T>\n  friend class IoOwn;\n  template <typename T>\n  friend class IoPtr;\n\n  void taskFailed(kj::Exception&& exception) override;\n  void requireCurrent();\n  void checkFarGet(const DeleteQueue& expectedQueue, const std::type_info& type);\n\n  kj::Maybe<jsg::JsRef<jsg::JsObject>> promiseContextTag;\n\n  class Runnable {\n   public:\n    using Exceptional = IoContext_Runnable_Exceptional;\n    virtual void run(Worker::Lock& lock) = 0;\n  };\n  void runImpl(Runnable& runnable,\n      Worker::LockType lockType,\n      kj::Maybe<InputGate::Lock> inputLock,\n      Runnable::Exceptional exceptional);\n\n  void abortFromHang(Worker::AsyncLock& asyncLock);\n\n  template <typename T>\n  struct IdentityFunc {\n    inline T operator()(jsg::Lock&, T&& value) const {\n      return kj::mv(value);\n    }\n  };\n  template <>\n  struct IdentityFunc<void> {\n    inline void operator()(jsg::Lock&) const {}\n  };\n\n  template <typename T>\n  struct ExceptionOr_ {\n    using Type = kj::OneOf<T, kj::Exception>;\n  };\n  template <>\n  struct ExceptionOr_<void> {\n    using Type = kj::Maybe<kj::Exception>;\n  };\n  template <typename T>\n  using ExceptionOr = ExceptionOr_<T>::Type;\n\n  template <typename T, typename InputLockOrMaybeCriticalSection, typename Func>\n  jsg::PromiseForResult<Func, T, true> awaitIoImpl(\n      jsg::Lock& js, kj::Promise<T> promise, InputLockOrMaybeCriticalSection ilOrCs, Func&& func);\n\n  // The IncomingRequest that is currently considered \"current\". This is always the\n  // latest-starting request that hasn't yet completed.\n  //\n  // For stateless requests, there is only ever one IncomingRequest per IoContext. For\n  // actors, there is one IoContext per actor, and each incoming request to the actor\n  // creates a new  IncomingRequest.\n  //\n  // The current request is tracked for metrics, logging, and tracing purposes. Any resource\n  // usage on the part of the actor, including outgoing subrequests, is attributed to the current\n  // request for logging and tracing. This is a hack, we don't actually know which request\n  // \"caused\" any particular resource usage, so this is merely our best guess.\n  //\n  // The IoChannelFactory must also be accessed through the currentIncomingRequest because it has\n  // some tracing context built in.\n  IncomingRequest& getCurrentIncomingRequest() {\n    KJ_REQUIRE(!incomingRequests.empty(), \"the IoContext has no current IncomingRequest\",\n        lastDeliveredLocation);\n    return incomingRequests.front();\n  }\n\n  // Run the given callback within the scope of this IoContext. This encapsulates the\n  // setup of a number of scopes that must be entered prior to running within the\n  // context, including entering the V8StackScope and acquiring the Worker::Lock.\n  void runInContextScope(Worker::LockType lockType,\n      kj::Maybe<InputGate::Lock> inputLock,\n      kj::Function<void(Worker::Lock&)> func);\n\n  kj::Promise<void> deleteQueueSignalTask;\n  static kj::Promise<void> startDeleteQueueSignalTask(IoContext* context);\n\n  friend class Finalizeable;\n  friend class DeleteQueue;\n  template <typename T>\n  friend kj::Promise<ExceptionOr<T>> promiseForExceptionOrT(kj::Promise<T> promise);\n  template <typename Result>\n  friend Result throwOrReturnResult(\n      jsg::Lock& js, IoContext::ExceptionOr<Result>&& exceptionOrResult);\n};\n\n// The SuppressIoContextScope utility is used to temporarily suppress the active IoContext\n// on the current thread while it is in scope.\nstruct SuppressIoContextScope {\n  IoContext* cached;\n  SuppressIoContextScope();\n  ~SuppressIoContextScope() noexcept(false);\n  KJ_DISALLOW_COPY_AND_MOVE(SuppressIoContextScope);\n};\n\n// =======================================================================================\n// inline implementation details\n\ntemplate <typename T>\nkj::Promise<T> IoContext::lockOutputWhile(kj::Promise<T> promise) {\n  return getActorOrThrow().getOutputGate().lockWhile(kj::mv(promise), getCurrentTraceSpan());\n}\n\ntemplate <typename Func>\nkj::PromiseForResult<Func, Worker::Lock&> IoContext::run(\n    Func&& func, kj::Maybe<kj::Own<InputGate::CriticalSection>> criticalSection) {\n  KJ_IF_SOME(cs, criticalSection) {\n    return cs.get()\n        ->wait(getCurrentTraceSpan())\n        .then([this, func = kj::fwd<Func>(func)](InputGate::Lock&& inputLock) mutable {\n      return run(kj::fwd<Func>(func), kj::mv(inputLock));\n    });\n  } else {\n    return run(kj::fwd<Func>(func));\n  }\n}\n\ntemplate <typename Func>\nkj::PromiseForResult<Func, Worker::Lock&> IoContext::run(\n    Func&& func, kj::Maybe<InputGate::Lock> inputLock) {\n  // Before we try running anything, let's make sure our IoContext hasn't been aborted. If it has\n  // been aborted, there's likely not an active request so later operations will fail anyway.\n  KJ_IF_SOME(ex, abortException) {\n    return kj::cp(ex);\n  }\n\n  kj::Promise<Worker::AsyncLock> asyncLockPromise = nullptr;\n  KJ_IF_SOME(a, actor) {\n    if (inputLock == kj::none) {\n      return a.getInputGate()\n          .wait(getCurrentTraceSpan())\n          .then([this, func = kj::fwd<Func>(func)](InputGate::Lock&& inputLock) mutable {\n        return run(kj::fwd<Func>(func), kj::mv(inputLock));\n      });\n    }\n\n    asyncLockPromise = worker->takeAsyncLockWhenActorCacheReady(now(), a, getMetrics());\n  } else {\n    asyncLockPromise = worker->takeAsyncLock(getMetrics());\n  }\n\n  return asyncLockPromise.then([this, inputLock = kj::mv(inputLock), func = kj::fwd<Func>(func)](\n                                   Worker::AsyncLock lock) mutable {\n    using Result = decltype(func(kj::instance<Worker::Lock&>()));\n\n    if constexpr (kj::isSameType<Result, void>()) {\n      struct RunnableImpl: public Runnable {\n        Func func;\n\n        RunnableImpl(Func&& func): func(kj::fwd<Func>(func)) {}\n        void run(Worker::Lock& lock) override {\n          func(lock);\n        }\n      };\n\n      RunnableImpl runnable(kj::fwd<Func>(func));\n      runImpl(runnable, lock, kj::mv(inputLock), Runnable::Exceptional(false));\n    } else {\n      struct RunnableImpl: public Runnable {\n        Func func;\n        kj::Maybe<Result> result;\n\n        RunnableImpl(Func&& func): func(kj::fwd<Func>(func)) {}\n        void run(Worker::Lock& lock) override {\n          result = func(lock);\n        }\n      };\n\n      RunnableImpl runnable{kj::fwd<Func>(func)};\n      runImpl(runnable, lock, kj::mv(inputLock), Runnable::Exceptional(false));\n      KJ_IF_SOME(r, runnable.result) {\n        return kj::mv(r);\n      } else {\n        KJ_UNREACHABLE;\n      }\n    }\n  });\n}\n\ntemplate <typename T, typename Func>\njsg::PromiseForResult<Func, T, true> IoContext::awaitIo(\n    jsg::Lock& js, kj::Promise<T> promise, Func&& func) {\n  return awaitIoImpl(\n      js, promise.attach(registerPendingEvent()), getCriticalSection(), kj::fwd<Func>(func));\n}\n\ntemplate <typename T>\njsg::Promise<T> IoContext::awaitIo(jsg::Lock& js, kj::Promise<T> promise) {\n  return awaitIoImpl(\n      js, promise.attach(registerPendingEvent()), getCriticalSection(), IdentityFunc<T>());\n}\n\ntemplate <typename T, typename Func>\njsg::PromiseForResult<Func, T, true> IoContext::awaitIoWithInputLock(\n    jsg::Lock& js, kj::Promise<T> promise, Func&& func) {\n  return awaitIoImpl(\n      js, promise.attach(registerPendingEvent()), getInputLock(), kj::fwd<Func>(func));\n}\n\ntemplate <typename T>\njsg::Promise<T> IoContext::awaitIoWithInputLock(jsg::Lock& js, kj::Promise<T> promise) {\n  return awaitIoImpl(js, promise.attach(registerPendingEvent()), getInputLock(), IdentityFunc<T>());\n}\n\ntemplate <typename T>\njsg::Promise<T> IoContext::awaitIoLegacy(jsg::Lock& js, kj::Promise<T> promise) {\n  return awaitIoImpl(js, kj::mv(promise), getCriticalSection(), IdentityFunc<T>());\n}\n\ntemplate <typename T>\njsg::Promise<T> IoContext::awaitIoLegacyWithInputLock(jsg::Lock& js, kj::Promise<T> promise) {\n  return awaitIoImpl(js, kj::mv(promise), getInputLock(), IdentityFunc<T>());\n}\n\n// To reduce the code size impact of awaitIoImpl, move promise continuation code out of\n// awaitIoImpl() where possible. This way, the then() parameters are only templated based on one\n// type each.\ntemplate <typename T>\nkj::Promise<IoContext::ExceptionOr<T>> promiseForExceptionOrT(kj::Promise<T> promise) {\n  if constexpr (jsg::isVoid<T>()) {\n    return promise.then([]() -> IoContext::ExceptionOr<T> { return kj::none; },\n        [](kj::Exception&& exception) -> IoContext::ExceptionOr<T> { return kj::mv(exception); });\n  } else {\n    return promise.then([](T&& result) -> IoContext::ExceptionOr<T> { return kj::mv(result); },\n        [](kj::Exception&& exception) -> IoContext::ExceptionOr<T> { return kj::mv(exception); });\n  }\n};\n\ntemplate <typename Result>\nResult throwOrReturnResult(jsg::Lock& js, IoContext::ExceptionOr<Result>&& exceptionOrResult) {\n  if constexpr (jsg::isVoid<Result>()) {\n    KJ_IF_SOME(e, exceptionOrResult) {\n      // Now that we're in a promise continuation, we can convert the error and get a good stack\n      // trace.\n      js.throwException(kj::mv(e));\n    }\n  } else {\n    KJ_SWITCH_ONEOF(exceptionOrResult) {\n      KJ_CASE_ONEOF(e, kj::Exception) {\n        // Now that we're in a promise continuation, we can convert the error and get a good stack\n        // trace.\n        js.throwException(kj::mv(e));\n      }\n      KJ_CASE_ONEOF(result, Result) {\n        return kj::mv(result);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n};\n\ntemplate <typename T, typename InputLockOrMaybeCriticalSection, typename Func>\njsg::PromiseForResult<Func, T, true> IoContext::awaitIoImpl(\n    jsg::Lock& js, kj::Promise<T> promise, InputLockOrMaybeCriticalSection ilOrCs, Func&& func) {\n  // WARNING: The fact that `promise` has been passed by value whereas `func` is by reference is\n  // actually important, because this means that if we throw an exception here in the function\n  // body, `promise` will be destroyed first, before `func`. That's important as often `func`\n  // holds ownership of objects that `promise` depends on.\n\n  requireCurrent();\n\n  // `T` is the type produced by the input promise. `Result` is the type of the final output\n  // promise. `Func` transforms from `T` to `Result`.\n  using Result = jsg::ReturnType<Func, T, true>;\n\n  // It is necessary for us to grab a reference to the jsg::AsyncContextFrame here\n  // and pass it into the then(). If the promise is rejected, and there is no rejection\n  // handler attached to it, an unhandledrejection event will be scheduled, and scheduling\n  // that event needs to be done within the appropriate frame to propagate the correct context.\n\n  // We need to catch exceptions from KJ and merge them into the result, so that they can propagate\n  // to JavaScript.\n  kj::Promise<ExceptionOr<T>> promiseExceptionOrT = promiseForExceptionOrT(kj::mv(promise));\n\n  // Reminder: This can throw JsExceptionThrown if the execution context has been terminated.\n  // it's important in that case that `promiseExceptionOrT` will be destroyed before `func`.\n  auto [jsPromise, resolver] = js.newPromiseAndResolver<ExceptionOr<Result>>();\n\n  addTask(promiseExceptionOrT.then(\n      [this, resolver = kj::mv(resolver), ilOrCs = kj::mv(ilOrCs),\n          maybeAsyncContext = jsg::AsyncContextFrame::currentRef(js),\n          // Reminder: It's important that `func` gets attached to the promise before the whole\n          // thing is passed to `addTask()`, so that it's impossible for `func` to be destroyed\n          // before the inner promise.\n          func = kj::fwd<Func>(func)](ExceptionOr<T>&& exceptionOrT) mutable {\n    struct FuncResultPair {\n      // It's important that `exceptionOrT` is destroyed before `Func`. Lambda captures are\n      // destroyed in unspecified order, so we wrap them in a struct to make it explicit.\n      Func func;\n      ExceptionOr<T> exceptionOrT;\n    };\n\n    return run(\n        [resolver = kj::mv(resolver),\n            funcResultPair = FuncResultPair{kj::fwd<Func>(func), kj::mv(exceptionOrT)},\n            maybeAsyncContext = kj::mv(maybeAsyncContext)](Worker::Lock& lock) mutable {\n      jsg::AsyncContextFrame::Scope asyncScope(lock, maybeAsyncContext);\n      jsg::Lock& js = lock;\n\n      if constexpr (jsg::isVoid<T>()) {\n        KJ_IF_SOME(e, funcResultPair.exceptionOrT) {\n          // We don't use `resolver.reject()` here because if we convert the kj::Exception into\n          // a JS Error here, it won't have a useful stack trace. V8 can generate a good stack\n          // trace as long as we construct the Error inside of a promise continuation, so we use\n          // a `.then()` below that actually extracts the kj::Exception and turn it into a JS\n          // Error.\n          resolver.resolve(js, kj::mv(e));\n        } else {\n          try {\n            js.tryCatch([&]() {\n              if constexpr (jsg::isVoid<Result>()) {\n                funcResultPair.func(js);\n                resolver.resolve(js, kj::none);\n              } else {\n                resolver.resolve(js, funcResultPair.func(js));\n              }\n            }, [&](jsg::Value error) {\n              // Here we can just `resolver.reject` because we already have a JS exception.\n              resolver.reject(js, error.getHandle(js));\n            });\n          } catch (jsg::JsExceptionThrown&) {\n            // An uncatchable JS exception -- presumably, the isolate has been terminated. We\n            // can't convert this into a promise rejection, we need to just propagate it up.\n            throw;\n          } catch (...) {\n            // Again, pass along the KJ exception so we can convert it later in the right context.\n            resolver.resolve(js, kj::getCaughtExceptionAsKj());\n          }\n        }\n      } else {\n        // T is not void.\n        KJ_SWITCH_ONEOF(funcResultPair.exceptionOrT) {\n          KJ_CASE_ONEOF(exception, kj::Exception) {\n            // Again, pass along the KJ exception so we can convert it later in the right context.\n            resolver.resolve(js, kj::mv(exception));\n          }\n          KJ_CASE_ONEOF(result, T) {\n            try {\n              js.tryCatch([&]() {\n                if constexpr (jsg::isVoid<Result>()) {\n                  funcResultPair.func(js, kj::mv(result));\n                  resolver.resolve(js, kj::none);\n                } else {\n                  // Here we can just `resolver.reject` because we already have a JS exception.\n                  resolver.resolve(js, funcResultPair.func(js, kj::mv(result)));\n                }\n              }, [&](jsg::Value error) { resolver.reject(js, error.getHandle(js)); });\n            } catch (jsg::JsExceptionThrown&) {\n              // An uncatchable JS exception -- presumably, the isolate has been terminated. We\n              // can't convert this into a promise rejection, we need to just propagate it up.\n              throw;\n            } catch (...) {\n              // Again, pass along the KJ exception so we can convert it later in the right context.\n              resolver.resolve(js, kj::getCaughtExceptionAsKj());\n            }\n          }\n        }\n      }\n    },\n        kj::mv(ilOrCs));\n  }));\n\n  // Reminder: This can throw JsExceptionThrown if the execution context has been terminated. We\n  // have already disowned `promise` and `func` by this point, though, so teardown order is no\n  // longer our concern.\n  return jsPromise.then(js, throwOrReturnResult<Result>);\n}\n\ntemplate <typename T>\nkj::_::ReducePromises<RemoveIoOwn<T>> IoContext::awaitJs(jsg::Lock& js, jsg::Promise<T> jsPromise) {\n  auto paf = kj::newPromiseAndFulfiller<RemoveIoOwn<T>>();\n  struct RefcountedFulfiller: public kj::Refcounted {\n    kj::Own<kj::PromiseFulfiller<RemoveIoOwn<T>>> fulfiller;\n    kj::Own<const AtomicWeakRef<Worker::Isolate>> maybeIsolate;\n    bool isDone = false;\n\n    RefcountedFulfiller(kj::Own<const AtomicWeakRef<Worker::Isolate>> maybeIsolate,\n        kj::Own<kj::PromiseFulfiller<RemoveIoOwn<T>>> fulfiller)\n        : fulfiller(kj::mv(fulfiller)),\n          maybeIsolate(kj::mv(maybeIsolate)) {}\n\n    ~RefcountedFulfiller() noexcept(false) {\n      if (!isDone) {\n        reject();\n      }\n    }\n\n   private:\n    void reject() {\n      // We use a weak isolate reference here in case the isolate gets dropped before this code\n      // is executed. In that case we default to `false` as we cannot access the original isolate.\n      auto hasExcessivelyExceededHeapLimit = maybeIsolate->tryAddStrongRef()\n                                                 .map([](kj::Own<const Worker::Isolate> isolate) {\n        return isolate->getLimitEnforcer().hasExcessivelyExceededHeapLimit();\n      }).orDefault(false);\n      if (hasExcessivelyExceededHeapLimit) {\n        auto e = JSG_KJ_EXCEPTION(OVERLOADED, Error, \"Worker has exceeded memory limit.\");\n        e.setDetail(MEMORY_LIMIT_DETAIL_ID, kj::heapArray<kj::byte>(0));\n        fulfiller->reject(kj::mv(e));\n      } else {\n        // The JavaScript resolver was garbage collected, i.e. JavaScript will never resolve\n        // this promise.\n        fulfiller->reject(JSG_KJ_EXCEPTION(FAILED, Error, \"Promise will never complete.\"));\n      }\n    }\n  };\n  auto& isolate = Worker::Isolate::from(js);\n  auto fulfiller = kj::refcounted<RefcountedFulfiller>(isolate.getWeakRef(), kj::mv(paf.fulfiller));\n\n  auto errorHandler = [fulfiller = addObject(kj::addRef(*fulfiller))](\n                          jsg::Lock& js, jsg::Value jsExceptionRef) mutable {\n    // Note: `context` can possibly be different than the one that started the wait, if the\n    // promise resolved from a different context. In that case the use of `fulfiller` will\n    // throw later on. But it's OK to use the wrong context up until that point.\n    auto& context = IoContext::current();\n\n    auto isolate = context.getCurrentLock().getIsolate();\n    auto jsException = jsExceptionRef.getHandle(js);\n\n    // TODO(someday): We log an \"uncaught exception\" here whenever a promise returned from JS to\n    //   C++ rejects. However, the C++ code waiting on the promise may do its own logging (e.g.\n    //   event.respondWith() does), in which case this is redundant. But, it's difficult to be\n    //   sure that all C++ consumers log properly, and even if they do, the stack trace is lost\n    //   once the exception has been tunneled into a KJ exception, so the later logging won't be\n    //   as useful. We should improve the tunneling to include stack traces and ensure that all\n    //   consumers do in fact log exceptions, then we can remove this.\n    context.logUncaughtException(\n        UncaughtExceptionSource::INTERNAL_ASYNC, jsg::JsValue(jsException));\n\n    fulfiller->fulfiller->reject(jsg::createTunneledException(isolate, jsException));\n    fulfiller->isDone = true;\n  };\n\n  if constexpr (jsg::isVoid<T>()) {\n    jsPromise.then(js, [fulfiller = addObject(kj::mv(fulfiller))](jsg::Lock&) mutable {\n      fulfiller->fulfiller->fulfill();\n      fulfiller->isDone = true;\n    }, kj::mv(errorHandler));\n  } else {\n    jsPromise.then(js, [fulfiller = addObject(kj::mv(fulfiller))](jsg::Lock&, T&& result) mutable {\n      if constexpr (isIoOwn<T>()) {\n        fulfiller->fulfiller->fulfill(kj::mv(*result));\n      } else {\n        fulfiller->fulfiller->fulfill(kj::mv(result));\n      }\n      fulfiller->isDone = true;\n    }, kj::mv(errorHandler));\n  }\n\n  return paf.promise.exclusiveJoin(onAbort().then([]() -> RemoveIoOwn<T> { KJ_UNREACHABLE; }));\n}\n\ntemplate <IoContext::TopUpFlag topUp, typename Func>\nauto IoContext::makeReentryCallback(Func func) {\n  // A reentry callback is meant for *re-*entry, so should only be created while already inside\n  // the IoContext. Initial entry into the IoContext should just use run().\n  requireCurrent();\n\n  // We need to:\n  // - Use addTask() to make sure that, if we're in an actor, the IncomingEvent stays alive while\n  //   the callback exists (and hibernation is blocked).\n  // - Call registerPendingEvent() to make sure that, if we're NOT in an actor, we don't conclude\n  //   that there's nothing left to wait for while the callback exists.\n  // TODO(perf): Probably both of these things could be done in simpler ways involving less\n  //   allocation, but it would require some refactoring.\n  auto [promise, fulfiller] = kj::newPromiseAndFulfiller<void>();\n  addTask(kj::mv(promise));\n  auto releaseNotifier =\n      kj::defer([fulfiller = kj::mv(fulfiller), pe = registerPendingEvent()]() mutable {\n    fulfiller->fulfill();\n  });\n\n  auto ioFunc = addObjectReverse(kj::heap(kj::fwd<Func>(func)));\n\n  return [self = getWeakRef(), cs = getCriticalSection(), releaseNotifier = kj::mv(releaseNotifier),\n             ioFunc = kj::mv(ioFunc)](auto&&... params) mutable {\n    auto& ctx = JSG_REQUIRE_NONNULL(self->tryGet(), Error,\n        \"The execution context which hosts this callback is no longer running.\");\n\n    if constexpr (topUp == TOP_UP) {\n      ctx.getLimitEnforcer().topUpActor();\n    }\n\n    return ctx.canceler.wrap(ctx.run(\n        [&ctx, &ioFunc, ... params = kj::fwd<decltype(params)>(params)](\n            Worker::Lock& lock) mutable {\n      using ResultType = kj::Decay<decltype(func(lock, kj::fwd<decltype(params)>(params)...))>;\n\n      auto& func = *ioFunc;\n\n      if constexpr (kj::isSameType<ResultType, void>()) {\n        (void)ctx;\n        func(lock, kj::fwd<decltype(params)>(params)...);\n      } else if constexpr (jsg::isPromise<ResultType>()) {\n        return ctx.awaitJs(lock, func(lock, kj::fwd<decltype(params)>(params)...));\n      } else {\n        (void)ctx;\n        return func(lock, kj::fwd<decltype(params)>(params)...);\n      }\n    },\n        kj::mv(cs)));\n  };\n}\n\ntemplate <typename T>\ninline IoOwn<T> IoContext::addObject(kj::Own<T> obj) {\n  requireCurrent();\n  return deleteQueue.queue->addObject(kj::mv(obj), ownedObjects);\n}\n\ntemplate <typename T>\ninline IoPtr<T> IoContext::addObject(T& obj) {\n  requireCurrent();\n  return IoPtr<T>(deleteQueue.queue.addRef(), &obj);\n}\n\ntemplate <typename Func>\nauto IoContext::addFunctor(Func&& func) {\n  if constexpr (kj::isReference<Func>()) {\n    return [func = addObject(func)](\n               auto&&... params) mutable { return (*func)(kj::fwd<decltype(params)>(params)...); };\n  } else {\n    return [func = addObject(kj::heap(kj::mv(func)))](\n               auto&&... params) mutable { return (*func)(kj::fwd<decltype(params)>(params)...); };\n  }\n}\n\ntemplate <typename T>\ninline ReverseIoOwn<T> IoContext::addObjectReverse(kj::Own<T> obj) {\n  // We intentionally don't requireCurrent() -- the only requirement is that the caller is in the\n  // same thread.\n  return deleteQueue.queue->addObjectReverse(getWeakRef(), kj::mv(obj), ownedObjects);\n}\n\ntemplate <typename Func>\njsg::PromiseForResult<Func, void, true> IoContext::blockConcurrencyWhile(\n    jsg::Lock& js, Func&& callback) {\n  auto lock = getInputLock();\n  auto cs = lock.startCriticalSection();\n  auto cs2 = kj::addRef(*cs);\n\n  using T = jsg::RemovePromise<jsg::ReturnType<Func, void, true>>;\n  auto [result, resolver] = js.newPromiseAndResolver<T>();\n\n  addTask(\n      cs->wait(getCurrentTraceSpan())\n          .then([this, callback = kj::mv(callback),\n                    maybeAsyncContext = jsg::AsyncContextFrame::currentRef(js)](\n                    InputGate::Lock inputLock) mutable {\n    return run(\n        [this, callback = kj::mv(callback), maybeAsyncContext = kj::mv(maybeAsyncContext)](\n            Worker::Lock& lock) mutable {\n      jsg::AsyncContextFrame::Scope scope(lock, maybeAsyncContext);\n      auto cb = kj::mv(callback);\n\n      // Remember that this can throw synchronously, and it's important that we catch such throws\n      // and call cs->failed().\n      auto promise = cb(lock);\n\n      // Arrange to time out if the critical section runs more than 30 seconds, so that objects\n      // won't be hung forever if they have a critical section that deadlocks.\n      auto timeout = afterLimitTimeout(30 * kj::SECONDS).then([]() -> T {\n        kj::throwFatalException(JSG_KJ_EXCEPTION(OVERLOADED, Error,\n            \"A call to blockConcurrencyWhile() in a Durable Object waited for \"\n            \"too long. The call was canceled and the Durable Object was reset.\"));\n      });\n\n      return awaitJs(lock, kj::mv(promise)).exclusiveJoin(kj::mv(timeout));\n    },\n        kj::mv(inputLock));\n  })\n          .then(\n              [this, cs = kj::mv(cs), resolver = kj::mv(resolver),\n                  maybeAsyncContext = jsg::AsyncContextFrame::currentRef(js)](T&& value) mutable {\n    auto inputLock = cs->succeeded();\n    return run(\n        [value = kj::mv(value), resolver = kj::mv(resolver),\n            maybeAsyncContext = kj::mv(maybeAsyncContext)](Worker::Lock& lock) mutable {\n      jsg::AsyncContextFrame::Scope scope(lock, maybeAsyncContext);\n      resolver.resolve(lock, kj::mv(value));\n    },\n        kj::mv(inputLock));\n  },\n              [cs = kj::mv(cs2)](kj::Exception&& e) mutable {\n    // Annotate as broken for periodic metrics.\n    auto msg = e.getDescription();\n    if (!msg.startsWith(\"broken.\"_kj) && !msg.startsWith(\"remote.broken.\"_kj)) {\n      // If we already set up a brokenness reason, we shouldn't override it.\n\n      auto description = jsg::annotateBroken(msg, \"broken.inputGateBroken\");\n      e.setDescription(kj::mv(description));\n    }\n\n    // Note that on failure, no further InputLocks will be obtainable and the actor will\n    // shut down, so don't worry about holding a lock until we get back to application code --\n    // we won't! In fact, we don't even bother calling resolver.reject() because it's meaningless\n    // at this point.\n    cs->failed(e);\n\n    kj::throwFatalException(kj::mv(e));\n  }));\n\n  return kj::mv(result);\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/io-gate-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"io-gate.h\"\n\n#include <kj/test.h>\n\nnamespace workerd {\nnamespace {\n\nKJ_TEST(\"InputGate basics\") {\n  kj::EventLoop loop;\n  kj::WaitScope ws(loop);\n\n  InputGate gate;\n\n  kj::Promise<InputGate::Lock> promise1 = gate.wait(nullptr);\n  kj::Promise<InputGate::Lock> promise2 = gate.wait(nullptr);\n  kj::Promise<InputGate::Lock> promise3 = gate.wait(nullptr);\n\n  KJ_ASSERT(promise1.poll(ws));\n  KJ_EXPECT(!promise2.poll(ws));\n  KJ_EXPECT(!promise3.poll(ws));\n\n  {\n    auto lock = promise1.wait(ws);\n\n    KJ_EXPECT(!promise2.poll(ws));\n    KJ_EXPECT(!promise3.poll(ws));\n\n    auto lock2 = lock.addRef(nullptr);\n    { auto drop = kj::mv(lock); }\n\n    KJ_EXPECT(!promise2.poll(ws));\n    KJ_EXPECT(!promise3.poll(ws));\n  }\n\n  KJ_EXPECT(promise2.poll(ws));\n  KJ_EXPECT(!promise3.poll(ws));  // we'll cancel this waiter to make sure that works\n\n  KJ_EXPECT(!gate.onBroken().poll(ws));\n}\n\nKJ_TEST(\"InputGate critical section\") {\n  kj::EventLoop loop;\n  kj::WaitScope ws(loop);\n\n  InputGate gate;\n\n  kj::Own<InputGate::CriticalSection> cs;\n\n  {\n    auto lock = gate.wait(nullptr).wait(ws);\n    cs = lock.startCriticalSection();\n  }\n\n  {\n    // Take the first lock.\n    auto firstLock = cs->wait(nullptr).wait(ws);\n\n    // Other locks are blocked.\n    auto wait1 = cs->wait(nullptr);\n    auto wait2 = cs->wait(nullptr);\n    KJ_EXPECT(!wait1.poll(ws));\n    KJ_EXPECT(!wait2.poll(ws));\n\n    // Drop it.\n    { auto drop = kj::mv(firstLock); }\n\n    // Now other locks make progress.\n    {\n      auto lock = wait1.wait(ws);\n      KJ_EXPECT(!wait2.poll(ws));\n    }\n    wait2.wait(ws);\n  }\n\n  // Can't lock the top-level gate while CriticalSection still exists.\n  auto outerWait = gate.wait(nullptr);\n  KJ_EXPECT(!outerWait.poll(ws));\n\n  {\n    auto lock = cs->wait(nullptr).wait(ws);\n    cs->succeeded();\n    KJ_EXPECT(!outerWait.poll(ws));\n  }\n\n  outerWait.wait(ws);\n}\n\nKJ_TEST(\"InputGate multiple critical sections start together\") {\n  kj::EventLoop loop;\n  kj::WaitScope ws(loop);\n\n  InputGate gate;\n\n  kj::Own<InputGate::CriticalSection> cs1;\n  kj::Own<InputGate::CriticalSection> cs2;\n\n  {\n    auto lock = gate.wait(nullptr).wait(ws);\n    cs1 = lock.startCriticalSection();\n    cs2 = lock.startCriticalSection();\n  }\n\n  // Start cs1.\n  cs1->wait(nullptr).wait(ws);\n\n  // Can't start cs2 yet.\n  auto cs2Wait = cs2->wait(nullptr);\n  KJ_EXPECT(!cs2Wait.poll(ws));\n\n  cs1->succeeded();\n\n  cs2Wait.wait(ws);\n}\n\nKJ_TEST(\"InputGate nested critical sections\") {\n  kj::EventLoop loop;\n  kj::WaitScope ws(loop);\n\n  InputGate gate;\n\n  kj::Own<InputGate::CriticalSection> cs1;\n  kj::Own<InputGate::CriticalSection> cs2;\n\n  {\n    auto lock = gate.wait(nullptr).wait(ws);\n    cs1 = lock.startCriticalSection();\n  }\n\n  {\n    auto lock = cs1->wait(nullptr).wait(ws);\n    cs2 = lock.startCriticalSection();\n  }\n\n  // Start cs2.\n  cs2->wait(nullptr).wait(ws);\n\n  // Can't start new tasks in cs1 until cs2 finishes.\n  auto cs1Wait = cs1->wait(nullptr);\n  KJ_EXPECT(!cs1Wait.poll(ws));\n\n  cs2->succeeded();\n\n  cs1Wait.wait(ws);\n}\n\nKJ_TEST(\"InputGate nested critical section outlives parent\") {\n  kj::EventLoop loop;\n  kj::WaitScope ws(loop);\n\n  InputGate gate;\n\n  kj::Own<InputGate::CriticalSection> cs1;\n  kj::Own<InputGate::CriticalSection> cs2;\n\n  {\n    auto lock = gate.wait(nullptr).wait(ws);\n    cs1 = lock.startCriticalSection();\n  }\n\n  {\n    auto lock = cs1->wait(nullptr).wait(ws);\n    cs2 = lock.startCriticalSection();\n  }\n\n  // Start cs2.\n  cs2->wait(nullptr).wait(ws);\n\n  // Mark cs1 done. (Note that, in a real program, this probably can't happen like this, because a\n  // lock would be taken on cs1 before marking it done, and that lock would wait for cs2 to\n  // finish. But I want to make sure it works anyway.)\n  cs1->succeeded();\n\n  // Can't start new tasks in at root until cs2 finishes.\n  auto rootWait = gate.wait(nullptr);\n  KJ_EXPECT(!rootWait.poll(ws));\n\n  cs2->succeeded();\n\n  rootWait.wait(ws);\n}\n\nKJ_TEST(\"InputGate deeply nested critical sections\") {\n  kj::EventLoop loop;\n  kj::WaitScope ws(loop);\n\n  InputGate gate;\n\n  kj::Own<InputGate::CriticalSection> cs1;\n  kj::Own<InputGate::CriticalSection> cs2;\n  kj::Own<InputGate::CriticalSection> cs3;\n  kj::Own<InputGate::CriticalSection> cs4;\n\n  {\n    auto lock = gate.wait(nullptr).wait(ws);\n    cs1 = lock.startCriticalSection();\n  }\n\n  {\n    auto lock = cs1->wait(nullptr).wait(ws);\n    cs2 = lock.startCriticalSection();\n  }\n\n  {\n    auto lock = cs2->wait(nullptr).wait(ws);\n    cs3 = lock.startCriticalSection();\n    cs4 = lock.startCriticalSection();\n  }\n\n  // Start cs2\n  cs2->wait(nullptr).wait(ws);\n\n  // Add some waiters to cs2, some of which are waiting to start more nested critical sections\n  auto lock = cs2->wait(nullptr).wait(ws);\n  auto waiter1 = cs2->wait(nullptr);\n  auto waiter2 = cs2->wait(nullptr);\n\n  // Both of these wait on cs2 indirectly, as they are nested under cs2\n  auto waiter3 = cs3->wait(nullptr);\n  auto waiter4 = cs4->wait(nullptr);\n\n  KJ_EXPECT(!waiter1.poll(ws));\n  KJ_EXPECT(!waiter2.poll(ws));\n  KJ_EXPECT(!waiter3.poll(ws));\n  KJ_EXPECT(!waiter4.poll(ws));\n\n  // Mark cs2 as complete with outstanding waiters, and drop our reference to it.\n  cs2->succeeded();\n  cs2 = nullptr;\n\n  // Our waiters should still be outstanding as we have not released the lock\n  KJ_EXPECT(!waiter1.poll(ws));\n  KJ_EXPECT(!waiter2.poll(ws));\n  KJ_EXPECT(!waiter3.poll(ws));\n  KJ_EXPECT(!waiter4.poll(ws));\n\n  // Drop some outstanding waiters\n  { auto drop = kj::mv(waiter2); }\n  { auto drop = kj::mv(waiter4); }\n\n  // Release the lock on cs2\n  { auto drop = kj::mv(lock); }\n\n  // cs3 should have started\n  KJ_ASSERT(!waiter1.poll(ws));\n  KJ_ASSERT(waiter3.poll(ws));\n  auto lock2 = waiter3.wait(ws);\n\n  // Add a waiter on cs3\n  auto waiter5 = cs3->wait(nullptr);\n  KJ_ASSERT(!waiter5.poll(ws));\n\n  // Can't start new tasks on the root until both cs1 and cs3 have succeeded, and all outstanding\n  // tasks have either been dropped or completed.\n  auto waiter6 = gate.wait(nullptr);\n  KJ_ASSERT(!waiter6.poll(ws));\n\n  cs1->succeeded();\n  cs3->succeeded();\n\n  // drop waiter5\n  { auto drop = kj::mv(waiter5); }\n\n  // Release the lock on cs3\n  { auto drop = kj::mv(lock2); }\n\n  // Our root task should be ready now.\n  KJ_ASSERT(waiter6.poll(ws));\n  waiter6.wait(ws);\n}\n\nKJ_TEST(\"InputGate critical section lock outlives critical section\") {\n  kj::EventLoop loop;\n  kj::WaitScope ws(loop);\n\n  InputGate gate;\n\n  kj::Own<InputGate::CriticalSection> cs;\n\n  {\n    auto lock = gate.wait(nullptr).wait(ws);\n    cs = lock.startCriticalSection();\n  }\n\n  // Start critical section.\n  auto lock = cs->wait(nullptr).wait(ws);\n  KJ_ASSERT(lock.isFor(gate));\n\n  // Mark it done, even though a lock is still outstanding.\n  cs->succeeded();\n\n  // Drop our reference.\n  cs = nullptr;\n\n  // Lock should have been reparented, so should still work.\n  KJ_ASSERT(lock.isFor(gate));\n\n  // Adding a ref and dropping it shouldn't cause trouble.\n  lock.addRef(nullptr);\n\n  // The gate should still be locked\n  auto waiter = gate.wait(nullptr);\n  KJ_EXPECT(!waiter.poll(ws));\n\n  // Drop the outstanding lock\n  { auto drop = kj::mv(lock); }\n\n  // Our waiter should resolve now\n  KJ_ASSERT(waiter.poll(ws));\n  KJ_EXPECT(waiter.wait(ws).isFor(gate));\n}\n\nKJ_TEST(\"InputGate broken\") {\n  kj::EventLoop loop;\n  kj::WaitScope ws(loop);\n\n  InputGate gate;\n\n  auto brokenPromise = gate.onBroken();\n\n  kj::Own<InputGate::CriticalSection> cs1;\n  kj::Own<InputGate::CriticalSection> cs2;\n  kj::Own<InputGate::CriticalSection> cs3;\n\n  {\n    auto lock = gate.wait(nullptr).wait(ws);\n    cs1 = lock.startCriticalSection();\n    cs3 = lock.startCriticalSection();\n  }\n\n  {\n    auto lock = cs1->wait(nullptr).wait(ws);\n    cs2 = lock.startCriticalSection();\n  }\n\n  // start cs2\n  cs2->wait(nullptr).wait(ws);\n\n  auto cs1Wait = cs1->wait(nullptr);\n  KJ_EXPECT(!cs1Wait.poll(ws));\n\n  auto cs3Wait = cs3->wait(nullptr);\n  KJ_EXPECT(!cs3Wait.poll(ws));\n\n  auto rootWait = gate.wait(nullptr);\n  KJ_EXPECT(!rootWait.poll(ws));\n\n  cs2->failed(KJ_EXCEPTION(FAILED, \"foobar\"));\n\n  KJ_EXPECT_THROW_MESSAGE(\"foobar\", cs1Wait.wait(ws));\n  KJ_EXPECT_THROW_MESSAGE(\"foobar\", cs3Wait.wait(ws));\n  KJ_EXPECT_THROW_MESSAGE(\"foobar\", rootWait.wait(ws));\n  KJ_EXPECT_THROW_MESSAGE(\"foobar\", cs2->wait(nullptr).wait(ws));\n  KJ_EXPECT_THROW_MESSAGE(\"foobar\", brokenPromise.wait(ws));\n  KJ_EXPECT_THROW_MESSAGE(\"foobar\", gate.onBroken().wait(ws));\n}\n\n// =======================================================================================\n\nKJ_TEST(\"OutputGate basics\") {\n  kj::EventLoop loop;\n  kj::WaitScope ws(loop);\n\n  OutputGate gate;\n\n  KJ_EXPECT(gate.wait(nullptr).poll(ws));\n\n  auto paf1 = kj::newPromiseAndFulfiller<void>();\n  auto blocker1 = gate.lockWhile(kj::mv(paf1.promise), nullptr);\n\n  auto promise1 = gate.wait(nullptr);\n  auto promise2 = gate.wait(nullptr);\n\n  auto paf2 = kj::newPromiseAndFulfiller<void>();\n  auto blocker2 = gate.lockWhile(kj::mv(paf2.promise), nullptr);\n\n  auto promise3 = gate.wait(nullptr);\n\n  KJ_EXPECT(!promise1.poll(ws));\n  KJ_EXPECT(!promise2.poll(ws));\n  KJ_EXPECT(!promise3.poll(ws));\n\n  KJ_EXPECT(!blocker1.poll(ws));\n  paf1.fulfiller->fulfill();\n  KJ_EXPECT(blocker1.poll(ws));\n  blocker1.wait(ws);\n\n  KJ_EXPECT(promise1.poll(ws));\n  promise1.wait(ws);\n  KJ_EXPECT(promise2.poll(ws));\n  promise2.wait(ws);\n  KJ_EXPECT(!promise3.poll(ws));\n\n  KJ_EXPECT(!blocker2.poll(ws));\n  paf2.fulfiller->fulfill();\n  KJ_EXPECT(blocker2.poll(ws));\n  blocker2.wait(ws);\n\n  KJ_EXPECT(promise3.poll(ws));\n  promise3.wait(ws);\n\n  KJ_EXPECT(!gate.onBroken().poll(ws));\n}\n\nKJ_TEST(\"OutputGate out-of-order\") {\n  kj::EventLoop loop;\n  kj::WaitScope ws(loop);\n\n  OutputGate gate;\n\n  KJ_EXPECT(gate.wait(nullptr).poll(ws));\n\n  auto paf1 = kj::newPromiseAndFulfiller<void>();\n  auto blocker1 = gate.lockWhile(kj::mv(paf1.promise), nullptr);\n\n  auto promise1 = gate.wait(nullptr);\n  auto promise2 = gate.wait(nullptr);\n\n  auto paf2 = kj::newPromiseAndFulfiller<void>();\n  auto blocker2 = gate.lockWhile(kj::mv(paf2.promise), nullptr);\n\n  auto promise3 = gate.wait(nullptr);\n\n  KJ_EXPECT(!promise1.poll(ws));\n  KJ_EXPECT(!promise2.poll(ws));\n  KJ_EXPECT(!promise3.poll(ws));\n\n  // Fulfill second blocker first.\n  KJ_EXPECT(!blocker2.poll(ws));\n  paf2.fulfiller->fulfill();\n  KJ_EXPECT(blocker2.poll(ws));\n  blocker2.wait(ws);\n\n  // Everything is still blocked.\n  KJ_EXPECT(!promise1.poll(ws));\n  KJ_EXPECT(!promise2.poll(ws));\n  KJ_EXPECT(!promise3.poll(ws));\n\n  // Fulfill the first one.\n  KJ_EXPECT(!blocker1.poll(ws));\n  paf1.fulfiller->fulfill();\n  KJ_EXPECT(blocker1.poll(ws));\n  blocker1.wait(ws);\n\n  // Everything unblocked.\n  KJ_EXPECT(promise1.poll(ws));\n  promise1.wait(ws);\n  KJ_EXPECT(promise2.poll(ws));\n  promise2.wait(ws);\n  KJ_EXPECT(promise3.poll(ws));\n  promise3.wait(ws);\n\n  KJ_EXPECT(!gate.onBroken().poll(ws));\n}\n\nKJ_TEST(\"OutputGate exception\") {\n  kj::EventLoop loop;\n  kj::WaitScope ws(loop);\n\n  OutputGate gate;\n  auto onBroken = gate.onBroken();\n\n  KJ_EXPECT(gate.wait(nullptr).poll(ws));\n\n  auto paf1 = kj::newPromiseAndFulfiller<void>();\n  auto blocker1 = gate.lockWhile(kj::mv(paf1.promise), nullptr);\n\n  auto promise1 = gate.wait(nullptr);\n  auto promise2 = gate.wait(nullptr);\n\n  auto paf2 = kj::newPromiseAndFulfiller<void>();\n  auto blocker2 = gate.lockWhile(kj::mv(paf2.promise), nullptr);\n\n  auto promise3 = gate.wait(nullptr);\n\n  KJ_EXPECT(!promise1.poll(ws));\n  KJ_EXPECT(!promise2.poll(ws));\n  KJ_EXPECT(!promise3.poll(ws));\n\n  // Let's have the second blocker fail first.\n  paf2.fulfiller->reject(KJ_EXCEPTION(FAILED, \"foo\"));\n  KJ_EXPECT(blocker2.poll(ws));\n  KJ_EXPECT_THROW_MESSAGE(\"foo\", blocker2.wait(ws));\n\n  // Promises are all still waiting. TECHNICALLY, it would be OK to fail-fast the third promise,\n  // but for now we don't.\n  KJ_EXPECT(!promise1.poll(ws));\n  KJ_EXPECT(!promise2.poll(ws));\n  KJ_EXPECT(!promise3.poll(ws));\n\n  // We are marked broken at this point, though.\n  KJ_ASSERT(onBroken.poll(ws));\n  KJ_EXPECT_THROW_MESSAGE(\"foo\", onBroken.wait(ws));\n\n  // Fulfill the first blocker (normally, not with an exception).\n  KJ_EXPECT(!blocker1.poll(ws));\n  paf1.fulfiller->fulfill();\n  KJ_EXPECT(blocker1.poll(ws));\n  blocker1.wait(ws);\n\n  // Everything unblocked, but only the third promise fails.\n  KJ_EXPECT(promise1.poll(ws));\n  promise1.wait(ws);\n  KJ_EXPECT(promise2.poll(ws));\n  promise2.wait(ws);\n  KJ_EXPECT(promise3.poll(ws));\n  KJ_EXPECT_THROW_MESSAGE(\"foo\", promise3.wait(ws));\n\n  // Still broken.\n  onBroken = gate.onBroken();\n  KJ_ASSERT(onBroken.poll(ws));\n  KJ_EXPECT_THROW_MESSAGE(\"foo\", onBroken.wait(ws));\n}\n\nKJ_TEST(\"OutputGate canceled\") {\n  kj::EventLoop loop;\n  kj::WaitScope ws(loop);\n\n  OutputGate gate;\n  auto onBroken = gate.onBroken();\n\n  KJ_EXPECT(gate.wait(nullptr).poll(ws));\n\n  auto paf1 = kj::newPromiseAndFulfiller<void>();\n  auto blocker1 = gate.lockWhile(kj::mv(paf1.promise), nullptr);\n\n  auto promise1 = gate.wait(nullptr);\n  auto promise2 = gate.wait(nullptr);\n\n  auto blocker2 = gate.lockWhile(kj::Promise<void>(kj::NEVER_DONE), nullptr);\n\n  auto promise3 = gate.wait(nullptr);\n\n  KJ_EXPECT(!promise1.poll(ws));\n  KJ_EXPECT(!promise2.poll(ws));\n  KJ_EXPECT(!promise3.poll(ws));\n\n  // Let's cancel the second blocker first.\n  blocker2 = nullptr;\n\n  // Promises are all still waiting. TECHNICALLY, it would be OK to fail-fast the third promise,\n  // but for now we don't.\n  KJ_EXPECT(!promise1.poll(ws));\n  KJ_EXPECT(!promise2.poll(ws));\n  KJ_EXPECT(!promise3.poll(ws));\n\n  // We are marked broken at this point, though.\n  KJ_ASSERT(onBroken.poll(ws));\n  KJ_EXPECT_THROW_MESSAGE(\"output lock was canceled before completion\", onBroken.wait(ws));\n\n  // Fulfill the first blocker (normally, not with an exception).\n  KJ_EXPECT(!blocker1.poll(ws));\n  paf1.fulfiller->fulfill();\n  KJ_EXPECT(blocker1.poll(ws));\n  blocker1.wait(ws);\n\n  // Everything unblocked, but only the third promise fails.\n  KJ_EXPECT(promise1.poll(ws));\n  promise1.wait(ws);\n  KJ_EXPECT(promise2.poll(ws));\n  promise2.wait(ws);\n  KJ_EXPECT(promise3.poll(ws));\n  KJ_EXPECT_THROW_MESSAGE(\"output lock was canceled before completion\", promise3.wait(ws));\n\n  // Still broken.\n  onBroken = gate.onBroken();\n  KJ_ASSERT(onBroken.poll(ws));\n  KJ_EXPECT_THROW_MESSAGE(\"output lock was canceled before completion\", onBroken.wait(ws));\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/io-gate.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/io/io-gate.h>\n\n#include <kj/debug.h>\n\nnamespace workerd {\n\nconst InputGate::Hooks InputGate::Hooks::DEFAULT;\n\nInputGate::InputGate(Hooks& hooks): InputGate(hooks, kj::newPromiseAndFulfiller<void>()) {}\nInputGate::InputGate(Hooks& hooks, kj::PromiseFulfillerPair<void> paf)\n    : hooks(hooks),\n      brokenPromise(paf.promise.fork()),\n      brokenState(kj::mv(paf.fulfiller)) {}\nInputGate::~InputGate() noexcept {\n  // Intentionally `noexcept` because if this throws then there are dangling references.\n  KJ_ASSERT(lockCount == 0,\n      \"destroying InputGate when locks are still present; they would become dangling references\");\n\n  // If the lock count is zero, then the waiters must be empty.\n  KJ_ASSERT(waiters.empty());\n}\n\nInputGate::Waiter::Waiter(kj::PromiseFulfiller<Lock>& fulfiller,\n    InputGate& gate,\n    bool isChildWaiter,\n    SpanParent parentSpan)\n    : fulfiller(fulfiller),\n      gate(&gate),\n      isChildWaiter(isChildWaiter),\n      waitSpan(parentSpan.newChild(\"input_gate_lock_wait\"_kjc)),\n      lockSpanParent(kj::mv(parentSpan)) {\n  gate.hooks.inputGateWaiterAdded();\n  if (isChildWaiter) {\n    gate.waitingChildren.add(*this);\n  } else {\n    gate.waiters.add(*this);\n  }\n}\nInputGate::Waiter::~Waiter() noexcept(false) {\n  gate->hooks.inputGateWaiterRemoved();\n  if (link.isLinked()) {\n    if (isChildWaiter) {\n      gate->waitingChildren.remove(*this);\n    } else {\n      gate->waiters.remove(*this);\n    }\n  }\n}\n\nkj::Promise<InputGate::Lock> InputGate::wait(SpanParent parentSpan) {\n  auto methodSpan = parentSpan.newChild(\"input_gate_wait_attempt\"_kjc);\n  KJ_IF_SOME(e, brokenState.tryGet<kj::Exception>()) {\n    return kj::cp(e);\n  } else if (lockCount == 0) {\n    return Lock(*this, methodSpan);\n  } else {\n    return kj::newAdaptedPromise<Lock, Waiter>(*this, false, methodSpan);\n  }\n}\n\nkj::Promise<void> InputGate::onBroken() {\n  KJ_IF_SOME(e, brokenState.tryGet<kj::Exception>()) {\n    return kj::cp(e);\n  } else {\n    return brokenPromise.addBranch();\n  }\n}\n\nInputGate::Lock::Lock(InputGate& gate, SpanParent parentSpan)\n    : gate(&gate),\n      cs(gate.isCriticalSection ? kj::Maybe(kj::addRef(static_cast<CriticalSection&>(gate)))\n                                : kj::none),\n      lockSpan(parentSpan.newChild(\"input_gate_lock_hold\"_kjc)) {\n  InputGate* gateToLock = &gate;\n\n  KJ_IF_SOME(c, cs) {\n    if (c.get()->state == CriticalSection::REPARENTED) {\n      gateToLock = &c.get()->parentAsInputGate();\n    }\n  }\n\n  if (++gateToLock->lockCount == 1) {\n    gateToLock->hooks.inputGateLocked();\n  }\n}\n\nvoid InputGate::releaseLock() {\n  if (isCriticalSection) {\n    auto& self = static_cast<CriticalSection&>(*this);\n    if (self.state == CriticalSection::REPARENTED) {\n      // This lock was for a critical section that has already completed, therefore the lock\n      // should be considered \"reparented\", and we should forward the release to the parent.\n\n      // Ensure any waiters on us have already been reparented.\n      KJ_DASSERT(self.waitingChildren.size() == 0);\n      KJ_DASSERT(self.waiters.size() == 0);\n      KJ_DASSERT(lockCount == 0);\n\n      self.parentAsInputGate().releaseLock();\n      return;\n    }\n  }\n\n  KJ_ASSERT(lockCount-- > 0);\n\n  // Check if any waiters can be released.\n  if (lockCount == 0) {\n    hooks.inputGateReleased();\n    if (!waitingChildren.empty()) {\n      auto& waiter = waitingChildren.front();\n      waitingChildren.remove(waiter);\n      waiter.fulfiller.fulfill(Lock(*this, kj::mv(waiter.lockSpanParent)));\n    } else if (!waiters.empty()) {\n      auto& waiter = waiters.front();\n      waiters.remove(waiter);\n      waiter.fulfiller.fulfill(Lock(*this, kj::mv(waiter.lockSpanParent)));\n    }\n  }\n}\n\nkj::Own<InputGate::CriticalSection> InputGate::Lock::startCriticalSection() {\n  return kj::refcounted<CriticalSection>(*gate);\n}\n\nkj::Maybe<InputGate::CriticalSection&> InputGate::Lock::getCriticalSection() {\n  if (gate->isCriticalSection) {\n    return static_cast<CriticalSection&>(*gate);\n  } else {\n    return kj::none;\n  }\n}\n\nbool InputGate::Lock::isFor(const InputGate& otherGate) const {\n  KJ_ASSERT(!otherGate.isCriticalSection);\n\n  InputGate* ptr = gate;\n  while (ptr->isCriticalSection) {\n    ptr = &static_cast<CriticalSection&>(*ptr).parentAsInputGate();\n  }\n  return ptr == &otherGate;\n}\n\nInputGate::CriticalSection::CriticalSection(InputGate& parent) {\n  isCriticalSection = true;\n  if (parent.isCriticalSection) {\n    this->parent = kj::addRef(static_cast<CriticalSection&>(parent));\n  } else {\n    this->parent = &parent;\n  }\n}\nInputGate::CriticalSection::~CriticalSection() noexcept(false) {\n  switch (state) {\n    case NOT_STARTED:\n      // Oh well.\n      break;\n    case INITIAL_WAIT:\n      // The initial wait() had better have been canceled... but we have no way to tell here.\n      break;\n    case RUNNING:\n      failed(KJ_EXCEPTION(FAILED,\n          \"jsg.Error: A critical section within this Durable Object awaited a Promise that \"\n          \"apparently will never complete. This could happen in particular if a critical section \"\n          \"awaits a task that was initiated outside of the critical section. Since a critical \"\n          \"section blocks all other tasks from completing, this leads to deadlock.\"));\n      break;\n    case REPARENTED:\n      // Common case.\n      break;\n  }\n}\n\nkj::Promise<InputGate::Lock> InputGate::CriticalSection::wait(SpanParent parentSpan) {\n  auto methodSpan = parentSpan.newChild(\"input_gate_critical_section_wait_attempt\"_kjc);\n  for (;;) {\n    switch (state) {\n      case NOT_STARTED: {\n        state = INITIAL_WAIT;\n\n        auto& target = parentAsInputGate();\n        KJ_IF_SOME(e, target.brokenState.tryGet<kj::Exception>()) {\n          // Oops, we're broken.\n          setBroken(e);\n          kj::throwFatalException(kj::cp(e));\n        }\n\n        // Add ourselves to this parent's child waiter list.\n        if (target.lockCount == 0) {\n          state = RUNNING;\n          parentLock = Lock(target, methodSpan);\n          continue;\n        } else {\n          try {\n            auto lock = co_await kj::newAdaptedPromise<Lock, Waiter>(target, true, methodSpan);\n            state = RUNNING;\n            parentLock = kj::mv(lock);\n            continue;\n          } catch (...) {\n            auto exception = kj::getCaughtExceptionAsKj();\n            state = RUNNING;\n            setBroken(exception);\n            kj::throwFatalException(kj::mv(exception));\n          }\n        }\n      }\n      case INITIAL_WAIT:\n        // To avoid the need for a ForkedPromise, we assume wait() is called once initially to\n        // get things started. This is the case in practice because any further tasks would be\n        // started only after some code runs under the initial lock.\n        KJ_FAIL_REQUIRE(\"CriticalSection::wait() should be called once initially\");\n      case RUNNING:\n        // CriticalSection is active, so defer to InputGate implementation.\n        co_return co_await InputGate::wait(methodSpan);\n      case REPARENTED:\n        // Once the CriticalSection has declared itself done, then any straggler tasks it initiated\n        // are adopted by the parent.\n        // WARNING: Don't use parentAsInputGate() here as that'll bypass the override of wait() if\n        //   the parent is a CriticalSection itself.\n        KJ_SWITCH_ONEOF(parent) {\n          KJ_CASE_ONEOF(p, InputGate*) {\n            co_return co_await p->wait(methodSpan);\n          }\n          KJ_CASE_ONEOF(c, kj::Own<CriticalSection>) {\n            co_return co_await c->wait(methodSpan);\n          }\n        }\n        KJ_UNREACHABLE;\n    }\n    KJ_UNREACHABLE;\n  }\n}\n\nInputGate::Lock InputGate::CriticalSection::succeeded() {\n  KJ_REQUIRE(state == RUNNING);\n\n  // Once the CriticalSection has declared itself done, then any straggler tasks it initiated are\n  // adopted by the parent.\n  auto& parentGate = parentAsInputGate();\n  for (auto& waiter: waitingChildren) {\n    waitingChildren.remove(waiter);\n    parentGate.waitingChildren.add(waiter);\n    waiter.gate = &parentGate;\n  }\n  for (auto& waiter: waiters) {\n    waiters.remove(waiter);\n    parentGate.waiters.add(waiter);\n    waiter.gate = &parentGate;\n  }\n  parentGate.lockCount += lockCount;\n  lockCount = 0;\n\n  state = REPARENTED;\n  auto result = KJ_ASSERT_NONNULL(kj::mv(parentLock));\n  parentLock = kj::none;\n  return result;\n}\n\nvoid InputGate::CriticalSection::failed(const kj::Exception& e) {\n  if (brokenState.is<kj::Exception>()) {\n    // Already failed I guess.\n    return;\n  }\n\n  setBroken(e);\n  KJ_SWITCH_ONEOF(parent) {\n    KJ_CASE_ONEOF(p, InputGate*) {\n      p->setBroken(e);\n    }\n    KJ_CASE_ONEOF(c, kj::Own<CriticalSection>) {\n      c->failed(e);\n    }\n  }\n}\n\nvoid InputGate::setBroken(const kj::Exception& e) {\n  for (auto& waiter: waitingChildren) {\n    waiter.fulfiller.reject(kj::cp(e));\n    waitingChildren.remove(waiter);\n  }\n  for (auto& waiter: waiters) {\n    waiter.fulfiller.reject(kj::cp(e));\n    waiters.remove(waiter);\n  }\n  KJ_IF_SOME(f, brokenState.tryGet<kj::Own<kj::PromiseFulfiller<void>>>()) {\n    f.get()->reject(kj::cp(e));\n  }\n  brokenState = kj::cp(e);\n}\n\nInputGate& InputGate::CriticalSection::parentAsInputGate() {\n  CriticalSection* ptr = this;\n  for (;;) {\n    KJ_SWITCH_ONEOF(ptr->parent) {\n      KJ_CASE_ONEOF(p, InputGate*) {\n        return *p;\n      }\n      KJ_CASE_ONEOF(c, kj::Own<CriticalSection>) {\n        if (c.get()->state == REPARENTED) {\n          // Keep looping...\n          ptr = c;\n        } else {\n          return *c.get();\n        }\n      }\n    }\n  }\n}\n\n// =======================================================================================\n\nOutputGate::OutputGate(Hooks& hooks)\n    : hooks(hooks),\n      pastLocksPromise(kj::Promise<void>(kj::READY_NOW).fork()) {}\nOutputGate::~OutputGate() noexcept(false) {}\n\nconst OutputGate::Hooks OutputGate::Hooks::DEFAULT;\n\nkj::Own<kj::PromiseFulfiller<void>> OutputGate::lock() {\n  auto paf = kj::newPromiseAndFulfiller<void>();\n  auto joined = kj::joinPromises(kj::arr(pastLocksPromise.addBranch(), kj::mv(paf.promise)));\n  pastLocksPromise = joined.fork();\n  return kj::mv(paf.fulfiller);\n}\n\nkj::Promise<void> OutputGate::wait(SpanParent parentSpan) {\n  hooks.outputGateWaiterAdded();\n  SpanBuilder waitSpan = parentSpan.newChild(\"output_gate_lock_wait\"_kjc);\n  return pastLocksPromise.addBranch().attach(\n      kj::defer([this]() { hooks.outputGateWaiterRemoved(); }), kj::mv(waitSpan));\n}\n\nkj::Promise<void> OutputGate::onBroken() {\n  KJ_REQUIRE(\n      !brokenState.is<kj::Own<kj::PromiseFulfiller<void>>>(), \"onBroken() can only be called once\");\n\n  KJ_IF_SOME(e, brokenState.tryGet<kj::Exception>()) {\n    return kj::cp(e);\n  } else {\n    auto paf = kj::newPromiseAndFulfiller<void>();\n    brokenState = kj::mv(paf.fulfiller);\n    return kj::mv(paf.promise);\n  }\n}\n\nbool OutputGate::isBroken() {\n  return brokenState.is<kj::Exception>();\n}\n\nnamespace {\n\nvoid END_OUTPUT_LOCK_CANCELATION_STACK_START_WAITER_STACK() {}\n\n}  // namespace\n\nkj::Exception OutputGate::makeUnfulfilledException() {\n  return kj::getDestructionReason(\n      reinterpret_cast<void*>(&END_OUTPUT_LOCK_CANCELATION_STACK_START_WAITER_STACK),\n      kj::Exception::Type::FAILED, __FILE__, __LINE__,\n      \"output lock was canceled before completion\"_kj);\n}\n\nvoid OutputGate::setBroken(const kj::Exception& e) {\n  // We assume the exception is already propagated into `pastLocksPromise`, so all we need to do\n  // is handle onBroken().\n  KJ_IF_SOME(f, brokenState.tryGet<kj::Own<kj::PromiseFulfiller<void>>>()) {\n    f.get()->reject(kj::cp(e));\n  }\n  brokenState = kj::cp(e);\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/io-gate.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// An I/O gate allows someone to \"lock\" a type of I/O so that other concurrent tasks trying to\n// perform that type of I/O are blocked until the lock is released.\n//\n// I/O gates are used in actors to implement consistency guarantees, allowing in-memory state and\n// storage to be synchronized.\n//\n// Each Actor has two main gates:\n// - Input gate: While locked, blocks all incoming I/O events of any type from being delivered to\n//   the actor, other than the specific event or events that hold the lock. This includes\n//   blocking responses to subrequests, timer events, input streams, etc. Used when storage\n//   operations are outstanding, so that awaiting a storage operation does not risk allowing\n//   concurrent events that render the state inconsistent.\n// - Output gate: While locked, blocks all outgoing messages from an actor that would allow the\n//   rest of the world to observe the actor's state. Held while writes that have been confirmed\n//   to the application are still being flushed to disk. If the flush fails, these messages will\n//   never be sent, so that the rest of the world cannot observe a prematurely-confirmed write.\n\n#include <workerd/io/trace.h>\n\n#include <kj/async.h>\n#include <kj/list.h>\n#include <kj/one-of.h>\n\nnamespace workerd {\n\nusing kj::uint;\n\n// An InputGate blocks incoming events from being delivered to an actor while the lock is held.\nclass InputGate {\n\n public:\n  // Hooks that can be used to customize InputGate behavior.\n  //\n  // Technically, everything implemented here could be accomplished by a class that wraps\n  // InputGate, but the part of the code that wants to implement these hooks (Worker::Actor)\n  // is far away from the part of the code that calls into the InputGate (ActorCache), and so\n  // it was more convenient to give Worker::Actor a way to inject behavior into InputGate which\n  // would kick in when ActorCache tried to use it.\n  class Hooks {\n\n   public:\n    // Optionally track metrics. In practice these are implemented by MetricsCollector::Actor, but\n    // we don't want to depend on that class from here.\n    virtual void inputGateLocked() {}\n    virtual void inputGateReleased() {}\n    virtual void inputGateWaiterAdded() {}\n    virtual void inputGateWaiterRemoved() {}\n\n    static const Hooks DEFAULT;\n  };\n\n  // Hooks has no member variables, so const_cast is acceptable.\n  InputGate(Hooks& hooks = const_cast<Hooks&>(Hooks::DEFAULT));\n  ~InputGate() noexcept;\n\n  class CriticalSection;\n\n  // A lock that blocks all new events from being delivered while it exists.\n  class Lock {\n   public:\n    KJ_DISALLOW_COPY(Lock);\n    Lock(Lock&& other): gate(other.gate), cs(kj::mv(other.cs)), lockSpan(kj::mv(other.lockSpan)) {\n      other.gate = nullptr;\n    }\n    ~Lock() noexcept(false) {\n      if (gate != nullptr) {\n        lockSpan.setTag(\"waiters\"_kjc, static_cast<int64_t>(gate->waiters.size()));\n        lockSpan.setTag(\"lock_count\"_kjc, static_cast<int64_t>(gate->lockCount));\n        gate->releaseLock();\n      }\n    }\n\n    // Increments the lock's refcount, returning a duplicate `Lock`. All `Lock`s must be dropped\n    // before the gate is unlocked.\n    Lock addRef(SpanParent parentSpan) {\n      return Lock(*gate, kj::mv(parentSpan));\n    }\n\n    // Start a new critical section from this lock. After `wait()` has been called on the returned\n    // critical section for the first time, no further Locks will be handed out by\n    // InputGate::wait() until the CriticalSection has been dropped.\n    //\n    // CriticalSections can be nested. If this Lock is itself part of a CriticalSection, the new\n    // CriticalSection will be nested within it and the outer CriticalSection's wait() won't\n    // produce a Lock again until the inner CriticalSection is dropped.\n    kj::Own<CriticalSection> startCriticalSection();\n\n    // If this lock was taken in a CriticalSection, return it.\n    kj::Maybe<CriticalSection&> getCriticalSection();\n\n    bool isFor(const InputGate& gate) const;\n\n    inline bool operator==(const Lock& other) const {\n      return gate == other.gate;\n    }\n\n   private:\n    // Becomes null on move.\n    InputGate* gate;\n\n    kj::Maybe<kj::Own<CriticalSection>> cs;\n\n    SpanBuilder lockSpan;\n\n    Lock(InputGate& gate, SpanParent parentSpan);\n    friend class InputGate;\n  };\n\n  // Wait until there are no `Lock`s, then create a new one and return it.\n  //\n  // If parentSpan is provided, child spans will be created to track:\n  // - Time spent waiting for the lock (if waiting is required)\n  // - Time spent holding the lock\n  kj::Promise<Lock> wait(SpanParent parentSpan);\n\n  // Rejects if and when calls to `wait()` become broken due to a failed critical section. The\n  // actor should be shut down in this case. This promise never resolves, only rejects.\n  kj::Promise<void> onBroken();\n\n private:\n  Hooks& hooks;\n\n  // How many instances of `Lock` currently exist? When this reaches zero, we'll release some\n  // waiters.\n  uint lockCount = 0;\n\n  // CriticalSection inherits InputGate for implementation convenience (since much implementation\n  // is shared).\n  bool isCriticalSection = false;\n\n  struct Waiter {\n    Waiter(kj::PromiseFulfiller<Lock>& fulfiller,\n        InputGate& gate,\n        bool isChildWaiter,\n        SpanParent parentSpan);\n    ~Waiter() noexcept(false);\n\n    kj::PromiseFulfiller<Lock>& fulfiller;\n    InputGate* gate;\n    bool isChildWaiter;\n    kj::ListLink<Waiter> link;\n\n    // Span tracking how long we wait for the lock. Ends when Waiter is destroyed.\n    SpanBuilder waitSpan;\n    // Parent span to pass to Lock when it's created.\n    SpanParent lockSpanParent;\n  };\n\n  kj::List<Waiter, &Waiter::link> waiters;\n\n  // Waiters representing CriticalSections that are ready to start. These take priority over other\n  // waiters.\n  kj::List<Waiter, &Waiter::link> waitingChildren;\n\n  // A fulfiller for onBroken(), or an exception if already broken.\n  kj::ForkedPromise<void> brokenPromise;\n  kj::OneOf<kj::Own<kj::PromiseFulfiller<void>>, kj::Exception> brokenState;\n\n  void releaseLock();\n\n  // Called when a critical section fails. All future waiters will throw this exception.\n  void setBroken(const kj::Exception& e);\n\n  InputGate(Hooks& hooks, kj::PromiseFulfillerPair<void> paf);\n};\n\n// A CriticalSection is a procedure that must not be interrupted by anything \"external\".\n// While a CriticalSection is running, all events that were not initiated by the\n// CriticalSection itself will be blocked from being delivered.\n//\n// The difference between a Lock and a CriticalSection is that a critical section may succeed\n// or fail. A failed critical section permanently breaks the input gate. Locks, on the other\n// hand, are simply released when dropped.\n//\n// A CriticalSection itself holds a Lock, which blocks the \"parent scope\" from continuing\n// execution until the critical section is done. Meanwhile, the code running inside the critical\n// section obtains nested Locks. These nested locks control concurrency of the operations\n// initiated within the critical section in the same way that input locks normally do at the\n// top-level scope. E.g., if a critical section initiates a storage read and a fetch() at the\n// same time, the fetch() is prevented from returning until after the storage read has returned.\nclass InputGate::CriticalSection: private InputGate, public kj::Refcounted {\n public:\n  CriticalSection(InputGate& parent);\n  ~CriticalSection() noexcept(false);\n\n  // Wait for a nested lock in order to continue this CriticalSection.\n  //\n  // The first call to wait() begins the CriticalSection. After that wait completes, until the\n  // CriticalSection is done and dropped, no other locks will be allowed on this InputGate, except\n  // locks requested by calling wait() on this CriticalSection -- or one of its children.\n  kj::Promise<Lock> wait(SpanParent parentSpan);\n\n  // Call when the critical section has completed successfully. If this is not called before the\n  // CriticalSection is dropped, then failed() is called implicitly.\n  //\n  // Returns the input lock that was held on the parent critical section. This can be used to\n  // continue execution in the parent before any other input arrives.\n  Lock succeeded();\n\n  // Call to indicate the CriticalSection has failed with the given exception. This immediately\n  // breaks the InputGate.\n  void failed(const kj::Exception& e);\n\n private:\n  enum State {\n    // wait() hasn't been called.\n    NOT_STARTED,\n\n    // wait() has been called once, and that wait hasn't finished yet.\n    INITIAL_WAIT,\n\n    // First lock has been obtained, waiting for success() or failed().\n    RUNNING,\n\n    // success() or failed() has been called.\n    REPARENTED\n  };\n\n  State state = NOT_STARTED;\n\n  // Points to the parent scope, which may be another CriticalSection in the case of nesting.\n  kj::OneOf<InputGate*, kj::Own<CriticalSection>> parent;\n\n  // A lock in the parent scope. `parentLock` becomes non-null after the first lock is obtained,\n  // and becomes null again when succeeded() is called.\n  kj::Maybe<Lock> parentLock;\n\n  friend class InputGate;\n\n  // Return a reference for the parent scope, skipping any reparented CriticalSections\n  InputGate& parentAsInputGate();\n};\n\n// An OutputGate blocks outgoing messages from an Actor until writes which they might depend on\n// are confirmed.\nclass OutputGate {\n public:\n  // Hooks that can be used to customize OutputGate behavior.\n  //\n  // Technically, everything implemented here could be accomplished by a class that wraps\n  // OutputGate, but the part of the code that wants to implement these hooks (Worker::Actor)\n  // is far away from the part of the code that calls into the OutputGate (ActorCache), and so\n  // it was more convenient to give Worker::Actor a way to inject behavior into OutputGate which\n  // would kick in when ActorCache tried to use it.\n  class Hooks {\n   public:\n    // Optionally make a promise which should be exclusiveJoin()ed with the lock promise to\n    // implement a timeout. The returned promise should be something that throws an exception\n    // after some timeout has expired.\n    virtual kj::Promise<void> makeTimeoutPromise() {\n      return kj::NEVER_DONE;\n    }\n\n    // Optionally track metrics. In practice these are implemented by MetricsCollector::Actor, but\n    // we don't want to depend on that class from here.\n\n    virtual void outputGateLocked() {}\n    virtual void outputGateReleased() {}\n    virtual void outputGateWaiterAdded() {}\n    virtual void outputGateWaiterRemoved() {}\n\n    static const Hooks DEFAULT;\n  };\n\n  // Hooks has no member variables, so const_cast is acceptable.\n  OutputGate(Hooks& hooks = const_cast<Hooks&>(Hooks::DEFAULT));\n  ~OutputGate() noexcept(false);\n\n  // Block all future `wait()` calls until `promise` completes. Returns a wrapper around `promise`.\n  // If `promise` rejects, the exception will propagate to all future `wait()`s. If the returned\n  // promise is canceled before completion, all future `wait()`s will also throw.\n  template <typename T>\n  kj::Promise<T> lockWhile(kj::Promise<T> promise, SpanParent parentSpan);\n\n  // Wait until all preceding locks are released. The wait will not be affected by any future\n  // call to `lockWhile()`.\n  kj::Promise<void> wait(SpanParent parentSpan);\n\n  // Rejects if and when calls to `wait()` become broken due to a failed lockWhile(). The actor\n  // should be shut down in this case. This promise never resolves, only rejects.\n  //\n  // This method can only be called once.\n  kj::Promise<void> onBroken();\n\n  bool isBroken();\n\n private:\n  Hooks& hooks;\n\n  kj::ForkedPromise<void> pastLocksPromise;\n\n  // A fulfiller for onBroken(), or an exception if already broken.\n  kj::OneOf<kj::Own<kj::PromiseFulfiller<void>>, kj::Exception> brokenState;\n\n  void setBroken(const kj::Exception& e);\n\n  kj::Own<kj::PromiseFulfiller<void>> lock();\n  static kj::Exception makeUnfulfilledException();\n};\n\n// =======================================================================================\n// inline implementation details\n\ntemplate <typename T>\nkj::Promise<T> OutputGate::lockWhile(kj::Promise<T> promise, SpanParent parentSpan) {\n  auto fulfiller = lock();\n  SpanBuilder lockSpan = parentSpan.newChild(\"output_gate_lock_hold\"_kjc);\n\n  if constexpr (std::is_void_v<T>) {\n    promise = promise.exclusiveJoin(hooks.makeTimeoutPromise());\n  } else {\n    promise = promise.exclusiveJoin(hooks.makeTimeoutPromise().then([]() -> T { KJ_UNREACHABLE; }));\n  }\n\n  hooks.outputGateLocked();\n  auto rejectIfCanceled = kj::defer([this, &fulfiller]() {\n    hooks.outputGateReleased();\n    if (fulfiller->isWaiting()) {\n      auto e = makeUnfulfilledException();\n      setBroken(e);\n      fulfiller->reject(kj::mv(e));\n    }\n  });\n\n  try {\n    if constexpr (std::is_void_v<T>) {\n      co_await promise;\n      fulfiller->fulfill();\n    } else {\n      auto v = co_await promise;\n      fulfiller->fulfill();\n      co_return v;\n    }\n  } catch (kj::Exception& e) {\n    setBroken(e);\n    lockSpan.setTag(\"error\"_kjc, true);\n    fulfiller->reject(kj::cp(e));\n    kj::throwFatalException(kj::cp(e));\n  }\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/io-own.c++",
    "content": "#include \"io-own.h\"\n\n#include \"io-context.h\"\n\n#include <workerd/jsg/util.h>\n\nnamespace workerd {\n\nvoid DeleteQueue::scheduleDeletion(OwnedObject* object) const {\n  if (IoContext::hasCurrent() && IoContext::current().deleteQueue.queue.get() == this) {\n    // Deletion from same thread. No need to enqueue.\n    kj::AllowAsyncDestructorsScope scope;\n    OwnedObjectList::unlink(*object);\n  } else {\n    auto lock = crossThreadDeleteQueue.lockExclusive();\n    KJ_IF_SOME(state, *lock) {\n      state.queue.add(object);\n    }\n  }\n}\n\nvoid DeleteQueue::scheduleAction(jsg::Lock& js, kj::Function<void(jsg::Lock&)>&& action) const {\n  {\n    auto lock = crossThreadDeleteQueue.lockExclusive();\n    KJ_IF_SOME(state, *lock) {\n      state.actions.add(kj::mv(action));\n      KJ_REQUIRE_NONNULL(state.crossThreadFulfiller)->fulfill();\n      return;\n    }\n  }\n\n  // The queue was deleted, likely because the IoContext was destroyed and the\n  // DeleteQueuePtr was invalidated. We are going to emit a warning and drop the\n  // actions on the floor without scheduling them.\n  KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n    // We are creating an error here just so we can include the JavaScript stack\n    // with the warning if it exists. We are not going to throw this error.\n    auto err = v8::Exception::Error(\n        js.str(\"A promise was resolved or rejected from a different request context than \"\n               \"the one it was created in. However, the creating request has already been \"\n               \"completed or canceled. Continuations for that request are unlikely to \"\n               \"run safely and have been canceled. If this behavior breaks your worker, \"\n               \"consider setting the `no_handle_cross_request_promise_resolution` \"\n               \"compatibility flag for your worker.\"_kj))\n                   .As<v8::Object>();\n    // TODO(soon): Add documentation link to this warning.\n    // Changing the name property to \"Warning\" will make the serialize stack start with\n    // \"Warning: \" rather that \"Error: \"\n    jsg::check(err->Set(js.v8Context(), js.str(\"name\"_kj), js.str(\"Warning\"_kj)));\n    auto stack = jsg::check(err->Get(js.v8Context(), js.str(\"stack\"_kj)));\n\n    // Safe to mutate here since we have the exclusive lock on the queue above.\n    ioContext.logWarning(kj::str(stack));\n  }\n}\n\nvoid DeleteQueue::checkFarGet(const DeleteQueue& deleteQueue, const std::type_info& type) {\n  IoContext::current().checkFarGet(deleteQueue, type);\n}\n\nvoid DeleteQueue::checkWeakGet(workerd::WeakRef<IoContext>& weak) {\n  JSG_REQUIRE(weak.isValid(), Error,\n      \"Couldn't complete operation because the execution context has ended.\");\n}\n\nkj::Promise<void> DeleteQueue::resetCrossThreadSignal() const {\n  auto lock = crossThreadDeleteQueue.lockExclusive();\n  KJ_IF_SOME(state, *lock) {\n    KJ_IF_SOME(fulfiller, state.crossThreadFulfiller) {\n      // We should only reset the signal if it has been fulfilled.\n      KJ_ASSERT(!fulfiller->isWaiting());\n    }\n    auto paf = kj::newPromiseAndCrossThreadFulfiller<void>();\n    state.crossThreadFulfiller = kj::mv(paf.fulfiller);\n    return kj::mv(paf.promise);\n  } else {\n    return kj::NEVER_DONE;\n  }\n}\n\nOwnedObjectList::~OwnedObjectList() noexcept(false) {\n  while (head != kj::none) {\n    // We want to have the same order of operations as the recursive destructor here. Without this\n    // optimization, `~SpecificOwnedObject<T>` is invoked first, then `~OwnedObject()` which\n    // destructs the next node which continues the process. The key takeaway is that we destroy each\n    // node's `T` before we move onto the next node. This duplicates that behavior by unlinking\n    // forward through the list, which hopefully should keep our stack size low no matter how many\n    // `OwnedObject` instances we have.\n    unlink(*KJ_ASSERT_NONNULL(head));\n  }\n}\n\nvoid OwnedObjectList::unlink(OwnedObject& object) {\n  KJ_IF_SOME(next, object.next) {\n    next.get()->prev = object.prev;\n  }\n  *object.prev = kj::mv(object.next);\n}\n\nvoid OwnedObjectList::link(kj::Own<OwnedObject> object) {\n  object->next = kj::mv(head);\n  KJ_IF_SOME(next, object->next) {\n    next.get()->prev = &object->next;\n  }\n  object->prev = &head;\n  head = kj::mv(object);\n}\n\nvoid IoCrossContextExecutor::execute(jsg::Lock& js, kj::Function<void(jsg::Lock&)>&& func) {\n  deleteQueue->scheduleAction(js, kj::mv(func));\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/io-own.h",
    "content": "#pragma once\n\n#include <workerd/util/weak-refs.h>\n\n#include <kj/async.h>\n#include <kj/common.h>\n#include <kj/function.h>\n#include <kj/mutex.h>\n#include <kj/refcount.h>\n#include <kj/vector.h>\n\n#include <typeinfo>\n\nnamespace workerd {\nnamespace jsg {\nclass Lock;\n}\n\nclass IoContext;\n\ntemplate <typename T>\nclass IoOwn;\ntemplate <typename T>\nclass IoPtr;\ntemplate <typename T>\nclass ReverseIoOwn;\n\ntemplate <typename T>\nstruct RemoveIoOwn_ {\n  using Type = T;\n  static constexpr bool is = false;\n};\ntemplate <typename T>\nstruct RemoveIoOwn_<IoOwn<T>> {\n  using Type = T;\n  static constexpr bool is = true;\n};\n\ntemplate <typename T>\nconstexpr bool isIoOwn() {\n  return RemoveIoOwn_<T>::is;\n}\ntemplate <typename T>\nusing RemoveIoOwn = RemoveIoOwn_<T>::Type;\n\nstruct OwnedObject {\n  kj::Maybe<kj::Own<OwnedObject>> next;\n  kj::Maybe<kj::Own<OwnedObject>>* prev;\n};\n\ntemplate <typename T>\nstruct SpecificOwnedObject: public OwnedObject {\n  SpecificOwnedObject(kj::Own<T> ptr): ptr(kj::mv(ptr)) {}\n  kj::Own<T> ptr;\n};\n\nclass OwnedObjectList {\n public:\n  OwnedObjectList() = default;\n  KJ_DISALLOW_COPY_AND_MOVE(OwnedObjectList);\n  ~OwnedObjectList() noexcept(false);\n\n  void link(kj::Own<OwnedObject> object);\n  static void unlink(OwnedObject& object);\n\n private:\n  kj::Maybe<kj::Own<OwnedObject>> head;\n};\n\n// Object which receives possibly-cross-thread deletions of owned objects.\nclass DeleteQueue: public kj::AtomicRefcounted {\n public:\n  DeleteQueue(): crossThreadDeleteQueue(State{kj::Vector<OwnedObject*>()}) {}\n\n  void scheduleDeletion(OwnedObject* object) const;\n  void scheduleAction(jsg::Lock& js, kj::Function<void(jsg::Lock&)>&& action) const;\n\n  struct State {\n    kj::Vector<OwnedObject*> queue;\n    // Actions that some other IoContext has requested be executed in this IoContext. When\n    // adding an action to this list, crossThreadFulfiller should be fulfilled, signaling the\n    // target IoContext to wake up and run actions. After draining the actions queue, the target\n    // IoContext should replace crossThreadFulfiller with a new one which will wake it up again.\n    //\n    // In particular, these actions are used to implement cross-context promise resolution.\n    //\n    // Keep in mind the IoContext could be destroyed before the cross-thread signal runs, in\n    // which case the actions will never run.\n    kj::Vector<kj::Function<void(jsg::Lock&)>> actions;\n    kj::Maybe<kj::Own<kj::CrossThreadPromiseFulfiller<void>>> crossThreadFulfiller;\n  };\n\n  // Pointers from IoOwns that were dropped in other threads, and therefore should be deleted\n  // whenever the IoContext gets around to it. The maybe is changed to kj::none when the\n  // IoContext goes away, at which point all OwnedObjects have already been deleted so\n  // cross-thread deletions can just be ignored.\n  kj::MutexGuarded<kj::Maybe<State>> crossThreadDeleteQueue;\n\n  // Implements the corresponding methods of IoContext and ActorContext.\n  template <typename T>\n  IoOwn<T> addObject(kj::Own<T> obj, OwnedObjectList& ownedObjects) const;\n\n  template <typename T>\n  ReverseIoOwn<T> addObjectReverse(kj::Own<workerd::WeakRef<IoContext>> weakRef,\n      kj::Own<T> obj,\n      OwnedObjectList& ownedObjects) const;\n\n  static void checkFarGet(const DeleteQueue& deleteQueue, const std::type_info& type);\n  static void checkWeakGet(workerd::WeakRef<IoContext>& weak);\n\n private:\n  template <typename T>\n  SpecificOwnedObject<T>* addObjectImpl(kj::Own<T> obj, OwnedObjectList& ownedObjects) const;\n\n  kj::Promise<void> resetCrossThreadSignal() const;\n\n  friend class IoContext;\n};\n\n// Object which can push actions into a specific DeleteQueue then signal its\n// owning IoContext to wake up to process the queue. This is a bit of a hack of\n// the DeleteQueue concept that allows us to use the same queue for more than\n// just deletions.\nclass IoCrossContextExecutor {\n public:\n  IoCrossContextExecutor(kj::Arc<DeleteQueue> deleteQueue): deleteQueue(kj::mv(deleteQueue)) {}\n\n  // Tries to execute the specified action to the owning IoContext.\n  // The target IoContext will be signaled to run the action as soon as it is able.\n  void execute(jsg::Lock& js, kj::Function<void(jsg::Lock&)>&& action);\n\n private:\n  friend class IoContext;\n  friend class DeleteQueue;\n\n  kj::Arc<DeleteQueue> deleteQueue;\n};\n\ntemplate <typename T>\ninline SpecificOwnedObject<T>* DeleteQueue::addObjectImpl(\n    kj::Own<T> obj, OwnedObjectList& ownedObjects) const {\n  // HACK: We need an Own<OwnedObject>, but we actually need to allocate it as the subclass\n  //   SpecificOwnedObject<T>. OwnedObject is not polymorphic, which means kj::Own will refuse\n  //   to upcast kj::Own<SpecificOwnedObject<T>> to kj::Own<OwnedObject> since it can't guarantee\n  //   the disposers are compatible. However, since we're only using single inheritance here, the\n  //   disposers *are* compatible (the numeric value of pointers to SpecificOwnedObject<T> and\n  //   its parent OwnedObject are equal). So, instead of forcing OwnedObject to be polymorphic\n  //   (which would have forced a bunch of useless vtables and vtable pointers)... I'm manually\n  //   constructing the kj::Own<> using a disposer that I know is compatible.\n  // TODO(cleanup): Can KJ be made to support this use case?\n  kj::Own<OwnedObject> ownedObject(new SpecificOwnedObject<T>(kj::mv(obj)),\n      kj::_::HeapDisposer<SpecificOwnedObject<T>>::instance);\n\n  auto result = static_cast<SpecificOwnedObject<T>*>(ownedObject.get());\n  ownedObjects.link(kj::mv(ownedObject));\n  return result;\n}\n\ntemplate <typename T>\ninline IoOwn<T> DeleteQueue::addObject(kj::Own<T> obj, OwnedObjectList& ownedObjects) const {\n  return IoOwn<T>(addRefToThis(), addObjectImpl(kj::mv(obj), ownedObjects));\n}\n\ntemplate <typename T>\ninline ReverseIoOwn<T> DeleteQueue::addObjectReverse(kj::Own<workerd::WeakRef<IoContext>> weakRef,\n    kj::Own<T> obj,\n    OwnedObjectList& ownedObjects) const {\n  return ReverseIoOwn<T>(kj::mv(weakRef), addObjectImpl(kj::mv(obj), ownedObjects));\n}\n\n// When the IoContext is destroyed, we need to null out the DeleteQueue. Complicating\n// matters a bit, we need to cancel all tasks (destroy the TaskSet) before this happens, so\n// we can't just do it in IoContext's destructor. As a hack, we customize our pointer\n// to the delete queue to get the tear-down order right.\nclass DeleteQueuePtr {\n public:\n  DeleteQueuePtr(kj::Arc<DeleteQueue> queue): queue(kj::mv(queue)) {}\n  KJ_DISALLOW_COPY_AND_MOVE(DeleteQueuePtr);\n  ~DeleteQueuePtr() noexcept(false) {\n    auto ptr = queue.get();\n    if (ptr != nullptr) {\n      auto lock = ptr->crossThreadDeleteQueue.lockExclusive();\n      KJ_IF_SOME(state, *lock) {\n        // The delete queue state may include a kj::CrossThreadPromiseFulfiller that\n        // needs to be destroyed. To do so, we need to allow async destructors here.\n        // We only want to destroy the crossThreadFulfiller in this scope tho, not\n        // everything that may be in the queue.\n        kj::AllowAsyncDestructorsScope scope;\n        state.crossThreadFulfiller = kj::none;\n      }\n      *lock = kj::none;\n    }\n  }\n  kj::Arc<DeleteQueue> queue;\n};\n\n// Owned pointer held by a V8 heap object, pointing to a KJ event loop object. Cannot be\n// dereferenced unless the isolate is executing on the appropriate event loop thread.\ntemplate <typename T>\nclass IoOwn {\n\n public:\n  IoOwn(IoOwn&& other);\n  IoOwn(decltype(nullptr)): item(nullptr) {}\n  ~IoOwn() noexcept(false);\n  KJ_DISALLOW_COPY(IoOwn);\n\n  T* operator->();\n  T& operator*() {\n    return *operator->();\n  }\n  operator kj::Own<T>() &&;\n  IoOwn& operator=(IoOwn&& other);\n  IoOwn& operator=(decltype(nullptr));\n\n  // Releases this object from the IoOwn, but instead of deleting it, attaches it to the\n  // IoContext (or ActorContext) such that it won't be destroyed until that context is torn\n  // down.\n  //\n  // This may need to be used in cases where an application could directly observe the destruction\n  // of this object. If that's the case, then the object cannot be destroyed during GC, as this\n  // would let the application observe GC, which might enable side channels. So, the destructor\n  // of the owning object must manually call `deferGcToContext()` to pass all such objects away\n  // to their respective contexts.\n  //\n  // Since this is expected to be called during GC, it is safe to call from a thread other than\n  // the one that owns the IoContext.\n  void deferGcToContext() &&;\n\n private:\n  friend class IoContext;\n  friend class DeleteQueue;\n\n  kj::Arc<DeleteQueue> deleteQueue;\n  SpecificOwnedObject<T>* item;\n\n  IoOwn(kj::Arc<DeleteQueue> deleteQueue, SpecificOwnedObject<T>* item)\n      : deleteQueue(kj::mv(deleteQueue)),\n        item(item) {}\n};\n\n// Reference held by a V8 heap object, pointing to a KJ event loop object. Cannot be\n// dereferenced unless the isolate is executing on the appropriate event loop thread.\ntemplate <typename T>\nclass IoPtr {\n public:\n  IoPtr(const IoPtr& other): deleteQueue(other.deleteQueue.addRef()), ptr(other.ptr) {}\n  IoPtr(IoPtr&& other) = default;\n\n  T* operator->();\n  T& operator*() {\n    return *operator->();\n  }\n  IoPtr& operator=(decltype(nullptr));\n\n private:\n  friend class IoContext;\n  friend class DeleteQueue;\n\n  kj::Arc<DeleteQueue> deleteQueue;\n  T* ptr;\n\n  IoPtr(kj::Arc<DeleteQueue> deleteQueue, T* ptr): deleteQueue(kj::mv(deleteQueue)), ptr(ptr) {}\n};\n\n// Owned pointer held by a KJ I/O object living in the same thread as an IoContext. The underlying\n// object is destroyed when the ReverseIoOwn is dropped OR when the IoContext is destroyed,\n// whichever comes first. Accessing the ReverseIoOwn after the IoContext is destroyed will throw.\n//\n// Use this when you have a KJ I/O object that could outlive an IoContext, but wants to hold onto\n// some information that itself should not outlive the IoContext. In particular, if a KJ I/O object\n// wants to hold JS handles (`jsg::JsRef`), this is normally safe as long as the handles do not\n// outlive the isolate they point into. But if the holder could outlive the IoContext, then it\n// could also outlive the isolate. In that case, the handles should be wrapped in an object held\n// using `ReverseIoOwn`.\ntemplate <typename T>\nclass ReverseIoOwn {\n public:\n  ReverseIoOwn(ReverseIoOwn&& other);\n  ReverseIoOwn(decltype(nullptr)): item(nullptr) {}\n  ~ReverseIoOwn() noexcept(false);\n  KJ_DISALLOW_COPY(ReverseIoOwn);\n\n  T* operator->();\n  T& operator*() {\n    return *operator->();\n  }\n  operator kj::Own<T>() &&;\n  ReverseIoOwn& operator=(ReverseIoOwn&& other);\n  ReverseIoOwn& operator=(decltype(nullptr));\n\n  // Try to get the underlying object if safe to dereference.\n  // Returns kj::none if the IoContext has been destroyed or if this is null.\n  // This is a safe alternative to operator->() that won't throw or crash.\n  kj::Maybe<T&> tryGet() {\n    if (item != nullptr && weakRef->isValid()) {\n      return *item->ptr.get();\n    }\n    return kj::none;\n  }\n\n private:\n  friend class IoContext;\n  friend class DeleteQueue;\n\n  kj::Own<workerd::WeakRef<IoContext>> weakRef;\n  SpecificOwnedObject<T>* item;\n\n  ReverseIoOwn(kj::Own<workerd::WeakRef<IoContext>> weakRef, SpecificOwnedObject<T>* item)\n      : weakRef(kj::mv(weakRef)),\n        item(item) {}\n};\n\ntemplate <typename T>\nIoOwn<T>::IoOwn(IoOwn&& other): deleteQueue(kj::mv(other.deleteQueue)),\n                                item(other.item) {\n  other.item = nullptr;\n}\n\ntemplate <typename T>\nIoOwn<T>::~IoOwn() noexcept(false) {\n  if (item != nullptr) {\n    deleteQueue->scheduleDeletion(item);\n  }\n}\n\ntemplate <typename T>\nIoOwn<T>& IoOwn<T>::operator=(IoOwn<T>&& other) {\n  if (item != nullptr) {\n    deleteQueue->scheduleDeletion(item);\n  }\n  deleteQueue = kj::mv(other.deleteQueue);\n  item = other.item;\n  other.item = nullptr;\n  return *this;\n}\n\ntemplate <typename T>\nIoOwn<T>& IoOwn<T>::operator=(decltype(nullptr)) {\n  if (item != nullptr) {\n    deleteQueue->scheduleDeletion(item);\n  }\n  deleteQueue = nullptr;\n  item = nullptr;\n  return *this;\n}\n\ntemplate <typename T>\nvoid IoOwn<T>::deferGcToContext() && {\n  // Turns out, if we simply *don't* enqueue the item for deletion, we get the behavior we want.\n  // So we can just null out the pointers here...\n  item = nullptr;\n  deleteQueue = nullptr;\n}\n\ntemplate <typename T>\nIoPtr<T>& IoPtr<T>::operator=(decltype(nullptr)) {\n  deleteQueue = nullptr;\n  ptr = nullptr;\n  return *this;\n}\n\ntemplate <typename T>\ninline T* IoOwn<T>::operator->() {\n  DeleteQueue::checkFarGet(*deleteQueue.get(), typeid(T));\n  return item->ptr;\n}\n\ntemplate <typename T>\ninline IoOwn<T>::operator kj::Own<T>() && {\n  DeleteQueue::checkFarGet(*deleteQueue.get(), typeid(T));\n  auto result = kj::mv(item->ptr);\n  OwnedObjectList::unlink(*item);\n  item = nullptr;\n  deleteQueue = nullptr;  // not needed anymore, might as well drop the refcount\n  return result;\n}\n\ntemplate <typename T>\ninline T* IoPtr<T>::operator->() {\n  DeleteQueue::checkFarGet(*deleteQueue.get(), typeid(T));\n  return ptr;\n}\n\ntemplate <typename T>\nReverseIoOwn<T>::ReverseIoOwn(ReverseIoOwn&& other)\n    : weakRef(kj::mv(other.weakRef)),\n      item(other.item) {\n  other.item = nullptr;\n}\n\ntemplate <typename T>\nReverseIoOwn<T>::~ReverseIoOwn() noexcept(false) {\n  if (item != nullptr && weakRef->isValid()) {\n    OwnedObjectList::unlink(*item);\n  }\n}\n\ntemplate <typename T>\nReverseIoOwn<T>& ReverseIoOwn<T>::operator=(ReverseIoOwn<T>&& other) {\n  if (item != nullptr) {\n    OwnedObjectList::unlink(*item);\n  }\n  weakRef = kj::mv(other.weakRef);\n  item = other.item;\n  other.item = nullptr;\n  return *this;\n}\n\ntemplate <typename T>\nReverseIoOwn<T>& ReverseIoOwn<T>::operator=(decltype(nullptr)) {\n  if (item != nullptr) {\n    OwnedObjectList::unlink(*item);\n  }\n  weakRef = nullptr;\n  item = nullptr;\n  return *this;\n}\n\ntemplate <typename T>\ninline T* ReverseIoOwn<T>::operator->() {\n  DeleteQueue::checkWeakGet(*weakRef);\n  return item->ptr;\n}\n\ntemplate <typename T>\ninline ReverseIoOwn<T>::operator kj::Own<T>() && {\n  DeleteQueue::checkWeakGet(*weakRef);\n  auto result = kj::mv(item->ptr);\n  OwnedObjectList::unlink(*item);\n  item = nullptr;\n  weakRef = nullptr;  // not needed anymore, might as well drop the refcount\n  return result;\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/io-thread-context.c++",
    "content": "#include \"io-thread-context.h\"\n\nnamespace workerd {\n\nThreadContext::HeaderIdBundle::HeaderIdBundle(kj::HttpHeaderTable::Builder& builder)\n    : table(builder.getFutureTable()),\n      contentEncoding(builder.add(\"Content-Encoding\")),\n      cfCacheStatus(builder.add(\"CF-Cache-Status\")),\n      cacheControl(builder.add(\"Cache-Control\")),\n      pragma(builder.add(\"Pragma\")),\n      cfCacheNamespace(builder.add(\"CF-Cache-Namespace\")),\n      range(builder.add(\"Range\")),\n      ifModifiedSince(builder.add(\"If-Modified-Since\")),\n      ifNoneMatch(builder.add(\"If-None-Match\")),\n      cfKvMetadata(builder.add(\"CF-KV-Metadata\")),\n      cfR2ErrorHeader(builder.add(\"CF-R2-Error\")),\n      cfBlobMetadataSize(builder.add(\"CF-R2-Metadata-Size\")),\n      cfBlobRequest(builder.add(\"CF-R2-Request\")),\n      authorization(builder.add(\"Authorization\")),\n      cfQueuesErrorCode(builder.add(\"CF-Queues-Error-Code\")),\n      cfQueuesErrorCause(builder.add(\"CF-Queues-Error-Cause\")),\n      secWebSocketProtocol(builder.add(\"Sec-WebSocket-Protocol\")),\n      userAgent(builder.add(\"User-Agent\")),\n      contentType(builder.add(\"Content-Type\")),\n      contentLength(builder.add(\"Content-Length\")),\n      accept(builder.add(\"Accept\")),\n      acceptEncoding(builder.add(\"Accept-Encoding\")),\n      cfRay(builder.add(\"CF-Ray\")),\n      origin(builder.add(\"Origin\")) {}\n\nThreadContext::ThreadContext(kj::Timer& timer,\n    kj::EntropySource& entropySource,\n    HeaderIdBundle headerIds,\n    capnp::HttpOverCapnpFactory& httpOverCapnpFactory,\n    capnp::ByteStreamFactory& byteStreamFactory,\n    bool fiddle)\n    : timer(timer),\n      entropySource(entropySource),\n      headerIds(headerIds),\n      httpOverCapnpFactory(httpOverCapnpFactory),\n      byteStreamFactory(byteStreamFactory),\n      fiddle(fiddle) {}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/io-thread-context.h",
    "content": "#pragma once\n\n#include <capnp/compat/http-over-capnp.h>\n#include <kj/compat/http.h>\n\nnamespace workerd {\n\n// Thread-level stuff needed to construct a IoContext. One of these is created for each\n// request-handling thread.\nclass ThreadContext {\n public:\n  struct HeaderIdBundle {\n    HeaderIdBundle(kj::HttpHeaderTable::Builder& builder);\n\n    const kj::HttpHeaderTable& table;\n\n    const kj::HttpHeaderId contentEncoding;\n    const kj::HttpHeaderId cfCacheStatus;  // used by cache API implementation\n    const kj::HttpHeaderId cacheControl;\n    const kj::HttpHeaderId pragma;\n    const kj::HttpHeaderId cfCacheNamespace;  // used by Cache binding implementation\n    const kj::HttpHeaderId range;\n    const kj::HttpHeaderId ifModifiedSince;\n    const kj::HttpHeaderId ifNoneMatch;\n    const kj::HttpHeaderId cfKvMetadata;        // used by KV binding implementation\n    const kj::HttpHeaderId cfR2ErrorHeader;     // used by R2 binding implementation\n    const kj::HttpHeaderId cfBlobMetadataSize;  // used by R2 binding implementation\n    const kj::HttpHeaderId cfBlobRequest;       // used by R2 binding implementation\n    const kj::HttpHeaderId authorization;       // used by R2 binding implementation\n    const kj::HttpHeaderId cfQueuesErrorCode;   // used by Queue binding implementation\n    const kj::HttpHeaderId cfQueuesErrorCause;  // used by Queue binding implementation\n    const kj::HttpHeaderId secWebSocketProtocol;\n    const kj::HttpHeaderId userAgent;       // used in tracing instrumentation\n    const kj::HttpHeaderId contentType;     // used in tracing instrumentation\n    const kj::HttpHeaderId contentLength;   // used in tracing instrumentation\n    const kj::HttpHeaderId accept;          // used in tracing instrumentation\n    const kj::HttpHeaderId acceptEncoding;  // used in tracing instrumentation\n    const kj::HttpHeaderId cfRay;           // used in tracing instrumentation\n    const kj::HttpHeaderId origin;\n  };\n\n  ThreadContext(kj::Timer& timer,\n      kj::EntropySource& entropySource,\n      HeaderIdBundle headerIds,\n      capnp::HttpOverCapnpFactory& httpOverCapnpFactory,\n      capnp::ByteStreamFactory& byteStreamFactory,\n      bool isFiddle);\n\n  // This should only be used to construct TimerChannel. Everything else should use TimerChannel.\n  inline kj::Timer& getUnsafeTimer() const {\n    return timer;\n  }\n  inline kj::EntropySource& getEntropySource() const {\n    return entropySource;\n  }\n  inline const kj::HttpHeaderTable& getHeaderTable() const {\n    return headerIds.table;\n  }\n  inline const HeaderIdBundle& getHeaderIds() const {\n    return headerIds;\n  }\n  inline capnp::HttpOverCapnpFactory& getHttpOverCapnpFactory() const {\n    return httpOverCapnpFactory;\n  }\n  inline capnp::ByteStreamFactory& getByteStreamFactory() const {\n    return byteStreamFactory;\n  }\n  inline bool isFiddle() const {\n    return fiddle;\n  }\n\n private:\n  // NOTE: This timer only updates when entering the event loop!\n  kj::Timer& timer;\n  kj::EntropySource& entropySource;\n  HeaderIdBundle headerIds;\n  capnp::HttpOverCapnpFactory& httpOverCapnpFactory;\n  capnp::ByteStreamFactory& byteStreamFactory;\n  bool fiddle;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/io-timers.c++",
    "content": "#include \"io-timers.h\"\n\nnamespace workerd {\n\nTimeoutId TimeoutId::Generator::getNext() {\n  // The maximum integer value that we can represent as a double to convey to jsg.\n  constexpr ValueType MAX_SAFE_INTEGER = (1ull << 53) - 1;\n\n  auto id = nextId++;\n  if (nextId > MAX_SAFE_INTEGER) {\n    KJ_LOG(WARNING, \"Unable to set timeout because there are no more unique ids\");\n    JSG_FAIL_REQUIRE(Error,\n        \"Unable to set timeout because there are no more unique ids \"\n        \"less than Number.MAX_SAFE_INTEGER.\");\n  }\n  return TimeoutId(id);\n}\n\nTimeoutManager::TimeoutParameters::TimeoutParameters(\n    bool repeat, int64_t msDelay, jsg::Function<void()> function)\n    : repeat(repeat),\n      msDelay(msDelay),\n      function(kj::mv(function)) {\n  // Don't allow pushing Date.now() backwards! This should be checked before TimeoutParameters\n  // is created but just in case...\n  if (msDelay < 0) {\n    this->msDelay = 0;\n  }\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/io-timers.h",
    "content": "#pragma once\n\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd {\n\nclass IoContext;\n\n// A TimeoutId is a positive non-zero integer value that explicitly identifies a timeout set on an\n// isolate.\n//\n// Lastly, timeout ids can experience integer roll over. It is expected that the\n// setTimeout/clearTimeout implementation will enforce id uniqueness for *active* timeouts. This\n// does not mean that an external user cannot have cached a timeout id for a long expired timeout.\n// However, clearTimeout implementations are expected to only have access to timeouts set via that\n// same implementation.\nclass TimeoutId {\n public:\n  // Use a double so that we can exceed the maximum value for uint32_t.\n  using NumberType = double;\n\n  // Store as a uint64_t so that we treat this id as an integer.\n  using ValueType = uint64_t;\n\n  class Generator;\n\n  // Convert an externally provided double into a TimeoutId. If you are making a new TimeoutId,\n  // use a Generator instead.\n  inline static TimeoutId fromNumber(NumberType id) {\n    return TimeoutId(static_cast<ValueType>(id));\n  }\n\n  // Convert a TimeoutId to an integer-convertable double for external consumption.\n  // Note that this is expected to be less than or equal to JavaScript Number.MAX_SAFE_INTEGER\n  // (2^53 - 1). To reach greater than that value in normal operation, we'd need a Generator to\n  // live far far longer than our normal release/restart cycle, be initialized with a large\n  // starting value, or experience active concurrency _somehow_.\n  inline NumberType toNumber() const {\n    return value;\n  }\n\n  inline bool operator<(TimeoutId id) const {\n    return value < id.value;\n  }\n\n private:\n  constexpr explicit TimeoutId(ValueType value): value(value) {}\n\n  ValueType value;\n};\n\nclass TimeoutId::Generator {\n public:\n  Generator() = default;\n  KJ_DISALLOW_COPY_AND_MOVE(Generator);\n\n  // Get the next TimeoutId for this generator. This function will never return a TimeoutId <= 0.\n  TimeoutId getNext();\n\n private:\n  // We always skip 0 per the spec:\n  // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers.\n  TimeoutId::ValueType nextId = 1;\n};\n\nclass TimeoutManager {\n public:\n  // Upper bound on the number of timeouts a user can *ever* have active.\n  constexpr static auto MAX_TIMEOUTS = 10'000;\n\n  struct TimeoutParameters {\n    TimeoutParameters(bool repeat, int64_t msDelay, jsg::Function<void()> function);\n\n    bool repeat;\n    int64_t msDelay;\n\n    // This is a maybe to allow cancel to clear it and free the reference\n    // when it is no longer needed.\n    kj::Maybe<jsg::Function<void()>> function;\n  };\n\n  virtual TimeoutId setTimeout(\n      IoContext& context, TimeoutId::Generator& generator, TimeoutParameters params) = 0;\n  virtual void clearTimeout(IoContext& context, TimeoutId id) = 0;\n  virtual size_t getTimeoutCount() const = 0;\n  virtual kj::Maybe<kj::Date> getNextTimeout() const = 0;\n  virtual void cancelAll() = 0;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/io-util.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"io-util.h\"\n\n#include \"io-context.h\"\n\n#include <kj/time.h>\n\nnamespace workerd {\n\ndouble dateNow() {\n  KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n    return (ioContext.now() - kj::UNIX_EPOCH) / kj::MILLISECONDS;\n  }\n\n  return 0.0;\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/io-util.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\nnamespace workerd {\n\n// Returns exactly what Date.now() would return.\ndouble dateNow();\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/limit-enforcer.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/io/outcome.capnp.h>\n#include <workerd/io/tracked-wasm-instance.h>\n\n#include <v8-isolate.h>\n\n#include <kj/async.h>   // For Promise\n#include <kj/debug.h>   // For KJ_REQUIRE\n#include <kj/memory.h>  // for Own\n#include <kj/one-of.h>  // for OneOf\n#include <kj/time.h>    // for Duration\n\nnamespace workerd {\nclass IsolateObserver;\nclass RequestObserver;\n\nstruct ActorCacheSharedLruOptions;\nclass IoContext;\n\nnamespace jsg {\nclass Lock;\n}  // namespace jsg\n\nstatic constexpr size_t DEFAULT_MAX_PBKDF2_ITERATIONS = 100'000;\n\n// Interface for an object that enforces resource limits on an Isolate level.\n//\n// See also LimitEnforcer, which enforces on a per-request level.\nclass IsolateLimitEnforcer: public kj::Refcounted {\n public:\n  // Get CreateParams to pass when constructing a new isolate.\n  virtual v8::Isolate::CreateParams getCreateParams() = 0;\n\n  // Further customize the isolate immediately after startup.\n  virtual void customizeIsolate(v8::Isolate* isolate) = 0;\n\n  virtual ActorCacheSharedLruOptions getActorCacheLruOptions() = 0;\n\n  // Like LimitEnforcer::enterJs(), but used to enforce limits on script startup.\n  //\n  // When the returned scope object is dropped, if a limit was exceeded, then `error` will be\n  // filled in to indicate what happened, otherwise it is left null.\n  virtual kj::Own<void> enterStartupJs(\n      jsg::Lock& lock, kj::OneOf<kj::Exception, kj::Duration>& limitErrorOrTime) const = 0;\n\n  // used to enforce limits on Python script startup.\n  virtual kj::Own<void> enterStartupPython(\n      jsg::Lock& lock, kj::OneOf<kj::Exception, kj::Duration>& limitErrorOrTime) const = 0;\n\n  // Like enterStartupJs(), but used when compiling a dynamically-imported module.\n  virtual kj::Own<void> enterDynamicImportJs(\n      jsg::Lock& lock, kj::OneOf<kj::Exception, kj::Duration>& limitErrorOrTime) const = 0;\n\n  // Like enterStartupJs(), but used to enforce tight limits in cases where we just intend\n  // to log an error to the inspector or the like.\n  virtual kj::Own<void> enterLoggingJs(\n      jsg::Lock& lock, kj::OneOf<kj::Exception, kj::Duration>& limitErrorOrTime) const = 0;\n\n  // Like enterStartupJs(), but used when receiving commands via the inspector protocol.\n  virtual kj::Own<void> enterInspectorJs(\n      jsg::Lock& lock, kj::OneOf<kj::Exception, kj::Duration>& limitErrorOrTime) const = 0;\n\n  // Notifies the enforcer that a request has been completed. The enforcer is more lenient about\n  // limits if several requests have been completed, vs. if limits are broken right off the bat.\n  virtual void completedRequest(kj::StringPtr id) const = 0;\n\n  // Called whenever exiting JavaScript execution (i.e. releasing the isolate lock). The enforcer\n  // may perform some resource usage checks at this time.\n  //\n  // Returns true if the isolate has exceeded limits and become condemned.\n  virtual bool exitJs(jsg::Lock& lock) const = 0;\n\n  // Report resource usage metrics to the given isolate metrics object.\n  virtual void reportMetrics(IsolateObserver& isolateMetrics) const = 0;\n\n  // Called when performing a crypto key derivation function (like pbkdf2) to determine if\n  // if the requested number of iterations is acceptable. If kj::none is returned, the\n  // number of iterations requested is acceptable. If a number is returned, the requested\n  // iterations is unacceptable and the return value specifies the maximum.\n  virtual kj::Maybe<size_t> checkPbkdfIterations(jsg::Lock& js, size_t iterations) const {\n    // By default, historically we've limited this to 100,000 iterations max. We'll set\n    // that as the default for now. To set a default of no-limit, this would be changed\n    // to return kj::none. Note, this current default limit is *WAY* below the recommended\n    // minimum iterations for pbkdf2.\n    // TODO(maybe): We might consider emitting a warning if the number of iterations is\n    // too low to be safe.\n    if (iterations > DEFAULT_MAX_PBKDF2_ITERATIONS) return DEFAULT_MAX_PBKDF2_ITERATIONS;\n    return kj::none;\n  }\n\n  // Called when a Blob is being created to determine the maximum allowed size of the Blob.\n  virtual size_t getBlobSizeLimit() const {\n    return 128 * 1024 * 1024;  // 128 MB\n  }\n\n  virtual bool hasExcessivelyExceededHeapLimit() const = 0;\n\n  // Returns the TrackedWasmInstanceList for this isolate. Subclasses own the list and provide\n  // it here. The returned object provides lock-guarded mutation methods and a read-only accessor\n  // for signal-handler use.\n  virtual const TrackedWasmInstanceList& getTrackedWasmInstances() const = 0;\n\n  // Inserts a custom mark event named `name` into this isolate's perf event data stream. At\n  // present, this is only implemented internally. Call this function from various APIs to be able\n  // to correlate perf event data with usage of those APIs.\n  //\n  // TODO(cleanup): This isn't strictly related to limit enforcement, so it's a bit odd here. It's\n  //  observability-related. However, our internal perf event observability is fairly tightly\n  //  coupled with our CPU time limiting system, so adding this function here is a path of least\n  //  resistance.\n  virtual void markPerfEvent(kj::LiteralStringConst name) const {};\n};\n\n// Abstract interface that enforces resource limits on a IoContext.\nclass LimitEnforcer {\n public:\n  // Called just after taking the isolate lock, before executing JavaScript code, to enforce\n  // limits on that code execution, particularly the CPU limit. The returned `Own<void>` should\n  // be dropped when JavaScript is done, before unlocking the isolate.\n  virtual kj::Own<void> enterJs(jsg::Lock& lock, IoContext& context) = 0;\n\n  // Called on each new event delivered that should cause an actor's resource limits to be\n  // \"topped up\". This method does nothing if the IoContext is not an actor. Note that this must\n  // not be called while in a JS scope, i.e. when `enterJs()` has been called and the returned\n  // object not yet dropped.\n  virtual void topUpActor() = 0;\n  // TODO(cleanup): This is called in WebSocket and JsRpcTargetBase when receiving an event, but\n  // should we do something more generic like use a membrane to detect any incoming RPC call?\n\n  // Called before starting a new subrequest. Throws a JSG exception if the limit has been\n  // reached.\n  //\n  // `isInHouse` is true for types of subrequests which we need to be \"in house\" (i.e. to another\n  // Cloudflare service, like Workers KV) and thus should not be subject to the same limits as\n  // external subrequests.\n  virtual void newSubrequest(bool isInHouse) = 0;\n\n  enum class KvOpType { GET, GET_WITH, PUT, LIST, DELETE, GET_BULK };\n  // Called before starting a KV operation. Throws a JSG exception if the operation should be\n  // blocked due to exceeding limits, such as the free tier daily operation limit.\n  virtual void newKvRequest(KvOpType op) = 0;\n\n  // Called before starting an attempt to write to the Analytics Engine. Throws\n  // a JSG exception if the operation should be blocked due to exceeding limits.\n  virtual void newAnalyticsEngineRequest() = 0;\n\n  // Applies a time limit to draining a request (i.e. waiting for `waitUntil()`s after the\n  // response has been sent). Returns a promise that will resolve (without error) when the time\n  // limit has expired. This should be joined with the drain task.\n  //\n  // This should not be called for actors, which are evicted when the supervisor decides to\n  // evict them, not on a timeout basis.\n  virtual kj::Promise<void> limitDrain() = 0;\n\n  // Like limitDrain() but applies a time limit to scheduled event processing.\n  virtual kj::Promise<void> limitScheduled() = 0;\n\n  // Like limitDrain() and limitScheduled() but applies a time limit to alarm event processing.\n  virtual kj::Duration getAlarmLimit() = 0;\n\n  // Gets a byte size limit to apply to operations that will buffer a possibly large amount of\n  // data in C++ memory, such as reading an entire HTTP response into an `ArrayBuffer`.\n  virtual size_t getBufferingLimit() = 0;\n\n  // If a limit has been exceeded which prevents further JavaScript execution, such as the CPU or\n  // memory limit, returns a request status code indicating which one. Returns null if no limits\n  // are exceeded.\n  virtual kj::Maybe<EventOutcome> getLimitsExceeded() = 0;\n\n  // Returns a promise that will reject if and when a limit is exceeded that prevents further\n  // JavaScript execution, such as the CPU or memory limit.\n  virtual kj::Promise<void> onLimitsExceeded() = 0;\n  // Sets a callback to call when the cpu limit is nearly exceeded. The callback must be signal safe\n  // and cannot take the isolate lock.\n  virtual void setCpuLimitNearlyExceededCallback(kj::Function<void(void)>) = 0;\n\n  // Throws an exception if a limit has already been exceeded which prevents further JavaScript\n  // execution, such as the CPU or memory limit.\n  virtual void requireLimitsNotExceeded() = 0;\n\n  // Report resource usage metrics to the given request metrics object.\n  virtual void reportMetrics(RequestObserver& requestMetrics) = 0;\n\n  // Only used downstream for internal metrics.\n  virtual kj::Duration consumeTimeElapsedForPeriodicLogging() = 0;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/maximum-compatibility-date.txt",
    "content": "2026-03-28\n"
  },
  {
    "path": "src/workerd/io/observer-test.c++",
    "content": "#include \"observer.h\"\n#include \"worker-interface.h\"\n\n#include <kj/test.h>\n\nnamespace workerd {\nnamespace {\n\nKJ_TEST(\"FeatureObserver\") {\n  FeatureObserver::init(FeatureObserver::createDefault());\n\n  auto& observer = KJ_ASSERT_NONNULL(FeatureObserver::get());\n\n  observer.use(FeatureObserver::Feature::TEST);\n  observer.use(FeatureObserver::Feature::TEST);\n  observer.use(FeatureObserver::Feature::TEST);\n\n  uint64_t count = 0;\n  observer.collect([&](FeatureObserver::Feature feature, const uint64_t value) {\n    KJ_ASSERT(feature == FeatureObserver::Feature::TEST);\n    count = value;\n  });\n  KJ_ASSERT(count == 3);\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/observer.c++",
    "content": "#include \"observer.h\"\n\n#include \"worker-interface.h\"\n\n#include <kj/common.h>\n#include <kj/debug.h>\n#include <kj/map.h>\n#include <kj/mutex.h>\n\nnamespace workerd {\n\nnamespace {\nkj::Maybe<kj::Own<FeatureObserver>> featureObserver;\n\nclass FeatureObserverImpl final: public FeatureObserver {\n public:\n  void use(Feature feature) const override {\n    auto lock = counts.lockExclusive();\n    lock->upsert(feature, 1, [](uint64_t& count, uint64_t value) { count += value; });\n  }\n\n  void collect(CollectCallback&& callback) const override {\n    auto lock = counts.lockShared();\n    for (auto& entry: *lock) {\n      callback(entry.key, entry.value);\n    }\n  }\n\n private:\n  kj::MutexGuarded<kj::HashMap<Feature, uint64_t>> counts;\n};\n\n}  // namespace\n\nkj::Own<FeatureObserver> FeatureObserver::createDefault() {\n  return kj::heap<FeatureObserverImpl>();\n}\n\nvoid FeatureObserver::init(kj::Own<FeatureObserver> instance) {\n  KJ_ASSERT(featureObserver == kj::none);\n  featureObserver = kj::mv(instance);\n}\n\nkj::Maybe<FeatureObserver&> FeatureObserver::get() {\n  KJ_IF_SOME(impl, featureObserver) {\n    return *impl;\n  }\n  return kj::none;\n}\n};  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/observer.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// Defines abstract interfaces for observing the activity of various components of the system,\n// e.g. to collect logs and metrics.\n\n#include <workerd/io/features.capnp.h>\n#include <workerd/io/trace.h>\n#include <workerd/jsg/observer.h>\n#include <workerd/util/sqlite.h>\n\n#include <kj/refcount.h>\n#include <kj/string.h>\n#include <kj/time.h>\n\nnamespace workerd {\n\nclass IoContext;\nclass WorkerInterface;\nclass LimitEnforcer;\nclass TimerChannel;\n\nclass WebSocketObserver: public kj::Refcounted {\n public:\n  virtual ~WebSocketObserver() noexcept(false) = default;\n  // Called when a worker sends a message on this WebSocket (includes close messages).\n  virtual void sentMessage(size_t bytes) {};\n  // Called when a worker receives a message on this WebSocket (includes close messages).\n  virtual void receivedMessage(size_t bytes) {};\n};\n\n// Observes a byte stream. Byte streams which use instances of this observer should call enqueue()\n// and dequeue() once for each chunk that passes through the stream. The order of enqueues should\n// match the order of dequeues.\n//\n// Byte observer implementations can then calculate the current number of chunks and the sum of the\n// size of the chunks in the internal queue by incrementing and decrementing each metric in\n// enqueue() and dequeue() respectively.\nclass ByteStreamObserver {\n public:\n  virtual ~ByteStreamObserver() noexcept(false) = default;\n  // Called when a chunk of size `bytes` is enqueued on the stream.\n  virtual void onChunkEnqueued(size_t bytes) {};\n  // Called when a chunk of size `bytes` is dequeued from the stream (e.g. when a writable byte\n  // stream writes the chunk to its corresponding sink).\n  virtual void onChunkDequeued(size_t bytes) {};\n};\n\n// Observes a specific request to a specific worker. Also observes outgoing subrequests.\n//\n// Observing anything is optional. Default implementations of all methods observe nothing.\nclass RequestObserver: public kj::Refcounted {\n public:\n  // This is called when the request is converted to a WebSocket connection terminating in a worker.\n  // An optional WebSocket observer may be returned to observe events on the worker's end of the\n  // WebSocket connection.\n  //\n  // This means that, when the returned observer observes a message being sent, the message is being\n  // sent from the worker to the client making the request.\n  virtual kj::Maybe<kj::Own<WebSocketObserver>> tryCreateWebSocketObserver() {\n    return kj::none;\n  };\n\n  // This is called when a writable byte stream is created whilst processing this request. It will\n  // be destroyed when the corresponding byte stream is destroyed.\n  virtual kj::Maybe<kj::Own<ByteStreamObserver>> tryCreateWritableByteStreamObserver() {\n    return kj::none;\n  }\n\n  // Invoked when the request is actually delivered.\n  //\n  // If, for some reason, this is not invoked before the object is destroyed, this indicate that\n  // the event was canceled for some reason before delivery. No JavaScript was invoked. In this\n  // case, the request should not be billed.\n  virtual void delivered() {};\n\n  // Call when no more JavaScript will run on behalf of this request. Note that deferred proxying\n  // may still be in progress.\n  virtual void jsDone() {}\n\n  // Called to indicate this was a prewarm request. Normal request metrics won't be logged, but\n  // the prewarm metric will be incremented.\n  virtual void setIsPrewarm() {}\n\n  // Describes the source of a failure\n  enum class FailureSource : uint8_t {\n    // Failure occurred during deferred proxying\n    DEFERRED_PROXY,\n\n    // Failure occurred elsewhere\n    OTHER,\n  };\n\n  // Report that the request failed with the given exception. This only needs to be called in\n  // cases where the wrapper created with wrapWorkerInterface() wouldn't otherwise see the\n  // exception, e.g. because it has been replaced with an HTTP error response or because it\n  // occurred asynchronously.\n  virtual void reportFailure(const kj::Exception& e, FailureSource source = FailureSource::OTHER) {}\n\n  // Called when an internal exception is observed during this request. Used to track which\n  // internal exception types occurred during a request, for metrics purposes. The same exception\n  // type may be reported multiple times during a single request; implementations should deduplicate.\n  virtual void reportInternalException(\n      const kj::Exception& e, jsg::InternalExceptionObserver::Detail detail) {}\n\n  // Wrap the given WorkerInterface with a version that collects metrics. This method may only be\n  // called once, and only one method call may be made to the returned interface.\n  //\n  // The returned reference remains valid as long as the observer and `worker` both remain live.\n  virtual WorkerInterface& wrapWorkerInterface(WorkerInterface& worker) {\n    return worker;\n  }\n\n  // Wrap an HttpClient so that its usage is counted in the request's subrequest stats.\n  virtual kj::Own<WorkerInterface> wrapSubrequestClient(kj::Own<WorkerInterface> client) {\n    return kj::mv(client);\n  }\n\n  // Wrap an HttpClient so that its usage is counted in the request's actor subrequest count.\n  virtual kj::Own<WorkerInterface> wrapActorSubrequestClient(kj::Own<WorkerInterface> client) {\n    return kj::mv(client);\n  }\n\n  // Used to record when a worker has used a dynamic dispatch binding.\n  virtual void setHasDispatched() {};\n\n  virtual SpanParent getSpan() {\n    return nullptr;\n  }\n\n  virtual void setOutcome(EventOutcome outcome) {}\n\n  virtual kj::Own<void> addedContextTask() {\n    return kj::Own<void>();\n  }\n  virtual kj::Own<void> addedWaitUntilTask() {\n    return kj::Own<void>();\n  }\n\n  virtual void setFailedOpen(bool value) {}\n\n  // Called when the language runtime for this worker encounters a fatal error during this\n  // invocation. Currently used for Pyodide fatal errors, but is language-agnostic and can be used\n  // for other language runtimes in the future.\n  virtual void setWorkerFatal() {}\n\n  virtual uint64_t clockRead() {\n    return 0;\n  }\n};\n\nclass JsgIsolateObserver: public kj::AtomicRefcounted, public jsg::IsolateObserver {};\n\nclass IsolateObserver: public kj::AtomicRefcounted {\n public:\n  virtual ~IsolateObserver() noexcept(false) {}\n\n  // Called when Worker::Isolate is created.\n  virtual void created() {};\n\n  // Called when the owning Worker::Script is being destroyed. The IsolateObserver may\n  // live a while longer to handle deferred proxy requests.\n  virtual void evicted() {}\n\n  virtual void teardownStarted() {}\n  virtual void teardownLockAcquired() {}\n  virtual void teardownFinished() {}\n\n  // Describes why a worker was started.\n  enum class StartType : uint8_t {\n    // Cold start with active request waiting.\n    COLD,\n\n    // Started due to prewarm hint (e.g. from TLS SNI); a real request is expected soon.\n    PREWARM,\n\n    // Started due to preload at process startup.\n    PRELOAD\n  };\n\n  // Created while parsing a script, to record related metrics.\n  class Parse {\n   public:\n    // Marks the ScriptReplica as finished parsing, which starts reporting of isolate metrics.\n    virtual void done() {}\n  };\n\n  virtual kj::Own<Parse> parse(StartType startType) const {\n    class FinalParse final: public Parse {};\n    return kj::heap<FinalParse>();\n  }\n\n  class LockTiming {\n   public:\n    // Called by `Isolate::takeAsyncLock()` when it is blocked by a different isolate lock on the\n    // same thread.\n    virtual void waitingForOtherIsolate(kj::StringPtr id) {}\n\n    // Call if this is an async lock attempt, before constructing LockRecord.\n    virtual void reportAsyncInfo(\n        uint currentLoad, bool threadWaitingSameLock, uint threadWaitingDifferentLockCount) {}\n    // TODO(cleanup): Should be able to get this data at `tryCreateLockTiming()` time. It'd be\n    //   easier if IsolateObserver were an AOP class, and thus had access to the real isolate.\n\n    virtual void start() {}\n    virtual void stop() {}\n\n    virtual void locked() {}\n    virtual void gcPrologue() {}\n    virtual void gcEpilogue() {}\n  };\n\n  // Construct a LockTiming if config.reportScriptLockTiming is true, or if the\n  // request (if any) is being traced.\n  virtual kj::Maybe<kj::Own<LockTiming>> tryCreateLockTiming(\n      kj::OneOf<SpanParent, kj::Maybe<RequestObserver&>> parentOrRequest) const {\n    return kj::none;\n  }\n\n  // Use like so:\n  //\n  //   auto lockTiming = MetricsCollector::ScriptReplica::LockTiming::tryCreate(\n  //       script, maybeRequest);\n  //   MetricsCollector::ScriptReplica::LockRecord record(lockTiming);\n  //   isolate.runInLockScope([&](MyIsolate::Lock& lock) {\n  //     record.locked();\n  //   });\n  //\n  // And `record()` will report the time spent waiting for the lock (including any asynchronous\n  // time you might insert between the construction of `lockTiming` and `LockRecord()`), plus\n  // the time spent holding the lock for the given ScriptReplica.\n  //\n  // This is a thin wrapper around LockTiming which efficiently handles the case where we don't\n  // want to track timing.\n  class LockRecord {\n   public:\n    explicit LockRecord(kj::Maybe<kj::Own<LockTiming>> lockTimingParam)\n        : lockTiming(kj::mv(lockTimingParam)) {\n      KJ_IF_SOME(l, lockTiming) l.get()->start();\n    }\n    ~LockRecord() noexcept(false) {\n      KJ_IF_SOME(l, lockTiming) l.get()->stop();\n    }\n    KJ_DISALLOW_COPY_AND_MOVE(LockRecord);\n\n    void locked() {\n      KJ_IF_SOME(l, lockTiming) l.get()->locked();\n    }\n    void gcPrologue() {\n      KJ_IF_SOME(l, lockTiming) l.get()->gcPrologue();\n    }\n    void gcEpilogue() {\n      KJ_IF_SOME(l, lockTiming) l.get()->gcEpilogue();\n    }\n\n   private:\n    // The presence of `lockTiming` determines whether or not we need to record timing data. If\n    // we have no `lockTiming`, then this LockRecord wrapper is just a big nothingburger.\n    kj::Maybe<kj::Own<LockTiming>> lockTiming;\n  };\n};\n\nclass WorkerObserver: public kj::AtomicRefcounted {\n public:\n  // Created while executing a script's global scope, to record related metrics.\n  class Startup {\n   public:\n    virtual void done() {}\n  };\n\n  virtual kj::Own<Startup> startup(IsolateObserver::StartType startType) const {\n    class FinalStartup final: public Startup {};\n    return kj::heap<FinalStartup>();\n  }\n\n  virtual void teardownStarted() {}\n  virtual void teardownLockAcquired() {}\n  virtual void teardownFinished() {}\n};\n\nclass ActorObserver: public kj::Refcounted, public SqliteObserver {\n public:\n  // Allows the observer to run in the background, periodically making observations. Owner must\n  // call this and store the promise. `limitEnforcer` is used to collect CPU usage metrics, it\n  // must remain valid as long as the loop is running.\n  virtual kj::Promise<void> flushLoop(TimerChannel& timer, LimitEnforcer& limitEnforcer) {\n    return kj::NEVER_DONE;\n  }\n\n  virtual void startRequest() {}\n  virtual void endRequest() {}\n\n  virtual void webSocketAccepted() {}\n  virtual void webSocketClosed() {}\n  virtual void receivedWebSocketMessage(size_t bytes) {}\n  virtual void sentWebSocketMessage(size_t bytes) {}\n\n  virtual void addCachedStorageReadUnits(uint32_t units) {}\n  virtual void addUncachedStorageReadUnits(uint32_t units) {}\n  virtual void addStorageWriteUnits(uint32_t units) {}\n  virtual void addStorageDeletes(uint32_t count) {}\n\n  virtual void storageReadCompleted(kj::Duration latency) {}\n  virtual void storageWriteCompleted(kj::Duration latency) {}\n\n  virtual void inputGateLocked() {}\n  virtual void inputGateReleased() {}\n  virtual void inputGateWaiterAdded() {}\n  virtual void inputGateWaiterRemoved() {}\n  virtual void outputGateLocked() {}\n  virtual void outputGateReleased() {}\n  virtual void outputGateWaiterAdded() {}\n  virtual void outputGateWaiterRemoved() {}\n\n  virtual void shutdown(uint16_t reasonCode, LimitEnforcer& limitEnforcer) {}\n};\n\n// RAII object to call `teardownFinished()` on an observer for you.\ntemplate <typename Observer>\nclass TeardownFinishedGuard {\n public:\n  TeardownFinishedGuard(Observer& ref): ref(ref) {}\n  ~TeardownFinishedGuard() noexcept(false) {\n    ref.teardownFinished();\n  }\n  KJ_DISALLOW_COPY_AND_MOVE(TeardownFinishedGuard);\n\n private:\n  Observer& ref;\n};\n\n// Provides counters/observers for various features. The intent is to\n// make it possible to collect metrics on which runtime features are\n// used and how often.\n//\n// There is exactly one instance of this class per worker process.\nclass FeatureObserver {\n public:\n  static kj::Own<FeatureObserver> createDefault();\n  static void init(kj::Own<FeatureObserver> instance);\n  static kj::Maybe<FeatureObserver&> get();\n\n  // A \"Feature\" is just an opaque identifier defined in the features.capnp\n  // file.\n  using Feature = workerd::Features;\n\n  // Called to increment the usage counter for a feature.\n  virtual void use(Feature feature) const {}\n\n  using CollectCallback = kj::Function<void(Feature, const uint64_t)>;\n  // This method is called from the internal metrics collection mechanism to harvest the\n  // current features and counts that have been recorded by the observer.\n  virtual void collect(CollectCallback&& callback) const {}\n\n  // Records the use of the feature if a FeatureObserver is available.\n  static inline void maybeRecordUse(Feature feature) {\n    KJ_IF_SOME(observer, get()) {\n      observer.use(feature);\n    }\n  }\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/outcome.capnp",
    "content": "# Copyright (c) 2017-2022 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n@0xe27bb9203d5e02a8;\n\nusing Cxx = import \"/capnp/c++.capnp\";\n$Cxx.namespace(\"workerd\");\n$Cxx.allowCancellation;\n\n# ========================================================================================\n# DO NOT MODIFY BELOW THIS COMMENT -- except if copying from the authoritative version.\n#\n# This enum is defined as part of the Cloudflare Workers log schemas. The upstream version\n# of the enum needs to be updated first, and then changes can be copied here.\n# ========================================================================================\n\nenum EventOutcome {\n  unknown @0;\n  ok @1;\n  exception @2;\n  exceededCpu @3;\n  killSwitch @4;\n  daemonDown @5;\n  scriptNotFound @6;\n  canceled @7;\n  exceededMemory @8;\n  loadShed @9;\n  responseStreamDisconnected @10;\n}\n"
  },
  {
    "path": "src/workerd/io/promise-wrapper-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/io/promise-wrapper.h>\n#include <workerd/jsg/jsg-test.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/setup.h>\n\n#include <kj/test.h>\n\nnamespace workerd::jsg::test {  // workerd\nnamespace {\n\njsg::V8System v8System;\n\nstruct CaptureThrowContext: public jsg::Object, public ContextGlobal {\n  kj::Promise<int> test1() {\n    JSG_FAIL_REQUIRE(TypeError, \"boom\");\n  }\n\n  kj::Promise<void> test2() {\n    JSG_FAIL_REQUIRE(TypeError, \"boom\");\n  }\n\n  int test3() {\n    JSG_FAIL_REQUIRE(TypeError, \"boom\");\n  }\n\n  kj::Promise<void> test4(const v8::FunctionCallbackInfo<v8::Value>& args) {\n    JSG_FAIL_REQUIRE(TypeError, \"boom\");\n  }\n\n  int test5(const v8::FunctionCallbackInfo<v8::Value>& args) {\n    JSG_FAIL_REQUIRE(TypeError, \"boom\");\n  }\n\n  v8::Local<v8::Promise> test6() {\n    JSG_FAIL_REQUIRE(TypeError, \"boom\");\n  }\n\n  v8::Local<v8::Promise> test7(const v8::FunctionCallbackInfo<v8::Value>& args) {\n    JSG_FAIL_REQUIRE(TypeError, \"boom\");\n  }\n\n  template <typename T>\n  v8::Local<v8::Value> testT(jsg::Lock& js, const jsg::TypeHandler<Function<T()>>& handler) {\n    return handler.wrap(js, [](Lock&) -> T { JSG_FAIL_REQUIRE(TypeError, \"boom\"); });\n  }\n\n  static kj::Promise<void> staticTest1() {\n    JSG_FAIL_REQUIRE(TypeError, \"boom\");\n  }\n\n  static kj::Promise<void> staticTest2(const v8::FunctionCallbackInfo<v8::Value>&) {\n    JSG_FAIL_REQUIRE(TypeError, \"boom\");\n  }\n\n  static kj::Promise<void> staticTest3(jsg::Lock& js) {\n    // Tests that JsExceptionThrown is handled properly.\n    jsg::throwTypeError(js.v8Isolate, \"boom\"_kj);\n  }\n\n  kj::Promise<void> getTest() {\n    JSG_FAIL_REQUIRE(TypeError, \"boom\");\n  }\n\n  JSG_RESOURCE_TYPE(CaptureThrowContext) {\n    JSG_METHOD(test1);\n    JSG_METHOD(test2);\n    JSG_METHOD(test3);\n    JSG_METHOD(test4);\n    JSG_METHOD(test5);\n    JSG_METHOD(test6);\n    JSG_METHOD(test7);\n    JSG_READONLY_PROTOTYPE_PROPERTY(test8, template testT<kj::Promise<void>>);\n    JSG_STATIC_METHOD(staticTest1);\n    JSG_STATIC_METHOD(staticTest2);\n    JSG_STATIC_METHOD(staticTest3);\n    JSG_READONLY_PROTOTYPE_PROPERTY(test, getTest);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(\n    CaptureThrowIsolate, CaptureThrowContext, jsg::TypeWrapperExtension<workerd::PromiseWrapper>);\n\nKJ_TEST(\"Async functions capture sync errors with flag\") {\n  Evaluator<CaptureThrowContext, CaptureThrowIsolate> e(v8System);\n  e.setCaptureThrowsAsRejections(true);\n  e.expectEval(\"test1()\", \"object\", \"[object Promise]\");\n  e.expectEval(\"test2()\", \"object\", \"[object Promise]\");\n  e.expectEval(\"test3()\", \"throws\", \"TypeError: boom\");\n  e.expectEval(\"test4()\", \"object\", \"[object Promise]\");\n  e.expectEval(\"test5()\", \"throws\", \"TypeError: boom\");\n  e.expectEval(\"test6()\", \"object\", \"[object Promise]\");\n  e.expectEval(\"test7()\", \"object\", \"[object Promise]\");\n  e.expectEval(\"test8()\", \"object\", \"[object Promise]\");\n  e.expectEval(\"CaptureThrowContext.staticTest1()\", \"object\", \"[object Promise]\");\n  e.expectEval(\"CaptureThrowContext.staticTest2()\", \"object\", \"[object Promise]\");\n  e.expectEval(\"CaptureThrowContext.staticTest3()\", \"object\", \"[object Promise]\");\n  e.expectEval(\"test\", \"object\", \"[object Promise]\");\n}\n\nKJ_TEST(\"Async functions do not capture sync errors without flag\") {\n  Evaluator<CaptureThrowContext, CaptureThrowIsolate> e(v8System);\n  e.setCaptureThrowsAsRejections(false);\n  e.expectEval(\"test1()\", \"throws\", \"TypeError: boom\");\n  e.expectEval(\"test2()\", \"throws\", \"TypeError: boom\");\n  e.expectEval(\"test3()\", \"throws\", \"TypeError: boom\");\n  e.expectEval(\"test4()\", \"throws\", \"TypeError: boom\");\n  e.expectEval(\"test5()\", \"throws\", \"TypeError: boom\");\n  e.expectEval(\"test6()\", \"throws\", \"TypeError: boom\");\n  e.expectEval(\"test7()\", \"throws\", \"TypeError: boom\");\n  e.expectEval(\"test8()\", \"throws\", \"TypeError: boom\");\n  e.expectEval(\"CaptureThrowContext.staticTest1()\", \"throws\", \"TypeError: boom\");\n  e.expectEval(\"CaptureThrowContext.staticTest2()\", \"throws\", \"TypeError: boom\");\n  e.expectEval(\"CaptureThrowContext.staticTest3()\", \"throws\", \"TypeError: boom\");\n  e.expectEval(\"test\", \"throws\", \"TypeError: boom\");\n}\n\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/io/promise-wrapper.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/io/io-context.h>\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd {\n\ntemplate <typename Self>\nclass PromiseWrapper {\n public:\n  template <typename T>\n  static constexpr const char* getName(kj::Promise<T>*) {\n    return \"Promise\";\n  }\n\n  // Explicitly disallow V8 handles, which are not safe for the KJ event loop to own directly.\n  template <typename T>\n  static constexpr const char* getName(kj::Promise<v8::Global<T>>*) = delete;\n  template <typename T>\n  static constexpr const char* getName(kj::Promise<v8::Local<T>>*) = delete;\n\n  template <typename T>\n  static constexpr const char* getName(kj::Promise<jsg::Ref<T>>*) {\n    return \"Promise\";\n  }\n  // For some reason, this wasn't needed for jsg::V8Ref<T>...\n\n  template <typename T>\n  v8::Local<v8::Promise> wrap(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      kj::Promise<T> promise) {\n    auto jsPromise = IoContext::current().awaitIoLegacy(js, kj::mv(promise));\n    return static_cast<Self&>(*this).wrap(js, context, kj::mv(creator), kj::mv(jsPromise));\n  }\n\n  template <typename T>\n  kj::Maybe<kj::Promise<T>> tryUnwrap(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      kj::Promise<T>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    auto& wrapper = static_cast<Self&>(*this);\n    auto jsPromise = KJ_UNWRAP_OR_RETURN(\n        wrapper.tryUnwrap(js, context, handle, (jsg::Promise<T>*)nullptr, parentObject), kj::none);\n    return IoContext::current().awaitJs(js, kj::mv(jsPromise));\n  }\n\n  template <typename T>\n  v8::Local<v8::Context> newContext(v8::Isolate* isolate, kj::Promise<T> value) = delete;\n  template <bool isContext = false, typename T>\n  v8::Local<v8::FunctionTemplate> getTemplate(v8::Isolate* isolate, kj::Promise<T>*) = delete;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/release-version.txt",
    "content": "2026-03-21\n"
  },
  {
    "path": "src/workerd/io/request-tracker.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"request-tracker.h\"\n\nnamespace workerd {\n\nRequestTracker::RequestTracker(Hooks& hooksImpl): hooks(hooksImpl) {};\n\nRequestTracker::~RequestTracker() noexcept(false) {}\n\nRequestTracker::ActiveRequest::ActiveRequest(kj::Badge<RequestTracker>, RequestTracker& parent)\n    : maybeParent(kj::addRef(parent)) {\n  parent.requestActive();\n}\nRequestTracker::ActiveRequest::~ActiveRequest() noexcept(false) {\n  KJ_IF_SOME(p, maybeParent) {\n    p.get()->requestInactive();\n  }\n}\n\nvoid RequestTracker::requestActive() {\n  if (activeRequests++ == 0) {\n    KJ_IF_SOME(h, hooks) {\n      h.active();\n    }\n  }\n}\n\nvoid RequestTracker::requestInactive() {\n  KJ_IF_SOME(h, hooks) {\n    if (--activeRequests == 0) {\n      h.inactive();\n    }\n  }\n}\n\nRequestTracker::ActiveRequest RequestTracker::startRequest() {\n  return ActiveRequest({}, *this);\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/request-tracker.h",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/refcount.h>\n\nnamespace workerd {\n\n// This class is used to track a number of associated requests so that some desired behavior\n// is carried out once all requests have completed. `activeRequests` is incremented each time a\n// new request is created, and then decremented once it completes.\nclass RequestTracker final: public kj::Refcounted {\n public:\n  class Hooks {\n   public:\n    virtual void active() = 0;\n    virtual void inactive() = 0;\n  };\n\n  // An object that should be associated with (attached to) a request.\n  class ActiveRequest final {\n   public:\n    // On creation, if the parent RequestTracker has 0 active requests, we call the `active()` hook.\n    // On destruction, if the RequestTracker has 0 active requests, we call the `inactive()` hook.\n    // Otherwise, we just increment/decrement the count on creation/destruction respectively.\n    ActiveRequest(kj::Badge<RequestTracker>, RequestTracker& parent);\n    ActiveRequest(ActiveRequest&& other) = default;\n    KJ_DISALLOW_COPY(ActiveRequest);\n    ~ActiveRequest() noexcept(false);\n\n   private:\n    kj::Maybe<kj::Own<RequestTracker>> maybeParent;\n  };\n\n  RequestTracker(Hooks& hooks);\n  ~RequestTracker() noexcept(false);\n  KJ_DISALLOW_COPY(RequestTracker);\n\n  // Returns a new ActiveRequest, thereby bumping the count of active requests associated with the\n  // RequestTracker. The ActiveRequest must be attached to the lifetime of the request such that we\n  // destroy the ActiveRequest when the request is finished. On destruction, we decrement the count\n  // of active requests associated with the RequestTracker, and if there are no more active requests\n  // we call the `inactive()` hook.\n  ActiveRequest startRequest();\n\n  void shutdown() {\n    // We want to prevent any hooks from running after this point.\n    hooks = kj::none;\n  }\n\n  kj::Own<RequestTracker> addRef() {\n    return kj::addRef(*this);\n  }\n\n private:\n  void requestActive();\n  void requestInactive();\n\n  int activeRequests = 0;\n  kj::Maybe<Hooks&> hooks;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/script-version.capnp",
    "content": "# Copyright (c) 2023 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n@0xd3b6bb739ff1b77a;\n\nusing Cxx = import \"/capnp/c++.capnp\";\n$Cxx.namespace(\"workerd\");\n\nstruct ScriptVersion {\n  id :group {\n    upper @0 :UInt64;\n    # Most significant bits of the UUID.\n    lower @1 :UInt64;\n    # Least significant bits of the UUID.\n  }\n  # An optional UUID identifying this version. A null UUID value (where both upper and lower values\n  # are 0) can be used to indicate the absence of an ID.\n  tag @2 :Text;\n  # An optional tag to associate with this version.\n  message @3 :Text;\n  # An optional message that can be used to describe this version.\n}\n"
  },
  {
    "path": "src/workerd/io/trace-stream.c++",
    "content": "#include <workerd/api/global-scope.h>\n#include <workerd/io/io-context.h>\n#include <workerd/io/io-own.h>\n#include <workerd/io/trace-stream.h>\n#include <workerd/io/worker-interface.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/util/completion-membrane.h>\n#include <workerd/util/strings.h>\n#include <workerd/util/uuid.h>\n\n#include <capnp/membrane.h>\n\n#include <algorithm>\n\nnamespace workerd::tracing {\nnamespace {\n\n#define STRS(V)                                                                                    \\\n  V(ALARM, \"alarm\")                                                                                \\\n  V(ATTRIBUTES, \"attributes\")                                                                      \\\n  V(BATCHSIZE, \"batchSize\")                                                                        \\\n  V(CANCELED, \"canceled\")                                                                          \\\n  V(CHANNEL, \"channel\")                                                                            \\\n  V(CFJSON, \"cfJson\")                                                                              \\\n  V(CLOSE, \"close\")                                                                                \\\n  V(CODE, \"code\")                                                                                  \\\n  V(COUNT, \"count\")                                                                                \\\n  V(CPUTIME, \"cpuTime\")                                                                            \\\n  V(CRON, \"cron\")                                                                                  \\\n  V(CUSTOM, \"custom\")                                                                              \\\n  V(DAEMONDOWN, \"daemonDown\")                                                                      \\\n  V(DEBUG, \"debug\")                                                                                \\\n  V(DIAGNOSTICCHANNEL, \"diagnosticChannel\")                                                        \\\n  V(DIAGNOSTIC, \"diagnostic\")                                                                      \\\n  V(DIAGNOSTICSTYPE, \"diagnosticsType\")                                                            \\\n  V(DISPATCHNAMESPACE, \"dispatchNamespace\")                                                        \\\n  V(DROPPEDEVENTS, \"droppedEvents\")                                                                \\\n  V(EMAIL, \"email\")                                                                                \\\n  V(ENTRYPOINT, \"entrypoint\")                                                                      \\\n  V(ERROR, \"error\")                                                                                \\\n  V(EVENT, \"event\")                                                                                \\\n  V(EXCEEDEDCPU, \"exceededCpu\")                                                                    \\\n  V(EXCEEDEDMEMORY, \"exceededMemory\")                                                              \\\n  V(EXCEPTION, \"exception\")                                                                        \\\n  V(EXECUTIONMODEL, \"executionModel\")                                                              \\\n  V(FETCH, \"fetch\")                                                                                \\\n  V(HEADERS, \"headers\")                                                                            \\\n  V(HIBERNATABLEWEBSOCKET, \"hibernatableWebSocket\")                                                \\\n  V(ID, \"id\")                                                                                      \\\n  V(INFO, \"info\")                                                                                  \\\n  V(INVOCATIONID, \"invocationId\")                                                                  \\\n  V(JSRPC, \"jsrpc\")                                                                                \\\n  V(KILLSWITCH, \"killSwitch\")                                                                      \\\n  V(LEVEL, \"level\")                                                                                \\\n  V(LOADSHED, \"loadShed\")                                                                          \\\n  V(LOG, \"log\")                                                                                    \\\n  V(MAILFROM, \"mailFrom\")                                                                          \\\n  V(MESSAGE, \"message\")                                                                            \\\n  V(METHOD, \"method\")                                                                              \\\n  V(NAME, \"name\")                                                                                  \\\n  V(OK, \"ok\")                                                                                      \\\n  V(ONSET, \"onset\")                                                                                \\\n  V(OUTCOME, \"outcome\")                                                                            \\\n  V(QUEUE, \"queue\")                                                                                \\\n  V(QUEUENAME, \"queueName\")                                                                        \\\n  V(RAWSIZE, \"rawSize\")                                                                            \\\n  V(RCPTTO, \"rcptTo\")                                                                              \\\n  V(RESPONSESTREAMDISCONNECTED, \"responseStreamDisconnected\")                                      \\\n  V(RETURN, \"return\")                                                                              \\\n  V(SCHEDULED, \"scheduled\")                                                                        \\\n  V(SCHEDULEDTIME, \"scheduledTime\")                                                                \\\n  V(SCRIPTNAME, \"scriptName\")                                                                      \\\n  V(SCRIPTNOTFOUND, \"scriptNotFound\")                                                              \\\n  V(SCRIPTTAGS, \"scriptTags\")                                                                      \\\n  V(SCRIPTVERSION, \"scriptVersion\")                                                                \\\n  V(SEQUENCE, \"sequence\")                                                                          \\\n  V(SPANCLOSE, \"spanClose\")                                                                        \\\n  V(SPANCONTEXT, \"spanContext\")                                                                    \\\n  V(SPANID, \"spanId\")                                                                              \\\n  V(SPANOPEN, \"spanOpen\")                                                                          \\\n  V(STACK, \"stack\")                                                                                \\\n  V(STATUSCODE, \"statusCode\")                                                                      \\\n  V(STREAMDIAGEVENT, \"streamDiagEvent\")                                                            \\\n  V(STREAMDIAGNOSTIC, \"streamDiagnostic\")                                                          \\\n  V(TAG, \"tag\")                                                                                    \\\n  V(TIMESTAMP, \"timestamp\")                                                                        \\\n  V(TRACEID, \"traceId\")                                                                            \\\n  V(TRACE, \"trace\")                                                                                \\\n  V(TRACES, \"traces\")                                                                              \\\n  V(TYPE, \"type\")                                                                                  \\\n  V(UNKNOWN, \"unknown\")                                                                            \\\n  V(URL, \"url\")                                                                                    \\\n  V(VALUE, \"value\")                                                                                \\\n  V(WALLTIME, \"wallTime\")                                                                          \\\n  V(WARN, \"warn\")                                                                                  \\\n  V(WASCLEAN, \"wasClean\")\n\n#define V(N, L) constexpr kj::LiteralStringConst N##_STR = L##_kjc;\nSTRS(V)\n#undef STRS\n\n// Utility that prevents creating duplicate JS strings while serializing a tail event.\nclass StringCache final {\n public:\n  StringCache() = default;\n  KJ_DISALLOW_COPY_AND_MOVE(StringCache);\n\n  // Inserted string keys must live as long as the cache. For string constants (the common case),\n  // we use LiteralStringConst and avoid memory allocation. For temporary strings, we pass in a\n  // StringPtr and allocate a string. Having ConstString as the value type fits both cases.\n  jsg::JsValue get(jsg::Lock& js, kj::LiteralStringConst value) {\n    return cache\n        .findOrCreate(value, [&]() -> decltype(cache)::Entry {\n      return {value, jsg::JsRef<jsg::JsValue>(js, js.strIntern(value))};\n    }).getHandle(js);\n  }\n  jsg::JsValue get(jsg::Lock& js, kj::StringPtr value) {\n    return cache\n        .findOrCreate(value, [&]() -> decltype(cache)::Entry {\n      return {kj::ConstString(kj::str(value)), jsg::JsRef<jsg::JsValue>(js, js.strIntern(value))};\n    }).getHandle(js);\n  }\n\n private:\n  kj::HashMap<kj::ConstString, jsg::JsRef<jsg::JsValue>> cache;\n};\n\n// Why ToJS(...) functions and not JSG_STRUCT? Good question. The various tracing:*\n// types are defined in the \"trace\" bazel target which currently does not depend on\n// jsg in any way. These also represent the internal API of these types which doesn't\n// really match exactly what we want to expose to users. In order to use JSG_STRUCT\n// we would either need to make the \"trace\" target depend on \"jsg\", which seems a bit\n// wasteful and unnecessary, or we'd need to define wrapper structs that use JSG_STRUCT\n// which also seems wasteful and unnecessary. We also don't need the type mapping for\n// these structs to be bidirectional. So, instead, let's just do the simple easy thing\n// and define a set of serializers to these types.\n\n// Serialize attribute value\njsg::JsValue ToJs(jsg::Lock& js, const Attribute::Value& value) {\n  KJ_SWITCH_ONEOF(value) {\n    KJ_CASE_ONEOF(str, kj::ConstString) {\n      return js.str(str);\n    }\n    KJ_CASE_ONEOF(b, bool) {\n      return js.boolean(b);\n    }\n    KJ_CASE_ONEOF(d, double) {\n      return js.num(d);\n    }\n    KJ_CASE_ONEOF(i, int64_t) {\n      return js.bigInt(i);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\n// Serialize attribute key:value(s) pair object\njsg::JsValue ToJs(jsg::Lock& js, const Attribute& attribute, StringCache& cache) {\n  auto obj = js.obj();\n  obj.set(js, NAME_STR, cache.get(js, attribute.name));\n\n  if (attribute.value.size() == 1) {\n    obj.set(js, VALUE_STR, ToJs(js, attribute.value[0]));\n  } else {\n    obj.set(js, VALUE_STR, js.arr(attribute.value.asPtr(), [](jsg::Lock& js, const auto& val) {\n      return ToJs(js, val);\n    }));\n  }\n\n  return obj;\n}\n\n// Serialize \"attributes\" event\njsg::JsValue ToJs(jsg::Lock& js, kj::ArrayPtr<const Attribute> attributes, StringCache& cache) {\n  auto obj = js.obj();\n  obj.set(js, TYPE_STR, cache.get(js, ATTRIBUTES_STR));\n  obj.set(js, INFO_STR, js.arr(attributes, [&cache](jsg::Lock& js, const auto& attr) {\n    return ToJs(js, attr, cache);\n  }));\n  return obj;\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const FetchResponseInfo& info, StringCache& cache) {\n  static const kj::StringPtr keys[] = {TYPE_STR, STATUSCODE_STR};\n  jsg::JsValue values[] = {cache.get(js, FETCH_STR), js.num(info.statusCode)};\n  return js.obj(kj::arrayPtr(keys), kj::arrayPtr(values));\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const FetchEventInfo& info, StringCache& cache) {\n  auto obj = js.obj();\n  obj.set(js, TYPE_STR, cache.get(js, FETCH_STR));\n  obj.set(js, METHOD_STR, cache.get(js, kj::str(info.method)));\n  obj.set(js, URL_STR, js.str(info.url));\n  if (info.cfJson.size() > 0) {\n    obj.set(js, CFJSON_STR, jsg::JsValue(js.parseJson(info.cfJson).getHandle(js)));\n  }\n\n  auto ToJs = [](jsg::Lock& js, const FetchEventInfo::Header& header, StringCache& cache) {\n    auto obj = js.obj();\n    obj.set(js, NAME_STR, cache.get(js, header.name));\n    obj.set(js, VALUE_STR, js.str(header.value));\n    return obj;\n  };\n\n  obj.set(js, HEADERS_STR,\n      js.arr(info.headers.asPtr(),\n          [&cache, &ToJs](jsg::Lock& js, const auto& header) { return ToJs(js, header, cache); }));\n\n  return obj;\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const JsRpcEventInfo& info, StringCache& cache) {\n  static const kj::StringPtr keys[] = {TYPE_STR};\n  jsg::JsValue values[] = {cache.get(js, JSRPC_STR)};\n  return js.obj(kj::arrayPtr(keys), kj::arrayPtr(values));\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const ScheduledEventInfo& info, StringCache& cache) {\n  auto obj = js.obj();\n  obj.set(js, TYPE_STR, cache.get(js, SCHEDULED_STR));\n  if (isPredictableModeForTest()) {\n    obj.set(js, SCHEDULEDTIME_STR, js.date(kj::UNIX_EPOCH));\n  } else {\n    obj.set(js, SCHEDULEDTIME_STR, js.date(info.scheduledTime));\n  }\n  obj.set(js, CRON_STR, js.str(info.cron));\n  return obj;\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const AlarmEventInfo& info, StringCache& cache) {\n  auto obj = js.obj();\n  obj.set(js, TYPE_STR, cache.get(js, ALARM_STR));\n  if (isPredictableModeForTest()) {\n    obj.set(js, SCHEDULEDTIME_STR, js.date(kj::UNIX_EPOCH));\n  } else {\n    obj.set(js, SCHEDULEDTIME_STR, js.date(info.scheduledTime));\n  }\n  return obj;\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const QueueEventInfo& info, StringCache& cache) {\n  static const kj::StringPtr keys[] = {TYPE_STR, QUEUENAME_STR, BATCHSIZE_STR};\n  jsg::JsValue values[] = {\n    cache.get(js, QUEUE_STR), js.str(info.queueName), js.num(info.batchSize)};\n  return js.obj(kj::arrayPtr(keys), kj::arrayPtr(values));\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const EmailEventInfo& info, StringCache& cache) {\n  static const kj::StringPtr keys[] = {TYPE_STR, MAILFROM_STR, RCPTTO_STR, RAWSIZE_STR};\n  jsg::JsValue values[] = {\n    cache.get(js, EMAIL_STR), js.str(info.mailFrom), js.str(info.rcptTo), js.num(info.rawSize)};\n  return js.obj(kj::arrayPtr(keys), kj::arrayPtr(values));\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const TraceEventInfo& info, StringCache& cache) {\n  auto obj = js.obj();\n  obj.set(js, TYPE_STR, cache.get(js, TRACE_STR));\n  obj.set(js, TRACES_STR,\n      js.arr(info.traces.asPtr(), [](jsg::Lock& js, const auto& trace) -> jsg::JsValue {\n    KJ_IF_SOME(name, trace.scriptName) {\n      return js.str(name);\n    }\n    return js.null();\n  }));\n  return obj;\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const HibernatableWebSocketEventInfo& info, StringCache& cache) {\n  auto obj = js.obj();\n  obj.set(js, TYPE_STR, cache.get(js, HIBERNATABLEWEBSOCKET_STR));\n\n  KJ_SWITCH_ONEOF(info.type) {\n    KJ_CASE_ONEOF(message, HibernatableWebSocketEventInfo::Message) {\n      auto mobj = js.obj();\n      mobj.set(js, TYPE_STR, cache.get(js, MESSAGE_STR));\n      obj.set(js, INFO_STR, mobj);\n    }\n    KJ_CASE_ONEOF(error, HibernatableWebSocketEventInfo::Error) {\n      auto mobj = js.obj();\n      mobj.set(js, TYPE_STR, cache.get(js, ERROR_STR));\n      obj.set(js, INFO_STR, mobj);\n    }\n    KJ_CASE_ONEOF(close, HibernatableWebSocketEventInfo::Close) {\n      auto mobj = js.obj();\n      mobj.set(js, TYPE_STR, cache.get(js, CLOSE_STR));\n      mobj.set(js, CODE_STR, js.num(close.code));\n      mobj.set(js, WASCLEAN_STR, js.boolean(close.wasClean));\n      obj.set(js, INFO_STR, mobj);\n    }\n  }\n\n  return obj;\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const CustomEventInfo& info, StringCache& cache) {\n  auto obj = js.obj();\n  obj.set(js, TYPE_STR, cache.get(js, CUSTOM_STR));\n  return obj;\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const EventOutcome& outcome, StringCache& cache) {\n  switch (outcome) {\n    case EventOutcome::OK:\n      return cache.get(js, OK_STR);\n    case EventOutcome::CANCELED:\n      return cache.get(js, CANCELED_STR);\n    case EventOutcome::EXCEPTION:\n      return cache.get(js, EXCEPTION_STR);\n    case EventOutcome::KILL_SWITCH:\n      return cache.get(js, KILLSWITCH_STR);\n    case EventOutcome::DAEMON_DOWN:\n      return cache.get(js, DAEMONDOWN_STR);\n    case EventOutcome::EXCEEDED_CPU:\n      return cache.get(js, EXCEEDEDCPU_STR);\n    case EventOutcome::EXCEEDED_MEMORY:\n      return cache.get(js, EXCEEDEDMEMORY_STR);\n    case EventOutcome::LOAD_SHED:\n      return cache.get(js, LOADSHED_STR);\n    case EventOutcome::RESPONSE_STREAM_DISCONNECTED:\n      return cache.get(js, RESPONSESTREAMDISCONNECTED_STR);\n    case EventOutcome::SCRIPT_NOT_FOUND:\n      return cache.get(js, SCRIPTNOTFOUND_STR);\n    case EventOutcome::UNKNOWN:\n      return cache.get(js, UNKNOWN_STR);\n  }\n  KJ_UNREACHABLE;\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const Onset& onset, StringCache& cache) {\n  auto obj = js.obj();\n  obj.set(js, TYPE_STR, cache.get(js, ONSET_STR));\n  obj.set(js, EXECUTIONMODEL_STR, cache.get(js, kj::str(onset.workerInfo.executionModel)));\n  obj.set(js, SPANID_STR, js.str(onset.spanId.toGoString()));\n\n  KJ_IF_SOME(ns, onset.workerInfo.dispatchNamespace) {\n    obj.set(js, DISPATCHNAMESPACE_STR, js.str(ns));\n  }\n  KJ_IF_SOME(entrypoint, onset.workerInfo.entrypoint) {\n    obj.set(js, ENTRYPOINT_STR, js.str(entrypoint));\n  }\n  KJ_IF_SOME(name, onset.workerInfo.scriptName) {\n    obj.set(js, SCRIPTNAME_STR, js.str(name));\n  }\n  KJ_IF_SOME(tags, onset.workerInfo.scriptTags) {\n    obj.set(js, SCRIPTTAGS_STR,\n        js.arr(tags.asPtr(), [](jsg::Lock& js, const kj::String& tag) { return js.str(tag); }));\n  }\n  KJ_IF_SOME(version, onset.workerInfo.scriptVersion) {\n    auto vobj = js.obj();\n    auto id = version->getId();\n    KJ_IF_SOME(uuid, UUID::fromUpperLower(id.getUpper(), id.getLower())) {\n      vobj.set(js, ID_STR, js.str(uuid.toString()));\n    }\n    if (version->hasTag()) {\n      vobj.set(js, TAG_STR, js.str(version->getTag()));\n    }\n    if (version->hasMessage()) {\n      vobj.set(js, MESSAGE_STR, js.str(version->getMessage()));\n    }\n    obj.set(js, SCRIPTVERSION_STR, vobj);\n  }\n\n  KJ_SWITCH_ONEOF(onset.info) {\n    KJ_CASE_ONEOF(fetch, FetchEventInfo) {\n      obj.set(js, INFO_STR, ToJs(js, fetch, cache));\n    }\n    KJ_CASE_ONEOF(jsrpc, JsRpcEventInfo) {\n      obj.set(js, INFO_STR, ToJs(js, jsrpc, cache));\n    }\n    KJ_CASE_ONEOF(scheduled, ScheduledEventInfo) {\n      obj.set(js, INFO_STR, ToJs(js, scheduled, cache));\n    }\n    KJ_CASE_ONEOF(alarm, AlarmEventInfo) {\n      obj.set(js, INFO_STR, ToJs(js, alarm, cache));\n    }\n    KJ_CASE_ONEOF(queue, QueueEventInfo) {\n      obj.set(js, INFO_STR, ToJs(js, queue, cache));\n    }\n    KJ_CASE_ONEOF(email, EmailEventInfo) {\n      obj.set(js, INFO_STR, ToJs(js, email, cache));\n    }\n    KJ_CASE_ONEOF(trace, TraceEventInfo) {\n      obj.set(js, INFO_STR, ToJs(js, trace, cache));\n    }\n    KJ_CASE_ONEOF(hws, HibernatableWebSocketEventInfo) {\n      obj.set(js, INFO_STR, ToJs(js, hws, cache));\n    }\n    KJ_CASE_ONEOF(custom, CustomEventInfo) {\n      obj.set(js, INFO_STR, ToJs(js, custom, cache));\n    }\n  }\n\n  if (onset.attributes.size() > 0) {\n    obj.set(js, ATTRIBUTES_STR,\n        js.arr(onset.attributes.asPtr(),\n            [&cache](jsg::Lock& js, const auto& attr) { return ToJs(js, attr, cache); }));\n  }\n\n  return obj;\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const Outcome& outcome, StringCache& cache) {\n  auto obj = js.obj();\n  obj.set(js, TYPE_STR, cache.get(js, OUTCOME_STR));\n  obj.set(js, OUTCOME_STR, ToJs(js, outcome.outcome, cache));\n\n  double cpuTime = outcome.cpuTime / kj::MILLISECONDS;\n  double wallTime = outcome.wallTime / kj::MILLISECONDS;\n\n  obj.set(js, CPUTIME_STR, js.num(cpuTime));\n  obj.set(js, WALLTIME_STR, js.num(wallTime));\n\n  return obj;\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const SpanOpen& spanOpen, StringCache& cache) {\n  auto obj = js.obj();\n  obj.set(js, TYPE_STR, cache.get(js, SPANOPEN_STR));\n  obj.set(js, NAME_STR, js.str(spanOpen.operationName));\n  // Export span ID as non-truncated hex value – in practice this will be a random span ID.\n  obj.set(js, SPANID_STR, js.str(spanOpen.spanId.toGoString()));\n\n  KJ_IF_SOME(info, spanOpen.info) {\n    KJ_SWITCH_ONEOF(info) {\n      KJ_CASE_ONEOF(fetch, FetchEventInfo) {\n        obj.set(js, INFO_STR, ToJs(js, fetch, cache));\n      }\n      KJ_CASE_ONEOF(jsrpc, JsRpcEventInfo) {\n        obj.set(js, INFO_STR, ToJs(js, jsrpc, cache));\n      }\n      KJ_CASE_ONEOF(custom, CustomInfo) {\n        obj.set(js, INFO_STR, ToJs(js, custom.asPtr(), cache));\n      }\n    }\n  }\n  return obj;\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const SpanClose& spanClose, StringCache& cache) {\n  auto obj = js.obj();\n  obj.set(js, TYPE_STR, cache.get(js, SPANCLOSE_STR));\n  obj.set(js, OUTCOME_STR, ToJs(js, spanClose.outcome, cache));\n  return obj;\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const DiagnosticChannelEvent& dce, StringCache& cache) {\n  auto obj = js.obj();\n  obj.set(js, TYPE_STR, cache.get(js, DIAGNOSTICCHANNEL_STR));\n  obj.set(js, CHANNEL_STR, cache.get(js, dce.channel));\n  jsg::Serializer::Released released{\n    .data = kj::heapArray<kj::byte>(dce.message),\n  };\n  jsg::Deserializer deser(js, released);\n  obj.set(js, MESSAGE_STR, deser.readValue(js));\n  return obj;\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const Exception& ex, StringCache& cache) {\n  auto obj = js.obj();\n  obj.set(js, TYPE_STR, cache.get(js, EXCEPTION_STR));\n  obj.set(js, NAME_STR, cache.get(js, ex.name));\n  obj.set(js, MESSAGE_STR, js.str(ex.message));\n  KJ_IF_SOME(stack, ex.stack) {\n    obj.set(js, STACK_STR, js.str(stack));\n  }\n  return obj;\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const LogLevel& level, StringCache& cache) {\n  switch (level) {\n    case LogLevel::DEBUG_:\n      return cache.get(js, DEBUG_STR);\n    case LogLevel::INFO:\n      return cache.get(js, INFO_STR);\n    case LogLevel::LOG:\n      return cache.get(js, LOG_STR);\n    case LogLevel::WARN:\n      return cache.get(js, WARN_STR);\n    case LogLevel::ERROR:\n      return cache.get(js, ERROR_STR);\n  }\n  KJ_UNREACHABLE;\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const Log& log, StringCache& cache) {\n  auto obj = js.obj();\n  obj.set(js, TYPE_STR, cache.get(js, LOG_STR));\n  obj.set(js, LEVEL_STR, ToJs(js, log.logLevel, cache));\n  // TODO(o11y): Check that we are always returning an object here\n  obj.set(js, MESSAGE_STR, jsg::JsValue(js.parseJson(log.message).getHandle(js)));\n  return obj;\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const StreamDiagnosticsEvent& streamDiag, StringCache& cache) {\n  auto obj = js.obj();\n  obj.set(js, TYPE_STR, cache.get(js, STREAMDIAGNOSTIC_STR));\n  // At present we only support the droppedEvents type.\n\n  // Handle droppedEvents\n  auto droppedEventsDiagnostic = js.obj();\n  droppedEventsDiagnostic.set(js, DIAGNOSTICSTYPE_STR, cache.get(js, DROPPEDEVENTS_STR));\n  droppedEventsDiagnostic.set(js, COUNT_STR, js.num(streamDiag.droppedEventsCount));\n  obj.set(js, DIAGNOSTIC_STR, kj::mv(droppedEventsDiagnostic));\n  return obj;\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const Return& ret, StringCache& cache) {\n  auto obj = js.obj();\n  obj.set(js, TYPE_STR, cache.get(js, RETURN_STR));\n\n  KJ_IF_SOME(info, ret.info) {\n    obj.set(js, INFO_STR, ToJs(js, info, cache));\n  }\n\n  return obj;\n}\n\njsg::JsValue ToJs(jsg::Lock& js, const TailEvent& event, StringCache& cache) {\n  auto obj = js.obj();\n\n  // Set SpanContext\n  auto sCObj = js.obj();\n  sCObj.set(js, TRACEID_STR, js.str(event.spanContext.getTraceId().toGoString()));\n  KJ_IF_SOME(spanId, event.spanContext.getSpanId()) {\n    sCObj.set(js, SPANID_STR, js.str(spanId.toGoString()));\n  }\n  obj.set(js, SPANCONTEXT_STR, kj::mv(sCObj));\n\n  obj.set(js, INVOCATIONID_STR, js.str(event.invocationId.toGoString()));\n  obj.set(js, TIMESTAMP_STR, js.date(event.timestamp));\n  obj.set(js, SEQUENCE_STR, js.num(event.sequence));\n\n  KJ_SWITCH_ONEOF(event.event) {\n    KJ_CASE_ONEOF(onset, Onset) {\n      obj.set(js, EVENT_STR, ToJs(js, onset, cache));\n    }\n    KJ_CASE_ONEOF(outcome, Outcome) {\n      obj.set(js, EVENT_STR, ToJs(js, outcome, cache));\n    }\n    KJ_CASE_ONEOF(spanOpen, SpanOpen) {\n      obj.set(js, EVENT_STR, ToJs(js, spanOpen, cache));\n    }\n    KJ_CASE_ONEOF(spanClose, SpanClose) {\n      obj.set(js, EVENT_STR, ToJs(js, spanClose, cache));\n    }\n    KJ_CASE_ONEOF(de, DiagnosticChannelEvent) {\n      obj.set(js, EVENT_STR, ToJs(js, de, cache));\n    }\n    KJ_CASE_ONEOF(ex, Exception) {\n      obj.set(js, EVENT_STR, ToJs(js, ex, cache));\n    }\n    KJ_CASE_ONEOF(log, Log) {\n      obj.set(js, EVENT_STR, ToJs(js, log, cache));\n    }\n    KJ_CASE_ONEOF(diagEvent, StreamDiagnosticsEvent) {\n      obj.set(js, EVENT_STR, ToJs(js, diagEvent, cache));\n    }\n    KJ_CASE_ONEOF(ret, Return) {\n      obj.set(js, EVENT_STR, ToJs(js, ret, cache));\n    }\n    KJ_CASE_ONEOF(attrs, CustomInfo) {\n      obj.set(js, EVENT_STR, ToJs(js, attrs, cache));\n    }\n  }\n\n  return obj;\n}\n\n// Returns the name of the handler function for this type of event.\nkj::Maybe<kj::StringPtr> getHandlerName(const TailEvent& event) {\n  KJ_SWITCH_ONEOF(event.event) {\n    KJ_CASE_ONEOF(_, Onset) {\n      KJ_FAIL_ASSERT(\"Onset event should only be provided to tailStream(), not returned handler\");\n      // return ONSET_STR;\n    }\n    KJ_CASE_ONEOF(_, Outcome) {\n      return OUTCOME_STR;\n    }\n    KJ_CASE_ONEOF(_, SpanOpen) {\n      return SPANOPEN_STR;\n    }\n    KJ_CASE_ONEOF(_, SpanClose) {\n      return SPANCLOSE_STR;\n    }\n    KJ_CASE_ONEOF(_, DiagnosticChannelEvent) {\n      return DIAGNOSTICCHANNEL_STR;\n    }\n    KJ_CASE_ONEOF(_, Exception) {\n      return EXCEPTION_STR;\n    }\n    KJ_CASE_ONEOF(_, Log) {\n      return LOG_STR;\n    }\n    KJ_CASE_ONEOF(_, StreamDiagnosticsEvent) {\n      return STREAMDIAGEVENT_STR;\n    }\n    KJ_CASE_ONEOF(_, Return) {\n      return RETURN_STR;\n    }\n    KJ_CASE_ONEOF(_, CustomInfo) {\n      return ATTRIBUTES_STR;\n    }\n  }\n  return kj::none;\n}\n\nclass TailStreamTarget final: public rpc::TailStreamTarget::Server {\n public:\n  TailStreamTarget(IoContext& ioContext,\n      kj::Maybe<kj::StringPtr> entrypointNamePtr,\n      kj::Maybe<Worker::VersionInfo> versionInfo,\n      Frankenvalue props,\n      kj::Own<kj::PromiseFulfiller<void>> doneFulfiller)\n      : weakIoContext(ioContext.getWeakRef()),\n        entrypointNamePtr(kj::mv(entrypointNamePtr)),\n        versionInfo(kj::mv(versionInfo)),\n        props(kj::mv(props)),\n        doneFulfiller(kj::mv(doneFulfiller)) {}\n\n  KJ_DISALLOW_COPY_AND_MOVE(TailStreamTarget);\n  ~TailStreamTarget() {\n    if (doneFulfiller->isWaiting()) {\n      doneFulfiller->reject(KJ_EXCEPTION(DISCONNECTED, \"Streaming tail session canceled.\"));\n    }\n  }\n\n  kj::Promise<void> report(ReportContext reportContext) override {\n    IoContext& ioContext = KJ_REQUIRE_NONNULL(weakIoContext->tryGet(),\n        \"The destination object for this tail session no longer exists.\", doneReceiving);\n\n    ioContext.getLimitEnforcer().topUpActor();\n\n    auto ownReportContext = capnp::CallContextHook::from(reportContext).addRef();\n    // We need to be able to access the results builder from both the promise below and its\n    // exception handler.\n    auto sharedResults = kj::rc<SharedResults>(reportContext.initResults());\n\n    auto promise = ioContext.run([this, &ioContext, sharedResults = sharedResults.addRef(),\n                                     reportContext, ownReportContext = ownReportContext->addRef()](\n                                     Worker::Lock& lock) mutable -> kj::Promise<void> {\n      auto params = reportContext.getParams();\n      KJ_ASSERT(params.hasEvents(), \"Events are required.\");\n      auto eventReaders = params.getEvents();\n      kj::Array<TailEvent> events = KJ_MAP(reader, eventReaders) { return TailEvent(reader); };\n\n      // If we have not yet received the onset event, the first event in the\n      // received collection must be an Onset event and must be handled separately.\n      // We will only dispatch the remaining events if a handler is returned.\n      auto result = ([&]() -> kj::Promise<void> {\n        KJ_IF_SOME(handler, maybeHandler) {\n          KJ_IF_SOME(h, handler.tryGet()) {\n            auto handle = h.getHandle(lock);\n            return handleEvents(lock, handle, ioContext, kj::mv(events), kj::mv(sharedResults));\n          } else {\n            KJ_LOG(ERROR, \"tail stream handler was destroyed while processing events\");\n            JSG_FAIL_REQUIRE(Error, \"Tail stream handler became invalid during event processing\");\n            KJ_UNREACHABLE;\n          }\n        } else {\n          return handleOnset(lock, ioContext, kj::mv(events), kj::mv(sharedResults));\n        }\n      })();\n\n      if (ioContext.hasOutputGate()) {\n        return result.then([weakIoContext = weakIoContext->addRef()]() mutable {\n          return KJ_REQUIRE_NONNULL(weakIoContext->tryGet()).waitForOutputLocks();\n        });\n      } else {\n        return kj::mv(result);\n      }\n    });\n\n    auto paf = kj::newPromiseAndFulfiller<void>();\n    promise = promise.then([&fulfiller = *paf.fulfiller]() { fulfiller.fulfill(); },\n        [&, &fulfiller = *paf.fulfiller, ownReportContext = kj::mv(ownReportContext),\n            results = kj::mv(sharedResults)](kj::Exception&& e) mutable {\n      // This is the top level exception catcher for tail events being delivered. We do not want to\n      // propagate JS exceptions to the client side here, all exceptions should stay within this\n      // customEvent. Instead, we propagate the exception to the doneFulfiller, where it is used to\n      // set the right outcome code and re-thrown if appropriate. By rejecting the doneFulfiller, we\n      // also ensure that no more tail events get delivered.\n      if (jsg::isTunneledException(e.getDescription())) {\n        auto description = jsg::stripRemoteExceptionPrefix(e.getDescription());\n        if (!description.startsWith(\"remote.\")) {\n          e.setDescription(kj::str(\"remote.\", description));\n        }\n      }\n      // We still fulfill this fulfiller to disarm the cancellation check below\n      fulfiller.fulfill();\n      results->setStop(true);\n      doneReceiving = true;\n      doneFulfiller->reject(kj::mv(e));\n    });\n    promise = promise.attach(kj::defer([fulfiller = kj::mv(paf.fulfiller)]() mutable {\n      if (fulfiller->isWaiting()) {\n        fulfiller->reject(JSG_KJ_EXCEPTION(FAILED, Error,\n            \"The destination execution context for this tail session was canceled while the \"\n            \"call was still running.\"));\n      }\n    }));\n    ioContext.addTask(kj::mv(promise));\n\n    return kj::mv(paf.promise);\n  }\n\n private:\n  // Used to share the results builder (and send the stop signal) from both the main code path and\n  // the exception handler.\n  struct SharedResults: public kj::Refcounted, rpc::TailStreamTarget::TailStreamResults::Builder {\n    SharedResults(rpc::TailStreamTarget::TailStreamResults::Builder results)\n        : rpc::TailStreamTarget::TailStreamResults::Builder(kj::mv(results)) {}\n  };\n  // Handles the very first (onset) event in the tail stream. This will cause\n  // the exported tailStream handler to be called, passing the onset event\n  // as the initial argument. If the tail stream wishes to continue receiving\n  // events for this invocation, it will return a handler in the form of an\n  // object or a function. If no handler is returned, the tail session is\n  // shutdown.\n  kj::Promise<void> handleOnset(Worker::Lock& lock,\n      IoContext& ioContext,\n      kj::Array<TailEvent> events,\n      kj::Rc<SharedResults> results) {\n    // There should be only a single onset event in this batch.\n    KJ_ASSERT(\n        events.size() == 1 && events[0].event.is<Onset>(), \"Expected only a single onset event\");\n    auto& event = events[0];\n\n    auto handler = KJ_REQUIRE_NONNULL(lock.getExportedHandler(entrypointNamePtr,\n                                          kj::mv(versionInfo), kj::mv(props), ioContext.getActor()),\n        \"Failed to get handler to worker.\");\n    StringCache stringCache;\n\n    jsg::Lock& js = lock;\n    auto target = jsg::JsObject(handler->self.getHandle(js));\n    v8::Local<v8::Value> maybeFn = target.get(js, \"tailStream\"_kj);\n\n    // If there's no actual tailStream handler, or if the tailStream export is\n    // something other than a function, we will emit a warning for the user\n    // then immediately return.\n    if (!maybeFn->IsFunction()) {\n      ioContext.logWarningOnce(\"A worker configured to act as a streaming tail worker does \"\n                               \"not export a tailStream() handler.\");\n      results->setStop(true);\n      doneReceiving = true;\n      doneFulfiller->fulfill();\n      return kj::READY_NOW;\n    }\n\n    // Invoke the tailStream handler function.\n    v8::Local<v8::Function> fn = maybeFn.As<v8::Function>();\n    kj::Maybe<v8::Local<v8::Object>> maybeCtx;\n    KJ_IF_SOME(hCtx, handler->getCtx()) {\n      maybeCtx = v8::Local<v8::Object>(\n          lock.getWorker().getIsolate().getApi().wrapExecutionContext(js, kj::mv(hCtx)));\n    }\n    v8::LocalVector<v8::Value> handlerArgs(js.v8Isolate, maybeCtx != kj::none ? 3 : 2);\n    handlerArgs[0] = ToJs(js, event, stringCache);\n    handlerArgs[1] = handler->env.getHandle(js);\n    KJ_IF_SOME(ctx, maybeCtx) {\n      handlerArgs[2] = ctx;\n    }\n\n    try {\n      auto result =\n          jsg::check(fn->Call(js.v8Context(), target, handlerArgs.size(), handlerArgs.data()));\n\n      // The handler can return a function, an object, undefined, or a promise\n      // for any of these. We will convert the result to a promise for consistent\n      // handling...\n      return ioContext.awaitJs(js,\n          js.toPromise(result).then(js,\n              ioContext.addFunctor([this, results = results.addRef(), &ioContext](\n                                       jsg::Lock& js, jsg::Value value) mutable {\n        // The value here can be one of a function, an object, or undefined.\n        // Any value other than these will result in a warning but will otherwise\n        // be treated like undefined.\n\n        // If a function or object is returned, then our tail worker wishes to\n        // keep receiving events! Yay! Otherwise, we will stop the stream by\n        // setting the stop field in the results.\n        auto handle = value.getHandle(js);\n        if (handle->IsFunction() || handle->IsObject()) {\n          // Sweet! Our tail worker wants to keep receiving events. Let's store\n          // the handler and return.\n          maybeHandler = ioContext.addObjectReverse(\n              kj::heap<jsg::JsRef<jsg::JsValue>>(js, jsg::JsValue(handle)));\n          return;\n        }\n\n        // If the handler returned any other kind of value, let's be nice and\n        // at least warn the user about it.\n        if (!handle->IsUndefined()) {\n          ioContext.logWarningOnce(\n              kj::str(\"tailStream() handler returned an unusable value. \"\n                      \"The tailStream() handler is expected to return either a function, an \"\n                      \"object, or undefined. Received \",\n                  jsg::JsValue(handle).typeOf(js)));\n        }\n        // And finally, we'll stop the stream since the tail worker did not return\n        // a handler for us to continue with.\n        results->setStop(true);\n        doneReceiving = true;\n        doneFulfiller->fulfill();\n      }),\n              ioContext.addFunctor(\n                  [&, results = results.addRef()](jsg::Lock& js, jsg::Value&& error) mutable {\n        // Received a JS error. Do not reject doneFulfiller yet, this will be handled when we catch\n        // the exception later.\n        results->setStop(true);\n        doneReceiving = true;\n        js.throwException(kj::mv(error));\n      })));\n    } catch (...) {\n      ioContext.logWarningOnce(\"A worker configured to act as a streaming tail worker did \"\n                               \"not return a valid tailStream() handler.\");\n      results->setStop(true);\n      doneReceiving = true;\n      doneFulfiller->fulfill();\n      return kj::READY_NOW;\n    }\n    KJ_UNREACHABLE;\n  }\n\n  kj::Promise<void> handleEvents(Worker::Lock& lock,\n      const jsg::JsValue& handler,\n      IoContext& ioContext,\n      kj::Array<TailEvent> events,\n      kj::Rc<SharedResults> results) {\n    jsg::Lock& js = lock;\n\n    // Should not ever happen but let's handle it anyway.\n    if (events.size() == 0) return kj::READY_NOW;\n\n    // Take the received set of events and dispatch them to the correct handler.\n\n    v8::Local<v8::Value> h = handler;\n    v8::LocalVector<v8::Value> returnValues(js.v8Isolate);\n    StringCache stringCache;\n\n    // If any of the events delivered are an outcome event, we will signal that\n    // the stream should be stopped and will fulfill the done promise.\n    bool finishing = false;\n\n    // When a tail worker receives its outcome event, we need to ensure that the final tail worker\n    // invocation is completed before destroying the tail worker customEvent and incomingRequest. To\n    // achieve this, we only fulfill the doneFulfiller after JS execution has completed.\n    bool doFulfill = false;\n\n    for (auto& event: events) {\n      // If we already received an outcome event, we will stop processing any\n      // further events.\n      if (finishing) break;\n      if (event.event.is<Outcome>()) {\n        finishing = true;\n        results->setStop(true);\n        doneReceiving = true;\n        // We set doFulfill to indicate that the outcome event has been received via RPC and no more\n        // events are expected.\n        doFulfill = true;\n      };\n\n      v8::Local<v8::Value> eventObj = ToJs(js, event, stringCache);\n      if (h->IsFunction()) {\n        // If the handler is a function, then we'll just pass all of the events to that\n        // function. If the function returns a promise and there are multiple events we\n        // will not wait for each promise to resolve before calling the next iteration.\n        // But we will wait for all promises to settle before returning the resolved\n        // kj promise.\n        auto fn = h.As<v8::Function>();\n        returnValues.push_back(jsg::check(fn->Call(js.v8Context(), h, 1, &eventObj)));\n      } else {\n        // If the handler is an object, then we need to know what kind of events\n        // we have and look for a specific handler function for each.\n        KJ_ASSERT(h->IsObject());\n        KJ_IF_SOME(name, getHandlerName(event)) {\n          jsg::JsObject obj = jsg::JsObject(h.As<v8::Object>());\n          v8::Local<v8::Value> val = obj.get(js, name);\n          // If the value is not a function, we'll ignore it entirely.\n          if (val->IsFunction()) {\n            auto fn = val.As<v8::Function>();\n            returnValues.push_back(jsg::check(fn->Call(js.v8Context(), h, 1, &eventObj)));\n          }\n        }\n      }\n    }\n    // We want the equivalent behavior to Promise.all([...]) here but v8 does not\n    // give us a C++ equivalent of Promise.all([...]) so we need to approximate it.\n    // We do so by chaining all of the promises together.\n    kj::Maybe<jsg::Promise<void>> promise;\n    for (auto& val: returnValues) {\n      KJ_IF_SOME(p, promise) {\n        promise = p.then(js,\n            [p = js.toPromise(val).whenResolved(js)](jsg::Lock& js) mutable { return kj::mv(p); });\n      } else {\n        promise = js.toPromise(val).whenResolved(js);\n      }\n    }\n\n    KJ_IF_SOME(p, promise) {\n      // When doFulfill is set, the last promise refers to the outcome event. In that case the chain\n      // of promises provides all remaining events to the user tail handler, so we should fulfill\n      // the doneFulfiller afterwards, indicating that TailStreamTarget has received all events over\n      // the stream and has done all its work, that the stream self-evidently did not get canceled\n      // prematurely. This applies even if promises were rejected.\n      // No need to catch exceptions here: They will be handled in report() alongside exceptions\n      // from the onset event etc. JSG knows how JS exceptions look like, so we don't need an\n      // identifier for them.\n      if (doFulfill) {\n        p = p.then(js, [&](jsg::Lock& js) {\n          doneReceiving = true;\n          doneFulfiller->fulfill();\n        });\n      }\n      return ioContext.awaitJs(js, kj::mv(p));\n    }\n    return kj::READY_NOW;\n  }\n\n  kj::Own<IoContext::WeakRef> weakIoContext;\n  kj::Maybe<kj::StringPtr> entrypointNamePtr;\n  kj::Maybe<Worker::VersionInfo> versionInfo;\n  Frankenvalue props;\n  // The done fulfiller is resolved when we receive the outcome event\n  // or rejected if the capability is dropped before receiving the outcome\n  // event.\n  kj::Own<kj::PromiseFulfiller<void>> doneFulfiller;\n\n  // The maybeHandler will be empty until we receive and process the\n  // onset event.\n  kj::Maybe<ReverseIoOwn<jsg::JsRef<jsg::JsValue>>> maybeHandler;\n\n  // Indicates that we told (or should have told) the client that we want no further events, used\n  // to debug events arriving when the IoContext is no longer valid.\n  bool doneReceiving = false;\n};\n}  // namespace\n\nEventInfo TailStreamCustomEvent::getEventInfo() const {\n  return TraceEventInfo(kj::Array<TraceEventInfo::TraceItem>(nullptr));\n}\n\nkj::Promise<WorkerInterface::CustomEvent::Result> TailStreamCustomEvent::run(\n    kj::Own<IoContext::IncomingRequest> incomingRequest,\n    kj::Maybe<kj::StringPtr> entrypointName,\n    kj::Maybe<Worker::VersionInfo> versionInfo,\n    Frankenvalue props,\n    kj::TaskSet& waitUntilTasks) {\n  IoContext& ioContext = incomingRequest->getContext();\n  incomingRequest->delivered();\n\n  auto [donePromise, doneFulfiller] = kj::newPromiseAndFulfiller<void>();\n  capFulfiller->fulfill(kj::heap<TailStreamTarget>(ioContext, kj::mv(entrypointName),\n      kj::mv(versionInfo), kj::mv(props), kj::mv(doneFulfiller)));\n\n  donePromise = donePromise.attach(ioContext.registerPendingEvent());\n\n  KJ_DEFER({\n    // waitUntil() should allow extending execution on the server side even when the client\n    // disconnects.\n    waitUntilTasks.add(incomingRequest->drain().attach(kj::mv(incomingRequest)));\n  });\n\n  auto eventOutcome = co_await donePromise.exclusiveJoin(ioContext.onAbort()).then([&]() {\n    return ioContext.waitUntilStatus();\n  }, [&incomingRequest](kj::Exception&& e) {\n    // If we have a JSG exception, just set the appropriate return code – this will already have\n    // been logged and we do not need to treat it like a KJ exception. Otherwise, re-throw the\n    // exception.\n    if (jsg::isTunneledException(e.getDescription())) {\n      incomingRequest->getMetrics().reportFailure(e);\n      return EventOutcome::EXCEPTION;\n    }\n    kj::throwRecoverableException(kj::mv(e));\n    KJ_UNREACHABLE;\n  });\n  KJ_IF_SOME(t, ioContext.getWorkerTracer()) {\n    t.setReturn(ioContext.now());\n  }\n\n  co_return WorkerInterface::CustomEvent::Result{.outcome = eventOutcome};\n}\n\nkj::Promise<WorkerInterface::CustomEvent::Result> TailStreamCustomEvent::sendRpc(\n    capnp::HttpOverCapnpFactory& httpOverCapnpFactory,\n    capnp::ByteStreamFactory& byteStreamFactory,\n    rpc::EventDispatcher::Client dispatcher) {\n  auto revokePaf = kj::newPromiseAndFulfiller<void>();\n\n  KJ_DEFER({\n    if (revokePaf.fulfiller->isWaiting()) {\n      revokePaf.fulfiller->reject(KJ_EXCEPTION(DISCONNECTED, \"Streaming tail session canceled\"));\n    }\n  });\n\n  auto req = dispatcher.tailStreamSessionRequest();\n  auto sent = req.send();\n\n  rpc::TailStreamTarget::Client cap = sent.getTopLevel();\n\n  cap = capnp::membrane(kj::mv(cap), kj::refcounted<RevokerMembrane>(kj::mv(revokePaf.promise)));\n\n  auto completionPaf = kj::newPromiseAndFulfiller<void>();\n  cap = capnp::membrane(\n      kj::mv(cap), kj::refcounted<CompletionMembrane>(kj::mv(completionPaf.fulfiller)));\n\n  capFulfiller->fulfill(kj::mv(cap));\n\n  // Forked promise for completion of all capabilities associated with the cap stream. This is\n  // expected to be resolved when the request is canceled or when the client receives the stop\n  // signal and deallocates cap after the tail worker indicates that it has processed all events\n  // successfully.\n  kj::ForkedPromise<void> forked = completionPaf.promise.fork();\n  try {\n    EventOutcome outcome = co_await sent.then([](auto resp) {\n      return resp.getResult();\n    }).exclusiveJoin(forked.addBranch().then([]() { return EventOutcome::CANCELED; }));\n\n    // If the sent promise returned first, we still need to wait for the parent process to drop the\n    // capability (which should happen right after it receives the stop signal) so that no\n    // capabilities remain in an incomplete state when we return.\n    co_await forked.addBranch();\n    co_return WorkerInterface::CustomEvent::Result{.outcome = outcome};\n  } catch (...) {\n    auto e = kj::getCaughtExceptionAsKj();\n    if (revokePaf.fulfiller->isWaiting()) {\n      revokePaf.fulfiller->reject(kj::cp(e));\n    }\n    kj::throwFatalException(kj::mv(e));\n  }\n}\n\nTailStreamWriter::TailStreamWriter(Pending pending, kj::TaskSet& waitUntilTasks)\n    : inner(kj::mv(pending)),\n      waitUntilTasks(waitUntilTasks) {}\n\nbool TailStreamWriter::reportImpl(TailEvent&& event, size_t sizeHint) {\n  // In reportImpl, our inner state must be active.\n  auto& actives = KJ_ASSERT_NONNULL(inner.tryGet<kj::Vector<kj::Own<Active>>>());\n\n  // We only care about sessions that are currently active, removing any inactive ones.\n  auto activeEnd = std::remove_if(actives.begin(), actives.end(),\n      [](const auto& active) { return active->capability == kj::none; });\n  if (activeEnd == actives.begin()) {\n    // Oh! We have no active sessions. Well, never mind then, let's\n    // transition to a closed state and drop everything on the floor.\n    inner = Closed{};\n\n    // Since we have no more living sessions (e.g. because all tail workers failed to return a valid\n    // handler), mark the state as closing as we can't handle future events anyway.\n    return true;\n  }\n\n  // We have at least some active sessions. Truncate the array to get rid of any inactive ones.\n  if (activeEnd != actives.end()) {\n    actives.truncate(activeEnd - actives.begin());\n  }\n\n  // We do not expect any events after the outcome.\n  bool isClosing = event.event.is<Outcome>();\n  // Deliver the event to the queue and make sure we are processing.\n  for (auto& active: actives) {\n    // Only queue the event if we don't have an excessive queue size yet. Return and Outcome\n    // events are only provided once and thus won't be dropped.\n    if (active->queueSize < maxQueueSize || event.event.is<Outcome>() || event.event.is<Return>()) {\n      // When we get to the outcome, no more events will be dropped. Inject an internal diagnostics\n      // event indicating how many events were dropped if applicable.\n      if (event.event.is<Outcome>() && active->droppedEvents > 0) {\n        StreamDiagnosticsEvent diag(active->droppedEvents);\n        TailEvent diagTailEvent(event.spanContext.clone(), event.invocationId, event.timestamp,\n            event.sequence, kj::mv(diag));\n        active->queue.push(kj::mv(diagTailEvent));\n        // Increment the outcome sequence number to keep things consistent.\n        event.sequence++;\n      }\n\n      // Optimization: Elide copy for last tail worker, helpful for common case of only one STW\n      // being present.\n      if (&active == &actives.back()) {\n        active->queue.push(kj::mv(event));\n      } else {\n        active->queue.push(event.clone());\n      }\n      // Adjust estimated queue size based on size hint and an arbitrary amount for serialization\n      // overhead. As long as this estimate is reasonably accurate, we won't need to check the\n      // size again when serializing the message.\n      active->queueSize += tailSerializationOverhead + sizeHint;\n    } else {\n      active->droppedEvents++;\n    }\n\n    if (!active->pumping) {\n      waitUntilTasks.add(pump(kj::addRef(*active)));\n    }\n  }\n\n  return isClosing;\n}\n\n// Delivers the queued tail events to a streaming tail worker.\n//\n// Note: An invocation of pump() may outlive the TailStreamWriter, as it is placed in\n//   `waitUntilTasks`. Hence, it is declared `static`, and owns a strong ref to its `Active`.\nkj::Promise<void> TailStreamWriter::pump(kj::Own<Active> current) {\n  current->pumping = true;\n  KJ_DEFER(current->pumping = false);\n\n  try {\n    if (!current->onsetSeen) {\n      // Our first event... yay! Our first job here will be to dispatch\n      // the onset event to the tail worker. If the tail worker wishes\n      // to handle the remaining events in the stream, then it will return\n      // a new capability to which those would be reported. This is done\n      // via the \"result.getPipeline()\" API below. If hasPipeline()\n      // returns false then that means the tail worker did not return\n      // a handler for this stream and no further attempts to deliver\n      // events should be made for this stream.\n      current->onsetSeen = true;\n      auto onsetEvent = KJ_ASSERT_NONNULL(current->queue.pop());\n      auto builder = KJ_ASSERT_NONNULL(current->capability).reportRequest();\n      auto eventsBuilder = builder.initEvents(1);\n      // When sending the onset event to the tail worker, the receiving end\n      // requires that the onset event be delivered separately, without any\n      // other events in the bundle. So here we'll separate it out and deliver\n      // just the one event...\n      onsetEvent.copyTo(eventsBuilder[0]);\n      auto result = co_await builder.send();\n      if (result.getStop()) {\n        // If our call to send returns a stop signal, then we'll clear\n        // the capability and be done.\n        current->queue.clear();\n        current->capability = kj::none;\n        co_return;\n      }\n    }\n\n    // If we got this far then we have a handler for all of our events.\n    // Deliver remaining streaming tail events in batches if possible.\n    while (!current->queue.empty()) {\n      auto builder = KJ_ASSERT_NONNULL(current->capability).reportRequest();\n      auto eventsBuilder = builder.initEvents(current->queue.size());\n      size_t n = 0;\n\n      // We're synchronously draining the queue – reset its size.\n      current->queueSize = 0;\n      current->queue.drainTo([&](TailEvent&& event) { event.copyTo(eventsBuilder[n++]); });\n\n      auto result = co_await builder.send();\n\n      // Note that although we cleared the current.queue above, it is\n      // possible/likely that additional events were added to the queue\n      // while the above builder.send() was being awaited. If the result\n      // comes back indicating that we should stop, then we'll stop here\n      // without any further processing. We'll defensively clear the\n      // queue again and drop the client stub. Otherwise, if result.getStop()\n      // is false, we'll loop back around to send any items that have since\n      // been added to the queue or exit this loop if there are no additional\n      // events waiting to be sent.\n      if (result.getStop()) {\n        current->queue.clear();\n        current->capability = kj::none;\n        co_return;\n      }\n    }\n  } catch (...) {\n    // If any RPC throws an exception, we should treat it as a stop signal, as this suggests\n    // the connection to the STW itself has been lost. (An excpetion thrown within the STW\n    // itself would have resulted in a `stop` return value instead of an exception over RPC.)\n    current->queue.clear();\n    current->capability = kj::none;\n    throw;\n  }\n}\n\n// If we are using streaming tail workers, initialize the mechanism that will deliver events\n// to that collection of tail workers.\nkj::Maybe<kj::Own<TailStreamWriter>> initializeTailStreamWriter(\n    kj::Array<kj::Own<WorkerInterface>> streamingTailWorkers, kj::TaskSet& waitUntilTasks) {\n  if (streamingTailWorkers.size() == 0) {\n    return kj::none;\n  }\n\n  return kj::heap<TailStreamWriter>(kj::mv(streamingTailWorkers), waitUntilTasks);\n}\n\nvoid TailStreamWriter::report(const InvocationSpanContext& context,\n    TailEvent::Event&& event,\n    kj::Date timestamp,\n    size_t sizeHint) {\n  // Becomes a no-op if a terminal event (close) has been reported, or if the stream closed due to\n  // not receiving a well-formed event handler. We need to disambiguate these cases as the former\n  // indicates an implementation error resulting in trailing events whereas the latter case is\n  // caused by a user error and events being reported after the stream being closed are expected –\n  // reject events following an outcome event, but otherwise just exit if the state has been closed.\n  // This could be an assert, but just log an error in case this is prevalent in some edge case.\n  if (outcomeSeen) {\n    KJ_LOG(ERROR, \"reported tail stream event after stream close \", event, kj::getStackTrace());\n  }\n  if (inner.is<Closed>()) {\n    return;\n  }\n  // The onset event must be first and must only happen once.\n  if (event.is<Onset>()) {\n    KJ_ASSERT(!onsetSeen, \"Tail stream onset already provided\");\n    onsetSeen = true;\n  } else {\n    KJ_ASSERT(onsetSeen, \"Tail stream onset was not reported\");\n    if (event.is<Outcome>()) {\n      outcomeSeen = true;\n    }\n  }\n\n  // A zero spanId at the TailEvent level signifies that no spanId should be provided to the tail\n  // worker (for Onset events). We go to great lengths to rule out getting an all-zero spanId by\n  // chance (see SpanId::fromEntropy()), so this should be safe.\n  TailEvent tailEvent(context.getTraceId(), context.getInvocationId(),\n      context.getSpanId() == SpanId::nullId ? kj::none : kj::Maybe(context.getSpanId()), timestamp,\n      sequence++, kj::mv(event));\n\n  KJ_SWITCH_ONEOF(inner) {\n    KJ_CASE_ONEOF(closed, Closed) {\n      // The tail stream has already been closed because we have received an outcome event. The\n      // writer should have failed and we actually shouldn't get here. Assert!\n      KJ_FAIL_ASSERT(\"tracing::TailStreamWriter report callback invoked after close\");\n    }\n    KJ_CASE_ONEOF(pending, Pending) {\n      // This is our first event! It has to be an onset event as we have validated above. Start each\n      // of our tail working sessions.\n\n      // Transitions into the active state by grabbing the pending client capability.\n      inner = kj::Vector<kj::Own<Active>>( KJ_MAP(wi, pending) {\n        auto customEvent = kj::heap<TailStreamCustomEvent>();\n        auto result = customEvent->getCap();\n        auto active = kj::refcounted<Active>(kj::mv(result));\n\n        // Attach the workerInterface and customEvent to the waitUntil tasks so that they stay alive\n        // until tail worker operations including JS execution are complete, including returning the\n        // outcome.\n        waitUntilTasks.add(wi->customEvent(kj::mv(customEvent))\n                               .attach(kj::mv(wi), kj::addRef(*active))\n                               .ignoreResult());\n        return active;\n      });\n\n      // At this point our writer state is \"active\", which means the state consists of one or more\n      // streaming tail worker client stubs to which the event will be dispatched.\n    }\n    KJ_CASE_ONEOF(active, kj::Vector<kj::Own<Active>>) {\n      // active tail stream writers have already been configured, process the event.\n    }\n  }\n\n  // The state is determined to be closing when it receives a terminal event (tracing::Outcome),\n  // or if there are no active tail workers left, we can close the internal state at that point.\n  if (reportImpl(kj::mv(tailEvent), sizeHint)) {\n    inner = Closed{};\n  }\n}\n\n}  // namespace workerd::tracing\n"
  },
  {
    "path": "src/workerd/io/trace-stream.h",
    "content": "#pragma once\n\n#include <workerd/io/io-context.h>\n#include <workerd/io/trace.h>\n#include <workerd/io/tracer.h>\n#include <workerd/io/worker-interface.h>\n#include <workerd/util/checked-queue.h>\n\nnamespace workerd::tracing {\n\n// A WorkerInterface::CustomEvent implementation used to deliver streaming tail\n// events to a tail worker.\nclass TailStreamCustomEvent final: public WorkerInterface::CustomEvent {\n public:\n  TailStreamCustomEvent(uint16_t typeId = TYPE,\n      kj::PromiseFulfillerPair<rpc::TailStreamTarget::Client> paf =\n          kj::newPromiseAndFulfiller<rpc::TailStreamTarget::Client>())\n      : capFulfiller(kj::mv(paf.fulfiller)),\n        clientCap(kj::mv(paf.promise)),\n        typeId(typeId) {}\n\n  ~TailStreamCustomEvent() noexcept(false) {\n    if (capFulfiller->isWaiting()) {\n      capFulfiller->reject(\n          KJ_EXCEPTION(DISCONNECTED, \"TailStreamCustomEvent was destroyed before completion\"));\n    }\n  }\n\n  kj::Promise<Result> run(kj::Own<IoContext::IncomingRequest> incomingRequest,\n      kj::Maybe<kj::StringPtr> entrypointName,\n      kj::Maybe<Worker::VersionInfo> versionInfo,\n      Frankenvalue props,\n      kj::TaskSet& waitUntilTasks) override;\n\n  kj::Promise<Result> sendRpc(capnp::HttpOverCapnpFactory& httpOverCapnpFactory,\n      capnp::ByteStreamFactory& byteStreamFactory,\n      rpc::EventDispatcher::Client dispatcher) override;\n\n  kj::Promise<Result> notSupported() override {\n    JSG_FAIL_REQUIRE(TypeError, \"The receiver is not a tail stream\");\n  }\n\n  uint16_t getType() override {\n    return typeId;\n  }\n\n  tracing::EventInfo getEventInfo() const override;\n\n  void failed(const kj::Exception& e) override {\n    capFulfiller->reject(kj::cp(e));\n  }\n\n  // Specify same type as with TraceCustomEvent here by default.\n  static constexpr uint16_t TYPE = 2;\n\n  rpc::TailStreamTarget::Client getCap() {\n    auto result = kj::mv(KJ_ASSERT_NONNULL(clientCap, \"can only call getCap() once\"));\n    clientCap = kj::none;\n    return result;\n  }\n\n private:\n  kj::Own<kj::PromiseFulfiller<workerd::rpc::TailStreamTarget::Client>> capFulfiller;\n  kj::Maybe<rpc::TailStreamTarget::Client> clientCap;\n  uint16_t typeId;\n};\n\n// A utility class that receives tracing events and generates/reports TailEvents.\nclass TailStreamWriter final {\n public:\n  // The maximum size of the queue, in bytes.\n  const size_t maxQueueSize = 2 * 1024 * 1024;\n  // The estimated overhead of TailEvent wrapping per message. This does not need to be very\n  // accurate, but should be enough to avoid allocating too much memory/hitting capnp RPC message\n  // size limits when sending many tiny events.\n  const size_t tailSerializationOverhead = 64;\n\n  // The initial state of our tail worker writer is that it is pending the first onset event. During\n  // this time we will only have a collection of WorkerInterface instances. When our first event is\n  // reported (the onset) we will arrange to acquire tailStream capabilities from each then use\n  // those to report the initial onset.\n  using Pending = kj::Array<kj::Own<WorkerInterface>>;\n  TailStreamWriter(Pending pending, kj::TaskSet& waitUntilTasks);\n  KJ_DISALLOW_COPY_AND_MOVE(TailStreamWriter);\n\n  void report(const InvocationSpanContext& context,\n      TailEvent::Event&& event,\n      kj::Date time,\n      size_t sizeHint);\n\n private:\n  // Instances of Active are refcounted. The TailStreamWriter itself holds the initial ref. Whenever\n  // events are being dispatched, an additional ref will be held by the outstanding pump promise in\n  // order to keep the client stub alive long enough for the rpc calls to complete. It is possible\n  // that the TailStreamWriter will be dropped while pump promises are still pending.\n  struct Active: public kj::Refcounted {\n    // Reference to keep the worker interface instance alive.\n    kj::Maybe<rpc::TailStreamTarget::Client> capability;\n    bool pumping = false;\n    bool onsetSeen = false;\n    // Estimated byte size of the queue, used to drop events to avoid excessive memory usage.\n    size_t queueSize = 0;\n    // The number of tail events we had to drop. We'll send a warning indicating this at the end of\n    // the stream.\n    uint32_t droppedEvents = 0;\n    workerd::util::Queue<TailEvent> queue;\n\n    Active(rpc::TailStreamTarget::Client capability): capability(kj::mv(capability)) {}\n  };\n\n  struct Closed {};\n\n  kj::OneOf<Pending, kj::Vector<kj::Own<Active>>, Closed> inner;\n  kj::TaskSet& waitUntilTasks;\n\n  static kj::Promise<void> pump(kj::Own<Active> current);\n  // Report an event to the tail stream writer.\n  // sizeHint: The approximate size of the event, in bytes.\n  bool reportImpl(TailEvent&& event, size_t sizeHint);\n\n  uint32_t sequence = 0;\n  bool onsetSeen = false;\n  bool outcomeSeen = false;\n};\n\nkj::Maybe<kj::Own<tracing::TailStreamWriter>> initializeTailStreamWriter(\n    kj::Array<kj::Own<WorkerInterface>> streamingTailWorkers, kj::TaskSet& waitUntilTasks);\n\n}  // namespace workerd::tracing\n"
  },
  {
    "path": "src/workerd/io/trace-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/io/trace.h>\n#include <workerd/util/thread-scopes.h>\n\n#include <capnp/message.h>\n#include <kj/compat/http.h>\n#include <kj/test.h>\n\nnamespace workerd::tracing {\nnamespace {\n\nclass FakeEntropySource final: public kj::EntropySource {\n public:\n  void generate(kj::ArrayPtr<byte> buffer) override {\n    // Write the uint64_t value to the buffer\n    buffer[0] = counter & 0xff;\n    buffer[1] = (counter >> 8) & 0xff;\n    buffer[2] = (counter >> 16) & 0xff;\n    buffer[3] = (counter >> 24) & 0xff;\n    buffer[4] = (counter >> 32) & 0xff;\n    buffer[5] = (counter >> 40) & 0xff;\n    buffer[6] = (counter >> 48) & 0xff;\n    buffer[7] = (counter >> 56) & 0xff;\n    counter++;\n  }\n\n private:\n  uint64_t counter = 0;\n};\n\nKJ_TEST(\"can read trace ID string format\") {\n  KJ_EXPECT(TraceId::fromGoString(\"z\"_kj) == kj::none);\n\n  KJ_EXPECT(TraceId::fromGoString(\"fedcba9876543210z\"_kj) == kj::none);\n\n  // Go parser supports non-(64 or 128) bit lengths -- unclear if anything cares.\n  KJ_EXPECT(TraceId(0, 0) == KJ_ASSERT_NONNULL(TraceId::fromGoString(\"\"_kj)));\n  KJ_EXPECT(TraceId(0x1, 0) == KJ_ASSERT_NONNULL(TraceId::fromGoString(\"1\"_kj)));\n\n  KJ_EXPECT(TraceId(0xfedcba9876543210, 0) ==\n      KJ_ASSERT_NONNULL(TraceId::fromGoString(\"fedcba9876543210\"_kj)));\n  KJ_EXPECT(TraceId(0xfedcba9876543210, 0) ==\n      KJ_ASSERT_NONNULL(TraceId::fromGoString(\"FEDCBA9876543210\"_kj)));\n\n  KJ_EXPECT(TraceId(0xfedcba9876543210, 0x1) ==\n      KJ_ASSERT_NONNULL(TraceId::fromGoString(\"01fedcba9876543210\"_kj)));\n\n  KJ_EXPECT(TraceId(0xfedcba9876543211, 0xfedcba9876543212) ==\n      KJ_ASSERT_NONNULL(TraceId::fromGoString(\"fedcba9876543212fedcba9876543211\"_kj)));\n\n  KJ_EXPECT(TraceId::fromGoString(\"01fedcba9876543212fedcba9876543211\"_kj) == kj::none);\n}\n\nKJ_TEST(\"can write trace ID string format\") {\n  KJ_EXPECT(TraceId(0x1, 0).toGoString() == \"0000000000000001\"_kj);\n  KJ_EXPECT(TraceId(0xfedcba9876543210, 0).toGoString() == \"fedcba9876543210\"_kj);\n  KJ_EXPECT(TraceId(0xfedcba9876543210, 0x1).toGoString() == \"0000000000000001fedcba9876543210\"_kj);\n\n  KJ_EXPECT(TraceId(0xfedcba9876543211, 0xfedcba9876543212).toGoString() ==\n      \"fedcba9876543212fedcba9876543211\"_kj);\n}\n\nKJ_TEST(\"can read trace ID protobuf format\") {\n  KJ_EXPECT(TraceId::fromProtobuf(\"\"_kjb) == kj::none);\n  KJ_EXPECT(TraceId::fromProtobuf(\"z\"_kjb) == kj::none);\n  KJ_EXPECT(TraceId::fromProtobuf(\"\\xfe\\xdc\\xba\\x98\\x76\\x54\\x32\\x12\\xfe\"_kjb) == kj::none);\n  KJ_EXPECT(\n      TraceId::fromProtobuf(\n          \"\\xfe\\xdc\\xba\\x98\\x76\\x54\\x32\\x12\\xfe\\xdc\\xba\\x98\\x76\\x54\\x32\\x11\\x01\"_kjb) == kj::none);\n\n  KJ_EXPECT(KJ_ASSERT_NONNULL(TraceId::fromProtobuf(\n                \"\\xfe\\xdc\\xba\\x98\\x76\\x54\\x32\\x12\\xfe\\xdc\\xba\\x98\\x76\\x54\\x32\\x11\"_kjb)) ==\n      TraceId(0xfedcba9876543211, 0xfedcba9876543212));\n}\n\nKJ_TEST(\"can write trace ID protobuf format\") {\n  KJ_EXPECT(TraceId(0, 0).toProtobuf() ==\n      \"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"_kjb);\n\n  KJ_EXPECT(TraceId(0xfedcba9876543211, 0xfedcba9876543212).toProtobuf() ==\n      \"\\xfe\\xdc\\xba\\x98\\x76\\x54\\x32\\x12\\xfe\\xdc\\xba\\x98\\x76\\x54\\x32\\x11\"_kjb);\n}\n\nKJ_TEST(\"InvocationSpanContext\") {\n  setPredictableModeForTest();\n  FakeEntropySource fakeEntropySource;\n  auto sc = InvocationSpanContext::newForInvocation(kj::none, fakeEntropySource);\n\n  // We can create an InvocationSpanContext...\n  static constexpr auto kCheck = TraceId(0x2a2a2a2a2a2a2a2a, 0x2a2a2a2a2a2a2a2a);\n  KJ_EXPECT(sc.getTraceId() == kCheck);\n  KJ_EXPECT(sc.getInvocationId() == kCheck);\n  KJ_EXPECT(sc.getSpanId() == SpanId(1));\n\n  // And serialize that to a capnp struct...\n  capnp::MallocMessageBuilder builder;\n  auto root = builder.initRoot<rpc::InvocationSpanContext>();\n  sc.toCapnp(root);\n\n  // Then back again...\n  auto sc2 = KJ_ASSERT_NONNULL(InvocationSpanContext::fromCapnp(root.asReader()));\n  KJ_EXPECT(sc2.getTraceId() == kCheck);\n  KJ_EXPECT(sc2.getInvocationId() == kCheck);\n  KJ_EXPECT(sc2.getSpanId() == SpanId(1));\n  KJ_EXPECT(sc2.isTrigger());\n\n  // The one that has been deserialized from capnp cannot create children...\n  try {\n    sc2.newChild();\n    KJ_FAIL_ASSERT(\"should not be able to create child span with SpanContext from capnp\");\n  } catch (kj::Exception& ex) {\n    KJ_EXPECT(ex.getDescription() ==\n        \"expected !isTrigger(); unable to create child spans on this context\"_kj);\n  }\n\n  auto sc3 = sc.newChild();\n  KJ_EXPECT(sc3.getTraceId() == kCheck);\n  KJ_EXPECT(sc3.getInvocationId() == kCheck);\n  KJ_EXPECT(sc3.getSpanId() == SpanId(2));\n\n  auto sc4 = InvocationSpanContext::newForInvocation(sc2, fakeEntropySource);\n  KJ_EXPECT(sc4.getTraceId() == kCheck);\n  KJ_EXPECT(sc4.getInvocationId() == kCheck);\n  KJ_EXPECT(sc4.getSpanId() == SpanId(3));\n\n  auto& sc5 = KJ_ASSERT_NONNULL(sc4.getParent());\n  KJ_EXPECT(sc5.getTraceId() == kCheck);\n  KJ_EXPECT(sc5.getInvocationId() == kCheck);\n  KJ_EXPECT(sc5.getSpanId() == SpanId(1));\n  KJ_EXPECT(sc5.isTrigger());\n}\n\nKJ_TEST(\"SpanContext\") {\n  setPredictableModeForTest();\n  FakeEntropySource fakeEntropySource;\n  auto sc =\n      SpanContext(TraceId::fromEntropy(fakeEntropySource), SpanId::fromEntropy(fakeEntropySource));\n\n  // We can create a SpanContext...\n  static constexpr auto kCheck = TraceId(0x2a2a2a2a2a2a2a2a, 0x2a2a2a2a2a2a2a2a);\n  KJ_EXPECT(sc.getTraceId() == kCheck);\n  KJ_EXPECT(sc.getSpanId() == SpanId(1));\n\n  // And serialize that to a capnp struct...\n  capnp::MallocMessageBuilder builder;\n  auto root = builder.initRoot<rpc::SpanContext>();\n  sc.toCapnp(root);\n\n  // Then back again...\n  auto sc2 = SpanContext::fromCapnp(root.asReader());\n  KJ_EXPECT(sc2.getTraceId() == kCheck);\n  KJ_EXPECT(sc2.getSpanId() == SpanId(1));\n}\n\nKJ_TEST(\"Read/Write FetchEventInfo works\") {\n  capnp::MallocMessageBuilder builder;\n  auto fetchInfoBuilder = builder.initRoot<rpc::Trace::FetchEventInfo>();\n\n  kj::Vector<FetchEventInfo::Header> headers;\n  headers.add(FetchEventInfo::Header(kj::str(\"foo\"), kj::str(\"bar\")));\n\n  FetchEventInfo info(\n      kj::HttpMethod::GET, kj::str(\"https://example.com\"), kj::str(\"{}\"), headers.releaseAsArray());\n\n  info.copyTo(fetchInfoBuilder);\n\n  auto reader = fetchInfoBuilder.asReader();\n\n  FetchEventInfo info2(reader);\n  KJ_ASSERT(info2.method == kj::HttpMethod::GET);\n  KJ_ASSERT(info2.url == \"https://example.com\"_kj);\n  KJ_ASSERT(info2.cfJson == \"{}\"_kj);\n  KJ_ASSERT(info2.headers.size() == 1);\n  KJ_ASSERT(info2.headers[0].name == \"foo\"_kj);\n  KJ_ASSERT(info2.headers[0].value == \"bar\"_kj);\n\n  FetchEventInfo info3 = info.clone();\n  KJ_ASSERT(info3.method == kj::HttpMethod::GET);\n  KJ_ASSERT(info3.url == \"https://example.com\"_kj);\n  KJ_ASSERT(info3.cfJson == \"{}\"_kj);\n  KJ_ASSERT(info3.headers.size() == 1);\n  KJ_ASSERT(info3.headers[0].name == \"foo\"_kj);\n  KJ_ASSERT(info3.headers[0].value == \"bar\"_kj);\n}\n\nKJ_TEST(\"Read/Write JsRpcEventInfo works\") {\n  capnp::MallocMessageBuilder builder;\n  auto jsRpcInfoBuilder = builder.initRoot<rpc::Trace::JsRpcEventInfo>();\n\n  JsRpcEventInfo info(kj::str(\"foo\"));\n\n  info.copyTo(jsRpcInfoBuilder);\n\n  auto reader = jsRpcInfoBuilder.asReader();\n\n  JsRpcEventInfo info2(reader);\n  KJ_ASSERT(info2.methodName == \"foo\"_kj);\n\n  JsRpcEventInfo info3 = info.clone();\n  KJ_ASSERT(info3.methodName == \"foo\"_kj);\n}\n\nKJ_TEST(\"Read/Write ScheduledEventInfo workers\") {\n  capnp::MallocMessageBuilder builder;\n  auto infoBuilder = builder.initRoot<rpc::Trace::ScheduledEventInfo>();\n\n  ScheduledEventInfo info(1.2, kj::str(\"foo\"));\n\n  info.copyTo(infoBuilder);\n\n  auto reader = infoBuilder.asReader();\n\n  ScheduledEventInfo info2(reader);\n  KJ_ASSERT(info2.scheduledTime == 1.2);\n  KJ_ASSERT(info2.cron == \"foo\"_kj);\n\n  ScheduledEventInfo info3 = info.clone();\n  KJ_ASSERT(info3.scheduledTime == 1.2);\n  KJ_ASSERT(info3.cron == \"foo\"_kj);\n}\n\nKJ_TEST(\"Read/Write AlarmEventInfo works\") {\n  capnp::MallocMessageBuilder builder;\n  auto infoBuilder = builder.initRoot<rpc::Trace::AlarmEventInfo>();\n\n  AlarmEventInfo info(kj::UNIX_EPOCH);\n\n  info.copyTo(infoBuilder);\n\n  auto reader = infoBuilder.asReader();\n\n  AlarmEventInfo info2(reader);\n  KJ_ASSERT(info.scheduledTime == info2.scheduledTime);\n\n  AlarmEventInfo info3 = info.clone();\n  KJ_ASSERT(info.scheduledTime == info3.scheduledTime);\n}\n\nKJ_TEST(\"Read/Write QueueEventInfo works\") {\n  capnp::MallocMessageBuilder builder;\n  auto infoBuilder = builder.initRoot<rpc::Trace::QueueEventInfo>();\n\n  QueueEventInfo info(kj::str(\"foo\"), 1);\n\n  info.copyTo(infoBuilder);\n\n  auto reader = infoBuilder.asReader();\n\n  QueueEventInfo info2(reader);\n  KJ_ASSERT(info2.queueName == \"foo\"_kj);\n  KJ_ASSERT(info2.batchSize == 1);\n\n  QueueEventInfo info3 = info.clone();\n  KJ_ASSERT(info2.queueName == \"foo\"_kj);\n  KJ_ASSERT(info2.batchSize == 1);\n}\n\nKJ_TEST(\"Read/Write EmailEventInfo works\") {\n  capnp::MallocMessageBuilder builder;\n  auto infoBuilder = builder.initRoot<rpc::Trace::EmailEventInfo>();\n\n  EmailEventInfo info(kj::str(\"foo\"), kj::str(\"bar\"), 1);\n  info.copyTo(infoBuilder);\n\n  auto reader = infoBuilder.asReader();\n\n  EmailEventInfo info2(reader);\n  KJ_ASSERT(info2.mailFrom == \"foo\"_kj);\n  KJ_ASSERT(info2.rcptTo == \"bar\"_kj);\n  KJ_ASSERT(info2.rawSize == 1);\n\n  EmailEventInfo info3 = info.clone();\n  KJ_ASSERT(info3.mailFrom == \"foo\"_kj);\n  KJ_ASSERT(info3.rcptTo == \"bar\"_kj);\n  KJ_ASSERT(info3.rawSize == 1);\n}\n\nKJ_TEST(\"Read/Write TraceEventInfo works\") {\n  capnp::MallocMessageBuilder builder;\n  auto infoBuilder = builder.initRoot<rpc::Trace::TraceEventInfo>();\n\n  kj::Vector<kj::Own<Trace>> items(1);\n  items.add(kj::heap<Trace>(kj::none, kj::str(\"foo\"), kj::none, kj::none, kj::none,\n      kj::Array<kj::String>(), kj::none, ExecutionModel::STATELESS));\n\n  TraceEventInfo info(items.asPtr());\n  info.copyTo(infoBuilder);\n\n  auto reader = infoBuilder.asReader();\n\n  TraceEventInfo info2(reader);\n  KJ_ASSERT(info2.traces.size() == 1);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(info2.traces[0].scriptName) == \"foo\"_kj);\n\n  TraceEventInfo info3 = info.clone();\n  KJ_ASSERT(info2.traces.size() == 1);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(info2.traces[0].scriptName) == \"foo\"_kj);\n}\n\nKJ_TEST(\"Read/Write HibernatableWebSocketEventInfo works\") {\n  capnp::MallocMessageBuilder builder;\n  auto infoBuilder = builder.initRoot<rpc::Trace::HibernatableWebSocketEventInfo>();\n\n  HibernatableWebSocketEventInfo info(HibernatableWebSocketEventInfo::Message{});\n  info.copyTo(infoBuilder);\n\n  auto reader = infoBuilder.asReader();\n\n  HibernatableWebSocketEventInfo info2(reader);\n  KJ_ASSERT(info2.type.is<HibernatableWebSocketEventInfo::Message>());\n\n  HibernatableWebSocketEventInfo info3 = info.clone();\n  KJ_ASSERT(info3.type.is<HibernatableWebSocketEventInfo::Message>());\n}\n\nKJ_TEST(\"Read/Write FetchResponseInfo works\") {\n  capnp::MallocMessageBuilder builder;\n  auto infoBuilder = builder.initRoot<rpc::Trace::FetchResponseInfo>();\n\n  FetchResponseInfo info(123);\n  info.copyTo(infoBuilder);\n\n  auto reader = infoBuilder.asReader();\n  FetchResponseInfo info2(reader);\n  KJ_ASSERT(info2.statusCode == 123);\n\n  FetchResponseInfo info3 = info.clone();\n  KJ_ASSERT(info3.statusCode == 123);\n}\n\nKJ_TEST(\"Read/Write DiagnosticChannelEvent works\") {\n  capnp::MallocMessageBuilder builder;\n  auto infoBuilder = builder.initRoot<rpc::Trace::DiagnosticChannelEvent>();\n\n  DiagnosticChannelEvent info(kj::UNIX_EPOCH, kj::str(\"foo\"), kj::Array<kj::byte>());\n  info.copyTo(infoBuilder);\n\n  auto reader = infoBuilder.asReader();\n\n  DiagnosticChannelEvent info2(reader);\n  KJ_ASSERT(info2.timestamp == info.timestamp);\n  KJ_ASSERT(info2.channel == \"foo\"_kj);\n  KJ_ASSERT(info2.message.size() == 0);\n\n  DiagnosticChannelEvent info3 = info.clone();\n  KJ_ASSERT(info3.timestamp == info.timestamp);\n  KJ_ASSERT(info3.channel == \"foo\"_kj);\n  KJ_ASSERT(info3.message.size() == 0);\n}\n\nKJ_TEST(\"Read/Write Log works\") {\n  capnp::MallocMessageBuilder builder;\n  auto infoBuilder = builder.initRoot<rpc::Trace::Log>();\n\n  Log info(kj::UNIX_EPOCH, LogLevel::INFO, kj::str(\"foo\"));\n  info.copyTo(infoBuilder);\n\n  auto reader = infoBuilder.asReader();\n  Log info2(reader);\n  KJ_ASSERT(info.timestamp == info2.timestamp);\n  KJ_ASSERT(info2.logLevel == LogLevel::INFO);\n  KJ_ASSERT(info2.message == \"foo\"_kj);\n\n  Log info3 = info.clone();\n  KJ_ASSERT(info.timestamp == info3.timestamp);\n  KJ_ASSERT(info3.logLevel == LogLevel::INFO);\n  KJ_ASSERT(info3.message == \"foo\"_kj);\n}\n\nKJ_TEST(\"Read/Write Exception works\") {\n  capnp::MallocMessageBuilder builder;\n  auto infoBuilder = builder.initRoot<rpc::Trace::Exception>();\n\n  Exception info(kj::UNIX_EPOCH, kj::str(\"foo\"), kj::str(\"bar\"), kj::none);\n  info.copyTo(infoBuilder);\n\n  auto reader = infoBuilder.asReader();\n  Exception info2(reader);\n  KJ_ASSERT(info.timestamp == info2.timestamp);\n  KJ_ASSERT(info2.name == \"foo\"_kj);\n  KJ_ASSERT(info2.message == \"bar\"_kj);\n  KJ_ASSERT(info2.stack == kj::none);\n\n  Exception info3 = info.clone();\n  KJ_ASSERT(info.timestamp == info3.timestamp);\n  KJ_ASSERT(info3.name == \"foo\"_kj);\n  KJ_ASSERT(info3.message == \"bar\"_kj);\n  KJ_ASSERT(info3.stack == kj::none);\n}\n\nKJ_TEST(\"Read/Write StreamDiagnosticsEvent works\") {\n  capnp::MallocMessageBuilder builder;\n  auto infoBuilder = builder.initRoot<rpc::Trace::StreamDiagnosticsEvent>();\n\n  StreamDiagnosticsEvent info(42);\n  info.copyTo(infoBuilder);\n\n  auto reader = infoBuilder.asReader();\n  StreamDiagnosticsEvent info2(reader);\n  KJ_ASSERT(info2.droppedEventsCount == 42);\n\n  StreamDiagnosticsEvent info3 = info.clone();\n  KJ_ASSERT(info3.droppedEventsCount == 42);\n}\n\nKJ_TEST(\"Read/Write Attribute works\") {\n  capnp::MallocMessageBuilder builder;\n  auto infoBuilder = builder.initRoot<rpc::Trace::Attribute>();\n\n  Attribute attr(\"foo\"_kjc, {123.0, 321.2});\n  attr.copyTo(infoBuilder);\n\n  auto reader = infoBuilder.asReader();\n  Attribute info2(reader);\n  KJ_ASSERT(info2.name == \"foo\"_kj);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(info2.value[0].tryGet<double>()) == 123.0);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(info2.value[1].tryGet<double>()) == 321.2);\n}\n\nKJ_TEST(\"Read/Write Return works\") {\n  capnp::MallocMessageBuilder builder;\n  auto infoBuilder = builder.initRoot<rpc::Trace::Return>();\n\n  FetchResponseInfo fetchInfo(123);\n  Return info(kj::mv(fetchInfo));\n  info.copyTo(infoBuilder);\n\n  auto reader = infoBuilder.asReader();\n  Return info2(reader);\n  auto& fetchInfo2 = KJ_ASSERT_NONNULL(info2.info);\n  KJ_ASSERT(fetchInfo2.statusCode == 123);\n\n  Return info3 = info.clone();\n  auto& fetchInfo3 = KJ_ASSERT_NONNULL(info3.info);\n  KJ_ASSERT(fetchInfo3.statusCode == 123);\n}\n\nKJ_TEST(\"Read/Write SpanOpen works\") {\n  capnp::MallocMessageBuilder builder;\n  auto infoBuilder = builder.initRoot<rpc::Trace::SpanOpen>();\n\n  SpanOpen info(0x2a2a2a2a2a2a2a2a, \"foo\"_kjc, kj::none);\n  info.copyTo(infoBuilder);\n\n  auto reader = infoBuilder.asReader();\n  SpanOpen info2(reader);\n  KJ_ASSERT(info2.operationName == \"foo\"_kj);\n  KJ_ASSERT(info2.info == kj::none);\n\n  SpanOpen info3 = info.clone();\n  KJ_ASSERT(info3.operationName == \"foo\"_kj);\n  KJ_ASSERT(info3.info == kj::none);\n}\n\nKJ_TEST(\"Read/Write SpanClose works\") {\n  capnp::MallocMessageBuilder builder;\n  auto infoBuilder = builder.initRoot<rpc::Trace::SpanClose>();\n\n  SpanClose info(EventOutcome::EXCEPTION);\n  info.copyTo(infoBuilder);\n\n  auto reader = infoBuilder.asReader();\n\n  SpanClose info2(reader);\n  KJ_ASSERT(info2.outcome == EventOutcome::EXCEPTION);\n\n  SpanClose info3 = info.clone();\n  KJ_ASSERT(info3.outcome == EventOutcome::EXCEPTION);\n}\n\nKJ_TEST(\"Read/Write Onset works\") {\n  capnp::MallocMessageBuilder builder;\n  auto infoBuilder = builder.initRoot<rpc::Trace::Onset>();\n\n  FetchEventInfo fetchInfo(\n      kj::HttpMethod::GET, kj::str(\"https://example.com\"), kj::str(\"{}\"), nullptr);\n\n  Onset info(staticSpanId, Onset::Info(kj::mv(fetchInfo)),\n      {\n        .scriptName = kj::str(\"foo\"),\n      },\n      nullptr);\n  info.copyTo(infoBuilder);\n\n  auto reader = infoBuilder.asReader();\n  Onset info2(reader);\n  FetchEventInfo& fetchInfo2 = KJ_ASSERT_NONNULL(info2.info.tryGet<FetchEventInfo>());\n  KJ_ASSERT(fetchInfo2.method == kj::HttpMethod::GET);\n  KJ_ASSERT(fetchInfo2.url == \"https://example.com\"_kj);\n  KJ_ASSERT(info2.workerInfo.executionModel == ExecutionModel::STATELESS);\n\n  Onset info3 = info.clone();\n  FetchEventInfo& fetchInfo3 = KJ_ASSERT_NONNULL(info3.info.tryGet<FetchEventInfo>());\n  KJ_ASSERT(fetchInfo3.method == kj::HttpMethod::GET);\n  KJ_ASSERT(fetchInfo3.url == \"https://example.com\"_kj);\n  KJ_ASSERT(info3.workerInfo.executionModel == ExecutionModel::STATELESS);\n}\n\nKJ_TEST(\"Read/Write Outcome works\") {\n  capnp::MallocMessageBuilder builder;\n  auto infoBuilder = builder.initRoot<rpc::Trace::Outcome>();\n\n  Outcome info(EventOutcome::EXCEPTION, 1 * kj::MILLISECONDS, 2 * kj::MILLISECONDS);\n  info.copyTo(infoBuilder);\n\n  auto reader = infoBuilder.asReader();\n  Outcome info2(reader);\n  KJ_ASSERT(info2.outcome == EventOutcome::EXCEPTION);\n  KJ_ASSERT(info2.wallTime == 2 * kj::MILLISECONDS);\n  KJ_ASSERT(info2.cpuTime == 1 * kj::MILLISECONDS);\n\n  Outcome info3 = info.clone();\n  KJ_ASSERT(info3.outcome == EventOutcome::EXCEPTION);\n  KJ_ASSERT(info3.wallTime == 2 * kj::MILLISECONDS);\n  KJ_ASSERT(info3.cpuTime == 1 * kj::MILLISECONDS);\n}\n\nKJ_TEST(\"Read/Write TailEvent works\") {\n  capnp::MallocMessageBuilder builder;\n  auto infoBuilder = builder.initRoot<rpc::Trace::TailEvent>();\n\n  auto context = SpanContext(TraceId(0, 0), {staticSpanId});\n  Log log(kj::UNIX_EPOCH, LogLevel::INFO, kj::str(\"foo\"));\n  auto invocationId = TraceId(0, 0);\n  TailEvent info(\n      context.getTraceId(), invocationId, context.getSpanId(), kj::UNIX_EPOCH, 0, kj::mv(log));\n  info.copyTo(infoBuilder);\n\n  auto reader = infoBuilder.asReader();\n\n  TailEvent info2(reader);\n  KJ_ASSERT(info2.timestamp == kj::UNIX_EPOCH);\n  KJ_ASSERT(info2.sequence == 0);\n  KJ_ASSERT(info2.invocationId == invocationId);\n  KJ_ASSERT(info2.spanContext == context);\n\n  auto& log2 = KJ_ASSERT_NONNULL(info2.event.tryGet<Log>());\n  KJ_ASSERT(log2.timestamp == kj::UNIX_EPOCH);\n  KJ_ASSERT(log2.logLevel == LogLevel::INFO);\n  KJ_ASSERT(log2.message == \"foo\"_kj);\n\n  TailEvent info3 = info.clone();\n  KJ_ASSERT(info3.timestamp == kj::UNIX_EPOCH);\n  KJ_ASSERT(info3.sequence == 0);\n  KJ_ASSERT(info3.invocationId == invocationId);\n  KJ_ASSERT(info3.spanContext == context);\n\n  auto& log3 = KJ_ASSERT_NONNULL(info3.event.tryGet<Log>());\n  KJ_ASSERT(log3.timestamp == kj::UNIX_EPOCH);\n  KJ_ASSERT(log3.logLevel == LogLevel::INFO);\n  KJ_ASSERT(log3.message == \"foo\"_kj);\n}\n\nKJ_TEST(\"Read/Write TailEvent with Multiple Attributes\") {\n  capnp::MallocMessageBuilder builder;\n  auto infoBuilder = builder.initRoot<rpc::Trace::TailEvent>();\n\n  TraceId traceId(0, 0);\n  auto context = SpanContext(traceId, {staticSpanId});\n\n  // An attribute event can have one or more Attributes specified.\n  kj::Vector<Attribute> attrs(2);\n  attrs.add(Attribute(\"foo\"_kjc, true));\n  attrs.add(Attribute(\"bar\"_kjc, static_cast<int64_t>(123)));\n\n  TailEvent info(kj::mv(context), traceId, kj::UNIX_EPOCH, 0, attrs.releaseAsArray());\n  info.copyTo(infoBuilder);\n\n  TailEvent info2(infoBuilder.asReader());\n  auto& attrs2 = KJ_ASSERT_NONNULL(info2.event.tryGet<kj::Array<Attribute>>());\n  KJ_ASSERT(attrs2.size() == 2);\n\n  KJ_ASSERT(attrs2[0].name == \"foo\"_kj);\n  KJ_ASSERT(attrs2[1].name == \"bar\"_kj);\n}\n\nKJ_TEST(\"Trace with Durable Object ID\") {\n  auto trace = kj::refcounted<Trace>(kj::str(\"test-stable-id\"), kj::str(\"test-script\"),\n      kj::none,  // scriptVersion\n      kj::str(\"test-namespace\"), kj::str(\"test-script-id\"),\n      kj::Array<kj::String>(),  // scriptTags\n      kj::str(\"test-entrypoint\"), ExecutionModel::DURABLE_OBJECT,\n      kj::str(\"abc123def456\")  // durableObjectId\n  );\n\n  capnp::MallocMessageBuilder builder;\n  auto traceBuilder = builder.initRoot<rpc::Trace>();\n  trace->copyTo(traceBuilder);\n\n  auto trace2 = kj::refcounted<Trace>(traceBuilder.asReader());\n  KJ_ASSERT(KJ_REQUIRE_NONNULL(trace2->durableObjectId) == \"abc123def456\"_kj);\n}\n}  // namespace\n}  // namespace workerd::tracing\n"
  },
  {
    "path": "src/workerd/io/trace.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/io/trace.h>\n#include <workerd/util/entropy.h>\n#include <workerd/util/thread-scopes.h>\n\n#include <capnp/message.h>\n#include <capnp/schema.h>\n#include <kj/compat/http.h>\n#include <kj/debug.h>\n#include <kj/time.h>\n\n#include <cstdlib>\n\nnamespace workerd {\n\nnamespace tracing {\nnamespace {\nkj::Maybe<kj::uint> tryFromHexDigit(char c) {\n  if ('0' <= c && c <= '9') {\n    return c - '0';\n  } else if ('a' <= c && c <= 'f') {\n    return c - ('a' - 10);\n  } else if ('A' <= c && c <= 'F') {\n    return c - ('A' - 10);\n  } else {\n    return kj::none;\n  }\n}\n\nkj::Maybe<uint64_t> hexToUint64(kj::ArrayPtr<const char> s) {\n  KJ_ASSERT(s.size() <= 16);\n  uint64_t value = 0;\n  for (auto ch: s) {\n    KJ_IF_SOME(d, tryFromHexDigit(ch)) {\n      value = (value << 4) + d;\n    } else {\n      return kj::none;\n    }\n  }\n  return value;\n}\n\nvoid addHex(kj::Vector<char>& out, uint64_t v) {\n  constexpr char HEX_DIGITS[] = \"0123456789abcdef\";\n  for (int i = 0; i < 16; ++i) {\n    out.add(HEX_DIGITS[v >> (64 - 4)]);\n    v = v << 4;\n  }\n};\n\nvoid addBigEndianBytes(kj::Vector<byte>& out, uint64_t v) {\n  for (int i = 0; i < 8; ++i) {\n    out.add(v >> (64 - 8));\n    v = v << 8;\n  }\n};\n}  // namespace\n\n// Reference: https://github.com/jaegertracing/jaeger/blob/e46f8737/model/ids.go#L58\nkj::Maybe<TraceId> TraceId::fromGoString(kj::ArrayPtr<const char> s) {\n  auto n = s.size();\n  if (n > 32) {\n    return kj::none;\n  } else if (n <= 16) {\n    KJ_IF_SOME(low, hexToUint64(s)) {\n      return TraceId(low, 0);\n    }\n  } else {\n    KJ_IF_SOME(high, hexToUint64(s.slice(0, n - 16))) {\n      KJ_IF_SOME(low, hexToUint64(s.slice(n - 16, n))) {\n        return TraceId(low, high);\n      }\n    }\n  }\n  return kj::none;\n}\n\n// Reference: https://github.com/jaegertracing/jaeger/blob/e46f8737/model/ids.go#L50\nkj::String TraceId::toGoString() const {\n  if (high == 0) {\n    kj::Vector<char> s(17);\n    addHex(s, low);\n    s.add('\\0');\n    return kj::String(s.releaseAsArray());\n  }\n  kj::Vector<char> s(33);\n  addHex(s, high);\n  addHex(s, low);\n  s.add('\\0');\n  return kj::String(s.releaseAsArray());\n}\n\n// Reference: https://github.com/jaegertracing/jaeger/blob/e46f8737/model/ids.go#L111\nkj::Maybe<TraceId> TraceId::fromProtobuf(kj::ArrayPtr<const byte> buf) {\n  if (buf.size() != 16) {\n    return kj::none;\n  }\n  uint64_t high = 0;\n  for (auto i: kj::zeroTo(8)) {\n    high = (high << 8) + buf[i];\n  }\n  uint64_t low = 0;\n  for (auto i: kj::zeroTo(8)) {\n    low = (low << 8) + buf[i + 8];\n  }\n  return TraceId(low, high);\n}\n\n// Reference: https://github.com/jaegertracing/jaeger/blob/e46f8737/model/ids.go#L81\nkj::Array<byte> TraceId::toProtobuf() const {\n  kj::Vector<byte> s(16);\n  addBigEndianBytes(s, high);\n  addBigEndianBytes(s, low);\n  return s.releaseAsArray();\n}\n\n// Reference https://www.w3.org/TR/trace-context/#trace-id\nkj::String TraceId::toW3C() const {\n  kj::Vector<char> s(32);\n  addHex(s, high);\n  addHex(s, low);\n  return kj::str(s.releaseAsArray());\n}\n\nnamespace {\nuint64_t getRandom64Bit(const kj::Maybe<kj::EntropySource&>& entropySource) {\n  uint64_t ret = 0;\n  uint8_t tries = 0;\n\n  do {\n    tries++;\n    KJ_IF_SOME(entropy, entropySource) {\n      entropy.generate(kj::asBytes(ret));\n    } else {\n      getEntropy(kj::asBytes(ret));\n    }\n    // On the extreme off chance that we ended with with zeroes\n    // let's try again, but only up to three times.\n  } while (ret == 0 && tries < 3);\n\n  return ret;\n}\n}  // namespace\n\nTraceId TraceId::fromEntropy(kj::Maybe<kj::EntropySource&> entropySource) {\n  if (isPredictableModeForTest()) {\n    return TraceId(staticSpanId, staticSpanId);\n  }\n\n  return TraceId(getRandom64Bit(entropySource), getRandom64Bit(entropySource));\n}\n\nkj::String SpanId::toGoString() const {\n  kj::Vector<char> s(16);\n  addHex(s, id);\n  s.add('\\0');\n  return kj::String(s.releaseAsArray());\n}\n\nSpanId SpanId::fromEntropy(kj::Maybe<kj::EntropySource&> entropySource) {\n  return SpanId(getRandom64Bit(entropySource));\n}\n\nkj::String KJ_STRINGIFY(const SpanId& id) {\n  return id;\n}\n\nkj::String KJ_STRINGIFY(const TraceId& id) {\n  return id;\n}\n\nInvocationSpanContext::InvocationSpanContext(kj::Badge<InvocationSpanContext>,\n    kj::Maybe<kj::EntropySource&> entropySource,\n    TraceId traceId,\n    TraceId invocationId,\n    SpanId spanId,\n    kj::Maybe<const InvocationSpanContext&> parentSpanContext)\n    : entropySource(entropySource),\n      traceId(kj::mv(traceId)),\n      invocationId(kj::mv(invocationId)),\n      spanId(kj::mv(spanId)),\n      parentSpanContext(parentSpanContext.map([](const InvocationSpanContext& ctx) {\n        return kj::heap<InvocationSpanContext>(ctx.clone());\n      })) {}\n\nInvocationSpanContext InvocationSpanContext::newChild() const {\n  KJ_ASSERT(!isTrigger(), \"unable to create child spans on this context\");\n  kj::Maybe<kj::EntropySource&> otherEntropySource = entropySource.map(\n      [](auto& es) -> kj::EntropySource& { return const_cast<kj::EntropySource&>(es); });\n  return InvocationSpanContext(kj::Badge<InvocationSpanContext>(), otherEntropySource, traceId,\n      invocationId, SpanId::fromEntropy(otherEntropySource), *this);\n}\n\nInvocationSpanContext InvocationSpanContext::newForInvocation(\n    kj::Maybe<const InvocationSpanContext&> triggerContext,\n    kj::Maybe<kj::EntropySource&> entropySource) {\n  kj::Maybe<const InvocationSpanContext&> parent;\n  auto traceId = triggerContext\n                     .map([&](auto& ctx) mutable {\n    parent = ctx;\n    return ctx.traceId;\n  }).orDefault([&] { return TraceId::fromEntropy(entropySource); });\n  return InvocationSpanContext(kj::Badge<InvocationSpanContext>(), entropySource, kj::mv(traceId),\n      TraceId::fromEntropy(entropySource), SpanId::fromEntropy(entropySource), kj::mv(parent));\n}\n\nTraceId TraceId::fromCapnp(rpc::TraceId::Reader reader) {\n  return TraceId(reader.getLow(), reader.getHigh());\n}\n\nvoid TraceId::toCapnp(rpc::TraceId::Builder writer) const {\n  writer.setLow(low);\n  writer.setHigh(high);\n}\n\nkj::Maybe<InvocationSpanContext> InvocationSpanContext::fromCapnp(\n    rpc::InvocationSpanContext::Reader reader) {\n  if (!reader.hasTraceId() || !reader.hasInvocationId()) {\n    // If the reader does not have a traceId or invocationId field then it is\n    // invalid and we will just ignore it.\n    return kj::none;\n  }\n\n  auto sc = InvocationSpanContext(kj::Badge<InvocationSpanContext>(), kj::none,\n      TraceId::fromCapnp(reader.getTraceId()), TraceId::fromCapnp(reader.getInvocationId()),\n      reader.getSpanId());\n  // If the traceId or invocationId are invalid, then we'll ignore them.\n  if (!sc.getTraceId() || !sc.getInvocationId()) return kj::none;\n  return kj::mv(sc);\n}\n\nvoid InvocationSpanContext::toCapnp(rpc::InvocationSpanContext::Builder writer) const {\n  traceId.toCapnp(writer.initTraceId());\n  invocationId.toCapnp(writer.initInvocationId());\n  writer.setSpanId(spanId);\n}\n\nInvocationSpanContext InvocationSpanContext::clone() const {\n  kj::Maybe<kj::EntropySource&> otherEntropySource = entropySource.map(\n      [](auto& es) -> kj::EntropySource& { return const_cast<kj::EntropySource&>(es); });\n  return InvocationSpanContext(kj::Badge<InvocationSpanContext>(), otherEntropySource, traceId,\n      invocationId, spanId,\n      parentSpanContext.map([](auto& ctx) -> const InvocationSpanContext& { return *ctx.get(); }));\n}\n\nkj::String KJ_STRINGIFY(const InvocationSpanContext& context) {\n  return kj::str(context.getTraceId(), \"-\", context.getInvocationId(), \"-\", context.getSpanId());\n}\n\nkj::String KJ_STRINGIFY(const TailEvent::Event& event) {\n  KJ_SWITCH_ONEOF(event) {\n    KJ_CASE_ONEOF(onset, Onset) {\n      return kj::str(\"Onset\");\n    }\n    KJ_CASE_ONEOF(outcome, Outcome) {\n      return kj::str(\"Outcome\");\n    }\n    KJ_CASE_ONEOF(spanOpen, SpanOpen) {\n      return spanOpen.toString();\n    }\n    KJ_CASE_ONEOF(spanClose, SpanClose) {\n      return spanClose.toString();\n    }\n    KJ_CASE_ONEOF(diagnosticChannelEvent, DiagnosticChannelEvent) {\n      return kj::str(\"diagnosticChannelEvent\");\n    }\n    KJ_CASE_ONEOF(exception, Exception) {\n      return kj::str(\"Exception\");\n    }\n    KJ_CASE_ONEOF(log, Log) {\n      return kj::str(\"Log\");\n    }\n    KJ_CASE_ONEOF(streamDiag, StreamDiagnosticsEvent) {\n      return kj::str(\"StreamDiagnosticsEvent(droppedEvents: \", streamDiag.droppedEventsCount, \")\");\n    }\n    KJ_CASE_ONEOF(ret, Return) {\n      return kj::str(\"Return\");\n    }\n    KJ_CASE_ONEOF(customInfo, CustomInfo) {\n      return kj::str(customInfo);\n    }\n  }\n  KJ_UNREACHABLE\n}\n\nkj::String KJ_STRINGIFY(const CustomInfo& customInfo) {\n  return kj::str(\n      \"CustomInfo: \", kj::strArray(KJ_MAP(attr, customInfo) { return kj::str(attr); }, \", \"));\n}\n\nSpanContext SpanContext::fromCapnp(rpc::SpanContext::Reader reader) {\n  auto info = reader.getInfo();\n  kj::Maybe<SpanId> spanId;\n  if (info.isSpanId()) {\n    spanId = info.getSpanId();\n  }\n\n  return SpanContext(TraceId::fromCapnp(reader.getTraceId()), spanId);\n}\n\nvoid SpanContext::toCapnp(rpc::SpanContext::Builder writer) const {\n  traceId.toCapnp(writer.initTraceId());\n  auto info = writer.initInfo();\n  KJ_IF_SOME(s, spanId) {\n    info.setSpanId(s);\n  }\n}\n\nSpanContext SpanContext::clone() const {\n  return SpanContext(traceId, spanId);\n}\n\nkj::String KJ_STRINGIFY(const SpanContext& context) {\n  return kj::str(context.getTraceId(), \"-\", context.getSpanId());\n}\n\nnamespace {\n\nstatic kj::HttpMethod validateMethod(capnp::HttpMethod method) {\n  KJ_REQUIRE(method <= capnp::HttpMethod::BAN, \"unknown method\", method);\n  return static_cast<kj::HttpMethod>(method);\n}\n\n}  // namespace\n\nFetchEventInfo::FetchEventInfo(\n    kj::HttpMethod method, kj::String url, kj::String cfJson, kj::Array<Header> headers)\n    : method(method),\n      url(kj::mv(url)),\n      cfJson(kj::mv(cfJson)),\n      headers(kj::mv(headers)) {}\n\nFetchEventInfo::FetchEventInfo(rpc::Trace::FetchEventInfo::Reader reader)\n    : method(validateMethod(reader.getMethod())),\n      url(kj::str(reader.getUrl())),\n      cfJson(kj::str(reader.getCfJson())) {\n  kj::Vector<Header> v;\n  v.addAll(reader.getHeaders());\n  headers = v.releaseAsArray();\n}\n\nvoid FetchEventInfo::copyTo(rpc::Trace::FetchEventInfo::Builder builder) const {\n  builder.setMethod(static_cast<capnp::HttpMethod>(method));\n  builder.setUrl(url);\n  builder.setCfJson(cfJson);\n\n  auto list = builder.initHeaders(headers.size());\n  for (auto i: kj::indices(headers)) {\n    headers[i].copyTo(list[i]);\n  }\n}\n\nFetchEventInfo FetchEventInfo::clone() const {\n  return FetchEventInfo(\n      method, kj::str(url), kj::str(cfJson), KJ_MAP(h, headers) { return h.clone(); });\n}\n\nkj::String FetchEventInfo::toString() const {\n  return kj::str(\"FetchEventInfo: \",\n      kj::delimited(\n          kj::arr(kj::str(method), kj::str(url), kj::str(cfJson), kj::str(headers)), \", \"_kjc));\n}\n\nFetchEventInfo::Header::Header(kj::String name, kj::String value)\n    : name(kj::mv(name)),\n      value(kj::mv(value)) {}\n\nFetchEventInfo::Header::Header(rpc::Trace::FetchEventInfo::Header::Reader reader)\n    : name(kj::str(reader.getName())),\n      value(kj::str(reader.getValue())) {}\n\nvoid FetchEventInfo::Header::copyTo(rpc::Trace::FetchEventInfo::Header::Builder builder) const {\n  builder.setName(name);\n  builder.setValue(value);\n}\n\nFetchEventInfo::Header FetchEventInfo::Header::clone() const {\n  return Header(kj::str(name), kj::str(value));\n}\n\nkj::String FetchEventInfo::Header::toString() const {\n  return kj::str(\"FetchEventInfo::Header: \", name, \", \", value);\n}\n\nJsRpcEventInfo::JsRpcEventInfo(kj::String methodName): methodName(kj::mv(methodName)) {}\n\nJsRpcEventInfo::JsRpcEventInfo(rpc::Trace::JsRpcEventInfo::Reader reader)\n    : methodName(kj::str(reader.getMethodName())) {}\n\nvoid JsRpcEventInfo::copyTo(rpc::Trace::JsRpcEventInfo::Builder builder) const {\n  builder.setMethodName(methodName);\n}\n\nJsRpcEventInfo JsRpcEventInfo::clone() const {\n  return JsRpcEventInfo(kj::str(methodName));\n}\n\nkj::String JsRpcEventInfo::toString() const {\n  return kj::str(\"JsRpcEventInfo: \", methodName);\n}\n\nScheduledEventInfo::ScheduledEventInfo(double scheduledTime, kj::String cron)\n    : scheduledTime(scheduledTime),\n      cron(kj::mv(cron)) {}\n\nScheduledEventInfo::ScheduledEventInfo(rpc::Trace::ScheduledEventInfo::Reader reader)\n    : scheduledTime(reader.getScheduledTime()),\n      cron(kj::str(reader.getCron())) {}\n\nvoid ScheduledEventInfo::copyTo(rpc::Trace::ScheduledEventInfo::Builder builder) const {\n  builder.setScheduledTime(scheduledTime);\n  builder.setCron(cron);\n}\n\nScheduledEventInfo ScheduledEventInfo::clone() const {\n  return ScheduledEventInfo(scheduledTime, kj::str(cron));\n}\n\nAlarmEventInfo::AlarmEventInfo(kj::Date scheduledTime): scheduledTime(scheduledTime) {}\n\nAlarmEventInfo::AlarmEventInfo(rpc::Trace::AlarmEventInfo::Reader reader)\n    : scheduledTime(reader.getScheduledTimeMs() * kj::MILLISECONDS + kj::UNIX_EPOCH) {}\n\nvoid AlarmEventInfo::copyTo(rpc::Trace::AlarmEventInfo::Builder builder) const {\n  builder.setScheduledTimeMs((scheduledTime - kj::UNIX_EPOCH) / kj::MILLISECONDS);\n}\n\nAlarmEventInfo AlarmEventInfo::clone() const {\n  return AlarmEventInfo(scheduledTime);\n}\n\nQueueEventInfo::QueueEventInfo(kj::String queueName, uint32_t batchSize)\n    : queueName(kj::mv(queueName)),\n      batchSize(batchSize) {}\n\nQueueEventInfo::QueueEventInfo(rpc::Trace::QueueEventInfo::Reader reader)\n    : queueName(kj::heapString(reader.getQueueName())),\n      batchSize(reader.getBatchSize()) {}\n\nvoid QueueEventInfo::copyTo(rpc::Trace::QueueEventInfo::Builder builder) const {\n  builder.setQueueName(queueName);\n  builder.setBatchSize(batchSize);\n}\n\nQueueEventInfo QueueEventInfo::clone() const {\n  return QueueEventInfo(kj::str(queueName), batchSize);\n}\n\nEmailEventInfo::EmailEventInfo(kj::String mailFrom, kj::String rcptTo, uint32_t rawSize)\n    : mailFrom(kj::mv(mailFrom)),\n      rcptTo(kj::mv(rcptTo)),\n      rawSize(rawSize) {}\n\nEmailEventInfo::EmailEventInfo(rpc::Trace::EmailEventInfo::Reader reader)\n    : mailFrom(kj::heapString(reader.getMailFrom())),\n      rcptTo(kj::heapString(reader.getRcptTo())),\n      rawSize(reader.getRawSize()) {}\n\nvoid EmailEventInfo::copyTo(rpc::Trace::EmailEventInfo::Builder builder) const {\n  builder.setMailFrom(mailFrom);\n  builder.setRcptTo(rcptTo);\n  builder.setRawSize(rawSize);\n}\n\nEmailEventInfo EmailEventInfo::clone() const {\n  return EmailEventInfo(kj::str(mailFrom), kj::str(rcptTo), rawSize);\n}\n\nnamespace {\nkj::Vector<TraceEventInfo::TraceItem> getTraceItemsFromTraces(\n    kj::ArrayPtr<const kj::Own<Trace>> traces) {\n  return KJ_MAP(t, traces) { return TraceEventInfo::TraceItem(mapCopyString(t->scriptName)); };\n}\n\nkj::Vector<TraceEventInfo::TraceItem> getTraceItemsFromReader(\n    rpc::Trace::TraceEventInfo::Reader reader) {\n  return KJ_MAP(r, reader.getTraces()) { return TraceEventInfo::TraceItem(r); };\n}\n}  // namespace\n\nTraceEventInfo::TraceEventInfo(kj::ArrayPtr<const kj::Own<Trace>> traces)\n    : traces(getTraceItemsFromTraces(traces)) {}\n\nTraceEventInfo::TraceEventInfo(rpc::Trace::TraceEventInfo::Reader reader)\n    : traces(getTraceItemsFromReader(reader)) {}\n\nvoid TraceEventInfo::copyTo(rpc::Trace::TraceEventInfo::Builder builder) const {\n  auto list = builder.initTraces(traces.size());\n  for (auto i: kj::indices(traces)) {\n    traces[i].copyTo(list[i]);\n  }\n}\n\nTraceEventInfo TraceEventInfo::clone() const {\n  return TraceEventInfo(KJ_MAP(item, traces) { return item.clone(); });\n}\n\nTraceEventInfo::TraceItem::TraceItem(kj::Maybe<kj::String> scriptName)\n    : scriptName(kj::mv(scriptName)) {}\n\nTraceEventInfo::TraceItem::TraceItem(rpc::Trace::TraceEventInfo::TraceItem::Reader reader)\n    : scriptName(kj::str(reader.getScriptName())) {}\n\nvoid TraceEventInfo::TraceItem::copyTo(\n    rpc::Trace::TraceEventInfo::TraceItem::Builder builder) const {\n  KJ_IF_SOME(name, scriptName) {\n    builder.setScriptName(name);\n  }\n}\n\nTraceEventInfo::TraceItem TraceEventInfo::TraceItem::clone() const {\n  return TraceItem(mapCopyString(scriptName));\n}\n\nDiagnosticChannelEvent::DiagnosticChannelEvent(\n    kj::Date timestamp, kj::String channel, kj::Array<kj::byte> message)\n    : timestamp(timestamp),\n      channel(kj::mv(channel)),\n      message(kj::mv(message)) {}\n\nDiagnosticChannelEvent::DiagnosticChannelEvent(rpc::Trace::DiagnosticChannelEvent::Reader reader)\n    : timestamp(kj::UNIX_EPOCH + reader.getTimestampNs() * kj::NANOSECONDS),\n      channel(kj::heapString(reader.getChannel())),\n      message(kj::heapArray<kj::byte>(reader.getMessage())) {}\n\nvoid DiagnosticChannelEvent::copyTo(rpc::Trace::DiagnosticChannelEvent::Builder builder) const {\n  builder.setTimestampNs((timestamp - kj::UNIX_EPOCH) / kj::NANOSECONDS);\n  builder.setChannel(channel);\n  builder.setMessage(message);\n}\n\nDiagnosticChannelEvent DiagnosticChannelEvent::clone() const {\n  return DiagnosticChannelEvent(timestamp, kj::str(channel), kj::heapArray<kj::byte>(message));\n}\n\nStreamDiagnosticsEvent::StreamDiagnosticsEvent(uint32_t droppedEventsCount)\n    : droppedEventsCount(droppedEventsCount) {}\n\nStreamDiagnosticsEvent::StreamDiagnosticsEvent(rpc::Trace::StreamDiagnosticsEvent::Reader reader) {\n  auto diagnosticReader = reader.getDiagnostic();\n  switch (diagnosticReader.which()) {\n    case rpc::Trace::StreamDiagnosticsEvent::Diagnostic::UNDEFINED:\n      KJ_FAIL_ASSERT(\"received invalid diagnostics event\");\n      break;\n    case rpc::Trace::StreamDiagnosticsEvent::Diagnostic::DROPPED_EVENTS:\n      auto droppedEvents = diagnosticReader.getDroppedEvents();\n      droppedEventsCount = droppedEvents.getCount();\n      KJ_DASSERT(droppedEventsCount > 0);\n      break;\n  }\n}\n\nvoid StreamDiagnosticsEvent::copyTo(rpc::Trace::StreamDiagnosticsEvent::Builder builder) const {\n  KJ_DASSERT(droppedEventsCount > 0);\n  auto diagnosticBuilder = builder.initDiagnostic();\n  auto droppedEventsBuilder = diagnosticBuilder.initDroppedEvents();\n  droppedEventsBuilder.setCount(droppedEventsCount);\n}\n\nStreamDiagnosticsEvent StreamDiagnosticsEvent::clone() const {\n  return StreamDiagnosticsEvent(droppedEventsCount);\n}\n\nHibernatableWebSocketEventInfo::HibernatableWebSocketEventInfo(Type type): type(type) {}\n\nHibernatableWebSocketEventInfo::HibernatableWebSocketEventInfo(\n    rpc::Trace::HibernatableWebSocketEventInfo::Reader reader)\n    : type(readFrom(reader)) {}\n\nvoid HibernatableWebSocketEventInfo::copyTo(\n    rpc::Trace::HibernatableWebSocketEventInfo::Builder builder) const {\n  auto typeBuilder = builder.initType();\n  KJ_SWITCH_ONEOF(type) {\n    KJ_CASE_ONEOF(_, Message) {\n      typeBuilder.setMessage();\n    }\n    KJ_CASE_ONEOF(close, Close) {\n      auto closeBuilder = typeBuilder.initClose();\n      closeBuilder.setCode(close.code);\n      closeBuilder.setWasClean(close.wasClean);\n    }\n    KJ_CASE_ONEOF(_, Error) {\n      typeBuilder.setError();\n    }\n  }\n}\n\nHibernatableWebSocketEventInfo HibernatableWebSocketEventInfo::clone() const {\n  KJ_SWITCH_ONEOF(type) {\n    KJ_CASE_ONEOF(_, Message) {\n      return HibernatableWebSocketEventInfo(Message{});\n    }\n    KJ_CASE_ONEOF(_, Error) {\n      return HibernatableWebSocketEventInfo(Error{});\n    }\n    KJ_CASE_ONEOF(close, Close) {\n      return HibernatableWebSocketEventInfo(Close{\n        .code = close.code,\n        .wasClean = close.wasClean,\n      });\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nHibernatableWebSocketEventInfo::Type HibernatableWebSocketEventInfo::readFrom(\n    rpc::Trace::HibernatableWebSocketEventInfo::Reader reader) {\n  auto type = reader.getType();\n  switch (type.which()) {\n    case rpc::Trace::HibernatableWebSocketEventInfo::Type::MESSAGE: {\n      return Message{};\n    }\n    case rpc::Trace::HibernatableWebSocketEventInfo::Type::CLOSE: {\n      auto close = type.getClose();\n      return Close{\n        .code = close.getCode(),\n        .wasClean = close.getWasClean(),\n      };\n    }\n    case rpc::Trace::HibernatableWebSocketEventInfo::Type::ERROR: {\n      return Error{};\n    }\n  }\n}\n\nFetchResponseInfo::FetchResponseInfo(uint16_t statusCode): statusCode(statusCode) {}\n\nFetchResponseInfo::FetchResponseInfo(rpc::Trace::FetchResponseInfo::Reader reader)\n    : statusCode(reader.getStatusCode()) {}\n\nvoid FetchResponseInfo::copyTo(rpc::Trace::FetchResponseInfo::Builder builder) const {\n  builder.setStatusCode(statusCode);\n}\n\nFetchResponseInfo FetchResponseInfo::clone() const {\n  return FetchResponseInfo(statusCode);\n}\n\nLog::Log(kj::Date timestamp, LogLevel logLevel, kj::String message)\n    : timestamp(timestamp),\n      logLevel(logLevel),\n      message(kj::mv(message)) {}\n\nvoid Log::copyTo(rpc::Trace::Log::Builder builder) const {\n  builder.setTimestampNs((timestamp - kj::UNIX_EPOCH) / kj::NANOSECONDS);\n  builder.setLogLevel(logLevel);\n  builder.setMessage(message);\n}\n\nLog Log::clone() const {\n  return Log(timestamp, logLevel, kj::str(message));\n}\n\nException::Exception(\n    kj::Date timestamp, kj::String name, kj::String message, kj::Maybe<kj::String> stack)\n    : timestamp(timestamp),\n      name(kj::mv(name)),\n      message(kj::mv(message)),\n      stack(kj::mv(stack)) {}\n\nLog::Log(rpc::Trace::Log::Reader reader)\n    : timestamp(kj::UNIX_EPOCH + reader.getTimestampNs() * kj::NANOSECONDS),\n      logLevel(reader.getLogLevel()),\n      message(kj::str(reader.getMessage())) {}\n\nException::Exception(rpc::Trace::Exception::Reader reader)\n    : timestamp(kj::UNIX_EPOCH + reader.getTimestampNs() * kj::NANOSECONDS),\n      name(kj::str(reader.getName())),\n      message(kj::str(reader.getMessage())) {\n  if (reader.hasStack()) {\n    stack = kj::str(reader.getStack());\n  }\n}\n\nvoid Exception::copyTo(rpc::Trace::Exception::Builder builder) const {\n  builder.setTimestampNs((timestamp - kj::UNIX_EPOCH) / kj::NANOSECONDS);\n  builder.setName(name);\n  builder.setMessage(message);\n  KJ_IF_SOME(s, stack) {\n    builder.setStack(s);\n  }\n}\n\nException Exception::clone() const {\n  return Exception(timestamp, kj::str(name), kj::str(message), mapCopyString(stack));\n}\n}  // namespace tracing\n\nTrace::Trace(kj::Maybe<kj::String> stableId,\n    kj::Maybe<kj::String> scriptName,\n    kj::Maybe<kj::Own<ScriptVersion::Reader>> scriptVersion,\n    kj::Maybe<kj::String> dispatchNamespace,\n    kj::Maybe<kj::String> scriptId,\n    kj::Array<kj::String> scriptTags,\n    kj::Maybe<kj::String> entrypoint,\n    ExecutionModel executionModel,\n    kj::Maybe<kj::String> durableObjectId)\n    : stableId(kj::mv(stableId)),\n      scriptName(kj::mv(scriptName)),\n      scriptVersion(kj::mv(scriptVersion)),\n      dispatchNamespace(kj::mv(dispatchNamespace)),\n      scriptId(kj::mv(scriptId)),\n      scriptTags(kj::mv(scriptTags)),\n      entrypoint(kj::mv(entrypoint)),\n      durableObjectId(kj::mv(durableObjectId)),\n      executionModel(executionModel) {}\nTrace::Trace(rpc::Trace::Reader reader) {\n  mergeFrom(reader, PipelineLogLevel::FULL);\n}\n\nTrace::~Trace() noexcept(false) {}\n\nvoid Trace::copyTo(rpc::Trace::Builder builder) const {\n  {\n    auto list = builder.initLogs(logs.size());\n    for (auto i: kj::indices(logs)) {\n      logs[i].copyTo(list[i]);\n    }\n  }\n\n  {\n    auto list = builder.initExceptions(exceptions.size());\n    for (auto i: kj::indices(exceptions)) {\n      exceptions[i].copyTo(list[i]);\n    }\n  }\n\n  builder.setTruncated(truncated);\n  builder.setOutcome(outcome);\n  builder.setCpuTime(cpuTime / kj::MILLISECONDS);\n  builder.setWallTime(wallTime / kj::MILLISECONDS);\n  KJ_IF_SOME(name, scriptName) {\n    builder.setScriptName(name);\n  }\n  KJ_IF_SOME(version, scriptVersion) {\n    builder.setScriptVersion(*version);\n  }\n  KJ_IF_SOME(id, scriptId) {\n    builder.setScriptId(id);\n  }\n  KJ_IF_SOME(ns, dispatchNamespace) {\n    builder.setDispatchNamespace(ns);\n  }\n  builder.setExecutionModel(executionModel);\n\n  {\n    auto list = builder.initScriptTags(scriptTags.size());\n    for (auto i: kj::indices(scriptTags)) {\n      list.set(i, scriptTags[i]);\n    }\n  }\n\n  KJ_IF_SOME(tags, tailAttributes) {\n    auto list = builder.initTailAttributes(tags.size());\n    for (auto i: kj::indices(tags)) {\n      tags[i].copyTo(list[i]);\n    }\n  }\n\n  KJ_IF_SOME(e, entrypoint) {\n    builder.setEntrypoint(e);\n  }\n\n  KJ_IF_SOME(id, durableObjectId) {\n    builder.setDurableObjectId(id);\n  }\n\n  builder.setEventTimestampNs((eventTimestamp - kj::UNIX_EPOCH) / kj::NANOSECONDS);\n\n  auto eventInfoBuilder = builder.initEventInfo();\n  KJ_IF_SOME(e, eventInfo) {\n    KJ_SWITCH_ONEOF(e) {\n      KJ_CASE_ONEOF(fetch, tracing::FetchEventInfo) {\n        auto fetchBuilder = eventInfoBuilder.initFetch();\n        fetch.copyTo(fetchBuilder);\n      }\n      KJ_CASE_ONEOF(jsRpc, tracing::JsRpcEventInfo) {\n        auto jsRpcBuilder = eventInfoBuilder.initJsRpc();\n        jsRpc.copyTo(jsRpcBuilder);\n      }\n      KJ_CASE_ONEOF(scheduled, tracing::ScheduledEventInfo) {\n        auto scheduledBuilder = eventInfoBuilder.initScheduled();\n        scheduled.copyTo(scheduledBuilder);\n      }\n      KJ_CASE_ONEOF(alarm, tracing::AlarmEventInfo) {\n        auto alarmBuilder = eventInfoBuilder.initAlarm();\n        alarm.copyTo(alarmBuilder);\n      }\n      KJ_CASE_ONEOF(queue, tracing::QueueEventInfo) {\n        auto queueBuilder = eventInfoBuilder.initQueue();\n        queue.copyTo(queueBuilder);\n      }\n      KJ_CASE_ONEOF(email, tracing::EmailEventInfo) {\n        auto emailBuilder = eventInfoBuilder.initEmail();\n        email.copyTo(emailBuilder);\n      }\n      KJ_CASE_ONEOF(trace, tracing::TraceEventInfo) {\n        auto traceBuilder = eventInfoBuilder.initTrace();\n        trace.copyTo(traceBuilder);\n      }\n      KJ_CASE_ONEOF(hibWs, tracing::HibernatableWebSocketEventInfo) {\n        auto hibWsBuilder = eventInfoBuilder.initHibernatableWebSocket();\n        hibWs.copyTo(hibWsBuilder);\n      }\n      KJ_CASE_ONEOF(custom, tracing::CustomEventInfo) {\n        eventInfoBuilder.initCustom();\n      }\n    }\n  } else {\n    eventInfoBuilder.setNone();\n  }\n\n  KJ_IF_SOME(fetchResponseInfo, this->fetchResponseInfo) {\n    auto fetchResponseInfoBuilder = builder.initResponse();\n    fetchResponseInfo.copyTo(fetchResponseInfoBuilder);\n  }\n\n  {\n    auto list = builder.initDiagnosticChannelEvents(diagnosticChannelEvents.size());\n    for (auto i: kj::indices(diagnosticChannelEvents)) {\n      diagnosticChannelEvents[i].copyTo(list[i]);\n    }\n  }\n}\n\nvoid Trace::mergeFrom(rpc::Trace::Reader reader, PipelineLogLevel pipelineLogLevel) {\n  // Sandboxed workers currently record their traces as if the pipeline log level were set to\n  // \"full\", so we may need to filter out the extra data after receiving the traces back.\n  if (pipelineLogLevel != PipelineLogLevel::NONE) {\n    logs.addAll(reader.getLogs());\n    exceptions.addAll(reader.getExceptions());\n    diagnosticChannelEvents.addAll(reader.getDiagnosticChannelEvents());\n  }\n\n  truncated = reader.getTruncated();\n  outcome = reader.getOutcome();\n  cpuTime = reader.getCpuTime() * kj::MILLISECONDS;\n  wallTime = reader.getWallTime() * kj::MILLISECONDS;\n\n  // mergeFrom() is called both when deserializing traces from a sandboxed\n  // worker and when deserializing traces sent to a sandboxed trace worker. In\n  // the former case, the trace's scriptName (and other fields like\n  // scriptVersion) are already set and the deserialized value is missing, so\n  // we need to be careful not to overwrite the set value.\n  if (reader.hasScriptName()) {\n    scriptName = kj::str(reader.getScriptName());\n  }\n\n  if (reader.hasScriptVersion()) {\n    scriptVersion = capnp::clone(reader.getScriptVersion());\n  }\n\n  if (reader.hasScriptId()) {\n    scriptId = kj::str(reader.getScriptId());\n  }\n\n  if (reader.hasDispatchNamespace()) {\n    dispatchNamespace = kj::str(reader.getDispatchNamespace());\n  }\n  executionModel = reader.getExecutionModel();\n\n  if (auto tags = reader.getScriptTags(); tags.size() > 0) {\n    scriptTags = KJ_MAP(tag, tags) { return kj::str(tag); };\n  }\n\n  if (auto tags = reader.getTailAttributes(); tags.size() > 0) {\n    tailAttributes = KJ_MAP(tag, tags) { return tracing::Attribute(tag); };\n  }\n\n  if (reader.hasEntrypoint()) {\n    entrypoint = kj::str(reader.getEntrypoint());\n  }\n\n  if (reader.hasDurableObjectId()) {\n    durableObjectId = kj::str(reader.getDurableObjectId());\n  }\n\n  eventTimestamp = kj::UNIX_EPOCH + reader.getEventTimestampNs() * kj::NANOSECONDS;\n\n  if (pipelineLogLevel == PipelineLogLevel::NONE) {\n    eventInfo = kj::none;\n  } else {\n    auto e = reader.getEventInfo();\n    switch (e.which()) {\n      case rpc::Trace::EventInfo::Which::FETCH:\n        eventInfo = tracing::FetchEventInfo(e.getFetch());\n        break;\n      case rpc::Trace::EventInfo::Which::JS_RPC:\n        eventInfo = tracing::JsRpcEventInfo(e.getJsRpc());\n        break;\n      case rpc::Trace::EventInfo::Which::SCHEDULED:\n        eventInfo = tracing::ScheduledEventInfo(e.getScheduled());\n        break;\n      case rpc::Trace::EventInfo::Which::ALARM:\n        eventInfo = tracing::AlarmEventInfo(e.getAlarm());\n        break;\n      case rpc::Trace::EventInfo::Which::QUEUE:\n        eventInfo = tracing::QueueEventInfo(e.getQueue());\n        break;\n      case rpc::Trace::EventInfo::Which::EMAIL:\n        eventInfo = tracing::EmailEventInfo(e.getEmail());\n        break;\n      case rpc::Trace::EventInfo::Which::TRACE:\n        eventInfo = tracing::TraceEventInfo(e.getTrace());\n        break;\n      case rpc::Trace::EventInfo::Which::HIBERNATABLE_WEB_SOCKET:\n        eventInfo = tracing::HibernatableWebSocketEventInfo(e.getHibernatableWebSocket());\n        break;\n      case rpc::Trace::EventInfo::Which::CUSTOM:\n        eventInfo = tracing::CustomEventInfo(e.getCustom());\n        break;\n      case rpc::Trace::EventInfo::Which::NONE:\n        eventInfo = kj::none;\n        break;\n    }\n  }\n\n  if (reader.hasResponse()) {\n    fetchResponseInfo = tracing::FetchResponseInfo(reader.getResponse());\n  }\n}\n\nnamespace tracing {\n\nAttribute::Attribute(kj::ConstString name, Value&& value)\n    : name(kj::mv(name)),\n      value(kj::arr(kj::mv(value))) {}\n\nAttribute::Attribute(kj::ConstString name, Values&& value)\n    : name(kj::mv(name)),\n      value(kj::mv(value)) {}\n\nnamespace {\nkj::Array<Attribute::Value> readValues(const rpc::Trace::Attribute::Reader& reader) {\n  // There should always be a value and it always have at least one entry in the list.\n  KJ_ASSERT(reader.hasValue());\n  auto value = reader.getValue();\n  return KJ_MAP(v, value) { return deserializeTagValue(v); };\n}\n\nkj::Maybe<FetchResponseInfo> readReturnInfo(const rpc::Trace::Return::Reader& reader) {\n  auto info = reader.getInfo();\n  switch (info.which()) {\n    case rpc::Trace::Return::Info::EMPTY:\n      return kj::none;\n    case rpc::Trace::Return::Info::FETCH: {\n      return kj::Maybe(FetchResponseInfo(info.getFetch()));\n    }\n  }\n  KJ_UNREACHABLE;\n}\n}  // namespace\n\nAttribute::Attribute(rpc::Trace::Attribute::Reader reader)\n    : name(kj::str(reader.getName())),\n      value(readValues(reader)) {}\n\nvoid Attribute::copyTo(rpc::Trace::Attribute::Builder builder) const {\n  builder.setName(name.asPtr());\n  auto vec = builder.initValue(value.size());\n  for (size_t n = 0; n < value.size(); n++) {\n    serializeTagValue(vec[n], value[n]);\n  }\n}\n\nAttribute Attribute::clone() const {\n  return Attribute(name.clone(), KJ_MAP(v, value) { return spanTagClone(v); });\n}\n\nkj::String Attribute::toString() const {\n  return kj::str(\"Attribute: \", name, \", \", value);\n}\n\nReturn::Return(kj::Maybe<FetchResponseInfo> info): info(kj::mv(info)) {}\nReturn::Return(rpc::Trace::Return::Reader reader): info(readReturnInfo(reader)) {}\n\nvoid Return::copyTo(rpc::Trace::Return::Builder builder) const {\n  KJ_IF_SOME(fetchInfo, info) {\n    auto infoBuilder = builder.initInfo();\n    fetchInfo.copyTo(infoBuilder.initFetch());\n  }\n}\n\nReturn Return::clone() const {\n  KJ_IF_SOME(fetchInfo, info) {\n    return Return(kj::Maybe(fetchInfo.clone()));\n  }\n  return Return();\n}\n\nSpanOpen::SpanOpen(SpanId spanId, kj::ConstString operationName, kj::Maybe<Info> info)\n    : operationName(kj::mv(operationName)),\n      info(kj::mv(info)),\n      spanId(spanId) {}\n\nnamespace {\nkj::Maybe<SpanOpen::Info> readSpanOpenInfo(rpc::Trace::SpanOpen::Reader& reader) {\n  auto info = reader.getInfo();\n  switch (info.which()) {\n    case rpc::Trace::SpanOpen::Info::EMPTY:\n      return kj::none;\n    case rpc::Trace::SpanOpen::Info::FETCH: {\n      return kj::Maybe(FetchEventInfo(info.getFetch()));\n    }\n    case rpc::Trace::SpanOpen::Info::JS_RPC: {\n      return kj::Maybe(JsRpcEventInfo(info.getJsRpc()));\n    }\n    case rpc::Trace::SpanOpen::Info::CUSTOM: {\n      auto custom = info.getCustom();\n      return kj::Maybe(KJ_MAP(a, custom) { return Attribute(a); });\n    }\n  }\n  KJ_UNREACHABLE;\n}\n}  // namespace\n\nSpanOpen::SpanOpen(rpc::Trace::SpanOpen::Reader reader)\n    : operationName(kj::str(reader.getOperationName())),\n      info(readSpanOpenInfo(reader)),\n      spanId(reader.getSpanId()) {}\n\nvoid SpanOpen::copyTo(rpc::Trace::SpanOpen::Builder builder) const {\n  builder.setOperationName(operationName.asPtr());\n  builder.setSpanId(spanId);\n  KJ_IF_SOME(i, info) {\n    auto infoBuilder = builder.initInfo();\n    KJ_SWITCH_ONEOF(i) {\n      KJ_CASE_ONEOF(fetch, FetchEventInfo) {\n        fetch.copyTo(infoBuilder.initFetch());\n      }\n      KJ_CASE_ONEOF(jsrpc, JsRpcEventInfo) {\n        jsrpc.copyTo(infoBuilder.initJsRpc());\n      }\n      KJ_CASE_ONEOF(custom, CustomInfo) {\n        auto customBuilder = infoBuilder.initCustom(custom.size());\n        for (size_t n = 0; n < custom.size(); n++) {\n          custom[n].copyTo(customBuilder[n]);\n        }\n      }\n    }\n  }\n}\n\nSpanOpen SpanOpen::clone() const {\n  constexpr auto cloneInfo = [](const kj::Maybe<Info>& info) -> kj::Maybe<SpanOpen::Info> {\n    return info.map([](const Info& info) -> SpanOpen::Info {\n      KJ_SWITCH_ONEOF(info) {\n        KJ_CASE_ONEOF(fetch, FetchEventInfo) {\n          return fetch.clone();\n        }\n        KJ_CASE_ONEOF(jsrpc, JsRpcEventInfo) {\n          return jsrpc.clone();\n        }\n        KJ_CASE_ONEOF(custom, CustomInfo) {\n          return KJ_MAP(attr, custom) { return attr.clone(); };\n        }\n      }\n      KJ_UNREACHABLE;\n    });\n  };\n  return SpanOpen(spanId, operationName.clone(), cloneInfo(info));\n}\n\nkj::String KJ_STRINGIFY(const SpanOpen::Info& info) {\n  KJ_SWITCH_ONEOF(info) {\n    KJ_CASE_ONEOF(fetch, FetchEventInfo) {\n      return fetch.toString();\n    }\n    KJ_CASE_ONEOF(jsrpc, JsRpcEventInfo) {\n      return jsrpc.toString();\n    }\n    KJ_CASE_ONEOF(customInfo, CustomInfo) {\n      return kj::str(customInfo);\n    }\n  }\n  KJ_UNREACHABLE\n}\n\nkj::String SpanOpen::toString() const {\n  return kj::str(\"SpanOpen:\", operationName, \", \", info);\n}\n\nSpanClose::SpanClose(EventOutcome outcome): outcome(outcome) {}\n\nSpanClose::SpanClose(rpc::Trace::SpanClose::Reader reader): outcome(reader.getOutcome()) {}\n\nvoid SpanClose::copyTo(rpc::Trace::SpanClose::Builder builder) const {\n  builder.setOutcome(outcome);\n}\n\nSpanClose SpanClose::clone() const {\n  return SpanClose(outcome);\n}\n\nkj::String SpanClose::toString() const {\n  return kj::str(\"SpanClose: \", outcome);\n}\n\nOnset::Info readOnsetInfo(const rpc::Trace::Onset::Info::Reader& info) {\n  switch (info.which()) {\n    case rpc::Trace::Onset::Info::FETCH: {\n      return FetchEventInfo(info.getFetch());\n    }\n    case rpc::Trace::Onset::Info::JS_RPC: {\n      return JsRpcEventInfo(info.getJsRpc());\n    }\n    case rpc::Trace::Onset::Info::SCHEDULED: {\n      return ScheduledEventInfo(info.getScheduled());\n    }\n    case rpc::Trace::Onset::Info::ALARM: {\n      return AlarmEventInfo(info.getAlarm());\n    }\n    case rpc::Trace::Onset::Info::QUEUE: {\n      return QueueEventInfo(info.getQueue());\n    }\n    case rpc::Trace::Onset::Info::EMAIL: {\n      return EmailEventInfo(info.getEmail());\n    }\n    case rpc::Trace::Onset::Info::TRACE: {\n      return TraceEventInfo(info.getTrace());\n    }\n    case rpc::Trace::Onset::Info::HIBERNATABLE_WEB_SOCKET: {\n      return HibernatableWebSocketEventInfo(info.getHibernatableWebSocket());\n    }\n    case rpc::Trace::Onset::Info::CUSTOM: {\n      return CustomEventInfo();\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid writeOnsetInfo(const Onset::Info& info, rpc::Trace::Onset::Info::Builder& infoBuilder) {\n  KJ_SWITCH_ONEOF(info) {\n    KJ_CASE_ONEOF(fetch, FetchEventInfo) {\n      fetch.copyTo(infoBuilder.initFetch());\n    }\n    KJ_CASE_ONEOF(jsrpc, JsRpcEventInfo) {\n      jsrpc.copyTo(infoBuilder.initJsRpc());\n    }\n    KJ_CASE_ONEOF(scheduled, ScheduledEventInfo) {\n      scheduled.copyTo(infoBuilder.initScheduled());\n    }\n    KJ_CASE_ONEOF(alarm, AlarmEventInfo) {\n      alarm.copyTo(infoBuilder.initAlarm());\n    }\n    KJ_CASE_ONEOF(queue, QueueEventInfo) {\n      queue.copyTo(infoBuilder.initQueue());\n    }\n    KJ_CASE_ONEOF(email, EmailEventInfo) {\n      email.copyTo(infoBuilder.initEmail());\n    }\n    KJ_CASE_ONEOF(trace, TraceEventInfo) {\n      trace.copyTo(infoBuilder.initTrace());\n    }\n    KJ_CASE_ONEOF(hws, HibernatableWebSocketEventInfo) {\n      hws.copyTo(infoBuilder.initHibernatableWebSocket());\n    }\n    KJ_CASE_ONEOF(custom, CustomEventInfo) {\n      infoBuilder.initCustom();\n    }\n  }\n}\n\nnamespace {\nkj::Maybe<kj::String> getScriptNameFromReader(const rpc::Trace::Onset::Reader& reader) {\n  if (reader.hasScriptName()) {\n    return kj::str(reader.getScriptName());\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::Own<ScriptVersion::Reader>> getScriptVersionFromReader(\n    const rpc::Trace::Onset::Reader& reader) {\n  if (reader.hasScriptVersion()) {\n    return capnp::clone(reader.getScriptVersion());\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::String> getDispatchNamespaceFromReader(const rpc::Trace::Onset::Reader& reader) {\n  if (reader.hasDispatchNamespace()) {\n    return kj::str(reader.getDispatchNamespace());\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::String> getScriptIdFromReader(const rpc::Trace::Onset::Reader& reader) {\n  if (reader.hasScriptId()) {\n    return kj::str(reader.getScriptId());\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::Array<kj::String>> getScriptTagsFromReader(const rpc::Trace::Onset::Reader& reader) {\n  if (reader.hasScriptTags()) {\n    auto tags = reader.getScriptTags();\n    kj::Vector<kj::String> scriptTags(tags.size());\n    for (const auto& tag: tags) {\n      scriptTags.add(kj::str(tag));\n    }\n    return kj::Maybe(scriptTags.releaseAsArray());\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::String> getEntrypointFromReader(const rpc::Trace::Onset::Reader& reader) {\n  if (reader.hasEntryPoint()) {\n    return kj::str(reader.getEntryPoint());\n  }\n  return kj::none;\n}\nOnset::WorkerInfo getWorkerInfoFromReader(const rpc::Trace::Onset::Reader& reader) {\n  return Onset::WorkerInfo{\n    .executionModel = reader.getExecutionModel(),\n    .scriptName = getScriptNameFromReader(reader),\n    .scriptVersion = getScriptVersionFromReader(reader),\n    .dispatchNamespace = getDispatchNamespaceFromReader(reader),\n    .scriptId = getScriptIdFromReader(reader),\n    .scriptTags = getScriptTagsFromReader(reader),\n    .entrypoint = getEntrypointFromReader(reader),\n  };\n}\n}  // namespace\n\nOnset::Onset(\n    SpanId spanId, Onset::Info&& info, Onset::WorkerInfo&& workerInfo, CustomInfo attributes)\n    : spanId(spanId),\n      info(kj::mv(info)),\n      workerInfo(kj::mv(workerInfo)),\n      attributes(kj::mv(attributes)) {}\n\nOnset::Onset(rpc::Trace::Onset::Reader reader)\n    : spanId(reader.getSpanId()),\n      info(readOnsetInfo(reader.getInfo())),\n      workerInfo(getWorkerInfoFromReader(reader)),\n      attributes(KJ_MAP(attr, reader.getAttributes()) { return Attribute(attr); }) {}\n\nvoid Onset::copyTo(rpc::Trace::Onset::Builder builder) const {\n  builder.setExecutionModel(workerInfo.executionModel);\n  builder.setSpanId(spanId);\n  KJ_IF_SOME(name, workerInfo.scriptName) {\n    builder.setScriptName(name);\n  }\n  KJ_IF_SOME(version, workerInfo.scriptVersion) {\n    builder.setScriptVersion(*version);\n  }\n  KJ_IF_SOME(name, workerInfo.dispatchNamespace) {\n    builder.setDispatchNamespace(name);\n  }\n  KJ_IF_SOME(scriptId, workerInfo.scriptId) {\n    builder.setScriptId(scriptId);\n  }\n  KJ_IF_SOME(tags, workerInfo.scriptTags) {\n    auto list = builder.initScriptTags(tags.size());\n    for (size_t i = 0; i < tags.size(); i++) {\n      list.set(i, tags[i]);\n    }\n  }\n  KJ_IF_SOME(e, workerInfo.entrypoint) {\n    builder.setEntryPoint(e);\n  }\n  auto infoBuilder = builder.initInfo();\n  writeOnsetInfo(info, infoBuilder);\n\n  auto attributeBuilder = builder.initAttributes(attributes.size());\n  for (size_t n = 0; n < attributes.size(); n++) {\n    attributes[n].copyTo(attributeBuilder[n]);\n  }\n}\n\nOnset::WorkerInfo Onset::WorkerInfo::clone() const {\n  return WorkerInfo{\n    .executionModel = executionModel,\n    .scriptName = mapCopyString(scriptName),\n    .scriptVersion = scriptVersion.map([](auto& version) { return capnp::clone(*version); }),\n    .dispatchNamespace = mapCopyString(dispatchNamespace),\n    .scriptId = mapCopyString(scriptId),\n    .scriptTags =\n        scriptTags.map([](auto& tags) { return KJ_MAP(tag, tags) { return kj::str(tag); }; }),\n    .entrypoint = mapCopyString(entrypoint),\n  };\n}\n\nEventInfo cloneEventInfo(const EventInfo& info) {\n  KJ_SWITCH_ONEOF(info) {\n    KJ_CASE_ONEOF(fetch, FetchEventInfo) {\n      return fetch.clone();\n    }\n    KJ_CASE_ONEOF(jsrpc, JsRpcEventInfo) {\n      return jsrpc.clone();\n    }\n    KJ_CASE_ONEOF(scheduled, ScheduledEventInfo) {\n      return scheduled.clone();\n    }\n    KJ_CASE_ONEOF(alarm, AlarmEventInfo) {\n      return alarm.clone();\n    }\n    KJ_CASE_ONEOF(queue, QueueEventInfo) {\n      return queue.clone();\n    }\n    KJ_CASE_ONEOF(email, EmailEventInfo) {\n      return email.clone();\n    }\n    KJ_CASE_ONEOF(trace, TraceEventInfo) {\n      return trace.clone();\n    }\n    KJ_CASE_ONEOF(hws, HibernatableWebSocketEventInfo) {\n      return hws.clone();\n    }\n    KJ_CASE_ONEOF(custom, CustomEventInfo) {\n      return CustomEventInfo();\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nOnset Onset::clone() const {\n  return Onset(spanId, cloneEventInfo(info), workerInfo.clone(),\n      KJ_MAP(attr, attributes) { return attr.clone(); });\n}\n\nOutcome::Outcome(EventOutcome outcome, kj::Duration cpuTime, kj::Duration wallTime)\n    : outcome(outcome),\n      cpuTime(cpuTime),\n      wallTime(wallTime) {}\n\nOutcome::Outcome(rpc::Trace::Outcome::Reader reader)\n    : outcome(reader.getOutcome()),\n      cpuTime(reader.getCpuTime() * kj::MILLISECONDS),\n      wallTime(reader.getWallTime() * kj::MILLISECONDS) {}\n\nvoid Outcome::copyTo(rpc::Trace::Outcome::Builder builder) const {\n  builder.setOutcome(outcome);\n  builder.setCpuTime(cpuTime / kj::MILLISECONDS);\n  builder.setWallTime(wallTime / kj::MILLISECONDS);\n}\n\nOutcome Outcome::clone() const {\n  return Outcome(outcome, cpuTime, wallTime);\n}\n\nTailEvent::TailEvent(\n    SpanContext context, TraceId invocationId, kj::Date timestamp, kj::uint sequence, Event&& event)\n    : spanContext(kj::mv(context)),\n      invocationId(invocationId),\n      timestamp(timestamp),\n      sequence(sequence),\n      event(kj::mv(event)) {}\n\nTailEvent::TailEvent(TraceId traceId,\n    TraceId invocationId,\n    kj::Maybe<SpanId> spanId,\n    kj::Date timestamp,\n    kj::uint sequence,\n    Event&& event)\n    : spanContext(kj::mv(traceId), kj::mv(spanId)),\n      invocationId(kj::mv(invocationId)),\n      timestamp(timestamp),\n      sequence(sequence),\n      event(kj::mv(event)) {}\n\nnamespace {\nTailEvent::Event readEventFromTailEvent(const rpc::Trace::TailEvent::Reader& reader) {\n  const auto event = reader.getEvent();\n  switch (event.which()) {\n    case rpc::Trace::TailEvent::Event::ONSET: {\n      return Onset(event.getOnset());\n    }\n    case rpc::Trace::TailEvent::Event::OUTCOME: {\n      return Outcome(event.getOutcome());\n    }\n    case rpc::Trace::TailEvent::Event::SPAN_OPEN: {\n      return SpanOpen(event.getSpanOpen());\n    }\n    case rpc::Trace::TailEvent::Event::SPAN_CLOSE: {\n      return SpanClose(event.getSpanClose());\n    }\n    case rpc::Trace::TailEvent::Event::ATTRIBUTE: {\n      auto listReader = event.getAttribute();\n      kj::Vector<Attribute> attrs(listReader.size());\n      for (const auto& reader: listReader) {\n        attrs.add(Attribute(reader));\n      }\n      return attrs.releaseAsArray();\n    }\n    case rpc::Trace::TailEvent::Event::RETURN: {\n      return Return(event.getReturn());\n    }\n    case rpc::Trace::TailEvent::Event::DIAGNOSTIC_CHANNEL_EVENT: {\n      return DiagnosticChannelEvent(event.getDiagnosticChannelEvent());\n    }\n    case rpc::Trace::TailEvent::Event::EXCEPTION: {\n      return Exception(event.getException());\n    }\n    case rpc::Trace::TailEvent::Event::LOG: {\n      return Log(event.getLog());\n    }\n    case rpc::Trace::TailEvent::Event::STREAM_DIAGNOSTICS: {\n      return StreamDiagnosticsEvent(event.getStreamDiagnostics());\n    }\n  }\n  KJ_UNREACHABLE;\n}\n}  // namespace\n\nTailEvent::TailEvent(rpc::Trace::TailEvent::Reader reader)\n    : spanContext(SpanContext::fromCapnp(reader.getSpanContext())),\n      invocationId(TraceId::fromCapnp(reader.getInvocationId())),\n      timestamp(kj::UNIX_EPOCH + reader.getTimestampNs() * kj::NANOSECONDS),\n      sequence(reader.getSequence()),\n      event(readEventFromTailEvent(reader)) {}\n\nvoid TailEvent::copyTo(rpc::Trace::TailEvent::Builder builder) const {\n  spanContext.toCapnp(builder.initSpanContext());\n  invocationId.toCapnp(builder.initInvocationId());\n  builder.setTimestampNs((timestamp - kj::UNIX_EPOCH) / kj::NANOSECONDS);\n  builder.setSequence(sequence);\n  auto eventBuilder = builder.initEvent();\n  KJ_SWITCH_ONEOF(event) {\n    KJ_CASE_ONEOF(onset, Onset) {\n      onset.copyTo(eventBuilder.initOnset());\n    }\n    KJ_CASE_ONEOF(outcome, Outcome) {\n      outcome.copyTo(eventBuilder.initOutcome());\n    }\n    KJ_CASE_ONEOF(open, SpanOpen) {\n      open.copyTo(eventBuilder.initSpanOpen());\n    }\n    KJ_CASE_ONEOF(close, SpanClose) {\n      close.copyTo(eventBuilder.initSpanClose());\n    }\n    KJ_CASE_ONEOF(diag, DiagnosticChannelEvent) {\n      diag.copyTo(eventBuilder.initDiagnosticChannelEvent());\n    }\n    KJ_CASE_ONEOF(ex, Exception) {\n      ex.copyTo(eventBuilder.initException());\n    }\n    KJ_CASE_ONEOF(log, Log) {\n      log.copyTo(eventBuilder.initLog());\n    }\n    KJ_CASE_ONEOF(streamDiag, StreamDiagnosticsEvent) {\n      streamDiag.copyTo(eventBuilder.initStreamDiagnostics());\n    }\n    KJ_CASE_ONEOF(ret, Return) {\n      ret.copyTo(eventBuilder.initReturn());\n    }\n    KJ_CASE_ONEOF(attrs, CustomInfo) {\n      // Mark is a collection of attributes.\n      auto attrBuilder = eventBuilder.initAttribute(attrs.size());\n      for (size_t n = 0; n < attrs.size(); n++) {\n        attrs[n].copyTo(attrBuilder[n]);\n      }\n    }\n  }\n}\n\nTailEvent TailEvent::clone() const {\n  constexpr auto cloneEvent = [](const Event& event) -> Event {\n    KJ_SWITCH_ONEOF(event) {\n      KJ_CASE_ONEOF(onset, Onset) {\n        return onset.clone();\n      }\n      KJ_CASE_ONEOF(outcome, Outcome) {\n        return outcome.clone();\n      }\n      KJ_CASE_ONEOF(open, SpanOpen) {\n        return open.clone();\n      }\n      KJ_CASE_ONEOF(close, SpanClose) {\n        return close.clone();\n      }\n      KJ_CASE_ONEOF(diag, DiagnosticChannelEvent) {\n        return diag.clone();\n      }\n      KJ_CASE_ONEOF(ex, Exception) {\n        return ex.clone();\n      }\n      KJ_CASE_ONEOF(log, Log) {\n        return log.clone();\n      }\n      KJ_CASE_ONEOF(streamDiag, StreamDiagnosticsEvent) {\n        return streamDiag.clone();\n      }\n      KJ_CASE_ONEOF(ret, Return) {\n        return ret.clone();\n      }\n      KJ_CASE_ONEOF(attrs, CustomInfo) {\n        return KJ_MAP(attr, attrs) { return attr.clone(); };\n      }\n    }\n    KJ_UNREACHABLE;\n  };\n  return TailEvent(spanContext.getTraceId(), invocationId, spanContext.getSpanId(), timestamp,\n      sequence, cloneEvent(event));\n}\n\nvoid CompleteSpan::copyTo(rpc::UserSpanData::Builder builder) const {\n  builder.setOperationName(operationName.asPtr());\n  builder.setStartTimeNs((startTime - kj::UNIX_EPOCH) / kj::NANOSECONDS);\n  builder.setEndTimeNs((endTime - kj::UNIX_EPOCH) / kj::NANOSECONDS);\n  builder.setSpanId(spanId);\n  builder.setParentSpanId(parentSpanId);\n\n  auto tagsParam = builder.initTags(tags.size());\n  auto i = 0;\n  for (auto& tag: tags) {\n    auto tagParam = tagsParam[i++];\n    tagParam.setKey(tag.key.asPtr());\n    serializeTagValue(tagParam.initValue(), tag.value);\n  }\n}\n\nCompleteSpan::CompleteSpan(rpc::UserSpanData::Reader reader)\n    : spanId(reader.getSpanId()),\n      parentSpanId(reader.getParentSpanId()),\n      operationName(kj::str(reader.getOperationName())),\n      startTime(kj::UNIX_EPOCH + reader.getStartTimeNs() * kj::NANOSECONDS),\n      endTime(kj::UNIX_EPOCH + reader.getEndTimeNs() * kj::NANOSECONDS) {\n  auto tagsParam = reader.getTags();\n  tags.reserve(tagsParam.size());\n  for (auto tagParam: tagsParam) {\n    tags.insert(kj::ConstString(kj::heapString(tagParam.getKey())),\n        deserializeTagValue(tagParam.getValue()));\n  }\n}\n\nSpanOpenData::SpanOpenData(rpc::SpanOpenData::Reader reader)\n    : spanId(reader.getSpanId()),\n      parentSpanId(reader.getParentSpanId()),\n      operationName(kj::str(reader.getOperationName())),\n      startTime(kj::UNIX_EPOCH + reader.getStartTimeNs() * kj::NANOSECONDS) {}\n\nvoid SpanOpenData::copyTo(rpc::SpanOpenData::Builder builder) const {\n  builder.setOperationName(operationName.asPtr());\n  builder.setStartTimeNs((startTime - kj::UNIX_EPOCH) / kj::NANOSECONDS);\n  builder.setSpanId(spanId);\n  builder.setParentSpanId(parentSpanId);\n}\n\nSpanEndData::SpanEndData(rpc::SpanEndData::Reader reader)\n    : spanId(reader.getSpanId()),\n      endTime(kj::UNIX_EPOCH + reader.getEndTimeNs() * kj::NANOSECONDS) {\n  auto tagsParam = reader.getTags();\n  tags.reserve(tagsParam.size());\n  for (auto tagParam: tagsParam) {\n    tags.insert(kj::ConstString(kj::heapString(tagParam.getKey())),\n        deserializeTagValue(tagParam.getValue()));\n  }\n}\n\nvoid SpanEndData::copyTo(rpc::SpanEndData::Builder builder) const {\n  builder.setEndTimeNs((endTime - kj::UNIX_EPOCH) / kj::NANOSECONDS);\n  builder.setSpanId(spanId);\n\n  auto tagsParam = builder.initTags(tags.size());\n  auto i = 0;\n  for (auto& tag: tags) {\n    auto tagParam = tagsParam[i++];\n    tagParam.setKey(tag.key.asPtr());\n    serializeTagValue(tagParam.initValue(), tag.value);\n  }\n}\n}  // namespace tracing\n\n// ======================================================================================\n\nSpanBuilder::SpanBuilder(kj::Maybe<kj::Own<SpanObserver>> observer,\n    kj::ConstString operationName,\n    kj::Maybe<kj::Date> startTime) {\n  KJ_IF_SOME(obs, observer) {\n    // TODO(o11y): Once we report the user tracing spanOpen event as soon as a span is created, we\n    // should be able to fold this virtual call and just get the timestamp directly.\n    kj::Date time = startTime.orDefault([&]() { return obs->getTime(); });\n    span.emplace(kj::mv(operationName), time);\n    this->observer = kj::mv(obs);\n  }\n}\n\nSpanBuilder& SpanBuilder::operator=(SpanBuilder&& other) {\n  end();\n  observer = kj::mv(other.observer);\n  span = kj::mv(other.span);\n  return *this;\n}\n\nSpanBuilder::~SpanBuilder() noexcept(false) {\n  end();\n}\n\nvoid SpanBuilder::end() {\n  KJ_IF_SOME(o, observer) {\n    KJ_IF_SOME(s, span) {\n      // TODO(performance): Fold this timer call if we are using I/O time, where we will look up\n      // I/O time later.\n      s.endTime = kj::systemPreciseCalendarClock().now();\n      o->report(s);\n      span = kj::none;\n    }\n  }\n}\n\nvoid SpanBuilder::setOperationName(kj::ConstString operationName) {\n  KJ_IF_SOME(s, span) {\n    s.operationName = kj::mv(operationName);\n  }\n}\n\nvoid SpanBuilder::setTag(kj::ConstString key, TagInitValue value) {\n  KJ_IF_SOME(s, span) {\n    // We allow passing a LiteralStringConst or StringPtr so that we don't have to allocate memory\n    // if we're not being observed.\n    TagValue v = [](TagInitValue value) -> Span::TagValue {\n      KJ_SWITCH_ONEOF(value) {\n        KJ_CASE_ONEOF(str, kj::StringPtr) {\n          return kj::ConstString(kj::str(str));\n        }\n        KJ_CASE_ONEOF(str, kj::LiteralStringConst) {\n          return kj::ConstString(str);\n        }\n        KJ_CASE_ONEOF(str, kj::ConstString) {\n          return kj::mv(str);\n        }\n        KJ_CASE_ONEOF(str, kj::String) {\n          return kj::ConstString(kj::mv(str));\n        }\n        KJ_CASE_ONEOF(val, int64_t) {\n          return val;\n        }\n        KJ_CASE_ONEOF(val, double) {\n          return val;\n        }\n        KJ_CASE_ONEOF(val, bool) {\n          return val;\n        }\n      }\n      KJ_UNREACHABLE;\n    }(kj::mv(value));\n\n    auto keyPtr = key.asPtr();\n    s.tags.upsert(kj::mv(key), kj::mv(v), [keyPtr](TagValue& existingValue, TagValue&& newValue) {\n      // This is a programming error, but not a serious one. We could alternatively just emit\n      // duplicate tags and leave the Jaeger UI in charge of warning about them.\n      [[maybe_unused]] static auto logged = [keyPtr]() {\n        if (isPredictableModeForTest()) {\n          // Logging in ERROR level to have this fail loudly during testing.\n          KJ_LOG(ERROR, \"overwriting previous tag\", keyPtr);\n        } else {\n          KJ_LOG(WARNING, \"overwriting previous tag\", keyPtr);\n        }\n        return true;\n      }();\n      existingValue = kj::mv(newValue);\n    });\n  }\n}\n\nvoid SpanBuilder::addLog(kj::Date timestamp, kj::ConstString key, TagValue value) {\n  KJ_IF_SOME(s, span) {\n    if (s.logs.size() >= Span::MAX_LOGS) {\n      ++s.droppedLogs;\n    } else {\n      s.logs.add(Span::Log{.timestamp = timestamp,\n        .tag = {\n          .key = kj::mv(key),\n          .value = kj::mv(value),\n        }});\n    }\n  }\n}\n\nvoid TraceContext::setTag(kj::ConstString key, SpanBuilder::TagInitValue value) {\n  if (!isObserved()) {\n    return;\n  }\n  // Fast path (without string allocations) if only some spans are observed.\n  if (!span.isObserved()) {\n    userSpan.setTag(kj::mv(key), kj::mv(value));\n    return;\n  }\n  if (!userSpan.isObserved()) {\n    span.setTag(kj::mv(key), kj::mv(value));\n    return;\n  }\n\n  // We need to duplicate the key and value since both are move-only types.\n  // Clone the value based on its type.\n  KJ_SWITCH_ONEOF(value) {\n    KJ_CASE_ONEOF(s, kj::StringPtr) {\n      span.setTag(key.clone(), s);\n      userSpan.setTag(kj::mv(key), s);\n    }\n    KJ_CASE_ONEOF(s, kj::String) {\n      span.setTag(key.clone(), kj::str(s));\n      userSpan.setTag(kj::mv(key), kj::mv(s));\n    }\n    KJ_CASE_ONEOF(s, kj::LiteralStringConst) {\n      span.setTag(key.clone(), s);\n      userSpan.setTag(kj::mv(key), s);\n    }\n    KJ_CASE_ONEOF(s, kj::ConstString) {\n      span.setTag(key.clone(), s.clone());\n      userSpan.setTag(kj::mv(key), kj::mv(s));\n    }\n    KJ_CASE_ONEOF(b, bool) {\n      span.setTag(key.clone(), b);\n      userSpan.setTag(kj::mv(key), b);\n    }\n    KJ_CASE_ONEOF(d, double) {\n      span.setTag(key.clone(), d);\n      userSpan.setTag(kj::mv(key), d);\n    }\n    KJ_CASE_ONEOF(i, int64_t) {\n      span.setTag(key.clone(), i);\n      userSpan.setTag(kj::mv(key), i);\n    }\n  }\n}\n\nSpan::TagValue spanTagClone(const Span::TagValue& tag) {\n  KJ_SWITCH_ONEOF(tag) {\n    KJ_CASE_ONEOF(str, kj::ConstString) {\n      return str.clone();\n    }\n    KJ_CASE_ONEOF(val, int64_t) {\n      return val;\n    }\n    KJ_CASE_ONEOF(val, double) {\n      return val;\n    }\n    KJ_CASE_ONEOF(val, bool) {\n      return val;\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nusing RpcValue = rpc::TagValue;\nvoid serializeTagValue(RpcValue::Builder builder, const Span::TagValue& value) {\n  KJ_SWITCH_ONEOF(value) {\n    KJ_CASE_ONEOF(b, bool) {\n      builder.setBool(b);\n    }\n    KJ_CASE_ONEOF(i, int64_t) {\n      builder.setInt64(i);\n    }\n    KJ_CASE_ONEOF(d, double) {\n      builder.setFloat64(d);\n    }\n    KJ_CASE_ONEOF(s, kj::ConstString) {\n      builder.setString(s.asPtr());\n    }\n  }\n}\n\nSpan::TagValue deserializeTagValue(RpcValue::Reader value) {\n  switch (value.which()) {\n    case RpcValue::BOOL:\n      return value.getBool();\n    case RpcValue::FLOAT64:\n      return value.getFloat64();\n    case RpcValue::INT64:\n      return value.getInt64();\n    case RpcValue::STRING:\n      return kj::ConstString(kj::heapString(value.getString()));\n    default:\n      KJ_UNREACHABLE;\n  }\n}\n\nScopedDurationTagger::ScopedDurationTagger(\n    SpanBuilder& span, kj::ConstString key, const kj::MonotonicClock& timer)\n    : span(span),\n      key(kj::mv(key)),\n      timer(timer),\n      startTime(timer.now()) {}\n\nScopedDurationTagger::~ScopedDurationTagger() noexcept(false) {\n  auto duration = timer.now() - startTime;\n  if (isPredictableModeForTest()) {\n    duration = 0 * kj::NANOSECONDS;\n  }\n  span.setTag(kj::mv(key), duration / kj::NANOSECONDS);\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/trace.capnp",
    "content": "# Copyright (c) 2017-2022 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n@0xc40f73be329a38d9;\n\nusing Cxx = import \"/capnp/c++.capnp\";\n$Cxx.namespace(\"workerd::rpc\");\n\n# This file contains trace helper structures. The Trace struct is deliberately not defined here –\n# many files just need to have the span/tag definitions available. Since Trace contains interfaces,\n# it also causes a large amount of code to be generated in the .capnp.h file, which affects header\n# parsing overhead/compile times for every file that depends on the capnp file defining Trace.\n\n# The value of a span tag.\nstruct TagValue {\n  union {\n    string @0 :Text;\n    bool @1 :Bool;\n    int64 @2 :Int64;\n    float64 @3 :Float64;\n  }\n}\n\n# A key/value span tag for tracing.\nstruct Tag {\n  key @0 :Text;\n  value @1 :TagValue;\n}\n\nstruct UserSpanData {\n  # Representation of a completed user span\n  operationName @0 :Text;\n\n  startTimeNs @1 :Int64;\n  endTimeNs @2 :Int64;\n  # Nanoseconds since Unix epoch\n\n  tags @3 :List(Tag);\n\n  spanId @4 :UInt64;\n  parentSpanId @5 :UInt64;\n}\n\nstruct SpanOpenData {\n  # Representation of a SpanOpen event, created when a user span is opened.\n  operationName @0 :Text;\n\n  startTimeNs @1 :Int64;\n  # Nanoseconds since Unix epoch\n\n  spanId @2 :UInt64;\n  parentSpanId @3 :UInt64;\n}\n\nstruct SpanEndData {\n  # Representation of an event that indicates completion of a user span. This information is\n  # provided to the streaming tail worker in the Attributes and SpanClose events.\n\n  endTimeNs @0 :Int64;\n  # Nanoseconds since Unix epoch\n\n  # List of span attributes\n  tags @1 :List(Tag);\n  spanId @2 :UInt64;\n}\n"
  },
  {
    "path": "src/workerd/io/trace.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/io/outcome.capnp.h>\n#include <workerd/io/trace.capnp.h>\n#include <workerd/io/worker-interface.capnp.h>\n#include <workerd/jsg/memory.h>\n#include <workerd/util/own-util.h>\n\n#include <kj/map.h>\n#include <kj/one-of.h>\n#include <kj/refcount.h>\n#include <kj/string.h>\n#include <kj/time.h>\n#include <kj/vector.h>\n\n#include <concepts>\n#include <initializer_list>\n\nnamespace kj {\nenum class HttpMethod;\nclass EntropySource;\n}  // namespace kj\n\nnamespace workerd {\n\nusing kj::byte;\nusing kj::uint;\n\nusing LogLevel = rpc::Trace::Log::Level;\nusing ExecutionModel = rpc::Trace::ExecutionModel;\n\nclass Trace;\n\nnamespace tracing {\n// A 128-bit globally unique trace identifier. This will be used for both\n// external and internal tracing. Specifically, for internal tracing, this\n// is used to represent tracing IDs for jaeger traces. For external tracing,\n// this is used for both the trace ID and invocation ID for tail workers.\nclass TraceId final {\n public:\n  // A null trace ID. This is only acceptable for use in tests.\n  constexpr TraceId(decltype(nullptr)) {}\n\n  // A trace ID with the given low and high values.\n  constexpr TraceId(uint64_t low, uint64_t high): low(low), high(high) {}\n\n  constexpr TraceId(const TraceId& other) = default;\n  constexpr TraceId& operator=(const TraceId& other) = default;\n\n  constexpr TraceId(TraceId&& other): low(other.low), high(other.high) {\n    other.low = 0;\n    other.high = 0;\n  }\n\n  constexpr TraceId& operator=(TraceId&& other) {\n    low = other.low;\n    high = other.high;\n    other.low = 0;\n    other.high = 0;\n    return *this;\n  }\n\n  constexpr TraceId& operator=(decltype(nullptr)) {\n    low = 0;\n    high = 0;\n    return *this;\n  }\n\n  constexpr bool operator==(const TraceId& other) const {\n    return low == other.low && high == other.high;\n  }\n  constexpr bool operator==(decltype(nullptr)) const {\n    return low == 0 && high == 0;\n  }\n  constexpr operator bool() const {\n    return low || high;\n  }\n\n  operator kj::String() const {\n    return toGoString();\n  }\n\n  // Replicates Jaeger go library's string serialization.\n  kj::String toGoString() const;\n\n  // Replicates Jaeger go library's protobuf serialization.\n  kj::Array<byte> toProtobuf() const;\n\n  // Replicates W3C Serialization\n  kj::String toW3C() const;\n\n  // Creates a random Trace Id, optionally using a given entropy source. If an\n  // entropy source is not given, then we fallback to using BoringSSL's RAND_bytes.\n  static TraceId fromEntropy(kj::Maybe<kj::EntropySource&> entropy = kj::none);\n\n  // Replicates Jaeger go library's string serialization.\n  static kj::Maybe<TraceId> fromGoString(kj::ArrayPtr<const char> s);\n\n  // Replicates Jaeger go library's protobuf serialization.\n  static kj::Maybe<TraceId> fromProtobuf(kj::ArrayPtr<const kj::byte> buf);\n\n  // A null trace ID. This is really only acceptable for use in tests.\n  static const TraceId nullId;\n\n  inline uint64_t getLow() const {\n    return low;\n  }\n  inline uint64_t getHigh() const {\n    return high;\n  }\n\n  static TraceId fromCapnp(rpc::TraceId::Reader reader);\n  void toCapnp(rpc::TraceId::Builder writer) const;\n\n private:\n  uint64_t low = 0;\n  uint64_t high = 0;\n};\nconstexpr TraceId TraceId::nullId = nullptr;\n\n// A 64-bit span identifier.\nclass SpanId final {\n public:\n  // A null span ID. This is only acceptable for use in tests.\n  constexpr SpanId(decltype(nullptr)): id(0) {}\n\n  constexpr SpanId(uint64_t id): id(id) {}\n  constexpr SpanId(const SpanId& other) = default;\n  constexpr SpanId& operator=(const SpanId& other) = default;\n  constexpr SpanId(SpanId&& other): id(other.id) {\n    other.id = 0;\n  }\n  constexpr SpanId& operator=(SpanId&& other) {\n    id = other.id;\n    other.id = 0;\n    return *this;\n  }\n  constexpr operator bool() const {\n    return id != 0;\n  }\n  constexpr bool operator==(const SpanId& other) const {\n    return id == other.id;\n  }\n  constexpr bool operator==(decltype(nullptr)) const {\n    return id == 0;\n  }\n\n  inline operator kj::String() const {\n    return toGoString();\n  }\n\n  inline operator uint64_t() const {\n    return id;\n  }\n\n  kj::String toGoString() const;\n\n  static const SpanId nullId;\n\n  constexpr uint64_t getId() const {\n    return id;\n  }\n\n  static SpanId fromEntropy(kj::Maybe<kj::EntropySource&> entropy = kj::none);\n\n private:\n  uint64_t id;\n};\nconstexpr SpanId SpanId::nullId = nullptr;\n// Fixed spanId value to be used for tests\nconstexpr uint64_t staticSpanId = 0x2a2a2a2a2a2a2a2aULL;\n\n// The InvocationSpanContext is a tuple of a trace id, invocation id, and span id.\n// The trace id represents a top-level request and should be shared across all\n// invocation spans and events within those spans. The invocation id identifies\n// a specific worker invocation. The span id identifies a specific span within an\n// invocation. Every invocation of every worker should have an InvocationSpanContext.\n// That may or may not have a trigger InvocationSpanContext.\nclass InvocationSpanContext final {\n public:\n  // The constructor is public only so kj::rc can see it and create a new instance.\n  // User code should use the static factory methods or the newChild method.\n  InvocationSpanContext(kj::Badge<InvocationSpanContext>,\n      kj::Maybe<kj::EntropySource&> entropySource,\n      TraceId traceId,\n      TraceId invocationId,\n      SpanId spanId,\n      kj::Maybe<const InvocationSpanContext&> parentSpanContext = kj::none);\n  // Still need a constructor to be available as long as span context is not propagated everywhere\n  // we need it.\n  InvocationSpanContext(TraceId traceId, TraceId invocationId, SpanId spanId)\n      : traceId(traceId),\n        invocationId(invocationId),\n        spanId(spanId) {};\n\n  KJ_DISALLOW_COPY(InvocationSpanContext);\n\n  InvocationSpanContext(InvocationSpanContext&& other) = default;\n  InvocationSpanContext& operator=(InvocationSpanContext&& other) = default;\n\n  inline bool operator==(const InvocationSpanContext& other) const {\n    return traceId == other.traceId && invocationId == other.invocationId && spanId == other.spanId;\n  }\n\n  inline const TraceId& getTraceId() const {\n    return traceId;\n  }\n\n  inline const TraceId& getInvocationId() const {\n    return invocationId;\n  }\n\n  inline const SpanId& getSpanId() const {\n    return spanId;\n  }\n\n  inline kj::Maybe<const InvocationSpanContext&> getParent() const {\n    KJ_IF_SOME(p, parentSpanContext) {\n      return *p;\n    }\n    return kj::none;\n  }\n\n  // Creates a new child span. If the current context does not have an entropy\n  // source this will assert. If isTrigger() is true then it will not have an\n  // entropy source.\n  InvocationSpanContext newChild() const;\n\n  // An InvocationSpanContext is a trigger context if it has no entropy source.\n  // This generally means the SpanContext was create from a capnp message and\n  // represents an InvocationSpanContext that was propagated from a parent\n  // or triggering context.\n  bool isTrigger() const {\n    return entropySource == kj::none;\n  }\n\n  // Creates a new InvocationSpanContext. If the triggerContext is given, then its\n  // traceId is used as the traceId for the newly created context. Otherwise a new\n  // traceId is generated. The invocationId is always generated new and the spanId\n  // will be 0 with no parent span.\n  static InvocationSpanContext newForInvocation(\n      kj::Maybe<const InvocationSpanContext&> triggerContext = kj::none,\n      kj::Maybe<kj::EntropySource&> entropySource = kj::none);\n\n  // Creates a new InvocationSpanContext from a capnp message. The returned\n  // InvocationSpanContext will not be capable of creating child spans and\n  // is considered only a \"trigger\" span.\n  static kj::Maybe<InvocationSpanContext> fromCapnp(rpc::InvocationSpanContext::Reader reader);\n  void toCapnp(rpc::InvocationSpanContext::Builder writer) const;\n  InvocationSpanContext clone() const;\n\n private:\n  // If there is no entropy source, then child spans cannot be created from\n  // this InvocationSpanContext.\n  kj::Maybe<kj::EntropySource&> entropySource;\n  TraceId traceId;\n  TraceId invocationId;\n  SpanId spanId;\n\n  // The parentSpanContext can be either a direct parent or a trigger\n  // context. If it is a trigger context, then it should have the same\n  // traceId but a different invocationId (unless predictable mode for\n  // testing is enabled). The isTrigger() should also return true.\n  kj::Maybe<kj::Own<InvocationSpanContext>> parentSpanContext;\n};\n\n// SpanContext as used for streaming tail worker tail events. spanId is always set except for Onset\n// events that don't inherit context from another invocation.\nstruct SpanContext {\n  SpanContext(TraceId traceId, kj::Maybe<SpanId> spanId): traceId(traceId), spanId(spanId) {};\n  KJ_DISALLOW_COPY(SpanContext);\n  SpanContext(SpanContext&& other) = default;\n  SpanContext& operator=(SpanContext&& other) = default;\n\n  inline bool operator==(const SpanContext& other) const {\n    return traceId == other.traceId && spanId == other.spanId;\n  }\n\n  inline const TraceId& getTraceId() const {\n    return traceId;\n  }\n\n  inline kj::Maybe<SpanId> getSpanId() const {\n    return spanId;\n  }\n\n  static SpanContext fromCapnp(rpc::SpanContext::Reader reader);\n  void toCapnp(rpc::SpanContext::Builder writer) const;\n  SpanContext clone() const;\n\n private:\n  TraceId traceId;\n  kj::Maybe<SpanId> spanId;\n};\n\nkj::String KJ_STRINGIFY(const SpanId& id);\nkj::String KJ_STRINGIFY(const TraceId& id);\nkj::String KJ_STRINGIFY(const InvocationSpanContext& context);\nkj::String KJ_STRINGIFY(const SpanContext& context);\n\n// The various structs defined below are used in both buffered tail workers\n// and streaming tail workers to report tail events.\n\n// Describes a fetch request\nstruct FetchEventInfo final {\n  struct Header;\n\n  explicit FetchEventInfo(\n      kj::HttpMethod method, kj::String url, kj::String cfJson, kj::Array<Header> headers);\n  FetchEventInfo(rpc::Trace::FetchEventInfo::Reader reader);\n  FetchEventInfo(FetchEventInfo&&) = default;\n  FetchEventInfo& operator=(FetchEventInfo&&) = default;\n  KJ_DISALLOW_COPY(FetchEventInfo);\n\n  struct Header final {\n    explicit Header(kj::String name, kj::String value);\n    Header(rpc::Trace::FetchEventInfo::Header::Reader reader);\n    Header(Header&&) = default;\n    Header& operator=(Header&&) = default;\n    KJ_DISALLOW_COPY(Header);\n\n    kj::String name;\n    kj::String value;\n\n    void copyTo(rpc::Trace::FetchEventInfo::Header::Builder builder) const;\n    Header clone() const;\n    kj::String toString() const;\n\n    JSG_MEMORY_INFO(Header) {\n      tracker.trackField(\"name\", name);\n      tracker.trackField(\"value\", value);\n    }\n  };\n\n  kj::HttpMethod method;\n  kj::String url;\n  // TODO(perf): It might be more efficient to store some sort of parsed JSON result instead?\n  kj::String cfJson;\n  kj::Array<Header> headers;\n\n  void copyTo(rpc::Trace::FetchEventInfo::Builder builder) const;\n  FetchEventInfo clone() const;\n  kj::String toString() const;\n};\n\n// Describes a jsrpc request\nstruct JsRpcEventInfo final {\n  explicit JsRpcEventInfo(kj::String methodName);\n  JsRpcEventInfo(rpc::Trace::JsRpcEventInfo::Reader reader);\n  JsRpcEventInfo(JsRpcEventInfo&&) = default;\n  JsRpcEventInfo& operator=(JsRpcEventInfo&&) = default;\n  KJ_DISALLOW_COPY(JsRpcEventInfo);\n\n  kj::String methodName;\n\n  void copyTo(rpc::Trace::JsRpcEventInfo::Builder builder) const;\n  JsRpcEventInfo clone() const;\n  kj::String toString() const;\n};\n\n// Describes a scheduled request\nstruct ScheduledEventInfo final {\n  explicit ScheduledEventInfo(double scheduledTime, kj::String cron);\n  ScheduledEventInfo(rpc::Trace::ScheduledEventInfo::Reader reader);\n  ScheduledEventInfo(ScheduledEventInfo&&) = default;\n  ScheduledEventInfo& operator=(ScheduledEventInfo&&) = default;\n  KJ_DISALLOW_COPY(ScheduledEventInfo);\n\n  double scheduledTime;\n  kj::String cron;\n\n  void copyTo(rpc::Trace::ScheduledEventInfo::Builder builder) const;\n  ScheduledEventInfo clone() const;\n};\n\n// Describes a Durable Object alarm request\nstruct AlarmEventInfo final {\n  explicit AlarmEventInfo(kj::Date scheduledTime);\n  AlarmEventInfo(rpc::Trace::AlarmEventInfo::Reader reader);\n  AlarmEventInfo(AlarmEventInfo&&) = default;\n  AlarmEventInfo& operator=(AlarmEventInfo&&) = default;\n  KJ_DISALLOW_COPY(AlarmEventInfo);\n\n  kj::Date scheduledTime;\n\n  void copyTo(rpc::Trace::AlarmEventInfo::Builder builder) const;\n  AlarmEventInfo clone() const;\n};\n\n// Describes a queue worker request\nstruct QueueEventInfo final {\n  explicit QueueEventInfo(kj::String queueName, uint32_t batchSize);\n  QueueEventInfo(rpc::Trace::QueueEventInfo::Reader reader);\n  QueueEventInfo(QueueEventInfo&&) = default;\n  QueueEventInfo& operator=(QueueEventInfo&&) = default;\n  KJ_DISALLOW_COPY(QueueEventInfo);\n\n  kj::String queueName;\n  uint32_t batchSize;\n\n  void copyTo(rpc::Trace::QueueEventInfo::Builder builder) const;\n  QueueEventInfo clone() const;\n};\n\n// Describes an email request\nstruct EmailEventInfo final {\n  explicit EmailEventInfo(kj::String mailFrom, kj::String rcptTo, uint32_t rawSize);\n  EmailEventInfo(rpc::Trace::EmailEventInfo::Reader reader);\n  EmailEventInfo(EmailEventInfo&&) = default;\n  EmailEventInfo& operator=(EmailEventInfo&&) = default;\n  KJ_DISALLOW_COPY(EmailEventInfo);\n\n  kj::String mailFrom;\n  kj::String rcptTo;\n  uint32_t rawSize;\n\n  void copyTo(rpc::Trace::EmailEventInfo::Builder builder) const;\n  EmailEventInfo clone() const;\n};\n\n// Describes a buffered tail worker request\nstruct TraceEventInfo final {\n  struct TraceItem;\n\n  explicit TraceEventInfo(kj::ArrayPtr<const kj::Own<Trace>> traces);\n  TraceEventInfo(kj::Array<TraceItem> traces): traces(kj::mv(traces)) {}\n  TraceEventInfo(rpc::Trace::TraceEventInfo::Reader reader);\n  TraceEventInfo(TraceEventInfo&&) = default;\n  TraceEventInfo& operator=(TraceEventInfo&&) = default;\n  KJ_DISALLOW_COPY(TraceEventInfo);\n\n  struct TraceItem final {\n    explicit TraceItem(kj::Maybe<kj::String> scriptName);\n    TraceItem(rpc::Trace::TraceEventInfo::TraceItem::Reader reader);\n    TraceItem(TraceItem&&) = default;\n    TraceItem& operator=(TraceItem&&) = default;\n    KJ_DISALLOW_COPY(TraceItem);\n\n    kj::Maybe<kj::String> scriptName;\n\n    void copyTo(rpc::Trace::TraceEventInfo::TraceItem::Builder builder) const;\n    TraceItem clone() const;\n  };\n\n  kj::Vector<TraceItem> traces;\n\n  void copyTo(rpc::Trace::TraceEventInfo::Builder builder) const;\n  TraceEventInfo clone() const;\n};\n\n// Describes a hibernatable web socket event\nstruct HibernatableWebSocketEventInfo final {\n  struct Message final {};\n  struct Close final {\n    uint16_t code;\n    bool wasClean;\n  };\n  struct Error final {};\n\n  using Type = kj::OneOf<Message, Close, Error>;\n\n  explicit HibernatableWebSocketEventInfo(Type type);\n  HibernatableWebSocketEventInfo(rpc::Trace::HibernatableWebSocketEventInfo::Reader reader);\n  HibernatableWebSocketEventInfo(HibernatableWebSocketEventInfo&&) = default;\n  HibernatableWebSocketEventInfo& operator=(HibernatableWebSocketEventInfo&&) = default;\n  KJ_DISALLOW_COPY(HibernatableWebSocketEventInfo);\n\n  Type type;\n\n  void copyTo(rpc::Trace::HibernatableWebSocketEventInfo::Builder builder) const;\n  HibernatableWebSocketEventInfo clone() const;\n  static Type readFrom(rpc::Trace::HibernatableWebSocketEventInfo::Reader reader);\n};\n\n// Describes a custom event\nstruct CustomEventInfo final {\n  explicit CustomEventInfo() {};\n  CustomEventInfo(rpc::Trace::CustomEventInfo::Reader reader) {};\n};\n\n// Describes a fetch response\nstruct FetchResponseInfo final {\n  explicit FetchResponseInfo(uint16_t statusCode);\n  FetchResponseInfo(rpc::Trace::FetchResponseInfo::Reader reader);\n  FetchResponseInfo(FetchResponseInfo&&) = default;\n  FetchResponseInfo& operator=(FetchResponseInfo&&) = default;\n  KJ_DISALLOW_COPY(FetchResponseInfo);\n\n  uint16_t statusCode;\n\n  void copyTo(rpc::Trace::FetchResponseInfo::Builder builder) const;\n  FetchResponseInfo clone() const;\n};\n\n// Describes an event published using the node:diagnostics_channel API\nstruct DiagnosticChannelEvent final {\n  explicit DiagnosticChannelEvent(\n      kj::Date timestamp, kj::String channel, kj::Array<kj::byte> message);\n  DiagnosticChannelEvent(rpc::Trace::DiagnosticChannelEvent::Reader reader);\n  DiagnosticChannelEvent(DiagnosticChannelEvent&&) = default;\n  KJ_DISALLOW_COPY(DiagnosticChannelEvent);\n\n  kj::Date timestamp;\n  kj::String channel;\n  kj::Array<kj::byte> message;\n\n  void copyTo(rpc::Trace::DiagnosticChannelEvent::Builder builder) const;\n  DiagnosticChannelEvent clone() const;\n};\n\n// Describes a stream diagnostics event. Currently only droppedEvents is supported.\nstruct StreamDiagnosticsEvent final {\n  explicit StreamDiagnosticsEvent(uint32_t droppedEventsCount);\n  StreamDiagnosticsEvent(rpc::Trace::StreamDiagnosticsEvent::Reader reader);\n  StreamDiagnosticsEvent(StreamDiagnosticsEvent&&) = default;\n  KJ_DISALLOW_COPY(StreamDiagnosticsEvent);\n\n  // The count of dropped events for the \"droppedEvents\" diagnostic. When we support other event\n  // types, this should be replaced with a kj::OneOf<> of all the different types.\n  uint32_t droppedEventsCount;\n\n  void copyTo(rpc::Trace::StreamDiagnosticsEvent::Builder builder) const;\n  StreamDiagnosticsEvent clone() const;\n};\n\n// Describes a log event\nstruct Log final {\n  explicit Log(kj::Date timestamp, LogLevel logLevel, kj::String message);\n  Log(rpc::Trace::Log::Reader reader);\n  Log(Log&&) = default;\n  KJ_DISALLOW_COPY(Log);\n  ~Log() noexcept(false) = default;\n\n  // Only as accurate as Worker's Date.now(), for Spectre mitigation.\n  kj::Date timestamp;\n\n  LogLevel logLevel;\n  // TODO(soon): Just string for now.  Eventually, capture serialized JS objects.\n  kj::String message;\n\n  void copyTo(rpc::Trace::Log::Builder builder) const;\n  Log clone() const;\n};\n\n// Describes an exception event\nstruct Exception final {\n  explicit Exception(\n      kj::Date timestamp, kj::String name, kj::String message, kj::Maybe<kj::String> stack);\n  Exception(rpc::Trace::Exception::Reader reader);\n  Exception(Exception&&) = default;\n  KJ_DISALLOW_COPY(Exception);\n  ~Exception() noexcept(false) = default;\n\n  // Only as accurate as Worker's Date.now(), for Spectre mitigation.\n  kj::Date timestamp;\n\n  kj::String name;\n  kj::String message;\n\n  kj::Maybe<kj::String> stack;\n\n  void copyTo(rpc::Trace::Exception::Builder builder) const;\n  Exception clone() const;\n};\n\n// EventInfo types are used to describe the onset of an invocation. The FetchEventInfo\n// can also be used to describe the start of a fetch subrequest.\nusing EventInfo = kj::OneOf<FetchEventInfo,\n    JsRpcEventInfo,\n    ScheduledEventInfo,\n    AlarmEventInfo,\n    QueueEventInfo,\n    EmailEventInfo,\n    TraceEventInfo,\n    HibernatableWebSocketEventInfo,\n    CustomEventInfo>;\n\nEventInfo cloneEventInfo(const EventInfo& info);\n\ntemplate <typename T>\nconcept AttributeValue = kj::isSameType<kj::ConstString, T>() || kj::isSameType<bool, T>() ||\n    kj::isSameType<double, T>() || kj::isSameType<int64_t, T>();\n\n// An Attribute mark is used to add detail to a span over its lifetime.\n// The Attribute struct can also be used to provide arbitrary additional\n// properties for some other structs.\n// Modeled after https://opentelemetry.io/docs/concepts/signals/traces/#attributes\nstruct Attribute final {\n  using Value = kj::OneOf<kj::ConstString, bool, double, int64_t>;\n  using Values = kj::Array<Value>;\n\n  explicit Attribute(kj::ConstString name, Value&& value);\n  explicit Attribute(kj::ConstString name, Values&& values);\n\n  template <AttributeValue V>\n  explicit Attribute(kj::ConstString name, kj::Array<V> vals)\n      : Attribute(kj::mv(name), KJ_MAP(v, vals) { return Value(kj::mv(v)); }) {}\n\n  template <AttributeValue V>\n  explicit Attribute(kj::ConstString name, std::initializer_list<V> list)\n      : Attribute(kj::mv(name), kj::heapArray<V>(list)) {}\n\n  Attribute(rpc::Trace::Attribute::Reader reader);\n  Attribute(Attribute&&) = default;\n  Attribute& operator=(Attribute&&) = default;\n  KJ_DISALLOW_COPY(Attribute);\n\n  kj::ConstString name;\n  Values value;\n\n  void copyTo(rpc::Trace::Attribute::Builder builder) const;\n  Attribute clone() const;\n  kj::String toString() const;\n};\nusing CustomInfo = kj::Array<Attribute>;\nkj::String KJ_STRINGIFY(const CustomInfo& customInfo);\n\nstruct CompleteSpan {\n  // Represents a completed span within user tracing.\n  tracing::SpanId spanId;\n  tracing::SpanId parentSpanId;\n\n  kj::ConstString operationName;\n  kj::Date startTime;\n  kj::Date endTime;\n  // Should be Span::TagMap, but we can't forward-declare that.\n  kj::HashMap<kj::ConstString, tracing::Attribute::Value> tags;\n\n  CompleteSpan(rpc::UserSpanData::Reader reader);\n  void copyTo(rpc::UserSpanData::Builder builder) const;\n  explicit CompleteSpan(tracing::SpanId spanId,\n      tracing::SpanId parentSpanId,\n      kj::ConstString operationName,\n      kj::Date startTime,\n      kj::Date endTime,\n      kj::HashMap<kj::ConstString, tracing::Attribute::Value> tags =\n          kj::HashMap<kj::ConstString, tracing::Attribute::Value>())\n      : spanId(spanId),\n        parentSpanId(parentSpanId),\n        operationName(kj::mv(operationName)),\n        startTime(startTime),\n        endTime(endTime),\n        tags(kj::mv(tags)) {}\n};\n\nstruct SpanOpenData {\n  // Represents the data needed for a SpanOpen event\n  tracing::SpanId spanId;\n  tracing::SpanId parentSpanId;\n\n  kj::ConstString operationName;\n  kj::Date startTime;\n\n  SpanOpenData(rpc::SpanOpenData::Reader reader);\n  void copyTo(rpc::SpanOpenData::Builder builder) const;\n  explicit SpanOpenData(tracing::SpanId spanId,\n      tracing::SpanId parentSpanId,\n      kj::ConstString operationName,\n      kj::Date startTime)\n      : spanId(spanId),\n        parentSpanId(parentSpanId),\n        operationName(kj::mv(operationName)),\n        startTime(startTime) {}\n};\n\nstruct SpanEndData {\n  // Represents the data needed when closing a span, including the Attributes and SpanClose events.\n  tracing::SpanId spanId;\n\n  kj::Date endTime;\n  // Should be Span::TagMap, but we can't forward-declare that.\n  kj::HashMap<kj::ConstString, tracing::Attribute::Value> tags;\n\n  SpanEndData(rpc::SpanEndData::Reader reader);\n  void copyTo(rpc::SpanEndData::Builder builder) const;\n  explicit SpanEndData(tracing::SpanId spanId,\n      kj::Date endTime,\n      kj::HashMap<kj::ConstString, tracing::Attribute::Value> tags =\n          kj::HashMap<kj::ConstString, tracing::Attribute::Value>())\n      : spanId(spanId),\n        endTime(endTime),\n        tags(kj::mv(tags)) {}\n};\n\n// A Return mark is used to mark the point at which a span operation returned\n// a value. For instance, when a fetch subrequest response is received, or when\n// the fetch handler returns a Response. Importantly, it does not signal that the\n// span has closed, which may not happen for some period of time after the return\n// mark is recorded (e.g. due to things like waitUntils or waiting to fully ready\n// the response body payload, etc).\nstruct Return final {\n  explicit Return(kj::Maybe<FetchResponseInfo> info = kj::none);\n  Return(rpc::Trace::Return::Reader reader);\n  Return(Return&&) = default;\n  Return& operator=(Return&&) = default;\n  KJ_DISALLOW_COPY(Return);\n\n  kj::Maybe<FetchResponseInfo> info = kj::none;\n\n  void copyTo(rpc::Trace::Return::Builder builder) const;\n  Return clone() const;\n};\n\n// Mark events no longer have a corresponding type, but the term generally refers to DiagnosticChannelEvent, Exception, Log, Return, and CustomInfo events.\n\n// Marks the opening of a child span within the streaming tail session.\nstruct SpanOpen final {\n  // If the span represents a subrequest, then the info describes the\n  // details of that subrequest.\n  using Info = kj::OneOf<FetchEventInfo, JsRpcEventInfo, CustomInfo>;\n\n  explicit SpanOpen(SpanId spanId, kj::ConstString operationName, kj::Maybe<Info> info = kj::none);\n  SpanOpen(rpc::Trace::SpanOpen::Reader reader);\n  SpanOpen(SpanOpen&&) = default;\n  SpanOpen& operator=(SpanOpen&&) = default;\n  KJ_DISALLOW_COPY(SpanOpen);\n\n  kj::ConstString operationName;\n  kj::Maybe<Info> info = kj::none;\n  SpanId spanId;\n\n  void copyTo(rpc::Trace::SpanOpen::Builder builder) const;\n  SpanOpen clone() const;\n  kj::String toString() const;\n};\n\n// Marks the closing of a child span within the streaming tail session.\n// Once emitted, no further mark events should occur within the closed\n// span.\nstruct SpanClose final {\n  explicit SpanClose(EventOutcome outcome = EventOutcome::OK);\n  SpanClose(rpc::Trace::SpanClose::Reader reader);\n  SpanClose(SpanClose&&) = default;\n  SpanClose& operator=(SpanClose&&) = default;\n  KJ_DISALLOW_COPY(SpanClose);\n\n  EventOutcome outcome = EventOutcome::OK;\n\n  void copyTo(rpc::Trace::SpanClose::Builder builder) const;\n  SpanClose clone() const;\n  kj::String toString() const;\n};\n\n// The Onset and Outcome event types are special forms of SpanOpen and\n// SpanClose that explicitly mark the start and end of the root span.\n// A streaming tail session will always begin with an Onset event, and\n// always end with an Outcome event.\nstruct Onset final {\n  using Info = EventInfo;\n\n  // Information about the worker that is being tailed.\n  struct WorkerInfo final {\n    ExecutionModel executionModel = ExecutionModel::STATELESS;\n    kj::Maybe<kj::String> scriptName;\n    kj::Maybe<kj::Own<ScriptVersion::Reader>> scriptVersion;\n    kj::Maybe<kj::String> dispatchNamespace;\n    kj::Maybe<kj::String> scriptId;\n    kj::Maybe<kj::Array<kj::String>> scriptTags;\n    kj::Maybe<kj::String> entrypoint;\n\n    WorkerInfo clone() const;\n  };\n\n  explicit Onset(\n      tracing::SpanId spanId, Info&& info, WorkerInfo&& workerInfo, CustomInfo attributes);\n\n  Onset(rpc::Trace::Onset::Reader reader);\n  Onset(Onset&&) = default;\n  Onset& operator=(Onset&&) = default;\n  KJ_DISALLOW_COPY(Onset);\n\n  tracing::SpanId spanId;\n  Info info;\n  WorkerInfo workerInfo;\n  CustomInfo attributes;\n\n  void copyTo(rpc::Trace::Onset::Builder builder) const;\n  Onset clone() const;\n};\n\n// Helper functions to copy onset info to/from rpc reader\nOnset::Info readOnsetInfo(const rpc::Trace::Onset::Info::Reader& info);\nvoid writeOnsetInfo(const tracing::Onset::Info& info, rpc::Trace::Onset::Info::Builder& builder);\n\nstruct Outcome final {\n  explicit Outcome(EventOutcome outcome, kj::Duration cpuTime, kj::Duration wallTime);\n  Outcome(rpc::Trace::Outcome::Reader reader);\n  Outcome(Outcome&&) = default;\n  Outcome& operator=(Outcome&&) = default;\n  KJ_DISALLOW_COPY(Outcome);\n\n  EventOutcome outcome = EventOutcome::OK;\n  kj::Duration cpuTime;\n  kj::Duration wallTime;\n\n  void copyTo(rpc::Trace::Outcome::Builder builder) const;\n  Outcome clone() const;\n  kj::String toString() const;\n};\n\n// A streaming tail worker receives a series of Tail Events. Tail events always\n// occur within an InvocationSpanContext. The first TailEvent delivered to a\n// streaming tail session is always an Onset. The final TailEvent delivered is\n// always an Outcome. Between those can be any number of SpanOpen, SpanClose,\n// and Mark events. Every SpanOpen *must* be associated with a SpanClose unless\n// the stream was abruptly terminated.\n// A future version may add support for Link events again.\nstruct TailEvent final {\n  using Event = kj::OneOf<Onset,\n      Outcome,\n      SpanOpen,\n      SpanClose,\n      DiagnosticChannelEvent,\n      Exception,\n      Log,\n      StreamDiagnosticsEvent,\n      Return,\n      CustomInfo>;\n\n  explicit TailEvent(SpanContext context,\n      TraceId invocationId,\n      kj::Date timestamp,\n      kj::uint sequence,\n      Event&& event);\n  TailEvent(TraceId traceId,\n      TraceId invocationId,\n      kj::Maybe<SpanId> spanId,\n      kj::Date timestamp,\n      kj::uint sequence,\n      Event&& event);\n  TailEvent(rpc::Trace::TailEvent::Reader reader);\n  TailEvent(TailEvent&&) = default;\n  TailEvent& operator=(TailEvent&&) = default;\n  KJ_DISALLOW_COPY(TailEvent);\n\n  // The span context this event is associated with.\n  SpanContext spanContext;\n  TraceId invocationId;\n\n  kj::Date timestamp;  // Unix epoch, Spectre-mitigated resolution\n  kj::uint sequence;\n\n  Event event;\n\n  void copyTo(rpc::Trace::TailEvent::Builder builder) const;\n  TailEvent clone() const;\n};\n\nkj::String KJ_STRINGIFY(const tracing::TailEvent::Event& event);\n\n}  // namespace tracing\n\nenum class PipelineLogLevel {\n  // WARNING: This must be kept in sync with PipelineDef::LogLevel (which is not in the OSS\n  //   release).\n  NONE,\n  FULL\n};\n\n// TODO(someday): See if we can merge similar code concepts...  Trace fills a role similar to\n// MetricsCollector::Reporter::StageEvent, and Tracer fills a role similar to\n// MetricsCollector::Request.  Currently, the major differences are:\n//\n//   - MetricsCollector::Request uses its destructor to measure a IoContext's wall time, so\n//     it needs to live exactly as long as its IoContext.  Tracer currently needs to live as\n//     long as both the IoContext and those of any subrequests.\n//   - Due to the difference in lifetimes, results of each become available in a different order,\n//     and intermediate values can be freed at different times.\n//   - Request builds a vector of results, while Tracer builds a tree.\n\n// TODO(cleanup) - worth separating into immutable Trace vs. mutable TraceBuilder?\n\n// Collects trace information about the handling of a worker/pipeline fetch event.\nclass Trace final: public kj::Refcounted {\n public:\n  explicit Trace(kj::Maybe<kj::String> stableId,\n      kj::Maybe<kj::String> scriptName,\n      kj::Maybe<kj::Own<ScriptVersion::Reader>> scriptVersion,\n      kj::Maybe<kj::String> dispatchNamespace,\n      kj::Maybe<kj::String> scriptId,\n      kj::Array<kj::String> scriptTags,\n      kj::Maybe<kj::String> entrypoint,\n      ExecutionModel executionModel,\n      kj::Maybe<kj::String> durableObjectId = kj::none);\n  Trace(rpc::Trace::Reader reader);\n  ~Trace() noexcept(false);\n  KJ_DISALLOW_COPY_AND_MOVE(Trace);\n\n  // Empty for toplevel worker.\n  kj::Maybe<kj::String> stableId;\n\n  // We treat the origin value as \"unset\".\n  kj::Date eventTimestamp = kj::UNIX_EPOCH;\n\n  kj::Maybe<tracing::EventInfo> eventInfo;\n\n  // TODO(someday): Work out what sort of information we may want to convey about the parent\n  // trace, if any.\n\n  kj::Maybe<kj::String> scriptName;\n  kj::Maybe<kj::Own<ScriptVersion::Reader>> scriptVersion;\n  kj::Maybe<kj::String> dispatchNamespace;\n  kj::Maybe<kj::String> scriptId;\n  kj::Array<kj::String> scriptTags;\n  kj::Maybe<kj::Array<tracing::Attribute>> tailAttributes;\n  kj::Maybe<kj::String> entrypoint;\n  kj::Maybe<kj::String> durableObjectId;\n\n  kj::Vector<tracing::Log> logs;\n  // A request's trace can have multiple exceptions due to separate request/waitUntil tasks.\n  kj::Vector<tracing::Exception> exceptions;\n\n  kj::Vector<tracing::DiagnosticChannelEvent> diagnosticChannelEvents;\n\n  ExecutionModel executionModel;\n  EventOutcome outcome = EventOutcome::UNKNOWN;\n\n  kj::Maybe<tracing::FetchResponseInfo> fetchResponseInfo;\n\n  kj::Duration cpuTime;\n  kj::Duration wallTime;\n\n  bool truncated = false;\n  bool exceededLogLimit = false;\n  bool exceededExceptionLimit = false;\n  bool exceededDiagnosticChannelEventLimit = false;\n  // Trace data is recorded outside of the JS heap.  To avoid DoS, we keep an estimate of trace\n  // data size, and we stop recording if too much is used.\n  size_t bytesUsed = 0;\n\n  // Copy content from this trace into `builder`.\n  void copyTo(rpc::Trace::Builder builder) const;\n\n  // Adds all content from `reader` to this `Trace`. (Typically this trace is empty before the\n  // call.)  Also applies filtering to the trace as if it were recorded with the given\n  // pipelineLogLevel.\n  void mergeFrom(rpc::Trace::Reader reader, PipelineLogLevel pipelineLogLevel);\n};\n\n// =======================================================================================\n\n// Helper function used when setting \"truncated_script_id\" tags. Truncates the scriptId to 10\n// characters.\ninline kj::String truncateScriptId(kj::StringPtr id) {\n  auto truncatedId = id.first(kj::min(id.size(), 10));\n  return kj::str(truncatedId);\n}\n\n// =======================================================================================\n// Span tracing\n//\n// TODO(cleanup): (Streaming) tail workers now have access to span tracing as well, but with that\n// the trace worker and span interfaces are still mostly independent of each other; separate span\n// tracing into a separate header.\n\nclass SpanBuilder;\nclass SpanObserver;\n\nstruct Span {\n  // Represents a trace span. `Span` objects are delivered to `SpanObserver`s for recording. To\n  // create a `Span`, use a `SpanBuilder`.\n\n public:\n  using TagValue = tracing::Attribute::Value;\n  // TODO(someday): Support binary bytes, too.\n  using TagMap = kj::HashMap<kj::ConstString, TagValue>;\n  using Tag = TagMap::Entry;\n\n  struct Log {\n    kj::Date timestamp;\n    Tag tag;\n  };\n\n  kj::ConstString operationName;\n  kj::Date startTime;\n  kj::Date endTime;\n  TagMap tags;\n  kj::Vector<Log> logs;\n\n  // We set an arbitrary (-ish) cap on log messages for safety. If we drop logs because of this,\n  // we report how many in a final \"dropped_logs\" log.\n  //\n  // At the risk of being too clever, I chose a limit that is one below a power of two so that\n  // we'll typically have space for one last element available for the \"dropped_logs\" log without\n  // needing to grow the vector.\n  static constexpr auto MAX_LOGS = 1023;\n  uint droppedLogs = 0;\n\n  explicit Span(kj::ConstString operationName, kj::Date startTime)\n      : operationName(kj::mv(operationName)),\n        startTime(startTime),\n        endTime(startTime) {}\n};\n\n// Utility functions for handling span tags.\nvoid serializeTagValue(rpc::TagValue::Builder builder, const Span::TagValue& value);\nSpan::TagValue deserializeTagValue(rpc::TagValue::Reader value);\n\n// Clone function for span tags, avoids memory allocation for string literals and non-string values.\nSpan::TagValue spanTagClone(const Span::TagValue& tag);\n\n// An opaque token which can be used to create child spans of some parent. This is typically\n// passed down from a caller to a callee when the caller wants to allow the callee to create\n// spans for itself that show up as children of the caller's span, but the caller does not\n// want to give the callee any other ability to modify the parent span.\nclass SpanParent {\n public:\n  SpanParent(SpanBuilder& builder);\n\n  // Make a SpanParent that causes children not to be reported anywhere.\n  SpanParent(decltype(nullptr)) {}\n\n  SpanParent(kj::Maybe<kj::Own<SpanObserver>> observer): observer(kj::mv(observer)) {}\n\n  SpanParent(SpanParent&& other) = default;\n  SpanParent& operator=(SpanParent&& other) = default;\n  KJ_DISALLOW_COPY(SpanParent);\n\n  SpanParent addRef();\n\n  // Create a new child span.\n  //\n  // `operationName` should be a string literal with infinite lifetime.\n  [[nodiscard]] SpanBuilder newChild(\n      kj::ConstString operationName, kj::Maybe<kj::Date> startTime = kj::none);\n\n  // Useful to skip unnecessary code when not observed.\n  bool isObserved() {\n    return observer != kj::none;\n  }\n\n  // Get the underlying SpanObserver representing the parent span.\n  //\n  // This is needed in particular when making outbound network requests that must be annotated with\n  // trace IDs in a way that is specific to the trace back-end being used. The caller must downcast\n  // the `SpanObserver` to the expected observer type in order to extract the trace ID.\n  kj::Maybe<SpanObserver&> getObserver() {\n    return observer;\n  }\n\n private:\n  kj::Maybe<kj::Own<SpanObserver>> observer;\n};\n\n// Interface for writing a span. Essentially, this is a mutable interface to a `Span` object,\n// given only to the code which is meant to create the span, whereas code that merely collects\n// and reports spans gets the `Span` type.\n//\n// The reason we use a separate builder type rather than rely on constness is so that the methods\n// can be no-ops when there is no observer, avoiding unnecessary allocations. To allow for this,\n// SpanBuilder is designed to be write-only -- you cannot read back the content. Only the\n// observer (if there is one) receives the content.\nclass SpanBuilder {\n public:\n  // Create a new top-level span that will report to the given observer. If the observer is null,\n  // no data is collected.\n  //\n  // `operationName` should be a string literal with infinite lifetime, or somehow otherwise be\n  // attached to the observer observing this span.\n  explicit SpanBuilder(kj::Maybe<kj::Own<SpanObserver>> observer,\n      kj::ConstString operationName,\n      kj::Maybe<kj::Date> startTime = kj::none);\n\n  // Make a SpanBuilder that ignores all calls. (Useful if you want to assign it later.)\n  SpanBuilder(decltype(nullptr)) {}\n\n  SpanBuilder(SpanBuilder&& other) = default;\n  SpanBuilder& operator=(SpanBuilder&& other);  // ends the existing span and starts a new one\n  KJ_DISALLOW_COPY(SpanBuilder);\n\n  ~SpanBuilder() noexcept(false);\n\n  // Finishes and submits the span. This is done implicitly by the destructor, but sometimes it's\n  // useful to be able to submit early. The SpanBuilder ignores all further method calls after this\n  // is invoked.\n  void end();\n\n  // Useful to skip unnecessary code when not observed.\n  bool isObserved() {\n    return observer != kj::none;\n  }\n\n  // Get the underlying SpanObserver representing the span.\n  //\n  // This is needed in particular when making outbound network requests that must be annotated with\n  // trace IDs in a way that is specific to the trace back-end being used. The caller must downcast\n  // the `SpanObserver` to the expected observer type in order to extract the trace ID.\n  kj::Maybe<SpanObserver&> getObserver() {\n    return observer;\n  }\n\n  // Create a new child span.\n  //\n  // `operationName` should be a string literal with infinite lifetime.\n  [[nodiscard]] SpanBuilder newChild(\n      kj::ConstString operationName, kj::Maybe<kj::Date> startTime = kj::none);\n\n  // Change the operation name from what was specified at span creation.\n  //\n  // `operationName` should be a string literal with infinite lifetime.\n  void setOperationName(kj::ConstString operationName);\n\n  using TagValue = Span::TagValue;\n  // `key` must point to memory that will remain valid all the way until this span's data is\n  // serialized.\n  // Allow setting tags with an extended set of types to elide string allocations when we have a\n  // string literal or are not being observed. We include String/LiteralStringConst here to avoid\n  // having to manually cast them to ConstString each time.\n  using TagInitValue = kj::OneOf<kj::StringPtr,\n      kj::String,\n      kj::LiteralStringConst,\n      kj::ConstString,\n      bool,\n      double,\n      int64_t>;\n\n  void setTag(kj::ConstString key, TagInitValue value);\n\n  // `key` must point to memory that will remain valid all the way until this span's data is\n  // serialized.\n  //\n  // The differences between this and `setTag()` is that logs are timestamped and may have\n  // duplicate keys.\n  void addLog(kj::Date timestamp, kj::ConstString key, TagValue value);\n\n private:\n  kj::Maybe<kj::Own<SpanObserver>> observer;\n  // The under-construction span, or null if the span has ended.\n  kj::Maybe<Span> span;\n\n  friend class SpanParent;\n};\n\n// Abstract interface for observing trace spans reported by the runtime. Different\n// implementations might support different tracing back-ends, e.g. Trace Workers, Jaeger, or\n// whatever infrastructure you prefer to use for this.\n//\n// A new SpanObserver is created at the start of each Span. The observer is used to report the\n// span data at the end of the span, as well as to construct child observers.\nclass SpanObserver: public kj::Refcounted {\n public:\n  // Allocate a new child span.\n  //\n  // Note that children can be created long after a span has completed.\n  [[nodiscard]] virtual kj::Own<SpanObserver> newChild() = 0;\n\n  // Report the span data. Called at the end of the span.\n  //\n  // This should always be called exactly once per observer at span completion time.\n  virtual void report(const Span& span) = 0;\n  // Report information about the span onset.\n  virtual void reportStart(kj::ConstString operationName, kj::Date startTime) = 0;\n\n  // The current time to be provided for the span. For user tracing, we will override this to\n  // provide I/O time. This *requires* that spans are only created when an IOContext is available\n  // (usually it is difficult to violate this assumption, but care must be taken that the observer\n  // isn't used directly to create a span before the IoContext has been constructed (previously this\n  // was a case with a top-level span owned by the WorkerTracer itself).\n  virtual kj::Date getTime() {\n    return kj::systemPreciseCalendarClock().now();\n  }\n};\n\ninline SpanParent::SpanParent(SpanBuilder& builder): observer(mapAddRef(builder.observer)) {}\n\ninline SpanParent SpanParent::addRef() {\n  return SpanParent(mapAddRef(observer));\n}\n\ninline SpanBuilder SpanParent::newChild(\n    kj::ConstString operationName, kj::Maybe<kj::Date> startTime) {\n  return SpanBuilder(observer.map([](kj::Own<SpanObserver>& obs) { return obs->newChild(); }),\n      kj::mv(operationName), startTime);\n}\n\ninline SpanBuilder SpanBuilder::newChild(\n    kj::ConstString operationName, kj::Maybe<kj::Date> startTime) {\n  return SpanBuilder(observer.map([](kj::Own<SpanObserver>& obs) { return obs->newChild(); }),\n      kj::mv(operationName), startTime);\n}\n\n// TraceContext to keep track of user tracing/existing tracing better\nclass TraceContext {\n public:\n  TraceContext(): span(nullptr), userSpan(nullptr) {}\n  TraceContext(SpanBuilder span, SpanBuilder userSpan)\n      : span(kj::mv(span)),\n        userSpan(kj::mv(userSpan)) {}\n  TraceContext(TraceContext&& other) = default;\n  TraceContext& operator=(TraceContext&& other) = default;\n  KJ_DISALLOW_COPY(TraceContext);\n\n  // Set a tag on both the internal span and user span.\n  void setTag(kj::ConstString key, SpanBuilder::TagInitValue value);\n  bool isObserved() {\n    return span.isObserved() || userSpan.isObserved();\n  }\n  SpanParent getInternalSpanParent() {\n    return SpanParent(span);\n  }\n\n private:\n  SpanBuilder span;\n  SpanBuilder userSpan;\n};\n\n// RAII object that measures the time duration over its lifetime. It tags this duration onto a\n// given request span using a specified tag name. Ideal for automatically tracking and logging\n// execution times within a scoped block.\nclass ScopedDurationTagger {\n public:\n  explicit ScopedDurationTagger(\n      SpanBuilder& span, kj::ConstString key, const kj::MonotonicClock& timer);\n  ~ScopedDurationTagger() noexcept(false);\n  KJ_DISALLOW_COPY_AND_MOVE(ScopedDurationTagger);\n\n private:\n  SpanBuilder& span;\n  kj::ConstString key;\n  const kj::MonotonicClock& timer;\n  const kj::TimePoint startTime;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/tracer.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/io/io-context.h>\n#include <workerd/io/trace-stream.h>\n#include <workerd/io/tracer.h>\n#include <workerd/util/sentry.h>\n#include <workerd/util/thread-scopes.h>\n\n#include <capnp/message.h>  // for capnp::clone()\n\nnamespace workerd {\n\nnamespace {\n\n// Approximately how much external data we allow in a trace before we start ignoring requests.  We\n// want this number to be big enough to be useful for tracing, but small enough to make it hard to\n// DoS the C++ heap -- keeping in mind we can record a trace per handler run during a request. For\n// streaming tail worker, this is the maximum size per tail event.\n// TODO(streaming-tail): Add a clear indicator for events being truncated based on MAX_TRACE_BYTES\n// so that developers can understand why this happens.\nstatic constexpr size_t MAX_TRACE_BYTES = 256 * 1024;\n\ntracing::Attribute::Value cloneAttributeValue(const tracing::Attribute::Value& value) {\n  KJ_SWITCH_ONEOF(value) {\n    KJ_CASE_ONEOF(boolean, bool) {\n      return tracing::Attribute::Value(boolean);\n    }\n    KJ_CASE_ONEOF(number, double) {\n      return tracing::Attribute::Value(number);\n    }\n    KJ_CASE_ONEOF(integer, int64_t) {\n      return tracing::Attribute::Value(integer);\n    }\n    KJ_CASE_ONEOF(string, kj::ConstString) {\n      return tracing::Attribute::Value(string.clone());\n    }\n  }\n  KJ_UNREACHABLE;\n}\n}  // namespace\n\nkj::Promise<kj::Own<Trace>> WorkerTracer::onComplete() {\n  KJ_REQUIRE(completeFulfiller == kj::none, \"onComplete() can only be called once\");\n\n  auto paf = kj::newPromiseAndFulfiller<kj::Own<Trace>>();\n  completeFulfiller = kj::mv(paf.fulfiller);\n  return kj::mv(paf.promise);\n}\n\nWorkerTracer::WorkerTracer(kj::Maybe<kj::Rc<kj::Refcounted>> parentPipeline,\n    kj::Own<Trace> trace,\n    PipelineLogLevel pipelineLogLevel,\n    kj::Maybe<kj::Array<tracing::Attribute>> tailAttributes,\n    kj::Maybe<kj::Own<tracing::TailStreamWriter>> maybeTailStreamWriter)\n    : pipelineLogLevel(pipelineLogLevel),\n      trace(kj::mv(trace)),\n      parentPipeline(kj::mv(parentPipeline)),\n      maybeTailStreamWriter(kj::mv(maybeTailStreamWriter)) {\n  KJ_IF_SOME(tags, tailAttributes) {\n    if (tags.size() == 0) {\n      tailAttributes = kj::none;\n    } else {\n      for (auto& tag: tags) {\n        KJ_REQUIRE(tag.value.size() == 1, \"tail attributes must contain exactly one value\");\n        setWorkerAttribute(tag.name.clone(), cloneAttributeValue(tag.value[0]));\n      }\n    }\n  }\n  this->trace->tailAttributes = kj::mv(tailAttributes);\n}\n\nWorkerTracer::~WorkerTracer() noexcept(false) {\n  // Report the outcome event, which should have been delivered by now.\n\n  // Do not attempt to report an outcome event if logging is disabled, as with other event types.\n  if (pipelineLogLevel == PipelineLogLevel::NONE) {\n    return;\n  }\n\n  // Report the outcome event if STWs are present. All worker events need to call setEventInfo at\n  // the start of the invocation to submit the onset event before any other tail events.\n  KJ_IF_SOME(writer, maybeTailStreamWriter) {\n    KJ_IF_SOME(spanContext, topLevelInvocationSpanContext) {\n      if (markedUnused) {\n        LOG_WARNING_PERIODICALLY(\"WorkerTracer was marked unused but actually was used\");\n      }\n      if (isPredictableModeForTest()) {\n        writer->report(spanContext,\n            tracing::Outcome(trace->outcome, 0 * kj::MILLISECONDS, 0 * kj::MILLISECONDS),\n            completeTime, 0);\n      } else {\n        writer->report(spanContext,\n            tracing::Outcome(trace->outcome, trace->cpuTime, trace->wallTime), completeTime, 0);\n      }\n    } else if (!markedUnused) {\n      // If no span context is available, we have a streaming tail worker set up but shut down the\n      // worker tracer without ever sending an Onset event. In that case we either failed to set up\n      // the Onset properly (indicating a bug – all event types are required to report an Onset at\n      // the start – although this is more likely to manifest as a \"Tail stream onset was not\n      // reported\" error) or we created a WorkerInterface with WorkerTracer without ever invoking it\n      // (which is not incorrect behavior, but likely indicates inefficient code that sets up\n      // WorkerInterfaces and then ends up not using it due to an error/incorrect parameters; such\n      // error checking should be done beforehand to avoid unused allocations). Report such cases.\n      // Note: If markedUnused is true, this tracer was intentionally not used (e.g., duplicate\n      // alarm request deduplication) and the warning should be suppressed.\n      LOG_ERROR_PERIODICALLY(\n          \"destructed WorkerTracer with STW without reporting Onset event\", kj::getStackTrace());\n    }\n  }\n\n  // Report the completed trace, if fulfiller is set up.\n  KJ_IF_SOME(f, completeFulfiller) {\n    f.get()->fulfill(kj::mv(trace));\n  }\n};\n\nconstexpr kj::LiteralStringConst logSizeExceeded =\n    \"[\\\"Log size limit exceeded: More than 256KB of data (across console.log statements, exception, request metadata and headers) was logged during a single request. Subsequent data for this request will not be recorded in logs, appear when tailing this Worker's logs, or in Tail Workers.\\\"]\"_kjc;\n\nvoid WorkerTracer::addLog(const tracing::InvocationSpanContext& context,\n    kj::Date timestamp,\n    LogLevel logLevel,\n    kj::String message) {\n  if (pipelineLogLevel == PipelineLogLevel::NONE) {\n    return;\n  }\n\n  // TODO(streaming-tail): Here we add the log to the trace object and the tail stream writer, if\n  // available. If the given worker stage is only tailed by a streaming tail worker, adding the log\n  // to the buffered trace object is not needed; this will be addressed in a future refactor.\n  KJ_IF_SOME(writer, maybeTailStreamWriter) {\n    // If message is too big on its own, truncate it.\n    size_t messageSize = kj::min(message.size(), MAX_TRACE_BYTES);\n    writer->report(context,\n        {tracing::Log(timestamp, logLevel, kj::str(message.first(messageSize)))}, timestamp,\n        messageSize);\n  }\n\n  if (trace->exceededLogLimit) {\n    return;\n  }\n\n  size_t messageSize = sizeof(tracing::Log) + message.size();\n  if (trace->bytesUsed + messageSize > MAX_TRACE_BYTES) {\n    // We use a JSON encoded array/string to match other console.log() recordings:\n    trace->logs.add(timestamp, LogLevel::WARN, kj::str(logSizeExceeded));\n    trace->exceededLogLimit = true;\n    trace->truncated = true;\n  } else {\n    trace->bytesUsed += messageSize;\n    trace->logs.add(timestamp, logLevel, kj::mv(message));\n  }\n}\n\nvoid WorkerTracer::addSpan(tracing::CompleteSpan&& span) {\n  // The span information is not transmitted via RPC at this point, we can decompose the span into\n  // spanOpen/spanEnd.\n  addSpanOpen(span.spanId, span.parentSpanId, kj::mv(span.operationName), span.startTime);\n  tracing::SpanEndData spanEnd(span.spanId, span.endTime, kj::mv(span.tags));\n  addSpanEnd(kj::mv(spanEnd), span.startTime);\n}\n\nvoid WorkerTracer::addSpanOpen(tracing::SpanId spanId,\n    tracing::SpanId parentSpanId,\n    kj::ConstString operationName,\n    kj::Date startTime) {\n  if (pipelineLogLevel == PipelineLogLevel::NONE) {\n    return;\n  }\n\n  auto& tailStreamWriter = KJ_UNWRAP_OR_RETURN(maybeTailStreamWriter);\n  auto& topLevelContext = KJ_ASSERT_NONNULL(topLevelInvocationSpanContext);\n  // Compose SpanOpen. An all-zero spanId is interpreted as having no spans above this one, thus we\n  // use the Onset spanId instead (taken from topLevelContext). We go to great lengths to rule out\n  // getting an all-zero spanId by chance (see SpanId::fromEntropy()), so this should be safe.\n  if (parentSpanId == tracing::SpanId::nullId) {\n    parentSpanId = topLevelContext.getSpanId();\n  }\n  size_t spanNameSize = operationName.size();\n  auto spanOpenContext = tracing::InvocationSpanContext(\n      topLevelContext.getTraceId(), topLevelContext.getInvocationId(), parentSpanId);\n  tailStreamWriter->report(\n      spanOpenContext, tracing::SpanOpen(spanId, kj::mv(operationName)), startTime, spanNameSize);\n}\n\nvoid WorkerTracer::addSpanEnd(tracing::SpanEndData&& span, kj::Maybe<kj::Date> maybeStartTime) {\n  if (pipelineLogLevel == PipelineLogLevel::NONE) {\n    return;\n  }\n\n  // Note: spans are not available in the buffered tail worker, so we don't need an exceededSpanLimit\n  // variable for it and it can't cause truncation.\n  auto& tailStreamWriter = KJ_UNWRAP_OR_RETURN(maybeTailStreamWriter);\n\n  adjustSpanTime(span, maybeStartTime);\n\n  size_t spanTagsSize = 0;\n  for (const Span::TagMap::Entry& tag: span.tags) {\n    spanTagsSize += tag.key.size();\n    KJ_SWITCH_ONEOF(tag.value) {\n      KJ_CASE_ONEOF(str, kj::ConstString) {\n        spanTagsSize += str.size();\n      }\n      KJ_CASE_ONEOF(val, bool) {\n        spanTagsSize++;\n      }\n      // int64_t and double\n      KJ_CASE_ONEOF_DEFAULT {\n        spanTagsSize += sizeof(int64_t);\n      }\n    }\n  }\n\n  // Compose Attributes and SpanClose, which are available at span completion time and transmitted\n  // together.\n  auto& topLevelContext = KJ_ASSERT_NONNULL(topLevelInvocationSpanContext);\n  auto spanComponentContext = tracing::InvocationSpanContext(\n      topLevelContext.getTraceId(), topLevelContext.getInvocationId(), span.spanId);\n\n  if (span.tags.size() && spanTagsSize <= MAX_TRACE_BYTES) {\n    tracing::CustomInfo attr = KJ_MAP(tag, span.tags) {\n      return tracing::Attribute(kj::mv(tag.key), kj::mv(tag.value));\n    };\n    tailStreamWriter->report(spanComponentContext, kj::mv(attr), span.endTime, spanTagsSize);\n  }\n  tailStreamWriter->report(spanComponentContext, tracing::SpanClose(), span.endTime, 0);\n}\n\nvoid WorkerTracer::addException(const tracing::InvocationSpanContext& context,\n    kj::Date timestamp,\n    kj::String name,\n    kj::String message,\n    kj::Maybe<kj::String> stack) {\n  // TODO(someday): For now, we're using logLevel == none as a hint to avoid doing anything\n  //   expensive while tracing.  We may eventually want separate configuration for exceptions vs.\n  //   logs.\n  if (pipelineLogLevel == PipelineLogLevel::NONE) {\n    return;\n  }\n\n  size_t messageSize = sizeof(tracing::Exception) + name.size() + message.size();\n  KJ_IF_SOME(s, stack) {\n    messageSize += s.size();\n  }\n  KJ_IF_SOME(writer, maybeTailStreamWriter) {\n    auto maybeTruncatedName = name.first(kj::min(name.size(), MAX_TRACE_BYTES));\n    auto maybeTruncatedMessage =\n        message.first(kj::min(message.size(), MAX_TRACE_BYTES - maybeTruncatedName.size()));\n    kj::Maybe<kj::String> maybeTruncatedStack;\n    auto maybeTruncatedStackSize = 0;\n    KJ_IF_SOME(s, stack) {\n      maybeTruncatedStackSize = kj::min(\n          s.size(), MAX_TRACE_BYTES - maybeTruncatedName.size() - maybeTruncatedMessage.size());\n      maybeTruncatedStack = kj::heapString(s.first(maybeTruncatedStackSize));\n    }\n    writer->report(context,\n        {tracing::Exception(timestamp, kj::str(maybeTruncatedName), kj::str(maybeTruncatedMessage),\n            kj::mv(maybeTruncatedStack))},\n        timestamp,\n        maybeTruncatedName.size() + maybeTruncatedMessage.size() + maybeTruncatedStackSize);\n  }\n\n  if (trace->exceededExceptionLimit) {\n    return;\n  }\n\n  if (trace->bytesUsed + messageSize > MAX_TRACE_BYTES) {\n    trace->exceededExceptionLimit = true;\n    trace->truncated = true;\n    trace->exceptions.add(timestamp, kj::str(\"Error\"),\n        kj::str(\"Trace resource limit exceeded; subsequent exceptions not recorded.\"), kj::none);\n  } else {\n    trace->bytesUsed += messageSize;\n    trace->exceptions.add(timestamp, kj::mv(name), kj::mv(message), kj::mv(stack));\n  }\n}\n\nvoid WorkerTracer::addDiagnosticChannelEvent(const tracing::InvocationSpanContext& context,\n    kj::Date timestamp,\n    kj::String channel,\n    kj::Array<kj::byte> message) {\n  if (pipelineLogLevel == PipelineLogLevel::NONE) {\n    return;\n  }\n\n  size_t messageSize = sizeof(tracing::DiagnosticChannelEvent) + channel.size() + message.size();\n  KJ_IF_SOME(writer, maybeTailStreamWriter) {\n    // Drop oversized diagnostic channel events instead of truncating them – a truncated message may\n    // not be deserialized correctly.\n    if (messageSize <= MAX_TRACE_BYTES) {\n      writer->report(context,\n          {tracing::DiagnosticChannelEvent(\n              timestamp, kj::str(channel), kj::heapArray<kj::byte>(message))},\n          timestamp, messageSize);\n    }\n  }\n\n  if (trace->exceededDiagnosticChannelEventLimit) {\n    return;\n  }\n\n  if (trace->bytesUsed + messageSize > MAX_TRACE_BYTES) {\n    trace->exceededDiagnosticChannelEventLimit = true;\n    trace->truncated = true;\n    trace->diagnosticChannelEvents.add(\n        timestamp, kj::str(\"workerd.LimitExceeded\"), kj::Array<kj::byte>());\n  } else {\n    trace->bytesUsed += messageSize;\n    trace->diagnosticChannelEvents.add(timestamp, kj::mv(channel), kj::mv(message));\n  }\n}\n\nvoid WorkerTracer::setEventInfo(\n    IoContext::IncomingRequest& incomingRequest, tracing::EventInfo&& info) {\n  // IoContext is available at this time, capture weakRef.\n  KJ_ASSERT(weakIoContext == kj::none, \"tracer can only be used for a single event\");\n  weakIoContext = incomingRequest.getContext().getWeakRef();\n  setEventInfoInternal(\n      incomingRequest.getInvocationSpanContext(), incomingRequest.now(), kj::mv(info));\n}\n\nvoid WorkerTracer::setEventInfoInternal(\n    const tracing::InvocationSpanContext& context, kj::Date timestamp, tracing::EventInfo&& info) {\n  KJ_ASSERT(trace->eventInfo == kj::none, \"tracer can only be used for a single event\");\n\n  // TODO(someday): For now, we're using logLevel == none as a hint to avoid doing anything\n  //   expensive while tracing.  We may eventually want separate configuration for event info vs.\n  //   logs.\n  // TODO(perf): Find a way to allow caller to avoid the cost of generation if the info struct\n  //   won't be used?\n  if (pipelineLogLevel == PipelineLogLevel::NONE) {\n    return;\n  }\n\n  trace->eventTimestamp = timestamp;\n  this->topLevelInvocationSpanContext = context.clone();\n\n  size_t eventSize = 0;\n  KJ_SWITCH_ONEOF(info) {\n    KJ_CASE_ONEOF(fetch, tracing::FetchEventInfo) {\n      eventSize += fetch.url.size();\n      for (const auto& header: fetch.headers) {\n        eventSize += header.name.size() + header.value.size();\n      }\n      eventSize += fetch.cfJson.size();\n      // Limit STW onset to MAX_TRACE_BYTES, beyond that dispatch a truncated event too.\n      if (eventSize > MAX_TRACE_BYTES) {\n        info = tracing::FetchEventInfo(fetch.method, {}, {}, {});\n      }\n    }\n    KJ_CASE_ONEOF_DEFAULT {}\n  }\n\n  KJ_IF_SOME(writer, maybeTailStreamWriter) {\n    // Provide WorkerInfo to the streaming tail worker if available. This data is provided when the\n    // WorkerTracer is created, but the actual onset event is the best time to send it.\n    auto workerInfo = tracing::Onset::WorkerInfo{\n      .executionModel = trace->executionModel,\n      .scriptName = mapCopyString(trace->scriptName),\n      .scriptVersion =\n          trace->scriptVersion.map([](auto& scriptVersion) -> kj::Own<ScriptVersion::Reader> {\n      return capnp::clone(*scriptVersion);\n    }),\n      .dispatchNamespace = mapCopyString(trace->dispatchNamespace),\n      .scriptId = mapCopyString(trace->scriptId),\n      .scriptTags = KJ_MAP(tag, trace->scriptTags) { return kj::str(tag); },\n      .entrypoint = mapCopyString(trace->entrypoint),\n    };\n\n    // Onset needs special handling for spanId: The top-level spanId is zero unless a trigger\n    // context is available (not yet implemented). The inner spanId is taken from the invocation\n    // span context, that span is being \"opened\" with the onset event. All other tail events have it\n    // as its parent span ID, except for recursive SpanOpens (which have the parent span instead)\n    // and Attribute/SpanClose events (which have the spanId opened in the corresponding SpanOpen).\n    auto onsetContext = tracing::InvocationSpanContext(\n        context.getTraceId(), context.getInvocationId(), tracing::SpanId::nullId);\n\n    // Not applying size accounting for Onset since it is sent separately\n    writer->report(onsetContext,\n        tracing::Onset(context.getSpanId(), cloneEventInfo(info), kj::mv(workerInfo),\n            attributes.releaseAsArray()),\n        timestamp, 0);\n  }\n\n  // truncation should only be needed for fetch events, since we only set eventSize there.\n  if (trace->bytesUsed + eventSize > MAX_TRACE_BYTES && eventSize > 0) {\n    trace->truncated = true;\n    trace->logs.add(timestamp, LogLevel::WARN,\n        kj::str(\"[\\\"Trace resource limit exceeded; could not capture event info.\\\"]\"));\n    trace->eventInfo =\n        tracing::FetchEventInfo(info.get<tracing::FetchEventInfo>().method, {}, {}, {});\n  } else {\n    trace->bytesUsed += eventSize;\n    trace->eventInfo = kj::mv(info);\n  }\n}\n\nvoid WorkerTracer::setOutcome(EventOutcome outcome, kj::Duration cpuTime, kj::Duration wallTime) {\n  trace->outcome = outcome;\n  trace->cpuTime = cpuTime;\n  trace->wallTime = wallTime;\n\n  // Defer reporting the actual outcome event to the WorkerTracer destructor: The outcome is\n  // reported when the metrics request is deallocated, but with ctx.waitUntil() there might be spans\n  // continuing to exist beyond that point. By the time the WorkerTracer is deallocated, the\n  // IoContext and its task set will be done and any additional spans will have wrapped up.\n  // This is somewhat at odds with the concept of \"streaming\" events, but benign as the WorkerTracer\n  // wraps up right after the metrics request object in the average case and since the outcome has a\n  // fixed size.\n}\n\nvoid WorkerTracer::recordTimestamp(kj::Date timestamp) {\n  if (completeTime == kj::UNIX_EPOCH) {\n    completeTime = timestamp;\n  }\n}\n\nkj::Date BaseTracer::getTime() {\n  auto& weakIoCtx = KJ_ASSERT_NONNULL(weakIoContext);\n  kj::Date timestamp = kj::UNIX_EPOCH;\n  weakIoCtx->runIfAlive([&timestamp](IoContext& context) { timestamp = context.now(); });\n  if (!weakIoCtx->isValid()) {\n    // This can happen if we the IoContext gets destroyed following an exception, but we still need\n    // to report a time for the return event.\n    if (completeTime != kj::UNIX_EPOCH) {\n      timestamp = completeTime;\n    } else {\n      // Otherwise, we can't actually get an end timestamp that makes sense.\n      if (isPredictableModeForTest()) {\n        KJ_FAIL_ASSERT(\"reported return event without valid IoContext or completeTime\");\n      } else {\n        LOG_WARNING_PERIODICALLY(\"reported return event without valid IoContext or completeTime\");\n      }\n    }\n  }\n  return timestamp;\n}\n\nvoid BaseTracer::adjustSpanTime(tracing::CompleteSpan& span) {\n  // To report I/O time, we need the IOContext to still be alive.\n  // weakIoContext is only none if we are tracing via RPC (in this case span times have already been\n  // adjusted) or if we failed to transmit an Onset event (in that case we'll get an error based on\n  // missing topLevelInvocationSpanContext right after).\n  if (weakIoContext != kj::none) {\n    auto& weakIoCtx = KJ_ASSERT_NONNULL(weakIoContext);\n    weakIoCtx->runIfAlive([this, &span](IoContext& context) {\n      if (context.hasCurrentIncomingRequest()) {\n        span.endTime = context.now();\n      } else {\n        // We have an IOContext, but there's no current IncomingRequest. Always log a warning here,\n        // this should not be happening. Still report completeTime as a useful timestamp if\n        // available.\n        bool hasCompleteTime = false;\n        if (completeTime != kj::UNIX_EPOCH) {\n          span.endTime = completeTime;\n          hasCompleteTime = true;\n        } else {\n          span.endTime = span.startTime;\n        }\n        if (isPredictableModeForTest()) {\n          KJ_FAIL_ASSERT(\n              \"reported span without current request\", span.operationName, hasCompleteTime);\n        } else {\n          LOG_WARNING_PERIODICALLY(\n              \"reported span without current request\", span.operationName, hasCompleteTime);\n        }\n      }\n    });\n    if (!weakIoCtx->isValid()) {\n      // This can happen if we start a customEvent from this event and cancel it after this IoContext\n      // gets destroyed. In that case we no longer have an IoContext available and can't get the\n      // current time, but the outcome timestamp will have already been set. Since the outcome\n      // timestamp is \"late enough\", simply use that.\n      // TODO(o11y): fix this – spans should not be outliving the IoContext.\n      if (completeTime != kj::UNIX_EPOCH) {\n        span.endTime = completeTime;\n      } else {\n        // Otherwise, we can't actually get an end timestamp that makes sense. Report a zero-duration\n        // span and log a warning (or fail assert in test mode).\n        span.endTime = span.startTime;\n        if (isPredictableModeForTest()) {\n          KJ_FAIL_ASSERT(\"reported span after IoContext was deallocated\", span.operationName);\n        } else {\n          KJ_LOG(WARNING, \"reported span after IoContext was deallocated\", span.operationName);\n        }\n      }\n    }\n  }\n}\n\nvoid BaseTracer::adjustSpanTime(tracing::SpanEndData& span, kj::Maybe<kj::Date> maybeStartTime) {\n  // To report I/O time, we need the IOContext to still be alive.\n  // weakIoContext is only none if we are tracing via RPC (in this case span times have already been\n  // adjusted) or if we failed to transmit an Onset event (in that case we'll get an error based on\n  // missing topLevelInvocationSpanContext right after).\n  if (weakIoContext != kj::none) {\n    auto& weakIoCtx = KJ_ASSERT_NONNULL(weakIoContext);\n    // startTime is generally available when we are not tracing via RPC, so we can assert that it is\n    // present. For the RPC case, the adjustment will already have been done earlier and it's ok\n    // for maybeStartTime to be none as this code won't run based on weakIoContext being none.\n    kj::Date startTime = KJ_ASSERT_NONNULL(maybeStartTime);\n    weakIoCtx->runIfAlive([this, &span, &startTime](IoContext& context) {\n      if (context.hasCurrentIncomingRequest()) {\n        span.endTime = context.now();\n      } else {\n        // We have an IOContext, but there's no current IncomingRequest. Always log a warning here,\n        // this should not be happening. Still report completeTime as a useful timestamp if\n        // available.\n        bool hasCompleteTime = false;\n        if (completeTime != kj::UNIX_EPOCH) {\n          span.endTime = completeTime;\n          hasCompleteTime = true;\n        } else {\n          span.endTime = startTime;\n        }\n        if (isPredictableModeForTest()) {\n          KJ_FAIL_ASSERT(\"reported span without current request\", hasCompleteTime);\n        } else {\n          LOG_WARNING_PERIODICALLY(\"reported span without current request\");\n        }\n      }\n    });\n    if (!weakIoCtx->isValid()) {\n      // This can happen if we start a customEvent from this event and cancel it after this IoContext\n      // gets destroyed. In that case we no longer have an IoContext available and can't get the\n      // current time, but the outcome timestamp will have already been set. Since the outcome\n      // timestamp is \"late enough\", simply use that.\n      // TODO(o11y): fix this – spans should not be outliving the IoContext.\n      if (completeTime != kj::UNIX_EPOCH) {\n        span.endTime = completeTime;\n      } else {\n        // Otherwise, we can't actually get an end timestamp that makes sense. Report a zero-duration\n        // span and log a warning (or fail assert in test mode).\n        span.endTime = startTime;\n        if (isPredictableModeForTest()) {\n          KJ_FAIL_ASSERT(\"reported span after IoContext was deallocated\");\n        } else {\n          KJ_LOG(WARNING, \"reported span after IoContext was deallocated\");\n        }\n      }\n    }\n  }\n}\n\nvoid WorkerTracer::setReturn(\n    kj::Maybe<kj::Date> timestamp, kj::Maybe<tracing::FetchResponseInfo> fetchResponseInfo) {\n  // Match the behavior of setEventInfo(). Any resolution of the TODO comments in setEventInfo()\n  // that are related to this check will probably also affect this function.\n  if (pipelineLogLevel == PipelineLogLevel::NONE) {\n    return;\n  }\n\n  KJ_IF_SOME(writer, maybeTailStreamWriter) {\n    auto& spanContext = KJ_UNWRAP_OR_RETURN(topLevelInvocationSpanContext);\n\n    // Fall back to weak IoContext if no timestamp is available\n    writer->report(spanContext,\n        tracing::Return({fetchResponseInfo.map([](auto& info) { return info.clone(); })}),\n        timestamp.orDefault([&]() { return getTime(); }), 0);\n  }\n\n  // Add fetch response info for buffered tail worker\n  KJ_IF_SOME(info, fetchResponseInfo) {\n    KJ_REQUIRE(KJ_REQUIRE_NONNULL(trace->eventInfo).is<tracing::FetchEventInfo>());\n    KJ_ASSERT(trace->fetchResponseInfo == kj::none, \"setFetchResponseInfo can only be called once\");\n    trace->fetchResponseInfo = kj::mv(info);\n  }\n}\n\nvoid BaseTracer::setMakeUserRequestSpanFunc(MakeUserRequestSpanFunc func) {\n  KJ_ASSERT(\n      makeUserRequestSpanFunc == kj::none, \"setMakeUserRequestSpanFunc can only be called once\");\n  makeUserRequestSpanFunc = kj::mv(func);\n}\n\nvoid WorkerTracer::setWorkerAttribute(kj::ConstString key, Span::TagValue value) {\n  attributes.add(tracing::Attribute{kj::mv(key), kj::mv(value)});\n}\n\nSpanParent BaseTracer::makeUserRequestSpan() {\n  KJ_IF_SOME(func, makeUserRequestSpanFunc) {\n    return func();\n  } else {\n    return SpanParent(nullptr);\n  }\n}\n\nvoid WorkerTracer::setJsRpcInfo(const tracing::InvocationSpanContext& context,\n    kj::Date timestamp,\n    const kj::ConstString& methodName) {\n  if (pipelineLogLevel == PipelineLogLevel::NONE) {\n    return;\n  }\n\n  // Update the method name in the already-set JsRpcEventInfo for buffered tail worker compatibility\n  KJ_IF_SOME(info, trace->eventInfo) {\n    KJ_SWITCH_ONEOF(info) {\n      KJ_CASE_ONEOF(jsRpcInfo, tracing::JsRpcEventInfo) {\n        jsRpcInfo.methodName = kj::str(methodName);\n      }\n      KJ_CASE_ONEOF_DEFAULT {}\n    }\n  }\n\n  KJ_IF_SOME(writer, maybeTailStreamWriter) {\n    auto tag = tracing::Attribute(\"jsrpc.method\"_kjc, methodName.clone());\n    writer->report(context, kj::arr(kj::mv(tag)), timestamp, methodName.size());\n  }\n}\n\nkj::Own<SpanObserver> UserSpanObserver::newChild() {\n  return kj::refcounted<UserSpanObserver>(kj::addRef(*submitter), spanId);\n}\n\nvoid UserSpanObserver::report(const Span& span) {\n  submitter->submitSpan(spanId, parentSpanId, span);\n}\n\nvoid UserSpanObserver::reportStart(kj::ConstString operationName, kj::Date startTime) {\n  submitter->submitSpanOpen(spanId, parentSpanId, kj::mv(operationName), startTime);\n}\n\n// Provide I/O time to the tracing system for user spans.\nkj::Date UserSpanObserver::getTime() {\n  return IoContext::current().now();\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/tracer.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/io/io-context.h>\n#include <workerd/io/trace.h>\n\n#include <kj/refcount.h>\n\nnamespace workerd {\nnamespace tracing {\nclass TailStreamWriter;\n}  // namespace tracing\n\nclass WorkerTracer;\n\n// An abstract class that defines shares functionality for tracers that have different\n// characteristics. This interface is used to submit both buffered and streaming tail events.\n// TODO(streaming-tail): When further consolidating the tail worker implementations, the interface\n// of the add* methods below should make more sense: The invocation span context below is currently\n// only being used in the streaming model, when we have switched the buffered model to streaming\n// there will be plenty of cleanup potential.\nclass BaseTracer: public kj::Refcounted {\n public:\n  virtual ~BaseTracer() noexcept(false) {};\n\n  // Adds log line to trace.  For Spectre, timestamp should only be as accurate as JS Date.now().\n  virtual void addLog(const tracing::InvocationSpanContext& context,\n      kj::Date timestamp,\n      LogLevel logLevel,\n      kj::String message) = 0;\n  // Add a complete span.\n  virtual void addSpan(tracing::CompleteSpan&& span) = 0;\n  // Add information about a span when it is opened, corresponds to SpanOpen event.\n  virtual void addSpanOpen(tracing::SpanId spanId,\n      tracing::SpanId parentSpanId,\n      kj::ConstString operationName,\n      kj::Date startTime) = 0;\n  // Add span events when the span is complete (Attributes and SpanClose).\n  virtual void addSpanEnd(tracing::SpanEndData&& span, kj::Maybe<kj::Date> maybeStartTime) = 0;\n\n  virtual void addException(const tracing::InvocationSpanContext& context,\n      kj::Date timestamp,\n      kj::String name,\n      kj::String message,\n      kj::Maybe<kj::String> stack) = 0;\n\n  virtual void addDiagnosticChannelEvent(const tracing::InvocationSpanContext& context,\n      kj::Date timestamp,\n      kj::String channel,\n      kj::Array<kj::byte> message) = 0;\n\n  // Adds info about the event that triggered the trace.  Must not be called more than once.\n  virtual void setEventInfo(\n      IoContext::IncomingRequest& incomingRequest, tracing::EventInfo&& info) = 0;\n\n  // Sets the return event for Streaming Tail Worker, including fetchResponseInfo (HTTP status code)\n  // if available. Must not be called more than once, and fetchResponseInfo should only be set for\n  // fetch events. For buffered tail worker, there is no distinct return event so we only add\n  // fetchResponseInfo to the trace if present.\n  virtual void setReturn(kj::Maybe<kj::Date> time = kj::none,\n      kj::Maybe<tracing::FetchResponseInfo> fetchResponseInfo = kj::none) = 0;\n\n  // Reports the outcome event of the worker invocation. For Streaming Tail Worker, this will be the\n  // final event, causing the stream to terminate.\n  virtual void setOutcome(EventOutcome outcome, kj::Duration cpuTime, kj::Duration wallTime) = 0;\n\n  // Report time as seen from the incoming Request when the request is complete, since it will not\n  // be available afterwards.\n  virtual void recordTimestamp(kj::Date timestamp) = 0;\n\n  SpanParent makeUserRequestSpan();\n\n  using MakeUserRequestSpanFunc = kj::Function<SpanParent()>;\n\n  // Allow setting the user request span after the tracer has been created so its observer can\n  // reference the tracer. This can only be set once.\n  void setMakeUserRequestSpanFunc(MakeUserRequestSpanFunc func);\n\n  virtual void setJsRpcInfo(const tracing::InvocationSpanContext& context,\n      kj::Date timestamp,\n      const kj::ConstString& methodName) = 0;\n\n  // Mark this tracer as intentionally unused (e.g., for duplicate alarm requests).\n  // When set, the destructor will not log a warning about missing Onset event.\n  void markUnused() {\n    markedUnused = true;\n  }\n\n protected:\n  // Retrieves the current timestamp. If the IoContext is no longer available, we assume that the\n  // worker must have wrapped up and reported its outcome event, we report completeTime in that case\n  // acordingly.\n  kj::Date getTime();\n\n  // helper method for addSpan() implementations\n  void adjustSpanTime(tracing::CompleteSpan& span);\n  void adjustSpanTime(tracing::SpanEndData& span, kj::Maybe<kj::Date> maybeStartTime);\n\n  // Function to create the root span for the new tracing format.\n  kj::Maybe<MakeUserRequestSpanFunc> makeUserRequestSpanFunc;\n\n  // Time to be reported for the outcome event time. This will be set before the outcome is\n  // dispatched.\n  kj::Date completeTime = kj::UNIX_EPOCH;\n\n  // Weak reference to the IoContext, used to report span end time if available.\n  kj::Maybe<kj::Own<IoContext::WeakRef>> weakIoContext;\n\n  // When true, the destructor will not log a warning about missing Onset event.\n  // Set via markUnused() when a tracer is intentionally not used (e.g., duplicate alarm requests).\n  bool markedUnused = false;\n};\n\n// Records a worker stage's trace information into a Trace object.  When all references to the\n// Tracer are released, its Trace is considered complete and ready for submission.\nclass WorkerTracer final: public BaseTracer {\n public:\n  explicit WorkerTracer(kj::Maybe<kj::Rc<kj::Refcounted>> parentPipeline,\n      kj::Own<Trace> trace,\n      PipelineLogLevel pipelineLogLevel,\n      kj::Maybe<kj::Array<tracing::Attribute>> tailAttributes,\n      kj::Maybe<kj::Own<tracing::TailStreamWriter>> maybeTailStreamWriter);\n  virtual ~WorkerTracer() noexcept(false);\n  KJ_DISALLOW_COPY_AND_MOVE(WorkerTracer);\n\n  // Returns a promise that fulfills when trace is complete. Only one such promise can\n  // exist at a time. Used in workerd, where we don't have to worry about pipelines.\n  kj::Promise<kj::Own<Trace>> onComplete();\n\n  void addLog(const tracing::InvocationSpanContext& context,\n      kj::Date timestamp,\n      LogLevel logLevel,\n      kj::String message) override;\n  void addSpan(tracing::CompleteSpan&& span) override;\n  void addSpanOpen(tracing::SpanId spanId,\n      tracing::SpanId parentSpanId,\n      kj::ConstString operationName,\n      kj::Date startTime) override;\n  void addSpanEnd(tracing::SpanEndData&& span, kj::Maybe<kj::Date> maybeStartTime) override;\n  void addException(const tracing::InvocationSpanContext& context,\n      kj::Date timestamp,\n      kj::String name,\n      kj::String message,\n      kj::Maybe<kj::String> stack) override;\n  void addDiagnosticChannelEvent(const tracing::InvocationSpanContext& context,\n      kj::Date timestamp,\n      kj::String channel,\n      kj::Array<kj::byte> message) override;\n  // Set event info (equivalent to Onset event under streaming). We use the incomingRequest here\n  // since the IoContext may not have the IncomingRequest linked to it yet (depending on if\n  // delivered() has been set), so it might not be possible to acquire the required timestamp and\n  // span context from it.\n  void setEventInfo(\n      IoContext::IncomingRequest& incomingRequest, tracing::EventInfo&& info) override;\n  // Variant for when we don't have a proper IoContext but instead provide context and timestamp\n  // directly, used internally for RPC-based tracing.\n  void setEventInfoInternal(\n      const tracing::InvocationSpanContext& context, kj::Date timestamp, tracing::EventInfo&& info);\n\n  void setOutcome(EventOutcome outcome, kj::Duration cpuTime, kj::Duration wallTime) override;\n  virtual void recordTimestamp(kj::Date timestamp) override;\n\n  // Set a worker-level tag/attribute to be provided in the onset event.\n  void setWorkerAttribute(kj::ConstString key, Span::TagValue value);\n\n  void setReturn(kj::Maybe<kj::Date> time = kj::none,\n      kj::Maybe<tracing::FetchResponseInfo> fetchResponseInfo = kj::none) override;\n\n  void setJsRpcInfo(const tracing::InvocationSpanContext& context,\n      kj::Date timestamp,\n      const kj::ConstString& methodName) override;\n\n private:\n  PipelineLogLevel pipelineLogLevel;\n  kj::Own<Trace> trace;\n  // span attributes to be added to the onset event.\n  kj::Vector<tracing::Attribute> attributes;\n\n  // TODO(streaming-tail): Top-level invocation span context, used to add a placeholder span context\n  // for trace events. This should no longer be needed after merging the existing span ID and\n  // InvocationSpanContext interfaces.\n  kj::Maybe<tracing::InvocationSpanContext> topLevelInvocationSpanContext;\n\n  // own an instance of the pipeline to make sure it doesn't get destroyed\n  // before we're finished tracing. kj::Refcounted serves as a fill-in here since the pipeline\n  // tracer is not needed otherwise.\n  kj::Maybe<kj::Rc<kj::Refcounted>> parentPipeline;\n  kj::Maybe<kj::Own<kj::PromiseFulfiller<kj::Own<Trace>>>> completeFulfiller;\n\n  kj::Maybe<kj::Own<tracing::TailStreamWriter>> maybeTailStreamWriter;\n};\n\nclass SpanSubmitter: public kj::Refcounted {\n public:\n  virtual void submitSpanOpen(tracing::SpanId spanId,\n      tracing::SpanId parentSpanId,\n      kj::ConstString operationName,\n      kj::Date startTime) = 0;\n  virtual void submitSpan(tracing::SpanId context, tracing::SpanId spanId, const Span& span) = 0;\n\n  virtual tracing::SpanId makeSpanId() = 0;\n};\n\n// The user tracing observer\nclass UserSpanObserver final: public SpanObserver {\n public:\n  // constructor for top-level observer\n  UserSpanObserver(kj::Own<SpanSubmitter> submitter)\n      : submitter(kj::mv(submitter)),\n        spanId(tracing::SpanId::nullId),\n        parentSpanId(tracing::SpanId::nullId) {}\n  // constructor for subsequent observers attached to a span\n  UserSpanObserver(kj::Own<SpanSubmitter> submitter, tracing::SpanId parentSpanId)\n      : submitter(kj::mv(submitter)),\n        spanId(this->submitter->makeSpanId()),\n        parentSpanId(parentSpanId) {}\n  KJ_DISALLOW_COPY(UserSpanObserver);\n\n  kj::Own<SpanObserver> newChild() override;\n  void report(const Span& span) override;\n  void reportStart(kj::ConstString operationName, kj::Date startTime) override;\n  kj::Date getTime() override;\n\n private:\n  kj::Own<SpanSubmitter> submitter;\n  tracing::SpanId spanId;\n  tracing::SpanId parentSpanId;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/tracked-wasm-instance-js-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"tracked-wasm-instance-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"tracked-wasm-instance-test.js\"),\n          (name = \"signal-basic.wasm\", wasm = embed \"wasm/signal-basic.wasm\"),\n          (name = \"signal-partial-exports.wasm\", wasm = embed \"wasm/signal-partial-exports.wasm\"),\n          (name = \"signal-terminated-only.wasm\", wasm = embed \"wasm/signal-terminated-only.wasm\"),\n          (name = \"signal-no-globals.wasm\", wasm = embed \"wasm/signal-no-globals.wasm\"),\n          (name = \"signal-bounds-check-overflow.wasm\", wasm = embed \"wasm/signal-bounds-check-overflow.wasm\"),\n          (name = \"signal-bounds-check-edge.wasm\", wasm = embed \"wasm/signal-bounds-check-edge.wasm\"),\n          (name = \"signal-bounds-check-valid.wasm\", wasm = embed \"wasm/signal-bounds-check-valid.wasm\"),\n          (name = \"signal-decoy-memory.wasm\", wasm = embed \"wasm/signal-decoy-memory.wasm\"),\n          (name = \"signal-externref-memory.wasm\", wasm = embed \"wasm/signal-externref-memory.wasm\"),\n          (name = \"signal-imported-memory.wasm\", wasm = embed \"wasm/signal-imported-memory.wasm\"),\n          (name = \"signal-memory-reclaim.wasm\", wasm = embed \"wasm/signal-memory-reclaim.wasm\"),\n          (name = \"signal-preinit.wasm\", wasm = embed \"wasm/signal-preinit.wasm\"),\n        ],\n        compatibilityFlags = [\"experimental\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/io/tracked-wasm-instance-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"tracked-wasm-instance.h\"\n\n#include <kj/test.h>\n\n#include <cstring>\n\nnamespace workerd {\nnamespace {\n\n// ---------------------------------------------------------------------------\n// SignalSafeList tests\n// ---------------------------------------------------------------------------\n\nKJ_TEST(\"SignalSafeList pushFront and iterate\") {\n  SignalSafeList<int> list;\n\n  KJ_EXPECT(list.isEmpty());\n\n  list.pushFront(3);\n  list.pushFront(2);\n  list.pushFront(1);\n\n  KJ_EXPECT(!list.isEmpty());\n\n  // Iterate should visit 1, 2, 3 (pushFront prepends).\n  int expected = 1;\n  list.iterate([&](int value) {\n    KJ_EXPECT(value == expected, value, expected);\n    ++expected;\n  });\n  KJ_EXPECT(expected == 4);\n}\n\nKJ_TEST(\"SignalSafeList filter removes matching nodes\") {\n  SignalSafeList<int> list;\n\n  list.pushFront(5);\n  list.pushFront(4);\n  list.pushFront(3);\n  list.pushFront(2);\n  list.pushFront(1);\n\n  // Keep only odd numbers.\n  list.filter([](int value) { return value % 2 != 0; });\n\n  kj::Vector<int> remaining;\n  list.iterate([&](int value) { remaining.add(value); });\n\n  KJ_EXPECT(remaining.size() == 3);\n  KJ_EXPECT(remaining[0] == 1);\n  KJ_EXPECT(remaining[1] == 3);\n  KJ_EXPECT(remaining[2] == 5);\n}\n\nKJ_TEST(\"SignalSafeList filter removes all nodes\") {\n  SignalSafeList<int> list;\n\n  list.pushFront(2);\n  list.pushFront(4);\n  list.pushFront(6);\n\n  list.filter([](int) { return false; });\n\n  KJ_EXPECT(list.isEmpty());\n}\n\nKJ_TEST(\"SignalSafeList filter keeps all nodes\") {\n  SignalSafeList<int> list;\n\n  list.pushFront(1);\n  list.pushFront(2);\n  list.pushFront(3);\n\n  list.filter([](int) { return true; });\n\n  int count = 0;\n  list.iterate([&](int) { ++count; });\n  KJ_EXPECT(count == 3);\n}\n\nKJ_TEST(\"SignalSafeList single element filter remove\") {\n  SignalSafeList<int> list;\n\n  list.pushFront(42);\n\n  list.filter([](int) { return false; });\n\n  KJ_EXPECT(list.isEmpty());\n}\n\nKJ_TEST(\"SignalSafeList clear removes all nodes\") {\n  SignalSafeList<int> list;\n\n  list.pushFront(3);\n  list.pushFront(2);\n  list.pushFront(1);\n\n  KJ_EXPECT(!list.isEmpty());\n\n  list.clear();\n\n  KJ_EXPECT(list.isEmpty());\n\n  // List should be reusable after clear.\n  list.pushFront(42);\n  KJ_EXPECT(!list.isEmpty());\n  int value = 0;\n  list.iterate([&](int v) { value = v; });\n  KJ_EXPECT(value == 42);\n}\n\nKJ_TEST(\"SignalSafeList clear on empty list is a no-op\") {\n  SignalSafeList<int> list;\n\n  KJ_EXPECT(list.isEmpty());\n  list.clear();\n  KJ_EXPECT(list.isEmpty());\n}\n\nKJ_TEST(\"SignalSafeList filter removes head only\") {\n  SignalSafeList<int> list;\n\n  list.pushFront(3);\n  list.pushFront(2);\n  list.pushFront(1);\n\n  // Remove head (value 1).\n  list.filter([](int value) { return value != 1; });\n\n  kj::Vector<int> remaining;\n  list.iterate([&](int value) { remaining.add(value); });\n\n  KJ_EXPECT(remaining.size() == 2);\n  KJ_EXPECT(remaining[0] == 2);\n  KJ_EXPECT(remaining[1] == 3);\n}\n\nKJ_TEST(\"SignalSafeList filter removes tail only\") {\n  SignalSafeList<int> list;\n\n  list.pushFront(3);\n  list.pushFront(2);\n  list.pushFront(1);\n\n  // Remove tail (value 3).\n  list.filter([](int value) { return value != 3; });\n\n  kj::Vector<int> remaining;\n  list.iterate([&](int value) { remaining.add(value); });\n\n  KJ_EXPECT(remaining.size() == 2);\n  KJ_EXPECT(remaining[0] == 1);\n  KJ_EXPECT(remaining[1] == 2);\n}\n\n// ---------------------------------------------------------------------------\n// TrackedWasmInstance memory-lifetime tests\n// ---------------------------------------------------------------------------\n\n// Simulates a WASM module's backing store (e.g. a v8::BackingStore). Sets a flag on destruction so\n// tests can observe exactly when the memory is reclaimed.\nstruct FakeBackingStore {\n  FakeBackingStore(bool& destroyed, size_t size)\n      : destroyed(destroyed),\n        data(kj::heapArray<kj::byte>(size)) {\n    memset(data.begin(), 0, data.size());\n  }\n  ~FakeBackingStore() noexcept(false) {\n    destroyed = true;\n  }\n\n  bool& destroyed;\n  kj::Array<kj::byte> data;\n};\n\n// Local test helpers that replicate the signal-writing logic formerly provided by the inline free\n// functions writeShutdownSignals, clearShutdownSignals, and writeTerminatedFlags.\n// The production code now lives in TrackedWasmInstanceList methods, but these unit tests exercise\n// the raw SignalSafeList directly without requiring a jsg::Lock.\n\nvoid writeShutdownSignals(SignalSafeList<TrackedWasmInstance>& signals) {\n  signals.iterate([](TrackedWasmInstance& signal) {\n    KJ_IF_SOME(offset, signal.signalByteOffset) {\n      uint32_t value = WASM_SIGNAL_SIGXCPU;\n      signal.memory.asPtr().slice(offset, offset + sizeof(value)).copyFrom(kj::asBytes(&value, 1));\n    }\n  });\n}\n\nvoid clearShutdownSignals(SignalSafeList<TrackedWasmInstance>& signals) {\n  signals.iterate([](TrackedWasmInstance& signal) {\n    KJ_IF_SOME(offset, signal.signalByteOffset) {\n      uint32_t value = 0;\n      signal.memory.asPtr().slice(offset, offset + sizeof(value)).copyFrom(kj::asBytes(&value, 1));\n    }\n  });\n}\n\nvoid writeTerminatedFlags(SignalSafeList<TrackedWasmInstance>& signals) {\n  signals.iterate([](TrackedWasmInstance& signal) {\n    uint32_t value = 1;\n    signal.memory.asPtr()\n        .slice(signal.terminatedByteOffset, signal.terminatedByteOffset + sizeof(value))\n        .copyFrom(kj::asBytes(&value, 1));\n  });\n}\n\nKJ_TEST(\"kj::Array attach keeps memory alive after module instance is dropped\") {\n  // This test proves that the kj::Array<kj::byte> in TrackedWasmInstance, created via attach(),\n  // keeps the underlying linear memory alive even after the original owner (simulating a WASM\n  // module instance) is dropped.\n\n  bool backingStoreDestroyed = false;\n  SignalSafeList<TrackedWasmInstance> signals;\n\n  // Allocate enough room for both signal fields (signalByteOffset=0, terminatedByteOffset=4).\n  constexpr size_t kMemorySize = 64;\n  constexpr uint32_t kSignalOffset = 0;\n  constexpr uint32_t kTerminatedOffset = sizeof(uint32_t);\n\n  {\n    // Create the backing store — this simulates the WASM module instance owning linear memory.\n    auto backingStore = kj::heap<FakeBackingStore>(backingStoreDestroyed, kMemorySize);\n\n    // Build a kj::Array that points into the backing store's data and keeps it alive via attach().\n    // This mirrors what the runtime does: take an ArrayPtr into the v8::BackingStore, then attach\n    // the BackingStore so the array owns a reference.\n    kj::Array<kj::byte> memory = backingStore->data.asPtr().attach(kj::mv(backingStore));\n\n    // Register in the signal list.\n    signals.pushFront(TrackedWasmInstance{\n      .memory = kj::mv(memory),\n      .signalByteOffset = kSignalOffset,\n      .terminatedByteOffset = kTerminatedOffset,\n    });\n\n    // `backingStore` has been moved away — the only reference keeping the memory alive is the\n    // kj::Array inside the SignalSafeList.\n  }\n\n  // The backing store must still be alive — the SignalSafeList's kj::Array owns it.\n  KJ_EXPECT(!backingStoreDestroyed);\n\n  // Prove the memory is accessible: write the shutdown signal through the list.\n  writeShutdownSignals(signals);\n\n  // Read back the signal value to confirm the write landed in live memory.\n  signals.iterate([&](TrackedWasmInstance& signal) {\n    uint32_t value = 0;\n    memcpy(&value, signal.memory.begin() + kSignalOffset, sizeof(value));\n    KJ_EXPECT(value == WASM_SIGNAL_SIGXCPU, value);\n  });\n\n  // Clear the signals (writes zero), then verify the clear also works on the still-live memory.\n  clearShutdownSignals(signals);\n  signals.iterate([&](TrackedWasmInstance& signal) {\n    uint32_t value = 0xff;\n    memcpy(&value, signal.memory.begin() + kSignalOffset, sizeof(value));\n    KJ_EXPECT(value == 0, value);\n  });\n\n  // Memory is still alive after all those read/write operations.\n  KJ_EXPECT(!backingStoreDestroyed);\n\n  // Now remove the entry from the list — this destroys the kj::Array, which in turn destroys\n  // the attached FakeBackingStore.\n  signals.filter([](TrackedWasmInstance&) { return false; });\n\n  KJ_EXPECT(backingStoreDestroyed);\n  KJ_EXPECT(signals.isEmpty());\n}\n\n// ---------------------------------------------------------------------------\n// Teardown-order test — models the real Worker::Isolate destruction sequence.\n//\n// In production, the member destruction order in Worker::Isolate is:\n//\n//   1. `api` destroyed  → V8 isolate disposed  (v8Alive becomes false)\n//   2. `limitEnforcer` destroyed → ~SignalSafeList() frees remaining entries\n//\n// Each TrackedWasmInstance entry holds a shared_ptr<v8::BackingStore> whose\n// destructor lives in libv8.so and may touch V8 isolate-internal state.\n// If those shared_ptrs are destroyed in step 2 (after V8 is gone), the\n// BackingStore destructor reads freed memory → use-after-free.\n//\n// The fix: call clear() in ~Isolate()'s destructor body (before member\n// destruction begins), while V8 is still alive.\n//\n// This test models that sequence with a mock that records whether \"V8\" was\n// still alive when each backing store was freed.  Without the clear() call,\n// the test fails because the backing stores are freed after \"V8 disposal\".\n// ---------------------------------------------------------------------------\n\n// Mock backing store that records whether V8 was alive at destruction time.\nstruct V8LifetimeAwareStore {\n  V8LifetimeAwareStore(const bool& v8Alive, bool& freedAfterV8, int& dtorCount, size_t size)\n      : v8Alive(v8Alive),\n        freedAfterV8(freedAfterV8),\n        dtorCount(dtorCount),\n        data(kj::heapArray<kj::byte>(size)) {\n    memset(data.begin(), 0, data.size());\n  }\n  ~V8LifetimeAwareStore() noexcept(false) {\n    ++dtorCount;\n    if (!v8Alive) {\n      freedAfterV8 = true;\n    }\n  }\n  const bool& v8Alive;\n  bool& freedAfterV8;\n  int& dtorCount;\n  kj::Array<kj::byte> data;\n};\n\n// Helper: push a TrackedWasmInstance backed by a V8LifetimeAwareStore.\nvoid pushV8Signal(SignalSafeList<TrackedWasmInstance>& list,\n    const bool& v8Alive,\n    bool& freedAfterV8,\n    int& dtorCount) {\n  constexpr size_t kSize = 64;\n  auto store = kj::heap<V8LifetimeAwareStore>(v8Alive, freedAfterV8, dtorCount, kSize);\n  auto memory = store->data.asPtr().attach(kj::mv(store));\n  list.pushFront(TrackedWasmInstance{\n    .memory = kj::mv(memory),\n    .signalByteOffset = static_cast<uint32_t>(0),\n    .terminatedByteOffset = sizeof(uint32_t),\n  });\n}\n\nKJ_TEST(\"backing stores freed before V8 disposal when clear() is called\") {\n  // Models the FIXED teardown sequence:\n  //   1. clear() called while V8 is alive  (the fix in ~Isolate)\n  //   2. V8 disposed\n  //   3. ~SignalSafeList runs on the now-empty list\n  //\n  // This must pass: no backing store is freed after V8 disposal.\n  constexpr int kEntries = 5;\n  bool v8Alive = true;\n  bool freedAfterV8[kEntries] = {};\n  int dtorCounts[kEntries] = {};\n\n  {\n    SignalSafeList<TrackedWasmInstance> list;\n    for (int i = 0; i < kEntries; ++i) {\n      pushV8Signal(list, v8Alive, freedAfterV8[i], dtorCounts[i]);\n    }\n\n    // ---- Simulates ~Isolate() destructor body (V8 still alive) ----\n    list.clear();\n\n    // All stores freed while V8 was alive.\n    for (auto count: dtorCounts) {\n      KJ_EXPECT(count == 1, \"entry not freed by clear()\", count);\n    }\n    for (auto bad: freedAfterV8) {\n      KJ_EXPECT(!bad, \"backing store freed after V8 disposal during clear()\");\n    }\n\n    // ---- Simulates `api` member destruction (V8 disposed) ----\n    v8Alive = false;\n\n    // ---- ~SignalSafeList runs here (simulates `limitEnforcer` destruction) ----\n  }\n\n  // No store was freed after V8 disposal, and each was freed exactly once.\n  for (auto bad: freedAfterV8) {\n    KJ_EXPECT(!bad, \"backing store freed after V8 disposal\");\n  }\n  for (auto count: dtorCounts) {\n    KJ_EXPECT(count == 1, \"double-freed or leaked\", count);\n  }\n}\n\nKJ_TEST(\"backing stores freed after V8 disposal WITHOUT clear() — the bug\") {\n  // Models the BUGGY teardown (no clear() call):\n  //   1. V8 disposed\n  //   2. ~SignalSafeList frees entries → BackingStore destructors run after V8 is gone\n  //\n  // This test ASSERTS THAT THE BUG EXISTS without the fix: at least one\n  // backing store is freed after V8 disposal.  If this test ever fails, it\n  // means the destruction order changed and the fix may need revisiting.\n  constexpr int kEntries = 3;\n  bool v8Alive = true;\n  bool freedAfterV8[kEntries] = {};\n  int dtorCounts[kEntries] = {};\n\n  {\n    SignalSafeList<TrackedWasmInstance> list;\n    for (int i = 0; i < kEntries; ++i) {\n      pushV8Signal(list, v8Alive, freedAfterV8[i], dtorCounts[i]);\n    }\n\n    // NO clear() — this is the bug.\n\n    // ---- Simulates `api` member destruction (V8 disposed) ----\n    v8Alive = false;\n\n    // ---- ~SignalSafeList runs here ----\n  }\n\n  // Prove the bug: all entries were freed AFTER V8 disposal.\n  for (auto bad: freedAfterV8) {\n    KJ_EXPECT(bad, \"expected backing store to be freed after V8 disposal (bug scenario)\");\n  }\n  // But each was still freed exactly once (no double-free in the buggy path either).\n  for (auto count: dtorCounts) {\n    KJ_EXPECT(count == 1, \"double-freed or leaked in buggy path\", count);\n  }\n}\n\n// ---------------------------------------------------------------------------\n// Signal offset permutation tests\n//\n// __instance_terminated is REQUIRED for registration; __instance_signal is OPTIONAL.\n// The two permutations that reach the C++ signal list are:\n//   1. Both signal + terminated offsets present\n//   2. Only terminated offset (signal = kj::none)\n//\n// (Permutations 3 and 4 — signal-only or neither — are rejected by the JS\n// shim before reaching C++, so they are only tested in the JS test file.)\n//\n// For each permutation we verify the full operation set:\n//   - writeShutdownSignals  (writes SIGXCPU to signal address)\n//   - clearShutdownSignals  (zeros the signal address)\n//   - writeTerminatedFlags (writes 1 to terminated address)\n//   - isModuleListening          (returns true when terminated == 0)\n//   - filter via isModuleListening (removes entries where terminated != 0)\n// ---------------------------------------------------------------------------\n\n// Helper: construct a TrackedWasmInstance backed by a FakeBackingStore.\nvoid pushSignal(SignalSafeList<TrackedWasmInstance>& list,\n    bool& destroyed,\n    kj::Maybe<uint32_t> signalOffset,\n    uint32_t terminatedOffset,\n    size_t memorySize = 64) {\n  auto store = kj::heap<FakeBackingStore>(destroyed, memorySize);\n  auto memory = store->data.asPtr().attach(kj::mv(store));\n  list.pushFront(TrackedWasmInstance{\n    .memory = kj::mv(memory),\n    .signalByteOffset = signalOffset,\n    .terminatedByteOffset = terminatedOffset,\n  });\n}\n\n// Helper: read a uint32 at `offset` from the first entry in the list.\nuint32_t readU32(SignalSafeList<TrackedWasmInstance>& list, uint32_t offset) {\n  uint32_t value = 0xDEADBEEF;\n  list.iterate([&](TrackedWasmInstance& signal) {\n    memcpy(&value, signal.memory.begin() + offset, sizeof(value));\n  });\n  return value;\n}\n\n// Permutation 1: both signal and terminated offsets present.\nKJ_TEST(\"permutation: both offsets — writeShutdownSignals writes SIGXCPU\") {\n  // `destroyed` must be declared before `signals` so that `signals` is destroyed first\n  // (reverse declaration order), preventing a stack-use-after-scope when FakeBackingStore's\n  // destructor writes to `destroyed`.\n  bool destroyed = false;\n  SignalSafeList<TrackedWasmInstance> signals;\n  constexpr uint32_t kSignalOffset = 0;\n  constexpr uint32_t kTerminatedOffset = sizeof(uint32_t);\n\n  pushSignal(signals, destroyed, kSignalOffset, kTerminatedOffset);\n  writeShutdownSignals(signals);\n  KJ_EXPECT(readU32(signals, kSignalOffset) == WASM_SIGNAL_SIGXCPU);\n}\n\nKJ_TEST(\"permutation: both offsets — clearShutdownSignals zeros signal\") {\n  bool destroyed = false;\n  SignalSafeList<TrackedWasmInstance> signals;\n  constexpr uint32_t kSignalOffset = 0;\n  constexpr uint32_t kTerminatedOffset = sizeof(uint32_t);\n\n  pushSignal(signals, destroyed, kSignalOffset, kTerminatedOffset);\n  writeShutdownSignals(signals);\n  KJ_EXPECT(readU32(signals, kSignalOffset) == WASM_SIGNAL_SIGXCPU);\n  clearShutdownSignals(signals);\n  KJ_EXPECT(readU32(signals, kSignalOffset) == 0);\n}\n\nKJ_TEST(\"permutation: both offsets �� writeTerminatedFlags writes 1\") {\n  bool destroyed = false;\n  SignalSafeList<TrackedWasmInstance> signals;\n  constexpr uint32_t kSignalOffset = 0;\n  constexpr uint32_t kTerminatedOffset = sizeof(uint32_t);\n\n  pushSignal(signals, destroyed, kSignalOffset, kTerminatedOffset);\n  writeTerminatedFlags(signals);\n  KJ_EXPECT(readU32(signals, kTerminatedOffset) == 1);\n}\n\nKJ_TEST(\"permutation: both offsets — isModuleListening and filter\") {\n  bool destroyed = false;\n  SignalSafeList<TrackedWasmInstance> signals;\n  constexpr uint32_t kSignalOffset = 0;\n  constexpr uint32_t kTerminatedOffset = sizeof(uint32_t);\n\n  pushSignal(signals, destroyed, kSignalOffset, kTerminatedOffset);\n\n  signals.iterate([](TrackedWasmInstance& s) { KJ_EXPECT(s.isModuleListening()); });\n\n  writeTerminatedFlags(signals);\n  signals.iterate([](TrackedWasmInstance& s) { KJ_EXPECT(!s.isModuleListening()); });\n\n  signals.filter([](const TrackedWasmInstance& s) { return s.isModuleListening(); });\n  KJ_EXPECT(signals.isEmpty());\n  KJ_EXPECT(destroyed);\n}\n\n// Permutation 2: only terminated offset (signal = kj::none).\nKJ_TEST(\"permutation: terminated only — writeShutdownSignals is a no-op\") {\n  bool destroyed = false;\n  SignalSafeList<TrackedWasmInstance> signals;\n  constexpr size_t kMemorySize = 64;\n  constexpr uint32_t kTerminatedOffset = 0;\n\n  pushSignal(signals, destroyed, kj::none, kTerminatedOffset, kMemorySize);\n  writeShutdownSignals(signals);\n\n  // Entire memory should still be zeroed — nothing was written.\n  signals.iterate([&](TrackedWasmInstance& signal) {\n    for (size_t i = 0; i < kMemorySize; ++i) {\n      KJ_EXPECT(signal.memory[i] == 0, \"unexpected non-zero byte at offset\", i);\n    }\n  });\n}\n\nKJ_TEST(\"permutation: terminated only — clearShutdownSignals is a no-op\") {\n  bool destroyed = false;\n  SignalSafeList<TrackedWasmInstance> signals;\n  constexpr size_t kMemorySize = 64;\n  constexpr uint32_t kTerminatedOffset = 0;\n\n  pushSignal(signals, destroyed, kj::none, kTerminatedOffset, kMemorySize);\n  clearShutdownSignals(signals);\n\n  signals.iterate([&](TrackedWasmInstance& signal) {\n    for (size_t i = 0; i < kMemorySize; ++i) {\n      KJ_EXPECT(signal.memory[i] == 0, \"unexpected non-zero byte at offset\", i);\n    }\n  });\n}\n\nKJ_TEST(\"permutation: terminated only — writeTerminatedFlags writes 1\") {\n  bool destroyed = false;\n  SignalSafeList<TrackedWasmInstance> signals;\n  constexpr uint32_t kTerminatedOffset = 0;\n\n  pushSignal(signals, destroyed, kj::none, kTerminatedOffset);\n  writeTerminatedFlags(signals);\n  KJ_EXPECT(readU32(signals, kTerminatedOffset) == 1);\n}\n\nKJ_TEST(\"permutation: terminated only — isModuleListening and filter\") {\n  bool destroyed = false;\n  SignalSafeList<TrackedWasmInstance> signals;\n  constexpr uint32_t kTerminatedOffset = 0;\n\n  pushSignal(signals, destroyed, kj::none, kTerminatedOffset);\n\n  signals.iterate([](TrackedWasmInstance& s) { KJ_EXPECT(s.isModuleListening()); });\n\n  writeTerminatedFlags(signals);\n  signals.iterate([](TrackedWasmInstance& s) { KJ_EXPECT(!s.isModuleListening()); });\n\n  signals.filter([](const TrackedWasmInstance& s) { return s.isModuleListening(); });\n  KJ_EXPECT(signals.isEmpty());\n  KJ_EXPECT(destroyed);\n}\n\n// Mixed list: both permutations in a single list.\n// Verifies that operations correctly target each entry based on its signal offset.\nKJ_TEST(\"permutation: mixed list — both-offsets and terminated-only entries coexist\") {\n  bool destroyedBoth = false;\n  bool destroyedTermOnly = false;\n  SignalSafeList<TrackedWasmInstance> signals;\n  constexpr size_t kMemorySize = 64;\n\n  // Push the both-offsets entry first (signal=0, terminated=4).\n  pushSignal(signals, destroyedBoth, static_cast<uint32_t>(0), sizeof(uint32_t), kMemorySize);\n  // Push the terminated-only entry second (it becomes the head).\n  pushSignal(signals, destroyedTermOnly, kj::none, 0, kMemorySize);\n\n  // --- writeShutdownSignals: only the both-offsets entry gets SIGXCPU ---\n  writeShutdownSignals(signals);\n\n  int index = 0;\n  signals.iterate([&](TrackedWasmInstance& signal) {\n    if (index == 0) {\n      // Head = terminated-only entry. Entire memory should be untouched.\n      for (size_t i = 0; i < kMemorySize; ++i) {\n        KJ_EXPECT(signal.memory[i] == 0, \"terminated-only entry modified at offset\", i);\n      }\n    } else {\n      // Both-offsets entry. Signal at offset 0 should be SIGXCPU.\n      uint32_t value = 0;\n      memcpy(&value, signal.memory.begin(), sizeof(value));\n      KJ_EXPECT(value == WASM_SIGNAL_SIGXCPU, value);\n    }\n    ++index;\n  });\n\n  // --- clearShutdownSignals: zeros the both-offsets entry's signal ---\n  clearShutdownSignals(signals);\n\n  index = 0;\n  signals.iterate([&](TrackedWasmInstance& signal) {\n    if (index == 1) {\n      uint32_t value = 0xff;\n      memcpy(&value, signal.memory.begin(), sizeof(value));\n      KJ_EXPECT(value == 0, \"clear did not zero signal on both-offsets entry\");\n    }\n    ++index;\n  });\n\n  // --- writeTerminatedFlags: both entries get terminated=1 ---\n  writeTerminatedFlags(signals);\n\n  index = 0;\n  signals.iterate([&](TrackedWasmInstance& signal) {\n    uint32_t terminated = 0;\n    memcpy(&terminated, signal.memory.begin() + signal.terminatedByteOffset, sizeof(terminated));\n    KJ_EXPECT(terminated == 1, \"entry\", index, \"terminated\", terminated);\n    ++index;\n  });\n\n  // --- isModuleListening: both should report not listening ---\n  signals.iterate([](TrackedWasmInstance& s) { KJ_EXPECT(!s.isModuleListening()); });\n\n  // --- filter: both entries removed, memory reclaimed ---\n  signals.filter([](const TrackedWasmInstance& s) { return s.isModuleListening(); });\n  KJ_EXPECT(signals.isEmpty());\n  KJ_EXPECT(destroyedBoth);\n  KJ_EXPECT(destroyedTermOnly);\n}\n\n// ---------------------------------------------------------------------------\n// TrackedWasmInstanceList method tests\n//\n// The methods writeShutdownSignal(), clearShutdownSignal(), and writeTerminatedSignal() on\n// TrackedWasmInstanceList are the production entry points called from signal handlers and the\n// CPU time limiter. The tests above exercise the same underlying logic through test-local\n// helpers on a raw SignalSafeList; these tests verify the methods themselves work correctly\n// through the TrackedWasmInstanceList wrapper.\n//\n// Since registerSignal() requires a jsg::Lock& (not available in plain KJ tests), we\n// populate the internal list directly via const_cast on signals(). This is acceptable in\n// tests — it mirrors what registerSignal() does internally.\n// ---------------------------------------------------------------------------\n\n// Helper: push a TrackedWasmInstance into a TrackedWasmInstanceList's internal list, bypassing\n// registerSignal() which requires jsg::Lock&.\nvoid pushEntry(const TrackedWasmInstanceList& list,\n    bool& destroyed,\n    kj::Maybe<uint32_t> signalOffset,\n    uint32_t terminatedOffset,\n    size_t memorySize = 64) {\n  auto store = kj::heap<FakeBackingStore>(destroyed, memorySize);\n  auto memory = store->data.asPtr().attach(kj::mv(store));\n  const_cast<SignalSafeList<TrackedWasmInstance>&>(list.signals())\n      .pushFront(TrackedWasmInstance{\n        .memory = kj::mv(memory),\n        .signalByteOffset = signalOffset,\n        .terminatedByteOffset = terminatedOffset,\n      });\n}\n\n// Helper: read a uint32 at `offset` from the first entry via the TrackedWasmInstanceList.\nuint32_t readU32FromList(const TrackedWasmInstanceList& list, uint32_t offset) {\n  uint32_t value = 0xDEADBEEF;\n  const_cast<SignalSafeList<TrackedWasmInstance>&>(list.signals())\n      .iterate([&](TrackedWasmInstance& entry) {\n    memcpy(&value, entry.memory.begin() + offset, sizeof(value));\n  });\n  return value;\n}\n\nKJ_TEST(\"TrackedWasmInstanceList::writeShutdownSignal writes SIGXCPU\") {\n  bool destroyed = false;\n  TrackedWasmInstanceList list;\n  constexpr uint32_t kSignalOffset = 0;\n  constexpr uint32_t kTerminatedOffset = sizeof(uint32_t);\n\n  pushEntry(list, destroyed, kSignalOffset, kTerminatedOffset);\n  list.writeShutdownSignal();\n  KJ_EXPECT(readU32FromList(list, kSignalOffset) == WASM_SIGNAL_SIGXCPU);\n}\n\nKJ_TEST(\"TrackedWasmInstanceList::clearShutdownSignal zeros the signal\") {\n  bool destroyed = false;\n  TrackedWasmInstanceList list;\n  constexpr uint32_t kSignalOffset = 0;\n  constexpr uint32_t kTerminatedOffset = sizeof(uint32_t);\n\n  pushEntry(list, destroyed, kSignalOffset, kTerminatedOffset);\n  list.writeShutdownSignal();\n  KJ_EXPECT(readU32FromList(list, kSignalOffset) == WASM_SIGNAL_SIGXCPU);\n\n  list.clearShutdownSignal();\n  KJ_EXPECT(readU32FromList(list, kSignalOffset) == 0);\n}\n\nKJ_TEST(\"TrackedWasmInstanceList::writeTerminatedSignal writes 1\") {\n  bool destroyed = false;\n  TrackedWasmInstanceList list;\n  constexpr uint32_t kSignalOffset = 0;\n  constexpr uint32_t kTerminatedOffset = sizeof(uint32_t);\n\n  pushEntry(list, destroyed, kSignalOffset, kTerminatedOffset);\n  list.writeTerminatedSignal();\n  KJ_EXPECT(readU32FromList(list, kTerminatedOffset) == 1);\n}\n\nKJ_TEST(\"TrackedWasmInstanceList::writeShutdownSignal skips entries without signal offset\") {\n  bool destroyed = false;\n  TrackedWasmInstanceList list;\n  constexpr size_t kMemorySize = 64;\n  constexpr uint32_t kTerminatedOffset = 0;\n\n  // Entry with no signal offset (kj::none).\n  pushEntry(list, destroyed, kj::none, kTerminatedOffset, kMemorySize);\n  list.writeShutdownSignal();\n\n  // Entire memory should still be zeroed — writeShutdownSignal is a no-op for this entry.\n  const_cast<SignalSafeList<TrackedWasmInstance>&>(list.signals())\n      .iterate([&](TrackedWasmInstance& entry) {\n    for (size_t i = 0; i < kMemorySize; ++i) {\n      KJ_EXPECT(entry.memory[i] == 0, \"unexpected non-zero byte at offset\", i);\n    }\n  });\n}\n\nKJ_TEST(\"TrackedWasmInstanceList::writeTerminatedSignal works with signal-only entry\") {\n  bool destroyed = false;\n  TrackedWasmInstanceList list;\n  constexpr uint32_t kTerminatedOffset = 0;\n\n  pushEntry(list, destroyed, kj::none, kTerminatedOffset);\n  list.writeTerminatedSignal();\n  KJ_EXPECT(readU32FromList(list, kTerminatedOffset) == 1);\n}\n\nKJ_TEST(\"TrackedWasmInstanceList methods work on a mixed list\") {\n  bool destroyedBoth = false;\n  bool destroyedTermOnly = false;\n  TrackedWasmInstanceList list;\n  constexpr size_t kMemorySize = 64;\n\n  // Push a both-offsets entry (signal=0, terminated=4).\n  pushEntry(list, destroyedBoth, static_cast<uint32_t>(0), sizeof(uint32_t), kMemorySize);\n  // Push a terminated-only entry (it becomes the head).\n  pushEntry(list, destroyedTermOnly, kj::none, 0, kMemorySize);\n\n  // writeShutdownSignal: only the both-offsets entry gets SIGXCPU.\n  list.writeShutdownSignal();\n\n  int index = 0;\n  const_cast<SignalSafeList<TrackedWasmInstance>&>(list.signals())\n      .iterate([&](TrackedWasmInstance& entry) {\n    if (index == 0) {\n      // Head = terminated-only entry. Entire memory should be untouched.\n      for (size_t i = 0; i < kMemorySize; ++i) {\n        KJ_EXPECT(entry.memory[i] == 0, \"terminated-only entry modified at offset\", i);\n      }\n    } else {\n      // Both-offsets entry. Signal at offset 0 should be SIGXCPU.\n      uint32_t value = 0;\n      memcpy(&value, entry.memory.begin(), sizeof(value));\n      KJ_EXPECT(value == WASM_SIGNAL_SIGXCPU, value);\n    }\n    ++index;\n  });\n\n  // clearShutdownSignal: zeros the both-offsets entry's signal.\n  list.clearShutdownSignal();\n\n  index = 0;\n  const_cast<SignalSafeList<TrackedWasmInstance>&>(list.signals())\n      .iterate([&](TrackedWasmInstance& entry) {\n    if (index == 1) {\n      uint32_t value = 0xff;\n      memcpy(&value, entry.memory.begin(), sizeof(value));\n      KJ_EXPECT(value == 0, \"clear did not zero signal on both-offsets entry\");\n    }\n    ++index;\n  });\n\n  // writeTerminatedSignal: both entries get terminated=1.\n  list.writeTerminatedSignal();\n\n  index = 0;\n  const_cast<SignalSafeList<TrackedWasmInstance>&>(list.signals())\n      .iterate([&](TrackedWasmInstance& entry) {\n    uint32_t terminated = 0;\n    memcpy(&terminated, entry.memory.begin() + entry.terminatedByteOffset, sizeof(terminated));\n    KJ_EXPECT(terminated == 1, \"entry\", index, \"terminated\", terminated);\n    ++index;\n  });\n}\n\nKJ_TEST(\"TrackedWasmInstanceList methods are no-ops on an empty list\") {\n  TrackedWasmInstanceList list;\n\n  // These should not crash or have any observable effect.\n  list.writeShutdownSignal();\n  list.clearShutdownSignal();\n  list.writeTerminatedSignal();\n\n  KJ_EXPECT(list.signals().isEmpty());\n}\n\n// ---------------------------------------------------------------------------\n// TrackedWasmInstance tests are also covered by the JS-level\n// tracked-wasm-instance-js-test.wd-test, which runs inside a real workerd\n// instance with V8 initialized. Registration requires the WebAssembly.instantiate\n// shim, so we test via JS rather than a plain kj_test.\n// ---------------------------------------------------------------------------\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/tracked-wasm-instance-test.js",
    "content": "// Tests for the WASM shutdown signal registration shim.\n//\n// These tests verify that the shimWebAssemblyInstantiate() code in worker.c++ correctly\n// detects __instance_signal / __instance_terminated exports, handles various memory\n// configurations, and rejects out-of-bounds addresses.\n\nimport basicModule from 'signal-basic.wasm';\nimport partialModule from 'signal-partial-exports.wasm';\nimport terminatedOnlyModule from 'signal-terminated-only.wasm';\nimport noGlobalsModule from 'signal-no-globals.wasm';\nimport overflowModule from 'signal-bounds-check-overflow.wasm';\nimport edgeModule from 'signal-bounds-check-edge.wasm';\nimport validModule from 'signal-bounds-check-valid.wasm';\nimport decoyModule from 'signal-decoy-memory.wasm';\nimport externrefMemoryModule from 'signal-externref-memory.wasm';\nimport importedMemoryModule from 'signal-imported-memory.wasm';\nimport reclaimModule from 'signal-memory-reclaim.wasm';\nimport preinitModule from 'signal-preinit.wasm';\n\n// ---------------------------------------------------------------------------\n// Export permutation tests\n//\n// __instance_terminated is REQUIRED for registration; __instance_signal is OPTIONAL.\n// The four permutations:\n//   1. Both present          → registers (signal + terminated)\n//   2. Only terminated       → registers (terminated only)\n//   3. Only signal           → NOT registered\n//   4. Neither               → NOT registered\n// ---------------------------------------------------------------------------\n\n// Permutation 1: both __instance_signal and __instance_terminated present.\n// The module should be registered and both addresses are functional.\nexport let bothGlobalsRegisters = {\n  async test() {\n    const instance = await WebAssembly.instantiate(basicModule);\n    // Registration should zero the signal field.\n    if (instance.exports.get_signal() !== 0) {\n      throw new Error('Expected signal to be 0 initially');\n    }\n  },\n};\n\n// Permutation 1 (sync): same test via the sync WebAssembly.Instance constructor.\nexport let syncBothGlobalsRegisters = {\n  test() {\n    const instance = new WebAssembly.Instance(basicModule);\n    if (instance.exports.get_signal() !== 0) {\n      throw new Error('Expected signal to be 0 initially');\n    }\n  },\n};\n\n// Permutation 2: only __instance_terminated present (no __instance_signal).\n// The module should be registered — __instance_signal is optional.\nexport let terminatedOnlyRegisters = {\n  async test() {\n    const instance = await WebAssembly.instantiate(terminatedOnlyModule);\n    if (instance.exports.get_terminated() !== 0) {\n      throw new Error('Expected terminated to be 0 initially');\n    }\n  },\n};\n\n// Permutation 2 (sync): same test via the sync WebAssembly.Instance constructor.\nexport let syncTerminatedOnlyRegisters = {\n  test() {\n    const instance = new WebAssembly.Instance(terminatedOnlyModule);\n    if (instance.exports.get_terminated() !== 0) {\n      throw new Error('Expected terminated to be 0 initially');\n    }\n  },\n};\n\n// Permutation 3: only __instance_signal present (no __instance_terminated).\n// The module should NOT be registered — __instance_terminated is required.\nexport let signalOnlySkipped = {\n  async test() {\n    const instance = await WebAssembly.instantiate(partialModule);\n    // Should succeed — the shim just doesn't register it.\n    if (instance.exports.get_signal() !== 0) {\n      throw new Error('Expected signal to be 0 initially');\n    }\n  },\n};\n\n// Permutation 3 (sync): same test via the sync WebAssembly.Instance constructor.\nexport let syncSignalOnlySkipped = {\n  test() {\n    const instance = new WebAssembly.Instance(partialModule);\n    if (instance.exports.get_signal() !== 0) {\n      throw new Error('Expected signal to be 0 initially');\n    }\n  },\n};\n\n// Permutation 4: neither __instance_signal nor __instance_terminated present.\n// The module should NOT be registered and should instantiate without error.\nexport let noGlobalsSkipped = {\n  async test() {\n    const instance = await WebAssembly.instantiate(noGlobalsModule);\n    // Module has a simple add function — verify it works.\n    if (instance.exports.add(2, 3) !== 5) {\n      throw new Error('Expected add(2, 3) to return 5');\n    }\n  },\n};\n\n// Permutation 4 (sync): same test via the sync WebAssembly.Instance constructor.\nexport let syncNoGlobalsSkipped = {\n  test() {\n    const instance = new WebAssembly.Instance(noGlobalsModule);\n    if (instance.exports.add(2, 3) !== 5) {\n      throw new Error('Expected add(2, 3) to return 5');\n    }\n  },\n};\n\n// Memory at the signal address is pre-initialized to 0xDEADBEEF via a data segment.\n// Registration should zero the signal field.\nexport let registrationZerosPreinitMemory = {\n  async test() {\n    const instance = await WebAssembly.instantiate(preinitModule);\n    if (instance.exports.get_signal() !== 0) {\n      throw new Error(\n        'Expected signal to be zeroed, got ' + instance.exports.get_signal()\n      );\n    }\n  },\n};\n\n// Same test via the sync WebAssembly.Instance constructor.\nexport let syncRegistrationZerosPreinitMemory = {\n  test() {\n    const instance = new WebAssembly.Instance(preinitModule);\n    if (instance.exports.get_signal() !== 0) {\n      throw new Error(\n        'Expected signal to be zeroed, got ' + instance.exports.get_signal()\n      );\n    }\n  },\n};\n\n// ---------------------------------------------------------------------------\n// Bounds checking tests\n// ---------------------------------------------------------------------------\n\n// __instance_signal beyond memory bounds — registration is silently skipped.\nexport let boundsCheckOverflow = {\n  async test() {\n    // Should instantiate without error; the module simply won't receive shutdown signals.\n    await WebAssembly.instantiate(overflowModule);\n  },\n};\n\n// __instance_signal at 65533 leaves only 3 bytes but needs 4 — silently skipped.\nexport let boundsCheckEdge = {\n  async test() {\n    await WebAssembly.instantiate(edgeModule);\n  },\n};\n\n// Both addresses exactly at the boundary — should succeed.\nexport let boundsCheckValid = {\n  async test() {\n    const instance = await WebAssembly.instantiate(validModule);\n    if (instance.exports.get_signal() !== 0) {\n      throw new Error('Expected signal to be 0 initially');\n    }\n  },\n};\n\n// ---------------------------------------------------------------------------\n// Memory detection tests\n// ---------------------------------------------------------------------------\n\n// A module that imports memory (not exports it) should still register.\nexport let importedMemoryDetected = {\n  async test() {\n    const memory = new WebAssembly.Memory({ initial: 1 });\n    const instance = await WebAssembly.instantiate(importedMemoryModule, {\n      env: { memory },\n    });\n    // If registration threw, we wouldn't get here.\n    if (instance.exports.get_signal() !== 0) {\n      throw new Error('Expected signal to be 0 initially');\n    }\n  },\n};\n\n// A WebAssembly.Memory passed as a non-memory import must not be used as the\n// module's linear memory. The module has internal memory but doesn't export it.\nexport let decoyMemoryIgnored = {\n  async test() {\n    const decoyMemory = new WebAssembly.Memory({ initial: 1 });\n    // The module imports (func \"env\" \"log\") only — decoy_memory is extra.\n    const instance = await WebAssembly.instantiate(decoyModule, {\n      env: {\n        log: () => {},\n        decoy_memory: decoyMemory,\n      },\n    });\n    // The shim should have found no memory (internal memory is inaccessible).\n    // Verify decoy memory is untouched (we can't trigger writeShutdownSignal\n    // in workerd, but we can verify instantiation didn't blow up).\n    const view = new Uint32Array(decoyMemory.buffer);\n    if (view[0] !== 0) {\n      throw new Error('Decoy memory was modified during instantiation');\n    }\n  },\n};\n\n// A module that imports a global named \"memory\" as externref must not be\n// confused for a linear memory import.  The shim checks Module.imports() kind\n// and should skip registration because the import's kind is 'global', not 'memory'.\nexport let externrefMemoryIgnored = {\n  async test() {\n    const instance = await WebAssembly.instantiate(externrefMemoryModule, {\n      env: { memory: null },\n    });\n    // Should instantiate fine — shim just doesn't register it.\n    if (instance.exports.get_value() !== 42) {\n      throw new Error('Expected get_value() to return 42');\n    }\n  },\n};\n\n// The sync Instance constructor should also detect imported memory.\nexport let syncInstanceImportedMemory = {\n  test() {\n    const memory = new WebAssembly.Memory({ initial: 1 });\n    const instance = new WebAssembly.Instance(importedMemoryModule, {\n      env: { memory },\n    });\n    if (instance.exports.get_signal() !== 0) {\n      throw new Error('Expected signal to be 0 initially');\n    }\n  },\n};\n\n// ---------------------------------------------------------------------------\n// GC reclamation test\n// ---------------------------------------------------------------------------\n\n// Instantiate many large (16MB) WASM modules, mark each as \"exited\" via\n// mark_exited(), then let GC reclaim them. If the GC prologue filter doesn't\n// clean up terminated entries, this will OOM.\nexport let gcReclaimsTerminatedModules = {\n  async test() {\n    for (let i = 0; i < 20; i++) {\n      const instance = await WebAssembly.instantiate(reclaimModule);\n      // Mark the module as exited so the GC prologue filter removes it.\n      instance.exports.mark_exited();\n    }\n    // If we get here without OOM, reclamation is working.\n  },\n};\n"
  },
  {
    "path": "src/workerd/io/tracked-wasm-instance.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"tracked-wasm-instance.h\"\n\nnamespace workerd {\n\nvoid TrackedWasmInstanceList::registerSignal(jsg::Lock&,\n    kj::Array<kj::byte> memory,\n    kj::Maybe<uint32_t> signalOffset,\n    uint32_t terminatedOffset) const {\n  // Silently skip registration if the terminated address would fall outside the module's linear\n  // memory. The terminated field is always required.\n  if (static_cast<size_t>(terminatedOffset) + WASM_SIGNAL_FIELD_BYTES > memory.size()) {\n    return;\n  }\n  // If a signal offset was provided, validate it fits in memory too.\n  KJ_IF_SOME(offset, signalOffset) {\n    if (static_cast<size_t>(offset) + WASM_SIGNAL_FIELD_BYTES > memory.size()) {\n      return;\n    }\n    // Zero the signal address to clear any stale signals.\n    uint32_t value = 0;\n    memory.asPtr().slice(offset, offset + WASM_SIGNAL_FIELD_BYTES).copyFrom(kj::asBytes(&value, 1));\n  }\n\n  // Safe to const_cast: the jsg::Lock& parameter proves we hold the isolate lock, which is the\n  // synchronization required by the signal-safe list for mutations.\n  const_cast<SignalSafeList<TrackedWasmInstance>&>(list).pushFront(\n      TrackedWasmInstance{.memory = kj::mv(memory),\n        .signalByteOffset = kj::mv(signalOffset),\n        .terminatedByteOffset = terminatedOffset});\n}\n\nvoid TrackedWasmInstanceList::filter(jsg::Lock&) const {\n  // Safe to const_cast: the jsg::Lock& parameter proves we hold the isolate lock.\n  const_cast<SignalSafeList<TrackedWasmInstance>&>(list).filter(\n      [](const TrackedWasmInstance& signal) { return signal.isModuleListening(); });\n}\n\nvoid TrackedWasmInstanceList::clear(jsg::Lock&) const {\n  // Safe to const_cast: the jsg::Lock& parameter proves we hold the isolate lock.\n  const_cast<SignalSafeList<TrackedWasmInstance>&>(list).clear();\n}\n\nvoid TrackedWasmInstanceList::writeShutdownSignal() const {\n  // Safe to const_cast: this is called from a signal handler on the same thread that holds the\n  // isolate lock, so there is no concurrent mutation of the list structure.\n  const_cast<SignalSafeList<TrackedWasmInstance>&>(list).iterate([](TrackedWasmInstance& signal) {\n    KJ_IF_SOME(offset, signal.signalByteOffset) {\n      uint32_t value = WASM_SIGNAL_SIGXCPU;\n      signal.memory.asPtr().slice(offset, offset + sizeof(value)).copyFrom(kj::asBytes(&value, 1));\n    }\n  });\n}\n\nvoid TrackedWasmInstanceList::clearShutdownSignal() const {\n  // Safe to const_cast: same-thread signal-handler context, no concurrent list mutation.\n  const_cast<SignalSafeList<TrackedWasmInstance>&>(list).iterate([](TrackedWasmInstance& signal) {\n    KJ_IF_SOME(offset, signal.signalByteOffset) {\n      uint32_t value = 0;\n      signal.memory.asPtr().slice(offset, offset + sizeof(value)).copyFrom(kj::asBytes(&value, 1));\n    }\n  });\n}\n\nvoid TrackedWasmInstanceList::writeTerminatedSignal() const {\n  // Safe to const_cast: same-thread signal-handler context, no concurrent list mutation.\n  const_cast<SignalSafeList<TrackedWasmInstance>&>(list).iterate([](TrackedWasmInstance& signal) {\n    uint32_t value = 1;\n    signal.memory.asPtr()\n        .slice(signal.terminatedByteOffset, signal.terminatedByteOffset + sizeof(value))\n        .copyFrom(kj::asBytes(&value, 1));\n  });\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/tracked-wasm-instance.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/array.h>\n#include <kj/common.h>\n\n#include <cstdint>\n\nnamespace workerd {\n\nnamespace jsg {\nclass Lock;\n}  // namespace jsg\n\n// Byte size of each signal field in WASM linear memory (a single uint32).\nconstexpr size_t WASM_SIGNAL_FIELD_BYTES = sizeof(uint32_t);\n\n// Represents a single WASM module that has opted into receiving the \"shut down\" signal when CPU\n// time is nearly exhausted. The module must export at least \"__instance_terminated\"; the\n// \"__instance_signal\" export is optional:\n//\n//   \"__instance_signal\"     — (optional) address of a uint32 in linear memory. When present,\n//                            the runtime writes SIGXCPU (24) here when CPU time is nearly\n//                            exhausted.\n//   \"__instance_terminated\" - address of a uint32 in linear memory. The WASM module writes a\n//                            non-zero value here when it has exited and is no longer listening.\n//                            The runtime checks this in a GC prologue hook and removes entries\n//                            where terminated is non-zero, allowing the linear memory to be\n//                            reclaimed. The runtime also writes 1 here when the isolate is\n//                            killed after exceeding its CPU limit.\nstruct TrackedWasmInstance {\n  // Owns a reference to the WASM module's linear memory. The underlying v8::BackingStore is kept\n  // alive via kj::Array's attach() mechanism, preventing V8 from garbage-collecting the memory\n  // while we still need to read/write signal addresses. This gets cleaned up in a V8 GC prologue\n  // hook where we atomically remove the entry from the signal list before releasing the memory.\n  //\n  // TODO: If a user were to grow a 64 bit linear memory >16GB, relocation will happen and this\n  // array will point to stale (but not free'd) memory. The impact is that the user will see a\n  // spike in memory usage and no longer receive the signal in that module instance. In practice this\n  // should almost never happen since they would hit a memory limit well before 16GB,\n  // and 64 bit WASM is currently used very infrequently anyways. Regardless, we should address this\n  // soon.\n  kj::Array<kj::byte> memory;\n\n  // Offset into `memory` of the uint32 the runtime writes SIGXCPU (24) to (__instance_signal).\n  // When kj::none, the module did not export __instance_signal and will not receive the\n  // SIGXCPU shutdown warning, but will still receive the terminated flag.\n  kj::Maybe<uint32_t> signalByteOffset;\n\n  // Offset into `memory` of the uint32 the module writes to (__instance_terminated).\n  uint32_t terminatedByteOffset;\n\n  // Returns true if the module is still listening for signals (terminated == 0).\n  // Returns false if the module has exited and this entry should be removed.\n  bool isModuleListening() const {\n    uint32_t terminated = 0;\n    for (auto& b:\n        memory.slice(terminatedByteOffset, terminatedByteOffset + WASM_SIGNAL_FIELD_BYTES)) {\n      terminated |= b;\n    }\n    return terminated == 0;\n  }\n};\n\n// A linked list type which is signal-safe (for reading), but not thread safe - it can handle\n// same-thread concurrency and pre-emptive reads ONLY.\n// SAFETY: All mutations are must happen with the isolate lock held!\ntemplate <typename T>\nclass SignalSafeList {\n public:\n  struct Node {\n    T value;\n    Node* next;\n    template <typename... Args>\n    explicit Node(Args&&... args): value(kj::fwd<Args>(args)...),\n                                   next(nullptr) {}\n  };\n\n  SignalSafeList() {}\n\n  ~SignalSafeList() noexcept(false) {\n    Node* node = __atomic_load_n(&head, __ATOMIC_RELAXED);\n    while (node != nullptr) {\n      Node* doomed = node;\n      node = __atomic_load_n(&doomed->next, __ATOMIC_RELAXED);\n      delete doomed;\n    }\n  }\n\n  // Prepends a new node constructed from `args` at the front of the list\n  template <typename... Args>\n  void pushFront(Args&&... args) {\n    Node* node = new Node(kj::fwd<Args>(args)...);\n    __atomic_store_n(&node->next, __atomic_load_n(&head, __ATOMIC_RELAXED), __ATOMIC_RELAXED);\n    __atomic_store_n(&head, node, __ATOMIC_RELEASE);\n  }\n\n  // Removes all nodes for which `predicate(node.value)` returns false\n  template <typename Predicate>\n  void filter(Predicate&& predicate) noexcept {\n    Node** prev = &head;\n    Node* current = __atomic_load_n(prev, __ATOMIC_RELAXED);\n\n    while (current != nullptr) {\n      Node* next = __atomic_load_n(&current->next, __ATOMIC_RELAXED);\n\n      if (predicate(current->value)) {\n        prev = &current->next;\n      } else {\n        // Splice out `current` by pointing its predecessor at `next`. Release ordering ensures a\n        // signal handler that loads *prev with acquire sees a fully consistent successor chain.\n        __atomic_store_n(prev, next, __ATOMIC_RELEASE);\n        delete current;\n      }\n\n      current = next;\n    }\n  }\n\n  // Removes all nodes from the list, destroying each one.\n  void clear() noexcept {\n    Node* current = __atomic_load_n(&head, __ATOMIC_RELAXED);\n    __atomic_store_n(&head, static_cast<Node*>(nullptr), __ATOMIC_RELEASE);\n    while (current != nullptr) {\n      Node* next = __atomic_load_n(&current->next, __ATOMIC_RELAXED);\n      delete current;\n      current = next;\n    }\n  }\n\n  // Returns true if the list is empty. Signal safe.\n  bool isEmpty() const {\n    return __atomic_load_n(&head, __ATOMIC_ACQUIRE) == nullptr;\n  }\n\n  // Traverses the list, calling `func(node.value)` for each node. Signal-safe (same-thread\n  // only), but not thread-safe — callers from a signal handler context should const_cast.\n  template <typename Func>\n  void iterate(Func&& func) {\n    Node* current = __atomic_load_n(&head, __ATOMIC_ACQUIRE);\n    while (current != nullptr) {\n      func(current->value);\n      current = __atomic_load_n(&current->next, __ATOMIC_ACQUIRE);\n    }\n  }\n\n private:\n  Node* head = nullptr;\n\n  KJ_DISALLOW_COPY_AND_MOVE(SignalSafeList);\n};\n\n// Encapsulates a SignalSafeList<TrackedWasmInstance> with operations that require the isolate\n// lock for mutation, and a read-only accessor for signal-handler use.\n//\n// The mutation methods are const and accept a jsg::Lock& to prove the caller holds the isolate\n// lock. Internally they const_cast the list, which is safe because the lock provides the\n// required synchronization. This design allows IsolateLimitEnforcer (whose methods are const\n// per KJ convention) to return a const reference without exposing mutable access to code that\n// does not hold the lock.\nclass TrackedWasmInstanceList {\n public:\n  // Registers a WASM module for receiving the \"shut down\" signal. The signal offset is optional:\n  // when kj::none, the module will still receive the terminated flag but will not get SIGXCPU.\n  // Silently skips registration if any provided offset falls outside the module's linear memory.\n  void registerSignal(jsg::Lock&,\n      kj::Array<kj::byte> memory,\n      kj::Maybe<uint32_t> signalOffset,\n      uint32_t terminatedOffset) const;\n\n  // Filters out entries where the module has exited (terminated != 0). Call from a GC prologue\n  // hook to allow linear memory to be reclaimed.\n  void filter(jsg::Lock&) const;\n\n  // Removes all entries unconditionally. Call before the V8 isolate is disposed, since each\n  // entry holds a shared_ptr<v8::BackingStore> whose destructor may access V8 state.\n  void clear(jsg::Lock&) const;\n\n  void writeShutdownSignal() const;\n\n  void clearShutdownSignal() const;\n\n  void writeTerminatedSignal() const;\n\n  // Returns the underlying signal-safe list for use by signal handlers and the CPU time limiter.\n  // The returned reference is const; signal-handler free functions use const_cast internally.\n  const SignalSafeList<TrackedWasmInstance>& signals() const {\n    return list;\n  }\n\n private:\n  SignalSafeList<TrackedWasmInstance> list;\n};\n\n// The value written to the signal address when CPU time is nearly exhausted.\n// This is the UNIX signal number for SIGXCPU (24). Technically the number itself\n// is not standardized, but for most architectures it is 24 so that is what we're going with.\n// We're inventing WASM signals from scratch so we can do whatever we want.\nconstexpr uint32_t WASM_SIGNAL_SIGXCPU = 24;\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/wasm/.gitignore",
    "content": "*.wasm\n"
  },
  {
    "path": "src/workerd/io/wasm/BUILD.bazel",
    "content": "load(\"//:build/wasm_tools_parse.bzl\", \"wasm_tools_parse\")\n\n[\n    wasm_tools_parse(\n        name = name.removesuffix(\".wat\") + \".wasm\",\n        src = name,\n        visibility = [\"//visibility:public\"],\n    )\n    for name in glob([\"*.wat\"])\n]\n"
  },
  {
    "path": "src/workerd/io/wasm/signal-basic.wat",
    "content": ";; Basic WASM module exporting __instance_signal, __instance_terminated, and memory.\n;; Used to verify that the registration shim detects both globals and registers\n;; the module, and that the sync WebAssembly.Instance constructor also registers.\n\n(module\n  (memory (export \"memory\") 1)\n\n  (global (export \"__instance_signal\") i32 (i32.const 0))\n  (global (export \"__instance_terminated\") i32 (i32.const 4))\n\n  (func (export \"get_signal\") (result i32)\n    (i32.load (global.get 0))\n  )\n)\n"
  },
  {
    "path": "src/workerd/io/wasm/signal-bounds-check-edge.wat",
    "content": ";; __instance_signal at 65533 leaves only 3 bytes, but we need 4.\n\n(module\n  (memory (export \"memory\") 1)\n  (global (export \"__instance_signal\") i32 (i32.const 65533))\n  (global (export \"__instance_terminated\") i32 (i32.const 0))\n)\n"
  },
  {
    "path": "src/workerd/io/wasm/signal-bounds-check-overflow.wat",
    "content": ";; __instance_signal points beyond memory bounds (70000 > 65536).\n\n(module\n  (memory (export \"memory\") 1)\n  (global (export \"__instance_signal\") i32 (i32.const 70000))\n  (global (export \"__instance_terminated\") i32 (i32.const 70004))\n)\n"
  },
  {
    "path": "src/workerd/io/wasm/signal-bounds-check-valid.wat",
    "content": ";; Both addresses fit exactly at the boundary of a 1-page (65536-byte) memory.\n;;   __instance_signal = 65528 (4 bytes from 65528..65531)\n;;   __instance_terminated = 65532 (4 bytes from 65532..65535)\n\n(module\n  (memory (export \"memory\") 1)\n  (global (export \"__instance_signal\") i32 (i32.const 65528))\n  (global (export \"__instance_terminated\") i32 (i32.const 65532))\n\n  (func (export \"get_signal\") (result i32)\n    (i32.load (global.get 0))\n  )\n)\n"
  },
  {
    "path": "src/workerd/io/wasm/signal-decoy-memory.wat",
    "content": ";; Module with internal memory (not exported) and signal globals.\n;; Imports a function so the imports object exists, but does NOT import memory.\n;; Used to verify that a WebAssembly.Memory in the imports object is not mistaken\n;; for the module's linear memory.\n\n(module\n  (import \"env\" \"log\" (func $log (param i32)))\n\n  (memory 1)\n\n  (global (export \"__instance_signal\") i32 (i32.const 0))\n  (global (export \"__instance_terminated\") i32 (i32.const 4))\n\n  (func (export \"get_signal\") (result i32)\n    (i32.load (i32.const 0))\n  )\n)\n"
  },
  {
    "path": "src/workerd/io/wasm/signal-externref-memory.wat",
    "content": ";; Module that imports a global named \"memory\" typed as externref, NOT as a\n;; linear memory import.  The shim's findMemory() checks WebAssembly.Module.imports()\n;; and only considers descriptors whose kind is 'memory'.  An externref global\n;; named \"memory\" will have kind 'global', so the shim must NOT register this\n;; module for shutdown signal handling.\n\n(module\n  ;; Import a global named \"memory\" — but as externref, not as linear memory.\n  (import \"env\" \"memory\" (global $mem externref))\n\n  ;; The two globals that would normally trigger signal registration.\n  (global (export \"__instance_signal\") i32 (i32.const 0))\n  (global (export \"__instance_terminated\") i32 (i32.const 4))\n\n  ;; No linear memory at all — the module cannot be registered.\n  ;; Export a simple function so the test can verify instantiation succeeded.\n  (func (export \"get_value\") (result i32)\n    (i32.const 42)\n  )\n)\n"
  },
  {
    "path": "src/workerd/io/wasm/signal-imported-memory.wat",
    "content": ";; Module that imports memory rather than defining its own.\n;; The shim must find the memory in the imports object via Module.imports().\n\n(module\n  (import \"env\" \"memory\" (memory 1))\n\n  (global (export \"__instance_signal\") i32 (i32.const 0))\n  (global (export \"__instance_terminated\") i32 (i32.const 4))\n\n  (func (export \"get_signal\") (result i32)\n    (i32.load (global.get 0))\n  )\n)\n"
  },
  {
    "path": "src/workerd/io/wasm/signal-memory-reclaim.wat",
    "content": ";; Large-memory module (16MB) for GC reclamation testing.\n;; Instantiating many of these without reclaiming terminated ones would OOM.\n\n(module\n  (memory (export \"memory\") 256)\n\n  (global (export \"__instance_signal\") i32 (i32.const 0))\n  (global (export \"__instance_terminated\") i32 (i32.const 4))\n\n  ;; Write non-zero to terminated address, signaling the module has exited.\n  (func (export \"mark_exited\")\n    (i32.store (global.get 1) (i32.const 1))\n  )\n\n  (func (export \"get_signal\") (result i32)\n    (i32.load (global.get 1))\n  )\n)\n"
  },
  {
    "path": "src/workerd/io/wasm/signal-no-globals.wat",
    "content": ";; Module that exports memory but neither __instance_signal nor __instance_terminated.\n;; The shim should NOT register this module — it should instantiate without error.\n\n(module\n  (memory (export \"memory\") 1)\n\n  (func (export \"add\") (param i32 i32) (result i32)\n    (i32.add (local.get 0) (local.get 1))\n  )\n)\n"
  },
  {
    "path": "src/workerd/io/wasm/signal-partial-exports.wat",
    "content": ";; Module that exports only __instance_signal but NOT __instance_terminated.\n;; The shim should NOT register this module because __instance_terminated is required.\n\n(module\n  (memory (export \"memory\") 1)\n\n  (global (export \"__instance_signal\") i32 (i32.const 0))\n\n  (func (export \"get_signal\") (result i32)\n    (i32.load (global.get 0))\n  )\n)\n"
  },
  {
    "path": "src/workerd/io/wasm/signal-preinit.wat",
    "content": ";; Module whose signal address is pre-initialized to a non-zero value (0xDEADBEEF)\n;; via a data segment. The runtime should zero the signal field during registration,\n;; so get_signal() should return 0 after instantiation.\n\n(module\n  (memory (export \"memory\") 1)\n\n  ;; Signal at byte 0, terminated at byte 4.\n  (global (export \"__instance_signal\") i32 (i32.const 0))\n  (global (export \"__instance_terminated\") i32 (i32.const 4))\n\n  ;; Pre-fill signal (bytes 0-3) with 0xDEADBEEF.\n  (data (i32.const 0) \"\\EF\\BE\\AD\\DE\")\n\n  (func (export \"get_signal\") (result i32)\n    (i32.load (i32.const 0))\n  )\n)\n"
  },
  {
    "path": "src/workerd/io/wasm/signal-terminated-only.wat",
    "content": ";; Module that exports only __instance_terminated but NOT __instance_signal.\n;; The shim should register this module — __instance_signal is optional. The module\n;; will receive the terminated flag when the isolate is killed, but will not receive\n;; the SIGXCPU warning signal.\n\n(module\n  (memory (export \"memory\") 1)\n\n  ;; terminated at byte 0.\n  (global (export \"__instance_terminated\") i32 (i32.const 0))\n\n  (func (export \"get_terminated\") (result i32)\n    (i32.load (global.get 0))\n  )\n\n  ;; Write a non-zero value to the terminated field, signalling that this instance\n  ;; has exited and the runtime may reclaim the linear memory.\n  (func (export \"mark_exited\")\n    (i32.store (global.get 0) (i32.const 1))\n  )\n)\n"
  },
  {
    "path": "src/workerd/io/wasm-instantiate-shim.js",
    "content": "// This file contains a shim for WebAssembly.Instance and WebAssembly.instantiate. Currently, the\n// runtime does not support instantiateStreaming, but if this ever changes, we will need to add a\n// shim for that too. V8's `SetWasmInstanceCallback` was considered as an alternative, but does\n// not quite work since it runs BEFORE instantiation, when the operations we want to do must happen\n// after.\n\n(function (originalInstantiate, originalInstance, registerShutdown, wa) {\n  // Find memory from exports or imports. Returns Memory instance or undefined.\n  // When searching imports, only considers entries whose declared import kind is\n  // 'memory' (via WebAssembly.Module.imports), so that a Memory passed as an\n  // externref is not mistaken for the module's linear memory.\n  function findMemory(instance, imports, module) {\n    // First, check if memory is exported\n    const importedMemory = wa.Module.imports(module).find(\n      ({ kind }) => kind === 'memory'\n    );\n    if (importedMemory) {\n      const value = imports[importedMemory.module][importedMemory.name];\n      return value instanceof wa.Memory && value;\n    }\n    const exportedMemory = wa.Module.exports(module).find(\n      ({ kind }) => kind === 'memory'\n    );\n    if (exportedMemory) return instance.exports[exportedMemory.name];\n    return undefined;\n  }\n\n  function checkAndRegisterShutdown(instance, imports, module) {\n    const exports = instance.exports;\n    if (!exports) return;\n    const terminatedGlobal = exports['__instance_terminated'];\n    // __instance_terminated is required; __instance_signal is optional.\n    if (!(terminatedGlobal instanceof wa.Global)) return;\n    const signalGlobal = exports['__instance_signal'];\n    const hasSignal = signalGlobal instanceof wa.Global;\n    const memory = findMemory(instance, imports, module);\n    if (memory) {\n      // Pass -1 as the signal offset when __instance_signal is not exported.\n      // The C++ side interprets -1 as \"no signal address\".\n      registerShutdown(\n        memory,\n        hasSignal ? signalGlobal.value : -1,\n        terminatedGlobal.value\n      );\n    }\n  }\n\n  // WebAssembly.instantiate has two overloads:\n  //   instantiate(bytes, imports?, compileOptions?) -> Promise<{module, instance}>\n  //   instantiate(module, imports?, compileOptions?) -> Promise<Instance>\n  // Use apply to forward all arguments so compileOptions (and any future args) are not dropped.\n  wa.instantiate = function instantiate() {\n    var args = arguments;\n    return originalInstantiate.apply(wa, args).then(function (result) {\n      // Called with bytes: result is {module, instance}.\n      // Called with a Module: result is just the Instance.\n      var instance = result.instance || result;\n      var module = result.module || args[0];\n      checkAndRegisterShutdown(instance, args[1], module);\n      return result;\n    });\n  };\n\n  // new WebAssembly.Instance(module, imports?)\n  // Forward all arguments and new.target so subclassing works correctly.\n  wa.Instance = function Instance() {\n    var instance = Reflect.construct(\n      originalInstance,\n      arguments,\n      new.target || originalInstance\n    );\n    checkAndRegisterShutdown(instance, arguments[1], arguments[0]);\n    return instance;\n  };\n  // Point the shim's prototype at the original so instanceof checks continue to work.\n  wa.Instance.prototype = originalInstance.prototype;\n  Object.defineProperty(wa.Instance.prototype, 'constructor', {\n    value: wa.Instance,\n    writable: true,\n    configurable: true,\n  });\n});\n"
  },
  {
    "path": "src/workerd/io/worker-entrypoint.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"worker-entrypoint.h\"\n\n#include <workerd/api/basics.h>\n#include <workerd/api/global-scope.h>\n#include <workerd/api/util.h>\n#include <workerd/io/features.h>\n#include <workerd/io/io-context.h>\n#include <workerd/io/limit-enforcer.h>\n#include <workerd/io/tracer.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/util/http-util.h>\n#include <workerd/util/sentry.h>\n#include <workerd/util/strings.h>\n#include <workerd/util/thread-scopes.h>\n#include <workerd/util/uncaught-exception-source.h>\n#include <workerd/util/use-perfetto-categories.h>\n\n#include <capnp/message.h>\n#include <kj/compat/http.h>\n\nnamespace workerd {\n\nnamespace {\n// Wrapper around a Worker that handles receiving a new event from the outside. In particular,\n// this handles:\n// - Creating a IoContext and making it current.\n// - Executing the worker under lock.\n// - Catching exceptions and converting them to HTTP error responses.\n//   - Or, falling back to proxying if passThroughOnException() was used.\n// - Finish waitUntil() tasks.\nclass WorkerEntrypoint final: public WorkerInterface {\n public:\n  // Call this instead of the constructor. It actually adds a wrapper object around the\n  // `WorkerEntrypoint`, but the wrapper still implements `WorkerInterface`.\n  //\n  // WorkerEntrypoint will create a IoContext, and that IoContext may outlive the\n  // WorkerEntrypoint by means of a waitUntil() task. Any object(s) which must be kept alive to\n  // support the worker for the lifetime of the IoContext (e.g., subsequent pipeline stages)\n  // must be passed in via `ioContextDependency`.\n  //\n  // If this is NOT a zone worker, then `zoneDefaultWorkerLimits` should be a default instance of\n  // WorkerLimits::Reader. Hence this is not necessarily the same as\n  // topLevelRequest.getZoneDefaultWorkerLimits(), since the top level request may be shared between\n  // zone and non-zone workers.\n  static kj::Own<WorkerInterface> construct(ThreadContext& threadContext,\n      kj::Own<const Worker> worker,\n      kj::Maybe<kj::StringPtr> entrypointName,\n      Frankenvalue props,\n      kj::Maybe<kj::Own<Worker::Actor>> actor,\n      kj::Own<LimitEnforcer> limitEnforcer,\n      kj::Own<void> ioContextDependency,\n      kj::Own<IoChannelFactory> ioChannelFactory,\n      kj::Own<RequestObserver> metrics,\n      kj::TaskSet& waitUntilTasks,\n      bool tunnelExceptions,\n      kj::Maybe<kj::Own<BaseTracer>> workerTracer,\n      kj::Maybe<kj::String> cfBlobJson,\n      kj::Maybe<Worker::VersionInfo> versionInfo,\n      kj::Maybe<tracing::InvocationSpanContext> maybeTriggerInvocationSpan);\n\n  kj::Promise<void> request(kj::HttpMethod method,\n      kj::StringPtr url,\n      const kj::HttpHeaders& headers,\n      kj::AsyncInputStream& requestBody,\n      Response& response) override;\n  kj::Promise<void> connect(kj::StringPtr host,\n      const kj::HttpHeaders& headers,\n      kj::AsyncIoStream& connection,\n      ConnectResponse& response,\n      kj::HttpConnectSettings settings) override;\n  kj::Promise<void> prewarm(kj::StringPtr url) override;\n  kj::Promise<ScheduledResult> runScheduled(kj::Date scheduledTime, kj::StringPtr cron) override;\n  kj::Promise<AlarmResult> runAlarm(kj::Date scheduledTime, uint32_t retryCount) override;\n  kj::Promise<bool> test() override;\n  kj::Promise<CustomEvent::Result> customEvent(kj::Own<CustomEvent> event) override;\n\n private:\n  class ResponseSentTracker;\n\n  // Members initialized at startup.\n\n  ThreadContext& threadContext;\n  kj::TaskSet& waitUntilTasks;\n  kj::Maybe<kj::Own<IoContext::IncomingRequest>> incomingRequest;\n  bool tunnelExceptions;\n  kj::Maybe<kj::StringPtr> entrypointName;\n  Frankenvalue props;\n  kj::Maybe<kj::String> cfBlobJson;\n  kj::Maybe<Worker::VersionInfo> versionInfo;\n\n  // Hacky members used to hold some temporary state while processing a request.\n  // See gory details in WorkerEntrypoint::request().\n\n  kj::Maybe<kj::Promise<void>> proxyTask;\n  kj::Maybe<kj::Own<WorkerInterface>> failOpenService;\n  bool loggedExceptionEarlier = false;\n  kj::Maybe<jsg::Ref<api::AbortController>> abortController;\n\n  void init(kj::Own<const Worker> worker,\n      kj::Maybe<kj::Own<Worker::Actor>> actor,\n      kj::Own<LimitEnforcer> limitEnforcer,\n      kj::Own<void> ioContextDependency,\n      kj::Own<IoChannelFactory> ioChannelFactory,\n      kj::Own<RequestObserver> metrics,\n      kj::Maybe<kj::Own<BaseTracer>> workerTracer,\n      kj::Maybe<tracing::InvocationSpanContext> maybeTriggerInvocationSpan);\n\n  template <typename T>\n  kj::Promise<T> maybeAddGcPassForTest(IoContext& context, kj::Promise<T> promise);\n\n  kj::Promise<WorkerEntrypoint::AlarmResult> runAlarmImpl(\n      kj::Own<IoContext::IncomingRequest> incomingRequest,\n      kj::Date scheduledTime,\n      uint32_t retryCount);\n\n public:  // For kj::heap() only; pretend this is private.\n  WorkerEntrypoint(kj::Badge<WorkerEntrypoint> badge,\n      ThreadContext& threadContext,\n      kj::TaskSet& waitUntilTasks,\n      bool tunnelExceptions,\n      kj::Maybe<kj::StringPtr> entrypointName,\n      Frankenvalue props,\n      kj::Maybe<kj::String> cfBlobJson,\n      kj::Maybe<Worker::VersionInfo> versionInfo);\n};\n\n// Simple wrapper around `HttpService::Response` to let us know if the response was sent\n// already.\nclass WorkerEntrypoint::ResponseSentTracker final: public kj::HttpService::Response {\n public:\n  ResponseSentTracker(kj::HttpService::Response& inner): inner(inner) {}\n  KJ_DISALLOW_COPY_AND_MOVE(ResponseSentTracker);\n\n  bool isSent() const {\n    return sent;\n  }\n  uint getHttpResponseStatus() const {\n    return httpResponseStatus;\n  }\n\n  kj::Own<kj::AsyncOutputStream> send(uint statusCode,\n      kj::StringPtr statusText,\n      const kj::HttpHeaders& headers,\n      kj::Maybe<uint64_t> expectedBodySize = kj::none) override {\n    TRACE_EVENT(\n        \"workerd\", \"WorkerEntrypoint::ResponseSentTracker::send()\", \"statusCode\", statusCode);\n    sent = true;\n    httpResponseStatus = statusCode;\n    return inner.send(statusCode, statusText, headers, expectedBodySize);\n  }\n\n  kj::Own<kj::WebSocket> acceptWebSocket(const kj::HttpHeaders& headers) override {\n    TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::ResponseSentTracker::acceptWebSocket()\");\n    sent = true;\n    return inner.acceptWebSocket(headers);\n  }\n\n private:\n  uint httpResponseStatus = 0;\n  kj::HttpService::Response& inner;\n  bool sent = false;\n};\n\nkj::Own<WorkerInterface> WorkerEntrypoint::construct(ThreadContext& threadContext,\n    kj::Own<const Worker> worker,\n    kj::Maybe<kj::StringPtr> entrypointName,\n    Frankenvalue props,\n    kj::Maybe<kj::Own<Worker::Actor>> actor,\n    kj::Own<LimitEnforcer> limitEnforcer,\n    kj::Own<void> ioContextDependency,\n    kj::Own<IoChannelFactory> ioChannelFactory,\n    kj::Own<RequestObserver> metrics,\n    kj::TaskSet& waitUntilTasks,\n    bool tunnelExceptions,\n    kj::Maybe<kj::Own<BaseTracer>> workerTracer,\n    kj::Maybe<kj::String> cfBlobJson,\n    kj::Maybe<Worker::VersionInfo> versionInfo,\n    kj::Maybe<tracing::InvocationSpanContext> maybeTriggerInvocationSpan) {\n  TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::construct()\");\n\n  auto obj =\n      kj::heap<WorkerEntrypoint>(kj::Badge<WorkerEntrypoint>(), threadContext, waitUntilTasks,\n          tunnelExceptions, entrypointName, kj::mv(props), kj::mv(cfBlobJson), kj::mv(versionInfo));\n  obj->init(kj::mv(worker), kj::mv(actor), kj::mv(limitEnforcer), kj::mv(ioContextDependency),\n      kj::mv(ioChannelFactory), kj::addRef(*metrics), kj::mv(workerTracer),\n      kj::mv(maybeTriggerInvocationSpan));\n  auto& wrapper = metrics->wrapWorkerInterface(*obj);\n  return kj::attachRef(wrapper, kj::mv(obj), kj::mv(metrics));\n}\n\nWorkerEntrypoint::WorkerEntrypoint(kj::Badge<WorkerEntrypoint> badge,\n    ThreadContext& threadContext,\n    kj::TaskSet& waitUntilTasks,\n    bool tunnelExceptions,\n    kj::Maybe<kj::StringPtr> entrypointName,\n    Frankenvalue props,\n    kj::Maybe<kj::String> cfBlobJson,\n    kj::Maybe<Worker::VersionInfo> versionInfo)\n    : threadContext(threadContext),\n      waitUntilTasks(waitUntilTasks),\n      tunnelExceptions(tunnelExceptions),\n      entrypointName(entrypointName),\n      props(kj::mv(props)),\n      cfBlobJson(kj::mv(cfBlobJson)),\n      versionInfo(kj::mv(versionInfo)) {}\n\nvoid WorkerEntrypoint::init(kj::Own<const Worker> worker,\n    kj::Maybe<kj::Own<Worker::Actor>> actor,\n    kj::Own<LimitEnforcer> limitEnforcer,\n    kj::Own<void> ioContextDependency,\n    kj::Own<IoChannelFactory> ioChannelFactory,\n    kj::Own<RequestObserver> metrics,\n    kj::Maybe<kj::Own<BaseTracer>> workerTracer,\n    kj::Maybe<tracing::InvocationSpanContext> maybeTriggerInvocationSpan) {\n  TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::init()\");\n  // We need to construct the IoContext -- unless this is an actor and it already has a\n  // IoContext, in which case we reuse it.\n\n  auto newContext = [&]() {\n    TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::init() create new IoContext\");\n    auto actorRef = actor.map([](kj::Own<Worker::Actor>& ptr) -> Worker::Actor& { return *ptr; });\n\n    // Attaching to refcount instance is safe here since this instance stays alive for the lifetime\n    // of the associated WorkerInterface, other references may be created below for actors requests\n    // in separate init() calls but this ioContextDependency does not need to live as long as those\n    // instances.\n    return kj::refcounted<IoContext>(threadContext, kj::mv(worker), actorRef, kj::mv(limitEnforcer))\n        .attachToThisReference(kj::mv(ioContextDependency));\n  };\n\n  kj::Own<IoContext> context;\n  KJ_IF_SOME(a, actor) {\n    KJ_IF_SOME(rc, a.get()->getIoContext()) {\n      context = kj::addRef(rc);\n    } else {\n      context = newContext();\n      a.get()->setIoContext(kj::addRef(*context));\n    }\n  } else {\n    context = newContext();\n  }\n\n  incomingRequest = kj::heap<IoContext::IncomingRequest>(kj::mv(context), kj::mv(ioChannelFactory),\n      kj::mv(metrics), kj::mv(workerTracer), kj::mv(maybeTriggerInvocationSpan))\n                        .attach(kj::mv(actor));\n}\n\nkj::Promise<void> WorkerEntrypoint::request(kj::HttpMethod method,\n    kj::StringPtr url,\n    const kj::HttpHeaders& headers,\n    kj::AsyncInputStream& requestBody,\n    Response& response) {\n  TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::request()\", \"url\", url.cStr(),\n      PERFETTO_FLOW_FROM_POINTER(this));\n  auto incomingRequest =\n      kj::mv(KJ_REQUIRE_NONNULL(this->incomingRequest, \"request() can only be called once\"));\n  this->incomingRequest = kj::none;\n  auto& context = incomingRequest->getContext();\n\n  auto wrappedResponse = kj::heap<ResponseSentTracker>(response);\n\n  bool isActor = context.getActor() != kj::none;\n  // HACK: Capture workerTracer directly, it's unclear how to acquire the right tracer from context\n  // when we need it (for DOs, IoContext may point to a different WorkerTracer by the time we use\n  // it). The tracer lives as long or longer than the IoContext (based on being co-owned\n  // by IncomingRequest and PipelineTracer) so long enough.\n  kj::Maybe<BaseTracer&> workerTracer;\n\n  KJ_IF_SOME(t, incomingRequest->getWorkerTracer()) {\n    kj::String cfJson;\n    KJ_IF_SOME(c, cfBlobJson) {\n      cfJson = kj::str(c);\n    }\n\n    // To match our historical behavior (when we used to pull the headers from the JavaScript\n    // object later on), we need to canonicalize the headers, including:\n    // - Lower-case the header name.\n    // - Combine multiple headers with the same name into a comma-delimited list. (This explicitly\n    //   breaks the Set-Cookie header, incidentally, but should be equivalent for all other\n    //   headers.)\n    kj::TreeMap<kj::String, kj::Vector<kj::StringPtr>> traceHeaders;\n    headers.forEach([&](kj::StringPtr name, kj::StringPtr value) {\n      kj::String lower = toLower(name);\n      auto& slot = traceHeaders.findOrCreate(\n          lower, [&]() { return decltype(traceHeaders)::Entry{kj::mv(lower), {}}; });\n      slot.add(value);\n    });\n    auto traceHeadersArray = KJ_MAP(entry, traceHeaders) {\n      return tracing::FetchEventInfo::Header(kj::mv(entry.key), kj::strArray(entry.value, \", \"));\n    };\n\n    t.setEventInfo(*incomingRequest,\n        tracing::FetchEventInfo(method, kj::str(url), kj::mv(cfJson), kj::mv(traceHeadersArray)));\n    workerTracer = t;\n  }\n\n  incomingRequest->delivered();\n\n  auto metricsForCatch = kj::addRef(incomingRequest->getMetrics());\n  auto metricsForProxyTask = kj::addRef(incomingRequest->getMetrics());\n\n  TRACE_EVENT_BEGIN(\"workerd\", \"WorkerEntrypoint::request() waiting on context\",\n      PERFETTO_TRACK_FROM_POINTER(&context), PERFETTO_FLOW_FROM_POINTER(this));\n\n  return context\n      .run([this, &context, method, url, &headers, &requestBody,\n               &metrics = incomingRequest->getMetrics(), &wrappedResponse = *wrappedResponse,\n               entrypointName = entrypointName](Worker::Lock& lock) mutable {\n    TRACE_EVENT_END(\"workerd\", PERFETTO_TRACK_FROM_POINTER(&context));\n    TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::request() run\", PERFETTO_FLOW_FROM_POINTER(this));\n    jsg::AsyncContextFrame::StorageScope traceScope = context.makeAsyncTraceScope(lock);\n    auto featureFlags = FeatureFlags::get(lock);\n\n    kj::Maybe<jsg::Ref<api::AbortSignal>> signal;\n\n    if (featureFlags.getEnableRequestSignal()) {\n      auto abortSignalFlag = featureFlags.getRequestSignalPassthrough()\n          ? api::AbortSignal::Flag::NONE\n          : api::AbortSignal::Flag::IGNORE_FOR_SUBREQUESTS;\n      jsg::Lock& js = lock;\n      signal.emplace(abortController.emplace(js.alloc<api::AbortController>(js, abortSignalFlag))\n                         ->getSignal());\n    }\n\n    return lock.getGlobalScope().request(method, url, headers, requestBody, wrappedResponse,\n        cfBlobJson, lock,\n        lock.getExportedHandler(\n            entrypointName, kj::mv(versionInfo), kj::mv(props), context.getActor()),\n        kj::mv(signal));\n  })\n      .then([this, &context, &wrappedResponse = *wrappedResponse, workerTracer](\n                api::DeferredProxy<void> deferredProxy) {\n    TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::request() deferred proxy step\",\n        PERFETTO_FLOW_FROM_POINTER(this));\n    proxyTask = kj::mv(deferredProxy.proxyTask);\n    KJ_IF_SOME(t, workerTracer) {\n      auto httpResponseStatus = wrappedResponse.getHttpResponseStatus();\n      if (httpResponseStatus != 0) {\n        t.setReturn(context.now(), tracing::FetchResponseInfo(httpResponseStatus));\n      } else {\n        t.setReturn(context.now());\n      }\n    }\n  })\n      .catch_([this, &context](kj::Exception&& exception) mutable -> kj::Promise<void> {\n    TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::request() catch\", PERFETTO_FLOW_FROM_POINTER(this));\n    // Log JS exceptions to the JS console, if fiddle is attached. This also has the effect of\n    // logging internal errors to syslog.\n    loggedExceptionEarlier = true;\n    context.logUncaughtExceptionAsync(UncaughtExceptionSource::REQUEST_HANDLER, kj::cp(exception));\n\n    // Do not allow the exception to escape the isolate without waiting for the output gate to\n    // open. Note that in the success path, this is taken care of in `FetchEvent::respondWith()`.\n    return context.waitForOutputLocks().then(\n#ifdef WORKERD_USE_PERFETTO\n        [exception = kj::mv(exception),\n            flow = PERFETTO_TERMINATING_FLOW_FROM_POINTER(this)]() mutable -> kj::Promise<void> {\n      TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::request() after output lock wait\", flow);\n      return kj::mv(exception);\n    });\n#else\n        [exception = kj::mv(exception)]() mutable -> kj::Promise<void> {\n      return kj::mv(exception);\n    });\n#endif  // defined(WORKERD_USE_PERFETTO)\n  })\n      .attach(kj::defer([this, incomingRequest = kj::mv(incomingRequest), &context]() mutable {\n    // The request has been canceled, but allow it to continue executing in the background.\n    if (context.isFailOpen()) {\n      // Fail-open behavior has been chosen, we'd better save an interface that we can use for\n      // that purpose later.\n      failOpenService = context.getSubrequestChannelNoChecks(\n          IoContext::NEXT_CLIENT_CHANNEL, false, kj::mv(cfBlobJson));\n    }\n\n    if (proxyTask == kj::none && !loggedExceptionEarlier) {\n      // When the client disconnects, trigger an abort on request.signal, unless the request has\n      // already completed normally, or failed with an exception.\n\n      // TODO(perf): Don't add a task to trigger the abort unless we know it has at least one\n      // listener.\n      KJ_IF_SOME(ctrl, abortController) {\n        context.addWaitUntil(context.run([ctrl = ctrl.addRef()](Worker::Lock& lock) mutable {\n          ctrl->getSignal()->triggerAbort(\n              lock, JSG_KJ_EXCEPTION(DISCONNECTED, DOMAbortError, \"The client has disconnected\"));\n        }));\n      }\n    }\n\n    // Release reference to the AbortController.\n    // Either the waitUntilTask holds a reference to it, or it will never be triggered at all.\n    abortController = kj::none;\n\n    auto promise = incomingRequest->drain().attach(kj::mv(incomingRequest));\n    waitUntilTasks.add(maybeAddGcPassForTest(context, kj::mv(promise)));\n  }))\n      .then([this, metrics = kj::mv(metricsForProxyTask)]() mutable -> kj::Promise<void> {\n    TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::request() finish proxying\",\n        PERFETTO_TERMINATING_FLOW_FROM_POINTER(this));\n    // Now that the IoContext is dropped (unless it had waitUntil()s), we can finish proxying\n    // without pinning it or the isolate into memory.\n    KJ_IF_SOME(p, proxyTask) {\n      return p.catch_([metrics = kj::mv(metrics)](kj::Exception&& e) mutable -> kj::Promise<void> {\n        metrics->reportFailure(e, RequestObserver::FailureSource::DEFERRED_PROXY);\n        return kj::mv(e);\n      });\n    } else {\n      return kj::READY_NOW;\n    }\n  })\n      .attach(kj::defer([this]() mutable {\n    // If we're being cancelled, we need to make sure `proxyTask` gets canceled.\n    proxyTask = kj::none;\n  }))\n      .catch_([this, wrappedResponse = kj::mv(wrappedResponse), isActor, method, url, &headers,\n                  &requestBody, metrics = kj::mv(metricsForCatch),\n                  workerTracer](kj::Exception&& exception) mutable -> kj::Promise<void> {\n    // Don't return errors to end user.\n    TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::request() exception\",\n        PERFETTO_TERMINATING_FLOW_FROM_POINTER(this));\n\n    auto isInternalException = !jsg::isTunneledException(exception.getDescription()) &&\n        !jsg::isDoNotLogException(exception.getDescription());\n    if (!loggedExceptionEarlier) {\n      // This exception seems to have originated during the deferred proxy task, so it was not\n      // logged to the IoContext earlier.\n      if (exception.getType() != kj::Exception::Type::DISCONNECTED && isInternalException) {\n        LOG_EXCEPTION(\"workerEntrypoint\", exception);\n      } else {\n        KJ_LOG(INFO, exception);  // Run with --verbose to see exception logs.\n      }\n    }\n\n    auto exceptionToPropagate = [&]() {\n      if (isInternalException) {\n        // We've already logged it here, the only thing that matters to the client is that we failed\n        // due to an internal error. Note that this does not need to be labeled \"remote.\" since jsg\n        // will sanitize it as an internal error. Note that we use `setDescription()` to preserve\n        // the exception type for `jsg::exceptionToJs(...)` downstream.\n        exception.setDescription(\n            kj::str(\"worker_do_not_log; Request failed due to internal error\"));\n        return kj::mv(exception);\n      } else {\n        // We do not care how many remote capnp servers this went through since we are returning\n        // it to the worker via jsg.\n        // TODO(someday) We also do this stripping when making the tunneled exception for\n        // `jsg::isTunneledException(...)`. It would be lovely if we could simply store some type\n        // instead of `loggedExceptionEarlier`. It would save use some work.\n        auto description = jsg::stripRemoteExceptionPrefix(exception.getDescription());\n        if (!description.startsWith(\"remote.\")) {\n          // If we already were annotated as remote from some other worker entrypoint, no point\n          // adding an additional prefix.\n          exception.setDescription(kj::str(\"remote.\", description));\n        }\n        return kj::mv(exception);\n      }\n    };\n\n    if (wrappedResponse->isSent()) {\n      // We can't fail open if the response was already sent, so set `failOpenService` null so that\n      // that branch isn't taken below.\n      failOpenService = kj::none;\n    }\n\n    if (isActor) {\n      // We want to tunnel exceptions from actors back to the caller.\n      // TODO(cleanup): We'd really like to tunnel exceptions any time a worker is calling another\n      // worker, not just for actors (and W2W below), but getting that right will require cleaning\n      // up error handling more generally.\n      return exceptionToPropagate();\n    } else KJ_IF_SOME(service, failOpenService) {\n      // Fall back to origin.\n\n      // We're catching the exception, but metrics should still indicate an exception.\n      metrics->reportFailure(exception);\n\n      auto promise = kj::evalNow([&] {\n        auto promise = service.get()->request(method, url, headers, requestBody, *wrappedResponse);\n        metrics->setFailedOpen(true);\n        return promise.attach(kj::mv(service));\n      });\n      return promise.catch_([this, wrappedResponse = kj::mv(wrappedResponse), workerTracer,\n                                metrics = kj::mv(metrics)](kj::Exception&& e) mutable {\n        metrics->setFailedOpen(false);\n        if (e.getType() != kj::Exception::Type::DISCONNECTED &&\n            // Avoid logging recognized external errors here, such as invalid headers returned from\n            // the server.\n            !jsg::isTunneledException(e.getDescription()) &&\n            !jsg::isDoNotLogException(e.getDescription())) {\n          LOG_EXCEPTION(\"failOpenFallback\", e);\n        }\n        if (!wrappedResponse->isSent()) {\n          kj::HttpHeaders headers(threadContext.getHeaderTable());\n          wrappedResponse->send(500, \"Internal Server Error\", headers, static_cast<uint64_t>(0));\n          KJ_IF_SOME(t, workerTracer) {\n            t.setReturn(kj::none, tracing::FetchResponseInfo(500));\n          }\n        }\n      });\n    } else if (tunnelExceptions) {\n      // Like with the isActor check, we want to return exceptions back to the caller.\n      // We don't want to handle this case the same as the isActor case though, since we want\n      // fail-open to operate normally, which means this case must happen after fail-open handling.\n      return exceptionToPropagate();\n    } else {\n      // Return error.\n\n      // We're catching the exception and replacing it with 5xx, but metrics should still indicate\n      // an exception.\n      metrics->reportFailure(exception);\n\n      // We can't send an error response if a response was already started; we can only drop the\n      // connection in that case.\n      if (!wrappedResponse->isSent()) {\n        kj::HttpHeaders headers(threadContext.getHeaderTable());\n        if (exception.getType() == kj::Exception::Type::OVERLOADED) {\n          wrappedResponse->send(503, \"Service Unavailable\", headers, static_cast<uint64_t>(0));\n        } else {\n          wrappedResponse->send(500, \"Internal Server Error\", headers, static_cast<uint64_t>(0));\n        }\n        KJ_IF_SOME(t, workerTracer) {\n          t.setReturn(\n              kj::none, tracing::FetchResponseInfo(wrappedResponse->getHttpResponseStatus()));\n        }\n      }\n\n      return kj::READY_NOW;\n    }\n  });\n}\n\nkj::Promise<void> WorkerEntrypoint::connect(kj::StringPtr host,\n    const kj::HttpHeaders& headers,\n    kj::AsyncIoStream& connection,\n    ConnectResponse& response,\n    kj::HttpConnectSettings settings) {\n  TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::connect()\");\n  auto incomingRequest =\n      kj::mv(KJ_REQUIRE_NONNULL(this->incomingRequest, \"connect() can only be called once\"));\n  this->incomingRequest = kj::none;\n  // Whenever we implement incoming connections over the `connect` handler we need to remember to\n  // add tracing `onset` and `return` events using setEventInfo()/setReturn(), as with the other\n  // event types here.\n  incomingRequest->delivered();\n  auto& context = incomingRequest->getContext();\n\n  KJ_DEFER({\n    // Since we called incomingRequest->delivered, we are obliged to call `drain()`.\n    auto promise = incomingRequest->drain().attach(kj::mv(incomingRequest));\n    waitUntilTasks.add(maybeAddGcPassForTest(context, kj::mv(promise)));\n  });\n\n  if (context.getWorker().getIsolate().getApi().getFeatureFlags().getConnectPassThrough()) {\n    // connect_pass_through feature flag means we should just forward the connect request on to\n    // the global outbound.\n\n    auto next = context.getSubrequestChannelNoChecks(\n        IoContext::NEXT_CLIENT_CHANNEL, false, kj::mv(cfBlobJson));\n\n    // Note: Intentionally return without co_await so that the `incomingRequest` is destroyed,\n    //   because we don't have any need to keep the context around.\n    return next->connect(host, headers, connection, response, settings);\n  }\n\n  JSG_FAIL_REQUIRE(TypeError, \"Incoming CONNECT on a worker not supported\");\n}\n\nkj::Promise<void> WorkerEntrypoint::prewarm(kj::StringPtr url) {\n  // Nothing to do, the worker is already loaded.\n  TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::prewarm()\", \"url\", url.cStr());\n  auto incomingRequest =\n      kj::mv(KJ_REQUIRE_NONNULL(this->incomingRequest, \"prewarm() can only be called once\"));\n  incomingRequest->getMetrics().setIsPrewarm();\n\n  // Intentionally don't call incomingRequest->delivered() for prewarm requests and do not create\n  // an Onset event, prewarm is not being traced.\n\n  // TODO(someday): Ideally, middleware workers would forward prewarm() to the next stage. At\n  //   present we don't have a good way to decide what stage that is, especially given that we'll\n  //   be switching to `next` being a binding in the future.\n  return kj::READY_NOW;\n}\n\nkj::Promise<WorkerInterface::ScheduledResult> WorkerEntrypoint::runScheduled(\n    kj::Date scheduledTime, kj::StringPtr cron) {\n  TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::runScheduled()\");\n  auto incomingRequest =\n      kj::mv(KJ_REQUIRE_NONNULL(this->incomingRequest, \"runScheduled() can only be called once\"));\n  this->incomingRequest = kj::none;\n  auto& context = incomingRequest->getContext();\n\n  KJ_ASSERT(context.getActor() == kj::none);\n  // This code currently doesn't work with actors because cancellations occur immediately, without\n  // calling context->drain(). We don't ever send scheduled events to actors. If we do, we'll have\n  // to think more about this.\n\n  double eventTime = (scheduledTime - kj::UNIX_EPOCH) / kj::MILLISECONDS;\n\n  KJ_IF_SOME(t, incomingRequest->getWorkerTracer()) {\n    t.setEventInfo(*incomingRequest, tracing::ScheduledEventInfo(eventTime, kj::str(cron)));\n  }\n\n  incomingRequest->delivered();\n\n  // Scheduled handlers run entirely in waitUntil() tasks.\n  context.addWaitUntil(\n      context.run([scheduledTime, cron, entrypointName = entrypointName,\n                      versionInfo = kj::mv(versionInfo), props = kj::mv(props), &context,\n                      &metrics = incomingRequest->getMetrics()](Worker::Lock& lock) mutable {\n    TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::runScheduled() run\");\n    jsg::AsyncContextFrame::StorageScope traceScope = context.makeAsyncTraceScope(lock);\n\n    lock.getGlobalScope().startScheduled(scheduledTime, cron, lock,\n        lock.getExportedHandler(\n            entrypointName, kj::mv(versionInfo), kj::mv(props), context.getActor()));\n  }));\n\n  static auto constexpr waitForFinished = [](IoContext& context,\n                                              kj::Own<IoContext::IncomingRequest> request)\n      -> kj::Promise<WorkerInterface::ScheduledResult> {\n    TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::runScheduled() waitForFinished()\");\n    auto result = co_await request->finishScheduled();\n    bool completed = result == IoContext_IncomingRequest::FinishScheduledResult::COMPLETED;\n    co_return WorkerInterface::ScheduledResult{.retry = context.shouldRetryScheduled(),\n      .outcome = completed ? context.waitUntilStatus() : EventOutcome::EXCEEDED_CPU};\n  };\n\n  auto promise = waitForFinished(context, kj::mv(incomingRequest));\n\n  return maybeAddGcPassForTest(context, kj::mv(promise));\n}\n\nkj::Promise<WorkerInterface::AlarmResult> WorkerEntrypoint::runAlarmImpl(\n    kj::Own<IoContext::IncomingRequest> incomingRequest,\n    kj::Date scheduledTime,\n    uint32_t retryCount) {\n  // We want to de-duplicate alarm requests as follows:\n  // - An alarm must not be canceled once it is running, UNLESS the whole actor is shut down.\n  // - If multiple alarm invocations arrive with the same scheduled time, we only run one.\n  // - If we are asked to schedule an alarm while one is running, we wait for the running alarm to\n  //   finish.\n  // - However, we schedule no more than one alarm. If another one (with yet another different\n  //   scheduled time) arrives while we still have one running and one scheduled, we discard the\n  //   previous scheduled alarm.\n\n  TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::runAlarmImpl()\");\n\n  auto& context = incomingRequest->getContext();\n  auto& actor = KJ_REQUIRE_NONNULL(context.getActor(), \"alarm() should only work with actors\");\n\n  KJ_IF_SOME(promise, actor.getAlarm(scheduledTime)) {\n    // There is a pre-existing alarm for `scheduledTime`, we can just wait for its result.\n    // TODO(someday) If the request responsible for fulfilling this alarm were to be cancelled, then\n    // we could probably take over and try to fulfill it ourselves. Maybe we'd want to loop on\n    // `actor.getAlarm()`? We'd have to distinguish between rescheduling and request cancellation.\n    auto result = co_await promise;\n    co_return result;\n  }\n\n  // There isn't a pre-existing alarm, we can set event info and call `delivered()` (which emits\n  // metrics events).\n  KJ_IF_SOME(t, incomingRequest->getWorkerTracer()) {\n    t.setEventInfo(*incomingRequest, tracing::AlarmEventInfo(scheduledTime));\n  }\n\n  incomingRequest->delivered();\n\n  auto scheduleAlarmResult = co_await actor.scheduleAlarm(scheduledTime);\n  KJ_SWITCH_ONEOF(scheduleAlarmResult) {\n    KJ_CASE_ONEOF(af, WorkerInterface::AlarmFulfiller) {\n      // We're now in charge of running this alarm!\n      auto cancellationGuard = kj::defer([&af]() {\n        // Our promise chain was cancelled, let's cancel our fulfiller for any other requests\n        // that were waiting on us.\n        af.cancel();\n      });\n\n      KJ_DEFER({\n        // The alarm has finished but allow the request to continue executing in the background.\n        waitUntilTasks.add(incomingRequest->drain().attach(kj::mv(incomingRequest)));\n      });\n\n      try {\n        auto result =\n            co_await context.run([scheduledTime, retryCount, entrypointName = entrypointName,\n                                     versionInfo = kj::mv(versionInfo), props = kj::mv(props),\n                                     &context](Worker::Lock& lock) mutable {\n          jsg::AsyncContextFrame::StorageScope traceScope = context.makeAsyncTraceScope(lock);\n\n          // If we have an invalid timeout, set it to the default value of 15 minutes.\n          auto timeout = context.getLimitEnforcer().getAlarmLimit();\n          if (timeout == 0 * kj::MILLISECONDS) {\n            LOG_NOSENTRY(WARNING, \"Invalid alarm timeout value. Using 15 minutes\", timeout);\n            timeout = 15 * kj::MINUTES;\n          }\n\n          auto handler = lock.getExportedHandler(\n              entrypointName, kj::mv(versionInfo), kj::mv(props), context.getActor());\n          return lock.getGlobalScope().runAlarm(scheduledTime, timeout, retryCount, lock, handler);\n        });\n\n        // The alarm handler was successfully complete. We must guarantee this same alarm does not\n        // run again.\n        if (result.outcome == EventOutcome::OK) {\n          // When an alarm handler completes its execution, the alarm is marked ready for deletion in\n          // actor-cache. This alarm change will only be reflected in the alarmsXX table, once cache\n          // flushes and changes are written to CRDB.\n          // If there are any pending flushes, they are locked with the actor output gate until\n          // they complete. We should wait until the output gate locks are released.\n          // If we don't wait, it's possible for alarm manager to pull the wrong alarm value (the\n          // same alarm that just completed) from CRDB before these changes are actually made,\n          // rerunning it, when it shouldn't.\n          co_await actor.getOutputGate().wait(context.getCurrentTraceSpan());\n        }\n\n        // We succeeded, inform any other entrypoints that may be waiting upon us.\n        af.fulfill(result);\n        cancellationGuard.cancel();\n        co_return result;\n      } catch (const kj::Exception& e) {\n        // We failed, inform any other entrypoints that may be waiting upon us.\n        af.reject(e);\n        cancellationGuard.cancel();\n        throw;\n      }\n    }\n    KJ_CASE_ONEOF(result, WorkerInterface::AlarmResult) {\n      // The alarm was cancelled while we were waiting to run, go ahead and return the result.\n      co_return result;\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\nkj::Promise<WorkerInterface::AlarmResult> WorkerEntrypoint::runAlarm(\n    kj::Date scheduledTime, uint32_t retryCount) {\n  TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::runAlarm()\");\n  auto incomingRequest =\n      kj::mv(KJ_REQUIRE_NONNULL(this->incomingRequest, \"runAlarm() can only be called once\"));\n  this->incomingRequest = kj::none;\n\n  auto& context = incomingRequest->getContext();\n  auto promise = runAlarmImpl(kj::mv(incomingRequest), scheduledTime, retryCount);\n  auto result = co_await maybeAddGcPassForTest(context, kj::mv(promise));\n  KJ_IF_SOME(t, context.getWorkerTracer()) {\n    t.setReturn(context.now());\n  }\n  co_return result;\n}\n\nkj::Promise<bool> WorkerEntrypoint::test() {\n  TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::test()\");\n  auto incomingRequest =\n      kj::mv(KJ_REQUIRE_NONNULL(this->incomingRequest, \"test() can only be called once\"));\n  this->incomingRequest = kj::none;\n  auto& context = incomingRequest->getContext();\n  KJ_IF_SOME(t, incomingRequest->getWorkerTracer()) {\n    t.setEventInfo(*incomingRequest, tracing::CustomEventInfo());\n  }\n\n  incomingRequest->delivered();\n\n  context.addWaitUntil(\n      context.run([entrypointName = entrypointName, versionInfo = kj::mv(versionInfo),\n                      props = kj::mv(props), &context, &metrics = incomingRequest->getMetrics()](\n                      Worker::Lock& lock) mutable -> kj::Promise<void> {\n    TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::test() run\");\n    jsg::AsyncContextFrame::StorageScope traceScope = context.makeAsyncTraceScope(lock);\n\n    return context.awaitJs(lock,\n        lock.getGlobalScope().test(lock,\n            lock.getExportedHandler(\n                entrypointName, kj::mv(versionInfo), kj::mv(props), context.getActor())));\n  }));\n\n  static auto constexpr waitForFinished =\n      [](IoContext& context, kj::Own<IoContext::IncomingRequest> request) -> kj::Promise<bool> {\n    TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::test() waitForFinished()\");\n    auto result = co_await request->finishScheduled();\n\n    if (result == IoContext_IncomingRequest::FinishScheduledResult::ABORTED) {\n      // If the test handler throws an exception (without aborting - just a regular exception),\n      // then `outcome` ends up being EventOutcome::EXCEPTION, which causes us to return false.\n      // But in that case we are separately relying on the exception being logged as an uncaught\n      // exception, rather than throwing it.\n      // This is why we don't rethrow the exception but rather log it as an uncaught exception.\n      try {\n        co_await context.onAbort();\n      } catch (...) {\n        auto exception = kj::getCaughtExceptionAsKj();\n        KJ_LOG(ERROR, exception);\n      }\n    }\n\n    // Not adding a return event here – we only provide rudimentary tracing support for test events\n    // (enough so that we can get logs/spans from them in wd-tests), so this is not needed in\n    // practice.\n\n    bool completed = result == IoContext_IncomingRequest::FinishScheduledResult::COMPLETED;\n    auto outcome = completed ? context.waitUntilStatus() : EventOutcome::EXCEEDED_CPU;\n    co_return outcome == EventOutcome::OK;\n  };\n\n  return maybeAddGcPassForTest(context, waitForFinished(context, kj::mv(incomingRequest)));\n}\n\nkj::Promise<WorkerInterface::CustomEvent::Result> WorkerEntrypoint::customEvent(\n    kj::Own<CustomEvent> event) {\n  TRACE_EVENT(\"workerd\", \"WorkerEntrypoint::customEvent()\", \"type\", event->getType());\n  auto incomingRequest =\n      kj::mv(KJ_REQUIRE_NONNULL(this->incomingRequest, \"customEvent() can only be called once\"));\n  this->incomingRequest = kj::none;\n\n  auto& context = incomingRequest->getContext();\n\n  // Set event info BEFORE calling run() to ensure onset event is reported before\n  // any user code executes (particularly important for actors whose constructors may run\n  // during delivered()).\n  KJ_IF_SOME(t, incomingRequest->getWorkerTracer()) {\n    t.setEventInfo(*incomingRequest, event->getEventInfo());\n  }\n\n  auto promise = event\n                     ->run(kj::mv(incomingRequest), entrypointName, kj::mv(versionInfo),\n                         kj::mv(props), waitUntilTasks)\n                     .attach(kj::mv(event));\n\n  // TODO(cleanup): In theory `context` may have been destroyed by now if `event->run()` dropped\n  //   the `incomingRequest` synchronously. No current implementation does that, and\n  //   maybeAddGcPassForTest() is a no-op outside of tests, so I'm ignoring the theoretical problem\n  //   for now. Otherwise we will need to `atomicAddRef()` the `Worker` at some point earlier on\n  //   but I'd like to avoid that in the non-test case.\n  return maybeAddGcPassForTest(context, kj::mv(promise));\n}\n\n#ifdef KJ_DEBUG\nvoid requestGc(const Worker& worker) {\n  TRACE_EVENT(\"workerd\", \"Debug: requestGc()\");\n  jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {\n    auto& isolate = worker.getIsolate();\n    auto lock = isolate.getApi().lock(stackScope);\n    lock->requestGcForTesting();\n  });\n}\n\ntemplate <typename T>\nkj::Promise<T> addGcPassForTest(IoContext& context, kj::Promise<T> promise) {\n  TRACE_EVENT(\"workerd\", \"Debug: addGcPassForTest\");\n  auto worker = kj::atomicAddRef(context.getWorker());\n  if constexpr (kj::isSameType<T, void>()) {\n    co_await promise;\n    requestGc(*worker);\n  } else {\n    auto ret = co_await promise;\n    requestGc(*worker);\n    co_return kj::mv(ret);\n  }\n}\n#endif\n\ntemplate <typename T>\nkj::Promise<T> WorkerEntrypoint::maybeAddGcPassForTest(IoContext& context, kj::Promise<T> promise) {\n#ifdef KJ_DEBUG\n  if (isPredictableModeForTest()) {\n    return addGcPassForTest(context, kj::mv(promise));\n  }\n#endif\n  return kj::mv(promise);\n}\n\n}  // namespace\n\nkj::Own<WorkerInterface> newWorkerEntrypoint(ThreadContext& threadContext,\n    kj::Own<const Worker> worker,\n    kj::Maybe<kj::StringPtr> entrypointName,\n    Frankenvalue props,\n    kj::Maybe<kj::Own<Worker::Actor>> actor,\n    kj::Own<LimitEnforcer> limitEnforcer,\n    kj::Own<void> ioContextDependency,\n    kj::Own<IoChannelFactory> ioChannelFactory,\n    kj::Own<RequestObserver> metrics,\n    kj::TaskSet& waitUntilTasks,\n    bool tunnelExceptions,\n    kj::Maybe<kj::Own<BaseTracer>> workerTracer,\n    kj::Maybe<kj::String> cfBlobJson,\n    kj::Maybe<Worker::VersionInfo> versionInfo,\n    kj::Maybe<tracing::InvocationSpanContext> maybeTriggerInvocationSpan) {\n  return WorkerEntrypoint::construct(threadContext, kj::mv(worker), kj::mv(entrypointName),\n      kj::mv(props), kj::mv(actor), kj::mv(limitEnforcer), kj::mv(ioContextDependency),\n      kj::mv(ioChannelFactory), kj::mv(metrics), waitUntilTasks, tunnelExceptions,\n      kj::mv(workerTracer), kj::mv(cfBlobJson), kj::mv(versionInfo),\n      kj::mv(maybeTriggerInvocationSpan));\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/worker-entrypoint.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/io/frankenvalue.h>\n#include <workerd/io/worker.h>\n\nnamespace workerd {\n\nclass IoChannelFactory;\nclass LimitEnforcer;\nclass RequestObserver;\nclass ThreadContext;\nclass WorkerInterface;\nclass BaseTracer;\n\nnamespace tracing {\nclass InvocationSpanContext;\n};\n\n// Create and return a wrapper around a Worker that handles receiving a new event\n// from the outside. In particular,\n// this handles:\n// - Creating a IoContext and making it current.\n// - Executing the worker under lock.\n// - Catching exceptions and converting them to HTTP error responses.\n//   - Or, falling back to proxying if passThroughOnException() was used.\n// - Finish waitUntil() tasks.\nkj::Own<WorkerInterface> newWorkerEntrypoint(ThreadContext& threadContext,\n    kj::Own<const Worker> worker,\n    kj::Maybe<kj::StringPtr> entrypointName,\n    Frankenvalue props,\n    kj::Maybe<kj::Own<Worker::Actor>> actor,\n    kj::Own<LimitEnforcer> limitEnforcer,\n    kj::Own<void> ioContextDependency,\n    kj::Own<IoChannelFactory> ioChannelFactory,\n    kj::Own<RequestObserver> metrics,\n    kj::TaskSet& waitUntilTasks,\n    bool tunnelExceptions,\n    kj::Maybe<kj::Own<BaseTracer>> workerTracer,\n    kj::Maybe<kj::String> cfBlobJson,\n    kj::Maybe<Worker::VersionInfo> versionInfo,\n    // The trigger invocation span may be propagated from other request. If it is provided,\n    // the implication is that this worker entrypoint is being created as a subrequest or\n    // subtask of another request. If it is kj::none, then this invocation is a top-level\n    // invocation.\n    kj::Maybe<tracing::InvocationSpanContext> maybeTriggerInvocationSpan = kj::none);\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/worker-fs-test.c++",
    "content": "#include \"worker-fs.h\"\n\n#include <kj/debug.h>\n#include <kj/test.h>\n\nnamespace workerd {\nnamespace {\n\nkj::Own<FsMap> createTestFsMap() {\n  auto map = kj::heap<FsMap>();\n  map->setBundleRoot(\"/mything/bundle\");\n  map->setTempRoot(\"/mything/temp\");\n  return kj::mv(map);\n}\n\nKJ_TEST(\"FsMap\") {\n  auto fsMap = createTestFsMap();\n\n  // Check that the paths are correct.\n  KJ_EXPECT(fsMap->getBundlePath().toString(false) == \"mything/bundle\");\n  KJ_EXPECT(fsMap->getTempPath().toString(false) == \"mything/temp\");\n  KJ_EXPECT(fsMap->getBundleRoot().equal(\"file:///mything/bundle\"_url));\n  KJ_EXPECT(fsMap->getTempRoot().equal(\"file:///mything/temp\"_url));\n}\n\nKJ_TEST(\"TmpDirStoreScope\") {\n  // We can create multiple temp storages on the heap...\n  auto tmpStoreOnHeap = TmpDirStoreScope::create();\n  auto tmpStoreOnHeap2 = TmpDirStoreScope::create();\n\n  KJ_EXPECT(!TmpDirStoreScope::hasCurrent());\n\n  {\n    // But we can only have one on the stack at a time per thread.\n    TmpDirStoreScope tmpDirStoreScope;\n    KJ_EXPECT(TmpDirStoreScope::hasCurrent());\n    KJ_ASSERT(&TmpDirStoreScope::current() == &tmpDirStoreScope);\n    KJ_ASSERT(&TmpDirStoreScope::current() != tmpStoreOnHeap.get());\n    KJ_ASSERT(&TmpDirStoreScope::current() != tmpStoreOnHeap2.get());\n  }\n  KJ_EXPECT(!TmpDirStoreScope::hasCurrent());\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/worker-fs.c++",
    "content": "#include \"worker-fs.h\"\n\n#include <workerd/io/io-context.h>\n#include <workerd/io/tracer.h>\n#include <workerd/util/uuid.h>\n#include <workerd/util/weak-refs.h>\n\n#include <algorithm>\n\nnamespace workerd {\n\n#define DEFINE_DEFAULTS_FOR_ROOTS(name, path)                                                      \\\n  const jsg::Url FsMap::kDefault##name##Path = path##_url;\nKNOWN_VFS_ROOTS(DEFINE_DEFAULTS_FOR_ROOTS)\n#undef DEFINE_DEFAULTS_FOR_ROOTS\n\n// Helper function to get the current working directory path.\n// Returns the Cwd if available, otherwise returns an empty path (root).\nkj::Maybe<kj::PathPtr> getCurrentWorkingDirectory() {\n  KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n    return ioContext.getTmpDirStoreScope().getCwd();\n  }\n  if (TmpDirStoreScope::hasCurrent()) {\n    return TmpDirStoreScope::current().getCwd();\n  }\n  return kj::none;\n}\n\n// Helper function to set the current working directory path.\n// Returns false if no context is available.\nbool setCurrentWorkingDirectory(kj::Path newCwd) {\n  KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n    ioContext.getTmpDirStoreScope().setCwd(kj::mv(newCwd));\n    return true;\n  } else if (TmpDirStoreScope::hasCurrent()) {\n    TmpDirStoreScope::current().setCwd(kj::mv(newCwd));\n    return true;\n  }\n  return false;\n}\n\nnamespace {\n// The SymbolicLinkRecursionGuardScope is used on-stack to guard against\n// circular symbolic links. As soon as a cycle is detected, it throws.\n// Since resolution is always synchronous, we can use thread-local to\n// track the current scope, allowing multiple scopes to be in the stack\n// without needed to pass the guard around or do any other bookkeeping.\nthread_local SymbolicLinkRecursionGuardScope* symbolicLinkGuard = nullptr;\n\n// Thread-local storage to track the current temp directory storage scope\n// on the stack.\nstatic thread_local TmpDirStoreScope* tmpDirStorageScope = nullptr;\n\n// The TmpDirectory is a special directory implementation that uses the\n// current TmpDirStoreScope to actually store the directory contents. The\n// current TmpDirStoreScope can either be set on the stack or via the current\n// IoContext. What this means is that every IoContext has it's own temporary\n// directory that is deleted when the IoContext is destructed. This allows\n// allows for top-level evaluations running outside of the IoContext to have\n// their own temporary directory space should we decide that temp files at\n// the global scope are useful.\nclass TmpDirectory final: public Directory {\n public:\n  kj::Maybe<kj::OneOf<FsError, Stat>> stat(jsg::Lock& js, kj::PathPtr ptr) override {\n    KJ_IF_SOME(dir, tryGetDirectory()) {\n      return kj::Maybe<kj::OneOf<FsError, Stat>>(dir->stat(js, ptr));\n    }\n    if (ptr.size() == 0) {\n      return kj::Maybe<kj::OneOf<FsError, Stat>>(Stat{\n        .type = FsType::DIRECTORY,\n        .size = 0,\n        .lastModified = kj::UNIX_EPOCH,\n        .writable = true,\n      });\n    }\n    return kj::none;\n  }\n\n  size_t count(jsg::Lock& js, kj::Maybe<FsType> typeFilter = kj::none) override {\n    KJ_IF_SOME(dir, tryGetDirectory()) {\n      return dir->count(js, typeFilter);\n    }\n    return 0;\n  }\n\n  Entry* begin() override {\n    KJ_IF_SOME(dir, tryGetDirectory()) {\n      return dir->begin();\n    }\n    return nullptr;\n  }\n\n  Entry* end() override {\n    KJ_IF_SOME(dir, tryGetDirectory()) {\n      return dir->end();\n    }\n    return nullptr;\n  }\n\n  const Entry* begin() const override {\n    KJ_IF_SOME(dir, tryGetDirectory()) {\n      return dir->begin();\n    }\n    return nullptr;\n  }\n\n  const Entry* end() const override {\n    KJ_IF_SOME(dir, tryGetDirectory()) {\n      return dir->end();\n    }\n    return nullptr;\n  }\n\n  kj::Maybe<FsNodeWithError> tryOpen(\n      jsg::Lock& js, kj::PathPtr path, OpenOptions options = {}) override {\n    KJ_IF_SOME(dir, tryGetDirectory()) {\n      return dir->tryOpen(js, path, kj::mv(options));\n    }\n    return kj::none;\n  }\n\n  kj::Maybe<FsError> add(jsg::Lock& js, kj::StringPtr name, Item entry) override {\n    KJ_IF_SOME(dir, tryGetDirectory()) {\n      return dir->add(js, name, kj::mv(entry));\n    }\n    return FsError::NOT_PERMITTED;\n  }\n\n  kj::OneOf<FsError, bool> remove(\n      jsg::Lock& js, kj::PathPtr path, RemoveOptions options = {}) override {\n    KJ_IF_SOME(dir, tryGetDirectory()) {\n      return dir->remove(js, path, kj::mv(options));\n    }\n    return false;\n  }\n\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"TmpDirectory\"_kj;\n  }\n\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(TmpDirectory);\n  }\n\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {\n    // The memory contents of this directory are not tracked. This is\n    // because they are entirely dependent on the current storage scope\n    // or IoContext. However, it is not likely that jsgGetMemoryInfo\n    // will be called when either of these are current.\n  }\n\n  kj::StringPtr getUniqueId(jsg::Lock&) const override {\n    KJ_IF_SOME(id, maybeUniqueId) {\n      return id;\n    }\n    // Generating a UUID requires randomness, which requires an IoContext.\n    auto& ioContext = JSG_REQUIRE_NONNULL(\n        IoContext::tryCurrent(), Error, \"Cannot generate a unique ID outside of a request\");\n    maybeUniqueId = workerd::randomUUID(ioContext.getEntropySource());\n    return KJ_ASSERT_NONNULL(maybeUniqueId);\n  }\n\n private:\n  mutable kj::Maybe<kj::String> maybeUniqueId;\n\n  kj::Maybe<kj::Rc<Directory>> tryGetDirectory() const {\n    KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n      return ioContext.getTmpDirStoreScope().getDirectory();\n    }\n    if (TmpDirStoreScope::hasCurrent()) {\n      return TmpDirStoreScope::current().getDirectory();\n    }\n    return kj::none;\n  }\n};\n\n// LazyDirectory is a directory that is lazily loaded on first access.\n// It is used, for example, by bundle-fs to load the bundle directory\n// from the worker configuration lazily when the directory is first\n// accessed in order to avoid the cost of loading the entire bundle if\n// the worker never actually accesses it.\nclass LazyDirectory final: public Directory {\n public:\n  LazyDirectory(kj::Function<kj::Rc<Directory>()> func): lazyDir(kj::mv(func)) {}\n\n  kj::Maybe<kj::OneOf<FsError, Stat>> stat(jsg::Lock& js, kj::PathPtr ptr) override {\n    return getDirectory()->stat(js, ptr);\n  }\n\n  size_t count(jsg::Lock& js, kj::Maybe<FsType> typeFilter = kj::none) override {\n    return getDirectory()->count(js, typeFilter);\n  }\n\n  Entry* begin() override {\n    return getDirectory()->begin();\n  }\n\n  Entry* end() override {\n    return getDirectory()->end();\n  }\n\n  const Entry* begin() const override {\n    return getDirectory()->begin();\n  }\n\n  const Entry* end() const override {\n    return getDirectory()->end();\n  }\n\n  kj::Maybe<FsNodeWithError> tryOpen(\n      jsg::Lock& js, kj::PathPtr path, OpenOptions options = {}) override {\n    return getDirectory()->tryOpen(js, path, kj::mv(options));\n  }\n\n  kj::Maybe<FsError> add(jsg::Lock& js, kj::StringPtr name, Item item) override {\n    return getDirectory()->add(js, name, kj::mv(item));\n  }\n\n  kj::OneOf<FsError, bool> remove(\n      jsg::Lock& js, kj::PathPtr path, RemoveOptions options = {}) override {\n    return getDirectory()->remove(js, path, kj::mv(options));\n  }\n\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"LazyDirectory\"_kj;\n  }\n\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(LazyDirectory);\n  }\n\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {\n    // We will only track the contents of this directory if it has\n    // be lazily loaded.\n    KJ_SWITCH_ONEOF(lazyDir) {\n      KJ_CASE_ONEOF(dir, kj::Rc<Directory>) {\n        dir->jsgGetMemoryInfo(tracker);\n        return;\n      }\n      KJ_CASE_ONEOF(func, kj::Function<kj::Rc<Directory>()>) {\n        // We don't know the contents of this directory  If it has not, then the contents are yet.\n        // Do not track.\n        return;\n      }\n    }\n  }\n\n  kj::StringPtr getUniqueId(jsg::Lock& js) const override {\n    return getDirectory()->getUniqueId(js);\n  }\n\n private:\n  mutable kj::OneOf<kj::Rc<Directory>, kj::Function<kj::Rc<Directory>()>> lazyDir;\n\n  kj::Rc<Directory> getDirectory() const {\n    KJ_SWITCH_ONEOF(lazyDir) {\n      KJ_CASE_ONEOF(dir, kj::Rc<Directory>) {\n        return dir.addRef();\n      }\n      KJ_CASE_ONEOF(func, kj::Function<kj::Rc<Directory>()>) {\n        auto dir = func();\n        auto ret = dir.addRef();\n        lazyDir = kj::mv(dir);\n        return kj::mv(ret);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n};\n\n// Validates that the given path does not contain any path separators and\n// can be parsed as a single path element. Throws if the checks fail.\nbool validatePathWithNoSeparators(kj::StringPtr path) {\n  try {\n    auto parsed = kj::Path::parse(path);\n    return parsed.size() == 1;\n  } catch (kj::Exception& e) {\n    return false;\n  }\n}\n\n// The primary implementation of the Directory interface.\ntemplate <bool Writable = false>\nclass DirectoryBase final: public Directory {\n public:\n  DirectoryBase() = default;\n\n  DirectoryBase(kj::HashMap<kj::String, Item> entries): entries(kj::mv(entries)) {\n    // When this constructor is used, we assume that the directory is read-only.\n    KJ_DASSERT(!Writable);\n  }\n\n  kj::Maybe<kj::OneOf<FsError, Stat>> stat(jsg::Lock& js, kj::PathPtr ptr) override {\n    // When the path ptr size is 0, then we're looking for the stat of this directory.\n    if (ptr.size() == 0) {\n      return kj::Maybe<kj::OneOf<FsError, Stat>>(Stat{\n        .type = FsType::DIRECTORY,\n        .size = 0,\n        .lastModified = kj::UNIX_EPOCH,\n        .writable = Writable,\n      });\n    }\n\n    // Otherwise, we need to look up the entry...\n    KJ_IF_SOME(found, entries.find(ptr[0])) {\n      KJ_SWITCH_ONEOF(found) {\n        KJ_CASE_ONEOF(file, kj::Rc<File>) {\n          // We found a file. If the remaining path is empty, yay! Return the stat.\n          if (ptr.size() == 1) {\n            return kj::Maybe<kj::OneOf<FsError, Stat>>(file->stat(js));\n          }\n          // Otherwise we'll fall through to return kj::none\n        }\n        KJ_CASE_ONEOF(dir, kj::Rc<Directory>) {\n          // We found a directory. We can just ask it for the stat. If the path\n          // ends up being empty, then that directory will return it's own stat.\n          return dir->stat(js, ptr.slice(1, ptr.size()));\n        }\n        KJ_CASE_ONEOF(link, kj::Rc<SymbolicLink>) {\n          // We found a symbolic link. We will resolve it and, if it resolves\n          // to something, we will ask it for the stat. Otherwise we return\n          // kj::none.\n          SymbolicLinkRecursionGuardScope guardScope;\n          KJ_IF_SOME(err, guardScope.checkSeen(link.get())) {\n            return kj::Maybe<kj::OneOf<FsError, Stat>>(err);\n          }\n          KJ_IF_SOME(resolved, link->resolve(js)) {\n            KJ_SWITCH_ONEOF(resolved) {\n              KJ_CASE_ONEOF(file, kj::Rc<File>) {\n                return kj::Maybe<kj::OneOf<FsError, Stat>>(file->stat(js));\n              }\n              KJ_CASE_ONEOF(dir, kj::Rc<Directory>) {\n                return dir->stat(js, ptr.slice(1, ptr.size()));\n              }\n              KJ_CASE_ONEOF(err, FsError) {\n                return kj::Maybe<kj::OneOf<FsError, Stat>>(err);\n              }\n            }\n            KJ_UNREACHABLE;\n          }\n        }\n      }\n    }\n    return kj::none;\n  }\n\n  size_t count(jsg::Lock& js, kj::Maybe<FsType> typeFilter = kj::none) override {\n    KJ_IF_SOME(type, typeFilter) {\n      return std::count_if(begin(), end(), [type](const auto& entry) {\n        KJ_SWITCH_ONEOF(entry.value) {\n          KJ_CASE_ONEOF(file, kj::Rc<File>) {\n            return type == FsType::FILE;\n          }\n          KJ_CASE_ONEOF(dir, kj::Rc<Directory>) {\n            return type == FsType::DIRECTORY;\n          }\n          KJ_CASE_ONEOF(link, kj::Rc<SymbolicLink>) {\n            return type == FsType::SYMLINK;\n          }\n        }\n        KJ_UNREACHABLE;\n      });\n    }\n    return entries.size();\n  }\n\n  Entry* begin() override {\n    return entries.begin();\n  }\n\n  Entry* end() override {\n    return entries.end();\n  }\n\n  const Entry* begin() const override {\n    return entries.begin();\n  }\n\n  const Entry* end() const override {\n    return entries.end();\n  }\n\n  kj::Maybe<FsNodeWithError> tryOpen(\n      jsg::Lock& js, kj::PathPtr path, OpenOptions opts = {}) override {\n    if (path.size() == 0) {\n      // An empty path ends up just returning this directory.\n      return kj::Maybe<FsNodeWithError>(kj::Rc<Directory>(addRefToThis()));\n    }\n\n    KJ_IF_SOME(found, entries.find(path[0])) {\n      if (path.size() == 1) {\n        // We found the entry, return it.\n        KJ_SWITCH_ONEOF(found) {\n          KJ_CASE_ONEOF(file, kj::Rc<File>) {\n            return kj::Maybe<FsNodeWithError>(file.addRef());\n          }\n          KJ_CASE_ONEOF(dir, kj::Rc<Directory>) {\n            return kj::Maybe<FsNodeWithError>(dir.addRef());\n          }\n          KJ_CASE_ONEOF(link, kj::Rc<SymbolicLink>) {\n            if (!opts.followLinks) {\n              // If we're not following links, when we just return the link itself\n              // here.\n              return kj::Maybe<FsNodeWithError>(link.addRef());\n            }\n            // Resolve the symbolic link and return the target, guarding against\n            // recursion while doing so.\n            SymbolicLinkRecursionGuardScope guardScope;\n            KJ_IF_SOME(err, guardScope.checkSeen(link.get())) {\n              return kj::Maybe<FsNodeWithError>(err);\n            }\n            return link->resolve(js);\n          }\n        }\n        KJ_UNREACHABLE;\n      }\n\n      // There's more than one component in the path, we need to keep looking.\n      path = path.slice(1, path.size());\n      KJ_SWITCH_ONEOF(found) {\n        KJ_CASE_ONEOF(file, kj::Rc<File>) {\n          // We found a file, but we were looking for a directory.\n          return kj::none;\n        }\n        KJ_CASE_ONEOF(dir, kj::Rc<Directory>) {\n          // We found a directory, continue searching.\n          return dir->tryOpen(js, path, kj::mv(opts));\n        }\n        KJ_CASE_ONEOF(link, kj::Rc<SymbolicLink>) {\n          // If the symbolic link resolves to a directory, then we can continue\n          // searching, otherwise we return nothing.\n          // Unless we're being asked to not follow links, then just return kj::none.\n          if (!opts.followLinks) {\n            return kj::none;\n          }\n          SymbolicLinkRecursionGuardScope guardScope;\n          KJ_IF_SOME(err, guardScope.checkSeen(link.get())) {\n            return kj::Maybe<FsNodeWithError>(err);\n          }\n          KJ_IF_SOME(resolved, link->resolve(js)) {\n            KJ_SWITCH_ONEOF(resolved) {\n              KJ_CASE_ONEOF(file, kj::Rc<File>) {\n                return kj::none;\n              }\n              KJ_CASE_ONEOF(dir, kj::Rc<Directory>) {\n                return dir->tryOpen(js, path, kj::mv(opts));\n              }\n              KJ_CASE_ONEOF(err, FsError) {\n                return kj::Maybe<FsNodeWithError>(err);\n              }\n            }\n          } else {\n            // The symbolic link does not resolve to anything.\n            return kj::none;\n          }\n        }\n      }\n    }\n\n    // If we haven't found anything, we can try to create a new file or directory\n    // if the directory is writable and the createAs parameter is set.\n    if constexpr (Writable) {\n      KJ_IF_SOME(type, opts.createAs) {\n        return tryCreate(js, path, type);\n      }\n    } else {\n      if (opts.createAs != kj::none) {\n        return kj::Maybe<FsNodeWithError>(FsError::READ_ONLY);\n      }\n    }\n\n    return kj::none;\n  }\n\n  kj::Maybe<FsError> add(jsg::Lock& js, kj::StringPtr name, Item fileOrDirectory) override {\n    if constexpr (Writable) {\n      if (!validatePathWithNoSeparators(name)) {\n        return FsError::INVALID_PATH;\n      }\n      if (entries.find(name) != kj::none) {\n        return FsError::ALREADY_EXISTS;\n      }\n      auto ret = ([&]() -> Item {\n        KJ_SWITCH_ONEOF(fileOrDirectory) {\n          KJ_CASE_ONEOF(file, kj::Rc<File>) {\n            return file.addRef();\n          }\n          KJ_CASE_ONEOF(dir, kj::Rc<Directory>) {\n            return dir.addRef();\n          }\n          KJ_CASE_ONEOF(link, kj::Rc<SymbolicLink>) {\n            return link.addRef();\n          }\n        }\n        KJ_UNREACHABLE;\n      })();\n      entries.insert(kj::str(name), kj::mv(ret));\n      return kj::none;\n    } else {\n      return FsError::NOT_PERMITTED;\n    }\n  }\n\n  // Tries to remove the file or directory at the given path. If the node does\n  // not exist, false will be returned. If the node is a directory and the recursive\n  // option is not set, an exception will be thrown if the directory is not empty.\n  // If the directory is read only, an exception will be thrown.\n  // If the node is a file, it will be removed regardless of the recursive option\n  // if the directory is not read only.\n  // The path must be relative to the current directory.\n  kj::OneOf<FsError, bool> remove(\n      jsg::Lock& js, kj::PathPtr path, RemoveOptions opts = {}) override {\n    if constexpr (Writable) {\n      if (path.size() == 0) return false;\n      KJ_IF_SOME(found, entries.find(path[0])) {\n        KJ_SWITCH_ONEOF(found) {\n          KJ_CASE_ONEOF(file, kj::Rc<File>) {\n            if (path.size() != 1) return FsError::NOT_DIRECTORY;\n            return entries.erase(path[0]);\n          }\n          KJ_CASE_ONEOF(dir, kj::Rc<Directory>) {\n            if (path.size() == 1) {\n              if (dir->count(js) > 0 && !opts.recursive) {\n                return FsError::NOT_EMPTY;\n              }\n              return entries.erase(path[0]);\n            }\n            return dir->remove(js, path.slice(1, path.size()), kj::mv(opts));\n          }\n          KJ_CASE_ONEOF(link, kj::Rc<SymbolicLink>) {\n            // If we found a symbolic link, we can remove it if our path\n            // is exactly the symbolic link. If the path is longer, then\n            // we are trying to remove the target of the symbolic link\n            // which we do not allow.\n            if (path.size() != 1) return FsError::NOT_DIRECTORY;\n            return entries.erase(path[0]);\n          }\n        }\n      }\n\n      return false;\n    } else {\n      return FsError::READ_ONLY;\n    }\n  }\n\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"Directory\"_kj;\n  }\n\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(DirectoryBase);\n  }\n\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {\n    for (auto& entry: entries) {\n      KJ_SWITCH_ONEOF(entry.value) {\n        KJ_CASE_ONEOF(file, kj::Rc<File>) {\n          tracker.trackField(\"file\", *file.get());\n          break;\n        }\n        KJ_CASE_ONEOF(dir, kj::Rc<Directory>) {\n          tracker.trackField(\"directory\", *dir.get());\n          break;\n        }\n        KJ_CASE_ONEOF(link, kj::Rc<SymbolicLink>) {\n          // There's no need to track the symbolic link itself.\n        }\n      }\n    }\n  }\n\n  kj::StringPtr getUniqueId(jsg::Lock&) const override {\n    KJ_IF_SOME(id, maybeUniqueId) {\n      return id;\n    }\n    // Generating a UUID requires randomness, which requires an IoContext.\n    auto& ioContext = JSG_REQUIRE_NONNULL(\n        IoContext::tryCurrent(), Error, \"Cannot generate a unique ID outside of a request\");\n    maybeUniqueId = workerd::randomUUID(ioContext.getEntropySource());\n    return KJ_ASSERT_NONNULL(maybeUniqueId);\n  }\n\n  void countTowardsIsolateLimit(jsg::Lock& js) const override {\n    // We only count writable directories towards the isolate limit.\n    // Why? Because read-only directories are controlled by the runtime\n    // and not users and we don't want to count them against the isolate.\n    if constexpr (Writable) {\n      if (maybeMemoryAdjustment == kj::none) {\n        maybeMemoryAdjustment = js.getExternalMemoryAdjustment(sizeof(DirectoryBase));\n      }\n    }\n  }\n\n private:\n  kj::HashMap<kj::String, Item> entries;\n  mutable kj::Maybe<kj::String> maybeUniqueId;\n  mutable kj::Maybe<jsg::ExternalMemoryAdjustment> maybeMemoryAdjustment;\n\n  // Called by tryOpen to create a new file or directory at the given path.\n  kj::Maybe<kj::OneOf<FsError, kj::Rc<File>, kj::Rc<Directory>>> tryCreate(\n      jsg::Lock& js, kj::PathPtr path, FsType createAs) {\n    KJ_DASSERT(Writable);\n    KJ_DASSERT(path.size() > 0);\n\n    // If the path size is one, then we are creating the file or directory\n    // in *this* directory.\n    if (path.size() == 1) {\n      switch (createAs) {\n        case FsType::FILE: {\n          auto file = File::newWritable(js);\n          auto ret = file.addRef();\n          entries.insert(kj::str(path[0]), kj::mv(file));\n          return kj::Maybe<kj::OneOf<FsError, kj::Rc<File>, kj::Rc<Directory>>>(kj::mv(ret));\n        }\n        case FsType::DIRECTORY: {\n          auto dir = Directory::newWritable(js);\n          auto ret = dir.addRef();\n          entries.insert(kj::str(path[0]), kj::mv(dir));\n          return kj::Maybe<kj::OneOf<FsError, kj::Rc<File>, kj::Rc<Directory>>>(kj::mv(ret));\n        }\n        case FsType::SYMLINK: {\n          return kj::Maybe<kj::OneOf<FsError, kj::Rc<File>, kj::Rc<Directory>>>(\n              FsError::NOT_PERMITTED);\n        }\n      }\n    }\n\n    // Otherwise we need to recursively create a directory and ask it to create the file.\n    auto dir = Directory::newWritable(js);\n    KJ_IF_SOME(ret,\n        dir->tryOpen(js, path.slice(1, path.size()),\n            OpenOptions{\n              .createAs = createAs,\n            })) {\n      // We will only create the new subdirectory in this directory if the\n      // child target was successfully created/opened.\n      entries.insert(kj::str(path[0]), kj::mv(dir));\n      KJ_SWITCH_ONEOF(ret) {\n        KJ_CASE_ONEOF(file, kj::Rc<File>) {\n          return kj::Maybe<kj::OneOf<FsError, kj::Rc<File>, kj::Rc<Directory>>>(kj::mv(file));\n        }\n        KJ_CASE_ONEOF(dir, kj::Rc<Directory>) {\n          return kj::Maybe<kj::OneOf<FsError, kj::Rc<File>, kj::Rc<Directory>>>(kj::mv(dir));\n        }\n        KJ_CASE_ONEOF(link, kj::Rc<SymbolicLink>) {\n          KJ_UNREACHABLE;\n        }\n        KJ_CASE_ONEOF(err, FsError) {\n          return kj::Maybe<kj::OneOf<FsError, kj::Rc<File>, kj::Rc<Directory>>>(err);\n        }\n      }\n    }\n    return kj::none;\n  }\n};\n\n// The implementation of the File interface.\nclass FileImpl final: public File {\n public:\n  // Constructor used to create a read-only file.\n  FileImpl(kj::ArrayPtr<const kj::byte> data): ownedOrView(data), lastModified(kj::UNIX_EPOCH) {}\n  FileImpl(kj::Array<kj::byte>&& owned) = delete;\n\n  // Constructor used to create a writable file.\n  FileImpl(jsg::Lock& js, kj::Array<kj::byte> owned)\n      : ownedOrView(Owned(js, kj::mv(owned))),\n        lastModified(kj::UNIX_EPOCH) {}\n\n  kj::Maybe<FsError> setLastModified(jsg::Lock& js, kj::Date date = kj::UNIX_EPOCH) override {\n    if (isWritable()) {\n      lastModified = date;\n    }\n    return kj::none;\n  }\n\n  Stat stat(jsg::Lock& js) override {\n    return Stat{\n      .type = FsType::FILE,\n      .size = static_cast<uint32_t>(readableView().size()),\n      .lastModified = lastModified,\n      .writable = isWritable(),\n    };\n  }\n\n  uint32_t read(jsg::Lock& js, uint32_t offset, kj::ArrayPtr<kj::byte> buffer) const override {\n    auto data = readableView();\n    if (offset >= data.size() || buffer.size() == 0) return 0;\n    auto src = data.slice(offset);\n    KJ_DASSERT(src.size() > 0);\n    if (buffer.size() > src.size()) {\n      buffer.first(src.size()).copyFrom(src);\n      return src.size();\n    }\n    buffer.copyFrom(src.first(buffer.size()));\n    return buffer.size();\n  }\n\n  // Writes data to the file at the given offset.\n  kj::OneOf<FsError, uint32_t> write(\n      jsg::Lock& js, uint32_t offset, kj::ArrayPtr<const kj::byte> buffer) override {\n    auto maxSize = Worker::Isolate::from(js).getLimitEnforcer().getBlobSizeLimit();\n    if (buffer.size() > maxSize) {\n      return FsError::FILE_SIZE_LIMIT_EXCEEDED;\n    }\n\n    size_t end = offset + buffer.size();\n    if (end > maxSize || end < offset /* overflow check */) {\n      return FsError::FILE_SIZE_LIMIT_EXCEEDED;\n    }\n    if (!isWritable()) {\n      return FsError::READ_ONLY;\n    }\n\n    if (end > writableView().size()) {\n      KJ_IF_SOME(err, resize(js, end)) {\n        return err;\n      }\n    }\n    writableView().slice(offset, end).copyFrom(buffer);\n    return static_cast<uint32_t>(buffer.size());\n  }\n\n  kj::Maybe<FsError> resize(jsg::Lock& js, uint32_t size) override {\n    if (!isWritable()) {\n      return FsError::READ_ONLY;\n    }\n    auto& owned = ownedOrView.get<Owned>();\n    if (size == owned.data.size()) return kj::none;  // Nothing to do.\n\n    auto maxSize = Worker::Isolate::from(js).getLimitEnforcer().getBlobSizeLimit();\n    if (size > maxSize) {\n      return FsError::FILE_SIZE_LIMIT_EXCEEDED;\n    }\n\n    auto newData = kj::heapArray<kj::byte>(size);\n\n    if (size > owned.data.size()) {\n      // To grow the file, we need to allocate a new array, copy the old data over,\n      // and replace the original.\n      newData.first(owned.data.size()).copyFrom(owned.data);\n      newData.slice(owned.data.size()).fill(0);\n    } else {\n      newData.asPtr().copyFrom(owned.data.first(size));\n    }\n    owned.adjustment.setNow(js, newData.size());\n    owned.data = kj::mv(newData);\n    return kj::none;\n  }\n\n  kj::Maybe<FsError> fill(jsg::Lock& js, kj::byte value, kj::Maybe<uint32_t> offset) override {\n    if (!isWritable()) {\n      return FsError::READ_ONLY;\n    }\n    auto view = writableView();\n    int actualOffset = offset.orDefault(0);\n    if (actualOffset >= view.size() || view.size() == 0) return kj::none;\n    view.slice(actualOffset).fill(value);\n    return kj::none;\n  }\n\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"File\"_kj;\n  }\n\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(FileImpl);\n  }\n\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {\n    // We only track the memory if we own the data.\n    KJ_SWITCH_ONEOF(ownedOrView) {\n      KJ_CASE_ONEOF(owned, Owned) {\n        tracker.trackField(\"owned\", owned.data);\n        return;\n      }\n      KJ_CASE_ONEOF(view, kj::ArrayPtr<const kj::byte>) {\n        return;\n      }\n    }\n  }\n\n  kj::OneOf<FsError, kj::Rc<File>> clone(jsg::Lock& js) override {\n    auto maxSize = Worker::Isolate::from(js).getLimitEnforcer().getBlobSizeLimit();\n    KJ_SWITCH_ONEOF(ownedOrView) {\n      KJ_CASE_ONEOF(owned, Owned) {\n        if (owned.data.size() > maxSize) [[unlikely]] {\n          return FsError::FILE_SIZE_LIMIT_EXCEEDED;\n        }\n        kj::Rc<File> file = kj::rc<FileImpl>(js, kj::heapArray<kj::byte>(owned.data));\n        return kj::mv(file);\n      }\n      KJ_CASE_ONEOF(view, kj::ArrayPtr<const kj::byte>) {\n        if (view.size() > maxSize) [[unlikely]] {\n          return FsError::FILE_SIZE_LIMIT_EXCEEDED;\n        }\n        kj::Rc<File> file = kj::rc<FileImpl>(js, kj::heapArray<kj::byte>(view));\n        return kj::mv(file);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  kj::Maybe<FsError> replace(jsg::Lock& js, kj::Rc<File> file) override {\n    if (!isWritable()) {\n      return FsError::READ_ONLY;\n    }\n\n    auto stat = file->stat(js);\n    auto buffer = kj::heapArray<kj::byte>(stat.size);\n    file->read(js, 0, buffer.asPtr());\n    auto& owned = ownedOrView.get<Owned>();\n    owned.adjustment.setNow(js, buffer.size());\n    owned.data = kj::mv(buffer);\n    lastModified = stat.lastModified;\n    return kj::none;\n  }\n\n  kj::StringPtr getUniqueId(jsg::Lock&) const override {\n    KJ_IF_SOME(id, maybeUniqueId) {\n      return id;\n    }\n    // Generating a UUID requires randomness, which requires an IoContext.\n    auto& ioContext = JSG_REQUIRE_NONNULL(\n        IoContext::tryCurrent(), Error, \"Cannot generate a unique ID outside of a request\");\n    maybeUniqueId = workerd::randomUUID(ioContext.getEntropySource());\n    return KJ_ASSERT_NONNULL(maybeUniqueId);\n  }\n\n  void countTowardsIsolateLimit(jsg::Lock& js) const override {\n    // We only count writable files towards the isolate limit. Read-only files are\n    // controlled by the runtime. We don't want to count them against the isolate.\n    if (isWritable()) {\n      if (maybeMemoryAdjustment == kj::none) {\n        maybeMemoryAdjustment = js.getExternalMemoryAdjustment(sizeof(FileImpl));\n      }\n    }\n  }\n\n private:\n  struct Owned {\n    kj::Array<kj::byte> data;\n    jsg::ExternalMemoryAdjustment adjustment;\n    Owned(jsg::Lock& js, kj::Array<kj::byte>&& data)\n        : data(kj::mv(data)),\n          adjustment(js.getExternalMemoryAdjustment(this->data.size())) {}\n  };\n  kj::OneOf<Owned, kj::ArrayPtr<const kj::byte>> ownedOrView;\n  kj::Date lastModified;\n  mutable kj::Maybe<kj::String> maybeUniqueId;\n  mutable kj::Maybe<jsg::ExternalMemoryAdjustment> maybeMemoryAdjustment;\n\n  bool isWritable() const {\n    // Our file is only writable if it owns the actual data buffer.\n    return ownedOrView.is<Owned>();\n  }\n\n  kj::ArrayPtr<kj::byte> writableView() {\n    return KJ_REQUIRE_NONNULL(ownedOrView.tryGet<Owned>()).data.asPtr();\n  }\n\n  jsg::ExternalMemoryAdjustment& getAdjustment() {\n    return KJ_REQUIRE_NONNULL(ownedOrView.tryGet<Owned>()).adjustment;\n  }\n\n  kj::ArrayPtr<const kj::byte> readableView() const {\n    KJ_SWITCH_ONEOF(ownedOrView) {\n      KJ_CASE_ONEOF(view, kj::ArrayPtr<const kj::byte>) {\n        return view;\n      }\n      KJ_CASE_ONEOF(owned, Owned) {\n        return owned.data.asPtr().asConst();\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n};\n\nusing ReadableDirectory = DirectoryBase<false>;\nusing WritableDirectory = DirectoryBase<true>;\n\nclass VirtualFileSystemImpl;\n\nclass FdHandle final {\n public:\n  FdHandle(kj::Rc<WeakRef<VirtualFileSystemImpl>> weakFs, int fd): weakFs(kj::mv(weakFs)), fd(fd) {}\n\n  ~FdHandle() noexcept(false);\n\n private:\n  kj::Rc<WeakRef<VirtualFileSystemImpl>> weakFs;\n  int fd;\n};\n\nclass VirtualFileSystemImpl final: public VirtualFileSystem {\n public:\n  VirtualFileSystemImpl(\n      kj::Own<FsMap> fsMap, kj::Rc<Directory>&& root, kj::Own<VirtualFileSystem::Observer> observer)\n      : fsMap(kj::mv(fsMap)),\n        root(kj::mv(root)),\n        observer(kj::mv(observer)),\n        weakThis(\n            kj::rc<WeakRef<VirtualFileSystemImpl>>(kj::Badge<VirtualFileSystemImpl>(), *this)) {}\n\n  kj::Rc<OpenedFile> getStdio(jsg::Lock& js, Stdio stdio) const override;\n\n  ~VirtualFileSystemImpl() noexcept(false) override {\n    weakThis->invalidate();\n  }\n\n  kj::Rc<Directory> getRoot(jsg::Lock& js) const override {\n    return root.addRef();\n  }\n\n  const jsg::Url& getBundleRoot() const override {\n    return fsMap->getBundleRoot();\n  }\n\n  const jsg::Url& getTmpRoot() const override {\n    return fsMap->getTempRoot();\n  }\n\n  const jsg::Url& getDevRoot() const override {\n    return fsMap->getDevRoot();\n  }\n\n  kj::OneOf<FsError, kj::Rc<OpenedFile>> openFd(\n      jsg::Lock& js, const jsg::Url& url, OpenOptions opts = {}) const override {\n\n    kj::Path root{};\n    auto str = kj::str(url.getPathname().slice(1));\n\n    // We will impose an absolute max number of total file descriptors to\n    // max int... in practice, the production system should condemn the\n    // worker far before this limit is reached. Note that this is not *opened*\n    // file descriptors, this is total file descriptors opened. There is no\n    // way to reset this counter.\n    static constexpr int kMax = kj::maxValue;\n    if (nextFd == kMax) {\n      observer->onMaxFds(openedFiles.size());\n      return FsError::TOO_MANY_OPEN_FILES;\n    }\n\n    auto rootDir = getRoot(js);\n    auto path = root.eval(str);\n\n    if (opts.exclusive && opts.write) {\n      // If the exclusive flag is set with the witable flag, then we fail\n      // if the file already exists.\n      KJ_IF_SOME(maybeStat, rootDir->stat(js, path)) {\n        KJ_SWITCH_ONEOF(maybeStat) {\n          KJ_CASE_ONEOF(stat, Stat) {\n            return FsError::ALREADY_EXISTS;\n          }\n          KJ_CASE_ONEOF(err, FsError) {\n            return err;\n          }\n        }\n        KJ_UNREACHABLE;\n      }\n    }\n\n    KJ_IF_SOME(node,\n        rootDir->tryOpen(js, path,\n            Directory::OpenOptions{\n              .createAs = FsType::FILE,\n              .followLinks = opts.followLinks,\n            })) {\n      KJ_SWITCH_ONEOF(node) {\n        KJ_CASE_ONEOF(file, kj::Rc<File>) {\n          if (opts.write) {\n            auto stat = file->stat(js);\n            if (!stat.writable) return FsError::NOT_PERMITTED;\n          }\n          KJ_DASSERT(openedFiles.find(nextFd) == kj::none);\n          KJ_DEFER(observer->onOpen(openedFiles.size(), nextFd));\n          auto fd = nextFd++;\n          auto opened = kj::rc<OpenedFile>(fd, opts.read, opts.write, opts.append, kj::mv(file));\n          openedFiles.insert(fd, opened.addRef());\n          return kj::mv(opened);\n        }\n        KJ_CASE_ONEOF(dir, kj::Rc<Directory>) {\n          if (opts.write) {\n            // Similar to Node.js, we do not allow opening fd's for\n            // directories for writing.\n            return FsError::NOT_PERMITTED_ON_DIRECTORY;\n          }\n          KJ_DASSERT(openedFiles.find(nextFd) == kj::none);\n          KJ_DEFER(observer->onOpen(openedFiles.size(), nextFd));\n          auto fd = nextFd++;\n          auto opened = kj::rc<OpenedFile>(fd, opts.read, opts.write, opts.append, kj::mv(dir));\n          openedFiles.insert(fd, opened.addRef());\n          return kj::mv(opened);\n        }\n        KJ_CASE_ONEOF(link, kj::Rc<SymbolicLink>) {\n          // Symlinks are never directly writable so the write flag\n          // makes no sense.\n          if (opts.write) {\n            return FsError::NOT_PERMITTED;\n          }\n          KJ_DASSERT(openedFiles.find(nextFd) == kj::none);\n          KJ_DEFER(observer->onOpen(openedFiles.size(), nextFd));\n          auto fd = nextFd++;\n          auto opened = kj::rc<OpenedFile>(fd, opts.read, opts.write, opts.append, kj::mv(link));\n          openedFiles.insert(fd, opened.addRef());\n          return kj::mv(opened);\n        }\n        KJ_CASE_ONEOF(err, FsError) {\n          // If we got an error, we just return it.\n          return err;\n        }\n      }\n      KJ_UNREACHABLE;\n    }\n\n    // The file does not exist, and apparently was not created. Likely the\n    // directory is not writable or does not exist.\n    return FsError::FAILED;\n  }\n\n  void closeFd(jsg::Lock& js, int fd) const override {\n    // We do not allow closing the stdio file descriptors.\n    static constexpr int kMaxFd = static_cast<int>(Stdio::ERR);\n    if (fd <= kMaxFd) return;\n    closeFdWithoutExplicitLock(fd);\n  }\n\n  kj::Maybe<kj::Rc<OpenedFile>> tryGetFd(jsg::Lock& js, int fd) const override {\n    static constexpr int kMaxFd = static_cast<int>(Stdio::ERR);\n    if (fd <= kMaxFd) {\n      return getStdio(js, static_cast<Stdio>(fd));\n    }\n    KJ_IF_SOME(opened, openedFiles.find(fd)) {\n      return opened.addRef();\n    }\n    return kj::none;\n  }\n\n  kj::Own<void> wrapFd(jsg::Lock& js, int fd) const override {\n    return kj::heap<FdHandle>(weakThis.addRef(), fd);\n  }\n\n  // While held, holds a lock on the given locator url. While locked,\n  // certain operations on the identified node are not allowed.\n  struct Lock {\n    // Because we can't guarantee that the the lock will certainly be destroyed\n    // before the VFS, we hold a weak reference to the VFS. If the VFS is destroyed\n    // first, the lock essentially becomes a no-op.\n    kj::Rc<WeakRef<VirtualFileSystemImpl>> vfs;\n    const jsg::Url& url;\n\n    void lock(const jsg::Url& locator) const {\n      vfs->runIfAlive([&locator](auto& vfs) {\n        KJ_IF_SOME(locked, vfs.locks.find(locator)) {\n          locked++;\n        } else {\n          vfs.locks.insert(locator.clone(), 1);\n        }\n      });\n    }\n\n    void unlock(const jsg::Url& locator) const {\n      vfs->runIfAlive([&locator](auto& vfs) {\n        KJ_IF_SOME(locked, vfs.locks.find(locator)) {\n          if (locked == 1) {\n            vfs.locks.erase(locator);\n          } else {\n            KJ_ASSERT(locked > 0);\n            --locked;\n          }\n        }\n      });\n    }\n\n    Lock(kj::Rc<WeakRef<VirtualFileSystemImpl>> vfs, const jsg::Url& url)\n        : vfs(kj::mv(vfs)),\n          url(url) {\n      lock(url);\n\n      // This is fun... per the webfs spec, we need to prevent directories from\n      // being removed if any locks are held on any files descendent from it,\n      // so when we grab a lock, we also need to lock the parent path.\n      auto maybeParent = url.getParent();\n      while (maybeParent != kj::none) {\n        auto& parent = KJ_ASSERT_NONNULL(maybeParent);\n        lock(parent);\n        maybeParent = parent.getParent();\n      }\n    }\n    ~Lock() noexcept(false) {\n      unlock(url);\n      auto maybeParent = url.getParent();\n      while (maybeParent != kj::none) {\n        auto& parent = KJ_ASSERT_NONNULL(maybeParent);\n        unlock(parent);\n        maybeParent = parent.getParent();\n      }\n    }\n  };\n\n  kj::Own<void> lock(jsg::Lock& js, const jsg::Url& locator) const override {\n    return kj::heap<Lock>(weakThis.addRef(), locator);\n  }\n  bool isLocked(jsg::Lock& js, const jsg::Url& locator) const override {\n    KJ_IF_SOME(locked, locks.find(locator)) {\n      return locked > 0;\n    }\n    return false;\n  }\n\n private:\n  // All operations on the VFS are expected to be performed while holding the\n  // isolate lock even tho the VFS itself does not depend directly on the\n  // lock. This is to ensure that the VFS operations are thread-safe without\n  // incurring additional locking overhead.\n  kj::Own<FsMap> fsMap;\n  mutable kj::Rc<Directory> root;\n  kj::Own<VirtualFileSystem::Observer> observer;\n  mutable kj::Rc<WeakRef<VirtualFileSystemImpl>> weakThis;\n  friend class FdHandle;\n\n  // The next file descriptor to be used for the next file opened.\n  mutable int nextFd = static_cast<int>(Stdio::ERR) + 1;\n\n  mutable kj::HashMap<int, kj::Rc<OpenedFile>> openedFiles;\n  mutable kj::HashMap<jsg::Url, size_t> locks;\n\n  void closeFdWithoutExplicitLock(int fd) const {\n    openedFiles.erase(fd);\n    observer->onClose(openedFiles.size(), nextFd);\n  }\n};\n\nFdHandle::~FdHandle() noexcept(false) {\n  weakFs->runIfAlive([&](VirtualFileSystemImpl& vfs) { vfs.closeFdWithoutExplicitLock(fd); });\n}\n\n}  // namespace\n\nkj::OneOf<FsError, jsg::JsString> File::readAllText(jsg::Lock& js) {\n  auto info = stat(js);\n  KJ_DASSERT(info.type == FsType::FILE);\n  if (info.size == 0) return js.str();\n\n  KJ_STACK_ARRAY(char, data, info.size, 4096, 4096);\n  auto size = read(js, 0, data.asBytes());\n  if (size != info.size) {\n    return FsError::FAILED;\n  }\n  return js.str(data);\n}\n\nkj::OneOf<FsError, jsg::BufferSource> File::readAllBytes(jsg::Lock& js) {\n  auto info = stat(js);\n  KJ_DASSERT(info.type == FsType::FILE);\n  auto backing = jsg::BackingStore::alloc<v8::Uint8Array>(js, info.size);\n  if (info.size > 0) {\n    KJ_ASSERT(read(js, 0, backing) == info.size);\n  }\n  return jsg::BufferSource(js, kj::mv(backing));\n}\n\nvoid Directory::Builder::add(\n    kj::StringPtr name, kj::OneOf<kj::Rc<File>, kj::Rc<Directory>> fileOrDirectory) {\n  KJ_REQUIRE(validatePathWithNoSeparators(name));\n  KJ_REQUIRE(entries.find(name) == kj::none, \"file or directory already exists: \\\"\", name, \"\\\"\");\n  entries.insert(kj::str(name), kj::mv(fileOrDirectory));\n}\n\nvoid Directory::Builder::add(kj::StringPtr name, kj::Own<Directory::Builder> builder) {\n  KJ_REQUIRE(validatePathWithNoSeparators(name));\n  KJ_REQUIRE(entries.find(name) == kj::none, \"file or directory already exists: \\\"\", name, \"\\\"\");\n  entries.insert(kj::str(name), kj::mv(builder));\n}\n\nvoid Directory::Builder::addPath(\n    kj::PathPtr path, kj::OneOf<kj::Rc<File>, kj::Rc<Directory>> fileOrDirectory) {\n  KJ_ASSERT(path.size() > 0);\n\n  if (path.size() == 1) {\n    return add(path[0], kj::mv(fileOrDirectory));\n  }\n\n  // We have multiple path segments. We need to either find or create the\n  // directory at the first segment and then add the rest of the path to\n  // it.\n  auto& entry = entries.findOrCreate(\n      path[0], [&] { return Entry{kj::str(path[0]), kj::heap<Directory::Builder>()}; });\n\n  KJ_SWITCH_ONEOF(entry) {\n    KJ_CASE_ONEOF(file, kj::Rc<File>) {\n      // The current entry is a file but we are trying to add a directory.\n      // This is an error.\n      KJ_FAIL_ASSERT(\"Path already exists and is a file: \", path[0]);\n    }\n    KJ_CASE_ONEOF(dir, kj::Rc<Directory>) {\n      // The current entry is a directory that is already built.\n      // This is an error.\n      KJ_FAIL_ASSERT(\"Path already exists and is a directory: \", path[0]);\n    }\n    KJ_CASE_ONEOF(builder, kj::Own<Directory::Builder>) {\n      // The current entry is a directory builder. We need to add the\n      // rest of the path to it.\n      builder->addPath(path.slice(1, path.size()), kj::mv(fileOrDirectory));\n    }\n  }\n}\n\nkj::Rc<Directory> Directory::Builder::finish() {\n  auto map = kj::mv(entries);\n  kj::HashMap<kj::String, Directory::Item> ret;\n\n  auto addEntry = [&](kj::String name, kj::OneOf<kj::Rc<File>, kj::Rc<Directory>>&& entry) {\n    ret.upsert(\n        kj::str(name), kj::mv(entry), [&](auto& current, auto&& node) { return kj::mv(node); });\n  };\n\n  for (auto& entry: map) {\n    KJ_SWITCH_ONEOF(entry.value) {\n      KJ_CASE_ONEOF(file, kj::Rc<File>) {\n        addEntry(kj::mv(entry.key), kj::mv(file));\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<Directory>) {\n        addEntry(kj::mv(entry.key), kj::mv(dir));\n      }\n      KJ_CASE_ONEOF(builder, kj::Own<Directory::Builder>) {\n        addEntry(kj::mv(entry.key), builder->finish());\n      }\n    }\n  }\n  return kj::rc<ReadableDirectory>(kj::mv(ret));\n}\n\nkj::Rc<Directory> Directory::newWritable() {\n  return kj::rc<WritableDirectory>();\n}\n\nkj::Rc<Directory> Directory::newEmptyReadonly() {\n  Directory::Builder builder;\n  return builder.finish();\n}\n\nkj::Rc<Directory> Directory::newWritable(jsg::Lock& js) {\n  auto dir = kj::rc<WritableDirectory>();\n  dir->countTowardsIsolateLimit(js);\n  return kj::mv(dir);\n}\n\nkj::Rc<File> File::newWritable(jsg::Lock& js, kj::Maybe<uint32_t> size) {\n  // We will cap the maximum size of the file.\n  auto maxSize = Worker::Isolate::from(js).getLimitEnforcer().getBlobSizeLimit();\n  auto actualSize = kj::min(size.orDefault(0), maxSize);\n  auto data = kj::heapArray<kj::byte>(actualSize);\n  if (actualSize > 0) data.asPtr().fill(0);\n  auto file = kj::rc<FileImpl>(js, kj::mv(data));\n  file->countTowardsIsolateLimit(js);\n  return kj::mv(file);\n}\n\nkj::Rc<File> File::newReadable(kj::ArrayPtr<const kj::byte> data) {\n  return kj::rc<FileImpl>(data);\n}\n\nkj::Own<VirtualFileSystem> newVirtualFileSystem(\n    kj::Own<FsMap> fsMap, kj::Rc<Directory>&& root, kj::Own<VirtualFileSystem::Observer> observer) {\n  return kj::heap<VirtualFileSystemImpl>(kj::mv(fsMap), kj::mv(root), kj::mv(observer));\n}\n\nkj::Own<VirtualFileSystem> newWorkerFileSystem(kj::Own<FsMap> fsMap,\n    kj::Rc<Directory> bundleDirectory,\n    kj::Own<VirtualFileSystem::Observer> observer) {\n  // Our root directory is a read-only directory\n  Directory::Builder builder;\n  builder.addPath(fsMap->getBundlePath(), kj::mv(bundleDirectory));\n  builder.addPath(fsMap->getTempPath(), getTmpDirectoryImpl());\n  builder.addPath(fsMap->getDevPath(), getDevDirectory());\n  return newVirtualFileSystem(kj::mv(fsMap), builder.finish(), kj::mv(observer));\n}\n\nkj::Rc<Directory> getTmpDirectoryImpl() {\n  return kj::rc<TmpDirectory>();\n}\n\nbool TmpDirStoreScope::hasCurrent() {\n  return tmpDirStorageScope != nullptr;\n}\n\nTmpDirStoreScope& TmpDirStoreScope::current() {\n  KJ_ASSERT(hasCurrent(), \"no current TmpDirStoreScope\");\n  return *tmpDirStorageScope;\n}\n\nTmpDirStoreScope::TmpDirStoreScope(kj::Maybe<kj::Badge<TmpDirStoreScope>> guard)\n    : dir(Directory::newWritable()),\n      // we use the /bundle cwd for the isolate vfs\n      // and the /tmp cwd for the iocontext vfs\n      cwd({\"bundle\"}) {\n  if (guard == kj::none) {\n    kj::requireOnStack(this, \"must be created on the stack\");\n    onStack = true;\n    KJ_ASSERT(!hasCurrent(), \"TmpDirStoreScope already exists on this thread\");\n    tmpDirStorageScope = this;\n  }\n}\n\nTmpDirStoreScope::~TmpDirStoreScope() noexcept(false) {\n  if (onStack) {\n    KJ_ASSERT(tmpDirStorageScope == this, \"this TmpDirStoreScope not on the stack\");\n    tmpDirStorageScope = nullptr;\n  }\n}\n\nkj::Own<TmpDirStoreScope> TmpDirStoreScope::create() {\n  // Creating the instance with the badge will ensure that\n  // it is not set as current in the stack.\n  return kj::heap<TmpDirStoreScope>(kj::Badge<TmpDirStoreScope>());\n}\n\nStat SymbolicLink::stat(jsg::Lock& js) {\n  return Stat{\n    .type = FsType::SYMLINK,\n    .size = 0,\n    .lastModified = kj::UNIX_EPOCH,\n    .writable = false,\n  };\n}\n\njsg::Url SymbolicLink::getTargetUrl() const {\n  auto path = getTargetPath().toString(false);\n  return KJ_ASSERT_NONNULL(jsg::Url::tryParse(path, \"file:///\"_kj));\n}\n\nkj::Maybe<kj::OneOf<FsError, kj::Rc<File>, kj::Rc<Directory>>> SymbolicLink::resolve(\n    jsg::Lock& js) {\n  KJ_IF_SOME(ret, root->tryOpen(js, getTargetPath())) {\n    KJ_SWITCH_ONEOF(ret) {\n      KJ_CASE_ONEOF(file, kj::Rc<File>) {\n        return kj::Maybe<kj::OneOf<FsError, kj::Rc<File>, kj::Rc<Directory>>>(kj::mv(file));\n      }\n      KJ_CASE_ONEOF(dir, kj::Rc<Directory>) {\n        return kj::Maybe<kj::OneOf<FsError, kj::Rc<File>, kj::Rc<Directory>>>(kj::mv(dir));\n      }\n      KJ_CASE_ONEOF(link, kj::Rc<SymbolicLink>) {\n        // The resolve(...) method here follows all symbolic links in the path,\n        // so when it encounters a symlink as the path is being processed, it\n        // will attempt to resolve it into it's target or return kj::none. If\n        // you want the symlink itself, then use tryOpen(...) on the directory\n        KJ_UNREACHABLE;\n      }\n      KJ_CASE_ONEOF(err, FsError) {\n        return kj::Maybe<kj::OneOf<FsError, kj::Rc<File>, kj::Rc<Directory>>>(err);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n  return kj::none;\n}\n\nkj::StringPtr SymbolicLink::getUniqueId(jsg::Lock&) const {\n  KJ_IF_SOME(id, maybeUniqueId) {\n    return id;\n  }\n  // Generating a UUID requires randomness, which requires an IoContext.\n  auto& ioContext = JSG_REQUIRE_NONNULL(\n      IoContext::tryCurrent(), Error, \"Cannot generate a unique ID outside of a request\");\n  maybeUniqueId = workerd::randomUUID(ioContext.getEntropySource());\n  return KJ_ASSERT_NONNULL(maybeUniqueId);\n}\n\nvoid SymbolicLink::countTowardsIsolateLimit(jsg::Lock& js) const {\n  if (maybeMemoryAdjustment == kj::none) {\n    maybeMemoryAdjustment = js.getExternalMemoryAdjustment(sizeof(SymbolicLink));\n  }\n}\n\nkj::Rc<Directory> getLazyDirectoryImpl(kj::Function<kj::Rc<Directory>()> func) {\n  return kj::rc<LazyDirectory>(kj::mv(func));\n}\n\nconst VirtualFileSystem& VirtualFileSystem::current(jsg::Lock& js) {\n  // The VFS is stored in an embedder data slot in the v8::Context associated with\n  // the current jsg::Lock. The actual instance is kept alive using a kj::Own held\n  // by the Worker::Script.\n  return KJ_ASSERT_NONNULL(jsg::getAlignedPointerFromEmbedderData<VirtualFileSystem>(\n      js.v8Context(), jsg::ContextPointerSlot::VIRTUAL_FILE_SYSTEM));\n}\n\nkj::Maybe<FsNodeWithError> VirtualFileSystem::resolve(\n    jsg::Lock& js, const jsg::Url& url, ResolveOptions options) const {\n  if (url.getProtocol() != \"file:\"_kj) {\n    // We only accept file URLs.\n    return kj::none;\n  }\n  // We want to strip the leading slash from the path.\n  auto path = kj::str(url.getPathname().slice(1));\n  kj::Path root{};\n  return getRoot(js)->tryOpen(js, root.eval(path),\n      Directory::OpenOptions{\n        .followLinks = options.followLinks,\n      });\n}\n\nkj::Maybe<kj::OneOf<FsError, Stat>> VirtualFileSystem::resolveStat(\n    jsg::Lock& js, const jsg::Url& url) const {\n  if (url.getProtocol() != \"file:\"_kj) {\n    // We only accept file URLs.\n    return kj::none;\n  }\n  // We want to strip the leading slash from the path.\n  auto path = kj::str(url.getPathname().slice(1));\n  kj::Path root{};\n  return getRoot(js)->stat(js, root.eval(path));\n}\n\nkj::Rc<SymbolicLink> VirtualFileSystem::newSymbolicLink(jsg::Lock& js, const jsg::Url& url) const {\n  KJ_REQUIRE(url.getProtocol() == \"file:\"_kj);\n  auto path = kj::str(url.getPathname().slice(1));\n  kj::Path root{};\n  return kj::rc<SymbolicLink>(getRoot(js), root.eval(path));\n}\n\nSymbolicLinkRecursionGuardScope::SymbolicLinkRecursionGuardScope() {\n  if (symbolicLinkGuard == nullptr) {\n    symbolicLinkGuard = this;\n  }\n}\nSymbolicLinkRecursionGuardScope::~SymbolicLinkRecursionGuardScope() noexcept(false) {\n  if (symbolicLinkGuard == this) {\n    symbolicLinkGuard = nullptr;\n  }\n}\n\nkj::Maybe<FsError> SymbolicLinkRecursionGuardScope::checkSeen(SymbolicLink* link) {\n  if (symbolicLinkGuard == nullptr) {\n    return kj::none;\n  }\n  auto& guard = *symbolicLinkGuard;\n  if (guard.linksSeen.find(link) != kj::none) {\n    return FsError::SYMLINK_DEPTH_EXCEEDED;\n  }\n  guard.linksSeen.insert(link);\n  return kj::none;\n}\n\nnamespace {\n// Implementations of special \"device\" files equivalent to special devices typically\n// found on posix systems.\n\n// /dev/null is a special file that discards all data written to it and returns\n// EOF on reads.\nclass DevNullFile final: public File {\n public:\n  DevNullFile() = default;\n\n  Stat stat(jsg::Lock& js) override {\n    return Stat{\n      .type = FsType::FILE,\n      .size = 0,\n      .lastModified = kj::UNIX_EPOCH,\n      .writable = true,\n      .device = true,\n    };\n  }\n\n  kj::OneOf<FsError, kj::Rc<File>> clone(jsg::Lock&) override {\n    kj::Rc<File> ref = addRefToThis();\n    return kj::mv(ref);\n  }\n\n  kj::Maybe<FsError> replace(jsg::Lock& js, kj::Rc<File> file) override {\n    return kj::none;\n  }\n\n  kj::Maybe<FsError> setLastModified(jsg::Lock& js, kj::Date date = kj::UNIX_EPOCH) override {\n    return kj::none;\n  }\n\n  kj::Maybe<FsError> fill(jsg::Lock& js, kj::byte value, kj::Maybe<uint32_t> offset) override {\n    return kj::none;\n  }\n\n  kj::Maybe<FsError> resize(jsg::Lock& js, uint32_t size) override {\n    return kj::none;\n  }\n\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"/dev/null\"_kj;\n  }\n\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(DevNullFile);\n  }\n\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {\n    // No-op.\n  }\n\n  uint32_t read(jsg::Lock& js, uint32_t offset, kj::ArrayPtr<kj::byte> buffer) const override {\n    return 0;\n  }\n\n  kj::OneOf<FsError, uint32_t> write(\n      jsg::Lock& js, uint32_t offset, kj::ArrayPtr<const kj::byte> buffer) override {\n    return static_cast<uint32_t>(buffer.size());\n  }\n\n  kj::StringPtr getUniqueId(jsg::Lock&) const override {\n    KJ_IF_SOME(id, maybeUniqueId) {\n      return id;\n    }\n    // Generating a UUID requires randomness, which requires an IoContext.\n    auto& ioContext = JSG_REQUIRE_NONNULL(\n        IoContext::tryCurrent(), Error, \"Cannot generate a unique ID outside of a request\");\n    maybeUniqueId = workerd::randomUUID(ioContext.getEntropySource());\n    return KJ_ASSERT_NONNULL(maybeUniqueId);\n  }\n\n private:\n  mutable kj::Maybe<kj::String> maybeUniqueId;\n};\n\n// /dev/zero is a special file that returns zeroes when read from and\n// ignores writes.\nclass DevZeroFile final: public File {\n public:\n  DevZeroFile() = default;\n\n  Stat stat(jsg::Lock& js) override {\n    return Stat{\n      .type = FsType::FILE,\n      .size = 0,\n      .lastModified = kj::UNIX_EPOCH,\n      .writable = true,\n      .device = true,\n    };\n  }\n\n  kj::OneOf<FsError, kj::Rc<File>> clone(jsg::Lock&) override {\n    kj::Rc<File> ref = addRefToThis();\n    return kj::mv(ref);\n  }\n\n  kj::Maybe<FsError> replace(jsg::Lock& js, kj::Rc<File> file) override {\n    return kj::none;\n  }\n\n  kj::Maybe<FsError> setLastModified(jsg::Lock& js, kj::Date date = kj::UNIX_EPOCH) override {\n    return kj::none;\n  }\n\n  kj::Maybe<FsError> fill(jsg::Lock& js, kj::byte value, kj::Maybe<uint32_t> offset) override {\n    return kj::none;\n  }\n\n  kj::Maybe<FsError> resize(jsg::Lock& js, uint32_t size) override {\n    return kj::none;\n  }\n\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"/dev/zero\"_kj;\n  }\n\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(DevZeroFile);\n  }\n\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {\n    // No-op.\n  }\n\n  uint32_t read(jsg::Lock& js, uint32_t offset, kj::ArrayPtr<kj::byte> buffer) const override {\n    buffer.fill(0);\n    return buffer.size();\n  }\n\n  kj::OneOf<FsError, uint32_t> write(\n      jsg::Lock& js, uint32_t offset, kj::ArrayPtr<const kj::byte> buffer) override {\n    return static_cast<uint32_t>(buffer.size());\n  }\n\n  kj::StringPtr getUniqueId(jsg::Lock&) const override {\n    KJ_IF_SOME(id, maybeUniqueId) {\n      return id;\n    }\n    // Generating a UUID requires randomness, which requires an IoContext.\n    auto& ioContext = JSG_REQUIRE_NONNULL(\n        IoContext::tryCurrent(), Error, \"Cannot generate a unique ID outside of a request\");\n    maybeUniqueId = workerd::randomUUID(ioContext.getEntropySource());\n    return KJ_ASSERT_NONNULL(maybeUniqueId);\n  }\n\n private:\n  mutable kj::Maybe<kj::String> maybeUniqueId;\n};\n\n// /dev/full is a special file that returns zeroes when read from and\n// returns an error when written to.\nclass DevFullFile final: public File {\n public:\n  DevFullFile() = default;\n\n  Stat stat(jsg::Lock& js) override {\n    return Stat{\n      .type = FsType::FILE,\n      .size = 0,\n      .lastModified = kj::UNIX_EPOCH,\n      .writable = true,\n      .device = true,\n    };\n  }\n\n  kj::OneOf<FsError, kj::Rc<File>> clone(jsg::Lock&) override {\n    kj::Rc<File> ref = addRefToThis();\n    return kj::mv(ref);\n  }\n\n  kj::Maybe<FsError> replace(jsg::Lock& js, kj::Rc<File> file) override {\n    return FsError::NOT_PERMITTED;\n  }\n\n  kj::Maybe<FsError> setLastModified(jsg::Lock& js, kj::Date date = kj::UNIX_EPOCH) override {\n    return kj::none;\n  }\n\n  kj::Maybe<FsError> fill(jsg::Lock& js, kj::byte value, kj::Maybe<uint32_t> offset) override {\n    return FsError::NOT_PERMITTED;\n  }\n\n  kj::Maybe<FsError> resize(jsg::Lock& js, uint32_t size) override {\n    return FsError::NOT_PERMITTED;\n  }\n\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"/dev/full\"_kj;\n  }\n\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(DevFullFile);\n  }\n\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {\n    // No-op.\n  }\n\n  uint32_t read(jsg::Lock& js, uint32_t offset, kj::ArrayPtr<kj::byte> buffer) const override {\n    buffer.fill(0);\n    return buffer.size();\n  }\n\n  kj::OneOf<FsError, uint32_t> write(\n      jsg::Lock& js, uint32_t offset, kj::ArrayPtr<const kj::byte> buffer) override {\n    return FsError::NOT_PERMITTED;\n  }\n\n  kj::StringPtr getUniqueId(jsg::Lock&) const override {\n    KJ_IF_SOME(id, maybeUniqueId) {\n      return id;\n    }\n    // Generating a UUID requires randomness, which requires an IoContext.\n    auto& ioContext = JSG_REQUIRE_NONNULL(\n        IoContext::tryCurrent(), Error, \"Cannot generate a unique ID outside of a request\");\n    maybeUniqueId = workerd::randomUUID(ioContext.getEntropySource());\n    return KJ_ASSERT_NONNULL(maybeUniqueId);\n  }\n\n private:\n  mutable kj::Maybe<kj::String> maybeUniqueId;\n};\n\nclass DevRandomFile final: public File {\n public:\n  DevRandomFile() = default;\n\n  Stat stat(jsg::Lock& js) override {\n    return Stat{\n      .type = FsType::FILE,\n      .size = 0,\n      .lastModified = kj::UNIX_EPOCH,\n      .writable = true,\n      .device = true,\n    };\n  }\n\n  kj::OneOf<FsError, kj::Rc<File>> clone(jsg::Lock&) override {\n    kj::Rc<File> ref = addRefToThis();\n    return kj::mv(ref);\n  }\n\n  kj::Maybe<FsError> replace(jsg::Lock& js, kj::Rc<File> file) override {\n    return FsError::NOT_PERMITTED;\n  }\n\n  kj::Maybe<FsError> setLastModified(jsg::Lock& js, kj::Date date = kj::UNIX_EPOCH) override {\n    return FsError::NOT_PERMITTED;\n  }\n\n  kj::Maybe<FsError> fill(jsg::Lock& js, kj::byte value, kj::Maybe<uint32_t> offset) override {\n    return FsError::NOT_PERMITTED;\n  }\n\n  kj::Maybe<FsError> resize(jsg::Lock& js, uint32_t size) override {\n    return FsError::NOT_PERMITTED;\n  }\n\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"/dev/random\"_kj;\n  }\n\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(DevRandomFile);\n  }\n\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {\n    // No-op.\n  }\n\n  kj::OneOf<FsError, uint32_t> write(\n      jsg::Lock& js, uint32_t offset, kj::ArrayPtr<const kj::byte> buffer) override {\n    return FsError::NOT_PERMITTED;\n  }\n\n  uint32_t read(jsg::Lock& js, uint32_t offset, kj::ArrayPtr<kj::byte> buffer) const override {\n    // We can only generate random bytes when we have an active IoContext.\n    // If there is no IoContext, this will return 0 bytes.\n    KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n      if (isPredictableModeForTest()) {\n        buffer.fill(9);\n      } else {\n        ioContext.getEntropySource().generate(buffer);\n      }\n      return buffer.size();\n    }\n    return 0;\n  }\n\n  kj::StringPtr getUniqueId(jsg::Lock&) const override {\n    KJ_IF_SOME(id, maybeUniqueId) {\n      return id;\n    }\n    // Generating a UUID requires randomness, which requires an IoContext.\n    auto& ioContext = JSG_REQUIRE_NONNULL(\n        IoContext::tryCurrent(), Error, \"Cannot generate a unique ID outside of a request\");\n    maybeUniqueId = workerd::randomUUID(ioContext.getEntropySource());\n    return KJ_ASSERT_NONNULL(maybeUniqueId);\n  }\n\n private:\n  mutable kj::Maybe<kj::String> maybeUniqueId;\n};\n\n// Write stdio via console.log. Somewhat convoluted, but this then supports:\n// - inspector reporting\n// - structured logging\n// - stdio output otherwise\nvoid writeStdio(jsg::Lock& js, VirtualFileSystem::Stdio type, kj::ArrayPtr<const kj::byte> bytes) {\n  auto chars = bytes.asChars();\n  size_t endPos = chars.size();\n  if (endPos > 0 && chars[endPos - 1] == '\\n') endPos--;\n\n  KJ_IF_SOME(console, js.global().get(js, \"console\"_kj).tryCast<jsg::JsObject>()) {\n    auto method = console.get(js, \"log\"_kj);\n    if (method.isFunction()) {\n      v8::Local<v8::Value> methodVal(method);\n      auto methodFunc = jsg::JsFunction(methodVal.As<v8::Function>());\n\n      kj::String outputStr;\n      auto isolate = &Worker::Isolate::from(js);\n      auto prefix = type == VirtualFileSystem::Stdio::OUT ? isolate->getStdoutPrefix()\n                                                          : isolate->getStderrPrefix();\n      if (endPos == 0) {\n        methodFunc.call(js, console, js.str(prefix));\n      } else if (prefix.size() > 0) {\n        methodFunc.call(js, console, js.str(kj::str(prefix, \" \"_kj, chars.first(endPos))));\n      } else {\n        methodFunc.call(js, console, js.str(chars.first(endPos)));\n      }\n      return;\n    }\n  }\n  KJ_LOG(WARNING, \"No console.log implementation available for stdio logging\");\n}\n\n// An StdioFile is a special file implementation used to represent stdin,\n// stdout, and stderr outputs. Writes are always forwarded to the underlying\n// logging mechanisms. Reads always return EOF (0-byte reads).\nclass StdioFile final: public File {\n public:\n  StdioFile(VirtualFileSystem::Stdio type)\n      : type(type),\n        // TODO(sometime): Investigate if we can refactor out the weakref here?\n        weakThis(kj::rc<WeakRef<StdioFile>>(kj::Badge<StdioFile>(), *this)) {}\n\n  ~StdioFile() noexcept(false) override {\n    weakThis->invalidate();\n  }\n\n  Stat stat(jsg::Lock& js) override {\n    return Stat{\n      .type = FsType::FILE,\n      .size = 0,\n      .lastModified = kj::UNIX_EPOCH,\n      .writable = true,\n      .device = false,\n    };\n  }\n\n  kj::OneOf<FsError, kj::Rc<File>> clone(jsg::Lock&) override {\n    kj::Rc<File> ref = addRefToThis();\n    return kj::mv(ref);\n  }\n\n  kj::Maybe<FsError> replace(jsg::Lock& js, kj::Rc<File> file) override {\n    return FsError::NOT_PERMITTED;\n  }\n\n  kj::Maybe<FsError> setLastModified(jsg::Lock& js, kj::Date date = kj::UNIX_EPOCH) override {\n    return FsError::NOT_PERMITTED;\n  }\n\n  kj::Maybe<FsError> fill(jsg::Lock& js, kj::byte value, kj::Maybe<uint32_t> offset) override {\n    return FsError::NOT_PERMITTED;\n  }\n\n  kj::Maybe<FsError> resize(jsg::Lock& js, uint32_t size) override {\n    return FsError::NOT_PERMITTED;\n  }\n\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"stdio\"_kj;\n  }\n\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(StdioFile);\n  }\n\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {}\n\n  kj::OneOf<FsError, uint32_t> write(\n      jsg::Lock& js, uint32_t, kj::ArrayPtr<const kj::byte> buffer) override {\n    if (buffer.size() > MAX_WRITE_SIZE) {\n      buffer = buffer.first(MAX_WRITE_SIZE);\n    }\n\n    if (buffer.size() == 0) return static_cast<uint32_t>(0);\n\n    // We ignore the offset here. All writes are assumed to be appends.\n    if (type != VirtualFileSystem::Stdio::IN) {\n      size_t pos = 0;\n\n      // Newline-based buffering\n      while (pos < buffer.size()) {\n        size_t newlinePos = pos;\n        while (newlinePos < buffer.size() && buffer[newlinePos] != '\\n') {\n          newlinePos++;\n        }\n\n        if (newlinePos < buffer.size()) {\n          auto lineData = buffer.slice(pos, newlinePos + 1);\n\n          if (!lineBuffer.empty()) {\n            // We have buffered data - append the line data to it\n            lineBuffer.addAll(lineData);\n            writeStdio(js, type, lineBuffer.asPtr());\n            lineBuffer.clear();\n          } else {\n            // No buffered data - log this line directly\n            writeStdio(js, type, lineData);\n          }\n\n          pos = newlinePos + 1;\n        } else {\n          // No newlines -> append to line buffer\n          auto remaining = buffer.slice(pos);\n          auto totalSize = lineBuffer.size() + remaining.size();\n\n          if (totalSize <= MAX_LINE_BUFFER_SIZE) {\n            lineBuffer.addAll(remaining);\n          } else {\n            // New data alone exceeds limit, replace entire line buffer\n            if (remaining.size() >= MAX_LINE_BUFFER_SIZE) {\n              lineBuffer.clear();\n              lineBuffer.addAll(remaining.slice(remaining.size() - MAX_LINE_BUFFER_SIZE));\n            } else {\n              // Combined size exceeds limit, remove oldest data\n              auto toRemove = totalSize - MAX_LINE_BUFFER_SIZE;\n              kj::Vector<kj::byte> newBuffer(MAX_LINE_BUFFER_SIZE);\n              newBuffer.addAll(lineBuffer.slice(toRemove, lineBuffer.size()));\n              newBuffer.addAll(remaining);\n              lineBuffer = kj::mv(newBuffer);\n            }\n          }\n\n          // Schedule a microtask to flush the lineBuffer\n          // this way synchronous writes join the line, but async writes will be on a new line\n          scheduleFlushMicrotask(js);\n\n          break;\n        }\n      }\n    }\n    return static_cast<uint32_t>(buffer.size());\n  }\n\n  uint32_t read(jsg::Lock&, uint32_t, kj::ArrayPtr<kj::byte>) const override {\n    return 0;  // EOF\n  }\n\n  kj::StringPtr getUniqueId(jsg::Lock&) const override {\n    KJ_IF_SOME(id, maybeUniqueId) {\n      return id;\n    }\n    // Generating a UUID requires randomness, which requires an IoContext.\n    auto& ioContext = JSG_REQUIRE_NONNULL(\n        IoContext::tryCurrent(), Error, \"Cannot generate a unique ID outside of a request\");\n    maybeUniqueId = workerd::randomUUID(ioContext.getEntropySource());\n    return KJ_ASSERT_NONNULL(maybeUniqueId);\n  }\n\n private:\n  VirtualFileSystem::Stdio type;\n  mutable kj::Maybe<kj::String> maybeUniqueId;\n\n  static constexpr size_t MAX_LINE_BUFFER_SIZE = 4096;\n  static constexpr size_t MAX_WRITE_SIZE = 16 * 1024;\n  mutable kj::Vector<kj::byte> lineBuffer;\n  mutable bool microtaskScheduled = false;\n\n  kj::Rc<WeakRef<StdioFile>> weakThis;\n\n  void scheduleFlushMicrotask(jsg::Lock& js) {\n    if (microtaskScheduled) return;\n    microtaskScheduled = true;\n\n    // Create ephemeral callback with weak reference for safety\n    auto callback = js.wrapSimpleFunction(js.v8Context(),\n        [weakThis = weakThis->addRef()](jsg::Lock& js, const v8::FunctionCallbackInfo<v8::Value>&) {\n      weakThis->runIfAlive([&](StdioFile& self) {\n        self.microtaskScheduled = false;\n\n        if (!self.lineBuffer.empty()) {\n          if (IoContext::hasCurrent()) {\n            writeStdio(js, self.type, self.lineBuffer.asPtr());\n          }\n          self.lineBuffer.clear();\n        }\n      });\n    });\n    js.v8Isolate->EnqueueMicrotask(callback);\n  }\n};\n\n}  // namespace\n\nkj::Rc<File> getDevNull() {\n  return kj::rc<DevNullFile>();\n}\n\nkj::Rc<File> getDevZero() {\n  return kj::rc<DevZeroFile>();\n}\n\nkj::Rc<File> getDevFull() {\n  return kj::rc<DevFullFile>();\n}\n\nkj::Rc<File> getDevRandom() {\n  return kj::rc<DevRandomFile>();\n}\n\nkj::Rc<Directory> getDevDirectory() {\n  Directory::Builder builder;\n  builder.add(\"null\", getDevNull());\n  builder.add(\"zero\", getDevZero());\n  builder.add(\"full\", getDevFull());\n  builder.add(\"random\", getDevRandom());\n  return builder.finish();\n}\n\nkj::Rc<VirtualFileSystem::OpenedFile> VirtualFileSystemImpl::getStdio(\n    jsg::Lock& js, Stdio stdio) const {\n  int n = static_cast<int>(stdio);\n  KJ_IF_SOME(existing, openedFiles.find(n)) {\n    return existing.addRef();\n  }\n  auto stdioFile = kj::rc<StdioFile>(stdio);\n  kj::Rc<workerd::File> file = kj::mv(stdioFile);\n  auto opened = kj::rc<VirtualFileSystem::OpenedFile>(n, true, true, true, kj::mv(file));\n  openedFiles.insert(n, opened.addRef());\n  return kj::mv(opened);\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/worker-fs.h",
    "content": "#pragma once\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/url.h>\n\n#include <kj/common.h>\n#include <kj/refcount.h>\n#include <kj/time.h>\n\n// Every worker instance will have its own root directory (/). In this root\n// directory we will have at least three special directories, the \"bundle\" root,\n// the \"dev\" root, and the \"temp\" root. More special directories can be added\n// later.\n//\n// The bundle root is where all of the modules that are included in the worker\n// bundle will be accessible. Everything in this directory will be strictly\n// read-only. The contents are populated by the worker configuration bundle.\n// By default, the bundle root will be /bundle but this can be overridden\n// using the FsMap API defined below.\n//\n// The dev root is where special \"device\" files will be accessible. For example,\n// the /dev/null, /dev/zero, /dev/full, and /dev/random files will be here.\n// By default these are in /dev but the location can be overridden using the\n// FsMap API.\n//\n// The temp root is where we will allow temporary files to be created.\n// Everything in this directory is read-write but will be transient. By default,\n// the temp root will be /tmp but this can also be overridden.\n//\n// Let's imagine the following simple workerd configuration:\n//\n// ```\n// const helloWorld :Workerd.Worker = (\n//   modules = [\n//     (name = \"worker\",\n//      esModule = embed \"worker.js\"),\n//     (name = \"foo\", text = \"Hello World!\"),\n//   ],\n//   compatibilityDate = \"2023-02-28\",\n// );\n// ```\n//\n// Given this configuration, the worker fs will initially have the following\n// structure:\n//\n// /\n// ├── bundle\n// │   ├── worker\n// │   └── foo\n// ├── dev\n// │   ├── null\n// │   ├── zero\n// │   ├── full\n// │   └── random\n// └── tmp\n//\n// We can access the filesystem using VirtualFileSystem::current():\n//\n// ```cpp\n// jsg::Lock& js = ...\n// auto& vfs = VirtualFileSystem::current(js);\n// // Resolve the root of the file system\n// KJ_IF_SOME(node, vfs.resolve(js, \"file:///path/to/thing\"_url)) {\n//   KJ_SWITCH_ONEOF(node) {\n//     KJ_CASE_ONEOF(file, kj::Rc<File>) {\n//       // ...\n//     }\n//     KJ_CASE_ONEOF(dir, kj::Rc<Directory>) {\n//       // ...\n//     }\n//     KJ_CASE_ONEOF(link, kj::Rc<SymbolicLink>) {\n//       // ...\n//     }\n//   }\n// }\n// ```\n//\n// The temporary file directory is a bit special in that the contents are fully\n// transient based on whether there is an active IoContext or not. We use a\n// special RAII TmpDirStoreScope scope to manage the contents of the temporary\n// directory. For example,\n//\n// ```cpp\n// KJ_IF_SOME(node, vfs.resolve(\"file:///tmp\"_url)) {\n//   auto& dir = KJ_ASSERT_NONNULL(node.tryGet<kj::Rc<Directory>>());\n//   TmpDirStoreScope temp_dir_scope;\n//   kj::Path path(\"a/b/c/foo.txt\")\n//   auto tmpFile = dir.tryOpen(js, path, { FsType::FILE });\n//   KJ_ASSERT(tmpFile.write(js, 0, \"Hello World!\"_kjb) == 12);\n//   // The temp dir scope is destructed and the file is deleted\n// }\n// ```\n//\n// If there is an active IoContext, the temporary file will instead be created\n// within that IoContext's TmpDirStoreScope, and will be deleted when the\n// IoContext is destructed. This allows us to have a single virtual file\n// system whose temporary directories are either deleted immediately as soon\n// as the execution scope is exited, or are specific to the IoContext and are\n// deleted when the IoContext is destructed. This mechanism allows us to have\n// multiple IoContexts active at the same time while still having a single\n// virtual file system whose contents correctly reflect the current IoContext.\n//\n// Note that operations on files and directories require having a jsg::Lock&.\n// This is used for several purposes. One, for writable files it is used to\n// provide access to the memory accounting systen, ensuring that the memory\n// held by writable files is appropriately accounted for towards to isolate\n// heap limit. Second, use of the jsg::Lock& ensures that file system mutations\n// are performed in a thread-safe manner -- specifically, we use it to ensure\n// that only one thread is allowed to access the file system at a time since\n// only one thread at a time can hold the jsg::Lock.\n//\n// The design here is intended to be extensible. We can add new root directories\n// in the future with different semantics and implementations. For example, to\n// support python workers, for instance, we can introduce a new root directory\n// that is backed by a tar/zip file containing the python standard library, etc.\n//\n// To support the implementation of node:fs the virtual file system needs a\n// concept of file descriptors that map somewhat cleanly to the posix notion\n// of file descriptors. This is a bit tricky because fds are a finite resource.\n// The VFS implementation uses a notion of file descriptors that are scoped\n// specifically to the worker... that is, fd 1 in one worker is not the same as\n// fd 1 in another. It will be a requirement that fd's are closed explicitly or\n// they may still \"leak\" beyond the scope where they are used, but when the\n// worker is torn down, all associated file descriptors will be automatically\n// destroyed/closed rather than leaking for the entire process. We use just a\n// simple in-memory table to track the file descriptors and their associated\n// files/directories.\n//\n// Note: It is important to keep in mind that Directory, File, and SymbolicLink\n// instances are kj::Refcounted objects. We utilize the Isolate lock to safely\n// manage the refcounts to avoid having to make these AtomicRefcounted, which\n// would carry additional overhead. This means that it is essentially that all\n// references to these objects, and all operations on them, as well as dropping\n// the references, are done while holding the isolate lock. It is particularly\n// necessary to take care when capturing these objects, for instance, into a\n// kj Promise then lambda. If the promise is dropped outside of the isolate lock,\n// the refcount will be decremented outside of the lock, causing issues. The\n// bottom line is that you should always be holding the isolate lock when\n// interacting with the virtual file system.\nnamespace workerd {\n\n// TODO(node-fs): Currently, all files and directories use a fixed last\n// modified time set to the Unix epoch. This is temporary.\n\nenum class FsType {\n  FILE,\n  DIRECTORY,\n  SYMLINK,\n};\n\n// Metadata about this filesystem node\nstruct Stat final {\n  FsType type = FsType::FILE;\n\n  // The size of the node in bytes. For directories, this value will\n  // always be 0. Note that we are intentionally limiting the size of\n  // files to max(uint32_t) or 4GB. This is well above the maximum\n  // isolate heap limit so in-memory files should generally never\n  // get this large.\n  uint32_t size = 0;\n\n  // The last modified time of the node.\n  kj::Date lastModified = kj::UNIX_EPOCH;\n\n  // The creation time of the node.\n  kj::Date created = kj::UNIX_EPOCH;\n\n  // Indicates if the node is writable.\n  bool writable = false;\n\n  // Indicates if the node is a device. Certain operations\n  // may behave differently on devices.\n  bool device = false;\n};\n\nclass SymbolicLink;\n\nenum class FsError {\n  // Path segment is not a directory\n  NOT_DIRECTORY,\n  // Directory is not empty\n  NOT_EMPTY,\n  // Node is read-only\n  READ_ONLY,\n  // Not permitted\n  NOT_PERMITTED,\n  // Not permitted on directory\n  NOT_PERMITTED_ON_DIRECTORY,\n  // Already exists\n  ALREADY_EXISTS,\n  // Too many open files\n  TOO_MANY_OPEN_FILES,\n  // Operation failed\n  FAILED,\n  // Not supported\n  NOT_SUPPORTED,\n  // Invalid path\n  INVALID_PATH,\n  // Exceeds file size limit\n  FILE_SIZE_LIMIT_EXCEEDED,\n  // Symlink depth exceeded\n  SYMLINK_DEPTH_EXCEEDED,\n};\n\n// A file in the virtual file system. If the file is read-only, then the\n// mutation methods will throw an exception.\n//\n// A file is writable if it owns its contents. In such cases, the memory\n// allocation is tracked by the corresponding isolate memory accounting.\nclass File: public kj::Refcounted {\n public:\n  // Attempt to set the last modified time of this node. If the node is\n  // read-only, this will be a non-op.\n  virtual kj::Maybe<FsError> setLastModified(jsg::Lock& js, kj::Date date) = 0;\n\n  // Returns the metadata for this node.\n  virtual Stat stat(jsg::Lock& js) KJ_WARN_UNUSED_RESULT = 0;\n\n  // Reads all the contents of the file as a string.\n  kj::OneOf<FsError, jsg::JsString> readAllText(jsg::Lock& js) KJ_WARN_UNUSED_RESULT;\n\n  // Reads all the contents of the file as a Uint8Array.\n  kj::OneOf<FsError, jsg::BufferSource> readAllBytes(jsg::Lock& js) KJ_WARN_UNUSED_RESULT;\n\n  // Reads data from the file at the given offset into the given buffer.\n  virtual uint32_t read(jsg::Lock& js, uint32_t offset, kj::ArrayPtr<kj::byte> buffer) const = 0;\n\n  // Replaces the full contents of the file with the given data.\n  // Equivalent to resize(js, data.size()) followed by write(js, 0, data).\n  kj::OneOf<FsError, uint32_t> writeAll(\n      jsg::Lock& js, kj::ArrayPtr<const kj::byte> data) KJ_WARN_UNUSED_RESULT {\n    KJ_IF_SOME(err, resize(js, data.size())) {\n      return err;\n    }\n    return write(js, 0, data);\n  }\n\n  // Replaces the full contents of the file with the given data.\n  // Equivalent to resize(js, data.size()) followed by write(js, 0, data).\n  kj::OneOf<FsError, uint32_t> writeAll(jsg::Lock& js, kj::StringPtr data) KJ_WARN_UNUSED_RESULT {\n    return writeAll(js, data.asBytes());\n  }\n\n  // Writes data to the file at the given offset. Returns the number of bytes\n  // written. Returns the number of bytes written. If the file is not writable,\n  // this will throw an exception. If the offset is greater than the current\n  // size of the file, the file may be resized to accommodate the new data or\n  // an exception may be thrown (depending on the underlying implementation).\n  virtual kj::OneOf<FsError, uint32_t> write(\n      jsg::Lock& js, uint32_t offset, kj::ArrayPtr<const kj::byte> data) KJ_WARN_UNUSED_RESULT = 0;\n\n  kj::OneOf<FsError, uint32_t> write(\n      jsg::Lock& js, uint32_t offset, kj::StringPtr data) KJ_WARN_UNUSED_RESULT {\n    return write(js, offset, data.asBytes());\n  }\n\n  // Fill the file with the given value from the given offset. This is more\n  // efficient that writing the same value as it does not require any allocations.\n  virtual kj::Maybe<FsError> fill(\n      jsg::Lock& js, kj::byte val, kj::Maybe<uint32_t> offset = kj::none) KJ_WARN_UNUSED_RESULT = 0;\n\n  // Resize the file allocation. Note that this is potentially an expensive\n  // operation as it requires allocating a new internal buffer and copying\n  // the data. If the size is smaller than the current size, the contents of\n  // the fill will be truncated. If the size is larger than the current size,\n  // the new contents of the file will be filled with zeroes.\n  virtual kj::Maybe<FsError> resize(jsg::Lock& js, uint32_t size) KJ_WARN_UNUSED_RESULT = 0;\n\n  // Creates a new readable/writable in-memory file. This file will not be\n  // initially included in a directory. To add it to a directory, use the\n  // add method. If the file is not added, it will be deleted\n  // when the handle is dropped. If size is given, the file will be initially\n  // filled with zeroes up to the given size, otherwise the file will be empty.\n  // The contents of the file will be tracked and counted towards the isolate\n  // external memory usage.\n  // If size is not given, the file will be empty. If the intent is to perform\n  // multiple writes to the file, it is recommended to specify a size up front\n  // or to resize the file as appropriate to account for the expected writes.\n  // This will avoid each individual write causing a reallocation of the\n  // internal buffer to accommodate the new data.\n  static kj::Rc<File> newWritable(\n      jsg::Lock& js, kj::Maybe<uint32_t> size = kj::none) KJ_WARN_UNUSED_RESULT;\n\n  // Creates a new readable in-memory file wrapping the given data. The file\n  // does not take ownership of the data and the data must remain valid for the\n  // lifetime of the file. The file will be read-only. It will not be initially\n  // included in a directory. The contents of the file will not be tracked and\n  // will not count towards the isolate external memory usage.\n  static kj::Rc<File> newReadable(kj::ArrayPtr<const kj::byte> data) KJ_WARN_UNUSED_RESULT;\n\n  virtual kj::StringPtr jsgGetMemoryName() const = 0;\n  virtual size_t jsgGetMemorySelfSize() const = 0;\n  virtual void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const = 0;\n\n  // Creates a copy of this file.\n  virtual kj::OneOf<FsError, kj::Rc<File>> clone(jsg::Lock& js) KJ_WARN_UNUSED_RESULT = 0;\n\n  // Replaces the contents of this file with the given file if possible.\n  // If this file is read-only, an exception will be thrown.\n  virtual kj::Maybe<FsError> replace(jsg::Lock& js, kj::Rc<File> file) KJ_WARN_UNUSED_RESULT = 0;\n\n  // Returns a UUID that uniquely identifies this fs node. The value stable across\n  // multiple calls to getUniqueId() on the same node, but is not guaranteed to\n  // be stable across worker restarts. This is primarily useful for implementing\n  // the FileSystemHandle.getUniqueId() API, which is not yet fully standardized\n  // but is being implemented and has Web Platform Tests.\n  virtual kj::StringPtr getUniqueId(jsg::Lock&) const = 0;\n\n  // Ensures that the symlink instance itself is counted towards the isolate\n  // memory limit.\n  virtual void countTowardsIsolateLimit(jsg::Lock& js) const {\n    // Non-op by default.\n  };\n};\n\n// A directory in the virtual file system. If the directory is read-only,\n// then the mutation methods will throw an exception.\nclass Directory: public kj::Refcounted {\n public:\n  // Returns the metadata for this node.\n  virtual kj::Maybe<kj::OneOf<FsError, Stat>> stat(\n      jsg::Lock& js, kj::PathPtr ptr) KJ_WARN_UNUSED_RESULT = 0;\n  Stat stat(jsg::Lock& js) KJ_WARN_UNUSED_RESULT {\n    // In this case, stat is guaranteed to succeed, so skip the error checking.\n    return KJ_ASSERT_NONNULL(stat(js, nullptr)).get<Stat>();\n  }\n\n  // Return the number of entries in this directory. If typeFilter is provided,\n  // only entries matching the given type will be counted.\n  virtual size_t count(\n      jsg::Lock& js, kj::Maybe<FsType> typeFilter = kj::none) KJ_WARN_UNUSED_RESULT = 0;\n\n  // Provides a simple iterator iterface over the directory entries. It is important\n  // to only use these iterators while holding the isolate lock as entries may have\n  // their refcounts incremented and decremented while iterating. To avoid issues using\n  // the iterators using idiomatic C++ syntax, we don't require passing the jsg::Lock&\n  // to the begin() and end() methods so it is important to ensure they are only called\n  // while holding the lock.\n  using Item = kj::OneOf<kj::Rc<File>, kj::Rc<Directory>, kj::Rc<SymbolicLink>>;\n  using Entry = kj::HashMap<kj::String, Item>::Entry;\n  virtual Entry* begin() = 0;\n  virtual Entry* end() = 0;\n  virtual const Entry* begin() const = 0;\n  virtual const Entry* end() const = 0;\n\n  struct OpenOptions {\n    // When set, if the path does not exist, we will attempt to create the\n    // node as the specified type. Type must be one of either FILE or DIRECTORY.\n    // Specifying SYMBOLICLINK as the type will throw an exception.\n    kj::Maybe<FsType> createAs;\n\n    // If the path points to a symbolic link, by default the link will be followed\n    // such that if the link points to a valid node, that node will be returned.\n    // If followLinks is false, however, the symbolic link itself will be returned.\n    bool followLinks = true;\n  };\n\n  // Tries opening the file or directory at the given path. If the node does\n  // not exist, and createAs is provided specifying a create mode, the node\n  // will be created as the specified type; otherwise kj::none is returned\n  // if the node does not exist. If the directory is read only and create is\n  // specified, an exception will be thrown.\n  // The path must be relative to the this directory.\n  // Note that when creating files using this method, the newly created file\n  // will have a size of 0 bytes. The file will be writable but each write\n  // could end up causing a new allocation. It the intent is to perform\n  // multiple writes, it is recommended to resize the file to the expected\n  // size up front or to use the newWritable method to create a file with the\n  // expected size and add it into the target directory.\n  //\n  // Note that tryOpen doubles as both an existence check and a stat operation.\n  // When the createAs parameter is not specified, the method will return\n  // kj::none if the file or directory does not exist.\n  //\n  // If the path identifies a symbolic link, the symbolic link will be resolved\n  // and the target, if it exists, will be returned. If the target does not exist,\n  // kj::none will be returned.\n  virtual kj::Maybe<kj::OneOf<FsError, kj::Rc<File>, kj::Rc<Directory>, kj::Rc<SymbolicLink>>>\n  tryOpen(jsg::Lock& js,\n      kj::PathPtr path,\n      OpenOptions options = {kj::none, true}) KJ_WARN_UNUSED_RESULT = 0;\n\n  // Attempts to move the given file or directory this directory with the given\n  // name. If the name already exists an exception will be thrown. The name must\n  // not contain any path separators. If this directory is read only, an exception\n  // will be thrown.\n  virtual kj::Maybe<FsError> add(\n      jsg::Lock& js, kj::StringPtr name, Item entry) KJ_WARN_UNUSED_RESULT = 0;\n\n  struct RemoveOptions {\n    // If true and the node is a directory, remove all entries in the directory.\n    bool recursive = false;\n  };\n\n  // Tries to remove the file, directory, or symlink at the given path. If the\n  // node does not exist, false will be returned. If the node is a directory and\n  // the recursive option is not set, an exception will be thrown if the directory\n  // is not empty. If the directory is read only, an exception will be thrown.\n  // If the node is a file, it will be removed regardless of the recursive option\n  // if the directory is not read only.\n  // If the node is a symlink, the symlink will be removed but the target will\n  // be left intact.\n  // The path must be relative to the current directory.\n  // Note that this method will only remove the node from this directory. If the\n  // node has references in other directories, those will not be removed.\n  // If the target is a symbolic link, the symbolic link will be removed but\n  // the target will not be removed.\n  virtual kj::OneOf<FsError, bool> remove(\n      jsg::Lock& js, kj::PathPtr path, RemoveOptions options = {false}) KJ_WARN_UNUSED_RESULT = 0;\n\n  virtual kj::StringPtr jsgGetMemoryName() const = 0;\n  virtual size_t jsgGetMemorySelfSize() const = 0;\n  virtual void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const = 0;\n\n  // Creates a new readable/writable in-memory directory. This directory will\n  // not be initially included in a directory. To add it to a directory, use the\n  // add method. If the directory is not added, it will be deleted\n  // when the handle is dropped.\n  static kj::Rc<Directory> newWritable() KJ_WARN_UNUSED_RESULT;\n\n  // As a utility in some cases, we need the ability to create empty read-only\n  // directories.\n  static kj::Rc<Directory> newEmptyReadonly() KJ_WARN_UNUSED_RESULT;\n\n  // Variation of newWritable that ensures the Directory instance itself is\n  // counted towwards the isolate memory limit. This should be the typical\n  // case for directories created by user code, such as when creating a\n  // temporary directory under /tmp. This should not be used for directories\n  // that are created by the runtime that aren't directly under the users\n  // control, such as the /tmp directory itself.\n  static kj::Rc<Directory> newWritable(jsg::Lock& js) KJ_WARN_UNUSED_RESULT;\n\n  // Used to build a new read-only directory. All files and directories added\n  // may or may not be writable. The directory will not be initially included\n  // in a directory. To add it to a directory, use the add method.\n  class Builder {\n   public:\n    Builder() = default;\n    KJ_DISALLOW_COPY_AND_MOVE(Builder);\n\n    // Adds a file or directory to the directory. The name must not contain any\n    // path separators. If the name already exists an exception is thrown.\n    void add(kj::StringPtr name, kj::OneOf<kj::Rc<File>, kj::Rc<Directory>> fileOrDirectory);\n\n    // Adds a directory builder to the directory. This is used to incrementally build\n    // up read-only directories. When finish is called, the directory will be\n    // finalized. The name must not contain any path separators.\n    void add(kj::StringPtr name, kj::Own<Directory::Builder> dir);\n\n    // Adds a file or directory at the given path. The path may contain path separators.\n    // Subdirectories will be created as needed (as read-only directories).\n    void addPath(kj::PathPtr path, kj::OneOf<kj::Rc<File>, kj::Rc<Directory>> fileOrDirectory);\n\n    // Finalizes and returns the directory. The directory will not be writable.\n    // The directory will not be initially included in a directory. To add it\n    // to a directory, use the add method.\n    kj::Rc<Directory> finish() KJ_WARN_UNUSED_RESULT;\n\n    using Map = kj::HashMap<kj::String,\n        kj::OneOf<kj::Rc<File>, kj::Rc<Directory>, kj::Own<Directory::Builder>>>;\n    using Entry = Map::Entry;\n\n   private:\n    Map entries;\n  };\n\n  // Returns a UUID that uniquely identifies this fs node. The value stable across\n  // multiple calls to getUniqueId() on the same node, but is not guaranteed to\n  // be stable across worker restarts. This is primarily useful for implementing\n  // the FileSystemHandle.getUniqueId() API, which is not yet fully standardized\n  // but is being implemented and has Web Platform Tests.\n  virtual kj::StringPtr getUniqueId(jsg::Lock&) const = 0;\n\n  // Ensures that the directory instance itself is counted towards the isolate\n  // memory limit. Not every directory needs to be counted so we don't do this\n  // by default automatically for every Directory instance.\n  virtual void countTowardsIsolateLimit(jsg::Lock& js) const {\n    // Non-op by default.\n  }\n};\n\n// The equivalent to a symbolic link. A symlink holds a reference to the\n// virtual file system and a target path. The target node may or may not\n// exist, can be deleted and recreated at any time and the symlink will\n// remain valid.\nclass SymbolicLink final: public kj::Refcounted {\n public:\n  SymbolicLink(kj::Rc<Directory> root, kj::Path targetPath)\n      : root(kj::mv(root)),\n        targetPath(kj::mv(targetPath)) {}\n  KJ_DISALLOW_COPY_AND_MOVE(SymbolicLink);\n\n  // Gets the stat for the symbolic link itself.\n  Stat stat(jsg::Lock& js) KJ_WARN_UNUSED_RESULT;\n\n  // Resolves the symbolic link into a file or directory.\n  kj::Maybe<kj::OneOf<FsError, kj::Rc<File>, kj::Rc<Directory>>> resolve(\n      jsg::Lock& js) KJ_WARN_UNUSED_RESULT;\n\n  // Returns the target path of the symbolic link.\n  kj::PathPtr getTargetPath() const KJ_WARN_UNUSED_RESULT {\n    return targetPath;\n  }\n\n  jsg::Url getTargetUrl() const KJ_WARN_UNUSED_RESULT;\n\n  // Returns a UUID that uniquely identifies this fs node. The value stable across\n  // multiple calls to getUniqueId() on the same node, but is not guaranteed to\n  // be stable across worker restarts. This is primarily useful for implementing\n  // the FileSystemHandle.getUniqueId() API, which is not yet fully standardized\n  // but is being implemented and has Web Platform Tests.\n  kj::StringPtr getUniqueId(jsg::Lock&) const;\n\n  // Ensures that the symlink instance itself is counted towards the isolate\n  // memory limit.\n  void countTowardsIsolateLimit(jsg::Lock& js) const;\n\n private:\n  kj::Rc<Directory> root;\n  kj::Path targetPath;\n  mutable kj::Maybe<kj::String> maybeUniqueId;\n  mutable kj::Maybe<jsg::ExternalMemoryAdjustment> maybeMemoryAdjustment;\n};\n\nusing FsNode = kj::OneOf<kj::Rc<File>, kj::Rc<Directory>, kj::Rc<SymbolicLink>>;\nusing FsNodeWithError = kj::OneOf<FsError, kj::Rc<File>, kj::Rc<Directory>, kj::Rc<SymbolicLink>>;\nclass FsMap;\n\n// The virtual file system interface. This is the main entry point for accessing the vfs.\n// It is important to always destroy the VirtualFileSystem instance under the isolate lock.\n// The VFS holds a table of Refcounted objects (File, Directory, SymbolicLink) that can only\n// be safely destroyed under the isolate lock.\nclass VirtualFileSystem {\n public:\n  virtual ~VirtualFileSystem() noexcept(false) {}\n\n  // The observer interface is used to allow the runtime to observe opening\n  // or closing of file descriptors in order to allow the runtime to keep\n  // an eye on the number of open file descriptors and take action if needed.\n  class Observer {\n   public:\n    virtual ~Observer() = default;\n\n    // openFds is the number of currently open file descriptors, including\n    // the one that was just opened. totalFds is the total number of file\n    // descriptors that have been opened total.\n    virtual void onOpen(size_t openFds, size_t totalFds) const {\n      // By default, do nothing.\n    }\n\n    // openFds is the number of currently open file descriptors, excluding\n    // the one that was just closed. totalFds is the total number of file\n    // descriptors that have been opened total.\n    virtual void onClose(size_t openFdCount, size_t totalFds) const {\n      // By default, do nothing.\n    }\n\n    // Called when the total maximum number of file descriptors the worker\n    // is allowed to open is reached. After this point, the worker will no\n    // longer be allowed to open any new file descriptors.\n    virtual void onMaxFds(size_t openFdCount) const {\n      // By default, do nothing.\n    }\n  };\n\n  // The root of the virtual file system.\n  virtual kj::Rc<Directory> getRoot(jsg::Lock& js) const KJ_WARN_UNUSED_RESULT = 0;\n\n  struct ResolveOptions {\n    bool followLinks = true;\n  };\n\n  // Resolves the given file URL into a file or directory.\n  kj::Maybe<FsNodeWithError> resolve(jsg::Lock& js,\n      const jsg::Url& url,\n      ResolveOptions options = {true}) const KJ_WARN_UNUSED_RESULT;\n\n  // Resolves the given file URL into metadata for a file or directory.\n  kj::Maybe<kj::OneOf<FsError, Stat>> resolveStat(\n      jsg::Lock& js, const jsg::Url& url) const KJ_WARN_UNUSED_RESULT;\n\n  // Creates a new symbolic link to the given target path. The target path\n  // does not need to exist.\n  kj::Rc<SymbolicLink> newSymbolicLink(\n      jsg::Lock& js, const jsg::Url& url) const KJ_WARN_UNUSED_RESULT;\n\n  // Return the configured root paths for the bundle, temp, and dev directories.\n  virtual const jsg::Url& getBundleRoot() const KJ_WARN_UNUSED_RESULT = 0;\n  virtual const jsg::Url& getTmpRoot() const KJ_WARN_UNUSED_RESULT = 0;\n  virtual const jsg::Url& getDevRoot() const KJ_WARN_UNUSED_RESULT = 0;\n\n  // Get the current virtual file system for the current isolate lock.\n  static const VirtualFileSystem& current(jsg::Lock&) KJ_WARN_UNUSED_RESULT;\n\n  // ==========================================================================\n  // File Descriptor support\n\n  struct OpenOptions {\n    // Open the file descriptor for reading.\n    bool read = true;\n    // Open the file descriptor for writing.\n    bool write = false;\n    // Open the file descriptor for appending. Ignored if write is false.\n    bool append = false;\n\n    // If true, opening the path will fail if it already exists.\n    bool exclusive = false;\n\n    // If true, and the destination is a symbolic link, the link will be\n    // followed such that the file descriptor is opened on the target\n    // of the symbolic link. If false, the file descriptor will be opened\n    // on the symbolic link itself.\n    bool followLinks = true;\n  };\n\n  // Represents an opened file descriptor.\n  struct OpenedFile: public kj::Refcounted {\n    // The file descriptor for the opened file.\n    int fd;\n    // The file descriptor was opened for reading.\n    bool read;\n    // The file descriptor was opened for writing.\n    bool write;\n    // The file descriptor was opened for appending (ignored if write is false).\n    bool append;\n    // The actual file, directory, or symlink that was opened.\n    FsNode node;\n\n    OpenedFile(int fd, bool read, bool write, bool append, FsNode node)\n        : fd(fd),\n          read(read),\n          write(write),\n          append(append),\n          node(kj::mv(node)) {}\n\n    // When reading from or writing to the file, if an offset is not\n    // explicitly given then the offset will be set to the current\n    // position in the file.\n    uint32_t position = 0;\n  };\n\n  enum class Stdio {\n    IN,\n    OUT,\n    ERR,\n  };\n  virtual kj::Rc<OpenedFile> getStdio(jsg::Lock& js, Stdio stdio) const KJ_WARN_UNUSED_RESULT = 0;\n\n  // Attempts to open a file descriptor for the given file URL. It's critical\n  // to understand that the file descriptor table is shared for the entire\n  // worker. This means that if a file descriptor is opened, it will remain\n  // open until it is closed or the worker is terminated. If too many file\n  // descriptors are left open the worker may run out of file descriptors and\n  // fail to open new files. Additionally, opening too many file descriptors\n  // may cause a production worker to be condemned in the system. There is a\n  // strict upper limit on the number of file descriptors that can be opened\n  // total. Once that limit is reached, all subsequent attempts to open a\n  // file descriptor will fail.\n  //\n  // If the file cannot be opened or created, an exception will be thrown.\n  virtual kj::OneOf<FsError, kj::Rc<OpenedFile>> openFd(jsg::Lock& js,\n      const jsg::Url& url,\n      OpenOptions options = {true, false, false, false, true}) const KJ_WARN_UNUSED_RESULT = 0;\n\n  // Closes the given file descriptor. This is a no-op if the file descriptor is not open.\n  // Using an int fd is not super nice but it is the most compatible with the node:fs\n  // API and posix in general.\n  virtual void closeFd(jsg::Lock& js, int fd) const = 0;\n\n  // Returns an opaque RAII handle that wraps a file descriptor.\n  // When it is dropped, the file descriptor will be closed. The handle\n  // holds a weak reference to the VirtualFileSystem so that, on the off\n  // chance the VirtualFileSystem is destroyed while the handle is still\n  // alive, destruction of the handle will become a no-op.\n  virtual kj::Own<void> wrapFd(jsg::Lock& js, int fd) const KJ_WARN_UNUSED_RESULT = 0;\n\n  // Attempts to get the opened file, directory, or symlink for the given file descriptor.\n  // Returns kj::none if the fd is not opened/known.\n  virtual kj::Maybe<kj::Rc<OpenedFile>> tryGetFd(\n      jsg::Lock& js, int fd) const KJ_WARN_UNUSED_RESULT = 0;\n\n  // Locks are used by the web file system to ensure that certain mutation operations\n  // are not permitted while a lock is held. These are not true locks, they are more\n  // like simple refcounts. While the refcount is greater than 0, the lock is held.\n  // The lock is released when the returned kj::Own<void> is dropped.\n  virtual kj::Own<void> lock(\n      jsg::Lock& js, const jsg::Url& locator) const KJ_WARN_UNUSED_RESULT = 0;\n  virtual bool isLocked(jsg::Lock& js, const jsg::Url& locator) const KJ_WARN_UNUSED_RESULT = 0;\n};\n\nkj::Own<VirtualFileSystem> newVirtualFileSystem(kj::Own<FsMap> fsMap,\n    kj::Rc<Directory>&& root,\n    kj::Own<VirtualFileSystem::Observer> observer = kj::heap<VirtualFileSystem::Observer>())\n    KJ_WARN_UNUSED_RESULT;\n\n// The FsMap is a configurable mapping of built-in \"known\" file system\n// paths to user-configurable locations. It is used to allow user-specified\n// \"mount\" points for certain built-in file system paths. For example, the\n// WorkerFileSystem exposes a built-in bundle path that is located at /bundle\n// by default. The FsMap allows the user to specify a different name for this\n// path, such as /mybundle, or even nested paths like /mybundle/a/b/c/modules/.\n// To add a new known root, add the name to the KNOWN_VFS_ROOTS macro and\n// specify the default path. The relevant API methods on the FsMap class\n// will be generated via the template.\n#define KNOWN_VFS_ROOTS(V)                                                                         \\\n  V(Bundle, \"file:///bundle/\")                                                                     \\\n  V(Temp, \"file:///tmp/\")                                                                          \\\n  V(Dev, \"file:///dev/\")\n\nclass FsMap final {\n public:\n  FsMap() = default;\n  KJ_DISALLOW_COPY_AND_MOVE(FsMap);\n\n#define DEFINE_METHODS_FOR_ROOTS(name, _)                                                          \\\n  static const jsg::Url kDefault##name##Path;                                                      \\\n  const jsg::Url& get##name##Root() const {                                                        \\\n    return maybe##name##Root.orDefault(kDefault##name##Path);                                      \\\n  }                                                                                                \\\n  const kj::Path get##name##Path() const {                                                         \\\n    auto path = kj::str(get##name##Root().getPathname().slice(1));                                 \\\n    return kj::Path::parse(path);                                                                  \\\n  }                                                                                                \\\n  void set##name##Root(jsg::Url url) {                                                             \\\n    KJ_REQUIRE(url.getProtocol() == \"file:\"_kj, \"url must be a file URL\");                         \\\n    maybe##name##Root = kj::mv(url);                                                               \\\n  }                                                                                                \\\n  void set##name##Root(kj::StringPtr path) {                                                       \\\n    auto url = KJ_REQUIRE_NONNULL(jsg::Url::tryParse(path, \"file:///\"_kj), \"invalid path\");        \\\n    set##name##Root(kj::mv(url));                                                                  \\\n  }\n\n  KNOWN_VFS_ROOTS(DEFINE_METHODS_FOR_ROOTS)\n\n#undef DEFINE_METHODS_FOR_ROOTS\n\n private:\n#define DEFINE_FIELDS_FOR_ROOTS(name, _) kj::Maybe<jsg::Url> maybe##name##Root;\n  KNOWN_VFS_ROOTS(DEFINE_FIELDS_FOR_ROOTS)\n#undef DEFINE_FIELDS_FOR_ROOTS\n};\n\n// An RAII object that stores the temporary directory items.\n// Instances can either live on the stack (in which case they\n// will be set in a thread-local and hasCurrent() will return\n// true, or they can be created on the heap and held (e.g. in\n// IoContext).\nclass TmpDirStoreScope final {\n public:\n  static bool hasCurrent();\n  static TmpDirStoreScope& current();\n  TmpDirStoreScope(kj::Maybe<kj::Badge<TmpDirStoreScope>> guard = kj::none);\n  KJ_DISALLOW_COPY_AND_MOVE(TmpDirStoreScope);\n  ~TmpDirStoreScope() noexcept(false);\n\n  static kj::Own<TmpDirStoreScope> create();\n\n  kj::Rc<Directory> getDirectory() const {\n    return dir.addRef();\n  }\n\n  kj::PathPtr getCwd() const {\n    return kj::PathPtr(cwd);\n  }\n\n  void setCwd(kj::Path newCwd) {\n    cwd = kj::mv(newCwd);\n  }\n\n private:\n  mutable kj::Rc<Directory> dir;\n  kj::Path cwd;\n  bool onStack = false;\n};\n\n// A scope utility that is used to guard against infinite recursion when\n// resolving symbolic links. This should only ever be stack allocated and\n// should never be shared outside of the current execution scope.\nclass SymbolicLinkRecursionGuardScope final {\n public:\n  SymbolicLinkRecursionGuardScope();\n  ~SymbolicLinkRecursionGuardScope() noexcept(false);\n  KJ_DISALLOW_COPY_AND_MOVE(SymbolicLinkRecursionGuardScope);\n\n  // Whenever a symbolic link is resolved, it needs to be checked against\n  // the recursion guard. If the link has already been seen, an exception\n  // will be thrown. If the link has not been seen, it will be recorded\n  // as seen so that it can be checked against in the future.\n  static kj::Maybe<FsError> checkSeen(SymbolicLink* link) KJ_WARN_UNUSED_RESULT;\n\n private:\n  kj::HashSet<SymbolicLink*> linksSeen;\n};\n\n// Every Worker instance has its own virtual filesystem. At a minimum, this\n// filesystem contains the worker's own bundled modules/files and a temporary\n// in-memory directory for the worker to use. The filesystem is not shared\n// between workers. The bundle delegate is a virtual directory delegate that\n// provides the directory structure for the worker's bundle.\nkj::Own<VirtualFileSystem> newWorkerFileSystem(kj::Own<FsMap> fsMap,\n    kj::Rc<Directory> bundleDirectory,\n    kj::Own<VirtualFileSystem::Observer> observer = kj::heap<VirtualFileSystem::Observer>())\n    KJ_WARN_UNUSED_RESULT;\n\n// Exposed only for testing purposes.\nkj::Rc<Directory> getTmpDirectoryImpl() KJ_WARN_UNUSED_RESULT;\n\n// Returns a directory that is lazily loaded on first access.\nkj::Rc<Directory> getLazyDirectoryImpl(\n    kj::Function<kj::Rc<Directory>()> func) KJ_WARN_UNUSED_RESULT;\n\nkj::Rc<File> getDevNull() KJ_WARN_UNUSED_RESULT;\nkj::Rc<File> getDevZero() KJ_WARN_UNUSED_RESULT;\nkj::Rc<File> getDevFull() KJ_WARN_UNUSED_RESULT;\nkj::Rc<File> getDevRandom() KJ_WARN_UNUSED_RESULT;\nkj::Rc<Directory> getDevDirectory() KJ_WARN_UNUSED_RESULT;\n\n// Helper functions for current working directory management\nkj::Maybe<kj::PathPtr> getCurrentWorkingDirectory() KJ_WARN_UNUSED_RESULT;\nbool setCurrentWorkingDirectory(kj::Path newCwd) KJ_WARN_UNUSED_RESULT;\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/worker-interface.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"worker-interface.h\"\n\n#include <workerd/util/http-util.h>\n\n#include <kj/debug.h>\n\nusing kj::byte;\nusing kj::uint;\n\nnamespace workerd {\n\nnamespace {\n// A WorkerInterface that delays requests until some promise resolves, then forwards them to the\n// interface the promise resolved to.\nclass PromisedWorkerInterface final: public WorkerInterface {\n public:\n  PromisedWorkerInterface(kj::Promise<kj::Own<WorkerInterface>> promise)\n      : promise(promise.then([this](kj::Own<WorkerInterface> result) { worker = kj::mv(result); })\n                    .fork()) {}\n\n  kj::Promise<void> request(kj::HttpMethod method,\n      kj::StringPtr url,\n      const kj::HttpHeaders& headers,\n      kj::AsyncInputStream& requestBody,\n      Response& response) override {\n    KJ_IF_SOME(w, worker) {\n      co_await w->request(method, url, headers, requestBody, response);\n    } else {\n      co_await promise;\n      co_await KJ_ASSERT_NONNULL(worker)->request(method, url, headers, requestBody, response);\n    }\n  }\n\n  kj::Promise<void> connect(kj::StringPtr host,\n      const kj::HttpHeaders& headers,\n      kj::AsyncIoStream& connection,\n      ConnectResponse& response,\n      kj::HttpConnectSettings settings) override {\n    KJ_IF_SOME(w, worker) {\n      co_await w->connect(host, headers, connection, response, kj::mv(settings));\n    } else {\n      co_await promise;\n      co_await KJ_ASSERT_NONNULL(worker)->connect(\n          host, headers, connection, response, kj::mv(settings));\n    }\n  }\n\n  kj::Promise<void> prewarm(kj::StringPtr url) override {\n    KJ_IF_SOME(w, worker) {\n      co_return co_await w->prewarm(url);\n    } else {\n      co_await promise;\n      co_return co_await KJ_ASSERT_NONNULL(worker)->prewarm(url);\n    }\n  }\n\n  kj::Promise<ScheduledResult> runScheduled(kj::Date scheduledTime, kj::StringPtr cron) override {\n    KJ_IF_SOME(wrk, worker) {\n      co_return co_await wrk->runScheduled(scheduledTime, cron);\n    } else {\n      co_await promise;\n      co_return co_await KJ_ASSERT_NONNULL(worker)->runScheduled(scheduledTime, cron);\n    }\n  }\n\n  kj::Promise<AlarmResult> runAlarm(kj::Date scheduledTime, uint32_t retryCount) override {\n    KJ_IF_SOME(w, worker) {\n      co_return co_await w->runAlarm(scheduledTime, retryCount);\n    } else {\n      co_await promise;\n      co_return co_await KJ_ASSERT_NONNULL(worker)->runAlarm(scheduledTime, retryCount);\n    }\n  }\n\n  kj::Promise<CustomEvent::Result> customEvent(kj::Own<CustomEvent> event) override {\n    KJ_IF_SOME(w, worker) {\n      co_return co_await w->customEvent(kj::mv(event));\n    } else {\n      try {\n        co_await promise;\n      } catch (...) {\n        // Due to the exception, we're going to discard our CustomEvent. But we should tell it\n        // about why it failed first. This is important for JsRpcSessionCustomEvent in\n        // particular, as it needs to resolve the RPC client to the correct error.\n        auto exception = kj::getCaughtExceptionAsKj();\n        event->failed(exception);\n        kj::throwFatalException(kj::mv(exception));\n      }\n      co_return co_await KJ_ASSERT_NONNULL(worker)->customEvent(kj::mv(event));\n    }\n  }\n\n private:\n  kj::ForkedPromise<void> promise;\n  kj::Maybe<kj::Own<WorkerInterface>> worker;\n};\n}  // namespace\n\nkj::Own<WorkerInterface> newPromisedWorkerInterface(kj::Promise<kj::Own<WorkerInterface>> promise) {\n  return kj::heap<PromisedWorkerInterface>(kj::mv(promise));\n}\n\nkj::Own<kj::HttpClient> asHttpClient(kj::Own<WorkerInterface> workerInterface) {\n  return kj::newHttpClient(*workerInterface).attach(kj::mv(workerInterface));\n}\n\n// =======================================================================================\nnamespace {\n// A Revocable WebSocket wrapper, revoked when revokeProm rejects\nclass RevocableWebSocket final: public kj::WebSocket {\n public:\n  RevocableWebSocket(kj::Own<WebSocket> ws, kj::Promise<void> revokeProm)\n      : ws(kj::mv(ws)),\n        revokeProm(revokeProm\n                       .catch_([this](kj::Exception&& e) -> kj::Promise<void> {\n                         canceler.cancel(kj::cp(e));\n                         KJ_IF_SOME(ws, this->ws.tryGet<kj::Own<kj::WebSocket>>()) {\n                           (ws)->abort();\n                         }\n                         this->ws = kj::mv(e);\n                         return kj::READY_NOW;\n                       })\n                       .eagerlyEvaluate(nullptr)) {}\n\n  kj::Promise<void> send(kj::ArrayPtr<const byte> message) override {\n    return wrap<void>(getInner().send(message));\n  }\n  kj::Promise<void> send(kj::ArrayPtr<const char> message) override {\n    return wrap<void>(getInner().send(message));\n  }\n\n  kj::Promise<void> close(uint16_t code, kj::StringPtr reason) override {\n    return wrap<void>(getInner().close(code, reason));\n  }\n\n  void disconnect() override {\n    KJ_IF_SOME(ws, this->ws.tryGet<kj::Own<kj::WebSocket>>()) {\n      return (ws)->disconnect();\n    }\n  }\n\n  void abort() override {\n    KJ_IF_SOME(ws, this->ws.tryGet<kj::Own<kj::WebSocket>>()) {\n      return (ws)->abort();\n    }\n  }\n\n  kj::Promise<void> whenAborted() override {\n    return wrap<void>(getInner().whenAborted());\n  }\n\n  kj::Promise<Message> receive(size_t maxSize) override {\n    return wrap<Message>(getInner().receive(maxSize));\n  }\n\n  kj::Promise<void> pumpTo(WebSocket& other) override {\n    return wrap<void>(getInner().pumpTo(other));\n  }\n\n  kj::Maybe<kj::Promise<void>> tryPumpFrom(WebSocket& other) override {\n    return wrap<void>(other.pumpTo(getInner()));\n  }\n\n  kj::Maybe<kj::String> getPreferredExtensions(ExtensionsContext ctx) override {\n    return getInner().getPreferredExtensions(ctx);\n  };\n\n  uint64_t sentByteCount() override {\n    return 0;\n  }\n  uint64_t receivedByteCount() override {\n    return 0;\n  }\n\n private:\n  template <typename T>\n  kj::Promise<T> wrap(kj::Promise<T> prom) {\n    // just to fix the revocation promise return type, serves no purpose otherwise\n    return canceler.wrap(kj::mv(prom));\n  }\n\n  kj::WebSocket& getInner() {\n    KJ_SWITCH_ONEOF(ws) {\n      KJ_CASE_ONEOF(e, kj::Exception) {\n        kj::throwFatalException(kj::cp(e));\n      }\n      KJ_CASE_ONEOF(ws, kj::Own<kj::WebSocket>) {\n        return *ws.get();\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  kj::OneOf<kj::Exception, kj::Own<kj::WebSocket>> ws;\n  kj::Promise<void> revokeProm;\n  kj::Canceler canceler;\n};\n\n// A HttpResponse that can revoke long-running websocket connections started as part of the\n// response. Ordinary HTTP requests are not revoked.\nclass RevocableWebSocketHttpResponse final: public kj::HttpService::Response {\n public:\n  RevocableWebSocketHttpResponse(kj::HttpService::Response& inner, kj::Promise<void> revokeProm)\n      : inner(inner),\n        revokeProm(revokeProm.fork()) {}\n\n  kj::Own<kj::AsyncOutputStream> send(uint statusCode,\n      kj::StringPtr statusText,\n      const kj::HttpHeaders& headers,\n      kj::Maybe<uint64_t> expectedBodySize = kj::none) override {\n    return inner.send(statusCode, statusText, headers, expectedBodySize);\n  }\n\n  kj::Own<kj::WebSocket> acceptWebSocket(const kj::HttpHeaders& headers) override {\n    return kj::heap<RevocableWebSocket>(inner.acceptWebSocket(headers), revokeProm.addBranch());\n  }\n\n private:\n  kj::HttpService::Response& inner;\n  kj::ForkedPromise<void> revokeProm;\n};\n\n// A WorkerInterface that cancels WebSockets when revokeProm is rejected.\n// Currently only supports cancelling for upgrades.\nclass RevocableWebSocketWorkerInterface final: public WorkerInterface {\n public:\n  RevocableWebSocketWorkerInterface(WorkerInterface& worker, kj::Promise<void> revokeProm);\n  kj::Promise<void> request(kj::HttpMethod method,\n      kj::StringPtr url,\n      const kj::HttpHeaders& headers,\n      kj::AsyncInputStream& requestBody,\n      Response& response) override;\n  kj::Promise<void> connect(kj::StringPtr host,\n      const kj::HttpHeaders& headers,\n      kj::AsyncIoStream& connection,\n      ConnectResponse& response,\n      kj::HttpConnectSettings settings) override;\n  kj::Promise<void> prewarm(kj::StringPtr url) override;\n  kj::Promise<ScheduledResult> runScheduled(kj::Date scheduledTime, kj::StringPtr cron) override;\n  kj::Promise<AlarmResult> runAlarm(kj::Date scheduledTime, uint32_t retryCount) override;\n  kj::Promise<CustomEvent::Result> customEvent(kj::Own<CustomEvent> event) override;\n\n private:\n  WorkerInterface& worker;\n  kj::ForkedPromise<void> revokeProm;\n};\n\nkj::Promise<void> RevocableWebSocketWorkerInterface::request(kj::HttpMethod method,\n    kj::StringPtr url,\n    const kj::HttpHeaders& headers,\n    kj::AsyncInputStream& requestBody,\n    kj::HttpService::Response& response) {\n  auto wrappedResponse = kj::heap<RevocableWebSocketHttpResponse>(response, revokeProm.addBranch());\n  return worker.request(method, url, headers, requestBody, *wrappedResponse)\n      .attach(kj::mv(wrappedResponse));\n}\n\nkj::Promise<void> RevocableWebSocketWorkerInterface::connect(kj::StringPtr host,\n    const kj::HttpHeaders& headers,\n    kj::AsyncIoStream& connection,\n    ConnectResponse& response,\n    kj::HttpConnectSettings settings) {\n  KJ_UNIMPLEMENTED(\n      \"TODO(someday): RevocableWebSocketWorkerInterface::connect() should be implemented to \"\n      \"disconnect long-lived connections similar to how it treats WebSockets\");\n}\n\nRevocableWebSocketWorkerInterface::RevocableWebSocketWorkerInterface(\n    WorkerInterface& worker, kj::Promise<void> revokeProm)\n    : worker(worker),\n      revokeProm(revokeProm.fork()) {}\n\nkj::Promise<void> RevocableWebSocketWorkerInterface::prewarm(kj::StringPtr url) {\n  return worker.prewarm(url);\n}\n\nkj::Promise<WorkerInterface::ScheduledResult> RevocableWebSocketWorkerInterface::runScheduled(\n    kj::Date scheduledTime, kj::StringPtr cron) {\n  return worker.runScheduled(scheduledTime, cron);\n}\n\nkj::Promise<WorkerInterface::AlarmResult> RevocableWebSocketWorkerInterface::runAlarm(\n    kj::Date scheduledTime, uint32_t retryCount) {\n  return worker.runAlarm(scheduledTime, retryCount);\n}\n\nkj::Promise<WorkerInterface::CustomEvent::Result> RevocableWebSocketWorkerInterface::customEvent(\n    kj::Own<CustomEvent> event) {\n  return worker.customEvent(kj::mv(event));\n}\n\n}  // namespace\n\nkj::Own<WorkerInterface> newRevocableWebSocketWorkerInterface(\n    kj::Own<WorkerInterface> worker, kj::Promise<void> revokeProm) {\n  return kj::heap<RevocableWebSocketWorkerInterface>(*worker, kj::mv(revokeProm))\n      .attach(kj::mv(worker));\n}\n\n// =======================================================================================\n\nnamespace {\n\nclass ErrorWorkerInterface final: public WorkerInterface {\n public:\n  ErrorWorkerInterface(kj::Exception&& exception): exception(exception) {}\n\n  kj::Promise<void> request(kj::HttpMethod method,\n      kj::StringPtr url,\n      const kj::HttpHeaders& headers,\n      kj::AsyncInputStream& requestBody,\n      Response& response) override {\n    kj::throwFatalException(kj::mv(exception));\n  }\n\n  kj::Promise<void> connect(kj::StringPtr host,\n      const kj::HttpHeaders& headers,\n      kj::AsyncIoStream& connection,\n      ConnectResponse& response,\n      kj::HttpConnectSettings settings) override {\n    kj::throwFatalException(kj::mv(exception));\n  }\n\n  kj::Promise<void> prewarm(kj::StringPtr url) override {\n    // ignore\n    return kj::READY_NOW;\n  }\n\n  kj::Promise<ScheduledResult> runScheduled(kj::Date scheduledTime, kj::StringPtr cron) override {\n    kj::throwFatalException(kj::mv(exception));\n  }\n\n  kj::Promise<AlarmResult> runAlarm(kj::Date scheduledTime, uint32_t retryCount) override {\n    kj::throwFatalException(kj::mv(exception));\n  }\n\n  kj::Promise<CustomEvent::Result> customEvent(kj::Own<CustomEvent> event) override {\n    kj::throwFatalException(kj::mv(exception));\n  }\n\n private:\n  kj::Exception exception;\n};\n\n}  // namespace\n\nkj::Own<WorkerInterface> WorkerInterface::fromException(kj::Exception&& e) {\n  return kj::heap<ErrorWorkerInterface>(kj::mv(e));\n}\n\n// =======================================================================================\n\nRpcWorkerInterface::RpcWorkerInterface(capnp::HttpOverCapnpFactory& httpOverCapnpFactory,\n    capnp::ByteStreamFactory& byteStreamFactory,\n    rpc::EventDispatcher::Client dispatcher)\n    : httpOverCapnpFactory(httpOverCapnpFactory),\n      byteStreamFactory(byteStreamFactory),\n      dispatcher(kj::mv(dispatcher)) {}\n\nkj::Promise<void> RpcWorkerInterface::request(kj::HttpMethod method,\n    kj::StringPtr url,\n    const kj::HttpHeaders& headers,\n    kj::AsyncInputStream& requestBody,\n    Response& response) {\n  auto inner = httpOverCapnpFactory.capnpToKj(dispatcher.getHttpServiceRequest().send().getHttp());\n  auto promise = inner->request(method, url, headers, requestBody, response);\n  return promise.attach(kj::mv(inner));\n}\n\nkj::Promise<void> RpcWorkerInterface::connect(kj::StringPtr host,\n    const kj::HttpHeaders& headers,\n    kj::AsyncIoStream& connection,\n    ConnectResponse& tunnel,\n    kj::HttpConnectSettings settings) {\n  auto inner = httpOverCapnpFactory.capnpToKj(dispatcher.getHttpServiceRequest().send().getHttp());\n  auto promise = inner->connect(host, headers, connection, tunnel, kj::mv(settings));\n  return promise.attach(kj::mv(inner));\n}\n\nkj::Promise<void> RpcWorkerInterface::prewarm(kj::StringPtr url) {\n  auto req = dispatcher.prewarmRequest(capnp::MessageSize{url.size() / sizeof(capnp::word) + 4, 0});\n  req.setUrl(url);\n  return req.sendIgnoringResult();\n}\n\nkj::Promise<WorkerInterface::ScheduledResult> RpcWorkerInterface::runScheduled(\n    kj::Date scheduledTime, kj::StringPtr cron) {\n  auto req = dispatcher.runScheduledRequest();\n  req.setScheduledTime((scheduledTime - kj::UNIX_EPOCH) / kj::SECONDS);\n  req.setCron(cron);\n  return req.send().then([](auto resp) {\n    auto respResult = resp.getResult();\n    return WorkerInterface::ScheduledResult{\n      .retry = respResult.getRetry(), .outcome = respResult.getOutcome()};\n  });\n}\n\nkj::Promise<WorkerInterface::AlarmResult> RpcWorkerInterface::runAlarm(\n    kj::Date scheduledTime, uint32_t retryCount) {\n  auto req = dispatcher.runAlarmRequest();\n  req.setScheduledTime((scheduledTime - kj::UNIX_EPOCH) / kj::MILLISECONDS);\n  req.setRetryCount(retryCount);\n  return req.send().then([](auto resp) {\n    auto respResult = resp.getResult();\n    return WorkerInterface::AlarmResult{.retry = respResult.getRetry(),\n      .retryCountsAgainstLimit = respResult.getRetryCountsAgainstLimit(),\n      .outcome = respResult.getOutcome()};\n  });\n}\n\nkj::Promise<WorkerInterface::CustomEvent::Result> RpcWorkerInterface::customEvent(\n    kj::Own<CustomEvent> event) {\n  return event->sendRpc(httpOverCapnpFactory, byteStreamFactory, dispatcher).attach(kj::mv(event));\n}\n\n// ======================================================================================\nWorkerInterface::AlarmFulfiller::AlarmFulfiller(\n    kj::Own<kj::PromiseFulfiller<AlarmResult>> fulfiller)\n    : maybeFulfiller(kj::mv(fulfiller)) {}\n\nWorkerInterface::AlarmFulfiller::~AlarmFulfiller() noexcept(false) {\n  KJ_IF_SOME(fulfiller, getFulfiller()) {\n    fulfiller.reject(KJ_EXCEPTION(FAILED, \"AlarmFulfiller destroyed without resolution\"));\n  }\n}\n\nvoid WorkerInterface::AlarmFulfiller::fulfill(const AlarmResult& result) {\n  KJ_IF_SOME(fulfiller, getFulfiller()) {\n    fulfiller.fulfill(kj::cp(result));\n  }\n}\n\nvoid WorkerInterface::AlarmFulfiller::reject(const kj::Exception& e) {\n  KJ_IF_SOME(fulfiller, getFulfiller()) {\n    fulfiller.reject(kj::cp(e));\n  }\n}\n\nvoid WorkerInterface::AlarmFulfiller::cancel() {\n  KJ_IF_SOME(fulfiller, getFulfiller()) {\n    fulfiller.fulfill(AlarmResult{\n      .retry = false,\n      .outcome = EventOutcome::CANCELED,\n    });\n  }\n}\n\nkj::Maybe<kj::PromiseFulfiller<WorkerInterface::AlarmResult>&> WorkerInterface::AlarmFulfiller::\n    getFulfiller() {\n  KJ_IF_SOME(fulfiller, maybeFulfiller) {\n    if (fulfiller.get()->isWaiting()) {\n      return *fulfiller;\n    }\n  }\n\n  return kj::none;\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/worker-interface.capnp",
    "content": "# Copyright (c) 2017-2022 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n@0xf7958855f6746344;\n\nusing Cxx = import \"/capnp/c++.capnp\";\n$Cxx.namespace(\"workerd::rpc\");\n# We do not use `$Cxx.allowCancellation` because runAlarm() currently depends on blocking\n# cancellation.\n\nusing import \"/capnp/compat/http-over-capnp.capnp\".HttpMethod;\nusing import \"/capnp/compat/http-over-capnp.capnp\".HttpService;\nusing import \"/capnp/compat/byte-stream.capnp\".ByteStream;\nusing import \"/workerd/io/outcome.capnp\".EventOutcome;\nusing import \"/workerd/io/script-version.capnp\".ScriptVersion;\nusing import \"/workerd/io/trace.capnp\".TagValue;\nusing import \"/workerd/io/trace.capnp\".UserSpanData;\nusing import \"/workerd/io/frankenvalue.capnp\".Frankenvalue;\n\n# A 128-bit trace ID used to identify traces.\nstruct TraceId {\n  high @0 :UInt64;\n  low @1 :UInt64;\n}\n\n# InvocationSpanContext used to identify the current tracing context. Only used internally so far.\nstruct InvocationSpanContext {\n  # The 128-bit ID uniquely identifying a trace.\n  traceId @0 :TraceId;\n  # The 128-bit ID identifying a worker stage invocation within a trace.\n  invocationId @1 :TraceId;\n  # The 64-bit span ID identifying an individual span within a worker stage invocation.\n  spanId @2 :UInt64;\n}\n\n# Span context for a tail event – this is provided for each tail event.\nstruct SpanContext {\n  # The 128-bit ID uniquely identifying a trace.\n  traceId @0 :TraceId;\n  # spanId in which this event is handled\n  # for Onset and SpanOpen events this would be the parent span id\n  # for Outcome and SpanClose these this would be the span id of the opening Onset and SpanOpen events\n  # For Hibernate and Mark this would be the span under which they were emitted.\n  # This is only empty if:\n  #  1. This is an Onset event\n  #  2. We are not inheriting any SpanContext. (e.g. this is a cross-account service binding or a new top-level invocation)\n  info :union {\n    empty @1 :Void;\n    spanId @2 :UInt64;\n  }\n}\n\nstruct Trace @0x8e8d911203762d34 {\n  logs @0 :List(Log);\n  struct Log {\n    timestampNs @0 :Int64;\n\n    logLevel @1 :Level;\n    enum Level {\n      debug @0 $Cxx.name(\"debug_\");  # avoid collision with macro on Apple platforms\n      info @1;\n      log @2;\n      warn @3;\n      error @4;\n    }\n\n    message @2 :Text;\n  }\n\n  obsolete26 @26 :List(UserSpanData);\n  # spans are unavailable in full trace objects.\n\n  exceptions @1 :List(Exception);\n  struct Exception {\n    timestampNs @0 :Int64;\n    name @1 :Text;\n    message @2 :Text;\n    stack @3 :Text;\n  }\n\n  outcome @2 :EventOutcome;\n  scriptName @4 :Text;\n  scriptVersion @19 :ScriptVersion;\n  scriptId @23 :Text;\n\n  eventTimestampNs @5 :Int64;\n\n  eventInfo :union {\n    none @3 :Void;\n    fetch @6 :FetchEventInfo;\n    jsRpc @21 :JsRpcEventInfo;\n    scheduled @7 :ScheduledEventInfo;\n    alarm @9 :AlarmEventInfo;\n    queue @15 :QueueEventInfo;\n    custom @13 :CustomEventInfo;\n    email @16 :EmailEventInfo;\n    trace @18 :TraceEventInfo;\n    hibernatableWebSocket @20 :HibernatableWebSocketEventInfo;\n  }\n  struct FetchEventInfo {\n    method @0 :HttpMethod;\n    url @1 :Text;\n    cfJson @2 :Text;\n    # Empty string indicates missing cf blob\n    headers @3 :List(Header);\n    struct Header {\n      name @0 :Text;\n      value @1 :Text;\n    }\n  }\n\n  struct JsRpcEventInfo {\n    methodName @0 :Text;\n  }\n\n  struct ScheduledEventInfo {\n    scheduledTime @0 :Float64;\n    cron @1 :Text;\n  }\n\n  struct AlarmEventInfo {\n    scheduledTimeMs @0 :Int64;\n  }\n\n  struct QueueEventInfo {\n    queueName @0 :Text;\n    batchSize @1 :UInt32;\n  }\n\n  struct EmailEventInfo {\n    mailFrom @0 :Text;\n    rcptTo @1 :Text;\n    rawSize @2 :UInt32;\n  }\n\n  struct TraceEventInfo {\n    struct TraceItem {\n      scriptName @0 :Text;\n    }\n\n    traces @0 :List(TraceItem);\n  }\n\n  struct HibernatableWebSocketEventInfo {\n    type :union {\n      message @0 :Void;\n      close :group {\n        code @1 :UInt16;\n        wasClean @2 :Bool;\n      }\n      error @3 :Void;\n    }\n  }\n\n  struct CustomEventInfo {}\n\n  response @8 :FetchResponseInfo;\n  struct FetchResponseInfo {\n    statusCode @0 :UInt16;\n  }\n\n  cpuTime @10 :UInt64;\n  wallTime @11 :UInt64;\n\n  dispatchNamespace @12 :Text;\n  scriptTags @14 :List(Text);\n\n  entrypoint @22 :Text;\n  durableObjectId @27 :Text;\n  tailAttributes @28 :List(Attribute);\n\n  diagnosticChannelEvents @17 :List(DiagnosticChannelEvent);\n  struct DiagnosticChannelEvent {\n    timestampNs @0 :Int64;\n    channel @1 :Text;\n    message @2 :Data;\n  }\n\n  # Indicates how many tail stream events were dropped in total.\n  struct DroppedEvents {\n    count @0 :UInt32;\n  }\n\n  struct StreamDiagnosticsEvent {\n    # In the future, we plan to support several types of events here, for now only the dropped\n    # events diagnostic is supported.\n    diagnostic :union {\n      undefined @0 :Void;\n      droppedEvents @1 :DroppedEvents;\n    }\n  }\n\n  truncated @24 :Bool;\n  # Indicates that the trace was truncated due to reaching the maximum size limit.\n\n  enum ExecutionModel {\n    stateless @0;\n    durableObject @1;\n    workflow @2;\n  }\n  executionModel @25 :ExecutionModel;\n  # the execution model of the worker being traced. Can be stateless for a regular worker,\n  # durableObject for a DO worker or workflow for the upcoming Workflows feature.\n\n  # =====================================================================================\n  # Additional types for streaming tail workers\n\n  struct Attribute {\n    # An Attribute mark is used to add detail to a span over its lifetime.\n    # The Attribute struct can also be used to provide arbitrary additional\n    # properties for some other structs.\n    # Modeled after https://opentelemetry.io/docs/concepts/signals/traces/#attributes\n    name @0 :Text;\n    value @1 :List(TagValue);\n  }\n\n  struct Return {\n    # A Return mark is used to mark the point at which a span operation returned\n    # a value. For instance, when a fetch subrequest response is received, or when\n    # the fetch handler returns a Response. Importantly, it does not signal that the\n    # span has closed, which may not happen for some period of time after the return\n    # mark is recorded (e.g. due to things like waitUntils or waiting to fully ready\n    # the response body payload, etc). Not all spans will have a Return mark.\n    info :union {\n      empty @0 :Void;\n      fetch @1 :FetchResponseInfo;\n    }\n  }\n\n  struct SpanOpen {\n    # Marks the opening of a child span within the streaming tail session.\n    operationName @0 :Text;\n    spanId @1 :UInt64;\n    # id for the span being opened by this SpanOpen event.\n    info :union {\n      empty @2 :Void;\n      custom @3 :List(Attribute);\n      fetch @4 :FetchEventInfo;\n      jsRpc @5 :JsRpcEventInfo;\n    }\n  }\n\n  struct SpanClose {\n    # Marks the closing of a child span within the streaming tail session.\n    # Once emitted, no further mark events should occur within the closed\n    # span.\n    outcome @0 :EventOutcome;\n  }\n\n  struct Onset {\n    # The Onset and Outcome event types are special forms of SpanOpen and\n    # SpanClose that explicitly mark the start and end of the root span.\n    # A streaming tail session will always begin with an Onset event, and\n    # always end with an Outcome event.\n    executionModel @0 :ExecutionModel;\n    scriptName @1 :Text;\n    scriptVersion @2 :ScriptVersion;\n    dispatchNamespace @3 :Text;\n    scriptId @4 :Text;\n    scriptTags @5 :List(Text);\n    entryPoint @6 :Text;\n\n    struct Info { union {\n      fetch @0 :FetchEventInfo;\n      jsRpc @1 :JsRpcEventInfo;\n      scheduled @2 :ScheduledEventInfo;\n      alarm @3 :AlarmEventInfo;\n      queue @4 :QueueEventInfo;\n      email @5 :EmailEventInfo;\n      trace @6 :TraceEventInfo;\n      hibernatableWebSocket @7 :HibernatableWebSocketEventInfo;\n      custom @8 :CustomEventInfo;\n    }\n    }\n    info @7: Info;\n    spanId @8: UInt64;\n    # id for the span being opened by this Onset event.\n    attributes @9 :List(Attribute);\n  }\n\n  struct Outcome {\n    outcome @0 :EventOutcome;\n    cpuTime @1 :UInt64;\n    wallTime @2 :UInt64;\n  }\n\n  struct TailEvent {\n    # A streaming tail worker receives a series of Tail Events. Tail events always occur within an\n    # InvocationSpanContext. The first TailEvent delivered to a streaming tail session is always an\n    # Onset. The final TailEvent delivered is always an Outcome. Between those can be any number of\n    # SpanOpen, SpanClose, and Mark events. Every SpanOpen *must* be associated with a SpanClose\n    # unless the stream was abruptly terminated.\n    # Inherited spanContext for this event.\n    spanContext @0: SpanContext;\n    # invocation id of the currently invoked worker stage.\n    # invocation id will always be unique to every Onset event and will be the same until the Outcome event.\n    invocationId @1: TraceId;\n    # time for the tail event. This will be provided as I/O time from the perspective of the tail worker.\n    timestampNs @2 :Int64;\n    # unique sequence identifier for this tail event, starting at zero.\n    sequence @3 :UInt32;\n    event :union {\n      onset @4 :Onset;\n      outcome @5 :Outcome;\n      spanOpen @6 :SpanOpen;\n      spanClose @7 :SpanClose;\n      attribute @8 :List(Attribute);\n      return @9 :Return;\n      diagnosticChannelEvent @10 :DiagnosticChannelEvent;\n      exception @11 :Exception;\n      log @12 :Log;\n      streamDiagnostics @13 :StreamDiagnosticsEvent;\n    }\n  }\n}\n\nstruct SendTracesRun @0xde913ebe8e1b82a5 {\n  outcome @0 :EventOutcome;\n}\n\nstruct ScheduledRun @0xd98fc1ae5c8095d0 {\n  outcome @0 :EventOutcome;\n\n  retry @1 :Bool;\n}\n\nstruct AlarmRun @0xfa8ea4e97e23b03d {\n  outcome @0 :EventOutcome;\n\n  retry @1 :Bool;\n  retryCountsAgainstLimit @2 :Bool = true;\n}\n\nstruct QueueMessage @0x944adb18c0352295 {\n  id @0 :Text;\n  timestampNs @1 :Int64;\n  data @2 :Data;\n  contentType @3 :Text;\n  attempts @4 :UInt16;\n}\n\nstruct QueueRetryBatch {\n  retry @0 :Bool;\n  union {\n    undefined @1 :Void;\n    delaySeconds @2 :Int32;\n  }\n}\n\nstruct QueueRetryMessage {\n  msgId @0 :Text;\n  union {\n    undefined @1 :Void;\n    delaySeconds @2 :Int32;\n  }\n}\n\nstruct QueueResponse @0x90e98932c0bfc0de {\n  outcome @0 :EventOutcome;\n  ackAll @1 :Bool;\n  retryBatch @2 :QueueRetryBatch;\n  # Retry options for the batch.\n  explicitAcks @3 :List(Text);\n  # List of Message IDs that were explicitly marked as acknowledged.\n  retryMessages @4 :List(QueueRetryMessage);\n  # List of retry options for messages that were explicitly marked for retry.\n}\n\nstruct HibernatableWebSocketEventMessage {\n  payload :union {\n    text @0 :Text;\n    data @1 :Data;\n    close :group {\n      code @2 :UInt16;\n      reason @3 :Text;\n      wasClean @4 :Bool;\n    }\n    error @5 :Text;\n    # TODO(someday): This could be an Exception instead of Text.\n  }\n  websocketId @6: Text;\n  eventTimeoutMs @7: UInt32;\n}\n\nstruct HibernatableWebSocketResponse {\n  outcome @0 :EventOutcome;\n}\n\ninterface HibernatableWebSocketEventDispatcher {\n  hibernatableWebSocketEvent @0 (message: HibernatableWebSocketEventMessage )\n      -> (result :HibernatableWebSocketResponse);\n  # Run a hibernatable websocket event\n}\n\nenum SerializationTag {\n  # Tag values for all serializable types supported by the Workers API.\n\n  invalid @0;\n  # Not assigned to anything. Reserved to make things less weird if a zero-valued tag gets written\n  # by accident.\n\n  jsRpcStub @1;\n\n  writableStream @2;\n  readableStream @3;\n\n  headers @4;\n  request @5;\n  response @6;\n\n  domException @7;\n  domExceptionV2 @8;\n  # Keep this value in sync with the DOMException::SERIALIZATION_TAG in\n  # /src/workerd/jsg/dom-exception (but we can't actually change this value\n  # without breaking things).\n\n  abortSignal @9;\n\n  nativeError @10;\n  # A JavaScript native error, such as Error, TypeError, etc. These are typically\n  # not handled as host objects in V8 but we handle them as such in workers in\n  # order to preserve additional information that we may attach to them.\n\n  serviceStub @11;\n  # A ServiceStub aka Fetcher aka Service Binding.\n  #\n  # Such stubs are different from jsRpcStub in that they don't point to a single live object, but\n  # instead represent a service that can be instantiated anywhere. This means that they can be\n  # passed around the world and instantiated in a different location, as well as persisted in\n  # long-term storage.\n  #\n  # Also because of all this, service stubs can be embedded in the `env` and `ctx.props` of other\n  # Workers. Regular RPC stubs cannot.\n\n  actorClass @12;\n  # An actor class reference, aka DurableObjectClass. Can be used to instantiate a facet.\n  #\n  # Similar to serviceStub, this refers to the entrypoint of a Worker that can be instantiated\n  # anywhere and any time, and thus can be persisted and used in `env` and `ctx.props`, etc.\n}\n\nenum StreamEncoding {\n  # Specifies the internal content-encoding of a ReadableStream or WritableStream. This serves an\n  # optimization which is not visible to the app: if we end up hooking up streams so that a source\n  # is pumped to a sink that has the same encoding, we can avoid a decompression/recompression\n  # round trip. However, if the application reads/writes raw bytes, then we must decode/encode\n  # them under the hood.\n\n  identity @0;\n  gzip @1;\n  brotli @2;\n}\n\ninterface Handle {\n  # Type with no methods, but something happens when you drop it.\n}\n\nstruct JsValue {\n  # A serialized JavaScript value being passed over RPC.\n\n  v8Serialized @0 :Data;\n  # JS value that has been serialized for network transport.\n\n  externals @1 :List(External);\n  # The serialized data may contain \"externals\" -- references to external resources that cannot\n  # simply be serialized. If so, they are placed in this separate list of externals.\n  #\n  # (We could also call these \"capabilities\", but that word is pretty overloaded already.)\n\n  struct External {\n    union {\n      invalid @0 :Void;\n      # Invalid default value to reduce confusion if an External wasn't initialized properly.\n      # This should never appear in a real JsValue.\n\n      rpcTarget @1 :JsRpcTarget;\n      # An object that can be called over RPC.\n\n      writableStream :group {\n        # A WritableStream. This is much easier to represent that ReadableStream because the bytes\n        # flow from the receiver to the sender, and therefore a round trip is obviously necessary\n        # before the bytes can begin flowing.\n\n        byteStream @2 :ByteStream;\n        encoding @3 :StreamEncoding;\n      }\n\n      readableStream :group {\n        # A ReadableStream. The sender of the JsValue will use the associated StreamSink to open a\n        # stream of type `ByteStream`.\n\n        stream @10 :ExternalPusher.InputStream;\n        # If present, a stream pushed using the destination isolate's ExternalPusher.\n        #\n        # If null (deprecated), then the sender will use the associated StreamSink to open a stream\n        # of type `ByteStream`. StreamSink is in the process of being replaced by ExternalPusher.\n\n        encoding @4 :StreamEncoding;\n        # Bytes read from the stream have this encoding.\n\n        expectedLength :union {\n          # NOTE: This is obsolete when `stream` is set. Instead, the length is passed to\n          #   ExternalPusher.pushByteStream().\n\n          unknown @5 :Void;\n          known @6 :UInt64;\n        }\n      }\n\n      abortTrigger @7 :Void;\n      # Indicates that an `AbortTrigger` is being passed, see the `AbortTrigger` interface for the\n      # mechanism used to trigger the abort later. This is modeled as a stream, since the sender is\n      # the one that will later on send the abort signal. This external will have an associated\n      # stream in the corresponding `StreamSink` with type `AbortTrigger`.\n      #\n      # TODO(soon): This will be obsolete when we stop using `StreamSink`; `abortSignal` will\n      #   replace it. (The name is wrong anyway -- this is the signal end, not the trigger end.)\n\n      abortSignal @11 :ExternalPusher.AbortSignal;\n      # Indicates that an `AbortSignal` is being passed.\n\n      subrequestChannelToken @8 :Data;\n      actorClassChannelToken @9 :Data;\n      # Encoded ChannelTokens. See channel-token.capnp.\n\n      # TODO(soon): WebSocket, Request, Response\n    }\n  }\n\n  interface StreamSink {\n    # A JsValue may contain streams that flow from the sender to the receiver. We don't want such\n    # streams to require a network round trip before the stream can begin pumping. So, we need a\n    # place to start sending bytes right away.\n    #\n    # To that end, JsRpcTarget::call() returns a `paramsStreamSink`. Immediately upon sending the\n    # request, the client can use promise pipelining to begin pushing bytes to this object.\n    #\n    # Similarly, the caller passes a `resultsStreamSink` to the callee. If the response contains\n    # any streams, it can start pushing to this immediately after responding.\n    #\n    # TODO(soon): This design is overcomplicated since it requires allocating StreamSinks for every\n    #   request, even when not used, and requires a lot of weird promise magic. The newer\n    #   ExternalPusher design is simpler, and only incurs overhead when used. Once all of\n    #   production has been updated to understand ExternalPusher, then we can flip an autogate to\n    #   use it by default. Once that has rolled out globally, we can remove StreamSink.\n\n    startStream @0 (externalIndex :UInt32) -> (stream :Capability);\n    # Opens a stream corresponding to the given index in the JsValue's `externals` array. The type\n    # of capability returned depends on the type of external. E.g. for `readableStream`, it is a\n    # `ByteStream`.\n  }\n\n  interface ExternalPusher {\n    # This object allows \"pushing\" external objects to a target isolate, so that they can\n    # sublequently be referenced by a `JsValue.External`. This allows implementing externals where\n    # the sender might need to send subsequent information to the receiver *before* the receiver\n    # has had a chance to call back to request it. For example, when a ReadableStream is sent over\n    # RPC, the sender will immediately start sending body bytes without waiting for a round trip.\n    #\n    # The key to ExternalPusher is that it constructs and returns capabilities pointing at objects\n    # living directly in the target isolate's runtime. These capabilities have empty interfaces,\n    # but can be passed back to the target in the `External` table of a `JsValue`. Since the\n    # capabilities point to objects directly in the recipient's memory space, they can then be\n    # unwrapped to obtain the underlying local object, which the recipient then uses to back the\n    # external value delivered to the application.\n    #\n    # Note that externals must be pushed BEFORE the JsValue that uses them is sent, so that they\n    # can be unwrapped immediately when deserializing the value.\n\n    pushByteStream @0 (lengthPlusOne :UInt64 = 0) -> (source :InputStream, sink :ByteStream);\n    # Creates a readable stream within the remote's memory space. `source` should be placed in a\n    # sublequent `External` of type `readableStream`. The caller should write bytes to `sink`.\n    #\n    # `lengthPlusOne` is the expected length of the stream, plus 1, with zero indicating no\n    # expectation. This is used e.g. when the `ReadableStream` was created with `FixedLengthStream`.\n    # (The weird \"plus one\" encoding is used because Cap'n Proto doesn't have a Maybe. Perhaps we\n    # can fix this eventually.)\n\n    interface InputStream {\n      # No methods. This will be unwrapped by the recipient to obtain the underlying local value.\n    }\n\n    pushAbortSignal @1 () -> (signal :AbortSignal, trigger :AbortTrigger);\n\n    interface AbortSignal {\n      # No methods. This can be unwrapped by the recipient to obtain a Promise<void> which\n      # rejects when the signal is aborted.\n    }\n\n    # TODO(soon):\n    # - AbortTrigger\n    # - Promises\n  }\n}\n\ninterface AbortTrigger $Cxx.allowCancellation {\n  # When an `AbortSignal` is sent over RPC, the sender initiates a \"stream\" with this RPC interface\n  # type which is later used to signal the abort. This is not really a \"stream\", since only one\n  # message is sent. But it makes sense to model this way because the message is sent in the same\n  # direction as the `JsValue` that originally transmitted the `AbortSignal` object.\n  # When an `AbortSignal` is serialized, the original signal is the client, and the deserialized\n  # clone is the server.\n\n  abort @0 (reason :JsValue) -> ();\n  # Allows a cloned abort signal to be triggered over RPC when the original signal is triggered.\n  # `reason` is an arbitrary JavaScript value which will appear in the resulting `AbortError`s.\n\n  release @1 () -> ();\n  # Informs a cloned signal that the original signal is being destroyed, and the abort will never\n  # be triggered. Otherwise, the cloned signal will treat a dropped cabability as an abort.\n}\n\ninterface JsRpcTarget extends(JsValue.ExternalPusher) $Cxx.allowCancellation {\n  # Target on which RPC methods may be invoked.\n  #\n  # This is the backing capnp type for a JsRpcStub, as well as used to represent top-level RPC\n  # events.\n  #\n  # JsRpcTarget must implement `JsValue.ExternalPusher` to allow externals to be pushed to the\n  # target in advance of a call that uses them.\n\n  struct CallParams {\n    union {\n      methodName @0 :Text;\n      # Equivalent to `methodPath` where the list has only one element equal to this.\n\n      methodPath @2 :List(Text);\n      # Path of properties to follow from the JsRpcTarget itself to find the method being called.\n      # E.g. if the application does:\n      #\n      #     myRpcTarget.foo.bar.baz()\n      #\n      # Then the path is [\"foo\", \"bar\", \"baz\"].\n      #\n      # The path can also be empty, which means that the JsRpcTarget itself is being invoked as a\n      # function.\n    }\n\n    operation :union {\n      callWithArgs @1 :JsValue;\n      # Call the property as a function. This is a JsValue that always encodes a JavaScript Array\n      # containing the arguments to the call.\n      #\n      # If `callWithArgs` is null (but is still the active member of the union), this indicates\n      # that the argument list is empty.\n\n      getProperty @3 :Void;\n      # This indicates that we are not actually calling a method at all, but rather retrieving the\n      # value of a property. RPC classes are allowed to define properties that can be fetched\n      # asynchronously, although more commonly properties will be RPC targets themselves and their\n      # methods will be invoked by sending a `methodPath` with more than one element. That is,\n      # imagine you have:\n      #\n      #     myRpcTarget.foo.bar();\n      #\n      # This code makes a single RPC call with a path of [\"foo\", \"bar\"]. However, you could also\n      # write:\n      #\n      #     let foo = await myRpcTarget.foo;\n      #     foo.bar();\n      #\n      # This will make two separate calls. The first call is to \"foo\" and `getProperty` is used.\n      # This returns a new JsRpcTarget. The second call is on that target, invoking the method\n      # \"bar\".\n    }\n\n    resultsStreamHandler :union {\n      # We're in the process of switching from `StreamSink` to `ExternalPusher`. A caller will only\n      # offer one or the other, and expect the callee to use that. (Initially, callers will still\n      # send StreamSink for backwards-compatibility, but once all recipients are able to understand\n      # ExternalPusher, we'll flip an autogate to make callers send it.)\n\n      streamSink @4 :JsValue.StreamSink;\n      # StreamSink used for ReadableStreams found in the results.\n\n      externalPusher @5 :JsValue.ExternalPusher;\n      # ExternalPusher object which will push into the caller's isolate. Use this to push externals\n      # that will be included in the results.\n    }\n  }\n\n  struct CallResults {\n    result @0 :JsValue;\n    # The returned value.\n\n    callPipeline @1 :JsRpcTarget;\n    # Enables promise pipelining on the eventual call result. This is a JsRpcTarget wrapping the\n    # result of the call, even if the result itself is a serializable object that would not\n    # normally be treated as an RPC target. The caller may use this to initiate speculative calls\n    # on this result without waiting for the initial call to complete (using promise pipelining).\n\n    hasDisposer @2 :Bool;\n    # If `hasDisposer` is true, the server side returned a serializable object (not a stub) with a\n    # disposer (Symbol.dispose). The disposer itself is not included in the object's serialization,\n    # but dropping the `callPipeline` will invoke it.\n    #\n    # On the client side, when an RPC returns a plain object, a disposer is added to it. In order\n    # to avoid confusion, we want the server-side disposer to be invoked only after the client-side\n    # disposer is invoked. To that end, when `hasDisposer` is true, the client should hold on to\n    # `callPipeline` until the disposer is invoked. If `hasDisposer` is false, `callPipeline` can\n    # safely be dropped immediately.\n\n    paramsStreamSink @3 :JsValue.StreamSink;\n    # StreamSink used for ReadableStreams found in the params. The caller begins sending bytes for\n    # these streams immediately using promise pipelining.\n  }\n\n  call @0 CallParams -> CallResults;\n  # Runs a Worker/DO's RPC method.\n}\n\ninterface TailStreamTarget $Cxx.allowCancellation {\n  # Interface used to deliver streaming tail events to a tail worker.\n  struct TailStreamParams {\n    events @0 :List(Trace.TailEvent);\n  }\n\n  struct TailStreamResults {\n    stop @0 :Bool;\n    # For an initial tailStream call, the stop flag indicates that the tail worker does\n    # not wish to continue receiving events. If the stop field is not set, or the value\n    # is false, then events will be delivered to the tail worker until stop is indicated.\n  }\n\n  report @0 TailStreamParams -> TailStreamResults;\n  # Report one or more streaming tail events to a tail worker.\n}\n\ninterface EventDispatcher @0xf20697475ec1752d {\n  # Interface used to deliver events to a Worker's global event handlers.\n\n  getHttpService @0 () -> (http :HttpService) $Cxx.allowCancellation;\n  # Gets the HTTP interface to this worker (to trigger FetchEvents).\n\n  sendTraces @1 (traces :List(Trace)) -> (result :SendTracesRun) $Cxx.allowCancellation;\n  # Deliver a trace event to a trace worker. This always completes immediately; the trace handler\n  # runs as a \"waitUntil\" task.\n\n  prewarm @2 (url :Text) $Cxx.allowCancellation;\n\n  runScheduled @3 (scheduledTime :Int64, cron :Text) -> (result :ScheduledRun)\n      $Cxx.allowCancellation;\n  # Runs a scheduled worker. Returns a ScheduledRun, detailing information about the run such as\n  # the outcome and whether the run should be retried. This does not complete immediately.\n\n\n  runAlarm @4 (scheduledTime :Int64, retryCount :UInt32) -> (result :AlarmRun);\n  # Runs a worker's alarm.\n  # scheduledTime is a unix timestamp in milliseconds for when the alarm should be run\n  # retryCount indicates the retry count, if it's a retry. Else it'll be 0.\n  # Returns an AlarmRun, detailing information about the run such as\n  # the outcome and whether the run should be retried. This does not complete immediately.\n  #\n  # TODO(cleanup): runAlarm()'s implementation currently relies on *not* allowing cancellation.\n  #   It would be cleaner to handle that inside the implementation so we could mark the entire\n  #   interface (and file) with allowCancellation.\n\n  queue @8 (messages :List(QueueMessage), queueName :Text) -> (result :QueueResponse)\n      $Cxx.allowCancellation;\n  # Delivers a batch of queue messages to a worker's queue event handler. Returns information about\n  # the success of the batch, including which messages should be considered acknowledged and which\n  # should be retried.\n\n  jsRpcSession @9 () -> (topLevel :JsRpcTarget) $Cxx.allowCancellation;\n  # Opens a JS rpc \"session\". The call does not return until the session is complete.\n  #\n  # `topLevel` is the top-level RPC target, on which exactly one method call can be made. This\n  # call must be made using pipelining since `jsRpcSession()` won't return until after the call\n  # completes.\n  #\n  # If, through the one top-level call, new capabilities are exchanged between the client and\n  # server, then `jsRpcSession()` won't return until all those capabilities have been dropped.\n  #\n  # In C++, we use `WorkerInterface::customEvent()` to dispatch this event.\n\n  tailStreamSession @10 () -> (topLevel :TailStreamTarget, result :EventOutcome) $Cxx.allowCancellation;\n  # Opens a streaming tail session. The call does not return until the session is complete.\n  #\n  # `topLevel` is the top-level tail session target, on which exactly one method call can\n  # be made. This call must be made using pipelining since `tailStreamSession()` won't return\n  # until after the call completes. result is accessed after the session is complete.\n\n  obsolete5 @5();\n  obsolete6 @6();\n  obsolete7 @7();\n  # Deleted methods, do not reuse these numbers.\n\n  # Other methods might be added to handle other kinds of events, e.g. TCP connections, or maybe\n  # even native Cap'n Proto RPC eventually.\n}\n\ninterface WorkerdBootstrap {\n  # Bootstrap interface exposed by workerd when serving Cap'n Proto RPC.\n\n  startEvent @0 (cfBlobJson :Text) -> (dispatcher :EventDispatcher);\n  # Start a new event. Exactly one event should be delivered to the returned EventDispatcher.\n  #\n  # If the event is an HTTP request, `cfBlobJson` optionally carries the JSON-encoded `request.cf`\n  # object. The dispatcher will pass it through to the worker via SubrequestMetadata.\n}\n\ninterface WorkerdDebugPort {\n  # Bootstrap interface exposed on the debug RPC port, if one is configured. This exposes access\n  # to all services in the process, with the ability for the client to specify arbitrary props, so\n  # this interface should be considered privileged, and should probably only be used for testing\n  # purposes.\n  #\n  # This interface is subject to change. It is intended for use by miniflare.\n\n  getEntrypoint @0 (service :Text, entrypoint :Text, props :Frankenvalue)\n              -> (entrypoint :WorkerdBootstrap);\n  # Get direct access to a stateless entrypoint.\n\n  getActor @1 (service :Text, entrypoint :Text, actorId :Text) -> (actor :WorkerdBootstrap);\n  # Get an actor (Durable Object) stub.\n  # The actorId should be a hex string for Durable Objects or a plain string for ephemeral actors.\n}\n"
  },
  {
    "path": "src/workerd/io/worker-interface.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/io/outcome.capnp.h>\n#include <workerd/io/trace.h>\n#include <workerd/io/worker-interface.capnp.h>\n#include <workerd/util/http-util.h>\n\n#include <capnp/compat/http-over-capnp.h>\n#include <kj/compat/http.h>\n#include <kj/debug.h>\n\nnamespace workerd {\n\nclass Frankenvalue;\nclass IoContext_IncomingRequest;\nstruct Worker_VersionInfo;\n\n// An interface representing the services made available by a worker/pipeline to handle a\n// request.\nclass WorkerInterface: public kj::HttpService {\n public:\n  // Constructs a WorkerInterface where any method called will throw the given exception.\n  static kj::Own<WorkerInterface> fromException(kj::Exception&& e);\n\n  // Make an HTTP request. (This method is inherited from HttpService, but re-declared here for\n  // visibility.)\n  kj::Promise<void> request(kj::HttpMethod method,\n      kj::StringPtr url,\n      const kj::HttpHeaders& headers,\n      kj::AsyncInputStream& requestBody,\n      kj::HttpService::Response& response) override = 0;\n  // TODO(perf): Consider changing this to return Promise<DeferredProxy>. This would allow\n  //   more resources to be dropped when merely proxying a request. However, it means we would no\n  //   longer be implementing kj::HttpService. But maybe that doesn't matter too much in practice.\n\n  // This is the same as the inherited HttpService::connect(), but we override it to be\n  // pure-virtual to force all subclasses of WorkerInterface to implement it explicitly rather\n  // than get the default implementation which throws an unimplemented exception.\n  kj::Promise<void> connect(kj::StringPtr host,\n      const kj::HttpHeaders& headers,\n      kj::AsyncIoStream& connection,\n      ConnectResponse& response,\n      kj::HttpConnectSettings settings) override = 0;\n\n  // Hints that this worker will likely be invoked in the near future, so should be warmed up now.\n  // This method should also call `prewarm()` on any subsequent pipeline stages that are expected\n  // to be invoked.\n  //\n  // If prewarm() has to do anything asynchronous, it should use \"waitUntil\" tasks.\n  virtual kj::Promise<void> prewarm(kj::StringPtr url) = 0;\n\n  struct ScheduledResult {\n    bool retry = true;\n    EventOutcome outcome = EventOutcome::UNKNOWN;\n  };\n\n  struct AlarmResult {\n    bool retry = true;\n    bool retryCountsAgainstLimit = true;\n    EventOutcome outcome = EventOutcome::UNKNOWN;\n  };\n\n  class AlarmFulfiller {\n   public:\n    AlarmFulfiller(kj::Own<kj::PromiseFulfiller<AlarmResult>> fulfiller);\n    KJ_DISALLOW_COPY(AlarmFulfiller);\n    AlarmFulfiller(AlarmFulfiller&&) = default;\n    AlarmFulfiller& operator=(AlarmFulfiller&&) = default;\n    ~AlarmFulfiller() noexcept(false);\n    void fulfill(const AlarmResult& result);\n    void reject(const kj::Exception& e);\n    void cancel();\n\n   private:\n    kj::Maybe<kj::Own<kj::PromiseFulfiller<AlarmResult>>> maybeFulfiller;\n    kj::Maybe<kj::PromiseFulfiller<AlarmResult>&> getFulfiller();\n  };\n\n  using ScheduleAlarmResult = kj::OneOf<AlarmResult, AlarmFulfiller>;\n\n  // Trigger a scheduled event with the given scheduled (unix timestamp) time and cron string.\n  // The cron string must be valid until the returned promise completes.\n  // Async work is queued in a \"waitUntil\" task set.\n  virtual kj::Promise<ScheduledResult> runScheduled(kj::Date scheduledTime, kj::StringPtr cron) = 0;\n\n  // Trigger an alarm event with the given scheduled (unix timestamp) time.\n  virtual kj::Promise<AlarmResult> runAlarm(kj::Date scheduledTime, uint32_t retryCount) = 0;\n\n  // Run the test handler. The returned promise resolves to true or false to indicate that the test\n  // passed or failed. In the case of a failure, information should have already been written to\n  // stderr and to the devtools; there is no need for the caller to write anything further. (If the\n  // promise rejects, this indicates a bug in the test harness itself.)\n  virtual kj::Promise<bool> test() {\n    return nullptr;\n  }\n  // TODO(someday): Produce a structured test report?\n\n  // These two constants are shared by multiple systems that invoke alarms (the production\n  // implementation, and the preview implementation), whose code live in completely different\n  // places. We end up defining them here mostly for lack of a better option.\n  static constexpr auto ALARM_RETRY_START_SECONDS = 2;  // not a duration so we can left shift it\n  static constexpr auto ALARM_RETRY_MAX_TRIES = 6;\n\n  class CustomEvent {\n   public:\n    struct Result {\n      // Outcome for logging / metrics purposes.\n      EventOutcome outcome;\n    };\n\n    // Deliver the event to an isolate in this process. `incomingRequest` has been newly-allocated\n    // for this event.\n    virtual kj::Promise<Result> run(kj::Own<IoContext_IncomingRequest> incomingRequest,\n        kj::Maybe<kj::StringPtr> entrypointName,\n        kj::Maybe<Worker_VersionInfo> versionInfo,\n        Frankenvalue props,\n        kj::TaskSet& waitUntilTasks) = 0;\n\n    // Forward the event over RPC.\n    virtual kj::Promise<Result> sendRpc(capnp::HttpOverCapnpFactory& httpOverCapnpFactory,\n        capnp::ByteStreamFactory& byteStreamFactory,\n        rpc::EventDispatcher::Client dispatcher) = 0;\n\n    // The event is not supported by the target, raise an appropriate error.\n    virtual kj::Promise<Result> notSupported() = 0;\n\n    // Get the type for this event for logging / metrics purposes. This is intended for use by the\n    // RequestObserver. The RequestObserver implementation will define what numbers correspond to\n    // what types.\n    virtual uint16_t getType() = 0;\n\n    // Get event info for tracing.\n    virtual tracing::EventInfo getEventInfo() const = 0;\n\n    // If the CustomEvent fails before any of the other methods are called, this may be invoked\n    // to report the failure reason.\n    virtual void failed(const kj::Exception& e) {}\n  };\n\n  // Allows delivery of a variety of event types by implementing a callback that delivers the\n  // event to a particular isolate. If and when the event is delivered to an isolate,\n  // `callback->run()` will be called inside a fresh IoContext::IncomingRequest to begin the\n  // event.\n  //\n  // If the event needs to return some sort of result, it's the responsibility of the callback to\n  // store that result in a side object that the event's invoker can inspect after the promise has\n  // resolved.\n  //\n  // Note that it is guaranteed that if the returned promise is canceled, `event` will be dropped\n  // immediately; if its callbacks have not run yet, they will not run at all. So, a CustomEvent\n  // implementation can hold references to objects it doesn't own as long as the returned promise\n  // will be canceled before those objects go away.\n  [[nodiscard]] virtual kj::Promise<CustomEvent::Result> customEvent(\n      kj::Own<CustomEvent> event) = 0;\n\n private:\n  kj::Maybe<kj::Own<kj::HttpService>> adapterService;\n};\n\n// Given a Promise for a WorkerInterface, return a WorkerInterface whose methods will first wait\n// for the promise, then invoke the destination object.\nkj::Own<WorkerInterface> newPromisedWorkerInterface(kj::Promise<kj::Own<WorkerInterface>> promise);\n\ntemplate <typename Func>\nclass LazyWorkerInterface final: public WorkerInterface {\n public:\n  LazyWorkerInterface(Func func): func(kj::mv(func)) {}\n\n  void ensureResolve() {\n    if (promise == kj::none) {\n      promise = KJ_ASSERT_NONNULL(func)()\n                    .then([this](kj::Own<WorkerInterface> result) { worker = kj::mv(result); })\n                    .eagerlyEvaluate(nullptr)\n                    .fork();\n      func = kj::none;\n    }\n  }\n\n  kj::Promise<void> request(kj::HttpMethod method,\n      kj::StringPtr url,\n      const kj::HttpHeaders& headers,\n      kj::AsyncInputStream& requestBody,\n      Response& response) override {\n    ensureResolve();\n    KJ_IF_SOME(w, worker) {\n      co_await w->request(method, url, headers, requestBody, response);\n    } else {\n      co_await KJ_ASSERT_NONNULL(promise);\n      co_await KJ_ASSERT_NONNULL(worker)->request(method, url, headers, requestBody, response);\n    }\n  }\n\n  kj::Promise<void> connect(kj::StringPtr host,\n      const kj::HttpHeaders& headers,\n      kj::AsyncIoStream& connection,\n      ConnectResponse& response,\n      kj::HttpConnectSettings settings) override {\n    ensureResolve();\n    KJ_IF_SOME(w, worker) {\n      co_await w->connect(host, headers, connection, response, kj::mv(settings));\n    } else {\n      co_await KJ_ASSERT_NONNULL(promise);\n      co_await KJ_ASSERT_NONNULL(worker)->connect(\n          host, headers, connection, response, kj::mv(settings));\n    }\n  }\n\n  kj::Promise<void> prewarm(kj::StringPtr url) override {\n    ensureResolve();\n    KJ_IF_SOME(w, worker) {\n      co_return co_await w->prewarm(url);\n    } else {\n      co_await KJ_ASSERT_NONNULL(promise);\n      co_return co_await KJ_ASSERT_NONNULL(worker)->prewarm(url);\n    }\n  }\n\n  kj::Promise<ScheduledResult> runScheduled(kj::Date scheduledTime, kj::StringPtr cron) override {\n    ensureResolve();\n    KJ_IF_SOME(w, worker) {\n      co_return co_await w->runScheduled(scheduledTime, cron);\n    } else {\n      co_await KJ_ASSERT_NONNULL(promise);\n      co_return co_await KJ_ASSERT_NONNULL(worker)->runScheduled(scheduledTime, cron);\n    }\n  }\n\n  kj::Promise<AlarmResult> runAlarm(kj::Date scheduledTime, uint32_t retryCount) override {\n    ensureResolve();\n    KJ_IF_SOME(w, worker) {\n      co_return co_await w->runAlarm(scheduledTime, retryCount);\n    } else {\n      co_await KJ_ASSERT_NONNULL(promise);\n      co_return co_await KJ_ASSERT_NONNULL(worker)->runAlarm(scheduledTime, retryCount);\n    }\n  }\n\n  kj::Promise<CustomEvent::Result> customEvent(kj::Own<CustomEvent> event) override {\n    ensureResolve();\n    KJ_IF_SOME(w, worker) {\n      co_return co_await w->customEvent(kj::mv(event));\n    } else {\n      co_await KJ_ASSERT_NONNULL(promise);\n      co_return co_await KJ_ASSERT_NONNULL(worker)->customEvent(kj::mv(event));\n    }\n  }\n\n private:\n  kj::Maybe<Func> func;\n  kj::Maybe<kj::ForkedPromise<void>> promise;\n  kj::Maybe<kj::Own<WorkerInterface>> worker;\n};\n// Similar to newPromisedWorkerInterface but receives a function that returns a Promise for a\n// WorkerInterface. This is useful when you are not sure if the worker will be used or not and\n// you don't want it to be created in case it isn't used. If you just create a\n// PromisedWorkerInterface then the async loop might run the promise before it is eventually\n// destroyed even if it was never used.\ntemplate <typename Func>\nkj::Own<WorkerInterface> newLazyWorkerInterface(Func func) {\n  return kj::heap<LazyWorkerInterface<Func>>(kj::mv(func));\n}\n\n// Adapts WorkerInterface to HttpClient, including taking ownership.\n//\n// (Use kj::newHttpClient() if you don't want to take ownership.)\nkj::Own<kj::HttpClient> asHttpClient(kj::Own<WorkerInterface> workerInterface);\n\n// A WorkerInterface that cancels WebSockets when revokeProm is rejected.\n// Currently only supports cancelling for upgrades.\nkj::Own<WorkerInterface> newRevocableWebSocketWorkerInterface(\n    kj::Own<WorkerInterface> worker, kj::Promise<void> revokeProm);\n\n// Implementation of WorkerInterface on top of rpc::EventDispatcher. Since an EventDispatcher\n// is intended to be single-use, this class is also inherently single-use (i.e. only one event\n// can be delivered).\nclass RpcWorkerInterface final: public WorkerInterface {\n public:\n  RpcWorkerInterface(capnp::HttpOverCapnpFactory& httpOverCapnpFactory,\n      capnp::ByteStreamFactory& byteStreamFactory,\n      rpc::EventDispatcher::Client dispatcher);\n\n  kj::Promise<void> request(kj::HttpMethod method,\n      kj::StringPtr url,\n      const kj::HttpHeaders& headers,\n      kj::AsyncInputStream& requestBody,\n      Response& response) override;\n\n  kj::Promise<void> connect(kj::StringPtr host,\n      const kj::HttpHeaders& headers,\n      kj::AsyncIoStream& connection,\n      ConnectResponse& tunnel,\n      kj::HttpConnectSettings settings) override;\n\n  kj::Promise<void> prewarm(kj::StringPtr url) override;\n  kj::Promise<ScheduledResult> runScheduled(kj::Date scheduledTime, kj::StringPtr cron) override;\n  kj::Promise<AlarmResult> runAlarm(kj::Date scheduledTime, uint32_t retryCount) override;\n  kj::Promise<CustomEvent::Result> customEvent(kj::Own<CustomEvent> event) override;\n\n private:\n  capnp::HttpOverCapnpFactory& httpOverCapnpFactory;\n  capnp::ByteStreamFactory& byteStreamFactory;\n  rpc::EventDispatcher::Client dispatcher;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/worker-modules.c++",
    "content": "#include \"worker-modules.h\"\n\nnamespace workerd::modules::python {\nkj::Own<api::pyodide::PyodideMetadataReader::State> createPyodideMetadataState(\n    const Worker::Script::ModulesSource& source,\n    api::pyodide::IsWorkerd isWorkerd,\n    api::pyodide::IsTracing isTracing,\n    api::pyodide::SnapshotToDisk snapshotToDisk,\n    api::pyodide::CreateBaselineSnapshot createBaselineSnapshot,\n    PythonSnapshotRelease::Reader pythonRelease,\n    kj::Maybe<kj::Array<kj::byte>> maybeSnapshot,\n    CompatibilityFlags::Reader featureFlags) {\n  auto mainModule = kj::str(source.mainModule);\n  auto modules = source.modules.asPtr();\n  int numFiles = 0;\n  int numRequirements = 0;\n  for (auto& module: modules) {\n    KJ_SWITCH_ONEOF(module.content) {\n      KJ_CASE_ONEOF(content, Worker::Script::TextModule) {\n        numFiles++;\n      }\n      KJ_CASE_ONEOF(content, Worker::Script::DataModule) {\n        numFiles++;\n      }\n      KJ_CASE_ONEOF(content, Worker::Script::WasmModule) {\n        // Not exposed to Python.\n      }\n      KJ_CASE_ONEOF(content, Worker::Script::JsonModule) {\n        numFiles++;\n      }\n      KJ_CASE_ONEOF(content, Worker::Script::EsModule) {\n        // Not exposed to Python.\n      }\n      KJ_CASE_ONEOF(content, Worker::Script::CommonJsModule) {\n        // Not exposed to Python.\n      }\n      KJ_CASE_ONEOF(content, Worker::Script::PythonModule) {\n        numFiles++;\n      }\n      KJ_CASE_ONEOF(content, Worker::Script::PythonRequirement) {\n        numRequirements++;\n      }\n      KJ_CASE_ONEOF(content, Worker::Script::CapnpModule) {\n        // Not exposed to Python.\n      }\n    }\n  }\n\n  auto names = kj::heapArrayBuilder<kj::String>(numFiles);\n  auto contents = kj::heapArrayBuilder<kj::Array<kj::byte>>(numFiles);\n  auto requirements = kj::heapArrayBuilder<kj::String>(numRequirements);\n  for (auto& module: modules) {\n    KJ_SWITCH_ONEOF(module.content) {\n      KJ_CASE_ONEOF(content, Worker::Script::TextModule) {\n        names.add(kj::str(module.name));\n        contents.add(kj::heapArray(content.body.asBytes()));\n      }\n      KJ_CASE_ONEOF(content, Worker::Script::DataModule) {\n        names.add(kj::str(module.name));\n        contents.add(kj::heapArray(content.body));\n      }\n      KJ_CASE_ONEOF(content, Worker::Script::WasmModule) {\n        // Not exposed to Python.\n      }\n      KJ_CASE_ONEOF(content, Worker::Script::JsonModule) {\n        names.add(kj::str(module.name));\n        contents.add(kj::heapArray(content.body.asBytes()));\n      }\n      KJ_CASE_ONEOF(content, Worker::Script::EsModule) {\n        // Not exposed to Python.\n      }\n      KJ_CASE_ONEOF(content, Worker::Script::CommonJsModule) {\n        // Not exposed to Python.\n      }\n      KJ_CASE_ONEOF(content, Worker::Script::PythonModule) {\n        KJ_REQUIRE(module.name.endsWith(\".py\"));\n        names.add(kj::str(module.name));\n        contents.add(kj::heapArray(content.body.asBytes()));\n      }\n      KJ_CASE_ONEOF(content, Worker::Script::PythonRequirement) {\n        requirements.add(kj::str(module.name));\n      }\n      KJ_CASE_ONEOF(content, Worker::Script::CapnpModule) {\n        // Not exposeud to Python.\n      }\n    }\n  }\n\n  auto lock = KJ_ASSERT_NONNULL(workerd::api::pyodide::getPyodideLock(pythonRelease),\n      kj::str(\"No lock file defined for Python packages release \", pythonRelease.getPackages()));\n\n  // clang-format off\n  return kj::heap<workerd::api::pyodide::PyodideMetadataReader::State>(\n      kj::mv(mainModule),\n      names.finish(),\n      contents.finish(),\n      requirements.finish(),\n      kj::str(pythonRelease.getPyodide()),\n      kj::str(pythonRelease.getPackages()),\n      kj::mv(lock),\n      isWorkerd,\n      isTracing,\n      snapshotToDisk,\n      createBaselineSnapshot,\n      kj::mv(maybeSnapshot));\n  // clang-format on\n}\n\nkj::Maybe<kj::Array<kj::byte>> tryGetMetadataSnapshot(\n    const api::pyodide::PythonConfig& pythonConfig, api::pyodide::SnapshotToDisk snapshotToDisk) {\n  kj::Maybe<kj::Array<kj::byte>> memorySnapshot = kj::none;\n  KJ_IF_SOME(snapshot, pythonConfig.loadSnapshotFromDisk) {\n    auto& root = KJ_REQUIRE_NONNULL(pythonConfig.snapshotDirectory);\n    kj::Path path(snapshot);\n    auto maybeFile = root->tryOpenFile(path);\n    if (maybeFile == kj::none) {\n      KJ_FAIL_REQUIRE(\"Expected to find\", snapshot, \"in the package cache directory\");\n    }\n    memorySnapshot = KJ_REQUIRE_NONNULL(maybeFile)->readAllBytes();\n  }\n  return kj::mv(memorySnapshot);\n}\n\njsg::Bundle::Reader retrievePyodideBundle(\n    const api::pyodide::PythonConfig& pyConfig, kj::StringPtr version) {\n  auto result = pyConfig.pyodideBundleManager.getPyodideBundle(version);\n  return KJ_ASSERT_NONNULL(result, \"Failed to get Pyodide bundle\", version);\n}\n}  // namespace workerd::modules::python\n"
  },
  {
    "path": "src/workerd/io/worker-modules.h",
    "content": "#pragma once\n\n#include <workerd/api/commonjs.h>\n#include <workerd/api/modules.h>\n#include <workerd/io/io-context.h>\n#include <workerd/io/worker.h>\n#include <workerd/jsg/modules-new.h>\n#include <workerd/util/strong-bool.h>\n\n#include <pyodide/python-entrypoint.embed.h>\n\n#include <capnp/blob.h>\n#include <capnp/schema-loader.h>\n#include <capnp/schema.h>\n\n// This header provides utilities for setting up the ModuleRegistry for a worker.\n// It is meant to be included in only two places; workerd-api.c++ and the equivalent\n// file in the internal repo. It is templated on the TypeWrapper and JsgIsolate types.\n\nnamespace workerd {\nnamespace api {\nclass ServiceWorkerGlobalScope;\n}  // namespace api\n\nWD_STRONG_BOOL(IsPythonWorker);\n\nnamespace modules::capnp {\n// Helper to iterate over the nested nodes of a schema for capnp modules, filtering\n// out the kinds we don't care about.\nvoid filterNestedNodes(const auto& schemaLoader, const auto& schema, auto fn) {\n  for (auto nested: schema.getProto().getNestedNodes()) {\n    auto child = schemaLoader.get(nested.getId());\n    switch (child.getProto().which()) {\n      case ::capnp::schema::Node::FILE:\n      case ::capnp::schema::Node::STRUCT:\n      case ::capnp::schema::Node::INTERFACE: {\n        fn(nested.getName(), child);\n        break;\n      }\n      case ::capnp::schema::Node::ENUM:\n      case ::capnp::schema::Node::CONST:\n      case ::capnp::schema::Node::ANNOTATION:\n        // These kinds are not implemented and cannot contain further nested scopes, so\n        // don't generate anything at all for now.\n        break;\n    }\n  }\n}\n\n// This is used only by the original module registry implementation in both workerd\n// and the internal project. It collects the exports and instantiates the exports of\n// a capnp module at the same time and returns a ModuleInfo for the original registry.\n// The new module registry variation uses a different approach where the exports are\n// collected up front by the exports are instantiated lazily when the module is actually\n// resolved.\ntemplate <typename JsgIsolate>\njsg::ModuleRegistry::ModuleInfo addCapnpModule(\n    typename JsgIsolate::Lock& lock, uint64_t typeId, kj::StringPtr name) {\n  const auto& schemaLoader = lock.template getCapnpSchemaLoader<api::ServiceWorkerGlobalScope>();\n  auto schema = schemaLoader.get(typeId);\n  auto fileScope = lock.v8Ref(lock.wrap(lock.v8Context(), schema).template As<v8::Value>());\n  kj::Vector<kj::StringPtr> exports;\n  kj::HashMap<kj::StringPtr, jsg::Value> topLevelDecls;\n\n  filterNestedNodes(schemaLoader, schema, [&](auto name, const auto& child) {\n    // topLevelDecls are the actual exported values...\n    topLevelDecls.insert(\n        name, lock.v8Ref(lock.wrap(lock.v8Context(), child).template As<v8::Value>()));\n    // ... while exports is just the list of names\n    exports.add(name);\n  });\n\n  return jsg::ModuleRegistry::ModuleInfo(lock, name, exports.asPtr().asConst(),\n      jsg::ModuleRegistry::CapnpModuleInfo(kj::mv(fileScope), kj::mv(topLevelDecls)));\n}\n}  // namespace modules::capnp\n\n// Creates an instance of the (new) ModuleRegistry. This method provides the\n// initialization logic that is agnostic to the Worker::Api implementation,\n// but accepts a callback parameter to handle the Worker::Api-specific details.\n//\n// Note: this is a big template but it will only be called from two places in\n// the codebase, one for workerd and one for the internal project. It depends\n// on the TypeWrapper specific to each project.\ntemplate <typename TypeWrapper>\nstatic kj::Arc<jsg::modules::ModuleRegistry> newWorkerModuleRegistry(\n    const jsg::ResolveObserver& resolveObserver,\n    kj::Maybe<const Worker::Script::ModulesSource&> maybeSource,\n    const CompatibilityFlags::Reader& featureFlags,\n    const jsg::Url& bundleBase,\n    auto setupForApi,\n    jsg::modules::ModuleRegistry::Builder::Options options =\n        jsg::modules::ModuleRegistry::Builder::Options::NONE) {\n  jsg::modules::ModuleRegistry::Builder builder(resolveObserver, bundleBase, options);\n\n  // This callback is used when a module is being loaded to arrange evaluating the\n  // module outside of the current IoContext.\n  builder.setEvalCallback([](jsg::Lock& js, const auto& module, auto v8Module,\n                              const auto& observer) -> jsg::Promise<jsg::Value> {\n    return js.tryOrReject<jsg::Value>([&] {\n      // Creating the SuppressIoContextScope here ensures that the current IoContext,\n      // if any, is moved out of the way while we are evaluating.\n      SuppressIoContextScope suppressIoContextScope;\n      KJ_DASSERT(!IoContext::hasCurrent(), \"Module evaluation must not be in an IoContext\");\n      return jsg::check(v8Module->Evaluate(js.v8Context()));\n    });\n  });\n\n  // Add the module bundles that are built into the runtime.\n  api::registerBuiltinModules<TypeWrapper>(builder, featureFlags);\n\n  bool hasPythonModules = false;\n\n  // Add the module bundles that are configured by the worker (if any)\n  // The only case where maybeSource is none is when the worker is using\n  // the old service worker script format or \"inherit\", in which case\n  // we will initialize a module registry with the built-ins, extensions,\n  // etc but no worker bundle modules will be added.\n  KJ_IF_SOME(source, maybeSource) {\n    // Register any capnp schemas contained in the source bundle\n    auto& schemaLoader = builder.getSchemaLoader();\n    for (auto schema: source.capnpSchemas) {\n      schemaLoader.load(schema);\n    }\n\n    jsg::modules::ModuleBundle::BundleBuilder bundleBuilder(bundleBase);\n    bool firstEsm = true;\n    using namespace workerd::api::pyodide;\n\n    for (auto& def: source.modules) {\n      KJ_SWITCH_ONEOF(def.content) {\n        KJ_CASE_ONEOF(content, Worker::Script::EsModule) {\n          jsg::modules::Module::Flags flags = jsg::modules::Module::Flags::ESM;\n          // Only the first ESM module we encounter is the main module.\n          // This should also be the first module in the list but we're\n          // not enforcing that here.\n          if (firstEsm) {\n            flags = flags | jsg::modules::Module::Flags::MAIN;\n            firstEsm = false;\n          }\n          if (content.ownBody != kj::none) {\n            // When the source is owned (e.g. transpiled TypeScript), we must\n            // copy it into the module registry since the owning rust::String\n            // may not outlive the registry.\n            bundleBuilder.addEsmModule(def.name, kj::heapArray<const char>(content.body), flags);\n          } else {\n            // The content.body points into process-lifetime capnp message\n            // buffers. We can safely pass a non-owning reference.\n            bundleBuilder.addEsmModule(def.name, content.body, flags);\n          }\n          break;\n        }\n        KJ_CASE_ONEOF(content, Worker::Script::TextModule) {\n          // The content.body is memory-resident and is expected to outlive the\n          // module registry. We can safely pass a reference to the module handler.\n          // It will not be copied into a JS string until the module is actually\n          // evaluated.\n          bundleBuilder.addSyntheticModule(\n              def.name, jsg::modules::Module::newTextModuleHandler(content.body));\n          break;\n        }\n        KJ_CASE_ONEOF(content, Worker::Script::DataModule) {\n          // The content.body is memory-resident and is expected to outlive the\n          // module registry. We can safely pass a reference to the module handler.\n          // It will not be copied into a JS string until the module is actually\n          // evaluated.\n          bundleBuilder.addSyntheticModule(\n              def.name, jsg::modules::Module::newDataModuleHandler(content.body));\n          break;\n        }\n        KJ_CASE_ONEOF(content, Worker::Script::WasmModule) {\n          // The content.body is memory-resident and is expected to outlive the\n          // module registry. We can safely pass a reference to the module handler.\n          // It will not be copied into a JS string until the module is actually\n          // evaluated.\n          bundleBuilder.addWasmModule(def.name, content.body);\n          break;\n        }\n        KJ_CASE_ONEOF(content, Worker::Script::JsonModule) {\n          // The content.body is memory-resident and is expected to outlive the\n          // module registry. We can safely pass a reference to the module handler.\n          // It will not be copied into a JS string until the module is actually\n          // evaluated.\n          bundleBuilder.addSyntheticModule(\n              def.name, jsg::modules::Module::newJsonModuleHandler(content.body));\n          break;\n        }\n        KJ_CASE_ONEOF(content, Worker::Script::CommonJsModule) {\n          kj::ArrayPtr<const kj::StringPtr> named;\n          KJ_IF_SOME(n, content.namedExports) {\n            named = n;\n          }\n          bundleBuilder.addSyntheticModule(def.name,\n              jsg::modules::Module::newCjsStyleModuleHandler<api::CommonJsModuleContext,\n                  TypeWrapper>(content.body, def.name),\n              KJ_MAP(name, named) { return kj::str(name); });\n          break;\n        }\n        KJ_CASE_ONEOF(content, Worker::Script::PythonModule) {\n          KJ_FAIL_ASSERT(\"Python modules are not currently supported with the new module registry\");\n          // KJ_REQUIRE(featureFlags.getPythonWorkers(),\n          //     \"The python_workers compatibility flag is required to use Python.\");\n          // firstEsm = false;\n          // hasPythonModules = true;\n          // kj::StringPtr entry = PYTHON_ENTRYPOINT;\n          // bundleBuilder.addEsmModule(def.name, entry);\n          // break;\n        }\n        KJ_CASE_ONEOF(content, Worker::Script::PythonRequirement) {\n          // Handled separately\n          break;\n        }\n        KJ_CASE_ONEOF(content, Worker::Script::CapnpModule) {\n          // For the new module registry, the implementation is a bit different than\n          // the original. Up front we collect only the names of the exports since we\n          // need to know those when we create the synthetic module. The actual exports\n          // themselves, however, are instantiated lazily when the module is actually\n          // resolved and evaluated.\n          auto& schemaLoader = builder.getSchemaLoader();\n          auto schema = schemaLoader.get(content.typeId);\n          kj::Vector<kj::String> exports;\n          modules::capnp::filterNestedNodes(schemaLoader, schema,\n              [&](auto name, const capnp::Schema& child) { exports.add(kj::str(name)); });\n\n          bundleBuilder.addSyntheticModule(def.name,\n              [typeId = content.typeId, &schemaLoader](jsg::Lock& js, const jsg::Url&,\n                  const jsg::modules::Module::ModuleNamespace& ns,\n                  const jsg::CompilationObserver& observer) {\n            auto& typeWrapper = TypeWrapper::from(js.v8Isolate);\n            KJ_IF_SOME(schema, schemaLoader.tryGet(typeId)) {\n              return js.tryCatch([&] {\n                // Set the default export...\n                ns.setDefault(js,\n                    jsg::JsValue(typeWrapper.wrap(js, js.v8Context(), kj::none, schema)\n                                     .template As<v8::Value>()));\n                // Set each of the named exports...\n                // The names must match what we collected when the bundle was built.\n                modules::capnp::filterNestedNodes(\n                    schemaLoader, schema, [&](auto name, const auto& child) {\n                  ns.set(js, name,\n                      jsg::JsValue(typeWrapper.wrap(js, js.v8Context(), kj::none, child)));\n                });\n                return true;\n              }, [&](jsg::Value exception) {\n                js.v8Isolate->ThrowException(exception.getHandle(js));\n                return false;\n              });\n            } else {\n              // The schema should have been loaded when the Worker::Script was created.\n              // This likely indicates an internal error of some kind.\n              js.v8Isolate->ThrowException(\n                  js.typeError(\"Invalid or unknown capnp module type identifier\"));\n              return false;\n            }\n          },\n              exports.releaseAsArray());\n        }\n      }\n    }\n\n    builder.add(bundleBuilder.finish());\n  }\n\n  // Now perform any Worker::Api-specific setup.\n  setupForApi(builder, hasPythonModules ? IsPythonWorker::YES : IsPythonWorker::NO);\n\n  // All done!\n  return builder.finish();\n}\n\n// ======================================================================================\n// Legacy module registry support\n\nnamespace modules::legacy {\n\ntemplate <typename JsgIsolate>\nstatic v8::Local<v8::String> compileTextGlobal(\n    typename JsgIsolate::Lock& lock, ::capnp::Text::Reader reader) {\n  return lock.wrapNoContext(reader);\n};\n\ntemplate <typename JsgIsolate>\nstatic v8::Local<v8::ArrayBuffer> compileDataGlobal(\n    typename JsgIsolate::Lock& lock, ::capnp::Data::Reader reader) {\n  return lock.wrapNoContext(kj::heapArray(reader));\n};\n\ntemplate <typename JsgIsolate>\nstatic v8::Local<v8::WasmModuleObject> compileWasmGlobal(typename JsgIsolate::Lock& lock,\n    ::capnp::Data::Reader reader,\n    const jsg::CompilationObserver& observer) {\n  lock.setAllowEval(true);\n  KJ_DEFER(lock.setAllowEval(false));\n\n  // Allow Wasm compilation to spawn a background thread for tier-up, i.e. recompiling\n  // Wasm with optimizations in the background. Otherwise Wasm startup is way too slow.\n  // Until tier-up finishes, requests will be handled using Liftoff-generated code, which\n  // compiles fast but runs slower.\n  AllowV8BackgroundThreadsScope scope;\n\n  return jsg::compileWasmModule(lock, reader, observer);\n};\n\ntemplate <typename JsgIsolate>\nstatic v8::Local<v8::Value> compileJsonGlobal(\n    typename JsgIsolate::Lock& lock, ::capnp::Text::Reader reader) {\n  return jsg::check(v8::JSON::Parse(lock.v8Context(), lock.wrapNoContext(reader)));\n};\n\n// Compiles a module for the legacy module registry, returning kj::none if the module\n// is a Python module or Python requirement, which are handled elsewhere.\ntemplate <typename JsgIsolate>\nkj::Maybe<jsg::ModuleRegistry::ModuleInfo> tryCompileLegacyModule(jsg::Lock& js,\n    kj::StringPtr name,\n    const Worker::Script::ModuleContent& moduleContent,\n    const jsg::CompilationObserver& observer,\n    CompatibilityFlags::Reader featureFlags) {\n  auto& lock = kj::downcast<typename JsgIsolate::Lock>(js);\n  KJ_SWITCH_ONEOF(moduleContent) {\n    KJ_CASE_ONEOF(content, Worker::Script::TextModule) {\n      return jsg::ModuleRegistry::ModuleInfo(js, name, kj::none,\n          jsg::ModuleRegistry::TextModuleInfo(\n              js, modules::legacy::compileTextGlobal<JsgIsolate>(lock, content.body)));\n    }\n    KJ_CASE_ONEOF(content, Worker::Script::DataModule) {\n      return jsg::ModuleRegistry::ModuleInfo(js, name, kj::none,\n          jsg::ModuleRegistry::DataModuleInfo(\n              js, modules::legacy::compileDataGlobal<JsgIsolate>(lock, content.body)));\n    }\n    KJ_CASE_ONEOF(content, Worker::Script::WasmModule) {\n      auto wasmModule =\n          modules::legacy::compileWasmGlobal<JsgIsolate>(lock, content.body, observer);\n      auto moduleInfo = jsg::ModuleRegistry::ModuleInfo(\n          js, name, kj::none, jsg::ModuleRegistry::WasmModuleInfo(js, wasmModule));\n      moduleInfo.setModuleSourceObject(lock, wasmModule.template As<v8::Object>());\n      return moduleInfo;\n    }\n    KJ_CASE_ONEOF(content, Worker::Script::JsonModule) {\n      return jsg::ModuleRegistry::ModuleInfo(js, name, kj::none,\n          jsg::ModuleRegistry::JsonModuleInfo(\n              js, modules::legacy::compileJsonGlobal<JsgIsolate>(lock, content.body)));\n    }\n    KJ_CASE_ONEOF(content, Worker::Script::EsModule) {\n      // TODO(soon): Make sure passing nullptr to compile cache is desired.\n      return jsg::ModuleRegistry::ModuleInfo(js, name, content.body, nullptr /* compile cache */,\n          jsg::ModuleInfoCompileOption::BUNDLE, observer);\n    }\n    KJ_CASE_ONEOF(content, Worker::Script::CommonJsModule) {\n      return jsg::ModuleRegistry::ModuleInfo(js, name, content.namedExports,\n          jsg::ModuleRegistry::CommonJsModuleInfo(lock, name, content.body,\n              kj::heap<api::CommonJsImpl<typename JsgIsolate::Lock>>(js, kj::Path::parse(name))));\n    }\n    KJ_CASE_ONEOF(content, Worker::Script::PythonModule) {\n      // Nothing to do. Handled elsewhere.\n      return kj::none;\n    }\n    KJ_CASE_ONEOF(content, Worker::Script::PythonRequirement) {\n      // Nothing to do. Handled elsewhere.\n      return kj::none;\n    }\n    KJ_CASE_ONEOF(content, Worker::Script::CapnpModule) {\n      return workerd::modules::capnp::addCapnpModule<JsgIsolate>(lock, content.typeId, name);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\ntemplate <typename JsgIsolate>\nkj::Array<Worker::Script::CompiledGlobal> compileServiceWorkerGlobals(jsg::Lock& js,\n    const Worker::Script::ScriptSource& source,\n    const Worker::Isolate& isolate,\n    const jsg::CompilationObserver& observer) {\n  auto& lock = kj::downcast<typename JsgIsolate::Lock>(js);\n\n  auto globals = source.globals.asPtr();\n  auto compiledGlobals = kj::heapArrayBuilder<Worker::Script::CompiledGlobal>(globals.size());\n\n  for (auto& global: globals) {\n    js.withinHandleScope([&] {\n      // Don't use String's usual TypeHandler here because we want to intern the string.\n      auto name = jsg::v8StrIntern(js.v8Isolate, global.name);\n\n      v8::Local<v8::Value> value;\n\n      KJ_SWITCH_ONEOF(global.content) {\n        KJ_CASE_ONEOF(content, Worker::Script::TextModule) {\n          value =\n              workerd::modules::legacy::template compileTextGlobal<JsgIsolate>(lock, content.body);\n        }\n        KJ_CASE_ONEOF(content, Worker::Script::DataModule) {\n          value =\n              workerd::modules::legacy::template compileDataGlobal<JsgIsolate>(lock, content.body);\n        }\n        KJ_CASE_ONEOF(content, Worker::Script::WasmModule) {\n          value = workerd::modules::legacy::template compileWasmGlobal<JsgIsolate>(\n              lock, content.body, observer);\n        }\n        KJ_CASE_ONEOF(content, Worker::Script::JsonModule) {\n          value =\n              workerd::modules::legacy::template compileJsonGlobal<JsgIsolate>(lock, content.body);\n        }\n        KJ_CASE_ONEOF(content, Worker::Script::EsModule) {\n          KJ_FAIL_REQUIRE(\"modules not supported with mainScript\");\n        }\n        KJ_CASE_ONEOF(content, Worker::Script::CommonJsModule) {\n          KJ_FAIL_REQUIRE(\"modules not supported with mainScript\");\n        }\n        KJ_CASE_ONEOF(content, Worker::Script::PythonModule) {\n          KJ_FAIL_REQUIRE(\"modules not supported with mainScript\");\n        }\n        KJ_CASE_ONEOF(content, Worker::Script::PythonRequirement) {\n          KJ_FAIL_REQUIRE(\"modules not supported with mainScript\");\n        }\n        KJ_CASE_ONEOF(content, Worker::Script::CapnpModule) {\n          KJ_FAIL_REQUIRE(\"modules not supported with mainScript\");\n        }\n      }\n\n      compiledGlobals.add(Worker::Script::CompiledGlobal{\n        {lock.v8Isolate, name},\n        {lock.v8Isolate, value},\n      });\n    });\n  }\n\n  return compiledGlobals.finish();\n}\n\n}  // namespace modules::legacy\n\n// ===========================================================================================\n// Python module support\n\nnamespace modules::python {\nkj::Own<api::pyodide::PyodideMetadataReader::State> createPyodideMetadataState(\n    const Worker::Script::ModulesSource& source,\n    api::pyodide::IsWorkerd isWorkerd,\n    api::pyodide::IsTracing isTracing,\n    api::pyodide::SnapshotToDisk snapshotToDisk,\n    api::pyodide::CreateBaselineSnapshot createBaselineSnapshot,\n    PythonSnapshotRelease::Reader pythonRelease,\n    kj::Maybe<kj::Array<kj::byte>> maybeSnapshot,\n    CompatibilityFlags::Reader featureFlags);\n\njsg::Bundle::Reader retrievePyodideBundle(\n    const api::pyodide::PythonConfig& pyConfig, kj::StringPtr version);\n\n// Registers all the modules that are common to both workerd and edgeworker.\n// Specialised modules like the Jaeger tracing module are registered in edgeworker only, if they\n// are not specified in the arguments to this function then they get injected as \"disabled\"\n// variants.\n//\n// This function is used by both workerd and edgeworker.\ntemplate <typename TracerApi, class Registry>\nvoid registerPythonCommonModules(jsg::Lock& lock,\n    Registry& modules,\n    CompatibilityFlags::Reader featureFlags,\n    jsg::Bundle::Reader pyodideBundle,\n    const workerd::WorkerSource::ModulesSource& source,\n    kj::Maybe<kj::Array<kj::byte>> maybeSnapshot,\n    api::pyodide::IsWorkerd isWorkerd,\n    api::pyodide::IsTracing isTracing,\n    api::pyodide::SnapshotToDisk snapshotToDisk,\n    api::pyodide::CreateBaselineSnapshot createBaselineSnapshot,\n    kj::Maybe<kj::Own<api::pyodide::ArtifactBundler_State>> artifacts,\n    kj::Maybe<jsg::Ref<api::pyodide::DiskCache>> diskCache,\n    kj::Maybe<jsg::Ref<TracerApi>> internalJaeger,\n    kj::Maybe<jsg::ModuleRegistry::ModuleCallback> maybeLimiter) {\n  KJ_REQUIRE(featureFlags.getPythonWorkers(),\n      \"The python_workers compatibility flag is required to use Python.\");\n\n  // We add `pyodide:` packages here including python-entrypoint-helper.js.\n  modules.addBuiltinBundle(PYODIDE_BUNDLE, kj::none);\n\n  using namespace workerd::api::pyodide;\n  auto pythonRelease = KJ_REQUIRE_NONNULL(getPythonSnapshotRelease(featureFlags));\n\n  // Inject SetupEmscripten module\n  {\n    auto emscriptenRuntime = api::pyodide::EmscriptenRuntime::initialize(\n        lock, isWorkerd == api::pyodide::IsWorkerd::YES, pyodideBundle);\n    modules.addBuiltinModule(\"internal:setup-emscripten\",\n        lock.alloc<api::pyodide::SetupEmscripten>(kj::mv(emscriptenRuntime)),\n        workerd::jsg::ModuleRegistry::Type::INTERNAL);\n  }\n\n  // Inject pyodide bundle.\n  modules.addBuiltinBundle(pyodideBundle);\n\n  modules.addBuiltinModule(\"pyodide-internal:runtime-generated/metadata\",\n      lock.alloc<PyodideMetadataReader>(workerd::modules::python::createPyodideMetadataState(source,\n          isWorkerd, isTracing, snapshotToDisk, createBaselineSnapshot, pythonRelease,\n          kj::mv(maybeSnapshot), featureFlags)),\n      jsg::ModuleRegistry::Type::INTERNAL);\n\n  // Inject packages tar file\n  modules.addBuiltinModule(\"pyodide-internal:packages_tar_reader\", \"export default { }\"_kj,\n      workerd::jsg::ModuleRegistry::Type::INTERNAL, {});\n\n  // Inject artifact bundler.\n  modules.addBuiltinModule(\"pyodide-internal:artifacts\",\n      lock.alloc<api::pyodide::ArtifactBundler>(kj::mv(artifacts).orDefault(\n          []() { return api::pyodide::ArtifactBundler::makeDisabledBundler(); })),\n      jsg::ModuleRegistry::Type::INTERNAL);\n\n  // Inject disk cache module\n  modules.addBuiltinModule(\"pyodide-internal:disk_cache\",\n      kj::mv(diskCache).orDefault([&lock]() { return lock.alloc<DiskCache>(); }),\n      jsg::ModuleRegistry::Type::INTERNAL);\n\n  // Inject the internal jaeger tracer (only implemented in Edgeworker)\n  KJ_IF_SOME(tracer, internalJaeger) {\n    modules.addBuiltinModule(\n        \"pyodide-internal:internalJaeger\", kj::mv(tracer), jsg::ModuleRegistry::Type::INTERNAL);\n  } else {\n    modules.addBuiltinModule(\"pyodide-internal:internalJaeger\",\n        DisabledInternalJaeger::create(lock), jsg::ModuleRegistry::Type::INTERNAL);\n  }\n\n  // Inject a WorkerFatalReporter for reporting fatal errors to Runtime Analytics.\n  modules.addBuiltinModule(\"pyodide-internal:fatal-reporter\",\n      lock.alloc<api::pyodide::WorkerFatalReporter>(), jsg::ModuleRegistry::Type::INTERNAL);\n\n  // Inject a SimplePythonLimiter\n  KJ_IF_SOME(limiter, maybeLimiter) {\n    modules.addBuiltinModule(\n        \"pyodide-internal:limiter\", kj::mv(limiter), jsg::ModuleRegistry::Type::INTERNAL);\n  } else {\n    modules.addBuiltinModule(\"pyodide-internal:limiter\", SimplePythonLimiter::makeDisabled(lock),\n        jsg::ModuleRegistry::Type::INTERNAL);\n  }\n}\n\n// This function is used to register Python Worker modules in workerd. It uses\n// registerPythonCommonModules and implements other workerd-specific functionality like the disk\n// cache.\ntemplate <typename JsgIsolate, class Registry>\nvoid registerPythonWorkerdModules(jsg::Lock& lockParam,\n    Registry& modules,\n    CompatibilityFlags::Reader featureFlags,\n    kj::Maybe<kj::Own<api::pyodide::ArtifactBundler_State>> artifacts,\n    const api::pyodide::PythonConfig& pythonConfig,\n    const workerd::WorkerSource::ModulesSource& source) {\n  KJ_REQUIRE(featureFlags.getPythonWorkers(),\n      \"The python_workers compatibility flag is required to use Python.\");\n\n  auto pythonRelease = KJ_ASSERT_NONNULL(getPythonSnapshotRelease(featureFlags));\n  auto version = getPythonBundleName(pythonRelease);\n  auto bundle = retrievePyodideBundle(pythonConfig, version);\n\n  // Inject pyodide bootstrap module (TODO: load this from the capnproto bundle?)\n  {\n    Worker::Script::Module module{\n      .name = source.mainModule, .content = Worker::Script::EsModule{PYTHON_ENTRYPOINT}};\n\n    auto info = modules::legacy::tryCompileLegacyModule<JsgIsolate>(\n        lockParam, module.name, module.content, modules.getObserver(), featureFlags);\n\n    auto path = kj::Path::parse(source.mainModule);\n    modules.add(path, kj::mv(KJ_REQUIRE_NONNULL(info)));\n  }\n\n  // Determine whether we are creating a baseline snapshot and/or snapshotting to/from disk. This\n  // functionality is only supported in workerd.\n  api::pyodide::CreateBaselineSnapshot createBaselineSnapshot(pythonConfig.createBaselineSnapshot);\n  api::pyodide::SnapshotToDisk snapshotToDisk(\n      pythonConfig.createSnapshot || createBaselineSnapshot);\n  kj::Maybe<kj::Array<kj::byte>> snapshot = kj::none;\n  KJ_IF_SOME(snapshotName, pythonConfig.loadSnapshotFromDisk) {\n    auto& root = KJ_REQUIRE_NONNULL(pythonConfig.snapshotDirectory);\n    kj::Path path(snapshotName);\n    auto maybeFile = root->tryOpenFile(path);\n    if (maybeFile == kj::none) {\n      KJ_FAIL_REQUIRE(\"Expected to find\", snapshotName, \"in the package cache directory\");\n    }\n    snapshot = KJ_REQUIRE_NONNULL(maybeFile)->readAllBytes();\n  }\n\n  // Create disk cache module\n  auto diskCache = lockParam.alloc<api::pyodide::DiskCache>(\n      pythonConfig.packageDiskCacheRoot, pythonConfig.snapshotDirectory);\n\n  modules::python::registerPythonCommonModules<api::pyodide::DisabledInternalJaeger>(lockParam,\n      modules, featureFlags, bundle, source, kj::mv(snapshot), api::pyodide::IsWorkerd::YES,\n      api::pyodide::IsTracing::NO, snapshotToDisk, createBaselineSnapshot, kj::mv(artifacts),\n      kj::mv(diskCache), kj::none /* internalJaeger */, kj::none /* limiter */);\n}\n}  // namespace modules::python\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/worker-source.h",
    "content": "#pragma once\n\n#include <rust/cxx.h>\n\n#include <capnp/schema.capnp.h>\n#include <kj/one-of.h>\n#include <kj/refcount.h>\n#include <kj/string.h>\n\nnamespace workerd {\n\nusing kj::byte;\n\nclass DynamicEnvBuilder;\n\n// Represents the source code for a Worker.\n//\n// Typically the Worker's source is delivered in a capnp message structure. However, workerd vs.\n// the edge runtime use different capnp schemas. This is mostly because the edge runtime is much\n// older and its definition is... ugly, so workerd replaced it with something cleaner for public\n// consumption.\n//\n// WorkerSource is a data structure that can be constructed from either representation -- as well\n// as from non-capnp-based sources, like the dynamic worker loader API.\n//\n// Note that this structure contains StringPtrs and ArrayPtrs pointing to external data which must\n// remain alive while the WorkerSource is alive. This is done because the source may be very large,\n// and we don't want to have to copy it all out of the original capnp structure.\nstruct WorkerSource {\n  // These structs are the variants of the `ModuleContent` `OneOf`, defining all the different\n  // module types.\n  struct EsModule {\n    kj::ArrayPtr<const char> body;\n    // Owns the body text in case it was transpiled during the load.\n    kj::Maybe<::rust::String> ownBody;\n  };\n  struct CommonJsModule {\n    kj::StringPtr body;\n    kj::Maybe<kj::Array<kj::StringPtr>> namedExports;\n  };\n  struct TextModule {\n    kj::StringPtr body;\n  };\n  struct DataModule {\n    kj::ArrayPtr<const byte> body;\n  };\n  struct WasmModule {\n    // Compiled .wasm file content.\n    kj::ArrayPtr<const byte> body;\n  };\n  struct JsonModule {\n    // JSON-encoded content; will be parsed automatically when imported.\n    kj::StringPtr body;\n  };\n  struct PythonModule {\n    kj::StringPtr body;\n  };\n\n  // PythonRequirement is a variant of ModuleContent, but has no body. The module name specifies\n  // a Python package to be provided by the system.\n  struct PythonRequirement {};\n\n  // CapnpModule is a .capnp Cap'n Proto schema file. The original text of the file isn't provided;\n  // instead, `ModulesSource::capnpSchemas` contains all the capnp schemas needed by the Worker,\n  // and the `CapnpModule` only specifies the type ID of a particular file found in there.\n  //\n  // TODO(someday): Support CapnpSchema in workerd. Today, it's only supported in the internal\n  //   codebase.\n  struct CapnpModule {\n    uint64_t typeId;\n  };\n\n  using ModuleContent = kj::OneOf<EsModule,\n      CommonJsModule,\n      TextModule,\n      DataModule,\n      WasmModule,\n      JsonModule,\n      PythonModule,\n      PythonRequirement,\n      CapnpModule>;\n\n  struct Module {\n    kj::StringPtr name;\n    ModuleContent content;\n\n    // Hack for tests: register this as an internal module. Not allowed in production.\n    bool treatAsInternalForTest = false;\n\n    Module clone() {\n      Module result{.name = name};\n\n      // TODO(cleanup): kj::OneOf should have a clone() method.\n      KJ_SWITCH_ONEOF(content) {\n        KJ_CASE_ONEOF(content, EsModule) {\n          result.content = content;\n        }\n        KJ_CASE_ONEOF(content, TextModule) {\n          result.content = content;\n        }\n        KJ_CASE_ONEOF(content, DataModule) {\n          result.content = content;\n        }\n        KJ_CASE_ONEOF(content, WasmModule) {\n          result.content = content;\n        }\n        KJ_CASE_ONEOF(content, JsonModule) {\n          result.content = content;\n        }\n        KJ_CASE_ONEOF(content, CommonJsModule) {\n          result.content = CommonJsModule{.body = content.body,\n            .namedExports = content.namedExports.map([](const kj::Array<kj::StringPtr>& other) {\n            return KJ_MAP(e, other) { return e; };\n          })};\n        }\n        KJ_CASE_ONEOF(content, PythonModule) {\n          result.content = content;\n        }\n        KJ_CASE_ONEOF(content, PythonRequirement) {\n          result.content = content;\n        }\n        KJ_CASE_ONEOF(content, CapnpModule) {\n          result.content = content;\n        }\n      }\n\n      return result;\n    }\n  };\n\n  // Representation of source code for a worker using Service Workers syntax (deprecated, but will\n  // be supported forever).\n  struct ScriptSource {\n    // Content of the script (JavaScript). Pointer is valid only until the Script constructor\n    // returns.\n    kj::StringPtr mainScript;\n\n    // Name of the script, used as the script origin for stack traces. Pointer is valid only until\n    // the Script constructor returns.\n    kj::StringPtr mainScriptName;\n\n    // Global variables to inject at startup.\n    //\n    // This is sort of weird and historical. Under the old Service Workers syntax, the entire\n    // Worker is one JavaScript file, so there are no \"modules\" in the normal sense. However,\n    // there were various extra blobs of data we wanted to distribute with the code: Wasm modules,\n    // as well as large text and data blobs (e.g. embedded asset files). We decided at the time\n    // that these made sense as types of bindings. But in fact they don't fit well in the bindings\n    // abstraction: most bindings are used as configuration, but these are whole files, too big\n    // to be treated like configuration. We ended up creating a mechanism to separate out these\n    // binding types and distribute them with the code rather than the config. We also need them\n    // to be delivered to the `Worker::Script` constructor rather than the `Worker` constructor\n    // (long story).\n    //\n    // When ES modules arrived, it suddenly made sense to just say that these are modules, not\n    // bindings. But of course, we have to keep supporting Service Workers syntax forever.\n    //\n    // Recall that in Service Workers syntax, bindings show up as global variables.\n    //\n    // So, this array contains the set of Service Worker bindings that are module-like (text, data,\n    // or Wasm blobs), which should be injected into the global scope. We reuse the `Module` type\n    // for this because it is convenient, but note that only a subset of types are actually\n    // supported as globals. In this array, the `name` of each `Module` is the global variable\n    // name.\n    kj::Array<Module> globals;\n\n    // The worker may have a bundle of capnp schemas attached. (In Service Workers syntax, these\n    // can't be referenced directly by the app, but they may be used by bindings.)\n    capnp::List<capnp::schema::Node>::Reader capnpSchemas;\n\n    ScriptSource clone() {\n      return {\n        .mainScript = mainScript,\n        .mainScriptName = mainScriptName,\n        .globals = KJ_MAP(g, globals) { return g.clone(); },\n        .capnpSchemas = capnpSchemas,\n      };\n    }\n  };\n\n  // Representation of source code for a worker using ES Modules syntax.\n  struct ModulesSource {\n    // Path to the main module, which can be looked up in the module registry. Pointer is valid\n    // only until the Script constructor returns.\n    kj::StringPtr mainModule;\n\n    // All the Worker's modules.\n    kj::Array<Module> modules;\n\n    // The worker may have a bundle of capnp schemas attached.\n    capnp::List<capnp::schema::Node>::Reader capnpSchemas;\n\n    bool isPython;\n\n    // Optional Python memory snapshot. The actual capnp type is declared in the internal codebase,\n    // so we use AnyStruct here. This is deprecated anyway.\n    kj::Maybe<capnp::AnyStruct::Reader> pythonMemorySnapshot;\n\n    ModulesSource clone() {\n      return {\n        .mainModule = mainModule,\n        .modules = KJ_MAP(m, modules) { return m.clone(); },\n        .capnpSchemas = capnpSchemas,\n        .isPython = isPython,\n        .pythonMemorySnapshot = pythonMemorySnapshot,\n      };\n    }\n  };\n\n  // The overall value is either ScriptSource or ModulesSource.\n  kj::OneOf<ScriptSource, ModulesSource> variant;\n\n  // See DynamicEnvBuilder, below. Not commonly used.\n  kj::Maybe<kj::Arc<DynamicEnvBuilder>> dynamicEnvBuilder;\n\n  WorkerSource(ScriptSource source): variant(kj::mv(source)) {}\n  WorkerSource(ModulesSource source): variant(kj::mv(source)) {}\n\n  // Clones everything owned by the `WorkerSource`. But where it contains external pointers, those\n  // pointers are kept as-is.\n  WorkerSource clone() {\n    KJ_SWITCH_ONEOF(variant) {\n      KJ_CASE_ONEOF(script, ScriptSource) {\n        return WorkerSource(script.clone());\n      }\n      KJ_CASE_ONEOF(modules, ModulesSource) {\n        return WorkerSource(modules.clone());\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n};\n\n// Bit of a hack: a `WorkerSource` can contain a `DynamicEnvBuilder`, which is an object that\n// has something to do with constructing the `env` object and the `IoChannelFactory`. This\n// mechanism is only used in the edge runtime when using dynamic worker loading, to work around a\n// historical mess that exists there: the script code and `env` (bindings) are loaded from\n// different places and can be mixed and matched, but the (much newer) dynamic worker loader API\n// has both of these coming from the same invocation of the loader callback. To get the correct\n// `env` through the windy passages and to the right place, we encode it in this \"attachment\" to\n// `WorkerSource`.\n//\n// In `workerd`, this is not needed at all, due to the design being much newer and cleaner.\n// Hopefully, the edge runtime can eventually be refactored to eliminate this!\nclass DynamicEnvBuilder: public kj::AtomicRefcounted {\n  // No methods here: This type exists strictly to be downcast to the appropriate subclass in the\n  // internal codebase.\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/worker.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"actor-cache.h\"\n\n#include <workerd/api/actor-state.h>\n#include <workerd/api/global-scope.h>\n#include <workerd/api/sockets.h>\n#include <workerd/api/streams/common.h>  // for api::StreamEncoding\n#include <workerd/io/cdp.capnp.h>\n#include <workerd/io/compatibility-date.h>\n#include <workerd/io/features.h>\n#include <workerd/io/frankenvalue.h>\n#include <workerd/io/tracer.h>\n#include <workerd/io/wasm-instantiate-shim.embed.h>\n#include <workerd/io/worker.h>\n#include <workerd/jsg/async-context.h>\n#include <workerd/jsg/inspector.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/modules-new.h>\n#include <workerd/jsg/script.h>\n#include <workerd/jsg/setup.h>\n#include <workerd/jsg/util.h>\n#include <workerd/rust/jsg/lib.rs.h>\n#include <workerd/rust/jsg/v8.rs.h>\n#include <workerd/util/autogate.h>\n#include <workerd/util/batch-queue.h>\n#include <workerd/util/color-util.h>\n#include <workerd/util/mimetype.h>\n#include <workerd/util/stream-utils.h>\n#include <workerd/util/thread-scopes.h>\n#include <workerd/util/uuid.h>\n#include <workerd/util/xthreadnotifier.h>\n\n#include <rust/jsg/ffi.h>\n#include <v8-inspector.h>\n#include <v8-profiler.h>\n#include <v8-wasm.h>\n\n#include <capnp/compat/json.h>\n#include <capnp/message.h>\n#include <kj/compat/brotli.h>\n#include <kj/compat/gzip.h>\n#include <kj/encoding.h>\n#include <kj/filesystem.h>\n#include <kj/map.h>\n\n#include <cstdint>\n#include <ctime>\n#include <map>\n#include <numeric>\n\n#if _WIN32\n#include <io.h>\n#include <windows.h>\n\n#include <kj/win32-api-version.h>\n#include <kj/windows-sanity.h>\n#else\n#include <sys/syscall.h>\n#include <unistd.h>\n#endif\n\nnamespace workerd {\n\nnamespace {\n\nconstexpr kj::StringPtr logLevelToString(LogLevel level) {\n  switch (level) {\n    case LogLevel::DEBUG_:\n      return \"debug\";\n    case LogLevel::INFO:\n      return \"info\";\n    case LogLevel::LOG:\n      return \"log\";\n    case LogLevel::WARN:\n      return \"warn\";\n    case LogLevel::ERROR:\n      return \"error\";\n    default:\n      return \"log\";\n  }\n}\n\nvoid headersToCDP(const kj::HttpHeaders& in, capnp::JsonValue::Builder out) {\n  std::map<kj::StringPtr, kj::Vector<kj::StringPtr>> inMap;\n  in.forEach([&](kj::StringPtr name, kj::StringPtr value) {\n    inMap.try_emplace(name, 1).first->second.add(value);\n  });\n\n  auto outObj = out.initObject(inMap.size());\n  auto headersPos = 0;\n  for (auto& entry: inMap) {\n    auto field = outObj[headersPos++];\n    field.setName(entry.first);\n\n    // CDP uses strange header representation where headers with multiple\n    // values are merged into one newline-delimited string\n    field.initValue().setString(kj::strArray(entry.second, \"\\n\"));\n  }\n}\n\nvoid stackTraceToCDP(jsg::Lock& js, cdp::Runtime::StackTrace::Builder builder) {\n  // TODO(cleanup): Maybe use V8Inspector::captureStackTrace() which does this for us. However, it\n  //   produces protocol objects in its own format which want to handle their whole serialization\n  //   to JSON. Also, those protocol objects are defined in generated code which we currently don't\n  //   include in our cached V8 build artifacts; we'd need to fix that. But maybe we should really\n  //   be using the V8-generated protocol objects rather than our parallel capnp versions!\n\n  auto stackTrace = v8::StackTrace::CurrentStackTrace(js.v8Isolate, 10);\n  auto frameCount = stackTrace->GetFrameCount();\n  auto callFrames = builder.initCallFrames(frameCount);\n  for (int i = 0; i < frameCount; i++) {\n    auto src = stackTrace->GetFrame(js.v8Isolate, i);\n    auto dest = callFrames[i];\n    auto url = src->GetScriptNameOrSourceURL();\n    if (!url.IsEmpty()) {\n      dest.setUrl(kj::str(url));\n    } else {\n      dest.setUrl(\"\"_kj);\n    }\n    dest.setScriptId(kj::str(src->GetScriptId()));\n    auto func = src->GetFunctionName();\n    if (!func.IsEmpty()) {\n      dest.setFunctionName(kj::str(func));\n    } else {\n      dest.setFunctionName(\"\"_kj);\n    }\n    // V8 locations are 1-based, but CDP locations are 0-based... oh, well\n    dest.setLineNumber(src->GetLineNumber() - 1);\n    dest.setColumnNumber(src->GetColumn() - 1);\n  }\n}\n\nkj::Own<capnp::JsonCodec> makeCdpJsonCodec() {\n  auto codec = kj::heap<capnp::JsonCodec>();\n  codec->handleByAnnotation<cdp::Command>();\n  codec->handleByAnnotation<cdp::Event>();\n  return codec;\n}\nconst capnp::JsonCodec& getCdpJsonCodec() {\n  static const kj::Own<capnp::JsonCodec> codec = makeCdpJsonCodec();\n  return *codec;\n}\n\n}  // namespace\n\n// =======================================================================================\n\nnamespace {\n\nusing ExceptionOrDuration = kj::OneOf<kj::Exception, kj::Duration>;\n\n// Inform the inspector of an exception thrown.\n//\n// Passes `source` as the exception's short message. Reconstructs `message` from `exception` if\n// `message` is empty.\nvoid sendExceptionToInspector(jsg::Lock& js,\n    v8_inspector::V8Inspector& inspector,\n    UncaughtExceptionSource source,\n    const jsg::JsValue& exception,\n    jsg::JsMessage message) {\n  jsg::sendExceptionToInspector(js, inspector, kj::str(source), exception, message);\n}\n\nvoid addExceptionToTrace(jsg::Lock& js,\n    IoContext& ioContext,\n    BaseTracer& tracer,\n    UncaughtExceptionSource source,\n    const jsg::JsValue& exception,\n    const jsg::TypeHandler<Worker::Api::ErrorInterface>& errorTypeHandler) {\n  if (source == UncaughtExceptionSource::INTERNAL ||\n      source == UncaughtExceptionSource::INTERNAL_ASYNC) {\n    // Skip redundant intermediate JS->C++ exception reporting.  See: IoContext::runImpl(),\n    // PromiseWrapper::tryUnwrap()\n    //\n    // TODO(someday): Arguably it could make sense to store these exceptions off to the side and\n    //   report them only if they don't end up being duplicates of a later exception that has a more\n    //   specific context. This would cover cases where the C++ code that eventually received the\n    //   exception never ended up reporting it.\n    return;\n  }\n\n  auto timestamp = ioContext.now();\n  Worker::Api::ErrorInterface error;\n\n  if (exception.isObject()) {\n    error = KJ_REQUIRE_NONNULL(errorTypeHandler.tryUnwrap(js, exception),\n        \"Should always be possible to unwrap error interface from an object.\");\n  }\n\n  kj::String name;\n  KJ_IF_SOME(n, error.name) {\n    name = kj::str(n);\n  } else {\n    name = kj::str(\"Error\");\n  }\n  kj::String message;\n  KJ_IF_SOME(m, error.message) {\n    message = kj::str(m);\n  } else {\n    // This doesn't appear to be an Error object. Fall back to stringifying the whole value as\n    // the message.\n    if (!js.v8Isolate->IsExecutionTerminating()) {\n      v8::TryCatch tryCatch(js.v8Isolate);\n      try {\n        message = exception.toString(js);\n      } catch (jsg::JsExceptionThrown&) {\n        // Failed to stringify.\n        //\n        // Note that we're intentionally not checking tryCatch.CanContinue() here, because we still\n        // want to continue even if the isolate has been terminated.\n      }\n    }\n  }\n\n  kj::Maybe<kj::String> stack;\n  KJ_IF_SOME(s, error.stack) {\n    kj::StringPtr slice = s;\n\n    // Normally `error.stack` repeats the error type and message first. We don't want send two\n    // copies of that to the trace so we'll strip it off.\n    if (slice.startsWith(name)) {\n      slice = slice.slice(name.size());\n      if (slice.startsWith(\": \"_kj)) {\n        slice = slice.slice(2);\n      }\n    }\n\n    if (slice.startsWith(message)) {\n      slice = slice.slice(message.size());\n      if (slice.startsWith(\"\\n\")) {\n        slice = slice.slice(1);\n      }\n    }\n\n    if (slice.size() > 0) {\n      stack = kj::str(slice);\n    }\n  }\n\n  tracer.addException(ioContext.getInvocationSpanContext(), timestamp, kj::mv(name),\n      kj::mv(message), kj::mv(stack));\n}\n\nvoid reportStartupError(kj::StringPtr id,\n    jsg::Lock& js,\n    const kj::Maybe<std::unique_ptr<v8_inspector::V8Inspector>>& inspector,\n    const IsolateLimitEnforcer& limitEnforcer,\n    ExceptionOrDuration limitErrorOrTime,\n    v8::TryCatch& catcher,\n    kj::Maybe<Worker::ValidationErrorReporter&> errorReporter,\n    kj::Maybe<kj::Exception>& permanentException,\n    SpanParent parentSpan,\n    bool isDynamicWorker) {\n  v8::TryCatch catcher2(js.v8Isolate);\n  ExceptionOrDuration limitErrorOrTime2 = 0 * kj::NANOSECONDS;\n  try {\n    KJ_SWITCH_ONEOF(limitErrorOrTime) {\n      KJ_CASE_ONEOF(limitError, kj::Exception) {\n        auto description = jsg::extractTunneledExceptionDescription(limitError.getDescription());\n\n        auto& ex = permanentException.emplace(kj::mv(limitError));\n        KJ_IF_SOME(e, errorReporter) {\n          e.addError(kj::heapString(description));\n        } else KJ_IF_SOME(i, inspector) {\n          // We want to extend just enough CPU time as is necessary to report the exception\n          // to the inspector here. 10 milliseconds should be more than enough.\n          auto limitScope = limitEnforcer.enterLoggingJs(js, limitErrorOrTime2);\n          jsg::sendExceptionToInspector(js, *i.get(), description);\n          // When the inspector is active, we don't want to throw here because then the inspector\n          // won't be able to connect and the developer will never know what happened.\n        } else {\n          // We should never get here in production if we've validated scripts before deployment.\n          KJ_LOG(WARNING, \"script startup exceeded resource limits\", id, ex);\n          kj::throwFatalException(kj::cp(ex));\n        }\n      }\n      KJ_CASE_ONEOF_DEFAULT {\n        if (catcher.HasCaught()) {\n          js.withinHandleScope([&] {\n            auto exception = catcher.Exception();\n\n            permanentException = js.exceptionToKj(js.v8Ref(exception));\n\n            KJ_IF_SOME(e, errorReporter) {\n              auto limitScope = limitEnforcer.enterLoggingJs(js, limitErrorOrTime2);\n\n              kj::Vector<kj::String> lines;\n              lines.add(kj::str(\"Uncaught \",\n                  jsg::extractTunneledExceptionDescription(\n                      KJ_ASSERT_NONNULL(permanentException).getDescription())));\n              jsg::JsMessage message(catcher.Message());\n              message.addJsStackTrace(js, lines);\n              e.addError(kj::strArray(lines, \"\\n\"));\n\n            } else KJ_IF_SOME(i, inspector) {\n              auto limitScope = limitEnforcer.enterLoggingJs(js, limitErrorOrTime2);\n              sendExceptionToInspector(js, *i.get(), UncaughtExceptionSource::INTERNAL,\n                  jsg::JsValue(exception), jsg::JsMessage(catcher.Message()));\n              // When the inspector is active, we don't want to throw here because then the inspector\n              // won't be able to connect and the developer will never know what happened.\n            } else {\n              // We should never get here in production if we've validated scripts before deployment.\n              // (unless this is a dynamic worker)\n              kj::Vector<kj::String> lines;\n              jsg::JsMessage message(catcher.Message());\n              message.addJsStackTrace(js, lines);\n              auto trace = kj::strArray(lines, \"; \");\n              auto description = KJ_ASSERT_NONNULL(permanentException).getDescription();\n              auto span = parentSpan.newChild(\"script_startup_exception\"_kjc);\n              span.setTag(\"error\"_kjc, true);\n              span.addLog(kj::systemPreciseCalendarClock().now(), \"exception\"_kjc,\n                  kj::ConstString(\n                      kj::str(\"script startup threw exception\", id, description, trace)));\n              if (isDynamicWorker) {\n                // Rethrow the tunneled JSG exception so it converts back to a JS Error.\n                kj::throwFatalException(kj::cp(KJ_ASSERT_NONNULL(permanentException)));\n              } else {\n                KJ_LOG(ERROR, \"script startup threw exception\", id, description, trace);\n                KJ_FAIL_REQUIRE(\"script startup threw exception\");\n              }\n            }\n          });\n        } else {\n          kj::throwFatalException(kj::cp(permanentException.emplace(\n              KJ_EXCEPTION(FAILED, \"returned empty handle but didn't throw exception?\", id))));\n        }\n      }\n    }\n  } catch (const jsg::JsExceptionThrown&) {\n#define LOG_AND_SET_PERM_EXCEPTION(...)                                                            \\\n  KJ_LOG(ERROR, __VA_ARGS__);                                                                      \\\n  if (permanentException == kj::none) {                                                            \\\n    permanentException = KJ_EXCEPTION(FAILED, __VA_ARGS__);                                        \\\n  }\n\n    KJ_SWITCH_ONEOF(limitErrorOrTime2) {\n      KJ_CASE_ONEOF(limitError2, kj::Exception) {\n        // TODO(cleanup): If we see this error show up in production, stop logging it, because I\n        //   guess it's not necessarily an error? The other two cases below are more worrying though.\n        KJ_LOG(ERROR, limitError2);\n        if (permanentException == kj::none) {\n          permanentException = kj::mv(limitError2);\n        }\n      }\n      KJ_CASE_ONEOF_DEFAULT {\n        if (catcher2.HasTerminated()) {\n          LOG_AND_SET_PERM_EXCEPTION(\n              \"script startup threw exception; during our attempt to stringify the exception, \"\n              \"the script apparently was terminated for non-resource-limit reasons.\",\n              id);\n        } else {\n          LOG_AND_SET_PERM_EXCEPTION(\n              \"script startup threw exception; furthermore, an attempt to stringify the exception \"\n              \"threw another exception, which shouldn't be possible?\",\n              id);\n        }\n      }\n    }\n#undef LOG_AND_SET_PERM_EXCEPTION\n  }\n}\n\nuint64_t getCurrentThreadId() {\n#if __linux__\n  return syscall(SYS_gettid);\n#elif _WIN32\n  return GetCurrentThreadId();\n#else\n  // Assume MacOS or BSD\n  uint64_t tid;\n  pthread_threadid_np(nullptr, &tid);\n  return tid;\n#endif\n}\n\n}  // namespace\n\n// Represents a thread's attempt to take an async lock. Each Isolate has a linked list of\n// `AsyncWaiter`s. A particular thread only ever owns one `AsyncWaiter` at a time.\nclass Worker::AsyncWaiter: public kj::Refcounted {\n public:\n  AsyncWaiter(kj::Own<const Isolate> isolate);\n  ~AsyncWaiter() noexcept;\n  KJ_DISALLOW_COPY_AND_MOVE(AsyncWaiter);\n\n private:\n  // Executor for this waiter's thread.\n  const kj::Executor& executor;\n\n  // The isolate for which this waiter is currently waiting.\n  kj::Own<const Isolate> isolate;\n\n  // Promise/fulfiller to fire when the waiter reaches the front of the list for the corresponding\n  // isolate.\n  kj::ForkedPromise<void> readyPromise = nullptr;\n  kj::Own<kj::CrossThreadPromiseFulfiller<void>> readyFulfiller;\n\n  // Promise/fulfiller to fire when the AsyncLock is finally released. This is used when a thread\n  // tries to take locks on multiple different isolates concurrently, in order to serialize the\n  // locks so only one is taken at a time. This is NOT a cross-thread fulfiller; it can only be\n  // fulfilled by the thread that owns the waiter.\n  kj::ForkedPromise<void> releasePromise = nullptr;\n  kj::Own<kj::PromiseFulfiller<void>> releaseFulfiller;\n\n  // Protected by the lock on `Isolate::asyncWaiters` for the isolate identified by\n  // `currentIsolate`. Must be null if `currentIsolate` is null. (All other members of `Waiter`\n  // can only be accessed by the thread that created the `Waiter`.)\n  kj::Maybe<AsyncWaiter&> next;\n  kj::Maybe<AsyncWaiter&>* prev;\n\n  static const kj::EventLoopLocal<AsyncWaiter*> threadCurrentWaiter;\n\n  friend class Worker::Isolate;\n  friend class Worker::AsyncLock;\n};\n\nclass Worker::InspectorClient: public v8_inspector::V8InspectorClient {\n public:\n  // Wall time in milliseconds with millisecond precision. console.time() and friends rely on this\n  // function to implement timers.\n  double currentTimeMS() override {\n    auto timePoint = kj::UNIX_EPOCH;\n\n    KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n      // We're on a request-serving thread.\n      timePoint = ioContext.now();\n    } else {\n      auto lockedState = state.lockExclusive();\n      KJ_IF_SOME(info, lockedState->inspectorTimerInfo) {\n        if (info.threadId == getCurrentThreadId()) {\n          // We're on an inspector-serving thread.\n          timePoint =\n              info.timer.now() + info.timerOffset - kj::origin<kj::TimePoint>() + kj::UNIX_EPOCH;\n        }\n      }\n      // We're at script startup time -- just return the Epoch.\n    }\n    return (timePoint - kj::UNIX_EPOCH) / kj::MILLISECONDS;\n  }\n\n  void setInspectorTimerInfo(kj::Timer& timer, kj::Duration timerOffset) {\n    auto lockedState = state.lockExclusive();\n    lockedState->inspectorTimerInfo = InspectorTimerInfo{timer, timerOffset, getCurrentThreadId()};\n  }\n\n  void setChannel(Worker::Isolate::InspectorChannelImpl& channel) {\n    auto lockedState = state.lockExclusive();\n    // There is only one active inspector channel at a time in workerd. The teardown of any\n    // previous channel should have invalidated `lockedState->channel`.\n    KJ_REQUIRE(lockedState->channel == kj::none);\n    lockedState->channel = channel;\n  }\n\n  void resetChannel() {\n    auto lockedState = state.lockExclusive();\n    lockedState->channel = kj::none;\n  }\n\n  // This method is called by v8 when a breakpoint or debugger statement is hit. This method\n  // processes debugger messages until `Debugger.resume()` is called, when v8 then calls\n  // `quitMessageLoopOnPause()`.\n  //\n  // This method is ultimately called from the `InspectorChannelImpl` and the isolate lock is\n  // held when this method is called.\n  void runMessageLoopOnPause(int contextGroupId) override {\n    auto lockedState = state.lockExclusive();\n    KJ_IF_SOME(channel, lockedState->channel) {\n      runMessageLoop = true;\n      do {\n        if (!dispatchOneMessageDuringPause(channel)) {\n          break;\n        }\n      } while (runMessageLoop);\n    }\n  }\n\n  // This method is called by v8 to resume execution after a breakpoint is hit.\n  void quitMessageLoopOnPause() override {\n    runMessageLoop = false;\n  }\n\n private:\n  static bool dispatchOneMessageDuringPause(Worker::Isolate::InspectorChannelImpl& channel);\n\n  struct InspectorTimerInfo {\n    kj::Timer& timer;\n    kj::Duration timerOffset;\n    uint64_t threadId;\n  };\n\n  bool runMessageLoop;\n\n  // State that may be set on a thread other than the isolate thread.\n  // These are typically set in attachInspector when an inspector connection is\n  // made.\n  struct State {\n    // Inspector channel to use to pump messages.\n    kj::Maybe<Worker::Isolate::InspectorChannelImpl&> channel;\n\n    // The timer and offset for the inspector-serving thread.\n    kj::Maybe<InspectorTimerInfo> inspectorTimerInfo;\n  };\n  kj::MutexGuarded<State> state;\n};\n\nstatic thread_local const Worker::Api* currentApi = nullptr;\n\nconst Worker::Api& Worker::Api::current() {\n  KJ_REQUIRE(currentApi != nullptr, \"not running JavaScript\");\n  return *currentApi;\n}\n\nkj::Maybe<const Worker::Api&> Worker::Api::tryCurrent() {\n  if (currentApi != nullptr) {\n    return *currentApi;\n  }\n  return kj::none;\n}\n\nstruct Worker::Impl {\n  kj::Maybe<jsg::JsContext<api::ServiceWorkerGlobalScope>> context;\n\n  // The environment blob to pass to handlers.\n  kj::Maybe<jsg::Value> env;\n  kj::Maybe<jsg::Value> ctxExports;\n\n  // Note: The default export is given the string name \"default\", because that's what V8 tells us,\n  // and so it's easiest to go with it. I guess that means that you can't actually name an export\n  // \"default\"?\n  kj::HashMap<kj::String, api::ExportedHandler> namedHandlers;\n  kj::HashMap<kj::String, ActorClassInfo> actorClasses;\n  kj::HashMap<kj::String, EntrypointClass> statelessClasses;\n  kj::HashMap<kj::String, EntrypointClass> workflowClasses;\n\n  // If set, then any attempt to use this worker shall throw this exception.\n  kj::Maybe<kj::Exception> permanentException;\n};\n\n// Note that Isolate mutable state is protected by locking the JsgWorkerIsolate unless otherwise\n// noted.\nstruct Worker::Isolate::Impl {\n  IsolateObserver& metrics;\n  kj::Own<InspectorClient> inspectorClient;\n  kj::Maybe<std::unique_ptr<v8_inspector::V8Inspector>> inspector;\n  InspectorPolicy inspectorPolicy;\n  kj::Maybe<kj::Own<v8::CpuProfiler>> profiler;\n  ActorCache::SharedLru actorCacheLru;\n\n  // Used by JSG/Rust integration.\n  ::rust::Box<::workerd::rust::jsg::Realm> realm;\n\n  // UUID for this isolate, initialized first time getUuid() is called.\n  kj::Lazy<kj::String> uuid;\n\n  // Notification messages to deliver to the next inspector client when it connects.\n  kj::Vector<kj::String> queuedNotifications;\n\n  // Set of warning log lines that should not be logged to the inspector again.\n  kj::HashSet<kj::String> warningOnceDescriptions;\n\n  // Set of error log lines that should not be logged again.\n  kj::HashSet<kj::String> errorOnceDescriptions;\n\n  // Instantaneous count of how many threads are trying to or have successfully obtained an\n  // AsyncLock on this isolate, used to implement getCurrentLoad().\n  mutable uint lockAttemptGauge = 0;\n\n  // Atomically incremented upon every successful lock. The ThreadProgressCounter in Impl::Lock\n  // registers a reference to `lockSuccessCounter` as the thread's progress counter during a lock\n  // attempt. This allows watchdogs to see evidence of forward progress in other threads, even if\n  // their own thread has blocked waiting for the lock for a long time.\n  mutable uint64_t lockSuccessCount = 0;\n\n  // Wrapper around JsgWorkerIsolate::Lock and various RAII objects which help us report metrics,\n  // measure instantaneous load, avoid spurious watchdog kills, and defer context destruction.\n  //\n  // Always use this wrapper in code which may face lock contention (that's mostly everywhere).\n  class Lock {\n\n   public:\n    explicit Lock(\n        const Worker::Isolate& isolate, Worker::LockType lockType, jsg::V8StackScope& stackScope)\n        : impl(*isolate.impl),\n          metrics([&isolate, &lockType]() -> kj::Maybe<kj::Own<IsolateObserver::LockTiming>> {\n            KJ_SWITCH_ONEOF(lockType.origin) {\n              KJ_CASE_ONEOF(sync, Worker::Lock::TakeSynchronously) {\n                // TODO(perf): We could do some tracking here to discover overly harmful synchronous\n                //   locks.\n                return isolate.getMetrics().tryCreateLockTiming(sync.getRequest());\n              }\n              KJ_CASE_ONEOF(async, AsyncLock*) {\n                KJ_REQUIRE(async->waiter->isolate.get() == &isolate,\n                    \"async lock was taken against a different isolate than the synchronous lock\");\n                return kj::mv(async->lockTiming);\n              }\n            }\n            KJ_UNREACHABLE;\n          }()),\n          progressCounter(impl.lockSuccessCount),\n          oldCurrentApi(currentApi),\n          limitEnforcer(isolate.getLimitEnforcer()),\n          loggingOptions(isolate.loggingOptions),\n          lock(isolate.api->lock(stackScope)) {\n      WarnAboutIsolateLockScope::maybeWarn();\n\n      // Increment the success count to expose forward progress to all threads.\n      __atomic_add_fetch(&impl.lockSuccessCount, 1, __ATOMIC_RELAXED);\n      metrics.locked();\n\n      // We record the current lock so our GC prologue/epilogue callbacks can report GC time via\n      // Jaeger tracing.\n      KJ_DASSERT(impl.currentLock == kj::none, \"Isolate lock taken recursively\");\n      impl.currentLock = *this;\n\n      // Now's a good time to destroy any workers queued up for destruction.\n      auto workersToDestroy = impl.workerDestructionQueue.lockExclusive()->pop();\n      for (auto& workerImpl: workersToDestroy.asArrayPtr()) {\n        KJ_IF_SOME(c, workerImpl->context) {\n          disposeContext(kj::mv(c));\n        }\n        workerImpl = nullptr;\n      }\n\n      currentApi = isolate.api.get();\n    }\n    ~Lock() noexcept(false) {\n      currentApi = oldCurrentApi;\n\n#ifdef KJ_DEBUG\n      // We lack a KJ_DASSERT_NONNULL because it would have to look a lot like KJ_IF_SOME, thus\n      // we use a pragma around KJ_DEBUG here.\n      auto& implCurrentLock = KJ_ASSERT_NONNULL(impl.currentLock, \"Isolate lock released twice\");\n      KJ_ASSERT(&implCurrentLock == this, \"Isolate lock released recursively\");\n#endif\n\n      if (shouldReportIsolateMetrics) {\n        // The isolate asked this lock to report the stats when it released. Let's do it.\n        limitEnforcer.reportMetrics(impl.metrics);\n      }\n      impl.currentLock = kj::none;\n    }\n    KJ_DISALLOW_COPY_AND_MOVE(Lock);\n\n    void setupContext(v8::Local<v8::Context> context) {\n      // The V8Inspector implements the `console` object.\n      KJ_IF_SOME(i, impl.inspector) {\n        i.get()->contextCreated(\n            v8_inspector::V8ContextInfo(context, 1, jsg::toInspectorStringView(\"Worker\")));\n      }\n      Worker::setupContext(*lock, context, loggingOptions);\n    }\n\n    void disposeContext(jsg::JsContext<api::ServiceWorkerGlobalScope> context) {\n      lock->withinHandleScope([&] {\n        auto v8Context = context.getHandle(*lock);\n        context->clear();\n        KJ_IF_SOME(i, impl.inspector) {\n          i.get()->contextDestroyed(v8Context);\n        }\n        { auto drop = kj::mv(context); }\n        lock->v8Isolate->ContextDisposedNotification(v8::ContextDependants::kNoDependants);\n      });\n    }\n\n    void gcPrologue() {\n      metrics.gcPrologue();\n      // Filter out tracked WASM instance entries where the module has exited, allowing\n      // the linear memory to be reclaimed.\n      limitEnforcer.getTrackedWasmInstances().filter(*lock);\n    }\n    void gcEpilogue() {\n      metrics.gcEpilogue();\n    }\n\n    // Call limitEnforcer.exitJs(), and also schedule to call limitEnforcer.reportMetrics()\n    // later. Returns true if condemned. We take a mutable reference to it to make sure the caller\n    // believes it has exclusive access.\n    bool checkInWithLimitEnforcer(Worker::Isolate& isolate);\n\n   private:\n    const Impl& impl;\n    IsolateObserver::LockRecord metrics;\n    ThreadProgressCounter progressCounter;\n    bool shouldReportIsolateMetrics = false;\n    const Api* oldCurrentApi;\n\n    const IsolateLimitEnforcer& limitEnforcer;  // only so we can call getIsolateStats()\n\n    // When structuredLogging is YES AND consoleMode is STDOUT js logs will be emitted to STDOUT\n    // as newline separated json objects\n    LoggingOptions loggingOptions;\n\n   public:\n    kj::Own<jsg::Lock> lock;\n  };\n\n  // Protected by v8::Locker -- if v8::Locker::IsLocked(isolate) is true, then it is safe to access\n  // this variable.\n  mutable kj::Maybe<Lock&> currentLock;\n\n  static constexpr auto WORKER_DESTRUCTION_QUEUE_INITIAL_SIZE = 8;\n  static constexpr auto WORKER_DESTRUCTION_QUEUE_MAX_CAPACITY = 100;\n\n  // Similar in spirit to the deferred destruction queue in jsg::IsolateBase. When a Worker is\n  // destroyed, it puts its Impl, which contains objects that need to be destroyed under the isolate\n  // lock, into this queue. Our own Isolate::Impl::Lock implementation then clears this queue the\n  // next time the isolate is locked, whether that be by a connection thread, or the Worker's own\n  // destructor if it owns the last `kj::Own<const Script>` reference.\n  //\n  // Fairly obviously, this member is protected by its own mutex, not the isolate lock.\n  const kj::MutexGuarded<BatchQueue<kj::Own<Worker::Impl>>> workerDestructionQueue{\n    WORKER_DESTRUCTION_QUEUE_INITIAL_SIZE, WORKER_DESTRUCTION_QUEUE_MAX_CAPACITY};\n  // TODO(cleanup): The only reason this exists and we can't just rely on the isolate's regular\n  //   deferred destruction queue to lazily destroy the various V8 objects in Worker::Impl is\n  //   because our GlobalScope object needs to have a function called on it, and any attached\n  //   inspector needs to be notified. JSG doesn't know about these things.\n\n  struct IsolateState {\n    kj::Own<InspectorClient> inspectorClient;\n    kj::Maybe<std::unique_ptr<v8_inspector::V8Inspector>> inspector;\n    ::rust::Box<::workerd::rust::jsg::Realm> realm;\n  };\n\n  static IsolateState initIsolate(\n      const Api& api, IsolateLimitEnforcer& limitEnforcer, InspectorPolicy inspectorPolicy) {\n    auto inspectorClient = kj::heap<InspectorClient>();\n    // Default constructor of ::rust::Box is deleted, so we use a Maybe to delay initialization.\n    kj::Maybe<::rust::Box<::workerd::rust::jsg::Realm>> realm;\n    kj::Maybe<std::unique_ptr<v8_inspector::V8Inspector>> inspector;\n    jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {\n      auto lock = api.lock(stackScope);\n      auto featureFlagsWords = capnp::canonicalize(api.getFeatureFlags());\n      realm = ::workerd::rust::jsg::realm_create(\n          lock->v8Isolate, featureFlagsWords.asBytes().as<kj_rs::Rust>());\n      lock->v8Isolate->SetData(\n          ::workerd::jsg::SetDataIndex::SET_DATA_RUST_REALM, &*KJ_REQUIRE_NONNULL(realm));\n\n      limitEnforcer.customizeIsolate(lock->v8Isolate);\n      if (inspectorPolicy != InspectorPolicy::DISALLOW) {\n        // We just created our isolate, so we don't need to use Isolate::Impl::Lock.\n        KJ_ASSERT(!isMultiTenantProcess(), \"inspector is not safe in multi-tenant processes\");\n        inspector = v8_inspector::V8Inspector::create(lock->v8Isolate, inspectorClient.get());\n      }\n    });\n    return {kj::mv(inspectorClient), kj::mv(inspector), kj::mv(KJ_REQUIRE_NONNULL(realm))};\n  }\n\n  Impl(IsolateObserver& metrics,\n      IsolateLimitEnforcer& limitEnforcer,\n      InspectorPolicy inspectorPolicy,\n      IsolateState state)\n      : metrics(metrics),\n        inspectorClient(kj::mv(state.inspectorClient)),\n        inspector(kj::mv(state.inspector)),\n        inspectorPolicy(inspectorPolicy),\n        actorCacheLru(limitEnforcer.getActorCacheLruOptions()),\n        realm(kj::mv(state.realm)) {}\n\n  Impl(const Api& api,\n      IsolateObserver& metrics,\n      IsolateLimitEnforcer& limitEnforcer,\n      InspectorPolicy inspectorPolicy)\n      : Impl(metrics,\n            limitEnforcer,\n            inspectorPolicy,\n            initIsolate(api, limitEnforcer, inspectorPolicy)) {}\n};\n\nnamespace {\n\nclass CpuProfilerDisposer final: public kj::Disposer {\n public:\n  virtual void disposeImpl(void* pointer) const override {\n    reinterpret_cast<v8::CpuProfiler*>(pointer)->Dispose();\n  }\n\n  static const CpuProfilerDisposer instance;\n};\n\nconst CpuProfilerDisposer CpuProfilerDisposer::instance{};\n\nstatic constexpr kj::StringPtr PROFILE_NAME = \"Default Profile\"_kj;\n\nstatic void setSamplingInterval(v8::CpuProfiler& profiler, int interval) {\n  profiler.SetSamplingInterval(interval);\n}\n\nstatic void startProfiling(jsg::Lock& js, v8::CpuProfiler& profiler) {\n  js.withinHandleScope([&] {\n    v8::CpuProfilingOptions options(\n        v8::kLeafNodeLineNumbers, v8::CpuProfilingOptions::kNoSampleLimit);\n    profiler.StartProfiling(jsg::v8StrIntern(js.v8Isolate, PROFILE_NAME), kj::mv(options));\n  });\n}\n\nstatic void stopProfiling(jsg::Lock& js, v8::CpuProfiler& profiler, cdp::Command::Builder& cmd) {\n  js.withinHandleScope([&] {\n    auto cpuProfile = profiler.StopProfiling(jsg::v8StrIntern(js.v8Isolate, PROFILE_NAME));\n    if (cpuProfile == nullptr) return;  // profiling never started\n\n    kj::Vector<const v8::CpuProfileNode*> allNodes;\n    kj::Vector<const v8::CpuProfileNode*> unvisited;\n\n    unvisited.add(cpuProfile->GetTopDownRoot());\n    while (!unvisited.empty()) {\n      auto next = unvisited.back();\n      allNodes.add(next);\n      unvisited.removeLast();\n      for (int i = 0; i < next->GetChildrenCount(); i++) {\n        unvisited.add(next->GetChild(i));\n      }\n    }\n\n    auto res = cmd.getProfilerStop().initResult();\n    auto profile = res.initProfile();\n    profile.setStartTime(cpuProfile->GetStartTime());\n    profile.setEndTime(cpuProfile->GetEndTime());\n\n    auto nodes = profile.initNodes(allNodes.size());\n    for (auto i: kj::indices(allNodes)) {\n      auto nodeBuilder = nodes[i];\n      nodeBuilder.setId(allNodes[i]->GetNodeId());\n\n      auto callFrame = nodeBuilder.initCallFrame();\n      callFrame.setFunctionName(allNodes[i]->GetFunctionNameStr());\n      callFrame.setScriptId(kj::str(allNodes[i]->GetScriptId()));\n      callFrame.setUrl(allNodes[i]->GetScriptResourceNameStr());\n      // V8 locations are 1-based, but CDP locations are 0-based...\n      callFrame.setLineNumber(allNodes[i]->GetLineNumber() - 1);\n      callFrame.setColumnNumber(allNodes[i]->GetColumnNumber() - 1);\n\n      nodeBuilder.setHitCount(allNodes[i]->GetHitCount());\n\n      auto children = nodeBuilder.initChildren(allNodes[i]->GetChildrenCount());\n      for (int j = 0; j < allNodes[i]->GetChildrenCount(); j++) {\n        children.set(j, allNodes[i]->GetChild(j)->GetNodeId());\n      }\n\n      auto hitLineCount = allNodes[i]->GetHitLineCount();\n      auto lineBuffer = kj::heapArray<v8::CpuProfileNode::LineTick>(hitLineCount);\n      allNodes[i]->GetLineTicks(lineBuffer.begin(), lineBuffer.size());\n\n      auto positionTicks = nodeBuilder.initPositionTicks(hitLineCount);\n      for (uint j = 0; j < hitLineCount; j++) {\n        auto positionTick = positionTicks[j];\n        positionTick.setLine(lineBuffer[j].line);\n        positionTick.setTicks(lineBuffer[j].hit_count);\n      }\n    }\n\n    auto sampleCount = cpuProfile->GetSamplesCount();\n    auto samples = profile.initSamples(sampleCount);\n    auto timeDeltas = profile.initTimeDeltas(sampleCount);\n    auto lastTimestamp = cpuProfile->GetStartTime();\n    for (int i = 0; i < sampleCount; i++) {\n      samples.set(i, cpuProfile->GetSample(i)->GetNodeId());\n      auto sampleTime = cpuProfile->GetSampleTimestamp(i);\n      timeDeltas.set(i, sampleTime - lastTimestamp);\n      lastTimestamp = sampleTime;\n    }\n  });\n}\n\n}  // anonymous namespace\n\nstruct Worker::Script::Impl {\n  kj::Own<workerd::VirtualFileSystem> vfs;\n  kj::Maybe<kj::Arc<workerd::jsg::modules::ModuleRegistry>> maybeNewModuleRegistry;\n  // When using the new module registry, the module registry itself holds the\n  // SchemaLoader, so we don't need to hold it here. When using the original\n  // module registry, however, we need a schema loader to instantiate capnp\n  // modules and bindings.\n  kj::Maybe<kj::Own<capnp::SchemaLoader>> maybeSchemaLoader;\n\n  kj::OneOf<jsg::NonModuleScript, kj::Path> unboundScriptOrMainModule;\n\n  kj::Array<CompiledGlobal> globals;\n\n  kj::Maybe<jsg::JsContext<api::ServiceWorkerGlobalScope>> moduleContext;\n\n  // If set, then any attempt to use this script shall throw this exception.\n  kj::Maybe<kj::Exception> permanentException;\n\n  Impl(kj::Own<workerd::VirtualFileSystem> vfs,\n      kj::Maybe<kj::Arc<workerd::jsg::modules::ModuleRegistry>> maybeNewModuleRegistry)\n      : vfs(kj::mv(vfs)),\n        maybeNewModuleRegistry(kj::mv(maybeNewModuleRegistry)) {\n    if (this->maybeNewModuleRegistry == kj::none) {\n      maybeSchemaLoader = kj::heap<capnp::SchemaLoader>();\n    }\n  }\n\n  struct DynamicImportResult {\n    jsg::Value value;\n    bool isException = false;\n    DynamicImportResult(jsg::Value value, bool isException = false)\n        : value(kj::mv(value)),\n          isException(isException) {}\n  };\n  using DynamicImportHandler = kj::Function<jsg::Value()>;\n\n  void configureDynamicImports(jsg::Lock& js, jsg::ModuleRegistry& modules) {\n    // This is only used with the original module registry implementation.\n    KJ_ASSERT(!FeatureFlags::get(js).getNewModuleRegistry(),\n        \"legacy dynamic imports must not be used with the new module registry\");\n    static auto constexpr handleDynamicImport =\n        [](kj::Own<const Worker> worker, DynamicImportHandler handler,\n            kj::Maybe<jsg::Ref<jsg::AsyncContextFrame>> asyncContext)\n        -> kj::Promise<DynamicImportResult> {\n      co_await kj::yield();\n      auto asyncLock = co_await worker->takeAsyncLockWithoutRequest(nullptr);\n\n      co_return worker->runInLockScope(asyncLock, [&](Worker::Lock& lock) {\n        TmpDirStoreScope tmpDirStoreScope;\n        return JSG_WITHIN_CONTEXT_SCOPE(lock, lock.getContext(), [&](jsg::Lock& js) {\n          jsg::AsyncContextFrame::Scope asyncContextScope(js, asyncContext);\n\n          // We have to wrap the call to handler in a try catch here because\n          // we have to tunnel any jsg::JsExceptionThrown instances back.\n          v8::TryCatch tryCatch(js.v8Isolate);\n          ExceptionOrDuration limitErrorOrTime = 0 * kj::NANOSECONDS;\n          try {\n            auto limitScope = worker->getIsolate().getLimitEnforcer().enterDynamicImportJs(\n                lock, limitErrorOrTime);\n            return DynamicImportResult(handler());\n          } catch (jsg::JsExceptionThrown&) {\n            // Handled below...\n          } catch (kj::Exception& ex) {\n            kj::throwFatalException(kj::mv(ex));\n          }\n\n          KJ_ASSERT(tryCatch.HasCaught());\n          if (!tryCatch.CanContinue() || tryCatch.Exception().IsEmpty()) {\n            // There's nothing else we can do here but throw a generic fatal exception.\n            KJ_SWITCH_ONEOF(limitErrorOrTime) {\n              KJ_CASE_ONEOF(limitError, kj::Exception) {\n                kj::throwFatalException(kj::mv(limitError));\n              }\n              KJ_CASE_ONEOF_DEFAULT {\n                kj::throwFatalException(\n                    JSG_KJ_EXCEPTION(FAILED, Error, \"Failed to load dynamic module.\"));\n              }\n            }\n          }\n          return DynamicImportResult(js.v8Ref(tryCatch.Exception()), true);\n        });\n      });\n    };\n\n    modules.setDynamicImportCallback([](jsg::Lock& js, DynamicImportHandler handler) mutable {\n      KJ_IF_SOME(context, IoContext::tryCurrent()) {\n        // If we are within the scope of a IoContext, then we are going to pop\n        // out of it to perform the actual module instantiation.\n\n        return context.awaitIo(js,\n            handleDynamicImport(kj::atomicAddRef(context.getWorker()), kj::mv(handler),\n                jsg::AsyncContextFrame::currentRef(js)),\n            [](jsg::Lock& js, DynamicImportResult result) {\n          if (result.isException) {\n            return js.rejectedPromise<jsg::Value>(kj::mv(result.value));\n          }\n          return js.resolvedPromise(kj::mv(result.value));\n        });\n      }\n\n      // If we got here, there is no current IoContext. We're going to perform the\n      // module resolution synchronously and we do not have to worry about blocking any\n      // i/o. We get here, for instance, when dynamic import is used at the top level of\n      // a script (which is weird, but allowed).\n      //\n      // We do not need to use limitEnforcer.enterDynamicImportJs() here because this should\n      // already be covered by the startup resource limiter.\n      return js.resolvedPromise(handler());\n    });\n  }\n\n  kj::Maybe<const workerd::jsg::modules::ModuleRegistry&> getNewModuleRegistry() const {\n    return maybeNewModuleRegistry.map(\n        [](auto& r) -> const workerd::jsg::modules::ModuleRegistry& { return *r.get(); });\n  }\n};\n\nnamespace {\n\n// Given an array of strings, return a valid serialized JSON string like:\n//   {\"flags\":[\"minimal_subrequests\",...]}\n//\n// Return null if the array is empty.\nkj::Maybe<kj::String> makeCompatJson(kj::ArrayPtr<kj::StringPtr> enableFlags) {\n  if (enableFlags.size() == 0) {\n    return kj::none;\n  }\n\n  // Calculate the size of the string we're going to generate.\n  constexpr auto PREFIX = \"{\\\"flags\\\":[\"_kj;\n  constexpr auto SUFFIX = \"]}\"_kj;\n  uint size = std::accumulate(enableFlags.begin(), enableFlags.end(),\n      // We need two quotes and one comma for each enable-flag past the first, plus a NUL char.\n      PREFIX.size() + SUFFIX.size() + 3 * enableFlags.size(),\n      [](uint z, kj::StringPtr s) { return z + s.size(); });\n\n  kj::Vector<char> json(size);\n\n  json.addAll(PREFIX);\n\n  bool first = true;\n  for (auto flag: enableFlags) {\n    if (first) {\n      first = false;\n    } else {\n      json.add(',');\n    }\n\n    json.add('\"');\n\n    for (auto& c: flag.asArray()) {\n      // TODO(cleanup): Copied from simpleJsonStringCheck(). Hopefully this will\n      //   go away forever soon.\n      KJ_REQUIRE(c != '\\\"');\n      KJ_REQUIRE(c != '\\\\');\n      KJ_REQUIRE(c >= 0x20);\n    }\n    json.addAll(flag);\n\n    json.add('\"');\n  }\n\n  json.addAll(SUFFIX);\n  json.add('\\0');\n\n  return kj::String(json.releaseAsArray());\n}\n\n// When a promise is created in a different IoContext, we need to use a\n// kj::CrossThreadFulfiller in order to wait on it. The Waiter instance will\n// be held on the Promise itself, and will be fulfilled/rejected when the\n// promise is resolved or rejected. This will signal all of the waiters\n// from other IoContexts.\njsg::Promise<void> addCrossThreadPromiseWaiter(jsg::Lock& js, v8::Local<v8::Promise>& promise) {\n  auto waiter = kj::newPromiseAndCrossThreadFulfiller<void>();\n\n  struct Waiter: public kj::Refcounted {\n    kj::Maybe<kj::Own<kj::CrossThreadPromiseFulfiller<void>>> fulfiller;\n    void done() {\n      KJ_IF_SOME(f, fulfiller) {\n        // Done this way so that the fulfiller is released as soon as possible\n        // when done as the JS promise may not clean up reactions right away.\n        f->fulfill();\n        fulfiller = kj::none;\n      }\n    }\n    Waiter(kj::Own<kj::CrossThreadPromiseFulfiller<void>> fulfiller)\n        : fulfiller(kj::mv(fulfiller)) {}\n  };\n\n  auto fulfiller = kj::refcounted<Waiter>(kj::mv(waiter.fulfiller));\n\n  auto onSuccess = [waiter = kj::addRef(*fulfiller)](\n                       jsg::Lock& js, jsg::Value value) mutable { waiter->done(); };\n\n  auto onFailure = [waiter = kj::mv(fulfiller)](\n                       jsg::Lock& js, jsg::Value exception) mutable { waiter->done(); };\n\n  js.toPromise(promise).then(js, kj::mv(onSuccess), kj::mv(onFailure));\n\n  return IoContext::current().awaitIo(js, kj::mv(waiter.promise));\n}\n\nstruct HeapSnapshotDeleter: public kj::Disposer {\n  static const HeapSnapshotDeleter INSTANCE;\n  void disposeImpl(void* ptr) const override {\n    auto snapshot = const_cast<v8::HeapSnapshot*>(static_cast<const v8::HeapSnapshot*>(ptr));\n    snapshot->Delete();\n  }\n};\nconst HeapSnapshotDeleter HeapSnapshotDeleter::INSTANCE;\n\n}  // namespace\n\nWorker::Isolate::Isolate(kj::Own<Api> apiParam,\n    kj::Own<IsolateObserver> metricsParam,\n    kj::StringPtr id,\n    kj::Own<IsolateLimitEnforcer> limitEnforcerParam,\n    InspectorPolicy inspectorPolicy,\n    LoggingOptions loggingOptions)\n    : metrics(kj::mv(metricsParam)),\n      id(kj::str(id)),\n      limitEnforcer(kj::mv(limitEnforcerParam)),\n      cpuLimitNearlyExceededCallback(\n          kj::MutexGuarded<kj::Maybe<kj::Function<void(void)>>>(kj::none)),\n      api(kj::mv(apiParam)),\n      loggingOptions(loggingOptions),\n      featureFlagsForFl(makeCompatJson(decompileCompatibilityFlagsForFl(api->getFeatureFlags()))),\n      impl(kj::heap<Impl>(*api, *metrics, *limitEnforcer, inspectorPolicy)),\n      weakIsolateRef(WeakIsolateRef::wrap(this)),\n      traceAsyncContextKey(kj::refcounted<jsg::AsyncContextFrame::StorageKey>()) {\n  api->setIsolateObserver(*metrics);\n  metrics->created();\n  // We just created our isolate, so we don't need to use Isolate::Impl::Lock (nor an async lock).\n  jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {\n    auto lock = api->lock(stackScope);\n    auto features = api->getFeatureFlags();\n\n    KJ_DASSERT(lock->v8Isolate->GetNumberOfDataSlots() >= jsg::SET_DATA_SLOTS_IN_USE);\n    KJ_DASSERT(lock->v8Isolate->GetData(jsg::SET_DATA_ISOLATE) == nullptr);\n    lock->v8Isolate->SetData(jsg::SET_DATA_ISOLATE, this);\n\n    lock->setCaptureThrowsAsRejections(features.getCaptureThrowsAsRejections());\n    // TODO(cleanup): Now that this list has grown significantly, we should probably\n    // refactor to pass all of the options in a single call instead of one by one.\n    if (features.getSetToStringTag()) {\n      lock->setToStringTag();\n    }\n    if (features.getShouldSetImmutablePrototype() || features.getPythonWorkers()) {\n      lock->setImmutablePrototype();\n    }\n    if (features.getSpecCompliantPropertyAttributes()) {\n      lock->setSpecCompliantPropertyAttributes();\n    }\n    if (features.getNodeJsCompatV2()) {\n      lock->setNodeJsCompatEnabled();\n    }\n    if (features.getEnableNodeJsProcessV2()) {\n      lock->setNodeJsProcessV2Enabled();\n    }\n    if (features.getRequireReturnsDefaultExport()) {\n      lock->setRequireReturnsDefaultExportEnabled();\n    }\n    if (features.getThrowOnUnrecognizedImportAssertion()) {\n      lock->setThrowOnUnrecognizedImportAssertion();\n    }\n    if (features.getNoTopLevelAwaitInRequire()) {\n      lock->disableTopLevelAwait();\n    }\n    if (features.getEnhancedErrorSerialization()) {\n      lock->setUsingEnhancedErrorSerialization();\n    }\n    if (features.getFastJsgStruct()) {\n      lock->setUsingFastJsgStruct();\n    }\n\n    if (impl->inspector != kj::none || ::kj::_::Debug::shouldLog(::kj::LogSeverity::INFO)) {\n      lock->setLoggerCallback([this](jsg::Lock& js, kj::StringPtr message) {\n        if (impl->inspector != kj::none) {\n          logMessage(js, static_cast<uint16_t>(cdp::LogType::WARNING), message);\n        }\n        KJ_LOG(INFO, \"console warning\", message);\n      });\n      lock->setErrorReporterCallback([this](jsg::Lock& js, kj::String desc,\n                                         const jsg::JsValue& error, const jsg::JsMessage& message) {\n        // Only add exception to trace when running within an I/O context with a tracer.\n        KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n          KJ_IF_SOME(tracer, ioContext.getWorkerTracer()) {\n            addExceptionToTrace(js, ioContext, tracer, UncaughtExceptionSource::REQUEST_HANDLER,\n                error, api->getErrorInterfaceTypeHandler(js));\n          }\n        }\n\n        KJ_IF_SOME(i, impl->inspector) {\n          jsg::sendExceptionToInspector(js, *i.get(), kj::str(desc), error, message);\n        }\n\n        // Run with --verbose to log JS exceptions to stderr. Useful when running tests.\n        KJ_LOG(INFO, \"uncaught exception\", desc);\n      });\n    }\n\n    // By default, V8's memory pressure level is \"none\". This tells V8 that no one else on the\n    // machine is competing for memory so it might as well use all it wants and be lazy about GC.\n    //\n    // In our production environment, however, we can safely assume that there is always memory\n    // pressure, because every machine is handling thousands of tenants all the time. So we might\n    // as well just throw the switch to \"moderate\" right away.\n    lock->v8Isolate->MemoryPressureNotification(v8::MemoryPressureLevel::kModerate);\n\n    // Register GC prologue and epilogue callbacks so that we can report GC CPU time via the\n    // \"request_context\" Jaeger span.\n    lock->v8Isolate->AddGCPrologueCallback(\n        [](v8::Isolate* isolate, v8::GCType type, v8::GCCallbackFlags flags, void* data) noexcept {\n      // We assume that a v8::Locker is alive during GC.\n      KJ_DASSERT(v8::Locker::IsLocked(isolate));\n      auto& self = *reinterpret_cast<Isolate*>(data);\n      // However, currentLock might not be available, if (like in our Worker::Isolate constructor) we\n      // don't use a Worker::Isolate::Impl::Lock.\n      KJ_IF_SOME(currentLock, self.impl->currentLock) {\n        currentLock.gcPrologue();\n      }\n    }, this);\n    lock->v8Isolate->AddGCEpilogueCallback(\n        [](v8::Isolate* isolate, v8::GCType type, v8::GCCallbackFlags flags, void* data) noexcept {\n      // We make similar assumptions about v8::Locker and currentLock as in the prologue callback.\n      KJ_DASSERT(v8::Locker::IsLocked(isolate));\n      auto& self = *reinterpret_cast<Isolate*>(data);\n      KJ_IF_SOME(currentLock, self.impl->currentLock) {\n        currentLock.gcEpilogue();\n      }\n    }, this);\n    lock->v8Isolate->SetPromiseRejectCallback([](v8::PromiseRejectMessage message) {\n      // TODO(cleanup): IoContext doesn't really need to be involved here. We are trying to call\n      // a method of ServiceWorkerGlobalScope, which is the context object. So we should be able to\n      // do something like unwrap(lock, isolate->GetCurrentContext()).emitPromiseRejection().\n      // However, JSG doesn't currently provide an easy way to do this.\n      KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n        try {\n          ioContext.getCurrentLock().reportPromiseRejectEvent(message);\n        } catch (jsg::JsExceptionThrown&) {\n          // V8 expects us to just return.\n          return;\n        }\n      }\n    });\n\n    // The PromiseCrossContextCallback is used to allow cross-IoContext promise following.\n    // When the IoContext scope is entered, we set the \"promise context tag\" associated\n    // with the IoContext on the Isolate that is locked. Any Promise that is created within\n    // that scope will be tagged with the same promise context tag. When an attempt to\n    // follow a promise occurs (e.g. either using Promise.prototype.then() or await, etc)\n    // our patched v8 logic will check to see if the followed promise's tag matches the\n    // current Isolate tag. If they do not, then v8 will invoke this callback. The promise\n    // here is the promise that belongs to a different IoContext.\n    lock->v8Isolate->SetPromiseCrossContextCallback(\n        [](v8::Local<v8::Context> context, v8::Local<v8::Promise> promise,\n            v8::Local<v8::Object> tag) -> v8::MaybeLocal<v8::Promise> {\n      auto& js = jsg::Lock::current();\n      try {\n        // Generally this condition is only going to happen when using dynamic imports.\n        // It should not be common.\n        JSG_REQUIRE(IoContext::hasCurrent(), Error,\n            \"Unable to wait on a promise created within a request when not running within a \"\n            \"request.\");\n\n        return js.wrapSimplePromise(\n            addCrossThreadPromiseWaiter(js, promise)\n                .then(js, [promise = js.v8Ref(promise.As<v8::Value>())](auto& js) mutable {\n          // Once the waiter has been resolved, return the now settled promise.\n          // Since the promise has been settled, it is now safe to access from\n          // other requests. Note that the resolved value of the promise still\n          // might not be safe to access! (e.g. if it contains any IoOwns attached\n          // to the other request IoContext).\n          return kj::mv(promise);\n        }));\n      } catch (jsg::JsExceptionThrown&) {\n        // Exceptions here are generally unexpected but possible because the jsg::Promise\n        // then can fail if the isolate is in the process of being torn down. Let's just\n        // return control back to V8 which should handle the case.\n        return v8::MaybeLocal<v8::Promise>();\n      } catch (...) {\n        auto ex = kj::getCaughtExceptionAsKj();\n        KJ_LOG(ERROR, \"Setting promise cross context follower failed unexpectedly\", ex);\n        jsg::throwInternalError(js.v8Isolate, kj::mv(ex));\n        return v8::MaybeLocal<v8::Promise>();\n      }\n    });\n\n    // The PromiseCrossContextResolveCallback is used to ensure that promise reactions\n    // are only scheduled on the microtask queue from the appropriate IoContext for the\n    // promise. Huh? Yeah, that's not super clear... let me explain a bit more.\n    // Every request runs in its own IoContext.\n    // Some I/O objects are bound to the IoContext when they are created.\n    // If these objects are accessed from the wrong IoContext, things blow up.\n    // If I create a promise in one request and pass the resolve/reject functions\n    // off to a different request, bad things can happen because the IoContext can\n    // actually change in the promise continuation. Take the following case for example:\n    //\n    // In request one:\n    //\n    //  const ab = AbortSignal.abort();  // AbortSignal is bound to the IoContext\n    //  const { promise, resolve } = Promise.withResolvers();\n    //  globalThis.resolve = resolve;\n    //  await promise;\n    //  console.log(ab.aborted);\n    //\n    // In request two:\n    //\n    //  globalThis.resolve();\n    //\n    // What previously would happen is that the `console.log(ab.aborted) after the\n    // `await promise` in request one would fail with an error because the current\n    // IoContext would change! (it would be the IoContext from request two!).\n    //\n    // That's bad.\n    //\n    // So this callback is added to ensure that the promise reactions for the promise\n    // being resolved are not scheduled until we are back in the correct IoContext for\n    // the promise.\n    //\n    // This happens by (ab)using the DeleteQueue that is specific to the owning\n    // IoContext. When the IoContext is entered, the isolate is updated with a\n    // current \"promise tag\". Whenever a promise is created, it is associated with\n    // the isolate's current tag. Whenever a promise is followed (calling .then, etc),\n    // we check the tag and arrange for a cross-thread resolve. When the promise is\n    // resolved or rejected, we check the tag also. If the promise tag and the current\n    // isolate tag do not match, the function below is called.\n    if (features.getHandleCrossRequestPromiseResolution()) {\n      lock->v8Isolate->SetPromiseCrossContextResolveCallback(\n          [](v8::Isolate* isolate, v8::Local<v8::Value> tag, v8::Local<v8::Data> reactions,\n              v8::Local<v8::Value> argument,\n              std::function<void(v8::Isolate * isolate, v8::Local<v8::Data> reactions,\n                  v8::Local<v8::Value> argument)> callback) -> v8::Maybe<void> {\n        try {\n          auto& js = jsg::Lock::from(isolate);\n\n          // The promise tag is generally opaque except for right here. The tag\n          // wraps an instanceof kj::Own<IoCrossContextExecutor>, which wraps an atomically\n          // refcounted pointer to the DeleteQueue for the correct isolate.\n          // We simply pass the given callback, reactions, and argument to\n          // a function that will be added to the queue inside DeleteQueue.\n          // The next time the relevant IoContext is entered, this queue will\n          // be drained and the actions will be run. Adding the task to the\n          // delete queue will also signal the IoContext that it should wake\n          // up and drain the queue. Simple, eh?\n          //\n          // A word of warning tho! It is possible for the IoContext to be\n          // destroyed before the promise is resolved. Any actions that have\n          // already been added to the queue would end up being dropped silently\n          // on the floor. Actions that are added to the queue now will be run\n          // immediately in the wrong IoContext.\n          auto& ref = jsg::unwrapOpaqueRef<kj::Own<IoCrossContextExecutor>>(isolate, tag);\n          ref->execute(js,\n              [reactions = jsg::Data(isolate, reactions), argument = jsg::V8Ref(isolate, argument),\n                  callback = kj::mv(callback)](jsg::Lock& js) mutable {\n            callback(js.v8Isolate, reactions.getHandle(js), argument.getHandle(js));\n          });\n          return v8::JustVoid();\n        } catch (jsg::JsExceptionThrown&) {\n          // Exceptions here are generally unexpected but possible because the jsg::Promise\n          // then can fail if the isolate is in the process of being torn down. Let's just\n          // return control back to V8 which should handle the case.\n          // Note that errors thrown here and below should cause the resolve() or reject()\n          // function calls to throw, which is unusual. Just important to keep that in mind.\n          // Most likely errors thrown here are fatal so that should be OK.\n          return v8::Nothing<void>();\n        } catch (...) {\n          jsg::throwInternalError(isolate, kj::getCaughtExceptionAsKj());\n          return v8::Nothing<void>();\n        }\n      });\n    }\n  });\n}\n\nWorker::Script::Script(kj::Own<const Isolate> isolateParam,\n    kj::StringPtr id,\n    const Script::Source& source,\n    IsolateObserver::StartType startType,\n    bool logNewScript,\n    kj::Maybe<ValidationErrorReporter&> errorReporter,\n    kj::Maybe<kj::Own<api::pyodide::ArtifactBundler_State>> artifacts,\n    SpanParent parentSpan,\n    kj::Own<workerd::VirtualFileSystem> vfs,\n    kj::Maybe<kj::Arc<workerd::jsg::modules::ModuleRegistry>> maybeNewModuleRegistry)\n    : isolate(kj::mv(isolateParam)),\n      id(kj::str(id)),\n      modular(source.variant.is<ModulesSource>()),\n      python(modular && source.variant.get<ModulesSource>().isPython),\n      impl(kj::heap<Impl>(kj::mv(vfs), kj::mv(maybeNewModuleRegistry))),\n      dynamicEnvBuilder(source.dynamicEnvBuilder.map(\n          [](const auto& inst) -> kj::Arc<DynamicEnvBuilder> { return inst.addRef(); })) {\n  auto parseMetrics = isolate->metrics->parse(startType);\n  // TODO(perf): It could make sense to take an async lock when constructing a script if we\n  //   co-locate multiple scripts in the same isolate. As of this writing, we do not, except in\n  //   previews, where it doesn't matter. If we ever do co-locate multiple scripts in the same\n  //   isolate, we may wish to make the RequestObserver object available here, in order to\n  //   attribute lock timing to that request.\n  jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {\n    Isolate::Impl::Lock recordedLock(\n        *isolate, Worker::Lock::TakeSynchronously(kj::none), stackScope);\n    auto& lock = *recordedLock.lock;\n\n    // If we throw an exception, it's important that `impl` is destroyed under lock.\n    KJ_ON_SCOPE_FAILURE({\n      auto implToDestroy = kj::mv(impl);\n      KJ_IF_SOME(c, implToDestroy->moduleContext) {\n        recordedLock.disposeContext(kj::mv(c));\n      } else {\n        // Else block to avoid dangling else clang warning.\n      }\n    });\n\n    lock.withinHandleScope([&] {\n      if (isolate->impl->inspector != kj::none || errorReporter != kj::none) {\n        lock.v8Isolate->SetCaptureStackTraceForUncaughtExceptions(true);\n      }\n\n      v8::Local<v8::Context> context;\n      if (modular) {\n        // Modules can't be compiled for multiple contexts. We need to create the real context now.\n        auto& mContext = impl->moduleContext.emplace(isolate->getApi().newContext(lock,\n            {\n              .newModuleRegistry = impl->getNewModuleRegistry(),\n              .schemaLoader = getSchemaLoader(),\n            }));\n        mContext->enableWarningOnSpecialEvents();\n        context = mContext.getHandle(lock);\n        recordedLock.setupContext(context);\n      } else {\n        // Although we're going to compile a script independent of context, V8 requires that\n        // there be an active context, otherwise it will segfault, I guess. So we create a\n        // dummy context. (Undocumented, as usual.)\n        context =\n            v8::Context::New(lock.v8Isolate, nullptr, v8::ObjectTemplate::New(lock.v8Isolate));\n        // We need to set the highest used index in every context we create to be a nullptr\n        // This is because we might later on call GetAlignedPointerFromEmbedderData which fails with\n        // a fatal error if the array is smaller than the given index.\n        jsg::setAlignedPointerInEmbedderData(\n            context, jsg::ContextPointerSlot::MAX_POINTER_SLOT, nullptr);\n      }\n\n      JSG_WITHIN_CONTEXT_SCOPE(lock, context, [&](jsg::Lock& js) {\n        // const_cast OK because we hold the isolate lock.\n        Worker::Isolate& lockedWorkerIsolate = const_cast<Isolate&>(*isolate);\n\n        if (logNewScript) {\n          // HACK: Log a message indicating that a new script was loaded. This is used only when the\n          //   inspector is enabled. We want to do this immediately after the context is created,\n          //   before the user gets a chance to modify the behavior of the console, which if they\n          //   did, we'd then need to be more careful to apply time limits and such.\n          lockedWorkerIsolate.logMessage(lock, static_cast<uint16_t>(cdp::LogType::WARNING),\n              \"Script modified; context reset.\");\n        }\n\n        // We need to register this context with the inspector, otherwise errors won't be\n        // reported. But we want it to be un-registered as soon as the script has been\n        // compiled, otherwise the inspector will end up with multiple contexts active which\n        // is very confusing for the user (since they'll have to select from the drop-down\n        // which context to use).\n        //\n        // (For modules, the context was already registered by `setupContext()`, above.\n        KJ_IF_SOME(i, isolate->impl->inspector) {\n          if (!modular) {\n            i.get()->contextCreated(\n                v8_inspector::V8ContextInfo(context, 1, jsg::toInspectorStringView(\"Compiler\")));\n          }\n        } else {\n        }  // Here to squash a compiler warning\n        KJ_DEFER({\n          if (!modular) {\n            KJ_IF_SOME(i, isolate->impl->inspector) {\n              i.get()->contextDestroyed(context);\n            } else {\n            }  // Here to squash a compiler warning\n          }\n        });\n\n        v8::TryCatch catcher(lock.v8Isolate);\n        ExceptionOrDuration limitErrorOrTime = 0 * kj::NANOSECONDS;\n\n        try {\n          try {\n            KJ_SWITCH_ONEOF(source.variant) {\n              KJ_CASE_ONEOF(script, ScriptSource) {\n                // This path is used for the older, service worker syntax workers.\n\n                if (script.capnpSchemas.size() > 0) {\n                  // const_cast OK because we hold the isolate lock.\n                  auto& schemaLoader = const_cast<capnp::SchemaLoader&>(getSchemaLoader());\n                  for (auto node: script.capnpSchemas) {\n                    schemaLoader.load(node);\n                  }\n                }\n\n                impl->globals =\n                    isolate->getApi().compileServiceWorkerGlobals(lock, script, *isolate);\n\n                {\n                  // It's unclear to me if CompileUnboundScript() can get trapped in any\n                  // infinite loops or excessively-expensive computation requiring a time\n                  // limit. We'll go ahead and apply a time limit just to be safe. Don't\n                  // add it to the rollover bank, though.\n                  auto limitScope =\n                      isolate->getLimitEnforcer().enterStartupJs(lock, limitErrorOrTime);\n                  impl->unboundScriptOrMainModule =\n                      jsg::NonModuleScript::compile(lock, script.mainScript, script.mainScriptName);\n                }\n              }\n\n              KJ_CASE_ONEOF(modulesSource, ModulesSource) {\n                // This path is used for the new ESM worker syntax.\n\n                if (modulesSource.capnpSchemas.size() > 0) {\n                  // const_cast OK because we hold the isolate lock.\n                  auto& schemaLoader = const_cast<capnp::SchemaLoader&>(getSchemaLoader());\n                  for (auto node: modulesSource.capnpSchemas) {\n                    schemaLoader.load(node);\n                  }\n                }\n\n                if (!isolate->getApi().getFeatureFlags().getNewModuleRegistry()) {\n                  kj::Own<void> limitScope;\n                  if (modulesSource.isPython) {\n                    limitScope =\n                        isolate->getLimitEnforcer().enterStartupPython(js, limitErrorOrTime);\n                  } else {\n                    limitScope = isolate->getLimitEnforcer().enterStartupJs(js, limitErrorOrTime);\n                  }\n                  impl->configureDynamicImports(lock, *jsg::ModuleRegistry::from(lock));\n                  isolate->getApi().compileModules(\n                      lock, modulesSource, *isolate, kj::mv(artifacts), parentSpan.addRef());\n                }\n                impl->unboundScriptOrMainModule = kj::Path::parse(modulesSource.mainModule);\n              }\n            }\n\n            parseMetrics->done();\n          } catch (const kj::Exception& e) {\n            lock.throwException(kj::cp(e));\n            // lock.throwException() here will throw a jsg::JsExceptionThrown which we catch\n            // in the outer try/catch.\n          }\n        } catch (const jsg::JsExceptionThrown&) {\n          reportStartupError(id, lock, isolate->impl->inspector, isolate->getLimitEnforcer(),\n              kj::mv(limitErrorOrTime), catcher, errorReporter, impl->permanentException,\n              parentSpan.addRef(), dynamicEnvBuilder != kj::none);\n        }\n      });\n    });\n  });\n}\n\nvoid Worker::Script::installVirtualFileSystemOnContext(v8::Local<v8::Context> context) const {\n  jsg::setAlignedPointerInEmbedderData(context, jsg::ContextPointerSlot::VIRTUAL_FILE_SYSTEM,\n      const_cast<VirtualFileSystem*>(impl->vfs.get()));\n}\n\nconst capnp::SchemaLoader& Worker::Script::getSchemaLoader() const {\n  KJ_IF_SOME(moduleRegistry, impl->maybeNewModuleRegistry) {\n    return moduleRegistry->getSchemaLoader();\n  } else {\n    return *KJ_ASSERT_NONNULL(impl->maybeSchemaLoader);\n  }\n}\n\nkj::Own<const Worker::Isolate::WeakIsolateRef> Worker::Isolate::getWeakRef() const {\n  return weakIsolateRef->addRef();\n}\n\nkj::StringPtr Worker::Isolate::getUuid() const {\n  // As of this writing, getUuid() is only used by actors, for metrics. We don't want to bother\n  // generating it if not used. The call site does not have nor want an isolate lock, so we use a\n  // kj::Lazy to make initialization thread-safe.\n  return impl->uuid.get(\n      [](kj::SpaceFor<kj::String>& space) { return space.construct(randomUUID(kj::none)); });\n}\n\nWorker::Isolate::~Isolate() noexcept(false) {\n  metrics->teardownStarted();\n\n  // Update the isolate stats one last time to make sure we're accurate for cleanup in\n  // `evicted()`.\n  limitEnforcer->reportMetrics(*metrics);\n\n  metrics->evicted();\n  weakIsolateRef->invalidate();\n  // The cpuLimitNearlyExceededCallback may hold references to objects owned by the isolate and\n  // their destructors need the isolate to still exist. So destroy them before we destroy the\n  // isolate.\n  *cpuLimitNearlyExceededCallback.lockExclusive() = kj::none;\n\n  // Make sure to destroy things under lock. This lock should never be contended since the isolate\n  // is about to be destroyed, but we have to take the lock in order to enter the isolate.\n  // It's also important that we lock one last time, in order to destroy any remaining workers in\n  // worker destruction queue.\n  jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {\n    Isolate::Impl::Lock recordedLock(*this, Worker::Lock::TakeSynchronously(kj::none), stackScope);\n    metrics->teardownLockAcquired();\n    auto inspector = kj::mv(impl->inspector);\n    auto dropTraceAsyncContextKey = kj::mv(traceAsyncContextKey);\n    // The Rust Realm must be dropped under lock since Realm::drop() accesses V8 globals\n    // and calls drop functions that may interact with V8.\n    auto dropRealm = kj::mv(impl->realm);\n\n    // Release all tracked WASM instance entries while V8 is still alive. Each entry holds a\n    // shared_ptr<v8::BackingStore> whose destructor who needs the isolate to still be alive.\n    // This is analogous to the cpuTimeLimitNearlyExceededCallback detaching above ^^^\n    limitEnforcer->getTrackedWasmInstances().clear(*recordedLock.lock);\n  });\n}\n\nWorker::Script::~Script() noexcept(false) {\n  // Make sure to destroy things under lock.\n  // TODO(perf): It could make sense to try to obtain an async lock before destroying a script if\n  //   multiple scripts are co-located in the same isolate. As of this writing, that doesn't happen\n  //   except in preview. In any case, Scripts are destroyed in the GC thread, where we don't care\n  //   too much about lock latency.\n  jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {\n    Isolate::Impl::Lock recordedLock(\n        *isolate, Worker::Lock::TakeSynchronously(kj::none), stackScope);\n    KJ_IF_SOME(c, impl->moduleContext) {\n      recordedLock.disposeContext(kj::mv(c));\n    }\n    impl = nullptr;\n  });\n}\n\nconst Worker::Isolate& Worker::Isolate::from(jsg::Lock& js) {\n  auto ptr = js.v8Isolate->GetData(jsg::SET_DATA_ISOLATE);\n  KJ_ASSERT(ptr != nullptr);\n  return *static_cast<const Worker::Isolate*>(ptr);\n}\n\nbool Worker::Isolate::Impl::Lock::checkInWithLimitEnforcer(Worker::Isolate& isolate) {\n  shouldReportIsolateMetrics = true;\n  return limitEnforcer.exitJs(*lock);\n}\n\nkj::Maybe<kj::Function<void(void)>> Worker::Isolate::getCpuLimitNearlyExceededCallback() const {\n  auto lock = cpuLimitNearlyExceededCallback.lockExclusive();\n  KJ_IF_SOME(cb, *lock) {\n    return cb.reference();\n  }\n  return kj::none;\n}\n\nvoid Worker::Isolate::setCpuLimitNearlyExceededCallback(kj::Function<void(void)> cb) const {\n  auto lock = cpuLimitNearlyExceededCallback.lockExclusive();\n  // Make sure we don't reassign the callback so we don't invalidate references we've passed out.\n  if (*lock == kj::none) {\n    *lock = kj::mv(cb);\n    return;\n  }\n  kj::throwRecoverableException(KJ_EXCEPTION(\n      FAILED, \"Python Workers Internal Error: CpuLimitNearlyExceededCallback already set\"));\n}\n\nvoid Worker::Isolate::registerTrackedWasmInstance(jsg::Lock& js,\n    kj::Array<kj::byte> memory,\n    kj::Maybe<uint32_t> signalOffset,\n    uint32_t terminatedOffset) const {\n  // Register the WASM module for receiving shutdown signals. The signal handler will\n  // iterate the list unconditionally when CPU time is nearly exhausted.\n  limitEnforcer->getTrackedWasmInstances().registerSignal(\n      js, kj::mv(memory), kj::mv(signalOffset), terminatedOffset);\n}\n\n// EW-1319: Set WebAssembly.Module @@HasInstance\n//\n// The instanceof operator can be changed by setting the @@HasInstance method\n// on the object, https://tc39.es/ecma262/#sec-instanceofoperator.\nvoid setWebAssemblyModuleHasInstance(jsg::Lock& lock, v8::Local<v8::Context> context) {\n  JSG_WITHIN_CONTEXT_SCOPE(lock, context, [&](jsg::Lock& lock) {\n    auto instanceof = [](const v8::FunctionCallbackInfo<v8::Value>& info) {\n      jsg::Lock::from(info.GetIsolate()).withinHandleScope([&] {\n        info.GetReturnValue().Set(info[0]->IsWasmModuleObject());\n      });\n    };\n    v8::Local<v8::Function> function = jsg::check(v8::Function::New(context, instanceof));\n\n    auto webAssembly =\n        KJ_ASSERT_NONNULL(lock.global().get(lock, \"WebAssembly\").tryCast<jsg::JsObject>());\n    auto module = KJ_ASSERT_NONNULL(webAssembly.get(lock, \"Module\").tryCast<jsg::JsObject>());\n\n    jsg::check(v8::Local<v8::Object>(module)->DefineOwnProperty(\n        context, v8::Symbol::GetHasInstance(lock.v8Isolate), function));\n  });\n}\n\n// Installs a shim around WebAssembly.instantiate and WebAssembly.Instance that hooks into the\n// shutdown signal if it exists\nvoid shimWebAssemblyInstantiate(jsg::Lock& lock, v8::Local<v8::Context> context) {\n  // We need to enter the context because this function compiles and executes JavaScript via\n  // v8::Script::Compile/Run. setupContext() is called before JSG_WITHIN_CONTEXT_SCOPE, so the\n  // context is not yet entered at this point.\n  v8::Context::Scope contextScope(context);\n\n  auto webAssembly =\n      KJ_ASSERT_NONNULL(lock.global().get(lock, \"WebAssembly\").tryCast<jsg::JsObject>());\n\n  // Create a C++ callback that the JS shims call to register a {memory, signalOffset,\n  // terminatedOffset} tuple.\n  // __registerTrackedWasmInstance(memory: WebAssembly.Memory, signalOffset: number,\n  //                              terminatedOffset: number)\n  // signalOffset may be -1, indicating the module did not export __instance_signal.\n  auto registerCb = [](const v8::FunctionCallbackInfo<v8::Value>& info) {\n    auto& js = jsg::Lock::from(info.GetIsolate());\n    js.withinHandleScope([&] {\n      if (info.Length() < 3 || !info[0]->IsWasmMemoryObject() || !info[1]->IsNumber() ||\n          !info[2]->IsUint32()) {\n        js.v8Isolate->ThrowException(js.str(\n            \"registerTrackedWasmInstance: expected (WebAssembly.Memory, number, uint32)\"_kj));\n        return;\n      }\n      auto memory = info[0].As<v8::WasmMemoryObject>();\n      // signalOffset is -1 when __instance_signal was not exported.\n      auto signalRaw = info[1].As<v8::Number>()->Value();\n      kj::Maybe<uint32_t> signalOffset;\n      if (signalRaw >= 0) {\n        signalOffset = static_cast<uint32_t>(signalRaw);\n      }\n      auto terminatedOffset = info[2].As<v8::Uint32>()->Value();\n      auto backingStore = memory->Buffer()->GetBackingStore();\n      auto wasmMemory =\n          kj::arrayPtr(static_cast<kj::byte*>(backingStore->Data()), backingStore->ByteLength())\n              .attach(kj::mv(backingStore));\n      KJ_IF_SOME(e, kj::runCatchingExceptions([&] {\n        Worker::Isolate::from(js).registerTrackedWasmInstance(\n            js, kj::mv(wasmMemory), signalOffset, terminatedOffset);\n      })) {\n        js.v8Isolate->ThrowException(js.exceptionToJs(kj::mv(e)).getHandle(js));\n      }\n    });\n  };\n  auto registerFn = jsg::check(v8::Function::New(context, registerCb));\n\n  // Build the shim in JavaScript. It wraps both WebAssembly.instantiate (async) and\n  // WebAssembly.Instance (sync constructor).\n  auto shimScript =\n      jsg::NonModuleScript::compile(lock, WASM_INSTANTIATE_SHIM, \"wasm-instantiate-shim.js\"_kj);\n  auto shimFn = KJ_ASSERT_NONNULL(shimScript.runAndReturn(lock).tryCast<jsg::JsFunction>());\n\n  // Grab the originals before they are replaced.\n  auto originalInstantiate = webAssembly.get(lock, \"instantiate\");\n  auto originalInstance = webAssembly.get(lock, \"Instance\");\n\n  // Call the factory — it mutates `wa` in place.\n  shimFn.call(lock, lock.global(), originalInstantiate, originalInstance,\n      jsg::JsFunction(registerFn), jsg::JsObject(webAssembly));\n}\n\nvoid Worker::setupContext(\n    jsg::Lock& lock, v8::Local<v8::Context> context, const LoggingOptions& loggingOptions) {\n  // Set WebAssembly.Module @@HasInstance\n  setWebAssemblyModuleHasInstance(lock, context);\n\n  // Shim WebAssembly.instantiate to detect modules exporting \"__instance_signal\".\n  if (util::Autogate::isEnabled(util::AutogateKey::WASM_SHUTDOWN_SIGNAL_SHIM)) {\n    shimWebAssemblyInstantiate(lock, context);\n  }\n\n  // We replace the default V8 console.log(), etc. methods, to give the worker access to\n  // logged content, and log formatted values to stdout/stderr locally.\n  auto global = context->Global();\n  auto consoleStr = jsg::v8StrIntern(lock.v8Isolate, \"console\");\n  auto console = jsg::check(global->Get(context, consoleStr)).As<v8::Object>();\n\n  auto setHandler = [&](const char* method, LogLevel level) {\n    auto methodStr = jsg::v8StrIntern(lock.v8Isolate, method);\n    v8::Global<v8::Function> original(\n        lock.v8Isolate, jsg::check(console->Get(context, methodStr)).As<v8::Function>());\n\n    auto f = lock.wrapSimpleFunction(context,\n        [loggingOptions, level, original = kj::mv(original)](\n            jsg::Lock& js, const v8::FunctionCallbackInfo<v8::Value>& info) {\n      handleLog(js, loggingOptions, level, original, info);\n    });\n    jsg::check(console->Set(context, methodStr, f));\n  };\n\n  setHandler(\"debug\", LogLevel::DEBUG_);\n  setHandler(\"error\", LogLevel::ERROR);\n  setHandler(\"info\", LogLevel::INFO);\n  setHandler(\"log\", LogLevel::LOG);\n  setHandler(\"warn\", LogLevel::WARN);\n}\n// =======================================================================================\n\nnamespace {\nkj::Maybe<jsg::JsObject> tryResolveMainModule(jsg::Lock& js,\n    const kj::Path& mainModule,\n    jsg::JsContext<api::ServiceWorkerGlobalScope>& jsContext,\n    const Worker::Script& script,\n    ExceptionOrDuration& limitErrorOrTime) {\n  kj::Own<void> limitScope;\n  if (script.isPython()) {\n    limitScope = script.getIsolate().getLimitEnforcer().enterStartupPython(js, limitErrorOrTime);\n  } else {\n    limitScope = script.getIsolate().getLimitEnforcer().enterStartupJs(js, limitErrorOrTime);\n  }\n\n  KJ_DEFER({\n    if (limitErrorOrTime.is<kj::Exception>()) {\n      // If we hit the limit in PerformMicrotaskCheckpoint() we may not have actually\n      // thrown an exception.\n      throw jsg::JsExceptionThrown();\n    }\n  });\n\n  // Before resolving the main module, if both nodejs_compat_v2 and the new\n  // module registry are enabled, let's pre-resolve the process and buffer modules.\n  // Why? Great question! Resolving these modules synchronously causes the microtask\n  // queue to be pumped, which we don't actually want to do while resolving the main\n  // module until we are ready. Both process and buffer are exposed via globalThis\n  // when the nodejs_compat_v2 flag is used, and if the top-level scope is accessing\n  // either globalThis.process or globalThis.buffer, then we need to make sure that\n  // the modules are already resolved so we don't pump the microtask queue while\n  // synchronously accessing those globals. Resolving them here ensures that they are\n  // ready to go before we begin evaluating the main module.\n  auto featureFlags = FeatureFlags::get(js);\n  if (featureFlags.getNodeJsCompatV2() && featureFlags.getNewModuleRegistry()) {\n    JSG_REQUIRE_NONNULL(js.resolveModule(\"node:process\", jsg::RequireEsm::YES), Error,\n        \"Failed to initialize node:process module\");\n    JSG_REQUIRE_NONNULL(js.resolveModule(\"node:buffer\", jsg::RequireEsm::YES), Error,\n        \"Failed to initialize node:buffer module\");\n  }\n\n  // When enable_nodejs_global_timers is enabled, load the module that makes all 6 timer\n  // functions (setTimeout, setInterval, clearTimeout, clearInterval, setImmediate,\n  // clearImmediate) available on globalThis as Node.js-compatible versions from node:timers.\n  if (featureFlags.getEnableNodejsGlobalTimers()) {\n    JSG_REQUIRE_NONNULL(js.resolveInternalModule(\"node-internal:internal_timers_global_override\"),\n        Error, \"Failed to initialize node-internal:internal_timers_global_override module\");\n  }\n\n  return js.resolveModule(mainModule.toString(false), jsg::RequireEsm::YES);\n}\n}  // anonymous namespace\n\nWorker::Worker(kj::Own<const Script> scriptParam,\n    kj::Own<WorkerObserver> metricsParam,\n    kj::FunctionParam<void(jsg::Lock& lock,\n        const Api& api,\n        v8::Local<v8::Object> target,\n        v8::Local<v8::Object> ctxExports)> compileBindings,\n    IsolateObserver::StartType startType,\n    SpanParent parentSpan,\n    LockType lockType,\n    kj::Maybe<ValidationErrorReporter&> errorReporter,\n    kj::Maybe<kj::Duration&> startupTime)\n    : script(kj::mv(scriptParam)),\n      metrics(kj::mv(metricsParam)),\n      impl(kj::heap<Impl>()) {\n  // Enter/lock isolate.\n  jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {\n    Isolate::Impl::Lock recordedLock(*script->isolate, lockType, stackScope);\n    auto& lock = *recordedLock.lock;\n\n    // If we throw an exception, it's important that `impl` is destroyed under lock.\n    KJ_ON_SCOPE_FAILURE({\n      auto implToDestroy = kj::mv(impl);\n      KJ_IF_SOME(c, implToDestroy->context) {\n        recordedLock.disposeContext(kj::mv(c));\n      } else {\n        // Else block to avoid dangling else clang warning.\n      }\n    });\n\n    auto maybeMakeSpan = [&](auto operationName) -> SpanBuilder {\n      auto span = parentSpan.newChild(kj::mv(operationName));\n      if (span.isObserved()) {\n        span.setTag(\"truncated_script_id\"_kjc, truncateScriptId(script->getId()));\n      }\n      return span;\n    };\n\n    auto currentSpan = maybeMakeSpan(\"lw:new_startup_metrics\"_kjc);\n\n    auto startupMetrics = metrics->startup(startType);\n\n    currentSpan = maybeMakeSpan(\"lw:new_context\"_kjc);\n\n    // Create a stack-allocated handle scope.\n    lock.withinHandleScope([&] {\n      jsg::JsContext<api::ServiceWorkerGlobalScope>* jsContext;\n\n      KJ_IF_SOME(c, script->impl->moduleContext) {\n        // Use the shared context from the script.\n        // const_cast OK because guarded by `lock`.\n        jsContext = const_cast<jsg::JsContext<api::ServiceWorkerGlobalScope>*>(&c);\n        currentSpan.setTag(\"module_context\"_kjc, true);\n      } else {\n        // Create a new context.\n        jsContext = &this->impl->context.emplace(script->isolate->getApi().newContext(lock,\n            {\n              .newModuleRegistry = script->impl->getNewModuleRegistry(),\n              .schemaLoader = script->getSchemaLoader(),\n            }));\n      }\n\n      v8::Local<v8::Context> context = KJ_REQUIRE_NONNULL(jsContext).getHandle(lock);\n\n      // Install the virtual file system on the context. Keep in mind that for service\n      // worker style workers, the Script may be shared between multiple Workers, even\n      // across different accounts. Currently, the internal state of the VFS does not\n      // contain any account-specific or worker-specific state so this is OK for now.\n      // The VFS would contain the script files only and any temporary files created\n      // within the context of a worker are always stored in temporary space attached\n      // to the IoContext or the current execution context. If we extend these capabilities\n      // in the future, we may need to revisit this. For modular workers, this is not\n      // an issue since each Worker gets its own Script instance.\n      script->installVirtualFileSystemOnContext(context);\n\n      if (!script->modular) {\n        recordedLock.setupContext(context);\n      }\n\n      if (script->impl->unboundScriptOrMainModule == nullptr) {\n        // Script failed to parse. Act as if the script was empty -- i.e. do nothing.\n        impl->permanentException =\n            script->impl->permanentException.map([](auto& e) { return kj::cp(e); });\n        return;\n      }\n\n      // Enter the context for compiling and running the script.\n      JSG_WITHIN_CONTEXT_SCOPE(lock, context, [&](jsg::Lock& js) {\n        v8::TryCatch catcher(lock.v8Isolate);\n        ExceptionOrDuration limitErrorOrTime = 0 * kj::NANOSECONDS;\n\n        try {\n          try {\n            currentSpan = maybeMakeSpan(\"lw:globals_instantiation\"_kjc);\n\n            v8::Local<v8::Object> bindingsScope;\n            if (script->isModular()) {\n              // Use `env` variable.\n              bindingsScope = v8::Object::New(lock.v8Isolate);\n              if (!FeatureFlags::get(js).getDisableImportableEnv()) {\n                lock.setWorkerEnv(lock.v8Ref(bindingsScope));\n              }\n            } else {\n              // Use global-scope bindings.\n              bindingsScope = context->Global();\n            }\n\n            // Load globals.\n            // const_cast OK because we hold the lock.\n            for (auto& global: const_cast<Script&>(*script).impl->globals) {\n              lock.v8Set(bindingsScope, global.name, global.value);\n            }\n\n            v8::Local<v8::Object> ctxExports = v8::Object::New(lock.v8Isolate);\n\n            compileBindings(lock, script->isolate->getApi(), bindingsScope, ctxExports);\n\n            // Execute script.\n            currentSpan = maybeMakeSpan(\"lw:top_level_execution\"_kjc);\n\n            // Ensure that our worker top-level bootstrap has a temporary directory\n            // storage scope. This is used to store temporary files created within\n            // the top-level evaluation of the worker. With this instantiated on\n            // the stack, temporary files will be cleaned up when the scope is\n            // destroyed, which means any temporary files created in the top-level\n            // evaluation will *not* be available to the worker after the top-level\n            // evaluation is complete.\n            TmpDirStoreScope tmpDirStoreScope;\n\n            // We allow eval and new Function() during startup, becaues startup time is entirely\n            // deterministic, so we can easily reproduce the input to eval() by just running the\n            // worker again. We do not allow eval() at runtime because we need to have a record of\n            // all code that executes in production for forensic purposes, and at runtime the input\n            // to eval() could have come from a remote source on which we don't have a record.\n            js.setAllowEval(FeatureFlags::get(js).getAllowEvalDuringStartup());\n            KJ_DEFER(js.setAllowEval(false));\n\n            KJ_SWITCH_ONEOF(script->impl->unboundScriptOrMainModule) {\n              KJ_CASE_ONEOF(unboundScript, jsg::NonModuleScript) {\n                auto limitScope =\n                    script->isolate->getLimitEnforcer().enterStartupJs(lock, limitErrorOrTime);\n                unboundScript.run(lock);\n                // Flush microtasks enqueued during top-level script evaluation.\n                // Without this flush, microtasks (e.g. promise continuations from async\n                // initialization) remain on the per-isolate microtask queue and can leak across\n                // V8 contexts when multiple Workers share an isolate (same script, different\n                // zones). The leaked microtasks then execute under the wrong IoContext, making\n                // things go boom.\n                lock.runMicrotasks();\n              }\n              KJ_CASE_ONEOF(mainModule, kj::Path) {\n                KJ_IF_SOME(ns,\n                    tryResolveMainModule(lock, mainModule, *jsContext, *script, limitErrorOrTime)) {\n                  impl->env = lock.v8Ref(bindingsScope.As<v8::Value>());\n                  impl->ctxExports = lock.v8Ref(ctxExports.As<v8::Value>());\n\n                  if (!FeatureFlags::get(js).getDisableImportableEnv()) {\n                    lock.setWorkerExports(lock.v8Ref(ctxExports));\n                  }\n\n                  auto& api = script->isolate->getApi();\n                  auto handlers = api.unwrapExports(lock, ns);\n                  auto entrypointClasses = api.getEntrypointClasses(lock);\n\n                  for (auto& handler: handlers.fields) {\n                    KJ_SWITCH_ONEOF(handler.value) {\n                      KJ_CASE_ONEOF(obj, api::ExportedHandler) {\n                        obj.env = lock.v8Ref(bindingsScope.As<v8::Value>());\n                        // Historically, non-class-based handlers reused the same ctx object for all requests.\n                        // This was an accident, but some Workers depend on it.\n                        // Newer worker with the unique_ctx_per_invocation will allocate a new ctx for every request.\n                        obj.ctx = js.alloc<api::ExecutionContext>(lock, jsg::JsValue(ctxExports));\n\n                        // Python Workers append all durable objects, worker entrypoint and workflow\n                        // entrypoint classes in the pythonEntrypoints named export.\n                        bool isPythonWorker = FeatureFlags::get(js).getPythonWorkers();\n                        if (handler.name == \"pythonEntrypoints\" && isPythonWorker) {\n                          auto handle = obj.self.getHandle(js);\n                          auto dict = js.toDict(handle);\n                          for (auto& field: dict.fields) {\n                            auto unwrapped = api.unwrapExport(lock, field.value);\n                            KJ_SWITCH_ONEOF(unwrapped) {\n                              KJ_CASE_ONEOF(cls, EntrypointClass) {\n                                processEntrypointClass(\n                                    js, kj::mv(cls), entrypointClasses, kj::mv(field.name));\n                              }\n                              KJ_CASE_ONEOF(obj, api::ExportedHandler) {\n                                KJ_FAIL_ASSERT(\"Expected EntrypointClass\");\n                              }\n                            }\n                          }\n                        } else {\n                          impl->namedHandlers.insert(kj::mv(handler.name), kj::mv(obj));\n                        }\n                      }\n                      KJ_CASE_ONEOF(cls, EntrypointClass) {\n                        processEntrypointClass(\n                            js, kj::mv(cls), entrypointClasses, kj::mv(handler.name));\n                      }\n                    }\n                  }\n                } else {\n                  JSG_FAIL_REQUIRE(TypeError, \"Main module name is not present in bundle.\");\n                }\n              }\n            }\n\n            KJ_IF_SOME(s, startupTime) {\n              KJ_SWITCH_ONEOF(limitErrorOrTime) {\n                KJ_CASE_ONEOF(startupTimeElapsed, kj::Duration) {\n                  s = startupTimeElapsed;\n                }\n                KJ_CASE_ONEOF(limitError, kj::Exception) {}\n              }\n            } else {\n            }\n            startupMetrics->done();\n          } catch (const kj::Exception& e) {\n            lock.throwException(kj::cp(e));\n            // lock.throwException() here will throw a jsg::JsExceptionThrown which we catch\n            // in the outer try/catch.\n          }\n        } catch (const jsg::JsExceptionThrown&) {\n          reportStartupError(script->id, lock, script->isolate->impl->inspector,\n              script->isolate->getLimitEnforcer(), kj::mv(limitErrorOrTime), catcher, errorReporter,\n              impl->permanentException, currentSpan, script->getDynamicEnvBuilder() != kj::none);\n        }\n      });\n\n      // Reset this back to its default after startup execution\n      // Leaving it on comes at the expense of collecting stack traces for all thrown exceptions\n      // Ref: https://github.com/cloudflare/workerd/issues/5332\n      if (script->isolate->impl->inspector == kj::none) {\n        lock.v8Isolate->SetCaptureStackTraceForUncaughtExceptions(false);\n      }\n    });\n  });\n}\n\nWorker::~Worker() noexcept(false) {\n  metrics->teardownStarted();\n\n  auto& isolateImpl = *script->getIsolate().impl;\n  auto lock = isolateImpl.workerDestructionQueue.lockExclusive();\n\n  // Previously, this metric meant the isolate lock. We might as well make it mean the worker\n  // destruction queue lock now to verify it is much less-contended than the isolate lock.\n  metrics->teardownLockAcquired();\n\n  // Defer destruction of our V8 objects, in particular our jsg::Context, which requires some\n  // finalization.\n  lock->push(kj::mv(impl));\n}\n\nvoid Worker::processEntrypointClass(jsg::Lock& js,\n    EntrypointClass cls,\n    EntrypointClasses entrypointClasses,\n    kj::String handlerName) {\n  js.withinHandleScope([&]() {\n    jsg::JsObject handle(KJ_ASSERT_NONNULL(cls.tryGetHandle(js.v8Isolate)));\n\n    for (;;) {\n      if (handle == entrypointClasses.durableObject) {\n        impl->actorClasses.insert(kj::mv(handlerName),\n            ActorClassInfo{\n              .cls = kj::mv(cls),\n              .missingSuperclass = false,\n            });\n        return;\n      } else if (handle == entrypointClasses.workerEntrypoint) {\n        impl->statelessClasses.insert(kj::mv(handlerName), kj::mv(cls));\n        return;\n      } else if (handle == entrypointClasses.workflowEntrypoint) {\n        impl->workflowClasses.insert(kj::mv(handlerName), kj::mv(cls));\n        return;\n      }\n\n      handle = KJ_UNWRAP_OR(handle.getPrototype(js).tryCast<jsg::JsObject>(), {\n        // Reached end of prototype chain.\n\n        // For historical reasons, we assume a class is a Durable Object\n        // class if it doesn't inherit anything.\n        // TODO(someday): Log a warning suggesting extending DurableObject.\n        // TODO(someday): Introduce a compat flag that makes this required.\n        impl->actorClasses.insert(kj::mv(handlerName),\n            ActorClassInfo{\n              .cls = kj::mv(cls),\n              .missingSuperclass = true,\n            });\n        return;\n      });\n    }\n  });\n}\n\nvoid Worker::handleLog(jsg::Lock& js,\n    const LoggingOptions& loggingOptions,\n    LogLevel level,\n    const v8::Global<v8::Function>& original,\n    const v8::FunctionCallbackInfo<v8::Value>& info) {\n  // Call original V8 implementation so messages sent to connected inspector if any\n  auto context = js.v8Context();\n  int length = info.Length();\n  // to pass additional arguments from this function to js' `formatLog` we add arguments to the end\n  // of the arguments vector, then in formatLog we `pop` these from the vector.\n  // 3 is just the number of args we currently pass.\n  v8::LocalVector<v8::Value> args(js.v8Isolate, length + 3);\n  for (auto i: kj::zeroTo(length)) args[i] = info[i];\n  jsg::check(original.Get(js.v8Isolate)->Call(context, info.This(), length, args.data()));\n\n  // The TryCatch is initialized here to catch cases where the v8 isolate's execution is\n  // terminating, usually as a result of an infinite loop. We need to perform the initialization\n  // here because `message` is called multiple times.\n  v8::TryCatch tryCatch(js.v8Isolate);\n  auto message = [&]() {\n    int length = info.Length();\n    kj::Vector<kj::String> stringified(length);\n    for (auto i: kj::zeroTo(length)) {\n      auto arg = info[i];\n      // serializeJson and v8::Value::ToString can throw JS exceptions\n      // (e.g. for recursive objects) so we eat them here, to ensure logging and non-logging code\n      // have the same exception behavior.\n      if (!tryCatch.CanContinue()) {\n        stringified.add(kj::str(\"{}\"));\n        break;\n      }\n      // The following code checks the `arg` to see if it should be serialised to JSON.\n      //\n      // We use the following criteria: if arg is null, a number, a boolean, an array, a string, an\n      // object or it defines a `toJSON` property that is a function, then the arg gets serialised\n      // to JSON.\n      //\n      // Otherwise we stringify the argument.\n      js.withinHandleScope([&] {\n        auto context = js.v8Context();\n        bool shouldSerialiseToJson = false;\n        if (arg->IsNull() || arg->IsNumber() || arg->IsArray() || arg->IsBoolean() ||\n            arg->IsString() ||\n            arg->IsUndefined()) {  // This is special cased for backwards compatibility.\n          shouldSerialiseToJson = true;\n        }\n        if (arg->IsObject()) {\n          v8::Local<v8::Object> obj = arg.As<v8::Object>();\n          v8::Local<v8::Object> freshObj = v8::Object::New(js.v8Isolate);\n\n          // Determine whether `obj` is constructed using `{}` or `new Object()`. This ensures\n          // we don't serialise values like Promises to JSON.\n          if (obj->GetPrototypeV2()->SameValue(freshObj->GetPrototypeV2()) ||\n              obj->GetPrototypeV2()->IsNull()) {\n            shouldSerialiseToJson = true;\n          }\n\n          // Check if arg has a `toJSON` property which is a function.\n          auto toJSONStr = jsg::v8StrIntern(js.v8Isolate, \"toJSON\"_kj);\n          v8::MaybeLocal<v8::Value> toJSON = obj->GetRealNamedProperty(context, toJSONStr);\n          if (!toJSON.IsEmpty()) {\n            if (jsg::check(toJSON)->IsFunction()) {\n              shouldSerialiseToJson = true;\n            }\n          }\n        }\n\n        if (kj::runCatchingExceptions([&]() {\n          // On the off chance the the arg is the request.cf object, let's make\n          // sure we do not log proxied fields here.\n          if (shouldSerialiseToJson) {\n            auto s = js.serializeJson(arg);\n            // serializeJson returns the string \"undefined\" for some values (undefined,\n            // Symbols, functions).  We remap these values to null to ensure valid JSON output.\n            if (s == \"undefined\"_kj) {\n              stringified.add(kj::str(\"null\"));\n            } else {\n              stringified.add(kj::mv(s));\n            }\n          } else {\n            stringified.add(js.serializeJson(jsg::check(arg->ToString(context))));\n          }\n        }) != kj::none) {\n          stringified.add(kj::str(\"{}\"));\n        };\n      });\n    }\n    return kj::str(\"[\", kj::delimited(stringified, \", \"_kj), \"]\");\n  };\n\n  // Only check tracing if console.log() was not invoked at the top level.\n  KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n    KJ_IF_SOME(tracer, ioContext.getWorkerTracer()) {\n      auto timestamp = ioContext.now();\n      tracer.addLog(ioContext.getInvocationSpanContext(), timestamp, level, message());\n    }\n  }\n\n  if (loggingOptions.consoleMode == Worker::ConsoleMode::INSPECTOR_ONLY) {\n    // Lets us dump console.log()s to stdout when running test-runner with --verbose flag, to make\n    // it easier to debug tests.  Note that when --verbose is not passed, KJ_LOG(INFO, ...) will\n    // not even evaluate its arguments, so `message()` will not be called at all.\n    KJ_LOG(INFO, \"console.log()\", message());\n  } else {\n    // Write to stdio if allowed by console mode. This is making use of our internal\n    // built-in implementation of the node:util inspect API.\n    static const ColorMode COLOR_MODE = permitsColor();\n#if _WIN32\n    static bool STDOUT_TTY = _isatty(_fileno(stdout));\n    static bool STDERR_TTY = _isatty(_fileno(stderr));\n#else\n    static bool STDOUT_TTY = isatty(STDOUT_FILENO);\n    static bool STDERR_TTY = isatty(STDERR_FILENO);\n#endif\n\n    // Log warnings and errors to stderr\n    // Always log to stdout when structuredLogging is enabled.\n    auto useStderr = level >= LogLevel::WARN && !loggingOptions.structuredLogging;\n    auto fd = useStderr ? stderr : stdout;\n    auto tty = useStderr ? STDERR_TTY : STDOUT_TTY;\n    auto colors =\n        COLOR_MODE == ColorMode::ENABLED || (COLOR_MODE == ColorMode::ENABLED_IF_TTY && tty);\n\n    constexpr auto kSpecifier = \"node-internal:internal_inspect\"_kj;\n    auto inspectModule = KJ_ASSERT_NONNULL(js.resolveInternalModule(kSpecifier));\n    v8::Local<v8::Value> formatLogVal = inspectModule.get(js, \"formatLog\"_kj);\n    KJ_ASSERT(formatLogVal->IsFunction());\n    auto formatLog = formatLogVal.As<v8::Function>();\n\n    auto levelStr = logLevelToString(level);\n    args[length] = js.boolean(colors);\n    args[length + 1] = js.boolean(loggingOptions.structuredLogging.toBool());\n    args[length + 2] = js.strIntern(levelStr);\n    auto formatted = js.toString(\n        jsg::check(formatLog->Call(context, js.v8Undefined(), length + 3, args.data())));\n    fprintf(fd, \"%s\\n\", formatted.cStr());\n    fflush(fd);\n  }\n}\n\nWorker::Lock::TakeSynchronously::TakeSynchronously(kj::Maybe<RequestObserver&> requestParam) {\n  KJ_IF_SOME(r, requestParam) {\n    request = &r;\n  }\n}\n\nkj::Maybe<RequestObserver&> Worker::Lock::TakeSynchronously::getRequest() {\n  if (request != nullptr) {\n    return *request;\n  }\n  return kj::none;\n}\n\nstruct Worker::Lock::Impl {\n  Isolate::Impl::Lock recordedLock;\n  jsg::Lock& inner;\n\n  Impl(const Worker& worker, LockType lockType, jsg::V8StackScope& stackScope)\n      : recordedLock(worker.getIsolate(), lockType, stackScope),\n        inner(*recordedLock.lock) {}\n};\n\nWorker::Lock::Lock(const Worker& constWorker, LockType lockType, jsg::V8StackScope& stackScope)\n    :  // const_cast OK because we took out a lock.\n      worker(const_cast<Worker&>(constWorker)),\n      impl(kj::heap<Impl>(worker, lockType, stackScope)) {\n  kj::requireOnStack(this, \"Worker::Lock MUST be allocated on the stack.\");\n}\n\nWorker::Lock::~Lock() noexcept(false) {\n  // const_cast OK because we hold -- nay, we *are* -- a lock on the script.\n  auto& isolate = const_cast<Isolate&>(worker.getIsolate());\n  if (impl->recordedLock.checkInWithLimitEnforcer(isolate)) {\n    isolate.disconnectInspector();\n  }\n}\n\nvoid Worker::Lock::requireNoPermanentException() {\n  KJ_IF_SOME(e, worker.impl->permanentException) {\n    // Block taking lock when worker failed to start up.\n    kj::throwFatalException(kj::cp(e));\n  }\n}\n\nWorker::Lock::operator jsg::Lock&() {\n  return impl->inner;\n}\n\nv8::Isolate* Worker::Lock::getIsolate() {\n  return impl->inner.v8Isolate;\n}\n\nv8::Local<v8::Context> Worker::Lock::getContext() {\n  KJ_IF_SOME(c, worker.impl->context) {\n    return c.getHandle(impl->inner);\n  } else KJ_IF_SOME(c, const_cast<Script&>(*worker.script).impl->moduleContext) {\n    return c.getHandle(impl->inner);\n  } else {\n    KJ_UNREACHABLE;\n  }\n}\n\ntemplate <typename T>\nstatic inline kj::Own<T> fakeOwn(T& ref) {\n  return kj::Own<T>(&ref, kj::NullDisposer::instance);\n}\n\nkj::Maybe<kj::Own<api::ExportedHandler>> Worker::Lock::getExportedHandler(\n    kj::Maybe<kj::StringPtr> name,\n    kj::Maybe<VersionInfo> versionInfo,\n    Frankenvalue props,\n    kj::Maybe<Worker::Actor&> actor) {\n  KJ_IF_SOME(a, actor) {\n    KJ_IF_SOME(h, a.getHandler()) {\n      return fakeOwn(h);\n    }\n  }\n\n  kj::StringPtr n = name.orDefault(\"default\"_kj);\n\n  auto getHandlerFromEntrypointClass =\n      [&](EntrypointClass& cls) -> kj::Maybe<kj::Own<api::ExportedHandler>> {\n    jsg::Lock& js = *this;\n    auto handler = kj::heap(cls(js,\n        js.alloc<api::ExecutionContext>(js,\n            jsg::JsValue(KJ_ASSERT_NONNULL(worker.impl->ctxExports).getHandle(js)), props.toJs(js),\n            kj::mv(versionInfo)),\n        KJ_ASSERT_NONNULL(worker.impl->env).addRef(js)));\n\n    // HACK: We set handler.env and handler.ctx to undefined because we already passed the real\n    //   env and ctx into the constructor, and we want the handler methods to act like they take\n    //   just one parameter.\n    handler->env = js.v8Ref(js.v8Undefined());\n    handler->ctx = kj::none;\n\n    return handler;\n  };\n\n  KJ_IF_SOME(h, worker.impl->namedHandlers.find(n)) {\n    jsg::Lock& js = *this;\n    if (!FeatureFlags::get(js).getReuseCtxAcrossNonclassEvents()) {\n      api::ExportedHandler constructedHandler = h.clone(js);\n      constructedHandler.ctx = js.alloc<api::ExecutionContext>(js,\n          jsg::JsValue(KJ_ASSERT_NONNULL(worker.impl->ctxExports).getHandle(js)), props.toJs(js),\n          kj::mv(versionInfo));\n      return kj::heap(kj::mv(constructedHandler));\n    }\n    return fakeOwn(h);\n  } else KJ_IF_SOME(cls, worker.impl->statelessClasses.find(n)) {\n    return getHandlerFromEntrypointClass(cls);\n  } else KJ_IF_SOME(cls, worker.impl->workflowClasses.find(n)) {\n    return getHandlerFromEntrypointClass(cls);\n  } else if (name == kj::none) {\n    // If the default export was requested, and we didn't find a handler for it, we'll fall back\n    // to addEventListener().\n    //\n    // Note: The original intention was that we only use addEventListener() for\n    //   service-worker-syntax scripts, but apparently the code has long allowed it for\n    //   modules-based script too, if they lacked an `export default`. Yikes! Sadly, there are\n    //   Workers in production relying on this so we are stuck with it.\n    return kj::none;\n  } else {\n    if (worker.impl->actorClasses.find(n) != kj::none) {\n      LOG_ERROR_PERIODICALLY(\"worker is not an actor but class name was requested\", n);\n    } else {\n      LOG_ERROR_PERIODICALLY(\"worker has no such named entrypoint\", n);\n    }\n\n    KJ_FAIL_ASSERT(\"worker_do_not_log; Unable to get exported handler\");\n  };\n}\n\napi::ServiceWorkerGlobalScope& Worker::Lock::getGlobalScope() {\n  return KJ_ASSERT_NONNULL(jsg::getAlignedPointerFromEmbedderData<api::ServiceWorkerGlobalScope>(\n      getContext(), jsg::ContextPointerSlot::GLOBAL_WRAPPER));\n}\n\nTimeoutId::Generator& Worker::Lock::getTimeoutIdGenerator() {\n  return getGlobalScope().timeoutIdGenerator;\n}\n\njsg::AsyncContextFrame::StorageKey& Worker::Lock::getTraceAsyncContextKey() {\n  // const_cast OK because we are a lock on this isolate.\n  auto& isolate = const_cast<Isolate&>(worker.getIsolate());\n  return *(isolate.traceAsyncContextKey);\n}\n\nbool Worker::Lock::isInspectorEnabled() {\n  return worker.script->isolate->impl->inspector != kj::none;\n}\n\nvoid Worker::Lock::logWarning(kj::StringPtr description) {\n  // const_cast OK because we are a lock on this isolate.\n  const_cast<Isolate&>(worker.getIsolate()).logWarning(description, *this);\n}\n\nvoid Worker::Lock::logWarningOnce(kj::StringPtr description) {\n  // const_cast OK because we are a lock on this isolate.\n  const_cast<Isolate&>(worker.getIsolate()).logWarningOnce(description, *this);\n}\n\nvoid Worker::Lock::logErrorOnce(kj::StringPtr description) {\n  // const_cast OK because we are a lock on this isolate.\n  const_cast<Isolate&>(worker.getIsolate()).logErrorOnce(description);\n}\n\nvoid Worker::Lock::logUncaughtException(kj::StringPtr description) {\n  // We don't add the exception to traces here, since it turns out that this path only gets hit by\n  // intermediate exception handling.\n  KJ_IF_SOME(i, worker.script->isolate->impl->inspector) {\n    JSG_WITHIN_CONTEXT_SCOPE(*this, getContext(),\n        [&](jsg::Lock& js) { jsg::sendExceptionToInspector(js, *i.get(), description); });\n  }\n\n  // Run with --verbose to log JS exceptions to stderr. Useful when running tests.\n  KJ_LOG(INFO, \"uncaught exception\", description);\n}\n\nvoid Worker::Lock::logUncaughtException(\n    UncaughtExceptionSource source, const jsg::JsValue& exception, const jsg::JsMessage& message) {\n  // Only add exception to trace when running within an I/O context with a tracer.\n  KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n    KJ_IF_SOME(tracer, ioContext.getWorkerTracer()) {\n      JSG_WITHIN_CONTEXT_SCOPE(*this, getContext(), [&](jsg::Lock& js) {\n        addExceptionToTrace(impl->inner, ioContext, tracer, source, exception,\n            worker.getIsolate().getApi().getErrorInterfaceTypeHandler(*this));\n      });\n    }\n  }\n\n  KJ_IF_SOME(i, worker.script->isolate->impl->inspector) {\n    JSG_WITHIN_CONTEXT_SCOPE(*this, getContext(),\n        [&](jsg::Lock& js) { sendExceptionToInspector(js, *i.get(), source, exception, message); });\n  }\n\n  // Run with --verbose to log JS exceptions to stderr. Useful when running tests.\n  if (kj::_::Debug::shouldLog(::kj::LogSeverity::INFO)) {\n    JSG_WITHIN_CONTEXT_SCOPE(*this, getContext(), [&](jsg::Lock& js) {\n      // Try to log `error.stack` if it exists.\n      KJ_IF_SOME(obj, exception.tryCast<jsg::JsObject>()) {\n        auto stack = obj.get(js, \"stack\");\n        if (!stack.isUndefined()) {\n          KJ_LOG(INFO, \"uncaught exception\", source, stack);\n          return;\n        }\n      } else {\n        // Compiler gives a spurious warning if this `else` isn't here.\n      }\n\n      KJ_LOG(INFO, \"uncaught exception\", source, exception);\n    });\n  }\n}\n\nvoid Worker::Lock::logUncaughtException(UncaughtExceptionSource source, kj::Exception&& exception) {\n  jsg::Lock& js = *this;\n  try {\n    auto jsError = js.exceptionToJsValue(kj::mv(exception),\n        {\n          .trusted = true,\n        });\n    logUncaughtException(source, jsError.getHandle(js));\n  } catch (const jsg::JsExceptionThrown&) {\n    // An exception occurred while trying to convert the exception to a JS value.\n    // With exceptionToJs, this should only happen if the isolate is terminating\n    // because of a fatal error when trying to deserialize a tunneled exception\n    // detail. In this case, we will want to log the original exception instead,\n    // so let's try exceptionToJs again but this time ignoring the detail, and\n    // if it throws again, we'll give up and propagate that exception to the\n    // caller.\n    auto jsError = js.exceptionToJsValue(kj::cp(exception), {.ignoreDetail = true});\n    logUncaughtException(source, jsError.getHandle(js));\n  }\n}\n\nvoid Worker::Lock::reportPromiseRejectEvent(v8::PromiseRejectMessage& message) {\n  getGlobalScope().emitPromiseRejection(*this, message.GetEvent(),\n      jsg::V8Ref<v8::Promise>(getIsolate(), message.GetPromise()),\n      jsg::V8Ref<v8::Value>(getIsolate(), message.GetValue()));\n}\n\nvoid Worker::Lock::validateHandlers(ValidationErrorReporter& errorReporter) {\n  JSG_WITHIN_CONTEXT_SCOPE(*this, getContext(), [&](jsg::Lock& js) {\n    kj::HashSet<kj::StringPtr> ignoredHandlers;\n    ignoredHandlers.insert(\"alarm\"_kj);\n    ignoredHandlers.insert(\"unhandledrejection\"_kj);\n    ignoredHandlers.insert(\"rejectionhandled\"_kj);\n\n    // Helper function to collect methods from a prototype chain\n    auto collectMethodsFromPrototypeChain = [&](jsg::JsValue startProto,\n                                                kj::HashSet<kj::String>& seenNames) {\n      // Find the prototype for `Object` by creating one.\n      auto obj = js.obj();\n      jsg::JsValue prototypeOfObject = obj.getPrototype(js);\n\n      // Walk the prototype chain.\n      jsg::JsValue proto = startProto;\n      for (;;) {\n        auto protoObj = KJ_UNWRAP_OR(proto.tryCast<jsg::JsObject>(), {\n          errorReporter.addError(\n              kj::str(\"Exported value's prototype chain does not end in Object.\"));\n          return;\n        });\n        if (protoObj == prototypeOfObject) {\n          // Reached the prototype for `Object`. Stop here.\n          break;\n        }\n\n        // Awkwardly, the prototype's members are not typically enumerable, so we have to\n        // enumerate them rather directly.\n        jsg::JsArray properties = protoObj.getPropertyNames(js, jsg::KeyCollectionFilter::OWN_ONLY,\n            jsg::PropertyFilter::SKIP_SYMBOLS, jsg::IndexFilter::SKIP_INDICES);\n        for (auto i: kj::zeroTo(properties.size())) {\n          auto name = properties.get(js, i).toString(js);\n          if (name == \"constructor\"_kj) {\n            // Don't treat special method `constructor` as an exported handler.\n            continue;\n          }\n\n          if (!ignoredHandlers.contains(name)) {\n            // Only report each method name once, even if it overrides a method in a superclass.\n            seenNames.upsert(kj::mv(name), [&](auto&, auto&&) {});\n          }\n        }\n\n        proto = protoObj.getPrototype(js);\n      }\n    };\n\n    KJ_IF_SOME(c, worker.impl->context) {\n      // Service workers syntax.\n      auto handlerNames = c->getHandlerNames();\n      kj::Vector<kj::String> handlers;\n      for (auto& name: handlerNames) {\n        if (!ignoredHandlers.contains(name)) {\n          handlers.add(kj::str(name));\n        }\n      }\n      if (handlers.empty()) {\n        errorReporter.addError(\n            kj::str(\"No event handlers were registered. This script does nothing.\"));\n      }\n      errorReporter.addEntrypoint(kj::none, handlers.releaseAsArray());\n    } else {\n      auto report = [&](kj::Maybe<kj::StringPtr> name, api::ExportedHandler& exported) {\n        auto handle = exported.self.getHandle(js);\n        if (handle->IsArray()) {\n          // HACK: toDict() will throw a TypeError if given an array, because jsg::DictWrapper is\n          //   designed to treat arrays as not matching when a dict is expected. However,\n          //   StructWrapper has no such restriction, and therefore an exported array will\n          //   successfully produce an ExportedHandler (presumably with no handler functions), and\n          //   hence we will see it here. Rather than try to correct this inconsistency between\n          //   struct and dict handling (which could have unintended consequences), let's just\n          //   work around by ignoring arrays here.\n          errorReporter.addEntrypoint(name, kj::Array<kj::String>());\n        } else {\n          // Use a HashSet to avoid duplicates when methods exist both as own properties\n          // and in the prototype chain\n          kj::HashSet<kj::String> methodSet;\n\n          // First, check for own properties (like a plain object literal)\n          auto dict = js.toDict(handle);\n          for (auto& field: dict.fields) {\n            if (!ignoredHandlers.contains(field.name)) {\n              methodSet.upsert(kj::mv(field.name), [&](auto&, auto&&) {});\n            }\n          }\n\n          // Then, check for methods in the prototype chain (like a class instance)\n          js.withinHandleScope([&]() {\n            collectMethodsFromPrototypeChain(jsg::JsObject(handle).getPrototype(js), methodSet);\n          });\n\n          // Convert HashSet to Array for reporting\n          errorReporter.addEntrypoint(name, KJ_MAP(n, methodSet) { return kj::mv(n); });\n        }\n      };\n\n      auto getEntrypointName = [&](kj::StringPtr key) -> kj::Maybe<kj::StringPtr> {\n        if (key == \"default\"_kj) {\n          return kj::none;\n        } else {\n          return key;\n        }\n      };\n\n      for (auto& entry: worker.impl->namedHandlers) {\n        report(getEntrypointName(entry.key), entry.value);\n      }\n      for (auto& entry: worker.impl->actorClasses) {\n        KJ_IF_SOME(entrypointName, getEntrypointName(entry.key)) {\n          errorReporter.addActorClass(entrypointName);\n        } else {\n          // Hmm, it appears someone tried to export a Durable Object class as a default\n          // entrypoint. This doesn't actually work: the runtime will not allow this DO class\n          // to be used, either for actors or as an entrypoint.\n          //\n          // TODO(someday): Make this a hard error. I'm hesitant to do it in my current change\n          //   for fear that it'll break someone somewhere forcing a rollback. For now we log.\n          LOG_PERIODICALLY(ERROR,\n              \"Exported actor class as default entrypoint. This doesn't work, but historically \"\n              \"did not produce a startup-time error.\");\n        }\n      }\n      for (auto& entry: worker.impl->statelessClasses) {\n        // We want to report all of the stateless class's members. To do this, we examine its\n        // prototype, and its prototype's prototype, and so on, until we get to Object's\n        // prototype, which we ignore.\n        auto entrypointName = getEntrypointName(entry.key);\n        kj::HashSet<kj::String> seenNames;\n\n        js.withinHandleScope([&]() {\n          // For stateless classes, we need to get the class's prototype property\n          jsg::JsObject ctor(KJ_ASSERT_NONNULL(entry.value.tryGetHandle(js.v8Isolate)));\n          jsg::JsValue proto = ctor.get(js, \"prototype\");\n          collectMethodsFromPrototypeChain(proto, seenNames);\n        });\n\n        errorReporter.addEntrypoint(entrypointName, KJ_MAP(n, seenNames) { return kj::mv(n); });\n      }\n\n      for (auto& entry: worker.impl->workflowClasses) {\n        KJ_IF_SOME(entrypointName, getEntrypointName(entry.key)) {\n          kj::HashSet<kj::String> seenNames;\n\n          js.withinHandleScope([&]() {\n            // For stateless classes, we need to get the class's prototype property\n            jsg::JsObject ctor(KJ_ASSERT_NONNULL(entry.value.tryGetHandle(js.v8Isolate)));\n            jsg::JsValue proto = ctor.get(js, \"prototype\");\n            collectMethodsFromPrototypeChain(proto, seenNames);\n          });\n\n          errorReporter.addWorkflowClass(entrypointName, KJ_MAP(n, seenNames) { return kj::mv(n); });\n        } else {\n        }\n      }\n    }\n  });\n}\n\n// =======================================================================================\n// AsyncLock implementation\n\nconst kj::EventLoopLocal<Worker::AsyncWaiter*> Worker::AsyncWaiter::threadCurrentWaiter;\n\nWorker::Isolate::AsyncWaiterList::~AsyncWaiterList() noexcept {\n  // It should be impossible for this list to be non-empty since each member of the list holds a\n  // strong reference back to us. But if the list is non-empty, we'd better crash here, to avoid\n  // dangling pointers.\n  KJ_ASSERT(head == kj::none, \"destroying non-empty waiter list?\");\n  KJ_ASSERT(tail == &head, \"tail pointer corrupted?\");\n}\n\nkj::Promise<Worker::AsyncLock> Worker::Isolate::takeAsyncLockWithoutRequest(\n    SpanParent parentSpan) const {\n  auto lockTiming = getMetrics().tryCreateLockTiming(kj::mv(parentSpan));\n  return takeAsyncLockImpl(kj::mv(lockTiming));\n}\n\nkj::Promise<Worker::AsyncLock> Worker::Isolate::takeAsyncLock(RequestObserver& request) const {\n  auto lockTiming = getMetrics().tryCreateLockTiming(kj::Maybe<RequestObserver&>(request));\n  return takeAsyncLockImpl(kj::mv(lockTiming));\n}\n\nkj::Promise<Worker::AsyncLock> Worker::Isolate::takeAsyncLockImpl(\n    kj::Maybe<kj::Own<IsolateObserver::LockTiming>> lockTiming) const {\n  kj::Maybe<uint> currentLoad;\n  if (lockTiming != kj::none) {\n    currentLoad = getCurrentLoad();\n  }\n\n  for (uint threadWaitingDifferentLockCount = 0;; ++threadWaitingDifferentLockCount) {\n    AsyncWaiter* waiter = *AsyncWaiter::threadCurrentWaiter;\n\n    if (waiter == nullptr) {\n      // Thread is not currently waiting on a lock.\n      KJ_IF_SOME(lt, lockTiming) {\n        lt.get()->reportAsyncInfo(KJ_ASSERT_NONNULL(currentLoad), false /* threadWaitingSameLock */,\n            threadWaitingDifferentLockCount);\n      }\n      auto newWaiter = kj::refcounted<AsyncWaiter>(kj::atomicAddRef(*this));\n      co_await newWaiter->readyPromise;\n      co_return AsyncLock(kj::mv(newWaiter), kj::mv(lockTiming));\n    } else if (waiter->isolate == this) {\n      // Thread is waiting on a lock already, and it's for the same isolate. We can coalesce the\n      // locks.\n      KJ_IF_SOME(lt, lockTiming) {\n        lt.get()->reportAsyncInfo(KJ_ASSERT_NONNULL(currentLoad), true /* threadWaitingSameLock */,\n            threadWaitingDifferentLockCount);\n      }\n      auto newWaiterRef = kj::addRef(*waiter);\n      co_await newWaiterRef->readyPromise;\n      co_return AsyncLock(kj::mv(newWaiterRef), kj::mv(lockTiming));\n    } else {\n      // Thread is already waiting for or holding a different isolate lock. Wait for that one to\n      // be released before we try to lock a different isolate.\n      // TODO(perf): Use of ForkedPromise leads to thundering herd here. Should be minor in practice,\n      //   but we could consider creating another linked list instead...\n      KJ_IF_SOME(lt, lockTiming) {\n        lt.get()->waitingForOtherIsolate(waiter->isolate->getId());\n      }\n      co_await waiter->releasePromise;\n    }\n  }\n}\n\nkj::Promise<Worker::AsyncLock> Worker::takeAsyncLockWithoutRequest(SpanParent parentSpan) const {\n  return script->getIsolate().takeAsyncLockWithoutRequest(kj::mv(parentSpan));\n}\n\nkj::Promise<Worker::AsyncLock> Worker::takeAsyncLock(RequestObserver& request) const {\n  return script->getIsolate().takeAsyncLock(request);\n}\n\nWorker::AsyncWaiter::AsyncWaiter(kj::Own<const Isolate> isolateParam)\n    : executor(kj::getCurrentThreadExecutor()),\n      isolate(kj::mv(isolateParam)) {\n  // Init `releasePromise` / `releaseFulfiller`.\n  {\n    auto paf = kj::newPromiseAndFulfiller<void>();\n    releasePromise = paf.promise.fork();\n    releaseFulfiller = kj::mv(paf.fulfiller);\n  }\n\n  // Add ourselves to the wait queue for this isolate.\n  auto lock = isolate->asyncWaiters.lockExclusive();\n  if (lock->tail == &lock->head) {\n    // Looks like the queue is empty, so we immediately get the lock.\n    readyPromise = kj::Promise<void>(kj::READY_NOW).fork();\n    // We can leave `readyFulfiller` null as no one will ever invoke it anyway.\n  } else {\n    // Arrange to get notified later.\n    auto paf = kj::newPromiseAndCrossThreadFulfiller<void>();\n    readyPromise = paf.promise.fork();\n    readyFulfiller = kj::mv(paf.fulfiller);\n  }\n\n  next = kj::none;\n  prev = lock->tail;\n  *lock->tail = this;\n  lock->tail = &next;\n\n  *threadCurrentWaiter = this;\n\n  __atomic_add_fetch(&isolate->impl->lockAttemptGauge, 1, __ATOMIC_RELAXED);\n}\n\nWorker::AsyncWaiter::~AsyncWaiter() noexcept {\n  // This destructor is `noexcept` because an exception here probably leaves the process in a bad\n  // state.\n\n  __atomic_sub_fetch(&isolate->impl->lockAttemptGauge, 1, __ATOMIC_RELAXED);\n\n  auto lock = isolate->asyncWaiters.lockExclusive();\n\n  releaseFulfiller->fulfill();\n\n  // Remove ourselves from the list.\n  *prev = next;\n  KJ_IF_SOME(n, next) {\n    n.prev = prev;\n  } else {\n    lock->tail = prev;\n  }\n\n  if (prev == &lock->head) {\n    // We held the lock before now. Alert the next waiter that they are now at the front of the\n    // line.\n    KJ_IF_SOME(n, next) {\n      n.readyFulfiller->fulfill();\n    }\n  }\n\n  auto& w = *threadCurrentWaiter;\n  KJ_ASSERT(w == this);\n  w = nullptr;\n}\n\nkj::Promise<void> Worker::AsyncLock::whenThreadIdle() {\n  AsyncWaiter*& currentWaiter = *AsyncWaiter::threadCurrentWaiter;\n  for (;;) {\n    if (currentWaiter != nullptr) {\n      co_await currentWaiter->releasePromise;\n      continue;\n    }\n\n    co_await kj::yieldUntilQueueEmpty();\n\n    if (currentWaiter == nullptr) {\n      co_return;\n    }\n    // Whoops, a new lock attempt appeared, loop.\n  }\n}\n\n// =======================================================================================\n\n// A proxy for OutputStream that internally buffers data as long as it's beyond a given limit.\n// Also, it counts size of all the data it has seen (whether it has hit the limit or not).\n//\n// We use this in the Network tab to report response stats and preview [decompressed] bodies,\n// but we don't want to keep buffering extremely large ones, so just discard buffered data\n// upon hitting a limit and don't return any body to the devtools frontend afterwards.\nclass Worker::Isolate::LimitedBodyWrapper: public kj::OutputStream {\n public:\n  LimitedBodyWrapper(size_t limit = 1 * 1024 * 1024): limit(limit) {\n    if (limit > 0) {\n      inner.emplace();\n    }\n  }\n\n  KJ_DISALLOW_COPY_AND_MOVE(LimitedBodyWrapper);\n\n  void reset() {\n    this->inner = kj::none;\n  }\n\n  void write(kj::ArrayPtr<const byte> data) override {\n    this->size += data.size();\n    KJ_IF_SOME(inner, this->inner) {\n      if (this->size <= this->limit) {\n        inner.write(data);\n      } else {\n        reset();\n      }\n    }\n  }\n\n  size_t getWrittenSize() {\n    return this->size;\n  }\n\n  kj::Maybe<kj::ArrayPtr<byte>> getArray() {\n    KJ_IF_SOME(inner, this->inner) {\n      return inner.getArray();\n    } else {\n      return kj::none;\n    }\n  }\n\n private:\n  size_t size = 0;\n  size_t limit = 0;\n  kj::Maybe<kj::VectorOutputStream> inner;\n};\n\nstruct MessageQueue {\n  kj::Vector<kj::String> messages;\n  size_t head;\n  enum class Status { ACTIVE, CLOSED } status;\n};\n\nclass Worker::Isolate::InspectorChannelImpl final: public v8_inspector::V8Inspector::Channel {\n public:\n  InspectorChannelImpl(kj::Own<const Worker::Isolate> isolateParam,\n      kj::Own<const kj::Executor> isolateThreadExecutor,\n      kj::WebSocket& webSocket)\n      : ioHandler(kj::mv(isolateThreadExecutor), webSocket),\n        state(kj::heap<State>(this, kj::mv(isolateParam))) {\n    ioHandler.connect(*this);\n  }\n\n  // In preview sessions, synchronous locks are not an issue. We declare an alternate spelling of\n  // the type so that all the individual locks below don't turn up in a search for synchronous\n  // locks.\n  using InspectorLock = Worker::Lock::TakeSynchronously;\n\n  ~InspectorChannelImpl() noexcept try {\n    // Stop message pump.\n    ioHandler.disconnect();\n\n    // Delete session under lock.\n    auto state = this->state.lockExclusive();\n\n    jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {\n      Isolate::Impl::Lock recordedLock(*state->get()->isolate, InspectorLock(kj::none), stackScope);\n      if (state->get()->isolate->currentInspectorSession != kj::none) {\n        const_cast<Isolate&>(*state->get()->isolate).disconnectInspector();\n      }\n      state->get()->teardownUnderLock();\n    });\n  } catch (...) {\n    // Unfortunately since we're inheriting from Channel which declares a virtual destructor with\n    // default exception constraints, we have to catch all exceptions here and log them.\n    // But different kinds of exceptions call for different ways to stringify the exception.\n    // kj::runCatchingExceptions() normally does this for us, but there's no way to use it while\n    // wrapping the whole destructor (including destructors of members). So... we do a native\n    // catch(...) and then we rethrow the exception inside a kj::runCatchingExceptions and then log\n    // that. Yeah.\n    //\n    // TODO(cleanup): Maybe we could add a kj::stringifyCurrentException() or\n    //     kj::logUncaughtException() or something?\n    KJ_IF_SOME(exception, kj::runCatchingExceptions([&]() { throw; })) {\n      KJ_LOG(ERROR, \"uncaught exception in ~Script() and the C++ standard is broken\", exception);\n    }\n  }\n\n  void disconnect() {\n    // Fake like the client requested close. This will cause outgoingLoop() to exit and everything\n    // will be cleaned up.\n    ioHandler.disconnect();\n  }\n\n  void dispatchProtocolMessage(kj::String message,\n      v8_inspector::V8InspectorSession& session,\n      Isolate& isolate,\n      jsg::V8StackScope& stackScope,\n      Isolate::Impl::Lock& recordedLock) {\n    capnp::MallocMessageBuilder messageBuilder;\n    auto cmd = messageBuilder.initRoot<cdp::Command>();\n    getCdpJsonCodec().decode(message, cmd);\n\n    switch (cmd.which()) {\n      case cdp::Command::UNKNOWN: {\n        break;\n      }\n      case cdp::Command::NETWORK_ENABLE: {\n        setNetworkEnabled(true);\n        cmd.getNetworkEnable().initResult();\n        break;\n      }\n      case cdp::Command::NETWORK_DISABLE: {\n        setNetworkEnabled(false);\n        cmd.getNetworkDisable().initResult();\n        break;\n      }\n      case cdp::Command::NETWORK_GET_RESPONSE_BODY: {\n        auto err = cmd.getNetworkGetResponseBody().initError();\n        err.setCode(-32600);\n        err.setMessage(\"Network.getResponseBody is not supported in this fork\");\n        break;\n      }\n      case cdp::Command::PROFILER_STOP: {\n        KJ_IF_SOME(p, isolate.impl->profiler) {\n          auto& lock = recordedLock.lock;\n          stopProfiling(*lock, *p, cmd);\n        }\n        break;\n      }\n      case cdp::Command::PROFILER_START: {\n        KJ_IF_SOME(p, isolate.impl->profiler) {\n          auto& lock = recordedLock.lock;\n          startProfiling(*lock, *p);\n        }\n        break;\n      }\n      case cdp::Command::PROFILER_SET_SAMPLING_INTERVAL: {\n        KJ_IF_SOME(p, isolate.impl->profiler) {\n          auto interval = cmd.getProfilerSetSamplingInterval().getParams().getInterval();\n          setSamplingInterval(*p, interval);\n        }\n        break;\n      }\n      case cdp::Command::PROFILER_ENABLE: {\n        auto& lock = recordedLock.lock;\n        isolate.impl->profiler = kj::Own<v8::CpuProfiler>(\n            v8::CpuProfiler::New(lock->v8Isolate, v8::kDebugNaming, v8::kLazyLogging),\n            CpuProfilerDisposer::instance);\n        break;\n      }\n      case cdp::Command::TAKE_HEAP_SNAPSHOT: {\n        auto& lock = recordedLock.lock;\n        takeHeapSnapshot(*lock, cmd.getTakeHeapSnapshot().getParams());\n        break;\n      }\n    }\n\n    if (!cmd.isUnknown()) {\n      sendNotification(cmd);\n      return;\n    }\n\n    auto& lock = recordedLock.lock;\n\n    // We have at times observed V8 bugs where the inspector queues a background task and\n    // then synchronously waits for it to complete, which would deadlock if background\n    // threads are disallowed. Since the inspector is in a process sandbox anyway, it's not\n    // a big deal to just permit those background threads.\n    AllowV8BackgroundThreadsScope allowBackgroundThreads;\n\n    ExceptionOrDuration limitErrorOrTime = 0 * kj::NANOSECONDS;\n    {\n      auto limitScope = isolate.getLimitEnforcer().enterInspectorJs(*lock, limitErrorOrTime);\n      session.dispatchProtocolMessage(jsg::toInspectorStringView(message));\n    }\n\n    // Run microtasks in case the user made an async call.\n    if (!limitErrorOrTime.is<kj::Exception>()) {\n      auto limitScope = isolate.getLimitEnforcer().enterInspectorJs(*lock, limitErrorOrTime);\n      lock->runMicrotasks();\n    } else {\n      // Oops, we already exceeded the limit, so force the microtask queue to be thrown away.\n      lock->terminateNextExecution();\n      lock->runMicrotasks();\n    }\n\n    KJ_SWITCH_ONEOF(limitErrorOrTime) {\n      KJ_CASE_ONEOF(limitError, kj::Exception) {\n        lock->withinHandleScope([&] {\n          // HACK: We want to print the error, but we need a context to do that.\n          //   We don't know which contexts exist in this isolate, so I guess we have to\n          //   create one. Ugh.\n          auto dummyContext = v8::Context::New(lock->v8Isolate);\n          // We need to set the highest used index in every context we create to be a nullptr\n          // This is because we might later on call GetAlignedPointerFromEmbedderData which fails with\n          // a fatal error if the array is smaller than the given index.\n          jsg::setAlignedPointerInEmbedderData(\n              dummyContext, jsg::ContextPointerSlot::MAX_POINTER_SLOT, nullptr);\n          auto& inspector = *KJ_ASSERT_NONNULL(isolate.impl->inspector);\n          inspector.contextCreated(v8_inspector::V8ContextInfo(dummyContext, 1,\n              v8_inspector::StringView(reinterpret_cast<const uint8_t*>(\"Worker\"), 6)));\n          JSG_WITHIN_CONTEXT_SCOPE(*lock, dummyContext, [&](jsg::Lock& js) {\n            jsg::sendExceptionToInspector(js, inspector,\n                jsg::extractTunneledExceptionDescription(limitError.getDescription()));\n          });\n          inspector.contextDestroyed(dummyContext);\n        });\n      }\n      KJ_CASE_ONEOF(startupTimeElapsed, kj::Duration) {}\n    }\n\n    if (recordedLock.checkInWithLimitEnforcer(isolate)) {\n      disconnect();\n    }\n  }\n\n  kj::Promise<void> messagePump() {\n    return ioHandler.messagePump();\n  }\n\n  void handleDispatchProtocolMessage(\n      Worker::AsyncLock& asyncLock, kj::MutexGuarded<MessageQueue>& incomingQueue) {\n    auto lockedState = state.lockExclusive();\n    v8_inspector::V8InspectorSession& session = *lockedState->get()->session;\n    Isolate& isolate = const_cast<Isolate&>(*lockedState->get()->isolate);\n    jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {\n      Isolate::Impl::Lock recordedLock(isolate, asyncLock, stackScope);\n\n      auto lockedQueue = incomingQueue.lockExclusive();\n      if (lockedQueue->status != MessageQueue::Status::ACTIVE) {\n        return;\n      }\n\n      auto messages = lockedQueue->messages.slice(lockedQueue->head, lockedQueue->messages.size());\n      for (auto& message: messages) {\n        dispatchProtocolMessage(kj::mv(message), session, isolate, stackScope, recordedLock);\n      }\n      lockedQueue->messages.clear();\n      lockedQueue->head = 0;\n    });\n  }\n\n  kj::Promise<void> dispatchProtocolMessages(kj::MutexGuarded<MessageQueue>& incomingQueue) {\n    // This method is called on the I/O thread, which also adds messages to the `incomingQueue`.\n    // So long as this method does not yield/resume mid-way, there is no concern about how\n    // long the queue lock is held for whilst dispatching messages.\n    auto i = kj::atomicAddRef(*this->state.lockExclusive()->get()->isolate);\n    auto asyncLock = co_await i->takeAsyncLockWithoutRequest(nullptr);\n    handleDispatchProtocolMessage(asyncLock, incomingQueue);\n  }\n\n  // ---------------------------------------------------------------------------\n  // implements Channel\n  //\n  // Keep in mind that these methods will be called from various threads!\n\n  void sendResponse(int callId, std::unique_ptr<v8_inspector::StringBuffer> message) override {\n    // callId is encoded in the message, too. Unsure why this method even exists.\n    sendNotification(kj::mv(message));\n  }\n\n  bool isNetworkEnabled() {\n    return __atomic_load_n(&networkEnabled, __ATOMIC_RELAXED);\n  }\n\n  void setNetworkEnabled(bool enable) {\n    __atomic_store_n(&networkEnabled, enable, __ATOMIC_RELAXED);\n  }\n\n  void sendNotification(kj::String message) {\n    ioHandler.send(kj::mv(message));\n  }\n\n  template <typename T>\n  void sendNotification(T&& message) {\n    sendNotification(getCdpJsonCodec().encode(message));\n  }\n\n  void sendNotification(std::unique_ptr<v8_inspector::StringBuffer> message) override {\n    sendNotification(kj::str(message->string()));\n  }\n\n  void flushProtocolNotifications() override {\n    // Are we supposed to do anything here? There's no documentation, so who knows? Maybe we could\n    // delay signaling the outgoing loop until this call?\n  }\n\n  // Dispatches one message whilst automatic CDP messages on the I/O worker thread is paused, called\n  // on the thread executing the isolate whilst execution is suspended due to a breakpoint or\n  // debugger statement.\n  bool dispatchOneMessageDuringPause();\n\n private:\n  // Class that manages the I/O for devtools connections. I/O is performed on the\n  // thread associated with the InspectorService (the thread that calls attachInspector).\n  // Most of the public API is intended for code running on the isolate thread, such as\n  // the InspectorChannelImpl and the InspectorClient.\n  class WebSocketIoHandler final {\n   public:\n    WebSocketIoHandler(kj::Own<const kj::Executor> isolateThreadExecutor, kj::WebSocket& webSocket)\n        : isolateThreadExecutor(kj::mv(isolateThreadExecutor)),\n          webSocket(webSocket) {\n      // Assume we are being instantiated on the InspectorService thread, the thread that will do\n      // I/O for CDP messages. Messages are delivered to the InspectorChannelImpl on the Isolate thread.\n      outgoingQueueNotifier = XThreadNotifier::create();\n    }\n\n    // Sets the channel that messages are delivered to.\n    void connect(InspectorChannelImpl& inspectorChannel) {\n      channel = inspectorChannel;\n    }\n\n    void disconnect() {\n      channel = kj::none;\n      shutdown();\n    }\n\n    // Blocked the current thread until a message arrives. This is intended\n    // for use in the InspectorClient when breakpoints are hit. The InspectorClient\n    // has to remain in runMessageLoopOnPause() but still receive CDP messages\n    // (e.g. resume).\n    kj::Maybe<kj::String> waitForMessage() {\n      return incomingQueue.when([](const MessageQueue& incomingQueue) {\n        return (incomingQueue.head < incomingQueue.messages.size() ||\n            incomingQueue.status == MessageQueue::Status::CLOSED);\n      }, [](MessageQueue& incomingQueue) -> kj::Maybe<kj::String> {\n        if (incomingQueue.status == MessageQueue::Status::CLOSED) return {};\n        return pollMessage(incomingQueue);\n      });\n    }\n\n    // Message pumping promise that should be evaluated on the InspectorService\n    // thread.\n    kj::Promise<void> messagePump() {\n      // Although inspector I/O must happen on the InspectorService thread (to make sure breakpoints\n      // don't block inspector I/O), inspector messages must be actually dispatched on the Isolate\n      // thread. So, we run the dispatch loop on the Isolate thread.\n      //\n      // Note that the above comment is only really accurate in vanilla workerd. In the case of the\n      // internal Cloudflare Workers runtime, `isolateThreadExecutor` may actually refer to the\n      // current thread's `kj::Executor`. That's fine; calling `executeAsync()` on the current\n      // thread's executor just posts the task to the event loop, and everything works as expected.\n\n      // Since the dispatch loop and the receive loop communicate over a XThreadNotifier, and\n      // XThreadNotifiers must be created on the thread which will call their `awaitNotification()`\n      // function, we awkwardly perform two `executeAsync()`s here, one to create the\n      // XThreadNotifier, then another to spawn the dispatch loop.\n      //\n      // We create a new XThreadNotifier for each `messagePump()` call, rather than try to re-use\n      // one long-term, because XThreadNotifiers' `awaitNotification()` function is not cancel-safe.\n      // That is, once its promise is cancelled, the notifier is broken.\n      auto incomingQueueNotifier =\n          co_await isolateThreadExecutor->executeAsync([]() { return XThreadNotifier::create(); });\n\n      auto dispatchLoopPromise = isolateThreadExecutor->executeAsync(\n          [this, notifier = kj::atomicAddRef(*incomingQueueNotifier)]() mutable {\n        return dispatchLoop(kj::mv(notifier));\n      });\n\n      co_return co_await receiveLoop(kj::mv(incomingQueueNotifier))\n          .exclusiveJoin(kj::mv(dispatchLoopPromise))\n          .exclusiveJoin(transmitLoop());\n    }\n\n    void send(kj::String message) {\n      auto lockedOutgoingQueue = outgoingQueue.lockExclusive();\n      if (lockedOutgoingQueue->status == MessageQueue::Status::CLOSED) return;\n      lockedOutgoingQueue->messages.add(kj::mv(message));\n      outgoingQueueNotifier->notify();\n    }\n\n   private:\n    static kj::Maybe<kj::String> pollMessage(MessageQueue& messageQueue) {\n      if (messageQueue.head < messageQueue.messages.size()) {\n        kj::String message = kj::mv(messageQueue.messages[messageQueue.head++]);\n        if (messageQueue.head == messageQueue.messages.size()) {\n          messageQueue.head = 0;\n          messageQueue.messages.clear();\n        }\n        return kj::mv(message);\n      }\n      return {};\n    }\n\n    void shutdown() {\n      // Drain incoming queue, the isolate thread may be waiting on it\n      // on will notice it is closed if woken without any messages to\n      // deliver in WebSocketIoWorker::waitForMessage().\n      {\n        auto lockedIncomingQueue = incomingQueue.lockExclusive();\n        lockedIncomingQueue->head = 0;\n        lockedIncomingQueue->messages.clear();\n        lockedIncomingQueue->status = MessageQueue::Status::CLOSED;\n      }\n      {\n        auto lockedOutgoingQueue = outgoingQueue.lockExclusive();\n        lockedOutgoingQueue->status = MessageQueue::Status::CLOSED;\n      }\n      // Wake any waiters since queue status fields have been updated.\n      outgoingQueueNotifier->notify();\n    }\n\n    // Must be called on the InspectorService thread.\n    kj::Promise<void> receiveLoop(kj::Own<XThreadNotifier> incomingQueueNotifier) {\n      for (;;) {\n        auto message = co_await webSocket.receive(MAX_MESSAGE_SIZE);\n        KJ_SWITCH_ONEOF(message) {\n          KJ_CASE_ONEOF(text, kj::String) {\n            incomingQueue.lockExclusive()->messages.add(kj::mv(text));\n            incomingQueueNotifier->notify();\n          }\n          KJ_CASE_ONEOF(blob, kj::Array<byte>) {\n            // Ignore.\n          }\n          KJ_CASE_ONEOF(close, kj::WebSocket::Close) {\n            shutdown();\n            // Pause here to give transmitLoop() the chance to finish and send a reply close.\n            // When `transmitLoop()` ends, `messagePump()` as a whole will end, canceling\n            // `receiveLoop()`.\n            co_await kj::Promise<void>(kj::NEVER_DONE);\n          }\n        }\n      }\n    }\n\n    // Must be called on the Isolate thread.\n    kj::Promise<void> dispatchLoop(kj::Own<XThreadNotifier> incomingQueueNotifier) {\n      for (;;) {\n        co_await incomingQueueNotifier->awaitNotification();\n        KJ_IF_SOME(c, channel) {\n          co_await c.dispatchProtocolMessages(this->incomingQueue);\n        }\n      }\n    }\n\n    // Must be called on the InspectorService thread.\n    kj::Promise<void> transmitLoop() {\n      for (;;) {\n        co_await outgoingQueueNotifier->awaitNotification();\n        try {\n          auto lockedOutgoingQueue = outgoingQueue.lockExclusive();\n          auto messages = kj::mv(lockedOutgoingQueue->messages);\n          bool receivedClose = lockedOutgoingQueue->status == MessageQueue::Status::CLOSED;\n          lockedOutgoingQueue.release();\n          co_await sendToWebSocket(kj::mv(messages));\n          if (receivedClose) {\n            co_await webSocket.close(1000, \"client closed connection\");\n            co_return;\n          }\n        } catch (kj::Exception& e) {\n          shutdown();\n          throw;\n        }\n      }\n    }\n\n    kj::Promise<void> sendToWebSocket(kj::Vector<kj::String> messages) {\n      for (auto& message: messages) {\n        co_await webSocket.send(message);\n      }\n    }\n\n    // We need access to the Isolate thread's kj::Executor to run the inspector dispatch loop. This\n    // doesn't actually have to be an Own, because the Isolate thread will destroy the Isolate\n    // before it exits, but it doesn't hurt.\n    kj::Own<const kj::Executor> isolateThreadExecutor;\n\n    kj::MutexGuarded<MessageQueue> incomingQueue;\n    // The notifier for `incomingQueue`, `incomingQueueNotifier`, is created once per\n    // `messagePump()` call, and never re-used, so it doesn't live here.\n\n    kj::MutexGuarded<MessageQueue> outgoingQueue;\n    // This XThreadNotifier must be created on the InspectorService thread.\n    kj::Own<XThreadNotifier> outgoingQueueNotifier;\n\n    kj::WebSocket& webSocket;        // only accessed on the InspectorService thread.\n    std::atomic_bool receivedClose;  // accessed on any thread (only transitions false -> true).\n    kj::Maybe<InspectorChannelImpl&> channel;  // only accessed on the isolate thread.\n\n    // Sometimes the inspector protocol sends large messages. KJ defaults to a 1MB size limit\n    // for WebSocket messages, which makes sense for production use cases, but for debug we should\n    // be OK to go larger. So, we'll accept 128MB.\n    static constexpr size_t MAX_MESSAGE_SIZE = 128u << 20;\n  };\n\n  WebSocketIoHandler ioHandler;\n\n  void takeHeapSnapshot(\n      jsg::Lock& js, cdp::HeapProfiler::Command::TakeHeapSnapshot::Params::Reader params) {\n    struct Activity: public v8::ActivityControl {\n      InspectorChannelImpl& channel;\n      Activity(InspectorChannelImpl& channel): channel(channel) {}\n\n      ControlOption ReportProgressValue(uint32_t done, uint32_t total) override {\n        capnp::MallocMessageBuilder message;\n        auto event = message.initRoot<cdp::Event>();\n        auto progressParams = event.initReportHeapSnapshotProgress();\n        progressParams.setDone(done);\n        progressParams.setTotal(total);\n        if (done == total) {\n          progressParams.setFinished(true);\n        }\n        auto notification = getCdpJsonCodec().encode(event);\n        channel.sendNotification(kj::mv(notification));\n        return ControlOption::kContinue;\n      }\n    };\n\n    struct Writer: public v8::OutputStream {\n      InspectorChannelImpl& channel;\n\n      Writer(InspectorChannelImpl& channel): channel(channel) {}\n      void EndOfStream() override {}\n\n      int GetChunkSize() override {\n        return 65536;  // big chunks == faster\n        // The chunk size here will determine the actual number of individual\n        // messages that are sent. The default is... rather small. Experience\n        // node and node-heapdump shows that this can be bumped up\n        // much higher to get better performance. Here we use the value\n        // that Node.js uses (see Node.js' FileOutputStream impl).\n      }\n\n      v8::OutputStream::WriteResult WriteAsciiChunk(char* data, int size) override {\n        capnp::MallocMessageBuilder message;\n        auto event = message.initRoot<cdp::Event>();\n\n        auto params = event.initAddHeapSnapshotChunk();\n        params.setChunk(kj::heapString(data, size));\n        auto notification = getCdpJsonCodec().encode(event);\n        channel.sendNotification(kj::mv(notification));\n\n        return v8::OutputStream::WriteResult::kContinue;\n      }\n    };\n\n    Activity activity(*this);\n    Writer writer(*this);\n\n    v8::HeapProfiler::HeapSnapshotOptions options{};\n    if (params.getReportProgress()) {\n      options.control = &activity;\n    }\n    if (params.getExposeInternals()) {\n      options.snapshot_mode = v8::HeapProfiler::HeapSnapshotMode::kExposeInternals;\n    }\n    if (params.getCaptureNumericValue()) {\n      options.numerics_mode = v8::HeapProfiler::NumericsMode::kExposeNumericValues;\n    }\n\n    auto profiler = js.v8Isolate->GetHeapProfiler();\n    auto snapshot = kj::Own<const v8::HeapSnapshot>(\n        profiler->TakeHeapSnapshot(options), HeapSnapshotDeleter::INSTANCE);\n    snapshot->Serialize(&writer);\n  }\n\n  struct State {\n    kj::Own<const Worker::Isolate> isolate;\n    std::unique_ptr<v8_inspector::V8InspectorSession> session;\n\n    State(InspectorChannelImpl* self, kj::Own<const Worker::Isolate> isolateParam)\n        : isolate(kj::mv(isolateParam)),\n          session(KJ_ASSERT_NONNULL(isolate->impl->inspector)\n                      ->connect(1,\n                          self,\n                          v8_inspector::StringView(),\n                          isolate->impl->inspectorPolicy == InspectorPolicy::ALLOW_UNTRUSTED\n                              ? v8_inspector::V8Inspector::kUntrusted\n                              : v8_inspector::V8Inspector::kFullyTrusted)) {}\n    ~State() noexcept(false) {\n      if (session != nullptr) {\n        KJ_LOG(ERROR,\n            \"Deleting InspectorChannelImpl::State without having called \"\n            \"teardownUnderLock()\",\n            kj::getStackTrace());\n\n        // Isolate locks are recursive so it should be safe to lock here.\n        jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {\n          Isolate::Impl::Lock recordedLock(*isolate, InspectorLock(kj::none), stackScope);\n          session = nullptr;\n        });\n      }\n    }\n\n    // Must be called with the worker isolate locked. Should be called immediately before\n    // destruction.\n    void teardownUnderLock() {\n      session = nullptr;\n    }\n\n    KJ_DISALLOW_COPY_AND_MOVE(State);\n  };\n  // Mutex ordering: You must lock this *before* locking the isolate.\n  kj::MutexGuarded<kj::Own<State>> state;\n\n  // Not under `state` lock due to lock ordering complications.\n  volatile bool networkEnabled = false;\n};\n\nbool Worker::Isolate::InspectorChannelImpl::dispatchOneMessageDuringPause() {\n  auto maybeMessage = ioHandler.waitForMessage();\n  // We can be paused by either hitting a debugger statement in a script or from hitting\n  // a breakpoint or someone hit break.\n  KJ_IF_SOME(message, maybeMessage) {\n    auto lockedState = this->state.lockExclusive();\n    // Received a message whilst script is running, probably in a breakpoint.\n    v8_inspector::V8InspectorSession& session = *lockedState->get()->session;\n    // const_cast OK because the IoContext has the lock.\n    Isolate& isolate = const_cast<Isolate&>(*lockedState->get()->isolate);\n    Worker::Lock& workerLock = IoContext::current().getCurrentLock();\n    Isolate::Impl::Lock& recordedLock = workerLock.impl->recordedLock;\n    jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {\n      dispatchProtocolMessage(kj::mv(message), session, isolate, stackScope, recordedLock);\n    });\n    return true;\n  } else {\n    // No message from waitForMessage() implies the connection is broken.\n    return false;\n  }\n}\n\nbool Worker::InspectorClient::dispatchOneMessageDuringPause(\n    Worker::Isolate::InspectorChannelImpl& channel) {\n  return channel.dispatchOneMessageDuringPause();\n}\n\nkj::Promise<void> Worker::Isolate::attachInspector(kj::Timer& timer,\n    kj::Duration timerOffset,\n    kj::HttpService::Response& response,\n    const kj::HttpHeaderTable& headerTable,\n    kj::HttpHeaderId controlHeaderId) const {\n  KJ_REQUIRE(impl->inspector != kj::none);\n\n  kj::HttpHeaders headers(headerTable);\n  headers.setPtr(controlHeaderId, \"{\\\"ewLog\\\":{\\\"status\\\":\\\"ok\\\"}}\");\n  auto webSocket = response.acceptWebSocket(headers);\n\n  // This `attachInspector()` overload is used by the internal Cloudflare Workers runtime, which has\n  // no concept of a single Isolate thread. Instead, it's OK for all inspector messages to be\n  // dispatched on the calling thread.\n  auto executor = kj::getCurrentThreadExecutor().addRef();\n\n  return attachInspector(kj::mv(executor), timer, timerOffset, *webSocket)\n      .attach(kj::mv(webSocket));\n}\n\nkj::Promise<void> Worker::Isolate::attachInspector(\n    kj::Own<const kj::Executor> isolateThreadExecutor,\n    kj::Timer& timer,\n    kj::Duration timerOffset,\n    kj::WebSocket& webSocket) const {\n  KJ_REQUIRE(impl->inspector != kj::none);\n\n  return jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {\n    Isolate::Impl::Lock recordedLock(\n        *this, InspectorChannelImpl::InspectorLock(kj::none), stackScope);\n    auto& lock = *recordedLock.lock;\n    auto& lockedSelf = const_cast<Worker::Isolate&>(*this);\n\n    // If another inspector was already connected, boot it, on the assumption that that connection\n    // is dead and this is why the user reconnected. While we could actually allow both inspector\n    // sessions to stay open (V8 supports this!), we'd then need to store a set of all connected\n    // inspectors in order to be able to disconnect all of them in case of an isolate purge... let's\n    // just not.\n    lockedSelf.disconnectInspector();\n\n    lockedSelf.impl->inspectorClient->setInspectorTimerInfo(timer, timerOffset);\n\n    auto channel = kj::heap<Worker::Isolate::InspectorChannelImpl>(\n        kj::atomicAddRef(*this), kj::mv(isolateThreadExecutor), webSocket);\n    lockedSelf.currentInspectorSession = *channel;\n    lockedSelf.impl->inspectorClient->setChannel(*channel);\n\n    // Send any queued notifications.\n    lock.withinHandleScope([&] {\n      for (auto& notification: lockedSelf.impl->queuedNotifications) {\n        channel->sendNotification(kj::mv(notification));\n      }\n      lockedSelf.impl->queuedNotifications.clear();\n    });\n\n    return channel->messagePump().attach(kj::mv(channel));\n  });\n}\n\nvoid Worker::Isolate::disconnectInspector() {\n  // If an inspector session is connected, proactively drop it, so as to force it to drop its\n  // reference on the script, so that the script can be deleted.\n  KJ_IF_SOME(current, currentInspectorSession) {\n    current.disconnect();\n    currentInspectorSession = kj::none;\n  }\n  impl->inspectorClient->resetChannel();\n}\n\nvoid Worker::Isolate::logWarning(kj::StringPtr description, Lock& lock) {\n  if (impl->inspector != kj::none) {\n    JSG_WITHIN_CONTEXT_SCOPE(lock, lock.getContext(), [&](jsg::Lock& js) {\n      logMessage(js, static_cast<uint16_t>(cdp::LogType::WARNING), description);\n    });\n  }\n\n  if (loggingOptions.consoleMode == Worker::ConsoleMode::INSPECTOR_ONLY) {\n    // Run with --verbose to log JS exceptions to stderr. Useful when running tests.\n    KJ_LOG(INFO, \"console warning\", description);\n  } else {\n    fprintf(stderr, \"%s\\n\", description.cStr());\n    fflush(stderr);\n  }\n\n  KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n    KJ_IF_SOME(tracer, ioContext.getWorkerTracer()) {\n      // json encoding is required over simply wrapping it in quotes to correctly escape the string.\n      capnp::JsonCodec json;\n      auto jsonDescription = kj::str(\"[\", json.encode(capnp::Text::Reader(description)), \"]\");\n\n      auto timestamp = ioContext.now();\n      tracer.addLog(\n          ioContext.getInvocationSpanContext(), timestamp, LogLevel::WARN, kj::mv(jsonDescription));\n    }\n  }\n}\n\nvoid Worker::Isolate::logWarningOnce(kj::StringPtr description, Lock& lock) {\n  impl->warningOnceDescriptions.findOrCreate(description, [&] {\n    logWarning(description, lock);\n    return kj::str(description);\n  });\n}\n\nvoid Worker::Isolate::logErrorOnce(kj::StringPtr description) {\n  impl->errorOnceDescriptions.findOrCreate(description, [&] {\n    KJ_LOG(ERROR, description);\n    return kj::str(description);\n  });\n}\n\nvoid Worker::Isolate::logMessage(jsg::Lock& js, uint16_t type, kj::StringPtr description) {\n  if (impl->inspector != kj::none) {\n    // We want to log a warning to the devtools console, as if `console.warn()` were called.\n    // However, the only public interface to call the real `console.warn()` is via JavaScript,\n    // where it could have been monkey-patched by the guest. We'd like to avoid having to worry\n    // about that blowing up in our face. So instead we arrange to send the proper devtools\n    // protocol messages ourselves.\n    //\n    // TODO(cleanup): It would be better if we could directly add the message to the inspector's\n    //   console log (without calling through JavaScript). What we're doing here has some problems.\n    //   In particular, if no client is connected yet, we attempt to queue up the messages to send\n    //   later, much like the real inspector does. This is kind of complicated, and doesn't quite\n    //   work right:\n    //   - The messages won't necessarily be in the right order with normal console logs made at\n    //     the same time (with identical timestamps).\n    //   - In theory we should queue *all* logged warnings and deliver them to every future client,\n    //     not just the next client to connect. But if we do that, we also need to respect the\n    //     protocol command to clear the history when requested. This was further than I cared to\n    //     go.\n    //   To fix these problems, maybe we should just patch V8 with a direct interface into the\n    //   inspector's own log. (Also, how does Chrome handle this?)\n\n    js.withinHandleScope([&] {\n      capnp::MallocMessageBuilder message;\n      auto event = message.initRoot<cdp::Event>();\n\n      auto params = event.initRuntimeConsoleApiCalled();\n      params.setType(static_cast<cdp::LogType>(type));\n      params.initArgs(1)[0].initString().setValue(description);\n      params.setExecutionContextId(v8_inspector::V8ContextInfo::executionContextId(js.v8Context()));\n      params.setTimestamp(impl->inspectorClient->currentTimeMS());\n      stackTraceToCDP(js, params.initStackTrace());\n\n      auto notification = getCdpJsonCodec().encode(event);\n      KJ_IF_SOME(i, currentInspectorSession) {\n        i.sendNotification(kj::mv(notification));\n      } else {\n        impl->queuedNotifications.add(kj::mv(notification));\n      }\n    });\n  }\n}\n\n// =======================================================================================\n\nstruct Worker::Actor::Impl {\n  Actor::Id actorId;\n  Frankenvalue props;\n  MakeStorageFunc makeStorage;\n\n  kj::Own<ActorObserver> metrics;\n\n  // When a boolean, indicates whether a `transient` should exist. If true, it will be initialized\n  // on the first `ensureConstructed()`.\n  kj::OneOf<bool, jsg::JsRef<jsg::JsValue>> transient;\n\n  kj::Maybe<kj::Own<ActorCacheInterface>> actorCache;\n\n  kj::Maybe<jsg::JsRef<jsg::JsObject>> ctxObject;\n\n  kj::Maybe<rpc::Container::Client> container;\n  kj::Maybe<FacetManager&> facetManager;\n  kj::Maybe<ActorVersion> version;\n\n  struct NoClass {};\n  struct Initializing {};\n\n  // If the actor is backed by a class, this field tracks the instance through its stages. The\n  // instance is constructed as part of the first request to be delivered.\n  kj::OneOf<NoClass,            // not class-based\n      Worker::ActorClassInfo*,  // constructor not run yet\n      Initializing,             // constructor currently running\n      api::ExportedHandler,     // fully constructed\n      kj::Exception             // constructor threw\n      >\n      classInstance;\n\n  class HooksImpl: public InputGate::Hooks, public OutputGate::Hooks, public ActorCache::Hooks {\n   public:\n    HooksImpl(kj::Own<Loopback> loopback, TimerChannel& timerChannel, ActorObserver& metrics)\n        : loopback(kj::mv(loopback)),\n          timerChannel(timerChannel),\n          metrics(metrics) {}\n\n    void inputGateLocked() override {\n      metrics.inputGateLocked();\n    }\n    void inputGateReleased() override {\n      metrics.inputGateReleased();\n    }\n    void inputGateWaiterAdded() override {\n      metrics.inputGateWaiterAdded();\n    }\n    void inputGateWaiterRemoved() override {\n      metrics.inputGateWaiterRemoved();\n    }\n    // Implements InputGate::Hooks.\n\n    kj::Promise<void> makeTimeoutPromise() override {\n      // This really only protects against total hangs. Lowering the timeout drastically is risky,\n      // since low timeouts can spuriously fire when under heavy CPU load, failing requests that\n      // would otherwise succeed.\n      auto timeout = 30 * kj::SECONDS;\n      co_await timerChannel.afterLimitTimeout(timeout);\n\n      kj::throwFatalException(KJ_EXCEPTION(OVERLOADED,\n          \"broken.outputGateBroken; jsg.Error: Durable Object storage operation exceeded \"\n          \"timeout which caused object to be reset.\"));\n    }\n\n    // Implements OutputGate::Hooks.\n\n    void outputGateLocked() override {\n      metrics.outputGateLocked();\n    }\n    void outputGateReleased() override {\n      metrics.outputGateReleased();\n    }\n    void outputGateWaiterAdded() override {\n      metrics.outputGateWaiterAdded();\n    }\n    void outputGateWaiterRemoved() override {\n      metrics.outputGateWaiterRemoved();\n    }\n\n    // Implements ActorCache::Hooks\n\n    void updateAlarmInMemory(kj::Maybe<kj::Date> newAlarmTime) override;\n    void storageReadCompleted(kj::Duration latency) override {\n      metrics.storageReadCompleted(latency);\n    }\n    void storageWriteCompleted(kj::Duration latency) override {\n      metrics.storageWriteCompleted(latency);\n    }\n\n   private:\n    kj::Own<Loopback> loopback;  // only for updateAlarmInMemory()\n    TimerChannel& timerChannel;  // only for afterLimitTimeout() and updateAlarmInMemory()\n    ActorObserver& metrics;\n\n    kj::Maybe<kj::Promise<void>> maybeAlarmPreviewTask;\n  };\n\n  HooksImpl hooks;\n\n  // Handles both input locks and request locks.\n  InputGate inputGate;\n\n  // Handles output locks.\n  OutputGate outputGate;\n\n  // `ioContext` is initialized upon delivery of the first request.\n  kj::Maybe<kj::Own<IoContext>> ioContext;\n\n  // If onBroken() is called while `ioContext` is still null, this is initialized. When\n  // `ioContext` is constructed, this will be fulfilled with `ioContext.onAbort()`.\n  kj::Maybe<kj::Own<kj::PromiseFulfiller<kj::Promise<void>>>> abortFulfiller;\n\n  // Task which periodically flushes metrics. Initialized after `ioContext` is initialized.\n  kj::Maybe<kj::Promise<void>> metricsFlushLoopTask;\n\n  // Allows sending requests back into this actor, recreating it as necessary. Safe to hold longer\n  // than the Worker::Actor is alive.\n  kj::Own<Loopback> loopback;\n\n  TimerChannel& timerChannel;\n\n  kj::ForkedPromise<void> shutdownPromise;\n  kj::Own<kj::PromiseFulfiller<void>> shutdownFulfiller;\n\n  // If this Actor has a HibernationManager, it means the Actor has recently accepted a Hibernatable\n  // websocket. We eventually move the HibernationManager into the DeferredProxy task\n  // (since it's long lived), but can still refer to the HibernationManager by passing a reference\n  // in each CustomEvent.\n  kj::Maybe<kj::Own<HibernationManager>> hibernationManager;\n  kj::Maybe<uint16_t> hibernationEventType;\n\n  struct ScheduledAlarm {\n    ScheduledAlarm(\n        kj::Date scheduledTime, kj::PromiseFulfillerPair<WorkerInterface::AlarmResult> pf)\n        : scheduledTime(scheduledTime),\n          resultFulfiller(kj::mv(pf.fulfiller)),\n          resultPromise(pf.promise.fork()) {}\n    KJ_DISALLOW_COPY(ScheduledAlarm);\n    ScheduledAlarm(ScheduledAlarm&&) = default;\n    ~ScheduledAlarm() noexcept(false) {}\n\n    kj::Date scheduledTime;\n    WorkerInterface::AlarmFulfiller resultFulfiller;\n    kj::ForkedPromise<WorkerInterface::AlarmResult> resultPromise;\n    kj::Promise<void> cleanupPromise = resultPromise.addBranch().then(\n        [](WorkerInterface::AlarmResult&&) {}, [](kj::Exception&&) {});\n    // The first thing we do after we get a result should be to remove the running alarm (if we got\n    // that far). So we grab the first branch now and ignore any results, before anyone else has a\n    // chance to do so.\n  };\n  struct RunningAlarm {\n    kj::Date scheduledTime;\n    kj::ForkedPromise<WorkerInterface::AlarmResult> resultPromise;\n  };\n  // If valid, we have an alarm invocation that has not yet received an `AlarmFulfiller` and thus\n  // is either waiting for a running alarm or its scheduled time.\n  kj::Maybe<ScheduledAlarm> maybeScheduledAlarm;\n\n  // If valid, we have an alarm invocation that has received an `AlarmFulfiller` and is currently\n  // considered running. This alarm is no longer cancelable.\n  kj::Maybe<RunningAlarm> maybeRunningAlarm;\n\n  // This is a forked promise so that we can schedule and then cancel multiple alarms while an alarm\n  // is running.\n  kj::ForkedPromise<void> runningAlarmTask = kj::Promise<void>(kj::READY_NOW).fork();\n\n  Impl(Worker::Actor& self,\n      Actor::Id actorId,\n      bool hasTransient,\n      MakeActorCacheFunc makeActorCache,\n      Frankenvalue props,\n      MakeStorageFunc makeStorage,\n      kj::Own<Loopback> loopback,\n      TimerChannel& timerChannel,\n      kj::Own<ActorObserver> metricsParam,\n      kj::Maybe<kj::Own<HibernationManager>> manager,\n      kj::Maybe<uint16_t>& hibernationEventType,\n      kj::Maybe<rpc::Container::Client> container,\n      kj::Maybe<FacetManager&> facetManager,\n      kj::PromiseFulfillerPair<void> paf = kj::newPromiseAndFulfiller<void>())\n      : actorId(kj::mv(actorId)),\n        props(kj::mv(props)),\n        makeStorage(kj::mv(makeStorage)),\n        metrics(kj::mv(metricsParam)),\n        transient(hasTransient),\n        container(kj::mv(container)),\n        facetManager(facetManager),\n        hooks(loopback->addRef(), timerChannel, *metrics),\n        inputGate(hooks),\n        outputGate(hooks),\n        loopback(kj::mv(loopback)),\n        timerChannel(timerChannel),\n        shutdownPromise(paf.promise.fork()),\n        shutdownFulfiller(kj::mv(paf.fulfiller)),\n        hibernationManager(kj::mv(manager)),\n        hibernationEventType(kj::mv(hibernationEventType)) {\n    actorCache =\n        makeActorCache(self.worker->getIsolate().impl->actorCacheLru, outputGate, hooks, *metrics);\n  }\n};\n\nkj::Promise<Worker::AsyncLock> Worker::takeAsyncLockWhenActorCacheReady(\n    kj::Date now, Actor& actor, RequestObserver& request) const {\n  auto lockTiming =\n      getIsolate().getMetrics().tryCreateLockTiming(kj::Maybe<RequestObserver&>(request));\n\n  KJ_IF_SOME(c, actor.impl->actorCache) {\n    KJ_IF_SOME(p, c.get()->evictStale(now)) {\n      // Got backpressure, wait for it.\n      // TODO(someday): Count this time period differently in lock timing data?\n      co_await p;\n    }\n  }\n\n  co_return co_await getIsolate().takeAsyncLockImpl(kj::mv(lockTiming));\n}\n\nWorker::Actor::Actor(const Worker& worker,\n    kj::Maybe<RequestTracker&> tracker,\n    Actor::Id actorId,\n    bool hasTransient,\n    MakeActorCacheFunc makeActorCache,\n    kj::Maybe<kj::StringPtr> className,\n    Frankenvalue props,\n    MakeStorageFunc makeStorage,\n    kj::Own<Loopback> loopback,\n    TimerChannel& timerChannel,\n    kj::Own<ActorObserver> metrics,\n    kj::Maybe<kj::Own<HibernationManager>> manager,\n    kj::Maybe<uint16_t> hibernationEventType,\n    kj::Maybe<rpc::Container::Client> container,\n    kj::Maybe<FacetManager&> facetManager,\n    kj::Maybe<ActorVersion> version)\n    : worker(kj::atomicAddRef(worker)),\n      tracker(tracker.map([](RequestTracker& tracker) { return tracker.addRef(); })) {\n  impl = kj::heap<Impl>(*this, kj::mv(actorId), hasTransient, kj::mv(makeActorCache), kj::mv(props),\n      kj::mv(makeStorage), kj::mv(loopback), timerChannel, kj::mv(metrics), kj::mv(manager),\n      hibernationEventType, kj::mv(container), facetManager);\n  impl->version = kj::mv(version);\n\n  KJ_IF_SOME(c, className) {\n    KJ_IF_SOME(cls, worker.impl->actorClasses.find(c)) {\n      // const_cast OK because we're just storing the pointer and will only use this under lock.\n      impl->classInstance = const_cast<ActorClassInfo*>(&cls);\n    } else {\n      kj::throwFatalException(KJ_EXCEPTION(FAILED, \"broken.ignored; no such actor class\", c));\n    }\n  } else {\n    impl->classInstance = Impl::NoClass();\n  }\n}\n\nvoid Worker::Actor::ensureConstructed(IoContext& context) {\n  KJ_IF_SOME(info, impl->classInstance.tryGet<ActorClassInfo*>()) {\n    // IMPORTANT: We need to set the state to \"Initializing\" synchronously, before\n    // ensureConstructedImpl() actually executes and acquires the input lock.\n    // This prevents multiple concurrent initialization attempts if multiple calls to\n    // ensureConstructed() arrive back-to-back.\n    //\n    // This doesn't create a race condition with getHandler() because InputGate::wait()\n    // synchronously adds the caller to the wait queue, even though it completes\n    // asynchronously. Any call to getHandler() that arrives after this point will\n    // have to wait for the input lock, which is only acquired and released by\n    // ensureConstructedImpl() when it completes initialization.\n    //\n    // So the \"actor still initializing\" error in getHandler() should be impossible\n    // unless a code path is bypassing the input lock mechanism.\n    context.addWaitUntil(ensureConstructedImpl(context, *info));\n    impl->classInstance = Impl::Initializing();\n  }\n}\n\nkj::Promise<void> Worker::Actor::ensureConstructedImpl(IoContext& context, ActorClassInfo& info) {\n  InputGate::Lock inputLock = co_await impl->inputGate.wait(context.getCurrentTraceSpan());\n\n  try {\n    bool containerRunning = false;\n    KJ_IF_SOME(c, impl->container) {\n      // We need to do an RPC to check if the container is running.\n      // TODO(perf): It would be nice if we could have started this RPC earlier, e.g. in parallel\n      //   with starting the script, and also if we could save the status across hibernations. But\n      //   that would require some refactoring, and this RPC should (eventally) be local, so it's\n      //   not a huge deal.\n      auto status = co_await c.statusRequest(capnp::MessageSize{4, 0}).send();\n      containerRunning = status.getRunning();\n    }\n\n    co_await context.run([this, &info, containerRunning](Worker::Lock& lock) {\n      jsg::Lock& js = lock;\n\n      kj::Maybe<jsg::Ref<api::DurableObjectStorage>> storage;\n      KJ_IF_SOME(c, impl->actorCache) {\n        storage = impl->makeStorage(lock, worker->getIsolate().getApi(), *c);\n      }\n\n      auto ctx = js.alloc<api::DurableObjectState>(js, cloneId(),\n          jsg::JsValue(KJ_ASSERT_NONNULL(lock.getWorker().impl->ctxExports).getHandle(js)),\n          impl->props.toJs(js), kj::mv(storage), kj::mv(impl->container), containerRunning,\n          impl->facetManager, impl->version.map([](ActorVersion& v) {\n        return ActorVersion{.cohort = v.cohort.map([](kj::String& s) { return kj::str(s); })};\n      }));\n\n      auto handler =\n          info.cls(lock, ctx.addRef(), KJ_ASSERT_NONNULL(lock.getWorker().impl->env).addRef(js));\n\n      // Since we JUST passed `ctx` into the class constructor, it definitely has a handle\n      // attached. Let's grab it and stash it to implement getCtx().\n      auto ctxHandle = jsg::JsObject(KJ_ASSERT_NONNULL(ctx.tryGetHandle(js)));\n      impl->ctxObject = jsg::JsRef<jsg::JsObject>(js, ctxHandle);\n\n      // HACK: We set handler.env to undefined because we already passed the real env into the\n      //   constructor, and we want the handler methods to act like they take just one parameter.\n      //   We do the same for handler.ctx, as ExecutionContext related tasks are performed\n      //   on the actor's state field instead.\n      handler.env = js.v8Ref(js.v8Undefined());\n      handler.ctx = kj::none;\n      handler.missingSuperclass = info.missingSuperclass;\n\n      impl->classInstance = kj::mv(handler);\n    }, inputLock.addRef(context.getCurrentTraceSpan()));\n    // We addRef() the inputLock above rather than kj::mv() it so that the lock remains held\n    // through the catch block below, if an exception is thrown. This is important since we\n    // MUST update `impl->classInstance` to something other than `Initializing` before we\n    // release the lock.\n  } catch (...) {\n    // Get the KJ exception\n    auto e = kj::getCaughtExceptionAsKj();\n\n    auto msg = e.getDescription();\n    if (!msg.startsWith(\"broken.\"_kj) && !msg.startsWith(\"remote.broken.\"_kj)) {\n      // If we already set up a brokenness reason, we shouldn't override it.\n      auto description = jsg::annotateBroken(msg, \"broken.constructorFailed\");\n      e.setDescription(kj::mv(description));\n    }\n\n    context.abort(kj::cp(e));\n    impl->classInstance = kj::mv(e);\n  }\n}\n\nWorker::Actor::~Actor() noexcept(false) {\n  // Note: We do not need an isolate lock to destroy the actor impl. Everything in it is specific\n  // to our thread, or is a handle that can be dropped outside of the lock.\n}\n\nvoid Worker::Actor::shutdown(uint16_t reasonCode, kj::Maybe<const kj::Exception&> error) {\n  // We're officially canceling all background work and we're going to destruct the Actor as soon\n  // as all IoContexts that reference it go out of scope. We might still log additional\n  // periodic messages, and that's good because we might care about that information. That said,\n  // we're officially \"broken\" from this point because we cannot service background work and our\n  // capability server should have triggered this (potentially indirectly) via its destructor.\n  KJ_IF_SOME(r, impl->ioContext) {\n    impl->metrics->shutdown(reasonCode, r.get()->getLimitEnforcer());\n  } else {\n    // The actor was shut down before the IoContext was even constructed, so no metrics are\n    // written.\n  }\n\n  shutdownActorCache(error);\n\n  impl->shutdownFulfiller->fulfill();\n}\n\nvoid Worker::Actor::shutdownActorCache(kj::Maybe<const kj::Exception&> error) {\n  KJ_IF_SOME(ac, impl->actorCache) {\n    ac.get()->shutdown(error);\n  } else {\n    // The actor was aborted before the actor cache was constructed, nothing to do.\n  }\n}\n\nkj::Promise<void> Worker::Actor::onShutdown() {\n  return impl->shutdownPromise.addBranch();\n}\n\nkj::Promise<void> Worker::Actor::onBroken() {\n  // TODO(soon): Detect and report other cases of brokenness, as described in worker.capnp.\n\n  kj::Promise<void> abortPromise = nullptr;\n\n  KJ_IF_SOME(rc, impl->ioContext) {\n    abortPromise = rc.get()->onAbort();\n  } else {\n    auto paf = kj::newPromiseAndFulfiller<kj::Promise<void>>();\n    abortPromise = kj::mv(paf.promise);\n    impl->abortFulfiller = kj::mv(paf.fulfiller);\n  }\n\n  return abortPromise;\n}\n\nconst Worker::Actor::Id& Worker::Actor::getId() {\n  return impl->actorId;\n}\n\nbool Worker::Actor::idsEqual(const Id& a, const Id& b) {\n  if (a.which() != b.which()) return false;\n\n  KJ_SWITCH_ONEOF(a) {\n    KJ_CASE_ONEOF(actorId, kj::Own<ActorIdFactory::ActorId>) {\n      return actorId->equals(*b.get<kj::Own<ActorIdFactory::ActorId>>());\n    }\n    KJ_CASE_ONEOF(str, kj::String) {\n      return str == b.get<kj::String>();\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nWorker::Actor::Id Worker::Actor::cloneId(Worker::Actor::Id& id) {\n  KJ_SWITCH_ONEOF(id) {\n    KJ_CASE_ONEOF(coloLocalId, kj::String) {\n      return kj::str(coloLocalId);\n    }\n    KJ_CASE_ONEOF(globalId, kj::Own<ActorIdFactory::ActorId>) {\n      return globalId->clone();\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nWorker::Actor::Id Worker::Actor::cloneId() {\n  return cloneId(impl->actorId);\n}\n\nkj::Maybe<jsg::JsRef<jsg::JsValue>> Worker::Actor::getTransient(Worker::Lock& lock) {\n  KJ_REQUIRE(&lock.getWorker() == worker.get());\n\n  if (impl->transient.tryGet<bool>().orDefault(false)) {\n    // First call and `hasTransient` was true. Initialize it now, since we have the lock.\n    jsg::Lock& js = lock;\n    impl->transient.init<jsg::JsRef<jsg::JsValue>>(js, js.obj());\n  }\n\n  return impl->transient.tryGet<jsg::JsRef<jsg::JsValue>>().map(\n      [&](jsg::JsRef<jsg::JsValue>& val) { return val.addRef(lock); });\n}\n\nkj::Maybe<ActorCacheInterface&> Worker::Actor::getPersistent() {\n  return impl->actorCache;\n}\n\nkj::Own<Worker::Actor::Loopback> Worker::Actor::getLoopback() {\n  return impl->loopback->addRef();\n}\n\nkj::Maybe<jsg::Ref<api::DurableObjectStorage>> Worker::Actor::makeStorageForSwSyntax(\n    Worker::Lock& lock) {\n  return impl->actorCache.map([&](kj::Own<ActorCacheInterface>& cache) {\n    return impl->makeStorage(lock, worker->getIsolate().getApi(), *cache);\n  });\n}\n\nvoid Worker::Actor::assertCanSetAlarm() {\n  KJ_SWITCH_ONEOF(impl->classInstance) {\n    KJ_CASE_ONEOF(_, Impl::NoClass) {\n      // Once upon a time, we allowed actors without classes. Let's make a nicer message if we\n      // we somehow see a classless actor attempt to run an alarm in the wild.\n      JSG_FAIL_REQUIRE(\n          TypeError, \"Your Durable Object must be class-based in order to call setAlarm()\");\n    }\n    KJ_CASE_ONEOF(_, Worker::ActorClassInfo*) {\n      KJ_FAIL_ASSERT(\"setAlarm() invoked before Durable Object ctor\");\n    }\n    KJ_CASE_ONEOF(_, Impl::Initializing) {\n      // We don't explicitly know if we have an alarm handler or not, so just let it happen. We'll\n      // handle it when we go to run the alarm.\n      return;\n    }\n    KJ_CASE_ONEOF(handler, api::ExportedHandler) {\n      JSG_REQUIRE(handler.alarm != kj::none, TypeError,\n          \"Your Durable Object class must have an alarm() handler in order to call setAlarm()\");\n      return;\n    }\n    KJ_CASE_ONEOF(exception, kj::Exception) {\n      // We've failed in the ctor, might as well just throw that exception for now.\n      kj::throwFatalException(kj::cp(exception));\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid Worker::Actor::Impl::HooksImpl::updateAlarmInMemory(kj::Maybe<kj::Date> newTime) {\n  if (newTime == kj::none) {\n    maybeAlarmPreviewTask = kj::none;\n    return;\n  }\n\n  auto scheduledTime = KJ_ASSERT_NONNULL(newTime);\n\n  auto retry = kj::coCapture([this, originalTime = scheduledTime]() -> kj::Promise<void> {\n    kj::Date scheduledTime = originalTime;\n\n    for (auto i: kj::zeroTo(WorkerInterface::ALARM_RETRY_MAX_TRIES)) {\n      co_await timerChannel.atTime(scheduledTime);\n      auto result = co_await loopback->getWorker(IoChannelFactory::SubrequestMetadata{})\n                        ->runAlarm(originalTime, i);\n\n      if (result.outcome == EventOutcome::OK || !result.retry) {\n        break;\n      }\n\n      auto delay = (WorkerInterface::ALARM_RETRY_START_SECONDS << i++) * kj::SECONDS;\n      scheduledTime = timerChannel.now() + delay;\n    }\n  });\n\n  maybeAlarmPreviewTask = retry();\n}\n\nkj::Maybe<kj::Promise<WorkerInterface::AlarmResult>> Worker::Actor::getAlarm(\n    kj::Date scheduledTime) {\n  KJ_IF_SOME(runningAlarm, impl->maybeRunningAlarm) {\n    if (runningAlarm.scheduledTime == scheduledTime) {\n      // The running alarm has the same time, we can just wait for it.\n      return runningAlarm.resultPromise.addBranch();\n    }\n  }\n\n  KJ_IF_SOME(scheduledAlarm, impl->maybeScheduledAlarm) {\n    if (scheduledAlarm.scheduledTime == scheduledTime) {\n      // The scheduled alarm has the same time, we can just wait for it.\n      return scheduledAlarm.resultPromise.addBranch();\n    }\n  }\n\n  return kj::none;\n}\n\nkj::Promise<WorkerInterface::ScheduleAlarmResult> Worker::Actor::scheduleAlarm(\n    kj::Date scheduledTime) {\n  KJ_IF_SOME(runningAlarm, impl->maybeRunningAlarm) {\n    if (runningAlarm.scheduledTime == scheduledTime) {\n      // The running alarm has the same time, we can just wait for it.\n      auto result = co_await runningAlarm.resultPromise;\n      co_return result;\n    }\n  }\n\n  KJ_IF_SOME(scheduledAlarm, impl->maybeScheduledAlarm) {\n    // We had a previously scheduled alarm, let's cancel it.\n    scheduledAlarm.resultFulfiller.cancel();\n    impl->maybeScheduledAlarm = kj::none;\n  }\n\n  KJ_IASSERT(impl->maybeScheduledAlarm == kj::none);\n  auto& scheduledAlarm = impl->maybeScheduledAlarm.emplace(\n      scheduledTime, kj::newPromiseAndFulfiller<WorkerInterface::AlarmResult>());\n\n  // Probably don't need to use kj::coCapture for this but doing so just to be on the\n  // safe side...\n  auto whenCanceled =\n      (kj::coCapture([&scheduledAlarm]() -> kj::Promise<WorkerInterface::ScheduleAlarmResult> {\n    // We've been cancelled, so return that result. Note that we cannot be resolved any other\n    // way until we return an AlarmFulfiller below.\n    co_return co_await scheduledAlarm.resultPromise;\n  }))();\n\n  // Date.now() < scheduledTime when the alarm comes in, since we subtract elapsed CPU time from\n  // the time of last I/O in the implementation of Date.now(). This difference could be used to\n  // implement a Spectre timer, so we have to wait a little longer until\n  // `Date.now() == scheduledTime`. Note that this also means that we could invoke ahead of its\n  // `scheduledTime` and we'll delay until appropriate, this may be useful in cases of clock skew.\n\n  co_return co_await handleAlarm(scheduledTime).exclusiveJoin(kj::mv(whenCanceled));\n}\n\nkj::Promise<WorkerInterface::ScheduleAlarmResult> Worker::Actor::handleAlarm(\n    kj::Date scheduledTime) {\n  // Let's wait for any running alarm to cleanup before we even delay.\n  co_await impl->runningAlarmTask;\n\n  co_await KJ_ASSERT_NONNULL(impl->ioContext)->atTime(scheduledTime);\n  // It's time to run! Let's tear apart the scheduled alarm and make a running alarm.\n\n  // `maybeScheduledAlarm` should have the same value we emplaced above. If another call to\n  // `scheduleAlarm()` emplaced a new value, then `whenCanceled` should have resolved which\n  // cancels this this promise chain.\n  auto scheduledAlarm = KJ_ASSERT_NONNULL(kj::mv(impl->maybeScheduledAlarm));\n  impl->maybeScheduledAlarm = kj::none;\n\n  impl->maybeRunningAlarm.emplace(Impl::RunningAlarm{\n    .scheduledTime = scheduledAlarm.scheduledTime,\n    .resultPromise = kj::mv(scheduledAlarm.resultPromise),\n  });\n  impl->runningAlarmTask = scheduledAlarm.cleanupPromise\n                               .attach(kj::defer([&impl = *impl]() {\n    // As soon as we get fulfilled or rejected, let's unset this alarm as the running alarm.\n    //\n    // NOTE: We could get here during `Actor`'s destructor, which in turn calls `Actor::Impl`'s\n    // destructor, which destroys `runningAlarmTask`, which is us. But in this case, `actor.impl`\n    // is already nulled out (the pointer gets nulled before the destructor runs). This is why we\n    // captured `impl` by reference above, rather than capturing `this`.\n    impl.maybeRunningAlarm = kj::none;\n  })).eagerlyEvaluate([](kj::Exception&& e) {\n    LOG_EXCEPTION(\"actorAlarmCleanup\", e);\n  }).fork();\n  co_return kj::mv(scheduledAlarm.resultFulfiller);\n}\n\nkj::Maybe<api::ExportedHandler&> Worker::Actor::getHandler() {\n  KJ_SWITCH_ONEOF(impl->classInstance) {\n    KJ_CASE_ONEOF(_, Impl::NoClass) {\n      return kj::none;\n    }\n    KJ_CASE_ONEOF(_, Worker::ActorClassInfo*) {\n      KJ_FAIL_ASSERT(\"ensureConstructed() wasn't called\");\n    }\n    KJ_CASE_ONEOF(_, Impl::Initializing) {\n      // This shouldn't be possible because ensureConstructed() would have initiated the\n      // construction task which would have taken an input lock as well as the isolate lock,\n      // which should have prevented any other code from executing on the actor until they\n      // were released.\n      KJ_FAIL_ASSERT(\"actor still initializing when getHandler() called\");\n    }\n    KJ_CASE_ONEOF(handler, api::ExportedHandler) {\n      return handler;\n    }\n    KJ_CASE_ONEOF(exception, kj::Exception) {\n      kj::throwFatalException(kj::cp(exception));\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nActorObserver& Worker::Actor::getMetrics() {\n  return *impl->metrics;\n}\n\nInputGate& Worker::Actor::getInputGate() {\n  return impl->inputGate;\n}\n\nOutputGate& Worker::Actor::getOutputGate() {\n  return impl->outputGate;\n}\n\nkj::Maybe<IoContext&> Worker::Actor::getIoContext() {\n  return impl->ioContext.map([](kj::Own<IoContext>& rc) -> IoContext& { return *rc; });\n}\n\nvoid Worker::Actor::setIoContext(kj::Own<IoContext> context) {\n  KJ_REQUIRE(impl->ioContext == kj::none);\n  KJ_IF_SOME(f, impl->abortFulfiller) {\n    f.get()->fulfill(context->onAbort());\n    impl->abortFulfiller = kj::none;\n  }\n  auto& limitEnforcer = context->getLimitEnforcer();\n  impl->ioContext = kj::mv(context);\n  impl->metricsFlushLoopTask =\n      impl->metrics->flushLoop(impl->timerChannel, limitEnforcer)\n          .eagerlyEvaluate([](kj::Exception&& e) { LOG_EXCEPTION(\"actorMetricsFlushLoop\", e); });\n}\n\njsg::JsObject Worker::Actor::getCtx(jsg::Lock& js) {\n  return KJ_REQUIRE_NONNULL(impl->ctxObject).getHandle(js);\n}\n\njsg::JsValue Worker::Actor::getEnv(jsg::Lock& js) {\n  return jsg::JsValue(KJ_REQUIRE_NONNULL(worker->impl->env).getHandle(js));\n}\n\nkj::Maybe<Worker::Actor::HibernationManager&> Worker::Actor::getHibernationManager() {\n  return impl->hibernationManager.map(\n      [](kj::Own<HibernationManager>& hib) -> HibernationManager& { return *hib; });\n}\n\nvoid Worker::Actor::setHibernationManager(kj::Own<HibernationManager> hib) {\n  KJ_REQUIRE(impl->hibernationManager == kj::none);\n  hib->setTimerChannel(impl->timerChannel);\n  // Not the cleanest way to provide hibernation manager with a timer channel reference, but\n  // where HibernationManager is constructed (actor-state), we don't have a timer channel ref.\n  impl->hibernationManager = kj::mv(hib);\n}\n\nkj::Maybe<uint16_t> Worker::Actor::getHibernationEventType() {\n  return impl->hibernationEventType;\n}\n\nkj::Own<Worker::Actor> Worker::Actor::addRef() {\n  KJ_IF_SOME(t, tracker) {\n    // We can attachToThisReference() here, attached object's lifetime being tied to refcounted\n    // instance is deliberate.\n    return kj::addRef(*this).attachToThisReference(t.get()->startRequest());\n  } else {\n    return kj::addRef(*this);\n  }\n}\n\n// =======================================================================================\n\nuint Worker::Isolate::getCurrentLoad() const {\n  return __atomic_load_n(&impl->lockAttemptGauge, __ATOMIC_RELAXED);\n}\n\nuint Worker::Isolate::getLockSuccessCount() const {\n  return __atomic_load_n(&impl->lockSuccessCount, __ATOMIC_RELAXED);\n}\n\nkj::Own<const Worker::Script> Worker::Isolate::newScript(kj::StringPtr scriptId,\n    const Script::Source& source,\n    IsolateObserver::StartType startType,\n    SpanParent parentSpan,\n    kj::Own<workerd::VirtualFileSystem> vfs,\n    bool logNewScript,\n    kj::Maybe<ValidationErrorReporter&> errorReporter,\n    kj::Maybe<kj::Own<api::pyodide::ArtifactBundler_State>> artifacts,\n    kj::Maybe<kj::Arc<workerd::jsg::modules::ModuleRegistry>> maybeNewModuleRegistry) const {\n  // Script doesn't already exist, so compile it.\n  return kj::atomicRefcounted<Script>(kj::atomicAddRef(*this), scriptId, source, startType,\n      logNewScript, errorReporter, kj::mv(artifacts), kj::mv(parentSpan), kj::mv(vfs),\n      kj::mv(maybeNewModuleRegistry));\n}\n\nvoid Worker::Isolate::completedRequest() const {\n  limitEnforcer->completedRequest(id);\n}\n\nbool Worker::Isolate::isInspectorEnabled() const {\n  return impl->inspector != kj::none;\n}\n\nnamespace {\n\n// We only run the inspector within process sandboxes. There, it is safe to query the real clock\n// for some things, and we do so because we may not have a IoContext available to get\n// Spectre-safe time.\n\n// Monotonic time in seconds with millisecond precision.\ndouble getMonotonicTimeForProcessSandboxOnly() {\n  KJ_REQUIRE(!isMultiTenantProcess(), \"precise timing not safe in multi-tenant processes\");\n  auto timePoint = kj::systemPreciseMonotonicClock().now();\n  return (timePoint - kj::origin<kj::TimePoint>()) / kj::MILLISECONDS / 1e3;\n}\n\n// Wall time in seconds with millisecond precision.\ndouble getWallTimeForProcessSandboxOnly() {\n  KJ_REQUIRE(!isMultiTenantProcess(), \"precise timing not safe in multi-tenant processes\");\n  auto timePoint = kj::systemPreciseCalendarClock().now();\n  return (timePoint - kj::UNIX_EPOCH) / kj::MILLISECONDS / 1e3;\n}\n}  // namespace\n\nclass Worker::Isolate::ResponseStreamWrapper final: public kj::AsyncOutputStream {\n public:\n  ResponseStreamWrapper(kj::Own<const Isolate> isolate,\n      kj::String requestId,\n      kj::Own<kj::AsyncOutputStream> inner,\n      api::StreamEncoding encoding,\n      RequestObserver& requestMetrics)\n      : constIsolate(kj::mv(isolate)),\n        requestId(kj::mv(requestId)),\n        inner(kj::mv(inner)),\n        requestMetrics(requestMetrics) {\n    if (encoding == api::StreamEncoding::GZIP) {\n      compStream.emplace().init<kj::GzipOutputStream>(decodedBuf, kj::GzipOutputStream::DECOMPRESS);\n    } else if (encoding == api::StreamEncoding::BROTLI) {\n      compStream.emplace().init<kj::BrotliOutputStream>(\n          decodedBuf, kj::BrotliOutputStream::DECOMPRESS);\n    }\n  }\n\n  ~ResponseStreamWrapper() noexcept(false) {\n    // It's possible that we already have an isolate lock, in which case we\n    // don't want to grab another one. Here, we can determine if we have a\n    // lock by checking if there is a current IoContext, if we do then we\n    // definitely have a current lock.\n    // While it is possible for us to have an isolate lock without a current\n    // IoContext, it is quite unlikely that we'd be cleaning up a\n    // ResponseStreamWrapper in that situation, so checking for the current\n    // IoContext should work fine.\n    if (IoContext::hasCurrent()) {\n      reportToInspector();\n    } else {\n      // In this case we assume we don't have a lock and need to grab one.\n      // If we continue to get warnings that we're taking the isolate lock\n      // recursively here, that means we're cleaning these outside of the\n      // IoContext but still have the isolate lock. In that case, we would\n      // likely need to add an API to jsg::Lock to get the current lock\n      // rather than relying on the IoContext.\n      jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {\n        Isolate::Impl::Lock recordedLock(*constIsolate, InspectorLock(requestMetrics), stackScope);\n        reportToInspector();\n      });\n    }\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override {\n    reportBytes(buffer);\n    return inner->write(buffer);\n  }\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {\n    for (auto& piece: pieces) {\n      reportBytes(piece);\n    }\n    return inner->write(pieces);\n  }\n  void reportBytes(kj::ArrayPtr<const byte> buffer) {\n    if (buffer.size() == 0) {\n      return;\n    }\n\n    rawSize += buffer.size();\n\n    auto prevDecodedSize = decodedBuf.getWrittenSize();\n    KJ_IF_SOME(comp, compStream) {\n      KJ_SWITCH_ONEOF(comp) {\n        KJ_CASE_ONEOF(gzip, kj::GzipOutputStream) {\n          // On invalid gzip discard the previously decoded body and rethrow to stop the stream.\n          // This way we will report sizes up to this point but won't read any more invalid data.\n          KJ_ON_SCOPE_FAILURE(decodedBuf.reset());\n\n          gzip.write(buffer);\n          gzip.flush();\n        }\n        KJ_CASE_ONEOF(brotli, kj::BrotliOutputStream) {\n          KJ_ON_SCOPE_FAILURE(decodedBuf.reset());\n\n          brotli.write(buffer);\n          brotli.flush();\n        }\n      }\n    } else {\n      decodedBuf.write(buffer);\n    }\n    auto decodedChunkSize = decodedBuf.getWrittenSize() - prevDecodedSize;\n\n    jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {\n      Isolate::Impl::Lock recordedLock(*constIsolate, InspectorLock(requestMetrics), stackScope);\n      auto& isolate = const_cast<Isolate&>(*constIsolate);\n\n      KJ_IF_SOME(i, isolate.currentInspectorSession) {\n        capnp::MallocMessageBuilder message;\n\n        auto event = message.initRoot<cdp::Event>();\n\n        auto params = event.initNetworkDataReceived();\n        params.setRequestId(requestId);\n        params.setEncodedDataLength(buffer.size());\n        params.setDataLength(decodedChunkSize);\n        params.setTimestamp(getMonotonicTimeForProcessSandboxOnly());\n\n        i.sendNotification(event);\n      }\n    });\n  }\n\n  // Intentionally not wrapping `tryPumpFrom` to force consumer to use `write` in a loop which,\n  // in turn, will report each chunk to the inspector to show progress of a slow response.\n\n  kj::Promise<void> whenWriteDisconnected() override {\n    return inner->whenWriteDisconnected();\n  }\n\n private:\n  using InspectorLock = InspectorChannelImpl::InspectorLock;\n\n  kj::Own<const Isolate> constIsolate;\n  kj::String requestId;\n  kj::Own<kj::AsyncOutputStream> inner;\n  size_t rawSize = 0;\n  LimitedBodyWrapper decodedBuf;\n  kj::Maybe<kj::OneOf<kj::GzipOutputStream, kj::BrotliOutputStream>> compStream;\n  RequestObserver& requestMetrics;\n\n  // Called when the wrapper is destroyed.\n  // This should only ever be called when we are holding the isolate lock.\n  void reportToInspector() {\n    auto& isolate = const_cast<Isolate&>(*constIsolate);\n\n    KJ_IF_SOME(i, isolate.currentInspectorSession) {\n      capnp::MallocMessageBuilder message;\n\n      auto event = message.initRoot<cdp::Event>();\n\n      auto params = event.initNetworkLoadingFinished();\n      params.setRequestId(requestId);\n      params.setEncodedDataLength(rawSize);\n      params.setTimestamp(getMonotonicTimeForProcessSandboxOnly());\n      auto response = params.initCfResponse();\n      KJ_IF_SOME(body, decodedBuf.getArray()) {\n        response.setBase64Encoded(true);\n        response.setBody(kj::encodeBase64(body));\n      }\n\n      i.sendNotification(event);\n    }\n  }\n};\n\nclass Worker::Isolate::SubrequestClient final: public WorkerInterface {\n public:\n  explicit SubrequestClient(kj::Own<const Isolate> isolate,\n      kj::Own<WorkerInterface> inner,\n      kj::HttpHeaderId contentEncodingHeaderId,\n      RequestObserver& requestMetrics)\n      : constIsolate(kj::mv(isolate)),\n        inner(kj::mv(inner)),\n        contentEncodingHeaderId(contentEncodingHeaderId),\n        requestMetrics(kj::addRef(requestMetrics)) {}\n  KJ_DISALLOW_COPY_AND_MOVE(SubrequestClient);\n  kj::Promise<void> request(kj::HttpMethod method,\n      kj::StringPtr url,\n      const kj::HttpHeaders& headers,\n      kj::AsyncInputStream& requestBody,\n      kj::HttpService::Response& response) override;\n  kj::Promise<void> connect(kj::StringPtr host,\n      const kj::HttpHeaders& headers,\n      kj::AsyncIoStream& connection,\n      kj::HttpService::ConnectResponse& tunnel,\n      kj::HttpConnectSettings settings) override;\n  kj::Promise<void> prewarm(kj::StringPtr url) override;\n  kj::Promise<ScheduledResult> runScheduled(kj::Date scheduledTime, kj::StringPtr cron) override;\n  kj::Promise<AlarmResult> runAlarm(kj::Date scheduledTime, uint32_t retryCount) override;\n  kj::Promise<CustomEvent::Result> customEvent(kj::Own<CustomEvent> event) override;\n\n private:\n  kj::Own<const Isolate> constIsolate;\n  kj::Own<WorkerInterface> inner;\n  kj::HttpHeaderId contentEncodingHeaderId;\n  kj::Own<RequestObserver> requestMetrics;\n};\n\nkj::Promise<void> Worker::Isolate::SubrequestClient::request(kj::HttpMethod method,\n    kj::StringPtr url,\n    const kj::HttpHeaders& headers,\n    kj::AsyncInputStream& requestBody,\n    kj::HttpService::Response& response) {\n  using InspectorLock = InspectorChannelImpl::InspectorLock;\n\n  auto signalRequest = [this, method, urlCopy = kj::str(url),\n                           headersCopy = headers.clone()]() -> kj::Maybe<kj::String> {\n    return jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) -> kj::Maybe<kj::String> {\n      Isolate::Impl::Lock recordedLock(*constIsolate, InspectorLock(*requestMetrics), stackScope);\n      auto& lock = *recordedLock.lock;\n      auto& isolate = const_cast<Isolate&>(*constIsolate);\n\n      if (isolate.currentInspectorSession == kj::none) {\n        return kj::none;\n      }\n\n      auto& i = KJ_ASSERT_NONNULL(isolate.currentInspectorSession);\n      if (!i.isNetworkEnabled()) {\n        return kj::none;\n      }\n\n      return lock.withinHandleScope([&] {\n        auto requestId = kj::str(isolate.nextRequestId++);\n\n        capnp::MallocMessageBuilder message;\n\n        auto event = message.initRoot<cdp::Event>();\n\n        auto params = event.initNetworkRequestWillBeSent();\n        params.setRequestId(requestId);\n        params.setLoaderId(\"\");\n        params.setTimestamp(getMonotonicTimeForProcessSandboxOnly());\n        params.setWallTime(getWallTimeForProcessSandboxOnly());\n        params.setType(cdp::Page::ResourceType::FETCH);\n\n        auto initiator = params.initInitiator();\n        initiator.setType(cdp::Network::Initiator::Type::SCRIPT);\n        stackTraceToCDP(lock, initiator.initStack());\n\n        auto request = params.initRequest();\n        request.setUrl(urlCopy);\n        request.setMethod(kj::str(method));\n\n        headersToCDP(headersCopy, request.initHeaders());\n\n        i.sendNotification(event);\n        return kj::mv(requestId);\n      });\n    });\n  };\n\n  auto signalResponse =\n      [this](kj::String requestId, uint statusCode, kj::StringPtr statusText,\n          const kj::HttpHeaders& headers,\n          kj::Own<kj::AsyncOutputStream> responseBody) -> kj::Own<kj::AsyncOutputStream> {\n    // Note that we cannot take the isolate lock here, because if this is a worker-to-worker\n    // subrequest, the destination isolate's lock may already be held, and we can't take multiple\n    // isolate locks at once as this could lead to deadlock if the lock orders aren't consistent.\n    //\n    // Meanwhile, though, `statusText` and `headers` may point to things that will go away\n    // immediately after we return. So, let's construct our message now, so that we don't have to\n    // make redundant copies.\n    //\n    // Note that signalResponse() is only called at all if signalRequest() determined that network\n    // inspection is enabled.\n\n    auto message = kj::heap<capnp::MallocMessageBuilder>();\n\n    auto event = message->initRoot<cdp::Event>();\n\n    auto params = event.initNetworkResponseReceived();\n    params.setRequestId(requestId);\n    params.setTimestamp(getMonotonicTimeForProcessSandboxOnly());\n    params.setType(cdp::Page::ResourceType::OTHER);\n\n    auto response = params.initResponse();\n    response.setStatus(statusCode);\n    response.setStatusText(statusText);\n    response.setProtocol(\"http/1.1\");\n    KJ_IF_SOME(type, headers.get(kj::HttpHeaderId::CONTENT_TYPE)) {\n      KJ_IF_SOME(parsed, MimeType::tryParse(type, MimeType::IGNORE_PARAMS)) {\n        response.setMimeType(parsed.toString());\n\n        // Normally Chrome would know what it's loading based on an element or API used for\n        // the request. We don't have that privilege, but still want network filters to work,\n        // so we do our best-effort guess of the resource type based on its mime type.\n        if (MimeType::HTML == parsed || MimeType::XHTML == parsed) {\n          params.setType(cdp::Page::ResourceType::DOCUMENT);\n        } else if (MimeType::CSS == parsed) {\n          params.setType(cdp::Page::ResourceType::STYLESHEET);\n        } else if (MimeType::isJavascript(parsed)) {\n          params.setType(cdp::Page::ResourceType::SCRIPT);\n        } else if (MimeType::isImage(parsed)) {\n          params.setType(cdp::Page::ResourceType::IMAGE);\n        } else if (MimeType::isAudio(parsed) || MimeType::isVideo(parsed)) {\n          params.setType(cdp::Page::ResourceType::MEDIA);\n        } else if (MimeType::isFont(parsed)) {\n          params.setType(cdp::Page::ResourceType::FONT);\n        } else if (MimeType::MANIFEST_JSON == parsed) {\n          params.setType(cdp::Page::ResourceType::MANIFEST);\n        } else if (MimeType::VTT == parsed) {\n          params.setType(cdp::Page::ResourceType::TEXT_TRACK);\n        } else if (MimeType::EVENT_STREAM == parsed) {\n          params.setType(cdp::Page::ResourceType::EVENT_SOURCE);\n        } else if (MimeType::isXml(parsed) || MimeType::isJson(parsed)) {\n          params.setType(cdp::Page::ResourceType::XHR);\n        }\n\n      } else {\n        response.setMimeType(MimeType::PLAINTEXT_STRING);\n      }\n    } else {\n      response.setMimeType(MimeType::PLAINTEXT_STRING);\n    }\n    headersToCDP(headers, response.initHeaders());\n\n    auto encoding = api::StreamEncoding::IDENTITY;\n    KJ_IF_SOME(encodingStr, headers.get(contentEncodingHeaderId)) {\n      if (encodingStr == \"gzip\") {\n        encoding = api::StreamEncoding::GZIP;\n      } else if (encodingStr == \"br\") {\n        encoding = api::StreamEncoding::BROTLI;\n      }\n    }\n\n    // Defer to a later turn of the event loop so that it's safe to take a lock.\n    return kj::newPromisedStream(kj::evalLater(\n        [this, responseBody = kj::mv(responseBody), message = kj::mv(message), event, encoding,\n            requestId = kj::mv(requestId)]() mutable -> kj::Own<kj::AsyncOutputStream> {\n      // Now we know we can lock...\n      return jsg::runInV8Stack(\n          [&](jsg::V8StackScope& stackScope) mutable -> kj::Own<kj::AsyncOutputStream> {\n        Isolate::Impl::Lock recordedLock(*constIsolate, InspectorLock(*requestMetrics), stackScope);\n        auto& isolate = const_cast<Isolate&>(*constIsolate);\n\n        // We shouldn't even get here if network inspection isn't active since signalRequest() would\n        // have returned null... but double-check anyway.\n        if (isolate.currentInspectorSession == kj::none) {\n          return kj::mv(responseBody);\n        }\n\n        auto& i = KJ_ASSERT_NONNULL(isolate.currentInspectorSession);\n        if (!i.isNetworkEnabled()) {\n          return kj::mv(responseBody);\n        }\n\n        i.sendNotification(event);\n\n        return kj::heap<ResponseStreamWrapper>(kj::atomicAddRef(*constIsolate), kj::mv(requestId),\n            kj::mv(responseBody), encoding, *requestMetrics);\n      });\n    }));\n  };\n  using SignalResponse = decltype(signalResponse);\n\n  class ResponseWrapper final: public kj::HttpService::Response {\n   public:\n    ResponseWrapper(\n        kj::HttpService::Response& inner, kj::String requestId, SignalResponse signalResponse)\n        : inner(inner),\n          requestId(kj::mv(requestId)),\n          signalResponse(kj::mv(signalResponse)) {}\n\n    kj::Own<kj::AsyncOutputStream> send(uint statusCode,\n        kj::StringPtr statusText,\n        const kj::HttpHeaders& headers,\n        kj::Maybe<uint64_t> expectedBodySize = kj::none) override {\n      auto body = inner.send(statusCode, statusText, headers, expectedBodySize);\n      return signalResponse(kj::mv(requestId), statusCode, statusText, headers, kj::mv(body));\n    }\n\n    kj::Own<kj::WebSocket> acceptWebSocket(const kj::HttpHeaders& headers) override {\n      auto webSocket = inner.acceptWebSocket(headers);\n      // TODO(someday): Support sending WebSocket frames over CDP. For now we fake an empty\n      //   response.\n      signalResponse(kj::mv(requestId), 101, \"Switching Protocols\", headers, newNullOutputStream());\n      return kj::mv(webSocket);\n    }\n\n   private:\n    kj::HttpService::Response& inner;\n    kj::String requestId;\n    SignalResponse signalResponse;\n  };\n\n  // For accurate lock metrics, we want to avoid taking a recursive isolate lock, so we postpone\n  // the request until a later turn of the event loop.\n  auto maybeRequestId = co_await kj::evalLater(kj::mv(signalRequest));\n\n  // While we checked above that the headers are valid, let's check again\n  // after the co_await...\n  KJ_IF_SOME(rid, maybeRequestId) {\n    ResponseWrapper wrapper(response, kj::mv(rid), kj::mv(signalResponse));\n    co_await inner->request(method, url, headers, requestBody, wrapper);\n  } else {\n    co_await inner->request(method, url, headers, requestBody, response);\n  }\n}\n\nkj::Promise<void> Worker::Isolate::SubrequestClient::connect(kj::StringPtr host,\n    const kj::HttpHeaders& headers,\n    kj::AsyncIoStream& connection,\n    kj::HttpService::ConnectResponse& tunnel,\n    kj::HttpConnectSettings settings) {\n  // TODO(someday): EW-7116 Figure out how to represent TCP connections in the devtools network tab.\n  return inner->connect(host, headers, connection, tunnel, kj::mv(settings));\n}\n\n// TODO(someday): Log other kinds of subrequests?\nkj::Promise<void> Worker::Isolate::SubrequestClient::prewarm(kj::StringPtr url) {\n  return inner->prewarm(url);\n}\nkj::Promise<WorkerInterface::ScheduledResult> Worker::Isolate::SubrequestClient::runScheduled(\n    kj::Date scheduledTime, kj::StringPtr cron) {\n  return inner->runScheduled(scheduledTime, cron);\n}\nkj::Promise<WorkerInterface::AlarmResult> Worker::Isolate::SubrequestClient::runAlarm(\n    kj::Date scheduledTime, uint32_t retryCount) {\n  return inner->runAlarm(scheduledTime, retryCount);\n}\nkj::Promise<WorkerInterface::CustomEvent::Result> Worker::Isolate::SubrequestClient::customEvent(\n    kj::Own<CustomEvent> event) {\n  return inner->customEvent(kj::mv(event));\n}\n\nkj::Own<WorkerInterface> Worker::Isolate::wrapSubrequestClient(kj::Own<WorkerInterface> client,\n    kj::HttpHeaderId contentEncodingHeaderId,\n    RequestObserver& requestMetrics) const {\n  if (impl->inspector != kj::none) {\n    client = kj::heap<SubrequestClient>(\n        kj::atomicAddRef(*this), kj::mv(client), contentEncodingHeaderId, requestMetrics);\n  }\n\n  return client;\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/io/worker.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// Classes to manage lifetime of workers, scripts, and isolates.\n\n#include <workerd/io/actor-cache.h>  // because we can't forward-declare ActorCache::SharedLru.\n#include <workerd/io/actor-id.h>\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/io/container.capnp.h>\n#include <workerd/io/frankenvalue.h>\n#include <workerd/io/io-channels.h>\n#include <workerd/io/io-timers.h>\n#include <workerd/io/observer.h>\n#include <workerd/io/request-tracker.h>\n#include <workerd/io/trace.h>\n#include <workerd/io/worker-interface.h>\n#include <workerd/io/worker-source.h>\n#include <workerd/jsg/async-context.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/modules-new.h>\n#include <workerd/jsg/modules.h>\n#include <workerd/util/strong-bool.h>\n#include <workerd/util/weak-refs.h>\n\n#include <kj/compat/http.h>\n#include <kj/mutex.h>\n\nnamespace v8 {\nclass BackingStore;\nclass Isolate;\n}  // namespace v8\n\nnamespace workerd {\n\nWD_STRONG_BOOL(StructuredLogging);\nWD_STRONG_BOOL(ProcessStdioPrefixed);\n\nnamespace api {\nclass DurableObjectState;\nclass DurableObjectStorage;\nclass ServiceWorkerGlobalScope;\nstruct ExportedHandler;\nstruct CryptoAlgorithm;\nstruct QueueExportedHandler;\nclass WebSocket;\nclass WebSocketRequestResponsePair;\nclass ExecutionContext;\nnamespace pyodide {\nstruct ArtifactBundler_State;\nstruct EmscriptenRuntime;\nKJ_DECLARE_NON_POLYMORPHIC(ArtifactBundler_State);\n}  // namespace pyodide\n}  // namespace api\n\nclass IsolateLimitEnforcer;\nenum class UncaughtExceptionSource;\nclass VirtualFileSystem;\n\nclass ThreadContext;\nclass IoContext;\nclass InputGate;\nclass OutputGate;\n\n// Type signature of an entrypoint implementation class (Durable Object or stateless service).\nusing ExecutionContextOrState =\n    kj::OneOf<jsg::Ref<api::ExecutionContext>, jsg::Ref<api::DurableObjectState>>;\nusing EntrypointClass =\n    jsg::Constructor<api::ExportedHandler(ExecutionContextOrState ctx, jsg::Value env)>;\n\n// The type of a top-level export -- either a simple handler or a class.\nusing NamedExport = kj::OneOf<EntrypointClass, api::ExportedHandler>;\n\nstruct EntrypointClasses {\n  // Class constructor for WorkerEntrypoint.\n  jsg::JsObject workerEntrypoint;\n\n  // Class constructor for DurableObject (aka api::DurableObjectBase).\n  jsg::JsObject durableObject;\n\n  // Class constructor for WorkflowEntrypoint\n  jsg::JsObject workflowEntrypoint;\n};\n\n// An instance of a Worker.\n//\n// Typically each worker script is loaded into a single Worker instance which is reused by\n// multiple requests. The Worker can only be used by one thread at a time, so multiple requests\n// for the same worker can block each other. JavaScript code is asynchronous, though, so any such\n// blocking should be brief.\n//\n// Note: This class should be referred to as \"Worker instance\" in cases where the bare word\n//   \"Worker\" is ambiguous. I considered naming the class WorkerInstance, but it feels redundant\n//   for a class name to end in \"Instance\". (\"I have an instance of WorkerInstance...\")\nclass Worker: public kj::AtomicRefcounted {\n public:\n  using VersionInfo = Worker_VersionInfo;\n  class Script;\n  class Isolate;\n  class Api;\n\n  class ValidationErrorReporter {\n   public:\n    virtual void addError(kj::String error) = 0;\n\n    // Report that the Worker implements a stateless entrypoint (e.g. WorkerEntrypoint or plain\n    // object export) with the given export name and methods.\n    virtual void addEntrypoint(\n        kj::Maybe<kj::StringPtr> exportName, kj::Array<kj::String> methods) = 0;\n\n    // Report that the Worker exports a Durable Object class with the given name.\n    virtual void addActorClass(kj::StringPtr exportName) = 0;\n\n    // Report that the Worker exports a Workflow class with the given name.\n    virtual void addWorkflowClass(kj::StringPtr exportName, kj::Array<kj::String> methods) = 0;\n  };\n\n  class LockType;\n\n  enum class ConsoleMode {\n    // Only send `console.log`s to the inspector. Default, production behavior.\n    INSPECTOR_ONLY,\n    // Send `console.log`s to the inspector and stdout/err. Behavior running `workerd` locally.\n    STDOUT,\n  };\n\n  struct LoggingOptions {\n    ConsoleMode consoleMode = Worker::ConsoleMode::INSPECTOR_ONLY;\n    StructuredLogging structuredLogging = StructuredLogging::NO;\n    ProcessStdioPrefixed processStdioPrefixed = ProcessStdioPrefixed::YES;\n    kj::ConstString stdoutPrefix = \"stdout:\"_kjc;\n    kj::ConstString stderrPrefix = \"stderr:\"_kjc;\n\n    LoggingOptions() = default;\n    LoggingOptions(LoggingOptions&&) = default;\n    LoggingOptions& operator=(LoggingOptions&&) = default;\n\n    explicit LoggingOptions(ConsoleMode mode): consoleMode(mode) {}\n\n    LoggingOptions(const LoggingOptions& other)\n        : consoleMode(other.consoleMode),\n          structuredLogging(other.structuredLogging),\n          processStdioPrefixed(other.processStdioPrefixed),\n          stdoutPrefix(other.stdoutPrefix.clone()),\n          stderrPrefix(other.stderrPrefix.clone()) {}\n\n    LoggingOptions& operator=(const LoggingOptions& other) {\n      consoleMode = other.consoleMode;\n      structuredLogging = other.structuredLogging;\n      processStdioPrefixed = other.processStdioPrefixed;\n      stdoutPrefix = other.stdoutPrefix.clone();\n      stderrPrefix = other.stderrPrefix.clone();\n      return *this;\n    }\n  };\n\n  explicit Worker(kj::Own<const Script> script,\n      kj::Own<WorkerObserver> metrics,\n      kj::FunctionParam<void(jsg::Lock& lock,\n          const Api& api,\n          v8::Local<v8::Object> target,\n          v8::Local<v8::Object> ctxExports)> compileBindings,\n      IsolateObserver::StartType startType,\n      SpanParent parentSpan,\n      LockType lockType,\n      kj::Maybe<ValidationErrorReporter&> errorReporter = kj::none,\n      kj::Maybe<kj::Duration&> startupTime = kj::none);\n  // `compileBindings()` is a callback that constructs all of the bindings and adds them as\n  // properties to `target`. It also compiles the `ctx.exports` object and writes it to\n  // `ctxExports`. Note that it is permissible for this callback to save a handle to `ctxExports`\n  // and fill it in later if needed, as long as it is filled in before any requests are started.\n\n  ~Worker() noexcept(false);\n  KJ_DISALLOW_COPY_AND_MOVE(Worker);\n\n  inline const Script& getScript() const {\n    return *script;\n  }\n\n  inline const Isolate& getIsolate() const;\n\n  inline const WorkerObserver& getMetrics() const {\n    return *metrics;\n  }\n\n  class Lock;\n\n  inline auto runInLockScope(LockType lockType, auto func) const;\n\n  class AsyncLock;\n\n  // Places this thread into the queue of threads which are interested in locking this isolate,\n  // and returns when it is this thread's turn. The thread must still obtain a `Worker::Lock`, but\n  // by obtaining an `AsyncLock` first, the thread ensures that it is not fighting over the lock\n  // with many other threads, and all interested threads get their fair turn.\n  kj::Promise<AsyncLock> takeAsyncLockWithoutRequest(SpanParent parentSpan) const;\n\n  // Places this thread into the queue of threads which are interested in locking this isolate,\n  // and returns when it is this thread's turn. The thread must still obtain a `Worker::Lock`, but\n  // by obtaining an `AsyncLock` first, the thread ensures that it is not fighting over the lock\n  // with many other threads, and all interested threads get their fair turn.\n  //\n  // The version accepting a `request` metrics object accumulates lock timing data and reports the\n  // data via `request`'s trace span.\n  kj::Promise<AsyncLock> takeAsyncLock(RequestObserver& request) const;\n\n  class Actor;\n\n  // Like takeAsyncLock(), but also takes care of actor cache time-based eviction and backpressure.\n  kj::Promise<AsyncLock> takeAsyncLockWhenActorCacheReady(\n      kj::Date now, Actor& actor, RequestObserver& request) const;\n\n  static void setupContext(\n      jsg::Lock& lock, v8::Local<v8::Context> context, const LoggingOptions& loggingOptions);\n\n private:\n  kj::Own<const Script> script;\n\n  kj::Own<WorkerObserver> metrics;\n\n  // metrics needs to be first to be destroyed last to correctly capture destruction timing.\n  // it needs script to report destruction time, so it comes right after that.\n  TeardownFinishedGuard<WorkerObserver&> teardownGuard{*metrics};\n\n  struct Impl;\n  kj::Own<Impl> impl;\n\n  struct ActorClassInfo {\n    EntrypointClass cls;\n    bool missingSuperclass;\n  };\n\n  class InspectorClient;\n  class AsyncWaiter;\n  friend constexpr bool _kj_internal_isPolymorphic(AsyncWaiter*);\n\n  static void handleLog(jsg::Lock& js,\n      const LoggingOptions& loggingOptions,\n      LogLevel level,\n      const v8::Global<v8::Function>& original,\n      const v8::FunctionCallbackInfo<v8::Value>& info);\n\n  void processEntrypointClass(jsg::Lock& js,\n      EntrypointClass cls,\n      EntrypointClasses entrypointClasses,\n      kj::String handlerName);\n};\n\n// A compiled script within an Isolate, but which hasn't been instantiated into a particular\n// context (Worker).\nclass Worker::Script: public kj::AtomicRefcounted {\n public:\n  ~Script() noexcept(false);\n  KJ_DISALLOW_COPY_AND_MOVE(Script);\n\n  inline kj::StringPtr getId() const {\n    return id;\n  }\n  inline const Isolate& getIsolate() const {\n    return *isolate;\n  }\n  inline bool isModular() const {\n    return modular;\n  }\n  inline bool isPython() const {\n    return python;\n  }\n  inline kj::Maybe<kj::Arc<DynamicEnvBuilder>> getDynamicEnvBuilder() const {\n    return mapAddRef(dynamicEnvBuilder);\n  }\n\n  void installVirtualFileSystemOnContext(v8::Local<v8::Context> context) const;\n\n  const capnp::SchemaLoader& getSchemaLoader() const;\n\n  struct CompiledGlobal {\n    jsg::V8Ref<v8::String> name;\n    jsg::V8Ref<v8::Value> value;\n  };\n\n  // Historically these types were declared here, but then they were moved to `WorkerSource`. We\n  // maintain aliases here for backwards compatibility.\n  // TODO(cleanup): Update all the references, then remove these.\n  using EsModule = WorkerSource::EsModule;\n  using CommonJsModule = WorkerSource::CommonJsModule;\n  using TextModule = WorkerSource::TextModule;\n  using DataModule = WorkerSource::DataModule;\n  using WasmModule = WorkerSource::WasmModule;\n  using JsonModule = WorkerSource::JsonModule;\n  using PythonModule = WorkerSource::PythonModule;\n  using PythonRequirement = WorkerSource::PythonRequirement;\n  using CapnpModule = WorkerSource::CapnpModule;\n  using ModuleContent = WorkerSource::ModuleContent;\n  using Module = WorkerSource::Module;\n  using ScriptSource = WorkerSource::ScriptSource;\n  using ModulesSource = WorkerSource::ModulesSource;\n  using Source = WorkerSource;\n\n private:\n  kj::Own<const Isolate> isolate;\n  kj::String id;\n  bool modular;\n  bool python;\n\n  struct Impl;\n  kj::Own<Impl> impl;\n\n  kj::Maybe<kj::Arc<DynamicEnvBuilder>> dynamicEnvBuilder;\n\n  friend class Worker;\n\n public:  // pretend this is private (needs to be public because allocated through template)\n  explicit Script(kj::Own<const Isolate> isolate,\n      kj::StringPtr id,\n      const Source& source,\n      IsolateObserver::StartType startType,\n      bool logNewScript,\n      kj::Maybe<ValidationErrorReporter&> errorReporter,\n      kj::Maybe<kj::Own<api::pyodide::ArtifactBundler_State>> artifacts,\n      SpanParent parentSpan,\n      kj::Own<workerd::VirtualFileSystem> vfs,\n      kj::Maybe<kj::Arc<workerd::jsg::modules::ModuleRegistry>> maybeNewModuleRegistry);\n};\n\n// Multiple zones may share the same script. We would like to compile each script only once,\n// yet still provide strong separation between zones. To that end, each Script gets a V8\n// Isolate, while each Zone sharing that script gets a JavaScript context (global object).\n//\n// Note that this means that multiple workers sharing the same script cannot execute\n// concurrently. Worker::Lock takes care of this.\n//\n// An Isolate maintains weak maps of Workers and Scripts loaded within it.\n//\n// An Isolate is persisted by strong references given to each `Worker::Script` returned from\n// `newScript()`. At various points, other strong references are made, but these are generally\n// ephemeral. So when the last script is destructed, the isolate can be expected to also be\n// destructed soon.\nclass Worker::Isolate: public kj::AtomicRefcounted {\n public:\n  // Determines whether a devtools inspector client can be attached.\n  enum class InspectorPolicy {\n    DISALLOW,\n    ALLOW_UNTRUSTED,\n    ALLOW_FULLY_TRUSTED,\n  };\n\n  // Creates an isolate with the given ID. The ID only matters for metrics-reporting purposes.\n  // Usually it matches the script ID. An exception is preview isolates: there, each preview\n  // session has one isolate which may load many iterations of the script (this allows the\n  // inspector session to stay open across them).\n  // The Isolate object owns the Api object and outlives it in order to report teardown timing.\n  // The Api object is created before the Isolate object and does not strictly require\n  // request-specific information.\n  explicit Isolate(kj::Own<Api> api,\n      kj::Own<IsolateObserver> metrics,\n      kj::StringPtr id,\n      kj::Own<IsolateLimitEnforcer> limitEnforcer,\n      InspectorPolicy inspectorPolicy,\n      LoggingOptions loggingOptions = {});\n\n  ~Isolate() noexcept(false);\n  KJ_DISALLOW_COPY_AND_MOVE(Isolate);\n\n  // Get the current Worker::Isolate from the current jsg::Lock\n  static const Isolate& from(jsg::Lock& js);\n\n  // This callback gets executed when the CPU limiter is nearly out of time. It has to be signal\n  // safe since we call it in a signal handler.\n  //\n  // We give a reference to the callback to the limit enforcer, so it has to outlive the limit\n  // enforcer. The Isolate outlives the limit enforcer. If this function is called a second time, we\n  // throw to avoid invalidating references.\n  void setCpuLimitNearlyExceededCallback(kj::Function<void(void)> cb) const;\n  // Returns a reference to cpuLimitNearlyExceededCallback. Can't outlive the Isolate.\n  kj::Maybe<kj::Function<void(void)>> getCpuLimitNearlyExceededCallback() const;\n\n  // Registers a WASM module's linear memory and offsets for receiving the \"shut down\" signal.\n  // The signal offset is optional: when kj::none, the module will only receive the terminated\n  // flag but will not get the SIGXCPU warning. See TrackedWasmInstanceList::registerSignal().\n  void registerTrackedWasmInstance(jsg::Lock& js,\n      kj::Array<kj::byte> memory,\n      kj::Maybe<uint32_t> signalOffset,\n      uint32_t terminatedOffset) const;\n\n  inline IsolateObserver& getMetrics() {\n    return *metrics;\n  }\n\n  inline const IsolateObserver& getMetrics() const {\n    return *metrics;\n  }\n\n  inline kj::StringPtr getId() const {\n    return id;\n  }\n\n  // Parses the given code to create a new script object and returns it.\n  //\n  // Note that the `source` is fully consumed before this method returns, so the underlying buffers\n  // it points into can be freed immediately after the call.\n  kj::Own<const Worker::Script> newScript(kj::StringPtr id,\n      const Script::Source& source,\n      IsolateObserver::StartType startType,\n      SpanParent parentSpan,\n      kj::Own<workerd::VirtualFileSystem> vfs,\n      bool logNewScript = false,\n      kj::Maybe<ValidationErrorReporter&> errorReporter = kj::none,\n      kj::Maybe<kj::Own<api::pyodide::ArtifactBundler_State>> artifacts = kj::none,\n      kj::Maybe<kj::Arc<workerd::jsg::modules::ModuleRegistry>> maybeNewModuleRegistry =\n          kj::none) const;\n\n  inline IsolateLimitEnforcer& getLimitEnforcer() {\n    return *limitEnforcer;\n  }\n\n  inline const IsolateLimitEnforcer& getLimitEnforcer() const {\n    return *limitEnforcer;\n  }\n\n  inline Api& getApi() {\n    return *api;\n  }\n\n  inline const Api& getApi() const {\n    return *api;\n  }\n\n  // Returns the number of threads currently blocked trying to lock this isolate's mutex (using\n  // takeAsyncLock()).\n  uint getCurrentLoad() const;\n\n  // Returns a count that is incremented upon every successful lock.\n  uint getLockSuccessCount() const;\n\n  // Accepts a connection to the V8 inspector and handles requests until the client disconnects.\n  // Also adds a special JSON value to the header identified by `controlHeaderId`, for compatibility\n  // with internal Cloudflare systems.\n  //\n  // This overload will dispatch all inspector messages on the _calling thread's_ `kj::Executor`.\n  // When linked against vanilla V8, this means that CPU profiling will only profile JavaScript\n  // running on the _calling thread_, which will most likely only be inspector console commands, and\n  // is not typically desired.\n  //\n  // For the above reason , this overload is currently only suitable for use by the internal Workers\n  // Runtime codebase, which patches V8 to profile whichever thread currently holds the `v8::Locker`\n  // for this Isolate.\n  kj::Promise<void> attachInspector(kj::Timer& timer,\n      kj::Duration timerOffset,\n      kj::HttpService::Response& response,\n      const kj::HttpHeaderTable& headerTable,\n      kj::HttpHeaderId controlHeaderId) const;\n\n  // Accepts a connection to the V8 inspector and handles requests until the client disconnects.\n  //\n  // This overload will dispatch all inspector messages on the `kj::Executor` passed in via\n  // `isolateThreadExecutor`. For CPU profiling to work as expected, this `kj::Executor` must be\n  // associated with the same thread which executes the Worker's JavaScript.\n  kj::Promise<void> attachInspector(kj::Own<const kj::Executor> isolateThreadExecutor,\n      kj::Timer& timer,\n      kj::Duration timerOffset,\n      kj::WebSocket& webSocket) const;\n\n  // Log a warning to the inspector if attached, and log an INFO severity message.\n  void logWarning(kj::StringPtr description, Worker::Lock& lock);\n\n  // logWarningOnce() only logs the warning if it has not already been logged for this\n  // worker instance.\n  void logWarningOnce(kj::StringPtr description, Worker::Lock& lock);\n\n  // Log an ERROR severity message, if it has not already been logged for this worker instance.\n  void logErrorOnce(kj::StringPtr description);\n\n  // Wrap an HttpClient to report subrequests to inspector.\n  kj::Own<WorkerInterface> wrapSubrequestClient(kj::Own<WorkerInterface> client,\n      kj::HttpHeaderId contentEncodingHeaderId,\n      RequestObserver& requestMetrics) const;\n\n  inline kj::Maybe<kj::StringPtr> getFeatureFlagsForFl() const {\n    return featureFlagsForFl;\n  }\n\n  // Called after each completed request. Does not require a lock.\n  void completedRequest() const;\n\n  // See Worker::takeAsyncLock().\n  kj::Promise<AsyncLock> takeAsyncLockWithoutRequest(SpanParent parentSpan) const;\n\n  // See Worker::takeAsyncLock().\n  kj::Promise<AsyncLock> takeAsyncLock(RequestObserver&) const;\n\n  bool isInspectorEnabled() const;\n\n  // Get the process stdio prefixed setting from logging options\n  inline kj::StringPtr getStdoutPrefix() const {\n    return loggingOptions.stdoutPrefix;\n  }\n\n  inline kj::StringPtr getStderrPrefix() const {\n    return loggingOptions.stderrPrefix;\n  }\n\n  // Represents a weak reference back to the isolate that code within the isolate can use as an\n  // indirect pointer when they want to be able to race destruction safely. A caller wishing to\n  // use a weak reference to the isolate should acquire a strong reference to weakIsolateRef.\n  // That will ensure it's always safe to invoke `tryAddStrongRef` to try to obtain a strong\n  // reference of the underlying isolate. This is because the Isolate's destructor will explicitly\n  // clear the underlying pointer that would be dereferenced by `tryAddStrongRef`. This means that\n  // after the refcount reaches 0, `tryAddStrongRef` is always still safe to invoke even if the\n  // underlying Isolate memory has been deallocated (provided ownership of the weak isolate\n  // reference is retained).\n  using WeakIsolateRef = AtomicWeakRef<Isolate>;\n\n  kj::Own<const WeakIsolateRef> getWeakRef() const;\n\n  // Get a UUID for this isolate.\n  kj::StringPtr getUuid() const;\n\n private:\n  kj::Promise<AsyncLock> takeAsyncLockImpl(\n      kj::Maybe<kj::Own<IsolateObserver::LockTiming>> lockTiming) const;\n\n  kj::Own<IsolateObserver> metrics;\n  // NOTE: destruction order is important here. The teardown guard should be destroyed after the\n  // `api` since API destruction may perform some aspects of isolate teardown.\n  TeardownFinishedGuard<IsolateObserver&> teardownGuard{*metrics};\n\n  kj::String id;\n  kj::Own<IsolateLimitEnforcer> limitEnforcer;\n  kj::MutexGuarded<kj::Maybe<kj::Function<void(void)>>> cpuLimitNearlyExceededCallback;\n  kj::Own<Api> api;\n  LoggingOptions loggingOptions;\n\n  // If non-null, a serialized JSON object with a single \"flags\" property, which is a list of\n  // compatibility enable-flags that are relevant to FL.\n  kj::Maybe<kj::String> featureFlagsForFl;\n\n  struct Impl;\n  kj::Own<Impl> impl;\n\n  // This is a weak reference that can be used to safely (in a multi-threaded context) try to\n  // acquire a strong reference to the isolate. To do that add a strong reference to the\n  // weakIsolateRef while it's safe and then call tryAddStrongRef which will return a strong\n  // reference if the object isn't being destroyed (it's safe to call this even if the destructor\n  // has already run).\n  kj::Own<const WeakIsolateRef> weakIsolateRef;\n\n  class InspectorChannelImpl;\n  kj::Maybe<InspectorChannelImpl&> currentInspectorSession;\n\n  struct AsyncWaiterList {\n    kj::Maybe<AsyncWaiter&> head = kj::none;\n    kj::Maybe<AsyncWaiter&>* tail = &head;\n\n    ~AsyncWaiterList() noexcept;\n  };\n\n  // Mutex-guarded linked list of threads waiting for an async lock on this worker. The lock\n  // protects the `AsyncWaiterList` as well as the next/prev pointers in each `AsyncWaiter` that\n  // is currently in the list.\n  kj::MutexGuarded<AsyncWaiterList> asyncWaiters;\n  // TODO(perf): Use a lock-free list? Tricky to get right. `asyncWaiters` should only be locked\n  //   briefly so there's probably not that much to gain.\n\n  friend class Worker::AsyncLock;\n\n  void disconnectInspector();\n\n  // Log a message as if with console.{log,warn,error,etc}. `type` must be one of the cdp::LogType\n  // enum, which unfortunately we cannot forward-declare, ugh.\n  void logMessage(jsg::Lock& js, uint16_t type, kj::StringPtr description);\n\n  class SubrequestClient;\n  class ResponseStreamWrapper;\n  class LimitedBodyWrapper;\n\n  size_t nextRequestId = 0;\n  kj::Own<jsg::AsyncContextFrame::StorageKey> traceAsyncContextKey;\n\n  friend class Worker;\n};\n\n// The \"API isolate\" is a wrapper around JSG which determines which APIs are available. This is\n// an abstract interface which can be customized to make the runtime support a different set of\n// APIs. All JSG wrapping/unwrapping is encapsulated within this.\n//\n// In contrast, the rest of the classes in `worker.h` are concerned more with lifecycle\n// management.\nclass Worker::Api {\n public:\n  // Get the current `Api` or throw if we're not currently executing JavaScript.\n  static const Api& current();\n  // TODO(cleanup): This is a hack thrown in quickly because IoContext::current() doesn't work in\n  //   the global scope (when no request is running). We need a better design here.\n\n  // Like `current()`, but returns `kj::none` if there is no current Api instance.\n  static kj::Maybe<const Api&> tryCurrent();\n\n  // Take a lock on the isolate.\n  virtual kj::Own<jsg::Lock> lock(jsg::V8StackScope& stackScope) const = 0;\n  // TODO(cleanup): Change all locking to a synchronous callback style rather than RAII style, so\n  //   that this doesn't have to allocate and so it's not possible to hold a lock while returning\n  //   to the event loop.\n\n  // Get the FeatureFlags this isolate is configured with. Returns a Reader that is owned by the\n  // Api.\n  virtual CompatibilityFlags::Reader getFeatureFlags() const = 0;\n\n  struct NewContextOptions {\n    // If the worker is using the new module registry system, this is the registry to\n    // install on the newly created context. If null, the old system is assumed.\n    kj::Maybe<const workerd::jsg::modules::ModuleRegistry&> newModuleRegistry;\n    kj::Maybe<const capnp::SchemaLoader&> schemaLoader;\n  };\n\n  // Create the context (global scope) object.\n  virtual jsg::JsContext<api::ServiceWorkerGlobalScope> newContext(\n      jsg::Lock& lock, NewContextOptions options = {}) const = 0;\n\n  virtual void compileModules(jsg::Lock& lock,\n      const Script::ModulesSource& source,\n      const Worker::Isolate& isolate,\n      kj::Maybe<kj::Own<api::pyodide::ArtifactBundler_State>> artifacts,\n      SpanParent parentSpan) const = 0;\n\n  virtual kj::Array<Worker::Script::CompiledGlobal> compileServiceWorkerGlobals(jsg::Lock& lock,\n      const Script::ScriptSource& source,\n      const Worker::Isolate& isolate) const = 0;\n\n  // Given a module's export namespace, return all the top-level exports.\n  virtual jsg::Dict<NamedExport> unwrapExports(\n      jsg::Lock& lock, v8::Local<v8::Value> moduleNamespace) const = 0;\n\n  virtual NamedExport unwrapExport(jsg::Lock& lock, v8::Local<v8::Value> exportVal) const = 0;\n\n  // Get the constructors for classes from which entrypoint classes may inherit.\n  //\n  // This can be used to check which class a particular entrypoint inherits from, by following\n  // the prototype chain from the entrypoint class's constructor.\n  virtual EntrypointClasses getEntrypointClasses(jsg::Lock& lock) const = 0;\n\n  // Convenience struct for accessing typical Error properties.\n  struct ErrorInterface {\n    jsg::Optional<kj::String> name;\n    jsg::Optional<kj::String> message;\n    jsg::Optional<kj::String> stack;\n    JSG_STRUCT(name, message, stack);\n  };\n  virtual const jsg::TypeHandler<ErrorInterface>& getErrorInterfaceTypeHandler(\n      jsg::Lock& lock) const = 0;\n  virtual const jsg::TypeHandler<api::QueueExportedHandler>& getQueueTypeHandler(\n      jsg::Lock& lock) const = 0;\n\n  // Look up crypto algorithms by case-insensitive name. This can be used to extend the set of\n  // WebCrypto algorithms supported.\n  virtual kj::Maybe<const api::CryptoAlgorithm&> getCryptoAlgorithm(kj::StringPtr name) const {\n    return kj::none;\n  }\n\n  // Apply JSG wrapping to the given ExecutionContext. This is needed in particular by the RPC\n  // server-side implementation, when invoking a top-level RPC method that takes env and ctx as\n  // params.\n  virtual jsg::JsObject wrapExecutionContext(\n      jsg::Lock& lock, jsg::Ref<api::ExecutionContext> ref) const = 0;\n\n  virtual const jsg::IsolateObserver& getObserver() const = 0;\n  virtual void setIsolateObserver(IsolateObserver&) = 0;\n\n  // Set the module fallback service callback, if any.\n  using ModuleFallbackCallback = kj::Maybe<kj::OneOf<kj::String, jsg::ModuleRegistry::ModuleInfo>>(\n      jsg::Lock& js,\n      kj::StringPtr,\n      kj::Maybe<kj::String>,\n      jsg::CompilationObserver&,\n      jsg::ModuleRegistry::ResolveMethod,\n      kj::Maybe<kj::StringPtr>);\n  virtual void setModuleFallbackCallback(kj::Function<ModuleFallbackCallback>&& callback) const {\n    // By default does nothing.\n  }\n};\n\n// A Worker may bounce between threads as it handles multiple requests, but can only actually\n// execute on one thread at a time. Each thread must therefore lock the Worker while executing\n// code.\n//\n// A Worker::Lock MUST be allocated on the stack.\nclass Worker::Lock {\n public:\n  // Worker locks should normally be taken asynchronously. The TakeSynchronously type can be used\n  // when a synchronous lock is unavoidable. The purpose of this type is to make it easy to find\n  // all the places where we take synchronous locks.\n  class TakeSynchronously {\n   public:\n    // We don't provide a default constructor so that call sites need to think about whether they\n    // have a Request& available to pass in.\n    explicit TakeSynchronously(kj::Maybe<RequestObserver&> request);\n\n    kj::Maybe<RequestObserver&> getRequest();\n\n   private:\n    // Non-null if this lock is being taken on behalf of a request.\n    RequestObserver* request = nullptr;\n    // HACK: The OneOf<TakeSynchronously, ...> in Worker::LockType doesn't like that\n    //   Maybe<RequestObserver&> forces us to have a mutable copy constructor. I couldn't figure\n    //   out how to work around it, so here we are with a raw pointer. :/\n  };\n\n  KJ_DISALLOW_COPY_AND_MOVE(Lock);\n  KJ_DISALLOW_AS_COROUTINE_PARAM;\n  ~Lock() noexcept(false);\n\n  void requireNoPermanentException();\n\n  Worker& getWorker() {\n    return worker;\n  }\n\n  operator jsg::Lock&();\n\n  v8::Isolate* getIsolate();\n  v8::Local<v8::Context> getContext();\n\n  bool isInspectorEnabled();\n  void logWarning(kj::StringPtr description);\n  void logWarningOnce(kj::StringPtr description);\n\n  void logErrorOnce(kj::StringPtr description);\n\n  // Logs an exception to the debug console or trace, if active.\n  void logUncaughtException(kj::StringPtr description);\n\n  // Logs an exception to the debug console or trace, if active.\n  //\n  // If the caller already has a copy of the exception stack, it can pass this in as an\n  // optimization. This value will be passed along to the trace handler, if there is one, rather\n  // than querying the property from the exception itself. This is also useful in the case that\n  // the exception itself is not the original and the stack is missing.\n  void logUncaughtException(UncaughtExceptionSource source,\n      const jsg::JsValue& exception,\n      const jsg::JsMessage& message = jsg::JsMessage());\n\n  // Version that takes a kj::Exception. If it has a serialized JS error attached as a detail, that\n  // error may be extracted and used.\n  void logUncaughtException(UncaughtExceptionSource source, kj::Exception&& exception);\n\n  void reportPromiseRejectEvent(v8::PromiseRejectMessage& message);\n\n  // Checks for problems with the registered event handlers (such as that there are none) and\n  // reports them to the error reporter.\n  void validateHandlers(ValidationErrorReporter& errorReporter);\n\n  // Get the ExportedHandler exported under the given name. `entrypointName` may be null to get the\n  // default handler. Returns null if this is not a modules-syntax worker (but `entrypointName`\n  // must be null in that case).\n  //\n  // `versionInfo` is used to populate `ctx.version` when enabled.\n  // `props` is the value to place in `ctx.props`.\n  //\n  // If running in an actor, the name and props are ignored and the entrypoint originally used to\n  // construct the actor is returned.\n  kj::Maybe<kj::Own<api::ExportedHandler>> getExportedHandler(\n      kj::Maybe<kj::StringPtr> entrypointName,\n      kj::Maybe<VersionInfo> versionInfo,\n      Frankenvalue props,\n      kj::Maybe<Worker::Actor&> actor);\n\n  // Get the C++ object representing the global scope.\n  api::ServiceWorkerGlobalScope& getGlobalScope();\n\n  // Get the timeout ID generator from this worker's ServiceWorkerGlobalScope.\n  TimeoutId::Generator& getTimeoutIdGenerator();\n\n  // Get the opaque storage key to use for recording trace information in async contexts.\n  jsg::AsyncContextFrame::StorageKey& getTraceAsyncContextKey();\n\n private:\n  explicit Lock(const Worker& worker, LockType lockType, jsg::V8StackScope&);\n  struct Impl;\n\n  Worker& worker;\n  kj::Own<Impl> impl;\n\n  friend class Worker;\n};\n\n// Can be initialized either from an `AsyncLock` or a `TakeSynchronously`, to indicate whether an\n// async lock is held and help us grep for places in the code that do not support async locks.\nclass Worker::LockType {\n public:\n  LockType(Lock::TakeSynchronously origin): origin(origin) {}\n  LockType(AsyncLock& origin): origin(&origin) {}\n\n private:\n  kj::OneOf<Lock::TakeSynchronously, AsyncLock*> origin;\n  friend class Worker::Isolate;\n};\n\n// The func must be a callback with the signature: T (jsg::Lock&), where T is any type.\nauto Worker::runInLockScope(LockType lockType, auto func) const {\n  return jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) -> auto {\n    Worker::Lock lock(*this, lockType, stackScope);\n    return func(lock);\n  });\n}\n\n// Represents the thread's ownership of an isolate's asynchronous lock. Call `takeAsyncLock()`\n// on a `Worker` or `Worker::Isolate` to obtain this. Pass it to the constructor of\n// `Worker::Lock` (as the `lockType`) in order to indicate that the calling thread has taken\n// the async lock first.\n//\n// You must never store an `AsyncLock` long-term. Use it in a continuation and then discard it.\n// To put it another way: An `AsyncLock` instance must never outlive an `evalLast()`.\nclass Worker::AsyncLock {\n public:\n  // Waits until the thread has no async locks, is not waiting on any locks, and has finished all\n  // pending events (a la `kj::evalLast()`).\n  static kj::Promise<void> whenThreadIdle();\n\n private:\n  kj::Own<AsyncWaiter> waiter;\n  kj::Maybe<kj::Own<IsolateObserver::LockTiming>> lockTiming;\n\n  AsyncLock(kj::Own<AsyncWaiter> waiter, kj::Maybe<kj::Own<IsolateObserver::LockTiming>> lockTiming)\n      : waiter(kj::mv(waiter)),\n        lockTiming(kj::mv(lockTiming)) {}\n\n  friend class Worker::Isolate;\n  friend class Worker::AsyncWaiter;\n};\n\n// Represents actor state within a Worker instance. This object tracks the JavaScript heap\n// objects backing `event.actorState`. Multiple `Actor`s can be created within a single `Worker`.\nclass Worker::Actor final: public kj::Refcounted {\n public:\n  // Callback which constructs the `ActorCacheInterface` instance (if any) for the Actor. This\n  // can be used to customize the storage implementation. This will be called synchronously in\n  // the constructor.\n  using MakeActorCacheFunc =\n      kj::Function<kj::Maybe<kj::Own<ActorCacheInterface>>(const ActorCache::SharedLru& sharedLru,\n          OutputGate& outputGate,\n          ActorCache::Hooks& hooks,\n          SqliteObserver& sqliteObserver)>;\n\n  // Callback which constructs the `DurableObjectStorage` instance for an actor. This can be used\n  // to customize the JavaScript API.\n  using MakeStorageFunc = kj::Function<jsg::Ref<api::DurableObjectStorage>(\n      jsg::Lock& js, const Api& api, ActorCacheInterface& actorCache)>;\n  // TODO(cleanup): Can we refactor the (internal-codebase) user of this so that it doesn't need\n  //   to customize the JS API but only the underlying ActorCacheInterface?\n\n  using Id = kj::OneOf<kj::Own<ActorIdFactory::ActorId>, kj::String>;\n  static bool idsEqual(const Id& a, const Id& b);\n\n  // Class that allows sending requests to this actor, recreating it as needed. It is safe to hold\n  // onto this for longer than a Worker::Actor is alive.\n  class Loopback {\n   public:\n    // Send a request to this actor, potentially re-creating it if it is not currently active.\n    // The returned kj::Own<WorkerInterface> may be held longer than Loopback, and is assumed\n    // to keep the Worker::Actor alive as well.\n    virtual kj::Own<WorkerInterface> getWorker(IoChannelFactory::SubrequestMetadata metadata) = 0;\n\n    virtual kj::Own<Loopback> addRef() = 0;\n  };\n\n  // The HibernationManager class manages HibernatableWebSockets created by an actor.\n  // The manager handles accepting new WebSockets, retrieving existing WebSockets by tag, and\n  // removing WebSockets from its collection when they disconnect.\n  class HibernationManager: public kj::Refcounted {\n   public:\n    virtual void acceptWebSocket(jsg::Ref<api::WebSocket> ws, kj::ArrayPtr<kj::String> tags) = 0;\n    virtual kj::Vector<jsg::Ref<api::WebSocket>> getWebSockets(\n        jsg::Lock& js, kj::Maybe<kj::StringPtr> tag) = 0;\n    virtual void hibernateWebSockets(Worker::Lock& lock) = 0;\n    virtual void setWebSocketAutoResponse(\n        kj::Maybe<kj::StringPtr> request, kj::Maybe<kj::StringPtr> response) = 0;\n    virtual kj::Maybe<jsg::Ref<api::WebSocketRequestResponsePair>> getWebSocketAutoResponse(\n        jsg::Lock& js) = 0;\n    virtual void setTimerChannel(TimerChannel& timerChannel) = 0;\n    virtual kj::Own<HibernationManager> addRef() = 0;\n    virtual void setEventTimeout(kj::Maybe<uint32_t> timeoutMs) = 0;\n    virtual kj::Maybe<uint32_t> getEventTimeout() = 0;\n  };\n\n  class FacetManager {\n   public:\n    // Information needed to start a facet.\n    struct StartInfo {\n      // The actor class, from a DurableObjectClass binding.\n      //\n      // WARNING: The object passed here MUST be directly from IoChannelFactory::getActorClass(),\n      //   as the FacetManager implementation is allowed to assume it can downcast to whatever\n      //   type the IoChannelFactory produces.\n      kj::Own<IoChannelFactory::ActorClassChannel> actorClass;\n\n      // ctx.id for the child object.\n      Worker::Actor::Id id;\n    };\n\n    // Returns the nesting depth of this facet. Root = 0, direct child of root = 1, etc.\n    virtual uint getDepth() const = 0;\n\n    // These methods are C++ equivalents of the JavaScript ctx.facets API.\n    virtual kj::Own<IoChannelFactory::ActorChannel> getFacet(\n        kj::StringPtr name, kj::Function<kj::Promise<StartInfo>()> getStartInfo) = 0;\n    virtual void abortFacet(kj::StringPtr name, kj::Exception reason) = 0;\n    virtual void deleteFacet(kj::StringPtr name) = 0;\n  };\n\n  // Create a new Actor hosted by this Worker. Note that this Actor object may only be manipulated\n  // from the thread that created it.\n  Actor(const Worker& worker,\n      kj::Maybe<RequestTracker&> tracker,\n      Id actorId,\n      bool hasTransient,\n      MakeActorCacheFunc makeActorCache,\n      kj::Maybe<kj::StringPtr> className,\n      Frankenvalue props,\n      MakeStorageFunc makeStorage,\n      kj::Own<Loopback> loopback,\n      TimerChannel& timerChannel,\n      kj::Own<ActorObserver> metrics,\n      kj::Maybe<kj::Own<HibernationManager>> manager,\n      kj::Maybe<uint16_t> hibernationEventType,\n      kj::Maybe<rpc::Container::Client> container = kj::none,\n      kj::Maybe<FacetManager&> facetManager = kj::none,\n      kj::Maybe<ActorVersion> version = kj::none);\n\n  ~Actor() noexcept(false);\n\n  // Call when starting any new request, to ensure that the actor object's constructor has run.\n  //\n  // This is used only for modules-syntax actors (which most are, since that's the only format we\n  // support publicly).\n  void ensureConstructed(IoContext&);\n\n  // Forces cancellation of all \"background work\" this actor is executing, i.e. work that is not\n  // happening on behalf of an active request. Note that this is not a part of the dtor because\n  // IoContext objects prolong the lifetime of their Actor.\n  //\n  // `reasonCode` is passed back to the WorkerObserver.\n  void shutdown(uint16_t reasonCode, kj::Maybe<const kj::Exception&> error = kj::none);\n\n  // Stops new work on behalf of the ActorCache. This does not cancel any ongoing flushes.\n  // TODO(soon) This should probably be folded into shutdown(). We'd need a piece that converts\n  // `error` to `reasonCode` in workerd to do this. There may also be opportunities to streamline\n  // interactions between `onAbort` and `onShutdown` promises.\n  void shutdownActorCache(kj::Maybe<const kj::Exception&> error);\n\n  // Get a promise that resolves when `shutdown()` has been called.\n  kj::Promise<void> onShutdown();\n\n  // Get a promise that rejects when this actor becomes broken in some way. See doc comments for\n  // WorkerRuntime.makeActor() in worker.capnp for a discussion of actor brokenness.\n  // Note that this doesn't cover every cause of actor brokenness -- some of them are fulfilled\n  // in worker-set or process-sandbox code, in particular code updates and exceeded memory.\n  // This method can only be called once.\n  kj::Promise<void> onBroken();\n\n  const Id& getId();\n  Id cloneId();\n  static Id cloneId(Id& id);\n  kj::Maybe<jsg::JsRef<jsg::JsValue>> getTransient(Worker::Lock& lock);\n  kj::Maybe<ActorCacheInterface&> getPersistent();\n  kj::Own<Loopback> getLoopback();\n\n  // Make the storage object for use in Service Workers syntax. This should not be used for\n  // modules-syntax workers. (Note that Service-Workers-syntax actors are not supported publicly.)\n  kj::Maybe<jsg::Ref<api::DurableObjectStorage>> makeStorageForSwSyntax(Worker::Lock& lock);\n\n  ActorObserver& getMetrics();\n\n  InputGate& getInputGate();\n  OutputGate& getOutputGate();\n\n  // Get the IoContext which should be used for all activity in this Actor. Returns null if\n  // setIoContext() hasn't been called yet.\n  kj::Maybe<IoContext&> getIoContext();\n\n  // Set the IoContext for this actor. This is called once, when starting the first request\n  // to the actor.\n  void setIoContext(kj::Own<IoContext> context);\n  // TODO(cleanup): Could we make it so the Worker::Actor can create the IoContext directly,\n  //   rather than have WorkerEntrypoint create it on the first request? We'd have to plumb through\n  //   some more information to the place where `Actor` is created, which might be uglier than it's\n  //   worth.\n\n  // Get the `ctx` object for this actor.\n  jsg::JsObject getCtx(jsg::Lock& js);\n\n  // Get the `env` object for this actor.\n  jsg::JsValue getEnv(jsg::Lock& js);\n\n  // Get the HibernationManager which should be used for all activity in this Actor. Returns null if\n  // setHibernationManager() hasn't been called yet.\n  kj::Maybe<HibernationManager&> getHibernationManager();\n\n  // Set the HibernationManager for this actor. This is called once, on the first call to\n  // `acceptWebSocket`.\n  void setHibernationManager(kj::Own<HibernationManager> manager);\n\n  // Gets the event type ID of the hibernation event, which is defined outside of workerd.\n  // Only needs to be called when allocating a HibernationManager!\n  kj::Maybe<uint16_t> getHibernationEventType();\n\n  inline const Worker& getWorker() {\n    return *worker;\n  }\n\n  void assertCanSetAlarm();\n\n  // If there is a scheduled or running alarm with the given `scheduledTime`, return a promise to\n  // its result. This allows use to de-dupe multiple requests to a single `IoContext::run()`.\n  kj::Maybe<kj::Promise<WorkerInterface::AlarmResult>> getAlarm(kj::Date scheduledTime);\n\n  // Wait for `Date.now()` to be greater than or equal to `scheduledTime`. If the promise resolves\n  // to an `AlarmFulfiller`, then the caller is responsible for invoking `fulfill()`, `reject()`, or\n  // `cancel()`. Otherwise, the scheduled alarm was overridden by another call to `scheduleAlarm()`\n  // and thus was cancelled. Note that callers likely want to invoke `getAlarm()` first to see if\n  // there is an existing alarm at `scheduledTime` for which they want to wait (instead of\n  // cancelling it).\n  kj::Promise<WorkerInterface::ScheduleAlarmResult> scheduleAlarm(kj::Date scheduledTime);\n\n  kj::Own<Worker::Actor> addRef();\n\n private:\n  kj::Promise<WorkerInterface::ScheduleAlarmResult> handleAlarm(kj::Date scheduledTime);\n\n  kj::Own<const Worker> worker;\n  kj::Maybe<kj::Own<RequestTracker>> tracker;\n  struct Impl;\n  kj::Own<Impl> impl;\n\n  kj::Maybe<api::ExportedHandler&> getHandler();\n  friend class Worker;\n\n  kj::Promise<void> ensureConstructedImpl(IoContext&, ActorClassInfo& info);\n};\n\nWD_STRONG_BOOL(PopulateVersionInfoMetadata);\n\n// Version information associated with a worker. These are made available through `ctx.version`.\n// This is also available through the preferred `Worker::VersionInfo` alias. `Worker_VersionInfo`\n// exists to allow for forward declaration in a couple of niche places.\nstruct Worker_VersionInfo {\n  kj::String id;\n  kj::Maybe<kj::String> cohort;\n  kj::Maybe<kj::String> key;\n  kj::Maybe<kj::String> versionOverride;\n\n  Worker_VersionInfo clone() const {\n    return {\n      .id = kj::str(id),\n      .cohort = cohort.map([](const kj::String& s) { return kj::str(s); }),\n      .key = key.map([](const kj::String& s) { return kj::str(s); }),\n      .versionOverride = versionOverride.map([](const kj::String& s) { return kj::str(s); }),\n    };\n  }\n\n  jsg::JsValue toJs(jsg::Lock& js, PopulateVersionInfoMetadata populateVersionInfoMetadata) const {\n    auto version = js.obj();\n    if (populateVersionInfoMetadata) {\n      auto metadata = js.obj();\n      metadata.set(js, \"id\"_kj, js.str(id));\n      version.set(js, \"metadata\"_kj, metadata);\n    }\n    KJ_IF_SOME(someCohort, cohort) {\n      version.set(js, \"cohort\"_kj, js.str(someCohort));\n    }\n    KJ_IF_SOME(someKey, key) {\n      version.set(js, \"key\"_kj, js.str(someKey));\n    }\n    KJ_IF_SOME(someVersionOverride, versionOverride) {\n      version.set(js, \"override\"_kj, js.str(someVersionOverride));\n    }\n    version.recursivelyFreeze(js);\n    return version;\n  }\n};\n\n// =======================================================================================\n// inline implementation details\n\ninline const Worker::Isolate& Worker::getIsolate() const {\n  return *script->isolate;\n}\n\nKJ_DECLARE_NON_POLYMORPHIC(Worker::AsyncWaiter);\n\n// An implementation of Worker::ValidationErrorReporter that collects errors into\n// a kj::Vector<kj::String>.\nstruct SimpleWorkerErrorReporter final: public Worker::ValidationErrorReporter {\n  void addError(kj::String error) override {\n    errors.add(kj::mv(error));\n  }\n  void addEntrypoint(kj::Maybe<kj::StringPtr> exportName, kj::Array<kj::String> methods) override {\n    KJ_UNREACHABLE;\n  }\n  void addActorClass(kj::StringPtr exportName) override {\n    KJ_UNREACHABLE;\n  }\n\n  void addWorkflowClass(kj::StringPtr exportName, kj::Array<kj::String> methods) override {\n    KJ_UNREACHABLE;\n  }\n\n  SimpleWorkerErrorReporter() = default;\n  KJ_DISALLOW_COPY_AND_MOVE(SimpleWorkerErrorReporter);\n  kj::Vector<kj::String> errors;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/jsg/AGENTS.md",
    "content": "# JSG (JavaScript Glue)\n\nSee `README.md` for terse reference (type mappings, macro catalog, error catalog).\nSee `docs/jsg.md` for narrative tutorial.\n\n## OVERVIEW\n\nMacro-driven C++/V8 binding layer: declares C++ types as JS-visible resources/structs with automatic type conversion.\n\n## KEY FILES\n\n| File             | Purpose                                                                                                                     |\n| ---------------- | --------------------------------------------------------------------------------------------------------------------------- |\n| `jsg.h`          | Core header (3115 lines): all macros (`JSG_RESOURCE_TYPE`, `JSG_METHOD`, etc.), type mappings, `Lock`, `V8Ref`, `GcVisitor` |\n| `resource.h`     | Template metaprogramming: V8 callback generation, `FunctionCallbackInfo` dispatch, prototype/constructor wiring             |\n| `struct.h`       | `JSG_STRUCT` value-type mapping: deep-copies C++ structs to/from JS objects                                                 |\n| `wrappable.h`    | GC integration: `Wrappable` base class, CppGC visitor hooks, ref marking, weak pointers                                     |\n| `promise.h`      | `jsg::Promise<T>` wrapping KJ promises ↔ JS promises; resolver pairs, coroutine integration                                |\n| `modules.h`      | `ModuleRegistry`: ESM/CJS module resolution, evaluation, top-level await handling                                           |\n| `modules-new.h`  | Replacement module system (new design)                                                                                      |\n| `setup.h`        | `V8System`, `IsolateBase`, `JsgConfig`; process-level V8 init; `JSG_DECLARE_ISOLATE_TYPE`                                   |\n| `function.h`     | `jsg::Function<Sig>` wrapping C++ callables ↔ JS functions                                                                 |\n| `memory.h`       | `MemoryTracker`, `JSG_MEMORY_INFO` macro; heap snapshot support                                                             |\n| `rtti.capnp`     | Cap'n Proto schema for type introspection; consumed by `types/` for TS generation                                           |\n| `rtti.h`         | C++ RTTI builder: walks JSG type graph → `rtti.capnp` structures                                                            |\n| `jsvalue.h`      | `JsValue`, `JsObject`, `JsString`, etc. — typed wrappers over `v8::Value`                                                   |\n| `type-wrapper.h` | `TypeWrapper` template: compile-time dispatch for C++ ↔ V8 conversions                                                     |\n| `meta.h`         | Argument unwrapping, `ArgumentContext`, parameter pack metaprogramming                                                      |\n| `fast-api.h`     | V8 Fast API call optimizations                                                                                              |\n| `ser.h`          | Structured clone: `Serializer`/`Deserializer`                                                                               |\n| `web-idl.h`      | Web IDL types: `NonCoercible<T>`, `Sequence`, etc.                                                                          |\n| `observer.h`     | `IsolateObserver`, `CompilationObserver` — hooks for metrics/tracing                                                        |\n\n## BINDING PATTERN\n\n```cpp\nclass MyType: public jsg::Object {\n  static jsg::Ref<MyType> constructor(kj::String s);\n  int getValue();\n  void doThing(jsg::Lock& js, int n);\n\n  JSG_RESOURCE_TYPE(MyType) {        // or (MyType, CompatibilityFlags::Reader flags)\n    JSG_METHOD(doThing);\n    JSG_PROTOTYPE_PROPERTY(value, getValue, setvalue);\n    JSG_NESTED_TYPE(SubType);\n    JSG_STATIC_METHOD(create);\n  }\n\n  void visitForGc(jsg::GcVisitor& visitor) {\n    visitor.visit(ref, v8ref);       // MUST trace all Ref<T>/V8Ref<T>/JsRef<T>\n  }\n\n  jsg::Ref<Other> ref;\n  jsg::V8Ref<v8::Object> v8ref;\n};\n```\n\n- Allocate resource objects: `js.alloc<MyType>(args...)`\n- Value types: `JSG_STRUCT(field1, field2)` — auto-converted by value\n- `jsg::Lock&` is the JS execution context; thread via method params\n- `TypeHandler<T>&` as trailing param gives manual conversion access\n- Compat flags param on `JSG_RESOURCE_TYPE` gates members conditionally\n\n## ANTI-PATTERNS\n\n- **NEVER** opaque-wrap `V8Ref<T>` — use handle directly\n- **NEVER** use v8::Context embedder data slot 0 (`ContextPointerSlot::RESERVED`)\n- **NEVER** hold traced handles (`Ref<T>`/`V8Ref<T>`) without marking in `visitForGc` — causes GC corruption\n- **NEVER** use `FastOneByteString` in Fast API calls (GC corruption risk)\n- **NEVER** unwrap `Ref<Object>` — use `V8Ref<v8::Object>` instead\n- `JSG_CATCH` is NOT a true catch — cannot rethrow with `throw`\n- `NonCoercible<T>` runs counter to Web IDL best practices; avoid in new APIs\n- Rust JSG bindings: see `src/rust/jsg/` and `src/rust/jsg-macros/`\n\n## INVARIANTS\n\nThese rules MUST be followed when writing or modifying JSG code:\n\n1. **MUST implement `visitForGc()`** on any Resource Type holding `Ref<T>`, `V8Ref<T>`,\n   `JsRef<T>`, `Function<T>`, `Promise<T>`, `Promise<T>::Resolver`, `BufferSource`, or\n   `Name` — see `README.md` §GC-Visitable Types for the complete list\n2. **MUST visit ALL GC-visitable fields** — missing one causes GC corruption\n3. **MUST NOT store `v8::Local<T>` or `JsValue` types as class members** — use `V8Ref<T>`\n   or `JsRef<T>` for persistence\n4. **MUST NOT put `v8::Global<T>` or `v8::Local<T>` in `JSG_STRUCT` fields** — use\n   `jsg::V8Ref<T>` or `jsg::JsRef<T>`\n5. **MUST NOT put `v8::Global<T>` or `v8::Local<T>` in `kj::Promise`** — compile-time deleted\n6. **MUST NOT pass `jsg::Lock` into KJ promise coroutines**\n7. **`JSG_SERIALIZABLE` MUST appear AFTER `JSG_RESOURCE_TYPE` block**, not inside it\n8. **Serialization tag enum values MUST NOT change** once data has been serialized\n9. **`Ref<T>` ownership MUST flow owner→owned**; backwards refs use raw `T&` or\n   `kj::Maybe<T&>`\n10. **Prefer `JSG_PROTOTYPE_PROPERTY`** over `JSG_INSTANCE_PROPERTY` unless there's a\n    specific reason — instance properties break GC optimization\n\n## CODE REVIEW RULE\n\nWhen reviewing changes to JSG code, check whether the change requires updates to\nany of these documentation files:\n\n- **`README.md`** — if the change adds/modifies type mappings, macros, error types,\n  serialization patterns, or reference tables\n- **`docs/jsg.md`** — if the change affects tutorial content, adds new patterns,\n  or changes API usage examples\n- **This file (`AGENTS.md`)** — if the change adds/removes files, changes the\n  architecture summary, or introduces new invariants\n\nFlag any needed doc updates in the review. Do not let behavioral or architectural\nchanges land without corresponding documentation updates.\n"
  },
  {
    "path": "src/workerd/jsg/BUILD.bazel",
    "content": "load(\"@aspect_rules_js//npm:defs.bzl\", \"npm_package\")\nload(\"//:build/js_capnp_library.bzl\", \"js_capnp_library\")\nload(\"//:build/kj_test.bzl\", \"kj_test\")\nload(\"//:build/wd_cc_capnp_library.bzl\", \"wd_cc_capnp_library\")\nload(\"//:build/wd_cc_library.bzl\", \"wd_cc_library\")\n\nexports_files([\"modules.capnp\"])\n\nwd_cc_library(\n    name = \"jsg\",\n    srcs = [],\n    hdrs = [\n        \"jsg-test.h\",\n        \"type-wrapper.h\",\n    ],\n    local_defines = [\"JSG_IMPLEMENTATION\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":exception\",\n        \":iterator\",\n        \":jsg-core\",\n        \":memory-tracker\",\n        \":url\",\n        \"//src/workerd/util\",\n        \"//src/workerd/util:sentry\",\n        \"//src/workerd/util:thread-scopes\",\n        \"@capnp-cpp//src/kj\",\n        \"@workerd-v8//:v8\",\n    ],\n)\n\nwd_cc_library(\n    name = \"iterator\",\n    srcs = [\n        \"iterator.c++\",\n    ],\n    hdrs = [\n        \"iterator.h\",\n        \"struct.h\",\n        \"value.h\",\n    ],\n    local_defines = [\"JSG_IMPLEMENTATION\"],\n    deps = [\n        \":exception\",\n        \":jsg-core\",\n        \":memory-tracker\",\n        \"//src/workerd/util\",\n        \"//src/workerd/util:sentry\",\n        \"//src/workerd/util:thread-scopes\",\n        \"@capnp-cpp//src/kj\",\n        \"@workerd-v8//:v8\",\n    ],\n)\n\nwd_cc_library(\n    name = \"script\",\n    srcs = [\"script.c++\"],\n    hdrs = [\"script.h\"],\n    local_defines = [\"JSG_IMPLEMENTATION\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":jsg-core\",\n        \"@capnp-cpp//src/kj\",\n        \"@workerd-v8//:v8\",\n    ],\n)\n\n# Subset of JSG that includes core JSG interfaces, but does not depend on type-wrapper API.\n# Factoring this out reduces the need for circular includes and will make future refactoring of JSG\n# much easier.\n# This is only visible in this package for now – to avoid extensive include changes in code using\n# the JSG type wrapper, the main JSG target should be used instead for now.\n# Avoid adding dependencies or new files to this when possible.\nwd_cc_library(\n    name = \"jsg-core\",\n    srcs = [\n        \"async-context.c++\",\n        \"buffersource.c++\",\n        \"dom-exception.c++\",\n        \"jsg.c++\",\n        \"jsvalue.c++\",\n        \"modules.c++\",\n        \"modules-new.c++\",\n        \"promise.c++\",\n        \"resource.c++\",\n        \"ser.c++\",\n        \"setup.c++\",\n        \"util.c++\",\n        \"v8-platform-wrapper.c++\",\n        \"wrappable.c++\",\n    ],\n    hdrs = [\n        \"async-context.h\",\n        \"buffersource.h\",\n        \"dom-exception.h\",\n        \"fast-api.h\",\n        \"function.h\",\n        \"jsg.h\",\n        \"jsvalue.h\",\n        \"modules.h\",\n        \"modules-new.h\",\n        \"promise.h\",\n        \"resource.h\",\n        \"ser.h\",\n        \"setup.h\",\n        \"util.h\",\n        \"v8-platform-wrapper.h\",\n        \"web-idl.h\",\n        \"wrappable.h\",\n    ],\n    # Some JSG headers can't be compiled on their own\n    features = [\"-parse_headers\"],\n    implementation_deps = [\n        \"//src/workerd/util:entropy\",\n        \"@simdutf\",\n        \"@ssl\",\n    ],\n    local_defines = [\"JSG_IMPLEMENTATION\"],\n    tags = [\"no-clang-tidy\"],\n    visibility = [\n        \"//src/rust/jsg:__pkg__\",\n        \"//src/rust/jsg-test:__pkg__\",\n    ],\n    deps = [\n        \":exception\",\n        \":exception_metadata_capnp\",\n        \":macro-meta\",\n        \":memory-tracker\",\n        \":meta\",\n        \":modules_capnp\",\n        \":observer\",\n        \":url\",\n        \"//src/workerd/util\",  # Required for util/batch-queue.h\n        \"//src/workerd/util:autogate\",\n        \"//src/workerd/util:sentry\",\n        \"//src/workerd/util:thread-scopes\",\n        \"@capnp-cpp//src/kj\",\n        \"@workerd-v8//:v8\",\n    ],\n)\n\nwd_cc_library(\n    name = \"compile-cache\",\n    srcs = [\n        \"compile-cache.c++\",\n    ],\n    hdrs = [\n        \"compile-cache.h\",\n    ],\n    local_defines = [\"JSG_IMPLEMENTATION\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@capnp-cpp//src/kj\",\n        \"@workerd-v8//:v8\",\n    ],\n)\n\nwd_cc_library(\n    name = \"inspector\",\n    srcs = [\n        \"inspector.c++\",\n    ],\n    hdrs = [\n        \"inspector.h\",\n    ],\n    implementation_deps = [\n        \":jsg-core\",\n        \"@capnp-cpp//src/kj\",\n        \"@simdutf\",\n        \"@workerd-v8//:v8\",\n    ],\n    local_defines = [\"JSG_IMPLEMENTATION\"],\n    # Some JSG headers can't be compiled on their own\n    visibility = [\"//visibility:public\"],\n)\n\nwd_cc_library(\n    name = \"memory-tracker\",\n    srcs = [\"memory.c++\"],\n    hdrs = [\"memory.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@capnp-cpp//src/kj\",\n        \"@workerd-v8//:v8\",\n    ],\n)\n\nwd_cc_library(\n    name = \"meta\",\n    hdrs = [\"meta.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@capnp-cpp//src/kj\",\n        \"@workerd-v8//:v8\",\n    ],\n)\n\nwd_cc_library(\n    name = \"url\",\n    srcs = [\"url.c++\"],\n    hdrs = [\n        \"url.h\",\n    ],\n    implementation_deps = [\n        \"//src/workerd/util:strings\",\n        \"@ada-url\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":memory-tracker\",\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nwd_cc_library(\n    name = \"exception\",\n    srcs = [\"exception.c++\"],\n    hdrs = [\"exception.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nwd_cc_capnp_library(\n    name = \"rtti_capnp\",\n    srcs = [\"rtti.capnp\"],\n    visibility = [\"//visibility:public\"],\n)\n\nwd_cc_capnp_library(\n    name = \"exception_metadata_capnp\",\n    srcs = [\"exception-metadata.capnp\"],\n    visibility = [\"//visibility:public\"],\n)\n\njs_capnp_library(\n    name = \"rtti_capnp_js\",\n    srcs = [\"rtti.capnp\"],\n    outs = [\n        \"rtti.js\",\n        \"rtti.ts\",\n    ],\n    data = [\"//:node_modules/capnp-es\"],\n    target_compatible_with = select({\n        \"@platforms//os:windows\": [\"@platforms//:incompatible\"],\n        \"@//build/config:no_build\": [\"@platforms//:incompatible\"],\n        \"//conditions:default\": [],\n    }),\n    visibility = [\"//visibility:public\"],\n)\n\nnpm_package(\n    name = \"jsg_js\",\n    srcs = [\":rtti_capnp_js\"],\n    # Required to ensure source files are copied when running internal builds\n    # that depend on `workerd` as an external repository\n    include_external_repositories = [\"workerd\"],\n    publishable = False,\n    visibility = [\"//visibility:public\"],\n)\n\nwd_cc_library(\n    name = \"rtti\",\n    hdrs = [\"rtti.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":jsg\",\n        \":rtti_capnp\",\n    ],\n)\n\nwd_cc_library(\n    name = \"macro-meta\",\n    hdrs = [\"macro-meta.h\"],\n    visibility = [\"//visibility:public\"],\n)\n\nwd_cc_capnp_library(\n    name = \"modules_capnp\",\n    srcs = [\"modules.capnp\"],\n    visibility = [\"//visibility:public\"],\n)\n\nwd_cc_library(\n    name = \"observer\",\n    hdrs = [\"observer.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/util:strong-bool\",\n        \"@capnp-cpp//src/kj\",\n        \"@workerd-v8//:v8\",\n    ],\n)\n\n[kj_test(\n    src = f,\n    local_defines = [\"JSG_IMPLEMENTATION\"],\n    deps = [\":jsg\"],\n) for f in glob(\n    [\"*-test.c++\"],\n    exclude = [\n        # defined below\n        \"macro-meta-test.c++\",\n        \"resource-test.c++\",\n        \"rtti-test.c++\",\n        \"url-test.c++\",\n        \"multiple-typewrappers-test.c++\",\n    ],\n)]\n\n# Moved out as macro-meta-test does not depend on V8 or JSG proper, this makes the test much\n# smaller.\nkj_test(\n    src = \"macro-meta-test.c++\",\n    deps = [\n        \":macro-meta\",\n    ],\n)\n\nkj_test(\n    src = \"rtti-test.c++\",\n    deps = [\n        \":rtti\",\n        \":rtti_test_capnp\",\n        # TODO: move text encoding out\n        \"@capnp-cpp//src/capnp:capnpc\",\n    ],\n)\n\nkj_test(\n    src = \"url-test.c++\",\n    deps = [\n        \":url\",\n        \"@ssl\",\n    ],\n)\n\nwd_cc_capnp_library(\n    name = \"resource_test_capnp\",\n    srcs = [\"resource-test.capnp\"],\n    data = glob([\"resource-test-*.js\"]),\n    deps = [\":modules_capnp\"],\n)\n\nwd_cc_capnp_library(\n    name = \"rtti_test_capnp\",\n    srcs = [\"rtti-test.capnp\"],\n    deps = [\":modules_capnp\"],\n)\n\nkj_test(\n    src = \"resource-test.c++\",\n    deps = [\n        \":jsg\",\n        \":resource_test_capnp\",\n    ],\n)\n\nkj_test(\n    src = \"multiple-typewrappers-test.c++\",\n    deps = [\n        \":jsg\",\n        \"//src/workerd/io:compatibility-date_capnp\",\n    ],\n)\n"
  },
  {
    "path": "src/workerd/jsg/README.md",
    "content": "# JSG Binding Layer Reference\n\nTerse reference for the JSG (JavaScript Glue) C++/V8 binding layer. For narrative tutorial,\nsee [docs/jsg.md](../../../docs/jsg.md).\n\nFor file map and coding invariants, see [AGENTS.md](AGENTS.md).\n\n## Primitive Type Mapping\n\n| C++ Type         | v8 Type         | JavaScript Type | Notes                                      |\n| ---------------- | --------------- | --------------- | ------------------------------------------ |\n| `bool`           | `v8::Boolean`   | `boolean`       |                                            |\n| `double`         | `v8::Number`    | `number`        |                                            |\n| `int`            | `v8::Integer`   | `number`        |                                            |\n| `int8_t`         | `v8::Integer`   | `number`        |                                            |\n| `int16_t`        | `v8::Integer`   | `number`        |                                            |\n| `int32_t`        | `v8::Int32`     | `number`        |                                            |\n| `int64_t`        | `v8::BigInt`    | `bigint`        |                                            |\n| `uint`           | `v8::Integer`   | `number`        |                                            |\n| `uint8_t`        | `v8::Integer`   | `number`        |                                            |\n| `uint16_t`       | `v8::Integer`   | `number`        |                                            |\n| `uint32_t`       | `v8::Uint32`    | `number`        |                                            |\n| `uint64_t`       | `v8::BigInt`    | `bigint`        |                                            |\n| `kj::String`     | `v8::String`    | `string`        | Owned; JS `string` never → `kj::StringPtr` |\n| `kj::StringPtr`  | `v8::String`    | `string`        | View; C++→JS only                          |\n| `jsg::USVString` | `v8::String`    | `string`        | USV-encoded variant                        |\n| `kj::Date`       | `v8::Date`      | `Date`          |                                            |\n| `nullptr`        | `v8::Null`      | `null`          | Via `kj::Maybe<T>`                         |\n| `nullptr`        | `v8::Undefined` | `undefined`     | Via `jsg::Optional<T>`                     |\n\n## Nullable/Optional Semantics\n\n| Type                      | JS `undefined` → C++ | JS `null` → C++ | C++ empty → JS |\n| ------------------------- | -------------------- | --------------- | -------------- |\n| `kj::Maybe<T>`            | `kj::none`           | `kj::none`      | `null`         |\n| `jsg::Optional<T>`        | `kj::none`           | **throws**      | `undefined`    |\n| `jsg::LenientOptional<T>` | `kj::none`           | `kj::none`      | `undefined`    |\n\n## Composite Type Mapping\n\n| C++ Type                      | JS Type                     | Notes                                                       |\n| ----------------------------- | --------------------------- | ----------------------------------------------------------- |\n| `kj::Array<T>`                | `Array`                     | Input must be JS array                                      |\n| `kj::ArrayPtr<T>`             | `Array`                     | View                                                        |\n| `kj::Array<kj::byte>`         | `ArrayBuffer`/`TypedArray`  | Shared backing store, no copy                               |\n| `kj::HashSet<T>`              | `Set`                       | No custom struct keys                                       |\n| `jsg::Sequence<T>`            | `Symbol.iterator` → `Array` | Accepts any iterable; outputs array                         |\n| `jsg::Generator<T>`           | `Symbol.iterator`           | Synchronous per-item iteration                              |\n| `jsg::AsyncGenerator<T>`      | `Symbol.asyncIterator`      | Async per-item iteration                                    |\n| `jsg::Dict<T>`                | `Object`                    | Record type; string keys, uniform value type                |\n| `kj::OneOf<T...>`             | Union                       | Web IDL validated at compile time                           |\n| `jsg::Function<Ret(Args...)>` | `Function`                  | Bidirectional: JS↔C++ callable                             |\n| `jsg::Promise<T>`             | `Promise`                   | Full `.then()`/`.catch_()` API                              |\n| `jsg::Name`                   | `string` or `Symbol`        | Property name wrapper                                       |\n| `jsg::BufferSource`           | `ArrayBuffer`/`TypedArray`  | Type-preserving; supports detach                            |\n| `jsg::V8Ref<T>`               | Any V8 type                 | Persistent strong reference                                 |\n| `jsg::Value`                  | Any                         | Alias for `V8Ref<v8::Value>`                                |\n| `jsg::Ref<T>`                 | Resource wrapper            | Strong ref to JSG Resource Type                             |\n| `jsg::HashableV8Ref<T>`       | Any V8 type                 | `V8Ref` + `hashCode()`                                      |\n| `jsg::MemoizedIdentity<T>`    | Any                         | Preserves JS object identity across round-trips             |\n| `jsg::Identified<T>`          | Any                         | Captures JS object identity + unwrapped value               |\n| `jsg::NonCoercible<T>`        | Exact type                  | No automatic coercion; `T` = `kj::String`, `bool`, `double` |\n| `jsg::LenientOptional<T>`     | Optional                    | Silently ignores type errors → `undefined`                  |\n\n## JsValue Type Mapping\n\n| JsValue Type        | V8 Equivalent         | Description                                                         |\n| ------------------- | --------------------- | ------------------------------------------------------------------- |\n| `JsValue`           | `v8::Value`           | Generic; holds any JS value                                         |\n| `JsBoolean`         | `v8::Boolean`         | Boolean                                                             |\n| `JsNumber`          | `v8::Number`          | Double-precision number                                             |\n| `JsInt32`           | `v8::Int32`           | 32-bit signed integer                                               |\n| `JsUint32`          | `v8::Uint32`          | 32-bit unsigned integer                                             |\n| `JsBigInt`          | `v8::BigInt`          | Arbitrary precision integer                                         |\n| `JsString`          | `v8::String`          | String                                                              |\n| `JsSymbol`          | `v8::Symbol`          | Symbol                                                              |\n| `JsObject`          | `v8::Object`          | Object                                                              |\n| `JsArray`           | `v8::Array`           | Array                                                               |\n| `JsMap`             | `v8::Map`             | Map collection                                                      |\n| `JsSet`             | `v8::Set`             | Set collection                                                      |\n| `JsFunction`        | `v8::Function`        | Function                                                            |\n| `JsPromise`         | `v8::Promise`         | Promise (state inspection only; use `jsg::Promise<T>` for chaining) |\n| `JsDate`            | `v8::Date`            | Date object                                                         |\n| `JsRegExp`          | `v8::RegExp`          | Regular expression                                                  |\n| `JsArrayBuffer`     | `v8::ArrayBuffer`     | ArrayBuffer                                                         |\n| `JsArrayBufferView` | `v8::ArrayBufferView` | TypedArray or DataView                                              |\n| `JsUint8Array`      | `v8::Uint8Array`      | Uint8Array                                                          |\n| `JsProxy`           | `v8::Proxy`           | Proxy object                                                        |\n\nKey rules: stack-only allocation (enforced in debug); implicit conversion to `v8::Local<T>`;\nuse `JsRef<T>` to persist beyond current scope.\n\n## JSG Macro Catalog\n\n### Resource Type Declaration\n\n| Macro                         | Description                                               |\n| ----------------------------- | --------------------------------------------------------- |\n| `JSG_RESOURCE_TYPE(T)`        | Required. Declares resource type with JS binding block    |\n| `JSG_RESOURCE_TYPE(T, flags)` | With `CompatibilityFlags::Reader` for conditional members |\n\n### Methods\n\n| Macro                                        | Context  | Description                                           |\n| -------------------------------------------- | -------- | ----------------------------------------------------- |\n| `JSG_METHOD(name)`                           | Instance | Expose method on prototype; auto-marshals args/return |\n| `JSG_METHOD_NAMED(jsName, cppMethod)`        | Instance | Different JS name (e.g., `delete` → `delete_`)        |\n| `JSG_STATIC_METHOD(name)`                    | Class    | Expose static method on constructor                   |\n| `JSG_STATIC_METHOD_NAMED(jsName, cppMethod)` | Class    | Different JS name for static                          |\n| `JSG_CALLABLE(name)`                         | Instance | Make object callable as function                      |\n\n### Properties\n\n| Macro                                               | Scope     | Writable         | Overridable by subclass |\n| --------------------------------------------------- | --------- | ---------------- | ----------------------- |\n| `JSG_READONLY_PROTOTYPE_PROPERTY(name, getter)`     | Prototype | No               | Yes                     |\n| `JSG_PROTOTYPE_PROPERTY(name, getter, setter)`      | Prototype | Yes              | Yes                     |\n| `JSG_READONLY_INSTANCE_PROPERTY(name, getter)`      | Instance  | No               | No                      |\n| `JSG_INSTANCE_PROPERTY(name, getter, setter)`       | Instance  | Yes              | No                      |\n| `JSG_LAZY_READONLY_INSTANCE_PROPERTY(name, getter)` | Instance  | No               | No                      |\n| `JSG_LAZY_INSTANCE_PROPERTY(name, getter)`          | Instance  | Yes (after eval) | No                      |\n| `JSG_STATIC_CONSTANT(name)`                         | Class     | No               | N/A                     |\n\n### Type Relationships\n\n| Macro                                | Description                                             |\n| ------------------------------------ | ------------------------------------------------------- |\n| `JSG_INHERIT(Base)`                  | Declare JS prototype inheritance                        |\n| `JSG_INHERIT_INTRINSIC(v8Intrinsic)` | Inherit from V8 intrinsic (e.g., `v8::kErrorPrototype`) |\n| `JSG_NESTED_TYPE(T)`                 | Expose nested type constructor (e.g., `Foo.Bar`)        |\n| `JSG_NESTED_TYPE_NAMED(T, jsName)`   | Nested type with different JS name                      |\n\n### Iterators\n\n| Macro                                                        | Description                          |\n| ------------------------------------------------------------ | ------------------------------------ |\n| `JSG_ITERATOR(Name, method, ValueType, State, nextFn)`       | Define sync iterator type            |\n| `JSG_ASYNC_ITERATOR(Name, method, ValueType, State, nextFn)` | Define async iterator type           |\n| `JSG_ITERABLE(method)`                                       | Set `Symbol.iterator` to method      |\n| `JSG_ASYNC_ITERABLE(method)`                                 | Set `Symbol.asyncIterator` to method |\n\n### Structs\n\n| Macro                             | Description                                           |\n| --------------------------------- | ----------------------------------------------------- |\n| `JSG_STRUCT(field1, field2, ...)` | Declare value-type mapping; only listed fields mapped |\n\n### TypeScript Generation\n\n| Macro                         | Context             | Description                         |\n| ----------------------------- | ------------------- | ----------------------------------- |\n| `JSG_TS_ROOT`                 | `JSG_RESOURCE_TYPE` | Mark as root for TS generation      |\n| `JSG_TS_OVERRIDE(...)`        | `JSG_RESOURCE_TYPE` | Customize generated TS definition   |\n| `JSG_TS_DEFINE(...)`          | `JSG_RESOURCE_TYPE` | Insert additional TS definitions    |\n| `JSG_STRUCT_TS_ROOT`          | `struct`            | Struct variant of `JSG_TS_ROOT`     |\n| `JSG_STRUCT_TS_OVERRIDE(...)` | `struct`            | Struct variant of `JSG_TS_OVERRIDE` |\n| `JSG_STRUCT_TS_DEFINE(...)`   | `struct`            | Struct variant of `JSG_TS_DEFINE`   |\n\n### Serialization\n\n| Macro                               | Context                         | Description                                          |\n| ----------------------------------- | ------------------------------- | ---------------------------------------------------- |\n| `JSG_SERIALIZABLE(tag, ...oldTags)` | After `JSG_RESOURCE_TYPE` block | Enable structured clone; first tag = current version |\n| `JSG_SERIALIZABLE_ONEWAY(tag)`      | After `JSG_RESOURCE_TYPE` block | Serialize only (deserialize via different type)      |\n\n### Error Handling\n\n| Macro                                      | Description                                   |\n| ------------------------------------------ | --------------------------------------------- |\n| `JSG_REQUIRE(cond, type, msg...)`          | Throw JS error if condition false             |\n| `JSG_REQUIRE_NONNULL(maybe, type, msg...)` | Unwrap `kj::Maybe`; throw if none             |\n| `JSG_FAIL_REQUIRE(type, msg...)`           | Unconditionally throw JS error                |\n| `JSG_ASSERT(cond, type, msg...)`           | Like `KJ_ASSERT` but produces JSG-style error |\n\n### Memory and Performance\n\n| Macro                               | Description                                   |\n| ----------------------------------- | --------------------------------------------- |\n| `JSG_MEMORY_INFO(TypeName) { ... }` | Shorthand for heap snapshot tracking          |\n| `JSG_ASSERT_FASTAPI(Class::method)` | Compile-time assert V8 Fast API compatibility |\n\n## Property Type Decision Matrix\n\n| Use case                                  | Macro                             | Why                                               |\n| ----------------------------------------- | --------------------------------- | ------------------------------------------------- |\n| Standard API property                     | `JSG_PROTOTYPE_PROPERTY`          | Overridable by subclasses; GC-friendly            |\n| Read-only standard API                    | `JSG_READONLY_PROTOTYPE_PROPERTY` | Same benefits, no setter                          |\n| Must shadow in subclass                   | `JSG_INSTANCE_PROPERTY`           | Own-property semantics prevent prototype override |\n| New global without breaking existing code | `JSG_LAZY_INSTANCE_PROPERTY`      | Evaluated once; user can override value           |\n| Constant on constructor                   | `JSG_STATIC_CONSTANT`             | Read-only class-level value                       |\n\n**Default choice:** `JSG_PROTOTYPE_PROPERTY` (or readonly variant). Use instance properties\nonly with specific justification — they break GC optimization.\n\n## GC-Visitable Types\n\nAll types that must be visited in `visitForGc()` if held as Resource Type members:\n\n| Type                        | Notes                            |\n| --------------------------- | -------------------------------- |\n| `jsg::Ref<T>`               | Strong ref to resource type      |\n| `jsg::V8Ref<T>`             | Persistent ref to V8 value       |\n| `jsg::Value`                | Alias for `V8Ref<v8::Value>`     |\n| `jsg::HashableV8Ref<T>`     | Hashable variant of `V8Ref`      |\n| `jsg::JsRef<T>`             | Persistent ref to JsValue type   |\n| `jsg::Optional<T>`          | When `T` is GC-visitable         |\n| `jsg::LenientOptional<T>`   | When `T` is GC-visitable         |\n| `jsg::Name`                 | Property name (string or symbol) |\n| `jsg::Function<Sig>`        | Wrapped JS/C++ function          |\n| `jsg::Promise<T>`           | JS promise wrapper               |\n| `jsg::Promise<T>::Resolver` | Promise resolver                 |\n| `jsg::BufferSource`         | Buffer with JS handle            |\n| `jsg::Sequence<T>`          | Iterable sequence                |\n| `jsg::Generator<T>`         | Sync generator                   |\n| `jsg::AsyncGenerator<T>`    | Async generator                  |\n| `kj::Maybe<T>`              | When `T` is GC-visitable         |\n\n## Error Type Catalog\n\n| JSG Error Name             | JS Exception Type                       | When to Use                                    |\n| -------------------------- | --------------------------------------- | ---------------------------------------------- |\n| `TypeError`                | `TypeError`                             | Wrong argument type, missing required argument |\n| `Error`                    | `Error`                                 | Generic error                                  |\n| `RangeError`               | `RangeError`                            | Value out of valid range                       |\n| `DOMOperationError`        | `DOMException(\"OperationError\")`        | Operation failed for operation-specific reason |\n| `DOMDataError`             | `DOMException(\"DataError\")`             | Provided data is invalid                       |\n| `DOMInvalidStateError`     | `DOMException(\"InvalidStateError\")`     | Object in wrong state for operation            |\n| `DOMNotSupportedError`     | `DOMException(\"NotSupportedError\")`     | Operation not supported                        |\n| `DOMSyntaxError`           | `DOMException(\"SyntaxError\")`           | Invalid syntax                                 |\n| `DOMInvalidAccessError`    | `DOMException(\"InvalidAccessError\")`    | Invalid access to object                       |\n| `DOMNotFoundError`         | `DOMException(\"NotFoundError\")`         | Requested item not found                       |\n| `DOMAbortError`            | `DOMException(\"AbortError\")`            | Operation was aborted                          |\n| `DOMInvalidCharacterError` | `DOMException(\"InvalidCharacterError\")` | Invalid character in string                    |\n| `DOMQuotaExceededError`    | `DOMException(\"QuotaExceededError\")`    | Storage quota exceeded                         |\n\n## JSG_TS_OVERRIDE Rules\n\n### Full Replacement\n\nOverride starts with `export `, `declare `, `type `, `abstract `, `class `, `interface `,\n`enum `, `const `, `var `, or `function ` → replaces the entire generated definition.\n`declare` added automatically if not present.\n\nSpecial case: `type X = never` → deletes the definition entirely.\n\n### Merge (all other overrides)\n\nOverride is converted to a class and merged with the generated definition:\n\n1. **Prefix inference:**\n   - Starts with `extends `, `implements `, `{` → prepend `class <Name> `\n   - Starts with `<` → prepend `class <Name>`\n   - Otherwise → prepend `class `\n\n2. **Suffix:** If override doesn't end with `}`, append ` {}`\n\n3. **Merge rules:**\n   - Different name → rename type and update all references\n   - Type parameters → copy to generated type\n   - Heritage clauses → replace generated heritage\n   - Members:\n     - New members → inserted at end\n     - Same-name member → replaces generated member\n     - Member typed `never` → removes generated member without inserting\n\n### Examples\n\n| Override                               | Effect                                  |\n| -------------------------------------- | --------------------------------------- |\n| `KVNamespaceListOptions`               | Rename type                             |\n| `{ json<T>(): Promise<T> }`            | Replace `json()` method, keep others    |\n| `<R = any> { read(): ... }`            | Add type param, replace listed methods  |\n| `{ actorState: never }`                | Remove `actorState` member              |\n| `extends EventTarget<Map>`             | Replace heritage                        |\n| `class Body { json<T>(): Promise<T> }` | Full replacement (starts with `class `) |\n| `type X = never`                       | Delete definition                       |\n\nNotes:\n\n- Renaming happens after all overrides applied; use original C++ names in cross-references\n- Roots visited before overrides; new types referenced by overrides may need `JSG_TS_ROOT`\n- `JSG_TS_DEFINE` inserts additional definitions; `declare` added automatically; once per block\n\n## Serialization Pattern\n\n### Signatures\n\n```cpp\nvoid serialize(jsg::Lock& js, jsg::Serializer& serializer);\nstatic jsg::Ref<T> deserialize(jsg::Lock& js, TagEnum tag, jsg::Deserializer& deser);\n```\n\nBoth may take additional `TypeHandler<T>&` trailing parameters.\n\n### Raw Methods\n\n| Serializer                                   | Deserializer                                        |\n| -------------------------------------------- | --------------------------------------------------- |\n| `writeRawUint32(uint32_t)`                   | `readRawUint32() → uint32_t`                        |\n| `writeRawUint64(uint64_t)`                   | `readRawUint64() → uint64_t`                        |\n| `writeRawBytes(ArrayPtr<const byte>)`        | `readRawBytes(size) → ArrayPtr<const byte>`         |\n| `writeLengthDelimited(ArrayPtr<const byte>)` | `readLengthDelimitedBytes() → ArrayPtr<const byte>` |\n| `writeLengthDelimited(StringPtr)`            | `readLengthDelimitedString() → String`              |\n| `write(js, JsValue)`                         | `readValue(js) → JsValue`                           |\n| `transfer(js, JsArrayBuffer)`                | `getVersion() → uint32_t`                           |\n\n### Rules\n\n- `JSG_SERIALIZABLE` MUST appear AFTER `JSG_RESOURCE_TYPE` block\n- Tag enum values MUST NOT change once data has been serialized\n- First tag = current version; subsequent tags = accepted old versions\n- `deserialize()` receives the tag for version dispatch\n\n## Web IDL Union Validation Rules\n\n`kj::OneOf<T...>` is validated at compile time against these rules:\n\n1. At most one boolean type\n2. At most one numeric type\n3. At most one string type\n4. At most one object type (excludes interface-like, callback, dictionary-like, sequence-like)\n5. At most one callback function type\n6. At most one dictionary-like type\n7. At most one sequence-like type\n8. At most one nullable (`kj::Maybe`) or dictionary type combined\n9. No duplicate types\n10. No `Optional<T>` types (use `kj::Maybe<T>`)\n\n## Web IDL Type Categories\n\n| Web IDL Category  | JSG Concept            | C++ Types                                          |\n| ----------------- | ---------------------- | -------------------------------------------------- |\n| Boolean           | `BooleanType`          | `bool`, `NonCoercible<bool>`                       |\n| Numeric           | `NumericType`          | `int`, `double`, `uint32_t`, etc.                  |\n| String            | `StringType`           | `kj::String`, `USVString`, `DOMString`, `JsString` |\n| Object            | `ObjectType`           | `v8::Local<v8::Object>`, `v8::Global<v8::Object>`  |\n| Symbol            | `SymbolType`           | (not yet implemented)                              |\n| Interface-like    | `InterfaceLikeType`    | `JSG_RESOURCE` types, `BufferSource`               |\n| Callback function | `CallbackFunctionType` | `kj::Function<T>`, `Constructor<T>`                |\n| Dictionary-like   | `DictionaryLikeType`   | `JSG_STRUCT` types, `Dict<V, K>`                   |\n| Sequence-like     | `SequenceLikeType`     | `kj::Array<T>`, `Sequence<T>`                      |\n\n## Module System Overview\n\n| Type       | Description                                     | Resolution priority                      |\n| ---------- | ----------------------------------------------- | ---------------------------------------- |\n| `BUNDLE`   | Worker bundle (user code)                       | From bundle: Bundle → Builtin → Fallback |\n| `BUILTIN`  | Runtime modules (`node:*`, `cloudflare:*`)      | From builtin: Builtin → Internal         |\n| `INTERNAL` | Only importable by builtins (`node-internal:*`) | From internal: Internal only             |\n\n## Module Info Types\n\n| Type                 | Description                  | Use case                  |\n| -------------------- | ---------------------------- | ------------------------- |\n| ESM                  | V8-compiled ES module        | Standard JS modules       |\n| `CommonJsModuleInfo` | CJS with `require`/`exports` | Node.js compat            |\n| `DataModuleInfo`     | Binary data as `ArrayBuffer` | Embedded binary resources |\n| `TextModuleInfo`     | Text content as string       | Embedded text             |\n| `WasmModuleInfo`     | WebAssembly module           | `.wasm` files             |\n| `JsonModuleInfo`     | Parsed JSON data             | Config files              |\n| `ObjectModuleInfo`   | JSG C++ object as module     | C++ API exposure          |\n| `CapnpModuleInfo`    | Cap'n Proto schema module    | Schema files              |\n\n## Two Module Implementations\n\n| Aspect            | Original (`modules.h`)         | New (`modules-new.h`)             |\n| ----------------- | ------------------------------ | --------------------------------- |\n| Specifiers        | `kj::Path`                     | URL-based                         |\n| Thread safety     | Single isolate                 | Cross-replica safe                |\n| import.meta       | No                             | Yes (`url`, `main`, `resolve()`)  |\n| Import attributes | No                             | Yes                               |\n| Bundles           | Flat registry                  | Modular `ModuleBundle`            |\n| Use when          | Simpler control, existing code | New code, URL specifiers, sharing |\n\n## Context Embedder Data Slots\n\n| Slot | Enum                       | Purpose                           |\n| ---- | -------------------------- | --------------------------------- |\n| 0    | `RESERVED`                 | **Never use** — reserved by V8    |\n| 1    | `GLOBAL_WRAPPER`           | Pointer to global object wrapper  |\n| 2    | `MODULE_REGISTRY`          | Pointer to module registry        |\n| 3    | `EXTENDED_CONTEXT_WRAPPER` | Extended type wrapper for context |\n| 4    | `VIRTUAL_FILE_SYSTEM`      | Virtual file system               |\n| 5    | `RUST_REALM`               | Rust realm pointer                |\n\n## Wrappable Lifecycle\n\n```\n1. C++ object created (no JS wrapper)\n         |\n2. Object passed to JavaScript\n         |\n3. attachWrapper() → JS wrapper created\n         |\n4. JS wrapper ←→ C++ object linked\n         |\n5. GC may collect wrapper if:\n   - No JS references\n   - No strong Ref<T>s\n   - Wrapper unmodified\n         |\n6. Wrapper collected, C++ alive →\n   new wrapper on next JS access\n         |\n7. C++ destroyed →\n   detachWrapper(); JS wrapper = empty shell\n```\n\nInternal fields: slot 0 = `WORKERD_WRAPPABLE_TAG`, slot 1 = `Wrappable*` pointer.\nCheck: `jsg::Wrappable::isWorkerdApiObject(object)`.\n\n## BackingStore Factory Methods\n\n| Method                                             | Template Param  | Description                                       |\n| -------------------------------------------------- | --------------- | ------------------------------------------------- |\n| `BackingStore::from<T>(js, kj::Array<byte>)`       | TypedArray type | From owned array (may copy if outside V8 sandbox) |\n| `BackingStore::alloc<T>(js, size)`                 | TypedArray type | Zero-initialized allocation inside sandbox        |\n| `BackingStore::wrap<T>(data, size, disposer, ctx)` | TypedArray type | External data with custom cleanup                 |\n\nSupported template params: `v8::ArrayBuffer`, `v8::Uint8Array`, `v8::Int8Array`,\n`v8::Uint8ClampedArray`, `v8::Uint16Array`, `v8::Int16Array`, `v8::Uint32Array`,\n`v8::Int32Array`, `v8::Float32Array`, `v8::Float64Array`, `v8::BigInt64Array`,\n`v8::BigUint64Array`, `v8::DataView`.\n\n## Observer Hooks\n\n| Observer                    | Method                            | When Called                               |\n| --------------------------- | --------------------------------- | ----------------------------------------- |\n| `CompilationObserver`       | `onEsmCompilationStart`           | ESM module compilation begins             |\n| `CompilationObserver`       | `onScriptCompilationStart`        | Non-ESM script compilation                |\n| `CompilationObserver`       | `onWasmCompilationStart`          | WebAssembly compilation                   |\n| `CompilationObserver`       | `onWasmCompilationFromCacheStart` | WASM from cached data                     |\n| `CompilationObserver`       | `onJsonCompilationStart`          | JSON module parsing                       |\n| `CompilationObserver`       | `onCompileCacheFound`             | Cached compilation data hit               |\n| `CompilationObserver`       | `onCompileCacheRejected`          | Cached data rejected (version mismatch)   |\n| `CompilationObserver`       | `onCompileCacheGenerated`         | New cache data generated                  |\n| `CompilationObserver`       | `onCompileCacheGenerationFailed`  | Cache generation failed                   |\n| `ResolveObserver`           | `onResolveModule`                 | Module resolution begins                  |\n| `InternalExceptionObserver` | `reportInternalException`         | Internal exception for metrics            |\n| `IsolateObserver`           | `onDynamicEval`                   | `eval()`, `new Function()`, etc. detected |\n\n`onXxxStart` methods return `kj::Own<void>` destroyed on completion (RAII timing).\nCompilation `Option`: `BUNDLE` (user code) or `BUILTIN` (runtime modules).\nResolve `Context`: `BUNDLE`, `BUILTIN`, `BUILTIN_ONLY`.\nResolve `Source`: `STATIC_IMPORT`, `DYNAMIC_IMPORT`, `REQUIRE`, `INTERNAL`.\n\n## RTTI Type Mapping\n\n| C++ Type                  | RTTI Kind   |\n| ------------------------- | ----------- |\n| `void`                    | `voidt`     |\n| `bool`                    | `boolt`     |\n| `int`, `double`, etc.     | `number`    |\n| `kj::String`, `USVString` | `string`    |\n| `kj::Array<T>`            | `array`     |\n| `kj::Maybe<T>`            | `maybe`     |\n| `kj::OneOf<T...>`         | `oneOf`     |\n| `jsg::Promise<T>`         | `promise`   |\n| `jsg::Dict<V, K>`         | `dict`      |\n| `JSG_STRUCT` types        | `structure` |\n| `JSG_RESOURCE` types      | `structure` |\n| `jsg::Function<T>`        | `function`  |\n\n## V8 Fast API Requirements\n\nFor a method to be Fast API compatible:\n\n- **Return type:** `void`, `bool`, `int32_t`, `uint32_t`, `float`, or `double`\n- **Parameter types:** Primitives above, `v8::Local<v8::Value>`, `v8::Local<v8::Object>`,\n  or TypeWrapper-unwrappable types\n- **Method:** Regular/const instance method, optionally with `jsg::Lock&` first param\n- Any `JSG_METHOD(name)` auto-enables fast path if signature is compatible\n- Use `JSG_ASSERT_FASTAPI(Class::method)` for compile-time verification\n\n## CompileCache\n\nProcess-lifetime in-memory cache for V8 compilation data (built-in modules only).\n\n- Singleton: `jsg::CompileCache::get()`\n- Lookup: `cache.find(key)` → `kj::Maybe<CompileCache::Data>`\n- Store: `cache.add(key, shared_ptr<CachedData>)`\n- Thread-safe (mutex-guarded)\n- Entries never removed or replaced\n"
  },
  {
    "path": "src/workerd/jsg/async-context.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#include \"async-context.h\"\n\n#include \"jsg.h\"\n\n#include <workerd/jsg/memory.h>\n\n#include <v8.h>\n\nnamespace workerd::jsg {\n\nnamespace {\ninline void maybeSetV8ContinuationContext(\n    v8::Isolate* isolate, kj::Maybe<AsyncContextFrame&> maybeFrame) {\n  v8::Local<v8::Value> value;\n  KJ_IF_SOME(frame, maybeFrame) {\n    value = frame.getJSWrapper(isolate);\n  } else {\n    value = v8::Undefined(isolate);\n  }\n  isolate->SetContinuationPreservedEmbedderDataV2(value);\n}\n}  // namespace\n\nAsyncContextFrame::AsyncContextFrame(Lock& js, StorageEntry storageEntry) {\n  KJ_IF_SOME(frame, current(js)) {\n    // Propagate the storage context of the current frame (if any).\n    // If current(js) returns nullptr, we assume we're in the root\n    // frame and there is no storage to propagate.\n    frame.storage.eraseAll([](const auto& entry) { return entry.key->isDead(); });\n    for (auto& entry: frame.storage) {\n      storage.insert(entry.clone(js));\n    }\n  }\n\n  // This case is extremely unlikely to happen but let's handle it anyway\n  // just out of an excess of caution.\n  if (storageEntry.key->isDead()) return;\n\n  storage.upsert(kj::mv(storageEntry), [](StorageEntry& existing, StorageEntry&& row) mutable {\n    existing.value = kj::mv(row.value);\n  });\n}\n\nAsyncContextFrame::StorageEntry::StorageEntry(kj::Own<StorageKey> key, Value value)\n    : key(kj::mv(key)),\n      value(kj::mv(value)) {}\n\nAsyncContextFrame::StorageEntry AsyncContextFrame::StorageEntry::clone(Lock& js) {\n  return StorageEntry(kj::addRef(*key), value.addRef(js));\n}\n\nkj::Maybe<AsyncContextFrame&> AsyncContextFrame::current(Lock& js) {\n  return current(js.v8Isolate);\n}\n\nkj::Maybe<Ref<AsyncContextFrame>> AsyncContextFrame::currentRef(Lock& js) {\n  return jsg::AsyncContextFrame::current(js).map(\n      [](jsg::AsyncContextFrame& frame) { return frame.addRef(); });\n}\n\nkj::Maybe<AsyncContextFrame&> AsyncContextFrame::current(v8::Isolate* isolate) {\n  auto value = isolate->GetContinuationPreservedEmbedderDataV2();\n  KJ_IF_SOME(wrappable, Wrappable::tryUnwrapOpaque(isolate, value.As<v8::Value>())) {\n    AsyncContextFrame* frame = dynamic_cast<AsyncContextFrame*>(&wrappable);\n    KJ_ASSERT(frame != nullptr);\n    return *frame;\n  }\n  return kj::none;\n}\n\nRef<AsyncContextFrame> AsyncContextFrame::create(Lock& js, StorageEntry storageEntry) {\n  return js.alloc<AsyncContextFrame>(js, kj::mv(storageEntry));\n}\n\nv8::Local<v8::Function> AsyncContextFrame::wrap(Lock& js,\n    V8Ref<v8::Function>& fn,\n    jsg::Function<void()> validate,\n    kj::Maybe<v8::Local<v8::Value>> thisArg) {\n  return wrap(js, fn.getHandle(js), kj::mv(validate), thisArg);\n}\n\nv8::Local<v8::Function> AsyncContextFrame::wrapSnapshot(Lock& js, jsg::Function<void()> validate) {\n  return js.wrapReturningFunction(js.v8Context(),\n      JSG_VISITABLE_LAMBDA((frame = AsyncContextFrame::currentRef(js), validate = kj::mv(validate)),\n          (frame, validate), (Lock& js, const v8::FunctionCallbackInfo<v8::Value>& args) {\n            validate(js);\n            auto context = js.v8Context();\n            JSG_REQUIRE(args[0]->IsFunction(), TypeError, \"The first argument must be a function\");\n            auto fn = args[0].As<v8::Function>();\n            v8::LocalVector<v8::Value> argv(js.v8Isolate, args.Length() - 1);\n            for (int n = 1; n < args.Length(); n++) {\n            argv[n - 1] = args[n];\n            }\n\n            AsyncContextFrame::Scope scope(js, frame);\n            return check(fn->Call(context, context->Global(), argv.size(), argv.data()));\n          }));\n}\n\nv8::Local<v8::Function> AsyncContextFrame::wrap(Lock& js,\n    v8::Local<v8::Function> fn,\n    jsg::Function<void()> validate,\n    kj::Maybe<v8::Local<v8::Value>> thisArg) {\n  auto context = js.v8Context();\n\n  return js.wrapReturningFunction(context,\n      JSG_VISITABLE_LAMBDA(\n          (frame = JSG_THIS, validate = kj::mv(validate),\n              thisArg = js.v8Ref(thisArg.orDefault(context->Global())), fn = js.v8Ref(fn)),\n          (frame, validate, thisArg, fn),\n          (Lock& js, const v8::FunctionCallbackInfo<v8::Value>& args) {\n            validate(js);\n            auto function = fn.getHandle(js);\n\n            v8::LocalVector<v8::Value> argv(js.v8Isolate, args.Length());\n            for (int n = 0; n < args.Length(); n++) {\n            argv[n] = args[n];\n            }\n\n            AsyncContextFrame::Scope scope(js, *frame.get());\n            return check(\n                function->Call(js.v8Context(), thisArg.getHandle(js), argv.size(), argv.data()));\n          }));\n}\n\nv8::Local<v8::Function> AsyncContextFrame::wrapRoot(\n    Lock& js, v8::Local<v8::Function> fn, kj::Maybe<v8::Local<v8::Value>> thisArg) {\n  auto context = js.v8Context();\n\n  return js.wrapReturningFunction(context,\n      JSG_VISITABLE_LAMBDA(\n          (thisArg = js.v8Ref(thisArg.orDefault(context->Global())), fn = js.v8Ref(fn)),\n          (thisArg, fn), (Lock& js, const v8::FunctionCallbackInfo<v8::Value>& args) {\n            auto function = fn.getHandle(js);\n\n            v8::LocalVector<v8::Value> argv(js.v8Isolate, args.Length());\n            for (int n = 0; n < args.Length(); n++) {\n            argv[n] = args[n];\n            }\n\n            AsyncContextFrame::Scope scope(js, kj::none);\n            return check(\n                function->Call(js.v8Context(), thisArg.getHandle(js), argv.size(), argv.data()));\n          }));\n}\n\nkj::Maybe<Value&> AsyncContextFrame::get(StorageKey& key) {\n  KJ_ASSERT(!key.isDead());\n  storage.eraseAll([](const auto& entry) { return entry.key->isDead(); });\n  return storage.find(key).map([](auto& entry) -> Value& { return entry.value; });\n}\n\nAsyncContextFrame::Scope::Scope(Lock& js, kj::Maybe<AsyncContextFrame&> resource)\n    : Scope(js.v8Isolate, resource) {}\n\nAsyncContextFrame::Scope::Scope(v8::Isolate* ptr, kj::Maybe<AsyncContextFrame&> maybeFrame)\n    : isolate(ptr),\n      prior(AsyncContextFrame::current(ptr)) {\n  maybeSetV8ContinuationContext(isolate, maybeFrame);\n}\n\nAsyncContextFrame::Scope::Scope(Lock& js, kj::Maybe<Ref<AsyncContextFrame>>& resource)\n    : Scope(js.v8Isolate, resource.map([](Ref<AsyncContextFrame>& frame) -> AsyncContextFrame& {\n        return *frame.get();\n      })) {}\n\nAsyncContextFrame::Scope::~Scope() noexcept(false) {\n  maybeSetV8ContinuationContext(isolate, prior);\n}\n\nAsyncContextFrame::StorageScope::StorageScope(Lock& js, StorageKey& key, Value store)\n    : frame(AsyncContextFrame::create(js, StorageEntry(kj::addRef(key), kj::mv(store)))),\n      scope(js, *frame) {}\n\nv8::Local<v8::Object> AsyncContextFrame::getJSWrapper(v8::Isolate* isolate) {\n  KJ_IF_SOME(handle, tryGetHandle(isolate)) {\n    return handle;\n  }\n  return attachOpaqueWrapper(isolate->GetCurrentContext(), true);\n}\n\nv8::Local<v8::Object> AsyncContextFrame::getJSWrapper(Lock& js) {\n  return getJSWrapper(js.v8Isolate);\n}\n\nvoid AsyncContextFrame::jsgVisitForGc(GcVisitor& visitor) {\n  // tracing will make the members weak and will allow\n  // them to be gc'd, which is not what we want.\n}\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/async-context.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include \"jsg.h\"\n\n#include <v8.h>\n\nnamespace workerd::jsg {\n\n#ifndef V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA\n#error \"V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA must be defined\"\n#endif\n\n// Provides for basic internal async context tracking. Eventually, it is expected that\n// this will be provided by V8 assuming that the AsyncContext proposal advances through\n// TC-39. For now, however, we implement a model that is similar but not quite identical\n// to that implemented by Node.js.\n//\n// At any point in time when JavaScript is running, there is a current \"Async Context Frame\",\n// within which any number of \"async resources\" can be created. The term \"resource\" here\n// comes from Node.js (which really doesn't take the time to define it properly). Conceptually,\n// an \"async resource\" is some Thing that generates asynchronous activity over time (either\n// once or repeatedly). For instance, a timer is an async resource that invokes a callback\n// after a certain period of time elapses; a promise is an async resource that may trigger\n// scheduling of a microtask at some point in the future, and so forth. Whether or not\n// \"resource\" is the best term to use to describe these, it's what we have because our\n// intent here is to stay aligned with Node.js' model as closely as possible.\n//\n// Every async resource maintains a reference to the Async Context Frame that was current\n// at the moment the resource is created.\n//\n// Frames form a logical stack. The default frame is the Root. We \"enter\" a frame by pushing\n// it onto to top of the stack (making it \"current\"), then perform some action within that\n// frame, then \"exit\" by popping it back off the stack. The Root is associated with the\n// Isolate itself such that every isolate always has at least one frame logically on the stack\n// at all times. In Node.js terms, the \"Async Context Frame\" would be most closely aligned\n// with the concept of an \"execution context\" or \"execution scope\".\n//\n// Every Frame has a storage context. The current frame determines the currently active\n// storage context. So, for instance, when we start executing, the Root Frame's storage\n// context is active. When a timeout elapses and a timer is going to fire, we enter the\n// timer's Frame which makes that frame's storage context active. Once the timer\n// callback has completed, we return back to the Root frame and storage context.\n//\n// All frames (except for the Root) are created within the scope of a parent, which by\n// default is whichever frame is current when the new frame is created. When the new frame\n// is created, it inherits a copy storage context of the parent.\n//\n// To implement all of this, however, we depend largely on an obscure v8 API on the\n// v8::Context object called SetContinuationPreservedEmbedderData and\n// GetContinuationPreservedEmbedderData. An AsyncContextFrame is a Wrappable because\n// because instances of AsyncContextFrame are set as the continuation-preserved embedder\n// data and that API requires a JS value.\n//\n// AsyncContextFrame::current() returns the current frame or nullptr. Returning nullptr\n// implies that we are in the \"root\" frame.\n//\n// AsyncContextFrame::StorageScope is created on stack to create a new frame and set\n// a stored value in the storage context before entering it.\n//\n// AsyncContextFrame::Scope is created on the stack to temporarily enter an existing\n// frame.\n//\n// AsyncContextFrame::StorageKey is used to define a storage cell within the storage\n// context.\nclass AsyncContextFrame final: public Wrappable {\n public:\n  // An opaque key that identifies an async-local storage cell within the frame.\n  class StorageKey: public kj::Refcounted {\n   public:\n    StorageKey(): hash(kj::hashCode(this)) {}\n    KJ_DISALLOW_COPY_AND_MOVE(StorageKey);\n\n    // The owner of the key should reset it when it goes away.\n    // The StorageKey is typically owned by an instance of AsyncLocalStorage (see\n    // the api/node/async-hooks.h). When the ALS instance is garbage collected, it\n    // must call reset to signal that this StorageKey is \"dead\" and can never be\n    // looked up again. Subsequent accesses to a frame will remove dead keys from\n    // the frame lazily. The lazy cleanup does mean that values may persist in\n    // memory a bit longer so if it proves to be problematic we can make the cleanup\n    // a bit more proactive.\n    void reset() {\n      dead = true;\n    }\n    // TODO(later): We should also evaluate the relatively unlikely case where an\n    // ALS is capturing a reference to itself and therefore can never be cleaned up.\n\n    bool isDead() const {\n      return dead;\n    }\n    inline uint hashCode() const {\n      return hash;\n    }\n    inline bool operator==(const StorageKey& other) const {\n      return this == &other;\n    }\n\n    JSG_MEMORY_INFO(StorageKey) {}\n\n   private:\n    uint hash;\n    bool dead = false;\n  };\n\n  struct StorageEntry {\n    kj::Own<StorageKey> key;\n    Value value;\n    StorageEntry(kj::Own<StorageKey> key, Value value);\n    StorageEntry clone(Lock& js);\n\n    JSG_MEMORY_INFO(StorageEntry) {\n      tracker.trackField(\"key\", key);\n      tracker.trackField(\"value\", value);\n    }\n  };\n\n  AsyncContextFrame(Lock& js, StorageEntry storageEntry);\n\n  inline Ref<AsyncContextFrame> addRef() {\n    return JSG_THIS;\n  }\n\n  // Returns the reference to the AsyncContextFrame currently at the top of the stack, if any.\n  static kj::Maybe<AsyncContextFrame&> current(Lock& js);\n\n  // Returns the reference to the AsyncContextFrame currently at the top of the stack, if any.\n  static kj::Maybe<AsyncContextFrame&> current(v8::Isolate* isolate);\n\n  // Convenience variation on current() that returns the result wrapped in a Ref for when we\n  // need to make sure the frame stays alive.\n  static kj::Maybe<Ref<AsyncContextFrame>> currentRef(Lock& js);\n\n  // Create a new AsyncContextFrame. The new frame inherits the storage context of the current\n  // frame (if any) and the given StorageEntry is added.\n  static Ref<AsyncContextFrame> create(Lock& js, StorageEntry storageEntry);\n\n  // Wraps the given JavaScript function such that whenever the wrapper function is called,\n  // the root AsyncContextFrame will be entered.\n  static v8::Local<v8::Function> wrapRoot(\n      Lock& js, v8::Local<v8::Function> fn, kj::Maybe<v8::Local<v8::Value>> thisArg = kj::none);\n\n  // Returns a function that captures the current frame and calls the function passed\n  // in as an argument within that captured context. Equivalent to wrapping a function\n  // with the signature (cb, ...args) => cb(...args).\n  // The validate function is called to ensure that the current frame is still valid.\n  // If the validate function throws, the wrapper will throw.\n  static v8::Local<v8::Function> wrapSnapshot(Lock& js, jsg::Function<void()> validate);\n\n  // Associates the given JavaScript function with this AsyncContextFrame, returning\n  // a wrapper function that will ensure appropriate propagation of the async context\n  // when the wrapper function is called.\n  v8::Local<v8::Function> wrap(Lock& js,\n      V8Ref<v8::Function>& fn,\n      jsg::Function<void()> validate,\n      kj::Maybe<v8::Local<v8::Value>> thisArg = kj::none);\n\n  // Associates the given JavaScript function with this AsyncContextFrame, returning\n  // a wrapper function that will ensure appropriate propagation of the async context\n  // when the wrapper function is called.\n  v8::Local<v8::Function> wrap(Lock& js,\n      v8::Local<v8::Function> fn,\n      jsg::Function<void()> validate,\n      kj::Maybe<v8::Local<v8::Value>> thisArg = kj::none);\n\n  // AsyncContextFrame::Scope makes the given AsyncContextFrame the current in the\n  // stack until the scope is destroyed.\n  struct Scope {\n    v8::Isolate* isolate;\n    kj::Maybe<AsyncContextFrame&> prior;\n    // If frame is nullptr, the root frame is assumed.\n    Scope(Lock& js, kj::Maybe<AsyncContextFrame&> frame = kj::none);\n    // If frame is nullptr, the root frame is assumed.\n    Scope(v8::Isolate* isolate, kj::Maybe<AsyncContextFrame&> frame = kj::none);\n    // If frame is nullptr, the root frame is assumed.\n    Scope(Lock& js, kj::Maybe<Ref<AsyncContextFrame>>& frame);\n    ~Scope() noexcept(false);\n    KJ_DISALLOW_COPY(Scope);\n  };\n\n  // Retrieves the value that is associated with the given key.\n  kj::Maybe<Value&> get(StorageKey& key);\n\n  // Gets an opaque JavaScript Object wrapper object for this frame. If a wrapper\n  // does not currently exist, one is created.\n  v8::Local<v8::Object> getJSWrapper(v8::Isolate* isolate);\n\n  // Gets an opaque JavaScript Object wrapper object for this frame. If a wrapper\n  // does not currently exist, one is created.\n  v8::Local<v8::Object> getJSWrapper(Lock& js);\n\n  // Creates a new AsyncContextFrame with a new value for the given\n  // StorageKey and sets that frame as current for as long as the StorageScope\n  // is alive.\n  struct StorageScope {\n    Ref<AsyncContextFrame> frame;\n    // Note that the scope here holds a bare ref to the AsyncContextFrame so it\n    // is important that these member fields stay in the correct cleanup order.\n    Scope scope;\n\n    StorageScope(Lock& js, StorageKey& key, Value store);\n    KJ_DISALLOW_COPY(StorageScope);\n  };\n\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"AsyncContextFrame\"_kjc;\n  }\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(AsyncContextFrame);\n  }\n  void jsgGetMemoryInfo(MemoryTracker& tracker) const override {\n    Wrappable::jsgGetMemoryInfo(tracker);\n    tracker.trackField(\"storage\", storage);\n  }\n\n private:\n  struct StorageEntryCallbacks {\n    StorageKey& keyForRow(StorageEntry& entry) const {\n      return *entry.key;\n    }\n\n    bool matches(const StorageEntry& entry, StorageKey& key) const {\n      return entry.key.get() == &key;\n    }\n\n    uint hashCode(StorageKey& key) const {\n      return key.hashCode();\n    }\n  };\n\n  using Storage = kj::Table<StorageEntry, kj::HashIndex<StorageEntryCallbacks>>;\n  Storage storage;\n\n  void jsgVisitForGc(GcVisitor& visitor) override;\n\n  friend struct StorageScope;\n  friend class IsolateBase;\n};\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/buffersource-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"buffersource.h\"\n#include \"jsg-test.h\"\n\n// ========================================================================================\nnamespace workerd::jsg::test {\nnamespace {\n\nV8System v8System;\n\nstruct BufferSourceContext: public jsg::Object, public jsg::ContextGlobal {\n  BufferSource takeBufferSource(BufferSource buf) {\n    auto ptr = buf.asArrayPtr();\n    KJ_ASSERT(!buf.isDetached());\n    KJ_ASSERT(buf.size() == 8);\n    KJ_ASSERT(ptr[0] == 0);\n\n    ptr[0] = 1;\n    KJ_ASSERT(ptr[0] == 1);\n\n    return kj::mv(buf);\n  }\n\n  BufferSource takeUint8Array(Lock& js, BufferSource buf) {\n    // A BufferSource that is initially attached can be detached, releasing the original\n    // object, then recreated as a new instance of the same type of JS object.\n    KJ_ASSERT(!buf.isDetached());\n    auto handle = buf.getHandle(js);\n    KJ_ASSERT(handle->IsUint8Array());\n    KJ_ASSERT(handle.As<v8::Uint8Array>()->ByteLength() > 0);\n\n    // Detaching removes the BackingStore from the BufferSource,\n    // rendering the BufferSource useless.\n    auto backingStore = buf.detach(js);\n    KJ_ASSERT(buf.isDetached());\n    KJ_ASSERT(handle.As<v8::Uint8Array>()->ByteLength() == 0);\n\n    // We can create a new view over the same shared backing store.\n    auto newView = backingStore.getTypedView<v8::DataView>();\n    auto dataView = newView.createHandle(js);\n    KJ_ASSERT(dataView->IsDataView());\n\n    // We can create a new BufferSource from the detached BackingStore.\n    return BufferSource(js, kj::mv(backingStore));\n  }\n\n  BufferSource makeBufferSource(jsg::Lock& js) {\n    return BufferSource(js, BackingStore::from(js, kj::arr<kj::byte>(1, 2, 3)));\n  }\n\n  BufferSource makeArrayBuffer(jsg::Lock& js) {\n    return BufferSource(js, BackingStore::alloc<v8::ArrayBuffer>(js, 3));\n  }\n\n  bool doTest(jsg::Lock& js, jsg::BufferSource buf) {\n    buf.asArrayPtr()[0] = 1;\n    buf.asArrayPtr()[1] = 2;\n    buf.asArrayPtr()[2] = 3;\n    buf.asArrayPtr()[3] = 4;\n    buf.asArrayPtr()[4] = 5;\n    buf.asArrayPtr()[5] = 6;\n    buf.asArrayPtr()[6] = 7;\n    buf.asArrayPtr()[7] = 8;\n\n    auto ptr = buf.asArrayPtr<uint32_t>();\n    KJ_ASSERT(ptr.size() == 2);\n    KJ_ASSERT(ptr[0] == 0x04030201);\n    KJ_ASSERT(ptr[1] == 0x08070605);\n    return true;\n  }\n\n  JSG_RESOURCE_TYPE(BufferSourceContext) {\n    JSG_METHOD(takeBufferSource);\n    JSG_METHOD(takeUint8Array);\n    JSG_METHOD(makeBufferSource);\n    JSG_METHOD(makeArrayBuffer);\n    JSG_METHOD(doTest);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(BufferSourceIsolate, BufferSourceContext);\n\nKJ_TEST(\"BufferSource works\") {\n  Evaluator<BufferSourceContext, BufferSourceIsolate> e(v8System);\n\n  // By default, a BufferSource handle is created as a DataView\n  e.expectEval(\"makeBufferSource() instanceof Uint8Array\", \"boolean\", \"true\");\n\n  // ... but can be other types also\n  e.expectEval(\"makeArrayBuffer() instanceof ArrayBuffer\", \"boolean\", \"true\");\n\n  e.expectEval(\n      \"const ab = new ArrayBuffer(9); takeBufferSource(new Uint8Array(ab, 1, 8)).byteLength === 8\",\n      \"boolean\", \"true\");\n\n  e.expectEval(\"const ab = new ArrayBuffer(8); takeBufferSource(ab) === ab\", \"boolean\", \"true\");\n\n  e.expectEval(\"const ab = new Uint8Array(8); takeBufferSource(ab) === ab\", \"boolean\", \"true\");\n\n  e.expectEval(\"const ab = new Uint16Array(4); takeBufferSource(ab) === ab\", \"boolean\", \"true\");\n\n  e.expectEval(\"const ab = new Uint32Array(2); takeBufferSource(ab) === ab\", \"boolean\", \"true\");\n\n  e.expectEval(\"const ab = new BigInt64Array(1); takeBufferSource(ab) === ab\", \"boolean\", \"true\");\n\n  e.expectEval(\"const ab = new Float16Array(4); takeBufferSource(ab) === ab\", \"boolean\", \"true\");\n\n  e.expectEval(\"const ab = new Float32Array(2); takeBufferSource(ab) === ab\", \"boolean\", \"true\");\n\n  e.expectEval(\"const ab = new Float64Array(1); takeBufferSource(ab) === ab\", \"boolean\", \"true\");\n\n  e.expectEval(\"const ab = new ArrayBuffer(4); \"\n               \"const u8 = new Uint8Array(ab, 1, 1);\"\n               \"const u2 = takeUint8Array(u8);\"\n               \"u8.byteLength === 0 && u2.byteLength === 1 && u2 instanceof Uint8Array && \"\n               \"u2.buffer.byteLength === 4 && u2.byteOffset === 1 && u8 !== u2\",\n      \"boolean\", \"true\");\n\n  e.expectEval(\"const buf = new Uint8Array(12); doTest(buf.subarray(4))\", \"boolean\", \"true\");\n}\n\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/buffersource.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"buffersource.h\"\n\nnamespace workerd::jsg {\n\nnamespace {\nauto getBacking(auto& handle) {\n  auto buffer = handle->IsArrayBuffer() ? handle.template As<v8::ArrayBuffer>()\n                                        : handle.template As<v8::ArrayBufferView>()->Buffer();\n  return buffer->GetBackingStore();\n}\n\nsize_t getByteLength(auto& handle) {\n  return handle->IsArrayBuffer() ? handle.template As<v8::ArrayBuffer>()->ByteLength()\n                                 : handle.template As<v8::ArrayBufferView>()->ByteLength();\n}\n\nsize_t getByteOffset(auto& handle) {\n  return handle->IsArrayBuffer() ? 0 : handle.template As<v8::ArrayBufferView>()->ByteOffset();\n}\n\nauto determineElementSize(auto& handle) {\n#define V(Type, size, _)                                                                           \\\n  if (handle->Is##Type()) return size;\n  JSG_ARRAY_BUFFER_VIEW_TYPES(V)\n#undef V\n  KJ_ASSERT(handle->IsDataView() || handle->IsArrayBuffer());\n  return 1;\n}\n\nbool isDetachable(auto handle) {\n  auto buffer = handle->IsArrayBuffer() ? handle.template As<v8::ArrayBuffer>()\n                                        : handle.template As<v8::ArrayBufferView>()->Buffer();\n  return buffer->IsDetachable();\n}\n\nbool determineIsIntegerType(auto& handle) {\n#define V(Type, _, integerView)                                                                    \\\n  if (handle->Is##Type()) return integerView;\n  JSG_ARRAY_BUFFER_VIEW_TYPES(V);\n#undef V\n  return false;\n}\n\nValue createHandle(Lock& js, BackingStore& backingStore) {\n  return js.withinHandleScope([&] { return js.v8Ref(backingStore.createHandle(js)); });\n}\n\n}  // namespace\n\nvoid GcVisitor::visit(BufferSource& value) {\n  visit(value.handle);\n}\n\nBackingStore::BackingStore(std::shared_ptr<v8::BackingStore> backingStore,\n    size_t byteLength,\n    size_t byteOffset,\n    size_t elementSize,\n    BufferSourceViewConstructor ctor,\n    bool integerType)\n    : backingStore(kj::mv(backingStore)),\n      byteLength(byteLength),\n      byteOffset(byteOffset),\n      elementSize(elementSize),\n      ctor(ctor),\n      integerType(integerType) {\n  KJ_REQUIRE(this->backingStore != nullptr);\n  KJ_REQUIRE(this->byteLength <= this->backingStore->ByteLength());\n  KJ_REQUIRE(this->byteLength % this->elementSize == 0,\n      kj::str(\"byteLength must be a multiple of \", this->elementSize, \".\"));\n}\n\nbool BackingStore::operator==(const BackingStore& other) {\n  return backingStore == other.backingStore && byteLength == other.byteLength &&\n      byteOffset == other.byteOffset;\n}\n\nkj::Maybe<BufferSource> BufferSource::tryAlloc(Lock& js, size_t size) {\n  v8::Local<v8::ArrayBuffer> buffer;\n  if (v8::ArrayBuffer::MaybeNew(js.v8Isolate, size).ToLocal(&buffer)) {\n    return BufferSource(js, v8::Uint8Array::New(buffer, 0, size).As<v8::Value>());\n  }\n  return kj::none;\n}\n\nkj::Maybe<BufferSource> BufferSource::tryAllocUnsafe(Lock& js, size_t size) {\n  v8::Local<v8::ArrayBuffer> buffer;\n  if (v8::ArrayBuffer::MaybeNew(\n          js.v8Isolate, size, v8::BackingStoreInitializationMode::kUninitialized)\n          .ToLocal(&buffer)) {\n    return BufferSource(js, v8::Uint8Array::New(buffer, 0, size).As<v8::Value>());\n  }\n  return kj::none;\n}\n\nBufferSource::BufferSource(Lock& js, v8::Local<v8::Value> handle)\n    : handle(js.v8Ref(handle)),\n      maybeBackingStore(BackingStore(getBacking(handle),\n          getByteLength(handle),\n          getByteOffset(handle),\n          determineElementSize(handle),\n          determineConstructor(handle),\n          determineIsIntegerType(handle))) {}\n\nBufferSource::BufferSource(Lock& js, BackingStore&& backingStore)\n    : handle(createHandle(js, backingStore)),\n      maybeBackingStore(kj::mv(backingStore)) {}\n\nBackingStore BufferSource::detach(Lock& js, kj::Maybe<v8::Local<v8::Value>> maybeKey) {\n  auto theHandle = handle.getHandle(js);\n  JSG_REQUIRE(isDetachable(theHandle), TypeError,\n      \"This BufferSource does not have a detachable backing store.\");\n  auto backingStore = kj::mv(JSG_REQUIRE_NONNULL(\n      maybeBackingStore, TypeError, \"This BufferSource has already been detached.\"));\n  maybeBackingStore = kj::none;\n\n  v8::Local<v8::Value> key = maybeKey.orDefault(v8::Local<v8::Value>());\n\n  auto buffer = theHandle->IsArrayBuffer() ? theHandle.As<v8::ArrayBuffer>()\n                                           : theHandle.As<v8::ArrayBufferView>()->Buffer();\n  jsg::check(buffer->Detach(key));\n\n  return kj::mv(backingStore);\n}\n\nbool BufferSource::canDetach(Lock& js) {\n  if (isDetached()) return false;\n  return isDetachable(handle.getHandle(js));\n}\n\nv8::Local<v8::Value> BufferSource::getHandle(Lock& js) {\n  return handle.getHandle(js);\n}\n\nvoid BufferSource::setDetachKey(Lock& js, v8::Local<v8::Value> key) {\n  auto handle = getHandle(js);\n  auto buffer = handle->IsArrayBuffer() ? handle.As<v8::ArrayBuffer>()\n                                        : handle.As<v8::ArrayBufferView>()->Buffer();\n  buffer->SetDetachKey(key);\n}\n\nBufferSource BufferSource::wrap(\n    Lock& js, void* data, size_t size, BackingStore::Disposer disposer, void* ctx) {\n  return BufferSource(js, BackingStore::wrap(data, size, disposer, ctx));\n}\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/buffersource.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"jsg.h\"\n\n#include <v8-typed-array.h>\n\nnamespace workerd::jsg {\n\n#define JSG_ARRAY_BUFFER_VIEW_TYPES(V)                                                             \\\n  V(Uint8Array, 1, true)                                                                           \\\n  V(Uint8ClampedArray, 1, true)                                                                    \\\n  V(Uint16Array, 2, true)                                                                          \\\n  V(Uint32Array, 4, true)                                                                          \\\n  V(Int8Array, 1, true)                                                                            \\\n  V(Int16Array, 2, true)                                                                           \\\n  V(Int32Array, 4, true)                                                                           \\\n  V(Float16Array, 2, false)                                                                        \\\n  V(Float32Array, 4, false)                                                                        \\\n  V(Float64Array, 8, false)                                                                        \\\n  V(BigInt64Array, 8, true)                                                                        \\\n  V(BigUint64Array, 8, true)\n\ntemplate <typename T>\nconcept BufferSourceType = requires(\n    T a) { kj::isSameType<v8::ArrayBuffer, T>() || std::is_base_of_v<v8::ArrayBufferView, T>; };\n\ntemplate <BufferSourceType T>\nstatic constexpr size_t getBufferSourceElementSize() {\n  if constexpr (kj::isSameType<v8::Uint8Array, T>() || kj::isSameType<v8::Uint8ClampedArray, T>() ||\n      kj::isSameType<v8::Int8Array, T>() || kj::isSameType<v8::DataView, T>() ||\n      kj::isSameType<v8::ArrayBuffer, T>() || kj::isSameType<v8::ArrayBufferView, T>() ||\n      kj::isSameType<v8::TypedArray, T>()) {\n    return 1;\n  }\n#define V(Type, size, _)                                                                           \\\n  else if constexpr (kj::isSameType<v8::Type, T>()) {                                              \\\n    return size;                                                                                   \\\n  }\n  JSG_ARRAY_BUFFER_VIEW_TYPES(V)\n#undef V\n  asm(\"no_matching_buffer_view_type\\n\");\n}\n\ntemplate <BufferSourceType T>\nstatic constexpr size_t checkIsIntegerType() {\n  if constexpr (kj::isSameType<v8::ArrayBuffer, T>() || kj::isSameType<v8::DataView, T>() ||\n      kj::isSameType<v8::ArrayBufferView, T>()) {\n    return false;\n  } else if constexpr (kj::isSameType<v8::TypedArray, T>()) {\n    return true;\n  }\n#define V(Type, _, res)                                                                            \\\n  else if constexpr (kj::isSameType<v8::Type, T>()) {                                              \\\n    return res;                                                                                    \\\n  }\n  JSG_ARRAY_BUFFER_VIEW_TYPES(V)\n#undef V\n  asm(\"no_matching_buffer_view_type\\n\");\n}\n\nclass BufferSource;\nclass BackingStore;\nusing BufferSourceViewConstructor = v8::Local<v8::Value> (*)(Lock&, BackingStore&);\n\n// The jsg::BackingStore wraps a v8::BackingStore and retains information about the\n// type of ArrayBuffer or ArrayBufferView to which it is associated. Namely, it records\n// the byte length, offset, element size, and constructor type allowing the view to be\n// recreated.\n//\n// Once allocated, the BackingStore can be safely used outside of the isolate lock.  If\n// using V8 sandboxing, it cannot be passed to another isolate unless that isolate is\n// part of the same IsolateGroup.\nclass BackingStore {\n public:\n  template <BufferSourceType T = v8::Uint8Array>\n  // This requires the js lock to ensure the backing is allocated in the correct\n  // V8 sandbox for the isolate.\n  static BackingStore from(Lock& js, kj::Array<kj::byte> data) {\n    // Creates a new BackingStore that takes over ownership of the given kj::Array.\n    // The bytes may be moved if they are not inside the sandbox already.\n    size_t size = data.size();\n    if (js.v8Isolate->GetGroup().SandboxContains(data.begin())) {\n      auto ptr = new kj::Array<byte>(kj::mv(data));\n      return BackingStore(v8::ArrayBuffer::NewBackingStore(ptr->begin(), size,\n                              [](void*, size_t, void* ptr) {\n        delete reinterpret_cast<kj::Array<byte>*>(ptr);\n      }, ptr),\n          size, 0, getBufferSourceElementSize<T>(), construct<T>, checkIsIntegerType<T>());\n    } else {\n      auto backingStore = js.allocBackingStore(size, Lock::AllocOption::UNINITIALIZED);\n\n      auto result = BackingStore(kj::mv(backingStore), size, 0, getBufferSourceElementSize<T>(),\n          construct<T>, checkIsIntegerType<T>());\n      memcpy(result.asArrayPtr().begin(), data.begin(), size);\n      return result;\n    }\n  }\n\n  // Creates a new BackingStore of the given size.\n  template <BufferSourceType T = v8::Uint8Array>\n  static BackingStore alloc(\n      Lock& js, size_t size, Lock::AllocOption init_mode = Lock::AllocOption::ZERO_INITIALIZED) {\n    return BackingStore(js.allocBackingStore(size, init_mode), size, 0,\n        getBufferSourceElementSize<T>(), construct<T>, checkIsIntegerType<T>());\n  }\n\n  using Disposer = void(void*, size_t, void*);\n\n  // Creates and returns a BackingStore that wraps an external data pointer\n  // with a custom disposer.\n  template <BufferSourceType T = v8::Uint8Array>\n  static BackingStore wrap(void* data, size_t size, Disposer disposer, void* ctx) {\n    return BackingStore(v8::ArrayBuffer::NewBackingStore(data, size, disposer, ctx), size, 0,\n        getBufferSourceElementSize<T>(), construct<T>, checkIsIntegerType<T>());\n  }\n\n  explicit BackingStore(std::shared_ptr<v8::BackingStore> backingStore,\n      size_t byteLength,\n      size_t byteOffset,\n      size_t elementSize,\n      BufferSourceViewConstructor ctor,\n      bool integerType);\n\n  BackingStore(BackingStore&& other) = default;\n  BackingStore& operator=(BackingStore&& other) = default;\n  KJ_DISALLOW_COPY(BackingStore);\n\n  template <typename T = kj::byte>\n  inline kj::ArrayPtr<T> asArrayPtr() KJ_LIFETIMEBOUND {\n    KJ_ASSERT(backingStore != nullptr, \"Invalid access after move.\");\n    KJ_ASSERT(byteLength % sizeof(T) == 0);\n    kj::byte* data = static_cast<kj::byte*>(backingStore->Data());\n    return kj::ArrayPtr<T>(reinterpret_cast<T*>(data + byteOffset), byteLength / sizeof(T));\n  }\n\n  template <typename T = kj::byte>\n  inline operator kj::ArrayPtr<T>() KJ_LIFETIMEBOUND {\n    return asArrayPtr<T>();\n  }\n\n  bool operator==(const BackingStore& other);\n\n  template <typename T = kj::byte>\n  inline const kj::ArrayPtr<const T> asArrayPtr() const KJ_LIFETIMEBOUND {\n    KJ_ASSERT(backingStore != nullptr, \"Invalid access after move.\");\n    KJ_ASSERT(byteLength % sizeof(T) == 0);\n    return kj::ArrayPtr<T>(\n        static_cast<T*>(backingStore->Data()) + byteOffset, byteLength / sizeof(T));\n  }\n\n  template <typename T = kj::byte>\n  inline operator const kj::ArrayPtr<const T>() const KJ_LIFETIMEBOUND {\n    return asArrayPtr<T>();\n  }\n\n  inline size_t size() const {\n    return byteLength;\n  };\n  inline size_t getOffset() const {\n    return byteOffset;\n  }\n  inline size_t getElementSize() const {\n    return elementSize;\n  }\n  inline bool isIntegerType() const {\n    return integerType;\n  }\n\n  // Creates a new BackingStore as a view over the same underlying v8::BackingStore\n  // but with different handle type information. This is required, for instance, in\n  // use cases like the Streams API where we have to be able to surface a Uint8Array\n  // view over the BackingStore to fulfill a BYOB read while maintaining the original\n  // type information to recreate the original type of view once the read is complete.\n  template <BufferSourceType T = v8::Uint8Array>\n  BackingStore getTypedView() {\n    return BackingStore(backingStore, byteLength, byteOffset, getBufferSourceElementSize<T>(),\n        construct<T>, checkIsIntegerType<T>());\n  }\n\n  template <BufferSourceType T = v8::Uint8Array>\n  BackingStore getTypedViewSlice(size_t start, size_t end) {\n    KJ_ASSERT(start <= end);\n    auto length = end - start;\n    auto startOffset = byteOffset + start;\n    KJ_ASSERT(length <= byteLength);\n    KJ_ASSERT(startOffset <= backingStore->ByteLength());\n    KJ_ASSERT(startOffset + length <= backingStore->ByteLength());\n    return BackingStore(backingStore, length, startOffset, getBufferSourceElementSize<T>(),\n        construct<T>, checkIsIntegerType<T>());\n  }\n\n  inline v8::Local<v8::Value> createHandle(Lock& js) {\n    return ctor(js, *this);\n  }\n\n  // Shrinks the effective size of the backing store by a number of bytes off\n  // the front of the data. Useful when incrementally consuming the data as\n  // we do in the streams implementation.\n  inline void consume(size_t bytes) {\n    KJ_ASSERT(bytes <= byteLength);\n    byteOffset += bytes;\n    byteLength -= bytes;\n  }\n\n  // Shrinks the effective size of the backing store by a number of bytes off\n  // the end of the data. Useful when a more limited view of the buffer is\n  // required (such as when fulfilling partial stream reads).\n  inline void trim(size_t bytes) {\n    KJ_ASSERT(bytes <= byteLength);\n    byteLength -= bytes;\n  }\n\n  // Similar to trim except that it explicitly sets the byte length to a value\n  // equal to or less than the current byte length.\n  inline void limit(size_t bytes) {\n    KJ_ASSERT(bytes <= byteLength);\n    byteLength = bytes;\n  }\n\n  inline BackingStore clone() {\n    return BackingStore(backingStore, byteLength, byteOffset, elementSize, ctor, integerType);\n  }\n\n  template <class T = v8::Uint8Array>\n  inline BackingStore copy(jsg::Lock& js) {\n    if (byteLength == 0) return BackingStore::alloc<T>(js, 0);\n    auto dest = BackingStore::alloc<T>(js, byteLength);\n    memcpy(dest.asArrayPtr().begin(), asArrayPtr().begin(), byteLength);\n    return dest;\n  }\n\n  JSG_MEMORY_INFO(BackingStore) {\n    tracker.trackFieldWithSize(\"buffer\", size());\n  }\n\n private:\n  std::shared_ptr<v8::BackingStore> backingStore;\n  size_t byteLength;\n  size_t byteOffset;\n  size_t elementSize;\n\n  // The ctor here is a pointer to a static template function that can create a\n  // new type-specific instance of the JavaScript ArrayBuffer or ArrayBufferView wrapper\n  // for the backing store. The specific type of constructor to store is determined\n  // when the BufferSource instance is created and it is used only if getHandle() is\n  // called on a BufferSource that has been detached.\n  BufferSourceViewConstructor ctor;\n\n  bool integerType;\n\n  template <BufferSourceType T>\n  static v8::Local<v8::Value> construct(Lock& js, BackingStore& store) {\n    if constexpr (kj::isSameType<v8::ArrayBuffer, T>()) {\n      return v8::ArrayBuffer::New(js.v8Isolate, store.backingStore);\n    } else if constexpr (kj::isSameType<v8::ArrayBufferView, T>()) {\n      return v8::DataView::New(v8::ArrayBuffer::New(js.v8Isolate, store.backingStore),\n          store.byteOffset, store.byteLength);\n    } else if constexpr (kj::isSameType<v8::TypedArray, T>()) {\n      return v8::Uint8Array::New(v8::ArrayBuffer::New(js.v8Isolate, store.backingStore),\n          store.byteOffset, store.byteLength);\n    } else {\n      return T::New(v8::ArrayBuffer::New(js.v8Isolate, store.backingStore), store.byteOffset,\n          store.byteLength / store.elementSize);\n    }\n  }\n\n  friend class BufferSource;\n};\n\n// A BufferSource is an abstraction for v8::ArrayBuffer and v8::ArrayBufferView types.\n// It has a couple of significant features relative to the alternative mapping between\n// kj::Array<kj::byte> and ArrayBuffer/ArrayBufferView:\n//\n//  * A BufferSource created from an ArrayBuffer/ArrayBufferView maintains a reference\n//    to JavaScript object, ensuring that when the BufferSource is passed back\n//    out to JavaScript, the same object will be returned.\n//  * A BufferSource can detach the BackingStore from the ArrayBuffer/ArrayBufferView.\n//    When doing so, the BackingStore is removed from the BufferSource and the association\n//    with the ArrayBuffer/ArrayBufferView is severed.\n//\n// When an object holds a reference to a BufferSource (e.g. as a member variable), it\n// must implement visitForGc and ensure the BufferSource is properly visited,\n//\n// As a side note, the name \"BufferSource\" comes from the Web IDL spec.\n//\n// How to use it:\n//\n// In methods that are exposed to JavaScript, specify jsg::BufferSource as the type:\n// e.g.\n//\n//   class MyAPiObject: public jsg::Object {\n//   public:\n//     jsg::BufferSource foo(jsg::Lock& js, jsg::BufferSource source) {\n//       // While the BufferSource is attached, you can access the data as an\n//       // kj::ArrayPtr...\n//       {\n//         auto ptr = kj::ArrayPtr<kj::byte>(source);\n//       }\n//\n//       // Or, you can detach the jsg::BackingStore from the BufferSource.\n//       auto backingStore = source.detach();\n//       auto ptr = kj::ArrayPtr<kj::byte>(backingStore);\n//       // Do something with ptr...\n//       return BufferSource(js, kj::mv(backingStore));\n//     }\n//   };\nclass BufferSource {\n public:\n  static kj::Maybe<BufferSource> tryAlloc(Lock& js, size_t size);\n\n  // The unsafe variant does not initialize the allocated memory. Use with caution!\n  static kj::Maybe<BufferSource> tryAllocUnsafe(Lock& js, size_t size);\n  static BufferSource wrap(\n      Lock& js, void* data, size_t size, BackingStore::Disposer disposer, void* ctx);\n\n  // Create a new BufferSource that takes over ownership of the given BackingStore.\n  explicit BufferSource(Lock& js, BackingStore&& backingStore);\n\n  // Create a BufferSource from the given JavaScript handle.\n  explicit BufferSource(Lock& js, v8::Local<v8::Value> handle);\n\n  BufferSource(BufferSource&&) = default;\n  BufferSource& operator=(BufferSource&&) = default;\n\n  KJ_DISALLOW_COPY(BufferSource);\n\n  // True if the BackingStore has been removed from this BufferSource.\n  inline bool isDetached() const {\n    return maybeBackingStore == kj::none;\n  }\n\n  bool canDetach(Lock& js);\n\n  // Removes the BackingStore from the BufferSource and severs its connection to\n  // the ArrayBuffer/ArrayBufferView handle.\n  // It's worth mentioning that detach can throw application-visible exceptions\n  // in the case the ArrayBuffer cannot be detached. Any detaching should be\n  // performed as early as possible in an API method implementation.\n  BackingStore detach(Lock& js, kj::Maybe<v8::Local<v8::Value>> maybeKey = kj::none);\n\n  v8::Local<v8::Value> getHandle(Lock& js);\n\n  template <typename T = kj::byte>\n  inline kj::ArrayPtr<T> asArrayPtr() KJ_LIFETIMEBOUND {\n    return KJ_ASSERT_NONNULL(maybeBackingStore).asArrayPtr<T>();\n  }\n\n  template <typename T = kj::byte>\n  inline operator kj::ArrayPtr<T>() KJ_LIFETIMEBOUND {\n    return asArrayPtr<T>();\n  }\n\n  template <typename T = kj::byte>\n  inline const kj::ArrayPtr<const T> asArrayPtr() const KJ_LIFETIMEBOUND {\n    return KJ_ASSERT_NONNULL(maybeBackingStore).asArrayPtr<T>();\n  }\n\n  template <typename T = kj::byte>\n  inline operator const kj::ArrayPtr<const T>() const KJ_LIFETIMEBOUND {\n    return asArrayPtr<T>();\n  }\n\n  inline size_t size() const {\n    return KJ_ASSERT_NONNULL(maybeBackingStore).size();\n  };\n\n  inline kj::Maybe<size_t> underlyingArrayBufferSize(Lock& js) {\n    if (isDetached()) {\n      return kj::none;\n    }\n    auto h = getHandle(js);\n    if (h->IsArrayBuffer()) {\n      return h.As<v8::ArrayBuffer>()->ByteLength();\n    } else if (h->IsArrayBufferView()) {\n      return h.As<v8::ArrayBufferView>()->Buffer()->ByteLength();\n    }\n    KJ_UNREACHABLE;\n  }\n\n  inline size_t getOffset() const {\n    return KJ_ASSERT_NONNULL(maybeBackingStore).getOffset();\n  }\n\n  inline size_t getElementSize() const {\n    return KJ_ASSERT_NONNULL(maybeBackingStore).getElementSize();\n  }\n\n  // Some standard APIs that use BufferSource / ArrayBufferView are limited to just\n  // supported \"Integer-type ArrayBufferViews\". As a convenience, when the BufferSource\n  // is created, we record whether or not the type qualifies as an integer type.\n  inline bool isIntegerType() const {\n    return KJ_ASSERT_NONNULL(maybeBackingStore).isIntegerType();\n  }\n\n  // Sets the detach key that must be provided with the detach(...) method\n  // to successfully detach the backing store.\n  void setDetachKey(Lock& js, v8::Local<v8::Value> key);\n\n  // Shrinks the effective size of the backing store by a number of bytes off\n  // the end of the data. Useful when a more limited view of the buffer is\n  // required (such as when fulfilling partial stream reads).\n  inline void trim(jsg::Lock& js, size_t bytes) {\n    auto& backing = KJ_ASSERT_NONNULL(maybeBackingStore);\n    backing.trim(bytes);\n    // When trimming, we need to also update the handle to reflect the new size.\n    handle = js.v8Ref(backing.createHandle(js));\n  }\n\n  inline BufferSource clone(jsg::Lock& js) {\n    return BufferSource(js, KJ_ASSERT_NONNULL(maybeBackingStore).clone());\n  }\n\n  template <class T = v8::Uint8Array>\n  inline BufferSource copy(jsg::Lock& js) {\n    KJ_IF_SOME(backing, maybeBackingStore) {\n      return BufferSource(js, backing.copy<T>(js));\n    }\n    return BufferSource(js, BackingStore::alloc<T>(js, 0));\n  }\n\n  inline void setToZero() {\n    KJ_IF_SOME(backing, maybeBackingStore) {\n      backing.asArrayPtr().fill(0);\n    }\n  }\n\n  template <BufferSourceType T = v8::Uint8Array>\n  BufferSource getTypedViewSlice(jsg::Lock& js, size_t start, size_t end) {\n    return BufferSource(js, KJ_ASSERT_NONNULL(maybeBackingStore).getTypedViewSlice<T>(start, end));\n  }\n\n  template <BufferSourceType T = v8::Uint8Array>\n  BufferSource getTypedView(jsg::Lock& js) {\n    return BufferSource(js, KJ_ASSERT_NONNULL(maybeBackingStore).getTypedView<T>());\n  }\n\n  JSG_MEMORY_INFO(BufferSource) {\n    tracker.trackField(\"handle\", handle);\n    KJ_IF_SOME(backing, maybeBackingStore) {\n      tracker.trackField(\"backing\", backing);\n    }\n  }\n\n private:\n  Value handle;\n  kj::Maybe<BackingStore> maybeBackingStore;\n\n  static auto determineConstructor(auto& value) {\n    if (value->IsArrayBuffer()) {\n      return BackingStore::construct<v8::ArrayBuffer>;\n    } else if (value->IsDataView()) {\n      return BackingStore::construct<v8::DataView>;\n    }\n#define V(Type, _, __) else if (value->Is##Type()) return BackingStore::construct<v8::Type>;\n    JSG_ARRAY_BUFFER_VIEW_TYPES(V)\n#undef V\n    KJ_UNREACHABLE;\n  }\n\n  friend class BackingStore;\n  friend class GcVisitor;\n};\n\n// TypeWrapper implementation for the BufferSource type.\nclass BufferSourceWrapper {\n public:\n  static constexpr const char* getName(BufferSource*) {\n    return \"BufferSource\";\n  }\n\n  v8::Local<v8::Value> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      BufferSource bufferSource) {\n    return bufferSource.getHandle(js);\n  }\n\n  kj::Maybe<BufferSource> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      BufferSource*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (!handle->IsArrayBuffer() && !handle->IsArrayBufferView()) {\n      return kj::none;\n    }\n    return BufferSource(js, handle);\n  }\n};\n\ninline BufferSource Lock::arrayBuffer(kj::Array<kj::byte> data) {\n  return BufferSource(*this, BackingStore::from<v8::ArrayBuffer>(*this, kj::mv(data)));\n}\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/compile-cache.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#include \"compile-cache.h\"\n\nnamespace workerd::jsg {\n\n// CompileCache::Data\n\nstd::unique_ptr<v8::ScriptCompiler::CachedData> CompileCache::Data::AsCachedData() {\n  return std::make_unique<v8::ScriptCompiler::CachedData>(\n      data, length, v8::ScriptCompiler::CachedData::BufferNotOwned);\n}\n\n// CompileCache\n\nvoid CompileCache::add(\n    kj::StringPtr key, std::shared_ptr<v8::ScriptCompiler::CachedData> cached) const {\n  cache.lockExclusive()->upsert(kj::str(key), Data(kj::mv(cached)), [](auto&, auto&&) {});\n}\n\nkj::Maybe<CompileCache::Data&> CompileCache::find(kj::StringPtr key) const {\n  auto lock = cache.lockExclusive();\n  KJ_IF_SOME(value, lock->find(key)) {\n    if (value.data != nullptr) {\n      return value;\n    }\n  }\n  return kj::none;\n}\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/compile-cache.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <v8-script.h>\n\n#include <kj/map.h>\n#include <kj/mutex.h>\n#include <kj/string.h>\n\nnamespace workerd::jsg {\n\n// The CompileCache is used to hold cached compilation data for built-in JavaScript modules.\n//\n// Importantly, this is a process-lifetime in-memory cache that is only appropriate for\n// built-in modules.\n//\n// The memory-safety of this cache depends on the assumption that entries are never removed\n// or replaced. If things are ever changed such that entries are removed/replaced, then\n// we'd likely need to have find return an atomic refcount or something similar.\nclass CompileCache {\n public:\n  class Data {\n   public:\n    Data(): data(nullptr), length(0), owningPtr(nullptr) {};\n    explicit Data(std::shared_ptr<v8::ScriptCompiler::CachedData> cached_data)\n        : data(cached_data->data),\n          length(cached_data->length),\n          owningPtr(cached_data) {};\n\n    // Returns a v8::ScriptCompiler::CachedData corresponding to this\n    // CompileCache::Data. The lifetime of the returned\n    // v8::ScriptCompiler::CachedData must not outlive that of the data.\n    std::unique_ptr<v8::ScriptCompiler::CachedData> AsCachedData();\n\n    const uint8_t* data;\n    size_t length;\n\n   private:\n    std::shared_ptr<void> owningPtr;\n  };\n\n  void add(kj::StringPtr key, std::shared_ptr<v8::ScriptCompiler::CachedData> cached) const;\n  kj::Maybe<Data&> find(kj::StringPtr key) const;\n\n  static const CompileCache& get() {\n    static const CompileCache instance;\n    return instance;\n  }\n\n private:\n  // The key is the address of the static global that was compiled to produce the CachedData.\n  kj::MutexGuarded<kj::HashMap<kj::String, Data>> cache;\n};\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/dom-exception-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"dom-exception.h\"\n#include \"jsg-test.h\"\n\nnamespace workerd::jsg::test {\nnamespace {\n\nV8System v8System;\n\nstruct DOMExceptionContext: public Object, public ContextGlobal {\n  JSG_RESOURCE_TYPE(DOMExceptionContext) {\n    JSG_NESTED_TYPE(DOMException);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(DOMExceptionIsolate, DOMExceptionContext);\n\nKJ_TEST(\"DOMException's prototype is ErrorPrototype\") {\n  Evaluator<DOMExceptionContext, DOMExceptionIsolate> e(v8System);\n  e.expectEval(\n      \"Object.getPrototypeOf(DOMException.prototype) === Error.prototype\", \"boolean\", \"true\");\n}\n\nKJ_TEST(\"DOMException has a stack property\") {\n  Evaluator<DOMExceptionContext, DOMExceptionIsolate> e(v8System);\n  e.expectEval(\"function throwError() { throw new DOMException('test error') }\\n\"\n               \"try { throwError() } catch (e) { e.stack }\",\n      \"string\",\n      \"Error: test error\\n\"\n      \"    at throwError (<anonymous>:1:31)\\n\"\n      \"    at <anonymous>:2:7\");\n}\n\nKJ_TEST(\"DOMException has legacy code constants\") {\n  Evaluator<DOMExceptionContext, DOMExceptionIsolate> e(v8System);\n  // Test a subset of error codes that commonly appear in web APIs.\n  e.expectEval(\"DOMException.INDEX_SIZE_ERR === 1\", \"boolean\", \"true\");\n  e.expectEval(\"DOMException.NOT_SUPPORTED_ERR === 9\", \"boolean\", \"true\");\n  e.expectEval(\"DOMException.SYNTAX_ERR === 12\", \"boolean\", \"true\");\n  e.expectEval(\"DOMException.INVALID_ACCESS_ERR === 15\", \"boolean\", \"true\");\n  e.expectEval(\"DOMException.TYPE_MISMATCH_ERR === 17\", \"boolean\", \"true\");\n  e.expectEval(\"DOMException.QUOTA_EXCEEDED_ERR === 22\", \"boolean\", \"true\");\n  e.expectEval(\"DOMException.DATA_CLONE_ERR === 25\", \"boolean\", \"true\");\n}\n\nKJ_TEST(\"jsg::Lock domException\") {\n  Evaluator<DOMExceptionContext, DOMExceptionIsolate> e(v8System);\n  e.getIsolate().runInLockScope([&](DOMExceptionIsolate::Lock& lock) {\n    JSG_WITHIN_CONTEXT_SCOPE(lock,\n        lock.template newContext<DOMExceptionContext>().getHandle(lock.v8Isolate),\n        [&](jsg::Lock& js) {\n      Ref<DOMException> exception = js.domException(kj::str(\"foo\"), kj::str(\"bar\"));\n\n      // The DOMException object should have the JS handle created already.\n      auto handle = JsObject(KJ_ASSERT_NONNULL(exception.tryGetHandle(js)));\n\n      // And it will have the stack.\n      KJ_ASSERT(handle.has(js, \"stack\"_kj));\n\n      KJ_ASSERT(exception->getName() == \"foo\"_kj);\n      KJ_ASSERT(exception->getMessage() == \"bar\"_kj);\n    });\n  });\n}\n\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/dom-exception.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"dom-exception.h\"\n\n#include \"jsvalue.h\"\n#include \"ser.h\"\n\n#include <workerd/jsg/memory.h>\n\n#include <kj/string.h>\n\n#include <map>\n\nnamespace workerd::jsg {\n\nRef<DOMException> DOMException::constructor(const v8::FunctionCallbackInfo<v8::Value>& args,\n    Optional<kj::String> message,\n    Optional<kj::String> name) {\n  Lock& js = Lock::from(args.GetIsolate());\n  kj::String errMessage = kj::mv(message).orDefault([&] { return kj::String(); });\n\n  // V8 gives Error objects a non-standard (but widely known) `stack` property, and Web IDL\n  // requires that DOMException get any non-standard properties that Error gets. Chrome honors\n  // this requirement only for runtime-generated DOMExceptions -- script-generated DOMExceptions\n  // don't get `stack`, even though script-generated Errors do. It's more convenient and, IMO,\n  // more conformant to just give all DOMExceptions a `stack` property.\n  jsg::check(v8::Exception::CaptureStackTrace(js.v8Context(), args.This()));\n\n  // This part is a bit of a hack. By default, the various properties on JavaScript errors\n  // are not enumerable. However, our implementation of DOMException has always defined\n  // them as enumerable, which means just setting the stack above would be a breaking change.\n  // To maintain backwards compat we have to define the stack as enumerable here.\n  v8::PropertyDescriptor prop;\n  prop.set_enumerable(true);\n  v8::Local<v8::String> stackName = js.str(\"stack\"_kjc);\n  jsg::check(args.This()->DefineProperty(js.v8Context(), stackName, prop));\n\n  return js.alloc<DOMException>(\n      kj::mv(errMessage), kj::mv(name).orDefault([] { return kj::str(\"Error\"); }));\n}\n\nkj::StringPtr DOMException::getName() const {\n  return name;\n}\n\nkj::StringPtr DOMException::getMessage() const {\n  return message;\n}\n\nint DOMException::getCode() const {\n  static const std::map<kj::StringPtr, int> legacyCodes{\n#define MAP_ENTRY(name, code, friendlyName) {friendlyName, code},\n    JSG_DOM_EXCEPTION_FOR_EACH_ERROR_NAME(MAP_ENTRY)\n#undef MAP_ENTRY\n  };\n  auto code = legacyCodes.find(name);\n  if (code != legacyCodes.end()) {\n    return code->second;\n  }\n  return 0;\n}\n\nvoid DOMException::serialize(jsg::Lock& js, jsg::Serializer& serializer) {\n  serializer.writeLengthDelimited(name);\n  serializer.writeLengthDelimited(message);\n\n  // It's a bit unfortunate that the stack here ends up also including the name and message\n  // so we end up duplicating some of the information here, but that's OK. It's better to\n  // keep this implementation simple rather than to implement any kind of deduplication.\n  KJ_IF_SOME(stack, this->stack.get(js, \"stack\")) {\n    serializer.writeLengthDelimited(stack);\n  } else {\n    // This branch shouldn't really be taken in the typical case. It's only here\n    // to handle the case where the stack property could not be unwrapped for some\n    // reason. We don't need to treat it as an error case, just set the stack to\n    // the empty string and move on.\n    serializer.writeLengthDelimited(\"\"_kj);\n  }\n}\n\njsg::Ref<DOMException> DOMException::deserialize(\n    jsg::Lock& js, uint tag, jsg::Deserializer& deserializer) {\n  switch (tag) {\n    case SERIALIZATION_TAG_V2: {\n      kj::String name = deserializer.readLengthDelimitedString();\n      kj::String message = deserializer.readLengthDelimitedString();\n      kj::String stack = deserializer.readLengthDelimitedString();\n      return js.domException(kj::mv(name), kj::mv(message), kj::mv(stack));\n    }\n    case SERIALIZATION_TAG: {\n      // This is the original serialization of DOMException. It was only\n      // used for a very short period of time (a matter of weeks) but there's\n      // still a remote chance that someone might use it in some persisted state\n      // somewhere. So let's go ahead and support it.\n      kj::String name = deserializer.readLengthDelimitedString();\n      auto errorForStack = KJ_ASSERT_NONNULL(deserializer.readValue(js).tryCast<JsObject>());\n      kj::String message =\n          KJ_ASSERT_NONNULL(errorForStack.get(js, \"message\"_kj).tryCast<JsString>()).toString(js);\n      kj::String stack =\n          KJ_ASSERT_NONNULL(errorForStack.get(js, \"stack\").tryCast<JsString>()).toString(js);\n      return js.domException(kj::mv(message), kj::mv(name), kj::mv(stack));\n    }\n    default:\n      KJ_UNREACHABLE;\n  }\n}\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/dom-exception.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"jsg.h\"\n\n#define JSG_DOM_EXCEPTION_FOR_EACH_ERROR_NAME(f)                                                   \\\n  f(INDEX_SIZE_ERR, 1, \"IndexSizeError\") f(DOMSTRING_SIZE_ERR, 2, \"DOMStringSizeError\")            \\\n      f(HIERARCHY_REQUEST_ERR, 3, \"HierarchyRequestError\") f(WRONG_DOCUMENT_ERR, 4,                \\\n          \"WrongDocumentError\") f(INVALID_CHARACTER_ERR, 5, \"InvalidCharacterError\")               \\\n          f(NO_DATA_ALLOWED_ERR, 6, \"NoDataAllowedError\")                                          \\\n              f(NO_MODIFICATION_ALLOWED_ERR, 7, \"NoModificationAllowedError\") f(                   \\\n                  NOT_FOUND_ERR, 8, \"NotFoundError\") f(NOT_SUPPORTED_ERR, 9, \"NotSupportedError\")  \\\n                  f(INUSE_ATTRIBUTE_ERR, 10, \"InUseAttributeError\") f(                             \\\n                      INVALID_STATE_ERR, 11, \"InvalidStateError\") f(SYNTAX_ERR, 12, \"SyntaxError\") \\\n                      f(INVALID_MODIFICATION_ERR, 13, \"InvalidModificationError\") f(NAMESPACE_ERR, \\\n                          14, \"NamespaceError\") f(INVALID_ACCESS_ERR, 15, \"InvalidAccessError\")    \\\n                          f(VALIDATION_ERR, 16, \"ValidationError\") f(TYPE_MISMATCH_ERR, 17,        \\\n                              \"TypeMismatchError\") f(SECURITY_ERR, 18, \"SecurityError\")            \\\n                              f(NETWORK_ERR, 19, \"NetworkError\") f(ABORT_ERR, 20, \"AbortError\")    \\\n                                  f(URL_MISMATCH_ERR, 21, \"URLMismatchError\")                      \\\n                                      f(QUOTA_EXCEEDED_ERR, 22, \"QuotaExceededError\")              \\\n                                          f(TIMEOUT_ERR, 23, \"TimeoutError\")                       \\\n                                              f(INVALID_NODE_TYPE_ERR, 24, \"InvalidNodeTypeError\") \\\n                                                  f(DATA_CLONE_ERR, 25, \"DataCloneError\")\n\nnamespace workerd::jsg {\nclass Serializer;\nclass Deserializer;\n\n// JSG allows DOMExceptions to be tunneled through kj::Exceptions (see exceptionToJs() for\n// details). While this feature is activated conditionally at run-time, and thus does not depend\n// on any specific concrete C++ type, JSG needs to be able to unit test the tunneled exception\n// functionality, thus the existence of this implementation.\n//\n// Note that DOMException is currently the only user-defined exception to get this special\n// treatment because it is the only non-builtin JS exception that standard web APIs are allowed to\n// throw, per Web IDL.\n//\n// Users of JSG are free (and encouraged) to use this implementation, but they can also opt into\n// the same tunneled exception feature by defining their own globally-accessible type named\n// \"DOMException\".\nclass DOMException: public Object {\n public:\n  DOMException(kj::String message, kj::String name): message(kj::mv(message)), name(kj::mv(name)) {}\n\n  // JS API\n\n  static Ref<DOMException> constructor(const v8::FunctionCallbackInfo<v8::Value>& args,\n      Optional<kj::String> message,\n      Optional<kj::String> name);\n\n  kj::StringPtr getName() const;\n  kj::StringPtr getMessage() const;\n  int getCode() const;\n\n#define JSG_DOM_EXCEPTION_CONSTANT_CXX(name, code, friendlyName) static constexpr int name = code;\n#define JSG_DOM_EXCEPTION_CONSTANT_JS(name, code, friendlyName) JSG_STATIC_CONSTANT(name);\n\n  // Define constexpr codes for every INDEX_SIZE_ERR, DOMSTRING_SIZE_ERR, etc.\n  JSG_DOM_EXCEPTION_FOR_EACH_ERROR_NAME(JSG_DOM_EXCEPTION_CONSTANT_CXX)\n\n  JSG_RESOURCE_TYPE(DOMException) {\n    JSG_INHERIT_INTRINSIC(v8::kErrorPrototype);\n\n    // TODO(conform): Per the spec, these should be prototype properties\n    // and not instance properties. Fixing this does require use of the\n    // flags.getJsgPropertyOnPrototypeTemplate() compatibility flag.\n    // The standard definition of DOMException can be found here:\n    // https://webidl.spec.whatwg.org/#idl-DOMException\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(message, getMessage);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(name, getName);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(code, getCode);\n\n    // Declare static JS constants for every INDEX_SIZE_ERR, DOMSTRING_SIZE_ERR, etc.\n    JSG_DOM_EXCEPTION_FOR_EACH_ERROR_NAME(JSG_DOM_EXCEPTION_CONSTANT_JS)\n\n    JSG_TS_OVERRIDE({\n      get stack(): any;\n      set stack(value: any);\n    });\n  }\n  JSG_REFLECTION(stack);\n\n#undef JSG_DOM_EXCEPTION_CONSTANT_CXX\n#undef JSG_DOM_EXCEPTION_CONSTANT_JS\n\n  void visitForMemoryInfo(MemoryTracker& tracker) const {\n    tracker.trackField(\"message\", message);\n    tracker.trackField(\"name\", name);\n  }\n\n  // TODO(cleanup): The value is taken from worker-interface.capnp, which we can't\n  // depend on directly here because we cannot introduce the dependency into JSG.\n  // Therefore we have to set it manually. A better solution long term is to actually\n  // move DOMException into workerd/api, but we'll do that separately.\n  static constexpr uint SERIALIZATION_TAG = 7;\n  static constexpr uint SERIALIZATION_TAG_V2 = 8;\n  JSG_SERIALIZABLE(SERIALIZATION_TAG_V2, SERIALIZATION_TAG);\n\n  void serialize(jsg::Lock& js, jsg::Serializer& serializer);\n  static jsg::Ref<DOMException> deserialize(\n      jsg::Lock& js, uint tag, jsg::Deserializer& deserializer);\n\n private:\n  kj::String message;\n  kj::String name;\n  PropertyReflection<kj::String> stack;\n};\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/exception-metadata.capnp",
    "content": "@0xa9ae63464030fcef;\n# Schema for JavaScript exception metadata passed through KJ exception details\n\nusing Cxx = import \"/capnp/c++.capnp\";\n$Cxx.namespace(\"workerd::jsg\");\n$Cxx.allowCancellation;\n\nstruct JsExceptionMetadata {\n  # JavaScript error type (e.g., \"Error\", \"TypeError\", \"RangeError\")\n  errorType @0 :Text;\n\n  # Full stack trace string as produced by V8\n  # Example: \"Error: User-thrown exception\\n    at Object.fetch (foo:4:11)\"\n  stackTrace @1 :Text;\n}\n"
  },
  {
    "path": "src/workerd/jsg/exception.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"exception.h\"\n\n#include <kj/debug.h>\n\nnamespace workerd::jsg {\n\nkj::StringPtr stripRemoteExceptionPrefix(kj::StringPtr internalMessage) {\n  while (internalMessage.startsWith(\"remote exception: \"_kj)) {\n    // Exception was passed over RPC.\n    internalMessage = internalMessage.slice(\"remote exception: \"_kj.size());\n  }\n  return internalMessage;\n}\n\nnamespace {\nconstexpr auto ERROR_PREFIX_DELIM = \"; \"_kj;\nconstexpr auto ERROR_REMOTE_PREFIX = \"remote.\"_kj;\nconstexpr auto ERROR_TUNNELED_PREFIX_JSG = \"jsg.\"_kj;\nconstexpr auto ERROR_INTERNAL_SOURCE_PREFIX_JSG = \"jsg-internal.\"_kj;\n}  // namespace\n\nTunneledErrorType tunneledErrorType(kj::StringPtr internalMessage) {\n  // A tunneled error in an internal message is prefixed by one of the following patterns,\n  // anchored at the beginning of the message:\n  //   jsg.\n  //   expected <...>; jsg.\n  //   broken.<...>; jsg.\n  // where <...> is some failed expectation from e.g. a KJ_REQUIRE.\n  //\n  // A tunneled error might have a prefix \"remote.\". This indicates it was tunneled from an actor or\n  // from one worker to another. If this prefix is present, we set `isFromRemote` to true, remove\n  // the \"remote.\" prefix, and continue processing the rest of the error.\n  //\n  // Additionally, a prefix of `jsg-internal.` instead of `jsg.` means \"throw a specific\n  // JavaScript error type, but still hide the message text from the app\".\n\n  internalMessage = stripRemoteExceptionPrefix(internalMessage);\n\n  struct Properties {\n    bool isFromRemote = false;\n    bool isDurableObjectReset = false;\n    bool isDoNotLogException = false;\n  };\n  Properties properties;\n\n  properties.isDoNotLogException = isDoNotLogException(internalMessage);\n\n  // Remove `remote.` (if present). Note that there are cases where we return a tunneled error\n  // through multiple workers, so let's be paranoid and allow for multiple \"remote.\" prefixes.\n  while (internalMessage.startsWith(ERROR_REMOTE_PREFIX)) {\n    properties.isFromRemote = true;\n    internalMessage = internalMessage.slice(ERROR_REMOTE_PREFIX.size());\n  }\n\n  auto findDelim = [](kj::StringPtr msg) -> size_t {\n    // Either return 0 if no matches or the index past the first delim if there are.\n    KJ_IF_SOME(i, msg.find(ERROR_PREFIX_DELIM)) {\n      return i + ERROR_PREFIX_DELIM.size();\n    }\n    return 0;\n  };\n\n  auto tryExtractError = [](kj::StringPtr msg,\n                             Properties properties) -> kj::Maybe<TunneledErrorType> {\n    if (msg.startsWith(ERROR_TUNNELED_PREFIX_JSG)) {\n      return TunneledErrorType{\n        .message = msg.slice(ERROR_TUNNELED_PREFIX_JSG.size()),\n        .isJsgError = true,\n        .isInternal = false,\n        .isFromRemote = properties.isFromRemote,\n        .isDurableObjectReset = properties.isDurableObjectReset,\n        .isDoNotLogException = properties.isDoNotLogException,\n      };\n    }\n    if (msg.startsWith(ERROR_INTERNAL_SOURCE_PREFIX_JSG)) {\n      return TunneledErrorType{\n        .message = msg.slice(ERROR_INTERNAL_SOURCE_PREFIX_JSG.size()),\n        .isJsgError = true,\n        .isInternal = true,\n        .isFromRemote = properties.isFromRemote,\n        .isDurableObjectReset = properties.isDurableObjectReset,\n        .isDoNotLogException = properties.isDoNotLogException,\n      };\n    }\n\n    return kj::none;\n  };\n\n  auto makeDefaultError = [](kj::StringPtr msg, Properties properties) {\n    return TunneledErrorType{\n      .message = msg,\n      .isJsgError = false,\n      .isInternal = true,\n      .isFromRemote = properties.isFromRemote,\n      .isDurableObjectReset = properties.isDurableObjectReset,\n      .isDoNotLogException = isDoNotLogException(msg),\n    };\n  };\n\n  if (internalMessage.startsWith(\"expected \")) {\n    // This was a test assertion, peel away delimiters until either we find an error or there are\n    // none left.\n    auto idx = findDelim(internalMessage);\n    while (idx) {\n      internalMessage = internalMessage.slice(idx);\n      KJ_IF_SOME(e, tryExtractError(internalMessage, properties)) {\n        return kj::mv(e);\n      }\n      idx = findDelim(internalMessage);\n    }\n\n    // We failed to extract an expected error, make a default one.\n    return makeDefaultError(internalMessage, properties);\n  }\n\n  while (internalMessage.startsWith(\"broken.\")) {\n    properties.isDurableObjectReset = true;\n\n    // Trim away all broken prefixes, they are not allowed to have internal delimiters.\n    internalMessage = internalMessage.slice(findDelim(internalMessage));\n  }\n\n  // There are no prefixes left, just try to extract the error.\n  KJ_IF_SOME(e, tryExtractError(internalMessage, properties)) {\n    return kj::mv(e);\n  } else {\n    return makeDefaultError(internalMessage, properties);\n  }\n}\n\nbool isTunneledException(kj::StringPtr internalMessage) {\n  return !tunneledErrorType(internalMessage).isInternal;\n}\n\nbool isDoNotLogException(kj::StringPtr internalMessage) {\n  return internalMessage.contains(\"worker_do_not_log\"_kjc);\n}\n\nkj::String annotateBroken(kj::StringPtr internalMessage, kj::StringPtr brokennessReason) {\n  // TODO(soon) Once we support multiple brokenness reasons, we can make this much simpler.\n\n  KJ_LOG(INFO, \"Annotating with brokenness\", internalMessage, brokennessReason);\n  auto tunneledInfo = tunneledErrorType(internalMessage);\n\n  kj::StringPtr remotePrefix;\n  if (tunneledInfo.isFromRemote) {\n    remotePrefix = ERROR_REMOTE_PREFIX;\n  }\n\n  kj::StringPtr prefixType = ERROR_TUNNELED_PREFIX_JSG;\n  kj::StringPtr internalErrorType;\n  if (tunneledInfo.isInternal) {\n    prefixType = ERROR_INTERNAL_SOURCE_PREFIX_JSG;\n    if (!tunneledInfo.isJsgError) {\n      // This is not a JSG error, so we need to give it a type.\n      internalErrorType = \"Error: \"_kj;\n    }\n  }\n\n  return kj::str(remotePrefix, brokennessReason, ERROR_PREFIX_DELIM, prefixType, internalErrorType,\n      tunneledInfo.message);\n}\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/exception.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/debug.h>\n#include <kj/string.h>\n\nnamespace workerd::jsg {\n\n#define JSG_EXCEPTION(jsErrorType) JSG_ERROR_##jsErrorType\n#define JSG_DOM_EXCEPTION(name) \"jsg.DOMException(\" name \")\"\n#define JSG_INTERNAL_DOM_EXCEPTION(name) \"jsg-internal.DOMException(\" name \")\"\n\n#define JSG_ERROR_DOMOperationError JSG_DOM_EXCEPTION(\"OperationError\")\n#define JSG_ERROR_DOMDataError JSG_DOM_EXCEPTION(\"DataError\")\n#define JSG_ERROR_DOMDataCloneError JSG_DOM_EXCEPTION(\"DataCloneError\")\n#define JSG_ERROR_DOMInvalidAccessError JSG_DOM_EXCEPTION(\"InvalidAccessError\")\n#define JSG_ERROR_DOMInvalidStateError JSG_DOM_EXCEPTION(\"InvalidStateError\")\n#define JSG_ERROR_DOMInvalidCharacterError JSG_DOM_EXCEPTION(\"InvalidCharacterError\")\n#define JSG_ERROR_DOMNotSupportedError JSG_DOM_EXCEPTION(\"NotSupportedError\")\n#define JSG_ERROR_DOMSyntaxError JSG_DOM_EXCEPTION(\"SyntaxError\")\n#define JSG_ERROR_DOMTimeoutError JSG_DOM_EXCEPTION(\"TimeoutError\")\n#define JSG_ERROR_DOMTypeMismatchError JSG_DOM_EXCEPTION(\"TypeMismatchError\")\n#define JSG_ERROR_DOMQuotaExceededError JSG_DOM_EXCEPTION(\"QuotaExceededError\")\n#define JSG_ERROR_DOMAbortError JSG_DOM_EXCEPTION(\"AbortError\")\n#define JSG_ERROR_DOMNotFoundError JSG_DOM_EXCEPTION(\"NotFoundError\")\n\n#define JSG_ERROR_TypeError \"jsg.TypeError\"\n#define JSG_ERROR_Error \"jsg.Error\"\n#define JSG_ERROR_RangeError \"jsg.RangeError\"\n\n#define JSG_ERROR_InternalDOMOperationError JSG_INTERNAL_DOM_EXCEPTION(\"OperationError\")\n\n#define JSG_KJ_EXCEPTION(type, jsErrorType, ...)                                                   \\\n  kj::Exception(kj::Exception::Type::type, __FILE__, __LINE__,                                     \\\n      kj::str(JSG_EXCEPTION(jsErrorType) \": \", __VA_ARGS__))\n\n#define JSG_ASSERT(cond, jsErrorType, ...)                                                         \\\n  KJ_ASSERT(cond, kj::str(JSG_EXCEPTION(jsErrorType) \": \", ##__VA_ARGS__))\n\n// Asserts if the method is compatible with v8 fast api\n#define JSG_ASSERT_FASTAPI(Method)                                                                 \\\n  static_assert(isFastApiCompatible<Method>, \"Method is not v8 fast api compatible\");\n\n#define JSG_REQUIRE(cond, jsErrorType, ...)                                                        \\\n  KJ_REQUIRE(cond, kj::str(JSG_EXCEPTION(jsErrorType) \": \", ##__VA_ARGS__))\n// Unlike KJ_REQUIRE, JSG_REQUIRE passes all message arguments through kj::str which makes it\n// \"prettier\". This does have some implications like if there's only string literal arguments then\n// there's an unnecessary heap copy. More importantly none of the expressions you pass in end up in\n// the resultant string AND you are responsible for formatting the resultant string. For example,\n// KJ_REQUIRE(false, \"some message\", x) formats it like \"some message; x = 5\". The \"equivalent\" via\n// this macro would be JSG_REQUIRE(false, \"some message \", x); which would yield a string like\n// \"some message 5\" (or JSG_REQUIRE(false, \"some message; x = \", x) if you wanted identical output,\n// but then why not use KJ_REQUIRE).\n\n#define JSG_REQUIRE_NONNULL(value, jsErrorType, ...)                                               \\\n  KJ_REQUIRE_NONNULL(value, kj::str(JSG_EXCEPTION(jsErrorType) \": \", ##__VA_ARGS__))\n// JSG_REQUIRE + KJ_REQUIRE_NONNULL.\n\n#define JSG_FAIL_REQUIRE(jsErrorType, ...)                                                         \\\n  KJ_FAIL_REQUIRE(kj::str(JSG_EXCEPTION(jsErrorType) \": \", ##__VA_ARGS__))\n// JSG_REQUIRE + KJ_FAIL_REQUIRE\n\n#define JSG_WARN_ONCE(msg, ...)                                                                    \\\n  static bool logOnce KJ_UNUSED = ([&] {                                                           \\\n    KJ_LOG(WARNING, msg, ##__VA_ARGS__);                                                           \\\n    return true;                                                                                   \\\n  })()\n\n// Conditionally log a warning, at most once. Useful for determining if code changes would break\n// any existing scripts.\n#define JSG_WARN_ONCE_IF(cond, msg, ...)                                                           \\\n  if (cond) {                                                                                      \\\n    JSG_WARN_ONCE(msg, ##__VA_ARGS__);                                                             \\\n  }\n\n// These are passthrough functions to KJ. We expect the error string to be\n// surfaced to the application.\n\n#define _JSG_INTERNAL_REQUIRE(cond, jsErrorType, ...)                                              \\\n  do {                                                                                             \\\n    try {                                                                                          \\\n      KJ_REQUIRE(cond, jsErrorType \": Cloudflare internal error.\");                                \\\n    } catch (const kj::Exception& e) {                                                             \\\n      KJ_LOG(ERROR, e, ##__VA_ARGS__);                                                             \\\n      throw e;                                                                                     \\\n    }                                                                                              \\\n  } while (0)\n\n#define _JSG_INTERNAL_REQUIRE_NONNULL(value, jsErrorType, ...)                                     \\\n  ([&]() -> decltype(auto) {                                                                       \\\n    try {                                                                                          \\\n      return KJ_REQUIRE_NONNULL(value, jsErrorType \": Cloudflare internal error.\");                \\\n    } catch (const kj::Exception& e) {                                                             \\\n      KJ_LOG(ERROR, e, ##__VA_ARGS__);                                                             \\\n      throw e;                                                                                     \\\n    }                                                                                              \\\n  }())\n\n#define _JSG_INTERNAL_FAIL_REQUIRE(jsErrorType, ...)                                               \\\n  do {                                                                                             \\\n    try {                                                                                          \\\n      KJ_FAIL_REQUIRE(jsErrorType \": Cloudflare internal error.\");                                 \\\n    } catch (const kj::Exception& e) {                                                             \\\n      KJ_LOG(ERROR, e, ##__VA_ARGS__);                                                             \\\n      throw e;                                                                                     \\\n    }                                                                                              \\\n  } while (0)\n\n// Given a KJ exception's description, strips any leading \"remote exception: \" prefixes.\nkj::StringPtr stripRemoteExceptionPrefix(kj::StringPtr internalMessage);\n\n// Given a KJ exception's description, returns whether it contains a tunneled exception that could\n// be converted back to JavaScript via exceptionToJs().\nbool isTunneledException(kj::StringPtr internalMessage);\n\n// Given a KJ exception's description, returns whether it contains the magic constant that indicates\n// the exception is the script's fault and isn't worth logging.\nbool isDoNotLogException(kj::StringPtr internalMessage);\n\n// Log an exception ala LOG_EXCEPTION, but only if it is worth logging and not a tunneled exception.\n#define LOG_EXCEPTION_IF_INTERNAL(context, exception)                                              \\\n  if (!jsg::isTunneledException(exception.getDescription()) &&                                     \\\n      !jsg::isDoNotLogException(exception.getDescription())) {                                     \\\n    LOG_EXCEPTION(context, exception);                                                             \\\n  }\n\nstruct TunneledErrorType {\n  // The original error message stripped of prefixes.\n  kj::StringPtr message;\n\n  // Was this error prefixed by JSG already?\n  bool isJsgError;\n\n  // Is this error internal? If so, the error message should be logged to syslog and hidden from\n  // the app.\n  bool isInternal;\n\n  // Was the error tunneled from either a worker or an actor?\n  bool isFromRemote;\n\n  // Was the error created because a durable object is broken?\n  bool isDurableObjectReset;\n\n  // Does the error contain the \"worker_do_not_log\" magic constant?\n  bool isDoNotLogException;\n};\n\nTunneledErrorType tunneledErrorType(kj::StringPtr internalMessage);\n\n// Annotate an internal message with the corresponding brokenness reason.\nkj::String annotateBroken(kj::StringPtr internalMessage, kj::StringPtr brokennessReason);\n\nconstexpr kj::Exception::DetailTypeId EXCEPTION_IS_USER_ERROR = 0x82aff7d637c30e47ull;\n\nstruct ExceptionToJsOptions {\n  // When ignoreDetail is true, tells kjExceptionToJs() to ignore any serialized\n  // exception detail in the kj::Exception.\n  bool ignoreDetail = false;\n\n  // When trusted is true and the kj::Exception has a serialized exception detail, the\n  // stack will be included in the deserialized error if it is available. When false,\n  // the stack will be omitted.\n  bool trusted = false;\n\n  // If the deserialized exception detail is not an object, then it will be ignored\n  // and we will fall back to constructing a new error object. The default is true\n  // to preserve existing behavior, but setting this to false may be useful in some\n  // cases. When false, the kjExceptionToJs() might return a non-object value.\n  bool allowNonObjects = false;\n};\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/fast-api-test.c++",
    "content": "#include \"jsg-test.h\"\n\n#include <workerd/util/autogate.h>\n\nnamespace workerd::jsg::test {\nnamespace {\n\nusing jsg::CallCounter;\n\nstruct WrappedInt {\n  int32_t i;\n\n  JSG_STRUCT(i);\n};\n\nclass StaticMethodContainer: public jsg::Object {\n public:\n  StaticMethodContainer() = default;\n\n  static int32_t staticMethod() {\n    return 42;\n  }\n\n  uint32_t getValue() const {\n    return value;\n  }\n\n  void setValue(uint32_t value_) {\n    value = value_;\n  }\n\n  JSG_RESOURCE_TYPE(StaticMethodContainer) {\n    JSG_STATIC_METHOD(staticMethod);\n    JSG_PROTOTYPE_PROPERTY(value, getValue, setValue);\n  }\n\n private:\n  uint32_t value = 42;\n};\n\nclass FastMethodContext: public jsg::Object, public jsg::ContextGlobal {\n public:\n  int32_t add(int32_t a, int32_t b) {\n    return a + b;\n  }\n\n  int32_t processValue(v8::Local<v8::Value> value) {\n    KJ_ASSERT(value->IsNumber(), \"Value must be a number\");\n    return value.As<v8::Int32>()->Value();\n  }\n\n  // When arbitrary type unwrapping is supported, keep this test as it is.\n  int32_t processObject(v8::Local<v8::Object> obj) {\n    auto& js = jsg::Lock::current();\n    auto context = js.v8Context();\n    auto testKey = v8::String::NewFromUtf8(js.v8Isolate, \"test\").ToLocalChecked();\n    v8::Local<v8::Value> value;\n    if (obj->Get(context, testKey).ToLocal(&value)) {\n      v8::String::Utf8Value type(js.v8Isolate, value->TypeOf(js.v8Isolate));\n      KJ_ASSERT(value->IsInt32(), \"Received \", *type);\n      return value.As<v8::Int32>()->Value();\n    }\n    return 0;\n  }\n\n  void voidMethod(uint32_t a, uint32_t b) {\n    // Do nothing. This is testing void return type.\n  }\n\n  int32_t throwError(int32_t code) {\n    if (code > 0) {\n      JSG_FAIL_REQUIRE(TypeError, \"Test error with code \", code);\n    }\n    return 0;\n  }\n\n  int32_t addWithLock(jsg::Lock& js, int32_t a, v8::Local<v8::Value> b) {\n    KJ_ASSERT(b->IsNumber(), \"Second parameter must be a number\");\n    int32_t bValue = b.As<v8::Int32>()->Value();\n    return a + bValue;\n  }\n\n  int32_t constAdd(int32_t a, int32_t b) {\n    return a + b;\n  }\n\n  int32_t constAddWithLock(jsg::Lock& js, int32_t a, int32_t b) {\n    return a + b;\n  }\n\n  int32_t unwrapStruct(jsg::Lock& js, WrappedInt w) {\n    return w.i;\n  }\n\n  int32_t unwrapUint(jsg::Lock& js, kj::uint u) {\n    return u;\n  }\n\n  int32_t unwrapString(jsg::Lock& js, kj::String str) {\n    return str.size();\n  }\n\n  int32_t unwrapBufferSource(jsg::Lock& js, jsg::BufferSource source) {\n    return source.size();\n  }\n\n  int32_t unwrapMaybe(jsg::Lock& js, kj::Maybe<kj::String> str) {\n    KJ_IF_SOME(s, str) {\n      return s.size();\n    } else {\n      return -1;\n    }\n  }\n\n  int32_t unwrapOptional(jsg::Lock& js, jsg::Optional<kj::String> str) {\n    KJ_IF_SOME(s, str) {\n      return s.size();\n    } else {\n      return -1;\n    }\n  }\n\n  int32_t unwrapLenientOptional(jsg::Lock& js, jsg::LenientOptional<kj::String> str) {\n    KJ_IF_SOME(s, str) {\n      return s.size();\n    } else {\n      return -1;\n    }\n  }\n\n  jsg::Ref<StaticMethodContainer> newContainer(jsg::Lock& js) {\n    return js.alloc<StaticMethodContainer>();\n  }\n\n  JSG_RESOURCE_TYPE(FastMethodContext) {\n    JSG_NESTED_TYPE(StaticMethodContainer);\n\n    JSG_METHOD(add);\n    JSG_METHOD(processValue);\n    JSG_METHOD(processObject);\n    JSG_METHOD(voidMethod);\n    JSG_METHOD(throwError);\n    JSG_METHOD(addWithLock);\n    JSG_METHOD(constAdd);\n    JSG_METHOD(constAddWithLock);\n    JSG_METHOD(unwrapStruct);\n    JSG_METHOD(unwrapUint);\n    JSG_METHOD(unwrapString);\n    JSG_METHOD(unwrapBufferSource);\n    JSG_METHOD(unwrapMaybe);\n    JSG_METHOD(unwrapOptional);\n    JSG_METHOD(unwrapLenientOptional);\n\n    JSG_METHOD(newContainer);\n  }\n};\n\nJSG_DECLARE_DEBUG_ISOLATE_TYPE(\n    FastMethodIsolate, FastMethodContext, WrappedInt, StaticMethodContainer);\n\njsg::V8System v8System({\"--allow-natives-syntax\"});\n\nstruct Test {\n  kj::LiteralStringConst expr;\n  kj::LiteralStringConst expectedReturnType;\n  kj::LiteralStringConst expectedReturnValue;\n  kj::LiteralStringConst target = \"\"_kjc;\n  kj::uint expectedSlowCount = 1;\n};\n\nCallCounter runTest(Test test) {\n  jsg::callCounter.reset();\n  JsgConfig config = {\n    .fastApiEnabled = true,\n  };\n  jsg::test::Evaluator<FastMethodContext, FastMethodIsolate, JsgConfig> e(v8System, config);\n\n  auto target = test.target == \"\"_kjc ? kj::str(test.expr) : kj::str(test.target, \".\", test.expr);\n  e.expectEval(target, test.expectedReturnType, test.expectedReturnValue);\n  KJ_ASSERT(jsg::callCounter == CallCounter(test.expectedSlowCount, 0));\n\n  e.expectEval(kj::str(\"const fastCall = () => { return \", target,\n                   \"; }; \"\n                   \"%PrepareFunctionForOptimization(fastCall); \"\n                   \"fastCall(); \"\n                   \"%OptimizeFunctionOnNextCall(fastCall); \"\n                   \"fastCall()\"),\n      test.expectedReturnType, test.expectedReturnValue);\n  return jsg::callCounter;\n}\n\nKJ_TEST(\"v8::Local<v8::Value> and v8::Local<v8::Object> as fast method parameters\") {\n  util::Autogate::initAutogateNamesForTest({\"v8-fast-api\"_kj});\n  KJ_ASSERT(runTest({\"processValue(42)\"_kjc, \"number\"_kjc, \"42\"_kjc}) == CallCounter(2, 1));\n  KJ_ASSERT(\n      runTest({\"processObject({test: 123})\"_kjc, \"number\"_kjc, \"123\"_kjc}) == CallCounter(2, 1));\n}\n\nKJ_TEST(\"Lock& as the first parameter in fast method calls\") {\n  KJ_ASSERT(runTest({\"addWithLock(3, 4)\"_kjc, \"number\"_kjc, \"7\"_kjc}) == CallCounter(2, 1));\n}\n\nKJ_TEST(\"Const methods in fast method calls\") {\n  KJ_ASSERT(runTest({\"constAdd(3, 4)\"_kjc, \"number\"_kjc, \"7\"_kjc}) == CallCounter(2, 1));\n}\n\nKJ_TEST(\"Const methods with Lock& in fast method calls\") {\n  KJ_ASSERT(runTest({\"constAddWithLock(3, 4)\"_kjc, \"number\"_kjc, \"7\"_kjc}) == CallCounter(2, 1));\n}\n\nKJ_TEST(\"type unwrapping arguments\") {\n  KJ_ASSERT(runTest({\"unwrapUint(4)\"_kjc, \"number\"_kjc, \"4\"_kjc}) == CallCounter(2, 1));\n  KJ_ASSERT(runTest({\"unwrapStruct({i: 3})\"_kjc, \"number\"_kjc, \"3\"_kjc}) == CallCounter(2, 1));\n  KJ_ASSERT(runTest({\"unwrapString('0123')\"_kjc, \"number\"_kjc, \"4\"_kjc}) == CallCounter(2, 1));\n  KJ_ASSERT(runTest({\"unwrapBufferSource(new Uint8Array(256))\"_kjc, \"number\"_kjc, \"256\"_kjc}) ==\n      CallCounter(2, 1));\n  KJ_ASSERT(runTest({\"unwrapMaybe(undefined)\"_kjc, \"number\"_kjc, \"-1\"_kjc}) == CallCounter(2, 1));\n  KJ_ASSERT(runTest({\"unwrapMaybe('foo')\"_kjc, \"number\"_kjc, \"3\"_kjc}) == CallCounter(2, 1));\n  KJ_ASSERT(\n      runTest({\"unwrapOptional(undefined)\"_kjc, \"number\"_kjc, \"-1\"_kjc}) == CallCounter(2, 1));\n  KJ_ASSERT(runTest({\"unwrapOptional('foo')\"_kjc, \"number\"_kjc, \"3\"_kjc}) == CallCounter(2, 1));\n  KJ_ASSERT(runTest({\"unwrapLenientOptional(undefined)\"_kjc, \"number\"_kjc, \"-1\"_kjc}) ==\n      CallCounter(2, 1));\n  KJ_ASSERT(\n      runTest({\"unwrapLenientOptional('foo')\"_kjc, \"number\"_kjc, \"3\"_kjc}) == CallCounter(2, 1));\n\n  KJ_ASSERT(runTest({\"StaticMethodContainer.staticMethod()\"_kjc, \"number\"_kjc, \"42\"_kjc}) ==\n      CallCounter(2, 1));\n}\n\nKJ_TEST(\"Fast methods should work with getters/setters\") {\n  KJ_ASSERT(\n      runTest({\"value\"_kjc, \"number\"_kjc, \"42\"_kjc, \"newContainer()\"_kjc, 2}) == CallCounter(5, 1));\n  KJ_ASSERT(runTest({\"value = 12\"_kjc, \"number\"_kjc, \"12\"_kjc, \"newContainer()\"_kjc, 2}) ==\n      CallCounter(5, 1));\n}\n\nKJ_TEST(\"Fast methods properly catch JSG_FAIL_REQUIRE errors\") {\n  jsg::callCounter.reset();\n  JsgConfig config = {\n    .fastApiEnabled = true,\n  };\n  jsg::test::Evaluator<FastMethodContext, FastMethodIsolate, JsgConfig> e(v8System, config);\n\n  // Test that directly calling the method results in an error\n  e.expectEval(\"throwError(42)\", \"throws\", \"TypeError: Test error with code 42\");\n  KJ_ASSERT(jsg::callCounter == CallCounter(1, 0));\n\n  // First run a non-throwing call to allow optimization\n  e.expectEval(\"throwError(0)\", \"number\", \"0\");\n  KJ_ASSERT(jsg::callCounter == CallCounter(2, 0));\n\n  e.expectEval(\"function fastThrow(code) { return throwError(code); }; \"\n               \"%PrepareFunctionForOptimization(fastThrow); \"\n               \"fastThrow(0); \"\n               \"%OptimizeFunctionOnNextCall(fastThrow); \"\n               \"fastThrow(42)\",\n      \"throws\", \"TypeError: Test error with code 42\");\n\n  // The counts should now include both the slow path and fast path calls\n  // 3 slow path calls (original direct call + non-throwing call + the initial optimization call that didn't throw)\n  // 1 fast path call (the optimized call that threw an error)\n  KJ_ASSERT(jsg::callCounter == CallCounter(3, 1));\n}\n\nKJ_TEST(\"isFastApiCompatible Detection\") {\n  static_assert(isFastApiCompatible<void (FastMethodContext::*)(jsg::Lock&, bool)>,\n      \"lock is accepted only as first argument\");\n\n  // Method type declarations\n  // -----------------------\n\n  // Compatible basic types\n  using VoidMethod = void (FastMethodContext::*)();\n  using IntMethod = int32_t (FastMethodContext::*)(int32_t);\n  using BoolMethod = bool (FastMethodContext::*)(double, bool);\n  using FloatMethod = float (FastMethodContext::*)(int32_t, float);\n\n  // V8 Local types as parameters - should be compatible\n  using V8ValueParamMethod = int32_t (FastMethodContext::*)(v8::Local<v8::Value>);\n  using V8ObjectParamMethod = int32_t (FastMethodContext::*)(v8::Local<v8::Object>);\n\n  // Const method variants - should be compatible\n  using ConstIntMethod = int32_t (FastMethodContext::*)(int32_t) const;\n  using ConstFloatMethod = float (FastMethodContext::*)(float) const;\n\n  // Methods with Lock& as first parameter - should be compatible\n  using LockFirstMethod = int32_t (FastMethodContext::*)(jsg::Lock&, int32_t);\n  using LockFirstVoidMethod = void (FastMethodContext::*)(jsg::Lock&, bool);\n  using LockFirstWithV8Local = int32_t (FastMethodContext::*)(jsg::Lock&, v8::Local<v8::Value>);\n  using ConstLockFirstMethod = int32_t (FastMethodContext::*)(jsg::Lock&, int32_t) const;\n\n  // ---- Non-compatible method types ----\n\n  // Pointer types - not compatible\n  using PointerParamMethod = void* (FastMethodContext::*)(void*);\n  using PointerReturnMethod = void* (FastMethodContext::*)();\n\n  // V8 Local as return type - not compatible\n  using V8ReturnMethod = v8::Local<v8::Value> (FastMethodContext::*)(int32_t);\n\n  // Non-primitive types - not compatible\n  using StringMethod = kj::String (FastMethodContext::*)(kj::String);\n  using ComplexMethod = Ref<FastMethodContext> (FastMethodContext::*)(jsg::Lock&);\n  using KjArrayMethod = kj::Array<int> (FastMethodContext::*)(int32_t);\n  using PromiseMethod = jsg::Promise<int> (FastMethodContext::*)(int32_t);\n  using MaybeVoidMethod = kj::Maybe<void> (FastMethodContext::*)();\n  using StaticMethodContainerMethod = void(StaticMethodContainer);\n  using KjPromiseMethod = void (FastMethodContext::*)(kj::Promise<void>);\n  using JsgPromiseMethod = void (FastMethodContext::*)(jsg::Promise<void>);\n\n  // Static assertions for compatible method types\n  // --------------------------------------------\n\n  // Basic primitives\n  static_assert(isFastApiCompatible<VoidMethod>, \"Void methods should be fast-method compatible\");\n  static_assert(isFastApiCompatible<IntMethod>, \"Integer methods should be fast-method compatible\");\n  static_assert(\n      isFastApiCompatible<BoolMethod>, \"Boolean methods should be fast-method compatible\");\n  static_assert(isFastApiCompatible<FloatMethod>, \"Float methods should be fast-method compatible\");\n\n  // V8 Local parameters\n  static_assert(isFastApiCompatible<V8ValueParamMethod>,\n      \"Methods with v8::Local<v8::Value> parameters should be fast-method compatible\");\n  static_assert(isFastApiCompatible<V8ObjectParamMethod>,\n      \"Methods with v8::Local<v8::Object> parameters should be fast-method compatible\");\n\n  // Const methods\n  static_assert(\n      isFastApiCompatible<ConstIntMethod>, \"Const methods should be fast-method compatible\");\n  static_assert(isFastApiCompatible<ConstFloatMethod>,\n      \"Const methods with float should be fast-method compatible\");\n\n  // Lock& as first parameter\n  static_assert(isFastApiCompatible<LockFirstMethod>,\n      \"Methods with Lock& as first parameter should be fast-method compatible\");\n  static_assert(isFastApiCompatible<LockFirstVoidMethod>,\n      \"Void methods with Lock& as first parameter should be fast-method compatible\");\n  static_assert(isFastApiCompatible<LockFirstWithV8Local>,\n      \"Methods with Lock& and v8::Local params should be fast-method compatible\");\n  static_assert(isFastApiCompatible<ConstLockFirstMethod>,\n      \"Const methods with Lock& as first parameter should be fast-method compatible\");\n\n  // Static assertions for incompatible method types\n  // ----------------------------------------------\n\n  // Pointer types\n  static_assert(!isFastApiCompatible<PointerParamMethod>,\n      \"Methods with pointer parameters should not be fast-method compatible\");\n  static_assert(!isFastApiCompatible<PointerReturnMethod>,\n      \"Methods returning pointers should not be fast-method compatible\");\n\n  // V8 Local return type\n  static_assert(!isFastApiCompatible<V8ReturnMethod>,\n      \"Methods returning v8::Local<v8::Value> should not be fast-method compatible\");\n\n  // Complex types\n  static_assert(!isFastApiCompatible<StringMethod>,\n      \"Methods with kj::String parameters should not be fast-method compatible\");\n  static_assert(!isFastApiCompatible<ComplexMethod>,\n      \"Methods with Ref return types should not be fast-method compatible\");\n  static_assert(!isFastApiCompatible<KjArrayMethod>,\n      \"Methods returning kj::Array should not be fast-method compatible\");\n  static_assert(!isFastApiCompatible<PromiseMethod>,\n      \"Methods returning Promise should not be fast-method compatible\");\n  static_assert(!isFastApiCompatible<MaybeVoidMethod>,\n      \"Methods returning Maybe<void> should not be fast-method compatible\");\n  static_assert(isFastApiCompatible<StaticMethodContainerMethod>, \"This should be compatible\");\n  static_assert(!isFastApiCompatible<KjPromiseMethod>, \"kj::Promise is not compatible\");\n  static_assert(!isFastApiCompatible<JsgPromiseMethod>, \"jsg::Promise is not compatible\");\n}\n\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/fast-api.h",
    "content": "#pragma once\n\n// Fast API implementation for workerd\n//\n// This file provides utilities to support V8 Fast API Calls, which allow optimized\n// method calls from JavaScript to C++ without going through the V8 API. Fast API\n// Calls perform type checks in the compiler instead of on the embedder side and\n// are subject to strict limitations - they cannot allocate on the JS heap or\n// trigger JS execution.\n//\n// The implementation provides concepts and helpers to determine which types and methods\n// are compatible with Fast API, handling both primitive types that can be passed directly\n// and wrapped objects that require conversion between JavaScript and C++.\n//\n// We don't add FastOneByteString because any GC call before FastOneByteString being copied\n// will corrupt the string data and cause catastrophic failures with almost zero stack trace.\n// For more information, see https://github.com/cloudflare/workerd/pull/4625.\n\n#include <v8-fast-api-calls.h>\n#include <v8-local-handle.h>\n#include <v8-value.h>\n\n#include <kj/async.h>\n#include <kj/common.h>\n#include <kj/string.h>\n\nnamespace workerd::jsg {\n\nclass DOMString;\nclass USVString;\nclass Lock;\ntemplate <typename T>\nclass Promise;\n\ntemplate <typename T>\nconstexpr bool isFunctionCallbackInfo = false;\n\ntemplate <typename T>\nconstexpr bool isFunctionCallbackInfo<v8::FunctionCallbackInfo<T>> = true;\n\ntemplate <typename T>\nconstexpr bool isKjPromise = false;\n\ntemplate <typename T>\nconstexpr bool isKjPromise<kj::Promise<T>> = true;\n\ntemplate <typename T>\nconstexpr bool isJsgPromise = false;\n\ntemplate <typename T>\nconstexpr bool isJsgPromise<jsg::Promise<T>> = true;\n\n// These types are passed by fast api as is and do not require wrapping/unwrapping.\ntemplate <typename T>\nconcept FastApiPrimitive = kj::isSameType<T, void>() || kj::isSameType<T, bool>() ||\n    kj::isSameType<T, int32_t>() || kj::isSameType<T, int64_t>() || kj::isSameType<T, uint32_t>() ||\n    kj::isSameType<T, uint64_t>() || kj::isSameType<T, float>() || kj::isSameType<T, double>();\n\n// Helper to determine if a type can be used as a parameter in V8 Fast API\ntemplate <typename T>\nconcept FastApiParam = !isFunctionCallbackInfo<kj::RemoveConst<kj::Decay<T>>> &&\n    !isKjPromise<kj::RemoveConst<kj::Decay<T>>> && !isJsgPromise<kj::RemoveConst<kj::Decay<T>>>;\n\n// Helper to determine if a type can be used as a return value in a V8 Fast API\ntemplate <typename T>\nconcept FastApiReturnParam = FastApiPrimitive<T>;\n\n// Helper to determine if a method can be used with V8 fast API\ntemplate <typename Ret, typename... Args>\nconcept FastApiMethod = FastApiReturnParam<Ret> && (FastApiParam<Args> && ...);\n\n// Helper to determine if a method pointer type is compatible with Fast API\ntemplate <typename Method>\nconstexpr bool isFastApiCompatible = false;\n\n// Specialization for non-static member functions\ntemplate <typename Class, typename Ret, typename... Args>\nconstexpr bool isFastApiCompatible<Ret (Class::*)(Args...)> = FastApiMethod<Ret, Args...>;\n\n// Specialization for const non-static member functions\ntemplate <typename Class, typename Ret, typename... Args>\nconstexpr bool isFastApiCompatible<Ret (Class::*)(Args...) const> = FastApiMethod<Ret, Args...>;\n\ntemplate <typename Class, typename Ret, typename... Args>\nconstexpr bool isFastApiCompatible<Ret (Class::*)(jsg::Lock&, Args...)> =\n    FastApiMethod<Ret, Args...>;\n\ntemplate <typename Class, typename Ret, typename... Args>\nconstexpr bool isFastApiCompatible<Ret (Class::*)(jsg::Lock&, Args...) const> =\n    FastApiMethod<Ret, Args...>;\n\n// Specialization for static functions\ntemplate <typename Ret, typename... Args>\nconstexpr bool isFastApiCompatible<Ret(Args...)> = FastApiMethod<Ret, Args...>;\n\ntemplate <typename Ret, typename... Args>\nconstexpr bool isFastApiCompatible<Ret(jsg::Lock&, Args...)> = FastApiMethod<Ret, Args...>;\n\ntemplate <typename T>\nstruct FastApiJSGToV8 {\n  using value = v8::Local<v8::Value>;\n};\n\ntemplate <typename T>\n  requires FastApiPrimitive<kj::RemoveConst<kj::Decay<T>>>\nstruct FastApiJSGToV8<T> {\n  using value = kj::RemoveConst<kj::Decay<T>>;\n};\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/function-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"jsg-test.h\"\n\nnamespace workerd::jsg::test {\nnamespace {\n\nV8System v8System;\nclass ContextGlobalObject: public Object, public ContextGlobal {};\n\nstruct CallbackContext: public ContextGlobalObject {\n  kj::String callCallback(Lock& js, jsg::Function<kj::String(kj::StringPtr, double)> function) {\n    return kj::str(function(js, \"foo\", 123), \", abc\");\n  }\n  double callCallbackReturningBox(Lock& js, jsg::Function<Ref<NumberBox>()> function) {\n    return function(js)->value;\n  }\n\n  struct Frobber {\n    kj::String s;\n    double n;\n    jsg::Function<kj::String(double)> frob;\n    Optional<jsg::Function<kj::String(double)>> optionalFrob;\n    kj::Maybe<jsg::Function<kj::String(double)>> maybeFrob;\n    JSG_STRUCT(s, n, frob, optionalFrob, maybeFrob);\n  };\n\n  kj::String callConstructor(Lock& js, Constructor<Frobber(kj::StringPtr, double)> constructor) {\n    auto frobber = constructor(js, \"foo\", 123);\n    return kj::str(frobber.s, frobber.n, frobber.frob(js, 321),\n        KJ_ASSERT_NONNULL(frobber.optionalFrob)(js, 654),\n        KJ_ASSERT_NONNULL(frobber.maybeFrob)(js, 987));\n  }\n\n  JSG_RESOURCE_TYPE(CallbackContext) {\n    JSG_NESTED_TYPE(NumberBox);\n    JSG_METHOD(callCallback);\n    JSG_METHOD(callCallbackReturningBox);\n    JSG_METHOD(callConstructor);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(CallbackIsolate, CallbackContext, CallbackContext::Frobber, NumberBox);\n\nKJ_TEST(\"callbacks\") {\n  Evaluator<CallbackContext, CallbackIsolate> e(v8System);\n  e.expectEval(\"callCallback((str, num) => {\\n\"\n               \"  return [typeof str, str, typeof num, num.toString(), 'bar'].join(', ');\\n\"\n               \"})\",\n      \"string\", \"string, foo, number, 123, bar, abc\");\n\n  e.expectEval(\"callCallback((str, num) => {\\n\"\n               \"  throw new Error('error message')\\n\"\n               \"})\",\n      \"throws\", \"Error: error message\");\n\n  e.expectEval(\"callCallbackReturningBox(() => {\\n\"\n               \"  return new NumberBox(123);\\n\"\n               \"})\",\n      \"number\", \"123\");\n  e.expectEval(\"callCallbackReturningBox(() => {\\n\"\n               \"  return 'foo';\\n\"\n               \"})\",\n      \"throws\", \"TypeError: Callback returned incorrect type; expected 'NumberBox'\");\n\n  e.expectEval(\"class Frobber {\\n\"\n               \"  constructor(s, n) {\\n\"\n               \"    this.s = s;\\n\"\n               \"    this.n = n;\\n\"\n               \"  }\\n\"\n               \"  frob(m) {\\n\"\n               \"    return this.s + (m + this.n);\\n\"\n               \"  }\\n\"\n               \"  optionalFrob(m) {\\n\"\n               \"    return 'opn' + this.s + (m + this.n);\\n\"\n               \"  }\\n\"\n               \"  maybeFrob(m) {\\n\"\n               \"    return 'mby' + this.s + (m + this.n);\\n\"\n               \"  }\\n\"\n               \"}\\n\"\n               \"callConstructor(Frobber)\",\n      \"string\", \"foo123foo444opnfoo777mbyfoo1110\");\n}\n\n// ========================================================================================\n\nstruct WrapContext: public ContextGlobalObject {\n  auto returnFunction(double value) {\n    return [value](Lock&, double value2) { return value + value2; };\n  }\n  auto returnFunctionWithInfo(double value) {\n    return [value](Lock&, const v8::FunctionCallbackInfo<v8::Value>& info, double value2) {\n      // Prove that we received `info` by adding in the argument count.\n      return value + value2 + info.Length();\n    };\n  }\n  auto returnFunctionMutable(double value) {\n    return [value](Lock&, double value2) mutable { return value + value2; };\n  }\n  auto returnFunctionWithInfoMutable(double value) {\n    return [value](Lock&, const v8::FunctionCallbackInfo<v8::Value>& info, double value2) mutable {\n      // Prove that we received `info` by adding in the argument count.\n      return value + value2 + info.Length();\n    };\n  }\n  auto returnFunctionReturningVoid(double value) {\n    return [value](Lock&, NumberBox& box) -> void { box.value = value; };\n  }\n\n  JSG_RESOURCE_TYPE(WrapContext) {\n    JSG_NESTED_TYPE(NumberBox);\n    JSG_METHOD(returnFunction);\n    JSG_METHOD(returnFunctionWithInfo);\n    JSG_METHOD(returnFunctionMutable);\n    JSG_METHOD(returnFunctionWithInfoMutable);\n    JSG_METHOD(returnFunctionReturningVoid);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(WrapIsolate, WrapContext, NumberBox);\n\nKJ_TEST(\"wrap functions\") {\n  Evaluator<WrapContext, WrapIsolate> e(v8System);\n\n  e.expectEval(\"returnFunction(123)(321)\", \"number\", \"444\");\n  e.expectEval(\"returnFunctionWithInfo(123)(321, '', undefined)\", \"number\", \"447\");\n  e.expectEval(\"returnFunctionMutable(123)(321)\", \"number\", \"444\");\n  e.expectEval(\"returnFunctionWithInfoMutable(123)(321, '', undefined)\", \"number\", \"447\");\n\n  e.expectEval(\"var nb = new NumberBox(321);\\n\"\n               \"var ret = returnFunctionReturningVoid(123)(nb);\\n\"\n               \"ret === undefined ? nb.value : 555\",\n      \"number\", \"123\");\n}\n\n// ========================================================================================\n\nstruct FunctionContext: public ContextGlobalObject {\n  auto test(Lock& js, Function<bool(int)> fn) {\n    return fn(js, 1);\n  }\n\n  struct Foo {\n    Function<bool()> fn;\n    JSG_STRUCT(fn);\n  };\n\n  auto test2(Lock& js, Foo foo) {\n    return foo.fn(js);\n  }\n\n  jsg::Function<double(double)> getSquare(Lock& js) {\n    jsg::Function<double(double)> result = [](Lock&, double x) { return x * x; };\n\n    // Check we can call it directly.\n    KJ_ASSERT(result(js, 11) == 121);\n\n    return result;\n  }\n\n  struct VisitDetector {\n    bool visited = false;\n    void visitForGc(GcVisitor& visitor) {\n      visited = true;\n    }\n  };\n\n  jsg::Function<int(int)> getGcLambda() {\n    return JSG_VISITABLE_LAMBDA((v1 = VisitDetector(), v2 = VisitDetector(), v3 = VisitDetector()),\n        (v1, v3), (Lock&, int i) {\n          KJ_ASSERT(i == 123);\n\n          // Should return 5, since v1 and v3 are visited but v2 is not. Note that a discovery\n          // visitation pass happens immediately upon constructing wrappers -- we don't need to wait\n          // for an actual GC pass, which is nice for this test.\n          return v1.visited + v2.visited * 2 + v3.visited * 4;\n        });\n  }\n\n  jsg::Function<int(int, int)> getTwoArgs() {\n    return JSG_VISITABLE_LAMBDA((), (), (Lock&, int i, int j) {\n      // Also test an unparenthesized comma...\n      return ++i, i * j;\n    });\n  }\n\n  kj::String testTryCatch(Lock& js, jsg::Function<int()> thrower) {\n    return js.tryCatch([&]() { return kj::str(thrower(js)); }, [&](Value exception) {\n      auto handle = exception.getHandle(js);\n      return kj::str(\"caught: \", handle);\n    });\n  }\n\n  kj::String testTryCatch2(Lock& js, jsg::Function<int()> thrower) {\n    // Here we prove that the macro is if-else friendly.\n    if (true) JSG_TRY(js) {\n        return kj::str(thrower(js));\n      }\n    JSG_CATCH(exception) {\n      auto handle = exception.getHandle(js);\n      return kj::str(\"caught: \", handle);\n    }\n    else {\n      KJ_UNREACHABLE;\n    }\n  }\n\n  kj::String testTryCatchWithOptions(Lock& js, jsg::Function<void()> thrower) {\n    // Test that JSG_CATCH can accept ExceptionToJsOptions.\n    JSG_TRY(js) {\n      thrower(js);\n      return kj::str(\"no exception\");\n    }\n    JSG_CATCH(exception, {.ignoreDetail = true}) {\n      auto handle = exception.getHandle(js);\n      return kj::str(\"caught with options: \", handle);\n    }\n  }\n\n  kj::String testNestedTryCatchInnerCatches(Lock& js, jsg::Function<void()> thrower) {\n    // Test nested JSG_TRY/JSG_CATCH where inner catches, outer doesn't see exception.\n    JSG_TRY(js) {\n      kj::String innerResult;\n      JSG_TRY(js) {\n        thrower(js);\n        innerResult = kj::str(\"inner: no exception\");\n      }\n      JSG_CATCH(innerException) {\n        innerResult = kj::str(\"inner caught: \", innerException.getHandle(js));\n      }\n      return kj::str(\"outer: no exception, \", innerResult);\n    }\n    JSG_CATCH(outerException) {\n      return kj::str(\"outer caught: \", outerException.getHandle(js));\n    }\n  }\n\n  kj::String testNestedTryCatchOuterCatches(Lock& js, jsg::Function<void()> thrower) {\n    // Test nested JSG_TRY/JSG_CATCH where inner rethrows, outer catches.\n    JSG_TRY(js) {\n      JSG_TRY(js) {\n        thrower(js);\n        return kj::str(\"inner: no exception\");\n      }\n      JSG_CATCH(innerException) {\n        // Rethrow so outer can catch\n        js.throwException(kj::mv(innerException));\n      }\n      return kj::str(\"outer: no exception\");\n    }\n    JSG_CATCH(outerException) {\n      return kj::str(\"outer caught: \", outerException.getHandle(js));\n    }\n  }\n\n  JSG_RESOURCE_TYPE(FunctionContext) {\n    JSG_METHOD(test);\n    JSG_METHOD(test2);\n    JSG_METHOD(testTryCatch);\n    JSG_METHOD(testTryCatch2);\n    JSG_METHOD(testTryCatchWithOptions);\n    JSG_METHOD(testNestedTryCatchInnerCatches);\n    JSG_METHOD(testNestedTryCatchOuterCatches);\n\n    JSG_READONLY_PROTOTYPE_PROPERTY(square, getSquare);\n    JSG_READONLY_PROTOTYPE_PROPERTY(gcLambda, getGcLambda);\n    JSG_READONLY_PROTOTYPE_PROPERTY(twoArgs, getTwoArgs);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(FunctionIsolate, FunctionContext, FunctionContext::Foo);\n\nKJ_TEST(\"jsg::Function<T>\") {\n  Evaluator<FunctionContext, FunctionIsolate> e(v8System);\n\n  e.expectEval(\"test((val) => val === 1)\", \"boolean\", \"true\");\n\n  // This variation checks that a jsg::Function pulled off a struct properly\n  // preserves \"this\" as a reference to the object it was pulled off of.\n  e.expectEval(\"const m = { fn() { return this === m; } }; test2(m);\", \"boolean\", \"true\");\n\n  e.expectEval(\"square(5)\", \"number\", \"25\");\n\n  e.expectEval(\"gcLambda(123)\", \"number\", \"5\");\n\n  e.expectEval(\"twoArgs(2, 5)\", \"number\", \"15\");\n\n  e.expectEval(\"testTryCatch(() => { return 123; })\", \"string\", \"123\");\n  e.expectEval(\"testTryCatch(() => { throw new Error('foo'); })\", \"string\", \"caught: Error: foo\");\n\n  e.expectEval(\"testTryCatch2(() => { return 123; })\", \"string\", \"123\");\n  e.expectEval(\"testTryCatch2(() => { throw new Error('foo'); })\", \"string\", \"caught: Error: foo\");\n\n  e.expectEval(\"testTryCatchWithOptions(() => {})\", \"string\", \"no exception\");\n  e.expectEval(\"testTryCatchWithOptions(() => { throw new Error('bar'); })\", \"string\",\n      \"caught with options: Error: bar\");\n\n  // Nested JSG_TRY/JSG_CATCH tests\n  e.expectEval(\"testNestedTryCatchInnerCatches(() => {})\", \"string\",\n      \"outer: no exception, inner: no exception\");\n  e.expectEval(\"testNestedTryCatchInnerCatches(() => { throw new Error('inner'); })\", \"string\",\n      \"outer: no exception, inner caught: Error: inner\");\n\n  e.expectEval(\"testNestedTryCatchOuterCatches(() => {})\", \"string\", \"inner: no exception\");\n  e.expectEval(\"testNestedTryCatchOuterCatches(() => { throw new Error('rethrown'); })\", \"string\",\n      \"outer caught: Error: rethrown\");\n}\n\nKJ_TEST(\"JSG_TRY/JSG_CATCH with TerminateExecution\") {\n  Evaluator<FunctionContext, FunctionIsolate> e(v8System);\n\n  // TerminateExecution should propagate through JSG_CATCH without being caught.\n  // The Evaluator's run() method will detect the termination and throw.\n  KJ_EXPECT_THROW_MESSAGE(\"TerminateExecution() was called\", e.run([](auto& js) {\n    // Test single-level JSG_TRY/JSG_CATCH with TerminateExecution\n    JSG_TRY(js) {\n      js.terminateExecutionNow();\n    }\n    JSG_CATCH(exception) {\n      (void)exception;\n      KJ_FAIL_ASSERT(\"TerminateExecution was caught by JSG_CATCH\");\n    }\n  }));\n\n  KJ_EXPECT_THROW_MESSAGE(\"TerminateExecution() was called\", e.run([](auto& js) {\n    // Test nested JSG_TRY/JSG_CATCH with TerminateExecution - should propagate through both\n    JSG_TRY(js) {\n      JSG_TRY(js) {\n        js.terminateExecutionNow();\n      }\n      JSG_CATCH(innerException) {\n        (void)innerException;\n        KJ_FAIL_ASSERT(\"TerminateExecution was caught by inner JSG_CATCH\");\n      }\n    }\n    JSG_CATCH(outerException) {\n      (void)outerException;\n      KJ_FAIL_ASSERT(\"TerminateExecution was caught by outer JSG_CATCH\");\n    }\n  }));\n}\n\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/function.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// INTERNAL IMPLEMENTATION FILE\n//\n// Handles wrapping a C++ function so that it can be called from JavaScript, and vice versa.\n\n#include \"jsg.h\"\n#include \"wrappable.h\"\n\n#include <workerd/jsg/meta.h>\n\n#include <v8-context.h>\n#include <v8-function.h>\n\n#include <kj/function.h>\n\nnamespace workerd::jsg {\n\ntemplate <typename Signature>\nclass WrappableFunction;\n\nclass WrappableFunctionBase: public Wrappable {\n public:\n  kj::StringPtr jsgGetMemoryName() const override {\n    return \"WrappableFunction\"_kjc;\n  }\n  void jsgGetMemoryInfo(MemoryTracker& tracker) const override {\n    Wrappable::jsgGetMemoryInfo(tracker);\n    visitForMemoryInfo(tracker);\n  }\n  virtual void visitForMemoryInfo(MemoryTracker& tracker) const {\n    // TODO(soon): Implement tracking for WrappableFunction.\n  }\n};\n\ntemplate <typename Ret, typename... Args>\nclass WrappableFunction<Ret(Args...)>: public WrappableFunctionBase {\n public:\n  WrappableFunction(bool needsGcTracing): needsGcTracing(needsGcTracing) {}\n  virtual Ret operator()(Lock& js, Args&&... args) = 0;\n\n  const bool needsGcTracing;\n\n  size_t jsgGetMemorySelfSize() const override {\n    return sizeof(WrappableFunction<Ret(Args...)>);\n  }\n};\n\ntemplate <typename Signature, typename Impl>\nclass WrappableFunctionImpl;\n\ntemplate <typename Ret, typename... Args, typename Impl>\nclass WrappableFunctionImpl<Ret(Args...), Impl>: public WrappableFunction<Ret(Args...)> {\n public:\n  WrappableFunctionImpl(Impl&& func)\n      : WrappableFunction<Ret(Args...)>(hasPublicVisitForGc<Impl>()),\n        func(kj::fwd<Impl>(func)) {}\n\n  Ret operator()(Lock& js, Args&&... args) override {\n    return func(js, kj::fwd<Args>(args)...);\n  }\n\n  void jsgVisitForGc(GcVisitor& visitor) override {\n    if constexpr (hasPublicVisitForGc<Impl>()) {\n      visitor.visit(func);\n    }\n  }\n\n private:\n  Impl func;\n};\n\ntemplate <typename TypeWrapper, typename Signature, typename = ArgumentIndexes<Signature>>\nstruct FunctorCallback;\n\ntemplate <typename TypeWrapper, typename Ret, typename... Args, size_t... indexes>\nstruct FunctorCallback<TypeWrapper, Ret(Args...), kj::_::Indexes<indexes...>> {\n  static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {\n    liftKj(args, [&]() {\n      auto isolate = args.GetIsolate();\n      auto context = isolate->GetCurrentContext();\n      auto& js = Lock::from(isolate);\n      auto& wrapper = TypeWrapper::from(isolate);\n      auto& func = extractInternalPointer<WrappableFunction<Ret(Args...)>, false>(\n          context, args.Data().As<v8::Object>());\n\n      if constexpr (isVoid<Ret>()) {\n        func(Lock::from(isolate),\n            wrapper.template unwrap<Args>(\n                js, context, args, indexes, TypeErrorContext::callbackArgument(indexes))...);\n      } else {\n        return wrapper.wrap(js, context, args.This(),\n            func(Lock::from(isolate),\n                wrapper.template unwrap<Args>(\n                    js, context, args, indexes, TypeErrorContext::callbackArgument(indexes))...));\n      }\n    });\n  }\n};\n\n// Specialization for functions that take `const v8::FunctionCallbackInfo<v8::Value>&` as their\n// second parameter (after Lock&).\ntemplate <typename TypeWrapper, typename Ret, typename... Args, size_t... indexes>\nstruct FunctorCallback<TypeWrapper,\n    Ret(const v8::FunctionCallbackInfo<v8::Value>&, Args...),\n    kj::_::Indexes<indexes...>> {\n  static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {\n    liftKj(args, [&]() {\n      auto isolate = args.GetIsolate();\n      auto context = isolate->GetCurrentContext();\n      auto& wrapper = TypeWrapper::from(isolate);\n      auto& js = Lock::from(isolate);\n      auto& func = extractInternalPointer<\n          WrappableFunction<Ret(const v8::FunctionCallbackInfo<v8::Value>&, Args...)>, false>(\n          context, args.Data().As<v8::Object>());\n\n      if constexpr (isVoid<Ret>()) {\n        func(js, args,\n            wrapper.template unwrap<Args>(\n                js, context, args, indexes, TypeErrorContext::callbackArgument(indexes))...);\n      } else {\n        return wrapper.wrap(js, context, args.This(),\n            func(js, args,\n                wrapper.template unwrap<Args>(\n                    js, context, args, indexes, TypeErrorContext::callbackArgument(indexes))...));\n      }\n    });\n  }\n};\n\ntemplate <typename Ret, typename... Args>\nclass Function<Ret(Args...)> {\n  // (Docs are in jsg.h, the public header.)\n\n public:\n  using NativeFunction = jsg::WrappableFunction<Ret(Args...)>;\n\n  // When holding a JavaScript function, `Wrapper` is a C++ function that will handle converting\n  // C++ arguments into JavaScript values and then call the JS function.\n  using Wrapper = Ret(jsg::Lock& js,\n      v8::Local<v8::Value> receiver,  // the `this` value in the function\n      v8::Local<v8::Function> fn,\n      Args...);\n\n  Function(Wrapper* wrapper, V8Ref<v8::Object> receiver, V8Ref<v8::Function> function)\n      : Function(wrapper, receiver.cast<v8::Value>(jsg::Lock::current()), kj::mv(function)) {}\n\n  // Construct jsg::Function wrapping a JavaScript function.\n  Function(Wrapper* wrapper, Value receiver, V8Ref<v8::Function> function)\n      : impl(JsImpl{\n          .wrapper = kj::mv(wrapper), .receiver = kj::mv(receiver), .handle = kj::mv(function)}) {}\n\n  // Construct jsg::Function wrapping a C++ function. The parameter can be a lambda or anything\n  // else with operator() with a compatible signature. If the parameter has a visitForGc(GcVisitor&)\n  // method, then GC visitation will be arranged.\n  template <typename Func,\n      typename = decltype(kj::instance<Func>()(kj::instance<Lock&>(), kj::instance<Args>()...))>\n  Function(Func&& func)\n      : impl(Ref<NativeFunction>(\n            alloc<WrappableFunctionImpl<Ret(Args...), Func>>(kj::fwd<Func>(func)))) {}\n\n  Function(Function&&) = default;\n  Function& operator=(Function&&) = default;\n  KJ_DISALLOW_COPY(Function);\n\n  Ret operator()(jsg::Lock& jsl, Args... args) {\n    KJ_SWITCH_ONEOF(impl) {\n      KJ_CASE_ONEOF(native, Ref<NativeFunction>) {\n        return (*native)(jsl, kj::fwd<Args>(args)...);\n      }\n      KJ_CASE_ONEOF(js, JsImpl) {\n        return (*js.wrapper)(\n            jsl, js.receiver.getHandle(jsl), js.handle.getHandle(jsl), kj::fwd<Args>(args)...);\n      }\n    }\n    __builtin_unreachable();\n  }\n\n  // Get a handle to the underlying function. If this is a native function,\n  // `makeNativeWrapper(Ref<Func>&)` is called to create the wrapper.\n  //\n  // Only the `FunctionWrapper` TypeWrapper mixin should call this. Anyone else needs to call\n  // `tryGetHandle()`.\n  template <typename MakeNativeWrapperFunc>\n  v8::Local<v8::Function> getOrCreateHandle(\n      v8::Isolate* isolate, MakeNativeWrapperFunc&& makeNativeWrapper) {\n    KJ_SWITCH_ONEOF(impl) {\n      KJ_CASE_ONEOF(native, Ref<NativeFunction>) {\n        return makeNativeWrapper(native);\n      }\n      KJ_CASE_ONEOF(js, JsImpl) {\n        return js.handle.getHandle(isolate);\n      }\n    }\n    __builtin_unreachable();\n  }\n\n  // Like getHandle() but if there's no wrapper yet, returns null.\n  kj::Maybe<v8::Local<v8::Function>> tryGetHandle(v8::Isolate* isolate) {\n    KJ_SWITCH_ONEOF(impl) {\n      KJ_CASE_ONEOF(native, Ref<NativeFunction>) {\n        return kj::none;\n      }\n      KJ_CASE_ONEOF(js, JsImpl) {\n        return js.handle.getHandle(isolate);\n      }\n    }\n    __builtin_unreachable();\n  }\n\n  inline void visitForGc(GcVisitor& visitor) {\n    KJ_SWITCH_ONEOF(impl) {\n      KJ_CASE_ONEOF(native, Ref<NativeFunction>) {\n        visitor.visit(native);\n      }\n      KJ_CASE_ONEOF(js, JsImpl) {\n        visitor.visit(js.receiver, js.handle);\n      }\n    }\n  }\n\n  inline Function<Ret(Args...)> addRef(v8::Isolate* isolate) {\n    KJ_SWITCH_ONEOF(impl) {\n      KJ_CASE_ONEOF(native, Ref<NativeFunction>) {\n        return Function<Ret(Args...)>(native.addRef());\n      }\n      KJ_CASE_ONEOF(js, JsImpl) {\n        return Function<Ret(Args...)>(\n            js.wrapper, js.receiver.addRef(isolate), js.handle.addRef(isolate));\n      }\n    }\n    __builtin_unreachable();\n  }\n\n  inline Function<Ret(Args...)> addRef(Lock& js) {\n    KJ_SWITCH_ONEOF(impl) {\n      KJ_CASE_ONEOF(native, Ref<NativeFunction>) {\n        return Function<Ret(Args...)>(native.addRef());\n      }\n      KJ_CASE_ONEOF(jsi, JsImpl) {\n        return Function<Ret(Args...)>(jsi.wrapper, jsi.receiver.addRef(js), jsi.handle.addRef(js));\n      }\n    }\n    __builtin_unreachable();\n  }\n\n  inline void setReceiver(Value receiver) {\n    KJ_IF_SOME(i, impl.template tryGet<JsImpl>()) {\n      i.receiver = kj::mv(receiver);\n    }\n  }\n\n  JSG_MEMORY_INFO(Function) {\n    KJ_SWITCH_ONEOF(impl) {\n      KJ_CASE_ONEOF(ref, Ref<NativeFunction>) {\n        tracker.trackField(\"native\", ref);\n      }\n      KJ_CASE_ONEOF(impl, JsImpl) {\n        tracker.trackField(\"impl\", impl);\n      }\n    }\n  }\n\n private:\n  Function(Ref<NativeFunction>&& func): impl(kj::mv(func)) {}\n\n  struct JsImpl {\n    Wrapper* wrapper;\n    Value receiver;\n    V8Ref<v8::Function> handle;\n\n    JSG_MEMORY_INFO(JsImpl) {\n      tracker.trackField(\"receiver\", receiver);\n      tracker.trackField(\"handle\", handle);\n    }\n  };\n\n  kj::OneOf<Ref<NativeFunction>, JsImpl> impl;\n  friend class MemoryTracker;\n};\n\ntemplate <typename T>\nclass Constructor: public jsg::Function<T> {\n public:\n  using jsg::Function<T>::Function;\n};\n\ntemplate <typename Method>\nstruct MethodSignature_;\ntemplate <typename T, typename Ret, typename... Args>\nstruct MethodSignature_<Ret (T::*)(Lock&, Args...)> {\n  using Type = Ret(Args...);\n};\ntemplate <typename T, typename Ret, typename... Args>\nstruct MethodSignature_<Ret (T::*)(Lock&, Args...) const> {\n  using Type = Ret(Args...);\n};\n\n// Extracts a function signature from a method type.\ntemplate <typename Method>\nusing MethodSignature = MethodSignature_<Method>::Type;\n\n// TypeWrapper mixin for functions / lambdas.\ntemplate <typename TypeWrapper>\nclass FunctionWrapper {\n\n public:\n  template <typename Func, typename = decltype(&kj::Decay<Func>::operator())>\n  static constexpr const char* getName(Func*) {\n    return \"function\";\n  }\n\n  template <typename Func,\n      typename Signature = MethodSignature<decltype(&kj::Decay<Func>::operator())>>\n  v8::Local<v8::Function> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      Func&& func) {\n    return wrap(js, context, creator, jsg::Function<Signature>(kj::mv(func)));\n  }\n\n  template <typename Signature>\n  v8::Local<v8::Function> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      Function<Signature>&& func) {\n    v8::Isolate* isolate = js.v8Isolate;\n    return func.getOrCreateHandle(isolate, [&](Ref<WrappableFunction<Signature>>& ref) {\n      v8::Local<v8::Object> data;\n      KJ_IF_SOME(h, ref->tryGetHandle(isolate)) {\n        // Apparently, this function has been wrapped before and already has an opaque handle.\n        // That's interesting. However, unfortunately, we don't have a handle to the v8::Function\n        // that was created last time, so we can't return the same function instance. This is\n        // arguably incorrect; what if the application added properties to it or something?\n        //\n        // Unfortunately, it is exceedingly difficult for us to store the function handle for\n        // reuse without introducing performance problems.\n        // - Ideally, we'd use the v8::Function itself as the object's wrapper, rather than an\n        //   \"opaque\" wrapper. However, this doesn't work, because we can't attach internal fields\n        //   to it. v8::Function::New() does not let us specify an internal field count. We can\n        //   specify internal fields if we create a FunctionTemplate and then create the Function\n        //   from that, but a FunctionTemplate only instantiates one Function (per Context). We\n        //   need a separate Function instance for object we want to wrap. So... this doesn't work.\n        //   (Note that V8's heap tracing API deeply depends on wrapper objects having two internal\n        //   fields, so using other schemes like v8::External doesn't help either.)\n        // - Another approach might be to store the v8::Function on the WrappableFunction, once\n        //   it's created. This is a cyclic reference, but we could rely on GC visitation to\n        //   collect it. The problem is, cyclic references can only be collected by tracing, not\n        //   by scavenging. Tracing runs much less often than scavenging. So we'd be forcing every\n        //   function object to live on the heap longer than otherwise necessary.\n        //\n        // In practice, it probably never matters that returning the same jsg::Function twice\n        // produces exactly the same JavaScript handle. So... screw it.\n        data = h;\n      } else {\n        data = ref->attachOpaqueWrapper(context, ref->needsGcTracing);\n      }\n\n      // TODO(conform): Correctly set `length` on all functions. Probably doesn't need a compat flag\n      //   but I'd like to do it as a separate commit which can be reverted. We also currently fail\n      //   to set this on constructors and methods (see resource.h). Remember not to count\n      //   injected parameters!\n      return check(\n          v8::Function::New(context, &FunctorCallback<TypeWrapper, Signature>::callback, data));\n    });\n  }\n\n  template <typename Ret, typename... Args>\n  kj::Maybe<Constructor<Ret(Args...)>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      Constructor<Ret(Args...)>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (!handle->IsFunction()) {\n      return kj::none;\n    }\n\n    auto isolate = js.v8Isolate;\n\n    auto wrapperFn = [](Lock& js, v8::Local<v8::Value> receiver, v8::Local<v8::Function> func,\n                         Args... args) -> Ret {\n      auto isolate = js.v8Isolate;\n      auto& typeWrapper = TypeWrapper::from(isolate);\n\n      return js.withinHandleScope([&] {\n        auto context = js.v8Context();\n        v8::Local<v8::Value> argv[sizeof...(Args)]{\n          typeWrapper.wrap(js, context, kj::none, kj::fwd<Args>(args))...};\n\n        v8::Local<v8::Object> result = check(func->NewInstance(context, sizeof...(Args), argv));\n        return typeWrapper.template unwrap<Ret>(\n            js, context, result, TypeErrorContext::callbackReturn());\n      });\n    };\n\n    return Constructor<Ret(Args...)>(wrapperFn,\n        V8Ref(isolate, parentObject.orDefault(context->Global())),\n        V8Ref(isolate, handle.As<v8::Function>()));\n  }\n\n  template <typename Ret, typename... Args>\n  kj::Maybe<Function<Ret(Args...)>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      Function<Ret(Args...)>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (!handle->IsFunction()) {\n      return kj::none;\n    }\n\n    auto isolate = js.v8Isolate;\n\n    auto wrapperFn = [](Lock& js, v8::Local<v8::Value> receiver, v8::Local<v8::Function> func,\n                         Args... args) -> Ret {\n      auto isolate = js.v8Isolate;\n      auto& typeWrapper = TypeWrapper::from(isolate);\n\n      return js.withinHandleScope([&] {\n        auto context = js.v8Context();\n        v8::LocalVector<v8::Value> argv(js.v8Isolate,\n            std::initializer_list<v8::Local<v8::Value>>{\n              typeWrapper.wrap(js, context, kj::none, kj::fwd<Args>(args))\n                  .template As<v8::Value>()...});\n\n        auto result = check(func->Call(context, receiver, argv.size(), argv.data()));\n        if constexpr (!isVoid<Ret>()) {\n          return typeWrapper.template unwrap<Ret>(\n              js, context, result, TypeErrorContext::callbackReturn());\n        } else {\n          return;\n        }\n      });\n    };\n\n    return Function<Ret(Args...)>(wrapperFn,\n        V8Ref(isolate, parentObject.orDefault(context->Global())),\n        V8Ref(isolate, handle.As<v8::Function>()));\n  }\n\n  template <typename Ret>\n  kj::Maybe<Function<Ret(Arguments<Value>)>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      Function<Ret(Arguments<Value>)>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (!handle->IsFunction()) {\n      return kj::none;\n    }\n\n    auto isolate = js.v8Isolate;\n\n    auto wrapperFn = [](Lock& js, v8::Local<v8::Value> receiver, v8::Local<v8::Function> func,\n                         Arguments<Value> args) -> Ret {\n      auto isolate = js.v8Isolate;\n      auto& typeWrapper = TypeWrapper::from(isolate);\n\n      return js.withinHandleScope([&] {\n        auto context = js.v8Context();\n\n        v8::Local<v8::Value> result;\n        if (args.size() > 0) {\n          v8::LocalVector<v8::Value> argv(js.v8Isolate, args.size());\n          for (size_t n = 0; n < args.size(); n++) {\n            argv[n] = args[n].getHandle(js);\n          }\n          result = check(func->Call(context, receiver, argv.size(), argv.data()));\n        } else {\n          result = check(func->Call(context, receiver, 0, nullptr));\n        }\n\n        if constexpr (!isVoid<Ret>()) {\n          return typeWrapper.template unwrap<Ret>(\n              js, context, result, TypeErrorContext::callbackReturn());\n        } else {\n          return;\n        }\n      });\n    };\n\n    return Function<Ret(Arguments<Value>)>(wrapperFn,\n        V8Ref(isolate, parentObject.orDefault(context->Global())),\n        V8Ref(isolate, handle.As<v8::Function>()));\n  }\n};\n\ntemplate <typename Func>\nclass VisitableLambda {\n public:\n  VisitableLambda(Func&& func): func(kj::fwd<Func>(func)) {}\n\n  template <typename... Params>\n  auto operator()(Params&&... params) {\n    return func(kj::fwd<Params>(params)...);\n  }\n\n  void visitForGc(GcVisitor& visitor) {\n    func(visitor);\n  }\n\n private:\n  Func func;\n};\n\ntemplate <typename... Params>\nconstexpr bool isGcVisitor() {\n  return false;\n}\ntemplate <>\nconstexpr bool isGcVisitor<GcVisitor&>() {\n  return true;\n}\n\n#define JSG_VISITABLE_LAMBDA(CAPTURES, VISITS, ...)                                                \\\n  ::workerd::jsg::VisitableLambda([JSG_EXPAND CAPTURES](auto&&... params) mutable {                \\\n    if constexpr (::workerd::jsg::isGcVisitor<decltype(params)...>()) {                            \\\n      (params.visit VISITS, ...);                                                                  \\\n    } else {                                                                                       \\\n      return ([&] __VA_ARGS__)(kj::fwd<decltype(params)>(params)...);                              \\\n    }                                                                                              \\\n  })\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/inspector.c++",
    "content": "#include \"inspector.h\"\n\n#include \"jsg.h\"\n#include \"jsvalue.h\"\n#include \"simdutf.h\"\n#include \"util.h\"\n\n#include <v8-inspector.h>\n\n#include <kj/encoding.h>\n#include <kj/string.h>\n\nnamespace v8_inspector {\nkj::String KJ_STRINGIFY(const v8_inspector::StringView& view) {\n  if (view.is8Bit()) {\n    auto bytes = kj::arrayPtr(view.characters8(), view.length());\n    for (auto b: bytes) {\n      if (b & 0x80) {\n        // Ugh, the bytes aren't just ASCII. We need to re-encode.\n        auto utf16 = kj::heapArray<char16_t>(bytes.size());\n        for (auto i: kj::indices(bytes)) {\n          utf16[i] = bytes[i];\n        }\n        return kj::decodeUtf16(utf16);\n      }\n    }\n\n    // Looks like it's all ASCII.\n    return kj::str(bytes.asChars());\n  } else {\n    return kj::decodeUtf16(\n        kj::arrayPtr(reinterpret_cast<const char16_t*>(view.characters16()), view.length()));\n  }\n}\n}  // namespace v8_inspector\n\nnamespace workerd::jsg {\nnamespace {\nclass StringViewWithScratch: public v8_inspector::StringView {\n public:\n  StringViewWithScratch(v8_inspector::StringView text, kj::Array<char16_t>&& scratch)\n      : v8_inspector::StringView(text),\n        scratch(kj::mv(scratch)) {}\n\n private:\n  kj::Array<char16_t> scratch;\n};\n}  // namespace\n\nv8_inspector::StringView toInspectorStringView(kj::StringPtr text) {\n  bool isAscii = simdutf::validate_ascii(text.begin(), text.size());\n\n  if (isAscii) {\n    return StringViewWithScratch(\n        v8_inspector::StringView(text.asBytes().begin(), text.size()), nullptr);\n  } else {\n    kj::Array<char16_t> scratch = kj::encodeUtf16(text);\n    return StringViewWithScratch(\n        v8_inspector::StringView(reinterpret_cast<uint16_t*>(scratch.begin()), scratch.size()),\n        kj::mv(scratch));\n  }\n}\n\n// Inform the inspector of a problem not associated with any particular exception object.\n//\n// Passes `description` as the exception's detailed message, dummy values for everything else.\nvoid sendExceptionToInspector(\n    jsg::Lock& js, v8_inspector::V8Inspector& inspector, kj::StringPtr description) {\n  inspector.exceptionThrown(js.v8Context(), v8_inspector::StringView(), v8::Local<v8::Value>(),\n      jsg::toInspectorStringView(description), v8_inspector::StringView(), 0, 0, nullptr, 0);\n}\n\nvoid sendExceptionToInspector(jsg::Lock& js,\n    v8_inspector::V8Inspector& inspector,\n    kj::String source,\n    const jsg::JsValue& exception,\n    jsg::JsMessage message) {\n  if (!message) {\n    // This exception didn't come with a Message. This can happen for exceptions delivered via\n    // v8::Promise::Catch(), or for exceptions which were tunneled through C++ promises. In the\n    // latter case, V8 will create a Message based on the current stack trace, but it won't be\n    // super meaningful.\n    message = jsg::JsMessage::create(js, jsg::JsValue(exception));\n  }\n\n  // TODO(cleanup): Move the inspector stuff into a utility within jsg to better\n  // encapsulate\n  KJ_ASSERT(message);\n  v8::Local<v8::Message> msg = message;\n\n  auto context = js.v8Context();\n\n  auto stackTrace = msg->GetStackTrace();\n\n  // The resource name is whatever we set in the Script ctor, e.g. \"worker.js\".\n  auto scriptResourceName = kj::str(msg->GetScriptResourceName());\n  auto detailedMessage = kj::str(msg->Get());\n\n  auto lineNumber = msg->GetLineNumber(context).FromMaybe(0);\n  auto startColumn = msg->GetStartColumn(context).FromMaybe(0);\n\n  // TODO(soon): EW-2636 Pass a real \"script ID\" as the last parameter instead of 0. I suspect this\n  //   has something to do with the incorrect links in the console when it logs uncaught exceptions.\n  inspector.exceptionThrown(context, jsg::toInspectorStringView(source), exception,\n      jsg::toInspectorStringView(detailedMessage), jsg::toInspectorStringView(scriptResourceName),\n      lineNumber, startColumn, inspector.createStackTrace(stackTrace), 0);\n}\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/inspector.h",
    "content": "#pragma once\n\nnamespace kj {\nclass String;\nclass StringPtr;\n}  // namespace kj\n\nnamespace v8_inspector {\nclass V8Inspector;\nclass StringView;\n}  // namespace v8_inspector\n\nnamespace workerd::jsg {\n\nclass Lock;\nclass JsValue;\nclass JsMessage;\n\nv8_inspector::StringView toInspectorStringView(kj::StringPtr text);\n\n// Inform the inspector of a problem not associated with any particular exception object.\n//\n// Passes `description` as the exception's detailed message, dummy values for everything else.\nvoid sendExceptionToInspector(\n    jsg::Lock& js, v8_inspector::V8Inspector& inspector, kj::StringPtr description);\n\n// Inform the inspector of an exception thrown.\n//\n// Passes `source` as the exception's short message. Reconstructs `message` from `exception` if\n// `message` is empty.\nvoid sendExceptionToInspector(jsg::Lock& js,\n    v8_inspector::V8Inspector& inspector,\n    kj::String source,\n    const JsValue& exception,\n    JsMessage message);\n\n}  // namespace workerd::jsg\n\nnamespace v8_inspector {\nkj::String KJ_STRINGIFY(const v8_inspector::StringView& view);\n}\n"
  },
  {
    "path": "src/workerd/jsg/iterator-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"jsg-test.h\"\n\nnamespace workerd::jsg::test {\nnamespace {\n\nV8System v8System;\n\nstruct GeneratorContext: public Object, public ContextGlobal {\n\n  kj::Array<kj::String> generatorTest(Lock& js, Generator<kj::String> generator) {\n    kj::Vector<kj::String> items;\n    while (true) {\n      KJ_IF_SOME(item, generator.next(js)) {\n        items.add(kj::mv(item));\n      } else {\n        break;\n      }\n    }\n    return items.releaseAsArray();\n  }\n\n  uint generatorErrorTest(Lock& js, Generator<kj::String> generator) {\n    uint count = 0;\n\n    // First call to next() should succeed and return \"a\"\n    KJ_IF_SOME(val, generator.next(js)) {\n      KJ_ASSERT(val == \"a\");\n      ++count;\n    }\n\n    // Second call - we'll throw an error, which should trigger the generator's\n    // throw handler (the catch block), which yields \"c\"\n    KJ_IF_SOME(val, generator.throw_(js, js.v8Ref<v8::Value>(js.str(\"boom\"_kj)))) {\n      KJ_ASSERT(val == \"c\");\n      ++count;\n    }\n\n    return count;\n  }\n\n  uint sequenceOfSequenceTest(Lock& js, Sequence<Sequence<kj::String>> sequence) {\n    uint count = 0;\n    for (auto& mem: sequence) {\n      count += mem.size();\n    }\n    return count;\n  }\n\n  uint asyncGeneratorTest(Lock& js, AsyncGenerator<kj::String> generator) {\n    uint count = 0;\n    bool finished = false;\n\n    // Get first item\n    generator.next(js)\n        .then(js, [&count, &generator](auto& js, auto value) {\n      KJ_ASSERT(KJ_ASSERT_NONNULL(value) == \"a\");\n      ++count;\n\n      // After getting first item, call return_() to terminate early\n      return generator.return_(js, kj::str(\"foo\")).then(js, [&count](auto& js, auto value) {\n        // return_() should give us back \"foo\" and mark as done\n        KJ_ASSERT(KJ_ASSERT_NONNULL(value) == \"foo\");\n        ++count;\n        return js.resolvedPromise();\n      });\n    }).then(js, [&finished](auto& js) {\n      finished = true;\n      return js.resolvedPromise();\n    });\n\n    js.runMicrotasks();\n\n    KJ_ASSERT(finished);\n    KJ_ASSERT(count == 2);\n\n    return count;\n  }\n\n  uint asyncGeneratorErrorTest(Lock& js, AsyncGenerator<kj::String> generator) {\n    uint count = 0;\n    bool finished = false;\n\n    // First call to next() should succeed and return \"a\"\n    generator.next(js)\n        .then(js, [&count, &generator](auto& js, auto value) {\n      KJ_ASSERT(KJ_ASSERT_NONNULL(value) == \"a\");\n      ++count;\n\n      // Second call - throw an error, which should trigger the generator's\n      // throw handler (the catch block), which yields \"c\"\n      return generator.throw_(js, js.template v8Ref<v8::Value>(js.str(\"boom\"_kj)))\n          .then(js, [&count](auto& js, auto value) {\n        KJ_ASSERT(KJ_ASSERT_NONNULL(value) == \"c\");\n        ++count;\n        return js.resolvedPromise();\n      });\n    }).then(js, [&finished](auto& js) {\n      finished = true;\n      return js.resolvedPromise();\n    });\n\n    js.runMicrotasks();\n\n    KJ_ASSERT(finished);\n    KJ_ASSERT(count == 2);\n\n    return count;\n  }\n\n  void manualAsyncGeneratorTest(Lock& js, AsyncGenerator<kj::String> generator) {\n    uint calls = 0;\n    generator.next(js).then(js, [&calls](auto& js, auto value) {\n      calls++;\n      KJ_ASSERT(KJ_ASSERT_NONNULL(value) == \"a\");\n      return js.resolvedPromise();\n    });\n\n    generator.next(js).then(js, [&calls](auto& js, auto value) {\n      calls++;\n      KJ_ASSERT(KJ_ASSERT_NONNULL(value) == \"b\");\n      return js.resolvedPromise();\n    });\n\n    generator.next(js).then(js, [&calls](auto& js, auto value) {\n      calls++;\n      KJ_ASSERT(value == kj::none);\n    });\n\n    js.runMicrotasks();\n    KJ_ASSERT(calls == 3);\n  }\n\n  void manualAsyncGeneratorTestEarlyReturn(Lock& js, AsyncGenerator<kj::String> generator) {\n    uint calls = 0;\n    generator.next(js).then(js, [&calls](auto& js, auto value) {\n      calls++;\n      KJ_ASSERT(KJ_ASSERT_NONNULL(value) == \"a\");\n      return js.resolvedPromise();\n    });\n\n    generator.return_(js, kj::str(\"foo\")).then(js, [&calls](auto& js, auto value) {\n      calls++;\n      KJ_ASSERT(KJ_ASSERT_NONNULL(value) == \"foo\");\n      return js.resolvedPromise();\n    });\n\n    generator.next(js).then(js, [&calls](auto& js, auto value) {\n      calls++;\n      KJ_ASSERT(value == kj::none);\n    });\n\n    js.runMicrotasks();\n    KJ_ASSERT(calls == 3);\n  }\n\n  void manualAsyncGeneratorTestThrow(Lock& js, AsyncGenerator<kj::String> generator) {\n    uint calls = 0;\n    generator.next(js).then(js, [&calls](auto& js, auto value) {\n      calls++;\n      KJ_ASSERT(KJ_ASSERT_NONNULL(value) == \"a\");\n      return js.resolvedPromise();\n    });\n\n    // The default implementation of throw on the Async generator will result in a\n    // rejected promise being returned by generator.throw_(...)\n    generator.throw_(js, js.v8Ref<v8::Value>(js.str(\"boom\"_kj)))\n        .catch_(js, [&calls](jsg::Lock& js, jsg::Value exception) {\n      calls++;\n      return kj::Maybe<kj::String>(kj::none);\n    });\n\n    generator.next(js).then(js, [&calls](auto& js, auto value) {\n      calls++;\n      KJ_ASSERT(value == kj::none);\n    });\n\n    js.runMicrotasks();\n    KJ_ASSERT(calls == 3);\n  }\n\n  struct Test {\n    int foo;\n    JSG_STRUCT(foo);\n  };\n\n  void generatorWrongType(Lock& js, Generator<Test> generator) {\n    // This should throw a type error when trying to unwrap the value\n    generator.next(js);\n  }\n\n  void generatorReturnNotCallable(Lock& js, Generator<kj::String> generator) {\n    // Per the GetMethod spec, if the 'return' property exists on the iterator\n    // but is not callable, calling return_() should throw a TypeError.\n    generator.return_(js);\n  }\n\n  void asyncGeneratorReturnNotCallable(Lock& js, AsyncGenerator<kj::String> generator) {\n    // Per the GetMethod spec, if the 'return' property exists on the async iterator\n    // but is not callable, calling return_() should produce a rejected promise with\n    // a TypeError.\n    bool gotRejection = false;\n    generator.return_(js).catch_(js, [&gotRejection](jsg::Lock& js, jsg::Value exception) {\n      gotRejection = true;\n      return kj::Maybe<kj::String>(kj::none);\n    });\n    js.runMicrotasks();\n    KJ_ASSERT(gotRejection);\n  }\n\n  JSG_RESOURCE_TYPE(GeneratorContext) {\n    JSG_METHOD(generatorTest);\n    JSG_METHOD(generatorErrorTest);\n    JSG_METHOD(sequenceOfSequenceTest);\n    JSG_METHOD(generatorWrongType);\n    JSG_METHOD(generatorReturnNotCallable);\n    JSG_METHOD(asyncGeneratorTest);\n    JSG_METHOD(asyncGeneratorErrorTest);\n    JSG_METHOD(manualAsyncGeneratorTest);\n    JSG_METHOD(manualAsyncGeneratorTestEarlyReturn);\n    JSG_METHOD(manualAsyncGeneratorTestThrow);\n    JSG_METHOD(asyncGeneratorReturnNotCallable);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(GeneratorIsolate, GeneratorContext, GeneratorContext::Test);\n\nKJ_TEST(\"Generator works\") {\n  Evaluator<GeneratorContext, GeneratorIsolate> e(v8System);\n\n  e.expectEval(\"generatorTest([undefined,2,3])\", \"object\", \"undefined,2,3\");\n\n  e.expectEval(\n      \"function* gen() { try { yield 'a'; yield 'b'; yield 'c'; } finally { yield 'd'; } };\"\n      \"generatorTest(gen())\",\n      \"object\", \"a,b,c,d\");\n\n  e.expectEval(\"function* gen() { try { yield 'a'; yield 'b'; } catch { yield 'c' } }; \"\n               \"generatorErrorTest(gen())\",\n      \"number\", \"2\");\n\n  e.expectEval(\"sequenceOfSequenceTest([['a','b'],['c', undefined]])\", \"number\", \"4\");\n\n  e.expectEval(\"generatorWrongType(['a'])\", \"throws\",\n      \"TypeError: Incorrect type: the provided value is not of type 'Test'.\");\n\n  // Per the GetMethod spec, if the 'return' property exists but is not callable,\n  // calling return_() should throw a TypeError.\n  e.expectEval(\"var iter = { [Symbol.iterator]() { return this; }, \"\n               \"next() { return { value: 'a', done: false }; }, \"\n               \"return: 42 }; \"\n               \"generatorReturnNotCallable(iter)\",\n      \"throws\", \"TypeError: Property 'return' is not a function\");\n}\n\nKJ_TEST(\"AsyncGenerator works\") {\n  Evaluator<GeneratorContext, GeneratorIsolate> e(v8System);\n\n  e.expectEval(\n      \"async function* foo() { yield 'a'; yield 'b'; }; asyncGeneratorTest(foo());\", \"number\", \"2\");\n\n  e.expectEval(\"async function* gen() { try { yield 'a'; yield 'b'; } catch { yield 'c' } }; \"\n               \"asyncGeneratorErrorTest(gen())\",\n      \"number\", \"2\");\n\n  e.expectEval(\"manualAsyncGeneratorTest(async function* foo() { yield 'a'; yield 'b'; }())\",\n      \"undefined\", \"undefined\");\n\n  e.expectEval(\"manualAsyncGeneratorTestEarlyReturn(async function* foo() \"\n               \"{ yield 'a'; yield 'b'; }())\",\n      \"undefined\", \"undefined\");\n\n  // e.expectEval(\"manualAsyncGeneratorTestThrow(async function* foo() { yield 'a'; yield 'b'; }())\",\n  //     \"undefined\", \"undefined\");\n\n  // Per the GetMethod spec, if the 'return' property exists but is not callable,\n  // calling return_() should produce a rejected promise with a TypeError.\n  e.expectEval(\"var iter = { [Symbol.asyncIterator]() { return this; }, \"\n               \"next() { return Promise.resolve({ value: 'a', done: false }); }, \"\n               \"return: 42 }; \"\n               \"asyncGeneratorReturnNotCallable(iter)\",\n      \"undefined\", \"undefined\");\n}\n\n// ======================================================================================\n// Test that JSG iterators respect TerminateExecution().\n//\n// When V8 builtins like Array.from() or spread syntax iterate a C++ iterator, the loop\n// runs entirely in C++ without JS back-edge interrupt checks. Without the termination\n// check in IteratorBase::nextImpl, the iterator would keep getting called indefinitely\n// after TerminateExecution(). See https://crbug.com/v8/14681 for the same class of bug\n// in Intl.Segmenter.\n\nstruct CountingIterable: public Object {\n  int terminateAfter;\n\n  explicit CountingIterable(int terminateAfter): terminateAfter(terminateAfter) {}\n\n  struct IterState {\n    int current = 0;\n    int terminateAfter;\n  };\n\n  // After producing `terminateAfter` items, requests termination but keeps returning\n  // values. Without the termination check in nextImpl, the iterator would keep getting called.\n  static kj::Maybe<int> nextItem(Lock& js, IterState& state) {\n    if (state.current >= state.terminateAfter) {\n      js.requestTermination();\n    }\n    return state.current++;\n  }\n\n  JSG_ITERATOR(ValuesIterator, values, int, IterState, nextItem);\n\n  JSG_RESOURCE_TYPE(CountingIterable) {\n    JSG_ITERABLE(values);\n  }\n};\n\nRef<CountingIterable::ValuesIterator> CountingIterable::values(Lock&) {\n  return alloc<ValuesIterator>(IterState{.current = 0, .terminateAfter = terminateAfter});\n}\n\nstruct TerminationIteratorContext: public Object, public ContextGlobal {\n  Ref<CountingIterable> makeCountingIterable(Lock& js, int terminateAfter) {\n    return js.alloc<CountingIterable>(terminateAfter);\n  }\n\n  JSG_RESOURCE_TYPE(TerminationIteratorContext) {\n    JSG_METHOD(makeCountingIterable);\n    JSG_NESTED_TYPE(CountingIterable);\n  }\n};\n\nJSG_DECLARE_ISOLATE_TYPE(TerminationIteratorIsolate,\n    TerminationIteratorContext,\n    CountingIterable,\n    CountingIterable::ValuesIterator,\n    CountingIterable::ValuesIterator::Next);\n\nKJ_TEST(\"Iterator respects TerminateExecution via Array.from()\") {\n  Evaluator<TerminationIteratorContext, TerminationIteratorIsolate> e(v8System);\n\n  e.getIsolate().runInLockScope([&](TerminationIteratorIsolate::Lock& lock) {\n    JSG_WITHIN_CONTEXT_SCOPE(lock,\n        lock.newContext<TerminationIteratorContext>().getHandle(lock.v8Isolate),\n        [&](jsg::Lock& js) {\n      // Array.from() runs the iteration in a V8 Torque builtin without JS back-edge\n      // interrupt checks. The iterator calls TerminateExecution() after 5 items but\n      // keeps returning values. The check in nextImpl should stop the iteration.\n      v8::Local<v8::String> source =\n          jsg::v8Str(js.v8Isolate, \"Array.from(makeCountingIterable(5))\");\n      v8::Local<v8::Script> script = v8::Script::Compile(js.v8Context(), source).ToLocalChecked();\n\n      v8::TryCatch catcher(js.v8Isolate);\n      v8::Local<v8::Value> result;\n      bool completed = script->Run(js.v8Context()).ToLocal(&result);\n\n      // The script should not complete normally -- the termination check in\n      // nextImpl should throw. Without the fix, this would loop indefinitely.\n      KJ_EXPECT(!completed);\n      KJ_EXPECT(catcher.HasCaught());\n    });\n  });\n}\n\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/iterator.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"iterator.h\"\n\nnamespace workerd::jsg {\n\nkj::Maybe<jsg::Promise<void>&> AsyncIteratorImpl::maybeCurrent() {\n  if (!pendingStack.empty()) {\n    return pendingStack.back();\n  }\n  return kj::none;\n}\n\nvoid AsyncIteratorImpl::pushCurrent(Promise<void> promise) {\n  pendingStack.push_back(kj::mv(promise));\n}\n\nvoid AsyncIteratorImpl::popCurrent() {\n  if (!pendingStack.empty()) {\n    pendingStack.pop_front();\n  }\n}\n\nvoid AsyncIteratorImpl::visitForGc(jsg::GcVisitor& visitor) {\n  visitor.visitAll(pendingStack);\n}\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/iterator.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/memory.h>\n#include <workerd/jsg/struct.h>\n#include <workerd/util/weak-refs.h>\n\n#include <list>\n\nnamespace workerd::jsg {\n\n// -----------------------------------------------------------------------------\n// Generators\n\ntemplate <typename TypeWrapper>\nclass GeneratorWrapper;\n\ntemplate <typename T>\nstruct GeneratorNext {\n  bool done;\n\n  // Value should only be nullptr if done is true. It does not\n  // *have* to be nullptr if done is true, however.\n  kj::Maybe<T> value;\n};\n\ntemplate <typename Signature, typename TypeWrapper>\nstatic kj::Maybe<Signature> tryGetGeneratorFunction(\n    Lock& js, JsObject& object, kj::StringPtr name) {\n  auto value = object.get(js, name);\n  return TypeWrapper::from(js.v8Isolate)\n      .tryUnwrap(js, js.v8Context(), value, static_cast<Signature*>(nullptr),\n          kj::Maybe<v8::Local<v8::Object>>(object));\n}\n\ntemplate <typename T>\nclass Generator final {\n  // See the documentation in jsg.h\n public:\n  template <typename TypeWrapper>\n  Generator(Lock& js, JsObject object, TypeWrapper*)\n      : maybeActive(Active(js, object, static_cast<TypeWrapper*>(nullptr))) {}\n  Generator(Generator&&) = default;\n  Generator& operator=(Generator&&) = default;\n  KJ_DISALLOW_COPY(Generator);\n\n  // If nothing is returned, the generator is complete.\n  kj::Maybe<T> next(Lock& js) {\n    KJ_IF_SOME(active, maybeActive) {\n      KJ_IF_SOME(nextfn, active.maybeNext) {\n        return js.tryCatch([&] {\n          auto result = nextfn(js);\n          if (result.done || result.value == kj::none) {\n            maybeActive = kj::none;\n          }\n          return kj::mv(result.value);\n        }, [&](Value exception) { return throw_(js, kj::mv(exception)); });\n      }\n      maybeActive = kj::none;\n    }\n    return kj::none;\n  }\n\n  // If nothing is returned, the generator is complete.\n  // Per GetMethod spec (https://262.ecma-international.org/#sec-getmethod), if the 'return'\n  // property exists but is not callable, we throw a TypeError.\n  kj::Maybe<T> return_(Lock& js, kj::Maybe<T> maybeValue = kj::none) {\n    KJ_IF_SOME(active, maybeActive) {\n      // Per GetMethod spec: if property exists but is not callable, throw TypeError\n      if (active.returnExistsButNotCallable) {\n        maybeActive = kj::none;\n        JSG_FAIL_REQUIRE(TypeError, \"Property 'return' is not a function\");\n      }\n\n      KJ_IF_SOME(returnFn, active.maybeReturn) {\n        return js.tryCatch([&] {\n          auto result = returnFn(js, kj::mv(maybeValue));\n          if (result.done || result.value == kj::none) {\n            maybeActive = kj::none;\n          }\n          return kj::mv(result.value);\n        }, [&](Value exception) { return throw_(js, kj::mv(exception)); });\n      }\n      maybeActive = kj::none;\n    }\n    return kj::none;\n  }\n\n  // If nothing is returned, the generator is complete. If there\n  // is no throw handler in the generator, the method will throw.\n  // It's also possible (and even likely) that the throw handler\n  // will just re-throw the exception.\n  kj::Maybe<T> throw_(Lock& js, Value exception) {\n    KJ_IF_SOME(active, maybeActive) {\n      KJ_IF_SOME(throwFn, active.maybeThrow) {\n        return js.tryCatch([&] -> kj::Maybe<T> {\n          auto result = throwFn(js, kj::mv(exception));\n          if (result.done || result.value == kj::none) {\n            maybeActive = kj::none;\n          }\n          return kj::mv(result.value);\n        }, [&](Value exception) -> kj::Maybe<T> {\n          maybeActive = kj::none;\n          js.throwException(kj::mv(exception));\n        });\n      }\n    }\n    js.throwException(kj::mv(exception));\n  }\n\n  void visitForGc(GcVisitor& visitor) {\n    visitForGc(maybeActive);\n  }\n\n private:\n  using Next = GeneratorNext<T>;\n  using NextSignature = Function<Next()>;\n  using ReturnSignature = Function<Next(Optional<T>)>;\n  using ThrowSignature = Function<Next(Optional<Value>)>;\n\n  struct Active final {\n    kj::Maybe<NextSignature> maybeNext;\n    kj::Maybe<ReturnSignature> maybeReturn;\n    kj::Maybe<ThrowSignature> maybeThrow;\n    // Per GetMethod spec (https://262.ecma-international.org/#sec-getmethod), if the\n    // 'return' property exists but is not callable, we should throw a TypeError.\n    // We track this state to defer the error to when return_() is actually called.\n    bool returnExistsButNotCallable = false;\n\n    template <typename TypeWrapper>\n    Active(Lock& js, JsObject object, TypeWrapper*)\n        : maybeNext(tryGetGeneratorFunction<NextSignature, TypeWrapper>(js, object, \"next\"_kj)),\n          // maybeReturn is initialized in the constructor body — see below.\n          maybeThrow(tryGetGeneratorFunction<ThrowSignature, TypeWrapper>(js, object, \"throw\"_kj)) {\n      // Per the GetMethod spec (https://262.ecma-international.org/#sec-getmethod):\n      //   1. If the property value is undefined or null, the method is absent — not an error.\n      //   2. If the property value exists but is not callable, throw a TypeError.\n      //   3. If the property value is callable, use it.\n      //\n      // tryGetGeneratorFunction() calls object.get() then tryUnwrap(), which returns\n      // kj::none for both cases (1) and (2) since tryUnwrap just checks IsFunction().\n      // To distinguish them we need access to the raw property value, so we inline the\n      // lookup here rather than calling tryGetGeneratorFunction(). This also avoids a\n      // second property lookup that could trigger observable side effects from getters\n      // or return a different value on each access.\n      auto returnVal = object.get(js, \"return\"_kj);\n      if (!returnVal.isNullOrUndefined()) {\n        maybeReturn =\n            TypeWrapper::from(js.v8Isolate)\n                .tryUnwrap(js, js.v8Context(), returnVal, static_cast<ReturnSignature*>(nullptr),\n                    kj::Maybe<v8::Local<v8::Object>>(object));\n        returnExistsButNotCallable = (maybeReturn == kj::none);\n      }\n    }\n    Active(Active&&) = default;\n    Active& operator=(Active&&) = default;\n    KJ_DISALLOW_COPY(Active);\n\n    void visitForGc(GcVisitor& visitor) {\n      visitor.visit(maybeNext, maybeReturn, maybeThrow);\n    }\n  };\n  kj::Maybe<Active> maybeActive;\n};\n\ntemplate <typename T>\nclass AsyncGenerator final {\n  // See the documentation in jsg.h\n public:\n  template <typename TypeWrapper>\n  AsyncGenerator(Lock& js, JsObject object, TypeWrapper*)\n      : maybeActive(Active(js, object, static_cast<TypeWrapper*>(nullptr))),\n        maybeSelfRef(kj::rc<WeakRef<AsyncGenerator>>(kj::Badge<AsyncGenerator>{}, *this)) {}\n  AsyncGenerator(AsyncGenerator&& other)\n      : maybeActive(kj::mv(other.maybeActive)),\n        maybeSelfRef(kj::rc<WeakRef<AsyncGenerator>>(kj::Badge<AsyncGenerator>{}, *this)) {\n    // Invalidate the old WeakRef since it's being moved.\n    KJ_IF_SOME(selfRef, other.maybeSelfRef) {\n      selfRef->invalidate();\n    }\n  }\n  AsyncGenerator& operator=(AsyncGenerator&& other) {\n    if (this != &other) {\n      KJ_IF_SOME(selfRef, maybeSelfRef) {\n        selfRef->invalidate();\n      }\n      KJ_IF_SOME(selfRef, other.maybeSelfRef) {\n        selfRef->invalidate();\n      }\n      maybeActive = kj::mv(other.maybeActive);\n      maybeSelfRef = kj::rc<WeakRef<AsyncGenerator>>(kj::Badge<AsyncGenerator>{}, *this);\n    }\n    return *this;\n  }\n  KJ_DISALLOW_COPY(AsyncGenerator);\n  ~AsyncGenerator() noexcept(false) {\n    KJ_IF_SOME(selfRef, maybeSelfRef) {\n      selfRef->invalidate();\n    }\n  }\n\n  // If nothing is returned, the generator is complete.\n  Promise<kj::Maybe<T>> next(Lock& js) {\n    KJ_IF_SOME(active, maybeActive) {\n      KJ_IF_SOME(next, active.maybeNext) {\n        auto& selfRef = KJ_ASSERT_NONNULL(maybeSelfRef);\n        return js.tryCatch([&] {\n          return next(js).then(js, [ref = selfRef.addRef()](Lock& js, auto result) {\n            if (result.done || result.value == kj::none) {\n              ref->runIfAlive([&](AsyncGenerator& self) { self.maybeActive = kj::none; });\n            }\n            return js.resolvedPromise<kj::Maybe<T>>(kj::mv(result.value));\n          }, [ref = selfRef.addRef()](Lock& js, Value exception) {\n            Promise<kj::Maybe<T>> retPromise = nullptr;\n            if (ref->runIfAlive([&](AsyncGenerator& self) {\n              retPromise = self.throw_(js, kj::mv(exception));\n            })) {\n              return kj::mv(retPromise);\n            }\n            return js.rejectedPromise<kj::Maybe<T>>(kj::mv(exception));\n          });\n        }, [&](Value exception) {\n          maybeActive = kj::none;\n          return throw_(js, kj::mv(exception));\n        });\n      }\n      maybeActive = kj::none;\n    }\n\n    return js.resolvedPromise(kj::Maybe<T>(kj::none));\n  }\n\n  // If nothing is returned, the generator is complete.\n  // Per GetMethod spec (https://262.ecma-international.org/#sec-getmethod), if the 'return'\n  // property exists but is not callable, we throw a TypeError.\n  Promise<kj::Maybe<T>> return_(Lock& js, kj::Maybe<T> maybeValue = kj::none) {\n    KJ_IF_SOME(active, maybeActive) {\n      // Per GetMethod spec: if property exists but is not callable, throw TypeError\n      if (active.returnExistsButNotCallable) {\n        maybeActive = kj::none;\n        return js.rejectedPromise<kj::Maybe<T>>(\n            js.typeError(\"Property 'return' is not a function\"_kj));\n      }\n\n      KJ_IF_SOME(return_, active.maybeReturn) {\n        auto& selfRef = KJ_ASSERT_NONNULL(maybeSelfRef);\n        return js.tryCatch([&] {\n          return return_(js, kj::mv(maybeValue))\n              .then(js, [ref = selfRef.addRef()](Lock& js, auto result) {\n            if (result.done || result.value == kj::none) {\n              ref->runIfAlive([&](AsyncGenerator& self) { self.maybeActive = kj::none; });\n            }\n            return js.resolvedPromise(kj::mv(result.value));\n          }, [ref = selfRef.addRef()](Lock& js, Value exception) {\n            // Per spec, rejections from return() should be propagated directly\n            ref->runIfAlive([&](AsyncGenerator& self) { self.maybeActive = kj::none; });\n            return js.rejectedPromise<kj::Maybe<T>>(kj::mv(exception));\n          });\n        }, [&](Value exception) {\n          maybeActive = kj::none;\n          return js.rejectedPromise<kj::Maybe<T>>(kj::mv(exception));\n        });\n      }\n      maybeActive = kj::none;\n    }\n    return js.resolvedPromise(kj::Maybe<T>(kj::none));\n  }\n\n  // If nothing is returned, the generator is complete. If there\n  // is no throw handler in the generator, the method will throw.\n  // It's also possible (and even likely) that the throw handler\n  // will just re-throw the exception.\n  Promise<kj::Maybe<T>> throw_(Lock& js, Value exception) {\n    KJ_IF_SOME(active, maybeActive) {\n      KJ_IF_SOME(throw_, active.maybeThrow) {\n        auto& selfRef = KJ_ASSERT_NONNULL(maybeSelfRef);\n        return js.tryCatch([&] {\n          return throw_(js, kj::mv(exception))\n              .then(js, [ref = selfRef.addRef()](Lock& js, auto result) {\n            if (result.done || result.value == kj::none) {\n              ref->runIfAlive([&](AsyncGenerator& self) { self.maybeActive = kj::none; });\n            }\n            // In this case, the exception was handled and we might have a value to return.\n            // The generator might still be active.\n            return js.resolvedPromise(kj::mv(result.value));\n          }, [ref = selfRef.addRef()](Lock& js, Value exception) {\n            ref->runIfAlive([&](AsyncGenerator& self) { self.maybeActive = kj::none; });\n            return js.rejectedPromise<kj::Maybe<T>>(kj::mv(exception));\n          });\n        }, [&](Value exception) {\n          maybeActive = kj::none;\n          return js.rejectedPromise<kj::Maybe<T>>(kj::mv(exception));\n        });\n      }\n      maybeActive = kj::none;\n    }\n    return js.rejectedPromise<kj::Maybe<T>>(kj::mv(exception));\n  }\n\n private:\n  using Next = GeneratorNext<T>;\n  using NextSignature = Function<Promise<Next>()>;\n  using ReturnSignature = Function<Promise<Next>(Optional<T>)>;\n  using ThrowSignature = Function<Promise<Next>(Optional<Value>)>;\n\n  struct Active final {\n    kj::Maybe<NextSignature> maybeNext;\n    kj::Maybe<ReturnSignature> maybeReturn;\n    kj::Maybe<ThrowSignature> maybeThrow;\n    // Per GetMethod spec, if property exists but is not callable, we should throw TypeError.\n    // We track this state to defer the error to when return_() is actually called.\n    bool returnExistsButNotCallable = false;\n\n    template <typename TypeWrapper>\n    Active(Lock& js, JsObject object, TypeWrapper*)\n        : maybeNext(tryGetGeneratorFunction<NextSignature, TypeWrapper>(js, object, \"next\"_kj)),\n          // maybeReturn is initialized in the constructor body — see below.\n          maybeThrow(tryGetGeneratorFunction<ThrowSignature, TypeWrapper>(js, object, \"throw\"_kj)) {\n      // Per the GetMethod spec (https://262.ecma-international.org/#sec-getmethod):\n      //   1. If the property value is undefined or null, the method is absent — not an error.\n      //   2. If the property value exists but is not callable, throw a TypeError.\n      //   3. If the property value is callable, use it.\n      //\n      // tryGetGeneratorFunction() calls object.get() then tryUnwrap(), which returns\n      // kj::none for both cases (1) and (2) since tryUnwrap just checks IsFunction().\n      // To distinguish them we need access to the raw property value, so we inline the\n      // lookup here rather than calling tryGetGeneratorFunction(). This also avoids a\n      // second property lookup that could trigger observable side effects from getters\n      // or return a different value on each access.\n      auto returnVal = object.get(js, \"return\"_kj);\n      if (!returnVal.isNullOrUndefined()) {\n        maybeReturn =\n            TypeWrapper::from(js.v8Isolate)\n                .tryUnwrap(js, js.v8Context(), returnVal, static_cast<ReturnSignature*>(nullptr),\n                    kj::Maybe<v8::Local<v8::Object>>(object));\n        returnExistsButNotCallable = (maybeReturn == kj::none);\n      }\n    }\n    Active(Active&&) = default;\n    Active& operator=(Active&&) = default;\n    KJ_DISALLOW_COPY(Active);\n\n    void visitForGc(GcVisitor& visitor) {\n      visitor.visit(maybeNext, maybeReturn, maybeThrow);\n    }\n  };\n  kj::Maybe<Active> maybeActive;\n  kj::Maybe<kj::Rc<WeakRef<AsyncGenerator>>> maybeSelfRef;\n};\n\ntemplate <typename T>\nclass AsyncGeneratorIgnoringStrings final {\n public:\n  template <typename TypeWrapper>\n  AsyncGeneratorIgnoringStrings(Lock& js, JsObject object, TypeWrapper* ptr)\n      : inner(AsyncGenerator<T>(js, object, ptr)) {}\n\n  AsyncGenerator<T> release() {\n    return kj::mv(inner);\n  }\n\n private:\n  AsyncGenerator<T> inner;\n};\n\ntemplate <typename TypeWrapper>\nclass GeneratorWrapper {\n public:\n  GeneratorWrapper(const auto& config): config(getConfig(config)) {}\n\n  template <typename T>\n  static constexpr const char* getName(Generator<T>*) {\n    return \"Generator\";\n  }\n\n  template <typename T>\n  static constexpr const char* getName(AsyncGenerator<T>*) {\n    return \"AsyncGenerator\";\n  }\n\n  template <typename T>\n  static constexpr const char* getName(AsyncGeneratorIgnoringStrings<T>*) {\n    return \"AsyncGenerator\";\n  }\n\n  template <typename T>\n  static constexpr const char* getName(GeneratorNext<T>*) {\n    return \"GeneratorNext\";\n  }\n\n  template <typename T>\n  v8::Local<v8::Object> wrap(\n      Lock& js, v8::Local<v8::Context>, kj::Maybe<v8::Local<v8::Object>>, Generator<T>&&) {\n    KJ_FAIL_ASSERT(\"Generator instances do not support wrap\");\n  }\n\n  template <typename T>\n  v8::Local<v8::Object> wrap(\n      Lock& js, v8::Local<v8::Context>, kj::Maybe<v8::Local<v8::Object>>, AsyncGenerator<T>&&) {\n    KJ_FAIL_ASSERT(\"AsyncGenerator instances do not support wrap\");\n  }\n\n  template <typename T>\n  v8::Local<v8::Object> wrap(Lock& js,\n      v8::Local<v8::Context>,\n      kj::Maybe<v8::Local<v8::Object>>,\n      AsyncGeneratorIgnoringStrings<T>&&) {\n    KJ_FAIL_ASSERT(\"AsyncGenerator instances do not support wrap\");\n  }\n\n  template <typename T>\n  v8::Local<v8::Object> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>>,\n      GeneratorNext<T>&& next) {\n    KJ_FAIL_ASSERT(\"GeneratorNext instances do not support wrap\");\n  }\n  // Generator, AsyncGenerator, and GeneratorNext instances should never be\n  // passed back out into JavaScript. Use Iterators for that.\n\n  template <typename T>\n  kj::Maybe<GeneratorNext<T>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      GeneratorNext<T>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (handle->IsObject()) {\n      auto isolate = js.v8Isolate;\n      auto& typeWrapper = TypeWrapper::from(isolate);\n      auto object = handle.template As<v8::Object>();\n\n      bool done = typeWrapper.template unwrap<bool>(js, context,\n          check(object->Get(context, v8StrIntern(isolate, \"done\"_kj))), TypeErrorContext::other());\n\n      auto value = check(object->Get(context, v8StrIntern(isolate, \"value\"_kj)));\n\n      if (done) {\n        // If done is true, then it is OK if the value does not map to anything.\n        // Why are we doing it this way? Currently in the Generator pattern, there\n        // is no way of distinguishing between the generator not having any return\n        // value or the generator having undefined as a return value. Because we\n        // cannot differentiate the two, we treat undefined specially and always\n        // return nullptr in this case rather than trying to map it to anything --\n        // even if the thing we'd be mapping to can safely handle undefined as\n        // a value.\n        if (value->IsUndefined()) {\n          return GeneratorNext<T>{\n            .done = true,\n            .value = kj::none,\n          };\n        } else {\n          return GeneratorNext<T>{\n            .done = true,\n            .value =\n                typeWrapper.tryUnwrap(js, context, value, static_cast<T*>(nullptr), parentObject),\n          };\n        }\n      }\n\n      KJ_IF_SOME(v, typeWrapper.tryUnwrap(js, context, value, (T*)nullptr, parentObject)) {\n        return GeneratorNext<T>{\n          .done = false,\n          .value = kj::mv(v),\n        };\n      } else {\n        throwTypeError(js.v8Isolate, TypeErrorContext::other(),\n            TypeWrapper::getName(static_cast<T*>(nullptr)));\n      }\n    }\n\n    return kj::none;\n  }\n\n  template <typename T>\n  kj::Maybe<Generator<T>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      Generator<T>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (handle->IsString()) {\n      // In order to be able to treat a string as a generator, we need to first\n      // convert it to a String object. Yes, this means that each call to next\n      // will yield a single character from the string, which is terrible but\n      // that's the spec.\n      handle = check(handle->ToObject(context));\n    }\n    if (handle->IsObject()) {\n      auto isolate = js.v8Isolate;\n      auto object = handle.As<v8::Object>();\n      auto iter = check(object->Get(context, v8::Symbol::GetIterator(isolate)));\n      if (iter->IsFunction()) {\n        auto func = iter.As<v8::Function>();\n        auto iterObj = check(func->Call(context, object, 0, nullptr));\n        if (iterObj->IsObject()) {\n          return Generator<T>(\n              js, JsObject(iterObj.As<v8::Object>()), static_cast<TypeWrapper*>(nullptr));\n        }\n      }\n    }\n    return kj::none;\n  }\n\n  template <typename T>\n  kj::Maybe<AsyncGenerator<T>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      AsyncGenerator<T>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (handle->IsString()) {\n      // In order to be able to treat a string as a generator, we need to first\n      // convert it to a String object. Yes, this means that each call to next\n      // will yield a single character from the string, which is terrible but\n      // that's the spec.\n      handle = check(handle->ToObject(context));\n    }\n    if (handle->IsObject()) {\n      auto isolate = js.v8Isolate;\n      auto object = handle.As<v8::Object>();\n      auto iter = check(object->Get(context, v8::Symbol::GetAsyncIterator(isolate)));\n      // If there is no async iterator, let's try a sync iterator.\n      if (iter->IsNullOrUndefined()) {\n        iter = check(object->Get(context, v8::Symbol::GetIterator(isolate)));\n      }\n      if (iter->IsFunction()) {\n        auto func = iter.As<v8::Function>();\n        auto iterObj = check(func->Call(context, object, 0, nullptr));\n        if (iterObj->IsObject()) {\n          return AsyncGenerator<T>(\n              js, JsObject(iterObj.As<v8::Object>()), static_cast<TypeWrapper*>(nullptr));\n        }\n      }\n    }\n    return kj::none;\n  }\n\n  template <typename T>\n  kj::Maybe<AsyncGeneratorIgnoringStrings<T>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      AsyncGeneratorIgnoringStrings<T>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    // This variation of the wrapper is used in cases where Strings should not be treated\n    // as iterators. Specifically, for cases like `kj::OneOf<kj::String,AsyncGenerator<T>>`\n    // where we want to allow strings to be passed through as strings but also want to allow\n    // sync and async generators to be handled as well. Without this, the strings would be\n    // treated as sync iterables.\n    if (config.fetchIterableTypeSupport && handle->IsObject() && !handle->IsStringObject()) {\n      auto isolate = js.v8Isolate;\n      auto object = handle.As<v8::Object>();\n\n      auto iter = check(object->Get(context, v8::Symbol::GetAsyncIterator(isolate)));\n      // If there is no async iterator, let's try a sync iterator.\n      if (iter->IsNullOrUndefined()) {\n        // Before checking for the sync iterator, let's also check to see if the object\n        // implements a custom toString to Symbol.toPrimitive method that is not the default\n        // Object.prototype.toString. If it does, then we won't treat it as\n        // an iterator either. If the object is an Array, then we skip this check since\n        // it's exceedingly uncommon for arrays to be subclassed with a custom toString method,\n        // so much that it's not worth handling the extreme edge case.\n        // This is to deal with edge cases around objects with customized stringify methods,\n        // which are likely more common than those with customized iterator methods. While\n        // these are both rare cases, it's better to err on the side of custom stringification\n        // rather than custom iteration.\n        if (config.fetchIterableTypeSupportOverrideAdjustment && !object->IsArray()) {\n          if (protoToString == kj::none) {\n            // TODO(cleanup): In several places in the codebase we have this pattern of\n            // lazily grabbing the object prototype. We should probably centralize this\n            // an cache it in the IsolateBase or something.\n            auto obj = js.obj();\n            auto proto = obj.getPrototype(js);\n            protoToString = jsg::JsRef(\n                js, KJ_ASSERT_NONNULL(proto.tryCast<jsg::JsObject>()).get(js, \"toString\"_kj));\n            toPrimitiveString = jsg::JsRef(js,\n                KJ_ASSERT_NONNULL(proto.tryCast<jsg::JsObject>()).get(js, js.symbolToPrimitive()));\n          }\n\n          // We only check that the toString/Symbol.toPrimitive is the same value as\n          // Object.prototype.toString/Symbol.toPrimitive. This does not guarantee every\n          // possible edge case but should be sufficient for our purposes.\n          auto jsobj = JsObject(object);\n          if (jsobj.get(js, \"toString\"_kj) != KJ_ASSERT_NONNULL(protoToString).getHandle(js) ||\n              jsobj.get(js, js.symbolToPrimitive()) !=\n                  KJ_ASSERT_NONNULL(toPrimitiveString).getHandle(js)) {\n            return kj::none;\n          }\n        }\n\n        iter = check(object->Get(context, v8::Symbol::GetIterator(isolate)));\n      }\n      if (iter->IsFunction()) {\n        auto func = iter.As<v8::Function>();\n        auto iterObj = check(func->Call(context, object, 0, nullptr));\n        if (iterObj->IsObject()) {\n          return AsyncGeneratorIgnoringStrings<T>(\n              js, JsObject(iterObj.As<v8::Object>()), static_cast<TypeWrapper*>(nullptr));\n        }\n      }\n    }\n    return kj::none;\n  }\n\n private:\n  const JsgConfig config;\n  kj::Maybe<jsg::JsRef<jsg::JsValue>> protoToString;\n  kj::Maybe<jsg::JsRef<jsg::JsValue>> toPrimitiveString;\n};\n\n// -----------------------------------------------------------------------------\n// Sequences\n\ntemplate <typename T>\nstruct Sequence: public kj::Array<T> {\n  // See the documentation in jsg.h\n  Sequence() = default;\n  Sequence(kj::Array<T> items): kj::Array<T>(kj::mv(items)) {}\n};\n\ntemplate <typename TypeWrapper>\nclass SequenceWrapper {\n  // TypeWrapper mixin for Sequences.\n\n public:\n  template <typename U>\n  static constexpr const char* getName(Sequence<U>*) {\n    // TODO(later): It would be nicer if the name included the demangled name of U\n    // e.g. Sequence<Foo>\n    return \"Sequence\";\n  }\n\n  template <typename U>\n  v8::Local<v8::Value> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      Sequence<U> sequence) {\n    v8::Isolate* isolate = js.v8Isolate;\n    v8::EscapableHandleScope handleScope(isolate);\n    v8::LocalVector<v8::Value> items(isolate, sequence.size());\n    for (auto i: kj::indices(sequence)) {\n      items[i] = static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::mv(sequence[i]));\n    }\n    return handleScope.Escape(v8::Array::New(isolate, items.data(), items.size()));\n  }\n\n  template <typename U>\n  v8::Local<v8::Value> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      Sequence<U>& sequence) {\n    v8::Isolate* isolate = js.v8Isolate;\n    v8::EscapableHandleScope handleScope(isolate);\n    v8::LocalVector<v8::Value> items(isolate, sequence.size());\n    for (auto i: kj::indices(sequence)) {\n      items[i] = static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::mv(sequence[i]));\n    }\n    return handleScope.Escape(v8::Array::New(isolate, items.data(), items.size()));\n  }\n\n  template <typename U>\n  kj::Maybe<Sequence<U>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      Sequence<U>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    auto isolate = js.v8Isolate;\n    auto& typeWrapper = TypeWrapper::from(isolate);\n    // In this case, if handle is a string, we likely do not want to treat it as\n    // a sequence of characters, which the Generator case would do. If someone\n    // really wants to treat a string as a sequence of characters, then they\n    // should use the Generator interface directly.\n    if (handle->IsString()) return kj::none;\n    KJ_IF_SOME(gen,\n        typeWrapper.tryUnwrap(js, context, handle, (Generator<U>*)nullptr, parentObject)) {\n      // The generator gives us no indication of how many items there might be, so we\n      // have to just keep pulling them until it says it's done.\n      kj::Vector<U> items;\n      while (true) {\n        KJ_IF_SOME(item, gen.next(js)) {\n          items.add(kj::mv(item));\n        } else {\n          gen.return_(js, kj::none);\n          break;\n        }\n      }\n      return Sequence<U>(items.releaseAsArray());\n    }\n    return kj::none;\n  }\n};\n\n// -----------------------------------------------------------------------------\n\ntemplate <typename SelfType, typename Type, typename State>\nclass IteratorBase: public Object {\n  // Provides the base implementation of JSG_ITERATOR types. See the documentation\n  // for JSG_ITERATOR for details.\n public:\n  using NextSignature = kj::Maybe<Type>(Lock&, State&);\n  explicit IteratorBase(State state): state(kj::mv(state)) {}\n  struct Next {\n    bool done;\n    Optional<Type> value;\n    JSG_STRUCT(done, value);\n  };\n\n  v8::Local<v8::Object> self(const v8::FunctionCallbackInfo<v8::Value>& info) {\n    return info.This();\n  }\n\n  void visitForGc(GcVisitor& visitor) {\n    if constexpr (hasPublicVisitForGc<State>()) {\n      visitor.visit(state);\n    }\n  }\n\n  JSG_MEMORY_INFO(IteratorBase) {\n    if constexpr (MemoryRetainer<State>) {\n      tracker.trackField(\"state\", state);\n    } else {\n      tracker.trackFieldWithSize(\"state\", sizeof(State));\n    }\n  }\n\n private:\n  State state;\n\n  Next nextImpl(Lock& js, NextSignature nextFunc) {\n    KJ_IF_SOME(value, nextFunc(js, state)) {\n      // When V8 builtins like Array.from() or spread syntax iterate a C++ iterator,\n      // the loop runs in C++ without JS back-edge interrupt checks. If\n      // TerminateExecution() was called (e.g. by the near-heap-limit callback), the\n      // interrupt won't be processed until V8 next checks the stack guard, which may\n      // never happen in the builtin loop. Check for a termination request here and\n      // throw a JS exception so V8 sees it on the callback return path.\n      // See https://crbug.com/v8/14681 for the same class of bug in Intl.Segmenter.\n      if (js.isTerminationRequested()) {\n        js.v8Isolate->ThrowError(v8Str(js.v8Isolate, \"Isolate terminated (OOM?)\"_kj));\n        throw JsExceptionThrown();\n      }\n      return Next{.done = false, .value = kj::mv(value)};\n    }\n    return Next{\n      .done = true,\n      .value = kj::none,\n    };\n  }\n\n  friend SelfType;\n};\n\nclass AsyncIteratorImpl {\n public:\n  struct Finished {};\n\n  kj::Maybe<Promise<void>&> maybeCurrent();\n\n  void pushCurrent(Promise<void> promise);\n\n  void popCurrent();\n\n  bool returning = false;\n\n  void visitForGc(GcVisitor& visitor);\n\n  template <typename Type>\n  struct Next {\n    bool done;\n    Optional<Type> value;\n    JSG_STRUCT(done, value);\n  };\n\n  JSG_MEMORY_INFO(AsyncIteratorImpl) {\n    // TODO(soon): Implement memory tracking\n  }\n\n private:\n  std::list<Promise<void>> pendingStack;\n};\n\n// Provides the base implementation of JSG_ASYNC_ITERATOR types. See the documentation\n// for JSG_ASYNC_ITERATOR for details.\n//\n// Objects that use AsyncIteratorBase will be usable with the for await syntax in\n// JavaScript, e.g.:\n//\n//   const obj = new MyNewAsyncIterableObject();\n//   for await (const chunk of obj) {\n//     console.log(chunk);\n//   }\n//\n// The for await syntax is just sugar for using an async generator object.\n// All async iterable objects will have a method that will return an instance\n// of the AsyncIteratorBase. This is typically a method named values() or\n// entries().\n//\n// const obj = new MyNewAsyncIterableObject();\n// const gen = obj.values();\n//\n// The async generator object has two methods: next() and return()\n// next() is called to fetch the next item from the iterator, and should\n// be called until there is no more data to return. The return() method\n// is called to signal early termination of the iterator. Both methods\n// return a JavaScript promise that resolves to an IteratorResult object\n// (an ordinary JavaScript object with a done and value property).\n//\n// const result = await gen.next();\n// console.log(result.done);   // true or false\n// console.log(result.value);  // the value yielded in this iteration.\n//\n// const result = await gen.return(\"foo\");\n// console.log(result.done);   // true\n// console.log(result.value);  // \"foo\" ... whatever value was passed in.\n//\n// It is important for the generator to queue and properly sequence concurrent\n// next() and return() calls. Specifically, the following pattern should read\n// five elements off the iterator before terminating it early:\n//\n// await Promise.all([\n//   gen.next(),         // must resolve to the first item\n//   gen.next(),         // must resolve to the second item\n//   gen.next(),         // must resolve to the third item\n//   gen.next(),         // must resolve to the fourth item\n//   gen.next(),         // must resolve to the fifth item\n//   gen.return(\"boom\"), // must not be processed until after the fifth next()\n// ]);\n//\n// Once return() is called, all subsequent next() and return() calls must just\n// return an immediately resolved promise indicating that the iterator is done.\ntemplate <typename SelfType, typename Type, typename State>\nclass AsyncIteratorBase: public Object {\n public:\n  using NextSignature = Promise<kj::Maybe<Type>>(Lock&, State&);\n  using ReturnSignature = Promise<void>(Lock&, State&, Optional<Type>&);\n  using Next = AsyncIteratorImpl::Next<Type>;\n  using Finished = AsyncIteratorImpl::Finished;\n\n  explicit AsyncIteratorBase(State state): state(InnerState{.state = kj::mv(state)}) {}\n\n  v8::Local<v8::Object> self(const v8::FunctionCallbackInfo<v8::Value>& info) {\n    return info.This();\n  }\n\n  void visitForGc(GcVisitor& visitor) {\n    KJ_IF_SOME(inner, state.template tryGet<InnerState>()) {\n      if constexpr (hasPublicVisitForGc<State>()) {\n        visitor.visit(inner.state);\n      }\n      visitor.visit(inner.impl);\n    }\n  }\n\n  JSG_MEMORY_INFO(AsyncIteratorBase) {\n    KJ_SWITCH_ONEOF(state) {\n      KJ_CASE_ONEOF(fin, Finished) {\n        tracker.trackFieldWithSize(\"state\", sizeof(Finished));\n      }\n      KJ_CASE_ONEOF(state, InnerState) {\n        tracker.trackField(\"state\", state);\n      }\n    }\n  }\n\n private:\n  struct InnerState {\n    State state;\n    AsyncIteratorImpl impl;\n\n    JSG_MEMORY_INFO(InnerState) {\n      if constexpr (MemoryRetainer<State>) {\n        tracker.trackField(\"state\", state);\n      } else {\n        tracker.trackFieldWithSize(\"state\", sizeof(State));\n      }\n      tracker.trackField(\"impl\", impl);\n    }\n  };\n\n  kj::OneOf<Finished, InnerState> state;\n\n  void pushCurrent(Lock& js, Promise<void> promise) {\n    auto& inner = state.template get<InnerState>();\n    auto result = promise.whenResolved(js).then(js, [this, self = JSG_THIS](Lock& js) {\n      // If state is Finished, then there's nothing we need to do here.\n      KJ_IF_SOME(inner, state.template tryGet<InnerState>()) {\n        inner.impl.popCurrent();\n      }\n      return js.resolvedPromise();\n    }, [this, self = JSG_THIS](Lock& js, Value value) {\n      KJ_IF_SOME(inner, state.template tryGet<InnerState>()) {\n        inner.impl.popCurrent();\n      }\n      return js.rejectedPromise<void>(kj::mv(value));\n    });\n    // The error is already propagated through the promise returned by nextImpl/returnImpl.\n    // Mark as handled so the internally-held promise does not trigger unhandledrejection.\n    result.markAsHandled(js);\n    inner.impl.pushCurrent(kj::mv(result));\n  }\n\n  Promise<Next> nextImpl(Lock& js, NextSignature nextFunc) {\n    KJ_SWITCH_ONEOF(state) {\n      KJ_CASE_ONEOF(finished, Finished) {\n        return js.resolvedPromise(Next{.done = true});\n      }\n      KJ_CASE_ONEOF(inner, InnerState) {\n        // If return_() has already been called on the async iterator, we just return an immediately\n        // resolved promise indicating done, regardless of whether there are still other outstanding\n        // next promises or not.\n        if (inner.impl.returning) {\n          return js.resolvedPromise(Next{.done = true});\n        }\n\n        auto callNext = [this, self = JSG_THIS, nextFunc = kj::mv(nextFunc)](Lock& js) mutable {\n          KJ_SWITCH_ONEOF(state) {\n            KJ_CASE_ONEOF(finished, Finished) {\n              return js.resolvedPromise(Next{.done = true});\n            }\n            KJ_CASE_ONEOF(inner, InnerState) {\n              auto promise = nextFunc(js, inner.state);\n              pushCurrent(js, promise.whenResolved(js));\n              return promise.then(\n                  js, [this, self = kj::mv(self)](Lock& js, kj::Maybe<Type> maybeResult) mutable {\n                KJ_IF_SOME(result, maybeResult) {\n                  return js.resolvedPromise(Next{.done = false, .value = kj::mv(result)});\n                } else {\n                  state.template init<Finished>();\n                  return js.resolvedPromise(Next{.done = true});\n                }\n              });\n            }\n          }\n          KJ_UNREACHABLE;\n        };\n\n        KJ_IF_SOME(current, inner.impl.maybeCurrent()) {\n          auto promise = current.whenResolved(js).then(js, kj::mv(callNext));\n          pushCurrent(js, promise.whenResolved(js));\n          return kj::mv(promise);\n        }\n\n        // Otherwise, call the next function and handle the result.\n        return callNext(js);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  Promise<Next> returnImpl(Lock& js, Optional<Type> value, ReturnSignature returnFunc) {\n    KJ_SWITCH_ONEOF(state) {\n      KJ_CASE_ONEOF(finished, Finished) {\n        return js.resolvedPromise(Next{.done = true, .value = kj::mv(value)});\n      }\n      KJ_CASE_ONEOF(inner, InnerState) {\n\n        // When inner.returning is true, return_() has already been called on the iterator.\n        // Any further calls to either next() or return_() will result in immediately resolved\n        // promises indicating a done status being returned, regardless of any other promises\n        // that may be pending.\n        if (inner.impl.returning) {\n          return js.resolvedPromise(Next{.done = true, .value = kj::mv(value)});\n        }\n\n        inner.impl.returning = true;\n\n        auto callReturn = [this, self = JSG_THIS, value = kj::mv(value),\n                              returnFunc = kj::mv(returnFunc)](Lock& js) mutable {\n          KJ_SWITCH_ONEOF(state) {\n            KJ_CASE_ONEOF(finished, Finished) {\n              return js.resolvedPromise(Next{.done = true, .value = kj::mv(value)});\n            }\n            KJ_CASE_ONEOF(inner, InnerState) {\n              return returnFunc(js, inner.state, value)\n                  .then(js, [this, self = kj::mv(self), value = kj::mv(value)](Lock& js) mutable {\n                state.template init<Finished>();\n                return js.resolvedPromise(Next{.done = true, .value = kj::mv(value)});\n              });\n            }\n          }\n          KJ_UNREACHABLE;\n        };\n\n        // If there is something on the pending stack, we are going to wait for that promise\n        // to resolve then call callReturn.\n        KJ_IF_SOME(current, inner.impl.maybeCurrent()) {\n          return current.whenResolved(js).then(js, kj::mv(callReturn));\n        }\n\n        // Otherwise, we call callReturn immediately.\n        return callReturn(js);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  friend SelfType;\n};\n\n// The JSG_ITERATOR macro provides a mechanism for easily implementing JavaScript-style iterators\n// for JSG_RESOURCE_TYPES.\n//\n// Example usage:\n//\n// class MyApiType: public jsg::Object {\n// private:\n//   struct IteratorState {\n//     // The iterator's internal state.\n//     // Implement visitForGc here if the state stores any visitable references.\n//   };\n//\n//   static kj::Maybe<kj::String> nextFunction(jsg::Lock& js, IteratorState& state) {\n//     // Return nullptr to indicate we've reached the end of the iterator.\n//     // Otherwise, return the next iterator value.\n//   }\n// public:\n//   JSG_ITERATOR(MyApiTypeIterator,\n//                 entries,\n//                 kj::String,\n//                 IteratorState,\n//                 nextFunction);\n//\n//   JSG_RESOURCE_TYPE(MyApiType) {\n//     JSG_METHOD(entries);\n//     JSG_ITERABLE(entries);\n//   }\n//\n//   jsg::Ref<MyApiTypeIterator> entries(jsg::Lock& js) {\n//     return js.alloc<MyApiTypeIterator>(IteratorState { /* any necessary state init */ });\n//   }\n// };\n//\n// In this example, instances of MyApiType will support the JavaScript synchronous iterator\n// pattern (e.g. for (const item of myApiType) {}).\n//\n// The actual iterator instance is defined by the type MyApiType::MyApiTypeIterator, which\n// will use the IteratorState struct to store internal state and the nextFunction to yield\n// the next value for the iterator.\n//\n// A member function named entries(Lock&) will be added to MyApiType that returns a\n// jsg::Ref<MyApiTypeIterator>() instance. It will be necessary for uses to provide the\n// implementation of the entries(Lock&) member function.\n#define JSG_ITERATOR(Name, Label, Type, State, NextFunc)                                           \\\n  class Name final: public jsg::IteratorBase<Name, Type, State> {                                  \\\n   public:                                                                                         \\\n    using jsg::IteratorBase<Name, Type, State>::IteratorBase;                                      \\\n    inline Next next(jsg::Lock& js) {                                                              \\\n      return nextImpl(js, NextFunc);                                                               \\\n    }                                                                                              \\\n    JSG_RESOURCE_TYPE(Name) {                                                                      \\\n      JSG_INHERIT_INTRINSIC(v8::kIteratorPrototype);                                               \\\n      JSG_METHOD(next);                                                                            \\\n      JSG_ITERABLE(self);                                                                          \\\n    }                                                                                              \\\n  };                                                                                               \\\n  jsg::Ref<Name> Label(jsg::Lock&);\n\n// Like JSG_ITERATOR but don't declare the method name automatically.\n//\n// TODO(cleanup): Change all JSG_ITERATOR usages to this. It's confusing for the macro to declare\n//   the method.\n#define JSG_ITERATOR_TYPE(Name, Type, State, NextFunc)                                             \\\n  class Name final: public jsg::IteratorBase<Name, Type, State> {                                  \\\n   public:                                                                                         \\\n    using jsg::IteratorBase<Name, Type, State>::IteratorBase;                                      \\\n    inline Next next(jsg::Lock& js) {                                                              \\\n      return nextImpl(js, NextFunc);                                                               \\\n    }                                                                                              \\\n    JSG_RESOURCE_TYPE(Name) {                                                                      \\\n      JSG_INHERIT_INTRINSIC(v8::kIteratorPrototype);                                               \\\n      JSG_METHOD(next);                                                                            \\\n      JSG_ITERABLE(self);                                                                          \\\n    }                                                                                              \\\n  };\n\n#define JSG_ASYNC_ITERATOR_TYPE(Name, Type, State, NextFunc, ReturnFunc)                           \\\n  class Name final: public jsg::AsyncIteratorBase<Name, Type, State> {                             \\\n   public:                                                                                         \\\n    using jsg::AsyncIteratorBase<Name, Type, State>::AsyncIteratorBase;                            \\\n    inline jsg::Promise<Next> next(jsg::Lock& js) {                                                \\\n      return nextImpl(js, NextFunc);                                                               \\\n    }                                                                                              \\\n    inline jsg::Promise<Next> return_(jsg::Lock& js, jsg::Optional<Type> value) {                  \\\n      return returnImpl(js, kj::mv(value), ReturnFunc);                                            \\\n    }                                                                                              \\\n    JSG_RESOURCE_TYPE(Name) {                                                                      \\\n      JSG_INHERIT_INTRINSIC(v8::kAsyncIteratorPrototype);                                          \\\n      JSG_METHOD(next);                                                                            \\\n      JSG_METHOD_NAMED(return, return_);                                                           \\\n      JSG_ASYNC_ITERABLE(self);                                                                    \\\n    }                                                                                              \\\n  };\n\n// The JSG_ASYNC_ITERATOR and JSG_ASYNC_ITERATOR_WITH_OPTIONS macros provide a mechanism for\n// easily implementing JavaScript-style asynchronous iterators for JSG_RESOURCE_TYPES.\n//\n// Example usage:\n//\n// class MyApiType: public jsg::Object {\n// private:\n//   struct IteratorState {\n//     // The iterator's internal state.\n//     // Implement visitForGc here if the state stores any visitable references.\n//   };\n//\n//   static jsg::Promise<kj::Maybe<kj::String>> nextFunction(\n//       jsg::Lock& js,\n//       IteratorState& state) {\n//     // Called to asynchronous get the next item for the iterator.\n//     // Return nullptr to indicate we've reached the end of the iterator.\n//     // Otherwise, return the next iterator value.\n//   }\n//\n//   static jsg::Promise<void> returnFunction(\n//       jsg::Lock& js,\n//       IteratorState& state,\n//       jsg::Optional<jsg::Value> value) {\n//     // Called when the iterator is abruptly terminated or when the\n//     // iterator generator's return() method is called. On success,\n//     // an immediately resolved promise should be returned.\n//   }\n//\n// public:\n//   JSG_ASYNC_ITERATOR(MyApiTypeIterator,\n//                       entries,\n//                       kj::String,\n//                       IteratorState,\n//                       nextFunction,\n//                       returnFunction);\n//\n//   JSG_RESOURCE_TYPE(MyApiType) {\n//     JSG_METHOD(entries);\n//     JSG_ASYNC_ITERABLE(entries);\n//   }\n//\n//   jsg::Ref<MyApiTypeIterator> entries(jsg::Lock& js) {\n//     return js.alloc<MyApiTypeIterator>(IteratorState { /* any necessary state init */ });\n//   }\n// };\n//\n// In this example, instances of MyApiType will support the JavaScript asynchronous iterator\n// pattern (e.g. for await (const item of myApiType) {}).\n//\n// The actual iterator instance is defined by the type MyApiType::MyApiTypeIterator, which\n// will use the IteratorState struct to store internal state and the nextFunction to yield\n// the next value for the iterator.\n//\n// A member function named entries(Lock&) will be added to MyApiType that returns a\n// jsg::Ref<MyApiTypeIterator>() instance. It will be necessary for uses to provide the\n// implementation of the entries(Lock&) member function.\n#define JSG_ASYNC_ITERATOR(Name, Label, Type, State, NextFunc, ReturnFunc)                         \\\n  JSG_ASYNC_ITERATOR_TYPE(Name, Type, State, NextFunc, ReturnFunc)                                 \\\n  jsg::Ref<Name> Label(jsg::Lock&);\n\n#define JSG_ASYNC_ITERATOR_WITH_OPTIONS(Name, Label, Type, State, NextFunc, ReturnFunc, Options)   \\\n  JSG_ASYNC_ITERATOR_TYPE(Name, Type, State, NextFunc, ReturnFunc)                                 \\\n  jsg::Ref<Name> Label(jsg::Lock&, jsg::Optional<Options>);\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/jsg-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"jsg-test.h\"\n\nnamespace workerd::jsg::test {\nnamespace {\n\n// Non-JSG types aren't GC-visitable.\nstatic_assert(!isGcVisitable<int>());\nstatic_assert(!isGcVisitable<kj::String>());\n\n// Various reference types are.\nstatic_assert(isGcVisitable<Ref<Object>>());\nstatic_assert(isGcVisitable<kj::Maybe<Ref<Object>>>());\nstatic_assert(isGcVisitable<Data>());\nstatic_assert(isGcVisitable<V8Ref<v8::Object>>());\n\n// Resource types are not directly visitable. Their visitForGc() is private. You should be visiting\n// a Ref<T> pointing at them instead.\nstatic_assert(!isGcVisitable<Object>());\nstatic_assert(!isGcVisitable<NumberBox>());\nstatic_assert(!isGcVisitable<BoxBox>());\n\n// Any type that defines a public visitForGc() is visitable.\nstatic_assert(isGcVisitable<TestStruct>());\nstatic_assert(isGcVisitable<kj::Maybe<TestStruct>>());\n\n// jsg::Lock is not acceptable as a coroutine param\nstatic_assert(kj::_::isDisallowedInCoroutine<Lock>());\nstatic_assert(kj::_::isDisallowedInCoroutine<Lock&>());\nstatic_assert(kj::_::isDisallowedInCoroutine<Lock*>());\n\n// ========================================================================================\n\nV8System v8System;\nclass ContextGlobalObject: public Object, public ContextGlobal {};\n\nstruct TestContext: public ContextGlobalObject {\n  JSG_RESOURCE_TYPE(TestContext) {}\n};\nJSG_DECLARE_ISOLATE_TYPE(TestIsolate, TestContext);\n\nKJ_TEST(\"hello world\") {\n  Evaluator<TestContext, TestIsolate> e(v8System);\n  e.expectEval(\"'Hello' + ', World!'\", \"string\", \"Hello, World!\");\n}\n\nKJ_TEST(\"throw\") {\n  Evaluator<TestContext, TestIsolate> e(v8System);\n  e.expectEval(\"throw new Error('some error message')\", \"throws\", \"Error: some error message\");\n}\n\nKJ_TEST(\"context type is exposed in the global scope\") {\n  Evaluator<TestContext, TestIsolate> e(v8System);\n  e.expectEval(\"this instanceof TestContext\", \"boolean\", \"true\");\n}\n\n// ========================================================================================\n\nstruct InheritContext: public ContextGlobalObject {\n  struct Other: public Object {\n    static jsg::Ref<Other> constructor(jsg::Lock& js) {\n      return js.alloc<Other>();\n    }\n    JSG_RESOURCE_TYPE(Other) {}\n  };\n\n  Ref<NumberBox> newExtendedAsBase(jsg::Lock& js, double value, kj::String text) {\n    return ExtendedNumberBox::constructor(js, value, kj::mv(text));\n  }\n\n  JSG_RESOURCE_TYPE(InheritContext) {\n    JSG_NESTED_TYPE(NumberBox);\n    JSG_NESTED_TYPE(Other);\n    JSG_NESTED_TYPE(ExtendedNumberBox);\n\n    JSG_METHOD(newExtendedAsBase);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(\n    InheritIsolate, InheritContext, NumberBox, InheritContext::Other, ExtendedNumberBox);\n\nKJ_TEST(\"inheritance\") {\n  Evaluator<InheritContext, InheritIsolate> e(v8System);\n  e.expectEval(\"var n = new ExtendedNumberBox(123, 'foo');\\n\"\n               \"n.increment();\\n\"\n               \"n.getValue()\",\n      \"number\", \"124\");\n\n  e.expectEval(\"var n = new ExtendedNumberBox(123, 'foo');\\n\"\n               \"n.increment();\\n\"\n               \"n.value\",\n      \"number\", \"124\");\n\n  e.expectEval(\"new ExtendedNumberBox(123, 'foo').getText()\", \"string\", \"foo\");\n\n  e.expectEval(\"var n = new ExtendedNumberBox(123, 'foo');\\n\"\n               \"n.setText('bar');\\n\"\n               \"n.text\",\n      \"string\", \"bar\");\n\n  e.expectEval(\"var n = new ExtendedNumberBox(123, 'foo');\\n\"\n               \"n.text = 'bar';\\n\"\n               \"n.getText()\",\n      \"string\", \"bar\");\n\n  e.expectEval(\"new ExtendedNumberBox(123, 'foo') instanceof NumberBox\", \"boolean\", \"true\");\n\n  e.expectEval(\"new ExtendedNumberBox(123, 'foo') instanceof ExtendedNumberBox\", \"boolean\", \"true\");\n\n  e.expectEval(\"new ExtendedNumberBox(123, 'foo') instanceof Other\", \"boolean\", \"false\");\n\n  e.expectEval(\"newExtendedAsBase(123, 'foo') instanceof NumberBox\", \"boolean\", \"true\");\n\n  e.expectEval(\"newExtendedAsBase(123, 'foo') instanceof ExtendedNumberBox\", \"boolean\", \"true\");\n}\n\n// ========================================================================================\n\nstruct Utf8Context: public ContextGlobalObject {\n  bool callWithBmpUnicode(Lock& js, jsg::Function<bool(kj::StringPtr)> function) {\n    return function(js, \"中国网络\");\n  }\n  bool callWithEmojiUnicode(Lock& js, jsg::Function<bool(kj::StringPtr)> function) {\n    return function(js, \"😺☁️☄️🐵\");\n  }\n  JSG_RESOURCE_TYPE(Utf8Context) {\n    JSG_METHOD(callWithBmpUnicode);\n    JSG_METHOD(callWithEmojiUnicode);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(Utf8Isolate, Utf8Context);\n\nKJ_TEST(\"utf-8 scripts\") {\n  Evaluator<Utf8Context, Utf8Isolate> e(v8System);\n\n  // BMP unicode.\n  e.expectEval(\"'中国网络'\", \"string\", \"中国网络\");\n\n  // Emoji unicode (including non-BMP characters).\n  e.expectEval(\"'😺☁️☄️🐵'\", \"string\", \"😺☁️☄️🐵\");\n\n  // Go the other way.\n  e.expectEval(\"callWithBmpUnicode(str => str == '中国网络')\", \"boolean\", \"true\");\n  e.expectEval(\"callWithEmojiUnicode(str => str == '😺☁️☄️🐵')\", \"boolean\", \"true\");\n}\n\n// ========================================================================================\n\nstruct RefContext: public ContextGlobalObject {\n  Ref<NumberBox> addAndReturnCopy(jsg::Lock& js, NumberBox& box, double value) {\n    auto copy = js.alloc<NumberBox>(box.value);\n    copy->value += value;\n    return copy;\n  }\n  Ref<NumberBox> addAndReturnOwn(Ref<NumberBox> box, double value) {\n    box->value += value;\n    return box;\n  }\n\n  JSG_RESOURCE_TYPE(RefContext) {\n    JSG_NESTED_TYPE(NumberBox);\n    JSG_METHOD(addAndReturnCopy);\n    JSG_METHOD(addAndReturnOwn);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(RefIsolate, RefContext, NumberBox);\n\nKJ_TEST(\"Ref\") {\n  Evaluator<RefContext, RefIsolate> e(v8System);\n  // addAndReturnCopy() creates a new object and returns it.\n  e.expectEval(\"var orig = new NumberBox(123);\\n\"\n               \"var result = addAndReturnCopy(orig, 321);\\n\"\n               \"[orig.value, result.value, orig == result].join(', ')\",\n      \"string\", \"123, 444, false\");\n\n  // addAndReturnOwn() modifies the original object and returns it by identity.\n  e.expectEval(\"var orig = new NumberBox(123);\\n\"\n               \"var result = addAndReturnOwn(orig, 321);\\n\"\n               \"[orig.value, result.value, orig == result].join(', ')\",\n      \"string\", \"444, 444, true\");\n}\n\n// ========================================================================================\n\nstruct ProtoContext: public ContextGlobalObject {\n  ProtoContext(): contextProperty(kj::str(\"default-context-property-value\")) {}\n\n  kj::StringPtr getContextProperty() {\n    return contextProperty;\n  }\n  void setContextProperty(kj::String s) {\n    contextProperty = kj::mv(s);\n  }\n\n  JSG_RESOURCE_TYPE(ProtoContext) {\n    JSG_NESTED_TYPE(NumberBox);\n    JSG_NESTED_TYPE(BoxBox);\n    JSG_NESTED_TYPE(ExtendedNumberBox);\n    JSG_METHOD(getContextProperty);\n    JSG_METHOD(setContextProperty);\n    JSG_INSTANCE_PROPERTY(contextProperty, getContextProperty, setContextProperty);\n  }\n\n private:\n  kj::String contextProperty;\n};\nJSG_DECLARE_ISOLATE_TYPE(ProtoIsolate, ProtoContext, NumberBox, BoxBox, ExtendedNumberBox);\n\nconst auto kIllegalInvocation =\n    \"TypeError: Illegal invocation: function called with incorrect `this` reference. \"\n    \"See https://developers.cloudflare.com/workers/observability/errors/#illegal-invocation-errors for details.\"_kj;\n\nKJ_TEST(\"can't invoke builtin methods with alternative 'this'\") {\n  Evaluator<ProtoContext, ProtoIsolate> e(v8System);\n  e.expectEval(\"NumberBox.prototype.getValue.call(123)\", \"throws\", kIllegalInvocation);\n  e.expectEval(\"NumberBox.prototype.getValue.call(new BoxBox(new NumberBox(123), 123))\", \"throws\",\n      kIllegalInvocation);\n  e.expectEval(\"getContextProperty.call(new NumberBox(123))\", \"throws\", kIllegalInvocation);\n}\n\nKJ_TEST(\"can't use builtin as prototype\") {\n  Evaluator<ProtoContext, ProtoIsolate> e(v8System);\n  e.expectEval(\"function JsType() {}\\n\"\n               \"JsType.prototype = new NumberBox(123);\\n\"\n               \"new JsType().getValue()\",\n      \"throws\", kIllegalInvocation);\n  e.expectEval(\"function JsType() {}\\n\"\n               \"JsType.prototype = new ExtendedNumberBox(123, 'foo');\\n\"\n               \"new JsType().getValue()\",\n      \"throws\", kIllegalInvocation);\n  e.expectEval(\"function JsType() {}\\n\"\n               \"JsType.prototype = new NumberBox(123);\\n\"\n               \"new JsType().value\",\n      \"number\", \"123\");\n  e.expectEval(\"function JsType() {}\\n\"\n               \"JsType.prototype = new NumberBox(123);\\n\"\n               \"let t = new JsType();\\n\"\n               \"Reflect.get(JsType.prototype, 'value', t)\\n\",\n      \"number\", \"123\");\n  e.expectEval(\"function JsType() {}\\n\"\n               \"JsType.prototype = new ExtendedNumberBox(123, 'foo');\\n\"\n               \"new JsType().value\",\n      \"number\", \"123\");\n  e.expectEval(\"function JsType() {}\\n\"\n               \"JsType.prototype = this;\\n\"\n               \"new JsType().getContextProperty()\",\n      \"throws\", kIllegalInvocation);\n\n  // For historical reasons, we allow using the global object as a prototype and accessing\n  // properties through a derived object. Our accessor implementations for global object properties\n  // ignore `this` and go directly to the singleton context object, so it doesn't matter.\n  //\n  // (Once upon a time, V8 supported a thing called an \"AccessorSignature\" which would handle the\n  // type checking, but it didn't work correctly for the global object. V8 later removed\n  // AccessorSignature entirely, forcing us to implement manual type checking. We could totally\n  // make our manual type checking work correctly for global properties, but, again, it doesn't\n  // really matter, and I'd rather not inadvertently break someone.)\n  e.expectEval(\"function JsType() {}\\n\"\n               \"JsType.prototype = this;\\n\"\n               \"new JsType().contextProperty\",\n      \"string\", \"default-context-property-value\");\n}\n\n// ========================================================================================\n\nstruct IcuContext: public ContextGlobalObject {\n  JSG_RESOURCE_TYPE(IcuContext) {}\n};\nJSG_DECLARE_ISOLATE_TYPE(IcuIsolate, IcuContext);\n\nKJ_TEST(\"ICU is properly initialized\") {\n  Evaluator<IcuContext, IcuIsolate> e(v8System);\n  e.expectEval(\"function charCodes(str) {\"\n               \"  let result = [];\\n\"\n               \"  for (let i = 0; i < str.length; i++) {\\n\"\n               \"    result.push(str.charCodeAt(i));\\n\"\n               \"  }\\n\"\n               \"  return result.join(',');\\n\"\n               \"}\"\n               \"[ charCodes('\\u1E9B\\u0323'),\\n\"\n               \"  charCodes('\\u1E9B\\u0323'.normalize('NFC')),\\n\"\n               \"  charCodes('\\u1E9B\\u0323'.normalize('NFD')),\\n\"\n               \"  charCodes('\\u1E9B\\u0323'.normalize('NFKD')),\\n\"\n               \"  charCodes('\\u1E9B\\u0323'.normalize('NFKC')) ].join(' ')\",\n\n      \"string\", \"7835,803 7835,803 383,803,775 115,803,775 7785\");\n}\n\n// ========================================================================================\n\nKJ_TEST(\"Uncaught JsExceptionThrown reports stack\") {\n  auto exception =\n      KJ_ASSERT_NONNULL(kj::runCatchingExceptions([&]() { throw JsExceptionThrown(); }));\n  KJ_ASSERT(\n      exception.getDescription().startsWith(\"std::exception: Uncaught JsExceptionThrown\\nstack: \"),\n      exception.getDescription());\n}\n\n// TODO(test): Find some way to verify that C++ objects get garbage-collected as expected (hard to\n//   test since GC technically does not guarantee that it will collect everything).\n\n// ========================================================================================\n\nstruct LockLogContext: public ContextGlobalObject {\n  JSG_RESOURCE_TYPE(LockLogContext) {}\n};\nJSG_DECLARE_ISOLATE_TYPE(LockLogIsolate, LockLogContext);\n\nKJ_TEST(\"jsg::Lock logWarning\") {\n  LockLogIsolate isolate(v8System, kj::heap<IsolateObserver>());\n  bool called = false;\n  isolate.runInLockScope([&](LockLogIsolate::Lock& lock) {\n    lock.setLoggerCallback([&called](jsg::Lock& js, auto message) {\n      KJ_ASSERT(message == \"Yes that happened\"_kj);\n      called = true;\n    });\n    lock.logWarning(\"Yes that happened\"_kj);\n    KJ_ASSERT(called);\n  });\n}\n\n// ========================================================================================\n// JSG_CALLABLE Test\nstruct CallableContext: public ContextGlobalObject {\n  struct MyCallable: public Object {\n   public:\n    static Ref<MyCallable> constructor(jsg::Lock& js) {\n      return js.alloc<MyCallable>();\n    }\n\n    bool foo() {\n      return true;\n    }\n\n    JSG_RESOURCE_TYPE(MyCallable) {\n      JSG_CALLABLE(foo);\n      JSG_METHOD(foo);\n    }\n  };\n\n  Ref<MyCallable> getCallable(jsg::Lock& js) {\n    return js.alloc<MyCallable>();\n  }\n\n  JSG_RESOURCE_TYPE(CallableContext) {\n    JSG_METHOD(getCallable);\n    JSG_NESTED_TYPE(MyCallable);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(CallableIsolate, CallableContext, CallableContext::MyCallable);\n\nKJ_TEST(\"Test JSG_CALLABLE\") {\n  Evaluator<CallableContext, CallableIsolate> e(v8System);\n\n  e.expectEval(\"let obj = getCallable(); obj.foo();\", \"boolean\", \"true\");\n  e.expectEval(\"let obj = getCallable(); obj();\", \"boolean\", \"true\");\n\n  e.expectEval(\"let obj = new MyCallable(); obj();\", \"boolean\", \"true\");\n\n  // It's weird, but still accepted.\n  e.expectEval(\"let obj = getCallable(); new obj();\", \"boolean\", \"true\");\n}\n\n// ========================================================================================\nstruct InterceptContext: public ContextGlobalObject {\n  struct ProxyImpl: public jsg::Object {\n    static jsg::Ref<ProxyImpl> constructor(jsg::Lock& js) {\n      return js.alloc<ProxyImpl>();\n    }\n\n    int getBar() {\n      return 123;\n    }\n\n    // JSG_WILDCARD_PROPERTY implementation\n    kj::Maybe<kj::StringPtr> testGetNamed(jsg::Lock& js, kj::String name) {\n      if (name == \"foo\") {\n        return \"bar\"_kj;\n      } else if (name == \"abc\") {\n        JSG_FAIL_REQUIRE(TypeError, \"boom\");\n      }\n      return kj::none;\n    }\n\n    JSG_RESOURCE_TYPE(ProxyImpl) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(bar, getBar);\n      JSG_WILDCARD_PROPERTY(testGetNamed);\n    }\n  };\n\n  JSG_RESOURCE_TYPE(InterceptContext) {\n    JSG_NESTED_TYPE(ProxyImpl);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(InterceptIsolate, InterceptContext, InterceptContext::ProxyImpl);\n\nKJ_TEST(\"Named interceptor\") {\n  Evaluator<InterceptContext, InterceptIsolate> e(v8System);\n  e.expectEval(\"p = new ProxyImpl; p.bar\", \"number\", \"123\");\n  e.expectEval(\"p = new ProxyImpl; Reflect.has(p, 'foo')\", \"boolean\", \"true\");\n  e.expectEval(\"p = new ProxyImpl; p.hasOwnProperty('foo')\", \"boolean\", \"false\");\n  e.expectEval(\n      \"p = new ProxyImpl; Object.getOwnPropertyDescriptor(p, 'foo')\", \"undefined\", \"undefined\");\n  e.expectEval(\"p = new ProxyImpl; Reflect.has(p, 'bar')\", \"boolean\", \"true\");\n  e.expectEval(\"p = new ProxyImpl; Reflect.has(p, 'baz')\", \"boolean\", \"false\");\n  e.expectEval(\"p = new ProxyImpl; p.abc\", \"throws\", \"TypeError: boom\");\n}\n\n// ========================================================================================\nstruct IsolateUuidContext: public ContextGlobalObject {\n  JSG_RESOURCE_TYPE(IsolateUuidContext) {}\n};\nJSG_DECLARE_ISOLATE_TYPE(IsolateUuidIsolate, IsolateUuidContext);\n\nKJ_TEST(\"External memory adjustment\") {\n  IsolateUuidIsolate isolate(v8System, kj::heap<IsolateObserver>());\n  isolate.runInLockScope([&](IsolateUuidIsolate::Lock& lock) {\n    // Creating an inner scope to check the case where the adjustment object does not outlive the isolate\n    {\n      // Creating with a specific amount works as expected\n      auto adjuster = lock.getExternalMemoryAdjustment(100);\n      KJ_ASSERT(adjuster.getAmount() == 100);\n\n      // Adjusting up works as expected\n      adjuster.adjust(10);\n      KJ_ASSERT(adjuster.getAmount() == 110);\n\n      // Adjusting down works as expected\n      adjuster.adjust(-10);\n      KJ_ASSERT(adjuster.getAmount() == 100);\n\n      // Setting an explicit value just works\n      adjuster.set(50);\n      KJ_ASSERT(adjuster.getAmount() == 50);\n\n      // Decrementing by more than the amount will throw an exception\n      try {\n        adjuster.adjust(-200);\n      } catch (...) {\n        auto exc = kj::getCaughtExceptionAsKj();\n        KJ_ASSERT(exc.getDescription() ==\n            \"expected amount >= -static_cast<ssize_t>(this->amount) [-200 >= -50]; Memory usage may not be decreased below zero\");\n      }\n\n      KJ_ASSERT(adjuster.getAmount() == 50);\n\n      adjuster.set(100);\n      auto adjuster2 = kj::mv(adjuster);\n      KJ_ASSERT(adjuster2.getAmount() == 100);\n\n      // Checking that the amount is zero after the adjuster is moved away would be nice to have,\n      // but we should aim to avoid use-after-move entirely.\n      // KJ_ASSERT(adjuster.getAmount() == 0);\n    }\n    // Note that we are not testing the actual effect on the isolate itself here.\n    // While we have added a getExternalMemory() API to the isolate via a patch in\n    // the internal repo, we have not added that patch to workerd so testing the\n    // specific external memory reported by the isolate is possible but a bit\n    // more cumbersome here.\n  });\n}\n\nKJ_TEST(\"External memory adjustment - defered\") {\n  kj::Arc<const ExternalMemoryTarget> target;\n\n  // A memory allocation that will outlive the isolate\n  kj::Array<kj::byte> mem;\n\n  {\n    IsolateUuidIsolate isolate(v8System, kj::heap<IsolateObserver>());\n\n    target = isolate.runInLockScope(\n        [&](IsolateUuidIsolate::Lock& lock) { return lock.getExternalMemoryTarget(); });\n\n    // Adjustment to memory while not holding lock will be applied later\n    auto adjuster1 = target->getAdjustment(1000);\n    KJ_ASSERT(adjuster1.getAmount() == 1000);\n    KJ_ASSERT(target->getPendingMemoryUpdateForTest() == 1000);\n\n    {\n      // This adjustment has no effect because the adjuster is destroyed before we take the lock again\n      auto adjuster2 = target->getAdjustment(1000);\n      KJ_ASSERT(adjuster2.getAmount() == 1000);\n      KJ_ASSERT(target->getPendingMemoryUpdateForTest() == 2000);\n    }\n\n    KJ_ASSERT(target->getPendingMemoryUpdateForTest() == 1000);\n    KJ_ASSERT(target->isIsolateAliveForTest());\n\n    isolate.runInLockScope([&](IsolateUuidIsolate::Lock& lock) {\n      // Once lock is taken, the amount is applied\n      KJ_ASSERT(target->getPendingMemoryUpdateForTest() == 0);\n\n      // Adjustment made while holding lock applies immediately\n      adjuster1.adjust(-500);\n      KJ_ASSERT(adjuster1.getAmount() == 500);\n      KJ_ASSERT(target->getPendingMemoryUpdateForTest() == 0);\n      KJ_ASSERT(target->isIsolateAliveForTest());\n    });\n\n    mem = isolate.runInLockScope([&](IsolateUuidIsolate::Lock& lock) {\n      return kj::heapArray<kj::byte>(100).attach(target->getAdjustment(100));\n    });\n  }\n\n  KJ_ASSERT(!target->isIsolateAliveForTest());\n\n  // Delete the long-lived array, which will call the adjustment's destructor, to make sure it's safe.\n  mem = nullptr;\n\n  // Making an adjustment anyway won't do anything but also won't crash\n  auto adjuster3 = target->getAdjustment(500);\n  KJ_ASSERT(target->getPendingMemoryUpdateForTest() == 400);\n}\n\nKJ_TEST(\"Memory Allocation Error Propagation\") {\n  class MyAllocator final: public v8::ArrayBuffer::Allocator {\n   public:\n    void* Allocate(size_t length) override {\n      return nullptr;\n    }\n    void* AllocateUninitialized(size_t length) override {\n      return nullptr;\n    }\n    void Free(void* data, size_t length) override {}\n    size_t MaxAllocationSize() const override {\n      return 10;\n    }\n  };\n\n  MyAllocator allocator;\n  v8::Isolate::CreateParams createParams;\n  createParams.constraints.ConfigureDefaults(10, 10);\n  createParams.array_buffer_allocator = &allocator;\n  IsolateUuidIsolate isolate(v8System, kj::heap<IsolateObserver>(), createParams);\n  isolate.runInLockScope([&](IsolateUuidIsolate::Lock& lock) {\n    KJ_EXPECT_THROW_MESSAGE(\n        \"Failed to allocate ArrayBuffer backing store\", lock.allocBackingStore(100 * 1024));\n  });\n}\n\nstruct MpkContext: public ContextGlobalObject {\n  JSG_RESOURCE_TYPE(MpkContext) {}\n};\nJSG_DECLARE_ISOLATE_TYPE(MpkIsolate, MpkContext);\nKJ_TEST(\"MemoryProtectionKeyScope\") {\n  // In workerd, since V8_ENABLE_SANDBOX is not defined, this test is largely\n  // a non-op, however, when v8 is built with V8_ENABLE_SANDBOX enabled and\n  // the isolate has a memory protection key, this test will (eventually)\n  // verify that the array buffer allocation is writable within the scope.\n  // Essentially once backing stores are protected, and mpk's are enabled,\n  // this shouldn't crash.\n  MpkIsolate isolate(v8System, kj::heap<IsolateObserver>());\n  std::shared_ptr<v8::BackingStore> store;\n  auto mpkScope = isolate.runInLockScope([&](MpkIsolate::Lock& lock) {\n    store = v8::ArrayBuffer::NewBackingStore(lock.v8Isolate, 10);\n    return lock.getMemoryProtectionKeyScope();\n  });\n  bool called = false;\n  // Now that we have our backing store, try writing to it outside the\n  // isolate lock, but within the mpk scope. If mpk's are enabled writing\n  // to the backing store without the mpk scope should segfault, but within\n  // the mpk scope it should succeed.\n  kj::ArrayPtr<kj::byte> bytes(static_cast<kj::byte*>(store->Data()), store->ByteLength());\n  KJ_EXPECT(mpkScope.runWithKey([&] {\n    called = true;\n    bytes.fill(1);\n    return 1;\n  }) == 1);\n  KJ_EXPECT(called);\n}\n\n}  // namespace\n\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/jsg-test.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// Common JSG testing infrastructure\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/resource.h>\n#include <workerd/jsg/setup.h>\n#include <workerd/jsg/type-wrapper.h>\n\n#include <kj/test.h>\n\nnamespace workerd::jsg::test {\n\n// Checks the evaluation of a blob of JS code under the given context and isolate types.\ntemplate <typename ContextType,\n    typename IsolateType,\n    typename ConfigurationType = decltype(nullptr),\n    // HACK: We allow passing another parameter here to set the template type of\n    // ModuleRegistryImpl correctly in expectEvalModule(). This type needs to be\n    // IsolateType##_TypeWrapper, but this is difficult to derive from the IsolateType\n    // typename and only a few tests use expectEvalModule(), so providing it is optional.\n    // Previously we always provided ContextType here, which causes a subtle UBSan/vptr\n    // violation.\n    typename IsolateType_TypeWrapper = ContextType>\nclass Evaluator {\n  // TODO(cleanup): `ConfigurationType` currently can optionally be specified to fix the build\n  //   in cases that the isolate includes types that require configuration, but currently the\n  //   type is always default-constructed. What if you want to specify a test config?\n public:\n  explicit Evaluator(V8System& v8System, ConfigurationType config = {})\n      : v8System(v8System),\n        config(config) {}\n\n  IsolateType& getIsolate() {\n    // Slightly more efficient to only instantiate each isolate type once (17s vs. 20s):\n    static IsolateType isolate(\n        v8System, v8::IsolateGroup::GetDefault(), config, kj::heap<IsolateObserver>());\n    return isolate;\n  }\n\n  void expectEvalModule(\n      kj::StringPtr code, kj::StringPtr expectedType, kj::StringPtr expectedValue) {\n    getIsolate().runInLockScope([&](IsolateType::Lock& lock) {\n      JSG_WITHIN_CONTEXT_SCOPE(lock,\n          lock.template newContext<ContextType>().getHandle(lock.v8Isolate), [&](jsg::Lock& js) {\n        // Compile code as \"main\" module\n        CompilationObserver observer;\n        auto modules = ModuleRegistryImpl<IsolateType_TypeWrapper>::from(js);\n        auto p = kj::Path::parse(\"main\");\n        modules->add(p,\n            jsg::ModuleRegistry::ModuleInfo(lock, \"main\", code, nullptr /* compile cache */,\n                ModuleInfoCompileOption::BUNDLE, observer));\n\n        // Instantiate the module\n        auto& moduleInfo = KJ_REQUIRE_NONNULL(modules->resolve(js, p));\n        auto module = moduleInfo.module.getHandle(js);\n        jsg::instantiateModule(js, module);\n\n        // Module has to export \"run\" function\n        auto moduleNs = check(module->GetModuleNamespace()->ToObject(js.v8Context()));\n        auto runValue = check(moduleNs->Get(js.v8Context(), v8StrIntern(lock.v8Isolate, \"run\"_kj)));\n\n        v8::TryCatch catcher(js.v8Isolate);\n\n        // Run the function to get the result.\n        v8::Local<v8::Value> result;\n        if (v8::Function::Cast(*runValue)\n                ->Call(js.v8Context(), js.v8Context()->Global(), 0, nullptr)\n                .ToLocal(&result)) {\n          v8::String::Utf8Value type(js.v8Isolate, result->TypeOf(js.v8Isolate));\n          v8::String::Utf8Value value(js.v8Isolate, result);\n\n          KJ_EXPECT(*type == expectedType, *type, expectedType);\n          KJ_EXPECT(*value == expectedValue, *value, expectedValue);\n        } else if (catcher.HasCaught()) {\n          v8::String::Utf8Value message(js.v8Isolate, catcher.Exception());\n\n          KJ_EXPECT(expectedType == \"throws\", expectedType, catcher.Exception());\n          KJ_EXPECT(*message == expectedValue, *message, expectedValue);\n        } else {\n          KJ_FAIL_EXPECT(\"returned empty handle but didn't throw exception?\");\n        }\n      });\n    });\n  }\n\n  void expectEval(kj::StringPtr code, kj::StringPtr expectedType, kj::StringPtr expectedValue) {\n    getIsolate().runInLockScope([&](IsolateType::Lock& lock) {\n      JSG_WITHIN_CONTEXT_SCOPE(lock,\n          lock.template newContext<ContextType>().getHandle(lock.v8Isolate), [&](jsg::Lock& js) {\n        // Create a string containing the JavaScript source code.\n        v8::Local<v8::String> source = jsg::v8Str(js.v8Isolate, code);\n\n        // Compile the source code.\n        v8::Local<v8::Script> script;\n        if (!v8::Script::Compile(js.v8Context(), source).ToLocal(&script)) {\n          KJ_FAIL_EXPECT(\"code didn't parse\", code);\n          return;\n        }\n\n        v8::TryCatch catcher(js.v8Isolate);\n\n        // Run the script to get the result.\n        v8::Local<v8::Value> result;\n        if (script->Run(js.v8Context()).ToLocal(&result)) {\n          v8::String::Utf8Value type(js.v8Isolate, result->TypeOf(js.v8Isolate));\n          v8::String::Utf8Value value(js.v8Isolate, result);\n\n          KJ_EXPECT(*type == expectedType, *type, expectedType);\n          KJ_EXPECT(*value == expectedValue, *value, expectedValue);\n        } else if (catcher.HasCaught()) {\n          v8::String::Utf8Value message(js.v8Isolate, catcher.Exception());\n\n          KJ_EXPECT(expectedType == \"throws\", expectedType, catcher.Exception());\n          KJ_EXPECT(*message == expectedValue, *message, expectedValue);\n        } else {\n          KJ_FAIL_EXPECT(\"returned empty handle but didn't throw exception?\");\n        }\n      });\n    });\n  }\n\n  void setAllowEval(bool b) {\n    getIsolate().runInLockScope([&](IsolateType::Lock& lock) { lock.setAllowEval(b); });\n  }\n\n  void setCaptureThrowsAsRejections(bool b) {\n    getIsolate().runInLockScope(\n        [&](IsolateType::Lock& lock) { lock.setCaptureThrowsAsRejections(b); });\n  }\n\n  void runMicrotasks() {\n    getIsolate().runInLockScope([&](IsolateType::Lock& lock) { lock.runMicrotasks(); });\n  }\n\n  void runMicrotasks(IsolateType::Lock& lock) {\n    lock.runMicrotasks();\n  }\n\n  // Run some C++ code in a new lock and context.\n  template <typename Func>\n  void run(Func&& func) {\n    getIsolate().runInLockScope([&](IsolateType::Lock& lock) {\n      JSG_WITHIN_CONTEXT_SCOPE(lock,\n          lock.template newContext<ContextType>().getHandle(lock.v8Isolate), [&](jsg::Lock& js) {\n        v8::TryCatch tryCatch(js.v8Isolate);\n\n        try {\n          func(lock);\n        } catch (JsExceptionThrown&) {\n          if (tryCatch.HasTerminated()) {\n            KJ_FAIL_ASSERT(\"TerminateExecution() was called\");\n          } else {\n            KJ_ASSERT(tryCatch.HasCaught());\n            jsg::throwTunneledException(js.v8Isolate, tryCatch.Exception());\n          }\n        }\n      });\n    });\n  }\n\n private:\n  V8System& v8System;\n  ConfigurationType config;\n};\n\nstruct NumberBox: public Object {\n  double value;\n\n  explicit NumberBox(double value): value(value) {}\n  NumberBox() = default;\n\n  static Ref<NumberBox> constructor(jsg::Lock& js, double value) {\n    return js.alloc<NumberBox>(value);\n  }\n\n  void increment() {\n    value += 1;\n  }\n  void incrementBy(double amount) {\n    value += amount;\n  }\n  void incrementByBox(NumberBox& amount) {\n    value += amount.value;\n  }\n\n  double add(double other) {\n    return value + other;\n  }\n  double addBox(NumberBox& other) {\n    return value + other.value;\n  }\n  Ref<NumberBox> addReturnBox(jsg::Lock& js, double other) {\n    return js.alloc<NumberBox>(value + other);\n  }\n  double addMultiple(NumberBox& a, double b, NumberBox& c) {\n    return value + a.value + b + c.value;\n  }\n\n  double getValue() {\n    return value;\n  }\n  void setValue(double newValue) {\n    value = newValue;\n  }\n\n  Ref<NumberBox> getBoxed(jsg::Lock& js) {\n    return js.alloc<NumberBox>(value);\n  }\n  void setBoxed(NumberBox& newValue) {\n    value = newValue.value;\n  }\n\n  v8::Local<v8::Value> getBoxedFromTypeHandler(\n      jsg::Lock& js, const TypeHandler<Ref<NumberBox>>& numberBoxTypeHandler) {\n    return numberBoxTypeHandler.wrap(js, js.alloc<NumberBox>(value));\n  }\n\n  JSG_RESOURCE_TYPE(NumberBox) {\n    JSG_METHOD(increment);\n    JSG_METHOD(incrementBy);\n    JSG_METHOD(incrementByBox);\n\n    JSG_METHOD(add);\n    JSG_METHOD(addBox);\n    JSG_METHOD(addReturnBox);\n    JSG_METHOD(addMultiple);\n\n    JSG_METHOD(getValue);\n    JSG_METHOD(setValue);\n\n    JSG_INSTANCE_PROPERTY(value, getValue, setValue);\n    JSG_INSTANCE_PROPERTY(boxed, getBoxed, setBoxed);\n    JSG_READONLY_INSTANCE_PROPERTY(boxedFromTypeHandler, getBoxedFromTypeHandler);\n  }\n};\n\nclass BoxBox: public Object {\n public:\n  explicit BoxBox(Ref<NumberBox> inner): inner(kj::mv(inner)) {}\n\n  Ref<NumberBox> inner;\n\n  static Ref<BoxBox> constructor(jsg::Lock& js, NumberBox& inner, double add) {\n    return js.alloc<BoxBox>(js.alloc<NumberBox>(inner.value + add));\n  }\n\n  Ref<NumberBox> getInner() {\n    return inner.addRef();\n  }\n\n  JSG_RESOURCE_TYPE(BoxBox) {\n    JSG_READONLY_INSTANCE_PROPERTY(inner, getInner);\n  }\n\n private:\n  void visitForGc(GcVisitor& visitor) {\n    visitor.visit(inner);\n  }\n};\n\nstruct ExtendedNumberBox: public NumberBox {\n  static Ref<ExtendedNumberBox> constructor(jsg::Lock& js, double value, kj::String text) {\n    auto result = js.alloc<ExtendedNumberBox>();\n    result->value = value;\n    result->text = kj::mv(text);\n    return result;\n  }\n\n  kj::StringPtr getText() {\n    return text;\n  }\n  void setText(kj::String newText) {\n    text = kj::mv(newText);\n  }\n\n  kj::String text;\n\n  JSG_RESOURCE_TYPE(ExtendedNumberBox) {\n    JSG_INHERIT(NumberBox);\n\n    JSG_METHOD(getText);\n    JSG_METHOD(setText);\n    JSG_INSTANCE_PROPERTY(text, getText, setText);\n  }\n};\n\nstruct TestStruct {\n  kj::String str;\n  double num;\n  Ref<NumberBox> box;\n\n  JSG_STRUCT(str, num, box);\n\n  void visitForGc(GcVisitor& visitor) {\n    visitor.visit(box);\n  }\n};\n\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/jsg.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"jsg.h\"\n\n#include \"setup.h\"\n#include \"simdutf.h\"\n\n#include <workerd/jsg/modules-new.h>\n#include <workerd/jsg/modules.h>\n#include <workerd/jsg/util.h>\n#include <workerd/util/thread-scopes.h>\n\n#ifdef V8_ENABLE_SANDBOX\n#include <sys/mman.h>\n#endif\n\nnamespace workerd::jsg {\n\nkj::String stringifyHandle(v8::Local<v8::Value> value) {\n  // TODO(cleanup): Perhaps we should require you to call `js.toString(handle)`?\n  auto& js = jsg::Lock::current();\n  return js.withinHandleScope([&] {\n    v8::Local<v8::String> str = workerd::jsg::check(value->ToDetailString(js.v8Context()));\n    v8::String::Utf8Value utf8(js.v8Isolate, str);\n    if (*utf8 == nullptr) {\n      return kj::str(\"(couldn't stringify)\");\n    } else {\n      return kj::str(*utf8);\n    }\n  });\n}\n\nJsExceptionThrown::JsExceptionThrown() {\n  tracePtr = kj::getStackTrace(trace, 0);\n}\n\nconst char* JsExceptionThrown::what() const noexcept {\n  whatBuffer =\n      kj::str(\"Uncaught JsExceptionThrown\\nstack: \", kj::stringifyStackTraceAddresses(tracePtr));\n  return whatBuffer.cStr();\n}\n\nvoid Data::destroy() {\n  assertInvariant();\n  if (isolate != nullptr) {\n    if (v8::Locker::IsLocked(isolate)) {\n      handle.Reset();\n\n      // If we have a TracedReference, Reset() it too, to let V8 know that this value is no longer\n      // used. Note that merely destroying the TracedReference does nothing -- only explicitly\n      // calling Reset() has an effect.\n      //\n      // In particular, this permits `Data` values to be collected by minor (non-tracing) GC, as\n      // long as there are no cycles.\n      //\n      // HOWEVER, this is not safe if the TracedReference is being destroyed as a result of a\n      // major (traced) GC. In that case, the TracedReference itself may point to a reference slot\n      // that was already collected, and trying to reset it would be UB.\n      //\n      // In all other cases, resetting the handle is safe:\n      // - During minor GC, TracedReferences aren't collected by the GC itself, so must still be\n      //   valid.\n      // - If the `Data` is being destroyed _not_ as part of GC, e.g. it's being destroyed because\n      //   the data structure holding it is being modified in a way that drops the reference, then\n      //   that implies that the reference is still reachable, so must still be valid.\n      KJ_IF_SOME(t, tracedHandle) {\n        if (!HeapTracer::isInCppgcDestructor()) {\n          t.Reset();\n        }\n      }\n    } else {\n      // This thread doesn't have the isolate locked right now. To minimize lock contention, we'll\n      // defer these handles' destruction to the next time the isolate is locked.\n      //\n      // Note that only the v8::Global part of `handle` needs to be destroyed under isolate lock.\n      // The `tracedRef` part has a trivial destructor so can be destroyed on any thread.\n      auto& jsgIsolate = *reinterpret_cast<IsolateBase*>(isolate->GetData(SET_DATA_ISOLATE_BASE));\n      jsgIsolate.deferDestruction(v8::Global<v8::Data>(kj::mv(handle)));\n    }\n    isolate = nullptr;\n  }\n}\n\nvoid Data::moveFromTraced(Data& other, v8::TracedReference<v8::Data>& otherTracedRef) noexcept {\n  // Implement move constructor when the source of the move has previously been visited for\n  // garbage collection.\n  //\n  // This method is `noexcept` because if an exception is thrown below we're probably going to\n  // segfault later.\n\n  // We must hold a lock to move from a GC-reachable reference. (But we don't generally need a lock\n  // for moving from non-GC-reachable refs.)\n  KJ_ASSERT(v8::Locker::IsLocked(isolate));\n\n  // Verify the handle was not garbage-collected by trying to read it. The intention is for this\n  // to crash if the handle was GC'ed before being moved away.\n  {\n    auto& js = jsg::Lock::from(isolate);\n    js.withinHandleScope([&] {\n      auto local = handle.Get(js.v8Isolate);\n      if (local->IsValue()) {\n        local.As<v8::Value>()->IsArrayBufferView();\n      }\n    });\n  }\n\n  // `other` is a traced `Data`, but once moved, we don't assume the new location is traced.\n  // So, we need to make the handle strong.\n  handle.ClearWeak();\n\n  // Presumably, `other` is about to be destroyed. The destructor of `TracedReference`, though,\n  // does nothing, because it doesn't know if the reference is even still valid, since it\n  // could be called during GC sweep time. But here, we know that `other` is definitely still\n  // valid, because we wouldn't be moving from an unreachable object. So we should Reset() the\n  // `TracedReference` so that V8 knows it's gone, which might make minor GCs more effective.\n  otherTracedRef.Reset();\n\n  other.tracedHandle = kj::none;\n}\n\nLock::Lock(v8::Isolate* v8Isolate)\n    : v8Isolate(v8Isolate),\n      locker(v8Isolate),\n      isolateScope(v8Isolate),\n      previousData(v8Isolate->GetData(SET_DATA_LOCK)),\n      warningsLogged(IsolateBase::from(v8Isolate).areWarningsLogged()) {\n  if (previousData != nullptr) {\n    // Hmm, there's already a current lock. It must be a recursive lock (i.e. a second lock taken\n    // on the same isolate in the same thread), otherwise `locker`'s constructor would have blocked\n    // waiting for the other thread to release the lock. We don't want to support this, but\n    // historically we have.\n#ifdef KJ_DEBUG\n    // In debug mode, abort immediately. This makes it a little easier to debug than if we threw\n    // an exception.\n    ([]() noexcept { KJ_FAIL_REQUIRE(\"attempt to take recursive isolate lock\"); })();\n#else\n    // In release mode, log the error.\n    // TODO(soon): This shouldn't happen but we know it does in at least one case. Once things\n    // are cleaned up and we know this no longer happens in production, change this to throw.\n    // Then we can stop storing `previousData`.\n    KJ_LOG(ERROR, \"took recursive isolate lock\", kj::getStackTrace());\n#endif\n  }\n  v8Isolate->SetData(SET_DATA_LOCK, this);\n}\nLock::~Lock() noexcept(false) {\n  v8Isolate->SetData(SET_DATA_LOCK, previousData);\n}\n\nValue Lock::parseJson(kj::ArrayPtr<const char> data) {\n  return withinHandleScope(\n      [&] { return v8Ref(jsg::check(v8::JSON::Parse(v8Context(), v8Str(v8Isolate, data)))); });\n}\n\nValue Lock::parseJson(v8::Local<v8::String> text) {\n  return withinHandleScope([&] { return v8Ref(jsg::check(v8::JSON::Parse(v8Context(), text))); });\n}\n\nkj::String Lock::serializeJson(v8::Local<v8::Value> value) {\n  return withinHandleScope(\n      [&] { return toString(jsg::check(v8::JSON::Stringify(v8Context(), value))); });\n}\n\nvoid Lock::recursivelyFreeze(Value& value) {\n  jsg::recursivelyFreeze(v8Context(), value.getHandle(*this));\n}\n\nv8::Local<v8::String> Lock::wrapString(kj::StringPtr text) {\n  return v8Str(v8Isolate, text);\n}\n\nbool Lock::toBool(v8::Local<v8::Value> value) {\n  return value->BooleanValue(v8Isolate);\n}\n\nv8::Local<v8::Value> Lock::v8Error(kj::StringPtr message) {\n  return v8::Exception::Error(v8Str(v8Isolate, message));\n}\n\nv8::Local<v8::Value> Lock::v8TypeError(kj::StringPtr message) {\n  return v8::Exception::TypeError(v8Str(v8Isolate, message));\n}\n\nvoid Lock::logWarning(kj::StringPtr message) {\n  IsolateBase::from(v8Isolate).logWarning(*this, message);\n}\n\nvoid Lock::setAllowEval(bool allow) {\n  IsolateBase::from(v8Isolate).setAllowEval({}, allow);\n}\n\nvoid Lock::setUsingEnhancedErrorSerialization() {\n  IsolateBase::from(v8Isolate).setUsingEnhancedErrorSerialization();\n}\n\nvoid Lock::setUsingFastJsgStruct() {\n  IsolateBase::from(v8Isolate).setUsingFastJsgStruct();\n}\n\nbool Lock::isUsingFastJsgStruct() const {\n  return IsolateBase::from(v8Isolate).getUsingFastJsgStruct();\n}\n\nbool Lock::isUsingEnhancedErrorSerialization() const {\n  return IsolateBase::from(v8Isolate).getUsingEnhancedErrorSerialization();\n}\n\nvoid Lock::setCaptureThrowsAsRejections(bool capture) {\n  IsolateBase::from(v8Isolate).setCaptureThrowsAsRejections({}, capture);\n}\n\nvoid Lock::setNodeJsCompatEnabled() {\n  IsolateBase::from(v8Isolate).setNodeJsCompatEnabled({}, true);\n}\n\nvoid Lock::setNodeJsProcessV2Enabled() {\n  IsolateBase::from(v8Isolate).setNodeJsProcessV2Enabled({}, true);\n}\n\nvoid Lock::setRequireReturnsDefaultExportEnabled() {\n  IsolateBase::from(v8Isolate).setRequireReturnsDefaultExportEnabled({}, true);\n}\n\nvoid Lock::setThrowOnUnrecognizedImportAssertion() {\n  IsolateBase::from(v8Isolate).setThrowOnUnrecognizedImportAssertion();\n}\n\nbool Lock::getThrowOnUnrecognizedImportAssertion() const {\n  return IsolateBase::from(v8Isolate).getThrowOnUnrecognizedImportAssertion();\n}\n\nvoid Lock::disableTopLevelAwait() {\n  IsolateBase::from(v8Isolate).disableTopLevelAwait();\n}\n\nvoid Lock::setToStringTag() {\n  IsolateBase::from(v8Isolate).enableSetToStringTag();\n}\n\nvoid Lock::setImmutablePrototype() {\n  IsolateBase::from(v8Isolate).enableSetImmutablePrototype();\n}\n\nvoid Lock::setSpecCompliantPropertyAttributes() {\n  IsolateBase::from(v8Isolate).enableSpecCompliantPropertyAttributes();\n}\n\nvoid Lock::setLoggerCallback(kj::Function<Logger>&& logger) {\n  IsolateBase::from(v8Isolate).setLoggerCallback({}, kj::mv(logger));\n}\n\nvoid Lock::setErrorReporterCallback(kj::Function<ErrorReporter>&& errorReporter) {\n  IsolateBase::from(v8Isolate).setErrorReporterCallback({}, kj::mv(errorReporter));\n}\n\nvoid Lock::requestGcForTesting() const {\n  if (!isPredictableModeForTest()) {\n    KJ_LOG(ERROR, \"Test GC used while not in a test\");\n    return;\n  }\n  v8Isolate->RequestGarbageCollectionForTesting(\n      v8::Isolate::GarbageCollectionType::kFullGarbageCollection);\n}\n\nvoid Lock::v8Set(v8::Local<v8::Object> obj, kj::StringPtr name, v8::Local<v8::Value> value) {\n  KJ_ASSERT(check(obj->Set(v8Context(), v8StrIntern(v8Isolate, name), value)));\n}\n\nvoid Lock::v8Set(v8::Local<v8::Object> obj, kj::StringPtr name, Value& value) {\n  v8Set(obj, name, value.getHandle(*this));\n}\n\nvoid Lock::v8Set(v8::Local<v8::Object> obj, V8Ref<v8::String>& name, Value& value) {\n  KJ_ASSERT(check(obj->Set(v8Context(), name.getHandle(*this), value.getHandle(*this))));\n}\n\nv8::Local<v8::Value> Lock::v8Get(v8::Local<v8::Object> obj, kj::StringPtr name) {\n  return check(obj->Get(v8Context(), v8StrIntern(v8Isolate, name)));\n}\n\nv8::Local<v8::Value> Lock::v8Get(v8::Local<v8::Array> obj, uint idx) {\n  return check(obj->Get(v8Context(), idx));\n}\n\nbool Lock::v8Has(v8::Local<v8::Object> obj, kj::StringPtr name) {\n  return check(obj->Has(v8Context(), v8StrIntern(v8Isolate, name)));\n}\n\nbool Lock::v8HasOwn(v8::Local<v8::Object> obj, kj::StringPtr name) {\n  return check(obj->HasOwnProperty(v8Context(), v8StrIntern(v8Isolate, name)));\n}\n\nvoid Lock::runMicrotasks() {\n  v8Isolate->PerformMicrotaskCheckpoint();\n\n  auto& isolate = IsolateBase::from(v8Isolate);\n  // We only expect at most a handful of extra checkpoints. Keep a generous cap\n  // to flush cascaded microtasks but prevent a potential busy loop.\n  static constexpr uint MAX_EXTRA_MICROTASK_CHECKPOINTS = 64;\n  for (uint i = 0; i < MAX_EXTRA_MICROTASK_CHECKPOINTS; ++i) {\n    if (!isolate.takeExtraMicrotaskCheckpointRequested({})) {\n      return;\n    }\n    v8Isolate->PerformMicrotaskCheckpoint();\n  }\n\n  if (isolate.takeExtraMicrotaskCheckpointRequested({})) {\n    KJ_LOG(WARNING, \"extra microtask checkpoint limit reached\", MAX_EXTRA_MICROTASK_CHECKPOINTS);\n  }\n}\n\nvoid Lock::requestExtraMicrotaskCheckpoint() {\n  IsolateBase::from(v8Isolate).requestExtraMicrotaskCheckpoint({});\n}\n\nvoid Lock::requestTermination() {\n  IsolateBase::from(v8Isolate).requestTermination();\n}\n\nbool Lock::isTerminationRequested() const {\n  return IsolateBase::from(v8Isolate).isTerminationRequested();\n}\n\nvoid Lock::terminateNextExecution() {\n  v8Isolate->TerminateExecution();\n}\n\n[[noreturn]] void Lock::terminateExecutionNow() {\n  terminateNextExecution();\n\n  // HACK: This has been observed to reliably make V8 check the termination flag and raise the\n  //   uncatchable termination exception.\n  jsg::check(v8::JSON::Stringify(v8Context(), str()));\n\n  // Shouldn't get here.\n  KJ_FAIL_ASSERT(\"V8 did not terminate execution when asked.\");\n}\n\nbool Lock::pumpMsgLoop() {\n  return IsolateBase::from(v8Isolate).pumpMsgLoop();\n}\n\nName Lock::newSymbol(kj::StringPtr symbol) {\n  return Name(*this, v8::Symbol::New(v8Isolate, v8StrIntern(v8Isolate, symbol)));\n}\n\nName Lock::newSharedSymbol(kj::StringPtr symbol) {\n  return Name(*this, v8::Symbol::For(v8Isolate, v8StrIntern(v8Isolate, symbol)));\n}\n\nName Lock::newApiSymbol(kj::StringPtr symbol) {\n  return Name(*this, v8::Symbol::ForApi(v8Isolate, v8StrIntern(v8Isolate, symbol)));\n}\n\nkj::Maybe<JsObject> Lock::resolveInternalModule(kj::StringPtr specifier) {\n  auto& isolate = IsolateBase::from(v8Isolate);\n  if (isolate.isUsingNewModuleRegistry()) {\n    return jsg::modules::ModuleRegistry::tryResolveModuleNamespace(\n        *this, specifier, jsg::modules::ResolveContext::Type::BUILTIN);\n  }\n\n  // Use the original module registry implementation\n  auto registry = ModuleRegistry::from(*this);\n  KJ_ASSERT(registry != nullptr);\n  auto module = registry->resolveInternalImport(*this, specifier);\n  return jsg::JsObject(module.getHandle(*this).As<v8::Object>());\n}\n\nkj::Maybe<JsObject> Lock::resolvePublicBuiltinModule(kj::StringPtr specifier) {\n  auto& isolate = IsolateBase::from(v8Isolate);\n  KJ_ASSERT(isolate.isUsingNewModuleRegistry());\n  return jsg::modules::ModuleRegistry::tryResolveModuleNamespace(\n      *this, specifier, jsg::modules::ResolveContext::Type::PUBLIC_BUILTIN);\n}\n\nkj::Maybe<JsObject> Lock::resolveModule(kj::StringPtr specifier, RequireEsm requireEsm) {\n  auto& isolate = IsolateBase::from(v8Isolate);\n  if (isolate.isUsingNewModuleRegistry()) {\n    return jsg::modules::ModuleRegistry::tryResolveModuleNamespace(\n        *this, specifier, jsg::modules::ResolveContext::Type::BUNDLE);\n  }\n\n  auto moduleRegistry = jsg::ModuleRegistry::from(*this);\n  if (moduleRegistry == nullptr) return kj::none;\n  auto spec = kj::Path::parse(specifier);\n  auto& info = JSG_REQUIRE_NONNULL(\n      moduleRegistry->resolve(*this, spec), Error, kj::str(\"No such module: \", specifier));\n  JSG_REQUIRE(!requireEsm || info.maybeSynthetic == kj::none, TypeError,\n      \"Main module must be an ES module.\");\n  auto module = info.module.getHandle(*this);\n  jsg::instantiateModule(*this, module);\n  return JsObject(module->GetModuleNamespace().As<v8::Object>());\n}\n\nvoid ExternalMemoryTarget::maybeDeferAdjustment(ssize_t amount) const {\n  // Carefully check whether `isolate` is locked by the current thread. Note that there's a\n  // possibility that the isolate is being torn down in a different thread, which means we cannot\n  // safely call `v8::Locekr::IsLocked()` on it.\n  if (amount == 0) return;\n  v8::Isolate* current = v8::Isolate::TryGetCurrent();\n  v8::Isolate* target = isolate.load(std::memory_order_relaxed);  // could be null!\n\n  if (current != nullptr && current == target) {\n    // The isolate is currently locked by this thread. Note that it's impossible that `isolate` is\n    // concurrently being torn down because only the thread that holds the isolate lock could be\n    // making such a change, and that's us, and we're not.\n    KJ_ASSERT(v8::Locker::IsLocked(target));\n\n    // We use AdjustAmountOfExternalAllocatedMemoryImpl() instead of ExternalMemoryAccounter\n    // because we explicitly want external memory to be allowed to live beyond the isolate\n    // in some cases. The Impl variant bypasses the destructor check that\n    // ExternalMemoryAccounter enforces (requiring adjustment to return to zero).\n    // See patches/v8/0031-Expose-AdjustAmountOfExternalAllocatedMemoryImpl.patch\n    target->AdjustAmountOfExternalAllocatedMemoryImpl(amount);\n  } else {\n    // We don't hold the isolate lock. Instead, record the adjustment to be applied the next time\n    // the isolate lock is acquired.\n    pendingExternalMemoryUpdate.fetch_add(amount, std::memory_order_relaxed);\n  }\n}\n\nvoid ExternalMemoryTarget::adjustNow(Lock& js, ssize_t amount) const {\n#ifdef KJ_DEBUG\n  v8::Isolate* target = isolate.load(std::memory_order_relaxed);\n  if (target != nullptr) {\n    KJ_ASSERT(target == js.v8Isolate);\n  }\n#endif\n  if (amount == 0) return;\n  js.v8Isolate->AdjustAmountOfExternalAllocatedMemoryImpl(amount);\n}\n\nvoid ExternalMemoryTarget::detach() const {\n  isolate.store(nullptr, std::memory_order_relaxed);\n}\n\nExternalMemoryAdjustment ExternalMemoryTarget::getAdjustment(size_t amount) const {\n  return ExternalMemoryAdjustment(this->addRefToThis(), amount);\n}\n\nvoid ExternalMemoryTarget::applyDeferredMemoryUpdate() const {\n  int64_t amount = pendingExternalMemoryUpdate.exchange(0, std::memory_order_relaxed);\n  if (amount != 0) {\n    isolate.load(std::memory_order_relaxed)->AdjustAmountOfExternalAllocatedMemoryImpl(amount);\n  }\n}\n\nbool ExternalMemoryTarget::isIsolateAliveForTest() const {\n  return isolate.load(std::memory_order_relaxed) != nullptr;\n}\n\nint64_t ExternalMemoryTarget::getPendingMemoryUpdateForTest() const {\n  return pendingExternalMemoryUpdate.load(std::memory_order_relaxed);\n}\n\nExternalMemoryAdjustment Lock::getExternalMemoryAdjustment(int64_t amount) {\n  auto adjustment = IsolateBase::from(v8Isolate).getExternalMemoryAdjustment(0);\n  adjustment.adjustNow(*this, amount);\n  return kj::mv(adjustment);\n}\n\nkj::Arc<const ExternalMemoryTarget> Lock::getExternalMemoryTarget() {\n  return IsolateBase::from(v8Isolate).getExternalMemoryTarget();\n}\n\nvoid ExternalMemoryAdjustment::maybeDeferAdjustment(ssize_t amount) {\n  KJ_ASSERT(amount >= -static_cast<ssize_t>(this->amount),\n      \"Memory usage may not be decreased below zero\");\n  if (amount == 0) return;\n  this->amount += amount;\n  externalMemory->maybeDeferAdjustment(amount);\n}\n\nExternalMemoryAdjustment::ExternalMemoryAdjustment(\n    kj::Arc<const ExternalMemoryTarget> externalMemory, size_t amount)\n    : externalMemory(kj::mv(externalMemory)) {\n  if (amount == 0) return;\n  maybeDeferAdjustment(amount);\n}\nExternalMemoryAdjustment::ExternalMemoryAdjustment(ExternalMemoryAdjustment&& other)\n    : externalMemory(kj::mv(other.externalMemory)),\n      amount(other.amount) {\n  other.amount = 0;\n}\n\nExternalMemoryAdjustment& ExternalMemoryAdjustment::operator=(ExternalMemoryAdjustment&& other) {\n  // If we currently have an amount, adjust it back to zero.\n  // In the case we don't have the isolate lock here, the adjustment\n  // will be deferred until the next time we do.\n\n  if (amount > 0) maybeDeferAdjustment(-amount);\n  externalMemory = kj::mv(other.externalMemory);\n  amount = other.amount;\n  other.amount = 0;\n  return *this;\n}\n\nExternalMemoryAdjustment::~ExternalMemoryAdjustment() noexcept(false) {\n  if (amount != 0) {\n    maybeDeferAdjustment(-amount);\n  }\n}\n\nvoid ExternalMemoryAdjustment::adjust(ssize_t amount) {\n  if (amount == 0) return;\n  maybeDeferAdjustment(amount);\n}\n\nvoid ExternalMemoryAdjustment::adjustNow(Lock& js, ssize_t amount) {\n  KJ_ASSERT(amount >= -static_cast<ssize_t>(this->amount),\n      \"Memory usage may not be decreased below zero\");\n\n  this->amount += amount;\n  externalMemory->adjustNow(js, amount);\n}\n\nvoid ExternalMemoryAdjustment::set(size_t amount) {\n  adjust(amount - this->amount);\n}\n\nvoid ExternalMemoryAdjustment::setNow(Lock& js, size_t amount) {\n  adjustNow(js, amount - this->amount);\n}\n\nName::Name(kj::String string): hash(kj::hashCode(string)), inner(kj::mv(string)) {}\n\nName::Name(kj::StringPtr string): hash(kj::hashCode(string)), inner(kj::str(string)) {}\n\nName::Name(Lock& js, v8::Local<v8::Symbol> symbol)\n    : hash(kj::hashCode(symbol->GetIdentityHash())),\n      inner(js.v8Ref(symbol)) {}\n\nkj::OneOf<kj::StringPtr, v8::Local<v8::Symbol>> Name::getUnwrapped(v8::Isolate* isolate) {\n  KJ_SWITCH_ONEOF(inner) {\n    KJ_CASE_ONEOF(str, kj::String) {\n      return str.asPtr();\n    }\n    KJ_CASE_ONEOF(symbol, V8Ref<v8::Symbol>) {\n      return symbol.getHandle(isolate);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid Name::visitForGc(GcVisitor& visitor) {\n  KJ_SWITCH_ONEOF(inner) {\n    KJ_CASE_ONEOF(string, kj::String) {}\n    KJ_CASE_ONEOF(symbol, V8Ref<v8::Symbol>) {\n      visitor.visit(symbol);\n    }\n  }\n}\n\nName Name::clone(jsg::Lock& js) {\n  KJ_SWITCH_ONEOF(inner) {\n    KJ_CASE_ONEOF(str, kj::String) {\n      return Name(kj::str(str));\n    }\n    KJ_CASE_ONEOF(symbol, V8Ref<v8::Symbol>) {\n      return Name(js, symbol.getHandle(js));\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::String Name::toString(jsg::Lock& js) {\n  KJ_SWITCH_ONEOF(inner) {\n    KJ_CASE_ONEOF(str, kj::String) {\n      return kj::str(str);\n    }\n    KJ_CASE_ONEOF(sym, V8Ref<v8::Symbol>) {\n      return kj::str(\"Symbol(\", sym.getHandle(js)->Description(js.v8Isolate), \")\");\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nbool isInGcDestructor() {\n  return HeapTracer::isInCppgcDestructor();\n}\n\nbool USVString::isValidUtf8() const {\n  return simdutf::validate_utf8(cStr(), size());\n}\n\nstd::unique_ptr<v8::BackingStore> Lock::allocBackingStore(size_t size, AllocOption init_mode) {\n  auto v8_mode = (init_mode == AllocOption::ZERO_INITIALIZED)\n      ? v8::BackingStoreInitializationMode::kZeroInitialized\n      : v8::BackingStoreInitializationMode::kUninitialized;\n  auto store = v8::ArrayBuffer::NewBackingStore(\n      v8Isolate, size, v8_mode, v8::BackingStoreOnFailureMode::kReturnNull);\n  JSG_REQUIRE(store != nullptr, RangeError, \"Failed to allocate ArrayBuffer backing store\");\n  return kj::mv(store);\n}\n\nconst capnp::SchemaLoader& ContextGlobal::getSchemaLoader() {\n  return KJ_ASSERT_NONNULL(schemaLoader);\n}\n\nvoid ContextGlobal::setSchemaLoader(const capnp::SchemaLoader& schemaLoader) {\n  this->schemaLoader = schemaLoader;\n}\n\n#ifdef V8_ENABLE_SANDBOX\n// These are disabled by default in workerd. We do not build workerd with\n// the V8_ENABLED_SANDBOX flag. If we do decide to enable it, we will need\n// additional setup to ensure that these are handled correctly on all platforms.\n// For now, keeping it simple. This bit will only be used in the internal\n// project.\nstatic constexpr int kPkeyNoRestrictions = 0;\nMemoryProtectionKeyScope::MemoryProtectionKeyScope(Lock& js)\n    : pkey(js.v8Isolate->GetMemoryProtectionKey()) {}\n\nMemoryProtectionKeyScope::PkeyScope::PkeyScope(int pkey): key(pkey), saved(pkey_get(key)) {\n  pkey_set(pkey, kPkeyNoRestrictions);\n}\nMemoryProtectionKeyScope::PkeyScope::~PkeyScope() {\n  pkey_set(key, saved);\n}\n#endif\n\nnamespace _ {\n\nJsgCatchScope::JsgCatchScope(Lock& js): js(js) {\n  tryCatchHolder.emplace(js.v8Isolate);\n}\n\nvoid JsgCatchScope::catchException(ExceptionToJsOptions options) {\n  // Be sure to release our TryCatch on the way out.\n  KJ_DEFER(tryCatchHolder = kj::none);\n\n  auto& tryCatch = KJ_ASSERT_NONNULL(tryCatchHolder).tryCatch;\n\n  // Same logic as that found in `jsg::Lock::tryCatch()`.\n  try {\n    throw;\n  } catch (JsExceptionThrown&) {\n    if (!tryCatch.CanContinue() || !tryCatch.HasCaught() || tryCatch.Exception().IsEmpty()) {\n      tryCatch.ReThrow();\n      throw;\n    }\n    caughtException.emplace(js.v8Isolate, tryCatch.Exception());\n  } catch (kj::Exception& e) {\n    caughtException.emplace(js.exceptionToJs(kj::mv(e), options));\n  }\n}\n\n}  // namespace _\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/jsg.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// Main public interface to JSG library.\n//\n// Any files declaring an API to export to JavaScript will need to include this header.\n\n#include \"util.h\"\n#include \"wrappable.h\"\n\n#include <workerd/jsg/exception.h>\n#include <workerd/jsg/macro-meta.h>\n#include <workerd/jsg/memory.h>\n#include <workerd/util/strong-bool.h>\n\n#include <v8-external-memory-accounter.h>\n#include <v8-forward.h>\n#include <v8-locker.h>\n#include <v8-profiler.h>\n#include <v8-regexp.h>\n\n#include <capnp/schema-loader.h>\n#include <kj/debug.h>\n#include <kj/exception.h>\n#include <kj/function.h>\n#include <kj/one-of.h>\n#include <kj/string.h>\n#include <kj/time.h>\n\nusing kj::byte;\nusing kj::uint;\n\n#if _MSC_VER\nusing ssize_t = long long;\n#endif\n\nnamespace workerd::jsg {\nkj::String stringifyHandle(v8::Local<v8::Value> value);\n}\n\nnamespace v8 {\n// Allows v8 handles to be passed to kj::str() as well as KJ_LOG and related macros.\ntemplate <workerd::jsg::V8Value T>\nkj::String KJ_STRINGIFY(v8::Local<T> value) {\n  return workerd::jsg::stringifyHandle(value);\n}\n}  // namespace v8\n\nnamespace workerd::jsg {\n\n// =======================================================================================\n// Macros for declaring type glue.\n\n#define JSG_RESOURCE_TYPE(Type, ...)                                                               \\\n  static constexpr ::workerd::jsg::JsgKind JSG_KIND KJ_UNUSED = ::workerd::jsg::JsgKind::RESOURCE; \\\n  using jsgSuper = jsgThis;                                                                        \\\n  using jsgThis = Type;                                                                            \\\n  inline kj::StringPtr jsgGetMemoryName() const override {                                         \\\n    return #Type##_kjc;                                                                            \\\n  }                                                                                                \\\n  inline size_t jsgGetMemorySelfSize() const override {                                            \\\n    return sizeof(Type);                                                                           \\\n  }                                                                                                \\\n  inline void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {                       \\\n    const Type* self = static_cast<const Type*>(this);                                             \\\n    jsgSuper::jsgGetMemoryInfo(tracker);                                                           \\\n    ::workerd::jsg::visitSubclassForMemoryInfo<Type>(self, tracker);                               \\\n  }                                                                                                \\\n  template <typename>                                                                              \\\n  friend constexpr bool ::workerd::jsg::resourceNeedsGcTracing();                                  \\\n  template <typename T>                                                                            \\\n  friend void ::workerd::jsg::visitSubclassForGc(T* obj, ::workerd::jsg::GcVisitor& visitor);      \\\n  inline void jsgVisitForGc(::workerd::jsg::GcVisitor& visitor) override {                         \\\n    jsgSuper::jsgVisitForGc(visitor);                                                              \\\n    ::workerd::jsg::visitSubclassForGc<Type>(this, visitor);                                       \\\n  }                                                                                                \\\n  static void jsgConfiguration(__VA_ARGS__);                                                       \\\n  template <typename Registry, typename Self>                                                      \\\n  static void registerMembers(Registry& registry, ##__VA_ARGS__)\n// Begins a block nested inside a C++ class to declare how that class should be accessible in\n// JavaScript. JSG_RESOURCE_TYPE declares that the class is a \"resource type\" in KJ parlance.\n//\n// https://github.com/sandstorm-io/capnproto/blob/master/style-guide.md#value-types-vs-resource-types\n//\n// In short, this means that the type is normally passed by reference, and that when JavaScript\n// code accesses members of the type, it calls back into C++. This differs from value types, which\n// are normally deep-copied into JavaScript objects such that C++ is no longer involved.\n//\n// Example usage:\n//\n//     class MyApiType: public jsg::Object {\n//       // Some type we want to expose to JavaScript.\n//     public:\n//       static jsg::Ref<MyType> constructor(bool b, kj::String s);\n//       // Called when JavaScript invokes `new MyType()`. The name `constructor` is special.\n//       // If you do not declare a constructor, then attempts to construct the type from\n//       // JavaScript will throw an exception, but you'll still be able to construct it in C++\n//       // using the regular C++ constructor(s).\n//\n//       void foo(int i, kj::String str);\n//       double bar();\n//       // Methods that can be called from JavaScript.\n//\n//       kj::StringPtr getBaz();\n//       void setBaz(kj::String value);\n//       // Methods implementing a property.\n//\n//       JSG_RESOURCE_TYPE(MyApiType) {\n//         JSG_METHOD(foo);\n//         JSG_METHOD(bar);\n//         JSG_INSTANCE_PROPERTY(baz, getBaz, setBaz);\n//       }\n//\n//     private:\n//       void visitForGc(jsg::GcVisitor visitor);\n//       // If this type contains any Ref or Value objects, it must implement visitForGc(), and when\n//       // this is called, it must call `visitor.visit()` on all handles that it knows about. If\n//       // the object doesn't hold any JS handles then it need not implement this. See the\n//       // definition of GcVisitor, below, for more information.\n//\n//       jsg::Value someValue;\n//       jsg::Ref<MyOtherApiType> someOtherResourceObject;\n//       jsg::V8Ref<v8::Map> someState;\n//       // Objects of resource type may be destroyed outside of the isolate lock. Therefore, if you\n//       // need to hold a reference to a V8 object in a resource type, you should use one of these\n//       // classes / class templates. In particular, holding a raw v8::Global<T> may result in\n//       // undefined behavior upon destruction.\n//     };\n//\n// Notice that method parameters and return types are automatically converted between C++ and\n// JavaScript. You specify the full set of types that your JavaScript execution environment will\n// support when you declare your Isolate (usually in high-level code).\n//\n// Additionally, the following types are always supported:\n// - C++ double, int <-> JS Number\n// - C++ kj::Date <-> JS Date in return position, JS Date or millisecond unix epoch as argument\n// - C++ kj::String, kj::StringPtr <-> JS String\n// - C++ kj::Maybe<T> <-> JS null or T\n// - C++ jsg::Optional<T> <-> JS undefined or T\n// - C++ jsg::LenientOptional<T> <-> JS undefined or T (treats type errors as JS undefined)\n// - C++ kj::OneOf<T, U, ...> <-> JS T or U or ...\n// - C++ kj::Array<T> <-> JS Array of T\n// - C++ kj::Array<byte> <-> JS ArrayBuffer\n// - C++ jsg::Dict<T> <-> JS Object used as a map of strings to values of type T\n// - C++ jsg::Function<T(U, V, ...)> <-> JS Function\n// - C++ jsg::Promise<T> <-> JS Promise\n// - C++ jsg::Ref<T> <-> JavaScript resource type\n// - C++ v8::Local<T> <-> JavaScript value\n//\n// There is also some magic. If the first parameter to a method has type\n// `const v8::FunctionCallbackInfo<v8::Value>&`, then it will receive the FunctionCallbackInfo\n// as passed from V8. (For property accessors, this should be PropertyCallbackInfo instead.) This\n// gives you an escape hatch by which you can directly access the V8 context when needed. In this\n// case the second parameter to your method will correspond to the first parameter passed from\n// JavaScript.\n//\n// As another piece of magic, you can add some special types to the end of your parameter list in\n// order to receive functionality from the JavaScript environment itself. These parameters will not\n// actually correspond to JavaScript parameters, and should always be placed at the end of the\n// argument list. They are:\n//\n// - const jsg::TypeHandler<T>&: Provides callback which can be used to convert between V8 handles\n//     and a C++ object of type T, and (for resource types) to allocate objects of type T on the V8\n//     heap. The reference is valid only until your method returns.\n// - `v8::Isolate*`: Receives the V8 isolate pointer.\n//\n// In yet more magic, you can add a single configuration parameter to the JSG_RESOURCE_TYPE macro:\n//\n//     class MyApiType: ... {\n//     public:\n//       JSG_RESOURCE_TYPE(MyApiType, uint apiVersion) {\n//         if (apiVersion > 42) {\n//           using namespace newapi;\n//           JSG_NESTED_TYPE(Widget);\n//         } else {\n//           using namespace oldapi;\n//           JSG_NESTED_TYPE(Widget);\n//         }\n//       }\n//     };\n//\n// Populate the configuration parameter by passing it to the JSG isolate's constructor (the type\n// declared by JSG_DECLARE_ISOLATE_TYPE in setup.h).\n//\n// Different resource types may have different configuration types. However, all the configuration\n// types must be constructable from a single \"meta\" configuration type, which is the type of the\n// configuration passed to the JSG isolate's constructor.\n\n// Use inside a JSG_RESOURCE_TYPE to declare that the resource type itself can be invoked as\n// a function.\n#define JSG_CALLABLE(name)                                                                         \\\n  do {                                                                                             \\\n    registry.template registerCallable<decltype(&Self::name), &Self::name>();                      \\\n  } while (false)\n\n// Use inside a JSG_RESOURCE_TYPE block to declare that the given method should be callable from\n// JavaScript on instances of the resource type.\n#define JSG_METHOD(name)                                                                           \\\n  do {                                                                                             \\\n    static const char NAME[] = #name;                                                              \\\n    registry.template registerMethod<NAME, &Self::name>();                                         \\\n  } while (false)\n\n// Like JSG_METHOD but allows you to specify a different name to use in JavaScript. This is\n// particularly useful when a JavaScript API wants to use a name that is a keyword in C++. For\n// example:\n//\n//     JSG_METHOD_NAMED(delete, delete_);\n#define JSG_METHOD_NAMED(name, method)                                                             \\\n  do {                                                                                             \\\n    static const char NAME[] = #name;                                                              \\\n    registry.template registerMethod<NAME, &Self::method>();                                       \\\n  } while (false)\n\n// Use inside a JSG_RESOURCE_TYPE block to declare that the given method should be callable from\n// JavaScript on the resource type's constructor.\n#define JSG_STATIC_METHOD(name)                                                                    \\\n  do {                                                                                             \\\n    static const char NAME[] = #name;                                                              \\\n    registry.template registerStaticMethod<NAME, decltype(Self::name), &Self::name>();             \\\n  } while (false)\n\n// Like JSG_METHOD_NAMED, but for static methods.\n#define JSG_STATIC_METHOD_NAMED(name, method)                                                      \\\n  do {                                                                                             \\\n    static const char NAME[] = #name;                                                              \\\n    registry.template registerStaticMethod<NAME, decltype(Self::method), &Self::method>();         \\\n  } while (false)\n\n// Use inside a JSG_RESOURCE_TYPE block to make objects of this type iterable. Pass in the name of\n// a method returning an object satisfying the requirements of a JavaScript iterator. Note that this\n// will NOT automatically register the method for you -- you still need to use JSG_METHOD{,_NAMED}\n// if you plan to expose the method to JavaScript. For example:\n//\n//     struct Iterable {\n//       static Iterable constructor();\n//       Iterator entries();\n//       JSG_RESOURCE_TYPE {\n//         JSG_ITERABLE(entries);\n//       }\n//     };\n//\n// will allow a resource type to be iterated over, but not its entries() function to be called.\n//\n//     for (let x of new Iterable()) { /* ... */ }            // GOOD\n//     for (let x of new Iterable().entries()) { /* ... */ }  // BAD!\n//\n// To enable the latter case, you would need to use JSG_METHOD(entries), and make Iterator itself\n// iterable.\n#define JSG_ITERABLE(method)                                                                       \\\n  do {                                                                                             \\\n    static const char NAME[] = #method;                                                            \\\n    registry.template registerIterable<NAME, decltype(&Self::method), &Self::method>();            \\\n  } while (false)\n\n// Use inside a JSG_RESOURCE_TYPE block to make objects of this type async iterable. Pass in the\n// name of a method returning a kj::Promise for an object satisfying the requirements of a\n// JavaScript iterator.\n#define JSG_ASYNC_ITERABLE(method)                                                                 \\\n  do {                                                                                             \\\n    static const char NAME[] = #method;                                                            \\\n    registry.template registerAsyncIterable<NAME, decltype(&Self::method), &Self::method>();       \\\n  } while (false)\n\n// JSG_DISPOSE and JSG_ASYNC_DISPOSE are used to make an object compatible with the\n// JavaScript using and await using keywords (respectively). These allow variables to\n// be defined in such a way that they will have their disposer functions automatically\n// called when the variable goes out of scope, for instance:\n//\n// class Foo { [Symbol.dispose]() { console.log('...'); }}\n// { using foo = new Foo(); }\n//\n// When the containing block exits, the [Symbol.dispose] function will be called on\n// the object, allowing cleanup actions to be performed.\n//\n// There are a number of guidelines that should be followed when implementing\n// disposer methods:\n//\n// 1. Always prefer Symbol.dispose over Symbol.asyncDispose, avoid defining both.\n// 2. Always assume that if the resource needs to be disposed, it's being disposed\n//    in an exception case. Clean disposal should always be explicit.\n// 3. At least for the time being, the exception will not be available to the\n//    disposer, so it will not be able to propagate the error\n// 4. Always implement disposal as an idempotent operation and remember that\n//    users can call the disposer methods directly as many times as they want.\n// 5. Remember that errors thrown from within the disposer will mask the original\n//    error using a SupressedError.\n#define JSG_DISPOSE(method)                                                                        \\\n  do {                                                                                             \\\n    static const char NAME[] = #method;                                                            \\\n    registry.template registerDispose<NAME, decltype(&Self::method), &Self::method>();             \\\n  } while (false)\n#define JSG_ASYNC_DISPOSE(method)                                                                  \\\n  do {                                                                                             \\\n    static const char NAME[] = #method;                                                            \\\n    registry.template registerAsyncDispose<NAME, decltype(&Self::method), &Self::method>();        \\\n  } while (false)\n\n// Use inside a JSG_RESOURCE_TYPE block to declare a property on this object that should be\n// accessible to JavaScript. `name` is the JavaScript member name, while `getter` and `setter` are\n// the names of C++ methods that get and set this property.\n//\n// WARNING: This is usually not what you want. Usually you want JSG_PROTOTYPE_PROPERTY instead.\n// Note that V8 implements instance properties by modifying the instance immediately after\n// construction, which is inefficient and can break some optimizations. For example, any object\n// with an instance property will not be possible to collect during minor GCs, only major GCs.\n// Prototype properties are on the prototype, so have no runtime overhead until they are used.\n#define JSG_INSTANCE_PROPERTY(name, getter, setter)                                                \\\n  do {                                                                                             \\\n    static const char NAME[] = #name;                                                              \\\n    registry.template registerInstanceProperty<NAME, decltype(&Self::getter), &Self::getter,       \\\n        decltype(&Self::setter), &Self::setter>();                                                 \\\n  } while (false)\n\n// Use inside a JSG_RESOURCE_TYPE block to declare a property on this object's prototype that\n// should be accessible to JavaScript. `name` is the JavaScript member name, while `getter` and\n// `setter` are the names of C++ methods that get and set this property.\n//\n// The key difference between JSG_INSTANCE_PROPERTY and JSG_PROTOTYPE_PROPERTY is in exactly how\n// the getters and setters are attached to created JavaScript object. Specifically,\n// JSG_INSTANCE_PROPERTY is similar to:\n//\n//   class Foo1 {\n//     constructor() {\n//       Object.defineProperty(this, 'bar', {\n//         get() { /* ... */ },\n//         set(v) { /* ... */ },\n//       });\n//     }\n//   }\n//\n// Whereas JSG_PROTOTYPE_PROPERTY is equivalent to:\n//\n//   class Foo2 {\n//     get bar() { /* ... */ }\n//     set bar(v) { /* ... */ }\n//   }\n//\n// The difference here is important because, in the former case, the properties are\n// defined directly on *instances* of Foo1 as own properties, while in latter case,\n// the properties are defined on the prototype of all Foo2 instances. In the former\n// case, using JSG_INSTANCE_PROPERTY, the properties are directly enumerable on all instances\n// of Foo1 such that calling Object.keys(new Foo1()) returns ['bar']. However, calling\n// Object.keys(new Foo2()) will return an empty array [] as prototype properties are\n// not directly enumerable.\n//\n// However, that's not the only critical difference. Because instance properties take\n// precedence over prototype properties, Foo1 is not properly subclassable. If I did:\n//\n//   class MyFoo1 extends Foo1 {\n//     get bar() { /** .. **/ }\n//   }\n//\n//   const myFoo1 = new MyFoo1();\n//   console.log(myFoo1.bar);\n//\n// The getter specified in the constructor of Foo1 would be called rather than the\n// getter defined in the MyFoo1 class, which is not what a user would expect!\n// This means that any resource type that uses JSG_INSTANCE_PROPERTY to attach properties\n// will not be properly subclassable. To allow subclasses to work correctly, use\n// JSG_PROTOTYPE_PROPERTY instead.\n#define JSG_PROTOTYPE_PROPERTY(name, getter, setter)                                               \\\n  do {                                                                                             \\\n    static const char NAME[] = #name;                                                              \\\n    registry.template registerPrototypeProperty<NAME, decltype(&Self::getter), &Self::getter,      \\\n        decltype(&Self::setter), &Self::setter>();                                                 \\\n  } while (false)\n\n// Like JSG_INSTANCE_PROPERTY but creates a property that will throw an exception if\n// JavaScript tries to assign to it.\n#define JSG_READONLY_INSTANCE_PROPERTY(name, getter)                                               \\\n  do {                                                                                             \\\n    static const char NAME[] = #name;                                                              \\\n    registry.template registerReadonlyInstanceProperty<NAME, decltype(&Self::getter),              \\\n        &Self::getter>();                                                                          \\\n  } while (false)\n\n// Like JSG_PROTOTYPE_PROPERTY but creates a property that will throw an exception if JavaScript\n// tries to assign to it.\n#define JSG_READONLY_PROTOTYPE_PROPERTY(name, getter)                                              \\\n  do {                                                                                             \\\n    static const char NAME[] = #name;                                                              \\\n    registry.template registerReadonlyPrototypeProperty<NAME, decltype(&Self::getter),             \\\n        &Self::getter>();                                                                          \\\n  } while (false)\n\n// A lazy property will call the getter the first time the property is access but will then\n// replace the property definition with a normal instance property using the returned value.\n// Keep in mind that, as an instance property, these lazily set properties cannot be overridden\n// by subclasses. They are set directly on the instance object itself when it is created.\n#define JSG_LAZY_INSTANCE_PROPERTY(name, getter)                                                   \\\n  do {                                                                                             \\\n    static const char NAME[] = #name;                                                              \\\n    registry.template registerLazyInstanceProperty<NAME, decltype(&Self::getter), &Self::getter,   \\\n        false>();                                                                                  \\\n  } while (false)\n\n#define JSG_LAZY_READONLY_INSTANCE_PROPERTY(name, getter)                                          \\\n  do {                                                                                             \\\n    static const char NAME[] = #name;                                                              \\\n    registry.template registerLazyInstanceProperty<NAME, decltype(&Self::getter), &Self::getter,   \\\n        true>();                                                                                   \\\n  } while (false)\n\n// Use inside a JSG_RESOURCE_TYPE block to declare a property that should be shown when calling\n// `node:util`'s `inspect()` function on values of this type. These properties will be shown when\n// `console.log()`ing too, and should be used to expose internal state useful for debugging.\n// `name` is the name of the property (displayed in square brackets), while `getter` is the name of\n// the C++ method that gets this property's value.\n#define JSG_INSPECT_PROPERTY(name, getter)                                                         \\\n  do {                                                                                             \\\n    static const char NAME[] = #name;                                                              \\\n    registry.template registerInspectProperty<NAME, decltype(&Self::getter), &Self::getter>();     \\\n  } while (false)\n\n// Use inside a JSG_RESOURCE_TYPE to expose a static property on the JavaScript constructor.\n// The property will be read-only and will call the specified static function when accessed.\n// The function should take no parameters or take jsg::Lock& as the first parameter.\n// Example:\n//   static int getVersion() { return 42; }\n//   JSG_RESOURCE_TYPE(MyClass) {\n//     JSG_STATIC_READONLY_PROPERTY(getVersion);  // Exposes as MyClass.getVersion\n//   }\n#define JSG_STATIC_READONLY_PROPERTY(name)                                                         \\\n  do {                                                                                             \\\n    static const char NAME[] = #name;                                                              \\\n    registry.template registerStaticProperty<NAME, decltype(Self::name), &Self::name>();           \\\n  } while (false)\n\n// Use inside a JSG_RESOURCE_TYPE to expose a static property with a different name than the\n// underlying C++ function. The property will be read-only and will call the specified static\n// getter function when accessed. The getter can optionally take jsg::Lock& as the first parameter.\n// Example:\n//   static kj::Array<kj::String> getSupportedTypes() { ... }\n//   JSG_RESOURCE_TYPE(MyClass) {\n//     JSG_STATIC_READONLY_PROPERTY_NAMED(supportedTypes, getSupportedTypes);  // MyClass.supportedTypes\n//   }\n#define JSG_STATIC_READONLY_PROPERTY_NAMED(name, getter)                                           \\\n  do {                                                                                             \\\n    static const char NAME[] = #name;                                                              \\\n    registry.template registerStaticProperty<NAME, decltype(Self::getter), &Self::getter>();       \\\n  } while (false)\n\n// Use inside a JSG_RESOURCE_TYPE to create a static constant member on the constructor and\n// prototype of this object. Only primitive data types (booleans, strings, numbers) are allowed.\n// Unlike the JSG_INSTANCE_PROPERTY and JSG_READONLY_PROPERTY macros, this does not use a getter\n// -- it expects a static constexpr member of a primitive type available in the class by the same\n// name. For example:\n//\n//     struct Interface {\n//       static Interface constructor()\n//       static constexpr int FOO_BAR = 123;\n//       JSG_RESOURCE_TYPE {\n//         JSG_STATIC_CONSTANT(FOO_BAR);\n//       }\n//     };\n//\n// will allow all of the following JS expressions to hold true:\n//\n//     Interface.FOO_BAR                              === 123\n//     Interface.prototype.FOO_BAR                    === 123\n//     new Interface().FOO_BAR                        === 123\n//     Object.getPrototypeOf(new Interface()).FOO_BAR === 123\n//\n// This is useful to implement constant interface members as specified in Web IDL:\n//   https://heycam.github.io/webidl/#idl-constants\n//\n// TODO(someday): This should probably also support the null JS value.\n#define JSG_STATIC_CONSTANT(name)                                                                  \\\n  do {                                                                                             \\\n    static const char NAME[] = #name;                                                              \\\n    registry.template registerStaticConstant<NAME, decltype(Self::name)>(Self::name);              \\\n  } while (false)\n\n// This works the same as JSG_STATIC_CONSTANT but allows us to provide an alias to an arbitrary c++\n// constant instead. For example:\n//     struct Interface {\n//       static Interface constructor()\n//       JSG_RESOURCE_TYPE {\n//         JSG_STATIC_CONSTANT_NAMED(FOO_BAR, SOME_SYSTEM_CONSTANT);\n//       }\n//     };\n#define JSG_STATIC_CONSTANT_NAMED(name, constant)                                                  \\\n  do {                                                                                             \\\n    static const char NAME[] = #name;                                                              \\\n    registry.template registerStaticConstant<NAME, decltype(constant)>(constant);                  \\\n  } while (false)\n\n// Use inside a JSG_RESOURCE_TYPE block to declare that this type inherits from another type,\n// which must also have a JSG_RESOURCE_TYPE block. This type must singly, non-virtually inherit\n// from the specified type. (Multiple inheritance and virtual inheritance will not work since we\n// rely on the pointer to the superclass and the subclass having the same numeric value.)\n#define JSG_INHERIT(Type)                                                                          \\\n  static_assert(kj::canConvert<Self&, Type&>(), #Type \" is not a superclass of this\");             \\\n  registry.template registerInherit<Type>()\n\n// Use inside a JSG_RESOURCE_TYPE block to declare that this type inherits from an intrinsic\n// prototype. This is primarily useful to inherit from v8::kErrorPrototype, like DOMException, and\n// v8::kIteratorPrototype.\n#define JSG_INHERIT_INTRINSIC(intrinsic)                                                           \\\n  do {                                                                                             \\\n    static const char NAME[] = #intrinsic;                                                         \\\n    registry.template registerInheritIntrinsic<NAME>(intrinsic);                                   \\\n  } while (false)\n\n// An isDetected() operation which detects if the expression t.getTemplate(isolate, &u) is available\n// for instances `t`, `u` of types T and U.\ntemplate <typename T, typename U>\nusing HasGetTemplateOverload = decltype(kj::instance<T&>().getTemplate(\n    static_cast<v8::Isolate*>(nullptr), static_cast<U*>(nullptr)));\n\n// Use inside a JSG_RESOURCE_TYPE block to declare that the given type should be visible as a\n// static member of this type. Typically, your \"global\" type would use several of these\n// declarations to make other types appear in the global scope. It is not necessary for the types\n// to be nested in C++.\n#define JSG_NESTED_TYPE(Type)                                                                      \\\n  do {                                                                                             \\\n    /* Note that `Type` may be incomplete here, we should be OK with that. */                      \\\n    static const char NAME[] = #Type;                                                              \\\n    registry.template registerNestedType<Type, NAME>();                                            \\\n  } while (false)\n\n#define JSG_NESTED_TYPE_NAMED(Type, Name)                                                          \\\n  do {                                                                                             \\\n    /* Note that `Type` may be incomplete here, we should be OK with that. */                      \\\n    static const char NAME[] = #Name;                                                              \\\n    registry.template registerNestedType<Type, NAME>();                                            \\\n  } while (false)\n\n// Adds reflection to a resource type. See PropertyReflection<T> for usage.\n#define JSG_REFLECTION(...)                                                                        \\\n  static constexpr bool jsgHasReflection = true;                                                   \\\n  template <typename TypeWrapper>                                                                  \\\n  void jsgInitReflection(TypeWrapper& wrapper) {                                                   \\\n    jsgSuper::jsgInitReflection(wrapper);                                                          \\\n    wrapper.initReflection(this, __VA_ARGS__);                                                     \\\n  }\n\n// Declares the type serializable. See jsg::Serializer for usage.\n#define JSG_SERIALIZABLE(TAG, ...)                                                                 \\\n  static_assert(static_cast<uint>(jsgSuper::jsgSerializeTag) != static_cast<uint>(TAG));           \\\n  static constexpr auto jsgSerializeTag = TAG;                                                     \\\n  static constexpr decltype(jsgSerializeTag) jsgSerializeOldTags[] = {__VA_ARGS__};                \\\n  static constexpr auto jsgSerializeOneway = false\n\n// Like JSG_SERIALIZABLE(), but the type has only a serialize() method and no deserialize(). It\n// is expected that the specified tag actually belongs to some other type, so a serialization\n// round trip will have the effect of replacing this type with that other type.\n//\n// Used e.g. for JsRpcTarget, which becomes JsRpcStub after serialization.\n#define JSG_ONEWAY_SERIALIZABLE(TAG)                                                               \\\n  static_assert(static_cast<uint>(jsgSuper::jsgSerializeTag) != static_cast<uint>(TAG));           \\\n  static constexpr auto jsgSerializeTag = TAG;                                                     \\\n  static constexpr decltype(jsgSerializeTag) jsgSerializeOldTags[] = {};                           \\\n  static constexpr auto jsgSerializeOneway = true\n\n// Declares a wildcard property getter. If a property is requested that isn't already present on\n// the object or its prototypes, the wildcard property getter will be given a chance to return the\n// property.\n//\n// WARNING: Be very careful about the property named \"then\". If it exists and is a function, V8\n//   will treat your type as a custom thenable, i.e. as a kind of Promise, which means among other\n//   things that any time a Promise would resolve to it, it will try to chain with it. You should\n//   probably return kj::none when \"then\" is requested.\n//\n// Example:\n//\n//   struct MyType {\n//     // Get the value of the named dynamic property. Returns none if the property doesn't exist.\n//     // `SomeType` can be any type that JSG is able to convert to JavaScript.\n//     kj::Maybe<SomeType> getWildcard(jsg::Lock& js, kj::StringPtr name);\n//\n//     JSG_RESOURCE_TYPE(MyType) {\n//       JSG_WILDCARD_PROPERTY(getWildcard);\n//     }\n//   };\n#define JSG_WILDCARD_PROPERTY(method)                                                              \\\n  do {                                                                                             \\\n    registry.template registerWildcardProperty<Self, decltype(&Self::method), &Self::method>();    \\\n  } while (false)\n\n// Use inside a JSG_RESOURCE_TYPE block to declare that this type should be considered a \"root\" for\n// the purposes of automatically generating TypeScript definitions. All \"root\" types and their\n// recursively referenced types (e.g. method parameter/return types, property types, inherits, etc)\n// will be included in the generated TypeScript. See the `## TypeScript` section of the JSG README.md\n// for more details.\n#define JSG_TS_ROOT() registry.registerTypeScriptRoot()\n\n// Use inside a JSG_RESOURCE_TYPE block to customise the generated TypeScript definition for this type.\n// This macro accepts a single override parameter containing a partial TypeScript statement definition.\n// Varargs are accepted so that overrides can contain `,` outside of balanced brackets. See the\n// `## TypeScript` section of the JSG README.md for many more details and examples.\n#define JSG_TS_OVERRIDE(...)                                                                       \\\n  do {                                                                                             \\\n    static const char OVERRIDE[] = JSG_STRING_LITERAL(__VA_ARGS__);                                \\\n    registry.template registerTypeScriptOverride<OVERRIDE>();                                      \\\n  } while (false)\n\n// Use inside a JSG_RESOURCE_TYPE block to insert additional TypeScript definitions next to the generated\n// TypeScript definition for this type. This macro accepts a single define parameter containing one or\n// more TypeScript definitions (e.g. interfaces, classes, type aliases, consts, ...). Varargs are accepted\n// so that defines can contain `,` outside of balanced brackets. See the `## TypeScript`section of the JSG\n// README.md for more details.\n#define JSG_TS_DEFINE(...)                                                                         \\\n  do {                                                                                             \\\n    static const char DEFINE[] = JSG_STRING_LITERAL(__VA_ARGS__);                                  \\\n    registry.template registerTypeScriptDefine<DEFINE>();                                          \\\n  } while (false)\n\n// Like JSG_TS_ROOT but for use with JSG_STRUCT. Should be placed adjacent to the JSG_STRUCT declaration,\n// inside the same `struct` definition. See the `## TypeScript` section of the JSG README.md for more\n// details.\n#define JSG_STRUCT_TS_ROOT() static constexpr bool _JSG_STRUCT_TS_ROOT_DO_NOT_USE_DIRECTLY = true\n\n// Like JSG_TS_OVERRIDE but for use with JSG_STRUCT. Should be placed adjacent to the JSG_STRUCT\n// declaration, inside the same `struct` definition. See the `## TypeScript` section of the JSG README.md\n// for many more details and examples.\n#define JSG_STRUCT_TS_OVERRIDE(...)                                                                \\\n  static constexpr char _JSG_STRUCT_TS_OVERRIDE_DO_NOT_USE_DIRECTLY[] =                            \\\n      JSG_STRING_LITERAL(__VA_ARGS__)\n\n// Like JSG_STRUCT_TS_OVERRIDE, however it enables dynamic selection of TS_OVERRIDE.\n// Should be placed adjacent to the JSG_STRUCT declaration, inside the same struct definition.\n#define JSG_STRUCT_TS_OVERRIDE_DYNAMIC(...)                                                        \\\n  static void jsgConfiguration(__VA_ARGS__);                                                       \\\n  template <typename Registry>                                                                     \\\n  static void registerTypeScriptDynamicOverride(Registry& registry, ##__VA_ARGS__)\n\n// Like JSG_TS_DEFINE but for use with JSG_STRUCT. Should be placed adjacent to the JSG_STRUCT\n// declaration, inside the same `struct` definition. See the `## TypeScript`section of the JSG README.md\n// for more details.\n#define JSG_STRUCT_TS_DEFINE(...)                                                                  \\\n  static constexpr char _JSG_STRUCT_TS_DEFINE_DO_NOT_USE_DIRECTLY[] =                              \\\n      JSG_STRING_LITERAL(__VA_ARGS__)\n\n// Adds a group of javascript modules to the module registry when context is instantiated.\n// bundle is of a Bundle type from workerd/jsg/modules.capnp.\n// Modules will be resolved according to their type and module registry normal resolve rules.\n#define JSG_CONTEXT_JS_BUNDLE(bundle)                                                              \\\n  do {                                                                                             \\\n    registry.registerJsBundle(bundle);                                                             \\\n  } while (false)\n\n// true when T has _JSG_STRUCT_TS_ROOT_DO_NOT_USE_DIRECTLY field generated by JSG_STRUCT_TS_ROOT\ntemplate <typename T>\nconcept HasStructTypeScriptRoot = requires { T::_JSG_STRUCT_TS_ROOT_DO_NOT_USE_DIRECTLY; };\n\n// true when T has _JSG_STRUCT_TS_OVERRIDE_DO_NOT_USE_DIRECTLY field generated by JSG_STRUCT_TS_OVERRIDE\ntemplate <typename T>\nconcept HasStructTypeScriptOverride = requires { T::_JSG_STRUCT_TS_OVERRIDE_DO_NOT_USE_DIRECTLY; };\n\n// true when T has _JSG_STRUCT_TS_DEFINE_DO_NOT_USE_DIRECTLY field generated by JSG_STRUCT_TS_DEFINE\ntemplate <typename T>\nconcept HasStructTypeScriptDefine = requires { T::_JSG_STRUCT_TS_DEFINE_DO_NOT_USE_DIRECTLY; };\n\n// Nest this inside a simple struct declaration in order to support translating it to/from a\n// JavaScript object / Web IDL dictionary.\n//\n//   struct MyStruct {\n//     double foo;\n//     kj::String bar;\n//     kj::String $public;\n//\n//     JSG_STRUCT(foo, bar, $public);\n//   };\n//\n// All of the types which are supported as method parameter / return types are also supported as\n// struct field types.\n//\n// Note that if you use `jsg::Optional<T>` as a field type, then the field will not be present at\n// all in JavaScript when the optional is null in C++ (as opposed to the field being present but\n// assigned the value `undefined`).\n//\n// Note that if a `validate` function is provided, then it will be called after the struct is\n// unwrapped from v8. This would be an appropriate time to throw an error.\n// Signature: void validate(jsg::Lock& js);\n// Example:\n// struct ValidatingFoo {\n//  kj::String abc;\n//  void validate(jsg::Lock& js) {\n//    JSG_REQUIRE(abc.size() != 0, TypeError, \"Field 'abc' had no length in 'ValidatingFoo'.\");\n//  }\n//  JSG_STRUCT(abc);\n// };\n//\n// In this example the validate method would throw a `TypeError` if the size of the `abc` field was zero.\n//\n// Fields with a starting '$' will have that dollar sign prefix stripped in the JS binding. A\n// motivating example to enact that change was WebCrypto which has a field in a dictionary called\n// \"public\". '$' was chosen as a token we can use because it's a character for a C++ identifier. If\n// the Javascript field needs to contain '$' for some reason (which they probably shouldn't since\n// identifiers starting with $ are rare in JS land, especially for things the runtime would be\n// exporting), then you should be able to use '$$' as the identifier prefix in C++ since only the\n// first '$' gets stripped.\n#define JSG_STRUCT(...)                                                                            \\\n  static constexpr ::workerd::jsg::JsgKind JSG_KIND KJ_UNUSED = ::workerd::jsg::JsgKind::STRUCT;   \\\n  static constexpr char JSG_FOR_EACH(JSG_STRUCT_FIELD_NAME, , __VA_ARGS__);                        \\\n  template <typename TypeWrapper, typename Self>                                                   \\\n  using JsgFieldWrappers =                                                                         \\\n      ::workerd::jsg::TypeTuple<JSG_FOR_EACH(JSG_STRUCT_FIELD, , __VA_ARGS__)>;                    \\\n  template <typename Self>                                                                         \\\n  static v8::Local<v8::DictionaryTemplate> jsgGetTemplate(v8::Isolate* isolate) {                  \\\n    kj::Vector<std::string_view> names;                                                            \\\n    JSG_FOR_EACH(JSG_STRUCT_FIELD_COL, , __VA_ARGS__);                                             \\\n    auto namesPtr = names.asPtr().asConst();                                                       \\\n    return v8::DictionaryTemplate::New(                                                            \\\n        isolate, v8::MemorySpan<const std::string_view>(namesPtr.begin(), namesPtr.size()));       \\\n  }                                                                                                \\\n  template <typename Registry, typename Self, typename Config>                                     \\\n  static void registerMembersInternal(Registry& registry, Config arg) {                            \\\n    JSG_FOR_EACH(JSG_STRUCT_REGISTER_MEMBER, , __VA_ARGS__);                                       \\\n    if constexpr (::workerd::jsg::HasStructTypeScriptRoot<Self>) {                                 \\\n      registry.registerTypeScriptRoot();                                                           \\\n    }                                                                                              \\\n    if constexpr (requires(jsg::GetConfiguration<Self> arg) {                                      \\\n                    registerTypeScriptDynamicOverride<Registry>(registry, arg);                    \\\n                  }) {                                                                             \\\n      registerTypeScriptDynamicOverride<Registry>(registry, arg);                                  \\\n    } else if constexpr (::workerd::jsg::HasStructTypeScriptOverride<Self>) {                      \\\n      registry.template registerTypeScriptOverride<                                                \\\n          Self::_JSG_STRUCT_TS_OVERRIDE_DO_NOT_USE_DIRECTLY>();                                    \\\n    }                                                                                              \\\n    if constexpr (::workerd::jsg::HasStructTypeScriptDefine<Self>) {                               \\\n      registry                                                                                     \\\n          .template registerTypeScriptDefine<Self::_JSG_STRUCT_TS_DEFINE_DO_NOT_USE_DIRECTLY>();   \\\n    }                                                                                              \\\n  }                                                                                                \\\n  template <typename Registry, typename Self>                                                      \\\n  static void registerMembers(Registry& registry)                                                  \\\n    requires(!jsg::HasConfiguration<Self>)                                                         \\\n  {                                                                                                \\\n    registerMembersInternal<Registry, Self, void*>(registry, nullptr);                             \\\n  }                                                                                                \\\n  template <typename Registry, typename Self>                                                      \\\n  static void registerMembers(Registry& registry, jsg::GetConfiguration<Self> arg)                 \\\n    requires jsg::HasConfiguration<Self>                                                           \\\n  {                                                                                                \\\n    registerMembersInternal<Registry, Self, jsg::GetConfiguration<Self>>(registry, arg);           \\\n  }\n\ntemplate <size_t N>\ninline consteval size_t prefixLengthToStrip(const char (&s)[N]) {\n  return s[0] == '$' ? 1 : 0;\n}\n\n// This string may not be what's actually exported to v8. For example, if it starts with a `$`, then\n// this value will still contain the `$` even though the `FieldWrapper` template argument will have\n// it stripped.\n#define JSG_STRUCT_FIELD_NAME(_, name) name##_JSG_NAME_DO_NOT_USE_DIRECTLY[] = #name\n\n#define JSG_STRUCT_FIELD_COL(_, name)                                                              \\\n  ::workerd::jsg::jsgAddToStructNames<decltype(::kj::instance<Self>().name),                       \\\n      name##_JSG_NAME_DO_NOT_USE_DIRECTLY, ::workerd::jsg::prefixLengthToStrip(#name)>(names)\n\n// (Internal implementation details for JSG_STRUCT.)\n#define JSG_STRUCT_FIELD(_, name)                                                                  \\\n  ::workerd::jsg::FieldWrapper<TypeWrapper, Self, decltype(::kj::instance<Self>().name),           \\\n      &Self::name, name##_JSG_NAME_DO_NOT_USE_DIRECTLY,                                            \\\n      ::workerd::jsg::prefixLengthToStrip(#name)>\n// (Internal implementation details for JSG_STRUCT.)\n#define JSG_STRUCT_REGISTER_MEMBER(_, name)                                                        \\\n  registry.template registerStructProperty<decltype(::kj::instance<Self>().name), &Self::name>(    \\\n      name##_JSG_NAME_DO_NOT_USE_DIRECTLY)\n\n// Indexes for adding API data to V8's Isolate object.\nenum SetDataIndex {\n  // The jsg::IsolateBase for a particular V8 isolate.\n  SET_DATA_ISOLATE_BASE,\n  // The TypeWrapper object for a particular V8 isolate.\n  SET_DATA_TYPE_WRAPPER,\n  // The lock associated with the V8 isolate.\n  SET_DATA_LOCK,\n  // The Worker::Isolate associated with the V8 isolate.\n  SET_DATA_ISOLATE,\n  // The address of the base of the 4Gbyte compressed pointer area.\n  // If we are using the sandbox it's also the base of the sandbox.\n  SET_DATA_CAGE_BASE,\n  // Used by JSG<->Rust integration.\n  SET_DATA_RUST_REALM,\n  // The number of slots workerd uses in the API data for Isolate objects.\n  SET_DATA_SLOTS_IN_USE,\n};\n\n// =======================================================================================\n// Special types\n//\n// These types can be used in C++ to represent various JavaScript idioms / Web IDL types.\n\nclass Lock;\nWD_STRONG_BOOL(RequireEsm);\n\n// Arbitrary V8 data, wrapped for storage from C++. You can't do much with it, so instead you\n// should probably use V8Ref<T>, a version of this that's strongly typed.\n//\n// When storing a Value inside a C++ object that is itself exported back to JavaScript, make sure\n// to implement GC visitation -- see GcVisitor, below.\n//\n// It is safe to destroy a strong jsg::Data object outside of the isolate lock. In this case,\n// the underlying V8 handles will be added to a queue, to be destroyed the next time a thread\n// locks the isolate. This means their destruction is non-deterministic, but that is true of V8\n// objects anyway, due to the GC. Weak jsg::Data (i.e., those which are reachable by V8's GC,\n// see GcVisitor below) must still be destroyed under the isolate lock to guard against concurrent\n// modification with the GC.\n//\n// Move construction and move assignment of strong jsg::Data is well-defined even without\n// holding the isolate lock. That is, it is safe to move Values unless you have implemented GC\n// visitation for them. Moving jsg::Data which are reachable via GC visitation is undefined\n// behavior outside of an isolate lock.\nclass Data {\n public:\n  Data(decltype(nullptr)) {}\n  ~Data() noexcept(false) {\n    destroy();\n  }\n  Data(Data&& other): isolate(other.isolate), handle(kj::mv(other.handle)) {\n    KJ_IF_SOME(t, other.tracedHandle) {\n      moveFromTraced(other, t);\n    }\n    other.isolate = nullptr;\n    assertInvariant();\n    other.assertInvariant();\n  }\n  Data& operator=(Data&& other) {\n    if (this != &other) {\n      destroy();\n      isolate = other.isolate;\n      handle = kj::mv(other.handle);\n      other.isolate = nullptr;\n      KJ_IF_SOME(t, other.tracedHandle) {\n        moveFromTraced(other, t);\n      }\n    }\n    assertInvariant();\n    other.assertInvariant();\n    return *this;\n  }\n  KJ_DISALLOW_COPY(Data);\n\n  Data(v8::Isolate* isolate, v8::Local<v8::Data> handle)\n      : isolate(isolate),\n        handle(isolate, handle) {}\n\n  // Get the raw underlying v8 handle.\n  v8::Local<v8::Data> getHandle(v8::Isolate* isolate) const {\n    return handle.Get(isolate);\n  }\n\n  // Get the raw underlying v8 handle.\n  v8::Local<v8::Data> getHandle(Lock& js) const;\n\n  Data addRef(v8::Isolate* isolate) {\n    return Data(isolate, getHandle(isolate));\n  }\n  Data addRef(Lock& js);\n\n  inline bool operator==(const Data& other) const {\n    return handle == other.handle;\n  }\n  inline bool operator==(const v8::Local<v8::Data>& other) const {\n    return handle == other;\n  }\n\n private:\n  // The isolate with which the handles below are associated.\n  v8::Isolate* isolate = nullptr;\n\n  // Handle to the value which will be marked strong if any untraced C++ references exist, weak\n  // otherwise.\n  v8::Global<v8::Data> handle;\n\n  // When `handle` is weak, `tracedHandle` is a copy of it used to integrate with V8 GC tracing.\n  // When `handle` is strong, we null out `tracedHandle`, because we don't need it, and it is\n  // illegal to hold onto a traced handle without actually marking it during each trace.\n  kj::Maybe<v8::TracedReference<v8::Data>> tracedHandle;\n\n  friend class GcVisitor;\n\n  void destroy();\n\n  // Debugging helpers.\n\n  // Assert that only empty values are associated with null isolates.\n  //\n  // Note that we use IASSERT (which is only enabled in debug) here because this function is\n  // intended to be invoked from the move ctor and assignment operator. We expect them to be\n  // invoked a lot and want them to be as optimizable as possible.\n  void assertInvariant() {\n    KJ_IASSERT(isolate != nullptr || handle.IsEmpty());\n  }\n\n  // Implement move constructor when the source of the move has previously been visited for\n  // garbage collection.\n  void moveFromTraced(Data& other, v8::TracedReference<v8::Data>& otherTracedRef) noexcept;\n\n  friend class MemoryTracker;\n};\n\n// A drop-in replacement for v8::Global<T>. Its big feature is that, like jsg::Data, a\n// jsg::V8Ref<T> is safe to destroy outside of the isolate lock.\n//\n// Generally you should prefer using jsg::Value (for v8::Value) or jsg::Ref<T>. Use a\n// jsg::V8Ref<T> when you need the type-safety of holding a handle to a specific V8 type.\ntemplate <typename T>\nclass V8Ref: private Data {\n public:\n  V8Ref(decltype(nullptr)): Data(nullptr) {}\n  V8Ref(v8::Isolate* isolate, v8::Local<T> handle): Data(isolate, handle) {}\n  V8Ref(V8Ref&& other): Data(kj::mv(other)) {}\n  V8Ref& operator=(V8Ref&& other) {\n    Data::operator=(kj::mv(other));\n    return *this;\n  }\n  KJ_DISALLOW_COPY(V8Ref);\n\n  v8::Local<T> getHandle(v8::Isolate* isolate) const {\n    if constexpr (std::is_base_of<v8::Value, T>()) {\n      // V8 doesn't let us cast directly from v8::Data to subtypes of v8::Value, so we're forced to\n      // use this double cast... Ech.\n      return Data::getHandle(isolate).template As<v8::Value>().template As<T>();\n    } else {\n      return Data::getHandle(isolate).template As<T>();\n    }\n  }\n  v8::Local<T> getHandle(jsg::Lock& js) const;\n\n  V8Ref addRef(v8::Isolate* isolate) {\n    return V8Ref(isolate, getHandle(isolate));\n  }\n  V8Ref addRef(jsg::Lock& js);\n\n  V8Ref deepClone(jsg::Lock& js);\n\n  inline bool operator==(const V8Ref& other) const {\n    return Data::operator==(other);\n  }\n  inline bool operator==(const v8::Local<T>& other) const {\n    return Data::operator==(other);\n  }\n\n  template <typename U>\n  V8Ref<U> cast(jsg::Lock& js);\n\n private:\n  friend class GcVisitor;\n  friend class MemoryTracker;\n};\n\nusing Value = V8Ref<v8::Value>;\n\n// Like V8Ref but also implements `hashCode()`. Useful as a key into a kj::HashTable.\n//\n// T must v8::Object or a subclass (or anything that implements GetIdentityHash()).\ntemplate <typename T>\nclass HashableV8Ref: public V8Ref<T> {\n public:\n  HashableV8Ref(decltype(nullptr)): V8Ref<T>(nullptr), identityHash(0) {}\n  HashableV8Ref(v8::Isolate* isolate, v8::Local<T> handle)\n      // TODO(perf): It's not clear if V8's `GetIdentityHash()` is intended to return uniform\n      //   results as required for KJ hashing, so we pass it to `kj::hashCode()` to further hash\n      //   it. This may be unnecessary. Note that there are several other call sites of\n      //   `GetIdentityHash()` which do the same -- if we decide we don't need this we should fix\n      //   all of them.\n      : V8Ref<T>(isolate, handle),\n        identityHash(kj::hashCode(handle->GetIdentityHash())) {}\n  HashableV8Ref(HashableV8Ref&& other) = default;\n  HashableV8Ref& operator=(HashableV8Ref&& other) = default;\n  KJ_DISALLOW_COPY(HashableV8Ref);\n\n  HashableV8Ref addRef(v8::Isolate* isolate) {\n    return HashableV8Ref(isolate, this->getHandle(isolate), identityHash);\n  }\n  HashableV8Ref addRef(jsg::Lock& js);\n\n  int hashCode() const {\n    return identityHash;\n  }\n\n private:\n  int identityHash;\n\n  HashableV8Ref(v8::Isolate* isolate, v8::Local<T> handle, int identityHash)\n      : V8Ref<T>(isolate, handle),\n        identityHash(identityHash) {}\n};\n\ntemplate <V8Value T>\nvoid MemoryTracker::trackField(\n    kj::StringPtr edgeName, const V8Ref<T>& value, kj::Maybe<kj::StringPtr> nodeName) {\n  // Even though we're passing in a template T, casting to a v8::Value is sufficient here.\n  trackField(edgeName, value.handle.Get(isolate_).template As<v8::Value>(), nodeName);\n}\n\n// A value of type T, or `undefined`.\n//\n// In C++, this has the same usage as kj::Maybe<T>. However, a null kj::Maybe<T> corresponds to\n// `null` in JavaScript, whereas a null Optional<T> corresponds to `undefined` in JavaScript.\n//\n// Note: Due to Web IDL's undefined-to-nullable coercion rule, a null Maybe<T> can also unwrap\n//   from an `undefined` value explicitly passed to a non-optional nullable.\n//\n// There are two main use cases for Optional<T>: optional function/method parameters and optional\n// JSG_STRUCT members. In both cases, a null value in C++ corresponds to the parameter/field not\n// being present at all in JavaScript, or explicitly set to `undefined`.\n//\n// In Web IDL, function parameters are considered required unless marked `optional`, while\n// dictionary (JSG_STRUCT) members are considered optional unless marked `required`. So, if you\n// were implementing an API specified in Web IDL like so:\n//\n//     dictionary Data {\n//       double number;\n//       required DOMString string;\n//     };\n//     void foo(optional Data data);\n//\n// An appropriate representation in C++ would be:\n//\n//     struct Data {\n//       Optional<double> number;\n//       kj::String string;\n//       JSG_STRUCT(number, string);\n//     };\n//     void foo(Optional<Data> data);\ntemplate <typename T>\nclass Optional: public kj::Maybe<T> {\n public:\n  // Inheriting constructors does not inherit copy/move constructors, so we declare a forwarding\n  // constructor instead.\n  template <typename... Params>\n  Optional(Params&&... params): kj::Maybe<T>(kj::fwd<Params>(params)...) {}\n};\n\n//  Identical to Optional, but rather than treating failures to unwrap a JS value to type T as an\n//  error, it just results in an unset LenientOptional.\ntemplate <typename T>\nclass LenientOptional: public kj::Maybe<T> {\n public:\n  // Inheriting constructors does not inherit copy/move constructors, so we declare a forwarding\n  // constructor instead.\n  template <typename... Params>\n  LenientOptional(Params&&... params): kj::Maybe<T>(kj::fwd<Params>(params)...) {}\n};\n\n// Use this type in a JSG_STRUCT to define a special field that will be filled in with a\n// reference to the original struct's JavaScript representation. This is useful e.g. if you\n// may need to pull additional fields out of the struct.\n//\n// Another option is to use jsg::Identified<MyStruct>, but sometimes storing the reference\n// into a field of the unwrapped struct is more convenient.\nclass SelfRef: public V8Ref<v8::Object> {\n public:\n  using V8Ref::V8Ref;\n\n  // Convert the V8Ref<v8::Object> to a V8Ref<v8::Value>\n  inline Value asValue(Lock& js) const;\n};\n\ntemplate <typename U>\nstatic constexpr bool isUsableStructField = !kj::isSameType<U, SelfRef>() &&\n    !kj::isSameType<U, Unimplemented>() && !kj::isSameType<U, WontImplement>();\n\ntemplate <typename T, const char* name, size_t prefix>\nvoid jsgAddToStructNames(auto& names) {\n  constexpr const char* exportedName = name + prefix;\n  if constexpr (isUsableStructField<T>) names.add(exportedName);\n}\n\n// A USVString has the exact same representation as a kj::String, but we guarantee that it meets\n// the WHATWG definition of a \"scalar value string\". Particularly, a USVString will never contain\n// invalid surrogate characters. A USVString should be used when implementing a Web API that\n// requires this behaviour.\n// See <https://infra.spec.whatwg.org/#scalar-value-string>\nclass USVString: public kj::String {\n public:\n  // Inheriting constructors does not inherit copy/move constructors, so we declare a forwarding\n  // constructor instead.\n  template <typename... Params>\n  explicit USVString(Params&&... params): kj::String(kj::fwd<Params>(params)...) {\n    KJ_DASSERT(isValidUtf8());\n  }\n\n private:\n  // This is a seperate method to avoid including simdutf in the header file.\n  bool isValidUtf8() const;\n};\n\n// A DOMString has the exact same representation as a kj::String, but may contain WTF-8 encoded\n// data like unpaired surrogate characters, that are not strictly valid in UTF-8. A DOMString\n// should be used when implementing a Web API that requires this behaviour, or when an explicit\n// decision is made to accept potentially invalid strings.\nclass DOMString: public kj::String {\n public:\n  // Inheriting constructors does not inherit copy/move constructors, so we declare a forwarding\n  // constructor instead.\n  template <typename... Params>\n  explicit DOMString(Params&&... params): kj::String(kj::fwd<Params>(params)...) {}\n};\n\n// A Dict<V, K> in C++ corresponds to a JavaScript object that is being used as a string -> value\n// map, where all the values are of type T.\n//\n// Note: A Dict<V, K> corresponds to a record<K, V> in the Web IDL language.\ntemplate <typename Value, typename Key = kj::String>\nstruct Dict {\n  // TODO(someday): Maybe make this a map and not an array? Current use case doesn't care, though.\n\n  // Field of an object.\n  struct Field {\n    Key name;\n    Value value;\n\n    JSG_MEMORY_INFO(Field) {\n      tracker.trackField(\"name\", name);\n      tracker.trackField(\"value\", value);\n    }\n  };\n\n  kj::Array<Field> fields;\n\n  JSG_MEMORY_INFO(Dict) {\n    for (const auto& field: fields) {\n      tracker.trackField(nullptr, field);\n    }\n  }\n};\n\ntemplate <typename T>\nclass TypeHandler;\n\n// When used as a function argument type, captures all remaining arguments passed to the method,\n// unwrapping them all as type T.\ntemplate <typename T>\nclass Arguments: public kj::Array<T> {\n public:\n  Arguments(kj::Array<T>&& value): kj::Array<T>(kj::mv(value)) {}\n\n  using ElementType = T;\n};\n\n// Is `T` some specialization of `Arguments<U>`?\ntemplate <typename T>\nstruct IsArguments_ {\n  static constexpr bool value = false;\n};\ntemplate <typename T>\nstruct IsArguments_<Arguments<T>> {\n  static constexpr bool value = true;\n};\ntemplate <typename T>\nconstexpr bool isArguments() {\n  return IsArguments_<T>::value;\n}\n\ntemplate <typename T>\nconstexpr bool resourceNeedsGcTracing();\ntemplate <typename T>\nvoid visitSubclassForGc(T* obj, GcVisitor& visitor);\n\n// All resource types must inherit from this.\nclass Object: private Wrappable {\n public:\n  using jsgThis = Object;\n\n  // Objects that extend from jsg::Object should never be copied or moved\n  // independently of their owning jsg::Ref so we explicitly delete the\n  // copy and move constructors and assignment operators to be safe.\n  KJ_DISALLOW_COPY_AND_MOVE(Object);\n\n  // Since we explicitly delete the copy and move constructors, we have\n  // to explicitly declare the default constructor.\n  Object() = default;\n\n  inline void jsgVisitForGc(GcVisitor& visitor) override {}\n\n  // Subclasses should override these to provide appropriate information for\n  // the heap snapshot process.\n  inline kj::StringPtr jsgGetMemoryName() const override {\n    return \"Object\";\n  }\n  inline size_t jsgGetMemorySelfSize() const override {\n    return sizeof(Object);\n  }\n  inline void jsgGetMemoryInfo(MemoryTracker& tracker) const override {\n    Wrappable::jsgGetMemoryInfo(tracker);\n  }\n  inline v8::Local<v8::Object> jsgGetMemoryInfoWrapperObject(v8::Isolate* isolate) override {\n    return Wrappable::jsgGetMemoryInfoWrapperObject(isolate);\n  }\n  inline bool jsgGetMemoryInfoIsRootNode() const override {\n    return Wrappable::jsgGetMemoryInfoIsRootNode();\n  }\n\n  static constexpr bool jsgHasReflection = false;\n  template <typename TypeWrapper>\n  inline void jsgInitReflection(TypeWrapper& wrapper) {}\n\n  // Dummy invalid serialization tag. This is only used to detect when a subclass has defined their\n  // own tag.\n  static constexpr uint jsgSerializeTag = kj::maxValue;\n\n private:\n  inline void visitForMemoryInfo(MemoryTracker& tracker) const {}\n  inline void visitForGc(GcVisitor& visitor) {}\n  template <typename>\n  friend constexpr bool ::workerd::jsg::resourceNeedsGcTracing();\n  template <typename T>\n  friend void visitSubclassForGc(T* obj, GcVisitor& visitor);\n  template <typename T>\n  friend void visitSubclassForMemoryInfo(const T* obj, MemoryTracker& visitor);\n  template <typename T>\n  friend class Ref;\n  friend class kj::Refcounted;\n  template <typename T>\n  friend kj::Own<T> kj::addRef(T& object);\n  template <typename T, typename... Params>\n  friend kj::Own<T> kj::refcounted(Params&&... params);\n  friend class GcVisitor;\n  template <typename, typename...>\n  friend class TypeWrapper;\n  template <typename, typename>\n  friend class ResourceWrapper;\n  template <typename>\n  friend class ObjectWrapper;\n  template <typename>\n  friend class SelfPropertyReader;\n  friend class MemoryTracker;\n};\n\n// Ref<T> is a reference to a resource type (a type with a JSG_RESOURCE_TYPE block) living on\n// the V8 heap.\n//\n// Use Ref<T> when you want a long-lived reference to such a type. If you only need a reference\n// that lasts until your method returns, you can specify the parameter type `T&` instead, which\n// is more efficient. Use Ref<T> when you need to keep the reference longer than that.\n//\n// WARNING: When storing Ref<T> in a C++ object that itself is referenced from the JS heap,\n// you must implement GC visitation; see GcVisitor, below.\n//\n// It is safe to destroy a jsg::Ref<T> object outside of the isolate lock. In this case,\n// the underlying V8 handles will be added to a queue, to be destroyed the next time a thread\n// locks the isolate. This means their destruction is non-deterministic, but that is true of V8\n// objects anyway, due to the GC.\n//\n// Move construction and move assignment of strong jsg::Ref<T>s is well-defined even without\n// holding the isolate lock. That is, it is safe to move Refs unless you have implemented GC\n// visitation for them. Moving jsg::Ref<T>s which are reachable via GC visitation is undefined\n// behavior outside of an isolate lock.\ntemplate <typename T>\nclass Ref {\n public:\n  Ref(decltype(nullptr)): strong(false) {}\n  Ref(Ref&& other): inner(kj::mv(other.inner)), strong(true) {\n    if (other.strong) {\n      other.strong = false;\n    } else {\n      inner->addStrongRef();\n    }\n  }\n\n  // Upgrade a KJ allocation to a Ref. This is useful if you want to allocate the object outside\n  // the isolate lock and then bring it in later. The object must be allocated with\n  // kj::refcounted. Once the Ref is constructed, the refcount is protected by the isolate lock\n  // going forward; you can no longer add or remove refs outside the lock.\n  explicit Ref(kj::Own<T> innerParam): inner(kj::mv(innerParam)), strong(true) {\n    inner->addStrongRef();\n  }\n  template <typename U, typename = kj::EnableIf<kj::canConvert<U&, T&>()>>\n  Ref(Ref<U>&& other): inner(kj::mv(other.inner)),\n                       strong(true) {\n    if (other.strong) {\n      other.strong = false;\n    } else {\n      inner->addStrongRef();\n    }\n  }\n  template <typename U>\n  Ref& operator=(Ref<U>&& other) {\n    destroy();\n    inner = kj::mv(other.inner);\n    strong = true;\n    if (other.strong) {\n      other.strong = false;\n    } else {\n      inner->addStrongRef();\n    }\n    return *this;\n  }\n  ~Ref() noexcept(false) {\n    destroy();\n  }\n  KJ_DISALLOW_COPY(Ref);\n\n  T& operator*() {\n    return *inner;\n  }\n  T* operator->() {\n    return inner.get();\n  }\n  T* get() {\n    return inner.get();\n  }\n\n  const T& operator*() const {\n    return *inner;\n  }\n  const T* operator->() const {\n    return inner.get();\n  }\n  const T* get() const {\n    return inner.get();\n  }\n\n  Ref addRef() & {\n    return Ref(kj::addRef(*inner));\n  }\n  Ref addRef() && = delete;  // would be redundant\n\n  // If the object has a JS wrapper, return it. Note that the JS wrapper is initialized lazily\n  // when the object is first passed to JS, so you can't be sure that it exists. To reliably\n  // get a handle (creating it on-demand if necessary), use a TypeHandler<Ref<T>>.\n  kj::Maybe<v8::Local<v8::Object>> tryGetHandle(v8::Isolate* isolate) {\n    return inner->tryGetHandle(isolate);\n  }\n\n  kj::Maybe<v8::Local<v8::Object>> tryGetHandle(Lock& js);\n\n  // Attach a JavaScript object which implements the JS interface for this C++ object. Normally,\n  // this happens automatically the first time the Ref is passed across the FFI barrier into JS.\n  // This method may be useful in order to use a different wrapper type than the one that would\n  // be used automatically. This method is also useful when implementing TypeWrapperExtensions.\n  //\n  // It is an error to attach a wrapper when another wrapper is already attached. Hence,\n  // typically this should only be called on a newly-allocated object.\n  void attachWrapper(v8::Isolate* isolate, v8::Local<v8::Object> object) {\n    inner->Wrappable::attachWrapper(isolate, object, resourceNeedsGcTracing<T>());\n  }\n\n private:\n  kj::Own<T> inner;\n\n  // If this has ever been traced, the parent object from which the trace originated. This is kept\n  // for debugging purposes only -- there should only ever be one parent for a particular ref.\n  //\n  // This field does NOT move when the Ref moves, because it's a property of the specific Ref\n  // location.\n  kj::Maybe<Wrappable&> parent;\n\n  // True if the ref is currently counted in the target's strong refcount.\n  bool strong;\n\n  void destroy() {\n    if (auto ptr = inner.get(); ptr != nullptr) {\n      inner->maybeDeferDestruction(strong, kj::mv(inner), static_cast<Wrappable*>(ptr));\n    }\n  }\n\n  template <typename>\n  friend class Ref;\n  template <typename U, typename... Params>\n  friend Ref<U> alloc(Params&&... params);\n  friend class Lock;\n  template <typename U>\n  friend Ref<U> _jsgThis(U* obj);\n  template <typename, typename>\n  friend class ResourceWrapper;\n  template <typename>\n  friend class ObjectWrapper;\n  friend class GcVisitor;\n};\n\ntemplate <MemoryRetainer T>\nvoid MemoryTracker::trackField(\n    kj::StringPtr edgeName, const Ref<T>& value, kj::Maybe<kj::StringPtr> nodeName) {\n  trackField(edgeName, value.get(), nodeName);\n}\n\ntemplate <typename T, typename... Params>\n// TODO(js.alloc): When most of the jsg::alloc users are updated we can uncomment\n// the deprecation here. When all uses are updated to use js.alloc, we can remove\n// this method entirely.\n//[[deprecated(\"Use js.alloc<T>(...) instead\")]]\nRef<T> alloc(Params&&... params) {\n  return Ref<T>(kj::refcounted<T>(kj::fwd<Params>(params)...));\n}\n\ntemplate <typename T>\nRef<T> _jsgThis(T* obj) {\n  return Ref<T>(kj::addRef(*obj));\n}\n\n#define JSG_THIS (::workerd::jsg::_jsgThis(this))\n\n// Holds a value of type `T` and allows it to be passed to JavaScript multiple times, resulting\n// in exactly the same JavaScript object each time (will compare equal using `===`). You may\n// pass `MemoizedIdentity<T>` by reference, e.g. you could define a method of a JSG_RESOURCE_TYPE\n// which returns `MemoizedIdentity<T>&`, returning a reference to a member of the object.\n//\n// Note that you don't need to wrap `jsg::Ref<T>` this way, as it already has the property that\n// only one wrapper will be created. `MemoizedIdentity` can wrap any type that is convertible to\n// JavaScript, including types that are otherwise pass-by-value.\ntemplate <typename T>\nclass MemoizedIdentity {\n public:\n  inline MemoizedIdentity(T value): value(kj::mv(value)) {}\n\n  inline MemoizedIdentity& operator=(T value) {\n    this->value = kj::mv(value);\n    return *this;\n  }\n\n  void visitForGc(GcVisitor& visitor);\n\n  JSG_MEMORY_INFO(MemoizedIdentity) {\n    KJ_SWITCH_ONEOF(value) {\n      KJ_CASE_ONEOF(val, T) {\n        if constexpr (MemoryRetainer<T>) {\n          tracker.trackField(\"value\", val);\n        } else {\n          tracker.trackFieldWithSize(\"value\", sizeof(T));\n        }\n      }\n      KJ_CASE_ONEOF(val, Value) {\n        tracker.trackField(\"value\", val);\n      }\n    }\n  }\n\n private:\n  kj::OneOf<T, Value> value;\n\n  template <typename TypeWrapper>\n  friend class MemoizedIdentityWrapper;\n  friend class MemoryTracker;\n};\n\n// Accept this type from JavaScript when you want to receive an object's identity in addition to\n// unwrapping it. This is useful, for example, if you need to be able to recognize when the\n// application passes in the same object again later.\n//\n// `T` must be a type whose JavaScript representation is an Object (including Functions), since\n// other types do not have a notion of identity-equality.\ntemplate <typename T>\nstruct Identified {\n  // Handle to the original object.\n  HashableV8Ref<v8::Object> identity;\n\n  // The object's unwrapped value.\n  T unwrapped;\n\n  JSG_MEMORY_INFO(Identified) {\n    tracker.trackField(\"identity\", identity);\n    if constexpr (MemoryRetainer<T>) {\n      tracker.trackField(\"unwrapped\", unwrapped);\n    } else {\n      tracker.trackFieldWithSize(\"unwrapped\", sizeof(T));\n    }\n  }\n};\n\n// jsg::Name represents a value that is either a string or a v8::Symbol. It is most useful for\n// use in APIs that can accept both interchangeably.\n//\n// Name implements hashCode() so it is suitable for use as a key in kj::HashMap, etc.\nclass Name final {\n public:\n  explicit Name(kj::String string);\n  explicit Name(kj::StringPtr string);\n  explicit Name(Lock& js, v8::Local<v8::Symbol> symbol);\n  KJ_DISALLOW_COPY(Name);\n  Name(Name&&) = default;\n  Name& operator=(Name&&) = default;\n\n  inline int hashCode() const {\n    return hash;\n  }\n\n  Name clone(jsg::Lock& js);\n\n  kj::String toString(jsg::Lock& js);\n\n  JSG_MEMORY_INFO(Name) {\n    KJ_SWITCH_ONEOF(inner) {\n      KJ_CASE_ONEOF(str, kj::String) {\n        tracker.trackField(\"inner\", str);\n      }\n      KJ_CASE_ONEOF(sym, V8Ref<v8::Symbol>) {\n        tracker.trackField(\"inner\", sym);\n      }\n    }\n  }\n\n private:\n  int hash;\n  kj::OneOf<kj::String, V8Ref<v8::Symbol>> inner;\n\n  kj::OneOf<kj::StringPtr, v8::Local<v8::Symbol>> getUnwrapped(v8::Isolate* isolate);\n\n  friend class NameWrapper;\n\n  void visitForGc(GcVisitor& visitor);\n\n  friend class MemoryTracker;\n};\n\n// jsg::Function<T> behaves much like kj::Function<T>, but can be passed to/from JS. It works in\n// both directions: you can receive a jsg::Function from JavaScript and call it from C++, and you\n// can also initialize a jsg::Function from a C++ lambda and pass it back to JavaScript.\n//\n// Since the function could be backed by JavaScript, when calling it, you must always pass\n// `jsg::Lock&` as the first parameter. When implementing a `jsg::Function` using a C++ lambda,\n// the lambda should similarly take `jsg::Lock&` as the first parameter. Note that this first\n// parameter is not declared in the function's signature. For example, `jsg::Function<int(int)>`\n// declares a function that accepts a parameter of type int and returns an int. However, when\n// actually calling it, you must still pass `jsg::Lock&`, with the `int` as the second parameter.\n// (Of course, from the JavaScript side, the lock parameter is hidden, and the `int` is in fact\n// the first parameter.)\n//\n// jsg::Function can be visited using a GcVisitor. If a jsg::Function is initialized from a\n// C++ functor object that happens to have a public method `visitForGc(jsg::GcVisitor&)`, then\n// it will arrange for that method to be called during GC tracing.\n//\n// Note that, obviously, a normal C++ lambda cannot have a `visitForGc()` method. So when writing\n// a visitable function in C++, you have to write out a struct or class with an `operator()`\n// method and a `visitForGc()` method. That's a bit of a pain, so the macro JSG_VISITABLE_LAMBDA()\n// is provided to assist. This lets you write something like a lambda expression where some of the\n// captured variables can be GC visited. Example:\n//\n//     jsg::Function<void(int)> myFunc =\n//         JSG_VISITABLE_LAMBDA((foo = getFoo(), bar, &baz),\n//                               (foo, baz.handle),\n//                               (jsg::Lock& js, int param) {\n//       // ... body of function ...\n//     });\n//\n// The first parameter to JSG_VISITABLE_LAMBDA is your capture list, in exactly the syntax that a\n// regular lambda would use, except in parentheses instead of square brackets. The second\n// parameter is a parenthesized list of visitation expressions. This will literally be used as a\n// parameter list to `gcVisitor.visit()`, e.g. in the above example\n// `gcVisitor.visit(foo, baz.handle)` will be called when visited. Finally, the third parameter\n// is the rest of the lambda expression -- parameter list followed by body block.\ntemplate <typename Signature>\nclass Function;\n\n// Use this to unwrap a JavaScript function that should be called as a constructor (with `new`).\n// The return type in this case is the constructed type. `Constructor` is a subclass of `Function`;\n// it can be used in all the same ways.\ntemplate <typename T>\nclass Constructor;\n\n// jsg::Promise<T> wraps a JavaScript promise. Use it when you want to pass Promises to or from\n// JavaScript.\n//\n// jsg::Promise<T> offers a `.then()` method which looks a lot like kj::Promise<T>'s similar\n// function, except that you must pass `Lock&` to it, and it passes `Lock&` back to the callback:\n//\n//     Promise<int> promise = ...;\n//     Promise<kj::String> promise2 = promise.then(js,\n//         [](Lock& js, int val) { return kj::str(val); })\n//\n// Unlike kj::Promise, jsg::Promises run on the V8 microtask loop, NOT on the KJ event loop. That\n// implies that the isolate is already locked and active during callbacks, and control does not\n// return to the KJ event loop at all if a promise continuation is immediately runnable.\n//\n// `.catch_()` and two-argument `.then()` are supported. Thrown exceptions are represented using\n// `jsg::Value`, since technically JavaScript allows throwing any type.\n//\n// The type T does not have to be convertible to/from JavaScript unless a Promise<T> is actually\n// passed to/from JavaScript. That is, you can have an intermediate Promise<U> where U is a type\n// that has no JavaScript representation. What actually happens is, when a Promise<T> is passed\n// from JS into C++, JSG adds a .then() which unwraps the value T, and when a Promise<T> is\n// passed back to JS, JSG adds a .then() to wrap the value again.\n//\n// If the type T is GC visitable (i.e. it is a type that you could pass to GcVisitor::visit()),\n// then the system will arrange to correctly visit it when the T is wrapped in a Promise.\n// Additionally, if a continuation function passed to `.then()` is GC-visitable, it will similarly\n// be visited. JSG_VISITABLE_LAMBDA is a useful in conjunction with `.then()` (see jsg::Function,\n// above).\n//\n// Unlike KJ promises, dropping a jsg::Promise does not cancel it. However, like a KJ promise,\n// a jsg::Promise can only have `.then()` called on it once; the continuation consumes the value.\n// This is so that pass-by-move C++ types can safely be passed through jsg::Promises. Of course,\n// once returned to JavaScript, JS code is free to call `.then()` as many times as it wants; this\n// restriction only applies to calling `.then()` in C++.\n//\n// When a JSG method returns a Promise, the system ensures that the object on which the method\n// was called will not be GC'ed until the Promise resolves (or is itself GC'ed, indicating it will\n// never resolve). This is a convenience so that method implementations that return promises do\n// not need to carefully capture a reference to `JSG_THIS`.\n//\n// You can construct an immediate Promise value using js.resolvedPromise() and\n// js.rejectedPromise() (see below).\n//\n// You can also create a promise/resolver pair:\n//\n//     auto [promise, resolver] = js.newPromiseAndResolver<kj::String>();\n//     resolver.resolve(js, kj::str(foo));\n//\n// The Promise exposes a markAsHandled() API that will mark JavaScript Promise such that rejections\n// are not reported to the isolate's unhandled rejection tracking mechanisms. Importantly, any then\n// then() or catch_() continuation on either type will return an unhandled Promise. But, any\n// whenResolved() continuation, and any type handler continuations added internally will be\n// automatically marked handled. Use of markAsHandled() should be rare. It is largely used by Web\n// Platform APIs in certain cases where consumption of a promise is optional, or where a promise\n// rejection is likely to be surfaced via multiple promises (and therefore only needs to be handled\n// once).\ntemplate <typename T>\nclass Promise;\n\ntemplate <typename T>\nstruct PromiseResolverPair;\n\n// Convenience template to detect a `jsg::Promise` type.\ntemplate <typename T>\nstruct IsPromise_ {\n  static constexpr bool value = false;\n};\ntemplate <typename T>\nstruct IsPromise_<Promise<T>> {\n  static constexpr bool value = true;\n};\ntemplate <typename T>\nconstexpr bool isPromise() {\n  return IsPromise_<T>::value;\n}\n\n// Convenience template to strip off `jsg::Promise`.\ntemplate <typename T>\nstruct RemovePromise_ {\n  using Type = T;\n};\ntemplate <typename T>\nstruct RemovePromise_<Promise<T>> {\n  using Type = T;\n};\ntemplate <typename T>\nusing RemovePromise = RemovePromise_<T>::Type;\n\n// Convenience template to add `jsg::Promise` if it is not present.\ntemplate <typename T>\nstruct MaintainPromise_ {\n  using Type = Promise<T>;\n};\ntemplate <typename T>\nstruct MaintainPromise_<Promise<T>> {\n  using Type = Promise<T>;\n};\ntemplate <typename T>\nusing MaintainPromise = MaintainPromise_<T>::Type;\n\n// Convenience template to calculate the return type of a function when passed parameter type T.\n// `T = void` is understood to mean no parameters.\ntemplate <typename Func, typename T, bool passLock>\nstruct ReturnType_;\ntemplate <typename Func, typename T>\nstruct ReturnType_<Func, T, false> {\n  using Type = decltype(kj::instance<Func>()(kj::instance<T>()));\n};\ntemplate <typename Func, typename T>\nstruct ReturnType_<Func, T, true> {\n  using Type = decltype(kj::instance<Func>()(kj::instance<Lock&>(), kj::instance<T>()));\n};\ntemplate <typename Func>\nstruct ReturnType_<Func, void, false> {\n  using Type = decltype(kj::instance<Func>()());\n};\ntemplate <typename Func>\nstruct ReturnType_<Func, void, true> {\n  using Type = decltype(kj::instance<Func>()(kj::instance<Lock&>()));\n};\ntemplate <typename Func, typename T, bool passLock = false>\nusing ReturnType = ReturnType_<Func, T, passLock>::Type;\n\n// Convenience template to produce a promise for the result of calling a function with the given\n// parameter type. This wraps the function's result type in `jsg::Promise` UNLESS the function\n// already returns a `jsg::Promise`, in which case the type is unchanged.\n// TODO(cleanup): The passLock = false variation is currently only used for js.evalNow().\n// It would be nice to refactor that a bit so we can clean up this template and simplify.\ntemplate <typename Func, typename Param, bool passLock>\nusing PromiseForResult = MaintainPromise<ReturnType<Func, Param, passLock>>;\n\n// All types declared with JSG_RESOURCE_TYPE which are intended to be used as the global object\n// must inherit jsg::ContextGlobal, in addition to inheriting jsg::Object\n// (or a subclass of jsg::Object).\n// jsg::Object should always be the first inherited class, and jsg::ContextGlobal second.\n// The lifetime of the global object matches the lifetime of the JavaScript context.\nclass ContextGlobal {\n public:\n  ContextGlobal() {}\n\n  KJ_DISALLOW_COPY_AND_MOVE(ContextGlobal);\n\n  const capnp::SchemaLoader& getSchemaLoader();\n\n private:\n  // This opaque owner is used to keep the ModuleRegistry alive as long as the ContextGlobal\n  // object is alive. This may be the legacy or new module registry, depending which one is\n  // in use. We don't care about the actual type here, just that it is kept alive.\n  kj::Own<void> moduleRegistryBackingOwner;\n  kj::Maybe<const capnp::SchemaLoader&> schemaLoader;\n\n  void setModuleRegistryBackingOwner(kj::Own<void> registry) {\n    moduleRegistryBackingOwner = kj::mv(registry);\n  }\n  void setSchemaLoader(const capnp::SchemaLoader& schemaLoader);\n\n  template <typename, typename>\n  friend class ResourceWrapper;\n};\n\n// Reference to a JavaScript context whose global object wraps a C++ object of type T. This is\n// similar to Ref but not the same, since JsContext provides access to the Context itself,\n// which is more than just the global object.\ntemplate <typename T>\nclass JsContext {\n public:\n  static_assert(\n      std::is_base_of_v<ContextGlobal, T>, \"context global type must extend jsg::ContextGlobal\");\n\n  JsContext(v8::Local<v8::Context> handle, Ref<T> object)\n      : handle(v8::Isolate::GetCurrent(), handle),\n        object(kj::mv(object)) {}\n\n  JsContext(JsContext&&) = default;\n  KJ_DISALLOW_COPY(JsContext);\n\n  T& operator*() {\n    return *object;\n  }\n  T* operator->() {\n    return object.get();\n  }\n\n  v8::Local<v8::Context> getHandle(v8::Isolate* isolate) const {\n    return handle.Get(isolate);\n  }\n  v8::Local<v8::Context> getHandle(Lock& js) const;\n\n private:\n  v8::Global<v8::Context> handle;\n  Ref<T> object;\n};\n\nclass BufferSource;\n\nconstexpr bool hasPublicVisitForGc_(...) {\n  return false;\n}\ntemplate <typename T, typename = decltype(&T::visitForGc)>\nconstexpr bool hasPublicVisitForGc_(T*) {\n  return true;\n}\n\ntemplate <typename T>\nconstexpr bool hasPublicVisitForGc() {\n  return hasPublicVisitForGc_(static_cast<T*>(nullptr));\n}\n\n// Visitor used during garbage collection. Any resource class that holds `Ref`s should\n// implement GC visitation by declaring a private method like:\n//\n//     private:\n//       void visitForGc(GcVisitor& visitor);\n//\n// In this method, call visitor.visit() on each `Ref` owned by the object.\n//\n// A `visitForGc()` method does NOT need to handle visiting superclasses. The JSG framework will\n// automatically discover the presence of `visitForGc()` in each class in the hierarchy and will\n// arrange for them all to be called. (Thus, when adding a new `visitForGc()` method to a class\n// that has many subclasses, there is no need to update the subclasses.)\n//\n// Functors (freestanding functions/callbacks/lambdas, not declared as resources) can also\n// implement GC visitation. To do so, implement the function as a struct with `operator()`, and\n// also give the function a `visitForGc()` method. In this case, `visitForGc()` must be public.\n//\n// GC visitation is optional. If your type owns no `Ref`s, it can skip implementing\n// `visitForGc()`. You can also omit `visitForGc()` if you don't care about the possibility of\n// reference cycles. Any `Ref` which is not explicitly visited will not be eligible for\n// garbage collection at all. Hence, failure to implement proper visitation may lead to memory\n// leaks, but NOT to use-after-free.\n//\n// Note that GC visitation technically only collects JavaScript objects, including wrapper\n// objects. C++ objects will not be collected if they contain reference cycles entirely in C++\n// land. That is, if you have two C++ objects that contain `Ref`s to each other, and you\n// implement GC visitation, the JavaScript wrapper objects wrapping these C++ objects will be\n// collected, but the C++ objects will not -- a `Ref` can never becomes \"dangling\", and\n// therefore the C++ objects cannot be destroyed because there's no correct order in which to\n// destroy them. To avoid this situation, make sure your C++ objects have clear ownership, so\n// that the reference graph is a DAG, just like you always would in C++.\nclass GcVisitor {\n public:\n  template <typename T>\n  void visit(Ref<T>& ref) {\n    ref.inner->visitRef(*this, ref.parent, ref.strong);\n  }\n\n  template <typename T>\n  void visit(kj::Maybe<Ref<T>>& maybeRef) {\n    KJ_IF_SOME(ref, maybeRef) {\n      visit(ref);\n    }\n  }\n\n  void visit(Data& data);\n\n  /// Visit a raw `v8::Global<Value>` + `v8::TracedReference<Data>` pair,\n  /// implementing the same strong↔traced dual-mode switching as `visit(Data&)`.\n  ///\n  /// Used by the Rust JSG FFI to support `v8::Global<T>` fields on Rust\n  /// resources without a full `jsg::Data` wrapper.\n  void visit(v8::Global<v8::Value>& strong, v8::TracedReference<v8::Data>& traced);\n\n  void visit(kj::Maybe<Data>& maybeData) {\n    KJ_IF_SOME(data, maybeData) {\n      visit(data);\n    }\n  }\n\n  template <typename T>\n  void visit(V8Ref<T>& value) {\n    visit(static_cast<Data&>(value));\n  }\n\n  template <typename T>\n  void visit(kj::Maybe<V8Ref<T>>& maybeValue) {\n    KJ_IF_SOME(value, maybeValue) {\n      visit(value);\n    }\n  }\n\n  void visit(BufferSource& bufferSource);\n\n  template <typename T, typename = kj::EnableIf<hasPublicVisitForGc<T>()>()>\n  void visit(T& supportsVisit) {\n    supportsVisit.visitForGc(*this);\n  }\n\n  template <typename T, typename = kj::EnableIf<hasPublicVisitForGc<T>()>()>\n  void visit(kj::Maybe<T>& maybeSupportsVisit) {\n    KJ_IF_SOME(supportsVisit, maybeSupportsVisit) {\n      supportsVisit.visitForGc(*this);\n    }\n  }\n\n  void visit() {}\n\n  template <typename T, typename U, typename... Args>\n  void visit(T& t, U& u, Args&... remaining) {\n    visit(t);\n    visit(u, kj::fwd<Args&>(remaining)...);\n  }\n\n  void visitAll(auto& collection) {\n    for (auto& item: collection) {\n      visit(item);\n    }\n  }\n\n private:\n  Wrappable& parent;\n  kj::Maybe<cppgc::Visitor&> cppgcVisitor;\n\n  explicit GcVisitor(Wrappable& parent, kj::Maybe<cppgc::Visitor&> cppgcVisitor)\n      : parent(parent),\n        cppgcVisitor(cppgcVisitor) {}\n  KJ_DISALLOW_COPY_AND_MOVE(GcVisitor);\n\n  friend class Wrappable;\n  friend class Object;\n  friend class HeapTracer;\n};\n\nconstexpr bool isGcVisitable_(...) {\n  return false;\n}\ntemplate <typename T, typename = decltype(kj::instance<GcVisitor>().visit(kj::instance<T&>()))>\nconstexpr bool isGcVisitable_(T*) {\n  return true;\n}\n\ntemplate <typename T>\nconstexpr bool isGcVisitable() {\n  return isGcVisitable_(static_cast<T*>(nullptr));\n}\n\n// TypeHandler translates between V8 values and local values for a particular type T.\n//\n// When you define a function or method that is to be wrapped by V8, you can append TypeHandler\n// references to your argument list, and they will automatically be filled in by the caller.\n// This allows you to manually manage objects of this type in your code. For example, you could\n// use this to manually test two different possible input types:\n//\n//     void myMethod(v8::Local<v8::Value> handle,\n//                   const TypeHandler<MyType1>& wrapper,\n//                   const TypeHandler<MyType2>& wrapper) {\n//       KJ_IF_SOME(value1, wrapper.tryUnwrap(handle)) {\n//         value1.someMyType1Method();\n//       } KJ_IF_SOME(value2, wrapper.tryUnwrap(handle)) {\n//         value2.someMyType2Method();\n//       }\n//     }\n//\n// To use a JSG_RESOURCE_TYPE in the TypeHandler, it must be listed in your isolate type's\n// JSG_DECLARE_ISOLATE_TYPE declaration. See JSG_DECLARE_ISOLATE_TYPE in setup.h for info.\n// For resource types, also need to wrap in Ref, i.e. `TypeHandler<jsg::Ref<T>>`.\ntemplate <typename T>\nclass TypeHandler {\n public:\n  // ---------------------------------------------------------------------------\n  // Interface for value types (i.e. types not declared using JSG_RESOURCE_TYPE).\n  //\n  // This includes builtin types, e.g. `double` or `kj::String`.\n  //\n  // These methods will fail for resource types.\n\n  // Wrap by value.\n  virtual v8::Local<v8::Value> wrap(Lock& js, T value) const = 0;\n\n  // Unwrap by value. Returns null if not the right type.\n  virtual kj::Maybe<T> tryUnwrap(Lock& js, v8::Local<v8::Value> handle) const = 0;\n};\n\n// Utility that allows C++ code in a resource type to examine properties that have been added to\n// its JavaScript wrapper.\n//\n// To use this, add a member of type `PropertyReflection<T>` to your resource type, then after\n// your JSG_RESOURCE_TYPE block (NOT inside it; at the class scope), write\n// `JSG_REFLECTION(name)`. You will then be able to use the reflection to read properties\n// set on the JavaScript side, interpreting them as the type `T`.\n//\n//     class Foo: public jsg::Object {\n//     public:\n//       ...\n//       JSG_RESOURCE_TYPE(EventTarget) {\n//         ...\n//       }\n//       JSG_REFLECTION(intReader, stringReader);\n//     private:\n//       PropertyReflection<int> intReader;\n//       PropertyReflection<kj::String> stringReader;\n//     }\n//\n// PropertyReflection's trick is that it isn't initialized until the JavaScript wrapper is\n// created. Until that point, get() just always returns nullptr.\n//\n// PropertyReflection's main use case is reading event handler `onfoo` properties. That is,\n// traditionally, instead of using `obj.addEventListener(\"foo\", func)` to register an event\n// handler, you can also do `obj.onfoo = func`.\ntemplate <typename T>\nclass PropertyReflection {\n public:\n  // Read the property of this object called `name`, unwrapping it as type `T`.\n  kj::Maybe<T> get(Lock& js, kj::StringPtr name);\n\n  // Read the property of this object called `name`, unwrapping it as type `T`.\n  kj::Maybe<T> get(v8::Isolate* isolate, kj::StringPtr name) {\n    v8::HandleScope scope(isolate);\n    KJ_IF_SOME(s, self) {\n      KJ_IF_SOME(h, s.tryGetHandle(isolate)) {\n        return unwrapper(isolate, h, name);\n      }\n    }\n    return kj::none;\n  }\n\n  // TODO(someday): Support for reading Symbols and Privates?\n\n private:\n  kj::Maybe<Wrappable&> self;\n\n  using Unwrapper = kj::Maybe<T>(v8::Isolate*, v8::Local<v8::Object> object, kj::StringPtr name);\n  Unwrapper* unwrapper = nullptr;\n\n  template <typename, typename...>\n  friend class TypeWrapper;\n};\n\ntemplate <typename T>\nconcept CoercibleType = kj::isSameType<kj::String, T>() || kj::isSameType<USVString, T>() ||\n    kj::isSameType<DOMString, T>() || kj::isSameType<bool, T>() || kj::isSameType<double, T>();\n// When updating this list, be sure to keep the corresponding checks in the NonCoercibleWrapper\n// class in value.h updated as well.\n\n// By default types in JavaScript can be implicitly converted to other types as needed. This\n// can lead to surprising results. For instance, passing null into an API method that accepts\n// string will have the null coerced into the string value \"null\". The NonCoercible type can\n// be used to disable automatic type coercion in APIs. For instance, NonCoercible<kj::String>\n// will ensure that any value other than a string will be rejected with a TypeError.\n//\n// Here, T can be only one of several types that support coercion:\n//\n// * kj::String, jsg::USVString, jsg::DOMString (value must be a string)\n// * bool (value must be a boolean)\n// * double (value must be a number)\n//\n// It should be pointed out that using NonCoercible<T> runs counter to Web IDL and general\n// Web Platform API best practices, which use type coercion fairly often. However, in certain\n// Cloudflare-specific APIs, automatic coercion can cause surprising developer experience\n// issues. Only use NonCoercible if you have a good reason to disable coercion. When in\n// doubt, don't use it.\ntemplate <CoercibleType T>\nstruct NonCoercible {\n  T value;\n};\n\n// -----------------------------------------------------------------------------\n\n// A Sequence<T> in C++ corresponds to a Sequence IDL type. A sequence is a list of values\n// that may or may not be an array. The key difference between the kj::Array mapping in\n// JSG and a jsg::Sequence, is that the jsg::Sequence can be initialized from any object\n// that exposes an @@iterable symbol. However, when a Sequence is surfaced back up to\n// JavaScript, it will always be an array.\n//\n// At the C++ level, the Sequence itself is just a kj::Array<Value>.\n//\n// Both jsg::Sequence and jsg::Generator provide the ability to work with synchronous\n// iterable/generator objects. The key difference is that jsg::Sequence will always\n// produce a kj::Array of the elements, does not allow for early termination of the\n// iteration, and does not provide access to the return value. jsg::Generator, on the\n// other hand, allows performing an action on each individual item, terminating the\n// iteration early, and retrieving the generators final return value, if any.\ntemplate <typename T>\nstruct Sequence;\n\n// jsg::Generator wraps a JavaScript synchronous generator.\n//\n// jsg::Generator offers a `.forEach()` method that will invoke a callback function for\n// each individual item produced by the generator:\n//\n//   Generator<int> generator = ...;\n//   generator.forEach(js, [](Lock& js, int val, GeneratorContext<T> context) {\n//     // Do something with val.\n//     // To exit early from the iteration, either call `context.return_()`,\n//     // which will call the `.return()` method on the underlying generator,\n//     // or throw a JavaScript exception, which will call the `.throw()`\n//     // method on the underlying generator.\n//   });\n//\n// The Generator<T> is intended only to be used when receiving a Generator object as\n// a parameter. Instances of Generator<T> cannot be passed back out to JavaScript. Refer\n// to the documentation for JSG_ITERATOR to see how to create and pass Generator/Iterable\n// objects back out to JavaScript.\n//\n// The `.forEach()` method is fully synchronous and will fully consume the generator\n// before it returns. Calling `.forEach()` a second time on the generator will return\n// immediately as a non-op.\ntemplate <typename T>\nclass Generator;\n\n// The jsg::AsyncGenerator wraps a JavaScript asynchronous generator.\n//\n// The jsg::AsyncGenerator is similar to jsg::Generator except that it supports\n// async iteration over the individual elements produced by the generator. The\n// `.forEach()` method returns a `Promise<kj::Maybe<T>>>` that is resolved once the\n// generator as been fully consumed. The callback passed in to `.forEach()` must\n// also return a `Promise<void>` that is resolved whenever the item has been consumed\n// and the iterator should advance to the next item.\n//\n//   AsyncGenerator<int> generator = ...;\n//   generator.forEach(js, [](Lock& js, int val, GeneratorContext<T> context) {\n//     // Do something with val.\n//     // To exit early from the iteration, either call `context.return_()`,\n//     // which will call the `.return()` method on the underlying generator,\n//     // or throw a JavaScript exception, which will call the `.throw()`\n//     // method on the underlying generator.\n//     return js.resolvedPromise();\n//   }).then(js, [](Lock&, kj::Maybe<T>) { KJ_DBG(\"DONE!\"); });\n//\n// The `.forEach()` method will fully consume the generator, returning a Promise\n// that is resolved once the generator completes. Calling `.forEach()` a second\n// time on the generator will return an immediately resolved promise.\ntemplate <typename T>\nclass AsyncGenerator;\n\n// The jsg::GeneratorContext is used with both jsg::Generator and jsg::AsyncGenerator\n// to allow for early termination of the generator iteration.\ntemplate <typename T>\nclass GeneratorContext;\n\n// -----------------------------------------------------------------------------\n\nstruct JsgConfig {\n  bool noSubstituteNull = false;\n  bool unwrapCustomThenables = false;\n  bool fetchIterableTypeSupport = false;\n  bool fetchIterableTypeSupportOverrideAdjustment = false;\n  bool fastApiEnabled = false;\n};\n\nstatic constexpr JsgConfig DEFAULT_JSG_CONFIG = {};\n\ntemplate <typename Config>\nstatic const JsgConfig& getConfig(const Config& config) {\n  if constexpr (kj::isSameType<Config, JsgConfig>() || kj::canConvert<Config, JsgConfig>()) {\n    // Returning a reference to a parameter is harmless here since call sites pass in a reference to\n    // config, which they can continue to use if returned here.\n    // NOLINTNEXTLINE(bugprone-return-const-ref-from-parameter)\n    return config;\n  } else {\n    return DEFAULT_JSG_CONFIG;\n  }\n}\n\n// -----------------------------------------------------------------------------\n\nclass IsolateBase;\ntemplate <typename TypeWrapper>\nclass Isolate;\n// Defined in setup.h -- most code doesn't need to use these directly.\n\ntemplate <typename T>\nconstexpr bool isV8Ref(T*) {\n  return false;\n}\ntemplate <typename T>\nconstexpr bool isV8Ref(V8Ref<T>*) {\n  return true;\n}\n\ntemplate <typename T>\nconstexpr bool isV8Ref() {\n  return isV8Ref(static_cast<T*>(nullptr));\n}\n\ntemplate <typename T>\nconstexpr bool isV8Local(T*) {\n  return false;\n}\ntemplate <typename T>\nconstexpr bool isV8Local(v8::Local<T>*) {\n  return true;\n}\n\ntemplate <typename T>\nconstexpr bool isV8Local() {\n  return isV8Local(static_cast<T*>(nullptr));\n}\n\ntemplate <typename T>\nconstexpr bool isV8MaybeLocal(T*) {\n  return false;\n}\ntemplate <typename T>\nconstexpr bool isV8MaybeLocal(v8::MaybeLocal<T>*) {\n  return true;\n}\n\ntemplate <typename T>\nconstexpr bool isV8MaybeLocal() {\n  return isV8MaybeLocal(static_cast<T*>(nullptr));\n}\n\nclass AsyncContextFrame;\ntemplate <typename T>\nclass JsRef;\n\n#define JS_V8_SYMBOLS(V)                                                                           \\\n  V(AsyncIterator)                                                                                 \\\n  V(HasInstance)                                                                                   \\\n  V(IsConcatSpreadable)                                                                            \\\n  V(Iterator)                                                                                      \\\n  V(Match)                                                                                         \\\n  V(Replace)                                                                                       \\\n  V(Search)                                                                                        \\\n  V(Split)                                                                                         \\\n  V(ToPrimitive)                                                                                   \\\n  V(ToStringTag)                                                                                   \\\n  V(Unscopables)                                                                                   \\\n  V(Dispose)                                                                                       \\\n  V(AsyncDispose)\n\nclass JsValue;\nclass JsMessage;\n#define JS_TYPE_CLASSES(V)                                                                         \\\n  V(Object)                                                                                        \\\n  V(Boolean)                                                                                       \\\n  V(Array)                                                                                         \\\n  V(String)                                                                                        \\\n  V(Symbol)                                                                                        \\\n  V(BigInt)                                                                                        \\\n  V(Number)                                                                                        \\\n  V(Int32)                                                                                         \\\n  V(Uint32)                                                                                        \\\n  V(Date)                                                                                          \\\n  V(RegExp)                                                                                        \\\n  V(Map)                                                                                           \\\n  V(Set)                                                                                           \\\n  V(Promise)                                                                                       \\\n  V(Proxy)                                                                                         \\\n  V(Function)                                                                                      \\\n  V(Uint8Array)                                                                                    \\\n  V(ArrayBuffer)                                                                                   \\\n  V(ArrayBufferView)\n\n#define V(Name) class Js##Name;\nJS_TYPE_CLASSES(V)\n#undef V\n\n// JsBufferSource is not in JS_TYPE_CLASSES because there is no v8::BufferSource\n// type (and hence no v8::Value::IsBufferSource() check). It is instead handled\n// with special-case logic in JsValue::tryCast and JsValueWrapper.\nclass JsBufferSource;\n\n#define V(Name) || kj::isSameType<T, Js##Name>()\ntemplate <typename T>\nconcept IsJsValue = kj::isSameType<T, JsValue>() ||\n    kj::isSameType<T, JsMessage>() JS_TYPE_CLASSES(V) || kj::isSameType<T, JsBufferSource>();\n#undef V\n\nclass DOMException;\nclass ExternalMemoryAdjustment;\n\n// Used to save a reference to an isolate that is responsible for external memory usage.\n// getAdjustment() can be invoked at any time to create a new RAII adjustment object\n// pointing to this isolate.\n//\n// Each isolate has a singleton `ExternalMemoryTarget`, which all `ExternalMemoryAdjustment`s\n// point to. The only purpose of this object is to hold a weak reference back to the isolate; the\n// reference is nulled out when the isolate is destroyed.\nclass ExternalMemoryTarget: public kj::AtomicRefcounted {\n public:\n  ExternalMemoryTarget(v8::Isolate* isolate): isolate(isolate) {}\n\n  ExternalMemoryAdjustment getAdjustment(size_t amount) const;\n\n  // Apply any deferred external memory updates. Must be called with isolate locked.\n  void applyDeferredMemoryUpdate() const;\n\n  // Disconnects the ExternalMemoryTarget from the isolate (called just before destroying the\n  // isolate).\n  void detach() const;\n\n  // These two methods are for tests only.\n  bool isIsolateAliveForTest() const;\n  int64_t getPendingMemoryUpdateForTest() const;\n\n private:\n  void maybeDeferAdjustment(ssize_t amount) const;\n  void adjustNow(Lock& js, ssize_t amount) const;\n\n  // Mutable so that it can be set null when the isolate is destroyed.\n  mutable std::atomic<v8::Isolate*> isolate;\n  static_assert(std::atomic<v8::Isolate*>::is_always_lock_free);\n\n  // Tracks changes to external memory that were applied from a thread that did not hold the\n  // isolate lock. These will be applied the next time the lock is taken.\n  mutable std::atomic<int64_t> pendingExternalMemoryUpdate = {0};\n  static_assert(std::atomic<int64_t>::is_always_lock_free);\n\n  friend class ExternalMemoryAdjustment;\n};\n\n// RAII class to adjust the amount of external memory attributed to an isolate.\n// The adjustment will be automatically decremented when the object is destroyed.\n// The allocation amount can be adjusted up or down during the lifetime of an object.\nclass ExternalMemoryAdjustment final {\n public:\n  ExternalMemoryAdjustment(kj::Arc<const ExternalMemoryTarget> externalMemory, size_t amount);\n  ExternalMemoryAdjustment(ExternalMemoryAdjustment&& other);\n  ExternalMemoryAdjustment& operator=(ExternalMemoryAdjustment&& other);\n  KJ_DISALLOW_COPY(ExternalMemoryAdjustment);\n  ~ExternalMemoryAdjustment() noexcept(false);\n\n  // Adjust the amount of external memory report up or down.\n  void adjust(ssize_t amount);\n\n  // Like adjust, except that the adjustment is applied immediately with no deferral.\n  void adjustNow(Lock& js, ssize_t amount);\n\n  // Set a specific amount of external memory to be attributed, overriding\n  // the previous amount.\n  void set(size_t amount);\n\n  // Like set(), except that the adjustment is applied immediately with no deferral.\n  void setNow(Lock& js, size_t amount);\n\n  inline size_t getAmount() const {\n    return amount;\n  }\n\n private:\n  kj::Arc<const ExternalMemoryTarget> externalMemory;\n  size_t amount = 0;\n\n  // If the isolate is locked, adjust the external memory immediately.\n  // Otherwise, if we don't have the isolate locked, defer the adjustment to the next\n  // time that we do.\n  void maybeDeferAdjustment(ssize_t amount);\n};\n\n// If memory protection keys are enabled, provides the ability to run a function\n// within the scope of a particular protection key associated with the isolate lock.\n// This class is designed to be movable.\nclass MemoryProtectionKeyScope final {\n public:\n  KJ_DISALLOW_COPY(MemoryProtectionKeyScope);\n  MemoryProtectionKeyScope(MemoryProtectionKeyScope&&) = default;\n  MemoryProtectionKeyScope& operator=(MemoryProtectionKeyScope&&) = default;\n\n  auto runWithKey(auto func) {\n#ifdef V8_ENABLE_SANDBOX\n    PkeyScope scope(pkey);\n#endif\n    return func();\n  }\n\n private:\n#ifdef V8_ENABLE_SANDBOX\n  int pkey;\n  MemoryProtectionKeyScope(Lock&);\n\n  struct PkeyScope {\n    int key;\n    int saved;\n    PkeyScope(int pkey);\n    ~PkeyScope();\n  };\n#else\n  MemoryProtectionKeyScope(Lock&) {\n    // No-op if sandboxing is not enabled.\n  }\n#endif\n\n  friend class Lock;\n};\n\n// Represents an isolate lock, which allows the current thread to execute JavaScript code within\n// an isolate. A thread must lock an isolate -- obtaining an instance of `Lock` -- before it can\n// manipulate JavaScript objects or execute JavaScript code inside the isolate.\n//\n// The `Lock` interface also provides access to basic JavaScript functionality, such as the\n// ability to construct basic JS values, throw and catch errors, etc.\n//\n// By convention, all functions which manipulate JavaScript take `Lock& js` as their first\n// parameter. A `Lock&` reference must never be stored as an object member nor captured in a\n// lambda, as `Lock`s are always constructed on the stack and so their lifetime is never\n// guaranteed beyond the end of the function call.\n//\n// Methods declared with JSG_METHOD and similar macros may optionally take a `Lock&` as the\n// first parameter. Template magic will automatically discover if the parameter is present and\n// will populate it. Such methods are always invoked under lock whether or not they have a\n// `Lock&` parameter, but it is recommended that you declare the parameter if the function\n// touches the JS heap in any way. This way, if someone wants to call the method directly from\n// C++, they know whether a lock is required.\n//\n// To create a lock in the first place, you have to create a specific instance of\n// Isolate<TypeWrapper>::Lock. Usually this is only done in top-level code, and the Lock is\n// passed down to everyone else from there. See setup.h for details.\n\nclass Lock {\n public:\n  // The underlying V8 isolate, useful for directly calling V8 APIs. Hopefully, this is rarely\n  // needed outside JSG itself.\n  v8::Isolate* const v8Isolate;\n\n  template <typename T, typename... Params>\n  Ref<T> alloc(Params&&... params) {\n    // TODO(soon): While it is possible to create jsg::Object instances outside of the\n    // isolate lock, we intend to change that in order to improve memory accounting and\n    // tracking of objects created while under lock. As such, all instances of jsg::alloc<T>(...)\n    // are to be replaced by js.alloc<T>(...). For now, these are functionally equivalent.\n    return Ref<T>(kj::refcounted<T>(kj::fwd<Params>(params)...));\n  }\n\n  // Like alloc() but attaches an external memory adjustment of size indicated by `accountedSize`.\n  template <typename T, typename... Params>\n  Ref<T> allocAccounted(size_t accountedSize, Params&&... params) {\n    return Ref<T>(kj::refcounted<T>(kj::fwd<Params>(params)...)\n                      .attach(getExternalMemoryAdjustment(accountedSize)));\n  }\n\n  // When you want to temporarily use a memory allocation that is protected\n  // by the isolate's memory protection key, use this to get a utility that\n  // will capture the key and allow you to run a function with the key enabled.\n  // The key use case is to allow tempporary access outside of the isolate lock\n  // for things like ArrayBuffer backing stores.\n  MemoryProtectionKeyScope getMemoryProtectionKeyScope() {\n    return MemoryProtectionKeyScope(*this);\n  }\n\n  v8::Local<v8::Context> v8Context() {\n    auto context = v8Isolate->GetCurrentContext();\n    KJ_ASSERT(!context.IsEmpty(), \"Isolate has no currently active v8::Context::Scope\");\n    return context;\n  }\n\n  // Get the current Lock for the given V8 isolate. Segfaults if the isolate is not locked.\n  //\n  // This method is intended to be used in callbacks from V8 that pass an isolate pointer but\n  // don't provide any further context. Most code should rely on the caller passing in a `Lock&`.\n  static Lock& from(v8::Isolate* v8Isolate) {\n    return *reinterpret_cast<Lock*>(v8Isolate->GetData(SET_DATA_LOCK));\n  }\n\n  // TODO(someday): A clang-tidy rule to enforce use of Lock::current over\n  // v8::Isolate::GetCurrent would be helpful.\n  static Lock& current() {\n    return from(v8::Isolate::GetCurrent());\n  }\n\n  // Signals that execution should be terminated immediately, including during C++ iterator\n  // callbacks where V8's own TerminateExecution() interrupt would not be checked.\n  // Also calls V8's TerminateExecution(). See IsolateBase::requestTermination().\n  void requestTermination();\n  bool isTerminationRequested() const;\n\n  // RAII construct that reports amount of external memory to be manually attributed to\n  // the isolate. When the returned ExtrernalMemoryAdjuster is dropped, the amount will\n  // be subtracted from the isolate's external memory accounting. If the adjuster is\n  // dropped while the isolate lock is not being held, the adjustment will be deferred\n  // until the next time the lock is held. The ExternalMemoryAdjustment itself can be\n  // moved and can be used to increment or decrement the amount of external memory\n  // held.\n  ExternalMemoryAdjustment getExternalMemoryAdjustment(int64_t amount = 0);\n\n  // Used to save a reference to an isolate that is responsible for external memory usage.\n  // getAdjustment() can be invoked at any time to create a new RAII adjustment object\n  // pointing to this isolate\n  kj::Arc<const ExternalMemoryTarget> getExternalMemoryTarget();\n\n  Value parseJson(kj::ArrayPtr<const char> data);\n  Value parseJson(v8::Local<v8::String> text);\n  template <typename T>\n  kj::String serializeJson(V8Ref<T>& value) {\n    return serializeJson(value.getHandle(*this));\n  }\n  template <typename T>\n  kj::String serializeJson(V8Ref<T>&& value) {\n    return serializeJson(value.getHandle(*this));\n  }\n\n  void recursivelyFreeze(Value& value);\n\n  // ---------------------------------------------------------------------------\n  // Exception-related stuff\n\n  // Converts the KJ exception to a JS exception. If the KJ exception is a tunneled JavaScript\n  // error, this reproduces the original error. If it is not a tunneled error, then it is treated\n  // as an internal error: the KJ exception message is logged to stderr, and a JavaScript error\n  // is returned with a generic description.\n  Value exceptionToJs(kj::Exception&& exception, ExceptionToJsOptions options = {});\n\n  JsRef<JsValue> exceptionToJsValue(kj::Exception&& exception, ExceptionToJsOptions options = {});\n\n  // Encodes the given JavaScript exception into a KJ exception, formatting the description in\n  // such a way that hopefully exceptionToJs() can reproduce something equivalent to the original\n  // JavaScript error.\n  kj::Exception exceptionToKj(const JsValue& exception);\n\n  // Encodes the given JavaScript exception into a KJ exception, formatting the description in\n  // such a way that hopefully exceptionToJs() can reproduce something equivalent to the original\n  // JavaScript error.\n  kj::Exception exceptionToKj(Value&& exception);\n\n  // Throws a JavaScript exception. The exception is scheduled on the isolate, and then an\n  // instance of `JsExceptionThrown` is thrown in C++. All places where JavaScript calls into C++\n  // via JSG understand how to handle this and propagate the exception back to JavaScript.\n  [[noreturn]] void throwException(Value&& exception);\n\n  [[noreturn]] void throwException(kj::Exception&& exception, ExceptionToJsOptions options = {}) {\n    throwException(exceptionToJs(kj::mv(exception), options));\n  }\n\n  [[noreturn]] void throwException(const JsValue& exception);\n\n  // Invokes `func()` synchronously, catching exceptions. In the event of an exception,\n  // `errorHandler()` will be called, passing the exception as type `jsg::Value`.\n  //\n  // KJ exceptions are also caught and will be converted to JS exceptions using exceptionToJs().\n  //\n  // Some kinds of exceptions explicitly will not be caught:\n  // - Exceptions where JavaScript execution cannot continue, such as the \"uncatchable exception\"\n  //   produced by IsolateBase::TerminateExecution().\n  // - C++ exceptions other than `kj::Exception`, e.g. `std::bad_alloc`. These exceptions are\n  //   assumed to be serious enough that they cannot be caught as if they were JavaScript errors,\n  //   and instead unwind must continue until C++ catches them.\n  //\n  // func() and errorHandler() must return the same type; the value they return will be returned\n  // from `tryCatch()` itself.\n  template <typename Func, typename ErrorHandler>\n  auto tryCatch(Func&& func,\n      ErrorHandler&& errorHandler,\n      // If an exception occurs, convert KJ exceptions to JS exceptions\n      // using these options.\n      ExceptionToJsOptions options = {}) -> decltype(func()) {\n    Value error = nullptr;\n\n    {\n      v8::TryCatch tryCatch(v8Isolate);\n      try {\n        return func();\n      } catch (JsExceptionThrown&) {\n        // If tryCatch.HasCaught() is false, it typically means that JsExceptionThrown\n        // was thrown without an exception actually being scheduled on the isolate.\n        // This may happen in particular when the JsExceptionThrown was the result of\n        // TerminateExecution() but V8 has since cleared the terminate flag because all\n        // JavaScript call frames have been unwound. Hence, we want to treat this the\n        // same as if `CanContinue()` returned false.\n        // TODO(cleanup): Do more investigation, maybe explicitly check for the termination\n        // flag or arrange to maintain our own separate termination flag to avoid confusion.\n        if (!tryCatch.CanContinue() || !tryCatch.HasCaught() || tryCatch.Exception().IsEmpty()) {\n          tryCatch.ReThrow();\n          throw;\n        }\n\n        error = Value(v8Isolate, tryCatch.Exception());\n      } catch (kj::Exception& e) {\n        error = exceptionToJs(kj::mv(e), options);\n      }\n    }\n\n    // We have to make sure the `v8::TryCatch` is off the stack before invoking `errorHandler`,\n    // otherwise the same `TryCatch` will catch any exceptions the error handler throws, ugh.\n    return errorHandler(kj::mv(error));\n  }\n\n  // Like tryCatch() but returns a Promise<T> that resolves to the result of func() or\n  // rejects with the result of errorHandler() if an exception is thrown.\n  template <typename T, typename Func>\n  Promise<T> tryOrReject(Func&& func) {\n    return tryCatch([&]() -> Promise<T> { return toPromise(func()); },\n        [&](Value&& error) -> Promise<T> { return rejectedPromise<T>(kj::mv(error)); });\n  }\n\n  // ---------------------------------------------------------------------------\n  // Promise-related stuff\n\n  // Get a pair of a Promise<T> and a Promise<T>::Resolver that resolves the promise. You should\n  // call this like:\n  //\n  //     auto [promise, resolver] = js.newPromiseAndResolver();\n  template <typename T>\n  PromiseResolverPair<T> newPromiseAndResolver();\n\n  // Construct an immediately-resolved promise resolving to the given value.\n  template <typename T>\n  Promise<T> resolvedPromise(T&& value);\n\n  // Construct an immediately-resolved promise resolving to the given value.\n  Promise<void> resolvedPromise();\n\n  // Construct an immediately-rejected promise throwing the given exception.\n  template <typename T>\n  Promise<T> rejectedPromise(v8::Local<v8::Value> exception);\n\n  // Construct an immediately-rejected promise throwing the given exception.\n  template <typename T>\n  Promise<T> rejectedPromise(jsg::Value exception);\n\n  // Construct an immediately-rejected promise throwing the given exception.\n  template <typename T>\n  Promise<T> rejectedPromise(kj::Exception&& exception, ExceptionToJsOptions options = {});\n\n  // Like above, but return a pure-JS promise, not a typed Promise.\n  JsPromise rejectedJsPromise(jsg::JsValue exception);\n  JsPromise rejectedJsPromise(kj::Exception&& exception, ExceptionToJsOptions options = {});\n\n  // Like `kj::evalNow()`, but returns a jsg::Promise for the result. Synchronous exceptions are\n  // caught and returned as a rejected promise.\n  //\n  // If an exception is caught as a result of TerminateExecution() being called, it is rethrown\n  // to the caller, not encapsulated in a promise.\n  //\n  // Note `func` is NOT expected to take `Lock&` as a parameter, as normally func should be a lambda\n  // that captures `[&]`, so will capture the caller's lock reference. Capturing the lock here is\n  // allowed since `func` is invoked synchronously.\n  template <class Func>\n  PromiseForResult<Func, void, false> evalNow(Func&& func);\n\n  // ---------------------------------------------------------------------------\n  // Name/Symbol stuff\n\n  // Creates a Name encapsulating a new unique v8::Symbol.\n  Name newSymbol(kj::StringPtr symbol);\n\n  // Creates a Name encapsulating a name from the global symbol registry.\n  // Equivalent to Symbol.for(symbol) in JavaScript.\n  Name newSharedSymbol(kj::StringPtr symbol);\n\n  // Similar to newSharedSymbol except that it uses a separate isolate registry\n  // that is not accessible by JavaScript.\n  Name newApiSymbol(kj::StringPtr symbol);\n\n  // ---------------------------------------------------------------------------\n  // Logging stuff\n\n  inline bool areWarningsLogged() const {\n    return warningsLogged;\n  }\n\n  // Emits the warning only if there is anywhere for the log to go (for instance,\n  // if debug logging is enabled or the inspector is being used).\n  void logWarning(kj::StringPtr message);\n\n  // TODO(later): Add the other log variants from IoContext? eg. logWarningOnce,\n  // logErrorOnce, logUncaughtException, etc.\n\n  // ---------------------------------------------------------------------------\n  // v8 Local handle related stuff\n  // TODO(cleanup): Direct use of v8::Local handles is discouraged and is something we are trying\n  // to move away from. However, there are still plenty of cases where we need to do so. The\n  // methods here help avoid directly using v8::Isolate and serve as an interim until we can\n  // eliminate direct use as much as possible.\n  // Convenience methods to unwrap various types of V8 values. All of these could be done manually\n  // via the V8 API, but these methods are much easier.\n\n  v8::Local<v8::Value> v8Undefined();\n  v8::Local<v8::Value> v8Null();\n\n  v8::Local<v8::Value> v8Error(kj::StringPtr message);\n  v8::Local<v8::Value> v8TypeError(kj::StringPtr message);\n\n  void v8Set(v8::Local<v8::Object> obj, V8Ref<v8::String>& name, Value& value);\n  void v8Set(v8::Local<v8::Object> obj, kj::StringPtr name, v8::Local<v8::Value> value);\n  void v8Set(v8::Local<v8::Object> obj, kj::StringPtr name, Value& value);\n  v8::Local<v8::Value> v8Get(v8::Local<v8::Object> obj, kj::StringPtr name);\n  v8::Local<v8::Value> v8Get(v8::Local<v8::Array> obj, uint idx);\n  bool v8Has(v8::Local<v8::Object> obj, kj::StringPtr name);\n  bool v8HasOwn(v8::Local<v8::Object> obj, kj::StringPtr name);\n\n  template <typename T>\n  V8Ref<T> v8Ref(v8::Local<T> local);\n  Data v8Data(v8::Local<v8::Data> data);\n\n  kj::String serializeJson(v8::Local<v8::Value> value);\n\n  v8::Local<v8::String> wrapString(kj::StringPtr text);\n  virtual v8::Local<v8::ArrayBuffer> wrapBytes(kj::Array<byte> data) = 0;\n  virtual v8::Local<v8::Function> wrapSimpleFunction(v8::Local<v8::Context> context,\n      jsg::Function<void(const v8::FunctionCallbackInfo<v8::Value>& info)> simpleFunction) = 0;\n\n  // A variation on wrapSimpleFunction that allows for a return value. While the wrapSimpleFunction\n  // implementation passes the FunctionCallbackInfo into the called function, any call to\n  // GetReturnValue().Set(...) to specify a return value will be ignored by the FunctorCallback\n  // wrapper. The wrapReturningFunction variation forces the wrapper to use the version that\n  // pays attention to the return value.\n  virtual v8::Local<v8::Function> wrapReturningFunction(v8::Local<v8::Context> context,\n      jsg::Function<v8::Local<v8::Value>(const v8::FunctionCallbackInfo<v8::Value>& info)>\n          returningFunction) = 0;\n  virtual v8::Local<v8::Function> wrapPromiseReturningFunction(v8::Local<v8::Context> context,\n      jsg::Function<jsg::Promise<jsg::Value>(const v8::FunctionCallbackInfo<v8::Value>& info)>\n          returningFunction) = 0;\n  // TODO(later): See if we can easily combine wrapSimpleFunction and wrapReturningFunction\n  // into one.\n\n  virtual v8::Local<v8::Promise> wrapSimplePromise(Promise<Value> promise) = 0;\n\n  bool toBool(v8::Local<v8::Value> value);\n  virtual kj::String toString(v8::Local<v8::Value> value) = 0;\n  virtual jsg::Dict<v8::Local<v8::Value>> toDict(v8::Local<v8::Value> value) = 0;\n  virtual jsg::Dict<JsValue> toDict(const jsg::JsValue& value) = 0;\n  virtual Promise<Value> toPromise(v8::Local<v8::Value> promise) = 0;\n\n  // ---------------------------------------------------------------------------\n  // Setup stuff\n\n  // Use to enable/disable dynamic code evaluation (via eval(), new Function(), or WebAssembly).\n  void setAllowEval(bool allow);\n\n  void setCaptureThrowsAsRejections(bool capture);\n  void setUsingEnhancedErrorSerialization();\n  void setUsingFastJsgStruct();\n  bool isUsingFastJsgStruct() const;\n  bool isUsingEnhancedErrorSerialization() const;\n\n  void setNodeJsCompatEnabled();\n  void setNodeJsProcessV2Enabled();\n  void setRequireReturnsDefaultExportEnabled();\n  void setThrowOnUnrecognizedImportAssertion();\n  bool getThrowOnUnrecognizedImportAssertion() const;\n  void setToStringTag();\n  void setImmutablePrototype();\n  void setSpecCompliantPropertyAttributes();\n  void disableTopLevelAwait();\n\n  using Logger = void(Lock&, kj::StringPtr);\n  void setLoggerCallback(kj::Function<Logger>&& logger);\n\n  using ErrorReporter = void(Lock&, kj::String, const JsValue&, const JsMessage&);\n  void setErrorReporterCallback(kj::Function<ErrorReporter>&& errorReporter);\n\n  // ---------------------------------------------------------------------------\n  // Misc. Stuff\n\n  // Sends an immediate request for full GC, this function is to ONLY be used in testing, otherwise\n  // it will throw. If a need for a minor GC is needed look at the call in jsg.c++ and the\n  // implementation in setup.c++. Use responsibly.\n  void requestGcForTesting() const;\n\n  // Runs the given function synchronously with a v8::HandleScope on the stack.\n  // If the fn returns a v8::Local<T> or v8::MaybeLocal<T> type, then\n  // v8::EscapableHandleScope is used ensuring that the v8::Local<T> return\n  // value is properly handled.\n  auto withinHandleScope(auto&& fn) {\n    using Ret = decltype(fn());\n    if constexpr (IsJsValue<Ret>) {\n      v8::EscapableHandleScope scope(v8Isolate);\n      v8::Local<v8::Value> value = fn();\n      return Ret(scope.Escape(value));\n    } else if constexpr (isV8Local<Ret>()) {\n      v8::EscapableHandleScope scope(v8Isolate);\n      return scope.Escape(fn());\n    } else if constexpr (isV8MaybeLocal<Ret>()) {\n      v8::EscapableHandleScope scope(v8Isolate);\n      return scope.EscapeMaybe(fn());\n    } else {\n      v8::HandleScope scope(v8Isolate);\n      return fn();\n    }\n  }\n\n  virtual Ref<DOMException> domException(\n      kj::String name, kj::String message, kj::Maybe<kj::String> stackValue = kj::none) = 0;\n\n  // Get the prototype object for the given C++ type (which must be a JSG_RESOURCE_TYPE).\n  //\n  // WARNING: A malicious script can tamper with this by overwriting the `prototype` property\n  // of the class object.\n  template <typename T>\n  JsObject getPrototypeFor();\n\n  // ====================================================================================\n  JsObject global() KJ_WARN_UNUSED_RESULT;\n  JsValue undefined() KJ_WARN_UNUSED_RESULT;\n  JsValue null() KJ_WARN_UNUSED_RESULT;\n  JsBoolean boolean(bool val) KJ_WARN_UNUSED_RESULT;\n  JsNumber num(double) KJ_WARN_UNUSED_RESULT;\n  JsNumber num(float) KJ_WARN_UNUSED_RESULT;\n  JsInt32 num(int8_t) KJ_WARN_UNUSED_RESULT;\n  JsInt32 num(int16_t) KJ_WARN_UNUSED_RESULT;\n  JsInt32 num(int32_t) KJ_WARN_UNUSED_RESULT;\n  JsUint32 num(uint8_t) KJ_WARN_UNUSED_RESULT;\n  JsUint32 num(uint16_t) KJ_WARN_UNUSED_RESULT;\n  JsUint32 num(uint32_t) KJ_WARN_UNUSED_RESULT;\n  JsBigInt bigInt(int64_t) KJ_WARN_UNUSED_RESULT;\n  JsBigInt bigInt(uint64_t) KJ_WARN_UNUSED_RESULT;\n  JsString str() KJ_WARN_UNUSED_RESULT;\n  JsString str(kj::ArrayPtr<const char16_t>) KJ_WARN_UNUSED_RESULT;\n  JsString str(kj::ArrayPtr<const uint16_t>) KJ_WARN_UNUSED_RESULT;\n  JsString str(kj::ArrayPtr<const char>) KJ_WARN_UNUSED_RESULT;\n  JsString str(kj::ArrayPtr<const kj::byte>) KJ_WARN_UNUSED_RESULT;\n  JsString strIntern(kj::StringPtr) KJ_WARN_UNUSED_RESULT;\n  JsString strExtern(kj::ArrayPtr<const char>) KJ_WARN_UNUSED_RESULT;\n  JsString strExtern(kj::ArrayPtr<const uint16_t>) KJ_WARN_UNUSED_RESULT;\n  JsSymbol symbol(kj::StringPtr) KJ_WARN_UNUSED_RESULT;\n  JsSymbol symbolShared(kj::StringPtr) KJ_WARN_UNUSED_RESULT;\n  JsSymbol symbolInternal(kj::StringPtr) KJ_WARN_UNUSED_RESULT;\n  JsObject obj() KJ_WARN_UNUSED_RESULT;\n  JsObject obj(kj::ArrayPtr<const kj::StringPtr> keys,\n      kj::ArrayPtr<jsg::JsValue> values) KJ_WARN_UNUSED_RESULT;\n  JsObject objNoProto() KJ_WARN_UNUSED_RESULT;\n  JsObject objNoProto(\n      kj::ArrayPtr<kj::StringPtr> keys, kj::ArrayPtr<jsg::JsValue> values) KJ_WARN_UNUSED_RESULT;\n  JsMap map() KJ_WARN_UNUSED_RESULT;\n  JsValue external(void*) KJ_WARN_UNUSED_RESULT;\n  JsValue error(kj::StringPtr message) KJ_WARN_UNUSED_RESULT;\n  JsValue typeError(kj::StringPtr message) KJ_WARN_UNUSED_RESULT;\n  JsValue rangeError(kj::StringPtr message) KJ_WARN_UNUSED_RESULT;\n  JsDate date(double timestamp) KJ_WARN_UNUSED_RESULT;\n  JsDate date(kj::Date date) KJ_WARN_UNUSED_RESULT;\n  JsDate date(kj::StringPtr date) KJ_WARN_UNUSED_RESULT;\n\n  // Returns a JsObject that is backed internally by a v8::External object that\n  // takes ownership over the inner.\n  template <typename T>\n  JsObject opaque(T&& inner) KJ_WARN_UNUSED_RESULT;\n\n  // Returns a jsg::BufferSource whose underlying JavaScript handle is a Uint8Array.\n  BufferSource bytes(kj::Array<kj::byte> data) KJ_WARN_UNUSED_RESULT;\n\n  // Returns a jsg::BufferSource whose underlying JavaScript handle is an ArrayBuffer\n  // as opposed to the default Uint8Array.  May copy and move the bytes if they are\n  // not in the right sandbox.\n  BufferSource arrayBuffer(kj::Array<kj::byte> data) KJ_WARN_UNUSED_RESULT;\n\n  enum class AllocOption { ZERO_INITIALIZED, UNINITIALIZED };\n\n  // Utility method to safely allocate a v8::BackingStore with allocation failure handling.\n  // Throws a javascript error if allocation fails.\n  //\n  // IMPORTANT: This method can trigger garbage collection, which may move or invalidate V8\n  // objects. Do NOT call this method while:\n  // - A v8::String::ValueView is alive (it holds internal V8 heap locks)\n  // - You have raw pointers to V8 heap data (e.g., from view.data8(), view.data16())\n  //\n  // Safe pattern: Copy V8 string data to off-heap memory FIRST (e.g., via JsString::writeInto()\n  // into kj::SmallArray), THEN call allocBackingStore(). See TextEncoder::encode() for example.\n  std::unique_ptr<v8::BackingStore> allocBackingStore(\n      size_t size, AllocOption init_mode = AllocOption::ZERO_INITIALIZED) KJ_WARN_UNUSED_RESULT;\n\n  enum RegExpFlags {\n    kNONE = v8::RegExp::Flags::kNone,\n    kGLOBAL = v8::RegExp::Flags::kGlobal,\n    kIGNORE_CASE = v8::RegExp::Flags::kIgnoreCase,\n    kMULTILINE = v8::RegExp::Flags::kMultiline,\n    kSTICKY = v8::RegExp::Flags::kSticky,\n    kUNICODE = v8::RegExp::Flags::kUnicode,\n    kDOTALL = v8::RegExp::Flags::kDotAll,\n    kLINEAR = v8::RegExp::Flags::kLinear,\n    kHAS_INDICES = v8::RegExp::Flags::kHasIndices,\n    kUNICODE_SETS = v8::RegExp::Flags::kUnicodeSets,\n  };\n\n  JsRegExp regexp(kj::StringPtr pattern,\n      RegExpFlags flags = RegExpFlags::kNONE,\n      kj::Maybe<uint32_t> backtrackLimit = kj::none) KJ_WARN_UNUSED_RESULT;\n\n  template <typename... Args>\n    requires(std::assignable_from<JsValue&, Args> && ...)\n  JsArray arr(const Args&... args) KJ_WARN_UNUSED_RESULT;\n\n  JsArray arr(kj::ArrayPtr<JsValue> values) KJ_WARN_UNUSED_RESULT;\n\n  // Create a JavaScript array from the given kj::ArrayPtr, passing each\n  // item through the given transformation function to create the appropriate\n  // JsValue.\n  template <typename T, typename Func>\n  JsArray arr(kj::ArrayPtr<T> values, Func fn) KJ_WARN_UNUSED_RESULT;\n\n  template <typename... Args>\n    requires(std::assignable_from<JsValue&, Args> && ...)\n  JsSet set(const Args&... args) KJ_WARN_UNUSED_RESULT;\n\n#define V(Name) JsSymbol symbol##Name() KJ_WARN_UNUSED_RESULT;\n  JS_V8_SYMBOLS(V)\n#undef V\n\n  void runMicrotasks();\n\n  // Request an extra microtask checkpoint after the current one completes.\n  void requestExtraMicrotaskCheckpoint();\n\n  // Sets the terminate-execution flag on the isolate so that the next time code tries to run, it\n  // will be terminated. (But note that V8 only checks the flag at certain times, so it's possible\n  // some code will actually execute before termination kicks in.)\n  void terminateNextExecution();\n\n  // Terminates exution immediately, forcing V8 to see the flag and react to it before returning.\n  // Always throws JsExceptionThrown.\n  [[noreturn]] void terminateExecutionNow();\n\n  bool pumpMsgLoop();\n\n  // Logs and reports the error to tail workers (if called within an request),\n  // the inspector (if attached), or to KJ_LOG(Info).\n  virtual void reportError(const JsValue& value) = 0;\n\n  // Store the worker environment.\n  virtual void setWorkerEnv(V8Ref<v8::Object> value) = 0;\n\n  // Retrieve the worker environment.\n  virtual kj::Maybe<V8Ref<v8::Object>> getWorkerEnv() = 0;\n\n  // Store the worker exports.\n  virtual void setWorkerExports(V8Ref<v8::Object> value) = 0;\n\n  // Retrieve the worker exports.\n  virtual kj::Maybe<V8Ref<v8::Object>> getWorkerExports() = 0;\n\n  // Resolve an internal module namespace from the given specifier.\n  // This variation can be used only for internal built-ins.\n  kj::Maybe<JsObject> resolveInternalModule(kj::StringPtr specifier);\n\n  // Resolve a user-importable built-in module namespace from the given specifier.\n  // Unlike resolveInternalModule, this only searches user-importable built-ins\n  // (PUBLIC_BUILTIN context), excluding internal-only modules and worker bundle\n  // modules. Use this for user-facing APIs like process.getBuiltinModule() that\n  // must not expose internal modules or return user bundle overrides.\n  // Only valid when the new module registry is in use.\n  kj::Maybe<JsObject> resolvePublicBuiltinModule(kj::StringPtr specifier);\n\n  // Resolve a module namespace from the given specifier.\n  // This variation includes modules from the worker bundle.\n  kj::Maybe<JsObject> resolveModule(\n      kj::StringPtr specifier, RequireEsm requireEsm = RequireEsm::NO);\n\n  // Returns the capnp::SchemaLoader for this isolate/context\n  template <typename T>\n  const capnp::SchemaLoader& getCapnpSchemaLoader() const {\n    return KJ_ASSERT_NONNULL(\n        jsg::getAlignedPointerFromEmbedderData<T>(\n            v8Isolate->GetCurrentContext(), ContextPointerSlot::GLOBAL_WRAPPER))\n        .getSchemaLoader();\n  }\n\n private:\n  // Mark the jsg::Lock as being disallowed from being passed as a parameter into\n  // a kj promise coroutine. Note that this only blocks directly passing the Lock\n  // in. Types that have the Lock included as a member field won't be caught and\n  // should themselves be marked with KJ_DISALLOW_AS_COROUTINE_PARAM. Note also\n  // that this would not stop someone from passing the v8::Isolate reference into\n  // the coroutine and using `Lock::from(...)` to get the Lock. Don't do that.\n  // jsg::Lock should NOT be used within a kj promise coroutine.\n  KJ_DISALLOW_AS_COROUTINE_PARAM;\n  friend class IsolateBase;\n  template <typename TypeWrapper>\n  friend class Isolate;\n\n  Lock(v8::Isolate* v8Isolate);\n  ~Lock() noexcept(false);\n\n  v8::Locker locker;\n  v8::Isolate::Scope isolateScope;\n\n  void* previousData;\n\n  bool warningsLogged;\n\n  friend class JsObject;\n  virtual kj::Maybe<Object&> getInstance(v8::Local<v8::Object> obj, const std::type_info& type) = 0;\n  virtual v8::Local<v8::Object> getPrototypeFor(const std::type_info& type) = 0;\n};\n\n// Ensures that the given fn is run within both a handlescope and the context scope.\n// The lock must be assignable to a jsg::Lock, and the context must be or be assignable\n// to a v8::Local<v8::Context>. The context will be evaluated within the handle scope.\n#define JSG_WITHIN_CONTEXT_SCOPE(lock, context, fn)                                                \\\n  (static_cast<jsg::Lock&>(lock)).withinHandleScope([&]() -> auto {                                \\\n    v8::Local<v8::Context> ctx = context;                                                          \\\n    KJ_ASSERT(!ctx.IsEmpty(), \"unable to enter invalid v8::Context\");                              \\\n    v8::Context::Scope scope(ctx);                                                                 \\\n    return fn(static_cast<jsg::Lock&>(lock));                                                      \\\n  })\n\n// The V8StackScope is used only as a marker to prove that we are running in the V8 stack\n// established by calling runInV8Stack(...)\nclass V8StackScope final {\n public:\n  KJ_DISALLOW_COPY_AND_MOVE(V8StackScope);\n\n private:\n  V8StackScope() = default;\n  KJ_DISALLOW_AS_COROUTINE_PARAM;\n\n  static auto runInV8StackImpl(void* pos, auto callback) __attribute__((noinline)) {\n#if V8_HAS_STACK_START_MARKER\n    // This currently depends on a V8 patch which hasn't been upstreamed. Note that workerd does\n    // not use this patch; it's only used internally. The patch is needed in order to work around\n    // oddities of our internal environment which do not apply to workerd. For workerd, V8's default\n    // behavior is just fine.\n    v8::StackStartMarker marker(pos);\n#endif\n    // We create a V8StackScope only as proof that we are running in the V8 stack.\n    V8StackScope stackScope;\n    return callback(stackScope);\n  }\n\n  friend auto runInV8Stack(auto callback);\n};\n\n// Ensures that a v8::StackStartMarker is allocated on the stack before calling the callback.\n// This must be used, for instance, before taking an isolate lock.\n// The reason why Isolate::Lock doesn't take care of this automatically is because it is often\n// allocated on the heap. The purpose of using runInV8Stack is to capture the start of the stack\n// range that V8 must scan when performing conservative stack-scanning garbage collection.\nauto runInV8Stack(auto callback) {\n  return V8StackScope::runInV8StackImpl(__builtin_frame_address(0), kj::mv(callback));\n};\n\n// Returns true if we are currently executing C++ destructors as a result of garbage collection\n// occurring.\nbool isInGcDestructor();\n\n// =======================================================================================\n// inline implementation details\n\ntemplate <typename T>\ntemplate <typename U>\nV8Ref<U> V8Ref<T>::cast(jsg::Lock& js) {\n  return js.v8Ref(getHandle(js).template As<U>());\n}\n\ntemplate <typename T>\ninline kj::Maybe<T> PropertyReflection<T>::get(Lock& js, kj::StringPtr name) {\n  return get(js.v8Isolate, name);\n}\n\ntemplate <typename T>\ninline V8Ref<T> Lock::v8Ref(v8::Local<T> local) {\n  return V8Ref(v8Isolate, local);\n}\n\ninline Data Lock::v8Data(v8::Local<v8::Data> local) {\n  return Data(v8Isolate, local);\n}\n\ninline v8::Local<v8::Value> Lock::v8Undefined() {\n  return v8::Undefined(v8Isolate);\n}\n\ninline v8::Local<v8::Value> Lock::v8Null() {\n  return v8::Null(v8Isolate);\n}\n\ninline Data Data::addRef(jsg::Lock& js) {\n  return Data(js.v8Isolate, getHandle(js));\n}\n\ntemplate <typename T>\nkj::Maybe<v8::Local<v8::Object>> Ref<T>::tryGetHandle(Lock& js) {\n  return tryGetHandle(js.v8Isolate);\n}\n\ntemplate <typename T>\ninline V8Ref<T> V8Ref<T>::addRef(jsg::Lock& js) {\n  return js.v8Ref(getHandle(js));\n}\n\ntemplate <typename T>\nV8Ref<T> V8Ref<T>::deepClone(jsg::Lock& js) {\n  return js.v8Ref(jsg::deepClone(js.v8Context(), getHandle(js)).template As<T>());\n}\n\ntemplate <typename T>\ninline HashableV8Ref<T> HashableV8Ref<T>::addRef(jsg::Lock& js) {\n  return HashableV8Ref(js.v8Isolate, this->getHandle(js), identityHash);\n}\n\ntemplate <typename T>\ninline v8::Local<T> V8Ref<T>::getHandle(jsg::Lock& js) const {\n  return getHandle(js.v8Isolate);\n}\n\ninline v8::Local<v8::Data> Data::getHandle(jsg::Lock& js) const {\n  return getHandle(js.v8Isolate);\n}\n\ntemplate <typename T>\ninline v8::Local<v8::Context> JsContext<T>::getHandle(Lock& js) const {\n  return handle.Get(js.v8Isolate);\n}\n\ninline Value SelfRef::asValue(Lock& js) const {\n  return Value(js.v8Isolate, getHandle(js).As<v8::Value>());\n}\n\nnamespace _ {\n\n// Helper class for JSG_TRY / JSG_CATCH macros.\n//\n// Sets up a v8::TryCatch on construction and converts caught exceptions to jsg::Value.\n// Handles both JsExceptionThrown (returns V8 exception directly) and kj::Exception\n// (converts via Lock::exceptionToJs()).\n//\n// This class is an implementation detail of the JSG_TRY / JSG_CATCH macros and should\n// not be used directly.\nclass JsgCatchScope {\n public:\n  explicit JsgCatchScope(Lock& js);\n\n  // Converts the in-flight exception to a jsg::Value and stores it.\n  // Called by JSG_CATCH macro.\n  void catchException(ExceptionToJsOptions options = {});\n\n  // Returns the caught exception. Must be called after catchException().\n  Value& getCaughtException() {\n    return KJ_ASSERT_NONNULL(caughtException);\n  }\n\n private:\n  Lock& js;\n\n  // Simple wrapper to work around v8::TryCatch's deleted operator new.\n  struct Holder {\n    v8::TryCatch tryCatch;\n    explicit Holder(v8::Isolate* isolate): tryCatch(isolate) {}\n  };\n\n  // We use two separate Maybe members rather than kj::OneOf<Holder, Value> because v8::TryCatch\n  // has deleted copy/move constructors, making it incompatible with OneOf's internal storage.\n  // The tryCatchHolder is active during the try block and released by catchException(), which\n  // then populates caughtException.\n\n  // Active during the try block, consumed by catchException().\n  kj::Maybe<Holder> tryCatchHolder;\n\n  // Populated by catchException(), returned by getCaughtException().\n  kj::Maybe<Value> caughtException;\n};\n\n}  // namespace _\n\n// JSG_TRY / JSG_CATCH macros for exception handling in JSG code.\n//\n// These macros provide clean exception handling that automatically converts both JavaScript\n// exceptions (JsExceptionThrown) and KJ exceptions (kj::Exception) to jsg::Value. This is\n// the recommended way to handle exceptions in JSG code.\n//\n// Usage:\n//   JSG_TRY(js) {\n//     someCodeThatMightThrow();\n//   } JSG_CATCH(exception) {\n//     // `exception` is a jsg::Value& containing the caught exception\n//     return js.rejectedPromise<void>(kj::mv(exception));\n//   }\n//\n// With ExceptionToJsOptions:\n//   JSG_TRY(js) {\n//     someCodeThatMightThrow();\n//   } JSG_CATCH(exception, {.ignoreDetail = true}) {\n//     // Handle exception with custom conversion options\n//   }\n//\n// JSG_TRY(js): Sets up exception handling with the given jsg::Lock. The `js` parameter makes\n// the isolate explicit and enables future coroutine support.\n//\n// JSG_CATCH(name, ...): Catches any exception and converts it to a jsg::Value. The `name`\n// parameter is a user-chosen identifier that will be a `jsg::Value&` in the handler block.\n// Optional ExceptionToJsOptions can be passed as a second argument.\n//\n// IMPORTANT: The code block following JSG_CATCH is NOT a true catch handler:\n// - You CANNOT rethrow with `throw` (there is no current exception)\n//\n// To rethrow the exception, use: js.throwException(kj::mv(exception));\n\n// Since we have two macros -- JSG_TRY and JSG_CATCH -- which must both access the same state,\n// we use a hard-coded variable name. This causes benign shadowing in nested JSG_TRY/JSG_CATCHes,\n// so we disable shadowing warnings. The `_jsg` prefix makes name collision unlikely.\n#define JSG_TRY(js)                                                                                \\\n  KJ_SILENCE_SHADOWING_BEGIN                                                                       \\\n  if (::workerd::jsg::_::JsgCatchScope _jsgTryCatch(js); true) try KJ_SILENCE_SHADOWING_END\n\n#define JSG_CATCH(exception, ...)                                                                  \\\n  catch (...) {                                                                                    \\\n    _jsgTryCatch.catchException(__VA_ARGS__);                                                      \\\n    goto KJ_UNIQUE_NAME(_jsgTryCatchHandler);                                                      \\\n  }                                                                                                \\\n  else KJ_UNIQUE_NAME(_jsgTryCatchHandler)                                                         \\\n      : if (auto& exception = _jsgTryCatch.getCaughtException(); false) {}                         \\\n  else\n\n}  // namespace workerd::jsg\n\n// clang-format off\n// These includes are needed for the JSG type glue macros to work.\n#include \"promise.h\"\n#include \"modules.h\"\n#include \"resource.h\"\n// JSG has very entrenched include cycles\n// NOLINTNEXTLINE(misc-header-include-cycle)\n#include \"jsvalue.h\"\n// clang-format on\n\n// The main JSG API no longer depends on the Type Wrapper, but to avoid extensive changes in\n// external code using JSG we still want it to be available when including jsg.h. This technically\n// violates Bazel's encapsulation philosophy (type-wrapper.h should not be visible from jsg.h), so\n// we only make jsg.h available for external code as part of the main jsg target including type-wrapper.h.\n#ifndef JSG_IMPLEMENTATION\n#include <workerd/jsg/type-wrapper.h>\n#endif  // JSG_IMPLEMENTATION\n"
  },
  {
    "path": "src/workerd/jsg/jsvalue-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"jsg-test.h\"\n#include \"jsvalue.h\"\nnamespace workerd::jsg::test {\nnamespace {\n\nV8System v8System;\nclass ContextGlobalObject: public Object, public ContextGlobal {};\n\nstruct JsValueContext: public ContextGlobalObject {\n  JsRef<JsValue> persisted;\n  JsValue takeJsValue(Lock& js, JsValue v) {\n    KJ_ASSERT(!v.isTruthy(js));\n    KJ_ASSERT(v.typeOf(js) == \"boolean\"_kj);\n    KJ_ASSERT(v.isBoolean());\n    KJ_ASSERT(!v.isObject());\n    JsBoolean b = KJ_ASSERT_NONNULL(v.tryCast<JsBoolean>());\n    KJ_ASSERT(!b.value(js));\n\n    return b;\n  }\n  JsValue takeJsString(Lock& js, Optional<JsString> v) {\n    return v.orDefault([&] { return js.str(\"bar\"_kj); });\n  }\n  JsValue takeJsNumber(Lock& js, Optional<JsNumber> v) {\n    return v.orDefault([&] { return js.num(42.0); });\n  }\n  JsBoolean takeJsBoolean(Lock& js, JsBoolean v, const TypeHandler<bool>& handler) {\n    auto ref = v.addRef(js);\n\n    // Because Js* types are trivially assignable to v8::Local<v8::Value>,\n    // they work out of the box with the existing TypeHandler<T> model\n    // and can be converted into more specific types easily.\n    bool result = KJ_ASSERT_NONNULL(handler.tryUnwrap(js, v));\n    KJ_ASSERT(result == v.value(js));\n\n    return ref.getHandle(js);\n  }\n  JsObject takeJsObject(JsObject v) {\n    return v;\n  }\n  JsArray takeJsArray(Lock& js, JsArray v) {\n    KJ_ASSERT(v.size() == 3);\n    JsValue val = v.get(js, 0);\n    KJ_ASSERT(val.isNumber());\n    KJ_ASSERT(kj::str(val) == \"1\");\n    return v;\n  }\n  JsValue getString(Lock& js) {\n    return js.str(\"foo\"_kj);\n  }\n  JsValue getStringIntern(Lock& js) {\n    return js.strIntern(\"foo\");\n  }\n  JsMap getMap(Lock& js) {\n    auto map = js.map();\n    map.set(js, \"foo\", js.num(1));\n    return map;\n  }\n  JsArray getArray(Lock& js) {\n    return js.arr(js.undefined(), js.null(), js.num(1));\n  }\n  JsSet getSet(Lock& js) {\n    return js.set(js.num(1), js.num(1), js.str(\"foo\"_kj), js.str(\"foo\"_kj));\n  }\n  void setRef(Lock& js, JsRef<JsString> value) {\n    JsValue v = value.getHandle(js);\n    persisted = v.addRef(js);\n  }\n  JsValue getRef(Lock& js) {\n    return persisted.getHandle(js);\n  }\n  JsDate getDate(Lock& js) {\n    return js.date(0);\n  }\n\n  JsValue callFunction(Lock& js, JsFunction fn) {\n    return fn.callNoReceiver(js, js.num(1));\n  }\n\n  struct Foo: public Object {\n    JSG_RESOURCE_TYPE(Foo) {}\n  };\n\n  JsValue checkProxyPrototype(Lock& js, JsValue value) {\n    JSG_REQUIRE(value.isProxy(), TypeError, \"not a proxy\");\n    auto obj = KJ_ASSERT_NONNULL(value.tryCast<JsObject>());\n    return obj.getPrototype(js);\n  }\n\n  JSG_RESOURCE_TYPE(JsValueContext) {\n    JSG_METHOD(takeJsValue);\n    JSG_METHOD(takeJsString);\n    JSG_METHOD(takeJsNumber);\n    JSG_METHOD(takeJsBoolean);\n    JSG_METHOD(takeJsObject);\n    JSG_METHOD(takeJsArray);\n    JSG_METHOD(getString);\n    JSG_METHOD(getStringIntern);\n    JSG_METHOD(getMap);\n    JSG_METHOD(getArray);\n    JSG_METHOD(getSet);\n    JSG_METHOD(setRef);\n    JSG_METHOD(getRef);\n    JSG_METHOD(getDate);\n    JSG_METHOD(checkProxyPrototype);\n    JSG_METHOD(callFunction);\n    JSG_NESTED_TYPE(Foo);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(JsValueIsolate, JsValueContext, JsValueContext::Foo);\n\nKJ_TEST(\"simple\") {\n  Evaluator<JsValueContext, JsValueIsolate> e(v8System);\n  e.expectEval(\"takeJsValue(false)\", \"boolean\", \"false\");\n  e.expectEval(\"takeJsString(123)\", \"string\", \"123\");\n  e.expectEval(\"takeJsString()\", \"string\", \"bar\");\n  e.expectEval(\"takeJsNumber(5)\", \"number\", \"5\");\n  e.expectEval(\"takeJsNumber()\", \"number\", \"42\");\n  // Empty string coerces to 0 value in JS. Ex: Number('') === 0\n  e.expectEval(\"takeJsNumber('')\", \"number\", \"0\");\n  // NaN is still a JsNumber. To check \"safety\", call toSafeInteger() method.\n  e.expectEval(\"takeJsNumber(NaN)\", \"number\", \"NaN\");\n  e.expectEval(\"Number({[Symbol.toPrimitive]() { return 1 }})\", \"number\", \"1\");\n  e.expectEval(\"takeJsBoolean(true)\", \"boolean\", \"true\");\n  e.expectEval(\"takeJsBoolean('hi')\", \"boolean\", \"true\");\n  e.expectEval(\"takeJsBoolean('')\", \"boolean\", \"false\");\n  e.expectEval(\"const o = {}; o === takeJsObject(o);\", \"boolean\", \"true\");\n  e.expectEval(\"const a = [1,2,3]; a[1] === takeJsArray(a)[1]\", \"boolean\", \"true\");\n  e.expectEval(\"getString()\", \"string\", \"foo\");\n  e.expectEval(\"getStringIntern()\", \"string\", \"foo\");\n  e.expectEval(\"const m = getMap(); m.get('foo')\", \"number\", \"1\");\n  e.expectEval(\"const s = getSet(); s.size === 2 && s.has(1) && s.has('foo') && !s.has('bar')\",\n      \"boolean\", \"true\");\n  e.expectEval(\"const a = getArray(); a[2];\", \"number\", \"1\");\n  e.expectEval(\"setRef('foo'); getRef('foo')\", \"string\", \"foo\");\n  e.expectEval(\"takeJsObject(undefined)\", \"throws\",\n      \"TypeError: Failed to execute 'takeJsObject' on 'JsValueContext': parameter 1 \"\n      \"is not of type 'JsObject'.\");\n  e.expectEval(\"getDate() instanceof Date\", \"boolean\", \"true\");\n  e.expectEval(\n      \"checkProxyPrototype(new Proxy(class extends Foo{}, {})) === Foo\", \"boolean\", \"true\");\n  e.expectEval(\"checkProxyPrototype(new Proxy({}, { getPrototypeOf() { return Foo; } } )) === Foo\",\n      \"boolean\", \"true\");\n  e.expectEval(\"checkProxyPrototype(new Proxy({}, { getPrototypeOf() { return String; } } )) \"\n               \"=== Foo\",\n      \"boolean\", \"false\");\n  e.expectEval(\"function f(val) { return this == globalThis && val === 1; }; callFunction(f);\",\n      \"boolean\", \"true\");\n}\n\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/jsvalue.c++",
    "content": "#include \"jsvalue.h\"\n\n#include \"buffersource.h\"\n#include \"ser.h\"\n#include \"simdutf.h\"\n\n#include <v8.h>\n\n#include <kj/string-tree.h>\n#include <kj/string.h>\n\n#include <cmath>\n\nnamespace workerd::jsg {\n\nJsValue::JsValue(v8::Local<v8::Value> inner): inner(inner) {\n  requireOnStack(this);\n}\n\nbool JsValue::operator==(const JsValue& other) const {\n  return inner == other.inner;\n}\n\nbool JsValue::strictEquals(const JsValue& other) const {\n  return inner->StrictEquals(other.inner);\n}\n\nJsMap::operator JsObject() {\n  return JsObject(inner);\n}\n\nvoid JsMap::set(Lock& js, const JsValue& name, const JsValue& value) {\n  check(inner->Set(js.v8Context(), name.inner, value.inner));\n}\n\nvoid JsMap::set(Lock& js, kj::StringPtr name, const JsValue& value) {\n  set(js, js.strIntern(name), value);\n}\n\nJsValue JsMap::get(Lock& js, const JsValue& name) {\n  return JsValue(check(inner->Get(js.v8Context(), name.inner)));\n}\n\nJsValue JsMap::get(Lock& js, kj::StringPtr name) {\n  return get(js, js.strIntern(name));\n}\n\nbool JsMap::has(Lock& js, const JsValue& name) {\n  return check(inner->Has(js.v8Context(), name.inner));\n}\n\nbool JsMap::has(Lock& js, kj::StringPtr name) {\n  return has(js, js.strIntern(name));\n}\n\nvoid JsMap::delete_(Lock& js, const JsValue& name) {\n  check(inner->Delete(js.v8Context(), name.inner));\n}\n\nvoid JsMap::delete_(Lock& js, kj::StringPtr name) {\n  delete_(js, js.strIntern(name));\n}\n\nvoid JsObject::defineProperty(Lock& js, kj::StringPtr name, const JsValue& value) {\n  v8::Local<v8::String> nameStr = js.strIntern(name);\n  check(inner->DefineOwnProperty(js.v8Context(), nameStr, value));\n}\n\nvoid JsObject::setReadOnly(Lock& js, kj::StringPtr name, const JsValue& value) {\n  v8::Local<v8::String> nameStr = js.strIntern(name);\n  check(inner->DefineOwnProperty(js.v8Context(), nameStr, value,\n      static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete)));\n}\n\nvoid JsObject::setNonEnumerable(Lock& js, const JsSymbol& name, const JsValue& value) {\n  check(inner->DefineOwnProperty(\n      js.v8Context(), name.inner, value.inner, v8::PropertyAttribute::DontEnum));\n}\n\nvoid JsObject::setPrivate(Lock& js, kj::StringPtr name, const JsValue& value) {\n  auto p = v8::Private::ForApi(js.v8Isolate, v8StrIntern(js.v8Isolate, name));\n  check(inner->SetPrivate(js.v8Context(), p, value.inner));\n}\n\nJsValue JsObject::getPrivate(Lock& js, kj::StringPtr name) {\n  auto p = v8::Private::ForApi(js.v8Isolate, v8StrIntern(js.v8Isolate, name));\n  return JsValue(check(inner->GetPrivate(js.v8Context(), p)));\n}\n\nbool JsObject::hasPrivate(Lock& js, kj::StringPtr name) {\n  auto p = v8::Private::ForApi(js.v8Isolate, v8StrIntern(js.v8Isolate, name));\n  return check(inner->HasPrivate(js.v8Context(), p));\n}\n\nint JsObject::hashCode() const {\n  return kj::hashCode(inner->GetIdentityHash());\n}\n\nkj::String JsObject::getConstructorName() {\n  return kj::str(inner->GetConstructorName());\n}\n\nJsArray JsObject::getPropertyNames(Lock& js,\n    KeyCollectionFilter keyFilter,\n    PropertyFilter propertyFilter,\n    IndexFilter indexFilter) {\n  auto v8keyFilter = keyFilter == KeyCollectionFilter::INCLUDE_PROTOTYPES\n      ? v8::KeyCollectionMode::kIncludePrototypes\n      : v8::KeyCollectionMode::kOwnOnly;\n  auto v8PropertyFilter = static_cast<v8::PropertyFilter>(propertyFilter);\n  auto v8IndexFilter = indexFilter == IndexFilter::INCLUDE_INDICES\n      ? v8::IndexFilter::kIncludeIndices\n      : v8::IndexFilter::kSkipIndices;\n  return JsArray(\n      check(inner->GetPropertyNames(js.v8Context(), v8keyFilter, v8PropertyFilter, v8IndexFilter)));\n}\n\nJsArray JsObject::previewEntries(bool* isKeyValue) {\n  return JsArray(check(inner->PreviewEntries(isKeyValue)));\n}\n\nvoid JsObject::recursivelyFreeze(Lock& js) {\n  jsg::recursivelyFreeze(js.v8Context(), inner);\n}\n\nvoid JsObject::seal(Lock& js) {\n  check(inner->SetIntegrityLevel(js.v8Context(), v8::IntegrityLevel::kSealed));\n}\n\nJsObject JsObject::jsonClone(Lock& js) {\n  auto tmp = JsValue(inner).toJson(js);\n  auto obj = KJ_ASSERT_NONNULL(JsValue::fromJson(js, tmp).tryCast<jsg::JsObject>());\n  return JsObject(obj);\n}\n\nJsValue JsObject::getPrototype(Lock& js) {\n  if (inner->IsProxy()) {\n    // Here we emulate the behavior of v8's GetPrototypeV2() function for proxies.\n    // If the proxy has a getPrototypeOf trap, we call it and return the result.\n    // Otherwise we return the prototype of the target object.\n    // Note that we do not check if the target object is extensible or not, or\n    // if the returned prototype is consistent with the target's prototype if\n    // the target is not extensible. See the comment below for more details.\n    auto proxy = inner.As<v8::Proxy>();\n    JSG_REQUIRE(!proxy->IsRevoked(), TypeError, \"Proxy is revoked\");\n    auto handler = proxy->GetHandler();\n    JSG_REQUIRE(handler->IsObject(), TypeError, \"Proxy handler is not an object\");\n    auto jsHandler = JsObject(handler.As<v8::Object>());\n    auto trap = jsHandler.get(js, \"getPrototypeOf\"_kj);\n    auto target = proxy->GetTarget();\n    if (trap.isUndefined()) {\n      JSG_REQUIRE(target->IsObject(), TypeError, \"Proxy target is not an object\");\n      // Run this through getPrototype to handle the case where the target is also a proxy.\n      return JsObject(target.As<v8::Object>()).getPrototype(js);\n    }\n    JSG_REQUIRE(trap.isFunction(), TypeError, \"Proxy getPrototypeOf trap is not a function\");\n    v8::Local<v8::Function> fn = ((v8::Local<v8::Value>)trap).As<v8::Function>();\n    v8::Local<v8::Value> args[] = {target};\n    auto ret = JsValue(check(fn->Call(js.v8Context(), jsHandler.inner, 1, args)));\n    JSG_REQUIRE(ret.isObject() || ret.isNull(), TypeError,\n        \"Proxy getPrototypeOf trap did not return an object or null\");\n    // TODO(maybe): V8 performs additional checks on the returned value to\n    // see if the proxy and the target are extensible or not, and if the\n    // returned prototype is consistent with the target's prototype if they\n    // are not extensible. To strictly match v8's behavior we should do the\n    // same but (a) v8 does not expose the necessary APIs to do so, and (b)\n    // it is not clear if we actually need to perform the additional check\n    // given how we are currently using this function.\n    return ret;\n  }\n  return JsValue(inner->GetPrototypeV2());\n}\n\nkj::String JsSymbol::description(Lock& js) const {\n  auto desc = inner->Description(js.v8Isolate);\n  if (desc.IsEmpty() || desc->IsUndefined()) {\n    return kj::String();\n  }\n  return kj::str(desc);\n}\n\nvoid JsSet::add(Lock& js, const JsValue& value) {\n  check(inner->Add(js.v8Context(), value.inner));\n}\n\nbool JsSet::has(Lock& js, const JsValue& value) const {\n  return check(inner->Has(js.v8Context(), value.inner));\n}\n\nbool JsSet::delete_(Lock& js, const JsValue& value) {\n  return check(inner->Delete(js.v8Context(), value.inner));\n}\n\nvoid JsSet::addAll(Lock& js, kj::ArrayPtr<const JsValue> values) {\n  for (const JsValue& value: values) {\n    check(inner->Add(js.v8Context(), value.inner));\n  }\n}\n\nvoid JsSet::clear() {\n  inner->Clear();\n}\n\nsize_t JsSet::size() const {\n  return inner->Size();\n}\n\nJsSet::operator JsArray() const {\n  return JsArray(inner->AsArray());\n}\n\nkj::Maybe<int32_t> JsInt32::value(Lock& js) const {\n  KJ_ASSERT(!inner.IsEmpty());\n  int32_t value;\n  // The Int32Value(...) operation can fail with a JS exception, in which case\n  // we return kj::none and the error should be allowed to propagate.\n  if (inner->Int32Value(js.v8Context()).To(&value)) {\n    return value;\n  }\n  return kj::none;\n}\n\nkj::Maybe<uint32_t> JsUint32::value(Lock& js) const {\n  KJ_ASSERT(!inner.IsEmpty());\n  uint32_t value;\n  // The Uint32Value(...) operation can fail with a JS exception, in which case\n  // we return kj::none and the error should be allowed to propagate.\n  if (inner->Uint32Value(js.v8Context()).To(&value)) {\n    return value;\n  }\n  return kj::none;\n};\n\nkj::Maybe<int64_t> JsBigInt::toInt64(Lock& js) const {\n  KJ_ASSERT(!inner.IsEmpty());\n  bool lossless = false;\n  int64_t value = inner->Int64Value(&lossless);\n  if (!lossless) {\n    js.v8Isolate->ThrowException(js.rangeError(\"BigInt value does not fit in int64_t\"));\n    return kj::none;\n  }\n  return value;\n}\n\nkj::Maybe<uint64_t> JsBigInt::toUint64(Lock& js) const {\n  KJ_ASSERT(!inner.IsEmpty());\n  bool lossless = false;\n  uint64_t value = inner->Uint64Value(&lossless);\n  if (!lossless) {\n    js.v8Isolate->ThrowException(js.rangeError(\"BigInt value does not fit in uint64_t\"));\n    return kj::none;\n  }\n  return value;\n}\n\nkj::Maybe<double> JsNumber::value(Lock& js) const {\n  KJ_ASSERT(!inner.IsEmpty());\n  double value;\n  // The NumberValue(...) operation can fail with a JS exception, in which case\n  // we return kj::none and the error should be allowed to propagate.\n  if (inner->NumberValue(js.v8Context()).To(&value)) {\n    return value;\n  }\n  return kj::none;\n}\n\n// ECMA-262, 15th edition, 21.1.2.5. Number.isSafeInteger\nbool JsNumber::isSafeInteger(Lock& js) const {\n  KJ_ASSERT(!inner.IsEmpty());\n  if (!inner->IsNumber()) return false;\n  KJ_IF_SOME(value, value(js)) {\n    if (std::isnan(value) || std::isinf(value) || std::trunc(value) != value) return false;\n    constexpr uint64_t MAX_SAFE_INTEGER = (1ull << 53) - 1;\n    if (std::abs(value) <= static_cast<double>(MAX_SAFE_INTEGER)) return true;\n  }\n  return false;\n}\n\nkj::Maybe<double> JsNumber::toSafeInteger(Lock& js) const {\n  if (isSafeInteger(js)) {\n    return inner.As<v8::Number>()->Value();\n  }\n  return kj::none;\n}\n\nbool JsValue::isTruthy(Lock& js) const {\n  KJ_ASSERT(!inner.IsEmpty());\n  return inner->BooleanValue(js.v8Isolate);\n}\n\nkj::String JsValue::toString(Lock& js) const {\n  KJ_ASSERT(!inner.IsEmpty());\n  return kj::str(inner);\n}\n\nJsString JsValue::toJsString(Lock& js) const {\n  KJ_ASSERT(!inner.IsEmpty());\n  return JsString(check(inner->ToString(js.v8Context())));\n}\n\nkj::String JsValue::typeOf(Lock& js) const {\n  KJ_ASSERT(!inner.IsEmpty());\n  return kj::str(inner->TypeOf(js.v8Isolate));\n}\n\n#define V(Type)                                                                                    \\\n  bool JsValue::is##Type() const {                                                                 \\\n    return inner->Is##Type();                                                                      \\\n  }\nJS_IS_TYPES(V)\n#undef V\n\nkj::String JsValue::toJson(Lock& js) const {\n  return kj::str(check(v8::JSON::Stringify(js.v8Context(), inner)));\n}\n\nJsValue JsValue::fromJson(Lock& js, kj::ArrayPtr<const char> input) {\n  return JsValue(check(v8::JSON::Parse(js.v8Context(), js.str(input))));\n}\n\nJsValue JsValue::fromJson(Lock& js, const JsValue& input) {\n  return JsValue(check(v8::JSON::Parse(js.v8Context(), input.inner.As<v8::String>())));\n}\n\nbool JsBoolean::value(Lock& js) const {\n  return inner->BooleanValue(js.v8Isolate);\n}\n\nuint32_t JsArray::size() const {\n  return inner->Length();\n}\n\nJsValue JsArray::get(Lock& js, uint32_t i) const {\n  return JsValue(check(inner->Get(js.v8Context(), i)));\n}\n\nvoid JsArray::add(Lock& js, const JsValue& value) {\n  check(inner->Set(js.v8Context(), size(), value.inner));\n}\n\nJsArray::operator JsObject() const {\n  return JsObject(inner.As<v8::Object>());\n}\n\nkj::String JsString::toString(jsg::Lock& js) const {\n  auto buf = kj::heapArray<char>(inner->Utf8LengthV2(js.v8Isolate) + 1);\n  inner->WriteUtf8V2(js.v8Isolate, buf.begin(), buf.size(), v8::String::WriteFlags::kNullTerminate);\n  return kj::String(kj::mv(buf));\n}\n\njsg::USVString JsString::toUSVString(Lock& js) const {\n  auto buf = kj::heapArray<char>(inner->Utf8LengthV2(js.v8Isolate) + 1);\n  inner->WriteUtf8V2(js.v8Isolate, buf.begin(), buf.size(),\n      v8::String::WriteFlags::kNullTerminate | v8::String::WriteFlags::kReplaceInvalidUtf8);\n  return jsg::USVString(kj::mv(buf));\n}\n\njsg::DOMString JsString::toDOMString(Lock& js) const {\n  auto buf = kj::heapArray<char>(inner->Utf8LengthV2(js.v8Isolate) + 1);\n  inner->WriteUtf8V2(js.v8Isolate, buf.begin(), buf.size(), v8::String::WriteFlags::kNullTerminate);\n  return jsg::DOMString(kj::mv(buf));\n}\n\nint JsString::hashCode() const {\n  return kj::hashCode(inner->GetIdentityHash());\n}\n\nJsString JsString::concat(Lock& js, const JsString& one, const JsString& two) {\n  return JsString(v8::String::Concat(js.v8Isolate, one.inner, two.inner));\n}\n\nbool JsString::operator==(const JsString& other) const {\n  return inner->StringEquals(other.inner);\n}\n\nJsString JsString::internalize(Lock& js) const {\n  return JsString(inner->InternalizeString(js.v8Isolate));\n}\n\nJsString::WriteIntoStatus JsString::writeInto(\n    Lock& js, kj::ArrayPtr<char> buffer, WriteFlags options) const {\n  WriteIntoStatus result = {0, 0};\n  if (buffer.size() > 0) {\n    result.written =\n        inner->WriteUtf8V2(js.v8Isolate, buffer.begin(), buffer.size(), options, &result.read);\n  }\n  return result;\n}\n\nJsString::WriteIntoStatus JsString::writeInto(\n    Lock& js, kj::ArrayPtr<uint16_t> buffer, WriteFlags options) const {\n  WriteIntoStatus result = {0, 0};\n  if (buffer.size() > 0) {\n    result.written = kj::min(buffer.size(), length(js));\n    inner->WriteV2(js.v8Isolate, 0, result.written, buffer.begin(), options);\n    result.read = length(js);\n  }\n  return result;\n}\n\nJsString::WriteIntoStatus JsString::writeInto(\n    Lock& js, kj::ArrayPtr<kj::byte> buffer, WriteFlags options) const {\n  WriteIntoStatus result = {0, 0};\n  if (buffer.size() > 0) {\n    result.written = kj::min(buffer.size(), length(js));\n    inner->WriteOneByteV2(\n        js.v8Isolate, 0, kj::min(length(js), buffer.size()), buffer.begin(), options);\n    result.read = length(js);\n  }\n  return result;\n}\n\nbool JsString::isFlat() const {\n  return inner->IsFlat();\n}\n\nbool JsString::containsOnlyOneByte() const {\n  return inner->ContainsOnlyOneByte();\n}\n\nkj::Maybe<JsArray> JsRegExp::operator()(Lock& js, const JsString& input) const {\n  auto result = check(inner->Exec(js.v8Context(), input));\n  if (result->IsNullOrUndefined()) return kj::none;\n  return JsArray(result.As<v8::Array>());\n}\n\nkj::Maybe<JsArray> JsRegExp::operator()(Lock& js, kj::StringPtr input) const {\n  auto result = check(inner->Exec(js.v8Context(), js.str(input)));\n  if (result->IsNull()) return kj::none;\n  return JsArray(result.As<v8::Array>());\n}\n\nbool JsRegExp::match(Lock& js, kj::StringPtr input) {\n  auto result = check(inner->Exec(js.v8Context(), js.str(input)));\n  return !result->IsNull();\n}\n\nkj::String JsDate::toUTCString(jsg::Lock& js) const {\n  JsString str(inner->ToUTCString());\n  return str.toString(js);\n}\n\nkj::String JsDate::toISOString(jsg::Lock& js) const {\n  JsString str(inner->ToISOString());\n  return str.toString(js);\n}\n\nJsDate::operator kj::Date() const {\n  return kj::UNIX_EPOCH + (int64_t(inner->ValueOf()) * kj::MILLISECONDS);\n}\n\nJsRegExp Lock::regexp(kj::StringPtr str, RegExpFlags flags, kj::Maybe<uint32_t> backtrackLimit) {\n  KJ_IF_SOME(limit, backtrackLimit) {\n    return JsRegExp(check(v8::RegExp::NewWithBacktrackLimit(\n        v8Context(), v8Str(v8Isolate, str), static_cast<v8::RegExp::Flags>(flags), limit)));\n  }\n  return JsRegExp(check(\n      v8::RegExp::New(v8Context(), v8Str(v8Isolate, str), static_cast<v8::RegExp::Flags>(flags))));\n}\n\nJsObject Lock::obj(kj::ArrayPtr<const kj::StringPtr> keys, kj::ArrayPtr<JsValue> values) {\n  KJ_DASSERT(keys.size() == values.size());\n  v8::LocalVector<v8::Name> keys_(v8Isolate, keys.size());\n  v8::LocalVector<v8::Value> values_(v8Isolate, keys.size());\n  for (size_t i = 0; i < keys.size(); i++) {\n    keys_[i] = strIntern(keys[i]).inner;\n    values_[i] = values[i];\n  }\n  return JsObject(v8::Object::New(\n      v8Isolate, v8::Object::New(v8Isolate), keys_.data(), values_.data(), keys.size()));\n}\n\nJsObject Lock::objNoProto(kj::ArrayPtr<kj::StringPtr> keys, kj::ArrayPtr<JsValue> values) {\n  KJ_DASSERT(keys.size() == values.size());\n  v8::LocalVector<v8::Name> keys_(v8Isolate, keys.size());\n  v8::LocalVector<v8::Value> values_(v8Isolate, keys.size());\n  for (size_t i = 0; i < keys.size(); i++) {\n    keys_[i] = strIntern(keys[i]).inner;\n    values_[i] = values[i];\n  }\n  return JsObject(\n      v8::Object::New(v8Isolate, v8::Null(v8Isolate), keys_.data(), values_.data(), keys.size()));\n}\n\nJsArray Lock::arr(kj::ArrayPtr<JsValue> values) {\n  v8::LocalVector<v8::Value> items(v8Isolate, values.size());\n  for (size_t n = 0; n < values.size(); n++) {\n    items[n] = values[n];\n  }\n  return JsArray(v8::Array::New(v8Isolate, items.data(), items.size()));\n}\n\n#define V(Name)                                                                                    \\\n  JsSymbol Lock::symbol##Name() {                                                                  \\\n    return JsSymbol(v8::Symbol::Get##Name(v8Isolate));                                             \\\n  }\nJS_V8_SYMBOLS(V)\n#undef V\n\nJsDate Lock::date(kj::StringPtr date) {\n  v8::Local<v8::Value> converted = check(v8::Date::Parse(v8Context(), str(date)));\n  KJ_REQUIRE(converted->IsDate());\n  return JsDate(converted.As<v8::Date>());\n}\n\nJsPromise Lock::rejectedJsPromise(jsg::JsValue exception) {\n  v8::EscapableHandleScope handleScope(v8Isolate);\n  auto context = v8Context();\n  auto resolver = check(v8::Promise::Resolver::New(context));\n  check(resolver->Reject(context, exception));\n  return JsPromise(handleScope.Escape(resolver->GetPromise()));\n}\n\nJsPromise Lock::rejectedJsPromise(kj::Exception&& exception, ExceptionToJsOptions options) {\n  return rejectedJsPromise(exceptionToJsValue(kj::mv(exception), options).getHandle(*this));\n}\n\nPromiseState JsPromise::state() {\n  switch (inner->State()) {\n    case v8::Promise::PromiseState::kPending:\n      return PromiseState::PENDING;\n    case v8::Promise::PromiseState::kFulfilled:\n      return PromiseState::FULFILLED;\n    case v8::Promise::PromiseState::kRejected:\n      return PromiseState::REJECTED;\n  }\n  KJ_UNREACHABLE\n}\n\nJsValue JsPromise::result() {\n  return JsValue(inner->Result());\n}\n\nJsValue JsProxy::target() {\n  return JsValue(inner->GetTarget());\n}\n\nJsValue JsProxy::handler() {\n  return JsValue(inner->GetHandler());\n}\n\nJsRef<JsValue> JsValue::addRef(Lock& js) {\n  return JsRef<JsValue>(js, *this);\n}\n\nJsValue JsValue::structuredClone(Lock& js, kj::Maybe<kj::Array<JsValue>> maybeTransfers) {\n  return jsg::structuredClone(js, *this, kj::mv(maybeTransfers));\n}\n\nJsMessage JsMessage::create(Lock& js, const JsValue& exception) {\n  return JsMessage(v8::Exception::CreateMessage(js.v8Isolate, exception));\n}\n\nvoid JsMessage::addJsStackTrace(Lock& js, kj::Vector<kj::String>& lines) {\n  if (inner.IsEmpty()) return;\n\n  // TODO(someday): Relying on v8::Message to pass around source locations means\n  // we can't provide the module name for errors like compiling wasm modules. We\n  // should have our own type, but it requires a refactor of how we pass around errors\n  // for script startup.\n\n  static constexpr auto addLineCol = [](kj::StringTree str, int line, int col) {\n    if (line != v8::Message::kNoLineNumberInfo) {\n      str = kj::strTree(kj::mv(str), \":\", line);\n      if (col != v8::Message::kNoColumnInfo) {\n        str = kj::strTree(kj::mv(str), \":\", col);\n      }\n    }\n    return str;\n  };\n\n  auto context = js.v8Context();\n  auto trace = inner->GetStackTrace();\n  if (trace.IsEmpty() || trace->GetFrameCount() == 0) {\n    kj::StringTree locationStr;\n\n    auto resourceNameVal = inner->GetScriptResourceName();\n    if (resourceNameVal->IsString()) {\n      auto resourceName = resourceNameVal.As<v8::String>();\n      if (!resourceName.IsEmpty() && resourceName->Length() != 0) {\n        locationStr = kj::strTree(\"  at \", resourceName);\n      }\n    }\n\n    auto lineNumber = jsg::check(inner->GetLineNumber(context));\n    auto columnNumber = jsg::check(inner->GetStartColumn(context));\n    locationStr = addLineCol(kj::mv(locationStr), lineNumber, columnNumber);\n\n    if (locationStr.size() > 0) {\n      lines.add(locationStr.flatten());\n    }\n  } else {\n    for (auto i: kj::zeroTo(trace->GetFrameCount())) {\n      auto frame = trace->GetFrame(js.v8Isolate, i);\n      kj::StringTree locationStr;\n\n      auto scriptName = frame->GetScriptName();\n      if (!scriptName.IsEmpty() && scriptName->Length() != 0) {\n        locationStr = kj::strTree(\"  at \", scriptName);\n      } else {\n        locationStr = kj::strTree(\"  at worker.js\");\n      }\n\n      auto lineNumber = frame->GetLineNumber();\n      auto columnNumber = frame->GetColumn();\n      locationStr = addLineCol(kj::mv(locationStr), lineNumber, columnNumber);\n\n      auto func = frame->GetFunctionName();\n      if (!func.IsEmpty() && func->Length() != 0) {\n        locationStr = kj::strTree(kj::mv(locationStr), \" in \", func);\n      }\n\n      lines.add(locationStr.flatten());\n    }\n  }\n}\n\nsize_t JsFunction::length(Lock& js) const {\n  JsObject obj = *this;\n  auto lengthVal = obj.get(js, \"length\"_kj);\n  KJ_IF_SOME(num, lengthVal.tryCast<jsg::JsNumber>()) {\n    return static_cast<size_t>(num.value(js).orDefault(0));\n  }\n  return 0;\n}\n\nJsString JsFunction::name(Lock& js) const {\n  JsObject obj = *this;\n  auto nameVal = obj.get(js, \"name\"_kj);\n  // It really shouldn't ever be possible for the name property to be non-string,\n  // but just in case, we check and throw if that happens.\n  return JSG_REQUIRE_NONNULL(\n      nameVal.tryCast<jsg::JsString>(), TypeError, \"Function name is not a string\");\n}\n\nJsValue JsFunction::call(Lock& js, const JsValue& recv, v8::LocalVector<v8::Value>& args) const {\n  v8::Local<v8::Function> fn = *this;\n  return JsValue(check(fn->Call(js.v8Context(), recv, args.size(), args.data())));\n}\n\nJsValue JsFunction::callNoReceiver(Lock& js, v8::LocalVector<v8::Value>& args) const {\n  return call(js, js.null(), args);\n}\n\nuint JsFunction::hashCode() const {\n  v8::Local<v8::Function> obj = *this;\n  return kj::hashCode(obj->GetIdentityHash());\n}\n\nBufferSource Lock::bytes(kj::Array<kj::byte> data) {\n  return BufferSource(*this, BackingStore::from(*this, kj::mv(data)));\n}\n\n// ======================================================================================\n// JsArrayBuffer\n\nJsArrayBuffer JsArrayBuffer::create(Lock& js, size_t length) {\n  JSG_REQUIRE(length < v8::ArrayBuffer::kMaxByteLength, RangeError, \"The length is too large\");\n  auto backing = v8::ArrayBuffer::NewBackingStore(js.v8Isolate, length,\n      v8::BackingStoreInitializationMode::kZeroInitialized,\n      v8::BackingStoreOnFailureMode::kReturnNull);\n  JSG_REQUIRE(backing != nullptr, RangeError, \"Failed to allocate memory for ArrayBuffer\");\n  return create(js, kj::mv(backing));\n}\n\nJsArrayBuffer JsArrayBuffer::create(Lock& js, kj::ArrayPtr<const kj::byte> data) {\n  auto buf = create(js, data.size());\n  buf.asArrayPtr().copyFrom(data);\n  return buf;\n}\n\nJsArrayBuffer JsArrayBuffer::create(Lock& js, std::unique_ptr<v8::BackingStore> backingStore) {\n  return JsArrayBuffer(v8::ArrayBuffer::New(js.v8Isolate, kj::mv(backingStore)));\n}\n\nkj::ArrayPtr<kj::byte> JsArrayBuffer::asArrayPtr() {\n  v8::Local<v8::ArrayBuffer> inner = *this;\n  if (inner->WasDetached()) [[unlikely]] {\n    return nullptr;\n  }\n  void* data = inner->GetBackingStore()->Data();\n  size_t length = inner->ByteLength();\n  return kj::ArrayPtr(static_cast<kj::byte*>(data), length);\n}\n\nkj::ArrayPtr<const kj::byte> JsArrayBuffer::asArrayPtr() const {\n  v8::Local<v8::ArrayBuffer> inner = *this;\n  if (inner->WasDetached()) [[unlikely]] {\n    return nullptr;\n  }\n  const void* data = inner->GetBackingStore()->Data();\n  size_t length = inner->ByteLength();\n  return kj::ArrayPtr(static_cast<const kj::byte*>(data), length);\n}\n\nJsArrayBuffer JsArrayBuffer::slice(Lock& js, size_t newLength) const {\n  JSG_REQUIRE(newLength <= size(), RangeError, \"New length exceeds buffer length\");\n  auto backing = v8::ArrayBuffer::NewBackingStore(js.v8Isolate, newLength,\n      v8::BackingStoreInitializationMode::kUninitialized,\n      v8::BackingStoreOnFailureMode::kReturnNull);\n  JSG_REQUIRE(backing != nullptr, RangeError, \"Failed to allocate memory for ArrayBuffer\");\n  auto dest = kj::ArrayPtr(static_cast<kj::byte*>(backing->Data()), newLength);\n  v8::Local<v8::ArrayBuffer> inner = *this;\n  dest.copyFrom(\n      kj::ArrayPtr(static_cast<const kj::byte*>(inner->GetBackingStore()->Data()), newLength));\n  return JsArrayBuffer(v8::ArrayBuffer::New(js.v8Isolate, kj::mv(backing)));\n}\n\nsize_t JsArrayBuffer::size() const {\n  v8::Local<v8::ArrayBuffer> inner = *this;\n  return inner->ByteLength();\n}\n\nkj::Array<kj::byte> JsArrayBuffer::copy() {\n  auto ptr = asArrayPtr();\n  return kj::heapArray(ptr);\n}\n\n// ======================================================================================\n// JsArrayBufferView\n\nsize_t JsArrayBufferView::size() const {\n  v8::Local<v8::ArrayBufferView> inner = *this;\n  return inner->ByteLength();\n}\n\nbool JsArrayBufferView::isIntegerType() const {\n  v8::Local<v8::ArrayBufferView> inner = *this;\n  return inner->IsUint8Array() || inner->IsUint8ClampedArray() || inner->IsInt8Array() ||\n      inner->IsUint16Array() || inner->IsInt16Array() || inner->IsUint32Array() ||\n      inner->IsInt32Array() || inner->IsBigInt64Array() || inner->IsBigUint64Array();\n}\n\n// ======================================================================================\n// JsBufferSource\n\nkj::ArrayPtr<kj::byte> JsBufferSource::asArrayPtr() {\n  v8::Local<v8::Value> inner = *this;\n  if (inner->IsArrayBuffer()) {\n    auto buf = inner.As<v8::ArrayBuffer>();\n    if (buf->WasDetached()) [[unlikely]] {\n      return nullptr;\n    }\n    return kj::ArrayPtr(static_cast<kj::byte*>(buf->Data()), buf->ByteLength());\n  } else {\n    KJ_DASSERT(inner->IsArrayBufferView());\n    auto view = inner.As<v8::ArrayBufferView>();\n    auto buf = view->Buffer();\n    if (buf->WasDetached()) [[unlikely]] {\n      return nullptr;\n    }\n    kj::byte* data = static_cast<kj::byte*>(buf->Data()) + view->ByteOffset();\n    return kj::ArrayPtr(data, view->ByteLength());\n  }\n}\n\nsize_t JsBufferSource::size() const {\n  v8::Local<v8::Value> inner = *this;\n  if (inner->IsArrayBuffer()) {\n    auto buf = inner.As<v8::ArrayBuffer>();\n    if (buf->WasDetached()) [[unlikely]] {\n      return 0;\n    }\n    return buf->ByteLength();\n  } else {\n    KJ_DASSERT(inner->IsArrayBufferView());\n    auto view = inner.As<v8::ArrayBufferView>();\n    if (view->Buffer()->WasDetached()) [[unlikely]] {\n      return 0;\n    }\n    return view->ByteLength();\n  }\n}\n\nbool JsBufferSource::isIntegerType() const {\n  v8::Local<v8::Value> inner = *this;\n  return inner->IsUint8Array() || inner->IsUint8ClampedArray() || inner->IsInt8Array() ||\n      inner->IsUint16Array() || inner->IsInt16Array() || inner->IsUint32Array() ||\n      inner->IsInt32Array() || inner->IsBigInt64Array() || inner->IsBigUint64Array();\n}\n\nbool JsBufferSource::isArrayBuffer() const {\n  v8::Local<v8::Value> inner = *this;\n  return inner->IsArrayBuffer();\n}\n\nbool JsBufferSource::isArrayBufferView() const {\n  v8::Local<v8::Value> inner = *this;\n  return inner->IsArrayBufferView();\n}\n\nkj::Array<kj::byte> JsBufferSource::copy() {\n  auto ptr = asArrayPtr();\n  return kj::heapArray(ptr);\n}\n\n// ======================================================================================\n// JsUint8Array\n\nJsUint8Array JsUint8Array::create(Lock& js, size_t length) {\n  JSG_REQUIRE(length < v8::ArrayBuffer::kMaxByteLength, RangeError, \"The length is too large\");\n  auto backing = v8::ArrayBuffer::NewBackingStore(js.v8Isolate, length,\n      v8::BackingStoreInitializationMode::kZeroInitialized,\n      v8::BackingStoreOnFailureMode::kReturnNull);\n  JSG_REQUIRE(backing != nullptr, RangeError, \"Failed to allocate memory for Uint8Array\");\n  return create(js, kj::mv(backing), 0, length);\n}\n\nJsUint8Array JsUint8Array::create(Lock& js, kj::ArrayPtr<const kj::byte> data) {\n  auto buf = create(js, data.size());\n  buf.asArrayPtr().copyFrom(data);\n  return buf;\n}\n\nJsUint8Array JsUint8Array::create(Lock& js, JsArrayBuffer& buffer) {\n  v8::Local<v8::ArrayBuffer> ab = buffer;\n  return JsUint8Array(v8::Uint8Array::New(ab, 0, ab->ByteLength()));\n}\n\nJsUint8Array JsUint8Array::create(\n    Lock& js, std::unique_ptr<v8::BackingStore> backingStore, size_t byteOffset, size_t length) {\n  return JsUint8Array(v8::Uint8Array::New(\n      v8::ArrayBuffer::New(js.v8Isolate, kj::mv(backingStore)), byteOffset, length));\n}\n\nJsUint8Array JsUint8Array::slice(Lock& js, size_t newLength) const {\n  JSG_REQUIRE(newLength <= size(), RangeError, \"New length exceeds array length\");\n  auto u8 = v8::Uint8Array::New(inner->Buffer(), inner->ByteOffset(), newLength);\n  return JsUint8Array(u8);\n}\n\nkj::ArrayPtr<const kj::byte> JsUint8Array::asArrayPtr() const {\n  auto buf = inner->Buffer();\n  if (buf->WasDetached()) [[unlikely]] {\n    return nullptr;\n  }\n  const kj::byte* data = static_cast<const kj::byte*>(buf->Data()) + inner->ByteOffset();\n  size_t length = inner->ByteLength();\n  return kj::ArrayPtr(data, length);\n}\n\nsize_t JsUint8Array::size() const {\n  return inner->ByteLength();\n}\n\nkj::Array<kj::byte> JsUint8Array::copy() {\n  auto ptr = asArrayPtr();\n  return kj::heapArray(ptr);\n}\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/jsvalue.h",
    "content": "#pragma once\n\n#include \"jsg.h\"\n\n#include <v8-container.h>\n#include <v8-date.h>\n#include <v8-external.h>\n#include <v8-proxy.h>\n\nnamespace workerd::jsg {\n\ninline void requireOnStack(void* self) {\n#ifdef KJ_DEBUG\n  kj::requireOnStack(self, \"JsValue types must be allocated on stack\");\n#endif\n}\n\n// The types listed in the JS_IS_TYPES macro are translated into is{Name}()\n// methods on the JsValue type. These correspond directly to equivalent v8::Value\n// types and therefore must be kept in sync.\n#define JS_IS_TYPES(V)                                                                             \\\n  V(Undefined)                                                                                     \\\n  V(Null)                                                                                          \\\n  V(NullOrUndefined)                                                                               \\\n  V(True)                                                                                          \\\n  V(False)                                                                                         \\\n  V(ArgumentsObject)                                                                               \\\n  V(NativeError)                                                                                   \\\n  V(Name)                                                                                          \\\n  V(AsyncFunction)                                                                                 \\\n  V(GeneratorFunction)                                                                             \\\n  V(GeneratorObject)                                                                               \\\n  V(WeakMap)                                                                                       \\\n  V(WeakSet)                                                                                       \\\n  V(WeakRef)                                                                                       \\\n  V(WasmNull)                                                                                      \\\n  V(ModuleNamespaceObject)                                                                         \\\n  V(MapIterator)                                                                                   \\\n  V(SetIterator)                                                                                   \\\n  V(External)                                                                                      \\\n  V(BigIntObject)                                                                                  \\\n  V(BooleanObject)                                                                                 \\\n  V(NumberObject)                                                                                  \\\n  V(StringObject)                                                                                  \\\n  V(SymbolObject)                                                                                  \\\n  V(TypedArray)                                                                                    \\\n  V(Uint8ClampedArray)                                                                             \\\n  V(Int8Array)                                                                                     \\\n  V(Uint16Array)                                                                                   \\\n  V(Int16Array)                                                                                    \\\n  V(Uint32Array)                                                                                   \\\n  V(Int32Array)                                                                                    \\\n  V(Float16Array)                                                                                  \\\n  V(Float32Array)                                                                                  \\\n  V(Float64Array)                                                                                  \\\n  V(BigInt64Array)                                                                                 \\\n  V(BigUint64Array)                                                                                \\\n  V(DataView)                                                                                      \\\n  V(SharedArrayBuffer)                                                                             \\\n  V(WasmMemoryObject)                                                                              \\\n  V(WasmModuleObject)                                                                              \\\n  JS_TYPE_CLASSES(V)\n\nstruct JsValueWrapper;\n\n// Filters for `JsObject::getPropertyNames()`\nenum PropertyFilter {\n  ALL_PROPERTIES = 0,\n  ONLY_WRITABLE = 1,\n  ONLY_ENUMERABLE = 2,\n  ONLY_CONFIGURABLE = 4,\n  SKIP_STRINGS = 8,\n  SKIP_SYMBOLS = 16\n};\nenum KeyCollectionFilter { OWN_ONLY, INCLUDE_PROTOTYPES };\nenum IndexFilter { INCLUDE_INDICES, SKIP_INDICES };\n\nenum PromiseState { PENDING, FULFILLED, REJECTED };\n\n// A JsValue is an abstraction for a JavaScript value that has not been mapped\n// to a C++ type. It wraps an underlying v8::Local<T> in order to avoid direct\n// use of the v8 API in many cases. The JsValue (and JsRef<T>) are meant to\n// fully replace (eventually) the use of jsg::V8Ref<T> and jsg::Value in\n// addition to replacing direct use of v8::Local<T>.\n//\n// JsValue types (including the related JsBoolean, JsArray, JsObject, etc) can\n// only be stack allocated and are not suitable for persistent storage of the\n// value. To persist the JavaScript value, use JsRef<T>.\n//\n// The jsg::Lock instance is used to create instances of the Js* types. For\n// example:\n//\n//   auto& js = Lock::from(isolate);\n//   js.withinHandleScope([&] {\n//     JsString str = js.str(\"foo\");\n//     JsNumber num = js.num(123);\n//     JsArray arr = js.arr(js.str(\"foo\"), js.num(123));\n//     JsObject obj = js.obj();\n//     obj.set(js, \"foo\", js.str(\"bar\"));\n//   });\n//\n// Note that the `js.withinHandleScope()` is only necessary if the code is not\n// already running within a handle scope (which jsg mapped methods on jsg::Object\n// instances always are).\n//\n// All of the Js* types can be trivially cast to JsValue via assignment.\n//\n//   JsValue val = js.str(\"foo\");\n//\n// A JsValue can be trivially cast to a more specific type if the underlying\n// JS type is compatible.\n//\n//   JsValue val = js.str(\"foo\");\n//   KJ_IF_SOME(str, val.tryCast<JsString>()) {\n//     // str is a JsString\n//   }\n//   KJ_IF_SOME(num, val.tryCast<JsNumber>()) {\n//     // never happens since val is not a number\n//   }\n//\n// Because JsValue types are trivially assignable to v8::Local<v8::Value>\n// they can be used together with TypeHandler<T> to convert to specific C++\n// types:\n//\n//   auto obj = js.obj();\n//   const TypeHandler<MyStruct>& handler = // ...\n//   MyStruct v = KJ_ASSERT_NONNULL(handler.tryUnwrap(js, obj));\nclass JsValue final {\n public:\n  template <typename T>\n  kj::Maybe<T> tryCast() const KJ_WARN_UNUSED_RESULT;\n\n  operator v8::Local<v8::Value>() const {\n    return inner;\n  }\n\n  bool operator==(const JsValue& other) const;\n  bool strictEquals(const JsValue& other) const;\n\n  bool isTruthy(Lock& js) const KJ_WARN_UNUSED_RESULT;\n  kj::String toString(Lock& js) const KJ_WARN_UNUSED_RESULT;\n  kj::String typeOf(Lock& js) const KJ_WARN_UNUSED_RESULT;\n  JsString toJsString(Lock& js) const KJ_WARN_UNUSED_RESULT;\n\n#define V(Type) bool is##Type() const KJ_WARN_UNUSED_RESULT;\n  JS_IS_TYPES(V)\n#undef V\n\n  kj::String toJson(Lock& js) const KJ_WARN_UNUSED_RESULT;\n  static JsValue fromJson(Lock& js, kj::ArrayPtr<const char> input) KJ_WARN_UNUSED_RESULT;\n  static JsValue fromJson(Lock& js, const JsValue& input) KJ_WARN_UNUSED_RESULT;\n\n  JsRef<JsValue> addRef(Lock& js) KJ_WARN_UNUSED_RESULT;\n\n  JsValue structuredClone(\n      Lock& js, kj::Maybe<kj::Array<JsValue>> maybeTransfers = kj::none) KJ_WARN_UNUSED_RESULT;\n\n  template <typename T>\n  static kj::Maybe<T&> tryGetExternal(Lock& js, const JsValue& value) KJ_WARN_UNUSED_RESULT;\n\n  explicit JsValue(v8::Local<v8::Value> inner);\n\n private:\n  v8::Local<v8::Value> inner;\n  friend class Lock;\n  friend struct JsValueWrapper;\n  template <typename T, typename Self>\n  friend class JsBase;\n  template <typename T>\n  friend class JsRef;\n\n#define V(Name) friend class Js##Name;\n  JS_TYPE_CLASSES(V)\n#undef V\n};\n\ntemplate <typename T, typename Self>\nclass JsBase {\n public:\n  operator v8::Local<v8::Value>() const {\n    return inner;\n  }\n  // Only provide the typed conversion when T is not already v8::Value,\n  // to avoid a duplicate operator signature (e.g. for JsBufferSource).\n  operator v8::Local<T>() const\n    requires(!kj::isSameType<T, v8::Value>())\n  {\n    return inner;\n  }\n  operator JsValue() const {\n    return JsValue(inner.template As<v8::Value>());\n  }\n  bool operator==(const JsValue& other) const KJ_WARN_UNUSED_RESULT {\n    return inner == other.inner;\n  }\n  bool operator==(const JsBase& other) const KJ_WARN_UNUSED_RESULT {\n    return inner == other.inner;\n  }\n  explicit JsBase(v8::Local<T> inner): inner(inner) {\n    requireOnStack(this);\n  }\n  JsRef<Self> addRef(Lock& js) KJ_WARN_UNUSED_RESULT;\n\n private:\n  v8::Local<T> inner;\n  friend class Lock;\n  friend class JsValue;\n#define V(Name) friend class Js##Name;\n  JS_TYPE_CLASSES(V)\n#undef V\n  friend struct JsValueWrapper;\n  template <typename U>\n  friend class JsRef;\n};\n\nclass JsBoolean final: public JsBase<v8::Boolean, JsBoolean> {\n public:\n  bool value(Lock& js) const KJ_WARN_UNUSED_RESULT;\n\n  using JsBase<v8::Boolean, JsBoolean>::JsBase;\n};\n\nclass JsArray final: public JsBase<v8::Array, JsArray> {\n public:\n  operator JsObject() const;\n  uint32_t size() const KJ_WARN_UNUSED_RESULT;\n  JsValue get(Lock& js, uint32_t i) const KJ_WARN_UNUSED_RESULT;\n  void add(Lock& js, const JsValue& value);\n\n  using JsBase<v8::Array, JsArray>::JsBase;\n};\n\nclass JsArrayBuffer final: public JsBase<v8::ArrayBuffer, JsArrayBuffer> {\n public:\n  static JsArrayBuffer create(Lock& js, size_t length);\n\n  // Allocate and copy data from the given ArrayPtr in a single step.\n  static JsArrayBuffer create(Lock& js, kj::ArrayPtr<const kj::byte> data);\n\n  static JsArrayBuffer create(Lock& js, std::unique_ptr<v8::BackingStore> backingStore);\n\n  JsArrayBuffer slice(Lock& js, size_t newLength) const;\n\n  kj::ArrayPtr<kj::byte> asArrayPtr();\n  kj::ArrayPtr<const kj::byte> asArrayPtr() const;\n\n  size_t size() const;\n\n  // Return a copy of this buffer's data as a kj::Array.\n  kj::Array<kj::byte> copy();\n\n  using JsBase<v8::ArrayBuffer, JsArrayBuffer>::JsBase;\n};\n\nclass JsArrayBufferView final: public JsBase<v8::ArrayBufferView, JsArrayBufferView> {\n public:\n  template <typename T = kj::byte>\n  kj::ArrayPtr<T> asArrayPtr() {\n    v8::Local<v8::ArrayBufferView> inner = *this;\n    auto buf = inner->Buffer();\n    if (buf->WasDetached()) [[unlikely]] {\n      return nullptr;\n    }\n    auto byteLength = inner->ByteLength();\n    T* data = reinterpret_cast<T*>(static_cast<kj::byte*>(buf->Data()) + inner->ByteOffset());\n    return kj::ArrayPtr(data, byteLength / sizeof(T));\n  }\n\n  size_t size() const;\n\n  // Returns true if the underlying view is an integer-typed TypedArray\n  // (e.g. Uint8Array, Int32Array, BigUint64Array) as opposed to a float-typed\n  // TypedArray or DataView.\n  bool isIntegerType() const;\n\n  using JsBase<v8::ArrayBufferView, JsArrayBufferView>::JsBase;\n};\n\nclass JsUint8Array final: public JsBase<v8::Uint8Array, JsUint8Array> {\n public:\n  static JsUint8Array create(Lock& js, size_t length);\n\n  // Allocate and copy data from the given ArrayPtr in a single step.\n  static JsUint8Array create(Lock& js, kj::ArrayPtr<const kj::byte> data);\n\n  // Create a Uint8Array view over the given ArrayBuffer.\n  static JsUint8Array create(Lock& js, JsArrayBuffer& buffer);\n\n  static JsUint8Array create(\n      Lock& js, std::unique_ptr<v8::BackingStore> backingStore, size_t byteOffset, size_t length);\n\n  JsUint8Array slice(Lock& js, size_t newLength) const;\n\n  template <typename T = kj::byte>\n  kj::ArrayPtr<T> asArrayPtr() {\n    v8::Local<v8::Uint8Array> inner = *this;\n    auto buf = inner->Buffer();\n    if (buf->WasDetached()) [[unlikely]] {\n      return nullptr;\n    }\n    auto byteLength = inner->ByteLength();\n    T* data = reinterpret_cast<T*>(static_cast<kj::byte*>(buf->Data()) + inner->ByteOffset());\n    return kj::ArrayPtr(data, byteLength / sizeof(T));\n  }\n\n  kj::ArrayPtr<const kj::byte> asArrayPtr() const;\n\n  size_t size() const;\n\n  // Return a copy of this buffer's data as a kj::Array.\n  kj::Array<kj::byte> copy();\n\n  using JsBase<v8::Uint8Array, JsUint8Array>::JsBase;\n};\n\n// A lightweight wrapper for ArrayBuffer | ArrayBufferView (the Web IDL \"BufferSource\"\n// type). Unlike jsg::BufferSource, this does NOT maintain a BackingStore, does NOT\n// support detach, and is stack-only. Use JsRef<JsBufferSource> for persistent storage.\n//\n// This type is based on v8::Value (not a specific V8 type) because there is no single\n// V8 type that represents both ArrayBuffer and ArrayBufferView. It is NOT included in\n// JS_TYPE_CLASSES; instead, JsValue::tryCast and JsValueWrapper handle it specially.\nclass JsBufferSource final: public JsBase<v8::Value, JsBufferSource> {\n public:\n  JsBufferSource(JsArrayBuffer& buffer): JsBase(static_cast<v8::Local<v8::Value>>(buffer)) {}\n  JsBufferSource(JsUint8Array& buffer): JsBase(static_cast<v8::Local<v8::Value>>(buffer)) {}\n  JsBufferSource(JsArrayBufferView& buffer): JsBase(static_cast<v8::Local<v8::Value>>(buffer)) {}\n\n  kj::ArrayPtr<kj::byte> asArrayPtr();\n\n  size_t size() const;\n\n  // Returns true if the underlying value is an integer-typed TypedArray.\n  bool isIntegerType() const;\n\n  bool isArrayBuffer() const;\n  bool isArrayBufferView() const;\n\n  // Return a copy of this buffer's data as a kj::Array.\n  kj::Array<kj::byte> copy();\n\n  using JsBase<v8::Value, JsBufferSource>::JsBase;\n};\n\nclass JsString final: public JsBase<v8::String, JsString> {\n public:\n  int length(Lock& js) const KJ_WARN_UNUSED_RESULT;\n  size_t utf8Length(Lock& js) const KJ_WARN_UNUSED_RESULT;\n  kj::String toString(Lock& js) const KJ_WARN_UNUSED_RESULT;\n  jsg::USVString toUSVString(Lock& js) const KJ_WARN_UNUSED_RESULT;\n  jsg::DOMString toDOMString(Lock& js) const KJ_WARN_UNUSED_RESULT;\n\n  int hashCode() const;\n\n  bool isFlat() const;\n  bool isOneByte(Lock& js) const KJ_WARN_UNUSED_RESULT;\n  bool containsOnlyOneByte() const;\n\n  bool operator==(const JsString& other) const;\n\n  // \"Internalize\" the string. Returns a string with the same content but which is identity-equal\n  // to all other internalized strings with the same content. If the string is already\n  // internalized, this returns the same value. Note that strings originating from literals in the\n  // code are always internalized.\n  JsString internalize(Lock& js) const;\n\n  static JsString concat(Lock& js, const JsString& one, const JsString& two) KJ_WARN_UNUSED_RESULT;\n\n  enum WriteFlags {\n    NONE = v8::String::WriteFlags::kNone,\n    NULL_TERMINATION = v8::String::WriteFlags::kNullTerminate,\n    REPLACE_INVALID_UTF8 = v8::String::WriteFlags::kReplaceInvalidUtf8,\n  };\n\n  template <typename T>\n  kj::Array<T> toArray(Lock& js, WriteFlags options = WriteFlags::NONE) const KJ_WARN_UNUSED_RESULT;\n\n  struct WriteIntoStatus {\n    // The number of elements (e.g. char, byte, uint16_t) read from this string.\n    size_t read;\n    // The number of elements (e.g. char, byte, uint16_t) written to the buffer.\n    size_t written;\n  };\n\n  // Copy string contents into a provided buffer (off-heap memory).\n  //\n  // IMPORTANT: This method does NOT flatten the V8 string or hold V8 heap locks. It safely\n  // copies data out of V8's heap into your buffer. This makes it safe to use before calling\n  // GC-triggering operations like Lock::allocBackingStore().\n  WriteIntoStatus writeInto(\n      Lock& js, kj::ArrayPtr<char> buffer, WriteFlags options = WriteFlags::NONE) const;\n  WriteIntoStatus writeInto(\n      Lock& js, kj::ArrayPtr<kj::byte> buffer, WriteFlags options = WriteFlags::NONE) const;\n  WriteIntoStatus writeInto(\n      Lock& js, kj::ArrayPtr<uint16_t> buffer, WriteFlags options = WriteFlags::NONE) const;\n\n  using JsBase<v8::String, JsString>::JsBase;\n};\n\nclass JsRegExp final: public JsBase<v8::RegExp, JsRegExp> {\n public:\n  kj::Maybe<JsArray> operator()(Lock& js, const JsString& input) const KJ_WARN_UNUSED_RESULT;\n  kj::Maybe<JsArray> operator()(Lock& js, kj::StringPtr input) const KJ_WARN_UNUSED_RESULT;\n  using JsBase<v8::RegExp, JsRegExp>::JsBase;\n\n  bool match(Lock& js, kj::StringPtr input);\n};\n\nclass JsDate final: public JsBase<v8::Date, JsDate> {\n public:\n  kj::String toUTCString(Lock& js) const;\n  kj::String toISOString(Lock& js) const;\n  operator kj::Date() const;\n  using JsBase<v8::Date, JsDate>::JsBase;\n};\n\n// Note `jsg::JsPromise` and `jsg::Promise` are not the same things.\n//\n// `jsg::JsPromise` wraps an arbitrary `v8::Local<v8::Promise>` to avoid direct use of the V8 API.\n// They have the same restrictions as other `JsValue`s (e.g. can only be stack allocated).\n// `jsg::JsPromise` cannot be awaited in C++. They are opaque references to JavaScript promises.\n//\n// `jsg::Promise<T>` wraps an JavaScript promise to an instantiable C++ type `T` with syntax that\n// makes it natural and ergonomic to consume within C++ (e.g. they provide a `then()` C++ method).\n//\n// You'll usually want to use `jsg::Promise<T>`. `jsg::JsPromise` should only be used when you need\n// direct access to the promise state (e.g. the promise state or its fulfilled value).\nclass JsPromise final: public JsBase<v8::Promise, JsPromise> {\n public:\n  PromiseState state();\n  JsValue result();\n  using JsBase<v8::Promise, JsPromise>::JsBase;\n};\n\nclass JsProxy final: public JsBase<v8::Proxy, JsProxy> {\n public:\n  JsValue target();\n  JsValue handler();\n  using JsBase<v8::Proxy, JsProxy>::JsBase;\n};\n\nclass JsSymbol final: public JsBase<v8::Symbol, JsSymbol> {\n public:\n  kj::String description(Lock& js) const KJ_WARN_UNUSED_RESULT;\n\n  using JsBase<v8::Symbol, JsSymbol>::JsBase;\n};\n\nclass JsSet final: public JsBase<v8::Set, JsSet> {\n public:\n  void add(Lock& js, const JsValue& value);\n  bool has(Lock& js, const JsValue& value) const;\n  bool delete_(Lock& js, const JsValue& value);\n  void clear();\n  size_t size() const;\n\n  template <IsJsValue... Args>\n  void addAll(Lock& js, Args... args) {\n    (check(inner->Add(js.v8Context(), args.inner)), ...);\n  }\n\n  void addAll(Lock& js, kj::ArrayPtr<const JsValue> values);\n\n  operator JsArray() const;\n\n  using JsBase<v8::Set, JsSet>::JsBase;\n};\n\nclass JsBigInt final: public JsBase<v8::BigInt, JsBigInt> {\n public:\n  // If the BigInt value does not fit in int64_t, returns kj::none\n  // and schedules an exception on the isolate.\n  kj::Maybe<int64_t> toInt64(Lock& js) const KJ_WARN_UNUSED_RESULT;\n\n  // If the BigInt value does not fit in int64_t, returns kj::none\n  // and schedules an exception on the isolate.\n  kj::Maybe<uint64_t> toUint64(Lock& js) const KJ_WARN_UNUSED_RESULT;\n\n  using JsBase<v8::BigInt, JsBigInt>::JsBase;\n};\n\nclass JsInt32 final: public JsBase<v8::Int32, JsInt32> {\n public:\n  kj::Maybe<int32_t> value(Lock& js) const KJ_WARN_UNUSED_RESULT;\n\n  using JsBase<v8::Int32, JsInt32>::JsBase;\n};\n\nclass JsUint32 final: public JsBase<v8::Uint32, JsUint32> {\n public:\n  kj::Maybe<uint32_t> value(Lock& js) const KJ_WARN_UNUSED_RESULT;\n\n  using JsBase<v8::Uint32, JsUint32>::JsBase;\n};\n\nclass JsNumber final: public JsBase<v8::Number, JsNumber> {\n public:\n  kj::Maybe<double> value(Lock& js) const KJ_WARN_UNUSED_RESULT;\n  bool isSafeInteger(Lock& js) const KJ_WARN_UNUSED_RESULT;\n  kj::Maybe<double> toSafeInteger(Lock& js) const KJ_WARN_UNUSED_RESULT;\n\n  using JsBase<v8::Number, JsNumber>::JsBase;\n};\n\nclass JsObject final: public JsBase<v8::Object, JsObject> {\n public:\n  template <typename T>\n  bool isInstanceOf(Lock& js) {\n    return js.getInstance(inner, typeid(T)) != kj::none;\n  }\n\n  template <typename T>\n  kj::Maybe<jsg::Ref<T>> tryUnwrapAs(Lock& js) {\n    KJ_IF_SOME(ins, js.getInstance(inner, typeid(T))) {\n      return _jsgThis(static_cast<T*>(&ins));\n    } else {\n      return kj::none;\n    }\n  }\n\n  void set(Lock& js, const JsValue& name, const JsValue& value);\n  void set(Lock& js, kj::StringPtr name, const JsValue& value);\n  void setReadOnly(Lock& js, kj::StringPtr name, const JsValue& value);\n  void setNonEnumerable(Lock& js, const JsSymbol& name, const JsValue& value);\n\n  // Like set but uses the defineProperty API instead in order to override\n  // the default property attributes. This is useful for defining properties\n  // that otherwise would not be normally settable, such as the name of an\n  // error object.\n  void defineProperty(Lock& js, kj::StringPtr name, const JsValue& value);\n\n  JsValue get(Lock& js, const JsValue& name) KJ_WARN_UNUSED_RESULT;\n  JsValue get(Lock& js, kj::StringPtr name) KJ_WARN_UNUSED_RESULT;\n\n  enum class HasOption {\n    NONE,\n    OWN,\n  };\n\n  bool has(Lock& js, const JsValue& name, HasOption option = HasOption::NONE) KJ_WARN_UNUSED_RESULT;\n  bool has(Lock& js, kj::StringPtr name, HasOption option = HasOption::NONE) KJ_WARN_UNUSED_RESULT;\n  void delete_(Lock& js, const JsValue& name);\n  void delete_(Lock& js, kj::StringPtr name);\n\n  void setPrivate(Lock& js, kj::StringPtr name, const JsValue& value);\n  JsValue getPrivate(Lock& js, kj::StringPtr name) KJ_WARN_UNUSED_RESULT;\n  bool hasPrivate(Lock& js, kj::StringPtr name) KJ_WARN_UNUSED_RESULT;\n\n  int hashCode() const;\n\n  kj::String getConstructorName() KJ_WARN_UNUSED_RESULT;\n  JsArray getPropertyNames(Lock& js,\n      KeyCollectionFilter keyFilter,\n      PropertyFilter propertyFilter,\n      IndexFilter indexFilter) KJ_WARN_UNUSED_RESULT;\n  JsArray previewEntries(bool* isKeyValue) KJ_WARN_UNUSED_RESULT;\n\n  // Returns the object's prototype, i.e. the property `__proto__`.\n  //\n  // Note that when called on a class constructor, this does NOT return `.prototype`, it still\n  // returns `.__proto__`. Usefully, though, a class constructor's `__proto__` is always the\n  // parent class's constructor.\n  JsValue getPrototype(Lock& js) KJ_WARN_UNUSED_RESULT;\n\n  using JsBase<v8::Object, JsObject>::JsBase;\n\n  void recursivelyFreeze(Lock&);\n  void seal(Lock&);\n  JsObject jsonClone(Lock&);\n};\n\n// Defined here because `JsObject` is an incomplete type in `jsg.h`.\ntemplate <typename T>\ninline JsObject Lock::getPrototypeFor() {\n  return JsObject(getPrototypeFor(typeid(T)));\n}\n\nclass JsMap final: public JsBase<v8::Map, JsMap> {\n public:\n  operator JsObject();\n\n  void set(Lock& js, const JsValue& name, const JsValue& value);\n  void set(Lock& js, kj::StringPtr name, const JsValue& value);\n  JsValue get(Lock& js, const JsValue& name) KJ_WARN_UNUSED_RESULT;\n  JsValue get(Lock& js, kj::StringPtr name) KJ_WARN_UNUSED_RESULT;\n  bool has(Lock& js, const JsValue& name) KJ_WARN_UNUSED_RESULT;\n  bool has(Lock& js, kj::StringPtr name) KJ_WARN_UNUSED_RESULT;\n  void delete_(Lock& js, const JsValue& name);\n  void delete_(Lock& js, kj::StringPtr name);\n\n  int hashCode() const;\n\n  using JsBase<v8::Map, JsMap>::JsBase;\n};\n\ntemplate <typename T>\ninline kj::Maybe<T> JsValue::tryCast() const {\n  if constexpr (kj::isSameType<T, JsValue>()) {\n    return JsValue(inner);\n  }\n#define V(Name)                                                                                    \\\n  else if constexpr (kj::isSameType<T, Js##Name>()) {                                              \\\n    if (!inner->Is##Name()) return kj::none;                                                       \\\n    return T(inner.template As<v8::Name>());                                                       \\\n  }\n  JS_TYPE_CLASSES(V)\n#undef V\n  // JsBufferSource is not in JS_TYPE_CLASSES because there is no\n  // v8::Value::IsBufferSource() method. Handle it explicitly.\n  else if constexpr (kj::isSameType<T, JsBufferSource>()) {\n    if (!inner->IsArrayBuffer() && !inner->IsArrayBufferView()) return kj::none;\n    return T(inner);\n  }\n  else {\n    return kj::none;\n  }\n}\n\ntemplate <typename T>\ninline kj::Maybe<T&> JsValue::tryGetExternal(Lock& js, const JsValue& value) {\n  if (!value.isExternal()) return kj::none;\n  return kj::Maybe<T&>(\n      *static_cast<T*>(value.inner.As<v8::External>()->Value(v8::kExternalPointerTypeTagDefault)));\n}\n\ntemplate <typename T>\ninline kj::Array<T> JsString::toArray(Lock& js, WriteFlags options) const {\n  if constexpr (kj::isSameType<T, kj::byte>()) {\n    KJ_DASSERT(inner->ContainsOnlyOneByte());\n    auto buf = kj::heapArray<kj::byte>(inner->Length());\n    inner->WriteOneByteV2(js.v8Isolate, 0, buf.size(), buf.begin(), options);\n    return kj::mv(buf);\n  } else {\n    auto buf = kj::heapArray<uint16_t>(inner->Length());\n    inner->WriteV2(js.v8Isolate, 0, buf.size(), buf.begin(), options);\n    return kj::mv(buf);\n  }\n}\n\ntemplate <typename... Args>\n  requires(std::assignable_from<JsValue&, Args> && ...)\ninline JsArray Lock::arr(const Args&... args) {\n  v8::Local<v8::Value> values[] = {args...};\n  return JsArray(v8::Array::New(v8Isolate, &values[0], sizeof...(Args)));\n}\n\ntemplate <typename T, typename Func>\ninline JsArray Lock::arr(kj::ArrayPtr<T> values, Func fn) {\n  v8::LocalVector<v8::Value> vec(v8Isolate);\n  vec.reserve(values.size());\n  for (const T& val: values) {\n    vec.push_back(fn(*this, val));\n  }\n  return JsArray(v8::Array::New(v8Isolate, vec.data(), vec.size()));\n}\n\ntemplate <typename... Args>\n  requires(std::assignable_from<JsValue&, Args> && ...)\ninline JsSet Lock::set(const Args&... args) {\n  auto set = v8::Set::New(v8Isolate);\n  (check(set->Add(v8Context(), args.inner)), ...);\n  return JsSet(set);\n}\n\ntemplate <typename T>\ninline JsObject Lock::opaque(T&& inner) {\n  auto wrapped = wrapOpaque(v8Context(), kj::mv(inner));\n  KJ_ASSERT(!wrapped.IsEmpty());\n  KJ_ASSERT(wrapped->IsObject());\n  return JsObject(wrapped.template As<v8::Object>());\n}\n\nclass JsFunction final: public JsBase<v8::Function, JsFunction> {\n public:\n  using JsBase<v8::Function, JsFunction>::JsBase;\n\n  // Calls the function with the given receiver and arguments.\n  template <IsJsValue... Args>\n  JsValue call(Lock& js, const JsValue& recv, Args... args) const {\n    v8::Local<v8::Function> fn = *this;\n    v8::Local<v8::Value> argv[] = {args...};\n    return JsValue(check(fn->Call(js.v8Context(), recv, sizeof...(Args), argv)));\n  }\n\n  // Calls the function with a null receiver and arguments.\n  template <IsJsValue... Args>\n  JsValue callNoReceiver(Lock& js, Args... args) const {\n    return call(js, js.null(), kj::fwd<Args...>(args...));\n  }\n\n  // Calls the function with the given receiver and arguments.\n  JsValue call(Lock& js, const JsValue& recv, v8::LocalVector<v8::Value>& args) const;\n\n  // Calls the function with a null receiver and arguments. When null is passed\n  // as the receiver, the global object is used instead.\n  JsValue callNoReceiver(Lock& js, v8::LocalVector<v8::Value>& args) const;\n\n  // Gets the function's length property.\n  size_t length(Lock& js) const;\n\n  // Gets the function's name property.\n  JsString name(Lock& js) const;\n\n  // Not guaranteed to be unique, but will be the same for the same function.\n  // Use the JsValue strictEquals() method for true identity comparison.\n  uint hashCode() const;\n\n  operator JsObject() const {\n    return JsObject(inner);\n  }\n};\n\n// A persistent handle for a Js* type suitable for storage and GC visitable.\n//\n// For example,\n//\n//   class Foo : public jsg::Object {\n//   public:\n//     void setStored(jsg::Lock& js, jsg::JsValue value) {\n//       stored = value.addRef(js);\n//     }\n//     JsValue getStored(jsg::Lock& js) {\n//       return stored.getHandle(js);\n//     }\n//     JSG_RESOURCE_TYPE(Foo) {\n//       JSG_PROTOTYPE_PROPERTY(stored, getStored, setStored);\n//     }\n//   private:\n//     jsg::JsRef<JsValue> stored;\n//\n//     void visitForGc(GcVisitor& visitor) { visitor.visit(stored); }\n//   };\ntemplate <typename T>\nclass JsRef final {\n  static_assert(std::is_assignable_v<JsValue, T>, \"JsRef<T>, T must be assignable to type JsValue\");\n\n public:\n  JsRef(): JsRef(nullptr) {}\n  JsRef(decltype(nullptr)): value(nullptr) {}\n  JsRef(Lock& js, const T& value): value(js.v8Isolate, value.inner) {}\n  JsRef(JsRef<T>& other) = delete;\n  JsRef(JsRef<T>&& other) = default;\n  template <typename U>\n  JsRef(Lock& js, V8Ref<U>&& v8Value)\n      : value(js.v8Isolate, v8Value.getHandle(js).template As<v8::Value>()) {}\n  JsRef& operator=(JsRef<T>& other) = delete;\n  JsRef& operator=(JsRef<T>&& other) = default;\n\n  T getHandle(Lock& js) const KJ_WARN_UNUSED_RESULT {\n    JsValue handle(value.getHandle(js));\n    return KJ_ASSERT_NONNULL(handle.tryCast<T>());\n  }\n\n  JsRef<T> addRef(Lock& js) KJ_WARN_UNUSED_RESULT {\n    return JsRef<T>(js, getHandle(js));\n  }\n\n  bool operator==(const JsRef<T>& other) {\n    return value == other.value;\n  }\n\n  void visitForGc(GcVisitor& visitor) {\n    visitor.visit(value);\n  }\n\n  // Supported only to allow for an easier transition for code that still\n  // requires V8Ref types.\n  template <typename U>\n  V8Ref<U> addV8Ref(Lock& js) KJ_WARN_UNUSED_RESULT {\n    return value.addRef(js);\n  }\n\n  // Supported only to allow for an easier transition for code that still\n  // requires V8Ref types.\n  template <typename U>\n  operator V8Ref<U>() && {\n    return kj::mv(value).template cast<U>(jsg::Lock::current());\n  }\n\n  JSG_MEMORY_INFO(JsRef) {\n    tracker.trackField(\"value\", value);\n  }\n\n private:\n  Value value;\n  friend class JsValue;\n#define V(Name) friend class Js##Name;\n  JS_TYPE_CLASSES(V)\n#undef V\n\n  friend class MemoryTracker;\n};\n\ntemplate <typename T, typename Self>\ninline JsRef<Self> JsBase<T, Self>::addRef(Lock& js) {\n  return JsRef<Self>(js, *static_cast<Self*>(this));\n}\n\ninline kj::String KJ_STRINGIFY(const JsValue& value) {\n  return value.toString(jsg::Lock::current());\n}\n\ntemplate <typename T>\nconcept JsValueType = std::is_assignable_v<JsValue, T>;\n\nstruct JsValueWrapper {\n#define TYPES_TO_WRAP(V)                                                                           \\\n  V(Value)                                                                                         \\\n  JS_TYPE_CLASSES(V)\n\n  template <JsValueType T>\n  static constexpr const std::type_info& getName(T*) {\n    return typeid(T);\n  }\n\n  template <JsValueType T>\n  static constexpr const std::type_info& getName(JsRef<T>*) {\n    return typeid(T);\n  }\n\n#define V(Name)                                                                                    \\\n  v8::Local<v8::Name> wrap(jsg::Lock& js, v8::Local<v8::Context> context,                          \\\n      kj::Maybe<v8::Local<v8::Object>> creator, Js##Name value) {                                  \\\n    return value;                                                                                  \\\n  }                                                                                                \\\n  v8::Local<v8::Name> wrap(jsg::Lock& js, v8::Local<v8::Context> context,                          \\\n      kj::Maybe<v8::Local<v8::Object>> creator, JsRef<Js##Name> value) {                           \\\n    return value.getHandle(js);                                                                    \\\n  }\n\n  TYPES_TO_WRAP(V)\n#undef V\n\n  // Manual wrap overloads for JsBufferSource which is not in JS_TYPE_CLASSES.\n  // The underlying V8 type is v8::Value since BufferSource spans both\n  // ArrayBuffer and ArrayBufferView.\n  v8::Local<v8::Value> wrap(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      JsBufferSource value) {\n    return value;\n  }\n  v8::Local<v8::Value> wrap(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      JsRef<JsBufferSource> value) {\n    return value.getHandle(js);\n  }\n\n  template <JsValueType T>\n  kj::Maybe<T> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      T*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if constexpr (kj::isSameType<T, JsString>()) {\n      return T(check(handle->ToString(context)));\n    } else if constexpr (kj::isSameType<T, JsBoolean>()) {\n      return T(handle->ToBoolean(js.v8Isolate));\n    } else if constexpr (kj::isSameType<T, JsNumber>()) {\n      return T(check(handle->ToNumber(context)));\n    } else {\n      JsValue value(handle);\n      KJ_IF_SOME(t, value.tryCast<T>()) {\n        return t;\n      }\n      return kj::none;\n    }\n  }\n\n  template <JsValueType T>\n  kj::Maybe<JsRef<T>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      JsRef<T>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    KJ_IF_SOME(result, tryUnwrap(js, context, handle, (T*)nullptr, parentObject)) {\n      return JsRef(js, result);\n    }\n    return kj::none;\n  }\n};\n\nclass JsMessage final {\n public:\n  static JsMessage create(Lock& js, const JsValue& exception);\n  explicit inline JsMessage() {\n    requireOnStack(this);\n  }\n  explicit inline JsMessage(v8::Local<v8::Message> inner): inner(inner) {\n    requireOnStack(this);\n  }\n  operator v8::Local<v8::Message>() const {\n    return inner;\n  }\n\n  // Is it possible for the underlying v8::Local<v8::Message> to be\n  // empty, in which case the bool() operator will return false.\n  operator bool() const {\n    return !inner.IsEmpty();\n  }\n\n  // Adds the JS Stack associated with this JsMessage to the given\n  // kj::Vector.\n  void addJsStackTrace(Lock& js, kj::Vector<kj::String>& lines);\n\n private:\n  v8::Local<v8::Message> inner;\n};\n\ninline JsObject Lock::global() {\n  return JsObject(v8Context()->Global());\n}\n\ninline JsValue Lock::undefined() {\n  return JsValue(v8::Undefined(v8Isolate));\n}\n\ninline JsValue Lock::null() {\n  return JsValue(v8::Null(v8Isolate));\n}\n\ninline JsBoolean Lock::boolean(bool val) {\n  return JsBoolean(v8::Boolean::New(v8Isolate, val));\n}\n\ninline JsNumber Lock::num(double val) {\n  return JsNumber(v8::Number::New(v8Isolate, val));\n}\n\ninline JsNumber Lock::num(float val) {\n  return JsNumber(v8::Number::New(v8Isolate, val));\n}\n\ninline JsInt32 Lock::num(int8_t val) {\n  return JsInt32(v8::Integer::New(v8Isolate, val).As<v8::Int32>());\n}\n\ninline JsInt32 Lock::num(int16_t val) {\n  return JsInt32(v8::Integer::New(v8Isolate, val).As<v8::Int32>());\n}\n\ninline JsInt32 Lock::num(int32_t val) {\n  return JsInt32(v8::Integer::New(v8Isolate, val).As<v8::Int32>());\n}\n\ninline JsBigInt Lock::bigInt(int64_t val) {\n  return JsBigInt(v8::BigInt::New(v8Isolate, val));\n}\n\ninline JsUint32 Lock::num(uint8_t val) {\n  return JsUint32(v8::Integer::NewFromUnsigned(v8Isolate, val).As<v8::Uint32>());\n}\n\ninline JsUint32 Lock::num(uint16_t val) {\n  return JsUint32(v8::Integer::NewFromUnsigned(v8Isolate, val).As<v8::Uint32>());\n}\n\ninline JsUint32 Lock::num(uint32_t val) {\n  return JsUint32(v8::Integer::NewFromUnsigned(v8Isolate, val).As<v8::Uint32>());\n}\n\ninline JsBigInt Lock::bigInt(uint64_t val) {\n  return JsBigInt(v8::BigInt::NewFromUnsigned(v8Isolate, val));\n}\n\ninline JsString Lock::str() {\n  return JsString(v8::String::Empty(v8Isolate));\n}\n\ninline JsString Lock::str(kj::ArrayPtr<const char16_t> str) {\n  return JsString(check(v8::String::NewFromTwoByte(v8Isolate,\n      reinterpret_cast<const uint16_t*>(str.begin()), v8::NewStringType::kNormal, str.size())));\n}\n\ninline JsString Lock::str(kj::ArrayPtr<const uint16_t> str) {\n  return JsString(check(\n      v8::String::NewFromTwoByte(v8Isolate, str.begin(), v8::NewStringType::kNormal, str.size())));\n}\n\ninline JsString Lock::str(kj::ArrayPtr<const char> str) {\n  return JsString(check(\n      v8::String::NewFromUtf8(v8Isolate, str.begin(), v8::NewStringType::kNormal, str.size())));\n}\n\ninline JsString Lock::str(kj::ArrayPtr<const kj::byte> str) {\n  return JsString(check(\n      v8::String::NewFromOneByte(v8Isolate, str.begin(), v8::NewStringType::kNormal, str.size())));\n}\n\ninline JsString Lock::strIntern(kj::StringPtr str) {\n  return JsString(check(v8::String::NewFromUtf8(\n      v8Isolate, str.begin(), v8::NewStringType::kInternalized, str.size())));\n}\n\ninline JsString Lock::strExtern(kj::ArrayPtr<const char> str) {\n  return JsString(newExternalOneByteString(*this, str));\n}\n\ninline JsString Lock::strExtern(kj::ArrayPtr<const uint16_t> str) {\n  return JsString(newExternalTwoByteString(*this, str));\n}\n\ninline JsObject Lock::obj() {\n  return JsObject(v8::Object::New(v8Isolate));\n}\n\ninline JsObject Lock::objNoProto() {\n  return JsObject(v8::Object::New(v8Isolate, v8::Null(v8Isolate), nullptr, nullptr, 0));\n}\n\ninline JsMap Lock::map() {\n  return JsMap(v8::Map::New(v8Isolate));\n}\n\ninline JsValue Lock::external(void* ptr) {\n  return JsValue(v8::External::New(v8Isolate, ptr, v8::kExternalPointerTypeTagDefault));\n}\n\ninline JsValue Lock::error(kj::StringPtr message) {\n  return JsValue(v8::Exception::Error(v8Str(v8Isolate, message)));\n}\n\ninline JsValue Lock::typeError(kj::StringPtr message) {\n  return JsValue(v8::Exception::TypeError(v8Str(v8Isolate, message)));\n}\n\ninline JsValue Lock::rangeError(kj::StringPtr message) {\n  return JsValue(v8::Exception::RangeError(v8Str(v8Isolate, message)));\n}\n\ninline JsSymbol Lock::symbol(kj::StringPtr str) {\n  return JsSymbol(v8::Symbol::New(v8Isolate, v8StrIntern(v8Isolate, str)));\n}\n\ninline JsSymbol Lock::symbolShared(kj::StringPtr str) {\n  return JsSymbol(v8::Symbol::For(v8Isolate, v8StrIntern(v8Isolate, str)));\n}\n\ninline JsSymbol Lock::symbolInternal(kj::StringPtr str) {\n  return JsSymbol(v8::Symbol::ForApi(v8Isolate, v8StrIntern(v8Isolate, str)));\n}\n\ninline JsDate Lock::date(double timestamp) {\n  return JsDate(check(v8::Date::New(v8Context(), timestamp)).As<v8::Date>());\n}\n\ninline JsDate Lock::date(kj::Date date) {\n  return JsDate(jsg::check(v8::Date::New(v8Context(), (date - kj::UNIX_EPOCH) / kj::MILLISECONDS))\n                    .As<v8::Date>());\n}\n\ninline void JsObject::set(Lock& js, const JsValue& name, const JsValue& value) {\n  check(inner->Set(js.v8Context(), name.inner, value.inner));\n}\n\ninline void JsObject::set(Lock& js, kj::StringPtr name, const JsValue& value) {\n  set(js, js.strIntern(name), value);\n}\n\ninline JsValue JsObject::get(Lock& js, const JsValue& name) {\n  return JsValue(check(inner->Get(js.v8Context(), name.inner)));\n}\n\ninline JsValue JsObject::get(Lock& js, kj::StringPtr name) {\n  return get(js, js.strIntern(name));\n}\n\ninline bool JsObject::has(Lock& js, const JsValue& name, HasOption option) {\n  if (option == HasOption::OWN) {\n    KJ_ASSERT(name.inner->IsName());\n    return check(inner->HasOwnProperty(js.v8Context(), name.inner.As<v8::Name>()));\n  } else {\n    return check(inner->Has(js.v8Context(), name.inner));\n  }\n}\n\ninline bool JsObject::has(Lock& js, kj::StringPtr name, HasOption option) {\n  return has(js, js.strIntern(name), option);\n}\n\ninline void JsObject::delete_(Lock& js, const JsValue& name) {\n  check(inner->Delete(js.v8Context(), name.inner));\n}\n\ninline void JsObject::delete_(Lock& js, kj::StringPtr name) {\n  delete_(js, js.strIntern(name));\n}\n\ninline int JsString::length(jsg::Lock& js) const {\n  return inner->Length();\n}\n\ninline bool JsString::isOneByte(jsg::Lock& js) const {\n  return inner->IsOneByte();\n}\n\ninline size_t JsString::utf8Length(jsg::Lock& js) const {\n  return inner->Utf8LengthV2(js.v8Isolate);\n}\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/macro-meta-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"macro-meta.h\"\n\n#include <kj/test.h>\n\nnamespace workerd::jsg {\nnamespace {\n\nstatic_assert(JSG_IF_NONEMPTY(abcd, 123) == 123);\nstatic_assert(JSG_IF_NONEMPTY(, abcd) true);\n\ntemplate <typename... T>\nconstexpr int sum(T... args) {\n  return (args + ...);\n}\n\n#define JSG_TEST_FOR_EACH_OP(op, x) (2 op x)\nstatic_assert(JSG_FOR_EACH(JSG_TEST_FOR_EACH_OP, *) true);\nstatic_assert(sum(JSG_FOR_EACH(JSG_TEST_FOR_EACH_OP, *, 1, 2, 3, 4)) == 20);\n\nstatic_assert(sum(JSG_FOR_EACH(JSG_TEST_FOR_EACH_OP,\n                                   *,\n                  1,\n                  2,\n                  3,\n                  4,\n                  5,\n                  6,\n                  7,\n                  8,\n                  1,\n                  2,\n                  3,\n                  4,\n                  5,\n                  6,\n                  7,\n                  8,\n                  1,\n                  2,\n                  3,\n                  4,\n                  5,\n                  6,\n                  7,\n                  8,\n                  1,\n                  2,\n                  3,\n                  4,\n                  5,\n                  6,\n                  7,\n                  8)) == 36 * 2 * 4);\n#undef JSG_TEST_FOR_EACH_OP\n\nKJ_TEST(\"macro meta\") {\n  // Nothing to actually do here; tests are compile-time\n}\n\n}  // namespace\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/macro-meta.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// INTERNAL IMPLEMENTATION FILE\n//\n// Some macro metaprogramming helpers.\n\n// =======================================================================================\n// TODO(cleanup): Move these macros to libkj.\n\n#define JSG_STRING_LITERAL_(...) #__VA_ARGS__\n#define JSG_STRING_LITERAL(...) JSG_STRING_LITERAL_(__VA_ARGS__)\n// JSG_STRING_LITERAL(foo, bar) expands to the string literal: \"foo, bar\"\n\n#define JSG_EXPAND(...) __VA_ARGS__\n// Identity macro. Often useful in macro hacking.\n\n#define JSG_IF_NONEMPTY_(                                                                          \\\n    dummy, part1, part2, part3, part4, part5, part6, part7, part8, result, ...)                    \\\n  JSG_EXPAND result\n#define JSG_IF_NONEMPTY_2_(...) dummy, ##__VA_ARGS__\n#define JSG_IF_NONEMPTY_3_(...) JSG_IF_NONEMPTY_(__VA_ARGS__)\n#define JSG_IF_NONEMPTY(arg, ...)                                                                  \\\n  JSG_IF_NONEMPTY_3_(JSG_IF_NONEMPTY_2_(arg), (__VA_ARGS__), (__VA_ARGS__), (__VA_ARGS__),         \\\n      (__VA_ARGS__), (__VA_ARGS__), (__VA_ARGS__), (__VA_ARGS__), (__VA_ARGS__), ())\n\n#define JSG_CAT(a, b) a##b\n// Paste two preprocessor tokens together. Useful in macro hacking.\n\n// If the first argument is empty, expands to nothing.\n//\n// If the first argument is not empty, expands to the remaining arguments.\n//\n// So:\n//    JSG_IF_NONEMPTY(, foo, bar) ->\n//    JSG_IF_NONEMPTY(x, foo, bar) -> foo, bar\n//\n// (We support multiple \"arguments\" because often the output needs to contain commas, e.g. because\n// it's a template type and the preprocessor doesn't recognize <...> as a grouping.)\n\n#define JSG_FOR_EACH_(op, param, A1, A2, A3, A4, A5, A6, A7, A8, B1, B2, B3, B4, B5, B6, B7, B8,   \\\n    C1, C2, C3, C4, C5, C6, C7, C8, D1, D2, D3, D4, D5, D6, D7, D8, E1, E2, E3, E4, E5, E6, E7,    \\\n    E8, F1, F2, F3, F4, F5, F6, F7, F8, sentinel, ...)                                             \\\n  JSG_IF_NONEMPTY(A1, op(param, A1))                                                               \\\n  JSG_IF_NONEMPTY(A2, , op(param, A2))                                                             \\\n  JSG_IF_NONEMPTY(A3, , op(param, A3))                                                             \\\n  JSG_IF_NONEMPTY(A4, , op(param, A4))                                                             \\\n  JSG_IF_NONEMPTY(A5, , op(param, A5))                                                             \\\n  JSG_IF_NONEMPTY(A6, , op(param, A6))                                                             \\\n  JSG_IF_NONEMPTY(A7, , op(param, A7))                                                             \\\n  JSG_IF_NONEMPTY(A8, , op(param, A8))                                                             \\\n  JSG_IF_NONEMPTY(B1, , op(param, B1))                                                             \\\n  JSG_IF_NONEMPTY(B2, , op(param, B2))                                                             \\\n  JSG_IF_NONEMPTY(B3, , op(param, B3))                                                             \\\n  JSG_IF_NONEMPTY(B4, , op(param, B4))                                                             \\\n  JSG_IF_NONEMPTY(B5, , op(param, B5))                                                             \\\n  JSG_IF_NONEMPTY(B6, , op(param, B6))                                                             \\\n  JSG_IF_NONEMPTY(B7, , op(param, B7))                                                             \\\n  JSG_IF_NONEMPTY(B8, , op(param, B8))                                                             \\\n  JSG_IF_NONEMPTY(C1, , op(param, C1))                                                             \\\n  JSG_IF_NONEMPTY(C2, , op(param, C2))                                                             \\\n  JSG_IF_NONEMPTY(C3, , op(param, C3))                                                             \\\n  JSG_IF_NONEMPTY(C4, , op(param, C4))                                                             \\\n  JSG_IF_NONEMPTY(C5, , op(param, C5))                                                             \\\n  JSG_IF_NONEMPTY(C6, , op(param, C6))                                                             \\\n  JSG_IF_NONEMPTY(C7, , op(param, C7))                                                             \\\n  JSG_IF_NONEMPTY(C8, , op(param, C8))                                                             \\\n  JSG_IF_NONEMPTY(D1, , op(param, D1))                                                             \\\n  JSG_IF_NONEMPTY(D2, , op(param, D2))                                                             \\\n  JSG_IF_NONEMPTY(D3, , op(param, D3))                                                             \\\n  JSG_IF_NONEMPTY(D4, , op(param, D4))                                                             \\\n  JSG_IF_NONEMPTY(D5, , op(param, D5))                                                             \\\n  JSG_IF_NONEMPTY(D6, , op(param, D6))                                                             \\\n  JSG_IF_NONEMPTY(D7, , op(param, D7))                                                             \\\n  JSG_IF_NONEMPTY(D8, , op(param, D8))                                                             \\\n  JSG_IF_NONEMPTY(E1, , op(param, E1))                                                             \\\n  JSG_IF_NONEMPTY(E2, , op(param, E2))                                                             \\\n  JSG_IF_NONEMPTY(E3, , op(param, E3))                                                             \\\n  JSG_IF_NONEMPTY(E4, , op(param, E4))                                                             \\\n  JSG_IF_NONEMPTY(E5, , op(param, E5))                                                             \\\n  JSG_IF_NONEMPTY(E6, , op(param, E6))                                                             \\\n  JSG_IF_NONEMPTY(E7, , op(param, E7))                                                             \\\n  JSG_IF_NONEMPTY(E8, , op(param, E8))                                                             \\\n  JSG_IF_NONEMPTY(F1, , op(param, F1))                                                             \\\n  JSG_IF_NONEMPTY(F2, , op(param, F2))                                                             \\\n  JSG_IF_NONEMPTY(F3, , op(param, F3))                                                             \\\n  JSG_IF_NONEMPTY(F4, , op(param, F4))                                                             \\\n  JSG_IF_NONEMPTY(F5, , op(param, F5))                                                             \\\n  JSG_IF_NONEMPTY(F6, , op(param, F6))                                                             \\\n  JSG_IF_NONEMPTY(F7, , op(param, F7))                                                             \\\n  JSG_IF_NONEMPTY(F8, , op(param, F8))                                                             \\\n  JSG_IF_NONEMPTY(sentinel, error_JSG_FOR_EACH_only_supports_48_parameters)\n\n#define JSG_FOR_EACH(op, param, ...)                                                               \\\n  JSG_FOR_EACH_(op, param, ##__VA_ARGS__, , , , , , , , , , , , , , , , , , , , , , , , , , , , ,  \\\n      , , , , , , , , , , , , , , , , , , , , )\n// JSG_FOR_EACH(op, param, A, B, C, ...) expands to: op(param, A), op(param, B), op(param, C) ...\n//\n// Currently only supports up to 48 params.\n"
  },
  {
    "path": "src/workerd/jsg/memory-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/jsg/memory.h>\n#include <workerd/jsg/setup.h>\n#include <workerd/jsg/type-wrapper.h>\n\n#include <v8-profiler.h>\n\n#include <kj/map.h>\n#include <kj/test.h>\n\nnamespace workerd::jsg::test {\nnamespace {\n\nV8System v8System;\nclass ContextGlobalObject: public Object, public ContextGlobal {};\n\nstruct Foo: public Object {\n  kj::String bar = kj::str(\"test\");\n\n  JSG_RESOURCE_TYPE(Foo) {}\n  void visitForMemoryInfo(MemoryTracker& tracker) const {\n    tracker.trackField(\"bar\", bar);\n  }\n};\n\nstruct MemoryTrackerContext: public ContextGlobalObject {\n  JSG_RESOURCE_TYPE(MemoryTrackerContext) {}\n};\nJSG_DECLARE_ISOLATE_TYPE(MemoryTrackerIsolate, MemoryTrackerContext, Foo);\n\nvoid runTest(auto callback) {\n  MemoryTrackerIsolate isolate(v8System, kj::heap<jsg::IsolateObserver>());\n  isolate.runInLockScope([&](MemoryTrackerIsolate::Lock& lock) {\n    JSG_WITHIN_CONTEXT_SCOPE(lock, lock.newContext<MemoryTrackerContext>().getHandle(lock),\n        [&](jsg::Lock& js) { callback(js, lock.getTypeHandler<Ref<Foo>>()); });\n  });\n}\n\nKJ_TEST(\"MemoryTracker test\") {\n  // Verifies that workerd details are included in the heapsnapshot.\n  // This is not a comprehensive test of the heapsnapshot content,\n  // it is designed just to make sure that we are, in fact, publishing\n  // internal details to the snapshot.\n\n  runTest([&](jsg::Lock& js, const TypeHandler<Ref<Foo>>& fooHandler) {\n    kj::Vector<char> serialized;\n    HeapSnapshotActivity activity([](auto, auto) { return true; });\n    HeapSnapshotWriter writer([&](kj::Maybe<kj::ArrayPtr<char>> maybeChunk) {\n      KJ_IF_SOME(chunk, maybeChunk) {\n        serialized.addAll(chunk);\n      }\n      return true;\n    });\n\n    auto foo = fooHandler.wrap(js, js.alloc<Foo>());\n    KJ_ASSERT(foo->IsObject());\n\n    auto profiler = js.v8Isolate->GetHeapProfiler();\n\n    HeapSnapshotDeleter deleter;\n\n    auto snapshot = kj::Own<const v8::HeapSnapshot>(\n        profiler->TakeHeapSnapshot(&activity, nullptr, true, true), deleter);\n    snapshot->Serialize(&writer, v8::HeapSnapshot::kJSON);\n\n    auto parsed = js.parseJson(serialized.asPtr());\n    JsValue value = JsValue(parsed.getHandle(js));\n    KJ_ASSERT(value.isObject());\n\n    JsObject obj = KJ_ASSERT_NONNULL(value.tryCast<JsObject>());\n\n    auto strings = obj.get(js, \"strings\");\n    KJ_ASSERT(strings.isArray());\n\n    JsArray array = KJ_ASSERT_NONNULL(strings.tryCast<JsArray>());\n\n    size_t count = 0;\n\n    kj::HashSet<kj::String> checks;\n    checks.insert(kj::str(\"workerd / IsolateBase\"));\n    checks.insert(kj::str(\"workerd / kj::String\"));\n    checks.insert(kj::str(\"workerd / HeapTracer\"));\n    checks.insert(kj::str(\"workerd / CppgcShim\"));\n    checks.insert(kj::str(\"workerd / MemoryTrackerContext\"));\n    checks.insert(kj::str(\"workerd / Foo\"));\n\n    // Find what we're looking for... this is slow but, you know\n    for (size_t n = 0; n < array.size(); n++) {\n      JsValue check = array.get(js, n);\n      auto str = check.toString(js);\n      if (str.startsWith(\"workerd /\")) {\n        count++;\n        KJ_ASSERT(checks.find(str) != kj::none);\n      }\n    }\n    KJ_ASSERT(count == checks.size());\n  });\n}\n\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/memory.c++",
    "content": "#include \"memory.h\"\n\n#include <v8-array-buffer.h>\n\n#include <kj/one-of.h>\n\nnamespace workerd::jsg {\n\nclass MemoryRetainerNode final: public v8::EmbedderGraph::Node {\n public:\n  static constexpr auto PREFIX = \"workerd /\";\n\n  const char* Name() override {\n    return name_.cStr();\n  }\n\n  const char* NamePrefix() override {\n    return PREFIX;\n  }\n\n  size_t SizeInBytes() override {\n    return size_;\n  }\n\n  bool IsRootNode() override {\n    KJ_SWITCH_ONEOF(isRootNode) {\n      KJ_CASE_ONEOF(b, bool) {\n        return b;\n      }\n      KJ_CASE_ONEOF(fn, kj::Function<bool()>) {\n        return fn();\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  v8::EmbedderGraph::Node::Detachedness GetDetachedness() override {\n    return detachedness_;\n  }\n\n  inline Node* JSWrapperNode() {\n    return wrapper_node_;\n  }\n\n  KJ_DISALLOW_COPY_AND_MOVE(MemoryRetainerNode);\n  ~MemoryRetainerNode() noexcept(true) {}\n\n private:\n  static v8::EmbedderGraph::Node::Detachedness fromDetachedState(MemoryInfoDetachedState state) {\n    switch (state) {\n      case MemoryInfoDetachedState::UNKNOWN:\n        return v8::EmbedderGraph::Node::Detachedness::kUnknown;\n      case MemoryInfoDetachedState::ATTACHED:\n        return v8::EmbedderGraph::Node::Detachedness::kAttached;\n      case MemoryInfoDetachedState::DETACHED:\n        return v8::EmbedderGraph::Node::Detachedness::kDetached;\n    }\n    KJ_UNREACHABLE;\n  }\n\n  static v8::EmbedderGraph::Node* maybeWrapperNode(\n      MemoryTracker* tracker, v8::Local<v8::Object> obj) {\n    if (!obj.IsEmpty()) return tracker->graph_->V8Node(obj.As<v8::Value>());\n    return nullptr;\n  }\n\n  MemoryRetainerNode(MemoryTracker* tracker,\n      const void* retainer,\n      const kj::StringPtr name,\n      const size_t size,\n      v8::Local<v8::Object> obj,\n      kj::Maybe<kj::Function<bool()>> checkIsRootNode,\n      MemoryInfoDetachedState detachedness)\n      : name_(name),\n        size_(size),\n        wrapper_node_(maybeWrapperNode(tracker, obj)),\n        detachedness_(fromDetachedState(detachedness)) {\n    KJ_IF_SOME(fn, checkIsRootNode) {\n      isRootNode = kj::mv(fn);\n    }\n  }\n\n  MemoryRetainerNode(\n      MemoryTracker* tracker, kj::StringPtr name, size_t size, bool isRootNode = false)\n      : name_(name),\n        size_(size),\n        isRootNode(isRootNode) {}\n\n  kj::StringPtr name_;\n  size_t size_ = 0;\n  v8::EmbedderGraph::Node* wrapper_node_ = nullptr;\n\n  kj::OneOf<bool, kj::Function<bool()>> isRootNode = false;\n\n  v8::EmbedderGraph::Node::Detachedness detachedness_ =\n      v8::EmbedderGraph::Node::Detachedness::kUnknown;\n\n  friend class MemoryTracker;\n};\n\nnamespace {\nkj::Maybe<MemoryRetainerNode&> getCurrentNode(const std::stack<MemoryRetainerNode*> stack) {\n  if (stack.empty()) return kj::none;\n  return *stack.top();\n}\n}  // namespace\n\nMemoryTracker::MemoryTracker(v8::Isolate* isolate, v8::EmbedderGraph* graph)\n    : isolate_(isolate),\n      graph_(graph) {}\n\nMemoryRetainerNode* MemoryTracker::addNode(const void* retainer,\n    const kj::StringPtr name,\n    const size_t size,\n    v8::Local<v8::Object> obj,\n    kj::Maybe<kj::Function<bool()>> checkIsRootNode,\n    MemoryInfoDetachedState detachedness,\n    kj::Maybe<kj::StringPtr> edgeName) {\n  KJ_IF_SOME(found, seen_.find(retainer)) {\n    return found;\n  }\n\n  MemoryRetainerNode* n = new MemoryRetainerNode(\n      this, retainer, name, size, obj, kj::mv(checkIsRootNode), detachedness);\n  graph_->AddNode(std::unique_ptr<v8::EmbedderGraph::Node>(n));\n  seen_.insert(retainer, n);\n\n  KJ_IF_SOME(currentNode, getCurrentNode(nodeStack_)) {\n    KJ_IF_SOME(name, edgeName) {\n      graph_->AddEdge(&currentNode, n, name.cStr());\n    } else {\n      graph_->AddEdge(&currentNode, n, nullptr);\n    }\n  }\n\n  if (n->JSWrapperNode() != nullptr) {\n    graph_->AddEdge(n, n->JSWrapperNode(), \"native_to_javascript\");\n    graph_->AddEdge(n->JSWrapperNode(), n, \"javascript_to_native\");\n  }\n\n  return n;\n}\n\nMemoryRetainerNode* MemoryTracker::addNode(\n    kj::StringPtr nodeName, size_t size, kj::Maybe<kj::StringPtr> edgeName) {\n  MemoryRetainerNode* n = new MemoryRetainerNode(this, nodeName, size);\n  graph_->AddNode(std::unique_ptr<v8::EmbedderGraph::Node>(n));\n\n  KJ_IF_SOME(currentNode, getCurrentNode(nodeStack_)) {\n    KJ_IF_SOME(name, edgeName) {\n      graph_->AddEdge(&currentNode, n, name.cStr());\n    } else {\n      graph_->AddEdge(&currentNode, n, nullptr);\n    }\n  }\n\n  return n;\n}\n\nMemoryRetainerNode* MemoryTracker::pushNode(\n    kj::StringPtr nodeName, size_t size, kj::Maybe<kj::StringPtr> edgeName) {\n  MemoryRetainerNode* n = addNode(nodeName, size, edgeName);\n  nodeStack_.push(n);\n  return n;\n}\n\nvoid MemoryTracker::decCurrentNodeSize(size_t size) {\n  KJ_IF_SOME(currentNode, getCurrentNode(nodeStack_)) {\n    currentNode.size_ -= size;\n  }\n}\n\nvoid MemoryTracker::addEdge(v8::EmbedderGraph::Node* node, kj::StringPtr edgeName) {\n  KJ_IF_SOME(currentNode, getCurrentNode(nodeStack_)) {\n    graph_->AddEdge(&currentNode, node, edgeName.cStr());\n  } else {\n    graph_->AddEdge(nullptr, node, edgeName.cStr());\n  }\n}\n\nvoid MemoryTracker::addEdge(MemoryRetainerNode* node, kj::StringPtr edgeName) {\n  KJ_IF_SOME(currentNode, getCurrentNode(nodeStack_)) {\n    graph_->AddEdge(&currentNode, node, edgeName.cStr());\n  } else {\n    graph_->AddEdge(nullptr, node, edgeName.cStr());\n  }\n}\n\n// ======================================================================================\n\nHeapSnapshotActivity::HeapSnapshotActivity(Callback callback): callback(kj::mv(callback)) {}\n\nv8::ActivityControl::ControlOption HeapSnapshotActivity::ReportProgressValue(\n    uint32_t done, uint32_t total) {\n  return callback(done, total) ? ControlOption::kContinue : ControlOption::kAbort;\n}\n\nHeapSnapshotWriter::HeapSnapshotWriter(Callback callback, size_t chunkSize)\n    : callback(kj::mv(callback)),\n      chunkSize(chunkSize) {}\n\nvoid HeapSnapshotWriter::EndOfStream() {\n  callback(kj::none);\n}\n\nint HeapSnapshotWriter::GetChunkSize() {\n  return chunkSize;\n}\n\nv8::OutputStream::WriteResult HeapSnapshotWriter::WriteAsciiChunk(char* data, int size) {\n  return callback(kj::ArrayPtr<char>(data, size)) ? v8::OutputStream::WriteResult::kContinue\n                                                  : v8::OutputStream::WriteResult::kAbort;\n}\n\nvoid MemoryTracker::trackField(\n    kj::StringPtr edgeName, const v8::BackingStore* value, kj::Maybe<kj::StringPtr> nodeName) {\n  trackFieldWithSize(edgeName, value->ByteLength(), \"BackingStore\"_kjc);\n}\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/memory.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Implements mechanism for incorporating details about the native (c++) objects\n// in a v8 heap snapshot. The design of the API and implementation were heavily\n// influenced by Node.js' implementation of the same feature.\n\n#pragma once\n\n#include <v8-profiler.h>\n\n#include <kj/common.h>\n#include <kj/debug.h>\n#include <kj/exception.h>\n#include <kj/hash.h>\n#include <kj/map.h>\n#include <kj/string.h>\n#include <kj/table.h>\n\n#include <stack>\n#include <string>\n\nnamespace v8 {\nclass BackingStore;\n}\n\nnamespace workerd::jsg {\n\n// The MemoryTracker is used to integrate with v8's BuildEmbedderGraph API.\n// It constructs the graph of embedder objects to be included in a generated\n// heap snapshot.\n//\n// The API is implemented using a visitor pattern. V8 calls the BuilderEmbedderGraph\n// callback (in setup.h) which in turn begins walking through the known embedder\n// objects collecting the necessary information.\n//\n// To instrument a struct or class so that it can be included in the graph, the\n// type must implement *at least* the following three methods:\n//\n//   kj::StringPtr jsgGetMemoryName() const;\n//   size_t jsgGetMemorySelfSize() const;\n//   void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const;\n//\n// The jsgGetMemoryName() method returns the name that should be used to identify\n// the type in the graph. This will be prefixed with \"workerd / \" in the actual\n// generated snapshot. For instance, if this method returns \"Foo\"_kjc, the heap\n// snapshot will contain \"workerd / Foo\".\n//\n// The jsgGetMemorySelfSize() method returns the *shallow* size of the type.\n// This would typically be implemented using sizeof(Type), and in the vast\n// majority of cases that's all it does. It is provided as a method, however,\n// in order to allow a type the ability to customize the size calculation.\n//\n// The jsgGetMemoryInfo(...) method is the method that is actually called to\n// visit instances of the type to collect details for the graph. Note that this\n// method is NOT expected to be called within the scope of an IoContext. It will\n// be called while within the isolate lock, however.\n//\n// Types may also implement the following additional methods to further customize\n// how they are represented in the graph:\n//\n//   v8::Local<v8::Object> jsgGetMemoryInfoWrapperObject();\n//   MemoryInfoDetachedState jsgGetMemoryInfoDetachedState() const;\n//   bool jsgGetMemoryInfoIsRootNode() const;\n//\n// Note that the `jsgGetMemoryInfoWrapperObject() method is called from within\n// a v8::HandleScope.\n//\n// For extremely simple cases, the JSG_MEMORY_INFO macro can be used to simplify\n// implementing these methods. It is a shortcut that provides basic implementations\n// of the jsgGetMemoryName() and jsgGetMemorySelfSize() methods:\n//\n//    JSG_MEMORY_INFO(Foo) {\n//      tracker.trackField(\"bar\", bar);\n//    }\n//\n// ... is equivalent to:\n//\n//    kj::StringPtr jsgGetMemoryName() const { return \"Foo\"_kjc; }\n//    size_t jsgGetMemorySelfSize() const { return sizeof(Foo); }\n//    void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n//      tracker.trackField(\"bar\", bar);\n//    }\n//\n// All jsg::Object instances provide a basic implementation of these methods.\n// Within a jsg::Object, your only responsibility would be to implement the\n// helper visitForMemoryInfo(jsg::MemoryTracker& tracker) const method only\n// if the type has additional fields that need to be tracked. This works a\n// lot like the visitForGc(...) method used for GC tracing:\n//\n//   class Foo : public jsg::Object {\n//   public:\n//     JSG_RESOURCE_TYPE(Foo) {}\n//\n//     void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n//       tracker.trackField(\"bar\", bar);\n//     }\n//     // ...\n//   };\n//\n// The constructed graph should include any fields that materially contribute\n// the retained memory of the type. This graph is primarily used for analysis\n// and investigation of memory issues in an application (e.g. hunting down\n// memory leaks, detecting bugs, optimizing memory usage, etc) so the information\n// should include details that are most useful for those purposes.\n//\n// This code is only ever called when a heap snapshot is being generated so\n// typically it should have very little cost. Heap snapshots are generally\n// fairly expensive to create, however, so care should be taken not to make\n// things too complicated. Ideally, none of the implementation methods in a\n// type should allocate. There is some allocation occurring internally while\n// building the graph, of course, but the methods for visitation (in particular\n// the jsgGetMemoryInfo(...) method) should not perform any allocations if it\n// can be avoided.\n\nclass MemoryTracker;\nclass MemoryRetainerNode;\n\ntemplate <typename T>\nclass V8Ref;\ntemplate <typename T>\nclass Ref;\n\nenum class MemoryInfoDetachedState {\n  UNKNOWN,\n  ATTACHED,\n  DETACHED,\n};\n\ntemplate <typename T>\nconcept MemoryRetainer = requires(const T* a) {\n  std::is_member_function_pointer_v<decltype(&T::jsgGetMemoryInfo)>;\n  std::is_member_function_pointer_v<decltype(&T::jsgGetMemoryName)>;\n  std::is_member_function_pointer_v<decltype(&T::jsgGetMemorySelfSize)>;\n};\n\ntemplate <typename T>\nconcept MemoryRetainerObject = requires(T a) {\n  MemoryRetainer<T>;\n  std::is_member_function_pointer_v<decltype(&T::jsgGetMemoryInfoWrapperObject)>;\n};\n\ntemplate <typename T>\nconcept MemoryRetainerDetachedState = requires(T a) {\n  MemoryRetainer<T>;\n  std::is_member_function_pointer_v<decltype(&T::jsgGetMemoryInfoDetachedState)>;\n};\n\ntemplate <typename T>\nconcept MemoryRetainerIsRootNode = requires(T a) {\n  MemoryRetainer<T>;\n  std::is_member_function_pointer_v<decltype(&T::jsgGetMemoryInfoIsRootNode)>;\n};\n\ntemplate <typename T>\nconcept V8Value = requires(T a) { std::is_assignable_v<v8::Value, T>; };\n\n// sometimes jsgGetMemoryName is virtual sometimes it is not, so ¯\\_(ツ)_/¯\n#define JSG_MEMORY_INFO(Name)                                                                      \\\n  _Pragma(\"GCC diagnostic push\") _Pragma(\"GCC diagnostic ignored \\\"-Wsuggest-override\\\"\")          \\\n      kj::StringPtr                                                                                \\\n      jsgGetMemoryName() const {                                                                   \\\n    return #Name##_kjc;                                                                            \\\n  }                                                                                                \\\n  size_t jsgGetMemorySelfSize() const {                                                            \\\n    return sizeof(Name);                                                                           \\\n  }                                                                                                \\\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const _Pragma(\"GCC diagnostic pop\")\n\n// jsg::MemoryTracker is used to construct the embedder graph for v8 heap\n// snapshot construction.\nclass MemoryTracker final {\n public:\n  inline void trackFieldWithSize(\n      kj::StringPtr edgeName, size_t size, kj::Maybe<kj::StringPtr> nodeName = kj::none);\n\n  inline void trackInlineFieldWithSize(\n      kj::StringPtr edgeName, size_t size, kj::Maybe<kj::StringPtr> nodeName = kj::none);\n\n  template <MemoryRetainer T>\n  inline void trackField(kj::StringPtr edgeName,\n      const kj::Own<T>& value,\n      kj::Maybe<kj::StringPtr> nodeName = kj::none);\n\n  template <MemoryRetainer T, typename D>\n  inline void trackField(kj::StringPtr edgeName,\n      const std::unique_ptr<T, D>& value,\n      kj::Maybe<kj::StringPtr> nodeName = kj::none);\n\n  template <MemoryRetainer T>\n  inline void trackField(kj::StringPtr edgeName,\n      const std::shared_ptr<T>& value,\n      kj::Maybe<kj::StringPtr> nodeName = kj::none);\n\n  template <V8Value T>\n  inline void trackField(\n      kj::StringPtr edgeName, const V8Ref<T>& value, kj::Maybe<kj::StringPtr> nodeName = kj::none);\n\n  template <MemoryRetainer T>\n  inline void trackField(\n      kj::StringPtr edgeName, const Ref<T>& value, kj::Maybe<kj::StringPtr> nodeName = kj::none);\n\n  template <MemoryRetainer T>\n  inline void trackField(\n      kj::StringPtr edgeName, const T& value, kj::Maybe<kj::StringPtr> nodeName = kj::none);\n\n  template <typename T>\n  inline void trackField(kj::StringPtr edgeName,\n      const kj::Maybe<T>& value,\n      kj::Maybe<kj::StringPtr> nodeName = kj::none);\n\n  template <typename T>\n  inline void trackField(kj::StringPtr edgeName,\n      const kj::Maybe<T&>& value,\n      kj::Maybe<kj::StringPtr> nodeName = kj::none);\n\n  inline void trackField(kj::StringPtr edgeName,\n      const kj::String& value,\n      kj::Maybe<kj::StringPtr> nodeName = kj::none);\n\n  inline void trackField(kj::StringPtr edgeName,\n      const kj::Exception& value,\n      kj::Maybe<kj::StringPtr> nodeName = kj::none);\n\n  template <typename T,\n      typename test = std::enable_if_t<std::numeric_limits<T>::is_specialized, bool>,\n      typename dummy = bool>\n  inline void trackField(kj::StringPtr edgeName,\n      const kj::Array<T>& value,\n      kj::Maybe<kj::StringPtr> nodeName = kj::none);\n\n  template <MemoryRetainer T, typename... Indexes>\n  void trackField(kj::StringPtr edgeName,\n      const kj::Table<T, Indexes...>& value,\n      kj::Maybe<kj::StringPtr> nodeName = kj::none,\n      kj::Maybe<kj::StringPtr> elementName = kj::none,\n      bool subtractFromSelf = true);\n\n  template <typename Key, typename Value>\n  void trackField(kj::StringPtr edgeName,\n      const kj::HashMap<Key, Value>& value,\n      kj::Maybe<kj::StringPtr> nodeName = kj::none);\n\n  template <MemoryRetainer T, typename Iterator = T::const_iterator>\n  inline void trackField(kj::StringPtr edgeName,\n      const T& value,\n      kj::Maybe<kj::StringPtr> nodeName = kj::none,\n      kj::Maybe<kj::StringPtr> elementName = kj::none,\n      bool subtractFromSelf = true);\n\n  template <MemoryRetainer T>\n  inline void trackField(kj::StringPtr edgeName,\n      const kj::ArrayPtr<T>& value,\n      kj::Maybe<kj::StringPtr> nodeName = kj::none,\n      kj::Maybe<kj::StringPtr> elementName = kj::none,\n      bool subtractFromSelf = true);\n\n  template <MemoryRetainer T>\n  inline void trackField(kj::StringPtr edgeName,\n      const kj::ArrayPtr<T* const>& value,\n      kj::Maybe<kj::StringPtr> nodeName = kj::none,\n      kj::Maybe<kj::StringPtr> elementName = kj::none,\n      bool subtractFromSelf = true);\n\n  template <MemoryRetainer T>\n  inline void trackField(\n      kj::StringPtr edgeName, const T* value, kj::Maybe<kj::StringPtr> nodeName = kj::none);\n\n  template <typename T>\n  inline void trackField(kj::StringPtr edgeName,\n      const std::basic_string<T>& value,\n      kj::Maybe<kj::StringPtr> nodeName = kj::none);\n\n  template <V8Value T>\n  inline void trackField(\n      kj::StringPtr edgeName, const v8::Eternal<T>& value, kj::StringPtr nodeName);\n\n  template <V8Value T>\n  inline void trackField(kj::StringPtr edgeName,\n      const v8::PersistentBase<T>& value,\n      kj::Maybe<kj::StringPtr> nodeName = kj::none);\n\n  template <V8Value T>\n  inline void trackField(kj::StringPtr edgeName,\n      const v8::Local<T>& value,\n      kj::Maybe<kj::StringPtr> nodeName = kj::none);\n\n  inline void trackField(kj::StringPtr edgeName,\n      const v8::BackingStore* value,\n      kj::Maybe<kj::StringPtr> nodeName = kj::none);\n\n  template <MemoryRetainer T>\n  inline void track(const T* retainer, kj::Maybe<kj::StringPtr> edgeName = kj::none);\n\n  template <MemoryRetainer T>\n  inline void trackInlineField(const T* retainer, kj::Maybe<kj::StringPtr> edgeName = kj::none);\n\n  inline v8::Isolate* isolate() {\n    return isolate_;\n  }\n\n  KJ_DISALLOW_COPY_AND_MOVE(MemoryTracker);\n\n private:\n  v8::Isolate* isolate_;\n  v8::EmbedderGraph* graph_;\n  std::stack<MemoryRetainerNode*> nodeStack_;\n  kj::HashMap<const void*, MemoryRetainerNode*> seen_;\n  KJ_DISALLOW_AS_COROUTINE_PARAM;\n\n  explicit MemoryTracker(v8::Isolate* isolate, v8::EmbedderGraph* graph);\n\n  KJ_NOINLINE MemoryRetainerNode* addNode(const void* retainer,\n      kj::StringPtr name,\n      size_t size,\n      v8::Local<v8::Object> obj,\n      kj::Maybe<kj::Function<bool()>> checkIsRootNode,\n      MemoryInfoDetachedState detachedness,\n      kj::Maybe<kj::StringPtr> edgeName);\n\n  template <MemoryRetainer T>\n  inline MemoryRetainerNode* pushNode(\n      const T* retainer, kj::Maybe<kj::StringPtr> edgeName = kj::none);\n\n  KJ_NOINLINE MemoryRetainerNode* addNode(\n      kj::StringPtr node_name, size_t size, kj::Maybe<kj::StringPtr> edgeName = kj::none);\n\n  KJ_NOINLINE MemoryRetainerNode* pushNode(\n      kj::StringPtr node_name, size_t size, kj::Maybe<kj::StringPtr> edgeName = kj::none);\n\n  KJ_NOINLINE void addEdge(MemoryRetainerNode* node, kj::StringPtr edgeName);\n  KJ_NOINLINE void addEdge(v8::EmbedderGraph::Node* node, kj::StringPtr edgeName);\n  void decCurrentNodeSize(size_t size);\n\n  friend class IsolateBase;\n  friend class MemoryRetainerNode;\n};\n\n// ======================================================================================\n\nvoid MemoryTracker::trackFieldWithSize(\n    kj::StringPtr edgeName, size_t size, kj::Maybe<kj::StringPtr> nodeName) {\n  if (size > 0) addNode(nodeName.orDefault(edgeName), size, edgeName);\n}\n\nvoid MemoryTracker::trackInlineFieldWithSize(\n    kj::StringPtr edgeName, size_t size, kj::Maybe<kj::StringPtr> nodeName) {\n  if (size > 0) addNode(nodeName.orDefault(edgeName), size, edgeName);\n}\n\nvoid MemoryTracker::trackField(\n    kj::StringPtr edgeName, const kj::String& value, kj::Maybe<kj::StringPtr> nodeName) {\n  trackFieldWithSize(edgeName, value.size(), \"kj::String\"_kjc);\n}\n\nvoid MemoryTracker::trackField(\n    kj::StringPtr edgeName, const kj::Exception& value, kj::Maybe<kj::StringPtr> nodeName) {\n  // Note that the size of the kj::Exception here only includes the\n  // shallow size of the kj::Exception type itself plus the length\n  // of the description string. We ignore the size of the stack and\n  // the context (if any). We could provide more detail but it's\n  // likely unnecessary.\n  trackFieldWithSize(\n      edgeName, sizeof(kj::Exception) + value.getDescription().size(), \"kj::Exception\"_kjc);\n}\n\ntemplate <MemoryRetainer T>\nvoid MemoryTracker::trackField(\n    kj::StringPtr edgeName, const kj::Own<T>& value, kj::Maybe<kj::StringPtr> nodeName) {\n  if (value.get() != nullptr) {\n    trackField(edgeName, value.get(), nodeName);\n  }\n}\n\ntemplate <MemoryRetainer T, typename D>\nvoid MemoryTracker::trackField(\n    kj::StringPtr edgeName, const std::unique_ptr<T, D>& value, kj::Maybe<kj::StringPtr> nodeName) {\n  if (value.get() != nullptr) {\n    return trackField(edgeName, value.get(), nodeName);\n  }\n}\n\ntemplate <MemoryRetainer T>\nvoid MemoryTracker::trackField(\n    kj::StringPtr edgeName, const std::shared_ptr<T>& value, kj::Maybe<kj::StringPtr> nodeName) {\n  if (value.get() != nullptr) {\n    return trackField(edgeName, value.get(), nodeName);\n  }\n}\n\ntemplate <typename T>\nvoid MemoryTracker::trackField(\n    kj::StringPtr edgeName, const kj::Maybe<T>& value, kj::Maybe<kj::StringPtr> nodeName) {\n  KJ_IF_SOME(v, value) {\n    trackField(edgeName, v, nodeName);\n  }\n}\n\ntemplate <typename T>\nvoid MemoryTracker::trackField(\n    kj::StringPtr edgeName, const kj::Maybe<T&>& value, kj::Maybe<kj::StringPtr> nodeName) {\n  KJ_IF_SOME(v, value) {\n    trackField(edgeName, v, nodeName);\n  }\n}\n\ntemplate <typename T>\nvoid MemoryTracker::trackField(\n    kj::StringPtr edgeName, const std::basic_string<T>& value, kj::Maybe<kj::StringPtr> nodeName) {\n  trackFieldWithSize(edgeName, value.size() * sizeof(T), \"std::basic_string\"_kjc);\n}\n\ntemplate <typename T, typename test, typename dummy>\nvoid MemoryTracker::trackField(\n    kj::StringPtr edgeName, const kj::Array<T>& value, kj::Maybe<kj::StringPtr> nodeName) {\n  trackFieldWithSize(edgeName, value.size() * sizeof(T), \"kj::Array<T>\"_kjc);\n}\n\ntemplate <MemoryRetainer T, typename... Indexes>\nvoid MemoryTracker::trackField(kj::StringPtr edgeName,\n    const kj::Table<T, Indexes...>& value,\n    kj::Maybe<kj::StringPtr> nodeName,\n    kj::Maybe<kj::StringPtr> elementName,\n    bool subtractFromSelf) {\n  if (value.begin() == value.end()) return;\n  if (subtractFromSelf) {\n    decCurrentNodeSize(sizeof(T));\n  }\n  pushNode(nodeName.orDefault(edgeName), sizeof(T), edgeName);\n  for (auto it = value.begin(); it != value.end(); ++it) {\n    trackField(nullptr, *it, elementName);\n  }\n  nodeStack_.pop();\n}\n\ntemplate <typename Key, typename Value>\nvoid MemoryTracker::trackField(kj::StringPtr edgeName,\n    const kj::HashMap<Key, Value>& value,\n    kj::Maybe<kj::StringPtr> nodeName) {\n  if (value.size() == 0) return;\n  pushNode(nodeName.orDefault(edgeName), sizeof(kj::HashMap<Key, Value>), edgeName);\n\n  for (const auto& entry: value) {\n    trackField(\"key\", entry.key);\n    trackField(\"value\", entry.value);\n  }\n  nodeStack_.pop();\n}\n\ntemplate <MemoryRetainer T, typename Iterator>\nvoid MemoryTracker::trackField(kj::StringPtr edgeName,\n    const T& value,\n    kj::Maybe<kj::StringPtr> nodeName,\n    kj::Maybe<kj::StringPtr> elementName,\n    bool subtractFromSelf) {\n  if (value.begin() == value.end()) return;\n  if (subtractFromSelf) {\n    decCurrentNodeSize(sizeof(T));\n  }\n  pushNode(nodeName.orDefault(edgeName), sizeof(T), edgeName);\n  for (Iterator it = value.begin(); it != value.end(); ++it) {\n    trackField(nullptr, *it, elementName);\n  }\n  nodeStack_.pop();\n}\n\ntemplate <MemoryRetainer T>\nvoid MemoryTracker::trackField(kj::StringPtr edgeName,\n    const kj::ArrayPtr<T>& value,\n    kj::Maybe<kj::StringPtr> nodeName,\n    kj::Maybe<kj::StringPtr> elementName,\n    bool subtractFromSelf) {\n  if (value.begin() == value.end()) return;\n  if (subtractFromSelf) {\n    decCurrentNodeSize(sizeof(T));\n  }\n  pushNode(nodeName.orDefault(edgeName), sizeof(T), edgeName);\n  for (const auto& item: value) {\n    trackField(nullptr, item, elementName);\n  }\n  nodeStack_.pop();\n}\n\ntemplate <MemoryRetainer T>\nvoid MemoryTracker::trackField(kj::StringPtr edgeName,\n    const kj::ArrayPtr<T* const>& value,\n    kj::Maybe<kj::StringPtr> nodeName,\n    kj::Maybe<kj::StringPtr> elementName,\n    bool subtractFromSelf) {\n  if (value.begin() == value.end()) return;\n  if (subtractFromSelf) {\n    decCurrentNodeSize(sizeof(T));\n  }\n  pushNode(nodeName.orDefault(edgeName), sizeof(T), edgeName);\n  for (const auto& item: value) {\n    trackField(nullptr, item, elementName);\n  }\n  nodeStack_.pop();\n}\n\ntemplate <MemoryRetainer T>\nvoid MemoryTracker::trackField(\n    kj::StringPtr edgeName, const T& value, kj::Maybe<kj::StringPtr> nodeName) {\n  trackField(edgeName, &value, nodeName);\n}\n\ntemplate <MemoryRetainer T>\nvoid MemoryTracker::trackField(\n    kj::StringPtr edgeName, const T* value, kj::Maybe<kj::StringPtr> nodeName) {\n  if (value == nullptr) return;\n  KJ_IF_SOME(found, seen_.find(value)) {\n    addEdge(found, edgeName);\n    return;\n  }\n  track(value, edgeName);\n}\n\ntemplate <V8Value T>\nvoid MemoryTracker::trackField(\n    kj::StringPtr edgeName, const v8::Eternal<T>& value, kj::StringPtr nodeName) {\n  trackField(edgeName, value.Get(isolate_));\n}\n\ntemplate <V8Value T>\nvoid MemoryTracker::trackField(\n    kj::StringPtr edgeName, const v8::PersistentBase<T>& value, kj::Maybe<kj::StringPtr> nodeName) {\n  if (!value.IsEmpty() && !value.IsWeak()) {\n    trackField(edgeName, value.Get(isolate_));\n  }\n}\n\ntemplate <V8Value T>\nvoid MemoryTracker::trackField(\n    kj::StringPtr edgeName, const v8::Local<T>& value, kj::Maybe<kj::StringPtr> nodeName) {\n  if (!value.IsEmpty()) {\n    addEdge(graph_->V8Node(value.template As<v8::Value>()), edgeName);\n  }\n}\n\ntemplate <MemoryRetainer T>\nvoid MemoryTracker::track(const T* retainer, kj::Maybe<kj::StringPtr> edgeName) {\n  v8::HandleScope handle_scope(isolate_);\n  KJ_IF_SOME(found, seen_.find(retainer)) {\n    addEdge(found, edgeName.orDefault(nullptr));\n    return;\n  }\n\n  pushNode(retainer, edgeName);\n  retainer->jsgGetMemoryInfo(*this);\n  nodeStack_.pop();\n}\n\ntemplate <MemoryRetainer T>\nvoid MemoryTracker::trackInlineField(const T* retainer, kj::Maybe<kj::StringPtr> edgeName) {\n  track(retainer, edgeName);\n}\n\ntemplate <MemoryRetainer T>\nMemoryRetainerNode* MemoryTracker::pushNode(const T* retainer, kj::Maybe<kj::StringPtr> edgeName) {\n  const kj::StringPtr name = retainer->jsgGetMemoryName();\n  const size_t size = retainer->jsgGetMemorySelfSize();\n  v8::Local<v8::Object> obj;\n  kj::Maybe<kj::Function<bool()>> checkIsRootNode = kj::none;\n  MemoryInfoDetachedState detachedness = MemoryInfoDetachedState::UNKNOWN;\n  v8::HandleScope handleScope(isolate());\n  if constexpr (MemoryRetainerObject<T>) {\n    obj = const_cast<T*>(retainer)->jsgGetMemoryInfoWrapperObject(isolate());\n  }\n  if constexpr (MemoryRetainerIsRootNode<T>) {\n    checkIsRootNode = [retainer]() { return retainer->jsgGetMemoryInfoIsRootNode(); };\n  }\n  if constexpr (MemoryRetainerDetachedState<T>) {\n    detachedness = retainer->jsgGetMemoryInfoDetachedState();\n  }\n\n  MemoryRetainerNode* n =\n      addNode(retainer, name, size, obj, kj::mv(checkIsRootNode), detachedness, edgeName);\n  nodeStack_.push(n);\n  return n;\n}\n\ntemplate <typename T>\ninline void visitSubclassForMemoryInfo(const T* obj, MemoryTracker& tracker) {\n  if constexpr (&T::visitForMemoryInfo != &T::jsgSuper::visitForMemoryInfo) {\n    obj->visitForMemoryInfo(tracker);\n  }\n}\n\n// ======================================================================================\n\nclass HeapSnapshotActivity final: public v8::ActivityControl {\n public:\n  using Callback = kj::Function<bool(uint32_t done, uint32_t total)>;\n\n  HeapSnapshotActivity(Callback callback);\n  ~HeapSnapshotActivity() noexcept(true) = default;\n\n  ControlOption ReportProgressValue(uint32_t done, uint32_t total) override;\n\n private:\n  Callback callback;\n};\n\nclass HeapSnapshotWriter final: public v8::OutputStream {\n public:\n  using Callback = kj::Function<bool(kj::Maybe<kj::ArrayPtr<char>>)>;\n\n  HeapSnapshotWriter(Callback callback, size_t chunkSize = 65536);\n  ~HeapSnapshotWriter() noexcept(true) = default;\n\n  void EndOfStream() override;\n\n  int GetChunkSize() override;\n\n  v8::OutputStream::WriteResult WriteAsciiChunk(char* data, int size) override;\n\n private:\n  Callback callback;\n  size_t chunkSize;\n};\n\nstruct HeapSnapshotDeleter: public kj::Disposer {\n  inline void disposeImpl(void* ptr) const override {\n    auto snapshot = const_cast<v8::HeapSnapshot*>(static_cast<const v8::HeapSnapshot*>(ptr));\n    snapshot->Delete();\n  }\n};\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/meta.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// INTERNAL IMPLEMENTATION FILE\n\n#include <v8-function-callback.h>\n\n#include <kj/tuple.h>\n\nnamespace workerd::jsg {\n\nclass Lock;\n\ntemplate <typename T>\nstruct ArgumentIndexes_;\ntemplate <typename T, typename Ret, typename... Args>\nstruct ArgumentIndexes_<Ret (T::*)(Args...)> {\n  using Indexes = kj::_::MakeIndexes<sizeof...(Args)>;\n};\ntemplate <typename T, typename Ret, typename... Args>\nstruct ArgumentIndexes_<Ret (T::*)(Lock&, Args...)> {\n  using Indexes = kj::_::MakeIndexes<sizeof...(Args)>;\n};\ntemplate <typename T, typename Ret, typename... Args>\nstruct ArgumentIndexes_<Ret (T::*)(const v8::FunctionCallbackInfo<v8::Value>&, Args...)> {\n  using Indexes = kj::_::MakeIndexes<sizeof...(Args)>;\n};\ntemplate <typename T, typename Ret, typename... Args>\nstruct ArgumentIndexes_<Ret (T::*)(Args...) const> {\n  using Indexes = kj::_::MakeIndexes<sizeof...(Args)>;\n};\ntemplate <typename T, typename Ret, typename... Args>\nstruct ArgumentIndexes_<Ret (T::*)(Lock&, Args...) const> {\n  using Indexes = kj::_::MakeIndexes<sizeof...(Args)>;\n};\ntemplate <typename T, typename Ret, typename... Args>\nstruct ArgumentIndexes_<Ret (T::*)(const v8::FunctionCallbackInfo<v8::Value>&, Args...) const> {\n  using Indexes = kj::_::MakeIndexes<sizeof...(Args)>;\n};\ntemplate <typename Ret, typename... Args>\nstruct ArgumentIndexes_<Ret(Args...)> {\n  using Indexes = kj::_::MakeIndexes<sizeof...(Args)>;\n};\ntemplate <typename Ret, typename... Args>\nstruct ArgumentIndexes_<Ret(Lock&, Args...)> {\n  using Indexes = kj::_::MakeIndexes<sizeof...(Args)>;\n};\ntemplate <typename Ret, typename... Args>\nstruct ArgumentIndexes_<Ret(const v8::FunctionCallbackInfo<v8::Value>&, Args...)> {\n  using Indexes = kj::_::MakeIndexes<sizeof...(Args)>;\n};\ntemplate <typename T>\nusing ArgumentIndexes = ArgumentIndexes_<T>::Indexes;\n// ArgumentIndexes<SomeMethodType> expands to kj::_::Indexes<0, 1, 2, 3, ..., n-1>, where n is the\n// number of arguments to the method, not counting the magic Lock or FunctionCallbackInfo parameter\n// (if any).\n\n// =======================================================================================\n// requiredArgumentCount<TypeWrapper, T> — counts leading required JS-visible arguments.\n//\n// Used by resource.h to set the Web IDL .length property on functions.\n//\n// Argument filtering is split across two layers for dependency reasons:\n//\n//  1. StripMagicParam_ (here in meta.h) removes the leading Lock& or\n//     FunctionCallbackInfo& parameter.  These are C++/V8 plumbing that always\n//     appear first and are never JS-visible.  meta.h can handle them because\n//     it only needs the v8 forward declarations it already includes.\n//\n//  2. RequiredArgCount_ (in type-wrapper.h) skips all \"injected\" parameter\n//     types that don't consume a JS argument.  It uses the ValueLessParameter\n//     concept to automatically detect types like TypeHandler<T> and\n//     InjectConfiguration types (e.g. CompatibilityFlags::Reader), plus\n//     isArguments<>() for variadic Arguments<T>.  The TypeWrapper template\n//     parameter is required because ValueLessParameter checks whether the\n//     wrapper has a 3-arg unwrap(js, context, T*) overload for the type.\n\n// Lightweight type list; kj::Tuple is an alias template and cannot be partially specialized.\ntemplate <typename... Ts>\nstruct TypeList {};\n\nnamespace detail {\n\n// Phase 1: Normalize member-function-pointer or free-function type to Ret(Args...).\ntemplate <typename T>\nstruct NormalizeFunc_;\ntemplate <typename C, typename R, typename... A>\nstruct NormalizeFunc_<R (C::*)(A...)> {\n  using type = R(A...);\n};\ntemplate <typename C, typename R, typename... A>\nstruct NormalizeFunc_<R (C::*)(A...) const> {\n  using type = R(A...);\n};\ntemplate <typename R, typename... A>\nstruct NormalizeFunc_<R(A...)> {\n  using type = R(A...);\n};\n\n// Phase 2: Strip leading Lock& / FunctionCallbackInfo& and yield the JS-visible args.\n// See the comment above for why this is separate from RequiredArgCount_.\ntemplate <typename T>\nstruct StripMagicParam_;\ntemplate <typename R, typename... A>\nstruct StripMagicParam_<R(A...)> {\n  using Args = TypeList<A...>;\n};\ntemplate <typename R, typename... A>\nstruct StripMagicParam_<R(Lock&, A...)> {\n  using Args = TypeList<A...>;\n};\ntemplate <typename R, typename... A>\nstruct StripMagicParam_<R(const v8::FunctionCallbackInfo<v8::Value>&, A...)> {\n  using Args = TypeList<A...>;\n};\n\ntemplate <typename T>\nusing MethodArgs = StripMagicParam_<typename NormalizeFunc_<T>::type>;\n\n// Forward declaration — specialized in type-wrapper.h where ValueLessParameter is visible.\ntemplate <typename TypeWrapper, typename ArgsList>\nstruct RequiredArgCount_;\n\n}  // namespace detail\n\n// Per Web IDL, the .length of a function is the number of leading required arguments.\n// The actual counting logic lives in type-wrapper.h (needs the ValueLessParameter concept).\ntemplate <typename TypeWrapper, typename T>\ninline constexpr int requiredArgumentCount =\n    detail::RequiredArgCount_<TypeWrapper, typename detail::MethodArgs<T>::Args>::value;\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/modules-new-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"observer.h\"\n#include \"type-wrapper.h\"\n#include \"url.h\"\n\n#include <workerd/jsg/modules-new.h>\n#include <workerd/jsg/modules.capnp.h>\n#include <workerd/jsg/setup.h>\n\n#include <capnp/message.h>\n#include <kj/async-io.h>\n#include <kj/test.h>\n#include <kj/thread.h>\n#include <kj/vector.h>\n\nnamespace workerd::jsg::test {\nnamespace {\nusing workerd::jsg::modules::Module;\nusing workerd::jsg::modules::ModuleBundle;\nusing workerd::jsg::modules::ModuleRegistry;\nusing workerd::jsg::modules::ResolveContext;\n\nV8System v8System;\nconst jsg::Url BASE = \"file:///\"_url;\n\nstruct ResolveObserverImpl: public ResolveObserver {\n  struct Request {\n    Url id;\n    ResolveObserver::Context context;\n    ResolveObserver::Source source;\n    bool found = false;\n  };\n  mutable kj::Vector<Request> modules;\n\n  struct MyResolveStatus: public ResolveObserver::ResolveStatus {\n    Request& request;\n    MyResolveStatus(Request& request): request(request) {}\n    void found() override {\n      request.found = true;\n    }\n    void notFound() override {\n      request.found = false;\n    }\n  };\n\n  kj::Own<ResolveObserver::ResolveStatus> onResolveModule(\n      const Url& id, Context context, Source source) const override {\n    modules.add(Request{\n      .id = id.clone(),\n      .context = context,\n      .source = source,\n    });\n    return kj::heap<MyResolveStatus>(modules.back());\n  }\n};\n\nstruct TestType: public jsg::Object {\n  bool barCalled = false;\n  kj::Maybe<JsRef<JsObject>> exports;\n\n  TestType(Lock&, const jsg::Url&) {}\n\n  void bar() {\n    barCalled = true;\n  }\n\n  JsObject getExports(Lock& js) {\n    KJ_IF_SOME(exp, exports) {\n      return exp.getHandle(js);\n    }\n    return exports.emplace(JsRef<JsObject>(js, js.obj())).getHandle(js);\n  }\n\n  void setExports(Lock& js, JsObject obj) {\n    exports = JsRef(js, obj);\n  }\n\n  JsValue require(Lock& js, kj::String specifier) {\n    return js.tryCatch([&] { return ModuleRegistry::resolve(js, specifier); },\n        [&](Value exception) -> JsValue { js.throwException(kj::mv(exception)); });\n  }\n\n  jsg::JsValue getModuleExports(jsg::Lock& js) {\n    return getExports(js);\n  }\n\n  JSG_RESOURCE_TYPE(TestType) {\n    JSG_METHOD(bar);\n    JSG_METHOD(require);\n    JSG_PROTOTYPE_PROPERTY(exports, getExports, setExports);\n  }\n};\n\nstruct TestTypeWrapper {\n  static TestTypeWrapper& from(v8::Isolate*) {\n    KJ_UNIMPLEMENTED(\"not implemented\");\n  }\n  v8::Local<v8::Value> wrap(jsg::Lock& lock,\n      v8::Local<v8::Context>,\n      kj::Maybe<v8::Local<v8::Object>>,\n      jsg::Ref<TestType>) {\n    KJ_UNIMPLEMENTED(\"not implemented\");\n  }\n};\n\nstruct TestContext: public Object, public ContextGlobal {\n  JSG_RESOURCE_TYPE(TestContext) {}\n};\nJSG_DECLARE_ISOLATE_TYPE(TestIsolate, TestContext, TestType);\n\n#define PREAMBLE(fn)                                                                               \\\n  TestIsolate isolate(v8System, v8::IsolateGroup::GetDefault(), 123, kj::heap<IsolateObserver>()); \\\n  isolate.runInLockScope([&](auto& lock) {                                                         \\\n    IsolateBase::from(lock.v8Isolate).setUsingNewModuleRegistry();                                 \\\n    JSG_WITHIN_CONTEXT_SCOPE(lock, lock.template newContext<TestContext>().getHandle(lock),        \\\n        [&](jsg::Lock& js) { fn(lock); });                                                         \\\n  });\n\n// ======================================================================================\n\nKJ_TEST(\"An empty registry\") {\n  // We should be able to create an empty registry that returns nothing.\n  // Basic resolution of this kind does not require an isolate lock.\n\n  ResolveObserverImpl observer;\n  ModuleRegistry::Builder registryBuilder(observer, BASE);\n  auto registry = registryBuilder.finish();\n  KJ_ASSERT(registry.get() != nullptr);\n\n  ResolveContext context = {\n    .type = ResolveContext::Type::BUNDLE,\n    .source = ResolveContext::Source::INTERNAL,\n    .normalizedSpecifier = \"file:///foo\"_url,\n    .referrerNormalizedSpecifier = BASE,\n  };\n\n  KJ_ASSERT(registry->lookup(context) == kj::none);\n\n  KJ_ASSERT(observer.modules.size() == 1);\n  KJ_ASSERT(observer.modules[0].found == false);\n}\n\n// ======================================================================================\n\nKJ_TEST(\"A empty fallback bundle\") {\n  // We should be able to create an empty fallback bundle that returns nothing.\n  // Basic resolution of this kind does not require an isolate lock.\n\n  bool called = false;\n  auto fallback = ModuleBundle::newFallbackBundle([&called](const ResolveContext& context) {\n    called = true;\n    return kj::none;\n  });\n\n  ResolveContext context = {\n    .type = ResolveContext::Type::BUNDLE,\n    .source = ResolveContext::Source::INTERNAL,\n    .normalizedSpecifier = \"file:///foo\"_url,\n    .referrerNormalizedSpecifier = BASE,\n  };\n\n  KJ_ASSERT(fallback->lookup(context) == kj::none);\n  KJ_ASSERT(called);\n}\n\n// ======================================================================================\n\nKJ_TEST(\"An empty user bundle\") {\n  // We should be able to create an empty user bundle that returns nothing.\n  // Basic resolution of this kind does not require an isolate lock.\n\n  ModuleBundle::BundleBuilder builder(BASE);\n  auto bundle = builder.finish();\n\n  ResolveContext context = {\n    .type = ResolveContext::Type::BUNDLE,\n    .source = ResolveContext::Source::INTERNAL,\n    .normalizedSpecifier = \"file:///foo\"_url,\n    .referrerNormalizedSpecifier = BASE,\n  };\n\n  KJ_ASSERT(bundle->lookup(context) == kj::none);\n}\n\n// ======================================================================================\n\nKJ_TEST(\"An empty built-in bundle\") {\n  // We should be able to create an empty built-in bundle that returns nothing.\n  // Basic resolution of this kind does not require an isolate lock.\n\n  ModuleBundle::BuiltinBuilder builder;\n  auto bundle = builder.finish();\n\n  ResolveContext context = {\n    .type = ResolveContext::Type::BUNDLE,\n    .source = ResolveContext::Source::INTERNAL,\n    .normalizedSpecifier = \"file:///foo\"_url,\n    .referrerNormalizedSpecifier = BASE,\n  };\n\n  KJ_ASSERT(bundle->lookup(context) == kj::none);\n}\n\n// ======================================================================================\n\nKJ_TEST(\"A registry with empty bundles\") {\n  // We should be able to create a registry with empty bundles that return nothing.\n  // Basic resolution of this kind does not require an isolate lock.\n\n  ResolveObserverImpl observer;\n  ModuleRegistry::Builder registryBuilder(\n      observer, BASE, ModuleRegistry::Builder::Options::ALLOW_FALLBACK);\n\n  registryBuilder.add(\n      ModuleBundle::newFallbackBundle([](const ResolveContext& context) { return kj::none; }));\n\n  ModuleBundle::BundleBuilder bundleBuilder(BASE);\n  registryBuilder.add(bundleBuilder.finish());\n\n  ModuleBundle::BuiltinBuilder builtinBuilder;\n  registryBuilder.add(builtinBuilder.finish());\n\n  auto registry = registryBuilder.finish();\n\n  ResolveContext context = {\n    .type = ResolveContext::Type::BUNDLE,\n    .source = ResolveContext::Source::INTERNAL,\n    .normalizedSpecifier = \"file:///foo\"_url,\n    .referrerNormalizedSpecifier = BASE,\n  };\n\n  KJ_ASSERT(registry->lookup(context) == kj::none);\n  KJ_ASSERT(observer.modules.size() == 1);\n  KJ_ASSERT(observer.modules[0].found == false);\n}\n\n// ======================================================================================\n\nKJ_TEST(\"A user bundle with a single ESM module\") {\n  ModuleBundle::BundleBuilder builder(BASE);\n\n  auto source = kj::str(\"export const foo = 123;\");\n  builder.addEsmModule(\"foo\", source, Module::Flags::MAIN);\n\n  auto bundle = builder.finish();\n\n  const auto id = \"file:///foo\"_url;\n\n  ResolveContext context = {\n    .type = ResolveContext::Type::BUNDLE,\n    .source = ResolveContext::Source::INTERNAL,\n    .normalizedSpecifier = id,\n    .referrerNormalizedSpecifier = BASE,\n  };\n\n  auto resolved = KJ_ASSERT_NONNULL(bundle->lookup(context));\n  auto& module = KJ_ASSERT_NONNULL(resolved.module);\n\n  KJ_ASSERT(module.id() == id);\n  KJ_ASSERT(module.isEsm());\n  KJ_ASSERT(module.isMain());\n  KJ_ASSERT(module.type() == Module::Type::BUNDLE);\n}\n\n// ======================================================================================\n\nKJ_TEST(\"A user bundle with an ESM module and a Synthetic module\") {\n  ModuleBundle::BundleBuilder builder(BASE);\n\n  auto source = kj::str(\"export const foo = 123;\");\n  builder.addEsmModule(\"foo\", source, Module::Flags::MAIN);\n  builder.addSyntheticModule(\n      \"foo/bar\", [](Lock&, const Url&, const Module::ModuleNamespace&, const CompilationObserver&) {\n    return true;\n  });\n\n  const auto foo = \"file:///foo\"_url;\n  const auto bar = \"file:///foo/bar\"_url;\n\n  auto bundle = builder.finish();\n\n  {\n    ResolveContext context = {\n      .type = ResolveContext::Type::BUNDLE,\n      .source = ResolveContext::Source::INTERNAL,\n      .normalizedSpecifier = foo,\n      .referrerNormalizedSpecifier = BASE,\n    };\n\n    auto resolved = KJ_ASSERT_NONNULL(bundle->lookup(context));\n    auto& module = KJ_ASSERT_NONNULL(resolved.module);\n\n    KJ_ASSERT(module.id() == foo);\n    KJ_ASSERT(module.isEsm());\n    KJ_ASSERT(module.isMain());\n    KJ_ASSERT(module.type() == Module::Type::BUNDLE);\n  }\n\n  {\n    ResolveContext context = {\n      .type = ResolveContext::Type::BUNDLE,\n      .source = ResolveContext::Source::INTERNAL,\n      .normalizedSpecifier = bar,\n      .referrerNormalizedSpecifier = BASE,\n    };\n\n    auto resolved = KJ_ASSERT_NONNULL(bundle->lookup(context));\n    auto& module = KJ_ASSERT_NONNULL(resolved.module);\n\n    KJ_ASSERT(module.id() == bar);\n    KJ_ASSERT(!module.isEsm());\n    KJ_ASSERT(!module.isMain());\n    KJ_ASSERT(module.type() == Module::Type::BUNDLE);\n  }\n}\n\n// ======================================================================================\n\nKJ_TEST(\"A built-in bundle with two modules\") {\n  ResolveObserverImpl observer;\n  ModuleRegistry::Builder registryBuilder(observer, BASE);\n\n  ModuleBundle::BuiltinBuilder builder;\n\n  const auto foo = \"foo:bar\"_url;\n  const auto bar = \"bar:baz\"_url;\n  auto source = \"export const foo = 123;\"_kjc;\n  builder.addEsm(foo, source.asArray());\n\n  struct W {\n    static W& from(v8::Isolate*) {\n      static W w;\n      return w;\n    }\n    v8::Local<v8::Value> wrap(jsg::Lock& lock,\n        v8::Local<v8::Context>,\n        kj::Maybe<v8::Local<v8::Object>>,\n        jsg::Ref<TestType>) {\n      return v8::Local<v8::Value>();\n    }\n  };\n  builder.addObject<TestType, W>(bar);\n\n  auto registry = registryBuilder.add(builder.finish()).finish();\n\n  {\n    ResolveContext context = {\n      .type = ResolveContext::Type::BUNDLE,\n      .source = ResolveContext::Source::INTERNAL,\n      .normalizedSpecifier = foo,\n      .referrerNormalizedSpecifier = foo,\n    };\n\n    auto& module = KJ_ASSERT_NONNULL(registry->lookup(context));\n\n    KJ_ASSERT(module.id() == foo);\n    KJ_ASSERT(module.isEsm());\n    KJ_ASSERT(!module.isMain());\n    KJ_ASSERT(module.type() == Module::Type::BUILTIN);\n  }\n\n  {\n    ResolveContext context = {\n      .type = ResolveContext::Type::BUNDLE,\n      .source = ResolveContext::Source::INTERNAL,\n      .normalizedSpecifier = bar,\n      .referrerNormalizedSpecifier = bar,\n    };\n\n    auto& module = KJ_ASSERT_NONNULL(registry->lookup(context));\n\n    KJ_ASSERT(module.id() == bar);\n    KJ_ASSERT(!module.isEsm());\n    KJ_ASSERT(!module.isMain());\n    KJ_ASSERT(module.type() == Module::Type::BUILTIN);\n  }\n\n  KJ_ASSERT(observer.modules.size() == 2);\n  KJ_ASSERT(observer.modules[0].id == foo);\n  KJ_ASSERT(observer.modules[1].id == bar);\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Built-in and Built-in only bundles\") {\n  ResolveObserverImpl observer;\n  ModuleRegistry::Builder registryBuilder(observer, BASE);\n\n  ModuleBundle::BuiltinBuilder builtinBuilder;\n  ModuleBundle::BuiltinBuilder builtinOnlyBuilder(ModuleBundle::BuiltinBuilder::Type::BUILTIN_ONLY);\n\n  const auto foo = \"foo:bar\"_url;\n  const auto bar = \"bar:baz\"_url;\n  auto source = \"export const foo = 123;\"_kjc;\n  builtinBuilder.addEsm(foo, source.asArray());\n\n  builtinOnlyBuilder.addObject<TestType, TestTypeWrapper>(bar);\n\n  auto registry =\n      registryBuilder.add(builtinBuilder.finish()).add(builtinOnlyBuilder.finish()).finish();\n\n  {\n    ResolveContext context = {\n      .type = ResolveContext::Type::BUNDLE,\n      .source = ResolveContext::Source::INTERNAL,\n      .normalizedSpecifier = foo,\n      .referrerNormalizedSpecifier = foo,\n    };\n\n    auto& module = KJ_ASSERT_NONNULL(registry->lookup(context));\n\n    KJ_ASSERT(module.id() == foo);\n    KJ_ASSERT(module.isEsm());\n    KJ_ASSERT(!module.isMain());\n    KJ_ASSERT(module.type() == Module::Type::BUILTIN);\n  }\n\n  {\n    ResolveContext context = {\n      .type = ResolveContext::Type::BUNDLE,\n      .source = ResolveContext::Source::INTERNAL,\n      .normalizedSpecifier = bar,\n      .referrerNormalizedSpecifier = bar,\n    };\n\n    // Built-in only modules cannot be resolved from a bundle context.\n    KJ_ASSERT(registry->lookup(context) == kj::none);\n  }\n\n  {\n    ResolveContext context = {\n      .type = ResolveContext::Type::BUILTIN,\n      .source = ResolveContext::Source::INTERNAL,\n      .normalizedSpecifier = bar,\n      .referrerNormalizedSpecifier = bar,\n    };\n\n    auto& module = KJ_ASSERT_NONNULL(registry->lookup(context));\n\n    KJ_ASSERT(module.id() == bar);\n    KJ_ASSERT(!module.isEsm());\n    KJ_ASSERT(!module.isMain());\n    KJ_ASSERT(module.type() == Module::Type::BUILTIN_ONLY);\n  }\n\n  {\n    ResolveContext context = {\n      .type = ResolveContext::Type::BUILTIN_ONLY,\n      .source = ResolveContext::Source::INTERNAL,\n      .normalizedSpecifier = bar,\n      .referrerNormalizedSpecifier = bar,\n    };\n\n    auto& module = KJ_ASSERT_NONNULL(registry->lookup(context));\n\n    KJ_ASSERT(module.id() == bar);\n    KJ_ASSERT(!module.isEsm());\n    KJ_ASSERT(!module.isMain());\n    KJ_ASSERT(module.type() == Module::Type::BUILTIN_ONLY);\n  }\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Built-in modules cannot use file:\") {\n  ModuleBundle::BuiltinBuilder builder;\n  const auto foo = \"file:///foo\"_url;\n  auto source = \"export const foo = 123;\"_kjc;\n\n  try {\n    builder.addEsm(foo, source.asArray());\n    KJ_FAIL_ASSERT(\"Expected an exception\");\n  } catch (kj::Exception& exception) {\n    KJ_ASSERT(exception.getDescription().endsWith(\n        \"The file: protocol is reserved for bundle type modules\"_kjc));\n  }\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Fallback bundle that returns something\") {\n  auto fallback = ModuleBundle::newFallbackBundle([](const ResolveContext& context) {\n    kj::Own<Module> mod = Module::newSynthetic(\"file:///foo\"_url, Module::Type::FALLBACK,\n        [](Lock&, const Url&, const Module::ModuleNamespace&, const CompilationObserver&) -> bool {\n      KJ_FAIL_ASSERT(\"Should not be called\");\n    });\n    return kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>>(kj::mv(mod));\n  });\n\n  ResolveObserverImpl observer;\n  ModuleRegistry::Builder registryBuilder(\n      observer, BASE, ModuleRegistry::Builder::Options::ALLOW_FALLBACK);\n  auto registry = registryBuilder.add(kj::mv(fallback)).finish();\n\n  const auto id = \"file:///foo\"_url;\n\n  {\n    ResolveContext context{\n      .type = ResolveContext::Type::BUNDLE,\n      .source = ResolveContext::Source::INTERNAL,\n      .normalizedSpecifier = id,\n      .referrerNormalizedSpecifier = BASE,\n    };\n\n    auto& module = KJ_ASSERT_NONNULL(registry->lookup(context));\n    KJ_ASSERT(module.id() == id);\n    KJ_ASSERT(module.type() == Module::Type::FALLBACK);\n    KJ_ASSERT(!module.isEsm());\n  }\n\n  // Built-in and built-in only contexts do not use the fallback\n  {\n    ResolveContext context{\n      .type = ResolveContext::Type::BUILTIN,\n      .source = ResolveContext::Source::INTERNAL,\n      .normalizedSpecifier = id,\n      .referrerNormalizedSpecifier = BASE,\n    };\n\n    KJ_ASSERT(registry->lookup(context) == kj::none);\n  }\n\n  {\n    ResolveContext context{\n      .type = ResolveContext::Type::BUILTIN_ONLY,\n      .source = ResolveContext::Source::INTERNAL,\n      .normalizedSpecifier = id,\n      .referrerNormalizedSpecifier = BASE,\n    };\n\n    KJ_ASSERT(registry->lookup(context) == kj::none);\n  }\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Duplicate module names in a single are caught and throw properly\") {\n  ModuleBundle::BundleBuilder builder(BASE);\n  builder.addSyntheticModule(\n      \"foo\", [](Lock&, const Url&, const Module::ModuleNamespace&, const CompilationObserver&) {\n    return true;\n  });\n  try {\n    builder.addSyntheticModule(\n        \"foo\", [](Lock&, const Url&, const Module::ModuleNamespace&, const CompilationObserver&) {\n      return true;\n    });\n    KJ_FAIL_ASSERT(\"Expected an exception\");\n  } catch (kj::Exception& exception) {\n    KJ_ASSERT(exception.getDescription() == \"Module \\\"file:///foo\\\" already added to bundle\"_kjc);\n  }\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Fallback bundles are not permitted in production\") {\n  ResolveObserverImpl observer;\n  ModuleRegistry::Builder registryBuilder(observer, BASE);\n  try {\n    registryBuilder.add(ModuleBundle::newFallbackBundle([](const ResolveContext& context) {\n      kj::Own<Module> mod =\n          Module::newSynthetic(context.normalizedSpecifier.clone(), Module::Type::FALLBACK,\n              [](Lock&, const Url&, const Module::ModuleNamespace&,\n                  const CompilationObserver&) -> bool { KJ_FAIL_ASSERT(\"Should not be called\"); });\n      return kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>>(kj::mv(mod));\n    }));\n    KJ_FAIL_ASSERT(\"Expected an exception\");\n  } catch (kj::Exception& exception) {\n    KJ_ASSERT(exception.getDescription().endsWith(\n        \"Fallback bundle types are not allowed for this registry\"_kjc));\n  }\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Compound Registry\") {\n  ResolveObserverImpl observer;\n  ModuleRegistry::Builder registryBuilder(\n      observer, BASE, ModuleRegistry::Builder::Options::ALLOW_FALLBACK);\n\n  const auto foo = \"foo:bar\"_url;      // Fallback\n  const auto bar = \"bar:baz\"_url;      // Built-in\n  const auto baz = \"abc:xyz\"_url;      // Built-in only\n  const auto qux = \"file:///qux\"_url;  // Bundle\n\n  registryBuilder.add(ModuleBundle::newFallbackBundle(\n      [&](const ResolveContext& context) -> kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>> {\n    if (context.normalizedSpecifier != foo) return kj::none;\n    kj::Own<Module> mod = Module::newSynthetic(foo.clone(), Module::Type::FALLBACK,\n        [](Lock&, const Url&, const Module::ModuleNamespace&, const CompilationObserver&) -> bool {\n      KJ_FAIL_ASSERT(\"should not have been called\");\n    });\n    return kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>>(kj::mv(mod));\n  }));\n\n  ModuleBundle::BuiltinBuilder builtinBuilder;\n  auto barSource = \"export const foo = 123;\"_kjc;\n  builtinBuilder.addEsm(bar, barSource.asArray());\n  registryBuilder.add(builtinBuilder.finish());\n\n  ModuleBundle::BuiltinBuilder builtinOnlyBuilder(ModuleBundle::BuiltinBuilder::Type::BUILTIN_ONLY);\n  builtinOnlyBuilder.addObject<TestType, TestTypeWrapper>(baz);\n  registryBuilder.add(builtinOnlyBuilder.finish());\n\n  ModuleBundle::BundleBuilder bundleBuilder(BASE);\n  auto quxSource = kj::str(\"export const foo = 123;\");\n  bundleBuilder.addEsmModule(\"qux\", quxSource, Module::Flags::MAIN);\n  registryBuilder.add(bundleBuilder.finish());\n\n  auto registry = registryBuilder.finish();\n\n  constexpr auto resolve = [](const auto& registry, ResolveContext::Type type, const Url& id) {\n    ResolveContext context{\n      .type = type,\n      .source = ResolveContext::Source::INTERNAL,\n      .normalizedSpecifier = id,\n      .referrerNormalizedSpecifier = BASE,\n    };\n    return registry->lookup(context);\n  };\n\n  {\n    // The fallback module is resolved when using a bundle context\n    auto& module = KJ_ASSERT_NONNULL(resolve(registry, ResolveContext::Type::BUNDLE, foo));\n    KJ_ASSERT(module.id() == foo);\n    KJ_ASSERT(module.type() == Module::Type::FALLBACK);\n    KJ_ASSERT(!module.isEsm());\n    KJ_ASSERT(!module.isMain());\n  }\n\n  {\n    // A built-in module is resolved when using a bundle context\n    auto& module = KJ_ASSERT_NONNULL(resolve(registry, ResolveContext::Type::BUNDLE, bar));\n    KJ_ASSERT(module.id() == bar);\n    KJ_ASSERT(module.type() == Module::Type::BUILTIN);\n    KJ_ASSERT(module.isEsm());\n    KJ_ASSERT(!module.isMain());\n  }\n\n  {\n    // A bundle module is resolved when using a bundle context\n    auto& module = KJ_ASSERT_NONNULL(resolve(registry, ResolveContext::Type::BUNDLE, qux));\n    KJ_ASSERT(module.id() == qux);\n    KJ_ASSERT(module.type() == Module::Type::BUNDLE);\n    KJ_ASSERT(module.isEsm());\n    KJ_ASSERT(module.isMain());\n  }\n\n  {\n    // A built-in module is resolved when using a builtin context\n    auto& module = KJ_ASSERT_NONNULL(resolve(registry, ResolveContext::Type::BUILTIN, bar));\n    KJ_ASSERT(module.id() == bar);\n    KJ_ASSERT(module.type() == Module::Type::BUILTIN);\n    KJ_ASSERT(module.isEsm());\n    KJ_ASSERT(!module.isMain());\n  }\n\n  {\n    // A built-in only module is resolved when using a built-in context\n    auto& module = KJ_ASSERT_NONNULL(resolve(registry, ResolveContext::Type::BUILTIN, baz));\n    KJ_ASSERT(module.id() == baz);\n    KJ_ASSERT(module.type() == Module::Type::BUILTIN_ONLY);\n    KJ_ASSERT(!module.isEsm());\n    KJ_ASSERT(!module.isMain());\n  }\n\n  {\n    // A built-in only module is resolved when using a built-in only context\n    auto& module = KJ_ASSERT_NONNULL(resolve(registry, ResolveContext::Type::BUILTIN_ONLY, baz));\n    KJ_ASSERT(module.id() == baz);\n    KJ_ASSERT(module.type() == Module::Type::BUILTIN_ONLY);\n    KJ_ASSERT(!module.isEsm());\n    KJ_ASSERT(!module.isMain());\n  }\n\n  // A built-in only module cannot be resolved from a bundle context\n  KJ_ASSERT(resolve(registry, ResolveContext::Type::BUNDLE, baz) == kj::none);\n\n  // Fallback modules cannot be resolved from a built-in context\n  KJ_ASSERT(resolve(registry, ResolveContext::Type::BUILTIN, foo) == kj::none);\n  KJ_ASSERT(resolve(registry, ResolveContext::Type::BUILTIN_ONLY, foo) == kj::none);\n\n  // Bundle modules cannot be resolved from a built-in or built-in only context\n  KJ_ASSERT(resolve(registry, ResolveContext::Type::BUILTIN, qux) == kj::none);\n  KJ_ASSERT(resolve(registry, ResolveContext::Type::BUILTIN_ONLY, qux) == kj::none);\n\n  // We should have seen eleven distinct resolution events.\n  KJ_ASSERT(observer.modules.size() == 11);\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Bundle shadows built-in\") {\n  // A bundle module can shadow a built-in\n  ResolveObserverImpl observer;\n  ModuleRegistry::Builder registryBuilder(observer, BASE);\n\n  const auto foo = \"foo:bar\"_url;\n\n  ModuleBundle::BuiltinBuilder builtinBuilder;\n  auto source = \"export const foo = 123;\"_kjc;\n  builtinBuilder.addEsm(foo, source.asArray());\n  registryBuilder.add(builtinBuilder.finish());\n\n  ModuleBundle::BundleBuilder bundleBuilder(BASE);\n  auto bundleSource = kj::str(\"export const foo = 456;\");\n  bundleBuilder.addEsmModule(\"foo:bar\", bundleSource, Module::Flags::MAIN);\n  registryBuilder.add(bundleBuilder.finish());\n\n  auto registry = registryBuilder.finish();\n\n  ResolveContext context{\n    .type = ResolveContext::Type::BUNDLE,\n    .source = ResolveContext::Source::INTERNAL,\n    .normalizedSpecifier = foo,\n    .referrerNormalizedSpecifier = BASE,\n  };\n\n  auto& module = KJ_ASSERT_NONNULL(registry->lookup(context));\n  KJ_ASSERT(module.id() == foo);\n  KJ_ASSERT(module.type() == Module::Type::BUNDLE);\n  KJ_ASSERT(module.isEsm());\n  KJ_ASSERT(module.isMain());\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Attaching a module registry works\") {\n  PREAMBLE(([&](Lock& js) {\n    ResolveObserver resolveObserver;\n    CompilationObserver compilationObserver;\n    ModuleRegistry::Builder registryBuilder(resolveObserver, BASE);\n\n    ModuleBundle::BundleBuilder bundleBuilder(BASE);\n    auto source = kj::str(\"export default 123; export const m = 'abc';\");\n    // Done this way to avoid including the nullptr at the end...\n    bundleBuilder.addEsmModule(\"main\", source);\n\n    auto mainSource = kj::str(\"import foo from 'main'; export default foo;\");\n    bundleBuilder.addEsmModule(\"worker1\", mainSource.first(mainSource.size()), Module::Flags::MAIN);\n\n    registryBuilder.add(bundleBuilder.finish());\n\n    auto registry = registryBuilder.finish();\n\n    const auto id = \"file:///main\"_url;\n\n    ResolveContext resolveContext{\n      .type = ResolveContext::Type::BUNDLE,\n      .source = ResolveContext::Source::INTERNAL,\n      .normalizedSpecifier = id,\n      .referrerNormalizedSpecifier = BASE,\n    };\n    KJ_ASSERT(registry->lookup(resolveContext) != kj::none);\n\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    js.tryCatch([&] {\n      auto val = ModuleRegistry::resolve(js, \"file:///worker1\");\n      KJ_ASSERT(val.isNumber());\n    }, [&](Value exception) { js.throwException(kj::mv(exception)); });\n\n    js.tryCatch([&] {\n      auto val = ModuleRegistry::resolve(js, \"worker1\");\n      KJ_ASSERT(val.isNumber());\n    }, [&](Value exception) { js.throwException(kj::mv(exception)); });\n\n    js.tryCatch([&] {\n      auto val = ModuleRegistry::resolve(js, \"./.././../worker1\");\n      KJ_ASSERT(val.isNumber());\n    }, [&](Value exception) { js.throwException(kj::mv(exception)); });\n\n    js.tryCatch([&] {\n      auto val = ModuleRegistry::resolve(js, \"file:///main\", \"m\"_kjc);\n      KJ_ASSERT(val.isString());\n    }, [&](Value exception) { js.throwException(kj::mv(exception)); });\n  }));\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Basic types of modules work (text, data, json, wasm)\") {\n  PREAMBLE(([&](Lock& js) {\n    ResolveObserver resolveObserver;\n    CompilationObserver compilationObserver;\n    ModuleRegistry::Builder registryBuilder(resolveObserver, BASE);\n\n    ModuleBundle::BundleBuilder bundleBuilder(BASE);\n    auto abcSource = kj::str(\"hello\");\n    auto xyzData = kj::heapArray<kj::byte>({1, 2, 3});\n    bundleBuilder.addSyntheticModule(\"abc\", Module::newTextModuleHandler(abcSource));\n    bundleBuilder.addSyntheticModule(\"xyz\", Module::newDataModuleHandler(xyzData));\n\n    auto json = kj::str(\"{\\\"foo\\\":123}\");\n    bundleBuilder.addSyntheticModule(\"json\", Module::newJsonModuleHandler(json.first(json.size())));\n\n    auto wasm = kj::heapArray<kj::byte>({\n      0x00,\n      0x61,\n      0x73,\n      0x6d,\n      0x01,\n      0x00,\n      0x00,\n      0x00,\n      0x01,\n      0x07,\n      0x01,\n      0x60,\n      0x02,\n      0x7f,\n      0x7f,\n      0x01,\n      0x7f,\n      0x03,\n      0x02,\n      0x01,\n      0x00,\n      0x07,\n      0x07,\n      0x01,\n      0x03,\n      0x61,\n      0x64,\n      0x64,\n      0x00,\n      0x00,\n      0x0a,\n      0x09,\n      0x01,\n      0x07,\n      0x00,\n      0x20,\n      0x00,\n      0x20,\n      0x01,\n      0x6a,\n      0x0b,\n    });\n    bundleBuilder.addSyntheticModule(\"wasm\", Module::newWasmModuleHandler(wasm));\n\n    auto mainSource2 = kj::str(\"export { default as abc } from 'abc';\"\n                               \"export { default as xyz } from 'xyz';\"\n                               \"export { default as json } from 'json';\"\n                               \"export { default as wasm } from 'wasm';\"\n                               \"export { default as wasm2 } from 'wasm?a';\");\n\n    bundleBuilder.addEsmModule(\"worker\", mainSource2, Module::Flags::MAIN);\n\n    registryBuilder.add(bundleBuilder.finish());\n\n    auto registry = registryBuilder.finish();\n\n    const auto id = \"file:///worker\"_url;\n\n    ResolveContext resolveContext{\n      .type = ResolveContext::Type::BUNDLE,\n      .source = ResolveContext::Source::INTERNAL,\n      .normalizedSpecifier = id,\n      .referrerNormalizedSpecifier = BASE,\n    };\n    auto& resolved KJ_UNUSED = KJ_ASSERT_NONNULL(registry->lookup(resolveContext));\n\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    js.tryCatch([&] {\n      auto val = ModuleRegistry::resolve(js, \"file:///worker\", \"abc\"_kjc);\n      KJ_ASSERT(val.isString());\n      KJ_ASSERT(kj::str(val) == \"hello\"_kjc);\n    }, [&](Value exception) { js.throwException(kj::mv(exception)); });\n\n    js.tryCatch([&] {\n      auto val = ModuleRegistry::resolve(js, \"file:///worker\", \"xyz\"_kjc);\n      KJ_ASSERT(val.isArrayBuffer());\n    }, [&](Value exception) { js.throwException(kj::mv(exception)); });\n\n    js.tryCatch([&] {\n      auto val1 = ModuleRegistry::resolve(js, \"file:///worker\", \"json\"_kjc);\n      auto val2 = ModuleRegistry::resolve(js, \"file:///json\", \"default\"_kjc);\n      KJ_ASSERT(val1.isObject());\n      KJ_ASSERT(val2.isObject());\n      KJ_ASSERT(val1.strictEquals(val2));\n      auto obj = KJ_ASSERT_NONNULL(val1.tryCast<JsObject>());\n      KJ_ASSERT(obj.get(js, \"foo\").isNumber());\n    }, [&](Value exception) { js.throwException(kj::mv(exception)); });\n\n    js.tryCatch([&] {\n      auto wasm1 = ModuleRegistry::resolve(js, \"file:///worker\", \"wasm\"_kjc);\n      auto wasm2 = ModuleRegistry::resolve(js, \"file:///wasm\", \"default\"_kjc);\n      auto wasm3 = ModuleRegistry::resolve(js, \"file:///worker\", \"wasm2\"_kjc);\n      KJ_ASSERT(wasm1.isWasmModuleObject());\n      KJ_ASSERT(wasm2.isWasmModuleObject());\n      KJ_ASSERT(wasm3.isWasmModuleObject());\n      KJ_ASSERT(wasm1.strictEquals(wasm2));\n      KJ_ASSERT(!wasm1.strictEquals(wasm3));\n    }, [&](Value exception) { js.throwException(kj::mv(exception)); });\n  }));\n}\n\n// ======================================================================================\n\nKJ_TEST(\"compileEvalFunction in synthetic module works\") {\n  PREAMBLE([&](Lock& js) {\n    CompilationObserver compilationObserver;\n    ResolveObserver resolveObserver;\n    ModuleBundle::BundleBuilder bundleBuilder(BASE);\n    bundleBuilder.addSyntheticModule(\"abc\",\n        [](Lock& js, const Url& id, const Module::ModuleNamespace& ns,\n            const CompilationObserver& observer) mutable -> bool {\n      // The compileEvalFunction is used in CommonJs/Node.js compat modules to\n      // evaluate the module as a function rather than as an ESM. This test just\n      // verifies that compileEvalFunction works as expected.\n      auto ext = js.alloc<TestType>(js, id);\n      auto& wrapper = TestIsolate_TypeWrapper::from(js.v8Isolate);\n      auto fn = Module::compileEvalFunction(js, \"bar(123);\"_kj, \"foo\"_kj,\n          JsObject(wrapper.wrap(js, js.v8Context(), kj::none, ext.addRef())), observer);\n      return js.tryCatch([&] {\n        fn(js);\n        KJ_ASSERT(ext->barCalled);\n        return ns.setDefault(js, js.num(123));\n      }, [&](Value exception) {\n        js.v8Isolate->ThrowException(exception.getHandle(js));\n        return false;\n      });\n    });\n\n    auto source = kj::str(\"import 'abc'\");\n    bundleBuilder.addEsmModule(\"main\", source, Module::Flags::MAIN);\n\n    auto registry =\n        ModuleRegistry::Builder(resolveObserver, BASE).add(bundleBuilder.finish()).finish();\n\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    js.tryCatch([&] {\n      auto val = ModuleRegistry::resolve(js, \"file:///main\");\n      KJ_ASSERT(val.isUndefined());\n    }, [&](Value exception) { js.throwException(kj::mv(exception)); });\n  });\n}\n\n// ======================================================================================\n\nKJ_TEST(\"import.meta works as expected\") {\n  PREAMBLE([&](Lock& js) {\n    ResolveObserver ResolveObserver;\n    CompilationObserver compilationObserver;\n\n    ModuleBundle::BundleBuilder bundleBuilder(BASE);\n    auto foo = kj::str(\"export default import.meta\");\n    bundleBuilder.addEsmModule(\"foo\", foo);\n    auto bar = kj::str(\"export default import.meta\");\n    bundleBuilder.addEsmModule(\"foo/././././bar\", bar, Module::Flags::MAIN);\n    auto registry =\n        ModuleRegistry::Builder(ResolveObserver, BASE).add(bundleBuilder.finish()).finish();\n\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    js.tryCatch([&] {\n      auto val = ModuleRegistry::resolve(js, \"file:///foo\");\n      KJ_ASSERT(val.isObject());\n      auto obj = KJ_ASSERT_NONNULL(val.tryCast<JsObject>());\n      JsValue url = obj.get(js, \"url\");\n      JsValue main = obj.get(js, \"main\");\n      JsValue res = obj.get(js, \"resolve\");\n\n      KJ_ASSERT(url.isString());\n      KJ_ASSERT(main.isBoolean());\n      KJ_ASSERT(res.isFunction());\n\n      KJ_ASSERT(url.toString(js) == \"file:///foo\"_kj);\n\n      auto mainVal = KJ_ASSERT_NONNULL(main.tryCast<JsBoolean>());\n      KJ_ASSERT(!mainVal.value(js));\n\n      auto& wrapper = TestIsolate_TypeWrapper::from(js.v8Isolate);\n      KJ_IF_SOME(fn,\n          wrapper.tryUnwrap(\n              js, js.v8Context(), res, (Function<kj::String(kj::String)>*)nullptr, kj::none)) {\n        KJ_ASSERT(fn(js, kj::str(\"foo/bar\")) == \"file:///foo/bar\"_kj);\n      } else {\n      }\n    }, [&](Value exception) { js.throwException(kj::mv(exception)); });\n\n    js.tryCatch([&] {\n      auto val = ModuleRegistry::resolve(js, \"file:///foo/bar\");\n      KJ_ASSERT(val.isObject());\n      auto obj = KJ_ASSERT_NONNULL(val.tryCast<JsObject>());\n      JsValue url = obj.get(js, \"url\");\n      JsValue main = obj.get(js, \"main\");\n      JsValue res = obj.get(js, \"resolve\");\n\n      KJ_ASSERT(url.isString());\n      KJ_ASSERT(main.isBoolean());\n      KJ_ASSERT(res.isFunction());\n\n      KJ_ASSERT(url.toString(js) == \"file:///foo/bar\"_kj);\n\n      auto mainVal = KJ_ASSERT_NONNULL(main.tryCast<JsBoolean>());\n      KJ_ASSERT(mainVal.value(js));\n    }, [&](Value exception) { js.throwException(kj::mv(exception)); });\n  });\n}\n\n// ======================================================================================\n\nKJ_TEST(\"import specifiers with query params and hash fragments work\") {\n  // If we have two imports with the same base specifier URL\n  // but different query params or hash fragments, they should\n  // resolve to the same underlying Module but get evaluated\n  // separately. This means the EvaluationCallback can be called\n  // multiple times.\n\n  PREAMBLE([&](jsg::Lock& js) {\n    ResolveObserver ResolveObserver;\n    CompilationObserver compilationObserver;\n\n    ModuleBundle::BundleBuilder bundleBuilder(BASE);\n    auto foo = kj::str(\"export default import.meta\");\n    bundleBuilder.addEsmModule(\"foo\", foo);\n\n    auto registry =\n        ModuleRegistry::Builder(ResolveObserver, BASE).add(bundleBuilder.finish()).finish();\n\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    js.tryCatch([&] {\n      auto val1 = ModuleRegistry::resolve(js, \"file:///foo?1\");\n      auto val2 = ModuleRegistry::resolve(js, \"file:///foo?2\");\n      auto val3 = ModuleRegistry::resolve(js, \"file:///foo#1\");\n      auto val4 = ModuleRegistry::resolve(js, \"file:///foo#2\");\n\n      KJ_ASSERT(val1.isObject());\n      KJ_ASSERT(val2.isObject());\n      KJ_ASSERT(val3.isObject());\n      KJ_ASSERT(val4.isObject());\n      KJ_ASSERT(!val1.strictEquals(val2));\n      KJ_ASSERT(!val2.strictEquals(val3));\n      KJ_ASSERT(!val3.strictEquals(val4));\n      KJ_ASSERT(!val4.strictEquals(val1));\n\n      auto obj = KJ_ASSERT_NONNULL(val1.tryCast<JsObject>());\n      auto url = obj.get(js, \"url\");\n      KJ_ASSERT(url.isString());\n      // The import.meta.url should include the query param and hash fragment\n      KJ_ASSERT(url.toString(js) == \"file:///foo?1\"_kj);\n    }, [&](Value exception) { js.throwException(kj::mv(exception)); });\n  });\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Previously resolved modules not found with incompatible resolve context\") {\n  // If we have a built-in only module that is resolved with a built-in context, that\n  // should not be found when later resolving with a bundle context.\n\n  PREAMBLE([&](Lock& js) {\n    ResolveObserverImpl observer;\n    CompilationObserver compilationObserver;\n\n    ModuleBundle::BuiltinBuilder builtinBuilder(ModuleBundle::BuiltinBuilder::Type::BUILTIN_ONLY);\n    const auto foo = \"foo:bar\"_url;\n\n    auto source = \"export default 123;\"_kjc;\n    builtinBuilder.addEsm(foo, source.first(source.size()).attach(kj::mv(source)));\n\n    auto barData = kj::heapArray<kj::byte>({1, 2, 3});\n\n    ModuleBundle::BundleBuilder bundleBuilder(BASE);\n    bundleBuilder.addSyntheticModule(\"bar\", Module::newDataModuleHandler(barData));\n\n    auto registry = ModuleRegistry::Builder(observer, BASE)\n                        .add(builtinBuilder.finish())\n                        .add(bundleBuilder.finish())\n                        .finish();\n\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    js.tryCatch([&] {\n      // The built-in only foo:bar module should be found when using a built-in context\n      auto value1 =\n          ModuleRegistry::resolve(js, \"foo:bar\", \"default\"_kjc, ResolveContext::Type::BUILTIN);\n\n      KJ_ASSERT(value1.isNumber());\n\n      // But since the module is an built-in only. it should not be found when\n      // resolving with a bundle context.\n      ModuleRegistry::resolve(js, \"foo:bar\", \"default\"_kjc, ResolveContext::Type::BUNDLE);\n      JSG_FAIL_REQUIRE(Error, \"Should have thrown\");\n    }, [&](Value exception) {\n      auto str = kj::str(exception.getHandle(js));\n      KJ_ASSERT(str == \"Error: Module not found: foo:bar\");\n    });\n\n    // Likewise, the bar module should be found when using a bundle context\n    js.tryCatch([&] {\n      auto value2 =\n          ModuleRegistry::resolve(js, \"file:///bar\", \"default\"_kjc, ResolveContext::Type::BUNDLE);\n      KJ_ASSERT(value2.isArrayBuffer());\n\n      // But should not be found from a built-in context\n      ModuleRegistry::resolve(js, \"file:///bar\", \"default\"_kjc, ResolveContext::Type::BUILTIN);\n      JSG_FAIL_REQUIRE(Error, \"Should have thrown\");\n    }, [&](Value exception) {\n      auto str = kj::str(exception.getHandle(js));\n      KJ_ASSERT(str == \"Error: Module not found: file:///bar\");\n    });\n  });\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Awaiting top-level dynamic import in synchronous require works as expected\") {\n  PREAMBLE([&](Lock& js) {\n    ResolveObserverImpl observer;\n    CompilationObserver compilationObserver;\n\n    ModuleBundle::BundleBuilder bundleBuilder(BASE);\n    auto foo = kj::str(\"export default (await import('bar')).default;\");\n    bundleBuilder.addEsmModule(\"foo\", foo);\n\n    auto bar = kj::str(\"export default 123;\");\n    bundleBuilder.addEsmModule(\"bar\", bar);\n\n    auto registry = ModuleRegistry::Builder(observer, BASE).add(bundleBuilder.finish()).finish();\n\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    ModuleRegistry::resolve(js, \"file:///foo\", \"default\"_kjc);\n  });\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Awaiting a never resolved promise in synchronous require fails as expected\") {\n  PREAMBLE([&](Lock& js) {\n    ResolveObserverImpl observer;\n    CompilationObserver compilationObserver;\n\n    ModuleBundle::BundleBuilder bundleBuilder(BASE);\n    auto foo = kj::str(\"const p = new Promise(() => {}); await p;\");\n    bundleBuilder.addEsmModule(\"foo\", foo);\n\n    auto registry = ModuleRegistry::Builder(observer, BASE).add(bundleBuilder.finish()).finish();\n\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    js.tryCatch([&] {\n      ModuleRegistry::resolve(js, \"file:///foo\", \"default\"_kjc);\n      KJ_FAIL_ASSERT(\"Should have failed\");\n    }, [&](Value exception) {\n      auto str = kj::str(exception.getHandle(js));\n      KJ_ASSERT(str ==\n          \"Error: Use of top-level await in a synchronously \"\n          \"required module is restricted to promises that are resolved \"\n          \"synchronously. This includes any top-level awaits in the \"\n          \"entrypoint module for a worker. Specifier: \\\"file:///foo\\\".\");\n    });\n  });\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Throwing an exception inside a ESM module works as expected\") {\n  PREAMBLE([&](Lock& js) {\n    ResolveObserverImpl observer;\n    CompilationObserver compilationObserver;\n\n    ModuleBundle::BundleBuilder bundleBuilder(BASE);\n    auto foo = kj::str(\"throw new Error('foo');\");\n    bundleBuilder.addEsmModule(\"foo\", foo);\n\n    auto registry = ModuleRegistry::Builder(observer, BASE).add(bundleBuilder.finish()).finish();\n\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    js.tryCatch([&] {\n      ModuleRegistry::resolve(js, \"file:///foo\", \"default\"_kjc);\n      JSG_FAIL_REQUIRE(Error, \"Should have thrown\");\n    }, [&](Value exception) {\n      auto str = kj::str(exception.getHandle(js));\n      KJ_ASSERT(str == \"Error: foo\");\n    });\n  });\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Syntax error in ESM module is properly reported\") {\n  PREAMBLE([&](Lock& js) {\n    ResolveObserverImpl observer;\n    CompilationObserver compilationObserver;\n\n    ModuleBundle::BundleBuilder bundleBuilder(BASE);\n\n    auto foo = kj::str(\"export default 123; syntax error\");\n    bundleBuilder.addEsmModule(\"foo\", foo);\n\n    auto registry = ModuleRegistry::Builder(observer, BASE).add(bundleBuilder.finish()).finish();\n\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    js.tryCatch(\n        [&] { ModuleRegistry::resolve(js, \"file:///foo\", \"default\"_kjc); }, [&](Value exception) {\n      auto str = kj::str(exception.getHandle(js));\n      KJ_ASSERT(str == \"SyntaxError: Unexpected identifier 'error'\");\n    });\n  });\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Throwing an exception inside a CJS-style eval module works as expected\") {\n  PREAMBLE([&](Lock& js) {\n    ResolveObserverImpl observer;\n    CompilationObserver compilationObserver;\n\n    ModuleBundle::BundleBuilder bundleBuilder(BASE);\n\n    kj::String source = kj::str(\"exports.foo = 123; throw new Error('bar');\");\n\n    bundleBuilder.addSyntheticModule(\"foo\",\n        Module::newCjsStyleModuleHandler<TestType, TestIsolate_TypeWrapper>(source, \"foo\"_kj));\n\n    auto registry = ModuleRegistry::Builder(observer, BASE).add(bundleBuilder.finish()).finish();\n\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    js.tryCatch([&] {\n      ModuleRegistry::resolve(js, \"file:///foo\", \"foo\"_kjc);\n      JSG_FAIL_REQUIRE(Error, \"Should have thrown\");\n    }, [&](Value exception) {\n      auto str = kj::str(exception.getHandle(js));\n      KJ_ASSERT(str == \"Error: bar\");\n    });\n  });\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Invalid JSON syntax module throws exception as expected\") {\n  PREAMBLE([&](Lock& js) {\n    ResolveObserverImpl observer;\n    CompilationObserver compilationObserver;\n\n    ModuleBundle::BundleBuilder bundleBuilder(BASE);\n    auto json = kj::str(\"not valid json\");\n    bundleBuilder.addSyntheticModule(\"foo\", Module::newJsonModuleHandler(json.first(json.size())));\n\n    auto esm = kj::str(\"import foo from 'foo'\");\n    bundleBuilder.addEsmModule(\"bar\", esm, Module::Flags::MAIN);\n\n    auto registry = ModuleRegistry::Builder(observer, BASE).add(bundleBuilder.finish()).finish();\n\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    js.tryCatch([&] {\n      ModuleRegistry::resolve(js, \"file:///foo\", \"default\"_kjc);\n      JSG_FAIL_REQUIRE(Error, \"Should have thrown\");\n    }, [&](Value exception) {\n      auto str = kj::str(exception.getHandle(js));\n      KJ_ASSERT(str == \"SyntaxError: Unexpected token 'o', \\\"not valid json\\\" is not valid JSON\");\n    });\n\n    // We can try multiple times and it doesn't matter.\n    js.tryCatch([&] {\n      ModuleRegistry::resolve(js, \"file:///foo\", \"default\"_kjc);\n      JSG_FAIL_REQUIRE(Error, \"Should have thrown\");\n    }, [&](Value exception) {\n      auto str = kj::str(exception.getHandle(js));\n      KJ_ASSERT(str == \"SyntaxError: Unexpected token 'o', \\\"not valid json\\\" is not valid JSON\");\n    });\n\n    // We get the same error even if statically imported after the previous imports\n    js.tryCatch([&] {\n      ModuleRegistry::resolve(js, \"file:///bar\", \"default\"_kjc);\n      JSG_FAIL_REQUIRE(Error, \"Should have thrown\");\n    }, [&](Value exception) {\n      auto str = kj::str(exception.getHandle(js));\n      KJ_ASSERT(str == \"SyntaxError: Unexpected token 'o', \\\"not valid json\\\" is not valid JSON\");\n    });\n  });\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Recursive import works or fails as expected\") {\n  PREAMBLE([&](Lock& js) {\n    ResolveObserverImpl observer;\n    CompilationObserver compilationObserver;\n\n    ModuleBundle::BundleBuilder bundleBuilder(BASE);\n\n    // A recursive import with an ESM works just fine...\n    auto foo = kj::str(\"import foo from 'foo'; export default 123;\");\n    bundleBuilder.addEsmModule(\"foo\", foo);\n\n    auto source = kj::str(\"require('bar')\");\n\n    // A CommonJS-style module, however, does not allow recursive evaluation.\n    bundleBuilder.addSyntheticModule(\"bar\",\n        Module::newCjsStyleModuleHandler<TestType, TestIsolate_TypeWrapper>(source, \"bar\"_kj));\n\n    auto registry = ModuleRegistry::Builder(observer, BASE).add(bundleBuilder.finish()).finish();\n\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    auto val1 = ModuleRegistry::resolve(js, \"file:///foo\", \"default\"_kjc);\n    KJ_ASSERT(val1.isNumber());\n\n    js.tryCatch(\n        [&] { ModuleRegistry::resolve(js, \"file:///bar\", \"default\"_kjc); }, [&](Value exception) {\n      auto str = kj::str(exception.getHandle(js));\n      KJ_ASSERT(str == \"Error: Module cannot be recursively evaluated: file:///bar\");\n    });\n  });\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Recursively require ESM from CJS required from ESM fails as expected (dynamic import)\") {\n  PREAMBLE([&](Lock& js) {\n    ResolveObserverImpl observer;\n    CompilationObserver compilationObserver;\n\n    ModuleBundle::BundleBuilder bundleBuilder(BASE);\n\n    // In this test, we have an ESM module (bar) that imports a CJS style\n    // module (foo) that synchronously tries to require the ESM module (bar).\n\n    // The circular dependency between foo and baz here, as CJS style modules,\n    // should be ok in that it should not throw an error. However, the circular\n    // dependency between foo and bar is more problematic since it is forbidden\n    // to depend on an ESM that has not yet been fully resolved.\n\n    auto source1 = kj::str(\"exports = require('foo');\");\n    auto source2 = kj::str(\"require('baz'); exports = require('bar');\");\n\n    bundleBuilder.addSyntheticModule(\"baz\",\n        Module::newCjsStyleModuleHandler<TestType, TestIsolate_TypeWrapper>(source1, \"baz\"_kj));\n\n    bundleBuilder.addSyntheticModule(\"foo\",\n        Module::newCjsStyleModuleHandler<TestType, TestIsolate_TypeWrapper>(source2, \"foo\"_kj));\n\n    auto bar = kj::str(\"export default {}; await import('foo');\");\n    bundleBuilder.addEsmModule(\"bar\", bar);\n\n    auto registry = ModuleRegistry::Builder(observer, BASE).add(bundleBuilder.finish()).finish();\n\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    js.tryCatch([&] {\n      ModuleRegistry::resolve(js, \"file:///bar\", \"default\"_kjc);\n      JSG_FAIL_REQUIRE(Error, \"should have failed\");\n    }, [&](Value exception) {\n      auto str = kj::str(exception.getHandle(js));\n      KJ_ASSERT(str == \"Error: Circular dependency when resolving module: file:///bar\");\n    });\n  });\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Recursively require ESM from CJS required from ESM fails as expected (static import)\") {\n  PREAMBLE([&](Lock& js) {\n    ResolveObserverImpl observer;\n    CompilationObserver compilationObserver;\n\n    ModuleBundle::BundleBuilder bundleBuilder(BASE);\n\n    // In this test, we have an ESM module (bar) that imports a CJS style\n    // module (foo) that synchronously tries to require the ESM module (bar).\n\n    // The circular dependency between foo and baz here, as CJS style modules,\n    // should be ok in that it should not throw an error. However, the circular\n    // dependency between foo and bar is more problematic since it is forbidden\n    // to depend on an ESM that has not yet been fully resolved.\n\n    auto source1 = kj::str(\"exports = require('foo');\");\n    auto source2 = kj::str(\"require('baz'); exports = require('bar');\");\n\n    bundleBuilder.addSyntheticModule(\"baz\",\n        Module::newCjsStyleModuleHandler<TestType, TestIsolate_TypeWrapper>(source1, \"baz\"_kj));\n\n    bundleBuilder.addSyntheticModule(\"foo\",\n        Module::newCjsStyleModuleHandler<TestType, TestIsolate_TypeWrapper>(source2, \"foo\"_kj));\n\n    auto bar = kj::str(\"export default {}; import bar from 'foo';\");\n    bundleBuilder.addEsmModule(\"bar\", bar);\n\n    auto registry = ModuleRegistry::Builder(observer, BASE).add(bundleBuilder.finish()).finish();\n\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    js.tryCatch([&] {\n      ModuleRegistry::resolve(js, \"file:///bar\", \"default\"_kjc);\n      JSG_FAIL_REQUIRE(Error, \"should have failed\");\n    }, [&](Value exception) {\n      auto str = kj::str(exception.getHandle(js));\n      KJ_ASSERT(str == \"Error: Circular dependency when resolving module: file:///bar\");\n    });\n  });\n}\n\n// ======================================================================================\nKJ_TEST(\"Resolution occurs relative to the referrer\") {\n  ResolveObserver observer;\n  CompilationObserver compilationObserver;\n  ModuleRegistry::Builder registryBuilder(observer, BASE);\n\n  ModuleBundle::BundleBuilder builder(BASE);\n  builder.addSyntheticModule(\"foo/bar\", Module::newDataModuleHandler(nullptr));\n  builder.addSyntheticModule(\"bar\", Module::newDataModuleHandler(nullptr));\n\n  // The base URL of the referrer is file:///foo/ ... so in each of the\n  // following cases, the specifier should be resolved relative to that.\n  // For instance, 'bar' should resolve as file:///foo/bar, while '../bar'\n  // should resolve as file:///bar\n\n  auto bar = kj::str(\"export * as abc from 'bar';\"           // file:///foo/bar\n                     \"export * as def from './bar';\"         // file:///foo/bar\n                     \"export * as ghi from '../bar';\"        // file:///bar\n                     \"export * as jkl from '/bar';\"          // file:///bar\n                     \"export * as lmn from '../foo/bar';\");  // file:///foo/bar\n  builder.addEsmModule(\"foo/\", bar);\n\n  auto registry = registryBuilder.add(builder.finish()).finish();\n\n  PREAMBLE([&](Lock& js) {\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    js.tryCatch([&] {\n      auto abc = ModuleRegistry::resolve(js, \"file:///foo/\", \"abc\"_kjc);\n      auto def = ModuleRegistry::resolve(js, \"file:///foo/\", \"def\"_kjc);\n      auto ghi = ModuleRegistry::resolve(js, \"file:///foo/\", \"ghi\"_kjc);\n      auto jkl = ModuleRegistry::resolve(js, \"file:///foo/\", \"jkl\"_kjc);\n      auto lmn = ModuleRegistry::resolve(js, \"file:///foo/\", \"lmn\"_kjc);\n\n      KJ_ASSERT(abc.strictEquals(def));\n      KJ_ASSERT(abc.strictEquals(lmn));\n      KJ_ASSERT(!abc.strictEquals(ghi));\n      KJ_ASSERT(ghi.strictEquals(jkl));\n    }, [&](Value exception) { js.throwException(kj::mv(exception)); });\n  });\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Building a bundle from a capnp description works\") {\n\n  capnp::MallocMessageBuilder builder;\n  auto bundle = builder.initRoot<workerd::jsg::Bundle>();\n\n  auto modules = bundle.initModules(3);\n\n  auto str = kj::str(\"export default 1+1;\");\n  auto wasm = kj::heapArray<kj::byte>({\n    0x00,\n    0x61,\n    0x73,\n    0x6d,\n    0x01,\n    0x00,\n    0x00,\n    0x00,\n    0x01,\n    0x07,\n    0x01,\n    0x60,\n    0x02,\n    0x7f,\n    0x7f,\n    0x01,\n    0x7f,\n    0x03,\n    0x02,\n    0x01,\n    0x00,\n    0x07,\n    0x07,\n    0x01,\n    0x03,\n    0x61,\n    0x64,\n    0x64,\n    0x00,\n    0x00,\n    0x0a,\n    0x09,\n    0x01,\n    0x07,\n    0x00,\n    0x20,\n    0x00,\n    0x20,\n    0x01,\n    0x6a,\n    0x0b,\n  });\n  auto data = kj::heapArray<kj::byte>({1, 2, 3});\n\n  modules[0].setName(\"foo:bar\");\n  modules[0].setSrc(str.asBytes());\n  modules[0].setType(workerd::jsg::ModuleType::BUILTIN);\n\n  modules[1].setName(\"foo:baz\");\n  modules[1].setWasm(wasm);\n  modules[1].setType(workerd::jsg::ModuleType::BUILTIN);\n\n  modules[2].setName(\"foo:qux\");\n  modules[2].setSrc(data.asBytes());\n  modules[2].setType(workerd::jsg::ModuleType::BUILTIN);\n\n  ModuleBundle::BuiltinBuilder bundleBuilder;\n  ModuleBundle::getBuiltInBundleFromCapnp(bundleBuilder, bundle.asReader());\n  auto moduleBundle = bundleBuilder.finish();\n\n  {\n    const auto foo = \"foo:bar\"_url;\n    ResolveContext context{\n      .type = ResolveContext::Type::BUILTIN,\n      .source = ResolveContext::Source::INTERNAL,\n      .normalizedSpecifier = foo,\n      .referrerNormalizedSpecifier = BASE,\n    };\n    auto resolved = KJ_ASSERT_NONNULL(moduleBundle->lookup(context));\n    auto& module = KJ_ASSERT_NONNULL(resolved.module);\n\n    KJ_ASSERT(module.id() == foo);\n  }\n\n  {\n    const auto bar = \"foo:baz\"_url;\n    ResolveContext context{\n      .type = ResolveContext::Type::BUILTIN,\n      .source = ResolveContext::Source::INTERNAL,\n      .normalizedSpecifier = bar,\n      .referrerNormalizedSpecifier = BASE,\n    };\n    auto resolved = KJ_ASSERT_NONNULL(moduleBundle->lookup(context));\n    auto& module = KJ_ASSERT_NONNULL(resolved.module);\n    KJ_ASSERT(module.id() == bar);\n  }\n\n  {\n    const auto qux = \"foo:qux\"_url;\n    ResolveContext context{\n      .type = ResolveContext::Type::BUILTIN,\n      .source = ResolveContext::Source::INTERNAL,\n      .normalizedSpecifier = qux,\n      .referrerNormalizedSpecifier = BASE,\n    };\n    auto resolved = KJ_ASSERT_NONNULL(moduleBundle->lookup(context));\n    auto& module = KJ_ASSERT_NONNULL(resolved.module);\n    KJ_ASSERT(module.id() == qux);\n  }\n\n  PREAMBLE([&](Lock& js) {\n    ResolveObserver resolveObserver;\n    CompilationObserver compilationObserver;\n    auto registry =\n        ModuleRegistry::Builder(resolveObserver, BASE).add(kj::mv(moduleBundle)).finish();\n\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    // The foo:bar module is interpreted as an ESM\n    js.tryCatch([&] {\n      auto val = ModuleRegistry::resolve(js, \"foo:bar\");\n      KJ_ASSERT(val.isNumber());\n    }, [&](Value exception) { js.throwException(kj::mv(exception)); });\n  });\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Using a registry from multiple threads works\") {\n\n  kj::AsyncIoContext io = kj::setupAsyncIo();\n\n  ModuleBundle::BundleBuilder bundleBuilder(BASE);\n  static const auto foo = \"export default 123; for (let n = 0; n < 100000; n++) {}\"_kjc;\n  bundleBuilder.addEsmModule(\"foo\", foo);\n  ResolveObserver resolveObserver;\n  auto registry =\n      ModuleRegistry::Builder(resolveObserver, BASE).add(bundleBuilder.finish()).finish();\n\n  struct NonOpErrorHandler final: public kj::TaskSet::ErrorHandler {\n    void taskFailed(kj::Exception&& exception) override {}\n  };\n  NonOpErrorHandler errorHandler;\n\n  kj::TaskSet tasks(errorHandler);\n\n  static const auto makeRunnable = [](kj::Arc<workerd::jsg::modules::ModuleRegistry> registry,\n                                       kj::Own<kj::PromiseFulfiller<void>> fulfiller) {\n    return [registry = kj::mv(registry), fulfiller = kj::mv(fulfiller)]() mutable {\n      PREAMBLE([&](Lock& js) {\n        CompilationObserver compilationObserver;\n        auto attached = registry->attachToIsolate(js, compilationObserver);\n        js.tryCatch([&] {\n          auto val = ModuleRegistry::resolve(js, \"file:///foo\");\n          KJ_ASSERT(val.isNumber());\n        }, [&](Value exception) { js.throwException(kj::mv(exception)); });\n      });\n      fulfiller->fulfill();\n    };\n  };\n\n  struct RunnableAndPromise {\n    kj::Promise<void> promise;\n    kj::Function<void()> runnable;\n  };\n\n  static const auto makeRunnableAndPromise =\n      [](kj::Arc<workerd::jsg::modules::ModuleRegistry> registry) -> RunnableAndPromise {\n    auto paf = kj::newPromiseAndCrossThreadFulfiller<void>();\n    return {kj::mv(paf.promise), makeRunnable(kj::mv(registry), kj::mv(paf.fulfiller))};\n  };\n\n  auto [paf1, task1] = makeRunnableAndPromise(registry.addRef());\n  kj::Thread(kj::mv(task1)).detach();\n  auto [paf2, task2] = makeRunnableAndPromise(registry.addRef());\n  kj::Thread(kj::mv(task2)).detach();\n  auto [paf3, task3] = makeRunnableAndPromise(registry.addRef());\n  kj::Thread(kj::mv(task3)).detach();\n  auto [paf4, task4] = makeRunnableAndPromise(registry.addRef());\n  kj::Thread(kj::mv(task4)).detach();\n  auto [paf5, task5] = makeRunnableAndPromise(registry.addRef());\n  kj::Thread(kj::mv(task5)).detach();\n\n  tasks.add(kj::mv(paf1));\n  tasks.add(kj::mv(paf2));\n  tasks.add(kj::mv(paf3));\n  tasks.add(kj::mv(paf4));\n  tasks.add(kj::mv(paf5));\n  tasks.onEmpty().wait(io.waitScope);\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Fallback service can see original raw specifier if provided\") {\n\n  ResolveObserver resolveObserver;\n  CompilationObserver compilationObserver;\n  ModuleRegistry::Builder builder(\n      resolveObserver, BASE, ModuleRegistry::Builder::Options::ALLOW_FALLBACK);\n  auto rawSpecifier = \"nothing\"_kjc;\n  const auto id = \"file:///nothing\"_url;\n\n  bool called = false;\n\n  builder.add(ModuleBundle::newFallbackBundle([&](const ResolveContext& context) {\n    KJ_ASSERT(context.rawSpecifier == rawSpecifier);\n    KJ_ASSERT(context.normalizedSpecifier == id);\n    KJ_ASSERT(context.referrerNormalizedSpecifier == BASE);\n    called = true;\n    return kj::none;\n  }));\n\n  auto registry = builder.finish();\n\n  ResolveContext context = {\n    .type = ResolveContext::Type::BUNDLE,\n    .source = ResolveContext::Source::INTERNAL,\n    .normalizedSpecifier = id,\n    .referrerNormalizedSpecifier = BASE,\n    .rawSpecifier = rawSpecifier,\n  };\n\n  KJ_ASSERT(registry->lookup(context) == kj::none);\n  KJ_ASSERT(called);\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Fallback service can return a module with a different specifier\") {\n\n  ResolveObserver resolveObserver;\n  CompilationObserver compilationObserver;\n  ModuleRegistry::Builder builder(\n      resolveObserver, BASE, ModuleRegistry::Builder::Options::ALLOW_FALLBACK);\n  auto rawSpecifier = \"nothing\"_kjc;\n  const auto id = \"file:///nothing\"_url;\n  const auto url = \"file:///different\"_url;\n\n  int called = 0;\n\n  builder.add(ModuleBundle::newFallbackBundle([&](const ResolveContext& context) {\n    called++;\n    kj::Own<Module> mod = Module::newSynthetic(\n        url.clone(), Module::Type::FALLBACK, Module::newDataModuleHandler(nullptr));\n    return kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>>(kj::mv(mod));\n  }));\n\n  auto registry = builder.finish();\n\n  ResolveContext context = {\n    .type = ResolveContext::Type::BUNDLE,\n    .source = ResolveContext::Source::INTERNAL,\n    .normalizedSpecifier = id,\n    .referrerNormalizedSpecifier = BASE,\n    .rawSpecifier = rawSpecifier,\n  };\n\n  auto& module1 = KJ_ASSERT_NONNULL(registry->lookup(context));\n\n  ResolveContext context2 = {\n    .type = ResolveContext::Type::BUNDLE,\n    .source = ResolveContext::Source::INTERNAL,\n    .normalizedSpecifier = url,\n    .referrerNormalizedSpecifier = BASE,\n    .rawSpecifier = rawSpecifier,\n  };\n\n  auto& module2 = KJ_ASSERT_NONNULL(registry->lookup(context2));\n\n  auto& module3 = KJ_ASSERT_NONNULL(registry->lookup(context));\n\n  // Both specifiers should resolve to the same module so the called count should be 1.\n  KJ_ASSERT(called == 1);\n  KJ_ASSERT(module1.id() == url);\n  KJ_ASSERT(&module1 == &module2);\n  KJ_ASSERT(&module2 == &module3);\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Percent-encoding in specifiers is normalized properly\") {\n  ResolveObserver resolveObserver;\n  CompilationObserver compilationObserver;\n\n  ModuleBundle::BundleBuilder builder(BASE);\n\n  // A specifier might have percent-encoded characters. We want those to be normalized\n  // so that they are matched correctly. For instance, %66oo%2fbar should be normalized\n  // to foo%2Fbar, and %66oo/bar should be normalized to foo/bar. Specifically, characters\n  // that generally do not need to be percent-encoded should be normalized to their\n  // unencoded form, while characters that need percent encoded should be normalized\n  // to their capitalized percent-encoded form (e.g. %2f becomes %2F). This ensures that\n  // when these different forms are used to import they will resolve to the expected\n  // module.\n\n  builder.addSyntheticModule(\"foo%2fbar\", Module::newDataModuleHandler(nullptr));\n  builder.addSyntheticModule(\"foo/bar\", Module::newDataModuleHandler(nullptr));\n\n  auto foo = kj::str(\"export { default as abc } from 'foo%2fbar';\"\n                     \"export { default as def } from 'foo/bar';\"\n                     \"export { default as ghi } from '%66oo/bar';\"\n                     \"export { default as jkl } from '%66oo%2fbar';\");\n  builder.addEsmModule(\"foo\", foo);\n\n  auto registry = ModuleRegistry::Builder(resolveObserver, BASE).add(builder.finish()).finish();\n\n  PREAMBLE([&](Lock& js) {\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    js.tryCatch([&] {\n      auto abc = ModuleRegistry::resolve(js, \"foo\", \"abc\"_kjc);\n      auto def = ModuleRegistry::resolve(js, \"foo\", \"def\"_kjc);\n      auto ghi = ModuleRegistry::resolve(js, \"foo\", \"ghi\"_kjc);\n      auto jkl = ModuleRegistry::resolve(js, \"foo\", \"jkl\"_kjc);\n\n      KJ_ASSERT(abc.strictEquals(jkl));\n      KJ_ASSERT(def.strictEquals(ghi));\n      KJ_ASSERT(!abc.strictEquals(def));\n    }, [&](Value exception) { js.throwException(kj::mv(exception)); });\n  });\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Aliased modules (import maps) work\") {\n  ResolveObserver resolveObserver;\n  CompilationObserver compilationObserver;\n  ModuleBundle::BundleBuilder builder(BASE);\n\n  builder.addSyntheticModule(\"http://example/foo\", Module::newDataModuleHandler(nullptr));\n  builder.alias(\"bar\", \"http://example/foo\");\n\n  try {\n    builder.alias(\"bar\", \"baz\");\n    KJ_FAIL_ASSERT(\"should have thrown\");\n  } catch (kj::Exception& ex) {\n    KJ_ASSERT(ex.getDescription() == \"Module \\\"file:///bar\\\" already added to bundle\");\n  }\n\n  try {\n    builder.alias(\"http://example/%66oo\", \"baz\");\n    KJ_FAIL_ASSERT(\"should have thrown\");\n  } catch (kj::Exception& ex) {\n    KJ_ASSERT(ex.getDescription() == \"Module \\\"http://example/foo\\\" already added to bundle\");\n  }\n\n  auto src = kj::str(\"export { default as abc } from 'bar';\"\n                     \"export { default as def } from 'http://example/%66oo';\");\n  builder.addEsmModule(\"qux\", src);\n\n  auto registry = ModuleRegistry::Builder(resolveObserver, BASE).add(builder.finish()).finish();\n\n  ResolveContext contextBar{\n    .type = ResolveContext::Type::BUNDLE,\n    .source = ResolveContext::Source::INTERNAL,\n    .normalizedSpecifier = \"file:///bar\"_url,\n    .referrerNormalizedSpecifier = BASE,\n  };\n\n  ResolveContext contextFoo{\n    .type = ResolveContext::Type::BUNDLE,\n    .source = ResolveContext::Source::INTERNAL,\n    .normalizedSpecifier = \"http://example/foo\"_url,\n    .referrerNormalizedSpecifier = BASE,\n  };\n\n  auto& bar = KJ_ASSERT_NONNULL(registry->lookup(contextBar));\n  auto& foo = KJ_ASSERT_NONNULL(registry->lookup(contextFoo));\n\n  // The aliases resolve to the same underlying module...\n  KJ_ASSERT(&bar == &foo);\n\n  PREAMBLE([&](Lock& js) {\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    js.tryCatch([&] {\n      // While the aliased identifiers resolve to the same underlying module, the\n      // evaluate into two separate module instances. This is similar in behavior\n      // to how query string and fragments work. The fact that they use the same\n      // underlying definition is not really that important.\n      auto abc = ModuleRegistry::resolve(js, \"qux\", \"abc\"_kjc);\n      auto def = ModuleRegistry::resolve(js, \"qux\", \"def\"_kjc);\n      KJ_ASSERT(abc.isArrayBuffer());\n      KJ_ASSERT(def.isArrayBuffer());\n      KJ_ASSERT(!abc.strictEquals(def));\n    }, [&](Value exception) { js.throwException(kj::mv(exception)); });\n  });\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Import attributes are currently unsupported\") {\n  ResolveObserver resolveObserver;\n  CompilationObserver compilationObserver;\n  ModuleBundle::BundleBuilder builder(BASE);\n\n  auto foo = kj::str(\"import abc from 'foo' with { type: 'json' };\");\n  builder.addEsmModule(\"foo\", foo);\n\n  auto registry = ModuleRegistry::Builder(resolveObserver, BASE).add(builder.finish()).finish();\n\n  PREAMBLE([&](Lock& js) {\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    js.tryCatch([&] {\n      ModuleRegistry::resolve(js, \"foo\", \"default\"_kjc);\n      JSG_FAIL_REQUIRE(Error, \"Should have thrown\");\n    }, [&](Value exception) {\n      auto str = kj::str(exception.getHandle(js));\n      KJ_ASSERT(str == \"TypeError: Import attributes are not supported\");\n    });\n  });\n}\n\n// ======================================================================================\nKJ_TEST(\"Using a deferred eval callback works\") {\n  ResolveObserver resolveObserver;\n  CompilationObserver compilationObserver;\n  ModuleBundle::BundleBuilder builder(BASE);\n\n  auto foo = kj::str(\"export default 1;\");\n  builder.addEsmModule(\"foo\", foo);\n\n  bool called = false;\n  auto registry = ModuleRegistry::Builder(resolveObserver, BASE)\n                      .add(builder.finish())\n                      .setEvalCallback([&called](Lock& js, const Module& module, auto v8Module,\n                                           const auto& observer) {\n    called = true;\n    return js.resolvedPromise<Value>(js.v8Ref<v8::Value>(js.num(123)));\n  }).finish();\n\n  PREAMBLE([&](Lock& js) {\n    auto attached = registry->attachToIsolate(js, compilationObserver);\n\n    js.tryCatch([&] {\n      ModuleRegistry::resolve(js, \"foo\", \"default\"_kjc);\n      KJ_ASSERT(false);\n    }, [&](auto exception) {});\n\n    // We don't care about the specific exception above. We only want to know that\n    // the eval callback was invoked.\n    KJ_ASSERT(called);\n  });\n}\n\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/modules-new.c++",
    "content": "// Copyright (c) 2017-2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"modules-new.h\"\n\n#include \"buffersource.h\"\n\n#include <workerd/jsg/function.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/util.h>\n\n#include <kj/mutex.h>\n#include <kj/table.h>\n\nnamespace workerd::jsg::modules {\n\nnamespace {\n// Returns kj::none if this given module is incapable of resolving the given\n// context. Otherwise, returns the module.\nkj::Maybe<const Module&> checkModule(const ResolveContext& context, const Module& module) {\n  if (!module.evaluateContext(context)) {\n    return kj::none;\n  }\n  return module;\n};\n\n// If the specifier is \"node:process\", returns the appropriate internal module\n// URL based on the enable_nodejs_process_v2 flag. Otherwise returns kj::none.\nkj::Maybe<const Url&> maybeRedirectNodeProcess(Lock& js, kj::ArrayPtr<const char> spec) {\n  if (spec == \"node:process\"_kjb.asChars()) {\n    static const auto publicProcess = \"node-internal:public_process\"_url;\n    static const auto legacyProcess = \"node-internal:legacy_process\"_url;\n    return isNodeJsProcessV2Enabled(js) ? publicProcess : legacyProcess;\n  }\n  return kj::none;\n}\n\nkj::String specifierToString(jsg::Lock& js, v8::Local<v8::String> spec) {\n  // Source files in workers end up being converted to UTF-8 bytes, so if the specifier\n  // string contains non-ASCII unicode characters, those will be directly encoded as UTF-8\n  // bytes, which unfortunately end up double-encoded if we try to read them using the\n  // regular js.toString() method. Doh! Fortunately they come through as one-byte strings,\n  // so we can detect that case and handle those correctly here.\n  if (spec->ContainsOnlyOneByte()) {\n    auto buf = kj::heapArray<char>(spec->Length() + 1);\n    spec->WriteOneByteV2(js.v8Isolate, 0, spec->Length(), buf.asBytes().begin(),\n        v8::String::WriteFlags::kNullTerminate);\n    KJ_ASSERT(buf[buf.size() - 1] == '\\0');\n    return kj::String(kj::mv(buf));\n  }\n  return js.toString(spec);\n}\n\n// Ensure that the given module has been instantiated or errored.\n// If false is returned, then an exception should have been scheduled\n// on the isolate.\nbool ensureInstantiated(Lock& js,\n    v8::Local<v8::Module> module,\n    const CompilationObserver& observer,\n    const Module& self) {\n  return module->GetStatus() != v8::Module::kUninstantiated ||\n      self.instantiate(js, module, observer);\n}\n\nconstexpr ResolveContext::Type moduleTypeToResolveContextType(Module::Type type) {\n  switch (type) {\n    case Module::Type::BUNDLE: {\n      return ResolveContext::Type::BUNDLE;\n    }\n    case Module::Type::BUILTIN: {\n      return ResolveContext::Type::BUILTIN;\n    }\n    case Module::Type::BUILTIN_ONLY: {\n      return ResolveContext::Type::BUILTIN_ONLY;\n    }\n    case Module::Type::FALLBACK: {\n      return ResolveContext::Type::BUNDLE;\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nconstexpr ModuleBundle::Type toModuleBuilderType(ModuleBundle::BuiltinBuilder::Type type) {\n  switch (type) {\n    case ModuleBundle::BuiltinBuilder::Type::BUILTIN:\n      return ModuleBundle::Type::BUILTIN;\n    case ModuleBundle::BuiltinBuilder::Type::BUILTIN_ONLY:\n      return ModuleBundle::Type::BUILTIN_ONLY;\n  }\n  KJ_UNREACHABLE;\n}\n\n// The implementation of Module for ESM.\nclass EsModule final: public Module {\n public:\n  explicit EsModule(Url id, Type type, Flags flags, kj::ArrayPtr<const char> source)\n      : Module(kj::mv(id), type, flags | Flags::ESM | Flags::EVAL),\n        source(source),\n        cachedData(kj::none) {\n    KJ_DASSERT(isEsm());\n  }\n  KJ_DISALLOW_COPY_AND_MOVE(EsModule);\n\n  v8::MaybeLocal<v8::Module> getDescriptor(\n      Lock& js, const CompilationObserver& observer) const override {\n    auto metrics = observer.onEsmCompilationStart(js.v8Isolate, kj::str(id().getHref()),\n        type() == Type::BUNDLE ? CompilationObserver::Option::BUNDLE\n                               : CompilationObserver::Option::BUILTIN);\n\n    static constexpr int resourceLineOffset = 0;\n    static constexpr int resourceColumnOffset = 0;\n    static constexpr bool resourceIsSharedCrossOrigin = false;\n    static constexpr int scriptId = -1;\n    static constexpr bool resourceIsOpaque = false;\n    static constexpr bool isWasm = false;\n    v8::ScriptOrigin origin(js.str(id().getHref()), resourceLineOffset, resourceColumnOffset,\n        resourceIsSharedCrossOrigin, scriptId, {}, resourceIsOpaque, isWasm, true);\n\n    auto options = v8::ScriptCompiler::CompileOptions::kNoCompileOptions;\n    bool cacheWasRejected = false;\n\n    v8::Local<v8::Module> module;\n    {\n      v8::ScriptCompiler::CachedData* data = nullptr;\n\n      // Check to see if we have cached compilation data for this module.\n      // Importantly, we want to allow multiple threads to be capable of\n      // reading and using the cached data without blocking each other\n      // (which is fine since using the cache does not modify it).\n      auto lock = cachedData.lockShared();\n      KJ_IF_SOME(c, *lock) {\n        // We new new here because v8 will take ownership of the CachedData instance,\n        // even tho we are maintaining ownership of the underlying buffer.\n        data = new v8::ScriptCompiler::CachedData(\n            c->data, c->length, v8::ScriptCompiler::CachedData::BufferPolicy::BufferNotOwned);\n        auto check = data->CompatibilityCheck(js.v8Isolate);\n        if (check != v8::ScriptCompiler::CachedData::kSuccess) {\n          // The cached data is not compatible with the current isolate. Let's\n          // not try using it.\n          delete data;\n          data = nullptr;\n        } else {\n          observer.onCompileCacheFound(js.v8Isolate);\n        }\n      }\n\n      // Note that the Source takes ownership of the CachedData pointer that we pass in.\n      // (but not the actual buffer it holds). Do not use data after this point.\n      v8::ScriptCompiler::Source source(js.strExtern(this->source), origin, data);\n\n      auto maybeCached = source.GetCachedData();\n      if (maybeCached != nullptr) {\n        if (!maybeCached->rejected) {\n          // We found valid cached data and set the option to consume it to avoid\n          // compiling again below...\n          options = v8::ScriptCompiler::CompileOptions::kConsumeCodeCache;\n        } else {\n          // In this case we'll just log a warning and continue on. This is potentially\n          // a signal that something with the compile cache is not working correctly but\n          // it is not a fatal error. If we spot this in the wild, it warrants some\n          // investigation but is not critical.\n          LOG_WARNING_ONCE(\"NOSENTRY Cached data for an ESM module was rejected\");\n          observer.onCompileCacheRejected(js.v8Isolate);\n          cacheWasRejected = true;\n        }\n      }\n\n      // Let's just double check that our options are valid. They should be\n      // since we're either consuming cached data or not using any options at all.\n      KJ_ASSERT(v8::ScriptCompiler::CompileOptionsIsValid(options));\n      if (!v8::ScriptCompiler::CompileModule(js.v8Isolate, &source, options).ToLocal(&module)) {\n        return v8::MaybeLocal<v8::Module>();\n      }\n    }\n\n    // If the cached data was rejected, clear it so subsequent isolates don't\n    // repeatedly check stale data. We then fall through to regenerate the cache\n    // below. In practice this is exceedingly unlikely since V8 version changes\n    // are the primary cause of cache rejection and we don't change V8 versions\n    // within a single binary, but we handle it for correctness.\n    if (cacheWasRejected) {\n      auto lock = cachedData.lockExclusive();\n      *lock = kj::none;\n    }\n\n    // If options is still kNoCompileOptions at this point, it means that we did not\n    // find any cached data for this module, or the cached data was rejected. In\n    // either case, we try generating it and store it. Multiple threads can end up\n    // lining up here to acquire the lock and generate the cache. We'll test to see\n    // if the cached data is still empty once the lock is acquired, and if it is\n    // not, we'll skip generation.\n    if (options == v8::ScriptCompiler::CompileOptions::kNoCompileOptions) {\n      auto lock = cachedData.lockExclusive();\n      if (*lock == kj::none) {\n        if (auto ptr = v8::ScriptCompiler::CreateCodeCache(module->GetUnboundModuleScript())) {\n          // Using the technically private kj::_::HeapDisposer to wrap the V8-allocated\n          // CachedData in a kj::Own. This pattern has precedent in io-own.h.\n          kj::Own<v8::ScriptCompiler::CachedData> cached(\n              ptr, kj::_::HeapDisposer<v8::ScriptCompiler::CachedData>::instance);\n          *lock = kj::mv(cached);\n          observer.onCompileCacheGenerated(js.v8Isolate);\n        } else {\n          observer.onCompileCacheGenerationFailed(js.v8Isolate);\n        }\n      }\n    }\n\n    return module;\n  }\n\n private:\n  v8::MaybeLocal<v8::Value> actuallyEvaluate(\n      Lock& js, v8::Local<v8::Module> module, const CompilationObserver& observer) const override {\n    return module->Evaluate(js.v8Context());\n  }\n\n  v8::MaybeLocal<v8::Value> evaluate(Lock& js,\n      v8::Local<v8::Module> module,\n      const CompilationObserver& observer,\n      const Evaluator& maybeEvaluate) const override {\n    if (!ensureInstantiated(js, module, observer, *this)) {\n      return v8::MaybeLocal<v8::Value>();\n    };\n\n    KJ_IF_SOME(result, maybeEvaluate(js, *this, module, observer)) {\n      return js.wrapSimplePromise(kj::mv(result));\n    }\n\n    return actuallyEvaluate(js, module, observer);\n  }\n\n  kj::ArrayPtr<const char> source;\n\n  // The cachedData holds the cached compilation data for this module, if any. It is\n  // generated on-demand the first time the module is compiled, if possible.\n  kj::MutexGuarded<kj::Maybe<kj::Own<v8::ScriptCompiler::CachedData>>> cachedData;\n};\n\n// A SyntheticModule is essentially any type of module that is not backed by an ESM\n// script. More specifically, it's a module in which we synthetically construct the\n// module namespace (i.e. the exports) and the evaluation steps. This is used for things\n// like CommonJS modules, JSON modules, etc.\nclass SyntheticModule final: public Module {\n public:\n  // The name of the default export.\n  static constexpr auto DEFAULT = \"default\"_kjc;\n\n  SyntheticModule(Url id,\n      Type type,\n      ModuleBundle::BundleBuilder::EvaluateCallback callback,\n      kj::Array<kj::String> namedExports,\n      Flags flags = Flags::NONE)\n      : Module(kj::mv(id), type, flags),\n        callback(kj::mv(callback)),\n        namedExports(kj::mv(namedExports)) {\n    // Synthetic modules can never be ESM or Main\n    KJ_DASSERT(!isEsm() && !isMain());\n  }\n\n  v8::MaybeLocal<v8::Module> getDescriptor(Lock& js, const CompilationObserver&) const override {\n    // We add one to the size to accomodate the default export.\n    v8::LocalVector<v8::String> exports(js.v8Isolate, namedExports.size() + 1);\n    int n = 0;\n    exports[n++] = js.strIntern(DEFAULT);\n    for (const auto& exp: namedExports) {\n      exports[n++] = js.strIntern(exp);\n    }\n    return v8::Module::CreateSyntheticModule(js.v8Isolate, js.str(id().getHref()),\n        v8::MemorySpan<const v8::Local<v8::String>>(exports.data(), exports.size()),\n        evaluationSteps);\n  }\n\n private:\n  static v8::MaybeLocal<v8::Value> evaluationSteps(\n      v8::Local<v8::Context> context, v8::Local<v8::Module> module);\n\n  v8::MaybeLocal<v8::Value> actuallyEvaluate(\n      Lock& js, v8::Local<v8::Module> module, const CompilationObserver& observer) const override {\n    // The return value will be a resolved promise.\n    v8::Local<v8::Promise::Resolver> resolver;\n\n    if (!v8::Promise::Resolver::New(js.v8Context()).ToLocal(&resolver)) {\n      return v8::MaybeLocal<v8::Value>();\n    }\n\n    ModuleNamespace ns(module, namedExports);\n    if (!callback(js, id(), ns, observer)) {\n      // An exception should already be scheduled with the isolate\n      return v8::MaybeLocal<v8::Value>();\n    }\n\n    if (resolver->Resolve(js.v8Context(), js.v8Undefined()).IsNothing()) {\n      return v8::MaybeLocal<v8::Value>();\n    }\n\n    return resolver->GetPromise();\n  }\n\n  v8::MaybeLocal<v8::Value> evaluate(Lock& js,\n      v8::Local<v8::Module> module,\n      const CompilationObserver& observer,\n      const Evaluator& maybeEvaluate) const override {\n    if (!ensureInstantiated(js, module, observer, *this)) {\n      return v8::MaybeLocal<v8::Value>();\n    }\n\n    // If this synthetic module is marked with Flags::EVAL, and the evalCallback\n    // is specified, then we defer evaluation to the given callback.\n    if (isEval()) {\n      KJ_IF_SOME(result, maybeEvaluate(js, *this, module, observer)) {\n        return js.wrapSimplePromise(kj::mv(result));\n      }\n    }\n\n    return actuallyEvaluate(js, module, observer);\n  }\n\n  // Marked mutable because kj::Function::operator() is non-const, but evaluation\n  // callbacks are conceptually const — they produce new JS objects each time without\n  // modifying the module's logical state. The callback is only ever invoked while\n  // holding the isolate lock, so concurrent mutation is not a concern.\n  mutable ModuleBundle::BundleBuilder::EvaluateCallback callback;\n  kj::Array<kj::String> namedExports;\n};\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wunused-function\"\nWD_STRONG_BOOL(SourcePhase);\n#pragma clang diagnostic pop\n\n// Binds a ModuleRegistry to an Isolate.\nclass IsolateModuleRegistry final {\n public:\n  static IsolateModuleRegistry& from(v8::Isolate* isolate) {\n    return KJ_ASSERT_NONNULL(jsg::getAlignedPointerFromEmbedderData<IsolateModuleRegistry>(\n        isolate->GetCurrentContext(), jsg::ContextPointerSlot::MODULE_REGISTRY));\n  }\n\n  struct SpecifierContext final {\n    ResolveContext::Type type;\n    Url id;\n    SpecifierContext(const ResolveContext& resolveContext)\n        : type(resolveContext.type),\n          id(resolveContext.normalizedSpecifier.clone()) {}\n    bool operator==(const SpecifierContext& other) const {\n      return type == other.type && id == other.id;\n    }\n    uint hashCode() const {\n      return kj::hashCode(type, id);\n    }\n  };\n\n  struct Entry final {\n    HashableV8Ref<v8::Module> key;\n    SpecifierContext context;\n    const Module& module;\n  };\n\n  IsolateModuleRegistry(\n      Lock& js, const ModuleRegistry& registry, const CompilationObserver& observer);\n  KJ_DISALLOW_COPY_AND_MOVE(IsolateModuleRegistry);\n\n  // Used to implement the normal static import of modules (using `import ... from`).\n  // Returns the v8::Module descriptor. If an empty v8::MaybeLocal is returned, then\n  // an exception has been scheduled with the isolate.\n  v8::MaybeLocal<v8::Module> resolve(Lock& js, const ResolveContext& context) {\n    // Do we already have a cached module for this context?\n    KJ_IF_SOME(found, lookupCache.find<kj::HashIndex<ContextCallbacks>>(context)) {\n      return found.key.getHandle(js);\n    }\n    // No? That's OK, let's look it up.\n    KJ_IF_SOME(found, resolveWithCaching(js, context)) {\n      return found.key.getHandle(js);\n    }\n\n    // Nothing found? Aw... fail!\n    JSG_FAIL_REQUIRE(Error, kj::str(\"Module not found: \", context.normalizedSpecifier.getHref()));\n  }\n\n  // Used to implement the async dynamic import of modules (using `await import(...)`)\n  // Returns a promise that is resolved once the module is resolved. If any empty\n  // v8::MaybeLocal is returned, then an exception has been scheduled with the isolate.\n  v8::MaybeLocal<v8::Promise> dynamicResolve(Lock& js,\n      Url normalizedSpecifier,\n      Url referrer,\n      kj::StringPtr rawSpecifier,\n      SourcePhase sourcePhase) {\n    static constexpr auto evaluate = [](Lock& js, Entry& entry, const CompilationObserver& observer,\n                                         const Module::Evaluator& maybeEvaluate) {\n      auto module = entry.key.getHandle(js);\n      return js\n          .toPromise(\n              check(entry.module.evaluate(js, module, observer, maybeEvaluate)).As<v8::Promise>())\n          .then(js, [module = js.v8Ref(module)](Lock& js, Value) mutable -> Promise<Value> {\n        return js.resolvedPromise(js.v8Ref(module.getHandle(js)->GetModuleNamespace()));\n      });\n    };\n\n    return js.wrapSimplePromise(js.tryCatch([&] -> Promise<Value> {\n      // The referrer should absolutely already be known to the registry\n      // or something bad happened.\n      auto& referring = JSG_REQUIRE_NONNULL(lookupCache.find<kj::HashIndex<UrlCallbacks>>(referrer),\n          TypeError, kj::str(\"Referring module not found in the registry: \", referrer.getHref()));\n\n      // Now that we know the referrer module, we can set the context for the\n      // next resolve. In particular, the \"type\" of the context is determine\n      // by the type of the referring module.\n      ResolveContext context = {\n        .type = moduleTypeToResolveContextType(referring.module.type()),\n        .source = ResolveContext::Source::DYNAMIC_IMPORT,\n        .normalizedSpecifier = normalizedSpecifier,\n        .referrerNormalizedSpecifier = referrer,\n        .rawSpecifier = rawSpecifier,\n      };\n\n      auto handleFoundModule = [&](Entry& found) -> Promise<Value> {\n        auto evaluatePromise = evaluate(js, found, getObserver(), inner.getEvaluator());\n        auto isWasm = found.module.isWasm();\n\n        if (!sourcePhase) {\n          return evaluatePromise;\n        } else {\n          // We only support source phase imports for Wasm modules.\n          // Source phase imports provide uninstantiated and unlinked representations for modules, as distinct\n          // from module instances. They effectively represent the compiled module without state.\n          // WebAssembly.Module is this representation for WebAssembly.\n          // JS source module handles as an instance of `ModuleSource` will be supported in due course,\n          // but are specified in a different spec ESM Phase Imports (https://github.com/tc39/proposal-esm-phase-imports).\n          // For builtins and other synthetic modules, there are currently no plans to make a source phase\n          // representation available, so that these would remain with the specified syntax error as\n          // implemented below.\n          if (isWasm) {\n            return evaluatePromise.then(js,\n                [normalizedSpecifier = normalizedSpecifier.clone()](\n                    Lock& js, Value namespaceValue) -> Value {\n              auto moduleNamespace = namespaceValue.getHandle(js).As<v8::Object>();\n              v8::Local<v8::Value> defaultExport;\n              if (moduleNamespace->Get(js.v8Context(), js.strIntern(\"default\"_kj))\n                      .ToLocal(&defaultExport)) {\n                if (defaultExport->IsWasmModuleObject()) {\n                  return js.v8Ref(defaultExport);\n                }\n              }\n              KJ_FAIL_REQUIRE(v8::Exception::SyntaxError,\n                  \"Source phase import not available for module: \", normalizedSpecifier.getHref());\n            });\n          }\n          return js.rejectedPromise<Value>(js.v8Ref(v8::Exception::SyntaxError(js.strIntern(kj::str(\n              \"Source phase import not available for module: \", normalizedSpecifier.getHref())))));\n        }\n      };\n\n      // Do we already have a cached module for this context?\n      KJ_IF_SOME(found, lookupCache.find<kj::HashIndex<ContextCallbacks>>(context)) {\n        return handleFoundModule(found);\n      }\n\n      // No? That's OK, let's look it up.\n      KJ_IF_SOME(found, resolveWithCaching(js, context)) {\n        return handleFoundModule(found);\n      }\n\n      // Nothing found? Aw... fail!\n      JSG_FAIL_REQUIRE(TypeError, kj::str(\"Module not found: \", normalizedSpecifier.getHref()));\n    }, [&](Value exception) -> Promise<Value> {\n      return js.rejectedPromise<Value>(kj::mv(exception));\n    }));\n  }\n\n  enum class RequireOption {\n    DEFAULT,\n    RETURN_EMPTY,\n  };\n\n  // Used to implement the synchronous dynamic import of modules in support of APIs\n  // like the CommonJS require. Returns the instantiated/evaluated module namespace.\n  // If an empty v8::MaybeLocal is returned and the default option is given, then an\n  // exception has been scheduled.\n  v8::MaybeLocal<v8::Object> require(\n      Lock& js, const ResolveContext& context, RequireOption option = RequireOption::DEFAULT) {\n    static constexpr auto evaluate = [](Lock& js, Entry& entry, const Url& id,\n                                         const CompilationObserver& observer,\n                                         const Module::Evaluator& maybeEvaluate) {\n      auto module = entry.key.getHandle(js);\n      auto status = module->GetStatus();\n\n      // If status is kErrored, that means a prior attempt to evaluate the module\n      // failed. We simply propagate the same error here.\n      if (status == v8::Module::kErrored) {\n        js.throwException(JsValue(module->GetException()));\n      }\n\n      // Circular dependencies should be fine when we are talking strictly\n      // about CJS/Node.js style modules. For ESM, it becomes more problematic\n      // because v8 will not allow us to grab the default export while the module\n      // is still evaluating.\n\n      if (entry.module.isEsm() && status == v8::Module::kEvaluating) {\n        JSG_FAIL_REQUIRE(Error, \"Circular dependency when resolving module: \", id);\n      }\n\n      // If the module has already been evaluated, or is in the process of being\n      // evaluated, return the module namespace object directly. Note that if the\n      // module is a synthetic module, and status is kEvaluating, it is possible\n      // and likely that the namespace has not yet been fully evaluated and will\n      // be incomplete here. This allows CJS circular dependencies to be supported\n      // to a degree. Just like in Node.js, however, such circular dependencies\n      // can still be problematic depending on how they are used.\n      if (status == v8::Module::kEvaluated || status == v8::Module::kEvaluating) {\n        return module->GetModuleNamespace().As<v8::Object>();\n      }\n\n      // Evaluate the module and grab the default export from the module namespace.\n      auto promise =\n          check(entry.module.evaluate(js, module, observer, maybeEvaluate)).As<v8::Promise>();\n\n      // Run the microtasks to ensure that any promises that happen to be scheduled\n      // during the evaluation of the top-level scope have a chance to be settled,\n      js.runMicrotasks();\n\n      static const auto kTopLevelAwaitError =\n          \"Use of top-level await in a synchronously required module is restricted to \"\n          \"promises that are resolved synchronously. This includes any top-level awaits \"\n          \"in the entrypoint module for a worker.\"_kj;\n\n      switch (promise->State()) {\n        case v8::Promise::kFulfilled: {\n          // This is what we want. The module namespace should be fully populated\n          // and evaluated at this point.\n          return module->GetModuleNamespace().As<v8::Object>();\n        }\n        case v8::Promise::kRejected: {\n          // Oops, there was an error. We should throw it.\n          js.throwException(JsValue(promise->Result()));\n          break;\n        }\n        case v8::Promise::kPending: {\n          // The module evaluation could not complete in a single drain of the\n          // microtask queue. This means we've got a pending promise somewhere\n          // that is being awaited preventing the module from being ready to\n          // go. We can't have that! Throw! Throw!\n          JSG_FAIL_REQUIRE(Error, kTopLevelAwaitError, \" Specifier: \\\"\", id, \"\\\".\");\n        }\n      }\n      KJ_UNREACHABLE;\n    };\n\n    return js.tryCatch([&]() -> v8::MaybeLocal<v8::Object> {\n      KJ_IF_SOME(processUrl, maybeRedirectNodeProcess(js, context.normalizedSpecifier.getHref())) {\n        ResolveContext newContext{\n          .type = ResolveContext::Type::BUILTIN_ONLY,\n          .source = context.source,\n          .normalizedSpecifier = processUrl,\n          .referrerNormalizedSpecifier = context.referrerNormalizedSpecifier,\n          .rawSpecifier = context.rawSpecifier,\n        };\n        return require(js, newContext, option);\n      }\n\n      // Do we already have a cached module for this context?\n      KJ_IF_SOME(found, lookupCache.find<kj::HashIndex<ContextCallbacks>>(context)) {\n        return evaluate(\n            js, found, context.normalizedSpecifier, getObserver(), inner.getEvaluator());\n      }\n\n      KJ_IF_SOME(found, resolveWithCaching(js, context)) {\n        return evaluate(\n            js, found, context.normalizedSpecifier, getObserver(), inner.getEvaluator());\n      }\n\n      if (option == RequireOption::RETURN_EMPTY) {\n        return v8::MaybeLocal<v8::Object>();\n      }\n      JSG_FAIL_REQUIRE(Error, kj::str(\"Module not found: \", context.normalizedSpecifier.getHref()));\n    }, [&](Value exception) {\n      // Use the isolate to rethrow the exception here instead of using the lock.\n      js.v8Isolate->ThrowException(exception.getHandle(js));\n      return v8::MaybeLocal<v8::Object>();\n    });\n  }\n\n  // Lookup a module that may have already been previously resolved and cached.\n  kj::Maybe<Entry&> lookup(Lock& js, v8::Local<v8::Module> module) {\n    return lookupCache\n        .find<kj::HashIndex<EntryCallbacks>>(HashableV8Ref<v8::Module>(js.v8Isolate, module))\n        .map([](Entry& entry) -> Entry& { return entry; });\n  }\n\n  const jsg::Url& getBundleBase() const {\n    return inner.getBundleBase();\n  }\n\n private:\n  const ModuleRegistry& inner;\n  const CompilationObserver& observer;\n\n  const CompilationObserver& getObserver() const {\n    return observer;\n  }\n\n  struct EntryCallbacks final {\n    const HashableV8Ref<v8::Module>& keyForRow(const Entry& entry) const {\n      return entry.key;\n    }\n    bool matches(const Entry& entry, const HashableV8Ref<v8::Module>& key) const {\n      return entry.key == key;\n    }\n    uint hashCode(const HashableV8Ref<v8::Module>& ref) const {\n      return ref.hashCode();\n    }\n  };\n\n  struct ContextCallbacks final {\n    const SpecifierContext& keyForRow(const Entry& entry) const {\n      return entry.context;\n    }\n    bool matches(const Entry& entry, const SpecifierContext& context) const {\n      return entry.context == context;\n    }\n    uint hashCode(const SpecifierContext& context) const {\n      return context.hashCode();\n    }\n  };\n\n  struct UrlCallbacks final {\n    const Url& keyForRow(const Entry& entry) const {\n      return entry.context.id;\n    }\n    bool matches(const Entry& entry, const Url& id) const {\n      return entry.context.id == id;\n    }\n    uint hashCode(const Url& id) const {\n      return id.hashCode();\n    }\n  };\n\n  // Resolves the module from the inner ModuleRegistry, caching the results.\n  kj::Maybe<Entry&> resolveWithCaching(\n      Lock& js, const ResolveContext& context) KJ_WARN_UNUSED_RESULT {\n    ResolveContext innerContext{\n      // The type identifies the resolution context as a bundle, builtin, or builtin-only.\n      .type = context.type,\n      // The source identifies the method of resolution (static import, dynamic import, etc).\n      // This is passed along for informational purposes only.\n      .source = context.source,\n      // The inner registry should ignore all URL query parameters and fragments\n      .normalizedSpecifier = context.normalizedSpecifier.clone(\n          Url::EquivalenceOption::IGNORE_FRAGMENTS | Url::EquivalenceOption::IGNORE_SEARCH),\n      // The referrer is passed along for informational purposes only.\n      .referrerNormalizedSpecifier = context.referrerNormalizedSpecifier,\n    };\n\n    KJ_IF_SOME(found, inner.lookup(innerContext)) {\n      return kj::Maybe<Entry&>(lookupCache.upsert(\n          Entry{\n            .key = HashableV8Ref<v8::Module>(\n                js.v8Isolate, check(found.getDescriptor(js, getObserver()))),\n            // Note that we cache specifically with the passed in context and not the\n            // innerContext that was created. This is because we want to use the original\n            // specifier URL (with query parameters and fragments) as part of the key for\n            // the lookup cache.\n            .context = context,\n            .module = found,\n          },\n          [](auto&, auto&&) {}));\n    }\n    return kj::none;\n  }\n\n  kj::Table<Entry,\n      kj::HashIndex<EntryCallbacks>,\n      kj::HashIndex<ContextCallbacks>,\n      kj::HashIndex<UrlCallbacks>>\n      lookupCache;\n  friend class SyntheticModule;\n};\n\nv8::MaybeLocal<v8::Value> SyntheticModule::evaluationSteps(\n    v8::Local<v8::Context> context, v8::Local<v8::Module> module) {\n  try {\n    auto& js = Lock::current();\n    auto& registry = IsolateModuleRegistry::from(js.v8Isolate);\n\n    KJ_IF_SOME(found, registry.lookup(js, module)) {\n      return found.module.evaluate(\n          js, module, registry.getObserver(), registry.inner.getEvaluator());\n    }\n\n    // This case really should never actually happen but we handle it anyway.\n    KJ_LOG(ERROR, \"Synthetic module not found in registry for evaluation\");\n\n    js.v8Isolate->ThrowError(js.str(\"Requested module does not exist\"_kj));\n    return v8::MaybeLocal<v8::Value>();\n  } catch (...) {\n    kj::throwFatalException(kj::getCaughtExceptionAsKj());\n  }\n}\n\n// Set up the special `import.meta` property for the module.\nvoid importMeta(\n    v8::Local<v8::Context> context, v8::Local<v8::Module> module, v8::Local<v8::Object> meta) {\n  auto& js = Lock::current();\n  auto& registry = IsolateModuleRegistry::from(js.v8Isolate);\n  try {\n    js.tryCatch([&] {\n      KJ_IF_SOME(found, registry.lookup(js, module)) {\n        auto href = found.context.id.getHref();\n\n        // V8's documentation says that the host should set the properties\n        // using CreateDataProperty.\n\n        if (meta->CreateDataProperty(js.v8Context(), v8::Local<v8::String>(js.strIntern(\"main\"_kj)),\n                    js.boolean(found.module.isMain()))\n                .IsNothing()) {\n          // Notice that we do not use check here. There should be an exception\n          // scheduled with the isolate, it will take care of it at this point.\n          return;\n        }\n\n        if (meta->CreateDataProperty(\n                    js.v8Context(), v8::Local<v8::String>(js.strIntern(\"url\"_kj)), js.str(href))\n                .IsNothing()) {\n          return;\n        }\n\n        // The import.meta.resolve(...) function is effectively a shortcut for\n        // new URL(specifier, import.meta.url).href. The idea is that it allows\n        // resolving import specifiers relative to the current modules base URL.\n        // Note that we do not validate that the resolved URL actually matches\n        // anything in the registry.\n        auto resolve = js.wrapReturningFunction(js.v8Context(),\n            [href = kj::mv(href)](\n                Lock& js, const v8::FunctionCallbackInfo<v8::Value>& args) -> JsValue {\n          // Note that we intentionally use ToString here to coerce whatever value is given\n          // into a string or throw if it cannot be coerced.\n          auto specifier = js.toString(args[0]);\n          KJ_IF_SOME(resolved, Url::tryParse(specifier.asPtr(), href)) {\n            auto normalized = resolved.clone(Url::EquivalenceOption::NORMALIZE_PATH);\n            return js.str(normalized.getHref());\n          } else {\n            // If the specifier could not be parsed and resolved successfully,\n            // the spec says to return null.\n            return js.null();\n          }\n        });\n\n        if (meta->CreateDataProperty(\n                    js.v8Context(), v8::Local<v8::String>(js.strIntern(\"resolve\"_kj)), resolve)\n                .IsNothing()) {\n          return;\n        }\n      }\n    }, [&](Value exception) {\n      // It would be exceedingly odd to end up here but we handle it anyway,\n      // just to ensure that we do not crash the isolate. The only thing we'll\n      // do is rethrow the error.\n      js.v8Isolate->ThrowException(exception.getHandle(js));\n    });\n  } catch (...) {\n    kj::throwFatalException(kj::getCaughtExceptionAsKj());\n  }\n}\n\n// Templated implementation for both evaluation and source phase dynamic imports\nv8::MaybeLocal<v8::Promise> dynamicImportModuleCallback(v8::Local<v8::Context> context,\n    v8::Local<v8::Data> host_defined_options,\n    v8::Local<v8::Value> resource_name,\n    v8::Local<v8::String> specifier,\n    SourcePhase isSourcePhase,\n    v8::Local<v8::FixedArray> import_attributes) {\n  auto& js = Lock::current();\n\n  // Since this method is called directly by V8, we don't want to use jsg::check\n  // or the js.rejectedPromise variants since those can throw JsExceptionThrown.\n  constexpr static auto rejected = [](jsg::Lock& js,\n                                       const jsg::JsValue& error) -> v8::MaybeLocal<v8::Promise> {\n    v8::Local<v8::Promise::Resolver> resolver;\n    if (!v8::Promise::Resolver::New(js.v8Context()).ToLocal(&resolver) ||\n        resolver->Reject(js.v8Context(), error).IsNothing()) {\n      return v8::MaybeLocal<v8::Promise>();\n    }\n    return resolver->GetPromise();\n  };\n\n  auto& registry = IsolateModuleRegistry::from(js.v8Isolate);\n  try {\n    return js.tryCatch([&]() -> v8::MaybeLocal<v8::Promise> {\n      auto spec = specifierToString(js, specifier);\n\n      // The proposed specification for import attributes strongly recommends that\n      // embedders reject import attributes and types they do not understand/implement.\n      // This is because import attributes can alter the interpretation of a module.\n      // Throwing an error for things we do not understand is the safest thing to do\n      // for backwards compatibility.\n      //\n      // For now, we do not support any import attributes, so if there are any at all\n      // we will reject the import.\n      if (!import_attributes.IsEmpty() && import_attributes->Length() > 0) {\n        return rejected(js, js.typeError(\"Import attributes are not supported\"));\n      };\n\n      Url referrer = ([&] {\n        if (resource_name.IsEmpty()) {\n          return registry.getBundleBase().clone();\n        }\n        auto str = js.toString(resource_name);\n        return KJ_ASSERT_NONNULL(Url::tryParse(str.asPtr()));\n      })();\n\n      // If Node.js Compat v2 mode is enable, we have to check to see if the specifier\n      // is a bare node specifier and resolve it to a full node: URL.\n      if (isNodeJsCompatEnabled(js)) {\n        KJ_IF_SOME(nodeSpec, checkNodeSpecifier(spec)) {\n          spec = kj::mv(nodeSpec);\n        }\n      }\n\n      // Handle process module redirection based on enable_nodejs_process_v2 flag\n      KJ_IF_SOME(processUrl, maybeRedirectNodeProcess(js, spec.asPtr())) {\n        auto processSpec = kj::str(processUrl.getHref());\n        return registry.dynamicResolve(\n            js, processUrl.clone(), kj::mv(referrer), processSpec, isSourcePhase);\n      }\n\n      KJ_IF_SOME(url, referrer.tryResolve(spec.asPtr())) {\n        auto normalized = url.clone(Url::EquivalenceOption::NORMALIZE_PATH);\n        return registry.dynamicResolve(\n            js, kj::mv(normalized), kj::mv(referrer), spec, isSourcePhase);\n      }\n\n      // We were not able to parse the specifier. We'll return a rejected promise.\n      return rejected(js, js.typeError(kj::str(\"Invalid module specifier: \", spec)));\n    }, [&](Value exception) -> v8::MaybeLocal<v8::Promise> {\n      // If there are any synchronously thrown exceptions, we want to catch them\n      // here and convert them into a rejected promise. The only exception are\n      // fatal cases where the isolate is terminating which won't make it here\n      // anyway.\n      return rejected(js, jsg::JsValue(exception.getHandle(js)));\n    });\n  } catch (...) {\n    kj::throwFatalException(kj::getCaughtExceptionAsKj());\n  }\n}\n\n// Wrapper functions to match the V8 callback signatures\nv8::MaybeLocal<v8::Promise> dynamicImport(v8::Local<v8::Context> context,\n    v8::Local<v8::Data> host_defined_options,\n    v8::Local<v8::Value> resource_name,\n    v8::Local<v8::String> specifier,\n    v8::Local<v8::FixedArray> import_attributes) {\n  return dynamicImportModuleCallback(\n      context, host_defined_options, resource_name, specifier, SourcePhase::NO, import_attributes);\n}\n\nv8::MaybeLocal<v8::Promise> dynamicImportWithPhase(v8::Local<v8::Context> context,\n    v8::Local<v8::Data> host_defined_options,\n    v8::Local<v8::Value> resource_name,\n    v8::Local<v8::String> specifier,\n    v8::ModuleImportPhase phase,\n    v8::Local<v8::FixedArray> import_attributes) {\n  SourcePhase sourcePhase =\n      (phase == v8::ModuleImportPhase::kSource) ? SourcePhase::YES : SourcePhase::NO;\n  return dynamicImportModuleCallback(\n      context, host_defined_options, resource_name, specifier, sourcePhase, import_attributes);\n}\n\nIsolateModuleRegistry::IsolateModuleRegistry(\n    Lock& js, const ModuleRegistry& registry, const CompilationObserver& observer)\n    : inner(registry),\n      observer(observer),\n      lookupCache(EntryCallbacks{}, ContextCallbacks{}, UrlCallbacks{}) {\n  auto isolate = js.v8Isolate;\n  auto context = isolate->GetCurrentContext();\n  KJ_ASSERT(!context.IsEmpty());\n  setAlignedPointerInEmbedderData(context, ContextPointerSlot::MODULE_REGISTRY, this);\n  isolate->SetHostImportModuleDynamicallyCallback(&dynamicImport);\n  isolate->SetHostImportModuleWithPhaseDynamicallyCallback(&dynamicImportWithPhase);\n  isolate->SetHostInitializeImportMetaObjectCallback(&importMeta);\n}\n\n// Generalized module resolution callback that handles both evaluation and source phase imports\ntemplate <bool IsSourcePhase>\nv8::MaybeLocal<std::conditional_t<IsSourcePhase, v8::Object, v8::Module>> resolveModuleCallback(\n    v8::Local<v8::Context> context,\n    v8::Local<v8::String> specifier,\n    v8::Local<v8::FixedArray> import_attributes,\n    v8::Local<v8::Module> referrer) {\n  using ReturnType = std::conditional_t<IsSourcePhase, v8::Object, v8::Module>;\n  auto& js = Lock::current();\n  auto& registry = IsolateModuleRegistry::from(js.v8Isolate);\n\n  return js.tryCatch([&]() -> v8::MaybeLocal<ReturnType> {\n    auto spec = specifierToString(js, specifier);\n\n    // The proposed specification for import attributes strongly recommends that\n    // embedders reject import attributes and types they do not understand/implement.\n    // This is because import attributes can alter the interpretation of a module.\n    // Throwing an error for things we do not understand is the safest thing to do\n    // for backwards compatibility.\n    //\n    // For now, we do not support any import attributes, so if there are any at all\n    // we will reject the import.\n    if (!import_attributes.IsEmpty() && import_attributes->Length() > 0) {\n      js.throwException(js.typeError(\"Import attributes are not supported\"));\n    }\n\n    ResolveContext::Type type = ResolveContext::Type::BUNDLE;\n\n    auto& referrerUrl = registry.lookup(js, referrer)\n                            .map([&](IsolateModuleRegistry::Entry& entry) -> const Url& {\n      type = moduleTypeToResolveContextType(entry.module.type());\n      return entry.context.id;\n    }).orDefault(registry.getBundleBase());\n\n    // If Node.js Compat v2 mode is enable, we have to check to see if the specifier\n    // is a bare node specifier and resolve it to a full node: URL.\n    if (isNodeJsCompatEnabled(js)) {\n      KJ_IF_SOME(nodeSpec, checkNodeSpecifier(spec)) {\n        spec = kj::mv(nodeSpec);\n      }\n    }\n\n    // Handle process module redirection based on enable_nodejs_process_v2 flag\n    if constexpr (!IsSourcePhase) {\n      KJ_IF_SOME(processUrl, maybeRedirectNodeProcess(js, spec.asPtr())) {\n        auto processSpec = kj::str(processUrl.getHref());\n        ResolveContext resolveContext = {\n          .type = ResolveContext::Type::BUILTIN_ONLY,\n          .source = ResolveContext::Source::STATIC_IMPORT,\n          .normalizedSpecifier = processUrl,\n          .referrerNormalizedSpecifier = referrerUrl,\n          .rawSpecifier = processSpec.asPtr(),\n        };\n\n        return registry.resolve(js, resolveContext);\n      }\n    }\n\n    KJ_IF_SOME(url, referrerUrl.tryResolve(spec)) {\n      // Make sure that percent-encoding in the path is normalized so we can match correctly.\n      auto normalized = url.clone(Url::EquivalenceOption::NORMALIZE_PATH);\n      ResolveContext resolveContext = {\n        .type = type,\n        .source = ResolveContext::Source::STATIC_IMPORT,\n        .normalizedSpecifier = normalized,\n        .referrerNormalizedSpecifier = referrerUrl,\n        .rawSpecifier = spec.asPtr(),\n      };\n\n      auto maybeResolved = registry.resolve(js, resolveContext);\n      if (maybeResolved.IsEmpty()) {\n        return v8::MaybeLocal<ReturnType>();\n      }\n\n      auto resolved = check(maybeResolved);\n\n      if constexpr (!IsSourcePhase) {\n        return resolved;\n      } else {\n        KJ_IF_SOME(entry, registry.lookup(js, resolved)) {\n          // We only support source phase imports for Wasm modules.\n          // Since we do not have an async pre-instantiation phase which populates compilation,\n          // and instead have compilation happening lazily in evaluate calls, we implement this\n          // hack to synchronously obtain the compiled Wasm leaning into the require implementation\n          // for the Wasm then plucking out the compiled Wasm module.\n          // In future, the source phase should be eagerly populated during pre-innstantiation\n          // with the compiled record, so that we can just directly read `sourceObject_` off of\n          // entry.module instead.\n          if (entry.module.isWasm()) {\n            v8::Local<v8::Object> moduleNamespace;\n            if (registry\n                    .require(js, resolveContext, IsolateModuleRegistry::RequireOption::RETURN_EMPTY)\n                    .ToLocal(&moduleNamespace)) {\n              v8::Local<v8::Value> defaultExport;\n              if (moduleNamespace->Get(js.v8Context(), js.strIntern(\"default\"_kj))\n                      .ToLocal(&defaultExport)) {\n                if (defaultExport->IsWasmModuleObject()) {\n                  return defaultExport.As<v8::Object>();\n                }\n              }\n            }\n          }\n        }\n        js.throwException(js.v8Ref(v8::Exception::SyntaxError(\n            js.strIntern(kj::str(\"Source phase import not available for module: \"_kj, spec)))));\n        return v8::MaybeLocal<ReturnType>();\n      }\n    }\n\n    js.throwException(js.error(kj::str(\"Invalid module specifier: \"_kj, specifier)));\n    return v8::MaybeLocal<ReturnType>();\n  }, [&](Value exception) -> v8::MaybeLocal<ReturnType> {\n    // If there are any synchronously thrown exceptions, we want to catch them\n    // here and convert them into a rejected promise. The only exception are\n    // fatal cases where the isolate is terminating which won't make it here\n    // anyway.\n    js.v8Isolate->ThrowException(exception.getHandle(js));\n    return v8::MaybeLocal<ReturnType>();\n  });\n}\n\n// The fallback module bundle calls a single resolve callback to resolve all modules\n// it is asked to resolve. Thread safety is provided by the ModuleRegistry's\n// MutexGuarded<Impl> exclusive lock — all calls to lookup() are made while\n// holding that lock.\nclass FallbackModuleBundle final: public ModuleBundle {\n public:\n  FallbackModuleBundle(Builder::ResolveCallback&& callback)\n      : ModuleBundle(Type::FALLBACK),\n        callback(kj::mv(callback)) {}\n\n  kj::Maybe<Resolved> lookup(const ResolveContext& context) override {\n    // Maybe it's an alias? If so, we just return the aliased specifier.\n    // We don't resolve again because the alias might be to a specifier\n    // in another bundle. We should start the resolution process over\n    // from the start.\n    KJ_IF_SOME(found, aliases.find(context.normalizedSpecifier)) {\n      return Resolved{\n        .specifier = kj::str(found),\n      };\n    }\n\n    // Maybe it's already cached? If so, we just return the cached module.\n    KJ_IF_SOME(found, storage.find(context.normalizedSpecifier)) {\n      return Resolved{\n        .module = *found,\n      };\n    }\n\n    // Well, let's actually try to resolve it.\n    KJ_IF_SOME(resolved, callback(context)) {\n      KJ_SWITCH_ONEOF(resolved) {\n        KJ_CASE_ONEOF(str, kj::String) {\n          // We got an alias back. Store it and return it, unless it's an alias\n          // to itself, in which case return kj::none. It's possible that a buggy\n          // fallback resolver could end up in an infinite loop of aliasing.\n          if (str == context.normalizedSpecifier.getHref()) {\n            return kj::none;\n          }\n          aliases.insert(context.normalizedSpecifier.clone(), kj::str(str));\n          return Resolved{\n            .specifier = kj::mv(str),\n          };\n        }\n        KJ_CASE_ONEOF(resolved, kj::Own<Module>) {\n          auto& module = *resolved;\n          // If the fallback service returned a module with a specifier that\n          // already exists in storage, ignore it and return kj::none. We can't\n          // have two different modules with the same specifier in the bundle.\n          if (storage.find(module.id()) != kj::none) {\n            return kj::none;\n          }\n          storage.insert(module.id().clone(), kj::mv(resolved));\n          if (context.normalizedSpecifier != module.id()) {\n            // We checked for the existence of the specifier alias above so this\n            // insert should always succeed. In debug mode, let's check.\n            KJ_DASSERT(aliases.find(context.normalizedSpecifier) == kj::none);\n            aliases.insert(context.normalizedSpecifier.clone(), kj::str(module.id().getHref()));\n          }\n          return Resolved{\n            .module = module,\n          };\n        }\n      }\n      KJ_UNREACHABLE;\n    }\n\n    return kj::none;\n  }\n\n private:\n  Builder::ResolveCallback callback;\n\n  kj::HashMap<Url, kj::Own<Module>> storage;\n  kj::HashMap<Url, kj::String> aliases;\n};\n\n// The static module bundle maintains an internal table of specifiers to resolve callbacks\n// in memory. Thread safety is provided by the ModuleRegistry's MutexGuarded<Impl>\n// exclusive lock — all calls to lookup() are made while holding that lock.\nclass StaticModuleBundle final: public ModuleBundle {\n public:\n  StaticModuleBundle(Type type,\n      kj::HashMap<Url, ModuleBundle::Builder::ResolveCallback> modules,\n      kj::HashMap<Url, Url> aliases)\n      : ModuleBundle(type),\n        modules(kj::mv(modules)),\n        aliases(kj::mv(aliases)) {}\n  KJ_DISALLOW_COPY_AND_MOVE(StaticModuleBundle);\n\n  kj::Maybe<Resolved> lookup(const ResolveContext& context) override {\n    // Is it an alias? If so, we just return the aliased specifier.\n    KJ_IF_SOME(aliased, aliases.find(context.normalizedSpecifier)) {\n      return Resolved{\n        .specifier = kj::str(aliased.getHref()),\n      };\n    }\n\n    // It's not an alias, maybe it's already cached?\n    KJ_IF_SOME(cached, cache.find(context.normalizedSpecifier)) {\n      return Resolved{\n        .module = checkModule(context, *cached),\n      };\n    }\n\n    // Not aliased or cached, we need to look it up.\n    KJ_IF_SOME(found, modules.find(context.normalizedSpecifier)) {\n      KJ_IF_SOME(resolved, found(context)) {\n        KJ_SWITCH_ONEOF(resolved) {\n          KJ_CASE_ONEOF(str, kj::String) {\n            return Resolved{\n              .specifier = kj::mv(str),\n            };\n          }\n          KJ_CASE_ONEOF(resolved, kj::Own<Module>) {\n            const Module& module = *resolved;\n            cache.insert(context.normalizedSpecifier.clone(), kj::mv(resolved));\n            return Resolved{\n              .module = checkModule(context, module),\n            };\n          }\n        }\n        KJ_UNREACHABLE;\n      }\n    }\n\n    return kj::none;\n  }\n\n private:\n  kj::HashMap<Url, ModuleBundle::Builder::ResolveCallback> modules;\n  kj::HashMap<Url, Url> aliases;\n  kj::HashMap<Url, kj::Own<Module>> cache;\n};\n\nkj::HashSet<kj::StringPtr> toHashSet(kj::ArrayPtr<const kj::String> arr) {\n  kj::HashSet<kj::StringPtr> set;\n  set.insertAll(arr);\n  // Make sure there is no \"default\" export listed explicitly in the set.\n  set.eraseMatch(\"default\"_kj);\n  return kj::mv(set);\n}\n\n}  // namespace\n\n// ======================================================================================\nkj::Own<ModuleBundle> ModuleBundle::newFallbackBundle(Builder::ResolveCallback callback) {\n  return kj::heap<FallbackModuleBundle>(kj::mv(callback));\n}\n\nvoid ModuleBundle::getBuiltInBundleFromCapnp(BuiltinBuilder& builder, Bundle::Reader bundle) {\n  getBuiltInBundleFromCapnp(builder, bundle, [](workerd::jsg::Module::Reader) { return true; });\n}\n\nvoid ModuleBundle::getBuiltInBundleFromCapnp(BuiltinBuilder& builder,\n    Bundle::Reader bundle,\n    kj::Function<bool(workerd::jsg::Module::Reader)> filter) {\n  auto typeFilter = ([&] {\n    switch (builder.type()) {\n      case Module::Type::BUILTIN:\n        return ModuleType::BUILTIN;\n      case Module::Type::BUILTIN_ONLY:\n        return ModuleType::INTERNAL;\n      case Module::Type::BUNDLE:\n        break;\n      case Module::Type::FALLBACK:\n        break;\n    }\n    KJ_UNREACHABLE;\n  })();\n\n  for (auto module: bundle.getModules()) {\n    if (module.getType() == typeFilter && filter(module)) {\n      auto id = KJ_ASSERT_NONNULL(Url::tryParse(module.getName()));\n      switch (module.which()) {\n        case workerd::jsg::Module::SRC: {\n          builder.addEsm(id, module.getSrc().asChars());\n          continue;\n        }\n        case workerd::jsg::Module::WASM: {\n          builder.addSynthetic(id, Module::newWasmModuleHandler(module.getWasm().asBytes()));\n          continue;\n        }\n        case workerd::jsg::Module::DATA: {\n          builder.addSynthetic(id, Module::newDataModuleHandler(module.getData().asBytes()));\n          continue;\n        }\n        case workerd::jsg::Module::JSON: {\n          builder.addSynthetic(id, Module::newJsonModuleHandler(module.getJson().asArray()));\n          continue;\n        }\n      }\n      KJ_UNREACHABLE;\n    }\n  }\n}\n\nModuleBundle::ModuleBundle(Type type): type_(type) {}\n\nModuleBundle::Builder::Builder(Type type): type_(type) {}\n\nModuleBundle::Builder& ModuleBundle::Builder::alias(const Url& alias, const Url& id) {\n  auto aliasNormed = alias.clone(Url::EquivalenceOption::NORMALIZE_PATH);\n  if (modules_.find(aliasNormed) != kj::none || aliases_.find(aliasNormed) != kj::none) {\n    KJ_FAIL_REQUIRE(kj::str(\"Module \\\"\", aliasNormed.getHref(), \"\\\" already added to bundle\"));\n  }\n  aliases_.insert(kj::mv(aliasNormed), id.clone(Url::EquivalenceOption::NORMALIZE_PATH));\n  return *this;\n}\n\nModuleBundle::Builder& ModuleBundle::Builder::add(\n    const Url& id, Builder::ResolveCallback callback) {\n  if (modules_.find(id) != kj::none || aliases_.find(id) != kj::none) {\n    KJ_FAIL_REQUIRE(kj::str(\"Module \\\"\", id.getHref(), \"\\\" already added to bundle\"));\n  }\n  modules_.insert(id.clone(), kj::mv(callback));\n  return *this;\n}\n\nkj::Own<ModuleBundle> ModuleBundle::Builder::finish() {\n  return kj::heap<StaticModuleBundle>(type_, kj::mv(modules_), kj::mv(aliases_));\n}\n\nvoid ModuleBundle::Builder::ensureIsNotBundleSpecifier(const Url& id) {\n  // The file: protocol is reserved for bundle type modules.\n  KJ_REQUIRE(\n      id.getProtocol() != \"file:\"_kjc, \"The file: protocol is reserved for bundle type modules\");\n}\n\n// ======================================================================================\n\nModuleBundle::BundleBuilder::BundleBuilder(const jsg::Url& bundleBase)\n    : ModuleBundle::Builder(Type::BUNDLE),\n      bundleBase(bundleBase) {}\n\nnamespace {\nstatic constexpr auto BUNDLE_CLONE_OPTIONS = jsg::Url::EquivalenceOption::IGNORE_FRAGMENTS |\n    jsg::Url::EquivalenceOption::IGNORE_SEARCH | jsg::Url::EquivalenceOption::NORMALIZE_PATH;\n\n// Takes the user-provided module name and normalizes it to a form that can be\n// resolved relative to the bundle base. This involves pre-parsing the name as a URL\n// relative to a dummy base URL in order to normalize out dot and double-dot segments,\n// then stripping off any leading slashes so that the name is always relative and cannot\n// be interpreted as an absolute path.\njsg::Url normalizeModuleName(kj::StringPtr name, const jsg::Url& base) {\n  // This first step normalizes out path segments like \".\" and \"..\", drops query\n  // strings and fragments, and normalizes percent-encoding in the path.\n  auto url = KJ_ASSERT_NONNULL(base.tryResolve(name)).clone(BUNDLE_CLONE_OPTIONS);\n\n  // If the protocol is not file:, then we don't need to do any more processing\n  // here. We will check the validity of the result as a module URL in the next\n  // step.\n  if (url.getProtocol() != \"file:\"_kj) {\n    return kj::mv(url);\n  }\n\n  auto urlPath = url.getPathname();\n  auto basePath = base.getPathname();\n\n  // The url path must not be identical to the base...\n  KJ_REQUIRE(urlPath != basePath, \"Invalid empty module name\");\n\n  // If the url path starts with the base path, then we're good!\n  if (urlPath.startsWith(basePath)) {\n    return kj::mv(url);\n  }\n\n  // Otherwise, let's make sure that the url path is processed as\n  // relative to the base path. We do this by stripping off any\n  // leading slashes from the front of the URL then re-resolve\n  // against the base. This should be an exceedingly rare edge\n  // case if the worker bundle is being constructed properly. It's\n  // meant only to handle cases where silliness like \"///foo\" is\n  // given as a module name.\n  while (urlPath.startsWith(\"/\"_kj) && urlPath.size() > 0) {\n    urlPath = urlPath.slice(1);\n  }\n  KJ_REQUIRE(urlPath.size() > 0, \"Invalid empty module name\");\n\n  return KJ_ASSERT_NONNULL(base.tryResolve(urlPath));\n}\n\nbool isValidBundleModuleUrl(const jsg::Url& url, const jsg::Url& base) {\n  KJ_DASSERT(base.getProtocol() == \"file:\"_kj);\n  KJ_DASSERT(base.getPathname().endsWith(\"/\"_kj));\n\n  // Let's forbid users from using cloudflare: and workerd: URLs in bundles so that\n  // we can protect those namespaces for our own future use. Specifically, these\n  // should only be used by the runtime to refer to built-in modules. We don't\n  // restrict other non-standard protocols like node:\n  KJ_REQUIRE(url.getProtocol() != \"cloudflare:\"_kj,\n      \"The cloudflare: protocol is reserved and cannot be used in module bundles\");\n  KJ_REQUIRE(url.getProtocol() != \"workerd:\"_kj,\n      \"The workerd: protocol is reserved and cannot be used in module bundles\");\n\n  // Let's forbid data: URL use from module bundles. They are not yet supported\n  // by the runtime due to dynamic eval restrictions. Even when we do support them\n  // eventually, we want to be able to reserve the data: URL namespace for that\n  // use. If we allowed worker bundles to use data: URLs that we could end up\n  // requiring a compat flag later to actually properly enable them.\n  KJ_REQUIRE(\n      url.getProtocol() != \"data:\"_kj, \"The data: protocol cannot be used in module bundles\");\n\n  if (url.getProtocol() != \"file:\"_kj) {\n    // Different protocols are always OK\n    return true;\n  }\n\n  // Module file: URLs must not have a host component.\n  // We already know the protocol is \"file:\" here because of the check above.\n  if (url.getHost() != \"\"_kj) {\n    return false;\n  }\n\n  // Check if url is subordinate to the base.\n  // This means url's path should start with base's path as a prefix\n  auto aPath = url.getPathname();\n  auto bPath = base.getPathname();\n\n  return aPath.startsWith(bPath);\n}\n\n// Converts the name given for a user-bundle module into a fully qualified module url.\n// This involves normalizing the name such that it is relative to the bundle base, removes\n// any query parameters or fragments, removes dot and double-dot path segments, normalizes\n// percent-encoding, and otherwise validates that the resulting URL is a valid URL.\nconst jsg::Url processModuleName(kj::StringPtr name, const jsg::Url& base) {\n  auto url = normalizeModuleName(name, base);\n  KJ_REQUIRE(isValidBundleModuleUrl(url, base), \"Invalid module name: \", name);\n  return url;\n}\n\n}  // namespace\n\nModuleBundle::BundleBuilder& ModuleBundle::BundleBuilder::addSyntheticModule(\n    kj::StringPtr name, EvaluateCallback callback, kj::Array<kj::String> namedExports) {\n  const auto url = processModuleName(name, bundleBase);\n  add(url,\n      [url = url.clone(), callback = kj::mv(callback), namedExports = kj::mv(namedExports),\n          type = type()](const ResolveContext& context) mutable\n      -> kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>> {\n    kj::Own<Module> mod =\n        Module::newSynthetic(kj::mv(url), type, kj::mv(callback), kj::mv(namedExports));\n    return kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>>(kj::mv(mod));\n  });\n  return *this;\n}\n\nModuleBundle::BundleBuilder& ModuleBundle::BundleBuilder::addEsmModule(\n    kj::StringPtr name, kj::ArrayPtr<const char> source, Module::Flags flags) {\n  const auto url = processModuleName(name, bundleBase);\n  add(url,\n      [url = url.clone(), source, flags, type = type()](const ResolveContext& context) mutable\n      -> kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>> {\n    kj::Own<Module> mod = kj::heap<EsModule>(kj::mv(url), type, flags, source);\n    return kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>>(kj::mv(mod));\n  });\n  return *this;\n}\n\nModuleBundle::BundleBuilder& ModuleBundle::BundleBuilder::addEsmModule(\n    kj::StringPtr name, kj::Array<const char> source, Module::Flags flags) {\n  const auto url = processModuleName(name, bundleBase);\n  add(url,\n      [url = url.clone(), source = kj::mv(source), flags, type = type()](\n          const ResolveContext& context) mutable\n      -> kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>> {\n    kj::Own<Module> mod = Module::newEsm(kj::mv(url), type, kj::mv(source), flags);\n    return kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>>(kj::mv(mod));\n  });\n  return *this;\n}\n\nModuleBundle::BundleBuilder& ModuleBundle::BundleBuilder::addWasmModule(\n    kj::StringPtr name, kj::ArrayPtr<const kj::byte> data) {\n  const auto url = processModuleName(name, bundleBase);\n  auto callback = jsg::modules::Module::newWasmModuleHandler(data);\n  add(url,\n      [url = url.clone(), callback = kj::mv(callback), type = type()](\n          const ResolveContext& context) mutable\n      -> kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>> {\n    kj::Own<Module> mod =\n        Module::newSynthetic(kj::mv(url), type, kj::mv(callback), nullptr, EsModule::Flags::WASM);\n    return kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>>(kj::mv(mod));\n  });\n  return *this;\n}\n\nModuleBundle::BundleBuilder& ModuleBundle::BundleBuilder::alias(\n    kj::StringPtr alias, kj::StringPtr name) {\n  const auto id = processModuleName(name, bundleBase);\n  const auto aliasUrl = processModuleName(alias, bundleBase);\n  Builder::alias(aliasUrl, id);\n  return *this;\n}\n\n// ======================================================================================\n\nModuleBundle::BuiltinBuilder::BuiltinBuilder(Type type)\n    : ModuleBundle::Builder(toModuleBuilderType(type)) {}\n\nModuleBundle::BuiltinBuilder& ModuleBundle::BuiltinBuilder::addSynthetic(\n    const Url& id, ModuleBundle::BundleBuilder::EvaluateCallback callback) {\n  ensureIsNotBundleSpecifier(id);\n  Builder::add(id,\n      [url = id.clone(), callback = kj::mv(callback), type = type()](\n          const ResolveContext& context) mutable\n      -> kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>> {\n    kj::Own<Module> mod = Module::newSynthetic(kj::mv(url), type, kj::mv(callback));\n    return kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>>(kj::mv(mod));\n  });\n  return *this;\n}\n\nModuleBundle::BuiltinBuilder& ModuleBundle::BuiltinBuilder::addEsm(\n    const Url& id, kj::ArrayPtr<const char> source) {\n  ensureIsNotBundleSpecifier(id);\n  Builder::add(id,\n      [url = id.clone(), source, type = type()](const ResolveContext& context) mutable\n      -> kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>> {\n    kj::Own<Module> mod = Module::newEsm(kj::mv(url), type, source);\n    return kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>>(kj::mv(mod));\n  });\n  return *this;\n}\n\n// ======================================================================================\nModuleRegistry::Impl::Impl(kj::ArrayPtr<kj::Vector<kj::Own<ModuleBundle>>> vectors) {\n  bundles[kBundle] = vectors[kBundle].releaseAsArray();\n  bundles[kBuiltin] = vectors[kBuiltin].releaseAsArray();\n  bundles[kBuiltinOnly] = vectors[kBuiltinOnly].releaseAsArray();\n  bundles[kFallback] = vectors[kFallback].releaseAsArray();\n}\n\nModuleRegistry::Builder::Builder(\n    const ResolveObserver& observer, const jsg::Url& bundleBase, Options options)\n    : observer(observer),\n      bundleBase(bundleBase),\n      options(options),\n      schemaLoader(kj::heap<capnp::SchemaLoader>()) {}\n\nbool ModuleRegistry::Builder::allowsFallback() const {\n  return (options & Options::ALLOW_FALLBACK) == Options::ALLOW_FALLBACK;\n}\n\nModuleRegistry::Builder& ModuleRegistry::Builder::add(kj::Own<ModuleBundle> bundle) {\n  if (!allowsFallback()) {\n    KJ_REQUIRE(bundle->type() != ModuleBundle::Type::FALLBACK,\n        \"Fallback bundle types are not allowed for this registry\");\n  }\n  bundles_[static_cast<int>(bundle->type())].add(kj::mv(bundle));\n  return *this;\n}\n\nModuleRegistry::Builder& ModuleRegistry::Builder::setEvalCallback(EvalCallback callback) {\n  maybeEvalCallback = kj::mv(callback);\n  return *this;\n}\n\nkj::Arc<ModuleRegistry> ModuleRegistry::Builder::finish() {\n  return kj::arc<ModuleRegistry>(this);\n}\n\nModuleRegistry::ModuleRegistry(ModuleRegistry::Builder* builder)\n    : observer(builder->observer),\n      bundleBase(builder->bundleBase),\n      impl(Impl(builder->bundles_.asPtr())),\n      maybeEvalCallback(kj::mv(builder->maybeEvalCallback)),\n      schemaLoader(kj::mv(builder->schemaLoader)) {}\n\nkj::Maybe<jsg::Promise<Value>> ModuleRegistry::evaluateImpl(jsg::Lock& js,\n    const Module& module,\n    v8::Local<v8::Module> v8Module,\n    const CompilationObserver& observer) const {\n  KJ_IF_SOME(callback, maybeEvalCallback) {\n    return callback(js, module, v8Module, observer);\n  }\n  return kj::none;\n}\n\nkj::Own<void> ModuleRegistry::attachToIsolate(Lock& js, const CompilationObserver& observer) const {\n  // The IsolateModuleRegistry is attached to the isolate as an embedder data slot.\n  // We have to keep it alive for the duration of the v8::Context so we return a\n  // kj::Own and store that in the jsg::JsContext\n  return kj::heap<IsolateModuleRegistry>(js, *this, observer);\n}\n\nkj::Maybe<ModuleRegistry::ModuleOrRedirect> ModuleRegistry::tryFindInBundle(\n    const ResolveContext& context, ModuleBundle& bundle, const Url& bundleBase) {\n  KJ_IF_SOME(found, bundle.lookup(context)) {\n    KJ_IF_SOME(str, found.specifier) {\n      // We received a redirect to another module specifier. Let's\n      // start resolution over again with the new specifier... but only\n      // if we can successfully parse the specifier as a URL.\n      KJ_IF_SOME(id, jsg::Url::tryParse(str.asPtr(), bundleBase.getHref())) {\n        return kj::Maybe(kj::mv(id));\n      }\n    }\n    KJ_IF_SOME(module, found.module) {\n      return kj::Maybe(ModuleRef{\n        .module = module,\n      });\n    }\n  }\n  return kj::none;\n}\n\nkj::Maybe<ModuleRegistry::ModuleOrRedirect> ModuleRegistry::tryFindInBundleGroup(\n    const ResolveContext& context, kj::ArrayPtr<kj::Own<ModuleBundle>> bundles) const {\n  for (auto& bundle: bundles) {\n    KJ_IF_SOME(found, tryFindInBundle(context, *bundle, bundleBase)) {\n      return kj::mv(found);\n    }\n  }\n  return kj::none;\n}\n\nkj::Maybe<const Module&> ModuleRegistry::lookupImpl(\n    Impl& impl, const ResolveContext& context, bool recursed) const {\n#define MODULE_LOOKUP(context, bundle)                                                             \\\n  KJ_IF_SOME(found, tryFindInBundleGroup(context, impl.bundles[bundle])) {                         \\\n    KJ_SWITCH_ONEOF(found) {                                                                       \\\n      KJ_CASE_ONEOF(url, Url) {                                                                    \\\n        if (recursed) { /* avoid recursing indefinitely */                                         \\\n          return kj::none;                                                                         \\\n        }                                                                                          \\\n        kj::HashMap<kj::StringPtr, kj::StringPtr> clonedAttrs;                                     \\\n        for (const auto& [key, value]: context.attributes) {                                       \\\n          clonedAttrs.insert(key, value);                                                          \\\n        }                                                                                          \\\n        ResolveContext ctx{                                                                        \\\n          .type = context.type,                                                                    \\\n          .source = context.source,                                                                \\\n          .normalizedSpecifier = url,                                                              \\\n          .referrerNormalizedSpecifier = context.referrerNormalizedSpecifier,                      \\\n          .rawSpecifier =                                                                          \\\n              context.rawSpecifier.map([](auto& str) -> kj::StringPtr { return str; }),            \\\n          .attributes = kj::mv(clonedAttrs),                                                       \\\n        };                                                                                         \\\n        return lookupImpl(impl, ctx, true);                                                        \\\n      }                                                                                            \\\n      KJ_CASE_ONEOF(mod, ModuleRef) {                                                              \\\n        return mod.module;                                                                         \\\n      }                                                                                            \\\n    }                                                                                              \\\n    KJ_UNREACHABLE;                                                                                \\\n  }\n\n  switch (context.type) {\n    case ResolveContext::Type::BUNDLE: {\n      // For bundle resolution, we only use Bundle, Builtin, and Fallback bundles,\n      // in that order.\n      MODULE_LOOKUP(context, kBundle);\n      MODULE_LOOKUP(context, kBuiltin);\n      MODULE_LOOKUP(context, kFallback);\n      break;\n    }\n    case ResolveContext::Type::BUILTIN: {\n      // For built-in resolution, we only use builtin and builtin-only bundles.\n      MODULE_LOOKUP(context, kBuiltin);\n      MODULE_LOOKUP(context, kBuiltinOnly);\n      break;\n    }\n    case ResolveContext::Type::BUILTIN_ONLY: {\n      // For built-in only resolution, we only use builtin-only bundles.\n      MODULE_LOOKUP(context, kBuiltinOnly);\n      break;\n    }\n    case ResolveContext::Type::PUBLIC_BUILTIN: {\n      // For public built-in resolution, we only use builtin bundles.\n      // This excludes both worker bundle modules and internal-only modules,\n      // returning only built-ins that are normally importable by user code.\n      MODULE_LOOKUP(context, kBuiltin);\n      break;\n    }\n  }\n\n#undef MODULE_LOOKUP\n\n  return kj::none;\n}\n\nkj::Maybe<const Module&> ModuleRegistry::lookup(const ResolveContext& context) const {\n  // If the embedder supports it, collect metrics on what modules were resolved.\n  auto metrics =\n      observer.onResolveModule(context.normalizedSpecifier, context.type, context.source);\n\n  // While multiple threads may be holding references to the registry, only one thread\n  // at a time may resolve a module. Resolving a module may involve mutating internal\n  // state (e.g. caching) so we lock here. Fortunately, module resolution should be\n  // fast, especially with caching, so this lock should be held only briefly.\n  auto lock = impl.lockExclusive();\n  KJ_IF_SOME(found, lookupImpl(*lock, context, false)) {\n    metrics->found();\n    return found;\n  }\n\n  metrics->notFound();\n  return kj::none;\n}\n\nkj::Maybe<JsObject> ModuleRegistry::tryResolveModuleNamespace(Lock& js,\n    kj::StringPtr specifier,\n    ResolveContext::Type type,\n    ResolveContext::Source source,\n    kj::Maybe<const Url&> maybeReferrer) {\n  auto& bound = IsolateModuleRegistry::from(js.v8Isolate);\n  auto url = ([&] {\n    KJ_IF_SOME(referrer, maybeReferrer) {\n      return KJ_ASSERT_NONNULL(referrer.tryResolve(specifier));\n    }\n    return KJ_ASSERT_NONNULL(bound.getBundleBase().tryResolve(specifier));\n  })();\n  auto normalized = url.clone(Url::EquivalenceOption::NORMALIZE_PATH);\n  ResolveContext context{\n    .type = type,\n    .source = source,\n    .normalizedSpecifier = normalized,\n    .referrerNormalizedSpecifier = maybeReferrer.orDefault(bound.getBundleBase()),\n    .rawSpecifier = specifier,\n  };\n  v8::TryCatch tryCatch(js.v8Isolate);\n  auto ns = bound.require(js, context, IsolateModuleRegistry::RequireOption::RETURN_EMPTY);\n  if (tryCatch.HasCaught()) {\n    tryCatch.ReThrow();\n    throw JsExceptionThrown();\n  }\n  if (ns.IsEmpty()) return kj::none;\n  return JsObject(check(ns));\n}\n\nJsValue ModuleRegistry::resolve(Lock& js,\n    kj::StringPtr specifier,\n    kj::StringPtr exportName,\n    ResolveContext::Type type,\n    ResolveContext::Source source,\n    kj::Maybe<const Url&> maybeReferrer) {\n  KJ_IF_SOME(ns, tryResolveModuleNamespace(js, specifier, type, source, maybeReferrer)) {\n    return ns.get(js, exportName);\n  }\n  JSG_FAIL_REQUIRE(Error, kj::str(\"Module not found: \", specifier));\n}\n\n// ======================================================================================\n\nModule::Module(Url id, Type type, Flags flags): id_(kj::mv(id)), type_(type), flags_(flags) {}\n\nkj::Maybe<jsg::Promise<Value>> Module::Evaluator::operator()(jsg::Lock& js,\n    const Module& module,\n    v8::Local<v8::Module> v8Module,\n    const CompilationObserver& observer) const {\n  return registry.evaluateImpl(js, module, v8Module, observer);\n}\n\nbool Module::instantiate(\n    Lock& js, v8::Local<v8::Module> module, const CompilationObserver& observer) const {\n  if (module->GetStatus() != v8::Module::kUninstantiated) {\n    return true;\n  }\n  // InstantiateModule is one of those methods that returns a Maybe<bool> but\n  // never returns Just(false). It either returns Just(true) or an empty Maybe\n  // to signal that the instantiation failed. Eventually I would expect V8 to\n  // replace the return value with a Maybe<void>.\n  return module\n      ->InstantiateModule(js.v8Context(), resolveModuleCallback<false>, resolveModuleCallback<true>)\n      .IsJust();\n}\n\nbool Module::isEval() const {\n  return (flags_ & Flags::EVAL) == Flags::EVAL;\n}\n\nbool Module::isEsm() const {\n  return (flags_ & Flags::ESM) == Flags::ESM;\n}\n\nbool Module::isMain() const {\n  return (flags_ & Flags::MAIN) == Flags::MAIN;\n}\n\nbool Module::isWasm() const {\n  return (flags_ & Flags::WASM) == Flags::WASM;\n}\n\nbool Module::evaluateContext(const ResolveContext& context) const {\n  if (context.normalizedSpecifier != id()) return false;\n  // TODO(soon): Check the import attributes in the context.\n  return true;\n}\n\nkj::Own<Module> Module::newSynthetic(\n    Url id, Type type, EvaluateCallback callback, kj::Array<kj::String> namedExports, Flags flags) {\n  return kj::heap<SyntheticModule>(kj::mv(id), type, kj::mv(callback), kj::mv(namedExports), flags);\n}\n\nkj::Own<Module> Module::newEsm(Url id, Type type, kj::Array<const char> code, Flags flags) {\n  return kj::heap<EsModule>(kj::mv(id), type, flags, code).attach(kj::mv(code));\n}\n\nkj::Own<Module> Module::newEsm(Url id, Type type, kj::ArrayPtr<const char> code) {\n  return kj::heap<EsModule>(kj::mv(id), type, Flags::ESM, code);\n}\n\nModule::ModuleNamespace::ModuleNamespace(\n    v8::Local<v8::Module> inner, kj::ArrayPtr<const kj::String> namedExports)\n    : inner(inner),\n      namedExports(toHashSet(namedExports)) {}\n\nbool Module::ModuleNamespace::set(Lock& js, kj::StringPtr name, JsValue value) const {\n  if (name != \"default\"_kj) {\n    KJ_REQUIRE(namedExports.find(name) != kj::none, kj::str(\"Module does not export \", name));\n  }\n\n  bool result;\n  if (!inner->SetSyntheticModuleExport(js.v8Isolate, js.strIntern(name), value).To(&result)) {\n    return false;\n  }\n  if (!result) {\n    js.v8Isolate->ThrowError(js.str(kj::str(\"Failed to set synthetic module export \", name)));\n  }\n  return result;\n}\n\nbool Module::ModuleNamespace::setDefault(Lock& js, JsValue value) const {\n  return set(js, SyntheticModule::DEFAULT, value);\n}\n\nkj::ArrayPtr<const kj::StringPtr> Module::ModuleNamespace::getNamedExports() const {\n  return kj::ArrayPtr<const kj::StringPtr>(namedExports.begin(), namedExports.size());\n}\n\n// ======================================================================================\n// Methods to create evaluation callbacks for common synthetic module types. It is\n// important to remember that evaluation callbacks can be called multiple times and\n// from multiple threads. The callbacks must be thread-safe and idempotent.\n\nModule::EvaluateCallback Module::newTextModuleHandler(kj::ArrayPtr<const char> data) {\n  return [data](Lock& js, const Url& id, const ModuleNamespace& ns,\n             const CompilationObserver&) -> bool {\n    JSG_TRY(js) {\n      return ns.setDefault(js, js.str(data));\n    }\n    JSG_CATCH(exception) {\n      js.v8Isolate->ThrowException(exception.getHandle(js));\n      return false;\n    }\n  };\n}\n\nModule::EvaluateCallback Module::newDataModuleHandler(kj::ArrayPtr<const kj::byte> data) {\n  return [data](Lock& js, const Url& id, const ModuleNamespace& ns,\n             const CompilationObserver&) -> bool {\n    JSG_TRY(js) {\n      auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, data.size());\n      backing.asArrayPtr().copyFrom(data);\n      auto buffer = jsg::BufferSource(js, kj::mv(backing));\n      return ns.setDefault(js, JsValue(buffer.getHandle(js)));\n    }\n    JSG_CATCH(exception) {\n      js.v8Isolate->ThrowException(exception.getHandle(js));\n      return false;\n    }\n  };\n}\n\nModule::EvaluateCallback Module::newJsonModuleHandler(kj::ArrayPtr<const char> data) {\n  return [data](Lock& js, const Url& id, const ModuleNamespace& ns,\n             const CompilationObserver& observer) -> bool {\n    return js.tryCatch([&] {\n      auto metrics = observer.onJsonCompilationStart(js.v8Isolate, data.size());\n      return ns.setDefault(js, JsValue(js.parseJson(data).getHandle(js)));\n    }, [&](Value exception) {\n      js.v8Isolate->ThrowException(exception.getHandle(js));\n      return false;\n    });\n  };\n}\n\nModule::EvaluateCallback Module::newWasmModuleHandler(kj::ArrayPtr<const kj::byte> data) {\n  struct Cache final {\n    kj::MutexGuarded<kj::Maybe<v8::CompiledWasmModule>> mutex;\n  };\n  return [data, cache = kj::heap<Cache>()](Lock& js, const Url& id, const ModuleNamespace& ns,\n             const CompilationObserver& observer) mutable -> bool {\n    return js.tryCatch([&]() -> bool {\n      js.setAllowEval(true);\n      KJ_DEFER(js.setAllowEval(false));\n\n      // Allow Wasm compilation to spawn a background thread for tier-up, i.e. recompiling\n      // Wasm with optimizations in the background. Otherwise Wasm startup is way too slow.\n      // Until tier-up finishes, requests will be handled using Liftoff-generated code, which\n      // compiles fast but runs slower.\n      AllowV8BackgroundThreadsScope scope;\n\n      {\n        // See if we can use a cached compiled module to speed things up.\n        auto lock = cache->mutex.lockShared();\n        KJ_IF_SOME(compiled, *lock) {\n          auto metrics = observer.onWasmCompilationFromCacheStart(js.v8Isolate);\n          auto result =\n              JsValue(check(v8::WasmModuleObject::FromCompiledModule(js.v8Isolate, compiled)));\n          return ns.setDefault(js, result);\n        }\n      }\n\n      auto module = jsg::compileWasmModule(js, data, observer);\n      auto lock = cache->mutex.lockExclusive();\n      *lock = module->GetCompiledModule();\n      auto result = JsValue(module);\n      return ns.setDefault(js, result);\n    }, [&](Value exception) {\n      js.v8Isolate->ThrowException(exception.getHandle(js));\n      return false;\n    });\n  };\n}\n\nFunction<void()> Module::compileEvalFunction(Lock& js,\n    kj::StringPtr code,\n    kj::StringPtr name,\n    kj::Maybe<JsObject> compileExtensions,\n    const CompilationObserver& observer) {\n  auto metrics = observer.onScriptCompilationStart(js.v8Isolate, name);\n  v8::ScriptOrigin origin(js.str(name));\n  v8::ScriptCompiler::Source source(js.str(code), origin);\n  auto fn = ([&] {\n    KJ_IF_SOME(ext, compileExtensions) {\n      v8::Local<v8::Object> obj = ext;\n      return check(\n          v8::ScriptCompiler::CompileFunction(js.v8Context(), &source, 0, nullptr, 1, &obj));\n    } else {\n      return check(\n          v8::ScriptCompiler::CompileFunction(js.v8Context(), &source, 0, nullptr, 0, nullptr));\n    }\n  })();\n\n  return [ref = js.v8Ref(fn)](Lock& js) mutable {\n    js.withinHandleScope([&] {\n      // Any return value is explicitly ignored.\n      JsValue(check(ref.getHandle(js)->Call(js.v8Context(), js.v8Context()->Global(), 0, nullptr)));\n    });\n  };\n}\n\n}  // namespace workerd::jsg::modules\n"
  },
  {
    "path": "src/workerd/jsg/modules-new.h",
    "content": "// Copyright (c) 2017-2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/jsvalue.h>\n#include <workerd/jsg/modules.capnp.h>\n#include <workerd/jsg/modules.h>\n#include <workerd/jsg/observer.h>\n#include <workerd/jsg/setup.h>\n#include <workerd/jsg/url.h>\n#include <workerd/jsg/util.h>\n\n#include <v8.h>\n\n#include <capnp/schema-loader.h>\n#include <kj/common.h>\n#include <kj/function.h>\n#include <kj/map.h>\n#include <kj/refcount.h>\n#include <kj/table.h>\n\nnamespace workerd::jsg::modules {\n\n// Defines and implements workerd's (new) module loader subsystem.\n//\n// This new implementation of ModuleRegistry is designed to be more flexible,\n// modular, and extensible than the previous implementation. It is also designed\n// to support handling import specifiers as URLs, implement import.meta, properly\n// handle import attributes, sharing of modules across isolate replicas, and more.\n//\n// Every Worker has exactly one ModuleRegistry associated with composed of\n// of or more ModuleBundles (e.g. a ModuleBundle with modules from the worker\n// bundle, a ModuleBundle representing built-in modules, a ModuleBundle that is\n// a fallback service, etc). When multiple replicas of a Worker are created, they\n// may share the same ModuleRegistry instance, since multiple workers may be\n// acting across multiple threads, the ModuleRegistry instance must be thread-safe.\n//\n// The ModuleRegistry is the collection of individual Modules that can be\n// imported (i.e. `import * from '...'` and `await import('...')`) or required\n// (i.e. `require('...')`).\n//\n// The ModuleRegistry is conceptually immutable once created (individual\n// instances may support dynamic resolution using, for instance, a fallback\n// service but there is no API to manipulate the ModuleRegistry once it has\n// been created). There is internal state that may change, such as caching of\n// resolved modules, and compile cache data, but the set of modules that are\n// available within the ModuleRegistry does not change. The only exception to\n// this is the fallback service, which may dynamically resolved modules that\n// are not otherwise available in the ModuleRegistry, but that is only used\n// for local development in workerd and is never used in production.\n//\n// All access to the ModuleRegistry is thread-safe by way of (a) requiring callers\n// to hold and pass the jsg::Lock& when resolving modules and (b) having the\n// ModuleRegistry instance by AtomicRefcounted with MutexGuarded locks. With\n// exception to the fallback service, all operations within the ModuleRegistry\n// are either synchronous or expected to use JavaScript promises to perform\n// asynchronous operations.\n//\n// When a Worker is created, a ModuleBundle is created to contain Module\n// definitions declared by the worker source bundle configuration. One or more\n// are also provided that provides modules from within the runtime.\n//\n// When a v8::Isolate* is created for a particular worker, the ModuleRegistry\n// instance will be bound to the isolate in the form of a new IsolateModuleRegistry\n// instance. This is the interface that is actually used to resolve specifiers into\n// imported modules. The ModuleRegistry instance itself is owned by the Worker::Script\n// and the IsolateModuleRegistry instance can be viewed as a client.\n//\n// When there are multiple IsolateModuleRegistry instances pointing to the same\n// ModuleRegistry instance, only one can peform resolution at a time, controlled\n// by an exclusive lock on a MutexGuarded within the ModuleRegistry.\n//\n// A note on language use: Resolving a module vs. Loading a module. We use\n// the term \"resolving\" to refer to the overall process of taking a specifier\n// and turning it into a Module instance. This includes locating the module\n// within the registry, loading the module source code (if necessary), compiling\n// the module (if necessary), instantiating the module (i.e. creating the v8::Module)\n// instance, and evaluating the module (i.e. running the code). The part the\n// ModuleRegistry is responsible for is locating the module within the registry\n// and maintaining the cache of loaded modules along with additional metadata\n// like the compile cache. The actual compilation, instantiation, and evaluation\n// is performed by the IsolateModuleRegistry instance associated with the v8\n// isolate/context.\n//\n// The ModuleRegistry, ModuleBundle, and individual Module instances do not /\n// must not store any state that is specific to an isolate.\n//\n// Specifiers are always handled as URLs.\n//\n// Built-in modules are always identified using a prefixed-specifier that\n// can be parsed as an absolute URL. For instance, `node:buffer` is a fully\n// qualified, absolute URL whose protocol is `node:` and whose pathname is\n// `buffer`.\n//\n// Specifiers for modules that come from the worker bundle config are always\n// relative to `file:///bundle` (by default). Note that the `/bundle` part\n// will be configurable in the future and is shared with the virtual file\n// system used by the worker.\n//\n// For ESM modules, the specifier is accessible using import.meta.url.\n// All ESM modules support import.meta.resolve(...)\n//\n// If the first module in the module worker bundle is an ESM it will specify\n// import.meta.main = true.\n//\n// All modules are evaluated lazily when they are first imported. That is,\n// the ModuleRegistry will not actually generate the v8::Local<v8::Module>,\n// compile scripts, or evaluate it until the module is actually imported.\n// This means that any modules that are never actually imported by a worker\n// will never actually be compiled or evaluated. Keep in mind, that this does\n// not mean everything is dynamically imported. Static imports will still\n// be statically resolved and evaluated when the module that imports them\n// is resolved/evaluated. The key difference is that if a module is never\n// imported, it will never be resolved/compiled/evaluated.\n//\n// A ModuleBundle will have one of three basic types: Bundle, Builtin,\n// or Builtin-Only.\n//\n//  * A Bundle ModuleBundle provides access to modules that are defined in the\n//    worker bundle configuration. These may always be imported by the worker\n//    bundle scripts and may override modules that are defined in the runtime.\n//  * A Builtin ModuleBundle provides access to modules that are compiled into\n//    the runtime and are importable by bundle scripts. These may always be\n//    imported by the worker bundle scripts.\n//  * A Builtin-Only ModuleBundle provides access to built-in modules that can\n//    only be imported by other built-ins. These are invisible to bundle scripts.\n//\n// A special fourth type of ModuleBundle that is available only for local\n// dev in workerd is the Fallback type. This will use dynamic resolution\n// using a configurable fallback service. This will not be available in\n// production.\n//\n// ModuleBundle instances will also have a resolution priority. That is,\n// when a ModuleRegistry is using multiple ModuleBundle instances, the order\n// in which they are searched is significant. The search order will vary based\n// on resolve context.\n//\n// * If import is called from a bundle script, the search order is:\n//\n//   1. Bundle ModuleBundle\n//   2. Builtin ModuleBundle\n//   3. Fallback ModuleBundle (if available)\n//\n// * If import is called from a builtin script, the search order is:\n//\n//   1. Builtin ModuleBundle\n//   2. Builtin-Only ModuleBundle\n//\n// * If import is called from a builtin-only script, the search order is:\n//\n//   1. Builtin-Only ModuleBundle\n//\n// When the Fallback ModuleBundle is used, modules loaded from the fallback\n// are handled as if they are bundle scripts. The key difference, however, is\n// that the fallback service is not limited to a specific URL root.\n//\n// Notice that for built-ins, the Bundle ModuleBundle is not used. This\n// means it will not be possible to import worker bundle modules from a\n// built-in.\n//\n// The ModuleRegistry evaluates modules synchronously and all modules are\n// evaluated outside of the current IoContext (if any). This means the\n// evaluation of any module cannot perform any i/o and therefore is expected\n// to resolve synchronously. This allows for both ESM and CommonJS style\n// imports/requires. However, this also means that module bundles that do need\n// to be resolved, loaded, and evaluated asynchronously (like the fallback\n// service) must make appropriate arrangements to be able to do so.\n//\n// Metrics can be collected for module loading and resolution.\n//\n// Other details:\n//\n// * Module specifiers can be aliases for other specifiers, but only one\n//   level of aliasing is supported. That is, an alias cannot point to another\n//   alias. When a specifier resolves to an alias, the resolution starts over\n//   and the aliased module can be located in any ModuleBundle. This is really\n//   closer to a symbolic link or a redirect than a true alias but \"alias\" is\n//   the term we've used historically with the fallback service in the original\n//   implementation so we're sticking with it for now.\n// * Import attributes are not currently implemented but will be in a future\n//   iteration. For now, if any import attributes are specified an error will\n//   be thrown.\n// * ES modules all support the compile cache. When the ModuleRegistry is\n//   shared across multiple replicas of a Worker, the compile cache will speed\n//   up module compilation since the same compile cache can be used across all\n//   replicas.\n\n// The ResolveContext identifies the module that is being resolved along with\n// other key bits of information that may be used to resolve the module.\nstruct ResolveContext final {\n  using Source = ResolveObserver::Source;\n  using Type = ResolveObserver::Context;\n\n  // The type of module being resolved (one of BUNDLE, BUILTIN, or BUILTIN_ONLY)\n  Type type;\n\n  // The source of the module resolution (e.g. import, dynamic import, require, etc);\n  Source source;\n\n  // The fully resolved absolute import specifier URL for the module being resolved.\n  const Url& normalizedSpecifier;\n\n  // The normalized specifier of the module that is importing this module.\n  const Url& referrerNormalizedSpecifier;\n\n  // The raw specifier is the original specifier passed in, if any,\n  // before it was normalized into the specifier URL.\n  kj::Maybe<kj::StringPtr> rawSpecifier = kj::none;\n\n  // Per the standard, import attributes are considered to be part of the\n  // specifier key when the module is resolved and cached.\n  kj::HashMap<kj::StringPtr, kj::StringPtr> attributes;\n};\n\nclass ModuleRegistry;\n\n// The abstraction of a module within the ModuleRegistry.\n// Importantly, a Module is immutable once created and must be thread-safe.\n// The Module class itself represents the definition of a module and not\n// its actual instantiation.\n// The Module class is a virtual base class that is specialized for the\n// different types of modules being supported. There are essentially two\n// types of modules: ESM and Synthetic. ESM modules are the standard backed\n// by an ESM module script. Synthetic modules are any other type of module.\nclass Module {\n public:\n  // The types here echo the types in ResolveContext::Type but also include\n  // the FALLBACK, which is used to identify modules that are loaded from the\n  // fallback service.\n  enum class Type : uint8_t {\n    BUNDLE,\n    BUILTIN,\n    BUILTIN_ONLY,\n    FALLBACK,\n  };\n\n  // The flags are set internally and are used to identify various properties\n  // of the module.\n  enum class Flags : uint8_t {\n    NONE = 0,\n    // A Module with the MAIN flag set would specify import.meta.main = true.\n    // This is generally only suitable for worker-bundle entry point modules,\n    // but could in theory be applied to any module. Typically only one module\n    // in the ModuleRegistry should have this flag set. We do not verify/enforce\n    // that however.\n    MAIN = 1 << 0,\n    // A Module with the ESM flag set is interpreted as an ECMAScript module.\n    ESM = 1 << 1,\n    // A Module with the EVAL flag set is interpreted as a module that requires\n    // code evaluation to complete. This is generally used for synthetic modules\n    // that require JavaScript evaluation outside of the current request context.\n    // The eval callback must be set or the flag is ignored.\n    EVAL = 1 << 2,\n    // A Module with the WASM flag set is a WebAssembly module.\n    WASM = 1 << 3,\n  };\n\n  // The Evaluator is used to to ensure evaluation of a module outside of an\n  // IoContext, when necessary.\n  class Evaluator final {\n   public:\n    KJ_DISALLOW_COPY_AND_MOVE(Evaluator);\n    kj::Maybe<jsg::Promise<Value>> operator()(jsg::Lock& js,\n        const Module& module,\n        v8::Local<v8::Module> v8Module,\n        const CompilationObserver& observer) const;\n\n   private:\n    Evaluator(const ModuleRegistry& registry): registry(registry) {}\n    const ModuleRegistry& registry;\n    friend class ModuleRegistry;\n  };\n\n  KJ_DISALLOW_COPY_AND_MOVE(Module);\n  virtual ~Module() noexcept(false) = default;\n\n  // The fully resolved absolute import specifier URL for the module.\n  inline const Url& id() const KJ_LIFETIMEBOUND {\n    return id_;\n  }\n\n  // The module type.\n  inline Type type() const {\n    return type_;\n  }\n\n  // If isEsm() returns false the implication is that the module is a synthetic module\n  bool isEsm() const;\n\n  // If isMain() returns true, then import.meta.main will be true for this module\n  bool isMain() const;\n\n  // If isEval() returns true, then the module requires code evaluation to complete\n  // outside of a request context (that is, it cannot perform certain I/O tasks).\n  inline bool isEval() const;\n\n  // If isWasm() returns true, then the module is a WebAssembly module.\n  inline bool isWasm() const;\n\n  // Returns a v8::Module representing this Module definition for the given isolate.\n  // The return value follows the established v8 rules for Maybe. If the returned\n  // maybe is empty, then an exception should have been scheduled on the isolate\n  // via the lock. Do not throw C++ exceptions from this method unless they are fatal.\n  // The returned v8::Module is not yet instantiated.\n  virtual v8::MaybeLocal<v8::Module> getDescriptor(\n      Lock& js, const CompilationObserver& observer) const KJ_WARN_UNUSED_RESULT = 0;\n\n  // Determines if this module can be resolved in the given context.\n  virtual bool evaluateContext(const ResolveContext& context) const KJ_WARN_UNUSED_RESULT;\n\n  // Instantiates the given module. If false is returned, then an exception should\n  // have been scheduled on the isolate via the lock. Do not throw C++ exceptions\n  // from this method unless they are fatal.\n  bool instantiate(Lock& js,\n      v8::Local<v8::Module> module,\n      const CompilationObserver& observer) const KJ_WARN_UNUSED_RESULT;\n\n  // Evaluates the given module, returning the result of the evaluation in the form\n  // of a JS value. This is the value that is actually returned by the import or\n  // require. The return value follows the established v8 rules for Maybe. If the\n  // returned maybe is empty, then an exception should have been scheduled on the\n  // isolate via the lock. If the module has not yet been instantiated, it will\n  // be instantiated first. Do not throw C++ exceptions from this method unless they\n  // are fatal.\n  virtual v8::MaybeLocal<v8::Value> evaluate(Lock& js,\n      v8::Local<v8::Module> module,\n      const CompilationObserver& observer,\n      const Evaluator& maybeEvaluate) const KJ_WARN_UNUSED_RESULT = 0;\n\n  virtual v8::MaybeLocal<v8::Value> actuallyEvaluate(Lock& js,\n      v8::Local<v8::Module> module,\n      const CompilationObserver& observer) const KJ_WARN_UNUSED_RESULT = 0;\n\n  // A helper interface that is used to make it easier for a synthetic module\n  // evaluation callback to set the exports of the module.\n  class ModuleNamespace final {\n   public:\n    explicit ModuleNamespace(\n        v8::Local<v8::Module> inner, kj::ArrayPtr<const kj::String> namedExports);\n    KJ_DISALLOW_COPY_AND_MOVE(ModuleNamespace);\n\n    bool set(Lock& js, kj::StringPtr name, JsValue value) const;\n    bool setDefault(Lock& js, JsValue value) const;\n\n    // Returns the list of the named exports expected for the module\n    // (should not include the \"default\")\n    kj::ArrayPtr<const kj::StringPtr> getNamedExports() const;\n\n   private:\n    v8::Local<v8::Module> inner;\n    kj::HashSet<kj::StringPtr> namedExports;\n  };\n\n  // The EvaluateCallback is used to evaluate a synthetic module. The callback\n  // is called after the module is resolved and instantiated. Note that this\n  // is different from the Module::Evaluator, which is used to ensure that\n  // evaluation of a module occurs outside of an IoContext. This callback\n  // is always called to actually perform the evaluation of a synthetic module.\n  using EvaluateCallback =\n      Function<bool(const Url&, const ModuleNamespace&, const CompilationObserver&)>;\n\n  static kj::Own<Module> newSynthetic(Url id,\n      Type type,\n      EvaluateCallback callback,\n      kj::Array<kj::String> namedExports = nullptr,\n      Flags flags = Flags::NONE);\n\n  // Creates a new ESM module that takes ownership of the given code array.\n  // This is generally used to construct ESM modules from a worker bundle.\n  static kj::Own<Module> newEsm(\n      Url id, Type type, kj::Array<const char> code, Flags flags = Flags::NONE);\n\n  // Creates a new ESM module that does not take ownership of the given code\n  // array. This is used to construct ESM modules from compiled-in built-in\n  // modules.\n  // This variation of newEsm does not take Flags as none of the existing\n  // Flags are relevant other than the ESM flag which will be set automatically.\n  static kj::Own<Module> newEsm(Url id, Type type, kj::ArrayPtr<const char> code);\n\n  // The following methods are used to create the evaluation callbacks for various\n  // kinds of common simple synthetic module types. The module registry is not\n  // limited to just these kinds of modules, however. These are just the most\n  // common.\n\n  static EvaluateCallback newTextModuleHandler(kj::ArrayPtr<const char> data) KJ_WARN_UNUSED_RESULT;\n  static EvaluateCallback newDataModuleHandler(\n      kj::ArrayPtr<const kj::byte> data) KJ_WARN_UNUSED_RESULT;\n  static EvaluateCallback newJsonModuleHandler(kj::ArrayPtr<const char> data) KJ_WARN_UNUSED_RESULT;\n  static EvaluateCallback newWasmModuleHandler(\n      kj::ArrayPtr<const kj::byte> data) KJ_WARN_UNUSED_RESULT;\n\n  // An eval function is used for CommonJS style modules (including Node.js compat\n  // modules. The expectation is that this method will be called when the CommonJS\n  // style module is evaluated (e.g. within the EvaluationCallback).\n  static Function<void()> compileEvalFunction(Lock& js,\n      kj::StringPtr code,\n      kj::StringPtr name,\n      kj::Maybe<JsObject> compileExtensions,\n      const CompilationObserver& observer) KJ_WARN_UNUSED_RESULT;\n\n  // Some modules may need to protect against being evaluated recursively. The\n  // EvaluateOnce class makes it possible to guard against that, returning false\n  // if evaluation has already been started. Once setEvaluating() returns true,\n  // subsequent calls will always return false — this is single-use by design\n  // (CJS modules should only be evaluated once).\n  class EvaluateOnce final {\n   public:\n    EvaluateOnce() = default;\n    KJ_DISALLOW_COPY_AND_MOVE(EvaluateOnce);\n\n    // On the first call, this returns true. On subsequent calls, it returns false.\n    bool setEvaluating() {\n      if (evaluating) return false;\n      evaluating = true;\n      return true;\n    }\n\n   private:\n    bool evaluating = false;\n  };\n\n  // A CjsStyleModuleHandler is used for CommonJS style modules (including\n  // The template type T must be a jsg::Object that implements a getExports(Lock&)\n  // method returning a JsValue. This is set as the default export of the\n  // synthetic module. All methods and properties exposed by the template\n  // type T are exposed as additional globals within the executed scope.\n  template <typename T, typename TypeWrapper>\n  static EvaluateCallback newCjsStyleModuleHandler(\n      kj::StringPtr source, kj::StringPtr name) KJ_WARN_UNUSED_RESULT {\n    return [source, name, evaluateOnce = kj::heap<EvaluateOnce>()](Lock& js, const Url& id,\n               const Module::ModuleNamespace& ns,\n               const CompilationObserver& observer) mutable -> bool {\n      return js.tryCatch([&] {\n        // A CJS module can only be evaluated once. Return early if evaluation\n        // has already been started.\n        if (!evaluateOnce->setEvaluating()) {\n          return true;\n        }\n        auto& wrapper = TypeWrapper::from(js.v8Isolate);\n        auto ext = js.alloc<T>(js, id);\n        ns.setDefault(js, ext->getExports(js));\n        auto fn = Module::compileEvalFunction(js, source, name,\n            JsObject(wrapper.wrap(js, js.v8Context(), kj::none, ext.addRef())), observer);\n        fn(js);\n        // If there are named exports specified for the module namespace,\n        // then we want to examine the ext->getExports() to extract those.\n        JsValue exports = ext->getModuleExports(js);\n        KJ_IF_SOME(obj, exports.template tryCast<JsObject>()) {\n          for (auto& name: ns.getNamedExports()) {\n            ns.set(js, name, obj.get(js, name));\n          }\n        }\n        return ns.setDefault(js, exports);\n      }, [&](Value exception) {\n        js.v8Isolate->ThrowException(exception.getHandle(js));\n        return false;\n      });\n    };\n  }\n\n  // A ModuleHandler used to create a synthetic module that is backed by a jsg::Object.\n  template <typename T, typename TypeWrapper, typename Func>\n  static EvaluateCallback newJsgObjectModuleHandler(Func factory) KJ_WARN_UNUSED_RESULT {\n    return [factory = kj::mv(factory)](Lock& js, const Url& id, const Module::ModuleNamespace& ns,\n               const CompilationObserver& observer) mutable -> bool {\n      Ref<T> instance = factory(js);\n      auto value =\n          TypeWrapper::from(js.v8Isolate).wrap(js, js.v8Context(), kj::none, kj::mv(instance));\n      return ns.setDefault(js, JsValue(value));\n    };\n  }\n\n protected:\n  Module(Url id, Type type, Flags flags = Flags::NONE);\n\n private:\n  const Url id_;\n  Type type_;\n  Flags flags_;\n\n  // TODO: Support source objects as optional instantiation-hook creations and move\n  // Wasm compilation to start at instantiation-time instead of evaluation-time.\n  // kj::Maybe<HashableV8Ref<v8::Object>> sourceObject_;\n};\n\nconstexpr Module::Flags operator&(const Module::Flags& a, const Module::Flags& b) {\n  return static_cast<Module::Flags>(static_cast<uint8_t>(a) & static_cast<uint8_t>(b));\n}\nconstexpr Module::Flags operator|(const Module::Flags& a, const Module::Flags& b) {\n  return static_cast<Module::Flags>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));\n}\n\n// A ModuleBundle is a source of modules that can be imported or required.\n// A ModuleRegistry is a collection of ModuleBundles.\n// Importantly, a ModuleBundle is immutable once created with exception to\n// any internal caching it may use to optimize resolution. Accesses to the\n// bundle must be thread-safe.\nclass ModuleBundle {\n public:\n  using Type = Module::Type;\n\n  // A Builder is used to construct a ModuleBundle.\n  class Builder {\n   public:\n    KJ_DISALLOW_COPY_AND_MOVE(Builder);\n\n    // The resolve callback is used to perform resolution of a module context.\n    // If the callback returns a string, then resolution will start over with\n    // the new specifier. If the callback returns a Module, then that module\n    // will be used as the resolved module. If the callback returns kj::none,\n    // then the module is not resolved.\n    using ResolveCallback =\n        kj::Function<kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>>(const ResolveContext&)>;\n\n    Builder& add(const Url& id, ResolveCallback callback) KJ_LIFETIMEBOUND;\n\n    Builder& alias(const Url& alias, const Url& id) KJ_LIFETIMEBOUND;\n\n    kj::Own<ModuleBundle> finish() KJ_WARN_UNUSED_RESULT;\n\n    inline Type type() const {\n      return type_;\n    }\n\n   protected:\n    Builder(Type type);\n\n    void ensureIsNotBundleSpecifier(const Url& id);\n\n    Type type_;\n    kj::HashMap<Url, ResolveCallback> modules_;\n    kj::HashMap<Url, Url> aliases_;\n  };\n\n  // Used to build a ModuleBundle representing modules sourced from a worker bundle.\n  class BundleBuilder final: public Builder {\n   public:\n    BundleBuilder(const jsg::Url& bundleBase);\n    KJ_DISALLOW_COPY_AND_MOVE(BundleBuilder);\n\n    using EvaluateCallback = Module::EvaluateCallback;\n\n    BundleBuilder& addSyntheticModule(kj::StringPtr name,\n        EvaluateCallback callback,\n        kj::Array<kj::String> namedExports = nullptr) KJ_LIFETIMEBOUND;\n\n    BundleBuilder& addEsmModule(kj::StringPtr name,\n        kj::ArrayPtr<const char> code,\n        Module::Flags flags = Module::Flags::ESM) KJ_LIFETIMEBOUND;\n\n    // Overload that takes ownership of the source data. Use this when the\n    // source buffer may not outlive the module registry (e.g. transpiled\n    // TypeScript where the backing rust::String has shorter lifetime).\n    BundleBuilder& addEsmModule(kj::StringPtr name,\n        kj::Array<const char> code,\n        Module::Flags flags = Module::Flags::ESM) KJ_LIFETIMEBOUND;\n\n    BundleBuilder& addWasmModule(\n        kj::StringPtr name, kj::ArrayPtr<const kj::byte> data) KJ_LIFETIMEBOUND;\n\n    BundleBuilder& alias(kj::StringPtr alias, kj::StringPtr name) KJ_LIFETIMEBOUND;\n\n   private:\n    const jsg::Url& bundleBase;\n  };\n\n  // Used to build a ModuleBundle representing modules sources from the runtime.\n  class BuiltinBuilder final: public Builder {\n   public:\n    enum class Type {\n      BUILTIN,\n      BUILTIN_ONLY,\n    };\n    BuiltinBuilder(Type type = Type::BUILTIN);\n    KJ_DISALLOW_COPY_AND_MOVE(BuiltinBuilder);\n\n    BuiltinBuilder& addSynthetic(\n        const Url& id, BundleBuilder::EvaluateCallback callback) KJ_LIFETIMEBOUND;\n\n    BuiltinBuilder& addEsm(const Url& id, kj::ArrayPtr<const char> source) KJ_LIFETIMEBOUND;\n\n    // Adds a module that is implemented in C++ as a jsg::Object\n    template <typename T, typename TypeWrapper>\n    BuiltinBuilder& addObject(const Url& id) KJ_LIFETIMEBOUND {\n      ensureIsNotBundleSpecifier(id);\n      add(id,\n          [id = id.clone(), type = type()](const ResolveContext& context) mutable\n          -> kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>> {\n        if (context.normalizedSpecifier != id) return kj::none;\n        kj::Own<Module> mod = Module::newSynthetic(kj::mv(id), type,\n            [](Lock& js, const Url& id, const Module::ModuleNamespace& ns,\n                const CompilationObserver&) {\n          auto value = TypeWrapper::from(js.v8Isolate)\n                           .wrap(js, js.v8Context(), kj::none, js.alloc<T>(js, id));\n          ns.setDefault(js, JsValue(value));\n          return true;\n        });\n        return kj::Maybe<kj::OneOf<kj::String, kj::Own<Module>>>(kj::mv(mod));\n      });\n      return *this;\n    }\n  };\n\n  static kj::Own<ModuleBundle> newFallbackBundle(\n      Builder::ResolveCallback callback) KJ_WARN_UNUSED_RESULT;\n\n  static void getBuiltInBundleFromCapnp(BuiltinBuilder& builder, Bundle::Reader bundle);\n\n  // Overload that accepts a per-module filter predicate. Only modules for which\n  // the filter returns true are added to the builder. This is used for per-module\n  // feature flag gating (e.g., individual node:* modules behind compat flags).\n  static void getBuiltInBundleFromCapnp(BuiltinBuilder& builder,\n      Bundle::Reader bundle,\n      kj::Function<bool(::workerd::jsg::Module::Reader)> filter);\n\n  KJ_DISALLOW_COPY_AND_MOVE(ModuleBundle);\n\n  inline Type type() const {\n    return type_;\n  }\n\n  virtual ~ModuleBundle() noexcept(false) = default;\n\n  struct Resolved {\n    // This struct exists only to work around the limitation that a kj::OneOf\n    // cannot be used with a const reference or we get a compiler error.\n    kj::Maybe<const Module&> module;\n    kj::Maybe<kj::String> specifier;\n  };\n\n  // Load a module context. If a string is returned, then it must be a\n  // module specifier. The resolution will start over with the new specifier.\n  // If a Module is returned, then that is the loaded module. If kj::none\n  // is returned, then the module is not known by this module.\n  virtual kj::Maybe<Resolved> lookup(\n      const ResolveContext& context) KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT = 0;\n\n protected:\n  ModuleBundle(Type type);\n\n private:\n  Type type_;\n};\n\n// A ModuleRegistry is a collection of zero or more ModuleBundles.\n// Importantly, the ModuleRegistry is immutable once created and\n// must be thread-safe. In workerd, the module registry is created\n// and owned by a single Worker instance. In production, however, a\n// single ModuleRegistry instance may be shared by multiple replicas\n// of a Worker and therefore must be AtomicRefcounted.\nclass ModuleRegistry final: public kj::AtomicRefcounted, public ModuleRegistryBase {\n private:\n  enum BundleIndices { kBundle, kBuiltin, kBuiltinOnly, kFallback, kBundleCount };\n\n public:\n  // The EvalCallback is used to to ensure evaluation of a module outside of an\n  // IoContext, when necessary. If the EvalCallback is not set, then the\n  // Flag::EVAL on a module is ignored. If the EvalCallback is set, then any\n  // Modules that have the Flag::EVAL set will have their evaluation deferred\n  // to this callback.\n  using EvalCallback = Function<jsg::Promise<Value>(\n      const Module& module, v8::Local<v8::Module> v8Module, const CompilationObserver& observer)>;\n\n  class Builder final {\n   public:\n    enum class Options {\n      NONE = 0,\n      // When set, allows the ModuleRegistry to use a fallback ModuleBundle to\n      // dynamically resolve a module that cannot be resolved by any other\n      // registered bundles. The fallback service is only used when using the\n      // ResolveContext::Type::BUNDLE context and is always the last bundle\n      // checked. The fallback service should only be used for local dev.\n      ALLOW_FALLBACK = 1 << 0,\n    };\n    Builder(const ResolveObserver& observer,\n        const jsg::Url& bundleBase,\n        Options options = Options::NONE);\n    KJ_DISALLOW_COPY_AND_MOVE(Builder);\n\n    Builder& add(kj::Own<ModuleBundle> bundle) KJ_LIFETIMEBOUND;\n\n    kj::Arc<ModuleRegistry> finish() KJ_WARN_UNUSED_RESULT;\n\n    Builder& setEvalCallback(EvalCallback callback) KJ_LIFETIMEBOUND;\n\n    capnp::SchemaLoader& getSchemaLoader() {\n      return *schemaLoader;\n    }\n\n   private:\n    bool allowsFallback() const;\n\n    // One slot for each of ModuleBundle::Type\n    const ResolveObserver& observer;\n    const jsg::Url& bundleBase;\n    const Options options;\n    kj::FixedArray<kj::Vector<kj::Own<ModuleBundle>>, ModuleRegistry::kBundleCount> bundles_;\n    kj::Maybe<EvalCallback> maybeEvalCallback = kj::none;\n    kj::Own<capnp::SchemaLoader> schemaLoader;\n    friend class ModuleRegistry;\n  };\n\n  kj::Maybe<const Module&> lookup(\n      const ResolveContext& context) const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;\n\n  // Attaches the ModuleRegistry to the given isolate by creating an IsolateModuleRegistry\n  // and linking that to the isolate.\n  kj::Own<void> attachToIsolate(Lock& js, const CompilationObserver& observer) const override;\n\n  // Synchronously resolve the specified module from the registry bound to the given lock.\n  // This will throw a JsExceptionThrown exception if the module cannot be found or an\n  // error occurs while the module is being evaluated. Modules resolved with this method\n  // must be capable of fully evaluating within one drain of the microtask queue.\n  static JsValue resolve(Lock& js,\n      kj::StringPtr specifier,\n      kj::StringPtr exportName = \"default\"_kjc,\n      ResolveContext::Type type = ResolveContext::Type::BUNDLE,\n      ResolveContext::Source source = ResolveContext::Source::INTERNAL,\n      kj::Maybe<const Url&> maybeReferrer = kj::none);\n\n  // Synchronously resolve the specified module from the registry bound to the given lock.\n  // This variant will return kj::none if the module cannot be found but will throw a\n  // JsExceptionThrown exception if an error occurs while the module is being evaluated.\n  // Modules resolved with this method must be capable of fully evaluating within one\n  // drain of the microtask queue.\n  static kj::Maybe<JsObject> tryResolveModuleNamespace(Lock& js,\n      kj::StringPtr specifier,\n      ResolveContext::Type type = ResolveContext::Type::BUNDLE,\n      ResolveContext::Source source = ResolveContext::Source::INTERNAL,\n      kj::Maybe<const Url&> maybeReferrer = kj::none);\n\n  // The constructor is public because kj::heap requires is to be. Do not\n  // use the constructor directly. Use the ModuleRegistry::Builder\n  ModuleRegistry(ModuleRegistry::Builder* builder);\n  KJ_DISALLOW_COPY_AND_MOVE(ModuleRegistry);\n\n  const jsg::Url& getBundleBase() const {\n    return bundleBase;\n  }\n\n  const capnp::SchemaLoader& getSchemaLoader() const override {\n    return *schemaLoader;\n  }\n\n  const Module::Evaluator getEvaluator() const {\n    return Module::Evaluator(*this);\n  }\n\n private:\n  struct Impl {\n    // One slot for each of ModuleBundle::Type, within each slot is\n    // an array of bundles of that type in registration order.\n    kj::FixedArray<kj::Array<kj::Own<ModuleBundle>>, kBundleCount> bundles;\n    Impl(kj::ArrayPtr<kj::Vector<kj::Own<ModuleBundle>>> bundles);\n  };\n\n  const ResolveObserver& observer;\n  const jsg::Url& bundleBase;\n  kj::MutexGuarded<Impl> impl;\n  // Marked mutable because kj::Function::operator() is non-const, but the eval\n  // callback is conceptually const — it is only ever invoked while holding the\n  // isolate lock, so concurrent mutation is not a concern.\n  mutable kj::Maybe<EvalCallback> maybeEvalCallback = kj::none;\n  kj::Own<capnp::SchemaLoader> schemaLoader;\n\n  struct ModuleRef {\n    const Module& module;\n  };\n  using ModuleOrRedirect = kj::OneOf<ModuleRef, Url>;\n\n  kj::Maybe<const Module&> lookupImpl(Impl& impl,\n      const ResolveContext& context,\n      bool recursed) const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;\n\n  kj::Maybe<ModuleOrRedirect> tryFindInBundleGroup(const ResolveContext& context,\n      kj::ArrayPtr<kj::Own<ModuleBundle>> bundles) const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;\n\n  // Attempts to find the module in the given bundle. If found, returns\n  // const Module&, if an alias is found, returns the new ResolveContext\n  // to try again with. If not found, returns kj::none.\n  static kj::Maybe<ModuleOrRedirect> tryFindInBundle(const ResolveContext& context,\n      ModuleBundle& bundle,\n      const Url& bundleBase) KJ_WARN_UNUSED_RESULT;\n\n  kj::Maybe<jsg::Promise<Value>> evaluateImpl(jsg::Lock& js,\n      const Module& module,\n      v8::Local<v8::Module> v8Module,\n      const CompilationObserver& observer) const;\n\n  friend class Module::Evaluator;\n};\n\nconstexpr ModuleRegistry::Builder::Options operator|(\n    const ModuleRegistry::Builder::Options& a, const ModuleRegistry::Builder::Options& b) {\n  return static_cast<ModuleRegistry::Builder::Options>(\n      static_cast<uint8_t>(a) | static_cast<uint8_t>(b));\n}\nconstexpr ModuleRegistry::Builder::Options operator&(\n    const ModuleRegistry::Builder::Options& a, const ModuleRegistry::Builder::Options& b) {\n  return static_cast<ModuleRegistry::Builder::Options>(\n      static_cast<uint8_t>(a) & static_cast<uint8_t>(b));\n}\n\n}  // namespace workerd::jsg::modules\n"
  },
  {
    "path": "src/workerd/jsg/modules.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"jsg.h\"\n#include \"setup.h\"\n#include \"util.h\"\n\n#include <v8-wasm.h>\n\n#include <kj/mutex.h>\n\nnamespace workerd::jsg {\n\nnamespace {\n\n// Generalized module resolution callback that handles both evaluation and source phase imports\ntemplate <bool IsSourcePhase>\nv8::MaybeLocal<std::conditional_t<IsSourcePhase, v8::Object, v8::Module>> resolveModuleCallback(\n    v8::Local<v8::Context> context,\n    v8::Local<v8::String> specifier,\n    v8::Local<v8::FixedArray> import_attributes,\n    v8::Local<v8::Module> referrer) {\n  using ReturnType = std::conditional_t<IsSourcePhase, v8::Object, v8::Module>;\n\n  auto& js = Lock::current();\n  v8::MaybeLocal<ReturnType> result;\n\n  // The specification for import attributes strongly recommends that embedders\n  // reject import attributes and types they do not understand/implement. This\n  // is because import attributes can alter the interpretation of a module and\n  // are considered to be part of the unique key for caching a module.\n  // Throwing an error for things we do not understand is the safest thing to do.\n  // However, historically we have not followed this guideline in the spec\n  // and unfortunately there are applications deployed that will break if we\n  // started enforcing that guideline without a compat flag.\n  if (!import_attributes.IsEmpty() && import_attributes->Length() > 0) {\n    JSG_REQUIRE(!js.getThrowOnUnrecognizedImportAssertion(), Error,\n        \"Unrecognized import attributes specified\");\n  }\n\n  js.tryCatch([&] {\n    auto registry = getModulesForResolveCallback(js.v8Isolate);\n    KJ_REQUIRE(registry != nullptr, \"didn't expect resolveCallback() now\");\n\n    auto ref = KJ_ASSERT_NONNULL(registry->resolve(js, referrer),\n        \"referrer passed to resolveCallback isn't in modules table\");\n\n    auto spec = kj::str(specifier);\n\n    if (isNodeJsCompatEnabled(js)) {\n      KJ_IF_SOME(nodeSpec, checkNodeSpecifier(spec)) {\n        spec = kj::mv(nodeSpec);\n      }\n    }\n\n    // Handle process module redirection based on enable_nodejs_process_v2 flag\n    if constexpr (!IsSourcePhase) {\n      if (spec == \"node:process\") {\n        auto specifierPath =\n            kj::Path::parse(isNodeJsProcessV2Enabled(js) ? \"node-internal:public_process\"_kj\n                                                         : \"node-internal:legacy_process\"_kj);\n        KJ_IF_SOME(info,\n            registry->resolve(\n                js, specifierPath, kj::none, ModuleRegistry::ResolveOption::INTERNAL_ONLY)) {\n          result = info.module.getHandle(js.v8Isolate);\n          return;\n        }\n      }\n    }\n\n    // If the referrer module is a built-in, it is only permitted to resolve\n    // internal modules. If the worker bundle provided an override for a builtin,\n    // then internalOnly will be false.\n    bool internalOnly =\n        ref.type == ModuleRegistry::Type::BUILTIN || ref.type == ModuleRegistry::Type::INTERNAL;\n\n    kj::Path targetPath = ([&] {\n      // If the specifier begins with one of our known prefixes, let's not resolve\n      // it against the referrer.\n      if (internalOnly || spec.startsWith(\"node:\") || spec.startsWith(\"cloudflare:\") ||\n          spec.startsWith(\"workerd:\")) {\n        return kj::Path::parse(spec);\n      }\n      return ref.specifier.parent().eval(spec);\n    })();\n\n    KJ_IF_SOME(resolved,\n        registry->resolve(js, targetPath, ref.specifier,\n            internalOnly ? ModuleRegistry::ResolveOption::INTERNAL_ONLY\n                         : ModuleRegistry::ResolveOption::DEFAULT,\n            ModuleRegistry::ResolveMethod::IMPORT, spec.asPtr())) {\n      if constexpr (!IsSourcePhase) {\n        result = resolved.module.getHandle(js);\n      } else {\n        KJ_IF_SOME(sourceObject, resolved.getModuleSourceObject(js)) {\n          result = sourceObject;\n        } else {\n          js.throwException(js.v8Ref(v8::Exception::SyntaxError(js.strIntern(\n              kj::str(\"Source phase import not available for module: \", targetPath.toString(),\n                  \".\\n  imported from \\\"\", ref.specifier.toString(), \"\\\"\")))));\n          return;\n        }\n      }\n    } else {\n      // This is a bit annoying. If the module was not found, then\n      // we need to check to see if it is a prefixed specifier. If it is,\n      // we'll try again with only the specifier and not the ref.specifier\n      // as parent. We have to do it this way just in case the worker bundle\n      // is using the prefix itself. (which isn't likely but is possible).\n      // We only need to do this if internalOnly is false.\n      if (!internalOnly && (spec.startsWith(\"node:\") || spec.startsWith(\"cloudflare:\"))) {\n        KJ_IF_SOME(resolve,\n            registry->resolve(js, kj::Path::parse(spec), ref.specifier,\n                ModuleRegistry::ResolveOption::DEFAULT, ModuleRegistry::ResolveMethod::IMPORT,\n                spec.asPtr())) {\n          if constexpr (!IsSourcePhase) {\n            result = resolve.module.getHandle(js);\n          } else {\n            js.throwException(js.v8Ref(v8::Exception::SyntaxError(js.strIntern(\n                kj::str(\"Source phase import not available for module: \", targetPath.toString(),\n                    \".\\n  imported from \\\"\", ref.specifier.toString(), \"\\\"\")))));\n            return;\n          }\n          return;\n        }\n      }\n      JSG_FAIL_REQUIRE(Error, \"No such module \\\"\", targetPath.toString(), \"\\\".\\n  imported from \\\"\",\n          ref.specifier.toString(), \"\\\"\");\n    }\n  }, [&](Value value) {\n    // We do not call js.throwException here since that will throw a JsExceptionThrown,\n    // which we do not want here. Instead, we'll schedule an exception on the isolate\n    // directly and set the result to an empty v8::MaybeLocal.\n    js.v8Isolate->ThrowException(value.getHandle(js));\n    result = v8::MaybeLocal<ReturnType>();\n  });\n\n  return result;\n}\n\n// Implementation of `v8::Module::SyntheticModuleEvaluationSteps`, which is called to initialize\n// the exports on a synthetic module. Obnoxiously, you can only initialize the exports in this\n// callback; V8 will crash if you try to call `SetSyntheticModuleExport()` from anywhere else.\nv8::MaybeLocal<v8::Value> evaluateSyntheticModuleCallback(\n    v8::Local<v8::Context> context, v8::Local<v8::Module> module) {\n  auto& js = Lock::current();\n  v8::EscapableHandleScope scope(js.v8Isolate);\n  v8::MaybeLocal<v8::Value> result;\n\n  KJ_IF_SOME(exception, kj::runCatchingExceptions([&]() {\n    auto registry = getModulesForResolveCallback(js.v8Isolate);\n    auto ref = KJ_ASSERT_NONNULL(registry->resolve(js, module),\n        \"module passed to evaluateSyntheticModuleCallback isn't in modules table\");\n\n    // V8 doc comments say this callback must always return an already-resolved promise... I don't\n    // know what the point of that is but I guess we'd better do what it says.\n    const auto makeResolvedPromise = [&]() {\n      v8::Local<v8::Promise::Resolver> resolver;\n      if (!v8::Promise::Resolver::New(context).ToLocal(&resolver)) {\n        // Return empty local and allow error to propagate.\n        return v8::Local<v8::Promise>();\n      }\n      if (!resolver->Resolve(context, js.v8Undefined()).IsJust()) {\n        // Return empty local and allow error to propagate.\n        return v8::Local<v8::Promise>();\n      }\n      return resolver->GetPromise();\n    };\n\n    auto& synthetic = KJ_REQUIRE_NONNULL(ref.module.maybeSynthetic, \"Not a synthetic module.\");\n    auto defaultStr = js.strIntern(\"default\"_kj);\n\n    KJ_SWITCH_ONEOF(synthetic) {\n      KJ_CASE_ONEOF(info, ModuleRegistry::CapnpModuleInfo) {\n        bool success = true;\n        success = success &&\n            module->SetSyntheticModuleExport(js.v8Isolate, defaultStr, info.fileScope.getHandle(js))\n                .IsJust();\n        for (auto& decl: info.topLevelDecls) {\n          success = success &&\n              module\n                  ->SetSyntheticModuleExport(\n                      js.v8Isolate, v8StrIntern(js.v8Isolate, decl.key), decl.value.getHandle(js))\n                  .IsJust();\n        }\n\n        if (success) {\n          result = makeResolvedPromise();\n        } else {\n          // leave `result` empty to propagate the JS exception\n        }\n      }\n      KJ_CASE_ONEOF(info, ModuleRegistry::CommonJsModuleInfo) {\n        v8::TryCatch catcher(js.v8Isolate);\n        // const_cast is safe here because we're protected by the isolate js.\n        auto& commonjs = const_cast<ModuleRegistry::CommonJsModuleInfo&>(info);\n        try {\n          commonjs.evalFunc(js);\n          auto exports = commonjs.getExports(js);\n          if (module->SetSyntheticModuleExport(js.v8Isolate, defaultStr, exports).IsJust()) {\n            KJ_IF_SOME(obj, exports.tryCast<JsObject>()) {\n              KJ_IF_SOME(exports, ref.module.maybeNamedExports) {\n                for (auto& name: exports) {\n                  // Ignore default... just in case someone was silly enough to include it.\n                  if (name == \"default\"_kj) continue;\n                  auto val = obj.get(js, name);\n                  if (!module->SetSyntheticModuleExport(js.v8Isolate, js.strIntern(name), val)\n                           .IsJust()) {\n                    break;\n                  }\n                }\n              }\n            }\n            result = makeResolvedPromise();\n          }\n        } catch (const JsExceptionThrown&) {\n          if (catcher.CanContinue()) catcher.ReThrow();\n          // leave `result` empty to propagate the JS exception\n        }\n      }\n      KJ_CASE_ONEOF(info, ModuleRegistry::TextModuleInfo) {\n        if (module->SetSyntheticModuleExport(js.v8Isolate, defaultStr, info.value.getHandle(js))\n                .IsJust()) {\n          result = makeResolvedPromise();\n        } else {\n          // leave 'result' empty to propagate the JS exception\n        }\n      }\n      KJ_CASE_ONEOF(info, ModuleRegistry::DataModuleInfo) {\n        if (module->SetSyntheticModuleExport(js.v8Isolate, defaultStr, info.value.getHandle(js))\n                .IsJust()) {\n          result = makeResolvedPromise();\n        } else {\n          // leave 'result' empty to propagate the JS exception\n        }\n      }\n      KJ_CASE_ONEOF(info, ModuleRegistry::WasmModuleInfo) {\n        if (module->SetSyntheticModuleExport(js.v8Isolate, defaultStr, info.value.getHandle(js))\n                .IsJust()) {\n          result = makeResolvedPromise();\n        } else {\n          // leave 'result' empty to propagate the JS exception\n        }\n      }\n      KJ_CASE_ONEOF(info, ModuleRegistry::JsonModuleInfo) {\n        if (module->SetSyntheticModuleExport(js.v8Isolate, defaultStr, info.value.getHandle(js))\n                .IsJust()) {\n          result = makeResolvedPromise();\n        } else {\n          // leave 'result' empty to propagate the JS exception\n        }\n      }\n      KJ_CASE_ONEOF(info, ModuleRegistry::ObjectModuleInfo) {\n        if (module->SetSyntheticModuleExport(js.v8Isolate, defaultStr, info.value.getHandle(js))\n                .IsJust()) {\n          result = makeResolvedPromise();\n        } else {\n          // leave 'result' empty to propagate the JS exception\n        }\n      }\n    }\n  })) {\n    // V8 doc comments say in the case of an error, throw the error and return an empty Maybe.\n    // I.e. NOT a rejected promise. OK...\n    js.v8Isolate->ThrowException(js.exceptionToJsValue(kj::mv(exception)).getHandle(js));\n    result = v8::Local<v8::Promise>();\n  }\n\n  return scope.EscapeMaybe(result);\n}\n\n}  // namespace\n\nModuleRegistry* getModulesForResolveCallback(v8::Isolate* isolate) {\n  return &KJ_ASSERT_NONNULL(jsg::getAlignedPointerFromEmbedderData<ModuleRegistry>(\n      isolate->GetCurrentContext(), jsg::ContextPointerSlot::MODULE_REGISTRY));\n}\n\nvoid instantiateModule(\n    jsg::Lock& js, v8::Local<v8::Module>& module, InstantiateModuleOptions options) {\n  KJ_ASSERT(!module.IsEmpty());\n  auto isolate = js.v8Isolate;\n  auto context = js.v8Context();\n\n  auto status = module->GetStatus();\n\n  // If the previous instantiation failed, throw the exception.\n  if (status == v8::Module::Status::kErrored) {\n    isolate->ThrowException(module->GetException());\n    throw jsg::JsExceptionThrown();\n  }\n\n  // Nothing to do if the module is already evaluated.\n  if (status == v8::Module::Status::kEvaluated || status == v8::Module::Status::kEvaluating) return;\n\n  if (status == v8::Module::Status::kUninstantiated) {\n    jsg::check(module->InstantiateModule(\n        context, resolveModuleCallback<false>, resolveModuleCallback<true>));\n  }\n\n  auto prom = jsg::check(module->Evaluate(context)).As<v8::Promise>();\n\n  if (module->IsGraphAsync() && prom->State() == v8::Promise::kPending) {\n    // If top level await has been disable, error.\n    JSG_REQUIRE(options != InstantiateModuleOptions::NO_TOP_LEVEL_AWAIT, Error,\n        \"Top-level await in module is not permitted at this time.\");\n  }\n  // We run microtasks to ensure that any promises that happen to be scheduled\n  // during the evaluation of the top level scope have a chance to be settled,\n  // even if those are not directly awaited.\n  js.runMicrotasks();\n\n  switch (prom->State()) {\n    case v8::Promise::kPending:\n      // Let's make sure nobody is depending on modules awaiting on pending promises.\n      JSG_FAIL_REQUIRE(Error, \"Top-level await in module is unsettled.\");\n    case v8::Promise::kRejected:\n      // Since we don't actually support I/O when instantiating a worker, we don't return the\n      // promise from module->Evaluate, which means we lose any errors that happen during\n      // instantiation if we don't throw the rejection exception here.\n      isolate->ThrowException(module->GetException());\n      throw jsg::JsExceptionThrown();\n    case v8::Promise::kFulfilled:\n      break;\n  }\n}\n\n// ===================================================================================\n\nnamespace {\n\nstatic CompilationObserver::Option convertOption(ModuleInfoCompileOption option) {\n  switch (option) {\n    case ModuleInfoCompileOption::BUILTIN:\n      return CompilationObserver::Option::BUILTIN;\n    case ModuleInfoCompileOption::BUNDLE:\n      return CompilationObserver::Option::BUNDLE;\n  }\n  KJ_UNREACHABLE;\n}\n\nv8::Local<v8::Module> compileEsmModule(jsg::Lock& js,\n    kj::StringPtr name,\n    kj::ArrayPtr<const char> content,\n    kj::ArrayPtr<const kj::byte> compileCache,\n    ModuleInfoCompileOption option,\n    const CompilationObserver& observer) {\n  // destroy the observer after compilation finished to indicate the end of the process.\n  auto compilationObserver =\n      observer.onEsmCompilationStart(js.v8Isolate, name, convertOption(option));\n\n  // Must pass true for `is_module`, but we can skip everything else.\n  constexpr int resourceLineOffset = 0;\n  constexpr int resourceColumnOffset = 0;\n  constexpr bool resourceIsSharedCrossOrigin = false;\n  constexpr int scriptId = -1;\n  constexpr bool resourceIsOpaque = false;\n  constexpr bool isWasm = false;\n  constexpr bool isModule = true;\n  v8::ScriptOrigin origin(v8StrIntern(js.v8Isolate, name), resourceLineOffset, resourceColumnOffset,\n      resourceIsSharedCrossOrigin, scriptId, {}, resourceIsOpaque, isWasm, isModule);\n  v8::Local<v8::String> contentStr;\n\n  if (option == ModuleInfoCompileOption::BUILTIN) {\n    // TODO(later): Use of newExternalOneByteString here limits our built-in source\n    // modules (for which this path is used) to only the latin1 character set. We\n    // may need to revisit that to import built-ins as UTF-16 (two-byte).\n    contentStr = jsg::newExternalOneByteString(js, content);\n  } else {\n    contentStr = jsg::v8Str(js.v8Isolate, content);\n  }\n\n  if (compileCache.size() > 0 && compileCache.begin() != nullptr) {\n    auto cached =\n        std::make_unique<v8::ScriptCompiler::CachedData>(compileCache.begin(), compileCache.size());\n    v8::ScriptCompiler::Source source(contentStr, origin, cached.release());\n    return jsg::check(v8::ScriptCompiler::CompileModule(\n        js.v8Isolate, &source, v8::ScriptCompiler::kConsumeCodeCache));\n  }\n\n  v8::ScriptCompiler::Source source(contentStr, origin);\n  return jsg::check(v8::ScriptCompiler::CompileModule(js.v8Isolate, &source));\n}\n\nv8::Local<v8::Module> createSyntheticModule(\n    jsg::Lock& js, kj::StringPtr name, kj::Maybe<kj::ArrayPtr<const kj::StringPtr>> maybeExports) {\n  v8::LocalVector<v8::String> exportNames(js.v8Isolate);\n  exportNames.push_back(v8StrIntern(js.v8Isolate, \"default\"_kj));\n  KJ_IF_SOME(exports, maybeExports) {\n    exportNames.reserve(exports.size());\n    for (auto& name: exports) {\n      exportNames.push_back(v8StrIntern(js.v8Isolate, name));\n    }\n  }\n  return v8::Module::CreateSyntheticModule(js.v8Isolate, v8StrIntern(js.v8Isolate, name),\n      v8::MemorySpan<const v8::Local<v8::String>>(exportNames.data(), exportNames.size()),\n      &evaluateSyntheticModuleCallback);\n}\n}  // namespace\n\nModuleRegistry::ModuleInfo::ModuleInfo(\n    jsg::Lock& js, v8::Local<v8::Module> module, kj::Maybe<SyntheticModuleInfo> maybeSynthetic)\n    : module(js.v8Isolate, module),\n      maybeSynthetic(kj::mv(maybeSynthetic)) {}\n\nModuleRegistry::ModuleInfo::ModuleInfo(jsg::Lock& js,\n    kj::StringPtr name,\n    kj::ArrayPtr<const char> content,\n    kj::ArrayPtr<const kj::byte> compileCache,\n    ModuleInfoCompileOption flags,\n    const CompilationObserver& observer)\n    : ModuleInfo(js, compileEsmModule(js, name, content, compileCache, flags, observer)) {}\n\nModuleRegistry::ModuleInfo::ModuleInfo(jsg::Lock& js,\n    kj::StringPtr name,\n    kj::Maybe<kj::ArrayPtr<const kj::StringPtr>> maybeExports,\n    SyntheticModuleInfo synthetic)\n    : ModuleInfo(js, createSyntheticModule(js, name, maybeExports), kj::mv(synthetic)) {\n  KJ_IF_SOME(exports, maybeExports) {\n    maybeNamedExports = KJ_MAP(name, exports) { return kj::str(name); };\n  }\n}\n\njsg::JsValue ModuleRegistry::CommonJsModuleInfo::getExports(jsg::Lock& js) {\n  return provider->getExports(js);\n}\n\nModuleRegistry::CapnpModuleInfo::CapnpModuleInfo(\n    Value fileScope, kj::HashMap<kj::StringPtr, jsg::Value> topLevelDecls)\n    : fileScope(kj::mv(fileScope)),\n      topLevelDecls(kj::mv(topLevelDecls)) {}\n\nv8::Local<v8::WasmModuleObject> compileWasmModule(\n    jsg::Lock& js, kj::ArrayPtr<const uint8_t> code, const CompilationObserver& observer) {\n  // destroy the observer after compilation finishes to indicate the end of the process.\n  auto compilationObserver = observer.onWasmCompilationStart(js.v8Isolate, code.size());\n\n  return jsg::check(v8::WasmModuleObject::Compile(\n      js.v8Isolate, v8::MemorySpan<const uint8_t>(code.begin(), code.size())));\n}\n\n// ======================================================================================\n\nkj::Maybe<kj::OneOf<kj::String, ModuleRegistry::ModuleInfo>> tryResolveFromFallbackService(Lock& js,\n    const kj::Path& specifier,\n    kj::Maybe<const kj::Path&>& referrer,\n    CompilationObserver& observer,\n    ModuleRegistry::ResolveMethod method,\n    kj::Maybe<kj::StringPtr> rawSpecifier) {\n  auto& isolateBase = IsolateBase::from(js.v8Isolate);\n  KJ_IF_SOME(fallback, isolateBase.tryGetModuleFallback()) {\n    kj::Maybe<kj::String> maybeRef;\n    KJ_IF_SOME(ref, referrer) {\n      maybeRef = ref.toString(true);\n    }\n    return fallback(js, specifier.toString(true), kj::mv(maybeRef), observer, method, rawSpecifier);\n  }\n  return kj::none;\n}\n\nJsValue ModuleRegistry::requireImpl(Lock& js, ModuleInfo& info, RequireImplOptions options) {\n  auto module = info.module.getHandle(js);\n\n  // If the module status is evaluating or instantiating then the module is likely\n  // has a circular dependency on itself. If the module is a CommonJS or NodeJS\n  // module, we can return the exports object directly here.\n  if (module->GetStatus() == v8::Module::Status::kEvaluating ||\n      module->GetStatus() == v8::Module::Status::kInstantiating) {\n    KJ_IF_SOME(synth, info.maybeSynthetic) {\n      KJ_IF_SOME(cjs, synth.tryGet<ModuleRegistry::CommonJsModuleInfo>()) {\n        return cjs.getExports(js);\n      }\n    }\n  }\n\n  // When using require(...) we previously allowed the required modules to use\n  // top-level await. With a compat flag we disable use of top-level await but\n  // ONLY when the module is synchronously required. The same module being imported\n  // either statically or dynamically can still use TLA. This aligns with behavior\n  // being implemented in other JS runtimes.\n  auto& isolateBase = IsolateBase::from(js.v8Isolate);\n  jsg::InstantiateModuleOptions opts = jsg::InstantiateModuleOptions::DEFAULT;\n  if (!isolateBase.isTopLevelAwaitEnabled()) {\n    opts = jsg::InstantiateModuleOptions::NO_TOP_LEVEL_AWAIT;\n\n    // If the module was already evaluated, let's check if it is async.\n    // If it is, we will throw an error. This case can happen if a previous\n    // attempt to require the module failed because the module was async.\n    if (module->GetStatus() == v8::Module::kEvaluated) {\n      JSG_REQUIRE(!module->IsGraphAsync(), Error,\n          \"Top-level await in module is not permitted at this time.\");\n    }\n  }\n\n  jsg::instantiateModule(js, module, opts);\n\n  if (info.maybeSynthetic == kj::none) {\n    // If the module is an ESM and the __cjsUnwrapDefault flag is set to true, we will\n    // always return the default export regardless of the options.\n    // Otherwise fallback to the options. This is an early version of the \"module.exports\"\n    // convention that Node.js finally adopted for require(esm) that was not officially\n    // adopted but there are a handful of modules in the ecosystem that supported it\n    // early. It's trivial for us to support here so let's just do so.\n    JsObject obj(module->GetModuleNamespace().As<v8::Object>());\n    if (obj.get(js, \"__cjsUnwrapDefault\"_kj) == js.boolean(true)) {\n      return obj.get(js, \"default\"_kj);\n    }\n    // If the ES Module namespace exports a \"module.exports\" key then that will be the\n    // export that is returned by the require(...) call per Node.js' recently added\n    // require(esm) support.\n    // See: https://nodejs.org/docs/latest/api/modules.html#loading-ecmascript-modules-using-require\n    if (obj.has(js, \"module.exports\"_kj)) {\n      // We only want to return the value if it is explicitly specified, otherwise we'll\n      // always be returning undefined.\n      return obj.get(js, \"module.exports\"_kj);\n    }\n  }\n\n  // Originally, require returned an object like `{default: module.exports}` when we really\n  // intended to return the module exports raw. We should be extracting `default` here.\n  // When Node.js recently finally adopted require(esm), they adopted the default behavior\n  // of exporting the module namespace, which is fun. We'll stick with our default here for\n  // now but users can get Node.js-like behavior by switching off the\n  // exportCommonJsDefaultNamespace compat flag.\n  if (options == RequireImplOptions::EXPORT_DEFAULT) {\n    return JsValue(check(module->GetModuleNamespace().As<v8::Object>()->Get(\n        js.v8Context(), v8StrIntern(js.v8Isolate, \"default\"))));\n  }\n\n  // When the flag is disabled, return the original module namespace\n  // to maintain backward compatibility (same object as ESM import returns).\n  if (!isRequireReturnsDefaultExportEnabled(js)) {\n    return JsValue(module->GetModuleNamespace());\n  }\n\n  // When require_returns_default_export flag is enabled:\n  // 1. If module has default export: return it directly (it should be mutable)\n  // 2. If no default export: return a mutable copy of the namespace\n  // This matches Node.js require(esm) behavior and allows monkey-patching.\n  // See: https://github.com/cloudflare/workerd/issues/5844\n\n  JsObject moduleNamespace(module->GetModuleNamespace().As<v8::Object>());\n  if (moduleNamespace.has(js, \"default\"_kj)) {\n    // Default export should be a regular mutable object, return it directly.\n    // No caching needed here since we're returning the module's own default export.\n    // Note: Modules should NOT re-export namespace objects as their default.\n    // If they do, the default export will be read-only which breaks monkey-patching.\n    return moduleNamespace.get(js, \"default\"_kj);\n  }\n\n  // No default export - return a cached mutable copy of the namespace, or create one.\n  KJ_IF_SOME(cached, info.maybeMutableExports) {\n    return JsValue(cached.getHandle(js));\n  }\n  auto mutableExports = createMutableModuleExports(js, moduleNamespace);\n  info.maybeMutableExports = V8Ref<v8::Object>(js.v8Isolate, mutableExports);\n  return mutableExports;\n}\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/modules.capnp",
    "content": "@0xc8cbb234694939d5;\n\nusing Cxx = import \"/capnp/c++.capnp\";\n$Cxx.namespace(\"workerd::jsg\");\n\nstruct Bundle {\n  # Group of modules to be loaded together.\n  # Bundles are currently generated during compilation process and linked with the workerd,\n  # but loading bundles from somewhere else will also be possible.\n  modules @0 :List(Module);\n}\n\nstruct Module {\n  # Javascript module with its source code.\n\n  name @0 :Text;\n  union {\n    src @1 :Data; # JS / TS code\n    wasm @4 :Data; # Wasm module\n    data @5 :Data; # Binary data module\n    json @6 :Text; # Json module\n  }\n  tsDeclaration @3 :Text;\n\n  type @2 :ModuleType;\n\n  # Optional compile cache to be used to speed up module loading\n  compileCache @7 :Data;\n}\n\n\nenum ModuleType {\n  bundle @0;\n  # Provided by the worker bundle. TODO: rename this to e.g., user?\n\n  builtin @1;\n  # Provided by the runtime and can be imported by the worker bundle.\n  # Can be overridden by modules in the worker bundle.\n\n  internal @2;\n  # Provided by runtime but can only imported by builtin modules.\n}\n"
  },
  {
    "path": "src/workerd/jsg/modules.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/jsg/function.h>\n#include <workerd/jsg/modules.capnp.h>\n#include <workerd/jsg/observer.h>\n#include <workerd/util/sentry.h>\n#include <workerd/util/thread-scopes.h>\n\n#include <v8-json.h>\n\n#include <kj/filesystem.h>\n#include <kj/map.h>\n\nnamespace workerd::jsg {\n\ntemplate <typename T>\nclass Promise;\n\nenum class InstantiateModuleOptions {\n  // Allows pending top-level await in the module when evaluated. Will cause\n  // the microtask queue to be drained once in an attempt to resolve those.\n  DEFAULT,\n  // Throws if the module evaluation results in a pending promise.\n  NO_TOP_LEVEL_AWAIT,\n};\n\nvoid instantiateModule(jsg::Lock& js,\n    v8::Local<v8::Module>& module,\n    InstantiateModuleOptions options = InstantiateModuleOptions::DEFAULT);\n\nenum class ModuleInfoCompileOption {\n  // The BUNDLE options tells the compile operation to treat the content as coming\n  // from a worker bundle.\n  BUNDLE,\n\n  // The BUILTIN option tells the compile operation to treat the content as a builtin\n  // module. This implies certain changes in behavior, such as treating the content\n  // as an immutable, process-lifetime buffer that will never be destroyed, and caching\n  // the compilation data.\n  BUILTIN,\n};\n\nv8::Local<v8::WasmModuleObject> compileWasmModule(\n    jsg::Lock& js, kj::ArrayPtr<const uint8_t> code, const CompilationObserver& observer);\n\n// The ModuleRegistry maintains the collection of modules known to a script that can be\n// required or imported.\nclass ModuleRegistry {\n public:\n  KJ_DISALLOW_COPY_AND_MOVE(ModuleRegistry);\n\n  ModuleRegistry() {}\n\n  using Type = ModuleType;\n\n  JSG_MEMORY_INFO(ModuleRegistry) {\n    // TODO(soon): Implement memory tracking for ModuleRegistry\n  }\n\n  enum class ResolveOption {\n    // Default resolution. Check the worker bundle first, then builtins.\n    DEFAULT,\n    // Built-in resolution. Check only non-internal builtins.\n    BUILTIN_ONLY,\n    // Internal resolution. Check only internal builtins.\n    INTERNAL_ONLY,\n  };\n\n  static inline ModuleRegistry* from(jsg::Lock& js) {\n    return &KJ_ASSERT_NONNULL(jsg::getAlignedPointerFromEmbedderData<ModuleRegistry>(\n        js.v8Context(), jsg::ContextPointerSlot::MODULE_REGISTRY));\n  }\n\n  struct CapnpModuleInfo {\n    Value fileScope;                                       // default import\n    kj::HashMap<kj::StringPtr, jsg::Value> topLevelDecls;  // named imports\n\n    CapnpModuleInfo(Value fileScope, kj::HashMap<kj::StringPtr, jsg::Value> topLevelDecls);\n    CapnpModuleInfo(CapnpModuleInfo&&) = default;\n    CapnpModuleInfo& operator=(CapnpModuleInfo&&) = default;\n  };\n\n  struct CommonJsModuleInfo {\n    struct CommonJsModuleProvider {\n      virtual JsObject getContext(Lock& js) = 0;\n      virtual JsValue getExports(Lock& js) = 0;\n      virtual ~CommonJsModuleProvider() noexcept(false) = default;\n    };\n\n    kj::Own<CommonJsModuleProvider> provider;\n    jsg::Function<void()> evalFunc;\n\n    CommonJsModuleInfo(auto& lock,\n        kj::StringPtr name,\n        kj::StringPtr content,\n        kj::Own<CommonJsModuleProvider> provider)\n        : provider(kj::mv(provider)),\n          evalFunc(initEvalFunc(lock, *this->provider, name, content)) {}\n\n    CommonJsModuleInfo(CommonJsModuleInfo&&) = default;\n    CommonJsModuleInfo& operator=(CommonJsModuleInfo&&) = default;\n\n    jsg::JsValue getExports(jsg::Lock& js);\n\n    static jsg::Function<void()> initEvalFunc(\n        auto& lock, CommonJsModuleProvider& provider, kj::StringPtr name, kj::StringPtr content) {\n      v8::ScriptOrigin origin(v8StrIntern(lock.v8Isolate, name));\n      v8::ScriptCompiler::Source source(v8Str(lock.v8Isolate, content), origin);\n      auto context = lock.v8Context();\n      v8::Local<v8::Object> handle = provider.getContext(lock);\n      auto fn =\n          jsg::check(v8::ScriptCompiler::CompileFunction(context, &source, 0, nullptr, 1, &handle));\n      return lock.template unwrap<jsg::Function<void()>>(context, fn);\n    }\n  };\n\n  template <typename T>\n  struct ValueModuleInfo {\n    jsg::V8Ref<T> value;\n\n    ValueModuleInfo(jsg::Lock& js, v8::Local<T> value): value(js.v8Isolate, value) {}\n\n    ValueModuleInfo(ValueModuleInfo&&) = default;\n    ValueModuleInfo& operator=(ValueModuleInfo&&) = default;\n  };\n\n  using DataModuleInfo = ValueModuleInfo<v8::ArrayBuffer>;\n  using TextModuleInfo = ValueModuleInfo<v8::String>;\n  using WasmModuleInfo = ValueModuleInfo<v8::WasmModuleObject>;\n  using JsonModuleInfo = ValueModuleInfo<v8::Value>;\n  using ObjectModuleInfo = ValueModuleInfo<v8::Object>;\n\n  struct ModuleInfo {\n    HashableV8Ref<v8::Module> module;\n\n    using SyntheticModuleInfo = kj::OneOf<CapnpModuleInfo,\n        CommonJsModuleInfo,\n        DataModuleInfo,\n        TextModuleInfo,\n        WasmModuleInfo,\n        JsonModuleInfo,\n        ObjectModuleInfo>;\n    kj::Maybe<SyntheticModuleInfo> maybeSynthetic;\n    kj::Maybe<kj::Array<kj::String>> maybeNamedExports;\n\n    // For source phase imports - stores the module source object (e.g., WebAssembly.Module)\n    kj::Maybe<V8Ref<v8::Object>> maybeModuleSourceObject;\n\n    // Cache for mutable module exports wrapper when require_returns_default_export flag is enabled.\n    // Used to ensure require() returns the same mutable object for the same module.\n    // This enables frameworks like Next.js to patch built-in module exports.\n    // See: https://github.com/cloudflare/workerd/issues/5844\n    mutable kj::Maybe<V8Ref<v8::Object>> maybeMutableExports;\n\n    ModuleInfo(jsg::Lock& js,\n        v8::Local<v8::Module> module,\n        kj::Maybe<SyntheticModuleInfo> maybeSynthetic = kj::none);\n\n    ModuleInfo(jsg::Lock& js,\n        kj::StringPtr name,\n        kj::ArrayPtr<const char> content,\n        kj::ArrayPtr<const kj::byte> compileCache,\n        ModuleInfoCompileOption flags,\n        const CompilationObserver& observer);\n\n    ModuleInfo(jsg::Lock& js,\n        kj::StringPtr name,\n        kj::Maybe<kj::ArrayPtr<const kj::StringPtr>> maybeExports,\n        SyntheticModuleInfo synthetic);\n\n    ModuleInfo(ModuleInfo&&) = default;\n    ModuleInfo& operator=(ModuleInfo&&) = default;\n\n    uint hashCode() const {\n      return module.hashCode();\n    }\n\n    // Set the module source object for source phase imports\n    void setModuleSourceObject(jsg::Lock& js, v8::Local<v8::Object> sourceObject) {\n      maybeModuleSourceObject = V8Ref<v8::Object>(js.v8Isolate, sourceObject);\n    }\n\n    // Get the module source object for source phase imports\n    kj::Maybe<v8::Local<v8::Object>> getModuleSourceObject(jsg::Lock& js) const {\n      KJ_IF_SOME(sourceObject, maybeModuleSourceObject) {\n        return sourceObject.getHandle(js);\n      }\n      return kj::none;\n    }\n  };\n\n  struct ModuleRef {\n    const kj::Path& specifier;\n    Type type;\n    ModuleInfo& module;\n  };\n\n  enum class ResolveMethod {\n    // Resolving using the standard static or dynamic import.\n    IMPORT,\n    // Resolving using the commonjs require method.\n    REQUIRE,\n  };\n\n  using ModuleCallback =\n      kj::Function<kj::Maybe<ModuleInfo>(Lock&, ResolveMethod, kj::Maybe<const kj::Path&>&)>;\n\n  virtual kj::Maybe<ModuleInfo&> resolve(jsg::Lock& js,\n      const kj::Path& specifier,\n      kj::Maybe<const kj::Path&> referrer = kj::none,\n      ResolveOption option = ResolveOption::DEFAULT,\n      ResolveMethod method = ResolveMethod::IMPORT,\n      kj::Maybe<kj::StringPtr> rawSpecifier = kj::none) = 0;\n\n  virtual kj::Maybe<ModuleRef> resolve(jsg::Lock& js, v8::Local<v8::Module> module) = 0;\n\n  virtual Promise<Value> resolveDynamicImport(jsg::Lock& js,\n      const kj::Path& specifier,\n      const kj::Path& referrer,\n      kj::StringPtr rawSpecifier) = 0;\n\n  virtual Value resolveInternalImport(jsg::Lock& js, kj::StringPtr specifier) = 0;\n\n  // The dynamic import callback is provided by the embedder to set up any context necessary\n  // for instantiating the module during a dynamic import. The handler function passed into\n  // the callback is called to actually perform the instantiation of the module.\n  using DynamicImportCallback = Promise<Value>(jsg::Lock& js, kj::Function<Value()> handler);\n\n  virtual void setDynamicImportCallback(kj::Function<DynamicImportCallback> func) = 0;\n\n  enum class RequireImplOptions {\n    // Require returns the module namespace.\n    DEFAULT,\n    // Require returns the default export.\n    EXPORT_DEFAULT,\n  };\n\n  static JsValue requireImpl(\n      Lock& js, ModuleInfo& info, RequireImplOptions options = RequireImplOptions::DEFAULT);\n};\n\ntemplate <typename TypeWrapper>\nv8::MaybeLocal<v8::Promise> dynamicImportCallback(v8::Local<v8::Context> context,\n    v8::Local<v8::Data> host_defined_options,\n    v8::Local<v8::Value> resource_name,\n    v8::Local<v8::String> specifier,\n    v8::Local<v8::FixedArray> import_attributes);\n\nkj::Maybe<kj::OneOf<kj::String, ModuleRegistry::ModuleInfo>> tryResolveFromFallbackService(Lock& js,\n    const kj::Path& specifier,\n    kj::Maybe<const kj::Path&>& referrer,\n    CompilationObserver& observer,\n    ModuleRegistry::ResolveMethod method,\n    kj::Maybe<kj::StringPtr> rawSpecifier);\n\ntemplate <typename TypeWrapper>\nclass ModuleRegistryImpl final: public ModuleRegistry {\n public:\n  KJ_DISALLOW_COPY_AND_MOVE(ModuleRegistryImpl);\n\n  ModuleRegistryImpl(CompilationObserver& observer): observer(observer) {}\n\n  static kj::Own<ModuleRegistryImpl<TypeWrapper>> install(\n      v8::Isolate* isolate, v8::Local<v8::Context> context, CompilationObserver& observer) {\n    auto registry = kj::heap<ModuleRegistryImpl<TypeWrapper>>(observer);\n    jsg::setAlignedPointerInEmbedderData(\n        context, jsg::ContextPointerSlot::MODULE_REGISTRY, registry.get());\n    isolate->SetHostImportModuleDynamicallyCallback(dynamicImportCallback<TypeWrapper>);\n    return kj::mv(registry);\n  }\n\n  static inline ModuleRegistryImpl* from(jsg::Lock& js) {\n    return &KJ_ASSERT_NONNULL(jsg::getAlignedPointerFromEmbedderData<ModuleRegistryImpl>(\n        js.v8Context(), jsg::ContextPointerSlot::MODULE_REGISTRY));\n  }\n\n  void setDynamicImportCallback(kj::Function<DynamicImportCallback> func) override {\n    dynamicImportHandler = kj::mv(func);\n  }\n\n  void add(kj::Path& specifier, ModuleInfo&& info) {\n    entries.insert(kj::heap<Entry>(specifier, Type::BUNDLE, kj::fwd<ModuleInfo>(info)));\n  }\n\n  void addBuiltinModule(Module::Reader module) {\n    if (module.which() != Module::SRC) {\n      auto specifier = module.getName();\n      auto path = kj::Path::parse(specifier);\n      switch (module.which()) {\n        case Module::WASM:\n          // The body of this callback is copied from `compileWasmGlobal` in\n          // src/workerd/server/workerd-api.c++.\n          addBuiltinModule(specifier,\n              [specifier, module, this](Lock& lock, ResolveMethod, kj::Maybe<const kj::Path&>&) {\n            lock.setAllowEval(true);\n            KJ_DEFER(lock.setAllowEval(false));\n\n            // Allow Wasm compilation to spawn a background thread for tier-up, i.e.\n            // recompiling Wasm with optimizations in the background. Otherwise Wasm startup\n            // is way too slow. Until tier-up finishes, requests will be handled using\n            // Liftoff-generated code, which compiles fast but runs slower.\n            AllowV8BackgroundThreadsScope scope;\n            auto wasmModule =\n                jsg::compileWasmModule(lock, module.getWasm().asBytes(), this->observer);\n            auto moduleInfo = jsg::ModuleRegistry::ModuleInfo(\n                lock, specifier, kj::none, jsg::ModuleRegistry::WasmModuleInfo(lock, wasmModule));\n            // Uncomment iff we want to permit source phase imports for builtin Wasm modules\n            // moduleInfo.setModuleSourceObject(lock, wasmModule.template As<v8::Object>());\n            return moduleInfo;\n          }, module.getType());\n          return;\n        case Module::DATA:\n          addBuiltinModule(specifier,\n              [specifier, module](Lock& lock, ResolveMethod, kj::Maybe<const kj::Path&>&) {\n            v8::Local<v8::ArrayBuffer> data =\n                lock.wrapBytes(kj::heapArray(module.getData().asBytes()));\n            return jsg::ModuleRegistry::ModuleInfo(\n                lock, specifier, kj::none, jsg::ModuleRegistry::DataModuleInfo(lock, data));\n          }, module.getType());\n          return;\n        case Module::JSON:\n          addBuiltinModule(specifier,\n              [specifier, module](Lock& lock, ResolveMethod, kj::Maybe<const kj::Path&>&) {\n            auto data =\n                jsg::check(v8::JSON::Parse(lock.v8Context(), lock.wrapString(module.getJson())));\n            return jsg::ModuleRegistry::ModuleInfo(\n                lock, specifier, kj::none, jsg::ModuleRegistry::JsonModuleInfo(lock, data));\n          }, module.getType());\n          return;\n        case Module::SRC:\n          KJ_UNREACHABLE\n      }\n    }\n    // TODO: asChars() might be wrong for wide characters\n    addBuiltinModule(module.getName(), module.getSrc().asChars(), module.getType(),\n        module.getCompileCache().asBytes());\n  }\n\n  void addBuiltinBundle(Bundle::Reader bundle, kj::Maybe<Type> maybeFilter = kj::none) {\n    for (auto module: bundle.getModules()) {\n      if (module.getType() == maybeFilter.orDefault(module.getType())) addBuiltinModule(module);\n    }\n  }\n\n  template <typename Func>\n  void addBuiltinBundleFiltered(Bundle::Reader bundle, Func filter) {\n    for (auto module: bundle.getModules()) {\n      if (filter(module)) {\n        addBuiltinModule(module);\n      }\n    }\n  }\n\n  // Register new module accessible by a given importPath. The module is instantiated\n  // after first resolve attempt within application has failed, i.e. it is possible for\n  // application to override the module.\n  // sourceCode has to exist while this ModuleRegistry exists.\n  // The expectation is for this method to be called during the assembly of worker global context\n  // after registering all user modules.\n  void addBuiltinModule(kj::StringPtr specifier,\n      kj::ArrayPtr<const char> sourceCode,\n      Type type = Type::BUILTIN,\n      kj::ArrayPtr<const kj::byte> compileCache = {}) {\n    KJ_ASSERT(type != Type::BUNDLE);\n    auto path = kj::Path::parse(specifier);\n    entries.insert(kj::heap<Entry>(path, type, sourceCode, compileCache));\n  }\n\n  void addBuiltinModule(\n      kj::StringPtr specifier, ModuleCallback factory, Type type = Type::BUILTIN) {\n    KJ_ASSERT(type != Type::BUNDLE);\n    auto path = kj::Path::parse(specifier);\n    entries.insert(kj::heap<Entry>(path, type, kj::mv(factory)));\n  }\n\n  template <typename T>\n  void addBuiltinModule(kj::StringPtr specifier, Type type = Type::BUILTIN) {\n    addBuiltinModule(specifier, alloc<T>(), type);\n  }\n\n  template <typename T>\n  void addBuiltinModule(kj::StringPtr specifier, Ref<T> object, Type type = Type::BUILTIN) {\n    addBuiltinModule(specifier,\n        [specifier = kj::str(specifier), object = kj::mv(object)](\n            Lock& js, ResolveMethod, kj::Maybe<const kj::Path&>&) mutable -> kj::Maybe<ModuleInfo> {\n      auto& wrapper = TypeWrapper::from(js.v8Isolate);\n      auto wrap = wrapper.wrap(js, js.v8Context(), kj::none, kj::mv(object));\n      return kj::Maybe(ModuleInfo(js, specifier, kj::none, ObjectModuleInfo(js, wrap)));\n    },\n        type);\n  }\n\n  kj::Maybe<ModuleInfo&> resolve(jsg::Lock& js,\n      const kj::Path& specifier,\n      kj::Maybe<const kj::Path&> referrer = kj::none,\n      ResolveOption option = ResolveOption::DEFAULT,\n      ResolveMethod method = ResolveMethod::IMPORT,\n      kj::Maybe<kj::StringPtr> rawSpecifier = kj::none) override {\n    using Key = Entry::Key;\n    if (option == ResolveOption::INTERNAL_ONLY) {\n      KJ_IF_SOME(entry, entries.find(Key(specifier, Type::INTERNAL))) {\n        return entry->module(js, observer, referrer, method);\n      }\n      return kj::none;\n    } else if (option == ResolveOption::BUILTIN_ONLY) {\n      KJ_IF_SOME(entry, entries.find(Key(specifier, Type::BUILTIN))) {\n        return entry->module(js, observer, referrer, method);\n      }\n    } else {\n      if (option == ResolveOption::DEFAULT) {\n        // First, we try to resolve a worker bundle version of the module.\n        KJ_IF_SOME(entry, entries.find(Key(specifier, Type::BUNDLE))) {\n          return entry->module(js, observer, referrer, method);\n        }\n      }\n      // Then we look for a built-in version of the module.\n      KJ_IF_SOME(entry, entries.find(Key(specifier, Type::BUILTIN))) {\n        return entry->module(js, observer, referrer, method);\n      }\n    }\n\n    // An internal only resolution should never go to the fallback service\n    KJ_DASSERT(option != ResolveOption::INTERNAL_ONLY);\n\n    // If the module is not found and we have a module fallback service configured,\n    // let's try that as a means of looking it up.\n    auto str = specifier.toString(true);\n    KJ_IF_SOME(found, fallbackServiceRedirects.find(str)) {\n      // The fallback service has already given us a redirect response for this specifier.\n      // let's use it to try to resolve. Make sure we're using DEFAULT resolution so BUNDLE-typed\n      // modules from the fallback service can be used.\n      option = ResolveOption::DEFAULT;\n      return resolve(js, specifier.parent().eval(found), referrer, option, method, rawSpecifier);\n    }\n    KJ_IF_SOME(info,\n        tryResolveFromFallbackService(js, specifier, referrer, observer, method, rawSpecifier)) {\n      // If we resolved a module from the fallback service, we have to be sure\n      // to add it to the registry...\n      KJ_SWITCH_ONEOF(info) {\n        KJ_CASE_ONEOF(i, ModuleInfo) {\n          auto type = Type::BUNDLE;\n          if (option == ResolveOption::BUILTIN_ONLY) {\n            if (str.startsWith(\"/node:\") || str.startsWith(\"/cloudflare:\") ||\n                str.startsWith(\"/workerd:\")) {\n              type = Type::BUILTIN;\n            }\n          }\n\n          entries.insert(kj::heap<Entry>(specifier, type, kj::mv(i)));\n          auto& entry = KJ_ASSERT_NONNULL(entries.find(Key(specifier, type)));\n          return entry->module(js, observer, referrer, method);\n        }\n        KJ_CASE_ONEOF(s, kj::String) {\n          // If a kj::String is returned, it means the fallback service is redirecting\n          // us to another module that should already be in the registry... or could\n          // itself end up calling back to the fallback service.\n          fallbackServiceRedirects.upsert(kj::mv(str), kj::str(s));\n          // Make sure we're using DEFAULT resolution so BUNDLE-typed modules from the fallback\n          // service can be used.\n          option = ResolveOption::DEFAULT;\n          return resolve(js, specifier.parent().eval(s), referrer, option, method, rawSpecifier);\n        }\n      }\n    }\n\n    return kj::none;\n  }\n\n  kj::Maybe<ModuleRef> resolve(jsg::Lock& js, v8::Local<v8::Module> module) override {\n    for (const kj::Own<Entry>& entry: entries) {\n      // Unfortunately we cannot use entries.find(...) in here because the module info can\n      // be initialized lazily at any point after the entry is indexed, making the lookup\n      // by module a bit problematic. Iterating through the entries is slower but it works.\n      KJ_IF_SOME(info, entry->info.template tryGet<ModuleInfo>()) {\n        if (info.module == module) {\n          return ModuleRef{\n            .specifier = entry->specifier,\n            .type = entry->type,\n            .module = const_cast<ModuleInfo&>(info),\n          };\n        }\n      }\n    }\n    return kj::none;\n  }\n\n  size_t size() const {\n    return entries.size();\n  }\n\n  Promise<Value> resolveDynamicImport(jsg::Lock& js,\n      const kj::Path& specifier,\n      const kj::Path& referrer,\n      kj::StringPtr rawSpecifier) override {\n    // Here, we first need to determine if the referrer is a built-in module\n    // or not. If it is a built-in, then we are only permitted to resolve\n    // internal modules. If the worker bundle provided an override for the\n    // built-in module, then the built-in was never registered and won't\n    // be found.\n    using Key = Entry::Key;\n    auto resolveOption = ModuleRegistry::ResolveOption::DEFAULT;\n    if (entries.find(Key(referrer, Type::BUNDLE)) != kj::none) {\n      // The referrer is found in the module bundle, so we use the default.\n    } else if (entries.find(Key(referrer, Type::BUILTIN)) != kj::none) {\n      resolveOption = ModuleRegistry::ResolveOption::INTERNAL_ONLY;\n    }\n\n    KJ_IF_SOME(info,\n        resolve(js, specifier, referrer, resolveOption, ResolveMethod::IMPORT, rawSpecifier)) {\n      KJ_IF_SOME(func, dynamicImportHandler) {\n        auto handler = [&info, isolate = js.v8Isolate]() -> Value {\n          auto& js = Lock::from(isolate);\n          auto module = info.module.getHandle(js);\n          instantiateModule(js, module);\n          return js.v8Ref(module->GetModuleNamespace());\n        };\n        return func(js, kj::mv(handler));\n      }\n\n      // If there is no dynamicImportHandler set, then we are going to handle that as if\n      // the module does not exist and fall through to the rejected promise below.\n    }\n\n    return js.rejectedPromise<Value>(\n        js.v8Error(kj::str(\"No such module \\\"\", specifier.toString(), \"\\\".\")));\n  }\n\n  Value resolveInternalImport(jsg::Lock& js, const kj::StringPtr specifier) override {\n    auto specifierPath = kj::Path(specifier);\n    auto resolveOption = jsg::ModuleRegistry::ResolveOption::INTERNAL_ONLY;\n    auto maybeModuleInfo =\n        resolve(js, specifierPath, kj::none, resolveOption, ResolveMethod::IMPORT, specifier);\n    auto moduleInfo = &KJ_REQUIRE_NONNULL(maybeModuleInfo, \"No such module \\\"\", specifier, \"\\\".\");\n    auto handle = moduleInfo->module.getHandle(js);\n    jsg::instantiateModule(js, handle);\n    return js.v8Ref(handle->GetModuleNamespace());\n  }\n\n  CompilationObserver& getObserver() {\n    return observer;\n  }\n\n private:\n  CompilationObserver& observer;\n  kj::Maybe<kj::Function<DynamicImportCallback>> dynamicImportHandler;\n\n  // When we build a bundle containing modules, we must build a table of modules to resolve imports.\n  //\n  // Because of the design of V8's resolver callback, we end up needing a table with two indexes:\n  // we need to be able to search it by path (filename) as well as search for a specific module\n  // object by identity. We use a kj::Table!\n  struct Entry {\n    using Info = kj::OneOf<ModuleInfo, kj::ArrayPtr<const char>, ModuleCallback>;\n\n    struct Key {\n      const kj::Path& specifier;\n      const Type type = Type::BUNDLE;\n      uint hash;\n\n      Key(const kj::Path& specifier, Type type)\n          : specifier(specifier),\n            type(type),\n            hash(kj::hashCode(specifier, type)) {}\n\n      uint hashCode() const {\n        return hash;\n      }\n    };\n\n    kj::Path specifier;\n    Type type;\n\n    // Either instantiated module or module source code.\n    Info info;\n\n    // Optional compileCache.\n    kj::ArrayPtr<const kj::byte> compileCache;\n\n    Entry(const kj::Path& specifier, Type type, ModuleInfo info)\n        : specifier(specifier.clone()),\n          type(type),\n          info(kj::mv(info)) {}\n\n    Entry(const kj::Path& specifier,\n        Type type,\n        kj::ArrayPtr<const char> src,\n        kj::ArrayPtr<const kj::byte> compileCache)\n        : specifier(specifier.clone()),\n          type(type),\n          info(src),\n          compileCache(compileCache) {}\n\n    Entry(const kj::Path& specifier, Type type, ModuleCallback factory)\n        : specifier(specifier.clone()),\n          type(type),\n          info(kj::mv(factory)) {}\n\n    Entry(Entry&&) = default;\n    Entry& operator=(Entry&&) = default;\n\n    // Lazily instantiate module from source code if needed\n    kj::Maybe<ModuleInfo&> module(jsg::Lock& js,\n        CompilationObserver& observer,\n        kj::Maybe<const kj::Path&> referrer,\n        ModuleRegistry::ResolveMethod method = ModuleRegistry::ResolveMethod::IMPORT) {\n      KJ_SWITCH_ONEOF(info) {\n        KJ_CASE_ONEOF(moduleInfo, ModuleInfo) {\n          return kj::Maybe<ModuleInfo&>(moduleInfo);\n        }\n        KJ_CASE_ONEOF(src, kj::ArrayPtr<const char>) {\n          info = ModuleInfo(js, specifier.toString(), src, compileCache,\n              ModuleInfoCompileOption::BUILTIN, observer);\n          return info.tryGet<ModuleInfo>();\n        }\n        KJ_CASE_ONEOF(src, ModuleCallback) {\n          KJ_IF_SOME(result, src(js, method, referrer)) {\n            info = kj::mv(result);\n          }\n          return info.tryGet<ModuleInfo>();\n        }\n      }\n      KJ_UNREACHABLE;\n    }\n  };\n\n  struct SpecifierHashCallbacks {\n    using Key = Entry::Key;\n\n    const Key keyForRow(const kj::Own<Entry>& row) const {\n      return Key(row->specifier, row->type);\n    }\n\n    bool matches(const kj::Own<Entry>& row, Key key) const {\n      return row->specifier == key.specifier && row->type == key.type;\n    }\n\n    uint hashCode(Key key) const {\n      return key.hashCode();\n    }\n  };\n\n  kj::Table<kj::Own<Entry>, kj::HashIndex<SpecifierHashCallbacks>> entries;\n  kj::HashMap<kj::String, kj::String> fallbackServiceRedirects;\n};\n\ntemplate <typename TypeWrapper>\nv8::MaybeLocal<v8::Promise> dynamicImportCallback(v8::Local<v8::Context> context,\n    v8::Local<v8::Data> host_defined_options,\n    v8::Local<v8::Value> resource_name,\n    v8::Local<v8::String> specifier,\n    v8::Local<v8::FixedArray> import_attributes) {\n  auto& js = Lock::current();\n  auto registry = ModuleRegistry::from(js);\n  auto& wrapper = TypeWrapper::from(js.v8Isolate);\n\n  // TODO(cleanup): This could probably be simplified using jsg::Promise\n  const auto makeRejected = [&](auto reason) {\n    v8::Local<v8::Promise::Resolver> resolver;\n    if (v8::Promise::Resolver::New(context).ToLocal(&resolver) &&\n        resolver->Reject(context, reason).IsJust()) {\n      return resolver->GetPromise();\n    }\n    return v8::Local<v8::Promise>();\n  };\n\n  // The specification for import attributes strongly recommends that embedders\n  // reject import attributes and types they do not understand/implement. This\n  // is because import attributes can alter the interpretation of a module and\n  // are considered to be part of the unique key for caching a module.\n  // Throwing an error for things we do not understand is the safest thing to do.\n  // However, historically we have not followed this guideline in the spec\n  // and unfortunately there are applications deployed that will break if we\n  // started enforcing that guideline without a compat flag.\n  if (!import_attributes.IsEmpty() && import_attributes->Length() > 0 &&\n      js.getThrowOnUnrecognizedImportAssertion()) {\n    return makeRejected(js.v8Error(\"Unrecognized import attributes specified\"));\n  }\n\n  // The dynamic import might be resolved synchronously or asynchronously.\n  // Accordingly, resolveDynamicImport will return a jsg::Promise<jsg::Value>\n  // that will resolve to the module's namespace object or will reject if there\n  // was any error.\n  //\n  // Importantly, we defensively catch any synchronous errors here and handle them\n  // explicitly as rejected Promises.\n  v8::TryCatch tryCatch(js.v8Isolate);\n\n  // TODO(cleanup): If kj::Path::parse or kj::Path::eval fail it is most likely the application's\n  // fault. We'll return a \"No such module\" error. We could handle this more gracefully\n  // if kj::Path had tryParse()/tryEval() variants.\n\n  auto maybeReferrerPath = ([&]() -> kj::Maybe<kj::Path> {\n    try {\n      return kj::Path::parse(kj::str(resource_name));\n    } catch (kj::Exception& ex) {\n      return kj::none;\n    }\n  })();\n\n  auto spec = kj::str(specifier);\n  if (isNodeJsCompatEnabled(js)) {\n    KJ_IF_SOME(nodeSpec, checkNodeSpecifier(spec)) {\n      spec = kj::mv(nodeSpec);\n    }\n  }\n\n  // Handle process module redirection based on enable_nodejs_process_v2 flag\n  if (spec == \"node:process\") {\n    auto processSpec = isNodeJsProcessV2Enabled(js) ? \"node-internal:public_process\"_kj\n                                                    : \"node-internal:legacy_process\"_kj;\n    try {\n      // Use resolveInternalImport for internal modules\n      auto moduleNamespace = registry->resolveInternalImport(js, processSpec);\n      v8::Local<v8::Promise::Resolver> resolver;\n      if (v8::Promise::Resolver::New(context).ToLocal(&resolver) &&\n          resolver->Resolve(context, moduleNamespace.getHandle(js)).IsJust()) {\n        return resolver->GetPromise();\n      }\n      return v8::Local<v8::Promise>();\n    } catch (JsExceptionThrown&) {\n      if (!tryCatch.CanContinue() || tryCatch.Exception().IsEmpty()) {\n        return v8::MaybeLocal<v8::Promise>();\n      }\n      return makeRejected(tryCatch.Exception());\n    } catch (kj::Exception& ex) {\n      return makeRejected(js.exceptionToJs(kj::mv(ex)).getHandle(js));\n    }\n  }\n\n  auto maybeSpecifierPath = ([&]() -> kj::Maybe<kj::Path> {\n    // If the specifier begins with one of our known prefixes, let's not resolve\n    // it against the referrer.\n    if (spec.startsWith(\"node:\") || spec.startsWith(\"cloudflare:\") || spec.startsWith(\"workerd:\")) {\n      return kj::Path::parse(spec);\n    }\n    KJ_IF_SOME(referrerPath, maybeReferrerPath) {\n      try {\n        return referrerPath.parent().eval(spec);\n      } catch (kj::Exception& ex) {\n        return kj::none;\n      }\n    }\n    return kj::none;\n  })();\n\n  if (maybeReferrerPath == kj::none || maybeSpecifierPath == kj::none) {\n    // If either of these are nullptr it means the kj::Path::parse or\n    // kj::Path::eval failed. We want to handle these as No such module\n    // errors.\n    return makeRejected(js.v8Error(kj::str(\"No such module \\\"\", specifier, \"\\\"\")));\n  }\n\n  auto& referrerPath = KJ_ASSERT_NONNULL(maybeReferrerPath);\n  auto& specifierPath = KJ_ASSERT_NONNULL(maybeSpecifierPath);\n\n  try {\n    return wrapper.wrap(js, context, kj::none,\n        registry->resolveDynamicImport(js, specifierPath, referrerPath, spec));\n  } catch (JsExceptionThrown&) {\n    // If the tryCatch.Exception().IsEmpty() here is true, no JavaScript error\n    // was scheduled which can happen in a few edge cases. Treat it as if\n    // CanContinue() is false.\n    if (!tryCatch.CanContinue() || tryCatch.Exception().IsEmpty()) {\n      // There's nothing else we can reasonably do.\n      return v8::MaybeLocal<v8::Promise>();\n    }\n\n    return makeRejected(tryCatch.Exception());\n  } catch (kj::Exception& ex) {\n    return makeRejected(exceptionToJs(js.v8Isolate, kj::mv(ex)));\n  }\n  KJ_UNREACHABLE;\n}\n\nModuleRegistry* getModulesForResolveCallback(v8::Isolate* isolate);\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/multiple-typewrappers-test.c++",
    "content": "#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/setup.h>\n#include <workerd/jsg/type-wrapper.h>\n\n#include <capnp/message.h>\n#include <kj/test.h>\n\nnamespace workerd::jsg::test {\njsg::V8System v8System;\n\nstruct TestApi1: public jsg::Object {\n  TestApi1() = default;\n  TestApi1(jsg::Lock&, const jsg::Url&) {}\n  int test1(jsg::Lock& js) {\n    return 1;\n  }\n\n  int test2(jsg::Lock& js) {\n    return 2;\n  }\n  static jsg::Ref<TestApi1> constructor(jsg::Lock& js) {\n    return js.alloc<TestApi1>();\n  }\n\n  JSG_RESOURCE_TYPE(TestApi1, workerd::CompatibilityFlags::Reader flags) {\n    if (flags.getPythonWorkers()) {\n      JSG_METHOD(test2);\n    } else {\n      JSG_METHOD(test1);\n    }\n  }\n};\nstruct TestApi2: public jsg::Object {\n  TestApi2() = default;\n  TestApi2(jsg::Lock&, const jsg::Url&) {}\n  int test1(jsg::Lock& js) {\n    return 1;\n  }\n\n  int test2(jsg::Lock& js) {\n    return 2;\n  }\n  static jsg::Ref<TestApi2> constructor(jsg::Lock& js) {\n    return js.alloc<TestApi2>();\n  }\n\n  JSG_RESOURCE_TYPE(TestApi2, workerd::CompatibilityFlags::Reader flags) {\n    if (flags.getPythonWorkers()) {\n      JSG_METHOD(test2);\n    } else {\n      JSG_METHOD(test1);\n    }\n  }\n};\n\nstruct BaseTestContext: public jsg::Object, public jsg::ContextGlobal {\n  int test1(jsg::Lock& js) {\n    return 1;\n  }\n\n  int test2(jsg::Lock& js) {\n    return 2;\n  }\n  JSG_RESOURCE_TYPE(BaseTestContext, workerd::CompatibilityFlags::Reader flags) {\n    if (flags.getPythonWorkers()) {\n      JSG_METHOD(test2);\n    } else {\n      JSG_METHOD(test1);\n    }\n    JSG_NESTED_TYPE(TestApi1);\n  }\n};\n\nstruct TestContext: public BaseTestContext {\n  int test3(jsg::Lock& js) {\n    return 3;\n  }\n\n  int test4(jsg::Lock& js) {\n    return 4;\n  }\n  JSG_RESOURCE_TYPE(TestContext, workerd::CompatibilityFlags::Reader flags) {\n    JSG_INHERIT(BaseTestContext);\n    if (flags.getPythonWorkers()) {\n      JSG_METHOD(test4);\n    } else {\n      JSG_METHOD(test3);\n    }\n    JSG_NESTED_TYPE(TestApi2);\n  }\n};\n\nJSG_DECLARE_ISOLATE_TYPE(TestIsolate, TestContext, BaseTestContext, TestApi1, TestApi2);\n\nclass Configuration {\n public:\n  Configuration(workerd::CompatibilityFlags::Reader& flags): flags(flags) {}\n  operator const workerd::CompatibilityFlags::Reader() const {\n    return flags;\n  }\n\n private:\n  workerd::CompatibilityFlags::Reader& flags;\n};\n\nvoid expectEval(\n    jsg::Lock& js, kj::StringPtr code, kj::StringPtr expectedType, kj::StringPtr expectedValue) {\n  // Create a string containing the JavaScript source code.\n  v8::Local<v8::String> source = jsg::v8Str(js.v8Isolate, code);\n\n  // Compile the source code.\n  v8::Local<v8::Script> script;\n  if (!v8::Script::Compile(js.v8Context(), source).ToLocal(&script)) {\n    KJ_FAIL_EXPECT(\"code didn't parse\", code);\n    return;\n  }\n\n  v8::TryCatch catcher(js.v8Isolate);\n\n  // Run the script to get the result.\n  v8::Local<v8::Value> result;\n  if (script->Run(js.v8Context()).ToLocal(&result)) {\n    v8::String::Utf8Value type(js.v8Isolate, result->TypeOf(js.v8Isolate));\n    v8::String::Utf8Value value(js.v8Isolate, result);\n\n    KJ_EXPECT(*type == expectedType, *type, expectedType);\n    KJ_EXPECT(*value == expectedValue, *value, expectedValue);\n  } else if (catcher.HasCaught()) {\n    v8::String::Utf8Value message(js.v8Isolate, catcher.Exception());\n\n    KJ_EXPECT(expectedType == \"throws\", expectedType, catcher.Exception());\n    KJ_EXPECT(*message == expectedValue, *message, expectedValue);\n  } else {\n    KJ_FAIL_EXPECT(\"returned empty handle but didn't throw exception?\");\n  }\n}\n\nKJ_TEST(\"Create a context with a configuration then create a default context with another\") {\n  capnp::MallocMessageBuilder flagsArena;\n  auto flags = flagsArena.initRoot<::workerd::CompatibilityFlags>();\n  auto flagsReader = flags.asReader();\n  TestIsolate isolate(v8System, v8::IsolateGroup::GetDefault(), Configuration(flagsReader),\n      kj::heap<IsolateObserver>(), defaultExternalStringAllocator(), {}, false);\n  isolate.runInLockScope([&](TestIsolate::Lock& lock) {\n    jsg::JsContext<TestContext> context =\n        lock.newContextWithConfiguration<TestContext>(Configuration(flagsReader), {});\n    v8::Local<v8::Context> ctx = context.getHandle(lock);\n    KJ_ASSERT(!ctx.IsEmpty(), \"unable to enter invalid v8::Context\");\n    v8::Context::Scope scope(ctx);\n\n    expectEval(lock, \"test1()\", \"number\", \"1\");\n    expectEval(lock, \"test2()\", \"throws\", \"ReferenceError: test2 is not defined\");\n    expectEval(lock, \"test3()\", \"number\", \"3\");\n    expectEval(lock, \"test4()\", \"throws\", \"ReferenceError: test4 is not defined\");\n    expectEval(lock, \"new TestApi1().test1()\", \"number\", \"1\");\n    expectEval(lock, \"new TestApi1().test2()\", \"throws\",\n        \"TypeError: (intermediate value).test2 is not a function\");\n    expectEval(lock, \"new TestApi2().test1()\", \"number\", \"1\");\n    expectEval(lock, \"new TestApi2().test2()\", \"throws\",\n        \"TypeError: (intermediate value).test2 is not a function\");\n  });\n  flags.setPythonWorkers(true);\n  isolate.instantiateDefaultWrapper(Configuration(flagsReader));\n  isolate.runInLockScope([&](TestIsolate::Lock& lock) {\n    jsg::JsContext<TestContext> context = lock.newContext<TestContext>();\n    v8::Local<v8::Context> ctx = context.getHandle(lock);\n    KJ_ASSERT(!ctx.IsEmpty(), \"unable to enter invalid v8::Context\");\n    v8::Context::Scope scope(ctx);\n\n    expectEval(lock, \"test1()\", \"throws\", \"ReferenceError: test1 is not defined\");\n    expectEval(lock, \"test2()\", \"number\", \"2\");\n    expectEval(lock, \"test3()\", \"throws\", \"ReferenceError: test3 is not defined\");\n    expectEval(lock, \"test4()\", \"number\", \"4\");\n    expectEval(lock, \"new TestApi1().test1()\", \"throws\",\n        \"TypeError: (intermediate value).test1 is not a function\");\n    expectEval(lock, \"new TestApi1().test2()\", \"number\", \"2\");\n    expectEval(lock, \"new TestApi2().test1()\", \"throws\",\n        \"TypeError: (intermediate value).test1 is not a function\");\n    expectEval(lock, \"new TestApi2().test2()\", \"number\", \"2\");\n  });\n}\n\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/observer.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/util/strong-bool.h>\n\n#include <v8-local-handle.h>\n\n#include <kj/common.h>\n#include <kj/exception.h>\n#include <kj/string.h>\n\n// Forward declare v8::Isolate here, this allows us to avoid including the V8 header and compile\n// some targets without depending on V8.\nnamespace v8 {\nclass Isolate;\n}\n\nnamespace workerd::jsg {\n\nclass Url;\n\nstruct ResolveObserver {\n  virtual ~ResolveObserver() noexcept(false) {}\n\n  // Identifies the context in which a module resolution is being performed.\n  enum class Context {\n    // The resolve is being performed by a worker bundle module\n    // (that is, a worker script is calling import or require).\n    BUNDLE,\n    // The resolve is being performed by a builtin module\n    // (that is, one of the modules built into the worker runtime).\n    BUILTIN,\n    // Like builtin, but it's a module that is *only* resolvable from a builtin\n    // (like the `node-internal:...` modules)\n    BUILTIN_ONLY,\n    // Resolves only user-importable built-in modules (the kBuiltin bundle),\n    // excluding both worker bundle modules and internal-only modules. Used\n    // by user-facing APIs like process.getBuiltinModule() that must not\n    // expose internal modules or return user bundle overrides.\n    PUBLIC_BUILTIN,\n  };\n\n  enum class Source {\n    // The resolve originated from a static import statement.\n    STATIC_IMPORT,\n    // The resolve originated from a dynamic import statement.\n    DYNAMIC_IMPORT,\n    // The resolve originated from a CommonJS require() call.\n    REQUIRE,\n    // The resolve originated from an internal direct call to\n    // the ModuleRegistry.\n    INTERNAL,\n  };\n\n  // Used to report the status of a module resolution.\n  class ResolveStatus {\n   public:\n    ResolveStatus() = default;\n    KJ_DISALLOW_COPY_AND_MOVE(ResolveStatus);\n    virtual ~ResolveStatus() noexcept(false) {}\n\n    // Indicates that the module resolution was successful and a\n    // matching module was found in the registry.\n    virtual void found() {}\n\n    // Indicates that the module resolution failed because no matching\n    // module was found in the registry.\n    virtual void notFound() {}\n\n    // Indicates that the module resolution failed because an error\n    // occurred.\n    virtual void exception(kj::Exception&& exception) {}\n  };\n\n  // Called when a module is being resolved. The returned ResolveStatus\n  // object will be used to report the result of the resolution.\n  // It is guaranteed that isolate lock is not held during invocation.\n  virtual kj::Own<ResolveStatus> onResolveModule(\n      const Url& specifier, Context context, Source source) const {\n    static ResolveStatus nonopStatus;\n    return {&nonopStatus, kj::NullDisposer::instance};\n  }\n\n  // Called when a module is being resolved. The returned ResolveStatus\n  // object will be used to report the result of the resolution.\n  // It is guaranteed that isolate lock is not held during invocation.\n  virtual kj::Own<ResolveStatus> onResolveModule(\n      kj::StringPtr specifier, Context context, Source source) const {\n    static ResolveStatus nonopStatus;\n    return {&nonopStatus, kj::NullDisposer::instance};\n  }\n};\n\nstruct CompilationObserver {\n  virtual ~CompilationObserver() noexcept(false) {}\n\n  // see ModuleInfoCompileOption\n  enum class Option { BUNDLE, BUILTIN };\n\n  // Monitors behavior of compilation processes.\n\n  // Called at the start of ESM compilation.\n  // Returned value will be destroyed when module compilation finishes.\n  // It is guaranteed that isolate lock is held during invocation.\n  virtual kj::Own<void> onEsmCompilationStart(\n      v8::Isolate* isolate, kj::StringPtr name, Option option) const {\n    return kj::Own<void>();\n  }\n\n  // Called at the start of Script (e.g. non-ESM) compilation.\n  // Returned value will be destroyed when module compilation finishes.\n  // It is guaranteed that isolate lock is held during invocation.\n  virtual kj::Own<void> onScriptCompilationStart(\n      v8::Isolate* isolate, kj::Maybe<kj::StringPtr> name = kj::none) const {\n    return kj::Own<void>();\n  }\n\n  // Called at the start of wasm compilation.\n  // Returned value will be destroyed when module compilation finishes.\n  // It is guaranteed that isolate lock is held during invocation.\n  virtual kj::Own<void> onWasmCompilationStart(v8::Isolate* isolate, size_t codeSize) const {\n    return kj::Own<void>();\n  }\n\n  // Variation that is called at the start of wasm compilation from cache.\n  // Returned value will be destroyed when module compilation finishes.\n  // It is guaranteed that isolate lock is held during invocation.\n  virtual kj::Own<void> onWasmCompilationFromCacheStart(v8::Isolate* isolate) const {\n    return kj::Own<void>();\n  }\n\n  // Called at the start of json module parsing.\n  // Returned value will be destroyed when parsing completes.\n  // It is guaranteed that isolate lock is held during invocation.\n  virtual kj::Own<void> onJsonCompilationStart(v8::Isolate* isolate, size_t inputSize) const {\n    return kj::Own<void>();\n  }\n\n  virtual void onCompileCacheFound(v8::Isolate* isolate) const {}\n  virtual void onCompileCacheRejected(v8::Isolate* isolate) const {}\n  virtual void onCompileCacheGenerated(v8::Isolate* isolate) const {}\n  virtual void onCompileCacheGenerationFailed(v8::Isolate* isolate) const {}\n};\n\nstruct InternalExceptionObserver {\n  virtual ~InternalExceptionObserver() noexcept(false) {}\n\n  struct Detail {\n    bool isInternal;\n    bool isFromRemote;\n    bool isDurableObjectReset;\n    using InternalErrorId = kj::FixedArray<char, 24>;\n    kj::Maybe<InternalErrorId> internalErrorId;\n  };\n\n  // Called when an internal exception is created (see exceptionToJs).\n  // Used to collect metrics on various internal error conditions.\n  virtual void reportInternalException(const kj::Exception&, Detail detail) {}\n};\n\nWD_STRONG_BOOL(IsCodeLike);\n\nstruct IsolateObserver: public CompilationObserver,\n                        public InternalExceptionObserver,\n                        public ResolveObserver {\n  virtual ~IsolateObserver() noexcept(false) {}\n\n  // Called when eval(), new Function(), or similar dynamic code generation\n  // is performed. Note that the source here may not be a string if isCodeLike\n  // is YES.\n  virtual void onDynamicEval(\n      v8::Local<v8::Context> context, v8::Local<v8::Value> source, IsCodeLike isCodeLike) {\n    // Default is to do nothing.\n  }\n};\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/promise-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"jsg-test.h\"\n\nnamespace workerd::jsg::test {\nnamespace {\n\nV8System v8System;\n\nint promiseTestResult = 0;\nkj::String catchTestResult;\n\nstruct PromiseContext: public jsg::Object, public jsg::ContextGlobal {\n  Promise<kj::String> makePromise(jsg::Lock& js) {\n    auto [p, r] = js.newPromiseAndResolver<int>();\n    resolver = kj::mv(r);\n    return p.then(js, [](jsg::Lock&, int i) { return i * 2; })\n        .then(js, [](jsg::Lock& js, int i) {\n      return js.resolvedPromise(i + 2);\n    }).then(js, [](jsg::Lock& js, int i) { return kj::str(i); });\n  }\n\n  void resolvePromise(Lock& js, int i) {\n    KJ_ASSERT_NONNULL(resolver).resolve(js, kj::mv(i));\n  }\n\n  void setResult(jsg::Lock& js, Promise<kj::String> promise) {\n    // Throwing away the result of `.then()` doesn't cancel it!\n    promise.then(js, [](jsg::Lock&, kj::String str) {\n      promiseTestResult = str.parseAs<int>();\n    }).then(js, [](jsg::Lock&) { promiseTestResult += 60000; });\n  }\n\n  void catchIt(jsg::Lock& js, Promise<int> promise) {\n    promise\n        .catch_(js,\n            [](jsg::Lock& js, Value value) -> int {\n      JSG_FAIL_REQUIRE(Error, kj::str(value.getHandle(js)));\n    })\n        .then(js, [](jsg::Lock& js, int i) {\n      KJ_FAIL_REQUIRE(\"shouldn't get here\");\n      return kj::str(\"nope\");\n    }, [](jsg::Lock& js, Value value) {\n      return kj::str(value.getHandle(js));\n    }).then(js, [](jsg::Lock&, kj::String s) { catchTestResult = kj::mv(s); });\n  }\n\n  Promise<kj::String> makeRejected(jsg::Lock& js, jsg::Value exception) {\n    return js.rejectedPromise<kj::String>(kj::mv(exception));\n  }\n\n  Promise<kj::String> makeRejectedKj(jsg::Lock& js) {\n    return js.rejectedPromise<kj::String>(JSG_KJ_EXCEPTION(FAILED, TypeError, \"bar\"));\n  }\n\n  void testConsumeResolved(jsg::Lock& js) {\n    auto [promise, resolver] = js.newPromiseAndResolver<int>();\n    KJ_EXPECT(promise.tryConsumeResolved(js) == kj::none);\n    resolver.resolve(js, 123);\n    KJ_EXPECT(KJ_ASSERT_NONNULL(promise.tryConsumeResolved(js)) == 123);\n\n    KJ_EXPECT(\n        js.rejectedPromise<kj::String>(v8StrIntern(js.v8Isolate, \"foo\")).tryConsumeResolved(js) ==\n        kj::none);\n  }\n\n  void whenResolved(jsg::Lock& js, jsg::Promise<int> promise) {\n    // The returned promise should resolve to undefined.\n\n    uint resolved = 0;\n\n    auto handle = promise.whenResolved(js).then(js, [&resolved](jsg::Lock&) {\n      resolved++;\n    }).consumeHandle(js);\n\n    promise.then(js, [&resolved](jsg::Lock&, int v) {\n      KJ_ASSERT(v == 1);\n      resolved++;\n    });\n\n    js.runMicrotasks();\n    KJ_ASSERT(resolved == 2);\n\n    {\n      KJ_ASSERT(handle->State() == v8::Promise::PromiseState::kFulfilled);\n      auto result = handle->Result();\n      KJ_ASSERT(!result.IsEmpty());\n      KJ_ASSERT(result->IsUndefined());\n    }\n  }\n\n  int thenable(jsg::Lock& js, jsg::Promise<int> promise) {\n    int result = 0;\n    promise.then(js, [&result](jsg::Lock& js, int val) { result = val; });\n    js.runMicrotasks();\n    return result;\n  }\n\n  JSG_RESOURCE_TYPE(PromiseContext) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(promise, makePromise);\n    JSG_METHOD(resolvePromise);\n    JSG_METHOD(setResult);\n    JSG_METHOD(catchIt);\n\n    JSG_METHOD(makeRejected);\n    JSG_METHOD(makeRejectedKj);\n\n    JSG_METHOD(testConsumeResolved);\n    JSG_METHOD(whenResolved);\n\n    JSG_METHOD(thenable);\n  }\n\n  kj::Maybe<Promise<int>::Resolver> resolver;\n};\nJSG_DECLARE_ISOLATE_TYPE(PromiseIsolate, PromiseContext);\n\nKJ_TEST(\"jsg::Promise<T>\") {\n  Evaluator<PromiseContext, PromiseIsolate> e(v8System);\n\n  e.expectEval(\"setResult(promise.then(i => i + 1 /* oops, i is a string */));\\n\"\n               \"resolvePromise(123)\",\n      \"undefined\", \"undefined\");\n\n  KJ_EXPECT(promiseTestResult == 0);\n\n  e.runMicrotasks();\n\n  KJ_EXPECT(promiseTestResult == 62481);\n}\n\nKJ_TEST(\"jsg::Promise<T> exception catching\") {\n  Evaluator<PromiseContext, PromiseIsolate> e(v8System);\n\n  {\n    e.expectEval(\"catchIt(Promise.reject('foo'))\", \"undefined\", \"undefined\");\n\n    KJ_EXPECT(catchTestResult == nullptr);\n\n    e.runMicrotasks();\n\n    KJ_EXPECT(catchTestResult == \"Error: foo\");\n    catchTestResult = nullptr;\n  }\n\n  {\n    e.expectEval(\"catchIt(makeRejected(123))\", \"undefined\", \"undefined\");\n\n    KJ_EXPECT(catchTestResult == nullptr);\n\n    e.runMicrotasks();\n\n    KJ_EXPECT(catchTestResult == \"Error: 123\");\n    catchTestResult = nullptr;\n  }\n\n  {\n    e.expectEval(\"catchIt(makeRejectedKj())\", \"undefined\", \"undefined\");\n\n    KJ_EXPECT(catchTestResult == nullptr);\n\n    e.runMicrotasks();\n\n    KJ_EXPECT(catchTestResult == \"Error: TypeError: bar\");\n    catchTestResult = nullptr;\n  }\n\n  { e.expectEval(\"testConsumeResolved()\", \"undefined\", \"undefined\"); }\n}\n\nKJ_TEST(\"whenResolved\") {\n  Evaluator<PromiseContext, PromiseIsolate> e(v8System);\n\n  e.expectEval(\"whenResolved(Promise.resolve(1))\", \"undefined\", \"undefined\");\n}\n\nKJ_TEST(\"thenable\") {\n  static const auto config = JsgConfig{\n    .unwrapCustomThenables = true,\n  };\n\n  struct ThenableConfig {\n    operator const JsgConfig&() const {\n      return config;\n    }\n  };\n\n  Evaluator<PromiseContext, PromiseIsolate, ThenableConfig> e(v8System);\n\n  e.expectEval(\"thenable({ then(res) { res(123) } })\", \"number\", \"123\");\n}\n\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/promise.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"async-context.h\"\n#include \"jsg.h\"\n\nnamespace workerd::jsg {\n\nnamespace {\ntemplate <typename T>\nv8::Local<T> getLocal(v8::Isolate* isolate, v8::Global<T>& global) {\n  if (!global.IsEmpty()) {\n    return global.Get(isolate);\n  }\n  return v8::Local<T>();\n};\n\nkj::Maybe<Ref<AsyncContextFrame>> getFrameRef(jsg::Lock& js) {\n  return AsyncContextFrame::current(js).map(\n      [](AsyncContextFrame& frame) -> Ref<AsyncContextFrame> { return frame.addRef(); });\n}\n\nkj::Maybe<AsyncContextFrame&> tryGetFrame(kj::Maybe<Ref<AsyncContextFrame>>& maybeFrame) {\n  return maybeFrame.map(\n      [](Ref<AsyncContextFrame>& frame) -> AsyncContextFrame& { return *frame.get(); });\n}\n}  // namespace\n\nUnhandledRejectionHandler::UnhandledRejection::UnhandledRejection(jsg::Lock& js,\n    jsg::V8Ref<v8::Promise> promise,\n    jsg::Value value,\n    v8::Local<v8::Message> message)\n    : hash(kj::hashCode(promise.getHandle(js)->GetIdentityHash())),\n      promise(js.v8Isolate, promise.getHandle(js)),\n      value(js.v8Isolate, value.getHandle(js)),\n      message(js.v8Isolate, message),\n      asyncContextFrame(getFrameRef(js)) {}\n\nvoid UnhandledRejectionHandler::report(\n    Lock& js, v8::PromiseRejectEvent event, jsg::V8Ref<v8::Promise> promise, jsg::Value value) {\n  js.tryCatch([&] {\n    switch (event) {\n      case v8::PromiseRejectEvent::kPromiseRejectWithNoHandler: {\n        rejectedWithNoHandler(js, kj::mv(promise), kj::mv(value));\n        return;\n      }\n      case v8::PromiseRejectEvent::kPromiseHandlerAddedAfterReject: {\n        handledAfterRejection(js, kj::mv(promise));\n        return;\n      }\n      case v8::PromiseRejectEvent::kPromiseRejectAfterResolved: {\n        break;\n      }\n      case v8::PromiseRejectEvent::kPromiseResolveAfterResolved: {\n        break;\n      }\n    }\n  }, [&](Value exception) {\n    // Exceptions here should be rare but possible. Any errors that occur\n    // here are likely fatal to the worker. This handling helps us avoid\n    // crashing. We'll log the error hand continue.\n    if (js.areWarningsLogged()) {\n      js.logWarning(kj::str(\"There was an error while reporting an unhandled promise rejection: \",\n          exception.getHandle(js)));\n    }\n  });\n}\n\nUnhandledRejectionHandler::UnhandledRejection::~UnhandledRejection() {\n  if (promise.IsWeak()) {\n    promise.ClearWeak();\n  }\n  if (value.IsWeak()) {\n    value.ClearWeak();\n  }\n}\n\nvoid UnhandledRejectionHandler::clear() {\n  warnedRejections.clear();\n  unhandledRejections.clear();\n}\n\nvoid UnhandledRejectionHandler::rejectedWithNoHandler(\n    jsg::Lock& js, jsg::V8Ref<v8::Promise> promise, jsg::V8Ref<v8::Value> value) {\n  auto message = v8::Exception::CreateMessage(js.v8Isolate, value.getHandle(js));\n\n  // It's not yet clear under what conditions it happens, but this can be called\n  // twice with the same promise. It really shouldn't happen in the regular cases\n  // but we address the edge case by using upsert and just replacing the existing\n  // value and message when it does.\n\n  unhandledRejections.upsert(\n      UnhandledRejection(js, kj::mv(promise), kj::mv(value), kj::mv(message)),\n      [&](UnhandledRejection& existing, UnhandledRejection&& replacement) {\n    // Replacing the promise here is defensive, since they have the same hash\n    // it *should* be the same promise, but let's be sure. We don't need to\n    // assert here because the book keeping on this is not critical.\n    existing = kj::mv(replacement);\n  });\n\n  ensureProcessingWarnings(js);\n}\n\nvoid UnhandledRejectionHandler::handledAfterRejection(\n    jsg::Lock& js, jsg::V8Ref<v8::Promise> promise) {\n  // If an unhandled rejection is found in the table, then all we need to do is erase it.\n  // If it's not found, then we'll skip on to the next step of determining if we've already\n  // emitted an unhandled rejection warning about this promise to determine if we need to\n  // emit another warning indicating that it's been handled.\n  KJ_DEFER(ensureProcessingWarnings(js));\n\n  HashedPromise key(promise.getHandle(js));\n\n  if (unhandledRejections.eraseMatch(key)) {\n    return;\n  }\n\n  KJ_IF_SOME(item, warnedRejections.find(key)) {\n    auto promise = getLocal(js.v8Isolate, item.promise);\n    if (!promise.IsEmpty()) {\n      AsyncContextFrame::Scope scope(js, tryGetFrame(item.asyncContextFrame));\n      handler(js, v8::kPromiseHandlerAddedAfterReject, jsg::HashableV8Ref(js.v8Isolate, promise),\n          js.v8Ref(js.v8Undefined()));\n    }\n    warnedRejections.release(item);\n  }\n}\n\nvoid UnhandledRejectionHandler::ensureProcessingWarnings(jsg::Lock& js) {\n  if (scheduled) {\n    return;\n  }\n  scheduled = true;\n  if (useMicrotasksCompletedCallback) {\n    // Schedule processing to run after the microtask checkpoint completes.\n    // This ensures that promise chains like `.then().catch()` have fully settled\n    // before we decide a rejection is unhandled. Using a microtask would race\n    // with V8's internal promise adoption microtasks and fire too early.\n    // See https://github.com/cloudflare/workerd/issues/6020\n    js.v8Isolate->AddMicrotasksCompletedCallback(\n        &UnhandledRejectionHandler::onMicrotasksCompleted, this);\n    // Ensure we get another microtask checkpoint to deliver the callback even if\n    // we're already past the current one.\n    js.requestExtraMicrotaskCheckpoint();\n  } else {\n    js.resolvedPromise().then(js, [this](jsg::Lock& js) { processWarnings(js); });\n  }\n}\n\nvoid UnhandledRejectionHandler::onMicrotasksCompleted(v8::Isolate* isolate, void* data) {\n  auto* handler = static_cast<UnhandledRejectionHandler*>(data);\n  KJ_DEFER(isolate->RemoveMicrotasksCompletedCallback(\n      &UnhandledRejectionHandler::onMicrotasksCompleted, data));\n  auto& js = Lock::from(isolate);\n  KJ_TRY {\n    handler->processWarnings(js);\n\n    // Ensure microtasks scheduled by unhandledrejection handlers run promptly.\n    js.requestExtraMicrotaskCheckpoint();\n  }\n  KJ_CATCH(exception) {\n    handler->scheduled = false;\n    KJ_LOG(ERROR, \"uncaught exception while processing unhandled rejections\", exception);\n  }\n}\n\nvoid UnhandledRejectionHandler::processWarnings(jsg::Lock& js) {\n  scheduled = false;\n  warnedRejections.eraseAll([](auto& value) { return !value.isAlive(); });\n\n  while (unhandledRejections.size() > 0) {\n    auto entry = unhandledRejections.release(*unhandledRejections.begin());\n\n    if (!entry.isAlive()) {\n      continue;\n    }\n\n    auto promise = getLocal(js.v8Isolate, entry.promise);\n    auto value = getLocal(js.v8Isolate, entry.value);\n\n    AsyncContextFrame::Scope scope(js, tryGetFrame(entry.asyncContextFrame));\n\n    // Most of the time it shouldn't be found but there are times where it can\n    // be duplicated -- such as when a promise gets rejected multiple times.\n    // Check quickly before inserting to avoid a crash.\n    // Keep strong refs through dispatch, then downgrade to weak to avoid leaks.\n    entry.promise.SetWeak();\n    entry.value.SetWeak();\n    warnedRejections.upsert(\n        kj::mv(entry), [](UnhandledRejection& existing, UnhandledRejection&& replacement) {\n      // We're just going to ignore if the unhandled rejection was already here.\n    });\n\n    js.tryCatch([&] {\n      handler(js, v8::kPromiseRejectWithNoHandler, jsg::HashableV8Ref(js.v8Isolate, promise),\n          js.v8Ref(value));\n    }, [&](Value exception) {\n      // If any exceptions occur while reporting the event, we will log them\n      // but otherwise ignore them. We do not want such errors to be fatal here.\n      if (js.areWarningsLogged()) {\n        js.logWarning(\n            kj::str(\"Exception while logging unhandled rejection:\", exception.getHandle(js)));\n      }\n    });\n  }\n}\n\nvoid UnhandledRejectionHandler::UnhandledRejection::visitForMemoryInfo(\n    MemoryTracker& tracker) const {\n  tracker.trackField(\"asyncContextFrame\", asyncContextFrame);\n}\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/promise.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"jsg.h\"\n#include \"util.h\"\n#include \"wrappable.h\"\n\n#include <v8-function.h>\n#include <v8-promise.h>\n\n#include <kj/async.h>\n#include <kj/table.h>\n\nnamespace workerd::jsg {\n\n// =======================================================================================\n// Utilities for wrapping arbitrary C++ in an opaque way. See wrapOpaque().\n//\n// At present this is used privately in the Promise implementation, but we could consider making\n// wrapOpaque() more public if it is useful.\n\ntemplate <typename T, bool = isGcVisitable<T>()>\nstruct OpaqueWrappable;\n\nstruct OpaqueWrappableBase: public Wrappable {\n  kj::StringPtr jsgGetMemoryName() const override final {\n    return \"OpaqueWrappable\"_kjc;\n  }\n  void jsgGetMemoryInfo(MemoryTracker& tracker) const override final {\n    Wrappable::jsgGetMemoryInfo(tracker);\n  }\n};\n\ntemplate <typename T>\nstruct OpaqueWrappable<T, false>: public OpaqueWrappableBase {\n  // Used to implement wrapOpaque().\n\n  OpaqueWrappable(T&& value): value(kj::mv(value)) {}\n\n  T value;\n  bool movedAway = false;\n\n  size_t jsgGetMemorySelfSize() const override final {\n    return sizeof(OpaqueWrappable);\n  }\n};\n\ntemplate <typename T>\nstruct OpaqueWrappable<T, true>: public OpaqueWrappable<T, false> {\n  // When T is GC-visitable, make sure to implement visitation.\n\n  using OpaqueWrappable<T, false>::OpaqueWrappable;\n\n  void jsgVisitForGc(GcVisitor& visitor) override {\n    if (!this->movedAway) {\n      visitor.visit(this->value);\n    }\n  }\n};\n\n// Create a JavaScript value that wraps `t` in an opaque way. JS code will see this as an empty\n// object, as if created by `{}`, but C++ code can unwrap the handle with `unwrapOpaque()`.\n//\n// If `T` is a type that can be passed to GcVisitor::visit(), then it will be visited whenever\n// the opaque handle is found to be reachable.\n//\n// Generally, the opaque handle should not actually be passed to the application at all. This\n// is useful in cases where the producer and consumer are both C++ code, but V8 requires that\n// a handle be used for some reason. For example, this is used to pass C++ values through V8\n// Promises.\n//\n// Opaque-wrapping of `V8Ref<T>` is explicitly disallowed to avoid waste. Just use the handle\n// directly in this case. If you really want to wrap a V8Ref opaquely, wrap it in a struct of\n// your own first. (Don't forget to implement `visitForGc()`.)\ntemplate <typename T>\nv8::Local<v8::Value> wrapOpaque(v8::Local<v8::Context> context, T&& t) {\n  static_assert(!kj::isReference<T>());\n  static_assert(!isV8Ref<T>(), \"no need to opaque-wrap regular JavaScript values\");\n  static_assert(!isV8Local<T>(), \"can't opaque-wrap non-persistent handles\");\n\n  auto wrapped = kj::refcounted<OpaqueWrappable<T>>(kj::mv(t));\n  return wrapped->attachOpaqueWrapper(context, isGcVisitable<T>());\n}\n\n// Unwraps a handle created using `wrapOpaque()`. This consumes (moves away) the underlying\n// value, so can only be called once. Throws if the handle is the wrong type or has already been\n// consumed previously.\ntemplate <typename T>\nT unwrapOpaque(v8::Isolate* isolate, v8::Local<v8::Value> handle) {\n  static_assert(!kj::isReference<T>());\n  static_assert(!isV8Ref<T>(), \"no need to opaque-wrap regular JavaScript values\");\n  static_assert(!isV8Local<T>(), \"can't opaque-wrap non-persistent handles\");\n\n  Wrappable& wrappable = KJ_ASSERT_NONNULL(Wrappable::tryUnwrapOpaque(isolate, handle));\n  OpaqueWrappable<T>* holder = dynamic_cast<OpaqueWrappable<T>*>(&wrappable);\n  KJ_ASSERT(holder != nullptr);\n  KJ_ASSERT(!holder->movedAway);\n  holder->movedAway = true;\n  return kj::mv(holder->value);\n}\n\n// Unwraps a handle created using `wrapOpaque()`, without consuming the value.  Throws if the\n// handle is the wrong type or has already been consumed previously.\ntemplate <typename T>\nT& unwrapOpaqueRef(v8::Isolate* isolate, v8::Local<v8::Value> handle) {\n  static_assert(!kj::isReference<T>());\n  static_assert(!isV8Ref<T>(), \"no need to opaque-wrap regular JavaScript values\");\n  static_assert(!isV8Local<T>(), \"can't opaque-wrap non-persistent handles\");\n\n  Wrappable& wrappable = KJ_ASSERT_NONNULL(Wrappable::tryUnwrapOpaque(isolate, handle));\n  OpaqueWrappable<T>* holder = dynamic_cast<OpaqueWrappable<T>*>(&wrappable);\n  KJ_ASSERT(holder != nullptr);\n  KJ_ASSERT(!holder->movedAway);\n  return holder->value;\n}\n\n// Destroys the value contained by an opaque handle, without returning it. This is equivalent\n// to calling unwrapOpaque<T>() and dropping the result, except that if the handle is the wrong\n// type, this function silently does nothing rather than throw.\ntemplate <typename T>\nvoid dropOpaque(v8::Isolate* isolate, v8::Local<v8::Value> handle) {\n  static_assert(!kj::isReference<T>());\n  static_assert(!isV8Ref<T>());\n\n  KJ_IF_SOME(wrappable, Wrappable::tryUnwrapOpaque(isolate, handle)) {\n    OpaqueWrappable<T>* holder = dynamic_cast<OpaqueWrappable<T>*>(&wrappable);\n    if (holder != nullptr) {\n      holder->movedAway = true;\n      auto drop KJ_UNUSED = kj::mv(holder->value);\n    }\n  }\n}\n\n// =======================================================================================\n// Promise implementation\n\n// This type (opaque-wrapped) is the type of the \"data\" for a continuation callback. We have both\n// the success and error callbacks share the same \"data\" object so that both underlying C++\n// callbacks are proactively destroyed after one of the runs. Otherwise, we'd only destroy the\n// function that was called, while the other one would have to wait for GC, which may mean\n// keeping around C++ resources longer than necessary.\ntemplate <typename ThenFunc, typename CatchFunc>\nstruct ThenCatchPair {\n  ThenFunc thenFunc;\n  CatchFunc catchFunc;\n};\n\n// FunctionCallback implementing a C++ .then() continuation on a JS promise.\n//\n// We expect the input is already an opaque-wrapped value, args.Data() is an opaque-wrapped C++\n// function to execute, and we want to produce an opaque-wrapped output or Promise.\ntemplate <typename FuncPairType, bool isCatch, typename Input, typename Output>\nvoid promiseContinuation(const v8::FunctionCallbackInfo<v8::Value>& args) {\n  liftKj(args, [&]() {\n    auto isolate = args.GetIsolate();\n#ifdef KJ_DEBUG\n    // In debug mode only, we verify that the function hasn't captured any KJ heap objects without\n    // a IoOwn. We don't bother with this check in release mode because it's pretty deterministic,\n    // so it's likely to be caught in debug, and we'd like to avoid the extra overhead in releases.\n    DISALLOW_KJ_IO_DESTRUCTORS_SCOPE;\n#endif\n    auto funcPair = unwrapOpaque<FuncPairType>(isolate, args.Data());\n#ifdef KJ_DEBUG\n    kj::AllowAsyncDestructorsScope allowAsyncDestructors;\n#endif\n    auto callFunc = [&]() -> Output {\n      auto& js = Lock::from(isolate);\n      if constexpr (isCatch) {\n        // Exception from V8 is not expected to be opaque-wrapped. It's just a Value.\n        return funcPair.catchFunc(js, Value(isolate, args[0]));\n      } else if constexpr (isVoid<Input>()) {\n        return funcPair.thenFunc(js);\n      } else if constexpr (isV8Ref<Input>()) {\n        return funcPair.thenFunc(js, Input(isolate, args[0]));\n      } else {\n        return funcPair.thenFunc(js, unwrapOpaque<Input>(isolate, args[0]));\n      }\n    };\n    if constexpr (isVoid<Output>()) {\n      callFunc();\n    } else if constexpr (isPromise<Output>()) {\n      // Continuation returns Promise. We don't want to opaque-wrap that, we want to return it\n      // raw, so that the V8 Promise machinery will chain it.\n\n      // We cast the return value to v8::Local<v8::Value> so that it doesn't trigger liftKj()'s\n      // special handling of promises, where it tries to catch exceptions and merge them into the\n      // promise. We don't need to do this, because this is being called as a .then() which already\n      // catches exceptions and does the right thing.\n      return v8::Local<v8::Value>(callFunc().consumeHandle(Lock::from(isolate)));\n    } else if constexpr (isV8Ref<Output>()) {\n      return callFunc().getHandle(isolate);\n    } else {\n      return wrapOpaque(isolate->GetCurrentContext(), callFunc());\n    }\n  });\n}\n\n// Promise continuation that propagates the value or exception unmodified, but makes sure to\n// proactively destroy the ThenCatchPair.\ntemplate <typename FuncPairType, bool isCatch>\nvoid identityPromiseContinuation(const v8::FunctionCallbackInfo<v8::Value>& args) {\n  auto isolate = args.GetIsolate();\n  dropOpaque<FuncPairType>(isolate, args.Data());\n  if constexpr (isCatch) {\n    isolate->ThrowException(args[0]);\n  } else {\n    args.GetReturnValue().Set(args[0]);\n  }\n}\n\ntemplate <typename TypeWrapper>\nclass PromiseWrapper;\n\ntemplate <typename T>\nclass Promise {\n public:\n  static_assert(!kj::canConvert<T*, v8::Data*>(),\n      \"jsg::Promise<T> expects T to be an instantiable C++ type, not a JS heap type; use \"\n      \"jsg::Promise<jsg::V8Ref<T>> to represent a promise for a JavaScript heap object.\");\n\n  Promise(v8::Isolate* isolate, v8::Local<v8::Promise> v8Promise)\n      : v8Promise(V8Ref<v8::Promise>(isolate, v8Promise)) {}\n\n  Promise(decltype(nullptr)): v8Promise(kj::none) {}\n  // For use when you're declaring a local variable that will be initialized later.\n\n  void markAsHandled(Lock& js) {\n    auto promise = getInner(js);\n    promise->MarkAsHandled();\n    markedAsHandled = true;\n  }\n\n  // Attach a continuation function and error handler to be called when this promise\n  // is fulfilled. It is important to remember that then(...) can synchronously throw\n  // a JavaScript exception (and jsg::JsExceptionThrown) in certain cases.\n  template <typename Func, typename ErrorFunc>\n  PromiseForResult<Func, T, true> then(Lock& js, Func&& func, ErrorFunc&& errorFunc) {\n    using Output = ReturnType<Func, T, true>;\n    static_assert(kj::isSameType<Output, ReturnType<ErrorFunc, Value, true>>(),\n        \"functions passed to .then() must return exactly the same type\");\n\n    using FuncPair = ThenCatchPair<Func, ErrorFunc>;\n    return thenImpl<Output>(js, FuncPair{kj::fwd<Func>(func), kj::fwd<ErrorFunc>(errorFunc)},\n        &promiseContinuation<FuncPair, false, T, Output>,\n        &promiseContinuation<FuncPair, true, Value, Output>);\n  }\n\n  // Attach a continuation function to be called when this promise is fulfilled.\n  // It is important to remember that then(...) can synchronously throw\n  // a JavaScript exception (and jsg::JsExceptionThrown) in certain cases.\n  template <typename Func>\n  PromiseForResult<Func, T, true> then(Lock& js, Func&& func) {\n    using Output = ReturnType<Func, T, true>;\n\n    // HACK: The error function is never called, so it need not actually be a functor.\n    using FuncPair = ThenCatchPair<Func, bool>;\n    return thenImpl<Output>(js, FuncPair{kj::fwd<Func>(func), false},\n        &promiseContinuation<FuncPair, false, T, Output>,\n        &identityPromiseContinuation<FuncPair, true>);\n  }\n\n  template <typename ErrorFunc>\n  Promise<T> catch_(Lock& js, ErrorFunc&& errorFunc) {\n    static_assert(kj::isSameType<T, ReturnType<ErrorFunc, Value, true>>(),\n        \"function passed to .catch_() must return exactly the promise's type\");\n\n    // HACK: The non-error function is never called, so it need not actually be a functor.\n    using FuncPair = ThenCatchPair<bool, ErrorFunc>;\n    return thenImpl<T>(js, FuncPair{false, kj::fwd<ErrorFunc>(errorFunc)},\n        &identityPromiseContinuation<FuncPair, false>,\n        &promiseContinuation<FuncPair, true, Value, T>);\n  }\n\n  // whenResolved returns a new Promise<void> that resolves when this promise resolves,\n  // stopping the propagation of the resolved value. Unlike then(), calling whenResolved()\n  // does not consume the promise, and whenResolved() can be called multiple times,\n  // with each call creating a new branch off the original promise. Another key difference\n  // with whenResolved() is that the markAsHandled status will propagate to the new Promise<void>\n  // returned by whenResolved().\n  Promise<void> whenResolved(Lock& js) {\n    auto promise = Promise<void>(js.v8Isolate, getInner(js));\n    if (markedAsHandled) {\n      promise.markAsHandled(js);\n    }\n    return kj::mv(promise);\n  }\n\n  v8::Local<v8::Promise> consumeHandle(Lock& js) {\n    auto result = getInner(js);\n    v8Promise = kj::none;\n    return result;\n  }\n\n  // If the promise is resolved, return the result, consuming the Promise. If it is pending\n  // or rejected, returns null. This can be used as an optimization or in tests, but you must\n  // never rely on it for correctness.\n  kj::Maybe<T> tryConsumeResolved(Lock& js) {\n    return js.withinHandleScope([&]() -> kj::Maybe<T> {\n      auto handle =\n          KJ_REQUIRE_NONNULL(v8Promise, \"jsg::Promise can only be used once\").getHandle(js);\n      switch (handle->State()) {\n        case v8::Promise::kPending:\n        case v8::Promise::kRejected:\n          return kj::none;\n        case v8::Promise::kFulfilled:\n          v8Promise = kj::none;\n          return unwrapOpaque<T>(js.v8Isolate, handle->Result());\n      }\n    });\n  }\n\n  class Resolver {\n   public:\n    Resolver(v8::Isolate* isolate, v8::Local<v8::Promise::Resolver> v8Resolver)\n        : v8Resolver(isolate, kj::mv(v8Resolver)) {}\n\n    template <typename U = T, typename = kj::EnableIf<!isVoid<U>()>>\n    void resolve(Lock& js, kj::NoInfer<U>&& value) {\n      js.withinHandleScope([&] {\n        auto context = js.v8Context();\n        v8::Local<v8::Value> handle;\n        if constexpr (isV8Ref<U>()) {\n          handle = value.getHandle(js);\n        } else {\n          handle = wrapOpaque(context, kj::mv(value));\n        }\n        check(v8Resolver.getHandle(js)->Resolve(context, handle));\n      });\n    }\n\n    template <typename U = T, typename = kj::EnableIf<isVoid<U>()>>\n    void resolve(Lock& js) {\n      js.withinHandleScope(\n          [&] { check(v8Resolver.getHandle(js)->Resolve(js.v8Context(), js.v8Undefined())); });\n    }\n\n    void resolve(Lock& js, Promise&& promise) {\n      // Resolve to another Promise.\n      check(v8Resolver.getHandle(js)->Resolve(js.v8Context(), promise.consumeHandle(js)));\n    }\n\n    void reject(Lock& js, v8::Local<v8::Value> exception) {\n      js.withinHandleScope(\n          [&] { check(v8Resolver.getHandle(js)->Reject(js.v8Context(), exception)); });\n    }\n\n    void reject(Lock& js, kj::Exception exception, ExceptionToJsOptions options = {}) {\n      reject(js, exceptionToJs(js.v8Isolate, kj::mv(exception), options));\n    }\n\n    Resolver addRef(Lock& js) {\n      return {js.v8Isolate, v8Resolver.getHandle(js)};\n    }\n    void visitForGc(GcVisitor& visitor) {\n      visitor.visit(v8Resolver);\n    }\n\n    JSG_MEMORY_INFO(Resolver) {\n      tracker.trackField(\"resolver\", v8Resolver);\n    }\n\n   private:\n    V8Ref<v8::Promise::Resolver> v8Resolver;\n    friend class MemoryTracker;\n  };\n\n  void visitForGc(GcVisitor& visitor) {\n    visitor.visit(v8Promise);\n  }\n\n  JSG_MEMORY_INFO(Promise) {\n    KJ_IF_SOME(promise, v8Promise) {\n      tracker.trackField(\"promise\", promise);\n    }\n  }\n\n  // Ths is for testing/diagnostics purposes only.\n  enum class State {\n    PENDING = v8::Promise::kPending,\n    FULFILLED = v8::Promise::kFulfilled,\n    REJECTED = v8::Promise::kRejected,\n    CONSUMED = 3  // Not a real state; indicates the Promise has been consumed.\n  };\n  State getState(Lock& js) {\n    KJ_IF_SOME(promise, v8Promise) {\n      return static_cast<State>(promise.getHandle(js)->State());\n    } else {\n      return State::CONSUMED;\n    }\n  }\n\n private:\n  kj::Maybe<V8Ref<v8::Promise>> v8Promise;\n  bool markedAsHandled = false;\n\n  v8::Local<v8::Promise> getInner(Lock& js) {\n    return KJ_REQUIRE_NONNULL(v8Promise, \"jsg::Promise can only be used once\").getHandle(js);\n  }\n\n  template <typename U = T, typename = kj::EnableIf<!isVoid<U>()>()>\n  Promise(Lock& js, kj::NoInfer<U>&& value) {\n    js.withinHandleScope([&] {\n      auto context = js.v8Context();\n      auto resolver = check(v8::Promise::Resolver::New(context));\n      v8::Local<v8::Value> handle;\n      if constexpr (isV8Ref<U>()) {\n        handle = value.getHandle(js);\n      } else {\n        handle = wrapOpaque(context, kj::mv(value));\n      };\n      check(resolver->Resolve(context, handle));\n      v8Promise.emplace(js.v8Isolate, resolver->GetPromise());\n    });\n  }\n\n  template <typename U = T, typename = kj::EnableIf<isVoid<U>()>()>\n  explicit Promise(Lock& js) {\n    js.withinHandleScope([&] {\n      auto context = js.v8Context();\n      auto resolver = check(v8::Promise::Resolver::New(context));\n      check(resolver->Resolve(context, js.v8Undefined()));\n      v8Promise.emplace(js.v8Isolate, resolver->GetPromise());\n    });\n  }\n\n  template <typename Result, typename FuncPair>\n  MaintainPromise<Result> thenImpl(Lock& js,\n      FuncPair&& funcPair,\n      v8::FunctionCallback thenCallback,\n      v8::FunctionCallback errCallback) {\n    return js.withinHandleScope([&] {\n      auto context = js.v8Context();\n\n      auto funcPairHandle = wrapOpaque(context, kj::mv(funcPair));\n\n      auto then = check(v8::Function::New(\n          context, thenCallback, funcPairHandle, 1, v8::ConstructorBehavior::kThrow));\n\n      auto errThen = check(v8::Function::New(\n          context, errCallback, funcPairHandle, 1, v8::ConstructorBehavior::kThrow));\n\n      return MaintainPromise<Result>(\n          js.v8Isolate, check(consumeHandle(js)->Then(context, then, errThen)));\n    });\n  }\n\n  friend class Lock;\n  template <typename TypeWrapper>\n  friend class PromiseWrapper;\n  friend class MemoryTracker;\n};\n\ntemplate <typename T>\nclass Promise<Promise<T>> {\n  static_assert(sizeof(T*) == 0, \"Promise<Promise<T>> is invalid; use Promise<T> instead\");\n};\n\ntemplate <typename T>\nclass Promise<kj::Promise<T>> {\n  static_assert(sizeof(T*) == 0, \"jsg::Promise<kj::Promise<T>> is illegal; you need a IoOwn!\");\n};\n\ntemplate <typename T>\nstruct PromiseResolverPair {\n  Promise<T> promise;\n  Promise<T>::Resolver resolver;\n\n  JSG_MEMORY_INFO(PromiseResolverPair) {\n    tracker.trackField(\"promise\", promise);\n    tracker.trackField(\"resolver\", resolver);\n  }\n};\n\ntemplate <typename T>\nPromiseResolverPair<T> Lock::newPromiseAndResolver() {\n  return withinHandleScope([&]() -> PromiseResolverPair<T> {\n    auto resolver = check(v8::Promise::Resolver::New(v8Context()));\n    auto promise = resolver->GetPromise();\n    return {{v8Isolate, promise}, {v8Isolate, resolver}};\n  });\n}\n\ntemplate <typename T>\ninline Promise<T> Lock::resolvedPromise(T&& value) {\n  return Promise<T>(*this, kj::fwd<T>(value));\n}\ninline Promise<void> Lock::resolvedPromise() {\n  return Promise<void>(*this);\n}\n\ntemplate <typename T>\nPromise<T> Lock::rejectedPromise(v8::Local<v8::Value> exception) {\n  auto [promise, resolver] = newPromiseAndResolver<T>();\n  resolver.reject(*this, exception);\n  return kj::mv(promise);\n}\n\ntemplate <typename T>\nPromise<T> Lock::rejectedPromise(jsg::Value exception) {\n  return withinHandleScope([&] { return rejectedPromise<T>(exception.getHandle(*this)); });\n}\n\ntemplate <typename T>\nPromise<T> Lock::rejectedPromise(kj::Exception&& exception, ExceptionToJsOptions options) {\n  return withinHandleScope(\n      [&] { return rejectedPromise<T>(exceptionToJs(kj::mv(exception), options)); });\n}\n\ntemplate <class Func>\nPromiseForResult<Func, void, false> Lock::evalNow(Func&& func) {\n  using Result = RemovePromise<ReturnType<Func, void>>;\n  v8::TryCatch tryCatch(v8Isolate);\n  try {\n    if constexpr (isPromise<ReturnType<Func, void>>()) {\n      return func();\n    } else {\n      return resolvedPromise<Result>(func());\n    }\n  } catch (jsg::JsExceptionThrown&) {\n    if (tryCatch.HasCaught() && tryCatch.CanContinue()) {\n      return rejectedPromise<Result>(tryCatch.Exception());\n    } else {\n      // Probably TerminateExecution() called.\n      tryCatch.ReThrow();\n      throw;\n    }\n  } catch (kj::Exception& e) {\n    return rejectedPromise<Result>(kj::mv(e));\n  } catch (std::exception& exception) {\n    return rejectedPromise<Result>(makeInternalError(v8Isolate, exception.what()));\n  } catch (...) {\n    return rejectedPromise<Result>(makeInternalError(\n        v8Isolate, kj::str(\"caught unknown exception of type: \", kj::getCaughtExceptionType())));\n  }\n}\n\n// -----------------------------------------------------------------------------\n\n// Continuation function that converts a promised C++ value into a JavaScript value.\ntemplate <typename TypeWrapper, typename Input>\nvoid thenWrap(const v8::FunctionCallbackInfo<v8::Value>& args) {\n  if constexpr (isVoid<Input>()) {\n    // No wrapping needed. Note that we still attach `thenWrap` to the promise chain only because\n    // we use `args.data` to prevent the object from being GC'ed while the promise is still\n    // executing.\n    args.GetReturnValue().SetUndefined();\n  } else if constexpr (isV8Ref<Input>()) {\n    // Similarly, no unwrapping needed.\n    args.GetReturnValue().Set(args[0]);\n  } else {\n    liftKj(args, [&]() {\n      v8::Isolate* isolate = args.GetIsolate();\n      auto& wrapper = TypeWrapper::from(isolate);\n      auto context = isolate->GetCurrentContext();\n      auto& lock = Lock::from(isolate);\n      return wrapper.wrap(lock, context, kj::none, unwrapOpaque<Input>(isolate, args[0]));\n    });\n  }\n}\n\n// Continuation function that converts a promised JavaScript value into a C++ value.\ntemplate <typename TypeWrapper, typename Output>\nvoid thenUnwrap(const v8::FunctionCallbackInfo<v8::Value>& args) {\n  liftKj(args, [&]() {\n    v8::Isolate* isolate = args.GetIsolate();\n    auto& wrapper = TypeWrapper::from(isolate);\n    auto context = isolate->GetCurrentContext();\n    auto& js = Lock::from(isolate);\n    return wrapOpaque(context,\n        wrapper.template unwrap<Output>(\n            js, context, args[0], TypeErrorContext::promiseResolution()));\n  });\n}\n\n// TypeWrapper mixin for Promise.\ntemplate <typename TypeWrapper>\nclass PromiseWrapper {\n public:\n  // The constructor here is a bit of a hack. The config is optional and might not be a JsgConfig\n  // object (or convertible to a JsgConfig) if is provided. However, because of the way TypeWrapper\n  // inherits PromiseWrapper, we always end up passing a config option (which might be\n  // std::nullptr_t). The getConfig allows us to handle any case using reasonable defaults.\n  PromiseWrapper(const auto& config): config(getConfig(config)) {}\n\n  template <typename T>\n  static constexpr const char* getName(Promise<T>*) {\n    return \"Promise\";\n  }\n\n  template <typename T>\n  v8::Local<v8::Promise> wrap(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      Promise<T>&& promise) {\n    // Add a .then() to unwrap the value (i.e. convert C++ value to JavaScript).\n    //\n    // We use `creator` as the `data` value for this continuation so that the creator object\n    // cannot be GC'ed while the callback still exists. This gives us the KJ-style guarantee that\n    // the object whose method returned the promise will not be destroyed while the promise is\n    // still executing.\n    auto markedAsHandled = promise.markedAsHandled;\n    auto then = check(v8::Function::New(context, &thenWrap<TypeWrapper, T>, creator.orDefault({}),\n        1, v8::ConstructorBehavior::kThrow));\n    auto ret = check(promise.consumeHandle(js)->Then(context, then));\n    // Although we added a .then() to the promise to translate the value to JavaScript, we would\n    // like things to behave as if the C++ code returned this Promise directly to JavaScript. In\n    // particular, if the C++ code marked the Promise handled, then the derived JavaScript promise\n    // ought to be marked as handled as well.\n    if (markedAsHandled) {\n      ret->MarkAsHandled();\n    }\n\n    return ret;\n  }\n\n  template <typename T>\n  kj::Maybe<Promise<T>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      Promise<T>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (handle->IsPromise()) {\n      auto promise = handle.As<v8::Promise>();\n      if constexpr (!isVoid<T>() && !isV8Ref<T>()) {\n        // Add a .then() to unwrap the promise's resolution (i.e. convert it from JS to C++).\n        // Note that we don't need to handle the rejection case here as there is no wrapping\n        // applied to exception values, so we just let it propagate through.\n        //\n        // TODO(perf): We could in theory check if promise->State() is kFulfilled and, in that\n        //   case, pull out promise->Result(), unwrap it, and make a new immediate promise.\n        //   Similarly in `wrap()`. Not clear if the added complexity is worth it, though.\n        auto then = check(v8::Function::New(\n            context, &thenUnwrap<TypeWrapper, T>, {}, 1, v8::ConstructorBehavior::kThrow));\n        promise = check(promise->Then(context, then));\n      }\n      return Promise<T>(js.v8Isolate, promise);\n    } else {\n      // Input is a resolved value (not a promise). Try to unwrap it now.\n\n      // If the input is an object that is not a promise, there's a chance it is a custom\n      // thenable (and object with a then method intended to be used as a promise). If that\n      // is the case, then we can handle the thenable by resolving it to a promise then\n      // unwrapping that promise.\n      // Unfortunately this needs to be gated by a compatibility flag because there are\n      // existing workers that appear to rely on the old behavior -- although it's not clear\n      // if those workers actually work the way they were intended to.\n      if (config.unwrapCustomThenables && isThenable(context, handle)) {\n        auto paf = check(v8::Promise::Resolver::New(context));\n        check(paf->Resolve(context, handle));\n        return tryUnwrap(\n            js, context, paf->GetPromise(), static_cast<Promise<T>*>(nullptr), parentObject);\n      }\n\n      if constexpr (isVoid<T>()) {\n        // When expecting Promise<void>, we treat absolutely any non-promise value as being\n        // an immediately-resolved promise. This is consistent with JavaScript where you'd\n        // commonly use `Promise.resolve(param).then(() => {...})` in order to coerce the param\n        // to a promise... normally you wouldn't bother checking that the param specifically\n        // resolved to `undefined`, you'd just throw away whatever it resolved to.\n        //\n        // It's possible to argue that we should actually allow only `undefined` here but\n        // changing it now could break existing users, e.g. html-rewriter.ew-test is broken\n        // because it writes `() => someExpression()` for a callback that's supposed to\n        // optionally return Promise<void> -- it seems like the callback isn't actually intending\n        // to return the result of `someExpression()` but does so by accident since the braces\n        // are missing. This is probably common in user code, too.\n        return js.resolvedPromise();\n      } else {\n        auto& wrapper = *static_cast<TypeWrapper*>(this);\n        KJ_IF_SOME(value, wrapper.tryUnwrap(js, context, handle, (T*)nullptr, parentObject)) {\n          return js.resolvedPromise(kj::mv(value));\n        } else {\n          // Wrong type.\n          return kj::none;\n        }\n      }\n    }\n  }\n\n private:\n  const JsgConfig config;\n\n  static bool isThenable(v8::Local<v8::Context> context, v8::Local<v8::Value> handle) {\n    if (handle->IsObject()) {\n      auto obj = handle.As<v8::Object>();\n      return check(obj->Has(context, v8StrIntern(v8::Isolate::GetCurrent(), \"then\")));\n    }\n    return false;\n  }\n};\n\n// -----------------------------------------------------------------------------\n\n// A utility used internally by ServiceWorkerGlobalScope to perform the book keeping\n// for unhandled promise rejection notifications. The handler maintains a table of\n// weak references to rejected promises that have not been handled and will handle\n// emitting events and console warnings as appropriate.\nclass UnhandledRejectionHandler {\n public:\n  using Handler = void(jsg::Lock& js,\n      v8::PromiseRejectEvent event,\n      jsg::V8Ref<v8::Promise> promise,\n      jsg::Value value);\n\n  explicit UnhandledRejectionHandler(kj::Function<Handler> handler): handler(kj::mv(handler)) {}\n\n  void report(jsg::Lock& js,\n      v8::PromiseRejectEvent event,\n      jsg::V8Ref<v8::Promise> promise,\n      jsg::Value value);\n\n  void setUseMicrotasksCompletedCallback(bool value) {\n    useMicrotasksCompletedCallback = value;\n  }\n\n  void clear();\n\n  JSG_MEMORY_INFO(UnhandledRejectionHandler) {\n    // TODO (soon): Can we reasonably measure the function handler?\n    tracker.trackField(\"unhandledRejections\", unhandledRejections);\n    tracker.trackField(\"warnedRejections\", warnedRejections);\n  }\n\n private:\n  // Used as part of the book keeping for unhandled rejections. When an\n  // unhandled rejection occurs, the unhandledRejections Table will be updated.\n  // If the rejection is later handled asynchronously, then the item will be\n  // removed from the table. When the unhandled rejection table is processed\n  // later in the event loop tick, any remaining rejections will generate a\n  // warning to the inspector console (if enabled);\n  struct UnhandledRejection {\n    explicit UnhandledRejection(jsg::Lock& js,\n        jsg::V8Ref<v8::Promise> promise,\n        jsg::Value value,\n        v8::Local<v8::Message> message);\n\n    ~UnhandledRejection();\n\n    UnhandledRejection(UnhandledRejection&& other) = default;\n    UnhandledRejection& operator=(UnhandledRejection&& other) = default;\n\n    // TODO(cleanup): It would be better to use a jsg::HashableV8Ref or\n    // jsg::Identity here but we need the Globals to always be weak so\n    // that the book keeping doesn't end up being a memory leak.\n\n    uint hash;\n\n    // We use v8::Globals directly here because these references are going to\n    // be made weak and could be garbage collected and cleared while the items\n    // are still in the unhandledRejections or warnedRejections tables.\n\n    v8::Global<v8::Promise> promise;\n    v8::Global<v8::Value> value;\n    v8::Global<v8::Message> message;\n    kj::Maybe<Ref<AsyncContextFrame>> asyncContextFrame;\n\n    inline bool isAlive() {\n      return !promise.IsEmpty() && !value.IsEmpty();\n    }\n\n    uint hashCode() const {\n      return hash;\n    }\n\n    JSG_MEMORY_INFO(UnhandledRejection) {\n      tracker.trackField(\"promise\", promise);\n      tracker.trackField(\"value\", value);\n      visitForMemoryInfo(tracker);\n    }\n    void visitForMemoryInfo(MemoryTracker& tracker) const;\n  };\n\n  // A v8::Promise with memoized hash code.\n  struct HashedPromise {\n    v8::Local<v8::Promise> promise;\n    uint hash;\n\n    HashedPromise(v8::Local<v8::Promise> promise)\n        : promise(promise),\n          hash(kj::hashCode(promise->GetIdentityHash())) {}\n\n    JSG_MEMORY_INFO(HashedPromise) {\n      tracker.trackField(\"promise\", promise);\n    }\n  };\n\n  struct UnhandledRejectionCallbacks {\n    inline const UnhandledRejection& keyForRow(\n        const UnhandledRejection& row KJ_LIFETIMEBOUND) const {\n      return row;\n    }\n    inline bool matches(const UnhandledRejection& a, const UnhandledRejection& b) const {\n      return a.promise == b.promise;\n    }\n    inline bool matches(const UnhandledRejection& a, const HashedPromise& b) const {\n      return a.promise == b.promise;\n    }\n    inline uint hashCode(const UnhandledRejection& row) const {\n      return row.hashCode();\n    }\n    inline uint hashCode(const HashedPromise& key) const {\n      return key.hash;\n    }\n  };\n\n  kj::Function<Handler> handler;\n  bool scheduled = false;\n  // Controlled by the unhandled_rejection_after_microtask_checkpoint compat flag.\n  bool useMicrotasksCompletedCallback = false;\n\n  using UnhandledRejectionsTable =\n      kj::Table<UnhandledRejection, kj::HashIndex<UnhandledRejectionCallbacks>>;\n\n  UnhandledRejectionsTable unhandledRejections;\n  UnhandledRejectionsTable warnedRejections;\n\n  void rejectedWithNoHandler(jsg::Lock& js, jsg::V8Ref<v8::Promise> promise, jsg::Value value);\n  void handledAfterRejection(jsg::Lock& js, jsg::V8Ref<v8::Promise> promise);\n  void ensureProcessingWarnings(jsg::Lock& js);\n  void processWarnings(jsg::Lock& js);\n\n  // Must be static: V8 requires a plain C function pointer for this callback.\n  static void onMicrotasksCompleted(v8::Isolate* isolate, void* data);\n};\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/resource-test-bootstrap.js",
    "content": "export function bootstrapFunction() {\n  return 'THIS_IS_BOOTSTRAP_FUNCTION';\n}\n\nexport class BootstrapClass {\n  run() {\n    return 'THIS_IS_BOOTSTRAP_CLASS';\n  }\n}\n"
  },
  {
    "path": "src/workerd/jsg/resource-test-builtin.js",
    "content": "export function builtinFunction() {\n  return 'THIS_IS_BUILTIN_FUNCTION';\n}\n"
  },
  {
    "path": "src/workerd/jsg/resource-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"jsg-test.h\"\n\n#include <workerd/jsg/resource-test.capnp.h>\n\nnamespace workerd::jsg::test {\nnamespace {\n\nV8System v8System;\nclass ContextGlobalObject: public Object, public ContextGlobal {};\n\nstruct BoxContext: public ContextGlobalObject {\n  JSG_RESOURCE_TYPE(BoxContext) {\n    JSG_NESTED_TYPE(NumberBox);\n    JSG_NESTED_TYPE(BoxBox);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(BoxIsolate, BoxContext, NumberBox, BoxBox);\n\nKJ_TEST(\"constructors and properties\") {\n  Evaluator<BoxContext, BoxIsolate> e(v8System);\n  e.expectEval(\"new NumberBox(123).value\", \"number\", \"123\");\n  e.expectEval(\"new NumberBox(123).boxed.value\", \"number\", \"123\");\n  e.expectEval(\"new BoxBox(new NumberBox(123), 321).inner.value\", \"number\", \"444\");\n  e.expectEval(\"var n = new NumberBox(123);\\n\"\n               \"n.value = 321;\\n\"\n               \"n.getValue()\",\n      \"number\", \"321\");\n  e.expectEval(\"var n = new NumberBox(123);\\n\"\n               \"n.boxed = new NumberBox(321);\\n\"\n               \"n.getValue()\",\n      \"number\", \"321\");\n  e.expectEval(\"new NumberBox(123) instanceof NumberBox\", \"boolean\", \"true\");\n  e.expectEval(\"new NumberBox(123) instanceof BoxBox\", \"boolean\", \"false\");\n}\n\nKJ_TEST(\"methods\") {\n  Evaluator<BoxContext, BoxIsolate> e(v8System);\n  e.expectEval(\"var n = new NumberBox(123);\\n\"\n               \"n.increment();\\n\"\n               \"n.getValue()\",\n      \"number\", \"124\");\n\n  e.expectEval(\"var n = new NumberBox(123);\\n\"\n               \"n.incrementBy(321);\\n\"\n               \"n.getValue()\",\n      \"number\", \"444\");\n\n  e.expectEval(\"var n = new NumberBox(123);\\n\"\n               \"n.incrementByBox(new NumberBox(321));\\n\"\n               \"n.getValue()\",\n      \"number\", \"444\");\n\n  e.expectEval(\"var n = new NumberBox(123);\\n\"\n               \"n.add(321)\",\n      \"number\", \"444\");\n\n  e.expectEval(\"var n = new NumberBox(123);\\n\"\n               \"n.addBox(new NumberBox(321))\",\n      \"number\", \"444\");\n\n  e.expectEval(\"var n = new NumberBox(123);\\n\"\n               \"n.addReturnBox(321).value\",\n      \"number\", \"444\");\n\n  e.expectEval(\"var n = new NumberBox(123);\\n\"\n               \"n.addMultiple(new NumberBox(321), 111, new NumberBox(2222))\",\n      \"number\", \"2777\");\n\n  e.expectEval(\"var n = new NumberBox(123);\\n\"\n               \"new n.increment();\",\n      \"throws\", \"TypeError: n.increment is not a constructor\");\n}\n\n// ========================================================================================\n\nstruct Mixin {\n  int getValue() {\n    return i;\n  }\n  Mixin(int i): i(i) {}\n  int i;\n};\nstruct InheritsMixin: public Object, public Mixin {\n  InheritsMixin(int i): Mixin(i) {}\n\n  JSG_RESOURCE_TYPE(InheritsMixin) {\n    JSG_METHOD(getValue);\n  }\n};\nstruct InheritsMixinContext: public ContextGlobalObject {\n  Ref<InheritsMixin> makeInheritsMixin(jsg::Lock& js, int i) {\n    return js.alloc<InheritsMixin>(i);\n  }\n\n  JSG_RESOURCE_TYPE(InheritsMixinContext) {\n    JSG_METHOD(makeInheritsMixin);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(InheritsMixinIsolate, InheritsMixinContext, InheritsMixin);\n\nKJ_TEST(\"JSG_METHODs can be implemented by mixins\") {\n  Evaluator<InheritsMixinContext, InheritsMixinIsolate> e(v8System);\n  e.expectEval(\"makeInheritsMixin(12345).getValue()\", \"number\", \"12345\");\n}\n\n// ========================================================================================\n\nstruct PrototypePropertyObject: public Object {\n  double value;\n\n  PrototypePropertyObject(double value): value(value) {}\n\n  static Ref<PrototypePropertyObject> constructor(jsg::Lock& js, double value) {\n    return js.alloc<PrototypePropertyObject>(value);\n  }\n\n  double getValue() {\n    return value;\n  }\n  void setValue(double v) {\n    value = v;\n  }\n\n  JSG_RESOURCE_TYPE(PrototypePropertyObject) {\n    JSG_PROTOTYPE_PROPERTY(value, getValue, setValue);\n  }\n};\n\nstruct PropContext: public ContextGlobalObject {\n  PropContext(): contextProperty(kj::str(\"default-context-property-value\")) {}\n\n  kj::StringPtr getContextProperty() {\n    return contextProperty;\n  }\n  void setContextProperty(kj::String s) {\n    contextProperty = kj::mv(s);\n  }\n\n  JSG_RESOURCE_TYPE(PropContext) {\n    JSG_METHOD(getContextProperty);\n    JSG_METHOD(setContextProperty);\n    JSG_INSTANCE_PROPERTY(contextProperty, getContextProperty, setContextProperty);\n\n    JSG_NESTED_TYPE(PrototypePropertyObject);\n  }\n\n private:\n  kj::String contextProperty;\n};\nJSG_DECLARE_ISOLATE_TYPE(PropIsolate, PropContext, PrototypePropertyObject);\n\nconst auto kIllegalInvocation =\n    \"TypeError: Illegal invocation: function called with incorrect `this` reference. \"\n    \"See https://developers.cloudflare.com/workers/observability/errors/#illegal-invocation-errors for details.\"_kj;\n\nKJ_TEST(\"context methods and properties\") {\n  Evaluator<PropContext, PropIsolate> e(v8System);\n  e.expectEval(\"getContextProperty()\", \"string\", \"default-context-property-value\");\n  e.expectEval(\"setContextProperty('foo');\\n\"\n               \"getContextProperty()\",\n      \"string\", \"foo\");\n\n  e.expectEval(\"contextProperty\", \"string\", \"default-context-property-value\");\n  e.expectEval(\"contextProperty = 'foo'; getContextProperty()\", \"string\", \"foo\");\n\n  e.expectEval(\"this.getContextProperty()\", \"string\", \"default-context-property-value\");\n  e.expectEval(\"this.setContextProperty('foo');\\n\"\n               \"getContextProperty()\",\n      \"string\", \"foo\");\n\n  e.expectEval(\"this.contextProperty\", \"string\", \"default-context-property-value\");\n  e.expectEval(\"this.contextProperty = 'foo'; getContextProperty()\", \"string\", \"foo\");\n\n  e.expectEval(\"let p = new PrototypePropertyObject(123);\\n\"\n               \"let o = {};\\n\"\n               \"o.__proto__ = p.__proto__;\\n\"\n               \"o.value\",\n      \"throws\", kIllegalInvocation);\n  e.expectEval(\"let p = new PrototypePropertyObject(123);\\n\"\n               \"let o = {};\\n\"\n               \"o.__proto__ = p.__proto__;\\n\"\n               \"o.value = 123\",\n      \"throws\", kIllegalInvocation);\n\n  e.expectEval(\"class P2 extends PrototypePropertyObject {\\n\"\n               \"  constructor(v) { super(v); }\\n\"\n               \"}\\n\"\n               \"let p = new P2(123);\\n\"\n               \"p.value\",\n      \"number\", \"123\");\n}\n\n// ========================================================================================\n\nstruct NonConstructibleContext: public ContextGlobalObject {\n  struct NonConstructible: public Object {\n    NonConstructible(double x): x(x) {}\n\n    double x;\n\n    double method() {\n      return x;\n    }\n\n    JSG_RESOURCE_TYPE(NonConstructible) {\n      JSG_METHOD(method);\n    }\n  };\n\n  Ref<NonConstructible> getNonConstructible(jsg::Lock& js, double x) {\n    return js.alloc<NonConstructible>(x);\n  }\n\n  JSG_RESOURCE_TYPE(NonConstructibleContext) {\n    JSG_NESTED_TYPE(NonConstructible);\n    JSG_METHOD(getNonConstructible);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(\n    NonConstructibleIsolate, NonConstructibleContext, NonConstructibleContext::NonConstructible);\n\nKJ_TEST(\"non-constructible types can't be constructed\") {\n  Evaluator<NonConstructibleContext, NonConstructibleIsolate> e(v8System);\n  e.expectEval(\"new NonConstructible().method()\", \"throws\", \"TypeError: Illegal constructor\");\n\n  e.expectEval(\"getNonConstructible(12321).method()\", \"number\", \"12321\");\n\n  e.expectEval(\"getNonConstructible(12321) instanceof NonConstructible\", \"boolean\", \"true\");\n}\n\n// ========================================================================================\n\nstruct IterableContext: public ContextGlobalObject {\n  class Iterable: public Object {\n   public:\n    static Ref<Iterable> constructor(jsg::Lock& js) {\n      return js.alloc<Iterable>();\n    }\n\n    class Iterator: public Object {\n     public:\n      struct NextValue {\n        bool done;\n        Optional<int> value;\n        JSG_STRUCT(done, value);\n      };\n\n      explicit Iterator(Ref<Iterable> parentParam)\n          : parent(kj::mv(parentParam)),\n            cursor(parent->values) {}\n\n      NextValue next() {\n        if (cursor == parent->values + sizeof(parent->values) / sizeof(*parent->values)) {\n          return {.done = true, .value = kj::none};\n        }\n        return {.done = false, .value = *cursor++};\n      }\n\n      v8::Local<v8::Object> self(const v8::FunctionCallbackInfo<v8::Value>& info) {\n        // Helper to make this iterator itself iterable. This allows code like\n        // `for (let k of iterable.entries())` to work.\n        return info.This();\n      }\n\n      JSG_RESOURCE_TYPE(Iterator) {\n        JSG_INHERIT_INTRINSIC(v8::kIteratorPrototype);\n        JSG_METHOD(next);\n        JSG_ITERABLE(self);\n      }\n\n     private:\n      Ref<Iterable> parent;\n      int* cursor;\n    };\n\n    Ref<Iterator> entries(const v8::FunctionCallbackInfo<v8::Value>& info) {\n      auto& js = jsg::Lock::from(info.GetIsolate());\n      return js.alloc<Iterator>(JSG_THIS);\n    }\n\n    JSG_RESOURCE_TYPE(Iterable) {\n      JSG_METHOD(entries);\n      JSG_ITERABLE(entries);\n    }\n\n   private:\n    int values[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};\n    // In real code, this data structure could be more complex, and we would need to think about\n    // iterator invalidation, which might require storing back-references from the parent iterable\n    // to all of its live iterators to make sure they can be nulled out if necessary. But then we\n    // need to worry about circular references ...\n  };\n\n  JSG_RESOURCE_TYPE(IterableContext) {\n    JSG_NESTED_TYPE(Iterable);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(IterableIsolate,\n    IterableContext,\n    IterableContext::Iterable,\n    IterableContext::Iterable::Iterator,\n    IterableContext::Iterable::Iterator::NextValue);\n\nKJ_TEST(\"Iterables can be iterated\") {\n  Evaluator<IterableContext, IterableIsolate> e(v8System);\n  e.expectEval(\"let results = [];\"\n               \"for (let n of new Iterable()) { results.push(n); };\"\n               \"'' + results.join('')\",\n      \"string\", \"0123456789\");\n  e.expectEval(\"let results = [];\"\n               \"for (let n of new Iterable().entries()) { results.push(n); };\"\n               \"'' + results.join('')\",\n      \"string\", \"0123456789\");\n  e.expectEval(\n      \"let arrayIterator = [][Symbol.iterator]();\"\n      \"let arrayIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf(arrayIterator));\"\n      \"let iterator = new Iterable().entries();\"\n      \"let iteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf(iterator));\"\n      \"iteratorPrototype === arrayIteratorPrototype\",\n      \"boolean\", \"true\");\n}\n\n// ========================================================================================\n\nstruct StaticContext: public ContextGlobalObject {\n  struct StaticConstants: public Object {\n    static Ref<StaticConstants> constructor(jsg::Lock& js) {\n      return js.alloc<StaticConstants>();\n    }\n\n    static constexpr double DOUBLE = 1.5;\n    static constexpr int INT = 123;\n    static constexpr bool BOOL = true;\n    static constexpr kj::StringPtr STRING = \"a static constant string\"_kj;\n\n    JSG_RESOURCE_TYPE(StaticConstants) {\n      JSG_STATIC_CONSTANT(DOUBLE);\n      JSG_STATIC_CONSTANT(INT);\n      JSG_STATIC_CONSTANT(BOOL);\n      JSG_STATIC_CONSTANT(STRING);\n    }\n  };\n\n  struct StaticMethods: public Object {\n    static Ref<StaticMethods> constructor(jsg::Lock& js) {\n      return js.alloc<StaticMethods>();\n    }\n\n    static v8::Local<v8::Value> passThrough(v8::Local<v8::Value> arg) {\n      return arg;\n    }\n    static v8::Local<v8::Value> passThroughWithInfo(\n        const v8::FunctionCallbackInfo<v8::Value>& info, v8::Local<v8::Value> arg) {\n      return arg;\n    }\n\n    static void voidCall() {}\n    static void voidCallWithInfo(const v8::FunctionCallbackInfo<v8::Value>& info) {}\n\n    static Unimplemented unimplementedStaticMethod() {\n      return {};\n    }\n\n    static void delete_() {}\n\n    JSG_RESOURCE_TYPE(StaticMethods) {\n      JSG_STATIC_METHOD(passThrough);\n      JSG_STATIC_METHOD(passThroughWithInfo);\n      JSG_STATIC_METHOD(voidCall);\n      JSG_STATIC_METHOD(voidCallWithInfo);\n      JSG_STATIC_METHOD_NAMED(delete, delete_);\n      JSG_STATIC_METHOD(unimplementedStaticMethod);\n    }\n  };\n\n  struct StaticProperties: public Object {\n    static Ref<StaticProperties> constructor(jsg::Lock& js) {\n      return js.alloc<StaticProperties>();\n    }\n\n    // Static property returning a simple value\n    static int simpleValue() {\n      return 42;\n    }\n\n    // Static property returning a string\n    static kj::String stringValue() {\n      return kj::str(\"static property string\");\n    }\n\n    // Static property returning an array\n    static kj::Array<int> arrayValue() {\n      auto array = kj::heapArray<int>(3);\n      array[0] = 1;\n      array[1] = 2;\n      array[2] = 3;\n      return kj::mv(array);\n    }\n\n    // Static property returning void (edge case)\n    static void voidProperty() {\n      // This should be handled gracefully even though it's unusual for a property\n    }\n\n    // A getter function to be used with JSG_STATIC_READONLY_PROPERTY_NAMED\n    static double getComputedValue() {\n      return 3.14159;\n    }\n\n    // Another named property getter\n    static kj::String getDescription() {\n      return kj::str(\"This is a static property\");\n    }\n\n    // Static property that takes Lock& as first parameter\n    static int withLockValue(jsg::Lock& js) {\n      // We can use the lock here if needed, but for testing just return a value\n      return 99;\n    }\n\n    // Static property with Lock& that returns an allocated object\n    static kj::String withLockString(jsg::Lock& js) {\n      return kj::str(\"property with lock\");\n    }\n\n    JSG_RESOURCE_TYPE(StaticProperties) {\n      JSG_STATIC_READONLY_PROPERTY(simpleValue);\n      JSG_STATIC_READONLY_PROPERTY(stringValue);\n      JSG_STATIC_READONLY_PROPERTY(arrayValue);\n      JSG_STATIC_READONLY_PROPERTY(voidProperty);\n      JSG_STATIC_READONLY_PROPERTY_NAMED(computedValue, getComputedValue);\n      JSG_STATIC_READONLY_PROPERTY_NAMED(description, getDescription);\n      JSG_STATIC_READONLY_PROPERTY(withLockValue);\n      JSG_STATIC_READONLY_PROPERTY(withLockString);\n    }\n  };\n\n  JSG_RESOURCE_TYPE(StaticContext) {\n    JSG_NESTED_TYPE(StaticConstants);\n    JSG_NESTED_TYPE(StaticMethods);\n    JSG_NESTED_TYPE(StaticProperties);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(StaticIsolate,\n    StaticContext,\n    StaticContext::StaticConstants,\n    StaticContext::StaticMethods,\n    StaticContext::StaticProperties);\n\nKJ_TEST(\"Static constants are exposed as constructor properties\") {\n  Evaluator<StaticContext, StaticIsolate> e(v8System);\n  e.expectEval(\"StaticConstants.DOUBLE === 1.5\", \"boolean\", \"true\");\n  e.expectEval(\"StaticConstants.INT === 123\", \"boolean\", \"true\");\n  e.expectEval(\"StaticConstants.BOOL === true\", \"boolean\", \"true\");\n  e.expectEval(\"StaticConstants.STRING === 'a static constant string'\", \"boolean\", \"true\");\n}\nKJ_TEST(\"Static constants are exposed as constructor prototype properties\") {\n  Evaluator<StaticContext, StaticIsolate> e(v8System);\n  e.expectEval(\"StaticConstants.prototype.DOUBLE === 1.5\", \"boolean\", \"true\");\n  e.expectEval(\"StaticConstants.prototype.INT === 123\", \"boolean\", \"true\");\n  e.expectEval(\"StaticConstants.prototype.BOOL === true\", \"boolean\", \"true\");\n  e.expectEval(\n      \"StaticConstants.prototype.STRING === 'a static constant string'\", \"boolean\", \"true\");\n}\nKJ_TEST(\"Static constants are exposed as object instance properties\") {\n  Evaluator<StaticContext, StaticIsolate> e(v8System);\n  e.expectEval(\"new StaticConstants().DOUBLE === 1.5\", \"boolean\", \"true\");\n  e.expectEval(\"new StaticConstants().INT === 123\", \"boolean\", \"true\");\n  e.expectEval(\"new StaticConstants().BOOL === true\", \"boolean\", \"true\");\n  e.expectEval(\"new StaticConstants().STRING === 'a static constant string'\", \"boolean\", \"true\");\n}\nKJ_TEST(\"Static constants are exposed as object instance prototype properties\") {\n  Evaluator<StaticContext, StaticIsolate> e(v8System);\n  e.expectEval(\"Object.getPrototypeOf(new StaticConstants()).DOUBLE === 1.5\", \"boolean\", \"true\");\n  e.expectEval(\"Object.getPrototypeOf(new StaticConstants()).INT === 123\", \"boolean\", \"true\");\n  e.expectEval(\"Object.getPrototypeOf(new StaticConstants()).BOOL === true\", \"boolean\", \"true\");\n  e.expectEval(\"Object.getPrototypeOf(new StaticConstants()).STRING === 'a static constant string'\",\n      \"boolean\", \"true\");\n}\nKJ_TEST(\"Static methods are exposed as constructor properties\") {\n  Evaluator<StaticContext, StaticIsolate> e(v8System);\n  e.expectEval(\"StaticMethods.passThrough(true)\", \"boolean\", \"true\");\n  e.expectEval(\"StaticMethods.passThroughWithInfo(true)\", \"boolean\", \"true\");\n  e.expectEval(\"StaticMethods.voidCall(); true;\", \"boolean\", \"true\");\n  e.expectEval(\"StaticMethods.voidCallWithInfo(); true;\", \"boolean\", \"true\");\n  e.expectEval(\"StaticMethods.delete(); true;\", \"boolean\", \"true\");\n  e.expectEval(\"StaticMethods.unimplementedStaticMethod()\", \"throws\",\n      \"Error: Failed to execute 'unimplementedStaticMethod' on 'StaticMethods': \"\n      \"the method is not implemented.\");\n  e.expectEval(\"new StaticMethods.passThrough(true);\", \"throws\",\n      \"TypeError: StaticMethods.passThrough is not a constructor\");\n}\nKJ_TEST(\"Static methods are not exposed as constructor prototype properties\") {\n  Evaluator<StaticContext, StaticIsolate> e(v8System);\n  e.expectEval(\"typeof StaticMethods.prototype.passThrough === 'undefined'\\n\"\n               \"&& typeof StaticMethods.prototype.passThroughWithInfo === 'undefined'\\n\"\n               \"&& typeof StaticMethods.prototype.voidCall === 'undefined'\\n\"\n               \"&& typeof StaticMethods.prototype.voidCallWithInfo === 'undefined'\\n\"\n               \"&& typeof StaticMethods.prototype.delete === 'undefined'\\n\"\n               \"&& typeof StaticMethods.prototype.unimplementedStaticMethod === 'undefined'\",\n      \"boolean\", \"true\");\n}\nKJ_TEST(\"Static methods are not exposed as object instance properties\") {\n  Evaluator<StaticContext, StaticIsolate> e(v8System);\n  e.expectEval(\"let obj = new StaticMethods();\\n\"\n               \"typeof obj.passThrough === 'undefined'\\n\"\n               \"&& typeof obj.passThroughWithInfo === 'undefined'\\n\"\n               \"&& typeof obj.voidCall === 'undefined'\\n\"\n               \"&& typeof obj.voidCallWithInfo === 'undefined'\\n\"\n               \"&& typeof obj.delete === 'undefined'\\n\"\n               \"&& typeof obj.unimplementedStaticMethod === 'undefined'\",\n      \"boolean\", \"true\");\n}\n\nKJ_TEST(\"Static properties are exposed as constructor properties\") {\n  Evaluator<StaticContext, StaticIsolate> e(v8System);\n  // Test simple value property\n  e.expectEval(\"StaticProperties.simpleValue\", \"number\", \"42\");\n  e.expectEval(\"StaticProperties.simpleValue === 42\", \"boolean\", \"true\");\n\n  // Test string property\n  e.expectEval(\"StaticProperties.stringValue\", \"string\", \"static property string\");\n  e.expectEval(\"StaticProperties.stringValue === 'static property string'\", \"boolean\", \"true\");\n\n  // Test array property\n  e.expectEval(\"Array.isArray(StaticProperties.arrayValue)\", \"boolean\", \"true\");\n  e.expectEval(\"StaticProperties.arrayValue.length\", \"number\", \"3\");\n  e.expectEval(\"StaticProperties.arrayValue[0]\", \"number\", \"1\");\n  e.expectEval(\"StaticProperties.arrayValue[1]\", \"number\", \"2\");\n  e.expectEval(\"StaticProperties.arrayValue[2]\", \"number\", \"3\");\n\n  // Test void property (should be undefined)\n  e.expectEval(\"StaticProperties.voidProperty\", \"undefined\", \"undefined\");\n\n  // Test named properties\n  e.expectEval(\"StaticProperties.computedValue\", \"number\", \"3.14159\");\n  e.expectEval(\"StaticProperties.description\", \"string\", \"This is a static property\");\n\n  // Test properties that take Lock& as first parameter\n  e.expectEval(\"StaticProperties.withLockValue\", \"number\", \"99\");\n  e.expectEval(\"StaticProperties.withLockString\", \"string\", \"property with lock\");\n}\n\nKJ_TEST(\"Static properties are read-only\") {\n  Evaluator<StaticContext, StaticIsolate> e(v8System);\n  // Attempting to write to static properties should have no effect\n  e.expectEval(\"StaticProperties.simpleValue = 100; StaticProperties.simpleValue\", \"number\", \"42\");\n  e.expectEval(\"StaticProperties.stringValue = 'new'; StaticProperties.stringValue\", \"string\",\n      \"static property string\");\n  e.expectEval(\n      \"StaticProperties.computedValue = 0; StaticProperties.computedValue\", \"number\", \"3.14159\");\n  e.expectEval(\n      \"StaticProperties.withLockValue = 200; StaticProperties.withLockValue\", \"number\", \"99\");\n}\n\nKJ_TEST(\"Static properties are not functions\") {\n  Evaluator<StaticContext, StaticIsolate> e(v8System);\n  // Static properties should not be callable as functions\n  e.expectEval(\"typeof StaticProperties.simpleValue\", \"string\", \"number\");\n  e.expectEval(\"StaticProperties.simpleValue()\", \"throws\",\n      \"TypeError: StaticProperties.simpleValue is not a function\");\n  e.expectEval(\"new StaticProperties.simpleValue()\", \"throws\",\n      \"TypeError: StaticProperties.simpleValue is not a constructor\");\n}\n\nKJ_TEST(\"Static properties are not exposed as instance properties\") {\n  Evaluator<StaticContext, StaticIsolate> e(v8System);\n  e.expectEval(\"let obj = new StaticProperties();\\n\"\n               \"typeof obj.simpleValue === 'undefined'\\n\"\n               \"&& typeof obj.stringValue === 'undefined'\\n\"\n               \"&& typeof obj.arrayValue === 'undefined'\\n\"\n               \"&& typeof obj.voidProperty === 'undefined'\\n\"\n               \"&& typeof obj.computedValue === 'undefined'\\n\"\n               \"&& typeof obj.description === 'undefined'\\n\"\n               \"&& typeof obj.withLockValue === 'undefined'\\n\"\n               \"&& typeof obj.withLockString === 'undefined'\",\n      \"boolean\", \"true\");\n}\n\nKJ_TEST(\"Static properties are not exposed on prototype\") {\n  Evaluator<StaticContext, StaticIsolate> e(v8System);\n  e.expectEval(\"typeof StaticProperties.prototype.simpleValue === 'undefined'\\n\"\n               \"&& typeof StaticProperties.prototype.stringValue === 'undefined'\\n\"\n               \"&& typeof StaticProperties.prototype.arrayValue === 'undefined'\\n\"\n               \"&& typeof StaticProperties.prototype.voidProperty === 'undefined'\\n\"\n               \"&& typeof StaticProperties.prototype.computedValue === 'undefined'\\n\"\n               \"&& typeof StaticProperties.prototype.description === 'undefined'\\n\"\n               \"&& typeof StaticProperties.prototype.withLockValue === 'undefined'\\n\"\n               \"&& typeof StaticProperties.prototype.withLockString === 'undefined'\",\n      \"boolean\", \"true\");\n}\nKJ_TEST(\"Static methods are not exposed as object instance prototype properties\") {\n  Evaluator<StaticContext, StaticIsolate> e(v8System);\n  e.expectEval(\"let objProto = Object.getPrototypeOf(new StaticMethods());\\n\"\n               \"typeof objProto.passThrough === 'undefined'\\n\"\n               \"&& typeof objProto.passThroughWithInfo === 'undefined'\\n\"\n               \"&& typeof objProto.voidCall === 'undefined'\\n\"\n               \"&& typeof objProto.voidCallWithInfo === 'undefined'\\n\"\n               \"&& typeof objProto.delete === 'undefined'\\n\"\n               \"&& typeof objProto.unimplementedStaticMethod === 'undefined'\",\n      \"boolean\", \"true\");\n}\nKJ_TEST(\"Static methods can be monkey-patched\") {\n  Evaluator<StaticContext, StaticIsolate> e(v8System);\n  e.expectEval(\"StaticMethods.passThrough = function(a) { return false; };\\n\"\n               \"StaticMethods.passThrough(true)\",\n      \"boolean\", \"false\");\n}\n\n// ========================================================================================\n\nstruct ReflectionContext: public ContextGlobalObject {\n  struct Super: public Object {\n    JSG_RESOURCE_TYPE(Super) {}\n  };\n\n  struct Reflector: public Super {\n    static jsg::Ref<Reflector> constructor(jsg::Lock& js) {\n      auto result = js.alloc<Reflector>();\n\n      // Check reflection returns null when wrapper isn't allocated.\n      KJ_EXPECT(result->intReflector.get(js.v8Isolate, \"foo\") == kj::none);\n      KJ_EXPECT(result->stringReflector.get(js.v8Isolate, \"foo\") == kj::none);\n\n      return result;\n    }\n\n    kj::Maybe<int> getIntReflection(jsg::Lock& js, kj::String name) {\n      return intReflector.get(js.v8Isolate, name);\n    }\n    kj::Maybe<kj::String> getStringReflection(jsg::Lock& js, kj::String name) {\n      return stringReflector.get(js.v8Isolate, name);\n    }\n\n    PropertyReflection<int> intReflector;\n    PropertyReflection<kj::String> stringReflector;\n\n    JSG_RESOURCE_TYPE(Reflector) {\n      JSG_INHERIT(Super);\n      JSG_METHOD(getIntReflection);\n      JSG_METHOD(getStringReflection);\n    }\n    JSG_REFLECTION(intReflector, stringReflector);\n  };\n\n  jsg::Ref<Reflector> makeReflector(jsg::Lock& js) {\n    return js.alloc<Reflector>();\n  }\n\n  jsg::Ref<Super> makeSuper(jsg::Lock& js) {\n    return js.alloc<Reflector>();\n  }\n\n  JSG_RESOURCE_TYPE(ReflectionContext) {\n    JSG_NESTED_TYPE(Reflector);\n    JSG_METHOD(makeReflector);\n    JSG_METHOD(makeSuper);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(\n    ReflectionIsolate, ReflectionContext, ReflectionContext::Super, ReflectionContext::Reflector);\n\nKJ_TEST(\"PropertyReflection works\") {\n  Evaluator<ReflectionContext, ReflectionIsolate> e(v8System);\n  e.expectEval(\"let r = new Reflector; r.getIntReflection('foo')\", \"object\", \"null\");\n  e.expectEval(\"let r = new Reflector; r.foo = 123; r.getIntReflection('foo')\", \"number\", \"123\");\n  e.expectEval(\"let r = new Reflector; r.foo = 123; r.getStringReflection('foo')\", \"string\", \"123\");\n\n  e.expectEval(\"let r = makeReflector(); r.foo = 123; r.getIntReflection('foo')\", \"number\", \"123\");\n  e.expectEval(\"let r = makeSuper(); r.foo = 123; r.getIntReflection('foo')\", \"number\", \"123\");\n}\n\n// ========================================================================================\n\nstruct InjectLockContext: public ContextGlobalObject {\n  struct Thingy: public Object {\n    Thingy(int val, v8::Isolate* v8Isolate): val(val), v8Isolate(v8Isolate) {}\n    int val;\n    v8::Isolate* v8Isolate;\n\n    static Ref<Thingy> constructor(Lock& js, int val) {\n      return js.alloc<Thingy>(val, js.v8Isolate);\n    }\n\n    int frob(Lock& js, int val2) {\n      KJ_ASSERT(js.v8Isolate == v8Isolate);\n      return val + val2;\n    }\n\n    int getVal(Lock& js) {\n      KJ_ASSERT(js.v8Isolate == v8Isolate);\n      return val;\n    }\n\n    void setVal(Lock& js, int val) {\n      KJ_ASSERT(js.v8Isolate == v8Isolate);\n      this->val = val;\n    }\n\n    static int borf(Lock& js, int val) {\n      return val * 2;\n    }\n\n    JSG_RESOURCE_TYPE(Thingy) {\n      JSG_METHOD(frob);\n      JSG_PROTOTYPE_PROPERTY(val, getVal, setVal);\n      JSG_STATIC_METHOD(borf);\n    }\n  };\n\n  JSG_RESOURCE_TYPE(InjectLockContext) {\n    JSG_NESTED_TYPE(Thingy);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(InjectLockIsolate, InjectLockContext, InjectLockContext::Thingy);\n\nKJ_TEST(\"Methods can take Lock& as first parameter\") {\n  Evaluator<InjectLockContext, InjectLockIsolate> e(v8System);\n  e.expectEval(\"let t = new Thingy(123); t.val\", \"number\", \"123\");\n}\n\n// ========================================================================================\n\nstruct JsBundleContext: public ContextGlobalObject {\n  JSG_RESOURCE_TYPE(JsBundleContext) {\n    JSG_CONTEXT_JS_BUNDLE(BUILTIN_BUNDLE);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(JsBundleIsolate, JsBundleContext);\n\nKJ_TEST(\"expectEvalModule function works\") {\n  Evaluator<JsBundleContext, JsBundleIsolate, decltype(nullptr), JsBundleIsolate_TypeWrapper> e(\n      v8System);\n  e.expectEvalModule(\"export function run() { return 123; }\", \"number\", \"123\");\n}\n\nKJ_TEST(\"bundle installed works\") {\n  Evaluator<JsBundleContext, JsBundleIsolate, decltype(nullptr), JsBundleIsolate_TypeWrapper> e(\n      v8System);\n  e.expectEvalModule(R\"(\n    import * as b from \"test:resource-test-builtin\";\n    export function run() { return b.builtinFunction(); }\n  )\",\n      \"string\", \"THIS_IS_BUILTIN_FUNCTION\");\n}\n\n// ========================================================================================\n\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/resource-test.capnp",
    "content": "@0x92df13ba26adb5fe;\n\nusing Modules = import \"/workerd/jsg/modules.capnp\";\n\nconst builtinBundle :Modules.Bundle = (\n  modules = [\n    (name = \"test:resource-test-builtin\", src = embed \"resource-test-builtin.js\", type = builtin)\n]);\n\n\nconst bootstrapBundle :Modules.Bundle = (\n  modules = [\n    (name = \"test:resource-test-bootstrap\", src = embed \"resource-test-bootstrap.js\", type = internal)\n]);\n"
  },
  {
    "path": "src/workerd/jsg/resource.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"jsg.h\"\n#include \"setup.h\"\n\nnamespace workerd::jsg {\n\n// TODO(cleanup): Factor out toObject(), getInterned() into some sort of v8 tools module?\n\nvoid exposeGlobalScopeType(v8::Isolate* isolate, v8::Local<v8::Context> context) {\n  auto global = context->Global();\n\n  const auto toObject = [context](\n                            v8::Local<v8::Value> value) { return check(value->ToObject(context)); };\n  const auto getInterned = [isolate, context](v8::Local<v8::Object> object, const char* s) {\n    return check(object->Get(context, v8StrIntern(isolate, s)));\n  };\n\n  auto constructor = getInterned(global, \"constructor\");\n  auto name = getInterned(toObject(constructor), \"name\");\n\n  KJ_ASSERT(check(global->Set(context, name, constructor)));\n}\n\nv8::Local<v8::Symbol> getSymbolDispose(v8::Isolate* isolate) {\n  return v8::Symbol::GetDispose(isolate);\n}\n\nvoid throwIfConstructorCalledAsFunction(\n    const v8::FunctionCallbackInfo<v8::Value>& args, const std::type_info& type) {\n  if (!args.IsConstructCall()) {\n    throwTypeError(args.GetIsolate(),\n        kj::str(\"Failed to construct '\", typeName(type),\n            \"': Please use the 'new' operator, this object constructor cannot be called \"\n            \"as a function.\"));\n  }\n}\n\nvoid scheduleUnimplementedConstructorError(\n    const v8::FunctionCallbackInfo<v8::Value>& args, const std::type_info& type) {\n  auto isolate = args.GetIsolate();\n  isolate->ThrowError(v8StrIntern(isolate,\n      kj::str(\"Failed to construct '\", typeName(type), \"': the constructor is not implemented.\")));\n}\n\nvoid scheduleUnimplementedMethodError(const v8::FunctionCallbackInfo<v8::Value>& args,\n    const std::type_info& type,\n    const char* methodName) {\n  auto isolate = args.GetIsolate();\n  isolate->ThrowError(v8StrIntern(isolate,\n      kj::str(\"Failed to execute '\", methodName, \"' on '\", typeName(type),\n          \"': the method is not implemented.\")));\n}\n\nvoid scheduleUnimplementedPropertyError(\n    v8::Isolate* isolate, const std::type_info& type, const char* propertyName) {\n  isolate->ThrowError(v8StrIntern(isolate,\n      kj::str(\"Failed to get the '\", propertyName, \"' property on '\", typeName(type),\n          \"': the property is not implemented.\")));\n}\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/resource.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// INTERNAL IMPLEMENTATION FILE\n//\n// A \"resource type\" (in KJ parlance) is the opposite of a \"value type\". In JSG, a resource is a\n// C++ class that will be wrapped and exposed to JavaScript by reference, such that JavaScript code\n// can call back to the class's methods. This differs from, say, a struct type, which will be deeply\n// converted into a JS object when passed into JS.\n\n#include <workerd/jsg/fast-api.h>\n#include <workerd/jsg/memory.h>\n#include <workerd/jsg/meta.h>\n#include <workerd/jsg/modules.capnp.h>\n// JSG has very entrenched include cycles\n// NOLINTNEXTLINE(misc-header-include-cycle)\n#include <workerd/jsg/ser.h>\n#include <workerd/jsg/util.h>\n#include <workerd/jsg/wrappable.h>\n\n#include <v8-template.h>\n\n#include <kj/debug.h>\n#include <kj/map.h>\n#include <kj/tuple.h>\n\n#include <type_traits>\n#include <typeindex>\n\nnamespace std {\ninline auto KJ_HASHCODE(const std::type_index& idx) {\n  // Make std::type_index (which points to std::type_info) usable as a kj::HashMap key.\n  // TODO(cleanup): This probably shouldn't live here, but where should it live? KJ?\n  return idx.hash_code();\n}\n}  // namespace std\n\nnamespace workerd::jsg {\n\nconst auto kIllegalInvocation =\n    \"Illegal invocation: function called with incorrect `this` reference. \"\n    \"See https://developers.cloudflare.com/workers/observability/errors/#illegal-invocation-errors for details.\"_kj;\n\nclass Serializer;\nclass Deserializer;\n\n// Return true if the type requires GC visitation, which we assume is the case if the type or any\n// superclass (other than Object) declares a `visitForGc()` method.\ntemplate <typename T>\nconstexpr bool resourceNeedsGcTracing() {\n  return &T::visitForGc != &Object::visitForGc;\n}\ntemplate <>\nconstexpr bool resourceNeedsGcTracing<Object>() {\n  return false;\n}\n\n// Call obj->visitForGc() if and only if T defines its own `visitForGc()` method -- do not call\n// the parent class's `visitForGc()`.\ntemplate <typename T>\ninline void visitSubclassForGc(T* obj, GcVisitor& visitor) {\n  if constexpr (&T::visitForGc != &T::jsgSuper::visitForGc) {\n    obj->visitForGc(visitor);\n  }\n}\n\nvoid throwIfConstructorCalledAsFunction(\n    const v8::FunctionCallbackInfo<v8::Value>& args, const std::type_info& type);\n\n// The scheduleUnimplemented* variants will schedule an exception on the isolate\n// but do not throw a JsExceptionThrown.\n\n// Called to throw errors about calling unimplemented functionality. It's assumed these are called\n// directly from the V8 trampoline without liftKj, so they don't throw JsExceptionThrown.\nvoid scheduleUnimplementedConstructorError(\n    const v8::FunctionCallbackInfo<v8::Value>& args, const std::type_info& type);\n\n// Called to throw errors about calling unimplemented functionality. It's assumed these are called\n// directly from the V8 trampoline without liftKj, so they don't throw JsExceptionThrown.\nvoid scheduleUnimplementedMethodError(const v8::FunctionCallbackInfo<v8::Value>& args,\n    const std::type_info& type,\n    const char* methodName);\n\n// Called to throw errors about calling unimplemented functionality. It's assumed these are called\n// directly from the V8 trampoline without liftKj, so they don't throw JsExceptionThrown.\nvoid scheduleUnimplementedPropertyError(\n    v8::Isolate* isolate, const std::type_info& type, const char* propertyName);\n\ntemplate <typename TypeWrapper, typename T>\nclass ResourceWrapper;\n\n// Implements the V8 callback function for calling the static `constructor()` method of the C++\n// class.\ntemplate <typename TypeWrapper,\n    typename T,\n    typename = decltype(T::constructor),\n    typename = ArgumentIndexes<decltype(T::constructor)>>\nstruct ConstructorCallback;\n\ntemplate <typename TypeWrapper, typename T, typename... Args, size_t... indexes>\nstruct ConstructorCallback<TypeWrapper, T, Ref<T>(Args...), kj::_::Indexes<indexes...>> {\n  static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {\n    liftKj(args, [&]() {\n      auto isolate = args.GetIsolate();\n\n      throwIfConstructorCalledAsFunction(args, typeid(T));\n\n      auto context = isolate->GetCurrentContext();\n      auto& js = Lock::from(isolate);\n      auto obj = args.This();\n      KJ_ASSERT(obj->InternalFieldCount() == Wrappable::INTERNAL_FIELD_COUNT);\n\n      auto& wrapper = TypeWrapper::from(isolate);\n\n      Ref<T> ptr = T::constructor(wrapper.template unwrap<Args>(js, context, args, indexes,\n          TypeErrorContext::constructorArgument(typeid(T), indexes))...);\n      if constexpr (T::jsgHasReflection) {\n        ptr->jsgInitReflection(wrapper);\n      }\n      ptr.attachWrapper(isolate, obj);\n    });\n  }\n};\n\n// Specialization for constructors that take `Lock&` as their first parameter.\ntemplate <typename TypeWrapper, typename T, typename... Args, size_t... indexes>\nstruct ConstructorCallback<TypeWrapper, T, Ref<T>(Lock&, Args...), kj::_::Indexes<indexes...>> {\n  static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {\n    liftKj(args, [&]() {\n      auto isolate = args.GetIsolate();\n\n      throwIfConstructorCalledAsFunction(args, typeid(T));\n\n      auto context = isolate->GetCurrentContext();\n      auto& js = Lock::from(isolate);\n      auto obj = args.This();\n      KJ_ASSERT(obj->InternalFieldCount() == Wrappable::INTERNAL_FIELD_COUNT);\n\n      auto& wrapper = TypeWrapper::from(isolate);\n\n      Ref<T> ptr = T::constructor(Lock::from(isolate),\n          wrapper.template unwrap<Args>(js, context, args, indexes,\n              TypeErrorContext::constructorArgument(typeid(T), indexes))...);\n      if constexpr (T::jsgHasReflection) {\n        ptr->jsgInitReflection(wrapper);\n      }\n      ptr.attachWrapper(isolate, obj);\n    });\n  }\n};\n\n// Specialization for constructors that take `const v8::FunctionCallbackInfo<v8::Value>&` as\n// their first parameter.\ntemplate <typename TypeWrapper, typename T, typename... Args, size_t... indexes>\nstruct ConstructorCallback<TypeWrapper,\n    T,\n    Ref<T>(const v8::FunctionCallbackInfo<v8::Value>&, Args...),\n    kj::_::Indexes<indexes...>> {\n  static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {\n    liftKj(args, [&]() {\n      auto isolate = args.GetIsolate();\n\n      throwIfConstructorCalledAsFunction(args, typeid(T));\n\n      auto context = isolate->GetCurrentContext();\n      auto& js = Lock::from(isolate);\n      auto obj = args.This();\n      KJ_ASSERT(obj->InternalFieldCount() == Wrappable::INTERNAL_FIELD_COUNT);\n\n      auto& wrapper = TypeWrapper::from(isolate);\n\n      Ref<T> ptr = T::constructor(args,\n          wrapper.template unwrap<Args>(js, context, args, indexes,\n              TypeErrorContext::constructorArgument(typeid(T), indexes))...);\n      if constexpr (T::jsgHasReflection) {\n        ptr->jsgInitReflection(wrapper);\n      }\n      ptr.attachWrapper(isolate, obj);\n    });\n  }\n};\n\ntemplate <typename TypeWrapper, typename T, typename... Args, size_t... indexes>\nstruct ConstructorCallback<TypeWrapper, T, Unimplemented(Args...), kj::_::Indexes<indexes...>> {\n  static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {\n    scheduleUnimplementedConstructorError(args, typeid(T));\n  }\n};\n\n// Implements the V8 callback function for calling a method of the C++ class.\ntemplate <typename TypeWrapper,\n    const char* methodName,\n    bool isContext,\n    typename T,\n    typename Method,\n    Method method,\n    typename Indexes>\nstruct MethodCallback;\n\ntemplate <typename TypeWrapper,\n    const char* methodName,\n    bool isContext,\n    typename T,\n    typename U,\n    typename Ret,\n    typename... Args,\n    Ret (U::*method)(Args...),\n    size_t... indexes>\nstruct MethodCallback<TypeWrapper,\n    methodName,\n    isContext,\n    T,\n    Ret (U::*)(Args...),\n    method,\n    kj::_::Indexes<indexes...>> {\n\n  static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {\n    if constexpr (TypeWrapper::trackCallCounts) {\n      callCounter.slow++;\n    }\n\n    liftKj(args, [&]() {\n      auto isolate = args.GetIsolate();\n      auto context = isolate->GetCurrentContext();\n      auto obj = args.This();\n      auto& wrapper = TypeWrapper::from(isolate);\n      auto& lock = Lock::from(isolate);\n      auto& self = extractInternalPointer<T, isContext>(context, obj);\n      if constexpr (isVoid<Ret>()) {\n        (self.*method)(wrapper.template unwrap<Args>(lock, context, args, indexes,\n            TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);\n      } else {\n        return wrapper.wrap(lock, context, obj,\n            (self.*method)(wrapper.template unwrap<Args>(lock, context, args, indexes,\n                TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...));\n      }\n    });\n  }\n\n  template <typename ReturnType = Ret>\n  static ReturnType fastCallback(v8::Local<v8::Object> receiver,\n      FastApiJSGToV8<Args>::value... fastArgs,\n      v8::FastApiCallbackOptions& options) {\n    if constexpr (TypeWrapper::trackCallCounts) {\n      callCounter.fast++;\n    }\n    auto isolate = options.isolate;\n    v8::HandleScope handleScope(isolate);\n    auto context = isolate->GetCurrentContext();\n    auto& js = Lock::from(isolate);\n    auto& self = extractInternalPointer<T, isContext>(context, receiver);\n    auto& wrapper = TypeWrapper::from(isolate);\n\n    return liftKj<Ret>(isolate, [&]() {\n      return (self.*method)(wrapper.template unwrapFastApi<Args>(js, context, fastArgs,\n          TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);\n    });\n  }\n};\n\n// Specialization for methods that take `Lock&` as their first parameter.\ntemplate <typename TypeWrapper,\n    const char* methodName,\n    bool isContext,\n    typename T,\n    typename U,\n    typename Ret,\n    typename... Args,\n    Ret (U::*method)(Lock&, Args...),\n    size_t... indexes>\nstruct MethodCallback<TypeWrapper,\n    methodName,\n    isContext,\n    T,\n    Ret (U::*)(Lock&, Args...),\n    method,\n    kj::_::Indexes<indexes...>> {\n  static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {\n    if constexpr (TypeWrapper::trackCallCounts) {\n      callCounter.slow++;\n    }\n    liftKj(args, [&]() {\n      auto isolate = args.GetIsolate();\n      auto context = isolate->GetCurrentContext();\n      auto obj = args.This();\n      auto& wrapper = TypeWrapper::from(isolate);\n      auto& self = extractInternalPointer<T, isContext>(context, obj);\n      auto& lock = Lock::from(isolate);\n      if constexpr (isVoid<Ret>()) {\n        (self.*method)(lock,\n            wrapper.template unwrap<Args>(lock, context, args, indexes,\n                TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);\n      } else {\n        return wrapper.wrap(lock, context, obj,\n            (self.*method)(lock,\n                wrapper.template unwrap<Args>(lock, context, args, indexes,\n                    TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...));\n      }\n    });\n  }\n\n  template <typename ReturnType = Ret>\n  static ReturnType fastCallback(v8::Local<v8::Object> receiver,\n      FastApiJSGToV8<Args>::value... fastArgs,\n      v8::FastApiCallbackOptions& options) {\n    if constexpr (TypeWrapper::trackCallCounts) {\n      callCounter.fast++;\n    }\n    auto isolate = options.isolate;\n    v8::HandleScope handleScope(isolate);\n    auto context = isolate->GetCurrentContext();\n    auto& self = extractInternalPointer<T, isContext>(context, receiver);\n    auto& lock = Lock::from(isolate);\n    auto& wrapper = TypeWrapper::from(isolate);\n\n    return liftKj<Ret>(isolate, [&]() {\n      return (self.*method)(lock,\n          wrapper.template unwrapFastApi<Args>(lock, context, fastArgs,\n              TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);\n    });\n  }\n};\n\n// Specialization for methods that take `const v8::FunctionCallbackInfo<v8::Value>&` as their\n// first parameter.\ntemplate <typename TypeWrapper,\n    const char* methodName,\n    bool isContext,\n    typename T,\n    typename U,\n    typename Ret,\n    typename... Args,\n    Ret (U::*method)(const v8::FunctionCallbackInfo<v8::Value>&, Args...),\n    size_t... indexes>\nstruct MethodCallback<TypeWrapper,\n    methodName,\n    isContext,\n    T,\n    Ret (U::*)(const v8::FunctionCallbackInfo<v8::Value>&, Args...),\n    method,\n    kj::_::Indexes<indexes...>> {\n  static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {\n    liftKj(args, [&]() {\n      auto isolate = args.GetIsolate();\n      auto context = isolate->GetCurrentContext();\n      auto obj = args.This();\n      auto& wrapper = TypeWrapper::from(isolate);\n      auto& lock = Lock::from(isolate);\n      auto& self = extractInternalPointer<T, isContext>(context, obj);\n      if constexpr (isVoid<Ret>()) {\n        (self.*method)(args,\n            wrapper.template unwrap<Args>(lock, context, args, indexes,\n                TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);\n      } else {\n        return wrapper.wrap(lock, context, obj,\n            (self.*method)(args,\n                wrapper.template unwrap<Args>(lock, context, args, indexes,\n                    TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...));\n      }\n    });\n  }\n};\n\ntemplate <typename TypeWrapper,\n    const char* methodName,\n    bool isContext,\n    typename T,\n    typename U,\n    typename... Args,\n    Unimplemented (U::*method)(Args...),\n    size_t... indexes>\nstruct MethodCallback<TypeWrapper,\n    methodName,\n    isContext,\n    T,\n    Unimplemented (U::*)(Args...),\n    method,\n    kj::_::Indexes<indexes...>> {\n  static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {\n    scheduleUnimplementedMethodError(args, typeid(T), methodName);\n  }\n};\n\n// Implements the V8 callback function for calling a static method of the C++ class.\n//\n// This is separate from MethodCallback<> because we need to know the interface type, T, and it\n// won't be deducible from Method when Method is a C++ static member function.\n//\n// In the explicit specializations of this template, we use TypeErrorContext::methodArgument() for\n// generating error messages, rather than concoct a new error message format specifically for static\n// methods. This matches Chrome's behavior.\n\ntemplate <typename TypeWrapper,\n    const char* methodName,\n    typename T,\n    typename Method,\n    Method* method,\n    typename Indexes>\nstruct StaticMethodCallback;\n\ntemplate <typename TypeWrapper,\n    const char* methodName,\n    typename T,\n    typename Ret,\n    typename... Args,\n    Ret (*method)(Args...),\n    size_t... indexes>\nstruct StaticMethodCallback<TypeWrapper,\n    methodName,\n    T,\n    Ret(Args...),\n    method,\n    kj::_::Indexes<indexes...>> {\n\n  static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {\n    if constexpr (TypeWrapper::trackCallCounts) {\n      callCounter.slow++;\n    }\n    liftKj(args, [&]() {\n      auto isolate = args.GetIsolate();\n      auto context = isolate->GetCurrentContext();\n      auto& wrapper = TypeWrapper::from(isolate);\n      auto& lock = Lock::from(isolate);\n      if constexpr (isVoid<Ret>()) {\n        (*method)(wrapper.template unwrap<Args>(lock, context, args, indexes,\n            TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);\n      } else {\n        return wrapper.wrap(lock, context, kj::none,\n            (*method)(wrapper.template unwrap<Args>(lock, context, args, indexes,\n                TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...));\n      }\n    });\n  }\n\n  template <typename ReturnType = Ret>\n  static ReturnType fastCallback(v8::Local<v8::Object> receiver,\n      FastApiJSGToV8<Args>::value... fastArgs,\n      v8::FastApiCallbackOptions& options) {\n    if constexpr (TypeWrapper::trackCallCounts) {\n      callCounter.fast++;\n    }\n    auto isolate = options.isolate;\n    v8::HandleScope handleScope(isolate);\n    auto context = isolate->GetCurrentContext();\n    auto& lock = Lock::from(isolate);\n    auto& wrapper = TypeWrapper::from(isolate);\n\n    return liftKj<Ret>(isolate, [&]() {\n      return (*method)(wrapper.template unwrapFastApi<Args>(lock, context, fastArgs,\n          TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);\n    });\n  }\n};\n\n// Specialization for methods that take `Lock&` as their first parameter.\ntemplate <typename TypeWrapper,\n    const char* methodName,\n    typename T,\n    typename Ret,\n    typename... Args,\n    Ret (*method)(Lock&, Args...),\n    size_t... indexes>\nstruct StaticMethodCallback<TypeWrapper,\n    methodName,\n    T,\n    Ret(Lock&, Args...),\n    method,\n    kj::_::Indexes<indexes...>> {\n  static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {\n    if constexpr (TypeWrapper::trackCallCounts) {\n      callCounter.slow++;\n    }\n\n    liftKj(args, [&]() {\n      auto isolate = args.GetIsolate();\n      auto context = isolate->GetCurrentContext();\n      auto& wrapper = TypeWrapper::from(isolate);\n      auto& lock = Lock::from(isolate);\n      if constexpr (isVoid<Ret>()) {\n        (*method)(lock,\n            wrapper.template unwrap<Args>(lock, context, args, indexes,\n                TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);\n      } else {\n        return wrapper.wrap(lock, context, kj::none,\n            (*method)(lock,\n                wrapper.template unwrap<Args>(lock, context, args, indexes,\n                    TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...));\n      }\n    });\n  }\n\n  template <typename ReturnType = Ret>\n  static ReturnType fastCallback(v8::Local<v8::Object> receiver,\n      FastApiJSGToV8<Args>::value... fastArgs,\n      v8::FastApiCallbackOptions& options) {\n    if constexpr (TypeWrapper::trackCallCounts) {\n      callCounter.fast++;\n    }\n    auto isolate = options.isolate;\n    v8::HandleScope handleScope(isolate);\n    auto context = isolate->GetCurrentContext();\n    auto& lock = Lock::from(isolate);\n    auto& wrapper = TypeWrapper::from(isolate);\n\n    return liftKj<Ret>(isolate, [&]() {\n      return (*method)(lock,\n          wrapper.template unwrapFastApi<Args>(lock, context, fastArgs,\n              TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);\n    });\n  }\n};\n\n// Specialization for methods that take `const v8::FunctionCallbackInfo<v8::Value>&` as their\n// first parameter.\ntemplate <typename TypeWrapper,\n    const char* methodName,\n    typename T,\n    typename Ret,\n    typename... Args,\n    Ret (*method)(const v8::FunctionCallbackInfo<v8::Value>&, Args...),\n    size_t... indexes>\nstruct StaticMethodCallback<TypeWrapper,\n    methodName,\n    T,\n    Ret(const v8::FunctionCallbackInfo<v8::Value>&, Args...),\n    method,\n    kj::_::Indexes<indexes...>> {\n\n  static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {\n    liftKj(args, [&]() {\n      auto isolate = args.GetIsolate();\n      auto context = isolate->GetCurrentContext();\n      auto& lock = Lock::from(isolate);\n      auto& wrapper = TypeWrapper::from(isolate);\n      if constexpr (isVoid<Ret>()) {\n        (*method)(args,\n            wrapper.template unwrap<Args>(lock, context, args, indexes,\n                TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...);\n      } else {\n        return wrapper.wrap(lock, context, kj::none,\n            (*method)(args,\n                wrapper.template unwrap<Args>(lock, context, args, indexes,\n                    TypeErrorContext::methodArgument(typeid(T), methodName, indexes))...));\n      }\n    });\n  }\n};\n\ntemplate <typename TypeWrapper,\n    const char* methodName,\n    typename T,\n    typename... Args,\n    Unimplemented (*method)(Args...),\n    size_t... indexes>\nstruct StaticMethodCallback<TypeWrapper,\n    methodName,\n    T,\n    Unimplemented(Args...),\n    method,\n    kj::_::Indexes<indexes...>> {\n\n  static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {\n    scheduleUnimplementedMethodError(args, typeid(T), methodName);\n  }\n};\n\n// Implements the V8 callback function for static property getters.\ntemplate <typename TypeWrapper,\n    const char* propertyName,\n    typename T,\n    typename Getter,\n    Getter* getter>\nstruct StaticPropertyCallback;\n\ntemplate <typename TypeWrapper, const char* propertyName, typename T, typename Ret, Ret (*getter)()>\nstruct StaticPropertyCallback<TypeWrapper, propertyName, T, Ret(), getter> {\n\n  static void callback(v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& args) {\n    liftKj(args, [&]() {\n      auto isolate = args.GetIsolate();\n      auto context = isolate->GetCurrentContext();\n      auto& wrapper = TypeWrapper::from(isolate);\n      auto& lock = Lock::from(isolate);\n\n      if constexpr (isVoid<Ret>()) {\n        (*getter)();\n      } else {\n        return wrapper.wrap(lock, context, kj::none, (*getter)());\n      }\n    });\n  }\n};\n\n// Specialization for static property getter that takes Lock& as first parameter\ntemplate <typename TypeWrapper,\n    const char* propertyName,\n    typename T,\n    typename Ret,\n    Ret (*getter)(Lock&)>\nstruct StaticPropertyCallback<TypeWrapper, propertyName, T, Ret(Lock&), getter> {\n\n  static void callback(v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& args) {\n    liftKj(args, [&]() {\n      auto isolate = args.GetIsolate();\n      auto context = isolate->GetCurrentContext();\n      auto& wrapper = TypeWrapper::from(isolate);\n      auto& lock = Lock::from(isolate);\n\n      if constexpr (isVoid<Ret>()) {\n        (*getter)(lock);\n      } else {\n        return wrapper.wrap(lock, context, kj::none, (*getter)(lock));\n      }\n    });\n  }\n};\n\n// Implements the V8 callback function for calling a property getter method of a C++ class.\ntemplate <typename TypeWrapper,\n    const char* methodName,\n    typename Method,\n    Method method,\n    bool isContext>\nstruct GetterCallback;\n\n#define JSG_DEFINE_GETTER_CALLBACK_STRUCTS(...)                                                    \\\n  template <typename TypeWrapper, const char* methodName, typename T, typename Ret,                \\\n      typename... Args, Ret (T::*method)(Args...) __VA_ARGS__, bool isContext>                     \\\n  struct GetterCallback<TypeWrapper, methodName, Ret (T::*)(Args...) __VA_ARGS__, method,          \\\n      isContext> {                                                                                 \\\n    static constexpr bool enumerable = true;                                                       \\\n    static void callback(v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& info) {   \\\n      if constexpr (TypeWrapper::trackCallCounts) {                                                \\\n        callCounter.slow++;                                                                        \\\n      }                                                                                            \\\n      liftKj(info, [&]() {                                                                         \\\n        auto isolate = info.GetIsolate();                                                          \\\n        auto context = isolate->GetCurrentContext();                                               \\\n        auto obj = info.HolderV2();                                                                \\\n        auto& js = Lock::from(isolate);                                                            \\\n        auto& wrapper = TypeWrapper::from(isolate);                                                \\\n        /* V8 no longer supports AccessorSignature, so we must manually verify `this`'s type. */   \\\n        if (!isContext &&                                                                          \\\n            !wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) {           \\\n          throwTypeError(isolate, kIllegalInvocation);                                             \\\n        }                                                                                          \\\n        auto& self = extractInternalPointer<T, isContext>(context, obj);                           \\\n        return wrapper.wrap(js, context, obj,                                                      \\\n            (self.*method)(                                                                        \\\n                wrapper.unwrap(js, context, static_cast<kj::Decay<Args>*>(nullptr))...));          \\\n      });                                                                                          \\\n    }                                                                                              \\\n    template <typename ReturnType = Ret>                                                           \\\n    static ReturnType fastCallback(                                                                \\\n        v8::Local<v8::Object> receiver, v8::FastApiCallbackOptions& options) {                     \\\n      if constexpr (TypeWrapper::trackCallCounts) {                                                \\\n        callCounter.fast++;                                                                        \\\n      }                                                                                            \\\n      auto isolate = options.isolate;                                                              \\\n      v8::HandleScope handleScope(isolate);                                                        \\\n      auto context = isolate->GetCurrentContext();                                                 \\\n      auto& lock = Lock::from(isolate);                                                            \\\n      auto& self = extractInternalPointer<T, isContext>(context, receiver);                        \\\n      auto& wrapper = TypeWrapper::from(isolate);                                                  \\\n      return liftKj<ReturnType>(isolate, [&]() {                                                   \\\n        return (self.*method)(wrapper.template unwrapFastApi<Args>(                                \\\n            lock, context, static_cast<kj::Decay<Args>*>(nullptr))...);                            \\\n      });                                                                                          \\\n    }                                                                                              \\\n  };                                                                                               \\\n                                                                                                   \\\n  /* Specialization for methods that take `Lock&` as their first parameter. */                     \\\n  template <typename TypeWrapper, const char* methodName, typename T, typename Ret,                \\\n      typename... Args, Ret (T::*method)(Lock&, Args...) __VA_ARGS__, bool isContext>              \\\n  struct GetterCallback<TypeWrapper, methodName, Ret (T::*)(Lock&, Args...) __VA_ARGS__, method,   \\\n      isContext> {                                                                                 \\\n    static constexpr bool enumerable = true;                                                       \\\n    static void callback(v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& info) {   \\\n      if constexpr (TypeWrapper::trackCallCounts) {                                                \\\n        callCounter.slow++;                                                                        \\\n      }                                                                                            \\\n      liftKj(info, [&]() {                                                                         \\\n        auto isolate = info.GetIsolate();                                                          \\\n        auto context = isolate->GetCurrentContext();                                               \\\n        auto& js = Lock::from(isolate);                                                            \\\n        auto obj = info.HolderV2();                                                                \\\n        auto& wrapper = TypeWrapper::from(isolate);                                                \\\n        /* V8 no longer supports AccessorSignature, so we must manually verify `this`'s type. */   \\\n        if (!isContext &&                                                                          \\\n            !wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) {           \\\n          throwTypeError(isolate, kIllegalInvocation);                                             \\\n        }                                                                                          \\\n        auto& self = extractInternalPointer<T, isContext>(context, obj);                           \\\n        return wrapper.wrap(js, context, obj,                                                      \\\n            (self.*method)(                                                                        \\\n                js, wrapper.unwrap(js, context, static_cast<kj::Decay<Args>*>(nullptr))...));      \\\n      });                                                                                          \\\n    }                                                                                              \\\n    template <typename ReturnType = Ret>                                                           \\\n    static ReturnType fastCallback(                                                                \\\n        v8::Local<v8::Object> receiver, v8::FastApiCallbackOptions& options) {                     \\\n      if constexpr (TypeWrapper::trackCallCounts) {                                                \\\n        callCounter.fast++;                                                                        \\\n      }                                                                                            \\\n      auto isolate = options.isolate;                                                              \\\n      v8::HandleScope handleScope(isolate);                                                        \\\n      auto context = isolate->GetCurrentContext();                                                 \\\n      auto& js = Lock::from(isolate);                                                              \\\n      auto& self = extractInternalPointer<T, isContext>(context, receiver);                        \\\n      auto& wrapper = TypeWrapper::from(isolate);                                                  \\\n      return liftKj<ReturnType>(isolate, [&]() {                                                   \\\n        return (self.*method)(js,                                                                  \\\n            wrapper.template unwrapFastApi<Args>(                                                  \\\n                js, context, static_cast<kj::Decay<Args>*>(nullptr))...);                          \\\n      });                                                                                          \\\n    }                                                                                              \\\n  };                                                                                               \\\n                                                                                                   \\\n  template <typename TypeWrapper, const char* propertyName, typename T,                            \\\n      Unimplemented (T::*method)() __VA_ARGS__, bool isContext>                                    \\\n  struct GetterCallback<TypeWrapper, propertyName, Unimplemented (T::*)() __VA_ARGS__, method,     \\\n      isContext> {                                                                                 \\\n    static constexpr bool enumerable = false;                                                      \\\n    static void callback(v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& info) {   \\\n      scheduleUnimplementedPropertyError(info.GetIsolate(), typeid(T), propertyName);              \\\n    }                                                                                              \\\n    template <typename ReturnType = Unimplemented>                                                 \\\n    static ReturnType fastCallback(                                                                \\\n        v8::Local<v8::Object> receiver, v8::FastApiCallbackOptions& options) {                     \\\n      scheduleUnimplementedPropertyError(options.isolate, typeid(T), propertyName);                \\\n      if constexpr (!isVoid<ReturnType>()) {                                                       \\\n        return ReturnType{};                                                                       \\\n      }                                                                                            \\\n    };                                                                                             \\\n  };\n\nJSG_DEFINE_GETTER_CALLBACK_STRUCTS()\nJSG_DEFINE_GETTER_CALLBACK_STRUCTS(const)\n\n#undef JSG_DEFINE_GETTER_CALLBACK_STRUCTS\n\ntemplate <typename TypeWrapper,\n    const char* methodName,\n    typename Method,\n    Method method,\n    bool isContext>\nstruct PropertyGetterCallback;\n\n#define JSG_DEFINE_PROPERTY_GETTER_CALLBACK_STRUCTS(...)                                           \\\n  template <typename TypeWrapper, const char* methodName, typename T, typename Ret,                \\\n      typename... Args, Ret (T::*method)(Args...) __VA_ARGS__, bool isContext>                     \\\n  struct PropertyGetterCallback<TypeWrapper, methodName, Ret (T::*)(Args...) __VA_ARGS__, method,  \\\n      isContext> {                                                                                 \\\n    static constexpr bool enumerable = true;                                                       \\\n    static void callback(const v8::FunctionCallbackInfo<v8::Value>& info) {                        \\\n      if constexpr (TypeWrapper::trackCallCounts) {                                                \\\n        callCounter.slow++;                                                                        \\\n      }                                                                                            \\\n      liftKj(info, [&]() {                                                                         \\\n        auto isolate = info.GetIsolate();                                                          \\\n        auto context = isolate->GetCurrentContext();                                               \\\n        auto& js = Lock::from(isolate);                                                            \\\n        auto obj = info.This();                                                                    \\\n        auto& wrapper = TypeWrapper::from(isolate);                                                \\\n        /* V8 no longer supports AccessorSignature, so we must manually verify `this`'s type. */   \\\n        if (!isContext &&                                                                          \\\n            !wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) {           \\\n          throwTypeError(isolate, kIllegalInvocation);                                             \\\n        }                                                                                          \\\n        auto& self = extractInternalPointer<T, isContext>(context, obj);                           \\\n        return wrapper.wrap(js, context, obj,                                                      \\\n            (self.*method)(                                                                        \\\n                wrapper.unwrap(js, context, static_cast<kj::Decay<Args>*>(nullptr))...));          \\\n      });                                                                                          \\\n    }                                                                                              \\\n    template <typename ReturnType = Ret>                                                           \\\n    static ReturnType fastCallback(                                                                \\\n        v8::Local<v8::Object> receiver, v8::FastApiCallbackOptions& options) {                     \\\n      if constexpr (TypeWrapper::trackCallCounts) {                                                \\\n        callCounter.fast++;                                                                        \\\n      }                                                                                            \\\n      auto isolate = options.isolate;                                                              \\\n      v8::HandleScope handleScope(isolate);                                                        \\\n      auto context = isolate->GetCurrentContext();                                                 \\\n      auto& self = extractInternalPointer<T, isContext>(context, receiver);                        \\\n      auto& wrapper = TypeWrapper::from(isolate);                                                  \\\n      auto& js = Lock::from(isolate);                                                              \\\n                                                                                                   \\\n      return liftKj<ReturnType>(isolate, [&]() -> ReturnType {                                     \\\n        return (self.*method)(                                                                     \\\n            wrapper.unwrap(js, context, static_cast<kj::Decay<Args>*>(nullptr))...);               \\\n      });                                                                                          \\\n    }                                                                                              \\\n  };                                                                                               \\\n                                                                                                   \\\n  /* Specialization for methods that take `Lock&` as their first parameter. */                     \\\n  template <typename TypeWrapper, const char* methodName, typename T, typename Ret,                \\\n      typename... Args, Ret (T::*method)(Lock&, Args...) __VA_ARGS__, bool isContext>              \\\n  struct PropertyGetterCallback<TypeWrapper, methodName, Ret (T::*)(Lock&, Args...) __VA_ARGS__,   \\\n      method, isContext> {                                                                         \\\n    static constexpr bool enumerable = true;                                                       \\\n    static void callback(const v8::FunctionCallbackInfo<v8::Value>& info) {                        \\\n      if constexpr (TypeWrapper::trackCallCounts) {                                                \\\n        callCounter.slow++;                                                                        \\\n      }                                                                                            \\\n      liftKj(info, [&]() {                                                                         \\\n        auto isolate = info.GetIsolate();                                                          \\\n        auto context = isolate->GetCurrentContext();                                               \\\n        auto& js = Lock::from(isolate);                                                            \\\n        auto obj = info.This();                                                                    \\\n        auto& wrapper = TypeWrapper::from(isolate);                                                \\\n        /* V8 no longer supports AccessorSignature, so we must manually verify `this`'s type. */   \\\n        if (!isContext &&                                                                          \\\n            !wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) {           \\\n          throwTypeError(isolate, kIllegalInvocation);                                             \\\n        }                                                                                          \\\n        auto& self = extractInternalPointer<T, isContext>(context, obj);                           \\\n        return wrapper.wrap(js, context, obj,                                                      \\\n            (self.*method)(                                                                        \\\n                js, wrapper.unwrap(js, context, static_cast<kj::Decay<Args>*>(nullptr))...));      \\\n      });                                                                                          \\\n    }                                                                                              \\\n    template <typename ReturnType = Ret>                                                           \\\n    static ReturnType fastCallback(                                                                \\\n        v8::Local<v8::Object> receiver, v8::FastApiCallbackOptions& options) {                     \\\n      if constexpr (TypeWrapper::trackCallCounts) {                                                \\\n        callCounter.fast++;                                                                        \\\n      }                                                                                            \\\n      auto isolate = options.isolate;                                                              \\\n      v8::HandleScope handleScope(isolate);                                                        \\\n      auto context = isolate->GetCurrentContext();                                                 \\\n      auto& self = extractInternalPointer<T, isContext>(context, receiver);                        \\\n      auto& js = Lock::from(isolate);                                                              \\\n      auto& wrapper = TypeWrapper::from(isolate);                                                  \\\n                                                                                                   \\\n      return liftKj<ReturnType>(isolate, [&]() -> ReturnType {                                     \\\n        return (self.*method)(                                                                     \\\n            js, wrapper.unwrap(js, context, static_cast<kj::Decay<Args>*>(nullptr))...);           \\\n      });                                                                                          \\\n    }                                                                                              \\\n  };                                                                                               \\\n  template <typename TypeWrapper, const char* propertyName, typename T,                            \\\n      Unimplemented (T::*method)() __VA_ARGS__, bool isContext>                                    \\\n  struct PropertyGetterCallback<TypeWrapper, propertyName, Unimplemented (T::*)() __VA_ARGS__,     \\\n      method, isContext> {                                                                         \\\n    static constexpr bool enumerable = false;                                                      \\\n    static void callback(const v8::FunctionCallbackInfo<v8::Value>& info) {                        \\\n      scheduleUnimplementedPropertyError(info.GetIsolate(), typeid(T), propertyName);              \\\n    }                                                                                              \\\n    template <typename ReturnType = Unimplemented>                                                 \\\n    static ReturnType fastCallback(                                                                \\\n        v8::Local<v8::Object> receiver, v8::FastApiCallbackOptions& options) {                     \\\n      scheduleUnimplementedPropertyError(options.isolate, typeid(T), propertyName);                \\\n      if constexpr (!isVoid<ReturnType>()) {                                                       \\\n        return ReturnType{};                                                                       \\\n      }                                                                                            \\\n    }                                                                                              \\\n  };\n\nJSG_DEFINE_PROPERTY_GETTER_CALLBACK_STRUCTS()\nJSG_DEFINE_PROPERTY_GETTER_CALLBACK_STRUCTS(const)\n\n// Implements the V8 callback function for calling a property setter method of a C++ class.\ntemplate <typename TypeWrapper,\n    const char* methodName,\n    typename Method,\n    Method method,\n    bool isContext>\nstruct SetterCallback;\n\ntemplate <typename TypeWrapper,\n    const char* methodName,\n    typename T,\n    typename Arg,\n    void (T::*method)(Arg),\n    bool isContext>\nstruct SetterCallback<TypeWrapper, methodName, void (T::*)(Arg), method, isContext> {\n  static void callback(\n      v8::Local<v8::Name>, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info) {\n    liftKj(info, [&]() {\n      auto isolate = info.GetIsolate();\n      auto context = isolate->GetCurrentContext();\n      auto& js = Lock::from(isolate);\n      auto obj = info.HolderV2();\n      auto& wrapper = TypeWrapper::from(isolate);\n      // V8 no longer supports AccessorSignature, so we must manually verify `this`'s type.\n      if (!isContext && !wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) {\n        throwTypeError(isolate, kIllegalInvocation);\n      }\n      auto& self = extractInternalPointer<T, isContext>(context, obj);\n      (self.*method)(wrapper.template unwrap<Arg>(\n          js, context, value, TypeErrorContext::setterArgument(typeid(T), methodName)));\n    });\n  }\n};\n\n// Specialization for methods that take `Lock&` as their first parameter.\ntemplate <typename TypeWrapper,\n    const char* methodName,\n    typename T,\n    typename Arg,\n    void (T::*method)(Lock&, Arg),\n    bool isContext>\nstruct SetterCallback<TypeWrapper, methodName, void (T::*)(Lock&, Arg), method, isContext> {\n  static void callback(\n      v8::Local<v8::Name>, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info) {\n    liftKj(info, [&]() {\n      auto isolate = info.GetIsolate();\n      auto context = isolate->GetCurrentContext();\n      auto obj = info.HolderV2();\n      auto& wrapper = TypeWrapper::from(isolate);\n      // V8 no longer supports AccessorSignature, so we must manually verify `this`'s type.\n      if (!isContext && !wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) {\n        throwTypeError(isolate, kIllegalInvocation);\n      }\n      auto& self = extractInternalPointer<T, isContext>(context, obj);\n      auto& js = Lock::from(isolate);\n      (self.*method)(js,\n          wrapper.template unwrap<Arg>(\n              js, context, value, TypeErrorContext::setterArgument(typeid(T), methodName)));\n    });\n  }\n};\n\ntemplate <typename TypeWrapper,\n    const char* methodName,\n    typename Method,\n    Method method,\n    bool isContext>\nstruct PropertySetterCallback;\n\ntemplate <typename TypeWrapper,\n    const char* methodName,\n    typename T,\n    typename Arg,\n    void (T::*method)(Arg),\n    bool isContext>\nstruct PropertySetterCallback<TypeWrapper, methodName, void (T::*)(Arg), method, isContext> {\n  static void callback(const v8::FunctionCallbackInfo<v8::Value>& info) {\n    if constexpr (TypeWrapper::trackCallCounts) {\n      callCounter.slow++;\n    }\n    liftKj(info, [&]() {\n      auto isolate = info.GetIsolate();\n      auto context = isolate->GetCurrentContext();\n      auto& js = Lock::from(isolate);\n      auto obj = info.This();\n      auto& wrapper = TypeWrapper::from(isolate);\n      // V8 no longer supports AccessorSignature, so we must manually verify `this`'s type.\n      if (!isContext && !wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) {\n        throwTypeError(isolate, kIllegalInvocation);\n      }\n      auto& self = extractInternalPointer<T, isContext>(context, obj);\n      (self.*method)(wrapper.template unwrap<Arg>(\n          js, context, info[0], TypeErrorContext::setterArgument(typeid(T), methodName)));\n    });\n  }\n\n  template <typename ReturnType = void>\n  static ReturnType fastCallback(v8::Local<v8::Object> receiver,\n      FastApiJSGToV8<Arg>::value fastArgs,\n      v8::FastApiCallbackOptions& options) {\n    if constexpr (TypeWrapper::trackCallCounts) {\n      callCounter.fast++;\n    }\n    auto isolate = options.isolate;\n    v8::HandleScope handleScope(isolate);\n    auto context = isolate->GetCurrentContext();\n    auto& js = Lock::from(isolate);\n    auto& self = extractInternalPointer<T, isContext>(context, receiver);\n    auto& wrapper = TypeWrapper::from(isolate);\n\n    liftKj<void>(isolate, [&]() -> void {\n      (self.*method)(wrapper.template unwrapFastApi<Arg>(\n          js, context, fastArgs, TypeErrorContext::setterArgument(typeid(T), methodName)));\n    });\n  }\n};\n\n// Specialization for methods that take `Lock&` as their first parameter.\ntemplate <typename TypeWrapper,\n    const char* methodName,\n    typename T,\n    typename Arg,\n    void (T::*method)(Lock&, Arg),\n    bool isContext>\nstruct PropertySetterCallback<TypeWrapper, methodName, void (T::*)(Lock&, Arg), method, isContext> {\n  static void callback(const v8::FunctionCallbackInfo<v8::Value>& info) {\n    if constexpr (TypeWrapper::trackCallCounts) {\n      callCounter.slow++;\n    }\n    liftKj(info, [&]() {\n      auto isolate = info.GetIsolate();\n      auto context = isolate->GetCurrentContext();\n      auto obj = info.This();\n      auto& wrapper = TypeWrapper::from(isolate);\n      // V8 no longer supports AccessorSignature, so we must manually verify `this`'s type.\n      if (!isContext && !wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) {\n        throwTypeError(isolate, kIllegalInvocation);\n      }\n      auto& self = extractInternalPointer<T, isContext>(context, obj);\n      auto& js = Lock::from(isolate);\n      (self.*method)(js,\n          wrapper.template unwrap<Arg>(\n              js, context, info[0], TypeErrorContext::setterArgument(typeid(T), methodName)));\n    });\n  }\n\n  template <typename ReturnType = void>\n  static ReturnType fastCallback(v8::Local<v8::Object> receiver,\n      FastApiJSGToV8<Arg>::value fastArgs,\n      v8::FastApiCallbackOptions& options) {\n    if constexpr (TypeWrapper::trackCallCounts) {\n      callCounter.fast++;\n    }\n    auto isolate = options.isolate;\n    v8::HandleScope handleScope(isolate);\n    auto context = isolate->GetCurrentContext();\n    auto& self = extractInternalPointer<T, isContext>(context, receiver);\n    auto& lock = Lock::from(isolate);\n    auto& wrapper = TypeWrapper::from(isolate);\n\n    liftKj<void>(isolate, [&]() -> void {\n      (self.*method)(lock,\n          wrapper.template unwrapFastApi<Arg>(\n              lock, context, fastArgs, TypeErrorContext::setterArgument(typeid(T), methodName)));\n    });\n  }\n};\n\ntemplate <typename T>\nclass TypeHandler;\n\n// Helper to call T::serialize() and pass along any TypeHandlers it needs.\ntemplate <typename TypeWrapper, typename T, typename Method = decltype(&T::serialize)>\nstruct SerializeInvoker;\ntemplate <typename TypeWrapper, typename T, typename... Types>\nstruct SerializeInvoker<TypeWrapper,\n    T,\n    void (T::*)(Lock&, Serializer&, const TypeHandler<Types>&...)> {\n  static void call(TypeWrapper& wrapper, T& target, Lock& js, Serializer& serializer) {\n    target.serialize(js, serializer, TypeWrapper::template TYPE_HANDLER_INSTANCE<Types>...);\n  }\n};\n\n// Helper to call T::deserialize() and pass along any TypeHandlers it needs, as well as wrap\n// the result.\ntemplate <typename TypeWrapper, typename T, typename Method = decltype(T::deserialize)>\nstruct DeserializeInvoker;\ntemplate <typename TypeWrapper, typename T, typename Ret, typename Tag, typename... Types>\nstruct DeserializeInvoker<TypeWrapper,\n    T,\n    Ret(Lock&, Tag, Deserializer&, const TypeHandler<Types>&...)> {\n  static v8::Local<v8::Object> call(\n      TypeWrapper& wrapper, Lock& js, Tag tag, Deserializer& deserializer) {\n    return wrapper.wrap(js, js.v8Context(), kj::none,\n        T::deserialize(\n            js, tag, deserializer, TypeWrapper::template TYPE_HANDLER_INSTANCE<Types>...));\n  }\n};\n\n// SFINAE helper to detect if a type has a static method called `constructor`.\n// Includes a static_assert to provide a clear error if the method exists but is non-static.\ntemplate <typename T, typename = void>\nconstexpr bool HasConstructorMethod = false;\n\ntemplate <typename T>\nconstexpr bool HasConstructorMethod<T, std::void_t<decltype(&T::constructor)>> = [] {\n  static_assert(!std::is_member_function_pointer_v<decltype(&T::constructor)>,\n      \"JSG_RESOURCE_TYPE constructor must be a static method.\");\n  return true;\n}();\n\n// Expose the global scope type as a nested type under the global scope itself, such that for some\n// global scope type `GlobalScope`, `this.GlobalScope === this.constructor` holds true. Note that\n// this does not actually allow the user to construct a global scope object (unless it has a static\n// C++ constructor member function, of course). It is primarily to allow code like\n// `this instanceof ServiceWorkerGlobalScope` to work correctly.\n//\n// This is implemented manually because the obvious way of doing it causes a crash:\n//\n//     struct GlobalScope {\n//       JSG_RESOURCE_TYPE {\n//         JSG_NESTED_TYPE(GlobalScope);  // BAD!\n//       }\n//     };\nvoid exposeGlobalScopeType(v8::Isolate* isolate, v8::Local<v8::Context> context);\n\nv8::Local<v8::Symbol> getSymbolDispose(v8::Isolate* isolate);\n\n// A configuration type that can be derived from any input type, because it contains nothing.\nclass NullConfiguration {\n public:\n  template <typename T>\n  NullConfiguration(T&&) {}\n};\n\n// TypeWrapper must list this type as its first superclass. The ResourceWrappers that it\n// subclasses will then be able to register themselves in the map.\ntemplate <typename TypeWrapper>\nclass DynamicResourceTypeMap {\n private:\n  using ReflectionInitializer = void(jsg::Object& object, TypeWrapper& wrapper);\n  struct DynamicTypeInfo {\n    v8::Local<v8::FunctionTemplate> tmpl;\n    kj::Maybe<ReflectionInitializer&> reflectionInitializer;\n  };\n\n  DynamicTypeInfo getDynamicTypeInfo(v8::Isolate* isolate, const std::type_info& type) {\n    KJ_IF_SOME(f, resourceTypeMap.find(std::type_index(type))) {\n      return (*f)(static_cast<TypeWrapper&>(*this), isolate);\n    } else {\n      KJ_FAIL_REQUIRE(\n          \"cannot wrap object type that was not registered with JSG_DECLARE_ISOLATE_TYPE\",\n          typeName(type));\n    }\n  }\n\n  using GetTypeInfoFunc = DynamicTypeInfo(TypeWrapper&, v8::Isolate*);\n\n  // Maps type_info values to functions that can be used to get the associated template. Used by\n  // ResourceWrapper.\n  //\n  // Fun fact: Comparing type_index on Linux is a simple pointer comparison. On Windows it is a\n  // string comparison (of the type names). On Mac arm64 it could be either, there's a special bit\n  // in the type_info that indicates if it is known to be unique. See\n  // _LIBCPP_TYPEINFO_COMPARISON_IMPLEMENTATION in <typeinfo> for more.\n  kj::HashMap<std::type_index, GetTypeInfoFunc*> resourceTypeMap;\n\n  // Map types to serializers. Given an `Object`, to serialize it, extract its typeinfo and look\n  // it up in this table. See jsg::Serializer for more about serialization.\n  //\n  // \"Why not just use a virtual function?\" Virtual functions give the wrong semantics for\n  // serialization, since it's essential that we use the serializer for the most-derived class,\n  // not for some parent class. If Foo extends Bar, and Bar is serializable, but Foo does not\n  // declare a serializer, then Foo is *not* serializable. Bar's serializer is not sufficient,\n  // since it doesn't know about Foo's extensions. But to get the right behavior from virtual\n  // calls, Foo would have to explicitly override Bar's serialize method to make it throw an\n  // exception instead. This seems error-prone.\n  //\n  // It also just provides nice symmetry with `deserializerMap`.\n  //\n  // The SerializeFunc() must always start by writing a tag.\n  using SerializeFunc = void(TypeWrapper&, Lock& js, jsg::Object& instance, Serializer& serializer);\n  kj::HashMap<std::type_index, SerializeFunc*> serializerMap;\n\n  // Map tag numbers to deserializer functions.\n  using DeserializeFunc = v8::Local<v8::Object>(\n      TypeWrapper&, Lock& js, uint tag, Deserializer& deserializer);\n  kj::HashMap<uint, DeserializeFunc*> deserializerMap;\n\n  template <typename, typename>\n  friend class ResourceWrapper;\n  template <typename>\n  friend class ObjectWrapper;\n  template <typename>\n  friend class Isolate;\n};\n\n// ======================================================================================\n// WildcardProperty implementation\n\nclass JsValue;\n\ntemplate <typename TypeWrapper, typename T, typename GetNamedMethod, GetNamedMethod getNamedMethod>\nstruct WildcardPropertyCallbacks;\n\ntemplate <typename TypeWrapper,\n    typename T,\n    typename U,\n    typename Ret,\n    kj::Maybe<Ret> (U::*getNamedMethod)(jsg::Lock&, kj::String)>\nstruct WildcardPropertyCallbacks<TypeWrapper,\n    T,\n    kj::Maybe<Ret> (U::*)(jsg::Lock&, kj::String),\n    getNamedMethod>: public v8::NamedPropertyHandlerConfiguration {\n  WildcardPropertyCallbacks()\n      : v8::NamedPropertyHandlerConfiguration(getter,\n            nullptr,\n            query,\n            nullptr,\n            nullptr,\n            nullptr,\n            descriptor,\n            v8::Local<v8::Value>(),\n            static_cast<v8::PropertyHandlerFlags>(\n                static_cast<int>(v8::PropertyHandlerFlags::kNonMasking) |\n                static_cast<int>(v8::PropertyHandlerFlags::kHasNoSideEffect) |\n                static_cast<int>(v8::PropertyHandlerFlags::kOnlyInterceptStrings))) {}\n\n  // Query callback is needed for V8 to properly handle property creation with correct\n  // enumerable attributes when the interceptor is on the instance template.\n  static v8::Intercepted query(\n      v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Integer>& info) {\n    v8::Intercepted result = v8::Intercepted::kNo;\n    liftKj(info, [&]() -> v8::Local<v8::Integer> {\n      auto isolate = info.GetIsolate();\n      auto context = isolate->GetCurrentContext();\n      auto obj = info.HolderV2();\n      auto& wrapper = TypeWrapper::from(isolate);\n      if (!wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) {\n        throwTypeError(isolate, kIllegalInvocation);\n      }\n      auto& self = extractInternalPointer<T, false>(context, obj);\n      auto& lock = Lock::from(isolate);\n      if ((self.*getNamedMethod)(lock, kj::str(name.As<v8::String>())) != kj::none) {\n        result = v8::Intercepted::kYes;\n      }\n      return {};\n    });\n    return result;\n  }\n\n  // We're patching hasOwnProperty to return false for interceptors in v8. Additionally always return false for getOwnPropertyDescriptor.\n  static v8::Intercepted descriptor(\n      v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {\n    return v8::Intercepted::kNo;\n  }\n\n  static v8::Intercepted getter(\n      v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {\n    v8::Intercepted result = v8::Intercepted::kNo;\n    liftKj(info, [&]() -> v8::Local<v8::Value> {\n      auto isolate = info.GetIsolate();\n      auto context = isolate->GetCurrentContext();\n      auto obj = info.HolderV2();\n      auto& wrapper = TypeWrapper::from(isolate);\n      if (!wrapper.getTemplate(isolate, static_cast<T*>(nullptr))->HasInstance(obj)) {\n        throwTypeError(isolate, kIllegalInvocation);\n      }\n      auto& self = extractInternalPointer<T, false>(context, obj);\n      auto& lock = Lock::from(isolate);\n      KJ_IF_SOME(value, (self.*getNamedMethod)(lock, kj::str(name.As<v8::String>()))) {\n        result = v8::Intercepted::kYes;\n        return wrapper.wrap(lock, context, obj, kj::fwd<Ret>(value));\n      } else {\n        // Return an empty handle to indicate the member doesn't exist.\n        return {};\n      }\n    });\n    return result;\n  }\n};\n\n// ======================================================================================\n\n// Used by the JSG_METHOD macro to register a method on a resource type.\ntemplate <typename TypeWrapper, typename Self, bool isContext>\nstruct ResourceTypeBuilder {\n  ResourceTypeBuilder(TypeWrapper& typeWrapper,\n      v8::Isolate* isolate,\n      v8::Local<v8::FunctionTemplate> constructor,\n      v8::Local<v8::ObjectTemplate> instance,\n      v8::Local<v8::ObjectTemplate> prototype,\n      v8::Local<v8::Signature> signature)\n      : typeWrapper(typeWrapper),\n        isolate(isolate),\n        constructor(constructor),\n        instance(instance),\n        prototype(prototype),\n        signature(signature) {\n    // Mark the prototype as belonging to a resource type. Calling `util.inspect()` will see this\n    // symbol and trigger custom inspect handling. This value of this symbol property maps names\n    // names of internal pseudo-properties to symbol-keyed-getters for accessing them, or false if\n    // the property is unimplemented.\n    // See `JSG_INSPECT_PROPERTY` for more details.\n    auto symbol = v8::Symbol::ForApi(isolate, v8StrIntern(isolate, \"kResourceTypeInspect\"_kj));\n    inspectProperties = v8::ObjectTemplate::New(isolate);\n    prototype->Set(symbol, inspectProperties,\n        static_cast<v8::PropertyAttribute>(\n            v8::PropertyAttribute::ReadOnly | v8::PropertyAttribute::DontEnum));\n  }\n\n  template <typename Type, typename GetNamedMethod, GetNamedMethod getNamedMethod>\n  inline void registerWildcardProperty() {\n    auto& resourceWrapper = static_cast<ResourceWrapper<TypeWrapper, Type>&>(typeWrapper);\n    KJ_ASSERT(\n        resourceWrapper.wildcardHandler == kj::none, \"only one wildcard per instance supported\");\n    resourceWrapper.wildcardHandler =\n        WildcardPropertyCallbacks<TypeWrapper, Type, GetNamedMethod, getNamedMethod>{};\n  }\n\n  template <typename Type>\n  inline void registerInherit() {\n    constructor->Inherit(\n        typeWrapper.template getTemplate<isContext>(isolate, static_cast<Type*>(nullptr)));\n    // Propagate wildcard proxy to children. It's a data property, so it should be propagated, but v8 only handles normal data properties.\n    auto& parentWrapper = static_cast<ResourceWrapper<TypeWrapper, Type>&>(typeWrapper);\n    if (parentWrapper.wildcardHandler != kj::none) {\n      auto& selfWrapper = static_cast<ResourceWrapper<TypeWrapper, Self>&>(typeWrapper);\n      KJ_ASSERT(\n          selfWrapper.wildcardHandler == kj::none, \"only one wildcard per instance supported\");\n      selfWrapper.wildcardHandler = parentWrapper.wildcardHandler;\n    }\n  }\n\n  template <const char* name>\n  inline void registerInheritIntrinsic(v8::Intrinsic intrinsic) {\n    auto intrinsicPrototype = v8::FunctionTemplate::New(isolate);\n    intrinsicPrototype->RemovePrototype();\n    auto prototypeString = ::workerd::jsg::v8StrIntern(isolate, \"prototype\");\n    intrinsicPrototype->SetIntrinsicDataProperty(prototypeString, intrinsic);\n    constructor->Inherit(intrinsicPrototype);\n  }\n\n  template <typename Method, Method method>\n  inline void registerCallable() {\n    // Note that we set the call handler on the instance and not the prototype.\n    // TODO(cleanup): Specifying the name (for error messages) as \"(called as function)\" is a bit\n    //   hacky but it's hard to do better while reusing `MethodCallback`.\n    static const char NAME[] = \"(called as function)\";\n    instance->SetCallAsFunctionHandler(&MethodCallback<TypeWrapper, NAME, isContext, Self, Method,\n                                       method, ArgumentIndexes<Method>>::callback);\n  }\n\n  template <const char* name, auto method>\n  inline void registerMethod() {\n    // Per Web IDL, function .length = number of required arguments.\n    constexpr int specLength = requiredArgumentCount<TypeWrapper, decltype(method)>;\n    const int length = getSpecCompliantPropertyAttributes(isolate) ? specLength : 0;\n\n    if constexpr (isFastApiCompatible<decltype(method)>) {\n      if (typeWrapper.isFastApiEnabled()) {\n        auto cFunction = v8::CFunction::Make(MethodCallback<TypeWrapper, name, isContext, Self,\n            decltype(method), method, ArgumentIndexes<decltype(method)>>::template fastCallback<>);\n        auto functionTemplate = v8::FunctionTemplate::NewWithCFunctionOverloads(isolate,\n            &MethodCallback<TypeWrapper, name, isContext, Self, decltype(method), method,\n                ArgumentIndexes<decltype(method)>>::callback,\n            v8::Local<v8::Value>(), signature, length, v8::ConstructorBehavior::kThrow,\n            v8::SideEffectType::kHasSideEffect, {&cFunction, 1});\n\n        prototype->Set(isolate, name, functionTemplate);\n        return;\n      }\n    }\n\n    prototype->Set(isolate, name,\n        v8::FunctionTemplate::New(isolate,\n            &MethodCallback<TypeWrapper, name, isContext, Self, decltype(method), method,\n                ArgumentIndexes<decltype(method)>>::callback,\n            v8::Local<v8::Value>(), signature, length, v8::ConstructorBehavior::kThrow));\n  }\n\n  template <const char* name, typename Method, Method method>\n  inline void registerStaticMethod() {\n    // Per Web IDL, function .length = number of required arguments.\n    constexpr int specLength = requiredArgumentCount<TypeWrapper, Method>;\n    const int length = getSpecCompliantPropertyAttributes(isolate) ? specLength : 0;\n\n    if constexpr (isFastApiCompatible<Method>) {\n      if (typeWrapper.isFastApiEnabled()) {\n        auto cFunction = v8::CFunction::Make(StaticMethodCallback<TypeWrapper, name, Self, Method,\n            method, ArgumentIndexes<Method>>::template fastCallback<>);\n\n        // Create a function template with both slow and fast paths\n        // Notably, we specify an empty signature because a static method invocation will have no holder\n        // object.\n        auto functionTemplate = v8::FunctionTemplate::NewWithCFunctionOverloads(isolate,\n            &StaticMethodCallback<TypeWrapper, name, Self, Method, method,\n                ArgumentIndexes<Method>>::callback,\n            v8::Local<v8::Value>(), v8::Local<v8::Signature>(), length,\n            v8::ConstructorBehavior::kThrow, v8::SideEffectType::kHasSideEffect, {&cFunction, 1});\n        functionTemplate->RemovePrototype();\n        constructor->Set(v8StrIntern(isolate, name), functionTemplate);\n        return;\n      }\n    }\n\n    // Notably, we specify an empty signature because a static method invocation will have no holder\n    // object.\n    auto functionTemplate = v8::FunctionTemplate::New(isolate,\n        &StaticMethodCallback<TypeWrapper, name, Self, Method, method,\n            ArgumentIndexes<Method>>::callback,\n        v8::Local<v8::Value>(), v8::Local<v8::Signature>(), length,\n        v8::ConstructorBehavior::kThrow);\n    functionTemplate->RemovePrototype();\n    constructor->Set(v8StrIntern(isolate, name), functionTemplate);\n  }\n\n  template <const char* name, typename Getter, Getter getter, typename Setter, Setter setter>\n  inline void registerInstanceProperty() {\n    auto v8Name = v8StrIntern(isolate, name);\n\n    using Gcb = GetterCallback<TypeWrapper, name, Getter, getter, isContext>;\n    if (!Gcb::enumerable) {\n      // Mark as unimplemented if `Gcb::enumerable` is `false`. This is only the case when `Getter`\n      // returns `Unimplemented`.\n      inspectProperties->Set(v8Name, v8::False(isolate), v8::PropertyAttribute::ReadOnly);\n    }\n\n    instance->SetNativeDataProperty(v8Name, Gcb::callback,\n        &SetterCallback<TypeWrapper, name, Setter, setter, isContext>::callback,\n        v8::Local<v8::Value>(),\n        Gcb::enumerable ? v8::PropertyAttribute::None : v8::PropertyAttribute::DontEnum);\n  }\n\n  template <const char* name, typename Getter, Getter getter, typename Setter, Setter setter>\n  inline void registerPrototypeProperty() {\n    auto v8Name = v8StrIntern(isolate, name);\n    v8::Local<v8::FunctionTemplate> getterFn;\n    v8::Local<v8::FunctionTemplate> setterFn;\n\n    using Gcb = PropertyGetterCallback<TypeWrapper, name, Getter, getter, isContext>;\n    using Scb = PropertySetterCallback<TypeWrapper, name, Setter, setter, isContext>;\n    if (!Gcb::enumerable) {\n      inspectProperties->Set(v8Name, v8::False(isolate), v8::PropertyAttribute::ReadOnly);\n    }\n\n    const bool specCompliant = getSpecCompliantPropertyAttributes(isolate);\n\n    bool useSlowApi = true;\n    if constexpr (isFastApiCompatible<Getter> && isFastApiCompatible<Setter>) {\n      if (typeWrapper.isFastApiEnabled()) {\n        auto getterCFunction = v8::CFunction::Make(Gcb::template fastCallback<>);\n        getterFn = v8::FunctionTemplate::NewWithCFunctionOverloads(isolate, &Gcb::callback,\n            v8::Local<v8::Value>(), signature, 0, v8::ConstructorBehavior::kThrow,\n            v8::SideEffectType::kHasSideEffect, {&getterCFunction, 1});\n\n        auto setterCFunction = v8::CFunction::Make(Scb::template fastCallback<>);\n        setterFn = v8::FunctionTemplate::NewWithCFunctionOverloads(isolate, &Scb::callback,\n            v8::Local<v8::Value>(), signature, specCompliant ? 1 : 0,\n            v8::ConstructorBehavior::kThrow, v8::SideEffectType::kHasSideEffect,\n            {&setterCFunction, 1});\n\n        useSlowApi = false;\n      }\n    }\n\n    if (useSlowApi) {\n      // Note that we cannot use `SetNativeDataProperty(...)` here the way we do with other\n      // properties because it will not properly handle the prototype chain when it comes\n      // to using setters... which is annoying. It means we end up having to use FunctionTemplates\n      // instead of the more convenient property callbacks.\n      if (specCompliant) {\n        // Per Web IDL, getter .length = 0; setter .length = 1.\n        getterFn = v8::FunctionTemplate::New(\n            isolate, Gcb::callback, v8::Local<v8::Value>(), v8::Local<v8::Signature>(), 0);\n        setterFn = v8::FunctionTemplate::New(\n            isolate, &Scb::callback, v8::Local<v8::Value>(), v8::Local<v8::Signature>(), 1);\n      } else {\n        getterFn = v8::FunctionTemplate::New(isolate, Gcb::callback);\n        setterFn = v8::FunctionTemplate::New(isolate, &Scb::callback);\n      }\n    }\n\n    if (specCompliant) {\n      // Per Web IDL, getter .name = \"get <name>\", setter .name = \"set <name>\".\n      getterFn->SetClassName(v8Str(isolate, kj::str(\"get \", name)));\n      setterFn->SetClassName(v8Str(isolate, kj::str(\"set \", name)));\n    }\n\n    prototype->SetAccessorProperty(v8Name, getterFn, setterFn,\n        Gcb::enumerable ? v8::PropertyAttribute::None : v8::PropertyAttribute::DontEnum);\n  }\n\n  template <const char* name, typename Getter, Getter getter>\n  inline void registerReadonlyInstanceProperty() {\n    auto v8Name = v8StrIntern(isolate, name);\n\n    using Gcb = GetterCallback<TypeWrapper, name, Getter, getter, isContext>;\n    if (!Gcb::enumerable) {\n      inspectProperties->Set(v8Name, v8::False(isolate), v8::PropertyAttribute::ReadOnly);\n    }\n\n    instance->SetNativeDataProperty(v8Name, &Gcb::callback, nullptr, v8::Local<v8::Value>(),\n        Gcb::enumerable ? v8::PropertyAttribute::ReadOnly\n                        : static_cast<v8::PropertyAttribute>(\n                              v8::PropertyAttribute::ReadOnly | v8::PropertyAttribute::DontEnum));\n  }\n\n  template <typename T>\n  inline void registerReadonlyInstanceProperty(kj::StringPtr name, T value) {\n    auto v8Name = v8StrIntern(isolate, name);\n    auto v8Value = typeWrapper.wrap(isolate, kj::none, kj::mv(value));\n    instance->Set(v8Name, v8Value, v8::PropertyAttribute::ReadOnly);\n  }\n\n  template <const char* name, typename Getter, Getter getter>\n  inline void registerReadonlyPrototypeProperty() {\n    auto v8Name = v8StrIntern(isolate, name);\n\n    using Gcb = PropertyGetterCallback<TypeWrapper, name, Getter, getter, isContext>;\n    if (!Gcb::enumerable) {\n      inspectProperties->Set(v8Name, v8::False(isolate), v8::PropertyAttribute::ReadOnly);\n    }\n    v8::Local<v8::FunctionTemplate> getterFn;\n    if (getSpecCompliantPropertyAttributes(isolate)) {\n      // Per Web IDL, getter .name = \"get <name>\", .length = 0.\n      getterFn = v8::FunctionTemplate::New(\n          isolate, Gcb::callback, v8::Local<v8::Value>(), v8::Local<v8::Signature>(), 0);\n      getterFn->SetClassName(v8Str(isolate, kj::str(\"get \", name)));\n    } else {\n      getterFn = v8::FunctionTemplate::New(isolate, Gcb::callback);\n    }\n\n    prototype->SetAccessorProperty(v8Name, getterFn, {},\n        Gcb::enumerable ? v8::PropertyAttribute::ReadOnly\n                        : static_cast<v8::PropertyAttribute>(\n                              v8::PropertyAttribute::DontEnum | v8::PropertyAttribute::ReadOnly));\n  }\n\n  template <const char* name, typename Getter, Getter getter, bool readOnly>\n  inline void registerLazyInstanceProperty() {\n    auto v8Name = v8StrIntern(isolate, name);\n\n    using Gcb = GetterCallback<TypeWrapper, name, Getter, getter, isContext>;\n    if (!Gcb::enumerable) {\n      inspectProperties->Set(v8Name, v8::False(isolate), v8::PropertyAttribute::ReadOnly);\n    }\n\n    v8::PropertyAttribute attributes =\n        Gcb::enumerable ? v8::PropertyAttribute::None : v8::PropertyAttribute::DontEnum;\n    if (readOnly) {\n      attributes = static_cast<v8::PropertyAttribute>(attributes | v8::PropertyAttribute::ReadOnly);\n    }\n    instance->SetLazyDataProperty(v8Name, &Gcb::callback, v8::Local<v8::Value>(), attributes);\n  }\n\n  template <const char* name, typename Getter, Getter getter>\n  inline void registerInspectProperty() {\n    using Gcb = PropertyGetterCallback<TypeWrapper, name, Getter, getter, isContext>;\n\n    auto v8Name = v8StrIntern(isolate, name);\n\n    // Create a new unique symbol so this property can only be accessed through `util.inspect()`\n    auto symbol = v8::Symbol::New(isolate, v8Name);\n    inspectProperties->Set(v8Name, symbol, v8::PropertyAttribute::ReadOnly);\n\n    auto getterFn = v8::FunctionTemplate::New(isolate, &Gcb::callback);\n    prototype->SetAccessorProperty(symbol, getterFn, {},\n        static_cast<v8::PropertyAttribute>(\n            v8::PropertyAttribute::ReadOnly | v8::PropertyAttribute::DontEnum));\n  }\n\n  template <const char* name, typename T>\n  inline void registerStaticConstant(T value) {\n    // The main difference between this and a read-only property is that a static constant has no\n    // getter but is simply a primitive value set at constructor creation time.\n\n    auto v8Name = v8StrIntern(isolate, name);\n    auto v8Value = typeWrapper.wrap(isolate, kj::none, kj::mv(value));\n\n    // Per Web IDL, constants are {writable: false, enumerable: true, configurable: false}.\n    const auto attrs = getSpecCompliantPropertyAttributes(isolate)\n        ? static_cast<v8::PropertyAttribute>(\n              v8::PropertyAttribute::ReadOnly | v8::PropertyAttribute::DontDelete)\n        : v8::PropertyAttribute::ReadOnly;\n    constructor->Set(v8Name, v8Value, attrs);\n    constructor->PrototypeTemplate()->Set(v8Name, v8Value, attrs);\n  }\n\n  template <const char* name, typename Getter, Getter getter>\n  inline void registerStaticProperty() {\n    constructor->SetNativeDataProperty(v8StrIntern(isolate, name),\n        &StaticPropertyCallback<TypeWrapper, name, Self, Getter, getter>::callback, nullptr,\n        v8::Local<v8::Value>(), v8::PropertyAttribute::ReadOnly);\n  }\n\n  template <const char* name, typename Method, Method method>\n  inline void registerIterable() {\n    prototype->Set(v8::Symbol::GetIterator(isolate),\n        v8::FunctionTemplate::New(isolate,\n            &MethodCallback<TypeWrapper, name, isContext, Self, Method, method,\n                ArgumentIndexes<Method>>::callback,\n            v8::Local<v8::Value>(), signature, 0, v8::ConstructorBehavior::kThrow),\n        v8::PropertyAttribute::DontEnum);\n  }\n\n  template <const char* name, typename Method, Method method>\n  inline void registerAsyncIterable() {\n    prototype->Set(v8::Symbol::GetAsyncIterator(isolate),\n        v8::FunctionTemplate::New(isolate,\n            &MethodCallback<TypeWrapper, name, isContext, Self, Method, method,\n                ArgumentIndexes<Method>>::callback,\n            v8::Local<v8::Value>(), signature, 0, v8::ConstructorBehavior::kThrow),\n        v8::PropertyAttribute::DontEnum);\n  }\n\n  template <const char* name, typename Method, Method method>\n  inline void registerDispose() {\n    prototype->Set(v8::Symbol::GetDispose(isolate),\n        v8::FunctionTemplate::New(isolate,\n            &MethodCallback<TypeWrapper, name, isContext, Self, Method, method,\n                ArgumentIndexes<Method>>::callback,\n            v8::Local<v8::Value>(), signature, 0, v8::ConstructorBehavior::kThrow),\n        v8::PropertyAttribute::DontEnum);\n  }\n\n  template <const char* name, typename Method, Method method>\n  inline void registerAsyncDispose() {\n    prototype->Set(v8::Symbol::GetAsyncDispose(isolate),\n        v8::FunctionTemplate::New(isolate,\n            &MethodCallback<TypeWrapper, name, isContext, Self, Method, method,\n                ArgumentIndexes<Method>>::callback,\n            v8::Local<v8::Value>(), signature, 0, v8::ConstructorBehavior::kThrow),\n        v8::PropertyAttribute::DontEnum);\n  }\n\n  template <typename Type, const char* name>\n  inline void registerNestedType() {\n    static_assert(Type::JSG_KIND == ::workerd::jsg::JsgKind::RESOURCE,\n        \"Type is not a resource type, and therefore cannot be declared nested\");\n\n    constexpr auto hasGetTemplate =\n        ::workerd::jsg::isDetected<::workerd::jsg::HasGetTemplateOverload, decltype(typeWrapper),\n            Type>();\n    static_assert(\n        hasGetTemplate, \"Type must be listed in JSG_DECLARE_ISOLATE_TYPE to be declared nested.\");\n\n    auto tmpl = typeWrapper.getTemplate(isolate, static_cast<Type*>(nullptr));\n\n    // Always install on the prototype so that types declared on parent classes are\n    // inherited through FunctionTemplate::Inherit().\n    prototype->Set(isolate, name, tmpl);\n\n    if constexpr (isContext) {\n      if (getSpecCompliantPropertyAttributes(isolate)) {\n        // Per Web IDL, [Exposed] interface objects must be own properties of the global\n        // object with { writable: true, enumerable: false, configurable: true }.  When\n        // building the global scope (isContext == true), also install on the instance\n        // template so that they become own properties of globalThis.\n        //\n        // NOTE: V8's FunctionTemplate::Inherit() does NOT propagate instance-template\n        // properties to child types.  This means nested types declared on a parent\n        // context type (e.g. WorkerGlobalScope) won't automatically appear as own\n        // properties of the child (e.g. ServiceWorkerGlobalScope).  To keep things\n        // simple, the leaf context type must declare all its nested types directly -\n        // don't rely on inheriting nested types from parent context classes.\n        instance->Set(isolate, name, tmpl, v8::PropertyAttribute::DontEnum);\n      }\n    }\n  }\n\n  inline void registerTypeScriptRoot() { /* only needed for RTTI */ }\n\n  template <const char* tsOverride>\n  inline void registerTypeScriptOverride() { /* only needed for RTTI */ }\n\n  template <const char* tsDefine>\n  inline void registerTypeScriptDefine() { /* only needed for RTTI */ }\n\n  inline void registerJsBundle(Bundle::Reader bundle) { /* handled at the second stage */ }\n\n private:\n  TypeWrapper& typeWrapper;\n  v8::Isolate* isolate;\n  v8::Local<v8::FunctionTemplate> constructor;\n  v8::Local<v8::ObjectTemplate> instance;\n  v8::Local<v8::ObjectTemplate> prototype;\n  v8::Local<v8::ObjectTemplate> inspectProperties;\n  v8::Local<v8::Signature> signature;\n};\n\n// initializes javascript parts of a context\ntemplate <typename TypeWrapper, typename Self>\nstruct JsSetup {\n  KJ_DISALLOW_COPY_AND_MOVE(JsSetup);\n\n  JsSetup(jsg::Lock& js, v8::Local<v8::Context> context): js(js), context(context) {}\n\n  inline void registerJsBundle(Bundle::Reader bundle) {\n    ModuleRegistryImpl<TypeWrapper>::from(js)->addBuiltinBundle(bundle);\n  }\n\n  // the rest of the callbacks are empty\n\n  template <typename Type>\n  inline void registerInherit() {}\n\n  template <const char* name>\n  inline void registerInheritIntrinsic(v8::Intrinsic intrinsic) {}\n\n  template <typename Method, Method method>\n  inline void registerCallable() {}\n\n  template <const char* name, auto method>\n  inline void registerMethod() {}\n\n  template <const char* name, typename Method, Method method>\n  inline void registerStaticMethod() {}\n\n  template <const char* name, typename Getter, Getter getter, typename Setter, Setter setter>\n  inline void registerInstanceProperty() {}\n\n  template <const char* name, typename Getter, Getter getter, typename Setter, Setter setter>\n  inline void registerPrototypeProperty() {}\n\n  template <const char* name, typename Getter, Getter getter>\n  inline void registerReadonlyInstanceProperty() {}\n\n  template <typename T>\n  inline void registerReadonlyInstanceProperty(kj::StringPtr name, T value) {}\n\n  template <const char* name, typename Getter, Getter getter>\n  inline void registerReadonlyPrototypeProperty() {}\n\n  template <const char* name, typename Getter, Getter getter, bool readOnly>\n  inline void registerLazyInstanceProperty() {}\n\n  template <const char* name, typename Getter, Getter getter>\n  inline void registerInspectProperty() {}\n\n  template <const char* name, typename T>\n  inline void registerStaticConstant(T value) {}\n\n  template <const char* name, typename Getter, Getter getter>\n  inline void registerStaticProperty() {}\n\n  template <const char* name, typename Method, Method method>\n  inline void registerIterable() {}\n\n  template <const char* name, typename Method, Method method>\n  inline void registerAsyncIterable() {}\n\n  template <typename Type, const char* name>\n  inline void registerNestedType() {}\n\n  inline void registerTypeScriptRoot() {}\n\n  template <const char* tsOverride>\n  inline void registerTypeScriptOverride() {}\n\n  template <const char* tsDefine>\n  inline void registerTypeScriptDefine() {}\n\n private:\n  jsg::Lock& js;\n  v8::Local<v8::Context> context;\n};\n\nclass ModuleRegistryBase {\n public:\n  virtual ~ModuleRegistryBase() noexcept(false) {}\n  virtual kj::Own<void> attachToIsolate(\n      Lock& js, const CompilationObserver& observer) const KJ_WARN_UNUSED_RESULT = 0;\n\n  virtual const capnp::SchemaLoader& getSchemaLoader() const = 0;\n};\n\nstruct NewContextOptions {\n  kj::Maybe<const ModuleRegistryBase&> newModuleRegistry = kj::none;\n  kj::Maybe<const capnp::SchemaLoader&> schemaLoader = kj::none;\n  bool enableWeakRef = false;\n};\n\n// TypeWrapper mixin for resource types (application-defined C++ classes declared with a\n// JSG_RESOURCE_TYPE block).\ntemplate <typename TypeWrapper, typename T>\nclass ResourceWrapper {\n public:\n  // If the JSG_RESOURCE_TYPE macro declared a configuration parameter, then `Configuration` will\n  // be that type, otherwise NullConfiguration which accepts any configuration.\n  using Configuration = DetectedOr<NullConfiguration, GetConfiguration, T>;\n\n  template <typename MetaConfiguration>\n  ResourceWrapper(MetaConfiguration&& configuration)\n      : configuration(kj::fwd<MetaConfiguration>(configuration)) {}\n\n  inline void initTypeWrapper() {\n    TypeWrapper& wrapper = static_cast<TypeWrapper&>(*this);\n    wrapper.resourceTypeMap.insert(typeid(T),\n        [](TypeWrapper& wrapper,\n            v8::Isolate* isolate) -> DynamicResourceTypeMap<TypeWrapper>::DynamicTypeInfo {\n      kj::Maybe<typename DynamicResourceTypeMap<TypeWrapper>::ReflectionInitializer&> rinit;\n      if constexpr (T::jsgHasReflection) {\n        rinit = [](jsg::Object& object, TypeWrapper& wrapper) {\n          static_cast<T&>(object).jsgInitReflection(wrapper);\n        };\n      }\n      return {wrapper.getTemplate(isolate, static_cast<T*>(nullptr)), rinit};\n    });\n\n    if constexpr (static_cast<uint>(T::jsgSerializeTag) !=\n        static_cast<uint>(T::jsgSuper::jsgSerializeTag)) {\n      // This type is declared JSG_SERIALIZABLE.\n      // HACK: The type of `serializer` should be `Serializer&`, not `auto&`, but Clang complains\n      //   about the `writeRawUint32()` call being made on an incomplete type if `ser.h` hasn't been\n      //   included -- *even if* T doesn't declare itself serializable and therefore this branch\n      //   should not be compiled at all! Unsure if this is a compiler bug.\n      wrapper.serializerMap.insert(\n          typeid(T), [](TypeWrapper& wrapper, Lock& js, jsg::Object& instance, auto& serializer) {\n        serializer.writeRawUint32(static_cast<uint>(T::jsgSerializeTag));\n        SerializeInvoker<TypeWrapper, T>::call(wrapper, static_cast<T&>(instance), js, serializer);\n      });\n\n      if constexpr (!T::jsgSerializeOneway) {\n        typename TypeWrapper::DeserializeFunc* deserializeFunc =\n            [](TypeWrapper& wrapper, Lock& js, uint tag, Deserializer& deserializer) {\n          // Cast the tag to the application's preferred tag type.\n          auto typedTag = static_cast<decltype(T::jsgSerializeTag)>(tag);\n          return DeserializeInvoker<TypeWrapper, T>::call(wrapper, js, typedTag, deserializer);\n        };\n\n        // We make duplicates here fatal because it's really hard to debug exceptions thrown during\n        // isolate startup and frankly this is pretty fatal for the runtime anyway.\n        auto reportDuplicate = [](auto&, auto&&) noexcept {\n          KJ_FAIL_REQUIRE(\"JSG_SERIALIZABLE declaration tried to register a duplicate type tag\");\n        };\n\n        wrapper.deserializerMap.upsert(\n            static_cast<uint>(T::jsgSerializeTag), deserializeFunc, reportDuplicate);\n        for (auto& oldTag: T::jsgSerializeOldTags) {\n          wrapper.deserializerMap.upsert(\n              static_cast<uint>(oldTag), deserializeFunc, reportDuplicate);\n        }\n      }\n    }\n  }\n\n  static constexpr const std::type_info& getName(T*) {\n    return typeid(T);\n  }\n\n  // `Ref<T>` is NOT a resource type -- TypeHandler<Ref<T>> should use the value-oriented\n  // implementation.\n  static constexpr const std::type_info& getName(Ref<T>*) {\n    return typeid(T);\n  }\n\n  v8::Local<v8::Object> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      Ref<T>&& value) {\n    // Wrap a value of type T.\n\n    auto isolate = js.v8Isolate;\n\n    KJ_IF_SOME(h, value->tryGetHandle(isolate)) {\n      return h;\n    } else {\n      auto& type = typeid(*value);\n      auto& wrapper = static_cast<TypeWrapper&>(*this);\n      // Check if *value is actually a subclass of T. If so, we need to dynamically look up the\n      // correct wrapper. But in the common case that it's exactly T, we can skip the lookup.\n      v8::Local<v8::FunctionTemplate> tmpl;\n      if (type == typeid(T)) {\n        tmpl = getTemplate(isolate, nullptr);\n        if constexpr (T::jsgHasReflection) {\n          value->jsgInitReflection(wrapper);\n        }\n      } else {\n        auto info = wrapper.getDynamicTypeInfo(isolate, type);\n        tmpl = info.tmpl;\n        KJ_IF_SOME(i, info.reflectionInitializer) {\n          i(*value, wrapper);\n        }\n      }\n      v8::Local<v8::Object> object = check(tmpl->InstanceTemplate()->NewInstance(context));\n      value.attachWrapper(isolate, object);\n      return object;\n    }\n  }\n\n  template <typename... Args>\n  JsContext<T> newContext(jsg::Lock& js,\n      jsg::NewContextOptions options,\n      CompilationObserver& compilationObserver,\n      T*,\n      Args&&... args) {\n    // Construct an instance of this type to be used as the Javascript global object, creating\n    // a new JavaScript context. Unfortunately, we have to do some things differently in this\n    // case, because of quirks in how V8 handles the global object. There appear to be bugs\n    // that prevent it from being treated uniformly for callback purposes. See:\n    //\n    //     https://groups.google.com/d/msg/v8-users/RET5b3KOa5E/3EvpRBzwAQAJ\n    //\n    // Because of this, our entire type registration system threads through an extra template\n    // parameter `bool isContext`. When the application decides to create a context using this\n    // type as the global, we instantiate this separate branch specifically for that type.\n    // Fortunately, for types that are never used as the global object, we never have to\n    // instantiate the `isContext = true` branch.\n\n    auto isolate = js.v8Isolate;\n    auto tmpl = getTemplate<true>(isolate, nullptr)->InstanceTemplate();\n    v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, tmpl);\n    auto global = context->Global();\n\n    auto ptr = js.alloc<T>(kj::fwd<Args>(args)...);\n    if constexpr (T::jsgHasReflection) {\n      ptr->jsgInitReflection(static_cast<TypeWrapper&>(*this));\n    }\n    ptr.attachWrapper(isolate, global);\n\n    // Disable `eval(code)` and `new Function(code)`. (Actually, setting this to `false` really\n    // means \"call the callback registered on the isolate to check\" -- setting it to `true` means\n    // \"skip callback and just allow\".)\n    context->AllowCodeGenerationFromStrings(false);\n\n    if (!options.enableWeakRef) {\n      check(global->Delete(context, v8StrIntern(isolate, \"WeakRef\"_kj)));\n      check(global->Delete(context, v8StrIntern(isolate, \"FinalizationRegistry\"_kj)));\n    }\n\n    // Store a pointer to this object in slot 1, to be extracted in callbacks.\n    jsg::setAlignedPointerInEmbedderData(\n        context, jsg::ContextPointerSlot::GLOBAL_WRAPPER, ptr.get());\n    // We need to set the highest used index in every context we create to be a nullptr\n    // This is because we might later on call GetAlignedPointerFromEmbedderData which fails with\n    // a fatal error if the array is smaller than the given index.\n    jsg::setAlignedPointerInEmbedderData(\n        context, jsg::ContextPointerSlot::MAX_POINTER_SLOT, nullptr);\n\n    // (Note: V8 docs say: \"Note that index 0 currently has a special meaning for Chrome's\n    // debugger.\" We aren't Chrome, but it does appear that some versions of V8 will mess with\n    // slot 0, causing us to segfault if we try to put anything there. So we avoid it and use slot\n    // 1, which seems to work just fine.)\n\n    // Expose the type of the global scope in the global scope itself.\n    exposeGlobalScopeType(isolate, context);\n\n    ptr->setModuleRegistryBackingOwner(([&]() -> kj::Own<void> {\n      KJ_IF_SOME(newModuleRegistry, options.newModuleRegistry) {\n        return JSG_WITHIN_CONTEXT_SCOPE(js, context, [&](jsg::Lock& js) {\n          // The context must be current for attachToIsolate to succeed.\n          return newModuleRegistry.attachToIsolate(js, compilationObserver);\n        });\n      } else {\n        return ModuleRegistryImpl<TypeWrapper>::install(isolate, context, compilationObserver);\n      }\n    })());\n\n    KJ_IF_SOME(loader, options.schemaLoader) {\n      ptr->setSchemaLoader(loader);\n    }\n\n    return JSG_WITHIN_CONTEXT_SCOPE(js, context, [&](jsg::Lock& js) {\n      setupJavascript(js);\n      return JsContext<T>(context, kj::mv(ptr));\n    });\n  }\n\n  kj::Maybe<T&> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      T*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    // Try to unwrap a value of type T.\n\n    if (handle->IsObject()) {\n      v8::Local<v8::Object> instance =\n          v8::Local<v8::Object>::Cast(handle)->FindInstanceInPrototypeChain(\n              getTemplate(js.v8Isolate, nullptr));\n      if (!instance.IsEmpty()) {\n        return extractInternalPointer<T, false>(context, instance);\n      }\n    }\n\n    return kj::none;\n  }\n\n  kj::Maybe<Ref<T>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      Ref<T>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    // Try to unwrap a value of type Ref<T>.\n\n    KJ_IF_SOME(p, tryUnwrap(js, context, handle, static_cast<T*>(nullptr), parentObject)) {\n      return Ref<T>(kj::addRef(p));\n    } else {\n      return kj::none;\n    }\n  }\n\n  template <bool isContext = false>\n  v8::Local<v8::FunctionTemplate> getTemplate(v8::Isolate* isolate, T*) {\n    v8::Global<v8::FunctionTemplate>& slot = isContext ? contextConstructor : memoizedConstructor;\n    if (slot.IsEmpty()) {\n      // Construct lazily.\n      v8::EscapableHandleScope scope(isolate);\n\n      v8::Local<v8::FunctionTemplate> constructor;\n      if constexpr (!isContext && HasConstructorMethod<T>) {\n        // Per Web IDL, constructor .length = number of required arguments.\n        constexpr int specCtorLength = requiredArgumentCount<TypeWrapper, decltype(T::constructor)>;\n        const int ctorLength = getSpecCompliantPropertyAttributes(isolate) ? specCtorLength : 0;\n        constructor =\n            v8::FunctionTemplate::New(isolate, &ConstructorCallback<TypeWrapper, T>::callback,\n                v8::Local<v8::Value>(), v8::Local<v8::Signature>(), ctorLength);\n      } else {\n        constructor = v8::FunctionTemplate::New(isolate, &throwIllegalConstructor);\n      }\n\n      auto prototype = constructor->PrototypeTemplate();\n\n      // Signatures protect our methods from being invoked with the wrong `this`.\n      auto signature = v8::Signature::New(isolate, constructor);\n\n      auto instance = constructor->InstanceTemplate();\n\n      instance->SetInternalFieldCount(Wrappable::INTERNAL_FIELD_COUNT);\n\n      auto classname = v8StrIntern(isolate, typeName(typeid(T)));\n\n      if (getShouldSetToStringTag(isolate)) {\n        prototype->Set(\n            v8::Symbol::GetToStringTag(isolate), classname, v8::PropertyAttribute::DontEnum);\n      }\n\n      // Previously, miniflare would use the lack of a Symbol.toStringTag on a class to\n      // detect a type that came from the runtime. That's obviously a bit problematic because\n      // Symbol.toStringTag is required for full compliance on standard web platform APIs.\n      // To help use cases where it is necessary to detect if a class is a runtime class, we\n      // will add a special symbol to the prototype of the class to indicate. Note that\n      // because this uses the global symbol registry user code could still mark their own\n      // classes with this symbol but that's unlikely to be a problem in any practical case.\n      auto internalMarker =\n          v8::Symbol::For(isolate, v8StrIntern(isolate, \"cloudflare:internal-class\"));\n      prototype->Set(internalMarker, internalMarker,\n          static_cast<v8::PropertyAttribute>(v8::PropertyAttribute::DontEnum |\n              v8::PropertyAttribute::DontDelete | v8::PropertyAttribute::ReadOnly));\n\n      if (getShouldSetImmutablePrototype(isolate)) {\n        constructor->ReadOnlyPrototype();\n      }\n\n      constructor->SetClassName(classname);\n\n      static_assert(kj::isSameType<typename T::jsgThis, T>(),\n          \"Name passed to JSG_RESOURCE_TYPE() must be the class's own name.\");\n\n      auto& typeWrapper = static_cast<TypeWrapper&>(*this);\n\n      ResourceTypeBuilder<TypeWrapper, T, isContext> builder(\n          typeWrapper, isolate, constructor, instance, prototype, signature);\n\n      if constexpr (isDetected<GetConfiguration, T>()) {\n        T::template registerMembers<decltype(builder), T>(builder, configuration);\n      } else {\n        T::template registerMembers<decltype(builder), T>(builder);\n      }\n\n      KJ_IF_SOME(handler, wildcardHandler) {\n        instance->SetHandler(handler);\n      }\n\n      auto result = scope.Escape(constructor);\n      slot.Reset(isolate, result);\n      return result;\n    } else {\n      return slot.Get(isolate);\n    }\n  }\n\n  kj::Maybe<v8::NamedPropertyHandlerConfiguration> wildcardHandler;\n\n private:\n  Configuration configuration;\n  v8::Global<v8::FunctionTemplate> memoizedConstructor;\n  v8::Global<v8::FunctionTemplate> contextConstructor;\n\n  void setupJavascript(jsg::Lock& js) {\n    JsSetup<TypeWrapper, T> setup(js, js.v8Context());\n\n    if constexpr (isDetected<GetConfiguration, T>()) {\n      T::template registerMembers<decltype(setup), T>(setup, configuration);\n    } else {\n      T::template registerMembers<decltype(setup), T>(setup);\n    }\n  }\n\n  template <typename, typename, typename, typename>\n  friend struct ConstructorCallback;\n};\n\n// Like ResourceWrapper for T = jsg::Object. We need some special-casing for this type.\ntemplate <typename TypeWrapper>\nclass ObjectWrapper {\n public:\n  static constexpr const std::type_info& getName(Object*) {\n    return typeid(Object);\n  }\n\n  static constexpr const std::type_info& getName(Ref<Object>*) {\n    return typeid(Object);\n  }\n\n  // Wrap a value of type T.\n  v8::Local<v8::Object> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      Ref<Object>&& value) {\n    auto isolate = js.v8Isolate;\n\n    KJ_IF_SOME(h, value->tryGetHandle(isolate)) {\n      return h;\n    } else {\n      auto& valueRef = *value;  // avoid compiler warning about typeid(*value) having side effects\n      auto& type = typeid(valueRef);\n      auto& wrapper = static_cast<TypeWrapper&>(*this);\n\n      // In ResourceWrapper::wrap() we check if the value is a subclass. Here, we assume it is\n      // always a subclass, because `jsg::Object` cannot be constructed directly.\n      auto info = wrapper.getDynamicTypeInfo(isolate, type);\n      v8::Local<v8::FunctionTemplate> tmpl = info.tmpl;\n      KJ_IF_SOME(i, info.reflectionInitializer) {\n        i(*value, wrapper);\n      }\n      v8::Local<v8::Object> object = check(tmpl->InstanceTemplate()->NewInstance(context));\n      value.attachWrapper(isolate, object);\n      return object;\n    }\n  }\n\n  // We do not support unwrapping Ref<Object>; use V8Ref<v8::Object> instead.\n  kj::Maybe<Ref<Object>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      Ref<Object>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) = delete;\n};\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/rtti-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/rtti-test.capnp.h>\n#include <workerd/jsg/rtti.h>\n#include <workerd/jsg/type-wrapper.h>\n\n#include <capnp/message.h>\n#include <capnp/serialize-text.h>\n#include <kj/test.h>\n\nstruct MockConfig {};\n\nnamespace workerd::jsg::rtti {\nnamespace {\n\ntemplate <typename T>\nkj::String tType() {\n  // returns textual encoding of rtti.\n  Builder<MockConfig> builder((MockConfig()));\n  auto type = builder.type<T>();\n  capnp::TextCodec codec;\n  return codec.encode(type);\n}\n\ntemplate <typename T>\nkj::String tStructure() {\n  // returns textual encoding of structure.\n  Builder<MockConfig> builder((MockConfig()));\n  auto type = builder.structure<T>();\n  capnp::TextCodec codec;\n  return codec.encode(type);\n}\n\nKJ_TEST(\"jsg::Js* types\") {\n  KJ_EXPECT(tType<JsValue>() == \"(unknown = void)\");\n  KJ_EXPECT(tType<JsObject>() == \"(object = void)\");\n  KJ_EXPECT(tType<JsBoolean>() == \"(boolt = void)\");\n  KJ_EXPECT(tType<JsArray>() == \"(array = (element = (unknown = void), name = \\\"jsg::JsArray\\\"))\");\n  KJ_EXPECT(tType<JsString>() == \"(string = (name = \\\"jsg::JsString\\\"))\");\n  KJ_EXPECT(tType<JsBigInt>() == \"(number = (name = \\\"jsg::JsBigInt\\\"))\");\n  KJ_EXPECT(tType<JsNumber>() == \"(number = (name = \\\"jsg::JsNumber\\\"))\");\n  KJ_EXPECT(tType<JsInt32>() == \"(number = (name = \\\"jsg::JsInt32\\\"))\");\n  KJ_EXPECT(tType<JsUint32>() == \"(number = (name = \\\"jsg::JsUint32\\\"))\");\n  KJ_EXPECT(tType<JsDate>() == \"(builtin = (type = kjDate))\");\n  KJ_EXPECT(tType<JsRegExp>() == \"(unknown = void)\");\n  KJ_EXPECT(tType<JsMap>() == \"(unknown = void)\");\n  KJ_EXPECT(tType<JsSet>() == \"(unknown = void)\");\n  KJ_EXPECT(tType<JsSymbol>() == \"(unknown = void)\");\n\n  KJ_EXPECT(tType<JsRef<JsValue>>() == \"(unknown = void)\");\n  KJ_EXPECT(tType<JsRef<JsObject>>() == \"(object = void)\");\n  KJ_EXPECT(tType<JsRef<JsBoolean>>() == \"(boolt = void)\");\n  KJ_EXPECT(\n      tType<JsRef<JsArray>>() == \"(array = (element = (unknown = void), name = \\\"jsg::JsArray\\\"))\");\n  KJ_EXPECT(tType<JsRef<JsString>>() == \"(string = (name = \\\"jsg::JsString\\\"))\");\n  KJ_EXPECT(tType<JsRef<JsBigInt>>() == \"(number = (name = \\\"jsg::JsBigInt\\\"))\");\n  KJ_EXPECT(tType<JsRef<JsNumber>>() == \"(number = (name = \\\"jsg::JsNumber\\\"))\");\n  KJ_EXPECT(tType<JsRef<JsInt32>>() == \"(number = (name = \\\"jsg::JsInt32\\\"))\");\n  KJ_EXPECT(tType<JsRef<JsUint32>>() == \"(number = (name = \\\"jsg::JsUint32\\\"))\");\n  KJ_EXPECT(tType<JsRef<JsDate>>() == \"(builtin = (type = kjDate))\");\n  KJ_EXPECT(tType<JsRef<JsRegExp>>() == \"(unknown = void)\");\n  KJ_EXPECT(tType<JsRef<JsMap>>() == \"(unknown = void)\");\n  KJ_EXPECT(tType<JsRef<JsSet>>() == \"(unknown = void)\");\n  KJ_EXPECT(tType<JsRef<JsSymbol>>() == \"(unknown = void)\");\n}\n\nKJ_TEST(\"primitive types\") {\n  KJ_EXPECT(tType<void>() == \"(voidt = void)\");\n  KJ_EXPECT(tType<bool>() == \"(boolt = void)\");\n  KJ_EXPECT(tType<v8::Value>() == \"(unknown = void)\");\n}\n\nKJ_TEST(\"number types\") {\n  KJ_EXPECT(tType<char>() == \"(number = (name = \\\"char\\\"))\");\n  KJ_EXPECT(tType<signed char>() == \"(number = (name = \\\"signed char\\\"))\");\n  KJ_EXPECT(tType<unsigned char>() == \"(number = (name = \\\"unsigned char\\\"))\");\n  KJ_EXPECT(tType<short>() == \"(number = (name = \\\"short\\\"))\");\n  KJ_EXPECT(tType<unsigned short>() == \"(number = (name = \\\"unsigned short\\\"))\");\n  KJ_EXPECT(tType<int>() == \"(number = (name = \\\"int\\\"))\");\n  KJ_EXPECT(tType<unsigned int>() == \"(number = (name = \\\"unsigned int\\\"))\");\n  KJ_EXPECT(tType<long>() == \"(number = (name = \\\"long\\\"))\");\n  KJ_EXPECT(tType<unsigned long>() == \"(number = (name = \\\"unsigned long\\\"))\");\n\n  KJ_EXPECT(tType<double>() == \"(number = (name = \\\"double\\\"))\");\n}\n\nKJ_TEST(\"string types\") {\n  KJ_EXPECT(tType<kj::String>() == \"(string = (name = \\\"kj::String\\\"))\");\n  KJ_EXPECT(tType<kj::StringPtr>() == \"(string = (name = \\\"kj::StringPtr\\\"))\");\n  KJ_EXPECT(tType<v8::String>() == \"(string = (name = \\\"v8::String\\\"))\");\n  KJ_EXPECT(tType<USVString>() == \"(string = (name = \\\"USVString\\\"))\");\n}\n\nKJ_TEST(\"object types\") {\n  KJ_EXPECT(tType<v8::Object>() == \"(object = void)\");\n  KJ_EXPECT(tType<jsg::Object>() == \"(object = void)\");\n}\n\nKJ_TEST(\"promises\") {\n  KJ_EXPECT(tType<kj::Promise<void>>() == \"(promise = (value = (voidt = void)))\");\n  KJ_EXPECT(tType<kj::Promise<int>>() == \"(promise = (value = (number = (name = \\\"int\\\"))))\");\n  KJ_EXPECT(tType<jsg::Promise<int>>() == \"(promise = (value = (number = (name = \\\"int\\\"))))\");\n  KJ_EXPECT(tType<v8::Promise>() == \"(promise = (value = (unknown = void)))\");\n}\n\nKJ_TEST(\"generic types\") {\n  KJ_EXPECT(tType<Ref<v8::Object>>() == \"(object = void)\");\n  KJ_EXPECT(tType<V8Ref<v8::Object>>() == \"(object = void)\");\n  KJ_EXPECT(tType<HashableV8Ref<v8::Object>>() == \"(object = void)\");\n  KJ_EXPECT(tType<v8::Local<v8::Object>>() == \"(object = void)\");\n  KJ_EXPECT(tType<jsg::Identified<v8::Object>>() == \"(object = void)\");\n  KJ_EXPECT(tType<jsg::MemoizedIdentity<v8::Object>>() == \"(object = void)\");\n  KJ_EXPECT(tType<jsg::NonCoercible<kj::String>>() == \"(string = (name = \\\"kj::String\\\"))\");\n\n  KJ_EXPECT(tType<kj::Array<int>>() ==\n      \"(array = (element = (number = (name = \\\"int\\\")), name = \\\"kj::Array\\\"))\");\n  KJ_EXPECT(tType<kj::ArrayPtr<int>>() ==\n      \"(array = (element = (number = (name = \\\"int\\\")), name = \\\"kj::ArrayPtr\\\"))\");\n  KJ_EXPECT(tType<jsg::Sequence<int>>() ==\n      \"(array = (element = (number = (name = \\\"int\\\")), name = \\\"jsg::Sequence\\\"))\");\n\n  KJ_EXPECT(tType<kj::Maybe<int>>() ==\n      \"(maybe = (value = (number = (name = \\\"int\\\")), name = \\\"kj::Maybe\\\"))\");\n  KJ_EXPECT(tType<jsg::Optional<int>>() ==\n      \"(maybe = (value = (number = (name = \\\"int\\\")), name = \\\"jsg::Optional\\\"))\");\n  KJ_EXPECT(tType<jsg::LenientOptional<int>>() ==\n      \"(maybe = (value = (number = (name = \\\"int\\\")), name = \\\"jsg::LenientOptional\\\"))\");\n\n  KJ_EXPECT(tType<jsg::Dict<int>>() ==\n      \"(dict = (key = (string = (name = \\\"kj::String\\\")), value = (number = (name = \\\"int\\\"))))\");\n  KJ_EXPECT((tType<jsg::Dict<int, double>>()) ==\n      \"(dict = (key = (number = (name = \\\"double\\\")), value = (number = (name = \\\"int\\\"))))\");\n\n  KJ_EXPECT((tType<kj::OneOf<int, double>>()) ==\n      \"(oneOf = (variants = [\"\n      \"(number = (name = \\\"int\\\")), \"\n      \"(number = (name = \\\"double\\\"))]))\");\n  KJ_EXPECT((tType<kj::OneOf<int, double, kj::String>>()) ==\n      \"(oneOf = (variants = [\"\n      \"(number = (name = \\\"int\\\")), \"\n      \"(number = (name = \\\"double\\\")), \"\n      \"(string = (name = \\\"kj::String\\\"))]))\");\n}\n\nKJ_TEST(\"builtins\") {\n  KJ_EXPECT(tType<jsg::BufferSource>() == \"(builtin = (type = jsgBufferSource))\");\n  KJ_EXPECT(tType<v8::Uint8Array>() == \"(builtin = (type = v8Uint8Array))\");\n  KJ_EXPECT(tType<v8::ArrayBufferView>() == \"(builtin = (type = v8ArrayBufferView))\");\n  KJ_EXPECT(tType<v8::Function>() == \"(builtin = (type = v8Function))\");\n  KJ_EXPECT(tType<kj::Date>() == \"(builtin = (type = kjDate))\");\n}\n\nKJ_TEST(\"jsgImpl\") {\n  KJ_EXPECT(tType<jsg::Lock>() == \"(jsgImpl = (type = jsgLock))\");\n  KJ_EXPECT(tType<jsg::SelfRef>() == \"(jsgImpl = (type = jsgSelfRef))\");\n  KJ_EXPECT(tType<jsg::Unimplemented>() == \"(jsgImpl = (type = jsgUnimplemented))\");\n  KJ_EXPECT(tType<v8::Isolate*>() == \"(jsgImpl = (type = v8Isolate))\");\n  KJ_EXPECT(tType<MockConfig>() == \"(jsgImpl = (type = configuration))\");\n  KJ_EXPECT(tType<jsg::TypeHandler<kj::Date>>() == \"(jsgImpl = (type = jsgTypeHandler))\");\n  KJ_EXPECT(tType<v8::FunctionCallbackInfo<v8::Value>>() ==\n      \"(jsgImpl = (type = v8FunctionCallbackInfo))\");\n  KJ_EXPECT(tType<v8::PropertyCallbackInfo<v8::Value>>() ==\n      \"(jsgImpl = (type = v8PropertyCallbackInfo))\");\n}\n\nKJ_TEST(\"functions\") {\n  KJ_EXPECT(tType<jsg::Function<int()>>() ==\n      \"(function = (returnType = (number = (name = \\\"int\\\")), args = []))\");\n  KJ_EXPECT(tType<jsg::Function<void(int a, double b)>>() ==\n      \"(function = (returnType = (voidt = void), args = [(number = (name = \\\"int\\\")), (number = (name = \\\"double\\\"))]))\");\n}\n\nKJ_TEST(\"c++ modifiers\") {\n  KJ_EXPECT(tType<const int>() == \"(number = (name = \\\"int\\\"))\");\n  KJ_EXPECT(tType<int&>() == \"(number = (name = \\\"int\\\"))\");\n  KJ_EXPECT(tType<int&&>() == \"(number = (name = \\\"int\\\"))\");\n  KJ_EXPECT(tType<const int&>() == \"(number = (name = \\\"int\\\"))\");\n}\n\nstruct Base: public Object {\n  JSG_RESOURCE_TYPE(Base) {\n    JSG_INHERIT_INTRINSIC(v8::kIteratorPrototype);\n  }\n};\n\nstruct TestResource: public Base {\n  void instanceMethod(int i, double f) {}\n  static int staticMethod() {\n    return 42;\n  }\n\n  int getSize() {\n    return 1;\n  }\n  void setSize(int size) {}\n\n  static jsg::Ref<TestResource> constructor(jsg::Optional<kj::String> label);\n\n  JSG_RESOURCE_TYPE(TestResource) {\n    JSG_INHERIT(Base);\n\n    JSG_METHOD(instanceMethod);\n    JSG_STATIC_METHOD(staticMethod);\n    JSG_INSTANCE_PROPERTY(size, getSize, setSize);\n    JSG_READONLY_INSTANCE_PROPERTY(readonlySize, getSize);\n    JSG_LAZY_INSTANCE_PROPERTY(lazySize, getSize);\n    JSG_LAZY_READONLY_INSTANCE_PROPERTY(lazyReadonlySize, getSize);\n    JSG_PROTOTYPE_PROPERTY(protoSize, getSize, setSize);\n    JSG_READONLY_PROTOTYPE_PROPERTY(protoReadonlySize, getSize);\n  }\n};\n\nKJ_TEST(\"resource reference\") {\n  KJ_EXPECT(tType<TestResource>() ==\n      \"(structure = (name = \\\"TestResource\\\", fullyQualifiedName = \\\"workerd::jsg::rtti::(anonymous namespace)::TestResource\\\"))\");\n}\n\nKJ_TEST(\"resource structure\") {\n  KJ_EXPECT(tStructure<Base>() ==\n      \"(name = \\\"Base\\\", members = [], \"\n      \"extends = (intrinsic = (name = \\\"v8::kIteratorPrototype\\\")), \"\n      \"iterable = false, asyncIterable = false, \"\n      \"fullyQualifiedName = \\\"workerd::jsg::rtti::(anonymous namespace)::Base\\\", tsRoot = false, \"\n      \"disposable = false, asyncDisposable = false)\");\n\n  KJ_EXPECT(tStructure<TestResource>() ==\n      \"(name = \\\"TestResource\\\", members = [\"\n      \"(method = (name = \\\"instanceMethod\\\", returnType = (voidt = void), args = [(number = (name = \\\"int\\\")), (number = (name = \\\"double\\\"))], static = false, fastApiCompatible = true)), \"\n      \"(method = (name = \\\"staticMethod\\\", returnType = (number = (name = \\\"int\\\")), args = [], static = true, fastApiCompatible = true)), \"\n      \"(property = (name = \\\"size\\\", type = (number = (name = \\\"int\\\")), readonly = false, lazy = false, prototype = false, getterFastApiCompatible = true, setterFastApiCompatible = true)), \"\n      \"(property = (name = \\\"readonlySize\\\", type = (number = (name = \\\"int\\\")), readonly = true, lazy = false, prototype = false, getterFastApiCompatible = true, setterFastApiCompatible = false)), \"\n      \"(property = (name = \\\"lazySize\\\", type = (number = (name = \\\"int\\\")), readonly = false, lazy = true, prototype = false, getterFastApiCompatible = false, setterFastApiCompatible = false)), \"\n      \"(property = (name = \\\"lazyReadonlySize\\\", type = (number = (name = \\\"int\\\")), readonly = true, lazy = true, prototype = false, getterFastApiCompatible = false, setterFastApiCompatible = false)), \"\n      \"(property = (name = \\\"protoSize\\\", type = (number = (name = \\\"int\\\")), readonly = false, lazy = false, prototype = true, getterFastApiCompatible = true, setterFastApiCompatible = true)), \"\n      \"(property = (name = \\\"protoReadonlySize\\\", type = (number = (name = \\\"int\\\")), readonly = true, lazy = false, prototype = true, getterFastApiCompatible = false, setterFastApiCompatible = false)), \"\n      \"(constructor = (args = [(maybe = (value = (string = (name = \\\"kj::String\\\")), name = \\\"jsg::Optional\\\"))]))], \"\n      \"extends = (structure = (name = \\\"Base\\\", fullyQualifiedName = \\\"workerd::jsg::rtti::(anonymous namespace)::Base\\\")), \"\n      \"iterable = false, asyncIterable = false, \"\n      \"fullyQualifiedName = \\\"workerd::jsg::rtti::(anonymous namespace)::TestResource\\\", tsRoot = false, \"\n      \"disposable = false, asyncDisposable = false)\");\n}\n\nstruct TestNested: jsg::Object {\n  JSG_RESOURCE_TYPE(TestNested) {\n    JSG_NESTED_TYPE(Base);\n  };\n};\n\nKJ_TEST(\"nested structure\") {\n  KJ_EXPECT(tStructure<TestNested>() ==\n      \"(name = \\\"TestNested\\\", members = [(\"\n      \"nested = (\"\n      \"structure = (\"\n      \"name = \\\"Base\\\", members = [], \"\n      \"extends = (intrinsic = (name = \\\"v8::kIteratorPrototype\\\")), \"\n      \"iterable = false, asyncIterable = false, \"\n      \"fullyQualifiedName = \\\"workerd::jsg::rtti::(anonymous namespace)::Base\\\", \"\n      \"tsRoot = false, disposable = false, asyncDisposable = false\"\n      \"), \"\n      \"name = \\\"Base\\\"))\"\n      \"], \"\n      \"iterable = false, asyncIterable = false, \"\n      \"fullyQualifiedName = \\\"workerd::jsg::rtti::(anonymous namespace)::TestNested\\\", tsRoot = false, \"\n      \"disposable = false, asyncDisposable = false)\");\n}\n\nstruct TestConstant: jsg::Object {\n  static constexpr int ENABLED [[maybe_unused]] = 1;\n\n  enum Type {\n    CIRCLE = 2,\n  };\n\n  JSG_RESOURCE_TYPE(TestConstant) {\n    JSG_STATIC_CONSTANT(ENABLED);\n    JSG_STATIC_CONSTANT(CIRCLE);\n  };\n};\n\nKJ_TEST(\"constant members\") {\n  KJ_EXPECT(tStructure<TestConstant>() ==\n      \"(name = \\\"TestConstant\\\", members = [\"\n      \"(constant = (name = \\\"ENABLED\\\", value = 1)), \"\n      \"(constant = (name = \\\"CIRCLE\\\", value = 2))], \"\n      \"iterable = false, asyncIterable = false, \"\n      \"fullyQualifiedName = \\\"workerd::jsg::rtti::(anonymous namespace)::TestConstant\\\", \"\n      \"tsRoot = false, disposable = false, asyncDisposable = false)\");\n}\n\nstruct TestStruct {\n  int a;\n  bool b;\n  JSG_STRUCT(a, b);\n};\n\nKJ_TEST(\"struct reference\") {\n  KJ_EXPECT(tType<TestStruct>() ==\n      \"(structure = (name = \\\"TestStruct\\\", fullyQualifiedName = \\\"workerd::jsg::rtti::(anonymous namespace)::TestStruct\\\"))\");\n}\n\nKJ_TEST(\"struct structure\") {\n  KJ_EXPECT(tStructure<TestStruct>() ==\n      \"(name = \\\"TestStruct\\\", members = [\"\n      \"(property = (name = \\\"a\\\", type = (number = (name = \\\"int\\\")), readonly = false, lazy = false, prototype = false, getterFastApiCompatible = false, setterFastApiCompatible = false)), \"\n      \"(property = (name = \\\"b\\\", type = (boolt = void), readonly = false, lazy = false, prototype = false, getterFastApiCompatible = false, setterFastApiCompatible = false))], \"\n      \"iterable = false, asyncIterable = false, \"\n      \"fullyQualifiedName = \\\"workerd::jsg::rtti::(anonymous namespace)::TestStruct\\\", \"\n      \"tsRoot = false, disposable = false, asyncDisposable = false)\");\n}\n\nstruct TestSymbolTable: public jsg::Object {\n  void acceptResource(const TestResource& resource) {};\n  void recursiveTypeFunction(const TestSymbolTable& table) {}\n\n  JSG_RESOURCE_TYPE(TestSymbolTable) {\n    JSG_METHOD(acceptResource);\n    JSG_METHOD(recursiveTypeFunction);\n  };\n};\n\nKJ_TEST(\"symbol table\") {\n  Builder<MockConfig> builder((MockConfig()));\n  auto type = builder.structure<TestSymbolTable>();\n  capnp::TextCodec codec;\n\n  KJ_EXPECT(codec.encode(type) ==\n      \"(name = \\\"TestSymbolTable\\\", members = [\"\n      \"(method = (name = \\\"acceptResource\\\", returnType = (voidt = void), args = [(structure = (name = \\\"TestResource\\\", fullyQualifiedName = \\\"workerd::jsg::rtti::(anonymous namespace)::TestResource\\\"))], static = false, fastApiCompatible = true)), \"\n      \"(method = (name = \\\"recursiveTypeFunction\\\", returnType = (voidt = void), args = [(structure = (name = \\\"TestSymbolTable\\\", fullyQualifiedName = \\\"workerd::jsg::rtti::(anonymous namespace)::TestSymbolTable\\\"))], static = false, fastApiCompatible = true))], \"\n      \"iterable = false, asyncIterable = false, \"\n      \"fullyQualifiedName = \\\"workerd::jsg::rtti::(anonymous namespace)::TestSymbolTable\\\", \"\n      \"tsRoot = false, disposable = false, asyncDisposable = false)\");\n\n  KJ_EXPECT(builder.structure(\"workerd::jsg::rtti::(anonymous namespace)::TestSymbolTable\"_kj) !=\n      kj::none);\n  KJ_EXPECT(\n      builder.structure(\"workerd::jsg::rtti::(anonymous namespace)::TestResource\"_kj) != kj::none);\n  KJ_EXPECT(KJ_REQUIRE_NONNULL(\n                builder.structure(\"workerd::jsg::rtti::(anonymous namespace)::TestResource\"_kj))\n                .getMembers()\n                .size() > 0);\n}\n\nstruct TestTypeScriptResourceType: public jsg::Object {\n  int getThing() {\n    return 42;\n  }\n\n  JSG_RESOURCE_TYPE(TestTypeScriptResourceType) {\n    JSG_READONLY_INSTANCE_PROPERTY(thing, getThing);\n\n    JSG_TS_ROOT();\n    JSG_TS_DEFINE(interface Define {});\n    JSG_TS_OVERRIDE({ readonly thing: 42 });\n  };\n};\n\nstruct TestTypeScriptStruct {\n  int structThing;\n  JSG_STRUCT(structThing);\n\n  JSG_STRUCT_TS_ROOT();\n  JSG_STRUCT_TS_DEFINE(interface StructDefine {});\n  JSG_STRUCT_TS_OVERRIDE(RenamedStructThing { structThing: 42 });\n};\n\nKJ_TEST(\"typescript macros\") {\n  KJ_EXPECT(tStructure<TestTypeScriptResourceType>() ==\n      \"(name = \\\"TestTypeScriptResourceType\\\", members = [\"\n      \"(property = (name = \\\"thing\\\", type = (number = (name = \\\"int\\\")), readonly = true, lazy = false, prototype = false, getterFastApiCompatible = true, setterFastApiCompatible = false))], \"\n      \"iterable = false, asyncIterable = false, \"\n      \"fullyQualifiedName = \\\"workerd::jsg::rtti::(anonymous namespace)::TestTypeScriptResourceType\\\", \"\n      \"tsRoot = true, \"\n      \"tsOverride = \\\"{ readonly thing: 42 }\\\", \"\n      \"tsDefine = \\\"interface Define {}\\\", \"\n      \"disposable = false, asyncDisposable = false)\");\n  KJ_EXPECT(tStructure<TestTypeScriptStruct>() ==\n      \"(name = \\\"TestTypeScriptStruct\\\", members = [\"\n      \"(property = (name = \\\"structThing\\\", type = (number = (name = \\\"int\\\")), readonly = false, lazy = false, prototype = false, getterFastApiCompatible = false, setterFastApiCompatible = false))], \"\n      \"iterable = false, asyncIterable = false, \"\n      \"fullyQualifiedName = \\\"workerd::jsg::rtti::(anonymous namespace)::TestTypeScriptStruct\\\", \"\n      \"tsRoot = true, \"\n      \"tsOverride = \\\"RenamedStructThing { structThing: 42 }\\\", \"\n      \"tsDefine = \\\"interface StructDefine {}\\\", \"\n      \"disposable = false, asyncDisposable = false)\");\n}\n\n}  // namespace\n}  // namespace workerd::jsg::rtti\n"
  },
  {
    "path": "src/workerd/jsg/rtti-test.capnp",
    "content": "@0xfe92df13ba26adb5;\n\nusing Modules = import \"/workerd/jsg/modules.capnp\";\n\nconst testBundle :Modules.Bundle = (\n  modules = [\n    (name = \"testBundle:internal\", src = \"export const foo = 'foo';\", type = internal, tsDeclaration=\"foo: string\")\n]);\n"
  },
  {
    "path": "src/workerd/jsg/rtti.capnp",
    "content": "# Copyright (c) 2017-2022 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n@0xb042d6da9e1721ad;\n# Runtime information about jsg types and definitions\n\nusing Cxx = import \"/capnp/c++.capnp\";\n$Cxx.namespace(\"workerd::jsg::rtti\");\n$Cxx.allowCancellation;\n# TODO: I can't figure out how to make both capnpc-ts and capnpc-cpp generators to see this import\n# without code changes. capnpc-ts code is weird:\n# https://github.com/jdiaz5513/capnp-ts/blob/master/packages/capnpc-ts/src/generators.ts#L92\n# using Modules = import \"/workerd/jsg/modules.capnp\";\n\nstruct Type {\n  # A description of the C++ type.\n  # It is as precise as needed for applications, and is mostly how the type looks from the js side.\n\n  union {\n    unknown @0 :Void;\n    # statically unknown type\n\n    voidt @1 :Void;\n    # void type\n\n    boolt @2 :Void;\n    # boolean type\n\n    number @3 :NumberType;\n    # number type\n\n    promise @4 :PromiseType;\n    # jsg, kj Promise\n\n    structure @5 :StructureType;\n    # jsg resource or struct\n\n    string @6 :StringType;\n    # any string-like type\n\n    object @7 :Void;\n    # generic object type\n\n    array @8 :ArrayType;\n    # Array or ArrayPtr\n\n    maybe @9 :MaybeType;\n    # kj::Maybe or jsg::Optional\n\n    dict @10 :DictType;\n    # jsg::Dict\n\n    oneOf @11: OneOfType;\n    # kj::OneOf\n\n    builtin @12 :BuiltinType;\n    # one of the builtin types\n\n    intrinsic @13 :IntrinsicType;\n    # one of v8 intrinsics\n\n    function @14 :FunctionType;\n    # jsg::Function\n\n    jsgImpl @15 :JsgImplType;\n    # jsg implementation type\n\n    jsBuiltin @16: JsBuiltinType;\n  }\n}\n\nstruct NumberType {\n  # Any c++ number type\n  name @0 :Text;\n}\n\nstruct PromiseType {\n  # kj or jsg Promise<T>\n  value @0 :Type;\n}\n\nstruct StructureType {\n  # Structure types need to be resolved separately to prevent circular references with types\n\n  name @0 :Text;\n\n  fullyQualifiedName @1 :Text;\n}\n\nstruct StringType {\n  # any string or string-like type\n  name @0 :Text;\n}\n\nstruct IntrinsicType {\n  # v8::Intrinsic\n\n  name @0 :Text;\n}\n\nstruct ArrayType {\n  # Array like structure\n  element @0 :Type;\n\n  name @1 :Text;\n}\n\nstruct MaybeType {\n  # kj::Maybe, jsg::Optional, jsg::LenientOptional\n  value @0 :Type;\n\n  name @1 :Text;\n}\n\nstruct DictType {\n  # jsg::dict\n  key @0 :Type;\n  value @1 :Type;\n}\n\nstruct OneOfType {\n  # kj::OneOf\n  variants @0 :List(Type);\n}\n\nstruct BuiltinType {\n  # One of the types provided by the JS or Runtime platform.\n\n  enum Type {\n    v8Uint8Array @0;\n    # v8::UInt8Array\n\n    v8ArrayBufferView @1;\n    # v8::ArrayBufferView\n\n    jsgBufferSource @2;\n    # BufferSource\n\n    kjDate @3;\n    # kj::Date\n\n    v8Function @4;\n    # v8::Function\n\n    v8ArrayBuffer @5;\n    # v8::ArrayBuffer\n  }\n\n  type @0 :Type;\n}\n\nstruct FunctionType {\n  # jsg::Function type\n\n  returnType @0 :Type;\n\n  args @1 :List(Type);\n}\n\nstruct JsgImplType {\n  # one of the internal jsg types that are not exposed directly but handled specially\n\n  enum Type {\n    configuration @0;\n    # api meta configuration object\n\n    v8Isolate @1;\n\n    jsgLock @2;\n\n    jsgTypeHandler @3;\n\n    jsgUnimplemented @4;\n\n    jsgVarargs @5;\n\n    jsgSelfRef @6;\n\n    v8FunctionCallbackInfo @7;\n\n    v8PropertyCallbackInfo @8;\n\n    jsgName @9;\n  }\n\n  type @0 :Type;\n}\n\nstruct Structure {\n  # A description of either JSG_RESOURCE or JSG_STRUCT\n\n  name @0 :Text;\n  # Structure name\n\n  fullyQualifiedName @5 :Text;\n  # Fully-qualified structure name including namespaces and parents\n\n  members @1 :List(Member);\n  # All members in declaration order\n\n  extends @2 :Type;\n  # base type\n\n  iterable @3 :Bool;\n  # true if the structure is iterable\n  iterator @6 :Method;\n  # Method returning iterator if the structure is iterable\n\n  asyncIterable @4 :Bool;\n  # true if the structure is async iterable\n  asyncIterator @7 :Method;\n  # Method returning async iterator if the structure is async iterable\n\n  disposable @13 :Bool;\n  # true if the structure is disposable\n  dispose @14 :Method;\n  # dispose method\n\n  asyncDisposable @15 :Bool;\n  # true if the structure is async disposable\n  asyncDispose @16 :Method;\n  # asyncDispose method\n\n  tsRoot @8 :Bool;\n  # See `JSG_TS_ROOT`'s documentation in the `## TypeScript` section of the JSG README.md.\n  # If `JSG_(STRUCT_)TS_ROOT` is declared for a type, this value will be `true`.\n\n  tsOverride @9 :Text;\n  # See `JSG_TS_OVERRIDE`'s documentation in the `## TypeScript` section of the JSG README.md.\n  # If `JSG_(STRUCT_)TS_OVERRIDE` is declared for a type, this value will be the contents of the\n  # macro declaration verbatim.\n\n  tsDefine @10 :Text;\n  # See `JSG_TS_DEFINE`'s documentation in the `## TypeScript` section of the JSG README.md.\n  # If `JSG_(STRUCT_)TS_DEFINE` is declared for a type, this value will be the contents of the\n  # macro declaration verbatim.\n\n  callable @11 :FunctionType;\n  # If this type is callable as a function, the signature of said function. Otherwise, null.\n\n  builtinModules @12 :List(Module);\n  # List of all builtin modules provided by the context.\n}\n\nstruct Member {\n  # One of structure members\n\n  union {\n    method @0 :Method;\n    # any kind of method\n\n    property @1 :Property;\n    # any kind of property\n\n    nested :group {\n      structure @2 :Structure;\n\n      name @5 :Text;\n      # For JSG_NESTED_TYPE_NAMED, if name is different to structure\n    }\n    # nested type\n\n    constant @3 :Constant;\n    # static constant\n\n    constructor @4 :Constructor;\n    # structure constructor\n  }\n}\n\nstruct Method {\n  name @0 :Text;\n  returnType @1 :Type;\n  args @2 :List(Type);\n  static @3 :Bool;\n  fastApiCompatible @4: Bool;\n}\n\nstruct Property {\n  name @0 :Text;\n  type @1 :Type;\n  readonly @2 :Bool;\n  lazy @3 :Bool;\n  prototype @4 :Bool;\n  getterFastApiCompatible @5: Bool;\n  setterFastApiCompatible @6: Bool;\n}\n\nstruct Constant {\n  # static constant in the resource\n\n  name @0 :Text;\n\n  value @1 :Int64;\n  # TODO: we may need a union here\n}\n\nstruct Constructor {\n  args @0 :List(Type);\n}\n\nstruct Module {\n  specifier @0 :Text;\n  # if anyone ever needs module type, it can be implemented by either fixing the Modules reference\n  # problem above or copying the original enum.\n  # type @1 :Modules.ModuleType;\n  union {\n    structureName @1 :Text;\n    tsDeclarations @2 :Text;\n  }\n}\n\nstruct StructureGroups {\n  # Collection of structure groups, consumed by TypeScript definitions generator\n\n  struct StructureGroup {\n    # Collection of related structures\n\n    name @0 :Text;\n\n    structures @1 :List(Structure);\n  }\n\n  groups @0 :List(StructureGroup);\n\n  modules @1 :List(Module);\n}\n\nstruct JsBuiltinType {\n  # special type for properties whose value is supplied by built-in javascript\n\n  module @0 :Text;\n  # module from which the property is imported\n\n  export @1 :Text;\n  # export name of the property\n}\n"
  },
  {
    "path": "src/workerd/jsg/rtti.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// Runtime type system for jsg.\n// Produces capnp description (rtti.capnp) of jsg structs, resources and c++ types of their\n// members.\n// Can be used to generate typescript type, dynamically invoke methods, fuzz, check backward\n// compatibility etc.\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/rtti.capnp.h>\n\n#include <capnp/message.h>\n#include <kj/map.h>\n\nnamespace workerd::jsg::rtti {\n\nnamespace impl {\n\n// Struct for partial specialization.\ntemplate <typename Configuration, typename T, typename Enable = void>\nstruct BuildRtti;\n\n}  // namespace impl\n\n// User's entry point into rtti.\n// Builder owns capnp builder for all the objects it returns, so usual capnp builder\n// rules apply.\n// The rtti describes object structure and their types.\n// All structure references in rtti are stored by name. The builder maintains a symbol table\n// which can be used to resolve them. It is guaranteed that the table is full enough to\n// interpret all types passed through a given builder.\ntemplate <typename MetaConfiguration>\nclass Builder {\n public:\n  const MetaConfiguration config;\n\n  Builder(const MetaConfiguration& config): config(config) {}\n\n  template <typename T>\n  Type::Reader type() {\n    auto type = builder.initRoot<Type>();\n    impl::BuildRtti<MetaConfiguration, T>::build(type, *this);\n    return type;\n  }\n\n  template <typename T>\n  Structure::Reader structure() {\n    auto name = jsg::fullyQualifiedTypeName(typeid(T));\n    KJ_IF_SOME(builder, symbols.find(name)) {\n      return builder->template getRoot<Structure>();\n    }\n\n    auto& builder = symbols.insert(kj::str(name), kj::heap<capnp::MallocMessageBuilder>()).value;\n    auto structure = builder->template initRoot<Structure>();\n    impl::BuildRtti<MetaConfiguration, T>::build(structure, *this);\n    return structure;\n  }\n\n  kj::Maybe<Structure::Reader> structure(kj::StringPtr name) {\n    // lookup structure in the symbol table\n    return symbols.find(name).map([](kj::Own<capnp::MallocMessageBuilder>& builder) {\n      return builder->template getRoot<Structure>();\n    });\n  }\n\n private:\n  capnp::MallocMessageBuilder builder;\n  kj::HashMap<kj::String, kj::Own<capnp::MallocMessageBuilder>> symbols;\n};\n\nnamespace impl {\n\n// Implementation tools\n\ntemplate <typename Function, typename = void>\nstruct FunctionTraits;\n\ntemplate <typename R, typename... Args>\nstruct FunctionTraits<R(Args...)> {\n  using ReturnType = R;\n  using ArgsTuple = std::tuple<Args...>;\n};\n\ntemplate <typename R, typename... Args>\nstruct FunctionTraits<R (*)(Args...)> {\n  using ReturnType = R;\n  using ArgsTuple = std::tuple<Args...>;\n};\n\ntemplate <typename This, typename R, typename... Args>\nstruct FunctionTraits<R (This::*)(Args...)> {\n  using ReturnType = R;\n  using ArgsTuple = std::tuple<Args...>;\n};\n\ntemplate <typename T>\nstruct FunctionTraits<T, std::void_t<decltype(&T::operator())>>\n    : public FunctionTraits<decltype(&T::operator())> {};\n\ntemplate <typename This, typename R, typename... Args>\nstruct FunctionTraits<R (This::*)(Args...) const> {\n  using ReturnType = R;\n  using ArgsTuple = std::tuple<Args...>;\n};\n\ntemplate <typename Configuration, typename Tuple>\nstruct TupleRttiBuilder {\n  static inline void build(capnp::List<Type>::Builder builder, Builder<Configuration>& rtti) {\n    build(std::make_integer_sequence<size_t, std::tuple_size_v<Tuple>>{}, builder, rtti);\n  }\n\n private:\n  template <size_t... Indexes>\n  static inline void build(std::integer_sequence<size_t, Indexes...> seq,\n      capnp::List<Type>::Builder builder,\n      Builder<Configuration>& rtti) {\n    ((buildIndex<Indexes>(builder, rtti)), ...);\n  }\n\n  template <size_t I>\n  static inline void buildIndex(capnp::List<Type>::Builder builder, Builder<Configuration>& rtti) {\n    BuildRtti<Configuration, std::tuple_element_t<I, Tuple>>::build(builder[I], rtti);\n  }\n};\n\n// Primitives\n\ntemplate <typename Configuration>\nstruct BuildRtti<Configuration, void> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    builder.setVoidt();\n  }\n};\n\ntemplate <typename Configuration>\nstruct BuildRtti<Configuration, bool> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    builder.setBoolt();\n  }\n};\n\ntemplate <typename Configuration>\nstruct BuildRtti<Configuration, jsg::JsBoolean> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    builder.setBoolt();\n  }\n};\n\ntemplate <typename Configuration>\nstruct BuildRtti<Configuration, v8::Value> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    builder.setUnknown();\n  }\n};\n\ntemplate <typename Configuration>\nstruct BuildRtti<Configuration, jsg::JsValue> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    builder.setUnknown();\n  }\n};\n\ntemplate <typename Configuration>\nstruct BuildRtti<Configuration, jsg::JsRegExp> {\n  // This isn't really unknown but we currently do not expose these types at all, so\n  // this is OK for now.\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    builder.setUnknown();\n  }\n};\n\ntemplate <typename Configuration>\nstruct BuildRtti<Configuration, jsg::JsMap> {\n  // This isn't really unknown but we currently do not expose these types at all, so\n  // this is OK for now.\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    builder.setUnknown();\n  }\n};\n\ntemplate <typename Configuration>\nstruct BuildRtti<Configuration, jsg::JsSet> {\n  // This isn't really unknown but we currently do not expose these types at all, so\n  // this is OK for now.\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    builder.setUnknown();\n  }\n};\n\ntemplate <typename Configuration>\nstruct BuildRtti<Configuration, jsg::JsSymbol> {\n  // This isn't really unknown but we currently do not expose these types at all, so\n  // this is OK for now.\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    builder.setUnknown();\n  }\n};\n\n// Numbers\n\n#define DECLARE_NUMBER_TYPE(T)                                                                     \\\n  template <typename Configuration>                                                                \\\n  struct BuildRtti<Configuration, T> {                                                             \\\n    static void build(Type::Builder builder, Builder<Configuration>& rtti) {                       \\\n      builder.initNumber().setName(#T);                                                            \\\n    }                                                                                              \\\n  };\n\n#define FOR_EACH_NUMBER_TYPE(F)                                                                    \\\n  F(char)                                                                                          \\\n  F(signed char)                                                                                   \\\n  F(unsigned char)                                                                                 \\\n  F(short)                                                                                         \\\n  F(unsigned short)                                                                                \\\n  F(int)                                                                                           \\\n  F(unsigned int)                                                                                  \\\n  F(long)                                                                                          \\\n  F(unsigned long)                                                                                 \\\n  F(long long)                                                                                     \\\n  F(unsigned long long)                                                                            \\\n  F(double)                                                                                        \\\n  F(jsg::JsNumber)                                                                                 \\\n  F(jsg::JsInt32)                                                                                  \\\n  F(jsg::JsUint32)                                                                                 \\\n  F(jsg::JsBigInt)\n\nFOR_EACH_NUMBER_TYPE(DECLARE_NUMBER_TYPE)\n\n#undef FOR_EACH_NUMBER_TYPE\n#undef DECLARE_NUMBER_TYPE\n\n// Strings\n\n#define DECLARE_STRING_TYPE(T)                                                                     \\\n  template <typename Configuration>                                                                \\\n  struct BuildRtti<Configuration, T> {                                                             \\\n    static void build(Type::Builder builder, Builder<Configuration>& rtti) {                       \\\n      builder.initString().setName(#T);                                                            \\\n    }                                                                                              \\\n  };\n\n#define FOR_EACH_STRING_TYPE(F)                                                                    \\\n  F(kj::String)                                                                                    \\\n  F(kj::StringPtr)                                                                                 \\\n  F(v8::String)                                                                                    \\\n  F(USVString)                                                                                     \\\n  F(DOMString)                                                                                     \\\n  F(jsg::JsString)\n\nFOR_EACH_STRING_TYPE(DECLARE_STRING_TYPE)\n\n#undef FOR_EACH_STRING_TYPE\n#undef DECLARE_STRING_TYPE\n\n// Object Types\n\ntemplate <typename Configuration>\nstruct BuildRtti<Configuration, v8::Object> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    builder.setObject();\n  }\n};\n\ntemplate <typename Configuration>\nstruct BuildRtti<Configuration, jsg::Object> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    builder.setObject();\n  }\n};\n\ntemplate <typename Configuration>\nstruct BuildRtti<Configuration, jsg::JsObject> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    builder.setObject();\n  }\n};\n\n// References\n\ntemplate <typename Configuration, typename T>\nstruct BuildRtti<Configuration, Ref<T>> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    BuildRtti<Configuration, T>::build(builder, rtti);\n  }\n};\n\ntemplate <typename Configuration, typename T>\nstruct BuildRtti<Configuration, V8Ref<T>> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    BuildRtti<Configuration, T>::build(builder, rtti);\n  }\n};\n\ntemplate <typename Configuration, typename T>\nstruct BuildRtti<Configuration, JsRef<T>> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    BuildRtti<Configuration, T>::build(builder, rtti);\n  }\n};\n\ntemplate <typename Configuration, typename T>\nstruct BuildRtti<Configuration, HashableV8Ref<T>> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    BuildRtti<Configuration, T>::build(builder, rtti);\n  }\n};\n\ntemplate <typename Configuration, typename T>\nstruct BuildRtti<Configuration, v8::Local<T>> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    BuildRtti<Configuration, T>::build(builder, rtti);\n  }\n};\n\ntemplate <typename Configuration, typename T>\nstruct BuildRtti<Configuration, v8::Global<T>> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    BuildRtti<Configuration, T>::build(builder, rtti);\n  }\n};\n\ntemplate <typename Configuration, typename T>\nstruct BuildRtti<Configuration, jsg::MemoizedIdentity<T>> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    BuildRtti<Configuration, T>::build(builder, rtti);\n  }\n};\n\ntemplate <typename Configuration, typename T>\nstruct BuildRtti<Configuration, jsg::Identified<T>> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    BuildRtti<Configuration, T>::build(builder, rtti);\n  }\n};\n\ntemplate <typename Configuration, typename T>\nstruct BuildRtti<Configuration, jsg::NonCoercible<T>> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    BuildRtti<Configuration, T>::build(builder, rtti);\n  }\n};\n\n// Maybe Types\n\n#define DECLARE_MAYBE_TYPE(T)                                                                      \\\n  template <typename Configuration, typename V>                                                    \\\n  struct BuildRtti<Configuration, T<V>> {                                                          \\\n    static void build(Type::Builder builder, Builder<Configuration>& rtti) {                       \\\n      auto maybe = builder.initMaybe();                                                            \\\n      BuildRtti<Configuration, V>::build(maybe.initValue(), rtti);                                 \\\n      maybe.setName(#T);                                                                           \\\n    }                                                                                              \\\n  };\n\n#define FOR_EACH_MAYBE_TYPE(F)                                                                     \\\n  F(kj::Maybe)                                                                                     \\\n  F(jsg::Optional)                                                                                 \\\n  F(jsg::LenientOptional)\n\nFOR_EACH_MAYBE_TYPE(DECLARE_MAYBE_TYPE)\n\n#undef FOR_EACH_MAYBE_TYPE\n#undef DECLARE_MAYBE_TYPE\n\n// Array Types\n\n#define DECLARE_ARRAY_TYPE(T)                                                                      \\\n  template <typename Configuration, typename V>                                                    \\\n  struct BuildRtti<Configuration, T<V>> {                                                          \\\n    static void build(Type::Builder builder, Builder<Configuration>& rtti) {                       \\\n      auto array = builder.initArray();                                                            \\\n      BuildRtti<Configuration, V>::build(array.initElement(), rtti);                               \\\n      array.setName(#T);                                                                           \\\n    }                                                                                              \\\n  };\n\n#define FOR_EACH_ARRAY_TYPE(F)                                                                     \\\n  F(kj::Array)                                                                                     \\\n  F(kj::ArrayPtr)                                                                                  \\\n  F(kj::HashSet)                                                                                   \\\n  F(jsg::Sequence)                                                                                 \\\n  F(jsg::AsyncGenerator)                                                                           \\\n  F(jsg::AsyncGeneratorIgnoringStrings)\n\ntemplate <typename Configuration>\nstruct BuildRtti<Configuration, jsg::JsArray> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    auto array = builder.initArray();\n    BuildRtti<Configuration, JsValue>::build(array.initElement(), rtti);\n    array.setName(\"jsg::JsArray\");\n  }\n};\n\nFOR_EACH_ARRAY_TYPE(DECLARE_ARRAY_TYPE)\n\n#undef FOR_EACH_ARRAY_TYPE\n#undef DECLARE_ARRAY_TYPE\n\n// Misc Generic Types\n\ntemplate <typename Configuration, typename K, typename V>\nstruct BuildRtti<Configuration, jsg::Dict<V, K>> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    auto dict = builder.initDict();\n    BuildRtti<Configuration, K>::build(dict.initKey(), rtti);\n    BuildRtti<Configuration, V>::build(dict.initValue(), rtti);\n  }\n};\n\ntemplate <typename Configuration, typename... Variants>\nstruct BuildRtti<Configuration, kj::OneOf<Variants...>> {\n  using Seq = std::index_sequence_for<Variants...>;\n  using Tuple = std::tuple<Variants...>;\n\n  template <size_t I>\n  static inline void buildVariant(\n      capnp::List<Type>::Builder builder, Builder<Configuration>& rtti) {\n    BuildRtti<Configuration, std::tuple_element_t<I, Tuple>>::build(builder[I], rtti);\n  }\n\n  template <size_t... Indexes>\n  static inline void buildVariants(std::integer_sequence<size_t, Indexes...> seq,\n      capnp::List<Type>::Builder builder,\n      Builder<Configuration>& rtti) {\n    ((buildVariant<Indexes>(builder, rtti)), ...);\n  }\n\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    auto variants = builder.initOneOf().initVariants(Seq::size());\n    buildVariants(Seq{}, variants, rtti);\n  }\n};\n\n// Promises\n\ntemplate <typename Configuration, typename T>\nstruct BuildRtti<Configuration, kj::Promise<T>> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    BuildRtti<Configuration, T>::build(builder.initPromise().initValue(), rtti);\n  }\n};\n\ntemplate <typename Configuration, typename T>\nstruct BuildRtti<Configuration, jsg::Promise<T>> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    BuildRtti<Configuration, T>::build(builder.initPromise().initValue(), rtti);\n  }\n};\n\ntemplate <typename Configuration>\nstruct BuildRtti<Configuration, v8::Promise> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    builder.initPromise().initValue().setUnknown();\n  }\n};\n\n// Builtins\n\n#define DECLARE_BUILTIN_TYPE(T, V)                                                                 \\\n  template <typename Configuration>                                                                \\\n  struct BuildRtti<Configuration, T> {                                                             \\\n    static void build(Type::Builder builder, Builder<Configuration>& rtti) {                       \\\n      builder.initBuiltin().setType(V);                                                            \\\n    }                                                                                              \\\n  };\n\n#define FOR_EACH_BUILTIN_TYPE(F, ...)                                                              \\\n  F(jsg::JsUint8Array, BuiltinType::Type::V8_UINT8_ARRAY)                                          \\\n  F(jsg::JsArrayBuffer, BuiltinType::Type::V8_ARRAY_BUFFER)                                        \\\n  F(jsg::JsArrayBufferView, BuiltinType::Type::V8_ARRAY_BUFFER_VIEW)                               \\\n  F(jsg::JsBufferSource, BuiltinType::Type::JSG_BUFFER_SOURCE)                                     \\\n  F(jsg::BufferSource, BuiltinType::Type::JSG_BUFFER_SOURCE)                                       \\\n  F(kj::Date, BuiltinType::Type::KJ_DATE)                                                          \\\n  F(v8::ArrayBufferView, BuiltinType::Type::V8_ARRAY_BUFFER_VIEW)                                  \\\n  F(v8::ArrayBuffer, BuiltinType::Type::V8_ARRAY_BUFFER)                                           \\\n  F(v8::Function, BuiltinType::Type::V8_FUNCTION)                                                  \\\n  F(v8::Uint8Array, BuiltinType::Type::V8_UINT8_ARRAY)                                             \\\n  F(jsg::JsDate, BuiltinType::Type::KJ_DATE)\n\nFOR_EACH_BUILTIN_TYPE(DECLARE_BUILTIN_TYPE)\n\n#undef FOR_EACH_BUILTIN_TYPE\n#undef DECLARE_BUILTIN_TYPE\n\n// Jsg implementation types\n\n#define DECLARE_JSG_IMPL_TYPE(T, V)                                                                \\\n  template <typename Configuration>                                                                \\\n  struct BuildRtti<Configuration, T> {                                                             \\\n    static void build(Type::Builder builder, Builder<Configuration>& rtti) {                       \\\n      builder.initJsgImpl().setType(V);                                                            \\\n    }                                                                                              \\\n  };\n\n#define FOR_EACH_JSG_IMPL_TYPE(F, ...)                                                             \\\n  F(jsg::Lock, JsgImplType::Type::JSG_LOCK)                                                        \\\n  F(jsg::Name, JsgImplType::Type::JSG_NAME)                                                        \\\n  F(jsg::SelfRef, JsgImplType::Type::JSG_SELF_REF)                                                 \\\n  F(jsg::Unimplemented, JsgImplType::Type::JSG_UNIMPLEMENTED)                                      \\\n  F(v8::Isolate*, JsgImplType::Type::V8_ISOLATE)                                                   \\\n  F(v8::FunctionCallbackInfo<v8::Value>, JsgImplType::Type::V8_FUNCTION_CALLBACK_INFO)             \\\n  F(v8::PropertyCallbackInfo<v8::Value>, JsgImplType::Type::V8_PROPERTY_CALLBACK_INFO)\n\nFOR_EACH_JSG_IMPL_TYPE(DECLARE_JSG_IMPL_TYPE)\n\n#undef FOR_EACH_JSG_IMPL_TYPE\n#undef DECLARE_JSG_IMPL_TYPE\n\ntemplate <typename Configuration, typename T>\nstruct BuildRtti<Configuration, Arguments<T>> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    // TODO(someday): Create a representation of Arguments<T> that actually encodes the type T.\n    builder.initJsgImpl().setType(JsgImplType::Type::JSG_VARARGS);\n  }\n};\n\ntemplate <typename Configuration>\nstruct BuildRtti<Configuration, Configuration> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    builder.initJsgImpl().setType(JsgImplType::Type::CONFIGURATION);\n  }\n};\n\ntemplate <typename Configuration, typename T>\nstruct BuildRtti<Configuration, jsg::TypeHandler<T>> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    builder.initJsgImpl().setType(JsgImplType::Type::JSG_TYPE_HANDLER);\n  }\n};\n\n// Functions\n\ntemplate <typename Configuration, typename Fn>\nstruct BuildRtti<Configuration, jsg::Function<Fn>> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    auto fn = builder.initFunction();\n    using Traits = FunctionTraits<Fn>;\n    BuildRtti<Configuration, typename Traits::ReturnType>::build(fn.initReturnType(), rtti);\n    using Args = Traits::ArgsTuple;\n    TupleRttiBuilder<Configuration, Args>::build(fn.initArgs(std::tuple_size_v<Args>), rtti);\n  }\n};\n\n// C++ modifiers\n\ntemplate <typename Configuration, typename T>\nstruct BuildRtti<Configuration, const T> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    BuildRtti<Configuration, T>::build(builder, rtti);\n  }\n};\n\ntemplate <typename Configuration, typename T>\nstruct BuildRtti<Configuration, T&> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    BuildRtti<Configuration, T>::build(builder, rtti);\n  }\n};\n\ntemplate <typename Configuration, typename T>\nstruct BuildRtti<Configuration, T&&> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    BuildRtti<Configuration, T>::build(builder, rtti);\n  }\n};\n\ntemplate <typename Configuration, typename T>\nstruct BuildRtti<Configuration, const T&> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    BuildRtti<Configuration, T>::build(builder, rtti);\n  }\n};\n\n// Structs\n\n// count all members in the structure\nstruct MemberCounter {\n  template <typename Type, typename GetNamedMethod, GetNamedMethod getNamedMethod>\n  inline void registerWildcardProperty() { /* not a member */ }\n\n  template <const char* name, auto Method>\n  inline void registerMethod() {\n    ++members;\n  }\n\n  template <typename Method, Method method>\n  inline void registerCallable() { /* not a member */ }\n\n  template <typename Type>\n  inline void registerInherit() { /* inherit is not a member */ }\n\n  template <const char* name>\n  inline void registerInheritIntrinsic(v8::Intrinsic intrinsic) { /* inherit is not a member */ }\n\n  template <const char* name, typename Method, Method method>\n  inline void registerIterable() { /* not a member */ }\n\n  template <const char* name, typename Method, Method method>\n  inline void registerAsyncIterable() { /* not a member */ }\n\n  template <const char* name, typename Method, Method method>\n  inline void registerDispose() { /* not a member */ }\n\n  template <const char* name, typename Method, Method method>\n  inline void registerAsyncDispose() { /* not a member */ }\n\n  template <typename Type, const char* name>\n  inline void registerNestedType() {\n    ++members;\n  }\n\n  template <typename Property, auto property>\n  inline void registerStructProperty(const char* name) {\n    ++members;\n  }\n\n  template <const char* name, typename Getter, Getter getter>\n  inline void registerReadonlyPrototypeProperty() {\n    ++members;\n  }\n\n  template <const char* name, typename Getter, Getter getter, typename Setter, Setter setter>\n  inline void registerPrototypeProperty() {\n    ++members;\n  }\n\n  template <const char* name, typename Getter, Getter getter>\n  inline void registerReadonlyInstanceProperty() {\n    ++members;\n  }\n\n  template <typename T>\n  inline void registerReadonlyInstanceProperty(kj::StringPtr, T value) {\n    ++members;\n  }\n\n  template <const char* name, typename Getter, Getter getter, typename Setter, Setter setter>\n  inline void registerInstanceProperty() {\n    ++members;\n  }\n\n  template <const char* name, typename Getter, Getter getter, bool readOnly>\n  inline void registerLazyInstanceProperty() {\n    ++members;\n  }\n\n  template <const char* name, typename Getter, Getter getter>\n  inline void registerInspectProperty() { /* not included */ }\n\n  template <const char* name, typename T>\n  inline void registerStaticConstant(T value) {\n    ++members;\n  }\n\n  template <const char* name, typename Getter, Getter getter>\n  inline void registerStaticProperty() {\n    ++members;\n  }\n\n  template <const char* name, typename Method, Method method>\n  inline void registerStaticMethod() {\n    ++members;\n  }\n\n  inline void registerTypeScriptRoot() { /* not a member */ }\n\n  template <const char* tsOverride>\n  inline void registerTypeScriptOverride() { /* not a member */ }\n\n  template <const char* tsDefine>\n  inline void registerTypeScriptDefine() { /* not a member */ }\n\n  inline void registerJsBundle(Bundle::Reader bundle) {\n    modules += bundle.getModules().size();\n  }\n\n  size_t members = 0;\n  size_t modules = 0;\n};\n\ntemplate <typename Self, typename Configuration>\nstruct MembersBuilder {\n  Structure::Builder structure;\n  capnp::List<Member>::Builder members;\n  capnp::List<Module>::Builder modules;\n  Builder<Configuration>& rtti;\n  uint memberIndex = 0;\n  uint moduleIndex = 0;\n\n  MembersBuilder(Structure::Builder structure,\n      capnp::List<Member>::Builder members,\n      capnp::List<Module>::Builder modules,\n      Builder<Configuration>& rtti)\n      : structure(structure),\n        members(members),\n        modules(modules),\n        rtti(rtti) {}\n\n  template <typename Type>\n  inline void registerInherit() {\n    BuildRtti<Configuration, Type>::build(structure.initExtends(), rtti);\n  }\n\n  template <const char* name>\n  inline void registerInheritIntrinsic(v8::Intrinsic intrinsic) {\n    structure.initExtends().initIntrinsic().setName(name);\n  }\n\n  template <typename Type, const char* name>\n  inline void registerNestedType() {\n    auto nested = members[memberIndex++].initNested();\n    nested.setName(name);\n    BuildRtti<Configuration, Type>::build(nested.initStructure(), rtti);\n  }\n\n  template <const char* name, typename Getter, Getter getter, typename Setter, Setter setter>\n  inline void registerInstanceProperty() {\n    auto prop = members[memberIndex++].initProperty();\n    prop.setName(name);\n    prop.setGetterFastApiCompatible(isFastApiCompatible<Getter>);\n    prop.setSetterFastApiCompatible(isFastApiCompatible<Setter>);\n    using GetterTraits = FunctionTraits<Getter>;\n    BuildRtti<Configuration, typename GetterTraits::ReturnType>::build(prop.initType(), rtti);\n  }\n\n  template <const char* name, typename Getter, Getter getter>\n  inline void registerReadonlyInstanceProperty() {\n    auto prop = members[memberIndex++].initProperty();\n    prop.setName(name);\n    prop.setReadonly(true);\n    prop.setGetterFastApiCompatible(isFastApiCompatible<Getter>);\n    using GetterTraits = FunctionTraits<Getter>;\n    BuildRtti<Configuration, typename GetterTraits::ReturnType>::build(prop.initType(), rtti);\n  }\n\n  template <typename T>\n  inline void registerReadonlyInstanceProperty(kj::StringPtr name, T value) {\n    auto prop = members[memberIndex++].initProperty();\n    prop.setName(name);\n    prop.setReadonly(true);\n    BuildRtti<Configuration, T>::build(prop.initType(), rtti);\n  }\n\n  template <const char* name, typename Getter, Getter getter, bool readOnly>\n  inline void registerLazyInstanceProperty() {\n    auto prop = members[memberIndex++].initProperty();\n    prop.setName(name);\n    prop.setReadonly(readOnly);\n    prop.setLazy(true);\n    using GetterTraits = FunctionTraits<Getter>;\n    BuildRtti<Configuration, typename GetterTraits::ReturnType>::build(prop.initType(), rtti);\n  }\n\n  template <const char* name, typename Getter, Getter getter, typename Setter, Setter setter>\n  inline void registerPrototypeProperty() {\n    auto prop = members[memberIndex++].initProperty();\n    prop.setName(name);\n    prop.setPrototype(true);\n    prop.setGetterFastApiCompatible(isFastApiCompatible<Getter>);\n    prop.setSetterFastApiCompatible(isFastApiCompatible<Setter>);\n    using GetterTraits = FunctionTraits<Getter>;\n    BuildRtti<Configuration, typename GetterTraits::ReturnType>::build(prop.initType(), rtti);\n  }\n\n  template <const char* name, typename Getter, Getter getter>\n  inline void registerReadonlyPrototypeProperty() {\n    auto prop = members[memberIndex++].initProperty();\n    prop.setName(name);\n    prop.setPrototype(true);\n    prop.setReadonly(true);\n    using GetterTraits = FunctionTraits<Getter>;\n    BuildRtti<Configuration, typename GetterTraits::ReturnType>::build(prop.initType(), rtti);\n  }\n\n  template <const char* name, typename Getter, Getter getter>\n  inline void registerInspectProperty() {}\n\n  template <const char* name, typename T>\n  inline void registerStaticConstant(T value) {\n    auto constant = members[memberIndex++].initConstant();\n    constant.setName(name);\n    constant.setValue(value);\n    // BuildRtti<Configuration, T>::build(constant.initType());\n  }\n\n  template <const char* name, typename Getter, Getter getter>\n  inline void registerStaticProperty() {\n    auto prop = members[memberIndex++].initProperty();\n    prop.setName(name);\n    prop.setReadonly(true);\n    prop.setGetterFastApiCompatible(isFastApiCompatible<Getter>);\n    using GetterTraits = FunctionTraits<Getter>;\n    BuildRtti<Configuration, typename GetterTraits::ReturnType>::build(prop.initType(), rtti);\n  }\n\n  template <typename Property, Property Self::*property>\n  void registerStructProperty(const char* name) {\n    auto prop = members[memberIndex++].initProperty();\n    prop.setName(name);\n    BuildRtti<Configuration, Property>::build(prop.initType(), rtti);\n  }\n\n  template <const char* name, auto Method>\n  inline void registerMethod() {\n    auto method = members[memberIndex++].initMethod();\n\n    method.setName(name);\n    method.setFastApiCompatible(isFastApiCompatible<decltype(Method)>);\n\n    using Traits = FunctionTraits<decltype(Method)>;\n    BuildRtti<Configuration, typename Traits::ReturnType>::build(method.initReturnType(), rtti);\n    using Args = Traits::ArgsTuple;\n    TupleRttiBuilder<Configuration, Args>::build(method.initArgs(std::tuple_size_v<Args>), rtti);\n  }\n\n  template <typename Method, Method method>\n  inline void registerCallable() {\n    auto func = structure.initCallable();\n\n    using Traits = FunctionTraits<Method>;\n    BuildRtti<Configuration, typename Traits::ReturnType>::build(func.initReturnType(), rtti);\n    using Args = Traits::ArgsTuple;\n    TupleRttiBuilder<Configuration, Args>::build(func.initArgs(std::tuple_size_v<Args>), rtti);\n  }\n\n  template <const char* name, typename Method, Method>\n  inline void registerStaticMethod() {\n    auto method = members[memberIndex++].initMethod();\n\n    method.setName(name);\n    method.setStatic(true);\n    method.setFastApiCompatible(isFastApiCompatible<Method>);\n\n    using Traits = FunctionTraits<Method>;\n    BuildRtti<Configuration, typename Traits::ReturnType>::build(method.initReturnType(), rtti);\n    using Args = Traits::ArgsTuple;\n    TupleRttiBuilder<Configuration, Args>::build(method.initArgs(std::tuple_size_v<Args>), rtti);\n  }\n\n  template <const char* name, typename Method, Method>\n  inline void registerIterable() {\n    structure.setIterable(true);\n\n    auto method = structure.initIterator();\n    method.setName(name);\n    using Traits = FunctionTraits<Method>;\n    BuildRtti<Configuration, typename Traits::ReturnType>::build(method.initReturnType(), rtti);\n    using Args = Traits::ArgsTuple;\n    TupleRttiBuilder<Configuration, Args>::build(method.initArgs(std::tuple_size_v<Args>), rtti);\n  }\n\n  template <const char* name, typename Method, Method>\n  inline void registerAsyncIterable() {\n    structure.setAsyncIterable(true);\n\n    auto method = structure.initAsyncIterator();\n    method.setName(name);\n    using Traits = FunctionTraits<Method>;\n    BuildRtti<Configuration, typename Traits::ReturnType>::build(method.initReturnType(), rtti);\n    using Args = Traits::ArgsTuple;\n    TupleRttiBuilder<Configuration, Args>::build(method.initArgs(std::tuple_size_v<Args>), rtti);\n  }\n\n  template <const char* name, typename Method, Method>\n  inline void registerDispose() {\n    structure.setDisposable(true);\n\n    auto method = structure.initDispose();\n    method.setName(name);\n    using Traits = FunctionTraits<Method>;\n    BuildRtti<Configuration, typename Traits::ReturnType>::build(method.initReturnType(), rtti);\n    using Args = Traits::ArgsTuple;\n    TupleRttiBuilder<Configuration, Args>::build(method.initArgs(std::tuple_size_v<Args>), rtti);\n  }\n\n  template <const char* name, typename Method, Method>\n  inline void registerAsyncDispose() {\n    structure.setAsyncDisposable(true);\n\n    auto method = structure.initAsyncDispose();\n    method.setName(name);\n    using Traits = FunctionTraits<Method>;\n    BuildRtti<Configuration, typename Traits::ReturnType>::build(method.initReturnType(), rtti);\n    using Args = Traits::ArgsTuple;\n    TupleRttiBuilder<Configuration, Args>::build(method.initArgs(std::tuple_size_v<Args>), rtti);\n  }\n\n  inline void registerTypeScriptRoot() {\n    structure.setTsRoot(true);\n  }\n\n  template <const char* tsOverride>\n  inline void registerTypeScriptOverride() {\n    structure.setTsOverride(tsOverride);\n  }\n\n  template <const char* tsDefine>\n  inline void registerTypeScriptDefine() {\n    structure.setTsDefine(tsDefine);\n  }\n\n  inline void registerJsBundle(Bundle::Reader bundle) {\n    for (auto module: bundle.getModules()) {\n      auto m = modules[moduleIndex++];\n      m.setSpecifier(module.getName());\n      m.setTsDeclarations(module.getTsDeclaration());\n    }\n  }\n\n  template <typename Type, typename GetNamedMethod, GetNamedMethod getNamedMethod>\n  inline void registerWildcardProperty() {\n    // Nothing to do in this case.\n  }\n};\n\n// Concept: true when T has registerMembers() function generated by JSG_RESOURCE/JSG_STRUCT\ntemplate <typename T>\nconcept HasRegisterMembers = requires { T::template registerMembers<MemberCounter, T>; };\n\n// Concept: true when T has constructor() function\ntemplate <typename T>\nconcept HasConstructor = requires { T::constructor; };\n\ntemplate <typename Configuration, typename T>\n  requires HasRegisterMembers<T>\nstruct BuildRtti<Configuration, T> {\n  static void build(Type::Builder builder, Builder<Configuration>& rtti) {\n    auto structure = builder.initStructure();\n    structure.setName(jsg::typeName(typeid(T)));\n    structure.setFullyQualifiedName(jsg::fullyQualifiedTypeName(typeid(T)));\n    rtti.template structure<T>();\n  }\n\n  static void build(Structure::Builder builder, Builder<Configuration>& rtti) {\n    builder.setName(jsg::typeName(typeid(T)));\n    builder.setFullyQualifiedName(jsg::fullyQualifiedTypeName(typeid(T)));\n\n    MemberCounter counter;\n    if constexpr (isDetected<GetConfiguration, T>()) {\n      T::template registerMembers<decltype(counter), T>(counter, rtti.config);\n    } else {\n      T::template registerMembers<decltype(counter), T>(counter);\n    }\n    auto membersCount = counter.members;\n\n    if constexpr (HasConstructor<T>) {\n      membersCount++;\n    }\n\n    auto members = builder.initMembers(membersCount);\n    auto modules = counter.modules > 0 ? builder.initBuiltinModules(counter.modules)\n                                       : capnp::List<Module>::Builder();\n    MembersBuilder<T, Configuration> membersBuilder(builder, members, modules, rtti);\n    if constexpr (isDetected<GetConfiguration, T>()) {\n      T::template registerMembers<decltype(membersBuilder), T>(membersBuilder, rtti.config);\n    } else {\n      T::template registerMembers<decltype(membersBuilder), T>(membersBuilder);\n    }\n\n    if constexpr (HasConstructor<T>) {\n      auto constructor = members[membersBuilder.memberIndex++].initConstructor();\n      using Traits = FunctionTraits<decltype(T::constructor)>;\n      using Args = Traits::ArgsTuple;\n      TupleRttiBuilder<Configuration, Args>::build(\n          constructor.initArgs(std::tuple_size_v<Args>), rtti);\n    }\n  }\n};\n\n}  // namespace impl\n\n}  // namespace workerd::jsg::rtti\n"
  },
  {
    "path": "src/workerd/jsg/script.c++",
    "content": "#include \"script.h\"\n\n#include <workerd/jsg/jsvalue.h>\n\nnamespace workerd::jsg {\n\njsg::JsValue NonModuleScript::runAndReturn(jsg::Lock& js) const {\n  auto boundScript = unboundScript.Get(js.v8Isolate)->BindToCurrentContext();\n  return jsg::JsValue(check(boundScript->Run(js.v8Context())));\n}\n\nvoid NonModuleScript::run(jsg::Lock& js) const {\n  auto boundScript = unboundScript.Get(js.v8Isolate)->BindToCurrentContext();\n  check(boundScript->Run(js.v8Context()));\n}\n\nNonModuleScript NonModuleScript::compile(jsg::Lock& js, kj::StringPtr code, kj::StringPtr name) {\n  // Create a dummy script origin for it to appear in Sources panel.\n  auto isolate = js.v8Isolate;\n  v8::ScriptOrigin origin(js.str(name));\n  v8::ScriptCompiler::Source source(js.str(code), origin);\n  return NonModuleScript(js, check(v8::ScriptCompiler::CompileUnboundScript(isolate, &source)));\n}\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/script.h",
    "content": "#pragma once\n\n#include <workerd/jsg/jsg.h>\n\nnamespace workerd::jsg {\n\n// jsg::NonModuleScript wraps a v8::UnboundScript.\n// An unbound script is a script that has been compiled but is not\n// yet bound to a specific context.\nclass NonModuleScript final {\n public:\n  NonModuleScript(jsg::Lock& js, v8::Local<v8::UnboundScript> script)\n      : unboundScript(js.v8Isolate, script) {}\n\n  NonModuleScript(NonModuleScript&&) = default;\n  NonModuleScript& operator=(NonModuleScript&&) = default;\n  KJ_DISALLOW_COPY(NonModuleScript);\n\n  // Running the script will create a v8::Script instance bound to the given\n  // context then will run it to completion.\n  void run(jsg::Lock& js) const;\n\n  jsg::JsValue runAndReturn(jsg::Lock& js) const;\n\n  static jsg::NonModuleScript compile(\n      jsg::Lock& js, kj::StringPtr code, kj::StringPtr name = \"worker.js\");\n\n private:\n  v8::Global<v8::UnboundScript> unboundScript;\n};\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/ser-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"jsg-test.h\"\n#include \"ser.h\"\n\nnamespace workerd::jsg::test {\nnamespace {\n\nV8System v8System;\nclass ContextGlobalObject: public jsg::Object, public ContextGlobal {};\n\nkj::Array<kj::byte> lastSerializedData;\n\nstruct SerTestContext: public ContextGlobalObject {\n  enum class SerializationTag {\n    FOO,\n    BAR,\n    BAZ,\n    QUX,\n  };\n\n  struct Foo: public jsg::Object {\n    uint i;\n    Foo(uint i): i(i) {}\n\n    static jsg::Ref<Foo> constructor(jsg::Lock& js, uint i) {\n      return js.alloc<Foo>(i);\n    }\n\n    int getI() {\n      return i;\n    }\n\n    JSG_RESOURCE_TYPE(Foo) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(i, getI);\n    }\n\n    void serialize(jsg::Lock& js, jsg::Serializer& serializer) {\n      serializer.writeRawUint32(i);\n    }\n    static jsg::Ref<Foo> deserialize(Lock& js, SerializationTag tag, Deserializer& deserializer) {\n      KJ_ASSERT(tag == SerializationTag::FOO);\n\n      // Intentionally deserialize differently so we can detect it.\n      return js.alloc<Foo>(deserializer.readRawUint32() + 2);\n    }\n    JSG_SERIALIZABLE(SerializationTag::FOO);\n  };\n\n  struct Bar: public jsg::Object {\n    kj::String text;\n    Bar(kj::String text): text(kj::mv(text)) {}\n\n    static jsg::Ref<Bar> constructor(jsg::Lock& js, kj::String text) {\n      return js.alloc<Bar>(kj::mv(text));\n    }\n\n    kj::String getText() {\n      return kj::str(text);\n    }\n\n    JSG_RESOURCE_TYPE(Bar) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(text, getText);\n    }\n\n    void serialize(jsg::Lock& js, jsg::Serializer& serializer) {\n      serializer.writeRawUint64(text.size());\n      serializer.writeRawBytes(text.asBytes());\n    }\n    static jsg::Ref<Bar> deserialize(Lock& js, SerializationTag tag, Deserializer& deserializer) {\n      KJ_ASSERT(tag == SerializationTag::BAR);\n\n      size_t size = deserializer.readRawUint64();\n      auto bytes = deserializer.readRawBytes(size);\n      // Intentionally deserialize differently so we can detect it.\n      return js.alloc<Bar>(kj::str(bytes.asChars(), '!'));\n    }\n    JSG_SERIALIZABLE(SerializationTag::BAR);\n  };\n\n  struct Baz: public jsg::Object {\n    bool serializeThrows;\n    Baz(bool serializeThrows): serializeThrows(serializeThrows) {}\n    static jsg::Ref<Baz> constructor(jsg::Lock& js, bool serializeThrows) {\n      return js.alloc<Baz>(serializeThrows);\n    }\n\n    JSG_RESOURCE_TYPE(Baz) {}\n\n    void serialize(jsg::Lock& js, jsg::Serializer& serializer) {\n      JSG_REQUIRE(!serializeThrows, Error, \"throw from serialize()\");\n    }\n    static jsg::Ref<Bar> deserialize(Lock& js, SerializationTag tag, Deserializer& deserializer) {\n      JSG_FAIL_REQUIRE(Error, \"throw from deserialize()\");\n    }\n    JSG_SERIALIZABLE(SerializationTag::BAZ);\n  };\n\n  // Qux is like Bar but serializes its string by converting it to a JS value first.\n  struct Qux: public jsg::Object {\n    kj::String text;\n    Qux(kj::String text): text(kj::mv(text)) {}\n\n    static jsg::Ref<Qux> constructor(jsg::Lock& js, kj::String text) {\n      return js.alloc<Qux>(kj::mv(text));\n    }\n\n    kj::String getText() {\n      return kj::str(text);\n    }\n\n    JSG_RESOURCE_TYPE(Qux) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(text, getText);\n    }\n\n    void serialize(\n        jsg::Lock& js, jsg::Serializer& serializer, const TypeHandler<kj::String>& stringHandler) {\n      // V2 prefers to serialize the string as a JS value.\n      serializer.write(js, JsValue(stringHandler.wrap(js, kj::str(text, '?'))));\n    }\n    static jsg::Ref<Qux> deserialize(Lock& js,\n        SerializationTag tag,\n        Deserializer& deserializer,\n        const TypeHandler<kj::String>& stringHandler) {\n      KJ_ASSERT(tag == SerializationTag::QUX);\n\n      return js.alloc<Qux>(\n          KJ_ASSERT_NONNULL(stringHandler.tryUnwrap(js, deserializer.readValue(js))));\n    }\n    JSG_SERIALIZABLE(SerializationTag::QUX);\n  };\n\n  JsValue roundTrip(Lock& js, JsValue in) {\n    auto content = ({\n      Serializer ser(js);\n      ser.write(js, in);\n      ser.release();\n    });\n\n    auto result = ({\n      Deserializer deser(js, content);\n      deser.readValue(js);\n    });\n\n    // Save the last serialization off to the side.\n    lastSerializedData = kj::mv(content.data);\n\n    return result;\n  }\n\n  JSG_RESOURCE_TYPE(SerTestContext) {\n    JSG_NESTED_TYPE(Foo);\n    JSG_NESTED_TYPE(Bar);\n    JSG_NESTED_TYPE(Baz);\n    JSG_NESTED_TYPE(Qux);\n    JSG_METHOD(roundTrip);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(SerTestIsolate,\n    SerTestContext,\n    SerTestContext::Foo,\n    SerTestContext::Bar,\n    SerTestContext::Baz,\n    SerTestContext::Qux);\n\n// Define a whole second JSG isolate type that contains \"updated\" code where Bar no longer wraps\n// a string, it wraps an arbitrary value.\nstruct SerTestContextV2: public ContextGlobalObject {\n  enum class SerializationTag { FOO, BAR_OLD, BAZ, QUX, BAR_V2 };\n\n  struct Bar: public jsg::Object {\n    JsRef<JsValue> val;\n    Bar(JsRef<JsValue> val): val(kj::mv(val)) {}\n\n    static jsg::Ref<Bar> constructor(jsg::Lock& js, JsRef<JsValue> val) {\n      return js.alloc<Bar>(kj::mv(val));\n    }\n\n    JsRef<JsValue> getVal(Lock& js) {\n      return val.addRef(js);\n    }\n\n    JSG_RESOURCE_TYPE(Bar) {\n      JSG_READONLY_PROTOTYPE_PROPERTY(val, getVal);\n    }\n\n    void serialize(jsg::Lock& js, jsg::Serializer& serializer) {\n      // V2 just writes a value!\n      serializer.write(js, JsValue(val.getHandle(js)));\n    }\n    static jsg::Ref<Bar> deserialize(Lock& js, SerializationTag tag, Deserializer& deserializer) {\n      if (tag == SerializationTag::BAR_OLD) {\n        // Oh, it's an old value.\n        size_t size = deserializer.readRawUint64();\n        auto bytes = deserializer.readRawBytes(size);\n\n        return js.alloc<Bar>(JsRef<JsValue>(js, js.str(kj::str(\"old:\", bytes.asChars()))));\n      } else {\n        KJ_ASSERT(tag == SerializationTag::BAR_V2);\n\n        return js.alloc<Bar>(JsRef<JsValue>(js, deserializer.readValue(js)));\n      }\n    }\n    JSG_SERIALIZABLE(SerializationTag::BAR_V2, SerializationTag::BAR_OLD);\n  };\n\n  JsValue roundTrip(Lock& js, JsValue in) {\n    auto content = ({\n      Serializer ser(js);\n      ser.write(js, in);\n      ser.release();\n    });\n\n    auto result = ({\n      Deserializer deser(js, content);\n      deser.readValue(js);\n    });\n\n    // Save the last serialization off to the side.\n    lastSerializedData = kj::mv(content.data);\n\n    return result;\n  }\n\n  JsValue deserializeLast(Lock& js) {\n    Deserializer deser(js, lastSerializedData);\n    return deser.readValue(js);\n  }\n\n  JSG_RESOURCE_TYPE(SerTestContextV2) {\n    JSG_NESTED_TYPE(Bar);\n    JSG_METHOD(roundTrip);\n    JSG_METHOD(deserializeLast);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(SerTestIsolateV2, SerTestContextV2, SerTestContextV2::Bar);\n\nKJ_TEST(\"serialization\") {\n  Evaluator<SerTestContext, SerTestIsolate> e(v8System);\n\n  // Test serializing built-in values.\n  e.expectEval(\"roundTrip(123)\", \"number\", \"123\");\n  e.expectEval(\"JSON.stringify(roundTrip({foo: 123}))\", \"string\", \"{\\\"foo\\\":123}\");\n\n  // Test serializing host objects.\n  e.expectEval(\"roundTrip(new Foo(123)).i\", \"number\", \"125\");\n  e.expectEval(\"roundTrip(new Qux(\\\"hello\\\")).text\", \"string\", \"hello?\");\n  e.expectEval(\"roundTrip(new Bar(\\\"hello\\\")).text\", \"string\", \"hello!\");\n\n  // Test throwing from serialize/deserialize\n  e.expectEval(\"roundTrip(new Baz(true)).text\", \"throws\", \"Error: throw from serialize()\");\n  e.expectEval(\"roundTrip(new Baz(false)).text\", \"throws\", \"Error: throw from deserialize()\");\n\n  // Let's set up the \"new version\" of the code.\n  Evaluator<SerTestContextV2, SerTestIsolateV2> e2(v8System);\n\n  // This will deserialize the last-serialized bytes from above, where we serialized Bar(\"hello\").\n  // However, it is using a \"new version\" of the code where Bar's serialization has changed, but\n  // the old version is still accepted.\n  e2.expectEval(\"deserializeLast().val\", \"string\", \"old:hello\");\n\n  // Also try round-tripping the new version. It now accepts arbitrary values, not just strings.\n  e2.expectEval(\"roundTrip(new Bar(123)).val\", \"number\", \"123\");\n\n  // Note that cycles through host objects are correctly serialized!\n  //\n  // V8 BUG ALERT: The below works if we use `obj` as the root of serialization, but NOT if we\n  //   use `bar` as the root. The reason is a flaw in the design of V8's callbacks for parsing\n  //   host objects. V8 makes a single callback to the embedder which fully reads the object and\n  //   returns a handle. However, this means that V8 cannot put the object into the backreference\n  //   table until this callback returns. If, while parsing the object, we encounter a\n  //   backreference to the object itself (a cycle), the deserializer will find the backreference\n  //   is not in the table and therefore raises an error. This is not a problem for native objects\n  //   because V8 allocates the object first, then immediately adds it to the backreference table,\n  //   and only then parses its content -- and this is why everything works fine if we start with\n  //   a native object as the root, as in this test. The API for host objects needs to be extended\n  //   somehow to allow the object to be inserted into the table before parsing its content.\n  e2.expectEval(\"let obj = {i: 321};\\n\"\n                \"let bar = new Bar(obj);\\n\"\n                \"obj.bar = bar;\\n\"\n                \"roundTrip(obj).bar.val.bar.val.bar.val.i\",\n      \"number\", \"321\");\n}\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/ser.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"ser.h\"\n\n#include \"dom-exception.h\"\n#include \"setup.h\"\n\n#include <v8-proxy.h>\n\nnamespace workerd::jsg {\nnamespace {\n// Keep in sync with the nativeError serialization tag defined in\n// worker-interface.capnp\nconstexpr uint32_t SERIALIZATION_TAG_NATIVE_ERROR = 10;\n\n// The error tag is serialized as a uint32_t immediately following\n// the SERIALIZATION_TAG_NATIVE_ERROR tag in order to more efficiently\n// determine the type of error when deserializing so that we can\n// construct the appropriate v8::Exception type on deserialization\n// without having to expensive string comparison on the error name.\nenum class ErrorTag : uint32_t {\n  // The UNKNOWN tag is used when the error name is not recognized.\n  // When this occurs, we will serialize the name of the error, and\n  // when it is deserialized, we will create a generic Error and then\n  // set the name to the stored name.\n  UNKNOWN,\n  ERROR,\n  TYPE_ERROR,\n  RANGE_ERROR,\n  REFERENCE_ERROR,\n  SYNTAX_ERROR,\n  WASM_COMPILE_ERROR,\n  WASM_LINK_ERROR,\n  WASM_RUNTIME_ERROR,\n  WASM_SUSPEND_ERROR,\n  EVAL_ERROR,\n  URI_ERROR,\n  AGGREGATE_ERROR,\n  SUPPRESSED_ERROR,\n};\n\nconstexpr ErrorTag getErrorTagFromName(jsg::Lock& js, kj::StringPtr str) {\n  if (str == \"Error\"_kj) {\n    return ErrorTag::ERROR;\n  } else if (str == \"TypeError\"_kj) {\n    return ErrorTag::TYPE_ERROR;\n  } else if (str == \"RangeError\"_kj) {\n    return ErrorTag::RANGE_ERROR;\n  } else if (str == \"ReferenceError\"_kj) {\n    return ErrorTag::REFERENCE_ERROR;\n  } else if (str == \"SyntaxError\"_kj) {\n    return ErrorTag::SYNTAX_ERROR;\n  } else if (str == \"WasmCompileError\"_kj) {\n    return ErrorTag::WASM_COMPILE_ERROR;\n  } else if (str == \"WasmLinkError\"_kj) {\n    return ErrorTag::WASM_LINK_ERROR;\n  } else if (str == \"WasmRuntimeError\"_kj) {\n    return ErrorTag::WASM_RUNTIME_ERROR;\n  } else if (str == \"WasmSuspendError\"_kj) {\n    return ErrorTag::WASM_SUSPEND_ERROR;\n  } else if (str == \"EvalError\"_kj) {\n    return ErrorTag::EVAL_ERROR;\n  } else if (str == \"URIError\"_kj) {\n    return ErrorTag::URI_ERROR;\n  } else if (str == \"AggregateError\"_kj) {\n    return ErrorTag::AGGREGATE_ERROR;\n  } else if (str == \"SuppressedError\"_kj) {\n    return ErrorTag::SUPPRESSED_ERROR;\n  }\n  return ErrorTag::UNKNOWN;\n}\n\nJsObject toJsError(Lock& js, ErrorTag tag, JsValue message) {\n  auto str = message.toJsString(js);\n  switch (tag) {\n    case ErrorTag::ERROR: {\n      return JsObject(v8::Exception::Error(str).As<v8::Object>());\n    }\n    case ErrorTag::TYPE_ERROR: {\n      return JsObject(v8::Exception::TypeError(str).As<v8::Object>());\n    }\n    case ErrorTag::RANGE_ERROR: {\n      return JsObject(v8::Exception::RangeError(str).As<v8::Object>());\n    }\n    case ErrorTag::REFERENCE_ERROR: {\n      return JsObject(v8::Exception::ReferenceError(str).As<v8::Object>());\n    }\n    case ErrorTag::SYNTAX_ERROR: {\n      return JsObject(v8::Exception::SyntaxError(str).As<v8::Object>());\n    }\n    case ErrorTag::WASM_COMPILE_ERROR: {\n      return JsObject(v8::Exception::WasmCompileError(str).As<v8::Object>());\n    }\n    case ErrorTag::WASM_LINK_ERROR: {\n      return JsObject(v8::Exception::WasmLinkError(str).As<v8::Object>());\n    }\n    case ErrorTag::WASM_RUNTIME_ERROR: {\n      return JsObject(v8::Exception::WasmRuntimeError(str).As<v8::Object>());\n    }\n    case ErrorTag::WASM_SUSPEND_ERROR: {\n      return JsObject(v8::Exception::WasmSuspendError(str).As<v8::Object>());\n    }\n    case ErrorTag::EVAL_ERROR: {\n      return JsObject(v8::Exception::EvalError(str).As<v8::Object>());\n    }\n    case ErrorTag::URI_ERROR: {\n      return JsObject(v8::Exception::URIError(str).As<v8::Object>());\n    }\n    case ErrorTag::AGGREGATE_ERROR: {\n      return JsObject(v8::Exception::AggregateError(str).As<v8::Object>());\n    }\n    case ErrorTag::SUPPRESSED_ERROR: {\n      return JsObject(v8::Exception::SuppressedError(str).As<v8::Object>());\n    }\n    case ErrorTag::UNKNOWN: {\n      return JsObject(v8::Exception::Error(str).As<v8::Object>());\n    }\n  }\n  return JsObject(v8::Exception::Error(str).As<v8::Object>());\n}\n}  // namespace\n\nvoid Serializer::ExternalHandler::serializeFunction(\n    jsg::Lock& js, jsg::Serializer& serializer, v8::Local<v8::Function> func) {\n  JSG_FAIL_REQUIRE(DOMDataCloneError, func, \" could not be cloned.\");\n}\n\nvoid Serializer::ExternalHandler::serializeProxy(\n    jsg::Lock& js, jsg::Serializer& serializer, v8::Local<v8::Proxy> proxy) {\n  JSG_FAIL_REQUIRE(DOMDataCloneError, proxy, \" could not be cloned.\");\n}\n\nSerializer::Serializer(Lock& js, Options options)\n    : externalHandler(options.externalHandler),\n      treatClassInstancesAsPlainObjects(options.treatClassInstancesAsPlainObjects),\n      treatErrorsAsHostObjects(js.isUsingEnhancedErrorSerialization()),\n      ser(js.v8Isolate, this) {\n#ifdef KJ_DEBUG\n  kj::requireOnStack(this, \"jsg::Serializer must be allocated on the stack\");\n#endif\n  if (!treatClassInstancesAsPlainObjects) {\n    prototypeOfObject = js.obj().getPrototype(js);\n  }\n  if (externalHandler != kj::none) {\n    // If we have an ExternalHandler, we'll ask it to serialize host objects.\n    ser.SetTreatFunctionsAsHostObjects(true);\n    ser.SetTreatProxiesAsHostObjects(true);\n  }\n  KJ_IF_SOME(version, options.version) {\n    KJ_ASSERT(version >= 13, \"The minimum serialization version is 13.\");\n    KJ_ASSERT(jsg::check(ser.SetWriteVersion(version)));\n  }\n  if (!options.omitHeader) {\n    ser.WriteHeader();\n  }\n}\n\nv8::Maybe<uint32_t> Serializer::GetSharedArrayBufferId(\n    v8::Isolate* isolate, v8::Local<v8::SharedArrayBuffer> sab) {\n  auto& js = jsg::Lock::from(isolate);\n  uint32_t n;\n  auto value = JsValue(sab);\n  for (n = 0; n < sharedArrayBuffers.size(); n++) {\n    // If the SharedArrayBuffer has already been added, return the existing ID for it.\n    if (sharedArrayBuffers[n].getHandle(js) == value) {\n      return v8::Just(n);\n    }\n  }\n  sharedArrayBuffers.add(jsg::JsRef(js, value));\n  sharedBackingStores.add(sab->GetBackingStore());\n  return v8::Just(n);\n}\n\nvoid Serializer::throwDataCloneErrorForObject(jsg::Lock& js, v8::Local<v8::Object> obj) {\n  // The default error that V8 would generate is \"#<TypeName> could not be cloned.\" -- for some\n  // reason, it surrounds the type name in \"#<>\", which seems bizarre? Let's generate a better\n  // error.\n  auto message = kj::str(\"Could not serialize object of type \\\"\", obj->GetConstructorName(),\n      \"\\\". This type does \"\n      \"not support serialization.\");\n  auto exception = js.domException(kj::str(\"DataCloneError\"), kj::mv(message));\n  js.throwException(jsg::JsValue(KJ_ASSERT_NONNULL(exception.tryGetHandle(js))));\n}\n\nvoid Serializer::ThrowDataCloneError(v8::Local<v8::String> message) {\n  auto& js = jsg::Lock::current();\n  try {\n    auto exception = js.domException(kj::str(\"DataCloneError\"), kj::str(message));\n    js.v8Isolate->ThrowException(KJ_ASSERT_NONNULL(exception.tryGetHandle(js)));\n  } catch (JsExceptionThrown&) {\n    // Apparently an exception was thrown during the construction of the DOMException. Most likely\n    // we were terminated. In any case, we'll let that exception stay scheduled and propagate back\n    // to V8.\n  } catch (...) {\n    // A KJ exception was thrown, we'll have to convert it to JavaScript and propagate that\n    // exception instead.\n    throwInternalError(js.v8Isolate, kj::getCaughtExceptionAsKj());\n  }\n}\n\nbool Serializer::HasCustomHostObject(v8::Isolate* isolate) {\n  // By default, V8 will call WriteHostObject() for objects that have internal fields. We only need\n  // to override IsHostObject() if we want to treat pure-JS objects differently, which we do if\n  // treatClassInstancesAsPlainObjects is false, or if treatErrorsAsHostObjects is true.\n  return !treatClassInstancesAsPlainObjects || treatErrorsAsHostObjects;\n}\n\nv8::Maybe<bool> Serializer::IsHostObject(v8::Isolate* isolate, v8::Local<v8::Object> object) {\n  // This is only called if HasCustomHostObject() returned true.\n  KJ_ASSERT(!treatClassInstancesAsPlainObjects || treatErrorsAsHostObjects);\n\n  // Any object with internal fields is DEFINITELY a host object!\n  if (object->InternalFieldCount() > 0) {\n    return v8::Just(true);\n  }\n\n  // Native errors are host object if the enhanced_error_serialization compat flag is enalbed.\n  if (object->IsNativeError()) {\n    return v8::Just(treatErrorsAsHostObjects);\n  }\n\n  // If `treatClassInstancesAsPlainObjects` is on (the historical default), then nothing else is\n  // a host object. If it has been turned off (e.g. for RPC), then any object that isn't a raw\n  // object will be treated as a host object (mainly so that we can error out on these).\n  if (treatClassInstancesAsPlainObjects) return v8::Just(false);\n  KJ_ASSERT(!prototypeOfObject.IsEmpty());\n\n  // If the object's prototype is Object.prototype, then it is a plain object, which we'll allow\n  // to be serialized normally. Otherwise, it is a class instance, which we should treat as a host\n  // object. Inside `WriteHostObject()` we will throw DataCloneError due to the object not having\n  // internal fields.\n  return v8::Just(object->GetPrototypeV2() != prototypeOfObject);\n}\n\nv8::Maybe<bool> Serializer::WriteHostObject(v8::Isolate* isolate, v8::Local<v8::Object> object) {\n  try {\n    jsg::Lock& js = jsg::Lock::from(isolate);\n\n    if (object->IsNativeError()) {\n      auto nameStr = js.str(\"name\"_kj);\n      auto messageStr = js.str(\"message\"_kj);\n\n      // Get the standard name, message, stack, cause, error, errors properties from\n      // the error object.\n      writeRawUint32(SERIALIZATION_TAG_NATIVE_ERROR);\n\n      // A mix of ad-hoc and regular serialization. We first serialize\n      // the error tag, which is an enum that identifies the type of error\n      // for faster/easier deserialization. Then we serialize the message which\n      // usually come from the prototype. Then we grab the own properties,\n      // serializing the number of them followed by each name and value in\n      // sequence.\n\n      jsg::JsObject errorObj(object);\n      auto name = errorObj.get(js, nameStr);\n      auto nameKjStr = name.toString(js);\n      auto tag = getErrorTagFromName(js, nameKjStr);\n      writeRawUint32(static_cast<uint32_t>(tag));\n      // We only write the name if it is not one of the known error types.\n      if (tag == ErrorTag::UNKNOWN) {\n        write(js, name);\n      }\n\n      write(js, errorObj.get(js, messageStr));\n\n      auto names = errorObj.getPropertyNames(js, KeyCollectionFilter::OWN_ONLY,\n          PropertyFilter::ALL_PROPERTIES, IndexFilter::SKIP_INDICES);\n\n      auto obj = js.obj();\n      for (size_t n = 0; n < names.size(); n++) {\n        auto name = names.get(js, n);\n        // The name typically comes from the prototype and therefore\n        // do not show up in the own properties of the error object, and the\n        // message we want to treat specially since we need it early in the\n        // deserialization.\n        // Since we already have them serialized above, we can filter them\n        // out here.\n        if (name.strictEquals(nameStr) || name.strictEquals(messageStr)) continue;\n\n        obj.set(js, name, errorObj.get(js, name));\n      }\n      write(js, obj);\n\n      return v8::Just(true);\n    }\n\n    if (object->InternalFieldCount() != Wrappable::INTERNAL_FIELD_COUNT ||\n        !Wrappable::isWorkerdApiObject(object)) {\n      KJ_IF_SOME(eh, externalHandler) {\n        if (object->IsProxy()) {\n          eh.serializeProxy(js, *this, object.As<v8::Proxy>());\n          return v8::Just(true);\n        } else if (object->IsFunction()) {\n          eh.serializeFunction(js, *this, object.As<v8::Function>());\n          return v8::Just(true);\n        }\n      }\n\n      // v8::ValueSerializer by default will send us anything that has internal fields, but this\n      // object doesn't appear to match the internal fields expected on a JSG object.\n      //\n      // We also get here if treatClassInstancesAsPlainObjects is false, and the object is an\n      // application-defined class. We don't currently support serializing class instances.\n      throwDataCloneErrorForObject(js, object);\n    }\n\n    Wrappable* wrappable = reinterpret_cast<Wrappable*>(\n        object->GetAlignedPointerFromInternalField(Wrappable::WRAPPED_OBJECT_FIELD_INDEX,\n            static_cast<v8::EmbedderDataTypeTag>(Wrappable::WRAPPED_OBJECT_FIELD_INDEX)));\n\n    // HACK: Although we don't technically know yet that `wrappable` is an `Object`, we know that\n    //   only subclasses of `Object` register serializers. So *if* a serializer is found, then this\n    //   cast is valid, and the pointer won't be accessed otherwise. We can't do a dynamic_cast\n    //   here since `Wrappable` is privately inherited by `Object` and anyway we don't want the\n    //   overhead of dynamic_cast.\n    // TODO(cleanup): Probably `Wrappable` should contain a bool indicating if it is an `Object`\n    //   or not?\n    Object* obj = reinterpret_cast<jsg::Object*>(wrappable);\n\n    if (!IsolateBase::from(isolate).serialize(\n            Lock::from(isolate), typeid(*wrappable), *obj, *this)) {\n      // This type is not serializable.\n      throwDataCloneErrorForObject(js, object);\n    }\n\n    return v8::Just(true);\n  } catch (JsExceptionThrown&) {\n    return v8::Nothing<bool>();\n  } catch (...) {\n    throwInternalError(isolate, kj::getCaughtExceptionAsKj());\n    return v8::Nothing<bool>();\n  }\n}\n\nSerializer::Released Serializer::release() {\n  KJ_ASSERT(!released, \"The data has already been released.\");\n  released = true;\n  sharedArrayBuffers.clear();\n  arrayBuffers.clear();\n  auto pair = ser.Release();\n  return Released{\n    .data = kj::Array(pair.first, pair.second, jsg::SERIALIZED_BUFFER_DISPOSER),\n    .sharedArrayBuffers = sharedBackingStores.releaseAsArray(),\n    .transferredArrayBuffers = backingStores.releaseAsArray(),\n  };\n}\n\nvoid Serializer::transfer(Lock& js, const JsValue& value) {\n  KJ_ASSERT(!released, \"The data has already been released.\");\n  // Currently we only allow transfer of ArrayBuffers\n  v8::Local<v8::ArrayBuffer> arrayBuffer;\n  if (value.isArrayBufferView()) {\n    auto view = v8::Local<v8::Value>(value).As<v8::ArrayBufferView>();\n    arrayBuffer = view->Buffer();\n  } else if (value.isArrayBuffer()) {\n    arrayBuffer = v8::Local<v8::Value>(value).As<v8::ArrayBuffer>();\n  } else {\n    JSG_FAIL_REQUIRE(TypeError, \"Object is not transferable\");\n  }\n\n  // SharedArrayBuffers are not transferable. Per the HTML spec, attempting to\n  // transfer a SharedArrayBuffer (or a view backed by one) must throw a\n  // DataCloneError. We must check before calling Detach(), which would trigger\n  // a fatal V8 CHECK failure on non-detachable buffers.\n  JSG_REQUIRE(arrayBuffer->IsDetachable(), DOMDataCloneError, \"Object is not transferable.\");\n\n  uint32_t n;\n  for (n = 0; n < arrayBuffers.size(); n++) {\n    // If the ArrayBuffer has already been added, we do not want to try adding it again.\n    if (arrayBuffers[n].getHandle(js) == value) {\n      return;\n    }\n  }\n  arrayBuffers.add(jsg::JsRef(js, value));\n\n  backingStores.add(arrayBuffer->GetBackingStore());\n  check(arrayBuffer->Detach(v8::Local<v8::Value>()));\n  ser.TransferArrayBuffer(n, arrayBuffer);\n}\n\nvoid Serializer::write(Lock& js, const JsValue& value) {\n  KJ_ASSERT(!released, \"The data has already been released.\");\n  KJ_ASSERT(check(ser.WriteValue(js.v8Context(), value)));\n}\n\nDeserializer::ExternalHandler::~ExternalHandler() noexcept(false) {}\n\nDeserializer::Deserializer(Lock& js,\n    kj::ArrayPtr<const kj::byte> data,\n    kj::Maybe<kj::ArrayPtr<std::shared_ptr<v8::BackingStore>>> transferredArrayBuffers,\n    kj::Maybe<kj::ArrayPtr<std::shared_ptr<v8::BackingStore>>> sharedArrayBuffers,\n    kj::Maybe<Options> maybeOptions)\n    : totalInputSize(data.size()),\n      deser(js.v8Isolate, data.begin(), data.size(), this),\n      sharedBackingStores(kj::mv(sharedArrayBuffers)) {\n#ifdef KJ_DEBUG\n  kj::requireOnStack(this, \"jsg::Deserializer must be allocated on the stack\");\n#endif\n  init(js, kj::mv(transferredArrayBuffers), kj::mv(maybeOptions));\n}\n\nDeserializer::Deserializer(\n    Lock& js, Serializer::Released& released, kj::Maybe<Options> maybeOptions)\n    : Deserializer(js,\n          released.data.asPtr(),\n          released.transferredArrayBuffers.asPtr(),\n          released.sharedArrayBuffers.asPtr(),\n          kj::mv(maybeOptions)) {}\n\nvoid Deserializer::init(Lock& js,\n    kj::Maybe<kj::ArrayPtr<std::shared_ptr<v8::BackingStore>>> transferredArrayBuffers,\n    kj::Maybe<Options> maybeOptions) {\n  auto options = kj::mv(maybeOptions).orDefault({});\n  externalHandler = options.externalHandler;\n  if (options.readHeader) {\n    check(deser.ReadHeader(js.v8Context()));\n  }\n  preserveStackInErrors = options.preserveStackInErrors;\n  KJ_IF_SOME(version, options.version) {\n    KJ_ASSERT(version >= 13, \"The minimum serialization version is 13.\");\n    deser.SetWireFormatVersion(version);\n  }\n  KJ_IF_SOME(arrayBuffers, transferredArrayBuffers) {\n    for (auto n: kj::indices(arrayBuffers)) {\n      deser.TransferArrayBuffer(n, v8::ArrayBuffer::New(js.v8Isolate, kj::mv((arrayBuffers)[n])));\n    }\n  }\n}\n\nJsValue Deserializer::readValue(Lock& js) {\n  return JsValue(check(deser.ReadValue(js.v8Context())));\n}\n\nuint Deserializer::readRawUint32() {\n  uint32_t result;\n  KJ_ASSERT(deser.ReadUint32(&result), \"deserialization failure, possible corruption\");\n  return result;\n}\n\nuint64_t Deserializer::readRawUint64() {\n  uint64_t result;\n  KJ_ASSERT(deser.ReadUint64(&result), \"deserialization failure, possible corruption\");\n  return result;\n}\n\nkj::ArrayPtr<const kj::byte> Deserializer::readRawBytes(size_t size) {\n  const void* data;\n  KJ_ASSERT(deser.ReadRawBytes(size, &data), \"deserialization failure, possible corruption\");\n  return kj::arrayPtr(reinterpret_cast<const kj::byte*>(data), size);\n}\n\nkj::ArrayPtr<const kj::byte> Deserializer::readLengthDelimitedBytes() {\n  return readRawBytes(readRawUint64());\n}\n\nkj::String Deserializer::readRawString(size_t size) {\n  return kj::str(readRawBytes(size).asChars());\n}\n\nkj::String Deserializer::readLengthDelimitedString() {\n  return kj::str(readLengthDelimitedBytes().asChars());\n}\n\nv8::MaybeLocal<v8::SharedArrayBuffer> Deserializer::GetSharedArrayBufferFromId(\n    v8::Isolate* isolate, uint32_t clone_id) {\n  KJ_IF_SOME(backingStores, sharedBackingStores) {\n    KJ_ASSERT(clone_id < backingStores.size());\n    return v8::SharedArrayBuffer::New(isolate, backingStores[clone_id]);\n  }\n  return v8::MaybeLocal<v8::SharedArrayBuffer>();\n}\n\nv8::MaybeLocal<v8::Object> Deserializer::ReadHostObject(v8::Isolate* isolate) {\n  try {\n    uint tag = readRawUint32();\n\n    if (tag == SERIALIZATION_TAG_NATIVE_ERROR) {\n      auto& js = Lock::from(isolate);\n      auto stack = js.str(\"stack\"_kj);\n\n      // The first uint32_t is the error tag, which identifies the type of error.\n      auto errorTag = static_cast<ErrorTag>(readRawUint32());\n      // If The error tag is UNKNOWN, we will read the name of the error next.\n      // If the error is known, we don't both serializing the name.\n      kj::Maybe<JsValue> maybeName;\n      if (errorTag == ErrorTag::UNKNOWN) {\n        maybeName = readValue(js);\n      }\n\n      // The next value is the message, which is always present.\n      // Now let's create the error object based on the tag and message.\n      auto obj = toJsError(js, errorTag, readValue(js));\n\n      // If we have a name, we set it on the error object. This is not\n      // perfect but it gets close enough. Specifically, when the error\n      // was serialized, if the user has modified the name or created\n      // their own subclass, then we end up having to create just a\n      // regular error here and change the name. It is not possible\n      // for us here to clone the exact error class that was used,\n      // so instanceof checks will not work as expected. But, that's ok.\n      KJ_IF_SOME(name, maybeName) {\n        // We use defineProperty here since the name is not typically\n        // modifiable with set() on error objects.\n        obj.defineProperty(js, \"name\"_kj, name);\n      }\n\n      // Now let's read the remaining properties... They were serialized as\n      // a plain object with some own properties.\n      KJ_IF_SOME(serObj, readValue(js).tryCast<JsObject>()) {\n        auto names = serObj.getPropertyNames(js, KeyCollectionFilter::OWN_ONLY,\n            PropertyFilter::ALL_PROPERTIES, IndexFilter::SKIP_INDICES);\n        for (size_t n = 0; n < names.size(); n++) {\n          auto name = names.get(js, n);\n          // If the preserveStackInErrors option is false, then we will not\n          // restore the serialized stack property if it is included in the\n          // serialized output.\n          if (!preserveStackInErrors && name.strictEquals(stack)) continue;\n          auto value = serObj.get(js, name);\n          obj.set(js, name, value);\n        }\n      }\n\n      v8::Local<v8::Object> ret = obj;\n      return ret;\n    }\n\n    KJ_IF_SOME(result, IsolateBase::from(isolate).deserialize(Lock::from(isolate), tag, *this)) {\n      return result;\n    } else {\n      // Unknown tag is a platform error, so use KJ assert.\n      KJ_FAIL_ASSERT(\"encountered unknown tag in deserialization\", tag);\n    }\n  } catch (JsExceptionThrown&) {\n    return {};\n  } catch (...) {\n    throwInternalError(isolate, kj::getCaughtExceptionAsKj());\n    return {};\n  }\n}\n\nvoid SerializedBufferDisposer::disposeImpl(void* firstElement,\n    size_t elementSize,\n    size_t elementCount,\n    size_t capacity,\n    void (*destroyElement)(void*)) const {\n  free(firstElement);\n}\n\nJsValue structuredClone(\n    Lock& js, const JsValue& value, kj::Maybe<kj::Array<JsValue>> maybeTransfer) {\n  Serializer ser(js);\n  KJ_IF_SOME(transfers, maybeTransfer) {\n    for (auto& item: transfers) {\n      ser.transfer(js, item);\n    }\n  }\n  ser.write(js, value);\n  auto released = ser.release();\n  Deserializer des(js, released);\n  return des.readValue(js);\n}\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/ser.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/jsg/jsg.h>\n\n#include <v8-value-serializer.h>\n\n#include <kj/vector.h>\n\nnamespace workerd::jsg {\n\n// Wraps the v8::ValueSerializer and v8::ValueSerializer::Delegate implementation.\n// Must be allocated on the stack, and requires that a v8::HandleScope exist in\n// the stack.\n//\n// To declare a JSG_RESOURCE_TYPE as serializeable, you must declare two special methods\n// `serialize()` and `deserialize()`, and also use the JSG_SERIALIZABLE macro, which must appear\n// after the `JSG_RESOURCE_TYPE` block (NOT inside it). Example:\n//\n//     class Foo: public jsg::Object {\n//     public:\n//       // ...\n//\n//       JSG_RESOURCE_TYPE(Foo) {\n//         // ...\n//       }\n//\n//       void serialize(jsg::Lock& js, jsg::Serializer& serializer);\n//       static jsg::Ref<Foo> deserialize(Lock& js, MyTag tag, Deserializer& deserializer);\n//       JSG_SERIALIZABLE(MyTag::FOO_V2, MyTag::FOO_V1);\n//\n//       // ...\n//     };\n//\n// * `MyTag` is some enum type declared in the application which enumerates all known serializeable\n//   types. This can be any enum, but it is suggested that all types in the application use the\n//   same enum type, and the numeric values of the tags must never change. A Cap'n Proto enum is\n//   suggested.\n// * `JSG_SERIALIZABLE`'s parameters are a list of tags that encode this type. There may be more\n//   than one tag listed to support versioning. The first tag listed is the current version, which\n//   is the format that `serialize()` will write. The other tags are non-current versions that\n//   `deserialize()` accepts in addition to the current version. These are usually old versions,\n//   but could also include a new version that hasn't fully rolled out yet -- it will be necessary\n//   to fully roll out support for parsing a new version before anyone can start generating it.\n// * The serialization system automatically handles writing and reading the tag values before\n//   calling the methods.\n// * serialize() makes a series of calls to serializer.write*() methods to write the content of the\n//   object.\n// * deserialize() makes a corresponding series of calls to deserializer.read*() methods to read\n//   the object. These must be the exact corresponding calls in the same order as serialize()\n//   would have made them. The sequence can never change once data has been written for a given\n//   tag version; the only way to change is to define a new version.\n// * Both `serialize()` and `deserialize()` can take additional arguments of the form\n//   `const jsg::TypeHandler<SomeType>&`, which will automatically be filled in with the\n//   corresponding type handler. This is useful if the serializer wants to, say, assemble a\n//   JSG_STRUCT, convert it into an actual JS object, and serialize that.\nclass Serializer final: v8::ValueSerializer::Delegate {\n public:\n  // \"Externals\" are values which can be serialized, but refer to some external resource, rather\n  // than being self-contained. The way externals are supported depends on the serialization\n  // context: passing externals over RPC, for example, is completely different from storing them\n  // to disk.\n  //\n  // A `Serializer` instance may have an `ExternalHandler` which can be used when serializing\n  // externals. This type has no methods, but is meant to be subclassed. A host object which\n  // represents an external and is trying to serialize itself should use dynamic_cast to try to\n  // downcast `ExternalHandler` to any particular handler interface it supports. If the handler\n  // doesn't implement any supported subclass, then serialization is not possible, and an\n  // appropriate exception should be thrown.\n  class ExternalHandler {\n   public:\n    // Tries to serialize a function as an external. The default implementation throws\n    // DataCloneError.\n    virtual void serializeFunction(\n        jsg::Lock& js, jsg::Serializer& serializer, v8::Local<v8::Function> func);\n\n    // Tries to serialize a proxy as an external. The default implementation throws\n    // DataCloneError.\n    //\n    // TODO(cleanup): This is a bit of a hack to support an RpcTarget that is wrapped in a Proxy.\n    //   For RpcTarget specifically, this works because inheriting RpcTarget is just a marker that\n    //   opts into serializing by creating a stub pointing at the object -- we can create a stub\n    //   pointing at the proxy instead. For any other type, serializing a Proxy probably isn't\n    //   possible, since the serialization wouldn't actually capture the Proxy logic? But I'm\n    //   not 100% certain of that. If we find other use cases in the future it may turn out that\n    //   they call for a different design.\n    virtual void serializeProxy(\n        jsg::Lock& js, jsg::Serializer& serializer, v8::Local<v8::Proxy> proxy);\n  };\n\n  struct Options {\n    // When set, overrides the default wire format version with the one provided.\n    kj::Maybe<uint32_t> version;\n    // When set to true, the serialization header is not written to the output buffer.\n    bool omitHeader = false;\n\n    // The structured clone spec states that instances of classes are serialized as if they were\n    // plain objects: their \"own\" properties are serialized, but the prototype is completely\n    // ignored. Upon deserialization, the value is no longer class instance, it's just a plain\n    // object. This is probably not useful behavior in any real use case, but that's what the spec\n    // says.\n    //\n    // If this flag is true, we'll follow the spec. If it's false, then instances of classes\n    // (i.e. objects which have a prototype which is not Object.prototype) will not be serializable\n    // (they throw DataCloneError).\n    //\n    // TODO(someday): Perhaps we could create a framework for application-defined classes to define\n    //   their own serializers. However, we would need to be extremely careful about this when\n    //   deserializing data from a possibly-malicious source. Such serialization frameworks have\n    //   a history of creating security bugs as people declare various classes serializable without\n    //   fully thinking through what an attacker could do by sending them an instance of that class\n    //   when it isn't expected. Probably, we just shouldn't support this over RPC at all. For DO\n    //   storage, it could be OK since the application would only be deserializing objects it wrote\n    //   itself, but it may not be worth it to support for only that use case.\n    bool treatClassInstancesAsPlainObjects = true;\n\n    // ExternalHandler, if any. Typically this would be allocated on the stack just before the\n    // Serializer.\n    kj::Maybe<ExternalHandler&> externalHandler;\n  };\n\n  struct Released {\n    // The serialized data.\n    kj::Array<kj::byte> data;\n\n    // All instances of SharedArrayBuffer seen during serialization. Pass these along to the\n    // deserializer to achieve actual sharing of buffers.\n    kj::Array<std::shared_ptr<v8::BackingStore>> sharedArrayBuffers;\n\n    // All ArrayBuffers that were passed to `transfer()`.\n    kj::Array<std::shared_ptr<v8::BackingStore>> transferredArrayBuffers;\n  };\n\n  explicit Serializer(Lock& js): Serializer(js, {}) {}\n  explicit Serializer(Lock& js, Options options);\n  inline ~Serializer() noexcept(true) {}  // noexcept(true) because Delegate's is noexcept\n\n  KJ_DISALLOW_COPY_AND_MOVE(Serializer);\n\n  kj::Maybe<ExternalHandler&> getExternalHandler() {\n    return externalHandler;\n  }\n\n  // Write a value.\n  //\n  // You can call this multiple times to write multiple values, then call `readValue()` the same\n  // number of times on the deserialization side.\n  void write(Lock& js, const JsValue& value);\n\n  // Implements the `transfer` option of `structuredClone()`. Pass each item in the transfer array\n  // to this method before calling `write()`. This gives the Serializer permission to serialize\n  // these values by detaching them (destroying the caller's handle) rather than make a copy. The\n  // detached content will show up as part of `Released`, where it should then be delivered to the\n  // Deserializer later.\n  void transfer(Lock& js, const JsValue& value);\n\n  Released release();\n\n  void writeRawUint32(uint32_t i) {\n    ser.WriteUint32(i);\n  }\n  void writeRawUint64(uint64_t i) {\n    ser.WriteUint64(i);\n  }\n\n  void writeRawBytes(kj::ArrayPtr<const kj::byte> bytes) {\n    ser.WriteRawBytes(bytes.begin(), bytes.size());\n  }\n\n  // Write a size followed by bytes.\n  void writeLengthDelimited(kj::ArrayPtr<const kj::byte> bytes) {\n    writeRawUint32(bytes.size());\n    writeRawBytes(bytes);\n  }\n  void writeLengthDelimited(kj::StringPtr text) {\n    writeLengthDelimited(text.asBytes());\n  }\n\n private:\n  // Throw a DataCloneError, complaining that the given object cannot be serialized. (This is\n  // similar to ThrowDataCloneError() except that it formats the error message itself, and it\n  // is expected to be called from KJ-ish code so it throws JsExceptionThrown rather than\n  // returning.)\n  [[noreturn]] void throwDataCloneErrorForObject(jsg::Lock& js, v8::Local<v8::Object> obj);\n\n  // v8::ValueSerializer::Delegate implementation\n  void ThrowDataCloneError(v8::Local<v8::String> message) override;\n  bool HasCustomHostObject(v8::Isolate* isolate) override;\n  v8::Maybe<bool> IsHostObject(v8::Isolate* isolate, v8::Local<v8::Object> object) override;\n  v8::Maybe<bool> WriteHostObject(v8::Isolate* isolate, v8::Local<v8::Object> object) override;\n\n  v8::Maybe<uint32_t> GetSharedArrayBufferId(\n      v8::Isolate* isolate, v8::Local<v8::SharedArrayBuffer> sab) override;\n\n  kj::Maybe<ExternalHandler&> externalHandler;\n\n  kj::Vector<jsg::JsRef<JsValue>> sharedArrayBuffers;\n  kj::Vector<jsg::JsRef<JsValue>> arrayBuffers;\n  kj::Vector<std::shared_ptr<v8::BackingStore>> sharedBackingStores;\n  kj::Vector<std::shared_ptr<v8::BackingStore>> backingStores;\n  bool released = false;\n  bool treatClassInstancesAsPlainObjects;\n  bool treatErrorsAsHostObjects = false;\n\n  // Initialized to point at the prototype of `Object` if and only if\n  // `treatClassInstancesAsPlainObjects` is false (in which case we will need to check against this\n  // prototype in IsHostObject()).\n  v8::Local<v8::Value> prototypeOfObject;\n\n  // The actual ValueSerializer. Note that it's important to define this last because its\n  // constructor will call back to the Delegate, which is this object, so we hope that this object\n  // is fully initialized before that point!\n  v8::ValueSerializer ser;\n};\n\n// Wraps the v8::ValueDeserializer and v8::ValueDeserializer::Delegate implementation.\n// Must be allocated on the stack, and requires that a v8::HandleScope exist in\n// the stack.\nclass Deserializer final: v8::ValueDeserializer::Delegate {\n public:\n  // Exactly like Serializer::ExternalHandler, but for Deserializer.\n  class ExternalHandler {\n   public:\n    virtual ~ExternalHandler() noexcept(false) = 0;\n  };\n\n  struct Options {\n    kj::Maybe<uint32_t> version;\n    bool readHeader = true;\n\n    // When the enahnced error serialization feature is enabled, and we are deserializing\n    // a serialized error, this option controls whether to include the serialized stack\n    // property in the deserialized error. If false, the stack property is not restored\n    // and will instead be set to the captured stack at the time of deserialization.\n    // This flag has no effect if the enhanced error serialization feature is disabled,\n    // or the values being deserialized are not errors (or do not contain any error objects).\n    bool preserveStackInErrors = true;\n\n    // ExternalHandler, if any. Typically this would be allocated on the stack just before the\n    // Deserializer.\n    kj::Maybe<ExternalHandler&> externalHandler;\n  };\n\n  explicit Deserializer(Lock& js,\n      kj::ArrayPtr<const kj::byte> data,\n      kj::Maybe<kj::ArrayPtr<std::shared_ptr<v8::BackingStore>>> transferredArrayBuffers = kj::none,\n      kj::Maybe<kj::ArrayPtr<std::shared_ptr<v8::BackingStore>>> sharedArrayBuffers = kj::none,\n      kj::Maybe<Options> maybeOptions = kj::none);\n\n  explicit Deserializer(\n      Lock& js, Serializer::Released& released, kj::Maybe<Options> maybeOptions = kj::none);\n\n  ~Deserializer() noexcept(true) {}  // noexcept(true) because Delegate's is noexcept\n\n  KJ_DISALLOW_COPY_AND_MOVE(Deserializer);\n\n  kj::Maybe<ExternalHandler&> getExternalHandler() {\n    return externalHandler;\n  }\n\n  JsValue readValue(Lock& js);\n\n  uint32_t readRawUint32();\n  uint64_t readRawUint64();\n\n  // Returns a view directly into the original buffer for the number of bytes requested. Always\n  // returns the exact amount; throws if not possible.\n  kj::ArrayPtr<const kj::byte> readRawBytes(size_t size);\n\n  // Reads a size (readRawUint64) followed by that many bytes.\n  kj::ArrayPtr<const kj::byte> readLengthDelimitedBytes();\n\n  // Read a string and make a copy. The copy is necessary since the text is not NUL-terminated on\n  // the wire. If you don't need NUL termination, read bytes and use `.asChars()`.\n  kj::String readRawString(size_t size);\n  kj::String readLengthDelimitedString();\n\n  inline uint32_t getVersion() const {\n    return deser.GetWireFormatVersion();\n  }\n\n private:\n  void init(Lock& js,\n      kj::Maybe<kj::ArrayPtr<std::shared_ptr<v8::BackingStore>>> transferredArrayBuffers = kj::none,\n      kj::Maybe<Options> maybeOptions = kj::none);\n\n  v8::MaybeLocal<v8::SharedArrayBuffer> GetSharedArrayBufferFromId(\n      v8::Isolate* isolate, uint32_t clone_id) override;\n  v8::MaybeLocal<v8::Object> ReadHostObject(v8::Isolate* isolate) override;\n\n  kj::Maybe<ExternalHandler&> externalHandler;\n\n  size_t totalInputSize;\n  v8::ValueDeserializer deser;\n  kj::Maybe<kj::ArrayPtr<std::shared_ptr<v8::BackingStore>>> sharedBackingStores;\n  bool preserveStackInErrors = true;\n};\n\n// Intended for use with v8::ValueSerializer data released into a kj::Array.\nclass SerializedBufferDisposer: public kj::ArrayDisposer {\n protected:\n  void disposeImpl(void* firstElement,\n      size_t elementSize,\n      size_t elementCount,\n      size_t capacity,\n      void (*destroyElement)(void*)) const override;\n};\nconstexpr SerializedBufferDisposer SERIALIZED_BUFFER_DISPOSER;\n\nJsValue structuredClone(\n    Lock& js, const JsValue& value, kj::Maybe<kj::Array<JsValue>> maybeTransfer = kj::none);\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/setup-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"jsg-test.h\"\n\nnamespace workerd::jsg::test {\nnamespace {\n\nV8System v8System;\n\nstruct EvalContext: public Object, public ContextGlobal {\n  JSG_RESOURCE_TYPE(EvalContext) {}\n};\nJSG_DECLARE_ISOLATE_TYPE(EvalIsolate, EvalContext);\n\nKJ_TEST(\"eval() is blocked\") {\n  Evaluator<EvalContext, EvalIsolate> e(v8System);\n  e.expectEval(\"eval('123')\", \"throws\",\n      \"EvalError: Code generation from strings disallowed for this context\");\n  e.expectEval(\"new Function('a', 'b', 'return a + b;')(123, 321)\", \"throws\",\n      \"EvalError: Code generation from strings disallowed for this context\");\n\n  // eval() with no args or a non-string arg is allowed even when eval is blocked\n  // (V8 returns the value as-is per spec since there is no string to compile).\n  e.expectEval(\"eval()\", \"undefined\", \"undefined\");\n  e.expectEval(\"eval(undefined)\", \"undefined\", \"undefined\");\n\n  // new Function() with no arguments is allowed even when eval is blocked (the\n  // synthesized source matches the known empty-body, no-parameter pattern).\n  e.expectEval(\"typeof new Function()\", \"string\", \"function\");\n\n  // new Function() with params and an undefined body is blocked (the body becomes\n  // the string \"undefined\" via ToString, producing a non-empty source).\n  e.expectEval(\"new Function('a', 'b', undefined)\", \"throws\",\n      \"EvalError: Code generation from strings disallowed for this context\");\n\n  // Extending Function with super() and no arguments is also allowed.\n  e.expectEval(\"class Foo extends Function { constructor() { super(); } }; typeof new Foo()\",\n      \"string\", \"function\");\n\n  e.getIsolate().runInLockScope([&](EvalIsolate::Lock& lock) { lock.setAllowEval(true); });\n\n  e.expectEval(\"eval('123')\", \"number\", \"123\");\n  e.expectEval(\"new Function('a', 'b', 'return a + b;')(123, 321)\", \"number\", \"444\");\n\n  e.getIsolate().runInLockScope([&](EvalIsolate::Lock& lock) { lock.setAllowEval(false); });\n\n  e.expectEval(\"eval('123')\", \"throws\",\n      \"EvalError: Code generation from strings disallowed for this context\");\n  e.expectEval(\"new Function('a', 'b', 'return a + b;')(123, 321)\", \"throws\",\n      \"EvalError: Code generation from strings disallowed for this context\");\n\n  // Note: It would be nice to test as well that WebAssembly is blocked, but that requires\n  //   setting up an event loop since the WebAssembly calls are all async. We'll test this\n  //   elsewhere.\n}\n\n// ========================================================================================\n\nstruct ConfigContext: public Object, public ContextGlobal {\n  struct Nested: public Object {\n    JSG_RESOURCE_TYPE(Nested, int configuration) {\n      KJ_EXPECT(configuration == 123, configuration);\n    }\n  };\n  struct OtherNested: public Object {\n    JSG_RESOURCE_TYPE(OtherNested) {}\n  };\n\n  JSG_RESOURCE_TYPE(ConfigContext) {\n    JSG_NESTED_TYPE(Nested);\n    JSG_NESTED_TYPE(OtherNested);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(\n    ConfigIsolate, ConfigContext, ConfigContext::Nested, ConfigContext::OtherNested);\n\nKJ_TEST(\"configuration values reach nested type declarations\") {\n  {\n    ConfigIsolate isolate(\n        v8System, v8::IsolateGroup::GetDefault(), 123, kj::heap<IsolateObserver>());\n    isolate.runInLockScope([&](ConfigIsolate::Lock& lock) {\n      jsg::Lock& js = lock;\n      js.withinHandleScope([&] { lock.newContext<ConfigContext>().getHandle(lock); });\n    });\n  }\n  {\n    KJ_EXPECT_LOG(ERROR, \"failed: expected configuration == 123\");\n    ConfigIsolate isolate(\n        v8System, v8::IsolateGroup::GetDefault(), 456, kj::heap<IsolateObserver>());\n    isolate.runInLockScope([&](ConfigIsolate::Lock& lock) {\n      jsg::Lock& js = lock;\n      js.withinHandleScope([&] { lock.newContext<ConfigContext>().getHandle(lock); });\n    });\n  }\n}\n\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/setup.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#if __APPLE__\n// We need to define `_XOPEN_SOURCE` to get `ucontext_t` on Mac.\n#define _XOPEN_SOURCE\n#endif\n\n#include \"setup.h\"\n\n#include \"libplatform/libplatform.h\"\n\n#include <v8-cppgc.h>\n#include <v8-initialization.h>\n\n#if !_WIN32\n#include <cxxabi.h>\n#include <ucontext.h>\n#endif\n\n#ifdef WORKERD_ICU_DATA_EMBED\n#include \"icu-data-file.embed.h\"\n\n#include <unicode/udata.h>\n#endif\n\n#if defined(__APPLE__) && defined(__aarch64__)\n#include <mach/mach.h>\n#endif\n\nnamespace workerd::jsg {\n\nstatic bool v8Initialized = false;\nstatic V8System::FatalErrorCallback* v8FatalErrorCallback = nullptr;\nstatic void reportV8FatalError(kj::StringPtr location, kj::StringPtr message) {\n  if (v8FatalErrorCallback == nullptr) {\n    KJ_LOG(FATAL, \"V8 fatal error\", location, message);\n    abort();\n  } else {\n    v8FatalErrorCallback(location, message);\n  }\n}\nstatic void v8DcheckError(const char* file, int line, const char* message) {\n  reportV8FatalError(kj::str(file, ':', line), message);\n}\n\nclass PlatformDisposer final: public kj::Disposer {\n public:\n  virtual void disposeImpl(void* pointer) const override {\n    delete static_cast<v8::Platform*>(pointer);\n  }\n\n  static const PlatformDisposer instance;\n};\n\nconst PlatformDisposer PlatformDisposer::instance{};\n\nkj::Own<v8::Platform> defaultPlatform(uint backgroundThreadCount) {\n  return kj::Own<v8::Platform>(\n      v8::platform::NewDefaultPlatform(backgroundThreadCount,  // default thread pool size\n          v8::platform::IdleTaskSupport::kDisabled,            // TODO(perf): investigate enabling\n          v8::platform::InProcessStackDumping::kDisabled,      // KJ's stack traces are better\n          nullptr)                                             // default TracingController\n          .release(),\n      PlatformDisposer::instance);\n}\n\nstatic kj::Own<v8::Platform> userPlatform(v8::Platform& platform) {\n  // Make a fake kj::Own that wraps a user-specified platform reference. V8's default platform can\n  // only be created with manual memory management, so V8System::Platform needs to be able to store\n  // a smart pointer. However, requiring user platforms to come in via kj::Owns feels unnatural.\n  return kj::Own<v8::Platform>(&platform, kj::NullDisposer::instance);\n}\n\nV8System::V8System(kj::ArrayPtr<const kj::StringPtr> flags) {\n  auto platform = defaultPlatform(0);\n  auto defaultPlatformPtr = platform.get();\n  init(kj::mv(platform), flags, [defaultPlatformPtr](v8::Isolate* isolate) {\n    return v8::platform::PumpMessageLoop(\n        defaultPlatformPtr, isolate, v8::platform::MessageLoopBehavior::kDoNotWait);\n  }, [defaultPlatformPtr](v8::Isolate* isolate) {\n    v8::platform::NotifyIsolateShutdown(defaultPlatformPtr, isolate);\n  });\n}\n\nV8System::V8System(v8::Platform& platformParam,\n    kj::ArrayPtr<const kj::StringPtr> flags,\n    v8::Platform* defaultPlatformPtr) {\n  KJ_REQUIRE_NONNULL(defaultPlatformPtr);\n  init(userPlatform(platformParam), flags, [defaultPlatformPtr](v8::Isolate* isolate) {\n    return v8::platform::PumpMessageLoop(\n        defaultPlatformPtr, isolate, v8::platform::MessageLoopBehavior::kDoNotWait);\n  }, [defaultPlatformPtr](v8::Isolate* isolate) {\n    v8::platform::NotifyIsolateShutdown(defaultPlatformPtr, isolate);\n  });\n}\n\nV8System::V8System(v8::Platform& platformParam,\n    kj::ArrayPtr<const kj::StringPtr> flags,\n    PumpMsgLoopType pumpMsgLoopFn,\n    ShutdownIsolateType shutdownIsolateFn) {\n  init(userPlatform(platformParam), flags, kj::mv(pumpMsgLoopFn), kj::mv(shutdownIsolateFn));\n}\n\nvoid V8System::init(kj::Own<v8::Platform> platformParam,\n    kj::ArrayPtr<const kj::StringPtr> flags,\n    PumpMsgLoopType pumpMsgLoopFn,\n    ShutdownIsolateType shutdownIsolateFn) {\n  platformInner = kj::mv(platformParam);\n  platformWrapper = kj::heap<V8PlatformWrapper>(*platformInner);\n  pumpMsgLoop = kj::mv(pumpMsgLoopFn);\n  shutdownIsolate = kj::mv(shutdownIsolateFn);\n\n#if V8_HAS_STACK_START_MARKER\n  v8::StackStartMarker::EnableForProcess();\n#endif\n\n  v8::V8::SetDcheckErrorHandler(&v8DcheckError);\n  v8::V8::SetFatalErrorHandler(&v8DcheckError);\n\n  // Note that v8::V8::SetFlagsFromString() simply ignores flags it doesn't recognize, which means\n  // typos don't generate any error. SetFlagsFromCommandLine() has the `remove_flags` option which\n  // leaves behind the flags V8 didn't recognize, so we'd like to use that for error checking\n  // purposes. Unfortunately, the interface is rather awkward, since it assumes you're going to\n  // run it on the raw argv array.\n  //\n  // Especially annoying is that V8 expects an array of `char*` -- not `const`. It won't actually\n  // modify the strings, so we'll just const_cast them here...\n  int argc = flags.size() + 1;\n  KJ_STACK_ARRAY(char*, argv, flags.size() + 2, 32, 32);\n  argv[0] = const_cast<char*>(\"fake-binary-name\");\n  for (auto i: kj::zeroTo(flags.size())) {\n    argv[i + 1] = const_cast<char*>(flags[i].cStr());\n  }\n  argv[argc] = nullptr;  // V8 probably doesn't need this but technically argv is NULL-terminated.\n\n  v8::V8::SetFlagsFromCommandLine(&argc, argv.begin(), true);\n\n  KJ_REQUIRE(argc == 1, \"unrecognized V8 flag\", argv[1]);\n\n  // At present, we're not confident the JSG GC integration works with incremental marking. We have\n  // seen bugs in the past that were fixed by adding this flag, although that was a long time ago\n  // and the code has changed a lot since then. Since Worker heaps are generally relatively small\n  // (limited to 128MB in Cloudflare Workers), incremental marking is probably not a win anyway,\n  // and can be disabled. If we want to support significantly larger heaps, we may want to revisit\n  // this. We'll want to do some stress testing first, and fix any bugs seen.\n  //\n  // (It turns out you can call v8::V8::SetFlagsFromString() as many times as you want to add\n  // more flags.)\n  v8::V8::SetFlagsFromString(\"--noincremental-marking\");\n\n  // These features are completed and enabled by default in Chrome, but not\n  // in V8. Follows Node.js: https://github.com/nodejs/node/pull/58154\n  v8::V8::SetFlagsFromString(\"--js-explicit-resource-management\");\n  v8::V8::SetFlagsFromString(\"--js-float16array\");\n\n  // Enable source phase imports for WebAssembly modules\n  v8::V8::SetFlagsFromString(\"--js-source-phase-imports\");\n\n#ifdef __APPLE__\n  // On macOS arm64, we find that V8 can be collecting pages that contain compiled code when\n  // handling requests in short succession. There are some specific differences for macOS arm64\n  // that may be a factor:\n  //   https://chromium.googlesource.com/v8/v8.git/+/refs/tags/11.5.150.4/src/heap/heap.h#2523\n  //\n  // Bugs attributable to this are https://github.com/cloudflare/workers-sdk/issues/2386 and\n  // CUSTESC-29094.\n  v8::V8::SetFlagsFromString(\"--single-threaded-gc\");\n#endif  // __APPLE__\n\n  if (isPredictableModeForTest()) {\n    v8::V8::SetFlagsFromString(\"--expose-gc\");\n  }\n\n#ifdef WORKERD_ICU_DATA_EMBED\n  // V8's bazel build files currently don't support the option to embed ICU data, so we do it\n  // ourselves. `ICU_DATA_FILE`, if defined, will refer to a `kj::ArrayPtr<const byte>` containing\n  // the data.\n  UErrorCode err = U_ZERO_ERROR;\n  udata_setCommonData(ICU_DATA_FILE.begin(), &err);\n  udata_setFileAccess(UDATA_ONLY_PACKAGES, &err);\n  KJ_ASSERT(err == U_ZERO_ERROR);\n#else\n  // We instruct V8 to compile in this data file, so passing nullptr should work here. If V8 is\n  // built incorrectly, this will crash.\n  v8::V8::InitializeICUDefaultLocation(nullptr);\n#endif\n\n  v8::V8::InitializePlatform(platformWrapper.get());\n\n  // A recent change in v8 initializes cppgc in V8::Initialize if it's not already initialized\n  // Hence the ordering here is important\n  cppgc::InitializeProcess(platformWrapper->GetPageAllocator());\n\n  v8::V8::Initialize();\n  v8Initialized = true;\n}\n\nV8System::~V8System() noexcept(false) {\n  v8::V8::Dispose();\n  v8::V8::DisposePlatform();\n  cppgc::ShutdownProcess();\n}\n\nvoid V8System::setFatalErrorCallback(FatalErrorCallback* callback) {\n  v8FatalErrorCallback = callback;\n}\n\nIsolateBase& IsolateBase::from(v8::Isolate* isolate) {\n  return *static_cast<IsolateBase*>(isolate->GetData(SET_DATA_ISOLATE_BASE));\n}\n\nvoid IsolateBase::buildEmbedderGraph(v8::Isolate* isolate, v8::EmbedderGraph* graph, void* data) {\n  try {\n    const auto base = static_cast<IsolateBase*>(data);\n    MemoryTracker tracker(isolate, graph);\n    tracker.track(base);\n  } catch (...) {\n    // Generating the heap snapshot should be a safe process that does not\n    // throw any exceptions. We'll treat any exception here as fatal, including\n    // JsExceptionThrown. Note that we're not entered into any particular v8::Context\n    // here so pulling out the details of the exception would be tricky anyway.\n    kj::throwFatalException(kj::getCaughtExceptionAsKj());\n  }\n}\n\nvoid IsolateBase::jsgGetMemoryInfo(MemoryTracker& tracker) const {\n  tracker.trackField(\"heapTracer\", heapTracer);\n}\n\nvoid IsolateBase::deferDestruction(Item item) {\n  KJ_REQUIRE_NONNULL(ptr, \"tried to defer destruction after V8 isolate was destroyed\");\n  KJ_REQUIRE(queueState == QueueState::ACTIVE, \"tried to defer destruction during isolate shutdown\",\n      queueState);\n  queue.lockExclusive()->push(kj::mv(item));\n}\n\nkj::Arc<const ExternalMemoryTarget> IsolateBase::getExternalMemoryTarget() {\n  return externalMemoryTarget.addRef();\n}\n\nvoid IsolateBase::terminateExecution() const {\n  ptr->TerminateExecution();\n}\n\nvoid IsolateBase::applyDeferredActions() {\n  // Clear the deferred destruction queue.\n  {\n    // Safe to destroy the popped batch outside of the lock because the lock is only actually used\n    // to guard the push buffer.\n    DISALLOW_KJ_IO_DESTRUCTORS_SCOPE;\n    auto drop = queue.lockExclusive()->pop();\n  }\n\n  externalMemoryTarget->applyDeferredMemoryUpdate();\n}\n\nHeapTracer::HeapTracer(v8::Isolate* isolate)\n    // Historically V8 would call IsRoot() to scan references, and then call ResetRoot() on those\n    // where IsRoot() returned false. Currently, V8 allows marking a reference as \"droppable\", and\n    // assumes droppable references are not roots. This way V8 only calls ResetRoot() on droppable\n    // references, and doesn't even call `IsRoot()` on anything else. See comment about droppable\n    // references in Wrappable::attachWrapper() for details.\n    : isolate(isolate) {\n  isolate->AddGCPrologueCallback(\n      [](v8::Isolate* isolate, v8::GCType type, v8::GCCallbackFlags flags, void* data) {\n    // We can expect that any freelisted shims will be collected during a major GC, because\n    // they are not in use therefore not reachable. We should therefore clear the freelist now,\n    // before the trace starts.\n    //\n    // Note that we cannot simply depend on the destructor of CppgcShim to remove objects from\n    // the freelist, because destructors do not actually run at trace time. They may be deferred\n    // to run some time after the trace is done. If we accidentally reuse a shim during that\n    // time, we'll have a problem as the shim will still be destroyed as it was already\n    // determined to be unreachable.\n    //\n    // We must clear the freelist in the GC prologue, not the epilogue, because when building in\n    // ASAN mode, V8 will poison the objects' memory, so our attempt to clear the freelist after\n    // the fact will trigger a spurious ASAN failure.\n    static_cast<HeapTracer*>(data)->clearFreelistedShims();\n  }, this, v8::GCType::kGCTypeMarkSweepCompact);\n\n  isolate->AddGCEpilogueCallback(\n      [](v8::Isolate* isolate, v8::GCType type, v8::GCCallbackFlags flags, void* data) {\n    auto& self = *static_cast<HeapTracer*>(data);\n    for (Wrappable* wrappable: self.detachLater) {\n      wrappable->detachWrapper(true);\n    }\n    self.detachLater.clear();\n  }, this, v8::GCType::kGCTypeAll);\n}\n\nvoid HeapTracer::destroy() {\n  DISALLOW_KJ_IO_DESTRUCTORS_SCOPE;\n  KJ_DEFER(isolate = nullptr);\n}\n\nHeapTracer& HeapTracer::getTracer(v8::Isolate* isolate) {\n  return IsolateBase::from(isolate).heapTracer;\n}\n\nvoid HeapTracer::ResetRoot(const v8::TracedReference<v8::Value>& handle) {\n  // V8 calls this to tell us when our wrapper can be dropped. See comment about droppable\n  // references in Wrappable::attachWrapper() for details.\n  v8::HandleScope scope(isolate);\n  auto& wrappable = *static_cast<Wrappable*>(\n      handle.As<v8::Object>().Get(isolate)->GetAlignedPointerFromInternalField(\n          Wrappable::WRAPPED_OBJECT_FIELD_INDEX,\n          static_cast<v8::EmbedderDataTypeTag>(Wrappable::WRAPPED_OBJECT_FIELD_INDEX)));\n  // V8 gets angry if we do not EXPLICITLY call `Reset()` on the wrapper. If we merely destroy it\n  // (which is what `detachWrapper()` will do) it is not satisfied, and will come back and try to\n  // visit the reference again, but it will DCHECK-fail on that second attempt because the\n  // reference is in an inconsistent state at that point.\n  KJ_ASSERT_NONNULL(wrappable.wrapper).Reset();\n\n  // We don't want to call `detachWrapper()` now because it may create new handles (specifically,\n  // if the wrappable has strong references, which means that its outgoing references need to be\n  // upgraded to strong).\n  detachLater.add(&wrappable);\n}\n\nbool HeapTracer::TryResetRoot(const v8::TracedReference<v8::Value>& handle) {\n  // This method is potentially called on a separate thread. Our ResetRoot() implementation,\n  // though, only works on the main thread. Return false to request V8 schedule the call for the\n  // main thread later on.\n  return false;\n}\n\nnamespace {\nstd::unique_ptr<v8::CppHeap> newCppHeap(V8PlatformWrapper* system) {\n  return jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {\n    v8::CppHeapCreateParams heapParams{{}};\n    heapParams.marking_support = cppgc::Heap::MarkingType::kAtomic;\n    heapParams.sweeping_support = cppgc::Heap::SweepingType::kAtomic;\n    return v8::CppHeap::Create(system, heapParams);\n  });\n}\nstatic v8::Isolate* newIsolate(\n    v8::Isolate::CreateParams&& params, v8::CppHeap* cppHeap, v8::IsolateGroup group) {\n  return jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) -> v8::Isolate* {\n    // We currently don't attempt to support incremental marking or sweeping. We probably could\n    // support them, but it will take some careful investigation and testing. It's not clear if\n    // this would be a win anyway, since Worker heaps are relatively small and therefore doing a\n    // full atomic mark-sweep usually doesn't require much of a pause.\n    //\n    // We probably won't ever support concurrent marking or sweeping because concurrent GC is\n    // only expected to be a win if there are idle CPU cores available. Workers normally run on\n    // servers that are handling many requests at once, thus it's expected CPU cores will be\n    // fully utilized. This differs from browser environments, where a user is typically doing\n    // only one thing at a time and thus likely has CPU cores to spare.\n\n    // V8 takes ownership of the v8::CppHeap.\n    params.cpp_heap = cppHeap;\n\n    if (params.array_buffer_allocator == nullptr &&\n        params.array_buffer_allocator_shared == nullptr) {\n#ifdef V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES\n      params.array_buffer_allocator_shared = std::shared_ptr<v8::ArrayBuffer::Allocator>(\n          v8::ArrayBuffer::Allocator::NewDefaultAllocator(group));\n#else\n      params.array_buffer_allocator_shared = std::shared_ptr<v8::ArrayBuffer::Allocator>(\n          v8::ArrayBuffer::Allocator::NewDefaultAllocator());\n#endif\n    }\n    return v8::Isolate::New(group, params);\n  });\n}\n}  // namespace\nIsolateBase::IsolateBase(V8System& system,\n    v8::Isolate::CreateParams&& createParams,\n    kj::Own<IsolateObserver> observer,\n    kj::Own<ExternalStringAllocator> externalStringAllocator,\n    v8::IsolateGroup group)\n    : v8System(system),\n      cppHeap(newCppHeap(const_cast<V8PlatformWrapper*>(system.platformWrapper.get()))),\n      ptr(newIsolate(kj::mv(createParams), cppHeap.release(), group)),\n      externalMemoryTarget(kj::arc<ExternalMemoryTarget>(ptr)),\n      envAsyncContextKey(kj::refcounted<AsyncContextFrame::StorageKey>()),\n      exportsAsyncContextKey(kj::refcounted<AsyncContextFrame::StorageKey>()),\n      heapTracer(ptr),\n      observer(kj::mv(observer)),\n      externalStringAllocator(kj::mv(externalStringAllocator)) {\n  jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {\n    ptr->SetEmbedderRootsHandler(&heapTracer);\n\n    ptr->SetFatalErrorHandler(&fatalError);\n    ptr->SetOOMErrorHandler(&oomError);\n    // We also set the global OOM error handler.  This is a bit of\n    // a hack: Later in the run the allocation of a sandbox may fail\n    // due to OOM.  In that case we want our handler to be called\n    // even though there is no current isolate.\n    v8::V8::SetFatalMemoryErrorCallback(&oomError);\n\n    ptr->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);\n    ptr->SetData(SET_DATA_ISOLATE_BASE, this);\n\n    ptr->SetModifyCodeGenerationFromStringsCallback(&modifyCodeGenCallback);\n    ptr->SetAllowWasmCodeGenerationCallback(&allowWasmCallback);\n\n    // We don't support SharedArrayBuffer so Atomics.wait() doesn't make sense, and might allow DoS\n    // attacks.\n    ptr->SetAllowAtomicsWait(false);\n\n    ptr->SetJitCodeEventHandler(v8::kJitCodeEventDefault, &jitCodeEvent);\n\n    // V8 10.5 introduced this API which is used to resolve the promise returned by\n    // WebAssembly.compile(). For some reason, the default implementation of the callback does not\n    // work -- the promise is never resolved. The only thing the default version does differently\n    // is it creates a `MicrotasksScope` with `kDoNotRunMicrotasks`. I do not understand what that\n    // is even supposed to do, but it seems related to `MicrotasksPolicy::kScoped`, which we don't\n    // use, we use `kExplicit`. Replacing the callback seems to solve the problem?\n    ptr->SetWasmAsyncResolvePromiseCallback(\n        [](v8::Isolate* isolate, v8::Local<v8::Context> context,\n            v8::Local<v8::Promise::Resolver> resolver, v8::Local<v8::Value> result,\n            v8::WasmAsyncSuccess success) {\n      switch (success) {\n        case v8::WasmAsyncSuccess::kSuccess:\n          resolver->Resolve(context, result).FromJust();\n          break;\n        case v8::WasmAsyncSuccess::kFail:\n          resolver->Reject(context, result).FromJust();\n          break;\n      }\n    });\n\n    ptr->GetHeapProfiler()->AddBuildEmbedderGraphCallback(buildEmbedderGraph, this);\n\n    {\n      // We don't need a v8::Locker here since there's no way another thread could be using the\n      // isolate yet, but we do need v8::Isolate::Scope.\n      v8::Isolate::Scope isolateScope(ptr);\n      v8::HandleScope scope(ptr);\n\n      // Create opaqueTemplate\n      auto opaqueTemplate = v8::FunctionTemplate::New(ptr, &throwIllegalConstructor);\n      opaqueTemplate->InstanceTemplate()->SetInternalFieldCount(Wrappable::INTERNAL_FIELD_COUNT);\n      this->opaqueTemplate.Reset(ptr, opaqueTemplate);\n    }\n  });\n}\n\nIsolateBase::~IsolateBase() noexcept(false) {\n  // Ensure objects that outlive the isolate won't attempt to modify external memory\n  // on the now-destroyed isolate.\n  externalMemoryTarget->detach();\n\n  jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {\n    // Terminate the v8::platform's task queue associated with this isolate\n    v8System.shutdownIsolate(ptr);\n    ptr->Dispose();\n    ptr = nullptr;\n    // TODO(cleanup): meaningless after V8 13.4 is released.\n    cppHeap.reset();\n  });\n}\n\nv8::Local<v8::FunctionTemplate> IsolateBase::getOpaqueTemplate(v8::Isolate* isolate) {\n  return static_cast<IsolateBase*>(isolate->GetData(SET_DATA_ISOLATE_BASE))\n      ->opaqueTemplate.Get(isolate);\n}\n\nvoid IsolateBase::dropWrappers(kj::FunctionParam<void()> drop) {\n  KJ_REQUIRE(queueState == QueueState::ACTIVE);\n  queueState = QueueState::DROPPING;\n  // Delete all wrappers.\n  jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {\n    v8::Locker lock(ptr);\n    v8::Isolate::Scope isolateScope(ptr);\n\n    // Make sure everything in the deferred destruction queue is dropped.\n    applyDeferredActions();\n\n    // We MUST call heapTracer.destroy(), but we can't do it yet because destroying other handles\n    // may call into the heap tracer.\n    KJ_DEFER(heapTracer.destroy());\n\n    // Make sure v8::Globals are destroyed under lock (but not until later).\n    KJ_DEFER(opaqueTemplate.Reset());\n    KJ_DEFER(workerEnvObj.Reset());\n    KJ_DEFER(workerExportsObj.Reset());\n\n    // Make sure the TypeWrapper is destroyed under lock by declaring a new copy of the variable\n    // that is destroyed before the lock is released.\n    drop();\n\n    // Destroy all wrappers.\n    heapTracer.clearWrappers();\n    queueState = QueueState::DROPPED;\n  });\n}\n\nvoid IsolateBase::fatalError(const char* location, const char* message) {\n  reportV8FatalError(location, message);\n}\nvoid IsolateBase::oomError(const char* location, const v8::OOMDetails& oom) {\n  kj::StringPtr detailPrefix, detail;\n  if (oom.detail != nullptr) {\n    detailPrefix = \"; detail: \"_kj;\n    detail = oom.detail;\n  }\n  auto message = kj::str(oom.is_heap_oom ? \": allocation failed: JavaScript heap out of memory\"_kj\n                                         : \": allocation failed: process out of memory\"_kj,\n      detailPrefix, detail);\n  reportV8FatalError(location, message);\n}\n\nv8::ModifyCodeGenerationFromStringsResult IsolateBase::modifyCodeGenCallback(\n    v8::Local<v8::Context> context, v8::Local<v8::Value> source, bool isCodeLike) {\n  // For undefined sources (e.g. eval() with no argument or eval(undefined)),\n  // there is no code generation from strings. V8 returns undefined as-is per spec.\n  // We allow it through without further checks.\n  // Note: Wasm compilation uses a separate callback (AllowWasmCodeGenerationCallback).\n  if (source->IsUndefined()) {\n    return {.codegen_allowed = true, .modified_source = {}};\n  }\n\n  // Allow empty-body, no-parameter Function constructor calls: `new Function()`, or\n  // `class Foo extends Function { constructor() { super(); } }`.\n  //\n  // V8 synthesizes the full source string before calling this callback (see\n  // CreateDynamicFunction in v8/src/builtins/builtins-function.cc). When called with\n  // no arguments (argc == 0), isCodeLike is true and the source is the exact string\n  // below — which contains no user-provided code.\n  //\n  // Security notes:\n  //   - isCodeLike is false on the eval() path, so this check cannot be reached via\n  //     eval(). An attacker cannot use eval(\"<evil> {\\n\\n}\") to bypass this.\n  //   - isCodeLike is also false when any arguments are plain strings (not CodeLike\n  //     objects), so new Function('a', 'b', undefined) cannot reach this check either.\n  //   - The exact string match ensures no user content (parameters or body) is present.\n  //   - We intentionally only match the no-parameter, no-body case. Calls like\n  //     new Function('a', 'b') are always blocked since the last argument becomes the\n  //     body via ToString(), producing a non-matching source string.\n  //\n  // NOTE: This pattern is tied to V8's CreateDynamicFunction format in\n  // builtins-function.cc:46-71 and must be reviewed during V8 updates. If the format\n  // changes, the setup-test and worker-test will fail, signaling that this constant\n  // needs updating.\n  static constexpr auto kEmptyFunctionSource = \"(function anonymous(\\n) {\\n\\n})\"_kj;\n  if (isCodeLike && source->IsString() &&\n      kj::str(source.As<v8::String>()) == kEmptyFunctionSource) {\n    return {.codegen_allowed = true, .modified_source = {}};\n  }\n\n  v8::Isolate* isolate = v8::Isolate::GetCurrent();\n  auto& base = IsolateBase::from(isolate);\n  if (base.evalAllowed) {\n    // If eval is allowed, notify the observer so that it can take any action necessary.\n    // Once possible action is logging the source to be evaluated for auditing purposes.\n    // TODO(cleanup): Consider making it so that `onDynamicEval()` returns true or false\n    // depending on whether eval should be allowed or not.\n    base.observer->onDynamicEval(context, source, isCodeLike ? IsCodeLike::YES : IsCodeLike::NO);\n  }\n\n  return {.codegen_allowed = base.evalAllowed, .modified_source = {}};\n}\n\nbool IsolateBase::allowWasmCallback(v8::Local<v8::Context> context, v8::Local<v8::String> source) {\n  // Don't allow WASM unless arbitrary eval() is allowed.\n  IsolateBase* self =\n      static_cast<IsolateBase*>(v8::Isolate::GetCurrent()->GetData(SET_DATA_ISOLATE_BASE));\n  return self->evalAllowed;\n}\n\nvoid IsolateBase::jitCodeEvent(const v8::JitCodeEvent* event) noexcept {\n  // We register this callback with V8 in order to build a mapping of code addresses to source\n  // code locations, which we use when reporting stack traces during crashes.\n\n  IsolateBase* self = static_cast<IsolateBase*>(event->isolate->GetData(SET_DATA_ISOLATE_BASE));\n  auto& codeMap = self->codeMap;\n\n  // Pointer comparison between pointers not from the same array is UB so we'd better operate on\n  // uintptr_t instead.\n  uintptr_t startAddr = reinterpret_cast<uintptr_t>(event->code_start);\n\n  struct UserData {\n    // The type we'll use in JitCodeEvent::user_data...\n\n    kj::Vector<CodeBlockInfo::PositionMapping> mapping;\n  };\n\n  switch (event->type) {\n    case v8::JitCodeEvent::CODE_ADDED: {\n      // Usually CODE_ADDED comes after CODE_END_LINE_INFO_RECORDING, but sometimes it doesn't,\n      // particularly in the case of Wasm where it appears no line info is provided.\n      auto& info = codeMap.findOrCreate(\n          startAddr, [&]() { return decltype(self->codeMap)::Entry{startAddr, CodeBlockInfo()}; });\n      info.size = event->code_len;\n      info.name = kj::str(kj::arrayPtr(event->name.str, event->name.len));\n      info.type = event->code_type;\n      break;\n    }\n\n    case v8::JitCodeEvent::CODE_MOVED:\n      KJ_IF_SOME(entry, codeMap.findEntry(startAddr)) {\n        auto info = kj::mv(entry.value);\n        codeMap.erase(entry);\n        codeMap.upsert(reinterpret_cast<uintptr_t>(event->new_code_start), kj::mv(info),\n            [&](CodeBlockInfo& existing, CodeBlockInfo&& replacement) {\n          // It seems sometimes V8 tells us that it \"moved\" a block to a location that already\n          // existed. Why? Who knows? There's no documentation. Let's do the best we can, which is:\n          // replace the existing with the new values, unless the new values are not initialized.\n          // (E.g. maybe the reason the block already exists is because CODE_ADDED or\n          // CODE_END_LINE_INFO_RECORDING was already delivered to the new location for some\n          // reason...)\n          if (replacement.type != kj::none) {\n            existing.size = replacement.size;\n            existing.type = replacement.type;\n            existing.name = kj::mv(replacement.name);\n          }\n          if (replacement.mapping != nullptr) {\n            existing.mapping = kj::mv(replacement.mapping);\n          }\n        });\n      } else {\n        // TODO(someday): Figure out why this triggers. As of v8 10.3 it actually happens in one\n        //   of our tests. This API is very undocumented, though, so I'm not sure what I should do.\n        //   Change this back to DEBUG_FAIL_PROD_LOG once debugged. (It was reduced to INFO logging\n        //   to avoid bothering users of workerd.)\n        KJ_LOG(INFO, \"CODE_MOVED for unknown code block?\");\n      }\n      break;\n\n    case v8::JitCodeEvent::CODE_REMOVED:\n      if (!codeMap.erase(startAddr)) {\n        DEBUG_FATAL_RELEASE_LOG(ERROR, \"CODE_REMOVED for unknown code block?\");\n      }\n      break;\n\n    case v8::JitCodeEvent::CODE_ADD_LINE_POS_INFO:\n      // V8 reports multiple \"position types\", POSITION and STATEMENT_POSITION. These are intended\n      // to produce two different mappings from instructions to locations. POSITION points to\n      // a specific expression while STATEMENT_POSITION only points to the enclosing statement.\n      // For our purposes, the former is strictly more useful than the latter, so we ignore\n      // STATEMENT_POSITION.\n      if (event->line_info.position_type == v8::JitCodeEvent::POSITION) {\n        UserData* data = static_cast<UserData*>(event->user_data);\n        data->mapping.add(CodeBlockInfo::PositionMapping{\n          static_cast<uint>(event->line_info.offset), static_cast<uint>(event->line_info.pos)});\n      }\n      break;\n\n    case v8::JitCodeEvent::CODE_START_LINE_INFO_RECORDING: {\n      UserData* data = new UserData();\n      data->mapping.reserve(256);\n\n      // Yes we are actually supposed to const_cast the event in order to set the user_data. This\n      // is nuts but it's what other users of this interface inside the V8 codebase actually do.\n      const_cast<v8::JitCodeEvent*>(event)->user_data = data;\n      break;\n    }\n\n    case v8::JitCodeEvent::CODE_END_LINE_INFO_RECORDING: {\n      // Sometimes CODE_END_LINE_INFO_RECORDING comes after CODE_ADDED, in particular with\n      // modules.\n      auto& info = codeMap.findOrCreate(\n          startAddr, [&]() { return decltype(self->codeMap)::Entry{startAddr, CodeBlockInfo()}; });\n\n      UserData* data = static_cast<UserData*>(event->user_data);\n      info.mapping = data->mapping.releaseAsArray();\n      delete data;\n\n      break;\n    }\n  }\n}\n\nvoid* getJsCageBase() {\n  if (!v8Initialized) return nullptr;\n  v8::Isolate* isolate = v8::Isolate::TryGetCurrent();\n  if (isolate == nullptr) return nullptr;\n  // Returns null if setJsCageBase was never called.\n  return isolate->GetData(SET_DATA_CAGE_BASE);\n}\n\nvoid setJsCageBase(void* cageBase) {\n  if (!v8Initialized) return;\n  v8::Isolate* isolate = v8::Isolate::TryGetCurrent();\n  if (isolate == nullptr) return;\n  isolate->SetData(SET_DATA_CAGE_BASE, cageBase);\n}\n\n#if _WIN32\nkj::Maybe<kj::StringPtr> getJsStackTrace(void* ucontext, kj::ArrayPtr<char> scratch) {\n  // This function is only called by the internal build which just targets Linux.\n  // Windows doesn't provide ucontext, so we'd need to rewrite this function's signature\n  // if we were to support it. `v8/src/libsampler/sampler.cc` provides a suitable\n  // implementation we could use.\n  KJ_UNIMPLEMENTED(\"getJsStackTrace() is not implemented on Windows\");\n}\n#else\nkj::Maybe<kj::StringPtr> getJsStackTrace(void* ucontext, kj::ArrayPtr<char> scratch) {\n  if (!v8Initialized) {\n    return kj::none;\n  }\n  v8::Isolate* isolate = v8::Isolate::TryGetCurrent();\n  if (isolate == nullptr) {\n    return kj::none;\n  }\n\n  char* pos = scratch.begin();\n  char* limit = scratch.end() - 1;\n  auto appendText = [&](const auto&... params) {\n    pos = kj::_::fillLimited(pos, limit, kj::toCharSequence(params)...);\n  };\n\n  v8::RegisterState state;\n  auto& mcontext = static_cast<ucontext_t*>(ucontext)->uc_mcontext;\n#if defined(__APPLE__) && defined(__x86_64__)\n  state.pc = reinterpret_cast<void*>(mcontext->__ss.__rip);\n  state.sp = reinterpret_cast<void*>(mcontext->__ss.__rsp);\n  state.fp = reinterpret_cast<void*>(mcontext->__ss.__rbp);\n#elif defined(__APPLE__) && defined(__aarch64__)\n  state.pc = reinterpret_cast<void*>(arm_thread_state64_get_pc(mcontext->__ss));\n  state.sp = reinterpret_cast<void*>(arm_thread_state64_get_sp(mcontext->__ss));\n  state.fp = reinterpret_cast<void*>(arm_thread_state64_get_fp(mcontext->__ss));\n#elif defined(__linux__) && defined(__x86_64__)\n  state.pc = reinterpret_cast<void*>(mcontext.gregs[REG_RIP]);\n  state.sp = reinterpret_cast<void*>(mcontext.gregs[REG_RSP]);\n  state.fp = reinterpret_cast<void*>(mcontext.gregs[REG_RBP]);\n#elif defined(__linux__) && defined(__aarch64__)\n  state.pc = reinterpret_cast<void*>(mcontext.pc);\n  state.sp = reinterpret_cast<void*>(mcontext.sp);\n  state.fp = reinterpret_cast<void*>(mcontext.regs[29]);\n  state.lr = reinterpret_cast<void*>(mcontext.regs[30]);\n#else\n#error \"Please add architecture support. See FillRegisterState() in v8/src/libsampler/sampler.cc\"\n#endif\n\n  v8::SampleInfo sampleInfo;\n  void* traceSpace[32]{};\n  isolate->GetStackSample(state, traceSpace, kj::size(traceSpace), &sampleInfo);\n\n  kj::StringPtr vmState = \"??\";\n  switch (sampleInfo.vm_state) {\n    case v8::StateTag::JS:\n      vmState = \"js\";\n      break;\n    case v8::StateTag::GC:\n      vmState = \"gc\";\n      break;\n    case v8::StateTag::PARSER:\n      vmState = \"parser\";\n      break;\n    case v8::StateTag::BYTECODE_COMPILER:\n      vmState = \"bytecode_compiler\";\n      break;\n    case v8::StateTag::COMPILER:\n      vmState = \"compiler\";\n      break;\n    case v8::StateTag::OTHER:\n      vmState = \"other\";\n      break;\n    case v8::StateTag::EXTERNAL:\n      vmState = \"external\";\n      break;\n    case v8::StateTag::ATOMICS_WAIT:\n      vmState = \"atomics_wait\";\n      break;\n    case v8::StateTag::IDLE:\n      vmState = \"idle\";\n      break;\n    case v8::StateTag::IDLE_EXTERNAL:\n      vmState = \"idle_external\";\n      break;\n    case v8::StateTag::LOGGING:\n      vmState = \"logging\";\n      break;\n  }\n  appendText(\"js: (\", vmState, \")\");\n\n  auto& codeMap = static_cast<IsolateBase*>(isolate->GetData(SET_DATA_ISOLATE_BASE))->codeMap;\n\n  for (auto i: kj::zeroTo(sampleInfo.frames_count)) {\n    uintptr_t addr = reinterpret_cast<uintptr_t>(traceSpace[i]);\n    auto range = codeMap.range(0, addr + 1);\n    bool matched = false;\n    kj::StringPtr prevName = nullptr;\n    if (range.begin() != range.end()) {\n      auto iter = range.end();\n      --iter;\n      auto& entry = *iter;\n      if (entry.key + entry.value.size > addr) {\n        // Yay, a match. Binary search it. We're looking for the first entry that is greater than\n        // the target address (then we'll back up one).\n        uint offset = addr - entry.key;\n        auto& mapping = entry.value.mapping;\n        size_t l = 0;\n        size_t r = mapping.size();\n        while (l < r) {\n          size_t mid = (l + r) / 2;\n          if (mapping[mid].instructionOffset <= offset) {\n            l = mid + 1;\n          } else {\n            r = mid;\n          }\n        }\n\n        matched = true;\n        appendText(' ');\n        if (entry.value.name != prevName) {\n          appendText('\\'', entry.value.name, '\\'');\n          prevName = entry.value.name;\n        }\n        if (l > 0) {\n          appendText('@', mapping[l - 1].sourceOffset);\n        } else {\n          appendText(\"@?\");\n        }\n      }\n    }\n\n    if (!matched) {\n      appendText(\" @?\");\n    }\n  }\n\n  *pos = '\\0';\n  return kj::StringPtr(scratch.begin(), pos - scratch.begin());\n}\n#endif\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/setup.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// Public API for setting up JavaScript context. Only high-level code needs to include this file.\n\n#include \"async-context.h\"\n#include \"jsg.h\"\n#include \"v8-platform-wrapper.h\"\n\n#include <workerd/jsg/observer.h>\n#include <workerd/jsg/util.h>\n#include <workerd/util/batch-queue.h>\n\n#include <v8-profiler.h>\n\n#include <kj/map.h>\n#include <kj/mutex.h>\n#include <kj/vector.h>\n\n#include <typeindex>\n\nnamespace workerd::jsg {\n\nclass Deserializer;\nclass Serializer;\n\n// Construct a default V8 platform, with the given background thread pool size.\n//\n// Passing zero for `backgroundThreadCount` causes V8 to ask glibc how many processors there are.\n// Now, glibc *could* answer this problem easily by calling `sched_getaffinity()`, which would\n// not only tell it how many cores exist, but also how many cores are available to this specific\n// process. But does glibc do that? No, it does not. Instead, it frantically tries to open\n// `/sys/devices/system/cpu/online`, then `/proc/stat`, then `/proc/cpuinfo`, and parses the text\n// it reads from whichever file successfully opens to find out the number of processors. Of course,\n// if you're in a sandbox, that probably won't work. And anyway, you probably don't actually want\n// V8 to consume all available cores with background work. So, please specify a thread pool size.\nkj::Own<v8::Platform> defaultPlatform(uint backgroundThreadCount);\n\n// In order to use any part of the JSG API, you must first construct a V8System. You can only\n// construct one of these per process. This performs process-wide initialization of the V8\n// library.\nclass V8System {\n  using PumpMsgLoopType = kj::Function<bool(v8::Isolate*)>;\n  using ShutdownIsolateType = kj::Function<void(v8::Isolate*)>;\n\n public:\n  // Uses the default v8::Platform implementation, as if by:\n  //   auto v8Platform = jsg::defaultPlatform();\n  //   auto v8System = V8System(*v8Platform, flags);\n  // (Optional) `flags` is a list of command-line flags to pass to V8, like \"--expose-gc\" or\n  // \"--single_threaded_gc\". An exception will be thrown if any flags are not recognized.\n  explicit V8System(kj::ArrayPtr<const kj::StringPtr> flags = nullptr);\n\n  // Use a possibly-custom v8::Platform wrapper over default v8::Platform, and apply flags.\n  explicit V8System(v8::Platform& platform,\n      kj::ArrayPtr<const kj::StringPtr> flags,\n      v8::Platform* defaultPlatformPtr);\n\n  // Use a possibly-custom v8::Platform implementation with custom task queue, and apply flags.\n  explicit V8System(v8::Platform& platform,\n      kj::ArrayPtr<const kj::StringPtr> flags,\n      PumpMsgLoopType,\n      ShutdownIsolateType);\n\n  ~V8System() noexcept(false);\n\n  using FatalErrorCallback = void(kj::StringPtr location, kj::StringPtr message);\n  static void setFatalErrorCallback(FatalErrorCallback* callback);\n\n private:\n  kj::Own<v8::Platform> platformInner;\n  kj::Own<V8PlatformWrapper> platformWrapper;\n  PumpMsgLoopType pumpMsgLoop;\n  ShutdownIsolateType shutdownIsolate;\n  friend class IsolateBase;\n\n  void init(kj::Own<v8::Platform>,\n      kj::ArrayPtr<const kj::StringPtr>,\n      PumpMsgLoopType,\n      ShutdownIsolateType);\n};\n\n// Base class of Isolate<T> containing parts that don't need to be templated, to avoid code\n// bloat.\nclass IsolateBase {\n public:\n  static IsolateBase& from(v8::Isolate* isolate);\n\n  // Unwraps a JavaScript exception as a kj::Exception.\n  virtual kj::Exception unwrapException(\n      Lock& js, v8::Local<v8::Context> context, v8::Local<v8::Value> exception) = 0;\n\n  // Wraps a kj::Exception as a JavaScript Exception.\n  virtual v8::Local<v8::Value> wrapException(\n      Lock& js, v8::Local<v8::Context> context, kj::Exception&& exception) = 0;\n\n  // Used by Serializer/Deserializer implementations, calls into DynamicResourceTypeMap\n  // serializerMap and deserializerMap.\n  virtual bool serialize(\n      Lock& js, std::type_index type, jsg::Object& instance, Serializer& serializer) = 0;\n  virtual kj::Maybe<v8::Local<v8::Object>> deserialize(\n      Lock& js, uint tag, Deserializer& deserializer) = 0;\n\n  // Calls V8's TerminateExecution(), cancelling JavaScript execution. Safe to call across\n  // threads, without holding the lock.\n  void terminateExecution() const;\n\n  // Like terminateExecution(), but also sets a flag that C++ iterator callbacks can check.\n  // V8 builtins like Array.from() loop over C++ callbacks without JS back-edge interrupt\n  // checks, so V8's TerminateExecution() alone would be ignored until the iteration completes.\n  void requestTermination() {\n    terminationRequested = true;\n    terminateExecution();\n  }\n  bool isTerminationRequested() const {\n    return terminationRequested;\n  }\n\n  using Logger = Lock::Logger;\n  inline void setLoggerCallback(kj::Badge<Lock>, kj::Function<Logger>&& logger) {\n    maybeLogger = kj::mv(logger);\n  }\n\n  using ErrorReporter = Lock::ErrorReporter;\n  inline void setErrorReporterCallback(kj::Badge<Lock>, kj::Function<ErrorReporter>&& reporter) {\n    maybeErrorReporter = kj::mv(reporter);\n  }\n\n  using ModuleFallbackCallback = kj::Maybe<kj::OneOf<kj::String, jsg::ModuleRegistry::ModuleInfo>>(\n      jsg::Lock&,\n      kj::StringPtr,\n      kj::Maybe<kj::String>,\n      jsg::CompilationObserver&,\n      jsg::ModuleRegistry::ResolveMethod,\n      kj::Maybe<kj::StringPtr>);\n  inline void setModuleFallbackCallback(kj::Function<ModuleFallbackCallback>&& callback) {\n    maybeModuleFallbackCallback = kj::mv(callback);\n  }\n  inline kj::Maybe<kj::Function<ModuleFallbackCallback>&> tryGetModuleFallback() {\n    KJ_IF_SOME(moduleFallbackCallback, maybeModuleFallbackCallback) {\n      return moduleFallbackCallback;\n    }\n    return kj::none;\n  }\n\n  // Requests an extra microtask checkpoint after the current one completes.\n  inline void requestExtraMicrotaskCheckpoint(kj::Badge<Lock>) {\n    extraMicrotaskCheckpointRequested = true;\n  }\n\n  // Returns true if an extra microtask checkpoint was requested since the last\n  // call, and clears the flag.\n  inline bool takeExtraMicrotaskCheckpointRequested(kj::Badge<Lock>) {\n    bool requested = extraMicrotaskCheckpointRequested;\n    extraMicrotaskCheckpointRequested = false;\n    return requested;\n  }\n\n  inline void setAllowEval(kj::Badge<Lock>, bool allow) {\n    if (alwaysAllowEval) return;\n    evalAllowed = allow;\n  }\n\n  inline void setAllowsAllowEval() {\n    alwaysAllowEval = true;\n    evalAllowed = true;\n  }\n\n  inline void setCaptureThrowsAsRejections(kj::Badge<Lock>, bool capture) {\n    captureThrowsAsRejections = capture;\n  }\n\n  inline void setNodeJsCompatEnabled(kj::Badge<Lock>, bool enabled) {\n    nodeJsCompatEnabled = enabled;\n  }\n\n  inline void setNodeJsProcessV2Enabled(kj::Badge<Lock>, bool enabled) {\n    nodeJsProcessV2Enabled = enabled;\n  }\n\n  inline void setRequireReturnsDefaultExportEnabled(kj::Badge<Lock>, bool enabled) {\n    requireReturnsDefaultExportEnabled = enabled;\n  }\n\n  inline bool areWarningsLogged() const {\n    return maybeLogger != kj::none;\n  }\n  inline bool areErrorsReported() const {\n    return maybeErrorReporter != kj::none;\n  }\n\n  inline bool isNodeJsCompatEnabled() const {\n    return nodeJsCompatEnabled;\n  }\n\n  inline bool isNodeJsProcessV2Enabled() const {\n    return nodeJsProcessV2Enabled;\n  }\n\n  inline bool isRequireReturnsDefaultExportEnabled() const {\n    return requireReturnsDefaultExportEnabled;\n  }\n\n  inline bool shouldSetToStringTag() const {\n    return setToStringTag;\n  }\n\n  void enableSetToStringTag() {\n    setToStringTag = true;\n  }\n\n  inline bool shouldSetImmutablePrototype() const {\n    return shouldSetImmutablePrototypeFlag;\n  }\n\n  void enableSetImmutablePrototype() {\n    shouldSetImmutablePrototypeFlag = true;\n  }\n\n  inline bool shouldUseSpecCompliantPropertyAttributes() const {\n    return specCompliantPropertyAttributesFlag;\n  }\n\n  void enableSpecCompliantPropertyAttributes() {\n    specCompliantPropertyAttributesFlag = true;\n  }\n\n  inline void disableTopLevelAwait() {\n    allowTopLevelAwait = false;\n  }\n\n  inline bool isTopLevelAwaitEnabled() const {\n    return allowTopLevelAwait;\n  }\n\n  // The logger will be optionally set by the isolate setup logic if there is anywhere\n  // for the log to go (for instance, if debug logging is enabled or the inspector is\n  // being used).\n  inline void logWarning(Lock& js, kj::StringPtr message) {\n    KJ_IF_SOME(logger, maybeLogger) {\n      logger(js, message);\n    }\n  }\n\n  inline void reportError(\n      Lock& js, kj::String desc, const JsValue& error, const JsMessage& message) {\n    KJ_IF_SOME(reporter, maybeErrorReporter) {\n      reporter(js, kj::mv(desc), error, message);\n    }\n  }\n\n  IsolateObserver& getObserver() {\n    return *observer;\n  }\n\n  ExternalStringAllocator& getExternalStringAllocator() {\n    return *externalStringAllocator;\n  }\n\n  // Implementation of MemoryRetainer\n  void jsgGetMemoryInfo(MemoryTracker& tracker) const;\n  kj::StringPtr jsgGetMemoryName() const {\n    return \"IsolateBase\"_kjc;\n  }\n  size_t jsgGetMemorySelfSize() const {\n    return sizeof(IsolateBase);\n  }\n  bool jsgGetMemoryInfoIsRootNode() const {\n    return true;\n  }\n\n  // Get an object referencing this isolate that can be used to adjust external memory usage later\n  kj::Arc<const ExternalMemoryTarget> getExternalMemoryTarget();\n\n  // Equivalent to getExternalMemoryTarget()->getAdjustment(amount), but saves an atomic refcount\n  // increment and decrement.\n  ExternalMemoryAdjustment getExternalMemoryAdjustment(int64_t amount) {\n    return externalMemoryTarget->getAdjustment(amount);\n  }\n\n  AsyncContextFrame::StorageKey& getEnvAsyncContextKey() {\n    return *envAsyncContextKey;\n  }\n\n  AsyncContextFrame::StorageKey& getExportsAsyncContextKey() {\n    return *exportsAsyncContextKey;\n  }\n\n  void setUsingNewModuleRegistry() {\n    usingNewModuleRegistry = true;\n  }\n\n  bool isUsingNewModuleRegistry() const {\n    return usingNewModuleRegistry;\n  }\n\n  void setThrowOnUnrecognizedImportAssertion() {\n    throwOnUnrecognizedImportAssertion = true;\n  }\n\n  bool getThrowOnUnrecognizedImportAssertion() const {\n    return throwOnUnrecognizedImportAssertion;\n  }\n\n  void setUsingEnhancedErrorSerialization() {\n    usingEnhancedErrorSerialization = true;\n  }\n\n  bool getUsingEnhancedErrorSerialization() const {\n    return usingEnhancedErrorSerialization;\n  }\n\n  void setUsingFastJsgStruct() {\n    usingFastJsgStruct = true;\n  }\n\n  bool getUsingFastJsgStruct() const {\n    return usingFastJsgStruct;\n  }\n\n  bool pumpMsgLoop() {\n    return v8System.pumpMsgLoop(ptr);\n  }\n\n  // Allows an object to register an that will be dropped when the destroy\n  // queue is drained under the isolate lock.\n  void destroyUnderLock(kj::Own<void> item) {\n    deferDestruction(kj::mv(item));\n  }\n\n  v8::Isolate* getIsolate() const {\n    return ptr;\n  }\n\n private:\n  template <typename TypeWrapper>\n  friend class Isolate;\n\n  static void buildEmbedderGraph(v8::Isolate* isolate, v8::EmbedderGraph* graph, void* data);\n\n  // The internals of a jsg::Ref<T> to be deleted.\n  class RefToDelete {\n   public:\n    RefToDelete(bool strong, kj::Own<void> ownWrappable, Wrappable* wrappable)\n        : strong(strong),\n          ownWrappable(kj::mv(ownWrappable)),\n          wrappable(wrappable) {}\n    ~RefToDelete() noexcept(false) {\n      if (ownWrappable.get() != nullptr && strong) {\n        wrappable->removeStrongRef();\n      }\n    }\n    RefToDelete(RefToDelete&&) = default;\n\n    // Default move ctor okay because ownWrappable.get() will be null if moved-from.\n    KJ_DISALLOW_COPY(RefToDelete);\n\n   private:\n    bool strong;\n    // Keeps the `wrappable` pointer below valid.\n    kj::Own<void> ownWrappable;\n    Wrappable* wrappable;\n  };\n\n  using Item = kj::OneOf<v8::Global<v8::Data>, RefToDelete, kj::Own<void>>;\n\n  V8System& v8System;\n  // TODO(cleanup): After v8 13.4 is fully released we can inline this into `newIsolate`\n  //                and remove this member.\n  std::unique_ptr<class v8::CppHeap> cppHeap;\n  v8::Isolate* ptr;\n  // When true, evalAllowed is true and switching it to false is a no-op.\n  bool alwaysAllowEval = false;\n  bool evalAllowed = false;\n\n  // The Web Platform API specifications require that any API that returns a JavaScript Promise\n  // should never throw errors synchronously. Rather, they are supposed to capture any synchronous\n  // throws and return a rejected Promise. Historically, Workers did not follow that guideline\n  // and there are a number of async APIs that currently throw. When the captureThrowsAsRejections\n  // flag is set, that old behavior is changed to be correct.\n  bool captureThrowsAsRejections = false;\n  bool asyncContextTrackingEnabled = false;\n  bool nodeJsCompatEnabled = false;\n  bool nodeJsProcessV2Enabled = false;\n  bool requireReturnsDefaultExportEnabled = false;\n  bool setToStringTag = false;\n  bool shouldSetImmutablePrototypeFlag = false;\n  bool specCompliantPropertyAttributesFlag = false;\n  bool allowTopLevelAwait = true;\n  bool usingNewModuleRegistry = false;\n  bool usingEnhancedErrorSerialization = false;\n  bool usingFastJsgStruct = false;\n  bool extraMicrotaskCheckpointRequested = false;\n  bool terminationRequested = false;\n\n  // Only used when the original module registry is used.\n  bool throwOnUnrecognizedImportAssertion = false;\n\n  kj::Maybe<kj::Function<Logger>> maybeLogger;\n  kj::Maybe<kj::Function<ErrorReporter>> maybeErrorReporter;\n  kj::Maybe<kj::Function<ModuleFallbackCallback>> maybeModuleFallbackCallback;\n\n  // FunctionTemplate used by Wrappable::attachOpaqueWrapper(). Just a constructor for an empty\n  // object with 2 internal fields.\n  v8::Global<v8::FunctionTemplate> opaqueTemplate;\n\n  // Object used as the underlying storage for a workers environment.\n  v8::Global<v8::Object> workerEnvObj;\n\n  // Object used as the underlying storage for a workers exports.\n  v8::Global<v8::Object> workerExportsObj;\n\n  /* *** External Memory accounting *** */\n  // ExternalMemoryTarget holds a weak reference back to the isolate. ExternalMemoryAjustments\n  // hold references to the ExternalMemoryTarget. This allows the ExternalMemoryAjustments to\n  // outlive the isolate.\n  kj::Arc<const ExternalMemoryTarget> externalMemoryTarget;\n\n  // A shared async context key for accessing env\n  kj::Own<AsyncContextFrame::StorageKey> envAsyncContextKey;\n\n  // A shared async context key for accessing exports\n  kj::Own<AsyncContextFrame::StorageKey> exportsAsyncContextKey;\n\n  // We expect queues to remain relatively small -- 8 is the largest size I have observed from local\n  // testing.\n  static constexpr auto DESTRUCTION_QUEUE_INITIAL_SIZE = 8;\n\n  // If a queue grows larger than this, we reset it back to the initial size.\n  static constexpr auto DESTRUCTION_QUEUE_MAX_CAPACITY = 10'000;\n\n  // We use a double buffer for our deferred destruction queue. This allows us to avoid any\n  // allocations in the general, steady state case, and forces us to clear the vector (a O(n)\n  // operation) outside of the queue lock.\n  const kj::MutexGuarded<BatchQueue<Item>> queue{\n    DESTRUCTION_QUEUE_INITIAL_SIZE, DESTRUCTION_QUEUE_MAX_CAPACITY};\n\n  enum QueueState { ACTIVE, DROPPING, DROPPED };\n  QueueState queueState = ACTIVE;\n\n  struct CodeBlockInfo {\n    size_t size = 0;\n    kj::Maybe<v8::JitCodeEvent::CodeType> type;\n    kj::String name;\n\n    struct PositionMapping {\n      uint instructionOffset;\n      uint sourceOffset;\n    };\n    kj::Array<PositionMapping> mapping;\n    // Sorted\n  };\n\n  // Maps instructions to source code locations.\n  kj::TreeMap<uintptr_t, CodeBlockInfo> codeMap;\n\n  explicit IsolateBase(V8System& system,\n      v8::Isolate::CreateParams&& createParams,\n      kj::Own<IsolateObserver> observer,\n      kj::Own<ExternalStringAllocator> externalStringAllocator,\n      v8::IsolateGroup group);\n  ~IsolateBase() noexcept(false);\n  KJ_DISALLOW_COPY_AND_MOVE(IsolateBase);\n\n  void dropWrappers(kj::FunctionParam<void()> drop);\n\n  bool getCaptureThrowsAsRejections() const {\n    return captureThrowsAsRejections;\n  }\n\n  // Add an item to the deferred destruction queue. Safe to call from any thread at any time.\n  void deferDestruction(Item item);\n\n  // Destroy everything in the deferred destruction queue and apply deferred external memory\n  // updates. Called each time a lock is taken. Must be called under the isolate lock.\n  void applyDeferredActions();\n\n  static void fatalError(const char* location, const char* message);\n  static void oomError(const char* location, const v8::OOMDetails& details);\n\n  static v8::ModifyCodeGenerationFromStringsResult modifyCodeGenCallback(\n      v8::Local<v8::Context> context, v8::Local<v8::Value> source, bool isCodeLike);\n  static bool allowWasmCallback(v8::Local<v8::Context> context, v8::Local<v8::String> source);\n  static bool jspiEnabledCallback(v8::Local<v8::Context> context);\n\n  static void jitCodeEvent(const v8::JitCodeEvent* event) noexcept;\n\n  friend kj::Maybe<kj::StringPtr> getJsStackTrace(void* ucontext, kj::ArrayPtr<char> scratch);\n\n  HeapTracer heapTracer;\n  kj::Own<IsolateObserver> observer;\n  kj::Own<ExternalStringAllocator> externalStringAllocator;\n\n  friend class Data;\n  friend class Wrappable;\n  friend class HeapTracer;\n  friend class ExternalMemoryTarget;\n\n  friend bool getCaptureThrowsAsRejections(v8::Isolate* isolate);\n  friend kj::Maybe<kj::StringPtr> getJsStackTrace(void* ucontext, kj::ArrayPtr<char> scratch);\n\n  friend kj::Exception createTunneledException(\n      v8::Isolate* isolate, v8::Local<v8::Value> exception);\n\n  // Get a singleton ObjectTemplate used for opaque wrappers (which have an empty-object interface\n  // in JavaScript). (Called by Wrappable::attachOpaqueWrapper().)\n  //\n  // This returns a FunctionTemplate which should be used as a constructor. That is, you can use\n  // use `->InstanceTemplate()->NewInstance()` to construct an object, and you can pass this to\n  // `FindInstanceInPrototypeChain()` on an existing object to check whether it was created using\n  // this template.\n  static v8::Local<v8::FunctionTemplate> getOpaqueTemplate(v8::Isolate* isolate);\n};\n\n// If JavaScript frames are currently on the stack, returns a string representing a stack trace\n// through it. The trace is built inside `scratch` without performing any allocation. This is\n// intended to be invoked from a signal handler.\nkj::Maybe<kj::StringPtr> getJsStackTrace(void* ucontext, kj::ArrayPtr<char> scratch);\n\n// Set the location of the pointer cage base for the current isolate.  This is only\n// used by getJsCageBase().\nvoid setJsCageBase(void* cageBase);\n\n// Get the location previously set by setJsCageBase() for the current isolate.  Returns\n// a null pointer if there is no current isolate.\nvoid* getJsCageBase();\n\n// Class representing a JavaScript execution engine, with the ability to wrap some set of API\n// classes which you specify.\n//\n// To use this, you must declare your own custom specialization listing all of the API types that\n// you want to support in this JavaScript context. API types are types which have\n// JSG_RESOURCE_TYPE or JSG_STRUCT declarations, as well as TypeWrapperExtensions.\n//\n// To declare a specialization, do:\n//\n//     JSG_DECLARE_ISOLATE_TYPE(MyIsolateType, MyApiType1, MyApiType2, ...);\n//\n// This declares a class `MyIsolateType` which is a subclass of Isolate. You can then\n// instantiate this class to begin executing JavaScript.\n//\n// You can instantiate multiple Isolates which can run on separate threads simultaneously.\n//\n// Example usage:\n//\n//     // Create once per process, probably in main().\n//     V8System system;\n//\n//     // Create an isolate with the ability to wrap MyType and MyContextType.\n//     JSG_DECLARE_ISOLATE_TYPE(MyIsolate, MyApiType, MyContextApiType);\n//     MyIsolate isolate(system);\n//\n//     // Lock the isolate in this thread (creates a v8::Isolate::Scope).\n//     isolate.runInLockScope([&] (MyIsolate::Lock& lock) {\n//       // Create a context based on MyContextType.\n//       v8::Local<v8::Context> context = lock.newContext(lock.isolate, MyContextType());\n//\n//       // Create an instance of MyType.\n//       v8::Local<v8::Object> obj = lock.getTypeHandler<MyType>().wrap(lock, context, MyType());\n//     });\n//\ntemplate <typename TypeWrapper>\nclass Isolate: public IsolateBase {\n public:\n  // Construct an isolate that requires configuration. `configuration` is a value that all\n  // individual wrappers' configurations must be able to be constructed from. For example, if all\n  // wrappers use the same configuration type, then `MetaConfiguration` should just be that type.\n  // If different wrappers use different types, then `MetaConfiguration` should be some value that\n  // inherits or defines conversion operators to each required type -- or the individual\n  // configuration types must declare constructors from `MetaConfiguration`.\n  // If `instantiateTypeWrapper` is false, then the default wrapper will not be instantiated\n  // and should be instantiated with `instantiateTypeWrapper` before `newContext` is called on\n  // a jsg::Lock of this Isolate.\n  //\n  // If using v8 sandboxing, the group argument controls which isolates share a\n  // sandbox, and which are isolated (as much as possible) in the event of a\n  // heap corruption attack. Note: The isolates in a group are limited to at\n  // most 4Gbytes of V8 heap in all.  Groups can be created with\n  // v8::IsolateGroup::Create().  (If using V8 pointer compression, this\n  // requires the enable_pointer_compression_multiple_cages build flag for V8.)\n  // Pass v8::IsolateGroup::Default() as the group to put all isolates in the\n  // same group.\n  template <typename MetaConfiguration>\n  explicit Isolate(V8System& system,\n      v8::IsolateGroup group,\n      MetaConfiguration&& configuration,\n      kj::Own<IsolateObserver> observer,\n      kj::Own<ExternalStringAllocator> externalStringAllocator = defaultExternalStringAllocator(),\n      v8::Isolate::CreateParams createParams = {},\n      bool instantiateTypeWrapper = true)\n      : IsolateBase(system,\n            kj::mv(createParams),\n            kj::mv(observer),\n            kj::mv(externalStringAllocator),\n            group) {\n    wrappers.resize(1);\n    if (instantiateTypeWrapper) {\n      instantiateDefaultWrapper(kj::fwd<MetaConfiguration>(configuration));\n    }\n  }\n\n  // Legacy isolate constructor that creates a new IsolateGroup for the new\n  // Isolate.  Currently used by non-sandboxing edgeworker, but deprecated.\n  template <typename MetaConfiguration>\n  explicit Isolate(V8System& system,\n      MetaConfiguration&& configuration,\n      kj::Own<IsolateObserver> observer,\n      v8::Isolate::CreateParams createParams = {},\n      bool instantiateTypeWrapper = true)\n      : IsolateBase(system,\n            kj::mv(createParams),\n            kj::mv(observer),\n            defaultExternalStringAllocator(),\n            v8::IsolateGroup::Create()) {\n    wrappers.resize(1);\n    if (instantiateTypeWrapper) {\n      instantiateDefaultWrapper(kj::fwd<MetaConfiguration>(configuration));\n    }\n  }\n\n  // Use this constructor when no wrappers have any required configuration.\n  explicit Isolate(V8System& system,\n      kj::Own<IsolateObserver> observer,\n      v8::Isolate::CreateParams createParams = {})\n      : Isolate(system,\n            v8::IsolateGroup::GetDefault(),\n            nullptr,\n            kj::mv(observer),\n            defaultExternalStringAllocator(),\n            kj::mv(createParams)) {}\n\n  template <typename MetaConfiguration>\n  void instantiateDefaultWrapper(MetaConfiguration&& configuration) {\n    KJ_DASSERT(wrappers[0].get() == nullptr);\n    auto wrapper = wrapperSpace.construct(ptr, kj::fwd<MetaConfiguration>(configuration));\n    wrapper->initTypeWrapper();\n    wrappers[0] = kj::mv(wrapper);\n  }\n\n  ~Isolate() noexcept(false) {\n    dropWrappers([this]() { wrappers.clear(); });\n  }\n\n  kj::Exception unwrapException(\n      Lock& js, v8::Local<v8::Context> context, v8::Local<v8::Value> exception) override {\n    return getWrapperByContext(context)->template unwrap<kj::Exception>(\n        js, context, exception, jsg::TypeErrorContext::other());\n  }\n\n  v8::Local<v8::Value> wrapException(\n      Lock& js, v8::Local<v8::Context> context, kj::Exception&& exception) override {\n    return getWrapperByContext(context)->wrap(\n        js, context, kj::none, kj::fwd<kj::Exception>(exception));\n  }\n\n  bool serialize(\n      Lock& js, std::type_index type, jsg::Object& instance, Serializer& serializer) override {\n    auto* wrapper = getWrapperByContext(js);\n    KJ_IF_SOME(func, wrapper->serializerMap.find(type)) {\n      func(*wrapper, js, instance, serializer);\n      return true;\n    } else {\n      return false;\n    }\n  }\n  kj::Maybe<v8::Local<v8::Object>> deserialize(\n      Lock& js, uint tag, Deserializer& deserializer) override {\n    auto* wrapper = getWrapperByContext(js);\n    KJ_IF_SOME(func, wrapper->deserializerMap.find(tag)) {\n      return func(*wrapper, js, tag, deserializer);\n    } else {\n      return kj::none;\n    }\n  }\n\n  // Before you can execute code in your Isolate you must lock it to the current thread by\n  // constructing a `Lock` on the stack.\n  class Lock final: public jsg::Lock {\n\n   public:\n    // `V8StackScope` must be provided to prove that one has been created on the stack before\n    // taking a lock. Any GC'ed pointers stored on the stack must be kept within this scope in\n    // order for V8's stack-scanning GC to find them.\n    Lock(const Isolate& isolate, V8StackScope&)\n        : jsg::Lock(isolate.ptr),\n          jsgIsolate(const_cast<Isolate&>(isolate)) {\n      jsgIsolate.applyDeferredActions();\n    }\n    KJ_DISALLOW_COPY_AND_MOVE(Lock);\n    KJ_DISALLOW_AS_COROUTINE_PARAM;\n\n    // Creates a `TypeHandler` for the given type. You can use this to convert between the type\n    // and V8 handles, as well as to allocate instances of the type on the V8 heap (if it is\n    // a resource type).\n    template <typename T>\n    const TypeHandler<T>& getTypeHandler() {\n      return TypeWrapper::template TYPE_HANDLER_INSTANCE<T>;\n    }\n\n    // Wrap a C++ value, returning a v8::Local (possibly of a specific type).\n    template <typename T>\n    auto wrap(v8::Local<v8::Context> context, T&& value) {\n      return jsgIsolate.getWrapperByContext(context)->wrap(\n          *this, context, kj::none, kj::fwd<T>(value));\n    }\n\n    // Wrap a context-independent value. Only a few built-in types, like numbers and strings,\n    // can be wrapped without a context.\n    template <typename T>\n    auto wrapNoContext(T&& value) {\n      return jsgIsolate.getWrapperByContext(*this)->wrap(v8Isolate, kj::none, kj::fwd<T>(value));\n    }\n\n    // Convert a JavaScript value to a C++ value, or throw a JS exception if the type doesn't\n    // match.\n    template <typename T>\n    auto unwrap(v8::Local<v8::Context> context, v8::Local<v8::Value> handle) {\n      return jsgIsolate.getWrapperByContext(context)->template unwrap<T>(\n          *this, context, handle, jsg::TypeErrorContext::other());\n    }\n\n    Ref<DOMException> domException(\n        kj::String name, kj::String message, kj::Maybe<kj::String> maybeStack) override {\n      return withinHandleScope([&] {\n        v8::Local<v8::FunctionTemplate> tmpl = jsgIsolate.getWrapperByContext(*this)->getTemplate(\n            v8Isolate, static_cast<DOMException*>(nullptr));\n        KJ_DASSERT(!tmpl.IsEmpty());\n        v8::Local<v8::Object> obj = check(tmpl->InstanceTemplate()->NewInstance(v8Context()));\n        v8::Local<v8::String> stackName = str(\"stack\"_kjc);\n\n        KJ_IF_SOME(stack, maybeStack) {\n          v8::PropertyDescriptor prop(str(stack), true);\n          prop.set_enumerable(true);\n          jsg::check(obj->DefineProperty(v8Context(), stackName, prop));\n        } else {\n          v8::Exception::CaptureStackTrace(v8Context(), obj);\n          v8::PropertyDescriptor prop;\n          prop.set_enumerable(true);\n          jsg::check(obj->DefineProperty(v8Context(), stackName, prop));\n        }\n\n        auto de = alloc<DOMException>(kj::mv(message), kj::mv(name));\n        de.attachWrapper(v8Isolate, obj);\n\n        return kj::mv(de);\n      });\n    }\n\n    // Returns the constructor function for a given type declared as JSG_RESOURCE_TYPE.\n    //\n    // Note there's a useful property of class constructor functions: A constructor's __proto__\n    // is set to the parent type's constructor. Thus you can discover whether one class is a\n    // subclass of another by following the __proto__ chain.\n    //\n    // TODO(cleanup): This should return `JsFunction`, but there is no such type. We only have\n    //   `jsg::Function<...>` (or perhaps more appropriately, `jsg::Constructor<...>`), but we\n    //   don't actually know the function signature so that's not useful here. Should we add a\n    //   `JsFunction` that has no signature?\n    template <typename T>\n    jsg::JsObject getConstructor(v8::Local<v8::Context> context) {\n      v8::EscapableHandleScope scope(v8Isolate);\n      v8::Local<v8::FunctionTemplate> tpl =\n          jsgIsolate.getWrapperByContext(context)->getTemplate(v8Isolate, static_cast<T*>(nullptr));\n      v8::Local<v8::Object> prototype = check(tpl->GetFunction(context));\n      return jsg::JsObject(scope.Escape(prototype));\n    }\n\n    v8::Local<v8::ArrayBuffer> wrapBytes(kj::Array<byte> data) override {\n      return jsgIsolate.getWrapperByContext(*this)->wrap(v8Isolate, kj::none, kj::mv(data));\n    }\n    v8::Local<v8::Function> wrapSimpleFunction(v8::Local<v8::Context> context,\n        jsg::Function<void(const v8::FunctionCallbackInfo<v8::Value>& info)> simpleFunction)\n        override {\n      return jsgIsolate.getWrapperByContext(context)->wrap(\n          *this, context, kj::none, kj::mv(simpleFunction));\n    }\n    v8::Local<v8::Function> wrapReturningFunction(v8::Local<v8::Context> context,\n        jsg::Function<v8::Local<v8::Value>(const v8::FunctionCallbackInfo<v8::Value>& info)>\n            returningFunction) override {\n      return jsgIsolate.getWrapperByContext(context)->wrap(\n          *this, context, kj::none, kj::mv(returningFunction));\n    }\n    v8::Local<v8::Function> wrapPromiseReturningFunction(v8::Local<v8::Context> context,\n        jsg::Function<jsg::Promise<jsg::Value>(const v8::FunctionCallbackInfo<v8::Value>& info)>\n            returningFunction) override {\n      return jsgIsolate.getWrapperByContext(context)->wrap(\n          *this, context, kj::none, kj::mv(returningFunction));\n    }\n    kj::String toString(v8::Local<v8::Value> value) override {\n      return jsgIsolate.getWrapperByContext(*this)->template unwrap<kj::String>(\n          *this, v8Isolate->GetCurrentContext(), value, jsg::TypeErrorContext::other());\n    }\n    jsg::Dict<v8::Local<v8::Value>> toDict(v8::Local<v8::Value> value) override {\n      return jsgIsolate.getWrapperByContext(*this)\n          ->template unwrap<jsg::Dict<v8::Local<v8::Value>>>(\n              *this, v8Isolate->GetCurrentContext(), value, jsg::TypeErrorContext::other());\n    }\n    jsg::Dict<jsg::JsValue> toDict(const jsg::JsValue& value) override {\n      return jsgIsolate.getWrapperByContext(*this)->template unwrap<jsg::Dict<jsg::JsValue>>(\n          *this, v8Isolate->GetCurrentContext(), value, jsg::TypeErrorContext::other());\n    }\n    v8::Local<v8::Promise> wrapSimplePromise(jsg::Promise<jsg::Value> promise) override {\n      return jsgIsolate.getWrapperByContext(*this)->wrap(\n          *this, v8Context(), kj::none, kj::mv(promise));\n    }\n    jsg::Promise<jsg::Value> toPromise(v8::Local<v8::Value> promise) override {\n      return jsgIsolate.getWrapperByContext(*this)->template unwrap<jsg::Promise<jsg::Value>>(\n          *this, v8Isolate->GetCurrentContext(), promise, jsg::TypeErrorContext::other());\n    }\n\n    template <typename T, typename... Args>\n    JsContext<T> newContextWithWrapper(\n        TypeWrapper* wrapper, NewContextOptions options, Args&&... args) {\n      // TODO(soon): Requiring move semantics for the global object is awkward. This should instead\n      //   allocate the object (forwarding arguments to the constructor) and return something like\n      //   a Ref.\n      auto context = wrapper->newContext(*this, options, jsgIsolate.getObserver(),\n          static_cast<T*>(nullptr), kj::fwd<Args>(args)...);\n      jsg::setAlignedPointerInEmbedderData(\n          context.getHandle(v8Isolate), jsg::ContextPointerSlot::EXTENDED_CONTEXT_WRAPPER, wrapper);\n      return context;\n    }\n\n    // Creates a new JavaScript \"context\", i.e. the global object. This is the first step to\n    // executing JavaScript code. T should be one of your API types which you want to use as the\n    // global object. `args...` are passed to the type's constructor.\n    template <typename T, typename... Args>\n    JsContext<T> newContext(NewContextOptions options, Args&&... args) {\n      KJ_DASSERT(!jsgIsolate.wrappers.empty());\n      KJ_DASSERT(jsgIsolate.wrappers[0].get() != nullptr);\n      return newContextWithWrapper<T>(\n          jsgIsolate.wrappers[0].get(), options, kj::fwd<Args>(args)...);\n    }\n\n    // Creates a new JavaScript \"context\", i.e. the global object. This is the first step to\n    // executing JavaScript code. T should be one of your API types which you want to use as the\n    // global object. `args...` are passed to the type's constructor.\n    template <typename T, typename... Args>\n    JsContext<T> newContext(Args&&... args) {\n      return newContext<T>(NewContextOptions{}, kj::fwd<Args>(args)...);\n    }\n\n    template <typename T, typename MetaConfiguration, typename... Args>\n    JsContext<T> newContextWithConfiguration(\n        MetaConfiguration&& configuration, NewContextOptions options, Args&&... args) {\n      jsgIsolate.hasExtraWrappers = true;\n      auto& wrapper = jsgIsolate.wrappers.add(\n          kj::heap<TypeWrapper>(jsgIsolate.ptr, kj::fwd<MetaConfiguration>(configuration)));\n      return newContextWithWrapper<T>(wrapper.get(), options, kj::fwd<Args>(args)...);\n    }\n\n    void reportError(const JsValue& value) override {\n      auto& js = Lock::from(v8Isolate);\n      KJ_IF_SOME(domException,\n          jsgIsolate.getWrapperByContext(*this)->tryUnwrap(\n              js, v8Context(), value, static_cast<DOMException*>(nullptr), kj::none)) {\n        auto desc =\n            kj::str(\"DOMException(\", domException.getName(), \"): \", domException.getMessage());\n        jsgIsolate.reportError(*this, kj::mv(desc), value, JsMessage::create(*this, value));\n      } else {\n        jsgIsolate.reportError(\n            *this, value.toString(*this), value, JsMessage::create(*this, value));\n      }\n    }\n\n    void setWorkerEnv(V8Ref<v8::Object> value) override {\n      jsgIsolate.workerEnvObj.Reset(v8Isolate, value.getHandle(*this));\n    }\n\n    kj::Maybe<V8Ref<v8::Object>> getWorkerEnv() override {\n      if (jsgIsolate.workerEnvObj.IsEmpty()) return kj::none;\n      return v8Ref<v8::Object>(jsgIsolate.workerEnvObj.Get(v8Isolate));\n    }\n\n    void setWorkerExports(V8Ref<v8::Object> value) override {\n      jsgIsolate.workerExportsObj.Reset(v8Isolate, value.getHandle(*this));\n    }\n\n    kj::Maybe<V8Ref<v8::Object>> getWorkerExports() override {\n      if (jsgIsolate.workerExportsObj.IsEmpty()) return kj::none;\n      return v8Ref<v8::Object>(jsgIsolate.workerExportsObj.Get(v8Isolate));\n    }\n\n   private:\n    Isolate& jsgIsolate;\n\n    virtual kj::Maybe<Object&> getInstance(\n        v8::Local<v8::Object> obj, const std::type_info& type) override {\n      auto instance = v8::Local<v8::Object>(obj)->FindInstanceInPrototypeChain(\n          jsgIsolate.getWrapperByContext(*this)->getDynamicTypeInfo(v8Isolate, type).tmpl);\n      if (instance.IsEmpty()) {\n        return kj::none;\n      } else {\n        return *reinterpret_cast<Object*>(\n            instance->GetAlignedPointerFromInternalField(Wrappable::WRAPPED_OBJECT_FIELD_INDEX,\n                static_cast<v8::EmbedderDataTypeTag>(Wrappable::WRAPPED_OBJECT_FIELD_INDEX)));\n      }\n    }\n\n    virtual v8::Local<v8::Object> getPrototypeFor(const std::type_info& type) override {\n      v8::EscapableHandleScope scope(v8Isolate);\n      auto tmpl = jsgIsolate.getWrapperByContext(*this)->getDynamicTypeInfo(v8Isolate, type).tmpl;\n      auto constructor = JsObject(check(tmpl->GetFunction(v8Context())));\n\n      // Note that `constructor.getPrototype()` returns the prototype of the constructor itself,\n      // which is NOT the same as the prototype of the object it constructs. For the latter we\n      // need to access the `prototype` property.\n      auto proto = constructor.get(*this, \"prototype\");\n\n      KJ_ASSERT(proto.isObject());\n      return scope.Escape(v8::Local<v8::Value>(proto).As<v8::Object>());\n    }\n  };\n\n  // The func must be a callback with the signature: T(jsg::Lock&)\n  // Be careful not to leak v8 objects outside of the scope.\n  auto runInLockScope(auto func) {\n    return runInV8Stack([&](V8StackScope& stackScope) {\n      Lock lock(*this, stackScope);\n      return lock.withinHandleScope([&] { return func(lock); });\n    });\n  }\n\n protected:\n  inline TypeWrapper* getWrapperByContext(jsg::Lock& js) {\n    if (KJ_LIKELY(!hasExtraWrappers)) {\n      return wrappers[0].get();\n    } else {\n      return getWrapperByContext(js.v8Context());\n    }\n  }\n  inline TypeWrapper* getWrapperByContext(v8::Local<v8::Context> context) {\n    if (KJ_LIKELY(!hasExtraWrappers)) {\n      return wrappers[0].get();\n    } else {\n      KJ_IF_SOME(data,\n          jsg::getAlignedPointerFromEmbedderData<TypeWrapper>(\n              context, ContextPointerSlot::EXTENDED_CONTEXT_WRAPPER)) {\n        return &data;\n      }\n      return wrappers[0].get();\n    }\n  }\n\n private:\n  kj::SpaceFor<TypeWrapper> wrapperSpace;\n  kj::Vector<kj::Own<TypeWrapper>> wrappers;  // Needs to be destroyed under lock...\n  // This is just an optimization boolean, when we only have one wrapper we can skip calling\n  // GetAlignedPointerFromEmbedderData and just return wrappers[0].\n  bool hasExtraWrappers = false;\n};\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/struct-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"jsg-test.h\"\n\nnamespace workerd::jsg::test {\nnamespace {\n\nV8System v8System;\n\nstruct SelfStruct {\n  SelfRef self;\n  int i;\n\n  JSG_STRUCT(self, i);\n};\n\nstruct StructContext: public Object, public ContextGlobal {\n  kj::String readTestStruct(TestStruct s) {\n    return kj::str(s.str, \", \", s.num, \", \", s.box->value);\n  }\n  TestStruct makeTestStruct(jsg::Lock& js, kj::String str, double num, NumberBox& box) {\n    return {kj::mv(str), num, js.alloc<NumberBox>(box.value)};\n  }\n  V8Ref<v8::Object> readSelfStruct(Lock& js, SelfStruct s) {\n    KJ_ASSERT(s.i == 123);\n    return kj::mv(s.self);\n  }\n  SelfStruct makeSelfStruct(Lock& js) {\n    return {.self = {js.v8Isolate, v8::Object::New(js.v8Isolate)}, .i = 456};\n  }\n\n  JSG_RESOURCE_TYPE(StructContext) {\n    JSG_NESTED_TYPE(NumberBox);\n    JSG_METHOD(readTestStruct);\n    JSG_METHOD(makeTestStruct);\n    JSG_METHOD(readSelfStruct);\n    JSG_METHOD(makeSelfStruct);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(StructIsolate, StructContext, NumberBox, TestStruct, SelfStruct);\n\nKJ_TEST(\"structs\") {\n  Evaluator<StructContext, StructIsolate> e(v8System);\n  e.expectEval(\n      \"readTestStruct({str: 'foo', num: 123, box: new NumberBox(456)})\", \"string\", \"foo, 123, 456\");\n  e.expectEval(\"var s = makeTestStruct('foo', 123, new NumberBox(456));\\n\"\n               \"[s.str, s.num, s.box.value].join(', ')\",\n      \"string\", \"foo, 123, 456\");\n\n  e.expectEval(\"readTestStruct({str: 'foo', num: 123, box: 'wrong'})\", \"throws\",\n      \"TypeError: Incorrect type for the 'box' field on 'TestStruct': the provided \"\n      \"value is not of type 'NumberBox'.\");\n\n  e.expectEval(\n      \"JSON.stringify(readSelfStruct({i: 123, x: 'foo'}))\", \"string\", \"{\\\"i\\\":123,\\\"x\\\":\\\"foo\\\"}\");\n  e.expectEval(\"JSON.stringify(makeSelfStruct())\", \"string\", \"{\\\"i\\\":456}\");\n}\n\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/struct.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// INTERNAL IMPLEMENTATION FILE\n//\n// Translates between C++ struct types and JavaScript objects. This translation is by value: the\n// struct is translated to/from a native JS object with the same field names.\n\n#include <workerd/jsg/util.h>\n#include <workerd/jsg/value.h>\n#include <workerd/jsg/web-idl.h>\n\n#include <concepts>\n#include <type_traits>\n\nnamespace workerd::jsg {\n\ntemplate <typename T>\nconstexpr bool isV8LocalOrData = isV8Local<T>() || std::is_base_of_v<v8::Data, T> || IsJsValue<T>;\n\ntemplate <typename T>\nconstexpr bool isV8LocalOrData<kj::Maybe<T>> = isV8LocalOrData<T>;\n\ntemplate <typename T>\nconstexpr bool isV8LocalOrData<Optional<T>> = isV8LocalOrData<T>;\n\ntemplate <typename T>\nconstexpr bool isV8LocalOrData<LenientOptional<T>> = isV8LocalOrData<T>;\n\ntemplate <typename T>\nconstexpr bool isV8LocalOrData<kj::Array<T>> = isV8LocalOrData<T>;\n\ntemplate <typename T>\nconstexpr bool isV8LocalOrData<kj::ArrayPtr<T>> = isV8LocalOrData<T>;\n\ntemplate <typename T>\nconstexpr bool isV8LocalOrData<Dict<T>> = isV8LocalOrData<T>;\n\ntemplate <typename T, typename... Rest>\nconstexpr bool isV8LocalOrData<kj::OneOf<T, Rest...>> =\n    isV8LocalOrData<T> || (isV8LocalOrData<Rest> || ...);\n\n// JSG_STRUCT member fields really should not be v8::Locals, v8::Datas, or JsValues because\n// there's no guarantee the v8::HandleScope will be valid when the field is accessed. Instead\n// they should be wrapped in jsg::V8Ref or jsg::JsRef. However, we only want to enforce this\n// for JSG_STRUCTs that we *receive* from JS, not for JSG_STRUCTs that we *send* to JS, so\n// we only actually apply this check when unwrapping (JS -> C++). Why? Great question! It's\n// because when we are sending a struct to JS, we know we have a valid v8::HandleScope and\n// it's fairly expensive to create a jsg::JsRef/jsg::V8Ref, especially when we need to do\n// so repeatedly (e.g. for an iterator, for instance).\ntemplate <typename T>\nconcept NotV8Local = !isV8LocalOrData<T>;\n\n// Just to be sure we got the concept right...\nstatic_assert(NotV8Local<int>);\nstatic_assert(NotV8Local<kj::String>);\nstatic_assert(NotV8Local<kj::Array<int>>);\nstatic_assert(NotV8Local<kj::Maybe<kj::String>>);\nstatic_assert(NotV8Local<kj::OneOf<int, kj::String>>);\nstatic_assert(!NotV8Local<kj::Maybe<v8::Local<v8::Object>>>);\nstatic_assert(!NotV8Local<kj::Maybe<JsValue>>);\nstatic_assert(!NotV8Local<jsg::Optional<v8::Local<v8::Object>>>);\nstatic_assert(!NotV8Local<jsg::Optional<JsObject>>);\nstatic_assert(!NotV8Local<kj::OneOf<int, v8::Local<v8::Object>>>);\nstatic_assert(!NotV8Local<kj::OneOf<int, JsValue>>);\nstatic_assert(!NotV8Local<kj::OneOf<int, kj::String, kj::Maybe<JsValue>>>);\nstatic_assert(\n    !NotV8Local<kj::OneOf<int, kj::String, kj::Maybe<kj::OneOf<int, kj::Maybe<JsValue>>>>>);\nstatic_assert(!NotV8Local<v8::Local<v8::Object>>);\nstatic_assert(!NotV8Local<JsValue>);\nstatic_assert(!NotV8Local<v8::Local<v8::Value>>);\nstatic_assert(!NotV8Local<v8::Value>);\nstatic_assert(!NotV8Local<kj::Array<JsValue>>);\nstatic_assert(!NotV8Local<kj::Array<v8::Local<v8::Object>>>);\nstatic_assert(!NotV8Local<Dict<JsValue>>);\n\ntemplate <typename TypeWrapper,\n    typename Struct,\n    typename T,\n    T Struct::*field,\n    const char* name,\n    size_t namePrefixStripLength>\nclass FieldWrapper {\n  static constexpr inline const char* exportedName = name + namePrefixStripLength;\n\n public:\n  using Type = T;\n\n  explicit FieldWrapper(v8::Isolate* isolate)\n      : nameHandle(isolate, v8StrIntern(isolate, exportedName)) {}\n\n  // The is the original, slow-path wrap implementation that uses Set(). Prefer the other overload\n  // for better performance. It is, however, a breaking change to remove this overload so we\n  // need to keep it with a compatibility flag.\n  void wrap(Lock& js,\n      TypeWrapper& wrapper,\n      v8::Isolate* isolate,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      Struct& in,\n      v8::Local<v8::Object> out) {\n    if constexpr (kj::isSameType<T, SelfRef>()) {\n      // Ignore SelfRef when converting to JS.\n    } else if constexpr (kj::isSameType<T, Unimplemented>() || kj::isSameType<T, WontImplement>()) {\n      // Fields with these types are required NOT to be present, so don't try to convert them.\n    } else {\n      if constexpr (webidl::OptionalType<Type>) {\n        // Don't even set optional fields that aren't present.\n        if (in.*field == kj::none) return;\n      }\n      auto value = wrapper.wrap(js, context, creator, kj::mv(in.*field));\n      check(out->Set(context, nameHandle.Get(isolate), value));\n    }\n  }\n\n  void wrap(Lock& js,\n      TypeWrapper& wrapper,\n      v8::Isolate* isolate,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      Struct& in,\n      v8::MaybeLocal<v8::Value>& out,\n      size_t& idx) {\n    if constexpr (kj::isSameType<T, SelfRef>()) {\n      // Ignore SelfRef when converting to JS.\n    } else if constexpr (kj::isSameType<T, Unimplemented>() || kj::isSameType<T, WontImplement>()) {\n      // Fields with these types are required NOT to be present, so don't try to convert them.\n    } else {\n      idx++;\n      out = wrapper.wrap(js, context, creator, kj::mv(in.*field));\n    }\n  }\n\n  Type unwrap(TypeWrapper& wrapper,\n      v8::Isolate* isolate,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Object> in) {\n    static_assert(NotV8Local<Type>);\n    v8::Local<v8::Value> jsValue = check(in->Get(context, nameHandle.Get(isolate)));\n    auto& js = Lock::from(isolate);\n    return wrapper.template unwrap<Type>(\n        js, context, jsValue, TypeErrorContext::structField(typeid(Struct), exportedName), in);\n  }\n\n private:\n  v8::Global<v8::Name> nameHandle;\n};\n\ntemplate <typename... T>\nstruct TypeTuple {\n  using Indexes = kj::_::MakeIndexes<sizeof...(T)>;\n};\n\ntemplate <typename Self,\n    typename T,\n    typename FieldWrapperTuple,\n    typename Indices = FieldWrapperTuple::Indexes>\nclass StructWrapper;\n\n// TypeWrapper mixin for struct types (application-defined C++ structs declared with a\n// JSG_STRUCT block).\ntemplate <typename Self, typename T, typename... FieldWrappers, size_t... indices>\nclass StructWrapper<Self, T, TypeTuple<FieldWrappers...>, kj::_::Indexes<indices...>> {\n public:\n  static const JsgKind JSG_KIND = JsgKind::STRUCT;\n\n  static constexpr const std::type_info& getName(T*) {\n    return typeid(T);\n  }\n\n  // A count of the JSG_STRUCT fields that are usable for the v8::DictionaryTemplate\n  // version of wrap (i.e. not SelfRef, Unimplemented, or WontImplement).\n  static constexpr size_t kCountOfUsableFields =\n      ((isUsableStructField<typename FieldWrappers::Type> ? 1 : 0) + ...);\n\n  v8::Local<v8::Object> wrap(\n      Lock& js, v8::Local<v8::Context> context, kj::Maybe<v8::Local<v8::Object>> creator, T&& in) {\n    auto isolate = js.v8Isolate;\n    auto& fields = getFields(isolate);\n\n    // Fast path using a cached dictionary template.\n    if (js.isUsingFastJsgStruct()) {\n      v8::MaybeLocal<v8::Value> values[kCountOfUsableFields]{};\n\n      size_t idx = 0;\n      (kj::get<indices>(fields).wrap(\n           js, static_cast<Self&>(*this), isolate, context, creator, in, values[idx], idx),\n          ...);\n\n      // We use a cached dictionary template to improve performance on repeated struct wraps.\n\n      v8::Local<v8::DictionaryTemplate> tmpl;\n      if (templateHandle.IsEmpty()) {\n        tmpl = T::template jsgGetTemplate<T>(isolate);\n        templateHandle.Reset(isolate, tmpl);\n      } else {\n        tmpl = templateHandle.Get(isolate);\n      }\n\n      // Make sure we filled in the expected number of fields.\n      KJ_ASSERT(idx == kCountOfUsableFields);\n\n      return tmpl->NewInstance(context, values);\n    }\n\n    // Original slow path.\n    v8::Local<v8::Object> out = v8::Object::New(isolate);\n    (kj::get<indices>(fields).wrap(\n         js, static_cast<Self&>(*this), isolate, context, creator, in, out),\n        ...);\n    return out;\n  }\n\n  kj::Maybe<T> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      T*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    // In the case that an individual field is the wrong type, we don't return null, but throw an\n    // exception directly. This is because:\n    // 1) If we returned null, we'd lose useful debugging information about which exact field was\n    //    incorrectly typed.\n    // 2) Returning null is intended to allow calling code to probe for different types, e.g. to\n    //    allow a parameter which is \"either a String or an ArrayBuffer\". Such probing really\n    //    intends to check the top-level type. Recursively probing all fields in order to check\n    //    if they match probably isn't a practical use case, since it would be inefficient and\n    //    could lead to ambiguous results, especially when fields are optional.\n    //\n    // For similar reasons, if we are initializing this dictionary from null/undefined, and the\n    // dictionary has required members, we throw.\n\n    if (handle->IsUndefined() || handle->IsNull()) {\n      if constexpr (((webidl::OptionalType<typename FieldWrappers::Type> ||\n                         kj::isSameType<typename FieldWrappers::Type, Unimplemented>()) &&\n                        ...)) {\n        return T{};\n      }\n      jsg::throwTypeError(js.v8Isolate,\n          kj::str(\"Cannot initialize \", typeid(T).name(),\n              \" with required members from an \"\n              \"undefined or null value.\"));\n    }\n\n    if (!handle->IsObject()) return kj::none;\n\n    auto& fields = getFields(js.v8Isolate);\n    auto in = handle.As<v8::Object>();\n\n    // Note: We unwrap struct members in the order in which the compiler evaluates the expressions\n    //   in `T { expressions... }`. This is technically a non-conformity from Web IDL's perspective:\n    //   it prescribes lexicographically-ordered member initialization, with base members ordered\n    //   before derived members. Objects with mutating getters might be broken by this, but it\n    //   doesn't seem worth fixing absent a compelling use case.\n    auto t =\n        T{kj::get<indices>(fields).unwrap(static_cast<Self&>(*this), js.v8Isolate, context, in)...};\n\n    // Note that if a `validate` function is provided, then it will be called after the struct is\n    // unwrapped from v8. This would be an appropriate time to throw an error.\n    // Signature: void validate(jsg::Lock& js);\n    if constexpr (requires { t.validate(js); }) {\n      t.validate(js);\n    }\n\n    return t;\n  }\n\n  void newContext() = delete;\n  void getTemplate() = delete;\n\n private:\n  v8::Global<v8::DictionaryTemplate> templateHandle;\n  kj::Maybe<kj::Tuple<FieldWrappers...>> lazyFields;\n\n  kj::Tuple<FieldWrappers...>& getFields(v8::Isolate* isolate) {\n    KJ_IF_SOME(f, lazyFields) {\n      return f;\n    } else {\n      return lazyFields.emplace(kj::tuple(FieldWrappers(isolate)...));\n    }\n  }\n};\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/tracing-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"jsg-test.h\"\n\nnamespace workerd::jsg::test {\nnamespace {\n\nV8System v8System({\"--expose-gc\"_kj});\n\nclass NumberBoxHolder: public Object {\n  // An object that holds a NumberBox and implements GC visitation correctly.\n  //\n  // This differs from BoxBox (in jsg-test.h) in that this just holds the exact object you give\n  // it, whereas BoxBox likes to create new objects.\n\n public:\n  explicit NumberBoxHolder(Ref<NumberBox> inner): inner(kj::mv(inner)) {}\n\n  Ref<NumberBox> inner;\n\n  static Ref<NumberBoxHolder> constructor(jsg::Lock& js, Ref<NumberBox> inner) {\n    return js.alloc<NumberBoxHolder>(kj::mv(inner));\n  }\n\n  Ref<NumberBox> getInner() {\n    return inner.addRef();\n  }\n\n  JSG_RESOURCE_TYPE(NumberBoxHolder) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(inner, getInner);\n  }\n\n private:\n  void visitForGc(GcVisitor& visitor) {\n    visitor.visit(inner);\n  }\n};\n\nclass GcDetector: public jsg::Object {\n  // Object which comes in pairs where one member of the pair can detect if the other has been\n  // collected.\n\n public:\n  ~GcDetector() noexcept(false) {\n    KJ_IF_SOME(s, sibling) s.sibling = kj::none;\n  }\n\n  kj::Maybe<GcDetector&> sibling;\n\n  bool getSiblingCollected() {\n    return sibling == kj::none;\n  }\n\n  bool touch() {\n    return true;\n  }\n\n  JSG_RESOURCE_TYPE(GcDetector) {\n    // NOTE: Using an instance property instead of a prototype property causes V8 to refuse to\n    //   collect the wrapper during minor GCs, as it always thinks the wrapper is \"modified\".\n    JSG_READONLY_PROTOTYPE_PROPERTY(siblingCollected, getSiblingCollected);\n    JSG_METHOD(touch);\n  }\n};\n\nclass GcDetectorBox: public jsg::Object {\n  // Contains a GcDetector. Useful for testing tracing scenarios.\n\n public:\n  GcDetectorBox(jsg::Lock& js): inner(js.alloc<GcDetector>()) {}\n  jsg::Ref<GcDetector> inner;\n\n  jsg::Ref<GcDetector> getInner() {\n    return inner.addRef();\n  }\n\n  JSG_RESOURCE_TYPE(GcDetectorBox) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(inner, getInner);\n  }\n\n private:\n  void visitForGc(GcVisitor& visitor) {\n    visitor.visit(inner);\n  }\n};\n\nclass ValueBox: public jsg::Object {\n  // Contains an arbitrary value.\n\n public:\n  ValueBox(jsg::Value inner): inner(kj::mv(inner)) {}\n\n  static jsg::Ref<ValueBox> constructor(jsg::Lock& js, jsg::Value inner) {\n    return js.alloc<ValueBox>(kj::mv(inner));\n  }\n\n  jsg::Value inner;\n\n  jsg::Value getInner(jsg::Lock& lock) {\n    return inner.addRef(lock);\n  }\n\n  JSG_RESOURCE_TYPE(ValueBox) {\n    JSG_READONLY_PROTOTYPE_PROPERTY(inner, getInner);\n  }\n\n private:\n  void visitForGc(GcVisitor& visitor) {\n    visitor.visit(inner);\n  }\n};\n\nstruct TraceTestContext: public Object, public ContextGlobal {\n  kj::Maybe<jsg::Ref<NumberBox>> strongRef;\n  // A strong reference to a NumberBox which may be get and set.\n\n  jsg::Ref<NumberBox> getStrongRef() {\n    return KJ_REQUIRE_NONNULL(strongRef).addRef();\n  }\n\n  void setStrongRef(jsg::Ref<NumberBox> ref) {\n    strongRef = kj::mv(ref);\n  }\n\n  kj::Array<jsg::Ref<GcDetector>> makeGcDetectorPair(jsg::Lock& js) {\n    auto obj1 = js.alloc<GcDetector>();\n    auto obj2 = js.alloc<GcDetector>();\n    obj1->sibling = *obj2;\n    obj2->sibling = *obj1;\n    return kj::arr(kj::mv(obj1), kj::mv(obj2));\n  }\n\n  kj::Array<jsg::Ref<GcDetectorBox>> makeGcDetectorBoxPair(jsg::Lock& js) {\n    auto obj1 = js.alloc<GcDetectorBox>(js);\n    auto obj2 = js.alloc<GcDetectorBox>(js);\n    obj1->inner->sibling = *obj2->inner;\n    obj2->inner->sibling = *obj1->inner;\n    return kj::arr(kj::mv(obj1), kj::mv(obj2));\n  }\n\n  void assert_(bool condition, jsg::Optional<kj::String> message) {\n    JSG_ASSERT(condition, Error, message.orDefault(nullptr));\n  }\n\n  JSG_RESOURCE_TYPE(TraceTestContext) {\n    JSG_NESTED_TYPE(NumberBox);\n    JSG_NESTED_TYPE(NumberBoxHolder);\n    JSG_NESTED_TYPE(GcDetector);\n    JSG_NESTED_TYPE(ValueBox);\n    JSG_METHOD(makeGcDetectorPair);\n    JSG_METHOD(makeGcDetectorBoxPair);\n    JSG_METHOD_NAMED(assert, assert_);\n    JSG_PROTOTYPE_PROPERTY(strongRef, getStrongRef, setStrongRef);\n  }\n};\n\nJSG_DECLARE_ISOLATE_TYPE(TraceTestIsolate,\n    TraceTestContext,\n    NumberBox,\n    NumberBoxHolder,\n    GcDetector,\n    GcDetectorBox,\n    ValueBox);\n\nKJ_TEST(\"GC collects objects when expected\") {\n  Evaluator<TraceTestContext, TraceTestIsolate> e(v8System);\n\n  // Test that a full GC can collect native objects.\n  e.expectEval(R\"(\n    let pair = makeGcDetectorPair();\n    let a = pair[0];\n    let b = pair[1];\n    pair = null;\n    a = null;\n    gc();\n    assert(b.siblingCollected, \"full GC did not collect native objects\");\n  )\",\n      \"undefined\", \"undefined\");\n\n  // Test that a full GC can collect native cyclic objects.\n  e.expectEval(R\"(\n    let pair = makeGcDetectorBoxPair();\n    let a = pair[0];\n    let b = pair[1].inner;\n    pair = null;\n    a.inner.cycle = a;  // create cycle involving a jsg::Ref and a V8 native reference\n    gc();\n    assert(!b.siblingCollected);\n    a = null;\n    gc();\n    assert(b.siblingCollected, \"full GC did not collect cycles\");\n  )\",\n      \"undefined\", \"undefined\");\n\n  // Test that minor GC can collect native objects.\n  e.expectEval(R\"(\n    let pair = makeGcDetectorPair();\n    let a = pair[0];\n    let b = pair[1];\n    pair = null;\n    a = null;\n    gc({type: \"minor\"});\n    assert(b.siblingCollected, \"minor GC did not collect native objects\");\n  )\",\n      \"undefined\", \"undefined\");\n\n  // Test that minor GC does not collect native objects whose wrappers have been \"modified\".\n  //\n  // This verifies our assumptions about how V8's EmbedderRootHandler works.\n  e.expectEval(R\"(\n    let pair = makeGcDetectorPair();\n    let a = pair[0];\n    let b = pair[1];\n    pair = null;\n    a.foo = 123;  // modify the wrapper\n    a = null;\n    gc({type: \"minor\"});\n    assert(!b.siblingCollected, \"minor GC collected modified native object\");\n  )\",\n      \"undefined\", \"undefined\");\n\n  // Test that minor GC collects a native object contained in another native object.\n  e.expectEval(R\"(\n    let pair = makeGcDetectorBoxPair();\n    let a = pair[0];\n    let b = pair[1].inner;\n    pair = null;\n    let inner = a.inner;\n    // If I don't wrap `inner.touch()` in an IIFE then `inner` doesn't get collected (even with a\n    // full GC). I guess when invoking a method on a native object, V8 ends up putting a handle on\n    // the stack which doesn't get released until the end of the function? Weird but whatever.\n    (() => {\n      assert(inner.touch());  // make sure inner wrapper is initialized\n    })();\n    inner = null;\n    a = null;\n    gc({type: \"minor\"});\n    assert(b.siblingCollected, \"minor GC did not collect transitive native objects\");\n  )\",\n      \"undefined\", \"undefined\");\n\n  // Test that minor GC can collect unreachable jsg::Value.\n  e.expectEval(R\"(\n    let pair = makeGcDetectorPair();\n    let a = pair[0];\n    let b = pair[1];\n    pair = null;\n\n    // Without the IIFE here, a hidden reference gets left on the stack or something.\n    (() => {\n      a = new ValueBox(a);\n    })();\n\n    a = null;\n\n    // We need two minor GC passes to fully collect the object. This is because the first GC pass\n    // collects the `ValueBox`, thus destroying its `jsg::Value inner` member, but V8's GC doesn't\n    // actually notice that this makes the inner object unreachable until a second pass.\n    // TODO(perf): When V8 implements \"unified young-generation\", circle back and see if we can\n    //   improved this.\n    gc({type: \"minor\"});\n    gc({type: \"minor\"});\n\n    assert(b.siblingCollected, \"minor GC did not collect jsg::Value\");\n  )\",\n      \"undefined\", \"undefined\");\n}\n\nKJ_TEST(\"TracedReference usage does not lead to crashes\") {\n  Evaluator<TraceTestContext, TraceTestIsolate> e(v8System);\n\n  e.expectEval(\n      // Create an object holding another object.\n      \"let holder = new NumberBoxHolder(new NumberBox(123));\\n\"\n\n      // Do a GC pass to make sure traced wrappers are allocated.\n      \"gc();\\n\"\n\n      // Now put the NumberBox into the context's strongRef, and remove the holder. So now\n      // the object is only reachable via a strong ref.\n      \"strongRef = holder.inner;\\n\"\n      \"holder = null;\\n\"\n\n      // Invoke GC. Since the NumberBox is not reachable via tracing from any other object, its\n      // tracedWrapper will not be marked and will therefore become invalid.\n      \"gc();\\n\"\n\n      // Now create a new holder which holds the NumberBox.\n      \"holder = new NumberBoxHolder(strongRef);\\n\"\n\n      // Invoke GC. The new holder will be traced, finding the NumberBox. It had better not crash\n      // on the tracedWrapper having been collected!\n      \"gc();\\n\"\n\n      // Verify the value is still there...\n      \"holder.inner.value\",\n      \"number\", \"123\");\n}\n\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/type-wrapper-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"jsg-test.h\"\n\nnamespace workerd::jsg::test {\nnamespace {\n\nV8System v8System;\nclass ContextGlobalObject: public Object, public ContextGlobal {};\n\n// ========================================================================================\n\nstruct TestExtensionType {\n  int32_t value;\n};\n\nstruct ExtensionContext: public ContextGlobalObject {\n  TestExtensionType toExtensionType(double value) {\n    return {static_cast<int32_t>(value)};\n  }\n  double fromExtensionType(TestExtensionType value) {\n    return value.value;\n  }\n\n  JSG_RESOURCE_TYPE(ExtensionContext) {\n    JSG_METHOD(toExtensionType);\n    JSG_METHOD(fromExtensionType);\n  }\n};\n\ntemplate <typename Self>\nclass TestExtension {\n  // Test manually extending the TypeWrapper with wrap/unwrap functions for a custom type.\n\n public:\n  static constexpr const char* getName(TestExtensionType*) {\n    return \"TestExtensionTypeName\";\n  }\n\n  v8::Local<v8::Number> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      TestExtensionType value) {\n    return v8::Number::New(js.v8Isolate, value.value);\n  }\n\n  v8::Local<v8::Context> newContext(v8::Isolate* isolate, TestExtensionType value) = delete;\n\n  kj::Maybe<TestExtensionType> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      TestExtensionType*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    return TestExtensionType{handle->Int32Value(context).ToChecked()};\n  }\n\n  template <bool isContext = false>\n  v8::Local<v8::FunctionTemplate> getTemplate(v8::Isolate* isolate, TestExtensionType*) = delete;\n};\n\nJSG_DECLARE_ISOLATE_TYPE(ExtensionIsolate, ExtensionContext, TypeWrapperExtension<TestExtension>);\n\nKJ_TEST(\"extensions\") {\n  Evaluator<ExtensionContext, ExtensionIsolate> e(v8System);\n  e.expectEval(\"fromExtensionType(toExtensionType(12.3))\", \"number\", \"12\");\n}\n\n// ========================================================================================\n\nstruct TypeHandlerContext: public ContextGlobalObject {\n  v8::Local<v8::Value> newNumberBox(\n      jsg::Lock& js, double value, const TypeHandler<Ref<NumberBox>>& handler) {\n    return handler.wrap(js, js.alloc<NumberBox>(value));\n  }\n  double openNumberBox(\n      jsg::Lock& js, v8::Local<v8::Value> handle, const TypeHandler<Ref<NumberBox>>& handler) {\n    return KJ_REQUIRE_NONNULL(handler.tryUnwrap(js, handle))->value;\n  }\n  v8::Local<v8::Value> wrapNumber(jsg::Lock& js, double value, const TypeHandler<double>& handler) {\n    return handler.wrap(js, value);\n  }\n  double unwrapNumber(\n      jsg::Lock& js, v8::Local<v8::Value> handle, const TypeHandler<double>& handler) {\n    return KJ_REQUIRE_NONNULL(handler.tryUnwrap(js, handle));\n  }\n\n  JSG_RESOURCE_TYPE(TypeHandlerContext) {\n    JSG_NESTED_TYPE(NumberBox);\n    JSG_METHOD(newNumberBox);\n    JSG_METHOD(openNumberBox);\n    JSG_METHOD(wrapNumber);\n    JSG_METHOD(unwrapNumber);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(TypeHandlerIsolate, TypeHandlerContext, NumberBox);\n\nKJ_TEST(\"type handlers\") {\n  Evaluator<TypeHandlerContext, TypeHandlerIsolate> e(v8System);\n  e.expectEval(\"newNumberBox(123).value\", \"number\", \"123\");\n  e.expectEval(\"openNumberBox(new NumberBox(123))\", \"number\", \"123\");\n  e.expectEval(\"wrapNumber(123)\", \"number\", \"123\");\n  e.expectEval(\"unwrapNumber(123)\", \"number\", \"123\");\n  e.expectEval(\"newNumberBox(789).boxedFromTypeHandler.value\", \"number\", \"789\");\n}\n\n// ========================================================================================\n\nstruct ArrayContext: public ContextGlobalObject {\n  double sumArray(kj::Array<double> array) {\n    double result = 0;\n    for (auto d: array) result += d;\n    return result;\n  }\n  kj::Array<double> returnArray(double dlength) {\n    size_t length = dlength;\n    auto builder = kj::heapArrayBuilder<double>(length);\n    for (uint i: kj::zeroTo(length)) builder.add(i);\n    return builder.finish();\n  }\n  kj::ArrayPtr<const double> returnArrayPtr() {\n    static const double VALUES[3] = {123, 456, 789};\n    return VALUES;\n  }\n  JSG_RESOURCE_TYPE(ArrayContext) {\n    JSG_METHOD(sumArray);\n    JSG_METHOD(returnArray);\n    JSG_METHOD(returnArrayPtr);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(ArrayIsolate, ArrayContext);\n\nKJ_TEST(\"arrays\") {\n  Evaluator<ArrayContext, ArrayIsolate> e(v8System);\n  e.expectEval(\"sumArray([123, 321, 33])\", \"number\", \"477\");\n  e.expectEval(\"returnArray(3).join(', ')\", \"string\", \"0, 1, 2\");\n  e.expectEval(\"returnArrayPtr(3).join(', ')\", \"string\", \"123, 456, 789\");\n\n  e.expectEval(\"sumArray([123, {}, 321])\", \"number\", \"NaN\");\n}\n\n// ========================================================================================\n\nstruct Uint8Context: public ContextGlobalObject {\n  kj::Array<byte> encodeUtf8(kj::String str) {\n    return kj::heapArray(str.asBytes());\n  }\n  kj::String decodeUtf8(kj::Array<byte> data) {\n    return kj::str(data.asChars());\n  }\n  kj::String decodeUtf8Const(kj::Array<const byte> data) {\n    return kj::str(data.asChars());\n  }\n  JSG_RESOURCE_TYPE(Uint8Context) {\n    JSG_METHOD(encodeUtf8);\n    JSG_METHOD(decodeUtf8);\n    JSG_METHOD(decodeUtf8Const);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(Uint8Isolate, Uint8Context);\n\nKJ_TEST(\"Uint8Arrays\") {\n  Evaluator<Uint8Context, Uint8Isolate> e(v8System);\n  uint byteSequence[] = {'f', 'o', 'o', ' ', 0xf0, 0x9f, 0x98, 0xba};\n  auto byteSequenceStr = kj::strArray(kj::ArrayPtr<uint>(byteSequence), \", \");\n\n  e.expectEval(\"new Uint8Array(encodeUtf8('foo 😺')).join(', ')\", \"string\", byteSequenceStr);\n  e.expectEval(kj::str(\"decodeUtf8(new Uint8Array([\", byteSequenceStr, \"]))\"), \"string\", \"foo 😺\");\n  e.expectEval(\n      kj::str(\"decodeUtf8(new Uint8Array([\", byteSequenceStr, \"]).buffer)\"), \"string\", \"foo 😺\");\n  e.expectEval(kj::str(\"var buf = new Uint8Array([\", byteSequenceStr,\n                   \"]).buffer;\\n\"\n                   \"decodeUtf8(new Uint8Array(buf, 1, 3))\"),\n      \"string\", \"oo \");\n\n  e.expectEval(\n      kj::str(\"decodeUtf8Const(new Uint8Array([\", byteSequenceStr, \"]))\"), \"string\", \"foo 😺\");\n}\n\n// ========================================================================================\n\nstruct UnwrappingContext: public ContextGlobalObject {\n  v8::Local<v8::ArrayBufferView> mutateArrayBufferView(v8::Local<v8::ArrayBufferView> value) {\n    if (value->ByteLength() > 0) {\n      auto backing = value->Buffer()->GetBackingStore();\n      *static_cast<kj::byte*>(backing->Data()) = 123;\n    }\n    return value;\n  }\n  JSG_RESOURCE_TYPE(UnwrappingContext) {\n    JSG_METHOD(mutateArrayBufferView);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(UnwrappingIsolate, UnwrappingContext);\n\nKJ_TEST(\"v8::Value subclass unwrapping\") {\n  Evaluator<UnwrappingContext, UnwrappingIsolate> e(v8System);\n  e.expectEval(\"let abv = new Uint8Array([0, 1, 2]);\\n\"\n               \"let abv2 = mutateArrayBufferView(abv);\\n\"\n               \"abv === abv2 && abv[0] === 123\",\n      \"boolean\", \"true\");\n}\n\n// ========================================================================================\n\nstruct UnimplementedContext: public ContextGlobalObject {\n  class UnimplementedConstructor: public Object {\n   public:\n    static Unimplemented constructor() {\n      return Unimplemented();\n    }\n    JSG_RESOURCE_TYPE(UnimplementedConstructor) {}\n  };\n  class UnimplementedConstructorParam: public Object {\n   public:\n    UnimplementedConstructorParam(int i): i(i) {}\n    static Ref<UnimplementedConstructorParam> constructor(jsg::Lock& js, int i, Unimplemented) {\n      return js.alloc<UnimplementedConstructorParam>(i);\n    }\n    int getI() {\n      return i;\n    }\n    int i;\n    JSG_RESOURCE_TYPE(UnimplementedConstructorParam) {\n      JSG_READONLY_INSTANCE_PROPERTY(i, getI);\n    }\n  };\n  Unimplemented unimplementedMethod() {\n    return Unimplemented();\n  }\n  int unimplementedParam(int i, Unimplemented) {\n    return i;\n  }\n  Unimplemented getUnimplemented() {\n    return Unimplemented();\n  }\n  void setUnimplemented(Unimplemented) {}\n  struct UnimplementedField {\n    int i;\n    Unimplemented unimplemented;\n    JSG_STRUCT(i, unimplemented);\n  };\n  int unimplementedField(UnimplementedField s) {\n    return s.i;\n  }\n  auto unimplementedCallbackArgument() {\n    return [](Lock&, int i, Unimplemented) { return i; };\n  }\n\n  class UnimplementedProperties: public Object {\n   public:\n    static Ref<UnimplementedProperties> constructor(jsg::Lock& js) {\n      return js.alloc<UnimplementedProperties>();\n    }\n\n    int getNumber() {\n      return 123;\n    }\n\n    Unimplemented getUnimplemented1() {\n      return Unimplemented();\n    }\n    void setUnimplemented1(Unimplemented) {}\n    Unimplemented getUnimplemented2() {\n      return Unimplemented();\n    }\n\n    int implementedMethod() {\n      return 123;\n    }\n    Unimplemented unimplementedMethod() {\n      return Unimplemented();\n    }\n\n    JSG_RESOURCE_TYPE(UnimplementedProperties) {\n      JSG_READONLY_INSTANCE_PROPERTY(number, getNumber);\n      JSG_INSTANCE_PROPERTY(unimplemented1, getUnimplemented1, setUnimplemented1);\n      JSG_READONLY_INSTANCE_PROPERTY(unimplemented2, getUnimplemented2);\n      JSG_METHOD(implementedMethod);\n      JSG_METHOD(unimplementedMethod);\n    }\n  };\n\n  struct StructWithUnimplementedMembers {\n    Optional<kj::String> optionalString;\n    Unimplemented unimplementedMember;\n    WontImplement wontImplementMember;\n    JSG_STRUCT(optionalString, unimplementedMember, wontImplementMember);\n  };\n\n  void takeStructWithUnimplementedMembers(StructWithUnimplementedMembers) {}\n\n  JSG_RESOURCE_TYPE(UnimplementedContext) {\n    JSG_NESTED_TYPE(UnimplementedConstructor);\n    JSG_NESTED_TYPE(UnimplementedConstructorParam);\n    JSG_METHOD(unimplementedMethod);\n    JSG_METHOD(unimplementedParam);\n    JSG_INSTANCE_PROPERTY(unimplemented, getUnimplemented, setUnimplemented);\n    JSG_METHOD(unimplementedField);\n    JSG_METHOD(unimplementedCallbackArgument);\n    JSG_NESTED_TYPE(UnimplementedProperties);\n    JSG_METHOD(takeStructWithUnimplementedMembers);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(UnimplementedIsolate,\n    UnimplementedContext,\n    UnimplementedContext::UnimplementedConstructor,\n    UnimplementedContext::UnimplementedConstructorParam,\n    UnimplementedContext::UnimplementedField,\n    UnimplementedContext::UnimplementedProperties,\n    UnimplementedContext::StructWithUnimplementedMembers);\n\nKJ_TEST(\"unimplemented errors\") {\n  Evaluator<UnimplementedContext, UnimplementedIsolate> e(v8System);\n  e.expectEval(\"new UnimplementedConstructor()\", \"throws\",\n      \"Error: Failed to construct 'UnimplementedConstructor': \"\n      \"the constructor is not implemented.\");\n\n  e.expectEval(\"new UnimplementedConstructorParam(123).i\", \"number\", \"123\");\n  e.expectEval(\"new UnimplementedConstructorParam(123, 456)\", \"throws\",\n      \"Error: Failed to construct 'UnimplementedConstructorParam': \"\n      \"constructor parameter 2 is not implemented.\");\n\n  e.expectEval(\"unimplementedMethod()\", \"throws\",\n      \"Error: Failed to execute 'unimplementedMethod' on 'UnimplementedContext': \"\n      \"the method is not implemented.\");\n\n  e.expectEval(\"unimplementedParam(123)\", \"number\", \"123\");\n  e.expectEval(\"unimplementedParam(123, 456)\", \"throws\",\n      \"Error: Failed to execute 'unimplementedParam' on 'UnimplementedContext': \"\n      \"parameter 2 is not implemented.\");\n\n  e.expectEval(\"unimplemented\", \"throws\",\n      \"Error: Failed to get the 'unimplemented' property on 'UnimplementedContext': \"\n      \"the property is not implemented.\");\n  e.expectEval(\"unimplemented = 123\", \"throws\",\n      \"Error: Failed to set the 'unimplemented' property on 'UnimplementedContext': \"\n      \"the ability to set this property is not implemented.\");\n\n  e.expectEval(\"unimplementedField({i: 123})\", \"number\", \"123\");\n  e.expectEval(\"unimplementedField({i: 123, unimplemented: 456})\", \"throws\",\n      \"Error: The 'unimplemented' field on 'UnimplementedField' is not implemented.\");\n\n  e.expectEval(\"unimplementedCallbackArgument()(123)\", \"number\", \"123\");\n  e.expectEval(\"unimplementedCallbackArgument()(123, 456)\", \"throws\",\n      \"Error: Failed to execute function: parameter 2 is not implemented.\");\n\n  // Verify that unimplemented properties are not enumerable by attempting to JSON-encode a class\n  // that has them. If they are enumerable, the encoder will try to access them and throw\n  // exceptions.\n  e.expectEval(\"JSON.stringify(new UnimplementedProperties)\", \"string\", \"{\\\"number\\\":123}\");\n\n  // Verify that structs with unimplemented/wont-implement members can still be initialized from\n  // null/undefined values.\n  e.expectEval(\"takeStructWithUnimplementedMembers(null)\", \"undefined\", \"undefined\");\n  e.expectEval(\"takeStructWithUnimplementedMembers(undefined)\", \"undefined\", \"undefined\");\n}\n\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/type-wrapper.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// INTERNAL IMPLEMENTATION FILE\n//\n// The TypeWrapper knows how to convert a variety of types between C++ and JavaScript.\n\n#include <workerd/jsg/buffersource.h>\n#include <workerd/jsg/dom-exception.h>\n#include <workerd/jsg/function.h>\n#include <workerd/jsg/iterator.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/jsvalue.h>\n#include <workerd/jsg/resource.h>\n#include <workerd/jsg/struct.h>\n#include <workerd/jsg/util.h>\n#include <workerd/jsg/value.h>\n#include <workerd/jsg/web-idl.h>\n#include <workerd/jsg/wrappable.h>\n\n#include <v8-wasm.h>\n\nnamespace workerd::jsg {\n\n// True if there is an unwrap() overload which does *not* take a v8::Value to unwrap for this\n// parameter type T. This is useful to identify types like TypeHandlers and v8::Isolate* which\n// functions can declare they accept at the end of their parameter list, but which are not created\n// from any particular JS value.\n// A concept that identifies types that can be unwrapped without needing a JS value\ntemplate <typename TypeWrapper, typename T>\nconcept ValueLessParameter =\n    requires(TypeWrapper wrapper, Lock& js, v8::Local<v8::Context> context, T* ptr) {\n      wrapper.unwrap(js, context, ptr);\n    };\n\n// =======================================================================================\n// RequiredArgCount_ specialization — counts leading required JS-visible arguments.\n//\n// This completes the definition of requiredArgumentCount<TypeWrapper, T> declared in meta.h.\n// It lives here (rather than meta.h or web-idl.h) because it needs the ValueLessParameter\n// concept above to automatically detect ALL injected parameter types — TypeHandler<T>,\n// InjectConfiguration<T> (e.g. CompatibilityFlags::Reader), and any future valueless types.\n//\n// Arguments<T> (variadic rest args) is detected separately via isArguments<>() because it\n// does not satisfy ValueLessParameter — it consumes remaining JS arguments rather than being\n// injected by the runtime.\n//\n// Template instantiation of requiredArgumentCount happens from resource.h templates, which\n// are only instantiated after this header is fully parsed, so these specializations are\n// guaranteed to be visible at the point of use.\nnamespace detail {\n\ntemplate <typename TypeWrapper>\nstruct RequiredArgCount_<TypeWrapper, TypeList<>> {\n  static constexpr int value = 0;\n};\n\ntemplate <typename TypeWrapper, typename Head, typename... Tail>\nstruct RequiredArgCount_<TypeWrapper, TypeList<Head, Tail...>> {\n  using D = kj::Decay<Head>;\n  static constexpr int value = (isArguments<D>() || ValueLessParameter<TypeWrapper, D>)\n      ? RequiredArgCount_<TypeWrapper, TypeList<Tail...>>::value  // skip injected args\n      : (webidl::isOptional<D> ? 0  // optional arg — stop counting required\n                               : 1 + RequiredArgCount_<TypeWrapper, TypeList<Tail...>>::value);\n};\n\n}  // namespace detail\n\n// TypeWrapper mixin for V8 handles.\n//\n// This is just a trivial pass-through.\nclass V8HandleWrapper {\n public:\n  template <V8Value T>\n  static constexpr const std::type_info& getName(v8::Local<T>*) {\n    return typeid(T);\n  }\n\n  template <V8Value T>\n  v8::Local<T> wrap(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      v8::Local<T> value) {\n    return value;\n  }\n\n  kj::Maybe<v8::Local<v8::Value>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      v8::Local<v8::Value>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    return handle;\n  }\n\n#define JSG_FOR_EACH_V8_VALUE_SUBCLASS(f)                                                          \\\n  f(ArrayBuffer) f(ArrayBufferView) f(TypedArray) f(DataView) f(Int8Array) f(Uint8Array)           \\\n      f(Uint8ClampedArray) f(Int16Array) f(Uint16Array) f(Int32Array) f(Uint32Array)               \\\n          f(Float16Array) f(Float32Array) f(Float64Array) f(Object) f(String) f(Function)          \\\n              f(WasmMemoryObject) f(BigInt)\n\n  // Define a tryUnwrap() overload for each interesting subclass of v8::Value.\n#define JSG_DEFINE_TRY_UNWRAP(type)                                                                \\\n  kj::Maybe<v8::Local<v8::type>> tryUnwrap(jsg::Lock& js, v8::Local<v8::Context> context,          \\\n      v8::Local<v8::Value> handle, v8::Local<v8::type>*,                                           \\\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {                                             \\\n    if (handle->Is##type()) {                                                                      \\\n      return handle.As<v8::type>();                                                                \\\n    }                                                                                              \\\n    return kj::none;                                                                               \\\n  }                                                                                                \\\n                                                                                                   \\\n  kj::Maybe<v8::Global<v8::type>> tryUnwrap(jsg::Lock& js, v8::Local<v8::Context> context,         \\\n      v8::Local<v8::Value> handle, v8::Global<v8::type>*,                                          \\\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {                                             \\\n    if (handle->Is##type()) {                                                                      \\\n      return v8::Global<v8::type>(js.v8Isolate, handle.As<v8::type>());                            \\\n    }                                                                                              \\\n    return kj::none;                                                                               \\\n  }                                                                                                \\\n                                                                                                   \\\n  kj::Maybe<V8Ref<v8::type>> tryUnwrap(jsg::Lock& js, v8::Local<v8::Context> context,              \\\n      v8::Local<v8::Value> handle, V8Ref<v8::type>*,                                               \\\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {                                             \\\n    if (handle->Is##type()) {                                                                      \\\n      return V8Ref<v8::type>(js.v8Isolate, handle.As<v8::type>());                                 \\\n    }                                                                                              \\\n    return kj::none;                                                                               \\\n  }                                                                                                \\\n  template <typename T = v8::type, typename = decltype(&T::GetIdentityHash)>                       \\\n  kj::Maybe<HashableV8Ref<T>> tryUnwrap(jsg::Lock& js, v8::Local<v8::Context> context,             \\\n      v8::Local<v8::Value> handle, HashableV8Ref<v8::type>*,                                       \\\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {                                             \\\n    if (handle->Is##type()) {                                                                      \\\n      return HashableV8Ref<v8::type>(js.v8Isolate, handle.As<v8::type>());                         \\\n    }                                                                                              \\\n    return kj::none;                                                                               \\\n  }\n\n  JSG_FOR_EACH_V8_VALUE_SUBCLASS(JSG_DEFINE_TRY_UNWRAP)\n\n#undef JSG_DEFINE_TRY_UNWRAP\n#undef JSG_FOR_EACH_V8_VALUE_SUBCLASS\n\n  template <V8Value T>\n  static constexpr const std::type_info& getName(v8::Global<T>*) {\n    return typeid(T);\n  }\n\n  template <V8Value T>\n  v8::Local<T> wrap(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      v8::Global<T> value) {\n    return value.Get(js.v8Isolate);\n  }\n\n  kj::Maybe<v8::Global<v8::Value>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      v8::Global<v8::Value>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    return v8::Global<v8::Value>(js.v8Isolate, handle);\n  }\n\n  template <V8Value T>\n  static constexpr const std::type_info& getName(V8Ref<T>*) {\n    return typeid(T);\n  }\n\n  template <V8Value T>\n  v8::Local<T> wrap(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      V8Ref<T> value) {\n    return value.getHandle(js.v8Isolate);\n  }\n\n  kj::Maybe<V8Ref<v8::Value>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      V8Ref<v8::Value>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    return V8Ref<v8::Value>(js.v8Isolate, handle);\n  }\n};\n\nclass UnimplementedWrapper {\n public:\n  static constexpr const std::type_info& getName(Unimplemented*) {\n    return typeid(Unimplemented);\n  }\n\n  v8::Local<v8::Value> wrap(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      Unimplemented value) = delete;\n  kj::Maybe<Unimplemented> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      Unimplemented*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    // Can only be `undefined`.\n    if (handle->IsUndefined()) {\n      return Unimplemented();\n    } else {\n      return kj::none;\n    }\n  }\n};\n\n// The application can use this type to extend TypeWrapper with its own custom mixins. The\n// template `Extension` is a mixin which will be inherited by the TypeWrapper. It will be passed\n// the full TypeWrapper specialization as a type parameter. See TypeWrapper, below, for an\n// explanation of the mixin design and use of CRTP.\n//\n// Specify `TypeWrapperExtension` in the same list as your API types. Example:\n//\n//     template <typename TypeWrapper>\n//     class MyMixin {\n//     public:\n//       // ... implementation ...\n//     };\n//\n//     JSG_DECLARE_ISOLATE_TYPE(MyIsolate, MyApiType1, MyApiType2,\n//         jsg::TypeWrapperExtension<MyMixin>, ...)\n//\n// The extension mixin must declare the following methods:\n//\n//     static constexpr const char* getName(T* dummy);\n//     v8::Local<v8::Value> wrap(jsg::Lock& js, v8::Local<v8::Context> jsContext,\n//                               kj::Maybe<v8::Local<v8::Object>> creator,\n//                               T cppValue);\n//     kj::Maybe<T> tryUnwrap(Lock& js, v8::Local<v8::Context> jsContext, v8::Local<v8::Value> jsHandle,\n//                            T* dummy, kj::Maybe<v8::Local<v8::Object>> parentObject);\n//\n//     Ref<T, v8::Context> newContext(v8::Isolate* isolate, T* dummy, Args&&... args);\n//     template <bool isContext = false>\n//     v8::Local<v8::FunctionTemplate> getTemplate(v8::Isolate* isolate, T*)\n//\n// Note that most mixins do not actually need the last two methods. Unfortunately, due to\n// limitation of the C++ `using` directive, we can't easily make these optional. You can,\n// however, declare them deleted, like:\n//\n//     void newContext() = delete;\n//     void getTemplate() = delete;\n//\n// The mixin's constructor can optionally accept a configuration value as its parameter, which\n// works the same way as the second parameter to `JSG_RESOURCE_TYPE`.\ntemplate <template <typename TypeWrapper> typename Extension>\nclass TypeWrapperExtension {\n public:\n  static const JsgKind JSG_KIND = JsgKind::EXTENSION;\n};\n\n// Include this type in the FFI type list to implement auto-injection of a parameter type based\n// on configuration. `Configuration` must be a type that can be constructed from the isolate's\n// meta configuration object. Wrapped functions will be able to accept `Configuration` as a\n// parameter type, and instead of being converted from a JavaScript parameter, it will instead\n// receive the isolate-global configuration.\n//\n// `Configuration` can be a reference type.\ntemplate <typename Configuration>\nclass InjectConfiguration {\n public:\n  static const JsgKind JSG_KIND = JsgKind::EXTENSION;\n};\n\n// Selects the appropriate mixin to support wrapping/unwrapping type T, which is one of the API\n// types passed to JSG_DECLARE_ISOLATE_TYPE() by the application.\ntemplate <typename Self, typename T, JsgKind kind = T::JSG_KIND>\nclass TypeWrapperBase;\n\n// Specialization of TypeWrapperBase for types that have a JSG_RESOURCE_TYPE block.\ntemplate <typename Self, typename T>\nclass TypeWrapperBase<Self, T, JsgKind::RESOURCE>: public ResourceWrapper<Self, T> {\n public:\n  template <typename MetaConfiguration>\n  TypeWrapperBase(MetaConfiguration& config): ResourceWrapper<Self, T>(config) {}\n\n  void unwrap() = delete;  // ResourceWrapper only implements tryUnwrap(), not unwrap()\n};\n\n// Specialization of TypeWrapperBase for types that have a JSG_STRUCT block.\ntemplate <typename Self, typename T>\nclass TypeWrapperBase<Self, T, JsgKind::STRUCT>\n    : public StructWrapper<Self, T, typename T::template JsgFieldWrappers<Self, T>> {\n public:\n  template <typename MetaConfiguration>\n  TypeWrapperBase(MetaConfiguration& config) {}\n\n  inline void initTypeWrapper() {}\n\n  void unwrap() = delete;  // StructWrapper only implements tryUnwrap(), not unwrap()\n};\n\n// Specialization of TypeWrapperBase for TypeWrapperExtension.\ntemplate <typename Self, template <typename> typename Extension>\nclass TypeWrapperBase<Self, TypeWrapperExtension<Extension>, JsgKind::EXTENSION>\n    : public Extension<Self> {\n  template <typename MetaConfiguration>\n  static constexpr bool sfinae(decltype(Extension<Self>(kj::instance<MetaConfiguration&>()))*) {\n    return true;  // extension constructor takes configuration argument\n  }\n  template <typename MetaConfiguration>\n  static constexpr bool sfinae(...) {\n    return false;  // extension constructor does not take arguments\n  }\n\n public:\n  template <typename MetaConfiguration,\n      typename = kj::EnableIf<!sfinae<MetaConfiguration>(static_cast<Extension<Self>*>(nullptr))>>\n  TypeWrapperBase(MetaConfiguration& config) {}\n\n  template <typename MetaConfiguration,\n      typename = kj::EnableIf<sfinae<MetaConfiguration>(static_cast<Extension<Self>*>(nullptr))>>\n  TypeWrapperBase(MetaConfiguration& config, bool = false): Extension<Self>(config) {}\n\n  void unwrap() = delete;  // extensions only implement tryUnwrap(), not unwrap()\n\n  inline void initTypeWrapper() {}\n};\n\n// Specialization of TypeWrapperBase for InjectConfiguration.\ntemplate <typename Self, typename Configuration>\nclass TypeWrapperBase<Self, InjectConfiguration<Configuration>, JsgKind::EXTENSION> {\n public:\n  template <typename MetaConfiguration>\n  TypeWrapperBase(MetaConfiguration& config): configuration(kj::fwd<MetaConfiguration>(config)) {}\n\n  static constexpr const char* getName(kj::Decay<Configuration>*) {\n    return \"Configuration\";\n  }\n\n  Configuration unwrap(Lock& js, v8::Local<v8::Context> context, Configuration*) {\n    return configuration;\n  }\n\n  void tryUnwrap() = delete;\n  void wrap() = delete;\n  void newContext() = delete;\n  void getTemplate() = delete;\n\n  inline void initTypeWrapper() {}\n\n private:\n  Configuration configuration;\n};\n\n// The TypeWrapper class aggregates functionality to convert between C++ values and JavaScript\n// values. It primarily implements two methods:\n//\n//     v8::Local<v8::Value> wrap(v8::Local<v8::Context> jsContext,\n//                               kj::Maybe<v8::Local<v8::Object>> creator\n//                               T cppValue);\n//     // Converts cppValue to JavaScript.\n//     //\n//     // `creator` is non-null when converting the return value of a method; in this case,\n//     // `creator` is the object on which the method was called. This is useful for some types\n//     // (like Promises) where the KJ convention is to assume that the creator must outlive the\n//     // returned object.\n//\n//     T unwrap<T>(v8::Local<v8::Context> jsContext, v8::Local<v8::Value> jsHandle);\n//     // Converts jsValue to C++, expecting type T.\n//\n// The design is based on mixins: TypeWrapper derives from classes that handle each individual\n// type. Each mixin is expected to implement the following methods:\n//\n//     static constexpr const char* getName(T* dummy);\n//     // Return the name of the type for the purpose of TypeError exception messages. Note that\n//     // you can also return `const std::type_info&` here, in which case the type name will\n//     // be derived by stripping off the namespace from the C++ type name.\n//\n//     v8::Local<v8::Value> wrap(v8::Local<v8::Context> jsContext,\n//                               kj::Maybe<v8::Local<v8::Object>> creator,\n//                               T cppValue);\n//     // Converts cppValue to JavaScript.\n//\n//     kj::Maybe<T> tryUnwrap(Lock& js, v8::Local<v8::Context> jsContext,\n//                            v8::Local<v8::Value> jsHandle, T* dummy,\n//                            kj::Maybe<v8::Local<v8::Object>> parentObject);\n//     // Converts jsValue to C++, expecting type T. If the input is not of type T, returns\n//     // null. If we're unwrapping a field of an object, then `parentObject` is the handle to\n//     // the object; this is useful when unwrapping a function, to bind `this`.\n//     //\n//     // Note that only a shallow type check is performed. E.g. if a struct type is expected,\n//     // tryUnwrap() will only return null if the input is not a JS Object. If it is an object,\n//     // but one of its fields is the wrong type, tryUnwrap() will throw a TypeError. The idea\n//     // here is that `tryUnwrap()` should only do the amount of type checking that one would\n//     // typically do in JavaScript to distinguish a variant type (e.g. \"string or number\").\n//     // Typically this is limited to what you can do with the `typeof` and `instanceof`\n//     // keywords on the top-level value.\n//\n// Note the `dummy` parameters of type T*. These will always be passed `nullptr`. The purpose of\n// these parameters is to select the correct overload for the desired type. Normally, one would\n// use an explicit template parameter for this, but that only works if all the methods are\n// actually specializations of the same template method declaration. That's not the case here,\n// because we're inheriting totally independent method declarations from all our mixins. So, we\n// have to slum it by passing `(T*)nullptr` as an argument purely for overload selection.\n//\n// Note that many of these mixins need to call back to the TypeWrapper recursively. For example,\n// OptionalWrapper (for Optional<T>) will need to call back to unwrap the inner T. To that end,\n// we use the Curiously Recurring Template Pattern, passing the TypeWrapper type itself to its\n// superclasses, so that they can cast themselves back to the subclass type and call it\n// recursively. See:\n//\n//     https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern\n//\n// Actually, TypeWrapper itself *also* takes itself as a template parameter called `Self`. This\n// is primarily done as a trick in order to make compiler error messages less difficult to read.\n// The `Self` parameter to `TypeWrapper` is actually a specific subclass of TypeWrapper. See\n// JSG_DECLARE_ISOLATE_TYPE in setup.h.\n//\n// Note that a pointer to the TypeWrapper object is stored in the V8 context's \"embedder data\",\n// in slot 1, so that we can get back to it from V8 callbacks.\ntemplate <typename Self, typename... T>\nclass TypeWrapper: public DynamicResourceTypeMap<Self>,\n                   public TypeWrapperBase<Self, T>...,\n                   public PrimitiveWrapper,\n                   public NameWrapper,\n                   public StringWrapper,\n                   public OptionalWrapper<Self>,\n                   public LenientOptionalWrapper<Self>,\n                   public MaybeWrapper<Self>,\n                   public OneOfWrapper<Self>,\n                   public ArrayWrapper<Self>,\n                   public SetWrapper<Self>,\n                   public SequenceWrapper<Self>,\n                   public GeneratorWrapper<Self>,\n                   public ArrayBufferWrapper,\n                   public DictWrapper<Self>,\n                   public DateWrapper,\n                   public BufferSourceWrapper,\n                   public FunctionWrapper<Self>,\n                   public PromiseWrapper<Self>,\n                   public NonCoercibleWrapper<Self>,\n                   public MemoizedIdentityWrapper<Self>,\n                   public IdentifiedWrapper<Self>,\n                   public SelfRefWrapper,\n                   public ExceptionWrapper<Self>,\n                   public ObjectWrapper<Self>,\n                   public V8HandleWrapper,\n                   public UnimplementedWrapper,\n                   public JsValueWrapper {\n  // TODO(soon): Should the TypeWrapper object be stored on the isolate rather than the context?\n public:\n  template <typename MetaConfiguration>\n  TypeWrapper(v8::Isolate* isolate, MetaConfiguration&& configuration)\n      : TypeWrapperBase<Self, T>(configuration)...,\n        MaybeWrapper<Self>(configuration),\n        GeneratorWrapper<Self>(configuration),\n        PromiseWrapper<Self>(configuration),\n        config(getConfig(configuration)) {\n    isolate->SetData(SET_DATA_TYPE_WRAPPER, this);\n  }\n  KJ_DISALLOW_COPY_AND_MOVE(TypeWrapper);\n\n  void initTypeWrapper() {\n    (TypeWrapperBase<Self, T>::initTypeWrapper(), ...);\n  }\n\n  static TypeWrapper& from(v8::Isolate* isolate) {\n    return *reinterpret_cast<TypeWrapper*>(isolate->GetData(SET_DATA_TYPE_WRAPPER));\n  }\n\n  bool isFastApiEnabled() const {\n    return config.fastApiEnabled;\n  }\n\n  using TypeWrapperBase<Self, T>::getName...;\n  using TypeWrapperBase<Self, T>::wrap...;\n  using TypeWrapperBase<Self, T>::newContext...;\n  using TypeWrapperBase<Self, T>::unwrap...;\n  using TypeWrapperBase<Self, T>::tryUnwrap...;\n  using TypeWrapperBase<Self, T>::getTemplate...;\n\n#define USING_WRAPPER(Name)                                                                        \\\n  using Name::getName;                                                                             \\\n  using Name::wrap;                                                                                \\\n  using Name::tryUnwrap\n\n  USING_WRAPPER(PrimitiveWrapper);\n  USING_WRAPPER(NameWrapper);\n  USING_WRAPPER(StringWrapper);\n  USING_WRAPPER(OptionalWrapper<Self>);\n  USING_WRAPPER(LenientOptionalWrapper<Self>);\n  USING_WRAPPER(MaybeWrapper<Self>);\n  USING_WRAPPER(OneOfWrapper<Self>);\n  USING_WRAPPER(ArrayWrapper<Self>);\n  USING_WRAPPER(SetWrapper<Self>);\n  USING_WRAPPER(SequenceWrapper<Self>);\n  USING_WRAPPER(GeneratorWrapper<Self>);\n  USING_WRAPPER(ArrayBufferWrapper);\n  USING_WRAPPER(DictWrapper<Self>);\n  USING_WRAPPER(DateWrapper);\n  USING_WRAPPER(BufferSourceWrapper);\n  USING_WRAPPER(FunctionWrapper<Self>);\n  USING_WRAPPER(PromiseWrapper<Self>);\n  USING_WRAPPER(NonCoercibleWrapper<Self>);\n  USING_WRAPPER(MemoizedIdentityWrapper<Self>);\n  USING_WRAPPER(IdentifiedWrapper<Self>);\n  USING_WRAPPER(SelfRefWrapper);\n  USING_WRAPPER(ExceptionWrapper<Self>);\n  USING_WRAPPER(ObjectWrapper<Self>);\n  USING_WRAPPER(V8HandleWrapper);\n  USING_WRAPPER(UnimplementedWrapper);\n  USING_WRAPPER(JsValueWrapper);\n#undef USING_WRAPPER\n\n  template <typename U>\n  class TypeHandlerImpl;\n\n  template <typename U>\n  static constexpr TypeHandlerImpl<U> TYPE_HANDLER_INSTANCE = TypeHandlerImpl<U>();\n\n  template <typename U>\n  static constexpr const char* getName(TypeHandler<U>*) {\n    return \"TypeHandler\";\n  }\n\n  template <typename U>\n  const TypeHandler<U>& unwrap(Lock& js, v8::Local<v8::Context>, TypeHandler<U>*) {\n    // if you're here because of compiler error template garbage, you forgot to register\n    // a type with JSG_DECLARE_ISOLATE_TYPE\n    return TYPE_HANDLER_INSTANCE<U>;\n  }\n\n  template <typename U>\n  kj::Maybe<const TypeHandler<U>&> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      TypeHandler<U>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    // TypeHandler is not a value that needs to be unwrapped from JS\n    return TYPE_HANDLER_INSTANCE<U>;\n  }\n\n  template <typename U>\n  auto unwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      TypeErrorContext errorContext,\n      kj::Maybe<v8::Local<v8::Object>> parentObject = kj::none) -> RemoveRvalueRef<U> {\n    auto maybe =\n        this->tryUnwrap(js, context, handle, static_cast<kj::Decay<U>*>(nullptr), parentObject);\n    KJ_IF_SOME(result, maybe) {\n      return kj::fwd<RemoveMaybe<decltype(maybe)>>(result);\n    } else {\n      throwTypeError(\n          js.v8Isolate, errorContext, TypeWrapper::getName(static_cast<kj::Decay<U>*>(nullptr)));\n    }\n  }\n\n  template <typename U, FastApiPrimitive A>\n  auto unwrapFastApi(\n      jsg::Lock& js, v8::Local<v8::Context> context, A& arg, TypeErrorContext errorContext) -> A {\n    return arg;\n  }\n\n  template <typename U>\n  auto unwrapFastApi(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value>& arg,\n      TypeErrorContext errorContext) -> RemoveRvalueRef<U> {\n    return unwrap<U>(js, context, arg, errorContext);\n  }\n\n  // Helper for unwrapping function/method arguments correctly. Specifically, we need logic to\n  // handle the case where the user passes in fewer arguments than the function has parameters.\n  template <typename U>\n  auto unwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      const v8::FunctionCallbackInfo<v8::Value>& args,\n      size_t parameterIndex,\n      TypeErrorContext errorContext) -> RemoveRvalueRef<U> {\n    using V = kj::Decay<U>;\n\n    if constexpr (isArguments<V>()) {\n      using E = V::ElementType;\n      size_t size = args.Length() >= parameterIndex ? args.Length() - parameterIndex : 0;\n      auto builder = kj::heapArrayBuilder<E>(size);\n      for (size_t i = parameterIndex; i < args.Length(); i++) {\n        builder.add(unwrap<E>(js, context, args[i], errorContext));\n      }\n      return builder.finish();\n    } else if constexpr (ValueLessParameter<Self, V>) {\n      // C++ parameters which don't unwrap JS values, like TypeHandlers or v8::FunctionCallbackInfo.\n      return unwrap(js, context, static_cast<V*>(nullptr));\n    } else {\n      if constexpr (!webidl::OptionalType<V> && !kj::isSameType<V, Unimplemented>()) {\n        // TODO(perf): Better to perform this parameter index check once, at the unwrap<U>() call\n        //   site. We'll need function length properties implemented correctly for that, most\n        //   likely -- see EW-386.\n        if (parameterIndex >= args.Length()) {\n          // We're unwrapping a nonexistent argument into a required parameter. Since Web IDL\n          // nullable types (Maybe<T>) can be initialized from `undefined`, we need to explicitly\n          // throw here, or else `f(Maybe<T>)` could be called like `f()`.\n          throwTypeError(\n              js.v8Isolate, errorContext, TypeWrapper::getName(static_cast<V*>(nullptr)));\n        }\n      }\n\n      // If we get here, we're either unwrapping into an optional or unimplemented parameter, in\n      // which cases we're fine with nonexistent arguments implying `undefined`, or we have an\n      // argument at this parameter index.\n      return unwrap<U>(js, context, args[parameterIndex], errorContext);\n    }\n  }\n\n  template <typename Holder, typename U>\n  void initReflection(Holder* holder, PropertyReflection<U>& reflection) {\n    reflection.self = holder;\n    reflection.unwrapper = [](v8::Isolate* isolate, v8::Local<v8::Object> object,\n                               kj::StringPtr name) -> kj::Maybe<U> {\n      auto context = isolate->GetCurrentContext();\n      auto& js = Lock::from(isolate);\n      auto value = jsg::check(object->Get(context, v8StrIntern(isolate, name)));\n      if (value->IsUndefined()) {\n        return kj::none;\n      } else {\n        // TypeErrorContext::structField() produces a pretty good error message for this case.\n        return from(isolate).template unwrap<U>(\n            js, context, value, TypeErrorContext::structField(typeid(Holder), name.cStr()), object);\n      }\n    };\n  }\n\n  template <typename Holder, typename... U>\n  void initReflection(Holder* holder, PropertyReflection<U>&... reflections) {\n    (initReflection(holder, reflections), ...);\n  }\n\n private:\n  const JsgConfig config;\n};\n\ntemplate <typename Self, typename... Types>\ntemplate <typename T>\nclass TypeWrapper<Self, Types...>::TypeHandlerImpl final: public TypeHandler<T> {\n public:\n  v8::Local<v8::Value> wrap(Lock& js, T value) const override {\n    auto isolate = js.v8Isolate;\n    auto context = js.v8Context();\n    return TypeWrapper::from(isolate).wrap(js, context, kj::none, kj::mv(value));\n  }\n\n  kj::Maybe<T> tryUnwrap(Lock& js, v8::Local<v8::Value> handle) const override {\n    auto isolate = js.v8Isolate;\n    auto context = js.v8Context();\n    return TypeWrapper::from(isolate).tryUnwrap(\n        js, context, handle, static_cast<T*>(nullptr), kj::none);\n  }\n};\n\n// This macro helps cut down on template spam in error messages. Instead of instantiating Isolate\n// directly, do:\n//\n//     JSG_DECLARE_ISOLATE_TYPE(MyIsolate, SomeApiType, AnotherApiType, ...);\n//\n// `MyIsolate` becomes your custom Isolate type, which will support wrapping all of the listed\n// API types.\n#define JSG_DECLARE_ISOLATE_TYPE(Type, ...)                                                        \\\n  class Type##_TypeWrapper;                                                                        \\\n  using Type##_TypeWrapperBase =                                                                   \\\n      ::workerd::jsg::TypeWrapper<Type##_TypeWrapper, jsg::DOMException, ##__VA_ARGS__>;           \\\n  class Type##_TypeWrapper final: public Type##_TypeWrapperBase {                                  \\\n   public:                                                                                         \\\n    [[maybe_unused]] static constexpr bool trackCallCounts = false;                                \\\n    using Type##_TypeWrapperBase::TypeWrapper;                                                     \\\n  };                                                                                               \\\n  class Type final: public ::workerd::jsg::Isolate<Type##_TypeWrapper> {                           \\\n   public:                                                                                         \\\n    using ::workerd::jsg::Isolate<Type##_TypeWrapper>::Isolate;                                    \\\n  }\n\n#define JSG_DECLARE_DEBUG_ISOLATE_TYPE(Type, ...)                                                  \\\n  class Type##_TypeWrapper;                                                                        \\\n  using Type##_TypeWrapperBase =                                                                   \\\n      ::workerd::jsg::TypeWrapper<Type##_TypeWrapper, jsg::DOMException, ##__VA_ARGS__>;           \\\n  class Type##_TypeWrapper final: public Type##_TypeWrapperBase {                                  \\\n   public:                                                                                         \\\n    [[maybe_unused]] static constexpr bool trackCallCounts = true;                                 \\\n    using Type##_TypeWrapperBase::TypeWrapper;                                                     \\\n  };                                                                                               \\\n  class Type final: public ::workerd::jsg::Isolate<Type##_TypeWrapper> {                           \\\n   public:                                                                                         \\\n    using ::workerd::jsg::Isolate<Type##_TypeWrapper>::Isolate;                                    \\\n  }\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/url-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"url.h\"\n\n#include <kj/table.h>\n#include <kj/test.h>\n\n#include <regex>\n\nnamespace workerd::jsg::test {\nnamespace {\n\nKJ_TEST(\"Basics\") {\n  Url theUrl = nullptr;\n  KJ_IF_SOME(url, Url::tryParse(\"http://example.org:81\"_kj)) {\n    KJ_ASSERT(url.getOrigin() == \"http://example.org:81\"_kj);\n    KJ_ASSERT(url.getHref() == \"http://example.org:81/\"_kj);\n    KJ_ASSERT(url.getProtocol() == \"http:\"_kj);\n    KJ_ASSERT(url.getHostname() == \"example.org\"_kj);\n    KJ_ASSERT(url.getHost() == \"example.org:81\"_kj);\n    KJ_ASSERT(url.getPort() == \"81\"_kj);\n    KJ_ASSERT(url.getPathname() == \"/\"_kj);\n    KJ_ASSERT(url.getSchemeType() == Url::SchemeType::HTTP);\n    KJ_ASSERT(url.getHostType() == Url::HostType::DEFAULT);\n    KJ_ASSERT(url.getUsername() == \"\"_kj);\n    KJ_ASSERT(url.getPassword() == \"\"_kj);\n    KJ_ASSERT(url.getHash() == \"\"_kj);\n    KJ_ASSERT(url.getSearch() == \"\"_kj);\n\n    theUrl = url.clone();\n    KJ_ASSERT(theUrl == url);\n    theUrl = kj::mv(url);\n\n    auto res = KJ_ASSERT_NONNULL(theUrl.resolve(\"abc\"_kj));\n    KJ_ASSERT(res.getHref() == \"http://example.org:81/abc\"_kj);\n\n    // jsg::Urls support KJ_STRINGIFY\n    KJ_ASSERT(kj::str(res) == \"http://example.org:81/abc\");\n\n    // jsg::Urls are suitable to be used as keys in a hashset, hashmap\n    kj::HashSet<Url> urls;\n    urls.insert(res.clone());\n    KJ_ASSERT(urls.contains(res));\n\n    kj::HashMap<Url, int> urlmap;\n    urlmap.insert(res.clone(), 1);\n    KJ_ASSERT(KJ_ASSERT_NONNULL(urlmap.find(res)) == 1);\n  } else {\n    KJ_FAIL_ASSERT(\"url could not be parsed\");\n  }\n\n  KJ_ASSERT(Url::idnToAscii(\"täst.de\"_kj) == \"xn--tst-qla.de\"_kj);\n  KJ_ASSERT(Url::idnToUnicode(\"xn--tst-qla.de\"_kj) == \"täst.de\"_kj);\n}\n\nKJ_TEST(\"Non-special URL\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"abc://123\"_kj));\n  KJ_ASSERT(url.getOrigin() == \"null\"_kj);\n  KJ_ASSERT(url.getProtocol() == \"abc:\"_kj);\n}\n\nKJ_TEST(\"Invalid Urls\") {\n  struct TestCase {\n    kj::StringPtr input;\n    kj::Maybe<kj::StringPtr> base = kj::none;\n  };\n  static const TestCase TESTS[] = {{\"http://f:b/c\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj)},\n    {\"http://f: /c\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj)},\n    {\"http://f:fifty-two/c\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj)},\n    {\"http://f:999999/c\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj)},\n    {\"non-special://f:999999/c\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj)},\n    {\"http://f: 21 / b ? d # e \"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj)},\n    {\"http://[1::2]:3:4\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj)},\n    {\"http://2001::1\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj)},\n    {\"http://2001::1]\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj)},\n    {\"http://2001::1]:80\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj)},\n    {\"http://[::127.0.0.1.]\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj)},\n    {\"file://example:1/\"_kj}, {\"file://example:test/\"_kj}, {\"file://example%/\"_kj},\n    {\"file://[example]/\"_kj}, {\"http://user:pass@/\"_kj}, {\"http://foo:-80/\"_kj},\n    {\"http:/:@/www.example.com\"_kj}, {\"http://user@/www.example.com\"_kj},\n    {\"http:@/www.example.com\"_kj}, {\"http:/@/www.example.com\"_kj}, {\"http://@/www.example.com\"_kj},\n    {\"https:@/www.example.com\"_kj}, {\"http:a:b@/www.example.com\"_kj},\n    {\"http:/a:b@/www.example.com\"_kj}, {\"http://a:b@/www.example.com\"_kj},\n    {\"http::@/www.example.com\"_kj}, {\"http:@:www.example.com\"_kj}, {\"http:/@:www.example.com\"_kj},\n    {\"http://@:www.example.com\"_kj},\n    {\"http://example example.com\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://Goo%20 goo%7C|.com\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://[]\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://[:]\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://GOO 　goo.com\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://﷐zyx.com\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://%ef%b7%90zyx.com\"_kj, kj::Maybe(\"http://other.com/\"_kj)}, {\"https://�\"_kj},\n    {\"https://%EF%BF%BD\"_kj}, {\"http://a.b.c.xn--pokxncvks\"_kj}, {\"http://10.0.0.xn--pokxncvks\"_kj},\n    {\"http://a.b.c.XN--pokxncvks\"_kj}, {\"http://a.b.c.Xn--pokxncvks\"_kj},\n    {\"http://10.0.0.XN--pokxncvks\"_kj}, {\"http://10.0.0.xN--pokxncvks\"_kj},\n    {\"http://％４１.com\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://%ef%bc%85%ef%bc%94%ef%bc%91.com\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://％００.com\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://%ef%bc%85%ef%bc%90%ef%bc%90.com\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://%zz%66%a.com\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://%25\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://hello%00\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://192.168.0.257\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://%3g%78%63%30%2e%30%32%35%30%2E.01\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://192.168.0.1 hello\"_kj, kj::Maybe(\"http://other.com/\"_kj)}, {\"https://x x:12\"_kj},\n    {\"http://[www.google.com]/\"_kj}, {\"http://[google.com]\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://[::1.2.3.4x]\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://[::1.2.3.]\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://[::1.2.]\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://[::.1.2]\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://[::1.]\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://[::.1]\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://[::%31]\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://%5B::1]\"_kj, kj::Maybe(\"http://other.com/\"_kj)}, {\"i\"_kj, kj::Maybe(\"sc:sd\"_kj)},\n    {\"i\"_kj, kj::Maybe(\"sc:sd/sd\"_kj)}, {\"../i\"_kj, kj::Maybe(\"sc:sd\"_kj)},\n    {\"../i\"_kj, kj::Maybe(\"sc:sd/sd\"_kj)}, {\"/i\"_kj, kj::Maybe(\"sc:sd\"_kj)},\n    {\"/i\"_kj, kj::Maybe(\"sc:sd/sd\"_kj)}, {\"?i\"_kj, kj::Maybe(\"sc:sd\"_kj)},\n    {\"?i\"_kj, kj::Maybe(\"sc:sd/sd\"_kj)}, {\"sc://@/\"_kj}, {\"sc://te@s:t@/\"_kj}, {\"sc://:/\"_kj},\n    {\"sc://:12/\"_kj}, {\"sc://a\\0b/\"_kj}, {\"sc://a b/\"_kj}, {\"sc://a<b\"_kj}, {\"sc://a>b\"_kj},\n    {\"sc://a[b/\"_kj}, {\"sc://a\\\\b/\"_kj}, {\"sc://a]b/\"_kj}, {\"sc://a^b\"_kj}, {\"sc://a|b/\"_kj},\n    {\"http://a\\0b/\"_kj}, {\"http://a\u0001b/\"_kj}, {\"http://a\u0002b/\"_kj}, {\"http://a\u0003b/\"_kj},\n    {\"http://a\u0004b/\"_kj}, {\"http://a\u0005b/\"_kj}, {\"http://a\u0006b/\"_kj}, {\"http://a\u0007b/\"_kj},\n    {\"http://a\bb/\"_kj}, {\"http://a\u000bb/\"_kj}, {\"http://a\fb/\"_kj}, {\"http://a\u000eb/\"_kj},\n    {\"http://a\u000fb/\"_kj}, {\"http://a\u0010b/\"_kj}, {\"http://a\u0011b/\"_kj}, {\"http://a\u0012b/\"_kj},\n    {\"http://a\u0013b/\"_kj}, {\"http://a\u0014b/\"_kj}, {\"http://a\u0015b/\"_kj}, {\"http://a\u0016b/\"_kj},\n    {\"http://a\u0017b/\"_kj}, {\"http://a\u0018b/\"_kj}, {\"http://a\u0019b/\"_kj}, {\"http://a\u001ab/\"_kj},\n    {\"http://a\u001bb/\"_kj}, {\"http://a\u001cb/\"_kj}, {\"http://a\u001db/\"_kj}, {\"http://a\u001eb/\"_kj},\n    {\"http://a\u001fb/\"_kj}, {\"http://a b/\"_kj}, {\"http://a%b/\"_kj}, {\"http://a<b\"_kj},\n    {\"http://a>b\"_kj}, {\"http://a[b/\"_kj}, {\"http://a]b/\"_kj}, {\"http://a^b\"_kj},\n    {\"http://a|b/\"_kj}, {\"http://ab/\"_kj}, {\"http://ho%00st/\"_kj}, {\"http://ho%01st/\"_kj},\n    {\"http://ho%02st/\"_kj}, {\"http://ho%03st/\"_kj}, {\"http://ho%04st/\"_kj}, {\"http://ho%05st/\"_kj},\n    {\"http://ho%06st/\"_kj}, {\"http://ho%07st/\"_kj}, {\"http://ho%08st/\"_kj}, {\"http://ho%09st/\"_kj},\n    {\"http://ho%0Ast/\"_kj}, {\"http://ho%0Bst/\"_kj}, {\"http://ho%0Cst/\"_kj}, {\"http://ho%0Dst/\"_kj},\n    {\"http://ho%0Est/\"_kj}, {\"http://ho%0Fst/\"_kj}, {\"http://ho%10st/\"_kj}, {\"http://ho%11st/\"_kj},\n    {\"http://ho%12st/\"_kj}, {\"http://ho%13st/\"_kj}, {\"http://ho%14st/\"_kj}, {\"http://ho%15st/\"_kj},\n    {\"http://ho%16st/\"_kj}, {\"http://ho%17st/\"_kj}, {\"http://ho%18st/\"_kj}, {\"http://ho%19st/\"_kj},\n    {\"http://ho%1Ast/\"_kj}, {\"http://ho%1Bst/\"_kj}, {\"http://ho%1Cst/\"_kj}, {\"http://ho%1Dst/\"_kj},\n    {\"http://ho%1Est/\"_kj}, {\"http://ho%1Fst/\"_kj}, {\"http://ho%20st/\"_kj}, {\"http://ho%23st/\"_kj},\n    {\"http://ho%25st/\"_kj}, {\"http://ho%2Fst/\"_kj}, {\"http://ho%3Ast/\"_kj}, {\"http://ho%3Cst/\"_kj},\n    {\"http://ho%3Est/\"_kj}, {\"http://ho%3Fst/\"_kj}, {\"http://ho%40st/\"_kj}, {\"http://ho%5Bst/\"_kj},\n    {\"http://ho%5Cst/\"_kj}, {\"http://ho%5Dst/\"_kj}, {\"http://ho%7Cst/\"_kj}, {\"http://ho%7Fst/\"_kj},\n    {\"ftp://example.com%80/\"_kj}, {\"ftp://example.com%A0/\"_kj}, {\"https://example.com%80/\"_kj},\n    {\"https://example.com%A0/\"_kj}, {\"http:\"_kj, kj::Maybe(\"https://example.org/foo/bar\"_kj)},\n    {\"http://10000000000\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://4294967296\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://0xffffffff1\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://256.256.256.256\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"https://0x100000000/test\"_kj}, {\"https://256.0.0.1/test\"_kj}, {\"file://%43%3A\"_kj},\n    {\"file://%43%7C\"_kj}, {\"file://%43|\"_kj}, {\"file://C%7C\"_kj}, {\"file://%43%7C/\"_kj},\n    {\"https://%43%7C/\"_kj}, {\"asdf://%43|/\"_kj}, {\"\\\\\\\\\\\\.\\\\Y:\"_kj}, {\"\\\\\\\\\\\\.\\\\y:\"_kj},\n    {\"http://[0:1:2:3:4:5:6:7:8]\"_kj, kj::Maybe(\"http://example.net/\"_kj)},\n    {\"https://[0::0::0]\"_kj}, {\"https://[0:.0]\"_kj}, {\"https://[0:0:]\"_kj},\n    {\"https://[0:1:2:3:4:5:6:7.0.0.0.1]\"_kj}, {\"https://[0:1.00.0.0.0]\"_kj},\n    {\"https://[0:1.290.0.0.0]\"_kj}, {\"https://[0:1.23.23]\"_kj}, {\"http://?\"_kj}, {\"http://#\"_kj},\n    {\"http://f:4294967377/c\"_kj, kj::Maybe(\"http://example.org/\"_kj)},\n    {\"http://f:18446744073709551697/c\"_kj, kj::Maybe(\"http://example.org/\"_kj)},\n    {\"http://f:340282366920938463463374607431768211537/c\"_kj, kj::Maybe(\"http://example.org/\"_kj)},\n    {\"non-special://[:80/\"_kj}, {\"http://[::127.0.0.0.1]\"_kj}, {\"a\"_kj}, {\"a/\"_kj}, {\"a//\"_kj},\n    {\"test-a-colon.html\"_kj, kj::Maybe(\"a:\"_kj)}, {\"test-a-colon-b.html\"_kj, kj::Maybe(\"a:b\"_kj)},\n    {\"file://­/p\"_kj}, {\"file://%C2%AD/p\"_kj}, {\"file://xn--/p\"_kj}, {\"#\"_kj}, {\"?\"_kj},\n    {\"http://1.2.3.4.5\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://1.2.3.4.5.\"_kj, kj::Maybe(\"http://other.com/\"_kj)}, {\"http://0..0x300/\"_kj},\n    {\"http://0..0x300./\"_kj}, {\"http://256.256.256.256.256\"_kj, kj::Maybe(\"http://other.com/\"_kj)},\n    {\"http://256.256.256.256.256.\"_kj, kj::Maybe(\"http://other.com/\"_kj)}, {\"http://1.2.3.08\"_kj},\n    {\"http://1.2.3.08.\"_kj}, {\"http://1.2.3.09\"_kj}, {\"http://09.2.3.4\"_kj},\n    {\"http://09.2.3.4.\"_kj}, {\"http://01.2.3.4.5\"_kj}, {\"http://01.2.3.4.5.\"_kj},\n    {\"http://0x100.2.3.4\"_kj}, {\"http://0x100.2.3.4.\"_kj}, {\"http://0x1.2.3.4.5\"_kj},\n    {\"http://0x1.2.3.4.5.\"_kj}, {\"http://foo.1.2.3.4\"_kj}, {\"http://foo.1.2.3.4.\"_kj},\n    {\"http://foo.2.3.4\"_kj}, {\"http://foo.2.3.4.\"_kj}, {\"http://foo.09\"_kj}, {\"http://foo.09.\"_kj},\n    {\"http://foo.0x4\"_kj}, {\"http://foo.0x4.\"_kj}, {\"http://0999999999999999999/\"_kj},\n    {\"http://foo.0x\"_kj}, {\"http://foo.0XFfFfFfFfFfFfFfFfFfAcE123\"_kj}, {\"http://💩.123/\"_kj},\n    {\"https://\\0y\"_kj}, {\"https://￿y\"_kj}, {\"\"_kj}, {\"https://­/\"_kj}, {\"https://%C2%AD/\"_kj},\n    {\"https://xn--/\"_kj}, {\"data://:443\"_kj}, {\"data://test:test\"_kj}, {\"data://[:1]\"_kj},\n    {\"javascript://:443\"_kj}, {\"javascript://test:test\"_kj}, {\"javascript://[:1]\"_kj},\n    {\"mailto://:443\"_kj}, {\"mailto://test:test\"_kj}, {\"mailto://[:1]\"_kj}, {\"intent://:443\"_kj},\n    {\"intent://test:test\"_kj}, {\"intent://[:1]\"_kj}, {\"urn://:443\"_kj}, {\"urn://test:test\"_kj},\n    {\"urn://[:1]\"_kj}, {\"turn://:443\"_kj}, {\"turn://test:test\"_kj}, {\"turn://[:1]\"_kj},\n    {\"stun://:443\"_kj}, {\"stun://test:test\"_kj}, {\"stun://[:1]\"_kj}};\n\n  for (auto& test: TESTS) {\n    KJ_IF_SOME(base, test.base) {\n      KJ_ASSERT(jsg::Url::tryParse(test.input, base) == kj::none);\n    } else {\n      KJ_ASSERT(jsg::Url::tryParse(test.input) == kj::none);\n    }\n  }\n}\n\nvoid test(kj::StringPtr input, kj::Maybe<kj::StringPtr> base, kj::StringPtr href) {\n  KJ_ASSERT(Url::canParse(input, base));\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(input, base));\n  KJ_ASSERT(url.getHref() == href);\n}\n\nKJ_TEST(\"Valid Urls\") {\n  struct TestCase {\n    kj::StringPtr input;\n    kj::Maybe<kj::StringPtr> base;\n    kj::StringPtr result;\n  };\n  static const TestCase TESTS[] = {\n    {\"http://example\\t.\\norg\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://example.org/\"_kj},\n    {\"http://user:pass@foo:21/bar;par?b#c\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://user:pass@foo:21/bar;par?b#c\"_kj},\n    {\"https://test:@test\"_kj, kj::none, \"https://test@test/\"_kj},\n    {\"https://:@test\"_kj, kj::none, \"https://test/\"_kj},\n    {\"non-special://test:@test/x\"_kj, kj::none, \"non-special://test@test/x\"_kj},\n    {\"non-special://:@test/x\"_kj, kj::none, \"non-special://test/x\"_kj},\n    {\"http:foo.com\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://example.org/foo/foo.com\"_kj},\n    {\"\\t   :foo.com   \\n\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://example.org/foo/:foo.com\"_kj},\n    {\" foo.com  \"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://example.org/foo/foo.com\"_kj},\n    {\"a:\\t foo.com\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"a: foo.com\"_kj},\n    {\"http://f:21/ b ? d # e \"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://f:21/%20b%20?%20d%20#%20e\"_kj},\n    {\"lolscheme:x x#x x\"_kj, kj::none, \"lolscheme:x x#x%20x\"_kj},\n    {\"http://f:/c\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://f/c\"_kj},\n    {\"http://f:0/c\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://f:0/c\"_kj},\n    {\"http://f:00000000000000/c\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://f:0/c\"_kj},\n    {\"http://f:00000000000000000000080/c\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://f/c\"_kj},\n    {\"http://f:\\n/c\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://f/c\"_kj},\n    {\"\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/foo/bar\"_kj},\n    {\"  \\t\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/foo/bar\"_kj},\n    {\":foo.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://example.org/foo/:foo.com/\"_kj},\n    {\":foo.com\\\\\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://example.org/foo/:foo.com/\"_kj},\n    {\":\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/foo/:\"_kj},\n    {\":a\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/foo/:a\"_kj},\n    {\":/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/foo/:/\"_kj},\n    {\":\\\\\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/foo/:/\"_kj},\n    {\":#\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/foo/:#\"_kj},\n    {\"#\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/foo/bar#\"_kj},\n    {\"#/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/foo/bar#/\"_kj},\n    {\"#\\\\\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/foo/bar#\\\\\"_kj},\n    {\"#;?\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/foo/bar#;?\"_kj},\n    {\"?\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/foo/bar?\"_kj},\n    {\"/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/\"_kj},\n    {\":23\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/foo/:23\"_kj},\n    {\"/:23\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/:23\"_kj},\n    {\"\\\\x\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/x\"_kj},\n    {\"\\\\\\\\x\\\\hello\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://x/hello\"_kj},\n    {\"::\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/foo/::\"_kj},\n    {\"::23\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/foo/::23\"_kj},\n    {\"foo://\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"foo://\"_kj},\n    {\"http://a:b@c:29/d\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://a:b@c:29/d\"_kj},\n    {\"http::@c:29\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://example.org/foo/:@c:29\"_kj},\n    {\"http://&a:foo(b]c@d:2/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://&a:foo(b%5Dc@d:2/\"_kj},\n    {\"http://::@c@d:2\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://:%3A%40c@d:2/\"_kj},\n    {\"http://foo.com:b@d/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://foo.com:b@d/\"_kj},\n    {\"http://foo.com/\\\\@\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://foo.com//@\"_kj},\n    {\"http:\\\\\\\\foo.com\\\\\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://foo.com/\"_kj},\n    {\"http:\\\\\\\\a\\\\b:c\\\\d@foo.com\\\\\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://a/b:c/d@foo.com/\"_kj},\n    {\"foo:/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"foo:/\"_kj},\n    {\"foo:/bar.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"foo:/bar.com/\"_kj},\n    {\"foo://///////\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"foo://///////\"_kj},\n    {\"foo://///////bar.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"foo://///////bar.com/\"_kj},\n    {\"foo:////://///\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"foo:////://///\"_kj},\n    {\"c:/foo\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"c:/foo\"_kj},\n    {\"//foo/bar\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://foo/bar\"_kj},\n    {\"http://foo/path;a??e#f#g\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://foo/path;a??e#f#g\"_kj},\n    {\"http://foo/abcd?efgh?ijkl\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://foo/abcd?efgh?ijkl\"_kj},\n    {\"http://foo/abcd#foo?bar\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://foo/abcd#foo?bar\"_kj},\n    {\"[61:24:74]:98\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://example.org/foo/[61:24:74]:98\"_kj},\n    {\"http:[61:27]/:foo\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://example.org/foo/[61:27]/:foo\"_kj},\n    {\"http://[2001::1]\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://[2001::1]/\"_kj},\n    {\"http://[::127.0.0.1]\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://[::7f00:1]/\"_kj},\n    {\"http://[0:0:0:0:0:0:13.1.68.3]\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://[::d01:4403]/\"_kj},\n    {\"http://[2001::1]:80\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://[2001::1]/\"_kj},\n    {\"http:/example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://example.org/example.com/\"_kj},\n    {\"ftp:/example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"ftp://example.com/\"_kj},\n    {\"https:/example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"https://example.com/\"_kj},\n    {\"madeupscheme:/example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"madeupscheme:/example.com/\"_kj},\n    {\"file:/example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"file:///example.com/\"_kj},\n    {\"ftps:/example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"ftps:/example.com/\"_kj},\n    {\"gopher:/example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"gopher:/example.com/\"_kj},\n    {\"ws:/example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"ws://example.com/\"_kj},\n    {\"wss:/example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"wss://example.com/\"_kj},\n    {\"data:/example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"data:/example.com/\"_kj},\n    {\"javascript:/example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"javascript:/example.com/\"_kj},\n    {\"mailto:/example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"mailto:/example.com/\"_kj},\n    {\"http:example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://example.org/foo/example.com/\"_kj},\n    {\"ftp:example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"ftp://example.com/\"_kj},\n    {\"https:example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"https://example.com/\"_kj},\n    {\"madeupscheme:example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"madeupscheme:example.com/\"_kj},\n    {\"ftps:example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"ftps:example.com/\"_kj},\n    {\"gopher:example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"gopher:example.com/\"_kj},\n    {\"ws:example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"ws://example.com/\"_kj},\n    {\"wss:example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"wss://example.com/\"_kj},\n    {\"data:example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"data:example.com/\"_kj},\n    {\"javascript:example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"javascript:example.com/\"_kj},\n    {\"mailto:example.com/\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"mailto:example.com/\"_kj},\n    {\"/a/b/c\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/a/b/c\"_kj},\n    {\"/a/ /c\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/a/%20/c\"_kj},\n    {\"/a%2fc\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/a%2fc\"_kj},\n    {\"/a/%2f/c\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/a/%2f/c\"_kj},\n    {\"#β\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/foo/bar#%CE%B2\"_kj},\n    {\"data:text/html,test#test\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"data:text/html,test#test\"_kj},\n    {\"tel:1234567890\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"tel:1234567890\"_kj},\n    {\"ssh://example.com/foo/bar.git\"_kj, kj::Maybe(\"http://example.org/\"_kj),\n      \"ssh://example.com/foo/bar.git\"_kj},\n    {\"file:c:\\\\foo\\\\bar.html\"_kj, kj::Maybe(\"file:///tmp/mock/path\"_kj),\n      \"file:///c:/foo/bar.html\"_kj},\n    {\"  File:c|////foo\\\\bar.html\"_kj, kj::Maybe(\"file:///tmp/mock/path\"_kj),\n      \"file:///c:////foo/bar.html\"_kj},\n    {\"C|/foo/bar\"_kj, kj::Maybe(\"file:///tmp/mock/path\"_kj), \"file:///C:/foo/bar\"_kj},\n    {\"/C|\\\\foo\\\\bar\"_kj, kj::Maybe(\"file:///tmp/mock/path\"_kj), \"file:///C:/foo/bar\"_kj},\n    {\"//C|/foo/bar\"_kj, kj::Maybe(\"file:///tmp/mock/path\"_kj), \"file:///C:/foo/bar\"_kj},\n    {\"//server/file\"_kj, kj::Maybe(\"file:///tmp/mock/path\"_kj), \"file://server/file\"_kj},\n    {\"\\\\\\\\server\\\\file\"_kj, kj::Maybe(\"file:///tmp/mock/path\"_kj), \"file://server/file\"_kj},\n    {\"/\\\\server/file\"_kj, kj::Maybe(\"file:///tmp/mock/path\"_kj), \"file://server/file\"_kj},\n    {\"file:///foo/bar.txt\"_kj, kj::Maybe(\"file:///tmp/mock/path\"_kj), \"file:///foo/bar.txt\"_kj},\n    {\"file:///home/me\"_kj, kj::Maybe(\"file:///tmp/mock/path\"_kj), \"file:///home/me\"_kj},\n    {\"//\"_kj, kj::Maybe(\"file:///tmp/mock/path\"_kj), \"file:///\"_kj},\n    {\"///\"_kj, kj::Maybe(\"file:///tmp/mock/path\"_kj), \"file:///\"_kj},\n    {\"///test\"_kj, kj::Maybe(\"file:///tmp/mock/path\"_kj), \"file:///test\"_kj},\n    {\"file://test\"_kj, kj::Maybe(\"file:///tmp/mock/path\"_kj), \"file://test/\"_kj},\n    {\"file://localhost\"_kj, kj::Maybe(\"file:///tmp/mock/path\"_kj), \"file:///\"_kj},\n    {\"file://localhost/\"_kj, kj::Maybe(\"file:///tmp/mock/path\"_kj), \"file:///\"_kj},\n    {\"file://localhost/test\"_kj, kj::Maybe(\"file:///tmp/mock/path\"_kj), \"file:///test\"_kj},\n    {\"test\"_kj, kj::Maybe(\"file:///tmp/mock/path\"_kj), \"file:///tmp/mock/test\"_kj},\n    {\"file:test\"_kj, kj::Maybe(\"file:///tmp/mock/path\"_kj), \"file:///tmp/mock/test\"_kj},\n    {\"http://example.com/././foo\"_kj, kj::none, \"http://example.com/foo\"_kj},\n    {\"http://example.com/./.foo\"_kj, kj::none, \"http://example.com/.foo\"_kj},\n    {\"http://example.com/foo/.\"_kj, kj::none, \"http://example.com/foo/\"_kj},\n    {\"http://example.com/foo/./\"_kj, kj::none, \"http://example.com/foo/\"_kj},\n    {\"http://example.com/foo/bar/..\"_kj, kj::none, \"http://example.com/foo/\"_kj},\n    {\"http://example.com/foo/bar/../\"_kj, kj::none, \"http://example.com/foo/\"_kj},\n    {\"http://example.com/foo/..bar\"_kj, kj::none, \"http://example.com/foo/..bar\"_kj},\n    {\"http://example.com/foo/bar/../ton\"_kj, kj::none, \"http://example.com/foo/ton\"_kj},\n    {\"http://example.com/foo/bar/../ton/../../a\"_kj, kj::none, \"http://example.com/a\"_kj},\n    {\"http://example.com/foo/../../..\"_kj, kj::none, \"http://example.com/\"_kj},\n    {\"http://example.com/foo/../../../ton\"_kj, kj::none, \"http://example.com/ton\"_kj},\n    {\"http://example.com/foo/%2e\"_kj, kj::none, \"http://example.com/foo/\"_kj},\n    {\"http://example.com/foo/%2e%2\"_kj, kj::none, \"http://example.com/foo/%2e%2\"_kj},\n    {\"http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar\"_kj, kj::none,\n      \"http://example.com/%2e.bar\"_kj},\n    {\"http://example.com////../..\"_kj, kj::none, \"http://example.com//\"_kj},\n    {\"http://example.com/foo/bar//../..\"_kj, kj::none, \"http://example.com/foo/\"_kj},\n    {\"http://example.com/foo/bar//..\"_kj, kj::none, \"http://example.com/foo/bar/\"_kj},\n    {\"http://example.com/foo\"_kj, kj::none, \"http://example.com/foo\"_kj},\n    {\"http://example.com/%20foo\"_kj, kj::none, \"http://example.com/%20foo\"_kj},\n    {\"http://example.com/foo%\"_kj, kj::none, \"http://example.com/foo%\"_kj},\n    {\"http://example.com/foo%2\"_kj, kj::none, \"http://example.com/foo%2\"_kj},\n    {\"http://example.com/foo%2zbar\"_kj, kj::none, \"http://example.com/foo%2zbar\"_kj},\n    {\"http://example.com/foo%2Â©zbar\"_kj, kj::none, \"http://example.com/foo%2%C3%82%C2%A9zbar\"_kj},\n    {\"http://example.com/foo%41%7a\"_kj, kj::none, \"http://example.com/foo%41%7a\"_kj},\n    {\"http://example.com/foo\\t%91\"_kj, kj::none, \"http://example.com/foo%C2%91%91\"_kj},\n    {\"http://example.com/foo%00%51\"_kj, kj::none, \"http://example.com/foo%00%51\"_kj},\n    {\"http://example.com/(%28:%3A%29)\"_kj, kj::none, \"http://example.com/(%28:%3A%29)\"_kj},\n    {\"http://example.com/%3A%3a%3C%3c\"_kj, kj::none, \"http://example.com/%3A%3a%3C%3c\"_kj},\n    {\"http://example.com/foo\\tbar\"_kj, kj::none, \"http://example.com/foobar\"_kj},\n    {\"http://example.com\\\\\\\\foo\\\\\\\\bar\"_kj, kj::none, \"http://example.com//foo//bar\"_kj},\n    {\"http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd\"_kj, kj::none,\n      \"http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd\"_kj},\n    {\"http://example.com/@asdf%40\"_kj, kj::none, \"http://example.com/@asdf%40\"_kj},\n    {\"http://example.com/你好你好\"_kj, kj::none,\n      \"http://example.com/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD\"_kj},\n    {\"http://example.com/‥/foo\"_kj, kj::none, \"http://example.com/%E2%80%A5/foo\"_kj},\n    {\"http://example.com/﻿/foo\"_kj, kj::none, \"http://example.com/%EF%BB%BF/foo\"_kj},\n    {\"http://example.com/‮/foo/‭/bar\"_kj, kj::none,\n      \"http://example.com/%E2%80%AE/foo/%E2%80%AD/bar\"_kj},\n    {\"http://www.google.com/foo?bar=baz#\"_kj, kj::none, \"http://www.google.com/foo?bar=baz#\"_kj},\n    {\"http://www.google.com/foo?bar=baz# »\"_kj, kj::none,\n      \"http://www.google.com/foo?bar=baz#%20%C2%BB\"_kj},\n    {\"data:test# »\"_kj, kj::none, \"data:test#%20%C2%BB\"_kj},\n    {\"http://www.google.com\"_kj, kj::none, \"http://www.google.com/\"_kj},\n    {\"http://192.0x00A80001\"_kj, kj::none, \"http://192.168.0.1/\"_kj},\n    {\"http://www/foo%2Ehtml\"_kj, kj::none, \"http://www/foo%2Ehtml\"_kj},\n    {\"http://www/foo/%2E/html\"_kj, kj::none, \"http://www/foo/html\"_kj},\n    {\"http://%25DOMAIN:foobar@foodomain.com/\"_kj, kj::none,\n      \"http://%25DOMAIN:foobar@foodomain.com/\"_kj},\n    {\"http:\\\\\\\\www.google.com\\\\foo\"_kj, kj::none, \"http://www.google.com/foo\"_kj},\n    {\"http://foo:80/\"_kj, kj::none, \"http://foo/\"_kj},\n    {\"http://foo:81/\"_kj, kj::none, \"http://foo:81/\"_kj},\n    {\"httpa://foo:80/\"_kj, kj::none, \"httpa://foo:80/\"_kj},\n    {\"https://foo:443/\"_kj, kj::none, \"https://foo/\"_kj},\n    {\"https://foo:80/\"_kj, kj::none, \"https://foo:80/\"_kj},\n    {\"ftp://foo:21/\"_kj, kj::none, \"ftp://foo/\"_kj},\n    {\"ftp://foo:80/\"_kj, kj::none, \"ftp://foo:80/\"_kj},\n    {\"gopher://foo:70/\"_kj, kj::none, \"gopher://foo:70/\"_kj},\n    {\"gopher://foo:443/\"_kj, kj::none, \"gopher://foo:443/\"_kj},\n    {\"ws://foo:80/\"_kj, kj::none, \"ws://foo/\"_kj}, {\"ws://foo:81/\"_kj, kj::none, \"ws://foo:81/\"_kj},\n    {\"ws://foo:443/\"_kj, kj::none, \"ws://foo:443/\"_kj},\n    {\"ws://foo:815/\"_kj, kj::none, \"ws://foo:815/\"_kj},\n    {\"wss://foo:80/\"_kj, kj::none, \"wss://foo:80/\"_kj},\n    {\"wss://foo:81/\"_kj, kj::none, \"wss://foo:81/\"_kj},\n    {\"wss://foo:443/\"_kj, kj::none, \"wss://foo/\"_kj},\n    {\"wss://foo:815/\"_kj, kj::none, \"wss://foo:815/\"_kj},\n    {\"http:/example.com/\"_kj, kj::none, \"http://example.com/\"_kj},\n    {\"ftp:/example.com/\"_kj, kj::none, \"ftp://example.com/\"_kj},\n    {\"https:/example.com/\"_kj, kj::none, \"https://example.com/\"_kj},\n    {\"madeupscheme:/example.com/\"_kj, kj::none, \"madeupscheme:/example.com/\"_kj},\n    {\"file:/example.com/\"_kj, kj::none, \"file:///example.com/\"_kj},\n    {\"ftps:/example.com/\"_kj, kj::none, \"ftps:/example.com/\"_kj},\n    {\"gopher:/example.com/\"_kj, kj::none, \"gopher:/example.com/\"_kj},\n    {\"ws:/example.com/\"_kj, kj::none, \"ws://example.com/\"_kj},\n    {\"wss:/example.com/\"_kj, kj::none, \"wss://example.com/\"_kj},\n    {\"data:/example.com/\"_kj, kj::none, \"data:/example.com/\"_kj},\n    {\"javascript:/example.com/\"_kj, kj::none, \"javascript:/example.com/\"_kj},\n    {\"mailto:/example.com/\"_kj, kj::none, \"mailto:/example.com/\"_kj},\n    {\"http:example.com/\"_kj, kj::none, \"http://example.com/\"_kj},\n    {\"ftp:example.com/\"_kj, kj::none, \"ftp://example.com/\"_kj},\n    {\"https:example.com/\"_kj, kj::none, \"https://example.com/\"_kj},\n    {\"madeupscheme:example.com/\"_kj, kj::none, \"madeupscheme:example.com/\"_kj},\n    {\"ftps:example.com/\"_kj, kj::none, \"ftps:example.com/\"_kj},\n    {\"gopher:example.com/\"_kj, kj::none, \"gopher:example.com/\"_kj},\n    {\"ws:example.com/\"_kj, kj::none, \"ws://example.com/\"_kj},\n    {\"wss:example.com/\"_kj, kj::none, \"wss://example.com/\"_kj},\n    {\"data:example.com/\"_kj, kj::none, \"data:example.com/\"_kj},\n    {\"javascript:example.com/\"_kj, kj::none, \"javascript:example.com/\"_kj},\n    {\"mailto:example.com/\"_kj, kj::none, \"mailto:example.com/\"_kj},\n    {\"http:@www.example.com\"_kj, kj::none, \"http://www.example.com/\"_kj},\n    {\"http:/@www.example.com\"_kj, kj::none, \"http://www.example.com/\"_kj},\n    {\"http://@www.example.com\"_kj, kj::none, \"http://www.example.com/\"_kj},\n    {\"http:a:b@www.example.com\"_kj, kj::none, \"http://a:b@www.example.com/\"_kj},\n    {\"http:/a:b@www.example.com\"_kj, kj::none, \"http://a:b@www.example.com/\"_kj},\n    {\"http://a:b@www.example.com\"_kj, kj::none, \"http://a:b@www.example.com/\"_kj},\n    {\"http://@pple.com\"_kj, kj::none, \"http://pple.com/\"_kj},\n    {\"http::b@www.example.com\"_kj, kj::none, \"http://:b@www.example.com/\"_kj},\n    {\"http:/:b@www.example.com\"_kj, kj::none, \"http://:b@www.example.com/\"_kj},\n    {\"http://:b@www.example.com\"_kj, kj::none, \"http://:b@www.example.com/\"_kj},\n    {\"http:a:@www.example.com\"_kj, kj::none, \"http://a@www.example.com/\"_kj},\n    {\"http:/a:@www.example.com\"_kj, kj::none, \"http://a@www.example.com/\"_kj},\n    {\"http://a:@www.example.com\"_kj, kj::none, \"http://a@www.example.com/\"_kj},\n    {\"http://www.@pple.com\"_kj, kj::none, \"http://www.@pple.com/\"_kj},\n    {\"http://:@www.example.com\"_kj, kj::none, \"http://www.example.com/\"_kj},\n    {\"/\"_kj, kj::Maybe(\"http://www.example.com/test\"_kj), \"http://www.example.com/\"_kj},\n    {\"/test.txt\"_kj, kj::Maybe(\"http://www.example.com/test\"_kj),\n      \"http://www.example.com/test.txt\"_kj},\n    {\".\"_kj, kj::Maybe(\"http://www.example.com/test\"_kj), \"http://www.example.com/\"_kj},\n    {\"..\"_kj, kj::Maybe(\"http://www.example.com/test\"_kj), \"http://www.example.com/\"_kj},\n    {\"test.txt\"_kj, kj::Maybe(\"http://www.example.com/test\"_kj),\n      \"http://www.example.com/test.txt\"_kj},\n    {\"./test.txt\"_kj, kj::Maybe(\"http://www.example.com/test\"_kj),\n      \"http://www.example.com/test.txt\"_kj},\n    {\"../test.txt\"_kj, kj::Maybe(\"http://www.example.com/test\"_kj),\n      \"http://www.example.com/test.txt\"_kj},\n    {\"../aaa/test.txt\"_kj, kj::Maybe(\"http://www.example.com/test\"_kj),\n      \"http://www.example.com/aaa/test.txt\"_kj},\n    {\"../../test.txt\"_kj, kj::Maybe(\"http://www.example.com/test\"_kj),\n      \"http://www.example.com/test.txt\"_kj},\n    {\"中/test.txt\"_kj, kj::Maybe(\"http://www.example.com/test\"_kj),\n      \"http://www.example.com/%E4%B8%AD/test.txt\"_kj},\n    {\"http://www.example2.com\"_kj, kj::Maybe(\"http://www.example.com/test\"_kj),\n      \"http://www.example2.com/\"_kj},\n    {\"//www.example2.com\"_kj, kj::Maybe(\"http://www.example.com/test\"_kj),\n      \"http://www.example2.com/\"_kj},\n    {\"file:...\"_kj, kj::Maybe(\"http://www.example.com/test\"_kj), \"file:///...\"_kj},\n    {\"file:..\"_kj, kj::Maybe(\"http://www.example.com/test\"_kj), \"file:///\"_kj},\n    {\"file:a\"_kj, kj::Maybe(\"http://www.example.com/test\"_kj), \"file:///a\"_kj},\n    {\"http://ExAmPlE.CoM\"_kj, kj::Maybe(\"http://other.com/\"_kj), \"http://example.com/\"_kj},\n    {\"http://GOO​⁠﻿goo.com\"_kj, kj::Maybe(\"http://other.com/\"_kj), \"http://googoo.com/\"_kj},\n    {\"\\0\u001b\u0004\u0012 http://example.com/\u001f \\r \"_kj, kj::none, \"http://example.com/\"_kj},\n    {\"http://www.foo。bar.com\"_kj, kj::Maybe(\"http://other.com/\"_kj), \"http://www.foo.bar.com/\"_kj},\n    {\"https://x/�?�#�\"_kj, kj::none, \"https://x/%EF%BF%BD?%EF%BF%BD#%EF%BF%BD\"_kj},\n    {\"http://Ｇｏ.com\"_kj, kj::Maybe(\"http://other.com/\"_kj), \"http://go.com/\"_kj},\n    {\"http://你好你好\"_kj, kj::Maybe(\"http://other.com/\"_kj), \"http://xn--6qqa088eba/\"_kj},\n    {\"https://faß.ExAmPlE/\"_kj, kj::none, \"https://xn--fa-hia.example/\"_kj},\n    {\"sc://faß.ExAmPlE/\"_kj, kj::none, \"sc://fa%C3%9F.ExAmPlE/\"_kj},\n    {\"http://%30%78%63%30%2e%30%32%35%30.01\"_kj, kj::Maybe(\"http://other.com/\"_kj),\n      \"http://192.168.0.1/\"_kj},\n    {\"http://%30%78%63%30%2e%30%32%35%30.01%2e\"_kj, kj::Maybe(\"http://other.com/\"_kj),\n      \"http://192.168.0.1/\"_kj},\n    {\"http://０Ｘｃ０．０２５０．０１\"_kj, kj::Maybe(\"http://other.com/\"_kj),\n      \"http://192.168.0.1/\"_kj},\n    {\"http://./\"_kj, kj::none, \"http://./\"_kj}, {\"http://../\"_kj, kj::none, \"http://../\"_kj},\n    {\"h://.\"_kj, kj::none, \"h://.\"_kj},\n    {\"http://foo:💩@example.com/bar\"_kj, kj::Maybe(\"http://other.com/\"_kj),\n      \"http://foo:%F0%9F%92%A9@example.com/bar\"_kj},\n    {\"#\"_kj, kj::Maybe(\"test:test\"_kj), \"test:test#\"_kj},\n    {\"#x\"_kj, kj::Maybe(\"mailto:x@x.com\"_kj), \"mailto:x@x.com#x\"_kj},\n    {\"#x\"_kj, kj::Maybe(\"data:,\"_kj), \"data:,#x\"_kj},\n    {\"#x\"_kj, kj::Maybe(\"about:blank\"_kj), \"about:blank#x\"_kj},\n    {\"#x:y\"_kj, kj::Maybe(\"about:blank\"_kj), \"about:blank#x:y\"_kj},\n    {\"#\"_kj, kj::Maybe(\"test:test?test\"_kj), \"test:test?test#\"_kj},\n    {\"https://@test@test@example:800/\"_kj, kj::Maybe(\"http://doesnotmatter/\"_kj),\n      \"https://%40test%40test@example:800/\"_kj},\n    {\"https://@@@example\"_kj, kj::Maybe(\"http://doesnotmatter/\"_kj), \"https://%40%40@example/\"_kj},\n    {\"http://`{}:`{}@h/`{}?`{}\"_kj, kj::Maybe(\"http://doesnotmatter/\"_kj),\n      \"http://%60%7B%7D:%60%7B%7D@h/%60%7B%7D?`{}\"_kj},\n    {\"http://host/?'\"_kj, kj::none, \"http://host/?%27\"_kj},\n    {\"notspecial://host/?'\"_kj, kj::none, \"notspecial://host/?'\"_kj},\n    {\"/some/path\"_kj, kj::Maybe(\"http://user@example.org/smth\"_kj),\n      \"http://user@example.org/some/path\"_kj},\n    {\"\"_kj, kj::Maybe(\"http://user:pass@example.org:21/smth\"_kj),\n      \"http://user:pass@example.org:21/smth\"_kj},\n    {\"/some/path\"_kj, kj::Maybe(\"http://user:pass@example.org:21/smth\"_kj),\n      \"http://user:pass@example.org:21/some/path\"_kj},\n    {\"i\"_kj, kj::Maybe(\"sc:/pa/pa\"_kj), \"sc:/pa/i\"_kj},\n    {\"i\"_kj, kj::Maybe(\"sc://ho/pa\"_kj), \"sc://ho/i\"_kj},\n    {\"i\"_kj, kj::Maybe(\"sc:///pa/pa\"_kj), \"sc:///pa/i\"_kj},\n    {\"../i\"_kj, kj::Maybe(\"sc:/pa/pa\"_kj), \"sc:/i\"_kj},\n    {\"../i\"_kj, kj::Maybe(\"sc://ho/pa\"_kj), \"sc://ho/i\"_kj},\n    {\"../i\"_kj, kj::Maybe(\"sc:///pa/pa\"_kj), \"sc:///i\"_kj},\n    {\"/i\"_kj, kj::Maybe(\"sc:/pa/pa\"_kj), \"sc:/i\"_kj},\n    {\"/i\"_kj, kj::Maybe(\"sc://ho/pa\"_kj), \"sc://ho/i\"_kj},\n    {\"/i\"_kj, kj::Maybe(\"sc:///pa/pa\"_kj), \"sc:///i\"_kj},\n    {\"?i\"_kj, kj::Maybe(\"sc:/pa/pa\"_kj), \"sc:/pa/pa?i\"_kj},\n    {\"?i\"_kj, kj::Maybe(\"sc://ho/pa\"_kj), \"sc://ho/pa?i\"_kj},\n    {\"?i\"_kj, kj::Maybe(\"sc:///pa/pa\"_kj), \"sc:///pa/pa?i\"_kj},\n    {\"#i\"_kj, kj::Maybe(\"sc:sd\"_kj), \"sc:sd#i\"_kj},\n    {\"#i\"_kj, kj::Maybe(\"sc:sd/sd\"_kj), \"sc:sd/sd#i\"_kj},\n    {\"#i\"_kj, kj::Maybe(\"sc:/pa/pa\"_kj), \"sc:/pa/pa#i\"_kj},\n    {\"#i\"_kj, kj::Maybe(\"sc://ho/pa\"_kj), \"sc://ho/pa#i\"_kj},\n    {\"#i\"_kj, kj::Maybe(\"sc:///pa/pa\"_kj), \"sc:///pa/pa#i\"_kj},\n    {\"about:/../\"_kj, kj::none, \"about:/\"_kj}, {\"data:/../\"_kj, kj::none, \"data:/\"_kj},\n    {\"javascript:/../\"_kj, kj::none, \"javascript:/\"_kj},\n    {\"mailto:/../\"_kj, kj::none, \"mailto:/\"_kj},\n    {\"sc://ñ.test/\"_kj, kj::none, \"sc://%C3%B1.test/\"_kj}, {\"sc://%/\"_kj, kj::none, \"sc://%/\"_kj},\n    {\"x\"_kj, kj::Maybe(\"sc://ñ\"_kj), \"sc://%C3%B1/x\"_kj}, {\"sc:\\\\../\"_kj, kj::none, \"sc:\\\\../\"_kj},\n    {\"sc::a@example.net\"_kj, kj::none, \"sc::a@example.net\"_kj},\n    {\"wow:%NBD\"_kj, kj::none, \"wow:%NBD\"_kj}, {\"wow:%1G\"_kj, kj::none, \"wow:%1G\"_kj},\n    {\"wow:￿\"_kj, kj::none, \"wow:%EF%BF%BF\"_kj},\n    {\"http://example.com/�𐟾�﷐﷏﷯ﷰ￾￿?�𐟾�﷐﷏﷯ﷰ￾￿\"_kj, kj::none,\n      \"http://example.com/%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF?%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF\"_kj},\n    {\"foo://ho\\tst/\"_kj, kj::none, \"foo://host/\"_kj},\n    {\"foo://ho\\nst/\"_kj, kj::none, \"foo://host/\"_kj},\n    {\"foo://ho\\rst/\"_kj, kj::none, \"foo://host/\"_kj},\n    {\"http://ho\\tst/\"_kj, kj::none, \"http://host/\"_kj},\n    {\"http://ho\\nst/\"_kj, kj::none, \"http://host/\"_kj},\n    {\"http://ho\\rst/\"_kj, kj::none, \"http://host/\"_kj},\n    {\"http://!\\\"$&'()*+,-.;=_`{}~/\"_kj, kj::none, \"http://!\\\"$&'()*+,-.;=_`{}~/\"_kj},\n    {\"sc://\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\u000b\f\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f!\\\"$%&'()*+,-.;=_`{}~/\"_kj, kj::none,\n      \"sc://%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\\\"$%&'()*+,-.;=_`{}~/\"_kj},\n    {\"ftp://%e2%98%83\"_kj, kj::none, \"ftp://xn--n3h/\"_kj},\n    {\"https://%e2%98%83\"_kj, kj::none, \"https://xn--n3h/\"_kj},\n    {\"http://127.0.0.1:10100/relative_import.html\"_kj, kj::none,\n      \"http://127.0.0.1:10100/relative_import.html\"_kj},\n    {\"http://facebook.com/?foo=%7B%22abc%22\"_kj, kj::none,\n      \"http://facebook.com/?foo=%7B%22abc%22\"_kj},\n    {\"https://localhost:3000/jqueryui@1.2.3\"_kj, kj::none,\n      \"https://localhost:3000/jqueryui@1.2.3\"_kj},\n    {\"h\\tt\\nt\\rp://h\\to\\ns\\rt:9\\t0\\n0\\r0/p\\ta\\nt\\rh?q\\tu\\ne\\rry#f\\tr\\na\\rg\"_kj, kj::none,\n      \"http://host:9000/path?query#frag\"_kj},\n    {\"?a=b&c=d\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://example.org/foo/bar?a=b&c=d\"_kj},\n    {\"??a=b&c=d\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj),\n      \"http://example.org/foo/bar??a=b&c=d\"_kj},\n    {\"http:\"_kj, kj::Maybe(\"http://example.org/foo/bar\"_kj), \"http://example.org/foo/bar\"_kj},\n    {\"sc:\"_kj, kj::Maybe(\"https://example.org/foo/bar\"_kj), \"sc:\"_kj},\n    {\"http://foo.bar/baz?qux#foo\bbar\"_kj, kj::none, \"http://foo.bar/baz?qux#foo%08bar\"_kj},\n    {\"http://foo.bar/baz?qux#foo\\\"bar\"_kj, kj::none, \"http://foo.bar/baz?qux#foo%22bar\"_kj},\n    {\"http://foo.bar/baz?qux#foo<bar\"_kj, kj::none, \"http://foo.bar/baz?qux#foo%3Cbar\"_kj},\n    {\"http://foo.bar/baz?qux#foo>bar\"_kj, kj::none, \"http://foo.bar/baz?qux#foo%3Ebar\"_kj},\n    {\"http://foo.bar/baz?qux#foo`bar\"_kj, kj::none, \"http://foo.bar/baz?qux#foo%60bar\"_kj},\n    {\"http://1.2.3.4/\"_kj, kj::Maybe(\"http://other.com/\"_kj), \"http://1.2.3.4/\"_kj},\n    {\"http://1.2.3.4./\"_kj, kj::Maybe(\"http://other.com/\"_kj), \"http://1.2.3.4/\"_kj},\n    {\"http://192.168.257\"_kj, kj::Maybe(\"http://other.com/\"_kj), \"http://192.168.1.1/\"_kj},\n    {\"http://192.168.257.\"_kj, kj::Maybe(\"http://other.com/\"_kj), \"http://192.168.1.1/\"_kj},\n    {\"http://192.168.257.com\"_kj, kj::Maybe(\"http://other.com/\"_kj), \"http://192.168.257.com/\"_kj},\n    {\"http://256\"_kj, kj::Maybe(\"http://other.com/\"_kj), \"http://0.0.1.0/\"_kj},\n    {\"http://256.com\"_kj, kj::Maybe(\"http://other.com/\"_kj), \"http://256.com/\"_kj},\n    {\"http://999999999\"_kj, kj::Maybe(\"http://other.com/\"_kj), \"http://59.154.201.255/\"_kj},\n    {\"http://999999999.\"_kj, kj::Maybe(\"http://other.com/\"_kj), \"http://59.154.201.255/\"_kj},\n    {\"http://999999999.com\"_kj, kj::Maybe(\"http://other.com/\"_kj), \"http://999999999.com/\"_kj},\n    {\"http://10000000000.com\"_kj, kj::Maybe(\"http://other.com/\"_kj), \"http://10000000000.com/\"_kj},\n    {\"http://4294967295\"_kj, kj::Maybe(\"http://other.com/\"_kj), \"http://255.255.255.255/\"_kj},\n    {\"http://0xffffffff\"_kj, kj::Maybe(\"http://other.com/\"_kj), \"http://255.255.255.255/\"_kj},\n    {\"https://0x.0x.0\"_kj, kj::none, \"https://0.0.0.0/\"_kj},\n    {\"file:///C%3A/\"_kj, kj::none, \"file:///C%3A/\"_kj},\n    {\"file:///C%7C/\"_kj, kj::none, \"file:///C%7C/\"_kj},\n    {\"asdf://%43%7C/\"_kj, kj::none, \"asdf://%43%7C/\"_kj},\n    {\"pix/submit.gif\"_kj,\n      kj::Maybe(\n          \"file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/anchor.html\"_kj),\n      \"file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif\"_kj},\n    {\"..\"_kj, kj::Maybe(\"file:///C:/\"_kj), \"file:///C:/\"_kj},\n    {\"..\"_kj, kj::Maybe(\"file:///\"_kj), \"file:///\"_kj},\n    {\"/\"_kj, kj::Maybe(\"file:///C:/a/b\"_kj), \"file:///C:/\"_kj},\n    {\"/\"_kj, kj::Maybe(\"file://h/C:/a/b\"_kj), \"file://h/C:/\"_kj},\n    {\"/\"_kj, kj::Maybe(\"file://h/a/b\"_kj), \"file://h/\"_kj},\n    {\"//d:\"_kj, kj::Maybe(\"file:///C:/a/b\"_kj), \"file:///d:\"_kj},\n    {\"//d:/..\"_kj, kj::Maybe(\"file:///C:/a/b\"_kj), \"file:///d:/\"_kj},\n    {\"..\"_kj, kj::Maybe(\"file:///ab:/\"_kj), \"file:///\"_kj},\n    {\"..\"_kj, kj::Maybe(\"file:///1:/\"_kj), \"file:///\"_kj},\n    {\"\"_kj, kj::Maybe(\"file:///test?test#test\"_kj), \"file:///test?test\"_kj},\n    {\"file:\"_kj, kj::Maybe(\"file:///test?test#test\"_kj), \"file:///test?test\"_kj},\n    {\"?x\"_kj, kj::Maybe(\"file:///test?test#test\"_kj), \"file:///test?x\"_kj},\n    {\"file:?x\"_kj, kj::Maybe(\"file:///test?test#test\"_kj), \"file:///test?x\"_kj},\n    {\"#x\"_kj, kj::Maybe(\"file:///test?test#test\"_kj), \"file:///test?test#x\"_kj},\n    {\"file:#x\"_kj, kj::Maybe(\"file:///test?test#test\"_kj), \"file:///test?test#x\"_kj},\n    {\"file:\\\\\\\\//\"_kj, kj::none, \"file:////\"_kj}, {\"file:\\\\\\\\\\\\\\\\\"_kj, kj::none, \"file:////\"_kj},\n    {\"file:\\\\\\\\\\\\\\\\?fox\"_kj, kj::none, \"file:////?fox\"_kj},\n    {\"file:\\\\\\\\\\\\\\\\#guppy\"_kj, kj::none, \"file:////#guppy\"_kj},\n    {\"file://spider///\"_kj, kj::none, \"file://spider///\"_kj},\n    {\"file:\\\\\\\\localhost//\"_kj, kj::none, \"file:////\"_kj},\n    {\"file:///localhost//cat\"_kj, kj::none, \"file:///localhost//cat\"_kj},\n    {\"file://\\\\/localhost//cat\"_kj, kj::none, \"file:////localhost//cat\"_kj},\n    {\"file://localhost//a//../..//\"_kj, kj::none, \"file://///\"_kj},\n    {\"/////mouse\"_kj, kj::Maybe(\"file:///elephant\"_kj), \"file://///mouse\"_kj},\n    {\"\\\\//pig\"_kj, kj::Maybe(\"file://lion/\"_kj), \"file:///pig\"_kj},\n    {\"\\\\/localhost//pig\"_kj, kj::Maybe(\"file://lion/\"_kj), \"file:////pig\"_kj},\n    {\"//localhost//pig\"_kj, kj::Maybe(\"file://lion/\"_kj), \"file:////pig\"_kj},\n    {\"/..//localhost//pig\"_kj, kj::Maybe(\"file://lion/\"_kj), \"file://lion//localhost//pig\"_kj},\n    {\"file://\"_kj, kj::Maybe(\"file://ape/\"_kj), \"file:///\"_kj},\n    {\"/rooibos\"_kj, kj::Maybe(\"file://tea/\"_kj), \"file://tea/rooibos\"_kj},\n    {\"/?chai\"_kj, kj::Maybe(\"file://tea/\"_kj), \"file://tea/?chai\"_kj},\n    {\"C|\"_kj, kj::Maybe(\"file://host/dir/file\"_kj), \"file://host/C:\"_kj},\n    {\"C|\"_kj, kj::Maybe(\"file://host/D:/dir1/dir2/file\"_kj), \"file://host/C:\"_kj},\n    {\"C|#\"_kj, kj::Maybe(\"file://host/dir/file\"_kj), \"file://host/C:#\"_kj},\n    {\"C|?\"_kj, kj::Maybe(\"file://host/dir/file\"_kj), \"file://host/C:?\"_kj},\n    {\"C|/\"_kj, kj::Maybe(\"file://host/dir/file\"_kj), \"file://host/C:/\"_kj},\n    {\"C|\\n/\"_kj, kj::Maybe(\"file://host/dir/file\"_kj), \"file://host/C:/\"_kj},\n    {\"C|\\\\\"_kj, kj::Maybe(\"file://host/dir/file\"_kj), \"file://host/C:/\"_kj},\n    {\"C\"_kj, kj::Maybe(\"file://host/dir/file\"_kj), \"file://host/dir/C\"_kj},\n    {\"C|a\"_kj, kj::Maybe(\"file://host/dir/file\"_kj), \"file://host/dir/C|a\"_kj},\n    {\"/c:/foo/bar\"_kj, kj::Maybe(\"file:///c:/baz/qux\"_kj), \"file:///c:/foo/bar\"_kj},\n    {\"/c|/foo/bar\"_kj, kj::Maybe(\"file:///c:/baz/qux\"_kj), \"file:///c:/foo/bar\"_kj},\n    {\"file:\\\\c:\\\\foo\\\\bar\"_kj, kj::Maybe(\"file:///c:/baz/qux\"_kj), \"file:///c:/foo/bar\"_kj},\n    {\"/c:/foo/bar\"_kj, kj::Maybe(\"file://host/path\"_kj), \"file://host/c:/foo/bar\"_kj},\n    {\"file://example.net/C:/\"_kj, kj::none, \"file://example.net/C:/\"_kj},\n    {\"file://1.2.3.4/C:/\"_kj, kj::none, \"file://1.2.3.4/C:/\"_kj},\n    {\"file://[1::8]/C:/\"_kj, kj::none, \"file://[1::8]/C:/\"_kj},\n    {\"C|/\"_kj, kj::Maybe(\"file://host/\"_kj), \"file://host/C:/\"_kj},\n    {\"/C:/\"_kj, kj::Maybe(\"file://host/\"_kj), \"file://host/C:/\"_kj},\n    {\"file:C:/\"_kj, kj::Maybe(\"file://host/\"_kj), \"file://host/C:/\"_kj},\n    {\"file:/C:/\"_kj, kj::Maybe(\"file://host/\"_kj), \"file://host/C:/\"_kj},\n    {\"//C:/\"_kj, kj::Maybe(\"file://host/\"_kj), \"file:///C:/\"_kj},\n    {\"file://C:/\"_kj, kj::Maybe(\"file://host/\"_kj), \"file:///C:/\"_kj},\n    {\"///C:/\"_kj, kj::Maybe(\"file://host/\"_kj), \"file:///C:/\"_kj},\n    {\"file:///C:/\"_kj, kj::Maybe(\"file://host/\"_kj), \"file:///C:/\"_kj},\n    {\"file:/C|/\"_kj, kj::none, \"file:///C:/\"_kj}, {\"file://C|/\"_kj, kj::none, \"file:///C:/\"_kj},\n    {\"file:\"_kj, kj::none, \"file:///\"_kj}, {\"file:?q=v\"_kj, kj::none, \"file:///?q=v\"_kj},\n    {\"file:#frag\"_kj, kj::none, \"file:///#frag\"_kj}, {\"file:///Y:\"_kj, kj::none, \"file:///Y:\"_kj},\n    {\"file:///Y:/\"_kj, kj::none, \"file:///Y:/\"_kj}, {\"file:///./Y\"_kj, kj::none, \"file:///Y\"_kj},\n    {\"file:///./Y:\"_kj, kj::none, \"file:///Y:\"_kj}, {\"file:///y:\"_kj, kj::none, \"file:///y:\"_kj},\n    {\"file:///y:/\"_kj, kj::none, \"file:///y:/\"_kj}, {\"file:///./y\"_kj, kj::none, \"file:///y\"_kj},\n    {\"file:///./y:\"_kj, kj::none, \"file:///y:\"_kj},\n    {\"file://localhost//a//../..//foo\"_kj, kj::none, \"file://///foo\"_kj},\n    {\"file://localhost////foo\"_kj, kj::none, \"file://////foo\"_kj},\n    {\"file:////foo\"_kj, kj::none, \"file:////foo\"_kj},\n    {\"file:///one/two\"_kj, kj::Maybe(\"file:///\"_kj), \"file:///one/two\"_kj},\n    {\"file:////one/two\"_kj, kj::Maybe(\"file:///\"_kj), \"file:////one/two\"_kj},\n    {\"//one/two\"_kj, kj::Maybe(\"file:///\"_kj), \"file://one/two\"_kj},\n    {\"///one/two\"_kj, kj::Maybe(\"file:///\"_kj), \"file:///one/two\"_kj},\n    {\"////one/two\"_kj, kj::Maybe(\"file:///\"_kj), \"file:////one/two\"_kj},\n    {\"file:///.//\"_kj, kj::Maybe(\"file:////\"_kj), \"file:////\"_kj},\n    {\"file:.//p\"_kj, kj::none, \"file:////p\"_kj}, {\"file:/.//p\"_kj, kj::none, \"file:////p\"_kj},\n    {\"http://[1:0::]\"_kj, kj::Maybe(\"http://example.net/\"_kj), \"http://[1::]/\"_kj},\n    {\"sc://ñ\"_kj, kj::none, \"sc://%C3%B1\"_kj}, {\"sc://ñ?x\"_kj, kj::none, \"sc://%C3%B1?x\"_kj},\n    {\"sc://ñ#x\"_kj, kj::none, \"sc://%C3%B1#x\"_kj},\n    {\"#x\"_kj, kj::Maybe(\"sc://ñ\"_kj), \"sc://%C3%B1#x\"_kj},\n    {\"?x\"_kj, kj::Maybe(\"sc://ñ\"_kj), \"sc://%C3%B1?x\"_kj}, {\"sc://?\"_kj, kj::none, \"sc://?\"_kj},\n    {\"sc://#\"_kj, kj::none, \"sc://#\"_kj}, {\"///\"_kj, kj::Maybe(\"sc://x/\"_kj), \"sc:///\"_kj},\n    {\"////\"_kj, kj::Maybe(\"sc://x/\"_kj), \"sc:////\"_kj},\n    {\"////x/\"_kj, kj::Maybe(\"sc://x/\"_kj), \"sc:////x/\"_kj},\n    {\"tftp://foobar.com/someconfig;mode=netascii\"_kj, kj::none,\n      \"tftp://foobar.com/someconfig;mode=netascii\"_kj},\n    {\"telnet://user:pass@foobar.com:23/\"_kj, kj::none, \"telnet://user:pass@foobar.com:23/\"_kj},\n    {\"ut2004://10.10.10.10:7777/Index.ut2\"_kj, kj::none, \"ut2004://10.10.10.10:7777/Index.ut2\"_kj},\n    {\"redis://foo:bar@somehost:6379/0?baz=bam&qux=baz\"_kj, kj::none,\n      \"redis://foo:bar@somehost:6379/0?baz=bam&qux=baz\"_kj},\n    {\"rsync://foo@host:911/sup\"_kj, kj::none, \"rsync://foo@host:911/sup\"_kj},\n    {\"git://github.com/foo/bar.git\"_kj, kj::none, \"git://github.com/foo/bar.git\"_kj},\n    {\"irc://myserver.com:6999/channel?passwd\"_kj, kj::none,\n      \"irc://myserver.com:6999/channel?passwd\"_kj},\n    {\"dns://fw.example.org:9999/foo.bar.org?type=TXT\"_kj, kj::none,\n      \"dns://fw.example.org:9999/foo.bar.org?type=TXT\"_kj},\n    {\"ldap://localhost:389/ou=People,o=JNDITutorial\"_kj, kj::none,\n      \"ldap://localhost:389/ou=People,o=JNDITutorial\"_kj},\n    {\"git+https://github.com/foo/bar\"_kj, kj::none, \"git+https://github.com/foo/bar\"_kj},\n    {\"urn:ietf:rfc:2648\"_kj, kj::none, \"urn:ietf:rfc:2648\"_kj},\n    {\"tag:joe@example.org,2001:foo/bar\"_kj, kj::none, \"tag:joe@example.org,2001:foo/bar\"_kj},\n    {\"non-spec:/.//\"_kj, kj::none, \"non-spec:/.//\"_kj},\n    {\"non-spec:/..//\"_kj, kj::none, \"non-spec:/.//\"_kj},\n    {\"non-spec:/a/..//\"_kj, kj::none, \"non-spec:/.//\"_kj},\n    {\"non-spec:/.//path\"_kj, kj::none, \"non-spec:/.//path\"_kj},\n    {\"non-spec:/..//path\"_kj, kj::none, \"non-spec:/.//path\"_kj},\n    {\"non-spec:/a/..//path\"_kj, kj::none, \"non-spec:/.//path\"_kj},\n    {\"/.//path\"_kj, kj::Maybe(\"non-spec:/p\"_kj), \"non-spec:/.//path\"_kj},\n    {\"/..//path\"_kj, kj::Maybe(\"non-spec:/p\"_kj), \"non-spec:/.//path\"_kj},\n    {\"..//path\"_kj, kj::Maybe(\"non-spec:/p\"_kj), \"non-spec:/.//path\"_kj},\n    {\"a/..//path\"_kj, kj::Maybe(\"non-spec:/p\"_kj), \"non-spec:/.//path\"_kj},\n    {\"\"_kj, kj::Maybe(\"non-spec:/..//p\"_kj), \"non-spec:/.//p\"_kj},\n    {\"path\"_kj, kj::Maybe(\"non-spec:/..//p\"_kj), \"non-spec:/.//path\"_kj},\n    {\"../path\"_kj, kj::Maybe(\"non-spec:/.//p\"_kj), \"non-spec:/path\"_kj},\n    {\"non-special://%E2%80%A0/\"_kj, kj::none, \"non-special://%E2%80%A0/\"_kj},\n    {\"non-special://H%4fSt/path\"_kj, kj::none, \"non-special://H%4fSt/path\"_kj},\n    {\"non-special://[1:2:0:0:5:0:0:0]/\"_kj, kj::none, \"non-special://[1:2:0:0:5::]/\"_kj},\n    {\"non-special://[1:2:0:0:0:0:0:3]/\"_kj, kj::none, \"non-special://[1:2::3]/\"_kj},\n    {\"non-special://[1:2::3]:80/\"_kj, kj::none, \"non-special://[1:2::3]:80/\"_kj},\n    {\"blob:https://example.com:443/\"_kj, kj::none, \"blob:https://example.com:443/\"_kj},\n    {\"blob:http://example.org:88/\"_kj, kj::none, \"blob:http://example.org:88/\"_kj},\n    {\"blob:d3958f5c-0777-0845-9dcf-2cb28783acaf\"_kj, kj::none,\n      \"blob:d3958f5c-0777-0845-9dcf-2cb28783acaf\"_kj},\n    {\"blob:\"_kj, kj::none, \"blob:\"_kj}, {\"blob:blob:\"_kj, kj::none, \"blob:blob:\"_kj},\n    {\"blob:blob:https://example.org/\"_kj, kj::none, \"blob:blob:https://example.org/\"_kj},\n    {\"blob:about:blank\"_kj, kj::none, \"blob:about:blank\"_kj},\n    {\"blob:file://host/path\"_kj, kj::none, \"blob:file://host/path\"_kj},\n    {\"blob:ftp://host/path\"_kj, kj::none, \"blob:ftp://host/path\"_kj},\n    {\"blob:ws://example.org/\"_kj, kj::none, \"blob:ws://example.org/\"_kj},\n    {\"blob:wss://example.org/\"_kj, kj::none, \"blob:wss://example.org/\"_kj},\n    {\"blob:http%3a//example.org/\"_kj, kj::none, \"blob:http%3a//example.org/\"_kj},\n    {\"http://0x7f.0.0.0x7g\"_kj, kj::none, \"http://0x7f.0.0.0x7g/\"_kj},\n    {\"http://0X7F.0.0.0X7G\"_kj, kj::none, \"http://0x7f.0.0.0x7g/\"_kj},\n    {\"http://[0:1:0:1:0:1:0:1]\"_kj, kj::none, \"http://[0:1:0:1:0:1:0:1]/\"_kj},\n    {\"http://[1:0:1:0:1:0:1:0]\"_kj, kj::none, \"http://[1:0:1:0:1:0:1:0]/\"_kj},\n    {\"http://example.org/test?\\\"\"_kj, kj::none, \"http://example.org/test?%22\"_kj},\n    {\"http://example.org/test?#\"_kj, kj::none, \"http://example.org/test?#\"_kj},\n    {\"http://example.org/test?<\"_kj, kj::none, \"http://example.org/test?%3C\"_kj},\n    {\"http://example.org/test?>\"_kj, kj::none, \"http://example.org/test?%3E\"_kj},\n    {\"http://example.org/test?⌣\"_kj, kj::none, \"http://example.org/test?%E2%8C%A3\"_kj},\n    {\"http://example.org/test?%23%23\"_kj, kj::none, \"http://example.org/test?%23%23\"_kj},\n    {\"http://example.org/test?%GH\"_kj, kj::none, \"http://example.org/test?%GH\"_kj},\n    {\"http://example.org/test?a#%EF\"_kj, kj::none, \"http://example.org/test?a#%EF\"_kj},\n    {\"http://example.org/test?a#%GH\"_kj, kj::none, \"http://example.org/test?a#%GH\"_kj},\n    {\"test-a-colon-slash.html\"_kj, kj::Maybe(\"a:/\"_kj), \"a:/test-a-colon-slash.html\"_kj},\n    {\"test-a-colon-slash-slash.html\"_kj, kj::Maybe(\"a://\"_kj),\n      \"a:///test-a-colon-slash-slash.html\"_kj},\n    {\"test-a-colon-slash-b.html\"_kj, kj::Maybe(\"a:/b\"_kj), \"a:/test-a-colon-slash-b.html\"_kj},\n    {\"test-a-colon-slash-slash-b.html\"_kj, kj::Maybe(\"a://b\"_kj),\n      \"a://b/test-a-colon-slash-slash-b.html\"_kj},\n    {\"http://example.org/test?a#b\\0c\"_kj, kj::none, \"http://example.org/test?a#b%00c\"_kj},\n    {\"non-spec://example.org/test?a#b\\0c\"_kj, kj::none, \"non-spec://example.org/test?a#b%00c\"_kj},\n    {\"non-spec:/test?a#b\\0c\"_kj, kj::none, \"non-spec:/test?a#b%00c\"_kj},\n    {\"10.0.0.7:8080/foo.html\"_kj, kj::Maybe(\"file:///some/dir/bar.html\"_kj),\n      \"file:///some/dir/10.0.0.7:8080/foo.html\"_kj},\n    {\"a!@$*=/foo.html\"_kj, kj::Maybe(\"file:///some/dir/bar.html\"_kj),\n      \"file:///some/dir/a!@$*=/foo.html\"_kj},\n    {\"a1234567890-+.:foo/bar\"_kj, kj::Maybe(\"http://example.com/dir/file\"_kj),\n      \"a1234567890-+.:foo/bar\"_kj},\n    {\"file://a­b/p\"_kj, kj::none, \"file://ab/p\"_kj},\n    {\"file://a%C2%ADb/p\"_kj, kj::none, \"file://ab/p\"_kj},\n    {\"file://loC𝐀𝐋𝐇𝐨𝐬𝐭/usr/bin\"_kj, kj::none, \"file:///usr/bin\"_kj},\n    {\"#link\"_kj, kj::Maybe(\"https://example.org/##link\"_kj), \"https://example.org/#link\"_kj},\n    {\"non-special:cannot-be-a-base-url-\\0\u0001\u001f\u001e~\"_kj, kj::none,\n      \"non-special:cannot-be-a-base-url-%00%01%1F%1E~%7F%C2%80\"_kj},\n    {\"https://www.example.com/path{path.html?query'=query#fragment<fragment\"_kj, kj::none,\n      \"https://www.example.com/path%7B%7Fpath.html?query%27%7F=query#fragment%3C%7Ffragment\"_kj},\n    {\"https://user:pass[@foo/bar\"_kj, kj::Maybe(\"http://example.org\"_kj),\n      \"https://user:pass%5B%7F@foo/bar\"_kj},\n    {\"foo:// !\\\"$%&'()*+,-.;<=>@[\\\\]^_`{|}~@host/\"_kj, kj::none,\n      \"foo://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/\"_kj},\n    {\"wss:// !\\\"$%&'()*+,-.;<=>@[]^_`{|}~@host/\"_kj, kj::none,\n      \"wss://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/\"_kj},\n    {\"foo://joe: !\\\"$%&'()*+,-.:;<=>@[\\\\]^_`{|}~@host/\"_kj, kj::none,\n      \"foo://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/\"_kj},\n    {\"wss://joe: !\\\"$%&'()*+,-.:;<=>@[]^_`{|}~@host/\"_kj, kj::none,\n      \"wss://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/\"_kj},\n    {\"foo://!\\\"$%&'()*+,-.;=_`{}~/\"_kj, kj::none, \"foo://!\\\"$%&'()*+,-.;=_`{}~/\"_kj},\n    {\"wss://!\\\"$&'()*+,-.;=_`{}~/\"_kj, kj::none, \"wss://!\\\"$&'()*+,-.;=_`{}~/\"_kj},\n    {\"foo://host/dir/? !\\\"$%&'()*+,-./:;<=>?@[\\\\]^_`{|}~\"_kj, kj::none,\n      \"foo://host/dir/?%20!%22$%&'()*+,-./:;%3C=%3E?@[\\\\]^_`{|}~\"_kj},\n    {\"wss://host/dir/? !\\\"$%&'()*+,-./:;<=>?@[\\\\]^_`{|}~\"_kj, kj::none,\n      \"wss://host/dir/?%20!%22$%&%27()*+,-./:;%3C=%3E?@[\\\\]^_`{|}~\"_kj},\n    {\"foo://host/dir/# !\\\"#$%&'()*+,-./:;<=>?@[\\\\]^_`{|}~\"_kj, kj::none,\n      \"foo://host/dir/#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\\\]^_%60{|}~\"_kj},\n    {\"wss://host/dir/# !\\\"#$%&'()*+,-./:;<=>?@[\\\\]^_`{|}~\"_kj, kj::none,\n      \"wss://host/dir/#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\\\]^_%60{|}~\"_kj},\n    {\"abc:rootless\"_kj, kj::Maybe(\"abc://host/path\"_kj), \"abc:rootless\"_kj},\n    {\"abc:rootless\"_kj, kj::Maybe(\"abc:/path\"_kj), \"abc:rootless\"_kj},\n    {\"abc:rootless\"_kj, kj::Maybe(\"abc:path\"_kj), \"abc:rootless\"_kj},\n    {\"abc:/rooted\"_kj, kj::Maybe(\"abc://host/path\"_kj), \"abc:/rooted\"_kj},\n    {\"http://foo.09..\"_kj, kj::none, \"http://foo.09../\"_kj},\n    {\"https://x/\\0y\"_kj, kj::none, \"https://x/%00y\"_kj},\n    {\"https://x/?\\0y\"_kj, kj::none, \"https://x/?%00y\"_kj},\n    {\"https://x/?#\\0y\"_kj, kj::none, \"https://x/?#%00y\"_kj},\n    {\"https://x/￿y\"_kj, kj::none, \"https://x/%EF%BF%BFy\"_kj},\n    {\"https://x/?￿y\"_kj, kj::none, \"https://x/?%EF%BF%BFy\"_kj},\n    {\"https://x/?#￿y\"_kj, kj::none, \"https://x/?#%EF%BF%BFy\"_kj},\n    {\"non-special:\\0y\"_kj, kj::none, \"non-special:%00y\"_kj},\n    {\"non-special:x/\\0y\"_kj, kj::none, \"non-special:x/%00y\"_kj},\n    {\"non-special:x/?\\0y\"_kj, kj::none, \"non-special:x/?%00y\"_kj},\n    {\"non-special:x/?#\\0y\"_kj, kj::none, \"non-special:x/?#%00y\"_kj},\n    {\"non-special:￿y\"_kj, kj::none, \"non-special:%EF%BF%BFy\"_kj},\n    {\"non-special:x/￿y\"_kj, kj::none, \"non-special:x/%EF%BF%BFy\"_kj},\n    {\"non-special:x/?￿y\"_kj, kj::none, \"non-special:x/?%EF%BF%BFy\"_kj},\n    {\"non-special:x/?#￿y\"_kj, kj::none, \"non-special:x/?#%EF%BF%BFy\"_kj},\n    {\"https://example.com/\\\"quoted\\\"\"_kj, kj::none, \"https://example.com/%22quoted%22\"_kj},\n    {\"https://a%C2%ADb/\"_kj, kj::none, \"https://ab/\"_kj},\n    {\"data://example.com:8080/pathname?search#hash\"_kj, kj::none,\n      \"data://example.com:8080/pathname?search#hash\"_kj},\n    {\"data:///test\"_kj, kj::none, \"data:///test\"_kj},\n    {\"data://test/a/../b\"_kj, kj::none, \"data://test/b\"_kj},\n    {\"javascript://example.com:8080/pathname?search#hash\"_kj, kj::none,\n      \"javascript://example.com:8080/pathname?search#hash\"_kj},\n    {\"javascript:///test\"_kj, kj::none, \"javascript:///test\"_kj},\n    {\"javascript://test/a/../b\"_kj, kj::none, \"javascript://test/b\"_kj},\n    {\"mailto://example.com:8080/pathname?search#hash\"_kj, kj::none,\n      \"mailto://example.com:8080/pathname?search#hash\"_kj},\n    {\"mailto:///test\"_kj, kj::none, \"mailto:///test\"_kj},\n    {\"mailto://test/a/../b\"_kj, kj::none, \"mailto://test/b\"_kj},\n    {\"intent://example.com:8080/pathname?search#hash\"_kj, kj::none,\n      \"intent://example.com:8080/pathname?search#hash\"_kj},\n    {\"intent:///test\"_kj, kj::none, \"intent:///test\"_kj},\n    {\"intent://test/a/../b\"_kj, kj::none, \"intent://test/b\"_kj},\n    {\"urn://example.com:8080/pathname?search#hash\"_kj, kj::none,\n      \"urn://example.com:8080/pathname?search#hash\"_kj},\n    {\"urn:///test\"_kj, kj::none, \"urn:///test\"_kj},\n    {\"urn://test/a/../b\"_kj, kj::none, \"urn://test/b\"_kj},\n    {\"turn://example.com:8080/pathname?search#hash\"_kj, kj::none,\n      \"turn://example.com:8080/pathname?search#hash\"_kj},\n    {\"turn:///test\"_kj, kj::none, \"turn:///test\"_kj},\n    {\"turn://test/a/../b\"_kj, kj::none, \"turn://test/b\"_kj},\n    {\"stun://example.com:8080/pathname?search#hash\"_kj, kj::none,\n      \"stun://example.com:8080/pathname?search#hash\"_kj},\n    {\"stun:///test\"_kj, kj::none, \"stun:///test\"_kj},\n    {\"stun://test/a/../b\"_kj, kj::none, \"stun://test/b\"_kj}, {\"w://x:0\"_kj, kj::none, \"w://x:0\"_kj},\n    {\"west://x:0\"_kj, kj::none, \"west://x:0\"_kj}};\n\n  for (auto& testCase: TESTS) {\n    test(testCase.input, testCase.base, testCase.result);\n  }\n}\n\nKJ_TEST(\"Search params (1)\") {\n  UrlSearchParams params;\n  params.append(\"foo\"_kj, \"bar\"_kj);\n  KJ_ASSERT(params.toStr() == \"foo=bar\"_kj);\n}\n\nKJ_TEST(\"Search params (2)\") {\n  auto params = KJ_ASSERT_NONNULL(UrlSearchParams::tryParse(\"foo=bar&a=b&a=c\"_kj));\n  KJ_ASSERT(params.has(\"a\"_kj));\n  KJ_ASSERT(params.has(\"foo\"_kj, kj::Maybe(\"bar\"_kj)));\n  KJ_ASSERT(!params.has(\"foo\"_kj, kj::Maybe(\"baz\"_kj)));\n  KJ_ASSERT(KJ_ASSERT_NONNULL(params.get(\"a\"_kj)) == \"b\"_kj);\n\n  auto all = params.getAll(\"a\"_kj);\n  KJ_ASSERT(all.size() == 2);\n  KJ_ASSERT(all[0] == \"b\"_kj);\n  KJ_ASSERT(all[1] == \"c\"_kj);\n\n  params.delete_(\"foo\"_kj);\n  params.delete_(\"a\"_kj, kj::Maybe(\"c\"_kj));\n\n  params.set(\"a\"_kj, \"z\"_kj);\n  KJ_ASSERT(kj::str(params) == \"a=z\");\n}\n\n// ======================================================================================\n\nKJ_TEST(\"Minimal URL Parse\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse 2\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org/\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - Username\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://abc@example.org/\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == \"abc\"_kj);\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - Username and Password\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://abc:xyz@example.org/\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == \"abc\"_kj);\n  KJ_ASSERT(url.getPassword() == \"xyz\"_kj);\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - Password, no Username\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://:xyz@example.org/\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == \"xyz\"_kj);\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - Port (non-default)\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org:123/\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org:123\"_kj);\n  KJ_ASSERT(url.getHostname() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == \"123\"_kj);\n  KJ_ASSERT(url.getPathname() == \"/\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - Port (default)\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org:443/\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - Port delimiter with no port)\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org:/\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - One path segment\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org/abc\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/abc\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - Leading single dot segment\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org/./abc\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/abc\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - Multiple single dot segment\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org/././././abc\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/abc\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - Leading double dot segment\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org/../abc\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/abc\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - Leading mixed dot segment\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org/../.././.././abc\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/abc\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - Three path segments\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org/a/b/c\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/a/b/c\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - Three path segments with double dot\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org/a/b/../c\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/a/c\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - Three path segments with single dot\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org/a/b/./c\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/a/b/c\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - Query present but empty\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org?\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - Query minimal\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org?123\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/\"_kj);\n  KJ_ASSERT(url.getSearch() == \"?123\"_kj);\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - Query minimal after missing port\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org:?123\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/\"_kj);\n  KJ_ASSERT(url.getSearch() == \"?123\"_kj);\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - Query minimal after missing port and empty path\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org:/?123\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/\"_kj);\n  KJ_ASSERT(url.getSearch() == \"?123\"_kj);\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - Fragment present but empty\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org#\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == kj::String());\n}\n\nKJ_TEST(\"Minimal URL Parse - Fragment minimal\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org#123\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == \"#123\"_kj);\n}\n\nKJ_TEST(\"Minimal URL Parse - Fragment minimal\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org?#123\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/\"_kj);\n  KJ_ASSERT(url.getSearch() == kj::String());\n  KJ_ASSERT(url.getHash() == \"#123\"_kj);\n}\n\nKJ_TEST(\"Minimal URL Parse - Fragment minimal\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://example.org?abc#123\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == kj::String());\n  KJ_ASSERT(url.getPassword() == kj::String());\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == kj::String());\n  KJ_ASSERT(url.getPathname() == \"/\"_kj);\n  KJ_ASSERT(url.getSearch() == \"?abc\"_kj);\n  KJ_ASSERT(url.getHash() == \"#123\"_kj);\n}\n\nKJ_TEST(\"Minimal URL Parse - All together\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://abc:xyz@example.org:123/a/b/c?abc#123\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n  KJ_ASSERT(url.getUsername() == \"abc\"_kj);\n  KJ_ASSERT(url.getPassword() == \"xyz\"_kj);\n  KJ_ASSERT(url.getHost() == \"example.org:123\"_kj);\n  KJ_ASSERT(url.getHostname() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPort() == \"123\"_kj);\n  KJ_ASSERT(url.getPathname() == \"/a/b/c\"_kj);\n  KJ_ASSERT(url.getSearch() == \"?abc\"_kj);\n  KJ_ASSERT(url.getHash() == \"#123\"_kj);\n}\n\nKJ_TEST(\"Minimal URL Parse - Not special (data URL)\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"data:something\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"data:\"_kj);\n  KJ_ASSERT(url.getPathname() == \"something\"_kj);\n}\n\nKJ_TEST(\"Minimal URL Parse - unknown scheme\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"com.tapbots.Ivory.219:/request_token?code=8\"_kj));\n\n  KJ_ASSERT(url.getProtocol() == \"com.tapbots.ivory.219:\"_kj);\n  KJ_ASSERT(url.getPathname() == \"/request_token\"_kj);\n  KJ_ASSERT(url.getSearch() == \"?code=8\"_kj);\n}\n\nKJ_TEST(\"Special scheme URLS\") {\n  kj::String tests[] = {\n    kj::str(\"http://example.org\"),\n    kj::str(\"https://example.org\"),\n    kj::str(\"ftp://example.org\"),\n    kj::str(\"ws://example.org\"),\n    kj::str(\"wss://example.org\"),\n    kj::str(\"file:///example\"),\n  };\n\n  for (const auto& test: tests) {\n    KJ_ASSERT_NONNULL(Url::tryParse(test.asPtr()));\n  }\n}\n\nKJ_TEST(\"Trim leading and trailing control/space\") {\n  auto input = kj::str(\" \\0\\1 http://example.org \\2\\3 \"_kj);\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(input.asPtr()));\n  KJ_ASSERT(url.getProtocol() == \"http:\"_kj);\n  KJ_ASSERT(url.getHost() == \"example.org\"_kj);\n  KJ_ASSERT(url.getPathname() == \"/\"_kj);\n}\n\nKJ_TEST(\"Percent encoding in username/password\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://%66oo:%66oo@example.com/\"_kj));\n  KJ_ASSERT(url.getUsername() == \"%66oo\"_kj);\n  KJ_ASSERT(url.getPassword() == \"%66oo\"_kj);\n}\n\nKJ_TEST(\"Percent encoding in hostname\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://%66oo\"_kj));\n  KJ_ASSERT(url.getHost() == \"foo\"_kj);\n}\n\nKJ_TEST(\"Percent encoding in hostname\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://%66oo\"_kj));\n  KJ_ASSERT(url.getHost() == \"foo\"_kj);\n}\n\nKJ_TEST(\"Percent encoding in pathname\") {\n  {\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://example.org/%2e/%31%32%ZZ\"_kj));\n    KJ_ASSERT(url.getPathname() == \"/%31%32%ZZ\"_kj);\n    // The %2e is properly detected as a single dot segment.\n    // The invalid percent encoded %ZZ is ignored.\n  }\n  {\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://example.org/%2e/%31%32%ZZ/%2E\"_kj));\n    KJ_ASSERT(url.getPathname() == \"/%31%32%ZZ/\"_kj);\n  }\n}\n\nKJ_TEST(\"Percent encoding in query\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://example.org?/%2e/%31%32%ZZ\"_kj));\n  KJ_ASSERT(url.getSearch() == \"?/%2e/%31%32%ZZ\"_kj);\n  // The invalid percent encoded %ZZ is ignored.\n}\n\nKJ_TEST(\"Percent encoding in fragment\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://example.org#/%2e/%31%32%ZZ\"_kj));\n  KJ_ASSERT(url.getHash() == \"#/%2e/%31%32%ZZ\"_kj);\n  // The invalid percent encoded %ZZ is ignored.\n}\n\nKJ_TEST(\"Percent encoding of non-ascii characters in path, query, fragment\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://example.org/café?café#café\"_kj));\n  KJ_ASSERT(url.getPathname() == \"/caf%C3%A9\"_kj);\n  KJ_ASSERT(url.getSearch() == \"?caf%C3%A9\"_kj);\n  KJ_ASSERT(url.getHash() == \"#caf%C3%A9\"_kj);\n}\n\nKJ_TEST(\"IDNA-conversion non-ascii characters in hostname\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://café.com\"_kj));\n  KJ_ASSERT(url.getHost() == \"xn--caf-dma.com\"_kj);\n}\n\nKJ_TEST(\"IPv4 in hostname\") {\n  {\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://123.210.123.121\"_kj));\n    KJ_ASSERT(url.getHost() == \"123.210.123.121\"_kj);\n  }\n\n  {\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://2077391737\"_kj));\n    KJ_ASSERT(url.getHost() == \"123.210.123.121\"_kj);\n  }\n\n  {\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://1.1\"_kj));\n    KJ_ASSERT(url.getHost() == \"1.0.0.1\"_kj);\n  }\n\n  {\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://0x1.0x1\"_kj));\n    KJ_ASSERT(url.getHost() == \"1.0.0.1\"_kj);\n  }\n\n  {\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://01.0x1\"_kj));\n    KJ_ASSERT(url.getHost() == \"1.0.0.1\"_kj);\n  }\n\n  {\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://0x1000001\"_kj));\n    KJ_ASSERT(url.getHost() == \"1.0.0.1\"_kj);\n  }\n\n  {\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://0100000001\"_kj));\n    KJ_ASSERT(url.getHost() == \"1.0.0.1\"_kj);\n  }\n\n  {\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://192.168.1\"_kj));\n    KJ_ASSERT(url.getHost() == \"192.168.0.1\"_kj);\n  }\n\n  {\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://192.0xa80001\"_kj));\n    KJ_ASSERT(url.getHost() == \"192.168.0.1\"_kj);\n  }\n\n  {\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://192.11010049\"_kj));\n    KJ_ASSERT(url.getHost() == \"192.168.0.1\"_kj);\n  }\n\n  {\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://0300.11010049\"_kj));\n    KJ_ASSERT(url.getHost() == \"192.168.0.1\"_kj);\n  }\n\n  {\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://0300.0xa80001\"_kj));\n    KJ_ASSERT(url.getHost() == \"192.168.0.1\"_kj);\n  }\n\n  {\n    // Yes, this is a valid IPv4 address also.\n    // You might be asking yourself, why would anyone do this?\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"http://0xc0.11010049\"_kj));\n    KJ_ASSERT(url.getHost() == \"192.168.0.1\"_kj);\n  }\n\n  {\n    KJ_ASSERT(Url::tryParse(\"https://999.999.999.999\"_kj) == kj::none);\n    KJ_ASSERT(Url::tryParse(\"https://123.999.999.999\"_kj) == kj::none);\n    KJ_ASSERT(Url::tryParse(\"https://123.123.999.999\"_kj) == kj::none);\n    KJ_ASSERT(Url::tryParse(\"https://123.123.123.999\"_kj) == kj::none);\n    KJ_ASSERT(Url::tryParse(\"https://4294967296\"_kj) == kj::none);\n  }\n}\n\nKJ_TEST(\"IPv6 in hostname\") {\n  {\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://[1:1:1:1:1:1:1:1]\"_kj));\n    KJ_ASSERT(url.getHost() == \"[1:1:1:1:1:1:1:1]\"_kj);\n  }\n\n  {\n    // Compressed segments work\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://[1::1]\"_kj));\n    KJ_ASSERT(url.getHost() == \"[1::1]\"_kj);\n  }\n\n  {\n    // Compressed segments work\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://[::]\"_kj));\n    KJ_ASSERT(url.getHost() == \"[::]\"_kj);\n  }\n\n  {\n    // Normalized form is shortest, lowercase serialization.\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://[11:AF:0:0:0::0001]\"_kj));\n    KJ_ASSERT(url.getHost() == \"[11:af::1]\"_kj);\n  }\n\n  {\n    // IPv4-in-IPv6 syntax is supported\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"https://[2001:db8:122:344::192.0.2.33]\"_kj));\n    KJ_ASSERT(url.getHost() == \"[2001:db8:122:344::c000:221]\"_kj);\n  }\n\n  KJ_ASSERT(Url::tryParse(\"https://[zz::top]\"_kj) == kj::none);\n}\n\nKJ_TEST(\"javascript: URLS\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"jAvAsCrIpT: alert('boo'); \"_kj));\n  KJ_ASSERT(url.getProtocol() == \"javascript:\"_kj);\n  KJ_ASSERT(url.getPathname() == \" alert('boo');\"_kj);\n}\n\nKJ_TEST(\"data: URLS\") {\n  {\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"data:,Hello%2C%20World%21\"_kj));\n    KJ_ASSERT(url.getProtocol() == \"data:\"_kj);\n    KJ_ASSERT(url.getPathname() == \",Hello%2C%20World%21\"_kj);\n  }\n  {\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==\"_kj));\n    KJ_ASSERT(url.getProtocol() == \"data:\"_kj);\n    KJ_ASSERT(url.getPathname() == \"text/plain;base64,SGVsbG8sIFdvcmxkIQ==\"_kj);\n  }\n  {\n    auto url = KJ_ASSERT_NONNULL(\n        Url::tryParse(\"data:text/html,%3Ch1%3EHello%2C%20World%21%3C%2Fh1%3E\"_kj));\n    KJ_ASSERT(url.getProtocol() == \"data:\"_kj);\n    KJ_ASSERT(url.getPathname() == \"text/html,%3Ch1%3EHello%2C%20World%21%3C%2Fh1%3E\"_kj);\n  }\n  {\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"data:text/html,<script>alert('hi');</script>\"_kj));\n    KJ_ASSERT(url.getProtocol() == \"data:\"_kj);\n    KJ_ASSERT(url.getPathname() == \"text/html,<script>alert('hi');</script>\"_kj);\n  }\n}\n\nKJ_TEST(\"blob: URLS\") {\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"blob:https://example.org\"_kj));\n  KJ_ASSERT(url.getProtocol() == \"blob:\"_kj);\n  KJ_ASSERT(url.getPathname() == \"https://example.org\"_kj);\n}\n\nKJ_TEST(\"Relative URLs\") {\n  {\n    auto url = KJ_ASSERT_NONNULL(\n        Url::tryParse(kj::String(), \"https://abc:def@example.org:81/a/b/c?query#fragment\"_kj));\n    KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n    KJ_ASSERT(url.getUsername() == \"abc\"_kj);\n    KJ_ASSERT(url.getPassword() == \"def\"_kj);\n    KJ_ASSERT(url.getHost() == \"example.org:81\"_kj);\n    KJ_ASSERT(url.getPathname() == \"/a/b/c\"_kj);\n    KJ_ASSERT(url.getSearch() == \"?query\"_kj);\n    KJ_ASSERT(url.getHash() == kj::String());\n  }\n\n  {\n    auto url = KJ_ASSERT_NONNULL(\n        Url::tryParse(\"/xyz\"_kj, \"https://abc:def@example.org:81/a/b/c?query#fragment\"_kj));\n    KJ_ASSERT(url.getProtocol() == \"https:\"_kj);\n    KJ_ASSERT(url.getUsername() == \"abc\"_kj);\n    KJ_ASSERT(url.getPassword() == \"def\"_kj);\n    KJ_ASSERT(url.getHost() == \"example.org:81\"_kj);\n    KJ_ASSERT(url.getPathname() == \"/xyz\"_kj);\n    KJ_ASSERT(url.getSearch() == kj::String());\n    KJ_ASSERT(url.getHash() == kj::String());\n  }\n\n  {\n    auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"../../../../../././../../././../.././abc\"_kj,\n        \"https://abc:def@example.org:81/a/b/c?query#fragment\"_kj));\n    KJ_ASSERT(url.getPathname() == \"/abc\"_kj);\n  }\n\n  {\n    auto url = Url::tryParse(\"/anything\"_kj, \"data:cannot-be-base\"_kj);\n    KJ_ASSERT(url == kj::none);\n  }\n}\n\nKJ_TEST(\"Can parse\") {\n  {\n    KJ_ASSERT(Url::canParse(\"http://example.org\"_kj));\n    KJ_ASSERT(Url::canParse(\"foo\"_kj, \"http://example.org\"_kj));\n    KJ_ASSERT(!Url::canParse(\"this is not a parseable URL\"_kj));\n    KJ_ASSERT(!Url::canParse(\"foo\"_kj, \"base is not a URL\"_kj));\n  }\n}\n\nKJ_TEST(\"Normalize path for comparison and cloning\") {\n  // The URL parser does not percent-decode characters in the result.\n  // For instance, even tho `f` does not need to be percent encoded,\n  // the value `%66oo` will be returned as is. In some cases we want\n  // to be able to treat `%66oo` and `foo` as equivalent for the sake\n  // of comparison and cloning. This is what the NORMALIZE_PATH option\n  // is for. It will percent-decode the path, then re-encode it.\n  // Note that there is a definite performance cost to this, so it\n  // should only be used when necessary.\n\n  auto url1 = \"file:///%66oo/boo%fe\"_url;\n  auto url2 = \"file:///foo/boo%fe\"_url;\n  auto url3 = \"file:///foo/boo%FE\"_url;\n\n  auto url4 = url1.clone(Url::EquivalenceOption::NORMALIZE_PATH);\n\n  KJ_ASSERT(url1.equal(url2, Url::EquivalenceOption::NORMALIZE_PATH));\n  KJ_ASSERT(url2.equal(url1, Url::EquivalenceOption::NORMALIZE_PATH));\n  KJ_ASSERT(url3 == url4);\n\n  // This one will not be equivalent because the %2f is not decoded\n  auto url5 = KJ_ASSERT_NONNULL(Url::tryParse(\"file:///foo%2fboo%fe\"_kj));\n\n  KJ_ASSERT(!url5.equal(url2, Url::EquivalenceOption::NORMALIZE_PATH));\n\n  auto url6 = url5.clone(Url::EquivalenceOption::NORMALIZE_PATH);\n  KJ_ASSERT(url6.getHref() == \"file:///foo%2Fboo%FE\"_kj);\n\n  auto url7 = \"file:///foo%2Fboo%2F\"_url;\n  url7 = url7.clone(Url::EquivalenceOption::NORMALIZE_PATH);\n  KJ_ASSERT(url7.getHref() == \"file:///foo%2Fboo%2F\"_kj);\n\n  auto url8 = \"file:///foo%2F%2f/bar\"_url;\n  url8 = url8.clone(Url::EquivalenceOption::NORMALIZE_PATH);\n  KJ_ASSERT(url8.getHref() == \"file:///foo%2F%2F/bar\"_kj);\n\n  auto url9 = \"file:///foo%2f%2F/bar\"_url;\n  url9 = url9.clone(Url::EquivalenceOption::NORMALIZE_PATH);\n  KJ_ASSERT(url9.getHref() == \"file:///foo%2F%2F/bar\"_kj);\n}\n\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/url.c++",
    "content": "#include \"url.h\"\n\n#include <workerd/util/strings.h>\n\n#include <kj/hash.h>\n\nextern \"C\" {\n#include \"ada_c.h\"\n}\n#include \"ada.h\"\n\n#include <unicode/uchar.h>\n#include <unicode/utf8.h>\n\n#include <kj/debug.h>\n#include <kj/string-tree.h>\n#include <kj/vector.h>\n\n#include <algorithm>\n#include <regex>\n#include <string>\n#include <vector>\n\nnamespace workerd::jsg {\n\nnamespace {\nclass AdaOwnedStringDisposer: public kj::ArrayDisposer {\n public:\n  static const AdaOwnedStringDisposer INSTANCE;\n\n protected:\n  void disposeImpl(void* firstElement,\n      size_t elementSize,\n      size_t elementCount,\n      size_t capacity,\n      void (*destroyElement)(void*)) const override {\n    ada_owned_string data = {static_cast<const char*>(firstElement), elementCount};\n    ada_free_owned_string(data);\n  }\n};\nconst AdaOwnedStringDisposer AdaOwnedStringDisposer::INSTANCE;\n\nkj::Own<void> wrap(ada_url url) {\n  return kj::disposeWith<ada_free>(url);\n}\n\ntemplate <typename T>\nT getInner(const kj::Own<void>& inner) {\n  const void* value = inner.get();\n  KJ_DASSERT(value != nullptr);\n  return const_cast<T>(value);\n}\n\nkj::Array<const char> normalizePathEncoding(kj::ArrayPtr<const char> pathname) {\n  // Sadly, this is a bit tricky because we do not want to decode %2f as a slash.\n  // we want to keep those as is. So we'll split the input around those bits.\n  // Unfortunately we need to split on either %2f or %2F, so we'll need to search\n  // through ourselves. This is simple enough, tho. We'll percent decode as we go,\n  // re-encode the pieces and then join them back together with %2F.\n\n  static constexpr auto findNext = [](std::string_view input) -> kj::Maybe<size_t> {\n    size_t pos = input.find(\"%2\", 0);\n    if (pos != std::string_view::npos) {\n      if (input[pos + 2] == 'f' || input[pos + 2] == 'F') {\n        return pos;\n      }\n    }\n    return kj::none;\n  };\n\n  std::string_view input(pathname.begin(), pathname.end());\n  std::vector<std::string> parts;\n\n  while (true) {\n    if (input.empty()) {\n      parts.emplace_back(\"\");\n      break;\n    }\n    KJ_IF_SOME(pos, findNext(input)) {\n      parts.push_back(ada::unicode::percent_decode(input.substr(0, pos), 0));\n      input = input.substr(pos + 3);\n      continue;\n    } else {\n      // No more %2f or %2F found. Add input to parts\n      parts.push_back(ada::unicode::percent_decode(input, 0));\n      break;\n    }\n  }\n\n  std::string res;\n  bool first = true;\n  for (auto& part: parts) {\n    auto encoded = ada::unicode::percent_encode(part, ada::character_sets::PATH_PERCENT_ENCODE);\n    if (!first)\n      res += \"%2F\";\n    else\n      first = false;\n    res += encoded;\n  }\n\n  kj::Array<const char> ret = kj::heapArray<const char>(res.length());\n  memcpy(const_cast<char*>(ret.begin()), res.data(), res.length());\n  return kj::mv(ret);\n}\n\nclass StringTreeHolder final {\n public:\n  StringTreeHolder(): tree(kj::strTree()) {}\n  StringTreeHolder(kj::StringTree&& tree): tree(kj::mv(tree)) {}\n  StringTreeHolder(kj::StringPtr ptr): tree(kj::strTree(ptr)) {}\n  StringTreeHolder(kj::String str): tree(kj::strTree(kj::mv(str))) {}\n\n  template <typename... Params>\n  StringTreeHolder& append(Params&&... params) {\n    // Keep the tree from getting too deeply nested by flattening it out\n    // once the depth gets to a certain point.\n    if (++depth % 2048 == 0) {\n      tree = kj::strTree(tree.flatten(), kj::fwd<Params>(params)...);\n    } else {\n      tree = kj::strTree(kj::mv(tree), kj::fwd<Params>(params)...);\n    }\n    return *this;\n  }\n\n  operator kj::String() && {\n    return kj::mv(tree).flatten();\n  }\n\n  operator kj::StringTree() && {\n    return kj::mv(tree);\n  }\n\n private:\n  kj::StringTree tree;\n  size_t depth = 0;\n};\n\n}  // namespace\n\nUrl::Url(kj::Own<void> inner): inner(kj::mv(inner)) {}\n\nbool Url::operator==(const Url& other) const {\n  return getHref() == other.getHref();\n}\n\nbool Url::equal(const Url& other, EquivalenceOption option) const {\n  if (option == EquivalenceOption::DEFAULT) {\n    return *this == other;\n  }\n\n  auto otherPathname = other.getPathname();\n  auto thisPathname = getPathname();\n  kj::Array<const char> otherPathnameStore = nullptr;\n  kj::Array<const char> thisPathnameStore = nullptr;\n\n  if ((option & EquivalenceOption::NORMALIZE_PATH) == EquivalenceOption::NORMALIZE_PATH) {\n    otherPathnameStore = normalizePathEncoding(otherPathname);\n    otherPathname = otherPathnameStore;\n    thisPathnameStore = normalizePathEncoding(thisPathname);\n    thisPathname = thisPathnameStore;\n  }\n\n  // If we are ignoring fragments, we'll compare each component separately:\n  return (other.getProtocol() == getProtocol()) && (other.getHost() == getHost()) &&\n      (other.getUsername() == getUsername()) && (other.getPassword() == getPassword()) &&\n      (otherPathname == thisPathname) &&\n      (((option & EquivalenceOption::IGNORE_SEARCH) == EquivalenceOption::IGNORE_SEARCH)\n              ? true\n              : other.getSearch() == getSearch()) &&\n      (((option & EquivalenceOption::IGNORE_FRAGMENTS) == EquivalenceOption::IGNORE_FRAGMENTS)\n              ? true\n              : other.getHash() == getHash());\n}\n\nbool Url::canParse(kj::StringPtr input, kj::Maybe<kj::StringPtr> base) {\n  return canParse(kj::ArrayPtr<const char>(input), base);\n}\n\nbool Url::canParse(kj::ArrayPtr<const char> input, kj::Maybe<kj::ArrayPtr<const char>> base) {\n  KJ_IF_SOME(b, base) {\n    return ada_can_parse_with_base(input.begin(), input.size(), b.begin(), b.size());\n  }\n  return ada_can_parse(input.begin(), input.size());\n}\n\nkj::Maybe<Url> Url::tryParse(kj::StringPtr input, kj::Maybe<kj::StringPtr> base) {\n  return tryParse(kj::ArrayPtr<const char>(input), base);\n}\n\nkj::Maybe<Url> Url::tryParse(\n    kj::ArrayPtr<const char> input, kj::Maybe<kj::ArrayPtr<const char>> base) {\n  ada_url result = nullptr;\n  KJ_IF_SOME(b, base) {\n    result = ada_parse_with_base(input.begin(), input.size(), b.begin(), b.size());\n  } else {\n    result = ada_parse(input.begin(), input.size());\n  }\n  if (!ada_is_valid(result)) {\n    ada_free(result);\n    return kj::none;\n  }\n  return Url(wrap(result));\n}\n\nkj::Maybe<Url> Url::resolve(kj::ArrayPtr<const char> input) {\n  return tryParse(input, getHref());\n}\n\nkj::ArrayPtr<const char> Url::getHref() const {\n  ada_string href = ada_get_href(getInner<ada_url>(inner));\n  return kj::ArrayPtr<const char>(href.data, href.length);\n}\n\nkj::ArrayPtr<const char> Url::getUsername() const {\n  ada_string username = ada_get_username(getInner<ada_url>(inner));\n  return kj::ArrayPtr<const char>(username.data, username.length);\n}\n\nkj::ArrayPtr<const char> Url::getPassword() const {\n  ada_string password = ada_get_password(getInner<ada_url>(inner));\n  return kj::ArrayPtr<const char>(password.data, password.length);\n}\n\nkj::ArrayPtr<const char> Url::getPort() const {\n  ada_string port = ada_get_port(getInner<ada_url>(inner));\n  return kj::ArrayPtr<const char>(port.data, port.length);\n}\n\nkj::ArrayPtr<const char> Url::getHash() const {\n  ada_string hash = ada_get_hash(getInner<ada_url>(inner));\n  return kj::ArrayPtr<const char>(hash.data, hash.length);\n}\n\nkj::ArrayPtr<const char> Url::getHost() const {\n  ada_string host = ada_get_host(getInner<ada_url>(inner));\n  return kj::ArrayPtr<const char>(host.data, host.length);\n}\n\nkj::ArrayPtr<const char> Url::getHostname() const {\n  ada_string hostname = ada_get_hostname(getInner<ada_url>(inner));\n  return kj::ArrayPtr<const char>(hostname.data, hostname.length);\n}\n\nkj::ArrayPtr<const char> Url::getPathname() const {\n  ada_string path = ada_get_pathname(getInner<ada_url>(inner));\n  return kj::ArrayPtr<const char>(path.data, path.length);\n}\n\nkj::ArrayPtr<const char> Url::getSearch() const {\n  ada_string search = ada_get_search(getInner<ada_url>(inner));\n  return kj::ArrayPtr<const char>(search.data, search.length);\n}\n\nkj::ArrayPtr<const char> Url::getProtocol() const {\n  ada_string protocol = ada_get_protocol(getInner<ada_url>(inner));\n  return kj::ArrayPtr<const char>(protocol.data, protocol.length);\n}\n\nkj::Array<const char> Url::getOrigin() const {\n  ada_owned_string result = ada_get_origin(getInner<ada_url>(inner));\n  return kj::Array<const char>(\n      const_cast<char*>(result.data), result.length, AdaOwnedStringDisposer::INSTANCE);\n}\n\nbool Url::setHref(kj::ArrayPtr<const char> value) {\n  return ada_set_href(getInner<ada_url>(inner), value.begin(), value.size());\n}\n\nbool Url::setHost(kj::ArrayPtr<const char> value) {\n  return ada_set_host(getInner<ada_url>(inner), value.begin(), value.size());\n}\n\nbool Url::setHostname(kj::ArrayPtr<const char> value) {\n  return ada_set_hostname(getInner<ada_url>(inner), value.begin(), value.size());\n}\n\nbool Url::setProtocol(kj::ArrayPtr<const char> value) {\n  return ada_set_protocol(getInner<ada_url>(inner), value.begin(), value.size());\n}\n\nbool Url::setUsername(kj::ArrayPtr<const char> value) {\n  return ada_set_username(getInner<ada_url>(inner), value.begin(), value.size());\n}\n\nbool Url::setPassword(kj::ArrayPtr<const char> value) {\n  return ada_set_password(getInner<ada_url>(inner), value.begin(), value.size());\n}\n\nbool Url::setPort(kj::Maybe<kj::ArrayPtr<const char>> value) {\n  KJ_IF_SOME(v, value) {\n    return ada_set_port(getInner<ada_url>(inner), v.begin(), v.size());\n  }\n  ada_clear_port(getInner<ada_url>(inner));\n  return true;\n}\n\nbool Url::setPathname(kj::ArrayPtr<const char> value) {\n  return ada_set_pathname(getInner<ada_url>(inner), value.begin(), value.size());\n}\n\nvoid Url::setSearch(kj::Maybe<kj::ArrayPtr<const char>> value) {\n  KJ_IF_SOME(v, value) {\n    return ada_set_search(getInner<ada_url>(inner), v.begin(), v.size());\n  }\n  ada_clear_search(getInner<ada_url>(inner));\n}\n\nvoid Url::setHash(kj::Maybe<kj::ArrayPtr<const char>> value) {\n  KJ_IF_SOME(v, value) {\n    return ada_set_hash(getInner<ada_url>(inner), v.begin(), v.size());\n  }\n  ada_clear_hash(getInner<ada_url>(inner));\n}\n\nUrl::SchemeType Url::getSchemeType() const {\n  uint8_t value = ada_get_scheme_type(getInner<ada_url>(inner));\n  KJ_REQUIRE(value <= static_cast<uint8_t>(SchemeType::FILE));\n  return static_cast<SchemeType>(value);\n}\n\nUrl::HostType Url::getHostType() const {\n  uint8_t value = ada_get_host_type(getInner<ada_url>(inner));\n  KJ_REQUIRE(value <= static_cast<uint8_t>(HostType::IPV6));\n  return static_cast<HostType>(value);\n}\n\nUrl Url::clone(EquivalenceOption option) const {\n  ada_url copy = ada_copy(getInner<ada_url>(inner));\n  if ((option & EquivalenceOption::IGNORE_FRAGMENTS) == EquivalenceOption::IGNORE_FRAGMENTS) {\n    ada_clear_hash(copy);\n  }\n  if ((option & EquivalenceOption::IGNORE_SEARCH) == EquivalenceOption::IGNORE_SEARCH) {\n    ada_clear_search(copy);\n  }\n  if ((option & EquivalenceOption::NORMALIZE_PATH) == EquivalenceOption::NORMALIZE_PATH) {\n    auto normalized = normalizePathEncoding(getPathname());\n    ada_set_pathname(copy, normalized.begin(), normalized.size());\n  }\n  return Url(wrap(copy));\n}\n\nkj::Array<const char> Url::idnToUnicode(kj::ArrayPtr<const char> value) {\n  ada_owned_string result = ada_idna_to_unicode(value.begin(), value.size());\n  return kj::Array<const char>(result.data, result.length, AdaOwnedStringDisposer::INSTANCE);\n}\n\nkj::Array<const char> Url::idnToAscii(kj::ArrayPtr<const char> value) {\n  ada_owned_string result = ada_idna_to_ascii(value.begin(), value.size());\n  return kj::Array<const char>(result.data, result.length, AdaOwnedStringDisposer::INSTANCE);\n}\n\nkj::Maybe<Url> Url::tryResolve(kj::ArrayPtr<const char> input) const {\n  return tryParse(input, getHref());\n}\n\nUrl::Relative Url::getRelative(RelativeOption option) const {\n  if (option == RelativeOption::STRIP_TAILING_SLASHES) {\n    auto pathname = getPathname();\n    if (pathname.endsWith(\"/\"_kj)) {\n      auto cloned = clone();\n      cloned.setPathname(pathname.first(pathname.size() - 1));\n      return cloned.getRelative();\n    }\n    // Otherwise, fall-through to the default behavior.\n  }\n  auto base = KJ_ASSERT_NONNULL(tryResolve(\".\"_kj));\n  auto pos = KJ_ASSERT_NONNULL(getPathname().findLast('/'));\n  return {\n    .base = kj::mv(base),\n    .name = kj::str(getPathname().slice(pos + 1)),\n  };\n}\n\nkj::Maybe<jsg::Url> Url::getParent() const {\n  auto parent = KJ_ASSERT_NONNULL(tryResolve(\".\"_kj));\n  auto pathname = parent.getPathname();\n  if (pathname.size() == 1) return kj::none;\n  auto trimmed = kj::str(pathname.first(pathname.size() - 1));\n  parent.setPathname(trimmed);\n  return kj::mv(parent);\n}\n\nkj::uint Url::hashCode() const {\n  return kj::hashCode(getHref());\n}\n\nkj::Array<kj::byte> Url::percentDecode(kj::ArrayPtr<const kj::byte> input) {\n  std::string_view data(input.asChars().begin(), input.size());\n  auto str = ada::unicode::percent_decode(data, 0);\n  auto ret = kj::heapArray<kj::byte>(str.size());\n  memcpy(ret.begin(), str.data(), str.size());\n  return kj::mv(ret);\n}\n\n// ======================================================================================\n\nnamespace {\nkj::Own<void> emptySearchParams() {\n  ada_url_search_params result = ada_parse_search_params(nullptr, 0);\n  KJ_ASSERT(result);\n  return kj::disposeWith<ada_free_search_params>(result);\n}\n}  // namespace\n\nUrlSearchParams::UrlSearchParams(): inner(emptySearchParams()) {}\n\nUrlSearchParams::UrlSearchParams(kj::Own<void> inner): inner(kj::mv(inner)) {}\n\nbool UrlSearchParams::operator==(const UrlSearchParams& other) const {\n  return toStr() == other.toStr();\n}\n\nvoid UrlSearchParams::reset(kj::Maybe<kj::ArrayPtr<const char>> input) {\n  KJ_IF_SOME(i, input) {\n    ada_search_params_reset(inner, i.begin(), i.size());\n  } else {\n    ada_search_params_reset(inner, nullptr, 0);\n  }\n}\n\nkj::Maybe<UrlSearchParams> UrlSearchParams::tryParse(kj::ArrayPtr<const char> input) {\n  ada_url_search_params result = ada_parse_search_params(input.begin(), input.size());\n  if (!result) return kj::none;\n  return UrlSearchParams(kj::disposeWith<ada_free_search_params>(result));\n}\n\nsize_t UrlSearchParams::size() const {\n  return ada_search_params_size(getInner<ada_url_search_params>(inner));\n}\n\nvoid UrlSearchParams::append(kj::ArrayPtr<const char> key, kj::ArrayPtr<const char> value) {\n  ada_search_params_append(\n      getInner<ada_url_search_params>(inner), key.begin(), key.size(), value.begin(), value.size());\n}\n\nvoid UrlSearchParams::set(kj::ArrayPtr<const char> key, kj::ArrayPtr<const char> value) {\n  ada_search_params_set(\n      getInner<ada_url_search_params>(inner), key.begin(), key.size(), value.begin(), value.size());\n}\n\nvoid UrlSearchParams::delete_(\n    kj::ArrayPtr<const char> key, kj::Maybe<kj::ArrayPtr<const char>> maybeValue) {\n  KJ_IF_SOME(value, maybeValue) {\n    ada_search_params_remove_value(getInner<ada_url_search_params>(inner), key.begin(), key.size(),\n        value.begin(), value.size());\n  } else {\n    ada_search_params_remove(getInner<ada_url_search_params>(inner), key.begin(), key.size());\n  }\n}\n\nbool UrlSearchParams::has(\n    kj::ArrayPtr<const char> key, kj::Maybe<kj::ArrayPtr<const char>> maybeValue) const {\n  KJ_IF_SOME(value, maybeValue) {\n    return ada_search_params_has_value(getInner<ada_url_search_params>(inner), key.begin(),\n        key.size(), value.begin(), value.size());\n  } else {\n    return ada_search_params_has(getInner<ada_url_search_params>(inner), key.begin(), key.size());\n  }\n}\n\nkj::Maybe<kj::ArrayPtr<const char>> UrlSearchParams::get(kj::ArrayPtr<const char> key) const {\n  auto result =\n      ada_search_params_get(getInner<ada_url_search_params>(inner), key.begin(), key.size());\n  if (result.data == nullptr) return kj::none;\n  return kj::ArrayPtr<const char>(result.data, result.length);\n}\n\nkj::Array<kj::ArrayPtr<const char>> UrlSearchParams::getAll(kj::ArrayPtr<const char> key) const {\n  ada_strings results =\n      ada_search_params_get_all(getInner<ada_url_search_params>(inner), key.begin(), key.size());\n  size_t size = ada_strings_size(results);\n  kj::Vector<kj::ArrayPtr<const char>> items(size);\n  for (size_t n = 0; n < size; n++) {\n    auto item = ada_strings_get(results, n);\n    items.add(kj::ArrayPtr<const char>(item.data, item.length));\n  }\n  return items.releaseAsArray().attach(kj::defer([results]() { ada_free_strings(results); }));\n}\n\nvoid UrlSearchParams::sort() {\n  ada_search_params_sort(getInner<ada_url_search_params>(inner));\n}\n\nUrlSearchParams::KeyIterator UrlSearchParams::getKeys() const {\n  return KeyIterator(kj::disposeWith<ada_free_search_params_keys_iter>(\n      ada_search_params_get_keys(getInner<ada_url_search_params>(inner))));\n}\n\nUrlSearchParams::ValueIterator UrlSearchParams::getValues() const {\n  return ValueIterator(kj::disposeWith<ada_free_search_params_values_iter>(\n      ada_search_params_get_values(getInner<ada_url_search_params>(inner))));\n}\n\nUrlSearchParams::EntryIterator UrlSearchParams::getEntries() const {\n  return EntryIterator(kj::disposeWith<ada_free_search_params_entries_iter>(\n      ada_search_params_get_entries(getInner<ada_url_search_params>(inner))));\n}\n\nkj::Array<const char> UrlSearchParams::toStr() const {\n  ada_owned_string result = ada_search_params_to_string(getInner<ada_url_search_params>(inner));\n  return kj::Array<const char>(result.data, result.length, AdaOwnedStringDisposer::INSTANCE);\n}\n\nUrlSearchParams::KeyIterator::KeyIterator(kj::Own<void> inner): inner(kj::mv(inner)) {}\n\nbool UrlSearchParams::KeyIterator::hasNext() const {\n  return ada_search_params_keys_iter_has_next(getInner<ada_url_search_params_keys_iter>(inner));\n}\n\nkj::Maybe<kj::ArrayPtr<const char>> UrlSearchParams::KeyIterator::next() const {\n  if (!hasNext()) return kj::none;\n  auto next = ada_search_params_keys_iter_next(getInner<ada_url_search_params_keys_iter>(inner));\n  return kj::ArrayPtr<const char>(next.data, next.length);\n}\n\nUrlSearchParams::ValueIterator::ValueIterator(kj::Own<void> inner): inner(kj::mv(inner)) {}\n\nbool UrlSearchParams::ValueIterator::hasNext() const {\n  return ada_search_params_values_iter_has_next(getInner<ada_url_search_params_values_iter>(inner));\n}\n\nkj::Maybe<kj::ArrayPtr<const char>> UrlSearchParams::ValueIterator::next() const {\n  if (!hasNext()) return kj::none;\n  auto next =\n      ada_search_params_values_iter_next(getInner<ada_url_search_params_values_iter>(inner));\n  return kj::ArrayPtr<const char>(next.data, next.length);\n}\n\nUrlSearchParams::EntryIterator::EntryIterator(kj::Own<void> inner): inner(kj::mv(inner)) {}\n\nbool UrlSearchParams::EntryIterator::hasNext() const {\n  return ada_search_params_entries_iter_has_next(\n      getInner<ada_url_search_params_entries_iter>(inner));\n}\n\nkj::Maybe<UrlSearchParams::EntryIterator::Entry> UrlSearchParams::EntryIterator::next() const {\n  if (!hasNext()) return kj::none;\n  auto next =\n      ada_search_params_entries_iter_next(getInner<ada_url_search_params_entries_iter>(inner));\n  return Entry{\n    .key = kj::ArrayPtr<const char>(next.key.data, next.key.length),\n    .value = kj::ArrayPtr<const char>(next.value.data, next.value.length),\n  };\n}\n\n// ======================================================================================\n// UrlPattern\n\nnamespace {\n\nconstexpr auto MODIFIER_OPTIONAL = \"?\"_kjc;\nconstexpr auto MODIFIER_ZERO_OR_MORE = \"*\"_kjc;\nconstexpr auto MODIFIER_ONE_OR_MORE = \"+\"_kjc;\n\ninline bool isAsciiDigit(char c) {\n  return c >= '0' && c <= '9';\n};\n\ninline bool isAscii(char codepoint) {\n  return codepoint >= 0x00 && codepoint <= 0x7f;\n};\n\ninline bool isForbiddenHostCodepoint(char c) {\n  return c == 0x00 || c == 0x09 /* Tab */ || c == 0x0a /* LF */ || c == 0x0d /* CR */ || c == ' ' ||\n      c == '#' || c == '%' || c == '/' || c == ':' || c == '<' || c == '>' || c == '?' ||\n      c == '@' || c == '[' || c == '\\\\' || c == ']' || c == '^' || c == '|';\n};\n\n// This is not meant to be a comprehensive validation that the hostname is\n// a proper IPv6 address. It's a quick check defined by the URLPattern spec.\ninline bool isIpv6(kj::ArrayPtr<const char> hostname) {\n  if (hostname.size() < 2) return false;\n  auto c1 = hostname[0];\n  auto c2 = hostname[1];\n  return (c1 == '[' || ((c1 == '{' || c1 == '\\\\') && c2 == '['));\n}\n\n// This additional check deals with a known bug in the URLPattern spec. The URL parser will\n// allow (and generally ignore) invalid characters in the hostname when running with the\n// HOST state override. The URLPattern spec, however, assumes that it doesn't.\ninline bool isValidHostnameInput(kj::StringPtr input) {\n  return isIpv6(input) || std::none_of(input.begin(), input.end(), isForbiddenHostCodepoint);\n}\n\ninline bool isValidCodepoint(uint32_t codepoint, bool first) {\n  // https://tc39.es/ecma262/#prod-IdentifierStart\n  if (first) {\n    return codepoint == '$' || codepoint == '_' || u_hasBinaryProperty(codepoint, UCHAR_ID_START);\n  }\n  return codepoint == '$' || codepoint == 0x200C ||  // Zero-width non-joiner\n      codepoint == 0x200D ||                         // Zero-width joiner\n      u_hasBinaryProperty(codepoint, UCHAR_ID_CONTINUE);\n};\n\ninline kj::Maybe<kj::String> strFromMaybePtr(const kj::Maybe<kj::StringPtr>& ptr) {\n  return ptr.map([](const kj::StringPtr& ptr) { return kj::str(ptr); });\n}\n\nusing Canonicalizer = kj::Maybe<kj::String>(kj::StringPtr, kj::Maybe<kj::StringPtr>);\n\nkj::Maybe<kj::String> canonicalizeProtocol(\n    kj::StringPtr protocol, kj::Maybe<kj::StringPtr> = kj::none) {\n  // @see https://wicg.github.io/urlpattern/#canonicalize-a-protocol\n  if (protocol.size() == 0) return kj::String();\n  auto input = kj::str(protocol, \"://dummy.test\");\n  KJ_IF_SOME(url, Url::tryParse(input.asPtr())) {\n    auto result = url.getProtocol();\n    return kj::str(result.first(result.size() - 1));\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::String> canonicalizeUsername(\n    kj::StringPtr username, kj::Maybe<kj::StringPtr> = kj::none) {\n  // @see https://wicg.github.io/urlpattern/#canonicalize-a-username\n  if (username.size() == 0) return kj::String();\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"fake://dummy.test\"_kj));\n  if (!url.setUsername(username)) return kj::none;\n  return kj::str(url.getUsername());\n}\n\nkj::Maybe<kj::String> canonicalizePassword(\n    kj::StringPtr password, kj::Maybe<kj::StringPtr> = kj::none) {\n  // @see https://wicg.github.io/urlpattern/#canonicalize-a-password\n  if (password.size() == 0) return kj::String();\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"fake://dummy.test\"_kj));\n  if (!url.setPassword(password)) return kj::none;\n  return kj::str(url.getPassword());\n}\n\nkj::Maybe<kj::String> canonicalizeHostname(\n    kj::StringPtr hostname, kj::Maybe<kj::StringPtr> = kj::none) {\n  // @see https://wicg.github.io/urlpattern/#canonicalize-a-hostname\n  if (hostname.size() == 0) return kj::String();\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"fake://dummy.test\"_kj));\n  if (!isValidHostnameInput(hostname)) return kj::none;\n  if (!url.setHostname(hostname)) return kj::none;\n  return kj::str(url.getHostname());\n}\n\nkj::Maybe<kj::String> canonicalizeIpv6Hostname(\n    kj::StringPtr hostname, kj::Maybe<kj::StringPtr> = kj::none) {\n  // @see https://wicg.github.io/urlpattern/#canonicalize-an-ipv6-hostname\n  if (!std::all_of(hostname.begin(), hostname.end(),\n          [](char c) { return isHexDigit(c) || c == '[' || c == ']' || c == ':'; })) {\n    return kj::none;\n  }\n  return kj::str(hostname);\n}\n\nkj::Maybe<kj::String> canonicalizePort(kj::StringPtr port, kj::Maybe<kj::StringPtr> protocol) {\n  // @see https://wicg.github.io/urlpattern/#canonicalize-a-port\n  if (port.size() == 0) return kj::String();\n  auto input = kj::str(protocol.orDefault(\"fake\"_kj), \"://dummy.test\");\n  KJ_IF_SOME(url, Url::tryParse(input.asPtr())) {\n    if (!url.setPort(kj::Maybe(port))) return kj::none;\n    return kj::str(url.getPort());\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::String> canonicalizePathname(\n    kj::StringPtr pathname, kj::Maybe<kj::StringPtr> = kj::none) {\n  // @see https://wicg.github.io/urlpattern/#canonicalize-a-pathname\n  if (pathname.size() == 0) return kj::String();\n  bool leadingSlash = pathname[0] == '/';\n  auto input = kj::str(\"fake://fake-url\", leadingSlash ? \"\" : \"/-\", pathname);\n  KJ_IF_SOME(url, Url::tryParse(input.asPtr())) {\n    auto result = url.getPathname();\n    return leadingSlash ? kj::str(result) : kj::str(result.slice(2));\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::String> canonicalizeOpaquePathname(\n    kj::StringPtr pathname, kj::Maybe<kj::StringPtr> = kj::none) {\n  // @see https://wicg.github.io/urlpattern/#canonicalize-an-opaque-pathname\n  if (pathname.size() == 0) return kj::String();\n  auto str = kj::str(\"fake:\", pathname);\n  KJ_IF_SOME(url, Url::tryParse(str.asPtr())) {\n    return kj::str(url.getPathname());\n  }\n  return kj::none;\n}\n\nkj::Maybe<kj::String> canonicalizeSearch(\n    kj::StringPtr search, kj::Maybe<kj::StringPtr> = kj::none) {\n  // @see https://wicg.github.io/urlpattern/#canonicalize-a-search\n  if (search.size() == 0) return kj::String();\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"fake://dummy.test\"_kj));\n  url.setSearch(kj::Maybe(search));\n  return url.getSearch().size() > 0 ? kj::str(url.getSearch().slice(1)) : kj::String();\n}\n\nkj::Maybe<kj::String> canonicalizeHash(kj::StringPtr hash, kj::Maybe<kj::StringPtr> = kj::none) {\n  // @see https://wicg.github.io/urlpattern/#canonicalize-a-hash\n  if (hash.size() == 0) return kj::String();\n  auto url = KJ_ASSERT_NONNULL(Url::tryParse(\"fake://dummy.test\"_kj));\n  url.setHash(kj::Maybe(hash));\n  return url.getHash().size() > 0 ? kj::str(url.getHash().slice(1)) : kj::String();\n}\n\nkj::Maybe<kj::String> chooseStr(kj::Maybe<kj::String> str, kj::Maybe<kj::StringPtr> other) {\n  KJ_IF_SOME(s, str) {\n    return kj::mv(s);\n  } else {\n    return strFromMaybePtr(other);\n  }\n}\n\nkj::String stripSuffixFromProtocol(kj::ArrayPtr<const char> data) {\n  if (data.back() == ':') {\n    return kj::str(data.first(data.size() - 1));\n  }\n  return kj::str(data);\n}\n\nkj::String escape(kj::ArrayPtr<const char> str, auto predicate) {\n  // Best case we don't have to escape anything so size remains the same,\n  // but let's pad a little just in case.\n  kj::Vector<char> result(str.size() + 10);\n  auto it = str.begin();\n  while (it != str.end()) {\n    auto c = *it;\n    if (predicate(c)) result.add('\\\\');\n    result.add(c);\n    ++it;\n  }\n  result.add('\\0');\n  return kj::String(result.releaseAsArray());\n}\n\nkj::String escapeRegexString(kj::ArrayPtr<const char> str) {\n  return escape(str, [](auto c) {\n    return c == '.' || c == '+' || c == '*' || c == '?' || c == '^' || c == '$' || c == '{' ||\n        c == '}' || c == '(' || c == ')' || c == '[' || c == ']' || c == '|' || c == '/' ||\n        c == '\\\\';\n  });\n}\n\nkj::String escapePatternString(kj::ArrayPtr<const char> str) {\n  return escape(str, [](auto c) {\n    return c == '+' || c == '*' || c == '?' || c == ':' || c == '{' || c == '}' || c == '(' ||\n        c == ')' || c == '\\\\';\n  });\n}\n\nstruct CompileComponentOptions {\n  kj::Maybe<char> delimiter;\n  kj::Maybe<char> prefix;\n  kj::String segmentWildcardRegexp;\n\n  kj::String initSegmentWildcardRegexp() {\n    KJ_IF_SOME(c, delimiter) {\n      return kj::str(\"[^\\\\\", c, \"]+\");\n    } else {\n      return kj::str(\"[^]+\");\n    }\n  }\n\n  CompileComponentOptions(kj::Maybe<char> delimiter, kj::Maybe<char> prefix)\n      : delimiter(delimiter),\n        prefix(prefix),\n        segmentWildcardRegexp(initSegmentWildcardRegexp()) {}\n\n  static const CompileComponentOptions DEFAULT;\n  static const CompileComponentOptions HOSTNAME;\n  static const CompileComponentOptions PATHNAME;\n};\nconst CompileComponentOptions CompileComponentOptions::DEFAULT(kj::none, kj::none);\nconst CompileComponentOptions CompileComponentOptions::HOSTNAME('.', kj::none);\nconst CompileComponentOptions CompileComponentOptions::PATHNAME('/', '/');\n\n// An individual piece of a URLPattern string. Used while parsing a URLPattern\n// string for the URLPattern constructor, test, or exec call.\nstruct Part {\n  enum class Type {\n    FIXED_TEXT,\n    REGEXP,\n    SEGMENT_WILDCARD,\n    FULL_WILDCARD,\n  };\n\n  enum class Modifier {\n    NONE,\n    OPTIONAL,      // ?\n    ZERO_OR_MORE,  // *\n    ONE_OR_MORE,   // +\n  };\n\n  Type type;\n  Modifier modifier;\n  kj::String value;\n  kj::String name;\n  kj::Maybe<kj::String> prefix;\n  kj::Maybe<kj::String> suffix;\n};\n\nkj::Maybe<kj::StringPtr> modifierToString(const Part::Modifier& modifier) {\n  switch (modifier) {\n    case Part::Modifier::NONE:\n      return kj::none;\n    case Part::Modifier::OPTIONAL:\n      return MODIFIER_OPTIONAL;\n    case Part::Modifier::ZERO_OR_MORE:\n      return MODIFIER_ZERO_OR_MORE;\n    case Part::Modifier::ONE_OR_MORE:\n      return MODIFIER_ONE_OR_MORE;\n  }\n  KJ_UNREACHABLE;\n}\n\n// String inputs passed into URLPattern constructor are parsed by first\n// interpreting them into a list of Tokens. Each token has a type, a\n// position index in the input string, and a value. The value is either\n// a individual codepoint or a substring of input. Once the tokens are\n// determined, the parsing algorithms convert those into a Part list.\n// The part list is then used to generate the internal JavaScript RegExps\n// that are used for the actual matching operation.\nstruct Token {\n  // Per the URLPattern spec, the tokenizer runs in one of two modes:\n  // Strict and Lenient. In Strict mode, invalid characters and sequences\n  // detected by the tokenizer will cause a TypeError to be thrown.\n  // In lenient mode, the invalid codepoints and sequences are marked\n  // but no error is thrown. When parsing a string passed to the\n  // URLPattern constructor, lenient mode is used. When parsing the\n  // pattern string for an individual component, strict mode is used.\n  enum class Policy {\n    STRICT,\n    LENIENT,\n  };\n\n  enum class Type {\n    INVALID_CHAR,    // 0\n    OPEN,            // 1\n    CLOSE,           // 2\n    REGEXP,          // 3\n    NAME,            // 4\n    CHAR,            // 5\n    ESCAPED_CHAR,    // 6\n    OTHER_MODIFIER,  // 7\n    ASTERISK,        // 8\n    END,             // 9\n  };\n\n  Type type = Type::INVALID_CHAR;\n  size_t index = 0;\n  kj::OneOf<char, kj::ArrayPtr<const char>> value = static_cast<char>(0);\n  Part::Modifier modifier = Part::Modifier::NONE;\n\n  operator kj::String() const {\n    KJ_SWITCH_ONEOF(value) {\n      KJ_CASE_ONEOF(codepoint, char) {\n        return kj::str(codepoint);\n      }\n      KJ_CASE_ONEOF(ptr, kj::ArrayPtr<const char>) {\n        return kj::str(ptr);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  bool operator==(const kj::String& other) const {\n    KJ_SWITCH_ONEOF(value) {\n      KJ_CASE_ONEOF(codepoint, char) {\n        return false;\n      }\n      KJ_CASE_ONEOF(string, kj::ArrayPtr<const char>) {\n        return other == string;\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  bool operator==(char other) {\n    KJ_SWITCH_ONEOF(value) {\n      KJ_CASE_ONEOF(codepoint, char) {\n        return codepoint == other;\n      }\n      KJ_CASE_ONEOF(string, kj::ArrayPtr<const char>) {\n        return false;\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  static Token asterisk(size_t index) {\n    return {\n      .type = Type::ASTERISK,\n      .index = index,\n      .value = '*',\n      .modifier = Part::Modifier::ZERO_OR_MORE,\n    };\n  }\n\n  static Token char_(size_t index, char codepoint) {\n    return {\n      .type = Type::CHAR,\n      .index = index,\n      .value = codepoint,\n    };\n  }\n\n  static Token close(size_t index) {\n    return {\n      .type = Type::CLOSE,\n      .index = index,\n    };\n  }\n\n  static Token end(size_t index) {\n    return {\n      .type = Type::END,\n      .index = index,\n    };\n  }\n\n  static Token escapedChar(size_t index, char codepoint) {\n    return {\n      .type = Type::ESCAPED_CHAR,\n      .index = index,\n      .value = codepoint,\n    };\n  }\n\n  static Token invalidChar(size_t index, char codepoint) {\n    return {\n      .index = index,\n      .value = codepoint,\n    };\n  }\n\n  static Token invalidSegment(size_t index, kj::ArrayPtr<const char> segment) {\n    return {\n      .type = Type::INVALID_CHAR,\n      .index = index,\n      .value = segment,\n    };\n  }\n\n  static Token name(size_t index, kj::ArrayPtr<const char> name) {\n    return {\n      .type = Type::NAME,\n      .index = index,\n      .value = name,\n    };\n  }\n\n  static Token open(size_t index) {\n    return {\n      .type = Type::OPEN,\n      .index = index,\n    };\n  }\n\n  static Token otherModifier(size_t index, char codepoint) {\n    KJ_DASSERT(codepoint == '?' || codepoint == '+');\n    return {\n      .type = Type::OTHER_MODIFIER,\n      .index = index,\n      .value = codepoint,\n      .modifier = codepoint == '?' ? Part::Modifier::OPTIONAL : Part::Modifier::ONE_OR_MORE,\n    };\n  }\n\n  static Token regex(size_t index, kj::ArrayPtr<const char> regex) {\n    return {\n      .type = Type::REGEXP,\n      .index = index,\n      .value = regex,\n    };\n  }\n};\n\nstruct RegexAndNameList {\n  kj::String regex;\n  kj::Array<kj::String> names;\n};\n\nUrlPattern::Result<kj::Array<Token>> tokenize(kj::StringPtr input, Token::Policy policy) {\n  auto it = input.begin();\n  size_t pos = 0;\n  kj::Vector<Token> tokenList(input.size() + 1);\n  // Scan the input and advance both it and pos until the given predicate return false.\n  const auto scanCodepoints = [&](auto predicate) {\n    bool first = true;\n    while (it != input.end()) {\n      uint32_t codepoint;\n      size_t starting = pos;\n      // Reads to the next codepoint boundary, incrementing pos accordingly.\n      // We use U8_NEXT_OR_FFFD here because the input is a raw sequence of\n      // UTF8 bytes but the predicate needs to check the decoded codepoint\n      // rather than looking at individual bytes. The macro will advance pos\n      // at is scans.\n      U8_NEXT_OR_FFFD(input.begin(), pos, input.size(), codepoint);\n      KJ_DASSERT(pos <= input.size());\n      // If our read codepoint does not match the predicate, we do not want to\n      // advance and we stop scanning.\n      if (!predicate(codepoint, first)) {\n        pos = starting;\n        break;\n      }\n      it += pos - starting;\n      first = false;\n    }\n  };\n\n  while (it != input.end()) {\n    auto c = *it;\n    switch (c) {\n      case '*': {\n        tokenList.add(Token::asterisk(pos++));\n        break;\n      }\n      case '?': {\n        KJ_FALLTHROUGH;\n      }\n      case '+': {\n        tokenList.add(Token::otherModifier(pos++, c));\n        break;\n      }\n      case '\\\\': {\n        ++it;\n        // The escape character is invalid if it comes at the end!\n        if (it == input.end()) {\n          if (policy == Token::Policy::STRICT) {\n            return kj::str(\"Syntax error in URL Pattern: invalid escape character at \", pos);\n          }\n          tokenList.add(Token::invalidChar(pos++, c));\n        } else {\n          tokenList.add(Token::escapedChar(pos, *it));\n          pos += 2;\n        }\n        break;\n      }\n      case '{': {\n        tokenList.add(Token::open(pos++));\n        break;\n      }\n      case '}': {\n        tokenList.add(Token::close(pos++));\n        break;\n      }\n      case ':': {\n        ++it;\n        // The name token is invalid if it comes at the end!\n        if (it == input.end()) {\n          if (policy == Token::Policy::STRICT) {\n            return kj::str(\"Syntax error in URL Pattern: invalid name start at \", pos);\n          }\n          tokenList.add(Token::invalidChar(pos++, c));\n          break;\n        }\n        auto start = ++pos;\n        scanCodepoints(isValidCodepoint);\n        if (start == pos) {\n          // There was a name token suffix without a valid name! Oh, the inhumanity of it all.\n          if (policy == Token::Policy::STRICT) {\n            return kj::str(\"Syntax error in URL Pattern: invalid name start at \", pos - 1);\n          }\n          tokenList.add(Token::invalidChar(pos - 1, c));\n        } else {\n          if (it == input.end()) {\n            tokenList.add(Token::name(start - 1, input.slice(start)));\n          } else {\n            tokenList.add(Token::name(start - 1, input.slice(start, pos)));\n          }\n        }\n        // We purposefully do not increment the iterator here because we are\n        // already at the next position.\n\n        continue;\n      }\n      case '(': {\n        ++it;\n        // The group token is invalid if it comes at the end!\n        if (it == input.end()) {\n          if (policy == Token::Policy::STRICT) {\n            return kj::str(\"Syntax error in URL Pattern: invalid regex start at \", pos);\n          }\n          tokenList.add(Token::invalidChar(pos++, c));\n          break;\n        }\n        size_t depth = 1;\n        size_t start = ++pos;\n        bool error = false;\n        while (it != input.end()) {\n          auto rc = *it;\n          if (!isAscii(rc)) {\n            if (policy == Token::Policy::STRICT) {\n              return kj::str(\"Syntax error in URL Pattern: invalid regex character at \", pos);\n            }\n            tokenList.add(Token::invalidChar(pos, rc));\n            error = true;\n            break;\n          } else if (pos == start && rc == '?') {\n            if (policy == Token::Policy::STRICT) {\n              return kj::str(\"Syntax error in URL Pattern: invalid regex character at \", pos);\n            }\n            tokenList.add(Token::invalidChar(pos, rc));\n            error = true;\n            break;\n          } else if (rc == '\\\\') {\n            it++;\n            // The escape character is invalid if it comes at the end of input\n            if (it == input.end()) {\n              if (policy == Token::Policy::STRICT) {\n                return kj::str(\n                    \"Syntax error in URL Pattern: invalid escape character in regex at \", pos);\n              }\n              tokenList.add(Token::invalidChar(pos, rc));\n              error = true;\n              break;\n            }\n            pos++;\n            rc = *it;\n            if (!isAscii(rc)) {\n              if (policy == Token::Policy::STRICT) {\n                return kj::str(\n                    \"Syntax error in URL Pattern: invalid escaped character in regex at \", pos);\n              }\n              tokenList.add(Token::invalidChar(pos, rc));\n              error = true;\n              break;\n            }\n            pos++;\n            it++;\n            continue;\n          } else if (rc == ')') {\n            depth--;\n            if (depth == 0) {\n              pos++;\n              it++;\n              break;\n            }\n          } else if (rc == '(') {\n            depth++;\n            it++;\n            // The group open character is invalid if it comes at the end of input\n            if (it == input.end()) {\n              if (policy == Token::Policy::STRICT) {\n                return kj::str(\"Syntax error in URL Pattern: invalid group in regex at \", pos);\n              }\n              tokenList.add(Token::invalidChar(pos, rc));\n              error = true;\n              break;\n            }\n            pos++;\n            rc = *it;\n            if (rc != '?') {\n              if (policy == Token::Policy::STRICT) {\n                return kj::str(\"Syntax error in URL Pattern: invalid group in regex at \", pos);\n              }\n              tokenList.add(Token::invalidChar(pos, rc));\n              error = true;\n              break;\n            }\n          }\n          it++;\n          pos++;\n        }\n        if (error) continue;\n        if (depth > 0 || start == pos) {\n          if (policy == Token::Policy::STRICT) {\n            return kj::str(\"Syntax error in URL Pattern: invalid regex segment at \", start);\n          }\n          tokenList.add(Token::invalidSegment(start, input.slice(start, pos - 1)));\n        } else {\n          tokenList.add(Token::regex(start - 1, input.slice(start, pos - 1)));\n        }\n        // We purposefully do not increment the iterator here because we are\n        // already at the next position.\n        continue;\n      }\n      default: {\n        tokenList.add(Token::char_(pos++, c));\n        break;\n      }\n    }\n    if (it == input.end()) break;\n    ++it;\n  }\n\n  tokenList.add(Token::end(input.size()));\n  return tokenList.releaseAsArray();\n}\n\nUrlPattern::Result<kj::Array<Part>> parsePattern(\n    kj::StringPtr input, Canonicalizer canonicalizer, const CompileComponentOptions& options) {\n  kj::Array<Token> tokens = nullptr;\n  KJ_SWITCH_ONEOF(tokenize(input, Token::Policy::STRICT)) {\n    KJ_CASE_ONEOF(err, kj::String) {\n      return kj::mv(err);\n    }\n    KJ_CASE_ONEOF(list, kj::Array<Token>) {\n      tokens = kj::mv(list);\n    }\n  }\n  // There should be at least one token in the list (the end token)\n  KJ_DASSERT(tokens.size() > 0);\n  kj::Vector<Part> partList(tokens.size());\n  kj::Maybe<StringTreeHolder> pendingFixedValue;\n  size_t index = 0;\n  size_t nextNumericName = 0;\n\n  auto segmentWildcardRegex = options.segmentWildcardRegexp.asPtr();\n\n  auto appendToPendingFixedValue = [&](kj::StringPtr value) mutable {\n    KJ_IF_SOME(pending, pendingFixedValue) {\n      pending.append(value);\n    } else {\n      pendingFixedValue.emplace(kj::strTree(value));\n    }\n  };\n\n  auto maybeAddPartFromPendingFixedValue = [&]() mutable -> bool {\n    KJ_IF_SOME(fixedValue, pendingFixedValue) {\n      kj::String value = kj::mv(fixedValue);\n      pendingFixedValue = kj::none;\n      if (value.size() == 0) return true;\n      KJ_IF_SOME(canonical, canonicalizer(value, kj::none)) {\n        partList.add(Part{\n          .type = Part::Type::FIXED_TEXT,\n          .modifier = Part::Modifier::NONE,\n          .value = kj::mv(canonical),\n        });\n        return true;\n      }\n      return false;\n    }\n    return true;\n  };\n\n  auto tryConsumeToken = [&](Token::Type type) -> kj::Maybe<Token&> {\n    KJ_DASSERT(index < tokens.size());\n    auto& next = tokens[index];\n    if (next.type != type) {\n      return kj::none;\n    }\n    index++;\n    return kj::Maybe<Token&>(next);\n  };\n\n  auto tryConsumeRegexOrWildcardToken = [&](kj::Maybe<Token&>& nameToken) {\n    auto token = tryConsumeToken(Token::Type::REGEXP);\n    if (nameToken == kj::none && token == kj::none) {\n      token = tryConsumeToken(Token::Type::ASTERISK);\n    }\n    return token;\n  };\n\n  auto tryConsumeModifierToken = [&]() -> kj::Maybe<Token&> {\n    KJ_IF_SOME(token, tryConsumeToken(Token::Type::OTHER_MODIFIER)) {\n      return kj::Maybe<Token&>(token);\n    }\n    return tryConsumeToken(Token::Type::ASTERISK);\n  };\n\n  auto consumeText = [&]() mutable -> kj::String {\n    StringTreeHolder result;\n    while (true) {\n      KJ_IF_SOME(token, tryConsumeToken(Token::Type::CHAR)) {\n        result.append(kj::String(token));\n      } else KJ_IF_SOME(token, tryConsumeToken(Token::Type::ESCAPED_CHAR)) {\n        result.append(kj::String(token));\n      } else {\n        break;\n      }\n    }\n    return kj::mv(result);\n  };\n\n  auto isDuplicateName = [&](kj::StringPtr name) -> bool {\n    return std::any_of(\n        partList.begin(), partList.end(), [&name](Part& part) { return part.name == name; });\n  };\n\n  auto maybeTokenToModifier = [](kj::Maybe<Token&> modifierToken) -> Part::Modifier {\n    KJ_IF_SOME(token, modifierToken) {\n      KJ_DASSERT(token.type == Token::Type::OTHER_MODIFIER || token.type == Token::Type::ASTERISK);\n      return token.modifier;\n    }\n    return Part::Modifier::NONE;\n  };\n\n  auto addPart = [&](kj::Maybe<kj::String> maybePrefix, kj::Maybe<Token&> nameToken,\n                     kj::Maybe<Token&> regexOrWildcardToken, kj::Maybe<kj::String> suffix,\n                     kj::Maybe<Token&> modifierToken) mutable -> kj::Maybe<kj::String> {\n    auto modifier = maybeTokenToModifier(modifierToken);\n    if (nameToken == kj::none && regexOrWildcardToken == kj::none &&\n        modifier == Part::Modifier::NONE) {\n      KJ_IF_SOME(prefix, maybePrefix) {\n        appendToPendingFixedValue(prefix);\n      }\n      return kj::none;\n    }\n    if (!maybeAddPartFromPendingFixedValue()) {\n      return kj::str(\"Syntax error in URL Pattern\");\n    }\n    if (nameToken == kj::none && regexOrWildcardToken == kj::none) {\n      KJ_DASSERT(suffix == kj::none || KJ_ASSERT_NONNULL(suffix).size() == 0);\n      KJ_IF_SOME(prefix, maybePrefix) {\n        if (prefix.size() > 0) {\n          KJ_IF_SOME(canonical, canonicalizer(prefix, kj::none)) {\n            partList.add(Part{\n              .type = Part::Type::FIXED_TEXT,\n              .modifier = modifier,\n              .value = kj::mv(canonical),\n            });\n          } else {\n            return kj::str(\"Syntax error in URL Pattern\");\n          }\n        }\n      }\n      return kj::none;\n    }\n    auto regexValue = kj::String();\n    KJ_IF_SOME(token, regexOrWildcardToken) {\n      if (token.type == Token::Type::ASTERISK) {\n        regexValue = kj::str(\".*\");\n      } else {\n        regexValue = kj::String(token);\n      }\n    } else {\n      regexValue = kj::str(segmentWildcardRegex);\n    }\n    auto type = Part::Type::REGEXP;\n    if (regexValue == segmentWildcardRegex) {\n      type = Part::Type::SEGMENT_WILDCARD;\n      regexValue = kj::String();\n    } else if (regexValue == \".*\") {\n      type = Part::Type::FULL_WILDCARD;\n      regexValue = kj::String();\n    }\n    auto name = kj::String();\n    KJ_IF_SOME(token, nameToken) {\n      name = kj::String(token);\n    } else if (regexOrWildcardToken != kj::none) {\n      name = kj::str(nextNumericName++);\n    }\n\n    if (isDuplicateName(name)) {\n      return kj::str(\"Syntax error in URL Pattern: Duplicated part names [\", name, \"]\");\n    }\n\n    kj::Maybe<kj::String> encodedPrefix;\n    kj::Maybe<kj::String> encodedSuffix;\n    KJ_IF_SOME(prefix, maybePrefix) {\n      KJ_IF_SOME(canonical, canonicalizer(prefix, kj::none)) {\n        encodedPrefix = kj::mv(canonical);\n      } else {\n        return kj::str(\"Syntax error in URL Pattern\");\n      }\n    }\n    KJ_IF_SOME(s, suffix) {\n      KJ_IF_SOME(canonical, canonicalizer(s, kj::none)) {\n        encodedSuffix = kj::mv(canonical);\n      } else {\n        return kj::str(\"Syntax error in URL Pattern\");\n      }\n    }\n\n    partList.add(Part{\n      .type = type,\n      .modifier = modifier,\n      .value = kj::mv(regexValue),\n      .name = kj::mv(name),\n      .prefix = kj::mv(encodedPrefix),\n      .suffix = kj::mv(encodedSuffix),\n    });\n\n    return kj::none;\n  };\n\n  while (index < tokens.size()) {\n    kj::Maybe<Token&> charToken = tryConsumeToken(Token::Type::CHAR);\n    kj::Maybe<Token&> nameToken = tryConsumeToken(Token::Type::NAME);\n    auto regexOrWildcardToken = tryConsumeRegexOrWildcardToken(nameToken);\n\n    if (nameToken != kj::none || regexOrWildcardToken != kj::none) {\n      auto maybePrefix = charToken.map([](Token& token) { return kj::String(token); });\n\n      KJ_IF_SOME(prefix, maybePrefix) {\n        if (prefix.size() > 0) {\n          KJ_IF_SOME(c, options.prefix) {\n            kj::String s;\n            if (prefix[0] != c) {\n              appendToPendingFixedValue(prefix);\n              maybePrefix = kj::none;\n            }\n          } else {\n            // If prefix is not empty, and is not the prefixCodePoint\n            // (which it can't be if we're here given that there is\n            // no prefix char), when we append prefix to pendingFixedValue,\n            // and clear prefix.\n            appendToPendingFixedValue(prefix);\n            maybePrefix = kj::none;\n          }\n        }\n      }\n      if (!maybeAddPartFromPendingFixedValue()) {\n        return kj::str(\"Syntax error in URL Pattern\");\n      }\n      auto modifierToken = tryConsumeModifierToken();\n      KJ_IF_SOME(err,\n          addPart(kj::mv(maybePrefix), nameToken, regexOrWildcardToken, kj::none, modifierToken)) {\n        return kj::mv(err);\n      }\n      continue;\n    }\n\n    kj::Maybe<Token&> fixedToken = charToken;\n    if (fixedToken == kj::none) {\n      fixedToken = tryConsumeToken(Token::Type::ESCAPED_CHAR);\n    }\n    KJ_IF_SOME(token, fixedToken) {\n      appendToPendingFixedValue(kj::String(token));\n      continue;\n    }\n    if (tryConsumeToken(Token::Type::OPEN) != kj::none) {\n      auto maybePrefix = consumeText();\n      auto nameToken = tryConsumeToken(Token::Type::NAME);\n      regexOrWildcardToken = tryConsumeRegexOrWildcardToken(nameToken);\n      auto suffix = consumeText();\n      if (tryConsumeToken(Token::Type::CLOSE) == kj::none) {\n        return kj::str(\"Syntax error in URL Pattern: Missing required close token\");\n      }\n      auto modifierToken = tryConsumeModifierToken();\n      KJ_IF_SOME(err,\n          addPart(kj::mv(maybePrefix), nameToken, regexOrWildcardToken, kj::mv(suffix),\n              modifierToken)) {\n        return kj::mv(err);\n      }\n      continue;\n    }\n    if (!maybeAddPartFromPendingFixedValue()) {\n      return kj::str(\"Syntax error in URL Pattern\");\n    }\n\n    if (tryConsumeToken(Token::Type::END) == kj::none) {\n      return kj::str(\"Syntax error in URL Pattern: Missing required end token\");\n    }\n  }\n\n  return partList.releaseAsArray();\n}\n\nRegexAndNameList generateRegexAndNameList(\n    kj::ArrayPtr<Part> partList, const CompileComponentOptions& options) {\n  // Worst case is that the nameList is equal to partList, although that will almost never\n  // be the case, so let's be more conservative in what we reserve.\n  kj::Vector<kj::String> nameList(partList.size() / 2);\n  StringTreeHolder regex(\"^\"_kj);\n\n  for (auto& part: partList) {\n    if (part.type == Part::Type::FIXED_TEXT) {\n      auto escaped = escapeRegexString(part.value);\n      if (part.modifier == Part::Modifier::NONE) {\n        regex.append(kj::mv(escaped));\n      } else {\n        regex.append(\"(?:\", kj::mv(escaped), \")\");\n        KJ_IF_SOME(c, modifierToString(part.modifier)) {\n          regex.append(c);\n        }\n      }\n      continue;\n    }\n\n    KJ_DASSERT(part.name.size() > 0);\n    nameList.add(kj::mv(part.name));\n    auto value = part.type == Part::Type::SEGMENT_WILDCARD ? kj::str(options.segmentWildcardRegexp)\n        : part.type == Part::Type::FULL_WILDCARD           ? kj::str(\".*\")\n                                                           : kj::mv(part.value);\n\n    if (part.prefix == kj::none && part.suffix == kj::none) {\n      if (part.modifier == Part::Modifier::NONE || part.modifier == Part::Modifier::OPTIONAL) {\n        regex.append(\"(\", value, \")\");\n        KJ_IF_SOME(c, modifierToString(part.modifier)) {\n          regex.append(c);\n        }\n      } else {\n        regex.append(\"((?:\", value, \")\");\n        KJ_IF_SOME(c, modifierToString(part.modifier)) {\n          regex.append(c, \")\");\n        } else {\n          regex.append(\")\");\n        }\n      }\n      continue;\n    }\n\n    auto escapedPrefix = part.prefix.map([](kj::String& str) {\n      return escapeRegexString(str);\n    }).orDefault(kj::String());\n    auto escapedSuffix = part.suffix.map([](kj::String& str) {\n      return escapeRegexString(str);\n    }).orDefault(kj::String());\n\n    if (part.modifier == Part::Modifier::NONE || part.modifier == Part::Modifier::OPTIONAL) {\n      regex.append(\"(?:\", escapedPrefix, \"(\", value, \")\", escapedSuffix, \")\");\n      KJ_IF_SOME(c, modifierToString(part.modifier)) {\n        regex.append(c);\n      }\n      continue;\n    }\n\n    regex.append(\"(?:\", escapedPrefix, \"((?:\", value, \")(?:\", escapedSuffix, escapedPrefix,\n        \"(?:\", value, \"))*)\", escapedSuffix, \")\");\n    if (part.modifier == Part::Modifier::ZERO_OR_MORE) {\n      regex.append(MODIFIER_ZERO_OR_MORE);\n    }\n  }\n\n  regex.append(\"$\");\n\n  return RegexAndNameList{\n    .regex = kj::mv(regex),\n    .names = nameList.releaseAsArray(),\n  };\n}\n\nkj::String generatePatternString(\n    kj::ArrayPtr<Part> partList, const CompileComponentOptions& options) {\n  StringTreeHolder pattern;\n  Part* previousPart = nullptr;\n  Part* nextPart = nullptr;\n  bool customName = false;\n  bool needsGrouping = false;\n  bool prefixIsEmpty = false;\n\n  const auto partPrefixEmpty = [](Part* part) {\n    if (part == nullptr) return true;\n    KJ_IF_SOME(prefix, part->prefix) {\n      return prefix.size() == 0;\n    }\n    return true;\n  };\n\n  const auto partSuffixEmpty = [](Part* part) {\n    if (part == nullptr) return true;\n    KJ_IF_SOME(prefix, part->suffix) {\n      return prefix.size() == 0;\n    }\n    return true;\n  };\n\n  const auto partSuffixIsValid = [&](Part* part) {\n    if (partSuffixEmpty(part)) return false;\n    auto& suffix = KJ_ASSERT_NONNULL(part->suffix);\n    return isValidCodepoint(suffix[0], false);\n  };\n\n  const auto checkNeedsGrouping = [&](Part& part) {\n    KJ_IF_SOME(suffix, part.suffix) {\n      if (suffix.size() > 0) return true;\n    }\n    KJ_IF_SOME(prefix, part.prefix) {\n      if (prefix.size() > 0) {\n        KJ_IF_SOME(c, options.prefix) {\n          return prefix[0] != c;\n        }\n      }\n    }\n    if (!needsGrouping && prefixIsEmpty && customName &&\n        part.type == Part::Type::SEGMENT_WILDCARD && part.modifier == Part::Modifier::NONE &&\n        nextPart != nullptr && partPrefixEmpty(nextPart) && partSuffixEmpty(nextPart)) {\n      if (nextPart->type == Part::Type::FIXED_TEXT) {\n        return nextPart->name.size() > 0 && isValidCodepoint(nextPart->name[0], false);\n      } else {\n        return nextPart->name.size() > 0 && isAsciiDigit(nextPart->name[0]);\n      }\n    }\n    return false;\n  };\n\n  for (size_t n = 0; n < partList.size(); n++) {\n    auto& part = partList[n];\n    previousPart = nullptr;\n    nextPart = nullptr;\n    if (n > 0) previousPart = &partList[n - 1];\n    if (n < partList.size() - 1) nextPart = &partList[n + 1];\n\n    if (part.type == Part::Type::FIXED_TEXT) {\n      if (part.modifier == Part::Modifier::NONE) {\n        pattern.append(escapePatternString(part.value));\n        continue;\n      }\n      pattern.append(\"{\", escapePatternString(part.value), \"}\");\n      KJ_IF_SOME(c, modifierToString(part.modifier)) {\n        pattern.append(c);\n      }\n      continue;\n    }\n\n    KJ_DASSERT(part.name.size() > 0);\n    customName = !isAsciiDigit(part.name[0]);\n    prefixIsEmpty = partPrefixEmpty(&part);\n    needsGrouping = checkNeedsGrouping(part);\n\n    if (!needsGrouping && prefixIsEmpty && previousPart != nullptr) {\n      // These additional checks on previousPart have to be separated out from the outer\n      // if because in some cases, they may be evaluated before the previousPart != nullptr\n      // check.\n      if (previousPart->type == Part::Type::FIXED_TEXT &&\n          (previousPart->value.size() > 0 &&\n              previousPart->value[previousPart->value.size() - 1] == options.prefix.orDefault(0))) {\n        needsGrouping = true;\n      }\n    }\n\n    StringTreeHolder subPattern;\n    KJ_IF_SOME(prefix, part.prefix) {\n      subPattern.append(escapePatternString(prefix));\n    }\n    if (customName) {\n      subPattern.append(\":\", part.name);\n    }\n\n    if (part.type == Part::Type::REGEXP) {\n      subPattern.append(\"(\", part.value, \")\");\n    } else if (part.type == Part::Type::SEGMENT_WILDCARD && !customName) {\n      subPattern.append(\"(\", options.segmentWildcardRegexp, \")\");\n    } else if (part.type == Part::Type::FULL_WILDCARD) {\n      if (!customName &&\n          (previousPart == nullptr || previousPart->type == Part::Type::FIXED_TEXT ||\n              previousPart->modifier != Part::Modifier::NONE || needsGrouping || !prefixIsEmpty)) {\n        subPattern.append(MODIFIER_ZERO_OR_MORE);\n      } else {\n        subPattern.append(\"(.*)\");\n      }\n    }\n    if (part.type == Part::Type::SEGMENT_WILDCARD && customName && partSuffixIsValid(&part)) {\n      subPattern.append(\"\\\\\");\n    }\n\n    KJ_IF_SOME(suffix, part.suffix) {\n      subPattern.append(escapePatternString(suffix));\n    }\n\n    if (needsGrouping) {\n      kj::String sub = kj::mv(subPattern);\n      subPattern = kj::strTree(\"{\", kj::mv(sub), \"}\");\n    }\n\n    KJ_IF_SOME(c, modifierToString(part.modifier)) {\n      subPattern.append(c);\n    }\n\n    kj::String sub = kj::mv(subPattern);\n    pattern.append(kj::mv(sub));\n  }\n  return kj::mv(pattern);\n}\n\nUrlPattern::Result<UrlPattern::Component> tryCompileComponent(kj::Maybe<kj::String>& input,\n    Canonicalizer canonicalizer,\n    const CompileComponentOptions& options) {\n  auto pattern = kj::mv(input).orDefault([] { return kj::str(MODIFIER_ZERO_OR_MORE); });\n  KJ_SWITCH_ONEOF(parsePattern(pattern, canonicalizer, options)) {\n    KJ_CASE_ONEOF(err, kj::String) {\n      return kj::mv(err);\n    }\n    KJ_CASE_ONEOF(partList, kj::Array<Part>) {\n      auto pattern = generatePatternString(partList, options);\n      auto regexAndNameList = generateRegexAndNameList(partList, options);\n      return UrlPattern::Component(\n          kj::mv(pattern), kj::mv(regexAndNameList.regex), kj::mv(regexAndNameList.names));\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nbool protocolComponentMatchesSpecialScheme(\n    kj::StringPtr regex, const UrlPattern::CompileOptions& options) {\n  std::regex rx(regex.begin(), regex.size());\n  std::cmatch cmatch;\n  return std::regex_match(\"http\", cmatch, rx) || std::regex_match(\"https\", cmatch, rx) ||\n      std::regex_match(\"ws\", cmatch, rx) || std::regex_match(\"wss\", cmatch, rx) ||\n      std::regex_match(\"ftp\", cmatch, rx);\n}\n\nUrlPattern::Result<UrlPattern::Init> tryParseConstructorString(\n    kj::StringPtr input, const UrlPattern::CompileOptions& options) {\n  enum class State {\n    INIT,\n    PROTOCOL,\n    AUTHORITY,\n    USERNAME,\n    PASSWORD,\n    HOSTNAME,\n    PORT,\n    PATHNAME,\n    SEARCH,\n    HASH,\n    DONE,\n  };\n  State state = State::INIT;\n\n  size_t inc = 0;\n  size_t depth = 0;\n  size_t ipv6Depth = 0;\n  bool protocolMatchesSpecialScheme = false;\n\n  UrlPattern::Init result{\n    .baseUrl = strFromMaybePtr(options.baseUrl),\n  };\n\n  kj::Array<Token> tokens = nullptr;\n  KJ_SWITCH_ONEOF(tokenize(input, Token::Policy::LENIENT)) {\n    KJ_CASE_ONEOF(err, kj::String) {\n      return kj::mv(err);\n    }\n    KJ_CASE_ONEOF(list, kj::Array<Token>) {\n      tokens = kj::mv(list);\n    }\n  }\n\n  // There should always be at least one token, and it should be type end.\n  KJ_DASSERT(tokens.size() > 0);\n  KJ_DASSERT(tokens.back().type == Token::Type::END);\n  auto it = tokens.begin();\n  auto start = it;\n\n  const auto rewind = [&](kj::Maybe<State> maybeNewState = kj::none) {\n    KJ_DASSERT(start <= it);\n    it = start;\n    KJ_DASSERT(tokens.begin() <= it && it < tokens.end());\n    inc = 0;\n    KJ_IF_SOME(newState, maybeNewState) {\n      state = newState;\n    }\n  };\n\n  const auto makeComponentString = [&]() {\n    KJ_DASSERT(tokens.begin() <= it && it < tokens.end());\n    KJ_DASSERT(start->index <= it->index);\n    return kj::str(input.slice(start->index, it->index));\n  };\n\n  const auto changeState = [&](State newState, int skip) {\n    if (state != State::INIT && state != State::AUTHORITY && state != State::DONE) {\n      auto value = makeComponentString();\n      switch (state) {\n        case State::PROTOCOL: {\n          result.protocol = kj::mv(value);\n          break;\n        }\n        case State::USERNAME: {\n          result.username = kj::mv(value);\n          break;\n        }\n        case State::PASSWORD: {\n          result.password = kj::mv(value);\n          break;\n        }\n        case State::HOSTNAME: {\n          result.hostname = kj::mv(value);\n          break;\n        }\n        case State::PORT: {\n          result.port = kj::mv(value);\n          break;\n        }\n        case State::PATHNAME: {\n          result.pathname = kj::mv(value);\n          break;\n        }\n        case State::SEARCH: {\n          result.search = kj::mv(value);\n          break;\n        }\n        case State::HASH: {\n          result.hash = kj::mv(value);\n          break;\n        }\n        case State::INIT: {\n          KJ_FALLTHROUGH;\n        }\n        case State::AUTHORITY: {\n          KJ_FALLTHROUGH;\n        }\n        case State::DONE: {\n          KJ_UNREACHABLE;\n        }\n      }\n    }\n    state = newState;\n    KJ_DASSERT(it + skip <= tokens.end());\n    it += skip;\n    KJ_DASSERT(tokens.begin() <= it && it < tokens.end());\n    start = it;\n    inc = 0;\n  };\n\n  const auto isNonSpecialPatternChar = [&](auto iter, char c) {\n    KJ_DASSERT(tokens.begin() <= iter && iter < tokens.end());\n    Token& token = *iter;\n    return (token.type == Token::Type::CHAR || token.type == Token::Type::ESCAPED_CHAR ||\n               token.type == Token::Type::INVALID_CHAR) &&\n        token == c;\n  };\n\n  const auto isProtocolSuffix = [&]() { return isNonSpecialPatternChar(it, ':'); };\n\n  const auto nextIsAuthoritySlashes = [&]() {\n    return isNonSpecialPatternChar(it + 1, '/') && isNonSpecialPatternChar(it + 2, '/');\n  };\n\n  const auto isIdentityTerminator = [&]() { return isNonSpecialPatternChar(it, '@'); };\n\n  const auto isPasswordPrefix = [&]() { return isNonSpecialPatternChar(it, ':'); };\n\n  const auto isPortPrefix = [&]() { return isNonSpecialPatternChar(it, ':'); };\n\n  const auto isPathnameStart = [&]() { return isNonSpecialPatternChar(it, '/'); };\n\n  const auto isSearchPrefix = [&]() {\n    if (isNonSpecialPatternChar(it, '?')) {\n      return true;\n    }\n    auto& token = *it;\n    if (token != '?') return false;\n\n    if (it == tokens.begin()) return true;\n\n    auto& previousToken = *(it - 1);\n    return previousToken.type != Token::Type::NAME && previousToken.type != Token::Type::REGEXP &&\n        previousToken.type != Token::Type::CLOSE && previousToken.type != Token::Type::ASTERISK;\n  };\n\n  const auto isHashPrefix = [&]() { return isNonSpecialPatternChar(it, '#'); };\n\n  const auto isGroupOpen = [&]() { return it->type == Token::Type::OPEN; };\n\n  const auto isGroupClose = [&]() { return it->type == Token::Type::CLOSE; };\n\n  const auto isIPv6Open = [&]() { return isNonSpecialPatternChar(it, '['); };\n\n  const auto isIPv6Close = [&]() { return isNonSpecialPatternChar(it, ']'); };\n\n  const auto computeMatchesSpecialScheme = [&] {\n    kj::Maybe<kj::String> input = makeComponentString();\n    KJ_SWITCH_ONEOF(tryCompileComponent(\n                        input, &canonicalizeProtocol, CompileComponentOptions::DEFAULT)) {\n      KJ_CASE_ONEOF(err, kj::String) {\n        // Ignore any errors at this point. If the component is invalid we'll\n        // catch it later.\n        return false;\n      }\n      KJ_CASE_ONEOF(component, UrlPattern::Component) {\n        return protocolComponentMatchesSpecialScheme(component.getRegex(), options);\n      }\n    }\n    KJ_UNREACHABLE;\n  };\n\n  while (it != tokens.end()) {\n    Token& token = *it;\n    inc = 1;\n\n    if (token.type == Token::Type::END) {\n      if (state == State::INIT) {\n        rewind();\n        if (isHashPrefix()) {\n          changeState(State::HASH, 1);\n        } else if (isSearchPrefix()) {\n          changeState(State::SEARCH, 1);\n          result.hash = kj::String();\n        } else {\n          changeState(State::PATHNAME, 0);\n          result.search = kj::String();\n          result.hash = kj::String();\n        }\n        // Since we called rewind and we know that sets inc to zero,\n        // and we know that nothing else here changed inc, there's no\n        // need to try to advance. Just continue.\n        continue;\n      }\n      if (state == State::AUTHORITY) {\n        rewind(State::HOSTNAME);\n        // Since we called rewind and we know that sets inc to zero,\n        // there's no need to try to advance. Just continue.\n        continue;\n      }\n      // We hit the end and we're all done!\n      changeState(State::DONE, 0);\n      break;\n    }\n    if (isGroupOpen()) {\n      depth++;\n      it += inc;\n      continue;\n    }\n    if (depth > 0) {\n      if (isGroupClose()) {\n        depth--;\n      } else {\n        it += inc;\n        continue;\n      }\n    }\n\n    switch (state) {\n      case State::INIT: {\n        if (isProtocolSuffix()) {\n          result.username = kj::String();\n          result.password = kj::String();\n          result.hostname = kj::String();\n          result.port = kj::String();\n          result.pathname = kj::String();\n          result.search = kj::String();\n          result.hash = kj::String();\n          rewind(State::PROTOCOL);\n        }\n        break;\n      }\n      case State::PROTOCOL: {\n        if (isProtocolSuffix()) {\n          computeMatchesSpecialScheme();\n          if (protocolMatchesSpecialScheme) result.pathname = kj::str(\"/\");\n          if (nextIsAuthoritySlashes())\n            changeState(State::AUTHORITY, 3);\n          else if (protocolMatchesSpecialScheme)\n            changeState(State::AUTHORITY, 1);\n          else\n            changeState(State::PATHNAME, 1);\n        }\n        break;\n      }\n      case State::AUTHORITY: {\n        if (isIdentityTerminator())\n          rewind(State::USERNAME);\n        else if (isPathnameStart() || isSearchPrefix() || isHashPrefix())\n          rewind(State::HOSTNAME);\n        break;\n      }\n      case State::USERNAME: {\n        if (isPasswordPrefix())\n          changeState(State::PASSWORD, 1);\n        else if (isIdentityTerminator())\n          changeState(State::HOSTNAME, 1);\n        break;\n      }\n      case State::PASSWORD: {\n        if (isIdentityTerminator()) changeState(State::HOSTNAME, 1);\n        break;\n      }\n      case State::HOSTNAME: {\n        if (isIPv6Open())\n          ipv6Depth++;\n        else if (isIPv6Close())\n          ipv6Depth--;\n        else if (isPortPrefix() && ipv6Depth == 0)\n          changeState(State::PORT, 1);\n        else if (isPathnameStart())\n          changeState(State::PATHNAME, 0);\n        else if (isSearchPrefix())\n          changeState(State::SEARCH, 1);\n        else if (isHashPrefix())\n          changeState(State::HASH, 1);\n        break;\n      }\n      case State::PORT: {\n        if (isPathnameStart())\n          changeState(State::PATHNAME, 0);\n        else if (isSearchPrefix())\n          changeState(State::SEARCH, 1);\n        else if (isHashPrefix())\n          changeState(State::HASH, 1);\n        break;\n      }\n      case State::PATHNAME: {\n        if (isSearchPrefix())\n          changeState(State::SEARCH, 1);\n        else if (isHashPrefix())\n          changeState(State::HASH, 1);\n        break;\n      }\n      case State::SEARCH: {\n        if (isHashPrefix()) changeState(State::HASH, 1);\n        break;\n      }\n      case State::HASH: {\n        // Nothing to do.\n        break;\n      }\n      case State::DONE: {\n        KJ_UNREACHABLE;\n      }\n    }\n\n    it += inc;\n  }\n\n  if (result.protocol == kj::none && result.baseUrl == kj::none) {\n    return kj::str(\"Syntax error in URL Pattern: a relative pattern must have a base URL.\");\n  }\n\n  return kj::mv(result);\n}\n}  // namespace\n\nUrlPattern::Component::Component(kj::String pattern, kj::String regex, kj::Array<kj::String> names)\n    : pattern(kj::mv(pattern)),\n      regex(kj::mv(regex)),\n      names(kj::mv(names)) {}\n\nUrlPattern::Result<UrlPattern> UrlPattern::tryCompileInit(\n    UrlPattern::Init init, const UrlPattern::CompileOptions& options) {\n  kj::Vector<UrlPattern::Component> components(7);\n\n  bool matchesSpecialScheme = false;\n\n  KJ_SWITCH_ONEOF(tryCompileComponent(\n                      init.protocol, &canonicalizeProtocol, CompileComponentOptions::DEFAULT)) {\n    KJ_CASE_ONEOF(err, kj::String) {\n      return kj::mv(err);\n    }\n    KJ_CASE_ONEOF(component, UrlPattern::Component) {\n      matchesSpecialScheme = protocolComponentMatchesSpecialScheme(component.getRegex(), options);\n      components.add(kj::mv(component));\n    }\n  }\n\n  const auto handleComponent =\n      [&](auto& input, Canonicalizer canonicalizer,\n          const CompileComponentOptions& options) -> kj::Maybe<kj::String> {\n    KJ_SWITCH_ONEOF(tryCompileComponent(input, canonicalizer, options)) {\n      KJ_CASE_ONEOF(err, kj::String) {\n        return kj::mv(err);\n      }\n      KJ_CASE_ONEOF(component, UrlPattern::Component) {\n        components.add(kj::mv(component));\n        return kj::none;\n      }\n    }\n    KJ_UNREACHABLE;\n  };\n\n  KJ_IF_SOME(err,\n      handleComponent(init.username, &canonicalizeUsername, CompileComponentOptions::DEFAULT)) {\n    return kj::mv(err);\n  }\n  KJ_IF_SOME(err,\n      handleComponent(init.password, &canonicalizePassword, CompileComponentOptions::DEFAULT)) {\n    return kj::mv(err);\n  }\n\n  Canonicalizer* hostnameCanonicalizer = &canonicalizeHostname;\n  KJ_IF_SOME(hostname, init.hostname) {\n    if (isIpv6(hostname.asPtr())) {\n      hostnameCanonicalizer = &canonicalizeIpv6Hostname;\n    }\n  }\n  KJ_IF_SOME(err,\n      handleComponent(init.hostname, hostnameCanonicalizer, CompileComponentOptions::HOSTNAME)) {\n    return kj::mv(err);\n  }\n\n  KJ_IF_SOME(err, handleComponent(init.port, &canonicalizePort, CompileComponentOptions::DEFAULT)) {\n    return kj::mv(err);\n  }\n\n  KJ_IF_SOME(err,\n      handleComponent(init.pathname,\n          matchesSpecialScheme ? &canonicalizePathname : &canonicalizeOpaquePathname,\n          matchesSpecialScheme ? CompileComponentOptions::PATHNAME\n                               : CompileComponentOptions::DEFAULT)) {\n    return kj::mv(err);\n  }\n  KJ_IF_SOME(err,\n      handleComponent(init.search, &canonicalizeSearch, CompileComponentOptions::DEFAULT)) {\n    return kj::mv(err);\n  }\n  KJ_IF_SOME(err, handleComponent(init.hash, &canonicalizeHash, CompileComponentOptions::DEFAULT)) {\n    return kj::mv(err);\n  }\n  return UrlPattern(components.releaseAsArray(), options.ignoreCase);\n}\n\nUrlPattern::Result<UrlPattern::Init> UrlPattern::processInit(\n    UrlPattern::Init init, kj::Maybe<UrlPattern::ProcessInitOptions> maybeOptions) {\n  auto options = maybeOptions.orDefault({});\n\n  Init result;\n  kj::Maybe<Url> maybeBaseUrl;\n\n  const auto isAbsolutePathname = [&](kj::StringPtr str) {\n    if (str.size() == 0) return false;\n    char c = str[0];\n    if (c == '/') return true;\n    if (options.mode == ProcessInitOptions::Mode::URL) return false;\n    return str.size() > 1 && (c == '\\\\' || c == '{') && str[1] == '/';\n  };\n\n  KJ_IF_SOME(base, init.baseUrl) {\n    KJ_IF_SOME(url, Url::tryParse(base.asPtr())) {\n      result.protocol = stripSuffixFromProtocol(url.getProtocol());\n      result.username = kj::str(url.getUsername());\n      result.password = kj::str(url.getPassword());\n      result.hostname = kj::str(url.getHostname());\n      result.port = kj::str(url.getPort());\n      result.pathname = escapePatternString(url.getPathname());\n      if (url.getSearch().size() > 0) {\n        result.search = escapePatternString(url.getSearch().slice(1));\n      } else {\n        result.search = kj::String();\n      }\n      if (url.getHash().size() > 0) {\n        result.hash = escapePatternString(url.getHash().slice(1));\n      } else {\n        result.hash = kj::String();\n      }\n      result.baseUrl = kj::mv(base);\n      maybeBaseUrl = kj::mv(url);\n    } else {\n      return kj::str(\"Invalid base URL.\");\n    }\n  }\n\n  if (options.mode == ProcessInitOptions::Mode::PATTERN) {\n    KJ_IF_SOME(protocol,\n        chooseStr(kj::mv(init.protocol), options.protocol).map([](kj::String&& str) mutable {\n      // It's silly but the URL spec always includes the : suffix in the value,\n      // while the URLPattern spec always omits it. Silly specs.\n      if (!str.size()) {\n        return kj::mv(str);\n      }\n      return stripSuffixFromProtocol(str.asPtr());\n    })) {\n      result.protocol = kj::mv(protocol);\n    }\n    KJ_IF_SOME(username, chooseStr(kj::mv(init.username), options.username)) {\n      result.username = kj::mv(username);\n    }\n    KJ_IF_SOME(password, chooseStr(kj::mv(init.password), options.password)) {\n      result.password = kj::mv(password);\n    }\n    KJ_IF_SOME(hostname, chooseStr(kj::mv(init.hostname), options.hostname)) {\n      result.hostname = kj::mv(hostname);\n    }\n    KJ_IF_SOME(port, chooseStr(kj::mv(init.port), options.port)) {\n      result.port = kj::mv(port);\n    }\n    KJ_IF_SOME(pathname, chooseStr(kj::mv(init.pathname), options.pathname)) {\n      if (!isAbsolutePathname(pathname)) {\n        KJ_IF_SOME(url, maybeBaseUrl) {\n          auto basePathname = url.getPathname();\n          KJ_IF_SOME(index, basePathname.findLast('/')) {\n            result.pathname = kj::str(basePathname.first(index + 1), pathname);\n          } else {\n            result.pathname = kj::str(basePathname);\n          }\n        } else {\n          result.pathname = kj::mv(pathname);\n        }\n      } else {\n        result.pathname = kj::mv(pathname);\n      }\n    }\n    KJ_IF_SOME(search, chooseStr(kj::mv(init.search), options.search)) {\n      if (search.size() > 0 && search[0] == '?') {\n        result.search = kj::str(search.slice(1));\n      } else {\n        result.search = kj::mv(search);\n      }\n    }\n    KJ_IF_SOME(hash, chooseStr(kj::mv(init.hash), options.hash)) {\n      if (hash.size() > 0 && hash[0] == '#') {\n        result.hash = kj::str(hash.slice(1));\n      } else {\n        result.hash = kj::mv(hash);\n      }\n    }\n    return result;\n  }\n\n  KJ_DASSERT(options.mode == ProcessInitOptions::Mode::URL);\n\n  // Things are a bit more complicated in this case. The individual components\n  // of Init are interpreted as URL components. The processing here must convert\n  // those into a canonical form. Unfortunately, however, it's not *quite* as\n  // simple as constructing a URL string from the inputs, parsing it, and then\n  // deconstructing the result. The validation rules per the URLPattern spec are\n  // a bit different for some of the components than for the URL spec so we handle\n  // each individually.\n\n  bool isAbsolute = false;\n  auto scratch = ([&]() -> kj::OneOf<Url, kj::String> {\n    KJ_IF_SOME(protocol, chooseStr(kj::mv(init.protocol), options.protocol)) {\n      // The protocol value we are given might not be valid. We'll check by\n      // attempting to use it to parse a URL.\n      bool emptyProtocol = protocol == \"\";\n      auto str = kj::str((emptyProtocol ? \"fake:\"_kj : protocol.asPtr()),\n          (emptyProtocol || protocol.asArray().back() == ':') ? \"\" : \":\", \"//a:b@fake-url\");\n      KJ_IF_SOME(parsed, Url::tryParse(str.asPtr())) {\n        // Nice. We have a good protocol component. Let's set the normalized version\n        // on the result and return the parsed URL to use as our temporary.\n        if (!emptyProtocol) {\n          result.protocol = stripSuffixFromProtocol(parsed.getProtocol());\n        }\n\n        // We set isAbsolute true here so that when we later want to normalize the\n        // pathname, we know not to try to resolve the path relative to the base.\n        isAbsolute = true;\n        return kj::mv(parsed);\n      } else {\n        // Doh, parsing failed. The protocol component is invalid.\n        return kj::str(\"Invalid URL protocol component\");\n      }\n    } else {\n      // There was not protocol component in the init or options. We still might\n      // have a base URL protocol. If we do, we're going to use it to construct\n      // our temporary URL we will use to canonicalize the rest. If we do not,\n      // we'll use a fake URL scheme.\n      KJ_IF_SOME(protocol, result.protocol) {\n        // We only want to create the temporary URL here and return it.\n        auto str = kj::str(protocol, \"://fake-url\");\n        return KJ_ASSERT_NONNULL(Url::tryParse(str.asPtr()));\n      } else {\n        return KJ_ASSERT_NONNULL(Url::tryParse(\"fake://fake-url\"_kj));\n      }\n    }\n  })();\n\n  KJ_SWITCH_ONEOF(scratch) {\n    KJ_CASE_ONEOF(err, kj::String) {\n      // Invalid URL protocol component.\n      return kj::mv(err);\n    }\n    KJ_CASE_ONEOF(url, Url) {\n      KJ_IF_SOME(username, chooseStr(kj::mv(init.username), options.username)) {\n        if (!url.setUsername(username.asPtr())) {\n          return kj::str(\"Invalid URL username component\");\n        }\n        result.username = kj::str(url.getUsername());\n      }\n      KJ_IF_SOME(password, chooseStr(kj::mv(init.password), options.password)) {\n        if (!url.setPassword(password.asPtr())) {\n          return kj::str(\"Invalid URL password component\");\n        }\n        result.password = kj::str(url.getPassword());\n      }\n      KJ_IF_SOME(hostname, chooseStr(kj::mv(init.hostname), options.hostname)) {\n        if (!isValidHostnameInput(hostname) || !url.setHostname(hostname.asPtr())) {\n          return kj::str(\"Invalid URL hostname component\");\n        }\n        result.hostname = kj::str(url.getHostname());\n      }\n      KJ_IF_SOME(port, chooseStr(kj::mv(init.port), options.port)) {\n        if (port.size() > 5 || !std::all_of(port.begin(), port.end(), isAsciiDigit)) {\n          return kj::str(\"Invalid URL port component\");\n        }\n        if (port.size() == 0) {\n          url.setPort(kj::none);\n        } else if (!url.setPort(kj::Maybe(port.asPtr()))) {\n          return kj::str(\"Invalid URL port component\");\n        }\n        result.port = kj::str(url.getPort());\n      }\n      KJ_IF_SOME(pathname, chooseStr(kj::mv(init.pathname), options.pathname)) {\n        if (isAbsolute) {\n          // isAbsolute is set only if we have an explicit protocol set for in init\n          // or options. This tells us that we are not going to resolve the path\n          // relative to the base URL at all.\n          if (!url.setPathname(pathname.asPtr())) {\n            return kj::str(\"Invalid URL pathname component\");\n          }\n          result.pathname = kj::str(url.getPathname());\n        } else {\n          // Here, our init/options did not specify a protocol, so we're either relying\n          // on the base URL or the fake. If we have a base URL, then, we want to resolve\n          // the path relative to the base URL path.\n          KJ_IF_SOME(base, maybeBaseUrl) {\n            // If there is a base URL, then we'll normalize the path by attempting to\n            // resolve against the base.\n            KJ_IF_SOME(resolved, base.resolve(pathname.asPtr())) {\n              result.pathname = kj::str(resolved.getPathname());\n            } else {\n              return kj::str(\"Invalid URL pathname component\");\n            }\n          } else {\n            if (!url.setPathname(pathname.asPtr())) {\n              return kj::str(\"Invalid URL pathname component\");\n            }\n            result.pathname = kj::str(url.getPathname());\n          }\n        }\n      }\n      KJ_IF_SOME(search, chooseStr(kj::mv(init.search), options.search)) {\n        url.setSearch(kj::Maybe(search.asPtr()));\n        // We slice here because the URL getter will always include the ? prefix\n        // but the URLPattern spec does not want it.\n        if (url.getSearch().size() > 0) {\n          result.search = kj::str(url.getSearch().slice(1));\n        } else {\n          result.search = kj::String();\n        }\n      }\n      KJ_IF_SOME(hash, chooseStr(kj::mv(init.hash), options.hash)) {\n        url.setHash(kj::Maybe(hash.asPtr()));\n        // We slice here because the URL getter will always include the # prefix\n        // but the URLPattern spec does not want it.\n        if (url.getHash().size() > 0) {\n          result.hash = kj::str(url.getHash().slice(1));\n        } else {\n          result.hash = kj::String();\n        }\n      }\n      return result;\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nUrlPattern::Result<UrlPattern> UrlPattern::tryCompile(\n    Init init, kj::Maybe<CompileOptions> maybeOptions) {\n  auto options = maybeOptions.orDefault({});\n  KJ_SWITCH_ONEOF(processInit(kj::mv(init))) {\n    KJ_CASE_ONEOF(err, kj::String) {\n      return kj::mv(err);\n    }\n    KJ_CASE_ONEOF(init, UrlPattern::Init) {\n      return tryCompileInit(kj::mv(init), options);\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nUrlPattern::Result<UrlPattern> UrlPattern::tryCompile(\n    kj::StringPtr input, kj::Maybe<CompileOptions> maybeOptions) {\n  auto options = maybeOptions.orDefault({});\n  KJ_SWITCH_ONEOF(tryParseConstructorString(input, options)) {\n    KJ_CASE_ONEOF(err, kj::String) {\n      return kj::mv(err);\n    }\n    KJ_CASE_ONEOF(init, UrlPattern::Init) {\n      KJ_SWITCH_ONEOF(processInit(kj::mv(init))) {\n        KJ_CASE_ONEOF(err, kj::String) {\n          return kj::mv(err);\n        }\n        KJ_CASE_ONEOF(init, UrlPattern::Init) {\n          return tryCompileInit(kj::mv(init), options);\n        }\n      }\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nUrlPattern::UrlPattern(kj::Array<Component> components, bool ignoreCase)\n    : protocol(kj::mv(components[0])),\n      username(kj::mv(components[1])),\n      password(kj::mv(components[2])),\n      hostname(kj::mv(components[3])),\n      port(kj::mv(components[4])),\n      pathname(kj::mv(components[5])),\n      search(kj::mv(components[6])),\n      hash(kj::mv(components[7])),\n      ignoreCase(ignoreCase) {}\n\n}  // namespace workerd::jsg\n\nconst workerd::jsg::Url operator\"\"_url(const char* str, size_t size) {\n  return KJ_ASSERT_NONNULL(workerd::jsg::Url::tryParse(kj::ArrayPtr<const char>(str, size)));\n}\n"
  },
  {
    "path": "src/workerd/jsg/url.h",
    "content": "#pragma once\n#include <workerd/jsg/memory.h>\n\n#include <kj/common.h>\n#include <kj/one-of.h>\n#include <kj/string.h>\n\nnamespace workerd::jsg {\n\n// A WHATWG-compliant URL implementation provided by ada-url.\nclass Url final {\n public:\n  // Keep in sync with ada::scheme:type\n  enum class SchemeType {\n    HTTP = 0,\n    NOT_SPECIAL = 1,\n    HTTPS = 2,\n    WS = 3,\n    FTP = 4,\n    WSS = 5,\n    FILE = 6\n  };\n\n  // Keep in sync with ada::url_host_type\n  enum class HostType {\n    DEFAULT = 0,\n    IPV4 = 1,\n    IPV6 = 2,\n  };\n\n  Url(decltype(nullptr)) {}\n  Url(Url&& other) = default;\n  KJ_DISALLOW_COPY(Url);\n\n  Url& operator=(Url&& other) = default;\n  bool operator==(const Url& other) const KJ_WARN_UNUSED_RESULT;\n\n  enum class EquivalenceOption {\n    DEFAULT = 0,\n    // When set, the fragment/hash portion of the URL will be ignored when comparing or\n    // cloning URLs.\n    IGNORE_FRAGMENTS = 1 << 0,\n    // When set, the search portion of the URL will be ignored when comparing or cloning URLs.\n    IGNORE_SEARCH = 1 << 1,\n    // When set, the pathname portion of the URL will be normalized by percent-decoding\n    // then re-encoding the pathname. This is useful when comparing URLs that may have\n    // different, but equivalent percent-encoded paths. e.g. %66oo and foo are equivalent.\n    NORMALIZE_PATH = 1 << 2,\n  };\n\n  bool equal(const Url& other,\n      EquivalenceOption option = EquivalenceOption::DEFAULT) const KJ_WARN_UNUSED_RESULT;\n\n  // Returns true if the given input can be successfully parsed as a URL. This is generally\n  // more performant than using tryParse and checking for a kj::none result if all you want\n  // to do is verify that the input is parseable. If you actually want to parse and use the\n  // result, use tryParse instead.\n  static bool canParse(kj::ArrayPtr<const char> input,\n      kj::Maybe<kj::ArrayPtr<const char>> base = kj::none) KJ_WARN_UNUSED_RESULT;\n  static bool canParse(\n      kj::StringPtr input, kj::Maybe<kj::StringPtr> base = kj::none) KJ_WARN_UNUSED_RESULT;\n\n  static kj::Maybe<Url> tryParse(kj::ArrayPtr<const char> input,\n      kj::Maybe<kj::ArrayPtr<const char>> base = kj::none) KJ_WARN_UNUSED_RESULT;\n  static kj::Maybe<Url> tryParse(\n      kj::StringPtr input, kj::Maybe<kj::StringPtr> base = kj::none) KJ_WARN_UNUSED_RESULT;\n\n  kj::Array<const char> getOrigin() const KJ_WARN_UNUSED_RESULT;\n  kj::ArrayPtr<const char> getProtocol() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;\n  kj::ArrayPtr<const char> getHref() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;\n  kj::ArrayPtr<const char> getPathname() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;\n  kj::ArrayPtr<const char> getUsername() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;\n  kj::ArrayPtr<const char> getPassword() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;\n  kj::ArrayPtr<const char> getPort() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;\n  kj::ArrayPtr<const char> getHash() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;\n  kj::ArrayPtr<const char> getHost() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;\n  kj::ArrayPtr<const char> getHostname() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;\n  kj::ArrayPtr<const char> getSearch() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;\n\n  bool setHref(kj::ArrayPtr<const char> value);\n  bool setHost(kj::ArrayPtr<const char> value);\n  bool setHostname(kj::ArrayPtr<const char> value);\n  bool setProtocol(kj::ArrayPtr<const char> value);\n  bool setUsername(kj::ArrayPtr<const char> value);\n  bool setPassword(kj::ArrayPtr<const char> value);\n  bool setPort(kj::Maybe<kj::ArrayPtr<const char>> value);\n  bool setPathname(kj::ArrayPtr<const char> value);\n  void setSearch(kj::Maybe<kj::ArrayPtr<const char>> value);\n  void setHash(kj::Maybe<kj::ArrayPtr<const char>> value);\n\n  kj::uint hashCode() const;\n\n  kj::Maybe<Url> resolve(kj::ArrayPtr<const char> input) KJ_WARN_UNUSED_RESULT;\n\n  // Copies this Url. If the option is set of EquivalenceOption::IGNORE_FRAGMENTS, the\n  // copied Url will clear any fragment/hash that exists.\n  Url clone(EquivalenceOption option = EquivalenceOption::DEFAULT) const KJ_WARN_UNUSED_RESULT;\n\n  // Resolve the input relative to this URL\n  kj::Maybe<Url> tryResolve(kj::ArrayPtr<const char> input) const KJ_WARN_UNUSED_RESULT;\n\n  enum class RelativeOption {\n    DEFAULT,\n    // If the URL ends with a trailing slash, remove it before determining the basename.\n    STRIP_TAILING_SLASHES,\n  };\n  struct Relative;\n  // Given this URL, returns a struct that is a basename and a base Url pair\n  // such that base.tryResolve(basename) is equivalent to this URL. Query\n  // parameters and fragments are not preserved.\n  Relative getRelative(RelativeOption option = RelativeOption::DEFAULT) const;\n\n  // Returns the parent URL of this URL, which is the URL with the last path component\n  // removed and the trailing slash removed if it exists. For instance, if the URL is\n  // \"https://example.com/foo/bar/baz\", the parent URL will be \"https://example.com/foo/bar\".\n  // If the URL has no parent (e.g. \"https://example.com/\") kj::none is returned.\n  kj::Maybe<jsg::Url> getParent() const KJ_WARN_UNUSED_RESULT;\n\n  HostType getHostType() const;\n  SchemeType getSchemeType() const;\n\n  // Convert an ASCII hostname to Unicode.\n  static kj::Array<const char> idnToUnicode(kj::ArrayPtr<const char> value) KJ_WARN_UNUSED_RESULT;\n\n  // Convert a Unicode hostname to ASCII.\n  static kj::Array<const char> idnToAscii(kj::ArrayPtr<const char> value) KJ_WARN_UNUSED_RESULT;\n\n  static bool isSpecialScheme(kj::StringPtr protocol);\n  static bool isSpecialSchemeDefaultPort(kj::StringPtr protocol, kj::StringPtr port);\n\n  JSG_MEMORY_INFO(Url) {\n    tracker.trackFieldWithSize(\"inner\",\n        getProtocol().size() + getUsername().size() + getPassword().size() + getHost().size() +\n            getPathname().size() + getHash().size() + getSearch().size());\n  }\n\n  static kj::Array<kj::byte> percentDecode(kj::ArrayPtr<const kj::byte> input);\n\n private:\n  Url(kj::Own<void> inner);\n  kj::Own<void> inner;\n};\n\nstruct Url::Relative {\n  Url base;\n  kj::String name;\n};\n\nconstexpr Url::EquivalenceOption operator|(Url::EquivalenceOption a, Url::EquivalenceOption b) {\n  return static_cast<Url::EquivalenceOption>(static_cast<int>(a) | static_cast<int>(b));\n}\nconstexpr Url::EquivalenceOption operator&(Url::EquivalenceOption a, Url::EquivalenceOption b) {\n  return static_cast<Url::EquivalenceOption>(static_cast<int>(a) & static_cast<int>(b));\n}\n\nclass UrlSearchParams final {\n public:\n  class KeyIterator final {\n   public:\n    bool hasNext() const;\n    kj::Maybe<kj::ArrayPtr<const char>> next() const;\n\n   private:\n    KeyIterator(kj::Own<void> inner);\n    kj::Own<void> inner;\n    friend class UrlSearchParams;\n  };\n  class ValueIterator final {\n   public:\n    bool hasNext() const;\n    kj::Maybe<kj::ArrayPtr<const char>> next() const;\n\n   private:\n    ValueIterator(kj::Own<void> inner);\n    kj::Own<void> inner;\n    friend class UrlSearchParams;\n  };\n  class EntryIterator final {\n   public:\n    struct Entry {\n      kj::ArrayPtr<const char> key;\n      kj::ArrayPtr<const char> value;\n    };\n    bool hasNext() const;\n    kj::Maybe<Entry> next() const;\n\n   private:\n    EntryIterator(kj::Own<void> inner);\n    kj::Own<void> inner;\n    friend class UrlSearchParams;\n  };\n\n  UrlSearchParams();\n  UrlSearchParams(UrlSearchParams&& other) = default;\n  KJ_DISALLOW_COPY(UrlSearchParams);\n\n  UrlSearchParams& operator=(UrlSearchParams&& other) = default;\n  bool operator==(const UrlSearchParams& other) const KJ_WARN_UNUSED_RESULT;\n\n  static kj::Maybe<UrlSearchParams> tryParse(kj::ArrayPtr<const char> input) KJ_WARN_UNUSED_RESULT;\n\n  size_t size() const;\n  void append(kj::ArrayPtr<const char> key, kj::ArrayPtr<const char> value);\n  void set(kj::ArrayPtr<const char> key, kj::ArrayPtr<const char> value);\n  void delete_(\n      kj::ArrayPtr<const char> key, kj::Maybe<kj::ArrayPtr<const char>> maybeValue = kj::none);\n  bool has(kj::ArrayPtr<const char> key,\n      kj::Maybe<kj::ArrayPtr<const char>> maybeValue = kj::none) const KJ_WARN_UNUSED_RESULT;\n  kj::Maybe<kj::ArrayPtr<const char>> get(\n      kj::ArrayPtr<const char> key) const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;\n  kj::Array<kj::ArrayPtr<const char>> getAll(\n      kj::ArrayPtr<const char> key) const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;\n  void sort();\n  KeyIterator getKeys() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;\n  ValueIterator getValues() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;\n  EntryIterator getEntries() const KJ_LIFETIMEBOUND KJ_WARN_UNUSED_RESULT;\n\n  kj::Array<const char> toStr() const KJ_WARN_UNUSED_RESULT;\n\n  JSG_MEMORY_INFO(Url) {\n    tracker.trackField(\"inner\", toStr());\n  }\n\n  void reset(kj::Maybe<kj::ArrayPtr<const char>> input);\n\n private:\n  UrlSearchParams(kj::Own<void> inner);\n  kj::Own<void> inner;\n};\n\ninline kj::String KJ_STRINGIFY(const Url& url) {\n  return kj::str(url.getHref());\n}\n\ninline kj::String KJ_STRINGIFY(const UrlSearchParams& searchParams) {\n  return kj::str(searchParams.toStr());\n}\n\n// ======================================================================================\n\n// Encapsulates a parsed URLPattern.\n// @see https://wicg.github.io/urlpattern\nclass UrlPattern final {\n public:\n  // If the value is T, the operation is successful.\n  // If the value is kj::String, that's an Error message.\n  template <typename T>\n  using Result = kj::OneOf<T, kj::String>;\n\n  // An individual, compiled component of a URLPattern.\n  class Component final {\n   public:\n    Component(kj::String pattern, kj::String regex, kj::Array<kj::String> names);\n\n    Component(Component&&) = default;\n    Component& operator=(Component&&) = default;\n    KJ_DISALLOW_COPY(Component);\n\n    inline kj::StringPtr getPattern() const KJ_LIFETIMEBOUND {\n      return pattern;\n    }\n    inline kj::StringPtr getRegex() const KJ_LIFETIMEBOUND {\n      return regex;\n    }\n    inline kj::ArrayPtr<const kj::String> getNames() const KJ_LIFETIMEBOUND {\n      return names.asPtr();\n    }\n\n    JSG_MEMORY_INFO(Component) {\n      tracker.trackField(\"pattern\", pattern);\n      tracker.trackField(\"regex\", regex);\n      for (const auto& name: names) {\n        tracker.trackField(\"name\", name);\n      }\n    }\n\n   private:\n    // The normalized pattern for this component.\n    kj::String pattern = nullptr;\n\n    // The generated JavaScript regular expression for this component.\n    kj::String regex = nullptr;\n\n    // The list of sub-component names extracted for this component.\n    kj::Array<kj::String> names = nullptr;\n  };\n\n  // A structure providing matching patterns for individual components of a URL.\n  struct Init {\n    kj::Maybe<kj::String> protocol;\n    kj::Maybe<kj::String> username;\n    kj::Maybe<kj::String> password;\n    kj::Maybe<kj::String> hostname;\n    kj::Maybe<kj::String> port;\n    kj::Maybe<kj::String> pathname;\n    kj::Maybe<kj::String> search;\n    kj::Maybe<kj::String> hash;\n    kj::Maybe<kj::String> baseUrl;\n  };\n\n  struct ProcessInitOptions {\n    enum class Mode {\n      PATTERN,\n      URL,\n    };\n    Mode mode = Mode::PATTERN;\n    kj::Maybe<kj::StringPtr> protocol = kj::none;\n    kj::Maybe<kj::StringPtr> username = kj::none;\n    kj::Maybe<kj::StringPtr> password = kj::none;\n    kj::Maybe<kj::StringPtr> hostname = kj::none;\n    kj::Maybe<kj::StringPtr> port = kj::none;\n    kj::Maybe<kj::StringPtr> pathname = kj::none;\n    kj::Maybe<kj::StringPtr> search = kj::none;\n    kj::Maybe<kj::StringPtr> hash = kj::none;\n  };\n\n  // Processes the given init according to the specified mode and options.\n  // If a kj::String is returned, then processing failed and the string\n  // is the description to include in the error message (if any).\n  static Result<Init> processInit(\n      Init init, kj::Maybe<ProcessInitOptions> options = kj::none) KJ_WARN_UNUSED_RESULT;\n\n  struct CompileOptions {\n    // The base URL to use. Only used in the compile(kj::StringPtr, ...) variant.\n    kj::Maybe<kj::StringPtr> baseUrl;\n    bool ignoreCase = false;\n  };\n\n  static Result<UrlPattern> tryCompile(\n      kj::StringPtr, kj::Maybe<CompileOptions> = kj::none) KJ_WARN_UNUSED_RESULT;\n  static Result<UrlPattern> tryCompile(\n      Init init, kj::Maybe<CompileOptions> = kj::none) KJ_WARN_UNUSED_RESULT;\n\n  UrlPattern(UrlPattern&&) = default;\n  UrlPattern& operator=(UrlPattern&&) = default;\n  KJ_DISALLOW_COPY(UrlPattern);\n\n  inline const Component& getProtocol() const KJ_LIFETIMEBOUND {\n    return protocol;\n  }\n  inline const Component& getUsername() const KJ_LIFETIMEBOUND {\n    return username;\n  }\n  inline const Component& getPassword() const KJ_LIFETIMEBOUND {\n    return password;\n  }\n  inline const Component& getHostname() const KJ_LIFETIMEBOUND {\n    return hostname;\n  }\n  inline const Component& getPort() const KJ_LIFETIMEBOUND {\n    return port;\n  }\n  inline const Component& getPathname() const KJ_LIFETIMEBOUND {\n    return pathname;\n  }\n  inline const Component& getSearch() const KJ_LIFETIMEBOUND {\n    return search;\n  }\n  inline const Component& getHash() const KJ_LIFETIMEBOUND {\n    return hash;\n  }\n\n  // If ignoreCase is true, the JavaScript regular expression created for each pattern\n  // must use the `vi` flag. Otherwise, they must use the `v` flag.\n  inline bool getIgnoreCase() const {\n    return ignoreCase;\n  }\n\n  JSG_MEMORY_INFO(UrlPattern) {\n    tracker.trackField(\"protocol\", protocol);\n    tracker.trackField(\"username\", username);\n    tracker.trackField(\"password\", password);\n    tracker.trackField(\"hostname\", hostname);\n    tracker.trackField(\"port\", port);\n    tracker.trackField(\"pathname\", pathname);\n    tracker.trackField(\"search\", search);\n    tracker.trackField(\"hash\", hash);\n  }\n\n private:\n  UrlPattern(kj::Array<Component> components, bool ignoreCase);\n\n  Component protocol;\n  Component username;\n  Component password;\n  Component hostname;\n  Component port;\n  Component pathname;\n  Component search;\n  Component hash;\n  bool ignoreCase;\n\n  static Result<UrlPattern> tryCompileInit(UrlPattern::Init init, const CompileOptions& options);\n};\n}  // namespace workerd::jsg\n\n// Append _url to a string literal to create a parsed URL. An assert will be triggered\n// if the value cannot be parsed successfully.\nconst workerd::jsg::Url operator\"\"_url(const char* str, size_t size) KJ_WARN_UNUSED_RESULT;\n"
  },
  {
    "path": "src/workerd/jsg/util-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"dom-exception.h\"\n#include \"jsg-test.h\"\n#include \"jsg.h\"\n\nnamespace workerd::jsg::test {\nnamespace {\n\nV8System v8System;\nclass ContextGlobalObject: public Object, public ContextGlobal {};\n\nstruct FreezeContext: public ContextGlobalObject {\n  void recursivelyFreeze(jsg::Lock& js, v8::Local<v8::Value> value) {\n    jsg::recursivelyFreeze(js.v8Isolate->GetCurrentContext(), value);\n  }\n  JSG_RESOURCE_TYPE(FreezeContext) {\n    JSG_METHOD(recursivelyFreeze);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(FreezeIsolate, FreezeContext);\n\nKJ_TEST(\"recursive freezing\") {\n  Evaluator<FreezeContext, FreezeIsolate> e(v8System);\n  e.expectEval(\"let obj = { foo: [ { bar: 1 } ] };\\n\"\n               \"recursivelyFreeze(obj);\\n\"\n               // We rely on non-strict mode here to silently discard our mutations.\n               \"obj.foo[0].bar = 2;\\n\"\n               \"obj.foo[0].baz = 3;\\n\"\n               \"obj.foo[1] = { qux: 4 };\\n\"\n               \"obj.bar = {};\\n\"\n               \"JSON.stringify(obj);\\n\",\n      \"string\", \"{\\\"foo\\\":[{\\\"bar\\\":1}]}\");\n}\n\n// ========================================================================================\n\nstruct CloneContext: public ContextGlobalObject {\n  v8::Local<v8::Value> deepClone(jsg::Lock& js, v8::Local<v8::Value> value) {\n    return jsg::deepClone(js.v8Isolate->GetCurrentContext(), value);\n  }\n  JSG_RESOURCE_TYPE(CloneContext) {\n    JSG_METHOD(deepClone);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(CloneIsolate, CloneContext);\n\nKJ_TEST(\"deep clone\") {\n  Evaluator<CloneContext, CloneIsolate> e(v8System);\n  e.expectEval(\n      \"let obj = { foo: [ { bar: 1 } ] };\\n\"\n      \"let clone = deepClone(obj);\\n\"\n      \"clone.foo[0].bar = 2;\\n\"\n      \"if (clone === obj) throw new Error('clone === obj');\\n\"\n      \"if (clone.foo[0] === obj.foo[0]) throw new Error('clone.foo[0] === obj.foo[0]');\\n\"\n      \"if (clone.foo[0].bar === obj.foo[0].bar) throw new Error('clone.foo[0].bar === obj.foo[0].bar');\\n\"\n      \"JSON.stringify(clone);\\n\",\n      \"string\", \"{\\\"foo\\\":[{\\\"bar\\\":2}]}\");\n}\n\n// ========================================================================================\n\nstruct TypeErrorContext: public ContextGlobalObject {\n  auto returnFunctionTakingBox(double value) {\n    return [value](Lock&, Ref<NumberBox> value2) mutable { return value + value2->value; };\n  }\n\n  JSG_RESOURCE_TYPE(TypeErrorContext) {\n    JSG_NESTED_TYPE(NumberBox);\n    JSG_METHOD(returnFunctionTakingBox);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(TypeErrorIsolate, TypeErrorContext, NumberBox);\n\nKJ_TEST(\"throw TypeError\") {\n  Evaluator<TypeErrorContext, TypeErrorIsolate> e(v8System);\n  e.expectEval(\"new NumberBox(123).addBox(321)\", \"throws\",\n      \"TypeError: Failed to execute 'addBox' on 'NumberBox': parameter 1 is not of \"\n      \"type 'NumberBox'.\");\n  e.expectEval(\"new NumberBox(123).boxed = 321\", \"throws\",\n      \"TypeError: Failed to set the 'boxed' property on 'NumberBox': the provided \"\n      \"value is not of type 'NumberBox'.\");\n  e.expectEval(\"NumberBox(123)\", \"throws\",\n      \"TypeError: Failed to construct 'NumberBox': Please use the 'new' operator, \"\n      \"this object constructor cannot be called as a function.\");\n  e.expectEval(\"returnFunctionTakingBox(123)(321)\", \"throws\",\n      \"TypeError: Failed to execute function: parameter 1 is not of type 'NumberBox'.\");\n}\n\n// ========================================================================================\n\nstruct ThrowContext: public ContextGlobalObject {\n  auto returnFunctionThatThrows(double value) {\n    return [](Lock&, double) -> double { KJ_FAIL_ASSERT(\"thrown from returnFunctionThatThrows\"); };\n  }\n  void throwException() {\n    KJ_FAIL_REQUIRE(\"thrown from throwException\");\n  }\n  double getThrowing() {\n    KJ_FAIL_REQUIRE(\"thrown from getThrowing\");\n  }\n  void setThrowing(double) {\n    KJ_FAIL_REQUIRE(\"thrown from setThrowing\");\n  }\n  JSG_RESOURCE_TYPE(ThrowContext) {\n    JSG_METHOD(returnFunctionThatThrows);\n    JSG_METHOD(throwException);\n    JSG_INSTANCE_PROPERTY(throwing, getThrowing, setThrowing);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(ThrowIsolate, ThrowContext);\n\nKJ_TEST(\"throw internal error\") {\n  setPredictableModeForTest();\n\n  Evaluator<ThrowContext, ThrowIsolate> e(v8System);\n  {\n    KJ_EXPECT_LOG(ERROR, \"thrown from throwException\");\n    e.expectEval(\"throwException()\", \"throws\",\n        \"Error: internal error; reference = 0123456789abcdefghijklmn\");\n  }\n  {\n    // We also expect the logged internal error to contain the error id.\n    KJ_EXPECT_LOG(ERROR, \"wdErrId = 0123456789abcdefghijklmn\");\n    e.expectEval(\"throwException()\", \"throws\",\n        \"Error: internal error; reference = 0123456789abcdefghijklmn\");\n  }\n\n  {\n    KJ_EXPECT_LOG(ERROR, \"thrown from getThrowing\");\n    e.expectEval(\n        \"throwing\", \"throws\", \"Error: internal error; reference = 0123456789abcdefghijklmn\");\n  }\n\n  {\n    KJ_EXPECT_LOG(ERROR, \"thrown from setThrowing\");\n    e.expectEval(\n        \"throwing = 123\", \"throws\", \"Error: internal error; reference = 0123456789abcdefghijklmn\");\n  }\n\n  {\n    KJ_EXPECT_LOG(ERROR, \"thrown from returnFunctionThatThrows\");\n    e.expectEval(\"returnFunctionThatThrows(123)(321)\", \"throws\",\n        \"Error: internal error; reference = 0123456789abcdefghijklmn\");\n  }\n}\n\n// ========================================================================================\n\nstruct TunneledContext: public ContextGlobalObject {\n  void throwTunneledTypeError() {\n    JSG_FAIL_REQUIRE(TypeError, \"thrown from throwTunneledTypeError\");\n  }\n  void throwTunneledTypeErrorWithoutMessage() {\n    KJ_FAIL_REQUIRE(\"jsg.TypeError <unseen message>\");\n  }\n  void throwTunneledTypeErrorLateColon() {\n    KJ_FAIL_REQUIRE(\"jsg.TypeError would be an appropriate error to throw here, but that would \"\n                    \"cause a problem: We actually don't want this top secret message to be visible \"\n                    \"to developers!\");\n  }\n  void throwTunneledTypeErrorWithExpectation() {\n    auto s = kj::str(\"Hello, world!\");\n    JSG_REQUIRE(s.startsWith(\";\"), TypeError, \"thrown from throwTunneledTypeErrorWithExpectation\");\n  }\n  void throwTunneledOperationError() {\n    JSG_FAIL_REQUIRE(DOMOperationError, \"thrown from throwTunneledOperationError\");\n  }\n  void throwTunneledOperationErrorWithoutMessage() {\n    KJ_FAIL_REQUIRE(\"jsg.DOMException(OperationError) <unseen message>\");\n  }\n  void throwTunneledOperationErrorLateColon() {\n    KJ_FAIL_REQUIRE(\"jsg.DOMException(OperationError) would be an appropriate error to throw here \"\n                    \"here, but that would cause a problem: We actually don't want this top secret \"\n                    \"message to be visible to developers!\");\n  }\n  void throwTunneledOperationErrorWithExpectation() {\n    auto s = kj::str(\"Hello, world!\");\n    JSG_REQUIRE(s.startsWith(\";\"), DOMOperationError,\n        \"thrown from \"\n        \"throwTunneledOperationErrorWithExpectation\");\n  }\n  void throwTunneledInternalOperationError() {\n    JSG_FAIL_REQUIRE(InternalDOMOperationError, \"thrown from throwTunneledInternalOperationError\");\n  }\n  void throwRemoteCpuExceededError() {\n    kj::throwFatalException(KJ_EXCEPTION(OVERLOADED,\n        \"remote exception: remote exception: worker_do_not_log; script exceeded time limit\",\n        \"script exceeded time limit\"));\n  }\n  void throwBadTunneledError() {\n    KJ_FAIL_REQUIRE(\" jsg.TypeError\");\n  }\n  void throwBadTunneledErrorWithExpectation() {\n    auto s = kj::str(\"Hello, world!\");\n    KJ_REQUIRE(s.startsWith(\";\"), \" jsg.TypeError\");\n  }\n  void throwRetunneledTypeError(jsg::Lock& js) {\n    // Not sure what to call this ...\n    v8::TryCatch tryCatch(js.v8Isolate);\n    try {\n      jsg::throwTypeError(js.v8Isolate, \"Dummy error message.\");\n      KJ_UNREACHABLE;\n    } catch (JsExceptionThrown&) {\n      throwTunneledException(js.v8Isolate, tryCatch.Exception());\n    }\n  }\n  void throwHugeExternalString(jsg::Lock& js) {\n    kj::ArrayPtr<const char> fakeBuf(reinterpret_cast<const char*>(1), v8::String::kMaxLength + 1);\n    jsg::newExternalOneByteString(js, fakeBuf);\n  }\n  void throwTunneledMacroTypeError() {\n    JSG_FAIL_REQUIRE(TypeError, \"thrown \", \"from \", \"throwTunneledMacroTypeError\");\n  }\n  void throwTunneledMacroTypeErrorWithExpectation() {\n    auto s = kj::str(\"Hello, world!\");\n    JSG_REQUIRE(\n        s.startsWith(\";\"), TypeError, \"thrown from throwTunneledMacroTypeErrorWithExpectation\");\n  }\n  void throwTunneledMacroOperationError() {\n    JSG_FAIL_REQUIRE(DOMOperationError, \"thrown \", \"from throwTunneledMacroOperationError\");\n  }\n  void throwTunneledMacroOperationErrorWithExpectation() {\n    auto s = kj::str(\"Hello, world!\");\n    JSG_REQUIRE(s.startsWith(\";\"), DOMOperationError, \"thrown from \",\n        kj::str(\"throwTunneledMacroOperationErrorWithExpectation\"));\n  }\n  // Test that the error types mapped to WasmCompileError are handled correctly\n  void throwTunneledCompileError() {\n    KJ_FAIL_REQUIRE(\"jsg.CompileError: thrown from throwTunneledCompileError\");\n  }\n  void throwTunneledLinkError() {\n    KJ_FAIL_REQUIRE(\"jsg.LinkError: thrown from throwTunneledLinkError\");\n  }\n  void throwTunneledRuntimeError() {\n    KJ_FAIL_REQUIRE(\"jsg.RuntimeError: thrown from throwTunneledRuntimeError\");\n  }\n  // Test that only valid DOM exceptions are processed\n  void throwTunneledDOMException() {\n    KJ_FAIL_REQUIRE(\"jsg.DOMException(Some error): thrown from throwTunneledDOMException\");\n  }\n  void throwTunneledInvalidDOMException() {\n    KJ_FAIL_REQUIRE(\"jsg.DOMException: thrown from throwTunneledInvalidDOMException\");\n  }\n  void throwTunneledGarbledDOMException() {\n    KJ_FAIL_REQUIRE(\"jsg.DOMException(: thrown from throwTunneledGarbledDOMException\");\n  }\n\n  JSG_RESOURCE_TYPE(TunneledContext) {\n    JSG_NESTED_TYPE(DOMException);\n    JSG_METHOD(throwTunneledTypeError);\n    JSG_METHOD(throwTunneledTypeErrorWithoutMessage);\n    JSG_METHOD(throwTunneledTypeErrorLateColon);\n    JSG_METHOD(throwTunneledTypeErrorWithExpectation);\n    JSG_METHOD(throwTunneledOperationError);\n    JSG_METHOD(throwTunneledOperationErrorWithoutMessage);\n    JSG_METHOD(throwTunneledOperationErrorLateColon);\n    JSG_METHOD(throwTunneledOperationErrorWithExpectation);\n    JSG_METHOD(throwTunneledInternalOperationError);\n    JSG_METHOD(throwRemoteCpuExceededError);\n    JSG_METHOD(throwBadTunneledError);\n    JSG_METHOD(throwBadTunneledErrorWithExpectation);\n    JSG_METHOD(throwRetunneledTypeError);\n    JSG_METHOD(throwHugeExternalString);\n    JSG_METHOD(throwTunneledMacroTypeError);\n    JSG_METHOD(throwTunneledMacroTypeErrorWithExpectation);\n    JSG_METHOD(throwTunneledMacroOperationError);\n    JSG_METHOD(throwTunneledMacroOperationErrorWithExpectation);\n    JSG_METHOD(throwTunneledCompileError);\n    JSG_METHOD(throwTunneledLinkError);\n    JSG_METHOD(throwTunneledRuntimeError);\n    JSG_METHOD(throwTunneledDOMException);\n    JSG_METHOD(throwTunneledInvalidDOMException);\n    JSG_METHOD(throwTunneledGarbledDOMException);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(TunneledIsolate, TunneledContext);\n\nKJ_TEST(\"throw tunneled exception\") {\n  setPredictableModeForTest();\n\n  Evaluator<TunneledContext, TunneledIsolate> e(v8System);\n  e.expectEval(\n      \"throwTunneledTypeError()\", \"throws\", \"TypeError: thrown from throwTunneledTypeError\");\n  e.expectEval(\"throwTunneledTypeErrorLateColon()\", \"throws\", \"TypeError\");\n  e.expectEval(\"throwTunneledTypeErrorWithExpectation()\", \"throws\",\n      \"TypeError: thrown from throwTunneledTypeErrorWithExpectation\");\n  e.expectEval(\"throwTunneledOperationError()\", \"throws\",\n      \"OperationError: thrown from throwTunneledOperationError\");\n  e.expectEval(\"throwTunneledOperationErrorWithoutMessage()\", \"throws\", \"OperationError\");\n  e.expectEval(\"throwTunneledOperationErrorLateColon()\", \"throws\", \"OperationError\");\n  e.expectEval(\"throwTunneledOperationErrorWithExpectation()\", \"throws\",\n      \"OperationError: thrown from throwTunneledOperationErrorWithExpectation\");\n  {\n    KJ_EXPECT_LOG(ERROR, \"thrown from throwTunneledInternalOperationError\");\n    e.expectEval(\"throwTunneledInternalOperationError()\", \"throws\",\n        \"OperationError: internal error; reference = 0123456789abcdefghijklmn\");\n  }\n  {\n    // We also expect the logged internal error to contain the error id.\n    KJ_EXPECT_LOG(ERROR, \"wdErrId = 0123456789abcdefghijklmn\");\n    e.expectEval(\"throwTunneledInternalOperationError()\", \"throws\",\n        \"OperationError: internal error; reference = 0123456789abcdefghijklmn\");\n  }\n  {\n    KJ_EXPECT_LOG(ERROR, \" jsg.TypeError\");\n    e.expectEval(\"throwBadTunneledError()\", \"throws\",\n        \"Error: internal error; reference = 0123456789abcdefghijklmn\");\n  }\n  {\n    KJ_EXPECT_LOG(ERROR, \"expected s.startsWith(\\\";\\\");  jsg.TypeError\");\n    e.expectEval(\"throwBadTunneledErrorWithExpectation()\", \"throws\",\n        \"Error: internal error; reference = 0123456789abcdefghijklmn\");\n  }\n  e.expectEval(\"throwTunneledMacroTypeError()\", \"throws\",\n      \"TypeError: thrown from throwTunneledMacroTypeError\");\n  e.expectEval(\"throwTunneledMacroTypeErrorWithExpectation()\", \"throws\",\n      \"TypeError: thrown from throwTunneledMacroTypeErrorWithExpectation\");\n  e.expectEval(\"throwTunneledMacroOperationError()\", \"throws\",\n      \"OperationError: thrown from throwTunneledMacroOperationError\");\n  e.expectEval(\"throwTunneledMacroOperationErrorWithExpectation()\", \"throws\",\n      \"OperationError: thrown from throwTunneledMacroOperationErrorWithExpectation\");\n  e.expectEval(\"throwTunneledCompileError()\", \"throws\",\n      \"CompileError: thrown from throwTunneledCompileError\");\n  e.expectEval(\n      \"throwTunneledLinkError()\", \"throws\", \"LinkError: thrown from throwTunneledLinkError\");\n  e.expectEval(\"throwTunneledRuntimeError()\", \"throws\",\n      \"RuntimeError: thrown from throwTunneledRuntimeError\");\n  e.expectEval(\n      \"throwTunneledDOMException()\", \"throws\", \"Some error: thrown from throwTunneledDOMException\");\n  {\n    KJ_EXPECT_LOG(ERROR, \" thrown from throwTunneledInvalidDOMException\");\n    e.expectEval(\"throwTunneledInvalidDOMException()\", \"throws\",\n        \"Error: internal error; reference = 0123456789abcdefghijklmn\");\n  }\n  {\n    KJ_EXPECT_LOG(ERROR, \" thrown from throwTunneledGarbledDOMException\");\n    e.expectEval(\"throwTunneledGarbledDOMException()\", \"throws\",\n        \"Error: internal error; reference = 0123456789abcdefghijklmn\");\n  }\n}\n\nKJ_TEST(\"runTunnelingExceptions\") {\n  Evaluator<TunneledContext, TunneledIsolate> e(v8System);\n  e.expectEval(\"throwRetunneledTypeError()\", \"throws\", \"TypeError: Dummy error message.\");\n  e.expectEval(\"throwHugeExternalString()\", \"throws\", \"Error: String allocation failed\");\n}\n\nKJ_TEST(\"isTunneledException\") {\n  TunneledContext context;\n  try {\n    context.throwTunneledTypeError();\n    KJ_UNREACHABLE;\n  } catch (kj::Exception& e) {\n    KJ_EXPECT(isTunneledException(e.getDescription()), e.getDescription());\n  }\n  try {\n    context.throwTunneledTypeErrorWithoutMessage();\n    KJ_UNREACHABLE;\n  } catch (kj::Exception& e) {\n    KJ_EXPECT(isTunneledException(e.getDescription()), e.getDescription());\n  }\n  try {\n    context.throwTunneledTypeErrorLateColon();\n    KJ_UNREACHABLE;\n  } catch (kj::Exception& e) {\n    KJ_EXPECT(isTunneledException(e.getDescription()), e.getDescription());\n  }\n  try {\n    context.throwTunneledTypeErrorWithExpectation();\n    KJ_UNREACHABLE;\n  } catch (kj::Exception& e) {\n    KJ_EXPECT(isTunneledException(e.getDescription()), e.getDescription());\n  }\n  try {\n    context.throwTunneledOperationError();\n    KJ_UNREACHABLE;\n  } catch (kj::Exception& e) {\n    KJ_EXPECT(isTunneledException(e.getDescription()), e.getDescription());\n  }\n  try {\n    context.throwTunneledOperationErrorLateColon();\n    KJ_UNREACHABLE;\n  } catch (kj::Exception& e) {\n    KJ_EXPECT(isTunneledException(e.getDescription()), e.getDescription());\n  }\n  try {\n    context.throwTunneledOperationErrorWithExpectation();\n    KJ_UNREACHABLE;\n  } catch (kj::Exception& e) {\n    KJ_EXPECT(isTunneledException(e.getDescription()), e.getDescription());\n  }\n\n  try {\n    context.throwBadTunneledError();\n    KJ_UNREACHABLE;\n  } catch (kj::Exception& e) {\n    KJ_EXPECT(!isTunneledException(e.getDescription()), e.getDescription());\n  }\n  try {\n    context.throwBadTunneledErrorWithExpectation();\n    KJ_UNREACHABLE;\n  } catch (kj::Exception& e) {\n    KJ_EXPECT(!isTunneledException(e.getDescription()), e.getDescription());\n  }\n\n  try {\n    context.throwRemoteCpuExceededError();\n    KJ_UNREACHABLE;\n  } catch (kj::Exception& e) {\n    KJ_EXPECT(!isTunneledException(e.getDescription()), e.getDescription());\n    KJ_EXPECT(isDoNotLogException(e.getDescription()), e.getDescription());\n  }\n\n  try {\n    JSG_FAIL_REQUIRE(InternalDOMOperationError, \"foo\");\n    KJ_UNREACHABLE;\n  } catch (kj::Exception& e) {\n    KJ_EXPECT(!isTunneledException(e.getDescription()), e.getDescription());\n  }\n}\n\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/util.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"dom-exception.h\"\n#include \"jsg.h\"  // can't include util.h directly due to weird cyclic dependency...\n#include \"ser.h\"\n#include \"setup.h\"\n\n#include <workerd/jsg/exception-metadata.capnp.h>\n#include <workerd/util/entropy.h>\n\n#include <capnp/message.h>\n#include <capnp/serialize.h>\n#include <kj/debug.h>\n\n#include <cstdlib>\n#include <set>\n\n#if !_WIN32\n#include <cxxabi.h>\n#endif\n\nnamespace workerd::jsg {\n\nbool getCaptureThrowsAsRejections(v8::Isolate* isolate) {\n  auto& jsgIsolate = *reinterpret_cast<IsolateBase*>(isolate->GetData(SET_DATA_ISOLATE_BASE));\n  return jsgIsolate.getCaptureThrowsAsRejections();\n}\n\nbool getShouldSetToStringTag(v8::Isolate* isolate) {\n  auto& jsgIsolate = *reinterpret_cast<IsolateBase*>(isolate->GetData(SET_DATA_ISOLATE_BASE));\n  return jsgIsolate.shouldSetToStringTag();\n}\n\nbool getShouldSetImmutablePrototype(v8::Isolate* isolate) {\n  auto& jsgIsolate = *reinterpret_cast<IsolateBase*>(isolate->GetData(SET_DATA_ISOLATE_BASE));\n  return jsgIsolate.shouldSetImmutablePrototype();\n}\n\nbool getSpecCompliantPropertyAttributes(v8::Isolate* isolate) {\n  auto& jsgIsolate = *reinterpret_cast<IsolateBase*>(isolate->GetData(SET_DATA_ISOLATE_BASE));\n  return jsgIsolate.shouldUseSpecCompliantPropertyAttributes();\n}\n\n#if _WIN32\nkj::String fullyQualifiedTypeName(const std::type_info& type) {\n  // type.name() returns a human-readable name on Windows:\n  // https://learn.microsoft.com/en-us/cpp/cpp/type-info-class?view=msvc-170\n  kj::StringPtr name = type.name();\n\n  // Remove struct prefix\n  if (name.startsWith(\"struct \")) {\n    name = name.slice(7);\n  }\n  // Remove class prefix\n  if (name.startsWith(\"class \")) {\n    name = name.slice(6);\n  }\n\n  kj::String result = kj::str(name);\n\n  // Replace instances of `anonymous namespace' with (anonymous namespace)\n  for (auto& c: result.asArray()) {\n    if (c == '`')\n      c = '(';\n    else if (c == '\\'')\n      c = ')';\n  }\n\n  return kj::mv(result);\n}\n#else\nkj::String fullyQualifiedTypeName(const std::type_info& type) {\n  int status;\n  char* buf = abi::__cxa_demangle(type.name(), nullptr, nullptr, &status);\n  kj::String result = kj::str(buf == nullptr ? type.name() : buf);\n  free(buf);\n\n  return kj::mv(result);\n}\n#endif\n\nkj::String typeName(const std::type_info& type) {\n  auto result = fullyQualifiedTypeName(type);\n\n  // Strip namespace, if any.\n  KJ_IF_SOME(pos, result.findLast(':')) {\n    result = kj::str(result.slice(pos + 1));\n  }\n\n  // Strip template args, if any.\n  //\n  // TODO(someday): Maybe just strip namespaces from each arg?\n  KJ_IF_SOME(pos, result.findFirst('<')) {\n    result = kj::str(result.first(pos));\n  }\n\n  return kj::mv(result);\n}\n\nnamespace {\n\n// For internal errors, we generate an ID to include when rendering user-facing \"internal error\"\n// exceptions and writing internal exception logs, to make it easier to search for logs\n// corresponding to \"internal error\" exceptions reported by users.\n//\n// We'll use an ID of 24 base-32 encoded characters, just because its relatively simple to\n// generate from random bytes.  This should give us a value with 120 bits of uniqueness, which is\n// about as good as a UUID.\n//\n// (We're not using base-64 encoding to avoid issues with case insensitive search, as well as\n// ensuring that the id is easy to select and copy via double-clicking.)\nusing InternalErrorId = kj::FixedArray<char, 24>;\n\nconstexpr char BASE32_DIGITS[] = \"0123456789abcdefghijklmnopqrstuv\";\n\nInternalErrorId makeInternalErrorId() {\n  InternalErrorId id;\n  if (isPredictableModeForTest()) {\n    // In testing mode, use content that generates a \"0123456789abcdefghijklm\" ID:\n    for (auto i: kj::indices(id)) {\n      id[i] = i;\n    }\n  } else {\n    getEntropy(kj::asBytes(id));\n  }\n  for (auto i: kj::indices(id)) {\n    id[i] = BASE32_DIGITS[static_cast<unsigned char>(id[i]) % 32];\n  }\n  return id;\n}\n\nkj::String renderInternalError(InternalErrorId& internalErrorId) {\n  return kj::str(\"internal error; reference = \", internalErrorId);\n}\n\n}  // namespace\n\nv8::Local<v8::Value> makeInternalError(v8::Isolate* isolate, kj::StringPtr internalMessage) {\n  auto wdErrId = makeInternalErrorId();\n  KJ_LOG(ERROR, internalMessage, wdErrId);\n  return v8::Exception::Error(v8Str(isolate, renderInternalError(wdErrId)));\n}\n\nnamespace {\n\nkj::StringPtr trimErrorMessage(kj::StringPtr errorString) {\n  // For strings beginning with ':' OWS, returns everything after the OWS. Otherwise returns the\n  // empty string.\n  if (errorString.startsWith(\":\")) {\n    errorString = errorString.slice(1);\n    while (errorString.startsWith(\" \")) {\n      errorString = errorString.slice(1);\n    }\n    return errorString;\n  }\n  return \"\";\n}\n\nbool setRemoteError(v8::Isolate* isolate, v8::Local<v8::Value>& exception) {\n  // If an exception was tunneled, we add a property `.remote` to the Javascript error.\n  KJ_ASSERT(exception->IsObject());\n  auto obj = exception.As<v8::Object>();\n  return jsg::check(obj->Set(\n      isolate->GetCurrentContext(), jsg::v8StrIntern(isolate, \"remote\"_kj), v8::True(isolate)));\n}\n\nbool setRetryableError(v8::Isolate* isolate, v8::Local<v8::Value>& exception) {\n  KJ_ASSERT(exception->IsObject());\n  auto obj = exception.As<v8::Object>();\n  return jsg::check(obj->Set(\n      isolate->GetCurrentContext(), jsg::v8StrIntern(isolate, \"retryable\"_kj), v8::True(isolate)));\n}\n\nbool setOverloadedError(v8::Isolate* isolate, v8::Local<v8::Value>& exception) {\n  KJ_ASSERT(exception->IsObject());\n  auto obj = exception.As<v8::Object>();\n  return jsg::check(obj->Set(\n      isolate->GetCurrentContext(), jsg::v8StrIntern(isolate, \"overloaded\"_kj), v8::True(isolate)));\n}\n\nbool setDurableObjectResetError(v8::Isolate* isolate, v8::Local<v8::Value>& exception) {\n  KJ_ASSERT(exception->IsObject());\n  auto obj = exception.As<v8::Object>();\n  return jsg::check(obj->Set(isolate->GetCurrentContext(),\n      jsg::v8StrIntern(isolate, \"durableObjectReset\"_kj), v8::True(isolate)));\n}\nstruct DecodedException {\n  v8::Local<v8::Value> handle;\n  bool isInternal;\n  bool isFromRemote;\n  bool isDurableObjectReset;\n  // TODO(cleanup): Maybe<> is redundant with isInternal flag field?\n  kj::Maybe<InternalErrorId> internalErrorId;\n  bool isDisconnection;\n  bool isDoNotLogException;\n};\n\nDecodedException decodeTunneledException(\n    v8::Isolate* isolate, const kj::Exception& exception, const ExceptionToJsOptions& options) {\n\n  // We currently support tunneling the following error types:\n  //\n  // - Error:        While the Web IDL spec claims this is reserved for use by program authors, this\n  //                 is broadly useful as a general-purpose error type.\n  // - RangeError:   Commonly thrown by web API implementations.\n  // - TypeError:    Commonly thrown by web API implementations.\n  // - SyntaxError:  Especially from JSON parsing.\n  // - ReferenceError: Not thrown by our APIs, but could be tunneled from user code.\n  // - DOMException: Commonly thrown by web API implementations.\n  //\n  // https://heycam.github.io/webidl/#idl-exceptions\n  //\n  // TODO(someday): Support arbitrary user-defined error types, not just Error?\n  auto tunneledInfo = tunneledErrorType(exception.getDescription());\n  DecodedException result;\n  result.isDisconnection = false;\n  result.isDoNotLogException = tunneledInfo.isDoNotLogException;\n\n  auto errorType = tunneledInfo.message;\n  auto appMessage = [&](kj::StringPtr errorString) -> kj::String {\n    if (tunneledInfo.isInternal) {\n      result.internalErrorId = makeInternalErrorId();\n      return renderInternalError(KJ_ASSERT_NONNULL(result.internalErrorId));\n    } else {\n      return kj::str(trimErrorMessage(errorString));\n    }\n  };\n  result.isInternal = tunneledInfo.isInternal;\n  result.isFromRemote = tunneledInfo.isFromRemote;\n  result.isDurableObjectReset = tunneledInfo.isDurableObjectReset;\n\n  auto addAdditionalInfo = [isolate, &result, &exception]() {\n    if (!result.handle->IsObject()) return;\n    // Note that if the error was deserialized from the TUNNELED_EXCEPTION_DETAIL_ID detail,\n    // these operations may overwrite properties that were already set on the serialized\n    // error object. That is fine, we want the metadata captured in the kj::Exception\n    // description to take precedence.\n    // TODO(someday): Maybe consider making this configurable when a deserialized\n    // error is used?\n    if (result.isFromRemote) {\n      setRemoteError(isolate, result.handle);\n    }\n\n    if (exception.getType() == kj::Exception::Type::DISCONNECTED) {\n      setRetryableError(isolate, result.handle);\n    } else if (exception.getType() == kj::Exception::Type::OVERLOADED) {\n      setOverloadedError(isolate, result.handle);\n    }\n\n    if (result.isDurableObjectReset) {\n      setDurableObjectResetError(isolate, result.handle);\n    }\n  };\n\n  if (tunneledInfo.isJsgError) {\n    // DOMExceptions require a parenthesized error name argument, like DOMException(SyntaxError).\n    // TODO(someday): We always handle DOMException specifically here rather than decoding from\n    // the serialized detail because using the detail breaks some tests that expect a specific\n    // error details. We'll need to investigate further to see if we can make this more consistent.\n    if (errorType.startsWith(\"DOMException(\")) {\n      errorType = errorType.slice(strlen(\"DOMException(\"));\n      // Check for closing brace\n      KJ_IF_SOME(closeParen, errorType.findFirst(')')) {\n        auto& js = Lock::from(isolate);\n        auto errorName = kj::str(errorType.first(closeParen));\n        auto message = appMessage(errorType.slice(1 + closeParen));\n        auto exception = js.domException(kj::mv(errorName), kj::mv(message));\n        result.handle = KJ_ASSERT_NONNULL(exception.tryGetHandle(js));\n        addAdditionalInfo();\n        return result;\n      }\n    }\n\n    auto& isolateBase = IsolateBase::from(isolate);\n    if ((options.trusted || isolateBase.getUsingEnhancedErrorSerialization()) &&\n        !options.ignoreDetail) {\n      // If the error was originally converted from a JS error, then we likely have\n      // serialized the original error object as a detail, if so, let's try to use\n      // that, otherwise, we'll fall back to constructing a new error object. If\n      // the ignoreDetail optiom is set, we skip trying to deserialize.\n      KJ_IF_SOME(serializedJsError, exception.getDetail(jsg::TUNNELED_EXCEPTION_DETAIL_ID)) {\n        kj::Maybe<jsg::JsValue> deserialized;\n        v8::TryCatch tryCatch(isolate);\n        try {\n          auto& js = Lock::from(isolate);\n          jsg::Deserializer deser(js, serializedJsError, kj::none, kj::none,\n              jsg::Deserializer::Options{\n                // By default, we do not preserve stacks in deserialized errors because\n                // of concerns sharing stack details over potentially untrusted boundaries.\n                // However, if the caller has explicitly indicated that the scope it trusted,\n                // we will preserve the stack in the deserialized error.\n                .preserveStackInErrors = options.trusted,\n              });\n          result.handle = deser.readValue(js);\n\n          // If the result came from a serialized JS detail, it might not be an object!\n          // If that's the case, and allowNonObjects is false (the default), we will ignore\n          // the deserialized data and fallback to the normal decoding below.\n          if (result.handle->IsObject() || options.allowNonObjects) {\n            addAdditionalInfo();\n            return result;\n          }\n        } catch (jsg::JsExceptionThrown&) {\n          if (!tryCatch.CanContinue()) {\n            tryCatch.ReThrow();\n            throw;\n          }\n          // Failed to deserialize, we'll ignore the error and continue with the original\n          // decoding below. For debugging purposes, when verbose logging is enabled, we\n          // will at least log the error.\n          if (!tryCatch.Exception().IsEmpty()) {\n            KJ_LOG(INFO, \"Failed to deserialize tunneled JS error detail\", tryCatch.Exception());\n          } else {\n            KJ_LOG(INFO, \"Failed to deserialize tunneled JS error detail (unknown error)\");\n          }\n        }\n      }\n    }\n\n    // It's neither a DOMException nor are we using a serialized detail, so it must be\n    // one of the standard JS error types (or we will treat it as such).\n#define HANDLE_AND_RETURN_V8_ERROR(error_name, error_type)                                         \\\n  if (errorType.startsWith(error_name)) {                                                          \\\n    auto message = appMessage(errorType.slice(strlen(error_name)));                                \\\n    result.handle = v8::Exception::error_type(v8Str(isolate, message));                            \\\n    addAdditionalInfo();                                                                           \\\n    return result;                                                                                 \\\n  }\n\n    JS_ERROR_TYPES(HANDLE_AND_RETURN_V8_ERROR)\n#undef HANDLE_AND_RETURN_V8_ERROR\n  }\n\n  // It's not a tunneled JavaScript error type that we recognize.\n  // Return an internal error.\n  result.isInternal = true;\n\n  // For disconnection errors, we ignore any tunneled error info and just return\n  // a generic \"Network connection lost\" error. One thing to keep in mind is that\n  // DOMExceptions with the AbortError name are also DISCONNECTED errors, but those\n  // are handled above as tunneled JS errors. It is important that we preserve the\n  // ordering of these checks, so keep this if block after the tunneled JS error\n  // handling above.\n  if (exception.getType() == kj::Exception::Type::DISCONNECTED) {\n    result.isDisconnection = true;\n    result.handle = v8::Exception::Error(v8StrIntern(isolate, \"Network connection lost.\"_kj));\n    if (tunneledInfo.isFromRemote) {\n      setRemoteError(isolate, result.handle);\n    }\n\n    // DISCONNECTED exceptions are considered retryable\n    setRetryableError(isolate, result.handle);\n\n    if (tunneledInfo.isDurableObjectReset) {\n      setDurableObjectResetError(isolate, result.handle);\n    }\n  } else {\n    // For everything return a generic error with an internal error id.\n    result.internalErrorId = makeInternalErrorId();\n    result.handle = v8::Exception::Error(\n        v8Str(isolate, renderInternalError(KJ_ASSERT_NONNULL(result.internalErrorId))));\n    addAdditionalInfo();\n  }\n  return result;\n}\n\n}  // namespace\n\nkj::StringPtr extractTunneledExceptionDescription(kj::StringPtr message) {\n  auto tunneledError = tunneledErrorType(message);\n  if (tunneledError.isInternal) {\n    // TODO(soon): Include an internal error ID in message, and also return the id.\n    return \"Error: internal error\";\n  } else {\n    return tunneledError.message;\n  }\n}\n\nv8::Local<v8::Value> exceptionToJs(\n    v8::Isolate* isolate, kj::Exception&& exception, ExceptionToJsOptions options) {\n  // TODO(cleanup): decodeTunneledException is currently only used here, consider\n  // inlining it back into this function.\n  auto tunneledException = decodeTunneledException(isolate, exception, options);\n\n  if (tunneledException.isInternal) {\n    // Don't log exceptions that have been explicitly marked with worker_do_not_log or are\n    // DISCONNECTED exceptions as these are unlikely to represent bugs worth tracking.\n    bool shouldLogWithInternalId =\n        !tunneledException.isDisconnection && !tunneledException.isDoNotLogException;\n    auto& observer = IsolateBase::from(isolate).getObserver();\n    observer.reportInternalException(exception,\n        {\n          .isInternal = tunneledException.isInternal,\n          .isFromRemote = tunneledException.isFromRemote,\n          .isDurableObjectReset = tunneledException.isDurableObjectReset,\n          .internalErrorId = shouldLogWithInternalId ? tunneledException.internalErrorId : kj::none,\n        });\n    if (shouldLogWithInternalId) {\n      // LOG_EXCEPTION(\"jsgInternalError\", ...), but with internal error ID:\n      auto& e = exception;\n      constexpr auto sentryErrorContext = \"jsgInternalError\";\n      auto& wdErrId = KJ_ASSERT_NONNULL(tunneledException.internalErrorId);\n      KJ_LOG(ERROR, e, sentryErrorContext, wdErrId);\n    } else {\n      KJ_LOG(INFO, exception);  // Run with --verbose to see exception logs.\n    }\n  }\n\n  return tunneledException.handle;\n}\n\nValue Lock::exceptionToJs(kj::Exception&& exception, ExceptionToJsOptions options) {\n  return withinHandleScope(\n      [&] { return Value(v8Isolate, jsg::exceptionToJs(v8Isolate, kj::mv(exception), options)); });\n}\n\nJsRef<JsValue> Lock::exceptionToJsValue(kj::Exception&& exception, ExceptionToJsOptions options) {\n  return withinHandleScope([&] {\n    JsValue val = JsValue(jsg::exceptionToJs(v8Isolate, kj::mv(exception), options));\n    return val.addRef(*this);\n  });\n}\n\nvoid Lock::throwException(Value&& exception) {\n  withinHandleScope([&] { v8Isolate->ThrowException(exception.getHandle(*this)); });\n  throw JsExceptionThrown();\n}\n\nvoid Lock::throwException(const JsValue& exception) {\n  withinHandleScope([&] { v8Isolate->ThrowException(exception); });\n  throw JsExceptionThrown();\n}\n\nvoid throwInternalError(v8::Isolate* isolate, kj::StringPtr internalMessage) {\n  isolate->ThrowException(makeInternalError(isolate, internalMessage));\n}\n\nvoid throwInternalError(\n    v8::Isolate* isolate, kj::Exception&& exception, ExceptionToJsOptions options) {\n  KJ_IF_SOME(renderingError, kj::runCatchingExceptions([&]() {\n    isolate->ThrowException(exceptionToJs(isolate, kj::mv(exception), options));\n  })) {\n    KJ_LOG(ERROR, \"error rendering exception\", renderingError);\n    KJ_LOG(ERROR, exception);\n    throwInternalError(isolate, \"error rendering exception\");\n  }\n}\n\nvoid addExceptionDetail(Lock& js, kj::Exception& exception, v8::Local<v8::Value> handle) {\n  v8::TryCatch tryCatch(js.v8Isolate);\n  try {\n    Serializer ser(js,\n        {// Make sure we don't break compatibility if V8 introduces a new version. This value can\n          // be bumped to match the new version once all of production is updated to understand it.\n          .version = 15});\n    ser.write(js, JsValue(handle));\n    exception.setDetail(TUNNELED_EXCEPTION_DETAIL_ID, ser.release().data);\n  } catch (JsExceptionThrown&) {\n    // Either:\n    // a. The exception is not serializable, and we caught the exception. We will just ignore it\n    //    and proceed without annotating.\n    // b. The isolate's execution is being terminated, and so tryCatch.CanContinue() is false. In\n    //    this case we cannot serialize the exception, but again we'll just move on without the\n    //    annotation.\n  }\n}\n\nvoid addJsExceptionMetadata(Lock& js, kj::Exception& exception, v8::Local<v8::Value> handle) {\n  // Extract JavaScript error type and stack trace\n  if (!handle->IsObject()) {\n    return;  // Not an error object, nothing to extract\n  }\n\n  auto errorObj = jsg::JsObject(handle.As<v8::Object>());\n\n  // Build Cap'n Proto message\n  capnp::MallocMessageBuilder message;\n  auto metadata = message.initRoot<JsExceptionMetadata>();\n\n  // Limit for user-controlled fields (4KB)\n  constexpr size_t MAX_FIELD_SIZE = 4096;\n\n  // Extract error name (e.g., \"Error\", \"TypeError\", \"RangeError\")\n  auto nameProp = errorObj.get(js, \"name\"_kj);\n  if (nameProp.isString()) {\n    auto errorType = nameProp.toString(js);\n    // Truncate to 4KB if needed\n    if (errorType.size() > MAX_FIELD_SIZE) {\n      errorType = kj::str(errorType.slice(0, MAX_FIELD_SIZE));\n    }\n    metadata.setErrorType(errorType);\n  }\n\n  // Extract stack trace string\n  auto stackProp = errorObj.get(js, \"stack\"_kj);\n  if (stackProp.isString()) {\n    auto stackTrace = stackProp.toString(js);\n    // Truncate to 4KB if needed\n    if (stackTrace.size() > MAX_FIELD_SIZE) {\n      stackTrace = kj::str(stackTrace.slice(0, MAX_FIELD_SIZE));\n    }\n    metadata.setStackTrace(stackTrace);\n  }\n\n  // Serialize to bytes using Cap'n Proto\n  auto words = capnp::messageToFlatArray(message);\n  exception.setDetail(JS_EXCEPTION_METADATA_DETAIL_ID, kj::heapArray(words.asBytes()));\n}\n\nstatic kj::String typeErrorMessage(TypeErrorContext c, const char* expectedType) {\n  kj::String type;\n\n  KJ_IF_SOME(t, c.type) {\n    type = typeName(t);\n  }\n\n  switch (c.kind) {\n    case TypeErrorContext::METHOD_ARGUMENT:\n      return kj::str(\"Failed to execute '\", c.memberName, \"' on '\", type, \"': parameter \",\n          c.argumentIndex + 1, \" is not of type '\", expectedType, \"'.\");\n    case TypeErrorContext::CONSTRUCTOR_ARGUMENT:\n      return kj::str(\"Failed to construct '\", type, \"': constructor parameter \",\n          c.argumentIndex + 1, \" is not of type '\", expectedType, \"'.\");\n    case TypeErrorContext::SETTER_ARGUMENT:\n      return kj::str(\"Failed to set the '\", c.memberName, \"' property on '\", type,\n          \"': the provided value is not of type '\", expectedType, \"'.\");\n    case TypeErrorContext::STRUCT_FIELD:\n      return kj::str(\"Incorrect type for the '\", c.memberName, \"' field on '\", type,\n          \"': the provided value is not of type '\", expectedType, \"'.\");\n    case TypeErrorContext::ARRAY_ELEMENT:\n      return kj::str(\"Incorrect type for array element \", c.argumentIndex,\n          \": the provided value is not of type '\", expectedType, \"'.\");\n    case TypeErrorContext::CALLBACK_ARGUMENT:\n      return kj::str(\"Failed to execute function: parameter \", c.argumentIndex + 1,\n          \" is not of type '\", expectedType, \"'.\");\n    case TypeErrorContext::CALLBACK_RETURN:\n      return kj::str(\"Callback returned incorrect type; expected '\", expectedType, \"'\");\n    case TypeErrorContext::DICT_KEY:\n      return kj::str(\"Incorrect type for map entry '\", c.memberName,\n          \"': the provided key is not of type '\", expectedType, \"'.\");\n    case TypeErrorContext::DICT_FIELD:\n      return kj::str(\"Incorrect type for map entry '\", c.memberName,\n          \"': the provided value is not of type '\", expectedType, \"'.\");\n    case TypeErrorContext::PROMISE_RESOLUTION:\n      return kj::str(\n          \"Incorrect type for Promise: the Promise did not resolve to '\", expectedType, \"'.\");\n    case TypeErrorContext::OTHER:\n      return kj::str(\"Incorrect type: the provided value is not of type '\", expectedType, \"'.\");\n  };\n\n  KJ_UNREACHABLE;\n}\n\nstatic kj::String unimplementedErrorMessage(TypeErrorContext c) {\n  kj::String type;\n\n  KJ_IF_SOME(t, c.type) {\n    type = typeName(t);\n  }\n\n  switch (c.kind) {\n    case TypeErrorContext::METHOD_ARGUMENT:\n      return kj::str(\"Failed to execute '\", c.memberName, \"' on '\", type, \"': parameter \",\n          c.argumentIndex + 1, \" is not implemented.\");\n    case TypeErrorContext::CONSTRUCTOR_ARGUMENT:\n      return kj::str(\"Failed to construct '\", type, \"': constructor parameter \",\n          c.argumentIndex + 1, \" is not implemented.\");\n    case TypeErrorContext::SETTER_ARGUMENT:\n      return kj::str(\"Failed to set the '\", c.memberName, \"' property on '\", type,\n          \"': the ability to set this property is not implemented.\");\n    case TypeErrorContext::STRUCT_FIELD:\n      return kj::str(\"The '\", c.memberName, \"' field on '\", type, \"' is not implemented.\");\n    case TypeErrorContext::ARRAY_ELEMENT:\n      KJ_UNREACHABLE;\n    case TypeErrorContext::CALLBACK_ARGUMENT:\n      return kj::str(\n          \"Failed to execute function: parameter \", c.argumentIndex + 1, \" is not implemented.\");\n    case TypeErrorContext::CALLBACK_RETURN:\n      KJ_UNREACHABLE;\n    case TypeErrorContext::DICT_KEY:\n      KJ_UNREACHABLE;\n    case TypeErrorContext::DICT_FIELD:\n      KJ_UNREACHABLE;\n    case TypeErrorContext::PROMISE_RESOLUTION:\n      KJ_UNREACHABLE;\n    case TypeErrorContext::OTHER:\n      KJ_UNREACHABLE;\n  };\n\n  KJ_UNREACHABLE;\n}\n\nvoid throwTypeError(v8::Isolate* isolate, kj::StringPtr message) {\n  isolate->ThrowException(v8::Exception::TypeError(v8Str(isolate, message)));\n  throw JsExceptionThrown();\n}\n\nvoid throwTypeError(v8::Isolate* isolate, TypeErrorContext errorContext, kj::String expectedType) {\n  kj::String message = typeErrorMessage(errorContext, expectedType.cStr());\n  throwTypeError(isolate, message);\n}\n\nvoid throwTypeError(v8::Isolate* isolate, TypeErrorContext errorContext, const char* expectedType) {\n  kj::String message = typeErrorMessage(errorContext, expectedType);\n  throwTypeError(isolate, message);\n}\n\nvoid throwTypeError(\n    v8::Isolate* isolate, TypeErrorContext errorContext, const std::type_info& expectedType) {\n  if (expectedType == typeid(Unimplemented)) {\n    isolate->ThrowError(v8StrIntern(isolate, unimplementedErrorMessage(errorContext)));\n    throw JsExceptionThrown();\n  } else {\n    throwTypeError(isolate, errorContext, typeName(expectedType).cStr());\n  }\n}\n\nstatic constexpr auto kIllegalConstructorMessage = \"Illegal constructor\";\n\nvoid throwIllegalConstructor(const v8::FunctionCallbackInfo<v8::Value>& args) {\n  auto isolate = args.GetIsolate();\n  isolate->ThrowException(\n      v8::Exception::TypeError(v8StrIntern(isolate, kIllegalConstructorMessage)));\n}\n\nvoid throwTunneledException(v8::Isolate* isolate, v8::Local<v8::Value> exception) {\n  kj::throwFatalException(createTunneledException(isolate, exception));\n}\n\nkj::Exception createTunneledException(v8::Isolate* isolate, v8::Local<v8::Value> exception) {\n  auto& jsgIsolate = *reinterpret_cast<IsolateBase*>(isolate->GetData(SET_DATA_ISOLATE_BASE));\n  auto& js = Lock::from(isolate);\n  return jsgIsolate.unwrapException(js, isolate->GetCurrentContext(), exception);\n}\n\nkj::Exception Lock::exceptionToKj(Value&& exception) {\n  return withinHandleScope(\n      [&] { return createTunneledException(v8Isolate, exception.getHandle(*this)); });\n}\n\nkj::Exception Lock::exceptionToKj(const JsValue& exception) {\n  return withinHandleScope([&] { return createTunneledException(v8Isolate, exception); });\n}\n\nstatic kj::byte DUMMY = 0;\nstatic kj::Array<kj::byte> getEmptyArray() {\n  // An older version of asBytes(), when given an empty ArrayBuffer, would often return an array\n  // with zero size but non-empty start address. Meanwhile, it turns out that some code,\n  // particularly in BoringSSL, does not like receiving a null pointer even when the length is\n  // zero -- it will spuriously produce an error. We could carefully find all the places where\n  // this is an issue and adjust the specific calls to avoid passing null pointers, but it is\n  // easier to change `asBytes()` so that it never produces a null start address in the first\n  // place.\n  return kj::Array<kj::byte>(&DUMMY, 0, kj::NullArrayDisposer::instance);\n}\n\nkj::Array<kj::byte> asBytes(v8::Local<v8::ArrayBuffer> arrayBuffer) {\n  auto backing = arrayBuffer->GetBackingStore();\n  kj::ArrayPtr bytes(static_cast<kj::byte*>(backing->Data()), backing->ByteLength());\n  if (bytes == nullptr) {\n    return getEmptyArray();\n  } else {\n    return bytes.attach(kj::mv(backing));\n  }\n}\nkj::Array<kj::byte> asBytes(v8::Local<v8::ArrayBufferView> arrayBufferView) {\n  auto backing = arrayBufferView->Buffer()->GetBackingStore();\n  kj::ArrayPtr buffer(static_cast<kj::byte*>(backing->Data()), backing->ByteLength());\n  auto sliceStart = arrayBufferView->ByteOffset();\n  auto sliceEnd = sliceStart + arrayBufferView->ByteLength();\n  KJ_ASSERT(buffer.size() >= sliceEnd);\n  auto bytes = buffer.slice(sliceStart, sliceEnd);\n  if (bytes == nullptr) {\n    return getEmptyArray();\n  } else {\n    return bytes.attach(kj::mv(backing));\n  }\n}\n\n// TODO(soon): If the returned kj::Array<kj::byte> is used outside of the isolate lock,\n// we'll need to ensure it works correctly once MPK (Memory Protection Keys) enforcement\n// is fully in place.\nkj::Array<kj::byte> asBytes(v8::Local<v8::SharedArrayBuffer> sharedArrayBuffer) {\n  auto backing = sharedArrayBuffer->GetBackingStore();\n  kj::ArrayPtr bytes(static_cast<kj::byte*>(backing->Data()), backing->ByteLength());\n  if (bytes == nullptr) {\n    return getEmptyArray();\n  } else {\n    return bytes.attach(kj::mv(backing));\n  }\n}\n\nvoid recursivelyFreeze(v8::Local<v8::Context> context, v8::Local<v8::Value> value) {\n  if (value->IsArray()) {\n    // Optimize array freezing (Array is a subclass of Object, but we can iterate it faster).\n    v8::HandleScope scope(v8::Isolate::GetCurrent());\n    auto arr = value.As<v8::Array>();\n\n    for (auto i: kj::zeroTo(arr->Length())) {\n      recursivelyFreeze(context, check(arr->Get(context, i)));\n    }\n\n    check(arr->SetIntegrityLevel(context, v8::IntegrityLevel::kFrozen));\n  } else if (value->IsObject()) {\n    v8::HandleScope scope(v8::Isolate::GetCurrent());\n    auto obj = value.As<v8::Object>();\n    auto names = check(obj->GetPropertyNames(context, v8::KeyCollectionMode::kOwnOnly,\n        v8::ALL_PROPERTIES, v8::IndexFilter::kIncludeIndices));\n\n    for (auto i: kj::zeroTo(names->Length())) {\n      recursivelyFreeze(context, check(obj->Get(context, check(names->Get(context, i)))));\n    }\n\n    check(obj->SetIntegrityLevel(context, v8::IntegrityLevel::kFrozen));\n  } else {\n    // Primitive type, nothing to do.\n  }\n}\n\nv8::Local<v8::Value> deepClone(v8::Local<v8::Context> context, v8::Local<v8::Value> value) {\n  // This is implemented in the classic JSON restringification way.\n  auto serialized = check(v8::JSON::Stringify(context, value));\n  return check(v8::JSON::Parse(context, serialized));\n}\n\nnamespace {\nv8::MaybeLocal<v8::Value> makeRejectedPromise(\n    v8::Isolate* isolate, v8::Local<v8::Value> exception) {\n  v8::Local<v8::Promise::Resolver> resolver;\n  auto context = isolate->GetCurrentContext();\n  if (!v8::Promise::Resolver::New(context).ToLocal(&resolver) ||\n      resolver->Reject(context, exception).IsNothing()) {\n    return v8::MaybeLocal<v8::Value>();\n  }\n\n  return resolver->GetPromise();\n};\n\nvoid returnRejectedPromiseImpl(auto info, v8::Local<v8::Value> exception, v8::TryCatch& tryCatch) {\n  v8::Local<v8::Value> promise;\n  if (!makeRejectedPromise(info.GetIsolate(), exception).ToLocal(&promise)) {\n    // If makeRejectedPromise fails, the tryCatch should have caught the error.\n    // Let's rethrow it if it isn't terminal.\n    if (tryCatch.CanContinue()) tryCatch.ReThrow();\n  }\n  info.GetReturnValue().Set(promise);\n}\n}  // namespace\n\nvoid returnRejectedPromise(const v8::FunctionCallbackInfo<v8::Value>& info,\n    v8::Local<v8::Value> exception,\n    v8::TryCatch& tryCatch) {\n  returnRejectedPromiseImpl<const v8::FunctionCallbackInfo<v8::Value>&>(info, exception, tryCatch);\n}\n\nvoid returnRejectedPromise(const v8::PropertyCallbackInfo<v8::Value>& info,\n    v8::Local<v8::Value> exception,\n    v8::TryCatch& tryCatch) {\n  returnRejectedPromiseImpl<const v8::PropertyCallbackInfo<v8::Value>&>(info, exception, tryCatch);\n}\n\nstatic ExternalStringAllocator& getAllocatorForIsolate(v8::Isolate* isolate) {\n  return IsolateBase::from(isolate).getExternalStringAllocator();\n}\n\n// Default allocator that uses standard new/delete.\n// We typically don't use the new/delete operators directly,\n// but in this case we have to because V8's ExternalStringResource may default to `delete this`\n// if not overridden, and we are allocating raw byte arrays for placement new.\nclass DefaultExternalStringAllocator final: public ExternalStringAllocator {\n public:\n  void* allocate(size_t size) override {\n    return operator new(size);\n  }\n  void deallocate(void* ptr) override {\n    operator delete(ptr);\n  }\n};\n\nkj::Own<ExternalStringAllocator> defaultExternalStringAllocator() {\n  static DefaultExternalStringAllocator allocator;\n  return kj::Own<ExternalStringAllocator>(&allocator, kj::NullDisposer::instance);\n}\n\n// ======================================================================================\n\ntemplate <typename Type, typename Data>\nclass ExternString: public Type {\n  // The implementation of ExternString here is very closely after the implementation of the same\n  // class in Node.js, with modifications to fit our conventions. It is distributed under the\n  // same MIT license that Node.js uses. The appropriate copyright attribution is included here:\n  //\n  // Copyright Node.js contributors. All rights reserved.\n\n  // Permission is hereby granted, free of charge, to any person obtaining a copy\n  // of this software and associated documentation files (the \"Software\"), to\n  // deal in the Software without restriction, including without limitation the\n  // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n  // sell copies of the Software, and to permit persons to whom the Software is\n  // furnished to do so, subject to the following conditions:\n\n  // The above copyright notice and this permission notice shall be included in\n  // all copies or substantial portions of the Software.\n\n  // THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n  // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n  // IN THE SOFTWARE.\n\n public:\n  inline const Data* data() const override {\n    return buf.begin();\n  }\n\n  inline size_t length() const override {\n    return buf.size();\n  }\n\n  inline uint64_t byteLength() const {\n    return length() * sizeof(Data);\n  }\n\n  // Override Dispose() so that V8 properly deallocates through the configured\n  // ExternalStringAllocator rather than using `delete this` (the default).\n  void Dispose() override {\n    auto& allocator = getAllocatorForIsolate(isolate);\n    this->~ExternString();\n    allocator.deallocate(this);\n  }\n\n  static v8::MaybeLocal<v8::String> createExtern(\n      v8::Isolate* isolate, kj::ArrayPtr<const Data>& buf) {\n    if (buf.size() == 0) {\n      return v8::String::Empty(isolate);\n    }\n\n    // TODO(now): In Node.js impl, we check to see if length is less than a specified\n    // minimum. If it is, it's likely more efficient to just copy and use a regular\n    // heap allocated string than an external. We're not doing that here currently, but\n    // we might?\n\n    auto& allocator = getAllocatorForIsolate(isolate);\n    auto mem = allocator.allocate(sizeof(ExternString<Type, Data>));\n    if (mem == nullptr) {\n      isolate->ThrowException(v8::Exception::Error(\n          v8::String::NewFromUtf8Literal(isolate, \"String allocation failed\")));\n      return v8::MaybeLocal<v8::String>();\n    }\n\n    auto resource = new (mem) ExternString<Type, Data>(isolate, buf);\n\n    v8::MaybeLocal<v8::String> str;\n    if constexpr (kj::isSameType<Type, v8::String::ExternalOneByteStringResource>()) {\n      str = v8::String::NewExternalOneByte(isolate, resource);\n    } else {\n      // resource here must be a v8::String::ExternalStringResource.\n      str = v8::String::NewExternalTwoByte(isolate, resource);\n    }\n    if (str.IsEmpty()) {\n      // This should happen only if the string is too long\n      resource->~ExternString<Type, Data>();\n      allocator.deallocate(mem);\n      isolate->ThrowException(v8::Exception::Error(\n          v8::String::NewFromUtf8Literal(isolate, \"String allocation failed\")));\n      return v8::MaybeLocal<v8::String>();\n    }\n\n    return str;\n  }\n\n private:\n  v8::Isolate* isolate;\n  kj::ArrayPtr<const Data> buf;\n\n  inline ExternString(v8::Isolate* isolate, kj::ArrayPtr<const Data>& buf)\n      : isolate(isolate),\n        buf(buf) {}\n};\n\nusing ExternOneByteString = ExternString<v8::String::ExternalOneByteStringResource, char>;\nusing ExternTwoByteString = ExternString<v8::String::ExternalStringResource, uint16_t>;\n\nv8::Local<v8::String> newExternalOneByteString(Lock& js, kj::ArrayPtr<const char> buf) {\n  return check(ExternOneByteString::createExtern(js.v8Isolate, buf));\n}\n\nv8::Local<v8::String> newExternalTwoByteString(Lock& js, kj::ArrayPtr<const uint16_t> buf) {\n  return check(ExternTwoByteString::createExtern(js.v8Isolate, buf));\n}\n\n// ======================================================================================\n// Module utilities\n\nJsObject createMutableModuleExports(Lock& js, JsObject moduleNamespace) {\n  auto result = js.objNoProto();\n  auto names = moduleNamespace.getPropertyNames(js, OWN_ONLY, ALL_PROPERTIES, INCLUDE_INDICES);\n\n  for (uint32_t i = 0; i < names.size(); i++) {\n    auto name = names.get(js, i);\n    result.set(js, name, moduleNamespace.get(js, name));\n  }\n\n  return result;\n}\n\n// ======================================================================================\n// Node.js Compat\n\nnamespace {\n// This list must be kept in sync with the list of builtins from Node.js.\n// It should be unlikely that anything is ever removed from this list, and\n// adding items to it is considered a semver-major change in Node.js.\nstatic const std::set<kj::StringPtr> NODEJS_BUILTINS{\"_http_agent\"_kj, \"_http_client\"_kj,\n  \"_http_common\"_kj, \"_http_incoming\"_kj, \"_http_outgoing\"_kj, \"_http_server\"_kj,\n  \"_stream_duplex\"_kj, \"_stream_passthrough\"_kj, \"_stream_readable\"_kj, \"_stream_transform\"_kj,\n  \"_stream_wrap\"_kj, \"_stream_writable\"_kj, \"_tls_common\"_kj, \"_tls_wrap\"_kj, \"assert\"_kj,\n  \"assert/strict\"_kj, \"async_hooks\"_kj, \"buffer\"_kj, \"child_process\"_kj, \"cluster\"_kj, \"console\"_kj,\n  \"constants\"_kj, \"crypto\"_kj, \"dgram\"_kj, \"diagnostics_channel\"_kj, \"dns\"_kj, \"dns/promises\"_kj,\n  \"domain\"_kj, \"events\"_kj, \"fs\"_kj, \"fs/promises\"_kj, \"http\"_kj, \"http2\"_kj, \"https\"_kj,\n  \"inspector\"_kj, \"inspector/promises\"_kj, \"module\"_kj, \"net\"_kj, \"os\"_kj, \"path\"_kj,\n  \"path/posix\"_kj, \"path/win32\"_kj, \"perf_hooks\"_kj, \"process\"_kj, \"punycode\"_kj, \"querystring\"_kj,\n  \"readline\"_kj, \"readline/promises\"_kj, \"repl\"_kj, \"sqlite\"_kj, \"stream\"_kj, \"stream/consumers\"_kj,\n  \"stream/promises\"_kj, \"stream/web\"_kj, \"string_decoder\"_kj, \"sys\"_kj, \"timers\"_kj,\n  \"timers/promises\"_kj, \"tls\"_kj, \"trace_events\"_kj, \"tty\"_kj, \"url\"_kj, \"util\"_kj, \"util/types\"_kj,\n  \"v8\"_kj, \"vm\"_kj, \"wasi\"_kj, \"worker_threads\"_kj, \"zlib\"_kj};\n}  // namespace\n\nkj::Maybe<kj::String> checkNodeSpecifier(kj::StringPtr specifier) {\n  // The sys module was renamed to 'util'. This shim remains to keep old programs\n  // working. `sys` is deprecated and shouldn't be used.\n  // Note to maintainers: Although this module has been deprecated for a while\n  // Node.js do not plan to remove it.\n  // See: https://github.com/nodejs/node/pull/35407#issuecomment-700693439\n  if (specifier == \"sys\" || specifier == \"node:sys\") [[unlikely]] {\n    return kj::str(\"node:util\");\n  }\n  if (NODEJS_BUILTINS.contains(specifier)) {\n    return kj::str(\"node:\", specifier);\n  } else if (specifier.startsWith(\"node:\")) {\n    return kj::str(specifier);\n  }\n  return kj::none;\n}\n\nbool isNodeJsCompatEnabled(jsg::Lock& js) {\n  return IsolateBase::from(js.v8Isolate).isNodeJsCompatEnabled();\n}\n\nbool isNodeJsProcessV2Enabled(jsg::Lock& js) {\n  return IsolateBase::from(js.v8Isolate).isNodeJsProcessV2Enabled();\n}\n\nbool isRequireReturnsDefaultExportEnabled(jsg::Lock& js) {\n  return IsolateBase::from(js.v8Isolate).isRequireReturnsDefaultExportEnabled();\n}\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/util.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// INTERNAL IMPLEMENTATION FILE\n//\n// This file contains misc utility functions used elsewhere.\n#include <workerd/jsg/exception.h>\n\n#include <v8-array-buffer.h>\n#include <v8-exception.h>\n#include <v8-primitive.h>\n#include <v8-promise.h>\n\n#include <kj/debug.h>\n#include <kj/exception.h>\n#include <kj/string.h>\n\n#include <typeinfo>\n\nnamespace v8 {\nclass Isolate;\n}\nnamespace workerd::jsg {\n\nclass Lock;\nclass JsObject;\n\nusing uint = unsigned int;\n\n#define JS_ERROR_TYPES(V)                                                                          \\\n  V(\"Error\", Error)                                                                                \\\n  V(\"RangeError\", RangeError)                                                                      \\\n  V(\"TypeError\", TypeError)                                                                        \\\n  V(\"SyntaxError\", SyntaxError)                                                                    \\\n  V(\"ReferenceError\", ReferenceError)                                                              \\\n  V(\"CompileError\", WasmCompileError)                                                              \\\n  V(\"LinkError\", WasmLinkError)                                                                    \\\n  V(\"RuntimeError\", WasmRuntimeError)                                                              \\\n  V(\"SuspendError\", WasmSuspendError)                                                              \\\n  V(\"AggregateError\", AggregateError)                                                              \\\n  V(\"SuppressedError\", SuppressedError)                                                            \\\n  V(\"URIError\", URIError)                                                                          \\\n  V(\"EvalError\", EvalError)\n\n// When a C++ callback wishes to throw a JavaScript exception, it should first call\n// isolate->ThrowException() to set the JavaScript error value, then it should throw\n// JsExceptionThrown() as a C++ exception. This will be caught by the callback glue before the\n// code returns to V8.\n//\n// This differs from the usual convention in V8 which is to return a v8::Maybe that is null in the\n// case an exception is thrown. Writing code that deals with maybes is cumbersome and error-prone\n// compared to C++ exceptions.\nclass JsExceptionThrown: public std::exception {\n public:\n  JsExceptionThrown();\n  ~JsExceptionThrown() noexcept = default;  // We must match `std::exception`'s noexcept.\n  const char* what() const noexcept override;\n\n private:\n  void* trace[16];\n  kj::ArrayPtr<void* const> tracePtr;\n  mutable kj::String whatBuffer;\n};\n\nbool getCaptureThrowsAsRejections(v8::Isolate* isolate);\nbool getShouldSetToStringTag(v8::Isolate* isolate);\nbool getShouldSetImmutablePrototype(v8::Isolate* isolate);\nbool getSpecCompliantPropertyAttributes(v8::Isolate* isolate);\n\nkj::String fullyQualifiedTypeName(const std::type_info& type);\nkj::String typeName(const std::type_info& type);\n\n// Creates a JavaScript error that obfuscates the exception details, while logging the full details\n// to stderr. If the KJ exception was created using throwTunneledException(), don't log anything\n// but instead return the original reconstructed JavaScript exception.\nv8::Local<v8::Value> makeInternalError(v8::Isolate* isolate, kj::StringPtr internalMessage);\n\n// Creates a JavaScript error that obfuscates the exception details, while logging the full details\n// to stderr. If the KJ exception was created using throwTunneledException(), don't log anything\n// but instead return the original reconstructed JavaScript exception.\nv8::Local<v8::Value> exceptionToJs(\n    v8::Isolate* isolate, kj::Exception&& exception, ExceptionToJsOptions options = {});\n\n// calls makeInternalError() and then tells the isolate to throw it.\nvoid throwInternalError(v8::Isolate* isolate, kj::StringPtr internalMessage);\n\n// calls makeInternalError() and then tells the isolate to throw it.\nvoid throwInternalError(\n    v8::Isolate* isolate, kj::Exception&& exception, ExceptionToJsOptions options = {});\n\nconstexpr kj::Exception::DetailTypeId TUNNELED_EXCEPTION_DETAIL_ID = 0xe8027292171b1646ull;\n\n// Detail type for JavaScript exception metadata (error type and stack trace)\nconstexpr kj::Exception::DetailTypeId JS_EXCEPTION_METADATA_DETAIL_ID = 0xa9ae63464030fcefull;\n\n// Add a serialized copy of the exception value to the KJ exception, as a \"detail\".\nvoid addExceptionDetail(Lock& js, kj::Exception& exception, v8::Local<v8::Value> handle);\n\n// Extract and add JavaScript exception metadata (error type and stack trace) to the KJ exception.\n// Serializes using Cap'n Proto schema defined in exception-metadata.capnp.\nvoid addJsExceptionMetadata(Lock& js, kj::Exception& exception, v8::Local<v8::Value> handle);\n\nstruct TypeErrorContext {\n  enum Kind : uint8_t {\n    METHOD_ARGUMENT,       // has type name, member (method) name, and argument index\n    CONSTRUCTOR_ARGUMENT,  // has type name, argument index\n    SETTER_ARGUMENT,       // has type name and member (property) name\n    STRUCT_FIELD,          // has type name and member (field) name\n    ARRAY_ELEMENT,         // has argument (element) index\n                           // TODO(someday): Capture where the array itself was declared?\n    CALLBACK_ARGUMENT,     // has argument index\n                           // TODO(someday): Track where callback was introduced for better errors.\n    CALLBACK_RETURN,       // has nothing\n                           // TODO(someday): Track where callback was introduced for better errors.\n    DICT_KEY,              // has member (key) name\n    DICT_FIELD,            // has member (field) name\n    PROMISE_RESOLUTION,    // has nothing\n                           // TODO(someday): Track where the promise was introduced.\n    OTHER,                 // has nothing\n  };\n\n  Kind kind;\n  uint argumentIndex;\n  kj::Maybe<const std::type_info&> type;\n  const char* memberName;\n\n  static inline TypeErrorContext methodArgument(\n      const std::type_info& type, const char* methodName, uint argumentIndex) {\n    return {METHOD_ARGUMENT, argumentIndex, type, methodName};\n  }\n  static inline TypeErrorContext constructorArgument(\n      const std::type_info& type, uint argumentIndex) {\n    return {CONSTRUCTOR_ARGUMENT, argumentIndex, type, nullptr};\n  }\n  static inline TypeErrorContext setterArgument(\n      const std::type_info& type, const char* propertyName) {\n    return {SETTER_ARGUMENT, 0, type, propertyName};\n  }\n  static inline TypeErrorContext structField(const std::type_info& type, const char* fieldName) {\n    return {STRUCT_FIELD, 0, type, fieldName};\n  }\n  static inline TypeErrorContext arrayElement(uint index) {\n    return {ARRAY_ELEMENT, index, kj::none, nullptr};\n  }\n  static inline TypeErrorContext callbackArgument(uint argumentIndex) {\n    return {CALLBACK_ARGUMENT, argumentIndex, kj::none, nullptr};\n  }\n  static inline TypeErrorContext callbackReturn() {\n    return {CALLBACK_RETURN, 0, kj::none, nullptr};\n  }\n  static inline TypeErrorContext dictKey(const char* keyName) {\n    return {DICT_KEY, 0, kj::none, keyName};\n  }\n  static inline TypeErrorContext dictField(const char* fieldName) {\n    return {DICT_FIELD, 0, kj::none, fieldName};\n  }\n  static inline TypeErrorContext promiseResolution() {\n    return {PROMISE_RESOLUTION, 0, kj::none, nullptr};\n  }\n  static inline TypeErrorContext other() {\n    return {OTHER, 0, kj::none, nullptr};\n  }\n};\n\n// Throw a JavaScript exception indicating an argument type error, and then throw a C++ exception\n// of type JsExceptionThrown, which will be caught by liftKj().\n[[noreturn]] void throwTypeError(\n    v8::Isolate* isolate, TypeErrorContext errorContext, const char* expectedType);\n\n// Throw a JavaScript exception indicating an argument type error, and then throw a C++ exception\n// of type JsExceptionThrown, which will be caught by liftKj().\n[[noreturn]] void throwTypeError(\n    v8::Isolate* isolate, TypeErrorContext errorContext, const std::type_info& expectedType);\n\n// Throw a JavaScript exception indicating an argument type error, and then throw a C++ exception\n// of type JsExceptionThrown, which will be caught by liftKj().\n[[noreturn]] void throwTypeError(\n    v8::Isolate* isolate, TypeErrorContext errorContext, kj::String expectedType);\n\n// Throw a JavaScript TypeError with a free-form message.\n[[noreturn]] void throwTypeError(v8::Isolate* isolate, kj::StringPtr message);\n\n// Callback used when attempting to construct a type that can't be constructed from JavaScript.\nvoid throwIllegalConstructor(const v8::FunctionCallbackInfo<v8::Value>& args);\n\nkj::StringPtr extractTunneledExceptionDescription(kj::StringPtr message);\n\n// Given a JavaScript exception, returns a KJ exception that contains a tunneled exception type that\n// can be converted back to JavaScript via makeInternalError().\nkj::Exception createTunneledException(v8::Isolate* isolate, v8::Local<v8::Value> exception);\n\n// Given a JavaScript exception, throw a KJ exception that contains a tunneled exception type that\n// can be converted back to JavaScript via makeInternalError().\n//\n// Equivalent to throwing the exception returned by `createTunneledException(exception)`.\n[[noreturn]] void throwTunneledException(v8::Isolate* isolate, v8::Local<v8::Value> exception);\n\ntemplate <typename T>\nv8::Local<T> check(v8::MaybeLocal<T> maybe) {\n  // V8 usually returns a MaybeLocal to mean that the function can throw a JavaScript exception.\n  // If the MaybeLocal is empty then an exception was already thrown.\n\n  v8::Local<T> result;\n  if (!maybe.ToLocal(&result)) {\n    throw JsExceptionThrown();\n  }\n  return result;\n}\n\ntemplate <typename T>\nT check(v8::Maybe<T> maybe) {\n  T result;\n  if (maybe.To(&result)) {\n    return result;\n  } else {\n    throw JsExceptionThrown();\n  }\n}\n\n// View the contents of the given v8::ArrayBuffer/ArrayBufferView as an ArrayPtr<byte>.\nkj::Array<kj::byte> asBytes(v8::Local<v8::ArrayBuffer> arrayBuffer);\n\n// View the contents of the given v8::ArrayBuffer/ArrayBufferView as an ArrayPtr<byte>.\nkj::Array<kj::byte> asBytes(v8::Local<v8::ArrayBufferView> arrayBufferView);\n\n// View the contents of the given v8::SharedArrayBuffer as an ArrayPtr<byte>.\nkj::Array<kj::byte> asBytes(v8::Local<v8::SharedArrayBuffer> sharedArrayBuffer);\n\n// Freeze the given object and all its members, making it recursively immutable.\n//\n// WARNING: This function is unsafe to call on user-provided content since if the value is cyclic\n//   or may contain non-simple values it won't do the right thing. It is safe to call this on the\n//   output of JSON.parse().\n// TODO(cleanup): Maybe replace this with a function that parses and freezes JSON in one step.\nvoid recursivelyFreeze(v8::Local<v8::Context> context, v8::Local<v8::Value> value);\n\n// Make a deep clone of the given object.\nv8::Local<v8::Value> deepClone(v8::Local<v8::Context> context, v8::Local<v8::Value> value);\n\n// Make a JavaScript String in v8's Heap.\n//\n// The type T of kj::Array<T> will determine the specific type of v8 string that is created:\n// * When T is const char, the kj::Array<T> is interpreted as UTF-8.\n// * When T is char16_t, the kj::Array<T> is interpreted as UTF-16.\n//\n// TODO(cleanup): Call sites should migrate to the new js.str(...) variants on jsg::Lock\n// rather than calling v8Str directly. Once the migration is a big further along, v8Str\n// and its variants will be explicitly marked deprecated.\ntemplate <typename T>\nv8::Local<v8::String> v8Str(v8::Isolate* isolate,\n    kj::ArrayPtr<T> ptr,\n    v8::NewStringType newType = v8::NewStringType::kNormal) {\n  if constexpr (kj::isSameType<char16_t, T>()) {\n    return check(v8::String::NewFromTwoByte(\n        isolate, reinterpret_cast<uint16_t*>(ptr.begin()), newType, ptr.size()));\n  } else if constexpr (kj::isSameType<const char16_t, T>()) {\n    return check(v8::String::NewFromTwoByte(\n        isolate, reinterpret_cast<const uint16_t*>(ptr.begin()), newType, ptr.size()));\n  } else if constexpr (kj::isSameType<uint16_t, T>()) {\n    return check(v8::String::NewFromTwoByte(isolate, ptr.begin(), newType, ptr.size()));\n  } else if constexpr (kj::isSameType<const char, T>()) {\n    return check(v8::String::NewFromUtf8(isolate, ptr.begin(), newType, ptr.size()));\n  } else if constexpr (kj::isSameType<char, T>()) {\n    return check(v8::String::NewFromUtf8(isolate, ptr.begin(), newType, ptr.size()));\n  } else {\n    KJ_UNREACHABLE;\n  }\n}\n\n// Make a JavaScript String in v8's Heap with the kj::StringPtr interpreted as UTF-8.\ninline v8::Local<v8::String> v8Str(v8::Isolate* isolate,\n    kj::StringPtr str,\n    v8::NewStringType newType = v8::NewStringType::kNormal) {\n  return v8Str(isolate, str.asArray(), newType);\n}\n\n// Make a JavaScript String in v8's Heap with the kj::ArrayPtr interpreted as Latin1.\ninline v8::Local<v8::String> v8StrFromLatin1(v8::Isolate* isolate,\n    kj::ArrayPtr<const kj::byte> ptr,\n    v8::NewStringType newType = v8::NewStringType::kNormal) {\n  return check(v8::String::NewFromOneByte(isolate, ptr.begin(), newType, ptr.size()));\n}\n\ninline v8::Local<v8::String> v8StrIntern(v8::Isolate* isolate, kj::StringPtr str) {\n  return v8Str(isolate, str, v8::NewStringType::kInternalized);\n}\n\ntemplate <typename T>\nconstexpr bool isVoid() {\n  return false;\n}\ntemplate <>\nconstexpr bool isVoid<void>() {\n  return true;\n}\n\ntemplate <typename T>\nstruct RemoveMaybe_;\ntemplate <typename T>\nstruct RemoveMaybe_<kj::Maybe<T>> {\n  using Type = T;\n};\ntemplate <typename T>\nusing RemoveMaybe = RemoveMaybe_<T>::Type;\n\ntemplate <typename T>\nstruct RemoveRvalueRef_ {\n  using Type = T;\n};\ntemplate <typename T>\nstruct RemoveRvalueRef_<T&&> {\n  using Type = T;\n};\ntemplate <typename T>\nusing RemoveRvalueRef = RemoveRvalueRef_<T>::Type;\n\nenum class JsgKind { RESOURCE, STRUCT, EXTENSION };\n\ntemplate <typename T>\nstruct LiftKj_ {\n  template <typename Info, typename Func, typename Ret = void>\n  static Ret apply(const Info& info, Func&& func) {\n    constexpr bool isFastApi = kj::isSameType<v8::Isolate*, Info>();\n    v8::Isolate* isolate;\n    if constexpr (isFastApi) {\n      isolate = info;\n    } else {\n      isolate = info.GetIsolate();\n    }\n\n    try {\n      try {\n        if constexpr (isFastApi) {\n          return func();\n        } else {\n          if constexpr (isVoid<T>()) {\n            func();\n            if constexpr (!kj::canConvert<Info&, v8::PropertyCallbackInfo<void>&>()) {\n              info.GetReturnValue().SetUndefined();\n            }\n          } else {\n            info.GetReturnValue().Set(func());\n          }\n        }\n      } catch (kj::Exception& exception) {\n        // This throwInternalError() overload may decode a tunneled error. While constructing the\n        // v8::Value representing the tunneled error, it itself may cause a JS exception to be\n        // thrown. This is the reason for the nested try-catch blocks -- we need to be able to\n        // swallow any JsExceptionThrown exceptions that this catch block generates.\n        throwInternalError(isolate, kj::mv(exception));\n      }\n    } catch (JsExceptionThrown&) {\n      // nothing to do\n    } catch (std::exception& exception) {\n      throwInternalError(isolate, exception.what());\n    } catch (...) {\n      throwInternalError(\n          isolate, kj::str(\"caught unknown exception of type: \", kj::getCaughtExceptionType()));\n    }\n\n    if constexpr (isFastApi && !isVoid<Ret>()) {\n      // Since we return early on fast api calls, this should never get executed\n      // unless there is an error thrown from the fast api call itself.\n      return Ret{};\n    }\n  }\n};\n\nvoid returnRejectedPromise(const v8::FunctionCallbackInfo<v8::Value>& info,\n    v8::Local<v8::Value> exception,\n    v8::TryCatch& tryCatch);\n\nvoid returnRejectedPromise(const v8::PropertyCallbackInfo<v8::Value>& info,\n    v8::Local<v8::Value> exception,\n    v8::TryCatch& tryCatch);\n\ntemplate <>\nstruct LiftKj_<v8::Local<v8::Promise>> {\n  template <typename Info, typename Func>\n  static void apply(Info& info, Func&& func) {\n    auto isolate = info.GetIsolate();\n    if (!getCaptureThrowsAsRejections(isolate)) {\n      // Capturing exceptions into rejected promises is not enabled, so fall back to the regular\n      // implementation.\n      LiftKj_<v8::Local<v8::Value>>::apply(info, kj::fwd<Func>(func));\n      return;\n    }\n\n    v8::TryCatch tryCatch(isolate);\n    try {\n      try {\n        info.GetReturnValue().Set(func());\n      } catch (kj::Exception& exception) {\n        // returnRejectedPromise() may decode a tunneled error. While constructing\n        // the v8::Value representing the tunneled error, it itself may cause a JS exception to be\n        // thrown. This is the reason for the nested try-catch blocks -- we need to be able to\n        // swallow any JsExceptionThrown exceptions that this catch block generates.\n        returnRejectedPromise(info, exceptionToJs(isolate, kj::mv(exception)), tryCatch);\n      }\n    } catch (JsExceptionThrown&) {\n      if (tryCatch.CanContinue()) {\n        returnRejectedPromise(info, tryCatch.Exception(), tryCatch);\n      }\n      // If CanContinue() is false, then there's nothing we can do.\n    } catch (std::exception& exception) {\n      throwInternalError(isolate, exception.what());\n    } catch (...) {\n      throwInternalError(\n          isolate, kj::str(\"caught unknown exception of type: \", kj::getCaughtExceptionType()));\n    }\n  }\n};\n\n// Lifts KJ code into V8 code: Catches exceptions and manages HandleScope. Converts the\n// function's return value into the appropriate V8 return.\n//\n// liftKj() translates certain KJ exceptions thrown into JS exceptions. KJ exceptions opt into\n// this behavior by suffixing their description strings with \"jsg.SomeError\", followed by an\n// optional colon, optional whitespace, and a message which will be exposed to the user.\n// In the example \"jsg.SomeError\", \"SomeError\" must be a specific recognized string:\n// \"RangeError\", \"TypeError\", or \"DOMException(name)\", where `name` is the \"error name\" of the\n// DOMException you wish to throw.\n//\n// The DOMException error name will typically be dictated by the spec governing the API you are\n// implementing. While it can be any string without a closing parenthesis as far as JSG is\n// concerned, it will likely be one of the ones listed here:\n//\n// https://heycam.github.io/webidl/#dfn-error-names-table\ntemplate <typename Info, typename Func>\nvoid liftKj(const Info& info, Func&& func) {\n  LiftKj_<decltype(func())>::apply(info, kj::fwd<Func>(func));\n}\n\n// Direct value version of liftKj for use with Fast API and other contexts where callback info isn't available\ntemplate <typename Ret, typename Func>\nRet liftKj(v8::Isolate* isolate, Func&& func) {\n  return LiftKj_<decltype(func())>::template apply<v8::Isolate*, Func, Ret>(\n      isolate, kj::fwd<Func>(func));\n}\n\nnamespace _ {\n\nstruct NoneDetected;\n\ntemplate <typename Default, typename AlwaysVoid, template <typename...> class Op, class... Args>\nstruct Detector {\n  using Type = Default;\n  static constexpr bool value = false;\n};\n\ntemplate <typename Default, template <typename...> class Op, class... Args>\nstruct Detector<Default, kj::VoidSfinae<Op<Args...>>, Op, Args...> {\n  using Type = Op<Args...>;\n  static constexpr bool value = true;\n};\n\n}  // namespace _\n\n// A typedef for `Op<Args...>` if that template is instantiable, otherwise `Default`.\ntemplate <typename Default, template <typename...> class Op, typename... Args>\nusing DetectedOr = _::Detector<Default, void, Op, Args...>::Type;\n\n// True if Op<Args...> is instantiable, false otherwise. This is basically the same as\n// std::experimental::is_detected from the library fundamentals TS v2.\n//   http://en.cppreference.com/w/cpp/experimental/is_detected\n//\ntemplate <template <typename...> class Op, typename... Args>\nconstexpr bool isDetected() {\n  return _::Detector<_::NoneDetected, void, Op, Args...>::value;\n}\n// TODO(cleanup): Should live in kj?\n\n// SFINAE-friendly accessor for a resource type's configuration parameter.\ntemplate <typename Arg>\nauto getParameterType(void (*)(Arg)) -> Arg;\n\n// SFINAE-friendly accessor for a resource type's configuration parameter.\ntemplate <typename T>\nusing GetConfiguration = decltype(getParameterType(&T::jsgConfiguration));\n\ntemplate <typename T>\nconcept HasConfiguration = requires(GetConfiguration<T> arg) { T::jsgConfiguration(arg); };\n\ninline bool isFinite(double value) {\n  return !(kj::isNaN(value) || value == kj::inf() || value == -kj::inf());\n}\n\ntemplate <typename T>\nconcept StrictlyBool = kj::isSameType<T, bool>();\n\n// ======================================================================================\n\nclass Lock;\n\n// Interface for allocating backing stores for v8 external string.\nclass ExternalStringAllocator {\n public:\n  virtual ~ExternalStringAllocator() = default;\n\n  virtual void* allocate(size_t size) = 0;\n  virtual void deallocate(void* ptr) = 0;\n};\n\n// Returns a singleton DefaultExternalStringAllocator.\nkj::Own<ExternalStringAllocator> defaultExternalStringAllocator();\n\n// Creates v8 Strings from buffers not on the v8 heap. These do not copy and do not\n// take ownership of the buf. The buf *must* point to a static constant with infinite\n// lifetime.\n//\n// It is important to understand that the OneByteString variant will interpret buf as\n// latin-1 rather than UTF-8, which is how KJ normally represents text. There is no\n// variation of external strings that support UTF-8 encoded bytes. To represent any\n// text outside of the Latin1 range, the two-byte (uint16_t) variant must be used.\n//\n// Note that these intentionally do not use the v8Str naming convention like the other\n// string methods because it needs to be absolutely clear that these use external buffers\n// that are not owned by the v8 heap.\nv8::Local<v8::String> newExternalOneByteString(Lock& js, kj::ArrayPtr<const char> buf);\n\n// Creates v8 Strings from buffers not on the v8 heap. These do not copy and do not\n// take ownership of the buf. The buf *must* point to a static constant with infinite\n// lifetime.\n//\n// It is important to understand that the OneByteString variant will interpret buf as\n// latin-1 rather than UTF-8, which is how KJ normally represents text. There is no\n// variation of external strings that support UTF-8 encoded bytes. To represent any\n// text outside of the Latin1 range, the two-byte (uint16_t) variant must be used.\n//\n// Note that these intentionally do not use the v8Str naming convention like the other\n// string methods because it needs to be absolutely clear that these use external buffers\n// that are not owned by the v8 heap.\nv8::Local<v8::String> newExternalTwoByteString(Lock& js, kj::ArrayPtr<const uint16_t> buf);\n\n// Use this type to mark APIs that are not implemented. Attempts to use the API will throw an\n// exception.\n// - Use Unimplemented as a method parameter type or struct field type to mark that\n//   parameter/field unimplemented; only the value `undefined` will be allowed.\n// - Use Unimplemented as the return type of a method to mark the whole method unimplemented.\n//   Have the method body simply return `Unimplemented()`.\nstruct Unimplemented {};\n// TODO(someday): We should consider making it easier for people to probe features by doing\n//   `if (obj.someMember)`. Currently this check would pass for methods and would throw an\n//   exception for properties. Is it possible for us to hook into the V8 feature where there are\n//   special values of `undefined` that augment the error message thrown if they are used?\n\n// Use to mark APIs that are not just unimplemented, but that we don't plan to implement, e.g.\n// standard ServiceWorker APIs that don't make sense for Workers.\nusing WontImplement = Unimplemented;\n\n// ======================================================================================\n// Module utilities\n\n// Creates a mutable copy of a module namespace object for CommonJS compatibility.\n// This is needed because ES module namespaces have read-only properties, but\n// CommonJS require() is expected to return objects with mutable properties.\n// This matches Node.js behavior when requiring an ESM module from CJS.\n// See: https://github.com/cloudflare/workerd/issues/5844\nJsObject createMutableModuleExports(Lock& js, JsObject moduleNamespace);\n\n// ======================================================================================\n// Node.js Compat\n\nkj::Maybe<kj::String> checkNodeSpecifier(kj::StringPtr specifier);\nbool isNodeJsCompatEnabled(jsg::Lock& js);\nbool isNodeJsProcessV2Enabled(jsg::Lock& js);\nbool isRequireReturnsDefaultExportEnabled(jsg::Lock& js);\n\n// The following counter is used to track the number of times a method is called.\n// This is mostly useful for validating/testing v8 fast api methods, but also for\n// tracking the number of times a method is called in general.\nstruct CallCounter {\n  // Referring to the slow method triggered at least once for a single method call.\n  uint32_t slow = 0;\n  // Referring to the fast method whenever V8 optimizes the method.\n  // Might or might not be called depending on the method's implementation,\n  // and the current limitations of v8 fast api.\n  uint32_t fast = 0;\n\n  void reset() {\n    slow = 0;\n    fast = 0;\n  }\n\n  bool operator==(const CallCounter& rhs) {\n    return slow == rhs.slow && fast == rhs.fast;\n  }\n};\n\ninline kj::String KJ_STRINGIFY(CallCounter& counter) {\n  return kj::str(\"(\", counter.slow, \", \", counter.fast, \")\");\n}\n\nstatic CallCounter callCounter{};\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/v8-platform-wrapper.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"v8-platform-wrapper.h\"\n\n#include \"jsg.h\"\n\n#include <v8-isolate.h>\n\nnamespace workerd::jsg {\n\nV8PlatformWrapper::JobTaskWrapper::JobTaskWrapper(std::unique_ptr<v8::JobTask> inner)\n    : inner(kj::mv(inner)) {}\n\nvoid V8PlatformWrapper::JobTaskWrapper::Run(v8::JobDelegate* delegate) {\n  runInV8Stack([&](jsg::V8StackScope& stackScope) { inner->Run(delegate); });\n}\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/v8-platform-wrapper.h",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <v8-platform.h>\n\n#include <kj/common.h>\n\nnamespace workerd::jsg {\n\nclass V8PlatformWrapper: public v8::Platform {\n public:\n  explicit V8PlatformWrapper(v8::Platform& inner): inner(inner) {}\n\n  v8::PageAllocator* GetPageAllocator() override {\n    return inner.GetPageAllocator();\n  }\n\n  int NumberOfWorkerThreads() override {\n    return inner.NumberOfWorkerThreads();\n  }\n\n  std::shared_ptr<v8::TaskRunner> GetForegroundTaskRunner(\n      v8::Isolate* isolate, v8::TaskPriority priority) override {\n    return inner.GetForegroundTaskRunner(isolate, priority);\n  }\n\n  void PostTaskOnWorkerThreadImpl(v8::TaskPriority priority,\n      std::unique_ptr<v8::Task> task,\n      const v8::SourceLocation& location) override {\n    inner.PostTaskOnWorkerThreadImpl(priority, kj::mv(task), location);\n  }\n\n  void PostDelayedTaskOnWorkerThreadImpl(v8::TaskPriority priority,\n      std::unique_ptr<v8::Task> task,\n      double delay_in_seconds,\n      const v8::SourceLocation& location) override {\n    inner.PostDelayedTaskOnWorkerThreadImpl(priority, kj::mv(task), delay_in_seconds, location);\n  }\n\n  std::unique_ptr<v8::JobHandle> CreateJobImpl(v8::TaskPriority priority,\n      std::unique_ptr<v8::JobTask> job_task,\n      const v8::SourceLocation& location) override {\n    return inner.CreateJobImpl(\n        priority, std::make_unique<JobTaskWrapper>(kj::mv(job_task)), location);\n  }\n\n  bool IdleTasksEnabled(v8::Isolate* isolate) override {\n    return inner.IdleTasksEnabled(isolate);\n  }\n\n  double MonotonicallyIncreasingTime() override {\n    return inner.MonotonicallyIncreasingTime();\n  }\n\n  int64_t CurrentClockTimeMilliseconds() override {\n    return inner.CurrentClockTimeMilliseconds();\n  }\n\n  double CurrentClockTimeMillis() override {\n    return inner.CurrentClockTimeMillis();\n  }\n\n  double CurrentClockTimeMillisecondsHighResolution() override {\n    return inner.CurrentClockTimeMillisecondsHighResolution();\n  }\n\n  v8::TracingController* GetTracingController() override {\n    return inner.GetTracingController();\n  }\n\n private:\n  v8::Platform& inner;\n\n  class JobTaskWrapper: public v8::JobTask {\n   public:\n    JobTaskWrapper(std::unique_ptr<v8::JobTask> inner);\n\n    void Run(v8::JobDelegate*) override;\n\n    size_t GetMaxConcurrency(size_t worker_count) const override {\n      return inner->GetMaxConcurrency(worker_count);\n    }\n\n   private:\n    std::unique_ptr<v8::JobTask> inner;\n  };\n};\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/value-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"jsg-test.h\"\n\nnamespace workerd::jsg::test {\nnamespace {\n\nV8System v8System;\nclass ContextGlobalObject: public Object, public ContextGlobal {};\n\nstruct BoolContext: public ContextGlobalObject {\n  kj::String takeBool(bool b) {\n    return kj::str(b);\n  }\n  JSG_RESOURCE_TYPE(BoolContext) {\n    JSG_METHOD(takeBool);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(BoolIsolate, BoolContext);\n\nKJ_TEST(\"bool\") {\n  Evaluator<BoolContext, BoolIsolate> e(v8System);\n  e.expectEval(\"takeBool(false)\", \"string\", \"false\");\n  e.expectEval(\"takeBool(true)\", \"string\", \"true\");\n  e.expectEval(\"takeBool(123)\", \"string\", \"true\");\n  e.expectEval(\"takeBool({})\", \"string\", \"true\");\n  e.expectEval(\"takeBool('')\", \"string\", \"false\");\n  e.expectEval(\"takeBool('false')\", \"string\", \"true\");\n  e.expectEval(\"takeBool(null)\", \"string\", \"false\");\n  e.expectEval(\"takeBool(undefined)\", \"string\", \"false\");\n  e.expectEval(\"takeBool()\", \"throws\",\n      \"TypeError: Failed to execute 'takeBool' on 'BoolContext': parameter 1 is \"\n      \"not of type 'boolean'.\");\n}\n\n// ========================================================================================\n\nstruct OptionalContext: public ContextGlobalObject {\n  struct TestOptionalFields {\n    Optional<kj::String> optional;\n    LenientOptional<kj::String> lenient;\n    kj::Maybe<kj::String> nullable;\n\n    JSG_STRUCT(optional, lenient, nullable);\n  };\n\n  struct TestAllOptionalFields {\n    Optional<kj::String> optString;\n    Optional<double> optDouble;\n\n    JSG_STRUCT(optString, optDouble);\n  };\n\n  double takeOptional(jsg::Lock& js, Optional<Ref<NumberBox>> num) {\n    return kj::mv(num).orDefault(js.alloc<NumberBox>(321))->value;\n  }\n  double takeMaybe(jsg::Lock& js, kj::Maybe<Ref<NumberBox>> num) {\n    return kj::mv(num).orDefault(js.alloc<NumberBox>(321))->value;\n  }\n  double takeLenientOptional(jsg::Lock& js, LenientOptional<Ref<NumberBox>> num) {\n    return kj::mv(num).orDefault(js.alloc<NumberBox>(321))->value;\n  }\n  kj::String takeOptionalMaybe(Optional<kj::Maybe<kj::String>> arg) {\n    return kj::mv(arg).orDefault(kj::str(\"(absent)\")).orDefault(kj::str(\"(null)\"));\n  }\n  Optional<Ref<NumberBox>> returnOptional(jsg::Lock& js, double value) {\n    if (value == 321)\n      return kj::none;\n    else\n      return js.alloc<NumberBox>(value);\n  }\n  kj::Maybe<Ref<NumberBox>> returnMaybe(jsg::Lock& js, double value) {\n    if (value == 321)\n      return kj::none;\n    else\n      return js.alloc<NumberBox>(value);\n  }\n\n  kj::String readTestOptionalFields(TestOptionalFields s) {\n    return kj::str(kj::mv(s.optional).orDefault(kj::str(\"(absent)\")), \", \",\n        kj::mv(s.lenient).orDefault(kj::str(\"(absent)\")), \", \",\n        kj::mv(s.nullable).orDefault(kj::str(\"(absent)\")));\n  }\n  TestOptionalFields makeTestOptionalFields(Optional<kj::String> optional,\n      LenientOptional<kj::String> lenient,\n      kj::Maybe<kj::String> nullable) {\n    return {kj::mv(optional), kj::mv(lenient), kj::mv(nullable)};\n  }\n\n  kj::String readTestAllOptionalFields(TestAllOptionalFields s) {\n    return kj::str(kj::mv(s.optString).orDefault(kj::str(\"(absent)\")), \", \",\n        kj::mv(s.optDouble).orDefault(321));\n  }\n\n  JSG_RESOURCE_TYPE(OptionalContext) {\n    JSG_NESTED_TYPE(NumberBox);\n    JSG_METHOD(takeOptional);\n    JSG_METHOD(takeMaybe);\n    JSG_METHOD(takeLenientOptional);\n    JSG_METHOD(takeOptionalMaybe);\n    JSG_METHOD(returnOptional);\n    JSG_METHOD(returnMaybe);\n    JSG_METHOD(readTestOptionalFields);\n    JSG_METHOD(makeTestOptionalFields);\n    JSG_METHOD(readTestAllOptionalFields);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(OptionalIsolate,\n    OptionalContext,\n    OptionalContext::TestOptionalFields,\n    OptionalContext::TestAllOptionalFields,\n    NumberBox);\n\nKJ_TEST(\"optionals and maybes\") {\n  Evaluator<OptionalContext, OptionalIsolate> e(v8System);\n  e.getIsolate().setUsingFastJsgStruct();\n  e.expectEval(\"takeOptional(new NumberBox(123))\", \"number\", \"123\");\n  e.expectEval(\"takeOptional()\", \"number\", \"321\");\n  e.expectEval(\"takeOptional(undefined)\", \"number\", \"321\");\n  e.expectEval(\"returnOptional(123).value\", \"number\", \"123\");\n  e.expectEval(\"returnOptional(321)\", \"undefined\", \"undefined\");\n\n  e.expectEval(\"takeMaybe(new NumberBox(123))\", \"number\", \"123\");\n  e.expectEval(\"takeMaybe(null)\", \"number\", \"321\");\n  e.expectEval(\"takeMaybe(undefined)\", \"number\", \"321\");\n  e.expectEval(\"returnMaybe(123).value\", \"number\", \"123\");\n  e.expectEval(\"returnMaybe(321)\", \"object\", \"null\");\n\n  e.expectEval(\"takeMaybe()\", \"throws\",\n      \"TypeError: Failed to execute 'takeMaybe' on 'OptionalContext': parameter 1 is not \"\n      \"of type 'NumberBox'.\");\n  e.expectEval(\"takeOptional(null)\", \"throws\",\n      \"TypeError: Failed to execute 'takeOptional' on 'OptionalContext': parameter 1 is not \"\n      \"of type 'NumberBox'.\");\n\n  e.expectEval(\"takeLenientOptional(new NumberBox(123))\", \"number\", \"123\");\n  e.expectEval(\"takeLenientOptional()\", \"number\", \"321\");\n  e.expectEval(\"takeLenientOptional(undefined)\", \"number\", \"321\");\n  e.expectEval(\"takeLenientOptional(null)\", \"number\", \"321\");\n  e.expectEval(\"takeLenientOptional((foo) => {})\", \"number\", \"321\");\n\n  e.expectEval(\"takeOptionalMaybe()\", \"string\", \"(absent)\");\n  e.expectEval(\"takeOptionalMaybe(null)\", \"string\", \"(null)\");\n  e.expectEval(\"takeOptionalMaybe(undefined)\", \"string\", \"(absent)\");\n  e.expectEval(\"takeOptionalMaybe('a string')\", \"string\", \"a string\");\n\n  e.expectEval(\n      \"readTestOptionalFields({nullable: null})\", \"string\", \"(absent), (absent), (absent)\");\n  e.expectEval(\"readTestOptionalFields({optional: 'foo', lenient: 'bar', nullable: null})\",\n      \"string\", \"foo, bar, (absent)\");\n  e.expectEval(\"readTestOptionalFields({optional: 'foo', lenient: 'bar', nullable: 'baz'})\",\n      \"string\", \"foo, bar, baz\");\n\n#define ENUMERATE_OBJECT                                                                           \\\n  \"var items = [];\\n\"                                                                              \\\n  \"for (var key in object) {\\n\"                                                                    \\\n  \"  items.push(key + ': ' + object[key]);\\n\"                                                      \\\n  \"}\\n\"                                                                                            \\\n  \"items.join(', ')\"\n\n  e.expectEval(\n      \"var object = makeTestOptionalFields(undefined, undefined, null);\\n\" ENUMERATE_OBJECT,\n      \"string\", \"optional: undefined, lenient: undefined, nullable: null\");\n  e.expectEval(\"var object = makeTestOptionalFields('foo', 'bar', null);\\n\" ENUMERATE_OBJECT,\n      \"string\", \"optional: foo, lenient: bar, nullable: null\");\n  e.expectEval(\"var object = makeTestOptionalFields('foo', 'bar', 'baz');\\n\" ENUMERATE_OBJECT,\n      \"string\", \"optional: foo, lenient: bar, nullable: baz\");\n  e.expectEval(\n      \"var object = makeTestOptionalFields(undefined, undefined, 'bar');\\n\" ENUMERATE_OBJECT,\n      \"string\", \"optional: undefined, lenient: undefined, nullable: bar\");\n#undef ENUMERATE_OBJECT\n\n  e.expectEval(\"readTestAllOptionalFields({})\", \"string\", \"(absent), 321\");\n  e.expectEval(\"readTestAllOptionalFields(null)\", \"string\", \"(absent), 321\");\n  e.expectEval(\"readTestAllOptionalFields(undefined)\", \"string\", \"(absent), 321\");\n  e.expectEval(\"readTestAllOptionalFields()\", \"throws\",\n      \"TypeError: Failed to execute 'readTestAllOptionalFields' on 'OptionalContext': \"\n      \"parameter 1 is not of type 'TestAllOptionalFields'.\");\n}\n\n// ========================================================================================\nstruct MaybeContext: public ContextGlobalObject {\n\n  void test(kj::Maybe<kj::OneOf<NonCoercible<kj::String>>> arg) {}\n\n  JSG_RESOURCE_TYPE(MaybeContext) {\n    JSG_METHOD(test);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(MaybeIsolate, MaybeContext);\n\nKJ_TEST(\"maybes - don't substitute null\") {\n\n  static const auto config = JsgConfig{\n    .noSubstituteNull = true,\n  };\n\n  struct MaybeConfig {\n    operator const JsgConfig&() const {\n      return config;\n    }\n  };\n\n  // This version uses the MaybeConfig above that sets noSubstituteNull = true.\n  Evaluator<MaybeContext, MaybeIsolate, MaybeConfig> e(v8System);\n  e.expectEval(\"test({})\", \"throws\",\n      \"TypeError: Failed to execute 'test' on 'MaybeContext': parameter 1 is not \"\n      \"of type 'string'.\");\n\n  // This version uses the default JsgConfig with the noSubstituteNull = false.\n  Evaluator<MaybeContext, MaybeIsolate, JsgConfig> e2(v8System);\n  e2.expectEval(\"test({})\", \"undefined\", \"undefined\");\n}\n\n// ========================================================================================\n\nstruct OneOfContext: public ContextGlobalObject {\n  kj::String takeOneOf(kj::OneOf<double, kj::String, Ref<NumberBox>> value) {\n    if (value.is<double>()) {\n      return kj::str(\"double: \", value.get<double>());\n    } else if (value.is<kj::String>()) {\n      return kj::str(\"kj::String: \", value.get<kj::String>());\n    } else if (value.is<Ref<NumberBox>>()) {\n      return kj::str(\"NumberBox: \", value.get<Ref<NumberBox>>()->value);\n    } else {\n      return kj::str(\"none of the above -- can't get here\");\n    }\n  }\n  kj::OneOf<double, kj::String, Ref<NumberBox>> returnOneOf(\n      kj::Maybe<double> num, kj::Maybe<kj::String> str, kj::Maybe<Ref<NumberBox>> box) {\n    kj::OneOf<double, kj::String, Ref<NumberBox>> result;\n    KJ_IF_SOME(n, num) {\n      result.init<double>(n);\n    } else KJ_IF_SOME(s, str) {\n      result.init<kj::String>(kj::mv(s));\n    } else KJ_IF_SOME(b, box) {\n      result.init<Ref<NumberBox>>(b.addRef());\n    }\n    return result;\n  }\n\n  using StringOrBool = kj::OneOf<kj::String, bool>;\n  using NumberOrBool = kj::OneOf<double, bool>;\n  using StringOrNumber = kj::OneOf<kj::String, double>;\n\n  kj::String takeStringOrBool(StringOrBool value) {\n    KJ_SWITCH_ONEOF(value) {\n      KJ_CASE_ONEOF(s, kj::String) {\n        return kj::str(\"kj::String: \", s);\n      }\n      KJ_CASE_ONEOF(b, bool) {\n        return kj::str(\"bool: \", b);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n  kj::String takeNumberOrBool(NumberOrBool value) {\n    KJ_SWITCH_ONEOF(value) {\n      KJ_CASE_ONEOF(d, double) {\n        return kj::str(\"double: \", d);\n      }\n      KJ_CASE_ONEOF(b, bool) {\n        return kj::str(\"bool: \", b);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n  kj::String takeStringOrNumber(StringOrNumber value) {\n    KJ_SWITCH_ONEOF(value) {\n      KJ_CASE_ONEOF(s, kj::String) {\n        return kj::str(\"kj::String: \", s);\n      }\n      KJ_CASE_ONEOF(d, double) {\n        return kj::str(\"double: \", d);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  using NestedOneOf = kj::OneOf<double, StringOrBool>;\n\n  kj::String takeNestedOneOf(NestedOneOf value) {\n    KJ_SWITCH_ONEOF(value) {\n      KJ_CASE_ONEOF(d, double) {\n        return kj::str(\"double: \", d);\n      }\n      KJ_CASE_ONEOF(oof, kj::OneOf<kj::String, bool>) {\n        KJ_SWITCH_ONEOF(oof) {\n          KJ_CASE_ONEOF(s, kj::String) {\n            return kj::str(\"kj::String: \", s);\n          }\n          KJ_CASE_ONEOF(b, bool) {\n            return kj::str(\"bool: \", b);\n          }\n        }\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  JSG_RESOURCE_TYPE(OneOfContext) {\n    JSG_NESTED_TYPE(NumberBox);\n    JSG_METHOD(takeOneOf);\n    JSG_METHOD(returnOneOf);\n    JSG_METHOD(takeStringOrBool);\n    JSG_METHOD(takeNumberOrBool);\n    JSG_METHOD(takeStringOrNumber);\n    JSG_METHOD(takeNestedOneOf);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(OneOfIsolate, OneOfContext, NumberBox);\n\nKJ_TEST(\"OneOf\") {\n  Evaluator<OneOfContext, OneOfIsolate> e(v8System);\n  e.expectEval(\"takeOneOf(123)\", \"string\", \"double: 123\");\n  e.expectEval(\"takeOneOf('foo')\", \"string\", \"kj::String: foo\");\n  e.expectEval(\"takeOneOf(new NumberBox(321))\", \"string\", \"NumberBox: 321\");\n  e.expectEval(\"takeOneOf(undefined)\", \"string\", \"kj::String: undefined\");\n\n  e.expectEval(\"returnOneOf(123, null, null)\", \"number\", \"123\");\n  e.expectEval(\"returnOneOf(null, 'foo', null)\", \"string\", \"foo\");\n  e.expectEval(\"returnOneOf(null, null, new NumberBox(321)).value\", \"number\", \"321\");\n  e.expectEval(\"returnOneOf(null, null, null)\", \"undefined\", \"undefined\");\n\n  e.expectEval(\"takeStringOrBool(123)\", \"string\", \"kj::String: 123\");\n  e.expectEval(\"takeStringOrBool('123')\", \"string\", \"kj::String: 123\");\n  e.expectEval(\"takeStringOrBool(true)\", \"string\", \"bool: true\");\n\n  e.expectEval(\"takeNumberOrBool(123)\", \"string\", \"double: 123\");\n  e.expectEval(\"takeNumberOrBool('123')\", \"string\", \"double: 123\");\n  e.expectEval(\"takeNumberOrBool(true)\", \"string\", \"bool: true\");\n\n  e.expectEval(\"takeStringOrNumber(123)\", \"string\", \"double: 123\");\n  e.expectEval(\"takeStringOrNumber('123')\", \"string\", \"kj::String: 123\");\n  e.expectEval(\"takeStringOrNumber(true)\", \"string\", \"kj::String: true\");\n\n  e.expectEval(\"takeNestedOneOf(123)\", \"string\", \"double: 123\");\n  e.expectEval(\"takeNestedOneOf('123')\", \"string\", \"kj::String: 123\");\n  e.expectEval(\"takeNestedOneOf(true)\", \"string\", \"bool: true\");\n  e.expectEval(\"takeNestedOneOf(undefined)\", \"string\", \"kj::String: undefined\");\n  e.expectEval(\"takeNestedOneOf(null)\", \"string\", \"kj::String: null\");\n  e.expectEval(\"takeNestedOneOf({})\", \"string\", \"kj::String: [object Object]\");\n}\n\n// ========================================================================================\n\nstruct DictContext: public ContextGlobalObject {\n  kj::String takeDict(Dict<Ref<NumberBox>> dict) {\n    return kj::strArray(\n        KJ_MAP(f, dict.fields) { return kj::str(f.name, \": \", f.value->value); }, \", \");\n  }\n  kj::String takeDictOfFunctions(Lock& js, Dict<Function<int()>> dict) {\n    return kj::strArray(\n        KJ_MAP(f, dict.fields) { return kj::str(f.name, \": \", f.value(js)); }, \", \");\n  }\n  Dict<Ref<NumberBox>> returnDict(jsg::Lock& js) {\n    auto builder = kj::heapArrayBuilder<Dict<Ref<NumberBox>>::Field>(3);\n    builder.add(Dict<Ref<NumberBox>>::Field{kj::str(\"foo\"), js.alloc<NumberBox>(123)});\n    builder.add(Dict<Ref<NumberBox>>::Field{kj::str(\"bar\"), js.alloc<NumberBox>(456)});\n    builder.add(Dict<Ref<NumberBox>>::Field{kj::str(\"baz\"), js.alloc<NumberBox>(789)});\n    return {builder.finish()};\n  }\n\n  JSG_RESOURCE_TYPE(DictContext) {\n    JSG_NESTED_TYPE(NumberBox);\n    JSG_METHOD(takeDict);\n    JSG_METHOD(takeDictOfFunctions);\n    JSG_METHOD(returnDict);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(DictIsolate, DictContext, NumberBox);\n\nKJ_TEST(\"dicts\") {\n  Evaluator<DictContext, DictIsolate> e(v8System);\n  e.expectEval(\n      \"takeDict({foo: new NumberBox(123), bar: new NumberBox(456), baz: new NumberBox(789)})\",\n      \"string\", \"foo: 123, bar: 456, baz: 789\");\n  e.expectEval(\"var dict = returnDict();\\n\"\n               \"[dict.foo.value, dict.bar.value, dict.baz.value].join(', ')\",\n      \"string\", \"123, 456, 789\");\n\n  e.expectEval(\"takeDict({foo: new NumberBox(123), bar: 456, baz: new NumberBox(789)})\", \"throws\",\n      \"TypeError: Incorrect type for map entry 'bar': the provided value is not of type \"\n      \"'NumberBox'.\");\n\n  e.expectEval(\"takeDictOfFunctions({\\n\"\n               \"  foo() { return this.bar() + 123; },\\n\"\n               \"  bar() { return 456; }\\n\"\n               \"})\",\n      \"string\", \"foo: 579, bar: 456\");\n}\n\n// ========================================================================================\n\nstruct IntContext: public ContextGlobalObject {\n  kj::String takeInt(int i) {\n    return kj::str(\"int: \", i);\n  }\n  int returnInt() {\n    return 123;\n  }\n  JSG_RESOURCE_TYPE(IntContext) {\n    JSG_METHOD(takeInt);\n    JSG_METHOD(returnInt);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(IntIsolate, IntContext);\n\nKJ_TEST(\"integers\") {\n  Evaluator<IntContext, IntIsolate> e(v8System);\n  e.expectEval(\"takeInt(123)\", \"string\", \"int: 123\");\n  e.expectEval(\"returnInt()\", \"number\", \"123\");\n\n  e.expectEval(\"takeInt(1)\", \"string\", \"int: 1\");\n  e.expectEval(\"takeInt(-1)\", \"string\", \"int: -1\");\n  e.expectEval(\"takeInt(123.5)\", \"string\", \"int: 123\");\n  e.expectEval(\"takeInt(null)\", \"string\", \"int: 0\");\n  e.expectEval(\"takeInt(undefined)\", \"string\", \"int: 0\");\n  e.expectEval(\"takeInt(Number.NaN)\", \"string\", \"int: 0\");\n  e.expectEval(\"takeInt(Number.POSITIVE_INFINITY)\", \"string\", \"int: 0\");\n  e.expectEval(\"takeInt(Number.NEGATIVE_INFINITY)\", \"string\", \"int: 0\");\n  e.expectEval(\"takeInt({})\", \"string\", \"int: 0\");\n\n  e.expectEval(\"takeInt(2147483647)\", \"string\", \"int: 2147483647\");\n  e.expectEval(\"takeInt(-2147483648)\", \"string\", \"int: -2147483648\");\n\n  e.expectEval(\"takeInt(2147483648)\", \"throws\",\n      \"TypeError: Value out of range. Must be between \"\n      \"-2147483648 and 2147483647 (inclusive).\");\n  e.expectEval(\"takeInt(-2147483649)\", \"throws\",\n      \"TypeError: Value out of range. Must be between \"\n      \"-2147483648 and 2147483647 (inclusive).\");\n  e.expectEval(\"takeInt(Number.MAX_SAFE_INTEGER)\", \"throws\",\n      \"TypeError: Value out of range. Must be between \"\n      \"-2147483648 and 2147483647 (inclusive).\");\n  e.expectEval(\"takeInt(-Number.MAX_SAFE_INTEGER)\", \"throws\",\n      \"TypeError: Value out of range. Must be between \"\n      \"-2147483648 and 2147483647 (inclusive).\");\n}\n\n// ========================================================================================\nstruct Uint32Context: public ContextGlobalObject {\n  kj::String takeUint32(uint32_t i) {\n    return kj::str(\"uint32_t: \", i);\n  }\n  uint32_t returnUint32() {\n    return 123;\n  }\n  uint32_t takeOneOfUint32(kj::OneOf<kj::String, uint32_t> i) {\n    KJ_SWITCH_ONEOF(i) {\n      KJ_CASE_ONEOF(str, kj::String) {\n        KJ_FAIL_ASSERT(\"Should not have been interpreted as a string.\");\n      }\n      KJ_CASE_ONEOF(num, uint32_t) {\n        return num;\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  JSG_RESOURCE_TYPE(Uint32Context) {\n    JSG_METHOD(takeUint32);\n    JSG_METHOD(takeOneOfUint32);\n    JSG_METHOD(returnUint32);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(Uint32Isolate, Uint32Context);\nKJ_TEST(\"unsigned integers\") {\n  Evaluator<Uint32Context, Uint32Isolate> e(v8System);\n  e.expectEval(\"takeUint32(123)\", \"string\", \"uint32_t: 123\");\n  e.expectEval(\"returnUint32()\", \"number\", \"123\");\n\n  e.expectEval(\"takeUint32(1)\", \"string\", \"uint32_t: 1\");\n  e.expectEval(\"takeUint32(123.5)\", \"string\", \"uint32_t: 123\");\n  e.expectEval(\"takeUint32(null)\", \"string\", \"uint32_t: 0\");\n\n  e.expectEval(\"takeOneOfUint32(1)\", \"number\", \"1\");\n\n  e.expectEval(\"takeUint32(-1)\", \"throws\",\n      \"TypeError: The value cannot be converted because it is negative and this \"\n      \"API expects a positive number.\");\n  e.expectEval(\"takeUint32({})\", \"throws\",\n      \"TypeError: The value cannot be converted because it is not an integer.\");\n  e.expectEval(\"takeUint32(undefined)\", \"throws\",\n      \"TypeError: The value cannot be converted because it is not an integer.\");\n  e.expectEval(\"takeUint32(Number.NaN)\", \"throws\",\n      \"TypeError: The value cannot be converted because it is not an integer.\");\n  e.expectEval(\"takeUint32(Number.POSITIVE_INFINITY)\", \"throws\",\n      \"TypeError: The value cannot be converted because it is not an integer.\");\n  e.expectEval(\"takeUint32(Number.NEGATIVE_INFINITY)\", \"throws\",\n      \"TypeError: The value cannot be converted because it is not an integer.\");\n\n  e.expectEval(\"takeUint32(4294967295)\", \"string\", \"uint32_t: 4294967295\");\n\n  e.expectEval(\"takeUint32(4294967296)\", \"throws\",\n      \"TypeError: Value out of range. Must be less than or equal to 4294967295.\");\n  e.expectEval(\"takeUint32(Number.MAX_SAFE_INTEGER)\", \"throws\",\n      \"TypeError: Value out of range. Must be less than or equal to 4294967295.\");\n}\n\n// ========================================================================================\nstruct Uint64Context: public ContextGlobalObject {\n  kj::String takeUint64(uint64_t i) {\n    return kj::str(\"uint64_t: \", i);\n  }\n  uint64_t returnUint64() {\n    return 123;\n  }\n  kj::String takeInt64(int64_t i) {\n    return kj::str(\"int64_t: \", i);\n  }\n  uint64_t takeOneOfUint64(kj::OneOf<kj::String, uint64_t> i) {\n    KJ_SWITCH_ONEOF(i) {\n      KJ_CASE_ONEOF(str, kj::String) {\n        KJ_FAIL_ASSERT(\"Should not have been interpreted as a string.\");\n      }\n      KJ_CASE_ONEOF(num, uint64_t) {\n        return num;\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n  int64_t takeOneOfInt64(kj::OneOf<kj::String, int64_t> i) {\n    KJ_SWITCH_ONEOF(i) {\n      KJ_CASE_ONEOF(str, kj::String) {\n        KJ_FAIL_ASSERT(\"Should not have been interpreted as a string.\");\n      }\n      KJ_CASE_ONEOF(num, int64_t) {\n        return num;\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n  int64_t returnInt64() {\n    return 123;\n  }\n  JSG_RESOURCE_TYPE(Uint64Context) {\n    JSG_METHOD(takeUint64);\n    JSG_METHOD(takeOneOfUint64);\n    JSG_METHOD(takeOneOfInt64);\n    JSG_METHOD(returnUint64);\n    JSG_METHOD(takeInt64);\n    JSG_METHOD(returnInt64);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(Uint64Isolate, Uint64Context);\nKJ_TEST(\"bigints\") {\n  Evaluator<Uint64Context, Uint64Isolate> e(v8System);\n  e.expectEval(\"takeUint64(123)\", \"string\", \"uint64_t: 123\");\n  e.expectEval(\"takeUint64(123n)\", \"string\", \"uint64_t: 123\");\n  e.expectEval(\"takeUint64(1n)\", \"string\", \"uint64_t: 1\");\n  e.expectEval(\"takeUint64(1)\", \"string\", \"uint64_t: 1\");\n  e.expectEval(\"takeUint64(123.5)\", \"string\", \"uint64_t: 123\");\n  e.expectEval(\"takeUint64(null)\", \"string\", \"uint64_t: 0\");\n  e.expectEval(\"takeUint64(BigInt(1))\", \"string\", \"uint64_t: 1\");\n\n  e.expectEval(\"takeOneOfUint64(1)\", \"bigint\", \"1\");\n  e.expectEval(\"takeOneOfUint64(1n)\", \"bigint\", \"1\");\n\n  e.expectEval(\"takeOneOfInt64(1)\", \"bigint\", \"1\");\n  e.expectEval(\"takeOneOfInt64(1n)\", \"bigint\", \"1\");\n\n  e.expectEval(\"takeInt64(123)\", \"string\", \"int64_t: 123\");\n  e.expectEval(\"takeInt64(123n)\", \"string\", \"int64_t: 123\");\n\n  e.expectEval(\"takeInt64(1n)\", \"string\", \"int64_t: 1\");\n  e.expectEval(\"takeInt64(-1n)\", \"string\", \"int64_t: -1\");\n  e.expectEval(\"takeInt64(1)\", \"string\", \"int64_t: 1\");\n  e.expectEval(\"takeInt64(-1)\", \"string\", \"int64_t: -1\");\n  e.expectEval(\"takeInt64(123.5)\", \"string\", \"int64_t: 123\");\n  e.expectEval(\"takeInt64(null)\", \"string\", \"int64_t: 0\");\n  e.expectEval(\"takeInt64('1')\", \"string\", \"int64_t: 1\");\n  e.expectEval(\"takeInt64(BigInt(-1))\", \"string\", \"int64_t: -1\");\n\n  e.expectEval(\"returnUint64()\", \"bigint\", \"123\");\n  e.expectEval(\"returnInt64()\", \"bigint\", \"123\");\n\n  e.expectEval(\"takeUint64(-1)\", \"throws\",\n      \"TypeError: The value cannot be converted because it is negative and this \"\n      \"API expects a positive bigint.\");\n  e.expectEval(\"takeUint64(-1n)\", \"throws\",\n      \"TypeError: The value cannot be converted because it is either negative and \"\n      \"this API expects a positive bigint, or the value would be truncated.\");\n\n  e.expectEval(\"takeUint64(undefined)\", \"throws\",\n      \"TypeError: The value cannot be converted because it is not an integer.\");\n  e.expectEval(\"takeInt64(undefined)\", \"throws\",\n      \"TypeError: The value cannot be converted because it is not an integer.\");\n  e.expectEval(\"takeInt64('hello')\", \"throws\",\n      \"TypeError: The value cannot be converted because it is not an integer.\");\n  e.expectEval(\"takeInt64({})\", \"throws\",\n      \"TypeError: The value cannot be converted because it is not an integer.\");\n  e.expectEval(\"takeInt64(Number.NaN)\", \"throws\",\n      \"TypeError: The value cannot be converted because it is not an integer.\");\n  e.expectEval(\"takeInt64(Number.POSITIVE_INFINITY)\", \"throws\",\n      \"TypeError: The value cannot be converted because it is not an integer.\");\n  e.expectEval(\"takeInt64(Number.NEGATIVE_INFINITY)\", \"throws\",\n      \"TypeError: The value cannot be converted because it is not an integer.\");\n\n  e.expectEval(\"takeUint64('hello')\", \"throws\",\n      \"TypeError: The value cannot be converted because it is not an integer.\");\n  e.expectEval(\"takeUint64({})\", \"throws\",\n      \"TypeError: The value cannot be converted because it is not an integer.\");\n  e.expectEval(\"takeUint64(Number.NaN)\", \"throws\",\n      \"TypeError: The value cannot be converted because it is not an integer.\");\n  e.expectEval(\"takeUint64(Number.POSITIVE_INFINITY)\", \"throws\",\n      \"TypeError: The value cannot be converted because it is not an integer.\");\n  e.expectEval(\"takeUint64(Number.NEGATIVE_INFINITY)\", \"throws\",\n      \"TypeError: The value cannot be converted because it is not an integer.\");\n\n  e.expectEval(\"takeUint64(18446744073709551615n)\", \"string\", \"uint64_t: 18446744073709551615\");\n\n  e.expectEval(\"takeUint64(18446744073709551616n)\", \"throws\",\n      \"TypeError: The value cannot be converted because it is either negative \"\n      \"and this API expects a positive bigint, or the value would be truncated.\");\n\n  e.expectEval(\"takeInt64(9223372036854775807n)\", \"string\", \"int64_t: 9223372036854775807\");\n  e.expectEval(\"takeInt64(9223372036854775808n)\", \"throws\",\n      \"TypeError: The value cannot be converted because it would be truncated.\");\n}\n\n// ========================================================================================\n\nstruct Int8Context: public ContextGlobalObject {\n  kj::String takeInt8(int8_t i) {\n    return kj::str(\"int8_t: \", i);\n  }\n  kj::String takeUint8(uint8_t i) {\n    return kj::str(\"uint8_t: \", i);\n  }\n  int8_t returnInt8() {\n    return 123;\n  }\n  uint8_t returnUint8() {\n    return 123;\n  }\n  JSG_RESOURCE_TYPE(Int8Context) {\n    JSG_METHOD(takeInt8);\n    JSG_METHOD(takeUint8);\n    JSG_METHOD(returnInt8);\n    JSG_METHOD(returnUint8);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(Int8Isolate, Int8Context);\n\nKJ_TEST(\"int8 integers\") {\n  Evaluator<Int8Context, Int8Isolate> e(v8System);\n  e.expectEval(\"takeInt8(123)\", \"string\", \"int8_t: 123\");\n  e.expectEval(\"takeUint8(123)\", \"string\", \"uint8_t: 123\");\n  e.expectEval(\"returnInt8()\", \"number\", \"123\");\n  e.expectEval(\"returnUint8()\", \"number\", \"123\");\n\n  e.expectEval(\"takeInt8(1)\", \"string\", \"int8_t: 1\");\n  e.expectEval(\"takeInt8(-1)\", \"string\", \"int8_t: -1\");\n  e.expectEval(\"takeInt8(123.5)\", \"string\", \"int8_t: 123\");\n\n  e.expectEval(\"takeInt8(127)\", \"string\", \"int8_t: 127\");\n  e.expectEval(\"takeInt8(-128)\", \"string\", \"int8_t: -128\");\n  e.expectEval(\"takeUint8(255)\", \"string\", \"uint8_t: 255\");\n\n  e.expectEval(\"takeUint8(-1)\", \"throws\",\n      \"TypeError: The value cannot be converted because it is negative and this \"\n      \"API expects a positive number.\");\n  e.expectEval(\"takeInt8(32768)\", \"throws\",\n      \"TypeError: Value out of range. Must be between \"\n      \"-128 and 127 (inclusive).\");\n  e.expectEval(\"takeInt8(-32769)\", \"throws\",\n      \"TypeError: Value out of range. Must be between \"\n      \"-128 and 127 (inclusive).\");\n  e.expectEval(\"takeInt8(Number.MAX_SAFE_INTEGER)\", \"throws\",\n      \"TypeError: Value out of range. Must be between \"\n      \"-128 and 127 (inclusive).\");\n  e.expectEval(\"takeInt8(-Number.MAX_SAFE_INTEGER)\", \"throws\",\n      \"TypeError: Value out of range. Must be between \"\n      \"-128 and 127 (inclusive).\");\n}\n\n// ========================================================================================\n\nstruct Int16Context: public ContextGlobalObject {\n  kj::String takeInt16(int16_t i) {\n    return kj::str(\"int16_t: \", i);\n  }\n  kj::String takeUint16(uint16_t i) {\n    return kj::str(\"uint16_t: \", i);\n  }\n  int16_t returnInt16() {\n    return 123;\n  }\n  uint16_t returnUint16() {\n    return 123;\n  }\n  JSG_RESOURCE_TYPE(Int16Context) {\n    JSG_METHOD(takeInt16);\n    JSG_METHOD(takeUint16);\n    JSG_METHOD(returnInt16);\n    JSG_METHOD(returnUint16);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(Int16Isolate, Int16Context);\n\nKJ_TEST(\"int16 integers\") {\n  Evaluator<Int16Context, Int16Isolate> e(v8System);\n  e.expectEval(\"takeInt16(123)\", \"string\", \"int16_t: 123\");\n  e.expectEval(\"takeUint16(123)\", \"string\", \"uint16_t: 123\");\n  e.expectEval(\"returnInt16()\", \"number\", \"123\");\n  e.expectEval(\"returnUint16()\", \"number\", \"123\");\n\n  e.expectEval(\"takeInt16(1)\", \"string\", \"int16_t: 1\");\n  e.expectEval(\"takeInt16(-1)\", \"string\", \"int16_t: -1\");\n  e.expectEval(\"takeInt16(123.5)\", \"string\", \"int16_t: 123\");\n\n  e.expectEval(\"takeInt16(32767)\", \"string\", \"int16_t: 32767\");\n  e.expectEval(\"takeInt16(-32768)\", \"string\", \"int16_t: -32768\");\n  e.expectEval(\"takeUint16(65535)\", \"string\", \"uint16_t: 65535\");\n\n  e.expectEval(\"takeUint16(-1)\", \"throws\",\n      \"TypeError: The value cannot be converted because it is negative and this \"\n      \"API expects a positive number.\");\n  e.expectEval(\"takeInt16(32768)\", \"throws\",\n      \"TypeError: Value out of range. Must be between \"\n      \"-32768 and 32767 (inclusive).\");\n  e.expectEval(\"takeInt16(-32769)\", \"throws\",\n      \"TypeError: Value out of range. Must be between \"\n      \"-32768 and 32767 (inclusive).\");\n  e.expectEval(\"takeInt16(Number.MAX_SAFE_INTEGER)\", \"throws\",\n      \"TypeError: Value out of range. Must be between \"\n      \"-32768 and 32767 (inclusive).\");\n  e.expectEval(\"takeInt16(-Number.MAX_SAFE_INTEGER)\", \"throws\",\n      \"TypeError: Value out of range. Must be between \"\n      \"-32768 and 32767 (inclusive).\");\n}\n\n// ========================================================================================\n\nstruct DoubleContext: public ContextGlobalObject {\n  kj::String takeDouble(double d) {\n    return kj::str(\"double: \", d);\n  }\n  double returnDouble() {\n    return 123.5;\n  }\n\n  JSG_RESOURCE_TYPE(DoubleContext) {\n    JSG_NESTED_TYPE(NumberBox);\n    JSG_METHOD(takeDouble);\n    JSG_METHOD(returnDouble);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(DoubleIsolate, DoubleContext, NumberBox);\n\nKJ_TEST(\"floating points\") {\n  Evaluator<DoubleContext, DoubleIsolate> e(v8System);\n  e.expectEval(\"takeDouble(123)\", \"string\", \"double: 123\");\n  e.expectEval(\"takeDouble(123.5)\", \"string\", \"double: 123.5\");\n  e.expectEval(\"takeDouble('123')\", \"string\", \"double: 123\");\n  e.expectEval(\"takeDouble(' \\\\r\\\\n123')\", \"string\", \"double: 123\");\n  e.expectEval(\"takeDouble('0x7b')\", \"string\", \"double: 123\");\n  e.expectEval(\"takeDouble(true)\", \"string\", \"double: 1\");\n  e.expectEval(\"takeDouble(Number.MAX_SAFE_INTEGER)\", \"string\", \"double: 9007199254740991\");\n  e.expectEval(\"takeDouble({ valueOf: function() { return 456.7; } })\", \"string\", \"double: 456.7\");\n  e.expectEval(\"returnDouble()\", \"number\", \"123.5\");\n\n  e.expectEval(\"takeDouble([Symbol.iterator])\", \"throws\",\n      \"TypeError: Cannot convert a Symbol value to a string\");\n  e.expectEval(\"takeDouble('123asdf')\", \"string\", \"double: nan\");\n  e.expectEval(\"takeDouble('asdf123')\", \"string\", \"double: nan\");\n  e.expectEval(\"takeDouble(null)\", \"string\", \"double: 0\");\n  e.expectEval(\"takeDouble(undefined)\", \"string\", \"double: nan\");\n  e.expectEval(\"takeDouble(Number.NaN)\", \"string\", \"double: nan\");\n  e.expectEval(\"takeDouble(Number.POSITIVE_INFINITY)\", \"string\", \"double: inf\");\n  e.expectEval(\"takeDouble(Number.NEGATIVE_INFINITY)\", \"string\", \"double: -inf\");\n  e.expectEval(\"takeDouble({})\", \"string\", \"double: nan\");\n  e.expectEval(\"takeDouble(new NumberBox(321))\", \"string\", \"double: nan\");\n}\n\n// ========================================================================================\n\nstruct StringContext: public ContextGlobalObject {\n  kj::String takeString(kj::String s) {\n    return kj::mv(s);\n  }\n  JSG_RESOURCE_TYPE(StringContext) {\n    JSG_METHOD(takeString);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(StringIsolate, StringContext);\n\nKJ_TEST(\"kj::Strings\") {\n  Evaluator<StringContext, StringIsolate> e(v8System);\n  e.expectEval(\"takeString(false)\", \"string\", \"false\");\n  e.expectEval(\"takeString(true)\", \"string\", \"true\");\n  e.expectEval(\"takeString(123)\", \"string\", \"123\");\n  e.expectEval(\"takeString(Number.NaN)\", \"string\", \"NaN\");\n  e.expectEval(\"takeString(Number.POSITIVE_INFINITY)\", \"string\", \"Infinity\");\n  e.expectEval(\"takeString(null)\", \"string\", \"null\");\n  e.expectEval(\"takeString(undefined)\", \"string\", \"undefined\");\n  e.expectEval(\"takeString('an actual string')\", \"string\", \"an actual string\");\n  e.expectEval(\n      \"takeString({ toString: function() { return 'toString()ed'; } })\", \"string\", \"toString()ed\");\n}\n\n// ========================================================================================\nstruct USVStringContext: public ContextGlobalObject {\n  jsg::USVString takeUSVString(jsg::USVString s) {\n    return kj::mv(s);\n  }\n\n  JSG_RESOURCE_TYPE(USVStringContext) {\n    JSG_METHOD(takeUSVString);\n  }\n};\n\nJSG_DECLARE_ISOLATE_TYPE(USVStringIsolate, USVStringContext);\n\nKJ_TEST(\"jsg::USVStrings\") {\n  Evaluator<USVStringContext, USVStringIsolate> e(v8System);\n  e.expectEval(\"takeUSVString('hello world')\", \"string\", \"hello world\");\n  // From JS to C++: Characters forbidden in UTF-8, like unpaired surrogates, are replaced with the Unicode replacement character\n  // From C++ to JS: The replacement character is valid and is converted from UTF-8 to UTF-16\n  e.expectEval(\"takeUSVString('\\\\uD835x')\", \"string\", u8\"\\uFFFDx\");\n  e.expectEval(\"takeUSVString('\\\\uD835x\\\\uDC53')\", \"string\", u8\"\\uFFFDx\\uFFFD\");\n}\n\n// ========================================================================================\nstruct DOMStringContext: public ContextGlobalObject {\n  jsg::DOMString takeDOMString(jsg::DOMString s) {\n    return kj::mv(s);\n  }\n\n  JSG_RESOURCE_TYPE(DOMStringContext) {\n    JSG_METHOD(takeDOMString);\n  }\n};\n\nJSG_DECLARE_ISOLATE_TYPE(DOMStringIsolate, DOMStringContext);\n\nKJ_TEST(\"jsg::DOMStrings\") {\n  Evaluator<DOMStringContext, DOMStringIsolate> e(v8System);\n  e.expectEval(\"takeDOMString('hello world')\", \"string\", \"hello world\");\n  // From JS to C++: Characters forbidden in UTF-8, like unpaired surrogates, are encoded anyway making this a WTF-8 encoded value.\n  // From C++ to JS: Each invalid byte in the encoding of the unpaired surrogate is replaced with the Unicode replacement character.\n  // WTF, that's 3 replacement characters for each unpaired surrogate.\n  // TODO(someday): Fix the conversion back into a V8 string such that we get back the WTF-16 value we had originally (an unpaired surrogate)\n  e.expectEval(\"[...takeDOMString('\\\\uD835x')]\", \"object\", u8\"\\uFFFD,\\uFFFD,\\uFFFD,x\");\n  e.expectEval(\"[...takeDOMString('\\\\uD835x\\\\uDC53')]\", \"object\",\n      u8\"\\uFFFD,\\uFFFD,\\uFFFD,x,\\uFFFD,\\uFFFD,\\uFFFD\");\n}\n\n// ========================================================================================\n\nstruct RawContext: public ContextGlobalObject {\n  struct TwoValues {\n    Value $foo;\n    Value $bar;\n    JSG_STRUCT($foo, $bar);\n  };\n  TwoValues twoValues(Value foo, Value bar) {\n    return {kj::mv(foo), kj::mv(bar)};\n  }\n  JSG_RESOURCE_TYPE(RawContext) {\n    JSG_METHOD(twoValues);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(RawIsolate, RawContext, RawContext::TwoValues);\n\nKJ_TEST(\"Raw Values\") {\n  Evaluator<RawContext, RawIsolate> e(v8System);\n  e.expectEval(\"JSON.stringify(twoValues({baz: 123}, 'abcd'))\",\n\n      \"string\", \"{\\\"foo\\\":{\\\"baz\\\":123},\\\"bar\\\":\\\"abcd\\\"}\");\n}\n\n// ========================================================================================\n\nstruct DateContext: public ContextGlobalObject {\n  kj::Date takeDate(kj::Date date) {\n    return date;\n  }\n  JSG_RESOURCE_TYPE(DateContext) {\n    JSG_METHOD(takeDate);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(DateIsolate, DateContext);\n\nKJ_TEST(\"Date Values\") {\n  Evaluator<DateContext, DateIsolate> e(v8System);\n  e.expectEval(\"takeDate(new Date('2022-01-22T00:54:57.893Z')).toUTCString()\", \"string\",\n      \"Sat, 22 Jan 2022 00:54:57 GMT\");\n  e.expectEval(\"takeDate(12345).valueOf()\", \"number\", \"12345\");\n  e.expectEval(\"takeDate(8640000000000000).valueOf()\", \"throws\",\n      \"TypeError: This API doesn't support dates after 2189.\"),\n      e.expectEval(\"takeDate(-8640000000000000).valueOf()\", \"throws\",\n          \"TypeError: This API doesn't support dates before 1687.\"),\n      e.expectEval(\"takeDate(1/0)\", \"throws\",\n          \"TypeError: The value cannot be converted because it is not a valid Date.\"),\n      e.expectEval(\"takeDate(new Date(1/0))\", \"throws\",\n          \"TypeError: The value cannot be converted because it is not a valid Date.\"),\n      e.expectEval(\"takeDate(new Date('1800-01-22T00:54:57.893Z')).toUTCString()\", \"string\",\n          \"Wed, 22 Jan 1800 00:54:57 GMT\");\n  e.expectEval(\"takeDate('2022-01-22T00:54:57.893Z')\", \"throws\",\n      \"TypeError: Failed to execute 'takeDate' on 'DateContext': parameter \"\n      \"1 is not of type 'date'.\");\n}\n\n// ========================================================================================\n\nstruct ArrayContext: public ContextGlobalObject {\n  kj::Array<int> takeArray(kj::Array<int> array) {\n    // The ArrayWrapper uses a stack array with a max size of 64. This is just a\n    // quick test to ensure that arrays larger than that are properly supported.\n    KJ_ASSERT(array.size() == 65);\n    KJ_ASSERT(array[64] == 1);\n    return kj::mv(array);\n  }\n  kj::Array<int> takeArguments(int i, Arguments<int> array) {\n    KJ_ASSERT(i == 123);\n    return kj::mv(array);\n  }\n  JSG_RESOURCE_TYPE(ArrayContext) {\n    JSG_METHOD(takeArray);\n    JSG_METHOD(takeArguments);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(ArrayIsolate, ArrayContext);\n\nKJ_TEST(\"Array Values\") {\n  Evaluator<ArrayContext, ArrayIsolate> e(v8System);\n  e.expectEval(\"m = Array(65); m[64] = 1; takeArray(m)[64]\", \"number\", \"1\");\n\n  e.expectEval(\"takeArguments(123, 456, 789, 321).join(', ')\", \"string\", \"456, 789, 321\");\n}\n\n// ========================================================================================\n\nstruct SetContext: public ContextGlobalObject {\n  kj::HashSet<kj::String> takeSet(kj::HashSet<kj::String> set) {\n    KJ_ASSERT(set.contains(\"42\"_kj));\n    return kj::mv(set);\n  }\n  kj::HashSet<kj::String> returnStrings(int i, int j, int k) {\n    auto result = kj::HashSet<kj::String>();\n    result.insert(kj::str(i));\n    result.insert(kj::str(j));\n    result.insert(kj::str(k));\n    return kj::mv(result);\n  }\n  JSG_RESOURCE_TYPE(SetContext) {\n    JSG_METHOD(takeSet);\n    JSG_METHOD(returnStrings);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(SetIsolate, SetContext);\n\nKJ_TEST(\"Set Values\") {\n  Evaluator<SetContext, SetIsolate> e(v8System);\n  e.expectEval(\"m = new Set(); m.add('42'); takeSet(m).has('42')\", \"boolean\", \"true\");\n  e.expectEval(\n      \"const toString = () => '42'; takeSet(new Set([{ toString }, { toString }])).has('42')\",\n      \"throws\", \"TypeError: Duplicate values in the set after unwrapping.\");\n\n  e.expectEval(\"returnStrings(123, 1024, 456).has('1024')\", \"boolean\", \"true\");\n}\n\n// ========================================================================================\n\nstruct SequenceContext: public ContextGlobalObject {\n  Sequence<kj::String> testSequence(Sequence<kj::String> sequence) {\n    KJ_ASSERT(sequence.size() == 2);\n    KJ_ASSERT(sequence[0] == \"a\");\n    KJ_ASSERT(sequence[1] == \"b\");\n    return kj::mv(sequence);\n  }\n\n  Sequence<int> testInt(Sequence<int> sequence) {\n    KJ_ASSERT(sequence.size(), 2);\n    return kj::mv(sequence);\n  }\n\n  struct Foo {\n    kj::String a;\n    JSG_STRUCT(a);\n  };\n\n  Sequence<Foo> testFoo(Sequence<Foo> sequence) {\n    KJ_ASSERT(sequence.size() == 1);\n    return kj::mv(sequence);\n  }\n\n  // Because the kj::OneOf lists kj::String separately, and because JavaScript\n  // strings are technically iterable, we want to make sure that the Sequence\n  // ignores strings.\n  bool oneof(kj::OneOf<kj::String, Sequence<kj::String>> input) {\n    KJ_SWITCH_ONEOF(input) {\n      KJ_CASE_ONEOF(str, kj::String) {\n        KJ_ASSERT(str == \"aa\");\n        return true;\n      }\n      KJ_CASE_ONEOF(seq, Sequence<kj::String>) {\n        KJ_ASSERT(seq[0] == \"b\");\n        KJ_ASSERT(seq[1] == \"b\");\n        return true;\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  JSG_RESOURCE_TYPE(SequenceContext) {\n    JSG_METHOD(testSequence);\n    JSG_METHOD(testInt);\n    JSG_METHOD(testFoo);\n    JSG_METHOD(oneof);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(SequenceIsolate, SequenceContext, SequenceContext::Foo);\n\nKJ_TEST(\"Sequence Values\") {\n  Evaluator<SequenceContext, SequenceIsolate> e(v8System);\n  e.expectEval(\"testSequence(['a', 'b']).join('')\", \"string\", \"ab\");\n  e.expectEval(\"const val = {*[Symbol.iterator]() { yield 'a'; yield 'b'; }};\"\n               \"testSequence(val).join('')\",\n      \"string\", \"ab\");\n  e.expectEval(\"testInt([1,2]).join('')\", \"string\", \"12\");\n  e.expectEval(\"testInt([1,'2']).join('')\", \"string\", \"12\");\n  e.expectEval(\"testInt([1,'a']).join('')\", \"string\", \"10\");\n  e.expectEval(\"testInt([1,null]).join('')\", \"string\", \"10\");\n  e.expectEval(\"testInt([1,NaN]).join('')\", \"string\", \"10\");\n  e.expectEval(\"testFoo([{a:'a'}])[0].a\", \"string\", \"a\");\n  e.expectEval(\"oneof('aa')\", \"boolean\", \"true\");\n  e.expectEval(\"oneof(['b', 'b'])\", \"boolean\", \"true\");\n  e.expectEval(\"testFoo({a:'a'})\", \"throws\",\n      \"TypeError: Failed to execute 'testFoo' on 'SequenceContext': parameter 1 is not of type 'Sequence'.\");\n}\n\n// ========================================================================================\n\nstruct NonCoercibleContext: public ContextGlobalObject {\n  template <CoercibleType T>\n  bool test(NonCoercible<T>) {\n    return true;\n  }\n\n  template <CoercibleType T>\n  bool testCoerced(T) {\n    return true;\n  }\n\n  bool testMaybeString(Optional<NonCoercible<kj::String>> value) {\n    KJ_IF_SOME(v, value) {\n      KJ_ASSERT(v.value != \"null\"_kj);\n    }\n    return true;\n  }\n\n  bool testMaybeStringCoerced(Optional<kj::String> value) {\n    KJ_ASSERT(KJ_ASSERT_NONNULL(value) == \"null\"_kj);\n    return true;\n  }\n\n  bool testOneOf(kj::OneOf<NonCoercible<bool>, NonCoercible<kj::String>> value) {\n    return true;\n  }\n\n  JSG_RESOURCE_TYPE(NonCoercibleContext) {\n    JSG_METHOD_NAMED(testString, template test<kj::String>);\n    JSG_METHOD_NAMED(testStringCoerced, template testCoerced<kj::String>);\n    JSG_METHOD_NAMED(testUSVString, template test<jsg::USVString>);\n    JSG_METHOD_NAMED(testUSVStringCoerced, template testCoerced<jsg::USVString>);\n    JSG_METHOD_NAMED(testDOMString, template test<jsg::DOMString>);\n    JSG_METHOD_NAMED(testDOMStringCoerced, template testCoerced<jsg::DOMString>);\n    JSG_METHOD_NAMED(testBoolean, template test<bool>);\n    JSG_METHOD_NAMED(testBooleanCoerced, template testCoerced<bool>);\n    JSG_METHOD_NAMED(testDouble, template test<double>);\n    JSG_METHOD_NAMED(testDoubleCoerced, template testCoerced<double>);\n    JSG_METHOD(testMaybeString);\n    JSG_METHOD(testMaybeStringCoerced);\n    JSG_METHOD(testOneOf);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(NonCoercibleIsolate, NonCoercibleContext);\n\nKJ_TEST(\"NonCoercible Values\") {\n  Evaluator<NonCoercibleContext, NonCoercibleIsolate> e(v8System);\n  e.expectEval(\"testString('')\", \"boolean\", \"true\");\n  e.expectEval(\"testString(null)\", \"throws\",\n      \"TypeError: Failed to execute 'testString' on 'NonCoercibleContext': parameter 1 is \"\n      \"not of type 'string'.\");\n  e.expectEval(\"testString({})\", \"throws\",\n      \"TypeError: Failed to execute 'testString' on 'NonCoercibleContext': parameter 1 is \"\n      \"not of type 'string'.\");\n  e.expectEval(\"testString(1)\", \"throws\",\n      \"TypeError: Failed to execute 'testString' on 'NonCoercibleContext': parameter 1 is \"\n      \"not of type 'string'.\");\n  e.expectEval(\"testStringCoerced('')\", \"boolean\", \"true\");\n  e.expectEval(\"testStringCoerced(null)\", \"boolean\", \"true\");\n  e.expectEval(\"testStringCoerced({})\", \"boolean\", \"true\");\n  e.expectEval(\"testStringCoerced(1)\", \"boolean\", \"true\");\n\n  e.expectEval(\"testUSVString('hi')\", \"boolean\", \"true\");\n  e.expectEval(\"testUSVString(null)\", \"throws\",\n      \"TypeError: Failed to execute 'testUSVString' on 'NonCoercibleContext': parameter 1 is \"\n      \"not of type 'USVString'.\");\n  e.expectEval(\"testUSVStringCoerced('hi')\", \"boolean\", \"true\");\n  e.expectEval(\"testUSVStringCoerced(null)\", \"boolean\", \"true\");\n\n  e.expectEval(\"testDOMString('hi')\", \"boolean\", \"true\");\n  e.expectEval(\"testDOMString(null)\", \"throws\",\n      \"TypeError: Failed to execute 'testDOMString' on 'NonCoercibleContext': parameter 1 is \"\n      \"not of type 'DOMString'.\");\n  e.expectEval(\"testDOMStringCoerced('hi')\", \"boolean\", \"true\");\n  e.expectEval(\"testDOMStringCoerced(null)\", \"boolean\", \"true\");\n\n  e.expectEval(\"testBoolean(true)\", \"boolean\", \"true\");\n  e.expectEval(\"testBoolean(null)\", \"throws\",\n      \"TypeError: Failed to execute 'testBoolean' on 'NonCoercibleContext': parameter 1 is\"\n      \" not of type 'boolean'.\");\n  e.expectEval(\"testBooleanCoerced(true)\", \"boolean\", \"true\");\n  e.expectEval(\"testBooleanCoerced(null)\", \"boolean\", \"true\");\n\n  e.expectEval(\"testDouble(1.1)\", \"boolean\", \"true\");\n  e.expectEval(\"testDouble(Infinity)\", \"boolean\", \"true\");\n  e.expectEval(\"testDouble(NaN)\", \"boolean\", \"true\");\n  e.expectEval(\"testDouble(null)\", \"throws\",\n      \"TypeError: Failed to execute 'testDouble' on 'NonCoercibleContext': parameter 1 is\"\n      \" not of type 'number'.\");\n  e.expectEval(\"testDoubleCoerced(1.1)\", \"boolean\", \"true\");\n  e.expectEval(\"testDoubleCoerced(null)\", \"boolean\", \"true\");\n\n  e.expectEval(\"testMaybeString('')\", \"boolean\", \"true\");\n  e.expectEval(\"testMaybeString(undefined)\", \"boolean\", \"true\");\n  e.expectEval(\"testMaybeString(null)\", \"throws\",\n      \"TypeError: Failed to execute 'testMaybeString' on 'NonCoercibleContext': parameter\"\n      \" 1 is not of type 'string'.\");\n  e.expectEval(\"testMaybeString(1)\", \"throws\",\n      \"TypeError: Failed to execute 'testMaybeString' on 'NonCoercibleContext': parameter\"\n      \" 1 is not of type 'string'.\");\n\n  e.expectEval(\"testMaybeStringCoerced(null)\", \"boolean\", \"true\");\n\n  e.expectEval(\"testOneOf(false)\", \"boolean\", \"true\");\n  e.expectEval(\"testOneOf('')\", \"boolean\", \"true\");\n  e.expectEval(\"testOneOf(new String(''))\", \"throws\",\n      \"TypeError: Failed to execute 'testOneOf' on 'NonCoercibleContext': parameter 1 is\"\n      \" not of type 'boolean or string'.\");\n}\n\n// ========================================================================================\n\nstruct MemoizedIdentityContext: public ContextGlobalObject {\n  static constexpr kj::Date DATE = kj::UNIX_EPOCH + 123 * kj::MILLISECONDS;\n  MemoizedIdentity<kj::Date> date = DATE;\n\n  kj::Date getDate() {\n    return DATE;\n  }\n\n  MemoizedIdentity<kj::Date>& getDateMemoized() {\n    return date;\n  }\n\n  JSG_RESOURCE_TYPE(MemoizedIdentityContext) {\n    JSG_METHOD(getDate);\n    JSG_METHOD(getDateMemoized);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(MemoizedIdentityIsolate, MemoizedIdentityContext);\n\nKJ_TEST(\"MemoizedIdentity Values\") {\n  Evaluator<MemoizedIdentityContext, MemoizedIdentityIsolate> e(v8System);\n  e.expectEval(\"getDate() === getDate()\", \"boolean\", \"false\");\n  e.expectEval(\"getDateMemoized() === getDateMemoized()\", \"boolean\", \"true\");\n}\n\n// ========================================================================================\n\nstruct IdentifiedContext: public ContextGlobalObject {\n  kj::String compare(jsg::Lock& js, Identified<kj::Date> a, Identified<kj::Date> b) {\n    bool result = a.identity == b.identity;\n    KJ_EXPECT(a.identity.hashCode() != 0);\n    KJ_EXPECT(b.identity.hashCode() != 0);\n    if (result) {\n      KJ_EXPECT(a.identity.hashCode() == b.identity.hashCode());\n    }\n    KJ_EXPECT(a.identity.hashCode() ==\n        kj::hashCode(a.identity.getHandle(js.v8Isolate)->GetIdentityHash()));\n    KJ_EXPECT(b.identity.hashCode() ==\n        kj::hashCode(b.identity.getHandle(js.v8Isolate)->GetIdentityHash()));\n\n    return kj::str(result, ' ', a.unwrapped - b.unwrapped);\n  }\n\n  JSG_RESOURCE_TYPE(IdentifiedContext) {\n    JSG_METHOD(compare);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(IdentifiedIsolate, IdentifiedContext);\n\nKJ_TEST(\"Identified values\") {\n  Evaluator<IdentifiedContext, IdentifiedIsolate> e(v8System);\n\n  e.expectEval(\"compare(new Date(123), new Date(123))\", \"string\", \"false 0ns\");\n  e.expectEval(\"compare(new Date(456), new Date(123))\", \"string\", \"false 333ms\");\n  e.expectEval(\"let d = new Date(123); compare(d, d)\", \"string\", \"true 0ns\");\n}\n\n// ========================================================================================\n\nstruct ExceptionContext: public ContextGlobalObject {\n\n  kj::String testToException(kj::Exception exception) {\n    return kj::str(exception.getDescription());\n  }\n\n  kj::Exception testFromException(int n) {\n    switch (n) {\n      case 1:\n        return JSG_KJ_EXCEPTION(FAILED, TypeError, \"boom\");\n      case 2:\n        return JSG_KJ_EXCEPTION(FAILED, DOMAbortError, \"boom\");\n    }\n    KJ_UNREACHABLE;\n  }\n\n  JSG_RESOURCE_TYPE(ExceptionContext) {\n    JSG_METHOD(testToException);\n    JSG_METHOD(testFromException);\n    JSG_NESTED_TYPE(DOMException);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(ExceptionIsolate, ExceptionContext);\n\nKJ_TEST(\"kj::Exception wrapper works\") {\n  Evaluator<ExceptionContext, ExceptionIsolate> e(v8System);\n\n  e.expectEval(\"testToException(new DOMException('boom', 'AbortError'))\", \"string\",\n      \"jsg.DOMException(AbortError): boom\");\n  e.expectEval(\"testToException(new SyntaxError('boom'))\", \"string\", \"jsg.SyntaxError: boom\");\n  e.expectEval(\"testToException(undefined)\", \"string\", \"jsg.Error: undefined\");\n  e.expectEval(\"testToException(1)\", \"string\", \"jsg.Error: 1\");\n\n  e.expectEval(\"testFromException(1)\", \"object\", \"TypeError: boom\");\n  e.expectEval(\"testFromException(2)\", \"object\", \"AbortError: boom\");\n}\n\n// ========================================================================================\nstruct NameContext: public ContextGlobalObject {\n  Name name(Name value) {\n    return kj::mv(value);\n  }\n\n  Name forSymbol(Lock& js, kj::String symbol) {\n    return js.newSymbol(symbol);\n  }\n\n  Name forSymbolShared(Lock& js, kj::String symbol) {\n    return js.newSharedSymbol(symbol);\n  }\n\n  Name forSymbolApi(Lock& js, kj::String symbol) {\n    return js.newApiSymbol(symbol);\n  }\n\n  JSG_RESOURCE_TYPE(NameContext) {\n    JSG_METHOD(name);\n    JSG_METHOD(forSymbol);\n    JSG_METHOD(forSymbolShared);\n    JSG_METHOD(forSymbolApi);\n  }\n};\nJSG_DECLARE_ISOLATE_TYPE(NameIsolate, NameContext);\n\nKJ_TEST(\"jsg::Name works\") {\n  Evaluator<NameContext, NameIsolate> e(v8System);\n  e.expectEval(\"name('hello')\", \"string\", \"hello\");\n  e.expectEval(\"name(Symbol('foo')).description\", \"string\", \"foo\");\n  e.expectEval(\"name(Symbol.for('foo')).description\", \"string\", \"foo\");\n  e.expectEval(\"forSymbol('foo').description\", \"string\", \"foo\");\n  e.expectEval(\"forSymbolShared('foo').description\", \"string\", \"foo\");\n  e.expectEval(\"forSymbolApi('foo').description\", \"string\", \"foo\");\n  e.expectEval(\"forSymbol('foo') !== Symbol.for('foo')\", \"boolean\", \"true\");\n  e.expectEval(\"forSymbolShared('foo') === Symbol.for('foo')\", \"boolean\", \"true\");\n  e.expectEval(\"forSymbolShared('foo') !== forSymbolApi('foo')\", \"boolean\", \"true\");\n}\n\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/value.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// INTERNAL IMPLEMENTATION FILE\n//\n// Handling of various basic value types: numbers, booleans, strings, optionals, maybes, variants,\n// arrays, buffers, dicts.\n\n#include <workerd/jsg/fast-api.h>\n#include <workerd/jsg/util.h>\n#include <workerd/jsg/web-idl.h>\n#include <workerd/jsg/wrappable.h>\n\n#include <v8-container.h>\n#include <v8-date.h>\n\n#include <kj/debug.h>\n#include <kj/one-of.h>\n#include <kj/time.h>\n\nnamespace workerd::jsg {\n\n// =======================================================================================\n// Primitives (numbers, booleans)\n// TypeWrapper mixin for numbers and booleans.\n//\n// This wrapper has extra wrap() overloads that take an isolate instead of a\n// lock and a context. This is used to implement static constants in\n// JavaScript: we need to be able to wrap C++ constants in V8 values before a\n// context has been entered.\n//\n// Note that we can't generally change the wrap(js, context, ...) functions to wrap(isolate, ...)\n// because ResourceWrapper<TW, T>::wrap() needs the context to create new object instances.\nclass PrimitiveWrapper {\n public:\n  static constexpr const char* getName(double*) {\n    return \"number\";\n  }\n\n  v8::Local<v8::Number> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      double value) {\n    return wrap(js.v8Isolate, creator, value);\n  }\n\n  v8::Local<v8::Number> wrap(\n      v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, double value) {\n    return v8::Number::New(isolate, value);\n  }\n\n  kj::Maybe<double> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      double*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    return check(handle->ToNumber(context))->Value();\n  }\n\n  static constexpr const char* getName(int8_t*) {\n    return \"byte\";\n  }\n\n  v8::Local<v8::Number> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      int8_t value) {\n    return wrap(js.v8Isolate, creator, value);\n  }\n\n  v8::Local<v8::Number> wrap(\n      v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, int8_t value) {\n    return v8::Integer::New(isolate, value);\n  }\n\n  kj::Maybe<int8_t> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      int8_t*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    auto value = check(handle->ToNumber(context))->Value();\n\n    JSG_REQUIRE(\n        isFinite(value), TypeError, \"The value cannot be converted because it is not an integer.\");\n\n    JSG_REQUIRE(\n        value <= static_cast<int8_t>(kj::maxValue) && value >= static_cast<int8_t>(kj::minValue),\n        TypeError,\n        kj::str(\"Value out of range. Must be between \", static_cast<int8_t>(kj::minValue), \" and \",\n            static_cast<int8_t>(kj::maxValue), \" (inclusive).\"));\n\n    return static_cast<int8_t>(value);\n  }\n\n  static constexpr const char* getName(uint8_t*) {\n    return \"octet\";\n  }\n\n  v8::Local<v8::Number> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      uint8_t value) {\n    return wrap(js.v8Isolate, creator, value);\n  }\n\n  v8::Local<v8::Number> wrap(\n      v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, uint8_t value) {\n    return v8::Integer::NewFromUnsigned(isolate, value);\n  }\n\n  kj::Maybe<uint8_t> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      uint8_t*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    auto value = check(handle->ToNumber(context))->Value();\n    JSG_REQUIRE(\n        isFinite(value), TypeError, \"The value cannot be converted because it is not an integer.\");\n\n    JSG_REQUIRE(value >= 0, TypeError,\n        \"The value cannot be converted because it is negative and this \"\n        \"API expects a positive number.\");\n\n    JSG_REQUIRE(value <= static_cast<uint8_t>(kj::maxValue), TypeError,\n        kj::str(\"Value out of range. Must be less than or equal to \",\n            static_cast<uint8_t>(kj::maxValue), \".\"));\n\n    return static_cast<uint8_t>(value);\n  }\n\n  static constexpr const char* getName(int16_t*) {\n    return \"short integer\";\n  }\n\n  v8::Local<v8::Number> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      int16_t value) {\n    return wrap(js.v8Isolate, creator, value);\n  }\n\n  v8::Local<v8::Number> wrap(\n      v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, int16_t value) {\n    return v8::Number::New(isolate, value);\n  }\n\n  kj::Maybe<int16_t> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      int16_t*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    auto value = check(handle->ToNumber(context))->Value();\n\n    JSG_REQUIRE(\n        isFinite(value), TypeError, \"The value cannot be converted because it is not an integer.\");\n\n    JSG_REQUIRE(\n        value <= static_cast<int16_t>(kj::maxValue) && value >= static_cast<int16_t>(kj::minValue),\n        TypeError,\n        kj::str(\"Value out of range. Must be between \", static_cast<int16_t>(kj::minValue), \" and \",\n            static_cast<int16_t>(kj::maxValue), \" (inclusive).\"));\n\n    return static_cast<int16_t>(value);\n  }\n\n  static constexpr const char* getName(uint16_t*) {\n    return \"unsigned short integer\";\n  }\n\n  v8::Local<v8::Number> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      uint16_t value) {\n    return wrap(js.v8Isolate, creator, value);\n  }\n\n  v8::Local<v8::Number> wrap(\n      v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, uint16_t value) {\n    return v8::Integer::NewFromUnsigned(isolate, value);\n  }\n\n  kj::Maybe<uint16_t> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      uint16_t*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    auto value = check(handle->ToNumber(context))->Value();\n    JSG_REQUIRE(\n        isFinite(value), TypeError, \"The value cannot be converted because it is not an integer.\");\n\n    JSG_REQUIRE(value >= 0, TypeError,\n        \"The value cannot be converted because it is negative and this \"\n        \"API expects a positive number.\");\n\n    JSG_REQUIRE(value <= static_cast<uint16_t>(kj::maxValue), TypeError,\n        kj::str(\"Value out of range. Must be less than or equal to \",\n            static_cast<uint16_t>(kj::maxValue), \".\"));\n\n    return static_cast<uint16_t>(value);\n  }\n\n  static constexpr const char* getName(int*) {\n    return \"integer\";\n  }\n\n  v8::Local<v8::Number> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      int value) {\n    return wrap(js.v8Isolate, creator, value);\n  }\n\n  v8::Local<v8::Number> wrap(\n      v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, int value) {\n    return v8::Number::New(isolate, value);\n  }\n\n  kj::Maybe<int> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      int*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (int num; handle->IsInt32() && handle->Int32Value(context).To(&num)) {\n      return num;\n    }\n\n    auto value = check(handle->ToNumber(context))->Value();\n    if (!isFinite(value)) {\n      return 0;\n    }\n\n    // One would think that RangeError is more appropriate than TypeError,\n    // but WebIDL says it should be TypeError.\n    JSG_REQUIRE(value <= static_cast<int>(kj::maxValue) && value >= static_cast<int>(kj::minValue),\n        TypeError,\n        kj::str(\"Value out of range. Must be between \", static_cast<int>(kj::minValue), \" and \",\n            static_cast<int>(kj::maxValue), \" (inclusive).\"));\n\n    return static_cast<int>(value);\n  }\n\n  static constexpr const char* getName(uint32_t*) {\n    return \"unsigned integer\";\n  }\n\n  v8::Local<v8::Number> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      uint32_t value) {\n    return wrap(js.v8Isolate, creator, value);\n  }\n\n  v8::Local<v8::Number> wrap(\n      v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, uint32_t value) {\n    return v8::Integer::NewFromUnsigned(isolate, value);\n  }\n\n  kj::Maybe<uint32_t> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      uint32_t*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (uint32_t num; handle->IsUint32() && handle->Uint32Value(context).To(&num)) {\n      return num;\n    }\n\n    auto value = check(handle->ToNumber(context))->Value();\n    JSG_REQUIRE(\n        isFinite(value), TypeError, \"The value cannot be converted because it is not an integer.\");\n\n    JSG_REQUIRE(value >= 0, TypeError,\n        \"The value cannot be converted because it is negative and this \"\n        \"API expects a positive number.\");\n\n    JSG_REQUIRE(value <= static_cast<uint32_t>(kj::maxValue), TypeError,\n        kj::str(\"Value out of range. Must be less than or equal to \",\n            static_cast<uint32_t>(kj::maxValue), \".\"));\n\n    return static_cast<uint32_t>(value);\n  }\n\n  static constexpr const char* getName(uint64_t*) {\n    return \"bigint\";\n  }\n\n  v8::Local<v8::BigInt> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      uint64_t value) {\n    return wrap(js.v8Isolate, creator, value);\n  }\n\n  v8::Local<v8::BigInt> wrap(\n      v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, uint64_t value) {\n    return v8::BigInt::New(isolate, value);\n  }\n\n  kj::Maybe<uint64_t> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      uint64_t*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (v8::Local<v8::BigInt> bigint;\n        handle->IsBigInt() && handle->ToBigInt(context).ToLocal(&bigint)) {\n      bool lossless;\n      auto value = bigint->Uint64Value(&lossless);\n      JSG_REQUIRE(lossless, TypeError,\n          \"The value cannot be converted because it is either negative and this \"\n          \"API expects a positive bigint, or the value would be truncated.\");\n      return value;\n    }\n\n    auto value = check(handle->ToNumber(context))->Value();\n    JSG_REQUIRE(\n        isFinite(value), TypeError, \"The value cannot be converted because it is not an integer.\");\n\n    JSG_REQUIRE(value >= 0, TypeError,\n        \"The value cannot be converted because it is negative and this \"\n        \"API expects a positive bigint.\");\n\n    JSG_REQUIRE(value <= static_cast<uint64_t>(kj::maxValue), TypeError,\n        kj::str(\"Value out of range. Must be less than or equal to \",\n            static_cast<uint64_t>(kj::maxValue), \".\"));\n\n    return static_cast<uint64_t>(value);\n  }\n\n  static constexpr const char* getName(int64_t*) {\n    return \"bigint\";\n  }\n\n  v8::Local<v8::BigInt> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      int64_t value) {\n    return wrap(js.v8Isolate, creator, value);\n  }\n\n  v8::Local<v8::BigInt> wrap(\n      v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, int64_t value) {\n    return v8::BigInt::New(isolate, value);\n  }\n\n  kj::Maybe<int64_t> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      int64_t*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (v8::Local<v8::BigInt> bigint;\n        handle->IsBigInt() && handle->ToBigInt(context).ToLocal(&bigint)) {\n      bool lossless;\n      auto value = bigint->Int64Value(&lossless);\n      JSG_REQUIRE(\n          lossless, TypeError, \"The value cannot be converted because it would be truncated.\");\n      return value;\n    }\n\n    auto value = check(handle->ToNumber(context))->Value();\n    JSG_REQUIRE(\n        isFinite(value), TypeError, \"The value cannot be converted because it is not an integer.\");\n\n    JSG_REQUIRE(\n        value <= static_cast<int64_t>(kj::maxValue) && value >= static_cast<int64_t>(kj::minValue),\n        TypeError,\n        kj::str(\"Value out of range. Must be between \", static_cast<int64_t>(kj::minValue), \" and \",\n            static_cast<int64_t>(kj::maxValue), \" (inclusive).\"));\n\n    return static_cast<int64_t>(value);\n  }\n\n  static constexpr const char* getName(bool*) {\n    return \"boolean\";\n  }\n\n  template <StrictlyBool T>\n  v8::Local<v8::Boolean> wrap(\n      Lock& js, v8::Local<v8::Context> context, kj::Maybe<v8::Local<v8::Object>> creator, T value) {\n    // The template is needed to prevent this overload from being chosen for arbitrary types that\n    // can convert to bool, such as pointers.\n    return wrap(js.v8Isolate, creator, value);\n  }\n\n  template <StrictlyBool T>\n  v8::Local<v8::Boolean> wrap(\n      v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, T value) {\n    // The template is needed to prevent this overload from being chosen for arbitrary types that\n    // can convert to bool, such as pointers.\n    return v8::Boolean::New(isolate, value);\n  }\n\n  kj::Maybe<bool> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      bool*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    return handle->ToBoolean(js.v8Isolate)->Value();\n  }\n};\n\n// =======================================================================================\n// Name\nclass NameWrapper {\n public:\n  static constexpr const char* getName(Name*) {\n    return \"string or Symbol\";\n  }\n\n  v8::Local<v8::Value> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      Name value) {\n    KJ_SWITCH_ONEOF(value.getUnwrapped(js.v8Isolate)) {\n      KJ_CASE_ONEOF(string, kj::StringPtr) {\n        return js.str(string);\n      }\n      KJ_CASE_ONEOF(symbol, v8::Local<v8::Symbol>) {\n        return symbol;\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  kj::Maybe<Name> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      Name*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (handle->IsSymbol()) {\n      return Name(js, handle.As<v8::Symbol>());\n    }\n\n    // Since most things are coercible to a string, this ought to catch pretty much\n    // any value other than symbol\n    return Name(js.toString(handle));\n  }\n};\n\n// =======================================================================================\n// Strings\n\n// TypeWrapper mixin for strings.\n//\n// This wrapper has an extra wrap() overload that takes an isolate instead of a context, for the\n// same reason discussed in PrimitiveWrapper.\nclass StringWrapper {\n public:\n  // TODO(someday): The conversion to kj::String doesn't explicitly consider the distinction\n  // between DOMString (~ WTF-8; could contain invalid code points) and USVString (invalid code\n  // points are always replaced with U+FFFD). Code should make an explict choice between the two.\n\n  template <typename T>\n    requires(kj::isSameType<T, kj::ArrayPtr<const char>>() ||\n        kj::isSameType<T, kj::Array<const char>>() || kj::isSameType<T, kj::String>())\n  static constexpr const char* getName(T*) {\n    return \"string\";\n  }\n\n  template <typename T>\n    requires(kj::isSameType<T, USVString>() || kj::isSameType<T, DOMString>())\n  static constexpr const char* getName(T*) {\n    if constexpr (kj::isSameType<T, USVString>()) {\n      return \"USVString\";\n    } else {\n      static_assert(kj::isSameType<T, DOMString>());\n      return \"DOMString\";\n    }\n  }\n\n  v8::Local<v8::String> wrap(\n      v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, kj::StringPtr value) {\n    return v8Str(isolate, value);\n  }\n\n  v8::Local<v8::String> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      kj::ArrayPtr<const char> value) {\n    return v8Str(js.v8Isolate, value);\n  }\n\n  v8::Local<v8::String> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      kj::Array<const char> value) {\n    return wrap(js, context, creator, value.asPtr());\n  }\n\n  template <typename T>\n    requires(kj::isSameType<T, const USVString&>() || kj::isSameType<T, const DOMString&>())\n  v8::Local<v8::String> wrap(\n      Lock& js, v8::Local<v8::Context> context, kj::Maybe<v8::Local<v8::Object>> creator, T value) {\n    // TODO(cleanup): Move to a HeaderStringWrapper in the api directory.\n    return v8Str(js.v8Isolate, value.asPtr());\n  }\n\n  template <typename T>\n    requires(kj::isSameType<T, kj::String>() || kj::isSameType<T, USVString>() ||\n        kj::isSameType<T, DOMString>())\n  kj::Maybe<T> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      T*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    // Note that if handle is already a string, calling ToString will just\n    // return handle without any further coercion. For any other type of\n    // value, v8 will try to coerce it into a string. So there is no need\n    // for us to check if handle is a string here or not, ToString does\n    // that for us.\n    JsString str(check(handle->ToString(context)));\n    if constexpr (kj::isSameType<T, kj::String>()) {\n      return str.toString(js);\n    } else if constexpr (kj::isSameType<T, USVString>()) {\n      return str.toUSVString(js);\n    } else if constexpr (kj::isSameType<T, DOMString>()) {\n      return str.toDOMString(js);\n    }\n\n    KJ_UNREACHABLE;\n  }\n};\n\n// =======================================================================================\n// Optional (value or undefined) and Maybe (value or null)\n\n// TypeWrapper mixin for optionals.\ntemplate <typename TypeWrapper>\nclass OptionalWrapper {\n public:\n  template <typename U>\n  static constexpr decltype(auto) getName(Optional<U>*) {\n    return TypeWrapper::getName(static_cast<kj::Decay<U>*>(nullptr));\n  }\n\n  template <typename U>\n  v8::Local<v8::Value> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      Optional<U> ptr) {\n    KJ_IF_SOME(p, ptr) {\n      return static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::fwd<U>(p));\n    } else {\n      return js.undefined();\n    }\n  }\n\n  template <typename U>\n  kj::Maybe<Optional<U>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      Optional<U>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (handle->IsUndefined()) {\n      return Optional<U>(kj::none);\n    } else {\n      return static_cast<TypeWrapper*>(this)\n          ->tryUnwrap(js, context, handle, static_cast<kj::Decay<U>*>(nullptr), parentObject)\n          .map([](auto&& value) -> Optional<U> { return kj::fwd<decltype(value)>(value); });\n    }\n  }\n};\n\n// TypeWrapper mixin for lenient optionals.\ntemplate <typename TypeWrapper>\nclass LenientOptionalWrapper {\n public:\n  template <typename U>\n  static constexpr decltype(auto) getName(LenientOptional<U>*) {\n    return TypeWrapper::getName(static_cast<kj::Decay<U>*>(nullptr));\n  }\n\n  template <typename U>\n  v8::Local<v8::Value> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      LenientOptional<U> ptr) {\n    KJ_IF_SOME(p, ptr) {\n      return static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::fwd<U>(p));\n    } else {\n      return js.undefined();\n    }\n  }\n\n  template <typename U>\n  kj::Maybe<LenientOptional<U>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      LenientOptional<U>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (handle->IsUndefined()) {\n      return LenientOptional<U>(kj::none);\n    } else {\n      KJ_IF_SOME(unwrapped,\n          static_cast<TypeWrapper*>(this)->tryUnwrap(\n              js, context, handle, static_cast<kj::Decay<U>*>(nullptr), parentObject)) {\n        return LenientOptional<U>(kj::mv(unwrapped));\n      } else {\n        return LenientOptional<U>(kj::none);\n      }\n    }\n  }\n};\n\n// TypeWrapper mixin for maybes.\ntemplate <typename TypeWrapper>\nclass MaybeWrapper {\n\n public:\n  // The constructor here is a bit of a hack. The config is optional and might not be a JsgConfig\n  // object (or convertible to a JsgConfig) if is provided. However, because of the way TypeWrapper\n  // inherits MaybeWrapper, we always end up passing a config option (which might be std::nullptr_t)\n  // The getConfig allows us to handle any case using reasonable defaults.\n  MaybeWrapper(const auto& config): config(getConfig(config)) {}\n\n  template <typename U>\n  static constexpr decltype(auto) getName(kj::Maybe<U>*) {\n    return TypeWrapper::getName(static_cast<kj::Decay<U>*>(nullptr));\n  }\n\n  template <typename U>\n  v8::Local<v8::Value> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      kj::Maybe<U> ptr) {\n    KJ_IF_SOME(p, ptr) {\n      return static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::fwd<U>(p));\n    } else {\n      return js.null();\n    }\n  }\n\n  template <typename U>\n  kj::Maybe<kj::Maybe<U>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      kj::Maybe<U>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (handle->IsNullOrUndefined()) {\n      return kj::Maybe<U>(kj::none);\n    } else if (config.noSubstituteNull) {\n      // There was a bug in the initial version of this method that failed to correctly handle\n      // the following tryUnwrap returning a nullptr because of an incorrect type. The\n      // noSubstituteNull compatibility flag is needed to fix that.\n      return static_cast<TypeWrapper*>(this)\n          ->tryUnwrap(js, context, handle, static_cast<kj::Decay<U>*>(nullptr), parentObject)\n          .map([](auto&& value) -> kj::Maybe<U> { return kj::fwd<decltype(value)>(value); });\n    } else {\n      return static_cast<TypeWrapper*>(this)->tryUnwrap(\n          js, context, handle, static_cast<kj::Decay<U>*>(nullptr), parentObject);\n    }\n  }\n\n private:\n  const JsgConfig config;\n};\n\n// =======================================================================================\n// OneOf / variants\n\ntemplate <typename T>\nconstexpr bool isOneOf = false;\ntemplate <typename... T>\nconstexpr bool isOneOf<kj::OneOf<T...>> = true;\n// TODO(cleanup): Move to kj/one-of.h?\n\n// TypeWrapper mixin for variants.\ntemplate <typename TypeWrapper>\nclass OneOfWrapper {\n public:\n  template <typename... U>\n  static kj::String getName(kj::OneOf<U...>*) {\n    const auto getNameStr = [](auto u) {\n      if constexpr (kj::isSameType<const std::type_info&, decltype(TypeWrapper::getName(u))>()) {\n        return typeName(TypeWrapper::getName(u));\n      } else {\n        return kj::str(TypeWrapper::getName(u));\n      }\n    };\n\n    return kj::strArray(kj::arr(getNameStr(static_cast<U*>(nullptr))...), \" or \");\n  }\n\n  template <typename U, typename... V>\n  bool wrapHelper(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      kj::OneOf<V...>& in,\n      v8::Local<v8::Value>& out) {\n    if (in.template is<U>()) {\n      out =\n          static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::mv(in.template get<U>()));\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n  template <typename... U>\n  v8::Local<v8::Value> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      kj::OneOf<U...> value) {\n    v8::Local<v8::Value> result;\n    if (!(wrapHelper<U>(js, context, creator, value, result) || ...)) {\n      result = js.undefined();\n    }\n    return result;\n  }\n\n  template <template <typename> class Predicate, typename U, typename... V>\n  bool unwrapHelperRecursive(\n      Lock& js, v8::Local<v8::Context> context, v8::Local<v8::Value> in, kj::OneOf<V...>& out) {\n    if constexpr (isOneOf<U>) {\n      // Ugh, a nested OneOf. We can't just call tryUnwrap(), because then our string/numeric\n      // coercion might trigger early.\n      U val;\n      if (unwrapHelper<Predicate>(js, context, in, val)) {\n        out.template init<U>(kj::mv(val));\n        return true;\n      }\n    } else if constexpr (Predicate<kj::Decay<U>>::value) {\n      KJ_IF_SOME(val,\n          static_cast<TypeWrapper*>(this)->tryUnwrap(\n              js, context, in, static_cast<U*>(nullptr), kj::none)) {\n        out.template init<U>(kj::mv(val));\n        return true;\n      }\n    }\n    return false;\n  }\n\n  template <template <typename> class Predicate, typename... U>\n  bool unwrapHelper(\n      Lock& js, v8::Local<v8::Context> context, v8::Local<v8::Value> in, kj::OneOf<U...>& out) {\n    return (unwrapHelperRecursive<Predicate, U>(js, context, in, out) || ...);\n  }\n\n  // Predicates for helping implement nested OneOf unwrapping. These must be struct templates\n  // because we can't pass variable templates as template template parameters.\n\n  template <typename T>\n  struct IsResourceType {\n    static constexpr bool value = webidl::isNonCallbackInterfaceType<T>;\n  };\n  template <typename T>\n  struct IsFallibleType {\n    static constexpr bool value =\n        !(webidl::isStringType<T> || webidl::isNumericType<T> || webidl::isBooleanType<T>);\n  };\n  template <typename T>\n  struct IsStringType {\n    static constexpr bool value = webidl::isStringType<T>;\n  };\n  template <typename T>\n  struct IsNumericType {\n    static constexpr bool value = webidl::isNumericType<T>;\n  };\n  template <typename T>\n  struct IsBooleanType {\n    static constexpr bool value = webidl::isBooleanType<T>;\n  };\n\n  template <typename... U>\n  kj::Maybe<kj::OneOf<U...>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      kj::OneOf<U...>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    (void)webidl::UnionTypeValidator<kj::OneOf<U...>>();\n    // Just need to instantiate this, static_asserts will do the rest.\n\n    // In order for string, numeric, and boolean coercion to function as expected, we need to follow\n    // the algorithm defined by Web IDL section 3.2.22 to convert JS values to OneOfs. That\n    // algorithm is written in a terribly wonky way, of course, but it appears we can restate it\n    // like so:\n    //\n    //   - Perform a series of breadth-first-searches on the OneOf, filtering out certain categories\n    //     of types on each run. For the types which are not filtered out, perform a tryUnwrap() on\n    //     that type, and succeed if that call succeeds (i.e., short-circuit). The filters used for\n    //     each pass are the following:\n    //       a. Consider only fallible (uncoercible) types.\n    //       b. If the JS value is a boolean, consider only boolean types.\n    //       c. If the JS value is a number, consider only numeric types.\n    //       d. Consider only string types.\n    //       e. Consider only numeric types.\n    //       f. Consider only boolean types.\n    //\n    // Note the symmetry across steps b-f. This way, strings only get coerced to numbers if the\n    // OneOf doesn't contain a string type, numbers only get coerced to strings if the OneOf doesn't\n    // contain a numeric type, objects only get coerced to a coercible type if there's no matching\n    // object type, null and undefined only get coerced to a coercible type if there's no nullable\n    // type, etc.\n    //\n    // TODO(soon): Hacked this by unwrapping into resource types first, so that we can unwrap\n    //   Requests and Responses into Initializers without them being interpreted as dictionaries. I\n    //   believe this is actually what the Web IDL spec prescribes anyway, but verify.\n    //\n    // TODO(someday): Prove that this is the same algorithm as the one defined by Web IDL.\n    kj::OneOf<U...> result;\n    if (unwrapHelper<IsResourceType>(js, context, handle, result) ||\n        unwrapHelper<IsFallibleType>(js, context, handle, result) ||\n        (handle->IsBoolean() && unwrapHelper<IsBooleanType>(js, context, handle, result)) ||\n        (handle->IsNumber() && unwrapHelper<IsNumericType>(js, context, handle, result)) ||\n        (handle->IsBigInt() && unwrapHelper<IsNumericType>(js, context, handle, result)) ||\n        (unwrapHelper<IsStringType>(js, context, handle, result)) ||\n        (unwrapHelper<IsNumericType>(js, context, handle, result)) ||\n        (unwrapHelper<IsBooleanType>(js, context, handle, result))) {\n      return kj::mv(result);\n    }\n    return kj::none;\n  }\n};\n\n// =======================================================================================\n// Arrays\n\n// TypeWrapper mixin for arrays.\ntemplate <typename TypeWrapper>\nclass ArrayWrapper {\n public:\n  template <typename U>\n  static constexpr const char* getName(kj::Array<U>*) {\n    return \"Array\";\n  }\n\n  template <typename U>\n  v8::Local<v8::Value> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      kj::Array<U> array) {\n    v8::Isolate* isolate = js.v8Isolate;\n    v8::EscapableHandleScope handleScope(isolate);\n\n    v8::LocalVector<v8::Value> items(isolate, array.size());\n    for (auto n = 0; n < items.size(); n++) {\n      items[n] = static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::mv(array[n]));\n    }\n    auto out = v8::Array::New(isolate, items.data(), items.size());\n\n    return handleScope.Escape(out);\n  }\n  template <typename U>\n  v8::Local<v8::Value> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      kj::ArrayPtr<U> array) {\n    v8::Isolate* isolate = js.v8Isolate;\n    v8::EscapableHandleScope handleScope(isolate);\n\n    v8::LocalVector<v8::Value> items(isolate, array.size());\n    for (auto n = 0; n < items.size(); n++) {\n      items[n] = static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::mv(array[n]));\n    }\n    auto out = v8::Array::New(isolate, items.data(), items.size());\n\n    return handleScope.Escape(out);\n  }\n  template <typename U>\n  v8::Local<v8::Value> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      kj::Array<U>& array) {\n    return static_cast<TypeWrapper*>(this)->wrap(js, context, creator, array.asPtr());\n  }\n\n  template <typename U>\n  kj::Maybe<kj::Array<U>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      kj::Array<U>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (!handle->IsArray()) {\n      return kj::none;\n    }\n\n    auto array = handle.As<v8::Array>();\n    auto length = array->Length();\n    auto builder = kj::heapArrayBuilder<U>(length);\n    for (auto i: kj::zeroTo(length)) {\n      v8::Local<v8::Value> element = check(array->Get(context, i));\n      builder.add(static_cast<TypeWrapper*>(this)->template unwrap<U>(\n          js, context, element, TypeErrorContext::arrayElement(i)));\n    }\n    return builder.finish();\n  }\n};\n\n// =======================================================================================\n// Sets\n\n// TypeWrapper mixin for sets.\ntemplate <typename TypeWrapper>\nclass SetWrapper {\n public:\n  template <typename U>\n  static constexpr const char* getName(kj::HashSet<U>*) {\n    return \"Set\";\n  }\n\n  template <typename U>\n  v8::Local<v8::Value> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      kj::HashSet<U> set) {\n    v8::Isolate* isolate = js.v8Isolate;\n    v8::EscapableHandleScope handleScope(isolate);\n\n    auto out = v8::Set::New(isolate);\n    for (auto& item: set) {\n      v8::HandleScope scope(isolate);\n      check(out->Add(\n          context, static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::mv(item))));\n    }\n\n    return handleScope.Escape(out);\n  }\n\n  template <typename U>\n  kj::Maybe<kj::HashSet<U>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      kj::HashSet<U>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (!handle->IsSet()) {\n      return kj::none;\n    }\n\n    auto set = handle.As<v8::Set>();\n    auto array = set->AsArray();\n    auto length = array->Length();\n    auto builder = kj::HashSet<U>();\n    builder.reserve(length);\n    for (auto i: kj::zeroTo(length)) {\n      v8::Local<v8::Value> element = check(array->Get(context, i));\n      auto value = static_cast<TypeWrapper*>(this)->template unwrap<U>(\n          js, context, element, TypeErrorContext::other());\n      builder.upsert(kj::mv(value), [&](U& existing, U&& replacement) {\n        JSG_FAIL_REQUIRE(TypeError, \"Duplicate values in the set after unwrapping.\");\n      });\n    }\n    return kj::mv(builder);\n  }\n};\n\n// =======================================================================================\n// ArrayBuffers / ArrayBufferViews\n//\n// This wrapper implements the following wrapping conversions:\n//  - kj::Array<kj::byte> -> ArrayBuffer\n//\n// And the following unwrapping conversions:\n//   - ArrayBuffer -> kj::Array<kj::byte>\n//     (the kj::Array object holds a Global to the unwrapped ArrayBuffer)\n//   - ArrayBufferView -> kj::Array<kj::byte>\n//     (the kj::Array object holds a Global to the unwrapped ArrayBufferView's backing buffer)\n//\n// Note that there are no conversions for kj::ArrayPtr<kj::byte>, since it does not own its own\n// buffer -- fine in C++, but problematic in a GC language like JS. Restricting the interface to\n// only operate on owned arrays makes memory management simpler and safer in both directions.\n//\n// Logically a kj::Array<byte> could be considered analogous to a Uint8Array in JS, and for a time\n// that was the wrapping conversion implemented by this wrapper. However, the most common use cases\n// in web platform APIs involve accepting BufferSources for processing as immutable input and\n// returning ArrayBuffers. Since a kj::byte does not map to any JavaScript primitive, establishing\n// a mapping between ArrayBuffer/ArrayBufferView and Array<byte> is unambiguous and\n// convenient. The few places where a specific TypedArray is expected (e.g. Uint8Array) can be\n// handled explicitly with a v8::Local<v8::Uint8Array> (or other appropriate TypedArray type).\n//\n// BufferSource arguments to web platform API methods are typically expected to be processed but not\n// mutated, such as the input parameter to TextDecoder.decode(). This processing might happen\n// asynchronously, such as the plaintext parameter to SubtleCrypto.encrypt(). I am unaware of any\n// use of BufferSources which involve mutating the underlying ArrayBuffer -- typically an explicit\n// ArrayBufferView is expected for this case, such as the parameters to crypto.getRandomValues() or\n// the Streams spec's BYOB reader's read() method.\n//\n// This suggests the following rules of thumb:\n//\n// 1. If a BufferSource parameter is used as input to a:\n//   - synchronous method: accept a `kj::Array<const kj::byte>`.\n//   - asynchronous method (user is allowed to re-use the buffer during processing): accept a\n//     `kj::Array<const kj::byte>` and explicitly copy its bytes.\n//\n// 2. If a method accepts an ArrayBufferView that it is expected to mutate:\n//   - accept a `v8::Local<v8::ArrayBufferView>` explicitly (handled by V8HandleWrapper in\n//     type-wrapper.h) rather than a `kj::Array<kj::byte>` -- otherwise your method's contract\n//     will be wider than intended.\n//   - use `jsg::asBytes()` as a quick way to get a `kj::ArrayPtr<kj::byte>` view onto it.\n//\n// 3. If a method returns an ArrayBuffer, create and return a `kj::Array<kj::byte>`.\nclass ArrayBufferWrapper {\n public:\n  static constexpr const char* getName(kj::ArrayPtr<byte>*) {\n    return \"ArrayBuffer or ArrayBufferView\";\n  }\n  static constexpr const char* getName(kj::ArrayPtr<const byte>*) {\n    return \"ArrayBuffer or ArrayBufferView\";\n  }\n  static constexpr const char* getName(kj::Array<byte>*) {\n    return \"ArrayBuffer or ArrayBufferView\";\n  }\n\n  v8::Local<v8::ArrayBuffer> wrap(jsg::Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      kj::Array<byte> value) {\n    return wrap(js.v8Isolate, creator, kj::mv(value));\n  }\n\n  v8::Local<v8::ArrayBuffer> wrap(\n      v8::Isolate* isolate, kj::Maybe<v8::Local<v8::Object>> creator, kj::Array<byte> value) {\n    // We need to construct a BackingStore that owns the byte array. We use the version of\n    // v8::ArrayBuffer::NewBackingStore() that accepts a deleter callback, and arrange for it to\n    // delete an Array<byte> placed on the heap.\n    //\n    size_t size = value.size();\n    if (size == 0) {\n      // BackingStore doesn't call custom deleter if begin is null, which it often is for empty\n      // arrays.\n      return v8::ArrayBuffer::New(isolate, 0);\n    }\n    byte* begin = value.begin();\n    if (isolate->GetGroup().SandboxContains(begin)) {\n      // TODO(perf): We could avoid an allocation here, perhaps, by decomposing the\n      //   kj::Array<byte> into its component pointer and disposer, and then pass the disposer\n      //   pointer as the \"deleter_data\" for NewBackingStore. However, KJ doesn't give us any way\n      //   to decompose an Array<T> this way, and it might not want to, as this could make it\n      //   impossible to support unifying Array<T> and Vector<T> in the future (i.e. making all\n      //   Array<T>s growable). So it may be best to stick with allocating an Array<byte> on the\n      //   heap after all...\n      auto ownerPtr = new kj::Array<byte>(kj::mv(value));\n\n      std::unique_ptr<v8::BackingStore> backing = v8::ArrayBuffer::NewBackingStore(\n          begin, size, [](void* begin, size_t size, void* ownerPtr) {\n        delete reinterpret_cast<kj::Array<byte>*>(ownerPtr);\n      }, ownerPtr);\n      KJ_REQUIRE(backing != nullptr, \"Failed to create ArrayBuffer backing store\");\n\n      return v8::ArrayBuffer::New(isolate, kj::mv(backing));\n    } else {\n      // The Array is not already inside the sandbox.  We have to make a copy and move it in.\n      // For performance reasons we might want to throw here and fix all callers to allocate\n      // inside the sandbox.\n      auto& js = Lock::from(isolate);\n      auto in_sandbox = js.allocBackingStore(size, Lock::AllocOption::UNINITIALIZED);\n\n      memcpy(in_sandbox->Data(), value.begin(), size);\n\n      return v8::ArrayBuffer::New(isolate, kj::mv(in_sandbox));\n    }\n  }\n\n  kj::Maybe<kj::Array<byte>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      kj::Array<byte>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (handle->IsArrayBufferView()) {\n      return asBytes(handle.As<v8::ArrayBufferView>());\n    } else if (handle->IsArrayBuffer()) {\n      return asBytes(handle.As<v8::ArrayBuffer>());\n    } else if (handle->IsSharedArrayBuffer()) {\n      return asBytes(handle.As<v8::SharedArrayBuffer>());\n    }\n    return kj::none;\n  }\n\n  kj::Maybe<kj::Array<const byte>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      kj::Array<const byte>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    return tryUnwrap(js, context, handle, static_cast<kj::Array<byte>*>(nullptr), parentObject);\n  }\n};\n\n// =======================================================================================\n// Dicts\n\n// TypeWrapper mixin for dictionaries (objects used as string -> value maps).\ntemplate <typename TypeWrapper>\nclass DictWrapper {\n public:\n  template <typename K, typename V>\n  static constexpr const char* getName(Dict<V, K>*) {\n    return \"object\";\n  }\n\n  template <typename K, typename V>\n  v8::Local<v8::Value> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      Dict<V, K> dict) {\n    static_assert(webidl::isStringType<K>, \"Dicts must be keyed on a string type.\");\n\n    v8::Isolate* isolate = js.v8Isolate;\n    v8::EscapableHandleScope handleScope(isolate);\n    auto out = v8::Object::New(isolate);\n    for (auto& field: dict.fields) {\n      // Set() returns Maybe<bool>. As usual, if the Maybe is null, then there was an exception,\n      // but I have no idea what it means if the Maybe was filled in with the boolean value false...\n      KJ_ASSERT(check(out->Set(context,\n          static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::mv(field.name)),\n          static_cast<TypeWrapper*>(this)->wrap(js, context, creator, kj::mv(field.value)))));\n    }\n    return handleScope.Escape(out);\n  }\n\n  template <typename K, typename V>\n  kj::Maybe<Dict<V, K>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      Dict<V, K>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    static_assert(webidl::isStringType<K>, \"Dicts must be keyed on a string type.\");\n\n    auto& wrapper = static_cast<TypeWrapper&>(*this);\n\n    if (!handle->IsObject() || handle->IsArray()) {\n      return kj::none;\n    }\n\n    auto object = handle.As<v8::Object>();\n    v8::Local<v8::Array> names = check(object->GetOwnPropertyNames(context));\n    auto length = names->Length();\n    auto builder = kj::heapArrayBuilder<typename Dict<V, K>::Field>(length);\n    for (auto i: kj::zeroTo(length)) {\n      v8::Local<v8::String> name = check(check(names->Get(context, i))->ToString(context));\n      v8::Local<v8::Value> value = check(object->Get(context, name));\n\n      if constexpr (kj::isSameType<K, kj::String>()) {\n        auto strName = JsString(name).toString(js);\n        const char* cstrName = strName.cStr();\n        builder.add(typename Dict<V, K>::Field{kj::mv(strName),\n          wrapper.template unwrap<V>(\n              js, context, value, TypeErrorContext::dictField(cstrName), object)});\n      } else {\n        // Here we have to be a bit more careful than for the kj::String case. The unwrap<K>() call\n        // may throw, but we need the name in UTF-8 for the very exception that it needs to throw.\n        // Thus, we do the unwrapping manually and UTF-8-convert the name only if it's needed.\n        auto unwrappedName = wrapper.tryUnwrap(js, context, name, static_cast<K*>(nullptr), object);\n        if (unwrappedName == kj::none) {\n          auto strName = JsString(name).toString(js);\n          throwTypeError(js.v8Isolate, TypeErrorContext::dictKey(strName.cStr()),\n              TypeWrapper::getName(static_cast<K*>(nullptr)));\n        }\n        auto unwrappedValue =\n            wrapper.tryUnwrap(js, context, value, static_cast<V*>(nullptr), object);\n        if (unwrappedValue == kj::none) {\n          auto strName = JsString(name).toString(js);\n          throwTypeError(js.v8Isolate, TypeErrorContext::dictField(strName.cStr()),\n              TypeWrapper::getName(static_cast<V*>(nullptr)));\n        }\n        builder.add(typename Dict<V, K>::Field{\n          KJ_ASSERT_NONNULL(kj::mv(unwrappedName)), KJ_ASSERT_NONNULL(kj::mv(unwrappedValue))});\n      }\n    }\n    return Dict<V, K>{builder.finish()};\n  }\n};\n\n// =======================================================================================\n// Dates\n\nclass DateWrapper {\n public:\n  static constexpr const char* getName(kj::Date*) {\n    return \"date\";\n  }\n\n  v8::Local<v8::Value> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      kj::Date date) {\n    return check(v8::Date::New(context, (date - kj::UNIX_EPOCH) / kj::MILLISECONDS));\n  }\n\n  kj::Maybe<kj::Date> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      kj::Date*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (handle->IsDate()) {\n      double millis = handle.template As<v8::Date>()->ValueOf();\n      return toKjDate(millis);\n    } else if (handle->IsNumber()) {\n      double millis = handle.template As<v8::Number>()->Value();\n      return toKjDate(millis);\n    } else {\n      return kj::none;\n    }\n  }\n\n private:\n  kj::Date toKjDate(double millis) {\n    JSG_REQUIRE(isFinite(millis), TypeError,\n        \"The value cannot be converted because it is not a valid Date.\");\n\n    // JS Date uses milliseconds stored as a double-precision float to represent times\n    // KJ uses nanoseconds stored as an int64_t, which is significantly smaller but larger\n    // than my lifetime.\n    //\n    // For most use-cases, throwing when we encounter a date outside of KJ's supported range is OK.\n    // API's that need to support time-travelers or historians may need to consider using the\n    // V8 Date type directly.\n    constexpr double millisToNanos = kj::MILLISECONDS / kj::NANOSECONDS;\n    double nanos = millis * millisToNanos;\n    JSG_REQUIRE(nanos < static_cast<int64_t>(kj::maxValue), TypeError,\n        \"This API doesn't support dates after 2189.\");\n    JSG_REQUIRE(nanos > static_cast<int64_t>(kj::minValue), TypeError,\n        \"This API doesn't support dates before 1687.\");\n    return kj::UNIX_EPOCH + static_cast<int64_t>(millis) * kj::MILLISECONDS;\n  };\n};\n\n// =======================================================================================\n// NonCoercible<T>\n\ntemplate <typename TypeWrapper>\nclass NonCoercibleWrapper {\n public:\n  template <CoercibleType T>\n  static auto getName(NonCoercible<T>*) {\n    return TypeWrapper::getName(static_cast<T*>(nullptr));\n  }\n\n  template <CoercibleType T>\n  v8::Local<v8::Value> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      NonCoercible<T>) = delete;\n\n  template <CoercibleType T>\n  kj::Maybe<NonCoercible<T>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      NonCoercible<T>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    auto& wrapper = static_cast<TypeWrapper&>(*this);\n    if constexpr (kj::isSameType<kj::String, T>() || kj::isSameType<jsg::USVString, T>() ||\n        kj::isSameType<jsg::DOMString, T>()) {\n      if (!handle->IsString()) return kj::none;\n      KJ_IF_SOME(value,\n          wrapper.tryUnwrap(js, context, handle, static_cast<T*>(nullptr), parentObject)) {\n        return NonCoercible<T>{\n          .value = kj::mv(value),\n        };\n      }\n      return kj::none;\n    } else if constexpr (kj::isSameType<bool, T>()) {\n      if (!handle->IsBoolean()) return kj::none;\n      return wrapper.tryUnwrap(js, context, handle, static_cast<T*>(nullptr), parentObject)\n          .map([](auto& value) {\n        return NonCoercible<T>{\n          .value = value,\n        };\n      });\n    } else if constexpr (kj::isSameType<double, T>()) {\n      if (!handle->IsNumber()) return kj::none;\n      return wrapper.tryUnwrap(js, context, handle, static_cast<T*>(nullptr), parentObject)\n          .map([](auto& value) {\n        return NonCoercible<T>{\n          .value = value,\n        };\n      });\n    } else {\n      return nullptr;\n    }\n  }\n};\n\n// =======================================================================================\n// MemoizedIdentity<T>\n\ntemplate <typename T>\nvoid MemoizedIdentity<T>::visitForGc(GcVisitor& visitor) {\n  KJ_SWITCH_ONEOF(value) {\n    KJ_CASE_ONEOF(raw, T) {\n      if constexpr (isGcVisitable<T>()) {\n        visitor.visit(raw);\n      }\n    }\n    KJ_CASE_ONEOF(handle, Value) {\n      return visitor.visit(handle);\n    }\n  }\n}\n\ntemplate <typename TypeWrapper>\nclass MemoizedIdentityWrapper {\n public:\n  template <typename T>\n  static auto getName(MemoizedIdentity<T>*) {\n    return TypeWrapper::getName(static_cast<T*>(nullptr));\n  }\n\n  template <typename T>\n  v8::Local<v8::Value> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      MemoizedIdentity<T>& value) {\n    auto& wrapper = static_cast<TypeWrapper&>(*this);\n    KJ_SWITCH_ONEOF(value.value) {\n      KJ_CASE_ONEOF(raw, T) {\n        auto handle = wrapper.wrap(js, context, creator, kj::mv(raw));\n        value.value.template init<Value>(js.v8Isolate, handle);\n        return handle;\n      }\n      KJ_CASE_ONEOF(handle, Value) {\n        return handle.getHandle(js.v8Isolate);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  template <typename T>\n  kj::Maybe<MemoizedIdentity<T>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      MemoizedIdentity<T>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) = delete;\n};\n\n// =======================================================================================\n// Identified<T>\n\ntemplate <typename TypeWrapper>\nclass IdentifiedWrapper {\n public:\n  template <typename T>\n  static auto getName(Identified<T>*) {\n    return TypeWrapper::getName(static_cast<T*>(nullptr));\n  }\n\n  template <typename T>\n  v8::Local<v8::Value> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      Identified<T>& value) = delete;\n\n  template <typename T>\n  kj::Maybe<Identified<T>> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      Identified<T>*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    if (!handle->IsObject()) {\n      return kj::none;\n    }\n\n    auto& wrapper = static_cast<TypeWrapper&>(*this);\n    return wrapper.tryUnwrap(js, context, handle, static_cast<T*>(nullptr), parentObject)\n        .map([&](T&& value) -> Identified<T> {\n      auto isolate = js.v8Isolate;\n      auto obj = handle.As<v8::Object>();\n      return {.identity = {isolate, obj}, .unwrapped = kj::mv(value)};\n    });\n  }\n};\n\n// =======================================================================================\n// SelfRef\n\nclass SelfRefWrapper {\n public:\n  static auto getName(SelfRef*) {\n    return \"SelfRef\";\n  }\n\n  v8::Local<v8::Value> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      const SelfRef& value) = delete;\n\n  kj::Maybe<SelfRef> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      SelfRef*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n    // I'm sticking this here because it's related and I'm lazy.\n    return SelfRef(js.v8Isolate,\n        KJ_ASSERT_NONNULL(parentObject, \"SelfRef can only be used as a member of a JSG_STRUCT.\"));\n  }\n};\n\n// =======================================================================================\n// kj::Exception\n//\n// The kj::Exception wrapper handles the translation of so-called \"tunneled\" exceptions\n// between KJ and JavaScript. The wrapper is capable of turning any JavaScript value into\n// a kj::Exception with the caveat that the kj::Exception is not guaranteed to retain all\n// of the detail. Likewise, it can turn a kj::Exception with the correct metadata into a\n// reasonable JavaScript exception.\n\nclass DOMException;\n\ntemplate <typename TypeWrapper>\nclass ExceptionWrapper {\n public:\n  static constexpr const char* getName(kj::Exception*) {\n    return \"Exception\";\n  }\n\n  v8::Local<v8::Value> wrap(Lock& js,\n      v8::Local<v8::Context> context,\n      kj::Maybe<v8::Local<v8::Object>> creator,\n      kj::Exception exception) {\n    return js.exceptionToJsValue(kj::mv(exception)).getHandle(js);\n  }\n\n  kj::Maybe<kj::Exception> tryUnwrap(Lock& js,\n      v8::Local<v8::Context> context,\n      v8::Local<v8::Value> handle,\n      kj::Exception*,\n      kj::Maybe<v8::Local<v8::Object>> parentObject) {\n\n    // If handle is a DOMException, then createTunneledException will not work\n    // here. We have to manually handle the DOMException case.\n    //\n    // Note that this is a general issue with any JSG_RESOURCE_TYPE that we\n    // happen to use as Errors. The createTunneledException() method uses V8's\n    // ToDetailString() to extract the detail about the error in a manner that\n    // is safe and side-effect free. Unfortunately, that mechanism does not\n    // work for JSG_RESOURCE_TYPE objects that are used as errors. For those,\n    // we need to drop down to the C++ interface and generate the kj::Exception\n    // ourselves. If any additional JSG_RESOURCE_TYPE error-like things are\n    // introduced, they'll need to be handled explicitly here also.\n    auto& wrapper = TypeWrapper::from(js.v8Isolate);\n    kj::Exception result = [&]() {\n      kj::Exception::Type excType = [&]() {\n        // Use .retryable and .overloaded properties as hints for what kj exception type to use.\n        if (handle->IsObject()) {\n          auto object = handle.As<v8::Object>();\n\n          if (js.toBool(check(object->Get(context, v8StrIntern(js.v8Isolate, \"overloaded\"_kj))))) {\n            return kj::Exception::Type::OVERLOADED;\n          }\n          if (js.toBool(check(object->Get(context, v8StrIntern(js.v8Isolate, \"retryable\"_kj))))) {\n            return kj::Exception::Type::DISCONNECTED;\n          }\n        }\n        return kj::Exception::Type::FAILED;\n      }();\n\n      KJ_IF_SOME(domException,\n          wrapper.tryUnwrap(\n              js, context, handle, static_cast<DOMException*>(nullptr), parentObject)) {\n        return KJ_EXCEPTION(FAILED,\n            kj::str(\"jsg.DOMException(\", domException.getName(), \"): \", domException.getMessage()));\n      } else {\n\n        static const constexpr kj::StringPtr PREFIXES[] = {\n#define V(name, _) name##_kj,\n          JS_ERROR_TYPES(V)\n#undef V\n              \"DOMException\"_kj,\n        };\n\n        kj::String reason;\n        if (!handle->IsObject()) {\n          // if the argument isn't an object, it couldn't possibly be an Error.\n          reason = kj::str(JSG_EXCEPTION(Error) \": \", handle);\n        } else {\n          reason = kj::str(handle);\n          bool found = false;\n          // If the error message starts with a platform error type that we tunnel,\n          // prefix it with \"jsg.\"\n          for (auto name: PREFIXES) {\n            if (reason.startsWith(name)) {\n              reason = kj::str(\"jsg.\", reason);\n              found = true;\n              break;\n            }\n          }\n          // Everything else should just come through as a normal error.\n          if (!found) {\n            reason = kj::str(JSG_EXCEPTION(Error) \": \", reason);\n          }\n        }\n        return kj::Exception(excType, __FILE__, __LINE__, kj::mv(reason));\n      }\n    }();\n\n    addExceptionDetail(js, result, handle);\n    addJsExceptionMetadata(js, result, handle);\n    return result;\n  }\n};\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/web-idl-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"jsg-test.h\"\n\nnamespace workerd::jsg::test {\nnamespace {\n\n// Static unit tests for Web IDL type concepts.\n\n// DictionaryType concept tests\nstatic_assert(webidl::DictionaryType<TestStruct>);\nstatic_assert(!webidl::DictionaryType<NumberBox>);\nstatic_assert(!webidl::DictionaryType<kj::Maybe<TestStruct>>);\n\n// NonCallbackInterfaceType concept tests\nstatic_assert(!webidl::NonCallbackInterfaceType<TestStruct>);\nstatic_assert(webidl::NonCallbackInterfaceType<NumberBox>);\nstatic_assert(!webidl::NonCallbackInterfaceType<kj::Maybe<NumberBox>>);\nstatic_assert(webidl::NonCallbackInterfaceType<Ref<NumberBox>>);\n\n// Backward-compatible variable template tests (these delegate to concepts)\nstatic_assert(webidl::isNonCallbackInterfaceType<TestStruct> == false);\nstatic_assert(webidl::isNonCallbackInterfaceType<NumberBox> == true);\nstatic_assert(webidl::isNonCallbackInterfaceType<kj::Maybe<NumberBox>> == false);\n\n// Additional type category concept tests\nstatic_assert(webidl::StringType<kj::String>);\nstatic_assert(webidl::StringType<USVString>);\nstatic_assert(webidl::StringType<DOMString>);\nstatic_assert(!webidl::StringType<int>);\n\nstatic_assert(webidl::NumericType<int>);\nstatic_assert(webidl::NumericType<double>);\nstatic_assert(!webidl::NumericType<kj::String>);\n\nstatic_assert(webidl::BooleanType<bool>);\nstatic_assert(!webidl::BooleanType<int>);\n\nstatic_assert(webidl::InterfaceLikeType<NumberBox>);\nstatic_assert(webidl::InterfaceLikeType<kj::Array<kj::byte>>);\nstatic_assert(!webidl::InterfaceLikeType<kj::String>);\n\nstatic_assert(webidl::DistinguishableType<kj::String>);\nstatic_assert(webidl::DistinguishableType<int>);\nstatic_assert(webidl::DistinguishableType<bool>);\nstatic_assert(webidl::DistinguishableType<NumberBox>);\n\nstatic_assert(webidl::nullableTypeCount<int> == 0);\nstatic_assert(webidl::nullableTypeCount<kj::Maybe<int>> == 1);\nstatic_assert(webidl::nullableTypeCount<kj::Maybe<int>, kj::Maybe<kj::String>> == 2);\nstatic_assert(webidl::nullableTypeCount<kj::OneOf<kj::Maybe<int>, kj::Maybe<kj::String>>> == 2);\nstatic_assert(\n    webidl::nullableTypeCount<kj::Maybe<kj::OneOf<kj::Maybe<int>, kj::Maybe<kj::String>>>> == 3);\nstatic_assert(webidl::nullableTypeCount<kj::OneOf<kj::Maybe<int>, kj::Maybe<kj::String>>,\n                  kj::OneOf<kj::Maybe<bool>, kj::Maybe<char>>> == 4);\nstatic_assert(webidl::nullableTypeCount<kj::Maybe<kj::OneOf<kj::Maybe<int>>>> == 2);\n\nstatic_assert(webidl::hasDuplicateTypes<int> == false);\nstatic_assert(webidl::hasDuplicateTypes<int, int> == true);\nstatic_assert(webidl::hasDuplicateTypes<int, bool> == false);\nstatic_assert(webidl::hasDuplicateTypes<bool, int, int> == true);\nstatic_assert(webidl::hasDuplicateTypes<int, bool, int> == true);\nstatic_assert(webidl::hasDuplicateTypes<int, int, bool> == true);\nstatic_assert(webidl::hasDuplicateTypes<int, int, bool, char> == true);\nstatic_assert(webidl::hasDuplicateTypes<int, bool, int, char> == true);\nstatic_assert(webidl::hasDuplicateTypes<int, bool, char, int> == true);\nstatic_assert(webidl::hasDuplicateTypes<bool, int, char, int> == true);\nstatic_assert(webidl::hasDuplicateTypes<bool, char, int, int> == true);\n\nstatic_assert(webidl::FlattenedTypeTraits<kj::String, USVString>::stringTypeCount == 2);\nstatic_assert(webidl::FlattenedTypeTraits<kj::String, DOMString>::stringTypeCount == 2);\n\n// =====================================================================================\n// ArgumentIndexes tests (meta.h)\n\n// Member function - no magic param.\nstruct Dummy {\n  int noMagic(int, double, bool);\n  int withLock(Lock&, int, double);\n  int withInfo(const v8::FunctionCallbackInfo<v8::Value>&, int);\n  int constNoMagic(int) const;\n  int constWithLock(Lock&, int, double) const;\n  int constWithInfo(const v8::FunctionCallbackInfo<v8::Value>&) const;\n};\n\nstatic_assert(\n    kj::isSameType<ArgumentIndexes<decltype(&Dummy::noMagic)>, kj::_::Indexes<0, 1, 2>>());\nstatic_assert(kj::isSameType<ArgumentIndexes<decltype(&Dummy::withLock)>, kj::_::Indexes<0, 1>>());\nstatic_assert(kj::isSameType<ArgumentIndexes<decltype(&Dummy::withInfo)>, kj::_::Indexes<0>>());\nstatic_assert(kj::isSameType<ArgumentIndexes<decltype(&Dummy::constNoMagic)>, kj::_::Indexes<0>>());\nstatic_assert(\n    kj::isSameType<ArgumentIndexes<decltype(&Dummy::constWithLock)>, kj::_::Indexes<0, 1>>());\nstatic_assert(kj::isSameType<ArgumentIndexes<decltype(&Dummy::constWithInfo)>, kj::_::Indexes<>>());\n\n// Free functions.\nstatic_assert(kj::isSameType<ArgumentIndexes<int(int, int)>, kj::_::Indexes<0, 1>>());\nstatic_assert(kj::isSameType<ArgumentIndexes<void(Lock&, int)>, kj::_::Indexes<0>>());\nstatic_assert(kj::isSameType<ArgumentIndexes<void()>, kj::_::Indexes<>>());\n\n// =====================================================================================\n// requiredArgumentCount tests (meta.h + type-wrapper.h)\n//\n// requiredArgumentCount<TypeWrapper, FuncType> uses the ValueLessParameter concept to detect\n// injected parameter types. We use a minimal mock wrapper that provides the right unwrap()\n// overloads so the concept checks succeed for TypeHandler<T> and a fake injected config type.\n\nstruct FakeConfig {};  // Simulates an InjectConfiguration<T> injected type.\n\nstruct MockTypeWrapper {\n  // Makes ValueLessParameter<MockTypeWrapper, TypeHandler<T>> true.\n  template <typename U>\n  const TypeHandler<U>& unwrap(Lock&, v8::Local<v8::Context>, TypeHandler<U>*);\n  // Makes ValueLessParameter<MockTypeWrapper, FakeConfig> true (simulates InjectConfiguration).\n  FakeConfig unwrap(Lock&, v8::Local<v8::Context>, FakeConfig*);\n};\n\n// Shorthand for the tests below.\ntemplate <typename T>\nconstexpr int rac = requiredArgumentCount<MockTypeWrapper, T>;\n\n// All required - count equals total visible args.\nstatic_assert(rac<int(int, double, bool)> == 3);\nstatic_assert(rac<int(Lock&, int, double, bool)> == 3);\n\n// No args — length is 0.\nstatic_assert(rac<void()> == 0);\nstatic_assert(rac<void(Lock&)> == 0);\n\n// Optional args stop the count.\nstatic_assert(rac<void(int, Optional<int>)> == 1);\nstatic_assert(rac<void(int, double, Optional<int>)> == 2);\nstatic_assert(rac<void(Optional<int>)> == 0);\nstatic_assert(rac<void(Lock&, int, Optional<int>)> == 1);\n\n// LenientOptional also stops the count.\nstatic_assert(rac<void(int, LenientOptional<int>)> == 1);\n\n// TypeHandler<T> is invisible - does not count and does not stop.\nstatic_assert(rac<void(TypeHandler<int>&, int, double)> == 2);\nstatic_assert(rac<void(int, TypeHandler<int>&, double)> == 2);\nstatic_assert(rac<void(int, TypeHandler<int>&, Optional<double>)> == 1);\n// Arguments following optionals are not counted.\nstatic_assert(rac<void(int, TypeHandler<int>&, Optional<double>, int)> == 1);\n\n// Arguments<T> is invisible - does not count and does not stop.\nstatic_assert(rac<void(int, Arguments<int>)> == 1);\n\n// InjectConfiguration types (e.g. CompatibilityFlags::Reader) are invisible.\nstatic_assert(rac<void(FakeConfig, int, double)> == 2);\nstatic_assert(rac<void(int, FakeConfig, double)> == 2);\nstatic_assert(rac<void(int, FakeConfig, Optional<double>)> == 1);\n\n// Member functions.\nstatic_assert(rac<decltype(&Dummy::noMagic)> == 3);\nstatic_assert(rac<decltype(&Dummy::withLock)> == 2);\nstatic_assert(rac<decltype(&Dummy::withInfo)> == 1);\nstatic_assert(rac<decltype(&Dummy::constNoMagic)> == 1);\nstatic_assert(rac<decltype(&Dummy::constWithLock)> == 2);\nstatic_assert(rac<decltype(&Dummy::constWithInfo)> == 0);\n\n// =====================================================================================\n// ValueLessParameter concept tests (type-wrapper.h)\n\nstatic_assert(ValueLessParameter<MockTypeWrapper, TypeHandler<int>> == true);\nstatic_assert(ValueLessParameter<MockTypeWrapper, FakeConfig> == true);\nstatic_assert(ValueLessParameter<MockTypeWrapper, int> == false);\nstatic_assert(ValueLessParameter<MockTypeWrapper, Optional<int>> == false);\nstatic_assert(ValueLessParameter<MockTypeWrapper, kj::String> == false);\n// Arguments<T> is NOT a ValueLessParameter — it has its own handling via isArguments<>().\nstatic_assert(ValueLessParameter<MockTypeWrapper, Arguments<int>> == false);\n\nKJ_TEST(\"web-idl meta\") {\n  // Nothing to actually do here; tests are compile-time\n}\n\n}  // namespace\n}  // namespace workerd::jsg::test\n"
  },
  {
    "path": "src/workerd/jsg/web-idl.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// INTERNAL IMPLEMENTATION FILE\n//\n// Type traits and concepts to help us map between C++ and Web IDL types.\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/meta.h>\n\n#include <kj/array.h>\n#include <kj/common.h>\n#include <kj/one-of.h>\n\nnamespace workerd::jsg::webidl {\n\n// =======================================================================================\n// Base detection concepts and helpers\n\n// True if T has a JSG_KIND static member (i.e., is a JSG type).\ntemplate <typename T>\nconcept HasJsgKind = requires { T::JSG_KIND; };\n\n// Helper to detect and unwrap Ref<T> types\ntemplate <typename T>\nstruct RefTraits_ {\n  static constexpr bool isRef = false;\n};\ntemplate <typename T>\nstruct RefTraits_<Ref<T>> {\n  static constexpr bool isRef = true;\n  using Type = T;\n};\n\ntemplate <typename T>\nconcept IsRef = RefTraits_<T>::isRef;\n\ntemplate <IsRef T>\nusing RefType = RefTraits_<T>::Type;\n\n// =======================================================================================\n// Optional type detection\n\ntemplate <typename T>\nconstexpr bool isOptional = false;\ntemplate <typename T>\nconstexpr bool isOptional<Optional<T>> = true;\ntemplate <typename T>\nconstexpr bool isOptional<LenientOptional<T>> = true;\n\ntemplate <typename T>\nconcept OptionalType = isOptional<T>;\n\n// Counts the number of Web IDL nullable types (modeled with kj::Maybe in JSG) that exist in\n// `T...`. This variable template is designed to accept unflattened OneOfs -- it will recurse\n// manually through the OneOfs, meaning `nullableTypeCount<Maybe<OneOf<Maybe<U>>>> == 2`.\n//\n// Implements the \"number of nullable member types\" algorithm defined here:\n// https://heycam.github.io/webidl/#dfn-number-of-nullable-member-types\ntemplate <typename... T>\nconstexpr size_t nullableTypeCount = 0;\n\ntemplate <typename T, typename... U>\nconstexpr size_t nullableTypeCount<T, U...> = nullableTypeCount<U...>;\ntemplate <typename T, typename... U>\nconstexpr size_t nullableTypeCount<kj::Maybe<T>, U...> =\n    1 + nullableTypeCount<T> + nullableTypeCount<U...>;\ntemplate <typename... T, typename... U>\nconstexpr size_t nullableTypeCount<kj::OneOf<T...>, U...> =\n    nullableTypeCount<T...> + nullableTypeCount<U...>;\n// TODO(soon): What to do with Optional? Unwrap? Hard error? It's not nullable.\n\n// =======================================================================================\n// Distinguishable type categories\n//\n// Web IDL defines nine different categories of distinguishable types, which are used to validate\n// union types. For a basic example, consider `kj::OneOf<double, int>`. From Web IDL's perspective,\n// these are both numeric types, thus the union is invalid.\n//\n// Note that these categories do not cover all Web IDL types, like Promises. Such types are not\n// allowed in unions under any circumstances.\n\n// True if T is a Web IDL dictionary type (modeled with JSG_STRUCT).\ntemplate <typename T>\nconcept DictionaryType = HasJsgKind<T> && (T::JSG_KIND == JsgKind::STRUCT);\n\n// Note: This covers Web IDL exception types as well. This doesn't seem to be a problem in practice,\n//   but it's worth knowing that the Web IDL spec considers the two categories distinct.\ntemplate <typename T>\nconcept NonCallbackInterfaceType_ = HasJsgKind<T> && (T::JSG_KIND == JsgKind::RESOURCE);\n\n// Helper to check if Ref<T> wraps a resource type\ntemplate <typename T>\nconstexpr bool isRefToResource_() {\n  if constexpr (IsRef<T>) {\n    return NonCallbackInterfaceType_<RefType<T>>;\n  } else {\n    return false;\n  }\n}\n\n// True if T is a Web IDL non-callback interface type (modeled with JSG_RESOURCE).\n// Handles both T and Ref<T> cases.\ntemplate <typename T>\nconcept NonCallbackInterfaceType = NonCallbackInterfaceType_<T> || isRefToResource_<T>();\n\ntemplate <typename T>\nconcept BufferSourceType = kj::isSameType<T, kj::Array<kj::byte>>() ||\n    kj::isSameType<T, kj::ArrayPtr<kj::byte>>() || kj::isSameType<T, kj::Array<const kj::byte>>() ||\n    kj::isSameType<T, kj::ArrayPtr<const kj::byte>>() || kj::isSameType<T, jsg::BufferSource>();\n\n// Helper for record type detection\ntemplate <typename T>\nstruct IsRecordType_: std::false_type {};\ntemplate <typename K, typename V>\nstruct IsRecordType_<Dict<V, K>>: std::true_type {};\n\ntemplate <typename T>\nconcept RecordType = IsRecordType_<T>::value;\n\ntemplate <typename T>\nconcept BooleanType = StrictlyBool<T> || kj::isSameType<T, NonCoercible<bool>>();\n\ntemplate <typename T>\nconcept IntegerType = kj::isSameType<T, int8_t>() || kj::isSameType<T, int16_t>() ||\n    kj::isSameType<T, int>() || kj::isSameType<T, int64_t>() || kj::isSameType<T, uint8_t>() ||\n    kj::isSameType<T, uint16_t>() || kj::isSameType<T, uint32_t>() ||\n    kj::isSameType<T, uint64_t>() || kj::isSameType<T, v8::Local<v8::BigInt>>();\n\ntemplate <typename T>\nconcept NumericType =\n    IntegerType<T> || kj::isSameType<T, double>() || kj::isSameType<T, NonCoercible<double>>();\n\ntemplate <typename T>\nconcept StringType = kj::isSameType<T, kj::String>() || kj::isSameType<T, USVString>() ||\n    kj::isSameType<T, DOMString>() || kj::isSameType<T, v8::Local<v8::String>>() ||\n    kj::isSameType<T, jsg::V8Ref<v8::String>>() || kj::isSameType<T, NonCoercible<kj::String>>() ||\n    kj::isSameType<T, NonCoercible<USVString>>() || kj::isSameType<T, NonCoercible<DOMString>>() ||\n    kj::isSameType<T, jsg::JsString>();\n\ntemplate <typename T>\nconcept ObjectType =\n    kj::isSameType<T, v8::Local<v8::Object>>() || kj::isSameType<T, v8::Global<v8::Object>>();\n\ntemplate <typename T>\nconcept SymbolType = false;\n// TODO(soon): kj::isSameType<T, v8::Local<v8::Symbol>>()?\n\n// Helper for callback function type detection\ntemplate <typename T>\nstruct IsCallbackFunctionType_: std::false_type {};\ntemplate <typename T>\nstruct IsCallbackFunctionType_<kj::Function<T>>: std::true_type {};\ntemplate <typename T>\nstruct IsCallbackFunctionType_<Constructor<T>>: std::true_type {};\n\ntemplate <typename T>\nconcept CallbackFunctionType = IsCallbackFunctionType_<T>::value;\n\n// True if T is a Web IDL buffer source type, exception type, or non-callback interface type. The\n// latter two cases are both modeled with JSG_RESOURCE_TYPE, which is why this trait only has two\n// predicates, rather than three.\ntemplate <typename T>\nconcept InterfaceLikeType = BufferSourceType<T> || NonCallbackInterfaceType<T>;\n\n// TODO(someday): Or callback interface types. Callback interface types seem to be going the way of\n//   the dodo -- fingers crossed that we won't have to implement them.\ntemplate <typename T>\nconcept DictionaryLikeType = DictionaryType<T> || RecordType<T>;\n\n// Helper for sequence-like type detection\ntemplate <typename T>\nstruct IsSequenceLikeType_: std::false_type {};\ntemplate <typename T>\nstruct IsSequenceLikeType_<kj::Array<T>>\n    : std::bool_constant<!kj::isSameType<T, kj::byte>() && !kj::isSameType<T, const kj::byte>()> {};\ntemplate <typename T>\nstruct IsSequenceLikeType_<Sequence<T>>: std::true_type {};\n\n// TODO(soon): And frozen array types.\ntemplate <typename T>\nconcept SequenceLikeType = IsSequenceLikeType_<T>::value;\n\n// True if T is listed in the table in Web IDL's distinguishable type algorithm:\n// https://heycam.github.io/webidl/#dfn-distinguishable, step 4.\ntemplate <typename T>\nconcept DistinguishableType =\n    BooleanType<T> || NumericType<T> || StringType<T> || ObjectType<T> || SymbolType<T> ||\n    InterfaceLikeType<T> || CallbackFunctionType<T> || DictionaryLikeType<T> || SequenceLikeType<T>;\n\ntemplate <typename T>\nconcept IndistinguishableType = !DistinguishableType<T>;\n\n// =======================================================================================\n// Backward-compatible variable templates\n//\n// These provide backward compatibility with code that uses the old constexpr bool style\n// that cannot use the concepts directly.\n\ntemplate <typename T>\nconstexpr bool isNonCallbackInterfaceType = NonCallbackInterfaceType<T>;\ntemplate <typename T>\nconstexpr bool isRecordType = RecordType<T>;\ntemplate <typename T>\nconstexpr bool isBooleanType = BooleanType<T>;\ntemplate <typename T>\nconstexpr bool isNumericType = NumericType<T>;\ntemplate <typename T>\nconstexpr bool isStringType = StringType<T>;\n\n// =======================================================================================\n// Type list utilities\n\ntemplate <typename... T>\nconstexpr bool hasDuplicateTypes = false;\ntemplate <typename T, typename U, typename... V>\nconstexpr bool hasDuplicateTypes<T, U, V...> =\n    kj::isSameType<T, U>() || hasDuplicateTypes<T, V...> || hasDuplicateTypes<U, V...>;\n\n// Traits computed over a flattened type list. Used for Web IDL union validation.\n// Uses the concept-based variable templates for counting.\ntemplate <typename... T>\nstruct FlattenedTypeTraits_ {\n  static constexpr size_t dictionaryTypeCount = (static_cast<size_t>(DictionaryType<T>) + ...);\n  static constexpr size_t booleanTypeCount = (static_cast<size_t>(BooleanType<T>) + ...);\n  static constexpr size_t numericTypeCount = (static_cast<size_t>(NumericType<T>) + ...);\n  static constexpr size_t stringTypeCount = (static_cast<size_t>(StringType<T>) + ...);\n  static constexpr size_t objectTypeCount = (static_cast<size_t>(ObjectType<T>) + ...);\n  static constexpr size_t symbolTypeCount = (static_cast<size_t>(SymbolType<T>) + ...);\n  static constexpr size_t interfaceLikeTypeCount =\n      (static_cast<size_t>(InterfaceLikeType<T>) + ...);\n  static constexpr size_t callbackFunctionTypeCount =\n      (static_cast<size_t>(CallbackFunctionType<T>) + ...);\n  static constexpr size_t dictionaryLikeTypeCount =\n      (static_cast<size_t>(DictionaryLikeType<T>) + ...);\n  static constexpr size_t sequenceLikeTypeCount = (static_cast<size_t>(SequenceLikeType<T>) + ...);\n\n  static constexpr bool hasDuplicateTypes = webidl::hasDuplicateTypes<T...>;\n  static constexpr bool hasIndistinguishableTypes = (IndistinguishableType<T> || ...);\n  static constexpr bool hasOptionalTypes = (OptionalType<T> || ...);\n};\n\ntemplate <typename Traits, typename... T>\nstruct Flatten;\ntemplate <typename Traits>\nstruct Flatten<Traits>: Traits {};\ntemplate <template <typename...> class Traits, typename... T, typename U, typename... V>\nstruct Flatten<Traits<T...>, U, V...>: Flatten<Traits<T..., U>, V...> {};\ntemplate <template <typename...> class Traits, typename... T, typename U, typename... V>\nstruct Flatten<Traits<T...>, Ref<U>, V...>: Flatten<Traits<T...>, U, V...> {};\ntemplate <template <typename...> class Traits, typename... T, typename U, typename... V>\nstruct Flatten<Traits<T...>, kj::Maybe<U>, V...>: Flatten<Traits<T...>, U, V...> {};\ntemplate <template <typename...> class Traits, typename... T, typename... U, typename... V>\nstruct Flatten<Traits<T...>, kj::OneOf<U...>, V...>: Flatten<Traits<T...>, U..., V...> {};\n\n// Flattens a list of types (recursively unwraps Maybes and OneOfs) and exposes some data about\n// those types: number of dictionary types, whether or not there are duplicate types, presence of\n// indistinguishable types, etc.\n//\n// Note: Web IDL dictates that we flatten nullables (Maybe) and unions (OneOf). We add one more\n//   flattening: Ref<T> -> T. We do this because JSG has two models for non-callback interface\n//   types: Ref<T> (unwrapped by reference) and T (unwrapped by copy/move). We need to be able to\n//   catch ambiguous OneOfs like `kj::OneOf<Interface, Ref<Interface>>`.\ntemplate <typename... T>\nusing FlattenedTypeTraits = Flatten<FlattenedTypeTraits_<>, T...>;\n\n// Instantiate to check that the type T satisfies the constraints on union types prescribed by\n// Web IDL spec: https://heycam.github.io/webidl/#idl-union\ntemplate <typename T>\nstruct UnionTypeValidator {\n  using Traits = FlattenedTypeTraits<T>;\n\n  static_assert(nullableTypeCount<T> + Traits::dictionaryTypeCount <= 1,\n      \"A Web IDL union (OneOf) may contain at most one nullable or dictionary type.\");\n\n  static_assert(Traits::booleanTypeCount <= 1,\n      \"A Web IDL union (OneOf) may contain at most one boolean type.\");\n  static_assert(Traits::numericTypeCount <= 1,\n      \"A Web IDL union (OneOf) may contain at most one numeric type.\");\n  static_assert(\n      Traits::stringTypeCount <= 1, \"A Web IDL union (OneOf) may contain at most one string type.\");\n  static_assert(\n      Traits::objectTypeCount <= 1, \"A Web IDL union (OneOf) may contain at most one object type.\");\n  static_assert(Traits::objectTypeCount == 0 ||\n          Traits::interfaceLikeTypeCount + Traits::callbackFunctionTypeCount +\n                  Traits::dictionaryLikeTypeCount + Traits::sequenceLikeTypeCount ==\n              0,\n      \"A Web IDL union (OneOf) may contain an object type only if it also contains no \"\n      \"interface-like, callback function, dictionary-like, or sequence-like types.\");\n  static_assert(\n      Traits::symbolTypeCount <= 1, \"A Web IDL union (OneOf) may contain at most one symbol type.\");\n  static_assert(Traits::callbackFunctionTypeCount <= 1,\n      \"A Web IDL union (OneOf) may contain at most one callback function type.\");\n  // TODO(cleanup): This next check made it impossible to define a type for named top-level module\n  //   exports, which are allowed to be objects or classes. I don't understand why this restriction\n  //   existed since it's definitely possible to distinguish a function from a non-function. Do\n  //   we really need to be enforcing WebIDL rules to the letter even when our type system is more\n  //   expressive?\n  //   static_assert(Traits::callbackFunctionTypeCount == 0 || Traits::dictionaryLikeTypeCount == 0,\n  //       \"A Web IDL union (OneOf) may contain a callback function type only if it also contains no \"\n  //       \"dictionary-like types.\");\n  static_assert(Traits::dictionaryLikeTypeCount <= 1,\n      \"A Web IDL union (OneOf) may contain at most one dictionary-like type.\");\n  static_assert(Traits::sequenceLikeTypeCount <= 1,\n      \"A Web IDL union (OneOf) may contain at most one sequence-like type.\");\n\n  // There is no `Traits::interfaceLikeTypeCount <= 1` check because Web IDL unions can have\n  // multiple interface-like types as long as:\n  //\n  //   1. They are not the same type.\n  //   2. No single platform object implements more than one of the interfaces in question.\n  //\n  // Condition (1) will be taken care of with the `hasDuplicateTypes` check below (and is why\n  // `FlattenedTypeTraits` unwraps `Ref`s). Condition (2) is difficult to guarantee, but unless\n  // we start using multiple-inheritance in our API implementation types, we should be safe.\n\n  static_assert(\n      !Traits::hasDuplicateTypes, \"A Web IDL union (OneOf) may not contain duplicate types.\");\n  // TODO(cleanup): This rule is incompatible with addEventListener(), whose second argument is\n  //   allowed to be either a function or an object with a `handleEvent()` method. If such a\n  //   fundamental web interface violates this rule, should we really be enforcing it?\n  //   static_assert(!Traits::hasIndistinguishableTypes,\n  //       \"A Web IDL union (OneOf) may only contain distinguishable types, i.e., types which fall \"\n  //       \"into one of the following categories: boolean, numeric, string, object, symbol, \"\n  //       \"interface-like, callback function, dictionary-like, or sequence-like. See the definition \"\n  //       \"of 'distinguishable' in the Web IDL spec for details.\");\n  static_assert(!Traits::hasOptionalTypes,\n      \"A Web IDL union (OneOf) may not contain any Optional<T> types. Optional<T> must only be \"\n      \"used to mark optional function/method parameters and non-required members of a \"\n      \"dictionary. Use Maybe<T> to represent nullable types.\");\n};\n\n}  // namespace workerd::jsg::webidl\n"
  },
  {
    "path": "src/workerd/jsg/wrappable.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"wrappable.h\"\n\n#include \"jsg.h\"\n#include \"setup.h\"\n\n#include <cppgc/allocation.h>\n#include <cppgc/garbage-collected.h>\n#include <v8-cppgc.h>\n\n#include <kj/async.h>\n#include <kj/debug.h>\n#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)\n#include <sanitizer/asan_interface.h>\n#endif\n\nnamespace workerd::jsg {\n\nnamespace {\n\nstatic thread_local bool inCppgcShimDestructor = false;\n\n};\n\nbool HeapTracer::isInCppgcDestructor() {\n  return inCppgcShimDestructor;\n}\n\nvoid HeapTracer::clearWrappers() {\n  // When clearing wrappers (at isolate shutdown), we may be destroying objects that were recently\n  // determined to be unreachable, but the CppgcShim destructors haven't been run yet. We need to\n  // treat this case as if we are running CppgcShim destructors, that is, assume any\n  // TracedReferences we destroy have already been collected so cannot be touched.\n  // TODO(cleanup): Rename `inCppgcShimDestructor` to `possiblyCollectingUnreachableObject`?\n  KJ_ASSERT(!inCppgcShimDestructor);\n  inCppgcShimDestructor = true;\n  KJ_DEFER(inCppgcShimDestructor = false);\n\n  while (!wrappers.empty()) {\n    // Don't freelist the shim because we're shutting down anyway.\n    auto& wrappable = wrappers.front();\n    auto ownWrappable = wrappable.detachWrapper(false);\n    // Clear the isolate pointer so that any code that later tries to use it (e.g.,\n    // maybeDeferDestruction() or GcVisitor) will see that the isolate is gone and skip\n    // V8 operations. Without this, objects that outlive their isolate (like WebSockets\n    // stored in a HibernationManager) would have a dangling isolate pointer and crash\n    // when trying to check v8::Locker::IsLocked() or create V8 handles.\n    ownWrappable->isolate = nullptr;\n  }\n  clearFreelistedShims();\n}\n\nusing JSGWrappable = workerd::jsg::Wrappable;\n\n// V8's GC integrates with cppgc, aka \"oilpan\", a garbage collector for C++ objects. We want to\n// integrate with the GC in order to receive GC visitation callbacks, so that the GC is able to\n// trace through our C++ objects to find what is reachable through them. The only way for us to\n// support this is by integrating with cppgc.\n//\n// However, workerd was written using KJ idioms long before cppgc existed. Rewriting all our code\n// to use cppgc allocation instead would be a highly invasive change. Maybe we'll do it someday,\n// but today is not the day. So, our API objects continue to be allocated on the regular (non-GC)\n// C++ heap.\n//\n// CppgcShim provides a compromise. For each API object that has been wrapped for use from JS,\n// we create a CppgcShim object on the cppgc heap. This basically just contains a pointer to the\n// regular old C++ object. This lets us get our GC visitation without fully integrating with\n// cppgc.\n//\n// There is an additional trick here: As of this writing, cppgc objects cannot be collected\n// during V8's minor GC passes (\"scavenge\" passes). Only full GCs (\"trace\" passes) can collect\n// them. But we do want our API objects to be collectable during minor GC. We integrate with V8's\n// EmbedderRootsHandler to get notification when these objects can be collected. But when they\n// are, what happens to the CppgcShim object we allocated? We can't force it to be collected\n// early. We could just discard it and let it be collected during the next major GC, but that\n// would mean accumulating a lot of garbage shims. Instead, we freelist the objects: when a\n// wrapper is collected during minor GC, the CppgcShim is placed in a freelist and can be\n// reused for a future allocation, if that allocation occurs before the next major GC. When a\n// major GC occurs, the freelist is cleared, since any unreachable CppgcShim objects are likely\n// condemned after that point and will be deleted shortly thereafter.\nclass Wrappable::CppgcShim final: public v8::Object::Wrappable {\n public:\n  CppgcShim(JSGWrappable& wrappable): state(Active{kj::addRef(wrappable)}) {\n    KJ_DASSERT(wrappable.cppgcShim == kj::none);\n    wrappable.cppgcShim = *this;\n  }\n\n  ~CppgcShim() {\n    // (Unlike most KJ destructors, we don't mark this noexcept(false) because it's called from\n    // V8 which doesn't support exceptions.)\n\n    KJ_DASSERT(!inCppgcShimDestructor);\n    inCppgcShimDestructor = true;\n    KJ_DEFER(inCppgcShimDestructor = false);\n\n    KJ_SWITCH_ONEOF(state) {\n      KJ_CASE_ONEOF(active, Active) {\n        KJ_DASSERT(&KJ_ASSERT_NONNULL(active.wrappable->cppgcShim) == this);\n        KJ_DASSERT(active.wrappable->strongWrapper.IsEmpty());\n        active.wrappable->detachWrapper(false);\n      }\n      KJ_CASE_ONEOF(freelisted, Freelisted) {\n        KJ_DASSERT(&KJ_ASSERT_NONNULL(*freelisted.prev) == this);\n        *freelisted.prev = freelisted.next;\n        KJ_IF_SOME(next, freelisted.next) {\n          KJ_DASSERT(next.state.get<Freelisted>().prev == &freelisted.next);\n          next.state.get<Freelisted>().prev = freelisted.prev;\n        }\n      }\n      KJ_CASE_ONEOF(d, Dead) {}\n    }\n  }\n\n  void Trace(cppgc::Visitor* visitor) const override {\n    KJ_SWITCH_ONEOF(state) {\n      KJ_CASE_ONEOF(active, Active) {\n        active.wrappable->traceFromV8(*visitor);\n      }\n      KJ_CASE_ONEOF(freelisted, Freelisted) {\n        // We're tracing a shim for an object that was collected in minor GC. This could happen\n        // due to conservative GC or due to incremental marking. Unfortunately the shim won't be\n        // collected on this pass but hopefully it can be on the next pass.\n      }\n      KJ_CASE_ONEOF(d, Dead) {}\n    }\n  }\n\n  const char* GetHumanReadableName() const override {\n    return \"CppgcShim\";\n  }\n\n  struct Active {\n    kj::Own<JSGWrappable> wrappable;\n  };\n\n  // The JavaScript wrapper using this shim was collected in a minor GC. cppgc objects can only\n  // be collected in full GC, so we freelist the shim object in the meantime.\n  struct Freelisted {\n    kj::Maybe<JSGWrappable::CppgcShim&> next;\n    kj::Maybe<JSGWrappable::CppgcShim&>* prev;\n    // kj::List doesn't quite work here because the list link is inside a OneOf. Also we want a\n    // LIFO list anyway so we don't need a tail pointer, which makes things easier. So we do it\n    // manually.\n  };\n  struct Dead {};\n\n  kj::StringPtr jsgGetMemoryName() const {\n    return \"CppgcShim\"_kjc;\n  }\n  size_t jsgGetMemorySelfSize() const {\n    return sizeof(CppgcShim);\n  }\n  void jsgGetMemoryInfo(MemoryTracker& tracker) const {\n    KJ_IF_SOME(active, state.tryGet<Active>()) {\n      tracker.trackField(\"wrappable\", active.wrappable);\n    }\n  }\n  bool jsgGetMemoryInfoIsRootNode() const {\n    return false;\n  }\n\n  mutable kj::OneOf<Active, Freelisted, Dead> state;\n  // This is `mutable` because `Trace()` is const. We configure V8 to perform traces atomically in\n  // the main thread so concurrency is not a concern.\n};\n\nvoid HeapTracer::addToFreelist(JSGWrappable::CppgcShim& shim) {\n  auto& freelisted = shim.state.init<JSGWrappable::CppgcShim::Freelisted>();\n  freelisted.next = freelistedShims;\n  KJ_IF_SOME(next, freelisted.next) {\n    next.state.get<JSGWrappable::CppgcShim::Freelisted>().prev = &freelisted.next;\n  }\n  freelisted.prev = &freelistedShims;\n  freelistedShims = shim;\n}\n\nJSGWrappable::CppgcShim* HeapTracer::allocateShim(JSGWrappable& wrappable) {\n  KJ_IF_SOME(shim, freelistedShims) {\n    freelistedShims = shim.state.get<JSGWrappable::CppgcShim::Freelisted>().next;\n    KJ_IF_SOME(next, freelistedShims) {\n      next.state.get<JSGWrappable::CppgcShim::Freelisted>().prev = &freelistedShims;\n    }\n    shim.state = JSGWrappable::CppgcShim::Active{kj::addRef(wrappable)};\n    KJ_DASSERT(wrappable.cppgcShim == kj::none);\n    wrappable.cppgcShim = shim;\n    return &shim;\n  } else {\n    auto& cppgcAllocHandle = isolate->GetCppHeap()->GetAllocationHandle();\n    return cppgc::MakeGarbageCollected<JSGWrappable::CppgcShim>(cppgcAllocHandle, wrappable);\n  }\n}\n\nvoid HeapTracer::clearFreelistedShims() {\n  for (;;) {\n    KJ_IF_SOME(shim, freelistedShims) {\n      freelistedShims = shim.state.get<JSGWrappable::CppgcShim::Freelisted>().next;\n      shim.state = JSGWrappable::CppgcShim::Dead{};\n    } else {\n      break;\n    }\n  }\n}\n\nvoid HeapTracer::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n  for (const auto& wrapper: wrappers) {\n    tracker.trackField(\"wrapper\", wrapper);\n  }\n  // TODO(soon): Track the other fields here?\n}\n\nkj::Own<JSGWrappable> JSGWrappable::detachWrapper(bool shouldFreelistShim) {\n  KJ_IF_SOME(shim, cppgcShim) {\n#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)\n    // There's a possibility that the CppgcShim has already been found to be unreachable by a GC\n    // pass, but has not actually been destroyed yet. For some reason, cppgc likes to delay the\n    // calling of actual destructors. However, in ASAN builds, cppgc will poison the memory in the\n    // meantime, because it figures that we \"shouldn't\" be accessing unreachable memory. This\n    // assumption makes sense in the abstract, but not for our specific use case, where we are\n    // essentially maintaining a weak pointer to the CppgcShim. If the destructor had been called,\n    // then `cppgcShim` here would have been nulled out at that time. We're expecting that until\n    // the destructor is called, we can still safely access the object to detach the wrapper.\n    //\n    // So to work around cppgc's incorrect assumption, we manually unpoison the memory.\n    //\n    // Note: An alternative strategy could have been for CppgcShim itself to allocate a separate\n    // C++ heap object to store its own state in, so that that state could be modified even while\n    // the CppgcShim object itself is poisoned. In this case `Wrappable::cppgcShim` would change to\n    // point at this state object, not to the `CppgcShim` itself. However, this approach would\n    // require extra heap allocation for everyone, just to satisfy ASAN, which seems undesirable.\n    ASAN_UNPOISON_MEMORY_REGION(&shim, sizeof(shim));\n#endif\n\n    auto& tracer = HeapTracer::getTracer(isolate);\n    auto result =\n        kj::mv(KJ_ASSERT_NONNULL(shim.state.tryGet<JSGWrappable::CppgcShim::Active>()).wrappable);\n    if (shouldFreelistShim) {\n      tracer.addToFreelist(shim);\n    } else {\n      shim.state = JSGWrappable::CppgcShim::Dead{};\n    }\n    wrapper = kj::none;\n    cppgcShim = kj::none;\n    strongWrapper.Reset();\n    tracer.removeWrapper({}, *this);\n    if (strongRefcount > 0) {\n      // Need to visit child references in order to convert them to strong references, since we\n      // no longer have an intervening wrapper.\n      GcVisitor visitor(*this, kj::none);\n      jsgVisitForGc(visitor);\n    }\n    return result;\n  } else {\n    return {};\n  }\n}\n\nv8::Local<v8::Object> Wrappable::getHandle(v8::Isolate* isolate) {\n  return KJ_REQUIRE_NONNULL(tryGetHandle(isolate));\n}\n\nvoid Wrappable::addStrongRef() {\n  // The `isolate == nullptr` check here ensures that `js.alloc<T>()` can be used with no\n  // isolate, simply allocating the object as a normal C++ heap object.\n  KJ_DREQUIRE(isolate == nullptr || v8::Isolate::TryGetCurrent() != nullptr,\n      \"referencing wrapper without isolate lock\");\n  if (strongRefcount++ == 0) {\n    // This object previously had no strong references, but now it has one.\n    KJ_IF_SOME(w, wrapper) {\n      // Copy the traced reference into the strong reference.\n      v8::HandleScope scope(isolate);\n      strongWrapper.Reset(isolate, w.Get(isolate));\n    } else {\n      // Since we have no JS wrapper, we're forced to recursively mark all references reachable\n      // through this wrapper as strong.\n      GcVisitor visitor(*this, kj::none);\n      jsgVisitForGc(visitor);\n    }\n  }\n}\nvoid Wrappable::removeStrongRef() {\n  KJ_DREQUIRE(isolate == nullptr || v8::Isolate::TryGetCurrent() == isolate,\n      \"destroying wrapper without isolate lock\");\n  if (--strongRefcount == 0) {\n    // This was the last strong reference.\n    if (wrapper == kj::none) {\n      // We have no wrapper. We need to mark all references held by this object as weak.\n      if (isolate != nullptr) {\n        // But only if the current isolate isn't null. If strong ref count is zero,\n        // the wrapper is empty, and isolate is null, then the child handles it has will\n        // be released anyway (since we're about to be destroyed), thus this visitation\n        // isn't required (and may be buggy, since it may happen outside the isolate lock).\n        GcVisitor visitor(*this, kj::none);\n        jsgVisitForGc(visitor);\n      }\n    } else {\n      // Just clear the strong ref.\n      strongWrapper.Reset();\n    }\n  }\n}\n\nvoid Wrappable::maybeDeferDestruction(bool strong, kj::Own<void> ownSelf, Wrappable* self) {\n  DISALLOW_KJ_IO_DESTRUCTORS_SCOPE;\n\n  auto item = IsolateBase::RefToDelete(strong, kj::mv(ownSelf), self);\n\n  if (isolate == nullptr || v8::Locker::IsLocked(isolate)) {\n    // If we never attached a wrapper and were never traced, or the isolate is already locked, then\n    // we can just destroy the Wrappable immediately.\n    auto drop = kj::mv(item);\n  } else {\n    // Otherwise, we have a wrapper and we don't have the isolate locked.\n    auto& jsgIsolate = *reinterpret_cast<IsolateBase*>(isolate->GetData(SET_DATA_ISOLATE_BASE));\n    jsgIsolate.deferDestruction(kj::mv(item));\n  }\n}\n\nvoid Wrappable::traceFromV8(cppgc::Visitor& cppgcVisitor) {\n  cppgcVisitor.Trace(KJ_ASSERT_NONNULL(wrapper));\n  GcVisitor visitor(*this, cppgcVisitor);\n  jsgVisitForGc(visitor);\n}\n\nvoid Wrappable::attachWrapper(\n    v8::Isolate* isolate, v8::Local<v8::Object> object, bool needsGcTracing) {\n  auto& tracer = HeapTracer::getTracer(isolate);\n\n  KJ_REQUIRE(wrapper == kj::none);\n  KJ_REQUIRE(strongWrapper.IsEmpty());\n\n  // The C++ Wrappable object must hold a TracedReference to its own JavaScript wrapper, while\n  // such a wrapper exists. This way, if the object is reached through C++ again later, we can\n  // return the same object to JavaScript.\n  //\n  // This reference is special: it is marked as \"droppable\". This tells V8 that we know how to\n  // recreate this wrapper on-demand (from the C++ object). This is an optimization: If the\n  // application drops all of its direct references to the wrapper, such that object is only\n  // reachable implicitly through C++ objects, then V8 can drop the wrapper entirely and have us\n  // recreate it later, when JS needs it again.\n  //\n  // For example, consider a Request object that contains a Headers object. Say the application\n  // accesses the Headers briefly, like `request.headers.get(\"foo\")` -- it doesn't keep around a\n  // direct reference to the Headers. But it DOES keep around a reference to the Request, and the\n  // C++ API object backing the Request keeps a `jsg::Ref<Headers>`. In this case, we do not really\n  // need the JavaScript wrapper for `Headers` to stick around. We know we can create a new one if\n  // and when it is needed. So we tell V8 that our internal reference is \"droppable\", so that it\n  // will go ahead and drop it in this scenario. (Specifically, v8 calls\n  // `EmbedderRootsHandler::ResetRoot()`, which is implemented by our `HeapTracer`, to tell us that\n  // it is dropping the wrapper.)\n  //\n  // Note that there are things that the application might do which actually make it unsafe for us\n  // to drop and recreate the wrapper. For example, the application could add a property to the\n  // wrapper object itself, like `request.headers.foo = 123`. Later on, when the app accesses\n  // `request.headers.foo` again, it expects the property will still be there. But if we dropped\n  // our wrapper and recreated it, the property would be gone. Luckily, V8 already handles this\n  // for us! V8 knows not to drop our wrapper if the application has done anything with it such\n  // that a recreated wrapper would no longer be equivalent.\n  wrapper.emplace(isolate, object, v8::TracedReference<v8::Object>::IsDroppable());\n  this->isolate = isolate;\n\n  // Add to list of objects to force-clean at isolate shutdown.\n  tracer.addWrapper({}, *this);\n\n  // Set up internal fields for a newly-allocated object.\n  KJ_REQUIRE(object->InternalFieldCount() == Wrappable::INTERNAL_FIELD_COUNT);\n  // The third argument is the type tag, a small integer that should be\n  // different for every pointer type to avoid type confusion attacks.  We just\n  // use the slot index for now, since we have a different pointer type for\n  // each slot.\n  auto tagAddress = const_cast<uint16_t*>(&WORKERD_WRAPPABLE_TAG);\n  object->SetAlignedPointerInInternalField(WRAPPABLE_TAG_FIELD_INDEX, tagAddress,\n      static_cast<v8::EmbedderDataTypeTag>(WRAPPABLE_TAG_FIELD_INDEX));\n  object->SetAlignedPointerInInternalField(WRAPPED_OBJECT_FIELD_INDEX, this,\n      static_cast<v8::EmbedderDataTypeTag>(WRAPPED_OBJECT_FIELD_INDEX));\n\n  v8::Object::Wrap<WRAPPABLE_TAG>(isolate, object, tracer.allocateShim(*this));\n\n  if (strongRefcount > 0) {\n    strongWrapper.Reset(isolate, object);\n\n    // This object has untraced references, but didn't have a wrapper. That means that any refs\n    // transitively reachable through the reference are strong. Now that a wrapper exists, the\n    // refs will be traced when the wrapper is traced, so they should be converted to traced\n    // references. Performing a visitation pass will update them.\n    GcVisitor visitor(*this, kj::none);\n    jsgVisitForGc(visitor);\n  }\n}\n\nvoid Wrappable::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {\n  tracker.trackField(\"cppgcshim\", cppgcShim);\n}\n\nv8::Local<v8::Object> Wrappable::attachOpaqueWrapper(\n    v8::Local<v8::Context> context, bool needsGcTracing) {\n  auto isolate = v8::Isolate::GetCurrent();\n  auto object =\n      jsg::check(IsolateBase::getOpaqueTemplate(isolate)->InstanceTemplate()->NewInstance(context));\n  attachWrapper(isolate, object, needsGcTracing);\n  return object;\n}\n\nkj::Maybe<Wrappable&> Wrappable::tryUnwrapOpaque(\n    v8::Isolate* isolate, v8::Local<v8::Value> handle) {\n  if (handle->IsObject()) {\n    v8::Local<v8::Object> instance =\n        v8::Local<v8::Object>::Cast(handle)->FindInstanceInPrototypeChain(\n            IsolateBase::getOpaqueTemplate(isolate));\n    if (!instance.IsEmpty()) {\n      return *reinterpret_cast<Wrappable*>(\n          instance->GetAlignedPointerFromInternalField(WRAPPED_OBJECT_FIELD_INDEX,\n              static_cast<v8::EmbedderDataTypeTag>(WRAPPED_OBJECT_FIELD_INDEX)));\n    }\n  }\n\n  return kj::none;\n}\n\nvoid Wrappable::jsgVisitForGc(GcVisitor& visitor) {\n  // Nothing; subclasses that need tracing will override.\n}\n\nvoid Wrappable::visitRef(GcVisitor& visitor, kj::Maybe<Wrappable&>& refParent, bool& refStrong) {\n  KJ_IF_SOME(p, refParent) {\n    KJ_ASSERT(&p == &visitor.parent);\n  } else {\n    refParent = visitor.parent;\n  }\n\n  if (isolate == nullptr) {\n    isolate = visitor.parent.isolate;\n  }\n\n  // Make ref strength match the parent.\n  if (visitor.parent.strongRefcount > 0 && visitor.parent.wrapper == kj::none) {\n    // This reference should be strong, because the parent has strong refs and does not have its\n    // own wrapper that will be traced.\n\n    if (!refStrong) {\n      // Ref transitions from weak to strong.\n      //\n      // This should never happen during a GC pass, since we should only be visiting traced\n      // references then.\n      KJ_ASSERT(visitor.cppgcVisitor == kj::none);\n      addStrongRef();\n      refStrong = true;\n    }\n  } else {\n    if (refStrong) {\n      // Ref transitions from strong to weak.\n      //\n      // Note that a Ref can become weak here as part of a GC pass. Specifically, the Ref might\n      // have previously been added to an object that already had a JS wrapper before the Ref was\n      // added. In this case, we won't detect that the Ref is traced until the next GC pass reaches\n      // it.\n      refStrong = false;\n      removeStrongRef();\n    }\n  }\n\n  KJ_IF_SOME(cgv, visitor.cppgcVisitor) {\n    // We're visiting for the purpose of a GC trace.\n    KJ_IF_SOME(w, wrapper) {\n      cgv.Trace(w);\n    } else {\n      // This object doesn't currently have a wrapper, so traces must transitively trace through\n      // it. However, as an optimization, we can skip the trace if we've already been traced in\n      // this trace pass.\n      GcVisitor subVisitor(*this, visitor.cppgcVisitor);\n      jsgVisitForGc(subVisitor);\n    }\n  }\n}\n\nvoid GcVisitor::visit(Data& value) {\n  if (!value.handle.IsEmpty()) {\n    // Make ref strength match the parent.\n    if (parent.strongRefcount > 0 && parent.wrapper == kj::none) {\n      // This is directly reachable by a strong ref, so mark the handle strong.\n      if (value.tracedHandle != kj::none) {\n        // Convert the handle back to strong and discard the traced reference.\n        value.handle.ClearWeak();\n        value.tracedHandle = kj::none;\n      }\n    } else {\n      // This is only reachable via traced objects, so the handle should be weak, and we should\n      // hold a TracedReference alongside it.\n      if (value.tracedHandle == kj::none) {\n        // Create the TracedReference.\n        v8::HandleScope scope(parent.isolate);\n        value.tracedHandle =\n            v8::TracedReference<v8::Data>(parent.isolate, value.handle.Get(parent.isolate));\n\n        // Set the handle weak.\n        value.handle.SetWeak();\n      }\n    }\n\n    KJ_IF_SOME(c, cppgcVisitor) {\n      KJ_IF_SOME(t, value.tracedHandle) {\n        c.Trace(t);\n      }\n    }\n  }\n}\n\nvoid GcVisitor::visit(v8::Global<v8::Value>& strong, v8::TracedReference<v8::Data>& traced) {\n  if (strong.IsEmpty()) {\n    return;\n  }\n\n  // Mirror visit(Data&): make handle strength match the parent.\n  //\n  // The `parent.wrapper == kj::none` check mirrors the same condition in\n  // visit(Data&): even when strongRefcount > 0, if the JS wrapper already\n  // exists we must keep the handle in traced mode so cppgc can follow edges\n  // from it.  Only when there is no wrapper yet (object not yet exported to JS)\n  // does a positive strongRefcount alone justify keeping the handle strong.\n  if (parent.strongRefcount > 0 && parent.wrapper == kj::none) {\n    // Parent has strong Rust refs and no JS wrapper — keep handle strong,\n    // discard any traced ref.\n    if (!traced.IsEmpty()) {\n      strong.ClearWeak();\n      traced.Reset();\n    }\n  } else {\n    // Parent is only reachable via GC tracing — downgrade to a TracedReference.\n    if (traced.IsEmpty()) {\n      v8::HandleScope scope(parent.isolate);\n      traced.Reset(parent.isolate, strong.Get(parent.isolate));\n      strong.SetWeak();\n    }\n  }\n\n  KJ_IF_SOME(c, cppgcVisitor) {\n    if (!traced.IsEmpty()) {\n      c.Trace(traced);\n    }\n  }\n}\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/jsg/wrappable.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// INTERNAL IMPLEMENTATION FILE\n//\n// This file defines basic helpers involved in wrapping C++ objects for JavaScript consumption,\n// including garbage-collecting those objects.\n\n#include <v8-context.h>\n#include <v8-object.h>\n#include <v8-version.h>\n\n#include <kj/common.h>\n#include <kj/debug.h>\n#include <kj/list.h>\n#include <kj/refcount.h>\n#include <kj/vector.h>\n\n// Niche value optimization for v8::TracedReference<T>. This teaches kj::Maybe to use\n// TracedReference's built-in empty state (IsEmpty()) as the \"none\" representation, eliminating\n// the extra bool + alignment padding that kj::Maybe normally adds. This saves 8 bytes per\n// Maybe<TracedReference<T>> instance while preserving full type safety.\nnamespace kj {\ntemplate <typename T>\nstruct MaybeTraits<v8::TracedReference<T>> {\n  static void initNone(v8::TracedReference<T>* ptr) noexcept {\n    ctor(*ptr);\n  }\n  static bool isNone(const v8::TracedReference<T>& ref) noexcept {\n    return ref.IsEmpty();\n  }\n  static constexpr bool noneIsMoveSafe = false;\n};\n}  // namespace kj\n\nnamespace cppgc {\nclass Visitor;\n}\n\nnamespace workerd::jsg {\n\n// The ContextPointerSlot enum defines the embedder slots we use in v8::Context for\n// storing pointers to various important objects.\nenum class ContextPointerSlot : int {\n  // Pointer slot 0 is special and should never be used by us.\n  RESERVED = 0,\n  GLOBAL_WRAPPER = 1,\n  MODULE_REGISTRY = 2,\n  EXTENDED_CONTEXT_WRAPPER = 3,\n  VIRTUAL_FILE_SYSTEM = 4,\n  // Keep the MAX_POINTER_SLOT as the last entry and always set to\n  // to the highest value of the other entries. We use this to\n  // ensure that the highest used index is always initialized in\n  // every context we create without having to update the specific\n  // callsites whenever we add a new slot. We can just make the\n  // change here.\n  MAX_POINTER_SLOT = VIRTUAL_FILE_SYSTEM,\n};\n\ninline void setAlignedPointerInEmbedderData(\n    v8::Local<v8::Context> context, ContextPointerSlot slot, void* ptr) {\n  // The type tag is a small integer that should be different for every pointer\n  // type to avoid type confusion attacks.  We just use the slot index for now,\n  // since we have a different pointer type for each slot.\n  KJ_DASSERT(slot != ContextPointerSlot::RESERVED, \"Attempt to use reserved embedder data slot.\");\n  context->SetAlignedPointerInEmbedderData(\n      static_cast<int>(slot), ptr, static_cast<v8::EmbedderDataTypeTag>(slot));\n}\n\ntemplate <typename T>\nkj::Maybe<T&> getAlignedPointerFromEmbedderData(\n    v8::Local<v8::Context> context, ContextPointerSlot slot) {\n  KJ_DASSERT(slot != ContextPointerSlot::RESERVED, \"Attempt to use reserved embedder data slot.\");\n  void* ptr = context->GetAlignedPointerFromEmbedderData(\n      static_cast<int>(slot), static_cast<v8::EmbedderDataTypeTag>(slot));\n  if (ptr == nullptr) return kj::none;\n  return *reinterpret_cast<T*>(ptr);\n}\n\nclass MemoryTracker;\n\nusing kj::uint;\n\nclass GcVisitor;\nclass HeapTracer;\n\n// Base class for C++ objects which can be \"wrapped\" for JavaScript consumption. A JavaScript\n// \"wrapper\" object is created, and then the JS wrapper and C++ Wrappable are \"attached\" to each\n// other via attachWrapper().\n//\n// A Wrappable instance does not necessarily have a wrapper attached. E.g. for JSG_RESOURCE\n// types, wrappers are allocated lazily when the object first gets passed into JavaScript.\n//\n// Wrappable is refcounted via kj::Refcounted. When a JavaScript wrapper exists, it counts as\n// a reference, keeping the object alive. When the JS object is garbage-collected, this\n// reference is dropped, freeing the C++ object (unless other references exist).\n//\n// Wrappable also maintains a *second* reference count on the wrapper itself. While the second\n// refcount is non-zero, the wrapper (the JavaScript object) will not be allowed to be\n// garbage-collected, even if there are no references to it from other JS objects. This is\n// important if the C++ object may be re-exported to JavaScript in the future and needs to have\n// the same identity at that point (including maintaining any monkey-patches that the script\n// may have applied to it previously).\n//\n// For resource types, this wrapper refcount counts the number of Ref<T>s that point to the\n// Wrappable and are not visible to GC tracing.\nclass Wrappable: public kj::Refcounted {\n public:\n  enum InternalFields : int {\n    // Field must contain a pointer to `WORKERD_WRAPPABLE_TAG`. This is a workerd-specific\n    // tag that helps us to identify a v8 API object as one of our own.\n    WRAPPABLE_TAG_FIELD_INDEX,\n\n    // Index of the internal field that points back to the `Wrappable`.\n    WRAPPED_OBJECT_FIELD_INDEX,\n\n    // Number of internal fields in a wrapper object.\n    INTERNAL_FIELD_COUNT,\n  };\n\n  static constexpr v8::CppHeapPointerTag WRAPPABLE_TAG = v8::CppHeapPointerTag::kDefaultTag;\n\n  // The value pointed to by the internal field field `WRAPPABLE_TAG_FIELD_INDEX`.\n  //\n  // This value was chosen randomly.\n  static constexpr uint16_t WORKERD_WRAPPABLE_TAG = 0xeb04;\n  static constexpr uint16_t WORKERD_RUST_WRAPPABLE_TAG = 0xeb05;\n\n  static bool isWorkerdApiObject(v8::Local<v8::Object> object) {\n    return object->GetAlignedPointerFromInternalField(WRAPPABLE_TAG_FIELD_INDEX,\n               static_cast<v8::EmbedderDataTypeTag>(WRAPPABLE_TAG_FIELD_INDEX)) ==\n        &WORKERD_WRAPPABLE_TAG;\n  }\n\n  void addStrongRef();\n  void removeStrongRef();\n  uint getStrongRefcount() const {\n    return strongRefcount;\n  }\n\n  // Called by jsg::Ref<T> to ensure that its Wrappable is destroyed under the isolate lock.\n  // `ownSelf` keeps the raw `self` pointer valid -- they are passed separately because Wrappable is\n  // a private base class of the object.\n  void maybeDeferDestruction(bool strong, kj::Own<void> ownSelf, Wrappable* self);\n\n  v8::Local<v8::Object> getHandle(v8::Isolate* isolate);\n\n  kj::Maybe<v8::Local<v8::Object>> tryGetHandle(v8::Isolate* isolate) {\n    return wrapper.map([&](v8::TracedReference<v8::Object>& ref) { return ref.Get(isolate); });\n  }\n\n  // Visits a Ref<T> pointing at this Wrappable. `refParent` and `refStrong` are the members of\n  // `Ref<T>`, and this method is invoked on the object the ref points at. (This avoids the need\n  // to templatize the implementation of this method.)\n  void visitRef(GcVisitor& visitor, kj::Maybe<Wrappable&>& refParent, bool& refStrong);\n\n  // Attach to a JavaScript object. This increments the Wrappable's refcount until `object`\n  // is garbage-collected (or unlink() is called).\n  //\n  // The object MUST have exactly 2 internal field slots, which will be initialized by this\n  // call as follows:\n  // - Internal field 0 is special and is used by the GC tracing implementation.\n  // - Internal field 1 is set to a pointer to the Wrappable. It can be used to unwrap the\n  //   object.\n  //\n  // If `needsGcTracing` is true, then the virtual method jsgVisitForGc() will be called to\n  // perform GC tracing. If false, the method is never called (may be more efficient, if the\n  // method does nothing anyway).\n  void attachWrapper(v8::Isolate* isolate, v8::Local<v8::Object> object, bool needsGcTracing);\n\n  // Attach an empty object as the wrapper.\n  v8::Local<v8::Object> attachOpaqueWrapper(v8::Local<v8::Context> context, bool needsGcTracing);\n\n  // If `handle` was originally returned by attachOpaqueWrapper(), return the Wrappable it wraps.\n  // Otherwise, return nullptr.\n  static kj::Maybe<Wrappable&> tryUnwrapOpaque(v8::Isolate* isolate, v8::Local<v8::Value> handle);\n\n  // Perform GC visitation. This is named with the `jsg` prefix because it pollutes the\n  // namespace of JSG_RESOURCE types.\n  virtual void jsgVisitForGc(GcVisitor& visitor);\n\n  virtual kj::StringPtr jsgGetMemoryName() const {\n    KJ_UNIMPLEMENTED(\"jsgGetTypeName is not implemented. \"\n                     \"It must be overridden by subclasses\");\n  }\n\n  virtual size_t jsgGetMemorySelfSize() const {\n    KJ_UNIMPLEMENTED(\"jsgGetMemorySelfSize is not implemented. \"\n                     \"It must be overridden by subclasses\");\n  }\n\n  virtual void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const;\n\n  virtual bool jsgGetMemoryInfoIsRootNode() const {\n    return strongRefcount > 0;\n  }\n\n  virtual v8::Local<v8::Object> jsgGetMemoryInfoWrapperObject(v8::Isolate* isolate) {\n    KJ_IF_SOME(handle, tryGetHandle(isolate)) {\n      return handle;\n    }\n    return v8::Local<v8::Object>();\n  }\n\n  // Detaches the wrapper from V8 and returns the reference that V8 had previously held.\n  // (Typically, the caller will ignore the return value, thus dropping the reference.)\n  kj::Own<Wrappable> detachWrapper(bool shouldFreelistShim);\n\n  // Called by HeapTracer when V8 tells us that it found a reference to this object.\n  void traceFromV8(cppgc::Visitor& cppgcVisitor);\n\n private:\n  class CppgcShim;\n\n  // If a JS wrapper is currently allocated, this point to the cppgc shim object.\n  kj::Maybe<CppgcShim&> cppgcShim;\n\n  // Handle to the JS wrapper object. The wrapper is created lazily when the object is first\n  // exported to JavaScript; until then, the wrapper is empty.\n  //\n  // If the wrapper object is \"unmodified\" from its original creation state, then V8 may choose to\n  // collect it even when it could still technically be reached via C++ objects. The idea here is\n  // that if the object is returned to JavaScript again later, the wrapper can be reconstructed at\n  // that time. However, if the wrapper is modified by the application (e.g. monkey-patched with\n  // a new property), then collecting and recreating it won't work. The logic to decide if an\n  // object has been \"modified\" is internal to V8 and baked into its use of EmbedderRootsHandler.\n  kj::Maybe<v8::TracedReference<v8::Object>> wrapper;\n\n  // Whenever there are non-GC-traced references to the object (i.e. from other C++ objects, i.e.\n  // strongRefcount > 0), and `wrapper` is non-null, then `strongWrapper` contains a copy of\n  // `wrapper`, to force it to stay alive. Otherwise, `strongWrapper` is empty.\n  v8::Global<v8::Object> strongWrapper;\n\n  // Will be non-null if `wrapper` has ever been non-null.\n  v8::Isolate* isolate = nullptr;\n\n  // How many strong Ref<T>s point at this object, forcing the wrapper to stay alive even if GC\n  // tracing doesn't find it?\n  //\n  // Whenever the value of the boolean expression (strongRefcount > 0 && wrapper.IsEmpty()) changes,\n  // a GC visitation is needed to update all outgoing refs.\n  uint strongRefcount = 0;\n\n  // When `wrapperRef` is non-empty, the Wrappable is a member of the list `HeapTracer::wrappers`.\n  kj::ListLink<Wrappable> link;\n\n  friend class GcVisitor;\n  friend class HeapTracer;\n  friend class MemoryTracker;\n};\n\n// For historical reasons, this is actually implemented in setup.c++.\nclass HeapTracer: public v8::EmbedderRootsHandler {\n public:\n  explicit HeapTracer(v8::Isolate* isolate);\n\n  ~HeapTracer() noexcept {\n    // Destructor has to be noexcept because it inherits from a V8 type that has a noexcept\n    // destructor.\n    KJ_IREQUIRE(isolate == nullptr, \"you must call HeapTracer.destroy()\");\n  }\n\n  // Call under isolate lock when shutting down isolate.\n  void destroy();\n\n  static HeapTracer& getTracer(v8::Isolate* isolate);\n\n  // Returns true if the current thread is currently executing the destructor of a CppgcShim\n  // object, which implies that we are collecting unreachable objects.\n  static bool isInCppgcDestructor();\n\n  void addWrapper(kj::Badge<Wrappable>, Wrappable& wrappable) {\n    wrappers.add(wrappable);\n  }\n  void removeWrapper(kj::Badge<Wrappable>, Wrappable& wrappable) {\n    wrappers.remove(wrappable);\n  }\n  void clearWrappers();\n\n  void addToFreelist(Wrappable::CppgcShim& shim);\n  Wrappable::CppgcShim* allocateShim(Wrappable& wrappable);\n  void clearFreelistedShims();\n\n  // implements EmbedderRootsHandler -------------------------------------------\n  void ResetRoot(const v8::TracedReference<v8::Value>& handle) override;\n  bool TryResetRoot(const v8::TracedReference<v8::Value>& handle) override;\n\n  kj::StringPtr jsgGetMemoryName() const {\n    return \"HeapTracer\"_kjc;\n  }\n  size_t jsgGetMemorySelfSize() const {\n    return sizeof(*this);\n  }\n  void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const;\n  bool jsgGetMemoryInfoIsRootNode() const {\n    return false;\n  }\n\n private:\n  v8::Isolate* isolate;\n  kj::Vector<Wrappable*> wrappersToTrace;\n\n  // Wrappables on which detachWrapper() should be called at the end of this GC pass.\n  kj::Vector<Wrappable*> detachLater;\n\n  // List of all Wrappables for which a JavaScript wrapper exists.\n  kj::List<Wrappable, &Wrappable::link> wrappers;\n\n  // List of shim objects for wrappers that were collected during a minor GC. The shim objects\n  // can be reused for future allocations.\n  kj::Maybe<Wrappable::CppgcShim&> freelistedShims;\n};\n\n// Try to use this in any scope where JavaScript wrapped objects are destroyed, to confirm that\n// they don't hold disallowed references to KJ I/O objects. IoOwn's destructor will explicitly\n// create AllowAsyncDestructorsScope to permit holding such objects via IoOwn. This is meant to\n// help catch bugs.\n#define DISALLOW_KJ_IO_DESTRUCTORS_SCOPE                                                           \\\n  kj::DisallowAsyncDestructorsScope disallow(                                                      \\\n      \"JavaScript heap objects must not contain KJ I/O objects without a IoOwn\")\n// TODO(soon):\n// - Track memory usage of native objects.\n\n// Given a handle to a resource type, extract the raw C++ object pointer.\ntemplate <typename T, bool isContext>\nT& extractInternalPointer(\n    const v8::Local<v8::Context>& context, const v8::Local<v8::Object>& object) {\n  // Due to bugs in V8, we can't use internal fields on the global object:\n  //   https://groups.google.com/d/msg/v8-users/RET5b3KOa5E/3EvpRBzwAQAJ\n  //\n  // So, when wrapping a global object, we store the pointer in the \"embedder data\" of the context\n  // instead of the internal fields of the object.\n\n  if constexpr (isContext) {\n    // V8 docs say EmbedderData slot 0 is special, so we use slot 1. (See comments in newContext().)\n    return KJ_ASSERT_NONNULL(\n        getAlignedPointerFromEmbedderData<T>(context, ContextPointerSlot::GLOBAL_WRAPPER));\n  } else {\n    KJ_ASSERT(object->InternalFieldCount() == Wrappable::INTERNAL_FIELD_COUNT);\n    return *reinterpret_cast<T*>(\n        object->GetAlignedPointerFromInternalField(Wrappable::WRAPPED_OBJECT_FIELD_INDEX,\n            static_cast<v8::EmbedderDataTypeTag>(Wrappable::WRAPPED_OBJECT_FIELD_INDEX)));\n  }\n}\n\n}  // namespace workerd::jsg\n"
  },
  {
    "path": "src/workerd/server/AGENTS.md",
    "content": "# server/\n\n## OVERVIEW\n\nBinary entry point + orchestration layer. `CliMain` (workerd.c++) dispatches subcommands (`serve`, `compile`, `test`, `fuzzilli`, `pyodide-lock`) via `kj::MainBuilder`. `Server` (server.c++, ~6K lines) is the god object: parses `workerd.capnp` config, constructs all service types as nested inner classes, wires sockets/bindings/actors, runs the event loop.\n\n## KEY FILES\n\n| File                     | Role                                                                                                                                                                                          |\n| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `workerd.c++`            | `CliMain`: subcommand dispatch, config loading, file watching, signal handling                                                                                                                |\n| `server.h/c++`           | `Server` class: 15+ nested inner classes (`WorkerService`, `NetworkService`, `ExternalHttpService`, `DiskDirectoryService`, etc.). Two-phase init: `startServices()` then `listenOnSockets()` |\n| `workerd.capnp`          | Config schema: `Config`, `Service`, `Worker`, `Socket`, `Extension`. Capability-based security model                                                                                          |\n| `workerd-api.h/c++`      | `WorkerdApi`: registers all JS API types with JSG, compiles modules/globals, extracts source from config. `Global` struct has 20+ binding variants as `kj::OneOf`                             |\n| `alarm-scheduler.h/c++`  | DO alarm scheduling with SQLite-backed persistence                                                                                                                                            |\n| `json-logger.h/c++`      | Structured JSON logging for tail workers                                                                                                                                                      |\n| `channel-token.h/c++`    | Opaque token encoding for cross-service channel references                                                                                                                                    |\n| `v8-platform-impl.h/c++` | Custom `v8::Platform` bridging V8 tasks to KJ event loop                                                                                                                                      |\n| `fallback-service.h/c++` | Module fallback resolution via external service                                                                                                                                               |\n| `container-client.h/c++` | Experimental (2025): Docker container lifecycle for DO containers                                                                                                                             |\n| `docker-api.capnp`       | Cap'n Proto schema for container management                                                                                                                                                   |\n| `pyodide.h/c++`          | Python worker preloading and snapshot management                                                                                                                                              |\n\n## TEST INFRASTRUCTURE\n\n`server-test.c++` (~6K lines): integration tests using inline Cap'n Proto config strings. Tests full server lifecycle with real V8 isolates.\n\n`tests/server-harness.mjs`: Node.js harness spawning `workerd` child processes for E2E tests. Subdirectories: `compile-tests`, `container-client`, `extensions`, `inspector`, `python`, `structured-logging`, `unsafe-eval`, `unsafe-module`, `weakref`.\n\nPattern: unit tests (`*-test.c++`) at directory level; integration/E2E tests in `tests/` using the harness.\n"
  },
  {
    "path": "src/workerd/server/BUILD.bazel",
    "content": "load(\"@bazel_skylib//lib:selects.bzl\", \"selects\")\nload(\"@bazel_skylib//rules:common_settings.bzl\", \"bool_flag\")\nload(\"@bazel_skylib//rules:copy_file.bzl\", \"copy_file\")\nload(\"//:build/kj_test.bzl\", \"kj_test\")\nload(\"//:build/wd_capnp_library.bzl\", \"wd_capnp_library\")\nload(\"//:build/wd_cc_binary.bzl\", \"wd_cc_binary\")\nload(\"//:build/wd_cc_embed.bzl\", \"wd_cc_embed\")\nload(\"//:build/wd_cc_library.bzl\", \"wd_cc_library\")\n\n# Flag that can be used to force-disable tcmalloc. Mainly used for ASAN builds.\n# TODO(cleanup): This feels ugly but I've exceeded my timebox for fighting Bazel for now.\nbool_flag(\n    name = \"use_tcmalloc\",\n    build_setting_default = True,\n)\n\nconfig_setting(\n    name = \"set_use_tcmalloc\",\n    flag_values = {\"use_tcmalloc\": \"True\"},\n)\n\nbool_flag(\n    name = \"use_transpiler\",\n    build_setting_default = True,\n)\n\nconfig_setting(\n    name = \"set_use_transpiler\",\n    flag_values = {\"use_transpiler\": \"True\"},\n)\n\nselects.config_setting_group(\n    name = \"really_use_tcmalloc\",\n    match_all = [\n        \":set_use_tcmalloc\",\n        \"//:is_linux\",\n    ],\n)\n\n# current malloc in use\nwd_cc_library(\n    name = \"malloc\",\n    visibility = [\"//visibility:public\"],\n    deps = select({\n        \":really_use_tcmalloc\": [\"@tcmalloc//tcmalloc\"],\n        \"//conditions:default\": [\"@bazel_tools//tools/cpp:malloc\"],\n    }),\n)\n\nwd_cc_embed(\n    name = \"workerd-capnp-schema\",\n    src = \"workerd.capnp\",\n    base_name = \"workerd-capnp-schema\",\n    is_text = True,\n)\n\nwd_cc_embed(\n    name = \"cpp-capnp-schema\",\n    src = \"@capnp-cpp//src/capnp:c++.capnp\",\n    base_name = \"cpp-capnp-schema\",\n    is_text = True,\n)\n\nwd_cc_binary(\n    name = \"workerd\",\n    srcs = [\"workerd.c++\"],\n    malloc = \":malloc\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":cpp-capnp-schema\",\n        \":json-logger\",\n        \":server\",\n        \":v8-platform-impl\",\n        \":workerd-capnp-schema\",\n        \":workerd_capnp\",\n        \"//src/pyodide:pyodide_extra_capnp\",\n        \"//src/rust/cxx-integration\",\n        \"//src/workerd/util:autogate\",\n        \"//src/workerd/util:perfetto\",\n        \"@capnp-cpp//src/capnp:capnpc\",\n    ],\n)\n\nwd_cc_library(\n    name = \"alarm-scheduler\",\n    srcs = [\n        \"alarm-scheduler.c++\",\n    ],\n    hdrs = [\n        \"alarm-scheduler.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/io\",\n        \"//src/workerd/util:sqlite\",\n        \"@capnp-cpp//src/kj\",\n        \"@capnp-cpp//src/kj:kj-async\",\n    ],\n)\n\nwd_cc_library(\n    name = \"actor-id-impl\",\n    srcs = [\n        \"actor-id-impl.c++\",\n    ],\n    hdrs = [\n        \"actor-id-impl.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/io:actor-id\",\n        \"//src/workerd/jsg:exception\",\n        \"//src/workerd/util:entropy\",\n        \"//src/workerd/util:own-util\",\n        \"//src/workerd/util:thread-scopes\",\n        \"@capnp-cpp//src/kj\",\n        \"@ssl\",\n    ],\n)\n\nwd_cc_library(\n    name = \"pyodide\",\n    srcs = [\n        \"pyodide.c++\",\n    ],\n    hdrs = [\n        \"pyodide.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/api:pyodide\",\n        \"//src/workerd/io:compatibility-date_capnp\",\n        \"//src/workerd/jsg\",\n        \"@capnp-cpp//src/kj\",\n        \"@capnp-cpp//src/kj:kj-async\",\n        \"@capnp-cpp//src/kj/compat:kj-gzip\",\n        \"@capnp-cpp//src/kj/compat:kj-tls\",\n    ],\n)\n\nwd_cc_library(\n    name = \"workerd-debug-port-client\",\n    srcs = [\n        \"workerd-debug-port-client.c++\",\n    ],\n    hdrs = [\n        \"workerd-debug-port-client.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/io\",\n    ],\n)\n\nwd_cc_library(\n    name = \"workerd-api\",\n    srcs = [\n        \"workerd-api.c++\",\n    ],\n    hdrs = [\n        \"workerd-api.h\",\n    ],\n    implementation_deps = [\n        \"//src/workerd/api:analytics-engine\",\n        \"//src/workerd/api:capnp\",\n        \"//src/workerd/api:kv\",\n        \"//src/workerd/api:streams\",\n        \"//src/workerd/api:urlpattern\",\n        \"//src/workerd/api:urlpattern-standard\",\n        \"//src/workerd/api:worker-loader\",\n        \"//src/workerd/io:promise-wrapper\",\n    ],\n    local_defines = select({\n        \":set_use_transpiler\": [\"WORKERD_USE_TRANSPILER\"],\n        \"//conditions:default\": [],\n    }),\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":actor-id-impl\",\n        \":fallback-service\",\n        \":pyodide\",\n        \":workerd-debug-port-client\",\n        \":workerd_capnp\",\n        \"//src/cloudflare\",\n        \"//src/node\",\n        \"//src/pyodide:pyodide_static\",\n        \"//src/pyodide:python-entrypoint\",\n        \"//src/workerd/api:html-rewriter\",\n        \"//src/workerd/api:hyperdrive\",\n        \"//src/workerd/api:memory-cache\",\n        \"//src/workerd/api:pyodide\",\n        \"//src/workerd/api:r2\",\n        \"//src/workerd/api:rtti\",\n        \"//src/workerd/api/node\",\n        \"//src/workerd/io\",\n        \"//src/workerd/io:worker-modules\",\n        \"//src/workerd/jsg\",\n        \"//src/workerd/util:perfetto\",\n        \"@capnp-cpp//src/kj/compat:kj-gzip\",\n        \"@capnp-cpp//src/kj/compat:kj-tls\",\n    ] + select({\n        \":set_use_transpiler\": [\"//src/rust/transpiler\"],\n        \"//conditions:default\": [],\n    }),\n)\n\nwd_cc_library(\n    name = \"channel-token\",\n    srcs = [\"channel-token.c++\"],\n    hdrs = [\"channel-token.h\"],\n    deps = [\n        \":channel-token_capnp\",\n        \"//src/workerd/io\",\n        \"//src/workerd/util:entropy\",\n    ],\n)\n\nwd_cc_library(\n    name = \"server\",\n    srcs = [\n        \"server.c++\",\n    ],\n    hdrs = [\n        \"server.h\",\n    ],\n    deps = [\n        \":actor-id-impl\",\n        \":alarm-scheduler\",\n        \":channel-token\",\n        \":channel-token_capnp\",\n        \":container-client\",\n        \":facet-tree-index\",\n        \":fallback-service\",\n        \":workerd-api\",\n        \":workerd_capnp\",\n        \"//src/cloudflare\",\n        \"//src/node\",\n        \"//src/pyodide:pyodide_static\",\n        \"//src/workerd/api:memory-cache\",\n        \"//src/workerd/api:pyodide\",\n        \"//src/workerd/io\",\n        \"//src/workerd/io:bundle-fs\",\n        \"//src/workerd/io:worker-entrypoint\",\n        \"//src/workerd/jsg\",\n        \"//src/workerd/util:perfetto\",\n        \"//src/workerd/util:websocket-error-handler\",\n        \"@capnp-cpp//src/kj/compat:kj-gzip\",\n        \"@capnp-cpp//src/kj/compat:kj-tls\",\n    ],\n)\n\nwd_capnp_library(src = \"docker-api.capnp\")\n\nwd_capnp_library(src = \"log-schema.capnp\")\n\nwd_capnp_library(\n    src = \"channel-token.capnp\",\n    deps = [\n        \"//src/workerd/io:frankenvalue_capnp\",\n    ],\n)\n\nwd_cc_library(\n    name = \"json-logger\",\n    srcs = [\"json-logger.c++\"],\n    hdrs = [\"json-logger.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":log-schema_capnp\",\n        \"@capnp-cpp//src/capnp/compat:json\",\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nwd_cc_library(\n    name = \"container-client\",\n    srcs = [\"container-client.c++\"],\n    hdrs = [\"container-client.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":channel-token\",\n        \":docker-api_capnp\",\n        \"//src/workerd/io\",\n        \"//src/workerd/io:container_capnp\",\n        \"//src/workerd/jsg\",\n        \"//src/workerd/util:strings\",\n        \"@ada-url\",\n        \"@capnp-cpp//src/capnp/compat:http-over-capnp\",\n        \"@capnp-cpp//src/kj\",\n        \"@capnp-cpp//src/kj:kj-async\",\n        \"@capnp-cpp//src/kj/compat:kj-http\",\n    ],\n)\n\nwd_cc_library(\n    name = \"fallback-service\",\n    srcs = [\n        \"fallback-service.c++\",\n    ],\n    hdrs = [\n        \"fallback-service.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":workerd_capnp\",\n        \"@capnp-cpp//src/kj\",\n        \"@capnp-cpp//src/kj:kj-async\",\n        \"@capnp-cpp//src/kj/compat:kj-http\",\n    ],\n)\n\nwd_cc_library(\n    name = \"v8-platform-impl\",\n    srcs = [\n        \"v8-platform-impl.c++\",\n    ],\n    hdrs = [\n        \"v8-platform-impl.h\",\n    ],\n    deps = [\n        \"//src/workerd/jsg\",\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nwd_cc_library(\n    name = \"facet-tree-index\",\n    srcs = [\n        \"facet-tree-index.c++\",\n    ],\n    hdrs = [\n        \"facet-tree-index.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nwd_capnp_library(\n    src = \"workerd.capnp\",\n    # Limit visibility to avoid accidental usage – there should be no need to use this outside of\n    # test-fixture and the workerd binary.\n    visibility = [\n        \":__pkg__\",\n        \"//src/workerd/api:__pkg__\",\n        \"//src/workerd/tests:__pkg__\",\n    ],\n)\n\nkj_test(\n    size = \"large\",\n    src = \"server-test.c++\",\n    deps = [\n        \":server\",\n        \"//src/workerd/util:test-util\",\n    ],\n)\n\nkj_test(\n    src = \"channel-token-test.c++\",\n    deps = [\n        \":server\",\n    ],\n)\n\nkj_test(\n    src = \"actor-id-impl-test.c++\",\n    deps = [\n        \":actor-id-impl\",\n        \"//src/workerd/jsg:exception\",\n        \"//src/workerd/util:thread-scopes\",\n        \"@capnp-cpp//src/kj\",\n        \"@ssl\",\n    ],\n)\n\ncopy_file(\n    name = \"pyodide.capnp.bin@rule\",\n    src = \"//src/pyodide:pyodide.capnp.bin_cross\",\n    out = \"pyodide.capnp.bin\",\n)\n\nkj_test(\n    src = \"container-client-test.c++\",\n    deps = [\n        \":container-client\",\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nkj_test(\n    src = \"facet-tree-index-test.c++\",\n    deps = [\n        \":facet-tree-index\",\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nkj_test(\n    src = \"json-logger-test.c++\",\n    deps = [\n        \":json-logger\",\n        \"@capnp-cpp//src/capnp/compat:json\",\n    ],\n)\n"
  },
  {
    "path": "src/workerd/server/actor-id-impl-test.c++",
    "content": "// Copyright (c) 2024-2029 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/jsg/exception.h>\n#include <workerd/server/actor-id-impl.h>\n\n#include <openssl/hmac.h>\n\n#include <kj/debug.h>\n#include <kj/encoding.h>\n#include <kj/test.h>\n\nconstexpr kj::byte zero32[SHA256_DIGEST_LENGTH] = {0};\n\nKJ_TEST(\"ActorIdImpl equals test\") {\n  using ActorIdImpl = workerd::server::ActorIdFactoryImpl::ActorIdImpl;\n  struct ActorEqualsTest {\n    ActorIdImpl actorLeft = {zero32, kj::none};\n    ActorIdImpl actorRight = {zero32, kj::none};\n    bool expectedResult;\n    ActorEqualsTest(kj::byte leftFill,\n        const char* leftString,\n        kj::byte rightFill,\n        const char* rightString,\n        bool expectedResult)\n        : expectedResult(expectedResult) {\n      kj::byte idParamCopier[SHA256_DIGEST_LENGTH] = {0};\n      memset(idParamCopier, leftFill, SHA256_DIGEST_LENGTH);\n      if (leftString == nullptr) {\n        actorLeft = ActorIdImpl(idParamCopier, kj::none);\n      } else {\n        actorLeft = ActorIdImpl(idParamCopier, kj::heapString(leftString));\n      }\n      memset(idParamCopier, rightFill, SHA256_DIGEST_LENGTH);\n      if (rightString == nullptr) {\n        actorRight = ActorIdImpl(idParamCopier, kj::none);\n      } else {\n        actorRight = ActorIdImpl(idParamCopier, kj::heapString(rightString));\n      }\n    }\n  };\n  using Test = ActorEqualsTest;\n  Test testCases[] = {\n    {0, nullptr, 0, nullptr, true},\n    {0, nullptr, 1, nullptr, false},\n    {0, \"hello\", 0, \"goodbye\", true},\n    {0, \"hello\", 1, \"goodbye\", false},\n    {0, \"hello\", 0, nullptr, true},\n    {0, \"hello\", 1, nullptr, false},\n  };\n  for (const auto& testCase: testCases) {\n    KJ_EXPECT(testCase.actorLeft.equals(testCase.actorRight) == testCase.expectedResult);\n  }\n}\n\nconstexpr size_t BASE_LENGTH = SHA256_DIGEST_LENGTH / 2;\nkj::String computeProperTestMac(const char* strId, const char* strKey) {\n  auto id = kj::decodeHex(kj::heapString(strId));\n  KJ_ASSERT(!id.hadErrors);\n  KJ_ASSERT(id.size() == SHA256_DIGEST_LENGTH);\n  kj::byte key[SHA256_DIGEST_LENGTH] = {0};\n  auto stringPtrKey = kj::StringPtr(strKey);\n  SHA256(stringPtrKey.asBytes().begin(), stringPtrKey.size(), key);\n  kj::byte hmacOut[SHA256_DIGEST_LENGTH] = {0};\n  unsigned int len = SHA256_DIGEST_LENGTH;\n  HMAC(EVP_sha256(), key, sizeof(key), id.begin(), BASE_LENGTH, hmacOut, &len);\n  KJ_ASSERT(len == SHA256_DIGEST_LENGTH);\n  auto ret = kj::heapArray<kj::byte>(SHA256_DIGEST_LENGTH);\n  memcpy(ret.begin(), id.begin(), BASE_LENGTH);\n  memcpy(ret.begin() + BASE_LENGTH, hmacOut, SHA256_DIGEST_LENGTH - BASE_LENGTH);\n  return kj::encodeHex(ret);\n}\n\nconstexpr const char deadbeef64[] =\n    \"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef\";\nKJ_TEST(\"ActorIdImplFactory idFromString test\") {\n  using ActorIdFactoryImpl = workerd::server::ActorIdFactoryImpl;\n  struct ActorFactoryFromStringTest {\n    ActorIdFactoryImpl actor;\n    kj::String string;\n    bool isFatal;\n    ActorFactoryFromStringTest(const char* actorString, const char* string, bool isFatal)\n        : actor(actorString),\n          string(kj::heapString(string)),\n          isFatal(isFatal) {}\n    ActorFactoryFromStringTest(const char* actorString, kj::String string, bool isFatal)\n        : actor(actorString),\n          string(kj::mv(string)),\n          isFatal(isFatal) {}\n  };\n  using Test = ActorFactoryFromStringTest;\n  Test testCases[] = {\n    {\"hello\", \"goodbye\", true},   // a random string of the wrong length\n    {\"hello\", deadbeef64, true},  //Gets past the first assert\n    {deadbeef64, computeProperTestMac(deadbeef64, deadbeef64),\n      false},  //Gets past the second assert\n  };\n  for (auto& testCase: testCases) {\n    if (testCase.isFatal) {\n      KJ_EXPECT_THROW(FAILED, testCase.actor.idFromString(kj::heapString(testCase.string)));\n    } else {\n      auto result = testCase.actor.idFromString(kj::heapString(testCase.string));\n      KJ_EXPECT(result->getName() == kj::none);\n    }\n  }\n}\n"
  },
  {
    "path": "src/workerd/server/actor-id-impl.c++",
    "content": "#include <workerd/jsg/exception.h>\n#include <workerd/server/actor-id-impl.h>\n#include <workerd/util/entropy.h>\n#include <workerd/util/own-util.h>\n#include <workerd/util/thread-scopes.h>\n\n#include <openssl/hmac.h>\n\n#include <kj/encoding.h>\n#include <kj/memory.h>\n\nnamespace workerd::server {\n\nActorIdFactoryImpl::ActorIdImpl::ActorIdImpl(\n    const kj::byte idParam[SHA256_DIGEST_LENGTH], kj::Maybe<kj::String> name)\n    : name(kj::mv(name)) {\n  memcpy(id, idParam, sizeof(id));\n}\n\nkj::String ActorIdFactoryImpl::ActorIdImpl::toString() const {\n  return kj::encodeHex(kj::ArrayPtr<const kj::byte>(id));\n}\n\nkj::Maybe<kj::StringPtr> ActorIdFactoryImpl::ActorIdImpl::getName() const {\n  return name;\n}\n\nkj::Maybe<kj::StringPtr> ActorIdFactoryImpl::ActorIdImpl::getJurisdiction() const {\n  return kj::none;\n}\n\nbool ActorIdFactoryImpl::ActorIdImpl::equals(const ActorId& other) const {\n  return kj::arrayPtr(id) == kj::arrayPtr(kj::downcast<const ActorIdImpl>(other).id);\n}\n\nkj::Own<ActorIdFactory::ActorId> ActorIdFactoryImpl::ActorIdImpl::clone() const {\n  return kj::heap<ActorIdImpl>(id, mapCopyString(name));\n}\n\nActorIdFactoryImpl::ActorIdFactoryImpl(kj::StringPtr uniqueKey) {\n  KJ_ASSERT(SHA256(uniqueKey.asBytes().begin(), uniqueKey.size(), key) == key);\n}\n\nActorIdFactoryImpl::ActorIdFactoryImpl(const kj::byte keyParam[SHA256_DIGEST_LENGTH]) {\n  memcpy(key, keyParam, sizeof(key));\n}\n\nkj::Own<ActorIdFactory::ActorId> ActorIdFactoryImpl::newUniqueId(\n    kj::Maybe<kj::StringPtr> jurisdiction) {\n  JSG_REQUIRE(\n      jurisdiction == kj::none, Error, \"Jurisdiction restrictions are not implemented in workerd.\");\n\n  // We want to randomly-generate the first 16 bytes, then HMAC those to produce the latter\n  // 16 bytes. But the HMAC will produce 32 bytes, so we're only taking a prefix of it. We'll\n  // allocate a single array big enough to output the HMAC as a suffix, which will then get\n  // truncated.\n  kj::byte id[BASE_LENGTH + SHA256_DIGEST_LENGTH]{};\n\n  if (isPredictableModeForTest()) {\n    memcpy(id, &counter, sizeof(counter));\n    kj::arrayPtr(id).slice(counter).fill(0);\n    ++counter;\n  } else {\n    getEntropy(kj::arrayPtr(id, BASE_LENGTH));\n  }\n\n  computeMac(id);\n  return kj::heap<ActorIdImpl>(id, kj::none);\n}\n\nkj::Own<ActorIdFactory::ActorId> ActorIdFactoryImpl::idFromName(kj::String name) {\n  kj::byte id[BASE_LENGTH + SHA256_DIGEST_LENGTH]{};\n\n  // Compute the first half of the ID by HMACing the name itself. We're using HMAC as a keyed\n  // hash here, not actually for authentication, but it works.\n  unsigned int len = SHA256_DIGEST_LENGTH;\n  KJ_ASSERT(\n      HMAC(EVP_sha256(), key, sizeof(key), name.asBytes().begin(), name.size(), id, &len) == id);\n  KJ_ASSERT(len == SHA256_DIGEST_LENGTH);\n\n  computeMac(id);\n  return kj::heap<ActorIdImpl>(id, kj::mv(name));\n}\n\nkj::Own<ActorIdFactory::ActorId> ActorIdFactoryImpl::idFromString(kj::String str) {\n  auto decoded = kj::decodeHex(str);\n  JSG_REQUIRE(str.size() == SHA256_DIGEST_LENGTH * 2 && !decoded.hadErrors &&\n          decoded.size() == SHA256_DIGEST_LENGTH,\n      TypeError, \"Invalid Durable Object ID: must be 64 hex digits\");\n\n  kj::byte id[BASE_LENGTH + SHA256_DIGEST_LENGTH]{};\n  memcpy(id, decoded.begin(), BASE_LENGTH);\n  computeMac(id);\n\n  // Verify that the computed mac matches the input.\n  JSG_REQUIRE(kj::arrayPtr(id).slice(BASE_LENGTH).startsWith(decoded.asPtr().slice(BASE_LENGTH)),\n      TypeError, \"Durable Object ID is not valid for this namespace.\");\n\n  return kj::heap<ActorIdImpl>(id, kj::none);\n}\n\nkj::Own<ActorIdFactory> ActorIdFactoryImpl::cloneWithJurisdiction(\n    kj::Maybe<kj::StringPtr> maybeJurisdiction) {\n  if (maybeJurisdiction == kj::none) {\n    return kj::heap<ActorIdFactoryImpl>(key);\n  }\n\n  JSG_FAIL_REQUIRE(Error, \"Jurisdiction restrictions are not implemented in workerd.\");\n}\n\nbool ActorIdFactoryImpl::matchesJurisdiction(const ActorId& id) {\n  return true;\n}\n\nvoid ActorIdFactoryImpl::computeMac(kj::byte id[BASE_LENGTH + SHA256_DIGEST_LENGTH]) {\n  // Given that the first `BASE_LENGTH` bytes of `id` are filled in, compute the second half\n  // of the ID by HMACing the first half. The id must be in a buffer large enough to store the\n  // first half of the ID plus a full HMAC, even though only a prefix of the HMAC becomes part\n  // of the final ID.\n\n  kj::byte* hmacOut = id + BASE_LENGTH;\n  unsigned int len = SHA256_DIGEST_LENGTH;\n  KJ_ASSERT(HMAC(EVP_sha256(), key, sizeof(key), id, BASE_LENGTH, hmacOut, &len) == hmacOut);\n  KJ_ASSERT(len == SHA256_DIGEST_LENGTH);\n}\n\n}  //namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/actor-id-impl.h",
    "content": "#pragma once\n\n#include <workerd/io/actor-id.h>\n\n#include <openssl/sha.h>\n\nnamespace workerd::server {\nclass ActorIdFactoryImpl final: public ActorIdFactory {\n public:\n  ActorIdFactoryImpl(kj::StringPtr uniqueKey);\n  ActorIdFactoryImpl(const kj::byte keyParam[SHA256_DIGEST_LENGTH]);\n\n  class ActorIdImpl final: public ActorId {\n   public:\n    ActorIdImpl(const kj::byte idParam[SHA256_DIGEST_LENGTH], kj::Maybe<kj::String> name);\n\n    kj::String toString() const override;\n    kj::Maybe<kj::StringPtr> getName() const override;\n    kj::Maybe<kj::StringPtr> getJurisdiction() const override;\n    bool equals(const ActorId& other) const override;\n    kj::Own<ActorId> clone() const override;\n\n    void clearName() {\n      name = kj::none;\n    }\n\n   private:\n    kj::byte id[SHA256_DIGEST_LENGTH];\n    kj::Maybe<kj::String> name;\n  };\n\n  kj::Own<ActorId> newUniqueId(kj::Maybe<kj::StringPtr> jurisdiction) override;\n  kj::Own<ActorId> idFromName(kj::String name) override;\n  kj::Own<ActorId> idFromString(kj::String str) override;\n  kj::Own<ActorIdFactory> cloneWithJurisdiction(\n      kj::Maybe<kj::StringPtr> maybeJurisdiction) override;\n  bool matchesJurisdiction(const ActorId& id) override;\n\n private:\n  kj::byte key[SHA256_DIGEST_LENGTH];\n\n  uint64_t counter = 0;  // only used in predictable mode\n\n  static constexpr size_t BASE_LENGTH = SHA256_DIGEST_LENGTH / 2;\n  void computeMac(kj::byte id[BASE_LENGTH + SHA256_DIGEST_LENGTH]);\n};\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/alarm-scheduler.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"alarm-scheduler.h\"\n\n#include <kj/debug.h>\n\n#include <cmath>\n\nnamespace workerd::server {\n\nint AlarmScheduler::maxJitterMsForDelay(kj::Duration delay) {\n  double delayMs = delay / kj::MILLISECONDS;\n  return std::floor(RETRY_JITTER_FACTOR * delayMs);\n}\n\nnamespace {\n\nstd::default_random_engine makeSeededRandomEngine() {\n  // Using the time as a seed here is fine, we just want to have some randomness for retry jitter\n  auto time = kj::systemPreciseMonotonicClock().now();\n  auto seed = (time - kj::origin<kj::TimePoint>()) / kj::NANOSECONDS;\n\n  std::default_random_engine engine(seed);\n  return engine;\n}\n\n}  // namespace\n\nAlarmScheduler::AlarmScheduler(\n    const kj::Clock& clock, kj::Timer& timer, const SqliteDatabase::Vfs& vfs, kj::Path path)\n    : clock(clock),\n      timer(timer),\n      random(makeSeededRandomEngine()),\n      db([&] {\n        auto db = kj::heap<SqliteDatabase>(vfs, kj::mv(path),\n            kj::WriteMode::CREATE | kj::WriteMode::MODIFY | kj::WriteMode::CREATE_PARENT);\n        ensureInitialized(*db);\n        return kj::mv(db);\n      }()),\n      tasks(*this) {\n  loadAlarmsFromDb();\n}\n\nvoid AlarmScheduler::ensureInitialized(SqliteDatabase& db) {\n  // TODO(sqlite): Do this automatically at a lower layer?\n  db.run(\"PRAGMA journal_mode=WAL;\");\n\n  db.run(R\"(\n    CREATE TABLE IF NOT EXISTS _cf_ALARM (\n      actor_unique_key TEXT,\n      actor_id TEXT,\n      scheduled_time INTEGER,\n      PRIMARY KEY (actor_unique_key, actor_id)\n    ) WITHOUT ROWID;\n  )\");\n}\n\nvoid AlarmScheduler::loadAlarmsFromDb() {\n  auto now = clock.now();\n\n  // TODO(someday): don't maintain the entire alarm set in memory -- right now for the usecase of\n  // local development, doing so is sufficient.\n  auto query = db->run(R\"(\n    SELECT actor_unique_key, actor_id, scheduled_time FROM _cf_ALARM;\n  )\");\n\n  while (!query.isDone()) {\n    auto date = kj::UNIX_EPOCH + (kj::NANOSECONDS * query.getInt64(2));\n\n    auto ownUniqueKey = kj::str(query.getText(0));\n    auto ownActorId = kj::str(query.getText(1));\n    auto actor = kj::attachVal(ActorKey{.uniqueKey = ownUniqueKey, .actorId = ownActorId},\n        kj::mv(ownUniqueKey), kj::mv(ownActorId));\n\n    alarms.insert(*actor, scheduleAlarm(now, kj::mv(actor), date));\n\n    query.nextRow();\n  }\n}\n\nvoid AlarmScheduler::registerNamespace(kj::StringPtr uniqueKey, GetActorFn getActor) {\n  namespaces.insert(uniqueKey, Namespace{.getActor = kj::mv(getActor)});\n}\n\nkj::Maybe<kj::Date> AlarmScheduler::getAlarm(ActorKey actor) {\n  // TODO(someday): Might be able to simplify AlarmScheduler somewhat, now that ActorSqlite no\n  // longer relies on it for getAlarm()?\n  KJ_IF_SOME(alarm, alarms.find(actor)) {\n    if (alarm.status == AlarmStatus::STARTED) {\n      // getAlarm() when the alarm handler is running should return null,\n      // unless an alarm is queued;\n      return alarm.queuedAlarm;\n    } else {\n      return alarm.scheduledTime;\n    }\n  } else {\n    // We currently retain the entire set of queued alarms in memory, no need to hit sqlite\n    return kj::none;\n  }\n}\n\nbool AlarmScheduler::setAlarm(ActorKey actor, kj::Date scheduledTime) {\n  int64_t scheduledTimeNs = (scheduledTime - kj::UNIX_EPOCH) / kj::NANOSECONDS;\n  auto query = stmtSetAlarm.run(actor.uniqueKey, actor.actorId, scheduledTimeNs);\n\n  bool existing = true;\n  auto& entry = alarms.findOrCreate(actor, [&]() {\n    existing = false;\n\n    auto ownUniqueKey = kj::str(actor.uniqueKey);\n    auto ownActorId = kj::str(actor.actorId);\n    auto ownActor = kj::attachVal(ActorKey{.uniqueKey = ownUniqueKey, .actorId = ownActorId},\n        kj::mv(ownUniqueKey), kj::mv(ownActorId));\n\n    return decltype(alarms)::Entry{\n      *ownActor, scheduleAlarm(clock.now(), kj::mv(ownActor), scheduledTime)};\n  });\n\n  if (existing) {\n    if (entry.status != AlarmStatus::WAITING) {\n      // We queue any new alarm after the existing alarm even if the new alarm has the same scheduled\n      // time, as receiving a notification directly maps to a write for that time in the actor.\n      entry.queuedAlarm = scheduledTime;\n    } else {\n      entry = scheduleAlarm(clock.now(), kj::mv(entry.actor), scheduledTime);\n    }\n  }\n\n  return query.changeCount() > 0;\n}\n\nbool AlarmScheduler::deleteAlarm(ActorKey actor) {\n  auto query = stmtDeleteAlarm.run(actor.uniqueKey, actor.actorId);\n\n  KJ_IF_SOME(entry, alarms.findEntry(actor)) {\n    KJ_IF_SOME(queued, entry.value.queuedAlarm) {\n      if (entry.value.status == AlarmStatus::STARTED) {\n        // If we are currently running an alarm, we want to delete the queued instead of current.\n        entry.value.queuedAlarm = kj::none;\n      } else {\n        entry.value = scheduleAlarm(clock.now(), kj::mv(entry.value.actor), queued);\n      }\n    } else {\n      if (entry.value.status != AlarmStatus::STARTED) {\n        // We can't remove running alarms.\n        alarms.erase(entry);\n      }\n    }\n  }\n\n  return query.changeCount() > 0;\n}\n\nkj::Promise<AlarmScheduler::RetryInfo> AlarmScheduler::runAlarm(\n    const ActorKey& actor, kj::Date scheduledTime, uint32_t retryCount) {\n  KJ_IF_SOME(ns, namespaces.find(actor.uniqueKey)) {\n    auto result = co_await ns.getActor(kj::str(actor.actorId))->runAlarm(scheduledTime, retryCount);\n\n    co_return RetryInfo{.retry = result.outcome != EventOutcome::OK && result.retry,\n      .retryCountsAgainstLimit = result.retryCountsAgainstLimit};\n  } else {\n    throw KJ_EXCEPTION(FAILED, \"uniqueKey for stored alarm was not registered?\");\n  }\n}\n\nAlarmScheduler::ScheduledAlarm AlarmScheduler::scheduleAlarm(\n    kj::Date now, kj::Own<ActorKey> actor, kj::Date scheduledTime) {\n  auto task = makeAlarmTask(scheduledTime - now, *actor, scheduledTime);\n\n  return ScheduledAlarm{kj::mv(actor), scheduledTime, kj::mv(task)};\n}\n\nkj::Promise<void> AlarmScheduler::checkTimestamp(kj::Duration delay, kj::Date scheduledTime) {\n  co_await timer.afterDelay(delay);\n\n  // Since we are waiting on timer.afterDelay, it's possible that timer.now() was behind\n  // the real time by a few ms, leading to premature alarm() execution. This checks it the current\n  // time is >= than scheduledTime to ensure we run alarms only on or after their scheduled time.\n  auto now = clock.now();\n  if (now < scheduledTime) {\n    // If it's not yet time to trigger the alarm, we shall wait a while longer until we can\n    // trigger it. This repeats until it's time for the alarm to run.\n    co_await checkTimestamp(scheduledTime - now, scheduledTime);\n  }\n}\n\nkj::Promise<void> AlarmScheduler::makeAlarmTask(\n    kj::Duration delay, const ActorKey& actorRef, kj::Date scheduledTime) {\n  co_await checkTimestamp(delay, scheduledTime);\n  uint32_t retryCount = 0;\n  {\n    auto& entry = KJ_ASSERT_NONNULL(alarms.findEntry(actorRef));\n    entry.value.status = AlarmStatus::STARTED;\n    retryCount = entry.value.countedRetry;\n  }\n\n  auto retryInfo = co_await ([&]() -> kj::Promise<RetryInfo> {\n    try {\n      co_return co_await runAlarm(actorRef, scheduledTime, retryCount);\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      KJ_LOG(WARNING, exception);\n      co_return RetryInfo{.retry = true,\n\n        // An exception here is \"weird\", they should normally\n        // be turned into AlarmResult statuses in the sandbox\n        // for any user-caused error. Let's not count this\n        // retry attempt against the limit.\n        .retryCountsAgainstLimit = false};\n    }\n  })();\n\n  try {\n    auto& entry = KJ_ASSERT_NONNULL(alarms.findEntry(actorRef));\n\n    // We can't overwrite our entry before moving ourselves out of it, as a promise cannot\n    // delete itself.\n    tasks.add(kj::mv(entry.value.task));\n\n    // If an alarm is queued, there's no point in retrying the current one -- proceed\n    // to running the queued alarm instead.\n    KJ_IF_SOME(a, entry.value.queuedAlarm) {\n      // creating a new alarm and overwriting the old one will reset\n      // `status` to WAITING and `queuedAlarm` to null\n      entry.value = scheduleAlarm(clock.now(), kj::mv(entry.value.actor), a);\n      co_return;\n    }\n\n    // When we reach this block of code and alarm has either succeeded or failed and may (or may\n    // not) retry. Setting the status of an alarm as FINISHED here, will allow deletion of alarms\n    // between retries. If there's a retry, `makeAlarmTask` is called, setting status as RUNNING\n    // again.\n    entry.value.status = AlarmStatus::FINISHED;\n\n    if (retryInfo.retry) {\n      // recreate the task, running after a delay determined using the retry factor\n      if (entry.value.countedRetry >= AlarmScheduler::RETRY_MAX_TRIES) {\n        deleteAlarm(*entry.value.actor);\n        co_return;\n      }\n      if (retryInfo.retryCountsAgainstLimit) {\n        entry.value.countedRetry++;\n\n        if (!entry.value.previousRetryCountedAgainstLimit) {\n          // The last retry didn't count against the limit, indicating it was due to some internal\n          // error. However, this retry does, meaning it's due to an error in user code,\n          // most likely a different error. We should reset the retry counter used for\n          // calculating backoff, so user-caused retries don't have an unnecessarily high backoff\n          // time if they come after internal-caused retries.\n\n          entry.value.backoff = 0;\n        }\n      }\n      entry.value.previousRetryCountedAgainstLimit = retryInfo.retryCountsAgainstLimit;\n\n      entry.value.backoff = kj::min(AlarmScheduler::RETRY_BACKOFF_MAX, entry.value.backoff);\n      auto delay = (AlarmScheduler::RETRY_START_SECONDS << entry.value.backoff) * kj::SECONDS;\n\n      std::uniform_int_distribution<> distribution(0, maxJitterMsForDelay(delay));\n      delay += distribution(random) * kj::MILLISECONDS;\n\n      entry.value.backoff++;\n      entry.value.retry++;\n\n      entry.value.task = makeAlarmTask(delay, actorRef, scheduledTime);\n    } else {\n      KJ_ASSERT(entry.value.queuedAlarm == kj::none);\n      deleteAlarm(actorRef);\n    }\n  } catch (...) {\n    auto exception = kj::getCaughtExceptionAsKj();\n    KJ_LOG(ERROR, \"Failed to run alarm and was unable to schedule a retry\", exception);\n  }\n}\n\nvoid AlarmScheduler::taskFailed(kj::Exception&& e) {\n  KJ_LOG(WARNING, e);\n}\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/alarm-scheduler.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/io/worker-interface.h>\n#include <workerd/util/sqlite.h>\n\n#include <kj/async.h>\n#include <kj/common.h>\n#include <kj/map.h>\n#include <kj/time.h>\n#include <kj/timer.h>\n\n#include <random>\n\nnamespace workerd::server {\n\nusing byte = kj::byte;\n\nstruct ActorKey {\n  kj::StringPtr uniqueKey;\n  kj::StringPtr actorId;\n\n  bool operator==(const ActorKey& other) const {\n    return uniqueKey == other.uniqueKey && actorId == other.actorId;\n  }\n\n  kj::Own<ActorKey> clone() const {\n    auto ownUniqueKey = kj::str(uniqueKey);\n    auto ownActorId = kj::str(actorId);\n\n    return kj::attachVal(ActorKey{.uniqueKey = ownUniqueKey, .actorId = ownActorId},\n        kj::mv(ownUniqueKey), kj::mv(ownActorId));\n  }\n};\n\ninline uint KJ_HASHCODE(const ActorKey& k) {\n  return kj::hashCode(k.uniqueKey, k.actorId);\n}\n\n// Allows scheduling alarm executions at specific times, returning a promise representing\n// the completion of the alarm event.\nclass AlarmScheduler final: kj::TaskSet::ErrorHandler {\n public:\n  static constexpr auto RETRY_START_SECONDS = WorkerInterface::ALARM_RETRY_START_SECONDS;\n\n  // Max number of \"valid\" retry attempts, i.e the worker returned an error\n  static constexpr auto RETRY_MAX_TRIES = WorkerInterface::ALARM_RETRY_MAX_TRIES;\n\n  // Bound for exponential backoff when RETRY_MAX_TRIES is exceeded due to internal errors.\n  // 2 << 9 is 1024 seconds, about 17 minutes. Total time spent in retries once the backoff limit\n  // is reached is over 30 minutes.\n  static constexpr auto RETRY_BACKOFF_MAX = 9;\n\n  // How much jitter should be applied to retry times to avoid bundled retries overloading\n  // some common dependency between a set of failed alarms\n  static constexpr auto RETRY_JITTER_FACTOR = 0.25;\n\n  using GetActorFn = kj::Function<kj::Own<WorkerInterface>(kj::String)>;\n\n  AlarmScheduler(\n      const kj::Clock& clock, kj::Timer& timer, const SqliteDatabase::Vfs& vfs, kj::Path path);\n\n  kj::Maybe<kj::Date> getAlarm(ActorKey actor);\n  bool setAlarm(ActorKey actor, kj::Date scheduledTime);\n  bool deleteAlarm(ActorKey actor);\n\n  void registerNamespace(kj::StringPtr uniqueKey, GetActorFn getActor);\n\n private:\n  enum class AlarmStatus { WAITING, STARTED, FINISHED };\n  const kj::Clock& clock;\n  kj::Timer& timer;\n  std::default_random_engine random;\n\n  struct Namespace {\n    GetActorFn getActor;\n  };\n  kj::HashMap<kj::StringPtr, Namespace> namespaces;\n  kj::Own<SqliteDatabase> db;\n  kj::TaskSet tasks;\n\n  struct ScheduledAlarm {\n    kj::Own<ActorKey> actor;\n    kj::Date scheduledTime;\n    kj::Promise<void> task;\n    kj::Maybe<kj::Date> queuedAlarm = kj::none;\n    // Once started, an alarm can have a single alarm queued behind it.\n    AlarmStatus status = AlarmStatus::WAITING;\n\n    bool previousRetryCountedAgainstLimit = false;\n\n    // Counter for calculating backoff -- separate from retry, so we can reset backoff without losing\n    // the total count of retry attempts\n    uint32_t backoff = 0;\n\n    // Counter for retry attempts, whether or not they apply to the limit\n    uint32_t retry = 0;\n\n    // Counter for retry attempts that apply to the retry limit.\n    uint32_t countedRetry = 0;\n  };\n\n  kj::HashMap<ActorKey, ScheduledAlarm> alarms;\n\n  struct RetryInfo {\n    bool retry;\n    bool retryCountsAgainstLimit;\n  };\n  kj::Promise<RetryInfo> runAlarm(\n      const ActorKey& actor, kj::Date scheduledTime, uint32_t retryCount);\n\n  void setAlarmInMemory(kj::Own<ActorKey> actor, kj::Date scheduledTime);\n\n  ScheduledAlarm scheduleAlarm(kj::Date now, kj::Own<ActorKey> actor, kj::Date scheduledTime);\n\n  kj::Promise<void> makeAlarmTask(\n      kj::Duration delay, const ActorKey& actor, kj::Date scheduledTime);\n\n  kj::Promise<void> checkTimestamp(kj::Duration delay, kj::Date scheduledTime);\n\n  SqliteDatabase::Statement stmtSetAlarm = db->prepare(R\"(\n    INSERT INTO _cf_ALARM VALUES(?, ?, ?)\n      ON CONFLICT DO UPDATE SET scheduled_time = excluded.scheduled_time;\n  )\");\n  SqliteDatabase::Statement stmtDeleteAlarm = db->prepare(R\"(\n    DELETE FROM _cf_ALARM WHERE actor_unique_key = ? AND actor_id = ?\n  )\");\n\n  void taskFailed(kj::Exception&& exception) override;\n\n  int maxJitterMsForDelay(kj::Duration delay);\n\n  static void ensureInitialized(SqliteDatabase& db);\n  void loadAlarmsFromDb();\n};\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/channel-token-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"channel-token.h\"\n\n#include <capnp/message.h>\n#include <kj/test.h>\n\nnamespace workerd::server {\nnamespace {\n\nkj::String strProps(const Frankenvalue& props) {\n  capnp::MallocMessageBuilder message;\n  auto builder = message.getRoot<rpc::Frankenvalue>();\n  props.threadSafeClone().toCapnp(builder);\n  return kj::str(builder.asReader());\n}\n\nstruct ServiceTriplet {\n  kj::String serviceName;\n  kj::Maybe<kj::String> entrypoint;\n  Frankenvalue props;\n\n  ServiceTriplet(kj::StringPtr serviceName, kj::Maybe<kj::StringPtr> entrypoint, Frankenvalue props)\n      : serviceName(kj::str(serviceName)),\n        entrypoint(entrypoint.map([](kj::StringPtr s) { return kj::str(s); })),\n        props(kj::mv(props)) {}\n  ServiceTriplet(ServiceTriplet&&) = default;\n\n  bool operator==(const ServiceTriplet& other) const {\n    return serviceName == other.serviceName && entrypoint == other.entrypoint &&\n        strProps(props) == strProps(other.props);\n  }\n\n  kj::String toString() const {\n    return kj::str('(', serviceName, \", \", entrypoint, \", \", strProps(props), ')');\n  }\n};\n\nclass MockSubrequestChannel: public IoChannelFactory::SubrequestChannel {\n public:\n  MockSubrequestChannel(ServiceTriplet triplet): triplet(kj::mv(triplet)) {}\n  ServiceTriplet triplet;\n\n  kj::Own<WorkerInterface> startRequest(IoChannelFactory::SubrequestMetadata metadata) override {\n    KJ_UNREACHABLE;\n  }\n  void requireAllowsTransfer() override {\n    KJ_UNREACHABLE;\n  }\n};\n\nclass MockActorClassChannel: public IoChannelFactory::ActorClassChannel {\n public:\n  MockActorClassChannel(ServiceTriplet triplet): triplet(kj::mv(triplet)) {}\n  ServiceTriplet triplet;\n\n  void requireAllowsTransfer() override {\n    KJ_UNREACHABLE;\n  }\n};\n\nclass MockResolver: public ChannelTokenHandler::Resolver {\n public:\n  kj::Own<IoChannelFactory::SubrequestChannel> resolveEntrypoint(\n      kj::StringPtr serviceName, kj::Maybe<kj::StringPtr> entrypoint, Frankenvalue props) override {\n    return kj::refcounted<MockSubrequestChannel>(\n        ServiceTriplet(serviceName, entrypoint, kj::mv(props)));\n  }\n\n  kj::Own<IoChannelFactory::ActorClassChannel> resolveActorClass(\n      kj::StringPtr serviceName, kj::Maybe<kj::StringPtr> entrypoint, Frankenvalue props) override {\n    return kj::refcounted<MockActorClassChannel>(\n        ServiceTriplet(serviceName, entrypoint, kj::mv(props)));\n  }\n};\n\nusing Usage = IoChannelFactory::ChannelTokenUsage;\n\nKJ_TEST(\"channel token basics\") {\n  MockResolver resolver;\n  ChannelTokenHandler handler(resolver);\n\n  auto props = Frankenvalue::fromJson(kj::str(\"{\\\"foo\\\": 123}\"));\n  auto token = handler.encodeSubrequestChannelToken(Usage::RPC, \"foo\", \"MyEntry\"_kj, props);\n\n  // Decoding works.\n  {\n    auto channel =\n        handler.decodeSubrequestChannelToken(Usage::RPC, token).downcast<MockSubrequestChannel>();\n    KJ_EXPECT(channel->triplet == ServiceTriplet(\"foo\", \"MyEntry\"_kj, props.clone()));\n  }\n\n  auto corruptedToken = [&](uint index) {\n    auto copy = kj::heapArray(token.asPtr());\n    copy[index] ^= 1;\n    return copy;\n  };\n\n  // Corrupting any byte of the token should make it invalid.\n  {\n    // Corrupt the magic number.\n    KJ_EXPECT_THROW_MESSAGE(\n        \"RPC_TOKEN_MAGIC\", handler.decodeSubrequestChannelToken(Usage::RPC, corruptedToken(2)));\n\n    // Corrupt the MAC.\n    KJ_EXPECT_THROW_MESSAGE(\"failed authentication\",\n        handler.decodeSubrequestChannelToken(Usage::RPC, corruptedToken(token.size() - 2)));\n\n    // Corrupt the IV.\n    KJ_EXPECT_THROW_MESSAGE(\"failed authentication\",\n        handler.decodeSubrequestChannelToken(Usage::RPC, corruptedToken(7)));\n\n    // Corrupt the key ID.\n    KJ_EXPECT_THROW_MESSAGE(\"failed authentication\",\n        handler.decodeSubrequestChannelToken(Usage::RPC, corruptedToken(20)));\n\n    // Corrupt the message body.\n    KJ_EXPECT_THROW_MESSAGE(\"failed authentication\",\n        handler.decodeSubrequestChannelToken(Usage::RPC, corruptedToken(37)));\n  }\n\n  // Can't parse as a storage token.\n  KJ_EXPECT_THROW_MESSAGE(\n      \"STORAGE_TOKEN_MAGIC\", handler.decodeSubrequestChannelToken(Usage::STORAGE, token));\n\n  // Can't use as wrong type.\n  KJ_EXPECT_THROW_MESSAGE(\n      \"channel token type mismatch\", handler.decodeActorClassChannelToken(Usage::RPC, token));\n}\n\nKJ_TEST(\"channel tokens for storage\") {\n  MockResolver resolver;\n  ChannelTokenHandler handler(resolver);\n\n  auto props = Frankenvalue::fromJson(kj::str(\"{\\\"foo\\\": 123}\"));\n  auto token = handler.encodeSubrequestChannelToken(Usage::STORAGE, \"foo\", \"MyEntry\"_kj, props);\n\n  // Decoding works.\n  {\n    auto channel = handler.decodeSubrequestChannelToken(Usage::STORAGE, token)\n                       .downcast<MockSubrequestChannel>();\n    KJ_EXPECT(channel->triplet == ServiceTriplet(\"foo\", \"MyEntry\"_kj, props.clone()));\n  }\n\n  auto corruptedToken = [&](uint index) {\n    auto copy = kj::heapArray(token.asPtr());\n    copy[index] ^= 1;\n    return copy;\n  };\n\n  // Corrupting the magic number breaks the token.\n  KJ_EXPECT_THROW_MESSAGE(\"STORAGE_TOKEN_MAGIC\",\n      handler.decodeSubrequestChannelToken(Usage::STORAGE, corruptedToken(2)));\n\n  // Can't parse as an RPC token.\n  KJ_EXPECT_THROW_MESSAGE(\n      \"RPC_TOKEN_MAGIC\", handler.decodeSubrequestChannelToken(Usage::RPC, token));\n\n  // Can't use as wrong type.\n  KJ_EXPECT_THROW_MESSAGE(\n      \"channel token type mismatch\", handler.decodeActorClassChannelToken(Usage::STORAGE, token));\n}\n\nKJ_TEST(\"actor class channel tokens\") {\n  MockResolver resolver;\n  ChannelTokenHandler handler(resolver);\n\n  auto props = Frankenvalue::fromJson(kj::str(\"{\\\"foo\\\": 123}\"));\n  auto token = handler.encodeActorClassChannelToken(Usage::RPC, \"foo\", \"MyEntry\"_kj, props);\n\n  // Decoding works.\n  {\n    auto channel =\n        handler.decodeActorClassChannelToken(Usage::RPC, token).downcast<MockActorClassChannel>();\n    KJ_EXPECT(channel->triplet == ServiceTriplet(\"foo\", \"MyEntry\"_kj, props.clone()));\n  }\n\n  // Decoding as the wrong type fails.\n  KJ_EXPECT_THROW_MESSAGE(\n      \"channel token type mismatch\", handler.decodeSubrequestChannelToken(Usage::RPC, token));\n}\n\n}  // namespace\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/channel-token.c++",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"channel-token.h\"\n\n#include <workerd/server/channel-token.capnp.h>\n#include <workerd/util/entropy.h>\n\n#include <openssl/evp.h>\n#include <openssl/sha.h>\n\n#include <capnp/serialize-packed.h>\n#include <kj/io.h>\n\n// It's 2025, nobody uses big-endian anymore. But just in case someone tries, flag it here.\n// Specifically, the magic number in TokenHeader is encoded in host order.\n#if !defined(__LITTLE_ENDIAN__) || __BYTE_ORDER != __LITTLE_ENDIAN\n#error \"This code assumes little-endian architecture.\"\n#endif\n\nnamespace workerd::server {\n\nChannelTokenHandler::ChannelTokenHandler(Resolver& resolver): resolver(resolver) {\n  getEntropy(tokenKey);\n\n  SHA256_CTX ctx{};\n  KJ_ASSERT(SHA256_Init(&ctx));\n  KJ_ASSERT(SHA256_Update(&ctx, tokenKey, sizeof(tokenKey)));\n\n  byte hash[SHA256_DIGEST_LENGTH]{};\n  KJ_ASSERT(SHA256_Final(hash, &ctx));\n\n  static_assert(KEY_ID_SIZE <= SHA256_DIGEST_LENGTH);\n  kj::arrayPtr(keyId).copyFrom(kj::arrayPtr(hash).first(KEY_ID_SIZE));\n}\n\nkj::Array<byte> ChannelTokenHandler::encodeChannelTokenImpl(ChannelToken::Type type,\n    IoChannelFactory::ChannelTokenUsage usage,\n    kj::StringPtr serviceName,\n    kj::Maybe<kj::StringPtr> entrypoint,\n    Frankenvalue& props) {\n  capnp::word scratch[128]{};\n  capnp::MallocMessageBuilder message(scratch);\n  auto builder = message.getRoot<ChannelToken>();\n\n  builder.setType(type);\n\n  builder.setName(serviceName);\n\n  KJ_IF_SOME(e, entrypoint) {\n    builder.setEntrypoint(e);\n  }\n\n  {\n    auto propsBuilder = builder.initProps();\n    props.toCapnp(propsBuilder);\n\n    auto capTable = props.getCapTable();\n    if (capTable.size() > 0) {\n      auto tableBuilder = propsBuilder.initCapTable().initAs<ChannelToken::FrankenvalueCapTable>();\n\n      auto caps = tableBuilder.initCaps(capTable.size());\n\n      for (auto i: kj::indices(capTable)) {\n        KJ_IF_SOME(subreq, kj::tryDowncast<IoChannelFactory::SubrequestChannel>(*capTable[i])) {\n          caps[i].setSubrequestChannel(subreq.getToken(usage));\n        } else KJ_IF_SOME(actorClass,\n            kj::tryDowncast<IoChannelFactory::ActorClassChannel>(*capTable[i])) {\n          caps[i].setActorClassChannel(actorClass.getToken(usage));\n        } else {\n          KJ_FAIL_REQUIRE(\"unknown type in props\");\n        }\n      }\n    }\n  }\n\n  kj::VectorOutputStream out;\n  capnp::writePackedMessage(out, message);\n\n  auto plaintext = out.getArray();\n\n  switch (usage) {\n    case IoChannelFactory::ChannelTokenUsage::RPC: {\n      static_assert(alignof(TokenHeader) <= __STDCPP_DEFAULT_NEW_ALIGNMENT__);\n      auto result = kj::heapArray<byte>(sizeof(TokenHeader) + plaintext.size() + AES_MAC_SIZE);\n      auto& header = *reinterpret_cast<TokenHeader*>(result.begin());\n\n      header.magic = ChannelToken::RPC_TOKEN_MAGIC;\n      getEntropy(header.iv);\n      kj::arrayPtr(header.keyId).copyFrom(keyId);\n\n      EVP_CIPHER_CTX* aesCtx = EVP_CIPHER_CTX_new();\n      KJ_ASSERT(aesCtx != nullptr);\n      KJ_DEFER(EVP_CIPHER_CTX_free(aesCtx));\n\n      KJ_ASSERT(EVP_EncryptInit(aesCtx, EVP_aes_256_gcm(), tokenKey, header.iv));\n\n      // Add header as AAD first.\n      {\n        int outSize = 0;\n        KJ_ASSERT(\n            EVP_EncryptUpdate(aesCtx, nullptr, &outSize, result.begin(), sizeof(TokenHeader)));\n        KJ_ASSERT(outSize == sizeof(TokenHeader));\n      }\n\n      // Encrypt the body.\n      {\n        int outSize = 0;\n        KJ_ASSERT(EVP_EncryptUpdate(aesCtx, result.begin() + sizeof(TokenHeader), &outSize,\n            plaintext.begin(), plaintext.size()));\n        KJ_ASSERT(outSize == plaintext.size());  // because AES-GCM is a stream cipher\n      }\n\n      int out = 0;\n      KJ_ASSERT(EVP_EncryptFinal_ex(aesCtx, nullptr, &out));\n      KJ_ASSERT(out == 0);  // No padding for stream ciphers like AES-GCM.\n\n      // Get the MAC.\n      KJ_ASSERT(EVP_CIPHER_CTX_ctrl(\n          aesCtx, EVP_CTRL_GCM_GET_TAG, AES_MAC_SIZE, result.end() - AES_MAC_SIZE));\n\n      return result;\n    }\n\n    case IoChannelFactory::ChannelTokenUsage::STORAGE: {\n      auto magic = kj::asBytes(ChannelToken::STORAGE_TOKEN_MAGIC);\n      auto result = kj::heapArray<byte>(magic.size() + plaintext.size());\n      result.slice(0, magic.size()).copyFrom(magic);\n      result.slice(magic.size()).copyFrom(plaintext);\n      return result;\n    }\n  }\n\n  KJ_UNREACHABLE;\n}\n\nkj::Array<byte> ChannelTokenHandler::encodeSubrequestChannelToken(\n    IoChannelFactory::ChannelTokenUsage usage,\n    kj::StringPtr serviceName,\n    kj::Maybe<kj::StringPtr> entrypoint,\n    Frankenvalue& props) {\n  return encodeChannelTokenImpl(\n      ChannelToken::Type::SUBREQUEST, usage, serviceName, entrypoint, props);\n}\n\nkj::Array<byte> ChannelTokenHandler::encodeActorClassChannelToken(\n    IoChannelFactory::ChannelTokenUsage usage,\n    kj::StringPtr serviceName,\n    kj::Maybe<kj::StringPtr> entrypoint,\n    Frankenvalue& props) {\n  return encodeChannelTokenImpl(\n      ChannelToken::Type::ACTOR_CLASS, usage, serviceName, entrypoint, props);\n}\n\nkj::Own<Frankenvalue::CapTableEntry> ChannelTokenHandler::decodeChannelTokenImpl(\n    ChannelToken::Type type,\n    IoChannelFactory::ChannelTokenUsage usage,\n    kj::ArrayPtr<const byte> token) {\n  kj::ArrayPtr<const byte> plaintext;\n  kj::Array<byte> ownPlaintext;\n\n  switch (usage) {\n    case IoChannelFactory::ChannelTokenUsage::RPC: {\n      TokenHeader header;\n      KJ_REQUIRE(token.size() >= sizeof(header) + AES_MAC_SIZE, \"invalid channel token for RPC\");\n\n      kj::asBytes(header).copyFrom(token.first(sizeof(TokenHeader)));\n      KJ_REQUIRE(header.magic == ChannelToken::RPC_TOKEN_MAGIC, \"invalid channel token for RPC\");\n\n      auto mac = token.slice(token.size() - AES_MAC_SIZE);\n      auto ciphertext = token.slice(sizeof(header), token.size() - AES_MAC_SIZE);\n\n      EVP_CIPHER_CTX* aesCtx = EVP_CIPHER_CTX_new();\n      KJ_ASSERT(aesCtx != nullptr);\n      KJ_DEFER(EVP_CIPHER_CTX_free(aesCtx));\n\n      KJ_ASSERT(EVP_DecryptInit(aesCtx, EVP_aes_256_gcm(), tokenKey, header.iv));\n\n      // Add header as AAD first.\n      {\n        int outSize = 0;\n        KJ_ASSERT(EVP_DecryptUpdate(aesCtx, nullptr, &outSize, token.begin(), sizeof(header)));\n        KJ_ASSERT(outSize == sizeof(TokenHeader));\n      }\n\n      // Decrypt the body.\n      ownPlaintext = kj::heapArray<byte>(ciphertext.size());\n      {\n        int outSize = 0;\n        KJ_ASSERT(EVP_DecryptUpdate(\n            aesCtx, ownPlaintext.begin(), &outSize, ciphertext.begin(), ciphertext.size()));\n        KJ_ASSERT(outSize == ownPlaintext.size());\n      }\n      plaintext = ownPlaintext;\n\n      // Check MAC.\n      KJ_ASSERT(EVP_CIPHER_CTX_ctrl(aesCtx, EVP_CTRL_GCM_SET_TAG, AES_MAC_SIZE,\n          // const_cast needed for the EVP_CIPHER_CTX_ctrl() interface, but this won't actually\n          // modify the buffer.\n          const_cast<byte*>(mac.begin())));\n\n      int out;\n      KJ_REQUIRE(EVP_DecryptFinal_ex(aesCtx, nullptr, &out), \"channel token failed authentication\");\n      KJ_ASSERT(out == 0);\n\n      break;\n    }\n\n    case IoChannelFactory::ChannelTokenUsage::STORAGE: {\n      uint32_t magic;\n      KJ_REQUIRE(token.size() >= sizeof(magic), \"invalid channel token for storage\");\n\n      kj::asBytes(magic).copyFrom(token.first(sizeof(magic)));\n      KJ_REQUIRE(magic == ChannelToken::STORAGE_TOKEN_MAGIC, \"invalid channel token for storage\");\n\n      plaintext = token.slice(sizeof(magic));\n      break;\n    }\n  }\n\n  kj::ArrayInputStream input(plaintext);\n  capnp::word scratch[128]{};\n  capnp::PackedMessageReader message(input, {}, scratch);\n  auto reader = message.getRoot<ChannelToken>();\n\n  KJ_REQUIRE(reader.getType() == type, \"channel token type mismatch\");\n\n  kj::Maybe<kj::StringPtr> entrypoint;\n  if (reader.hasEntrypoint()) {\n    entrypoint = reader.getEntrypoint();\n  }\n\n  Frankenvalue props;\n  if (reader.hasProps()) {\n    auto propsReader = reader.getProps();\n    auto tableReader = propsReader.getCapTable().getAs<ChannelToken::FrankenvalueCapTable>();\n\n    kj::Vector<kj::Own<Frankenvalue::CapTableEntry>> capTable;\n    if (tableReader.hasCaps()) {\n      auto caps = tableReader.getCaps();\n      capTable.reserve(caps.size());\n\n      for (auto cap: caps) {\n        switch (cap.which()) {\n          case ChannelToken::FrankenvalueCapTable::Cap::UNKNOWN:\n            break;\n          case ChannelToken::FrankenvalueCapTable::Cap::SUBREQUEST_CHANNEL:\n            capTable.add(decodeSubrequestChannelToken(usage, cap.getSubrequestChannel()));\n            continue;\n          case ChannelToken::FrankenvalueCapTable::Cap::ACTOR_CLASS_CHANNEL:\n            capTable.add(decodeActorClassChannelToken(usage, cap.getActorClassChannel()));\n            continue;\n        }\n        KJ_FAIL_REQUIRE(\"unknown cap table type\", cap.which());\n      }\n    }\n\n    props = Frankenvalue::fromCapnp(propsReader, kj::mv(capTable));\n  }\n\n  // HACK: It would be more type-safe for us to return the (name, entrypoint, props) triplet and\n  //   let the caller call the appropriate resolver method. However, this would require making\n  //   heap string copies of the name and entrypoint which would just be thrown way immediately.\n  //   Since both types happen to subclass Frankenvalue::CapTableEntry, we just make the resolver\n  //   call here, return either type, and let the caller downcast to the right type.\n  switch (type) {\n    case ChannelToken::Type::SUBREQUEST:\n      return resolver.resolveEntrypoint(reader.getName(), entrypoint, kj::mv(props));\n    case ChannelToken::Type::ACTOR_CLASS:\n      return resolver.resolveActorClass(reader.getName(), entrypoint, kj::mv(props));\n  }\n\n  KJ_UNREACHABLE;\n}\n\nkj::Own<IoChannelFactory::SubrequestChannel> ChannelTokenHandler::decodeSubrequestChannelToken(\n    IoChannelFactory::ChannelTokenUsage usage, kj::ArrayPtr<const byte> token) {\n  return decodeChannelTokenImpl(ChannelToken::Type::SUBREQUEST, usage, token)\n      .downcast<IoChannelFactory::SubrequestChannel>();\n}\n\nkj::Own<IoChannelFactory::ActorClassChannel> ChannelTokenHandler::decodeActorClassChannelToken(\n    IoChannelFactory::ChannelTokenUsage usage, kj::ArrayPtr<const byte> token) {\n  return decodeChannelTokenImpl(ChannelToken::Type::ACTOR_CLASS, usage, token)\n      .downcast<IoChannelFactory::ActorClassChannel>();\n}\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/channel-token.capnp",
    "content": "# Copyright (c) 2025 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n@0xc086f616deb649e5;\n\nusing Cxx = import \"/capnp/c++.capnp\";\n$Cxx.namespace(\"workerd::server\");\n$Cxx.allowCancellation;\n\nusing Frankenvalue = import \"/workerd/io/frankenvalue.capnp\".Frankenvalue;\n\nstruct ChannelToken {\n  # Internal structure of a channel token in workerd, as returned by\n  # {SubrequestChannel,ActorClassChannel}::getToken().\n  #\n  # For RPC tokens, this structure is encoded as a \"packed\" capnp message and then AES-GCM\n  # encrypted using a secret service key and random IV to form the final token. The full token\n  # contains, in order:\n  # * 4-byte magic number: 0x821dad26 (little-endian encoded)\n  # * 12-byte IV\n  # * 16-byte key ID (prefix of SHA-256 hash of secret key, not encrypted)\n  # * ciphertext\n  # * 16-byte MAC (covering ciphertext and the first 32 bytes as AAD)\n  #\n  # The encryption (particularly the MAC) is important in order to ensure that someone speaking to\n  # workerd over RPC cannot trivially invoke an arbitrary service with arbitrary props by simply\n  # presenting a channel token.\n  #\n  # As of this writing, for RPC tokens, the secret key is generated randomly at process startup.\n  # This means that RPC tokens are only usable within the same workerd process that created them,\n  # which also has the side effect of meaning there is no backwards-compatibilty concern. The\n  # format is likely to change in the future once we figure out more how it should actually be used.\n  #\n  # The 16-byte key ID is included to enable routing. Hypothetically, if workerd processes were to\n  # register their key IDs in some lookup service, then based on the key ID you could find an\n  # appropriate workerd instance to connect to to use this token. As of this writing, this is still\n  # speculative.\n  #\n  # For storage tokens -- which are only permitted today with the experimental\n  # allow_irrevocable_stub_storage compat flag -- the format is:\n  # * 4-byte magic number: 0x9082806d (little-endian encoded)\n  # * plaintext (\"packed\" ChannelToken)\n  #\n  # There is no encryption in this case. This format is experimental and will be replaced in the\n  # future with a different kind of token that refers into some sort of token grant table.\n\n  const rpcTokenMagic :UInt32 = 0x821dad26;\n  const storageTokenMagic :UInt32 = 0x9082806d;\n\n  type @0 :Type;\n  # What type of channel does this point to? This is encoded as a safety measure. In normal\n  # operation the envelope containing the token always knows what type it is meant to be, but we\n  # want to prevent any possible shenanigans from someone taking a channel token of one type and\n  # trying to stuff it in an envelope for a different type.\n\n  enum Type {\n    subrequest @0;  # token for IoChannelFactory::SubrequestChannel\n    actorClass @1;  # token for IoChannelFactory::ActorClassChannel\n  }\n\n  name @1 :Text;\n  # Name of the service in the workerd config's services list.\n\n  entrypoint @2 :Text;\n  # Name of the entrypoint the channel points at. For subrequest channels this must be a\n  # WorkerEntrypoint derivative (or plain object implementing `ExportedHandlers`). For actor class\n  # channels this must be a `DurableObject` implementation.\n\n  props @3 :Frankenvalue;\n\n  struct FrankenvalueCapTable {\n    # CapTable representation for `ChannelToken.props`.\n\n    caps @0 :List(Cap);\n\n    struct Cap {\n      union {\n        unknown @0 :Void;\n        # Dummy default value, never appears in practice.\n\n        subrequestChannel @1 :Data;\n        actorClassChannel @2 :Data;\n        # Nested capabilities are represented using fully encoded channel tokens themselves (rather\n        # than ChannelToken capnp structs) for a couple reasons:\n        # 1. This simplifies the abstractions needed when encoding a channel token -- just call\n        #    getToken() on any nested channels.\n        # 2. Channel tokens may be tied to the particular workerd instance that they came from, and\n        #    cannot be decoded on other instances, but I suspect we will eventually want to support\n        #    props containing capabilities pointing to other workerd instances.\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/workerd/server/channel-token.h",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/io/io-channels.h>\n#include <workerd/server/channel-token.capnp.h>\n\nnamespace workerd::server {\n\n// Helper class to encode channel tokens for workerd.\n//\n// This is an internal implementation helper for `Server` (in `server.h`), separated out into its\n// own module solely for unit testing purposes. Nobody except `Server` should use this interface\n// directly.\n//\n// Note that all `Frankenvalue`s here are expected to contain cap tables holding live instances\n// of `SubrequestChannel` and `ActorClassChannel`.\nclass ChannelTokenHandler {\n public:\n  // Callbacks implemented by `Server` (in `server.h`) to resolve entrypoint designators to live\n  // objects.\n  //\n  // (In theory, we could have a decodeChannelToken() method that returns the service name,\n  // entrypoint name, and props as a struct, but this would require extra string copies and would\n  // also make abstractions a little messier in server.c++.)\n  class Resolver {\n   public:\n    virtual kj::Own<IoChannelFactory::SubrequestChannel> resolveEntrypoint(\n        kj::StringPtr serviceName, kj::Maybe<kj::StringPtr> entrypoint, Frankenvalue props) = 0;\n\n    virtual kj::Own<IoChannelFactory::ActorClassChannel> resolveActorClass(\n        kj::StringPtr serviceName, kj::Maybe<kj::StringPtr> entrypoint, Frankenvalue props) = 0;\n  };\n\n  explicit ChannelTokenHandler(Resolver& resolver);\n\n  // Helpers to implement `IoChannelFactory::{SubrequestChannel,ActorClassChannel}::getToken()`.\n  kj::Array<byte> encodeSubrequestChannelToken(IoChannelFactory::ChannelTokenUsage usage,\n      kj::StringPtr serviceName,\n      kj::Maybe<kj::StringPtr> entrypoint,\n      Frankenvalue& props);\n  kj::Array<byte> encodeActorClassChannelToken(IoChannelFactory::ChannelTokenUsage usage,\n      kj::StringPtr serviceName,\n      kj::Maybe<kj::StringPtr> entrypoint,\n      Frankenvalue& props);\n\n  // Helpers to implement `IoChannelFactory::{subrequestChannel,actorClass}FromToken()`.\n  kj::Own<IoChannelFactory::SubrequestChannel> decodeSubrequestChannelToken(\n      IoChannelFactory::ChannelTokenUsage usage, kj::ArrayPtr<const byte> token);\n  kj::Own<IoChannelFactory::ActorClassChannel> decodeActorClassChannelToken(\n      IoChannelFactory::ChannelTokenUsage usage, kj::ArrayPtr<const byte> token);\n\n private:\n  // Annoyingly the OpenSSL/BoringSSL headers don't seem to define these as static constants.\n  static constexpr uint AES_KEY_SIZE = 32;\n  static constexpr uint AES_IV_SIZE = 12;\n  static constexpr uint AES_MAC_SIZE = 16;\n\n  // The key ID is the 16-byte prefix of a SHA-256 hash of the secret key.\n  static constexpr uint KEY_ID_SIZE = 16;\n\n  Resolver& resolver;\n  byte tokenKey[AES_KEY_SIZE];\n  byte keyId[KEY_ID_SIZE];\n\n  struct TokenHeader {\n    uint32_t magic;\n    byte iv[AES_IV_SIZE];\n    byte keyId[KEY_ID_SIZE];\n  };\n  static_assert(sizeof(TokenHeader) == 32);\n\n  // Implementation for both `encode` methods.\n  kj::Array<byte> encodeChannelTokenImpl(ChannelToken::Type type,\n      IoChannelFactory::ChannelTokenUsage usage,\n      kj::StringPtr serviceName,\n      kj::Maybe<kj::StringPtr> entrypoint,\n      Frankenvalue& props);\n\n  // Implementation that dynamically returns either SubrequestChannel or ActorClassChannel, which\n  // both happen to inherit CapTableEntry. The caller will immediately downcast to the right type.\n  kj::Own<Frankenvalue::CapTableEntry> decodeChannelTokenImpl(ChannelToken::Type type,\n      IoChannelFactory::ChannelTokenUsage usage,\n      kj::ArrayPtr<const byte> token);\n};\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/container-client-test.c++",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Tests for container-client JSON decoding.\n//\n// These tests verify that JSON responses from the Docker API are decoded into\n// Cap'n Proto messages whose backing storage outlives the decode call. The\n// original decodeJsonResponse<T>() returned a Builder pointing into a\n// stack-local MallocMessageBuilder; the builder was then accessed after the\n// message was destroyed (use-after-free). Under ASAN this test would crash\n// with the old code.\n\n#include \"container-client.h\"\n\n#include <kj/test.h>\n\nnamespace workerd::server {\nnamespace {\n\n// Regression test for VULN-127728: ContainerInspectResponse decode must not\n// use-after-free.  With the old buggy code every field access after decode\n// dereferences freed heap memory (detectable by ASAN).\nKJ_TEST(\"decodeJsonResponse ContainerInspectResponse - no use-after-free\") {\n  // Minimal JSON matching what Docker returns for /containers/<id>/json.\n  auto json = R\"({\n    \"Id\": \"abc123\",\n    \"Created\": \"2025-01-01T00:00:00Z\",\n    \"Path\": \"/bin/sh\",\n    \"Args\": [\"--http-egress-port\", \"9000\"],\n    \"State\": {\n      \"Status\": \"running\",\n      \"Running\": true,\n      \"Paused\": false,\n      \"Restarting\": false,\n      \"OOMKilled\": false,\n      \"Dead\": false,\n      \"Pid\": 42,\n      \"ExitCode\": 0,\n      \"Error\": \"\",\n      \"StartedAt\": \"2025-01-01T00:00:01Z\",\n      \"FinishedAt\": \"0001-01-01T00:00:00Z\"\n    },\n    \"NetworkSettings\": {\n      \"Bridge\": \"\",\n      \"SandboxID\": \"\",\n      \"HairpinMode\": false,\n      \"LinkLocalIPv6Address\": \"\",\n      \"LinkLocalIPv6PrefixLen\": 0,\n      \"SandboxKey\": \"\",\n      \"EndpointID\": \"\",\n      \"Gateway\": \"172.17.0.1\",\n      \"GlobalIPv6Address\": \"\",\n      \"GlobalIPv6PrefixLen\": 0,\n      \"IPAddress\": \"172.17.0.2\",\n      \"IPPrefixLen\": 16,\n      \"IPv6Gateway\": \"\",\n      \"MacAddress\": \"02:42:ac:11:00:02\",\n      \"Networks\": {},\n      \"Ports\": {\n        \"8080/tcp\": [\n          {\"HostIp\": \"0.0.0.0\", \"HostPort\": \"55000\"}\n        ]\n      }\n    }\n  })\"_kj;\n\n  auto message = decodeJsonResponse<docker_api::Docker::ContainerInspectResponse>(json);\n  auto root = message->getRoot<docker_api::Docker::ContainerInspectResponse>();\n\n  // Every access below would be a heap-use-after-free with the old code.\n  KJ_EXPECT(root.getId() == \"abc123\");\n  KJ_EXPECT(root.hasState());\n\n  auto state = root.getState();\n  KJ_EXPECT(state.getStatus() == \"running\");\n  KJ_EXPECT(state.getRunning() == true);\n\n  KJ_EXPECT(root.hasArgs());\n  auto args = root.getArgs();\n  KJ_EXPECT(args.size() == 2);\n  KJ_EXPECT(args[0] == \"--http-egress-port\");\n  KJ_EXPECT(args[1] == \"9000\");\n\n  auto ports = root.getNetworkSettings().getPorts().getObject();\n  KJ_EXPECT(ports.size() == 1);\n  KJ_EXPECT(ports[0].getName() == \"8080/tcp\");\n  auto array = ports[0].getValue().getArray();\n  KJ_EXPECT(array.size() == 1);\n  auto obj = array[0].getObject();\n  KJ_EXPECT(obj.size() == 2);\n  // HostPort is obj[1] in the order Docker returns\n  auto mappedPort = obj[1].getValue().getString();\n  KJ_EXPECT(mappedPort == \"55000\");\n}\n\nKJ_TEST(\"decodeJsonResponse NetworkInspectResponse - no use-after-free\") {\n  auto json = R\"({\n    \"Name\": \"bridge\",\n    \"Id\": \"net123\",\n    \"IPAM\": {\n      \"Driver\": \"default\",\n      \"Config\": [\n        {\"Subnet\": \"172.17.0.0/16\", \"Gateway\": \"172.17.0.1\"}\n      ]\n    }\n  })\"_kj;\n\n  auto message = decodeJsonResponse<docker_api::Docker::NetworkInspectResponse>(json);\n  auto root = message->getRoot<docker_api::Docker::NetworkInspectResponse>();\n\n  KJ_EXPECT(root.getName() == \"bridge\");\n  auto ipamConfig = root.getIpam().getConfig();\n  KJ_EXPECT(ipamConfig.size() == 1);\n  KJ_EXPECT(ipamConfig[0].getSubnet() == \"172.17.0.0/16\");\n  KJ_EXPECT(ipamConfig[0].getGateway() == \"172.17.0.1\");\n}\n\nKJ_TEST(\"decodeJsonResponse ContainerMonitorResponse - no use-after-free\") {\n  auto json = R\"({\"StatusCode\": 0})\"_kj;\n\n  auto message = decodeJsonResponse<docker_api::Docker::ContainerMonitorResponse>(json);\n  auto root = message->getRoot<docker_api::Docker::ContainerMonitorResponse>();\n\n  KJ_EXPECT(root.getStatusCode() == 0);\n}\n\nKJ_TEST(\"decodeJsonResponse ContainerMonitorResponse - non-zero exit\") {\n  auto json = R\"({\"StatusCode\": 137})\"_kj;\n\n  auto message = decodeJsonResponse<docker_api::Docker::ContainerMonitorResponse>(json);\n  auto root = message->getRoot<docker_api::Docker::ContainerMonitorResponse>();\n\n  KJ_EXPECT(root.getStatusCode() == 137);\n}\n\n}  // namespace\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/container-client.c++",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"container-client.h\"\n\n#include \"ada.h\"\n\n#include <workerd/io/container.capnp.h>\n#include <workerd/io/worker-interface.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/url.h>\n#include <workerd/server/docker-api.capnp.h>\n#include <workerd/util/strings.h>\n\n#include <capnp/compat/json.h>\n#include <capnp/message.h>\n#include <kj/async-io.h>\n#include <kj/async.h>\n#include <kj/cidr.h>\n#include <kj/compat/http.h>\n#include <kj/debug.h>\n#include <kj/encoding.h>\n#include <kj/exception.h>\n#include <kj/string.h>\n\nnamespace workerd::server {\n\nnamespace {\n\nconstexpr uint16_t SIDECAR_INGRESS_PORT = 39001;\n\nstruct ParsedAddress {\n  kj::OneOf<kj::CidrRange, kj::String> destination;\n  kj::Maybe<uint16_t> port;\n};\n\nstruct HostAndPort {\n  kj::String host;\n  kj::Maybe<uint16_t> port;\n};\n\n// Strips a port suffix from a string, returning the host and port separately.\n// For IPv6, expects brackets: \"[::1]:8080\" -> (\"::1\", 8080)\n// For IPv4: \"10.0.0.1:8080\" -> (\"10.0.0.1\", 8080)\n// If no port, returns the host as-is with no port.\nHostAndPort stripPort(kj::StringPtr str) {\n  if (str.startsWith(\"[\")) {\n    // Bracketed IPv6: \"[ipv6]\" or \"[ipv6]:port\"\n    size_t closeBracket =\n        KJ_REQUIRE_NONNULL(str.findLast(']'), \"Unclosed '[' in address string.\", str);\n\n    auto host = str.slice(1, closeBracket);\n\n    if (str.size() > closeBracket + 1) {\n      KJ_REQUIRE(\n          str.slice(closeBracket + 1).startsWith(\":\"), \"Expected port suffix after ']'.\", str);\n      auto port = KJ_REQUIRE_NONNULL(\n          str.slice(closeBracket + 2).tryParseAs<uint16_t>(), \"Invalid port number.\", str);\n      return {kj::str(host), port};\n    }\n    return {kj::str(host), kj::none};\n  }\n\n  // No brackets - check if there's exactly one colon (IPv4 with port)\n  // IPv6 without brackets has 2+ colons and no port suffix supported\n  KJ_IF_SOME(colonPos, str.findLast(':')) {\n    auto afterColon = str.slice(colonPos + 1);\n    KJ_IF_SOME(port, afterColon.tryParseAs<uint16_t>()) {\n      // Valid port - but only treat as port for IPv4 (check no other colons before)\n      auto beforeColon = str.first(colonPos);\n      if (beforeColon.findFirst(':') == kj::none) {\n        return {kj::str(beforeColon), port};\n      }\n    }\n  }\n\n  return {kj::str(str), kj::none};\n}\n\n// Build a CidrRange from a host string, adding /32 or /128 prefix if not present.\nkj::CidrRange makeCidr(kj::StringPtr host) {\n  if (host.findFirst('/') != kj::none) {\n    return kj::CidrRange(host);\n  }\n  // No CIDR prefix - add /32 for IPv4, /128 for IPv6\n  bool isIpv6 = host.findFirst(':') != kj::none;\n  return kj::CidrRange(kj::str(host, isIpv6 ? \"/128\" : \"/32\"));\n}\n\nkj::Maybe<kj::CidrRange> tryMakeCidr(kj::StringPtr host) {\n  kj::Maybe<kj::CidrRange> cidr;\n  KJ_IF_SOME(_, kj::runCatchingExceptions([&]() { cidr = makeCidr(host); })) {\n    return kj::none;\n  }\n\n  return kj::mv(cidr);\n}\n\n// normalizeHostname normalizes the hostname. It's designed to receive the hostname when\n// proxy-everything sends the HTTP CONNECT with the X-Hostname hint.\nkj::String normalizeHostname(kj::StringPtr hostname) {\n  auto url = kj::str(\"http://\", hostname);\n  auto parsed = ada::parse<ada::url_aggregator>({url.begin(), url.size()}, nullptr);\n  KJ_REQUIRE(parsed.has_value(), \"Invalid X-Hostname URL hint.\", hostname);\n  auto normalizedHostname = parsed->get_hostname();\n  return kj::heapString(normalizedHostname.data(), normalizedHostname.size());\n}\n\n// hostnameGlobMatches should match patterns like:\n//   cloudflare.*.com\n//   cloudflare.com\n//   cloudflare\n//   *\n//\n// hostname must be normalized beforehand\nbool hostnameGlobMatches(kj::StringPtr pattern, kj::StringPtr hostname) {\n  size_t patternIndex = 0;\n  size_t hostnameIndex = 0;\n  size_t restartHostnameIndex = 0;\n  kj::Maybe<size_t> starPatternIndex;\n\n  while (hostnameIndex < hostname.size()) {\n    if (patternIndex < pattern.size() && pattern[patternIndex] == '*') {\n      starPatternIndex = patternIndex++;\n      restartHostnameIndex = hostnameIndex;\n      continue;\n    }\n\n    if (patternIndex < pattern.size() && pattern[patternIndex] == hostname[hostnameIndex]) {\n      ++patternIndex;\n      ++hostnameIndex;\n      continue;\n    }\n\n    KJ_IF_SOME(starIndex, starPatternIndex) {\n      patternIndex = starIndex + 1;\n      hostnameIndex = ++restartHostnameIndex;\n      continue;\n    }\n\n    return false;\n  }\n\n  while (patternIndex < pattern.size() && pattern[patternIndex] == '*') {\n    ++patternIndex;\n  }\n\n  return patternIndex == pattern.size();\n}\n\nkj::Maybe<kj::StringPtr> getHeader(const kj::HttpHeaders& headers, kj::StringPtr name) {\n  kj::Maybe<kj::StringPtr> result;\n  headers.forEach([&](kj::StringPtr headerName, kj::StringPtr value) {\n    if (result == kj::none && workerd::strcaseeq(headerName, name)) {\n      result = value;\n    }\n  });\n  return result;\n}\n\n// Parses \"host[:port]\" strings. Handles:\n// - IPv4: \"10.0.0.1\", \"10.0.0.1:8080\", \"10.0.0.0/8\", \"10.0.0.0/8:8080\"\n// - IPv6 with brackets: \"[::1]\", \"[::1]:8080\", \"[fe80::1]\", \"[fe80::/10]:8080\"\n// - IPv6 without brackets: \"::1\", \"fe80::1\", \"fe80::/10\"\nParsedAddress parseHostPort(kj::StringPtr str) {\n  auto hostAndPort = stripPort(str);\n  KJ_REQUIRE(hostAndPort.host.size() > 0, \"Host must not be empty.\", str);\n\n  KJ_IF_SOME(cidr, tryMakeCidr(hostAndPort.host)) {\n    return {\n      .destination = kj::mv(cidr),\n      .port = hostAndPort.port,\n    };\n  }\n\n  return {\n    .destination = workerd::toLower(hostAndPort.host),\n    .port = hostAndPort.port,\n  };\n}\n\nkj::StringPtr signalToString(uint32_t signal) {\n  switch (signal) {\n    case 1:\n      return \"SIGHUP\"_kj;  // Hangup\n    case 2:\n      return \"SIGINT\"_kj;  // Interrupt\n    case 3:\n      return \"SIGQUIT\"_kj;  // Quit\n    case 4:\n      return \"SIGILL\"_kj;  // Illegal instruction\n    case 5:\n      return \"SIGTRAP\"_kj;  // Trace trap\n    case 6:\n      return \"SIGABRT\"_kj;  // Abort\n    case 7:\n      return \"SIGBUS\"_kj;  // Bus error\n    case 8:\n      return \"SIGFPE\"_kj;  // Floating point exception\n    case 9:\n      return \"SIGKILL\"_kj;  // Kill\n    case 10:\n      return \"SIGUSR1\"_kj;  // User signal 1\n    case 11:\n      return \"SIGSEGV\"_kj;  // Segmentation violation\n    case 12:\n      return \"SIGUSR2\"_kj;  // User signal 2\n    case 13:\n      return \"SIGPIPE\"_kj;  // Broken pipe\n    case 14:\n      return \"SIGALRM\"_kj;  // Alarm clock\n    case 15:\n      return \"SIGTERM\"_kj;  // Termination\n    case 16:\n      return \"SIGSTKFLT\"_kj;  // Stack fault (Linux)\n    case 17:\n      return \"SIGCHLD\"_kj;  // Child status changed\n    case 18:\n      return \"SIGCONT\"_kj;  // Continue\n    case 19:\n      return \"SIGSTOP\"_kj;  // Stop\n    case 20:\n      return \"SIGTSTP\"_kj;  // Terminal stop\n    case 21:\n      return \"SIGTTIN\"_kj;  // Background read from tty\n    case 22:\n      return \"SIGTTOU\"_kj;  // Background write to tty\n    case 23:\n      return \"SIGURG\"_kj;  // Urgent condition on socket\n    case 24:\n      return \"SIGXCPU\"_kj;  // CPU limit exceeded\n    case 25:\n      return \"SIGXFSZ\"_kj;  // File size limit exceeded\n    case 26:\n      return \"SIGVTALRM\"_kj;  // Virtual alarm clock\n    case 27:\n      return \"SIGPROF\"_kj;  // Profiling alarm clock\n    case 28:\n      return \"SIGWINCH\"_kj;  // Window size change\n    case 29:\n      return \"SIGIO\"_kj;  // I/O now possible\n    case 30:\n      return \"SIGPWR\"_kj;  // Power failure restart (Linux)\n    case 31:\n      return \"SIGSYS\"_kj;  // Bad system call\n    default:\n      return \"SIGKILL\"_kj;\n  }\n}\n}  // namespace\n\nContainerClient::ContainerClient(capnp::ByteStreamFactory& byteStreamFactory,\n    kj::Timer& timer,\n    kj::Network& network,\n    kj::String dockerPath,\n    kj::String containerName,\n    kj::String imageName,\n    kj::String containerEgressInterceptorImage,\n    kj::TaskSet& waitUntilTasks,\n    kj::Promise<void> pendingCleanup,\n    kj::Function<void(kj::Promise<void>)> cleanupCallback,\n    ChannelTokenHandler& channelTokenHandler)\n    : byteStreamFactory(byteStreamFactory),\n      timer(timer),\n      network(network),\n      dockerPath(kj::mv(dockerPath)),\n      containerName(kj::encodeUriComponent(kj::str(containerName))),\n      sidecarContainerName(kj::encodeUriComponent(kj::str(containerName, \"-proxy\"))),\n      imageName(kj::mv(imageName)),\n      containerEgressInterceptorImage(kj::mv(containerEgressInterceptorImage)),\n      waitUntilTasks(waitUntilTasks),\n      pendingCleanup(kj::mv(pendingCleanup).fork()),\n      cleanupCallback(kj::mv(cleanupCallback)),\n      channelTokenHandler(channelTokenHandler) {}\n\nContainerClient::~ContainerClient() noexcept(false) {\n  stopEgressListener();\n\n  // Best-effort cleanup for both containers.\n  auto sidecarCleanup = dockerApiRequest(network, kj::str(dockerPath), kj::HttpMethod::DELETE,\n      kj::str(\"/containers/\", sidecarContainerName, \"?force=true\"))\n                            .ignoreResult()\n                            .catch_([](kj::Exception&&) {});\n\n  auto mainCleanup = dockerApiRequest(network, kj::str(dockerPath), kj::HttpMethod::DELETE,\n      kj::str(\"/containers/\", containerName, \"?force=true\"))\n                         .ignoreResult()\n                         .catch_([](kj::Exception&&) {});\n\n  // Pass the joined cleanup promise to the callback. The callback wraps it with the\n  // canceler (so a future client creation can cancel it), stores it so the next\n  // ContainerClient can await it, and adds a branch to waitUntilTasks to keep the\n  // underlying I/O alive.\n  cleanupCallback(kj::joinPromises(kj::arr(kj::mv(sidecarCleanup), kj::mv(mainCleanup))));\n}\n\n// Docker-specific Port implementation that implements rpc::Container::Port::Server\n// It does a HTTP CONNECT to the proxy-everything sidecar port.\nclass ContainerClient::DockerPort final: public rpc::Container::Port::Server {\n public:\n  DockerPort(ContainerClient& containerClient, kj::String containerHost, uint16_t containerPort)\n      : containerClient(containerClient),\n        containerHost(kj::mv(containerHost)),\n        containerPort(containerPort) {}\n\n  kj::Promise<void> connect(ConnectContext context) override {\n    auto mappedPort = JSG_REQUIRE_NONNULL(containerClient.sidecarIngressHostPort, Error,\n        \"connect(): Container ingress proxy is not running.\");\n\n    auto dstAddr = kj::str(containerHost, \":\", containerPort);\n\n    auto address = co_await containerClient.network.parseAddress(kj::str(\"127.0.0.1:\", mappedPort));\n\n    kj::HttpHeaderTable::Builder headerTableBuilder;\n    auto xDstAddrHeader = headerTableBuilder.add(\"X-Dst-Addr\");\n    auto headerTable = headerTableBuilder.build();\n    kj::HttpHeaders headers(*headerTable);\n    headers.set(xDstAddrHeader, kj::str(dstAddr));\n\n    auto proxyConnection = co_await address->connect();\n    auto httpClient = kj::newHttpClient(*headerTable, *proxyConnection)\n                          .attach(kj::mv(proxyConnection), kj::mv(headerTable));\n    auto connectRequest = httpClient->connect(dstAddr, headers, {});\n    auto status = co_await kj::mv(connectRequest.status);\n\n    if (status.statusCode == 400) {\n      throw JSG_KJ_EXCEPTION(\n          DISCONNECTED, Error, \"Container is not listening to port \", containerPort);\n    }\n\n    if (status.statusCode < 200 || status.statusCode >= 300) {\n      KJ_IF_SOME(errorBody, status.errorBody) {\n        auto errorBodyText = co_await errorBody->readAllText();\n        JSG_FAIL_REQUIRE(Error, \"Connecting to container port through proxy-everything failed: [\",\n            status.statusCode, \"] \", status.statusText, \" \", errorBodyText);\n      }\n\n      JSG_FAIL_REQUIRE(Error, \"Connecting to container port through proxy-everything failed: [\",\n          status.statusCode, \"] \", status.statusText);\n    }\n\n    auto connection = kj::mv(connectRequest.connection);\n    auto upPipe = kj::newOneWayPipe();\n    auto upEnd = kj::mv(upPipe.in);\n    auto results = context.getResults();\n    results.setUp(containerClient.byteStreamFactory.kjToCapnp(kj::mv(upPipe.out)));\n    auto downEnd = containerClient.byteStreamFactory.capnpToKj(context.getParams().getDown());\n\n    pumpTask =\n        kj::joinPromisesFailFast(kj::arr(upEnd->pumpTo(*connection), connection->pumpTo(*downEnd)))\n            .ignoreResult()\n            .attach(kj::mv(httpClient), kj::mv(upEnd), kj::mv(connection), kj::mv(downEnd));\n    co_return;\n  }\n\n private:\n  // ContainerClient is owned by the Worker::Actor and keeps it alive.\n  ContainerClient& containerClient;\n  kj::String containerHost;\n  uint16_t containerPort;\n  kj::Maybe<kj::Promise<void>> pumpTask;\n};\n\n// HTTP service that handles HTTP CONNECT requests from the container sidecar (proxy-everything).\n// When the sidecar intercepts container egress traffic, it sends HTTP CONNECT to this service.\n// After accepting the CONNECT, the tunnel carries the actual HTTP request from the container,\n// which we parse and forward to the appropriate SubrequestChannel based on egressMappings.\n// Inner HTTP service that handles requests inside the CONNECT tunnel.\n// Forwards requests to the worker binding via SubrequestChannel.\nclass InnerEgressService final: public kj::HttpService {\n public:\n  using ChannelLookup = kj::Function<kj::Maybe<kj::Own<IoChannelFactory::SubrequestChannel>>()>;\n\n  InnerEgressService(ChannelLookup lookupChannel, kj::StringPtr destAddr)\n      : lookupChannel(kj::mv(lookupChannel)),\n        destAddr(kj::str(destAddr)) {}\n\n  kj::Promise<void> request(kj::HttpMethod method,\n      kj::StringPtr requestUri,\n      const kj::HttpHeaders& headers,\n      kj::AsyncInputStream& requestBody,\n      Response& response) override {\n    // Look up the channel on each request so we always use the latest mapping,\n    // even if it was replaced via interceptOutboundHttp while the tunnel is open.\n    auto channel =\n        KJ_REQUIRE_NONNULL(lookupChannel(), \"egress mapping disappeared during active tunnel\");\n\n    IoChannelFactory::SubrequestMetadata metadata;\n    auto worker = channel->startRequest(kj::mv(metadata));\n    auto urlForWorker = kj::str(requestUri);\n    // Probably only a path, try to get it from Host:\n    if (requestUri.startsWith(\"/\")) {\n      auto baseUrl = kj::str(\"http://\", destAddr);\n      // Use Host: when possible\n      KJ_IF_SOME(host, headers.get(kj::HttpHeaderId::HOST)) {\n        baseUrl = kj::str(\"http://\", host);\n      }\n\n      // Parse url, if invalid, try to use the original requestUri (http://<ip>/<path>\n      KJ_IF_SOME(parsedUrl, jsg::Url::tryParse(requestUri, baseUrl.asPtr())) {\n        urlForWorker = kj::str(parsedUrl.getHref());\n      } else {\n        urlForWorker = kj::str(baseUrl, requestUri);\n      }\n    }\n\n    co_await worker->request(method, urlForWorker, headers, requestBody, response);\n  }\n\n private:\n  ChannelLookup lookupChannel;\n  kj::String destAddr;\n};\n\n// Outer HTTP service that handles CONNECT requests from the sidecar.\nclass EgressHttpService final: public kj::HttpService {\n public:\n  EgressHttpService(ContainerClient& containerClient, kj::HttpHeaderTable& headerTable)\n      : containerClient(containerClient),\n        headerTable(headerTable) {}\n\n  kj::Promise<void> request(kj::HttpMethod method,\n      kj::StringPtr url,\n      const kj::HttpHeaders& headers,\n      kj::AsyncInputStream& requestBody,\n      Response& response) override {\n    // Regular HTTP requests are not expected - we only handle CONNECT\n    co_return co_await response.sendError(405, \"Method Not Allowed\", headerTable);\n  }\n\n  kj::Promise<void> connect(kj::StringPtr host,\n      const kj::HttpHeaders& headers,\n      kj::AsyncIoStream& connection,\n      ConnectResponse& response,\n      kj::HttpConnectSettings settings) override {\n    auto destAddr = kj::str(host);\n    kj::Maybe<kj::String> requestHostname;\n    // X-Hostname is set by proxy-everything\n    // when it peeks over a connection and sees a HTTP header.\n    KJ_IF_SOME(value, getHeader(headers, \"X-Hostname\")) {\n      requestHostname = kj::str(value);\n    }\n\n    kj::HttpHeaders responseHeaders(headerTable);\n    response.accept(200, \"OK\", responseHeaders);\n\n    auto mapping = containerClient.findEgressMapping(destAddr, /*defaultPort=*/80,\n        requestHostname.map([](auto& hostname) {\n      return kj::Maybe<kj::StringPtr>(hostname);\n    }).orDefault(kj::none));\n\n    if (mapping != kj::none) {\n      // Layer an HttpServer on top of the tunnel to handle HTTP parsing/serialization.\n      // InnerEgressService looks up the mapping on each request so channel replacements\n      // via interceptOutboundHttp are picked up on existing tunnels.\n      auto innerService = kj::heap<InnerEgressService>(\n          [&client = containerClient, addr = kj::str(destAddr),\n              hostname = kj::mv(\n                  requestHostname)]() -> kj::Maybe<kj::Own<IoChannelFactory::SubrequestChannel>> {\n        return client.findEgressMapping(addr, /*defaultPort=*/80,\n            hostname.map([](auto& value) {\n          return kj::Maybe<kj::StringPtr>(value);\n        }).orDefault(kj::none));\n      },\n          destAddr);\n      auto innerServer =\n          kj::heap<kj::HttpServer>(containerClient.timer, headerTable, *innerService);\n\n      co_await innerServer->listenHttpCleanDrain(connection);\n\n      co_return;\n    }\n\n    if (!containerClient.internetEnabled.orDefault(false)) {\n      connection.shutdownWrite();\n      co_return;\n    }\n\n    // No egress mapping and internet enabled, so forward via raw TCP\n    auto addr = co_await containerClient.network.parseAddress(destAddr);\n    auto destConn = co_await addr->connect();\n\n    auto connToDestination = connection.pumpTo(*destConn).then(\n        [&destConn = *destConn](uint64_t) { destConn.shutdownWrite(); });\n\n    auto destinationToConn =\n        destConn->pumpTo(connection).then([&connection](uint64_t) { connection.shutdownWrite(); });\n\n    co_await kj::joinPromisesFailFast(\n        kj::arr(kj::mv(connToDestination), kj::mv(destinationToConn)));\n    co_return;\n  }\n\n private:\n  ContainerClient& containerClient;\n  kj::HttpHeaderTable& headerTable;\n};\n\nkj::Promise<ContainerClient::IPAMConfigResult> ContainerClient::getDockerBridgeIPAMConfig() {\n  auto response = co_await dockerApiRequest(\n      network, kj::str(dockerPath), kj::HttpMethod::GET, kj::str(\"/networks/bridge\"));\n  if (response.statusCode == 200) {\n    auto message = decodeJsonResponse<docker_api::Docker::NetworkInspectResponse>(response.body);\n    auto jsonRoot = message->getRoot<docker_api::Docker::NetworkInspectResponse>();\n    auto ipamConfig = jsonRoot.getIpam().getConfig();\n    if (ipamConfig.size() > 0) {\n      auto config = ipamConfig[0];\n      co_return IPAMConfigResult{\n        .gateway = kj::str(config.getGateway()),\n        .subnet = kj::str(config.getSubnet()),\n      };\n    }\n  }\n\n  JSG_FAIL_REQUIRE(Error,\n      \"Failed to get bridge. \"\n      \"Status: \",\n      response.statusCode, \", Body: \", response.body);\n}\n\nkj::Promise<bool> ContainerClient::isDaemonIpv6Enabled() {\n  // Inspect the default bridge network. When the Docker daemon has \"ipv6\": true in\n  // daemon.json, the default bridge gets an IPv6 IPAM subnet entry (e.g. \"fd00::/80\").\n  auto response = co_await dockerApiRequest(\n      network, kj::str(dockerPath), kj::HttpMethod::GET, kj::str(\"/networks/bridge\"));\n\n  if (response.statusCode != 200) {\n    co_return false;\n  }\n\n  auto message = decodeJsonResponse<docker_api::Docker::NetworkInspectResponse>(response.body);\n  auto jsonRoot = message->getRoot<docker_api::Docker::NetworkInspectResponse>();\n  for (auto config: jsonRoot.getIpam().getConfig()) {\n    // IPv6 subnets contain ':' (e.g. \"fd00::/80\", \"2001:db8::/64\")\n    if (kj::StringPtr(config.getSubnet()).findFirst(':') != kj::none) {\n      co_return true;\n    }\n  }\n\n  co_return false;\n}\n\n// Returns the gateway IP on Linux for direct container access.\n// Returns kj::none on macOS where Docker Desktop routes host-gateway to host loopback.\nstatic kj::Maybe<kj::String> gatewayForPlatform(kj::String gateway) {\n#ifdef __APPLE__\n  return kj::none;\n#else\n  return kj::mv(gateway);\n#endif\n}\n\nkj::Maybe<uint16_t> tryParsePublishedHostPort(capnp::json::Value::Reader portMappingValue) {\n  if (portMappingValue.isNull()) {\n    return kj::none;\n  }\n\n  JSG_REQUIRE(\n      portMappingValue.isArray(), Error, \"Malformed ContainerInspect port mapping response\");\n  auto bindings = portMappingValue.getArray();\n  if (bindings.size() == 0) {\n    return kj::none;\n  }\n\n  auto binding = bindings[0];\n  JSG_REQUIRE(binding.isObject(), Error, \"Malformed ContainerInspect port binding response\");\n  for (auto field: binding.getObject()) {\n    if (field.getName() == \"HostPort\") {\n      auto value = field.getValue();\n      JSG_REQUIRE(value.isString(), Error, \"Malformed ContainerInspect port binding response\");\n      kj::StringPtr hostPort = value.getString();\n      return KJ_REQUIRE_NONNULL(\n          hostPort.tryParseAs<uint16_t>(), \"Malformed ContainerInspect host port\");\n    }\n  }\n\n  KJ_FAIL_REQUIRE(\"Malformed ContainerInspect port binding response: missing HostPort\");\n}\n\nkj::Promise<uint16_t> ContainerClient::startEgressListener(\n    kj::String listenAddress, uint16_t port) {\n  auto service = kj::heap<EgressHttpService>(*this, headerTable);\n  auto httpServer = kj::heap<kj::HttpServer>(timer, headerTable, *service);\n  auto& httpServerRef = *httpServer;\n\n  egressHttpServer = httpServer.attach(kj::mv(service));\n\n  auto addr = co_await network.parseAddress(kj::str(listenAddress, \":\", port));\n  auto listener = addr->listen();\n\n  uint16_t chosenPort = listener->getPort();\n\n  egressListenerTask = httpServerRef.listenHttp(*listener)\n                           .attach(kj::mv(listener))\n                           .eagerlyEvaluate([](kj::Exception&& e) {\n    LOG_EXCEPTION(\n        \"Workerd could not listen in the TCP port to proxy traffic off the docker container\", e);\n  });\n\n  co_return chosenPort;\n}\n\nvoid ContainerClient::stopEgressListener() {\n  egressListenerTask = kj::none;\n  egressHttpServer = kj::none;\n  egressListenerStarted.store(false, std::memory_order_release);\n}\n\nkj::Promise<ContainerClient::Response> ContainerClient::dockerApiRequest(kj::Network& network,\n    kj::String dockerPath,\n    kj::HttpMethod method,\n    kj::String endpoint,\n    kj::Maybe<kj::String> body) {\n  kj::HttpHeaderTable headerTable;\n  auto address = co_await network.parseAddress(dockerPath);\n  auto connection = co_await address->connect();\n  auto httpClient = kj::newHttpClient(headerTable, *connection).attach(kj::mv(connection));\n  kj::HttpHeaders headers(headerTable);\n  headers.setPtr(kj::HttpHeaderId::HOST, \"localhost\");\n\n  KJ_IF_SOME(requestBody, body) {\n    headers.setPtr(kj::HttpHeaderId::CONTENT_TYPE, \"application/json\");\n    headers.set(kj::HttpHeaderId::CONTENT_LENGTH, kj::str(requestBody.size()));\n\n    auto req = httpClient->request(method, endpoint, headers, requestBody.size());\n    {\n      auto body = kj::mv(req.body);\n      co_await body->write(requestBody.asBytes());\n    }\n    auto response = co_await req.response;\n    auto result = co_await response.body->readAllText();\n    co_return Response{.statusCode = response.statusCode, .body = kj::mv(result)};\n  } else {\n    auto req = httpClient->request(method, endpoint, headers);\n    { auto body = kj::mv(req.body); }\n    auto response = co_await req.response;\n    auto result = co_await response.body->readAllText();\n    co_return Response{.statusCode = response.statusCode, .body = kj::mv(result)};\n  }\n}\n\nkj::Promise<ContainerClient::InspectResponse> ContainerClient::inspectContainer() {\n  auto endpoint = kj::str(\"/containers/\", containerName, \"/json\");\n\n  auto response = co_await dockerApiRequest(\n      network, kj::str(dockerPath), kj::HttpMethod::GET, kj::mv(endpoint));\n  // We check if the container with the given name exist, and if it's not,\n  // we simply return false while avoiding an unnecessary error.\n  if (response.statusCode == 404) {\n    co_return InspectResponse{.isRunning = false};\n  }\n\n  JSG_REQUIRE(response.statusCode == 200, Error, \"Container inspect failed\");\n  // Parse JSON response\n  auto message = decodeJsonResponse<docker_api::Docker::ContainerInspectResponse>(response.body);\n  auto jsonRoot = message->getRoot<docker_api::Docker::ContainerInspectResponse>();\n\n  // Look for Status field in the JSON object\n  JSG_REQUIRE(jsonRoot.hasState(), Error, \"Malformed ContainerInspect response\");\n  auto state = jsonRoot.getState();\n  JSG_REQUIRE(state.hasStatus(), Error, \"Malformed ContainerInspect response\");\n  auto status = state.getStatus();\n  // Treat both \"running\" and \"restarting\" as running. The \"restarting\" state occurs when\n  // Docker is automatically restarting a container (due to restart policy). From the user's\n  // perspective, a restarting container is still \"alive\" and should be treated as running\n  // so that start() correctly refuses to start a duplicate and destroy() can clean it up.\n  bool running = status == \"running\" || status == \"restarting\";\n  co_return InspectResponse{.isRunning = running};\n}\n\nkj::Promise<kj::Maybe<ContainerClient::SidecarInspectResponse>> ContainerClient::inspectSidecar() {\n  auto endpoint = kj::str(\"/containers/\", sidecarContainerName, \"/json\");\n  auto response = co_await dockerApiRequest(\n      network, kj::str(dockerPath), kj::HttpMethod::GET, kj::mv(endpoint));\n\n  if (response.statusCode == 404) {\n    co_return kj::none;\n  }\n\n  JSG_REQUIRE(response.statusCode == 200, Error, \"Sidecar container inspect failed\");\n\n  auto message = decodeJsonResponse<docker_api::Docker::ContainerInspectResponse>(response.body);\n  auto jsonRoot = message->getRoot<docker_api::Docker::ContainerInspectResponse>();\n\n  // Check if sidecar is actually running\n  bool running = false;\n  if (jsonRoot.hasState()) {\n    auto state = jsonRoot.getState();\n    if (state.hasStatus()) {\n      auto status = state.getStatus();\n      running = status == \"running\" || status == \"restarting\";\n    }\n  }\n\n  if (!running) {\n    co_return kj::none;\n  }\n\n  kj::Maybe<uint16_t> ingressHostPort;\n\n  auto ingressPortKey = kj::str(SIDECAR_INGRESS_PORT, \"/tcp\");\n  for (auto portMapping: jsonRoot.getNetworkSettings().getPorts().getObject()) {\n    if (portMapping.getName() != ingressPortKey) {\n      continue;\n    }\n\n    ingressHostPort = tryParsePublishedHostPort(portMapping.getValue());\n    break;\n  }\n\n  auto requiredIngressHostPort =\n      KJ_REQUIRE_NONNULL(ingressHostPort, \"running sidecar missing ingress host port\");\n\n  co_return SidecarInspectResponse{\n    .ingressHostPort = requiredIngressHostPort,\n  };\n}\n\nkj::Promise<void> ContainerClient::updateSidecarEgressPort(\n    uint16_t ingressHostPort, uint16_t egressPort) {\n  capnp::JsonCodec codec;\n  codec.handleByAnnotation<docker_api::ProxyEverything::Port>();\n  capnp::MallocMessageBuilder message;\n  auto jsonRoot = message.initRoot<docker_api::ProxyEverything::Port>();\n  jsonRoot.setPort(egressPort);\n\n  auto body = codec.encode(jsonRoot);\n  auto response = co_await dockerApiRequest(network, kj::str(\"127.0.0.1:\", ingressHostPort),\n      kj::HttpMethod::PUT, kj::str(\"/egress\"), kj::mv(body));\n\n  JSG_REQUIRE(response.statusCode >= 200 && response.statusCode < 300, Error,\n      \"Updating sidecar egress port failed with: \", response.statusCode, \" \", response.body);\n}\n\nkj::Promise<void> ContainerClient::updateSidecarEgressConfig(\n    uint16_t ingressHostPort, uint16_t egressPort) {\n  capnp::JsonCodec codec;\n  codec.handleByAnnotation<docker_api::ProxyEverything::Port>();\n  capnp::MallocMessageBuilder message;\n  auto jsonRoot = message.initRoot<docker_api::ProxyEverything::Port>();\n  jsonRoot.setPort(egressPort);\n\n  auto allowHostnames = getDnsAllowHostnames();\n  auto dns = jsonRoot.initDns();\n  auto allowHostnamesList = dns.initAllowHostnames(allowHostnames.size());\n  for (auto i: kj::indices(allowHostnames)) {\n    allowHostnamesList.set(i, allowHostnames[i]);\n  }\n\n  KJ_IF_SOME(enabled, internetEnabled) {\n    jsonRoot.initInternet().setEnabled(enabled);\n  }\n\n  auto body = codec.encode(jsonRoot);\n  auto response = co_await dockerApiRequest(network, kj::str(\"127.0.0.1:\", ingressHostPort),\n      kj::HttpMethod::PUT, kj::str(\"/egress\"), kj::mv(body));\n\n  JSG_REQUIRE(response.statusCode >= 200 && response.statusCode < 300, Error,\n      \"Updating sidecar egress config failed with: \", response.statusCode, \" \", response.body);\n}\n\nkj::Promise<void> ContainerClient::createContainer(\n    kj::Maybe<capnp::List<capnp::Text>::Reader> entrypoint,\n    kj::Maybe<capnp::List<capnp::Text>::Reader> environment,\n    rpc::Container::StartParams::Reader params) {\n  capnp::JsonCodec codec;\n  codec.handleByAnnotation<docker_api::Docker::ContainerCreateRequest>();\n  capnp::MallocMessageBuilder message;\n  auto jsonRoot = message.initRoot<docker_api::Docker::ContainerCreateRequest>();\n  jsonRoot.setImage(imageName);\n  // Add entrypoint if provided\n  KJ_IF_SOME(ep, entrypoint) {\n    auto jsonCmd = jsonRoot.initCmd(ep.size());\n    for (uint32_t i: kj::zeroTo(ep.size())) {\n      jsonCmd.set(i, ep[i]);\n    }\n  }\n\n  auto envSize = environment.map([](auto& env) { return env.size(); }).orDefault(0);\n  auto jsonEnv = jsonRoot.initEnv(envSize + kj::size(defaultEnv));\n\n  KJ_IF_SOME(env, environment) {\n    for (uint32_t i: kj::zeroTo(env.size())) {\n      jsonEnv.set(i, env[i]);\n    }\n  }\n\n  for (uint32_t i: kj::zeroTo(kj::size(defaultEnv))) {\n    jsonEnv.set(envSize + i, defaultEnv[i]);\n  }\n\n  // Pass user-supplied labels as Docker object labels, visible via `docker inspect`.\n  if (params.hasLabels()) {\n    auto lbls = params.getLabels();\n    auto labelsObj = jsonRoot.initLabels().initObject(lbls.size());\n    for (auto i: kj::zeroTo(lbls.size())) {\n      labelsObj[i].setName(lbls[i].getName());\n      labelsObj[i].initValue().setString(lbls[i].getValue());\n    }\n  }\n\n  auto hostConfig = jsonRoot.initHostConfig();\n  // We need to set a restart policy to avoid having ambiguous states\n  // where the container we're managing is stuck at \"exited\" state.\n  hostConfig.initRestartPolicy().setName(\"on-failure\");\n\n  hostConfig.setNetworkMode(kj::str(\"container:\", sidecarContainerName));\n\n  // When containersPidNamespace is NOT enabled, use host PID namespace for backwards compatibility.\n  // This allows the container to see processes on the host.\n  if (!params.getCompatibilityFlags().getContainersPidNamespace()) {\n    hostConfig.setPidMode(\"host\");\n  }\n\n  auto response = co_await dockerApiRequest(network, kj::str(dockerPath), kj::HttpMethod::POST,\n      kj::str(\"/containers/create?name=\", containerName), codec.encode(jsonRoot));\n\n  // statusCode 409 refers to \"conflict\". Occurs when a container with the given name exists.\n  // In that case we destroy and re-create the container. We retry a few times with delays\n  // because Docker may take a moment to fully release the container name after removal.\n  constexpr int MAX_RETRIES = 3;\n  constexpr auto RETRY_DELAY = 100 * kj::MILLISECONDS;\n\n  for (int attempt = 0; response.statusCode == 409 && attempt < MAX_RETRIES; ++attempt) {\n    co_await destroyContainer();\n    co_await timer.afterDelay(RETRY_DELAY);\n    response = co_await dockerApiRequest(network, kj::str(dockerPath), kj::HttpMethod::POST,\n        kj::str(\"/containers/create?name=\", containerName), codec.encode(jsonRoot));\n  }\n\n  // statusCode 201 refers to \"container created successfully\"\n  if (response.statusCode != 201) {\n    JSG_REQUIRE(response.statusCode != 404, Error, \"No such image available named \", imageName);\n    JSG_REQUIRE(response.statusCode != 409, Error, \"Container already exists\");\n    JSG_FAIL_REQUIRE(\n        Error, \"Create container failed with [\", response.statusCode, \"] \", response.body);\n  }\n}\n\nkj::Promise<void> ContainerClient::startContainer() {\n  auto endpoint = kj::str(\"/containers/\", containerName, \"/start\");\n  // We have to send an empty body since docker API will throw an error if we don't.\n  auto response = co_await dockerApiRequest(\n      network, kj::str(dockerPath), kj::HttpMethod::POST, kj::mv(endpoint), kj::str(\"\"));\n  // statusCode 304 refers to \"container already started\"\n  JSG_REQUIRE(response.statusCode != 304, Error, \"Container already started\");\n  // statusCode 204 refers to \"no error\"\n  JSG_REQUIRE(response.statusCode == 204, Error, \"Starting container failed with: \", response.body);\n}\n\nkj::Promise<void> ContainerClient::stopContainer() {\n  auto endpoint = kj::str(\"/containers/\", containerName, \"/stop\");\n  auto response = co_await dockerApiRequest(\n      network, kj::str(dockerPath), kj::HttpMethod::POST, kj::mv(endpoint));\n  // statusCode 204 refers to \"no error\"\n  // statusCode 304 refers to \"container already stopped\"\n  // Both are fine to avoid when stop container is called.\n  JSG_REQUIRE(response.statusCode == 204 || response.statusCode == 304, Error,\n      \"Stopping container failed with: \", response.body);\n}\n\nkj::Promise<void> ContainerClient::killContainer(uint32_t signal) {\n  auto endpoint = kj::str(\"/containers/\", containerName, \"/kill?signal=\", signalToString(signal));\n  auto response = co_await dockerApiRequest(\n      network, kj::str(dockerPath), kj::HttpMethod::POST, kj::mv(endpoint));\n  // statusCode 409 refers to \"container is not running\"\n  // We should not throw an error when the container is already not running.\n  JSG_REQUIRE(response.statusCode == 204 || response.statusCode == 409, Error,\n      \"Stopping container failed with: \", response.body);\n}\n\n// Destroys the container.\n// No-op when the container does not exist.\n// Wait for the container to actually be stopped and removed when it exists.\nkj::Promise<void> ContainerClient::destroyContainer() {\n  auto endpoint = kj::str(\"/containers/\", containerName, \"?force=true\");\n  auto response = co_await dockerApiRequest(\n      network, kj::str(dockerPath), kj::HttpMethod::DELETE, kj::mv(endpoint));\n  // statusCode 204 refers to \"no error\"\n  // statusCode 404 refers to \"no such container\"\n  // statusCode 409 refers to \"removal already in progress\" (race between concurrent destroys)\n  // All of which are fine for us since we're tearing down the container anyway.\n  JSG_REQUIRE(\n      response.statusCode == 204 || response.statusCode == 404 || response.statusCode == 409, Error,\n      \"Removing a container failed with: \", response.body);\n  // Do not send a wait request if container doesn't exist. This avoids sending an\n  // unnecessary request.\n  if (response.statusCode == 204 || response.statusCode == 409) {\n    response = co_await dockerApiRequest(network, kj::str(dockerPath), kj::HttpMethod::POST,\n        kj::str(\"/containers/\", containerName, \"/wait?condition=removed\"));\n    JSG_REQUIRE(response.statusCode == 200 || response.statusCode == 404, Error,\n        \"Waiting for container removal failed with: \", response.statusCode, response.body);\n  }\n}\n\n// Creates the sidecar container that owns the shared network namespace.\n// The application container joins this namespace and all ingress/egress goes through it.\nkj::Promise<void> ContainerClient::createSidecarContainer(\n    uint16_t egressPort, kj::String networkCidr) {\n  // Equivalent to: docker run --cap-add=NET_ADMIN -p <random-host>:39001 ...\n  capnp::JsonCodec codec;\n  codec.handleByAnnotation<docker_api::Docker::ContainerCreateRequest>();\n  capnp::MallocMessageBuilder message;\n  auto jsonRoot = message.initRoot<docker_api::Docker::ContainerCreateRequest>();\n  jsonRoot.setImage(containerEgressInterceptorImage);\n\n  auto ipv6Enabled = co_await isDaemonIpv6Enabled();\n\n  // determined by the number of flags we need to pass to proxy-everything\n  uint32_t cmdSize =\n      7;  // --http-egress-port <port> --http-ingress-address 0.0.0.0:<port> --docker-gateway-cidr <cidr> --dns-enabled\n  if (!ipv6Enabled) cmdSize += 1;  // --disable-ipv6\n\n  auto cmd = jsonRoot.initCmd(cmdSize);\n  uint32_t idx = 0;\n  cmd.set(idx++, \"--http-egress-port\");\n  cmd.set(idx++, kj::str(egressPort));\n  cmd.set(idx++, \"--http-ingress-address\");\n  cmd.set(idx++, kj::str(\"0.0.0.0:\", SIDECAR_INGRESS_PORT));\n  cmd.set(idx++, \"--docker-gateway-cidr\");\n  cmd.set(idx++, networkCidr);\n  cmd.set(idx++, \"--dns-enabled\");\n  if (!ipv6Enabled) {\n    cmd.set(idx++, \"--disable-ipv6\");\n  }\n\n  jsonRoot.initExposedPorts().setRaw(kj::str(\"{\\\"\", SIDECAR_INGRESS_PORT, \"/tcp\\\":{}}\"));\n\n  auto hostConfig = jsonRoot.initHostConfig();\n  hostConfig.setPublishAllPorts(true);\n  hostConfig.setNetworkMode(\"bridge\");\n\n  auto extraHosts = hostConfig.initExtraHosts(1);\n  extraHosts.set(0, \"host.docker.internal:host-gateway\"_kj);\n\n  // Sidecar needs NET_ADMIN capability for iptables/TPROXY\n  auto capAdd = hostConfig.initCapAdd(1);\n  capAdd.set(0, \"NET_ADMIN\");\n\n  auto response = co_await dockerApiRequest(network, kj::str(dockerPath), kj::HttpMethod::POST,\n      kj::str(\"/containers/create?name=\", sidecarContainerName), codec.encode(jsonRoot));\n\n  if (response.statusCode == 409) {\n    // Already created, nothing to do\n    co_return;\n  }\n\n  if (response.statusCode != 201) {\n    JSG_REQUIRE(response.statusCode != 404, Error, \"No such image available named \",\n        containerEgressInterceptorImage,\n        \". Please ensure the container egress interceptor image is built and available.\");\n    JSG_FAIL_REQUIRE(Error, \"Failed to create the networking sidecar [\", response.statusCode, \"] \",\n        response.body);\n  }\n}\n\nkj::Promise<void> ContainerClient::startSidecarContainer() {\n  auto endpoint = kj::str(\"/containers/\", sidecarContainerName, \"/start\");\n  auto response = co_await dockerApiRequest(\n      network, kj::str(dockerPath), kj::HttpMethod::POST, kj::mv(endpoint), kj::str(\"\"));\n  // statusCode 304 refers to \"container already started\"\n  // statusCode 204 refers to \"request succeeded\"\n  JSG_REQUIRE(response.statusCode == 204 || response.statusCode == 304, Error,\n      \"Starting network sidecar container failed with: \", response.statusCode, response.body);\n}\n\nkj::Promise<void> ContainerClient::destroySidecarContainer() {\n  auto endpoint = kj::str(\"/containers/\", sidecarContainerName, \"?force=true\");\n  auto responseDestroy = co_await dockerApiRequest(\n      network, kj::str(dockerPath), kj::HttpMethod::DELETE, kj::mv(endpoint));\n  // statusCode 204 refers to \"no error\"\n  // statusCode 404 refers to \"no such container\"\n  // statusCode 409 refers to \"removal already in progress\" (race between concurrent destroys)\n  // All of which are fine for us since we're tearing down the sidecar\n  JSG_REQUIRE(responseDestroy.statusCode == 204 || responseDestroy.statusCode == 404 ||\n          responseDestroy.statusCode == 409,\n      Error, \"Destroying network sidecar container failed with: \", responseDestroy.statusCode,\n      responseDestroy.body);\n  auto response = co_await dockerApiRequest(network, kj::str(dockerPath), kj::HttpMethod::POST,\n      kj::str(\"/containers/\", sidecarContainerName, \"/wait?condition=removed\"));\n  JSG_REQUIRE(response.statusCode == 200 || response.statusCode == 404, Error,\n      \"Destroying docker network sidecar container failed: \", response.statusCode, response.body);\n}\n\nContainerClient::RpcTurn ContainerClient::getRpcTurn() {\n  auto paf = kj::newPromiseAndFulfiller<void>();\n  auto prev = mutationQueue.addBranch();\n  mutationQueue = paf.promise.fork();\n  return {kj::mv(prev), kj::mv(paf.fulfiller)};\n}\n\nkj::Promise<void> ContainerClient::status(StatusContext context) {\n  // Wait for any pending cleanup from a previous ContainerClient (Docker DELETE).\n  // If the cleanup was already cancelled via containerCleanupCanceler the .catch_()\n  // in the destructor resolves it immediately, so this is a no-op in that case.\n  co_await pendingCleanup.addBranch();\n\n  auto [ready, done] = getRpcTurn();\n  co_await ready;\n  KJ_DEFER(done->fulfill());\n\n  const auto [isRunning] = co_await inspectContainer();\n  containerStarted.store(isRunning, std::memory_order_release);\n  containerSidecarStarted.store(false, std::memory_order_release);\n  this->sidecarIngressHostPort = kj::none;\n\n  if (isRunning) {\n    // If the sidecar container is already running (e.g. workerd restarted while\n    // containers stayed up), recover its published ingress port, then configure\n    // it to use our current egress listener port.\n    auto sidecar = KJ_REQUIRE_NONNULL(co_await inspectSidecar(),\n        \"Recovered running container without a running networking sidecar\");\n    containerSidecarStarted.store(true, std::memory_order_release);\n    this->sidecarIngressHostPort = sidecar.ingressHostPort;\n    co_await ensureEgressListenerStarted();\n    co_await updateSidecarEgressPort(sidecar.ingressHostPort, egressListenerPort);\n  }\n\n  context.getResults().setRunning(isRunning);\n}\n\nkj::Promise<void> ContainerClient::start(StartContext context) {\n  auto [ready, done] = getRpcTurn();\n  co_await ready;\n  KJ_DEFER(done->fulfill());\n\n  const auto params = context.getParams();\n\n  // Get the lists directly from Cap'n Proto\n  kj::Maybe<capnp::List<capnp::Text>::Reader> entrypoint = kj::none;\n  kj::Maybe<capnp::List<capnp::Text>::Reader> environment = kj::none;\n\n  if (params.hasEntrypoint()) {\n    entrypoint = params.getEntrypoint();\n  }\n\n  if (params.hasEnvironmentVariables()) {\n    environment = params.getEnvironmentVariables();\n  }\n\n  internetEnabled = params.getEnableInternet();\n\n  co_await ensureEgressListenerStarted();\n  containerSidecarStarted = false;\n  co_await ensureSidecarStarted();\n\n  co_await createContainer(entrypoint, environment, params);\n  co_await startContainer();\n\n  containerStarted.store(true, std::memory_order_release);\n}\n\nkj::Promise<void> ContainerClient::monitor(MonitorContext context) {\n  // Wait for any in-progress mutating RPCs (e.g. start()) to complete\n  // before issuing the Docker wait request.\n  co_await mutationQueue.addBranch();\n\n  auto results = context.getResults();\n  KJ_DEFER(containerStarted.store(false, std::memory_order_release));\n\n  auto endpoint = kj::str(\"/containers/\", containerName, \"/wait\");\n  auto response = co_await dockerApiRequest(\n      network, kj::str(dockerPath), kj::HttpMethod::POST, kj::mv(endpoint));\n\n  JSG_REQUIRE(response.statusCode == 200, Error,\n      \"Monitoring container failed with: \", response.statusCode, \" \", response.body);\n\n  auto message = decodeJsonResponse<docker_api::Docker::ContainerMonitorResponse>(response.body);\n  auto jsonRoot = message->getRoot<docker_api::Docker::ContainerMonitorResponse>();\n  results.setExitCode(jsonRoot.getStatusCode());\n}\n\nkj::Promise<void> ContainerClient::destroy(DestroyContext context) {\n  auto [ready, done] = getRpcTurn();\n  co_await ready;\n  KJ_DEFER(done->fulfill());\n\n  this->sidecarIngressHostPort = kj::none;\n  co_await destroyContainer();\n  co_await destroySidecarContainer();\n}\n\nkj::Promise<void> ContainerClient::signal(SignalContext context) {\n  auto [ready, done] = getRpcTurn();\n  co_await ready;\n  KJ_DEFER(done->fulfill());\n\n  const auto params = context.getParams();\n  co_await killContainer(params.getSigno());\n}\n\nkj::Promise<void> ContainerClient::setInactivityTimeout(SetInactivityTimeoutContext context) {\n  auto [ready, done] = getRpcTurn();\n  co_await ready;\n  KJ_DEFER(done->fulfill());\n\n  auto params = context.getParams();\n  auto durationMs = params.getDurationMs();\n\n  JSG_REQUIRE(\n      durationMs > 0, Error, \"setInactivityTimeout() requires durationMs > 0, got \", durationMs);\n\n  auto timeout = durationMs * kj::MILLISECONDS;\n\n  // Add a timer task that holds a reference to this ContainerClient.\n  waitUntilTasks.add(timer.afterDelay(timeout).then([self = kj::addRef(*this)]() {\n    // This callback does nothing but drop the reference\n  }));\n\n  co_return;\n}\n\nkj::Promise<void> ContainerClient::getTcpPort(GetTcpPortContext context) {\n  co_await mutationQueue.addBranch();\n\n  const auto params = context.getParams();\n  uint16_t port = params.getPort();\n  auto results = context.getResults();\n  auto dockerPort = kj::heap<DockerPort>(*this, kj::str(\"127.0.0.1\"), port);\n  results.setPort(kj::mv(dockerPort));\n  co_return;\n}\n\nkj::Promise<void> ContainerClient::listenTcp(ListenTcpContext context) {\n  KJ_UNIMPLEMENTED(\"listenTcp not implemented for Docker containers - use port mapping instead\");\n}\n\nvoid ContainerClient::upsertEgressMapping(EgressMapping mapping) {\n  for (auto& m: egressMappings) {\n    if (m.port != mapping.port) {\n      continue;\n    }\n\n    bool matches = false;\n    KJ_SWITCH_ONEOF(m.destination) {\n      KJ_CASE_ONEOF(existingCidr, kj::CidrRange) {\n        KJ_IF_SOME(newCidr, mapping.destination.tryGet<kj::CidrRange>()) {\n          matches = existingCidr.toString() == newCidr.toString();\n        }\n      }\n      KJ_CASE_ONEOF(existingHostnameGlob, kj::String) {\n        KJ_IF_SOME(newHostnameGlob, mapping.destination.tryGet<kj::String>()) {\n          matches = existingHostnameGlob == newHostnameGlob;\n        }\n      }\n    }\n\n    if (matches) {\n      m.channel = kj::mv(mapping.channel);\n      return;\n    }\n  }\n\n  egressMappings.add(kj::mv(mapping));\n}\n\nkj::Vector<kj::String> ContainerClient::getDnsAllowHostnames() const {\n  // result N can be at most size of egressMappings.\n  kj::Vector<kj::String> result;\n\n  for (auto& mapping: egressMappings) {\n    KJ_SWITCH_ONEOF(mapping.destination) {\n      KJ_CASE_ONEOF(_, kj::CidrRange) {\n        result.add(kj::str(\"*\"));\n        return result;\n      }\n      KJ_CASE_ONEOF(hostnameGlob, kj::String) {\n        bool alreadyPresent = false;\n        // Check if we have the hostnameGlob already present in the DNS allow\n        // list.\n        for (auto& existing: result) {\n          if (existing == hostnameGlob) {\n            alreadyPresent = true;\n            break;\n          }\n        }\n\n        if (!alreadyPresent) {\n          result.add(kj::str(hostnameGlob));\n        }\n      }\n    }\n  }\n\n  return result;\n}\n\nkj::Maybe<kj::Own<workerd::IoChannelFactory::SubrequestChannel>> ContainerClient::findEgressMapping(\n    kj::StringPtr destAddr, uint16_t defaultPort, kj::Maybe<kj::StringPtr> hostname) {\n  auto hostAndPort = stripPort(destAddr);\n  uint16_t port = hostAndPort.port.orDefault(defaultPort);\n  kj::Maybe<kj::String> normalizedHostname;\n  KJ_IF_SOME(hostnameValue, hostname) {\n    normalizedHostname = normalizeHostname(hostnameValue);\n  }\n\n  for (auto& mapping: egressMappings) {\n    // Mappings can differ in port, and cidr/hostname.\n    // Users can specify things like google.com:7070, or 0.0.0.0:7070\n    if (mapping.port != 0 && mapping.port != port) {\n      continue;\n    }\n\n    KJ_SWITCH_ONEOF(mapping.destination) {\n      KJ_CASE_ONEOF(cidr, kj::CidrRange) {\n        if (cidr.matches(hostAndPort.host)) {\n          return kj::addRef(*mapping.channel);\n        }\n      }\n      KJ_CASE_ONEOF(hostnameGlob, kj::String) {\n        KJ_IF_SOME(hostnameValue, normalizedHostname) {\n          if (hostnameGlobMatches(hostnameGlob, hostnameValue)) {\n            return kj::addRef(*mapping.channel);\n          }\n        }\n      }\n    }\n  }\n\n  return kj::none;\n}\n\nkj::Promise<void> ContainerClient::ensureSidecarStarted() {\n  if (containerSidecarStarted.exchange(true, std::memory_order_acquire)) {\n    co_return;\n  }\n\n  // We need to call destroy here, it's mandatory that this is a fresh sidecar\n  // start. Maybe we lost track of it on a previous workerd restart.\n  co_await destroySidecarContainer();\n\n  KJ_ON_SCOPE_FAILURE(containerSidecarStarted.store(false, std::memory_order_release));\n\n  auto ipamConfig = co_await getDockerBridgeIPAMConfig();\n  co_await createSidecarContainer(egressListenerPort, kj::mv(ipamConfig.subnet));\n  co_await startSidecarContainer();\n\n  auto sidecar = KJ_REQUIRE_NONNULL(co_await inspectSidecar(), \"started sidecar not running\");\n  this->sidecarIngressHostPort = sidecar.ingressHostPort;\n\n  // Wait for the sidecar's HTTP server to be ready by calling updateSidecarEgressConfig\n  // in a retry loop with a per-attempt timeout.\n  constexpr int MAX_READY_RETRIES = 10;\n  constexpr auto READY_RETRY_DELAY = 200 * kj::MILLISECONDS;\n  constexpr auto READY_ATTEMPT_TIMEOUT = 2 * kj::SECONDS;\n  for (int attempt = 0;; ++attempt) {\n    kj::Maybe<kj::Exception> maybeError;\n    try {\n      co_await timer.timeoutAfter(READY_ATTEMPT_TIMEOUT,\n          updateSidecarEgressConfig(sidecar.ingressHostPort, egressListenerPort));\n    } catch (...) {\n      maybeError = kj::getCaughtExceptionAsKj();\n    }\n\n    if (maybeError == kj::none) co_return;\n    if (attempt >= MAX_READY_RETRIES - 1)\n      kj::throwFatalException(kj::mv(KJ_REQUIRE_NONNULL(maybeError)));\n    co_await timer.afterDelay(READY_RETRY_DELAY);\n  }\n}\n\nkj::Promise<void> ContainerClient::ensureEgressListenerStarted(uint16_t port) {\n  if (egressListenerStarted.exchange(true, std::memory_order_acquire)) {\n    co_return;\n  }\n\n  KJ_ON_SCOPE_FAILURE(egressListenerStarted.store(false, std::memory_order_release));\n\n  // Determine the listen address: on Linux, use the Docker bridge gateway IP\n  // and fall back to loopback (Docker Desktop\n  // routes host-gateway to host loopback through the VM).\n  auto ipamConfig = co_await getDockerBridgeIPAMConfig();\n  egressListenerPort = co_await startEgressListener(\n      gatewayForPlatform(kj::mv(ipamConfig.gateway)).orDefault(kj::str(\"127.0.0.1\")), port);\n}\n\nkj::Promise<void> ContainerClient::setEgressHttp(SetEgressHttpContext context) {\n  auto [ready, done] = getRpcTurn();\n  co_await ready;\n  KJ_DEFER(done->fulfill());\n\n  auto params = context.getParams();\n  auto hostPortStr = kj::str(params.getHostPort());\n  auto tokenBytes = params.getChannelToken();\n\n  auto parsed = parseHostPort(hostPortStr);\n  uint16_t port = parsed.port.orDefault(80);\n\n  co_await ensureEgressListenerStarted();\n\n  if (containerStarted.load(std::memory_order_acquire)) {\n    // Only try to create and start a sidecar container\n    // if the user container is running.\n    co_await ensureSidecarStarted();\n  }\n\n  auto subrequestChannel = channelTokenHandler.decodeSubrequestChannelToken(\n      workerd::IoChannelFactory::ChannelTokenUsage::RPC, tokenBytes);\n\n  upsertEgressMapping(EgressMapping{\n    .destination = kj::mv(parsed.destination),\n    .port = port,\n    .channel = kj::mv(subrequestChannel),\n  });\n\n  KJ_IF_SOME(ingressHostPort, sidecarIngressHostPort) {\n    co_await updateSidecarEgressConfig(ingressHostPort, egressListenerPort);\n  }\n\n  co_return;\n}\n\nkj::Own<ContainerClient> ContainerClient::addRef() {\n  return kj::addRef(*this);\n}\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/container-client.h",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/io/container.capnp.h>\n#include <workerd/io/io-channels.h>\n#include <workerd/server/channel-token.h>\n#include <workerd/server/docker-api.capnp.h>\n\n#include <capnp/compat/byte-stream.h>\n#include <capnp/compat/json.h>\n#include <capnp/list.h>\n#include <capnp/message.h>\n#include <kj/async-io.h>\n#include <kj/async.h>\n#include <kj/cidr.h>\n#include <kj/compat/http.h>\n#include <kj/map.h>\n#include <kj/refcount.h>\n#include <kj/string.h>\n\n#include <atomic>\n\nnamespace workerd::server {\n\n// Decode a JSON string into a Cap'n Proto message of type T. The MallocMessageBuilder is\n// heap-allocated and returned as an owned pointer so that the decoded data outlives this\n// call. Callers must keep the returned message alive while accessing the root via\n// message->getRoot<T>(). A previous version allocated the builder on the stack and returned\n// a Builder (which is just a pointer into the message's arena); that caused every caller to\n// dereference freed memory after the function returned.\ntemplate <typename T>\nkj::Own<capnp::MallocMessageBuilder> decodeJsonResponse(kj::StringPtr response) {\n  auto message = kj::heap<capnp::MallocMessageBuilder>();\n  capnp::JsonCodec codec;\n  codec.handleByAnnotation<T>();\n  auto jsonRoot = message->initRoot<T>();\n  codec.decode(response, jsonRoot);\n  return message;\n}\n\n// Docker-based implementation that implements the rpc::Container::Server interface\n// so it can be used as a rpc::Container::Client via kj::heap<ContainerClient>().\n// This allows the Container JSG class to use Docker directly without knowing\n// it's talking to Docker instead of a real RPC service.\n//\n// ContainerClient is reference-counted to support actor reconnection with inactivity timeouts.\n// When setInactivityTimeout() is called, a timer holds a reference to prevent premature\n// destruction. The ContainerClient can be shared across multiple actor lifetimes\nclass ContainerClient final: public rpc::Container::Server, public kj::Refcounted {\n public:\n  ContainerClient(capnp::ByteStreamFactory& byteStreamFactory,\n      kj::Timer& timer,\n      kj::Network& network,\n      kj::String dockerPath,\n      kj::String containerName,\n      kj::String imageName,\n      kj::String containerEgressInterceptorImage,\n      kj::TaskSet& waitUntilTasks,\n      kj::Promise<void> pendingCleanup,\n      kj::Function<void(kj::Promise<void>)> cleanupCallback,\n      ChannelTokenHandler& channelTokenHandler);\n\n  ~ContainerClient() noexcept(false);\n\n  // Implement rpc::Container::Server interface\n  kj::Promise<void> status(StatusContext context) override;\n  kj::Promise<void> start(StartContext context) override;\n  kj::Promise<void> monitor(MonitorContext context) override;\n  kj::Promise<void> destroy(DestroyContext context) override;\n  kj::Promise<void> signal(SignalContext context) override;\n  kj::Promise<void> getTcpPort(GetTcpPortContext context) override;\n  kj::Promise<void> listenTcp(ListenTcpContext context) override;\n  kj::Promise<void> setInactivityTimeout(SetInactivityTimeoutContext context) override;\n  kj::Promise<void> setEgressHttp(SetEgressHttpContext context) override;\n\n  kj::Own<ContainerClient> addRef();\n\n private:\n  capnp::ByteStreamFactory& byteStreamFactory;\n  kj::HttpHeaderTable headerTable;\n  kj::Timer& timer;\n  kj::Network& network;\n  kj::String dockerPath;\n  kj::String containerName;\n  kj::String sidecarContainerName;\n  kj::String imageName;\n\n  // Container egress interceptor image name (sidecar for egress proxy)\n  kj::String containerEgressInterceptorImage;\n\n  kj::TaskSet& waitUntilTasks;\n\n  // Forked promise representing pending cleanup from a previous ContainerClient for the same\n  // container ID. status() co_awaits a branch so that Docker inspect only runs after any\n  // in-flight DELETE from the previous client has settled (either completed or been cancelled\n  // via containerCleanupCanceler, in which case the .catch_() resolves it immediately).\n  kj::ForkedPromise<void> pendingCleanup;\n\n  static constexpr kj::StringPtr defaultEnv[] = {\"CLOUDFLARE_COUNTRY_A2=XX\"_kj,\n    \"CLOUDFLARE_DEPLOYMENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"_kj,\n    \"CLOUDFLARE_LOCATION=loc01\"_kj, \"CLOUDFLARE_REGION=REGN\"_kj,\n    \"CLOUDFLARE_APPLICATION_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"_kj,\n    \"CLOUDFLARE_DURABLE_OBJECT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"_kj};\n\n  // Docker-specific Port implementation\n  class DockerPort;\n\n  // EgressHttpService handles CONNECT requests from proxy-anything sidecar\n  friend class EgressHttpService;\n\n  struct Response {\n    kj::uint statusCode;\n    kj::String body;\n  };\n\n  struct InspectResponse {\n    bool isRunning;\n  };\n\n  struct IPAMConfigResult {\n    kj::String gateway;\n    kj::String subnet;\n  };\n\n  struct SidecarInspectResponse {\n    uint16_t ingressHostPort;\n  };\n\n  // Docker API v1.50 helper methods\n  static kj::Promise<Response> dockerApiRequest(kj::Network& network,\n      kj::String dockerPath,\n      kj::HttpMethod method,\n      kj::String endpoint,\n      kj::Maybe<kj::String> body = kj::none);\n  kj::Promise<InspectResponse> inspectContainer();\n\n  kj::Promise<void> updateSidecarEgressPort(uint16_t ingressHostPort, uint16_t egressPort);\n  kj::Promise<void> updateSidecarEgressConfig(uint16_t ingressHostPort, uint16_t egressPort);\n  kj::Promise<void> createContainer(kj::Maybe<capnp::List<capnp::Text>::Reader> entrypoint,\n      kj::Maybe<capnp::List<capnp::Text>::Reader> environment,\n      rpc::Container::StartParams::Reader params);\n  kj::Promise<void> startContainer();\n  kj::Promise<void> stopContainer();\n  kj::Promise<void> killContainer(uint32_t signal);\n  kj::Promise<void> destroyContainer();\n\n  // Sidecar container management (for egress proxy)\n  // Inspect the sidecar container to retrieve the port to ingress to\n  kj::Promise<kj::Maybe<SidecarInspectResponse>> inspectSidecar();\n  kj::Promise<void> createSidecarContainer(uint16_t egressPort, kj::String networkCidr);\n  kj::Promise<void> startSidecarContainer();\n  kj::Promise<void> destroySidecarContainer();\n  kj::Promise<void> monitorSidecarContainer();\n\n  // Cleanup callback invoked from the destructor. Receives the joined cleanup promise so\n  // ActorNamespace can wrap it with the canceler, store it for the next ContainerClient\n  // to await, and add a branch to waitUntilTasks to keep the cleanup tasks alive.\n  kj::Function<void(kj::Promise<void>)> cleanupCallback;\n\n  // For redeeming channel tokens received via setEgressHttp\n  ChannelTokenHandler& channelTokenHandler;\n\n  // Represents a parsed egress mapping. IP/CIDR mappings match all hostnames,\n  // while hostnameGlob mappings only match requests carrying a matching hostname.\n  struct EgressMapping {\n    kj::OneOf<kj::CidrRange, kj::String> destination;\n    uint16_t port;  // 0 means match all ports\n    kj::Own<workerd::IoChannelFactory::SubrequestChannel> channel;\n  };\n\n  kj::Vector<EgressMapping> egressMappings;\n\n  // Insert or replace an egress mapping. If a mapping with the same destination and port\n  // already exists, its channel is replaced; otherwise a new mapping is added.\n  void upsertEgressMapping(EgressMapping mapping);\n  kj::Vector<kj::String> getDnsAllowHostnames() const;\n\n  // Find a matching egress mapping for the given destination address (host:port format).\n  // Returns an addRef'd Own so the channel stays alive even if the mapping is later replaced.\n  kj::Maybe<kj::Own<workerd::IoChannelFactory::SubrequestChannel>> findEgressMapping(\n      kj::StringPtr destAddr, uint16_t defaultPort, kj::Maybe<kj::StringPtr> hostname);\n\n  // Whether general internet access is enabled for this container, when known.\n  kj::Maybe<bool> internetEnabled = kj::none;\n\n  std::atomic_bool containerStarted = false;\n  std::atomic_bool containerSidecarStarted = false;\n  std::atomic_bool egressListenerStarted = false;\n\n  kj::Maybe<kj::Own<kj::HttpServer>> egressHttpServer;\n  kj::Maybe<kj::Promise<void>> egressListenerTask;\n\n  uint16_t egressListenerPort = 0;\n  kj::Maybe<uint16_t> sidecarIngressHostPort;\n\n  // All mutating RPCs need to ask and wait on an RpcTurn before doing any mutations.\n  // monitor() is an exception. It waits for all pending mutating RPCs without joining\n  // the queue itself.\n  kj::ForkedPromise<void> mutationQueue = kj::Promise<void>(kj::READY_NOW).fork();\n\n  struct RpcTurn {\n    kj::Promise<void> ready;\n    kj::Own<kj::PromiseFulfiller<void>> done;\n  };\n  // Get a turn to run mutating RPC.\n  // Callers will receive a RpcTurn where they can wait and then resolve\n  // when they finish through a KJ defer.\n  RpcTurn getRpcTurn();\n\n  // Get the Docker bridge network gateway IP and subnet.\n  kj::Promise<IPAMConfigResult> getDockerBridgeIPAMConfig();\n  // Check if the Docker daemon has IPv6 enabled by inspecting the default bridge network's\n  // IPAM config for IPv6 subnets.\n  kj::Promise<bool> isDaemonIpv6Enabled();\n  // Start the egress listener on the given address. If port is 0, an OS-chosen port is used.\n  kj::Promise<uint16_t> startEgressListener(kj::String listenAddress, uint16_t port = 0);\n  void stopEgressListener();\n  // Ensure the egress listener is started exactly once.\n  // Uses egressListenerStarted as a guard. Called from setEgressHttp() and status().\n  // If port is non-zero, binds to that specific port (for reconnecting to an existing sidecar).\n  kj::Promise<void> ensureEgressListenerStarted(uint16_t port = 0);\n  // Ensure the egress listener and sidecar container are started exactly once.\n  // Uses containerSidecarStarted as a guard. Called from both start() and setEgressHttp().\n  kj::Promise<void> ensureSidecarStarted();\n};\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/docker-api.capnp",
    "content": "# Copyright (c) 2017-2022 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n@0xfafd0bdf57acc528;\nusing Cxx = import \"/capnp/c++.capnp\";\nusing Json = import \"/capnp/compat/json.capnp\";\n\n$Cxx.namespace(\"workerd::docker_api\");\n$Cxx.allowCancellation;\n\nstruct Docker {\n  # Docker API structures for container operations\n  struct LogConfig {\n    type @0 :Text $Json.name(\"Type\");\n    config @1 :Json.Value $Json.name(\"Config\");\n  }\n\n  struct RestartPolicy {\n    name @0 :Text $Json.name(\"Name\"); # \"no\", \"always\", \"unless-stopped\", \"on-failure\"\n    maximumRetryCount @1 :UInt32 $Json.name(\"MaximumRetryCount\");\n  }\n\n  struct WeightDevice {\n    path @0 :Text;\n    weight @1 :UInt16;\n  }\n\n  struct ThrottleDevice {\n    path @0 :Text;\n    rate @1 :UInt64;\n  }\n\n  struct DeviceMapping {\n    pathOnHost @0 :Text;\n    pathInContainer @1 :Text;\n    cgroupPermissions @2 :Text;\n  }\n\n  struct DeviceRequest {\n    driver @0 :Text;\n    count @1 :Int32;\n    deviceIds @2 :List(Text);\n    capabilities @3 :List(List(Text));\n    options @4 :Json.Value;\n  }\n\n  struct Ulimit {\n    name @0 :Text;\n    soft @1 :UInt64;\n    hard @2 :UInt64;\n  }\n\n  struct IPAMConfig {\n    ipv4Address @0 :Text $Json.name(\"IPv4Address\");\n    ipv6Address @1 :Text $Json.name(\"IPv6Address\");\n    linkLocalIps @2 :List(Text) $Json.name(\"LinkLocalIPs\");\n  }\n\n  struct EndpointSettings {\n    ipamConfig @0 :IPAMConfig $Json.name(\"IPAMConfig\");\n    links @1 :List(Text) $Json.name(\"Links\");\n    aliases @2 :List(Text) $Json.name(\"Aliases\");\n    networkId @3 :Text $Json.name(\"NetworkID\");\n    endpointId @4 :Text $Json.name(\"EndpointID\");\n    gateway @5 :Text $Json.name(\"Gateway\");\n    ipAddress @6 :Text $Json.name(\"IPAddress\");\n    ipPrefixLen @7 :UInt32 $Json.name(\"IPPrefixLen\");\n    ipv6Gateway @8 :Text $Json.name(\"IPv6Gateway\");\n    globalIpv6Address @9 :Text $Json.name(\"GlobalIPv6Address\");\n    globalIpv6PrefixLen @10 :UInt32 $Json.name(\"GlobalIPv6PrefixLen\");\n    macAddress @11 :Text $Json.name(\"MacAddress\");\n    driverOpts @12 :Json.Value $Json.name(\"DriverOpts\");\n  }\n\n\n  struct ContainerCreateRequest {\n    # Request body for ContainerCreate operation - flattened structure\n    # Container configuration fields\n    hostname @0 :Text $Json.name(\"Hostname\");\n    domainname @1 :Text $Json.name(\"Domainname\");\n    user @2 :Text $Json.name(\"User\");\n    attachStdin @3 :Bool = false $Json.name(\"AttachStdin\");\n    attachStdout @4 :Bool = false $Json.name(\"AttachStdout\");\n    attachStderr @5 :Bool = false $Json.name(\"AttachStderr\");\n    exposedPorts @6 :Json.Value $Json.name(\"ExposedPorts\"); # Ports mapped to empty objects\n    tty @7 :Bool $Json.name(\"Tty\");\n    openStdin @8 :Bool $Json.name(\"OpenStdin\");\n    stdinOnce @9 :Bool $Json.name(\"StdinOnce\");\n    env @10 :List(Text) $Json.name(\"Env\"); # Environment variables in \"KEY=value\" format\n    cmd @11 :List(Text) $Json.name(\"Cmd\"); # Command to run\n    entrypoint @12 :Text $Json.name(\"Entrypoint\"); # Can be string or array - simplified as Text\n    image @13 :Text $Json.name(\"Image\"); # Image name/reference\n    labels @14 :Json.Value $Json.name(\"Labels\"); # Labels as key-value pairs\n    volumes @15 :Json.Value $Json.name(\"Volumes\"); # Volume mount points mapped to empty objects\n    workingDir @16 :Text $Json.name(\"WorkingDir\");\n    networkDisabled @17 :Bool $Json.name(\"NetworkDisabled\");\n    macAddress @18 :Text $Json.name(\"MacAddress\");\n    stopSignal @19 :Text $Json.name(\"StopSignal\");\n    stopTimeout @20 :UInt32 = 10 $Json.name(\"StopTimeout\");\n\n    # Host configuration\n    hostConfig @21 :HostConfig $Json.name(\"HostConfig\");\n\n    # Networking configuration\n    # networkingConfig @22 :NetworkingConfig $Json.name(\"NetworkingConfig\");\n\n    struct HostConfig {\n      # Container configuration that depends on the host\n      binds @0 :List(Text) $Json.name(\"Binds\"); # Volume bindings\n      links @1 :List(Text) $Json.name(\"Links\");\n      memory @2 :UInt32 $Json.name(\"Memory\");\n      memorySwap @3 :UInt32 $Json.name(\"MemorySwap\");\n      memoryReservation @4 :UInt32 $Json.name(\"MemoryReservation\");\n      kernelMemory @5 :UInt32 $Json.name(\"KernelMemory\");\n      nanoCpus @6 :UInt32 $Json.name(\"NanoCpus\");\n      cpuPercent @7 :UInt32 $Json.name(\"CpuPercent\");\n      cpuShares @8 :UInt32 $Json.name(\"CpuShares\");\n      cpuPeriod @9 :UInt32 $Json.name(\"CpuPeriod\");\n      cpuRealtimePeriod @10 :UInt32 $Json.name(\"CpuRealtimePeriod\");\n      cpuRealtimeRuntime @11 :UInt32 $Json.name(\"CpuRealtimeRuntime\");\n      cpuQuota @12 :UInt32 $Json.name(\"CpuQuota\");\n      cpusetCpus @13 :Text $Json.name(\"CpusetCpus\");\n      cpusetMems @14 :Text $Json.name(\"CpusetMems\");\n      maximumIOps @15 :UInt32 $Json.name(\"MaximumIOps\");\n      maximumIOBps @16 :UInt32 $Json.name(\"MaximumIOBps\");\n      blkioWeight @17 :UInt16 $Json.name(\"BlkioWeight\");\n      blkioWeightDevice @18 :List(WeightDevice) $Json.name(\"BlkioWeightDevice\");\n      blkioDeviceReadBps @19 :List(ThrottleDevice) $Json.name(\"BlkioDeviceReadBps\");\n      blkioDeviceReadIOps @20 :List(ThrottleDevice) $Json.name(\"BlkioDeviceReadIOps\");\n      blkioDeviceWriteBps @21 :List(ThrottleDevice) $Json.name(\"BlkioDeviceWriteBps\");\n      blkioDeviceWriteIOps @22 :List(ThrottleDevice) $Json.name(\"BlkioDeviceWriteIOps\");\n      memorySwappiness @23 :UInt32 $Json.name(\"MemorySwappiness\");\n      oomKillDisable @24 :Bool $Json.name(\"OomKillDisable\");\n      oomScoreAdj @25 :Int32 $Json.name(\"OomScoreAdj\");\n      pidMode @26 :Text $Json.name(\"PidMode\");\n      pidsLimit @27 :Int32 $Json.name(\"PidsLimit\"); # Can be -1\n      portBindings @28 :Json.Value $Json.name(\"PortBindings\"); # Port bindings map\n      publishAllPorts @29 :Bool $Json.name(\"PublishAllPorts\");\n      privileged @30 :Bool $Json.name(\"Privileged\");\n      readonlyRootfs @31 :Bool $Json.name(\"ReadonlyRootfs\");\n      dns @32 :List(Text) $Json.name(\"Dns\");\n      dnsOptions @33 :List(Text) $Json.name(\"DnsOptions\");\n      dnsSearch @34 :List(Text) $Json.name(\"DnsSearch\");\n      volumesFrom @35 :List(Text) $Json.name(\"VolumesFrom\");\n      capAdd @36 :List(Text) $Json.name(\"CapAdd\");\n      capDrop @37 :List(Text) $Json.name(\"CapDrop\");\n      groupAdd @38 :List(Text) $Json.name(\"GroupAdd\");\n      restartPolicy @39 :RestartPolicy $Json.name(\"RestartPolicy\");\n      autoRemove @40 :Bool $Json.name(\"AutoRemove\");\n      networkMode @41 :Text $Json.name(\"NetworkMode\");\n      devices @42 :List(DeviceMapping) $Json.name(\"Devices\");\n      ulimits @43 :List(Ulimit) $Json.name(\"Ulimits\");\n      logConfig @44 :LogConfig $Json.name(\"LogConfig\");\n      securityOpt @45 :List(Text) $Json.name(\"SecurityOpt\");\n      storageOpt @46 :Json.Value $Json.name(\"StorageOpt\");\n      cgroupParent @47 :Text $Json.name(\"CgroupParent\");\n      volumeDriver @48 :Text $Json.name(\"VolumeDriver\");\n      shmSize @49 :UInt32 $Json.name(\"ShmSize\");\n      extraHosts @50 :List(Text) $Json.name(\"ExtraHosts\"); # --add-host entries in \"host:ip\" format\n\n    }\n  }\n\n  struct ContainerCreateResponse {\n    # Response from ContainerCreate operation\n    id @0 :Text $Json.name(\"Id\"); # The ID of the created container\n    warnings @1 :List(Text) $Json.name(\"Warnings\"); # Warnings encountered when creating the container\n  }\n\n  struct ContainerMonitorResponse {\n    statusCode @0 :Int32 $Json.name(\"StatusCode\");\n  }\n\n  struct ContainerState {\n    # Container's running state\n    status @0 :Text $Json.name(\"Status\"); # \"created\", \"running\", \"paused\", \"restarting\", \"removing\", \"exited\", \"dead\"\n    running @1 :Bool $Json.name(\"Running\");\n    paused @2 :Bool $Json.name(\"Paused\");\n    restarting @3 :Bool $Json.name(\"Restarting\");\n    oomKilled @4 :Bool $Json.name(\"OOMKilled\");\n    dead @5 :Bool $Json.name(\"Dead\");\n    pid @6 :UInt32 $Json.name(\"Pid\");\n    exitCode @7 :Int32 $Json.name(\"ExitCode\");\n    error @8 :Text $Json.name(\"Error\");\n    startedAt @9 :Text $Json.name(\"StartedAt\");\n    finishedAt @10 :Text $Json.name(\"FinishedAt\");\n  }\n\n  struct GraphDriverData {\n    # Information about the storage driver\n    name @0 :Text $Json.name(\"Name\");\n    data @1 :Json.Value $Json.name(\"Data\");\n  }\n\n  struct MountPoint {\n    # Mount point configuration inside the container\n    type @0 :Text $Json.name(\"Type\"); # \"bind\", \"volume\", \"tmpfs\", \"npipe\"\n    name @1 :Text $Json.name(\"Name\");\n    source @2 :Text $Json.name(\"Source\");\n    destination @3 :Text $Json.name(\"Destination\");\n    driver @4 :Text $Json.name(\"Driver\");\n    mode @5 :Text $Json.name(\"Mode\");\n    rw @6 :Bool $Json.name(\"RW\");\n    propagation @7 :Text $Json.name(\"Propagation\");\n  }\n\n  struct NetworkSettings {\n    # Network settings for the container\n    bridge @0 :Text $Json.name(\"Bridge\");\n    sandboxId @1 :Text $Json.name(\"SandboxID\");\n    hairpinMode @2 :Bool $Json.name(\"HairpinMode\");\n    linkLocalIpv6Address @3 :Text $Json.name(\"LinkLocalIPv6Address\");\n    linkLocalIpv6PrefixLen @4 :UInt32 $Json.name(\"LinkLocalIPv6PrefixLen\");\n    sandboxKey @5 :Text $Json.name(\"SandboxKey\");\n    endpointId @6 :Text $Json.name(\"EndpointID\");\n    gateway @7 :Text $Json.name(\"Gateway\");\n    globalIpv6Address @8 :Text $Json.name(\"GlobalIPv6Address\");\n    globalIpv6PrefixLen @9 :UInt32 $Json.name(\"GlobalIPv6PrefixLen\");\n    ipAddress @10 :Text $Json.name(\"IPAddress\");\n    ipPrefixLen @11 :UInt32 $Json.name(\"IPPrefixLen\");\n    ipv6Gateway @12 :Text $Json.name(\"IPv6Gateway\");\n    macAddress @13 :Text $Json.name(\"MacAddress\");\n    networks @14 :Json.Value $Json.name(\"Networks\");\n    ports @15 :Json.Value $Json.name(\"Ports\");\n  }\n\n  struct ContainerConfig {\n    # Container configuration (reusable from create)\n    hostname @0 :Text $Json.name(\"Hostname\");\n    domainname @1 :Text $Json.name(\"Domainname\");\n    user @2 :Text $Json.name(\"User\");\n    attachStdin @3 :Bool $Json.name(\"AttachStdin\");\n    attachStdout @4 :Bool $Json.name(\"AttachStdout\");\n    attachStderr @5 :Bool $Json.name(\"AttachStderr\");\n    exposedPorts @6 :Json.Value $Json.name(\"ExposedPorts\");\n    tty @7 :Bool $Json.name(\"Tty\");\n    openStdin @8 :Bool $Json.name(\"OpenStdin\");\n    stdinOnce @9 :Bool $Json.name(\"StdinOnce\");\n    env @10 :List(Text) $Json.name(\"Env\");\n    cmd @11 :List(Text) $Json.name(\"Cmd\");\n    image @12 :Text $Json.name(\"Image\");\n    volumes @13 :Json.Value $Json.name(\"Volumes\");\n    workingDir @14 :Text $Json.name(\"WorkingDir\");\n    entrypoint @15 :List(Text) $Json.name(\"Entrypoint\");\n    networkDisabled @16 :Bool $Json.name(\"NetworkDisabled\");\n    macAddress @17 :Text $Json.name(\"MacAddress\");\n    onBuild @18 :List(Text) $Json.name(\"OnBuild\");\n    labels @19 :Json.Value $Json.name(\"Labels\");\n    stopSignal @20 :Text $Json.name(\"StopSignal\");\n    stopTimeout @21 :UInt32 $Json.name(\"StopTimeout\");\n    shell @22 :List(Text) $Json.name(\"Shell\");\n  }\n\n  struct ContainerInspectResponse {\n    # Response from ContainerInspect operation\n    id @0 :Text $Json.name(\"Id\");\n    created @1 :Text $Json.name(\"Created\");\n    path @2 :Text $Json.name(\"Path\");\n    args @3 :List(Text) $Json.name(\"Args\");\n    state @4 :ContainerState $Json.name(\"State\");\n    networkSettings @5 :NetworkSettings $Json.name(\"NetworkSettings\");\n  }\n\n  struct Command {\n    struct ContainerCreate {\n      struct Params {\n        name @0 :Text; # Optional container name\n        body @1 :ContainerCreateRequest;\n      }\n      struct Result {\n        response @0 :ContainerCreateResponse;\n      }\n    }\n\n    struct ContainerInspect {\n      struct Params {\n        id @0 :Text; # Container ID or name\n      }\n      struct Result {\n        response @0 :ContainerInspectResponse;\n      }\n    }\n  }\n\n  # Network inspection response (GET /networks/{id})\n  struct NetworkInspectResponse {\n    name @0 :Text $Json.name(\"Name\");\n    id @1 :Text $Json.name(\"Id\");\n    ipam @2 :IPAM $Json.name(\"IPAM\");\n\n    struct IPAM {\n      driver @0 :Text $Json.name(\"Driver\");\n      config @1 :List(IPAMConfig) $Json.name(\"Config\");\n\n      struct IPAMConfig {\n        subnet @0 :Text $Json.name(\"Subnet\");\n        gateway @1 :Text $Json.name(\"Gateway\");\n      }\n    }\n  }\n\n  # Network create request (POST /networks/create)\n  # Equivalent to: docker network create -d bridge --ipv6 workerd-network\n  struct NetworkCreateRequest {\n    name @0 :Text $Json.name(\"Name\");\n    driver @1 :Text $Json.name(\"Driver\");  # \"bridge\", \"overlay\", etc.\n    enableIpv6 @2 :Bool $Json.name(\"EnableIPv6\");\n  }\n\n  # Network create response\n  struct NetworkCreateResponse {\n    id @0 :Text $Json.name(\"Id\");\n    warning @1 :Text $Json.name(\"Warning\");\n  }\n}\n\nstruct ProxyEverything {\n  struct Dns {\n    allowHostnames @0 :List(Text) $Json.name(\"allowHostnames\");\n  }\n\n  struct Internet {\n    enabled @0 :Bool $Json.name(\"enabled\");\n  }\n\n  struct Port {\n    port @0 :UInt16 $Json.name(\"port\");\n    dns @1 :Dns $Json.name(\"dns\");\n    internet @2 :Internet $Json.name(\"internet\");\n  }\n}\n"
  },
  {
    "path": "src/workerd/server/facet-tree-index-test.c++",
    "content": "#include \"facet-tree-index.h\"\n\n#include <kj/io.h>\n#include <kj/memory.h>\n#include <kj/test.h>\n#include <kj/time.h>\n\nnamespace workerd::server {\nnamespace {\n\nusing kj::byte;\nusing kj::uint;\n\nstruct ExpectedChildInfo {\n  uint id;\n  kj::StringPtr name;\n};\nvoid expectChildren(\n    FacetTreeIndex& index, uint parentId, kj::ArrayPtr<const ExpectedChildInfo> expected) {\n  index.forEachChild(parentId, [&](uint id, kj::StringPtr name) {\n    if (expected.size() == 0) {\n      KJ_FAIL_EXPECT(\"unexpected child\", id, name);\n    } else {\n      KJ_EXPECT(id == expected.front().id);\n      KJ_EXPECT(name == expected.front().name);\n      expected = expected.slice(1);\n    }\n  });\n  KJ_EXPECT(expected.size() == 0, \"missing child\", expected.front().id, expected.front().name);\n}\n\nKJ_TEST(\"FacetTreeIndex basic functionality\") {\n  auto file = kj::newInMemoryFile(kj::nullClock());\n\n  {\n    // Test with new empty file\n    FacetTreeIndex index(file->clone());\n\n    // Get IDs for facets\n    uint id1 = index.getId(0, \"facet1\");\n    uint id2 = index.getId(0, \"facet2\");\n    uint id3 = index.getId(id1, \"child1\");\n    uint id4 = index.getId(id1, \"child2\");\n    uint id5 = index.getId(id2, \"child1\");\n\n    // Check that IDs are assigned correctly\n    KJ_EXPECT(id1 == 1);\n    KJ_EXPECT(id2 == 2);\n    KJ_EXPECT(id3 == 3);\n    KJ_EXPECT(id4 == 4);\n    KJ_EXPECT(id5 == 5);\n\n    // Check that IDs are stable\n    KJ_EXPECT(index.getId(0, \"facet1\") == id1);\n    KJ_EXPECT(index.getId(0, \"facet2\") == id2);\n    KJ_EXPECT(index.getId(id1, \"child1\") == id3);\n    KJ_EXPECT(index.getId(id1, \"child2\") == id4);\n    KJ_EXPECT(index.getId(id2, \"child1\") == id5);\n\n    // Test forEachChild().\n    expectChildren(index, 0, {{1, \"facet1\"}, {2, \"facet2\"}});\n    expectChildren(index, 1, {{3, \"child1\"}, {4, \"child2\"}});\n    expectChildren(index, 2, {{5, \"child1\"}});\n    expectChildren(index, 3, {});\n    expectChildren(index, 4, {});\n    expectChildren(index, 5, {});\n  }\n\n  {\n    // Test with existing file (persistence)\n    FacetTreeIndex index(file->clone());\n\n    // Check that IDs are the same as before\n    KJ_EXPECT(index.getId(0, \"facet1\") == 1);\n    KJ_EXPECT(index.getId(0, \"facet2\") == 2);\n    KJ_EXPECT(index.getId(1, \"child1\") == 3);\n    KJ_EXPECT(index.getId(1, \"child2\") == 4);\n    KJ_EXPECT(index.getId(2, \"child1\") == 5);\n\n    // Add some new facets\n    uint id6 = index.getId(3, \"grandchild1\");\n    uint id7 = index.getId(3, \"grandchild2\");\n\n    KJ_EXPECT(id6 == 6);\n    KJ_EXPECT(id7 == 7);\n\n    expectChildren(index, 0, {{1, \"facet1\"}, {2, \"facet2\"}});\n    expectChildren(index, 1, {{3, \"child1\"}, {4, \"child2\"}});\n    expectChildren(index, 2, {{5, \"child1\"}});\n    expectChildren(index, 3, {{6, \"grandchild1\"}, {7, \"grandchild2\"}});\n    expectChildren(index, 4, {});\n    expectChildren(index, 5, {});\n    expectChildren(index, 6, {});\n    expectChildren(index, 7, {});\n  }\n\n  {\n    // Test again with existing file\n    FacetTreeIndex index(file->clone());\n\n    // Check all IDs were preserved\n    KJ_EXPECT(index.getId(0, \"facet1\") == 1);\n    KJ_EXPECT(index.getId(0, \"facet2\") == 2);\n    KJ_EXPECT(index.getId(1, \"child1\") == 3);\n    KJ_EXPECT(index.getId(1, \"child2\") == 4);\n    KJ_EXPECT(index.getId(2, \"child1\") == 5);\n    KJ_EXPECT(index.getId(3, \"grandchild1\") == 6);\n    KJ_EXPECT(index.getId(3, \"grandchild2\") == 7);\n\n    expectChildren(index, 0, {{1, \"facet1\"}, {2, \"facet2\"}});\n    expectChildren(index, 1, {{3, \"child1\"}, {4, \"child2\"}});\n    expectChildren(index, 2, {{5, \"child1\"}});\n    expectChildren(index, 3, {{6, \"grandchild1\"}, {7, \"grandchild2\"}});\n    expectChildren(index, 4, {});\n    expectChildren(index, 5, {});\n    expectChildren(index, 6, {});\n    expectChildren(index, 7, {});\n  }\n}\n\nKJ_TEST(\"FacetTreeIndex error handling\") {\n  auto file = kj::newInMemoryFile(kj::nullClock());\n  FacetTreeIndex index(file->clone());\n\n  // Add some initial facets\n  index.getId(0, \"facet1\");\n  index.getId(0, \"facet2\");\n\n  // Test error cases\n\n  // Empty name\n  KJ_EXPECT_THROW_MESSAGE(\"Facet name cannot be empty\", index.getId(0, \"\"));\n\n  // Invalid parent\n  KJ_EXPECT_THROW_MESSAGE(\"Invalid parent ID\", index.getId(999, \"child\"));\n\n  // Same name but different parents should get different IDs\n  uint id1 = index.getId(1, \"sameName\");\n  uint id2 = index.getId(2, \"sameName\");\n  KJ_EXPECT(id1 != id2);\n\n  // Test name uniqueness per parent\n  uint id3 = index.getId(1, \"sameName\");\n  KJ_EXPECT(id3 == id1);\n}\n\nKJ_TEST(\"FacetTreeIndex corruption handling\") {\n  auto file = kj::newInMemoryFile(kj::nullClock());\n\n  // Create a file with corrupted data\n  {\n    // Write valid header and some valid entries\n    constexpr uint64_t MAGIC_NUMBER = 0xc4cdce5bc5b0ef57;\n    file->write(0,\n        kj::ArrayPtr<const byte>(\n            reinterpret_cast<const byte*>(&MAGIC_NUMBER), sizeof(MAGIC_NUMBER)));\n\n    // Write valid entry: parent=0, name=\"valid\"\n    uint16_t parent = 0;\n    uint16_t nameLen = 5;\n    byte entry[4 + 5] = {0};\n    memcpy(entry, &parent, 2);\n    memcpy(entry + 2, &nameLen, 2);\n    memcpy(entry + 4, \"valid\", 5);\n    file->write(sizeof(MAGIC_NUMBER), kj::ArrayPtr<const byte>(entry, sizeof(entry)));\n\n    // Write corrupted entry: parent=999 (invalid), name=\"corrupt\"\n    uint16_t badParent = 999;\n    uint16_t badNameLen = 7;\n    byte badEntry[4 + 7] = {0};\n    memcpy(badEntry, &badParent, 2);\n    memcpy(badEntry + 2, &badNameLen, 2);\n    memcpy(badEntry + 4, \"corrupt\", 7);\n    file->write(\n        sizeof(MAGIC_NUMBER) + sizeof(entry), kj::ArrayPtr<const byte>(badEntry, sizeof(badEntry)));\n\n    // Write valid entry after corruption that should be ignored\n    uint16_t ignoredParent = 0;\n    uint16_t ignoredNameLen = 7;\n    byte ignoredEntry[4 + 7] = {0};\n    memcpy(ignoredEntry, &ignoredParent, 2);\n    memcpy(ignoredEntry + 2, &ignoredNameLen, 2);\n    memcpy(ignoredEntry + 4, \"ignored\", 7);\n    file->write(sizeof(MAGIC_NUMBER) + sizeof(entry) + sizeof(badEntry),\n        kj::ArrayPtr<const byte>(ignoredEntry, sizeof(ignoredEntry)));\n  }\n\n  // Open corrupted file\n  {\n    FacetTreeIndex index(file->clone());\n\n    // Check that only valid entries were read\n    KJ_EXPECT(index.getId(0, \"valid\") == 1);\n\n    // The corrupted entry and everything after it should have been ignored\n    // So this should create a new entry\n    uint id = index.getId(0, \"corrupt\");\n    KJ_EXPECT(id == 2);\n\n    // Similarly, \"ignored\" should be new\n    uint id2 = index.getId(0, \"ignored\");\n    KJ_EXPECT(id2 == 3);\n  }\n\n  // Open yet again, make sure that the newly-added entries were written successfully.\n  {\n    FacetTreeIndex index(file->clone());\n    KJ_EXPECT(index.getId(0, \"valid\") == 1);\n    KJ_EXPECT(index.getId(0, \"corrupt\") == 2);\n    KJ_EXPECT(index.getId(0, \"ignored\") == 3);\n  }\n}\n\nKJ_TEST(\"FacetTreeIndex tree structure\") {\n  auto file = kj::newInMemoryFile(kj::nullClock());\n\n  FacetTreeIndex index(file->clone());\n\n  // Build a tree with multiple levels\n  uint id1 = index.getId(0, \"root1\");\n  uint id2 = index.getId(0, \"root2\");\n\n  uint id3 = index.getId(id1, \"level1_1\");\n  uint id4 = index.getId(id1, \"level1_2\");\n  uint id5 = index.getId(id2, \"level1_3\");\n\n  uint id6 = index.getId(id3, \"level2_1\");\n  uint id7 = index.getId(id3, \"level2_2\");\n  uint id8 = index.getId(id4, \"level2_3\");\n\n  uint id9 = index.getId(id6, \"level3_1\");\n\n  // Verify IDs\n  KJ_EXPECT(id1 == 1);\n  KJ_EXPECT(id2 == 2);\n  KJ_EXPECT(id3 == 3);\n  KJ_EXPECT(id4 == 4);\n  KJ_EXPECT(id5 == 5);\n  KJ_EXPECT(id6 == 6);\n  KJ_EXPECT(id7 == 7);\n  KJ_EXPECT(id8 == 8);\n  KJ_EXPECT(id9 == 9);\n\n  // Verify stable lookup\n  KJ_EXPECT(index.getId(id1, \"level1_1\") == id3);\n  KJ_EXPECT(index.getId(id3, \"level2_1\") == id6);\n  KJ_EXPECT(index.getId(id6, \"level3_1\") == id9);\n}\n\nKJ_TEST(\"FacetTreeIndex handles truncated files correctly\") {\n  auto file = kj::newInMemoryFile(kj::nullClock());\n\n  // Step 1: Create a file with a few entries\n  {\n    FacetTreeIndex index(file->clone());\n    uint id1 = index.getId(0, \"entry1\");\n    uint id2 = index.getId(0, \"entry2\");\n    uint id3 = index.getId(0, \"entry3\");\n\n    KJ_EXPECT(id1 == 1);\n    KJ_EXPECT(id2 == 2);\n    KJ_EXPECT(id3 == 3);\n  }\n\n  // Step 2: Corrupt the last entry by overwriting its nameLength field with an invalid large value\n  auto fileSize = file->stat().size;\n  uint offset =\n      fileSize - 8;  // Go to the nameLength field of the last entry (2 bytes before \"entry3\")\n\n  // Write an impossibly large nameLength value\n  uint16_t hugeNameLength = 65000;  // Much larger than any valid name in our test file\n  file->write(offset,\n      kj::ArrayPtr<const byte>(\n          reinterpret_cast<const byte*>(&hugeNameLength), sizeof(hugeNameLength)));\n\n  // Step 3: Re-read the index and add a new entry\n  {\n    FacetTreeIndex index(file->clone());\n\n    // First two entries should still be valid\n    KJ_EXPECT(index.getId(0, \"entry1\") == 1);\n    KJ_EXPECT(index.getId(0, \"entry2\") == 2);\n\n    // The corrupted entry (entry3) should not be found, so this new entry\n    // should get the ID 3 (reusing the ID that was intended for entry3)\n    uint id = index.getId(0, \"replacement\");\n    KJ_EXPECT(id == 3);\n  }\n\n  // Step 4: Re-read the file again and add yet another new entry\n  {\n    FacetTreeIndex index(file->clone());\n\n    // Immediately get a new entry, without checking existing ones first\n    // This should get ID 4, not reuse ID 3 again\n    uint id = index.getId(0, \"another\");\n\n    // Now check that all previous entries are remembered\n    KJ_EXPECT(id == 4);\n    KJ_EXPECT(index.getId(0, \"entry1\") == 1);\n    KJ_EXPECT(index.getId(0, \"entry2\") == 2);\n    KJ_EXPECT(index.getId(0, \"replacement\") == 3);\n  }\n}\n\n}  // namespace\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/facet-tree-index.c++",
    "content": "#include \"facet-tree-index.h\"\n\n#include <kj/debug.h>\n#include <kj/io.h>\n\nnamespace workerd::server {\n\nusing kj::byte;\nusing kj::uint;\n\nFacetTreeIndex::FacetTreeIndex(kj::Own<const kj::File> fileParam): file(kj::mv(fileParam)) {\n  // Read the file to populate the initial index\n\n  auto fileBytes = file->readAllBytes();\n\n  // Check if the magic number is present.\n  //\n  // If the file size is less than or equal to the magic number size itself, it's possible that a\n  // previous session suffered a failure while writing the magic number. In that case we can assume\n  // nothing was ever written to the index, so we just rewrite it and start over.\n  if (fileBytes.size() <= sizeof(MAGIC_NUMBER)) {\n    // New file, initialize with magic number.\n    file->write(0, kj::asBytes(MAGIC_NUMBER));\n    file->datasync();\n    offset = sizeof(MAGIC_NUMBER);\n    return;\n  }\n\n  // On the other hand, because we datasync() immediately after writing the magic number, we can\n  // assume that if _more_ bytes are written than just the magic number, then a failure did _not_\n  // occurr during the writing of the magic number, and therefore, if it contains the wrong bytes,\n  // the file must be in a format we don't recognize.\n  uint64_t magic = 0;\n  memcpy(&magic, fileBytes.begin(), sizeof(magic));\n  KJ_REQUIRE(magic == MAGIC_NUMBER, \"unknown magic number on facet tree index\");\n  offset = sizeof(magic);\n\n  // Read entries\n  while (offset + sizeof(EntryHeader) <= fileBytes.size()) {\n    KJ_REQUIRE(nextId() <= MAX_ID, \"Maximum number of facets exceeded\");\n\n    EntryHeader header;\n    memcpy(&header, fileBytes.begin() + offset, sizeof(header));\n\n    // Validation checks\n    if (header.nameLength == 0) {\n      // Empty name is invalid.\n      break;\n    }\n\n    if (offset + sizeof(EntryHeader) + header.nameLength > fileBytes.size()) {\n      // Name extends beyond file bounds, invalid.\n      break;\n    }\n\n    if (header.parentId >= nextId()) {\n      // Invalid parent ID (parent must already exist).\n      break;\n    }\n\n    // Extract the name\n    kj::String name = kj::heapString(\n        reinterpret_cast<const char*>(fileBytes.begin() + offset + sizeof(EntryHeader)),\n        header.nameLength);\n\n    bool duplicate = false;\n    entries.upsert(\n        Entry{header.parentId, kj::mv(name)}, [&](Entry&, Entry&&) { duplicate = true; });\n\n    if (duplicate) {\n      // Duplicate entry is invalid.\n      break;\n    }\n\n    // Entry was valid and processed successfully, now we can update the offset\n    offset += sizeof(EntryHeader) + header.nameLength;\n  }\n\n  if (offset < fileBytes.size()) {\n    // It appears we stopped at a corrupted entry. We assume such corruption can only be the result\n    // of a power failure in the middle of writing an entry during a past session. Any entry which\n    // was written but not synced can be presumed to have never been used, so we can simply\n    // truncate it from the file.\n    file->truncate(offset);\n  }\n}\n\nuint FacetTreeIndex::getId(uint parent, kj::StringPtr name) {\n  KJ_REQUIRE(name.size() > 0, \"Facet name cannot be empty\");\n  KJ_REQUIRE(name.size() <= (uint16_t)kj::maxValue, \"Facet name too long\");\n  KJ_REQUIRE(parent <= entries.size(), \"Invalid parent ID\");\n\n  // Use findOrCreate to either find an existing entry or create a new one\n  auto& entry = entries.findOrCreate(EntryPtr{parent, name}, [&]() -> Entry {\n    // New entry, need to assign a new ID and append to file\n    KJ_REQUIRE(nextId() <= MAX_ID, \"Maximum number of facets exceeded\");\n\n    // Prepare entry data\n    EntryHeader header{\n      .parentId = static_cast<uint16_t>(parent),\n      .nameLength = static_cast<uint16_t>(name.size()),\n    };\n\n// Don't whine about VLA being non-standard.\n#pragma clang diagnostic ignored \"-Wvla-cxx-extension\"\n\n    size_t entrySize = sizeof(EntryHeader) + header.nameLength;\n    byte entryData[entrySize];\n    memcpy(entryData, &header, sizeof(header));\n    memcpy(entryData + sizeof(EntryHeader), name.begin(), header.nameLength);\n\n    file->write(offset, kj::arrayPtr(entryData, entrySize));\n\n    // We don't want to return an entry that might disappear after a power failure, so sync it\n    // now.\n    file->datasync();\n\n    offset += entrySize;\n\n    return Entry{parent, kj::heapString(name)};\n  });\n\n  // Calculate the ID based on the entry's position in the set\n  // Root facet (ID 0) isn't in the entries set, so add 1 to the index\n  return 1 + (&entry - entries.begin());\n}\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/facet-tree-index.h",
    "content": "#pragma once\n\n#include <kj/filesystem.h>\n#include <kj/map.h>\n\nnamespace workerd::server {\n\nusing kj::uint;\n\n// Implements an index, stored on disk, which maps leaves of a tree to small integers in a stable\n// way.\n//\n// Specifically, this is used to assign numeric IDs to facets of Durable Objects. Each Durable\n// Object is potentially composed of a tree of \"facets\". One facet -- with ID zero -- serves as\n// the root facet. All other facets have a parent facet and a name. Names are unique among facets\n// with the same parent, but not globally. Each facet is assigned a numeric ID the first time it is\n// seen. These IDs are assigned sequentially.\n//\n// We assume that the total number of facets created for a single Durable Object over its entire\n// lifetime will never be very large. Therefore, it is reasonable to store the entire tree index\n// in memory, loaded in its entirely at startup. Because of this, entries can simply be stored in\n// order by ID (starting with ID 1, since no entry is needed for the root). We also assume that\n// it's never necessary to delete an entry -- while a facet itself can be deleted, if a new facet\n// is created with the same name, it should use the same ID. Therefore, the index file can be\n// append-only, modified only when a never-before-seen facet is created.\n//\n// The facet index file therefore uses a very simple format. The index is simply a sequence of\n// entries, where each entry is composed of:\n// * A 2-byte integer specifying the parent ID.\n// * A 2-byte integer specifying the length of the name. Note this cannot be zero.\n// * The bytes of the name itself (not including any NUL terminator).\n//\n// Note that the format implicitly limits a Durable Object to have no more than 65536 facets in\n// its entire lifetime. An attempt to exceed this limit will throw an exception. If this ever comes\n// up in practice, we probably need to rethink the format -- not just the size of the integers, but\n// the entire design, as it is not designed for so many facets.\n//\n// Notice that the index file's design is such that updating the file strictly at append operation.\n// This avoids the need for a write-ahead log on updates. It is still possible, in the event of\n// a power failure during an update, that the tail of the index will be corrupted. This is OK,\n// becaues that tail could not have been relied upon yet. When reading the file, if a nonsensical\n// entry is seen (parent ID out-of-range, name overrunning the end of the file, empty name, or\n// duplicate entry), the remainder of the file from that point can simply be ignored. In the\n// unlikely event that corrupted entries by coincidence appear to be valid, no harm is done -- this\n// only has the effect of assigning IDs to names that will never actually be used.\n//\n// The index file is prefixed with the 8-byte magic number 0xc4cdce5bc5b0ef57. All integers\n// (including the magic number) are in host byte order (which is little-endian on all supported\n// platforms).\nclass FacetTreeIndex {\n public:\n  // Construct the index, reading the given file to populate the initial index, and then arranging\n  // to append new entries to the file as needed.\n  FacetTreeIndex(kj::Own<const kj::File> file);\n\n  // Gets the ID for the given facet, assigning it if needed.\n  uint getId(uint parent, kj::StringPtr name);\n\n  // For each child of the given parent ID, call the callback.\n  template <typename Func>\n  void forEachChild(uint parentId, Func&& callback) {\n    for (auto& child: entries.range(EntryPtr{parentId, nullptr}, EntryPtr{parentId + 1, nullptr})) {\n      KJ_IASSERT(child.parent == parentId);\n      uint childId = 1 + (&child - entries.begin());\n      callback(childId, child.name);\n    }\n  }\n\n private:\n  kj::Own<const kj::File> file;\n\n  // Offset at which to write the next entry. Typically points to the end of the file (except when\n  // a corrupted tail was detected).\n  uint offset = 0;\n\n  struct EntryPtr;\n\n  struct Entry {\n    uint parent;\n    kj::String name;\n\n    bool operator==(const Entry& other) const = default;\n    bool operator<(const Entry& other) const {\n      if (parent < other.parent) return true;\n      if (parent > other.parent) return false;\n      return name < other.name;\n    }\n    bool operator<(const EntryPtr& other) const {\n      if (parent < other.parent) return true;\n      if (parent > other.parent) return false;\n      return name < other.name;\n    }\n  };\n\n  struct EntryPtr {\n    uint parent;\n    kj::StringPtr name;\n\n    bool operator==(const Entry& other) const {\n      return parent == other.parent && name == other.name;\n    }\n  };\n\n  // All entries. Note that there's no need to store the ID of each entry since they are strictly\n  // ordered with no erasures. kj::TreeSet is based on kj::Table which maintains the original\n  // insertion order (as long as no erasures occur), so the index of any entry can be computed\n  // by subtracting `entries.begin()` from its pointer. (Add 1 to the index to get the ID, since\n  // the root is ID zero.)\n  kj::TreeSet<Entry> entries;\n\n  // Next ID that will be assigned. Off-by-one due to root not being in the set.\n  inline uint nextId() {\n    return entries.size() + 1;\n  }\n\n  static constexpr uint64_t MAGIC_NUMBER = 0xc4cdce5bc5b0ef57;\n  static constexpr uint MAX_ID = static_cast<uint16_t>(kj::maxValue);\n\n  struct EntryHeader {\n    uint16_t parentId;\n    uint16_t nameLength;\n  };\n};\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/fallback-service.c++",
    "content": "#include \"fallback-service.h\"\n\n#include <capnp/compat/json.h>\n#include <capnp/message.h>\n#include <kj/async-io.h>\n#include <kj/compat/http.h>\n#include <kj/compat/url.h>\n#include <kj/debug.h>\n#include <kj/one-of.h>\n#include <kj/string.h>\n#include <kj/thread.h>\n\nnamespace workerd::fallback {\nnamespace {\n\nconstexpr kj::StringPtr getMethodFromType(ImportType type) {\n  switch (type) {\n    case ImportType::IMPORT:\n      return \"import\"_kjc;\n    case ImportType::REQUIRE:\n      return \"require\"_kjc;\n    case ImportType::INTERNAL:\n      return \"internal\"_kjc;\n  }\n  KJ_UNREACHABLE;\n}\n\nModuleOrRedirect handleReturnPayload(\n    kj::Maybe<kj::String> jsonPayload, bool redirect, kj::StringPtr specifier) {\n  KJ_IF_SOME(payload, jsonPayload) {\n    // If the payload is empty then the fallback service failed to fetch the module.\n    if (payload.size() == 0) return kj::none;\n\n    // If redirect is true then the fallback service returned a 301 redirect. The\n    // payload is the specifier of the new target module.\n    if (redirect) {\n      return kj::Maybe(kj::mv(payload));\n    }\n\n    // The response from the fallback service must be a valid JSON serialization\n    // of the workerd module configuration. If it is not, or if there is any other\n    // error when processing here, we'll log the exception and return nothing.\n    KJ_TRY {\n      capnp::MallocMessageBuilder moduleMessage;\n      capnp::JsonCodec json;\n      json.handleByAnnotation<server::config::Worker::Module>();\n      auto moduleBuilder = moduleMessage.initRoot<server::config::Worker::Module>();\n      json.decode(payload, moduleBuilder);\n\n      // If the module fallback service returns a name in the module then it has to\n      // match the specifier we passed in. This is an optional sanity check.\n      if (moduleBuilder.hasName()) {\n        if (moduleBuilder.getName() != specifier) {\n          KJ_LOG(ERROR,\n              \"Fallback service failed to fetch module: returned module \"\n              \"name does not match specifier\",\n              moduleBuilder.getName(), specifier);\n          return kj::none;\n        }\n      } else {\n        moduleBuilder.setName(kj::str(specifier));\n      }\n\n      kj::Own<server::config::Worker::Module::Reader> ret = capnp::clone(moduleBuilder.asReader());\n      return ModuleOrRedirect(kj::mv(ret));\n    }\n    KJ_CATCH(exception) {\n      KJ_LOG(ERROR, \"Fallback service failed to fetch module\", exception, specifier);\n      return kj::none;\n    }\n  }\n\n  // If we got here, no jsonPayload was received and we return nothing.\n  return kj::none;\n}\n\n}  // namespace\n\n// ---- FallbackServiceClient implementation ----\n\nFallbackServiceClient::FallbackServiceClient(kj::String address)\n    : ownedAddress(kj::mv(address)),\n      thread([this]() { threadMain(); }) {}\n\nFallbackServiceClient::~FallbackServiceClient() noexcept(false) {\n  // Signal the background thread to exit. The kj::Thread destructor\n  // (which runs after this body) will join the thread.\n  auto lock = state.lockExclusive();\n  lock->shutdown = true;\n}\n\nModuleOrRedirect FallbackServiceClient::tryResolve(Version version,\n    ImportType type,\n    kj::StringPtr specifier,\n    kj::StringPtr rawSpecifier,\n    kj::StringPtr referrer,\n    const kj::HashMap<kj::StringPtr, kj::StringPtr>& attributes) {\n  // Submit request to background thread.\n  {\n    auto lock = state.lockExclusive();\n    KJ_REQUIRE(!lock->shutdown, \"FallbackServiceClient has been shut down\");\n    KJ_ASSERT(!lock->hasRequest, \"FallbackServiceClient does not support concurrent requests\");\n    lock->version = version;\n    lock->type = type;\n    lock->specifier = specifier;\n    lock->rawSpecifier = rawSpecifier;\n    lock->referrer = referrer;\n    lock->attributes = &attributes;\n    lock->hasRequest = true;\n  }\n\n  // Block until the background thread has processed our request.\n  return state.when([](const SharedState& s) { return s.responseReady || s.shutdown; },\n      [](SharedState& s) -> ModuleOrRedirect {\n    if (!s.responseReady) {\n      // Background thread shut down without producing a response.\n      return kj::none;\n    }\n    auto result = kj::mv(s.response);\n    s.responseReady = false;\n    return result;\n  });\n}\n\nvoid FallbackServiceClient::threadMain() {\n  KJ_TRY {\n    // Set up the async I/O context, DNS resolution, and HTTP client once.\n    // These are reused for all subsequent requests.\n    kj::AsyncIoContext io = kj::setupAsyncIo();\n    kj::HttpHeaderTable::Builder builder;\n    kj::HttpHeaderId kMethod = builder.add(\"x-resolve-method\");\n    auto headerTable = builder.build();\n\n    auto addr = io.provider->getNetwork().parseAddress(ownedAddress, 80).wait(io.waitScope);\n    auto client = kj::newHttpClient(io.provider->getTimer(), *headerTable, *addr, {});\n\n    while (true) {\n      // Wait for a request or shutdown signal.\n      // We copy to owned kj::String locals so that lifetimes are robust -- the caller\n      // blocks on responseReady (not hasRequest), but owning the strings makes this\n      // safe against future refactors that might change that invariant.\n      Version version;\n      ImportType type;\n      kj::String specifier;\n      kj::Maybe<kj::String> rawSpecifier;\n      kj::String referrer;\n      const kj::HashMap<kj::StringPtr, kj::StringPtr>* attributes;\n      bool shouldExit = state.when([](const SharedState& s) { return s.hasRequest || s.shutdown; },\n          [&](SharedState& s) -> bool {\n        if (s.shutdown) return true;\n        version = s.version;\n        type = s.type;\n        specifier = kj::str(s.specifier);\n        if (s.rawSpecifier != nullptr) {\n          rawSpecifier = kj::str(s.rawSpecifier);\n        }\n        referrer = kj::str(s.referrer);\n        attributes = s.attributes;\n        s.hasRequest = false;\n        return false;\n      });\n      if (shouldExit) return;\n\n      // Process the request using the shared HTTP client.\n      ModuleOrRedirect result = kj::none;\n\n      if (version == Version::V1) {\n        // === V1: GET request with query parameters ===\n        kj::Maybe<kj::String> jsonPayload;\n        bool redirect = false;\n        bool prefixed = false;\n        kj::Url url;\n        kj::StringPtr actualSpecifier = nullptr;\n\n        KJ_IF_SOME(pos, specifier.findLast('/')) {\n          auto segment = specifier.slice(pos + 1);\n          if (segment.startsWith(\"node:\") || segment.startsWith(\"cloudflare:\") ||\n              segment.startsWith(\"workerd:\")) {\n            actualSpecifier = segment;\n            url.query.add(\n                kj::Url::QueryParam{.name = kj::str(\"specifier\"), .value = kj::str(segment)});\n            prefixed = true;\n          }\n        }\n        if (!prefixed) {\n          actualSpecifier = specifier;\n          if (actualSpecifier.startsWith(\"/\")) {\n            actualSpecifier = specifier.slice(1);\n          }\n          url.query.add(kj::Url::QueryParam{kj::str(\"specifier\"), kj::str(specifier)});\n        }\n        url.query.add(kj::Url::QueryParam{kj::str(\"referrer\"), kj::str(referrer)});\n        // V1 always includes rawSpecifier in the query, defaulting to empty if absent.\n        KJ_IF_SOME(rs, rawSpecifier) {\n          url.query.add(kj::Url::QueryParam{kj::str(\"rawSpecifier\"), kj::str(rs)});\n        } else {\n          url.query.add(kj::Url::QueryParam{kj::str(\"rawSpecifier\"), kj::str(\"\")});\n        }\n\n        auto spec = url.toString(kj::Url::HTTP_REQUEST);\n\n        // Retry once on disconnect (stale pooled connection).\n        for (int attempt = 0; attempt < 2; attempt++) {\n          KJ_TRY {\n            kj::HttpHeaders headers(*headerTable);\n            headers.setPtr(kMethod, getMethodFromType(type));\n            headers.setPtr(kj::HttpHeaderId::HOST, \"localhost\"_kj);\n\n            auto request = client->request(kj::HttpMethod::GET, spec, headers, kj::none);\n            kj::HttpClient::Response resp = request.response.wait(io.waitScope);\n\n            if (resp.statusCode == 301) {\n              KJ_IF_SOME(loc, resp.headers->get(kj::HttpHeaderId::LOCATION)) {\n                redirect = true;\n                jsonPayload = kj::str(loc);\n              } else {\n                KJ_LOG(ERROR, \"Fallback service returned a redirect with no location\", spec);\n              }\n              // Drain the response body to allow HTTP/1.1 connection reuse.\n              resp.body->readAllBytes().wait(io.waitScope);\n            } else if (resp.statusCode != 200) {\n              auto payload = resp.body->readAllText().wait(io.waitScope);\n              KJ_LOG(ERROR, \"Fallback service failed to fetch module\", payload, spec);\n            } else {\n              jsonPayload = resp.body->readAllText().wait(io.waitScope);\n            }\n            break;  // Success, no retry needed.\n          }\n          KJ_CATCH(exception) {\n            if (attempt == 0 && exception.getType() == kj::Exception::Type::DISCONNECTED) {\n              // Stale pooled connection; retry with a fresh one.\n              continue;\n            }\n            KJ_LOG(ERROR, \"Fallback service failed to fetch module\", exception, spec);\n          }\n        }\n\n        result = handleReturnPayload(kj::mv(jsonPayload), redirect, actualSpecifier);\n\n      } else {\n        // === V2: POST request with JSON body ===\n        capnp::JsonCodec json;\n        capnp::MallocMessageBuilder moduleMessage;\n        auto requestMsg = moduleMessage.initRoot<server::config::FallbackServiceRequest>();\n        requestMsg.setType(getMethodFromType(type));\n        requestMsg.setSpecifier(specifier);\n        requestMsg.setReferrer(referrer);\n\n        KJ_IF_SOME(rs, rawSpecifier) {\n          requestMsg.setRawSpecifier(rs);\n        }\n\n        KJ_ASSERT(attributes != nullptr);\n        if (attributes->size() > 0) {\n          auto attrs = requestMsg.initAttributes(attributes->size());\n          size_t n = 0;\n          for (auto& attr: *attributes) {\n            attrs[n].setName(attr.key);\n            attrs[n].setValue(attr.value);\n            n++;\n          }\n        }\n\n        auto payload = json.encode(requestMsg);\n\n        kj::Maybe<kj::String> jsonPayload;\n        bool redirect = false;\n\n        // Retry once on disconnect (stale pooled connection).\n        for (int attempt = 0; attempt < 2; attempt++) {\n          KJ_TRY {\n            kj::HttpHeaders headers(*headerTable);\n            headers.setPtr(kj::HttpHeaderId::HOST, \"localhost\");\n\n            auto request = client->request(kj::HttpMethod::POST, \"/\", headers, payload.size());\n            request.body->write(payload.asPtr().asBytes()).wait(io.waitScope);\n\n            kj::HttpClient::Response resp = request.response.wait(io.waitScope);\n\n            if (resp.statusCode == 301) {\n              KJ_IF_SOME(loc, resp.headers->get(kj::HttpHeaderId::LOCATION)) {\n                redirect = true;\n                jsonPayload = kj::str(loc);\n              } else {\n                KJ_LOG(ERROR, \"Fallback service returned a redirect with no location\", specifier);\n              }\n              // Drain the response body to allow HTTP/1.1 connection reuse.\n              resp.body->readAllBytes().wait(io.waitScope);\n            } else if (resp.statusCode != 200) {\n              auto body = resp.body->readAllText().wait(io.waitScope);\n              KJ_LOG(ERROR, \"Fallback service failed to fetch module\", body, specifier);\n            } else {\n              jsonPayload = resp.body->readAllText().wait(io.waitScope);\n            }\n            break;  // Success, no retry needed.\n          }\n          KJ_CATCH(exception) {\n            if (attempt == 0 && exception.getType() == kj::Exception::Type::DISCONNECTED) {\n              // Stale pooled connection; retry with a fresh one.\n              continue;\n            }\n            KJ_LOG(ERROR, \"Fallback service failed to fetch module\", exception);\n          }\n        }\n\n        result = handleReturnPayload(kj::mv(jsonPayload), redirect, specifier);\n      }\n\n      // Deliver the result to the calling thread.\n      {\n        auto lock = state.lockExclusive();\n        lock->response = kj::mv(result);\n        lock->responseReady = true;\n      }\n    }\n  }\n  KJ_CATCH(exception) {\n    KJ_LOG(ERROR, \"Fallback service thread exiting; module resolution disabled\", exception);\n    // Signal any waiting caller and prevent future requests.\n    auto lock = state.lockExclusive();\n    lock->response = kj::none;\n    lock->responseReady = true;\n    lock->shutdown = true;\n  }\n}\n\n}  // namespace workerd::fallback\n"
  },
  {
    "path": "src/workerd/server/fallback-service.h",
    "content": "#pragma once\n\n#include <workerd/server/workerd.capnp.h>\n\n#include <kj/common.h>\n#include <kj/map.h>\n#include <kj/mutex.h>\n#include <kj/one-of.h>\n#include <kj/string.h>\n#include <kj/thread.h>\n\nnamespace workerd::fallback {\n\n// The fallback service is a mechanism used only in workerd local development.\n// It is used to use an external http service to resolve module specifiers\n// dynamically if the module is not found in the static bundles. A worker\n// must be configured to use the fallback service and workerd must be started\n// with the --experimental CLI flag.\n//\n// There are two versions of the fallback service protocol:\n//\n// V1: The request is sent to the fallback service as a GET request using\n// query strings to pass the details. The specifier and referrer are treated\n// as strings. Import attributes are not included.\n//\n// V2: The request is sent to the fallback service as a POST request using\n// JSON to pass the details. The specifier and referrer are treated as URLs.\n// Import attributes are included.\n//\n// The fallback service may return either a JSON string describing the module\n// configuration, a 301 redirect to a different module specifier, or an error.\nenum class ImportType {\n  // The import is a static or dynamic import\n  IMPORT,\n  // The import is a CommonJs-style require()\n  REQUIRE,\n  // The import originated from inside the runtime\n  INTERNAL,\n};\n\nenum class Version {\n  // With V1 of the fallback service, the request is sent to the fallback\n  // service as a GET request using query strings to pass the details.\n  // The specifier and referrer are treated as strings. Import attributes\n  // are not included.\n  V1,\n  // With V2 of the fallback service, the request is sent to the fallback\n  // service as a POST request using JSON to pass the details. The specifier\n  // and referrer are treated as URLs. Import attributes are included.\n  V2,\n};\n\nusing ModuleOrRedirect =\n    kj::Maybe<kj::OneOf<kj::String, kj::Own<server::config::Worker::Module::Reader>>>;\n\n// A persistent client for the fallback service that uses a single background\n// thread with a long-lived HTTP client for all module resolution requests.\n// This avoids creating a new OS thread, DNS lookup, and TCP connection for\n// each request, which can exhaust ephemeral ports when many modules are\n// resolved concurrently (e.g. running many test files with vitest-pool-workers).\n//\n// IMPORTANT: This class supports only one caller at a time. tryResolve() will\n// assert if called concurrently. Module resolution in workerd is single-threaded\n// per isolate/registry so this is safe in practice.\nclass FallbackServiceClient {\n public:\n  explicit FallbackServiceClient(kj::String address);\n  ~FallbackServiceClient() noexcept(false);\n\n  KJ_DISALLOW_COPY_AND_MOVE(FallbackServiceClient);\n\n  ModuleOrRedirect tryResolve(Version version,\n      ImportType type,\n      kj::StringPtr specifier,\n      kj::StringPtr rawSpecifier,\n      kj::StringPtr referrer,\n      const kj::HashMap<kj::StringPtr, kj::StringPtr>& attributes);\n\n private:\n  // Shared state between the calling thread and the background thread.\n  // Access is serialized through kj::MutexGuarded.\n  struct SharedState {\n    // Request fields - valid only when hasRequest is true.\n    // These contain non-owning references into the caller's stack frame,\n    // which is safe because the caller blocks until responseReady is set.\n    Version version = Version::V1;\n    ImportType type = ImportType::IMPORT;\n    kj::StringPtr specifier;\n    kj::StringPtr rawSpecifier;\n    kj::StringPtr referrer;\n    const kj::HashMap<kj::StringPtr, kj::StringPtr>* attributes = nullptr;\n    bool hasRequest = false;\n\n    // Response field - valid only when responseReady is true.\n    ModuleOrRedirect response;\n    bool responseReady = false;\n\n    // Set to true to signal the background thread to exit.\n    bool shutdown = false;\n  };\n\n  kj::String ownedAddress;\n  kj::MutexGuarded<SharedState> state;\n  kj::Thread thread;\n\n  void threadMain();\n};\n\n}  // namespace workerd::fallback\n"
  },
  {
    "path": "src/workerd/server/json-logger-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#if __linux__\n#include \"json-logger.h\"\n\n#include <workerd/server/log-schema.capnp.h>\n\n#include <fcntl.h>\n#include <unistd.h>\n\n#include <capnp/compat/json.h>\n#include <capnp/message.h>\n#include <kj/async-unix.h>\n#include <kj/io.h>\n\n#include <cstdio>\n#endif  // __linux__\n#include <kj/test.h>\n\nnamespace workerd::server {\nnamespace {\n#if __linux__\n// This test uses pipe2 and dup2 to capture stdout which is far easier on linux.\n\nstruct FdPair {\n  kj::AutoCloseFd output;\n  kj::AutoCloseFd input;\n};\n\nauto makePipeFds() {\n  int pipeFds[2];\n  KJ_SYSCALL(pipe2(pipeFds, O_CLOEXEC));\n\n  return FdPair{\n    .output = kj::AutoCloseFd(pipeFds[0]),\n    .input = kj::AutoCloseFd(pipeFds[1]),\n  };\n}\n\nclass OutputCapture {\n public:\n  OutputCapture(int fd): targetFd(fd), originalFd(dup(fd)) {\n    auto pipe = makePipeFds();\n    KJ_SYSCALL(dup2(pipe.input.get(), targetFd));\n    readFd = kj::mv(pipe.output);\n  }\n\n  ~OutputCapture() {\n    KJ_SYSCALL(dup2(originalFd, targetFd));\n    close(originalFd);\n  }\n\n  kj::String readOutput() {\n    fflush(targetFd == STDOUT_FILENO ? stdout : stderr);\n\n    char buffer[4096];\n    ssize_t n;\n    KJ_SYSCALL(n = read(readFd.get(), buffer, sizeof(buffer) - 1));\n    buffer[n] = '\\0';\n\n    return kj::str(buffer, n);\n  }\n\n private:\n  int targetFd;\n  int originalFd;\n  kj::AutoCloseFd readFd;\n};\n\nkj::Maybe<kj::String> findJsonEntryContaining(kj::StringPtr output, kj::StringPtr searchText) {\n  size_t start = 0;\n\n  while (start < output.size()) {\n    KJ_IF_SOME(nlPos, output.slice(start).findFirst('\\n')) {\n      auto line = output.slice(start, start + nlPos);\n      kj::String lineStr = kj::str(line);\n      if (lineStr.contains(searchText)) {\n        return kj::mv(lineStr);\n      }\n      start = start + nlPos + 1;\n    } else {\n      auto line = output.slice(start);\n      kj::String lineStr = kj::str(line);\n      if (lineStr.contains(searchText)) {\n        return kj::mv(lineStr);\n      }\n      break;\n    }\n  }\n\n  return kj::none;\n}\n\nvoid validateJsonLogEntry(kj::StringPtr jsonString,\n    log_schema::LogEntry::LogLevel expectedLevel,\n    kj::StringPtr expectedMessage) {\n  capnp::JsonCodec codec;\n  codec.handleByAnnotation<log_schema::LogEntry>();\n\n  capnp::MallocMessageBuilder message;\n  auto logEntry = message.initRoot<log_schema::LogEntry>();\n  codec.decode(jsonString, logEntry);\n\n  KJ_EXPECT(logEntry.getLevel() == expectedLevel);\n  KJ_EXPECT(logEntry.getMessage() == expectedMessage);\n  KJ_EXPECT(logEntry.getTimestamp() > 0);\n}\n\nKJ_TEST(\"JsonLogger stdout validation\") {\n  JsonLogger logger;\n  OutputCapture capture(STDOUT_FILENO);\n\n  KJ_LOG(ERROR, \"Test JSON message\");\n\n  auto output = capture.readOutput();\n  auto jsonEntry = KJ_ASSERT_NONNULL(findJsonEntryContaining(output, \"Test JSON message\"));\n  validateJsonLogEntry(jsonEntry, log_schema::LogEntry::LogLevel::ERROR, \"Test JSON message\");\n\n  capnp::JsonCodec codec;\n  codec.handleByAnnotation<log_schema::LogEntry>();\n  capnp::MallocMessageBuilder message;\n  auto logEntry = message.initRoot<log_schema::LogEntry>();\n  codec.decode(jsonEntry, logEntry);\n\n  auto source = logEntry.getSource();\n  KJ_EXPECT(kj::StringPtr(source.begin(), source.size()).contains(\"json-logger-test.c++\"));\n}\n\nKJ_TEST(\"StructuredLoggingProcessContext - plain text mode by default\") {\n  StructuredLoggingProcessContext context(\"test-program\");\n\n  KJ_EXPECT(context.getProgramName() == \"test-program\");\n\n  OutputCapture capture(STDERR_FILENO);\n  context.warning(\"Test warning message\");\n\n  auto output = capture.readOutput();\n\n  KJ_EXPECT(output.contains(\"Test warning message\"));\n  KJ_EXPECT(!output.contains(\"{\"));\n}\n\nKJ_TEST(\"StructuredLoggingProcessContext - structured logging mode\") {\n  StructuredLoggingProcessContext context(\"test-program\");\n  context.enableStructuredLogging();\n\n  OutputCapture capture(STDERR_FILENO);\n  context.warning(\"Test structured warning\");\n\n  auto output = capture.readOutput();\n  auto jsonEntry = KJ_ASSERT_NONNULL(findJsonEntryContaining(output, \"Test structured warning\"));\n  validateJsonLogEntry(\n      jsonEntry, log_schema::LogEntry::LogLevel::WARNING, \"Test structured warning\");\n}\n\nKJ_TEST(\"StructuredLoggingProcessContext - error handling in structured mode\") {\n  StructuredLoggingProcessContext context(\"test-program\");\n  context.enableStructuredLogging();\n\n  OutputCapture capture(STDERR_FILENO);\n  context.error(\"Test structured error\");\n\n  auto output = capture.readOutput();\n  auto jsonEntry = KJ_ASSERT_NONNULL(findJsonEntryContaining(output, \"Test structured error\"));\n  validateJsonLogEntry(jsonEntry, log_schema::LogEntry::LogLevel::ERROR, \"Test structured error\");\n}\n\n#endif  // __linux__\n\nKJ_TEST(\"Blank test because KJ fails when 0 tests are enabled\") {}\n\n}  // namespace\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/json-logger.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"json-logger.h\"\n\n#include <workerd/server/log-schema.capnp.h>\n\n#include <capnp/compat/json.h>\n#include <capnp/message.h>\n#include <kj/debug.h>\n#include <kj/io.h>\n#include <kj/main.h>\n#include <kj/miniposix.h>\n#include <kj/string.h>\n\nnamespace workerd::server {\n\nlog_schema::LogEntry::LogLevel severityToLogLevel(kj::LogSeverity severity) {\n  switch (severity) {\n    case kj::LogSeverity::INFO:\n      return log_schema::LogEntry::LogLevel::INFO;\n    case kj::LogSeverity::WARNING:\n      return log_schema::LogEntry::LogLevel::WARNING;\n    case kj::LogSeverity::ERROR:\n      return log_schema::LogEntry::LogLevel::ERROR;\n    case kj::LogSeverity::FATAL:\n      return log_schema::LogEntry::LogLevel::FATAL;\n    case kj::LogSeverity::DBG:\n      return log_schema::LogEntry::LogLevel::DEBUG_;\n  }\n}\n\nkj::String buildJsonLogMessage(\n    kj::LogSeverity severity, const char* file, int line, int contextDepth, kj::StringPtr text) {\n  capnp::MallocMessageBuilder message;\n  auto logEntry = message.initRoot<log_schema::LogEntry>();\n\n  logEntry.setTimestamp(\n      (kj::systemPreciseCalendarClock().now() - kj::UNIX_EPOCH) / kj::MILLISECONDS);\n\n  logEntry.setLevel(severityToLogLevel(severity));\n\n  auto location = kj::str(file, \":\", line);\n  logEntry.setSource(location);\n\n  logEntry.setMessage(text);\n\n  if (contextDepth > 0) {\n    logEntry.setContextDepth(static_cast<uint32_t>(contextDepth));\n  }\n\n  capnp::JsonCodec codec;\n  codec.handleByAnnotation<log_schema::LogEntry>();\n  codec.setPrettyPrint(false);  // Compact JSON for logs\n  return codec.encode(logEntry);\n}\n\nvoid JsonLogger::logMessage(\n    kj::LogSeverity severity, const char* file, int line, int contextDepth, kj::String&& text) {\n  // Prevent infinite recursion if logging code itself logs\n  if (loggingInProgress) {\n    return;\n  }\n  loggingInProgress = true;\n  KJ_DEFER(loggingInProgress = false);\n\n  auto json = buildJsonLogMessage(severity, file, line, contextDepth, text);\n\n  // Write directly to stdout with no buffering.\n  kj::FdOutputStream(STDOUT_FILENO).write({json.asBytes(), \"\\n\"_kj.asBytes()});\n}\n\nkj::Function<void(kj::Function<void()>)> JsonLogger::getThreadInitializer() {\n  auto nextInit = next.getThreadInitializer();\n\n  return [nextInit = kj::mv(nextInit)](kj::Function<void()> func) mutable {\n    nextInit([&]() {\n      JsonLogger logger;\n\n      // Make sure func is destroyed before the context is destroyed.\n      auto ownFunc = kj::mv(func);\n      ownFunc();\n    });\n  };\n}\n\n// =======================================================================================\n// StructuredLoggingProcessContext implementation\n\nStructuredLoggingProcessContext::StructuredLoggingProcessContext(kj::StringPtr programName)\n    : topLevelContext(programName) {}\n\nvoid StructuredLoggingProcessContext::enableStructuredLogging() {\n  useStructuredLogging = true;\n  jsonLogger.emplace();\n}\n\nkj::StringPtr StructuredLoggingProcessContext::getProgramName() {\n  return topLevelContext.getProgramName();\n}\n\nvoid StructuredLoggingProcessContext::exit() {\n  topLevelContext.exit();\n}\n\nvoid StructuredLoggingProcessContext::warning(kj::StringPtr message) const {\n  if (useStructuredLogging) {\n    auto json = buildJsonLogMessage(kj::LogSeverity::WARNING, __FILE__, __LINE__, 0, message);\n    topLevelContext.warning(json);\n  } else {\n    topLevelContext.warning(message);\n  }\n}\n\nvoid StructuredLoggingProcessContext::error(kj::StringPtr message) const {\n  if (useStructuredLogging) {\n    auto json = buildJsonLogMessage(kj::LogSeverity::ERROR, __FILE__, __LINE__, 0, message);\n    topLevelContext.error(json);\n  } else {\n    topLevelContext.error(message);\n  }\n}\n\nvoid StructuredLoggingProcessContext::exitError(kj::StringPtr message) {\n  if (useStructuredLogging) {\n    auto json = buildJsonLogMessage(kj::LogSeverity::ERROR, __FILE__, __LINE__, 0, message);\n    topLevelContext.exitError(json);\n  } else {\n    topLevelContext.exitError(message);\n  }\n}\n\nvoid StructuredLoggingProcessContext::exitInfo(kj::StringPtr message) {\n  if (useStructuredLogging) {\n    auto json = buildJsonLogMessage(kj::LogSeverity::INFO, __FILE__, __LINE__, 0, message);\n    topLevelContext.exitInfo(json);\n  } else {\n    topLevelContext.exitInfo(message);\n  }\n}\n\nvoid StructuredLoggingProcessContext::increaseLoggingVerbosity() {\n  topLevelContext.increaseLoggingVerbosity();\n}\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/json-logger.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/exception.h>\n#include <kj/function.h>\n#include <kj/main.h>\n#include <kj/string.h>\n\nnamespace workerd::server {\n\nclass JsonLogger: public kj::ExceptionCallback {\n public:\n  void logMessage(kj::LogSeverity severity,\n      const char* file,\n      int line,\n      int contextDepth,\n      kj::String&& text) override;\n\n  kj::Function<void(kj::Function<void()>)> getThreadInitializer() override;\n\n  StackTraceMode stackTraceMode() override {\n    return StackTraceMode::ADDRESS_ONLY;\n  }\n\n private:\n  bool loggingInProgress = false;\n};\n\nclass StructuredLoggingProcessContext final: public kj::ProcessContext {\n  // A ProcessContext implementation that supports both plain text and structured JSON logging.\n  // This context wraps TopLevelProcessContext and adds the ability to emit log messages in\n  // JSON format when structured logging is enabled.\n\n public:\n  explicit StructuredLoggingProcessContext(kj::StringPtr programName);\n\n  // Enable structured JSON logging. This can only be called once and cannot be reversed.\n  // When enabled: Log messages are formatted as JSON and sent to stdout or stderr\n  //               This also enables an ExceptionCallback to replace KJ_LOGs with structured logs.\n  //               To reduce code duplication from TopLevelProcessContext, while JsonLogger sends\n  //               all logs to stdout, StructuredLoggingProcessContext sends all to the fd that\n  //               TopLevelProcessContext would have sent to.\n  // When disabled: Log messages are sent as plain text to stdout or stderr (like\n  //                TopLevelProcessContext)\n  void enableStructuredLogging();\n\n  kj::StringPtr getProgramName() override;\n  KJ_NORETURN(void exit() override);\n  void warning(kj::StringPtr message) const override;\n  void error(kj::StringPtr message) const override;\n  KJ_NORETURN(void exitError(kj::StringPtr message) override);\n  KJ_NORETURN(void exitInfo(kj::StringPtr message) override);\n  void increaseLoggingVerbosity() override;\n\n private:\n  kj::TopLevelProcessContext topLevelContext;\n  kj::Maybe<JsonLogger> jsonLogger;\n  bool useStructuredLogging = false;\n};\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/log-schema.capnp",
    "content": "# Copyright (c) 2017-2022 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n@0x9f8a7b6c5d4e3f2a;\nusing Cxx = import \"/capnp/c++.capnp\";\nusing Json = import \"/capnp/compat/json.capnp\";\n\n$Cxx.namespace(\"workerd::server::log_schema\");\n$Cxx.allowCancellation;\n\nstruct LogEntry {\n  # Structured log entry for workerd JSON logging\n\n  timestamp @0 :UInt64 $Json.name(\"timestamp\");\n  # Unix timestamp in milliseconds when the log was generated\n\n  level @1 :LogLevel $Json.name(\"level\");\n  # Severity level of the log message\n\n  source @2 :Text $Json.name(\"source\");\n  # Source file and line number where the log originated (e.g., \"server.c++:123\")\n\n  message @3 :Text $Json.name(\"message\");\n  # The actual log message content\n\n  contextDepth @4 :UInt32 $Json.name(\"context_depth\");\n  # Context depth for nested operations (optional, only included if > 0)\n\n  enum LogLevel {\n    debug @0 $Cxx.name(\"debug_\") $Json.name(\"debug\");\n    # in C++, the constant will be DEBUG_ to avoid clashing with the DEBUG macro\n    info @1 $Json.name(\"info\");\n    warning @2 $Json.name(\"warning\");\n    error @3 $Json.name(\"error\");\n    fatal @4 $Json.name(\"fatal\");\n  }\n}\n"
  },
  {
    "path": "src/workerd/server/pyodide.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#include \"pyodide.h\"\n\n#include <workerd/api/pyodide/pyodide.h>\n\n#include <kj/array.h>\n#include <kj/common.h>\n#include <kj/compat/gzip.h>\n#include <kj/compat/tls.h>\n#include <kj/debug.h>\n#include <kj/string.h>\n\nnamespace workerd::server {\n\n// Helper functions for bundle file operations\nkj::Path getPyodideBundleFileName(kj::StringPtr version) {\n  return kj::Path(kj::str(\"pyodide_\", version, \".capnp.bin\"));\n}\n\nkj::Maybe<kj::Own<const kj::ReadableFile>> getPyodideBundleFile(\n    const kj::Maybe<kj::Own<const kj::Directory>>& maybeDir, kj::StringPtr version) {\n  KJ_IF_SOME(dir, maybeDir) {\n    kj::Path filename = getPyodideBundleFileName(version);\n    auto file = dir->tryOpenFile(filename);\n\n    return file;\n  }\n\n  return kj::none;\n}\n\nvoid writePyodideBundleFileToDisk(const kj::Maybe<kj::Own<const kj::Directory>>& maybeDir,\n    kj::StringPtr version,\n    kj::ArrayPtr<byte> bytes) {\n  KJ_IF_SOME(dir, maybeDir) {\n    kj::Path filename = getPyodideBundleFileName(version);\n    auto replacer = dir->replaceFile(filename, kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n    replacer->get().writeAll(bytes);\n    replacer->commit();\n  }\n}\n\n// Used to preload the Pyodide bundle during workerd startup\nkj::Promise<kj::Maybe<jsg::Bundle::Reader>> fetchPyodideBundle(\n    const api::pyodide::PythonConfig& pyConfig,\n    kj::String version,\n    kj::Network& network,\n    kj::Timer& timer) {\n  if (pyConfig.pyodideBundleManager.getPyodideBundle(version) != kj::none) {\n    co_return pyConfig.pyodideBundleManager.getPyodideBundle(version);\n  }\n\n  auto maybePyodideBundleFile = getPyodideBundleFile(pyConfig.pyodideDiskCacheRoot, version);\n  KJ_IF_SOME(pyodideBundleFile, maybePyodideBundleFile) {\n    auto body = pyodideBundleFile->readAllBytes();\n    pyConfig.pyodideBundleManager.setPyodideBundleData(kj::str(version), kj::mv(body));\n    co_return pyConfig.pyodideBundleManager.getPyodideBundle(version);\n  }\n\n  if (version == \"dev\") {\n    // the \"dev\" version is special and indicates we're using the tip-of-tree version built for testing\n    // so we shouldn't fetch it from the internet, only check for its existence in the disk cache\n    co_return kj::none;\n  }\n\n  kj::String url =\n      kj::str(\"https://pyodide-capnp-bin.edgeworker.net/pyodide_\", version, \".capnp.bin\");\n  KJ_LOG(INFO, \"Loading Pyodide bundle from internet\", url);\n  kj::HttpHeaderTable table;\n\n  kj::TlsContext::Options options;\n  options.useSystemTrustStore = true;\n\n  kj::Own<kj::TlsContext> tls = kj::heap<kj::TlsContext>(kj::mv(options));\n  auto tlsNetwork = tls->wrapNetwork(network);\n  auto client = kj::newHttpClient(timer, table, network, *tlsNetwork);\n\n  kj::HttpHeaders headers(table);\n\n  auto req = client->request(kj::HttpMethod::GET, url.asPtr(), headers);\n\n  auto res = co_await req.response;\n  KJ_ASSERT(res.statusCode == 200, \"Request for Pyodide bundle failed\", url);\n  auto body = co_await res.body->readAllBytes();\n\n  writePyodideBundleFileToDisk(pyConfig.pyodideDiskCacheRoot, version, body);\n\n  pyConfig.pyodideBundleManager.setPyodideBundleData(kj::str(version), kj::mv(body));\n\n  co_return pyConfig.pyodideBundleManager.getPyodideBundle(version);\n}\n\n// Downloads a package with retry logic (up to 3 attempts with 5-second delays)\nkj::Promise<kj::Maybe<kj::Array<byte>>> downloadPackageWithRetry(kj::HttpClient& client,\n    kj::Timer& timer,\n    kj::HttpHeaderTable& headerTable,\n    kj::StringPtr url,\n    kj::StringPtr path) {\n  constexpr uint retryLimit = 3;\n  kj::HttpHeaders headers(headerTable);\n\n  for (uint retryCount = 0; retryCount < retryLimit; ++retryCount) {\n    if (retryCount > 0) {\n      // Sleep for 5 seconds before retrying\n      co_await timer.afterDelay(5 * kj::SECONDS);\n      KJ_LOG(INFO, \"Retrying package download\", path, \"attempt\", retryCount + 1, \"of\", retryLimit);\n    }\n\n    try {\n      auto req = client.request(kj::HttpMethod::GET, url, headers);\n      auto res = co_await req.response;\n\n      if (res.statusCode != 200) {\n        KJ_LOG(WARNING, \"Failed to download package\", path, res.statusCode, \"attempt\",\n            retryCount + 1, \"of\", retryLimit);\n        continue;  // Try again in the next iteration\n      }\n\n      // Request succeeded, read the body\n      co_return co_await res.body->readAllBytes();\n    } catch (kj::Exception& e) {\n      if (retryCount + 1 >= retryLimit) {\n        // This was our last attempt\n        KJ_LOG(WARNING, \"Failed to download package after all retry attempts\", path, e, \"attempts\",\n            retryLimit);\n      } else {\n        KJ_LOG(WARNING, \"Failed to download package\", path, e, \"attempt\", retryCount + 1, \"of\",\n            retryLimit, \"will retry\");\n      }\n    }\n  }\n\n  co_return kj::none;  // All retry attempts failed\n}\n\n// Loads a single Python package, either from disk cache or by downloading it\nkj::Promise<void> loadPyodidePackage(const api::pyodide::PythonConfig& pyConfig,\n    const api::pyodide::PyodidePackageManager& pyodidePackageManager,\n    kj::StringPtr packagesVersion,\n    kj::StringPtr filename,\n    kj::Network& network,\n    kj::Timer& timer) {\n\n  auto path = kj::str(\"python-package-bucket/\", packagesVersion, \"/\", filename);\n  // First check if we already have this package in memory\n  if (pyodidePackageManager.getPyodidePackage(path) != kj::none) {\n    co_return;\n  }\n\n  // Then check disk cache\n  KJ_IF_SOME(diskCachePath, pyConfig.packageDiskCacheRoot) {\n    auto parsedPath = kj::Path::parse(filename);\n    if (diskCachePath->exists(parsedPath)) {\n      try {\n        auto file = diskCachePath->openFile(parsedPath);\n        auto blob = file->readAllBytes();\n\n        // Decompress the package\n        kj::ArrayInputStream ais(blob);\n        kj::GzipInputStream gzip(ais);\n        auto decompressed = gzip.readAllBytes();\n\n        // Store in memory\n        pyodidePackageManager.setPyodidePackageData(kj::str(path), kj::mv(decompressed));\n        co_return;\n      } catch (kj::Exception& e) {\n        // Something went wrong while reading or processing the file\n        KJ_LOG(WARNING, \"Failed to read or process package from disk cache\", path, e);\n      }\n    }\n  }\n\n  // Need to fetch from network\n  kj::HttpHeaderTable table;\n  kj::TlsContext::Options tlsOptions;\n  tlsOptions.useSystemTrustStore = true;\n  kj::Own<kj::TlsContext> tlsContext = kj::heap<kj::TlsContext>(kj::mv(tlsOptions));\n\n  auto tlsNetwork = tlsContext->wrapNetwork(network);\n  auto client = kj::newHttpClient(timer, table, network, *tlsNetwork);\n\n  kj::String url = kj::str(api::pyodide::PYTHON_PACKAGES_URL, path);\n\n  auto maybeBody = co_await downloadPackageWithRetry(*client, timer, table, url, path);\n  KJ_IF_SOME(body, maybeBody) {\n    // Successfully downloaded the package\n    // Save the compressed data to disk cache (if enabled)\n    KJ_IF_SOME(diskCachePath, pyConfig.packageDiskCacheRoot) {\n      try {\n        auto parsedPath = kj::Path::parse(path);\n        auto file = diskCachePath->openFile(parsedPath,\n            kj::WriteMode::CREATE | kj::WriteMode::MODIFY | kj::WriteMode::CREATE_PARENT);\n        file->writeAll(body);\n      } catch (kj::Exception& e) {\n        KJ_LOG(WARNING, \"Failed to write package to disk cache\", e);\n      }\n    }\n\n    // Now decompress and store in memory\n    kj::ArrayInputStream ais(body);\n    kj::GzipInputStream gzip(ais);\n    auto decompressed = gzip.readAllBytes();\n\n    pyodidePackageManager.setPyodidePackageData(kj::str(path), kj::mv(decompressed));\n  } else {\n    KJ_FAIL_ASSERT(\"Failed to download package after all retry attempts\", path);\n  }\n\n  co_return;\n}\n\nkj::Promise<void> fetchPyodidePackages(const api::pyodide::PythonConfig& pyConfig,\n    const api::pyodide::PyodidePackageManager& pyodidePackageManager,\n    kj::ArrayPtr<kj::String> pythonRequirements,\n    workerd::PythonSnapshotRelease::Reader pythonSnapshotRelease,\n    kj::Network& network,\n    kj::Timer& timer) {\n  auto packagesVersion = pythonSnapshotRelease.getPackages();\n\n  auto pyodideLock = api::pyodide::getPyodideLock(pythonSnapshotRelease);\n  if (pyodideLock == kj::none) {\n    KJ_LOG(WARNING, \"No lock file found for Python packages version\", packagesVersion);\n    co_return;\n  }\n\n  auto filenames = api::pyodide::getPythonPackageFiles(\n      KJ_ASSERT_NONNULL(pyodideLock), pythonRequirements, packagesVersion);\n\n  kj::Vector<kj::Promise<void>> promises(filenames.size());\n  for (const auto& filename: filenames) {\n    promises.add(loadPyodidePackage(\n        pyConfig, pyodidePackageManager, packagesVersion, filename, network, timer));\n  }\n\n  co_await kj::joinPromisesFailFast(promises.releaseAsArray());\n}\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/pyodide.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <workerd/api/pyodide/pyodide.h>\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/jsg/jsg.h>\n\n#include <kj/array.h>\n#include <kj/compat/http.h>\n#include <kj/filesystem.h>\n#include <kj/string.h>\n#include <kj/timer.h>\n\nnamespace workerd::server {\n\n// Used to preload the Pyodide bundle during workerd startup\nkj::Promise<kj::Maybe<jsg::Bundle::Reader>> fetchPyodideBundle(\n    const api::pyodide::PythonConfig& pyConfig,\n    kj::String version,\n    kj::Network& network,\n    kj::Timer& timer);\n\n// Preloads all required Python packages for a worker\nkj::Promise<void> fetchPyodidePackages(const api::pyodide::PythonConfig& pyConfig,\n    const api::pyodide::PyodidePackageManager& pyodidePackageManager,\n    kj::ArrayPtr<kj::String> pythonRequirements,\n    workerd::PythonSnapshotRelease::Reader pythonSnapshotRelease,\n    kj::Network& network,\n    kj::Timer& timer);\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/server-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"server.h\"\n\n#include <workerd/jsg/jsg-test.h>\n#include <workerd/jsg/setup.h>\n#include <workerd/util/autogate.h>\n#include <workerd/util/capnp-mock.h>\n\n#include <capnp/compat/http-over-capnp.h>\n#include <capnp/rpc-twoparty.h>\n#include <kj/async-queue.h>\n#include <kj/encoding.h>\n#include <kj/test.h>\n\n#include <cstdlib>\n#include <regex>\n\n#if __linux__\n#include <unistd.h>\n#endif\n\nnamespace workerd::server {\nnamespace {\n\n#define KJ_FAIL_EXPECT_AT(location, ...) KJ_LOG_AT(ERROR, location, ##__VA_ARGS__);\n#define KJ_EXPECT_AT(cond, location, ...)                                                          \\\n  if (auto _kjCondition = ::kj::_::MAGIC_ASSERT << cond)                                           \\\n    ;                                                                                              \\\n  else                                                                                             \\\n    KJ_FAIL_EXPECT_AT(location, \"failed: expected \" #cond, _kjCondition, ##__VA_ARGS__)\n\njsg::V8System v8System;\n// This can only be created once per process, so we have to put it at the top level.\n\nconst bool verboseLog = ([]() {\n  // TODO(beta): Improve uncaught exception reporting so that we don't have to do this.\n  kj::_::Debug::setLogLevel(kj::LogSeverity::INFO);\n  return true;\n})();\n\nkj::Own<config::Config::Reader> parseConfig(kj::StringPtr text, kj::SourceLocation loc) {\n  capnp::MallocMessageBuilder builder;\n  auto root = builder.initRoot<config::Config>();\n  KJ_IF_SOME(exception, kj::runCatchingExceptions([&]() { TEXT_CODEC.decode(text, root); })) {\n    KJ_FAIL_REQUIRE_AT(loc, exception);\n  }\n\n  util::Autogate::initAutogate(root.asReader().getAutogates());\n\n  return capnp::clone(root.asReader());\n}\n\n// Accept an indented block of text and remove the indentation. From each line of text, this will\n// remove a number of spaces up to the indentation of the first line.\n//\n// This is intended to allow multi-line raw text to be specified conveniently using C++11\n// `R\"(blah)\"` literal syntax, without the need to mess up indentation relative to the\n// surrounding code.\nkj::String operator\"\"_blockquote(const char* str, size_t n) {\n  kj::StringPtr text(str, n);\n\n  // Ignore a leading newline so that `R\"(` can be placed on the line before the initial indent.\n  if (text.startsWith(\"\\n\")) {\n    text = text.slice(1);\n  }\n\n  // Count indent size.\n  size_t indent = 0;\n  while (text.startsWith(\" \")) {\n    text = text.slice(1);\n    ++indent;\n  }\n\n  // Process lines.\n  kj::Vector<char> result;\n  while (text != nullptr) {\n    // Add data from this line.\n    auto nl = text.findFirst('\\n').orDefault(text.size() - 1) + 1;\n    result.addAll(text.first(nl));\n    text = text.slice(nl);\n\n    // Skip indent of next line, up to the expected indent size.\n    size_t seenIndent = 0;\n    while (seenIndent < indent && text.startsWith(\" \")) {\n      text = text.slice(1);\n      ++seenIndent;\n    }\n  }\n\n  result.add('\\0');\n  return kj::String(result.releaseAsArray());\n}\n\nclass TestStream {\n public:\n  TestStream(kj::WaitScope& ws, kj::Own<kj::AsyncIoStream> stream)\n      : ws(ws),\n        stream(kj::mv(stream)) {}\n\n  void send(kj::StringPtr data, kj::SourceLocation loc = {}) {\n    stream->write(data.asBytes()).wait(ws);\n  }\n  void recv(kj::StringPtr expected, kj::SourceLocation loc = {}) {\n    auto actual = readAllAvailable();\n    if (actual == nullptr) {\n      KJ_FAIL_EXPECT_AT(loc, \"message never received\");\n    } else {\n      KJ_EXPECT_AT(actual == expected, loc);\n    }\n  }\n  void recvRegex(kj::StringPtr matcher, kj::SourceLocation loc = {}) {\n    auto actual = readAllAvailable();\n    if (actual == nullptr) {\n      KJ_FAIL_EXPECT_AT(loc, \"message never received\");\n    } else {\n      std::regex target(matcher.cStr());\n      KJ_EXPECT(std::regex_match(actual.cStr(), target), actual, matcher, loc);\n    }\n  }\n\n  void recvWebSocket(kj::StringPtr expected, kj::SourceLocation loc = {}) {\n    auto actual = readWebSocketMessage();\n    KJ_EXPECT_AT(actual == expected, loc);\n  }\n\n  void recvWebSocketRegex(kj::StringPtr matcher, kj::SourceLocation loc = {}) {\n    auto actual = readWebSocketMessage();\n    std::regex target(matcher.cStr());\n    KJ_EXPECT(std::regex_match(actual.cStr(), target), actual, matcher, loc);\n  }\n\n  void recvWebSocketClose(int expectedCode) {\n    auto actual = readWebSocketMessage();\n    KJ_EXPECT(actual.size() >= 2);\n    int gotCode = (static_cast<uint8_t>(actual[0]) << 8) + static_cast<uint8_t>(actual[1]);\n    KJ_EXPECT(gotCode == expectedCode);\n  }\n\n  void sendHttpGet(kj::StringPtr path, kj::SourceLocation loc = {}) {\n    send(kj::str(\"GET \", path,\n             \" HTTP/1.1\\n\"\n             \"Host: foo\\n\"\n             \"\\n\"),\n        loc);\n  }\n\n  void recvHttp200(kj::StringPtr expectedResponse, kj::SourceLocation loc = {}) {\n    recv(kj::str(\"HTTP/1.1 200 OK\\n\"\n                 \"Content-Length: \",\n             expectedResponse.size(),\n             \"\\n\"\n             \"Content-Type: text/plain;charset=UTF-8\\n\"\n             \"\\n\",\n             expectedResponse),\n        loc);\n  }\n\n  void httpGet200(kj::StringPtr path, kj::StringPtr expectedResponse, kj::SourceLocation loc = {}) {\n    sendHttpGet(path, loc);\n    recvHttp200(expectedResponse, loc);\n  }\n\n  // Return true if the stream is at EOF.\n  bool isEof() {\n    if (premature != kj::none) {\n      // We still have unread data so we're definitely not at EOF.\n      return false;\n    }\n\n    char c;\n    auto promise = stream->tryRead(&c, 1, 1);\n    if (!promise.poll(ws)) {\n      // Read didn't complete immediately. We have no data available, but we're not at EOF.\n      return false;\n    }\n\n    size_t n = promise.wait(ws);\n    if (n == 0) {\n      return true;\n    } else {\n      // Oops, the stream had data available and we accidentally read a byte of it. Store that off\n      // to the side.\n      KJ_ASSERT(n == 1);\n      premature = c;\n      return false;\n    }\n  }\n\n  void upgradeToWebSocket() {\n    send(R\"(\n      GET / HTTP/1.1\n      Host: foo\n      Upgrade: websocket\n      Sec-WebSocket-Key: AAAAAAAAAAAAAAAAAAAAAA==\n      Sec-WebSocket-Version: 13\n\n    )\"_blockquote,\n        {});\n\n    recv(R\"(\n      HTTP/1.1 101 Switching Protocols\n      Connection: Upgrade\n      Upgrade: websocket\n      Sec-WebSocket-Accept: ICX+Yqv66kxgM0FcWaLWlFLwTAI=\n\n    )\"_blockquote,\n        {});\n  }\n\n  kj::AsyncIoStream& getStream() {\n    return *stream;\n  }\n\n private:\n  kj::WaitScope& ws;\n  kj::Own<kj::AsyncIoStream> stream;\n\n  // isEof() may prematurely read a character. Keep it off to the side for the next actual read.\n  kj::Maybe<char> premature;\n\n  kj::String readAllAvailable() {\n    kj::Vector<char> buffer(256);\n    KJ_IF_SOME(p, premature) {\n      buffer.add(p);\n    }\n\n    // Continuously try to read until there's nothing to read (or we've gone way past the size\n    // expected).\n    for (;;) {\n      size_t pos = buffer.size();\n      buffer.resize(kj::max(buffer.size() + 256, buffer.capacity()));\n\n      auto promise = stream->tryRead(buffer.begin() + pos, 1, buffer.size() - pos);\n      if (!promise.poll(ws)) {\n        // A tryRead() of 1 byte didn't resolve, there must be no data to read.\n        buffer.resize(pos);\n        break;\n      }\n      size_t n = promise.wait(ws);\n      if (n == 0) {\n        buffer.resize(pos);\n        break;\n      }\n\n      // Strip out `\\r`s for convenience. We do this in-place...\n      for (size_t i: kj::range(pos, pos + n)) {\n        if (buffer[i] != '\\r') {\n          buffer[pos++] = buffer[i];\n        }\n      }\n      buffer.resize(pos);\n    };\n\n    buffer.add('\\0');\n    return kj::String(buffer.releaseAsArray());\n  }\n\n  kj::String readWebSocketMessage(size_t maxMessageSize = 1 << 24) {\n    // Reads a single, non-fragmented WebSocket message. Returns just the payload.\n    kj::Vector<uint8_t> header(256);\n    kj::Vector<uint8_t> mask(4);\n\n    KJ_IF_SOME(p, premature) {\n      header.add(p);\n      premature = kj::Maybe<char>();\n    }\n\n    tryRead(header, 2 - header.size(), \"reading first two bytes of header\");\n    bool masked = header[1] & 0x80;\n    size_t sevenBitPayloadLength = header[1] & 0x7f;\n    size_t realPayloadLength = sevenBitPayloadLength;\n\n    if (sevenBitPayloadLength == 126) {\n      tryRead(header, 2, \"reading 16-bit payload length\");\n      realPayloadLength = (static_cast<size_t>(header[2]) << 8) + static_cast<size_t>(header[3]);\n    } else if (sevenBitPayloadLength == 127) {\n      tryRead(header, 8, \"reading 64-bit payload length\");\n      realPayloadLength = (static_cast<size_t>(header[2]) << 56) +\n          (static_cast<size_t>(header[3]) << 48) + (static_cast<size_t>(header[4]) << 40) +\n          (static_cast<size_t>(header[5]) << 32) + (static_cast<size_t>(header[6]) << 24) +\n          (static_cast<size_t>(header[7]) << 16) + (static_cast<size_t>(header[8]) << 8) +\n          (static_cast<size_t>(header[9]));\n\n      KJ_REQUIRE(realPayloadLength <= maxMessageSize,\n          kj::str(\"Payload size too big (\", realPayloadLength, \" > \", maxMessageSize, \")\"));\n    }\n\n    if (masked) {\n      tryRead(mask, 4, \"reading mask key\");\n      // Currently we assume the mask is always 0, so its application is a no-op, hence we don't\n      // bother.\n    }\n    kj::Vector<char> payload(realPayloadLength + 1);\n\n    tryRead(payload, realPayloadLength, \"reading payload\");\n    payload.add('\\0');\n    return kj::String(payload.releaseAsArray());\n  }\n\n  template <typename T>\n  void tryRead(kj::Vector<T>& buffer, size_t bytesToRead, kj::StringPtr what) {\n    static_assert(sizeof(T) == 1, \"not byte-sized\");\n\n    size_t pos = buffer.size();\n    size_t bytesRead = 0;\n    buffer.resize(buffer.size() + bytesToRead);\n    while (bytesRead < bytesToRead) {\n      auto promise = stream->tryRead(buffer.begin() + pos, 1, buffer.size() - pos);\n      KJ_REQUIRE(promise.poll(ws), kj::str(\"No data available while \", what));\n      // A tryRead() of 1 byte didn't resolve, there must be no data to read.\n\n      size_t n = promise.wait(ws);\n      KJ_REQUIRE(n > 0, kj::str(\"Not enough data while \", what));\n      bytesRead += n;\n    }\n  }\n};\n\nclass TestServer final: private kj::Filesystem, private kj::EntropySource, private kj::Clock {\n public:\n  TestServer(kj::StringPtr configText,\n      Worker::ConsoleMode consoleMode = Worker::ConsoleMode::INSPECTOR_ONLY,\n      kj::SourceLocation loc = {})\n      : ws(loop),\n        config(parseConfig(configText, loc)),\n        root(kj::newInMemoryDirectory(*this)),\n        pwd(kj::Path({\"current\", \"dir\"})),\n        cwd(root->openSubdir(pwd, kj::WriteMode::CREATE | kj::WriteMode::CREATE_PARENT)),\n        timer(kj::origin<kj::TimePoint>()),\n        server(*this,\n            timer,\n            timer,\n            mockNetwork,\n            *this,\n            Worker::LoggingOptions(consoleMode),\n            [this](kj::String error) {\n              if (expectedErrors.startsWith(error) && expectedErrors[error.size()] == '\\n') {\n                expectedErrors = expectedErrors.slice(error.size() + 1);\n              } else {\n                KJ_FAIL_EXPECT(error, expectedErrors);\n              }\n            }),\n        fakeDate(kj::UNIX_EPOCH),\n        mockNetwork(*this, {}, {}) {}\n\n  ~TestServer() noexcept(false) {\n    for (auto& subq: subrequests) {\n      subq.value->rejectAll(KJ_EXCEPTION(FAILED, \"test ended\"));\n    }\n\n    if (!unwindDetector.isUnwinding()) {\n      // Make sure any errors are reported.\n      KJ_IF_SOME(t, runTask) {\n        t.poll(ws);\n      }\n    }\n  }\n\n  // Start the server. Call before connect().\n  void start(kj::Promise<void> drainWhen = kj::NEVER_DONE) {\n    KJ_REQUIRE(runTask == kj::none);\n    auto task =\n        server.run(v8System, *config, kj::mv(drainWhen)).eagerlyEvaluate([](kj::Exception&& e) {\n      KJ_FAIL_EXPECT(e);\n    });\n    KJ_EXPECT(!task.poll(ws));\n    runTask = kj::mv(task);\n  }\n\n  // Call instead of `start()` when the config is expected to produce errors. The parameter is\n  // the expected list of errors messages, one per line.\n  void expectErrors(kj::StringPtr expected) {\n    expectedErrors = expected;\n    server.run(v8System, *config).poll(ws);\n    KJ_EXPECT(expectedErrors == nullptr, \"some expected errors weren't seen\");\n  }\n\n  // Connect to the server on the given address. The string just has to match what is in the\n  // config; the actual connection is in-memory with no network involved.\n  TestStream connect(kj::StringPtr addr) {\n    return TestStream(ws, KJ_REQUIRE_NONNULL(sockets.find(addr), addr)->connect().wait(ws));\n  }\n\n  // Try to connect to the address and return whether or not this connection attempt hangs,\n  // i.e. a listener exists but connections are not being accepted.\n  bool connectHangs(kj::StringPtr addr) {\n    return !KJ_REQUIRE_NONNULL(sockets.find(addr), addr)->connect().poll(ws);\n  }\n\n  // Expect an incoming connection on the given address and from a network with the given\n  // allowed / denied peer list.\n  TestStream receiveSubrequest(kj::StringPtr addr,\n      kj::ArrayPtr<const kj::StringPtr> allowedPeers = nullptr,\n      kj::ArrayPtr<const kj::StringPtr> deniedPeers = nullptr,\n      kj::SourceLocation loc = {}) {\n    auto expectedFilter = peerFilterToString(allowedPeers, deniedPeers);\n\n    auto promise = getSubrequestQueue(addr).pop();\n    KJ_ASSERT_AT(promise.poll(ws), loc, \"never received expected subrequest\", addr);\n\n    auto info = promise.wait(ws);\n    auto actualFilter = info.peerFilter;\n    KJ_EXPECT_AT(actualFilter == expectedFilter, loc);\n\n    auto pipe = kj::newTwoWayPipe();\n    info.fulfiller->fulfill(kj::mv(pipe.ends[0]));\n    return TestStream(ws, kj::mv(pipe.ends[1]));\n  }\n\n  TestStream receiveInternetSubrequest(kj::StringPtr addr, kj::SourceLocation loc = {}) {\n    return receiveSubrequest(addr, {\"public\"_kj}, {}, loc);\n  }\n\n  // Advance the timer through `seconds` seconds of virtual time.\n  void wait(size_t seconds) {\n    auto delayPromise = timer.afterDelay(seconds * kj::SECONDS).eagerlyEvaluate(nullptr);\n    while (!delayPromise.poll(ws)) {\n      // Since this test has no external I/O at all other than time, we know no events could\n      // possibly occur until the next timer event. So just advance directly to it and continue.\n      timer.advanceTo(KJ_ASSERT_NONNULL(timer.nextEvent()));\n    }\n    delayPromise.wait(ws);\n  }\n\n  kj::WaitScope& getWaitScope() {\n    return ws;\n  }\n\n  kj::EventLoop loop;\n  kj::WaitScope ws;\n\n  kj::Own<config::Config::Reader> config;\n  kj::Own<const kj::Directory> root;\n  kj::Path pwd;\n  kj::Own<const kj::Directory> cwd;\n  kj::TimerImpl timer;\n  Server server;\n\n  kj::Maybe<kj::Promise<void>> runTask;\n  kj::StringPtr expectedErrors;\n\n  kj::Date fakeDate;\n\n private:\n  kj::UnwindDetector unwindDetector;\n\n  // ---------------------------------------------------------------------------\n  // implements Filesystem\n\n  const kj::Directory& getRoot() const override {\n    return *root;\n  }\n  const kj::Directory& getCurrent() const override {\n    return *cwd;\n  }\n  kj::PathPtr getCurrentPath() const override {\n    return pwd;\n  }\n\n  // ---------------------------------------------------------------------------\n  // implements Network\n\n  // Addresses that the server is listening on.\n  kj::HashMap<kj::String, kj::Own<kj::NetworkAddress>> sockets;\n\n  class MockNetwork;\n\n  struct SubrequestInfo {\n    kj::Own<kj::PromiseFulfiller<kj::Own<kj::AsyncIoStream>>> fulfiller;\n    kj::StringPtr peerFilter;\n  };\n  using SubrequestQueue = kj::ProducerConsumerQueue<SubrequestInfo>;\n  // Expected incoming connections and callbacks that should be used to handle them.\n  kj::HashMap<kj::String, kj::Own<SubrequestQueue>> subrequests;\n\n  SubrequestQueue& getSubrequestQueue(kj::StringPtr addr) {\n    return *subrequests.findOrCreate(addr, [&]() -> decltype(subrequests)::Entry {\n      return {kj::str(addr), kj::heap<SubrequestQueue>()};\n    });\n  }\n\n  static kj::String peerFilterToString(\n      kj::ArrayPtr<const kj::StringPtr> allow, kj::ArrayPtr<const kj::StringPtr> deny) {\n    if (allow == nullptr && deny == nullptr) {\n      return kj::str(\"(none)\");\n    } else {\n      return kj::str(\"allow: [\", kj::strArray(allow, \", \"),\n          \"], \"\n          \"deny: [\",\n          kj::strArray(deny, \", \"), \"]\");\n    }\n  }\n\n  class MockAddress final: public kj::NetworkAddress {\n   public:\n    MockAddress(TestServer& test, kj::StringPtr peerFilter, kj::String address)\n        : test(test),\n          peerFilter(peerFilter),\n          address(kj::mv(address)) {}\n\n    kj::Promise<kj::Own<kj::AsyncIoStream>> connect() override {\n      KJ_IF_SOME(addr, test.sockets.find(address)) {\n        // If someone is listening on this address, connect directly to them.\n        return addr->connect();\n      }\n\n      auto [promise, fulfiller] = kj::newPromiseAndFulfiller<kj::Own<kj::AsyncIoStream>>();\n\n      test.getSubrequestQueue(address).push({kj::mv(fulfiller), peerFilter});\n\n      return kj::mv(promise);\n    }\n    kj::Own<kj::ConnectionReceiver> listen() override {\n      auto pipe = kj::newCapabilityPipe();\n      auto receiver = kj::heap<kj::CapabilityStreamConnectionReceiver>(*pipe.ends[0])\n                          .attach(kj::mv(pipe.ends[0]));\n      auto sender = kj::heap<kj::CapabilityStreamNetworkAddress>(kj::none, *pipe.ends[1])\n                        .attach(kj::mv(pipe.ends[1]));\n      test.sockets.insert(kj::str(address), kj::mv(sender));\n      return receiver;\n    }\n    kj::Own<kj::NetworkAddress> clone() override {\n      KJ_UNIMPLEMENTED(\"unused\");\n    }\n    kj::String toString() override {\n      KJ_UNIMPLEMENTED(\"unused\");\n    }\n\n   private:\n    TestServer& test;\n    kj::StringPtr peerFilter;\n    kj::String address;\n  };\n\n  class MockNetwork final: public kj::Network {\n   public:\n    MockNetwork(TestServer& test,\n        kj::ArrayPtr<const kj::StringPtr> allow,\n        kj::ArrayPtr<const kj::StringPtr> deny)\n        : test(test),\n          filter(peerFilterToString(allow, deny)) {}\n\n    kj::Promise<kj::Own<kj::NetworkAddress>> parseAddress(\n        kj::StringPtr addr, uint portHint = 0) override {\n      return kj::Own<kj::NetworkAddress>(kj::heap<MockAddress>(test, filter, kj::str(addr)));\n    }\n    kj::Own<kj::NetworkAddress> getSockaddr(const void* sockaddr, uint len) override {\n      KJ_UNIMPLEMENTED(\"unused\");\n    }\n    kj::Own<kj::Network> restrictPeers(\n        kj::ArrayPtr<const kj::StringPtr> allow, kj::ArrayPtr<const kj::StringPtr> deny) override {\n      KJ_ASSERT(filter == \"(none)\", \"can't nest restrictPeers()\");\n      return kj::heap<MockNetwork>(test, allow, deny);\n    }\n\n   private:\n    TestServer& test;\n    kj::String filter;\n  };\n\n  MockNetwork mockNetwork;\n\n  // ---------------------------------------------------------------------------\n  // implements EntropySource\n\n  void generate(kj::ArrayPtr<kj::byte> buffer) override {\n    kj::byte random = 4;  // chosen by fair die roll by Randall Munroe in 2007.\n                          // guaranteed to be random.\n    buffer.fill(random);\n  }\n\n  // ---------------------------------------------------------------------------\n  // implements Clock\n\n  kj::Date now() const override {\n    return fakeDate;\n  }\n};\n\n// =======================================================================================\n// Test Workers\n\nkj::String singleWorker(kj::StringPtr def) {\n  return kj::str(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = )\"_kj,\n      def, R\"(\n      )\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n}\n\nKJ_TEST(\"Server: serve basic Service Worker\") {\n  TestServer test(singleWorker(R\"((\n    compatibilityDate = \"2022-08-17\",\n    serviceWorkerScript =\n        `addEventListener(\"fetch\", event => {\n        `  event.respondWith(new Response(\"Hello: \" + event.request.url + \"\\n\"));\n        `})\n  ))\"_kj));\n\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n\n  // Send a request, get a response.\n  conn.httpGet200(\"/\", \"Hello: http://foo/\\n\");\n\n  // Send another request on the same connection, different path and host.\n  conn.send(R\"(\n    GET /baz/qux?corge=grault HTTP/1.1\n    Host: bar\n\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 39\n    Content-Type: text/plain;charset=UTF-8\n\n    Hello: http://bar/baz/qux?corge=grault\n  )\"_blockquote);\n\n  // A request without `Host:` should 400.\n  conn.send(R\"(\n    GET /baz/qux?corge=grault HTTP/1.1\n\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 400 Bad Request\n    Content-Length: 11\n\n    Bad Request)\"_blockquote);\n}\n\nKJ_TEST(\"Server: use service name as Service Worker origin\") {\n  TestServer test(singleWorker(R\"((\n    compatibilityDate = \"2022-08-17\",\n    serviceWorkerScript =\n        `addEventListener(\"fetch\", event => {\n        `  event.respondWith(new Response(new Error(\"Doh!\").stack));\n        `})\n  ))\"_kj));\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\", R\"(\n    Error: Doh!\n        at hello:2:34)\"_blockquote);\n}\n\nKJ_TEST(\"Server: serve basic modular Worker\") {\n  TestServer test(singleWorker(R\"((\n    compatibilityDate = \"2022-08-17\",\n    modules = [\n      ( name = \"main.js\",\n        esModule =\n          `export default {\n          `  async fetch(request) {\n          `    return new Response(\"Hello: \" + request.url);\n          `  }\n          `}\n      )\n    ]\n  ))\"_kj));\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\", \"Hello: http://foo/\");\n}\n\nKJ_TEST(\"Server: serve modular Worker with imports\") {\n  TestServer test(singleWorker(R\"((\n    compatibilityDate = \"2022-08-17\",\n    modules = [\n      ( name = \"main.js\",\n        esModule =\n          `import { MESSAGE as FOO } from \"foo.js\";\n          `import BAR from \"bar.txt\";\n          `import BAZ from \"baz.bin\";\n          `import QUX from \"qux.json\";\n          `import CORGE from \"corge.js\";\n          `import SQUARE_WASM from \"square.wasm\";\n          `const SQUARE = new WebAssembly.Instance(SQUARE_WASM, {});\n          `export default {\n          `  async fetch(request) {\n          `    return new Response([\n          `        FOO, BAR, new TextDecoder().decode(BAZ), QUX.message, CORGE.message,\n          `        \"square.wasm says square(5) = \" + SQUARE.exports.square(5)]\n          `        .join(\"\\n\"));\n          `  }\n          `}\n      ),\n      ( name = \"foo.js\",\n        esModule =\n          `export let MESSAGE = \"Hello from foo.js\"\n      ),\n      ( name = \"bar.txt\",\n        text = \"Hello from bar.txt\"\n      ),\n      ( name = \"baz.bin\",\n        data = \"Hello from baz.bin\"\n      ),\n      ( name = \"qux.json\",\n        json = `{\"message\": \"Hello from qux.json\"}\n      ),\n      ( name = \"corge.js\",\n        commonJsModule =\n          `module.exports.message = \"Hello from corge.js\";\n      ),\n      ( name = \"square.wasm\",\n        # Exports a function 'square(x)' that returns x^2.\n        wasm = 0x\"00 61 73 6d 01 00 00 00  01 06 01 60 01 7f 01 7f\n                  03 02 01 00 05 03 01 00  02 06 08 01 7f 01 41 80\n                  88 04 0b 07 13 02 06 6d  65 6d 6f 72 79 02 00 06\n                  73 71 75 61 72 65 00 00  0a 09 01 07 00 20 00 20\n                  00 6c 0b\"\n      )\n    ]\n  ))\"_kj));\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\",\n      \"Hello from foo.js\\n\"\n      \"Hello from bar.txt\\n\"\n      \"Hello from baz.bin\\n\"\n      \"Hello from qux.json\\n\"\n      \"Hello from corge.js\\n\"\n      \"square.wasm says square(5) = 25\");\n}\n\nKJ_TEST(\"Server: compatibility dates\") {\n  // The easiest flag to test is the presence of the global `navigator`.\n  auto selfNavigatorCheckerWorker = [](kj::StringPtr compatProperties) {\n    return singleWorker(kj::str(R\"((\n      )\",\n        compatProperties, R\"(,\n      modules = [\n        ( name = \"main.js\",\n          esModule =\n              `export default {\n              `  async fetch(request) {\n              `    return new Response(!!self.navigator);\n              `  }\n              `}\n        )\n      ]\n    ))\"_kj));\n  };\n\n  {\n    TestServer test(selfNavigatorCheckerWorker(\"compatibilityDate = \\\"2022-08-17\\\"\"));\n\n    test.start();\n    auto conn = test.connect(\"test-addr\");\n    conn.httpGet200(\"/\", \"true\");\n  }\n\n  // In the past, the global wasn't there.\n  {\n    TestServer test(selfNavigatorCheckerWorker(\"compatibilityDate = \\\"2020-01-01\\\"\"));\n\n    test.start();\n    auto conn = test.connect(\"test-addr\");\n    conn.httpGet200(\"/\", \"false\");\n  }\n\n  // Disable using a flag instead of a date.\n  {\n    TestServer test(selfNavigatorCheckerWorker(\n        \"compatibilityDate = \\\"2022-08-17\\\", compatibilityFlags = [\\\"no_global_navigator\\\"]\"));\n\n    test.start();\n    auto conn = test.connect(\"test-addr\");\n    conn.httpGet200(\"/\", \"false\");\n  }\n}\n\nKJ_TEST(\"Server: compatibility dates are required\") {\n  TestServer test(singleWorker(R\"((\n    serviceWorkerScript =\n        `addEventListener(\"fetch\", event => {\n        `  event.respondWith(new Response(\"Hello: \" + event.request.url + \"\\n\"));\n        `})\n  ))\"_kj));\n\n  test.expectErrors(R\"(\n    service hello: Worker must specify compatibilityDate.\n  )\"_blockquote);\n}\n\nKJ_TEST(\"Server: value bindings\") {\n#if _WIN32\n  _putenv(\"TEST_ENVIRONMENT_VAR=Hello from environment variable\");\n#else\n  setenv(\"TEST_ENVIRONMENT_VAR\", \"Hello from environment variable\", true);\n#endif\n\n  TestServer test(singleWorker(R\"((\n    compatibilityDate = \"2022-08-17\",\n    # (Must use Service Worker syntax to allow Wasm bindings.)\n    serviceWorkerScript =\n      `const SQUARE = new WebAssembly.Instance(BAZ, {});\n      `async function handle(request) {\n      `  let items = [];\n      `  items.push(FOO);\n      `  items.push(new TextDecoder().decode(BAR));\n      `  items.push(\"wasm says square(5) = \" + SQUARE.exports.square(5));\n      `  items.push(QUX.message);\n      `  items.push(CORGE);\n      `  items.push(\"GRAULT is null? \" + (GRAULT === null));\n      `  return new Response(items.join(\"\\n\"));\n      `}\n      `addEventListener(\"fetch\", event => {\n      `  event.respondWith(handle(event.request));\n      `});\n      ,\n    bindings = [\n      ( name = \"FOO\", text = \"Hello from text binding\" ),\n      ( name = \"BAR\", data = \"Hello from data binding\" ),\n      ( name = \"BAZ\",\n        # Exports a function 'square(x)' that returns x^2.\n        wasmModule = 0x\"00 61 73 6d 01 00 00 00  01 06 01 60 01 7f 01 7f\n                        03 02 01 00 05 03 01 00  02 06 08 01 7f 01 41 80\n                        88 04 0b 07 13 02 06 6d  65 6d 6f 72 79 02 00 06\n                        73 71 75 61 72 65 00 00  0a 09 01 07 00 20 00 20\n                        00 6c 0b\"\n      ),\n      ( name = \"QUX\",\n        json = `{\"message\": \"Hello from json binding\"}\n      ),\n      ( name = \"CORGE\", fromEnvironment = \"TEST_ENVIRONMENT_VAR\" ),\n      ( name = \"GRAULT\", fromEnvironment = \"TEST_NONEXISTENT_ENVIRONMENT_VAR\" ),\n    ]\n  ))\"_kj));\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\",\n      \"Hello from text binding\\n\"\n      \"Hello from data binding\\n\"\n      \"wasm says square(5) = 25\\n\"\n      \"Hello from json binding\\n\"\n      \"Hello from environment variable\\n\"\n      \"GRAULT is null? true\");\n}\n\nKJ_TEST(\"Server: WebCrypto bindings\") {\n  TestServer test(singleWorker(R\"((\n    compatibilityDate = \"2022-08-17\",\n    modules = [\n      ( name = \"main.js\",\n        esModule =\n          `function hex(buffer) {\n          `  return [...new Uint8Array(buffer)]\n          `      .map(x => x.toString(16).padStart(2, '0'))\n          `      .join('');\n          `}\n          `\n          `export default {\n          `  async fetch(request, env) {\n          `    let items = [];\n          `\n          `    let plaintext = new TextEncoder().encode(\"hello\");\n          `    let sig = await crypto.subtle.sign({\"name\": \"HMAC\", \"hash\": \"SHA-256\"},\n          `                                       env.hmac, plaintext);\n          `    items.push(\"hmac signature is \" + hex(sig));\n          `    let ver1 = await crypto.subtle.verify({\"name\": \"HMAC\", \"hash\": \"SHA-256\"},\n          `                                          env.hmac, sig, plaintext);\n          `    let ver2 = await crypto.subtle.verify({\"name\": \"HMAC\", \"hash\": \"SHA-256\"},\n          `                                          env.hmac, sig, new Uint8Array([12, 34]));\n          `    items.push(\"hmac verifications: \" + ver1 + \", \" + ver2);\n          `    items.push(\"hmac extractable? \" + env.hmac.extractable);\n          `\n          `    let hexSig = await crypto.subtle.sign({\"name\": \"HMAC\", \"hash\": \"SHA-256\"},\n          `                                          env.hmacHex, plaintext);\n          `    let b64Sig = await crypto.subtle.sign({\"name\": \"HMAC\", \"hash\": \"SHA-256\"},\n          `                                          env.hmacBase64, plaintext);\n          `    let jwkSig = await crypto.subtle.sign({\"name\": \"HMAC\", \"hash\": \"SHA-256\"},\n          `                                          env.hmacJwk, plaintext);\n          `    items.push(\"hmac signature (hex key) is \" + hex(hexSig));\n          `    items.push(\"hmac signature (base64 key) is \" + hex(b64Sig));\n          `    items.push(\"hmac signature (jwk key) is \" + hex(jwkSig));\n          `\n          `    try {\n          `      await crypto.subtle.verify({\"name\": \"HMAC\", \"hash\": \"SHA-256\"},\n          `                                 env.hmacHex, sig, plaintext);\n          `      items.push(\"verification with hmacHex was allowed\");\n          `    } catch (err) {\n          `      items.push(\"verification with hmacHex was not allowed: \" + err.message);\n          `    }\n          `\n          `    let ecsig = await crypto.subtle.sign(\n          `        {\"name\": \"ECDSA\", \"namedCurve\": \"P-256\", \"hash\": \"SHA-256\"},\n          `        env.ecPriv, plaintext);\n          `    let ecver = await crypto.subtle.verify(\n          `        {\"name\": \"ECDSA\", \"namedCurve\": \"P-256\", \"hash\": \"SHA-256\"},\n          `        env.ecPub, ecsig, plaintext);\n          `    items.push(\"ec verification: \" + ecver);\n          `    items.push(\"ec extractable? \" + env.ecPriv.extractable +\n          `                             \", \" + env.ecPub.extractable);\n          `\n          `    return new Response(items.join(\"\\n\"));\n          `  }\n          `}\n      )\n    ],\n    bindings = [\n      ( name = \"hmac\",\n        cryptoKey = (\n          raw = \"testkey\",\n          algorithm = (\n            json = `{\"name\": \"HMAC\", \"hash\": \"SHA-256\"}\n          ),\n          usages = [ sign, verify ]\n        )\n      ),\n      ( name = \"hmacHex\",\n        cryptoKey = (\n          hex = \"746573746b6579\",\n          algorithm = (\n            json = `{\"name\": \"HMAC\", \"hash\": \"SHA-256\"}\n          ),\n          usages = [ sign ]\n        )\n      ),\n      ( name = \"hmacBase64\",\n        cryptoKey = (\n          base64 = \"dGVzdGtleQ==\",\n          algorithm = (\n            json = `{\"name\": \"HMAC\", \"hash\": \"SHA-256\"}\n          ),\n          usages = [ sign ]\n        )\n      ),\n      ( name = \"hmacJwk\",\n        cryptoKey = (\n          jwk = `{\"alg\":\"HS256\",\"k\":\"dGVzdGtleQ\",\"kty\":\"oct\"}\n          ,\n          algorithm = (\n            json = `{\"name\": \"HMAC\", \"hash\": \"SHA-256\"}\n          ),\n          usages = [ sign ]\n        )\n      ),\n\n      ( name = \"ecPriv\",\n        cryptoKey = (\n          pkcs8 =\n            `-----BEGIN PRIVATE KEY-----\n            `MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgXB5SjGILYt4DxPho\n            `VUX/lMnLzpJD5R6Jl0bLCuRj8V2hRANCAAQ6pM4KrujAsw2xz0qA6l4DF/waMYVP\n            `QNOAakb+S9GwkOgrTbw6AYoawTaW68Vbwadfe2S02ya6yEKGyE3N56by\n            `-----END PRIVATE KEY-----\n          ,\n          algorithm = (\n            json = `{\"name\": \"ECDSA\", \"namedCurve\": \"P-256\"}\n          ),\n          usages = [ sign ]\n        )\n      ),\n\n      ( name = \"ecPub\",\n        cryptoKey = (\n          spki =\n            `-----BEGIN PUBLIC KEY-----\n            `MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOqTOCq7owLMNsc9KgOpeAxf8GjGF\n            `T0DTgGpG/kvRsJDoK028OgGKGsE2luvFW8GnX3tktNsmushChshNzeem8g==\n            `-----END PUBLIC KEY-----\n          ,\n          algorithm = (\n            json = `{\"name\": \"ECDSA\", \"namedCurve\": \"P-256\"}\n          ),\n          usages = [ verify ],\n          extractable = true\n        )\n      )\n    ]\n  ))\"_kj));\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\",\n      \"hmac signature is 4a27693183b28d2616209d6ff5e77646af5fc06ea6affac37415995b07be2ddf\\n\"\n      \"hmac verifications: true, false\\n\"\n      \"hmac extractable? false\\n\"\n      \"hmac signature (hex key) is \"\n      \"4a27693183b28d2616209d6ff5e77646af5fc06ea6affac37415995b07be2ddf\\n\"\n      \"hmac signature (base64 key) is \"\n      \"4a27693183b28d2616209d6ff5e77646af5fc06ea6affac37415995b07be2ddf\\n\"\n      \"hmac signature (jwk key) is \"\n      \"4a27693183b28d2616209d6ff5e77646af5fc06ea6affac37415995b07be2ddf\\n\"\n      \"verification with hmacHex was not allowed: \"\n      \"Requested key usage \\\"verify\\\" does not match any usage listed in this CryptoKey.\\n\"\n      \"ec verification: true\\n\"\n      \"ec extractable? false, true\");\n}\n\nKJ_TEST(\"Server: subrequest to default outbound\") {\n  TestServer test(singleWorker(R\"((\n    compatibilityDate = \"2022-08-17\",\n    modules = [\n      ( name = \"main.js\",\n        esModule =\n          `export default {\n          `  async fetch(request, env) {\n          `    let resp = await fetch(\"http://subhost/foo\");\n          `    let txt = await resp.text();\n          `    return new Response(\n          `        \"sub X-Foo header: \" + resp.headers.get(\"X-Foo\") + \"\\n\" +\n          `        \"sub body: \" + txt);\n          `  }\n          `}\n      )\n    ]\n  ))\"_kj));\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.sendHttpGet(\"/\");\n\n  auto subreq = test.receiveInternetSubrequest(\"subhost\");\n  subreq.recv(R\"(\n    GET /foo HTTP/1.1\n    Host: subhost\n\n  )\"_blockquote);\n  subreq.send(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 6\n    X-Foo: bar\n\n    corge\n  )\"_blockquote);\n\n  conn.recvHttp200(R\"(\n    sub X-Foo header: bar\n    sub body: corge\n  )\"_blockquote);\n}\n\nKJ_TEST(\"Server: override 'internet' service\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    return fetch(request);\n                `  }\n                `}\n            )\n          ]\n        )\n      ),\n      ( name = \"internet\",\n        external = \"proxy-host\" )\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.sendHttpGet(\"/\");\n\n  auto subreq = test.receiveSubrequest(\"proxy-host\");\n  subreq.recv(R\"(\n    GET / HTTP/1.1\n    Host: foo\n\n  )\"_blockquote);\n  subreq.send(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 2\n    Content-Type: text/plain;charset=UTF-8\n\n    OK\n  )\"_blockquote);\n\n  conn.recvHttp200(\"OK\");\n}\n\nKJ_TEST(\"Server: override globalOutbound\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    return fetch(request);\n                `  }\n                `}\n            )\n          ],\n          globalOutbound = \"alternate-outbound\"\n        )\n      ),\n      ( name = \"alternate-outbound\",\n        external = \"proxy-host\" )\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.sendHttpGet(\"/\");\n\n  auto subreq = test.receiveSubrequest(\"proxy-host\");\n  subreq.recv(R\"(\n    GET / HTTP/1.1\n    Host: foo\n\n  )\"_blockquote);\n  subreq.send(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 2\n    Content-Type: text/plain;charset=UTF-8\n\n    OK\n  )\"_blockquote);\n\n  conn.recvHttp200(\"OK\");\n}\n\nKJ_TEST(\"Server: connect() to default outbound\") {\n  TestServer test(singleWorker(R\"((\n    compatibilityDate = \"2022-08-17\",\n    compatibilityFlags = [\"nodejs_compat\"],\n    modules = [\n      ( name = \"main.js\",\n        esModule =\n          `import { connect } from 'cloudflare:sockets';\n          `import assert from 'node:assert';\n          `\n          `export default {\n          `  async fetch(request, env) {\n          `    let sock = connect(\"subhost:123\");\n          `\n          `    let writer = sock.writable.getWriter();\n          `    await writer.write(new TextEncoder().encode(\"hello\"));\n          `    await writer.close();\n          `\n          `    let reader = sock.readable.getReader();\n          `    let chunk = await reader.read();\n          `    assert.strictEqual(chunk.done, false);\n          `    assert.strictEqual(new TextDecoder().decode(chunk.value), \"goodbye\");\n          `\n          `    await sock.close();\n          `    return new Response(\"OK\");\n          `  }\n          `}\n      )\n    ]\n  ))\"_kj));\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.sendHttpGet(\"/\");\n\n  auto subreq = test.receiveInternetSubrequest(\"subhost:123\");\n  subreq.recv(\"hello\");\n  subreq.send(\"goodbye\");\n\n  conn.recvHttp200(\"OK\");\n}\n\nKJ_TEST(\"Server: connect() with Worker as outbound, no connect_pass_though\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          compatibilityFlags = [\"nodejs_compat\"],\n          globalOutbound = \"outbound-worker\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `import { connect } from 'cloudflare:sockets';\n                `import assert from 'node:assert';\n                `\n                `export default {\n                `  async fetch(request, env) {\n                `    // TODO(bug): At present this throws synchronously, which seems like a bug in\n                `    //   the implementation of connect(): errors coming from the destination\n                `    //   service really ought to be async (in prod, they always will be), showing\n                `    //   up on the first read or write. At present, though, I'm not looking to\n                `    //   fix this bug.\n                `    assert.throws(() => connect(\"subhost:123\"), {\n                `      name: \"TypeError\",\n                `      message: \"Incoming CONNECT on a worker not supported\",\n                `    });\n                `\n                `    return new Response(\"OK\");\n                `  }\n                `}\n            )\n          ]\n        )\n      ),\n      ( name = \"outbound-worker\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    throw new Error(\"HTTP not expected\");\n                `  }\n                `}\n            )\n          ]\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n\n  test.server.allowExperimental();\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.sendHttpGet(\"/\");\n\n  conn.recvHttp200(\"OK\");\n}\n\nKJ_TEST(\"Server: connect() with Worker as outbound, with connect_pass_though\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          compatibilityFlags = [\"nodejs_compat\"],\n          globalOutbound = \"outbound-worker\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `import { connect } from 'cloudflare:sockets';\n                `import assert from 'node:assert';\n                `\n                `export default {\n                `  async fetch(request, env) {\n                `    let sock = connect(\"subhost:123\");\n                `\n                `    let writer = sock.writable.getWriter();\n                `    await writer.write(new TextEncoder().encode(\"hello\"));\n                `    await writer.close();\n                `\n                `    let reader = sock.readable.getReader();\n                `    let chunk = await reader.read();\n                `    assert.strictEqual(chunk.done, false);\n                `    assert.strictEqual(new TextDecoder().decode(chunk.value), \"goodbye\");\n                `\n                `    await sock.close();\n                `    return new Response(\"OK\");\n                `  }\n                `}\n            )\n          ]\n        )\n      ),\n      ( name = \"outbound-worker\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          compatibilityFlags = [\"connect_pass_through\"],\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    throw new Error(\"HTTP not expected\");\n                `  }\n                `}\n            )\n          ]\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n\n  test.server.allowExperimental();\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.sendHttpGet(\"/\");\n\n  auto subreq = test.receiveInternetSubrequest(\"subhost:123\");\n  subreq.recv(\"hello\");\n  subreq.send(\"goodbye\");\n\n  conn.recvHttp200(\"OK\");\n}\n\nKJ_TEST(\"Server: capability bindings\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    let items = [];\n                `    items.push(await (await env.fetcher.fetch(\"http://foo\")).text());\n                `    items.push(await env.kv.get(\"bar\"));\n                `    items.push(await (await env.r2.get(\"baz\")).text());\n                `    await env.queue.send(\"hello\");\n                `    items.push(\"Hello from Queue\\n\");\n                `    const connection = await env.hyperdrive.connect();\n                `    const encoded = new TextEncoder().encode(\"hyperdrive-test\");\n                `    await connection.writable.getWriter().write(new Uint8Array(encoded));\n                `    items.push(`Hello from Hyperdrive(${env.hyperdrive.user})\\n`);\n                `    return new Response(items.join(\"\"));\n                `  }\n                `}\n            )\n          ],\n          bindings = [\n            ( name = \"fetcher\",\n              service = \"service-outbound\"\n            ),\n            ( name = \"kv\",\n              kvNamespace = \"kv-outbound\"\n            ),\n            ( name = \"r2\",\n              r2Bucket = \"r2-outbound\"\n            ),\n            ( name = \"queue\",\n              queue = \"queue-outbound\"\n            ),\n            ( name = \"hyperdrive\",\n              hyperdrive = (\n                designator = \"hyperdrive-outbound\",\n                database = \"test-db\",\n                user = \"test-user\",\n                password = \"test-password\",\n                scheme = \"postgresql\"\n              )\n            )\n          ]\n        )\n      ),\n      ( name = \"service-outbound\", external = \"service-host\" ),\n      ( name = \"kv-outbound\", external = \"kv-host\" ),\n      ( name = \"r2-outbound\", external = \"r2-host\" ),\n      ( name = \"queue-outbound\", external = \"queue-host\" ),\n      ( name = \"hyperdrive-outbound\", external = (\n        address = \"hyperdrive-host\",\n        tcp = ()\n      ))\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.sendHttpGet(\"/\");\n\n  {\n    auto subreq = test.receiveSubrequest(\"service-host\");\n    subreq.recv(R\"(\n      GET / HTTP/1.1\n      Host: foo\n\n    )\"_blockquote);\n    subreq.send(R\"(\n      HTTP/1.1 200 OK\n      Content-Length: 16\n      Content-Type: text/plain;charset=UTF-8\n\n      Hello from HTTP\n    )\"_blockquote);\n  }\n\n  {\n    auto subreq = test.receiveSubrequest(\"kv-host\");\n    subreq.recv(R\"(\n      GET /bar?urlencoded=true HTTP/1.1\n      Host: fake-host\n      CF-KV-FLPROD-405: https://fake-host/bar?urlencoded=true\n\n    )\"_blockquote);\n    subreq.send(R\"(\n      HTTP/1.1 200 OK\n      Content-Length: 14\n\n      Hello from KV\n    )\"_blockquote);\n  }\n\n  {\n    auto subreq = test.receiveSubrequest(\"r2-host\");\n    subreq.recv(R\"(\n      GET / HTTP/1.1\n      Host: fake-host\n      CF-R2-Request: {\"version\":1,\"method\":\"get\",\"object\":\"baz\"}\n\n    )\"_blockquote);\n    subreq.send(R\"(\n      HTTP/1.1 200 OK\n      Content-Length: 16\n      CF-R2-Metadata-Size: 2\n\n      {}Hello from R2\n    )\"_blockquote);\n  }\n\n  {\n    auto subreq = test.receiveSubrequest(\"queue-host\");\n    // We use a regex match to avoid dealing with the non-text characters in the POST body (which\n    // may change as v8 serialization versions change over time).\n    subreq.recvRegex(R\"(\n      POST /message HTTP/1.1\n      Content-Length: 9\n      Host: fake-host\n      Content-Type: application/octet-stream\n\n      .+hello)\"_blockquote);\n    subreq.send(R\"(\n      HTTP/1.1 200 OK\n      Content-Length: 2\n\n      OK\n    )\"_blockquote);\n  }\n\n  {\n    auto subreq = test.receiveSubrequest(\"hyperdrive-host\");\n    subreq.recv(\"hyperdrive-test\");\n  }\n  conn.recvHttp200(R\"(\n    Hello from HTTP\n    Hello from KV\n    Hello from R2\n    Hello from Queue\n    Hello from Hyperdrive(test-user)\n  )\"_blockquote);\n}\n\nKJ_TEST(\"Server: cyclic bindings\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"service1\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    if (request.url.endsWith(\"/done\")) {\n                `      return new Response(\"!\");\n                `    } else {\n                `      let resp2 = await env.service2.fetch(request);\n                `      let text = await resp2.text();\n                `      return new Response(\"Hello \" + text);\n                `    }\n                `  }\n                `}\n            )\n          ],\n          bindings = [(name = \"service2\", service = \"service2\")]\n        )\n      ),\n      ( name = \"service2\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    let resp2 = await env.service1.fetch(\"http://foo/done\");\n                `    let text = await resp2.text();\n                `    return new Response(\"World\" + text);\n                `  }\n                `}\n            )\n          ],\n          bindings = [(name = \"service1\", service = \"service1\")]\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"service1\"\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\", \"Hello World!\");\n}\n\nKJ_TEST(\"Server: named entrypoints\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    return new Response(\"hello from default entrypoint\");\n                `  }\n                `}\n                `export let foo = {\n                `  async fetch(request, env) {\n                `    return new Response(\"hello from foo entrypoint\");\n                `  }\n                `}\n                `export let bar = {\n                `  async fetch(request, env) {\n                `    return new Response(\"hello from bar entrypoint\");\n                `  }\n                `}\n                `\n                `// Also export some symbols that aren't valid entrypoints, but we should still\n                `// be allowed to point sockets at them. (Sending any actual requests to them\n                `// will still fail.)\n                `export let invalidObj = {};  // no handlers\n                `export let invalidArray = [1, 2];\n                `export let invalidMap = new Map();\n            )\n          ]\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\", address = \"test-addr\", service = \"hello\" ),\n      ( name = \"alt1\", address = \"foo-addr\", service = (name = \"hello\", entrypoint = \"foo\")),\n      ( name = \"alt2\", address = \"bar-addr\", service = (name = \"hello\", entrypoint = \"bar\")),\n\n      ( name = \"invalid1\", address = \"invalid1-addr\",\n        service = (name = \"hello\", entrypoint = \"invalidObj\")),\n      ( name = \"invalid2\", address = \"invalid2-addr\",\n        service = (name = \"hello\", entrypoint = \"invalidArray\")),\n      ( name = \"invalid3\", address = \"invalid3-addr\",\n        service = (name = \"hello\", entrypoint = \"invalidMap\")),\n    ]\n  ))\"_kj);\n\n  test.start();\n\n  {\n    auto conn = test.connect(\"test-addr\");\n    conn.httpGet200(\"/\", \"hello from default entrypoint\");\n  }\n\n  {\n    auto conn = test.connect(\"foo-addr\");\n    conn.httpGet200(\"/\", \"hello from foo entrypoint\");\n  }\n\n  {\n    auto conn = test.connect(\"bar-addr\");\n    conn.httpGet200(\"/\", \"hello from bar entrypoint\");\n  }\n}\n\nKJ_TEST(\"Server: invalid entrypoint\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    return env.svc.fetch(request);\n                `  }\n                `}\n            )\n          ],\n          bindings = [(name = \"svc\", service = (name = \"hello\", entrypoint = \"bar\"))],\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\", address = \"test-addr\", service = \"hello\" ),\n      ( name = \"alt1\", address = \"foo-addr\", service = (name = \"hello\", entrypoint = \"foo\")),\n    ]\n  ))\"_kj);\n\n  test.expectErrors(\n      \"Worker \\\"hello\\\"'s binding \\\"svc\\\" refers to service \\\"hello\\\" with a named entrypoint \"\n      \"\\\"bar\\\", but \\\"hello\\\" has no such named entrypoint.\\n\"\n      \"Socket \\\"alt1\\\" refers to service \\\"hello\\\" with a named entrypoint \\\"foo\\\", but \\\"hello\\\" \"\n      \"has no such named entrypoint.\\n\");\n}\n\nKJ_TEST(\"Server: referencing non-extant default entrypoint is not an error\") {\n  // For historical reasons, it's not a config error to refer to to the default entrypoint of\n  // a service that has no default export.\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export let alt = {\n                `  async fetch(request, env) {\n                `    return new Response(\"OK\");\n                `  }\n                `}\n            )\n          ],\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\", address = \"test-addr\", service = \"hello\" ),\n    ]\n  ))\"_kj);\n  test.start();\n\n  // A request will still fail at runtime, but we shouldn't have seen startup/config errors.\n  auto conn = test.connect(\"test-addr\");\n  conn.sendHttpGet(\"/\");\n\n  // Due to the Deep Magic (bugs) going back to the dawn of Module Workers, if an HTTP request is\n  // delivered to the default entrypoint of a module worker that has no default export, then the\n  // system will fall back to calling event handlers registered with addEventListener(\"fetch\").\n  //\n  // There is a magic deeper still in which, due to mistakes introduced in the stillness and the\n  // darkness before Module Workers dawned, if none of those event listeners call\n  // `event.respondWith()` (perhaps because *there are no event listeners*), then the request falls\n  // back to default handling, in which it simply passes through to fetch() and makes a subrequest.\n  //\n  // So... we expect... a subrequest...\n  {\n    auto subreq = test.receiveSubrequest(\"foo\", {\"public\"});\n    subreq.recv(R\"(\n      GET / HTTP/1.1\n      Host: foo\n\n    )\"_blockquote);\n    subreq.send(R\"(\n      HTTP/1.1 200 OK\n      Content-Length: 3\n\n      wat)\"_blockquote);\n  }\n\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 3\n\n    wat)\"_blockquote);\n}\n\nKJ_TEST(\"Server: referencing DO class as entrypoint is not an error\") {\n  // For historical reasons, it's not a config error to refer to an actor class as a stateless\n  // entrypoint.\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `import { DurableObject } from \"cloudflare:workers\"\n                `\n                `export class SomeActor extends DurableObject {}\n                `\n                `export default {\n                `  async fetch(request, env) {\n                `    return new Response(\"OK\");\n                `  }\n                `}\n            )\n          ],\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = (name = \"hello\", entrypoint = \"SomeActor\")\n      ),\n    ]\n  ))\"_kj);\n\n  // We see a log warning at config time, but config otherwise completes successfully.\n  {\n    // TODO(soon): Restore this warning once miniflare no longer generates config that causes\n    //   it to log spuriously.\n    //\n    // KJ_EXPECT_LOG(WARNING,\n    //     \"A ServiceDesignator in the config referenced the entrypoint \\\"SomeActor\\\", but this \"\n    //     \"class does not extend 'WorkerEntrypoint'. Attempts to call this entrypoint will \"\n    //     \"fail at runtime, but historically this was not a startup-time error. Future \"\n    //     \"versions of workerd may make this a startup-time error.\");\n    test.start();\n  }\n\n  // However, a request will still fail at runtime.\n  KJ_EXPECT_LOG(ERROR, \"worker is not an actor but class name was requested\");\n  KJ_EXPECT_LOG(INFO, \"Unable to get exported handler\");\n  KJ_EXPECT_LOG(ERROR, \"Unable to get exported handler\");\n\n  auto conn = test.connect(\"test-addr\");\n  conn.sendHttpGet(\"/\");\n  conn.recv(R\"(\n    HTTP/1.1 500 Internal Server Error\n    Connection: close\n    Content-Length: 21\n\n    Internal Server Error)\"_blockquote);\n}\n\nKJ_TEST(\"Server: exporting a DO class as the default export is not an error\") {\n  // For historical reasons, it's not a config error to export a DO class as the default\n  // entrypoint. It doesn't work at runtime, but it's not a config error.\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `import { DurableObject } from \"cloudflare:workers\"\n                `\n                `export default class extends DurableObject {\n                `  async fetch(request) {\n                `    return new Response(\"this should not be called\");\n                `  }\n                `}\n            )\n          ],\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      ),\n    ]\n  ))\"_kj);\n\n  // We see a log error at config time, but config otherwise completes successfully.\n  {\n    KJ_EXPECT_LOG(ERROR,\n        \"Exported actor class as default entrypoint. This doesn't work, but historically \"\n        \"did not produce a startup-time error.\");\n    test.start();\n  }\n\n  // Note that there is no way to actually configure the default export as a DO class since\n  // `className` is non-optional in both `DurableObjectNamespace` and\n  // `DurableObjectNamespaceDesignator`.\n  //\n  // We can, however, try to send a stateless request to the default entrypoint and see what\n  // happens!\n  //\n  // Since the runtime does not believe there is any (stateless) entrypoint exported as the\n  // default entrypoint, if you try to send a request to it, it behaves the same as if there were\n  // no `export default` at all.\n  //\n  // The behavior of this is quite strange. See the comment in the earlier test:\n  //\n  //   KJ_TEST(\"Server: referencing non-extant default entrypoint is not an error\")\n  auto conn = test.connect(\"test-addr\");\n  conn.sendHttpGet(\"/\");\n\n  {\n    auto subreq = test.receiveSubrequest(\"foo\", {\"public\"});\n    subreq.recv(R\"(\n      GET / HTTP/1.1\n      Host: foo\n\n    )\"_blockquote);\n    subreq.send(R\"(\n      HTTP/1.1 200 OK\n      Content-Length: 3\n\n      wat)\"_blockquote);\n  }\n\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 3\n\n    wat)\"_blockquote);\n}\n\nKJ_TEST(\"Server: configuring a DO namespace with no class export is not an error\") {\n  // For historical reasons, it's not a config error to configure a DO namespace when there is\n  // no corresponding class export.\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    return env.ns.get(env.ns.newUniqueId()).fetch(request);\n                `    //return new Response(\"OK\");\n                `  }\n                `}\n            )\n          ],\n          bindings = [(name = \"ns\", durableObjectNamespace = \"MyActorClass\")],\n          durableObjectNamespaces = [\n            ( className = \"MyActorClass\",\n              uniqueKey = \"mykey\",\n            )\n          ],\n          durableObjectStorage = (inMemory = void)\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      ),\n    ]\n  ))\"_kj);\n\n  // We see a log warning at config time, but config otherwise completes successfully.\n  {\n    KJ_EXPECT_LOG(WARNING,\n        \"A DurableObjectNamespace in the config referenced the class \\\"MyActorClass\\\", but \"\n        \"no such Durable Object class is exported from the worker. Please make sure the \"\n        \"class name matches, it is exported, and the class extends 'DurableObject'. \"\n        \"Attempts to call to this Durable Object class will fail at runtime, but historically \"\n        \"this was not a startup-time error. Future versions of workerd may make this a \"\n        \"startup-time error.\");\n    test.start();\n  }\n\n  // However, a request will still fail at runtime.\n  KJ_EXPECT_LOG(ERROR, \"no such actor class\");\n  KJ_EXPECT_LOG(INFO, \"internal error\");\n  KJ_EXPECT_LOG(INFO, \"internal error\");\n  KJ_EXPECT_LOG(ERROR, \"internal error\");\n\n  auto conn = test.connect(\"test-addr\");\n  conn.sendHttpGet(\"/\");\n  conn.recv(R\"(\n    HTTP/1.1 500 Internal Server Error\n    Connection: close\n    Content-Length: 21\n\n    Internal Server Error)\"_blockquote);\n}\n\nKJ_TEST(\"Server: call queue handler on service binding\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"service1\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          compatibilityFlags = [\"service_binding_extra_handlers\"],\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    let result = await env.service2.queue(\"queueName1\", [\n                `        {id: \"1\", timestamp: 12345, body: \"my message\", attempts: 1},\n                `        {id: \"msg2\", timestamp: 23456, body: 22, attempts: 2},\n                `    ]);\n                `    return new Response(`queue outcome: ${result.outcome}, ackAll: ${result.ackAll}`);\n                `  }\n                `}\n            )\n          ],\n          bindings = [(name = \"service2\", service = \"service2\")]\n        )\n      ),\n      ( name = \"service2\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    throw new Error(\"unimplemented\");\n                `  },\n                `  async queue(event) {\n                `    if (event.queue == \"queueName1\" &&\n                `        event.messages.length == 2 &&\n                `        event.messages[0].id == \"1\" &&\n                `        event.messages[0].timestamp.getTime() == 12345 &&\n                `        event.messages[0].body == \"my message\" &&\n                `        event.messages[0].attempts == 1 &&\n                `        event.messages[1].id == \"msg2\" &&\n                `        event.messages[1].timestamp.getTime() == 23456 &&\n                `        event.messages[1].body == 22 &&\n                `        event.messages[1].attempts == 2) {\n                `      event.ackAll();\n                `      return;\n                `    }\n                `    throw new Error(\"messages didn't match expectations: \" + JSON.stringify(event.messages));\n                `  }\n                `}\n            )\n          ]\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"service1\"\n      )\n    ]\n  ))\"_kj);\n\n  test.server.allowExperimental();\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\", \"queue outcome: ok, ackAll: true\");\n}\n\nKJ_TEST(\"Server: Durable Objects (in memory)\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    let id = env.ns.idFromName(request.url)\n                `    let actor = env.ns.get(id)\n                `    return await actor.fetch(request)\n                `  }\n                `}\n                `export class MyActorClass {\n                `  constructor(state, env) {\n                `    this.storage = state.storage;\n                `    this.id = state.id;\n                `    if (this.id.constructor.name != \"DurableObjectId\") {\n                `      throw new Error(\"durable ID should be type DurableObjectId, \" +\n                `                      `got: ${this.id.constructor.name}`);\n                `    }\n                `    if (this.id.name) {\n                `      throw new Error(\"ctx.id for Durable Object should not have a .name \" +\n                `                      `property, got: ${this.id.name}`);\n                `    }\n                `  }\n                `  async fetch(request) {\n                `    let count = (await this.storage.get(\"foo\")) || 0;\n                `    this.storage.put(\"foo\", count + 1);\n                `    return new Response(this.id + \": \" + request.url + \" \" + count);\n                `  }\n                `}\n            )\n          ],\n          bindings = [(name = \"ns\", durableObjectNamespace = \"MyActorClass\")],\n          durableObjectNamespaces = [\n            ( className = \"MyActorClass\",\n              uniqueKey = \"mykey\",\n            )\n          ],\n          durableObjectStorage = (inMemory = void)\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\n      \"/\", \"59002eb8cf872e541722977a258a12d6a93bbe8192b502e1c0cb250aa91af234: http://foo/ 0\");\n  conn.httpGet200(\n      \"/\", \"59002eb8cf872e541722977a258a12d6a93bbe8192b502e1c0cb250aa91af234: http://foo/ 1\");\n  conn.httpGet200(\n      \"/\", \"59002eb8cf872e541722977a258a12d6a93bbe8192b502e1c0cb250aa91af234: http://foo/ 2\");\n  conn.httpGet200(\n      \"/bar\", \"02b496f65dd35cbac90e3e72dc5a398ee93926ea4a3821e26677082d2e6f9b79: http://foo/bar 0\");\n  conn.httpGet200(\n      \"/bar\", \"02b496f65dd35cbac90e3e72dc5a398ee93926ea4a3821e26677082d2e6f9b79: http://foo/bar 1\");\n  conn.httpGet200(\n      \"/\", \"59002eb8cf872e541722977a258a12d6a93bbe8192b502e1c0cb250aa91af234: http://foo/ 3\");\n  conn.httpGet200(\n      \"/bar\", \"02b496f65dd35cbac90e3e72dc5a398ee93926ea4a3821e26677082d2e6f9b79: http://foo/bar 2\");\n}\n\nKJ_TEST(\"Server: Simultaneous requests to a DO that hasn't started don't cause split brain\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2025-04-01\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `import {DurableObject} from \"cloudflare:workers\"\n                `export default {\n                `  async fetch(request, env) {\n                `    let id = env.ns.idFromName(request.url)\n                `    let actor = env.ns.get(id)\n                `    let promise1 = actor.increment()\n                `    let promise2 = actor.increment()\n                `    let promise3 = actor.increment()\n                `    return new Response(`${await promise1} ${await promise2} ${await promise3}`)\n                `  }\n                `}\n                `export class Counter extends DurableObject {\n                `  counter = 0;\n                `  async increment() {\n                `    return this.counter++;\n                `  }\n                `}\n            )\n          ],\n          bindings = [(name = \"ns\", durableObjectNamespace = \"Counter\")],\n          durableObjectNamespaces = [\n            ( className = \"Counter\",\n              uniqueKey = \"mykey\",\n            )\n          ],\n          durableObjectStorage = (inMemory = void)\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\", \"0 1 2\");\n}\n\nKJ_TEST(\"Server: Broken DO stays broken until stub replaced\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2025-04-01\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `import {DurableObject} from \"cloudflare:workers\"\n                `export default {\n                `  async fetch(request, env) {\n                `    let id = env.ns.idFromName(request.url)\n                `    let actor = env.ns.get(id)\n                `    let i1 = await actor.increment()\n                `    try { await actor.abort() } catch {}\n                `    try {\n                `      let i2 = await actor.increment();\n                `      throw new Error(`expected error from broken stub, got ${i2}`);\n                `    } catch (err) {\n                `      if (!err.message.includes(\"test abort reason\")) {\n                `        throw err\n                `      }\n                `    }\n                `    actor = env.ns.get(id)\n                `    let i3 = await actor.increment()\n                `    return new Response(`${i1} ${i3}`)\n                `  }\n                `}\n                `export class Counter extends DurableObject {\n                `  counter = 0;\n                `  async increment() {\n                `    return this.counter++;\n                `  }\n                `  async abort() {\n                `    this.ctx.abort(new Error(\"test abort reason\"));\n                `  }\n                `}\n            )\n          ],\n          bindings = [(name = \"ns\", durableObjectNamespace = \"Counter\")],\n          durableObjectNamespaces = [\n            ( className = \"Counter\",\n              uniqueKey = \"mykey\",\n            )\n          ],\n          durableObjectStorage = (inMemory = void)\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\", \"0 0\");\n}\n\nKJ_TEST(\"Server: Durable Objects (on disk)\") {\n  kj::StringPtr config = R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    let id = env.ns.idFromName(request.url)\n                `    let actor = env.ns.get(id)\n                `    return await actor.fetch(request)\n                `  }\n                `}\n                `export class MyActorClass {\n                `  constructor(state, env) {\n                `    this.storage = state.storage;\n                `    this.id = state.id;\n                `    if (this.id.constructor.name != \"DurableObjectId\") {\n                `      throw new Error(\"durable ID should be type DurableObjectId, \" +\n                `                      `got: ${this.id.constructor.name}`);\n                `    }\n                `  }\n                `  async fetch(request) {\n                `    let count = (await this.storage.get(\"foo\")) || 0;\n                `    this.storage.put(\"foo\", count + 1);\n                `    return new Response(this.id + \": \" + request.url + \" \" + count);\n                `  }\n                `}\n            )\n          ],\n          bindings = [(name = \"ns\", durableObjectNamespace = \"MyActorClass\")],\n          durableObjectNamespaces = [\n            ( className = \"MyActorClass\",\n              uniqueKey = \"mykey\",\n            )\n          ],\n          durableObjectStorage = (localDisk = \"my-disk\")\n        )\n      ),\n      ( name = \"my-disk\",\n        disk = (\n          path = \"../../var/do-storage\",\n          writable = true,\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj;\n\n  // Create a directory outside of the test scope which we can use across multiple TestServers.\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n\n  {\n    TestServer test(config);\n\n    // Link our directory into the test filesystem.\n    test.root->transfer(kj::Path({\"var\"_kj, \"do-storage\"_kj}),\n        kj::WriteMode::CREATE | kj::WriteMode::CREATE_PARENT, *dir, nullptr,\n        kj::TransferMode::LINK);\n\n    test.start();\n    auto conn = test.connect(\"test-addr\");\n    conn.httpGet200(\n        \"/\", \"59002eb8cf872e541722977a258a12d6a93bbe8192b502e1c0cb250aa91af234: http://foo/ 0\");\n    conn.httpGet200(\n        \"/\", \"59002eb8cf872e541722977a258a12d6a93bbe8192b502e1c0cb250aa91af234: http://foo/ 1\");\n    conn.httpGet200(\n        \"/\", \"59002eb8cf872e541722977a258a12d6a93bbe8192b502e1c0cb250aa91af234: http://foo/ 2\");\n    conn.httpGet200(\"/bar\",\n        \"02b496f65dd35cbac90e3e72dc5a398ee93926ea4a3821e26677082d2e6f9b79: http://foo/bar 0\");\n    conn.httpGet200(\"/bar\",\n        \"02b496f65dd35cbac90e3e72dc5a398ee93926ea4a3821e26677082d2e6f9b79: http://foo/bar 1\");\n    conn.httpGet200(\n        \"/\", \"59002eb8cf872e541722977a258a12d6a93bbe8192b502e1c0cb250aa91af234: http://foo/ 3\");\n    conn.httpGet200(\"/bar\",\n        \"02b496f65dd35cbac90e3e72dc5a398ee93926ea4a3821e26677082d2e6f9b79: http://foo/bar 2\");\n\n    // The storage directory contains .sqlite and .sqlite-wal files for both objects. Note that\n    // the `-shm` files are missing because SQLite doesn't actually tell the VFS to create these\n    // as separate files, it leaves it up to the VFS to decide how shared memory works, and our\n    // KJ-wrapping VFS currently doesn't put this in SHM files. If we were using a real disk\n    // directory, though, they would be there.\n    KJ_EXPECT(dir->openSubdir(kj::Path({\"mykey\"}))->listNames().size() == 4);\n    KJ_EXPECT(dir->exists(kj::Path(\n        {\"mykey\", \"02b496f65dd35cbac90e3e72dc5a398ee93926ea4a3821e26677082d2e6f9b79.sqlite\"})));\n    KJ_EXPECT(dir->exists(kj::Path(\n        {\"mykey\", \"02b496f65dd35cbac90e3e72dc5a398ee93926ea4a3821e26677082d2e6f9b79.sqlite-wal\"})));\n    KJ_EXPECT(dir->exists(kj::Path(\n        {\"mykey\", \"59002eb8cf872e541722977a258a12d6a93bbe8192b502e1c0cb250aa91af234.sqlite\"})));\n    KJ_EXPECT(dir->exists(kj::Path(\n        {\"mykey\", \"59002eb8cf872e541722977a258a12d6a93bbe8192b502e1c0cb250aa91af234.sqlite-wal\"})));\n  }\n\n  // Having torn everything down, the WAL files should be gone.\n  KJ_EXPECT(dir->openSubdir(kj::Path({\"mykey\"}))->listNames().size() == 2);\n  KJ_EXPECT(dir->exists(kj::Path(\n      {\"mykey\", \"02b496f65dd35cbac90e3e72dc5a398ee93926ea4a3821e26677082d2e6f9b79.sqlite\"})));\n  KJ_EXPECT(dir->exists(kj::Path(\n      {\"mykey\", \"59002eb8cf872e541722977a258a12d6a93bbe8192b502e1c0cb250aa91af234.sqlite\"})));\n\n  // Let's start a new server and verify it can load the files from disk.\n  {\n    TestServer test(config);\n\n    // Link our directory into the test filesystem.\n    test.root->transfer(kj::Path({\"var\"_kj, \"do-storage\"_kj}),\n        kj::WriteMode::CREATE | kj::WriteMode::CREATE_PARENT, *dir, nullptr,\n        kj::TransferMode::LINK);\n\n    test.start();\n    auto conn = test.connect(\"test-addr\");\n    conn.httpGet200(\n        \"/\", \"59002eb8cf872e541722977a258a12d6a93bbe8192b502e1c0cb250aa91af234: http://foo/ 4\");\n    conn.httpGet200(\n        \"/\", \"59002eb8cf872e541722977a258a12d6a93bbe8192b502e1c0cb250aa91af234: http://foo/ 5\");\n    conn.httpGet200(\"/bar\",\n        \"02b496f65dd35cbac90e3e72dc5a398ee93926ea4a3821e26677082d2e6f9b79: http://foo/bar 3\");\n  }\n}\n\nKJ_TEST(\"Server: Ephemeral Objects\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    let actor = env.ns.get(request.url)\n                `    return await actor.fetch(request)\n                `  }\n                `}\n                `export class MyActorClass {\n                `  constructor(state, env) {\n                `    if (state.storage) throw new Error(\"storage shouldn't be present\");\n                `    this.id = state.id;\n                `    if (typeof this.id != \"string\") {\n                `      throw new Error(\"ephemeral ID should be type string, \" +\n                `                      `got: ${this.id.constructor.name}`);\n                `    }\n                `    this.count = 0;\n                `  }\n                `  async fetch(request) {\n                `    return new Response(this.id + \": \" + request.url + \" \" + this.count++);\n                `  }\n                `}\n            )\n          ],\n          bindings = [(name = \"ns\", durableObjectNamespace = \"MyActorClass\")],\n          durableObjectNamespaces = [\n            ( className = \"MyActorClass\",\n              ephemeralLocal = void,\n            )\n          ],\n          durableObjectStorage = (inMemory = void)\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n\n  test.server.allowExperimental();\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\", \"http://foo/: http://foo/ 0\");\n  conn.httpGet200(\"/\", \"http://foo/: http://foo/ 1\");\n  conn.httpGet200(\"/\", \"http://foo/: http://foo/ 2\");\n  conn.httpGet200(\"/bar\", \"http://foo/bar: http://foo/bar 0\");\n  conn.httpGet200(\"/bar\", \"http://foo/bar: http://foo/bar 1\");\n  conn.httpGet200(\"/\", \"http://foo/: http://foo/ 3\");\n  conn.httpGet200(\"/bar\", \"http://foo/bar: http://foo/bar 2\");\n}\n\nKJ_TEST(\"Server: Durable Objects (ephemeral) eviction\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2023-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    let id = env.ns.idFromName(\"59002eb8cf872e541722977a258a12d6a93bbe8192b502e1c0cb250aa91af234\");\n                `    let obj = env.ns.get(id)\n                `    if (request.url.endsWith(\"/setup\")) {\n                `      return await obj.fetch(\"http://example.com/setup\");\n                `    } else if (request.url.endsWith(\"/check\")) {\n                `      try {\n                `        return await obj.fetch(\"http://example.com/check\");\n                `      } catch(e) {\n                `        throw e;\n                `      }\n                `    } else if (request.url.endsWith(\"/checkEvicted\")) {\n                `      return await obj.fetch(\"http://example.com/checkEvicted\");\n                `    }\n                `    return new Response(\"Invalid Route!\")\n                `  }\n                `}\n                `export class MyActorClass {\n                `  constructor(state, env) {\n                `    this.defaultMessage = false; // Set to true on first \"setup\" request\n                `  }\n                `  async fetch(request) {\n                `    if (request.url.endsWith(\"/setup\")) {\n                `      // Request 1, set defaultMessage, will remain true as long as actor is live.\n                `      this.defaultMessage = true;\n                `      return new Response(\"OK\");\n                `    } else if (request.url.endsWith(\"/check\")) {\n                `      // Request 2, assert that actor is still in alive (defaultMessage is still true).\n                `      if (this.defaultMessage) {\n                `        // Actor is still alive and we did not re-run the constructor\n                `        return new Response(\"OK\");\n                `      }\n                `      throw new Error(\"Error: Actor was evicted!\");\n                `    } else if (request.url.endsWith(\"/checkEvicted\")) {\n                `      // Final request (3), check if the defaultMessage has been set to false,\n                `      //  indicating the actor was evicted\n                `      if (!this.defaultMessage) {\n                `        // Actor was evicted and we re-ran the constructor!\n                `        return new Response(\"OK\");\n                `      }\n                `      throw new Error(\"Error: Actor was not evicted! We were still alive.\");\n                `    }\n                `  }\n                `}\n            )\n          ],\n          bindings = [(name = \"ns\", durableObjectNamespace = \"MyActorClass\")],\n          durableObjectNamespaces = [\n            ( className = \"MyActorClass\",\n              uniqueKey = \"mykey\",\n            )\n          ],\n          durableObjectStorage = (inMemory = void)\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/setup\", \"OK\");\n  conn.httpGet200(\"/check\", \"OK\");\n\n  // Force hibernation by waiting 10 seconds.\n  test.wait(10);\n  // Need a second connection because of 5 second HTTP timeout.\n  auto connTwo = test.connect(\"test-addr\");\n  connTwo.httpGet200(\"/checkEvicted\", \"OK\");\n}\n\nKJ_TEST(\"Server: Durable Objects (ephemeral) prevent eviction\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2023-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    let id = env.ns.idFromName(\"59002eb8cf872e541722977a258a12d6a93bbe8192b502e1c0cb250aa91af234\");\n                `    let obj = env.ns.get(id);\n                `    if (request.url.endsWith(\"/setup\")) {\n                `      return await obj.fetch(\"http://example.com/setup\");\n                `    } else if (request.url.endsWith(\"/assertNotEvicted\")) {\n                `      try {\n                `        return await obj.fetch(\"http://example.com/assertNotEvicted\");\n                `      } catch(e) {\n                `        throw e;\n                `      }\n                `    }\n                `    return new Response(\"Invalid Route!\")\n                `  }\n                `}\n                `export class MyActorClass {\n                `  constructor(state, env) {\n                `    this.defaultMessage = false; // Set to true on first \"setup\" request\n                `  }\n                `  async fetch(request) {\n                `    if (request.url.endsWith(\"/setup\")) {\n                `      // Request 1, set defaultMessage, will remain true as long as actor is live.\n                `      this.defaultMessage = true;\n                `      return new Response(\"OK\");\n                `    } else if (request.url.endsWith(\"/assertNotEvicted\")) {\n                `      // Request 2, assert that actor is still in alive (defaultMessage is still true).\n                `      if (this.defaultMessage) {\n                `        // Actor is still alive and we did not re-run the constructor\n                `        return new Response(\"OK\");\n                `      }\n                `      throw new Error(\"Error: Actor was evicted!\");\n                `    }\n                `  }\n                `}\n            )\n          ],\n          bindings = [(name = \"ns\", durableObjectNamespace = \"MyActorClass\")],\n          durableObjectNamespaces = [\n            ( className = \"MyActorClass\",\n              uniqueKey = \"mykey\",\n              preventEviction = true,\n            )\n          ],\n          durableObjectStorage = (inMemory = void)\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/setup\", \"OK\");\n  conn.httpGet200(\"/assertNotEvicted\", \"OK\");\n\n  // Attempt to force hibernation by waiting 10 seconds.\n  test.wait(10);\n  // Need a second connection because of 5 second HTTP timeout.\n  auto connTwo = test.connect(\"test-addr\");\n  connTwo.httpGet200(\"/assertNotEvicted\", \"OK\");\n}\n\nKJ_TEST(\"Server: Durable Object evictions when callback scheduled\") {\n  kj::StringPtr config = R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2023-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    let id = env.ns.idFromName(\"59002eb8cf872e541722977a258a12d6a93bbe8192b502e1c0cb250aa91af234\");\n                `    let obj = env.ns.get(id)\n                `    return await obj.fetch(request.url);\n                `  }\n                `}\n                `export class MyActorClass {\n                `  constructor(state, env) {\n                `    this.defaultMessage = false; // Set to true on first \"setup\" request\n                `    this.storage = state.storage;\n                `    this.count = 0;\n                `  }\n                `  async fetch(request) {\n                `    if (request.url.endsWith(\"/15Seconds\")) {\n                `      // Schedule a callback to run in 15 seconds.\n                `      // The DO should NOT be evicted by the inactivity timeout before this runs.\n                `      this.defaultMessage = true;\n                `      let id = setInterval(() => { clearInterval(id); }, 15000);\n                `      return new Response(\"OK\");\n                `    } else if (request.url.endsWith(\"/20Seconds\")) {\n                `      // Schedule a callback to run every 20 seconds.\n                `      // The DO should expire after 70 seconds.\n                `      this.defaultMessage = true;\n                `      this.count = 0;\n                `      await this.storage.put(\"count\", this.count);\n                `      let id = setInterval(() => {\n                `        // Increment number of times we ran this.\n                `        this.count += 1;\n                `        this.storage.put(\"count\", this.count);\n                `      }, 20000);\n                `      return new Response(\"OK\");\n                `    } else if (request.url.endsWith(\"/assertActive\")) {\n                `      // Assert that actor is still in alive (defaultMessage is still true).\n                `      if (this.defaultMessage) {\n                `        // Actor is still alive and we did not re-run the constructor\n                `        return new Response(\"OK\");\n                `      }\n                `      throw new Error(\"Error: Actor was evicted!\");\n                `    } else if (request.url.endsWith(\"/assertEvicted\")) {\n                `      // Check if the defaultMessage has been set to false,\n                `      // indicating the actor was evicted\n                `      if (!this.defaultMessage) {\n                `        // Actor was evicted and we re-ran the constructor!\n                `        return new Response(\"OK\");\n                `      }\n                `      throw new Error(\"Error: Actor was not evicted! We were still alive.\");\n                `    } else if (request.url.endsWith(\"/assertEvictedAndCount\")) {\n                `      // Check if the defaultMessage has been set to false,\n                `      // indicating the actor was evicted\n                `      if (!this.defaultMessage) {\n                `        var count = await this.storage.get(\"count\");\n                `        if (!(4 < count && count < 8)) {\n                `          // Something must have gone wrong. We have a 70 sec expiration,\n                `          // and worst case is it takes ~140 seconds to evict. The callback runs\n                `          // every 20 seconds, so it has to be evicted before the 8th callback.\n                `          throw new Error(`Callback ran ${count} times, expected between 4 to 8!`);\n                `        }\n                `        // Actor was evicted and we had the right count!\n                `        return new Response(\"OK\");\n                `      }\n                `      throw new Error(\"Error: Actor was not evicted! We were still alive.\");\n                `    }\n                `  }\n                `}\n            )\n          ],\n          bindings = [(name = \"ns\", durableObjectNamespace = \"MyActorClass\")],\n          durableObjectNamespaces = [\n            ( className = \"MyActorClass\",\n              uniqueKey = \"mykey\",\n            )\n          ],\n          durableObjectStorage = (localDisk = \"my-disk\")\n        )\n      ),\n      ( name = \"my-disk\",\n        disk = (\n          path = \"../../var/do-storage\",\n          writable = true,\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj;\n\n  // Create a directory outside of the test scope which we can use across multiple TestServers.\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  {\n    TestServer test(config);\n    // Link our directory into the test filesystem.\n    test.root->transfer(kj::Path({\"var\"_kj, \"do-storage\"_kj}),\n        kj::WriteMode::CREATE | kj::WriteMode::CREATE_PARENT, *dir, nullptr,\n        kj::TransferMode::LINK);\n\n    test.start();\n    auto conn = test.connect(\"test-addr\");\n    // Setup a callback that will run in 15 seconds.\n    // This callback should prevent the DO from being evicted.\n    conn.httpGet200(\"/15Seconds\", \"OK\");\n\n    // If we weren't waiting on anything, the DO would be evicted after 10 seconds,\n    // however, it will actually be evicted in 25 seconds (15 seconds until setInterval is cleared +\n    // 10 seconds for inactivity timer).\n\n    test.wait(15);\n    // The `setInterval()` will be cleared around now. Let's verify that we didn't get evicted.\n\n    // Need a new connection because of 5 second HTTP timeout.\n    auto connTwo = test.connect(\"test-addr\");\n    connTwo.httpGet200(\"/assertActive\", \"OK\");\n\n    // Force hibernation by waiting at least 10 seconds since we haven't scheduled any new work.\n    test.wait(10);\n\n    // Need a new connection because of 5 second HTTP timeout.\n    auto connThree = test.connect(\"test-addr\");\n    connThree.httpGet200(\"/assertEvicted\", \"OK\");\n\n    // Now we know we aren't evicting DOs early if they have future work scheduled. Next, let's\n    // ensure we ARE evicting DOs if there are no connected clients for 70 seconds.\n    // Note that the `/20seconds` path calls setInterval to run every 20 seconds, and never clears.\n    auto connFour = test.connect(\"test-addr\");\n    connFour.httpGet200(\"/20Seconds\", \"OK\");\n    // It's unlikely, but the worst case is the cleanupLoop checks just before the 70 sec expiration,\n    // and has to wait another 70 seconds before trying to remove again. We'll wait for 142 seconds\n    // to account for this.\n    test.wait(142);\n\n    auto connFive = test.connect(\"test-addr\");\n    connFive.httpGet200(\"/assertEvictedAndCount\", \"OK\");\n  }\n}\n\nKJ_TEST(\"Server: Durable Objects websocket\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2023-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    let id = env.ns.idFromName(\"59002eb8cf872e541722977a258a12d6a93bbe8192b502e1c0cb250aa91af234\");\n                `    let obj = env.ns.get(id)\n                `    return await obj.fetch(request);\n                `  }\n                `}\n                `\n                `export class MyActorClass {\n                `  constructor(state) {}\n                `\n                `  async fetch(request) {\n                `    let pair = new WebSocketPair();\n                `    let ws = pair[1]\n                `    ws.accept();\n                `\n                `    ws.addEventListener(\"message\", (m) => {\n                `      ws.send(m.data);\n                `    });\n                `    ws.addEventListener(\"close\", (c) => {\n                `      ws.close(c.code, c.reason);\n                `    });\n                `\n                `    return new Response(null, {status: 101, statusText: \"Switching Protocols\", webSocket: pair[0]});\n                `  }\n                `}\n            )\n          ],\n          bindings = [(name = \"ns\", durableObjectNamespace = \"MyActorClass\")],\n          durableObjectNamespaces = [\n            ( className = \"MyActorClass\",\n              uniqueKey = \"mykey\",\n            )\n          ],\n          durableObjectStorage = (inMemory = void)\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n  auto wsConn = test.connect(\"test-addr\");\n  wsConn.upgradeToWebSocket();\n  constexpr kj::StringPtr expectedOne = \"Hello\"_kj;\n  constexpr kj::StringPtr expectedTwo = \"There\"_kj;\n  // \\x81\\x05 are part of the websocket frame.\n  // \\x81 is 10000001 -- leftmost bit implies this is the final frame, rightmost implies text data.\n  // \\x05 says the payload length is 5.\n  wsConn.send(kj::str(\"\\x81\\x05\", expectedOne));\n  wsConn.send(kj::str(\"\\x81\\x05\", expectedTwo));\n  wsConn.recvWebSocket(expectedOne);\n  wsConn.recvWebSocket(expectedTwo);\n\n  // Force hibernation by waiting 10 seconds.\n  test.wait(10);\n  wsConn.send(kj::str(\"\\x81\\x05\", expectedOne));\n  wsConn.send(kj::str(\"\\x81\\x05\", expectedTwo));\n  wsConn.recvWebSocket(expectedOne);\n  wsConn.recvWebSocket(expectedTwo);\n}\n\nKJ_TEST(\"Server: Durable Objects websocket hibernation\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2023-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    let id = env.ns.idFromName(\"59002eb8cf872e541722977a258a12d6a93bbe8192b502e1c0cb250aa91af234\");\n                `    let obj = env.ns.get(id)\n                `\n                `    // 1. Create a websocket (request 1)\n                `    // 2. Use websocket once\n                `    // 3. Let actor hibernate\n                `    // 4. Wake actor by sending new request (request 2)\n                `    //  - This confirms we get back hibernation manager.\n                `    //    5. Use websocket once\n                `    // 6. Let actor hibernate\n                `    // 7. Wake actor by using websocket\n                `    //  - This confirms we get back hibernation manager.\n                `    //    8. Use websocket once\n                `    try {\n                `      return await obj.fetch(request);\n                `    } catch (err) {\n                `      if (request.url.endsWith(\"/abort\")) {\n                `        // expected\n                `        return new Response(\"OK\");\n                `      } else {\n                `        throw err;\n                `      }\n                `    }\n                `  }\n                `}\n                `\n                `export class MyActorClass {\n                `  constructor(state) {\n                `    this.state = state;\n                `    // If reqCount is 0, then the actor's constructor has run.\n                `    // This implies we're starting up, so either this is the first request or we were evicted.\n                `    this.reqCount = 0;\n                `  }\n                `\n                `  async fetch(request) {\n                `    if (request.url.endsWith(\"/\")) {\n                `      // Request 1, accept a websocket.\n                `      let pair = new WebSocketPair(true);\n                `      let ws = pair[1];\n                `      this.state.acceptWebSocket(ws);\n                `\n                `      this.reqCount += 1;\n                `      if (this.reqCount != 1) {\n                `        throw new Error(`Expected request count of 1 but got ${this.reqCount}`);\n                `      }\n                `      return new Response(null, {status: 101, statusText: \"Switching Protocols\", webSocket: pair[0]});\n                `    } else if (request.url.endsWith(\"/wakeUpAndCheckWS\")) {\n                `      // Request 2, wake actor and check if WS available.\n                `      let allWebsockets = this.state.getWebSockets();\n                `      for (const ws of allWebsockets) {\n                `        ws.send(\"Hello! Just woke up from a nap.\");\n                `      }\n                `\n                `      this.reqCount += 1;\n                `      if (this.reqCount != 1) {\n                `        throw new Error(`Expected request count of 1 but got ${this.reqCount}`);\n                `      }\n                `\n                `      return new Response(\"OK\");\n                `    } else if (request.url.endsWith(\"/abort\")) {\n                `      this.state.abort(\"test abort message\");\n                `    }\n                `    return new Error(\"Unknown path!\");\n                `  }\n                `\n                `  async webSocketMessage(ws, msg) {\n                `    if (msg == \"Regular message.\") {\n                `      ws.send(\"Regular response.\");\n                `    } else if (msg == \"Confirm actor was evicted.\") {\n                `      // Called when waking from hibernation due to inbound websocket message.\n                `      if (this.reqCount == 0) {\n                `        ws.send(\"OK\")\n                `      } else {\n                `        ws.send(`[ FAILURE ] - reqCount was ${this.reqCount} so actor wasn't evicted`);\n                `      }\n                `    }\n                `  }\n                `\n                `  async webSocketClose(ws, code, reason, wasClean) {\n                `    if (code == 1006) {\n                `      if (reason != \"WebSocket disconnected without sending Close frame.\") {\n                `        throw new Error(`Got abnormal closure with unexpected reason: ${reason}`);\n                `      }\n                `      if (wasClean) {\n                `        throw new Error(\"Got abnormal closure but wasClean was true!\");\n                `      }\n                `    } else if (code != 1234) {\n                `      throw new Error(`Expected close code 1234, got ${code}`);\n                `    } else if (reason != \"OK\") {\n                `      throw new Error(`Expected close reason \"OK\", got ${reason}`);\n                `    } else {\n                `      ws.close(4321, \"KO\");\n                `    }\n                `  }\n                `\n                `  async webSocketError(ws, error) {\n                `    console.log(`Encountered error: ${error}`);\n                `    throw new Error(error);\n                `  }\n                `}\n\n            )\n          ],\n          bindings = [(name = \"ns\", durableObjectNamespace = \"MyActorClass\")],\n          durableObjectNamespaces = [\n            ( className = \"MyActorClass\",\n              uniqueKey = \"mykey\",\n            )\n          ],\n          durableObjectStorage = (inMemory = void)\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n  auto wsConn = test.connect(\"test-addr\");\n  wsConn.upgradeToWebSocket();\n  // 1. Make hibernatable ws and use it.\n  constexpr kj::StringPtr message = \"Regular message.\"_kj;\n  constexpr kj::StringPtr response = \"Regular response.\"_kj;\n  wsConn.send(kj::str(\"\\x81\\x10\", message));\n  wsConn.recvWebSocket(response);\n\n  // 2. Hibernate\n  test.wait(10);\n  // 3. Use normal connection and read from ws.\n  {\n    auto conn = test.connect(\"test-addr\");\n    conn.httpGet200(\"/wakeUpAndCheckWS\", \"OK\"_kj);\n  }\n  constexpr kj::StringPtr unpromptedResponse = \"Hello! Just woke up from a nap.\"_kj;\n  wsConn.recvWebSocket(unpromptedResponse);\n\n  // 4. Hibernate again\n  test.wait(10);\n\n  // 5. Wake up by sending a message.\n  constexpr kj::StringPtr confirmEviction = \"Confirm actor was evicted.\"_kj;\n  constexpr kj::StringPtr evicted = \"OK\"_kj;\n  wsConn.send(kj::str(\"\\x81\\x1a\", confirmEviction));\n  wsConn.recvWebSocket(evicted);\n\n  // 6. Hibernate again\n  test.wait(10);\n\n  // 7. Wake up the actor and have it abort itself. This should disconnect the WebSocket, even\n  // though the WebSocket itself is still hibernated.\n  KJ_EXPECT_LOG(INFO, \"Error: test abort message\");\n  KJ_EXPECT_LOG(INFO, \"other end of WebSocketPipe was destroyed\");\n  {\n    auto conn = test.connect(\"test-addr\");\n    conn.httpGet200(\"/abort\", \"OK\"_kj);\n  }\n\n  KJ_EXPECT(wsConn.isEof());\n}\n\nKJ_TEST(\"Server: tail workers\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2024-11-01\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(req, env, ctx) {\n                `    console.log(\"foo\", \"bar\");\n                `    console.log(\"baz\");\n                `    return new Response(\"OK\");\n                `  }\n                `}\n            )\n          ],\n          tails = [\"tail\", \"tail2\"],\n        )\n      ),\n      ( name = \"tail\",\n        worker = (\n          compatibilityDate = \"2024-11-01\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async tail(req, env, ctx) {\n                `    await fetch(\"http://tail\", {\n                `      method: \"POST\",\n                `      body: JSON.stringify(req[0].logs.map(log => log.message))\n                `    });\n                `  }\n                `}\n            )\n          ],\n        )\n      ),\n      ( name = \"tail2\",\n        worker = (\n          compatibilityDate = \"2024-11-01\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async tail(req, env, ctx) {\n                `    await fetch(\"http://tail2/\" + req[0].logs.length);\n                `  }\n                `}\n            )\n          ],\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.sendHttpGet(\"/\");\n  conn.recvHttp200(\"OK\");\n\n  auto subreq = test.receiveInternetSubrequest(\"tail\");\n  subreq.recv(R\"(\n    POST / HTTP/1.1\n    Content-Length: 23\n    Host: tail\n    Content-Type: text/plain;charset=UTF-8\n\n    [[\"foo\",\"bar\"],[\"baz\"]])\"_blockquote);\n\n  auto subreq2 = test.receiveInternetSubrequest(\"tail2\");\n  subreq2.recv(R\"(\n    GET /2 HTTP/1.1\n    Host: tail2\n\n    )\"_blockquote);\n\n  subreq.send(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 0\n\n  )\"_blockquote);\n\n  subreq2.send(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 0\n\n  )\"_blockquote);\n}\n\n// =======================================================================================\n// Test HttpOptions on receive\n\nKJ_TEST(\"Server: serve proxy requests\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          serviceWorkerScript =\n              `addEventListener(\"fetch\", event => {\n              `  event.respondWith(new Response(\"Hello: \" + event.request.url + \"\\n\"));\n              `})\n        )\n      )\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\",\n        http = (style = proxy)\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n\n  // Send a proxy-style request. No `Host:` header!\n  conn.send(R\"(\n    GET http://foo/bar HTTP/1.1\n\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 22\n    Content-Type: text/plain;charset=UTF-8\n\n    Hello: http://foo/bar\n  )\"_blockquote);\n}\n\nKJ_TEST(\"Server: forwardedProtoHeader\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          serviceWorkerScript =\n              `addEventListener(\"fetch\", event => {\n              `  event.respondWith(new Response(\"Hello: \" + event.request.url + \"\\n\"));\n              `})\n        )\n      )\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\",\n        http = (forwardedProtoHeader = \"Test-Proto\")\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n\n  // Send a request with a forwarded proto header.\n  conn.send(R\"(\n    GET /bar HTTP/1.1\n    Host: foo\n    tEsT-pRoTo: baz\n\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 21\n    Content-Type: text/plain;charset=UTF-8\n\n    Hello: baz://foo/bar\n  )\"_blockquote);\n\n  // Send a request without one.\n  conn.send(R\"(\n    GET /bar HTTP/1.1\n    Host: foo\n\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 22\n    Content-Type: text/plain;charset=UTF-8\n\n    Hello: http://foo/bar\n  )\"_blockquote);\n}\n\nKJ_TEST(\"Server: cfBlobHeader\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          serviceWorkerScript =\n              `addEventListener(\"fetch\", event => {\n              `  if (event.request.cf) {\n              `    event.respondWith(new Response(\"cf.foo = \" + event.request.cf.foo + \"\\n\"));\n              `  } else {\n              `    event.respondWith(new Response(\"cf is null\\n\"));\n              `  }\n              `})\n        )\n      )\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\",\n        http = (cfBlobHeader = \"CF-Blob\")\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n\n  // Send a request with a CF blob.\n  conn.send(R\"(\n    GET / HTTP/1.1\n    Host: bar\n    cF-bLoB: {\"foo\": \"hello\"}\n\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 15\n    Content-Type: text/plain;charset=UTF-8\n\n    cf.foo = hello\n  )\"_blockquote);\n\n  // Send a request without one\n  conn.send(R\"(\n    GET / HTTP/1.1\n    Host: bar\n\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 11\n    Content-Type: text/plain;charset=UTF-8\n\n    cf is null\n  )\"_blockquote);\n}\n\nKJ_TEST(\"Server: inject headers on incoming request/response\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          serviceWorkerScript =\n              `addEventListener(\"fetch\", event => {\n              `  let text = [...event.request.headers]\n              `      .map(([k,v]) => { return `${k}: ${v}\\n` }).join(\"\");\n              `  event.respondWith(new Response(text));\n              `})\n        )\n      )\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\",\n        http = (\n          injectRequestHeaders = [\n            (name = \"Foo\", value = \"oof\"),\n            (name = \"Bar\", value = \"rab\"),\n          ],\n          injectResponseHeaders = [\n            (name = \"Baz\", value = \"zab\"),\n            (name = \"Qux\", value = \"xuq\"),\n          ]\n        )\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n\n  // Send a request, check headers.\n  conn.send(R\"(\n    GET / HTTP/1.1\n    Host: example.com\n\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 36\n    Content-Type: text/plain;charset=UTF-8\n    Baz: zab\n    Qux: xuq\n\n    bar: rab\n    foo: oof\n    host: example.com\n  )\"_blockquote);\n}\n\nKJ_TEST(\"Server: drain incoming HTTP connections\") {\n  TestServer test(singleWorker(R\"((\n    compatibilityDate = \"2022-08-17\",\n    serviceWorkerScript =\n        `addEventListener(\"fetch\", event => {\n        `  event.respondWith(new Response(\"hello\"));\n        `})\n  ))\"_kj));\n\n  auto paf = kj::newPromiseAndFulfiller<void>();\n\n  test.start(kj::mv(paf.promise));\n\n  auto conn = test.connect(\"test-addr\");\n  auto conn2 = test.connect(\"test-addr\");\n\n  // Send a request on each connection, get a response.\n  conn.httpGet200(\"/\", \"hello\");\n  conn2.httpGet200(\"/\", \"hello\");\n\n  // Send a partial request on conn2.\n  conn2.send(\"GET\");\n\n  // No EOF yet.\n  KJ_EXPECT(!conn.isEof());\n  KJ_EXPECT(!conn2.isEof());\n\n  // Drain the server.\n  paf.fulfiller->fulfill();\n\n  // Now we get EOF on conn.\n  KJ_EXPECT(conn.isEof());\n\n  // But conn2 is still open.\n  KJ_EXPECT(!conn2.isEof());\n\n  // New connections shouldn't be accepted at this point.\n  KJ_EXPECT(test.connectHangs(\"test-addr\"));\n\n  // Finish the request on conn2.\n  conn2.send(\" / HTTP/1.1\\nHost: foo\\n\\n\");\n\n  // We receive a response with Connection: close\n  conn2.recv(R\"(\n    HTTP/1.1 200 OK\n    Connection: close\n    Content-Length: 5\n    Content-Type: text/plain;charset=UTF-8\n\n    hello)\"_blockquote);\n\n  // And then the connection is, in fact, closed.\n  KJ_EXPECT(conn2.isEof());\n}\n\n// =======================================================================================\n// Test alternate service types\n//\n// We're going to stop using JavaScript here because it's not really helping. We can directly\n// connect a socket to a non-Worker service.\n\nKJ_TEST(\"Server: network outbound with allow/deny\") {\n  TestServer test(R\"((\n    services = [\n      (name = \"hello\", network = (allow = [\"foo\", \"bar\"], deny = [\"baz\", \"qux\"]))\n    ],\n    sockets = [\n      (name = \"main\", address = \"test-addr\", service = \"hello\")\n    ]\n  ))\"_kj);\n\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n\n  conn.sendHttpGet(\"/path\");\n\n  {\n    auto subreq = test.receiveSubrequest(\"foo\", {\"foo\", \"bar\"}, {\"baz\", \"qux\"});\n    subreq.recv(R\"(\n      GET /path HTTP/1.1\n      Host: foo\n\n    )\"_blockquote);\n    subreq.send(R\"(\n      HTTP/1.1 200 OK\n      Content-Length: 2\n      Content-Type: text/plain;charset=UTF-8\n\n      OK)\"_blockquote);\n  }\n\n  conn.recvHttp200(\"OK\");\n}\n\nKJ_TEST(\"Server: external server\") {\n  TestServer test(R\"((\n    services = [\n      (name = \"hello\", external = \"ext-addr\")\n    ],\n    sockets = [\n      (name = \"main\", address = \"test-addr\", service = \"hello\")\n    ]\n  ))\"_kj);\n\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n\n  conn.sendHttpGet(\"/path\");\n\n  {\n    auto subreq = test.receiveSubrequest(\"ext-addr\");\n    subreq.recv(R\"(\n      GET /path HTTP/1.1\n      Host: foo\n\n    )\"_blockquote);\n    subreq.send(R\"(\n      HTTP/1.1 200 OK\n      Content-Length: 2\n      Content-Type: text/plain;charset=UTF-8\n\n      OK)\"_blockquote);\n  }\n\n  conn.recvHttp200(\"OK\");\n}\n\nKJ_TEST(\"Server: external server proxy style\") {\n  TestServer test(R\"((\n    services = [\n      (name = \"hello\", external = (address = \"ext-addr\", http = (style = proxy)))\n    ],\n    sockets = [\n      (name = \"main\", address = \"test-addr\", service = \"hello\")\n    ]\n  ))\"_kj);\n\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n\n  conn.sendHttpGet(\"/path\");\n\n  {\n    auto subreq = test.receiveSubrequest(\"ext-addr\");\n    subreq.recv(R\"(\n      GET http://foo/path HTTP/1.1\n      Host: foo\n\n    )\"_blockquote);\n    subreq.send(R\"(\n      HTTP/1.1 200 OK\n      Content-Length: 2\n      Content-Type: text/plain;charset=UTF-8\n\n      OK)\"_blockquote);\n  }\n\n  conn.recvHttp200(\"OK\");\n}\n\nKJ_TEST(\"Server: external server forwarded-proto\") {\n  TestServer test(R\"((\n    services = [\n      (name = \"hello\", external = (address = \"ext-addr\", http = (forwardedProtoHeader = \"X-Proto\")))\n    ],\n    sockets = [\n      (name = \"main\", address = \"test-addr\", service = \"hello\", http = (style = proxy))\n    ]\n  ))\"_kj);\n\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n\n  conn.send(R\"(\n    GET https://foo/path HTTP/1.1\n\n  )\"_blockquote);\n\n  {\n    auto subreq = test.receiveSubrequest(\"ext-addr\");\n    subreq.recv(R\"(\n      GET /path HTTP/1.1\n      Host: foo\n      X-Proto: https\n\n    )\"_blockquote);\n    subreq.send(R\"(\n      HTTP/1.1 200 OK\n      Content-Length: 2\n      Content-Type: text/plain;charset=UTF-8\n\n      OK)\"_blockquote);\n  }\n\n  conn.recvHttp200(\"OK\");\n}\n\nKJ_TEST(\"Server: external server inject headers\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        external = (\n          address = \"ext-addr\",\n          http = (\n            injectRequestHeaders = [\n              (name = \"Foo\", value = \"oof\"),\n              (name = \"Bar\", value = \"rab\"),\n            ],\n            injectResponseHeaders = [\n              (name = \"Baz\", value = \"zab\"),\n              (name = \"Qux\", value = \"xuq\"),\n            ]\n          )\n        )\n      )\n    ],\n    sockets = [\n      (name = \"main\", address = \"test-addr\", service = \"hello\")\n    ]\n  ))\"_kj);\n\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n\n  conn.sendHttpGet(\"/path\");\n\n  {\n    auto subreq = test.receiveSubrequest(\"ext-addr\");\n    subreq.recv(R\"(\n      GET /path HTTP/1.1\n      Host: foo\n      Foo: oof\n      Bar: rab\n\n    )\"_blockquote);\n    subreq.send(R\"(\n      HTTP/1.1 200 OK\n      Content-Length: 2\n      Content-Type: text/plain;charset=UTF-8\n\n      OK)\"_blockquote);\n  }\n\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 2\n    Content-Type: text/plain;charset=UTF-8\n    Baz: zab\n    Qux: xuq\n\n    OK)\"_blockquote);\n}\n\nKJ_TEST(\"Server: external server cf blob header\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    return env.ext.fetch(\"http://ext/path2\", {cf: {hello: \"world\"}});\n                `  }\n                `}\n            )\n          ],\n          bindings = [(name = \"ext\", service = \"ext\")]\n        )\n      ),\n      (name = \"ext\", external = (address = \"ext-addr\", http = (cfBlobHeader = \"CF-Blob\")))\n    ],\n    sockets = [\n      (name = \"main\", address = \"test-addr\", service = \"hello\")\n    ]\n  ))\"_kj);\n\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n\n  conn.sendHttpGet(\"/path\");\n\n  {\n    auto subreq = test.receiveSubrequest(\"ext-addr\");\n    subreq.recv(R\"(\n      GET /path2 HTTP/1.1\n      Host: ext\n      CF-Blob: {\"hello\":\"world\"}\n\n    )\"_blockquote);\n    subreq.send(R\"(\n      HTTP/1.1 200 OK\n      Content-Length: 2\n      Content-Type: text/plain;charset=UTF-8\n\n      OK)\"_blockquote);\n  }\n\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 2\n    Content-Type: text/plain;charset=UTF-8\n\n    OK)\"_blockquote);\n}\n\nKJ_TEST(\"Server: disk service\") {\n  TestServer test(R\"((\n    services = [\n      (name = \"hello\", disk = \"../../frob/blah\")\n    ],\n    sockets = [\n      (name = \"main\", address = \"test-addr\", service = \"hello\")\n    ]\n  ))\"_kj);\n\n  auto mode = kj::WriteMode::CREATE | kj::WriteMode::CREATE_PARENT;\n  auto dir = test.root->openSubdir(kj::Path({\"frob\"_kj, \"blah\"_kj}), mode);\n  test.fakeDate =\n      kj::UNIX_EPOCH + 2 * kj::DAYS + 5 * kj::HOURS + 18 * kj::MINUTES + 23 * kj::SECONDS;\n  dir->openFile(kj::Path({\"foo.txt\"}), mode)->writeAll(\"hello from foo.txt\\n\");\n  dir->openFile(kj::Path({\"numbers.txt\"}), mode)->writeAll(\"0123456789\\n\");\n  test.fakeDate = kj::UNIX_EPOCH + 400 * kj::DAYS + 2 * kj::HOURS + 52 * kj::MINUTES +\n      9 * kj::SECONDS + 163 * kj::MILLISECONDS;\n  dir->openFile(kj::Path({\"bar.txt\"}), mode)->writeAll(\"hello from bar.txt\\n\");\n  test.fakeDate = kj::UNIX_EPOCH;\n  dir->openFile(kj::Path({\"baz\", \"qux.txt\"}), mode)->writeAll(\"hello from qux.txt\\n\");\n  dir->openFile(kj::Path({\".dot\"}), mode)->writeAll(\"this is a dotfile\\n\");\n  dir->openFile(kj::Path({\".dotdir\", \"foo\"}), mode)->writeAll(\"this is a dotfile\\n\");\n\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n\n  conn.sendHttpGet(\"/foo.txt\");\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 19\n    Content-Type: application/octet-stream\n    Last-Modified: Sat, 03 Jan 1970 05:18:23 GMT\n\n    hello from foo.txt\n  )\"_blockquote);\n\n  conn.sendHttpGet(\"/bar.txt\");\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 19\n    Content-Type: application/octet-stream\n    Last-Modified: Fri, 05 Feb 1971 02:52:09 GMT\n\n    hello from bar.txt\n  )\"_blockquote);\n\n  conn.sendHttpGet(\"/baz/qux.txt\");\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 19\n    Content-Type: application/octet-stream\n    Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT\n\n    hello from qux.txt\n  )\"_blockquote);\n\n  // TODO(beta): Test listing a directory. Unfortunately it doesn't work against the in-memory\n  //   filesystem right now.\n  //\n  // conn.sendHttpGet(\"/\");\n\n  // HEAD returns no content.\n  conn.send(R\"(\n    HEAD /numbers.txt HTTP/1.1\n    Host: foo\n\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 11\n    Content-Type: application/octet-stream\n    Last-Modified: Sat, 03 Jan 1970 05:18:23 GMT\n\n  )\"_blockquote);\n\n  // GET with single range returns partial content.\n  conn.send(R\"(\n    GET /numbers.txt HTTP/1.1\n    Host: foo\n    Range: bytes=3-5\n\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 206 Partial Content\n    Content-Length: 3\n    Content-Type: application/octet-stream\n    Content-Range: bytes 3-5/11\n    Last-Modified: Sat, 03 Jan 1970 05:18:23 GMT\n\n    345)\"_blockquote);\n\n  // GET with single covering range returns full content.\n  conn.send(R\"(\n    GET /numbers.txt HTTP/1.1\n    Host: foo\n    Range: bytes=-50\n\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 11\n    Content-Type: application/octet-stream\n    Last-Modified: Sat, 03 Jan 1970 05:18:23 GMT\n\n    0123456789\n  )\"_blockquote);\n\n  // GET with many ranges returns full content.\n  conn.send(R\"(\n    GET /numbers.txt HTTP/1.1\n    Host: foo\n    Range: bytes=1-3, 6-8\n\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 11\n    Content-Type: application/octet-stream\n    Last-Modified: Sat, 03 Jan 1970 05:18:23 GMT\n\n    0123456789\n  )\"_blockquote);\n\n  // GET with unsatisfiable range.\n  conn.send(R\"(\n    GET /numbers.txt HTTP/1.1\n    Host: foo\n    Range: bytes=20-30\n\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 416 Range Not Satisfiable\n    Content-Length: 21\n    Content-Range: bytes */11\n\n    Range Not Satisfiable)\"_blockquote);\n\n  // File not found...\n  conn.sendHttpGet(\"/no-such-file.txt\");\n  conn.recv(R\"(\n    HTTP/1.1 404 Not Found\n    Content-Length: 9\n\n    Not Found)\"_blockquote);\n\n  // Directory not found...\n  conn.sendHttpGet(\"/no-such-dir/file.txt\");\n  conn.recv(R\"(\n    HTTP/1.1 404 Not Found\n    Content-Length: 9\n\n    Not Found)\"_blockquote);\n\n  // PUT is denied because not writable.\n  conn.send(R\"(\n    PUT /corge.txt HTTP/1.1\n    Host: foo\n    Content-Length: 6\n\n    corge\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 405 Method Not Allowed\n    Content-Length: 18\n\n    Method Not Allowed)\"_blockquote);\n\n  // DELETE is denied because not writable.\n  conn.send(R\"(\n    DELETE /corge.txt HTTP/1.1\n    Host: foo\n\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 405 Method Not Allowed\n    Content-Length: 18\n\n    Method Not Allowed)\"_blockquote);\n\n  // POST is denied because invalid method.\n  conn.send(R\"(\n    POST /corge.txt HTTP/1.1\n    Host: foo\n    Content-Length: 6\n\n    corge\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 501 Not Implemented\n    Content-Length: 15\n\n    Not Implemented)\"_blockquote);\n\n  // Dotfile access is denied.\n  conn.sendHttpGet(\"/.dot\");\n  conn.recv(R\"(\n    HTTP/1.1 404 Not Found\n    Content-Length: 9\n\n    Not Found)\"_blockquote);\n\n  // Dotfile directory access is denied.\n  conn.sendHttpGet(\"/.dotdir/foo\");\n  conn.recv(R\"(\n    HTTP/1.1 404 Not Found\n    Content-Length: 9\n\n    Not Found)\"_blockquote);\n}\n\nKJ_TEST(\"Server: disk service writable\") {\n  TestServer test(R\"((\n    services = [\n      (name = \"hello\", disk = (path = \"../../frob/blah\", writable = true))\n    ],\n    sockets = [\n      (name = \"main\", address = \"test-addr\", service = \"hello\")\n    ]\n  ))\"_kj);\n\n  auto mode = kj::WriteMode::CREATE | kj::WriteMode::CREATE_PARENT;\n  auto dir = test.root->openSubdir(kj::Path({\"frob\"_kj, \"blah\"_kj}), mode);\n  dir->openFile(kj::Path({\"existing.txt\"}), mode)->writeAll(\"replace me!\");\n\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n\n  // Write a file.\n  conn.send(R\"(\n    PUT /newfile.txt HTTP/1.1\n    Host: foo\n    Content-Length: 6\n\n    corge\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 204 No Content\n\n    )\"_blockquote);\n\n  // Read it back.\n  KJ_EXPECT(dir->openFile(kj::Path({\"newfile.txt\"}))->readAllText() == \"corge\\n\");\n\n  // Delete it.\n  conn.send(R\"(\n    DELETE /newfile.txt HTTP/1.1\n    Host: foo\n\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 204 No Content\n\n    )\"_blockquote);\n  KJ_EXPECT(!dir->exists(kj::Path({\"newfile.txt\"})));\n\n  // Delete a non-existent file.\n  conn.send(R\"(\n    DELETE /notfound.txt HTTP/1.1\n    Host: foo\n\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 404 Not Found\n    Content-Length: 9\n\n    Not Found)\"_blockquote);\n\n  // Replace a file.\n  conn.send(R\"(\n    PUT /existing.txt HTTP/1.1\n    Host: foo\n    Content-Length: 7\n\n    grault\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 204 No Content\n\n    )\"_blockquote);\n\n  // Read it back.\n  KJ_EXPECT(dir->openFile(kj::Path({\"existing.txt\"}))->readAllText() == \"grault\\n\");\n\n  // Write a file to a new directory.\n  conn.send(R\"(\n    PUT /newdir/newfile.txt HTTP/1.1\n    Host: foo\n    Content-Length: 7\n\n    garply\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 204 No Content\n\n    )\"_blockquote);\n\n  // Read it back.\n  KJ_EXPECT(dir->openFile(kj::Path({\"newdir\", \"newfile.txt\"}))->readAllText() == \"garply\\n\");\n\n  // Delete the new directory.\n  conn.send(R\"(\n    DELETE /newdir/ HTTP/1.1\n    Host: foo\n\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 204 No Content\n\n    )\"_blockquote);\n  KJ_EXPECT(!dir->exists(kj::Path({\"newdir\"})));\n\n  // POST is denied because invalid method.\n  conn.send(R\"(\n    POST /corge.txt HTTP/1.1\n    Host: foo\n    Content-Length: 6\n\n    waldo\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 501 Not Implemented\n    Content-Length: 15\n\n    Not Implemented)\"_blockquote);\n\n  // Dotfile write access is denied.\n  conn.send(R\"(\n    PUT /.dot HTTP/1.1\n    Host: foo\n    Content-Length: 6\n\n    waldo\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 403 Unauthorized\n    Content-Length: 12\n\n    Unauthorized)\"_blockquote);\n\n  // Dotfile directory write access is denied.\n  conn.send(R\"(\n    PUT /.dotdir/foo HTTP/1.1\n    Host: foo\n    Content-Length: 6\n\n    waldo\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 403 Unauthorized\n    Content-Length: 12\n\n    Unauthorized)\"_blockquote);\n\n  // Dotfile delete access is denied.\n  conn.send(R\"(\n    DELETE /.dot HTTP/1.1\n    Host: foo\n\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 403 Unauthorized\n    Content-Length: 12\n\n    Unauthorized)\"_blockquote);\n\n  // Root write is denied.\n  conn.send(R\"(\n    PUT / HTTP/1.1\n    Host: foo\n    Content-Length: 6\n\n    corge\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 403 Unauthorized\n    Content-Length: 12\n\n    Unauthorized)\"_blockquote);\n\n  // Root delete is denied.\n  conn.send(R\"(\n    DELETE / HTTP/1.1\n    Host: foo\n\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 403 Unauthorized\n    Content-Length: 12\n\n    Unauthorized)\"_blockquote);\n}\n\nKJ_TEST(\"Server: disk service allow dotfiles\") {\n  TestServer test(R\"((\n    services = [\n      (name = \"hello\", disk = (path = \"../../frob\", writable = true, allowDotfiles = true))\n    ],\n    sockets = [\n      (name = \"main\", address = \"test-addr\", service = \"hello\")\n    ]\n  ))\"_kj);\n\n  auto mode = kj::WriteMode::CREATE | kj::WriteMode::CREATE_PARENT;\n  auto dir = test.root->openSubdir(kj::Path({\"frob\"_kj}), mode);\n\n  // Put a file at root that shouldn't be accessible.\n  test.root->openFile(kj::Path({\"secret\"}), mode)->writeAll(\"this is super-secret\");\n\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n\n  conn.send(R\"(\n    PUT /.dot HTTP/1.1\n    Host: foo\n    Content-Length: 6\n\n    waldo\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 204 No Content\n\n    )\"_blockquote);\n\n  KJ_EXPECT(dir->openFile(kj::Path({\".dot\"}))->readAllText() == \"waldo\\n\");\n\n  conn.sendHttpGet(\"/.dot\");\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 6\n    Content-Type: application/octet-stream\n    Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT\n\n    waldo\n  )\"_blockquote);\n\n  conn.sendHttpGet(\"/../secret\");\n  conn.recv(R\"(\n    HTTP/1.1 404 Not Found\n    Content-Length: 9\n\n    Not Found)\"_blockquote);\n  conn.sendHttpGet(\"/%2e%2e/secret\");\n  conn.recv(R\"(\n    HTTP/1.1 404 Not Found\n    Content-Length: 9\n\n    Not Found)\"_blockquote);\n\n  conn.send(R\"(\n    PUT /../secret HTTP/1.1\n    Host: foo\n    Content-Length: 5\n\n    evil\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 204 No Content\n\n    )\"_blockquote);\n  // This actually wrote to /secret, because URL parsing simply ignores leading \"../\".\n  KJ_EXPECT(dir->openFile(kj::Path({\"secret\"}))->readAllText() == \"evil\\n\");\n  KJ_EXPECT(test.root->openFile(kj::Path({\"secret\"}))->readAllText() == \"this is super-secret\");\n\n  conn.send(R\"(\n    PUT /%2e%2e/secret HTTP/1.1\n    Host: foo\n    Content-Length: 5\n\n    evil\n  )\"_blockquote);\n  conn.recv(R\"(\n    HTTP/1.1 403 Unauthorized\n    Content-Length: 12\n\n    Unauthorized)\"_blockquote);\n  // This didn't work.\n  KJ_EXPECT(test.root->openFile(kj::Path({\"secret\"}))->readAllText() == \"this is super-secret\");\n}\n\n// =======================================================================================\n// Test Cache API\n\nKJ_TEST(\"Server: If no cache service is defined, access to the cache API should error\") {\n  TestServer test(singleWorker(R\"((\n    compatibilityDate = \"2022-08-17\",\n    modules = [\n      ( name = \"test.js\",\n        esModule =\n          `export default {\n          `  async fetch(request) {\n          `    try {\n          `      return new Response(await caches.default.match(request))\n          `    } catch (e) {return new Response(e.message)}\n          `\n          `  }\n          `}\n      )\n    ]\n  ))\"_kj));\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\", \"No Cache was configured\");\n}\n\nKJ_TEST(\"Server: cached response\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          cacheApiOutbound = \"cache-outbound\",\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env, ctx) {\n                `    const cache = caches.default;\n                `    let response = await cache.match(request);\n                `    return response ?? new Response('not cached');\n                `  }\n                `}\n            )\n          ]\n        )\n      ),\n      ( name = \"cache-outbound\", external = \"cache-host\" ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.sendHttpGet(\"/\");\n\n  {\n    auto subreq = test.receiveSubrequest(\"cache-host\");\n    subreq.recv(R\"(\n      GET / HTTP/1.1\n      Host: foo\n      Cache-Control: only-if-cached\n\n    )\"_blockquote);\n    subreq.send(R\"(\n      HTTP/1.1 200 OK\n      CF-Cache-Status: HIT\n      Content-Length: 6\n\n      cached)\"_blockquote);\n  }\n\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 6\n    CF-Cache-Status: HIT\n\n    cached)\"_blockquote);\n}\n\nKJ_TEST(\"Server: cache name is passed through to service\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          cacheApiOutbound = \"cache-outbound\",\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env, ctx) {\n                `    const cache = await caches.open('test-cache');\n                `    let response = await cache.match(request);\n                `    return response ?? new Response('not cached');\n                `  }\n                `}\n            )\n          ]\n        )\n      ),\n      ( name = \"cache-outbound\", external = \"cache-host\" ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.sendHttpGet(\"/\");\n\n  {\n    auto subreq = test.receiveSubrequest(\"cache-host\");\n    subreq.recv(R\"(\n      GET / HTTP/1.1\n      Host: foo\n      Cache-Control: only-if-cached\n      CF-Cache-Namespace: test-cache\n\n    )\"_blockquote);\n    subreq.send(R\"(\n      HTTP/1.1 200 OK\n      CF-Cache-Status: HIT\n      Content-Length: 6\n\n      cached)\"_blockquote);\n  }\n\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 6\n    CF-Cache-Status: HIT\n\n    cached)\"_blockquote);\n}\n\n// =======================================================================================\n// Test the test command\n\nKJ_TEST(\"Server: cache name is passed through to service\") {\n  kj::StringPtr config = R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async test(controller, env, ctx) {}\n                `}\n                `export let fail = {\n                `  async test(controller, env, ctx) {\n                `    throw new Error(\"ded\");\n                `  }\n                `}\n                `export let nonTest = {\n                `  async fetch(req, env, ctx) {\n                `    return new Response(\"ok\");\n                `  }\n                `}\n            )\n          ]\n        )\n      ),\n      ( name = \"another\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async test(controller, env, ctx) {\n                `    console.log(env.MESSAGE);\n                `  }\n                `}\n            )\n          ],\n          bindings = [\n            ( name = \"MESSAGE\", text = \"other test\" ),\n          ]\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj;\n\n  {\n    TestServer test(config);\n    KJ_EXPECT_LOG(DBG, \"[ TEST ] hello\");\n    KJ_EXPECT_LOG(DBG, \"[ PASS ] hello\");\n    KJ_EXPECT(test.server.test(v8System, *test.config, \"hello\", \"default\").wait(test.ws));\n  }\n\n  {\n    TestServer test(config);\n    KJ_EXPECT_LOG(DBG, \"[ TEST ] hello:fail\");\n    KJ_EXPECT_LOG(INFO, \"Error: ded\");\n    KJ_EXPECT_LOG(DBG, \"[ FAIL ] hello:fail\");\n    KJ_EXPECT(!test.server.test(v8System, *test.config, \"hello\", \"fail\").wait(test.ws));\n  }\n\n  {\n    TestServer test(config);\n    KJ_EXPECT_LOG(DBG, \"[ TEST ] hello\");\n    KJ_EXPECT_LOG(DBG, \"[ PASS ] hello\");\n    KJ_EXPECT_LOG(DBG, \"[ TEST ] hello:fail\");\n    KJ_EXPECT_LOG(INFO, \"Error: ded\");\n    KJ_EXPECT_LOG(DBG, \"[ FAIL ] hello:fail\");\n    KJ_EXPECT(!test.server.test(v8System, *test.config, \"hello\", \"*\").wait(test.ws));\n  }\n\n  {\n    TestServer test(config);\n    KJ_EXPECT_LOG(DBG, \"[ TEST ] hello\");\n    KJ_EXPECT_LOG(DBG, \"[ PASS ] hello\");\n    KJ_EXPECT_LOG(DBG, \"[ TEST ] another\");\n    KJ_EXPECT_LOG(INFO, \"other test\");\n    KJ_EXPECT_LOG(DBG, \"[ PASS ] another\");\n    KJ_EXPECT(test.server.test(v8System, *test.config, \"*\", \"default\").wait(test.ws));\n  }\n\n  {\n    TestServer test(config);\n    KJ_EXPECT_LOG(DBG, \"[ TEST ] hello\");\n    KJ_EXPECT_LOG(DBG, \"[ PASS ] hello\");\n    KJ_EXPECT_LOG(DBG, \"[ TEST ] hello:fail\");\n    KJ_EXPECT_LOG(INFO, \"Error: ded\");\n    KJ_EXPECT_LOG(DBG, \"[ FAIL ] hello:fail\");\n    KJ_EXPECT_LOG(DBG, \"[ TEST ] another\");\n    KJ_EXPECT_LOG(INFO, \"other test\");\n    KJ_EXPECT_LOG(DBG, \"[ PASS ] another\");\n    KJ_EXPECT(!test.server.test(v8System, *test.config, \"*\", \"*\").wait(test.ws));\n  }\n}\n\n// =======================================================================================\n\nKJ_TEST(\"Server: JS RPC over HTTP connections\") {\n  // Test that we can send RPC over an ExternalServer pointing back to our own loopback socket,\n  // as long as both are configured with a `capnpConnectHost`.\n\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2024-02-23\",\n          compatibilityFlags = [\"experimental\"],\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `import {WorkerEntrypoint} from \"cloudflare:workers\";\n                `export default {\n                `  async fetch(request, env) {\n                `    return new Response(\"got: \" + await env.OUT.frob(3, 11));\n                `  }\n                `}\n                `export class MyRpc extends WorkerEntrypoint {\n                `  async frob(a, b) { return a * b + 2; }\n                `}\n            )\n          ],\n          bindings = [( name = \"OUT\", service = \"outbound\")]\n        )\n      ),\n      (name = \"outbound\", external = (address = \"loopback\", http = (capnpConnectHost = \"cappy\")))\n    ],\n    sockets = [\n      ( name = \"main\", address = \"test-addr\", service = \"hello\" ),\n      ( name = \"alt1\", address = \"loopback\",\n        service = (name = \"hello\", entrypoint = \"MyRpc\"),\n        http = (capnpConnectHost = \"cappy\")),\n    ]\n  ))\"_kj);\n\n  test.server.allowExperimental();\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\", \"got: 35\");\n}\n\nKJ_TEST(\"Server: Entrypoint binding with props\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2024-02-23\",\n          compatibilityFlags = [\"experimental\"],\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `import {WorkerEntrypoint} from \"cloudflare:workers\";\n                `export default {\n                `  async fetch(request, env) {\n                `    return new Response(\"got: \" + await env.MyRpc.getProps());\n                `  }\n                `}\n                `export class MyRpc extends WorkerEntrypoint {\n                `  getProps() { return this.ctx.props.foo; }\n                `}\n            )\n          ],\n          bindings = [\n            ( name = \"MyRpc\",\n              service = (\n                name = \"hello\",\n                entrypoint = \"MyRpc\",\n                props = (\n                  json = `{\"foo\": 123}\n                )\n              )\n            )\n          ]\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\", address = \"test-addr\", service = \"hello\" ),\n    ]\n  ))\"_kj);\n\n  test.server.allowExperimental();\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\", \"got: 123\");\n}\n\nKJ_TEST(\"Server: ctx.exports self-referential bindings\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2025-02-23\",\n          compatibilityFlags = [\"enable_ctx_exports\"],\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `import { WorkerEntrypoint, DurableObject, WorkflowEntrypoint } from \"cloudflare:workers\";\n                `export default {\n                `  async fetch(request, env, ctx) {\n                `    // First set the actor state the old fashion way, to make sure we get\n                `    // reconnected to the same actor when using self-referential bindings.\n                `    {\n                `      let bindingActor = env.NS.get(env.NS.idFromName(\"qux\"));\n                `      await bindingActor.setValue(234);\n                `    }\n                `\n                `    let actor = ctx.exports.MyActor.get(ctx.exports.MyActor.idFromName(\"qux\"));\n                `    return new Response([\n                `      await ctx.exports.MyEntrypoint.foo(123),\n                `      await ctx.exports.AnotherEntrypoint.bar(321),\n                `      await actor.baz(),\n                `      await ctx.exports.default.corge(555),\n                `      await actor.grault(456),\n                `      ctx.exports.UnconfiguredActor.constructor.name,\n                `      await ctx.exports.MyEntrypoint.myProps(),\n                `      await ctx.exports.MyEntrypoint({props: {foo: 123, bar: \"abc\"}}).myProps(),\n                `      MyWorkflow in ctx.exports,\n                `    ].join(\", \"));\n                `  },\n                `  corge(i) { return `corge: ${i}` }\n                `}\n                `export class MyEntrypoint extends WorkerEntrypoint {\n                `  foo(i) { return `foo: ${i}` }\n                `  grault(i) { return `grault: ${i}` }\n                `  myProps() { return JSON.stringify(this.ctx.props) }\n                `}\n                `export class AnotherEntrypoint extends WorkerEntrypoint {\n                `  bar(i) { return `bar: ${i}` }\n                `}\n                `export class MyActor extends DurableObject {\n                `  setValue(i) { this.value = i; }\n                `  baz() { return `baz: ${this.value}` }\n                `  grault(i) { return this.ctx.exports.MyEntrypoint.grault(i); }\n                `}\n                `export class UnconfiguredActor extends DurableObject {\n                `  qux(i) { return `qux: ${i}` }\n                `}\n                `export class MyWorkflow extends WorkflowEntrypoint {}\n            )\n          ],\n          bindings = [\n            # A regular binding, just here to make sure it doesn't mess up self-referential\n            # channel numbers.\n            ( name = \"INTERNET\", service = \"internet\" ),\n\n            # Similarly, an actor namespace binding.\n            (name = \"NS\", durableObjectNamespace = \"MyActor\")\n          ],\n          durableObjectNamespaces = [\n            ( className = \"MyActor\",\n              uniqueKey = \"mykey\",\n            )\n          ],\n          durableObjectStorage = (inMemory = void)\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\", address = \"test-addr\", service = \"hello\" ),\n    ]\n  ))\"_kj);\n\n  test.server.allowExperimental();\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\",\n      \"foo: 123, bar: 321, baz: 234, corge: 555, grault: 456, LoopbackDurableObjectClass, \"\n      \"{}, {\\\"foo\\\":123,\\\"bar\\\":\\\"abc\\\"}, false\");\n}\n\nKJ_TEST(\"Server: loopback binding calls accept version property\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2025-08-01\",\n          compatibilityFlags = [\"enable_ctx_exports\", \"enable_version_api\"],\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env, ctx) {\n                `    const serviceVersions = await Promise.all([\n                `      ctx.exports.default({ version: {} }),\n                `      ctx.exports.default({ version: { cohort: null } }),\n                `      ctx.exports.default({ version: { cohort: \"test\" } }),\n                `      ctx.exports.default({ props: {}, version: { cohort: \"test\" } }),\n                `    ].map(service => service.version));\n                `    if (serviceVersions.every(version => version === this.version)) {\n                `      return new Response(serviceVersions[0]);\n                `    }\n                `    return new Response(null, { status: 500 });\n                `  },\n                `  get version() { return \"constant\"; },\n                `}\n            )\n          ],\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\", address = \"test-addr\", service = \"hello\" ),\n    ]\n  ))\"_kj);\n\n  test.server.allowExperimental();\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\", \"constant\");\n}\n\n// =======================================================================================\n\n// TODO(beta): Test TLS (send and receive)\n// TODO(beta): Test CLI overrides\n\nKJ_TEST(\"Server: encodeResponseBody: manual option\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    // Make a subrequest with encodeResponseBody: \"manual\"\n                `    let response = await fetch(\"http://subhost/foo\", {\n                `      encodeResponseBody: \"manual\"\n                `    });\n                `\n                `    // Get the raw bytes, which should not be decompressed\n                `    let rawBytes = await response.arrayBuffer();\n                `    let decoder = new TextDecoder();\n                `    let rawText = decoder.decode(rawBytes);\n                `\n                `    return new Response(\n                `      \"Content-Encoding: \" + response.headers.get(\"Content-Encoding\") + \"\\n\" +\n                `      \"Raw content: \" + rawText\n                `    );\n                `  }\n                `}\n            )\n          ]\n        )\n      )\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.sendHttpGet(\"/\");\n\n  auto subreq = test.receiveInternetSubrequest(\"subhost\");\n  subreq.recv(R\"(\n    GET /foo HTTP/1.1\n    Host: subhost\n\n  )\"_blockquote);\n\n  // Send a response with Content-Encoding: gzip, but the body is not actually\n  // compressed - it's just \"fake-gzipped-content\" as plain text\n  subreq.send(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 20\n    Content-Encoding: gzip\n\n    fake-gzipped-content\n  )\"_blockquote);\n\n  // Verify that:\n  // 1. The Content-Encoding header was preserved\n  // 2. The body was not decompressed (we get the raw \"fake-gzipped-content\")\n  conn.recvHttp200(R\"(\n    Content-Encoding: gzip\n    Raw content: fake-gzipped-content)\"_blockquote);\n}\n\nKJ_TEST(\"Server: encodeResponseBody: manual pass-through\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2022-08-17\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env) {\n                `    // Make a subrequest with encodeResponseBody: \"manual\" and pass through the response\n                `    return fetch(\"http://subhost/foo\", {\n                `      encodeResponseBody: \"manual\"\n                `    });\n                `  }\n                `}\n            )\n          ]\n        )\n      )\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.sendHttpGet(\"/\");\n\n  auto subreq = test.receiveInternetSubrequest(\"subhost\");\n  subreq.recv(R\"(\n    GET /foo HTTP/1.1\n    Host: subhost\n\n  )\"_blockquote);\n\n  // Send a response with Content-Encoding: gzip, but the body is not actually\n  // compressed - it's just \"fake-gzipped-content\" as plain text\n  subreq.send(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 20\n    Content-Encoding: gzip\n\n    fake-gzipped-content\n  )\"_blockquote);\n\n  // Verify that the response is passed through verbatim, with:\n  // 1. The Content-Encoding header preserved\n  // 2. The body not decompressed\n  // 3. The body not re-encoded\n  conn.recv(R\"(\n    HTTP/1.1 200 OK\n    Content-Length: 20\n    Content-Encoding: gzip\n\n    fake-gzipped-content)\"_blockquote);\n}\n\nKJ_TEST(\"Server: Catch websocket server errors\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2025-04-01\",\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n               ` export default {\n               `   async fetch(request) {\n               `     try {\n               `        return await handleRequest(request)\n               `     } catch (e) {\n               `        console.log(\"eerrrrr\", e)\n               `        return new Response(\"ok\")\n               `     }\n               `   }\n               ` }\n               `\n               ` let lastError = \"none\";\n               `\n               ` async function handleRequest(request) {\n               `   const upgradeHeader = request.headers.get('Upgrade');\n               `   if (!upgradeHeader || upgradeHeader !== 'websocket') {\n               `       return new Response('Expected Upgrade: websocket' , { status: 426 });\n               `   }\n               `\n               `   const webSocketPair = new WebSocketPair();\n               `   const [client, server] = Object.values(webSocketPair);\n               `\n               `   server.accept();\n               `   server.addEventListener('message', event => {\n               `       if (event.data === \"getLastError\") {\n               `         server.send(lastError)\n               `       } else {\n               `         let msg = event.data\n               `         server.send(msg)\n               `       }\n               `   });\n               `\n               `   server.addEventListener('error', event => {\n               `     lastError = event.message;\n               `   });\n               `\n               `   return new Response(null, {\n               `       status: 101,\n               `       webSocket: client,\n               `   });\n               ` }\n            )\n          ]\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj);\n\n  class NotVeryGoodEntropySource: public kj::EntropySource {\n   public:\n    void generate(kj::ArrayPtr<byte> buffer) override {\n      buffer.fill('4');\n    }\n  };\n\n  KJ_EXPECT_LOG(ERROR,\n      \"jsg.Error: WebSocket protocol error; protocolError.statusCode = 1009; protocolError.description = Message is too large: 34603008 > 33554432\");\n  test.start();\n  auto& waitScope = test.getWaitScope();\n\n  kj::HttpHeaderTable headerTable;\n  NotVeryGoodEntropySource entropySource;\n  kj::HttpHeaders headers(headerTable);\n  headers.setPtr(kj::HttpHeaderId::HOST, \"foo\");\n  headers.setPtr(kj::HttpHeaderId::UPGRADE, \"websocket\");\n  {\n    auto wsConn = test.connect(\"test-addr\");\n    auto client = kj::newHttpClient(\n        headerTable, wsConn.getStream(), kj::HttpClientSettings{.entropySource = entropySource});\n    auto res = client->openWebSocket(\"/\", headers).wait(waitScope);\n    KJ_ASSERT(res.statusCode == 101, res.statusCode, res.statusText);\n    auto ws = kj::mv(res.webSocketOrBody.get<kj::Own<kj::WebSocket>>());\n    const auto smallMessage = kj::str(\"hello\");\n    ws->send(smallMessage).wait(waitScope);\n    auto smallResponse = ws->receive().wait(waitScope);\n    KJ_EXPECT(smallResponse.get<kj::String>() == smallMessage);\n    const auto bigMessage = kj::heapArray<kj::byte>(33 * 1024 * 1024);\n    auto sendProm =\n        kj::evalNow([&]() { return ws->send(bigMessage); }).then([]() {}, [](kj::Exception ex) {});\n    // Message is too big; we should close the connection.\n    auto msg = ws->receive().wait(waitScope);\n    sendProm.wait(waitScope);\n    auto& resp = msg.get<kj::WebSocket::Close>();\n    KJ_EXPECT(resp.code == 1009);  // WebSocket-ese for \"message too large\"\n  }\n  {\n    auto wsConn = test.connect(\"test-addr\");\n    headers.setPtr(kj::HttpHeaderId::HOST, \"foo\");\n    headers.setPtr(kj::HttpHeaderId::UPGRADE, \"websocket\");\n    auto client = kj::newHttpClient(\n        headerTable, wsConn.getStream(), kj::HttpClientSettings{.entropySource = entropySource});\n    auto res = client->openWebSocket(\"/\", headers).wait(waitScope);\n    KJ_ASSERT(res.statusCode == 101, res.statusCode, res.statusText);\n    auto ws = kj::mv(res.webSocketOrBody.get<kj::Own<kj::WebSocket>>());\n    const auto query = kj::str(\"getLastError\");\n    ws->send(query).wait(waitScope);\n    auto response = ws->receive().wait(waitScope);\n\n    kj::StringPtr responseString = response.get<kj::String>();\n    KJ_EXPECT(responseString.find(\"1009\"_kjc) != kj::none, responseString);  // Error code\n    KJ_EXPECT(responseString.find(\"Message is too large\"_kjc) != kj::none, responseString);\n    ws->close(1000, \"\").wait(waitScope);\n  }\n}\n\nKJ_TEST(\"Server: Durable Object facets\") {\n  kj::StringPtr config = R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2025-04-01\",\n          compatibilityFlags = [\"experimental\",\"enable_ctx_exports\"],\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `import { DurableObject } from \"cloudflare:workers\";\n                `export default {\n                `  async fetch(request, env, ctx) {\n                `    let id = ctx.exports.MyActorClass.idFromName(\"name\");\n                `    let actor = ctx.exports.MyActorClass.get(id);\n                `    return await actor.fetch(request);\n                `  }\n                `}\n                `export class MyActorClass extends DurableObject {\n                `  async fetch(request) {\n                `    let results = [];\n                `\n                `    if (request.url.endsWith(\"/part1\")) {\n                `      let foo = this.ctx.facets.get(\"foo\",\n                `          () => ({class: this.ctx.exports.CounterFacet, id: \"abc\"}));\n                `      results.push(await foo.increment(true));  // increments foo\n                `      results.push(await foo.increment());  // increments foo\n                `      results.push(await foo.increment());  // increments foo\n                `      await foo.assertId(\"abc\");\n                `\n                `      let bar = this.ctx.facets.get(\"bar\", () => ({class: this.env.NESTED}));\n                `      results.push(await bar.increment(\"foo\", true));  // increments bar.foo\n                `      results.push(await bar.increment(\"bar\", true));  // increments bar.bar\n                `      results.push(await bar.increment(\"foo\"));        // increments bar.foo\n                `      await bar.assertId(this.ctx.id.toString());\n                `\n                `      // Get foo again to make sure we get the same object.\n                `      let foo2 = this.ctx.facets.get(\"foo\", () => {\n                `        throw new Error(\"callback should not be called when already running\");\n                `      });\n                `      results.push(await foo2.increment());  // increments foo\n                `      results.push(await foo.increment());   // increments foo\n                `      await foo.assertId(\"abc\");\n                `    } else if (request.url.endsWith(\"/part2\")) {\n                `      let callbackCount = 0;\n                `\n                `      // Get in a different order from before to make sure ID assignment is\n                `      // consistent.\n                `      let bar = this.ctx.facets.get(\"bar\", () => {\n                `        ++callbackCount;\n                `        return {class: this.env.NESTED};\n                `      });\n                `      results.push(await bar.increment(\"bar\", true));  // increments bar.bar\n                `      results.push(await bar.increment(\"foo\", true));  // increments bar.foo\n                `      let foo = this.ctx.facets.get(\"foo\", async () => {\n                `        await Promise.resolve();  // prove that callback can be async\n                `        ++callbackCount;\n                `        return {class: this.env.COUNTER, id: \"abc\"};\n                `      });\n                `      results.push(await foo.increment(true));  // increments foo\n                `\n                `      if (callbackCount !== 2) {\n                `        throw new Error(`callbackCount = ${callbackCount} (expected 2)`);\n                `      }\n                `\n                `      // Force \"foo\" to abort, so we can start it up with a different class.\n                `      this.ctx.facets.abort(\"foo\", new Error(\"test abort facet\"));\n                `\n                `      let foo2 = this.ctx.facets.get(\n                `          \"foo\", () => ({class: this.env.EXFILTRATOR, id: \"abc\"}));\n                `      results.push(await foo2.exfiltrate());\n                `\n                `      try {\n                `        await foo.increment();\n                `        throw new Error(\"broken stub didn't throw?\");\n                `      } catch (err) {\n                `        if (err.message != \"test abort facet\") {\n                `          throw err;\n                `        }\n                `      }\n                `\n                `      // Delete bar, which recursively deletes its children.\n                `      this.ctx.facets.delete(\"bar\");\n                `    } else if (request.url.endsWith(\"/props\")) {\n                `      results.push(JSON.stringify(this.ctx.props));\n                `\n                `      let prop1 = this.ctx.facets.get(\"prop1\",\n                `          () => ({class: this.env.COUNTER, id: \"abc\"}));\n                `      results.push(await prop1.myProps());\n                `\n                `      let prop2 = this.ctx.facets.get(\"prop2\",\n                `          () => ({class: this.ctx.exports.CounterFacet, id: \"abc\"}));\n                `      results.push(await prop2.myProps());\n                `\n                `      let prop3 = this.ctx.facets.get(\"prop3\",\n                `          () => ({class: this.ctx.exports.CounterFacet({props: {bProp: 321}}),\n                `                  id: \"abc\"}));\n                `      results.push(await prop3.myProps());\n                `\n                `      let prop4 = this.ctx.facets.get(\"prop4\",\n                `          () => ({class: this.ctx.exports.MyActorClass, id: \"abc\"}));\n                `      results.push(await prop4.mainClassProps());\n                `\n                `      let prop5 = this.ctx.facets.get(\"prop5\",\n                `          () => ({class: this.ctx.exports.MyActorClass({props: {cProp: 555}}),\n                `                  id: \"abc\"}));\n                `      results.push(await prop5.mainClassProps());\n                `    } else {\n                `      throw new Error(`bad url: ${request.url}`);\n                `    }\n                `\n                `    return new Response(results.join(\" \"));\n                `  }\n                `  mainClassProps() { return JSON.stringify(this.ctx.props) }\n                `}\n                `export class CounterFacet extends DurableObject {\n                `  async increment(first) {\n                `    let storedI = (await this.ctx.storage.get(\"value\")) || 0;\n                `    if (first) {\n                `      this.i = storedI;\n                `    } else if (this.i != storedI) {\n                `      throw new Error(\"inconsistent stored value ${storedI} != ${this.i}\");\n                `    }\n                `    this.ctx.storage.put(\"value\", this.i + 1);\n                `    return this.i++;\n                `  }\n                `  assertId(id) {\n                `    if (this.ctx.id.toString() != id) {\n                `      throw new Error(`Wrong ID, expected ${id}, got ${this.ctx.id}`);\n                `    }\n                `  }\n                `  myProps() { return JSON.stringify(this.ctx.props) }\n                `}\n                `export class NestedFacet extends DurableObject {\n                `  increment(name, first) {\n                `    let facet = this.ctx.facets.get(name, () => ({class: this.env.COUNTER}));\n                `    return facet.increment(first);\n                `  }\n                `  assertId(id) {\n                `    if (this.ctx.id.toString() != id) {\n                `      throw new Error(`Wrong ID, expected ${id}, got ${this.ctx.id}`);\n                `    }\n                `  }\n                `}\n                `export class ExfiltrationFacet extends DurableObject {\n                `  exfiltrate() {\n                `    return this.ctx.storage.get(\"value\");\n                `  }\n                `}\n            )\n          ],\n          bindings = [\n            ( name = \"COUNTER\",\n              durableObjectClass = (\n                name = \"hello\",\n                entrypoint = \"CounterFacet\",\n                props = (\n                  json = `{\"aProp\": 123}\n                )\n              )\n            ),\n            (name = \"NESTED\", durableObjectClass = (name = \"hello\", entrypoint = \"NestedFacet\")),\n            ( name = \"EXFILTRATOR\",\n              durableObjectClass = (name = \"hello\", entrypoint = \"ExfiltrationFacet\") )\n          ],\n          durableObjectNamespaces = [\n            ( className = \"MyActorClass\",\n              uniqueKey = \"mykey\",\n            )\n          ],\n          durableObjectStorage = (localDisk = \"my-disk\")\n        )\n      ),\n      ( name = \"my-disk\",\n        disk = (\n          path = \"../../do-storage\",\n          writable = true,\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj;\n\n  // Create a directory outside of the test scope which we can use across multiple TestServers.\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n\n  {\n    TestServer test(config);\n\n    // Link our directory into the test filesystem.\n    test.root->transfer(\n        kj::Path({\"do-storage\"_kj}), kj::WriteMode::CREATE, *dir, nullptr, kj::TransferMode::LINK);\n\n    test.server.allowExperimental();\n    test.start();\n    auto conn = test.connect(\"test-addr\");\n    conn.httpGet200(\"/part1\", \"0 1 2 0 0 1 3 4\");\n  }\n\n  // Verify the expected files exist.\n  auto nsDir = dir->openSubdir(kj::Path({\"mykey\"}));\n  KJ_EXPECT(nsDir->exists(\n      kj::Path({\"3652ef6221834806dc8df802d1d216e27b7d07e0a6b7adf6cfdaeec90f06459a.sqlite\"})));\n  KJ_EXPECT(nsDir->exists(\n      kj::Path({\"3652ef6221834806dc8df802d1d216e27b7d07e0a6b7adf6cfdaeec90f06459a.1.sqlite\"})));\n  KJ_EXPECT(nsDir->exists(\n      kj::Path({\"3652ef6221834806dc8df802d1d216e27b7d07e0a6b7adf6cfdaeec90f06459a.2.sqlite\"})));\n  KJ_EXPECT(nsDir->exists(\n      kj::Path({\"3652ef6221834806dc8df802d1d216e27b7d07e0a6b7adf6cfdaeec90f06459a.3.sqlite\"})));\n  KJ_EXPECT(nsDir->exists(\n      kj::Path({\"3652ef6221834806dc8df802d1d216e27b7d07e0a6b7adf6cfdaeec90f06459a.4.sqlite\"})));\n  KJ_EXPECT(nsDir->exists(\n      kj::Path({\"3652ef6221834806dc8df802d1d216e27b7d07e0a6b7adf6cfdaeec90f06459a.facets\"})));\n\n  // We should only have created four child facets (foo, bar, bar.foo, bar.bar). No ID 5 should\n  // exist.\n  KJ_EXPECT(!nsDir->exists(\n      kj::Path({\"3652ef6221834806dc8df802d1d216e27b7d07e0a6b7adf6cfdaeec90f06459a.5.sqlite\"})));\n\n  // We didn't create any other durable objects in the namespace. All files in the namespace should\n  // be prefixed with our one DO ID.\n  for (auto& name: nsDir->listNames()) {\n    KJ_EXPECT(name.startsWith(\"3652ef6221834806dc8df802d1d216e27b7d07e0a6b7adf6cfdaeec90f06459a.\"),\n        \"unexpected file found in namespace storage\", name);\n  }\n\n  // Start a new server, make sure it's able to load the files again.\n  {\n    TestServer test(config);\n\n    // Link our directory into the test filesystem.\n    test.root->transfer(\n        kj::Path({\"do-storage\"_kj}), kj::WriteMode::CREATE, *dir, nullptr, kj::TransferMode::LINK);\n\n    test.server.allowExperimental();\n    test.start();\n    auto conn = test.connect(\"test-addr\");\n    conn.httpGet200(\"/part2\", \"1 2 5 6\");\n  }\n\n  // Root and foo still exist, bar does not.\n  KJ_EXPECT(nsDir->exists(\n      kj::Path({\"3652ef6221834806dc8df802d1d216e27b7d07e0a6b7adf6cfdaeec90f06459a.sqlite\"})));\n  KJ_EXPECT(nsDir->exists(\n      kj::Path({\"3652ef6221834806dc8df802d1d216e27b7d07e0a6b7adf6cfdaeec90f06459a.1.sqlite\"})));\n  KJ_EXPECT(!nsDir->exists(\n      kj::Path({\"3652ef6221834806dc8df802d1d216e27b7d07e0a6b7adf6cfdaeec90f06459a.2.sqlite\"})));\n  KJ_EXPECT(!nsDir->exists(\n      kj::Path({\"3652ef6221834806dc8df802d1d216e27b7d07e0a6b7adf6cfdaeec90f06459a.3.sqlite\"})));\n  KJ_EXPECT(!nsDir->exists(\n      kj::Path({\"3652ef6221834806dc8df802d1d216e27b7d07e0a6b7adf6cfdaeec90f06459a.4.sqlite\"})));\n\n  // Test facets can have custom ctx.props.\n  {\n    TestServer test(config);\n\n    // We don't need the existing storage but the path does have to exist for the test to work.\n    test.root->openSubdir(kj::Path({\"do-storage\"_kj}), kj::WriteMode::CREATE);\n\n    test.server.allowExperimental();\n    test.start();\n    auto conn = test.connect(\"test-addr\");\n    conn.httpGet200(\"/props\", \"{} {\\\"aProp\\\":123} {} {\\\"bProp\\\":321} {} {\\\"cProp\\\":555}\");\n  }\n}\n\nKJ_TEST(\"Server: Durable Object facet limits\") {\n  kj::StringPtr config = R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2025-04-01\",\n          compatibilityFlags = [\"experimental\"],\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `import { DurableObject } from \"cloudflare:workers\";\n                `export default {\n                `  async fetch(request, env, ctx) {\n                `    let id = env.MY_ACTOR.idFromName(\"limits\");\n                `    let actor = env.MY_ACTOR.get(id);\n                `    return await actor.fetch(request);\n                `  }\n                `}\n                `export class MyActorClass extends DurableObject {\n                `  async fetch(request) {\n                `    let url = new URL(request.url);\n                `    switch (url.pathname) {\n                `      case \"/name-too-long\": {\n                `        try {\n                `          this.ctx.facets.get(\"x\".repeat(257),\n                `              () => ({class: this.env.RECURSIVE}));\n                `          return new Response(\"no error\");\n                `        } catch (e) {\n                `          return new Response(e.constructor.name + \": \" + e.message);\n                `        }\n                `      }\n                `      case \"/name-256-ok\": {\n                `        this.ctx.facets.get(\"x\".repeat(256),\n                `            () => ({class: this.env.RECURSIVE}));\n                `        return new Response(\"ok\");\n                `      }\n                `      case \"/abort-name-too-long\": {\n                `        try {\n                `          this.ctx.facets.abort(\"x\".repeat(257), new Error(\"test\"));\n                `          return new Response(\"no error\");\n                `        } catch (e) {\n                `          return new Response(e.constructor.name + \": \" + e.message);\n                `        }\n                `      }\n                `      case \"/delete-name-too-long\": {\n                `        try {\n                `          this.ctx.facets.delete(\"x\".repeat(257));\n                `          return new Response(\"no error\");\n                `        } catch (e) {\n                `          return new Response(e.constructor.name + \": \" + e.message);\n                `        }\n                `      }\n                `      case \"/depth-ok\": {\n                `        // Create 3 levels of facets below root = 4 total (the max).\n                `        let facet = this.ctx.facets.get(\"a\",\n                `            () => ({class: this.env.RECURSIVE}));\n                `        return new Response(await facet.nestOk(2));\n                `      }\n                `      case \"/depth-exceeded\": {\n                `        // Create 3 levels below root, then try one more.\n                `        let facet = this.ctx.facets.get(\"b\",\n                `            () => ({class: this.env.RECURSIVE}));\n                `        return new Response(await facet.nestDeeper(2));\n                `      }\n                `    }\n                `  }\n                `}\n                `export class RecursiveFacet extends DurableObject {\n                `  async nestOk(remaining) {\n                `    if (remaining <= 0) return \"ok\";\n                `    let facet = this.ctx.facets.get(\"child\",\n                `        () => ({class: this.env.RECURSIVE}));\n                `    return await facet.nestOk(remaining - 1);\n                `  }\n                `  async nestDeeper(remaining) {\n                `    if (remaining <= 0) {\n                `      try {\n                `        this.ctx.facets.get(\"too-deep\",\n                `            () => ({class: this.env.RECURSIVE}));\n                `        return \"no error, unexpected\";\n                `      } catch (e) {\n                `        return e.constructor.name + \": \" + e.message;\n                `      }\n                `    }\n                `    let facet = this.ctx.facets.get(\"child\",\n                `        () => ({class: this.env.RECURSIVE}));\n                `    return await facet.nestDeeper(remaining - 1);\n                `  }\n                `}\n            )\n          ],\n          bindings = [\n            (name = \"MY_ACTOR\", durableObjectNamespace = \"MyActorClass\"),\n            (name = \"RECURSIVE\",\n              durableObjectClass = (name = \"hello\", entrypoint = \"RecursiveFacet\"))\n          ],\n          durableObjectNamespaces = [\n            ( className = \"MyActorClass\",\n              uniqueKey = \"mykey\",\n            )\n          ],\n          durableObjectStorage = (localDisk = \"my-disk\")\n        )\n      ),\n      ( name = \"my-disk\",\n        disk = (\n          path = \"../../do-storage\",\n          writable = true,\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ]\n  ))\"_kj;\n\n  TestServer test(config);\n  test.root->openSubdir(kj::Path({\"do-storage\"_kj}), kj::WriteMode::CREATE);\n  test.server.allowExperimental();\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n\n  // Name length limit.\n  conn.httpGet200(\"/name-too-long\", \"TypeError: Facet name is too long (max 256 characters).\");\n  conn.httpGet200(\"/name-256-ok\", \"ok\");\n  conn.httpGet200(\n      \"/abort-name-too-long\", \"TypeError: Facet name is too long (max 256 characters).\");\n  conn.httpGet200(\n      \"/delete-name-too-long\", \"TypeError: Facet name is too long (max 256 characters).\");\n\n  // Depth limit.\n  conn.httpGet200(\"/depth-ok\", \"ok\");\n  conn.httpGet200(\"/depth-exceeded\",\n      \"Error: Facet nesting depth limit exceeded. \"\n      \"The maximum depth including the root Durable Object is 4.\");\n}\n\nKJ_TEST(\"Server: Pass service stubs in ctx.props.\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2025-08-01\",\n          compatibilityFlags = [\"enable_ctx_exports\"],\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `import { WorkerEntrypoint } from \"cloudflare:workers\";\n                `export default {\n                `  async fetch(request, env, ctx) {\n                `    let props = {\n                `      foo: ctx.exports.FooEntry({props: {greeting: \"Hello\"}}),\n                `      foo2: ctx.exports.FooEntry({props: {greeting: \"Welcome\"}}),\n                `    }\n                `    let result = await ctx.exports.BarEntry({props}).run();\n                `    return new Response(result);\n                `  },\n                `}\n                `export class FooEntry extends WorkerEntrypoint {\n                `  greet(name) { return `${this.ctx.props.greeting}, ${name}!` }\n                `}\n                `export class BarEntry extends WorkerEntrypoint {\n                `  async run() {\n                `    let greet1 = await this.ctx.props.foo.greet(\"Alice\");\n                `    let greet2 = await this.ctx.props.foo2.greet(\"Bob\");\n                `    return [greet1, greet2].join(\"\\n\");\n                `  }\n                `}\n            )\n          ],\n        )\n      ),\n    ],\n    sockets = [\n      ( name = \"main\", address = \"test-addr\", service = \"hello\" ),\n    ]\n  ))\"_kj);\n\n  test.server.allowExperimental();\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\", \"Hello, Alice!\\nWelcome, Bob!\");\n}\n\n#if __linux__\n// This test uses pipe2 and dup2 to capture stdout which is far easier on linux.\n\nstruct FdPair {\n  kj::AutoCloseFd output;\n  kj::AutoCloseFd input;\n};\n\nauto makePipeFds() {\n  int pipeFds[2];\n  KJ_SYSCALL(pipe2(pipeFds, 0));\n\n  return FdPair{\n    .output = kj::AutoCloseFd(pipeFds[0]),\n    .input = kj::AutoCloseFd(pipeFds[1]),\n  };\n}\n\ntemplate <typename Func>\nauto expectLogLine(int fd, Func&& f) {\n  char buffer[4096];\n  int pos = 0;\n  char c;\n  while (read(fd, &c, 1) == 1) {\n    if (c == '\\n') {\n      break;\n    }\n    if (pos < sizeof(buffer) - 1) {\n      buffer[pos++] = c;\n    }\n  }\n  buffer[pos] = '\\0';  // null-terminate\n\n  kj::StringPtr logline(buffer);\n  f(logline);\n}\n\nKJ_TEST(\"Server: structured logging with console methods\") {\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2024-11-01\",\n          compatibilityFlags = [\n            \"nodejs_compat\",\n            \"experimental\",\n            \"enable_nodejs_process_v2\"\n          ],\n          modules = [\n            ( name = \"main.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env, ctx) {\n                `    console.log(\"This is a log message\", { key: \"value\" });\n                `    console.info(\"This is an info message\");\n                `    console.warn(\"This is a warning message\");\n                `    console.error(\"This is an error message\");\n                `    console.debug(\"This is a debug message\");\n                `    console.debug({a: 1});\n                `\n                `    process.stdout.write(\"stdout\");\n                `    process.stdout.write(\"stdout with\\nmultiple\\nnewlines\\nlog\");\n                `    process.stdout.write(\"ged\");\n                `    process.stderr.write(\"stderr\");\n                `    await 0;\n                `    process.stderr.write(\"after await\");\n                `\n                `    try {\n                `      throw new Error(\"Test exception for structured logging\");\n                `    } catch (e) {\n                `      console.error(e);\n                `    }\n                `\n                `    return new Response(\"Structured logging test completed\");\n                `  }\n                `}\n            )\n          ]\n        )\n      )\n    ],\n    sockets = [\n      ( name = \"main\",\n        address = \"test-addr\",\n        service = \"hello\"\n      )\n    ],\n    # Enable structured logging for this test\n    structuredLogging = true\n  ))\"_kj,\n      Worker::ConsoleMode::STDOUT);\n  auto interceptorPipe = makePipeFds();\n  int originalStdout = dup(STDOUT_FILENO);\n  int originalStderr = dup(STDERR_FILENO);\n  KJ_SYSCALL(dup2(interceptorPipe.input.get(), STDOUT_FILENO));\n  KJ_SYSCALL(dup2(interceptorPipe.input.get(), STDERR_FILENO));\n  interceptorPipe.input = nullptr;\n  KJ_DEFER({\n    // Restore stdout/stderr\n    KJ_SYSCALL(dup2(originalStdout, STDOUT_FILENO));\n    close(originalStdout);\n    KJ_SYSCALL(dup2(originalStderr, STDERR_FILENO));\n    close(originalStderr);\n  });\n\n  test.server.allowExperimental();\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n\n  conn.sendHttpGet(\"/\");\n  conn.recvHttp200(\"Structured logging test completed\");\n\n  expectLogLine(interceptorPipe.output.get(), [](kj::StringPtr logline) {\n    KJ_ASSERT(logline.contains(R\"({\"timestamp\")\"), logline);\n    KJ_ASSERT(logline.contains(R\"(\"level\":\"log\")\"), logline);\n    KJ_ASSERT(logline.contains(R\"(\"message\":\"This is a log message { key: 'value' }\")\"), logline);\n  });\n\n  expectLogLine(interceptorPipe.output.get(), [](kj::StringPtr logline) {\n    KJ_ASSERT(logline.contains(R\"(\"level\":\"info\")\"), logline);\n    KJ_ASSERT(logline.contains(R\"(\"message\":\"This is an info message\")\"), logline);\n  });\n\n  expectLogLine(interceptorPipe.output.get(), [](kj::StringPtr logline) {\n    KJ_ASSERT(logline.contains(R\"(\"level\":\"warn\")\"), logline);\n    KJ_ASSERT(logline.contains(R\"(\"message\":\"This is a warning message\")\"), logline);\n  });\n\n  expectLogLine(interceptorPipe.output.get(), [](kj::StringPtr logline) {\n    KJ_ASSERT(logline.contains(R\"(\"level\":\"error\")\"), logline);\n    KJ_ASSERT(logline.contains(R\"(\"message\":\"This is an error message\")\"), logline);\n  });\n\n  expectLogLine(interceptorPipe.output.get(), [](kj::StringPtr logline) {\n    KJ_ASSERT(logline.contains(R\"(\"level\":\"debug\")\"), logline);\n    KJ_ASSERT(logline.contains(R\"(\"message\":\"This is a debug message\")\"), logline);\n  });\n\n  expectLogLine(interceptorPipe.output.get(), [](kj::StringPtr logline) {\n    KJ_ASSERT(logline.contains(R\"(\"level\":\"debug\")\"), logline);\n    KJ_ASSERT(logline.contains(R\"(\"message\":\"{ a: 1 }\")\"), logline);\n  });\n\n  // process.stdout should be logs split by newline\n  expectLogLine(interceptorPipe.output.get(), [](kj::StringPtr logline) {\n    KJ_ASSERT(logline.contains(R\"(\"level\":\"log\")\"), logline);\n    KJ_ASSERT(logline.contains(R\"(\"message\":\"stdout: stdoutstdout with\")\"), logline);\n  });\n\n  expectLogLine(interceptorPipe.output.get(), [](kj::StringPtr logline) {\n    KJ_ASSERT(logline.contains(R\"(\"level\":\"log\")\"), logline);\n    KJ_ASSERT(logline.contains(R\"(\"message\":\"stdout: multiple\")\"), logline);\n  });\n\n  expectLogLine(interceptorPipe.output.get(), [](kj::StringPtr logline) {\n    KJ_ASSERT(logline.contains(R\"(\"level\":\"log\")\"), logline);\n    KJ_ASSERT(logline.contains(R\"(\"message\":\"stdout: newlines\")\"), logline);\n  });\n\n  expectLogLine(interceptorPipe.output.get(), [](kj::StringPtr logline) {\n    KJ_ASSERT(logline.contains(R\"(\"level\":\"log\")\"), logline);\n    KJ_ASSERT(logline.contains(R\"(\"message\":\"stdout: logged\")\"), logline);\n  });\n\n  // process.stderr should be info\n  expectLogLine(interceptorPipe.output.get(), [](kj::StringPtr logline) {\n    KJ_ASSERT(logline.contains(R\"(\"level\":\"log\")\"), logline);\n    KJ_ASSERT(logline.contains(R\"(\"message\":\"stderr: stderr\")\"), logline);\n  });\n\n  expectLogLine(interceptorPipe.output.get(), [](kj::StringPtr logline) {\n    KJ_ASSERT(logline.contains(R\"(\"level\":\"error\")\"), logline);\n    KJ_ASSERT(\n        logline.contains(\n            R\"_(\"message\":\"Error: Test exception for structured logging\\n    at Object.fetch (main.js:18:13)\")_\"),\n        logline);\n  });\n\n  expectLogLine(interceptorPipe.output.get(), [](kj::StringPtr logline) {\n    KJ_ASSERT(logline.contains(R\"(\"level\":\"log\")\"), logline);\n    KJ_ASSERT(logline.contains(R\"(\"message\":\"stderr: after await\")\"), logline);\n  });\n}\n\nKJ_TEST(\"Server: transpiled typescript\") {\n  TestServer test(singleWorker(R\"((\n    compatibilityDate = \"2025-08-01\",\n    compatibilityFlags = [\"typescript_strip_types\"],\n    modules = [\n      ( name = \"main.ts\",\n        esModule =\n          `export default {\n          `  async fetch(request): Promise<Response> {\n          `    return new Response(\"Hello from typescript\");\n          `  }\n          `} satisfies ExportedHandler<Env>;\n      )\n    ]\n  ))\"_kj));\n  test.server.allowExperimental();\n  test.start();\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\", \"Hello from typescript\");\n}\n\nKJ_TEST(\"Server: transpiled typescript failure\") {\n  TestServer test(singleWorker(R\"((\n    compatibilityDate = \"2025-08-01\",\n    compatibilityFlags = [\"typescript_strip_types\"],\n    modules = [\n      ( name = \"main.ts\",\n        esModule =\n          `enum Foo { A, B }\n          `export default {\n          `  async fetch(request): Promise<Response> {\n          `    return new Response(\"Hello from typescript\");\n          `  }\n          `} satisfies ExportedHandler<Env>;\n      )\n    ]\n  ))\"_kj));\n  test.server.allowExperimental();\n\n  test.expectErrors(R\"(service hello: Error transpiling main.ts : Unsupported syntax\n    TypeScript enum is not supported in strip-only mode\nservice hello: Uncaught TypeError: Main module must be an ES module.\n)\");\n}\n\n#endif  // __linux__\n\n// Helper types for V8 serialization in tests\nclass SerializationContextGlobalObject: public jsg::Object, public jsg::ContextGlobal {};\nstruct SerializationTestContext: public SerializationContextGlobalObject {\n  JSG_RESOURCE_TYPE(SerializationTestContext) {}\n};\nJSG_DECLARE_ISOLATE_TYPE(SerializationTestIsolate, SerializationTestContext);\n\n// Helper function to serialize JavaScript values using V8\nkj::Array<kj::byte> serializeJsArguments(\n    std::initializer_list<std::function<jsg::JsValue(jsg::Lock&)>> argBuilders) {\n  // Create an evaluator to get access to a V8 isolate\n  jsg::test::Evaluator<SerializationTestContext, SerializationTestIsolate> evaluator(v8System);\n\n  kj::Array<kj::byte> result;\n  evaluator.run([&](auto& lock) {\n    jsg::Lock& js = lock;\n\n    // Create an array with the arguments\n    auto argsArray = js.arr();\n    for (auto& builder: argBuilders) {\n      argsArray.add(js, builder(js));\n    }\n\n    // Serialize the array using jsg::Serializer\n    jsg::Serializer serializer(js,\n        jsg::Serializer::Options{\n          .version = 15,\n          .omitHeader = false,\n        });\n    serializer.write(js, jsg::JsValue(argsArray));\n    result = serializer.release().data;\n  });\n\n  return result;\n}\n\n// Helper function to deserialize V8 data and convert to JSON string\nkj::String deserializeV8ToJson(kj::ArrayPtr<const kj::byte> data) {\n  jsg::test::Evaluator<SerializationTestContext, SerializationTestIsolate> evaluator(v8System);\n\n  kj::String result;\n  evaluator.run([&](auto& lock) {\n    jsg::Lock& js = lock;\n\n    // Deserialize the V8 data\n    jsg::Deserializer deserializer(js, data, kj::none, kj::none, jsg::Deserializer::Options{});\n    auto value = deserializer.readValue(js);\n\n    // Convert to JSON string\n    result = js.serializeJson(value);\n  });\n\n  return result;\n}\n\nKJ_TEST(\"Server: debug port RPC calls\") {\n  // This test connects to the debug port via Cap'n Proto RPC and makes actual RPC calls.\n  TestServer test(R\"((\n    services = [\n      ( name = \"hello\",\n        worker = (\n          compatibilityDate = \"2024-01-01\",\n          modules = [\n            ( name = \"worker.js\",\n              esModule =\n                `export default {\n                `  async fetch(request) {\n                `    return new Response(\"Hello from hello service\");\n                `  }\n                `}\n            )\n          ]\n        )\n      ),\n      ( name = \"world\",\n        worker = (\n          compatibilityDate = \"2024-01-01\",\n          modules = [\n            ( name = \"worker.js\",\n              esModule =\n                `export default {\n                `  async fetch(request) {\n                `    return new Response(\"Hello from world service\");\n                `  }\n                `}\n            )\n          ]\n        )\n      ),\n      ( name = \"named-entrypoint\",\n        worker = (\n          compatibilityDate = \"2024-01-01\",\n          modules = [\n            ( name = \"worker.js\",\n              esModule =\n                `export let customHandler = {\n                `  async fetch(request) {\n                `    return new Response(\"Hello from custom entrypoint\");\n                `  }\n                `}\n                `export default {\n                `  async fetch(request) {\n                `    return new Response(\"Default handler\");\n                `  }\n                `}\n            )\n          ]\n        )\n      ),\n      ( name = \"props-service\",\n        worker = (\n          compatibilityDate = \"2024-01-01\",\n          modules = [\n            ( name = \"worker.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env, ctx) {\n                `    const greeting = ctx?.props?.greeting || \"no greeting\";\n                `    const name = ctx?.props?.name || \"no name\";\n                `    return new Response(\"Props: \" + greeting + \" \" + name);\n                `  }\n                `}\n            )\n          ]\n        )\n      ),\n      ( name = \"actor-service\",\n        worker = (\n          compatibilityDate = \"2024-01-01\",\n          modules = [\n            ( name = \"worker.js\",\n              esModule =\n                `export class MyActor {\n                `  constructor(state, env) {\n                `    this.state = state;\n                `  }\n                `  async fetch(request) {\n                `    const url = new URL(request.url);\n                `    if (url.pathname === \"/increment\") {\n                `      let count = (await this.state.storage.get(\"count\")) || 0;\n                `      count++;\n                `      await this.state.storage.put(\"count\", count);\n                `      return new Response(\"Count: \" + count);\n                `    }\n                `    return new Response(\"Actor: \" + this.state.id.toString());\n                `  }\n                `}\n            )\n          ],\n          durableObjectNamespaces = [\n            ( className = \"MyActor\", uniqueKey = \"test-actor\" )\n          ],\n          durableObjectStorage = ( inMemory = void )\n        )\n      ),\n      ( name = \"rpc-service\",\n        worker = (\n          compatibilityDate = \"2024-09-02\",\n          compatibilityFlags = [\"experimental\"],\n          modules = [\n            ( name = \"worker.js\",\n              esModule =\n                `import {WorkerEntrypoint} from \"cloudflare:workers\";\n                `export default class extends WorkerEntrypoint {\n                `  async add(a, b) {\n                `    return a + b;\n                `  }\n                `  async multiply(x, y) {\n                `    return x * y;\n                `  }\n                `  async greet(name) {\n                `    return \"Hello, \" + name + \"!\";\n                `  }\n                `}\n            )\n          ]\n        )\n      )\n    ],\n    sockets = [\n      ( name = \"main\", address = \"test-addr\", service = \"hello\" )\n    ]\n  ))\"_kj);\n\n  // Enable the debug port on a unique address\n  test.server.enableDebugPort(kj::str(\"debug-addr\"));\n\n  // Allow experimental features for RPC service\n  test.server.allowExperimental();\n\n  test.start();\n\n  // Connect to the debug port\n  auto debugConn = test.connect(\"debug-addr\");\n\n  // Create a TwoPartyClient for Cap'n Proto RPC\n  capnp::TwoPartyClient client(debugConn.getStream());\n\n  // Get the debug port capability\n  auto debugPort = client.bootstrap().castAs<rpc::WorkerdDebugPort>();\n\n  // Set up HTTP-over-Cap'n-Proto factory to convert Cap'n Proto HttpService to KJ HttpService\n  capnp::ByteStreamFactory byteStreamFactory;\n  kj::HttpHeaderTable::Builder headerTableBuilder;\n  capnp::HttpOverCapnpFactory httpOverCapnpFactory(\n      byteStreamFactory, headerTableBuilder, capnp::HttpOverCapnpFactory::LEVEL_2);\n  auto headerTable = headerTableBuilder.build();\n\n  // Helper to get bootstrap from service and entrypoint\n  auto getBootstrap = [&](kj::StringPtr service, kj::Maybe<kj::StringPtr> entrypoint,\n                          auto&& propsBuilder) {\n    auto req = debugPort.getEntrypointRequest();\n    req.setService(service);\n    KJ_IF_SOME(e, entrypoint) {\n      req.setEntrypoint(e);\n    }\n    auto props = req.initProps();\n    propsBuilder(props);\n    auto resp = req.send().wait(test.ws);\n    return resp.getEntrypoint();\n  };\n\n  // Helper to get a dispatcher from a bootstrap client\n  auto getDispatcherFromBootstrap = [&](rpc::WorkerdBootstrap::Client bootstrap) {\n    auto eventResp = bootstrap.startEventRequest().send().wait(test.ws);\n    return eventResp.getDispatcher();\n  };\n\n  // Helper to get dispatcher from service and entrypoint (composes the two above)\n  auto getDispatcher = [&](kj::StringPtr service, kj::Maybe<kj::StringPtr> entrypoint,\n                           auto&& propsBuilder) {\n    return getDispatcherFromBootstrap(getBootstrap(service, entrypoint, propsBuilder));\n  };\n\n  // Helper to make HTTP request from a dispatcher\n  auto makeHttpRequestFromDispatcher = [&](rpc::EventDispatcher::Client dispatcher,\n                                           kj::StringPtr path) -> kj::String {\n    auto capnpHttpService = dispatcher.getHttpServiceRequest().send().wait(test.ws).getHttp();\n\n    // Convert to KJ HttpService and make request\n    auto kjHttpService = httpOverCapnpFactory.capnpToKj(kj::mv(capnpHttpService));\n    auto httpClient = kj::newHttpClient(*kjHttpService);\n    auto url = kj::str(\"http://test\", path);\n    auto httpResponse = httpClient->request(kj::HttpMethod::GET, url, kj::HttpHeaders(*headerTable))\n                            .response.wait(test.ws);\n\n    KJ_EXPECT(httpResponse.statusCode == 200);\n    return httpResponse.body->readAllText().wait(test.ws);\n  };\n\n  // Helper to make HTTP request from a bootstrap client (works for both entrypoints and actors)\n  auto makeHttpRequestFromBootstrap = [&](rpc::WorkerdBootstrap::Client bootstrap,\n                                          kj::StringPtr path) -> kj::String {\n    return makeHttpRequestFromDispatcher(getDispatcherFromBootstrap(kj::mv(bootstrap)), path);\n  };\n\n  // Helper to make HTTP request through an entrypoint with custom props\n  auto makeHttpRequestImpl = [&](kj::StringPtr service, kj::Maybe<kj::StringPtr> entrypoint,\n                                 auto&& propsBuilder) {\n    return makeHttpRequestFromDispatcher(getDispatcher(service, entrypoint, propsBuilder), \"/\");\n  };\n\n  // Convenience wrapper with default empty props\n  auto makeHttpRequest = [&](kj::StringPtr service, kj::Maybe<kj::StringPtr> entrypoint) {\n    return makeHttpRequestImpl(service, entrypoint, [](auto& props) { props.setEmptyObject(); });\n  };\n\n  // Test 1: Request a non-existent service should fail\n  KJ_EXPECT_THROW_MESSAGE(\"Service not found\",\n      getBootstrap(\"nonexistent\", kj::none, [](auto& props) { props.setEmptyObject(); }));\n\n  // Test 2: Get entrypoint for different services\n  KJ_EXPECT(makeHttpRequest(\"hello\", kj::none) == \"Hello from hello service\");\n  KJ_EXPECT(makeHttpRequest(\"world\", kj::none) == \"Hello from world service\");\n\n  // Test 3: Named entrypoint works\n  KJ_EXPECT(\n      makeHttpRequest(\"named-entrypoint\", \"customHandler\"_kjc) == \"Hello from custom entrypoint\");\n\n  // Test 4: Passing props object works\n  KJ_EXPECT(makeHttpRequestImpl(\"props-service\", kj::none, [](auto& props) {\n    props.setEmptyObject();\n    auto properties = props.initProperties(2);\n    properties[0].setName(\"greeting\");\n    properties[0].setJson(\"\\\"Hello\\\"\");\n    properties[1].setName(\"name\");\n    properties[1].setJson(\"\\\"World\\\"\");\n  }) == \"Props: Hello World\");\n\n  // Test 5: Getting an actor works and we can call methods on it\n  {\n    // Create a deterministic actor ID\n    kj::byte actorIdBytes[32] = {};\n    for (size_t i = 0; i < sizeof(actorIdBytes); i++) {\n      actorIdBytes[i] = static_cast<kj::byte>(i);\n    }\n\n    // Helper to make an HTTP request to the actor\n    auto makeActorRequest = [&](kj::StringPtr path) -> kj::String {\n      auto req = debugPort.getActorRequest();\n      req.setService(\"actor-service\");\n      req.setEntrypoint(\"MyActor\");\n      // Convert actor ID bytes to hex string\n      req.setActorId(kj::encodeHex(kj::arrayPtr(actorIdBytes, sizeof(actorIdBytes))));\n      auto resp = req.send().wait(test.ws);\n      return makeHttpRequestFromBootstrap(resp.getActor(), path);\n    };\n\n    // Make a first request to increment the counter\n    {\n      auto bodyText = makeActorRequest(\"/increment\");\n      KJ_EXPECT(bodyText == \"Count: 1\");\n    }\n\n    // Make a second request to increment again - verifies state persistence\n    {\n      auto bodyText = makeActorRequest(\"/increment\");\n      KJ_EXPECT(bodyText == \"Count: 2\");\n    }\n\n    // Make a request to verify the actor ID is correct\n    {\n      auto bodyText = makeActorRequest(\"/\");\n\n      // The actor should return its ID as a hex string\n      // Convert our actor ID bytes to hex string to compare\n      kj::String expectedId = kj::encodeHex(kj::arrayPtr(actorIdBytes, sizeof(actorIdBytes)));\n      kj::String expectedResponse = kj::str(\"Actor: \", expectedId);\n      KJ_EXPECT(bodyText == expectedResponse, bodyText, expectedResponse);\n    }\n  }\n\n  // Test 6: Call RPC methods using jsRpcSession with V8-serialized arguments\n  {\n    // Get dispatcher and JS RPC session - use pipelining because jsRpcSession() doesn't return until session closes\n    auto dispatcher =\n        getDispatcher(\"rpc-service\", kj::none, [](auto& props) { props.setEmptyObject(); });\n    auto rpcSessionReq = dispatcher.jsRpcSessionRequest();\n    auto sessionPromise = rpcSessionReq.send();\n    auto rpcTarget = sessionPromise.getTopLevel();\n\n    // Test calling add(5, 3) -> 8\n    auto v8SerializedArgs = serializeJsArguments({[](jsg::Lock& js) {\n      return jsg::JsValue(js.num(5));\n    }, [](jsg::Lock& js) { return jsg::JsValue(js.num(3)); }});\n\n    auto callReq = rpcTarget.callRequest();\n    callReq.setMethodName(\"add\");\n    auto operation = callReq.initOperation();\n    auto jsValue = operation.initCallWithArgs();\n    jsValue.setV8Serialized(v8SerializedArgs);\n\n    auto callResp = callReq.send().wait(test.ws);\n    auto result = callResp.getResult();\n\n    auto resultData = result.getV8Serialized();\n    KJ_EXPECT(resultData.size() > 0, \"Result should be non-empty\");\n\n    auto jsonResult = deserializeV8ToJson(resultData);\n    KJ_EXPECT(jsonResult == \"8\", jsonResult, \"Expected result to be 8\");\n  }\n}\n\nKJ_TEST(\"Server: workerdDebugPort binding loopback test\") {\n  // This test verifies that a worker can use the workerdDebugPort binding to connect\n  // back to the same workerd instance's debug port and access other services.\n  TestServer test(R\"((\n    services = [\n      ( name = \"target-service\",\n        worker = (\n          compatibilityDate = \"2024-01-01\",\n          modules = [\n            ( name = \"worker.js\",\n              esModule =\n                `export default {\n                `  async fetch(request) {\n                `    return new Response(\"Hello from target!\");\n                `  }\n                `}\n                `export let namedHandler = {\n                `  async fetch(request) {\n                `    return new Response(\"Hello from named entrypoint!\");\n                `  }\n                `}\n            )\n          ]\n        )\n      ),\n      ( name = \"test-service\",\n        worker = (\n          compatibilityDate = \"2024-01-01\",\n          compatibilityFlags = [\"experimental\"],\n          modules = [\n            ( name = \"worker.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env, ctx) {\n                `    // Connect to the debug port\n                `    const client = await env.debugPort.connect(\"debug-addr\");\n                `\n                `    // Test 1: Access the default entrypoint\n                `    const defaultFetcher = client.getEntrypoint(\"target-service\");\n                `    const defaultResp = await defaultFetcher.fetch(\"http://fake-host/\");\n                `    const defaultText = await defaultResp.text();\n                `    if (defaultText !== \"Hello from target!\") {\n                `      throw new Error(\"Expected 'Hello from target!' but got: \" + defaultText);\n                `    }\n                `\n                `    // Test 2: Access a named entrypoint\n                `    const namedFetcher = client.getEntrypoint(\"target-service\", \"namedHandler\");\n                `    const namedResp = await namedFetcher.fetch(\"http://fake-host/\");\n                `    const namedText = await namedResp.text();\n                `    if (namedText !== \"Hello from named entrypoint!\") {\n                `      throw new Error(\"Expected 'Hello from named entrypoint!' but got: \" + namedText);\n                `    }\n                `\n                `    return new Response(\"All tests passed!\");\n                `  }\n                `}\n            )\n          ],\n          bindings = [\n            ( name = \"debugPort\",\n              workerdDebugPort = void\n            )\n          ]\n        )\n      )\n    ],\n    sockets = [\n      ( name = \"main\", address = \"test-addr\", service = \"test-service\" )\n    ]\n  ))\"_kj);\n\n  // Enable the debug port on a known address\n  test.server.enableDebugPort(kj::str(\"debug-addr\"));\n  test.server.allowExperimental();\n\n  test.start();\n\n  // Run the test by invoking the fetch handler\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\", \"All tests passed!\");\n}\n\nKJ_TEST(\"Server: workerdDebugPort binding with props\") {\n  // This test verifies that props can be passed through the workerdDebugPort binding.\n  TestServer test(R\"((\n    services = [\n      ( name = \"target-service\",\n        worker = (\n          compatibilityDate = \"2024-01-01\",\n          compatibilityFlags = [\"experimental\"],\n          modules = [\n            ( name = \"worker.js\",\n              esModule =\n                `import {WorkerEntrypoint} from \"cloudflare:workers\";\n                `export class PropsHandler extends WorkerEntrypoint {\n                `  async fetch(request) {\n                `    const props = this.ctx.props;\n                `    return new Response(JSON.stringify(props));\n                `  }\n                `}\n            )\n          ]\n        )\n      ),\n      ( name = \"test-service\",\n        worker = (\n          compatibilityDate = \"2024-01-01\",\n          compatibilityFlags = [\"experimental\"],\n          modules = [\n            ( name = \"worker.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env, ctx) {\n                `    // Connect to the debug port\n                `    const client = await env.debugPort.connect(\"debug-addr\");\n                `\n                `    // Test passing props to the entrypoint\n                `    const fetcher = client.getEntrypoint(\n                `        \"target-service\", \"PropsHandler\", {foo: \"bar\", num: 42});\n                `    const resp = await fetcher.fetch(\"http://fake-host/\");\n                `    const props = await resp.json();\n                `\n                `    if (props.foo !== \"bar\") {\n                `      throw new Error(\"Expected props.foo to be 'bar' but got: \" + props.foo);\n                `    }\n                `    if (props.num !== 42) {\n                `      throw new Error(\"Expected props.num to be 42 but got: \" + props.num);\n                `    }\n                `\n                `    return new Response(\"Props test passed!\");\n                `  }\n                `}\n            )\n          ],\n          bindings = [\n            ( name = \"debugPort\",\n              workerdDebugPort = void\n            )\n          ]\n        )\n      )\n    ],\n    sockets = [\n      ( name = \"main\", address = \"test-addr\", service = \"test-service\" )\n    ]\n  ))\"_kj);\n\n  test.server.enableDebugPort(kj::str(\"debug-addr\"));\n  test.server.allowExperimental();\n\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\", \"Props test passed!\");\n}\n\nKJ_TEST(\"Server: workerdDebugPort binding getActor\") {\n  // This test verifies that getActor can be used to access Durable Objects via the debug port.\n  TestServer test(R\"((\n    services = [\n      ( name = \"do-service\",\n        worker = (\n          compatibilityDate = \"2024-01-01\",\n          compatibilityFlags = [\"experimental\"],\n          modules = [\n            ( name = \"worker.js\",\n              esModule =\n                `import {DurableObject} from \"cloudflare:workers\";\n                `export default {\n                `  async fetch(request) {\n                `    return new Response(\"DO service default handler\");\n                `  }\n                `}\n                `export class Counter extends DurableObject {\n                `  counter = 0;\n                `  async fetch(request) {\n                `    this.counter++;\n                `    return new Response(\"Counter: \" + this.counter);\n                `  }\n                `}\n            )\n          ],\n          durableObjectNamespaces = [\n            ( className = \"Counter\",\n              uniqueKey = \"test-do-key\"\n            )\n          ],\n          durableObjectStorage = (inMemory = void)\n        )\n      ),\n      ( name = \"test-service\",\n        worker = (\n          compatibilityDate = \"2024-01-01\",\n          compatibilityFlags = [\"experimental\"],\n          modules = [\n            ( name = \"worker.js\",\n              esModule =\n                `export default {\n                `  async fetch(request, env, ctx) {\n                `    // Connect to the debug port\n                `    const client = await env.debugPort.connect(\"debug-addr\");\n                `\n                `    // Get the same actor twice using a fixed ID\n                `    const actorId = \"0\".repeat(64);\n                `\n                `    const actor1 = client.getActor(\"do-service\", \"Counter\", actorId);\n                `    const resp1 = await actor1.fetch(\"http://fake-host/\");\n                `    const text1 = await resp1.text();\n                `    if (text1 !== \"Counter: 1\") {\n                `      throw new Error(\"Expected 'Counter: 1' but got: \" + text1);\n                `    }\n                `\n                `    // Second request to same actor should increment counter\n                `    const actor2 = client.getActor(\"do-service\", \"Counter\", actorId);\n                `    const resp2 = await actor2.fetch(\"http://fake-host/\");\n                `    const text2 = await resp2.text();\n                `    if (text2 !== \"Counter: 2\") {\n                `      throw new Error(\"Expected 'Counter: 2' but got: \" + text2);\n                `    }\n                `\n                `    // Different actor ID should have independent state (counter starts at 1)\n                `    const differentActorId = \"1\".repeat(64);\n                `    const actor3 = client.getActor(\"do-service\", \"Counter\", differentActorId);\n                `    const resp3 = await actor3.fetch(\"http://fake-host/\");\n                `    const text3 = await resp3.text();\n                `    if (text3 !== \"Counter: 1\") {\n                `      throw new Error(\"Expected 'Counter: 1' for different actor but got: \" + text3);\n                `    }\n                `\n                `    return new Response(\"DO actor test passed!\");\n                `  }\n                `}\n            )\n          ],\n          bindings = [\n            ( name = \"debugPort\",\n              workerdDebugPort = void\n            )\n          ]\n        )\n      )\n    ],\n    sockets = [\n      ( name = \"main\", address = \"test-addr\", service = \"test-service\" )\n    ]\n  ))\"_kj);\n\n  test.server.enableDebugPort(kj::str(\"debug-addr\"));\n  test.server.allowExperimental();\n\n  test.start();\n\n  auto conn = test.connect(\"test-addr\");\n  conn.httpGet200(\"/\", \"DO actor test passed!\");\n}\n\nKJ_TEST(\"Server: workerdDebugPort WebSocket passthrough via WorkerEntrypoint\") {\n  // This test verifies that a WebSocket obtained via the debug port can be passed through\n  // a service binding response (from a WorkerEntrypoint). This was previously broken because\n  // the debug port connection was destroyed when the intermediate IoContext finished.\n  TestServer test(R\"((\n    services = [\n      ( name = \"target-service\",\n        worker = (\n          compatibilityDate = \"2024-01-01\",\n          modules = [\n            ( name = \"worker.js\",\n              esModule =\n                `export default {\n                `  async fetch(request) {\n                `    // Accept WebSocket upgrade and echo messages with a prefix\n                `    const upgradeHeader = request.headers.get(\"Upgrade\");\n                `    if (upgradeHeader === \"websocket\") {\n                `      const pair = new WebSocketPair();\n                `      pair[1].accept();\n                `      pair[1].addEventListener(\"message\", (e) => {\n                `        pair[1].send(\"echo:\" + e.data);\n                `      });\n                `      return new Response(null, { status: 101, webSocket: pair[0] });\n                `    }\n                `    return new Response(\"Not a WebSocket request\");\n                `  }\n                `}\n            )\n          ]\n        )\n      ),\n      ( name = \"proxy-service\",\n        worker = (\n          compatibilityDate = \"2024-01-01\",\n          compatibilityFlags = [\"experimental\"],\n          modules = [\n            ( name = \"worker.js\",\n              esModule =\n                `import {WorkerEntrypoint} from \"cloudflare:workers\";\n                `\n                `// This WorkerEntrypoint gets a WebSocket via debug port and passes it through\n                `export class Proxy extends WorkerEntrypoint {\n                `  async fetch(request) {\n                `    const client = await this.env.debugPort.connect(\"debug-addr\");\n                `    const fetcher = client.getEntrypoint(\"target-service\");\n                `    const response = await fetcher.fetch(request);\n                `    if (response.webSocket) {\n                `      // Pass through the WebSocket from the debug port\n                `      return new Response(null, { status: 101, webSocket: response.webSocket });\n                `    }\n                `    return response;\n                `  }\n                `}\n                `\n                `export default {\n                `  async fetch(request, env) {\n                `    // Route through the Proxy entrypoint to test WebSocket passthrough\n                `    return env.proxy.fetch(request);\n                `  }\n                `}\n            )\n          ],\n          bindings = [\n            ( name = \"debugPort\", workerdDebugPort = void ),\n            ( name = \"proxy\", service = (name = \"proxy-service\", entrypoint = \"Proxy\") )\n          ]\n        )\n      )\n    ],\n    sockets = [\n      ( name = \"main\", address = \"test-addr\", service = \"proxy-service\" )\n    ]\n  ))\"_kj);\n\n  test.server.enableDebugPort(kj::str(\"debug-addr\"));\n  test.server.allowExperimental();\n\n  test.start();\n\n  // Connect and upgrade to WebSocket\n  auto wsConn = test.connect(\"test-addr\");\n  wsConn.upgradeToWebSocket();\n\n  // Send a message and verify we get the echoed response\n  // WebSocket frame: 0x81 = final frame + text, 0x05 = payload length 5\n  constexpr kj::StringPtr testMessage = \"hello\"_kj;\n  wsConn.send(kj::str(\"\\x81\\x05\", testMessage));\n  wsConn.recvWebSocket(\"echo:hello\");\n\n  // Send another message to verify the connection stays alive\n  constexpr kj::StringPtr testMessage2 = \"world\"_kj;\n  wsConn.send(kj::str(\"\\x81\\x05\", testMessage2));\n  wsConn.recvWebSocket(\"echo:world\");\n}\n}  // namespace\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/server.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"server.h\"\n\n#include \"container-client.h\"\n#include \"pyodide.h\"\n#include \"workerd-api.h\"\n\n#include <workerd/api/actor-state.h>\n#include <workerd/api/analytics-engine.capnp.h>\n#include <workerd/api/pyodide/pyodide.h>\n#include <workerd/api/trace.h>\n#include <workerd/api/worker-rpc.h>\n#include <workerd/io/actor-cache.h>\n#include <workerd/io/actor-id.h>\n#include <workerd/io/actor-sqlite.h>\n#include <workerd/io/bundle-fs.h>\n#include <workerd/io/compatibility-date.h>\n#include <workerd/io/container.capnp.h>\n#include <workerd/io/hibernation-manager.h>\n#include <workerd/io/io-context.h>\n#include <workerd/io/limit-enforcer.h>\n#include <workerd/io/request-tracker.h>\n#include <workerd/io/trace-stream.h>\n#include <workerd/io/worker-entrypoint.h>\n#include <workerd/io/worker-fs.h>\n#include <workerd/io/worker-interface.h>\n#include <workerd/io/worker.h>\n#include <workerd/server/actor-id-impl.h>\n#include <workerd/server/facet-tree-index.h>\n#include <workerd/server/fallback-service.h>\n#include <workerd/util/http-util.h>\n#include <workerd/util/mimetype.h>\n#include <workerd/util/use-perfetto-categories.h>\n#include <workerd/util/uuid.h>\n#include <workerd/util/websocket-error-handler.h>\n\n#include <openssl/bio.h>\n#include <openssl/pem.h>\n\n#include <capnp/compat/json.h>\n#include <capnp/message.h>\n#include <capnp/rpc-twoparty.h>\n#include <kj/compat/http.h>\n#include <kj/compat/tls.h>\n#include <kj/compat/url.h>\n#include <kj/debug.h>\n#include <kj/encoding.h>\n#include <kj/glob-filter.h>\n#include <kj/map.h>\n\n#include <cstdlib>\n#include <ctime>\n\nnamespace workerd::server {\n\nnamespace {\n\nstruct PemData {\n  kj::String type;\n  kj::Array<byte> data;\n};\n\n// Decode PEM format using OpenSSL helpers.\nstatic kj::Maybe<PemData> decodePem(kj::ArrayPtr<const char> text) {\n  // TODO(cleanup): Should this be part of the KJ TLS library? We don't technically use it for TLS.\n  //   Maybe KJ should have a general crypto library that wraps OpenSSL?\n\n  BIO* bio = BIO_new_mem_buf(const_cast<char*>(text.begin()), text.size());\n  KJ_DEFER(BIO_free(bio));\n\n  class OpenSslDisposer: public kj::ArrayDisposer {\n   public:\n    void disposeImpl(void* firstElement,\n        size_t elementSize,\n        size_t elementCount,\n        size_t capacity,\n        void (*destroyElement)(void*)) const override {\n      OPENSSL_free(firstElement);\n    }\n  };\n  static constexpr OpenSslDisposer disposer;\n\n  char* namePtr = nullptr;\n  char* headerPtr = nullptr;\n  byte* dataPtr = nullptr;\n  long dataLen = 0;\n  if (!PEM_read_bio(bio, &namePtr, &headerPtr, &dataPtr, &dataLen)) {\n    return kj::none;\n  }\n  kj::Array<char> nameArr(namePtr, strlen(namePtr) + 1, disposer);\n  KJ_DEFER(OPENSSL_free(headerPtr));\n  kj::Array<kj::byte> data(dataPtr, dataLen, disposer);\n\n  return PemData{kj::String(kj::mv(nameArr)), kj::mv(data)};\n}\n\n// Returns a time string in the format HTTP likes to use.\nstatic kj::String httpTime(kj::Date date) {\n  time_t time = (date - kj::UNIX_EPOCH) / kj::SECONDS;\n#if _WIN32\n  // `gmtime` is thread-safe on Windows: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/gmtime-gmtime32-gmtime64?view=msvc-170#return-value\n  auto tm = *gmtime(&time);\n#else\n  struct tm tm;\n  KJ_ASSERT(gmtime_r(&time, &tm) == &tm);\n#endif\n  char buf[256]{};\n  size_t n = strftime(buf, sizeof(buf), \"%a, %d %b %Y %H:%M:%S GMT\", &tm);\n  KJ_ASSERT(n > 0);\n  return kj::heapString(buf, n);\n}\n\nstatic kj::String escapeJsonString(kj::StringPtr text) {\n  static const char HEXDIGITS[] = \"0123456789abcdef\";\n  kj::Vector<char> escaped(text.size() + 1);\n\n  for (char c: text) {\n    switch (c) {\n      case '\"':\n        escaped.addAll(\"\\\\\\\"\"_kj);\n        break;\n      case '\\\\':\n        escaped.addAll(\"\\\\\\\\\"_kj);\n        break;\n      case '\\b':\n        escaped.addAll(\"\\\\b\"_kj);\n        break;\n      case '\\f':\n        escaped.addAll(\"\\\\f\"_kj);\n        break;\n      case '\\n':\n        escaped.addAll(\"\\\\n\"_kj);\n        break;\n      case '\\r':\n        escaped.addAll(\"\\\\r\"_kj);\n        break;\n      case '\\t':\n        escaped.addAll(\"\\\\t\"_kj);\n        break;\n      default:\n        if (static_cast<uint8_t>(c) < 0x20) {\n          escaped.addAll(\"\\\\u00\"_kj);\n          uint8_t c2 = c;\n          escaped.add(HEXDIGITS[c2 / 16]);\n          escaped.add(HEXDIGITS[c2 % 16]);\n        } else {\n          escaped.add(c);\n        }\n        break;\n    }\n  }\n\n  return kj::str(\"\\\"\", escaped.releaseAsArray(), \"\\\"\");\n}\n\ntemplate <typename T>\nstatic inline kj::Own<T> fakeOwn(T& ref) {\n  return kj::Own<T>(&ref, kj::NullDisposer::instance);\n}\n\nvoid throwDynamicEntrypointTransferError() {\n  JSG_FAIL_REQUIRE(DOMDataCloneError,\n      \"Entrypoints to dynamically-loaded workers cannot be transferred to other Workers, \"\n      \"because the system does not know how to reload this Worker from scratch. Instead, \"\n      \"have the parent Worker expose an entrypoint which constructs the dynamic worker \"\n      \"and forwards to it.\");\n}\n\n}  // namespace\n\n// =======================================================================================\n\nServer::Server(kj::Filesystem& fs,\n    kj::Timer& timer,\n    const kj::MonotonicClock& monotonicClock,\n    kj::Network& network,\n    kj::EntropySource& entropySource,\n    Worker::LoggingOptions loggingOptions,\n    kj::Function<void(kj::String)> reportConfigError)\n    : fs(fs),\n      timer(timer),\n      monotonicClock(monotonicClock),\n      network(network),\n      entropySource(entropySource),\n      reportConfigError(kj::mv(reportConfigError)),\n      loggingOptions(loggingOptions),\n      memoryCacheProvider(kj::heap<api::MemoryCacheProvider>(timer)),\n      channelTokenHandler(*this),\n      tasks(*this) {}\n\nstruct Server::GlobalContext {\n  jsg::V8System& v8System;\n  capnp::ByteStreamFactory byteStreamFactory;\n  capnp::HttpOverCapnpFactory httpOverCapnpFactory;\n  ThreadContext threadContext;\n  kj::HttpHeaderTable& headerTable;\n\n  GlobalContext(\n      Server& server, jsg::V8System& v8System, kj::HttpHeaderTable::Builder& headerTableBuilder)\n      : v8System(v8System),\n        httpOverCapnpFactory(\n            byteStreamFactory, headerTableBuilder, capnp::HttpOverCapnpFactory::LEVEL_2),\n        threadContext(server.timer,\n            server.entropySource,\n            headerTableBuilder,\n            httpOverCapnpFactory,\n            byteStreamFactory,\n            false /* isFiddle -- TODO(beta): support */),\n        headerTable(headerTableBuilder.getFutureTable()) {}\n};\n\nclass Server::Service: public IoChannelFactory::SubrequestChannel {\n public:\n  // Cross-links this service with other services. Must be called once before `startRequest()`.\n  virtual void link(Worker::ValidationErrorReporter& errorReporter) {}\n\n  // Drops any cross-links created during link(). This called just before all the services are\n  // destroyed. An `Own<T>` cannot be destroyed unless the object it points to still exists, so\n  // we must clear all the `Own<Service>`s before we can actually destroy the `Service`s.\n  virtual void unlink() {}\n\n  // Begin an incoming request. Returns a `WorkerInterface` object that will be used for one\n  // request then discarded.\n  virtual kj::Own<WorkerInterface> startRequest(\n      IoChannelFactory::SubrequestMetadata metadata) override = 0;\n\n  // Returns true if the service exports the given handler, e.g. `fetch`, `scheduled`, etc.\n  virtual bool hasHandler(kj::StringPtr handlerName) = 0;\n\n  // Return the service itself, or the underlying service if this instance wraps another service as\n  // with EntrypointService.\n  virtual Service* service() {\n    return this;\n  }\n\n  // Implemented by EntrypointService for loopback ctx.exports entrypoints, to allow props to be\n  // specified.\n  virtual kj::Own<Service> forProps(Frankenvalue props) {\n    KJ_FAIL_REQUIRE(\"can't override props for this service\");\n  }\n\n  void requireAllowsTransfer() override {\n    // We consider all `Service` implementations to be safe to transfer, except for dynamic workers\n    // which we'll handle explicitly.\n  }\n};\n\nclass Server::ActorClass: public IoChannelFactory::ActorClassChannel {\n public:\n  // The caller must call this before calling newActor(). If it returns a promise, then the\n  // caller must await the promise before calling other methods.\n  //\n  // In particular, this is needed with dynamically-loaded workers. The isolate may still be\n  // loading when the caller calls `getDurableObjectClass()` on it.\n  virtual kj::Maybe<kj::Promise<void>> whenReady() {\n    return kj::none;\n  }\n\n  // Construct a new instance of the class. The parameters here are passed into `Worker::Actor`'s\n  // constructor.\n  virtual kj::Own<Worker::Actor> newActor(kj::Maybe<RequestTracker&> tracker,\n      Worker::Actor::Id actorId,\n      Worker::Actor::MakeActorCacheFunc makeActorCache,\n      Worker::Actor::MakeStorageFunc makeStorage,\n      kj::Own<Worker::Actor::Loopback> loopback,\n      kj::Maybe<kj::Own<Worker::Actor::HibernationManager>> manager,\n      kj::Maybe<rpc::Container::Client> container,\n      kj::Maybe<Worker::Actor::FacetManager&> facetManager) = 0;\n\n  // Start a request on the actor. (The actor must have been created using newActor().)\n  virtual kj::Own<WorkerInterface> startRequest(\n      IoChannelFactory::SubrequestMetadata metadata, kj::Own<Worker::Actor> actor) = 0;\n\n  virtual kj::Own<ActorClass> forProps(Frankenvalue props) {\n    KJ_FAIL_REQUIRE(\"can't override props for this actor class\");\n  }\n};\n\nServer::~Server() noexcept {\n  // This destructor is explicitly `noexcept` because if one of the `unlink()`s throws then we'd\n  // have a hard time avoiding a segfault later... and we're shutting down the server anyway so\n  // whatever, better to crash.\n\n  // It's important to cancel all tasks before we start tearing down.\n  tasks.clear();\n\n  // Unlink all the services, which should remove all refcount cycles.\n  unlinkWorkerLoaders();\n  for (auto& service: services) {\n    service.value->unlink();\n  }\n\n  // Verify that unlinking actually eliminated cycles. Otherwise we have a memory leak -- and\n  // potentially use-after-free if we allow the `Server` to be destroyed while services still\n  // exist.\n  for (auto& service: services) {\n    KJ_ASSERT(\n        !service.value->isShared(), \"service still has references after unlinking\", service.key);\n  }\n}\n\n// =======================================================================================\n\nkj::Own<kj::TlsContext> Server::makeTlsContext(config::TlsOptions::Reader conf) {\n  kj::TlsContext::Options options;\n\n  struct Attachments {\n    kj::Maybe<kj::TlsKeypair> keypair;\n    kj::Array<kj::TlsCertificate> trustedCerts;\n  };\n  auto attachments = kj::heap<Attachments>();\n\n  if (conf.hasKeypair()) {\n    auto pairConf = conf.getKeypair();\n    options.defaultKeypair = attachments->keypair.emplace(\n        kj::TlsKeypair{.privateKey = kj::TlsPrivateKey(pairConf.getPrivateKey()),\n          .certificate = kj::TlsCertificate(pairConf.getCertificateChain())});\n  }\n\n  options.verifyClients = conf.getRequireClientCerts();\n  options.useSystemTrustStore = conf.getTrustBrowserCas();\n\n  auto trustList = conf.getTrustedCertificates();\n  if (trustList.size() > 0) {\n    attachments->trustedCerts = KJ_MAP(cert, trustList) { return kj::TlsCertificate(cert); };\n    options.trustedCertificates = attachments->trustedCerts;\n  }\n\n  switch (conf.getMinVersion()) {\n    case config::TlsOptions::Version::GOOD_DEFAULT:\n      // Don't change.\n      goto validVersion;\n    case config::TlsOptions::Version::SSL3:\n      options.minVersion = kj::TlsVersion::SSL_3;\n      goto validVersion;\n    case config::TlsOptions::Version::TLS1_DOT0:\n      options.minVersion = kj::TlsVersion::TLS_1_0;\n      goto validVersion;\n    case config::TlsOptions::Version::TLS1_DOT1:\n      options.minVersion = kj::TlsVersion::TLS_1_1;\n      goto validVersion;\n    case config::TlsOptions::Version::TLS1_DOT2:\n      options.minVersion = kj::TlsVersion::TLS_1_2;\n      goto validVersion;\n    case config::TlsOptions::Version::TLS1_DOT3:\n      options.minVersion = kj::TlsVersion::TLS_1_3;\n      goto validVersion;\n  }\n  reportConfigError(kj::str(\"Encountered unknown TlsOptions::minVersion setting. Was the \"\n                            \"config compiled with a newer version of the schema?\"));\n\nvalidVersion:\n  if (conf.hasCipherList()) {\n    options.cipherList = conf.getCipherList();\n  }\n\n  return kj::heap<kj::TlsContext>(kj::mv(options));\n}\n\nkj::Promise<kj::Own<kj::NetworkAddress>> Server::makeTlsNetworkAddress(\n    config::TlsOptions::Reader conf,\n    kj::StringPtr addrStr,\n    kj::Maybe<kj::StringPtr> certificateHost,\n    uint defaultPort) {\n  auto context = makeTlsContext(conf);\n\n  KJ_IF_SOME(h, certificateHost) {\n    auto parsed = co_await network.parseAddress(addrStr, defaultPort);\n    co_return context->wrapAddress(kj::mv(parsed), h).attach(kj::mv(context));\n  }\n\n  // Wrap the `Network` itself so we can use the TLS implementation's `parseAddress()` to extract\n  // the authority from the address.\n  auto tlsNetwork = context->wrapNetwork(network);\n  auto parsed = co_await network.parseAddress(addrStr, defaultPort);\n  co_return parsed.attach(kj::mv(context));\n}\n\n// =======================================================================================\n\n// Helper to apply config::HttpOptions.\nclass Server::HttpRewriter {\n  // TODO(beta): Do we want to automatically add `Date`, `Server` (to outgoing responses),\n  //   `User-Agent` (to outgoing requests), etc.?\n\n public:\n  HttpRewriter(\n      config::HttpOptions::Reader httpOptions, kj::HttpHeaderTable::Builder& headerTableBuilder)\n      : style(httpOptions.getStyle()),\n        requestInjector(httpOptions.getInjectRequestHeaders(), headerTableBuilder),\n        responseInjector(httpOptions.getInjectResponseHeaders(), headerTableBuilder) {\n    if (httpOptions.hasForwardedProtoHeader()) {\n      forwardedProtoHeader = headerTableBuilder.add(httpOptions.getForwardedProtoHeader());\n    }\n    if (httpOptions.hasCfBlobHeader()) {\n      cfBlobHeader = headerTableBuilder.add(httpOptions.getCfBlobHeader());\n    }\n    if (httpOptions.hasCapnpConnectHost()) {\n      capnpConnectHost = httpOptions.getCapnpConnectHost();\n    }\n  }\n\n  bool hasCfBlobHeader() {\n    return cfBlobHeader != kj::none;\n  }\n\n  bool needsRewriteRequest() {\n    return style == config::HttpOptions::Style::HOST || hasCfBlobHeader() ||\n        !requestInjector.empty();\n  }\n\n  // Attach this to the promise returned by request().\n  struct Rewritten {\n    kj::Own<kj::HttpHeaders> headers;\n    kj::String ownUrl;\n  };\n\n  Rewritten rewriteOutgoingRequest(\n      kj::StringPtr& url, const kj::HttpHeaders& headers, kj::Maybe<kj::StringPtr> cfBlobJson) {\n    Rewritten result{kj::heap(headers.cloneShallow()), nullptr};\n\n    if (style == config::HttpOptions::Style::HOST) {\n      auto parsed = kj::Url::parse(url, kj::Url::HTTP_PROXY_REQUEST,\n          kj::Url::Options{.percentDecode = false, .allowEmpty = true});\n      result.headers->set(kj::HttpHeaderId::HOST, kj::mv(parsed.host));\n      KJ_IF_SOME(h, forwardedProtoHeader) {\n        result.headers->set(h, kj::mv(parsed.scheme));\n      }\n      url = result.ownUrl = parsed.toString(kj::Url::HTTP_REQUEST);\n    }\n\n    KJ_IF_SOME(h, cfBlobHeader) {\n      KJ_IF_SOME(b, cfBlobJson) {\n        result.headers->setPtr(h, b);\n      } else {\n        result.headers->unset(h);\n      }\n    }\n\n    requestInjector.apply(*result.headers);\n\n    return result;\n  }\n\n  kj::Maybe<Rewritten> rewriteIncomingRequest(kj::StringPtr& url,\n      kj::StringPtr physicalProtocol,\n      const kj::HttpHeaders& headers,\n      kj::Maybe<kj::String>& cfBlobJson) {\n    Rewritten result{kj::heap(headers.cloneShallow()), nullptr};\n\n    if (style == config::HttpOptions::Style::HOST) {\n      auto parsed = kj::Url::parse(\n          url, kj::Url::HTTP_REQUEST, kj::Url::Options{.percentDecode = false, .allowEmpty = true});\n      parsed.host = kj::str(KJ_UNWRAP_OR_RETURN(headers.get(kj::HttpHeaderId::HOST), kj::none));\n\n      KJ_IF_SOME(h, forwardedProtoHeader) {\n        KJ_IF_SOME(s, headers.get(h)) {\n          parsed.scheme = kj::str(s);\n          result.headers->unset(h);\n        }\n      }\n\n      if (parsed.scheme == nullptr) parsed.scheme = kj::str(physicalProtocol);\n\n      url = result.ownUrl = parsed.toString(kj::Url::HTTP_PROXY_REQUEST);\n    }\n\n    KJ_IF_SOME(h, cfBlobHeader) {\n      KJ_IF_SOME(b, headers.get(h)) {\n        cfBlobJson = kj::str(b);\n        result.headers->unset(h);\n      }\n    }\n\n    requestInjector.apply(*result.headers);\n\n    return result;\n  }\n\n  bool needsRewriteResponse() {\n    return !responseInjector.empty();\n  }\n\n  void rewriteResponse(kj::HttpHeaders& headers) {\n    responseInjector.apply(headers);\n  }\n\n  kj::Maybe<kj::StringPtr> getCapnpConnectHost() {\n    return capnpConnectHost;\n  }\n\n private:\n  config::HttpOptions::Style style;\n  kj::Maybe<kj::HttpHeaderId> forwardedProtoHeader;\n  kj::Maybe<kj::HttpHeaderId> cfBlobHeader;\n  kj::Maybe<kj::StringPtr> capnpConnectHost;\n\n  class HeaderInjector {\n   public:\n    HeaderInjector(capnp::List<config::HttpOptions::Header>::Reader headers,\n        kj::HttpHeaderTable::Builder& headerTableBuilder)\n        : injectedHeaders(KJ_MAP(header, headers) {\n            InjectedHeader result;\n            result.id = headerTableBuilder.add(header.getName());\n            if (header.hasValue()) {\n              result.value = kj::str(header.getValue());\n            }\n            return result;\n          }) {}\n\n    bool empty() {\n      return injectedHeaders.size() == 0;\n    }\n\n    void apply(kj::HttpHeaders& headers) {\n      for (auto& header: injectedHeaders) {\n        KJ_IF_SOME(v, header.value) {\n          headers.setPtr(header.id, v);\n        } else {\n          headers.unset(header.id);\n        }\n      }\n    }\n\n   private:\n    struct InjectedHeader {\n      kj::HttpHeaderId id;\n      kj::Maybe<kj::String> value;\n    };\n    kj::Array<InjectedHeader> injectedHeaders;\n  };\n\n  HeaderInjector requestInjector;\n  HeaderInjector responseInjector;\n};\n\n// =======================================================================================\n\n// Service used when the service's config is invalid.\nclass Server::InvalidConfigService final: public Service {\n public:\n  kj::Own<WorkerInterface> startRequest(IoChannelFactory::SubrequestMetadata metadata) override {\n    JSG_FAIL_REQUIRE(Error, \"Service cannot handle requests because its config is invalid.\");\n  }\n\n  bool hasHandler(kj::StringPtr handlerName) override {\n    return false;\n  }\n};\n\nclass Server::InvalidConfigActorClass final: public ActorClass {\n public:\n  void requireAllowsTransfer() override {\n    // Can't get here because workerd would have failed to start.\n    KJ_UNREACHABLE;\n  }\n\n  kj::Own<Worker::Actor> newActor(kj::Maybe<RequestTracker&> tracker,\n      Worker::Actor::Id actorId,\n      Worker::Actor::MakeActorCacheFunc makeActorCache,\n      Worker::Actor::MakeStorageFunc makeStorage,\n      kj::Own<Worker::Actor::Loopback> loopback,\n      kj::Maybe<kj::Own<Worker::Actor::HibernationManager>> manager,\n      kj::Maybe<rpc::Container::Client> container,\n      kj::Maybe<Worker::Actor::FacetManager&> facetManager) override {\n    JSG_FAIL_REQUIRE(\n        Error, \"Cannot instantiate Durable Object class because its config is invalid.\");\n  }\n\n  kj::Own<WorkerInterface> startRequest(\n      IoChannelFactory::SubrequestMetadata metadata, kj::Own<Worker::Actor> actor) override {\n    // Can't get here because creating the actor would have required calling the other method.\n    KJ_UNREACHABLE;\n  }\n};\n\n// Return a fake Own pointing to the singleton.\nkj::Own<Server::Service> Server::makeInvalidConfigService() {\n  return {invalidConfigServiceSingleton.get(), kj::NullDisposer::instance};\n}\n\n// A NetworkAddress whose connect() method waits for a Promise<NetworkAddress> and then forwards\n// to it. Used by ExternalHttpService so that we don't have to wait for DNS lookup before the\n// server can start.\nclass PromisedNetworkAddress final: public kj::NetworkAddress {\n  // TODO(cleanup): kj::Network should be extended with a new version of parseAddress() which does\n  //   not do DNS lookup immediately, and therefore can return a NetworkAddress synchronously.\n  //   In fact, this version should be designed to redo the DNS lookup periodically to see if it\n  //   changed, which would be nice for workerd when the remote address may change over time.\n public:\n  PromisedNetworkAddress(kj::Promise<kj::Own<kj::NetworkAddress>> promise)\n      : promise(promise.then([this](kj::Own<kj::NetworkAddress> result) { addr = kj::mv(result); })\n                    .fork()) {}\n\n  kj::Promise<kj::Own<kj::AsyncIoStream>> connect() override {\n    KJ_IF_SOME(a, addr) {\n      co_return co_await a.get()->connect();\n    } else {\n      co_await promise;\n      co_return co_await KJ_ASSERT_NONNULL(addr)->connect();\n    }\n  }\n\n  kj::Promise<kj::AuthenticatedStream> connectAuthenticated() override {\n    KJ_IF_SOME(a, addr) {\n      co_return co_await a.get()->connectAuthenticated();\n    } else {\n      co_await promise;\n      co_return co_await KJ_ASSERT_NONNULL(addr)->connectAuthenticated();\n    }\n  }\n\n  // We don't use any other methods, and they seem kinda annoying to implement.\n  kj::Own<kj::ConnectionReceiver> listen() override {\n    KJ_UNIMPLEMENTED(\"PromisedNetworkAddress::listen() not implemented\");\n  }\n  kj::Own<kj::NetworkAddress> clone() override {\n    KJ_UNIMPLEMENTED(\"PromisedNetworkAddress::clone() not implemented\");\n  }\n  kj::String toString() override {\n    KJ_UNIMPLEMENTED(\"PromisedNetworkAddress::toString() not implemented\");\n  }\n\n private:\n  kj::ForkedPromise<void> promise;\n  kj::Maybe<kj::Own<kj::NetworkAddress>> addr;\n};\n\nclass Server::ExternalTcpService final: public Service, private WorkerInterface {\n public:\n  ExternalTcpService(kj::Own<kj::NetworkAddress> addrParam): addr(kj::mv(addrParam)) {}\n\n  kj::Own<WorkerInterface> startRequest(IoChannelFactory::SubrequestMetadata metadata) override {\n    return {this, kj::NullDisposer::instance};\n  }\n\n  bool hasHandler(kj::StringPtr handlerName) override {\n    return handlerName == \"fetch\"_kj || handlerName == \"connect\"_kj;\n  }\n\n private:\n  kj::Own<kj::NetworkAddress> addr;\n\n  kj::Promise<void> request(kj::HttpMethod method,\n      kj::StringPtr url,\n      const kj::HttpHeaders& headers,\n      kj::AsyncInputStream& requestBody,\n      kj::HttpService::Response& response) override {\n    throwUnsupported();\n  }\n\n  kj::Promise<void> connect(kj::StringPtr host,\n      const kj::HttpHeaders& headers,\n      kj::AsyncIoStream& connection,\n      ConnectResponse& tunnel,\n      kj::HttpConnectSettings settings) override {\n    TRACE_EVENT(\"workerd\", \"ExternalTcpService::connect()\", \"host\", host.cStr());\n    auto io_stream = co_await addr->connect();\n\n    auto promises = kj::heapArrayBuilder<kj::Promise<void>>(2);\n\n    promises.add(connection.pumpTo(*io_stream).then([&io_stream = *io_stream](uint64_t size) {\n      io_stream.shutdownWrite();\n    }));\n\n    promises.add(io_stream->pumpTo(connection).then([&connection](uint64_t size) {\n      connection.shutdownWrite();\n    }));\n\n    tunnel.accept(200, \"OK\", kj::HttpHeaders(kj::HttpHeaderTable{}));\n\n    co_await kj::joinPromisesFailFast(promises.finish()).attach(kj::mv(io_stream));\n  }\n\n  kj::Promise<void> prewarm(kj::StringPtr url) override {\n    return kj::READY_NOW;\n  }\n  kj::Promise<ScheduledResult> runScheduled(kj::Date scheduledTime, kj::StringPtr cron) override {\n    throwUnsupported();\n  }\n  kj::Promise<AlarmResult> runAlarm(kj::Date scheduledTime, uint32_t retryCount) override {\n    throwUnsupported();\n  }\n  kj::Promise<CustomEvent::Result> customEvent(kj::Own<CustomEvent> event) override {\n    return event->notSupported();\n  }\n\n  [[noreturn]] void throwUnsupported() {\n    JSG_FAIL_REQUIRE(Error, \"External TCP servers don't support this event type.\");\n  }\n};\n\n// Service used when the service is configured as external HTTP service.\nclass Server::ExternalHttpService final: public Service {\n public:\n  ExternalHttpService(kj::Own<kj::NetworkAddress> addrParam,\n      kj::Own<HttpRewriter> rewriter,\n      kj::HttpHeaderTable& headerTable,\n      kj::Timer& timer,\n      kj::EntropySource& entropySource,\n      capnp::ByteStreamFactory& byteStreamFactory,\n      capnp::HttpOverCapnpFactory& httpOverCapnpFactory)\n      : addr(kj::mv(addrParam)),\n        webSocketErrorHandler(kj::heap<JsgifyWebSocketErrors>()),\n        inner(kj::newHttpClient(timer,\n            headerTable,\n            *addr,\n            {.entropySource = entropySource,\n              .webSocketCompressionMode = kj::HttpClientSettings::MANUAL_COMPRESSION,\n              .webSocketErrorHandler = *webSocketErrorHandler})),\n        serviceAdapter(kj::newHttpService(*inner)),\n        rewriter(kj::mv(rewriter)),\n        headerTable(headerTable),\n        byteStreamFactory(byteStreamFactory),\n        httpOverCapnpFactory(httpOverCapnpFactory) {}\n\n  kj::Own<WorkerInterface> startRequest(IoChannelFactory::SubrequestMetadata metadata) override {\n    return kj::heap<WorkerInterfaceImpl>(*this, kj::mv(metadata));\n  }\n\n  bool hasHandler(kj::StringPtr handlerName) override {\n    return handlerName == \"fetch\"_kj || handlerName == \"connect\"_kj;\n  }\n\n private:\n  kj::Own<kj::NetworkAddress> addr;\n\n  kj::Own<JsgifyWebSocketErrors> webSocketErrorHandler;\n  kj::Own<kj::HttpClient> inner;\n  kj::Own<kj::HttpService> serviceAdapter;\n\n  kj::Own<HttpRewriter> rewriter;\n\n  kj::HttpHeaderTable& headerTable;\n  capnp::ByteStreamFactory& byteStreamFactory;\n  capnp::HttpOverCapnpFactory& httpOverCapnpFactory;\n\n  struct CapnpClient {\n    kj::Own<kj::AsyncIoStream> connection;\n    capnp::TwoPartyClient rpcSystem;\n\n    CapnpClient(kj::Own<kj::AsyncIoStream> connectionParam)\n        : connection(kj::mv(connectionParam)),\n          rpcSystem(*connection) {}\n  };\n\n  // capnpClient is created on-demand when RPC is needed.\n  kj::Maybe<CapnpClient> capnpClient;\n\n  // This task nulls out `capnpClient` when the connection is lost.\n  kj::Promise<void> clearCapnpClientTask = nullptr;\n\n  // Get an WorkerdBootstrap representing the service on the other end of an HTTP connection. May\n  // reuse an existing connection, or form a new one over `client`.\n  rpc::WorkerdBootstrap::Client getOutgoingCapnp(kj::HttpClient& client) {\n    KJ_IF_SOME(c, capnpClient) {\n      return c.rpcSystem.bootstrap().castAs<rpc::WorkerdBootstrap>();\n    }\n\n    // No existing client, need to create a new one.\n    kj::StringPtr host = KJ_UNWRAP_OR(rewriter->getCapnpConnectHost(),\n        { return JSG_KJ_EXCEPTION(FAILED, Error, \"This ExternalServer not configured for RPC.\"); });\n\n    auto req = client.connect(host, kj::HttpHeaders(headerTable), {});\n    auto& c = capnpClient.emplace(kj::mv(req.connection));\n\n    // Arrange that when the connection is lost, we'll null out `capnpClient`. This ensures that\n    // on the next event, we'll attempt to reconnect.\n    //\n    // TODO(perf): Time out idle connections?\n    clearCapnpClientTask =\n        c.rpcSystem.onDisconnect().attach(kj::defer([this]() {\n      capnpClient = kj::none;\n    })).eagerlyEvaluate(nullptr);\n\n    return c.rpcSystem.bootstrap().castAs<rpc::WorkerdBootstrap>();\n  }\n\n  class WorkerInterfaceImpl final: public WorkerInterface, private kj::HttpService::Response {\n   public:\n    WorkerInterfaceImpl(ExternalHttpService& parent, IoChannelFactory::SubrequestMetadata metadata)\n        : parent(kj::addRef(parent)),\n          metadata(kj::mv(metadata)) {}\n\n    kj::Promise<void> request(kj::HttpMethod method,\n        kj::StringPtr url,\n        const kj::HttpHeaders& headers,\n        kj::AsyncInputStream& requestBody,\n        kj::HttpService::Response& response) override {\n      TRACE_EVENT(\"workerd\", \"ExternalHttpServer::request()\");\n      KJ_REQUIRE(wrappedResponse == kj::none, \"object should only receive one request\");\n      wrappedResponse = response;\n      if (parent->rewriter->needsRewriteRequest()) {\n        auto rewrite = parent->rewriter->rewriteOutgoingRequest(url, headers, metadata.cfBlobJson);\n        return parent->serviceAdapter->request(method, url, *rewrite.headers, requestBody, *this)\n            .attach(kj::mv(rewrite));\n      } else {\n        return parent->serviceAdapter->request(method, url, headers, requestBody, *this);\n      }\n    }\n\n    kj::Promise<void> connect(kj::StringPtr host,\n        const kj::HttpHeaders& headers,\n        kj::AsyncIoStream& connection,\n        ConnectResponse& tunnel,\n        kj::HttpConnectSettings settings) override {\n      TRACE_EVENT(\"workerd\", \"ExternalHttpServer::connect()\");\n      return parent->serviceAdapter->connect(host, headers, connection, tunnel, kj::mv(settings));\n    }\n\n    kj::Promise<void> prewarm(kj::StringPtr url) override {\n      return kj::READY_NOW;\n    }\n    kj::Promise<ScheduledResult> runScheduled(kj::Date scheduledTime, kj::StringPtr cron) override {\n      throwUnsupported();\n    }\n    kj::Promise<AlarmResult> runAlarm(kj::Date scheduledTime, uint32_t retryCount) override {\n      throwUnsupported();\n    }\n\n    kj::Promise<CustomEvent::Result> customEvent(kj::Own<CustomEvent> event) override {\n      // We'll use capnp RPC for custom events.\n      auto bootstrap = parent->getOutgoingCapnp(*parent->inner);\n      auto dispatcher =\n          bootstrap.startEventRequest(capnp::MessageSize{4, 0}).send().getDispatcher();\n      return event\n          ->sendRpc(parent->httpOverCapnpFactory, parent->byteStreamFactory, kj::mv(dispatcher))\n          .attach(kj::mv(event));\n    }\n\n   private:\n    kj::Own<ExternalHttpService> parent;\n    IoChannelFactory::SubrequestMetadata metadata;\n    kj::Maybe<kj::HttpService::Response&> wrappedResponse;\n\n    [[noreturn]] void throwUnsupported() {\n      JSG_FAIL_REQUIRE(Error, \"External HTTP servers don't support this event type.\");\n    }\n\n    kj::Own<kj::AsyncOutputStream> send(uint statusCode,\n        kj::StringPtr statusText,\n        const kj::HttpHeaders& headers,\n        kj::Maybe<uint64_t> expectedBodySize) override {\n      TRACE_EVENT(\"workerd\", \"ExternalHttpService::send()\", \"status\", statusCode);\n      auto& response = KJ_ASSERT_NONNULL(wrappedResponse);\n      if (parent->rewriter->needsRewriteResponse()) {\n        auto rewrite = headers.cloneShallow();\n        parent->rewriter->rewriteResponse(rewrite);\n        return response.send(statusCode, statusText, rewrite, expectedBodySize);\n      } else {\n        return response.send(statusCode, statusText, headers, expectedBodySize);\n      }\n    }\n\n    kj::Own<kj::WebSocket> acceptWebSocket(const kj::HttpHeaders& headers) override {\n      TRACE_EVENT(\"workerd\", \"ExternalHttpService::acceptWebSocket()\");\n      auto& response = KJ_ASSERT_NONNULL(wrappedResponse);\n      if (parent->rewriter->needsRewriteResponse()) {\n        auto rewrite = headers.cloneShallow();\n        parent->rewriter->rewriteResponse(rewrite);\n        return response.acceptWebSocket(rewrite);\n      } else {\n        return response.acceptWebSocket(headers);\n      }\n    }\n  };\n};\n\nkj::Own<Server::Service> Server::makeExternalService(kj::StringPtr name,\n    config::ExternalServer::Reader conf,\n    kj::HttpHeaderTable::Builder& headerTableBuilder) {\n  TRACE_EVENT(\"workerd\", \"Server::makeExternalService()\", \"name\", name.cStr());\n  kj::StringPtr addrStr = nullptr;\n  kj::String ownAddrStr = nullptr;\n\n  KJ_IF_SOME(override, externalOverrides.findEntry(name)) {\n    addrStr = ownAddrStr = kj::mv(override.value);\n    externalOverrides.erase(override);\n  } else if (conf.hasAddress()) {\n    addrStr = conf.getAddress();\n  } else {\n    reportConfigError(kj::str(\"External service \\\"\", name,\n        \"\\\" has no address in the config, so must be specified \"\n        \"on the command line with `--external-addr`.\"));\n    return makeInvalidConfigService();\n  }\n\n  switch (conf.which()) {\n    case config::ExternalServer::HTTP: {\n      // We have to construct the rewriter upfront before waiting on any promises, since the\n      // HeaderTable::Builder is only available synchronously.\n      auto rewriter = kj::heap<HttpRewriter>(conf.getHttp(), headerTableBuilder);\n      auto addr = kj::heap<PromisedNetworkAddress>(network.parseAddress(addrStr, 80));\n      return kj::refcounted<ExternalHttpService>(kj::mv(addr), kj::mv(rewriter),\n          headerTableBuilder.getFutureTable(), timer, entropySource,\n          globalContext->byteStreamFactory, globalContext->httpOverCapnpFactory);\n    }\n    case config::ExternalServer::HTTPS: {\n      auto httpsConf = conf.getHttps();\n      kj::Maybe<kj::StringPtr> certificateHost;\n      if (httpsConf.hasCertificateHost()) {\n        certificateHost = httpsConf.getCertificateHost();\n      }\n      auto rewriter = kj::heap<HttpRewriter>(httpsConf.getOptions(), headerTableBuilder);\n      auto addr = kj::heap<PromisedNetworkAddress>(\n          makeTlsNetworkAddress(httpsConf.getTlsOptions(), addrStr, certificateHost, 443));\n      return kj::refcounted<ExternalHttpService>(kj::mv(addr), kj::mv(rewriter),\n          headerTableBuilder.getFutureTable(), timer, entropySource,\n          globalContext->byteStreamFactory, globalContext->httpOverCapnpFactory);\n    }\n    case config::ExternalServer::TCP: {\n      auto tcpConf = conf.getTcp();\n      auto addr = kj::heap<PromisedNetworkAddress>(network.parseAddress(addrStr, 80));\n      if (tcpConf.hasTlsOptions()) {\n        kj::Maybe<kj::StringPtr> certificateHost;\n        if (tcpConf.hasCertificateHost()) {\n          certificateHost = tcpConf.getCertificateHost();\n        }\n        addr = kj::heap<PromisedNetworkAddress>(\n            makeTlsNetworkAddress(tcpConf.getTlsOptions(), addrStr, certificateHost, 0));\n      }\n      return kj::refcounted<ExternalTcpService>(kj::mv(addr));\n    }\n  }\n  reportConfigError(kj::str(\"External service named \\\"\", name,\n      \"\\\" has unrecognized protocol. Was the config \"\n      \"compiled with a newer version of the schema?\"));\n  return makeInvalidConfigService();\n}\n\n// Service used when the service is configured as network service.\nclass Server::NetworkService final: public Service, private WorkerInterface {\n public:\n  NetworkService(kj::HttpHeaderTable& headerTable,\n      kj::Timer& timer,\n      kj::EntropySource& entropySource,\n      kj::Own<kj::Network> networkParam,\n      kj::Maybe<kj::Own<kj::Network>> tlsNetworkParam,\n      kj::Maybe<kj::SecureNetworkWrapper&> tlsContext)\n      : network(kj::mv(networkParam)),\n        tlsNetwork(kj::mv(tlsNetworkParam)),\n        webSocketErrorHandler(kj::heap<JsgifyWebSocketErrors>()),\n        inner(kj::newHttpClient(timer,\n            headerTable,\n            *network,\n            tlsNetwork,\n            {.entropySource = entropySource,\n              .webSocketCompressionMode = kj::HttpClientSettings::MANUAL_COMPRESSION,\n              .webSocketErrorHandler = *webSocketErrorHandler,\n              .tlsContext = tlsContext})),\n        serviceAdapter(kj::newHttpService(*inner)) {}\n\n  kj::Own<WorkerInterface> startRequest(IoChannelFactory::SubrequestMetadata metadata) override {\n    return {this, kj::NullDisposer::instance};\n  }\n\n  bool hasHandler(kj::StringPtr handlerName) override {\n    return handlerName == \"fetch\"_kj || handlerName == \"connect\"_kj;\n  }\n\n private:\n  kj::Own<kj::Network> network;\n  kj::Maybe<kj::Own<kj::Network>> tlsNetwork;\n  kj::Own<JsgifyWebSocketErrors> webSocketErrorHandler;\n  kj::Own<kj::HttpClient> inner;\n  kj::Own<kj::HttpService> serviceAdapter;\n\n  kj::Promise<void> request(kj::HttpMethod method,\n      kj::StringPtr url,\n      const kj::HttpHeaders& headers,\n      kj::AsyncInputStream& requestBody,\n      kj::HttpService::Response& response) override {\n    TRACE_EVENT(\"workerd\", \"NetworkService::request()\");\n    return serviceAdapter->request(method, url, headers, requestBody, response);\n  }\n\n  kj::Promise<void> connect(kj::StringPtr host,\n      const kj::HttpHeaders& headers,\n      kj::AsyncIoStream& connection,\n      ConnectResponse& tunnel,\n      kj::HttpConnectSettings settings) override {\n    TRACE_EVENT(\"workerd\", \"NetworkService::connect()\");\n    // This code is hit when the global `connect` function is called in a JS worker script.\n    // It represents a proxy-less TCP connection, which means we can simply defer the handling of\n    // the connection to the service adapter (likely NetworkHttpClient). Its behavior will be to\n    // connect directly to the host over TCP.\n    return serviceAdapter->connect(host, headers, connection, tunnel, kj::mv(settings));\n  }\n\n  kj::Promise<void> prewarm(kj::StringPtr url) override {\n    return kj::READY_NOW;\n  }\n  kj::Promise<ScheduledResult> runScheduled(kj::Date scheduledTime, kj::StringPtr cron) override {\n    throwUnsupported();\n  }\n  kj::Promise<AlarmResult> runAlarm(kj::Date scheduledTime, uint32_t retryCount) override {\n    throwUnsupported();\n  }\n  kj::Promise<CustomEvent::Result> customEvent(kj::Own<CustomEvent> event) override {\n    return event->notSupported();\n  }\n\n  [[noreturn]] void throwUnsupported() {\n    JSG_FAIL_REQUIRE(Error, \"External HTTP servers don't support this event type.\");\n  }\n};\n\nkj::Own<Server::Service> Server::makeNetworkService(config::Network::Reader conf) {\n  TRACE_EVENT(\"workerd\", \"Server::makeNetworkService()\");\n  auto restrictedNetwork = network.restrictPeers( KJ_MAP(a, conf.getAllow()) -> kj::StringPtr {\n    return a;\n  }, KJ_MAP(a, conf.getDeny()) -> kj::StringPtr { return a; });\n\n  kj::Maybe<kj::Own<kj::Network>> tlsNetwork;\n  kj::Maybe<kj::SecureNetworkWrapper&> tlsContext;\n  if (conf.hasTlsOptions()) {\n    auto ownedTlsContext = makeTlsContext(conf.getTlsOptions());\n    tlsContext = ownedTlsContext;\n    tlsNetwork = ownedTlsContext->wrapNetwork(*restrictedNetwork).attach(kj::mv(ownedTlsContext));\n  }\n\n  return kj::refcounted<NetworkService>(globalContext->headerTable, timer, entropySource,\n      kj::mv(restrictedNetwork), kj::mv(tlsNetwork), tlsContext);\n}\n\n// Service used when the service is configured as disk directory service.\nclass Server::DiskDirectoryService final: public Service, private WorkerInterface {\n public:\n  DiskDirectoryService(config::DiskDirectory::Reader conf,\n      kj::Own<const kj::Directory> dir,\n      kj::HttpHeaderTable::Builder& headerTableBuilder)\n      : writable(*dir),\n        readable(kj::mv(dir)),\n        headerTable(headerTableBuilder.getFutureTable()),\n        hLastModified(headerTableBuilder.add(\"Last-Modified\")),\n        allowDotfiles(conf.getAllowDotfiles()) {}\n  DiskDirectoryService(config::DiskDirectory::Reader conf,\n      kj::Own<const kj::ReadableDirectory> dir,\n      kj::HttpHeaderTable::Builder& headerTableBuilder)\n      : readable(kj::mv(dir)),\n        headerTable(headerTableBuilder.getFutureTable()),\n        hLastModified(headerTableBuilder.add(\"Last-Modified\")),\n        allowDotfiles(conf.getAllowDotfiles()) {}\n\n  kj::Own<WorkerInterface> startRequest(IoChannelFactory::SubrequestMetadata metadata) override {\n    return {this, kj::NullDisposer::instance};\n  }\n\n  kj::Maybe<const kj::Directory&> getWritable() {\n    return writable;\n  }\n\n  bool hasHandler(kj::StringPtr handlerName) override {\n    return handlerName == \"fetch\"_kj;\n  }\n\n private:\n  kj::Maybe<const kj::Directory&> writable;\n  kj::Own<const kj::ReadableDirectory> readable;\n  kj::HttpHeaderTable& headerTable;\n  kj::HttpHeaderId hLastModified;\n  bool allowDotfiles;\n\n  kj::Promise<void> request(kj::HttpMethod method,\n      kj::StringPtr urlStr,\n      const kj::HttpHeaders& requestHeaders,\n      kj::AsyncInputStream& requestBody,\n      kj::HttpService::Response& response) override {\n    TRACE_EVENT(\"workerd\", \"DiskDirectoryService::request()\", \"url\", urlStr.cStr());\n    auto url = kj::Url::parse(urlStr);\n\n    bool blockedPath = false;\n    kj::Path path = nullptr;\n    KJ_IF_SOME(exception,\n        kj::runCatchingExceptions([&]() { path = kj::Path(url.path.releaseAsArray()); })) {\n      (void)exception;  // squash compiler warning about unused var\n      // If the Path constructor throws, this path is not valid (e.g. it contains \"..\").\n      blockedPath = true;\n    }\n\n    if (!blockedPath && !allowDotfiles) {\n      for (auto& part: path) {\n        if (part.startsWith(\".\")) {\n          blockedPath = true;\n          break;\n        }\n      }\n    }\n\n    if (method == kj::HttpMethod::GET || method == kj::HttpMethod::HEAD) {\n      if (blockedPath) {\n        co_return co_await response.sendError(404, \"Not Found\", headerTable);\n      }\n\n      auto file = KJ_UNWRAP_OR(readable->tryOpenFile(path),\n          { co_return co_await response.sendError(404, \"Not Found\", headerTable); });\n\n      auto meta = file->stat();\n\n      switch (meta.type) {\n        case kj::FsNode::Type::FILE: {\n          // If this is a GET request with a Range header, return partial content if a single\n          // satisfiable range is specified.\n          // TODO(someday): consider supporting multiple ranges with multipart/byteranges\n          kj::Maybe<kj::HttpByteRange> range;\n          if (method == kj::HttpMethod::GET) {\n            KJ_IF_SOME(header, requestHeaders.get(kj::HttpHeaderId::RANGE)) {\n              KJ_SWITCH_ONEOF(kj::tryParseHttpRangeHeader(header.asArray(), meta.size)) {\n                KJ_CASE_ONEOF(ranges, kj::Array<kj::HttpByteRange>) {\n                  KJ_ASSERT(ranges.size() > 0);\n                  if (ranges.size() == 1) range = ranges[0];\n                }\n                KJ_CASE_ONEOF(_, kj::HttpEverythingRange) {}\n                KJ_CASE_ONEOF(_, kj::HttpUnsatisfiableRange) {\n                  kj::HttpHeaders headers(headerTable);\n                  headers.set(kj::HttpHeaderId::CONTENT_RANGE, kj::str(\"bytes */\", meta.size));\n                  co_return co_await response.sendError(416, \"Range Not Satisfiable\", headers);\n                }\n              }\n            }\n          }\n\n          kj::HttpHeaders headers(headerTable);\n          headers.set(kj::HttpHeaderId::CONTENT_TYPE, MimeType::OCTET_STREAM.toString());\n          headers.set(hLastModified, httpTime(meta.lastModified));\n\n          // We explicitly set the Content-Length header because if we don't, and we were called\n          // by a local Worker (without an actual HTTP connection in between), then the Worker\n          // will not see a Content-Length header, but being able to query the content length\n          // (especially with HEAD requests) is quite useful.\n          // TODO(cleanup): Arguably the implementation of `fetch()` should be adjusted so that\n          //   if no `Content-Length` header is returned, but the body size is known via the KJ\n          //   HTTP API, then the header should be filled in automatically. Unclear if this is safe\n          //   to change without a compat flag.\n\n          if (method == kj::HttpMethod::HEAD) {\n            headers.set(kj::HttpHeaderId::CONTENT_LENGTH, kj::str(meta.size));\n            response.send(200, \"OK\", headers, meta.size);\n            co_return;\n          } else KJ_IF_SOME(r, range) {\n            KJ_ASSERT(r.start <= r.end);\n            auto rangeSize = r.end - r.start + 1;\n            headers.set(kj::HttpHeaderId::CONTENT_LENGTH, kj::str(rangeSize));\n            headers.set(kj::HttpHeaderId::CONTENT_RANGE,\n                kj::str(\"bytes \", r.start, \"-\", r.end, \"/\", meta.size));\n            auto out = response.send(206, \"Partial Content\", headers, rangeSize);\n\n            auto in = kj::heap<kj::FileInputStream>(*file, r.start);\n            co_return co_await in->pumpTo(*out, rangeSize).ignoreResult();\n          } else {\n            headers.set(kj::HttpHeaderId::CONTENT_LENGTH, kj::str(meta.size));\n            auto out = response.send(200, \"OK\", headers, meta.size);\n\n            auto in = kj::heap<kj::FileInputStream>(*file);\n            co_return co_await in->pumpTo(*out, meta.size).ignoreResult();\n          }\n        }\n        case kj::FsNode::Type::DIRECTORY: {\n          // Whoooops, we opened a directory. Back up and start over.\n\n          auto dir = readable->openSubdir(path);\n\n          kj::HttpHeaders headers(headerTable);\n          headers.set(kj::HttpHeaderId::CONTENT_TYPE, MimeType::JSON.toString());\n          headers.set(hLastModified, httpTime(meta.lastModified));\n\n          // We intentionally don't provide the expected size here in order to reserve the right\n          // to switch to streaming directory listing in the future.\n          auto out = response.send(200, \"OK\", headers);\n\n          if (method == kj::HttpMethod::HEAD) {\n            co_return;\n          } else {\n            auto entries = dir->listEntries();\n            kj::Vector<kj::String> jsonEntries(entries.size());\n            for (auto& entry: entries) {\n              if (!allowDotfiles && entry.name.startsWith(\".\")) {\n                continue;\n              }\n\n              kj::StringPtr type = \"other\";\n              switch (entry.type) {\n                case kj::FsNode::Type::FILE:\n                  type = \"file\";\n                  break;\n                case kj::FsNode::Type::DIRECTORY:\n                  type = \"directory\";\n                  break;\n                case kj::FsNode::Type::SYMLINK:\n                  type = \"symlink\";\n                  break;\n                case kj::FsNode::Type::BLOCK_DEVICE:\n                  type = \"blockDevice\";\n                  break;\n                case kj::FsNode::Type::CHARACTER_DEVICE:\n                  type = \"characterDevice\";\n                  break;\n                case kj::FsNode::Type::NAMED_PIPE:\n                  type = \"namedPipe\";\n                  break;\n                case kj::FsNode::Type::SOCKET:\n                  type = \"socket\";\n                  break;\n                case kj::FsNode::Type::OTHER:\n                  type = \"other\";\n                  break;\n              }\n\n              jsonEntries.add(\n                  kj::str(\"{\\\"name\\\":\", escapeJsonString(entry.name), \",\\\"type\\\":\\\"\", type, \"\\\"}\"));\n            };\n\n            auto content = kj::str('[', kj::strArray(jsonEntries, \",\"), ']');\n\n            co_return co_await out->write(content.asBytes());\n          }\n        }\n        default:\n          co_return co_await response.sendError(406, \"Not Acceptable\", headerTable);\n      }\n    } else if (method == kj::HttpMethod::PUT) {\n      auto& w = KJ_UNWRAP_OR(writable,\n          { co_return co_await response.sendError(405, \"Method Not Allowed\", headerTable); });\n\n      if (blockedPath || path.size() == 0) {\n        co_return co_await response.sendError(403, \"Unauthorized\", headerTable);\n      }\n\n      auto replacer = w.replaceFile(\n          path, kj::WriteMode::CREATE | kj::WriteMode::MODIFY | kj::WriteMode::CREATE_PARENT);\n      auto stream = kj::heap<kj::FileOutputStream>(replacer->get());\n\n      co_await requestBody.pumpTo(*stream);\n\n      replacer->commit();\n      kj::HttpHeaders headers(headerTable);\n      response.send(204, \"No Content\", headers);\n      co_return;\n    } else if (method == kj::HttpMethod::DELETE) {\n      auto& w = KJ_UNWRAP_OR(writable,\n          { co_return co_await response.sendError(405, \"Method Not Allowed\", headerTable); });\n\n      if (blockedPath || path.size() == 0) {\n        co_return co_await response.sendError(403, \"Unauthorized\", headerTable);\n      }\n\n      auto found = w.tryRemove(path);\n\n      kj::HttpHeaders headers(headerTable);\n      if (found) {\n        response.send(204, \"No Content\", headers);\n        co_return;\n      } else {\n        co_return co_await response.sendError(404, \"Not Found\", headers);\n      }\n    } else {\n      co_return co_await response.sendError(501, \"Not Implemented\", headerTable);\n    }\n  }\n\n  kj::Promise<void> connect(kj::StringPtr host,\n      const kj::HttpHeaders& headers,\n      kj::AsyncIoStream& connection,\n      kj::HttpService::ConnectResponse& response,\n      kj::HttpConnectSettings settings) override {\n    throwUnsupported();\n  }\n  kj::Promise<void> prewarm(kj::StringPtr url) override {\n    return kj::READY_NOW;\n  }\n  kj::Promise<ScheduledResult> runScheduled(kj::Date scheduledTime, kj::StringPtr cron) override {\n    throwUnsupported();\n  }\n  kj::Promise<AlarmResult> runAlarm(kj::Date scheduledTime, uint32_t retryCount) override {\n    throwUnsupported();\n  }\n  kj::Promise<CustomEvent::Result> customEvent(kj::Own<CustomEvent> event) override {\n    return event->notSupported();\n  }\n\n  [[noreturn]] void throwUnsupported() {\n    JSG_FAIL_REQUIRE(Error, \"Disk directory services don't support this event type.\");\n  }\n};\n\nkj::Own<Server::Service> Server::makeDiskDirectoryService(kj::StringPtr name,\n    config::DiskDirectory::Reader conf,\n    kj::HttpHeaderTable::Builder& headerTableBuilder) {\n  TRACE_EVENT(\"workerd\", \"Server::makeDiskDirectoryService()\");\n  kj::StringPtr pathStr = nullptr;\n  kj::String ownPathStr;\n\n  KJ_IF_SOME(override, directoryOverrides.findEntry(name)) {\n    pathStr = ownPathStr = kj::mv(override.value);\n    directoryOverrides.erase(override);\n  } else if (conf.hasPath()) {\n    pathStr = conf.getPath();\n  } else {\n    reportConfigError(kj::str(\"Directory \\\"\", name,\n        \"\\\" has no path in the config, so must be specified on the \"\n        \"command line with `--directory-path`.\"));\n    return makeInvalidConfigService();\n  }\n\n  auto path = fs.getCurrentPath().evalNative(pathStr);\n\n  if (conf.getWritable()) {\n    auto openDir = KJ_UNWRAP_OR(fs.getRoot().tryOpenSubdir(kj::mv(path), kj::WriteMode::MODIFY), {\n      reportConfigError(kj::str(\"Directory named \\\"\", name, \"\\\" not found: \", pathStr));\n      return makeInvalidConfigService();\n    });\n\n    return kj::refcounted<DiskDirectoryService>(conf, kj::mv(openDir), headerTableBuilder);\n  } else {\n    auto openDir = KJ_UNWRAP_OR(fs.getRoot().tryOpenSubdir(kj::mv(path)), {\n      reportConfigError(kj::str(\"Directory named \\\"\", name, \"\\\" not found: \", pathStr));\n      return makeInvalidConfigService();\n    });\n\n    return kj::refcounted<DiskDirectoryService>(conf, kj::mv(openDir), headerTableBuilder);\n  }\n}\n\n// =======================================================================================\n\n// This class exists to update the InspectorService's table of isolates when a config\n// has multiple services. The InspectorService exists on the stack of its own thread and\n// initializes state that is bound to the thread, e.g. a http server and an event loop.\n// This class provides a small thread-safe interface to the InspectorService so <name>:<isolate>\n// mappings can be added after the InspectorService has started.\n//\n// The Cloudflare devtools only show the first service in workerd configuration. This service\n// is always contains a users code. However, in packaging user code wrangler may add\n// additional services that also have code. If using Chrome devtools to inspect a workerd,\n// instance all services are visible and can be debugged.\nclass Server::InspectorServiceIsolateRegistrar final {\n public:\n  InspectorServiceIsolateRegistrar() {}\n  ~InspectorServiceIsolateRegistrar() noexcept(true);\n\n  void registerIsolate(kj::StringPtr name, Worker::Isolate* isolate);\n\n  KJ_DISALLOW_COPY_AND_MOVE(InspectorServiceIsolateRegistrar);\n\n private:\n  void attach(const Server::InspectorService* anInspectorService) {\n    *inspectorService.lockExclusive() = anInspectorService;\n  }\n\n  void detach() {\n    *inspectorService.lockExclusive() = nullptr;\n  }\n\n  kj::MutexGuarded<const InspectorService*> inspectorService;\n  friend class Server::InspectorService;\n};\n\n// Implements the interface for the devtools inspector protocol.\n//\n// The InspectorService is created when workerd serve is called using the -i option\n// to define the inspector socket.\nclass Server::InspectorService final: public kj::HttpService, public kj::HttpServerErrorHandler {\n public:\n  InspectorService(kj::Own<const kj::Executor> isolateThreadExecutor,\n      kj::Timer& timer,\n      kj::HttpHeaderTable::Builder& headerTableBuilder,\n      InspectorServiceIsolateRegistrar& registrar)\n      : isolateThreadExecutor(kj::mv(isolateThreadExecutor)),\n        timer(timer),\n        headerTable(headerTableBuilder.getFutureTable()),\n        server(timer, headerTable, *this, kj::HttpServerSettings{.errorHandler = *this}),\n        registrar(registrar) {\n    registrar.attach(this);\n  }\n\n  ~InspectorService() {\n    KJ_IF_SOME(r, registrar) {\n      r.detach();\n    }\n  }\n\n  void invalidateRegistrar() {\n    registrar = kj::none;\n  }\n\n  kj::Promise<void> handleApplicationError(\n      kj::Exception exception, kj::Maybe<kj::HttpService::Response&> response) override {\n    if (exception.getType() == kj::Exception::Type::DISCONNECTED) {\n      // Don't send a response, just close connection.\n      co_return;\n    }\n    KJ_LOG(ERROR, kj::str(\"Uncaught exception: \", exception));\n    KJ_IF_SOME(r, response) {\n      co_return co_await r.sendError(500, \"Internal Server Error\", headerTable);\n    }\n  }\n\n  kj::Promise<void> request(kj::HttpMethod method,\n      kj::StringPtr url,\n      const kj::HttpHeaders& headers,\n      kj::AsyncInputStream& requestBody,\n      kj::HttpService::Response& response) override {\n    // The inspector protocol starts with the debug client sending ordinary HTTP GET requests\n    // to /json/version and then to /json or /json/list. These must respond with valid JSON\n    // documents that list the details of what isolates are available for inspection. Each\n    // isolate must be listed separately. In the advertisement for each isolate is a URL\n    // and a unique ID. The client will use the URL and ID to open a WebSocket request to\n    // actually connect the debug session.\n    kj::HttpHeaders responseHeaders(headerTable);\n    if (headers.isWebSocket()) {\n      KJ_IF_SOME(pos, url.findLast('/')) {\n        auto id = url.slice(pos + 1);\n\n        KJ_IF_SOME(isolate, isolates.find(id)) {\n          // If getting the strong ref doesn't work it means that the Worker::Isolate\n          // has already been cleaned up. We use a weak ref here in order to keep from\n          // having the Worker::Isolate itself having to know anything at all about the\n          // IsolateService and the registration process. So instead of having Isolate\n          // explicitly clean up after itself we lazily evaluate the weak ref and clean\n          // up when necessary.\n          KJ_IF_SOME(ref, isolate->tryAddStrongRef()) {\n            // When using --verbose, we'll output some logging to indicate when the\n            // inspector client is attached/detached.\n            KJ_LOG(INFO, kj::str(\"Inspector client attaching [\", id, \"]\"));\n            auto webSocket = response.acceptWebSocket(responseHeaders);\n            kj::Duration timerOffset = 0 * kj::MILLISECONDS;\n            try {\n              co_return co_await ref->attachInspector(\n                  isolateThreadExecutor->addRef(), timer, timerOffset, *webSocket);\n            } catch (...) {\n              auto exception = kj::getCaughtExceptionAsKj();\n              if (exception.getType() == kj::Exception::Type::DISCONNECTED) {\n                // This likely just means that the inspector client was closed.\n                // Nothing to do here but move along.\n                KJ_LOG(INFO, \"Inspector client detached\"_kj);\n                co_return;\n              } else {\n                // If it's any other kind of error, propagate it!\n                kj::throwFatalException(kj::mv(exception));\n              }\n            }\n          } else {\n            // If we can't get a strong ref to the isolate here, it's been cleaned\n            // up. The only thing we're going to do is clean up here and act like\n            // nothing happened.\n            isolates.erase(id);\n          }\n        }\n\n        KJ_LOG(INFO, kj::str(\"Unknown worker session [\", id, \"]\"));\n        co_return co_await response.sendError(404, \"Unknown worker session\", responseHeaders);\n      }\n\n      // No / in url!? That's weird\n      co_return co_await response.sendError(400, \"Invalid request\", responseHeaders);\n    }\n\n    // If the request is not a WebSocket request, it must be a GET to fetch details\n    // about the implementation.\n    if (method != kj::HttpMethod::GET) {\n      co_return co_await response.sendError(501, \"Unsupported Operation\", responseHeaders);\n    }\n\n    if (url.endsWith(\"/json/version\")) {\n      responseHeaders.set(kj::HttpHeaderId::CONTENT_TYPE, MimeType::JSON.toString());\n      auto content = kj::str(\"{\\\"Browser\\\": \\\"workerd\\\", \\\"Protocol-Version\\\": \\\"1.3\\\" }\");\n      auto out = response.send(200, \"OK\", responseHeaders, content.size());\n      co_return co_await out->write(content.asBytes());\n    } else if (url.endsWith(\"/json\") || url.endsWith(\"/json/list\") ||\n        url.endsWith(\"/json/list?for_tab\")) {\n      responseHeaders.set(kj::HttpHeaderId::CONTENT_TYPE, MimeType::JSON.toString());\n\n      auto baseWsUrl = KJ_UNWRAP_OR(headers.get(kj::HttpHeaderId::HOST),\n          { co_return co_await response.sendError(400, \"Bad Request\", responseHeaders); });\n\n      kj::Vector<kj::String> entries(isolates.size());\n      kj::Vector<kj::String> toRemove;\n      for (auto& entry: isolates) {\n        // While we don't actually use the strong ref here we still attempt to acquire it\n        // in order to determine if the isolate is actually still around. If the isolate\n        // has been destroyed the weak ref will be cleared. We do it this way to keep from\n        // having the Worker::Isolate know anything at all about the InspectorService.\n        // We'll lazily clean up whenever we detect that the ref has been invalidated.\n        //\n        // TODO(cleanup): If we ever enable reloading of isolates for live services, we may\n        // want to refactor this such that the WorkerService holds a handle to the registration\n        // as opposed to using this lazy cleanup mechanism. For now, however, this is\n        // sufficient.\n        KJ_IF_SOME(ref, entry.value->tryAddStrongRef()) {\n          (void)ref;  // squash compiler warning about unused ref\n          kj::Vector<kj::String> fields(9);\n          fields.add(kj::str(\"\\\"id\\\":\\\"\", entry.key, \"\\\"\"));\n          fields.add(kj::str(\"\\\"title\\\":\\\"workerd: worker \", entry.key, \"\\\"\"));\n          fields.add(kj::str(\"\\\"type\\\":\\\"node\\\"\"));\n          fields.add(kj::str(\"\\\"description\\\":\\\"workerd worker\\\"\"));\n          fields.add(kj::str(\"\\\"webSocketDebuggerUrl\\\":\\\"ws://\", baseWsUrl, \"/\", entry.key, \"\\\"\"));\n          fields.add(kj::str(\n              \"\\\"devtoolsFrontendUrl\\\":\\\"devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=\",\n              baseWsUrl, \"/\\\"\"));\n          fields.add(kj::str(\n              \"\\\"devtoolsFrontendUrlCompat\\\":\\\"devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=\",\n              baseWsUrl, \"/\\\"\"));\n          fields.add(kj::str(\"\\\"faviconUrl\\\":\\\"https://workers.cloudflare.com/favicon.ico\\\"\"));\n          fields.add(kj::str(\"\\\"url\\\":\\\"https://workers.dev\\\"\"));\n          entries.add(kj::str('{', kj::strArray(fields, \",\"), '}'));\n        } else {\n          // If we're not able to get a reference to the isolate here, it's\n          // been cleaned up and we should remove it from the list. We do this\n          // after iterating to make sure we don't invalidate the iterator.\n          toRemove.add(kj::str(entry.key));\n        }\n      }\n      // Clean up if necessary\n      for (auto& key: toRemove) {\n        isolates.erase(key);\n      }\n\n      auto content = kj::str('[', kj::strArray(entries, \",\"), ']');\n\n      auto out = response.send(200, \"OK\", responseHeaders, content.size());\n      co_return co_await out->write(content.asBytes()).attach(kj::mv(content), kj::mv(out));\n    }\n\n    co_return co_await response.sendError(500, \"Not yet implemented\", responseHeaders);\n  }\n\n  kj::Promise<void> listen(kj::Own<kj::ConnectionReceiver> listener) {\n    // Note that we intentionally do not make inspector connections be part of the usual drain()\n    // procedure. Inspector connections are always long-lived WebSockets, and we do not want the\n    // existence of such a connection to hold the server open. We do, however, want the connection\n    // to stay open until all other requests are drained, for debugging purposes.\n    //\n    // Thus:\n    // * We let connection loop tasks live on `HttpServer`'s own `TaskSet`, rather than our\n    //   server's main `TaskSet` which we wait to become empty on drain.\n    // * We do not add this `HttpServer` to the server's `httpServers` list, so it will not receive\n    //   drain() requests. (However, our caller does cancel listening on the server port as soon\n    //   as we begin draining, since we may want new connections to go to a new instance of the\n    //   server.)\n    co_return co_await server.listenHttp(*listener);\n  }\n\n  void registerIsolate(kj::StringPtr name, Worker::Isolate* isolate) {\n    isolates.insert(kj::str(name), isolate->getWeakRef());\n  }\n\n private:\n  kj::Own<const kj::Executor> isolateThreadExecutor;\n  kj::Timer& timer;\n  kj::HttpHeaderTable& headerTable;\n  kj::HashMap<kj::String, kj::Own<const Worker::Isolate::WeakIsolateRef>> isolates;\n  kj::HttpServer server;\n  kj::Maybe<InspectorServiceIsolateRegistrar&> registrar;\n};\n\nServer::InspectorServiceIsolateRegistrar::~InspectorServiceIsolateRegistrar() noexcept(true) {\n  auto lockedInspectorService = this->inspectorService.lockExclusive();\n  if (lockedInspectorService != nullptr) {\n    auto is = const_cast<InspectorService*>(*lockedInspectorService);\n    is->invalidateRegistrar();\n  }\n}\n\nvoid Server::InspectorServiceIsolateRegistrar::registerIsolate(\n    kj::StringPtr name, Worker::Isolate* isolate) {\n  auto lockedInspectorService = this->inspectorService.lockExclusive();\n  if (lockedInspectorService != nullptr) {\n    auto is = const_cast<InspectorService*>(*lockedInspectorService);\n    is->registerIsolate(name, isolate);\n  }\n}\n\n// =======================================================================================\nnamespace {\nclass RequestObserverWithTracer final: public RequestObserver, public WorkerInterface {\n public:\n  RequestObserverWithTracer(kj::Maybe<kj::Own<WorkerTracer>> tracer, kj::TaskSet& waitUntilTasks)\n      : tracer(kj::mv(tracer)) {}\n\n  ~RequestObserverWithTracer() noexcept(false) {\n    KJ_IF_SOME(t, tracer) {\n      // for a more precise end time, set the end timestamp now, if available\n      KJ_IF_SOME(ioContext, IoContext::tryCurrent()) {\n        auto time = ioContext.now();\n        t->recordTimestamp(time);\n      }\n      t->setOutcome(\n          outcome, 0 * kj::MILLISECONDS /* cpu time */, 0 * kj::MILLISECONDS /* wall time */);\n    }\n  }\n\n  WorkerInterface& wrapWorkerInterface(WorkerInterface& worker) override {\n    if (tracer != kj::none) {\n      inner = worker;\n      return *this;\n    }\n    return worker;\n  }\n\n  void reportFailure(const kj::Exception& exception, FailureSource source) override {\n    outcome = EventOutcome::EXCEPTION;\n  }\n\n  void setOutcome(EventOutcome newOutcome) override {\n    outcome = newOutcome;\n  }\n\n  // WorkerInterface\n  kj::Promise<void> request(kj::HttpMethod method,\n      kj::StringPtr url,\n      const kj::HttpHeaders& headers,\n      kj::AsyncInputStream& requestBody,\n      kj::HttpService::Response& response) override {\n    try {\n      SimpleResponseObserver responseWrapper(&fetchStatus, response);\n      co_await KJ_ASSERT_NONNULL(inner).request(method, url, headers, requestBody, responseWrapper);\n    } catch (...) {\n      fetchStatus = 500;\n      auto exception = kj::getCaughtExceptionAsKj();\n      reportFailure(exception, FailureSource::OTHER);\n      kj::throwFatalException(kj::mv(exception));\n    }\n  }\n\n  kj::Promise<void> connect(kj::StringPtr host,\n      const kj::HttpHeaders& headers,\n      kj::AsyncIoStream& connection,\n      ConnectResponse& response,\n      kj::HttpConnectSettings settings) override {\n    try {\n      co_return co_await KJ_ASSERT_NONNULL(inner).connect(\n          host, headers, connection, response, settings);\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      reportFailure(exception, FailureSource::OTHER);\n      kj::throwFatalException(kj::mv(exception));\n    }\n  }\n\n  kj::Promise<void> prewarm(kj::StringPtr url) override {\n    try {\n      co_return co_await KJ_ASSERT_NONNULL(inner).prewarm(url);\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      reportFailure(exception, FailureSource::OTHER);\n      kj::throwFatalException(kj::mv(exception));\n    }\n  }\n\n  kj::Promise<ScheduledResult> runScheduled(kj::Date scheduledTime, kj::StringPtr cron) override {\n    try {\n      co_return co_await KJ_ASSERT_NONNULL(inner).runScheduled(scheduledTime, cron);\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      reportFailure(exception, FailureSource::OTHER);\n      kj::throwFatalException(kj::mv(exception));\n    }\n  }\n\n  kj::Promise<AlarmResult> runAlarm(kj::Date scheduledTime, uint32_t retryCount) override {\n    try {\n      co_return co_await KJ_ASSERT_NONNULL(inner).runAlarm(scheduledTime, retryCount);\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      reportFailure(exception, FailureSource::OTHER);\n      kj::throwFatalException(kj::mv(exception));\n    }\n  }\n\n  kj::Promise<bool> test() override {\n    try {\n      co_return co_await KJ_ASSERT_NONNULL(inner).test();\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      reportFailure(exception, FailureSource::OTHER);\n      kj::throwFatalException(kj::mv(exception));\n    }\n  }\n\n  kj::Promise<CustomEvent::Result> customEvent(kj::Own<CustomEvent> event) override {\n    try {\n      co_return co_await KJ_ASSERT_NONNULL(inner).customEvent(kj::mv(event));\n    } catch (...) {\n      auto exception = kj::getCaughtExceptionAsKj();\n      reportFailure(exception, FailureSource::OTHER);\n      kj::throwFatalException(kj::mv(exception));\n    }\n  }\n\n private:\n  kj::Maybe<kj::Own<WorkerTracer>> tracer;\n  kj::Maybe<WorkerInterface&> inner;\n  EventOutcome outcome = EventOutcome::OK;\n  kj::uint fetchStatus = 0;\n};\n\nclass SequentialSpanSubmitter final: public SpanSubmitter {\n public:\n  SequentialSpanSubmitter(kj::Own<WorkerTracer> workerTracer): workerTracer(kj::mv(workerTracer)) {}\n  void submitSpan(tracing::SpanId spanId, tracing::SpanId parentSpanId, const Span& span) override {\n    // This code path is workerd-only, we can safely utilize submitSpanOpen here.\n    submitSpanOpen(spanId, parentSpanId, span.operationName.clone(), span.startTime);\n    kj::Date startTime = span.startTime;\n    tracing::SpanEndData span2(spanId, span.endTime);\n    span2.tags.reserve(span.tags.size());\n    for (auto& tag: span.tags) {\n      span2.tags.insert(tag.key.clone(), spanTagClone(tag.value));\n    }\n    if (isPredictableModeForTest()) {\n      startTime = span2.endTime = kj::UNIX_EPOCH;\n    }\n\n    workerTracer->addSpanEnd(kj::mv(span2), startTime);\n  }\n\n  void submitSpanOpen(tracing::SpanId spanId,\n      tracing::SpanId parentSpanId,\n      kj::ConstString operationName,\n      kj::Date startTime) override {\n    if (isPredictableModeForTest()) {\n      startTime = kj::UNIX_EPOCH;\n    }\n    workerTracer->addSpanOpen(spanId, parentSpanId, kj::mv(operationName), startTime);\n  }\n\n  tracing::SpanId makeSpanId() override {\n    return tracing::SpanId(nextSpanId++);\n  }\n  KJ_DISALLOW_COPY_AND_MOVE(SequentialSpanSubmitter);\n\n private:\n  uint64_t nextSpanId = 1;\n  kj::Own<WorkerTracer> workerTracer;\n};\n\n// IsolateLimitEnforcer that enforces no limits.\nclass NullIsolateLimitEnforcer final: public IsolateLimitEnforcer {\n public:\n  v8::Isolate::CreateParams getCreateParams() override {\n    return {};\n  }\n\n  void customizeIsolate(v8::Isolate* isolate) override {}\n\n  ActorCacheSharedLruOptions getActorCacheLruOptions() override {\n    // TODO(someday): Make this configurable?\n    return {.softLimit = 16 * (1ull << 20),  // 16 MiB\n      .hardLimit = 128 * (1ull << 20),       // 128 MiB\n      .staleTimeout = 30 * kj::SECONDS,\n      .dirtyListByteLimit = 8 * (1ull << 20),  // 8 MiB\n      .maxKeysPerRpc = 128,\n\n      // For now, we use `neverFlush` to implement in-memory-only actors.\n      // See WorkerService::getActor().\n      .neverFlush = true};\n  }\n\n  kj::Own<void> enterStartupJs(\n      jsg::Lock& lock, kj::OneOf<kj::Exception, kj::Duration>&) const override {\n    return {};\n  }\n\n  kj::Own<void> enterStartupPython(\n      jsg::Lock& lock, kj::OneOf<kj::Exception, kj::Duration>&) const override {\n    return {};\n  }\n\n  kj::Own<void> enterDynamicImportJs(\n      jsg::Lock& lock, kj::OneOf<kj::Exception, kj::Duration>&) const override {\n    return {};\n  }\n\n  kj::Own<void> enterLoggingJs(\n      jsg::Lock& lock, kj::OneOf<kj::Exception, kj::Duration>&) const override {\n    return {};\n  }\n\n  kj::Own<void> enterInspectorJs(\n      jsg::Lock& loc, kj::OneOf<kj::Exception, kj::Duration>&) const override {\n    return {};\n  }\n\n  void completedRequest(kj::StringPtr id) const override {}\n\n  bool exitJs(jsg::Lock& lock) const override {\n    return false;\n  }\n\n  void reportMetrics(IsolateObserver& isolateMetrics) const override {}\n\n  kj::Maybe<size_t> checkPbkdfIterations(jsg::Lock& lock, size_t iterations) const override {\n    // No limit on the number of iterations in workerd\n    return kj::none;\n  }\n\n  bool hasExcessivelyExceededHeapLimit() const override {\n    return false;\n  }\n\n  const TrackedWasmInstanceList& getTrackedWasmInstances() const override {\n    return trackedWasmInstances;\n  }\n\n private:\n  TrackedWasmInstanceList trackedWasmInstances;\n};\n\n}  // namespace\n\n// Shared ErrorReporter base implemnetation. The logic to collect entrypoint information is the\n// same regardless of where the code came from.\nstruct Server::ErrorReporter: public Worker::ValidationErrorReporter {\n  // The `HashSet`s are the set of exported handlers, like `fetch`, `test`, etc.\n  kj::HashMap<kj::String, kj::HashSet<kj::String>> namedEntrypoints;\n  kj::Maybe<kj::HashSet<kj::String>> defaultEntrypoint;\n  kj::HashSet<kj::String> actorClasses;\n  kj::HashSet<kj::String> workflowClasses;\n\n  void addEntrypoint(kj::Maybe<kj::StringPtr> exportName, kj::Array<kj::String> methods) override {\n    kj::HashSet<kj::String> set;\n    for (auto& method: methods) {\n      set.insert(kj::mv(method));\n    }\n    KJ_IF_SOME(e, exportName) {\n      namedEntrypoints.insert(kj::str(e), kj::mv(set));\n    } else {\n      defaultEntrypoint = kj::mv(set);\n    }\n  }\n\n  void addActorClass(kj::StringPtr exportName) override {\n    actorClasses.insert(kj::str(exportName));\n  }\n\n  void addWorkflowClass(kj::StringPtr exportName, kj::Array<kj::String> methods) override {\n    // At runtime, we need to add it into the normal namedEntrypoints for Workflows to appear\n    // in `WorkerService`. This is a different method compared to `addEntrypoint` because we need to\n    // check for `WorkflowEntrypoint` inheritance at validation time.\n    kj::HashSet<kj::String> set;\n    for (auto& method: methods) {\n      set.insert(kj::mv(method));\n    }\n    namedEntrypoints.insert(kj::str(exportName), kj::mv(set));\n    workflowClasses.insert(kj::str(exportName));\n  }\n};\n\n// Implementation of ErrorReporter specifically for reporting errors in the top-level workerd\n// config.\nstruct Server::ConfigErrorReporter final: public ErrorReporter {\n  ConfigErrorReporter(Server& server, kj::StringPtr name): server(server), name(name) {}\n\n  Server& server;\n  kj::StringPtr name;\n\n  void addError(kj::String error) override {\n    server.handleReportConfigError(kj::str(\"service \", name, \": \", error));\n  }\n};\n\n// Implementation of ErrorReporter for dynamically-loaded Workers. We'll collect the errors and\n// report them in an exception at the end.\nstruct Server::DynamicErrorReporter final: public ErrorReporter {\n  kj::Vector<kj::String> errors;\n\n  void addError(kj::String error) override {\n    errors.add(kj::mv(error));\n  }\n\n  void throwIfErrors() {\n    if (!errors.empty()) {\n      JSG_FAIL_REQUIRE(Error, \"Failed to start Worker:\\n\", kj::strArray(errors, \"\\n\"));\n    }\n  }\n};\n\nclass Server::WorkerService final: public Service,\n                                   private kj::TaskSet::ErrorHandler,\n                                   private IoChannelFactory,\n                                   private TimerChannel,\n                                   private LimitEnforcer {\n public:\n  class ActorNamespace;\n\n  // I/O channels, delivered when link() is called.\n  struct LinkedIoChannels {\n    kj::Array<kj::Own<IoChannelFactory::SubrequestChannel>> subrequest;\n    kj::Array<kj::Maybe<ActorNamespace&>> actor;  // null = configuration error\n    kj::Array<kj::Own<ActorClass>> actorClass;\n    kj::Maybe<kj::Own<IoChannelFactory::SubrequestChannel>> cache;\n    kj::Maybe<const kj::Directory&> actorStorage;\n    AlarmScheduler& alarmScheduler;\n    kj::Array<kj::Own<IoChannelFactory::SubrequestChannel>> tails;\n    kj::Array<kj::Own<IoChannelFactory::SubrequestChannel>> streamingTails;\n    kj::Array<kj::Rc<WorkerLoaderNamespace>> workerLoaders;\n    kj::Maybe<kj::Network&> workerdDebugPortNetwork;\n  };\n  using LinkCallback =\n      kj::Function<LinkedIoChannels(WorkerService&, Worker::ValidationErrorReporter&)>;\n  using AbortActorsCallback = kj::Function<void(kj::Maybe<const kj::Exception&> reason)>;\n\n  WorkerService(ChannelTokenHandler& channelTokenHandler,\n      kj::Maybe<kj::StringPtr> serviceName,\n      ThreadContext& threadContext,\n      const kj::MonotonicClock& monotonicClock,\n      kj::Own<const Worker> worker,\n      kj::Maybe<kj::HashSet<kj::String>> defaultEntrypointHandlers,\n      kj::HashMap<kj::String, kj::HashSet<kj::String>> namedEntrypoints,\n      kj::HashSet<kj::String> actorClassEntrypointsParam,\n      LinkCallback linkCallback,\n      AbortActorsCallback abortActorsCallback,\n      kj::Maybe<kj::String> dockerPathParam,\n      kj::Maybe<kj::String> containerEgressInterceptorImageParam,\n      bool isDynamic)\n      : channelTokenHandler(channelTokenHandler),\n        serviceName(serviceName),\n        threadContext(threadContext),\n        monotonicClock(monotonicClock),\n        ioChannels(kj::mv(linkCallback)),\n        worker(kj::mv(worker)),\n        defaultEntrypointHandlers(kj::mv(defaultEntrypointHandlers)),\n        namedEntrypoints(kj::mv(namedEntrypoints)),\n        actorClassEntrypoints(kj::mv(actorClassEntrypointsParam)),\n        waitUntilTasks(*this),\n        abortActorsCallback(kj::mv(abortActorsCallback)),\n        dockerPath(kj::mv(dockerPathParam)),\n        containerEgressInterceptorImage(kj::mv(containerEgressInterceptorImageParam)),\n        isDynamic(isDynamic) {}\n\n  // Call immediately after the constructor to set up `actorNamespaces`. This can't happen during\n  // the constructor itself since it sets up cyclic references, which will throw an exception if\n  // done during the constructor.\n  void initActorNamespaces(\n      const kj::HashMap<kj::String, ActorConfig>& actorClasses, kj::Network& network) {\n    actorNamespaces.reserve(actorClasses.size());\n    for (auto& entry: actorClasses) {\n      if (!actorClassEntrypoints.contains(entry.key)) {\n        KJ_LOG(WARNING,\n            kj::str(\"A DurableObjectNamespace in the config referenced the class \\\"\", entry.key,\n                \"\\\", but no such Durable Object class is exported from the worker. Please make \"\n                \"sure the class name matches, it is exported, and the class extends \"\n                \"'DurableObject'. Attempts to call to this Durable Object class will fail at \"\n                \"runtime, but historically this was not a startup-time error. Future versions of \"\n                \"workerd may make this a startup-time error.\"));\n      }\n\n      auto actorClass = kj::refcounted<ActorClassImpl>(*this, entry.key, Frankenvalue());\n      auto ns = kj::heap<ActorNamespace>(kj::mv(actorClass), entry.value,\n          threadContext.getUnsafeTimer(), threadContext.getByteStreamFactory(), channelTokenHandler,\n          network, dockerPath, containerEgressInterceptorImage, waitUntilTasks);\n      actorNamespaces.insert(entry.key, kj::mv(ns));\n    }\n  }\n\n  void requireAllowsTransfer() override {\n    if (isDynamic) throwDynamicEntrypointTransferError();\n  }\n\n  kj::Maybe<kj::Own<Service>> getEntrypoint(kj::Maybe<kj::StringPtr> name, Frankenvalue props) {\n    const kj::HashSet<kj::String>* handlers;\n    KJ_IF_SOME(n, name) {\n      KJ_IF_SOME(entry, namedEntrypoints.findEntry(n)) {\n        name = entry.key;  // replace with more-permanent string\n        handlers = &entry.value;\n      } else KJ_IF_SOME(className, actorClassEntrypoints.find(n)) {\n        // TODO(soon): Restore this warning once miniflare no longer generates config that causes\n        //   it to log spuriously.\n        //\n        // KJ_LOG(WARNING,\n        //     kj::str(\"A ServiceDesignator in the config referenced the entrypoint \\\"\", n,\n        //         \"\\\", but this class does not extend 'WorkerEntrypoint'. Attempts to call this \"\n        //         \"entrypoint will fail at runtime, but historically this was not a startup-time \"\n        //         \"error. Future versions of workerd may make this a startup-time error.\"));\n\n        static const kj::HashSet<kj::String> EMPTY_HANDLERS;\n        name = className;  // replace with more-permanent string\n        handlers = &EMPTY_HANDLERS;\n      } else {\n        return kj::none;\n      }\n    } else {\n      KJ_IF_SOME(d, defaultEntrypointHandlers) {\n        handlers = &d;\n      } else {\n        // It would appear that there is no default export, therefore this refers to an entrypoint\n        // that doesn't exist! However, this was historically allowed. For backwards-compatibility,\n        // we preserve this behavior, by returning a reference to the WorkerService itself, whose\n        // startRequest() will fail.\n        //\n        // What will happen if you invoke this entrypoint? Not what you think. Check out the\n        // test case in server-test.c++ entitled \"referencing non-extant default entrypoint is not\n        // an error\" for the sordid details.\n        return kj::addRef(*this);\n      }\n    }\n    return kj::refcounted<EntrypointService>(*this, name, kj::mv(props), *handlers);\n  }\n\n  // Like getEntrypoint() but used specifically to get the entrypoint for use in ctx.exports,\n  // where it can be used raw (props are empty), or can be specialized with props.\n  kj::Own<Service> getLoopbackEntrypoint(kj::Maybe<kj::StringPtr> name) {\n    const kj::HashSet<kj::String>* handlers;\n    KJ_IF_SOME(n, name) {\n      KJ_IF_SOME(entry, namedEntrypoints.findEntry(n)) {\n        name = entry.key;  // replace with more-permanent string\n        handlers = &entry.value;\n      } else {\n        KJ_FAIL_REQUIRE(\"getLoopbackEntrypoint() called for entrypoint that doesn't exist\");\n      }\n    } else {\n      KJ_IF_SOME(d, defaultEntrypointHandlers) {\n        handlers = &d;\n      } else {\n        KJ_FAIL_REQUIRE(\"getLoopbackEntrypoint() called for entrypoint that doesn't exist\");\n      }\n    }\n    return kj::refcounted<EntrypointService>(*this, name, kj::none, *handlers);\n  }\n\n  kj::Maybe<kj::Own<ActorClass>> getActorClass(kj::Maybe<kj::StringPtr> name, Frankenvalue props) {\n    KJ_IF_SOME(className, actorClassEntrypoints.find(KJ_UNWRAP_OR(name, return kj::none))) {\n      return kj::refcounted<ActorClassImpl>(*this, className, kj::mv(props));\n    } else {\n      return kj::none;\n    }\n  }\n\n  kj::Own<ActorClass> getLoopbackActorClass(kj::StringPtr name) {\n    // Look up a more permanent class name string. (Also validates this is actually an export.)\n    kj::StringPtr className = KJ_REQUIRE_NONNULL(actorClassEntrypoints.find(name),\n        \"getLoopbackActorClass() called for actor class that doesn't exist\");\n\n    return kj::refcounted<ActorClassImpl>(*this, className, kj::none);\n  }\n\n  bool hasDefaultEntrypoint() {\n    return defaultEntrypointHandlers != kj::none;\n  }\n\n  kj::Array<kj::StringPtr> getEntrypointNames() {\n    return KJ_MAP(e, namedEntrypoints) -> kj::StringPtr { return e.key; };\n  }\n\n  kj::Array<kj::StringPtr> getActorClassNames() {\n    return KJ_MAP(name, actorClassEntrypoints) -> kj::StringPtr { return name; };\n  }\n\n  void link(Worker::ValidationErrorReporter& errorReporter) override {\n    LinkCallback callback =\n        kj::mv(KJ_REQUIRE_NONNULL(ioChannels.tryGet<LinkCallback>(), \"already called link()\"));\n    auto linked = callback(*this, errorReporter);\n\n    for (auto& ns: actorNamespaces) {\n      ns.value->link(linked.actorStorage, linked.alarmScheduler);\n    }\n\n    ioChannels = kj::mv(linked);\n  }\n\n  void unlink() override {\n    // Need to remove all waited until tasks before destroying `ioChannels`\n    waitUntilTasks.clear();\n\n    // Need to tear down all actors before tearing down `ioChannels.actorStorage`.\n    actorNamespaces.clear();\n\n    // OK, now we can unlink.\n    ioChannels = {};\n  }\n\n  kj::Maybe<ActorNamespace&> getActorNamespace(kj::StringPtr name) {\n    KJ_IF_SOME(a, actorNamespaces.find(name)) {\n      return *a;\n    } else {\n      return kj::none;\n    }\n  }\n\n  kj::HashMap<kj::StringPtr, kj::Own<ActorNamespace>>& getActorNamespaces() {\n    return actorNamespaces;\n  }\n\n  kj::Own<WorkerInterface> startRequest(IoChannelFactory::SubrequestMetadata metadata) override {\n    return startRequest(kj::mv(metadata), kj::none, {});\n  }\n\n  bool hasHandler(kj::StringPtr handlerName) override {\n    KJ_IF_SOME(h, defaultEntrypointHandlers) {\n      return h.contains(handlerName);\n    } else {\n      return false;\n    }\n  }\n\n  kj::Own<WorkerInterface> startRequest(IoChannelFactory::SubrequestMetadata metadata,\n      kj::Maybe<kj::StringPtr> entrypointName,\n      Frankenvalue props,\n      kj::Maybe<kj::Own<Worker::Actor>> actor = kj::none,\n      bool isTracer = false) {\n    TRACE_EVENT(\"workerd\", \"Server::WorkerService::startRequest()\");\n\n    auto& channels = KJ_ASSERT_NONNULL(ioChannels.tryGet<LinkedIoChannels>());\n\n    kj::Vector<kj::Own<WorkerInterface>> bufferedTailWorkers(channels.tails.size());\n    kj::Vector<kj::Own<WorkerInterface>> streamingTailWorkers(channels.streamingTails.size());\n    auto addWorkerIfNotRecursiveTracer = [this, isTracer](\n                                             kj::Vector<kj::Own<WorkerInterface>>& workers,\n                                             IoChannelFactory::SubrequestChannel& channel) {\n      // Caution here... if the tail worker ends up having a circular dependency\n      // on the worker we'll end up with an infinite loop trying to initialize.\n      // We can test this directly but it's more difficult to test indirect\n      // loops (dependency of dependency, etc). Here we're just going to keep\n      // it simple and just check the direct dependency.\n      // If service refers to an EntrypointService, we need to compare with the underlying\n      // WorkerService to match this.\n      auto& service = KJ_UNWRAP_OR(kj::dynamicDowncastIfAvailable<Service>(channel), {\n        // Not a Service, probably not self-referential.\n        workers.add(channel.startRequest({}));\n        return;\n      });\n\n      if (service.service() == this) {\n        if (!isTracer) {\n          // This is a self-reference. Create a request with isTracer=true.\n          KJ_IF_SOME(s, kj::dynamicDowncastIfAvailable<WorkerService>(service)) {\n            workers.add(s.startRequest({}, kj::none, {}, kj::none, true));\n          } else KJ_IF_SOME(s, kj::dynamicDowncastIfAvailable<EntrypointService>(service)) {\n            workers.add(s.startRequest({}, true));\n          } else {\n            KJ_FAIL_ASSERT(\"Unexpected service type in recursive tail worker declaration\");\n          }\n        } else {\n          // Intentionally left empty to prevent infinite recursion with tail workers tailing\n          // themselves\n        }\n      } else {\n        workers.add(service.startRequest({}));\n      }\n    };\n\n    // Do not add tracers for worker interfaces with the \"test\" entrypoint – we generally do not\n    // need to trace the test event, although this is useful to test that span tracing works, so\n    // we are not implementing a (more complex) mechanism to disable tracing for all test() events\n    // here.\n    if (entrypointName.orDefault(\"\") != \"test\"_kj) {\n      for (auto& service: channels.tails) {\n        addWorkerIfNotRecursiveTracer(bufferedTailWorkers, *service);\n      }\n      for (auto& service: channels.streamingTails) {\n        addWorkerIfNotRecursiveTracer(streamingTailWorkers, *service);\n      }\n    }\n\n    kj::Maybe<kj::Own<WorkerTracer>> workerTracer = kj::none;\n\n    if (!bufferedTailWorkers.empty() || !streamingTailWorkers.empty()) {\n      // Setting up buffered tail workers support, but only if we actually have tail workers\n      // configured.\n      auto executionModel =\n          actor == kj::none ? ExecutionModel::STATELESS : ExecutionModel::DURABLE_OBJECT;\n      auto tailStreamWriter = tracing::initializeTailStreamWriter(\n          streamingTailWorkers.releaseAsArray(), waitUntilTasks);\n      auto trace = kj::refcounted<Trace>(kj::none /* stableId */, kj::none /* scriptName */,\n          kj::none /* scriptVersion */, kj::none /* dispatchNamespace */, kj::none /* scriptId */,\n          nullptr /* scriptTags */, mapCopyString(entrypointName), executionModel,\n          kj::none /* durableObjectId */);\n      kj::Own<WorkerTracer> tracer = kj::refcounted<WorkerTracer>(\n          kj::none, kj::mv(trace), PipelineLogLevel::FULL, kj::none, kj::mv(tailStreamWriter));\n\n      // When the tracer is complete, deliver traces to any buffered tail workers. We end up\n      // creating two references to the WorkerTracer, one held by the observer and one that will be\n      // passed to the IoContext. This ensures that the tracer lives long enough to receive all\n      // events.\n      if (!bufferedTailWorkers.empty()) {\n        waitUntilTasks.add(tracer->onComplete().then(\n            kj::coCapture([tailWorkers = bufferedTailWorkers.releaseAsArray()](\n                              kj::Own<Trace> trace) mutable -> kj::Promise<void> {\n          for (auto& worker: tailWorkers) {\n            auto event = kj::heap<workerd::api::TraceCustomEvent>(\n                workerd::api::TraceCustomEvent::TYPE, kj::arr(kj::addRef(*trace)));\n            co_await worker->customEvent(kj::mv(event)).ignoreResult();\n          }\n          co_return;\n        })));\n      }\n      workerTracer = kj::mv(tracer);\n    }\n\n    KJ_IF_SOME(w, workerTracer) {\n      w->setMakeUserRequestSpanFunc([&w = *w]() {\n        return SpanParent(kj::refcounted<UserSpanObserver>(\n            kj::refcounted<SequentialSpanSubmitter>(kj::addRef(w))));\n      });\n    }\n    kj::Own<RequestObserver> observer =\n        kj::refcounted<RequestObserverWithTracer>(mapAddRef(workerTracer), waitUntilTasks);\n\n    return newWorkerEntrypoint(\n        threadContext, kj::atomicAddRef(*worker), entrypointName, kj::mv(props), kj::mv(actor),\n        kj::Own<LimitEnforcer>(this, kj::NullDisposer::instance), {},  // ioContextDependency\n        kj::Own<IoChannelFactory>(this, kj::NullDisposer::instance), kj::mv(observer),\n        waitUntilTasks,\n        true,                  // tunnelExceptions\n        kj::mv(workerTracer),  // workerTracer\n        kj::mv(metadata.cfBlobJson),\n        kj::none  // versionInfo\n    );\n  }\n\n  class ActorNamespace final {\n   public:\n    ActorNamespace(kj::Own<ActorClass> actorClass,\n        const ActorConfig& config,\n        kj::Timer& timer,\n        capnp::ByteStreamFactory& byteStreamFactory,\n        ChannelTokenHandler& channelTokenHandler,\n        kj::Network& dockerNetwork,\n        kj::Maybe<kj::StringPtr> dockerPath,\n        kj::Maybe<kj::StringPtr> containerEgressInterceptorImage,\n        kj::TaskSet& waitUntilTasks)\n        : actorClass(kj::mv(actorClass)),\n          config(config),\n          timer(timer),\n          byteStreamFactory(byteStreamFactory),\n          channelTokenHandler(channelTokenHandler),\n          dockerNetwork(dockerNetwork),\n          dockerPath(dockerPath),\n          containerEgressInterceptorImage(containerEgressInterceptorImage),\n          waitUntilTasks(waitUntilTasks) {}\n\n    // Called at link time to provide needed resources.\n    void link(kj::Maybe<const kj::Directory&> serviceActorStorage,\n        kj::Maybe<AlarmScheduler&> alarmScheduler) {\n      KJ_IF_SOME(dir, serviceActorStorage) {\n        KJ_IF_SOME(d, config.tryGet<Durable>()) {\n          // Create a subdirectory for this namespace based on the unique key.\n          this->actorStorage.emplace(dir.openSubdir(\n              kj::Path({d.uniqueKey}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY));\n        }\n      }\n\n      this->alarmScheduler = alarmScheduler;\n    }\n\n    const ActorConfig& getConfig() {\n      return config;\n    }\n\n    kj::Own<IoChannelFactory::ActorChannel> getActorChannel(Worker::Actor::Id id) {\n      KJ_IF_SOME(doId, id.tryGet<kj::Own<ActorIdFactory::ActorId>>()) {\n        // To emulate production, we have to recreate this ID.\n        ActorIdFactoryImpl::ActorIdImpl* idImpl =\n            dynamic_cast<ActorIdFactoryImpl::ActorIdImpl*>(doId.get());\n        KJ_ASSERT(idImpl != nullptr, \"Unexpected ActorId type?\");\n        idImpl->clearName();\n      }\n\n      return kj::refcounted<ActorChannelImpl>(getActorContainer(kj::mv(id)));\n    }\n\n    class ActorContainer;\n    using ActorMap = kj::HashMap<kj::StringPtr, kj::Own<ActorContainer>>;\n\n    // ActorContainer mostly serves as a wrapper around Worker::Actor.\n    // We use it to associate a HibernationManager with the Worker::Actor, since the\n    // Worker::Actor can be destroyed during periods of prolonged inactivity.\n    //\n    // We use a RequestTracker to track strong references to this ActorContainer's Worker::Actor.\n    // Once there are no Worker::Actor's left (excluding our own), `inactive()` is triggered and we\n    // initiate the eviction of the Durable Object. If no requests arrive in the next 10 seconds,\n    // the DO is evicted, otherwise we cancel the eviction task.\n    class ActorContainer final: public RequestTracker::Hooks,\n                                public kj::Refcounted,\n                                public Worker::Actor::FacetManager {\n     public:\n      // Information which is needed before start() can be called, but may not be available yet\n      // when the ActorContainer is constructed (especially in the case of facets).\n      struct ClassAndId {\n        kj::Own<ActorClass> actorClass;\n        Worker::Actor::Id id;\n\n        ClassAndId(kj::Own<ActorClass> actorClass, Worker::Actor::Id id)\n            : actorClass(kj::mv(actorClass)),\n              id(kj::mv(id)) {}\n      };\n\n      ActorContainer(kj::String key,\n          ActorNamespace& ns,\n          kj::Maybe<ActorContainer&> parent,\n          kj::OneOf<ClassAndId, kj::Promise<ClassAndId>> classAndIdParam,\n          kj::Timer& timer)\n          : key(kj::mv(key)),\n            tracker(kj::refcounted<RequestTracker>(*this)),\n            ns(ns),\n            root(parent.map([](ActorContainer& p) -> ActorContainer& { return p.root; })\n                     .orDefault(*this)),\n            parent(parent),\n            timer(timer),\n            lastAccess(timer.now()) {\n        KJ_SWITCH_ONEOF(classAndIdParam) {\n          KJ_CASE_ONEOF(value, ClassAndId) {\n            // `classAndId` is immediately available.\n            classAndId = kj::mv(value);\n          }\n          KJ_CASE_ONEOF(promise, kj::Promise<ClassAndId>) {\n            // We are receiving a promise for a `ClassAndId` to come later. Arrange to initialize\n            // `classAndId` from the promise. Create a `ForkedPromise<void>` that resolves when\n            // initialization is complete.\n            classAndId = promise\n                             .then([this](ClassAndId value) {\n              auto& forked = KJ_ASSERT_NONNULL(classAndId.tryGet<kj::ForkedPromise<void>>());\n              if (!forked.hasBranches()) {\n                // HACK: We're about to replace the ForkedPromise but it has no one waiting on it,\n                //   so we'd end up cancelling ourselves. Add a branch and detach it so this doesn't\n                //   happen.\n                forked.addBranch().detach([](auto&&) {});\n              }\n\n              classAndId = kj::mv(value);\n            }).fork();\n          }\n        }\n      }\n\n      ~ActorContainer() noexcept(false) {\n        // Shutdown the tracker so we don't use active/inactive hooks anymore.\n        tracker->shutdown();\n\n        for (auto& facet: facets) {\n          facet.value->abort(kj::none);\n        }\n\n        KJ_IF_SOME(a, actor) {\n          // Unknown broken reason.\n          auto reason = 0;\n          a->shutdown(reason);\n        }\n\n        // Drop the container client reference\n        // If setInactivityTimeout() was called, there's still a timer holding a reference\n        // If not, this may be the last reference and the ContainerClient destructor will run\n        containerClient = kj::none;\n      }\n\n      void active() override {\n        // We're handling a new request, cancel the eviction promise.\n        shutdownTask = kj::none;\n      }\n\n      void inactive() override {\n        // Durable objects are evictable by default.\n        bool isEvictable = true;\n        KJ_SWITCH_ONEOF(ns.config) {\n          KJ_CASE_ONEOF(c, Durable) {\n            isEvictable = c.isEvictable;\n          }\n          KJ_CASE_ONEOF(c, Ephemeral) {\n            isEvictable = c.isEvictable;\n          }\n        }\n        if (isEvictable) {\n          KJ_IF_SOME(a, actor) {\n            KJ_IF_SOME(m, a->getHibernationManager()) {\n              // The hibernation manager needs to survive actor eviction and be passed to the actor\n              // constructor next time we create it.\n              manager = m.addRef();\n            }\n          }\n          shutdownTask =\n              handleShutdown().eagerlyEvaluate([](kj::Exception&& e) { KJ_LOG(ERROR, e); });\n        }\n      }\n\n      kj::StringPtr getKey() {\n        return key;\n      }\n      RequestTracker& getTracker() {\n        return *tracker;\n      }\n      kj::Maybe<kj::Own<Worker::Actor::HibernationManager>> tryGetManagerRef() {\n        return manager.map(\n            [&](kj::Own<Worker::Actor::HibernationManager>& m) { return kj::addRef(*m); });\n      }\n      void updateAccessTime() {\n        lastAccess = timer.now();\n        KJ_IF_SOME(p, parent) {\n          p.updateAccessTime();\n        }\n      }\n      kj::TimePoint getLastAccess() {\n        return lastAccess;\n      }\n\n      bool hasClients() {\n        // If anyone holds a reference to the container other than the actor map, then it must be\n        // a client.\n        if (isShared()) return true;\n        for (auto& facet: facets) {\n          if (facet.value->hasClients()) return true;\n        }\n        return false;\n      }\n      kj::Own<ActorContainer> addRef() {\n        return kj::addRef(*this);\n      }\n\n      // Get the actor, starting it if it's not already running.\n      kj::Promise<kj::Own<Worker::Actor>> getActor() {\n        requireNotBroken();\n\n        if (actor == kj::none) {\n          KJ_IF_SOME(promise, classAndId.tryGet<kj::ForkedPromise<void>>()) {\n            co_await promise;\n          }\n\n          auto& [actorClass, id] = KJ_ASSERT_NONNULL(classAndId.tryGet<ClassAndId>());\n\n          KJ_IF_SOME(promise, actorClass->whenReady()) {\n            co_await promise;\n          }\n\n          // A concurrent request could have started the actor, so check again.\n          if (actor == kj::none) {\n            start(actorClass, id);\n          }\n        }\n\n        co_return KJ_ASSERT_NONNULL(actor)->addRef();\n      }\n\n      kj::Promise<kj::Own<WorkerInterface>> startRequest(\n          IoChannelFactory::SubrequestMetadata metadata) {\n        auto actor = co_await getActor();\n\n        if (ns.cleanupTask == kj::none) {\n          // Need to start the cleanup loop.\n          ns.cleanupTask = ns.cleanupLoop();\n        }\n\n        // Since `getActor()` completed, `classAndId` must be resolved.\n        auto& actorClass = KJ_ASSERT_NONNULL(classAndId.tryGet<ClassAndId>()).actorClass;\n\n        co_return actorClass->startRequest(kj::mv(metadata), kj::mv(actor))\n            .attach(kj::defer([self = kj::addRef(*this)]() mutable { self->updateAccessTime(); }));\n      }\n\n      // Abort this actor, shutting it down.\n      //\n      // It is the caller's responsibility to ensure that the aborted ActorContainer has been\n      // removed from any maps that would cause it to receive further traffic, since any further\n      // requests will be expected to fail. abort() does NOT attempt to remove the ActorContainer\n      // from the parent facet map since at most call sites it makes more sense to handle this\n      // directly.\n      void abort(kj::Maybe<const kj::Exception&> reason) {\n        if (brokenReason != kj::none) return;\n\n        KJ_IF_SOME(a, actor) {\n          // Unknown broken reason.\n          a->shutdown(0, reason);\n        }\n\n        for (auto& facet: facets) {\n          facet.value->abort(reason);\n        }\n\n        onBrokenTask = kj::none;\n        shutdownTask = kj::none;\n        manager = kj::none;\n        tracker->shutdown();\n        actor = kj::none;\n        containerClient = kj::none;\n\n        KJ_IF_SOME(r, reason) {\n          brokenReason = kj::cp(r);\n        } else {\n          brokenReason = JSG_KJ_EXCEPTION(FAILED, Error, \"Actor aborted for uknown reason.\");\n        }\n      }\n\n      kj::Own<ActorContainer> getFacetContainer(\n          kj::String childKey, kj::Function<kj::Promise<StartInfo>()> getStartInfo) {\n        auto makeContainer = [&]() {\n          auto promise = callFacetStartCallback(kj::mv(getStartInfo));\n          return kj::refcounted<ActorContainer>(\n              kj::mv(childKey), ns, *this, kj::mv(promise), timer);\n        };\n\n        bool isNew = false;\n\n        auto& entry = facets.findOrCreateEntry(childKey, [&]() mutable {\n          isNew = true;\n          auto container = makeContainer();\n          return ActorMap::Entry{container->getKey(), kj::mv(container)};\n        });\n\n        return entry.value->addRef();\n      }\n\n      uint getDepth() const override {\n        KJ_IF_SOME(p, parent) {\n          return 1 + p.getDepth();\n        }\n        return 0;\n      }\n\n      kj::Own<IoChannelFactory::ActorChannel> getFacet(\n          kj::StringPtr name, kj::Function<kj::Promise<StartInfo>()> getStartInfo) override {\n        auto facet = getFacetContainer(kj::str(name), kj::mv(getStartInfo));\n        return kj::refcounted<ActorChannelImpl>(kj::mv(facet));\n      }\n\n      void abortFacet(kj::StringPtr name, kj::Exception reason) override {\n        KJ_IF_SOME(entry, facets.findEntry(name)) {\n          entry.value->abort(reason);\n          facets.erase(entry);\n        }\n      }\n\n      void deleteFacet(kj::StringPtr name) override {\n        // First, abort any running facets.\n        abortFacet(name, JSG_KJ_EXCEPTION(FAILED, Error, \"Facet was deleted.\"));\n\n        // Then delete the underlying storage.\n        KJ_IF_SOME(as, ns.actorStorage) {\n          // Note that if there's no facet index then there couldn't possibly be any child storage.\n          KJ_IF_SOME(index, getFacetTreeIndexIfNotEmpty()) {\n            uint childId = index.getId(getFacetId(), name);\n            deleteDescendantStorage(*as.directory, childId);\n            as.directory->remove(getSqlitePathForId(childId));\n          }\n        }\n      }\n\n     private:\n      // The actor is constructed after the ActorContainer so it starts off empty.\n      kj::Maybe<kj::Own<Worker::Actor>> actor;\n\n      kj::String key;\n      kj::Own<RequestTracker> tracker;\n      ActorNamespace& ns;\n      ActorContainer& root;\n      kj::Maybe<ActorContainer&> parent;\n      kj::Timer& timer;\n      kj::TimePoint lastAccess;\n      kj::Maybe<kj::Own<Worker::Actor::HibernationManager>> manager;\n      kj::Maybe<kj::Promise<void>> shutdownTask;\n      kj::Maybe<kj::Promise<void>> onBrokenTask;\n      kj::Maybe<kj::Exception> brokenReason;\n\n      // Reference to the ContainerClient (if container is enabled for this actor)\n      kj::Maybe<kj::Own<ContainerClient>> containerClient;\n\n      // If this is a `ForkedPromise<void>`, await the promise. When it has resolved, then\n      // `classAndId` will have been replaced with the resolved `ClassAndId` value.\n      kj::OneOf<ClassAndId, kj::ForkedPromise<void>> classAndId;\n\n      // FacetTreeIndex for this actor. Only initialized on the root.\n      kj::Maybe<kj::Own<FacetTreeIndex>> facetTreeIndex;\n\n      // ID of this facet. Initialized when getFacetId() is first called.\n      kj::Maybe<uint> facetId;\n\n      ActorMap facets;\n\n      // Get the facet ID for this facet. The root facet always has ID zero, but all other facets\n      // need to be looked up in the index to make sure they are assigned consistent IDs.\n      uint getFacetId() {\n        KJ_IF_SOME(f, facetId) {\n          return f;\n        }\n\n        ActorContainer& parent = KJ_UNWRAP_OR(this->parent, return 0);\n\n        FacetTreeIndex& index = root.ensureFacetTreeIndex();\n        return index.getId(parent.getFacetId(), key);\n      }\n\n      // Get the facet tree index, opening the file if it hasn't been opened yet, and creating it\n      // if it hasn't been created yet.\n      FacetTreeIndex& ensureFacetTreeIndex() {\n        KJ_REQUIRE(parent == kj::none, \"only 'root' may ensureFacetTreeIndex()\");\n\n        KJ_IF_SOME(i, facetTreeIndex) {\n          return *i;\n        } else {\n          // Facet tree index hasn't been initialized yet. Do that now (opening the existing file,\n          // or creating it if it doesn't exist).\n          auto& as = KJ_REQUIRE_NONNULL(\n              ns.actorStorage, \"can't call getFacetId() when there's no backing storage\");\n          auto indexFile = as.directory->openFile(\n              kj::Path({kj::str(key, \".facets\")}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n          return *facetTreeIndex.emplace(kj::heap<FacetTreeIndex>(kj::mv(indexFile)));\n        }\n      }\n\n      // Like ensureFacetTreeIndex() but if the index doesn't exist on disk, return kj::none.\n      kj::Maybe<FacetTreeIndex&> getFacetTreeIndexIfNotEmpty() {\n        KJ_REQUIRE(parent == kj::none);\n\n        KJ_IF_SOME(i, facetTreeIndex) {\n          return *i;\n        } else {\n          // Facet tree index hasn't been initialized yet. If the file exists, open it. Otherwise,\n          // assume empty and return none.\n          auto& as = KJ_UNWRAP_OR(ns.actorStorage, return kj::none);\n          auto indexFile = KJ_UNWRAP_OR(\n              as.directory->tryOpenFile(kj::Path({kj::str(key, \".facets\")}), kj::WriteMode::MODIFY),\n              return kj::none);\n          return *facetTreeIndex.emplace(kj::heap<FacetTreeIndex>(kj::mv(indexFile)));\n        }\n      }\n\n      // Get the path to the facet's sqlite database, within the actor namespace directory.\n      kj::Path getSqlitePathForId(uint id) {\n        if (id == 0) {\n          return kj::Path({kj::str(root.key, \".sqlite\")});\n        } else {\n          return kj::Path({kj::str(root.key, '.', id, \".sqlite\")});\n        }\n      }\n\n      void deleteDescendantStorage(const kj::Directory& dir, uint parentId) {\n        KJ_IF_SOME(index, getFacetTreeIndexIfNotEmpty()) {\n          deleteDescendantStorage(dir, index, parentId);\n        } else {\n          // There's no index, so there must be no facets (other than the root).\n          KJ_ASSERT(parentId == 0);\n        }\n      }\n\n      void deleteDescendantStorage(const kj::Directory& dir, FacetTreeIndex& index, uint parentId) {\n        index.forEachChild(parentId, [&](uint childId, kj::StringPtr childName) {\n          deleteDescendantStorage(dir, index, childId);\n          dir.remove(getSqlitePathForId(childId));\n        });\n      }\n\n      void requireNotBroken() {\n        KJ_IF_SOME(e, brokenReason) {\n          kj::throwFatalException(kj::cp(e));\n        }\n      }\n\n      kj::Promise<void> monitorOnBroken(Worker::Actor& actor) {\n        try {\n          // It's possible for this to never resolve if the actor never breaks,\n          // in which case the returned promise will just be canceled.\n          co_await actor.onBroken();\n          KJ_FAIL_ASSERT(\"actor.onBroken() resolved normally?\");\n        } catch (...) {\n          brokenReason = kj::getCaughtExceptionAsKj();\n        }\n\n        for (auto& facet: facets) {\n          facet.value->abort(brokenReason);\n        }\n        facets.clear();\n\n        // HACK: Dropping the ActorContainer will delete onBrokenTask, cancelling ourselves. This\n        //   would crash. To avoid the problem, detach ourselves. This is safe because we know that\n        //   once we return there's nothing left for this promise to do anyway.\n        KJ_ASSERT_NONNULL(onBrokenTask).detach([](kj::Exception&& e) {});\n\n        // Hollow out the object, so that if it still has references, they won't keep these parts\n        // alive. Since any further calls to `getActor()` will throw, we don't have to worry about\n        // the actor being recreated.\n        auto actorToDrop = kj::mv(this->actor);\n        tracker->shutdown();\n        auto managerToDrop = kj::mv(manager);\n\n        // Note that we remove the entire ActorContainer from the map -- this drops the\n        // HibernationManager so any connected hibernatable websockets will be disconnected.\n        KJ_IF_SOME(p, parent) {\n          p.facets.erase(key);\n        } else {\n          ns.actors.erase(key);\n        }\n\n        // WARNING: `this` MAY HAVE BEEN DELETED as a result of the above `erase()`. Do not access\n        //   it again here.\n      }\n\n      // Processes the eviction of the Durable Object and hibernates active websockets.\n      kj::Promise<void> handleShutdown() {\n        // After 10 seconds of inactivity, we destroy the Worker::Actor and hibernate any active\n        // JS WebSockets.\n        // TODO(someday): We could make this timeout configurable to make testing less burdensome.\n        co_await timer.afterDelay(10 * kj::SECONDS);\n        // Cancel the onBroken promise, since we're about to destroy the actor anyways and don't\n        // want to trigger it.\n        onBrokenTask = kj::none;\n        KJ_IF_SOME(a, actor) {\n          if (a->isShared()) {\n            // Our ActiveRequest refcounting has broken somewhere. This is likely because we're\n            // `addRef`-ing an actor that has had an ActiveRequest attached to its kj::Own (in other\n            // words, the ActiveRequest count is less than it should be).\n            //\n            // Rather than dropping our actor and possibly ending up with split-brain,\n            // we should opt out of the deferred proxy optimization and log the error to Sentry.\n            KJ_LOG(ERROR,\n                \"Detected internal bug in hibernation: Durable Object has strong references \"\n                \"when hibernation timeout expired.\");\n\n            co_return;\n          }\n          KJ_IF_SOME(m, manager) {\n            auto& worker = a->getWorker();\n            auto workerStrongRef = kj::atomicAddRef(worker);\n            // Take an async lock, we can't use `takeAsyncLock(RequestObserver&)` since we don't\n            // have an `IncomingRequest` at this point.\n            //\n            // Note that we do not have a race here because this is part of the `shutdownTask`\n            // promise. If a new request comes in while we're waiting to get the lock then we will\n            // cancel this promise.\n            Worker::AsyncLock asyncLock = co_await worker.takeAsyncLockWithoutRequest(nullptr);\n            workerStrongRef->runInLockScope(\n                asyncLock, [&](Worker::Lock& lock) { m->hibernateWebSockets(lock); });\n          }\n          a->shutdown(\n              0, KJ_EXCEPTION(DISCONNECTED, \"broken.dropped; Actor freed due to inactivity\"));\n        }\n        // Destroy the last strong Worker::Actor reference.\n        actor = kj::none;\n\n        // Drop our reference to the ContainerClient\n        // If setInactivityTimeout() was called, the timer still holds a reference\n        // so the container stays alive until the timeout expires\n        containerClient = kj::none;\n      }\n\n      void start(kj::Own<ActorClass>& actorClass, Worker::Actor::Id& id) {\n        KJ_REQUIRE(actor == nullptr);\n\n        auto makeActorCache = [this](const ActorCache::SharedLru& sharedLru, OutputGate& outputGate,\n                                  ActorCache::Hooks& hooks,\n                                  SqliteObserver& sqliteObserver) mutable {\n          return ns.config.tryGet<Durable>().map(\n              [&](const Durable& d) -> kj::Own<ActorCacheInterface> {\n            KJ_IF_SOME(as, ns.actorStorage) {\n              kj::Own<ActorSqlite::Hooks> sqliteHooks;\n              if (parent == kj::none) {\n                KJ_IF_SOME(a, ns.alarmScheduler) {\n                  sqliteHooks = kj::heap<ActorSqliteHooks>(\n                      a, ActorKey{.uniqueKey = d.uniqueKey, .actorId = key});\n                } else {\n                  // No alarm scheduler available, use default hooks instance.\n                  sqliteHooks = fakeOwn(ActorSqlite::Hooks::getDefaultHooks());\n                }\n              } else {\n                // TODO(someday): Support alarms in facets, somehow.\n                sqliteHooks = fakeOwn(ActorSqlite::Hooks::getDefaultHooks());\n              }\n\n              uint selfId = getFacetId();\n              auto path = getSqlitePathForId(selfId);\n              auto db = kj::heap<SqliteDatabase>(\n                  as.vfs, kj::mv(path), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n              // Before we do anything, make sure the database is in WAL mode. We also need to\n              // do this after reset() is used, so register a callback for that.\n              db->run(\"PRAGMA journal_mode=WAL;\");\n\n              db->afterReset([this, &dir = *as.directory, selfId](SqliteDatabase& db) {\n                db.run(\"PRAGMA journal_mode=WAL;\");\n\n                // reset() is used when the app called deleteAll(), in which case we also want to\n                // delete all child facets.\n                // TODO(someday): Arguably this should be transactional somehow so if we fail here\n                //   we don't leave the facets still there after the parent has already been reset.\n                //   But most filesystems do not support transactions, so we'd have to do something\n                //   like store a flag in the parent DB saying \"reset pending\" so that on a restart\n                //   we retry the deletions. Note that in production on SRS, this is actually\n                //   transactional -- there's only a problem when running locally with workerd.\n                deleteDescendantStorage(dir, selfId);\n              });\n\n              return kj::heap<ActorSqlite>(kj::mv(db), outputGate,\n                  [](SpanParent) -> kj::Promise<void> { return kj::READY_NOW; }, *sqliteHooks)\n                  .attach(kj::mv(sqliteHooks));\n            } else {\n              // Create an ActorCache backed by a fake, empty storage. Elsewhere, we configure\n              // ActorCache never to flush, so this effectively creates in-memory storage.\n              return kj::heap<ActorCache>(\n                  newEmptyReadOnlyActorStorage(), sharedLru, outputGate, hooks);\n            }\n          });\n        };\n\n        bool enableSql = true;\n        kj::Maybe<config::Worker::DurableObjectNamespace::ContainerOptions::Reader>\n            containerOptions = kj::none;\n        kj::Maybe<kj::StringPtr> uniqueKey;\n        KJ_SWITCH_ONEOF(ns.config) {\n          KJ_CASE_ONEOF(c, Durable) {\n            enableSql = c.enableSql;\n            containerOptions = c.containerOptions;\n            uniqueKey = c.uniqueKey;\n          }\n          KJ_CASE_ONEOF(c, Ephemeral) {\n            enableSql = c.enableSql;\n          }\n        }\n\n        auto makeStorage =\n            [enableSql = enableSql](jsg::Lock& js, const Worker::Api& api,\n                ActorCacheInterface& actorCache) -> jsg::Ref<api::DurableObjectStorage> {\n          return js.alloc<api::DurableObjectStorage>(\n              js, IoContext::current().addObject(actorCache), enableSql);\n        };\n\n        auto loopback = kj::refcounted<Loopback>(*this);\n\n        kj::Maybe<rpc::Container::Client> container = kj::none;\n        KJ_IF_SOME(config, containerOptions) {\n          KJ_ASSERT(config.hasImageName(), \"Image name is required\");\n          auto imageName = config.getImageName();\n          kj::String containerId;\n          KJ_SWITCH_ONEOF(id) {\n            KJ_CASE_ONEOF(globalId, kj::Own<ActorIdFactory::ActorId>) {\n              containerId = globalId->toString();\n            }\n            KJ_CASE_ONEOF(existingId, kj::String) {\n              containerId = kj::str(existingId);\n            }\n          }\n\n          container = ns.getContainerClient(\n              kj::str(\"workerd-\", KJ_ASSERT_NONNULL(uniqueKey), \"-\", containerId), imageName);\n        }\n\n        auto actor = actorClass->newActor(getTracker(), Worker::Actor::cloneId(id),\n            kj::mv(makeActorCache), kj::mv(makeStorage), kj::mv(loopback), tryGetManagerRef(),\n            kj::mv(container), *this);\n        onBrokenTask = monitorOnBroken(*actor);\n        this->actor = kj::mv(actor);\n      }\n\n      // Helper coroutine to call `getStartInfo()`, the start callback for a facet, while making\n      // sure the function stays alive until the returned promise resolves.\n      static kj::Promise<ClassAndId> callFacetStartCallback(\n          kj::Function<kj::Promise<StartInfo>()> getStartInfo) {\n        auto info = co_await getStartInfo();\n        co_return ClassAndId(info.actorClass.downcast<ActorClass>(), kj::mv(info.id));\n      }\n    };\n\n    kj::Own<ActorContainer> getActorContainer(Worker::Actor::Id id) {\n      kj::String key;\n\n      KJ_SWITCH_ONEOF(id) {\n        KJ_CASE_ONEOF(obj, kj::Own<ActorIdFactory::ActorId>) {\n          KJ_REQUIRE(config.is<Durable>());\n          key = obj->toString();\n        }\n        KJ_CASE_ONEOF(str, kj::String) {\n          KJ_REQUIRE(config.is<Ephemeral>());\n          key = kj::str(str);\n        }\n      }\n\n      return actors\n          .findOrCreate(key, [&]() mutable {\n        auto container = kj::refcounted<ActorContainer>(kj::mv(key), *this, kj::none,\n            ActorContainer::ClassAndId(kj::addRef(*actorClass), kj::mv(id)), timer);\n\n        return kj::HashMap<kj::StringPtr, kj::Own<ActorContainer>>::Entry{\n          container->getKey(), kj::mv(container)};\n      })->addRef();\n    }\n\n    kj::Own<ContainerClient> getContainerClient(\n        kj::StringPtr containerId, kj::StringPtr imageName) {\n      KJ_IF_SOME(existingClient, containerClients.find(containerId)) {\n        return existingClient->addRef();\n      }\n\n      // No existing container in the map, create a new one\n      auto& dockerPathRef = KJ_ASSERT_NONNULL(\n          dockerPath, \"dockerPath must be defined to enable containers on this Durable Object.\");\n\n      // Grab a branch of any pending cleanup from a previous ContainerClient for this\n      // container. If it exists, pass it to the container client so it knows that it has to sync.\n      kj::Promise<void> previousCleanup = kj::READY_NOW;\n      KJ_IF_SOME(state, containerCleanupState.find(containerId)) {\n        previousCleanup = state.promise.addBranch();\n      }\n\n      // Upsert the cleanup state for this container ID. Replacing the\n      // canceler auto-cancels any in-flight cleanup tasks from the previous\n      // client's destructor. The generation counter is bumped on replacement\n      // so the cleanup callback can detect stale ownership without relying\n      // on raw pointer identity (which is vulnerable to address reuse).\n      auto canceler = kj::heap<kj::Canceler>();\n      uint64_t capturedGeneration = 0;\n      containerCleanupState.upsert(kj::str(containerId),\n          ContainerCleanupState{.canceler = kj::mv(canceler)},\n          [&capturedGeneration](ContainerCleanupState& existing, ContainerCleanupState&& incoming) {\n        existing.canceler = kj::mv(incoming.canceler);\n        capturedGeneration = ++existing.generation;\n      });\n\n      // Cleanup callback: invoked from the ContainerClient destructor with the joined\n      // with a cleanup promise\n      kj::Function<void(kj::Promise<void>)> cleanupCallback =\n          [this, containerId = kj::str(containerId), capturedGeneration](\n              kj::Promise<void> cleanupPromise) mutable {\n        KJ_IF_SOME(state, containerCleanupState.find(containerId)) {\n          if (state.generation != capturedGeneration) {\n            // A newer ContainerClient has replaced us already with another destructor.\n            // drop the promise.\n            return;\n          }\n\n          containerClients.erase(containerId);\n          // Wrap with the canceler so a future client creation can cancel these\n          // tasks\n          auto cancellable =\n              state.canceler->wrap(kj::mv(cleanupPromise)).catch_([](kj::Exception&&) {});\n\n          auto forked = kj::mv(cancellable).fork();\n          waitUntilTasks.add(forked.addBranch());\n          state.promise = kj::mv(forked);\n        }\n      };\n\n      auto client = kj::refcounted<ContainerClient>(byteStreamFactory, timer, dockerNetwork,\n          kj::str(dockerPathRef), kj::str(containerId), kj::str(imageName),\n          kj::str(KJ_ASSERT_NONNULL(containerEgressInterceptorImage,\n              \"containerEgressInterceptorImage must be configured for containers.\")),\n          waitUntilTasks, kj::mv(previousCleanup), kj::mv(cleanupCallback), channelTokenHandler);\n\n      // Store raw pointer in map (does not own)\n      containerClients.insert(kj::str(containerId), client.get());\n\n      return kj::mv(client);\n    }\n\n    void abortAll(kj::Maybe<const kj::Exception&> reason) {\n      for (auto& actor: actors) {\n        actor.value->abort(reason);\n      }\n      actors.clear();\n    }\n\n   private:\n    kj::Own<ActorClass> actorClass;\n    const ActorConfig& config;\n\n    struct ActorStorage {\n      kj::Own<const kj::Directory> directory;\n      SqliteDatabase::Vfs vfs;\n\n      ActorStorage(kj::Own<const kj::Directory> directoryParam)\n          : directory(kj::mv(directoryParam)),\n            vfs(*directory) {}\n    };\n\n    // Note: The Vfs must not be torn down until all actors have been torn down, so we have to\n    //   declare `actorStorage` before `actors`.\n    kj::Maybe<ActorStorage> actorStorage;\n\n    // Tracks the canceler and cleanup promise for a Docker container's lifecycle cleanup.\n    // Useful to await on async calls of a ContainerClient destructor when the new\n    // one appears before they've been resolved.\n    struct ContainerCleanupState {\n      // Canceler that wraps the promise fired in ~ContainerClient. Replacing\n      // it cancels any pending cleanup, which resolves the promise immediately.\n      kj::Own<kj::Canceler> canceler;\n\n      // Forked cleanup promise. A branch is added to waitUntilTasks to keep the I/O alive,\n      // and another branch is passed to the next ContainerClient so its status() can await.\n      kj::ForkedPromise<void> promise = kj::Promise<void>(kj::READY_NOW).fork();\n\n      // Monotonically increasing counter, bumped each time the canceler is replaced\n      // via upsert. The cleanup callback captures the generation at creation time and\n      // compares it to detect whether a newer ContainerClient has taken ownership,\n      // avoiding a raw-pointer identity check that is vulnerable to address reuse.\n      uint64_t generation = 0;\n    };\n\n    // Per-container cleanup state: canceler + forked cleanup promise.\n    kj::HashMap<kj::String, ContainerCleanupState> containerCleanupState;\n\n    // Map of container IDs to ContainerClients (for reconnection support with inactivity timeouts).\n    // The map holds raw pointers (not ownership) - ContainerClients are owned by actors and timers.\n    // When the last reference is dropped, the destructor removes the entry from this map.\n    kj::HashMap<kj::String, ContainerClient*> containerClients;\n\n    // If the actor is broken, we remove it from the map. However, if it's just evicted due to\n    // inactivity, we keep the ActorContainer in the map but drop the Own<Worker::Actor>. When a new\n    // request comes in, we recreate the Own<Worker::Actor>.\n    ActorMap actors;\n\n    kj::Maybe<kj::Promise<void>> cleanupTask;\n    kj::Timer& timer;\n    capnp::ByteStreamFactory& byteStreamFactory;\n    ChannelTokenHandler& channelTokenHandler;\n    kj::Network& dockerNetwork;\n    kj::Maybe<kj::StringPtr> dockerPath;\n    kj::Maybe<kj::StringPtr> containerEgressInterceptorImage;\n    kj::TaskSet& waitUntilTasks;\n    kj::Maybe<AlarmScheduler&> alarmScheduler;\n\n    // Removes actors from `actors` after 70 seconds of last access.\n    kj::Promise<void> cleanupLoop() {\n      constexpr auto EXPIRATION = 70 * kj::SECONDS;\n\n      // Don't bother running the loop if the config doesn't allow eviction.\n      KJ_SWITCH_ONEOF(config) {\n        KJ_CASE_ONEOF(c, Durable) {\n          if (!c.isEvictable) co_return;\n        }\n        KJ_CASE_ONEOF(c, Ephemeral) {\n          if (!c.isEvictable) co_return;\n        }\n      }\n\n      while (true) {\n        auto now = timer.now();\n        actors.eraseAll([&](auto&, kj::Own<ActorContainer>& entry) {\n          // Check getLastAccess() before hasClients() since it's faster.\n          if ((now - entry->getLastAccess()) <= EXPIRATION) {\n            // Used recently; don't evict.\n            return false;\n          }\n\n          if (entry->hasClients()) {\n            // There's still an active client; don't evict.\n            return false;\n          }\n\n          // No clients and not used in a while, evict this actor.\n          return true;\n        });\n\n        co_await timer.atTime(now + EXPIRATION);\n      }\n    }\n\n    // Implements actor loopback, which is used by websocket hibernation to deliver events to the\n    // actor from the websocket's read loop.\n    class Loopback: public Worker::Actor::Loopback, public kj::Refcounted {\n     public:\n      Loopback(ActorContainer& actorContainer): actorContainer(actorContainer) {}\n\n      kj::Own<WorkerInterface> getWorker(IoChannelFactory::SubrequestMetadata metadata) override {\n        return newPromisedWorkerInterface(actorContainer.startRequest(kj::mv(metadata)));\n      }\n\n      kj::Own<Worker::Actor::Loopback> addRef() override {\n        return kj::addRef(*this);\n      }\n\n     private:\n      ActorContainer& actorContainer;\n    };\n\n    class ActorSqliteHooks final: public ActorSqlite::Hooks {\n     public:\n      ActorSqliteHooks(AlarmScheduler& alarmScheduler, ActorKey actor)\n          : alarmScheduler(alarmScheduler),\n            actor(actor) {}\n\n      // We ignore the priorTask in workerd because everything should run synchronously.\n      kj::Promise<void> scheduleRun(\n          kj::Maybe<kj::Date> newAlarmTime, kj::Promise<void> priorTask) override {\n        KJ_IF_SOME(scheduledTime, newAlarmTime) {\n          alarmScheduler.setAlarm(actor, scheduledTime);\n        } else {\n          alarmScheduler.deleteAlarm(actor);\n        }\n        return kj::READY_NOW;\n      }\n\n     private:\n      AlarmScheduler& alarmScheduler;\n      ActorKey actor;\n    };\n  };\n\n private:\n  class EntrypointService final: public Service {\n   public:\n    EntrypointService(WorkerService& worker,\n        kj::Maybe<kj::StringPtr> entrypoint,\n        kj::Maybe<Frankenvalue> props,\n        const kj::HashSet<kj::String>& handlers)\n        : worker(kj::addRef(worker)),\n          entrypoint(entrypoint),\n          handlers(handlers),\n          props(kj::mv(props)) {}\n\n    kj::Own<WorkerInterface> startRequest(IoChannelFactory::SubrequestMetadata metadata) override {\n      return startRequest(kj::mv(metadata), false);\n    }\n\n    kj::Own<WorkerInterface> startRequest(\n        IoChannelFactory::SubrequestMetadata metadata, bool isTracer) {\n      Frankenvalue props;\n      KJ_IF_SOME(p, this->props) {\n        props = p.clone();\n      } else {\n        // Calling ctx.exports loopback without specifying props. Use empty props.\n      }\n      return worker->startRequest(kj::mv(metadata), entrypoint, kj::mv(props), kj::none, isTracer);\n    }\n\n    bool hasHandler(kj::StringPtr handlerName) override {\n      return handlers.contains(handlerName);\n    }\n\n    // Return underlying WorkerService.\n    virtual Service* service() override {\n      return worker;\n    }\n\n    kj::Own<Service> forProps(Frankenvalue props) override {\n      if (this->props != kj::none) {\n        // This entrypoint is already specialized. Delegate to the default implementation (which\n        // will throw an exception).\n        return Service::forProps(kj::mv(props));\n      }\n\n      return kj::refcounted<EntrypointService>(*worker, entrypoint, kj::mv(props), handlers);\n    }\n\n    void requireAllowsTransfer() override {\n      worker->requireAllowsTransfer();\n    }\n\n    kj::Array<byte> getToken(ChannelTokenUsage usage) override {\n      worker->requireAllowsTransfer();\n\n      // If requireAllowsTransfer() passed, then we are not dynamic so should have a service name.\n      // Unspecialized loopback entrypoints are not serializable, so if we get here we must have\n      // props.\n      return worker->channelTokenHandler.encodeSubrequestChannelToken(\n          usage, KJ_ASSERT_NONNULL(worker->serviceName), entrypoint, KJ_ASSERT_NONNULL(props));\n    }\n\n   private:\n    kj::Own<WorkerService> worker;\n    kj::Maybe<kj::StringPtr> entrypoint;\n    const kj::HashSet<kj::String>& handlers;\n    kj::Maybe<Frankenvalue> props;\n  };\n\n  class ActorClassImpl final: public ActorClass {\n   public:\n    ActorClassImpl(WorkerService& service, kj::StringPtr className, kj::Maybe<Frankenvalue> props)\n        : service(kj::addRef(service)),\n          className(className),\n          props(kj::mv(props)) {}\n\n    void requireAllowsTransfer() override {\n      service->requireAllowsTransfer();\n    }\n\n    kj::Own<Worker::Actor> newActor(kj::Maybe<RequestTracker&> tracker,\n        Worker::Actor::Id actorId,\n        Worker::Actor::MakeActorCacheFunc makeActorCache,\n        Worker::Actor::MakeStorageFunc makeStorage,\n        kj::Own<Worker::Actor::Loopback> loopback,\n        kj::Maybe<kj::Own<Worker::Actor::HibernationManager>> manager,\n        kj::Maybe<rpc::Container::Client> container,\n        kj::Maybe<Worker::Actor::FacetManager&> facetManager) override {\n      TimerChannel& timerChannel = *service;\n\n      // We define this event ID in the internal codebase, but to have WebSocket Hibernation\n      // work for local development we need to pass an event type.\n      static constexpr uint16_t hibernationEventTypeId = 8;\n\n      Frankenvalue props;\n      KJ_IF_SOME(p, this->props) {\n        props = p.clone();\n      } else {\n        // Using ctx.exports class loopback without specifying props. Use empty props.\n      }\n\n      return kj::refcounted<Worker::Actor>(*service->worker, tracker, kj::mv(actorId), true,\n          kj::mv(makeActorCache), className, kj::mv(props), kj::mv(makeStorage), kj::mv(loopback),\n          timerChannel, kj::refcounted<ActorObserver>(), kj::mv(manager), hibernationEventTypeId,\n          kj::mv(container), facetManager);\n    }\n\n    kj::Own<WorkerInterface> startRequest(\n        IoChannelFactory::SubrequestMetadata metadata, kj::Own<Worker::Actor> actor) override {\n      // The `props` parameter is empty here because props are not passed per-request, they are\n      // passed at Actor construction time.\n      return service->startRequest(kj::mv(metadata), className, {}, kj::mv(actor));\n    }\n\n    kj::Own<ActorClass> forProps(Frankenvalue props) override {\n      if (this->props != kj::none) {\n        // This entrypoint is already specialized. Delegate to the default implementation (which\n        // will throw an exception).\n        return ActorClass::forProps(kj::mv(props));\n      }\n\n      return kj::refcounted<ActorClassImpl>(*service, className, kj::mv(props));\n    }\n\n    kj::Array<byte> getToken(ChannelTokenUsage usage) override {\n      service->requireAllowsTransfer();\n\n      // If requireAllowsTransfer() passed, then we are not dynamic so should have a service name.\n      // Unspecialized loopback entrypoints are not serializable, so if we get here we must have\n      // props.\n      return service->channelTokenHandler.encodeActorClassChannelToken(\n          usage, KJ_ASSERT_NONNULL(service->serviceName), className, KJ_ASSERT_NONNULL(props));\n    }\n\n   private:\n    kj::Own<WorkerService> service;\n    kj::StringPtr className;\n    kj::Maybe<Frankenvalue> props;\n  };\n\n  ChannelTokenHandler& channelTokenHandler;\n\n  // This service's name as defined in the original config, or null if it's a dynamic isolate.\n  // Used only for serialization.\n  kj::Maybe<kj::StringPtr> serviceName;\n\n  ThreadContext& threadContext;\n  const kj::MonotonicClock& monotonicClock;\n\n  // LinkedIoChannels owns the SqliteDatabase::Vfs, so make sure it is destroyed last.\n  kj::OneOf<LinkCallback, LinkedIoChannels> ioChannels;\n\n  kj::Own<const Worker> worker;\n  kj::Maybe<kj::HashSet<kj::String>> defaultEntrypointHandlers;\n  kj::HashMap<kj::String, kj::HashSet<kj::String>> namedEntrypoints;\n  kj::HashSet<kj::String> actorClassEntrypoints;\n  kj::HashMap<kj::StringPtr, kj::Own<ActorNamespace>> actorNamespaces;\n  kj::TaskSet waitUntilTasks;\n  AbortActorsCallback abortActorsCallback;\n  kj::Maybe<kj::String> dockerPath;\n  kj::Maybe<kj::String> containerEgressInterceptorImage;\n  bool isDynamic;\n\n  class ActorChannelImpl final: public IoChannelFactory::ActorChannel {\n   public:\n    ActorChannelImpl(kj::Own<ActorNamespace::ActorContainer> actorContainer)\n        : actorContainer(kj::mv(actorContainer)) {}\n    ~ActorChannelImpl() noexcept(false) {\n      actorContainer->updateAccessTime();\n    }\n\n    kj::Own<WorkerInterface> startRequest(IoChannelFactory::SubrequestMetadata metadata) override {\n      return newPromisedWorkerInterface(actorContainer->startRequest(kj::mv(metadata)));\n    }\n\n   private:\n    kj::Own<ActorNamespace::ActorContainer> actorContainer;\n  };\n\n  // ---------------------------------------------------------------------------\n  // implements kj::TaskSet::ErrorHandler\n\n  void taskFailed(kj::Exception&& exception) override {\n    KJ_LOG(ERROR, exception);\n  }\n\n  // ---------------------------------------------------------------------------\n  // implements IoChannelFactory\n\n  kj::Own<WorkerInterface> startSubrequest(uint channel, SubrequestMetadata metadata) override {\n    auto& channels =\n        KJ_REQUIRE_NONNULL(ioChannels.tryGet<LinkedIoChannels>(), \"link() has not been called\");\n\n    KJ_REQUIRE(channel < channels.subrequest.size(), \"invalid subrequest channel number\");\n    return channels.subrequest[channel]->startRequest(kj::mv(metadata));\n  }\n\n  capnp::Capability::Client getCapability(uint channel) override {\n    KJ_FAIL_REQUIRE(\"no capability channels\");\n  }\n  class CacheClientImpl final: public CacheClient {\n   public:\n    CacheClientImpl(\n        IoChannelFactory::SubrequestChannel& cacheService, kj::HttpHeaderId cacheNamespaceHeader)\n        : cacheService(kj::addRef(cacheService)),\n          cacheNamespaceHeader(cacheNamespaceHeader) {}\n\n    kj::Own<kj::HttpClient> getDefault(CacheClient::SubrequestMetadata metadata) override {\n      return kj::heap<CacheHttpClientImpl>(*cacheService, cacheNamespaceHeader, kj::none,\n          kj::mv(metadata.cfBlobJson), kj::mv(metadata.parentSpan));\n    }\n\n    kj::Own<kj::HttpClient> getNamespace(\n        kj::StringPtr cacheName, CacheClient::SubrequestMetadata metadata) override {\n      auto encodedName = kj::encodeUriComponent(cacheName);\n      return kj::heap<CacheHttpClientImpl>(*cacheService, cacheNamespaceHeader, kj::mv(encodedName),\n          kj::mv(metadata.cfBlobJson), kj::mv(metadata.parentSpan));\n    }\n\n   private:\n    kj::Own<IoChannelFactory::SubrequestChannel> cacheService;\n    kj::HttpHeaderId cacheNamespaceHeader;\n  };\n\n  class CacheHttpClientImpl final: public kj::HttpClient {\n   public:\n    CacheHttpClientImpl(IoChannelFactory::SubrequestChannel& parent,\n        kj::HttpHeaderId cacheNamespaceHeader,\n        kj::Maybe<kj::String> cacheName,\n        kj::Maybe<kj::String> cfBlobJson,\n        SpanParent parentSpan)\n        : client(asHttpClient(parent.startRequest({kj::mv(cfBlobJson), kj::mv(parentSpan)}))),\n          cacheName(kj::mv(cacheName)),\n          cacheNamespaceHeader(cacheNamespaceHeader) {}\n\n    Request request(kj::HttpMethod method,\n        kj::StringPtr url,\n        const kj::HttpHeaders& headers,\n        kj::Maybe<uint64_t> expectedBodySize = kj::none) override {\n\n      return client->request(method, url, addCacheNameHeader(headers, cacheName), expectedBodySize);\n    }\n\n   private:\n    kj::Own<kj::HttpClient> client;\n    kj::Maybe<kj::String> cacheName;\n    kj::HttpHeaderId cacheNamespaceHeader;\n\n    kj::HttpHeaders addCacheNameHeader(\n        const kj::HttpHeaders& headers, kj::Maybe<kj::StringPtr> cacheName) {\n      auto headersCopy = headers.cloneShallow();\n      KJ_IF_SOME(name, cacheName) {\n        headersCopy.setPtr(cacheNamespaceHeader, name);\n      }\n\n      return headersCopy;\n    }\n  };\n\n  kj::Own<CacheClient> getCache() override {\n    auto& channels =\n        KJ_REQUIRE_NONNULL(ioChannels.tryGet<LinkedIoChannels>(), \"link() has not been called\");\n    auto& cache = *JSG_REQUIRE_NONNULL(channels.cache, Error, \"No Cache was configured\");\n    return kj::heap<CacheClientImpl>(cache, threadContext.getHeaderIds().cfCacheNamespace);\n  }\n\n  TimerChannel& getTimer() override {\n    return *this;\n  }\n\n  kj::Promise<void> writeLogfwdr(\n      uint channel, kj::FunctionParam<void(capnp::AnyPointer::Builder)> buildMessage) override {\n    auto& context = IoContext::current();\n\n    auto headers = kj::HttpHeaders(context.getHeaderTable());\n    auto client = context.getHttpClient(channel, true, kj::none, \"writeLogfwdr\"_kjc);\n\n    auto urlStr = kj::str(\"https://fake-host\");\n\n    capnp::MallocMessageBuilder requestMessage;\n    auto requestBuilder = requestMessage.initRoot<capnp::AnyPointer>();\n\n    buildMessage(requestBuilder);\n    capnp::JsonCodec json;\n    auto requestJson = json.encode(requestBuilder.getAs<api::AnalyticsEngineEvent>());\n\n    co_await context.waitForOutputLocks();\n\n    auto innerReq = client->request(kj::HttpMethod::POST, urlStr, headers, requestJson.size());\n    auto request = attachToRequest(kj::mv(innerReq), kj::refcountedWrapper(kj::mv(client)));\n\n    co_await request.body->write(requestJson.asBytes())\n        .attach(kj::mv(requestJson), kj::mv(request.body));\n    auto response = co_await request.response;\n\n    KJ_REQUIRE(response.statusCode >= 200 && response.statusCode < 300,\n        \"writeLogfwdr request returned an error\");\n    co_await response.body->readAllBytes().attach(kj::mv(response.body)).ignoreResult();\n    co_return;\n  }\n\n  kj::Own<SubrequestChannel> getSubrequestChannel(uint channel,\n      kj::Maybe<Frankenvalue> props,\n      kj::Maybe<VersionRequest> versionRequest) override {\n    auto& channels =\n        KJ_REQUIRE_NONNULL(ioChannels.tryGet<LinkedIoChannels>(), \"link() has not been called\");\n\n    KJ_REQUIRE(channel < channels.subrequest.size(), \"invalid subrequest channel number\");\n\n    SubrequestChannel& channelRef = *channels.subrequest[channel];\n\n    KJ_IF_SOME(p, props) {\n      // Requesting specialization of loopback (ctx.exports) entrypoint with props.\n      auto& service = KJ_REQUIRE_NONNULL(kj::dynamicDowncastIfAvailable<Service>(channelRef),\n          \"referenced channel is not a loopback channel\");\n      return service.forProps(kj::mv(p));\n    }\n\n    return kj::addRef(channelRef);\n  }\n\n  kj::Own<ActorChannel> getGlobalActor(uint channel,\n      const ActorIdFactory::ActorId& id,\n      kj::Maybe<kj::String> locationHint,\n      ActorGetMode mode,\n      bool enableReplicaRouting,\n      ActorRoutingMode routingMode,\n      SpanParent parentSpan,\n      kj::Maybe<ActorVersion> version) override {\n    JSG_REQUIRE(mode == ActorGetMode::GET_OR_CREATE, Error,\n        \"workerd only supports GET_OR_CREATE mode for getting actor stubs\");\n    JSG_REQUIRE(!enableReplicaRouting, Error, \"workerd does not support replica routing.\");\n    JSG_REQUIRE(routingMode == ActorRoutingMode::DEFAULT, Error,\n        \"workerd does not support replica routing.\");\n    auto& channels =\n        KJ_REQUIRE_NONNULL(ioChannels.tryGet<LinkedIoChannels>(), \"link() has not been called\");\n\n    KJ_REQUIRE(channel < channels.actor.size(), \"invalid actor channel number\");\n    auto& ns = JSG_REQUIRE_NONNULL(\n        channels.actor[channel], Error, \"Actor namespace configuration was invalid.\");\n    KJ_REQUIRE(ns.getConfig().is<Durable>());  // should have been verified earlier\n    return ns.getActorChannel(id.clone());\n  }\n\n  kj::Own<ActorChannel> getColoLocalActor(\n      uint channel, kj::StringPtr id, SpanParent parentSpan) override {\n    auto& channels =\n        KJ_REQUIRE_NONNULL(ioChannels.tryGet<LinkedIoChannels>(), \"link() has not been called\");\n\n    KJ_REQUIRE(channel < channels.actor.size(), \"invalid actor channel number\");\n    auto& ns = JSG_REQUIRE_NONNULL(\n        channels.actor[channel], Error, \"Actor namespace configuration was invalid.\");\n    KJ_REQUIRE(ns.getConfig().is<Ephemeral>());  // should have been verified earlier\n    return ns.getActorChannel(kj::str(id));\n  }\n\n  kj::Own<ActorClassChannel> getActorClass(uint channel, kj::Maybe<Frankenvalue> props) override {\n    auto& channels =\n        KJ_REQUIRE_NONNULL(ioChannels.tryGet<LinkedIoChannels>(), \"link() has not been called\");\n\n    KJ_REQUIRE(channel < channels.actorClass.size(), \"invalid actor class channel number\");\n\n    ActorClass& cls = *channels.actorClass[channel];\n\n    KJ_IF_SOME(p, props) {\n      return cls.forProps(kj::mv(p));\n    }\n\n    return kj::addRef(cls);\n  }\n\n  void abortAllActors(kj::Maybe<kj::Exception&> reason) override {\n    abortActorsCallback(reason);\n  }\n\n  kj::Own<WorkerStubChannel> loadIsolate(uint loaderChannel,\n      kj::Maybe<kj::String> name,\n      kj::Function<kj::Promise<DynamicWorkerSource>()> fetchSource) override;\n\n  kj::Network& getWorkerdDebugPortNetwork() override {\n    auto& channels =\n        KJ_REQUIRE_NONNULL(ioChannels.tryGet<LinkedIoChannels>(), \"link() has not been called\");\n    return KJ_REQUIRE_NONNULL(channels.workerdDebugPortNetwork,\n        \"workerdDebugPort binding is not enabled for this worker\");\n  }\n\n  kj::Own<SubrequestChannel> subrequestChannelFromToken(\n      ChannelTokenUsage usage, kj::ArrayPtr<const byte> token) override {\n    return channelTokenHandler.decodeSubrequestChannelToken(usage, token);\n  }\n\n  kj::Own<ActorClassChannel> actorClassFromToken(\n      ChannelTokenUsage usage, kj::ArrayPtr<const byte> token) override {\n    return channelTokenHandler.decodeActorClassChannelToken(usage, token);\n  }\n\n  // ---------------------------------------------------------------------------\n  // implements TimerChannel\n\n  void syncTime() override {\n    // Nothing to do\n  }\n\n  kj::Date now(kj::Maybe<kj::Date>) override {\n    return kj::systemPreciseCalendarClock().now();\n  }\n\n  kj::Promise<void> atTime(kj::Date when) override {\n    auto delay = when - now(kj::none);\n    // We can't use `afterDelay(delay)` here because kj::Timer::afterDelay() is equivalent to\n    // `atTime(timer.now() + delay)`, and kj::Timer::now() only advances when the event loop\n    // polls for I/O. If JavaScript executed for a significant amount of time since the last\n    // poll (e.g. compiling/running a script before the first setTimeout), timer.now() will be\n    // stale and the delay will effectively be shortened by that staleness, causing the timer\n    // to fire too early. Instead, we compute the target time using a fresh reading from the\n    // monotonic clock so the delay is measured from the actual present.\n    return threadContext.getUnsafeTimer().atTime(monotonicClock.now() + delay);\n  }\n\n  kj::Promise<void> afterLimitTimeout(kj::Duration t) override {\n    return threadContext.getUnsafeTimer().afterDelay(t);\n  }\n\n  // ---------------------------------------------------------------------------\n  // implements LimitEnforcer\n  //\n  // No limits are enforced.\n\n  kj::Own<void> enterJs(jsg::Lock& lock, IoContext& context) override {\n    return {};\n  }\n  void topUpActor() override {}\n  void newSubrequest(bool isInHouse) override {}\n  void newKvRequest(KvOpType op) override {}\n  void newAnalyticsEngineRequest() override {}\n  kj::Promise<void> limitDrain() override {\n    return kj::NEVER_DONE;\n  }\n  kj::Promise<void> limitScheduled() override {\n    return kj::NEVER_DONE;\n  }\n  kj::Duration getAlarmLimit() override {\n    return 15 * kj::MINUTES;\n  }\n  size_t getBufferingLimit() override {\n    return kj::maxValue;\n  }\n  kj::Maybe<EventOutcome> getLimitsExceeded() override {\n    return kj::none;\n  }\n  kj::Promise<void> onLimitsExceeded() override {\n    return kj::NEVER_DONE;\n  }\n  void setCpuLimitNearlyExceededCallback(kj::Function<void(void)> cb) override {}\n  void requireLimitsNotExceeded() override {}\n  void reportMetrics(RequestObserver& requestMetrics) override {}\n  kj::Duration consumeTimeElapsedForPeriodicLogging() override {\n    return 0 * kj::SECONDS;\n  }\n};\n\nstruct FutureSubrequestChannel {\n  kj::OneOf<config::ServiceDesignator::Reader, kj::Own<IoChannelFactory::SubrequestChannel>>\n      designator;\n  kj::String errorContext;\n\n  kj::Own<IoChannelFactory::SubrequestChannel> lookup(Server& server) && {\n    KJ_SWITCH_ONEOF(designator) {\n      KJ_CASE_ONEOF(conf, config::ServiceDesignator::Reader) {\n        return server.lookupService(conf, kj::mv(errorContext));\n      }\n      KJ_CASE_ONEOF(channel, kj::Own<IoChannelFactory::SubrequestChannel>) {\n        return kj::mv(channel);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n};\n\nstruct FutureActorChannel {\n  config::Worker::Binding::DurableObjectNamespaceDesignator::Reader designator;\n  kj::String errorContext;\n};\n\nstruct FutureActorClassChannel {\n  kj::OneOf<config::ServiceDesignator::Reader, kj::Own<Server::ActorClass>> designator;\n  kj::String errorContext;\n\n  kj::Own<Server::ActorClass> lookup(Server& server) && {\n    KJ_SWITCH_ONEOF(designator) {\n      KJ_CASE_ONEOF(conf, config::ServiceDesignator::Reader) {\n        return server.lookupActorClass(conf, kj::mv(errorContext));\n      }\n      KJ_CASE_ONEOF(channel, kj::Own<Server::ActorClass>) {\n        return kj::mv(channel);\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n};\n\nstruct FutureWorkerLoaderChannel {\n  kj::String name;  // for error logging, not necessarily unique\n  kj::Maybe<kj::String> id;\n};\n\nstatic kj::Maybe<WorkerdApi::Global> createBinding(kj::StringPtr workerName,\n    config::Worker::Reader conf,\n    config::Worker::Binding::Reader binding,\n    Worker::ValidationErrorReporter& errorReporter,\n    kj::Vector<FutureSubrequestChannel>& subrequestChannels,\n    kj::Vector<FutureActorChannel>& actorChannels,\n    kj::Vector<FutureActorClassChannel>& actorClassChannels,\n    kj::Vector<FutureWorkerLoaderChannel>& workerLoaderChannels,\n    bool& hasWorkerdDebugPortBinding,\n    kj::HashMap<kj::String, kj::HashMap<kj::String, Server::ActorConfig>>& actorConfigs,\n    bool experimental) {\n  // creates binding object or returns null and reports an error\n  using Global = WorkerdApi::Global;\n  kj::StringPtr bindingName = binding.getName();\n  TRACE_EVENT(\"workerd\", \"Server::WorkerService::createBinding()\", \"name\", workerName.cStr(),\n      \"binding\", bindingName.cStr());\n  auto makeGlobal = [&](auto&& value) {\n    return Global{.name = kj::str(bindingName), .value = kj::mv(value)};\n  };\n\n  auto errorContext = kj::str(\"Worker \\\"\", workerName, \"\\\"'s binding \\\"\", bindingName, \"\\\"\");\n\n  switch (binding.which()) {\n    case config::Worker::Binding::UNSPECIFIED:\n      errorReporter.addError(kj::str(errorContext, \" does not specify any binding value.\"));\n      return kj::none;\n\n    case config::Worker::Binding::PARAMETER:\n      KJ_UNIMPLEMENTED(\"TODO(beta): parameters\");\n\n    case config::Worker::Binding::TEXT:\n      return makeGlobal(kj::str(binding.getText()));\n    case config::Worker::Binding::DATA:\n      return makeGlobal(kj::heapArray<byte>(binding.getData()));\n    case config::Worker::Binding::JSON:\n      return makeGlobal(Global::Json{kj::str(binding.getJson())});\n\n    case config::Worker::Binding::WASM_MODULE:\n      if (conf.isServiceWorkerScript()) {\n        // Already handled earlier.\n      } else {\n        errorReporter.addError(kj::str(errorContext,\n            \" is a Wasm binding, but Wasm bindings are not allowed in \"\n            \"modules-based scripts. Use Wasm modules instead.\"));\n      }\n      return kj::none;\n\n    case config::Worker::Binding::CRYPTO_KEY: {\n      auto keyConf = binding.getCryptoKey();\n      Global::CryptoKey keyGlobal;\n\n      switch (keyConf.which()) {\n        case config::Worker::Binding::CryptoKey::RAW:\n          keyGlobal.format = kj::str(\"raw\");\n          keyGlobal.keyData = kj::heapArray<kj::byte>(keyConf.getRaw());\n          goto validFormat;\n        case config::Worker::Binding::CryptoKey::HEX: {\n          keyGlobal.format = kj::str(\"raw\");\n          auto decoded = kj::decodeHex(keyConf.getHex());\n          if (decoded.hadErrors) {\n            errorReporter.addError(\n                kj::str(\"CryptoKey binding \\\"\", binding.getName(), \"\\\" contained invalid hex.\"));\n          }\n          keyGlobal.keyData = kj::Array<byte>(kj::mv(decoded));\n          goto validFormat;\n        }\n        case config::Worker::Binding::CryptoKey::BASE64: {\n          keyGlobal.format = kj::str(\"raw\");\n          auto decoded = kj::decodeBase64(keyConf.getBase64());\n          if (decoded.hadErrors) {\n            errorReporter.addError(\n                kj::str(\"CryptoKey binding \\\"\", binding.getName(), \"\\\" contained invalid base64.\"));\n          }\n          keyGlobal.keyData = kj::Array<byte>(kj::mv(decoded));\n          goto validFormat;\n        }\n        case config::Worker::Binding::CryptoKey::PKCS8: {\n          keyGlobal.format = kj::str(\"pkcs8\");\n          auto pem = KJ_UNWRAP_OR(decodePem(keyConf.getPkcs8()), {\n            errorReporter.addError(kj::str(\n                \"CryptoKey binding \\\"\", binding.getName(), \"\\\" contained invalid PEM format.\"));\n            return kj::none;\n          });\n          if (pem.type != \"PRIVATE KEY\") {\n            errorReporter.addError(kj::str(\"CryptoKey binding \\\"\", binding.getName(),\n                \"\\\" contained wrong PEM type, \"\n                \"expected \\\"PRIVATE KEY\\\" but got \\\"\",\n                pem.type, \"\\\".\"));\n            return kj::none;\n          }\n          keyGlobal.keyData = kj::mv(pem.data);\n          goto validFormat;\n        }\n        case config::Worker::Binding::CryptoKey::SPKI: {\n          keyGlobal.format = kj::str(\"spki\");\n          auto pem = KJ_UNWRAP_OR(decodePem(keyConf.getSpki()), {\n            errorReporter.addError(kj::str(\n                \"CryptoKey binding \\\"\", binding.getName(), \"\\\" contained invalid PEM format.\"));\n            return kj::none;\n          });\n          if (pem.type != \"PUBLIC KEY\") {\n            errorReporter.addError(kj::str(\"CryptoKey binding \\\"\", binding.getName(),\n                \"\\\" contained wrong PEM type, \"\n                \"expected \\\"PUBLIC KEY\\\" but got \\\"\",\n                pem.type, \"\\\".\"));\n            return kj::none;\n          }\n          keyGlobal.keyData = kj::mv(pem.data);\n          goto validFormat;\n        }\n        case config::Worker::Binding::CryptoKey::JWK:\n          keyGlobal.format = kj::str(\"jwk\");\n          keyGlobal.keyData = Global::Json{kj::str(keyConf.getJwk())};\n          goto validFormat;\n      }\n      errorReporter.addError(kj::str(\"Encountered unknown CryptoKey type for binding \\\"\",\n          binding.getName(), \"\\\". Was the config compiled with a newer version of the schema?\"));\n      return kj::none;\n    validFormat:\n\n      auto algorithmConf = keyConf.getAlgorithm();\n      switch (algorithmConf.which()) {\n        case config::Worker::Binding::CryptoKey::Algorithm::NAME:\n          keyGlobal.algorithm = Global::Json{escapeJsonString(algorithmConf.getName())};\n          goto validAlgorithm;\n        case config::Worker::Binding::CryptoKey::Algorithm::JSON:\n          keyGlobal.algorithm = Global::Json{kj::str(algorithmConf.getJson())};\n          goto validAlgorithm;\n      }\n      errorReporter.addError(kj::str(\"Encountered unknown CryptoKey algorithm type for binding \\\"\",\n          binding.getName(), \"\\\". Was the config compiled with a newer version of the schema?\"));\n      return kj::none;\n    validAlgorithm:\n\n      keyGlobal.extractable = keyConf.getExtractable();\n      keyGlobal.usages = KJ_MAP(usage, keyConf.getUsages()) { return kj::str(usage); };\n\n      return makeGlobal(kj::mv(keyGlobal));\n      return kj::none;\n    }\n\n    case config::Worker::Binding::SERVICE: {\n      uint channel = static_cast<uint>(subrequestChannels.size()) +\n          IoContext::SPECIAL_SUBREQUEST_CHANNEL_COUNT;\n      subrequestChannels.add(FutureSubrequestChannel{binding.getService(), kj::mv(errorContext)});\n      return makeGlobal(\n          Global::Fetcher{.channel = channel, .requiresHost = true, .isInHouse = false});\n    }\n\n    case config::Worker::Binding::DURABLE_OBJECT_NAMESPACE: {\n      auto actorBinding = binding.getDurableObjectNamespace();\n      const Server::ActorConfig* actorConfig;\n      if (actorBinding.hasServiceName()) {\n        auto& svcMap = KJ_UNWRAP_OR(actorConfigs.find(actorBinding.getServiceName()), {\n          errorReporter.addError(kj::str(errorContext, \" refers to a service \\\"\",\n              actorBinding.getServiceName(), \"\\\", but no such service is defined.\"));\n          return kj::none;\n        });\n\n        actorConfig = &KJ_UNWRAP_OR(svcMap.find(actorBinding.getClassName()), {\n          errorReporter.addError(\n              kj::str(errorContext, \" refers to a Durable Object namespace named \\\"\",\n                  actorBinding.getClassName(), \"\\\" in service \\\"\", actorBinding.getServiceName(),\n                  \"\\\", but no such Durable Object namespace is defined by that service.\"));\n          return kj::none;\n        });\n      } else {\n        auto& localActorConfigs = KJ_ASSERT_NONNULL(actorConfigs.find(workerName));\n        actorConfig = &KJ_UNWRAP_OR(localActorConfigs.find(actorBinding.getClassName()), {\n          errorReporter.addError(kj::str(errorContext,\n              \" refers to a Durable Object namespace named \\\"\", actorBinding.getClassName(),\n              \"\\\", but no such Durable Object namespace is defined \"\n              \"by this Worker.\"));\n          return kj::none;\n        });\n      }\n\n      uint channel = static_cast<uint>(actorChannels.size());\n      actorChannels.add(FutureActorChannel{actorBinding, kj::mv(errorContext)});\n\n      KJ_SWITCH_ONEOF(*actorConfig) {\n        KJ_CASE_ONEOF(durable, Server::Durable) {\n          return makeGlobal(Global::DurableActorNamespace{\n            .actorChannel = channel, .uniqueKey = durable.uniqueKey});\n        }\n        KJ_CASE_ONEOF(_, Server::Ephemeral) {\n          return makeGlobal(Global::EphemeralActorNamespace{.actorChannel = channel});\n        }\n      }\n\n      return kj::none;\n    }\n\n    case config::Worker::Binding::KV_NAMESPACE: {\n      uint channel = static_cast<uint>(subrequestChannels.size()) +\n          IoContext::SPECIAL_SUBREQUEST_CHANNEL_COUNT;\n      subrequestChannels.add(\n          FutureSubrequestChannel{binding.getKvNamespace(), kj::mv(errorContext)});\n\n      return makeGlobal(Global::KvNamespace{\n        .subrequestChannel = channel, .bindingName = kj::str(binding.getName())});\n    }\n\n    case config::Worker::Binding::R2_BUCKET: {\n      uint channel = static_cast<uint>(subrequestChannels.size()) +\n          IoContext::SPECIAL_SUBREQUEST_CHANNEL_COUNT;\n      subrequestChannels.add(FutureSubrequestChannel{binding.getR2Bucket(), kj::mv(errorContext)});\n      return makeGlobal(Global::R2Bucket{.subrequestChannel = channel,\n        .bucket = kj::str(binding.getR2Bucket().getName()),\n        .bindingName = kj::str(binding.getName())});\n    }\n\n    case config::Worker::Binding::R2_ADMIN: {\n      uint channel = static_cast<uint>(subrequestChannels.size()) +\n          IoContext::SPECIAL_SUBREQUEST_CHANNEL_COUNT;\n      subrequestChannels.add(FutureSubrequestChannel{binding.getR2Admin(), kj::mv(errorContext)});\n      return makeGlobal(Global::R2Admin{.subrequestChannel = channel});\n    }\n\n    case config::Worker::Binding::QUEUE: {\n      uint channel = static_cast<uint>(subrequestChannels.size()) +\n          IoContext::SPECIAL_SUBREQUEST_CHANNEL_COUNT;\n      subrequestChannels.add(FutureSubrequestChannel{binding.getQueue(), kj::mv(errorContext)});\n\n      return makeGlobal(Global::QueueBinding{.subrequestChannel = channel});\n    }\n\n    case config::Worker::Binding::WRAPPED: {\n      auto wrapped = binding.getWrapped();\n      kj::Vector<Global> innerGlobals;\n      for (const auto& innerBinding: wrapped.getInnerBindings()) {\n        KJ_IF_SOME(global,\n            createBinding(workerName, conf, innerBinding, errorReporter, subrequestChannels,\n                actorChannels, actorClassChannels, workerLoaderChannels, hasWorkerdDebugPortBinding,\n                actorConfigs, experimental)) {\n          innerGlobals.add(kj::mv(global));\n        } else {\n          // we've already communicated the error\n          return kj::none;\n        }\n      }\n      return makeGlobal(Global::Wrapped{\n        .moduleName = kj::str(wrapped.getModuleName()),\n        .entrypoint = kj::str(wrapped.getEntrypoint()),\n        .innerBindings = innerGlobals.releaseAsArray(),\n      });\n    }\n\n    case config::Worker::Binding::FROM_ENVIRONMENT: {\n      const char* value = getenv(binding.getFromEnvironment().cStr());\n      if (value == nullptr) {\n        // TODO(cleanup): Maybe make a Global::Null? (Can't use nullptr_t in OneOf.) For now,\n        // using JSON gets the job done hackily.\n        return makeGlobal(Global::Json{kj::str(\"null\")});\n      } else {\n        return makeGlobal(kj::str(value));\n      }\n    }\n\n    case config::Worker::Binding::ANALYTICS_ENGINE: {\n      if (!experimental) {\n        errorReporter.addError(kj::str(\n            \"AnalyticsEngine bindings are an experimental feature which may change or go away in the future.\"\n            \"You must run workerd with `--experimental` to use this feature.\"));\n      }\n\n      uint channel = static_cast<uint>(subrequestChannels.size()) +\n          IoContext::SPECIAL_SUBREQUEST_CHANNEL_COUNT;\n      subrequestChannels.add(\n          FutureSubrequestChannel{binding.getAnalyticsEngine(), kj::mv(errorContext)});\n\n      return makeGlobal(Global::AnalyticsEngine{\n        .subrequestChannel = channel,\n        .dataset = kj::str(binding.getAnalyticsEngine().getName()),\n        .version = 0,\n      });\n    }\n    case config::Worker::Binding::HYPERDRIVE: {\n      uint channel = static_cast<uint>(subrequestChannels.size()) +\n          IoContext::SPECIAL_SUBREQUEST_CHANNEL_COUNT;\n      subrequestChannels.add(\n          FutureSubrequestChannel{binding.getHyperdrive().getDesignator(), kj::mv(errorContext)});\n      return makeGlobal(Global::Hyperdrive{\n        .subrequestChannel = channel,\n        .database = kj::str(binding.getHyperdrive().getDatabase()),\n        .user = kj::str(binding.getHyperdrive().getUser()),\n        .password = kj::str(binding.getHyperdrive().getPassword()),\n        .scheme = kj::str(binding.getHyperdrive().getScheme()),\n      });\n    }\n    case config::Worker::Binding::UNSAFE_EVAL: {\n      if (!experimental) {\n        errorReporter.addError(kj::str(\"Unsafe eval is an experimental feature. \",\n            \"You must run workerd with `--experimental` to use this feature.\"));\n        return kj::none;\n      }\n      return makeGlobal(Global::UnsafeEval{});\n    }\n    case config::Worker::Binding::MEMORY_CACHE: {\n      if (!experimental) {\n        errorReporter.addError(kj::str(\n            \"MemoryCache bindings are an experimental feature which may change or go away \"\n            \"in the future. You must run workerd with `--experimental` to use this feature.\"));\n        return kj::none;\n      }\n      auto cache = binding.getMemoryCache();\n      // TODO(cleanup): Should we have some reasonable default for these so they can\n      // be optional?\n      if (!cache.hasLimits()) {\n        errorReporter.addError(\n            kj::str(\"MemoryCache bindings must specify limits. Please \"\n                    \"update the binding in the worker configuration and try again.\"));\n        return kj::none;\n      }\n      Global::MemoryCache cacheCopy;\n      // The id is optional. If provided, then multiple bindings with the same id will\n      // share the same cache. Otherwise, a unique id is generated for the cache.\n      if (cache.hasId()) {\n        cacheCopy.cacheId = kj::str(cache.getId());\n      }\n      auto limits = cache.getLimits();\n      cacheCopy.maxKeys = limits.getMaxKeys();\n      cacheCopy.maxValueSize = limits.getMaxValueSize();\n      cacheCopy.maxTotalValueSize = limits.getMaxTotalValueSize();\n      return makeGlobal(kj::mv(cacheCopy));\n    }\n\n    case config::Worker::Binding::DURABLE_OBJECT_CLASS: {\n      if (!experimental) {\n        errorReporter.addError(kj::str(\n            \"Durable Object class bindings are an experimental feature which may change or go away \"\n            \"in the future. You must run workerd with `--experimental` to use this feature.\"));\n        return kj::none;\n      }\n      uint channel = actorClassChannels.size();\n      actorClassChannels.add(\n          FutureActorClassChannel{binding.getDurableObjectClass(), kj::mv(errorContext)});\n      return makeGlobal(Global::ActorClass{.channel = channel});\n    }\n\n    case config::Worker::Binding::WORKER_LOADER: {\n      if (!experimental) {\n        errorReporter.addError(kj::str(\n            \"Worker loader bindings are an experimental feature which may change or go away \"\n            \"in the future. You must run workerd with `--experimental` to use this feature.\"));\n        return kj::none;\n      }\n\n      auto loaderConf = binding.getWorkerLoader();\n\n      FutureWorkerLoaderChannel channel;\n      if (loaderConf.hasId()) {\n        channel.name = kj::str(loaderConf.getId());\n        channel.id = kj::str(channel.name);\n      } else {\n        channel.name = kj::str(bindingName);\n      }\n\n      uint channelNumber = workerLoaderChannels.size();\n      workerLoaderChannels.add(kj::mv(channel));\n      return makeGlobal(Global::WorkerLoader{.channel = channelNumber});\n    }\n\n    case config::Worker::Binding::WORKERD_DEBUG_PORT: {\n      if (!experimental) {\n        errorReporter.addError(kj::str(\n            \"workerdDebugPort bindings are an experimental feature which may change or go away \"\n            \"in the future. You must run workerd with `--experimental` to use this feature.\"));\n        return kj::none;\n      }\n\n      hasWorkerdDebugPortBinding = true;\n      return makeGlobal(Global::WorkerdDebugPort{});\n    }\n  }\n  errorReporter.addError(kj::str(errorContext,\n      \"has unrecognized type. Was the config compiled with a newer version of \"\n      \"the schema?\"));\n}\n\nuint startInspector(\n    kj::StringPtr inspectorAddress, Server::InspectorServiceIsolateRegistrar& registrar);\n\nvoid Server::abortAllActors(kj::Maybe<const kj::Exception&> reason) {\n  for (auto& service: services) {\n    if (WorkerService* worker = dynamic_cast<WorkerService*>(&*service.value)) {\n      for (auto& [className, ns]: worker->getActorNamespaces()) {\n        bool isEvictable = true;\n        KJ_SWITCH_ONEOF(ns->getConfig()) {\n          KJ_CASE_ONEOF(c, Durable) {\n            isEvictable = c.isEvictable;\n          }\n          KJ_CASE_ONEOF(c, Ephemeral) {\n            isEvictable = c.isEvictable;\n          }\n        }\n        if (isEvictable) ns->abortAll(reason);\n      }\n    }\n  }\n}\n\n// WorkerDef is an intermediate representation of everything from `config::Worker::Reader` that\n// `Server::makeWorkerImpl()` needs. Similar to `WorkerSource`, we factor out this intermediate\n// representation so that we can potentially build it dynamically from input that isn't a\n// workerd config file.\nstruct Server::WorkerDef {\n  CompatibilityFlags::Reader featureFlags;\n  WorkerSource source;\n  kj::Maybe<kj::StringPtr> moduleFallback;\n  const kj::HashMap<kj::String, ActorConfig>& localActorConfigs;\n  bool isDynamic;\n\n  FutureSubrequestChannel globalOutbound;\n  kj::Maybe<FutureSubrequestChannel> cacheApiOutbound;\n  kj::Vector<FutureSubrequestChannel> subrequestChannels;\n  kj::Vector<FutureActorChannel> actorChannels;\n  kj::Vector<FutureActorClassChannel> actorClassChannels;\n  kj::Vector<FutureWorkerLoaderChannel> workerLoaderChannels;\n  bool hasWorkerdDebugPortBinding = false;\n  kj::Array<FutureSubrequestChannel> tails;\n  kj::Array<FutureSubrequestChannel> streamingTails;\n\n  // Dynamically-loaded isolates can't directly have storage, so for now I'm using a raw capnp\n  // Reader here. A default-constructed Reader will have type `none` which is appropriate for\n  // dynamically-loaded workers. Same story for ContainerEngine.\n  config::Worker::DurableObjectStorage::Reader actorStorageConf;\n  config::Worker::ContainerEngine::Reader containerEngineConf;\n\n  // Similar to the `compileBindings` callback passed into `Worker`'s constructor, except that\n  // `ctx.exports` is taken care of separately. This is provided as a callback since `env` is\n  // constructed in a vastly different way for dynamically-loaded workers.\n  kj::Function<void(jsg::Lock& lock, const Worker::Api& api, v8::Local<v8::Object> target)>\n      compileBindings;\n\n  // If the WorkerDef was created from a DymamicWorkerSource and that\n  // source contains a clone of the source bundle, this will take ownership.\n  kj::Maybe<kj::Own<void>> maybeOwnedSourceCode;\n};\n\nclass Server::WorkerLoaderNamespace: public kj::Refcounted {\n public:\n  WorkerLoaderNamespace(Server& server, kj::String namespaceName)\n      : server(server),\n        namespaceName(kj::mv(namespaceName)) {}\n\n  void unlink() {\n    for (auto& isolate: isolates) {\n      isolate.value->unlink();\n    }\n  }\n\n  kj::Own<WorkerStubChannel> loadIsolate(\n      kj::Maybe<kj::String> name, kj::Function<kj::Promise<DynamicWorkerSource>()> fetchSource) {\n    KJ_IF_SOME(n, name) {\n      return isolates\n          .findOrCreate(n,\n              [&]() -> decltype(isolates)::Entry {\n        // This name isn't actually used in any maps nor is it ever revealed back to the app, but it\n        // may be used in error logs.\n        auto isolateName = kj::str(namespaceName, ':', n);\n\n        return {.key = kj::mv(n),\n          .value = kj::rc<WorkerStubImpl>(server, kj::mv(isolateName), kj::mv(fetchSource))};\n      })\n          .addRef()\n          .toOwn();\n    } else {\n      auto isolateName = kj::str(namespaceName, \":dynamic:\", randomUUID(server.entropySource));\n      return kj::rc<WorkerStubImpl>(server, kj::mv(isolateName), kj::mv(fetchSource)).toOwn();\n    }\n  }\n\n private:\n  Server& server;\n  kj::String namespaceName;\n\n  class WorkerStubImpl;\n  kj::HashMap<kj::String, kj::Rc<WorkerStubImpl>> isolates;\n\n  class NullGlobalOutboundChannel: public IoChannelFactory::SubrequestChannel {\n   public:\n    kj::Own<WorkerInterface> startRequest(IoChannelFactory::SubrequestMetadata metadata) override {\n      JSG_FAIL_REQUIRE(Error,\n          \"This worker is not permitted to access the internet via global functions like fetch(). \"\n          \"It must use capabilities (such as bindings in 'env') to talk to the outside world.\");\n    }\n\n    void requireAllowsTransfer() override {\n      // It's difficult to get here, because the null outbound is not normally something you can\n      // reference. That said, it is possible to get a `Fetcher` representing the `next` outbound\n      // by pulling it off an incoming `Request` object, and in practice that points to the same\n      // thing as the null outbound. You could then try to transfer it.\n      //\n      // We disallow this for now because it's not clear why it would be needed. That said, if it\n      // is needed for some reason, it wouldn't be hard to support. But we might want to change\n      // the error message it throws from startRequest(), since the error would be somewhat\n      // misleading after the channel has been transferred.\n      JSG_FAIL_REQUIRE(DOMDataCloneError, \"The null global outbound is not transferrable.\");\n    }\n  };\n\n  class WorkerStubImpl final: public WorkerStubChannel, public kj::Refcounted {\n   public:\n    WorkerStubImpl(Server& server,\n        kj::String isolateName,\n        kj::Function<kj::Promise<DynamicWorkerSource>()> fetchSource)\n        : startupTask(start(server, kj::mv(isolateName), kj::mv(fetchSource)).fork()) {}\n\n    ~WorkerStubImpl() {\n      unlink();\n    }\n\n    void unlink() {\n      KJ_IF_SOME(s, service) {\n        s->unlink();\n      }\n    }\n\n    kj::Own<IoChannelFactory::SubrequestChannel> getEntrypoint(\n        kj::Maybe<kj::String> name, Frankenvalue props) override {\n      return kj::refcounted<SubrequestChannelImpl>(addRefToThis(), kj::mv(name), kj::mv(props));\n    }\n\n    kj::Own<IoChannelFactory::ActorClassChannel> getActorClass(\n        kj::Maybe<kj::String> name, Frankenvalue props) override {\n      return kj::refcounted<ActorClassImpl>(addRefToThis(), kj::mv(name), kj::mv(props));\n    }\n\n   private:\n    kj::Maybe<kj::Own<WorkerService>> service;  // null if still starting up\n    kj::ForkedPromise<void> startupTask;        // resolves when `service` is non-null\n\n    kj::Promise<void> start(Server& server,\n        kj::String isolateName,\n        kj::Function<kj::Promise<DynamicWorkerSource>()> fetchSource) {\n      auto source = co_await fetchSource();\n      static const kj::HashMap<kj::String, ActorConfig> EMPTY_ACTOR_CONFIGS;\n\n      // Rewrite the capabilities in `env` in order to build the I/O channel table.\n      kj::Vector<FutureSubrequestChannel> subrequestChannels;\n      kj::Vector<FutureActorClassChannel> actorClassChannels;\n      source.env.rewriteCaps([&](kj::Own<Frankenvalue::CapTableEntry> entry) {\n        if (auto channel = dynamic_cast<IoChannelFactory::SubrequestChannel*>(entry.get())) {\n          uint channelNumber =\n              subrequestChannels.size() + IoContext::SPECIAL_SUBREQUEST_CHANNEL_COUNT;\n          subrequestChannels.add(FutureSubrequestChannel{\n            .designator = kj::addRef(*channel),\n            .errorContext = kj::str(\"Worker's env\"),\n          });\n          return kj::heap<IoChannelCapTableEntry>(\n              IoChannelCapTableEntry::SUBREQUEST, channelNumber);\n        } else if (auto channel = dynamic_cast<ActorClass*>(entry.get())) {\n          uint channelNumber = subrequestChannels.size();\n          actorClassChannels.add(FutureActorClassChannel{\n            .designator = kj::addRef(*channel),\n            .errorContext = kj::str(\"Worker's env\"),\n          });\n          return kj::heap<IoChannelCapTableEntry>(\n              IoChannelCapTableEntry::ACTOR_CLASS, channelNumber);\n        } else {\n          // Generally, it shouldn't be possible to get here, but just in case, let's at least\n          // provide some sort of error, although it's a vague one.\n          JSG_FAIL_REQUIRE(DOMDataCloneError,\n              \"Dynamic 'env' contains one or more objects that are not supported for use in \"\n              \"'env', although they would be supported in 'props'.\");\n        }\n      });\n\n      WorkerDef def{\n        .featureFlags = source.compatibilityFlags,\n        .source = kj::mv(source.source),\n        .moduleFallback = kj::none,\n        .localActorConfigs = EMPTY_ACTOR_CONFIGS,\n        .isDynamic = true,\n\n        // clang-format off\n        .globalOutbound{\n          .designator = kj::mv(source.globalOutbound)\n              .orDefault([]() { return kj::refcounted<NullGlobalOutboundChannel>(); }),\n          .errorContext = kj::str(\"Worker's globalOutbound\"),\n        },\n\n        .subrequestChannels = kj::mv(subrequestChannels),\n        .actorClassChannels = kj::mv(actorClassChannels),\n\n        .tails = KJ_MAP(tail, source.tails) -> FutureSubrequestChannel {\n          return {\n            .designator = kj::mv(tail),\n            .errorContext = kj::str(\"Worker's tail\"),\n          };\n        },\n        .streamingTails = KJ_MAP(tail, source.streamingTails) -> FutureSubrequestChannel {\n          return {\n            .designator = kj::mv(tail),\n            .errorContext = kj::str(\"Worker's streaming tail\"),\n          };\n        },\n\n        .compileBindings = [env = kj::mv(source.env)](\n            jsg::Lock& js, const Worker::Api& api, v8::Local<v8::Object> target) mutable {\n          env.populateJsObject(js, jsg::JsObject(target));\n        },\n\n        // Note here that we always keep the ownContent from the source, even if\n        // ownContentIsRpcResponse is true. This is safe in workerd because we\n        // are single-threaded here and we don't need to worry about the cross-thread\n        // ownership issues. For the downstream use, however, we need to be careful\n        // to not copy the ownContent if it is an RPC response.\n        .maybeOwnedSourceCode = kj::mv(source.ownContent),\n        // clang-format on\n      };\n\n      DynamicErrorReporter errorReporter;\n\n      auto service = co_await server.makeWorkerImpl(isolateName, kj::mv(def), {}, errorReporter);\n      errorReporter.throwIfErrors();\n\n      service->link(errorReporter);\n      errorReporter.throwIfErrors();\n\n      this->service = kj::mv(service);\n    }\n\n    class SubrequestChannelImpl final: public IoChannelFactory::SubrequestChannel {\n     public:\n      SubrequestChannelImpl(\n          kj::Rc<WorkerStubImpl> isolate, kj::Maybe<kj::String> entrypointName, Frankenvalue props)\n          : isolate(kj::mv(isolate)),\n            entrypointName(kj::mv(entrypointName)),\n            props(kj::mv(props)) {}\n\n      kj::Own<WorkerInterface> startRequest(\n          IoChannelFactory::SubrequestMetadata metadata) override {\n        if (isolate->service == kj::none) {\n          return newPromisedWorkerInterface(\n              isolate->startupTask.addBranch().then([this, metadata = kj::mv(metadata)]() mutable {\n            return startRequestImpl(kj::mv(metadata));\n          }));\n        } else {\n          return startRequestImpl(kj::mv(metadata));\n        }\n      }\n\n      void requireAllowsTransfer() override {\n        throwDynamicEntrypointTransferError();\n      }\n\n     private:\n      kj::Rc<WorkerStubImpl> isolate;\n      kj::Maybe<kj::String> entrypointName;\n      Frankenvalue props;  // moved away when `entrypointService` is initialized\n\n      kj::Maybe<kj::Own<Service>> entrypointService;\n\n      kj::Own<WorkerInterface> startRequestImpl(IoChannelFactory::SubrequestMetadata metadata) {\n        auto& service = KJ_ASSERT_NONNULL(isolate->service);\n        if (entrypointService == kj::none) {\n          entrypointService = service->getEntrypoint(entrypointName, kj::mv(props));\n        }\n        KJ_IF_SOME(ep, entrypointService) {\n          return ep->startRequest(kj::mv(metadata));\n        } else {\n          KJ_IF_SOME(en, entrypointName) {\n            JSG_FAIL_REQUIRE(Error, \"Worker has no such entrypoint: \", en);\n          } else {\n            JSG_FAIL_REQUIRE(Error, \"Worker has no default entrypoint.\");\n          }\n        }\n      }\n    };\n\n    class ActorClassImpl final: public ActorClass {\n     public:\n      ActorClassImpl(\n          kj::Rc<WorkerStubImpl> isolate, kj::Maybe<kj::String> entrypointName, Frankenvalue props)\n          : isolate(kj::mv(isolate)),\n            entrypointName(kj::mv(entrypointName)),\n            props(kj::mv(props)) {}\n\n      void requireAllowsTransfer() override {\n        throwDynamicEntrypointTransferError();\n      }\n\n      kj::Maybe<kj::Promise<void>> whenReady() override {\n        if (inner != kj::none) return kj::none;\n\n        KJ_IF_SOME(service, isolate->service) {\n          inner = service->getActorClass(entrypointName, kj::mv(props));\n          return kj::none;\n        }\n\n        // Have to wait for the isolate to start up.\n        return isolate->startupTask.addBranch().then([this]() {\n          if (inner == kj::none) {\n            inner =\n                KJ_ASSERT_NONNULL(isolate->service)->getActorClass(entrypointName, kj::mv(props));\n          }\n        });\n      }\n\n      kj::Own<Worker::Actor> newActor(kj::Maybe<RequestTracker&> tracker,\n          Worker::Actor::Id actorId,\n          Worker::Actor::MakeActorCacheFunc makeActorCache,\n          Worker::Actor::MakeStorageFunc makeStorage,\n          kj::Own<Worker::Actor::Loopback> loopback,\n          kj::Maybe<kj::Own<Worker::Actor::HibernationManager>> manager,\n          kj::Maybe<rpc::Container::Client> container,\n          kj::Maybe<Worker::Actor::FacetManager&> facetManager) override {\n        return getInner().newActor(tracker, kj::mv(actorId), kj::mv(makeActorCache),\n            kj::mv(makeStorage), kj::mv(loopback), kj::mv(manager), kj::mv(container),\n            facetManager);\n      }\n\n      kj::Own<WorkerInterface> startRequest(\n          IoChannelFactory::SubrequestMetadata metadata, kj::Own<Worker::Actor> actor) override {\n        return getInner().startRequest(kj::mv(metadata), kj::mv(actor));\n      }\n\n     private:\n      kj::Rc<WorkerStubImpl> isolate;\n      kj::Maybe<kj::String> entrypointName;\n      Frankenvalue props;  // moved away when `inner` is initialized\n\n      kj::Maybe<kj::Own<ActorClass>> inner;\n\n      ActorClass& getInner() {\n        return *KJ_ASSERT_NONNULL(\n            inner, \"ActorClassChannel is not ready yet; should have awaited whenReady()\");\n      }\n    };\n  };\n};\n\nvoid Server::unlinkWorkerLoaders() {\n  for (auto& loader: workerLoaderNamespaces) {\n    loader.value->unlink();\n  }\n  for (auto& loader: anonymousWorkerLoaderNamespaces) {\n    loader->unlink();\n  }\n}\n\nkj::Own<WorkerStubChannel> Server::WorkerService::loadIsolate(uint loaderChannel,\n    kj::Maybe<kj::String> name,\n    kj::Function<kj::Promise<DynamicWorkerSource>()> fetchSource) {\n  auto& channels =\n      KJ_REQUIRE_NONNULL(ioChannels.tryGet<LinkedIoChannels>(), \"link() has not been called\");\n  KJ_REQUIRE(loaderChannel < channels.workerLoaders.size(), \"invalid worker loader channel number\");\n\n  return channels.workerLoaders[loaderChannel]->loadIsolate(kj::mv(name), kj::mv(fetchSource));\n}\n\nkj::Promise<kj::Own<Server::Service>> Server::makeWorker(kj::StringPtr name,\n    config::Worker::Reader conf,\n    capnp::List<config::Extension>::Reader extensions) {\n  TRACE_EVENT(\"workerd\", \"Server::makeWorker()\", \"name\", name.cStr());\n  auto& localActorConfigs = KJ_ASSERT_NONNULL(actorConfigs.find(name));\n\n  ConfigErrorReporter errorReporter(*this, name);\n\n  capnp::MallocMessageBuilder arena;\n  // TODO(beta): Factor out FeatureFlags from WorkerBundle.\n  auto featureFlags = arena.initRoot<CompatibilityFlags>();\n\n  KJ_IF_SOME(overrideDate, testCompatibilityDateOverride) {\n    // When testCompatibilityDateOverride is set, the config must NOT specify compatibilityDate.\n    if (conf.hasCompatibilityDate()) {\n      errorReporter.addError(kj::str(\n          \"Worker specifies compatibilityDate but --compat-date was provided. \"\n          \"When using --compat-date, workers must not specify compatibilityDate in the config. \"\n          \"Use compatibilityFlags to enable/disable specific flags if needed.\"));\n    }\n    // Use FUTURE_FOR_TEST to allow any valid date (including far future like 2999-12-31)\n    // without validation against CODE_VERSION or current date.\n    compileCompatibilityFlags(overrideDate, conf.getCompatibilityFlags(), featureFlags,\n        errorReporter, experimental, CompatibilityDateValidation::FUTURE_FOR_TEST);\n  } else if (conf.hasCompatibilityDate()) {\n    compileCompatibilityFlags(conf.getCompatibilityDate(), conf.getCompatibilityFlags(),\n        featureFlags, errorReporter, experimental, CompatibilityDateValidation::CODE_VERSION);\n  } else {\n    errorReporter.addError(kj::str(\"Worker must specify compatibilityDate.\"));\n  }\n\n  kj::Vector<FutureSubrequestChannel> subrequestChannels;\n  kj::Vector<FutureActorChannel> actorChannels;\n  kj::Vector<FutureActorClassChannel> actorClassChannels;\n  kj::Vector<FutureWorkerLoaderChannel> workerLoaderChannels;\n  bool hasWorkerdDebugPortBinding = false;\n\n  auto confBindings = conf.getBindings();\n  kj::Vector<WorkerdApi::Global> globals(confBindings.size());\n  for (auto binding: confBindings) {\n    KJ_IF_SOME(global,\n        createBinding(name, conf, binding, errorReporter, subrequestChannels, actorChannels,\n            actorClassChannels, workerLoaderChannels, hasWorkerdDebugPortBinding, actorConfigs,\n            experimental)) {\n      globals.add(kj::mv(global));\n    }\n  }\n\n  // Construct `WorkerDef` from `conf`.\n  WorkerDef def{\n    .featureFlags = featureFlags.asReader(),\n    .source = WorkerdApi::extractSource(name, conf, featureFlags.asReader(), errorReporter),\n    .moduleFallback = conf.hasModuleFallback() ? kj::some(conf.getModuleFallback()) : kj::none,\n    .localActorConfigs = localActorConfigs,\n    .isDynamic = false,\n\n    .globalOutbound{\n      .designator = conf.getGlobalOutbound(),\n      .errorContext = kj::str(\"Worker \\\"\", name, \"\\\"'s globalOutbound\"),\n    },\n\n    .cacheApiOutbound = conf.hasCacheApiOutbound()\n        ? kj::some(FutureSubrequestChannel{\n            .designator = conf.getCacheApiOutbound(),\n            .errorContext = kj::str(\"Worker \\\"\", name, \"\\\"'s cacheApiOutbound\"),\n          })\n        : kj::none,\n\n    .subrequestChannels = kj::mv(subrequestChannels),\n    .actorChannels = kj::mv(actorChannels),\n    .actorClassChannels = kj::mv(actorClassChannels),\n    .workerLoaderChannels = kj::mv(workerLoaderChannels),\n    .hasWorkerdDebugPortBinding = hasWorkerdDebugPortBinding,\n\n    // clang-format off\n    .tails = KJ_MAP(tail, conf.getTails()) -> FutureSubrequestChannel {\n      return {\n        .designator = tail,\n        .errorContext = kj::str(\"Worker \\\"\", name, \"\\\"'s tails\"),\n      };\n    },\n\n    .streamingTails = KJ_MAP(streamingTail, conf.getStreamingTails()) -> FutureSubrequestChannel {\n      return {\n        .designator = streamingTail,\n        .errorContext = kj::str(\"Worker \\\"\", name, \"\\\"'s streaming tails\"),\n      };\n    },\n\n    .actorStorageConf = conf.getDurableObjectStorage(),\n    .containerEngineConf = conf.getContainerEngine(),\n\n    .compileBindings = [globals = kj::mv(globals)](\n        jsg::Lock& lock, const Worker::Api& api, v8::Local<v8::Object> target) {\n      return WorkerdApi::from(api).compileGlobals(lock, globals, target, 1);\n    },\n    // clang-format on\n  };\n\n  co_return co_await makeWorkerImpl(name, kj::mv(def), extensions, errorReporter);\n}\n\nkj::Promise<kj::Own<Server::WorkerService>> Server::makeWorkerImpl(kj::StringPtr name,\n    WorkerDef def,\n    capnp::List<config::Extension>::Reader extensions,\n    ErrorReporter& errorReporter) {\n  // Load Python artifacts if this is a Python worker\n  co_await preloadPython(name, def, errorReporter);\n\n  auto jsgobserver = kj::atomicRefcounted<JsgIsolateObserver>();\n  auto observer = kj::atomicRefcounted<IsolateObserver>();\n  auto limitEnforcer = kj::refcounted<NullIsolateLimitEnforcer>();\n\n  // Create the FsMap that will be used to map known file system\n  // roots to configurable locations.\n  // TODO(node-fs): This is set up to allow users to configure the \"mount\"\n  // points for known roots but we currently do not expose that in the\n  // config. So for now this just uses the defaults.\n  auto workerFs = newWorkerFileSystem(kj::heap<FsMap>(), getBundleDirectory(def.source));\n\n  // TODO(soon): Either make python workers support the new module registry before\n  // NMR is defaulted on, or disable NMR by default when python workers are enabled.\n  // While NMR is experimental, we'll just throw an error if both are enabled.\n  if (def.featureFlags.getPythonWorkers()) {\n    KJ_REQUIRE(!def.featureFlags.getNewModuleRegistry(),\n        \"Python workers do not currently support the new ModuleRegistry implementation. \"\n        \"Please disable the new ModuleRegistry feature flag to use Python workers.\");\n  }\n\n  bool usingNewModuleRegistry = def.featureFlags.getNewModuleRegistry();\n  kj::Maybe<kj::Arc<jsg::modules::ModuleRegistry>> newModuleRegistry;\n  // TODO(soon): Python workers do not currently support the new module registry.\n  if (usingNewModuleRegistry) {\n    KJ_REQUIRE(experimental,\n        \"The new ModuleRegistry implementation is an experimental feature. \"\n        \"You must run workerd with `--experimental` to use this feature.\");\n\n    // We use the same path for modules that the virtual file system uses.\n    // For instance, if the user specifies a bundle path of \"/foo/bar\" and\n    // there is a module in the bundle at \"/foo/bar/baz.js\", then the module's\n    // import specifier url will be \"file:///foo/bar/baz.js\".\n    const jsg::Url& bundleBase = workerFs->getBundleRoot();\n\n    // In workerd the module registry is always associated with just a single\n    // worker instance, so we initialize it here. In production, however, a\n    // single instance may be shared across multiple replicas.\n    kj::Maybe<kj::String> maybeFallbackService;\n    KJ_IF_SOME(moduleFallback, def.moduleFallback) {\n      maybeFallbackService = kj::str(moduleFallback);\n    }\n\n    using ArtifactBundler = workerd::api::pyodide::ArtifactBundler;\n    auto isPythonWorker = def.featureFlags.getPythonWorkers();\n    auto artifactBundler = isPythonWorker\n        ? ArtifactBundler::makePackagesOnlyBundler(pythonConfig.pyodidePackageManager)\n        : ArtifactBundler::makeDisabledBundler();\n\n    newModuleRegistry = WorkerdApi::newWorkerdModuleRegistry(*jsgobserver,\n        def.source.variant.tryGet<Worker::Script::ModulesSource>(), def.featureFlags, pythonConfig,\n        bundleBase, extensions, kj::mv(maybeFallbackService), kj::mv(artifactBundler));\n  }\n\n  auto isolateGroup = v8::IsolateGroup::GetDefault();\n  auto api = kj::heap<WorkerdApi>(globalContext->v8System, def.featureFlags, extensions,\n      limitEnforcer->getCreateParams(), isolateGroup, kj::mv(jsgobserver), *memoryCacheProvider,\n      pythonConfig);\n\n  auto inspectorPolicy = Worker::Isolate::InspectorPolicy::DISALLOW;\n  if (inspectorOverride != kj::none) {\n    // For workerd, if the inspector is enabled, it is always fully trusted.\n    inspectorPolicy = Worker::Isolate::InspectorPolicy::ALLOW_FULLY_TRUSTED;\n  }\n  Worker::LoggingOptions isolateLoggingOptions = loggingOptions;\n  isolateLoggingOptions.consoleMode =\n      def.source.variant.is<WorkerSource::ScriptSource>() && !usingNewModuleRegistry\n      ? Worker::ConsoleMode::INSPECTOR_ONLY\n      : loggingOptions.consoleMode;\n  auto isolate = kj::atomicRefcounted<Worker::Isolate>(kj::mv(api), kj::mv(observer), name,\n      kj::mv(limitEnforcer), inspectorPolicy, kj::mv(isolateLoggingOptions));\n\n  // If we are using the inspector, we need to register the Worker::Isolate\n  // with the inspector service.\n  KJ_IF_SOME(isolateRegistrar, inspectorIsolateRegistrar) {\n    isolateRegistrar->registerIsolate(name, isolate.get());\n  }\n\n  if (!usingNewModuleRegistry) {\n    KJ_IF_SOME(moduleFallback, def.moduleFallback) {\n      KJ_REQUIRE(experimental,\n          \"The module fallback service is an experimental feature. \"\n          \"You must run workerd with `--experimental` to use the module fallback service.\");\n      // If the config has the moduleFallback option, then we are going to set up the ability\n      // to load certain modules from a fallback service. This is generally intended for local\n      // dev/testing purposes only.\n      auto& apiIsolate = isolate->getApi();\n      auto fallbackClient =\n          kj::heap<workerd::fallback::FallbackServiceClient>(kj::str(moduleFallback));\n      apiIsolate.setModuleFallbackCallback(\n          [client = kj::mv(fallbackClient), featureFlags = apiIsolate.getFeatureFlags()](\n              jsg::Lock& js, kj::StringPtr specifier, kj::Maybe<kj::String> referrer,\n              jsg::CompilationObserver& observer, jsg::ModuleRegistry::ResolveMethod method,\n              kj::Maybe<kj::StringPtr> rawSpecifier) mutable\n          -> kj::Maybe<kj::OneOf<kj::String, jsg::ModuleRegistry::ModuleInfo>> {\n        kj::HashMap<kj::StringPtr, kj::StringPtr> attributes;\n        KJ_IF_SOME(moduleOrRedirect,\n            client->tryResolve(workerd::fallback::Version::V1,\n                method == jsg::ModuleRegistry::ResolveMethod::IMPORT\n                    ? workerd::fallback::ImportType::IMPORT\n                    : workerd::fallback::ImportType::REQUIRE,\n                specifier, rawSpecifier.orDefault(nullptr), referrer.orDefault(kj::String()),\n                attributes)) {\n          KJ_SWITCH_ONEOF(moduleOrRedirect) {\n            KJ_CASE_ONEOF(redirect, kj::String) {\n              // If a string is returned, then the fallback service returned a 301 redirect.\n              // The value is the specifier of the new target module.\n              return kj::Maybe(kj::mv(redirect));\n            }\n            KJ_CASE_ONEOF(module, kj::Own<config::Worker::Module::Reader>) {\n              KJ_IF_SOME(module,\n                  WorkerdApi::tryCompileModule(js, *module, observer, featureFlags)) {\n                return kj::Maybe(kj::mv(module));\n              }\n              KJ_LOG(ERROR, \"Fallback service does not support this module type\", module->which());\n            }\n          }\n        }\n\n        return kj::none;\n      });\n    }\n  }\n\n  using ArtifactBundler = workerd::api::pyodide::ArtifactBundler;\n  auto isPythonWorker = def.featureFlags.getPythonWorkers();\n  auto artifactBundler = isPythonWorker\n      ? ArtifactBundler::makePackagesOnlyBundler(pythonConfig.pyodidePackageManager)\n      : ArtifactBundler::makeDisabledBundler();\n\n  auto script = isolate->newScript(name, def.source, IsolateObserver::StartType::COLD,\n      SpanParent(nullptr), workerFs.attach(kj::mv(def.maybeOwnedSourceCode)), false, errorReporter,\n      kj::mv(artifactBundler), kj::mv(newModuleRegistry));\n\n  using Global = WorkerdApi::Global;\n  jsg::V8Ref<v8::Object> ctxExportsHandle = nullptr;\n  auto compileBindings = [&](jsg::Lock& lock, const Worker::Api& api, v8::Local<v8::Object> target,\n                             v8::Local<v8::Object> ctxExports) {\n    // We can't fill in ctx.exports yet because we need to run the validator first to discover\n    // entrypoints, which we cannot do until after the Worker constructor completes. We are\n    // permitted to hold a handle until then, though.\n    ctxExportsHandle = lock.v8Ref(ctxExports);\n\n    return def.compileBindings(lock, api, target);\n  };\n  auto worker = kj::atomicRefcounted<Worker>(kj::mv(script), kj::atomicRefcounted<WorkerObserver>(),\n      kj::mv(compileBindings), IsolateObserver::StartType::COLD, SpanParent(nullptr),\n      Worker::Lock::TakeSynchronously(kj::none), errorReporter);\n\n  uint totalActorChannels = 0;\n\n  worker->runInLockScope(Worker::Lock::TakeSynchronously(kj::none), [&](Worker::Lock& lock) {\n    lock.validateHandlers(errorReporter);\n\n    // Build `ctx.exports` based on the entrypoints reported by `validateHandlers()`.\n    kj::Vector<Global> ctxExports(\n        errorReporter.namedEntrypoints.size() + def.localActorConfigs.size());\n\n    // Start numbering loopback channels for stateless entrypoints after the last subrequest\n    // channel used by bindings.\n    uint nextSubrequestChannel =\n        def.subrequestChannels.size() + IoContext::SPECIAL_SUBREQUEST_CHANNEL_COUNT;\n    if (errorReporter.defaultEntrypoint != kj::none) {\n      ctxExports.add(Global{.name = kj::str(\"default\"),\n        .value = Global::LoopbackServiceStub{.channel = nextSubrequestChannel++}});\n    }\n    for (auto& ep: errorReporter.namedEntrypoints) {\n      // Workflow classes are treated as stateless entrypoints for runtime purposes, but should\n      // NOT be reflected in ctx.exports.\n      // TODO(someday): Currently Workflows must be given a name independent of their class name,\n      //   and the binding must reference that name. If the name were just the class name -- like\n      //   Durable Object namespaces -- then we could put a `Workflow` binding into `ctx.exports`.\n      if (!errorReporter.workflowClasses.contains(ep.key)) {\n        ctxExports.add(Global{.name = kj::str(ep.key),\n          .value = Global::LoopbackServiceStub{.channel = nextSubrequestChannel++}});\n      }\n    }\n\n    // Start numbering loopback channels for actor classes after the last actor channel and actor\n    // class channel used by bindings. Note that every exported actor class will have a ctx.exports\n    // entry, but only the ones that have storage configured will be namespace bindings; the others\n    // will be simply actor class bindings, which can be used with facets. We will iterate over\n    // the exported class names and cross-reference with the storage config. Note that if the\n    // storage config contains a class name that isn't among the exports, we won't create a\n    // ctx.exports entry for it (it wouldn't work anyway).\n    uint nextActorChannel = def.actorChannels.size();\n    uint nextActorClassChannel = def.actorClassChannels.size();\n    for (auto& className: errorReporter.actorClasses) {\n      uint actorClassChannel = nextActorClassChannel++;\n\n      decltype(Global::value) value;\n      KJ_IF_SOME(ns, def.localActorConfigs.find(className)) {\n        // This class has storage attached. We'll create a loopback actor namespace binding.\n        KJ_SWITCH_ONEOF(ns) {\n          KJ_CASE_ONEOF(durable, Durable) {\n            value = Global::LoopbackDurableActorNamespace{\n              .actorChannel = nextActorChannel++,\n              .uniqueKey = durable.uniqueKey,\n              .classChannel = actorClassChannel,\n            };\n          }\n          KJ_CASE_ONEOF(ephemeral, Ephemeral) {\n            value = Global::LoopbackEphemeralActorNamespace{\n              .actorChannel = nextActorChannel++,\n              .classChannel = actorClassChannel,\n            };\n          }\n        }\n      } else {\n        // No storage attached. We'll create an actual class binding (for use with facets).\n        value = Global::LoopbackActorClass{.channel = actorClassChannel};\n      }\n      ctxExports.add(Global{.name = kj::str(className), .value = kj::mv(value)});\n    }\n    totalActorChannels = nextActorChannel;\n\n    JSG_WITHIN_CONTEXT_SCOPE(lock, lock.getContext(), [&](jsg::Lock& js) {\n      WorkerdApi::from(worker->getIsolate().getApi())\n          .compileGlobals(lock, ctxExports, ctxExportsHandle.getHandle(js), 1);\n    });\n\n    // As an optimization, drop this now while we have the lock.\n    { auto drop = kj::mv(ctxExportsHandle); }\n  });\n\n  auto linkCallback = [this, def = kj::mv(def), totalActorChannels](WorkerService& workerService,\n                          Worker::ValidationErrorReporter& errorReporter) mutable {\n    WorkerService::LinkedIoChannels result{.alarmScheduler = *alarmScheduler};\n\n    auto entrypointNames = workerService.getEntrypointNames();\n    auto actorClassNames = workerService.getActorClassNames();\n\n    auto services = kj::heapArrayBuilder<kj::Own<IoChannelFactory::SubrequestChannel>>(\n        def.subrequestChannels.size() + IoContext::SPECIAL_SUBREQUEST_CHANNEL_COUNT +\n        entrypointNames.size() + workerService.hasDefaultEntrypoint());\n\n    auto globalService = kj::mv(def.globalOutbound).lookup(*this);\n\n    // Bind both \"next\" and \"null\" to the global outbound. (The difference between these is a\n    // legacy artifact that no one should be depending on.)\n    static_assert(IoContext::SPECIAL_SUBREQUEST_CHANNEL_COUNT == 2);\n    services.add(kj::addRef(*globalService));\n    services.add(kj::mv(globalService));\n\n    for (auto& channel: def.subrequestChannels) {\n      services.add(kj::mv(channel).lookup(*this));\n    }\n\n    // Link the ctx.exports self-referential channels. Note that it's important these are added\n    // in exactyl the same order as the channels were allocated earlier when we compiled the\n    // ctx.exports bindings.\n    if (workerService.hasDefaultEntrypoint()) {\n      services.add(workerService.getLoopbackEntrypoint(/*name=*/kj::none));\n    }\n    for (auto& ep: entrypointNames) {\n      services.add(workerService.getLoopbackEntrypoint(ep));\n    }\n\n    result.subrequest = services.finish();\n\n    // Set up actor class channels\n    auto actorClasses = kj::heapArrayBuilder<kj::Own<ActorClass>>(\n        def.actorClassChannels.size() + actorClassNames.size());\n\n    for (auto& channel: def.actorClassChannels) {\n      actorClasses.add(kj::mv(channel).lookup(*this));\n    }\n\n    auto linkedActorChannels =\n        kj::heapArrayBuilder<kj::Maybe<WorkerService::ActorNamespace&>>(totalActorChannels);\n\n    for (auto& channel: def.actorChannels) {\n      WorkerService* targetService = &workerService;\n      if (channel.designator.hasServiceName()) {\n        auto& svc = KJ_UNWRAP_OR(this->services.find(channel.designator.getServiceName()), {\n          // error was reported earlier\n          linkedActorChannels.add(kj::none);\n          continue;\n        });\n        targetService = dynamic_cast<WorkerService*>(svc.get());\n        if (targetService == nullptr) {\n          // error was reported earlier\n          linkedActorChannels.add(kj::none);\n          continue;\n        }\n      }\n\n      // (If getActorNamespace() returns null, an error was reported earlier.)\n      linkedActorChannels.add(targetService->getActorNamespace(channel.designator.getClassName()));\n    };\n\n    // Link the ctx.exports self-referential actor channels. Again, it's important that these\n    // be added in the same order as before. kj::HashMap iteration order is deterministic, and\n    // is exactly insertion order as long as no entries have been removed, so we can expect that\n    // `workerService.getActorClassNames()` iterates in the same order as\n    // `errorReporter.actorClasses` did earlier. As before, every exported class gets an actor\n    // class channel, but only the ones with configured storage will also get namespace channels.\n    auto& selfActorNamespaces = workerService.getActorNamespaces();\n    for (auto& className: actorClassNames) {\n      actorClasses.add(workerService.getLoopbackActorClass(className));\n      KJ_IF_SOME(ns, selfActorNamespaces.find(className)) {\n        linkedActorChannels.add(*ns);\n      }\n    }\n\n    result.actor = linkedActorChannels.finish();\n    result.actorClass = actorClasses.finish();\n\n    KJ_IF_SOME(out, def.cacheApiOutbound) {\n      result.cache = kj::mv(out).lookup(*this);\n    }\n\n    if (def.actorStorageConf.isLocalDisk()) {\n      kj::StringPtr diskName = def.actorStorageConf.getLocalDisk();\n      KJ_IF_SOME(svc, this->services.find(def.actorStorageConf.getLocalDisk())) {\n        auto diskSvc = dynamic_cast<DiskDirectoryService*>(svc.get());\n        if (diskSvc == nullptr) {\n          errorReporter.addError(kj::str(\"durableObjectStorage config refers to the service \\\"\",\n              diskName, \"\\\", but that service is not a local disk service.\"));\n        } else KJ_IF_SOME(dir, diskSvc->getWritable()) {\n          result.actorStorage = dir;\n        } else {\n          errorReporter.addError(\n              kj::str(\"durableObjectStorage config refers to the disk service \\\"\", diskName,\n                  \"\\\", but that service is defined read-only.\"));\n        }\n      } else {\n        errorReporter.addError(kj::str(\"durableObjectStorage config refers to a service \\\"\",\n            diskName, \"\\\", but no such service is defined.\"));\n      }\n    }\n\n    kj::HashMap<kj::StringPtr, WorkerService::ActorNamespace&> durableNamespacesByUniqueKey;\n    for (auto& [className, ns]: workerService.getActorNamespaces()) {\n      KJ_IF_SOME(config, ns->getConfig().tryGet<Server::Durable>()) {\n        auto& actorNs =\n            ns;  // clangd gets confused trying to use ns directly in the capture below??\n\n        auto idFactory = kj::heap<ActorIdFactoryImpl>(config.uniqueKey);\n\n        alarmScheduler->registerNamespace(config.uniqueKey,\n            [&actorNs, idFactory = kj::mv(idFactory)](\n                kj::String idStr) mutable -> kj::Own<WorkerInterface> {\n          Worker::Actor::Id id = idFactory->idFromString(kj::mv(idStr));\n          auto actorContainer = actorNs->getActorContainer(kj::mv(id));\n          return newPromisedWorkerInterface(actorContainer->startRequest({}));\n        });\n      }\n    }\n\n    result.tails = KJ_MAP(tail, def.tails) { return kj::mv(tail).lookup(*this); };\n\n    result.streamingTails = KJ_MAP(tail, def.streamingTails) { return kj::mv(tail).lookup(*this); };\n\n    result.workerLoaders = KJ_MAP(il, def.workerLoaderChannels) {\n      KJ_IF_SOME(id, il.id) {\n        return workerLoaderNamespaces\n            .findOrCreate(id, [&]() -> decltype(workerLoaderNamespaces)::Entry {\n          return {\n            .key = kj::mv(id),\n            .value = kj::rc<WorkerLoaderNamespace>(*this, kj::mv(il.name)),\n          };\n        }).addRef();\n      } else {\n        return anonymousWorkerLoaderNamespaces\n            .add(kj::rc<WorkerLoaderNamespace>(*this, kj::mv(il.name)))\n            .addRef();\n      }\n    };\n\n    if (def.hasWorkerdDebugPortBinding) {\n      result.workerdDebugPortNetwork = network;\n    }\n\n    return result;\n  };\n\n  kj::Maybe<kj::String> dockerPath = kj::none;\n  kj::Maybe<kj::String> containerEgressInterceptorImage = kj::none;\n  switch (def.containerEngineConf.which()) {\n    case config::Worker::ContainerEngine::NONE:\n      // No container engine configured\n      break;\n    case config::Worker::ContainerEngine::LOCAL_DOCKER: {\n      auto dockerConf = def.containerEngineConf.getLocalDocker();\n      dockerPath = kj::str(dockerConf.getSocketPath());\n      if (dockerConf.hasContainerEgressInterceptorImage()) {\n        containerEgressInterceptorImage = kj::str(dockerConf.getContainerEgressInterceptorImage());\n      }\n      break;\n    }\n  }\n\n  kj::Maybe<kj::StringPtr> serviceName;\n  if (!def.isDynamic) serviceName = name;\n\n  auto result =\n      kj::refcounted<WorkerService>(channelTokenHandler, serviceName, globalContext->threadContext,\n          monotonicClock, kj::mv(worker), kj::mv(errorReporter.defaultEntrypoint),\n          kj::mv(errorReporter.namedEntrypoints), kj::mv(errorReporter.actorClasses),\n          kj::mv(linkCallback), KJ_BIND_METHOD(*this, abortAllActors), kj::mv(dockerPath),\n          kj::mv(containerEgressInterceptorImage), def.isDynamic);\n  result->initActorNamespaces(def.localActorConfigs, network);\n  co_return result;\n}\n\n// =======================================================================================\n\nkj::Promise<kj::Own<Server::Service>> Server::makeService(config::Service::Reader conf,\n    kj::HttpHeaderTable::Builder& headerTableBuilder,\n    capnp::List<config::Extension>::Reader extensions) {\n  kj::StringPtr name = conf.getName();\n\n  switch (conf.which()) {\n    case config::Service::UNSPECIFIED:\n      reportConfigError(kj::str(\"Service named \\\"\", name, \"\\\" does not specify what to serve.\"));\n      co_return makeInvalidConfigService();\n\n    case config::Service::EXTERNAL:\n      co_return makeExternalService(name, conf.getExternal(), headerTableBuilder);\n\n    case config::Service::NETWORK:\n      co_return makeNetworkService(conf.getNetwork());\n\n    case config::Service::WORKER:\n      co_return co_await makeWorker(name, conf.getWorker(), extensions);\n\n    case config::Service::DISK:\n      co_return makeDiskDirectoryService(name, conf.getDisk(), headerTableBuilder);\n  }\n\n  reportConfigError(kj::str(\"Service named \\\"\", name,\n      \"\\\" has unrecognized type. Was the config compiled with a \"\n      \"newer version of the schema?\"));\n  co_return makeInvalidConfigService();\n}\n\nvoid Server::taskFailed(kj::Exception&& exception) {\n  fatalFulfiller->reject(kj::mv(exception));\n}\n\nkj::Own<Server::Service> Server::lookupService(\n    config::ServiceDesignator::Reader designator, kj::String errorContext) {\n  kj::StringPtr targetName = designator.getName();\n  Service* service = KJ_UNWRAP_OR(services.find(targetName), {\n    reportConfigError(kj::str(errorContext, \" refers to a service \\\"\", targetName,\n        \"\\\", but no such service is defined.\"));\n    return kj::addRef(*invalidConfigServiceSingleton);\n  });\n\n  kj::Maybe<kj::StringPtr> entrypointName;\n  if (designator.hasEntrypoint()) {\n    entrypointName = designator.getEntrypoint();\n  }\n\n  auto props = [&]() -> Frankenvalue {\n    auto props = designator.getProps();\n    switch (props.which()) {\n      case config::ServiceDesignator::Props::EMPTY:\n        return {};\n      case config::ServiceDesignator::Props::JSON:\n        return Frankenvalue::fromJson(kj::str(props.getJson()));\n    }\n    reportConfigError(kj::str(errorContext,\n        \" has unrecognized props type. Was the config compiled with a \"\n        \"newer version of the schema?\"));\n    return {};\n  }();\n\n  if (WorkerService* worker = dynamic_cast<WorkerService*>(service)) {\n    KJ_IF_SOME(ep, worker->getEntrypoint(entrypointName, kj::mv(props))) {\n      return kj::mv(ep);\n    } else KJ_IF_SOME(ep, entrypointName) {\n      reportConfigError(kj::str(errorContext, \" refers to service \\\"\", targetName,\n          \"\\\" with a named entrypoint \\\"\", ep, \"\\\", but \\\"\", targetName,\n          \"\\\" has no such named entrypoint.\"));\n      return kj::addRef(*invalidConfigServiceSingleton);\n    } else {\n      reportConfigError(kj::str(errorContext, \" refers to service \\\"\", targetName,\n          \"\\\", but does not specify an entrypoint, and the service does not have a \"\n          \"default entrypoint.\"));\n      return kj::addRef(*invalidConfigServiceSingleton);\n    }\n  } else {\n    KJ_IF_SOME(ep, entrypointName) {\n      reportConfigError(kj::str(errorContext, \" refers to service \\\"\", targetName,\n          \"\\\" with a named entrypoint \\\"\", ep, \"\\\", but \\\"\", targetName,\n          \"\\\" is not a Worker, so does not have any named entrypoints.\"));\n    } else if (!props.empty()) {\n      reportConfigError(kj::str(errorContext, \" refers to service \\\"\", targetName,\n          \"\\\" and provides a `props` value, but \\\"\", targetName,\n          \"\\\" is not a Worker, so cannot accept `props`\"));\n    }\n\n    return kj::addRef(*service);\n  }\n}\n\nkj::Own<Server::ActorClass> Server::lookupActorClass(\n    config::ServiceDesignator::Reader designator, kj::String errorContext) {\n  // TODO(cleanup): There's a lot of repeated code with lookupService(), should it be refactored?\n\n  kj::StringPtr targetName = designator.getName();\n  Service* service = KJ_UNWRAP_OR(services.find(targetName), {\n    reportConfigError(kj::str(errorContext, \" refers to a service \\\"\", targetName,\n        \"\\\", but no such service is defined.\"));\n    return kj::addRef(*invalidConfigActorClassSingleton);\n  });\n\n  kj::Maybe<kj::StringPtr> entrypointName;\n  if (designator.hasEntrypoint()) {\n    entrypointName = designator.getEntrypoint();\n  }\n\n  auto props = [&]() -> Frankenvalue {\n    auto props = designator.getProps();\n    switch (props.which()) {\n      case config::ServiceDesignator::Props::EMPTY:\n        return {};\n      case config::ServiceDesignator::Props::JSON:\n        return Frankenvalue::fromJson(kj::str(props.getJson()));\n    }\n    reportConfigError(kj::str(errorContext,\n        \" has unrecognized props type. Was the config compiled with a \"\n        \"newer version of the schema?\"));\n    return {};\n  }();\n\n  if (WorkerService* worker = dynamic_cast<WorkerService*>(service)) {\n    KJ_IF_SOME(ep, worker->getActorClass(entrypointName, kj::mv(props))) {\n      return kj::mv(ep);\n    } else KJ_IF_SOME(ep, entrypointName) {\n      reportConfigError(kj::str(errorContext, \" refers to service \\\"\", targetName,\n          \"\\\" with a Durable Object entrypoint \\\"\", ep, \"\\\", but \\\"\", targetName,\n          \"\\\" has no such exported entrypoint class.\"));\n      return kj::addRef(*invalidConfigActorClassSingleton);\n    } else {\n      reportConfigError(kj::str(errorContext, \" refers to service \\\"\", targetName,\n          \"\\\", but does not specify an entrypoint, and the service does export a \"\n          \"Durable Object class as its default entrypoint.\"));\n      return kj::addRef(*invalidConfigActorClassSingleton);\n    }\n  } else {\n    KJ_IF_SOME(ep, entrypointName) {\n      reportConfigError(kj::str(errorContext, \" refers to service \\\"\", targetName,\n          \"\\\" with a named Durable Object entrypoint \\\"\", ep, \"\\\", but \\\"\", targetName,\n          \"\\\" is not a Worker, so does not have any named entrypoints.\"));\n    } else {\n      reportConfigError(kj::str(errorContext, \" refers to service \\\"\", targetName,\n          \"\\\" as a Durable Object class, but \\\"\", targetName,\n          \"\\\" is not a Worker, so cannot be used as a class.\"));\n    }\n\n    return kj::addRef(*invalidConfigActorClassSingleton);\n  }\n}\n\nkj::Own<IoChannelFactory::SubrequestChannel> Server::resolveEntrypoint(\n    kj::StringPtr serviceName, kj::Maybe<kj::StringPtr> entrypoint, Frankenvalue props) {\n  auto& service = *JSG_REQUIRE_NONNULL(services.find(serviceName), Error,\n      \"Stub refers to a service that doesn't exist: \", serviceName);\n\n  auto& worker = JSG_REQUIRE_NONNULL(kj::tryDowncast<WorkerService>(service), Error,\n      \"Stub refers to a service that is not a Worker: \", serviceName);\n\n  return JSG_REQUIRE_NONNULL(worker.getEntrypoint(entrypoint, kj::mv(props)), Error,\n      \"Stub refers to a an entrypoint of the target service that doesn't exist: \",\n      entrypoint.orDefault(\"default\"));\n}\n\nkj::Own<IoChannelFactory::ActorClassChannel> Server::resolveActorClass(\n    kj::StringPtr serviceName, kj::Maybe<kj::StringPtr> entrypoint, Frankenvalue props) {\n  auto& service = *JSG_REQUIRE_NONNULL(services.find(serviceName), Error,\n      \"Stub refers to a service that doesn't exist: \", serviceName);\n\n  auto& worker = JSG_REQUIRE_NONNULL(kj::tryDowncast<WorkerService>(service), Error,\n      \"Stub refers to a service that is not a Worker: \", serviceName);\n\n  return JSG_REQUIRE_NONNULL(worker.getActorClass(entrypoint, kj::mv(props)), Error,\n      \"Stub refers to a an entrypoint of the target service that doesn't exist: \",\n      entrypoint.orDefault(\"default\"));\n}\n\n// =======================================================================================\n\nclass Server::WorkerdBootstrapImpl final: public rpc::WorkerdBootstrap::Server {\n public:\n  WorkerdBootstrapImpl(kj::Own<IoChannelFactory::SubrequestChannel> service,\n      capnp::HttpOverCapnpFactory& httpOverCapnpFactory)\n      : service(kj::mv(service)),\n        httpOverCapnpFactory(httpOverCapnpFactory) {}\n\n  kj::Promise<void> startEvent(StartEventContext context) override {\n    // Extract the optional cf blob from the RPC params and pass it along with the\n    // service channel to EventDispatcherImpl. The cf blob will be included in\n    // SubrequestMetadata when creating the WorkerInterface for HTTP events.\n    kj::Maybe<kj::String> cfBlobJson;\n    auto params = context.getParams();\n    if (params.hasCfBlobJson()) {\n      cfBlobJson = kj::str(params.getCfBlobJson());\n    }\n    context.initResults(capnp::MessageSize{4, 1})\n        .setDispatcher(kj::heap<EventDispatcherImpl>(\n            httpOverCapnpFactory, kj::addRef(*service), kj::mv(cfBlobJson)));\n    return kj::READY_NOW;\n  }\n\n private:\n  kj::Own<IoChannelFactory::SubrequestChannel> service;\n  capnp::HttpOverCapnpFactory& httpOverCapnpFactory;\n\n  class EventDispatcherImpl final: public rpc::EventDispatcher::Server {\n   public:\n    EventDispatcherImpl(capnp::HttpOverCapnpFactory& httpOverCapnpFactory,\n        kj::Own<IoChannelFactory::SubrequestChannel> service,\n        kj::Maybe<kj::String> cfBlobJson)\n        : httpOverCapnpFactory(httpOverCapnpFactory),\n          service(kj::mv(service)),\n          cfBlobJson(kj::mv(cfBlobJson)) {}\n\n    kj::Promise<void> getHttpService(GetHttpServiceContext context) override {\n      // Create WorkerInterface with cf blob metadata (if provided via startEvent).\n      IoChannelFactory::SubrequestMetadata metadata;\n      KJ_IF_SOME(cf, cfBlobJson) {\n        metadata.cfBlobJson = kj::str(cf);\n      }\n      auto worker = getService()->startRequest(kj::mv(metadata));\n      context.initResults(capnp::MessageSize{4, 1})\n          .setHttp(httpOverCapnpFactory.kjToCapnp(kj::mv(worker)));\n      return kj::READY_NOW;\n    }\n\n    kj::Promise<void> sendTraces(SendTracesContext context) override {\n      auto traces =\n          KJ_MAP(trace, context.getParams().getTraces()){ return kj::refcounted<Trace>(trace); };\n      auto event = kj::heap<api::TraceCustomEvent>(api::TraceCustomEvent::TYPE, kj::mv(traces));\n      auto worker = getWorker();\n      auto result = co_await worker->customEvent(kj::mv(event));\n      auto resp = context.getResults().getResult();\n      resp.setOutcome(result.outcome);\n    }\n\n    kj::Promise<void> prewarm(PrewarmContext context) override {\n      throwUnsupported();\n    }\n\n    kj::Promise<void> runScheduled(RunScheduledContext context) override {\n      throwUnsupported();\n    }\n\n    kj::Promise<void> runAlarm(RunAlarmContext context) override {\n      throwUnsupported();\n    }\n\n    kj::Promise<void> queue(QueueContext context) override {\n      throwUnsupported();\n    }\n\n    kj::Promise<void> jsRpcSession(JsRpcSessionContext context) override {\n      auto customEvent = kj::heap<api::JsRpcSessionCustomEvent>(\n          api::JsRpcSessionCustomEvent::WORKER_RPC_EVENT_TYPE);\n\n      auto cap = customEvent->getCap();\n      capnp::PipelineBuilder<JsRpcSessionResults> pipelineBuilder;\n      pipelineBuilder.setTopLevel(cap);\n      context.setPipeline(pipelineBuilder.build());\n      context.getResults().setTopLevel(kj::mv(cap));\n\n      auto worker = getWorker();\n      return worker->customEvent(kj::mv(customEvent)).ignoreResult().attach(kj::mv(worker));\n    }\n\n    kj::Promise<void> tailStreamSession(TailStreamSessionContext context) override {\n      auto customEvent = kj::heap<tracing::TailStreamCustomEvent>();\n      auto cap = customEvent->getCap();\n      capnp::PipelineBuilder<TailStreamSessionResults> pipelineBuilder;\n      pipelineBuilder.setTopLevel(cap);\n      context.setPipeline(pipelineBuilder.build());\n      context.getResults().setTopLevel(kj::mv(cap));\n\n      auto worker = getWorker();\n      auto result = co_await worker->customEvent(kj::mv(customEvent)).attach(kj::mv(worker));\n      auto response = context.getResults();\n      response.setResult(result.outcome);\n    }\n\n   private:\n    capnp::HttpOverCapnpFactory& httpOverCapnpFactory;\n    kj::Maybe<kj::Own<IoChannelFactory::SubrequestChannel>> service;\n    kj::Maybe<kj::String> cfBlobJson;\n\n    kj::Own<IoChannelFactory::SubrequestChannel> getService() {\n      auto result =\n          kj::mv(KJ_ASSERT_NONNULL(service, \"EventDispatcher can only be used for one request\"));\n      service = kj::none;\n      return result;\n    }\n\n    kj::Own<WorkerInterface> getWorker() {\n      // For non-HTTP events (RPC, traces, etc.), create WorkerInterface with\n      // empty metadata since there's no HTTP request to extract cf from.\n      return getService()->startRequest({});\n    }\n\n    [[noreturn]] void throwUnsupported() {\n      JSG_FAIL_REQUIRE(Error, \"RPC connections don't yet support this event type.\");\n    }\n  };\n};\n\nclass Server::HttpListener final: public kj::Refcounted {\n public:\n  HttpListener(Server& owner,\n      kj::Own<kj::ConnectionReceiver> listener,\n      kj::Own<Service> service,\n      kj::StringPtr physicalProtocol,\n      kj::Own<HttpRewriter> rewriter,\n      kj::HttpHeaderTable& headerTable,\n      kj::Timer& timer,\n      capnp::HttpOverCapnpFactory& httpOverCapnpFactory)\n      : owner(owner),\n        listener(kj::mv(listener)),\n        service(kj::mv(service)),\n        headerTable(headerTable),\n        timer(timer),\n        httpOverCapnpFactory(httpOverCapnpFactory),\n        physicalProtocol(physicalProtocol),\n        rewriter(kj::mv(rewriter)) {}\n\n  kj::Promise<void> run() {\n    TRACE_EVENT(\"workerd\", \"HttpListener::run\");\n    for (;;) {\n      kj::AuthenticatedStream stream = co_await listener->acceptAuthenticated();\n      TRACE_EVENT(\"workerd\", \"HTTPListener handle connection\");\n\n      kj::Maybe<kj::String> cfBlobJson;\n      if (!rewriter->hasCfBlobHeader()) {\n        // Construct a cf blob describing the client identity.\n\n        kj::PeerIdentity* peerId;\n\n        KJ_IF_SOME(tlsId,\n            kj::dynamicDowncastIfAvailable<kj::TlsPeerIdentity>(*stream.peerIdentity)) {\n          peerId = &tlsId.getNetworkIdentity();\n\n          // TODO(someday): Add client certificate info to the cf blob? At present, KJ only\n          //   supplies the common name, but that doesn't even seem to be one of the fields that\n          //   Cloudflare-hosted Workers receive. We should probably try to match those.\n        } else {\n          peerId = stream.peerIdentity;\n        }\n\n        KJ_IF_SOME(remote, kj::dynamicDowncastIfAvailable<kj::NetworkPeerIdentity>(*peerId)) {\n          cfBlobJson = kj::str(\"{\\\"clientIp\\\": \", escapeJsonString(remote.toString()), \"}\");\n        } else KJ_IF_SOME(local, kj::dynamicDowncastIfAvailable<kj::LocalPeerIdentity>(*peerId)) {\n          auto creds = local.getCredentials();\n\n          kj::Vector<kj::String> parts;\n          KJ_IF_SOME(p, creds.pid) {\n            parts.add(kj::str(\"\\\"clientPid\\\":\", p));\n          }\n          KJ_IF_SOME(u, creds.uid) {\n            parts.add(kj::str(\"\\\"clientUid\\\":\", u));\n          }\n\n          cfBlobJson = kj::str(\"{\", kj::strArray(parts, \",\"), \"}\");\n        }\n      }\n\n      auto conn = kj::heap<Connection>(*this, kj::mv(cfBlobJson));\n\n      static auto constexpr listen = [](kj::Own<HttpListener> self, kj::Own<Connection> conn,\n                                         kj::Own<kj::AsyncIoStream> stream) -> kj::Promise<void> {\n        try {\n          co_await conn->listedHttp.httpServer.listenHttp(kj::mv(stream));\n        } catch (...) {\n          KJ_LOG(ERROR, kj::getCaughtExceptionAsKj());\n        }\n      };\n\n      // Run the connection handler loop in the global task set, so that run() waits for open\n      // connections to finish before returning, even if the listener loop is canceled. However,\n      // do not consider exceptions from a specific connection to be fatal.\n      owner.tasks.add(listen(kj::addRef(*this), kj::mv(conn), kj::mv(stream.stream)));\n    }\n  }\n\n private:\n  Server& owner;\n  kj::Own<kj::ConnectionReceiver> listener;\n  kj::Own<Service> service;\n  kj::HttpHeaderTable& headerTable;\n  kj::Timer& timer;\n  capnp::HttpOverCapnpFactory& httpOverCapnpFactory;\n  kj::StringPtr physicalProtocol;\n  kj::Own<HttpRewriter> rewriter;\n\n  kj::Maybe<capnp::TwoPartyServer> capnpServer;\n\n  kj::Promise<void> acceptCapnpConnection(kj::AsyncIoStream& conn) {\n    KJ_IF_SOME(s, capnpServer) {\n      return s.accept(conn);\n    }\n\n    // Capnp server not initialized. Create it now.\n    auto& s = capnpServer.emplace(\n        kj::heap<WorkerdBootstrapImpl>(kj::addRef(*service), httpOverCapnpFactory));\n    return s.accept(conn);\n  }\n\n  struct Connection final: public kj::HttpService, public kj::HttpServerErrorHandler {\n    Connection(HttpListener& parent, kj::Maybe<kj::String> cfBlobJson)\n        : parent(parent),\n          cfBlobJson(kj::mv(cfBlobJson)),\n          webSocketErrorHandler(kj::heap<JsgifyWebSocketErrors>()),\n          listedHttp(parent.owner,\n              parent.timer,\n              parent.headerTable,\n              *this,\n              kj::HttpServerSettings{.errorHandler = *this,\n                .webSocketErrorHandler = *webSocketErrorHandler,\n                .webSocketCompressionMode = kj::HttpServerSettings::MANUAL_COMPRESSION}) {}\n\n    HttpListener& parent;\n    kj::Maybe<kj::String> cfBlobJson;\n    kj::Own<JsgifyWebSocketErrors> webSocketErrorHandler;\n    ListedHttpServer listedHttp;\n\n    class ResponseWrapper final: public kj::HttpService::Response {\n     public:\n      ResponseWrapper(kj::HttpService::Response& inner, HttpRewriter& rewriter)\n          : inner(inner),\n            rewriter(rewriter) {}\n\n      kj::Own<kj::AsyncOutputStream> send(uint statusCode,\n          kj::StringPtr statusText,\n          const kj::HttpHeaders& headers,\n          kj::Maybe<uint64_t> expectedBodySize = kj::none) override {\n        TRACE_EVENT(\"workerd\", \"ResponseWrapper::send()\");\n        auto rewrite = headers.cloneShallow();\n        rewriter.rewriteResponse(rewrite);\n        return inner.send(statusCode, statusText, rewrite, expectedBodySize);\n      }\n\n      kj::Own<kj::WebSocket> acceptWebSocket(const kj::HttpHeaders& headers) override {\n        TRACE_EVENT(\"workerd\", \"ResponseWrapper::acceptWebSocket()\");\n        auto rewrite = headers.cloneShallow();\n        rewriter.rewriteResponse(rewrite);\n        return inner.acceptWebSocket(rewrite);\n      }\n\n     private:\n      kj::HttpService::Response& inner;\n      HttpRewriter& rewriter;\n    };\n\n    // ---------------------------------------------------------------------------\n    // implements kj::HttpService\n\n    kj::Promise<void> request(kj::HttpMethod method,\n        kj::StringPtr url,\n        const kj::HttpHeaders& headers,\n        kj::AsyncInputStream& requestBody,\n        kj::HttpService::Response& response) override {\n      TRACE_EVENT(\"workerd\", \"Connection:request()\");\n      IoChannelFactory::SubrequestMetadata metadata;\n      metadata.cfBlobJson = mapCopyString(cfBlobJson);\n\n      Response* wrappedResponse = &response;\n      kj::Own<ResponseWrapper> ownResponse;\n      if (parent.rewriter->needsRewriteResponse()) {\n        wrappedResponse = ownResponse = kj::heap<ResponseWrapper>(response, *parent.rewriter);\n      }\n\n      if (parent.rewriter->needsRewriteRequest() || cfBlobJson != kj::none) {\n        auto rewrite = KJ_UNWRAP_OR(parent.rewriter->rewriteIncomingRequest(\n                                        url, parent.physicalProtocol, headers, metadata.cfBlobJson),\n            { co_return co_await response.sendError(400, \"Bad Request\", parent.headerTable); });\n        auto worker = parent.service->startRequest(kj::mv(metadata));\n        co_return co_await worker->request(\n            method, url, *rewrite.headers, requestBody, *wrappedResponse);\n      } else {\n        auto worker = parent.service->startRequest(kj::mv(metadata));\n        co_return co_await worker->request(method, url, headers, requestBody, *wrappedResponse);\n      }\n    }\n\n    kj::Promise<void> connect(kj::StringPtr host,\n        const kj::HttpHeaders& headers,\n        kj::AsyncIoStream& connection,\n        ConnectResponse& response,\n        kj::HttpConnectSettings settings) override {\n      KJ_IF_SOME(h, parent.rewriter->getCapnpConnectHost()) {\n        if (h == host) {\n          // Client is requesting to open a capnp session!\n          response.accept(200, \"OK\", kj::HttpHeaders(parent.headerTable));\n          return parent.acceptCapnpConnection(connection);\n        }\n      }\n\n      // TODO(someday): Deliver connect() event to to worker? For now we call the default\n      //   implementation which throws an exception.\n      return kj::HttpService::connect(host, headers, connection, response, kj::mv(settings));\n    }\n\n    // ---------------------------------------------------------------------------\n    // implements kj::HttpServerErrorHandler\n\n    kj::Promise<void> handleApplicationError(\n        kj::Exception exception, kj::Maybe<kj::HttpService::Response&> response) override {\n      if (exception.getType() == kj::Exception::Type::DISCONNECTED) {\n        // Don't send a response, just close connection.\n        co_return;\n      }\n      KJ_LOG(ERROR, kj::str(\"Uncaught exception: \", exception));\n      KJ_IF_SOME(r, response) {\n        co_return co_await r.sendError(500, \"Internal Server Error\", parent.headerTable);\n      }\n    }\n  };\n};\n\nkj::Promise<void> Server::listenHttp(kj::Own<kj::ConnectionReceiver> listener,\n    kj::Own<Service> service,\n    kj::StringPtr physicalProtocol,\n    kj::Own<HttpRewriter> rewriter) {\n  auto obj =\n      kj::refcounted<HttpListener>(*this, kj::mv(listener), kj::mv(service), physicalProtocol,\n          kj::mv(rewriter), globalContext->headerTable, timer, globalContext->httpOverCapnpFactory);\n  co_return co_await obj->run();\n}\n\n// =======================================================================================\n// Debug port for exposing all services via RPC\n\nclass Server::DebugPortListener {\n public:\n  DebugPortListener(Server& owner,\n      kj::Own<kj::ConnectionReceiver> listener,\n      capnp::HttpOverCapnpFactory& httpOverCapnpFactory)\n      : owner(owner),\n        listener(kj::mv(listener)),\n        httpOverCapnpFactory(httpOverCapnpFactory) {}\n\n  kj::Promise<void> run() {\n    capnp::TwoPartyServer server(kj::heap<WorkerdDebugPortImpl>(&owner, httpOverCapnpFactory));\n    co_return co_await server.listen(*listener);\n  }\n\n private:\n  Server& owner;\n  kj::Own<kj::ConnectionReceiver> listener;\n  capnp::HttpOverCapnpFactory& httpOverCapnpFactory;\n\n  class WorkerdDebugPortImpl final: public rpc::WorkerdDebugPort::Server {\n   public:\n    WorkerdDebugPortImpl(\n        workerd::server::Server* srvPtr, capnp::HttpOverCapnpFactory& httpOverCapnpFactory)\n        : srv(*srvPtr),\n          httpOverCapnpFactory(httpOverCapnpFactory) {}\n\n    kj::Promise<void> getEntrypoint(GetEntrypointContext context) override {\n      auto params = context.getParams();\n      auto serviceName = params.getService();\n      auto propsReader = params.getProps();\n\n      // Look up the service\n      auto& serviceEntry =\n          KJ_ASSERT_NONNULL(srv.services.find(serviceName), \"Service not found\", serviceName);\n      auto service = serviceEntry->service();\n\n      // Convert props from Frankenvalue if provided\n      Frankenvalue props;\n      if (params.hasProps()) {\n        props = Frankenvalue::fromCapnp(propsReader);\n      }\n\n      kj::Own<Service> targetService;\n\n      // Try to cast to WorkerService to support entrypoints and props\n      auto* workerService = dynamic_cast<WorkerService*>(service);\n      if (workerService != nullptr) {\n        // This is a WorkerService, use getEntrypoint which supports both entrypoints and props\n        kj::Maybe<kj::StringPtr> maybeEntrypoint;\n        if (params.hasEntrypoint()) {\n          maybeEntrypoint = params.getEntrypoint();\n        }\n\n        targetService =\n            KJ_ASSERT_NONNULL(workerService->getEntrypoint(maybeEntrypoint, kj::mv(props)),\n                \"Entrypoint not found\", maybeEntrypoint.orDefault(\"(default)\"));\n      } else {\n        // Not a WorkerService\n        KJ_ASSERT(\n            !params.hasEntrypoint(), \"Service does not support named entrypoints\", serviceName);\n\n        // Try to apply props if the service supports it\n        if (params.hasProps()) {\n          targetService = service->forProps(kj::mv(props));\n        } else {\n          // No props, just use the service as-is\n          targetService = kj::addRef(*service);\n        }\n      }\n\n      // Return a WorkerdBootstrap that wraps this service using the generic implementation.\n      context.initResults(capnp::MessageSize{4, 1})\n          .setEntrypoint(\n              kj::heap<WorkerdBootstrapImpl>(kj::mv(targetService), httpOverCapnpFactory));\n      return kj::READY_NOW;\n    }\n\n    kj::Promise<void> getActor(GetActorContext context) override {\n      auto params = context.getParams();\n      auto serviceName = params.getService();\n      auto entrypointName = params.getEntrypoint();\n      auto actorIdStr = params.getActorId();\n\n      // Look up the service\n      auto& serviceEntry =\n          KJ_ASSERT_NONNULL(srv.services.find(serviceName), \"Service not found\", serviceName);\n      auto service = serviceEntry->service();\n\n      // Try to cast to WorkerService\n      auto* workerService = dynamic_cast<WorkerService*>(service);\n      KJ_REQUIRE(workerService != nullptr, \"Service does not support actors\", serviceName);\n\n      // Look up the actor namespace\n      auto& actorNamespace = KJ_ASSERT_NONNULL(workerService->getActorNamespace(entrypointName),\n          \"Actor namespace not found\", entrypointName);\n\n      // Create an actor ID - use the namespace config to determine if it's durable or ephemeral\n      Worker::Actor::Id actorId;\n      KJ_SWITCH_ONEOF(actorNamespace.getConfig()) {\n        KJ_CASE_ONEOF(c, Durable) {\n          // Durable Object ID (hex-encoded SHA256 hash)\n          auto decoded = kj::decodeHex(actorIdStr);\n          KJ_REQUIRE(decoded.size() == SHA256_DIGEST_LENGTH,\n              \"Invalid Durable Object ID: expected 64 hex characters (32 bytes)\", decoded.size());\n          kj::Own<ActorIdFactory::ActorId> id =\n              kj::heap<ActorIdFactoryImpl::ActorIdImpl>(decoded.begin(), kj::none);\n          actorId = kj::mv(id);\n        }\n        KJ_CASE_ONEOF(c, Ephemeral) {\n          // Ephemeral actor ID (plain string)\n          actorId = kj::str(actorIdStr);\n        }\n      }\n\n      // Wrap the actor channel using the generic WorkerdBootstrap implementation.\n      context.initResults(capnp::MessageSize{4, 1})\n          .setActor(kj::heap<WorkerdBootstrapImpl>(\n              actorNamespace.getActorChannel(kj::mv(actorId)), httpOverCapnpFactory));\n      return kj::READY_NOW;\n    }\n\n   private:\n    workerd::server::Server& srv;\n    capnp::HttpOverCapnpFactory& httpOverCapnpFactory;\n  };\n};\n\nkj::Promise<void> Server::listenDebugPort(kj::Own<kj::ConnectionReceiver> listener) {\n  DebugPortListener obj(*this, kj::mv(listener), globalContext->httpOverCapnpFactory);\n  co_return co_await obj.run();\n}\n\n// =======================================================================================\n// Server::run()\n\nkj::Promise<void> Server::handleDrain(kj::Promise<void> drainWhen) {\n  co_await drainWhen;\n  TRACE_EVENT(\"workerd\", \"Server::handleDrain()\");\n  // Tell all HttpServers to drain. This causes them to disconnect any connections that don't\n  // have a request in-flight.\n  for (auto& httpServer: httpServers) {\n    // The promise returned by `drain()` resolves when all connections have ended. But, we need\n    // the promise returned by handleDrain() to resolve immediately when draining has started,\n    // since that's what signals us to stop accepting incoming connections. So, we should not\n    // co_await the promise returned by `drain()`. Technically, we don't actually have to wait\n    // on it at all -- `drain()` returns the promise end of a promise-and-fulfiller, so simply\n    // dropping it won't actually cancel anything. But since that's not documented in drain()'s\n    // doc comment, we instead add the promise to `tasks` to be safe.\n    tasks.add(httpServer.httpServer.drain());\n  }\n}\n\nkj::Promise<void> Server::run(\n    jsg::V8System& v8System, config::Config::Reader config, kj::Promise<void> drainWhen) {\n  TRACE_EVENT(\"workerd\", \"Server.run\");\n\n  // Update logging settings from config (overridding structuredLogging when so)\n  if (config.hasLogging()) {\n    auto logging = config.getLogging();\n    loggingOptions.structuredLogging = StructuredLogging(logging.getStructuredLogging());\n    if (logging.hasStdoutPrefix()) {\n      loggingOptions.stdoutPrefix = kj::ConstString(kj::str(logging.getStdoutPrefix()));\n    }\n    if (logging.hasStderrPrefix()) {\n      loggingOptions.stderrPrefix = kj::ConstString(kj::str(logging.getStderrPrefix()));\n    }\n  } else {\n    loggingOptions.structuredLogging = StructuredLogging(config.getStructuredLogging());\n  }\n\n  kj::HttpHeaderTable::Builder headerTableBuilder;\n  globalContext = kj::heap<GlobalContext>(*this, v8System, headerTableBuilder);\n  invalidConfigServiceSingleton = kj::refcounted<InvalidConfigService>();\n  invalidConfigActorClassSingleton = kj::refcounted<InvalidConfigActorClass>();\n\n  auto [fatalPromise, fatalFulfiller] = kj::newPromiseAndFulfiller<void>();\n  this->fatalFulfiller = kj::mv(fatalFulfiller);\n\n  auto forkedDrainWhen = handleDrain(kj::mv(drainWhen)).fork();\n\n  co_await startServices(v8System, config, headerTableBuilder, forkedDrainWhen);\n\n  auto listenPromise = listenOnSockets(config, headerTableBuilder, forkedDrainWhen);\n\n  // We should have registered all headers synchronously. This is important because we want to\n  // be able to start handling requests as soon as the services are available, even if some other\n  // services take longer to get ready.\n  auto ownHeaderTable = headerTableBuilder.build();\n\n  co_return co_await listenPromise.exclusiveJoin(kj::mv(fatalPromise));\n}\n\nvoid Server::startAlarmScheduler(config::Config::Reader config) {\n  auto& clock = kj::systemPreciseCalendarClock();\n  auto dir = kj::newInMemoryDirectory(clock);\n  auto vfs = kj::heap<SqliteDatabase::Vfs>(*dir).attach(kj::mv(dir));\n\n  // TODO(someday): support persistent storage for alarms\n\n  alarmScheduler =\n      kj::heap<AlarmScheduler>(clock, timer, *vfs, kj::Path({\"alarms.sqlite\"})).attach(kj::mv(vfs));\n}\n\n// Configure and start the inspector socket, returning the port the socket started on.\nuint startInspector(\n    kj::StringPtr inspectorAddress, Server::InspectorServiceIsolateRegistrar& registrar) {\n  static constexpr uint UNASSIGNED_PORT = 0;\n  static constexpr uint DEFAULT_PORT = 9229;\n  kj::MutexGuarded<uint> inspectorPort(UNASSIGNED_PORT);\n\n  // `startInspector()` is called on the Isolate thread. V8 requires CPU profiling to be started and\n  // stopped on the same thread which executes JavaScript -- that is, the Isolate thread -- which\n  // means we need to dispatch inspector messages on this thread. To help make that happen, we\n  // capture this thread's kj::Executor here, and pass it into the InspectorService below. Later,\n  // when the InspectorService receives a WebSocket connection, it calls\n  // `Isolate::attachInspector()`, which uses the kj::Executor we create here to create a\n  // XThreadNotifier and start a dispatch loop. The InspectorService reads subsequent WebSocket\n  // inspector messages and feeds them to that dispatch loop via the XThreadNotifier.\n  auto isolateThreadExecutor = kj::getCurrentThreadExecutor().addRef();\n\n  // Start the InspectorService thread.\n  kj::Thread thread([inspectorAddress, &inspectorPort, &registrar,\n                        isolateThreadExecutor = kj::mv(isolateThreadExecutor)]() mutable {\n    kj::AsyncIoContext io = kj::setupAsyncIo();\n\n    kj::HttpHeaderTable::Builder headerTableBuilder;\n\n    // Create the special inspector service.\n    auto inspectorService(kj::heap<Server::InspectorService>(\n        kj::mv(isolateThreadExecutor), io.provider->getTimer(), headerTableBuilder, registrar));\n    auto ownHeaderTable = headerTableBuilder.build();\n\n    // Configure and start the inspector socket.\n\n    auto& network = io.provider->getNetwork();\n\n    // TODO(cleanup): There's an issue here that if listen fails, nothing notices. The\n    // server will continue running but will no longer accept inspector connections.\n    // This should be fixed by:\n    // 1. Replacing the kj::NEVER_DONE with listen\n    // 2. Making the thread's lambda `noexcept` so that if it throws the process crashes\n    // 3. Probably also throw if listen completes without an exception (even if unlikely to\n    //    happen)\n    auto listen = (kj::coCapture(\n        [&network, &inspectorAddress, &inspectorPort, &inspectorService]() -> kj::Promise<void> {\n      auto parsed = co_await network.parseAddress(inspectorAddress, DEFAULT_PORT);\n      auto listener = parsed->listen();\n      // EW-7716: Signal to thread that started the inspector service that the inspector is ready.\n      *inspectorPort.lockExclusive() = listener->getPort();\n      KJ_LOG(INFO, \"Inspector is listening\");\n      co_await inspectorService->listen(kj::mv(listener));\n    }))();\n\n    kj::NEVER_DONE.wait(io.waitScope);\n  });\n  thread.detach();\n\n  // EW-7716: Wait for the InspectorService instance to be initialized before proceeding.\n  return inspectorPort.when([](const uint& port) { return port != UNASSIGNED_PORT; },\n      [](const uint& port) { return port; });\n}\n\nkj::Promise<void> Server::preloadPython(\n    kj::StringPtr workerName, const WorkerDef& workerDef, ErrorReporter& errorReporter) {\n  if (workerDef.featureFlags.getPythonWorkers()) {\n    auto pythonRelease = getPythonSnapshotRelease(workerDef.featureFlags);\n    KJ_IF_SOME(release, pythonRelease) {\n      auto version = getPythonBundleName(release);\n\n      // Fetch the Pyodide bundle.\n      co_await server::fetchPyodideBundle(pythonConfig, kj::mv(version), network, timer);\n\n      // Preload Python packages.\n      KJ_IF_SOME(modulesSource, workerDef.source.variant.tryGet<Worker::Script::ModulesSource>()) {\n        if (modulesSource.isPython) {\n          auto pythonRequirements = getPythonRequirements(modulesSource);\n\n          // Store the packages in the package manager that is stored in the pythonConfig\n          co_await server::fetchPyodidePackages(pythonConfig, pythonConfig.pyodidePackageManager,\n              pythonRequirements, release, network, timer);\n        }\n      }\n    }\n  }\n}\n\nkj::Promise<void> Server::startServices(jsg::V8System& v8System,\n    config::Config::Reader config,\n    kj::HttpHeaderTable::Builder& headerTableBuilder,\n    kj::ForkedPromise<void>& forkedDrainWhen) {\n  // ---------------------------------------------------------------------------\n  // Configure services\n  TRACE_EVENT(\"workerd\", \"startServices\");\n\n  // First pass: Extract actor namespace configs.\n  for (auto serviceConf: config.getServices()) {\n    kj::StringPtr name = serviceConf.getName();\n    kj::HashMap<kj::String, ActorConfig> serviceActorConfigs;\n\n    if (serviceConf.isWorker()) {\n      auto workerConf = serviceConf.getWorker();\n      bool hadDurable = false;\n      for (auto ns: workerConf.getDurableObjectNamespaces()) {\n        switch (ns.which()) {\n          case config::Worker::DurableObjectNamespace::UNIQUE_KEY:\n            hadDurable = true;\n            serviceActorConfigs.insert(kj::str(ns.getClassName()),\n                Durable{.uniqueKey = kj::str(ns.getUniqueKey()),\n                  .isEvictable = !ns.getPreventEviction(),\n                  .enableSql = ns.getEnableSql(),\n                  .containerOptions = ns.hasContainer() ? kj::Maybe(ns.getContainer()) : kj::none});\n            continue;\n          case config::Worker::DurableObjectNamespace::EPHEMERAL_LOCAL:\n            if (!experimental) {\n              reportConfigError(kj::str(\n                  \"Ephemeral objects (Durable Object namespaces with type 'ephemeralLocal') are an \"\n                  \"experimental feature which may change or go away in the future. You must run \"\n                  \"workerd with `--experimental` to use this feature.\"));\n            }\n            serviceActorConfigs.insert(kj::str(ns.getClassName()),\n                Ephemeral{.isEvictable = !ns.getPreventEviction(), .enableSql = ns.getEnableSql()});\n            continue;\n        }\n        reportConfigError(kj::str(\"Encountered unknown DurableObjectNamespace type in service \\\"\",\n            name, \"\\\", class \\\"\", ns.getClassName(),\n            \"\\\". Was the config compiled with a newer version \"\n            \"of the schema?\"));\n      }\n\n      switch (workerConf.getDurableObjectStorage().which()) {\n        case config::Worker::DurableObjectStorage::NONE:\n          if (hadDurable) {\n            reportConfigError(kj::str(\"Worker service \\\"\", name,\n                \"\\\" implements durable object classes but has \"\n                \"`durableObjectStorage` set to `none`.\"));\n          }\n          goto validDurableObjectStorage;\n        case config::Worker::DurableObjectStorage::IN_MEMORY:\n        case config::Worker::DurableObjectStorage::LOCAL_DISK:\n          goto validDurableObjectStorage;\n      }\n      reportConfigError(kj::str(\"Encountered unknown durableObjectStorage type in service \\\"\", name,\n          \"\\\". Was the config compiled with a newer version of the schema?\"));\n\n    validDurableObjectStorage:\n      if (workerConf.hasDurableObjectUniqueKeyModifier()) {\n        // This should be implemented along with parameterized workers. It's not relevant\n        // otherwise, but let's make sure no one sets it accidentally.\n        KJ_UNIMPLEMENTED(\"durableObjectUniqueKeyModifier is not implemented yet\");\n      }\n    }\n\n    actorConfigs.upsert(kj::str(name), kj::mv(serviceActorConfigs), [&](auto&&...) {\n      reportConfigError(kj::str(\"Config defines multiple services named \\\"\", name, \"\\\".\"));\n    });\n  }\n\n  // If we are using the inspector, we need to register the Worker::Isolate\n  // with the inspector service.\n  KJ_IF_SOME(inspectorAddress, inspectorOverride) {\n    auto registrar = kj::heap<InspectorServiceIsolateRegistrar>();\n    auto port = startInspector(inspectorAddress, *registrar);\n    KJ_IF_SOME(stream, controlOverride) {\n      auto message = kj::str(\"{\\\"event\\\":\\\"listen-inspector\\\",\\\"port\\\":\", port, \"}\\n\");\n      try {\n        stream->write(message.asBytes());\n      } catch (kj::Exception& e) {\n        KJ_LOG(ERROR, e);\n      }\n    }\n    inspectorIsolateRegistrar = kj::mv(registrar);\n  }\n\n  // Second pass: Build services.\n  for (auto serviceConf: config.getServices()) {\n    kj::StringPtr name = serviceConf.getName();\n    auto service = co_await makeService(serviceConf, headerTableBuilder, config.getExtensions());\n\n    services.upsert(kj::str(name), kj::mv(service), [&](auto&&...) {\n      reportConfigError(kj::str(\"Config defines multiple services named \\\"\", name, \"\\\".\"));\n    });\n  }\n\n  // Make the default \"internet\" service if it's not there already.\n  services.findOrCreate(\"internet\"_kj, [&]() {\n    auto publicNetwork = network.restrictPeers({\"public\"_kj});\n\n    kj::TlsContext::Options options;\n    options.useSystemTrustStore = true;\n\n    kj::Own<kj::TlsContext> tls = kj::heap<kj::TlsContext>(kj::mv(options));\n    auto tlsNetwork = tls->wrapNetwork(*publicNetwork);\n\n    // Attaching to refcounted NetworkService is safe since services map is long-lived\n    auto service = kj::refcounted<NetworkService>(globalContext->headerTable, timer, entropySource,\n        kj::mv(publicNetwork), kj::mv(tlsNetwork), *tls)\n                       .attachToThisReference(kj::mv(tls));\n\n    return decltype(services)::Entry{kj::str(\"internet\"_kj), kj::mv(service)};\n  });\n\n  // Start the alarm scheduler before linking services\n  startAlarmScheduler(config);\n\n  // Third pass: Cross-link services.\n  for (auto& service: services) {\n    ConfigErrorReporter errorReporter(*this, service.key);\n    service.value->link(errorReporter);\n  }\n}\n\nkj::Promise<void> Server::listenOnSockets(config::Config::Reader config,\n    kj::HttpHeaderTable::Builder& headerTableBuilder,\n    kj::ForkedPromise<void>& forkedDrainWhen,\n    bool forTest) {\n  // ---------------------------------------------------------------------------\n  // Start sockets\n  TRACE_EVENT(\"workerd\", \"listenOnSockets\");\n  for (auto sock: config.getSockets()) {\n    kj::StringPtr name = sock.getName();\n    kj::StringPtr addrStr = nullptr;\n    kj::String ownAddrStr;\n    kj::Maybe<kj::Own<kj::ConnectionReceiver>> listenerOverride;\n\n    kj::Own<Service> service = lookupService(sock.getService(), kj::str(\"Socket \\\"\", name, \"\\\"\"));\n\n    KJ_IF_SOME(override, socketOverrides.findEntry(name)) {\n      KJ_SWITCH_ONEOF(override.value) {\n        KJ_CASE_ONEOF(str, kj::String) {\n          addrStr = ownAddrStr = kj::mv(str);\n          break;\n        }\n        KJ_CASE_ONEOF(l, kj::Own<kj::ConnectionReceiver>) {\n          listenerOverride = kj::mv(l);\n          break;\n        }\n      }\n      socketOverrides.erase(override);\n    } else if (sock.hasAddress()) {\n      addrStr = sock.getAddress();\n    } else {\n      reportConfigError(kj::str(\"Socket \\\"\", name,\n          \"\\\" has no address in the config, so must be specified on the \"\n          \"command line with `--socket-addr`.\"));\n      continue;\n    }\n\n    uint defaultPort = 0;\n    config::HttpOptions::Reader httpOptions;\n    kj::Maybe<kj::Own<kj::TlsContext>> tls;\n    kj::StringPtr physicalProtocol;\n    switch (sock.which()) {\n      case config::Socket::HTTP:\n        defaultPort = 80;\n        httpOptions = sock.getHttp();\n        physicalProtocol = \"http\";\n        goto validSocket;\n      case config::Socket::HTTPS: {\n        auto https = sock.getHttps();\n        defaultPort = 443;\n        httpOptions = https.getOptions();\n        tls = makeTlsContext(https.getTlsOptions());\n        physicalProtocol = \"https\";\n        goto validSocket;\n      }\n    }\n    reportConfigError(kj::str(\"Encountered unknown socket type in \\\"\", name,\n        \"\\\". Was the config compiled with a \"\n        \"newer version of the schema?\"));\n    continue;\n\n  validSocket:\n    using PromisedReceived = kj::Promise<kj::Own<kj::ConnectionReceiver>>;\n    PromisedReceived listener = nullptr;\n    KJ_IF_SOME(l, listenerOverride) {\n      listener = kj::mv(l);\n    } else {\n      listener = ([](kj::Promise<kj::Own<kj::NetworkAddress>> promise) -> PromisedReceived {\n        auto parsed = co_await promise;\n        co_return parsed->listen();\n      })(network.parseAddress(addrStr, defaultPort));\n    }\n\n    KJ_IF_SOME(t, tls) {\n      listener = ([](kj::Promise<kj::Own<kj::ConnectionReceiver>> promise,\n                      kj::Own<kj::TlsContext> tls) -> PromisedReceived {\n        auto port = co_await promise;\n        co_return tls->wrapPort(kj::mv(port)).attach(kj::mv(tls));\n      })(kj::mv(listener), kj::mv(t));\n    }\n\n    // Need to create rewriter before waiting on anything since `headerTableBuilder` will no longer\n    // be available later.\n    auto rewriter = kj::heap<HttpRewriter>(httpOptions, headerTableBuilder);\n\n    auto handle = kj::coCapture(\n        [this, service = kj::mv(service), rewriter = kj::mv(rewriter), physicalProtocol, name](\n            kj::Promise<kj::Own<kj::ConnectionReceiver>> promise) mutable -> kj::Promise<void> {\n      TRACE_EVENT(\"workerd\", \"setup listenHttp\");\n      auto listener = co_await promise;\n      KJ_IF_SOME(stream, controlOverride) {\n        auto message = kj::str(\"{\\\"event\\\":\\\"listen\\\",\\\"socket\\\":\\\"\", name,\n            \"\\\",\\\"port\\\":\", listener->getPort(), \"}\\n\");\n        try {\n          stream->write(message.asBytes());\n        } catch (kj::Exception& e) {\n          KJ_LOG(ERROR, e);\n        }\n      }\n      co_await listenHttp(kj::mv(listener), kj::mv(service), physicalProtocol, kj::mv(rewriter));\n    });\n    tasks.add(handle(kj::mv(listener)).exclusiveJoin(forkedDrainWhen.addBranch()));\n  }\n\n  // Start debug port if configured\n  KJ_IF_SOME(addr, debugPortOverride) {\n    auto handle = kj::coCapture(\n        [this, addr = kj::str(addr)](kj::ForkedPromise<void>& drain) mutable -> kj::Promise<void> {\n      auto parsed = co_await network.parseAddress(addr, 0);\n      auto listener = parsed->listen();\n\n      KJ_IF_SOME(stream, controlOverride) {\n        auto message = kj::str(\"{\\\"event\\\":\\\"listen\\\",\\\"socket\\\":\\\"debug-port\"\n                               \"\\\",\\\"port\\\":\",\n            listener->getPort(), \"}\\n\");\n        try {\n          stream->write(message.asBytes());\n        } catch (kj::Exception& e) {\n          KJ_LOG(ERROR, e);\n        }\n      }\n\n      co_await listenDebugPort(kj::mv(listener));\n    });\n    tasks.add(handle(forkedDrainWhen).exclusiveJoin(forkedDrainWhen.addBranch()));\n  }\n\n  for (auto& unmatched: socketOverrides) {\n    reportConfigError(kj::str(\"Config did not define any socket named \\\"\", unmatched.key,\n        \"\\\" to match the override \"\n        \"provided on the command line.\"));\n  }\n\n  for (auto& unmatched: externalOverrides) {\n    reportConfigError(kj::str(\"Config did not define any external service named \\\"\", unmatched.key,\n        \"\\\" to match the \"\n        \"override provided on the command line.\"));\n  }\n\n  for (auto& unmatched: directoryOverrides) {\n    if (forTest && unmatched.key == \"TEST_TMPDIR\") {\n      // Due to a historical bug, `workerd test` didn't check for the existence of unmatched\n      // overrides, and our own tests became dependent on the ability to override TEST_TMPDIR\n      // even if it was not used in the config. For now, we ignore this problem.\n      //\n      // TODO(cleanup): Figure out the right solution here.\n      continue;\n    }\n\n    reportConfigError(kj::str(\"Config did not define any disk service named \\\"\", unmatched.key,\n        \"\\\" to match the \"\n        \"override provided on the command line.\"));\n  }\n\n  co_await tasks.onEmpty();\n\n  // Give a chance for any errors to bubble up before we return success. In particular\n  // Server::taskFailed() fulfills `fatalFulfiller`, which causes the server to exit with an error.\n  // But the `TaskSet` may have become empty at the same time. We want the error to win the race\n  // against the success.\n  //\n  // TODO(cleanup): A better solution would be for `TaskSet` to have a new variant of the\n  //   `onEmpty()` method like `onEmptyOrException()`, which propagates any exception thrown by\n  //   any task.\n  co_await kj::yieldUntilQueueEmpty();\n}\n\n// =======================================================================================\n// Server::test()\n\nkj::Promise<bool> Server::test(jsg::V8System& v8System,\n    config::Config::Reader config,\n    kj::StringPtr servicePattern,\n    kj::StringPtr entrypointPattern) {\n\n  if (config.hasLogging()) {\n    auto logging = config.getLogging();\n    loggingOptions.structuredLogging = StructuredLogging(logging.getStructuredLogging());\n    if (logging.hasStdoutPrefix()) {\n      loggingOptions.stdoutPrefix = kj::ConstString(kj::str(logging.getStdoutPrefix()));\n    }\n    if (logging.hasStderrPrefix()) {\n      loggingOptions.stderrPrefix = kj::ConstString(kj::str(logging.getStderrPrefix()));\n    }\n  } else {\n    loggingOptions.structuredLogging = StructuredLogging(config.getStructuredLogging());\n  }\n\n  kj::HttpHeaderTable::Builder headerTableBuilder;\n  globalContext = kj::heap<GlobalContext>(*this, v8System, headerTableBuilder);\n  invalidConfigServiceSingleton = kj::refcounted<InvalidConfigService>();\n\n  auto [fatalPromise, fatalFulfiller] = kj::newPromiseAndFulfiller<void>();\n  this->fatalFulfiller = kj::mv(fatalFulfiller);\n\n  auto forkedDrainWhen = kj::Promise<void>(kj::NEVER_DONE).fork();\n\n  co_await startServices(v8System, config, headerTableBuilder, forkedDrainWhen);\n\n  // Tests usually do not configure sockets, but they can, especially loopback sockets. Arrange\n  // to wait on them. Crash if listening fails.\n  auto listenPromise =\n      listenOnSockets(config, headerTableBuilder, forkedDrainWhen,\n          /* forTest = */ true)\n          .eagerlyEvaluate([](kj::Exception&& e) noexcept { kj::throwFatalException(kj::mv(e)); });\n\n  auto ownHeaderTable = headerTableBuilder.build();\n\n  // TODO(someday): If the inspector is enabled, pause and wait for an inspector connection before\n  //   proceeding?\n\n  kj::GlobFilter serviceGlob(servicePattern);\n  kj::GlobFilter entrypointGlob(entrypointPattern);\n\n  uint passCount = 0, failCount = 0;\n\n  auto doTest = [&](Service& service, kj::StringPtr name) -> kj::Promise<void> {\n    // TODO(soon): Better way of reporting test results, KJ_LOG is ugly. We should probably have\n    //   some sort of callback interface. It would be nice to report the exceptions thrown through\n    //   that interface too... can we? Use a tracer maybe?\n    // HACK: We use DBG log level because INFO logging is optional, and warning/error would confuse\n    //   people. Note that server-test.c++ actually tests for this logging, so simply writing to\n    //   stderr wouldn't work.\n    KJ_LOG(DBG, kj::str(\"[ TEST ] \"_kj, name));\n    auto req = service.startRequest({});\n    auto start = monotonicClock.now();\n\n    bool result = co_await req->test();\n    if (result) {\n      ++passCount;\n    } else {\n      ++failCount;\n    }\n\n    auto end = monotonicClock.now();\n    auto duration = end - start;\n\n    KJ_LOG(DBG, kj::str(result ? \"[ PASS ] \"_kj : \"[ FAIL ] \"_kj, name, \" (\", duration, \")\"));\n  };\n\n  for (auto& service: services) {\n    if (serviceGlob.matches(service.key)) {\n      if (service.value->hasHandler(\"test\"_kj) && entrypointGlob.matches(\"default\"_kj)) {\n        co_await doTest(*service.value, service.key);\n      }\n\n      if (WorkerService* worker = dynamic_cast<WorkerService*>(service.value.get())) {\n        for (auto& name: worker->getEntrypointNames()) {\n          if (entrypointGlob.matches(name)) {\n            kj::Own<Service> ep = KJ_ASSERT_NONNULL(worker->getEntrypoint(name, /*props=*/{}));\n            if (ep->hasHandler(\"test\"_kj)) {\n              co_await doTest(*ep, kj::str(service.key, ':', name));\n            }\n          }\n        }\n      }\n    }\n  }\n\n  if (passCount + failCount == 0) {\n    KJ_LOG(ERROR, \"No tests found!\");\n  }\n\n  co_return passCount > 0 && failCount == 0;\n}\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/server.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"channel-token.h\"\n\n#include <workerd/api/memory-cache.h>\n#include <workerd/api/pyodide/pyodide.h>\n#include <workerd/io/worker.h>\n#include <workerd/server/alarm-scheduler.h>\n#include <workerd/server/workerd.capnp.h>\n\n#include <kj/async-io.h>\n#include <kj/compat/http.h>\n#include <kj/filesystem.h>\n#include <kj/map.h>\n#include <kj/one-of.h>\n\nnamespace kj {\nclass TlsContext;\n}\n\nnamespace workerd::jsg {\nclass V8System;\n}\n\nnamespace workerd::server {\n\nusing api::pyodide::PythonConfig;\n\n// Implements the single-tenant Workers Runtime server / CLI.\n//\n// The purpose of this class is to implement the core logic independently of the CLI itself,\n// in such a way that it can be unit-tested. workerd.c++ implements the CLI wrapper around this.\nclass Server final: private kj::TaskSet::ErrorHandler, private ChannelTokenHandler::Resolver {\n public:\n  Server(kj::Filesystem& fs,\n      kj::Timer& timer,\n      const kj::MonotonicClock& monotonicClock,\n      kj::Network& network,\n      kj::EntropySource& entropySource,\n      Worker::LoggingOptions loggingOptions,\n      kj::Function<void(kj::String)> reportConfigError);\n  ~Server() noexcept;\n\n  // Permit experimental features to be used. These features may break backwards compatibility\n  // in the future.\n  void allowExperimental() {\n    experimental = true;\n  }\n\n  void overrideSocket(kj::String name, kj::Own<kj::ConnectionReceiver> port) {\n    socketOverrides.upsert(kj::mv(name), kj::mv(port));\n  }\n  void overrideSocket(kj::String name, kj::String addr) {\n    socketOverrides.upsert(kj::mv(name), kj::mv(addr));\n  }\n  void overrideDirectory(kj::String name, kj::String path) {\n    directoryOverrides.upsert(kj::mv(name), kj::mv(path));\n  }\n  void overrideExternal(kj::String name, kj::String addr) {\n    externalOverrides.upsert(kj::mv(name), kj::mv(addr));\n  }\n  void enableInspector(kj::String addr) {\n    inspectorOverride = kj::mv(addr);\n  }\n  void enableControl(uint fd) {\n    controlOverride = kj::heap<kj::FdOutputStream>(fd);\n  }\n  void enableDebugPort(kj::String addr) {\n    debugPortOverride = kj::mv(addr);\n  }\n  void setPackageDiskCacheRoot(kj::Maybe<kj::Own<const kj::Directory>>&& dir) {\n    pythonConfig.packageDiskCacheRoot = kj::mv(dir);\n  }\n  void setPyodideDiskCacheRoot(kj::Maybe<kj::Own<const kj::Directory>>&& dir) {\n    pythonConfig.pyodideDiskCacheRoot = kj::mv(dir);\n  }\n  void setPythonCreateSnapshot() {\n    pythonConfig.createSnapshot = true;\n  }\n  void setPythonCreateBaselineSnapshot() {\n    pythonConfig.createBaselineSnapshot = true;\n  }\n  void setPythonLoadSnapshot(kj::String snapshot) {\n    pythonConfig.loadSnapshotFromDisk = kj::mv(snapshot);\n  }\n  void setPythonSnapshotDirectory(kj::Maybe<kj::Own<const kj::Directory>>&& dir) {\n    pythonConfig.snapshotDirectory = kj::mv(dir);\n  }\n\n  // Set the compatibility date to use for all workers. When set, workers in the config must NOT\n  // specify compatibilityDate (an error is reported if they do). This is used for testing to\n  // ensure tests run with both old and new compat dates.\n  void setTestCompatibilityDateOverride(kj::String date) {\n    testCompatibilityDateOverride = kj::mv(date);\n  }\n\n  // Runs the server using the given config.\n  kj::Promise<void> run(jsg::V8System& v8System,\n      config::Config::Reader conf,\n      kj::Promise<void> drainWhen = kj::NEVER_DONE);\n\n  // Executes one or more tests. By default, all exported test handlers from all entrypoints to\n  // all services in the config are executed. Glob patterns can be specified to match specific\n  // service and entrypoint names.\n  //\n  // The returned promise resolves true if at least one test ran and no tests failed.\n  kj::Promise<bool> test(jsg::V8System& v8System,\n      config::Config::Reader conf,\n      kj::StringPtr servicePattern = \"*\"_kj,\n      kj::StringPtr entrypointPattern = \"*\"_kj);\n\n  struct Durable {\n    kj::String uniqueKey;\n    bool isEvictable;\n    bool enableSql;\n    kj::Maybe<config::Worker::DurableObjectNamespace::ContainerOptions::Reader> containerOptions;\n  };\n  struct Ephemeral {\n    bool isEvictable;\n    bool enableSql;\n  };\n  using ActorConfig = kj::OneOf<Durable, Ephemeral>;\n\n  class InspectorService;\n  class InspectorServiceIsolateRegistrar;\n\n  void handleReportConfigError(kj::String error) {\n    reportConfigError(kj::mv(error));\n  }\n\n private:\n  kj::Filesystem& fs;\n  kj::Timer& timer;\n  // monotonicClock must produce time values consistent with those produced by timer whenever\n  // timer updates, but monotonicClock updates continuously (not just when system I/O is polled).\n  const kj::MonotonicClock& monotonicClock;\n  kj::Network& network;\n  kj::EntropySource& entropySource;\n  kj::Function<void(kj::String)> reportConfigError;\n  PythonConfig pythonConfig = PythonConfig{.packageDiskCacheRoot = kj::none,\n    .pyodideDiskCacheRoot = kj::none,\n    .createSnapshot = false,\n    .createBaselineSnapshot = false,\n    .loadSnapshotFromDisk = kj::none};\n\n  bool experimental = false;\n\n  // When set, overrides compatibilityDate for all workers and enforces that workers don't\n  // specify their own compatibilityDate.\n  kj::Maybe<kj::String> testCompatibilityDateOverride;\n\n  Worker::LoggingOptions loggingOptions;\n\n  kj::Own<api::MemoryCacheProvider> memoryCacheProvider;\n\n  ChannelTokenHandler channelTokenHandler;\n\n  kj::HashMap<kj::String, kj::OneOf<kj::String, kj::Own<kj::ConnectionReceiver>>> socketOverrides;\n  kj::HashMap<kj::String, kj::String> directoryOverrides;\n\n  // Overrides from the command line.\n  //\n  // String overrides are left as strings rather than parsed by the caller in order to reuse the\n  // code that parses strings from the config file.\n  kj::HashMap<kj::String, kj::String> externalOverrides;\n\n  kj::Maybe<kj::String> inspectorOverride;\n  kj::Maybe<kj::Own<InspectorServiceIsolateRegistrar>> inspectorIsolateRegistrar;\n  kj::Maybe<kj::Own<kj::FdOutputStream>> controlOverride;\n  kj::Maybe<kj::String> debugPortOverride;\n\n  struct GlobalContext;\n  // General context needed to construct workers. Initialized early in run().\n  kj::Own<GlobalContext> globalContext;\n\n  class Service;\n  kj::Own<Service> invalidConfigServiceSingleton;\n\n  class ActorClass;\n  kj::Own<ActorClass> invalidConfigActorClassSingleton;\n\n  // Information about all known actor namespaces. Maps serviceName -> className -> config.\n  // This needs to be populated in advance of constructing any services, in order to be able to\n  // correctly construct dependent services.\n  kj::HashMap<kj::String, kj::HashMap<kj::String, ActorConfig>> actorConfigs;\n\n  kj::HashMap<kj::String, kj::Own<Service>> services;\n\n  class WorkerLoaderNamespace;\n  kj::HashMap<kj::String, kj::Rc<WorkerLoaderNamespace>> workerLoaderNamespaces;\n  kj::Vector<kj::Rc<WorkerLoaderNamespace>> anonymousWorkerLoaderNamespaces;\n\n  kj::Own<kj::PromiseFulfiller<void>> fatalFulfiller;\n\n  // Initialized in startAlarmScheduler().\n  kj::Own<AlarmScheduler> alarmScheduler;\n\n  // An HttpServer object maintained in a linked list.\n  struct ListedHttpServer {\n    Server& owner;\n    kj::HttpServer httpServer;\n    kj::ListLink<ListedHttpServer> link;\n\n    template <typename... Params>\n    ListedHttpServer(Server& owner, Params&&... params)\n        : owner(owner),\n          httpServer(kj::fwd<Params>(params)...) {\n      owner.httpServers.add(*this);\n    };\n    ~ListedHttpServer() noexcept(false) {\n      owner.httpServers.remove(*this);\n    }\n  };\n\n  // All active HttpServer objects -- used to implement drain().\n  kj::List<ListedHttpServer, &ListedHttpServer::link> httpServers;\n\n  // Especially includes server loop tasks to listen on sockets. Any error is considered fatal.\n  kj::TaskSet tasks;\n\n  // Reports an exception thrown by a task in `tasks`.\n  void taskFailed(kj::Exception&& exception) override;\n\n  // Tell all HttpServers to drain once the drainWhen promise resolves.\n  // This causes them to disconnect any connections that do not have a\n  // request in flight.\n  kj::Promise<void> handleDrain(kj::Promise<void> drainWhen);\n\n  kj::Own<kj::TlsContext> makeTlsContext(config::TlsOptions::Reader conf);\n  kj::Promise<kj::Own<kj::NetworkAddress>> makeTlsNetworkAddress(config::TlsOptions::Reader conf,\n      kj::StringPtr addrStr,\n      kj::Maybe<kj::StringPtr> certificateHost,\n      uint defaultPort = 0);\n\n  class HttpRewriter;\n\n  kj::Own<Service> makeInvalidConfigService();\n  kj::Own<Service> makeExternalService(kj::StringPtr name,\n      config::ExternalServer::Reader conf,\n      kj::HttpHeaderTable::Builder& headerTableBuilder);\n  kj::Own<Service> makeNetworkService(config::Network::Reader conf);\n  kj::Own<Service> makeDiskDirectoryService(kj::StringPtr name,\n      config::DiskDirectory::Reader conf,\n      kj::HttpHeaderTable::Builder& headerTableBuilder);\n  kj::Promise<kj::Own<Service>> makeWorker(kj::StringPtr name,\n      config::Worker::Reader conf,\n      capnp::List<config::Extension>::Reader extensions);\n  kj::Promise<kj::Own<Service>> makeService(config::Service::Reader conf,\n      kj::HttpHeaderTable::Builder& headerTableBuilder,\n      capnp::List<config::Extension>::Reader extensions);\n\n  // Aborts all actors in this server except those in namespaces marked with `preventEviction`.\n  void abortAllActors(kj::Maybe<const kj::Exception&> reason);\n\n  // Can only be called in the link stage.\n  //\n  // May return a new object or may return a fake-own around a long-lived object.\n  kj::Own<Service> lookupService(\n      config::ServiceDesignator::Reader designator, kj::String errorContext);\n\n  // Like lookupService() but looks up an actor class (especially for use as a facet class).\n  // Returns none on a config error.\n  kj::Own<ActorClass> lookupActorClass(\n      config::ServiceDesignator::Reader designator, kj::String errorContext);\n\n  // Pretty similar to lookupService() and lookupActorClass(), but these callbacks are called by\n  // the `ChannelTokenHandler` when decoding tokens.\n  kj::Own<IoChannelFactory::SubrequestChannel> resolveEntrypoint(\n      kj::StringPtr serviceName, kj::Maybe<kj::StringPtr> entrypoint, Frankenvalue props) override;\n  kj::Own<IoChannelFactory::ActorClassChannel> resolveActorClass(\n      kj::StringPtr serviceName, kj::Maybe<kj::StringPtr> entrypoint, Frankenvalue props) override;\n\n  kj::Array<byte> encodeChannelToken(IoChannelFactory::ChannelTokenUsage usage,\n      kj::StringPtr serviceName,\n      kj::Maybe<kj::StringPtr> entrypoint,\n      Frankenvalue& props);\n\n  void decodeChannelToken(IoChannelFactory::ChannelTokenUsage usage,\n      kj::ArrayPtr<const byte> token,\n      kj::FunctionParam<void(\n          kj::StringPtr serviceName, kj::Maybe<kj::StringPtr> entrypoint, Frankenvalue props)>\n          callback);\n\n  kj::Promise<void> listenHttp(kj::Own<kj::ConnectionReceiver> listener,\n      kj::Own<Service> service,\n      kj::StringPtr physicalProtocol,\n      kj::Own<HttpRewriter> rewriter);\n\n  kj::Promise<void> listenDebugPort(kj::Own<kj::ConnectionReceiver> listener);\n\n  class InvalidConfigService;\n  class InvalidConfigActorClass;\n  class ExternalHttpService;\n  class ExternalTcpService;\n  class NetworkService;\n  class DiskDirectoryService;\n  class WorkerService;\n  class WorkerEntrypointService;\n  class WorkerdBootstrapImpl;\n  class HttpListener;\n  class DebugPortListener;\n\n  struct ErrorReporter;\n  struct ConfigErrorReporter;\n  struct DynamicErrorReporter;\n  struct WorkerDef;\n  kj::Promise<kj::Own<WorkerService>> makeWorkerImpl(kj::StringPtr name,\n      WorkerDef def,\n      capnp::List<config::Extension>::Reader extensions,\n      ErrorReporter& errorReporter);\n\n  kj::Promise<void> startServices(jsg::V8System& v8System,\n      config::Config::Reader config,\n      kj::HttpHeaderTable::Builder& headerTableBuilder,\n      kj::ForkedPromise<void>& forkedDrainWhen);\n\n  // Must be called after startServices!\n  void startAlarmScheduler(config::Config::Reader config);\n\n  kj::Promise<void> listenOnSockets(config::Config::Reader config,\n      kj::HttpHeaderTable::Builder& headerTableBuilder,\n      kj::ForkedPromise<void>& forkedDrainWhen,\n      bool forTest = false);\n\n  void unlinkWorkerLoaders();\n\n  kj::Promise<void> preloadPython(\n      kj::StringPtr workerName, const WorkerDef& workerDef, ErrorReporter& errorReporter);\n\n  friend struct FutureSubrequestChannel;\n  friend struct FutureActorClassChannel;\n};\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/tests/BUILD.bazel",
    "content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_library\")\n\njs_library(\n    name = \"server-harness_js_lib\",\n    srcs = [\"server-harness.mjs\"],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/workerd/server/tests/compile-tests/BUILD.bazel",
    "content": "load(\"@rules_shell//shell:sh_test.bzl\", \"sh_test\")\n\nsh_test(\n    name = \"helloworld_compile_test\",\n    size = \"medium\",\n    srcs = [\"compile-test.sh\"],\n    args = [\n        \"$(location //src/workerd/server:workerd)\",\n        \"$(location //samples:helloworld/config.capnp)\",\n        \"$(location compile-helloworld-test.ok)\",\n    ],\n    data = [\n        \"compile-helloworld-test.ok\",\n        \"//samples:helloworld/config.capnp\",\n        \"//samples:helloworld/worker.js\",\n        \"//src/workerd/server:workerd\",\n    ],\n    tags = [\"no-qemu\"],\n)\n"
  },
  {
    "path": "src/workerd/server/tests/compile-tests/compile-helloworld-test.ok",
    "content": "Hello World\n"
  },
  {
    "path": "src/workerd/server/tests/compile-tests/compile-test.sh",
    "content": "#!/bin/bash\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\n# $1 -> workerd binary path\n# $2 -> path to desired file to compile\n# $3 -> port to curl\n# $4 -> desired output\n\n# Help Function\nfunction show_help() {\n  echo \"\nThe Compile Test script is designed to aid in testing compiled workerd binaries.\nIt works by verifying that the result of a curl command on a running compiled binary matches an expected output.\nusage: compile-test.sh [-d] [-h] <workerd command> <file-to-compile> <port-to-curl> <expected-output-file>\n  options:\n    -d print out where tmp files are created and do not delete them\n    -h this help message\n\n  Note: all flags must occur before arguments\n\"\n}\n\nwhile getopts \"h\" option; do\n  case ${option} in\n    h)\n      show_help\n      exit\n      ;;\n  esac\ndone\n\nshift $(expr $OPTIND - 1)\nWORKERD_BINARY=$1\nCAPNP_SOURCE=$2\nEXPECTED=$3\n\nCAPNP_BINARY=$(mktemp)\nPORT_FILE=$(mktemp)\n\n# Compile the app\n$WORKERD_BINARY compile $CAPNP_SOURCE > $CAPNP_BINARY\n\n# Run the app\n$CAPNP_BINARY -shttp=localhost:0 --control-fd=1 > $PORT_FILE &\nKILL=$!\n\n# Make intermediate files before trying to wait on the bindings\nOUTPUT=$(mktemp)\nFIXED_OUTPUT=$(mktemp)\nFIXED_EXPECTED=$(mktemp)\n\n# Wait on the port bindings to occur\nwhile ! grep \\\"socket\\\"\\:\\\"http\\\" $PORT_FILE; do\n  sleep .1\ndone\n\n# Identify the port chosen by the binary\nPORT=`grep \\\"socket\\\"\\:\\\"http\\\" $PORT_FILE | sed 's/^.*\\\"port\\\"://g' | sed 's/\\}//g' |head -n 1`\n\n# Request output\ncurl localhost:$PORT -o $OUTPUT\n\n# Compare the tests to the expected output\nsed -e's/[[:space:]]*$//' $OUTPUT > $FIXED_OUTPUT\nsed -e's/[[:space:]]*$//' $EXPECTED > $FIXED_EXPECTED\ndiff $FIXED_OUTPUT $FIXED_EXPECTED\n\n# Clean up running workerd\nkill -9 $KILL\n\n# Clean up temp files\nrm -f $CAPNP_BINARY\nrm -f $PORT_FILE\nrm -f $OUTPUT\nrm -f $FIXED_OUTPUT\nrm -f $FIXED_EXPECTED\n"
  },
  {
    "path": "src/workerd/server/tests/container-client/BUILD.bazel",
    "content": "load(\"//:build/wd_test.bzl\", \"wd_test\")\n\nwd_test(\n    size = \"enormous\",\n    src = \"container-client.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"test.js\"],\n    tags = [\n        \"requires-container-engine\",\n        \"requires-network\",  # Accesses unix://var/run/docker.sock\n    ],\n)\n"
  },
  {
    "path": "src/workerd/server/tests/container-client/README.md",
    "content": "## container-client test\n\nTo run the tests:\n\n1. Make sure Docker is installed and running correctly\n\n   ```shell\n   docker ps\n   ```\n\n2. Use correct Docker context\n\n   ```shell\n   docker context use default\n   ```\n\n3. Remove existing containers labeled as \"cf-container-client-test\"\n\n   ```shell\n   docker ps -aq --filter name=workerd-container-client-test | xargs -r docker rm -f\n   ```\n\n4. Remove existing Docker image\n\n   ```shell\n   docker image rm cf-container-client-test\n   ```\n\n5. Build Docker images\n\n   ```shell\n   bazel run //images:load_all\n   ```\n\n6. Run the test\n\n   ```shell\n   just stream-test //src/workerd/server/tests/container-client:container-client@\n   ```\n"
  },
  {
    "path": "src/workerd/server/tests/container-client/container-client.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"internet\", network = ( allow = [\"private\"] ) ),\n    ( name = \"container-client-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"test.js\")\n        ],\n        compatibilityFlags = [\"enable_ctx_exports\", \"nodejs_compat\", \"experimental\", \"containers_pid_namespace\"],\n        containerEngine = (localDocker = (socketPath = \"unix:/var/run/docker.sock\", containerEgressInterceptorImage = \"cloudflare/proxy-everything:main\")),\n        durableObjectNamespaces = [\n          ( className = \"DurableObjectExample\",\n            uniqueKey = \"container-client-test-DurableObjectExample\",\n            container = (imageName = \"cloudflare/workerd/container-client-test\") ),\n          ( className = \"DurableObjectExample2\",\n            uniqueKey = \"container-client-test-DurableObjectExample2\",\n            container = (imageName = \"cloudflare/workerd/container-client-test\") ),\n        ],\n        durableObjectStorage = (localDisk = \"TEST_TMPDIR\"),\n        bindings = [\n          ( name = \"MY_CONTAINER\", durableObjectNamespace = \"DurableObjectExample\" ),\n          ( name = \"MY_DUPLICATE_CONTAINER\", durableObjectNamespace = \"DurableObjectExample2\" ),\n        ],\n      )\n    ),\n    ( name = \"TEST_TMPDIR\", disk = (writable = true) ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/container-client/test.js",
    "content": "import { DurableObject, WorkerEntrypoint } from 'cloudflare:workers';\nimport assert from 'node:assert';\nimport { scheduler } from 'node:timers/promises';\n\n// 5s timeout for some of the requests going to the container.\n// We can get to have a stack trace with an\n// abort signal.\nconst DEFAULT_TIMEOUT_DURATION = 10_000;\n\n// Use a unique DO name per test invocation because different test flavors may\n// run concurrently, and this avoids them accidentally sharing the same object.\nfunction getRandomDurableObjectName(name) {\n  return `${name}-${crypto.randomUUID()}`;\n}\n\n// **IMPORTANT NOTE**\n//\n// When writing a test, don't forget to call waitUntilContainerIsHealthy\n// before testing the behaviour with your container.\n//\n// Don't forget to call monitor() after calling start(), as there\n// is an issue with not calling monitor() in Durable Objects where\n// we might lose track of the container lifetime.\n//\n\nexport class DurableObjectExample extends DurableObject {\n  async testExitCode() {\n    const container = this.ctx.container;\n    if (container.running) {\n      let monitor = container.monitor().catch((_err) => {});\n      await container.destroy();\n      await monitor;\n    }\n    assert.strictEqual(container.running, false);\n\n    // Start container with invalid entrypoint\n    {\n      container.start({\n        entrypoint: ['node', 'nonexistant.js'],\n      });\n\n      let exitCode = undefined;\n      await container.monitor().catch((err) => {\n        exitCode = err.exitCode;\n      });\n\n      assert.strictEqual(typeof exitCode, 'number');\n      assert.notEqual(0, exitCode);\n    }\n\n    // Start container with valid entrypoint and stop it\n    {\n      container.start();\n\n      await scheduler.wait(500);\n\n      let exitCode = undefined;\n      const monitor = container.monitor().catch((err) => {\n        exitCode = err.exitCode;\n      });\n      await container.destroy();\n      await monitor;\n\n      assert.strictEqual(typeof exitCode, 'number');\n      assert.equal(137, exitCode);\n    }\n  }\n\n  async testBasics() {\n    const container = this.ctx.container;\n    if (container.running) {\n      let monitor = container.monitor().catch((_err) => {});\n\n      await container.destroy();\n      await monitor;\n    }\n\n    assert.strictEqual(container.running, false);\n\n    // Start container with valid configuration\n    container.start({\n      env: { A: 'B', C: 'D', L: 'F' },\n      enableInternet: true,\n    });\n\n    const monitor = container.monitor().catch((_err) => {});\n\n    await this.waitUntilContainerIsHealthy();\n\n    await container.destroy();\n\n    await monitor;\n    assert.strictEqual(container.running, false);\n  }\n\n  async testSetInactivityTimeout(timeout) {\n    const container = this.ctx.container;\n    if (container.running) {\n      let monitor = container.monitor().catch((_err) => {});\n      await container.destroy();\n      await monitor;\n    }\n    assert.strictEqual(container.running, false);\n\n    container.start();\n\n    assert.strictEqual(container.running, true);\n\n    // Wait for container to be running\n    await scheduler.wait(500);\n\n    try {\n      await container.setInactivityTimeout(0);\n    } catch (err) {\n      assert.strictEqual(err.name, 'TypeError');\n      assert.match(\n        err.message,\n        /setInactivityTimeout\\(\\) cannot be called with a durationMs <= 0/\n      );\n    }\n\n    if (timeout > 0) {\n      await container.setInactivityTimeout(timeout);\n    }\n  }\n\n  async start() {\n    assert.strictEqual(this.ctx.container.running, false);\n    this.ctx.container.start();\n    assert.strictEqual(this.ctx.container.running, true);\n\n    // Wait for container to be running\n    await scheduler.wait(500);\n  }\n\n  // Assert that the container is running\n  async expectRunning(running) {\n    assert.strictEqual(this.ctx.container.running, running);\n    await this.ctx.container.destroy();\n  }\n\n  async abort() {\n    await this.ctx.storage.put('aborted', true);\n    await this.ctx.storage.sync();\n    this.ctx.abort();\n  }\n\n  async alarm() {\n    const alarmValue = (await this.ctx.storage.get('alarm')) ?? 0;\n\n    const aborted = await this.ctx.storage.get('aborted');\n    assert.strictEqual(!!this.ctx.container, true);\n    if (aborted) {\n      await this.ctx.storage.put('aborted-confirmed', true);\n    }\n\n    await this.ctx.storage.put('alarm', alarmValue + 1);\n  }\n\n  async getAlarmIndex() {\n    return (await this.ctx.storage.get('alarm')) ?? 0;\n  }\n\n  async startAlarm(start, ms) {\n    if (start && !this.ctx.container.running) {\n      this.ctx.container.start();\n    }\n    await this.ctx.storage.setAlarm(Date.now() + ms);\n  }\n\n  async checkAlarmAbortConfirmation() {\n    const abortConfirmation = await this.ctx.storage.get('aborted-confirmed');\n    if (!abortConfirmation) {\n      throw new Error(\n        `Abort confirmation did not get inserted: ${abortConfirmation}`\n      );\n    }\n  }\n\n  async testWs() {\n    const { container } = this.ctx;\n\n    if (!container.running) {\n      container.start({\n        env: { WS_ENABLED: 'true' },\n        enableInternet: true,\n      });\n    }\n\n    await this.waitUntilContainerIsHealthy();\n\n    const res = await container.getTcpPort(8080).fetch('http://foo/ws', {\n      headers: {\n        Upgrade: 'websocket',\n        Connection: 'Upgrade',\n        'Sec-WebSocket-Key': 'x3JJHMbDL1EzLkh9GBhXDw==',\n        'Sec-WebSocket-Version': '13',\n      },\n      signal: AbortSignal.timeout(DEFAULT_TIMEOUT_DURATION),\n    });\n\n    // Should get WebSocket upgrade response\n    assert.strictEqual(res.status, 101);\n    assert.strictEqual(res.headers.get('upgrade'), 'websocket');\n    assert.strictEqual(!!res.webSocket, true);\n\n    // Test basic WebSocket communication\n    const ws = res.webSocket;\n    ws.accept();\n\n    // Listen for response\n    const messagePromise = new Promise((resolve) => {\n      ws.addEventListener(\n        'message',\n        (event) => {\n          resolve(event.data);\n        },\n        { once: true }\n      );\n    });\n\n    // Send a test message\n    ws.send('Hello WebSocket!');\n\n    assert.strictEqual(await messagePromise, 'Echo: Hello WebSocket!');\n\n    ws.close();\n    await container.destroy();\n  }\n\n  getStatus() {\n    return this.ctx.container.running;\n  }\n\n  async waitUntilContainerIsHealthy() {\n    const container = this.ctx.container;\n    {\n      let resp;\n      // The retry count here is arbitrary. Can increase it if necessary.\n      const maxRetries = 15;\n      for (let i = 1; i <= maxRetries; i++) {\n        try {\n          resp = await container.getTcpPort(8080).fetch('http://foo/bar/baz', {\n            method: 'POST',\n            body: 'hello',\n            signal: AbortSignal.timeout(DEFAULT_TIMEOUT_DURATION),\n          });\n          break;\n        } catch (e) {\n          if (!e.message.includes('Container is not listening to port 8080')) {\n            console.error(\n              'Error querying getTcpPort().fetch() that is not related to the container not listening yet',\n              e.message\n            );\n\n            throw e;\n          }\n\n          if (i === maxRetries) {\n            console.error(\n              `Failed to connect to container ${container.id}. Retried ${i} times`\n            );\n            throw e;\n          }\n\n          await scheduler.wait(500);\n        }\n      }\n\n      assert.equal(resp.status, 200);\n      assert.equal(resp.statusText, 'OK');\n      assert.strictEqual(await resp.text(), 'Hello World!');\n    }\n  }\n\n  async fetchIntercept(host) {\n    return await this.ctx.container\n      .getTcpPort(8080)\n      .fetch('http://foo/intercept', {\n        headers: { 'x-host': host },\n        signal: AbortSignal.timeout(DEFAULT_TIMEOUT_DURATION),\n      });\n  }\n\n  async expectIntercept(host, expectedStatus, expectedBody) {\n    const response = await this.fetchIntercept(host);\n    assert.equal(response.status, expectedStatus);\n    assert.equal(await response.text(), expectedBody);\n  }\n\n  async testPortNotListening() {\n    const container = this.ctx.container;\n    if (container.running) {\n      const monitor = container.monitor().catch((_err) => {});\n      await container.destroy();\n      await monitor;\n    }\n\n    container.start();\n    const monitor = container.monitor().catch((_err) => {});\n    await this.waitUntilContainerIsHealthy();\n\n    await assert.rejects(\n      container.getTcpPort(8081).fetch('http://foo/bar', {\n        signal: AbortSignal.timeout(DEFAULT_TIMEOUT_DURATION),\n      }),\n      /Container is not listening to port 8081/\n    );\n\n    await container.destroy();\n    await monitor;\n  }\n\n  async testLabels() {\n    const container = this.ctx.container;\n    if (container.running) {\n      let monitor = container.monitor().catch((_err) => {});\n      await container.destroy();\n      await monitor;\n    }\n\n    assert.strictEqual(container.running, false);\n\n    container.start({\n      enableInternet: true,\n      labels: { team: 'workers', environment: 'testing' },\n    });\n\n    const monitor = container.monitor().catch((_err) => {});\n    await this.waitUntilContainerIsHealthy();\n\n    assert.strictEqual(container.running, true);\n\n    await container.destroy();\n    await monitor;\n    assert.strictEqual(container.running, false);\n  }\n\n  async testLabelValidation() {\n    const container = this.ctx.container;\n    if (container.running) {\n      let monitor = container.monitor().catch((_err) => {});\n      await container.destroy();\n      await monitor;\n    }\n\n    assert.strictEqual(container.running, false);\n\n    // Empty label name\n    assert.throws(() => container.start({ labels: { '': 'value' } }), {\n      message: /Label names cannot be empty/,\n    });\n\n    // Label name with control character\n    assert.throws(\n      () => container.start({ labels: { 'bad\\x01name': 'value' } }),\n      { message: /Label names cannot contain control characters \\(index 0\\)/ }\n    );\n\n    // Label value with control character\n    assert.throws(() => container.start({ labels: { name: 'bad\\x01value' } }), {\n      message: /Label values cannot contain control characters \\(index 0\\)/,\n    });\n  }\n\n  async testPidNamespace() {\n    const container = this.ctx.container;\n    if (container.running) {\n      let monitor = container.monitor().catch((_err) => {});\n      await container.destroy();\n      await monitor;\n    }\n\n    assert.strictEqual(container.running, false);\n\n    container.start({\n      enableInternet: true,\n    });\n\n    const monitor = container.monitor().catch((_err) => {});\n    await this.waitUntilContainerIsHealthy();\n\n    const resp = await container\n      .getTcpPort(8080)\n      .fetch('http://foo/pid-namespace', {\n        signal: AbortSignal.timeout(DEFAULT_TIMEOUT_DURATION),\n      });\n\n    assert.equal(resp.status, 200);\n    const data = await resp.json();\n\n    await container.destroy();\n    await monitor;\n    assert.strictEqual(container.running, false);\n\n    return data;\n  }\n\n  async testSetEgressHttpWithInternet() {\n    const container = this.ctx.container;\n    if (container.running) {\n      let monitor = container.monitor().catch((_err) => {});\n      await container.destroy();\n      await monitor;\n    }\n\n    container.start({ enableInternet: true });\n\n    await this.waitUntilContainerIsHealthy();\n\n    await container.interceptOutboundHttp(\n      'googlefakedomain.com',\n      this.ctx.exports.TestService({ props: { id: 2 } })\n    );\n\n    await this.expectIntercept(\n      'googlefakedomain.com',\n      200,\n      'hello binding: 2 http://googlefakedomain.com/'\n    );\n\n    await this.expectIntercept(\n      'googlefakedomainother.com',\n      500,\n      'googlefakedomainother.com fetch failed'\n    );\n\n    await container.interceptAllOutboundHttp(\n      this.ctx.exports.TestService({ props: { id: 5 } })\n    );\n\n    await this.expectIntercept(\n      'google.com',\n      200,\n      'hello binding: 5 http://google.com/'\n    );\n  }\n\n  async testSetEgressHttpNoInternet() {\n    const container = this.ctx.container;\n\n    if (!container.running) container.start();\n\n    // wait for container to be available\n    await this.waitUntilContainerIsHealthy();\n\n    await container.interceptOutboundHttp(\n      'google.com',\n      this.ctx.exports.TestService({ props: { id: 2 } })\n    );\n\n    await this.expectIntercept(\n      'google.com',\n      200,\n      'hello binding: 2 http://google.com/'\n    );\n\n    // This should fail as there is no hostname that matches it.\n    await this.expectIntercept('google2.com', 500, 'google2.com fetch failed');\n\n    await container.interceptOutboundHttp(\n      'google2.com',\n      this.ctx.exports.TestService({ props: { id: 4 } })\n    );\n\n    await this.expectIntercept(\n      'google.com',\n      200,\n      'hello binding: 2 http://google.com/'\n    );\n    await this.expectIntercept(\n      'google2.com',\n      200,\n      'hello binding: 4 http://google2.com/'\n    );\n\n    // From now on, all hostnames resolve to Workerd.\n    await container.interceptAllOutboundHttp(\n      this.ctx.exports.TestService({ props: { id: 6 } })\n    );\n\n    await this.expectIntercept(\n      'google.com',\n      200,\n      'hello binding: 2 http://google.com/'\n    );\n\n    await this.expectIntercept(\n      'google2.com',\n      200,\n      'hello binding: 4 http://google2.com/'\n    );\n\n    await this.expectIntercept(\n      'google3.com',\n      200,\n      'hello binding: 6 http://google3.com/'\n    );\n\n    await this.expectIntercept(\n      '1.1.1.1',\n      200,\n      'hello binding: 6 http://1.1.1.1/'\n    );\n\n    await this.expectIntercept('1.1.1.1:90', 500, '1.1.1.1:90 fetch failed');\n    await this.expectIntercept(\n      'google.com:9000',\n      500,\n      'google.com:9000 fetch failed'\n    );\n  }\n\n  async testSetEgressHttp() {\n    const container = this.ctx.container;\n\n    // Set up egress TCP mapping to route requests to the binding\n    // We can configure this even before the container starts.\n    await container.interceptOutboundHttp(\n      '1.2.3.4',\n      this.ctx.exports.TestService({ props: { id: 1234 } })\n    );\n\n    if (!container.running) container.start();\n\n    // Keep container alive after abort();\n    container.monitor().catch((err) => {\n      console.error('Container exited with an error:', err.message);\n    });\n\n    // wait for container to be available\n    await this.waitUntilContainerIsHealthy();\n\n    // Set up egress TCP mapping to route requests to the binding\n    // This registers the binding's channel token with the container runtime\n    await container.interceptOutboundHttp(\n      '11.0.0.1:9999',\n      this.ctx.exports.TestService({ props: { id: 1 } })\n    );\n\n    await container.interceptOutboundHttp(\n      '11.0.0.2:9999',\n      this.ctx.exports.TestService({ props: { id: 2 } })\n    );\n\n    // we catch all http requests to port 80\n    await container.interceptAllOutboundHttp(\n      this.ctx.exports.TestService({ props: { id: 3 } })\n    );\n\n    {\n      const response = await container\n        .getTcpPort(8080)\n        .fetch('http://foo/intercept', {\n          headers: { 'x-host': '1.2.3.4:80' },\n          signal: AbortSignal.timeout(DEFAULT_TIMEOUT_DURATION),\n        });\n      assert.equal(response.status, 200);\n      assert.equal(\n        await response.text(),\n        'hello binding: 1234 http://1.2.3.4/'\n      );\n    }\n\n    {\n      const response = await container\n        .getTcpPort(8080)\n        .fetch('http://foo/intercept', {\n          headers: { 'x-host': '11.0.0.1:9999' },\n          signal: AbortSignal.timeout(DEFAULT_TIMEOUT_DURATION),\n        });\n      assert.equal(response.status, 200);\n      assert.equal(\n        await response.text(),\n        'hello binding: 1 http://11.0.0.1:9999/'\n      );\n    }\n\n    {\n      const response = await container\n        .getTcpPort(8080)\n        .fetch('http://foo/intercept', {\n          headers: { 'x-host': '11.0.0.2:9999' },\n          signal: AbortSignal.timeout(DEFAULT_TIMEOUT_DURATION),\n        });\n      assert.equal(response.status, 200);\n      assert.equal(\n        await response.text(),\n        'hello binding: 2 http://11.0.0.2:9999/'\n      );\n    }\n\n    {\n      const response = await container\n        .getTcpPort(8080)\n        .fetch('http://foo/intercept', {\n          headers: { 'x-host': '15.0.0.2:80' },\n          signal: AbortSignal.timeout(DEFAULT_TIMEOUT_DURATION),\n        });\n      assert.equal(response.status, 200);\n      assert.equal(await response.text(), 'hello binding: 3 http://15.0.0.2/');\n    }\n\n    {\n      const response = await container\n        .getTcpPort(8080)\n        .fetch('http://foo/intercept', {\n          headers: { 'x-host': '[111::]:80' },\n          signal: AbortSignal.timeout(DEFAULT_TIMEOUT_DURATION),\n        });\n      assert.equal(response.status, 200);\n      assert.equal(await response.text(), 'hello binding: 3 http://[111::]/');\n    }\n\n    {\n      const response = await container\n        .getTcpPort(8080)\n        .fetch('http://foo/intercept', {\n          headers: { 'x-host': 'google.com/hello/world' },\n          signal: AbortSignal.timeout(DEFAULT_TIMEOUT_DURATION),\n        });\n      assert.equal(response.status, 200);\n      assert.equal(\n        await response.text(),\n        'hello binding: 3 http://google.com/hello/world'\n      );\n    }\n\n    // test we can set another TestService\n    await container.interceptAllOutboundHttp(\n      this.ctx.exports.TestService({ props: { id: 1212 } })\n    );\n\n    {\n      // We preserved the order...\n      const response = await container\n        .getTcpPort(8080)\n        .fetch('http://foo/intercept', {\n          headers: { 'x-host': '11.0.0.2:9999' },\n          signal: AbortSignal.timeout(DEFAULT_TIMEOUT_DURATION),\n        });\n      assert.equal(response.status, 200);\n      assert.equal(\n        await response.text(),\n        'hello binding: 2 http://11.0.0.2:9999/'\n      );\n    }\n\n    {\n      // and we updated the id, even for existing connections\n      const response = await container\n        .getTcpPort(8080)\n        .fetch('http://foo/intercept', {\n          headers: { 'x-host': '15.0.0.2:80' },\n          signal: AbortSignal.timeout(DEFAULT_TIMEOUT_DURATION),\n        });\n      assert.equal(response.status, 200);\n      assert.equal(\n        await response.text(),\n        'hello binding: 1212 http://15.0.0.2/'\n      );\n    }\n\n    {\n      // and we updated the id for new connections\n      const response = await container\n        .getTcpPort(8080)\n        .fetch('http://foo/intercept', {\n          headers: { 'x-host': '15.0.0.55:80' },\n          signal: AbortSignal.timeout(DEFAULT_TIMEOUT_DURATION),\n        });\n      assert.equal(response.status, 200);\n      assert.equal(\n        await response.text(),\n        'hello binding: 1212 http://15.0.0.55/'\n      );\n    }\n  }\n\n  async testInterceptWebSocket() {\n    const container = this.ctx.container;\n    if (container.running) {\n      const monitor = container.monitor().catch((_err) => {});\n      await container.destroy();\n      await monitor;\n    }\n\n    assert.strictEqual(container.running, false);\n    // Set up egress mapping to route WebSocket requests to the binding\n    await container.interceptOutboundHttp(\n      '11.0.0.1:9999',\n      this.ctx.exports.TestService({ props: { id: 42 } })\n    );\n\n    // Start container with WebSocket proxy mode enabled\n    container.start({\n      env: { WS_ENABLED: 'true', WS_PROXY_TARGET: '11.0.0.1:9999' },\n    });\n\n    container.monitor().catch((_err) => {});\n\n    // Wait for container to be available\n    await this.waitUntilContainerIsHealthy();\n\n    assert.strictEqual(container.running, true);\n\n    // Connect to container's /ws endpoint which proxies to the intercepted address\n    // Flow: DO -> container:8080/ws -> container connects to 11.0.0.1:9999/ws\n    //       -> sidecar intercepts -> workerd -> TestService worker binding\n    const res = await container.getTcpPort(8080).fetch('http://foo/ws', {\n      headers: {\n        Upgrade: 'websocket',\n        Connection: 'Upgrade',\n        'Sec-WebSocket-Key': 'x3JJHMbDL1EzLkh9GBhXDw==',\n        'Sec-WebSocket-Version': '13',\n      },\n      signal: AbortSignal.timeout(DEFAULT_TIMEOUT_DURATION),\n    });\n\n    // Should get WebSocket upgrade response\n    assert.strictEqual(res.status, 101);\n    assert.strictEqual(res.headers.get('upgrade'), 'websocket');\n    assert.strictEqual(!!res.webSocket, true);\n\n    const ws = res.webSocket;\n    ws.binaryType = 'arraybuffer';\n    ws.accept();\n\n    // Listen for response\n    const { promise, resolve, reject } = Promise.withResolvers();\n\n    ws.addEventListener(\n      'message',\n      (event) => {\n        resolve(event.data);\n      },\n      { once: true }\n    );\n\n    const timeout = setTimeout(() => {\n      reject(new Error('Websocket message not received within 5 seconds'));\n    }, 5_000);\n\n    // Send a test message - should go through the whole chain and come back\n    ws.send('Hello through intercept!');\n\n    // Should receive response from TestService binding with id 42\n    const response = new TextDecoder().decode(await promise);\n    clearTimeout(timeout);\n    assert.strictEqual(response, 'Binding 42: Hello through intercept!');\n\n    ws.close();\n    await container.destroy();\n  }\n}\n\nexport class TestService extends WorkerEntrypoint {\n  fetch(request) {\n    // Check if this is a WebSocket upgrade request\n    const upgradeHeader = request.headers.get('Upgrade');\n    if (upgradeHeader && upgradeHeader.toLowerCase() === 'websocket') {\n      // Handle WebSocket upgrade\n      const [client, server] = Object.values(new WebSocketPair());\n\n      server.binaryType = 'arraybuffer';\n      server.accept();\n\n      server.addEventListener('message', (event) => {\n        // Echo back with binding id prefix\n        server.send(\n          `Binding ${this.ctx.props.id}: ${new TextDecoder().decode(event.data)}`\n        );\n      });\n\n      return new Response(null, {\n        status: 101,\n        webSocket: client,\n      });\n    }\n\n    // Regular HTTP request\n    return new Response(\n      'hello binding: ' + this.ctx.props.id + ' ' + request.url\n    );\n  }\n}\n\nexport class DurableObjectExample2 extends DurableObjectExample {}\n\n// Test basic container status\nexport const testStatus = {\n  async test(_ctrl, env) {\n    for (const CONTAINER of [env.MY_CONTAINER, env.MY_DUPLICATE_CONTAINER]) {\n      for (const name of ['testStatus', 'testStatus2']) {\n        const id = CONTAINER.idFromName(getRandomDurableObjectName(name));\n        const stub = CONTAINER.get(id);\n        assert.strictEqual(await stub.getStatus(), false);\n      }\n    }\n  },\n};\n\n// Test basic container functionality\nexport const testBasics = {\n  async test(_ctrl, env) {\n    for (const CONTAINER of [env.MY_CONTAINER, env.MY_DUPLICATE_CONTAINER]) {\n      const id = CONTAINER.idFromName(getRandomDurableObjectName('testBasics'));\n      const stub = CONTAINER.get(id);\n      await stub.testBasics();\n    }\n  },\n};\n\n// Test exit code monitor functionality\nexport const testExitCode = {\n  async test(_ctrl, env) {\n    const id = env.MY_CONTAINER.idFromName(\n      getRandomDurableObjectName('testExitCode')\n    );\n    const stub = env.MY_CONTAINER.get(id);\n    await stub.testExitCode();\n  },\n};\n\n// Test WebSocket functionality\nexport const testWebSockets = {\n  async test(_ctrl, env) {\n    const id = env.MY_CONTAINER.idFromName(\n      getRandomDurableObjectName('testWebsockets')\n    );\n    const stub = env.MY_CONTAINER.get(id);\n    await stub.testWs();\n  },\n};\n\nexport const testPortNotListening = {\n  async test(_ctrl, env) {\n    const id = env.MY_CONTAINER.idFromName(\n      getRandomDurableObjectName('testPortNotListening')\n    );\n    const stub = env.MY_CONTAINER.get(id);\n    await stub.testPortNotListening();\n  },\n};\n\n// Test alarm functionality with containers\nexport const testAlarm = {\n  async test(_ctrl, env) {\n    // Test that we can recover the use_containers flag correctly in setAlarm\n    // after a DO has been evicted\n    const id = env.MY_CONTAINER.idFromName(\n      getRandomDurableObjectName('testAlarm')\n    );\n    let stub = env.MY_CONTAINER.get(id);\n\n    // Start immediate alarm\n    await stub.startAlarm(true, 0);\n\n    // Wait for alarm to trigger\n    let retries = 0;\n    while ((await stub.getAlarmIndex()) === 0 && retries < 50) {\n      await scheduler.wait(20);\n      retries++;\n    }\n\n    await scheduler.wait(50);\n\n    // Set alarm for future and abort\n    await stub.startAlarm(false, 1000);\n\n    try {\n      await stub.abort();\n    } catch {\n      // Expected to throw\n    }\n\n    stub = env.MY_CONTAINER.get(id);\n    let confirmed = false;\n    for (let i = 0; i < 50 && !confirmed; i++) {\n      try {\n        await stub.checkAlarmAbortConfirmation();\n        confirmed = true;\n      } catch (e) {\n        assert.match(\n          e.message,\n          /Abort confirmation did not get inserted/,\n          `Unexpected error while polling for alarm: ${e.message}`\n        );\n\n        await scheduler.wait(100);\n      }\n    }\n    if (!confirmed) {\n      await stub.checkAlarmAbortConfirmation();\n    }\n  },\n};\n\nexport const testContainerShutdown = {\n  async test(_, env) {\n    const name = getRandomDurableObjectName('testContainerShutdown');\n\n    {\n      const stub = env.MY_CONTAINER.getByName(name);\n      await stub.start();\n      await assert.rejects(() => stub.abort(), {\n        name: 'Error',\n        message: 'Application called abort() to reset Durable Object.',\n      });\n    }\n\n    // Wait for the container to be shutdown after the DO aborts\n    await scheduler.wait(500);\n\n    {\n      const stub = env.MY_CONTAINER.getByName(name);\n\n      // Container should not be running after DO exited\n      await stub.expectRunning(false);\n    }\n  },\n};\n\nexport const testSetInactivityTimeout = {\n  async test(_ctrl, env) {\n    const name = getRandomDurableObjectName('testSetInactivityTimeout');\n\n    {\n      const stub = env.MY_CONTAINER.getByName(name);\n\n      await stub.testSetInactivityTimeout(10_000);\n\n      await assert.rejects(() => stub.abort(), {\n        name: 'Error',\n        message: 'Application called abort() to reset Durable Object.',\n      });\n    }\n\n    // Here we wait to ensure that if setInactivityTimeout *doesn't* work, the\n    // container has enough time to shutdown after the DO is aborted. If we\n    // don't wait then ctx.container.running will always be true, even without\n    // setInactivityTimeout, because the container won't have stoped yet.\n    await scheduler.wait(500);\n\n    {\n      const stub = env.MY_CONTAINER.getByName(name);\n\n      // Container should still be running after DO exited\n      await stub.expectRunning(true);\n    }\n  },\n};\n\n// Test that custom labels are passed through to the container\nexport const testLabels = {\n  async test(_ctrl, env) {\n    const id = env.MY_CONTAINER.idFromName(\n      getRandomDurableObjectName('testLabels')\n    );\n    const stub = env.MY_CONTAINER.get(id);\n    await stub.testLabels();\n  },\n};\n\n// Test that invalid labels are rejected with clear error messages\nexport const testLabelValidation = {\n  async test(_ctrl, env) {\n    const id = env.MY_CONTAINER.idFromName(\n      getRandomDurableObjectName('testLabelValidation')\n    );\n    const stub = env.MY_CONTAINER.get(id);\n    await stub.testLabelValidation();\n  },\n};\n\n// Test PID namespace isolation behavior\n// When containers_pid_namespace is ENABLED, the container has its own isolated PID namespace.\n// We verify this by checking that PID 1 in the container's namespace is the container's\n// init process, not the host's init process (systemd, launchd, etc.).\nexport const testPidNamespace = {\n  async test(_ctrl, env) {\n    const id = env.MY_CONTAINER.idFromName(\n      getRandomDurableObjectName('testPidNamespace')\n    );\n    const stub = env.MY_CONTAINER.get(id);\n    const data = await stub.testPidNamespace();\n\n    // When using an isolated PID namespace, PID 1 should be the container's entrypoint\n    // (a bash script that runs node), not the host's init process (systemd, launchd, init).\n    assert.match(\n      data.init,\n      /container-client-test/,\n      `Expected PID 1 to be the container entrypoint, but got: ${data.init}`\n    );\n  },\n};\n\n// Test setEgressHttp hostname functionality with internet (check we can establish\n// outbound with others).\nexport const testSetEgressHttpWithInternet = {\n  async test(_ctrl, env) {\n    const id = env.MY_CONTAINER.idFromName(\n      getRandomDurableObjectName('testSetEgressHttpWithInternet')\n    );\n    let stub = env.MY_CONTAINER.get(id);\n    await stub.testSetEgressHttpWithInternet();\n  },\n};\n\n// Test setEgressHttp hostname functionality with no internet\nexport const testSetEgressHttpNoInternet = {\n  async test(_ctrl, env) {\n    const id = env.MY_CONTAINER.idFromName(\n      getRandomDurableObjectName('testSetEgressHttpNoInternet')\n    );\n    let stub = env.MY_CONTAINER.get(id);\n    await stub.testSetEgressHttpNoInternet();\n  },\n};\n\n// Test setEgressHttp functionality - registers a binding's channel token with the container\nexport const testSetEgressHttp = {\n  async test(_ctrl, env) {\n    const id = env.MY_CONTAINER.idFromName(\n      getRandomDurableObjectName('testSetEgressHttp')\n    );\n    let stub = env.MY_CONTAINER.get(id);\n    await stub.testSetEgressHttp();\n    try {\n      // test we recover from aborts\n      await stub.abort();\n    } catch {}\n\n    stub = env.MY_CONTAINER.get(id);\n    // should work idempotent\n    await stub.testSetEgressHttp();\n  },\n};\n\n// Test WebSocket through interceptOutboundHttp - DO -> container -> worker binding via WebSocket\nexport const testInterceptWebSocket = {\n  async test(_ctrl, env) {\n    const id = env.MY_CONTAINER.idFromName(\n      getRandomDurableObjectName('testInterceptWebSocket')\n    );\n\n    const stub = env.MY_CONTAINER.get(id);\n    await stub.testInterceptWebSocket();\n  },\n};\n"
  },
  {
    "path": "src/workerd/server/tests/extensions/BUILD.bazel",
    "content": "load(\"//:build/wd_test.bzl\", \"wd_test\")\n\nwd_test(\n    src = \"extensions-test.wd-test\",\n    data = glob([\n        \"*.js\",\n        \"*.capnp\",\n    ]),\n)\n"
  },
  {
    "path": "src/workerd/server/tests/extensions/binding.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport function wrap(env) {\n  if (!env.secret) {\n    throw new Error('secret internal binding is not specified');\n  }\n\n  return {\n    tryOpen(key) {\n      return key === env.secret;\n    },\n  };\n}\n\nexport default wrap;\n"
  },
  {
    "path": "src/workerd/server/tests/extensions/extension.capnp",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst extension :Workerd.Extension = (\n  modules = [\n    ( name = \"test:module\", esModule = embed \"module.js\" ),\n    ( name = \"test:binding\", esModule = embed \"binding.js\", internal = true ),\n    ( name = \"test-internal:internal-module\", esModule = embed \"internal-module.js\", internal = true ),\n  ]\n);\n"
  },
  {
    "path": "src/workerd/server/tests/extensions/extensions-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport * as assert from 'node:assert';\nimport { openDoor } from 'test:module';\n\nexport const test_module_api = {\n  test() {\n    assert.throws(() => openDoor('test key'));\n    assert.equal(openDoor('0p3n s3sam3'), true);\n  },\n};\n\nexport const test_builtin_dynamic_import = {\n  async test() {\n    await assert.doesNotReject(import('test:module'));\n  },\n};\n\n// internal modules can't be imported\nexport const test_builtin_internal_dynamic_import = {\n  async test() {\n    await assert.rejects(import('test-internal:internal-module'));\n  },\n};\n\nexport const test_wrapped_binding = {\n  async test(ctr, env) {\n    assert.ok(env.door, 'binding is not present');\n    assert.equal(typeof env.door, 'object');\n    assert.ok(env.door.tryOpen);\n    assert.equal(typeof env.door.tryOpen, 'function');\n\n    // binding uses a different secret specified in the config\n    assert.ok(env.door.tryOpen('open sesame'));\n    assert.ok(!env.door.tryOpen('bad secret'));\n\n    // check there are no other properties available\n    assert.deepEqual(Object.keys(env.door), ['tryOpen']);\n    assert.deepEqual(Object.getOwnPropertyNames(env.door), ['tryOpen']);\n\n    assert.ok(env.customDoor, 'custom binding is not present');\n    assert.ok(!env.customDoor.tryOpen('open sesame'));\n    assert.ok(env.customDoor.tryOpen('custom open sesame'));\n  },\n};\n"
  },
  {
    "path": "src/workerd/server/tests/extensions/extensions-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\nusing TestExtension = import \"extension.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"extensions-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"extensions-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"],\n        bindings = [\n          (\n            name = \"door\",\n            wrapped = (\n              moduleName = \"test:binding\",\n              innerBindings = [ (name = \"secret\", text = \"open sesame\") ],\n            )\n          ),\n          (\n            name = \"customDoor\",\n            wrapped = (\n              moduleName = \"test:binding\",\n              # test custom wrap function name\n              entrypoint = \"wrap\",\n              innerBindings = [ (name = \"secret\", text = \"custom open sesame\") ],\n            )\n          )\n        ],\n     )\n    ),\n  ],\n  extensions = [ TestExtension.extension ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/extensions/internal-module.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport default {\n  caveKey: '0p3n s3sam3',\n};\n"
  },
  {
    "path": "src/workerd/server/tests/extensions/module.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport secret from 'test-internal:internal-module';\n\nexport function openDoor(key) {\n  if (key != secret.caveKey) throw new Error('Wrong key: ' + key);\n  return true;\n}\n"
  },
  {
    "path": "src/workerd/server/tests/inspector/BUILD.bazel",
    "content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_test\")\n\njs_test(\n    name = \"inspector-test\",\n    size = \"large\",\n    data = [\n        \":config.capnp\",\n        \":index.mjs\",\n        \"//:node_modules/chrome-remote-interface\",\n        \"//src/workerd/server:workerd\",\n        \"//src/workerd/server/tests:server-harness_js_lib\",\n    ],\n    entry_point = \"driver.mjs\",\n    env = {\n        \"WORKERD_BINARY\": \"$(rootpath //src/workerd/server:workerd)\",\n        \"WORKERD_CONFIG\": \"$(rootpath :config.capnp)\",\n    },\n    tags = [\"js-test\"],\n)\n"
  },
  {
    "path": "src/workerd/server/tests/inspector/config.capnp",
    "content": "# config.capnp\nusing Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    ( name = \"main\", worker = .worker ),\n  ],\n  sockets = [\n    ( name = \"http\", address = \"*:0\", http = (), service = \"main\" ),\n  ]\n);\n\nconst worker :Workerd.Worker = (\n  modules = [\n    ( name = \"./index.mjs\", esModule = embed \"index.mjs\" )\n  ],\n  compatibilityDate = \"2024-01-01\",\n  compatibilityFlags = [\"nodejs_compat\"],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/inspector/driver.mjs",
    "content": "import { env } from 'node:process';\nimport { beforeEach, afterEach, test } from 'node:test';\nimport assert from 'node:assert';\nimport CDP from 'chrome-remote-interface';\nimport { WorkerdServerHarness } from '../server-harness.mjs';\n\n// Global that is reset for each test.\nlet workerd;\n\nassert(\n  env.WORKERD_BINARY !== undefined,\n  'You must set the WORKERD_BINARY environment variable.'\n);\nassert(\n  env.WORKERD_CONFIG !== undefined,\n  'You must set the WORKERD_CONFIG environment variable.'\n);\n\n// Start workerd.\nbeforeEach(async () => {\n  workerd = new WorkerdServerHarness({\n    workerdBinary: env.WORKERD_BINARY,\n    workerdConfig: env.WORKERD_CONFIG,\n\n    // Hard-coded to match a socket name expected in the `workerdConfig` file.\n    listenPortNames: ['http'],\n  });\n\n  await workerd.start();\n\n  // We wait for the worker's HTTP port to come online before starting the test case. If we don't,\n  // and the inspector port comes online first, there's a chance the inspector connection will fail\n  // with 404 because the isolate doesn't exist yet.\n  await workerd.getListenPort('http');\n});\n\n// Stop workerd.\nafterEach(async () => {\n  const [code, signal] = await workerd.stop();\n  assert(code === 0 || signal === 'SIGTERM');\n  workerd = null;\n});\n\nasync function connectInspector(port) {\n  return await CDP({\n    port,\n\n    // Hard-coded to match a service name expected in the `workerdConfig` file.\n    target: '/main',\n\n    // Required to avoid trying to load the Protocol (schema, I guess?) from workerd, which doesn't\n    // implement the inspector protocol message in question.\n    local: true,\n  });\n}\n\nasync function profileAndExpectDeriveBitsFrames(inspectorClient) {\n  // Enable and start profiling.\n  await inspectorClient.Profiler.enable();\n  await inspectorClient.Profiler.start();\n\n  // Drive the worker with a test request. A single one is sufficient.\n  let httpPort = await workerd.getListenPort('http');\n  const response = await fetch(`http://localhost:${httpPort}`);\n  await response.arrayBuffer();\n\n  // Stop and disable profiling.\n  const profile = await inspectorClient.Profiler.stop();\n  await inspectorClient.Profiler.disable();\n\n  // Figure out which function name was most frequently sampled.\n  let hitCountMap = new Map();\n\n  for (let node of profile.profile.nodes) {\n    if (hitCountMap.get(node.callFrame.functionName) === undefined) {\n      hitCountMap.set(node.callFrame.functionName, 0);\n    }\n    hitCountMap.set(\n      node.callFrame.functionName,\n      hitCountMap.get(node.callFrame.functionName) + node.hitCount\n    );\n  }\n\n  let max = {\n    name: null,\n    count: 0,\n  };\n\n  for (let [name, count] of hitCountMap) {\n    if (count > max.count) {\n      max.name = name;\n      max.count = count;\n    }\n  }\n\n  // The most CPU-intensive function our test script runs is `deriveBits()`, so we expect that to be\n  // the most frequently sampled function.\n  assert.equal(max.name, 'deriveBits');\n  assert.notEqual(max.count, 0);\n}\n\n// Regression test for:\n// - https://github.com/cloudflare/workerd/issues/1754\n// - https://github.com/cloudflare/workerd/issues/2564\n//\n// At one time, workerd profiling broke, and started producing only \"(program)\" frames. My original\n// attempt at a fix subsequently caused workerd to segfault on the second inspector connection. This\n// rather expensive test case exercises both regressions.\ntest('Profiler mostly sees deriveBits() frames, and can safely reconnect', async () => {\n  for (let i = 0; i < 2; ++i) {\n    let inspectorClient = await connectInspector(\n      await workerd.getListenInspectorPort()\n    );\n    await profileAndExpectDeriveBitsFrames(inspectorClient);\n    await inspectorClient.close();\n  }\n});\n"
  },
  {
    "path": "src/workerd/server/tests/inspector/index.mjs",
    "content": "// index.mjs\nimport { Buffer } from 'node:buffer';\n\nconst encoder = new TextEncoder();\n\nasync function pbkdf2Derive(password) {\n  const passwordArray = encoder.encode(password);\n  const passwordKey = await crypto.subtle.importKey(\n    'raw',\n    passwordArray,\n    'PBKDF2',\n    false,\n    ['deriveBits']\n  );\n  const saltArray = crypto.getRandomValues(new Uint8Array(16));\n  const keyBuffer = await crypto.subtle.deriveBits(\n    { name: 'PBKDF2', hash: 'SHA-256', salt: saltArray, iterations: 1_000_000 },\n    passwordKey,\n    256\n  );\n  return Buffer.from(keyBuffer).toString('base64');\n}\n\nexport default {\n  async fetch(request, env, ctx) {\n    return new Response(await pbkdf2Derive('hello!'));\n  },\n};\n"
  },
  {
    "path": "src/workerd/server/tests/python/BUILD.bazel",
    "content": "load(\"//src/workerd/server/tests/python:import_tests.bzl\", \"gen_rust_import_tests\")\nload(\"//src/workerd/server/tests/python:py_wd_test.bzl\", \"py_wd_test\", \"python_test_setup\")\nload(\"//src/workerd/server/tests/python/vendor_pkg_tests:vendor_test.bzl\", \"vendored_py_wd_test\")\n\npython_test_setup()\n\n# NOTE: Each test takes a while to start up, so if possible add additional tests to pytest or to\n# top-level-tests or sdk or some other existing test.\n\n# TODO: This only works for pytest-asyncio <= 1.1.0, starting from pytest-asyncio 1.2.0 there is a\n# problem.\nvendored_py_wd_test(\n    \"pytest-asyncio\",\n    data = glob([\"pytest/tests/**\"]),\n    level = 0,\n    main_py_file = \"pytest/main.py\",\n    test_template = \"pytest/pytest.wd-test\",\n)\n\npy_wd_test(\"hello\")\n\npy_wd_test(\"top-level-tests\")\n\npy_wd_test(\"env-param\")\n\npy_wd_test(\"asgi\")\n\npy_wd_test(\"asgi-sse\")\n\npy_wd_test(\"random\")\n\npy_wd_test(\"subdirectory\")\n\npy_wd_test(\n    \"sdk\",\n    compat_date = \"2026-01-01\",\n)\n\ngen_rust_import_tests()\n\npy_wd_test(\"undefined-handler\")\n\npy_wd_test(\"vendor_dir\")\n\npy_wd_test(\"dont-snapshot-pyodide\")\n\npy_wd_test(\"filter-non-py-files\")\n\npy_wd_test(\"durable-object\")\n\npy_wd_test(\n    \"durable-object-websocket\",\n    make_snapshot = False,\n    # TODO: enabling this will reproduce a strange issue, see comment in EW-9767.\n    use_snapshot = None,\n)\n\npy_wd_test(\"worker-entrypoint\")\n\npy_wd_test(\n    \"jspi\",\n    skip_python_flags = [\"0.26.0a2\"],\n)\n\npy_wd_test(\"python-rpc\")\n\npy_wd_test(\"workflow-entrypoint\")\n\npy_wd_test(\"vendor_dir_compat_flag\")\n\npy_wd_test(\"default-class-with-legacy-global-handlers\")\n\npy_wd_test(\n    \"fastapi\",\n    make_snapshot = False,\n    use_snapshot = \"fastapi\",\n)\n\npy_wd_test(\n    \"numpy\",\n    make_snapshot = False,\n    use_snapshot = \"numpy\",\n)\n\npy_wd_test(\"python-compat-flag\")\n"
  },
  {
    "path": "src/workerd/server/tests/python/asgi/asgi.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"python-asgi\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"worker.py\"),\n        ],\n        bindings = [\n          ( name = \"SELF\", service = \"python-asgi\" ),\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"rpc\", \"python_no_global_handlers\"],\n      )\n    )\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/asgi/worker.py",
    "content": "import asyncio\nimport logging\n\nimport asgi\nimport js\nfrom workers import Request, WorkerEntrypoint\n\nfrom pyodide.ffi import to_js\n\n\ndef check_encoding(byte_str, encoding=\"utf-8\"):\n    try:\n        byte_str.decode(encoding)\n    except UnicodeDecodeError:\n        return False\n    return True\n\n\nclass Server:\n    def __init__(self):\n        pass\n\n    async def __call__(self, scope, receive, send) -> None:\n        scope[\"app\"] = self\n\n        assert scope[\"type\"] in (\"http\", \"websocket\", \"lifespan\")\n\n        if scope[\"type\"] == \"lifespan\":\n            message = await receive()\n            if message[\"type\"] == \"lifespan.startup\":\n                await send({\"type\": \"lifespan.startup.complete\"})\n            return\n\n        elif scope[\"type\"] == \"http\":\n            headers = scope[\"headers\"]\n            for header in headers:\n                assert isinstance(header[0], bytes) and isinstance(header[1], bytes)\n                assert check_encoding(header[0]) and check_encoding(header[1])\n\n            await receive()\n            # Send response and return\n            await send(\n                {\n                    \"type\": \"http.response.start\",\n                    \"status\": 200,\n                    \"headers\": headers,\n                }\n            )\n\n            await send(\n                {\n                    \"type\": \"http.response.body\",\n                    \"body\": b\"Hello, World\",\n                }\n            )\n\n\nexample_hdr = {\"Header1\": \"Value1\", \"Header2\": \"Value2\"}\n\n\nclass Default(WorkerEntrypoint):\n    async def fetch(self, request):\n        # Verify that `asgi` can handle JS-style headers and Python-style headers:\n        js_request = js.Request.new(\"http://example.com/\", headers=to_js(example_hdr))\n        py_request = Request(\"http://example.com/\", headers=example_hdr)\n        js_scope = asgi.request_to_scope(js_request, self.env)\n        py_scope = asgi.request_to_scope(py_request, self.env)\n        assert (\n            js_scope[\"headers\"]\n            == py_scope[\"headers\"]\n            == [(k.lower().encode(), v.encode()) for k, v in example_hdr.items()]\n        )\n\n        # Standard asgi.fetch test path.\n        return await asgi.fetch(app, request, self.env, self.ctx)\n\n    async def test(self, ctrl):\n        await header_test(self.env)\n        await test_error_after_response_is_logged(self.env)\n        await test_background_task_error_is_logged()\n        await test_app_exception_before_response_is_logged()\n\n\napp = Server()\n\n\nasync def header_test(env):\n    response = await env.SELF.fetch(\"http://example.com/\", headers=to_js(example_hdr))\n    for header in response.headers.items():\n        assert isinstance(header[0], str) and isinstance(header[1], str)\n        expected_hdr = {k.lower(): v.lower() for k, v in example_hdr.items()}\n        assert header[0] in expected_hdr.keys()\n        assert expected_hdr[header[0]] == header[1].lower()\n\n\n# ---------------------------------------------------------------------------\n# Logging tests\n# ---------------------------------------------------------------------------\n\n\nclass _ListHandler(logging.Handler):\n    \"\"\"A logging handler that captures records into a list for assertions.\"\"\"\n\n    def __init__(self):\n        super().__init__()\n        self.records: list[logging.LogRecord] = []\n\n    def emit(self, record):\n        self.records.append(record)\n\n    def clear(self):\n        self.records.clear()\n\n\ndef _install_handler():\n    \"\"\"Install a ListHandler on the 'asgi' logger and return it.\"\"\"\n    handler = _ListHandler()\n    logger = logging.getLogger(\"asgi\")\n    logger.addHandler(handler)\n    # Ensure the logger level is low enough to capture everything.\n    logger.setLevel(logging.DEBUG)\n    return handler\n\n\ndef _remove_handler(handler):\n    logging.getLogger(\"asgi\").removeHandler(handler)\n\n\nclass _ErrorAfterResponseApp:\n    \"\"\"ASGI app that sends a valid response, then raises an exception.\"\"\"\n\n    async def __call__(self, scope, receive, send):\n        if scope[\"type\"] == \"lifespan\":\n            message = await receive()\n            if message[\"type\"] == \"lifespan.startup\":\n                await send({\"type\": \"lifespan.startup.complete\"})\n            return\n\n        if scope[\"type\"] == \"http\":\n            await receive()\n            await send(\n                {\n                    \"type\": \"http.response.start\",\n                    \"status\": 200,\n                    \"headers\": [(b\"content-type\", b\"text/plain\")],\n                }\n            )\n            await send(\n                {\n                    \"type\": \"http.response.body\",\n                    \"body\": b\"ok\",\n                }\n            )\n            # Response is already sent — now raise an error.\n            raise RuntimeError(\"post-response error for testing\")\n\n\nasync def test_error_after_response_is_logged(env):\n    handler = _install_handler()\n    try:\n        req = js.Request.new(\"http://example.com/log-test\")\n        # The response should still succeed — the error happens after it's sent.\n        response = await asgi.fetch(_ErrorAfterResponseApp(), req, env)\n        assert response.status == 200, f\"Expected 200, got {response.status}\"\n\n        # Let the event loop run\n        await asyncio.sleep(20)\n\n        # The error should have been logged, not swallowed.\n        error_records = [r for r in handler.records if r.levelno >= logging.ERROR]\n        assert len(error_records) > 0, (\n            \"Expected at least one ERROR log record for post-response exception, \"\n            f\"got {len(error_records)}. All records: {[r.getMessage() for r in handler.records]}\"\n        )\n        matched = any(\n            \"post-response error for testing\" in str(r.exc_info[1])\n            if r.exc_info\n            else False\n            for r in error_records\n        )\n        assert matched, (\n            \"Expected log message containing 'post-response error for testing', \"\n            f\"got: {[r.getMessage() for r in error_records]}\"\n        )\n    finally:\n        _remove_handler(handler)\n\n\nasync def test_background_task_error_is_logged():\n    handler = _install_handler()\n    try:\n\n        async def failing_task():\n            raise ValueError(\"background task failure for testing\")\n\n        asgi.run_in_background(failing_task())\n\n        # Let the event loop run\n        await asyncio.sleep(20)\n\n        error_records = [r for r in handler.records if r.levelno >= logging.ERROR]\n        assert len(error_records) > 0, (\n            \"Expected at least one ERROR log for background task failure, \"\n            f\"got {len(error_records)}. All records: {[r.getMessage() for r in handler.records]}\"\n        )\n        matched = any(\n            \"background task failure for testing\" in str(r.exc_info[1])\n            if r.exc_info\n            else False\n            for r in error_records\n        )\n        assert matched, (\n            \"Expected log message containing 'background task failure for testing', \"\n            f\"got: {[r.getMessage() for r in error_records]}\"\n        )\n    finally:\n        _remove_handler(handler)\n\n\nclass _ErrorBeforeResponseApp:\n    \"\"\"ASGI app that raises before sending any response.\"\"\"\n\n    async def __call__(self, scope, receive, send):\n        if scope[\"type\"] == \"lifespan\":\n            message = await receive()\n            if message[\"type\"] == \"lifespan.startup\":\n                await send({\"type\": \"lifespan.startup.complete\"})\n            return\n\n        if scope[\"type\"] == \"http\":\n            await receive()\n            raise RuntimeError(\"app crash before response for testing\")\n\n\nasync def test_app_exception_before_response_is_logged():\n    handler = _install_handler()\n    try:\n        req = js.Request.new(\"http://example.com/crash-test\")\n        threw = False\n        try:\n            await asgi.fetch(_ErrorBeforeResponseApp(), req, {})\n        except RuntimeError as e:\n            threw = True\n            assert \"app crash before response for testing\" in str(e), (\n                f\"Expected original exception message, got: {e}\"\n            )\n\n        assert threw, \"Expected RuntimeError to be raised from asgi.fetch\"\n\n        # fetch() should have logged the error before re-raising.\n        error_records = [r for r in handler.records if r.levelno >= logging.ERROR]\n        assert len(error_records) > 0, (\n            \"Expected at least one ERROR log for request failure, \"\n            f\"got {len(error_records)}. All records: {[r.getMessage() for r in handler.records]}\"\n        )\n        matched = any(\"ASGI request failed\" in r.getMessage() for r in error_records)\n        assert matched, (\n            \"Expected log message containing 'ASGI request failed', \"\n            f\"got: {[r.getMessage() for r in error_records]}\"\n        )\n    finally:\n        _remove_handler(handler)\n"
  },
  {
    "path": "src/workerd/server/tests/python/asgi-sse/asgi-sse.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"python-asgi-sse\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"worker.py\"),\n        ],\n        bindings = [\n          ( name = \"SELF\", service = \"python-asgi-sse\" ),\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"python_no_global_handlers\"],\n      )\n    )\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/asgi-sse/worker.py",
    "content": "class SSEServer:\n    def __init__(self):\n        pass\n\n    async def __call__(self, scope, receive, send) -> None:\n        scope[\"app\"] = self\n\n        assert scope[\"type\"] in (\"http\", \"websocket\", \"lifespan\")\n\n        if scope[\"type\"] == \"lifespan\":\n            message = await receive()\n            if message[\"type\"] == \"lifespan.startup\":\n                await send({\"type\": \"lifespan.startup.complete\"})\n            return\n\n        elif scope[\"type\"] == \"http\":\n            # Receive the request\n            await receive()\n\n            # Send SSE response headers\n            await send(\n                {\n                    \"type\": \"http.response.start\",\n                    \"status\": 200,\n                    \"headers\": [\n                        (b\"cache-control\", b\"no-store\"),\n                        (b\"connection\", b\"keep-alive\"),\n                        (b\"content-type\", b\"text/event-stream; charset=utf-8\"),\n                        (b\"x-accel-buffering\", b\"no\"),\n                    ],\n                }\n            )\n\n            # Send initial event\n            await send(\n                {\n                    \"type\": \"http.response.body\",\n                    \"body\": b\"event: endpoint\\r\\ndata: /messages/?session_id=test123\\r\\n\\r\\n\",\n                    \"more_body\": True,\n                }\n            )\n\n            # Send three ping events\n            for i in range(3):\n                # In a real app we would wait between events, but in the test we'll send them quickly\n                await send(\n                    {\n                        \"type\": \"http.response.body\",\n                        \"body\": f\": ping - message {i + 1}\\r\\n\\r\\n\".encode(),\n                        \"more_body\": i < 2,  # last message has more_body=False\n                    }\n                )\n\n\nfrom workers import WorkerEntrypoint\n\n\nclass Default(WorkerEntrypoint):\n    async def fetch(self, request):\n        import asgi\n\n        return await asgi.fetch(app, request, self.env, self.ctx)\n\n    async def test(self, ctrl):\n        await test_sse(self.env)\n\n\napp = SSEServer()\n\n\nasync def test_sse(env):\n    # Make a request to our SSE endpoint\n    response = await env.SELF.fetch(\"http://example.com/sse\")\n\n    # Verify the response has the correct headers for SSE\n    assert response.headers[\"content-type\"] == \"text/event-stream; charset=utf-8\"\n    assert response.headers[\"cache-control\"] == \"no-store\"\n\n    # Use a simple method to convert the stream to text\n    from js import TextDecoder\n\n    # Read the stream in a simpler way\n    reader = response.body.getReader()\n    content = \"\"\n    decoder = TextDecoder.new()\n\n    while True:\n        result = await reader.read()\n        if result.done:\n            break\n        # Use TextDecoder to convert the chunk to text\n        chunk_text = decoder.decode(result.value, {\"stream\": True})\n        content += chunk_text\n\n    # Final flush\n    content += decoder.decode()\n    # Verify the expected events are in the response\n    assert \"event: endpoint\" in content\n    assert \"data: /messages/?session_id=test123\" in content\n    assert \": ping - message 1\" in content\n    assert \": ping - message 2\" in content\n    assert \": ping - message 3\" in content\n"
  },
  {
    "path": "src/workerd/server/tests/python/default-class-with-legacy-global-handlers/default-class-with-legacy-global-handlers.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"default-class-with-legacy-global-handlers\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"worker.py\")\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/default-class-with-legacy-global-handlers/worker.py",
    "content": "from workers import WorkerEntrypoint\n\n# Without the fix, this failed with:\n#\n# service default-class-with-legacy-global-handlers: Uncaught TypeError: Cannot define multiple default entrypoints\n#   at pyodide:python-entrypoint-helper:286:15 in handleDefaultClass\n#   at pyodide:python-entrypoint-helper:321:5 in initPython\n\n\nclass Default(WorkerEntrypoint):\n    def test(*args):\n        pass\n"
  },
  {
    "path": "src/workerd/server/tests/python/dont-snapshot-pyodide/dont-snapshot-pyodide.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"dont-snapshot-pyodide\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"worker.py\"),\n          (name = \"numpy\", pythonRequirement = \"\")\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/dont-snapshot-pyodide/worker.py",
    "content": "\"\"\"\nTo trigger the bug we need to do two things:\n1. import `pyodide` at top level\n2. ensure that there is some package requirement in wd-test\n3. make test() async\n\nImporting numpy isn't really necessary but we need to include it as a requirement in the wd-test\nfile so that we consider making a package snapshot. In the buggy code, importing pyodide at top\nlevel then makes the package snapshot import pyodide while making the snapshot. Importing pyodide\nbefore calling finalizeBootstrap messes up the runtime state and causes various weird and malign\nsymptoms.\n\"\"\"\n\nimport numpy\n\nimport pyodide\n\n\nasync def test():\n    # Mention imports so that ruff won't remove them\n    pyodide  # noqa: B018\n    numpy  # noqa: B018\n"
  },
  {
    "path": "src/workerd/server/tests/python/durable-object/durable-object.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n    (name = \"TEST_TMPDIR\", disk = (writable = true)),\n  ],\n);\n\nconst mainWorker :Workerd.Worker = (\n\n  compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"rpc\", \"python_no_global_handlers\"],\n\n  modules = [\n    (name = \"worker.py\", pythonModule = embed \"worker.py\"),\n  ],\n\n  durableObjectNamespaces = [\n    ( className = \"DurableObjectExample\",\n      uniqueKey = \"210bd0cbd803ef7883a1ee9d86cce06e\",\n      enableSql = true ),\n  ],\n\n  durableObjectStorage = (localDisk = \"TEST_TMPDIR\"),\n\n  bindings = [\n    (name = \"ns\", durableObjectNamespace = \"DurableObjectExample\"),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/durable-object/worker.py",
    "content": "# Copyright (c) 2023 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\nfrom asyncio import sleep\nfrom urllib.parse import urlparse\n\nfrom js import Date\nfrom workers import DurableObject, FetchResponse, Request, Response, WorkerEntrypoint\n\nimport pyodide\n\n\nclass MixinTest:\n    def test_mixin(self):\n        return 1234\n\n\nclass DurableObjectExample(DurableObject, MixinTest):\n    def __init__(self, state, env):\n        super().__init__(state, env)\n        assert self.env is not None\n        assert self.ctx is not None\n\n        self.state = state\n        self.counter = 0\n        self.storage = state.storage\n        self.alarm_triggered = False\n\n        # Test blockConcurrencyWhile in the constructor with a Python async callback.\n        # This is a common pattern for initializing DO state.\n        async def init_callback():\n            await self.storage.put(\"initialized\", True)\n\n        self.ctx.blockConcurrencyWhile(init_callback)\n\n    async def fetch(self, request):\n        assert isinstance(request, Request)\n\n        curr = await self.storage.getAlarm()\n        if not curr:\n            self.storage.setAlarm(Date.now() + 100)\n\n        url = urlparse(request.url)\n        if url.path == \"/counter\":\n            self.counter += 1\n            return Response(f\"hello from python {self.counter}\")\n        elif url.path == \"/alarm\":\n            return Response(str(self.alarm_triggered))\n        else:\n            return Response(\"404\")\n\n    async def alarm(self, alarm_info):\n        self.alarm_triggered = True\n\n    async def no_args_method(self):\n        return \"value from python\"\n\n    async def args_method(self, arg):\n        return \"value from python \" + arg\n\n    def mutate_dict(self, my_dict):\n        my_dict[\"foo\"] = 42\n\n    async def test_block_concurrency_while(self):\n        # Verify the constructor's blockConcurrencyWhile ran successfully\n        initialized = await self.storage.get(\"initialized\")\n        assert initialized, f\"Expected True but got {initialized}\"\n\n        # Test blockConcurrencyWhile with a Python async callback that returns a value.\n        async def my_callback():\n            await self.storage.put(\"blocked\", \"yes\")\n            return 42\n\n        result = await self.ctx.blockConcurrencyWhile(my_callback)\n        assert result == 42, f\"Expected 42 but got {result}\"\n        blocked = await self.storage.get(\"blocked\")\n        assert blocked == \"yes\", f\"Expected 'yes' but got {blocked}\"\n\n        return True\n\n    async def test_self_call(self):\n        test_dict = dict()\n        test_dict[\"test\"] = 1\n        self.mutate_dict(test_dict)\n        assert test_dict[\"test\"] == 1\n        assert test_dict[\"foo\"] == 42\n        return True\n\n    def jspi_method(self, arg):\n        from pyodide.ffi import run_sync\n\n        run_sync(sleep(0.01))\n        return arg + 1\n\n\nclass Default(WorkerEntrypoint):\n    async def test(self, ctrl):\n        id = self.env.ns.idFromName(\"A\")\n        obj = self.env.ns.get(id)\n\n        first_resp = await obj.fetch(\"http://foo.com/counter\")\n        first_resp_data = await first_resp.text()\n        assert first_resp_data == \"hello from python 1\"\n\n        second_resp = await obj.fetch(\"http://foo.com/counter\")\n        second_resp_data = await second_resp.text()\n        assert second_resp_data == \"hello from python 2\"\n\n        no_arg_resp = await obj.no_args_method()\n        assert no_arg_resp == \"value from python\"\n\n        arg_resp = await obj.args_method(\"test\")\n        assert arg_resp == \"value from python test\"\n\n        assert await obj.test_block_concurrency_while()\n\n        assert await obj.test_self_call()\n\n        if pyodide.__version__ != \"0.26.0a2\":\n            res = await obj.jspi_method(9)\n            assert res == 10\n\n        # Verify that a mixin method can be called via RPC.\n        assert await obj.test_mixin() == 1234\n\n        # Verify that DO fetch is wrapped.\n        third_resp = await obj.fetch(\"http://foo.com/counter\")\n        assert isinstance(third_resp, FetchResponse)\n        third_resp_data = await third_resp.text()\n        assert third_resp_data == \"hello from python 3\"\n\n        # Wait for alarm to get triggered.\n        for _ in range(20):\n            await sleep(0.2)\n            resp = await obj.fetch(\"http://foo.com/alarm\")\n\n            alarm_triggered = await resp.text() == \"True\"\n            if alarm_triggered:\n                break\n        else:\n            raise AssertionError(\"Alarm never triggered\")\n"
  },
  {
    "path": "src/workerd/server/tests/python/durable-object-websocket/durable-object-websocket.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n    (name = \"tester\", worker = .testerWorker),\n    (name = \"TEST_TMPDIR\", disk = (writable = true)),\n  ],\n);\n\nconst mainWorker :Workerd.Worker = (\n\n  compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"python_no_global_handlers\"],\n\n  modules = [\n    (name = \"worker.py\", pythonModule = embed \"worker.py\"),\n  ],\n\n  durableObjectNamespaces = [\n    ( className = \"DurableObjectWebSocket\",\n      uniqueKey = \"210bd0cbd803ef7883a1ee9d86cce06e\",\n      enableSql = true ),\n  ],\n\n  durableObjectStorage = (localDisk = \"TEST_TMPDIR\"),\n\n  bindings = [\n    (name = \"ns\", durableObjectNamespace = \"DurableObjectWebSocket\"),\n  ],\n);\n\n\nconst testerWorker :Workerd.Worker = (\n\n  compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"python_no_global_handlers\"],\n\n  modules = [\n    (name = \"tester.js\", esModule = embed \"tester.js\"),\n  ],\n\n  bindings = [\n    ( name = \"PYTHON\", service = \"main\" ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/durable-object-websocket/tester.js",
    "content": "export default {\n  async test(ctrl, env, ctx) {\n    const resp = await env.PYTHON.fetch('http://example.com/websocket', {\n      headers: { Upgrade: 'websocket' },\n    });\n    const ws = resp.webSocket;\n    if (!ws) {\n      throw new Error('No websocket');\n    }\n    ws.accept();\n    const messagePromise = new Promise((resolve) => {\n      ws.addEventListener('message', (msg) => {\n        console.log('Received in JS Tester: ', msg.data);\n        resolve(msg.data);\n      });\n    });\n    ws.send('test');\n\n    await messagePromise;\n    ws.close();\n  },\n};\n"
  },
  {
    "path": "src/workerd/server/tests/python/durable-object-websocket/worker.py",
    "content": "# Copyright (c) 2023 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\nfrom js import WebSocketPair\nfrom workers import DurableObject, Request, Response, WorkerEntrypoint\n\n\nclass DurableObjectWebSocket(DurableObject):\n    def __init__(self, state, env):\n        super().__init__(state, env)\n\n    async def fetch(self, request):\n        assert isinstance(request, Request)\n        web_socket_pair = WebSocketPair.new()\n        client, server = web_socket_pair\n        self.ctx.acceptWebSocket(server)\n        return Response(None, status=101, web_socket=client)\n\n    async def webSocketMessage(self, ws, message):\n        print(\"Received in Python DO WS message: \", message)\n        ws.send(\"hello\")\n\n    async def webSocketClose(self, ws, code, reason, wasClean):\n        print(\"Closed in Python DO WS\")\n\n\nclass Default(WorkerEntrypoint):\n    async def fetch(self, request):\n        if request.method == \"GET\" and request.url.endswith(\"/websocket\"):\n            upgrade_header = request.headers.get(\"Upgrade\")\n            if upgrade_header != \"websocket\":\n                return Response(\n                    None,\n                    status=426,\n                    status_text=\"Expected Upgrade\",\n                    headers={\"Content-Type\": \"text/plain\"},\n                )\n\n            # We are explicitly testing usage via `self.env` here rather than the\n            # argument to `fetch` and using `getByName` rather than `get`.\n            stub = self.env.ns.getByName(\"A\")\n            return await stub.fetch(request)\n        return Response(\"Not found\", status=404)\n"
  },
  {
    "path": "src/workerd/server/tests/python/env-param/env.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"python-hello\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"worker.py\")\n        ],\n        bindings = [\n          (\n            name = \"secret\",\n            text = \"thisisasecret\"\n          ),\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/env-param/worker.py",
    "content": "def test(ctx, env):\n    assert hasattr(env, \"secret\")\n    assert env.secret == \"thisisasecret\"\n"
  },
  {
    "path": "src/workerd/server/tests/python/fastapi/fastapi.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"fastapi\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"worker.py\"),\n          (name = \"fastapi\", pythonRequirement = \"fastapi\")\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"disable_python_dedicated_snapshot\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/fastapi/worker.py",
    "content": "import fastapi\nfrom workers import WorkerEntrypoint\n\n\nclass Default(WorkerEntrypoint):\n    def test(self):\n        assert fastapi.__version__ in {\"0.110.0\", \"0.116.1\"}\n"
  },
  {
    "path": "src/workerd/server/tests/python/filter-non-py-files/filter-files.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\n# This is a really slow way to test that PyodideMetadataReader::getWorkerFiles works.\n# TODO: replace with a unit test?\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"dont-snapshot-pyodide\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"worker.py\"),\n          # a file with no `.py` extension to get filtered out\n          (name = \"fake_shared_library.so\", data = \"This isn't really a shared library...\"),\n          # We need a package dependency to trigger the package snapshot logic which we're trying to\n          # test.\n          (name = \"numpy\", pythonRequirement = \"\")\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/filter-non-py-files/worker.py",
    "content": "def test():\n    pass\n"
  },
  {
    "path": "src/workerd/server/tests/python/hello/hello.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"python-hello\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"worker.py\")\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/hello/worker.py",
    "content": "def test():\n    from js import console\n    from js.WebAssembly import Suspending\n\n    Suspending  # noqa: B018\n\n    # This just tests that nothing raises when we run this. It isn't great though\n    # because we don't test whether we printed anything.\n    # TODO: update this to test that something happened\n    print(\"Does this work?\")\n    console.log(\"Does this work?\")  # both should print the same output\n"
  },
  {
    "path": "src/workerd/server/tests/python/import_tests.bzl",
    "content": "load(\"@bazel_skylib//rules:write_file.bzl\", \"write_file\")\nload(\"//:build/python_metadata.bzl\", \"BUNDLE_VERSION_INFO\", \"PYTHON_IMPORTS_TO_TEST\")\nload(\"//src/workerd/server/tests/python:py_wd_test.bzl\", \"py_wd_test\")\n\ndef _generate_import_py_file(imports):\n    res = \"\"\n    for imp in imports:\n        res += \"import \" + imp + \"\\n\"\n\n    res += \"from workers import WorkerEntrypoint\\n\"\n    res += \"class Default(WorkerEntrypoint):\\n\"\n    res += \"    def test(self):\\n\"\n    res += \"        pass\"\n    return res\n\nWD_FILE_TEMPLATE = \"\"\"\nusing Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n    services = [\n        ( name = \"python-import-{name}\",\n            worker = (\n                modules = [\n                    (name = \"worker.py\", pythonModule = embed \"./worker.py\"),\n                    {requirements}\n                ],\n                compatibilityFlags = [%PYTHON_FEATURE_FLAGS],\n            )\n        ),\n    ]\n);\"\"\"\n\ndef _generate_wd_test_file(name, requirements):\n    l = []\n    for req in requirements:\n        l.append('(name = \"{}\", pythonRequirement = \"\"),\\n'.format(req))\n    requirements = \"\".join(l)\n    return WD_FILE_TEMPLATE.format(name = name, requirements = requirements)\n\ndef _test(name, directory, wd_test, py_file, python_version, **kwds):\n    py_wd_test(\n        name = name,\n        directory = directory,\n        src = wd_test,\n        python_flags = [python_version],\n        use_snapshot = None,\n        make_snapshot = False,\n        skip_default_data = True,\n        data = [py_file],\n        **kwds\n    )\n\n# to_test is a dictionary from library name to list of imports\ndef _gen_import_tests(to_test, python_version, pkg_skip_versions):\n    for lib in to_test.keys():\n        skip_python_flags = [version for version, packages in pkg_skip_versions.items() if lib in packages]\n        if BUNDLE_VERSION_INFO[\"development\"][\"real_pyodide_version\"] in skip_python_flags:\n            skip_python_flags.append(\"development\")\n        if lib.endswith(\"-tests\"):\n            # TODO: The pyodide-build-scripts should be updated to not emit these packages. Once\n            # that's done we can remove this check.\n            continue\n\n        prefix = \"import/\" + lib\n        worker_py_fname = python_version + \"/\" + prefix + \"/worker.py\"\n        wd_test_fname = python_version + \"/\" + prefix + \"/import.wd-test\"\n        write_file(\n            name = worker_py_fname + \"@rule\",\n            out = worker_py_fname,\n            content = [_generate_import_py_file(to_test[lib])],\n        )\n        write_file(\n            name = wd_test_fname + \"@rule\",\n            out = wd_test_fname,\n            content = [_generate_wd_test_file(lib, [lib])],\n        )\n\n        _test(\n            name = prefix,\n            directory = lib,\n            wd_test = wd_test_fname,\n            py_file = worker_py_fname,\n            python_version = python_version,\n            skip_python_flags = skip_python_flags,\n        )\n\ndef gen_import_tests(*, pkg_skip_versions = {}):\n    for python_version, info in BUNDLE_VERSION_INFO.items():\n        to_test = PYTHON_IMPORTS_TO_TEST[info[\"packages\"]]\n        _gen_import_tests(to_test, python_version, pkg_skip_versions = pkg_skip_versions)\n\ndef _rotations(lst):\n    result = []\n    cur = lst\n    for i in range(len(lst)):\n        result.append(cur)\n        cur = cur[1:] + [cur[0]]\n    return result\n\ndef _pkg_permutations(lst):\n    return _rotations(lst) + _rotations(reversed(lst))\n\ndef _gen_rust_import_tests(python_version):\n    pyodide_version = BUNDLE_VERSION_INFO[python_version][\"real_pyodide_version\"]\n    if pyodide_version == \"0.26.0a2\":\n        pkgs = _rotations([\"tiktoken\", \"pydantic\"])\n    else:\n        pkgs = _pkg_permutations([\"cryptography\", \"jiter\", \"tiktoken\", \"pydantic\"])\n\n    for res in pkgs:\n        name = \"-\".join(res)\n        prefix = \"import2/\" + name\n        worker_py_fname = python_version + \"/\" + prefix + \"/worker.py\"\n        wd_test_fname = python_version + \"/\" + prefix + \"/import.wd-test\"\n        write_file(\n            name = worker_py_fname + \"@rule\",\n            out = worker_py_fname,\n            content = [_generate_import_py_file(res)],\n        )\n        write_file(\n            name = wd_test_fname + \"@rule\",\n            out = wd_test_fname,\n            content = [_generate_wd_test_file(name, res)],\n        )\n\n        _test(\n            name = prefix,\n            directory = name,\n            wd_test = wd_test_fname,\n            py_file = worker_py_fname,\n            python_version = python_version,\n        )\n\ndef gen_rust_import_tests():\n    for python_version in BUNDLE_VERSION_INFO.keys():\n        _gen_rust_import_tests(python_version)\n"
  },
  {
    "path": "src/workerd/server/tests/python/jspi/jspi.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"jspi\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"worker.py\")\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/jspi/worker.py",
    "content": "from asyncio import sleep\n\nfrom pyodide import __version__\n\n\ndef jspi_sleep():\n    if __version__ == \"0.26.0a2\":\n        print(\"JSPI not supported on 0.26.0a2, skipping\")\n        return\n\n    from pyodide.ffi import run_sync\n\n    run_sync(sleep(0.1))\n    print(\"Okay\")\n\n\n# TODO(EW-9316): the below test wasn't actually testing jspi at top-level because it only ran in\n# worker and workerd used to not run the top-level at the top-level... this was fixed and now the\n# below fails.\n# print(\"Testing JSPI at top level...\")\n# jspi_sleep()\n\n\ndef test():\n    print(\"Testing JSPI in handler...\")\n    jspi_sleep()\n"
  },
  {
    "path": "src/workerd/server/tests/python/numpy/numpy.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"numpy\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"worker.py\"),\n          (name = \"numpy\", pythonRequirement = \"numpy\")\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"disable_python_dedicated_snapshot\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/numpy/worker.py",
    "content": "import numpy as np\nfrom workers import WorkerEntrypoint\n\n\nclass Default(WorkerEntrypoint):\n    def test(self):\n        res = np.arange(12).reshape((3, -1))[::-2, ::-2]\n        assert str(res) == \"[[11  9]\\n [ 3  1]]\"\n"
  },
  {
    "path": "src/workerd/server/tests/python/py_wd_test.bzl",
    "content": "load(\"@bazel_skylib//rules:copy_file.bzl\", \"copy_file\")\nload(\"@bazel_skylib//rules:expand_template.bzl\", \"expand_template\")\nload(\"//:build/python_metadata.bzl\", \"BUNDLE_VERSION_INFO\")\nload(\"//:build/wd_test.bzl\", \"wd_test\")\n\ndef _get_enable_flags(python_flag):\n    flags = [BUNDLE_VERSION_INFO[python_flag][\"enable_flag_name\"]]\n    if \"python_workers\" not in flags:\n        flags.append(\"python_workers\")\n    for key, value in BUNDLE_VERSION_INFO.items():\n        # With all-compat-flags variant we might end up accidently using a newer python bundle than\n        # intended. To make sure we get the speicific intended version we also need to disable newer\n        # python versions.\n        if python_flag != key and value[\"enable_flag_name\"] not in [\"python_workers\", \"python_workers_development\"]:\n            flags.append(\"no_\" + value[\"enable_flag_name\"])\n    return flags\n\ndef _py_wd_test_helper(\n        name,\n        src,\n        python_flag,\n        *,\n        make_snapshot,\n        use_snapshot,\n        args,\n        feature_flags,\n        data = [],\n        **kwargs):\n    name_flag = name + \"_\" + python_flag\n    templated_src = name_flag.replace(\"/\", \"-\") + \"@template\"\n    templated_src = \"/\".join(src.split(\"/\")[:-1] + [templated_src])\n\n    pkg_tag = BUNDLE_VERSION_INFO[python_flag][\"packages\"]\n    data = data + [\"@all_pyodide_wheels_%s//:whls\" % pkg_tag]\n    args = args + [\"--pyodide-package-disk-cache-dir\"]\n\n    # +pyodide+ is a bzlmod canonical repository name\n    args.append(\"../+pyodide+all_pyodide_wheels_%s\" % pkg_tag)\n\n    load_snapshot = None\n    pyodide_version = BUNDLE_VERSION_INFO[python_flag][\"real_pyodide_version\"]\n    if use_snapshot == \"stacked\":\n        if pyodide_version == \"0.26.0a2\":\n            use_snapshot = None\n        else:\n            use_snapshot = \"baseline\"\n            if make_snapshot:\n                feature_flags = feature_flags + [\"python_dedicated_snapshot\"]\n    if use_snapshot:\n        version_info = BUNDLE_VERSION_INFO[python_flag]\n\n        snapshot = version_info[use_snapshot + \"_snapshot\"]\n        data = data + [\":python_snapshots\"]\n        load_snapshot = snapshot\n\n    if load_snapshot and not make_snapshot:\n        args += [\"--python-load-snapshot\", \"load_snapshot.bin\"]\n\n    flags = _get_enable_flags(python_flag) + feature_flags\n    feature_flags_txt = \",\".join(['\"{}\"'.format(flag) for flag in flags])\n\n    expand_template(\n        name = name_flag + \"@rule\",\n        out = templated_src,\n        template = src,\n        substitutions = {\"%PYTHON_FEATURE_FLAGS\": feature_flags_txt},\n    )\n\n    # Since we bumped the development flag to point to 0.28.2, it doesn't work on windows CI.\n    # TODO: Fix this.\n    if python_flag == \"development\":\n        kwargs[\"target_compatible_with\"] = select({\n            \"@platforms//os:windows\": [\"@platforms//:incompatible\"],\n            \"//conditions:default\": [],\n        })\n\n    wd_test(\n        src = templated_src,\n        name = name_flag,\n        args = args,\n        python_snapshot_test = make_snapshot,\n        data = data,\n        load_snapshot = load_snapshot,\n        # TODO(soon): at the time of disabling these they all passed but because of how slow python\n        #             tests are we disabled them for now. We should re-enable them when we have\n        #             a better way to run them.\n        generate_all_autogates_variant = False,\n        generate_all_compat_flags_variant = False,\n        **kwargs\n    )\n\ndef _snapshot_file(snapshot):\n    if not snapshot:\n        return []\n    copy_file(\n        name = \"pyodide-snapshot-%s@copy\" % snapshot,\n        src = \"@pyodide-snapshot-%s//file\" % snapshot,\n        out = snapshot,\n        visibility = [\"//visibility:public\"],\n    )\n    return [\":\" + snapshot]\n\ndef _snapshot_files(\n        name,\n        baseline_snapshot = None,\n        numpy_snapshot = None,\n        fastapi_snapshot = None,\n        dedicated_fastapi_snapshot = None,\n        **_kwds):\n    if name == \"development\":\n        return []\n    result = []\n    result += _snapshot_file(baseline_snapshot)\n    result += _snapshot_file(numpy_snapshot)\n    result += _snapshot_file(fastapi_snapshot)\n    result += _snapshot_file(dedicated_fastapi_snapshot)\n    return result\n\ndef _snapshot_file_group():\n    snapshots = []\n    for x in BUNDLE_VERSION_INFO.values():\n        snapshots += _snapshot_files(**x)\n\n    native.filegroup(\n        name = \"python_snapshots\",\n        data = snapshots,\n        visibility = [\"//visibility:public\"],\n    )\n\ndef _capnp_bundle(id, **_kwds):\n    if id == \"dev\":\n        return\n    name = \"pyodide_%s.capnp.bin\" % id\n    copy_file(\n        name = name + \"@rule\",\n        src = \"@%s//file\" % name,\n        out = \"pyodide-bundle-cache/\" + name,\n        visibility = [\"//visibility:public\"],\n    )\n\ndef _capnp_bundles_file_group():\n    # pyodide_dev.capnp.bin represents a custom pyodide version \"dev\" that is generated\n    # at build time using the latest contents of the src/pyodide directory.\n    # This is used to run tests to ensure that they are always run against the latest build of\n    # the Pyodide bundle.\n    copy_file(\n        name = \"pyodide_dev.capnp.bin@rule\",\n        src = \"//src/pyodide:pyodide.capnp.bin_cross\",\n        out = \"pyodide-bundle-cache/pyodide_dev.capnp.bin\",\n        visibility = [\"//visibility:public\"],\n    )\n    for info in BUNDLE_VERSION_INFO.values():\n        _capnp_bundle(**info)\n\ndef _capnp_rules():\n    return [\"//src/workerd/server/tests/python:pyodide_%s.capnp.bin@rule\" % info[\"id\"] for info in BUNDLE_VERSION_INFO.values()]\n\ndef python_test_setup():\n    _capnp_bundles_file_group()\n    _snapshot_file_group()\n\ndef compute_python_flags(python_flags, skip_python_flags):\n    if python_flags == \"all\":\n        python_flags = BUNDLE_VERSION_INFO.keys()\n    python_flags = [flag for flag in python_flags if flag not in skip_python_flags and flag in BUNDLE_VERSION_INFO]\n    return python_flags\n\ndef py_wd_test(\n        directory = None,\n        *,\n        src = None,\n        data = None,\n        name = None,\n        python_flags = \"all\",\n        skip_python_flags = [],\n        feature_flags = [],\n        args = [],\n        size = \"enormous\",\n        tags = [],\n        make_snapshot = True,\n        use_snapshot = \"stacked\",\n        skip_default_data = False,\n        **kwargs):\n    python_flags = compute_python_flags(python_flags, skip_python_flags)\n    if data == None:\n        data = []\n    if directory and not skip_default_data:\n        data += native.glob(\n            [\n                directory + \"/**\",\n            ],\n            exclude = [\"**/*.wd-test\"],\n        )\n    if src == None:\n        src = native.glob([directory + \"/*.wd-test\"])[0]\n    if name == None and directory != None:\n        name = directory\n    elif name == None:\n        name = src.removesuffix(\".wd-test\")\n    data += _capnp_rules()\n    args = args + [\n        \"--pyodide-bundle-disk-cache-dir\",\n        \"$(location //src/workerd/server/tests/python:pyodide_dev.capnp.bin@rule)/..\",\n        \"--experimental\",\n        \"--python-snapshot-dir\",\n        \".\",\n    ]\n\n    # Python tests are extremely slow with coverage instrumentation, skip them\n    tags = tags + [\"py_wd_test\", \"python\", \"no-coverage\"]\n\n    for python_flag in python_flags:\n        _py_wd_test_helper(\n            name,\n            src,\n            python_flag,\n            make_snapshot = make_snapshot,\n            use_snapshot = use_snapshot,\n            feature_flags = feature_flags,\n            data = data,\n            args = args,\n            size = size,\n            tags = tags,\n            **kwargs\n        )\n"
  },
  {
    "path": "src/workerd/server/tests/python/pytest/main.py",
    "content": "import os\n\nimport pytest\nfrom workers import WorkerEntrypoint\n\nfrom pyodide.webloop import WebLoop\n\n\nasync def noop(*args):\n    pass\n\n\n# pytest-asyncio relies on these but in Pyodide < 0.29 WebLoop does not implement them\nWebLoop.shutdown_asyncgens = noop\nWebLoop.shutdown_default_executor = noop\n\n\nclass Default(WorkerEntrypoint):\n    async def test(self):\n        os.chdir(\"/session/metadata/tests\")\n        assert pytest.main([\".\", \"-vv\"]) == 0\n"
  },
  {
    "path": "src/workerd/server/tests/python/pytest/pytest.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"pytest-vendor-test\",\n      worker = (\n        modules = [\n          (name = \"main.py\", pythonModule = embed \"pytest/main.py\"),\n          (name = \"tests/test_env.py\", pythonModule = embed \"pytest/tests/test_env.py\"),\n          (name = \"tests/test_fs.py\", pythonModule = embed \"pytest/tests/test_fs.py\"),\n          (name = \"tests/test_import_from_javascript.py\", pythonModule = embed \"pytest/tests/test_import_from_javascript.py\"),\n          (name = \"tests/test_dynlib_loading.py\", pythonModule = embed \"pytest/tests/test_dynlib_loading.py\"),\n          %PYTHON_VENDORED_MODULES%\n        ],\n        compatibilityFlags = [\n          %PYTHON_FEATURE_FLAGS,\n          \"python_no_global_handlers\",\n          \"unwrap_custom_thenables\",\n        ],\n        bindings = [\n          (\n            name = \"secret\",\n            text = \"thisisasecret\"\n          ),\n          (name = \"FOO\", text = \"BAR\"),\n          (name = \"TEST_VALUE\", text = \"TEST_STRING\"),\n          (name = \"CACHE\", memoryCache = (\n            id = \"abc123\",\n            limits = (\n              maxKeys = 10,\n              maxValueSize = 1024,\n              maxTotalValueSize = 1024,\n            ),\n          )),\n        ],\n      ),\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/pytest/tests/test_dynlib_loading.py",
    "content": "from pathlib import Path\n\nimport pytest\n\n\ndef use(x):\n    pass\n\n\ndef test_dynlib_loading(tmp_path, monkeypatch):\n    # fmt: off\n    # The data here was produced as follows:\n    #\n    # touch a.c\n    # emcc -c a.c\n    # emcc a.o -o a.so -sSIDE_MODULE\n    # wasm-strip a.so -R target_features\n    # xxd -i a.so\n    #\n    # I used emcc 4.0.9.\n    Path(tmp_path / \"a.so\").write_bytes(\n        bytes(\n            [\n                0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x08, 0x64,\n                0x79, 0x6c, 0x69, 0x6e, 0x6b, 0x2e, 0x30, 0x01, 0x04, 0x00, 0x00, 0x00,\n                0x00, 0x01, 0x04, 0x01, 0x60, 0x00, 0x00, 0x02, 0x38, 0x03, 0x03, 0x65,\n                0x6e, 0x76, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x00,\n                0x03, 0x65, 0x6e, 0x76, 0x0d, 0x5f, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72,\n                0x79, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x03, 0x7f, 0x00, 0x03, 0x65, 0x6e,\n                0x76, 0x0c, 0x5f, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x62, 0x61,\n                0x73, 0x65, 0x03, 0x7f, 0x00, 0x03, 0x02, 0x01, 0x00, 0x07, 0x15, 0x01,\n                0x11, 0x5f, 0x5f, 0x77, 0x61, 0x73, 0x6d, 0x5f, 0x63, 0x61, 0x6c, 0x6c,\n                0x5f, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x00, 0x00, 0x0a, 0x04, 0x01, 0x02,\n                0x00, 0x0b\n            ]\n        )\n    )\n    # fmt: on\n    monkeypatch.syspath_prepend(tmp_path)\n    with pytest.raises(\n        ImportError, match=\"Can only load shared libraries from read only file systems\"\n    ):\n        import a\n\n        use(a)\n"
  },
  {
    "path": "src/workerd/server/tests/python/pytest/tests/test_env.py",
    "content": "import pytest\nfrom js import Date, Reflect\nfrom workers import env, import_from_javascript, patch_env\n\nfrom pyodide import __version__\nfrom pyodide.ffi import create_proxy, to_js\n\nif __version__ == \"0.26.0a2\":\n    pytest.skip(\"Not supported on 0.26.0a2\", allow_module_level=True)\n\n\n@pytest.mark.asyncio\nasync def test_memory_cache():\n    # Test that cache exists and is accessible\n    assert env.CACHE, \"env.CACHE should exist\"\n\n    # Test accessing the cache\n    async def memory_cache_read(x):\n        return to_js(\n            {\n                \"value\": 123,\n                \"expiration\": Date.now() + 10000,\n            }\n        )\n\n    cached = await env.CACHE.read(\n        \"hello\",\n        create_proxy(memory_cache_read),\n    )\n    assert cached == 123, \"cached value should be 123\"\n\n\n@pytest.mark.asyncio\nasync def test_with_env():\n    workers_module = import_from_javascript(\"cloudflare:workers\")\n\n    # Test withEnv\n    async def import_with_env():\n        workers_module = import_from_javascript(\"cloudflare:workers\")\n        env = workers_module.env\n        return env.FOO\n\n    result = await workers_module.withEnv(to_js({\"FOO\": 1}), import_with_env)\n    assert result == 1\n\n\ndef check_normal():\n    assert env.secret == \"thisisasecret\"\n    assert not hasattr(env, \"NAME\")\n    assert not hasattr(env, \"place\")\n    assert not hasattr(env, \"extra\")\n    assert set(Reflect.ownKeys(env).to_py()).issuperset({\"secret\"})\n\n\ndef test_patch_env():\n    check_normal()\n\n    with patch_env(NAME=1, place=\"Somewhere\"):\n        assert env.NAME == 1\n        assert env.place == \"Somewhere\"\n        assert not hasattr(env, \"secret\")\n\n    check_normal()\n\n    with patch_env({\"NAME\": 1, \"place\": \"Somewhere\"}):\n        assert env.NAME == 1\n        assert env.place == \"Somewhere\"\n        assert not hasattr(env, \"secret\")\n\n    check_normal()\n\n    with patch_env([(\"NAME\", 1), (\"place\", \"Somewhere\")]):\n        assert env.NAME == 1\n        assert env.place == \"Somewhere\"\n        assert not hasattr(env, \"secret\")\n\n    check_normal()\n\n    with patch_env({\"NAME\": 1, \"place\": \"Somewhere\"}, extra=22):\n        assert env.NAME == 1\n        assert env.place == \"Somewhere\"\n        assert env.extra == 22\n        assert not hasattr(env, \"secret\")\n\n    check_normal()\n\n    try:\n        with patch_env(NAME=1, place=\"Somewhere\"):\n            raise RuntimeError(\"oops\")  # noqa: TRY301\n    except Exception:\n        pass\n\n    check_normal()\n"
  },
  {
    "path": "src/workerd/server/tests/python/pytest/tests/test_fs.py",
    "content": "import os\nfrom pathlib import Path\n\n\ndef test_seek_metadata_fs():\n    fh = Path(__file__).open()\n    print(fh.fileno())\n    print(f\"This file is {os.lseek(fh.fileno(), 0, os.SEEK_END)} characters long\")\n"
  },
  {
    "path": "src/workerd/server/tests/python/pytest/tests/test_import_from_javascript.py",
    "content": "import pytest\nfrom js import setTimeout\nfrom workers import import_from_javascript\n\nfrom pyodide import __version__\nfrom pyodide.ffi import create_once_callable\n\n\ndef test_import_from_javascript():\n    # Import the workers module from cloudflare:workers\n    workers_module = import_from_javascript(\"cloudflare:workers\")\n\n    # Test that we can access the imported module's exports\n    assert hasattr(workers_module, \"env\"), \"env property should exist\"\n    assert hasattr(workers_module, \"withEnv\"), \"withEnv function should exist\"\n    assert hasattr(workers_module, \"waitUntil\"), \"waitUntil function should exist\"\n\n    # Test with actual env values\n    assert workers_module.env.TEST_VALUE == \"TEST_STRING\", (\n        \"TEST_VALUE should be correctly set\"\n    )\n\n    # Test sockets module\n    sockets_module = import_from_javascript(\"cloudflare:sockets\")\n    assert hasattr(sockets_module, \"connect\"), (\n        \"sockets_module should have 'connect' attribute\"\n    )\n\n    def f():\n        pass\n\n    setTimeout(create_once_callable(f), 1)\n\n\n@pytest.mark.skipif(__version__ == \"0.26.0a2\", reason=\"Requires JSPI\")\ndef test_import_vectorize():\n    # Assert that imports that depend on JSPI work as expected\n    vectorize = import_from_javascript(\"cloudflare:vectorize\")\n    assert hasattr(vectorize, \"DistanceMetric\")\n\n\ndef test_import_failures():\n    # Try importing an unsafe module which should fail\n    with pytest.raises(ImportError):\n        import_from_javascript(\"internal:unsafe-eval\")\n\n    # Assert that non existent modules throw ImportError\n    with pytest.raises(ImportError):\n        import_from_javascript(\"crypto\")\n"
  },
  {
    "path": "src/workerd/server/tests/python/python-compat-flag/python-compat-flag.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"python-compat-flag\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"worker.py\"),\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"python_workflows\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/python-compat-flag/worker.py",
    "content": "import _cloudflare_compat_flags\nfrom workers import WorkerEntrypoint\n\n\nclass Default(WorkerEntrypoint):\n    def test(self):\n        assert _cloudflare_compat_flags.python_workflows\n        assert not _cloudflare_compat_flags.python_no_global_handlers\n"
  },
  {
    "path": "src/workerd/server/tests/python/python-rpc/python-rpc.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"py\", worker = .pyWorker),\n    (name = \"js\", worker = .jsWorker),\n  ],\n);\n\nconst pyWorker :Workerd.Worker = (\n\n  compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"rpc\", \"disable_python_no_global_handlers\", \"set_tostring_tag\"],\n\n  modules = [\n    (name = \"worker.py\", pythonModule = embed \"worker.py\"),\n  ],\n\n  bindings = [\n    (name = \"PythonRpc\", service = (name = \"py\", entrypoint = \"PythonRpcTester\")),\n    (name = \"JsRpc\", service = (name = \"js\", entrypoint = \"JsRpcTester\")),\n    (name = \"FOO\", text = \"text binding\"),\n  ],\n);\n\nconst jsWorker :Workerd.Worker = (\n\n  compatibilityFlags = [\"nodejs_compat\", \"rpc\"],\n\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\"),\n  ],\n\n  bindings = [\n    (name = \"PythonRpc\", service = (name = \"py\", entrypoint = \"PythonRpcTester\")),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/python-rpc/worker.js",
    "content": "import { WorkerEntrypoint } from 'cloudflare:workers';\n\nimport * as assert from 'node:assert';\n\nexport class JsRpcTester extends WorkerEntrypoint {\n  async noArgs() {\n    return 'hello from js';\n  }\n  async oneArg(a) {\n    return `${a}`;\n  }\n  async identity(x) {\n    return x;\n  }\n  async handleResponse(resp) {\n    // Verify that we receive a JS object here...\n    assert.deepStrictEqual(resp.constructor.name, 'Response');\n    return resp;\n  }\n\n  async handleRequest(req) {\n    assert.deepStrictEqual(req.constructor.name, 'Request');\n    return req;\n  }\n}\n\nexport default {\n  async test(ctrl, env, ctx) {\n    // JS types\n    for (const val of [\n      1,\n      'test',\n      [1, 2, 3],\n      new Map([['key', 42]]),\n      42,\n      1.2345,\n      false,\n      true,\n      undefined,\n    ]) {\n      const response = await env.PythonRpc.identity(val);\n      assert.deepStrictEqual(response, val);\n    }\n\n    const null_resp = await env.PythonRpc.identity(null);\n    assert.ok(null_resp === null || null_resp === undefined);\n\n    // Web/API Types\n    const py_response = await env.PythonRpc.handle_response(\n      new Response('this is a response')\n    );\n    assert.deepStrictEqual(await py_response.text(), 'this is a response');\n    assert.equal(py_response.constructor.name, 'Response');\n\n    const py_request = await env.PythonRpc.handle_request(\n      new Request('https://test.com', { method: 'POST' })\n    );\n    assert.deepStrictEqual(py_request.method, 'POST');\n    assert.equal(py_request.constructor.name, 'Request');\n  },\n};\n"
  },
  {
    "path": "src/workerd/server/tests/python/python-rpc/worker.py",
    "content": "# Copyright (c) 2023 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\nimport collections.abc\nimport json\nfrom asyncio import Future, sleep\nfrom datetime import datetime\nfrom http import HTTPMethod\nfrom unittest import TestCase\n\nimport js\nfrom workers import Blob, Request, Response, WorkerEntrypoint, handler\n\nfrom pyodide.ffi import JsException, JsProxy, to_js\n\nassertRaises = TestCase().assertRaises\nassertRaisesRegex = TestCase().assertRaisesRegex\n\ntestFuture = Future()\n\n\nclass PythonRpcTester(WorkerEntrypoint):\n    def __init__(self, ctx, env):\n        super().__init__(ctx, env)\n        # Verify that the superclass constructor initialises the env/ctx fields.\n        assert self.env is not None\n        assert self.ctx is not None\n\n        assert not isinstance(env, JsProxy)\n        self.env = env\n\n    async def no_args(self):\n        return \"hello from python\"\n\n    async def one_arg(self, x):\n        return f\"{x}\"\n\n    async def identity(self, x):\n        assert not isinstance(x, JsProxy)\n        return x\n\n    async def handle_response(self, response):\n        # Verify that we receive a Python object here...\n        assert isinstance(response, Response)\n        return response\n\n    async def handle_request(self, req):\n        assert isinstance(req, Request)\n        return req\n\n    async def check_env(self):\n        # Verify that the `env` supplied to the entrypoint class is wrapped.\n        curr_date = datetime.now()\n        py_date = await self.env.PythonRpc.identity(curr_date)\n        assert isinstance(py_date, datetime)\n        # JavaScript Date objects only have millisecond precision\n        assert abs((py_date - curr_date).microseconds) < 1e6\n\n        return True\n\n    async def sleep_then_set_result(self):\n        await sleep(0.1)\n        testFuture.set_result(100)\n\n    async def test_wait_until_coroutine_lifetime(self):\n        self.ctx.waitUntil(self.sleep_then_set_result())\n\n\nclass CustomType:\n    def __init__(self, x):\n        self.x = 42\n\n\ndef assert_equal(a, b, avoid_type_check):\n    # Convert to JSON so that the contents of the values can be easily compared.\n    received = json.dumps(\n        json.loads(js.JSON.stringify(a) if isinstance(a, JsProxy) else json.dumps(a))\n    )\n    expected = json.dumps(\n        json.loads(js.JSON.stringify(b) if isinstance(b, JsProxy) else json.dumps(b))\n    )\n    if received != expected:\n        raise ValueError(\n            f\"Assert failed, args contents are not equal. received='{received}' expected='{expected}'\"\n        )\n\n    if type(a) is not type(b) and not avoid_type_check:\n        raise ValueError(\n            f\"Assert failed, types don't match. received={type(a)} expected={type(b)}\"\n        )\n\n\n@handler\nasync def test(ctrl, env, ctx):\n    # https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#supported_types\n    #\n    # Workers RPC doesn't support all of the above, but does support all the JS Types and\n    # some of the Web API types.\n    #\n    # ReadableStream and WritableStream are also supported.\n    #\n    # We verify that we can send and receive as much of these as possible, including between\n    # Python<->Python, JS<->Python and vice versa. We also ensure that the types we receive are\n    # native Python types, rather than `JsProxy`s.\n\n    # Simple tests.\n    assert await env.PythonRpc.no_args() == \"hello from python\"\n    assert await env.JsRpc.noArgs() == \"hello from js\"\n    assert await env.PythonRpc.one_arg(42) == \"42\"\n    assert await env.JsRpc.oneArg(42) == \"42\"\n    arr = await env.PythonRpc.identity([1, 2, 3])\n    assert isinstance(arr, collections.abc.Sequence) and not isinstance(arr, str)\n\n    # Verify that text bindings can be accessed.\n    assert env.FOO == \"text binding\"\n\n    # Python Types\n    for val in [\"test\", [1, 2, 3], {\"key\": 42}, 42, 1.2345, False, True, None]:\n        received = await env.PythonRpc.identity(val)\n        assert not isinstance(received, JsProxy), (\n            \"Expected the returned value from RPC to be a Python type.\"\n        )\n        assert_equal(received, val, False)\n        received = await env.JsRpc.identity(val)\n        assert not isinstance(received, JsProxy), (\n            \"Expected the returned value from RPC to be a Python type.\"\n        )\n        assert_equal(received, val, False)\n\n    curr_date = datetime.now()\n    py_date = await env.PythonRpc.identity(curr_date)\n    assert isinstance(py_date, datetime)\n    # JavaScript Date objects only have millisecond precision\n    assert abs((py_date - curr_date).microseconds) < 1e6\n\n    py_date_list = await env.PythonRpc.identity([datetime.now(), datetime.now()])\n    for d in py_date_list:\n        assert isinstance(d, datetime)\n\n    py_set = await env.PythonRpc.identity({1, 2, 3})\n    assert isinstance(py_set, set)\n    assert 2 in py_set\n    assert 42 not in py_set\n\n    py_binary = await env.PythonRpc.identity(b\"binary\")\n    assert isinstance(py_binary, memoryview)\n    py_binary = await env.PythonRpc.identity(memoryview(b\"abcefg\"))\n    assert isinstance(py_binary, memoryview)\n\n    # JS types\n    for val in [\n        to_js([1, 2, 3, 4]),\n        js.Number.new(\"1234\"),\n    ]:\n        received = await env.PythonRpc.identity(val)\n        assert not isinstance(received, JsProxy), (\n            \"Expected the returned value from RPC to be a Python type.\"\n        )\n        assert_equal(received, val.to_py(), True)\n        received = await env.JsRpc.identity(val)\n        assert not isinstance(received, JsProxy), (\n            \"Expected the returned value from RPC to be a Python type.\"\n        )\n        assert_equal(received, val.to_py(), True)\n\n    for val in [\n        js.ArrayBuffer.new(8),\n        js.DataView.new(js.ArrayBuffer.new(16)),\n        js.Int16Array.of(\"10\", \"24\"),\n    ]:\n        js_binary = await env.PythonRpc.identity(val)\n        assert isinstance(js_binary, memoryview)\n        js_binary = await env.JsRpc.identity(val)\n        assert isinstance(js_binary, memoryview)\n\n    js_map = await env.PythonRpc.identity(\n        js.Map.new(\n            [\n                [1, \"one\"],\n                [2, \"two\"],\n                [3, \"three\"],\n            ]\n        ),\n    )\n    assert isinstance(js_map, dict)\n    assert js_map[1] == \"one\"\n\n    js_set = await env.PythonRpc.identity(\n        js.Set.new([1, 2, 3, 4]),\n    )\n    assert isinstance(js_set, set)\n    assert 1 in js_set\n    assert 42 not in js_set\n\n    js_undefined = await env.PythonRpc.identity(js.undefined)\n    assert js_undefined is None\n\n    js_date = await env.PythonRpc.identity(js.Date.new())\n    assert isinstance(js_date, datetime)\n\n    js_date_list = await env.PythonRpc.identity([js.Date.new(), js.Date.new()])\n    for d in js_date_list:\n        assert isinstance(d, datetime)\n\n    js_exception = await env.PythonRpc.identity(js.Error.new(\"message\"))\n    assert isinstance(js_exception, Exception)\n\n    js_obj = await env.PythonRpc.identity(\n        to_js({\"foo\": 42}, dict_converter=js.Object.fromEntries)\n    )\n    assert isinstance(js_obj, dict)\n    assert js_obj[\"foo\"] == 42\n\n    # Web/API Types\n    # - Response\n    py_response = await env.PythonRpc.handle_response(Response(\"this is a response\"))\n    assert isinstance(py_response, Response)\n    assert await py_response.text() == \"this is a response\"\n    js_response = await env.JsRpc.handleResponse(Response(\"this is a response\"))\n    assert await js_response.text() == \"this is a response\"\n    assert isinstance(js_response, Response)\n\n    # - Request\n    py_response = await env.PythonRpc.handle_request(\n        Request(\"https://test.com\", method=HTTPMethod.POST)\n    )\n    assert isinstance(py_response, Request)\n    assert py_response.method == \"POST\"\n    js_response = await env.JsRpc.handleRequest(\n        Request(\"https://test.com\", method=HTTPMethod.POST)\n    )\n    assert js_response.method == \"POST\"\n    assert isinstance(js_response, Request)\n\n    # - Verify that a JS type can be sent.\n    py_response2 = await env.PythonRpc.handle_response(js.Response.new(\"a JS response\"))\n    assert await py_response2.text() == \"a JS response\"\n\n    # Verify that sending unsupported types fails.\n    data_clone_regex = \"^DataCloneError\"\n    with assertRaisesRegex(JsException, data_clone_regex):\n        await env.PythonRpc.one_arg(CustomType(42))\n    with assertRaisesRegex(JsException, data_clone_regex):\n        await env.PythonRpc.one_arg(Blob(\"print(42)\", content_type=\"text/python\"))\n    with assertRaisesRegex(JsException, data_clone_regex):\n        await env.PythonRpc.identity(complex(1.23))\n    with assertRaises(TypeError):\n        await env.PythonRpc.identity((1, 2, 3))\n    with assertRaisesRegex(JsException, data_clone_regex):\n        await env.PythonRpc.identity(range(0, 30, 5))\n    with assertRaises(TypeError):\n        await env.PythonRpc.identity(bytearray.fromhex(\"2Ef0 F1f2  \"))\n    with assertRaises(TypeError):\n        await env.PythonRpc.identity([(1, 2, 3)])\n    # TODO: Support RegExp.\n    with assertRaises(TypeError):\n        await env.PythonRpc.identity(js.RegExp.new(\"ab+c\", \"i\"))\n    with assertRaises(TypeError):\n        await env.PythonRpc.identity(lambda x: x + x)\n    with assertRaises(TypeError):\n\n        def my_func():\n            pass\n\n        await env.PythonRpc.identity(my_func)\n    with assertRaises(TypeError):\n        await env.PythonRpc.identity({\"test\": (1, 2, 3)})\n\n    # Verify that the `env` in the DO is correctly wrapped.\n    assert await env.PythonRpc.check_env()\n\n    # Check that the coroutine returned by sleep_then_set_result() lasts long enough.\n    # sleep_then_set_result() resolves testFuture after sleeping for 100ms.\n    await env.PythonRpc.test_wait_until_coroutine_lifetime()\n    assert not testFuture.done()\n    await sleep(0.2)\n    assert testFuture.result() == 100\n"
  },
  {
    "path": "src/workerd/server/tests/python/random/random.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"python-hello\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"worker.py\")\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\", \"disable_python_check_rng_state\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/random/worker.py",
    "content": "\"\"\"\nVerify that calling `random` at the top-level throws.\n\nCalls to random should only work inside a request context.\n\"\"\"\n\n# Disable do not `assert False` lint\n# ruff: noqa: B011\n\nfrom random import choice, randbytes, random\n\ntry:\n    random()\nexcept RuntimeError as e:\n    assert (\n        repr(e)\n        == \"RuntimeError('Cannot use random.random() outside of request context')\"\n    )\nelse:\n    assert False\n\ntry:\n    randbytes(5)\nexcept RuntimeError as e:\n    assert (\n        repr(e)\n        == \"RuntimeError('Cannot use random.randbytes() outside of request context')\"\n    )\nelse:\n    assert False\n\ntry:\n    choice([1, 2, 3])\nexcept RuntimeError as e:\n    assert (\n        repr(e)\n        == \"RuntimeError('Cannot use random.choice() outside of request context')\"\n    )\nelse:\n    assert False\n\n\ndef t1():\n    from random import randbytes, random\n\n    random()\n    randbytes(5)\n    choice([1, 2, 3])\n\n\ndef t2():\n    random()\n    randbytes(5)\n    choice([1, 2, 3])\n\n    t1()\n\n\ndef test():\n    t2()\n"
  },
  {
    "path": "src/workerd/server/tests/python/sdk/proxy.js",
    "content": "export default {\n  async fetch(req, env) {\n    const url = new URL(req.url);\n    if (\n      url.hostname == 'python-packages.edgeworker.net' ||\n      url.hostname == 'pyodide-packages.runtime-playground.workers.dev'\n    ) {\n      return env.INTERNET.fetch(req);\n    } else if (url.hostname == 'example.com') {\n      return env.PYTHON.fetch(req);\n    }\n\n    throw new Error('Invalid url in proxy.js: ' + url);\n  },\n};\n"
  },
  {
    "path": "src/workerd/server/tests/python/sdk/sdk.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst python :Workerd.Worker = (\n  modules = [\n    (name = \"worker.py\", pythonModule = embed \"worker.py\")\n  ],\n  bindings = [\n    ( name = \"SELF\", service = \"python-sdk\" ),\n  ],\n  compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"service_binding_extra_handlers\", \"rpc\", \"python_no_global_handlers\", \"fetch_standard_url\", \"formdata_parser_supports_files\", \"python_request_headers_preserve_commas\"],\n);\n\nconst server :Workerd.Worker = (\n  modules = [\n    (name = \"server.py\", pythonModule = embed \"server.py\")\n  ],\n  compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"rpc\", \"python_no_global_handlers\", \"fetch_standard_url\", \"formdata_parser_supports_files\", \"python_request_headers_preserve_commas\"],\n);\n\n# We need this proxy so that internal requests made to fetch packages for Python Workers are sent\n# out to the open internet and not to the python-server worker.\nconst jsChooserProxy :Workerd.Worker = (\n  modules = [\n    (name = \"proxy.js\", esModule = embed \"proxy.js\")\n  ],\n  bindings = [\n    ( name = \"PYTHON\", service = \"python-server\" ),\n    ( name = \"INTERNET\", service = \"external\" ),\n  ],\n);\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"external\",\n      network = (\n        tlsOptions = (trustBrowserCas = true)\n      )\n    ),\n    ( name = \"internet\",\n      worker = .jsChooserProxy\n    ),\n    ( name = \"python-server\",\n      worker = .server\n    ),\n    ( name = \"python-sdk\",\n      worker = .python\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/sdk/server.py",
    "content": "from workers import Blob, FormData, Response, WorkerEntrypoint\n\n\nclass Default(WorkerEntrypoint):\n    async def fetch(self, request):\n        if request.url.endswith(\"/sub\"):\n            return Response(\"Hi there!\")\n        elif request.url.endswith(\"/sub_headers\"):\n            assert request.headers.get(\"Custom-Req-Header\") == \"123\"\n            return Response(\n                \"Hi there!\", headers={\"Custom-Header-That-Should-Passthrough\": True}\n            )\n        elif request.url.endswith(\"/redirect\"):\n            return Response.redirect(\"https://example.com/sub\", status=301)\n        elif request.url.endswith(\"/formdata\"):\n            data = FormData({\"field\": \"value\"})\n            return Response(data)\n        elif request.url.endswith(\"/formdatablob\"):\n            data = FormData({\"field\": \"value\"})\n            data[\"blob.py\"] = Blob(\"print(42)\", content_type=\"text/python\")\n            data.append(\n                \"metadata\",\n                Blob(\"{}\", content_type=\"text/python\"),\n                filename=\"metadata.json\",\n            )\n            return Response(data)\n        elif request.url.endswith(\"/ignore\"):\n            # Just a test path that we ignore, used for requests that we don't care about\n            # the result of.\n            return Response(\"ignored\")\n        else:\n            raise ValueError(\"Unexpected path \" + request.url)\n\n    def test(self):\n        pass\n"
  },
  {
    "path": "src/workerd/server/tests/python/sdk/worker.py",
    "content": "# The tests in this file are primarily spread across Default.fetch() (in this module) and\n# Default.fetch() (in server.py).\n#\n# The code in `Default.test()` (in this module) is used to actually perform the testing and its\n# behaviour doesn't need to strictly be held consistent. In fact it uses the JS fetch, so it's not\n# going to follow the SDK at all.\n\nfrom contextlib import asynccontextmanager\nfrom http import HTTPMethod, HTTPStatus\n\nimport js\nfrom workers import Blob, File, FormData, Request, Response, WorkerEntrypoint, fetch\n\nimport pyodide.http\nfrom pyodide.ffi import JsProxy, to_js\n\n\n@asynccontextmanager\nasync def _mock_fetch(check):\n    async def mocked_fetch(original_fetch, url, opts):\n        check(url, opts)\n        return await original_fetch(url, opts)\n\n    original_fetch = pyodide.http._jsfetch\n    pyodide.http._jsfetch = lambda url, opts: mocked_fetch(original_fetch, url, opts)\n    try:\n        yield\n    finally:\n        pyodide.http._jsfetch = original_fetch\n\n\nclass Default(WorkerEntrypoint):\n    # Each path in this handler is its own test. The URLs that are being fetched\n    # here are defined in server.py.\n    async def fetch(self, request):\n        assert isinstance(request, Request)\n        if request.url.endswith(\"/modify\"):\n            resp = await fetch(\"https://example.com/sub\")\n            return Response(\n                resp.body,\n                status=201,\n                headers={\"Custom-Header-That-Should-Passthrough\": \"modified\"},\n            )\n        elif request.url.endswith(\"/modify_tuple\"):\n            resp = await fetch(\"https://example.com/sub\")\n            return Response(\n                resp.body,\n                headers=[\n                    (\"Custom-Header-That-Should-Passthrough\", \"modified\"),\n                    (\"Custom-Header-That-Should-Passthrough\", \"another\"),\n                ],\n            )\n        elif request.url.endswith(\"/fetch_opts\"):\n            resp = await fetch(\n                \"https://example.com/sub_headers\",\n                method=HTTPMethod.GET,\n                body=None,\n                headers={\"Custom-Req-Header\": 123},\n            )\n            return resp\n        elif request.url.endswith(\"/jsism\"):\n            # Headers should be specified via a keyword argument, but it's done\n            # differently in JS. So we test what happens when someone does it\n            # that way accidentally to make sure we give them a reasonable error\n            # message.\n            try:\n                resp = await fetch(\n                    \"https://example.com/sub\",\n                    {\"headers\": {\"Custom-Req-Header\": 123}},\n                )\n            except TypeError as exc:\n                if str(exc) == \"fetch() takes 1 positional argument but 2 were given\":\n                    return Response(\"success\")\n\n            return Response(\"fail\")\n        elif request.url.endswith(\"/undefined_opts\"):\n            # This tests two things:\n            #   * `Response.redirect` static method\n            #   * that other options can be passed into `fetch` (so that we can support\n            #       new options without updating this code)\n\n            # Mock pyodide.http._jsfetch to ensure `foobarbaz` gets passed in.\n            def fetch_check(url, opts):\n                assert opts.foobarbaz == 42\n\n            async with _mock_fetch(fetch_check):\n                resp = await fetch(\n                    \"https://example.com/redirect\", redirect=\"manual\", foobarbaz=42\n                )\n\n            return resp\n        elif request.url.endswith(\"/response_inherited\"):\n            expected = \"test123\"\n            resp = Response(expected)\n            text = await resp.text()\n            if text == expected:\n                return Response(\"success\")\n            else:\n                return Response(\"invalid\")\n        elif request.url.endswith(\"/redirect_invalid_input\"):\n            try:\n                return Response.redirect(\"\", HTTPStatus.BAD_GATEWAY)\n            except ValueError:\n                return Response(\"success\")\n            return Response(\"invalid\")\n        elif request.url.endswith(\"/response_json\"):\n            return Response.json({\"obj\": {\"field\": 123}}, status=HTTPStatus.NOT_FOUND)\n        elif request.url.endswith(\"/formdata\"):\n            # server.py creates a new FormData and verifies that it can be passed\n            # to Response.\n            resp = await fetch(\"https://example.com/formdata\")\n            data = await resp.formData()\n            if data[\"field\"] == \"value\":\n                return Response(\"success\")\n            else:\n                return Response(\"fail\")\n        elif request.url.endswith(\"/formdatablob\"):\n            # server.py creates a new FormData and verifies that it can be passed\n            # to Response.\n            resp = await fetch(\"https://example.com/formdatablob\")\n            data = await resp.formData()\n            assert data[\"field\"] == \"value\"\n            assert (await data[\"blob.py\"].text()) == \"print(42)\"\n            assert data[\"blob.py\"].content_type == \"text/python\"\n            assert data[\"metadata\"].name == \"metadata.json\"\n\n            return Response(\"success\")\n        elif request.url.endswith(\"/cf_opts\"):\n            resp = await fetch(\n                \"http://example.com/redirect\",\n                redirect=\"manual\",\n                cf={\n                    \"cacheTtl\": 5,\n                    \"cacheEverything\": True,\n                    \"cacheKey\": \"someCustomKey\",\n                },\n            )\n            assert resp.status == 301\n            return Response(\"success\")\n        elif request.url.endswith(\"/event_decorator\"):\n            # Verify that the `@event`` decorator has transformed the `request` parameter to the correct\n            # type.\n            #\n            # Try to grab headers which should contain a duplicated header.\n            headers = request.headers.get_all(\"X-Custom-Header\")\n            assert \"some_value, some_other_value\" in headers\n            return Response(\"success\")\n        else:\n            resp = await fetch(\"https://example.com/sub\")\n            return resp\n\n    async def scheduled(self, ctrl, env, ctx):\n        assert ctrl.scheduledTime == 1000\n        assert ctrl.cron == \"* * * * 30\"\n\n    async def test(self):\n        js_env = self.env._env\n        await can_support_scheduled_cron_trigger(js_env)\n        await can_return_custom_fetch_response(js_env)\n        await can_modify_response(js_env)\n        await can_use_duplicate_headers(js_env)\n        await can_use_fetch_opts(js_env)\n        await gets_nice_error_on_jsism(js_env)\n        await can_use_undefined_options_and_redirect(js_env)\n        await can_use_inherited_response_methods(js_env)\n        await errors_on_invalid_input_to_redirect(js_env)\n        await can_use_response_json(js_env)\n        await can_request_form_data(js_env)\n        await form_data_unit_tests(js_env)\n        await blob_unit_tests(js_env)\n        await can_request_form_data_blob(js_env)\n        await replace_body_unit_tests(js_env)\n        await can_use_cf_fetch_opts(js_env)\n        await request_unit_tests(js_env)\n        await can_use_event_decorator(js_env)\n        await response_unit_tests(js_env)\n        await response_buffer_source_unit_tests(js_env)\n        await can_fetch_python_request()\n\n\n# TODO: Right now the `fetch` that's available on a binding is the JS fetch.\n# We may wish to rewrite it to be the same as the `fetch` defined in\n# `cloudflare.workers`. Doing so will require a feature flag.\n#\n# WARNING: Don't expect the below code itself to make use of the features in the SDK, it's mostly\n# calling the JS fetch via the FFI.\n\n\nasync def can_return_custom_fetch_response(env):\n    assert isinstance(env, JsProxy), (\n        \"Expecting the env for these tests not to be wrapped\"\n    )\n    response = await env.SELF.fetch(\n        \"http://example.com/\",\n    )\n    text = await response.text()\n    assert text == \"Hi there!\"\n\n\nasync def can_modify_response(env):\n    response = await env.SELF.fetch(\n        \"http://example.com/modify\",\n    )\n    text = await response.text()\n    assert text == \"Hi there!\"\n    assert response.status == 201\n    assert response.headers.get(\"Custom-Header-That-Should-Passthrough\") == \"modified\"\n\n\nasync def can_use_duplicate_headers(env):\n    response = await env.SELF.fetch(\n        \"http://example.com/modify_tuple\",\n    )\n    text = await response.text()\n    assert text == \"Hi there!\"\n    assert (\n        response.headers.get(\"Custom-Header-That-Should-Passthrough\")\n        == \"modified, another\"\n    )\n\n\nasync def can_use_fetch_opts(env):\n    response = await env.SELF.fetch(\n        \"http://example.com/fetch_opts\",\n    )\n    text = await response.text()\n    assert text == \"Hi there!\"\n    assert response.headers.get(\"Custom-Header-That-Should-Passthrough\") == \"true\"\n\n\nasync def gets_nice_error_on_jsism(env):\n    response = await env.SELF.fetch(\n        \"http://example.com/jsism\",\n    )\n    text = await response.text()\n    assert text == \"success\"\n\n\nasync def can_use_undefined_options_and_redirect(env):\n    response = await env.SELF.fetch(\n        \"http://example.com/undefined_opts\", redirect=\"manual\"\n    )\n    # The above path in this worker hits server's /redirect path,\n    # which returns a 301 to /sub. The code uses a fetch option which\n    # instructs it to not follow redirects, so we expect to get\n    # a 301 here.\n    assert response.status == 301\n\n\nasync def can_use_inherited_response_methods(env):\n    response = await env.SELF.fetch(\n        \"http://example.com/response_inherited\",\n    )\n    text = await response.text()\n    assert text == \"success\"\n\n\nasync def errors_on_invalid_input_to_redirect(env):\n    response = await env.SELF.fetch(\n        \"http://example.com/redirect_invalid_input\",\n    )\n    text = await response.text()\n    assert text == \"success\"\n\n\nasync def can_use_response_json(env):\n    response = await env.SELF.fetch(\n        \"http://example.com/response_json\",\n    )\n    text = await response.text()\n    assert text == '{\"obj\": {\"field\": 123}}'\n\n\nasync def can_request_form_data(env):\n    response = await env.SELF.fetch(\n        \"http://example.com/formdata\",\n    )\n    text = await response.text()\n    assert text == \"success\"\n\n\nasync def form_data_unit_tests(env):\n    # Verify that existing JS formdata is loaded correctly.\n    js_data = js.FormData.new()\n    js_data.append(\"foobar\", 123)\n    js_data.append(\"key\", \"lorem ipsum\")\n    js_data.append(\"key\", \"dolor sit amet\")\n    data = FormData(js_data)\n    assert data[\"foobar\"] == \"123\"\n    assert data[\"key\"] == \"lorem ipsum\"\n    assert data.get_all(\"key\") == [\"lorem ipsum\", \"dolor sit amet\"]\n    assert \"unknown key\" not in data\n    assert \"key\" in data\n\n    # Verify that dictionary can instantiate form data.\n    data = FormData({\"key\": \"foobar\"})\n    assert data[\"key\"] == \"foobar\"\n    assert \"key\" in data\n\n    # Test iterators\n    data.append(\"key\", \"another\")\n    data.append(\"key2\", \"foobar2\")\n    data_items = set(data.items())\n    assert (\"key\", \"foobar\") in data_items\n    assert (\"key\", \"another\") in data_items\n    assert (\"key2\", \"foobar2\") in data_items\n    data_keys = set(data.keys())\n    assert len(list(data.keys())) == 3\n    assert \"key\" in data_keys\n    assert \"key2\" in data_keys\n    data_values = set(data.values())\n    assert \"foobar\" in data_values\n    assert \"another\" in data_values\n    assert \"foobar2\" in data_values\n\n\nasync def blob_unit_tests(env):\n    # Verify that we can create a FormData and add Blob in there.\n    data = FormData()\n    data[\"test\"] = Blob([\"some string\"])\n    test_contents = await data[\"test\"].text()\n    assert test_contents == \"some string\"\n    data[\"js\"] = js.Blob.new(to_js([\"another string\"]))\n    # Even though we added a JS Blob, we should get a Python Blob.\n    assert isinstance(data[\"js\"], Blob)\n    data.append(\"blah\", js.Blob.new(to_js([\"another string\"])))\n    # Iterating through the items should give us Python Blobs.\n    for key, val in data.items():\n        assert isinstance(key, str)\n        assert isinstance(val, Blob)\n    # Verify that content type can be set.\n    content_type_blob = Blob([\"test\"], content_type=\"application/json\")\n    assert content_type_blob.js_object.type == \"application/json\"\n    # Verify instance properties.\n    assert content_type_blob.size == 4\n    assert content_type_blob.content_type == \"application/json\"\n    # Verify instance methods.\n    assert (await content_type_blob.bytes()) == b\"test\"\n    assert (await content_type_blob.slice(1, 3).text()) == \"es\"\n    # Verify that Blob can be created with a memoryview.\n    memory_data = b\"foobar\"\n    memory_view_blob = Blob([memoryview(memory_data)])\n    assert await memory_view_blob.text() == \"foobar\"\n    # Verify that Blob constructor inherits type.\n    inherited = Blob(content_type_blob)\n    not_inherited = Blob(content_type_blob, content_type=\"other/type\")\n    assert inherited.content_type == \"application/json\"\n    assert not_inherited.content_type == \"other/type\"\n    not_inherited2 = Blob([content_type_blob])\n    assert not_inherited2.content_type == \"\"\n    # Verify that we can create Files.\n    file_blob = File(\"my file\", filename=\"test.txt\", content_type=\"text/plain\")\n    assert file_blob.content_type == \"text/plain\"\n    assert file_blob.name == \"test.txt\"\n\n\nasync def can_request_form_data_blob(env):\n    response = await env.SELF.fetch(\n        \"http://example.com/formdatablob\",\n    )\n    text = await response.text()\n    assert text == \"success\"\n\n\nasync def replace_body_unit_tests(env):\n    response = Response(\"test\", status=201, status_text=\"Created\")\n    cloned = response.replace_body(\"other\")\n    assert cloned.status == 201\n    assert cloned.status_text == \"Created\"\n    t = await cloned.text()\n    assert t == \"other\"\n\n\nasync def can_use_cf_fetch_opts(env):\n    response = await env.SELF.fetch(\n        \"http://example.com/cf_opts\",\n    )\n    text = await response.text()\n    assert text == \"success\"\n\n\nasync def request_unit_tests(env):\n    req = Request(\"https://test.com\", method=HTTPMethod.POST)\n    assert req.method == HTTPMethod.POST\n    assert repr(req) == \"Request(method='POST', url='https://test.com/')\"\n\n    # Verify that we can pass JS headers to Request\n    js_headers = js.Headers.new()\n    js_headers.set(\"foo\", \"bar\")\n    req_with_headers = Request(\"http://example.com\", headers=js_headers)\n    assert req_with_headers.headers[\"foo\"] == \"bar\"\n\n    # Verify that we can pass a dictionary as headers to Request\n    req_with_headers = Request(\"http://example.com\", headers={\"aaaa\": \"test\"})\n    assert req_with_headers.headers[\"aaaa\"] == \"test\"\n\n    # Verify that BodyUserError is thrown correctly.\n    req_used_twice = Request(\n        \"http://example.com\", body='{\"field\": 42}', method=HTTPMethod.POST\n    )\n    data = await req_used_twice.json()\n    assert data[\"field\"] == 42\n    try:\n        req_used_twice.clone()\n        raise ValueError(\"Expected to throw\")  # noqa: TRY301\n    except Exception as exc:\n        assert exc.__class__.__name__ == \"OSError\"  # TODO: BodyUsedError when available\n\n    # Verify that duplicate header keys are returned correctly.\n    js_headers = js.Headers.new()\n    js_headers.append(\"Accept-encoding\", \"deflate\")\n    js_headers.append(\"Accept-encoding\", \"gzip\")\n    req_with_dup_headers = Request(\"http://example.com\", headers=js_headers)\n    assert req_with_dup_headers.url == \"http://example.com/\"\n    encoding = req_with_dup_headers.headers.get_all(\"Accept-encoding\")\n    assert encoding == [\"deflate, gzip\"]\n\n    # Verify that header values containing commas are preserved.\n    js_headers = js.Headers.new()\n    js_headers.set(\"User-Agent\", \"Example, Agent/1.0\")\n    req_with_user_agent = Request(\"http://example.com\", headers=js_headers)\n    assert req_with_user_agent.headers.get(\"User-Agent\") == \"Example, Agent/1.0\"\n    assert req_with_user_agent.headers.get_all(\"User-Agent\") == [\"Example, Agent/1.0\"]\n\n    # Verify that header values with commas are preserved when using Python dict.\n    req_dict_comma = Request(\n        \"http://example.com\",\n        headers={\n            \"User-Agent\": \"Python, Client/2.0\",\n            \"Accept\": \"text/html, application/json\",\n        },\n    )\n    assert req_dict_comma.headers.get(\"User-Agent\") == \"Python, Client/2.0\"\n    assert \"text/html\" in req_dict_comma.headers.get(\"Accept\")\n    assert \"application/json\" in req_dict_comma.headers.get(\"Accept\")\n\n    # Verify that Set-Cookie headers are preserved as distinct values.\n    js_headers = js.Headers.new()\n    js_headers.append(\"Set-Cookie\", \"a=b, c=d\")\n    js_headers.append(\"Set-Cookie\", \"e=f\")\n    req_with_set_cookie = Request(\"http://example.com\", headers=js_headers)\n    assert req_with_set_cookie.headers.get_all(\"Set-Cookie\") == [\"a=b, c=d\", \"e=f\"]\n\n    # Verify that Set-Cookie headers work with Python list of tuples.\n    req_tuple_cookies = Request(\n        \"http://example.com\",\n        headers=[\n            (\"Set-Cookie\", \"session=abc123\"),\n            (\"Set-Cookie\", \"token=xyz789\"),\n            (\"X-Custom\", \"value\"),\n        ],\n    )\n    assert req_tuple_cookies.headers.get_all(\"Set-Cookie\") == [\n        \"session=abc123\",\n        \"token=xyz789\",\n    ]\n    assert req_tuple_cookies.headers.get(\"X-Custom\") == \"value\"\n\n    # Verify that we can get a Blob.\n    req_for_blob = Request(\"http://example.com\", body=\"foobar\", method=\"POST\")\n    blob = await req_for_blob.blob()\n    assert (await blob.text()) == \"foobar\"\n\n    # Verify that we can get a FormData back.\n    js_form_data = js.FormData.new()\n    js_form_data.append(\"foobar\", 123)\n    req_with_form_data = Request(\"http://example.com\", body=js_form_data, method=\"POST\")\n    form_data = await req_with_form_data.form_data()\n    assert form_data[\"foobar\"] == \"123\"\n\n\nasync def can_use_event_decorator(env):\n    js_headers = js.Headers.new()\n    js_headers.append(\"X-Custom-Header\", \"some_value\")\n    js_headers.append(\"X-Custom-Header\", \"some_other_value\")\n    response = await env.SELF.fetch(\n        \"http://example.com/event_decorator\", headers=js_headers\n    )\n    text = await response.text()\n    assert text == \"success\"\n\n\nasync def response_unit_tests(env):\n    response_json = Response.json([1, 2, 3])\n    assert await response_json.text() == \"[1, 2, 3]\"\n    assert (\n        repr(response_json)\n        == \"Response(status=200, status_text='OK', content_type='application/json')\"\n    )\n\n    response_json = Response.from_json([1, 2, 3])\n    assert await response_json.text() == \"[1, 2, 3]\"\n\n    response_json = Response(\"[1, 2, 3]\")\n    assert await response_json.json() == [1, 2, 3]\n\n    response_json = Response.json(\"test\")\n    assert await response_json.text() == '\"test\"'\n\n    response_json = Response.json([\"hi\", \"foo\", 42])\n    assert await response_json.text() == '[\"hi\", \"foo\", 42]'\n\n    response_json = Response.json({\"field\": 42})\n    assert await response_json.text() == '{\"field\": 42}'\n\n    response_json = Response.json(\"test\")\n    assert response_json.headers.get(\"content-type\") == \"application/json\"\n\n    response_json = Response.json(\"test\", headers={\"x-other-header\": \"42\"})\n    assert response_json.headers.get(\"content-type\") == \"application/json\"\n\n    response_json = Response.json(\"test\", headers={\"Content-Type\": \"42\"})\n    assert response_json.headers.get(\"content-type\") == \"42\"\n\n    response_none = Response(None, status=204)\n    assert response_none.status == 204\n    assert response_none.body is None\n\n    response_bytes = Response(b\"test\")\n    assert response_bytes.status == 200\n    assert await response_bytes.text() == \"test\"\n\n    class Test:\n        def __init__(self, x):\n            self.x = x\n\n    try:\n        response_json = Response.json(Test(42))\n        await response_json.text()\n        raise ValueError(\"Should have raised\")  # noqa: TRY301\n    except Exception as err:\n        assert str(err) == \"Object of type Test is not JSON serializable\"\n\n    try:\n        response_json = Response.json({1, 2, 3})\n        await response_json.text()\n        raise ValueError(\"Should have raised\")  # noqa: TRY301\n    except Exception as err:\n        assert str(err) == \"Object of type set is not JSON serializable\"\n\n    try:\n        response_json = Response(Test(42))\n        await response_json.text()\n        raise ValueError(\"Should have raised\")  # noqa: TRY301\n    except Exception as err:\n        assert str(err) == \"Unsupported type in Response: Test\"\n\n    response_ws = Response(\n        None, status=101, web_socket=js.WebSocket.new(\"ws://example.com/ignore\")\n    )\n    # TODO: it doesn't seem possible to access webSocket even in JS\n    assert response_ws.status == 101\n\n\nasync def response_buffer_source_unit_tests(env):\n    buffer_source_cases = [\n        # TODO: Float16Array is not supported in Pyodide <= 0.29 (pyodide/pyodide#6005)\n        (\"ArrayBuffer\", js.Uint8Array.new(to_js([1, 2, 3, 4, 5, 6, 7, 8])).buffer),\n        (\"DataView\", js.DataView.new(js.Uint8Array.new(to_js([9, 10, 11, 12])).buffer)),\n        (\"Uint8Array\", js.Uint8Array.new(to_js([1, 2, 3, 4]))),\n        (\"Uint8ClampedArray\", js.Uint8ClampedArray.new(to_js([1, 2, 3, 4]))),\n        (\"Int8Array\", js.Int8Array.new(to_js([1, -1, 2, -2]))),\n        (\"Uint16Array\", js.Uint16Array.new(to_js([1, 2, 3, 4]))),\n        (\"Int16Array\", js.Int16Array.new(to_js([1, -2, 3, -4]))),\n        (\"Uint32Array\", js.Uint32Array.new(to_js([1, 2, 3, 4]))),\n        (\"Int32Array\", js.Int32Array.new(to_js([1, -2, 3, -4]))),\n        (\"Float32Array\", js.Float32Array.new(to_js([1.5, -2.5, 3.25, -4.75]))),\n        (\"Float64Array\", js.Float64Array.new(to_js([1.5, -2.5]))),\n        # BigInt64 not supported in Pyodide <= 0.26\n        # (\"BigInt64Array\", js.BigInt64Array.new(to_js([2**53 + 1, -(2**53 + 1), 2**54 + 2, -(2**54 + 2)]))),\n        # (\"BigUint64Array\", js.BigUint64Array.new(to_js([2**53 + 1, 2**54 + 2, 2**55 + 3, 2**56 + 4]))),\n        # Test partial views to verify they work correctly when not viewing the whole backing buffer\n        (\n            \"Uint8Array.subarray\",\n            js.Uint8Array.new(to_js([0, 1, 2, 3, 4, 5])).subarray(1),\n        ),\n        (\"Int8Array.subarray\", js.Int8Array.new(to_js([0, 1, -1, 2, -2])).subarray(1)),\n    ]\n\n    for type_name, body in buffer_source_cases:\n        expected_length = int(body.byteLength)\n        try:\n            response = Response(body)\n        except TypeError as exc:\n            raise AssertionError(\n                f\"Response rejected BufferSource type {type_name}\"\n            ) from exc\n\n        buffer = await response.buffer()\n        assert int(buffer.byteLength) == expected_length, (\n            f\"Response buffer length mismatch for {type_name}\"\n        )\n\n\nasync def can_fetch_python_request():\n    def fetch_check(request, opts):\n        assert isinstance(request, JsProxy)\n\n    async with _mock_fetch(fetch_check):\n        await fetch(Request(\"https://example.com/redirect\"))\n\n\nasync def can_support_scheduled_cron_trigger(env):\n    result = await env.SELF.scheduled(scheduledTime=1000, cron=\"* * * * 30\")\n    assert result.outcome == \"ok\"\n"
  },
  {
    "path": "src/workerd/server/tests/python/subdirectory/a.py",
    "content": "from js import Response\nfrom subdir.a import x\nfrom workers import WorkerEntrypoint\n\n\nclass Default(WorkerEntrypoint):\n    def fetch(self, request):\n        return Response.new(\"hello world\")\n\n    def test(self):\n        print(\"Hi there, this is a test\", x)\n"
  },
  {
    "path": "src/workerd/server/tests/python/subdirectory/subdir/__init__.py",
    "content": ""
  },
  {
    "path": "src/workerd/server/tests/python/subdirectory/subdir/a.py",
    "content": "x = 7\n"
  },
  {
    "path": "src/workerd/server/tests/python/subdirectory/subdirectory.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"main\",\n      worker = (\n        modules = [\n          (name = \"a.py\", pythonModule = embed \"./a.py\"),\n          (name = \"subdir/__init__.py\", pythonModule = embed \"./subdir/__init__.py\"),\n          (name = \"subdir/a.py\", pythonModule = embed \"./subdir/a.py\"),\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS],\n      )\n    ),\n  ],\n);\n\n"
  },
  {
    "path": "src/workerd/server/tests/python/top-level-tests/env.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"python-hello\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"worker.py\")\n        ],\n        compatibilityFlags = [\n          %PYTHON_FEATURE_FLAGS,\n          \"python_no_global_handlers\",\n          \"unwrap_custom_thenables\"\n        ],\n        bindings = [\n          (\n            name = \"secret\",\n            text = \"thisisasecret\"\n          ),\n          (name = \"FOO\", text = \"BAR\"),\n          (name = \"TEST_VALUE\", text = \"TEST_STRING\"),\n          (name = \"CACHE\", memoryCache = (\n            id = \"abc123\",\n            limits = (\n              maxKeys = 10,\n              maxValueSize = 1024,\n              maxTotalValueSize = 1024,\n            ),\n          )),\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/top-level-tests/worker.py",
    "content": "# Check that multiprocessing top level import works\nimport multiprocessing\n\nfrom workers import WorkerEntrypoint, import_from_javascript\n\nfrom pyodide import __version__\n\n\ndef use(x):\n    pass\n\n\ndef f():\n    pass\n\n\ndef top_level_test():\n    # Test the regular import function\n    workers_module = import_from_javascript(\"cloudflare:workers\")\n    env = workers_module.env\n    assert env.FOO == \"BAR\", \"env.FOO should be 'BAR'\"\n    sockets_module = import_from_javascript(\"cloudflare:sockets\")\n\n    vectorize = None\n    threw = False\n    try:\n        vectorize = import_from_javascript(\"cloudflare:vectorize\")\n    except ImportError:\n        threw = True\n\n    if __version__ == \"0.26.0a2\":\n        # Should have thrown throw \"ImportError: Failed to import 'cloudflare:vectorize': Only 'cloudflare:workers' and 'cloudflare:sockets' are available until the next python runtime version.\"\n        msg = 'import_from_javascript(\"cloudflare:vectorize\") not expected to work in the global scope in 0.26.0a2'\n        assert threw, msg\n        assert vectorize is None, msg\n    else:\n        assert {\"DistanceMetric\", \"KnownModel\", \"MetadataRetrievalLevel\"}.issubset(\n            dir(vectorize)\n        )\n\n    from js import setTimeout\n\n    from pyodide.ffi import create_once_callable\n\n    # Checks for the patch to top level setTimeout() that is needed for top level\n    # `import_from_javascript``.\n    # 1. Test that setTimeout with a zero timeout works at top level\n    setTimeout(create_once_callable(f), 0)\n\n    # 2. Test that setTimeout with nonzero timeout fails with the expected error.\n    err = None\n    try:\n        setTimeout(create_once_callable(f), 1)\n    except Exception as e:\n        err = e\n    finally:\n        assert err\n        assert str(err).startswith(\n            \"Error: Disallowed operation called within global scope\"\n        )\n        del err\n    return [workers_module, env, sockets_module, vectorize]\n\n\n# Check serialization of modules\nTOP_LEVEL = top_level_test()\n\n\nclass Default(WorkerEntrypoint):\n    async def test(self):\n        use(multiprocessing)\n"
  },
  {
    "path": "src/workerd/server/tests/python/undefined-handler/main.py",
    "content": "def test():\n    pass\n"
  },
  {
    "path": "src/workerd/server/tests/python/undefined-handler/server.mjs",
    "content": "import { rejects } from 'node:assert';\n\nexport default {\n  async test(ctrl, env) {\n    await rejects(env.pythonWorker.fetch(new Request('http://example.com')), {\n      name: 'Error',\n      message:\n        'Python entrypoint \"undefined_handler.py\" does not export a handler named \"on_fetch\"',\n    });\n  },\n};\n"
  },
  {
    "path": "src/workerd/server/tests/python/undefined-handler/undefined-handler.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst undefinedHandler :Workerd.Worker = (\n  modules = [\n    (name = \"undefined_handler.py\", pythonModule = embed \"undefined_handler.py\")\n  ],\n  compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\"],\n);\n\nconst server :Workerd.Worker = (\n  modules = [\n    (name = \"server.js\", esModule = embed \"server.mjs\")\n  ],\n  bindings = [\n    ( name = \"pythonWorker\", service = \"undefinedHandler\" ),\n  ],\n  compatibilityFlags = [\"nodejs_compat\"],\n);\n\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"external\",\n      network = (\n        tlsOptions = (trustBrowserCas = true)\n      )\n    ),\n    ( name = \"server\",\n      worker = .server\n    ),\n    ( name = \"undefinedHandler\",\n      worker = .undefinedHandler\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/undefined-handler/undefined_handler.py",
    "content": "def test():\n    pass\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_dir/vendor/a.py",
    "content": "A = 77\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_dir/vendor_dir.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"vendor_dir\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"worker.py\"),\n          (name = \"duplicate.py\", pythonModule = embed \"vendor/a.py\"),\n          (name = \"python_modules/a.py\", pythonModule = embed \"vendor/a.py\"),\n          # This module below is only here to verify that we don't crash because of\n          # duplicate module names.\n          (name = \"python_modules/duplicate.py\", pythonModule = embed \"vendor/a.py\"),\n          # This module below exercises a bug which caused our internal introspection.py\n          # module to import it instead of the SDK module. See EW-9317 for more info.\n          (name = \"workers.py\", pythonModule = embed \"vendor/a.py\"),\n          (name = \"numpy\", pythonRequirement = \"\")\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_dir/worker.py",
    "content": "import sys\n\nfrom a import A\n\n\ndef test():\n    assert A == 77\n    test_path_ordering()\n\n\ndef test_path_ordering():\n    \"\"\"Verify that the Python path is set in the correct order.\n    '/session/metadata/python_modules' should be positioned after the system paths\n    but before any site-packages paths.\n    \"\"\"\n    python_modules_path = \"/session/metadata/python_modules\"\n    python_modules_index = sys.path.index(python_modules_path)\n\n    # Check that vendor_path is in sys.path\n    assert python_modules_index >= 0, f\"{python_modules_path} not found in sys.path\"\n\n    # Verify that all site-packages paths come after the vendor path\n    for i, path in enumerate(sys.path):\n        if \"site-packages\" in path:\n            assert i > python_modules_index, (\n                f\"site-packages path {path} appears before python_modules path\"\n            )\n\n    # Verify system paths come before the vendor path\n    system_paths = [\n        \"/lib/python312.zip\",\n        \"/lib/python3.12\",\n        \"/lib/python3.12/lib-dynload\",\n    ]\n    for path in system_paths:\n        if path in sys.path:\n            assert sys.path.index(path) < python_modules_index, (\n                f\"System path {path} appears after python_modules path\"\n            )\n\n    # Print the path for debugging if needed\n    print(\"Python sys.path:\", sys.path)\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_dir_compat_flag/vendor/a.py",
    "content": "A = 77\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_dir_compat_flag/vendor_dir_compat_flag.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"vendor_dir_compat_flag\",\n      worker = (\n        modules = [\n          (name = \"worker.py\", pythonModule = embed \"worker.py\"),\n          (name = \"vendor/a.py\", pythonModule = embed \"vendor/a.py\"),\n          (name = \"numpy\", pythonRequirement = \"\")\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"python_workers_force_new_vendor_path\", \"disable_python_no_global_handlers\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_dir_compat_flag/worker.py",
    "content": "import sys\n\n\ndef test():\n    try:\n        from a import A  # noqa: F401\n\n        raise Exception(\"Should not be able to import a\")  # noqa: TRY002\n    except ImportError:\n        pass\n    vendor_path = \"/session/metadata/vendor\"\n    assert vendor_path not in sys.path\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_pkg_tests/BUILD",
    "content": "load(\"//src/workerd/server/tests/python:py_wd_test.bzl\", \"python_test_setup\")\nload(\":vendor_test.bzl\", \"vendored_py_wd_test\")\n\npython_test_setup()\n\nvendored_py_wd_test(\"fastapi\")\n\nvendored_py_wd_test(\"beautifulsoup4\")\n\nvendored_py_wd_test(\n    \"python-workers-runtime-sdk\",\n    feature_flags = [\"enable_python_external_sdk\"],\n)\n\nvendored_py_wd_test(\n    \"python-workers-runtime-sdk-override\",\n    feature_flags = [\"disable_python_external_sdk\"],\n    main_py_file = \"python-workers-runtime-sdk.py\",\n    test_template = \"python-workers-runtime-sdk_vendor.wd-test\",\n    vendored_package_name = \"python-workers-runtime-sdk\",\n)\n\n# vendored_py_wd_test(\"scipy\")\n\nvendored_py_wd_test(\n    \"shapely\",\n    feature_flags = [\"python_dedicated_snapshot\"],\n    make_snapshot = True,\n    python_flags = [\"0.28.2\"],\n    use_snapshot = \"baseline\",\n)\n\n# Note: the `existing_dedicated` tests verify that a dedicated snapshot which has been deployed to\n# production continues to work. Because of this the snapshot should never be changed and should\n# remain the same to ensure Workers in production continue to work.\nvendored_py_wd_test(\n    \"existing_dedicated_fastapi\",\n    feature_flags = [\"python_dedicated_snapshot\"],\n    make_snapshot = False,\n    python_flags = [\"0.28.2\"],\n    use_snapshot = \"dedicated_fastapi\",\n    vendored_package_name = \"fastapi\",\n)\n\nexports_files([\"generate_modules.py\"])\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_pkg_tests/beautifulsoup4.py",
    "content": "import bs4\nfrom workers import WorkerEntrypoint\n\n\nclass Default(WorkerEntrypoint):\n    async def test(self):\n        assert bs4.__version__\n        # Use beautifulsoup4 to parse HTML\n        soup = bs4.BeautifulSoup(\"<html></html>\", \"html.parser\")\n        print(soup.prettify())\n\n        print(\"BeautifulSoup4 ran successfully!\")\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_pkg_tests/beautifulsoup4_vendor.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"beautifulsoup4-vendor-test\",\n      worker = (\n        modules = [\n          (name = \"main.py\", pythonModule = embed \"beautifulsoup4.py\"),\n          %PYTHON_VENDORED_MODULES%\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_pkg_tests/existing_dedicated_fastapi.py",
    "content": "import fastapi\nfrom fastapi import FastAPI\nfrom workers import WorkerEntrypoint\n\napp = FastAPI()\n\n\n@app.get(\"/\")\nasync def root():\n    message = \"This is an example of FastAPI\"\n    return {\"message\": message}\n\n\nclass Default(WorkerEntrypoint):\n    async def fetch(self, request):\n        import asgi\n\n        return await asgi.fetch(app, request.js_object, self.env)\n\n    async def test(self, a, b, c):\n        assert fastapi.__version__\n        # The dedicated snapshot we are testing contains source code which is different to\n        # below. It will pass the test instead. So we fail this script to show that the snapshot\n        # wasn't loaded correctly.\n        print(\"FastAPI dedicated snapshot wasn't loaded\")\n        raise AssertionError(\"FastAPI dedicated snapshot wasn't loaded\")\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_pkg_tests/existing_dedicated_fastapi_vendor.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"existing-dedicated-fastapi-vendor-test\",\n      worker = (\n        modules = [\n          # The snapshot's main module is also named worker.py. This needs to match.\n          (name = \"worker.py\", pythonModule = embed \"existing_dedicated_fastapi.py\"),\n          %PYTHON_VENDORED_MODULES%\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_pkg_tests/fastapi.py",
    "content": "import fastapi\nfrom workers import WorkerEntrypoint\n\n\nclass Default(WorkerEntrypoint):\n    async def test(self):\n        assert fastapi.__version__\n        print(\"FastAPI imported successfully!\")\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_pkg_tests/fastapi_vendor.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"fastapi-vendor-test\",\n      worker = (\n        modules = [\n          (name = \"main.py\", pythonModule = embed \"fastapi.py\"),\n          %PYTHON_VENDORED_MODULES%\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\", \"disable_python_check_rng_state\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_pkg_tests/generate_modules.py",
    "content": "# This script reads the list of files from a zip file and outputs a list of Cap'n Proto module\n# definitions to be used in a .wd-test for Python tests.\nimport argparse\nfrom pathlib import Path\n\n\nclass MyArgumentParser(argparse.ArgumentParser):\n    def convert_arg_line_to_args(self, arg_line):\n        return arg_line.split()\n\n\ndef get_parser():\n    parser = MyArgumentParser(\n        description=\"Generate Cap'n Proto module definitions for Python tests\",\n        fromfile_prefix_chars=\"@\",\n    )\n    parser.add_argument(\n        \"--level\",\n        type=int,\n        default=1,\n        help=\"Directory level for relative path calculation (default: 1)\",\n    )\n    parser.add_argument(\n        \"--template\",\n        type=str,\n        help=\"Template file\",\n    )\n    parser.add_argument(\n        \"--out\",\n        type=str,\n        help=\"Output file\",\n    )\n    parser.add_argument(\n        \"file_paths\",\n        nargs=\"*\",\n        help=\"List of file paths or @response_file containing space-separated paths\",\n    )\n    return parser\n\n\ndef make_module_list(file_paths, level=1):\n    modules = []\n    for f_path in file_paths:\n        if Path(f_path).is_dir():\n            continue\n        # The path from bazel is relative to the exec root, e.g.:\n        # external/fastapi_src/fastapi/__init__.py\n        # We need to strip the prefix to get the module path.\n        #\n        # On Windows, we replace windows-style path separators with standard posix path separators.\n        components = Path(f_path).parts\n        # without `external/` prefix\n        parents = 6 + level\n        embed_path = (\"../\" * parents) + str(Path(*components[1:])).replace(\n            \"\\\\\", \"\\\\\\\\\"\n        )\n        # without `external/fastapi_src/` prefix\n        module_path = str(Path(\"python_modules\", *components[2:])).replace(\"\\\\\", \"/\")\n\n        # Format as a Cap'n Proto module definition.\n        if f_path.endswith(\".py\"):\n            modules.append(\n                f'(name = \"{module_path}\", pythonModule = embed \"{embed_path}\")'\n            )\n        else:\n            modules.append(f'(name = \"{module_path}\", data = embed \"{embed_path}\")')\n    return \",\\n\".join(modules) + \",\\n\"\n\n\ndef write_output(modules, template_path, outfile):\n    template = Path(template_path).read_text()\n    result = template.replace(\"%PYTHON_VENDORED_MODULES%\", modules)\n    Path(outfile).write_text(result)\n\n\ndef main():\n    parser = get_parser()\n    args = parser.parse_args()\n    level = args.level\n    file_paths = args.file_paths\n    outfile = args.out\n    template = args.template\n    modules = make_module_list(file_paths, level)\n    write_output(modules, template, outfile)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_pkg_tests/python-workers-runtime-sdk.py",
    "content": "# The python-workers-runtime-sdk vendored files include the sdk in tree from workerd\n# 9ca3b8e36e86e34f16ecd9680334ba0e0261549c plus one extra function. If we don't at least have the\n# things imported in introspection.py we'll crash on startup.\nfrom workers import WorkerEntrypoint, extra_function\n\n\nclass Default(WorkerEntrypoint):\n    async def test(self):\n        assert extra_function() == 42\n        print(\"workers-sdk imported successfully!\")\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_pkg_tests/python-workers-runtime-sdk_vendor.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"workers-sdk-vendor-test\",\n      worker = (\n        modules = [\n          (name = \"main.py\", pythonModule = embed \"python-workers-runtime-sdk.py\"),\n          %PYTHON_VENDORED_MODULES%\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_pkg_tests/scipy.py",
    "content": "import numpy as np\nfrom scipy import linalg\nfrom workers import WorkerEntrypoint\n\n\nclass Default(WorkerEntrypoint):\n    async def test(self):\n        c = np.array([1, 3, 6, 10])\n        r = np.array([1, -1, -2, -3])\n        b = np.array([1, 2, 2, 5])\n        res = linalg.solve_toeplitz((c, r), b)\n        assert (\n            repr(res) == \"array([ 1.66666667, -1.        , -2.66666667,  2.33333333])\"\n        )\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_pkg_tests/scipy_vendor.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"scipy-vendor-test\",\n      worker = (\n        modules = [\n          (name = \"main.py\", pythonModule = embed \"scipy.py\"),\n          %PYTHON_VENDORED_MODULES%\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_pkg_tests/shapely.py",
    "content": "import shapely\nfrom workers import WorkerEntrypoint\n\n\nclass Default(WorkerEntrypoint):\n    async def test(self):\n        assert shapely.__version__\n        print(\"shapely imported successfully!\")\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_pkg_tests/shapely_vendor.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"shapely-vendor-test\",\n      worker = (\n        modules = [\n          (name = \"main.py\", pythonModule = embed \"shapely.py\"),\n          %PYTHON_VENDORED_MODULES%\n        ],\n        compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"disable_python_no_global_handlers\"],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/vendor_pkg_tests/vendor_test.bzl",
    "content": "load(\"//:build/python_metadata.bzl\", \"BUNDLE_VERSION_INFO\")\nload(\"//src/workerd/server/tests/python:py_wd_test.bzl\", \"compute_python_flags\", \"py_wd_test\")\n\ndef _vendored_py_wd_test(name, version, test_template, main_py_file, vendored_srcs_target_prefix, level, data, **kwds):\n    \"\"\"Creates a Python Workers test which includes vendored packages in its bundle, the\n    http_archive target containing the vendored sources should be specified in `vendored_srcs_target_prefix`.\n\n    Args:\n        name: Name of the test\n        version: The version of the package bundle\n        test_template: The .wd-test template file\n        main_py_file: The main Python file for the test\n        vendored_srcs_target_prefix: The prefix of the Bazel target containing the vendored sources\n    \"\"\"\n    vendored_srcs_target = vendored_srcs_target_prefix + \"_\" + version + \"//:all_srcs\"\n\n    # Generate module list\n    module_list_name = name + \"_modules_string\" + \"_\" + version\n    substitution_name = name + \"_perform_substitution\" + \"_\" + version\n    native.genrule(\n        name = substitution_name,\n        srcs = [\n            test_template,\n            vendored_srcs_target,\n            \"//src/workerd/server/tests/python/vendor_pkg_tests:generate_modules.py\",\n        ],\n        outs = [name + \".test.generated\" + \"_\" + version],\n        cmd = \"\"\"\n        # Create a file with all the file paths to avoid Windows command line length limits\n        echo \"$(locations %s)\" > paths.txt\n        $(execpath @python_3_13//:python3) \\\n            $(location //src/workerd/server/tests/python/vendor_pkg_tests:generate_modules.py) \\\n            --level=%s \\\n            --template=$(location %s) \\\n            --out=$@ \\\n            @paths.txt\n        \"\"\" % (vendored_srcs_target, level, test_template),\n        tools = [\"@python_3_13//:python3\"],\n    )\n\n    # Create the py_wd_test\n    py_wd_test(\n        name = name,\n        src = \":\" + substitution_name,\n        python_flags = [version],\n        data = data + [\n            main_py_file,\n            vendored_srcs_target,\n        ],\n        # Disable on windows because of flakiness\n        # TODO fix this\n        target_compatible_with = select({\n            \"@platforms//os:windows\": [\"@platforms//:incompatible\"],\n            \"//conditions:default\": [],\n        }),\n        **kwds\n    )\n\ndef vendored_py_wd_test(\n        name,\n        test_template = None,\n        main_py_file = None,\n        vendored_srcs_target_prefix = None,\n        python_flags = \"all\",\n        skip_python_flags = [],\n        vendored_package_name = None,\n        level = 1,\n        data = [],\n        **kwds):\n    python_flags = compute_python_flags(python_flags, skip_python_flags)\n    bzl_name = \"%s_vendor_test\" % name\n    if test_template == None:\n        test_template = \"%s_vendor.wd-test\" % name\n    if main_py_file == None:\n        main_py_file = \"%s.py\" % name\n    if vendored_package_name == None:\n        vendored_package_name = name\n    if vendored_srcs_target_prefix == None:\n        vendored_srcs_target_prefix = \"@%s_src\" % vendored_package_name\n\n    for flag in python_flags:\n        info = BUNDLE_VERSION_INFO[flag]\n        if vendored_package_name not in info[\"vendored_packages_for_tests\"]:\n            fail(\"Not found\", vendored_package_name, \"in\", info[\"vendored_packages_for_tests\"])\n        _vendored_py_wd_test(bzl_name, info[\"name\"], test_template, main_py_file, vendored_srcs_target_prefix, level = level, data = data, **kwds)\n"
  },
  {
    "path": "src/workerd/server/tests/python/worker-entrypoint/worker-entrypoint.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"main\", worker = .mainWorker),\n    (name = \"TEST_TMPDIR\", disk = (writable = true)),\n  ],\n);\n\nconst mainWorker :Workerd.Worker = (\n\n  compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"rpc\", \"disable_python_no_global_handlers\"],\n\n  modules = [\n    (name = \"worker.py\", pythonModule = embed \"worker.py\"),\n  ],\n\n  bindings = [\n    (name = \"entrypoint\", service = (name = \"main\", entrypoint = \"WorkerEntrypointExample\")),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/worker-entrypoint/worker.py",
    "content": "# Copyright (c) 2023 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\nimport os\n\nfrom workers import WorkerEntrypoint\n\n\nclass WorkerEntrypointExample(WorkerEntrypoint):\n    def __init__(self, state, env):\n        self.state = state\n        self.counter = 0\n\n    async def rpc_trigger_func(self):\n        # Test that entropy works in WorkerEntrypoint...\n        os.urandom(4)\n        self.counter += 1\n        return f\"hello from python {self.counter}\"\n\n\nasync def test(ctrl, env, ctx):\n    first_resp = await env.entrypoint.rpc_trigger_func()\n    assert first_resp == \"hello from python 1\"\n\n    second_resp = await env.entrypoint.rpc_trigger_func()\n    # We expect the counter to stay the same since this isn't a durable object.\n    assert second_resp == \"hello from python 1\"\n"
  },
  {
    "path": "src/workerd/server/tests/python/workflow-entrypoint/worker.js",
    "content": "import { RpcTarget } from 'cloudflare:workers';\nimport * as assert from 'node:assert';\n\nclass Context extends RpcTarget {\n  constructor(shouldSendCtx) {\n    super();\n    this.shouldSendCtx = shouldSendCtx;\n  }\n\n  async do(name, fn) {\n    try {\n      const ctx = { attempt: '1', metadata: 'expected_return_metadata' };\n      const result = this.shouldSendCtx ? await fn(ctx) : await fn();\n      return result;\n    } catch (e) {\n      console.log(`Error received: ${e.name} Message: ${e.message}`);\n      // let's rethrow here since the engine does the same\n      throw e;\n    }\n  }\n}\n\nexport default {\n  async test(ctrl, env, ctx) {\n    let stubStep = new Context(true);\n\n    // Tests forward compat - i.e.: python workflows should be compatible with steps that pass a ctx argument\n    // this param is optional and searched by name. Meaning it's not positional\n    let resp = await env.PythonWorkflow.run(\n      {\n        foo: 'bar',\n      },\n      stubStep\n    );\n    assert.deepStrictEqual(resp, 'foobar');\n\n    // Tests backwards compat for dependency resolution - previously deps were not following a name based\n    // approach. Instead, they were passed in the same order as they were declared in the depends param\n    // This test also doesn't pass any ctx to steps\n    stubStep = new Context(false);\n    resp = await env.PythonWorkflowDepends.run(\n      {\n        foo: 'bar',\n      },\n      stubStep\n    );\n    assert.deepStrictEqual(resp, 'foobar');\n\n    // Tests forwards compat for dependency resolution - pass ctx onto workflow that runs with the legacy\n    // code path\n    stubStep = new Context(true);\n    resp = await env.PythonWorkflowDepends.run(\n      {\n        foo: 'bar',\n      },\n      stubStep\n    );\n    assert.deepStrictEqual(resp, 'foobar');\n  },\n};\n"
  },
  {
    "path": "src/workerd/server/tests/python/workflow-entrypoint/workflow-entrypoint.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (name = \"py\", worker = .pyWorker),\n    (name = \"pyOld\", worker = .pyWorkerOld),\n    (name = \"js\", worker = .jsWorker),\n  ],\n);\n\nconst pyWorker :Workerd.Worker = (\n  compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"python_workflows\", \"python_workflows_implicit_dependencies\"],\n\n  modules = [\n    (name = \"workflow.py\", pythonModule = embed \"workflow.py\"),\n  ],\n\n  bindings = [\n    (name = \"PythonWorkflow\", service = (name = \"py\", entrypoint = \"WorkflowEntrypointExample\")),\n  ],\n);\n\nconst pyWorkerOld :Workerd.Worker = (\n  compatibilityFlags = [%PYTHON_FEATURE_FLAGS, \"python_workflows\", \"no_python_workflows_implicit_dependencies\"],\n\n  modules = [\n    (name = \"workflow-old.py\", pythonModule = embed \"workflow-old.py\"),\n  ],\n\n  bindings = [\n    (name = \"PythonWorkflowDepends\", service = (name = \"pyOld\", entrypoint = \"PythonWorkflowDepends\")),\n  ],\n);\n\nconst jsWorker :Workerd.Worker = (\n  compatibilityFlags = [\"nodejs_compat\", \"rpc\"],\n\n  modules = [\n    (name = \"worker\", esModule = embed \"worker.js\"),\n  ],\n\n  bindings = [\n    (name = \"PythonWorkflow\", service = (name = \"py\", entrypoint = \"WorkflowEntrypointExample\")),\n    (name = \"PythonWorkflowDepends\", service = (name = \"pyOld\", entrypoint = \"PythonWorkflowDepends\")),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/python/workflow-entrypoint/workflow-old.py",
    "content": "# Copyright (c) 2025 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\nfrom workers import WorkflowEntrypoint\n\n\nclass PythonWorkflowDepends(WorkflowEntrypoint):\n    # Tests backward compatibility: legacy depends parameter with positional args\n    # and ctx support in step callbacks.\n    async def run(self, event, step):\n        @step.do(\"step_1\")\n        async def step_1():\n            # tests backwards compat with workflows that don't have ctx in the step callback\n            print(\"Executing step 1\")\n            return \"foo\"\n\n        @step.do(\"step_2\")\n        async def step_2(ctx):\n            if ctx is not None:\n                assert ctx[\"attempt\"] == \"1\"\n            print(\"Executing step 2\")\n            return \"bar\"\n\n        @step.do(\"step_3\", depends=[step_1, step_2], concurrent=True)\n        async def step_3(result1=(), ctx=None, result2=()):\n            if ctx is not None:\n                assert ctx[\"attempt\"] == \"1\"\n            assert result1 == \"foo\"\n            assert result2 == \"bar\"\n            return result1 + result2\n\n        return await step_3()\n\n\nasync def test(ctrl, env, ctx):\n    pass\n"
  },
  {
    "path": "src/workerd/server/tests/python/workflow-entrypoint/workflow.py",
    "content": "# Copyright (c) 2025 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\nfrom workers import WorkflowEntrypoint\n\n\nclass WorkflowEntrypointExample(WorkflowEntrypoint):\n    async def run(self, event, step):\n        async def await_step(fn):\n            try:\n                return await fn()\n            except TypeError as e:\n                print(f\"Successfully caught {type(e).__name__}: {e}\")\n\n        @step.do(\"my_failing\")\n        async def my_failing():\n            print(\"Executing my_failing\")\n            raise TypeError(\"Intentional error in my_failing\")\n\n        @step.do(\"normal_step\")\n        async def normal_step(ctx):\n            assert ctx[\"attempt\"] == \"1\"\n            return \"done\"\n\n        await normal_step()\n\n        @step.do()\n        async def step_1():\n            print(\"Executing step 1\")\n            return {\"foo\": \"foo\"}\n\n        @step.do()\n        async def step_2():\n            print(\"Executing step 2\")\n            return {\"bar\": \"bar\"}\n\n        # DAG example with error handling\n        @step.do(\"step_3\", concurrent=True)\n        async def step_3():\n            # this should never run because one of the dependencies will fail\n            pass\n\n        await await_step(step_3)\n\n        # `step_1` and `step_2` run serially\n        @step.do(\"step_4\", concurrent=True)\n        async def step_4(step_1, ctx, step_2):\n            assert ctx[\"attempt\"] == \"1\"\n            print(\"Executing step 4 (depends on step 1 and step 2)\")\n            assert step_1[\"foo\"] == \"foo\"\n            assert step_2[\"bar\"] == \"bar\"\n\n        await await_step(step_4)\n\n        # tests step memoization - steps 1 and 2 are already resolved\n        @step.do(\"step_5\", concurrent=False)\n        async def step_5(ctx, step_1=(), step_2=()):\n            print(\"Executing step 5 (depends on step 1 and step 2)\")\n            assert step_1[\"foo\"] == \"foo\"\n            assert step_2[\"bar\"] == \"bar\"\n\n            return step_1[\"foo\"] + step_2[\"bar\"]\n\n        return await step_5()\n\n\nasync def test(ctrl, env, ctx):\n    pass\n"
  },
  {
    "path": "src/workerd/server/tests/server-harness.mjs",
    "content": "import { spawn } from 'node:child_process';\nimport assert from 'node:assert';\n\n// A convenience class to:\n// - start workerd\n// - wait for its listen ports to be opened and reported\n// - stop workerd\nexport class WorkerdServerHarness {\n  // Properties set by our constructor and never changed.\n  #workerdBinary = null;\n  #workerdConfig = null;\n  #listenPortNames = null;\n\n  // Properties set by `start()` and cleared by `stop()`.\n  #child = null;\n  #listenPorts = null;\n  #listenInspectorPort = null;\n  #closed = null;\n\n  constructor({ workerdBinary, workerdConfig, listenPortNames }) {\n    this.#workerdBinary = workerdBinary;\n    this.#workerdConfig = workerdConfig;\n    this.#listenPortNames = listenPortNames;\n  }\n\n  // Spawn our workerd process and wait for it to start.\n  async start() {\n    assert.equal(this.#child, null);\n\n    // One after STDIN, STDOUT, and STDERR.\n    const CONTROL_FD = 3;\n\n    const args = [\n      'serve',\n      this.#workerdConfig,\n      '--verbose',\n      '--inspector-addr=127.0.0.1:0',\n      `--control-fd=${CONTROL_FD}`,\n    ];\n\n    const options = {\n      stdio: [\n        'inherit',\n        'inherit',\n        'inherit',\n        // One more for our control FD.\n        'pipe',\n      ],\n    };\n\n    // Start the subprocess.\n    console.log('[HARNESS] Starting workerd with args:', args);\n    this.#child = spawn(this.#workerdBinary, args, options);\n\n    // Create a promise for every named listen port we were told in our constructor to expect. Parse\n    // messages from our control FD and resolve the promises as we see ports come online.\n    //\n    // TODO(perf): Registering a separate callback for every named port isn't very efficient --\n    // we'll parse JSON N times -- but we typically don't have many named ports, and I don't want to\n    // spend forever on this code.\n    this.#listenPorts = new Map();\n    for (const listenPort of this.#listenPortNames) {\n      this.#listenPorts.set(\n        listenPort,\n        new Promise((resolve, reject) => {\n          this.#child.stdio[CONTROL_FD].on('data', (data) => {\n            const parsed = JSON.parse(data);\n            console.log('[HARNESS] Control message:', parsed);\n            if (parsed.event === 'listen' && parsed.socket === listenPort) {\n              resolve(parsed.port);\n            }\n          });\n          this.#child.once('error', reject);\n        })\n      );\n    }\n\n    // Do the same as the above for the inspector port.\n    this.#listenInspectorPort = new Promise((resolve, reject) => {\n      this.#child.stdio[CONTROL_FD].on('data', (data) => {\n        const parsed = JSON.parse(data);\n        console.log('[HARNESS] Inspector message:', parsed);\n        if (parsed.event === 'listen-inspector') {\n          resolve(parsed.port);\n        }\n      });\n      this.#child.once('error', reject);\n    });\n\n    // Set up a closed promise, too.\n    this.#closed = new Promise((resolve, reject) => {\n      this.#child\n        .once('close', (code, signal) => resolve([code, signal]))\n        .once('error', reject);\n    });\n\n    // Wait for the subprocess to complete spawning before we return.\n    await new Promise((resolve, reject) => {\n      this.#child.once('spawn', resolve).once('error', reject);\n      console.log('[HARNESS] Workerd process spawned');\n    });\n  }\n\n  // Return a promise for the inspector port.\n  async getListenInspectorPort() {\n    assert.notEqual(this.#listenInspectorPort, null);\n    return await this.#listenInspectorPort;\n  }\n\n  // Return a promise for the named listen port.\n  async getListenPort(name) {\n    assert.notEqual(this.#listenPorts, null);\n    assert.notEqual(this.#listenPorts.get(name), undefined);\n    return await this.#listenPorts.get(name);\n  }\n\n  // Send SIGTERM to workerd and wait for it to completely finish.\n  async stop() {\n    assert.notEqual(this.#child, null);\n\n    console.log('[HARNESS] Sending SIGTERM to workerd...');\n\n    // First try SIGTERM with a timeout\n    const SIGTERM_TIMEOUT = 5000; // 5 seconds\n    const SIGKILL_TIMEOUT = 2000; // 2 more seconds for SIGKILL\n\n    let result;\n    let killed = false;\n\n    // Set up a timeout for SIGTERM\n    const sigtermTimeout = setTimeout(() => {\n      if (!killed) {\n        console.log('[HARNESS] SIGTERM timeout, sending SIGKILL...');\n        this.#child.kill('SIGKILL');\n        killed = true;\n      }\n    }, SIGTERM_TIMEOUT);\n\n    // Set up a final timeout for SIGKILL\n    const sigkillTimeout = setTimeout(() => {\n      if (!killed) {\n        console.error(\n          '[HARNESS] Process did not respond to signals, forcing termination'\n        );\n        // This shouldn't happen, but just in case...\n        process.exit(1);\n      }\n    }, SIGTERM_TIMEOUT + SIGKILL_TIMEOUT);\n\n    try {\n      // Send SIGTERM\n      this.#child.kill('SIGTERM');\n\n      // Wait for the process to close\n      result = await this.#closed;\n      killed = true;\n      console.log('[HARNESS] Workerd process terminated successfully');\n    } finally {\n      // Clean up timeouts\n      clearTimeout(sigtermTimeout);\n      clearTimeout(sigkillTimeout);\n    }\n\n    this.#child = null;\n    this.#listenPorts = null;\n    this.#listenInspectorPort = null;\n    this.#closed = null;\n\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/workerd/server/tests/structured-logging/BUILD.bazel",
    "content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_test\")\n\njs_test(\n    name = \"structured-logging-test\",\n    data = [\n        \":structured-logging-json.wd-test\",\n        \"//src/workerd/server:workerd\",\n    ],\n    entry_point = \"structured-logging-test.mjs\",\n    env = {\n        \"WORKERD_BINARY\": \"$(rootpath //src/workerd/server:workerd)\",\n        \"WD_TEST_CONFIG\": \"$(rootpath :structured-logging-json.wd-test)\",\n    },\n)\n"
  },
  {
    "path": "src/workerd/server/tests/structured-logging/structured-logging-json.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    (\n      name = \"main\",\n      worker = (\n        modules = [\n          (\n            name = \"worker.js\",\n            esModule = \"invalid javascript syntax {{{\"\n          )\n        ],\n        compatibilityDate = \"2023-05-18\",\n      )\n    )\n  ],\n  sockets = [\n    (\n      name = \"http\",\n      address = \"*:8080\",\n      http = (),\n      service = \"main\"\n    )\n  ],\n  structuredLogging = true\n);\n"
  },
  {
    "path": "src/workerd/server/tests/structured-logging/structured-logging-test.mjs",
    "content": "import { spawn } from 'node:child_process';\nimport { env } from 'node:process';\nimport { test } from 'node:test';\nimport assert from 'node:assert';\n\nassert(\n  env.WORKERD_BINARY !== undefined,\n  'You must set the WORKERD_BINARY environment variable.'\n);\nassert(\n  env.WD_TEST_CONFIG !== undefined,\n  'You must set the WD_TEST_CONFIG environment variable.'\n);\n\ntest('structured logging produces valid JSON with required fields', async () => {\n  const result = await new Promise((resolve) => {\n    let output = '';\n    const child = spawn(env.WORKERD_BINARY, ['test', env.WD_TEST_CONFIG], {\n      stdio: ['pipe', 'pipe', 'pipe'],\n    });\n\n    child.stdout.on('data', (data) => (output += data));\n    child.stderr.on('data', (data) => (output += data));\n    child.on('close', () => resolve(output));\n  });\n\n  // Parse as JSON - extract the first JSON line\n  let logEntry;\n  assert.doesNotThrow(() => {\n    // Split by lines and find the first valid JSON line\n    const lines = result.split('\\n');\n    const jsonLine = lines.find((line) => line.trim().startsWith('{'));\n    assert(jsonLine, 'No JSON line found in output');\n    logEntry = JSON.parse(jsonLine);\n  }, `Output is not valid JSON: ${result}`);\n\n  // Verify required structure\n  assert.strictEqual(\n    typeof logEntry,\n    'object',\n    'Log entry should be an object'\n  );\n  assert(logEntry !== null, 'Log entry should not be null');\n\n  // Verify required fields exist with correct types\n  assert(\n    typeof logEntry.level === 'string',\n    `level should be string, got: ${typeof logEntry.level}`\n  );\n  assert(\n    typeof logEntry.message === 'string',\n    `message should be string, got: ${typeof logEntry.message}`\n  );\n  assert(\n    typeof logEntry.timestamp === 'string',\n    `timestamp should be string, got: ${typeof logEntry.timestamp}`\n  );\n\n  // Verify timestamp is a valid ISO date\n  assert.doesNotThrow(() => {\n    new Date(logEntry.timestamp);\n  }, `timestamp should be valid date: ${logEntry.timestamp}`);\n\n  // Verify error content is present in the message\n  assert(\n    logEntry.message.includes('SyntaxError'),\n    `Missing SyntaxError in message: ${logEntry.message}`\n  );\n  assert(\n    logEntry.message.includes('javascript'),\n    `Missing javascript reference in message: ${logEntry.message}`\n  );\n});\n"
  },
  {
    "path": "src/workerd/server/tests/unsafe-eval/BUILD.bazel",
    "content": "load(\"//:build/wd_test.bzl\", \"wd_test\")\n\nwd_test(\n    src = \"unsafe-eval-test.wd-test\",\n    data = glob([\n        \"*.js\",\n        \"*.capnp\",\n    ]),\n)\n"
  },
  {
    "path": "src/workerd/server/tests/unsafe-eval/extension.capnp",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst extension :Workerd.Extension = (\n  modules = [\n    ( name = \"test:module\", esModule = embed \"module.js\" ),\n  ]\n);\n"
  },
  {
    "path": "src/workerd/server/tests/unsafe-eval/module.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { default as UnsafeEval } from 'internal:unsafe-eval';\n\nexport function doEval() {\n  return UnsafeEval.eval('1 + 1');\n}\n"
  },
  {
    "path": "src/workerd/server/tests/unsafe-eval/unsafe-eval-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport * as assert from 'node:assert';\nimport { doEval } from 'test:module';\n\nexport const test_can_use_eval_via_proxy = {\n  async test() {\n    assert.equal(doEval(), 2);\n  },\n};\n\n// internal modules can't be imported\nexport const test_cannot_import_unsafe_eval = {\n  async test() {\n    await assert.rejects(import('internal:unsafe-eval'));\n  },\n};\n"
  },
  {
    "path": "src/workerd/server/tests/unsafe-eval/unsafe-eval-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\nusing TestExtension = import \"extension.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"unsafe-eval-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"unsafe-eval-test.js\")\n        ],\n        compatibilityFlags = [\"nodejs_compat\"],\n     )\n    ),\n  ],\n  extensions = [ TestExtension.extension ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/unsafe-module/BUILD.bazel",
    "content": "load(\"//:build/wd_test.bzl\", \"wd_test\")\n\nwd_test(\n    src = \"unsafe-module-test.wd-test\",\n    args = [\"--experimental\"],\n    data = glob([\"*.js\"]),\n)\n"
  },
  {
    "path": "src/workerd/server/tests/unsafe-module/unsafe-module-test.js",
    "content": "import assert from 'node:assert';\nimport unsafe from 'workerd:unsafe';\n\nfunction createTestObject(type) {\n  return class {\n    id = crypto.randomUUID();\n    fetch() {\n      return new Response(`${type}:${this.id}`);\n    }\n  };\n}\nexport const TestDurableObject = createTestObject('durable');\nexport const TestDurableObjectPreventEviction = createTestObject(\n  'durable-prevent-eviction'\n);\nexport const TestEphemeralObject = createTestObject('ephemeral');\nexport const TestEphemeralObjectPreventEviction = createTestObject(\n  'ephemeral-prevent-eviction'\n);\n\nexport const test_abort_all_durable_objects = {\n  async test(ctrl, env, ctx) {\n    const durableId = env.DURABLE.newUniqueId();\n    const durablePreventEvictionId = env.DURABLE_PREVENT_EVICTION.newUniqueId();\n\n    let durableStub = env.DURABLE.get(durableId);\n    const durablePreventEvictionStub = env.DURABLE_PREVENT_EVICTION.get(\n      durablePreventEvictionId\n    );\n    let ephemeralStub = env.EPHEMERAL.get('thing');\n    const ephemeralPreventEvictionStub =\n      env.EPHEMERAL_PREVENT_EVICTION.get('thing');\n\n    const durableRes1 = await (await durableStub.fetch('http://x')).text();\n    const durablePreventEvictionRes1 = await (\n      await durablePreventEvictionStub.fetch('http://x')\n    ).text();\n    const ephemeralRes1 = await (await ephemeralStub.fetch('http://x')).text();\n    const ephemeralPreventEvictionRes1 = await (\n      await ephemeralPreventEvictionStub.fetch('http://x')\n    ).text();\n\n    await unsafe.abortAllDurableObjects();\n\n    // Since the objects were aborted, trying to use their stubs now should reject.\n    await assert.rejects(() => durableStub.fetch('http://x'), {\n      name: 'Error',\n      message: 'Application called abortAllDurableObjects().',\n    });\n    await assert.rejects(() => ephemeralStub.fetch('http://x'), {\n      name: 'Error',\n      message: 'Application called abortAllDurableObjects().',\n    });\n\n    // Recreate the stubs because they are broken.\n    durableStub = env.DURABLE.get(durableId);\n    ephemeralStub = env.EPHEMERAL.get('thing');\n\n    const durableRes2 = await (await durableStub.fetch('http://x')).text();\n    const durablePreventEvictionRes2 = await (\n      await durablePreventEvictionStub.fetch('http://x')\n    ).text();\n    const ephemeralRes2 = await (await ephemeralStub.fetch('http://x')).text();\n    const ephemeralPreventEvictionRes2 = await (\n      await ephemeralPreventEvictionStub.fetch('http://x')\n    ).text();\n\n    // Irrespective of abort status, verify responses start with expected prefix\n    assert.match(durableRes1, /^durable:/);\n    assert.match(durableRes2, /^durable:/);\n    assert.match(durablePreventEvictionRes1, /^durable-prevent-eviction:/);\n    assert.match(ephemeralRes1, /^ephemeral:/);\n    assert.match(ephemeralRes2, /^ephemeral:/);\n    assert.match(ephemeralPreventEvictionRes1, /^ephemeral-prevent-eviction:/);\n\n    // Response from aborted objects should change\n    assert.notStrictEqual(durableRes1, durableRes2);\n    assert.notStrictEqual(ephemeralRes1, ephemeralRes2);\n\n    // Response from objects in namespaces that have prevent eviction set shouldn't change\n    assert.strictEqual(durablePreventEvictionRes1, durablePreventEvictionRes2);\n    assert.strictEqual(\n      ephemeralPreventEvictionRes1,\n      ephemeralPreventEvictionRes2\n    );\n  },\n};\n"
  },
  {
    "path": "src/workerd/server/tests/unsafe-module/unsafe-module-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"unsafe-module-test\",\n      worker = (\n        modules = [\n          ( name = \"worker\", esModule = embed \"unsafe-module-test.js\" ),\n        ],\n        compatibilityFlags = [\"nodejs_compat\", \"unsafe_module\"],\n        durableObjectNamespaces = [\n          ( className = \"TestDurableObject\", uniqueKey = \"durable\" ),\n          ( className = \"TestDurableObjectPreventEviction\", uniqueKey = \"durable-prevent-eviction\", preventEviction = true ),\n          ( className = \"TestEphemeralObject\", ephemeralLocal = void ),\n          ( className = \"TestEphemeralObjectPreventEviction\", ephemeralLocal = void, preventEviction = true ),\n        ],\n        durableObjectStorage = (inMemory = void),\n        bindings = [\n          ( name = \"DURABLE\", durableObjectNamespace = \"TestDurableObject\" ),\n          ( name = \"DURABLE_PREVENT_EVICTION\", durableObjectNamespace = \"TestDurableObjectPreventEviction\" ),\n          ( name = \"EPHEMERAL\", durableObjectNamespace = \"TestEphemeralObject\" ),\n          ( name = \"EPHEMERAL_PREVENT_EVICTION\", durableObjectNamespace = \"TestEphemeralObjectPreventEviction\" ),\n        ],\n     )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/server/tests/weakref/BUILD.bazel",
    "content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_test\")\n\njs_test(\n    name = \"js-weak-ref-test\",\n    data = [\n        \":config.capnp\",\n        \":index.mjs\",\n        \"//src/workerd/server:workerd\",\n        \"//src/workerd/server/tests:server-harness_js_lib\",\n    ],\n    entry_point = \"test.mjs\",\n    env = {\n        \"WORKERD_BINARY\": \"$(rootpath //src/workerd/server:workerd)\",\n        \"WORKERD_CONFIG\": \"$(rootpath :config.capnp)\",\n    },\n    flaky = True,\n    tags = [\"js-test\"],\n)\n"
  },
  {
    "path": "src/workerd/server/tests/weakref/config.capnp",
    "content": "# config.capnp\nusing Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  v8Flags = [ \"--expose-gc\" ],\n  services = [\n    ( name = \"main\", worker = .worker ),\n  ],\n  sockets = [\n    ( name = \"http\", address = \"*:0\", http = (), service = \"main\" ),\n  ]\n);\n\nconst worker :Workerd.Worker = (\n  modules = [\n    ( name = \"./index.mjs\", esModule = embed \"index.mjs\" )\n  ],\n  compatibilityFlags = [\"enable_weak_ref\"],\n);\n\n"
  },
  {
    "path": "src/workerd/server/tests/weakref/index.mjs",
    "content": "let frCounter = 0;\nlet weakRefState = { isDereferenced: false, value: null };\n\nconst fr = new FinalizationRegistry(() => {\n  ++frCounter;\n});\n\nexport default {\n  async fetch(request, env, ctx) {\n    const url = new URL(request.url);\n    const test = url.searchParams.get('test') || 'fr';\n\n    if (test === 'fr') {\n      // Test FinalizationRegistry\n      (function () {\n        fr.register({}, '');\n      })();\n\n      // Ensure obj gets GC'd\n      gc();\n\n      if (frCounter > 0) {\n        await scheduler.wait(10);\n      }\n\n      return new Response(frCounter.toString());\n    } else if (test === 'weakref') {\n      // Test WeakRef\n      if (url.searchParams.has('create')) {\n        const obj = { message: \"I'm alive!\" };\n        const ref = new WeakRef(obj);\n\n        // Store the WeakRef for later testing\n        weakRefState.ref = ref;\n        weakRefState.isDereferenced = false;\n        weakRefState.value = ref.deref()?.message || null;\n\n        return Response.json({\n          created: true,\n          value: weakRefState.value,\n        });\n      }\n      if (url.searchParams.has('gc')) {\n        // Force garbage collection and check if the WeakRef has been cleared\n        gc();\n        await scheduler.wait(10); // Give GC time to clean up\n      }\n\n      // Just return the current state\n      const value = weakRefState.ref?.deref()?.message || null;\n      weakRefState.isDereferenced = value === null;\n      weakRefState.value = value;\n\n      return Response.json({\n        isDereferenced: weakRefState.isDereferenced,\n        value: weakRefState.value,\n      });\n    }\n\n    return new Response('Invalid test', { status: 400 });\n  },\n};\n"
  },
  {
    "path": "src/workerd/server/tests/weakref/test.mjs",
    "content": "/*\nThis is a node.js script which handlers workerd lifecycle and sends multiple requests.\nFinalizationRegistry callbacks run, if scheduled, across I/O boundaries, and wd-test,\nwhich only run tests within a single test() handler, are not enough to test this behaviour.\nThis test now covers both FinalizationRegistry and WeakRef APIs.\n*/\n\nimport { env } from 'node:process';\nimport { beforeEach, afterEach, test, after } from 'node:test';\nimport assert from 'node:assert';\nimport { WorkerdServerHarness } from '../server-harness.mjs';\n\n// Global that is reset for each test.\nlet workerd;\n\nassert.notStrictEqual(\n  env.WORKERD_BINARY,\n  undefined,\n  'You must set the WORKERD_BINARY environment variable.'\n);\nassert.notStrictEqual(\n  env.WORKERD_CONFIG,\n  undefined,\n  'You must set the WORKERD_CONFIG environment variable.'\n);\n\n// Start workerd.\nbeforeEach(async () => {\n  console.log('[TEST] Starting workerd process...');\n  workerd = new WorkerdServerHarness({\n    workerdBinary: env.WORKERD_BINARY,\n    workerdConfig: env.WORKERD_CONFIG,\n\n    // Hard-coded to match a socket name expected in the `workerdConfig` file.\n    listenPortNames: ['http'],\n  });\n\n  await workerd.start();\n\n  await workerd.getListenPort('http');\n  console.log('[TEST] Workerd process started successfully');\n});\n\n// Stop workerd.\nafterEach(async () => {\n  if (workerd) {\n    console.log('[TEST] Stopping workerd process...');\n    try {\n      const [code, signal] = await workerd.stop();\n      console.log(`[TEST] Workerd stopped with code=${code}, signal=${signal}`);\n      assert(\n        code === 0 || signal === 'SIGTERM',\n        `code=${code}, signal=${signal}`\n      );\n    } catch (error) {\n      console.error('[TEST] Error stopping workerd:', error);\n      throw error;\n    } finally {\n      workerd = null;\n    }\n  }\n  console.log('[TEST] Cleanup complete');\n});\n\n// FinalizationRegistry callbacks run across I/O boundaries\ntest('JS FinalizationRegistry', async () => {\n  let httpPort = await workerd.getListenPort('http');\n\n  // The first request doesn't do any I/O so we won't notice the effects\n  // of FinalizationRegistry cleanup callbacks as part of the response\n  const response = await fetch(`http://localhost:${httpPort}?test=fr`);\n  console.log('[TEST] First FR request completed');\n  assert.strictEqual(await response.text(), '0');\n\n  // Subsequent requests do I/O so we will immediately see\n  // the effects of FinalizationRegistry cleanup callbacks\n  for (let i = 0; i < 2; ++i) {\n    console.log(`[TEST] FR request ${i + 2} starting...`);\n    const response = await fetch(`http://localhost:${httpPort}?test=fr`);\n    console.log(`[TEST] FR request ${i + 2} completed`);\n    assert.strictEqual(await response.text(), `${i + 2}`);\n  }\n});\n\n// Test WeakRef behavior\ntest('JS WeakRef', async () => {\n  let httpPort = await workerd.getListenPort('http');\n\n  // Create a new object and a WeakRef to it\n  console.log('[TEST] Creating WeakRef object...');\n  let response = await fetch(\n    `http://localhost:${httpPort}?test=weakref&create`\n  );\n  let data = await response.json();\n  console.log('[TEST] WeakRef object created:', data);\n\n  // Verify the object is created and accessible via the WeakRef\n  assert.strictEqual(data.created, true);\n  assert.strictEqual(data.value, \"I'm alive!\");\n\n  // Check that the WeakRef is still valid\n  console.log('[TEST] Checking WeakRef validity...');\n  response = await fetch(`http://localhost:${httpPort}?test=weakref`);\n  data = await response.json();\n  console.log('[TEST] WeakRef validity check result:', data);\n  assert.strictEqual(data.isDereferenced, false);\n  assert.strictEqual(data.value, \"I'm alive!\");\n\n  // Force garbage collection and check if the WeakRef is dereferenced\n  console.log('[TEST] Forcing garbage collection...');\n  response = await fetch(`http://localhost:${httpPort}?test=weakref&gc`);\n  data = await response.json();\n  console.log('[TEST] GC result:', data);\n  // Check if the reference is gone after GC\n  assert.strictEqual(data.isDereferenced, true);\n  assert.strictEqual(data.value, null);\n\n  // Create a new object again to verify WeakRef can be reset\n  console.log('[TEST] Creating new WeakRef object after GC...');\n  response = await fetch(`http://localhost:${httpPort}?test=weakref&create`);\n  data = await response.json();\n  console.log('[TEST] New WeakRef object created:', data);\n\n  // The new object should be accessible\n  assert.strictEqual(data.created, true);\n  assert.strictEqual(data.value, \"I'm alive!\");\n});\n\n// Ensure clean exit\nafter(async () => {\n  console.log('[TEST] All tests completed, ensuring clean exit...');\n  // Give a moment for any cleanup to complete\n  await new Promise((resolve) => setTimeout(resolve, 100));\n\n  // Force exit to prevent hanging\n  console.log('[TEST] Exiting process...');\n  process.exit(0);\n});\n\n// Handle uncaught errors\nprocess.on('uncaughtException', (error) => {\n  console.error('[TEST] Uncaught exception:', error);\n  process.exit(1);\n});\n"
  },
  {
    "path": "src/workerd/server/v8-platform-impl.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"v8-platform-impl.h\"\n\n#include <kj/time.h>\n\nnamespace workerd::server {\n\ndouble WorkerdPlatform::CurrentClockTimeMillis() noexcept {\n  return (kj::systemPreciseCalendarClock().now() - kj::UNIX_EPOCH) / kj::MILLISECONDS;\n}\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/v8-platform-impl.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <v8-platform.h>\n\n#include <kj/common.h>\n\nnamespace workerd::server {\n\n// Workerd-specific implementation of v8::Platform.\n//\n// We customize the CurrentClockTimeMillis() virtual method in order to control the value\n// returned by `Date.now()`.\n//\n// Everything else gets passed through to the wrapped v8::Platform implementation (presumably\n// from `jsg::defaultPlatform()`).\nclass WorkerdPlatform final: public v8::Platform {\n public:\n  // This takes a reference to its wrapped platform because otherwise we would have to destroy a\n  // kj::Own in our noexcept destructor (feasible but ugly).\n  explicit WorkerdPlatform(v8::Platform& inner): inner(inner) {}\n\n  ~WorkerdPlatform() noexcept {}\n\n  // =====================================================================================\n  // v8::Platform API\n\n  v8::PageAllocator* GetPageAllocator() noexcept override {\n    return inner.GetPageAllocator();\n  }\n\n  int NumberOfWorkerThreads() noexcept override {\n    return inner.NumberOfWorkerThreads();\n  }\n\n  std::shared_ptr<v8::TaskRunner> GetForegroundTaskRunner(\n      v8::Isolate* isolate, v8::TaskPriority priority) noexcept override {\n    return inner.GetForegroundTaskRunner(isolate, priority);\n  }\n\n  void PostTaskOnWorkerThreadImpl(v8::TaskPriority priority,\n      std::unique_ptr<v8::Task> task,\n      const v8::SourceLocation& location) override {\n    inner.PostTaskOnWorkerThreadImpl(priority, kj::mv(task), location);\n  }\n\n  void PostDelayedTaskOnWorkerThreadImpl(v8::TaskPriority priority,\n      std::unique_ptr<v8::Task> task,\n      double delay_in_seconds,\n      const v8::SourceLocation& location) override {\n    inner.PostDelayedTaskOnWorkerThreadImpl(priority, kj::mv(task), delay_in_seconds, location);\n  }\n\n  bool IdleTasksEnabled(v8::Isolate* isolate) noexcept override {\n    return inner.IdleTasksEnabled(isolate);\n  }\n\n  std::unique_ptr<v8::JobHandle> CreateJobImpl(v8::TaskPriority priority,\n      std::unique_ptr<v8::JobTask> job_task,\n      const v8::SourceLocation& location) override {\n    return inner.CreateJobImpl(priority, kj::mv(job_task), location);\n  }\n\n  double MonotonicallyIncreasingTime() noexcept override {\n    return inner.MonotonicallyIncreasingTime();\n  }\n\n  // Overridden to return KJ time\n  double CurrentClockTimeMillis() noexcept override;\n\n  v8::TracingController* GetTracingController() noexcept override {\n    return inner.GetTracingController();\n  }\n\n private:\n  v8::Platform& inner;\n};\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/workerd-api.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"workerd-api.h\"\n\n#include <workerd/api/actor-state.h>\n#include <workerd/api/actor.h>\n#include <workerd/api/analytics-engine.h>\n#include <workerd/api/base64.h>\n#include <workerd/api/cache.h>\n#include <workerd/api/capnp.h>\n#include <workerd/api/commonjs.h>\n#include <workerd/api/container.h>\n#include <workerd/api/crypto/impl.h>\n#include <workerd/api/encoding.h>\n#include <workerd/api/events.h>\n#include <workerd/api/eventsource.h>\n#include <workerd/api/export-loopback.h>\n#include <workerd/api/filesystem.h>\n#include <workerd/api/global-scope.h>\n#include <workerd/api/html-rewriter.h>\n#include <workerd/api/hyperdrive.h>\n#include <workerd/api/kv.h>\n#include <workerd/api/memory-cache.h>\n#include <workerd/api/modules.h>\n#include <workerd/api/node/node.h>\n#include <workerd/api/performance.h>\n#include <workerd/api/pyodide/pyodide.h>\n#include <workerd/api/pyodide/requirements.h>\n#include <workerd/api/pyodide/setup-emscripten.h>\n#include <workerd/api/queue.h>\n#include <workerd/api/r2-admin.h>\n#include <workerd/api/r2.h>\n#include <workerd/api/scheduled.h>\n#include <workerd/api/sockets.h>\n#include <workerd/api/sql.h>\n#include <workerd/api/streams.h>\n#include <workerd/api/streams/standard.h>\n#include <workerd/api/sync-kv.h>\n#include <workerd/api/trace.h>\n#include <workerd/api/tracing-module.h>\n#include <workerd/api/unsafe.h>\n#include <workerd/api/url-standard.h>\n#include <workerd/api/urlpattern-standard.h>\n#include <workerd/api/urlpattern.h>\n#include <workerd/api/worker-loader.h>\n#include <workerd/api/worker-rpc.h>\n#include <workerd/api/workers-module.h>\n#include <workerd/io/compatibility-date.h>\n#include <workerd/io/promise-wrapper.h>\n#include <workerd/io/worker-modules.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/modules-new.h>\n#include <workerd/jsg/setup.h>\n#include <workerd/jsg/url.h>\n#include <workerd/jsg/util.h>\n#ifdef WORKERD_USE_TRANSPILER\n#include <workerd/rust/transpiler/lib.rs.h>\n#endif  // defined(WORKERD_USE_TRANSPILER)\n#include <workerd/server/actor-id-impl.h>\n#include <workerd/server/fallback-service.h>\n#include <workerd/server/workerd-debug-port-client.h>\n#include <workerd/util/autogate.h>\n#include <workerd/util/thread-scopes.h>\n#include <workerd/util/use-perfetto-categories.h>\n\n#include <kj-rs/kj-rs.h>\n#include <pyodide/generated/pyodide_extra.capnp.h>\n#include <pyodide/python-entrypoint.embed.h>\n\n#include <kj/compat/gzip.h>\n#include <kj/compat/http.h>\n#include <kj/compat/tls.h>\n#include <kj/compat/url.h>\n\nusing namespace kj_rs;\n\nnamespace workerd::server {\n\nusing api::pyodide::PythonConfig;\n\nnamespace {\nJSG_DECLARE_ISOLATE_TYPE(JsgWorkerdIsolate,\n    // Declares the listing of host object types and structs that the jsg\n    // automatic type mapping will understand. Each of the various\n    // NNNN_ISOLATE_TYPES macros are defined in different header files\n    // (e.g. GLOBAL_SCOPE_ISOLATE_TYPES is defined in api/global-scope.h).\n    //\n    // Global scope types are defined first just by convention, the rest\n    // of the list is in alphabetical order for easier readability (the\n    // actual order of the items is unimportant), followed by additional\n    // types defined in worker.c++ or as part of jsg.\n    //\n    // When adding a new NNNN_ISOLATE_TYPES macro, remember to add it to\n    // src/workerd/api/rtti.c++ too (and tools/api-encoder.c++ for the\n    // time being), so it gets included in the TypeScript types.\n    EW_GLOBAL_SCOPE_ISOLATE_TYPES,\n\n    EW_ACTOR_ISOLATE_TYPES,\n    EW_ACTOR_STATE_ISOLATE_TYPES,\n    EW_ANALYTICS_ENGINE_ISOLATE_TYPES,\n    EW_BASE64_ISOLATE_TYPES,\n    EW_BASICS_ISOLATE_TYPES,\n    EW_BLOB_ISOLATE_TYPES,\n    EW_CACHE_ISOLATE_TYPES,\n    EW_CAPNP_TYPES,\n    EW_CONTAINER_ISOLATE_TYPES,\n    EW_CJS_ISOLATE_TYPES,\n    EW_CRYPTO_ISOLATE_TYPES,\n    EW_ENCODING_ISOLATE_TYPES,\n    EW_EVENTS_ISOLATE_TYPES,\n    EW_FORMDATA_ISOLATE_TYPES,\n    EW_HTML_REWRITER_ISOLATE_TYPES,\n    EW_HTTP_ISOLATE_TYPES,\n    EW_SOCKETS_ISOLATE_TYPES,\n    EW_KV_ISOLATE_TYPES,\n    EW_PYODIDE_ISOLATE_TYPES,\n    EW_QUEUE_ISOLATE_TYPES,\n    EW_R2_PUBLIC_BETA_ADMIN_ISOLATE_TYPES,\n    EW_R2_PUBLIC_BETA_ISOLATE_TYPES,\n    EW_WORKER_RPC_ISOLATE_TYPES,\n    EW_SCHEDULED_ISOLATE_TYPES,\n    EW_STREAMS_ISOLATE_TYPES,\n    EW_TRACE_ISOLATE_TYPES,\n    EW_UNSAFE_ISOLATE_TYPES,\n    EW_MEMORY_CACHE_ISOLATE_TYPES,\n    EW_URL_ISOLATE_TYPES,\n    EW_URL_STANDARD_ISOLATE_TYPES,\n    EW_URLPATTERN_ISOLATE_TYPES,\n    EW_URLPATTERN_STANDARD_ISOLATE_TYPES,\n    EW_WEB_FILESYSTEM_ISOLATE_TYPE,\n    EW_FILESYSTEM_ISOLATE_TYPES,\n    EW_WEBSOCKET_ISOLATE_TYPES,\n    EW_SQL_ISOLATE_TYPES,\n    EW_SYNC_KV_ISOLATE_TYPES,\n    EW_NODE_ISOLATE_TYPES,\n    EW_RTTI_ISOLATE_TYPES,\n    EW_HYPERDRIVE_ISOLATE_TYPES,\n    EW_EVENTSOURCE_ISOLATE_TYPES,\n    EW_WORKER_LOADER_ISOLATE_TYPES,\n    EW_MESSAGECHANNEL_ISOLATE_TYPES,\n    EW_WORKERS_MODULE_ISOLATE_TYPES,\n    EW_EXPORT_LOOPBACK_ISOLATE_TYPES,\n    EW_PERFORMANCE_ISOLATE_TYPES,\n    EW_TRACING_MODULE_ISOLATE_TYPES,\n    EW_WORKERD_DEBUG_PORT_CLIENT_ISOLATE_TYPES,\n    workerd::api::EnvModule,\n    workerd::api::PythonPatchedEnv,\n\n    jsg::TypeWrapperExtension<PromiseWrapper>,\n    jsg::InjectConfiguration<CompatibilityFlags::Reader>,\n    Worker::Api::ErrorInterface);\n\nstatic const PythonConfig defaultConfig{\n  .packageDiskCacheRoot = kj::none,\n  .pyodideDiskCacheRoot = kj::none,\n  .createSnapshot = false,\n  .createBaselineSnapshot = false,\n};\n\n// An ActorStorage implementation which will always respond to reads as if the state is empty,\n// and will fail any writes.\nclass EmptyReadOnlyActorStorageImpl final: public rpc::ActorStorage::Stage::Server {\n public:\n  kj::Promise<void> get(GetContext context) override {\n    return kj::READY_NOW;\n  }\n  kj::Promise<void> getMultiple(GetMultipleContext context) override {\n    return context.getParams()\n        .getStream()\n        .endRequest(capnp::MessageSize{2, 0})\n        .sendIgnoringResult();\n  }\n  kj::Promise<void> list(ListContext context) override {\n    return context.getParams()\n        .getStream()\n        .endRequest(capnp::MessageSize{2, 0})\n        .sendIgnoringResult();\n  }\n  kj::Promise<void> getAlarm(GetAlarmContext context) override {\n    return kj::READY_NOW;\n  }\n  kj::Promise<void> txn(TxnContext context) override {\n    auto results = context.getResults(capnp::MessageSize{2, 1});\n    results.setTransaction(kj::heap<TransactionImpl>());\n    return kj::READY_NOW;\n  }\n\n private:\n  class TransactionImpl final: public rpc::ActorStorage::Stage::Transaction::Server {\n   protected:\n    kj::Promise<void> get(GetContext context) override {\n      return kj::READY_NOW;\n    }\n    kj::Promise<void> getMultiple(GetMultipleContext context) override {\n      return context.getParams()\n          .getStream()\n          .endRequest(capnp::MessageSize{2, 0})\n          .sendIgnoringResult();\n    }\n    kj::Promise<void> list(ListContext context) override {\n      return context.getParams()\n          .getStream()\n          .endRequest(capnp::MessageSize{2, 0})\n          .sendIgnoringResult();\n    }\n    kj::Promise<void> getAlarm(GetAlarmContext context) override {\n      return kj::READY_NOW;\n    }\n    kj::Promise<void> commit(CommitContext context) override {\n      return kj::READY_NOW;\n    }\n  };\n};\n\n}  // namespace\n\n/**\n * This function matches the implementation of `getPythonRequirements` in the internal repo. But it\n * works on the workerd ModulesSource definition rather than the WorkerBundle.\n */\nkj::Array<kj::String> getPythonRequirements(const Worker::Script::ModulesSource& source) {\n  kj::Vector<kj::String> requirements;\n\n  for (auto& def: source.modules) {\n    KJ_SWITCH_ONEOF(def.content) {\n      KJ_CASE_ONEOF(content, Worker::Script::PythonRequirement) {\n        requirements.add(api::pyodide::canonicalizePythonPackageName(def.name));\n      }\n      KJ_CASE_ONEOF_DEFAULT {\n        break;\n      }\n    }\n  }\n\n  return requirements.releaseAsArray();\n}\n\nstruct WorkerdApi::Impl final {\n  kj::Own<CompatibilityFlags::Reader> features;\n  capnp::List<config::Extension>::Reader extensions;\n  kj::Own<JsgIsolateObserver> observer;\n  JsgWorkerdIsolate jsgIsolate;\n  api::MemoryCacheProvider& memoryCacheProvider;\n  const PythonConfig& pythonConfig;\n\n  class Configuration {\n   public:\n    Configuration(Impl& impl)\n        : features(*impl.features),\n          jsgConfig(jsg::JsgConfig{\n            .noSubstituteNull = features.getNoSubstituteNull(),\n            .unwrapCustomThenables = features.getUnwrapCustomThenables(),\n            .fetchIterableTypeSupport = features.getFetchIterableTypeSupport(),\n            .fetchIterableTypeSupportOverrideAdjustment =\n                features.getFetchIterableTypeSupportOverrideAdjustment(),\n            .fastApiEnabled = util::Autogate::isEnabled(util::AutogateKey::V8_FAST_API),\n          }) {}\n    operator const CompatibilityFlags::Reader() const {\n      return features;\n    }\n    operator const jsg::JsgConfig&() const {\n      return jsgConfig;\n    }\n\n   private:\n    CompatibilityFlags::Reader& features;\n    jsg::JsgConfig jsgConfig;\n  };\n\n  Impl(jsg::V8System& v8System,\n      CompatibilityFlags::Reader featuresParam,\n      capnp::List<config::Extension>::Reader extensionsParam,\n      v8::Isolate::CreateParams createParams,\n      v8::IsolateGroup group,\n      kj::Own<JsgIsolateObserver> observerParam,\n      api::MemoryCacheProvider& memoryCacheProvider,\n      const PythonConfig& pythonConfig = defaultConfig)\n      : features(capnp::clone(featuresParam)),\n        extensions(extensionsParam),\n        observer(kj::atomicAddRef(*observerParam)),\n        jsgIsolate(v8System,\n            group,\n            Configuration(*this),\n            kj::mv(observerParam),\n            jsg::defaultExternalStringAllocator(),\n            kj::mv(createParams)),\n        memoryCacheProvider(memoryCacheProvider),\n        pythonConfig(pythonConfig) {\n    jsgIsolate.runInLockScope([&](JsgWorkerdIsolate::Lock& lock) {\n      if (featuresParam.getNewModuleRegistry()) {\n        jsgIsolate.setUsingNewModuleRegistry();\n      }\n\n      // Allows us to begin experimenting with eval/new fuction enabled in\n      // preparation for *possibly* enabling it by default in the future\n      // once v8 sandbox is fully enabled and rolled out.\n      if (featuresParam.getExperimentalAllowEvalAlways()) {\n        jsgIsolate.setAllowsAllowEval();\n      }\n    });\n  }\n};\n\nWorkerdApi::WorkerdApi(jsg::V8System& v8System,\n    CompatibilityFlags::Reader features,\n    capnp::List<config::Extension>::Reader extensions,\n    v8::Isolate::CreateParams createParams,\n    v8::IsolateGroup group,\n    kj::Own<JsgIsolateObserver> observer,\n    api::MemoryCacheProvider& memoryCacheProvider,\n    const PythonConfig& pythonConfig)\n    : impl(kj::heap<Impl>(v8System,\n          features,\n          extensions,\n          kj::mv(createParams),\n          group,\n          kj::mv(observer),\n          memoryCacheProvider,\n          pythonConfig)) {}\nWorkerdApi::~WorkerdApi() noexcept(false) {}\n\nkj::Own<jsg::Lock> WorkerdApi::lock(jsg::V8StackScope& stackScope) const {\n  return kj::heap<JsgWorkerdIsolate::Lock>(impl->jsgIsolate, stackScope);\n}\nCompatibilityFlags::Reader WorkerdApi::getFeatureFlags() const {\n  return *impl->features;\n}\njsg::JsContext<api::ServiceWorkerGlobalScope> WorkerdApi::newContext(\n    jsg::Lock& lock, Worker::Api::NewContextOptions options) const {\n  jsg::NewContextOptions opts{\n    .newModuleRegistry = options.newModuleRegistry,\n    .schemaLoader = options.schemaLoader,\n    .enableWeakRef = getFeatureFlags().getJsWeakRef(),\n  };\n  return kj::downcast<JsgWorkerdIsolate::Lock>(lock).newContext<api::ServiceWorkerGlobalScope>(\n      kj::mv(opts));\n}\njsg::Dict<NamedExport> WorkerdApi::unwrapExports(\n    jsg::Lock& lock, v8::Local<v8::Value> moduleNamespace) const {\n  return kj::downcast<JsgWorkerdIsolate::Lock>(lock).unwrap<jsg::Dict<NamedExport>>(\n      lock.v8Context(), moduleNamespace);\n}\nNamedExport WorkerdApi::unwrapExport(jsg::Lock& lock, v8::Local<v8::Value> exportVal) const {\n  return kj::downcast<JsgWorkerdIsolate::Lock>(lock).unwrap<NamedExport>(\n      lock.v8Context(), exportVal);\n}\nEntrypointClasses WorkerdApi::getEntrypointClasses(jsg::Lock& lock) const {\n  auto& typedLock = kj::downcast<JsgWorkerdIsolate::Lock>(lock);\n\n  return {\n    .workerEntrypoint = typedLock.getConstructor<api::WorkerEntrypoint>(lock.v8Context()),\n    .durableObject = typedLock.getConstructor<api::DurableObjectBase>(lock.v8Context()),\n    .workflowEntrypoint = typedLock.getConstructor<api::WorkflowEntrypoint>(lock.v8Context()),\n  };\n}\nconst jsg::TypeHandler<Worker::Api::ErrorInterface>& WorkerdApi::getErrorInterfaceTypeHandler(\n    jsg::Lock& lock) const {\n  return kj::downcast<JsgWorkerdIsolate::Lock>(lock).getTypeHandler<ErrorInterface>();\n}\n\nconst jsg::TypeHandler<api::QueueExportedHandler>& WorkerdApi::getQueueTypeHandler(\n    jsg::Lock& lock) const {\n  return kj::downcast<JsgWorkerdIsolate::Lock>(lock).getTypeHandler<api::QueueExportedHandler>();\n}\n\njsg::JsObject WorkerdApi::wrapExecutionContext(\n    jsg::Lock& lock, jsg::Ref<api::ExecutionContext> ref) const {\n  return jsg::JsObject(\n      kj::downcast<JsgWorkerdIsolate::Lock>(lock).wrap(lock.v8Context(), kj::mv(ref)));\n}\n\nconst jsg::IsolateObserver& WorkerdApi::getObserver() const {\n  return *impl->observer;\n}\n\nvoid WorkerdApi::setIsolateObserver(IsolateObserver&) {};\n\nWorker::Script::Source WorkerdApi::extractSource(kj::StringPtr name,\n    config::Worker::Reader conf,\n    CompatibilityFlags::Reader featureFlags,\n    Worker::ValidationErrorReporter& errorReporter) {\n  TRACE_EVENT(\"workerd\", \"WorkerdApi::extractSource()\");\n  switch (conf.which()) {\n    case config::Worker::MODULES: {\n      auto modules = conf.getModules();\n      if (modules.size() == 0) {\n        errorReporter.addError(kj::str(\"Modules list cannot be empty.\"));\n        goto invalid;\n      }\n\n      bool isPython = false;\n      auto moduleArray = KJ_MAP(module, modules) -> Worker::Script::Module {\n        if (module.isPythonModule()) {\n          isPython = true;\n        }\n        return readModuleConf(module, featureFlags, errorReporter);\n      };\n\n      Worker::Script::ModulesSource result{\n        .mainModule = modules[0].getName(), .modules = kj::mv(moduleArray), .isPython = isPython};\n\n      return result;\n    }\n    case config::Worker::SERVICE_WORKER_SCRIPT: {\n      uint wasmCount = 0;\n      for (auto binding: conf.getBindings()) {\n        if (binding.isWasmModule()) ++wasmCount;\n      }\n\n      auto globals = kj::heapArrayBuilder<Worker::Script::Module>(wasmCount);\n      for (auto binding: conf.getBindings()) {\n        if (binding.isWasmModule()) {\n          globals.add(Worker::Script::Module{.name = binding.getName(),\n            .content = Worker::Script::WasmModule{.body = binding.getWasmModule()}});\n        }\n      }\n\n      return Worker::Script::ScriptSource{\n        .mainScript = conf.getServiceWorkerScript(),\n        .mainScriptName = name,\n        .globals = globals.finish(),\n      };\n    }\n    case config::Worker::INHERIT:\n      // TODO(beta): Support inherit.\n      KJ_FAIL_ASSERT(\"inherit should have been handled earlier\");\n  }\n\n  errorReporter.addError(kj::str(\"Encountered unknown Worker code type. Was the \"\n                                 \"config compiled with a newer version of the schema?\"));\ninvalid:\n  return Worker::Script::ScriptSource{\"\"_kj, name, nullptr};\n}\n\nkj::Array<Worker::Script::CompiledGlobal> WorkerdApi::compileServiceWorkerGlobals(jsg::Lock& js,\n    const Worker::Script::ScriptSource& source,\n    const Worker::Isolate& isolate) const {\n  TRACE_EVENT(\"workerd\", \"WorkerdApi::compileScriptGlobals()\");\n  const jsg::CompilationObserver& observer = *impl->observer;\n  return workerd::modules::legacy::compileServiceWorkerGlobals<JsgWorkerdIsolate>(\n      js, source, isolate, observer);\n}\n\nnamespace {\nkj::Maybe<jsg::ModuleRegistry::ModuleInfo> tryCompileLegacyModule(jsg::Lock& js,\n    kj::StringPtr name,\n    const Worker::Script::ModuleContent& content,\n    const jsg::CompilationObserver& observer,\n    CompatibilityFlags::Reader featureFlags) {\n  return modules::legacy::tryCompileLegacyModule<JsgWorkerdIsolate>(\n      js, name, content, observer, featureFlags);\n}\n}  // namespace\n\n// Part of the original module registry implementation.\nkj::Maybe<jsg::ModuleRegistry::ModuleInfo> WorkerdApi::tryCompileModule(jsg::Lock& js,\n    config::Worker::Module::Reader conf,\n    const jsg::CompilationObserver& observer,\n    CompatibilityFlags::Reader featureFlags) {\n  auto module = readModuleConf(conf, featureFlags);\n  return tryCompileLegacyModule(js, module.name, module.content, observer, featureFlags);\n}\n\nWorker::Script::Module WorkerdApi::readModuleConf(config::Worker::Module::Reader conf,\n    CompatibilityFlags::Reader featureFlags,\n    kj::Maybe<Worker::ValidationErrorReporter&> errorReporter) {\n  return {.name = conf.getName(), .content = [&]() -> Worker::Script::ModuleContent {\n    switch (conf.which()) {\n      case config::Worker::Module::TEXT:\n        return Worker::Script::TextModule{conf.getText()};\n      case config::Worker::Module::DATA:\n        return Worker::Script::DataModule{conf.getData()};\n      case config::Worker::Module::WASM:\n        return Worker::Script::WasmModule{conf.getWasm()};\n      case config::Worker::Module::JSON:\n        return Worker::Script::JsonModule{conf.getJson()};\n      case config::Worker::Module::ES_MODULE:\n        // TODO(soon): Update this to also support full TS transform\n        // with a separate compat flag.\n#ifdef WORKERD_USE_TRANSPILER\n        if (featureFlags.getTypescriptStripTypes()) {\n          auto output = rust::transpiler::ts_strip(\n              // value comes from capnp so it is a valid utf-8\n              conf.getName().as<RustUncheckedUtf8>(), conf.getEsModule().asBytes().as<Rust>());\n\n          if (output.success) {\n            return Worker::Script::EsModule{\n              .body = ::kj::from<Rust>(output.code), .ownBody = kj::mv(output.code)};\n          }\n\n          auto description = kj::str(\"Error transpiling \", conf.getName(), \" : \", output.error);\n          for (auto& diag: output.diagnostics) {\n            description = kj::str(description, \"\\n    \", diag.message);\n          }\n          KJ_IF_SOME(reporter, errorReporter) {\n            reporter.addError(kj::mv(description));\n            return Worker::Script::TextModule{\"\"};\n          } else {\n            KJ_FAIL_REQUIRE(description);\n          }\n        }\n#endif  // defined(WORKERD_USE_TRANSPILER)\n        return Worker::Script::EsModule{static_cast<kj::StringPtr>(conf.getEsModule())};\n      case config::Worker::Module::COMMON_JS_MODULE: {\n        Worker::Script::CommonJsModule result{.body = conf.getCommonJsModule()};\n        if (conf.hasNamedExports()) {\n          result.namedExports = KJ_MAP(name, conf.getNamedExports()) -> kj::StringPtr { return name; };\n        }\n        return result;\n      }\n      case config::Worker::Module::PYTHON_MODULE:\n        return Worker::Script::PythonModule{conf.getPythonModule()};\n      case config::Worker::Module::PYTHON_REQUIREMENT:\n        return Worker::Script::PythonRequirement{};\n      case config::Worker::Module::OBSOLETE: {\n        // A non-supported or obsolete module type was configured\n        KJ_FAIL_REQUIRE(\"Worker bundle specified an unsupported module type\");\n      }\n    }\n\n    KJ_IF_SOME(e, errorReporter) {\n      e.addError(kj::str(\"Encountered unknown Worker.Module type. Was the \"\n                         \"config compiled with a newer version of the schema?\"));\n      return Worker::Script::TextModule{\"\"};\n    } else {\n      KJ_FAIL_REQUIRE(\"unknown module type\", (uint)conf.which());\n    }\n  }()};\n}\n\n// Part of the original module registry implementation.\nvoid WorkerdApi::compileModules(jsg::Lock& lockParam,\n    const Worker::Script::ModulesSource& source,\n    const Worker::Isolate& isolate,\n    kj::Maybe<kj::Own<api::pyodide::ArtifactBundler_State>> artifacts,\n    SpanParent parentSpan) const {\n  TRACE_EVENT(\"workerd\", \"WorkerdApi::compileModules()\");\n  lockParam.withinHandleScope([&] {\n    auto modules = jsg::ModuleRegistryImpl<JsgWorkerdIsolate_TypeWrapper>::from(lockParam);\n\n    using namespace workerd::api::pyodide;\n    auto featureFlags = getFeatureFlags();\n\n    for (auto& module: source.modules) {\n      auto path = kj::Path::parse(module.name);\n      auto maybeInfo = tryCompileLegacyModule(\n          lockParam, module.name, module.content, modules->getObserver(), featureFlags);\n      KJ_IF_SOME(info, maybeInfo) {\n        modules->add(path, kj::mv(info));\n      }\n    }\n\n    api::registerModules(*modules, featureFlags);\n\n    if (source.isPython) {\n      modules::python::registerPythonWorkerdModules<JsgWorkerdIsolate>(\n          lockParam, *modules, featureFlags, kj::mv(artifacts), impl->pythonConfig, source);\n    }\n\n    for (auto extension: impl->extensions) {\n      for (auto module: extension.getModules()) {\n        modules->addBuiltinModule(module.getName(), module.getEsModule().asArray(),\n            module.getInternal() ? jsg::ModuleRegistry::Type::INTERNAL\n                                 : jsg::ModuleRegistry::Type::BUILTIN);\n      }\n    }\n  });\n}\n\nstatic v8::Local<v8::Value> createBindingValue(JsgWorkerdIsolate::Lock& lock,\n    const WorkerdApi::Global& global,\n    CompatibilityFlags::Reader featureFlags,\n    uint32_t ownerId,\n    api::MemoryCacheProvider& memoryCacheProvider) {\n  TRACE_EVENT(\"workerd\", \"WorkerdApi::createBindingValue()\");\n  using Global = WorkerdApi::Global;\n  auto context = lock.v8Context();\n\n  v8::Local<v8::Value> value;\n\n  // When new binding types are created. If their value resolves to be a string\n  // or a JSON stringified/stringifiable value, then it should be added to\n  // process.env here as well, just like with Global::Json and kj::String\n  // entries.\n  //\n  // It is important to understand the process.env is fundamentally different\n  // from the existing bag of bindings. The keys and values on process.env are\n  // fundamentally a Record<string, string>, where any value set on process.env\n  // is coerced to a string. Having a separate object for process.env is the\n  // easiest approach as opposed to wrapping the bindings/env with a proxy that\n  // tries to abstract the details. If this ends up needing to change later then\n  // as long as the observable behavior remains the same we can do so without\n  // Yet Another Compat Flag.\n\n  KJ_SWITCH_ONEOF(global.value) {\n    KJ_CASE_ONEOF(json, Global::Json) {\n      value = jsg::check(v8::JSON::Parse(context, lock.str(json.text)));\n    }\n\n    KJ_CASE_ONEOF(pipeline, Global::Fetcher) {\n      value = lock.wrap(context,\n          lock.alloc<api::Fetcher>(pipeline.channel,\n              pipeline.requiresHost ? api::Fetcher::RequiresHostAndProtocol::YES\n                                    : api::Fetcher::RequiresHostAndProtocol::NO,\n              pipeline.isInHouse));\n    }\n\n    KJ_CASE_ONEOF(loopback, Global::LoopbackServiceStub) {\n      value = lock.wrap(context, lock.alloc<api::LoopbackServiceStub>(loopback.channel));\n    }\n\n    KJ_CASE_ONEOF(ns, Global::KvNamespace) {\n      value = lock.wrap(context,\n          lock.alloc<api::KvNamespace>(kj::str(ns.bindingName),\n              kj::Array<api::KvNamespace::AdditionalHeader>{}, ns.subrequestChannel));\n    }\n\n    KJ_CASE_ONEOF(r2, Global::R2Bucket) {\n      value = lock.wrap(context,\n          lock.alloc<api::public_beta::R2Bucket>(\n              featureFlags, r2.subrequestChannel, kj::str(r2.bucket), kj::str(r2.bindingName)));\n    }\n\n    KJ_CASE_ONEOF(r2a, Global::R2Admin) {\n      value = lock.wrap(\n          context, lock.alloc<api::public_beta::R2Admin>(featureFlags, r2a.subrequestChannel));\n    }\n\n    KJ_CASE_ONEOF(ns, Global::QueueBinding) {\n      value = lock.wrap(context, lock.alloc<api::WorkerQueue>(ns.subrequestChannel));\n    }\n\n    KJ_CASE_ONEOF(key, Global::CryptoKey) {\n      api::SubtleCrypto::ImportKeyData keyData;\n      KJ_SWITCH_ONEOF(key.keyData) {\n        KJ_CASE_ONEOF(data, kj::Array<byte>) {\n          keyData = kj::heapArray(data.asPtr());\n        }\n        KJ_CASE_ONEOF(json, Global::Json) {\n          v8::Local<v8::String> str = lock.wrap(context, kj::mv(json.text));\n          v8::Local<v8::Value> obj = jsg::check(v8::JSON::Parse(context, str));\n          keyData = lock.unwrap<api::SubtleCrypto::ImportKeyData>(context, obj);\n        }\n      }\n\n      v8::Local<v8::String> algoStr = lock.wrap(context, kj::mv(key.algorithm.text));\n      v8::Local<v8::Value> algo = jsg::check(v8::JSON::Parse(context, algoStr));\n      auto importKeyAlgo =\n          lock.unwrap<kj::OneOf<kj::String, api::SubtleCrypto::ImportKeyAlgorithm>>(context, algo);\n\n      jsg::Ref<api::CryptoKey> importedKey =\n          api::SubtleCrypto().importKeySync(lock, key.format, kj::mv(keyData),\n              api::interpretAlgorithmParam(kj::mv(importKeyAlgo)), key.extractable, key.usages);\n\n      value = lock.wrap(context, kj::mv(importedKey));\n    }\n\n    KJ_CASE_ONEOF(cache, Global::MemoryCache) {\n      value = lock.wrap(context,\n          lock.alloc<api::MemoryCache>(\n              api::SharedMemoryCache::Use(memoryCacheProvider.getInstance(cache.cacheId),\n                  {\n                    .maxKeys = cache.maxKeys,\n                    .maxValueSize = cache.maxValueSize,\n                    .maxTotalValueSize = cache.maxTotalValueSize,\n                  })));\n    }\n\n    KJ_CASE_ONEOF(ns, Global::EphemeralActorNamespace) {\n      value = lock.wrap(context, lock.alloc<api::ColoLocalActorNamespace>(ns.actorChannel));\n    }\n    KJ_CASE_ONEOF(ns, Global::LoopbackEphemeralActorNamespace) {\n      value = lock.wrap(context,\n          lock.alloc<api::LoopbackColoLocalActorNamespace>(\n              ns.actorChannel, lock.alloc<api::LoopbackDurableObjectClass>(ns.classChannel)));\n    }\n\n    KJ_CASE_ONEOF(ns, Global::DurableActorNamespace) {\n      value = lock.wrap(context,\n          lock.alloc<api::DurableObjectNamespace>(\n              ns.actorChannel, kj::heap<ActorIdFactoryImpl>(ns.uniqueKey)));\n    }\n    KJ_CASE_ONEOF(ns, Global::LoopbackDurableActorNamespace) {\n      value = lock.wrap(context,\n          lock.alloc<api::LoopbackDurableObjectNamespace>(ns.actorChannel,\n              kj::heap<ActorIdFactoryImpl>(ns.uniqueKey),\n              lock.alloc<api::LoopbackDurableObjectClass>(ns.classChannel)));\n    }\n\n    KJ_CASE_ONEOF(ae, Global::AnalyticsEngine) {\n      // Use subrequestChannel as logfwdrChannel\n      value = lock.wrap(context,\n          lock.alloc<api::AnalyticsEngine>(\n              ae.subrequestChannel, kj::str(ae.dataset), ae.version, ownerId));\n    }\n\n    KJ_CASE_ONEOF(text, kj::String) {\n      value = lock.wrap(context, kj::mv(text));\n    }\n\n    KJ_CASE_ONEOF(data, kj::Array<byte>) {\n      value = lock.wrap(context, kj::heapArray(data.asPtr()));\n    }\n\n    KJ_CASE_ONEOF(wrapped, Global::Wrapped) {\n      auto moduleRegistry = jsg::ModuleRegistry::from(lock);\n      auto moduleName = kj::Path::parse(wrapped.moduleName);\n\n      // wrapped bindings can be produced by internal modules only\n      KJ_IF_SOME(moduleInfo,\n          moduleRegistry->resolve(\n              lock, moduleName, kj::none, jsg::ModuleRegistry::ResolveOption::INTERNAL_ONLY)) {\n        // obtain the module\n        auto module = moduleInfo.module.getHandle(lock);\n        jsg::instantiateModule(lock, module);\n\n        // build env object with inner bindings\n        auto env = v8::Object::New(lock.v8Isolate);\n        for (const auto& innerBinding: wrapped.innerBindings) {\n          lock.v8Set(env, innerBinding.name,\n              createBindingValue(lock, innerBinding, featureFlags, ownerId, memoryCacheProvider));\n        }\n\n        // obtain exported function to call\n        auto moduleNs = jsg::check(module->GetModuleNamespace()->ToObject(context));\n        auto fn = lock.v8Get(moduleNs, wrapped.entrypoint);\n        KJ_ASSERT(fn->IsFunction(), \"Entrypoint is not a function\", wrapped.entrypoint);\n\n        // invoke the function, its result will be binding value\n        v8::Local<v8::Value> arg = env.As<v8::Value>();\n        value = jsg::check(v8::Function::Cast(*fn)->Call(context, context->Global(), 1, &arg));\n      } else {\n        KJ_LOG(\n            ERROR, \"wrapped binding module can't be resolved (internal modules only)\", moduleName);\n      }\n    }\n    KJ_CASE_ONEOF(hyperdrive, Global::Hyperdrive) {\n      value = lock.wrap(context,\n          lock.alloc<api::Hyperdrive>(hyperdrive.subrequestChannel, kj::str(hyperdrive.database),\n              kj::str(hyperdrive.user), kj::str(hyperdrive.password), kj::str(hyperdrive.scheme)));\n    }\n    KJ_CASE_ONEOF(unsafe, Global::UnsafeEval) {\n      value = lock.wrap(context, lock.alloc<api::UnsafeEval>());\n    }\n\n    KJ_CASE_ONEOF(actorClass, Global::ActorClass) {\n      value = lock.wrap(context, lock.alloc<api::DurableObjectClass>(actorClass.channel));\n    }\n\n    KJ_CASE_ONEOF(actorClass, Global::LoopbackActorClass) {\n      value = lock.wrap(context, lock.alloc<api::LoopbackDurableObjectClass>(actorClass.channel));\n    }\n\n    KJ_CASE_ONEOF(workerLoader, Global::WorkerLoader) {\n      value = lock.wrap(context,\n          lock.alloc<api::WorkerLoader>(\n              workerLoader.channel, CompatibilityDateValidation::CODE_VERSION));\n    }\n\n    KJ_CASE_ONEOF(_, Global::WorkerdDebugPort) {\n      value = lock.wrap(context, lock.alloc<WorkerdDebugPortConnector>());\n    }\n  }\n\n  return value;\n}\n\nvoid WorkerdApi::compileGlobals(jsg::Lock& lockParam,\n    kj::ArrayPtr<const Global> globals,\n    v8::Local<v8::Object> target,\n    uint32_t ownerId) const {\n  TRACE_EVENT(\"workerd\", \"WorkerdApi::compileGlobals()\");\n  auto& lock = kj::downcast<JsgWorkerdIsolate::Lock>(lockParam);\n  lockParam.withinHandleScope([&] {\n    auto& featureFlags = *impl->features;\n\n    for (auto& global: globals) {\n      lockParam.withinHandleScope([&] {\n        // Don't use String's usual TypeHandler here because we want to intern the string.\n        auto value =\n            createBindingValue(lock, global, featureFlags, ownerId, impl->memoryCacheProvider);\n        KJ_ASSERT(!value.IsEmpty(), \"global did not produce v8::Value\");\n        lockParam.v8Set(target, global.name, value);\n      });\n    }\n  });\n}\n\nvoid WorkerdApi::setModuleFallbackCallback(kj::Function<ModuleFallbackCallback>&& callback) const {\n  auto& isolateBase = const_cast<JsgWorkerdIsolate&>(impl->jsgIsolate);\n  isolateBase.setModuleFallbackCallback(kj::mv(callback));\n}\n\n// =======================================================================================\n\nWorkerdApi::Global WorkerdApi::Global::clone() const {\n  Global result;\n  result.name = kj::str(name);\n\n  KJ_SWITCH_ONEOF(value) {\n    KJ_CASE_ONEOF(json, Global::Json) {\n      result.value = json.clone();\n    }\n    KJ_CASE_ONEOF(fetcher, Global::Fetcher) {\n      result.value = fetcher.clone();\n    }\n    KJ_CASE_ONEOF(loopback, Global::LoopbackServiceStub) {\n      result.value = loopback.clone();\n    }\n    KJ_CASE_ONEOF(kvNamespace, Global::KvNamespace) {\n      result.value = kvNamespace.clone();\n    }\n    KJ_CASE_ONEOF(r2Bucket, Global::R2Bucket) {\n      result.value = r2Bucket.clone();\n    }\n    KJ_CASE_ONEOF(r2Admin, Global::R2Admin) {\n      result.value = r2Admin.clone();\n    }\n    KJ_CASE_ONEOF(queueBinding, Global::QueueBinding) {\n      result.value = queueBinding.clone();\n    }\n    KJ_CASE_ONEOF(key, Global::CryptoKey) {\n      result.value = key.clone();\n    }\n    KJ_CASE_ONEOF(cache, Global::MemoryCache) {\n      result.value = cache.clone();\n    }\n    KJ_CASE_ONEOF(ns, Global::EphemeralActorNamespace) {\n      result.value = ns.clone();\n    }\n    KJ_CASE_ONEOF(ns, Global::LoopbackEphemeralActorNamespace) {\n      result.value = ns.clone();\n    }\n    KJ_CASE_ONEOF(ns, Global::DurableActorNamespace) {\n      result.value = ns.clone();\n    }\n    KJ_CASE_ONEOF(ns, Global::LoopbackDurableActorNamespace) {\n      result.value = ns.clone();\n    }\n    KJ_CASE_ONEOF(ae, Global::AnalyticsEngine) {\n      result.value = ae.clone();\n    }\n    KJ_CASE_ONEOF(text, kj::String) {\n      result.value = kj::str(text);\n    }\n    KJ_CASE_ONEOF(data, kj::Array<byte>) {\n      result.value = kj::heapArray(data.asPtr());\n    }\n    KJ_CASE_ONEOF(wrapped, Global::Wrapped) {\n      result.value = wrapped.clone();\n    }\n    KJ_CASE_ONEOF(hyperdrive, Global::Hyperdrive) {\n      result.value = hyperdrive.clone();\n    }\n    KJ_CASE_ONEOF(unsafe, Global::UnsafeEval) {\n      result.value = Global::UnsafeEval{};\n    }\n\n    KJ_CASE_ONEOF(actorClass, Global::ActorClass) {\n      result.value = actorClass.clone();\n    }\n    KJ_CASE_ONEOF(actorClass, Global::LoopbackActorClass) {\n      result.value = actorClass.clone();\n    }\n    KJ_CASE_ONEOF(workerLoader, Global::WorkerLoader) {\n      result.value = workerLoader.clone();\n    }\n    KJ_CASE_ONEOF(workerdDebugPort, Global::WorkerdDebugPort) {\n      result.value = workerdDebugPort.clone();\n    }\n  }\n\n  return result;\n}\n\nconst WorkerdApi& WorkerdApi::from(const Worker::Api& api) {\n  return kj::downcast<const WorkerdApi>(api);\n}\n\n// =======================================================================================\n\n// TODO(soon): These are required for python workers but we don't support those yet\n// with the new module registry. Uncomment these when we do.\n// namespace {\n// static constexpr auto PYTHON_TAR_READER = \"export default { }\"_kj;\n\n// static const auto bootrapSpecifier = \"internal:setup-emscripten\"_url;\n// static const auto metadataSpecifier = \"pyodide-internal:runtime-generated/metadata\"_url;\n// static const auto artifactsSpecifier = \"pyodide-internal:artifacts\"_url;\n// static const auto internalJaegerSpecifier = \"pyodide-internal:internalJaeger\"_url;\n// static const auto diskCacheSpecifier = \"pyodide-internal:disk_cache\"_url;\n// static const auto limiterSpecifier = \"pyodide-internal:limiter\"_url;\n// static const auto tarReaderSpecifier = \"pyodide-internal:packages_tar_reader\"_url;\n// }  // namespace\n\nkj::Arc<jsg::modules::ModuleRegistry> WorkerdApi::newWorkerdModuleRegistry(\n    const jsg::ResolveObserver& observer,\n    kj::Maybe<const Worker::Script::ModulesSource&> maybeSource,\n    const CompatibilityFlags::Reader& featureFlags,\n    const PythonConfig& pythonConfig,\n    const jsg::Url& bundleBase,\n    capnp::List<config::Extension>::Reader extensions,\n    kj::Maybe<kj::String> maybeFallbackService,\n    kj::Maybe<kj::Own<api::pyodide::ArtifactBundler_State>> artifacts) {\n\n  return newWorkerModuleRegistry<JsgWorkerdIsolate_TypeWrapper>(observer, maybeSource, featureFlags,\n      bundleBase,\n      [&](jsg::modules::ModuleRegistry::Builder& builder, IsPythonWorker isPythonWorker) {\n    // TODO(later): The new module registry should eventually support python workers\n    // as well, but for now we forbid it. There are a number of nuances to python workers\n    // and modules that need to be worked out.\n    KJ_REQUIRE(!isPythonWorker, \"Python workers are not supported with the new module registry\");\n    // if (isPythonWorker) {\n    //   using namespace api::pyodide;\n\n    //   // It's not possible to have a python worker without a source bundle.\n    //   auto& source = KJ_ASSERT_NONNULL(maybeSource);\n\n    //   // To support python workers we create two modules bundles, one BUILTIN\n    //   // and the other BUILTIN_ONLY. The BUILTIN bundle contains support modules\n    //   // that need to be importable by the python worker bootstrap module (which\n    //   // is added to the BUNDLE modules). The BUILTIN_ONLY bundle contains support\n    //   // modules that are used by the BUILTIN modules and are not intended to be\n    //   // accessible from the worker itself.\n\n    //   // Inject metadata that the entrypoint module will read.\n    //   auto pythonRelease = KJ_ASSERT_NONNULL(getPythonSnapshotRelease(featureFlags));\n    //   auto version = getPythonBundleName(pythonRelease);\n    //   auto bundle = retrievePyodideBundle(pythonConfig, version);\n\n    //   // We end up adding modules from the bundle twice, once to get BUILTIN modules\n    //   // and again to get the BUILTIN_ONLY modules. These end up in two different\n    //   // module bundles.\n    //   jsg::modules::ModuleBundle::BuiltinBuilder pyodideSdkBuilder;\n\n    //   // There are two bundles that are relevant here, PYODIDE_BUNDLE, which is\n    //   // fixed and contains compiled-in modules, and the bundle that is fetched\n    //   // that contains the more dynamic implementation details. We have to process\n    //   // both.\n    //   jsg::modules::ModuleBundle::getBuiltInBundleFromCapnp(pyodideSdkBuilder, PYODIDE_BUNDLE);\n    //   jsg::modules::ModuleBundle::getBuiltInBundleFromCapnp(pyodideSdkBuilder, bundle);\n    //   builder.add(pyodideSdkBuilder.finish());\n\n    //   jsg::modules::ModuleBundle::BuiltinBuilder pyodideBundleBuilder(\n    //       jsg::modules::ModuleBundle::BuiltinBuilder::Type::BUILTIN_ONLY);\n\n    //   jsg::modules::ModuleBundle::getBuiltInBundleFromCapnp(pyodideBundleBuilder, PYODIDE_BUNDLE);\n    //   jsg::modules::ModuleBundle::getBuiltInBundleFromCapnp(pyodideBundleBuilder, bundle);\n\n    //   pyodideBundleBuilder.addSynthetic(bootrapSpecifier,\n    //       jsg::modules::Module::newJsgObjectModuleHandler<api::pyodide::SetupEmscripten,\n    //           JsgWorkerdIsolate_TypeWrapper>(\n    //           [bundle = capnp::clone(bundle)](\n    //               jsg::Lock& js) mutable -> jsg::Ref<api::pyodide::SetupEmscripten> {\n    //     auto emscriptenRuntime = api::pyodide::EmscriptenRuntime::initialize(js, true, *bundle);\n    //     return js.alloc<api::pyodide::SetupEmscripten>(kj::mv(emscriptenRuntime));\n    //   }));\n\n    //   pyodideBundleBuilder.addEsm(tarReaderSpecifier, PYTHON_TAR_READER);\n\n    //   api::pyodide::CreateBaselineSnapshot createBaselineSnapshot(\n    //       pythonConfig.createBaselineSnapshot);\n    //   api::pyodide::SnapshotToDisk snapshotToDisk(\n    //       pythonConfig.createSnapshot || createBaselineSnapshot);\n    //   auto maybeSnapshot = tryGetMetadataSnapshot(pythonConfig, snapshotToDisk);\n    //   auto state = workerd::modules::python::createPyodideMetadataState(source,\n    //       api::pyodide::IsWorkerd::YES, api::pyodide::IsTracing::NO, snapshotToDisk,\n    //       createBaselineSnapshot, pythonRelease, kj::mv(maybeSnapshot), featureFlags);\n\n    //   pyodideBundleBuilder.addSynthetic(metadataSpecifier,\n    //       jsg::modules::Module::newJsgObjectModuleHandler<api::pyodide::PyodideMetadataReader,\n    //           JsgWorkerdIsolate_TypeWrapper>(\n    //           [state = kj::mv(state)](\n    //               jsg::Lock& js) mutable -> jsg::Ref<api::pyodide::PyodideMetadataReader> {\n    //     // The ModuleRegistry may be shared across multiple isolates and workers.\n    //     // We need to clone the PyodideMetadataReader::State for each instance\n    //     // that is evaluated. Typically this is only once per python worker\n    //     // but could be more in the future.\n    //     return js.alloc<PyodideMetadataReader>(state->clone());\n    //   }));\n    //   // Inject artifact bundler.\n    //   pyodideBundleBuilder.addSynthetic(artifactsSpecifier,\n    //       jsg::modules::Module::newJsgObjectModuleHandler<ArtifactBundler,\n    //           JsgWorkerdIsolate_TypeWrapper>(\n    //           [](jsg::Lock& js) mutable -> jsg::Ref<ArtifactBundler> {\n    //     return js.alloc<ArtifactBundler>(ArtifactBundler::makeDisabledBundler());\n    //   }));\n    //   // Inject jaeger internal tracer in a disabled state (we don't have a use for it in workerd)\n    //   pyodideBundleBuilder.addSynthetic(internalJaegerSpecifier,\n    //       jsg::modules::Module::newJsgObjectModuleHandler<DisabledInternalJaeger,\n    //           JsgWorkerdIsolate_TypeWrapper>(\n    //           [](jsg::Lock& js) mutable -> jsg::Ref<DisabledInternalJaeger> {\n    //     return DisabledInternalJaeger::create(js);\n    //   }));\n    //   // Inject disk cache module\n    //   pyodideBundleBuilder.addSynthetic(diskCacheSpecifier,\n    //       jsg::modules::Module::newJsgObjectModuleHandler<DiskCache, JsgWorkerdIsolate_TypeWrapper>(\n    //           [&packageDiskCacheRoot = pythonConfig.packageDiskCacheRoot](jsg::Lock& js) mutable\n    //           -> jsg::Ref<DiskCache> { return js.alloc<DiskCache>(packageDiskCacheRoot); }));\n    //   // Inject a (disabled) SimplePythonLimiter\n    //   pyodideBundleBuilder.addSynthetic(limiterSpecifier,\n    //       jsg::modules::Module::newJsgObjectModuleHandler<SimplePythonLimiter,\n    //           JsgWorkerdIsolate_TypeWrapper>(\n    //           [](jsg::Lock& js) mutable -> jsg::Ref<SimplePythonLimiter> {\n    //     return SimplePythonLimiter::makeDisabled(js);\n    //   }));\n\n    //   builder.add(pyodideBundleBuilder.finish());\n    // }\n\n    // Handle extensions (extensions are a workerd-specific concept)\n    jsg::modules::ModuleBundle::BuiltinBuilder publicExtensionsBuilder(\n        jsg::modules::ModuleBundle::BuiltinBuilder::Type::BUILTIN);\n    jsg::modules::ModuleBundle::BuiltinBuilder privateExtensionsBuilder(\n        jsg::modules::ModuleBundle::BuiltinBuilder::Type::BUILTIN_ONLY);\n\n    for (auto extension: extensions) {\n      for (auto module: extension.getModules()) {\n        KJ_IF_SOME(url, jsg::Url::tryParse(module.getName())) {\n          if (module.getInternal()) {\n            privateExtensionsBuilder.addEsm(url, module.getEsModule().asArray());\n          } else {\n            publicExtensionsBuilder.addEsm(url, module.getEsModule().asArray());\n          }\n        } else {\n          KJ_LOG(WARNING, \"Ignoring extension module with invalid name\", module.getName());\n        }\n      }\n    }\n\n    builder.add(publicExtensionsBuilder.finish());\n    builder.add(privateExtensionsBuilder.finish());\n\n    // If we have a fallback service configured, add the fallback bundle.\n    // The fallback bundle is used only in workerd local development mode.\n    // If a module is not found in the static bundles, a registry that is\n    // configured to use the fallback will send a request to the fallback\n    // service to try resolving.\n    KJ_IF_SOME(fallbackService, maybeFallbackService) {\n      auto fallbackClient =\n          kj::heap<workerd::fallback::FallbackServiceClient>(kj::str(fallbackService));\n      builder.add(jsg::modules::ModuleBundle::newFallbackBundle(\n          [client = kj::mv(fallbackClient), featureFlags](\n              const jsg::modules::ResolveContext& context) mutable\n          -> kj::Maybe<kj::OneOf<kj::String, kj::Own<jsg::modules::Module>>> {\n        auto normalizedSpecifier = kj::str(context.normalizedSpecifier.getHref());\n        auto referrer = kj::str(context.referrerNormalizedSpecifier.getHref());\n        KJ_IF_SOME(resolved,\n            client->tryResolve(workerd::fallback::Version::V2,\n                workerd::fallback::ImportType::IMPORT, normalizedSpecifier,\n                context.rawSpecifier.orDefault(nullptr), referrer, context.attributes)) {\n          KJ_SWITCH_ONEOF(resolved) {\n            KJ_CASE_ONEOF(str, kj::String) {\n              // The fallback service returned an alternative specifier.\n              // The resolution must start over with the new specifier.\n              return kj::Maybe<kj::OneOf<kj::String, kj::Own<jsg::modules::Module>>>(kj::mv(str));\n            }\n            KJ_CASE_ONEOF(def, kj::Own<server::config::Worker::Module::Reader>) {\n              // The fallback service returned a module definition.\n              // We need to convert that into a Module instance.\n              auto mod = readModuleConf(*def, featureFlags, kj::none);\n              KJ_IF_SOME(id, jsg::Url::tryParse(mod.name)) {\n                // Note that unlike the regular case, the module content returned\n                // by the fallback service is not guaranteed to be memory-resident.\n                // We need to copy the content into a heap-allocated arrays and\n                // make sure those stay alive while the Module is alive.\n                KJ_SWITCH_ONEOF(mod.content) {\n                  KJ_CASE_ONEOF(content, Worker::Script::EsModule) {\n                    return kj::Maybe<kj::OneOf<kj::String, kj::Own<jsg::modules::Module>>>(\n                        jsg::modules::Module::newEsm(kj::mv(id),\n                            jsg::modules::Module::Type::FALLBACK,\n                            kj::heapArray<const char>(content.body)));\n                  }\n                  KJ_CASE_ONEOF(content, Worker::Script::TextModule) {\n                    auto ownedData = kj::str(content.body);\n                    auto ptr = ownedData.asPtr();\n                    return kj::Maybe<kj::OneOf<kj::String, kj::Own<jsg::modules::Module>>>(\n                        jsg::modules::Module::newSynthetic(kj::mv(id),\n                            jsg::modules::Module::Type::FALLBACK,\n                            jsg::modules::Module::newTextModuleHandler(ptr))\n                            .attach(kj::mv(ownedData)));\n                  }\n                  KJ_CASE_ONEOF(content, Worker::Script::DataModule) {\n                    auto ownedData = kj::heapArray<uint8_t>(content.body);\n                    auto ptr = ownedData.asPtr();\n                    return kj::Maybe<kj::OneOf<kj::String, kj::Own<jsg::modules::Module>>>(\n                        jsg::modules::Module::newSynthetic(kj::mv(id),\n                            jsg::modules::Module::Type::FALLBACK,\n                            jsg::modules::Module::newDataModuleHandler(ptr))\n                            .attach(kj::mv(ownedData)));\n                  }\n                  KJ_CASE_ONEOF(content, Worker::Script::WasmModule) {\n                    auto ownedData = kj::heapArray<uint8_t>(content.body);\n                    auto ptr = ownedData.asPtr();\n                    return kj::Maybe<kj::OneOf<kj::String, kj::Own<jsg::modules::Module>>>(\n                        jsg::modules::Module::newSynthetic(kj::mv(id),\n                            jsg::modules::Module::Type::FALLBACK,\n                            jsg::modules::Module::newWasmModuleHandler(ptr))\n                            .attach(kj::mv(ownedData)));\n                  }\n                  KJ_CASE_ONEOF(content, Worker::Script::JsonModule) {\n                    auto ownedData = kj::heapArray<const char>(content.body);\n                    auto ptr = ownedData.asPtr();\n                    return kj::Maybe<kj::OneOf<kj::String, kj::Own<jsg::modules::Module>>>(\n                        jsg::modules::Module::newSynthetic(kj::mv(id),\n                            jsg::modules::Module::Type::FALLBACK,\n                            jsg::modules::Module::newJsonModuleHandler(ptr))\n                            .attach(kj::mv(ownedData)));\n                  }\n                  KJ_CASE_ONEOF(content, Worker::Script::CommonJsModule) {\n                    auto ownedData = kj::str(content.body);\n                    auto ptr = ownedData.asPtr();\n                    kj::ArrayPtr<const kj::StringPtr> named;\n                    KJ_IF_SOME(n, content.namedExports) {\n                      named = n;\n                    }\n                    return kj::Maybe<kj::OneOf<kj::String, kj::Own<jsg::modules::Module>>>(\n                        jsg::modules::Module::newSynthetic(kj::mv(id),\n                            jsg::modules::Module::Type::FALLBACK,\n                            jsg::modules::Module::newCjsStyleModuleHandler<\n                                api::CommonJsModuleContext, JsgWorkerdIsolate_TypeWrapper>(\n                                ptr, mod.name),\n              KJ_MAP(name, named) {\n                      return kj::str(name);\n                    }).attach(kj::mv(ownedData)));\n                  }\n                  KJ_CASE_ONEOF(content, Worker::Script::PythonModule) {\n                    // Python modules are not supported.in fallback\n                    KJ_LOG(WARNING, \"Fallback service returned a Python module\");\n                    return kj::none;\n                  }\n                  KJ_CASE_ONEOF(content, Worker::Script::PythonRequirement) {\n                    // Python requirement modules are not supported.in fallback\n                    KJ_LOG(WARNING, \"Fallback service returned a Python requirement\");\n                    return kj::none;\n                  }\n                  KJ_CASE_ONEOF(content, Worker::Script::CapnpModule) {\n                    // Capnp modules are not supported.in fallback\n                    KJ_LOG(WARNING, \"Fallback service returned a Capnp module\");\n                    return kj::none;\n                  }\n                }\n                KJ_UNREACHABLE;\n              }\n              KJ_LOG(WARNING, \"Fallback service returned an invalid id\");\n              return kj::none;\n            }\n          }\n        }\n        return kj::none;\n      }));\n    }\n  }, jsg::modules::ModuleRegistry::Builder::Options::ALLOW_FALLBACK);\n}\n\nkj::Own<rpc::ActorStorage::Stage::Server> newEmptyReadOnlyActorStorage() {\n  return kj::heap<EmptyReadOnlyActorStorageImpl>();\n}\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/workerd-api.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"workerd/api/pyodide/pyodide.h\"\n\n#include <workerd/io/worker-fs.h>\n#include <workerd/io/worker.h>\n#include <workerd/server/workerd.capnp.h>\n\nnamespace workerd {\nnamespace api {\nnamespace pyodide {\nstruct PythonConfig;\n}\n}  // namespace api\n}  // namespace workerd\nnamespace workerd {\nnamespace jsg {\nclass V8System;\nnamespace modules {\nclass ModuleRegistry;\n}\n}  // namespace jsg\n}  // namespace workerd\n\nnamespace workerd::api {\nclass MemoryCacheProvider;\n}\n\nnamespace workerd::server {\n\nusing api::pyodide::PythonConfig;\n\n// A Worker::Api implementation with support for all the APIs supported by the OSS runtime.\nclass WorkerdApi final: public Worker::Api {\n public:\n  WorkerdApi(jsg::V8System& v8System,\n      CompatibilityFlags::Reader features,\n      capnp::List<config::Extension>::Reader extensions,\n      v8::Isolate::CreateParams createParams,\n      v8::IsolateGroup group,\n      kj::Own<JsgIsolateObserver> observer,\n      api::MemoryCacheProvider& memoryCacheProvider,\n      const PythonConfig& pythonConfig);\n  ~WorkerdApi() noexcept(false);\n\n  static const WorkerdApi& from(const Worker::Api&);\n\n  kj::Own<jsg::Lock> lock(jsg::V8StackScope& stackScope) const override;\n  CompatibilityFlags::Reader getFeatureFlags() const override;\n  jsg::JsContext<api::ServiceWorkerGlobalScope> newContext(\n      jsg::Lock& lock, Worker::Api::NewContextOptions options = {}) const override;\n  jsg::Dict<NamedExport> unwrapExports(\n      jsg::Lock& lock, v8::Local<v8::Value> moduleNamespace) const override;\n  NamedExport unwrapExport(jsg::Lock& lock, v8::Local<v8::Value> exportVal) const override;\n  EntrypointClasses getEntrypointClasses(jsg::Lock& lock) const override;\n  const jsg::TypeHandler<ErrorInterface>& getErrorInterfaceTypeHandler(\n      jsg::Lock& lock) const override;\n  const jsg::TypeHandler<api::QueueExportedHandler>& getQueueTypeHandler(\n      jsg::Lock& lock) const override;\n  jsg::JsObject wrapExecutionContext(\n      jsg::Lock& lock, jsg::Ref<api::ExecutionContext> ref) const override;\n  const jsg::IsolateObserver& getObserver() const override;\n  void setIsolateObserver(IsolateObserver&) override;\n\n  static Worker::Script::Source extractSource(kj::StringPtr name,\n      config::Worker::Reader conf,\n      CompatibilityFlags::Reader featureFlags,\n      Worker::ValidationErrorReporter& errorReporter);\n\n  void compileModules(jsg::Lock& lock,\n      const Worker::Script::ModulesSource& source,\n      const Worker::Isolate& isolate,\n      kj::Maybe<kj::Own<api::pyodide::ArtifactBundler_State>> artifacts,\n      SpanParent parentSpan) const override;\n\n  kj::Array<Worker::Script::CompiledGlobal> compileServiceWorkerGlobals(jsg::Lock& lock,\n      const Worker::Script::ScriptSource& source,\n      const Worker::Isolate& isolate) const override;\n\n  // A pipeline-level binding.\n  struct Global {\n    // TODO(cleanup): Get rid of this and just load from config.Worker.bindings capnp structure\n    //   directly.\n\n    struct Json {\n      kj::String text;\n\n      Json clone() const {\n        return Json{.text = kj::str(text)};\n      }\n    };\n    struct Fetcher {\n      uint channel;\n      bool requiresHost;\n      bool isInHouse;\n\n      Fetcher clone() const {\n        return *this;\n      }\n    };\n    struct LoopbackServiceStub {\n      uint channel;\n\n      LoopbackServiceStub clone() const {\n        return *this;\n      }\n    };\n    struct KvNamespace {\n      uint subrequestChannel;\n      kj::String bindingName;\n\n      KvNamespace clone() const {\n        return KvNamespace{\n          .subrequestChannel = subrequestChannel, .bindingName = kj::str(bindingName)};\n      }\n    };\n    struct R2Bucket {\n      uint subrequestChannel;\n      kj::String bucket;\n      kj::String bindingName;\n\n      R2Bucket clone() const {\n        return R2Bucket{\n          .subrequestChannel = subrequestChannel,\n          .bucket = kj::str(bucket),\n          .bindingName = kj::str(bindingName),\n        };\n      }\n    };\n    struct R2Admin {\n      uint subrequestChannel;\n\n      R2Admin clone() const {\n        return *this;\n      }\n    };\n    struct QueueBinding {\n      uint subrequestChannel;\n\n      QueueBinding clone() const {\n        return *this;\n      }\n    };\n    struct CryptoKey {\n      kj::String format;\n      kj::OneOf<kj::Array<byte>, Json> keyData;\n      Json algorithm;\n      bool extractable;\n      kj::Array<kj::String> usages;\n\n      CryptoKey clone() const {\n        decltype(keyData) clonedKeyData;\n        KJ_SWITCH_ONEOF(keyData) {\n          KJ_CASE_ONEOF(bytes, kj::Array<byte>) {\n            clonedKeyData = kj::heapArray(bytes.asPtr());\n          }\n          KJ_CASE_ONEOF(json, Json) {\n            clonedKeyData = json.clone();\n          }\n        }\n        return CryptoKey{\n          .format = kj::str(format),\n          .keyData = kj::mv(clonedKeyData),\n          .algorithm = algorithm.clone(),\n          .extractable = extractable,\n          .usages = KJ_MAP(s, usages) { return kj::str(s); },\n        };\n      }\n    };\n\n    struct MemoryCache {\n      kj::Maybe<kj::String> cacheId = kj::none;\n      uint32_t maxKeys;\n      uint32_t maxValueSize;\n      uint64_t maxTotalValueSize;\n\n      MemoryCache clone() const {\n        return MemoryCache{\n          .cacheId = cacheId.map([](auto& id) { return kj::str(id); }),\n          .maxKeys = maxKeys,\n          .maxValueSize = maxValueSize,\n          .maxTotalValueSize = maxTotalValueSize,\n        };\n      }\n    };\n\n    struct EphemeralActorNamespace {\n      uint actorChannel;\n\n      EphemeralActorNamespace clone() const {\n        return *this;\n      }\n    };\n    struct LoopbackEphemeralActorNamespace {\n      uint actorChannel;\n      uint classChannel;\n\n      LoopbackEphemeralActorNamespace clone() const {\n        return *this;\n      }\n    };\n    struct DurableActorNamespace {\n      uint actorChannel;\n      kj::StringPtr uniqueKey;\n\n      DurableActorNamespace clone() const {\n        return *this;\n      }\n    };\n    struct LoopbackDurableActorNamespace {\n      uint actorChannel;\n      kj::StringPtr uniqueKey;\n      uint classChannel;\n\n      LoopbackDurableActorNamespace clone() const {\n        return *this;\n      }\n    };\n    struct Wrapped {\n      // data carrier for configured WrappedBinding\n      kj::String moduleName;\n      kj::String entrypoint;\n      kj::Array<Global> innerBindings;\n\n      Wrapped clone() const {\n        return Wrapped{.moduleName = kj::str(moduleName),\n          .entrypoint = kj::str(entrypoint),\n          .innerBindings = KJ_MAP(b, innerBindings) { return b.clone(); }};\n      }\n    };\n    struct AnalyticsEngine {\n      uint subrequestChannel;\n      kj::String dataset;\n      int64_t version;\n      AnalyticsEngine clone() const {\n        return AnalyticsEngine{\n          .subrequestChannel = subrequestChannel, .dataset = kj::str(dataset), .version = version};\n      }\n    };\n    struct Hyperdrive {\n      uint subrequestChannel;\n      kj::String database;\n      kj::String user;\n      kj::String password;\n      kj::String scheme;\n\n      Hyperdrive clone() const {\n        return Hyperdrive{\n          .subrequestChannel = subrequestChannel,\n          .database = kj::str(database),\n          .user = kj::str(user),\n          .password = kj::str(password),\n          .scheme = kj::str(scheme),\n        };\n      }\n    };\n    struct UnsafeEval {};\n\n    struct ActorClass {\n      uint channel;\n\n      ActorClass clone() const {\n        return *this;\n      }\n    };\n\n    struct LoopbackActorClass {\n      uint channel;\n\n      LoopbackActorClass clone() const {\n        return *this;\n      }\n    };\n\n    struct WorkerLoader {\n      uint channel;\n\n      WorkerLoader clone() const {\n        return *this;\n      }\n    };\n\n    struct WorkerdDebugPort {\n      WorkerdDebugPort clone() const {\n        return *this;\n      }\n    };\n\n    kj::String name;\n    kj::OneOf<Json,\n        Fetcher,\n        LoopbackServiceStub,\n        KvNamespace,\n        R2Bucket,\n        R2Admin,\n        CryptoKey,\n        EphemeralActorNamespace,\n        LoopbackEphemeralActorNamespace,\n        DurableActorNamespace,\n        LoopbackDurableActorNamespace,\n        QueueBinding,\n        kj::String,\n        kj::Array<byte>,\n        Wrapped,\n        AnalyticsEngine,\n        Hyperdrive,\n        UnsafeEval,\n        MemoryCache,\n        ActorClass,\n        LoopbackActorClass,\n        WorkerLoader,\n        WorkerdDebugPort>\n        value;\n\n    Global clone() const;\n  };\n\n  void compileGlobals(jsg::Lock& lock,\n      kj::ArrayPtr<const Global> globals,\n      v8::Local<v8::Object> target,\n      uint32_t ownerId) const;\n\n  // Part of the original module registry API.\n  static kj::Maybe<jsg::ModuleRegistry::ModuleInfo> tryCompileModule(jsg::Lock& js,\n      config::Worker::Module::Reader conf,\n      const jsg::CompilationObserver& observer,\n      CompatibilityFlags::Reader featureFlags);\n\n  // Convert a module definition from workerd config to a Worker::Script::Module (which may contain\n  // string pointers into the config).\n  static Worker::Script::Module readModuleConf(config::Worker::Module::Reader conf,\n      CompatibilityFlags::Reader featureFlags,\n      kj::Maybe<Worker::ValidationErrorReporter&> errorReporter = kj::none);\n\n  using ModuleFallbackCallback = Worker::Api::ModuleFallbackCallback;\n  void setModuleFallbackCallback(kj::Function<ModuleFallbackCallback>&& callback) const override;\n\n  // Create the ModuleRegistry instance for the worker.\n  static kj::Arc<jsg::modules::ModuleRegistry> newWorkerdModuleRegistry(\n      const jsg::ResolveObserver& resolveObserver,\n      kj::Maybe<const Worker::Script::ModulesSource&> source,\n      const CompatibilityFlags::Reader& featureFlags,\n      const PythonConfig& pythonConfig,\n      const jsg::Url& bundleBase,\n      capnp::List<config::Extension>::Reader extensions,\n      kj::Maybe<kj::String> fallbackService = kj::none,\n      kj::Maybe<kj::Own<api::pyodide::ArtifactBundler_State>> artifacts = kj::none);\n\n private:\n  struct Impl;\n  kj::Own<Impl> impl;\n};\n\nkj::Array<kj::String> getPythonRequirements(const Worker::Script::ModulesSource& source);\n\n// An ActorStorage implementation which will always respond to reads as if the state is empty,\n// and will fail any writes. Defined here to be used by test-fixture and server.\nkj::Own<rpc::ActorStorage::Stage::Server> newEmptyReadOnlyActorStorage();\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/workerd-debug-port-client.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"workerd-debug-port-client.h\"\n\n#include <workerd/api/http.h>\n#include <workerd/io/frankenvalue.h>\n#include <workerd/io/io-context.h>\n#include <workerd/io/worker-interface.h>\n\n#include <kj/memory.h>\n\nnamespace workerd::server {\n\nnamespace {\n// A SubrequestChannel that makes requests to a remote worker via the debug port.\n//\n// The connection ref is attached to WorkerInterfaces returned by startRequest().\n// For HTTP fetch, the response body/WebSocket gets this attached (deferred proxying),\n// ensuring the connection stays alive as long as the response is in use.\nclass WorkerdBootstrapSubrequestChannel final: public IoChannelFactory::SubrequestChannel {\n public:\n  WorkerdBootstrapSubrequestChannel(rpc::WorkerdBootstrap::Client bootstrap,\n      capnp::HttpOverCapnpFactory& httpOverCapnpFactory,\n      capnp::ByteStreamFactory& byteStreamFactory,\n      kj::Own<DebugPortConnectionState> connectionState)\n      : bootstrap(kj::mv(bootstrap)),\n        httpOverCapnpFactory(httpOverCapnpFactory),\n        byteStreamFactory(byteStreamFactory),\n        connectionState(kj::mv(connectionState)) {}\n\n  kj::Own<WorkerInterface> startRequest(IoChannelFactory::SubrequestMetadata metadata) override {\n    // Pass cfBlobJson as an RPC parameter on startEvent so the server can include it\n    // in SubrequestMetadata when creating the WorkerInterface.\n    auto req = bootstrap.startEventRequest();\n    KJ_IF_SOME(cf, metadata.cfBlobJson) {\n      req.setCfBlobJson(cf);\n    }\n    auto dispatcher = req.send().getDispatcher();\n    // Attach connection ref for deferred proxying - the HTTP response body/WebSocket\n    // will get this WorkerInterface attached, keeping the connection alive.\n    return kj::heap<RpcWorkerInterface>(httpOverCapnpFactory, byteStreamFactory, kj::mv(dispatcher))\n        .attach(connectionState->addRef());\n  }\n\n  void requireAllowsTransfer() override {\n    JSG_FAIL_REQUIRE(Error, \"WorkerdDebugPort bindings cannot be transferred to other workers\");\n  }\n\n private:\n  rpc::WorkerdBootstrap::Client bootstrap;\n  capnp::HttpOverCapnpFactory& httpOverCapnpFactory;\n  capnp::ByteStreamFactory& byteStreamFactory;\n  kj::Own<DebugPortConnectionState> connectionState;\n};\n\njsg::Ref<api::Fetcher> wrapBootstrapAsFetcher(jsg::Lock& js,\n    IoContext& context,\n    rpc::WorkerdBootstrap::Client bootstrap,\n    kj::Own<DebugPortConnectionState> connectionState) {\n  kj::Own<IoChannelFactory::SubrequestChannel> subrequestChannel =\n      kj::refcounted<WorkerdBootstrapSubrequestChannel>(kj::mv(bootstrap),\n          context.getHttpOverCapnpFactory(), context.getByteStreamFactory(),\n          kj::mv(connectionState));\n  return js.alloc<api::Fetcher>(\n      context.addObject(kj::mv(subrequestChannel)), api::Fetcher::RequiresHostAndProtocol::NO);\n}\n}  // namespace\n\njsg::Ref<api::Fetcher> WorkerdDebugPortClient::getEntrypoint(jsg::Lock& js,\n    kj::String service,\n    jsg::Optional<kj::String> entrypoint,\n    jsg::Optional<jsg::JsRef<jsg::JsObject>> props) {\n  auto& context = IoContext::current();\n\n  auto req = state->debugPort.getEntrypointRequest();\n  req.setService(service);\n  KJ_IF_SOME(e, entrypoint) {\n    req.setEntrypoint(e);\n  }\n  KJ_IF_SOME(p, props) {\n    Frankenvalue::fromJs(js, p.getHandle(js)).toCapnp(req.initProps());\n  }\n\n  // Use Cap'n Proto pipelining: extract the entrypoint capability from the in-flight\n  // RPC response without waiting for it to resolve. The capability is a lazy proxy that\n  // only triggers the actual network round-trip when first used (e.g. fetch()).\n  auto bootstrap = req.send().getEntrypoint();\n  return wrapBootstrapAsFetcher(js, context, kj::mv(bootstrap), state->addRef());\n}\n\njsg::Ref<api::Fetcher> WorkerdDebugPortClient::getActor(\n    jsg::Lock& js, kj::String service, kj::String entrypoint, kj::String actorId) {\n  auto& context = IoContext::current();\n\n  auto req = state->debugPort.getActorRequest();\n  req.setService(service);\n  req.setEntrypoint(entrypoint);\n  req.setActorId(actorId);\n\n  // Use Cap'n Proto pipelining: extract the actor capability from the in-flight\n  // RPC response without waiting for it to resolve.\n  auto bootstrap = req.send().getActor();\n  return wrapBootstrapAsFetcher(js, context, kj::mv(bootstrap), state->addRef());\n}\n\njsg::Ref<WorkerdDebugPortClient> WorkerdDebugPortConnector::connect(\n    jsg::Lock& js, kj::String address) {\n  auto& context = IoContext::current();\n  auto connectPromise =\n      context.getIoChannelFactory().getWorkerdDebugPortNetwork().parseAddress(address).then(\n          [](kj::Own<kj::NetworkAddress> addr) { return addr->connect(); });\n\n  // Use kj::newPromisedStream() to get an AsyncIoStream immediately. The actual TCP\n  // connection is deferred — Cap'n Proto pipelining queues all RPC calls until connected.\n  auto stream = kj::newPromisedStream(kj::mv(connectPromise));\n  auto rpcClient = kj::heap<capnp::TwoPartyClient>(*stream);\n  auto debugPort = rpcClient->bootstrap().castAs<rpc::WorkerdDebugPort>();\n  auto state = kj::refcounted<DebugPortConnectionState>(\n      kj::mv(stream), kj::mv(rpcClient), kj::mv(debugPort));\n  return js.alloc<WorkerdDebugPortClient>(context.addObject(kj::mv(state)));\n}\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/workerd-debug-port-client.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/io/io-channels.h>\n#include <workerd/io/io-own.h>\n#include <workerd/io/worker-interface.capnp.h>\n#include <workerd/jsg/jsg.h>\n\n#include <capnp/rpc-twoparty.h>\n\nnamespace workerd::api {\nclass Fetcher;\n}  // namespace workerd::api\n\nnamespace workerd::server {\n\n// Holds the I/O state for a debug port connection: the TCP stream, capnp RPC client,\n// and debug port capability. Refcounted to support deferred proxying - response bodies\n// and WebSockets are proxied through the capnp connection, so it must stay alive until\n// they're fully consumed. See WorkerdBootstrapSubrequestChannel::startRequest().\nclass DebugPortConnectionState: public kj::Refcounted {\n public:\n  DebugPortConnectionState(kj::Own<kj::AsyncIoStream> connection,\n      kj::Own<capnp::TwoPartyClient> rpcClient,\n      rpc::WorkerdDebugPort::Client debugPort)\n      : connection(kj::mv(connection)),\n        rpcClient(kj::mv(rpcClient)),\n        debugPort(kj::mv(debugPort)) {}\n\n  kj::Own<DebugPortConnectionState> addRef() {\n    return kj::addRef(*this);\n  }\n\n  kj::Own<kj::AsyncIoStream> connection;\n  kj::Own<capnp::TwoPartyClient> rpcClient;\n  rpc::WorkerdDebugPort::Client debugPort;\n};\n\n// JS interface for a connected workerd debug port.\n// This class is returned from WorkerdDebugPortConnector::connect() and provides\n// access to a remote workerd instance's WorkerdDebugPort RPC interface.\nclass WorkerdDebugPortClient: public jsg::Object {\n public:\n  // Create a WorkerdDebugPortClient with an established connection.\n  // Takes an IoOwn reference to the connection state.\n  explicit WorkerdDebugPortClient(IoOwn<DebugPortConnectionState> state): state(kj::mv(state)) {}\n\n  // Get access to a stateless entrypoint on the remote workerd instance.\n  // Uses Cap'n Proto pipelining to return a Fetcher synchronously — the actual\n  // RPC resolution is deferred until the Fetcher is first used (e.g. fetch()).\n  //\n  // @param service - The service name in the remote workerd process\n  // @param entrypoint - The entrypoint name to access (if omitted, uses the default handler)\n  // @param props - Optional props to pass to the entrypoint\n  // @returns A Fetcher that lazily resolves on first use\n  jsg::Ref<api::Fetcher> getEntrypoint(jsg::Lock& js,\n      kj::String service,\n      jsg::Optional<kj::String> entrypoint,\n      jsg::Optional<jsg::JsRef<jsg::JsObject>> props);\n\n  // Get access to an actor (Durable Object) stub on the remote workerd instance.\n  // Uses Cap'n Proto pipelining to return a Fetcher synchronously — the actual\n  // RPC resolution is deferred until the Fetcher is first used (e.g. fetch()).\n  //\n  // @param service - The service name in the remote workerd process\n  // @param entrypoint - The entrypoint/class name to access\n  // @param actorId - The actor ID (hex string for DOs, plain string for ephemeral)\n  // @returns A Fetcher that lazily resolves on first use\n  jsg::Ref<api::Fetcher> getActor(\n      jsg::Lock& js, kj::String service, kj::String entrypoint, kj::String actorId);\n\n  JSG_RESOURCE_TYPE(WorkerdDebugPortClient) {\n    JSG_METHOD(getEntrypoint);\n    JSG_METHOD(getActor);\n\n    JSG_TS_ROOT();\n    JSG_TS_OVERRIDE({\n      getEntrypoint<T extends Rpc.WorkerEntrypointBranded | undefined>(\n          service: string, entrypoint?: string, props?: Record<string, unknown>): Fetcher<T>;\n      getActor<T extends Rpc.DurableObjectBranded | undefined>(\n          service: string, entrypoint: string, actorId: string): Fetcher<T>;\n    });\n  }\n\n private:\n  IoOwn<DebugPortConnectionState> state;\n};\n\n// JS interface for the workerdDebugPort binding.\n// This binding provides a connect() method to dynamically connect to any workerd\n// instance's debug port.\nclass WorkerdDebugPortConnector: public jsg::Object {\n public:\n  WorkerdDebugPortConnector() = default;\n\n  // Connect to a remote workerd debug port at the given address.\n  // Returns synchronously using kj::newPromisedStream() to defer the TCP connection.\n  // Cap'n Proto pipelining ensures that all subsequent RPC calls (getEntrypoint, getActor)\n  // are queued until the connection is established.\n  //\n  // @param address - The address of the remote workerd debug port (e.g., \"localhost:1234\")\n  // @returns A WorkerdDebugPortClient that lazily connects on first use\n  jsg::Ref<WorkerdDebugPortClient> connect(jsg::Lock& js, kj::String address);\n\n  JSG_RESOURCE_TYPE(WorkerdDebugPortConnector) {\n    JSG_METHOD(connect);\n  }\n};\n\n#define EW_WORKERD_DEBUG_PORT_CLIENT_ISOLATE_TYPES                                                 \\\n  workerd::server::WorkerdDebugPortClient, workerd::server::WorkerdDebugPortConnector\n\n}  // namespace workerd::server\n"
  },
  {
    "path": "src/workerd/server/workerd.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"server.h\"\n\n#include <workerd/api/unsafe.h>\n#include <workerd/io/compatibility-date.capnp.h>\n#include <workerd/io/compatibility-date.h>\n#include <workerd/io/release-version.embed.h>\n#include <workerd/jsg/setup.h>\n#include <workerd/rust/cxx-integration/lib.rs.h>\n#include <workerd/server/cpp-capnp-schema.embed.h>\n#include <workerd/server/json-logger.h>\n#include <workerd/server/v8-platform-impl.h>\n#include <workerd/server/workerd-capnp-schema.embed.h>\n#include <workerd/server/workerd.capnp.h>\n#include <workerd/util/autogate.h>\n#include <workerd/util/entropy.h>\n\n#include <errno.h>\n#include <fcntl.h>\n#ifdef __linux__\n#include <sys/mman.h>\n#include <sys/stat.h>\n#endif\n\n#include <capnp/dynamic.h>\n#include <capnp/message.h>\n#include <capnp/schema-parser.h>\n#include <capnp/serialize.h>\n#include <kj/async-queue.h>\n#include <kj/encoding.h>\n#include <kj/filesystem.h>\n#include <kj/main.h>\n#include <kj/map.h>\n\n#if _WIN32\n#include <windows.h>\n#include <winsock2.h>\n\n#include <kj/async-win32.h>\n#include <kj/win32-api-version.h>\n#include <kj/windows-sanity.h>\n\n#include <iostream>\n#else\n#include <sys/ioctl.h>\n#include <sys/socket.h>\n#include <sys/syscall.h>\n#include <unistd.h>\n\n#include <kj/async-unix.h>\n#endif\n\n#if __linux__\n#include <sys/inotify.h>\n#elif __APPLE__ || __FreeBSD__ || __OpenBSD__ || __NetBSD__ || __DragonFly__\n#define WORKERD_USE_KQUEUE_FOR_FILE_WATCHER 1\n#include <sys/event.h>\n#include <sys/time.h>\n#include <sys/types.h>\n#endif\n\n#ifdef __GLIBC__\n#include <sys/auxv.h>\n#endif\n\n#ifdef __APPLE__\n#include <crt_externs.h>\n#include <libproc.h>\n#define environ (*_NSGetEnviron())\n#endif\n\n#include <workerd/util/use-perfetto-categories.h>\n\n// since kj installs their global signal handlers\n// and exits with 1 Fuzzilli doesn't realize that an application crashed due to the signo.\n// Therefore, we install a handler before and just raise the signo\n#ifdef WORKERD_FUZZILLI\n\nvoid signalHandler(int signo, siginfo_t* info, void* context) noexcept {\n  // inform reprl - remove debug output for clean testing\n  struct sigaction sa = {};\n  sa.sa_handler = SIG_DFL;\n  sigemptyset(&sa.sa_mask);\n  sa.sa_flags = 0;\n  sigaction(signo, &sa, nullptr);\n  raise(signo);\n}\n\nvoid initSignalHandlers() {\n  struct sigaction action {};\n  action.sa_flags = SA_SIGINFO;\n  action.sa_sigaction = &signalHandler;\n\n  for (auto signo: {SIGBUS, SIGFPE, SIGABRT, SIGILL, SIGTRAP, SIGSEGV}) {\n    KJ_SYSCALL(sigaction(signo, &action, nullptr));\n  }\n}\n#endif\n\nnamespace workerd::server {\nnamespace {\n\nstatic kj::StringPtr getVersionString() {\n  static const kj::String result = kj::str(\"workerd \", RELEASE_VERSION);\n  return result;\n}\n\n// =======================================================================================\n\n// For ASan's leak sanitizer, suppress warnings about leaks with stacks that include \"unknown\n// modules\". This suppression is adopted from the GN build and applies to addresses that LSan can't\n// symbolize or even map to a binary – perhaps JIT or snapshot-generated code in V8's case?\n// TODO(someday): Suppression is needed to get several python tests to pass under LSan. Investigate\n// if this is an actual leak (perhaps a bug in V8 itself since it is suppressed there?) at a later\n// time.\n#if __has_feature(address_sanitizer)\nextern \"C\" __attribute__((no_sanitize(\"address\"))) __attribute__((visibility(\"default\")))\n__attribute__((used)) const char*\n__lsan_default_suppressions() {\n  return \"leak:<unknown module>\\n\";\n}\n#endif\n\n// =======================================================================================\n\nclass EntropySourceImpl: public kj::EntropySource {\n public:\n  void generate(kj::ArrayPtr<kj::byte> buffer) override {\n    getEntropy(buffer);\n  }\n};\n\n// =======================================================================================\n// Some generic CLI helpers so that we can throw exceptions rather than return\n// kj::MainBuilder::Validity. Honestly I do not know how people put up with patterns like\n// Result<T, E>, it seems like such a slog.\n\nclass CliError {\n public:\n  CliError(kj::String description): description(kj::mv(description)) {}\n  kj::String description;\n};\n\ntemplate <typename Func>\nauto cliMethod(Func&& func) {\n  return [func = kj::fwd<Func>(func)](auto&&... params) mutable -> kj::MainBuilder::Validity {\n    try {\n      func(kj::fwd<decltype(params)>(params)...);\n      return true;\n    } catch (CliError& e) {\n      return kj::mv(e.description);\n    }\n  };\n}\n\n// Pass to MainBuilder when a function returning kj::MainBuilder::Validity is needed, implemented\n// by a method of this class.\n#define CLI_METHOD(name) cliMethod(KJ_BIND_METHOD(*this, name))\n\n// Throws an exception that is caught and reported as a usage error.\n#define CLI_ERROR(...) throw CliError(kj::str(__VA_ARGS__))\n\nconstexpr capnp::ReaderOptions CONFIG_READER_OPTIONS = {\n  .traversalLimitInWords = kj::maxValue\n  // Configs can legitimately be very large and are not malicious, so use an effectively-infinite\n  // traversal limit.\n};\n\n// =======================================================================================\n\n#if __linux__\n\n// Class which uses inotify to watch a set of files and alert when they change.\nclass FileWatcher {\n public:\n  FileWatcher(kj::UnixEventPort& port)\n      : inotifyFd(makeInotify()),\n        observer(port, inotifyFd, kj::UnixEventPort::FdObserver::OBSERVE_READ) {}\n\n  bool isSupported() {\n    return true;\n  }\n\n  void watch(kj::PathPtr path, kj::Maybe<const kj::ReadableFile&> file) {\n    // `file` is provided if available. The Linux implementation doesn't use it.\n\n    auto pathStr = path.parent().toNativeString(true);\n\n    int wd = watches.findOrCreate(pathStr, [&]() {\n      int wd;\n      uint32_t mask = IN_DELETE | IN_MODIFY | IN_MOVE | IN_CREATE;\n      KJ_SYSCALL(wd = inotify_add_watch(inotifyFd, pathStr.cStr(), mask));\n      return decltype(watches)::Entry{kj::mv(pathStr), wd};\n    });\n\n    auto& files =\n        filesWatched.findOrCreate(wd, [&]() { return decltype(filesWatched)::Entry{wd, {}}; });\n\n    files.upsert(kj::str(path.basename()[0]), [](auto&&...) {});\n  }\n\n  kj::Promise<void> onChange() {\n    kj::byte buffer[4096]{};\n\n    for (;;) {\n      ssize_t n;\n      KJ_NONBLOCKING_SYSCALL(n = read(inotifyFd, buffer, sizeof(buffer)));\n\n      if (n < 0) {\n        // No more data to read.\n        co_await observer.whenBecomesReadable();\n        continue;\n      }\n\n      kj::byte* ptr = buffer;\n      while (n > 0) {\n        KJ_ASSERT(n >= sizeof(struct inotify_event));\n\n        auto& event = *reinterpret_cast<struct inotify_event*>(ptr);\n        size_t eventSize = sizeof(struct inotify_event) + event.len;\n        KJ_ASSERT(n >= eventSize);\n        KJ_ASSERT(eventSize % sizeof(void*) == 0);\n        ptr += eventSize;\n        n -= eventSize;\n\n        if (event.len > 0 && event.name[0] != '\\0') {\n          auto& watched = KJ_ASSERT_NONNULL(filesWatched.find(event.wd));\n          if (watched.find(kj::StringPtr(event.name)) != kj::none) {\n            // HIT! We saw a change.\n            co_return;\n          }\n        }\n      }\n    }\n  }\n\n private:\n  kj::OwnFd inotifyFd;\n  kj::UnixEventPort::FdObserver observer;\n\n  kj::HashMap<kj::String, int> watches;\n  kj::HashMap<int, kj::HashSet<kj::String>> filesWatched;\n\n  static kj::OwnFd makeInotify() {\n    return KJ_SYSCALL_FD(inotify_init1(IN_NONBLOCK | IN_CLOEXEC));\n  }\n};\n\n#elif WORKERD_USE_KQUEUE_FOR_FILE_WATCHER\n\n// Class which uses inotify to watch a set of files and alert when they change.\n//\n// This version uses kqueue to watch for changes in files. kqueue typically doesn't scale well\n// to watching whole directory trees, since it must keep a file descriptor open for each watched\n// file. However, for our use case, we don't really want to watch a directory tree anyway, we\n// want to watch the specific set of files which were opened while parsing the config. This is\n// not so bad, probably.\n//\n// Apple provides the FSEvents API as an alternative, but it seems way more complicated and I\n// can't tell if it would provide a real advantage. Plus, kqueue works on BSD systems.\nclass FileWatcher {\n public:\n  FileWatcher(kj::UnixEventPort& port)\n      : kqueueFd(makeKqueue()),\n        observer(port, kqueueFd, kj::UnixEventPort::FdObserver::OBSERVE_READ) {}\n\n  bool isSupported() {\n    return true;\n  }\n\n  void watch(kj::PathPtr path, kj::Maybe<const kj::ReadableFile&> file) {\n    KJ_IF_SOME(f, file) {\n      KJ_IF_SOME(fd, f.getFd()) {\n        // We need to duplicate the FD because the original will probably be closed later and\n        // closing the FD unregisters it from kqueue.\n        watchFd(KJ_SYSCALL_FD(dup(fd)));\n        return;\n      }\n    }\n\n    // No existing file, open from disk.\n    watchFd(KJ_SYSCALL_FD(open(path.toNativeString(true).cStr(), O_RDONLY)));\n  }\n\n  kj::Promise<void> onChange() {\n    for (;;) {\n      struct kevent event;\n      struct timespec timeout;\n      memset(&event, 0, sizeof(event));\n      memset(&timeout, 0, sizeof(timeout));\n\n      int n;\n      KJ_SYSCALL(n = kevent(kqueueFd, nullptr, 0, &event, 1, &timeout));\n\n      if (n == 0) {\n        // No events, wait for the kqueue to become readable indicating an event has been\n        // delivered.\n        co_await observer.whenBecomesReadable();\n        continue;\n      } else {\n        // We only pay attention to events that indicate changes in the first place, so there's\n        // no need to examine the event, it definitely means something changed.\n        co_return;\n      }\n    }\n  }\n\n private:\n  kj::OwnFd kqueueFd;\n  kj::UnixEventPort::FdObserver observer;\n  kj::Vector<kj::OwnFd> filesWatched;\n\n  static kj::OwnFd makeKqueue() {\n    auto fd = KJ_SYSCALL_FD(kqueue());\n    KJ_SYSCALL(fcntl(fd, F_SETFD, FD_CLOEXEC));\n    return kj::mv(fd);\n  }\n\n  void watchFd(kj::OwnFd fd) {\n    KJ_SYSCALL(fcntl(fd, F_SETFD, FD_CLOEXEC));\n\n    struct kevent change;\n    memset(&change, 0, sizeof(change));\n    change.ident = fd.get();\n    change.filter = EVFILT_VNODE;\n    change.flags = EV_ADD | EV_CLEAR;\n    change.fflags = NOTE_WRITE | NOTE_EXTEND | NOTE_DELETE | NOTE_RENAME;\n    KJ_SYSCALL(kevent(kqueueFd, &change, 1, nullptr, 0, nullptr));\n    filesWatched.add(kj::mv(fd));\n  }\n};\n\n#elif _WIN32\n\nclass FileWatcher {\n public:\n  FileWatcher(kj::Win32EventPort& port) {}\n\n  bool isSupported() {\n    return false;\n  }\n\n  void watch(kj::PathPtr path, kj::Maybe<const kj::ReadableFile&> file) {}\n\n  kj::Promise<void> onChange() {\n    return kj::NEVER_DONE;\n  }\n\n private:\n};\n\n#else\n\n// Dummy FileWatcher implementation for operating systems that aren't supported yet.\nclass FileWatcher {\n public:\n  FileWatcher(kj::UnixEventPort& port) {}\n\n  bool isSupported() {\n    return false;\n  }\n\n  void watch(kj::PathPtr path, kj::Maybe<const kj::ReadableFile&> file) {}\n\n  kj::Promise<void> onChange() {\n    return kj::NEVER_DONE;\n  }\n\n private:\n};\n\n#endif  // #__linux__, #else\n\n// =======================================================================================\n\nkj::Maybe<kj::Own<capnp::SchemaFile>> tryImportBulitin(kj::StringPtr name);\n\n// Callbacks for capnp::SchemaFileLoader. Implementing this interface lets us control import\n// resolution, which we want to do mainly so that we can set watches on all imported files.\n//\n// These callbacks also give us more control over error reporting, in particular the ability\n// to not throw an exception on the first error seen.\nclass SchemaFileImpl final: public capnp::SchemaFile {\n public:\n  class ErrorReporter {\n   public:\n    virtual void reportParsingError(\n        kj::StringPtr file, SourcePos start, SourcePos end, kj::StringPtr message) = 0;\n  };\n\n  SchemaFileImpl(const kj::Directory& root,\n      kj::PathPtr current,\n      kj::Path fullPathParam,\n      kj::PathPtr basePath,\n      kj::ArrayPtr<const kj::Path> importPath,\n      kj::Own<const kj::ReadableFile> fileParam,\n      kj::Maybe<FileWatcher&> watcher,\n      ErrorReporter& errorReporter)\n      : root(root),\n        current(current),\n        fullPath(kj::mv(fullPathParam)),\n        basePath(basePath),\n        importPath(importPath),\n        file(kj::mv(fileParam)),\n        watcher(watcher),\n        errorReporter(errorReporter) {\n    if (fullPath.startsWith(current)) {\n      // Simplify display name by removing current directory prefix.\n      displayName = fullPath.slice(current.size(), fullPath.size()).toNativeString();\n    } else {\n      // Use full path.\n      displayName = fullPath.toNativeString(true);\n    }\n\n    KJ_IF_SOME(w, watcher) {\n      w.watch(fullPath, *file);\n    }\n  }\n\n  kj::StringPtr getDisplayName() const override {\n    return displayName;\n  }\n\n  kj::Array<const char> readContent() const override {\n    uint64_t size = file->stat().size;\n    if (!size) {\n      return nullptr;\n    }\n    return file->mmap(0, file->stat().size).releaseAsChars();\n  }\n\n  kj::Maybe<kj::Own<SchemaFile>> import(kj::StringPtr target) const override {\n    if (target.startsWith(\"/\")) {\n      auto parsedPath = kj::Path::parse(target.slice(1));\n      for (auto& candidate: importPath) {\n        auto newFullPath = candidate.append(parsedPath);\n\n        KJ_IF_SOME(newFile, root.tryOpenFile(newFullPath)) {\n          return kj::implicitCast<kj::Own<SchemaFile>>(kj::heap<SchemaFileImpl>(root, current,\n              kj::mv(newFullPath), candidate, importPath, kj::mv(newFile), watcher, errorReporter));\n        }\n      }\n      // No matching file found. Check if we have a builtin.\n      return tryImportBulitin(target);\n    } else {\n      auto relativeTo = fullPath.slice(basePath.size(), fullPath.size());\n      auto parsed = relativeTo.parent().eval(target);\n      auto newFullPath = basePath.append(parsed);\n\n      KJ_IF_SOME(newFile, root.tryOpenFile(newFullPath)) {\n        return kj::implicitCast<kj::Own<SchemaFile>>(kj::heap<SchemaFileImpl>(root, current,\n            kj::mv(newFullPath), basePath, importPath, kj::mv(newFile), watcher, errorReporter));\n      } else {\n        return kj::none;\n      }\n    }\n  }\n\n  bool operator==(const SchemaFile& other) const override {\n    if (auto downcasted = dynamic_cast<const SchemaFileImpl*>(&other)) {\n      return fullPath == downcasted->fullPath;\n    } else {\n      return false;\n    }\n  }\n\n  size_t hashCode() const override {\n    return kj::hashCode(fullPath);\n  }\n\n  void reportError(SourcePos start, SourcePos end, kj::StringPtr message) const override {\n    errorReporter.reportParsingError(displayName, start, end, message);\n  }\n\n private:\n  const kj::Directory& root;\n  kj::PathPtr current;\n\n  // Full path from root of filesystem to the file.\n  kj::Path fullPath;\n\n  // If this file was reached by scanning `importPath`, `basePath` is the particular import path\n  // directory that was used, otherwise it is empty. `basePath` is always a prefix of `fullPath`.\n  kj::PathPtr basePath;\n\n  // Paths to search for absolute imports.\n  kj::ArrayPtr<const kj::Path> importPath;\n\n  kj::Own<const kj::ReadableFile> file;\n  kj::String displayName;\n\n  // Mutable because the SchemaParser interface forces us to make all our methods `const` so that\n  // parsing can happen on multiple threads, but we do not actually use multiple threads for\n  // parsing, so we're good.\n  mutable kj::Maybe<FileWatcher&> watcher;\n\n  ErrorReporter& errorReporter;\n};\n\n// A schema file whose text is embedded into the binary for convenience.\n//\n// TODO(someday): Could `capnp::SchemaParser` be updated such that it can use the compiled-in\n//   schema nodes rather than re-parse the file from scratch? This is tricky as some information\n//   is lost after compilation which is needed to compile dependents, e.g. aliases are erased.\nclass BuiltinSchemaFileImpl final: public capnp::SchemaFile {\n public:\n  BuiltinSchemaFileImpl(kj::StringPtr name, kj::StringPtr content): name(name), content(content) {}\n\n  kj::StringPtr getDisplayName() const override {\n    return name;\n  }\n\n  kj::Array<const char> readContent() const override {\n    return kj::Array<const char>(content.begin(), content.size(), kj::NullArrayDisposer::instance);\n  }\n\n  kj::Maybe<kj::Own<SchemaFile>> import(kj::StringPtr target) const override {\n    return tryImportBulitin(target);\n  }\n\n  bool operator==(const SchemaFile& other) const override {\n    if (auto downcasted = dynamic_cast<const BuiltinSchemaFileImpl*>(&other)) {\n      return downcasted->name == name;\n    } else {\n      return false;\n    }\n  }\n\n  size_t hashCode() const override {\n    return kj::hashCode(name);\n  }\n\n  void reportError(SourcePos start, SourcePos end, kj::StringPtr message) const override {\n    KJ_FAIL_ASSERT(\"parse error in built-in schema?\", start.line, start.column, message);\n  }\n\n private:\n  kj::StringPtr name;\n  kj::StringPtr content;\n};\n\nkj::Maybe<kj::Own<capnp::SchemaFile>> tryImportBulitin(kj::StringPtr name) {\n  if (name == \"/capnp/c++.capnp\") {\n    return kj::heap<BuiltinSchemaFileImpl>(\"/capnp/c++.capnp\", CPP_CAPNP_SCHEMA);\n  } else if (name == \"/workerd/workerd.capnp\") {\n    return kj::heap<BuiltinSchemaFileImpl>(\"/workerd/workerd.capnp\", WORKERD_CAPNP_SCHEMA);\n  } else {\n    return kj::none;\n  }\n}\n\n// =======================================================================================\n\n// A kj::Network implementation which wraps some other network and optionally (if enabled)\n// implements \"loopback:\" network addresses, which are expected to be serviced within the same\n// process. Loopback addresses are enabled only when running `workerd test`. The purpose is to\n// allow end-to-end testing of the network stack without creating a real external-facing socket.\n//\n// There is no use for loopback sockets in production since direct service bindings are more\n// efficient while solving the same problems.\nclass NetworkWithLoopback final: public kj::Network {\n public:\n  NetworkWithLoopback(kj::Network& inner, kj::AsyncIoProvider& ioProvider)\n      : inner(inner),\n        ioProvider(ioProvider),\n        loopbackEnabled(rootLoopbackEnabled) {}\n\n  NetworkWithLoopback(\n      kj::Own<kj::Network> inner, kj::AsyncIoProvider& ioProvider, bool& loopbackEnabled)\n      : inner(*inner),\n        ownInner(kj::mv(inner)),\n        ioProvider(ioProvider),\n        loopbackEnabled(loopbackEnabled) {}\n\n  // Call once to enable loopback addresses.\n  void enableLoopback() {\n    loopbackEnabled = true;\n  }\n\n  kj::Promise<kj::Own<kj::NetworkAddress>> parseAddress(\n      kj::StringPtr addr, uint portHint = 0) override {\n    if (loopbackEnabled && addr.startsWith(PREFIX)) {\n      return kj::Own<kj::NetworkAddress>(kj::heap<LoopbackAddr>(*this, addr.slice(PREFIX.size())));\n    } else {\n      return inner.parseAddress(addr, portHint);\n    }\n  }\n\n  kj::Own<kj::NetworkAddress> getSockaddr(const void* sockaddr, uint len) override {\n    return inner.getSockaddr(sockaddr, len);\n  }\n\n  kj::Own<kj::Network> restrictPeers(kj::ArrayPtr<const kj::StringPtr> allow,\n      kj::ArrayPtr<const kj::StringPtr> deny = nullptr) override {\n    return kj::heap<NetworkWithLoopback>(\n        inner.restrictPeers(allow, deny), ioProvider, loopbackEnabled);\n  }\n\n private:\n  kj::Network& inner;\n  kj::Own<kj::Network> ownInner;\n  kj::AsyncIoProvider& ioProvider KJ_UNUSED;\n  bool rootLoopbackEnabled = false;\n\n  // Reference to `rootLoopbackEnabled` of the root NetworkWithLoopback. All descendants\n  // (created using `restrictPeers()` will share the same flag value.\n  bool& loopbackEnabled;\n\n  using ConnectionQueue = kj::ProducerConsumerQueue<kj::Own<kj::AsyncIoStream>>;\n  kj::HashMap<kj::String, kj::Own<ConnectionQueue>> loopbackQueues;\n\n  ConnectionQueue& getLoopbackQueue(kj::StringPtr name) {\n    return *loopbackQueues.findOrCreate(name, [&]() {\n      return decltype(loopbackQueues)::Entry{\n        .key = kj::str(name),\n        .value = kj::heap<ConnectionQueue>(),\n      };\n    });\n  }\n\n  static constexpr kj::StringPtr PREFIX = \"loopback:\"_kj;\n\n  class LoopbackAddr final: public kj::NetworkAddress {\n   public:\n    LoopbackAddr(NetworkWithLoopback& parent, kj::StringPtr name)\n        : parent(parent),\n          name(kj::str(name)) {}\n\n    kj::Promise<kj::Own<kj::AsyncIoStream>> connect() override {\n      // The purpose of loopback sockets is to actually test the network stack end-to-end. If\n      // people don't want to test the full stack, then they can create a direct service binding\n      // without going through a loopback socket.\n      //\n      // So, we create a real loopback socket here.\n      auto pipe = parent.ioProvider.newTwoWayPipe();\n\n      parent.getLoopbackQueue(name).push(kj::mv(pipe.ends[0]));\n      return kj::mv(pipe.ends[1]);\n    }\n\n    kj::Own<kj::ConnectionReceiver> listen() override {\n      return kj::heap<LoopbackReceiver>(parent.getLoopbackQueue(name));\n    }\n\n    kj::Own<kj::NetworkAddress> clone() override {\n      return kj::heap<LoopbackAddr>(parent, name);\n    }\n\n    kj::String toString() override {\n      return kj::str(PREFIX, name);\n    }\n\n   private:\n    NetworkWithLoopback& parent;\n    kj::String name;\n  };\n\n  class LoopbackReceiver final: public kj::ConnectionReceiver {\n   public:\n    LoopbackReceiver(ConnectionQueue& queue): queue(queue) {}\n\n    kj::Promise<kj::Own<kj::AsyncIoStream>> accept() override {\n      return queue.pop();\n    }\n\n    uint getPort() override {\n      return 0;\n    }\n\n   private:\n    ConnectionQueue& queue;\n  };\n};\n\n// =======================================================================================\n\nclass CliMain final: public SchemaFileImpl::ErrorReporter {\n public:\n  CliMain(StructuredLoggingProcessContext& context, char** argv)\n      : context(context),\n        argv(argv),\n        server(kj::heap<Server>(*fs,\n            io.provider->getTimer(),\n            kj::systemPreciseMonotonicClock(),\n            network,\n            entropySource,\n            Worker::LoggingOptions(Worker::ConsoleMode::STDOUT),\n            [&](kj::String error) {\n              if (watcher == kj::none) {\n                // TODO(someday): Don't just fail on the first error, keep going in order to report\n                //   additional errors. The tricky part is we don't currently have any signal of when\n                //   the server has completely finished loading, and also we probably don't want to\n                //   accept any connections on any of the sockets if the server is partially broken.\n                context.exitError(error);\n              } else {\n                // In --watch mode, we don't want to exit from errors, we want to wait until things\n                // change. It's OK if we try to serve requests despite brokenness since this is a\n                // development server.\n                hadErrors = true;\n                context.error(error);\n              }\n            })) {\n    KJ_IF_SOME(e, exeInfo) {\n      auto& exe = *e.file;\n      auto size = exe.stat().size;\n      KJ_ASSERT(size > sizeof(COMPILED_MAGIC_SUFFIX) + sizeof(uint64_t));\n      kj::byte magic[sizeof(COMPILED_MAGIC_SUFFIX)]{};\n      exe.read(size - sizeof(COMPILED_MAGIC_SUFFIX), magic);\n      if (kj::arrayPtr(magic) == kj::asBytes(COMPILED_MAGIC_SUFFIX)) {\n        // Oh! It appears we are running a compiled binary, it has a config appended to the end.\n        uint64_t configSize;\n        exe.read(size - sizeof(COMPILED_MAGIC_SUFFIX) - sizeof(uint64_t), kj::asBytes(configSize));\n        KJ_ASSERT(size - sizeof(COMPILED_MAGIC_SUFFIX) - sizeof(uint64_t) >\n            configSize * sizeof(capnp::word));\n        size_t offset = size - sizeof(COMPILED_MAGIC_SUFFIX) - sizeof(uint64_t) -\n            configSize * sizeof(capnp::word);\n\n        auto mapping = exe.mmap(offset, configSize * sizeof(capnp::word));\n        KJ_ASSERT(reinterpret_cast<uintptr_t>(mapping.begin()) % sizeof(capnp::word) == 0,\n            \"compiled-in config is not aligned correctly?\");\n\n        config = capnp::readMessageUnchecked<config::Config>(\n            reinterpret_cast<const capnp::word*>(mapping.begin()));\n        configOwner = kj::heap(kj::mv(mapping));\n      }\n    } else {\n      context.warning(\n          \"Unable to find and open the program executable, so unable to determine if there is a \"\n          \"compiled-in config file. Proceeding on the assumption that there is not.\");\n    }\n\n    // We don't want to force people to specify top-level file IDs in `workerd` config files, as\n    // those IDs would be totally irrelevant.\n    schemaParser.setFileIdsRequired(false);\n  }\n\n  kj::MainFunc getMain() {\n    if (config == kj::none) {\n      return kj::MainBuilder(\n          context, getVersionString(), \"Runs the Workers JavaScript/Wasm runtime.\")\n          .addSubCommand(\"serve\", KJ_BIND_METHOD(*this, getServe), \"run the server\")\n          .addSubCommand(\n              \"compile\", KJ_BIND_METHOD(*this, getCompile), \"create a self-contained binary\")\n#ifdef WORKERD_FUZZILLI\n          .addSubCommand(\"fuzzilli\", KJ_BIND_METHOD(*this, getFuzz), \"run reprl for fuzzing\")\n#endif\n          .addSubCommand(\"test\", KJ_BIND_METHOD(*this, getTest), \"run unit tests\")\n          .addSubCommand(\"pyodide-lock\", KJ_BIND_METHOD(*this, getPyodideLock),\n              \"outputs the package lock file used by Pyodide\")\n          .addSubCommand(\"make-pyodide-baseline-snapshot\",\n              KJ_BIND_METHOD(*this, getMakePyodideBaselineSnapshot),\n              \"Make a Pyodide baseline memory snapshot\")\n          .build();\n      // TODO(someday):\n      // \"validate\": Loads the config and parses all the code to report errors, but then exits\n      //   without serving anything.\n      // \"explain\": Produces human-friendly description of the config.\n    } else {\n      // We already have a config, meaning this must be a compiled binary.\n      auto builder = kj::MainBuilder(context, getVersionString(),\n          \"Serve requests based on the compiled config.\",\n          \"This binary has an embedded configuration.\");\n      return addServeOptions(builder);\n    }\n  }\n\n  kj::MainBuilder& addConfigParsingOptionsNoConstName(kj::MainBuilder& builder) {\n    return builder\n        .addOptionWithArg({'I', \"import-path\"}, CLI_METHOD(addImportPath), \"<dir>\",\n            \"Add <dir> to the list of directories searched for non-relative \"\n            \"imports in the config file (ones that start with a '/').\")\n        .addOption({'b', \"binary\"},\n            [this]() {\n      binaryConfig = true;\n      return true;\n    },\n            \"Specifies that the configuration file is an encoded binary Cap'n Proto \"\n            \"message, rather than the usual text format. This is particularly useful when \"\n            \"driving the server from higher-level tooling that automatically generates a \"\n            \"config.\")\n        .expectArg(\"<config-file>\", CLI_METHOD(parseConfigFile));\n  }\n\n  kj::MainBuilder& addConfigParsingOptions(kj::MainBuilder& builder) {\n    return addConfigParsingOptionsNoConstName(builder).expectOptionalArg(\n        \"<const-name>\", CLI_METHOD(setConstName));\n  }\n\n  kj::MainBuilder& addServeOrTestOptions(kj::MainBuilder& builder) {\n    return builder\n        .addOptionWithArg({'d', \"directory-path\"}, CLI_METHOD(overrideDirectory), \"<name>=<path>\",\n            \"Override the directory named <name> to point to <path> instead of the \"\n            \"path specified in the config file.\")\n        .addOptionWithArg({'e', \"external-addr\"}, CLI_METHOD(overrideExternal), \"<name>=<addr>\",\n            \"Override the external service named <name> to connect to the address \"\n            \"<addr> instead of the address specified in the config file.\")\n        .addOptionWithArg({'i', \"inspector-addr\"}, CLI_METHOD(enableInspector), \"<addr>\",\n            \"Enable the inspector protocol to connect to the address <addr>.\")\n#ifdef WORKERD_USE_PERFETTO\n        // TODO(later): In the future, we might want to enable providing a perfetto\n        // TraceConfig structure here rather than just the categories.\n        .addOptionWithArg({\"p\", \"perfetto-trace\"}, CLI_METHOD(enablePerfetto),\n            \"<path>=<categories>\", \"Enable perfetto tracing output to the specified file.\")\n#endif\n        .addOption({'w', \"watch\"}, CLI_METHOD(watch),\n            \"Watch configuration files (and server binary) and reload if they change. \"\n            \"Useful for development, but not recommended in production.\")\n        .addOption({\"experimental\"},\n            [this]() {\n      server->allowExperimental();\n      return true;\n    },\n            \"Permit the use of experimental features which may break backwards \"\n            \"compatibility in a future release.\")\n        .addOptionWithArg({\"pyodide-package-disk-cache-dir\"}, CLI_METHOD(setPackageDiskCacheDir),\n            \"<path>\",\n            \"Use <path> as a disk cache to avoid repeatedly fetching packages from the internet. \")\n        .addOptionWithArg({\"pyodide-bundle-disk-cache-dir\"}, CLI_METHOD(setPyodideDiskCacheDir),\n            \"<path>\",\n            \"Use <path> as a disk cache to avoid repeatedly fetching Pyodide bundles from the internet. \")\n        .addOption({\"python-save-snapshot\"},\n            [this]() {\n      server->setPythonCreateSnapshot();\n      return true;\n    }, \"Save a dedicated snapshot to the disk cache\")\n        .addOption({\"python-save-baseline-snapshot\"},\n            [this]() {\n      server->setPythonCreateBaselineSnapshot();\n      return true;\n    }, \"Save a baseline snapshot to the disk cache\")\n        .addOptionWithArg({\"python-load-snapshot\"}, CLI_METHOD(setPythonLoadSnapshot), \"<path>\",\n            \"Load a snapshot from the python snapshot directory.\")\n        .addOptionWithArg({\"python-snapshot-dir\"}, CLI_METHOD(setPythonSnapshotDirectory), \"<path>\",\n            \"Set the snapshot snapshot directory.\");\n  }\n\n  kj::MainFunc addServeOptions(kj::MainBuilder& builder) {\n    return addServeOrTestOptions(builder)\n        .addOptionWithArg({'s', \"socket-addr\"}, CLI_METHOD(overrideSocketAddr), \"<name>=<addr>\",\n            \"Override the socket named <name> to bind to the address <addr> instead \"\n            \"of the address specified in the config file.\")\n        .addOptionWithArg({'S', \"socket-fd\"}, CLI_METHOD(overrideSocketFd), \"<name>=<fd>\",\n            \"Override the socket named <name> to listen on the already-open socket \"\n            \"descriptor <fd> instead of the address specified in the config file.\")\n        .addOptionWithArg({\"control-fd\"}, CLI_METHOD(enableControl), \"<fd>\",\n            \"Enable sending of control messages on descriptor <fd>. Currently this \"\n            \"only reports the port each socket is listening on when ready.\")\n        .addOptionWithArg({\"debug-port\"}, CLI_METHOD(enableDebugPort), \"<addr>\",\n            \"Listen on the specified address for debug RPC connections. This exposes \"\n            \"a privileged interface that allows access to all services in the process. \"\n            \"For use by miniflare and local development only.\")\n        .callAfterParsing(CLI_METHOD(serve))\n        .build();\n  }\n\n  kj::MainFunc getServe() {\n    auto builder = kj::MainBuilder(context, getVersionString(), \"Serve requests based on a config.\",\n        \"Serves requests based on the configuration specified in <config-file>.\");\n    return addServeOptions(addConfigParsingOptions(builder));\n  }\n\n  kj::MainFunc getPyodideLock() {\n    auto builder = kj::MainBuilder(\n        context, getVersionString(), \"Outputs the package lock file used by Pyodide.\");\n    return builder\n        .callAfterParsing([]() -> kj::MainBuilder::Validity {\n      static const PythonConfig config{\n        .packageDiskCacheRoot = kj::none,\n        .pyodideDiskCacheRoot = kj::none,\n        .createSnapshot = false,\n        .createBaselineSnapshot = false,\n      };\n\n      capnp::MallocMessageBuilder message;\n      // TODO(EW-8977): Implement option to specify python worker flags.\n      auto features = message.getRoot<CompatibilityFlags>();\n      features.setPythonWorkers(true);\n      auto pythonRelease = KJ_ASSERT_NONNULL(getPythonSnapshotRelease(features));\n\n      auto lock = KJ_ASSERT_NONNULL(api::pyodide::getPyodideLock(pythonRelease));\n\n      printf(\"%s\\n\", lock.cStr());\n      fflush(stdout);\n      return true;\n    }).build();\n  }\n\n  kj::MainFunc getTest() {\n    auto builder = kj::MainBuilder(context, getVersionString(), \"Runs tests based on a config.\",\n        \"Runs tests for services defined in <config-file>. <filter>, if given, specifies \"\n        \"exactly which tests to run. It has one of the following formats:\\n\"\n        \"    <service-pattern>\\n\"\n        \"    <service-pattern>:<entrypoint-pattern>\\n\"\n        \"    <const-name>:<service-pattern>:<entrypoint-pattern>\\n\"\n        \"<service-pattern> is a glob pattern matching names of services which should be tested. \"\n        \"If not specified, '*' is assumed (which matches all services). <entrypoint-pattern> \"\n        \"is a glob pattern matching entrypoints within each service which should be tested; \"\n        \"again, the default is '*'. <const-name> has the same meaning as for the `serve` \"\n        \"command (this is rarely used).\\n\"\n        \"\\n\"\n        \"Tests can be defined by exporting a function called `test` instead of (or in addition \"\n        \"to) `fetch`. Example:\\n\"\n        \"    export default {\\n\"\n        \"      async test(ctrl, env, ctx) {\\n\"\n        \"        if (1 + 1 != 2) {\\n\"\n        \"          throw new Error('math is broken!');\\n\"\n        \"        }\\n\"\n        \"      }\\n\"\n        \"    }\\n\"\n        \"The test passes if the test function completes without throwing. Multiple tests can \"\n        \"be exported under different entrypoint names:\\n\"\n        \"    export let test1 = {\\n\"\n        \"      async test(ctrl, env, ctx) {\\n\"\n        \"        ...\\n\"\n        \"      }\\n\"\n        \"    }\\n\"\n        \"    export let test2 = {\\n\"\n        \"      async test(ctrl, env, ctx) {\\n\"\n        \"        ...\\n\"\n        \"      }\\n\"\n        \"    }\\n\");\n    return addServeOrTestOptions(addConfigParsingOptionsNoConstName(builder))\n        .addOption({\"no-verbose\"},\n            [this]() {\n      noVerbose = true;\n      return true;\n    },\n            \"Disable INFO-level logging for this test. Otherwise, INFO logging is enabled by \"\n            \"default for tests in order to show uncaught exceptions, but it can be noisey.\")\n        .addOption({\"predictable\"},\n            [this]() {\n      predictable = true;\n      return true;\n    },\n            \"Enable predictable mode. This makes workerd behave more deterministically by using \"\n            \"pre-set values instead of random data or timestamps to facilitate testing.\")\n        .addOption({\"all-autogates\"},\n            [this]() {\n      allAutogates = true;\n      return true;\n    },\n            \"Enable all autogates. This is useful for testing code paths that are guarded by \"\n            \"autogates.\")\n        .addOptionWithArg({\"compat-date\"}, CLI_METHOD(setTestCompatDate), \"<date>\",\n            \"Set the compatibility date for all workers. When specified, workers must NOT \"\n            \"specify compatibilityDate in the config. Use '0000-00-00' for oldest behavior \"\n            \"or '9999-12-31' for newest behavior.\")\n        .expectOptionalArg(\"<filter>\", CLI_METHOD(setTestFilter))\n        .callAfterParsing(CLI_METHOD(test))\n        .build();\n  }\n\n  kj::MainFunc getFuzz() {\n    auto builder = kj::MainBuilder(context, getVersionString(),\n        \"Creates a custom signal handler and depending on the config leverages Stdin.reprl() to communicate with fuzzilli.\");\n\n    return addServeOrTestOptions(addConfigParsingOptionsNoConstName(builder))\n        .callAfterParsing(CLI_METHOD(test))\n        .build();\n  }\n\n  kj::MainFunc getCompile() {\n    auto builder = kj::MainBuilder(context, getVersionString(),\n        \"Builds a self-contained binary from a config.\",\n        \"This parses a config file in the same manner as the \\\"serve\\\" command, but instead \"\n        \"of then running it, it outputs a new binary to stdout that embeds the config and all \"\n        \"associated Worker code and data as one self-contained unit. This binary may then \"\n        \"be executed on another system to run the config -- without any other files being \"\n        \"present on that system.\");\n    return addConfigParsingOptions(builder)\n        .addOption({\"config-only\"},\n            [this]() {\n      configOnly = true;\n      return true;\n    },\n            \"Only write the encoded binary config to stdout. Do not attach it to an executable. \"\n            \"The encoded config can be used as input to the \\\"serve\\\" command, without the need \"\n            \"for any other files to be present.\")\n        .callAfterParsing(CLI_METHOD(compile))\n        .build();\n  }\n\n  kj::MainFunc getMakePyodideBaselineSnapshot() {\n    server->allowExperimental();\n    server->setPythonCreateBaselineSnapshot();\n    auto builder =\n        kj::MainBuilder(context, getVersionString(), \"Make a Pyodide baseline memory snapshot\", \"\");\n    setPyodideDiskCacheDir(\".\");\n    return builder.expectArg(\"<python-version>\", CLI_METHOD(parsePythonCompatFlag))\n        .expectArg(\"<output-directory>\", CLI_METHOD(setPackageDiskCacheDir))\n        .callAfterParsing(CLI_METHOD(test))\n        .build();\n  }\n\n  void addImportPath(kj::StringPtr pathStr) {\n    auto path = fs->getCurrentPath().evalNative(pathStr);\n    if (fs->getRoot().tryOpenSubdir(path) != kj::none) {\n      importPath.add(kj::mv(path));\n    } else {\n      CLI_ERROR(\"No such directory.\");\n    }\n  }\n\n  struct Override {\n    kj::String name;\n    kj::StringPtr value;\n  };\n  Override parseOverride(kj::StringPtr str) {\n    auto equalPos = KJ_UNWRAP_OR(str.findFirst('='), CLI_ERROR(\"Expected <name>=<value>\"));\n    return {kj::str(str.first(equalPos)), str.slice(equalPos + 1)};\n  }\n\n  void overrideSocketAddr(kj::StringPtr param) {\n    auto [name, value] = parseOverride(param);\n    server->overrideSocket(kj::mv(name), kj::str(value));\n  }\n\n#if _WIN32\n  void validateSocketFd(uint fd, kj::StringPtr label) {\n    int acceptcon = 0;\n    int optlen = sizeof(acceptcon);\n    int result = getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, (char*)&acceptcon, &optlen);\n    if (result == SOCKET_ERROR) {\n      // https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockopt#return-value\n      switch (int error = WSAGetLastError()) {\n        case WSAENOTSOCK:\n          CLI_ERROR(\"File descriptor is not a socket.\");\n        case WSAENOPROTOOPT:\n          // Some operating systems don't support SO_ACCEPTCONN; in that case just move on and\n          // assume it is listening.\n          break;\n        default:\n          KJ_FAIL_SYSCALL(\"getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN)\", error);\n      }\n    } else if (!acceptcon) {\n      CLI_ERROR(\"Socket for \", label, \" is not listening.\");\n    }\n  }\n#else\n  void validateSocketFd(uint fd, kj::StringPtr label) {\n    int acceptcon = 0;\n    socklen_t optlen = sizeof(acceptcon);\n    KJ_SYSCALL_HANDLE_ERRORS(getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &acceptcon, &optlen)) {\n      case EBADF:\n        CLI_ERROR(\"File descriptor is not open.\");\n      case ENOTSOCK:\n        CLI_ERROR(\"File descriptor is not a socket.\");\n      case ENOPROTOOPT:\n        // Some operating systems don't support SO_ACCEPTCONN; in that case just move on and\n        // assume it is listening.\n        break;\n      default:\n        KJ_FAIL_SYSCALL(\"getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN)\", error);\n    }\n    else {\n      if (!acceptcon) {\n        CLI_ERROR(\"Socket for \", label, \" is not listening.\");\n      }\n    }\n  }\n#endif\n\n  void overrideSocketFd(kj::StringPtr param) {\n    auto [name, value] = parseOverride(param);\n\n    int fd = KJ_UNWRAP_OR(value.tryParseAs<uint>(),\n        CLI_ERROR(\"Socket value must be a file descriptor (non-negative integer).\"));\n\n    validateSocketFd(fd, name);\n\n    inheritedFds.add(fd);\n    server->overrideSocket(kj::mv(name),\n        io.lowLevelProvider->wrapListenSocketFd(fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP));\n  }\n\n  void overrideDirectory(kj::StringPtr param) {\n    auto [name, value] = parseOverride(param);\n    server->overrideDirectory(kj::mv(name), kj::str(value));\n  }\n\n  void overrideExternal(kj::StringPtr param) {\n    auto [name, value] = parseOverride(param);\n    server->overrideExternal(kj::mv(name), kj::str(value));\n  }\n\n#ifdef WORKERD_USE_PERFETTO\n  void enablePerfetto(kj::StringPtr param) {\n    auto [name, value] = parseOverride(param);\n    perfettoTraceDestination = kj::str(name);\n    perfettoTraceCategories = kj::str(value);\n  }\n#endif\n\n  void enableInspector(kj::StringPtr param) {\n    server->enableInspector(kj::str(param));\n  }\n\n  void enableControl(kj::StringPtr param) {\n    int fd = KJ_UNWRAP_OR(param.tryParseAs<uint>(),\n        CLI_ERROR(\"Output value must be a file descriptor (non-negative integer).\"));\n    server->enableControl(fd);\n  }\n\n  void enableDebugPort(kj::StringPtr param) {\n    server->enableDebugPort(kj::str(param));\n  }\n\n  void setPackageDiskCacheDir(kj::StringPtr pathStr) {\n    kj::Path path = fs->getCurrentPath().eval(pathStr);\n    kj::Maybe<kj::Own<const kj::Directory>> dir =\n        fs->getRoot().tryOpenSubdir(path, kj::WriteMode::MODIFY);\n    server->setPackageDiskCacheRoot(\n        kj::mv(KJ_UNWRAP_OR(dir, CLI_ERROR(\"package disk cache dir must exist\"))));\n  }\n\n  void setPyodideDiskCacheDir(kj::StringPtr pathStr) {\n    kj::Path path = fs->getCurrentPath().eval(pathStr);\n    kj::Maybe<kj::Own<const kj::Directory>> dir =\n        fs->getRoot().tryOpenSubdir(path, kj::WriteMode::MODIFY);\n    server->setPyodideDiskCacheRoot(kj::mv(dir));\n  }\n\n  void setPythonLoadSnapshot(kj::StringPtr pathStr) {\n    server->setPythonLoadSnapshot(kj::str(pathStr));\n  }\n  void setPythonSnapshotDirectory(kj::StringPtr pathStr) {\n    kj::Path path = fs->getCurrentPath().eval(pathStr);\n    kj::Maybe<kj::Own<const kj::Directory>> dir =\n        fs->getRoot().tryOpenSubdir(path, kj::WriteMode::MODIFY);\n    server->setPythonSnapshotDirectory(kj::mv(dir));\n  }\n\n  void parsePythonCompatFlag(kj::StringPtr compatFlagStr) {\n    auto builder = kj::heap<capnp::MallocMessageBuilder>();\n    auto configBuilder = builder->initRoot<config::Config>();\n    auto service = configBuilder.initServices(1)[0];\n    service.setName(\"main\");\n    auto worker = service.initWorker();\n    worker.setCompatibilityDate(\"2023-12-18\");\n    auto flags = worker.initCompatibilityFlags(2);\n    flags.set(0, compatFlagStr);\n    flags.set(1, \"python_workers\");\n    auto mod = worker.initModules(1)[0];\n    mod.setName(\"main.py\");\n    mod.setPythonModule(\"def test():\\n pass\");\n    config = configBuilder.asReader();\n    configOwner = kj::mv(builder);\n    util::Autogate::initAutogate(getConfig().getAutogates());\n  }\n\n  void watch() {\n#if _WIN32\n    auto& w = watcher.emplace(io.win32EventPort);\n#else\n    auto& w = watcher.emplace(io.unixEventPort);\n#endif\n    if (!w.isSupported()) {\n      CLI_ERROR(\"File watching is not yet implemented on your OS. Sorry! Pull requests welcome!\");\n    }\n\n    KJ_IF_SOME(e, exeInfo) {\n      w.watch(fs->getCurrentPath().eval(e.path), kj::none);\n    } else {\n      CLI_ERROR(\"Can't use --watch when we're unable to find our own executable.\");\n    }\n  }\n\n  void parseConfigFile(kj::StringPtr pathStr) {\n    if (pathStr == \"-\") {\n      // Read from stdin.\n\n      if (!binaryConfig) {\n        CLI_ERROR(\"Reading config from stdin is only allowed with --binary.\");\n      }\n\n      // Can't use mmap() because it's probably not a file.\n#if _WIN32\n      auto handle = GetStdHandle(STD_INPUT_HANDLE);\n      auto stream = kj::HandleInputStream(handle);\n      auto reader = kj::heap<capnp::InputStreamMessageReader>(stream, CONFIG_READER_OPTIONS);\n#else\n      auto reader = kj::heap<capnp::StreamFdMessageReader>(STDIN_FILENO, CONFIG_READER_OPTIONS);\n#endif\n      config = reader->getRoot<config::Config>();\n      configOwner = kj::mv(reader);\n    } else {\n      // Read file from disk.\n      auto path = fs->getCurrentPath().evalNative(pathStr);\n      auto file = KJ_UNWRAP_OR(fs->getRoot().tryOpenFile(path), CLI_ERROR(\"No such file.\"));\n\n      // Use stat() to check that we have a file vs a directory which will fail to mmap\n      auto metadata = file->stat();\n      if (metadata.type != kj::FsNode::Type::FILE) {\n        CLI_ERROR(\"Config path is not a file.\");\n      }\n\n      if (binaryConfig) {\n        // Interpret as binary config.\n        auto mapping = file->mmap(0, file->stat().size);\n        auto words = kj::arrayPtr(reinterpret_cast<const capnp::word*>(mapping.begin()),\n            mapping.size() / sizeof(capnp::word));\n        auto reader = kj::heap<capnp::FlatArrayMessageReader>(words, CONFIG_READER_OPTIONS)\n                          .attach(kj::mv(mapping));\n        config = reader->getRoot<config::Config>();\n        configOwner = kj::mv(reader);\n      } else {\n        // Interpret as schema file.\n        schemaParser.loadCompiledTypeAndDependencies<config::Config>();\n\n        parsedSchema = schemaParser.parseFile(kj::heap<SchemaFileImpl>(fs->getRoot(),\n            fs->getCurrentPath(), kj::mv(path), nullptr, importPath, kj::mv(file), watcher, *this));\n\n        // Construct a list of top-level constants of type `Config`. If there is exactly one,\n        // we can use it by default.\n        for (auto nested: parsedSchema.getAllNested()) {\n          if (nested.getProto().isConst()) {\n            auto constSchema = nested.asConst();\n            auto type = constSchema.getType();\n            if (type.isStruct() &&\n                type.asStruct().getProto().getId() == capnp::typeId<config::Config>()) {\n              topLevelConfigConstants.add(constSchema);\n            }\n          }\n        }\n      }\n    }\n\n    // We'll fail at getConfig() if there are multiple top level Config objects.\n    // The error message says that you have to specify which config to use, but\n    // it's not clear that there is any mechanism to do that.\n    util::Autogate::initAutogate(getConfig().getAutogates());\n  }\n\n  void setConstName(kj::StringPtr name) {\n    auto parent = parsedSchema;\n\n    for (;;) {\n      auto dotPos = KJ_UNWRAP_OR(name.findFirst('.'), break);\n      auto parentName = name.first(dotPos);\n      parent = KJ_UNWRAP_OR(parent.findNested(kj::str(parentName)),\n          CLI_ERROR(\"No such constant is defined in the config file (the parent scope '\",\n              parentName, \"' does not exist).\"));\n      name = name.slice(dotPos + 1);\n    }\n\n    auto node = KJ_UNWRAP_OR(parsedSchema.findNested(name),\n        CLI_ERROR(\"No such constant is defined in the config file.\"));\n\n    if (!node.getProto().isConst()) {\n      CLI_ERROR(\"Symbol is not a constant.\");\n    }\n\n    auto constSchema = node.asConst();\n    auto type = constSchema.getType();\n    if (!type.isStruct() || type.asStruct().getProto().getId() != capnp::typeId<config::Config>()) {\n      CLI_ERROR(\"Constant is not of type 'Config'.\");\n    }\n\n    config = constSchema.as<config::Config>();\n  }\n\n  void setTestFilter(kj::StringPtr filter) {\n    kj::Vector<kj::String> parts;\n\n    for (;;) {\n      KJ_IF_SOME(pos, filter.findFirst(':')) {\n        parts.add(kj::str(filter.first(pos)));\n        filter = filter.slice(pos + 1);\n      } else {\n        parts.add(kj::str(filter));\n        break;\n      }\n    }\n\n    switch (parts.size()) {\n      case 0:\n        KJ_UNREACHABLE;\n      case 1:\n        testServicePattern = kj::mv(parts[0]);\n        break;\n      case 2:\n        testServicePattern = kj::mv(parts[0]);\n        testEntrypointPattern = kj::mv(parts[1]);\n        break;\n      case 3:\n        setConstName(parts[0]);\n        testServicePattern = kj::mv(parts[1]);\n        testEntrypointPattern = kj::mv(parts[2]);\n        break;\n      default:\n        CLI_ERROR(\"Too many colons.\");\n    }\n  }\n\n  void setTestCompatDate(kj::StringPtr date) {\n    testCompatDate = kj::str(date);\n  }\n\n  void compile() {\n    if (hadErrors) {\n      // Errors were already reported with context.error(), so context.exit() will exit with a\n      // non-zero code.\n      context.exit();\n    }\n\n    config::Config::Reader config = getConfig();\n\n#if _WIN32\n    if (_isatty(_fileno(stdout))) {\n#else\n    if (isatty(STDOUT_FILENO)) {\n#endif\n      context.exitError(\n          \"Refusing to write binary to the terminal. Please use `>` to send the output to a file.\");\n    }\n\n#if !_WIN32\n    // Grab the inode info before we write anything.\n    struct stat stats;\n    KJ_SYSCALL(fstat(STDOUT_FILENO, &stats));\n#endif\n\n#if _WIN32\n    kj::FdOutputStream out(_fileno(stdout));\n#else\n    kj::FdOutputStream out(STDOUT_FILENO);\n#endif\n\n    if (configOnly) {\n      // Write just the config -- in normal message format -- to stdout.\n      uint64_t size = config.totalSize().wordCount + 1;\n      capnp::MallocMessageBuilder builder(size + 1);\n      builder.setRoot(config);\n      KJ_DASSERT(builder.getSegmentsForOutput().size() == 1);\n      capnp::writeMessage(out, builder);\n    } else {\n      // Write an executable file to stdout by concatenating this executable, the config, and the\n      // magic suffix. This takes advantage of the fact that you can append arbitrary stuff to an\n      // ELF binary or Windows executable without affecting the ability to execute the program.\n\n      // Copy the executable to the output.\n      {\n        auto& exe = KJ_UNWRAP_OR(exeInfo,\n            CLI_ERROR(\n                \"Unable to find and open the program's own executable, so cannot produce a new \"\n                \"binary with compiled-in config.\"));\n\n        auto mapping = exe.file->mmap(0, exe.file->stat().size);\n        out.write(mapping);\n\n        // Pad to a word boundary if necessary.\n        size_t n = mapping.size() % sizeof(capnp::word);\n        if (n != 0) {\n          kj::byte pad[sizeof(capnp::word)] = {0};\n          out.write(kj::arrayPtr(pad).slice(n));\n        }\n      }\n\n      // Now write the config, plus magic suffix. We're going to write the config as a\n      // single-segment flat message, which makes it easier to consume.\n      {\n        uint64_t size = config.totalSize().wordCount + 1;\n        static_assert(sizeof(uint64_t) + sizeof(COMPILED_MAGIC_SUFFIX) == sizeof(capnp::word) * 3);\n        auto words = kj::heapArray<capnp::word>(size + 3);\n        words.asBytes().fill(0);\n        capnp::copyToUnchecked(config, words.first(size));\n\n        memcpy(&words[words.size() - 3], &size, sizeof(size));\n        memcpy(&words[words.size() - 2], COMPILED_MAGIC_SUFFIX, sizeof(COMPILED_MAGIC_SUFFIX));\n\n        out.write(words.asBytes());\n      }\n\n#if !_WIN32\n      // If we wrote a regular file, and it was empty before we started writing, then let's go ahead\n      // and set the executable bit on the file.\n      if (S_ISREG(stats.st_mode) && stats.st_size == 0) {\n        // Add executable bit for all users who have read access.\n        mode_t mode = stats.st_mode;\n        if (mode & S_IRUSR) {\n          mode |= S_IXUSR;\n        }\n        if (mode & S_IRGRP) {\n          mode |= S_IXGRP;\n        }\n        if (mode & S_IROTH) {\n          mode |= S_IXOTH;\n        }\n        KJ_SYSCALL(fchmod(STDOUT_FILENO, mode));\n      }\n#endif\n    }\n  }\n\n  template <typename Func>\n  void serveImpl(Func&& func) noexcept {\n    if (hadErrors) {\n      // Can't start, stuff is broken.\n      KJ_IF_SOME(w, watcher) {\n        // In --watch mode, it's annoying if the server exits and stops watching. Let's wait for\n        // someone to fix the config.\n        context.warning(\n            \"Can't start server due to config errors, waiting for config files to change...\");\n        waitForChanges(w).wait(io.waitScope);\n        reloadFromConfigChange();\n      } else {\n        // Errors were reported earlier, so context.exit() will exit with a non-zero status.\n        context.exit();\n      }\n    } else {\n#ifdef WORKERD_USE_PERFETTO\n      kj::Maybe<PerfettoSession> maybePerfettoSession;\n      KJ_IF_SOME(dest, perfettoTraceDestination) {\n        maybePerfettoSession =\n            PerfettoSession(dest, kj::mv(perfettoTraceCategories).orDefault(kj::String()));\n      }\n#endif\n      TRACE_EVENT(\"workerd\", \"serveImpl()\");\n      auto config = getConfig();\n\n      // Configure structured logging in the process context\n      if (config.hasLogging() ? config.getLogging().getStructuredLogging()\n                              : config.getStructuredLogging()) {\n        context.enableStructuredLogging();\n      }\n\n      auto platform = jsg::defaultPlatform(0);\n      WorkerdPlatform v8Platform(*platform);\n      jsg::V8System v8System(v8Platform,\n          KJ_MAP(flag, config.getV8Flags()) -> kj::StringPtr { return flag; }, platform.get());\n      auto promise = func(v8System, config);\n      KJ_IF_SOME(w, watcher) {\n        promise = promise.exclusiveJoin(waitForChanges(w).then([this]() {\n          // Watch succeeded.\n          reloadFromConfigChange();\n        }));\n      }\n      promise.wait(io.waitScope);\n#ifdef WORKERD_USE_PERFETTO\n      KJ_IF_SOME(perfettoSession, maybePerfettoSession) {\n        auto dropMe = kj::mv(perfettoSession);\n        maybePerfettoSession = kj::none;\n      }\n#endif\n\n      if (getenv(\"KJ_CLEAN_SHUTDOWN\") == nullptr) {\n        context.exit();\n      }\n\n      // Server maintains a reference to the v8 platform. Clean up before destroying the platform.\n      server = nullptr;\n    }\n  }\n\n  void serve() noexcept {\n    serveImpl([&](jsg::V8System& v8System, config::Config::Reader config) {\n#if _WIN32\n      return server->run(v8System, config);\n#else\n      return server->run(v8System, config,\n          // Gracefully drain when SIGTERM is received.\n          io.unixEventPort.onSignal(SIGTERM).ignoreResult());\n#endif\n    });\n  }\n\n  void test() {\n    if (!noVerbose) {\n      // Always turn on info logging when running tests so that uncaught exceptions are displayed.\n      // TODO(beta): This can be removed once we improve our error logging story.\n      kj::_::Debug::setLogLevel(kj::LogSeverity::INFO);\n    }\n    if (predictable) {\n      setPredictableModeForTest();\n    }\n    if (allAutogates) {\n      util::Autogate::initAllAutogates();\n    }\n\n    KJ_IF_SOME(compatDate, testCompatDate) {\n      server->setTestCompatibilityDateOverride(kj::str(compatDate));\n    }\n\n    // Enable loopback sockets in tests only.\n    network.enableLoopback();\n\n    serveImpl([&](jsg::V8System& v8System, config::Config::Reader config) {\n      return server\n          ->test(v8System, config,\n              testServicePattern.map([](auto& s) -> kj::StringPtr { return s; }).orDefault(\"*\"_kj),\n              testEntrypointPattern.map([](auto& s) -> kj::StringPtr {\n        return s;\n      }).orDefault(\"*\"_kj))\n          .then([this](bool result) -> kj::Promise<void> {\n        if (!result) {\n          context.error(\"Tests failed!\");\n        }\n\n        if (watcher == kj::none) {\n          return kj::READY_NOW;\n        } else {\n          // Pause forever waiting for watcher.\n          return kj::NEVER_DONE;\n        }\n      });\n    });\n  }\n\n#if _WIN32\n  void reloadFromConfigChange() {\n    KJ_UNREACHABLE(\"Watching is not yet implemented on Windows\");\n  }\n#else\n  [[noreturn]] void reloadFromConfigChange() {\n    // Write extra spaces to fully overwrite the line that we wrote earlier with a CR but no LF:\n    //     \"Noticed configuration change, reloading shortly...\\r\"\n    context.warning(\"Reloading due to config change...                                      \");\n    for (auto fd: inheritedFds) {\n      // Disable close-on-exec for inherited FDs so that the successor process can also inherit\n      // them.\n      KJ_SYSCALL(ioctl(fd, FIONCLEX));\n    }\n    bool missingBinary = false;\n    for (;;) {\n      KJ_SYSCALL_HANDLE_ERRORS(execve(KJ_ASSERT_NONNULL(exeInfo).path.cStr(), argv, environ)) {\n        case ENOENT: {\n          // Write a message\n          // TODO(cleanup): Writing directly to stderr is super-hacky.\n          if (!missingBinary) {\n            context.warning(\"The server executable is missing! Waiting for it to reappear...\\r\");\n            missingBinary = true;\n          }\n          sleep(1);\n          break;\n        }\n        default:\n          KJ_FAIL_SYSCALL(\"execve\", error);\n      }\n    }\n  }\n#endif\n\n private:\n  StructuredLoggingProcessContext& context;\n  char** argv;\n\n  bool binaryConfig = false;\n  bool configOnly = false;\n  bool noVerbose = false;\n  bool predictable = false;\n  bool allAutogates = false;\n  kj::Maybe<kj::String> testCompatDate;\n  kj::Maybe<FileWatcher> watcher;\n\n  kj::Own<kj::Filesystem> fs = kj::newDiskFilesystem();\n  kj::AsyncIoContext io = kj::setupAsyncIo();\n  NetworkWithLoopback network{io.provider->getNetwork(), *io.provider};\n  EntropySourceImpl entropySource;\n\n  kj::Vector<kj::Path> importPath;\n  capnp::SchemaParser schemaParser;\n  capnp::ParsedSchema parsedSchema;\n  kj::Vector<capnp::ConstSchema> topLevelConfigConstants;\n\n  kj::Own<void> configOwner;  // backing object for `config`, if it's not `schemaParser`.\n  kj::Maybe<config::Config::Reader> config;\n\n  kj::Vector<int> inheritedFds;\n\n  kj::Maybe<kj::String> testServicePattern;\n  kj::Maybe<kj::String> testEntrypointPattern;\n\n#ifdef WORKERD_USE_PERFETTO\n  kj::Maybe<kj::String> perfettoTraceDestination;\n  kj::Maybe<kj::String> perfettoTraceCategories;\n#endif\n\n  kj::Own<Server> server;\n\n  // This is a randomly-generated 128-bit number that identifies when a binary has been compiled\n  // with a specific config in order to run stand-alone.\n  static constexpr uint64_t COMPILED_MAGIC_SUFFIX[2] = {// The layout of such a binary is:\n    //\n    // - Binary executable data (copy of the Workers Runtime binary).\n    // - Padding to 8-byte boundary.\n    // - Cap'n-Proto-encoded config.\n    // - 8-byte size of config, counted in 8-byte words.\n    // - 16-byte magic number COMPILED_MAGIC_SUFFIX.\n\n    0xa69eda94d3cc02b5ull, 0xa3d977fdbf547d7full};\n\n  struct ExeInfo {\n    kj::String path;\n    kj::Own<const kj::ReadableFile> file;\n  };\n\n#if _WIN32\n  static kj::Maybe<ExeInfo> tryOpenExe(kj::Filesystem& fs, kj::StringPtr path) {\n    // TODO(bug): Like with Unix below, we should probably use native CreateFile() here, but it has\n    // sooooo many arguments, I don't want to deal with it.\n    auto parsedPath = fs.getCurrentPath().evalNative(path);\n    KJ_IF_SOME(file, fs.getRoot().tryOpenFile(parsedPath)) {\n      return ExeInfo{kj::str(path), kj::mv(file)};\n    }\n    return kj::none;\n  }\n#else\n  static kj::Maybe<ExeInfo> tryOpenExe(kj::Filesystem& fs, kj::StringPtr path) {\n    // Use open() and not fs.getRoot().tryOpenFile() because we probably want to use true kernel\n    // path resolution here, not KJ's logical path resolution.\n    int fd = open(path.cStr(), O_RDONLY);\n    if (fd < 0) {\n      return kj::none;\n    }\n    return ExeInfo{kj::str(path), kj::newDiskFile(kj::OwnFd(fd))};\n  }\n#endif\n\n  static kj::Maybe<ExeInfo> getExecFile(kj::ProcessContext& context, kj::Filesystem& fs) {\n#ifdef __GLIBC__\n    auto execfn = getauxval(AT_EXECFN);\n    if (execfn != 0) {\n      return tryOpenExe(fs, reinterpret_cast<const char*>(execfn));\n    }\n#endif\n\n#if __linux__\n    KJ_IF_SOME(link, fs.getRoot().tryReadlink(kj::Path({\"proc\", \"self\", \"exe\"}))) {\n      return tryOpenExe(fs, link);\n    }\n#endif\n\n#if __APPLE__\n    // https://astojanov.github.io/blog/2011/09/26/pid-to-absolute-path.html\n    pid_t pid = getpid();\n    char pathbuf[PROC_PIDPATHINFO_MAXSIZE];\n    if (proc_pidpath(pid, pathbuf, sizeof(pathbuf)) > 0) {\n      return tryOpenExe(fs, pathbuf);\n    }\n#endif\n\n#if _WIN32\n    wchar_t pathbuf[MAX_PATH];\n    int result = GetModuleFileNameW(NULL, pathbuf, MAX_PATH);\n    if (result > 0) {\n      auto decoded = kj::decodeWideString(kj::arrayPtr(pathbuf, result));\n      KJ_ASSERT(!decoded.hadErrors);\n      return tryOpenExe(fs, decoded);\n    }\n#endif\n\n    // TODO(beta): Fall back to searching $PATH.\n    return kj::none;\n  }\n\n  config::Config::Reader getConfig() {\n    KJ_IF_SOME(c, config) {\n      return c;\n    } else {\n      // The optional `<const-name>` parameter must not have been given -- otherwise we would have\n      // a non-null `config` by this point. See if we can infer the correct constant...\n      if (topLevelConfigConstants.empty()) {\n        context.exitError(\n            \"The config file does not define any top-level constants of type 'Config'.\");\n      } else if (topLevelConfigConstants.size() == 1) {\n        return config.emplace(topLevelConfigConstants[0].as<config::Config>());\n      } else {\n        auto names = KJ_MAP(cnst, topLevelConfigConstants) { return cnst.getShortDisplayName(); };\n        // TODO: this error message says \"you must specify which one to use\".\n        // This is not actually possible? Either fix the error message to say\n        // **how** to specify which config object to use or tell user to define\n        // exactly one top level Config constant.\n        context.exitError(kj::str(\n            \"The config file defines multiple top-level constants of type 'Config', so you must \"\n            \"specify which one to use. The options are: \",\n            kj::strArray(names, \", \")));\n      }\n    }\n  }\n\n  kj::Maybe<ExeInfo> exeInfo = getExecFile(context, *fs);\n\n  bool hadErrors = false;\n\n  void reportParsingError(kj::StringPtr file,\n      capnp::SchemaFile::SourcePos start,\n      capnp::SchemaFile::SourcePos end,\n      kj::StringPtr message) override {\n    if (start.line == end.line && start.column < end.column) {\n      context.error(kj::str(\n          file, \":\", start.line + 1, \":\", start.column + 1, \"-\", end.column + 1, \": \", message));\n    } else {\n      context.error(kj::str(file, \":\", start.line + 1, \":\", start.column + 1, \": \", message));\n    }\n\n    hadErrors = true;\n  }\n\n#if _WIN32\n  kj::Promise<void> waitForChanges(FileWatcher& watcher) {\n    KJ_UNIMPLEMENTED(\"Watching is not yet implemented on Windows\");\n  }\n#else\n  // Wait for the FileWatcher to report a change, and then wait a moment for changes to settle\n  // down, in case there's a bunch of changes all at once.\n  kj::Promise<void> waitForChanges(FileWatcher& watcher) {\n    co_await watcher.onChange();\n\n    // Saw our first change!\n\n    // Let the user know we saw the config change.\n    // We don't include a newline but rather a carriage return so that when the next\n    // line is written, this line disappears, to reduce noise.\n    // TODO(cleanup): Writing directly to stderr is super-hacky.\n    auto message = \"Noticed configuration change, reloading shortly...\\r\"_kjb;\n    kj::FdOutputStream(STDERR_FILENO).write(message);\n\n    static auto const waitForResult = [](kj::Promise<void> promise,\n                                          bool result = false) -> kj::Promise<bool> {\n      co_await promise;\n      co_return result;\n    };\n\n    for (;;) {\n      auto nextChange = waitForResult(watcher.onChange());\n      auto timeout =\n          waitForResult(io.provider->getTimer().afterDelay(500 * kj::MILLISECONDS), true);\n      bool sawTimeout = co_await nextChange.exclusiveJoin(kj::mv(timeout));\n\n      // If we timed out, we end the loop. If we didn't time out, then we must have seen yet\n      // another change, so we loop again with a new timeout.\n      if (sawTimeout) break;\n    }\n\n    co_return;\n  }\n#endif\n};\n\n}  // namespace\n}  // namespace workerd::server\n\nint main(int argc, char* argv[]) {\n  workerd::server::StructuredLoggingProcessContext context(argv[0]);\n\n#if !_WIN32\n  kj::UnixEventPort::captureSignal(SIGTERM);\n#endif\n  workerd::rust::cxx_integration::init();\n  workerd::server::CliMain mainObject(context, argv);\n\n#if defined(WORKERD_FUZZILLI) && defined(__linux__)\n  initSignalHandlers();\n#endif\n\n  return ::kj::runMainAndExit(context, mainObject.getMain(), argc, argv);\n}\n"
  },
  {
    "path": "src/workerd/server/workerd.capnp",
    "content": "# Copyright (c) 2017-2022 Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\n@0xe6afd26682091c01;\n# This file defines the schema for configuring the workerd runtime.\n#\n# A config file can be written as a `.capnp` file that imports this file and then defines a\n# constant of type `Config`. Alternatively, various higher-level tooling (e.g. wrangler) may\n# generate configs for you, outputting a binary Cap'n Proto file.\n#\n# To start a server with a config, do:\n#\n#     workerd serve my-config.capnp constantName\n#\n# You can also build a new self-contained binary which combines the `workerd` binary with your\n# configuration and all your source code:\n#\n#     workerd compile my-config.capnp constantName -o my-server-bin\n#\n# This binary can then be run stand-alone.\n#\n# A common theme in this configuration is capability-based design. We generally like to avoid\n# giving a Worker the ability to access external resources by name, since this makes it hard\n# to see and restrict what each Worker can access. Instead, the default is that a Worker has\n# access to no privileged resources at all, and you must explicitly declare \"bindings\" to give\n# it access to specific resources. A binding gives the Worker a JavaScript API object that points\n# to a specific resource. This means that by changing config alone, you can fully control which\n# resources an Worker connects to. (You can even disallow access to the public internet, although\n# public internet access is granted by default.)\n#\n# This config format is fairly powerful, allowing you to do things like define a TLS-terminating\n# reverse proxy server without using any actual JavaScript code. However, you should not be\n# afraid to fall back to code for anything the config cannot express, as Workers are very fast\n# to execute!\n\n# Any capnp files imported here must be:\n# 1. embedded using wd_cc_embed\n# 2. added to `tryImportBulitin` in workerd.c++ (grep for '\"/workerd/workerd.capnp\"').\nusing Cxx = import \"/capnp/c++.capnp\";\n$Cxx.namespace(\"workerd::server::config\");\n$Cxx.allowCancellation;\n\nstruct Config {\n  # Top-level configuration for a workerd instance.\n\n  services @0 :List(Service);\n  # List of named services defined by this server. These names are private; they are only used\n  # to refer to the services from elsewhere in this config file, as well as for logging and the\n  # like. Services are not reachable until you configure some way to make them reachable, such\n  # as via a Socket.\n  #\n  # If you do not define any service called \"internet\", one is defined implicitly, representing\n  # the ability to access public internet servers. An explicit definition would look like:\n  #\n  #     ( name = \"internet\",\n  #       network = (\n  #         allow = [\"public\"],   # Allows connections to publicly-routable addresses only.\n  #         tlsOptions = (trustBrowserCas = true)\n  #       )\n  #     )\n  #\n  # The \"internet\" service backs the global `fetch()` function in a Worker, unless that Worker's\n  # configuration specifies some other service using the `globalOutbound` setting.\n\n  sockets @1 :List(Socket);\n  # List of sockets on which this server will listen, and the services that will be exposed\n  # through them.\n\n  v8Flags @2 :List(Text);\n  # List of \"command-line\" flags to pass to V8, like \"--expose-gc\". We put these in the config\n  # rather than on the actual command line because for most use cases, managing these via the\n  # config file is probably cleaner and easier than passing on the actual CLI.\n  #\n  # WARNING: Use at your own risk. V8 flags can have all sorts of wild effects including completely\n  #   breaking everything. V8 flags also generally do not come with any guarantee of stability\n  #   between V8 versions. Most users should not set any V8 flags.\n\n  extensions @3 :List(Extension);\n  # Extensions provide capabilities to all workers. Extensions are usually prepared separately\n  # and are late-linked with the app using this config field.\n\n  autogates @4 :List(Text);\n  # A list of gates which are enabled.\n  # These are used to gate features/changes in workerd and in our internal repo. See the equivalent\n  # config definition in our internal repo for more details.\n\n  structuredLogging @5 :Bool = false;\n  # If true, logs will be emitted as JSON for structured logging.\n  # When false, logs use the traditional human-readable format.\n  # This affects the format of logs from KJ_LOG and exception reporting as well as js logs.\n  # This won't work for logs coming from service worker syntax workers with the old module registry.\n  # Note: This field is obsolete and deprecated. Use the logging struct instead.\n\n  logging @6 : LoggingOptions;\n  # Console and Stdio logging configuration options.\n}\n\nstruct LoggingOptions {\n  structuredLogging @0 :Bool = false;\n  # Override of top-level structured logging (only when true).\n  # If true, logs will be emitted as JSON for structured logging.\n  # When false, logs use the traditional human-readable format.\n  # This affects the format of logs from KJ_LOG and exception reporting as well as js logs.\n  # This won't work for logs coming from service worker syntax workers with the old module registry.\n\n  stdoutPrefix @1 :Text;\n  # Set a custom prefix for process.stdout. Defaults to \"stdout: \".\n\n  stderrPrefix @2 :Text;\n  # Set a custom prefix for process.stderr. Defaults to \"stderr: \".\n}\n\n# ========================================================================================\n# Sockets\n\nstruct Socket {\n  name @0 :Text;\n  # Each socket has a unique name which can be used on the command line to override the socket's\n  # address with `--socket-addr <name>=<addr>` or `--socket-fd <name>=<fd>`.\n\n  address @1 :Text;\n  # Address/port on which this socket will listen. Optional; if not specified, then you will be\n  # required to specify the socket on the command line with with `--socket-addr <name>=<addr>` or\n  # `--socket-fd <name>=<fd>`.\n  #\n  # Examples:\n  # - \"*:80\": Listen on port 80 on all local IPv4 and IPv6 interfaces.\n  # - \"1.2.3.4\": Listen on the specific IPv4 address on the default port for the protocol.\n  # - \"1.2.3.4:80\": Listen on the specific IPv4 address and port.\n  # - \"1234:5678::abcd\": Listen on the specific IPv6 address on the default port for the protocol.\n  # - \"[1234:5678::abcd]:80\": Listen on the specific IPv6 address and port.\n  # - \"unix:/path/to/socket\": Listen on a Unix socket.\n  # - \"unix-abstract:name\": On Linux, listen on the given \"abstract\" Unix socket name.\n  # - \"example.com:80\": Perform a DNS lookup to determine the address, and then listen on it. If\n  #     this resolves to multiple addresses, listen on all of them.\n  #\n  # (These are the formats supported by KJ's parseAddress().)\n\n  union {\n    http @2 :HttpOptions;\n    https :group {\n      options @3 :HttpOptions;\n      tlsOptions @4 :TlsOptions;\n    }\n\n    # TODO(someday): TCP, TCP proxy, SMTP, Cap'n Proto, ...\n  }\n\n  service @5 :ServiceDesignator;\n  # Service name which should handle requests on this socket.\n\n  # TODO(someday): Support mapping different hostnames to different services? Or should that be\n  #   done strictly via JavaScript?\n}\n\n# ========================================================================================\n# Services\n\nstruct Service {\n  # Defines a named service. Each server has a list of named services. The names are private,\n  # used to refer to the services within this same config file.\n\n  name @0 :Text;\n  # Name of the service. Used only to refer to the service from elsewhere in the config file.\n  # Services are not accessible unless you explicitly configure them to be, such as through a\n  # `Socket` or through a binding from another Worker.\n\n  union {\n    unspecified @1 :Void;\n    # (This catches when someone forgets to specify one of the union members. Do not set this.)\n\n    worker @2 :Worker;\n    # A Worker!\n\n    network @3 :Network;\n    # A service that implements access to a network. fetch() requests are routed according to\n    # the URL hostname.\n\n    external @4 :ExternalServer;\n    # A service that forwards all requests to a specific remote server. Typically used to\n    # connect to a back-end server on your internal network.\n\n    disk @5 :DiskDirectory;\n    # An HTTP service backed by a directory on disk, supporting a basic HTTP GET/PUT. Generally\n    # not intended to be exposed directly to the internet; typically you want to bind this into\n    # a Worker that adds logic for setting Content-Type and the like.\n  }\n\n  # TODO(someday): Allow defining a list of middlewares to stack on top of the service. This would\n  #   be a list of Worker names, where each Worker must have a binding called `next`. This\n  #   implicitly creates an inherited worker that wraps this service, with the `next` binding\n  #   pointing to the service itself (or to the next middleware in the stack).\n}\n\nstruct ServiceDesignator {\n  # A reference to a service from elsewhere in the config file, e.g. from a service binding in a\n  # Worker.\n  #\n  # In the case that only `name` needs to be specified, then you can provide a raw string wherever\n  # `ServiceDesignator` is needed. Cap'n proto automatically assumes the string is intended to be\n  # the value for `name`, since that is the first field. In other words, if you would otherwise\n  # write something like:\n  #\n  #     bindings = [(service = (name = \"foo\"))]\n  #\n  # You can write this instead, which is equivalent:\n  #\n  #     bindings = [(service = \"foo\")]\n\n  name @0 :Text;\n  # Name of the service in the Config.services list.\n\n  entrypoint @1 :Text;\n  # A modules-syntax Worker can export multiple named entrypoints. `export default {` specifies\n  # the default entrypoint, whereas `export let foo = {` defines an entrypoint named `foo`. If\n  # `entrypoint` is specified here, it names an alternate entrypoint to use on the target worker,\n  # otherwise the default is used.\n\n  props :union {\n    # Value to provide in `ctx.props` in the target worker.\n\n    empty @2 :Void;\n    # Empty object. (This is the default.)\n\n    json @3 :Text;\n    # A JSON-encoded value.\n  }\n\n  # TODO(someday): Options to specify which event types are allowed.\n  # TODO(someday): Allow adding an outgoing middleware stack here (see TODO in Service, above).\n}\n\nstruct Worker {\n  union {\n    modules @0 :List(Module);\n    # The Worker is composed of ES modules that may import each other. The first module in the list\n    # is the main module, which exports event handlers.\n\n    serviceWorkerScript @1 :Text;\n    # The Worker is composed of one big script that uses global `addEventListener()` to register\n    # event handlers.\n    #\n    # The value of this field is the raw source code. When using Cap'n Proto text format, use the\n    # `embed` directive to read the code from an external file:\n    #\n    #     serviceWorkerScript = embed \"worker.js\"\n\n    inherit @2 :Text;\n    # Inherit the configuration of some other Worker by its service name. This Worker is a clone\n    # of the other worker, but various settings can be modified:\n    # * `bindings`, if specified, overrides specific named bindings. (Each binding listed in the\n    #   derived worker must match the name and type of some binding in the inherited worker.)\n    # * `globalOutbound`, if non-null, overrides the one specified in the inherited worker.\n    # * `compatibilityDate` and `compatibilityFlags` CANNOT be modified; they must be null.\n    # * If the inherited worker defines durable object namespaces, then the derived worker must\n    #   specify `durableObjectStorage` to specify where its instances should be stored. Each\n    #   devived worker receives its own namespace of objects. `durableObjectUniqueKeyModifier`\n    #   must also be specified by derived workers.\n    #\n    # This can be useful when you want to run the same Worker in multiple configurations or hooked\n    # up to different back-ends. Note that all derived workers run in the same isolate as the\n    # base worker; they differ in the content of the `env` object passed to them, which contains\n    # the bindings. (When using service workers syntax, the global scope contains the bindings;\n    # in this case each derived worker runs in its own global scope, though still in the same\n    # isolate.)\n  }\n\n  struct Module {\n    name @0 :Text;\n    # Name (or path) used to import the module.\n\n    union {\n      esModule @1 :Text;\n      # An ES module file with imports and exports.\n      #\n      # As with `serviceWorkerScript`, above, the value is the raw source code.\n\n      commonJsModule @2 :Text;\n      # A common JS module, using require().\n\n      text @3 :Text;\n      # A raw text blob. Importing this will produce a string with the value.\n\n      data @4 :Data;\n      # A raw data blob. Importing this will produce an ArrayBuffer with the value.\n\n      wasm @5 :Data;\n      # A Wasm module. The value is a compiled binary Wasm module file. Importing this will produce\n      # a `WebAssembly.Module` object, which you can then instantiate.\n\n      json @6 :Text;\n      # Importing this will produce the result of parsing the given text as JSON.\n\n      obsolete @7 :Text;\n      # This position used to be the nodeJsCompatModule type that has now been\n      # obsoleted.\n\n      pythonModule @8 :Text;\n      # A Python module. All bundles containing this value type are converted into a JS/WASM Worker\n      # Bundle prior to execution.\n\n      pythonRequirement @9 :Text;\n      # A Python package that is required by this bundle. The package must be supported by\n      # Pyodide (https://pyodide.org/en/stable/usage/packages-in-pyodide.html). All packages listed\n      # will be installed prior to the execution of the worker.\n      #\n      # The value of this field is ignored and should always be an empty string. Only the module\n      # name matters. The field should have been declared `Void`, but it's difficult to change now.\n    }\n\n    namedExports @10 :List(Text);\n    # For commonJsModule modules, this is a list of named exports that the\n    # module expects to be exported once the evaluation is complete.\n    #\n    # (`commonJsModule` should have been a group containing the body and `namedExports`, but it's\n    # too late to change now.)\n  }\n\n  compatibilityDate @3 :Text;\n  compatibilityFlags @4 :List(Text);\n  # See: https://developers.cloudflare.com/workers/platform/compatibility-dates/\n  #\n  # `compatibilityDate` must be specified, unless the Worker inhits from another worker, in which\n  # case it must not be specified. `compatibilityFlags` can optionally be specified when\n  # `compatibilityDate` is specified.\n\n  bindings @5 :List(Binding);\n  # List of bindings, which give the Worker access to external resources and configuration\n  # settings.\n  #\n  # For Workers using ES modules syntax, the bindings are delivered via the `env` object. For\n  # service workers syntax, each binding shows up as a global variable.\n\n  struct Binding {\n    name @0 :Text;\n\n    union {\n      unspecified @1 :Void;\n      # (This catches when someone forgets to specify one of the union members. Do not set this.)\n\n      parameter :group {\n        # Indicates that the Worker requires a binding of the given type, but it won't be specified\n        # here. Another Worker can inherit this Worker and fill in this binding.\n\n        type @2 :Type;\n        # Expected type of this parameter.\n\n        optional @3 :Bool;\n        # If true, this binding is optional. Derived workers need not specify it, in which case\n        # the binding won't be present in the environment object passed to the worker.\n        #\n        # When a Worker has any non-optional parameters that haven't been filled in, then it can\n        # only be used for inheritance; it cannot be invoked directly.\n      }\n\n      text @4 :Text;\n      # A string.\n\n      data @5 :Data;\n      # An ArrayBuffer.\n\n      json @6 :Text;\n      # A value parsed from JSON.\n\n      wasmModule @7 :Data;\n      # A WebAssembly module. The binding will be an instance of `WebAssembly.Module`. Only\n      # supported when using Service Workers syntax.\n      #\n      # DEPRECATED: Please switch to ES modules syntax instead, and embed Wasm modules as modules.\n\n      cryptoKey @8 :CryptoKey;\n      # A CryptoKey instance, for use with the WebCrypto API.\n      #\n      # Note that by setting `extractable = false`, you can prevent the Worker code from accessing\n      # or leaking the raw key material; it will only be able to use the key to perform WebCrypto\n      # operations.\n\n      service @9 :ServiceDesignator;\n      # Binding to a named service (possibly, a worker).\n\n      durableObjectClass @26 :ServiceDesignator;\n      # A Durable Object class binding, without an actual storage namespace. This can be used to\n      # implement a facet.\n\n      durableObjectNamespace @10 :DurableObjectNamespaceDesignator;\n      # Binding to the durable object namespace implemented by the given class.\n      #\n      # In the common case that this refers to a class in the same Worker, you can specify just\n      # a string, like:\n      #\n      #     durableObjectNamespace = \"MyClass\"\n\n      kvNamespace @11 :ServiceDesignator;\n      # A KV namespace, implemented by the named service. The Worker sees a KvNamespace-typed\n      # binding. Requests to the namespace will be converted into HTTP requests targeting the\n      # given service name.\n\n      r2Bucket @12 :ServiceDesignator;\n      r2Admin @13 :ServiceDesignator;\n      # R2 bucket and admin API bindings. Similar to KV namespaces, these turn operations into\n      # HTTP requests aimed at the named service.\n\n      wrapped @14 :WrappedBinding;\n      # Wraps a collection of inner bindings in a common api functionality.\n\n      queue @15 :ServiceDesignator;\n      # A Queue binding, implemented by the named service. Requests to the\n      # namespace will be converted into HTTP requests targeting the given\n      # service name.\n\n      fromEnvironment @16 :Text;\n      # Takes the value of an environment variable from the system. The value specified here is\n      # the name of a system environment variable. The value of the binding is obtained by invoking\n      # `getenv()` with that name. If the environment variable isn't set, the binding value is\n      # `null`.\n\n      analyticsEngine @17 :ServiceDesignator;\n      # A binding for Analytics Engine. Allows workers to store information through Analytics Engine Events.\n      # workerd will forward AnalyticsEngineEvents to designated service in the body of HTTP requests\n      # This binding is subject to change and requires the `--experimental` flag\n\n      hyperdrive :group {\n        designator @18 :ServiceDesignator;\n        database @19 :Text;\n        user @20 :Text;\n        password @21 :Text;\n        scheme @22 :Text;\n      }\n      # A binding for Hyperdrive. Allows workers to use Hyperdrive caching & pooling for Postgres\n      # databases.\n\n      unsafeEval @23 :Void;\n      # A simple binding that enables access to the UnsafeEval API.\n\n      memoryCache :group {\n        # A binding representing access to an in-memory cache.\n\n        id @24 :Text;\n        # The identifier associated with this cache. Any number of isolates\n        # can access the same in-memory cache (within the same process), and\n        # each worker may use any number of in-memory caches.\n\n        limits @25 :MemoryCacheLimits;\n      }\n\n      workerLoader :group {\n        # A binding representing the ability to dynamically load Workers from code presented at\n        # runtime.\n        #\n        # A Worker loader is not just a function that loads a Worker, but also serves as a\n        # cache of Workers, automatically unloading Workers that are not in use. To that end, each\n        # Worker must have a name, and if a Worker with that name already exists, it'll be reused.\n\n        id @27 :Text;\n        # Optional: The identifier associated with this Worker loader. Multiple Workers can bind to\n        # the same ID in order to access the same loader, so that if they request the same name\n        # from it, they'll end up sharing the same loaded Worker.\n        #\n        # (If omitted, the binding will not share a cache with any other binding.)\n      }\n\n      workerdDebugPort @28 :Void;\n      # A binding that provides a connect() method to dynamically connect to any workerd\n      # instance's debug port. This allows dynamic access to worker entrypoints via the\n      # WorkerdDebugPort RPC interface.\n      #\n      # Usage: const client = await env.DEBUG_PORT.connect(\"localhost:1234\");\n      #        const fetcher = await client.getEntrypoint(\"service\", \"entrypoint\");\n      #\n      # This is a workerd-only API intended for local development and testing.\n\n      # TODO(someday): dispatch, other new features\n    }\n\n    struct Type {\n      # Specifies the type of a parameter binding.\n\n      union {\n        unspecified @0 :Void;\n        # (This catches when someone forgets to specify one of the union members. Do not set this.)\n\n        text @1 :Void;\n        data @2 :Void;\n        json @3 :Void;\n        wasm @4 :Void;\n        cryptoKey @5 :List(CryptoKey.Usage);\n        service @6 :Void;\n        durableObjectNamespace @7 :Void;\n        kvNamespace @8 :Void;\n        r2Bucket @9 :Void;\n        r2Admin @10 :Void;\n        queue @11 :Void;\n        analyticsEngine @12 : Void;\n        hyperdrive @13: Void;\n        durableObjectClass @14: Void;\n        workerdDebugPort @15: Void;\n      }\n    }\n\n    struct DurableObjectNamespaceDesignator {\n      # The type of a Durable Object namespace binding.\n\n      className @0 :Text;\n      # Exported class name that implements the Durable Object.\n\n      serviceName @1 :Text;\n      # The service name of the worker that defines this class. If omitted, the current worker\n      # is assumed.\n      #\n      # Use of this field is discouraged. Instead, when accessing a different Worker's Durable\n      # Objects, specify a `service` binding to that worker, and have the worker implement an\n      # appropriate API.\n      #\n      # (This is intentionally not a ServiceDesignator because you cannot choose an alternate\n      # entrypoint here; the class name IS the entrypoint.)\n    }\n\n    struct CryptoKey {\n      # Parameters to crypto.subtle.importKey().\n\n      union {\n        raw @0 :Data;\n        hex @1 :Text;\n        base64 @2 :Text;\n        # Raw key material, possibly hex or base64-encoded. Use this for symmetric keys.\n        #\n        # Hint: `raw` would typically be used with Cap'n Proto's `embed` syntax to embed an\n        # external binary key file. `hex` or `base64` could do that too but can also be specified\n        # inline.\n\n        pkcs8 @3 :Text;\n        # Private key in PEM-encoded PKCS#8 format.\n\n        spki @4 :Text;\n        # Public key in PEM-encoded SPKI format.\n\n        jwk @5 :Text;\n        # Key in JSON format.\n      }\n\n      algorithm :union {\n        # Value for the `algorithm` parameter.\n\n        name @6 :Text;\n        # Just a name, like `AES-GCM`.\n\n        json @7 :Text;\n        # An object, encoded here as JSON.\n      }\n\n      extractable @8 :Bool = false;\n      # Is the Worker allowed to export this key to obtain the underlying key material? Setting\n      # this false ensures that the key cannot be leaked by errant JavaScript code; the key can\n      # only be used in WebCrypto operations.\n\n      usages @9 :List(Usage);\n      # What operations is this key permitted to be used for?\n\n      enum Usage {\n        encrypt @0;\n        decrypt @1;\n        sign @2;\n        verify @3;\n        deriveKey @4;\n        deriveBits @5;\n        wrapKey @6;\n        unwrapKey @7;\n      }\n    }\n\n    struct MemoryCacheLimits {\n      maxKeys @0 :UInt32;\n      maxValueSize @1 :UInt32;\n      maxTotalValueSize @2 :UInt64;\n    }\n\n    struct WrappedBinding {\n      # A binding that wraps a group of (lower-level) bindings in a common API.\n\n      moduleName @0 :Text;\n      # Wrapper module name.\n      # The module must be an internal one (provided by extension or registered in the c++ code).\n      # Module will be instantitated during binding initialization phase.\n\n      entrypoint @1 :Text = \"default\";\n      # Module needs to export a function with a given name (default export gets \"default\" name).\n      # The function needs to accept a single `env` argument - a dictionary with inner bindings.\n      # Function will be invoked during initialization phase and its return value will be used as\n      # resulting binding value.\n\n      innerBindings @2 :List(Binding);\n      # Inner bindings that will be created and passed in the env dictionary.\n      # These bindings shall be used to implement end-user api, and are not available to the\n      # binding consumers unless \"re-exported\" in wrapBindings function.\n    }\n  }\n\n  globalOutbound @6 :ServiceDesignator = \"internet\";\n  # Where should the global \"fetch\" go to? The default is the service called \"internet\", which\n  # should usually be configured to talk to the public internet.\n\n  cacheApiOutbound @11 :ServiceDesignator;\n  # Where should cache API (i.e. caches.default and caches.open(...)) requests go?\n\n  durableObjectNamespaces @7 :List(DurableObjectNamespace);\n  # List of durable object namespaces in this Worker.\n\n  struct DurableObjectNamespace {\n    className @0 :Text;\n    # Exported class name that implements the Durable Object.\n    #\n    # Changing the class name will not break compatibility with existing storage, so long as\n    # `uniqueKey` stays the same.\n\n    union {\n      uniqueKey @1 :Text;\n      # A unique, stable ID associated with this namespace. This could be a  GUID, or any other\n      # string which does not appear anywhere else in the world.\n      #\n      # This string is used to ensure that objects of this class have unique identifiers distinct\n      # from objects of any other class. Object IDs are cryptographically derived from `uniqueKey`\n      # and validated against it. It is impossible to guess or forge a valid object ID without\n      # knowing the `uniqueKey`. Hence, if you keep the key secret, you can prevent anyone from\n      # forging IDs. However, if you don't care if users can forge valid IDs, then it's not a big\n      # deal if the key leaks.\n      #\n      # DO NOT LOSE this key, otherwise it may be difficult or impossible to recover stored data.\n\n      ephemeralLocal @2 :Void;\n      # Instances of this class are ephemeral -- they have no durable storage at all. The\n      # `state.storage` API will not be present. Additionally, this namespace will allow arbitrary\n      # strings as IDs. There are no `idFromName()` nor `newUniqueId()` methods; `get()` takes any\n      # string as a parameter.\n      #\n      # Ephemeral objects are NOT globally unique, only \"locally\" unique, for some definition of\n      # \"local\". For example, on Cloudflare's network, these objects are unique per-colo.\n      #\n      # WARNING: Cloudflare Workers currently limits this feature to Cloudflare-internal users\n      #   only, because using them correctly requires deep understanding of Cloudflare network\n      #   topology. We're working on something better for public consuption. Until then for\n      #   \"ephemeral\" use cases we recommend using regular durable objects and just not storing\n      #   anything. An object that hasn't stored anything will not consume any storage space on\n      #   disk.\n    }\n\n    preventEviction @3 :Bool;\n    # By default, Durable Objects are evicted after 10 seconds of inactivity, and expire 70 seconds\n    # after all clients have disconnected. Some applications may want to keep their Durable Objects\n    # pinned to memory forever, so we provide this flag to change the default behavior.\n    #\n    # Note that this is only supported in Workerd; production Durable Objects cannot toggle eviction.\n\n    enableSql @4 :Bool;\n    # Whether or not Durable Objects in this namespace can use the `storage.sql` API to execute SQL\n    # queries.\n    #\n    # workerd uses SQLite to back all Durable Objects, but the SQL API is hidden by default to\n    # emulate behavior of traditional DO namespaces on Cloudflare that aren't SQLite-backed. This\n    # flag should be enabled when testing code that will run on a SQLite-backed namespace.\n\n    container @5 :ContainerOptions;\n    # If present, Durable Objects in this namespace have attached containers.\n    # workerd will talk to the configured container engine to start containers for each\n    # Durable Object based on the given image. The Durable Object can access the container via the\n    # ctx.container API. TODO(CloudChamber): add link to docs.\n\n    struct ContainerOptions {\n      imageName @0 :Text;\n      # Image name to be used to create the container using supported provider.\n      # By default, we pull the \"latest\" tag of this image.\n    }\n  }\n\n  durableObjectUniqueKeyModifier @8 :Text;\n  # Additional text which is hashed together with `DurableObjectNamespace.uniqueKey`. When using\n  # worker inheritance, each derived worker must specify a unique modifier to ensure that its\n  # Durable Object instances have unique IDs from all other workers inheriting the same parent.\n  #\n  # DO NOT LOSE this value, otherwise it may be difficult or impossible to recover stored data.\n\n  durableObjectStorage :union {\n    # Specifies where this worker's Durable Objects are stored.\n\n    none @9 :Void;\n    # Default. The worker has no Durable Objects. `durableObjectNamespaces` must be empty, or\n    # define all namespaces as `ephemeralLocal`, or this must be an abstract worker (meant to be\n    # inherited by other workers, who will specify `durableObjectStorage`).\n\n    inMemory @10 :Void;\n    # The `state.storage` API stores in-memory only. All stored data will persist for the\n    # lifetime of the process, but will be lost upon process exit.\n    #\n    # Individual objects will still shut down when idle as normal -- only data stored with the\n    # `state.storage` interface is persistent for the lifetime of the process.\n    #\n    # This mode is intended for local testing purposes.\n\n    localDisk @12 :Text;\n    # ** EXPERIMENTAL; SUBJECT TO BACKWARDS-INCOMPATIBLE CHANGE **\n    #\n    # Durable Object data will be stored in a directory on local disk. This field is the name of\n    # a service, which must be a DiskDirectory service. For each Durable Object class, a\n    # subdirectory will be created using `uniqueKey` as the name. Within the directory, one or\n    # more files are created for each object, with names `<id>.<ext>`, where `.<ext>` may be any of\n    # a number of different extensions depending on the storage mode. (Currently, the main storage\n    # is a file with the extension `.sqlite`, and in certain situations extra files with the\n    # extensions `.sqlite-wal`, and `.sqlite-shm` may also be present.)\n  }\n\n  # TODO(someday): Support distributing objects across a cluster. At present, objects are always\n  #   local to one instance of the runtime.\n\n  moduleFallback @13 :Text;\n\n  tails @14 :List(ServiceDesignator);\n  # List of tail worker services that should receive tail events for this worker.\n  # See: https://developers.cloudflare.com/workers/observability/logs/tail-workers/\n\n  streamingTails @15 :List(ServiceDesignator);\n  # List of streaming tail worker services that should receive tail events for this worker.\n  # NOTE: This will be deleted in a future refactor, do not depend on this.\n\n  containerEngine :union {\n    none @16 :Void;\n    # No container engine configured. Container operations will not be available.\n\n    localDocker @17 :DockerConfiguration;\n    # Use local Docker daemon for container operations.\n    # Only used for local development and testing purposes.\n  }\n\n  struct DockerConfiguration {\n    socketPath @0 :Text;\n    # Path to the Docker socket.\n\n    containerEgressInterceptorImage @1 :Text;\n    # Docker image name for the container egress interceptor sidecar.\n    # This sidecar intercepts outbound traffic from containers and routes it\n    # through workerd for egress mappings (setEgressHttp bindings).\n    # You can find this image in repositories like DockerHub: https://hub.docker.com/r/cloudflare/proxy-everything\n  }\n}\n\nstruct ExternalServer {\n  # Describes the ability to talk to a specific server, typically a back-end server available\n  # on the internal network.\n  #\n  # When a Worker contains a service binding that points to an ExternalServer, *all* fetch()\n  # calls on that binding will be delivered to that server, regardless of whether the hostname\n  # or protocol specified in the URL actually match the hostname or protocol used by the actual\n  # server. Typically, a Worker implementing a reverse proxy would use this to forward a request\n  # to a back-end application server. Such a back-end typically does not have a real public\n  # hostname, since it is only reachable through the proxy, but the requests forwarded to it will\n  # keep the hostname that was on the original request.\n  #\n  # Note that this also implies that regardless of whether the original URL was http: or https:,\n  # the request will be delivered to the target server using the protocol specified below. A\n  # header like `X-Forwarded-Proto` can be used to pass along the original protocol; see\n  # `HttpOptions`.\n\n  address @0 :Text;\n  # Address/port of the server. Optional; if not specified, then you will be required to specify\n  # the address on the command line with with `--external-addr <name>=<addr>`.\n  #\n  # Examples:\n  # - \"1.2.3.4\": Connect to the given IPv4 address on the protocol's default port.\n  # - \"1.2.3.4:80\": Connect to the given IPv4 address and port.\n  # - \"1234:5678::abcd\": Connect to the given IPv6 address on the protocol's default port.\n  # - \"[1234:5678::abcd]:80\": Connect to the given IPv6 address and port.\n  # - \"unix:/path/to/socket\": Connect to the given Unix Domain socket by path.\n  # - \"unix-abstract:name\": On Linux, connect to the given \"abstract\" Unix socket name.\n  # - \"example.com:80\": Perform a DNS lookup to determine the address, and then connect to it.\n  #\n  # (These are the formats supported by KJ's parseAddress().)\n\n  union {\n    http @1 :HttpOptions;\n    # Talk to the server over unencrypted HTTP.\n\n    https :group {\n      # Talk to the server over encrypted HTTPS.\n\n      options @2 :HttpOptions;\n      tlsOptions @3 :TlsOptions;\n\n      certificateHost @4 :Text;\n      # If present, expect the host to present a certificate authenticating it as this hostname.\n      # If `certificateHost` is not provided, then the certificate is checked against `address`.\n    }\n\n    tcp :group {\n      # Connect to the server over raw TCP. Bindings to this service will only support the\n      # `connect()` method; `fetch()` will throw an exception.\n      tlsOptions @5 :TlsOptions;\n      certificateHost @6 :Text;\n    }\n\n    # TODO(someday): Cap'n Proto RPC\n  }\n}\n\nstruct Network {\n  # Describes the ability to talk to a network.\n  #\n  # This is commonly used to define the \"internet\" service which is the default `globalOutbound`\n  # for all Workers. To prevent SSRF, by default Workers will not be permitted to reach internal\n  # network addresses using global fetch(). It's recommended that you create ExternalServer\n  # bindings instead to grant access to specific servers. However, if you really want to, you\n  # can configure a service that grants arbitrary internal network access, like:\n  #\n  #     ( name = \"internalNetwork\",\n  #       network = (\n  #         allow = [\"public\", \"private\"],\n  #       )\n  #     )\n\n  allow @0 :List(Text) = [\"public\"];\n  deny @1 :List(Text);\n  # Specifies which network addresses the Worker will be allowed to connect to, e.g. using fetch().\n  # The default allows publicly-routable IP addresses only, in order to prevent SSRF attacks.\n  #\n  # The allow and deny lists specify network blocks in CIDR notation (IPv4 and IPv6), such as\n  # \"192.0.2.0/24\" or \"2001:db8::/32\". Traffic will be permitted as long as the address\n  # matches at least one entry in the allow list and none in the deny list.\n  #\n  # In addition to IPv4 and IPv6 CIDR notation, several special strings may be specified:\n  # - \"private\": Matches network addresses that are reserved by standards for private networks,\n  #   such as \"10.0.0.0/8\" or \"192.168.0.0/16\". This is a superset of \"local\".\n  # - \"public\": Opposite of \"private\".\n  # - \"local\": Matches network addresses that are defined by standards to only be accessible from\n  #   the local machine, such as \"127.0.0.0/8\" or Unix domain addresses.\n  # - \"network\": Opposite of \"local\".\n  # - \"unix\": Matches all Unix domain socket addresses. (In the future, we may support specifying a\n  #   glob to narrow this to specific paths.)\n  # - \"unix-abstract\": Matches Linux's \"abstract unix domain\" addresses. (In the future, we may\n  #   support specifying a glob.)\n  #\n  # In the case that the Worker specifies a DNS hostname rather than a raw address, these rules are\n  # used to filter the addresses returned by the lookup. If none of the returned addresses turn\n  # out to be permitted, then the system will behave as if the DNS entry did not exist.\n  #\n  # (The above is exactly the format supported by kj::Network::restrictPeers().)\n\n  tlsOptions @2 :TlsOptions;\n}\n\nstruct DiskDirectory {\n  # Configures access to a directory on disk. This is a type of service which will expose an HTTP\n  # interface to the directory content.\n  #\n  # This is very bare-bones, generally not suitable for serving a web site on its own. In\n  # particular, no attempt is made to guess the `Content-Type` header. You normally would wrap\n  # this in a Worker that fills in the metadata in the way you want.\n  #\n  # A GET request targeting a directory (rather than a file) will return a basic JSAN directory\n  # listing like:\n  #\n  #     [{\"name\":\"foo\",\"type\":\"file\"},{\"name\":\"bar\",\"type\":\"directory\"}]\n  #\n  # Possible \"type\" values are \"file\", \"directory\", \"symlink\", \"blockDevice\", \"characterDevice\",\n  # \"namedPipe\", \"socket\", \"other\".\n  #\n  # `Content-Type` will be `application/octet-stream` for files or `application/json` for a\n  # directory listing. Files will have a `Content-Length` header, directories will not. Symlinks\n  # will be followed (but there is intentionally no way to create one, even if `writable` is\n  # `true`), and treated according to the type of file they point to. The other inode types cannot\n  # be opened; trying to do so will produce a \"406 Not Acceptable\" error (on the theory that there\n  # is no acceptable format for these, regardless of what the client says it accepts).\n  #\n  # `HEAD` requests are properly optimized to perform a stat() without actually opening the file.\n\n  path @0 :Text;\n  # The filesystem path of the directory. If not specified, then it must be specified on the\n  # command line with `--directory-path <service-name>=<path>`.\n  #\n  # Relative paths are interpreted relative to the current directory where the server is executed,\n  # NOT relative to the config file. So, you should usually use absolute paths in the config file.\n\n  writable @1 :Bool = false;\n  # Whether to support PUT requests for writing. A PUT will write to a temporary file which\n  # is atomically moved into place upon successful completion of the upload. Parent directories are\n  # created as needed.\n\n  allowDotfiles @2 :Bool = false;\n  # Whether to allow access to files and directories whose name starts with '.'. These are made\n  # inaccessible by default since they very often store metadata that is not meant to be served,\n  # e.g. a git repository or an `.htaccess` file.\n  #\n  # Note that the special links \".\" and \"..\" will never be accessible regardless of this setting.\n}\n\n# ========================================================================================\n# Protocol options\n\nstruct HttpOptions {\n  # Options for using HTTP (as a client or server). In particular, this specifies behavior that is\n  # important in the presence of proxy servers, whether forward or reverse.\n\n  style @0 :Style = host;\n\n  enum Style {\n    host @0;\n    # Normal HTTP. The request line contains only the path, and the separate `Host` header\n    # specifies the hostname.\n\n    proxy @1;\n    # HTTP proxy protocol. The request line contains a full URL instead of a path. No `Host`\n    # header is required. This is the protocol used by HTTP forward proxies. This allows you to\n    # implement such a proxy as a Worker.\n  }\n\n  forwardedProtoHeader @1 :Text;\n  # If specified, then when the given header is present on a request, it specifies the protocol\n  # (\"http\" or \"https\") that was used by the original client. The request URL reported to the\n  # Worker will reflect this protocol. Otherwise, the URL will reflect the actual physical protocol\n  # used by the server in receiving the request.\n  #\n  # This option is useful when this server sits behind a reverse proxy that performs TLS\n  # termination. Typically such proxies forward the original protocol in a header named something\n  # like \"X-Forwarded-Proto\".\n  #\n  # This setting is ignored when `style` is `proxy`.\n\n  cfBlobHeader @2 :Text;\n  # If set, then the `request.cf` object will be encoded (as JSON) into / parsed from the header\n  # with this name. Otherwise, it will be discarded on send / `undefined` on receipt.\n\n  injectRequestHeaders @3 :List(Header);\n  # List of headers which will be automatically injected into all requests. This can be used\n  # e.g. to add an authorization token to all requests when using `ExternalServer`. It can also\n  # apply to incoming requests received on a `Socket` to modify the headers that will be delivered\n  # to the app. Any existing header with the same name is removed.\n\n  injectResponseHeaders @4 :List(Header);\n  # Same as `injectRequestHeaders` but for responses.\n\n  struct Header {\n    name @0 :Text;\n    # Case-insensitive.\n\n    value @1 :Text;\n    # If null, the header will be removed.\n  }\n\n  capnpConnectHost @5 :Text;\n  # A CONNECT request for this host+port will be treated as a request to form a Cap'n Proto RPC\n  # connection. The server will expose a WorkerdBootstrap as the bootstrap interface, allowing\n  # events to be delivered to the target worker via capnp. Clients will use capnp for non-HTTP\n  # event types (especially JSRPC).\n\n  # TODO(someday): When we support TCP, include an option to deliver CONNECT requests to the\n  #   TCP handler.\n}\n\nstruct TlsOptions {\n  # Options that apply when using TLS. Can apply on either the client or the server side, depending\n  # on the context.\n  #\n  # This is based on KJ's TlsContext::Options.\n\n  keypair @0 :Keypair;\n  # The default private key and certificate to use. Optional when acting as a client.\n\n  struct Keypair {\n    privateKey @0 :Text;\n    # Private key in PEM format. Supports PKCS8 keys as well as \"traditional format\" RSA and DSA\n    # keys.\n    #\n    # Remember that you can use Cap'n Proto's `embed` syntax to reference an external file.\n\n    certificateChain @1 :Text;\n    # Certificate chain in PEM format. A chain can be constructed by concatenating multiple\n    # PEM-encoded certificates, starting with the leaf certificate.\n  }\n\n  # TODO(someday): Support SNI-based keypair selection? Is a hostname -> keypair map good enough?\n  #   Does it need to support wildcards? Maybe we should just let you provide a pile of certs and\n  #   we can figure out which hosts each one matches?\n\n  requireClientCerts @1 :Bool = false;\n  # If true, then when acting as a server, incoming connections will be rejected unless they bear\n  # a certificate signed by one of the trusted CAs.\n  #\n  # Typically, when using this, you'd set `trustBrowserCas = false` and list a specific private CA\n  # in `trustedCertificates`.\n\n  trustBrowserCas @2 :Bool = false;\n  # If true, trust certificates which are signed by one of the CAs that browsers normally trust.\n  # You should typically set this true when talking to the public internet, but you may want to\n  # set it false when talking to servers on your internal network.\n\n  trustedCertificates @3 :List(Text);\n  # Additional CA certificates to trust, in PEM format. Remember that you can use Cap'n Proto's\n  # `embed` syntax to read the certificates from other files.\n\n  minVersion @4 :Version = goodDefault;\n  # Minimum TLS version that will be allowed. Generally you should not override this unless you\n  # have unusual backwards-compatibility needs.\n\n  enum Version {\n    goodDefault @0;\n    # A good default chosen by the code maintainers. May change over time.\n\n    ssl3 @1;\n    tls1Dot0 @2;\n    tls1Dot1 @3;\n    tls1Dot2 @4;\n    tls1Dot3 @5;\n  }\n\n  cipherList @5 :Text;\n  # OpenSSL cipher list string. The default is a curated list designed to be compatible with\n  # almost all software in current use (specifically, based on Mozilla's \"intermediate\"\n  # recommendations). The defaults will change in future versions of this software to account\n  # for the latest cryptanalysis.\n  #\n  # Generally you should only specify your own `cipherList` if:\n  # - You have extreme backwards-compatibility needs and wish to enable obsolete and/or broken\n  #   algorithms.\n  # - You need quickly to disable an algorithm recently discovered to be broken.\n}\n\n# ========================================================================================\n# Extensions\n\nstruct Extension {\n  # Additional capabilities for workers.\n\n  modules @0 :List(Module);\n  # List of javascript modules provided by the extension.\n  # These modules can either be imported directly as user-level api (if not marked internal)\n  # or used to define more complicated workerd constructs such as wrapped bindings and events.\n\n  struct Module {\n    # A module extending workerd functionality.\n\n    name @0 :Text;\n    # Full js module name.\n\n    internal @1 :Bool = false;\n    # Internal modules can be imported by other extension modules only and not the user code.\n\n    esModule @2 :Text;\n    # Raw source code of ES module.\n  }\n}\n\n# ========================================================================================\n# Fallback Service Request\n#  Used only to define the JSON structure of a request to the fallback service.\n\nstruct FallbackServiceRequest {\n  type @0 :Text;\n  specifier @1 :Text;\n  rawSpecifier @2 :Text;\n  referrer @3 :Text;\n\n  struct Attribute {\n    name @0: Text;\n    value @1: Text;\n  }\n  attributes @4 :List(Attribute);\n}\n"
  },
  {
    "path": "src/workerd/tests/BUILD.bazel",
    "content": "load(\"//:build/kj_test.bzl\", \"kj_test\")\nload(\"//:build/wd_cc_benchmark.bzl\", \"wd_cc_benchmark\")\nload(\"//:build/wd_cc_library.bzl\", \"wd_cc_library\")\nload(\"//:build/wd_test.bzl\", \"wd_test\")\n\nwd_cc_library(\n    name = \"bench-tools\",\n    hdrs = [\"bench-tools.h\"],\n    defines = select({\n        \"//src/workerd/server:really_use_tcmalloc\": [\"WD_USE_TCMALLOC\"],\n        \"//conditions:default\": [],\n    }),\n    # clang-tidy fails on this target when using clang-tidy-21 together with llvm-19 headers due to\n    # changes in how intrinsics are defined - disable clang-tidy here until we start using LLVM 21\n    # everywhere.\n    tags = [\n        \"no-clang-tidy\",\n        \"workerd-benchmark\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@capnp-cpp//src/kj:kj-test\",\n        \"@google_benchmark//:benchmark\",\n    ] + select({\n        # tcmalloc is only available on Linux and when use_tcmalloc is enabled.\n        # We use the malloc_extension API to configure tcmalloc for deterministic benchmarks.\n        \"//src/workerd/server:really_use_tcmalloc\": [\"@tcmalloc//tcmalloc:malloc_extension\"],\n        \"//conditions:default\": [],\n    }),\n)\n\nwd_cc_library(\n    name = \"test-fixture\",\n    srcs = [\"test-fixture.c++\"],\n    hdrs = [\"test-fixture.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/io\",\n        \"//src/workerd/jsg\",\n        \"//src/workerd/server:workerd-api\",\n        \"//src/workerd/util:autogate\",\n    ],\n)\n\nkj_test(\n    src = \"test-fixture-test.c++\",\n    deps = [\":test-fixture\"],\n)\n\n# Use `bazel run //src/workerd/tests:bench-json` to benchmark\nwd_cc_benchmark(\n    name = \"bench-json\",\n    srcs = [\"bench-json.c++\"],\n    deps = [\n        \"//src/workerd/api:r2-api_capnp\",\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nwd_cc_benchmark(\n    name = \"bench-mimetype\",\n    srcs = [\"bench-mimetype.c++\"],\n    deps = [\n        \"//src/workerd/util:mimetype\",\n    ],\n)\n\nwd_cc_benchmark(\n    name = \"bench-kj-headers\",\n    srcs = [\"bench-kj-headers.c++\"],\n    deps = [\n        \"@capnp-cpp//src/kj/compat:kj-http\",\n    ],\n)\n\nwd_cc_benchmark(\n    name = \"bench-api-headers\",\n    srcs = [\"bench-api-headers.c++\"],\n    deps = [\n        \":test-fixture\",\n    ],\n)\n\nwd_cc_benchmark(\n    name = \"bench-global-scope\",\n    srcs = [\"bench-global-scope.c++\"],\n    deps = [\":test-fixture\"],\n)\n\nwd_cc_benchmark(\n    name = \"bench-regex\",\n    srcs = [\"bench-regex.c++\"],\n    deps = [\n        \":test-fixture\",\n        \"//src/workerd/util\",\n    ],\n)\n\nwd_cc_benchmark(\n    name = \"bench-util\",\n    srcs = [\"bench-util.c++\"],\n    deps = [\n        \":test-fixture\",\n        \"//src/workerd/jsg\",\n    ],\n)\n\nwd_cc_benchmark(\n    name = \"bench-text-encoder\",\n    srcs = [\"bench-text-encoder.c++\"],\n    deps = [\":test-fixture\"],\n)\n\nwd_cc_benchmark(\n    name = \"bench-response\",\n    srcs = [\"bench-response.c++\"],\n    deps = [\":test-fixture\"],\n)\n\nwd_cc_benchmark(\n    name = \"bench-jsstring\",\n    srcs = [\"bench-jsstring.c++\"],\n    deps = [\n        \":test-fixture\",\n        \"//src/workerd/jsg\",\n    ],\n)\n\n# Benchmark for comparing stream piping implementations\n# Tagged manual because it takes too long for CI - run explicitly with:\n#   bazel run //src/workerd/tests:bench-stream-piping\nwd_cc_benchmark(\n    name = \"bench-stream-piping\",\n    srcs = [\"bench-stream-piping.c++\"],\n    tags = [\"manual\"],\n    deps = [\n        \":test-fixture\",\n        \"//src/workerd/io\",\n        \"//src/workerd/jsg\",\n    ],\n)\n\n# Benchmark for PumpToReader (ReadableStream::pumpTo path in standard.c++).\n# Run before and after DrainingReader adoption to measure improvement.\n#   bazel run --config=opt //src/workerd/tests:bench-pumpto\nwd_cc_benchmark(\n    name = \"bench-pumpto\",\n    srcs = [\"bench-pumpto.c++\"],\n    tags = [\"manual\"],\n    deps = [\n        \":test-fixture\",\n        \"//src/workerd/io\",\n        \"//src/workerd/jsg\",\n    ],\n)\n\nwd_test(\n    src = \"unknown-import-assertions-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"unknown-import-assertions-test.js\"],\n)\n\nwd_test(\n    src = \"module-imports-test.wd-test\",\n    args = [\"--experimental\"],\n    data = [\n        \"module-imports-test.js\",\n        \"test.wasm\",\n    ],\n)\n\nwd_test(\n    src = \"performance-test.wd-test\",\n    data = [\"performance-test.js\"],\n)\n\n# REPRL tests using KJ test framework\nwd_cc_library(\n    name = \"libreprl\",\n    srcs = [\"libreprl/libreprl.c\"],\n    hdrs = [\"libreprl/libreprl.h\"],\n    copts = [\n        \"-std=c23\",\n    ],\n)\n\nkj_test(\n    src = \"test-reprl.c++\",\n    data = [\n        \"//fuzzilli:config.capnp\",\n        \"//fuzzilli:worker.js\",\n        \"//src/workerd/server:workerd\",\n    ],\n    tags = [\"requires-fuzzilli\"],\n    deps = [\n        \":libreprl\",\n        \"@bazel_tools//tools/cpp/runfiles\",\n    ],\n)\n"
  },
  {
    "path": "src/workerd/tests/bench-api-headers.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/api/http.h>\n#include <workerd/tests/bench-tools.h>\n#include <workerd/tests/test-fixture.h>\n\n// A benchmark for js Header class.\n\nnamespace workerd {\nnamespace {\n\nstruct ApiHeaders: public benchmark::Fixture {\n  virtual ~ApiHeaders() noexcept(true) {}\n\n  struct Header {\n    bool append;\n    kj::StringPtr name;\n    kj::StringPtr value;\n  };\n\n  void SetUp(benchmark::State& state) noexcept(true) override {\n    fixture = kj::heap<TestFixture>();\n\n    kj::HttpHeaderTable::Builder builder;\n    builder.add(\"Host\");\n    builder.add(\"Accept\");\n    builder.add(\"Content-Type\");\n    builder.add(\"Last-Modified\");\n    table = builder.build();\n    kjHeaders = kj::heap<kj::HttpHeaders>(*table);\n    auto in = kj::heapString(\n        \"GET /favicon.ico HTTP/1.1\\r\\n\"\n        \"Host: 0.0.0.0=5000\\r\\n\"\n        \"User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\\r\\n\"\n        \"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\\r\\n\"\n        \"Accept-Language: en-us,en;q=0.5\\r\\n\"\n        \"Accept-Encoding: gzip,deflate\\r\\n\"\n        \"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\\r\\n\"\n        \"Keep-Alive: 300\\r\\n\"\n        \"Connection: keep-alive\\r\\n\"\n        \"\\r\\n\");\n    KJ_EXPECT(kjHeaders->tryParseRequest(in.asArray()).is<kj::HttpHeaders::Request>());\n    original = kj::mv(in);\n  }\n\n  void TearDown(benchmark::State& state) noexcept(true) override {\n    fixture = nullptr;\n  }\n\n  kj::Maybe<kj::String> original;\n  kj::Own<TestFixture> fixture;\n  kj::Own<kj::HttpHeaderTable> table;\n  kj::Own<kj::HttpHeaders> kjHeaders;\n  Header kHeaders[13] = {Header{false, \"Host\"_kj, \"example.com\"_kj},\n    Header{false, \"User-Agent\"_kj,\n      \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\"_kj},\n    Header{false, \"Accept\"_kj,\n      \"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\"_kj},\n    Header{false, \"Accept-Language\"_kj, \"en-US,en;q=0.9\"_kj},\n    Header{false, \"Accept-Encoding\"_kj, \"gzip, deflate, br\"_kj},\n    Header{false, \"Content-Type\"_kj, \"application/json; charset=utf-8\"_kj},\n    Header{false, \"Authorization\"_kj,\n      \"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0\"_kj},\n    Header{false, \"Cache-Control\"_kj, \"no-cache, no-store, must-revalidate\"_kj},\n    Header{false, \"Content-Length\"_kj, \"1234\"_kj},\n    Header{false, \"Referer\"_kj, \"https://www.example.com/page?query=value&other=param\"_kj},\n    Header{false, \"X-Forwarded-For\"_kj, \"203.0.113.1, 198.51.100.17\"_kj},\n    Header{true, \"Set-Cookie\"_kj, \"new_session=token123; Path=/; Secure; HttpOnly\"_kj},\n    Header{true, \"Set-Cookie\"_kj, \"new_session=token124; Path=/abc; Secure; HttpOnly\"_kj}};\n};\n\n// initialization performs a lot of copying, benchmark it\nBENCHMARK_F(ApiHeaders, constructor)(benchmark::State& state) {\n  fixture->runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    for (auto _: state) {\n      for (size_t i = 0; i < 10000; ++i) {\n        benchmark::DoNotOptimize(\n            js.alloc<api::Headers>(js, *kjHeaders, api::Headers::Guard::REQUEST));\n        benchmark::DoNotOptimize(i);\n      }\n    }\n  });\n}\n\nBENCHMARK_F(ApiHeaders, set_append)(benchmark::State& state) {\n  fixture->runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    for (auto _: state) {\n      for (size_t i = 0; i < 1000; ++i) {\n        auto headers = js.alloc<api::Headers>();\n        // Set common headers with various representative lengths\n        for (auto& h: kHeaders) {\n          if (h.append) {\n            headers->append(env.js, kj::str(h.name), kj::str(h.value));\n          } else {\n            headers->set(env.js, kj::str(h.name), kj::str(h.value));\n          }\n        }\n        benchmark::DoNotOptimize(i);\n      }\n    }\n  });\n}\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/tests/bench-global-scope.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/tests/bench-tools.h>\n#include <workerd/tests/test-fixture.h>\n\n// A benchmark for GlobalScope functionality.\n\nnamespace workerd {\nnamespace {\n\nstruct GlobalScopeBenchmark: public benchmark::Fixture {\n  virtual ~GlobalScopeBenchmark() noexcept(true) {}\n\n  void SetUp(benchmark::State& state) noexcept(true) override {\n    TestFixture::SetupParams params = {.mainModuleSource = R\"(\n        export default {\n          async fetch(request) {\n            return new Response(\"OK\");\n          },\n        };\n      )\"_kj};\n    fixture = kj::heap<TestFixture>(kj::mv(params));\n  }\n\n  void TearDown(benchmark::State& state) noexcept(true) override {\n    fixture = nullptr;\n  }\n\n  kj::Own<TestFixture> fixture;\n};\n\nBENCHMARK_F(GlobalScopeBenchmark, request)(benchmark::State& state) {\n  for (auto _: state) {\n    for (size_t i = 0; i < 1000; ++i) {\n      benchmark::DoNotOptimize(\n          fixture->runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"TEST\"_kj));\n      benchmark::DoNotOptimize(i);\n    }\n  }\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/tests/bench-json.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/api/r2-api.capnp.h>\n#include <workerd/tests/bench-tools.h>\n\n#include <benchmark/benchmark.h>\n\n#include <capnp/compat/json.h>\n#include <capnp/message.h>\n#include <kj/string.h>\n#include <kj/test.h>\n\n// Example test, derived from capnproto's json test.\nstatic void Test_JSON_ENC(benchmark::State& state) {\n  capnp::JsonCodec json;\n  // Perform setup here\n\n  for (auto _: state) {\n    for (size_t i = 0; i < 10000; i++) {\n      // This code gets timed\n      KJ_EXPECT(json.encode(capnp::VOID) == \"null\");\n      KJ_EXPECT(json.encode(true) == \"true\");\n      KJ_EXPECT(json.encode(false) == \"false\");\n      KJ_EXPECT(json.encode(123) == \"123\");\n      KJ_EXPECT(json.encode(-5.5) == \"-5.5\");\n      KJ_EXPECT(json.encode(capnp::Text::Reader(\"foo\")) == \"\\\"foo\\\"\");\n      KJ_EXPECT(json.encode(capnp::Text::Reader(\"ab\\\"cd\\\\ef\\x03\")) == \"\\\"ab\\\\\\\"cd\\\\\\\\ef\\\\u0003\\\"\");\n\n      json.setPrettyPrint(false);\n      kj::byte bytes[] = {12, 34, 56};\n      KJ_EXPECT(json.encode(capnp::Data::Reader(bytes, 3)) == \"[12,34,56]\");\n\n      json.setPrettyPrint(true);\n      KJ_EXPECT(json.encode(capnp::Data::Reader(bytes, 3)) == \"[12, 34, 56]\");\n    }\n  }\n}\n\nstatic void Test_JSON_DEC(benchmark::State& state) {\n  //Test R2BindingRequest, a more complex example\n  capnp::JsonCodec json;\n  capnp::MallocMessageBuilder responseMessage;\n  json.handleByAnnotation<workerd::api::public_beta::R2BindingRequest>();\n  static constexpr kj::StringPtr dummy =\n      \"{\\\"version\\\":1,\\\"method\\\":\\\"completeMultipartUpload\\\",\\\"object\\\":\\\"multipart_object_name4\\\",\\\"uploadId\\\":\\\"uploadId\\\",\\\"parts\\\":[{\\\"etag\\\":\\\"1234\\\",\\\"part\\\":1},{\\\"etag\\\":\\\"56789\\\",\\\"part\\\":2}]}\"_kj;\n\n  for (auto _: state) {\n    for (size_t i = 0; i < 100000; i++) {\n      auto responseBuilder =\n          responseMessage.initRoot<workerd::api::public_beta::R2BindingRequest>();\n      json.decode(dummy, responseBuilder);\n      benchmark::DoNotOptimize(responseBuilder);\n    }\n  }\n}\n\nWD_BENCHMARK(Test_JSON_ENC);\nWD_BENCHMARK(Test_JSON_DEC);\n// Register both functions as benchmarks – we link benchmark_main so there's no need for a main\n// function.\n"
  },
  {
    "path": "src/workerd/tests/bench-jsstring.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/setup.h>\n#include <workerd/tests/bench-tools.h>\n#include <workerd/tests/test-fixture.h>\n\n// Benchmark for JsString.utf8Length() method across different string types:\n// latin1/utf16, flat/non-flat, and various sizes.\n\nnamespace workerd {\nnamespace {\n\ntemplate <size_t N>\njsg::JsString createLatin1String(jsg::Lock& js) {\n  kj::FixedArray<char, N> vec;\n  vec.fill('a');\n  if (N > 1) {\n    vec[N / 2] = static_cast<char>(0xC0);\n  }\n  return js.str(vec.asPtr());\n}\n\ntemplate <size_t N>\njsg::JsString createUtf16String(jsg::Lock& js) {\n  kj::FixedArray<uint16_t, N> vec;\n  vec.fill(0x1F600 & 0xFFFF);\n  return js.str(vec.asPtr());\n}\n\ntemplate <size_t N>\njsg::JsString createInvalidUtf16String(jsg::Lock& js) {\n  kj::FixedArray<uint16_t, N> vec;\n  vec.fill(0x1F600 & 0xFFFF);\n  if (N > 1) {\n    vec[N / 2] = 0xD800;\n  }\n  return js.str(vec.asPtr());\n}\n\n// Benchmarks utf8Length on 32-char latin1 flat strings\nstatic void JsString_Utf8Length_Latin1_Flat_32(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto str = createLatin1String<32>(js);\n\n    for (auto _: state) {\n      KJ_ASSERT(str.isFlat());\n      benchmark::DoNotOptimize(str.utf8Length(js));\n    }\n  });\n}\n\n// Benchmarks utf8Length on 256-char latin1 flat strings\nstatic void JsString_Utf8Length_Latin1_Flat_256(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto str = createLatin1String<256>(js);\n\n    for (auto _: state) {\n      KJ_ASSERT(str.isFlat());\n      benchmark::DoNotOptimize(str.utf8Length(js));\n    }\n  });\n}\n\n// Benchmarks utf8Length on 1024-char latin1 flat strings\nstatic void JsString_Utf8Length_Latin1_Flat_1024(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto str = createLatin1String<1024>(js);\n\n    for (auto _: state) {\n      KJ_ASSERT(str.isFlat());\n      benchmark::DoNotOptimize(str.utf8Length(js));\n    }\n  });\n}\n\n// Benchmarks utf8Length on 8192-char latin1 flat strings\nstatic void JsString_Utf8Length_Latin1_Flat_8192(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto str = createLatin1String<8192>(js);\n\n    for (auto _: state) {\n      KJ_ASSERT(str.isFlat());\n      benchmark::DoNotOptimize(str.utf8Length(js));\n    }\n  });\n}\n\n// Benchmarks utf8Length on 256-char utf16 flat strings\nstatic void JsString_Utf8Length_Utf16_Flat_256(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto str = createUtf16String<256>(js);\n\n    for (auto _: state) {\n      KJ_ASSERT(str.isFlat());\n      benchmark::DoNotOptimize(str.utf8Length(js));\n    }\n  });\n}\n\n// Benchmarks utf8Length on 1024-char utf16 flat strings\nstatic void JsString_Utf8Length_Utf16_Flat_1024(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto str = createUtf16String<1024>(js);\n\n    for (auto _: state) {\n      KJ_ASSERT(str.isFlat());\n      benchmark::DoNotOptimize(str.utf8Length(js));\n    }\n  });\n}\n\n// Benchmarks utf8Length on 8192-char utf16 flat strings\nstatic void JsString_Utf8Length_Utf16_Flat_8192(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto str = createUtf16String<8192>(js);\n\n    for (auto _: state) {\n      KJ_ASSERT(str.isFlat());\n      benchmark::DoNotOptimize(str.utf8Length(js));\n    }\n  });\n}\n\n// Benchmarks utf8Length on 256-char utf16 flat strings with invalid UTF-16\nstatic void JsString_Utf8Length_Utf16_Invalid_Flat_256(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto str = createInvalidUtf16String<256>(js);\n\n    for (auto _: state) {\n      KJ_ASSERT(str.isFlat());\n      benchmark::DoNotOptimize(str.utf8Length(js));\n    }\n  });\n}\n\n// Benchmarks utf8Length on 1024-char utf16 flat strings with invalid UTF-16\nstatic void JsString_Utf8Length_Utf16_Invalid_Flat_1024(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto str = createInvalidUtf16String<1024>(js);\n\n    for (auto _: state) {\n      KJ_ASSERT(str.isFlat());\n      benchmark::DoNotOptimize(str.utf8Length(js));\n    }\n  });\n}\n\n// Benchmarks utf8Length on 8192-char utf16 flat strings with invalid UTF-16\nstatic void JsString_Utf8Length_Utf16_Invalid_Flat_8192(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto str = createInvalidUtf16String<8192>(js);\n\n    for (auto _: state) {\n      KJ_ASSERT(str.isFlat());\n      benchmark::DoNotOptimize(str.utf8Length(js));\n    }\n  });\n}\n\n// Benchmarks utf8Length on 256-char non-flat latin1 strings (deep tree, 8 pieces)\nstatic void JsString_Utf8Length_Latin1_NonFlat_256(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto piece = createLatin1String<32>(js);\n\n    for (auto _: state) {\n      // Build deep tree by concatenating 8 pieces (depth ~7-8)\n      auto result = piece;\n      for (int i = 1; i < 8; i++) {\n        result = jsg::JsString::concat(js, result, piece);\n      }\n      KJ_ASSERT(!result.isFlat());\n      benchmark::DoNotOptimize(result.utf8Length(js));\n    }\n  });\n}\n\n// Benchmarks utf8Length on 1024-char non-flat latin1 strings (deep tree, 16 pieces)\nstatic void JsString_Utf8Length_Latin1_NonFlat_1024(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto piece = createLatin1String<64>(js);\n\n    for (auto _: state) {\n      // Build deep tree by concatenating 16 pieces (depth ~15-16)\n      auto result = piece;\n      for (int i = 1; i < 16; i++) {\n        result = jsg::JsString::concat(js, result, piece);\n      }\n      KJ_ASSERT(!result.isFlat());\n      benchmark::DoNotOptimize(result.utf8Length(js));\n    }\n  });\n}\n\n// Benchmarks utf8Length on 8192-char non-flat latin1 strings (deep tree, 32 pieces)\nstatic void JsString_Utf8Length_Latin1_NonFlat_8192(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto piece = createLatin1String<256>(js);\n\n    for (auto _: state) {\n      // Build deep tree by concatenating 32 pieces (depth ~31-32)\n      auto result = piece;\n      for (int i = 1; i < 32; i++) {\n        result = jsg::JsString::concat(js, result, piece);\n      }\n      KJ_ASSERT(!result.isFlat());\n      benchmark::DoNotOptimize(result.utf8Length(js));\n    }\n  });\n}\n\n// Benchmarks utf8Length on 256-char non-flat utf16 strings (deep tree, 8 pieces)\nstatic void JsString_Utf8Length_Utf16_NonFlat_256(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto piece = createUtf16String<32>(js);\n\n    for (auto _: state) {\n      // Build deep tree by concatenating 8 pieces (depth ~7-8)\n      auto result = piece;\n      for (int i = 1; i < 8; i++) {\n        result = jsg::JsString::concat(js, result, piece);\n      }\n      KJ_ASSERT(!result.isFlat());\n      benchmark::DoNotOptimize(result.utf8Length(js));\n    }\n  });\n}\n\n// Benchmarks utf8Length on 1024-char non-flat utf16 strings (deep tree, 16 pieces)\nstatic void JsString_Utf8Length_Utf16_NonFlat_1024(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto piece = createUtf16String<64>(js);\n\n    for (auto _: state) {\n      // Build deep tree by concatenating 16 pieces (depth ~15-16)\n      auto result = piece;\n      for (int i = 1; i < 16; i++) {\n        result = jsg::JsString::concat(js, result, piece);\n      }\n      KJ_ASSERT(!result.isFlat());\n      benchmark::DoNotOptimize(result.utf8Length(js));\n    }\n  });\n}\n\n// Benchmarks utf8Length on 8192-char non-flat utf16 strings (deep tree, 32 pieces)\nstatic void JsString_Utf8Length_Utf16_NonFlat_8192(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto piece = createUtf16String<256>(js);\n\n    for (auto _: state) {\n      // Build deep tree by concatenating 32 pieces (depth ~31-32)\n      auto result = piece;\n      for (int i = 1; i < 32; i++) {\n        result = jsg::JsString::concat(js, result, piece);\n      }\n      KJ_ASSERT(!result.isFlat());\n      benchmark::DoNotOptimize(result.utf8Length(js));\n    }\n  });\n}\n\n// Benchmarks utf8Length on 256-char non-flat utf16 strings with invalid UTF-16 (deep tree, 8 pieces)\nstatic void JsString_Utf8Length_Utf16_Invalid_NonFlat_256(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto piece = createInvalidUtf16String<32>(js);\n\n    for (auto _: state) {\n      // Build deep tree by concatenating 8 pieces (depth ~7-8)\n      auto result = piece;\n      for (int i = 1; i < 8; i++) {\n        result = jsg::JsString::concat(js, result, piece);\n      }\n      KJ_ASSERT(!result.isFlat());\n      benchmark::DoNotOptimize(result.utf8Length(js));\n    }\n  });\n}\n\n// Benchmarks utf8Length on 1024-char non-flat utf16 strings with invalid UTF-16 (deep tree, 16 pieces)\nstatic void JsString_Utf8Length_Utf16_Invalid_NonFlat_1024(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto piece = createInvalidUtf16String<64>(js);\n\n    for (auto _: state) {\n      // Build deep tree by concatenating 16 pieces (depth ~15-16)\n      auto result = piece;\n      for (int i = 1; i < 16; i++) {\n        result = jsg::JsString::concat(js, result, piece);\n      }\n      KJ_ASSERT(!result.isFlat());\n      benchmark::DoNotOptimize(result.utf8Length(js));\n    }\n  });\n}\n\n// Benchmarks utf8Length on 8192-char non-flat utf16 strings with invalid UTF-16 (deep tree, 32 pieces)\nstatic void JsString_Utf8Length_Utf16_Invalid_NonFlat_8192(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto piece = createInvalidUtf16String<256>(js);\n\n    for (auto _: state) {\n      // Build deep tree by concatenating 32 pieces (depth ~31-32)\n      auto result = piece;\n      for (int i = 1; i < 32; i++) {\n        result = jsg::JsString::concat(js, result, piece);\n      }\n      KJ_ASSERT(!result.isFlat());\n      benchmark::DoNotOptimize(result.utf8Length(js));\n    }\n  });\n}\n\nWD_BENCHMARK(JsString_Utf8Length_Latin1_Flat_32);\nWD_BENCHMARK(JsString_Utf8Length_Latin1_Flat_256);\nWD_BENCHMARK(JsString_Utf8Length_Latin1_Flat_1024);\nWD_BENCHMARK(JsString_Utf8Length_Latin1_Flat_8192);\nWD_BENCHMARK(JsString_Utf8Length_Utf16_Flat_256);\nWD_BENCHMARK(JsString_Utf8Length_Utf16_Flat_1024);\nWD_BENCHMARK(JsString_Utf8Length_Utf16_Flat_8192);\nWD_BENCHMARK(JsString_Utf8Length_Utf16_Invalid_Flat_256);\nWD_BENCHMARK(JsString_Utf8Length_Utf16_Invalid_Flat_1024);\nWD_BENCHMARK(JsString_Utf8Length_Utf16_Invalid_Flat_8192);\nWD_BENCHMARK(JsString_Utf8Length_Latin1_NonFlat_256);\nWD_BENCHMARK(JsString_Utf8Length_Latin1_NonFlat_1024);\nWD_BENCHMARK(JsString_Utf8Length_Latin1_NonFlat_8192);\nWD_BENCHMARK(JsString_Utf8Length_Utf16_NonFlat_256);\nWD_BENCHMARK(JsString_Utf8Length_Utf16_NonFlat_1024);\nWD_BENCHMARK(JsString_Utf8Length_Utf16_NonFlat_8192);\nWD_BENCHMARK(JsString_Utf8Length_Utf16_Invalid_NonFlat_256);\nWD_BENCHMARK(JsString_Utf8Length_Utf16_Invalid_NonFlat_1024);\nWD_BENCHMARK(JsString_Utf8Length_Utf16_Invalid_NonFlat_8192);\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/tests/bench-kj-headers.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/tests/bench-tools.h>\n\n#include <kj/compat/http.h>\n\nnamespace workerd {\nnamespace {\n\nstruct KjHeaders: public benchmark::Fixture {\n  virtual ~KjHeaders() noexcept(true) {}\n\n  void SetUp(benchmark::State& state) noexcept(true) override {\n    kj::HttpHeaderTable::Builder builder;\n    builder.add(\"Host\");\n    builder.add(\"Accept\");\n    builder.add(\"Content-Type\");\n    builder.add(\"Last-Modified\");\n    table = builder.build();\n  }\n\n  kj::Own<kj::HttpHeaderTable> table;\n};\n\nBENCHMARK_F(KjHeaders, Parse)(benchmark::State& state) {\n  kj::String in = kj::heapString(\n      \"GET /favicon.ico HTTP/1.1\\r\\n\"\n      \"Host: 0.0.0.0=5000\\r\\n\"\n      \"User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\\r\\n\"\n      \"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\\r\\n\"\n      \"Accept-Language: en-us,en;q=0.5\\r\\n\"\n      \"Accept-Encoding: gzip,deflate\\r\\n\"\n      \"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\\r\\n\"\n      \"Keep-Alive: 300\\r\\n\"\n      \"Connection: keep-alive\\r\\n\"\n      \"\\r\\n\");\n\n  for (auto _: state) {\n    kj::HttpHeaders headers(*table);\n\n    for (size_t i = 0; i < 1000; ++i) {\n      benchmark::DoNotOptimize(\n          headers.tryParseRequest(in.asArray()).is<kj::HttpHeaders::Request>());\n      benchmark::DoNotOptimize(i);\n    }\n  }\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/tests/bench-mimetype.c++",
    "content": "#include <workerd/tests/bench-tools.h>\n#include <workerd/util/mimetype.h>\n\nnamespace workerd {\nnamespace {\n\nstatic void MimeType_ParseAndSerialize(benchmark::State& state) {\n  for (auto _: state) {\n    for (size_t i = 0; i < 10000; i++) {\n      benchmark::DoNotOptimize(MimeType::parse(\"text/plain;charset=UTF-8\"_kj).toString());\n      benchmark::DoNotOptimize(\n          MimeType::parse(\"multipart/byteranges; boundary=3d6b6a416f9b5\"_kj).toString());\n      benchmark::DoNotOptimize(\n          MimeType::parse(\"video/webm;codecs=\\\"vp09.02.10.10.01.09.16.09.01,opus\\\"\"_kj).toString());\n\n      // longest entry from https://www.iana.org/assignments/media-types/media-types.xhtml\n      benchmark::DoNotOptimize(MimeType::parse(\n          \"application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml\"_kj)\n                                   .toString());\n\n      benchmark::DoNotOptimize(i);\n    }\n  }\n}\n\nstatic void MimeType_Serialize(benchmark::State& state) {\n  for (auto _: state) {\n    for (size_t i = 0; i < 100000; ++i) {\n      benchmark::DoNotOptimize(MimeType::PLAINTEXT.toString());\n      benchmark::DoNotOptimize(MimeType::CSS.toString());\n      benchmark::DoNotOptimize(MimeType::HTML.toString());\n      benchmark::DoNotOptimize(MimeType::JSON.toString());\n\n      benchmark::DoNotOptimize(i);\n    }\n  }\n}\n\nWD_BENCHMARK(MimeType_ParseAndSerialize)->Name(\"Mimetype::ParseAndSerialize\");\nWD_BENCHMARK(MimeType_Serialize)->Name(\"Mimetype::Serialize\");\n\n}  // namespace\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/tests/bench-pumpto.c++",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Benchmark for PumpToReader in standard.c++.\n//\n// Measures the performance of ReadableStream::pumpTo() which routes through\n// ReadableStreamJsController::pumpTo() → PumpToReader::pumpLoop().\n//\n// This benchmark establishes a baseline before the DrainingReader adoption,\n// then the same benchmarks are re-run after the change to quantify improvement.\n// This test was originally written to measure improvement from DrainingReader\n// adoption (deployed by an autogate), but remains broadly useful as a benchmark\n// even after we remove the autogate.\n//\n// Usage:\n//   # Capture baseline (before changes):\n//   bazel run --config=opt //src/workerd/tests:bench-pumpto \\\n//       -- --benchmark_format=json --benchmark_out=baseline.json\n//\n//   # Capture comparison (after changes):\n//   bazel run --config=opt //src/workerd/tests:bench-pumpto \\\n//       -- --benchmark_format=json --benchmark_out=after.json\n//\n// Key metrics:\n//   - bytes_per_second: Primary throughput metric.\n//   - WriteOps: Average sink write calls per iteration. Directly measures batching.\n//     Before DrainingReader adoption: WriteOps ≈ numChunks (one write per chunk).\n//     After: WriteOps ≪ numChunks (one vectored write per drain cycle).\n\n#include <workerd/api/streams/standard.h>\n#include <workerd/api/system-streams.h>\n#include <workerd/tests/bench-tools.h>\n#include <workerd/tests/test-fixture.h>\n\nnamespace workerd::api::streams {\nnamespace {\n\n// =============================================================================\n// Stream configuration\n// =============================================================================\n\nenum class StreamType {\n  VALUE,             // Default ReadableStreamDefaultController\n  BYTE,              // ReadableByteStreamController\n  IO_LATENCY_VALUE,  // Value stream that yields to KJ event loop between chunks\n};\n\nstruct StreamConfig {\n  StreamType type = StreamType::VALUE;\n};\n\n// =============================================================================\n// Test utilities\n// =============================================================================\n\n// A discarding sink that counts bytes written and number of write operations.\nstruct DiscardingSink final: public kj::AsyncOutputStream {\n  size_t bytesWritten = 0;\n  size_t writeCount = 0;\n\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override {\n    writeCount++;\n    bytesWritten += buffer.size();\n    co_return;\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {\n    writeCount++;\n    for (auto piece: pieces) {\n      bytesWritten += piece.size();\n    }\n    co_return;\n  }\n\n  kj::Promise<void> whenWriteDisconnected() override {\n    return kj::NEVER_DONE;\n  }\n\n  void reset() {\n    bytesWritten = 0;\n    writeCount = 0;\n  }\n};\n\n// =============================================================================\n// Stream creation helpers\n// =============================================================================\n\nstatic size_t benchChunkCounterStatic = 0;\n\n// Creates a JS-backed value ReadableStream that produces data synchronously in pull().\njsg::Ref<ReadableStream> createValueStream(\n    jsg::Lock& js, size_t chunkSize, size_t numChunks, size_t* counter) {\n  return ReadableStream::constructor(js,\n      UnderlyingSource{\n        .pull =\n            [chunkSize, numChunks, counter](jsg::Lock& js, auto controller) {\n    auto& c =\n        KJ_ASSERT_NONNULL(controller.template tryGet<jsg::Ref<ReadableStreamDefaultController>>());\n\n    if ((*counter)++ < numChunks) {\n      auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, chunkSize);\n      jsg::BufferSource buffer(js, kj::mv(backing));\n      buffer.asArrayPtr().fill(0xAB);\n      c->enqueue(js, buffer.getHandle(js));\n    }\n    if (*counter == numChunks) {\n      c->close(js);\n    }\n    return js.resolvedPromise();\n  },\n        .expectedLength = chunkSize * numChunks,\n      },\n      StreamQueuingStrategy{\n        .highWaterMark = 0,\n      });\n}\n\n// Creates a JS-backed byte ReadableStream that produces data synchronously in pull().\njsg::Ref<ReadableStream> createByteStream(\n    jsg::Lock& js, size_t chunkSize, size_t numChunks, size_t* counter) {\n  return ReadableStream::constructor(js,\n      UnderlyingSource{\n        .type = kj::str(\"bytes\"),\n        .pull =\n            [chunkSize, numChunks, counter](jsg::Lock& js, auto controller) {\n    auto& c =\n        KJ_ASSERT_NONNULL(controller.template tryGet<jsg::Ref<ReadableByteStreamController>>());\n\n    if ((*counter)++ < numChunks) {\n      auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, chunkSize);\n      jsg::BufferSource buffer(js, kj::mv(backing));\n      buffer.asArrayPtr().fill(0xAB);\n      c->enqueue(js, kj::mv(buffer));\n    }\n    if (*counter == numChunks) {\n      c->close(js);\n    }\n    return js.resolvedPromise();\n  },\n        .expectedLength = chunkSize * numChunks,\n      },\n      StreamQueuingStrategy{\n        .highWaterMark = 0,\n      });\n}\n\n// Creates a value stream that yields to the KJ event loop between chunks.\n// Simulates a network stream where data arrives with real I/O latency.\n// Each chunk requires a KJ event loop iteration, so DrainingReader cannot batch them.\njsg::Ref<ReadableStream> createIoLatencyValueStream(\n    jsg::Lock& js, size_t chunkSize, size_t numChunks, size_t* counter) {\n  return ReadableStream::constructor(js,\n      UnderlyingSource{\n        .pull =\n            [chunkSize, numChunks, counter](jsg::Lock& js, auto controller) {\n    auto& c =\n        KJ_ASSERT_NONNULL(controller.template tryGet<jsg::Ref<ReadableStreamDefaultController>>());\n\n    if (*counter >= numChunks) {\n      c->close(js);\n      return js.resolvedPromise();\n    }\n\n    // Use IoContext.awaitIo() to wait for a KJ event loop yield.\n    // kj::evalLater() schedules on the next KJ event loop iteration.\n    auto& ioContext = IoContext::current();\n    auto cRef = c.addRef();\n    return ioContext.awaitIo(js, kj::evalLater([]() {}),\n        JSG_VISITABLE_LAMBDA(\n            (cRef = kj::mv(cRef), chunkSize, numChunks, counter), (cRef), (jsg::Lock & js) mutable {\n              if ((*counter)++ < numChunks) {\n              auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, chunkSize);\n              jsg::BufferSource buffer(js, kj::mv(backing));\n              buffer.asArrayPtr().fill(0xAB);\n              cRef->enqueue(js, buffer.getHandle(js));\n              }\n              if (*counter == numChunks) {\n              cRef->close(js);\n              }\n            }));\n  },\n        .expectedLength = chunkSize * numChunks,\n      },\n      StreamQueuingStrategy{\n        .highWaterMark = 0,\n      });\n}\n\njsg::Ref<ReadableStream> createConfiguredStream(\n    jsg::Lock& js, size_t chunkSize, size_t numChunks, const StreamConfig& config) {\n  benchChunkCounterStatic = 0;\n  size_t* counter = &benchChunkCounterStatic;\n\n  switch (config.type) {\n    case StreamType::VALUE:\n      return createValueStream(js, chunkSize, numChunks, counter);\n    case StreamType::BYTE:\n      return createByteStream(js, chunkSize, numChunks, counter);\n    case StreamType::IO_LATENCY_VALUE:\n      return createIoLatencyValueStream(js, chunkSize, numChunks, counter);\n  }\n  KJ_UNREACHABLE;\n}\n\n// =============================================================================\n// Core benchmark function\n// =============================================================================\n\n// Exercises: ReadableStream::pumpTo() → ReadableStreamJsController::pumpTo() → PumpToReader\nstatic void benchPumpTo(\n    benchmark::State& state, size_t chunkSize, size_t numChunks, const StreamConfig& config) {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n\n  DiscardingSink sink;\n  size_t expectedBytes = chunkSize * numChunks;\n\n  for (auto _: state) {\n    sink.reset();\n\n    fixture.runInIoContext([&](const TestFixture::Environment& env) {\n      auto stream = createConfiguredStream(env.js, chunkSize, numChunks, config);\n\n      // Wrap DiscardingSink as a WritableStreamSink via newSystemStream.\n      // This is the production path: PumpToReader receives a WritableStreamSink.\n      kj::Own<kj::AsyncOutputStream> fakeOwn(&sink, kj::NullDisposer::instance);\n      auto writableSink = newSystemStream(kj::mv(fakeOwn), StreamEncoding::IDENTITY, env.context);\n\n      return env.context.waitForDeferredProxy(stream->pumpTo(env.js, kj::mv(writableSink), true));\n    });\n\n    KJ_ASSERT(sink.bytesWritten == expectedBytes, \"Expected\", expectedBytes, \"bytes but got\",\n        sink.bytesWritten);\n  }\n\n  state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(expectedBytes));\n  state.counters[\"WriteOps\"] =\n      benchmark::Counter(sink.writeCount, benchmark::Counter::kAvgIterations);\n}\n\n// =============================================================================\n// Stream configs\n// =============================================================================\n\nstatic const StreamConfig VALUE_DEFAULT{.type = StreamType::VALUE};\nstatic const StreamConfig BYTE_DEFAULT{.type = StreamType::BYTE};\nstatic const StreamConfig IO_LATENCY_VALUE_DEFAULT{.type = StreamType::IO_LATENCY_VALUE};\n\n// =============================================================================\n// Synchronous streams — 1 MiB total payload\n// =============================================================================\n// These are the primary benchmarks. Data is produced synchronously in the pull\n// callback. DrainingReader (post-change) can drain all chunks in a single lock\n// acquisition, so small-chunk benchmarks should see large improvement.\n\n// Value streams\nstatic void PumpTo_64B_Value(benchmark::State& state) {\n  benchPumpTo(state, 64, 16384, VALUE_DEFAULT);\n}\nstatic void PumpTo_256B_Value(benchmark::State& state) {\n  benchPumpTo(state, 256, 4096, VALUE_DEFAULT);\n}\nstatic void PumpTo_1KB_Value(benchmark::State& state) {\n  benchPumpTo(state, 1024, 1024, VALUE_DEFAULT);\n}\nstatic void PumpTo_4KB_Value(benchmark::State& state) {\n  benchPumpTo(state, 4096, 256, VALUE_DEFAULT);\n}\nstatic void PumpTo_16KB_Value(benchmark::State& state) {\n  benchPumpTo(state, 16384, 64, VALUE_DEFAULT);\n}\nstatic void PumpTo_64KB_Value(benchmark::State& state) {\n  benchPumpTo(state, 65536, 16, VALUE_DEFAULT);\n}\n\n// Byte streams\nstatic void PumpTo_64B_Byte(benchmark::State& state) {\n  benchPumpTo(state, 64, 16384, BYTE_DEFAULT);\n}\nstatic void PumpTo_256B_Byte(benchmark::State& state) {\n  benchPumpTo(state, 256, 4096, BYTE_DEFAULT);\n}\nstatic void PumpTo_1KB_Byte(benchmark::State& state) {\n  benchPumpTo(state, 1024, 1024, BYTE_DEFAULT);\n}\nstatic void PumpTo_4KB_Byte(benchmark::State& state) {\n  benchPumpTo(state, 4096, 256, BYTE_DEFAULT);\n}\nstatic void PumpTo_16KB_Byte(benchmark::State& state) {\n  benchPumpTo(state, 16384, 64, BYTE_DEFAULT);\n}\nstatic void PumpTo_64KB_Byte(benchmark::State& state) {\n  benchPumpTo(state, 65536, 16, BYTE_DEFAULT);\n}\n\n// =============================================================================\n// I/O latency streams — 64 KiB total payload\n// =============================================================================\n// Each chunk requires a KJ event loop yield, simulating real network I/O.\n// DrainingReader cannot batch these (at most 1 chunk per drain cycle).\n// These verify no regression from the PumpToReader change.\n// Smaller total payload because each chunk incurs real event loop overhead.\n\nstatic void PumpTo_256B_IoLatency(benchmark::State& state) {\n  benchPumpTo(state, 256, 256, IO_LATENCY_VALUE_DEFAULT);\n}\nstatic void PumpTo_4KB_IoLatency(benchmark::State& state) {\n  benchPumpTo(state, 4096, 16, IO_LATENCY_VALUE_DEFAULT);\n}\nstatic void PumpTo_64KB_IoLatency(benchmark::State& state) {\n  benchPumpTo(state, 65536, 1, IO_LATENCY_VALUE_DEFAULT);\n}\n\n// =============================================================================\n// Large payload — 10 MiB total, sync value streams\n// =============================================================================\n// Sustained throughput test with small chunks. More data amortizes fixture\n// setup cost, yielding more stable measurements.\n\nstatic void PumpTo_64B_10MB_Value(benchmark::State& state) {\n  benchPumpTo(state, 64, 163840, VALUE_DEFAULT);\n}\nstatic void PumpTo_256B_10MB_Value(benchmark::State& state) {\n  benchPumpTo(state, 256, 40960, VALUE_DEFAULT);\n}\nstatic void PumpTo_1KB_10MB_Value(benchmark::State& state) {\n  benchPumpTo(state, 1024, 10240, VALUE_DEFAULT);\n}\n\n// =============================================================================\n// Register benchmarks\n// =============================================================================\n\n// Sync 1 MiB — value streams\nWD_BENCHMARK(PumpTo_64B_Value);\nWD_BENCHMARK(PumpTo_256B_Value);\nWD_BENCHMARK(PumpTo_1KB_Value);\nWD_BENCHMARK(PumpTo_4KB_Value);\nWD_BENCHMARK(PumpTo_16KB_Value);\nWD_BENCHMARK(PumpTo_64KB_Value);\n\n// Sync 1 MiB — byte streams\nWD_BENCHMARK(PumpTo_64B_Byte);\nWD_BENCHMARK(PumpTo_256B_Byte);\nWD_BENCHMARK(PumpTo_1KB_Byte);\nWD_BENCHMARK(PumpTo_4KB_Byte);\nWD_BENCHMARK(PumpTo_16KB_Byte);\nWD_BENCHMARK(PumpTo_64KB_Byte);\n\n// I/O latency — 64 KiB (no-regression check)\nWD_BENCHMARK(PumpTo_256B_IoLatency);\nWD_BENCHMARK(PumpTo_4KB_IoLatency);\nWD_BENCHMARK(PumpTo_64KB_IoLatency);\n\n// Large payload — 10 MiB value streams\nWD_BENCHMARK(PumpTo_64B_10MB_Value);\nWD_BENCHMARK(PumpTo_256B_10MB_Value);\nWD_BENCHMARK(PumpTo_1KB_10MB_Value);\n\n}  // namespace\n}  // namespace workerd::api::streams\n"
  },
  {
    "path": "src/workerd/tests/bench-regex.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/tests/bench-tools.h>\n#include <workerd/tests/test-fixture.h>\n\n#include <kj/test.h>\n\n// A benchmark for regular expressions performance. Note that this is at least in part benchmarking\n// V8's Regex implementation.\n\nnamespace workerd {\nnamespace {\n\nstruct RegExpBenchmark: public benchmark::Fixture {\n  virtual ~RegExpBenchmark() noexcept(true) {}\n\n  void SetUp(benchmark::State& state) noexcept(true) override {\n    TestFixture::SetupParams params = {.mainModuleSource = R\"(\n        export default {\n          async fetch(request, env, ctx) {\n            const body = await request.text();\n            // Common english language words, derived from \"12dicts\", which is in the public domain.\n            // The first 600 words were chosen to have words with shared substrings and require compiling a longer expression.\n            const dict_exp = new RegExp(\"^(A|a|aback|abacus|abandon|abandoned|abandonment|abashed|abate|abbey|abbr.|abbreviate|abbreviation|ABC|ABC's|abdicate|abdication|abdomen|abdominal|abduct|abduction|aberration|abet|abhor|abhorrence|abhorrent|abide|abiding|ability|abject|ablaze|able|able-bodied|ably|abnormal|abnormality|abnormally|aboard|abolish|abolition|abolitionist|abominable|aboriginal|aborigine|abort|abortion|abortive|abound|about|about-face|above|aboveboard|abrasive|abrasively|abreast|abridge|abridgment|abroad|abrupt|abruptly|abruptness|abscess|abscond|absence|absent|absentee|absenteeism|absently|absent-minded|absent-mindedly|absent-mindedness|absolute|absolutely|absolve|absorb|absorbed|absorbent|absorbing|absorption|abstain|abstention|abstinence|abstinent|abstract|abstraction|absurd|absurdity|absurdly|abundance|abundant|abundantly|abuse|abusive|abysmal|abysmally|abyss|AC|academic|academically|academy|accelerate|acceleration|accelerator|accent|accented|accentuate|accept|acceptability|acceptable|acceptably|acceptance|accepted|access|accessibility|accessible|accessory|accident|accidental|accidentally|accident-prone|acclaim|acclaimed|acclimate|acclimation|accolade|accommodate|accommodating|accommodation|accommodations|accompaniment|accompanist|accompany|accomplice|accomplish|accomplished|accomplishment|accord|accordance|accordingly|according to|accordion|accost|account|accountability|accountable|accountant|accounting|accreditation|accredited|accrue|accumulate|accumulation|accuracy|accurate|accurately|accusation|accuse|accused|accuser|accusing|accusingly|accustom|accustomed|ace|acerbic|ache|achieve|achievement|achiever|Achilles' heel|achy|acid|acidic|acidity|acid rain|acknowledge|acknowledged|acknowledgment|acne|acorn|acoustic|acoustics|acquaint|acquaintance|acquainted|acquiesce|acquiescence|acquire|acquisition|acquit|acquittal|acre|acrid|acrimonious|acrimony|acrobat|acrobatic|acrobatics|acronym|across|across from|across-the-board|acrylic|ACT|act|acting|action|activate|activation|active|activism|activist|activity|actor|actress|actual|actuality|actualization|actually|acumen|acupuncture|acute|acute angle|acutely|ad|A.D.|adage|adamant|adamantly|Adam's apple|adapt|adaptable|adaptation|adapter|add|addict|addicted|addiction|addictive|addition|additional|additionally|additive|address|adept|adeptly|adequacy|adequate|adequately|adhere|adherence|adherent|adhesion|adhesive|ad hoc|adjacent|adjectival|adjective|adjoin|adjoining|adjourn|adjournment|adjudicate|adjudicator|adjunct|adjust|adjustable|adjustment|ad lib|ad-lib|administer|administration|administrative|administrator|admirable|admirably|admiral|admiration|admire|admirer|admiring|admiringly|admissible|admission|admit|admittance|admittedly|admonish|admonition|adobe|adolescence|adolescent|adopt|adopted|adoption|adoptive|adorable|adoration|adore|adorn|adornment|adrenaline|adrift|adroit|adroitly|adulation|adult|adulterate|adulteration|adultery|advance|advanced|advancement|advantage|advantageous|Advent|advent|adventure|adventurer|adventurous|adverb|adverbial|adversary|adverse|adversely|adversity|advertise|advertisement|advertiser|advertising|advice|advisable|advise|adviser|advisory|advocacy|advocate|aerial|aerobic|aerobics|aerodynamic|aerodynamics|aerosol|aerospace|aesthetic|aesthetically|aesthetics|afar|affable|affably|affair|affairs|affect|affectation|affected|affection|affectionate|affectionately|affidavit|affiliate|affiliated|affiliation|affinity|affirm|affirmation|affirmative|affirmative action|affirmatively|affix|afflict|affliction|affluence|affluent|afford|affordable|affront|afloat|afraid|afresh|Africa|African|African-American|after|aftereffect|afterlife|aftermath|afternoon|aftershave|aftershock|afterthought|afterward|afterwards|again|against|age|aged|agency|agenda|agent|ages|aggravate|aggravating|aggravation|aggression|aggressive|aggressively|aggressiveness|aggressor|aggrieved|aghast|agile|agility|aging|agitate|agitated|agitation|agitator|agnostic|agnosticism|ago|agonize|agonized|agonizing|agonizingly|agony|agree|agreeable|agreeably|agreed|agreement|agricultural|agriculture|ah|aha|ahead|aid|aide|AIDS|ailing|ailment|aim|aimless|aimlessly|ain't|air|air bag|air base|airborne|air-conditioned|air conditioner|air conditioning|aircraft|aircraft carrier|airfare|airfield|air force|airily|airing|airless|airline|airliner|airmail|airplane|airport|air raid|airs|airspace|airstrip|airtight|air time|air traffic controller|airwaves|airy|aisle|ajar|akin|a la carte|a la mode|alarm|alarm clock|alarmed|alarming|alarmingly|alarmist|alas|albeit|albino|album|alcohol|alcoholic|alcoholism|alcove|alderman|alderwoman|ale|alert|alfalfa|algae|algebra|algebraic|algorithm|alias|alibi|alien|alienate|alienation|alight|align|alignment|alike|alimony|alive|alkali|alkaline|all|Allah|all-American|all-around|allay|all-clear|allegation|allege|alleged|allegedly|allegiance|allegorical|allegory|allergic|allergy|alleviate|alleviation|alley|alliance|allied|alligator|all-inclusive|allocate|allocation|allot|allotment|all-out|allow|allowable|allowance|alloy|all right|all-star|allude|allure|alluring|allusion|ally|alma mater|almanac|almighty|almond|almost|alms|aloft|aloha|alone|along|alongside|aloof|aloud|alphabet|alphabetical|alphabetically|alpine|already|alright|also|altar|alter|alteration|altercation|alternate|alternately|alternation|alternative|alternatively|although|altitude|alto|altogether|altruism|altruistic|aluminum|alumna|alumnae|alumni|alumnus|always|AM|am|A.M.|amalgamate|amalgamation|amass|amateur)$\");\n            if (dict_exp.test(body)) {\n              return new Response(\"word found in dictionary\");\n            }\n            return new Response(\"error: word not found\", {status: 400});\n          }\n        }\n      )\"_kj};\n    fixture = kj::heap<TestFixture>(kj::mv(params));\n  }\n\n  void TearDown(benchmark::State& state) noexcept(true) override {\n    fixture = nullptr;\n  }\n\n  kj::Own<TestFixture> fixture;\n};\n\nBENCHMARK_F(RegExpBenchmark, request)(benchmark::State& state) {\n  for (auto _: state) {\n    auto result =\n        fixture->runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"accepted\"_kj);\n    KJ_EXPECT(result.statusCode == 200 && result.body == \"word found in dictionary\"_kj);\n    auto result2 =\n        fixture->runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"invalid\"_kj);\n    KJ_EXPECT(result2.statusCode == 400 && result2.body == \"error: word not found\"_kj);\n  }\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/tests/bench-response.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/api/http.h>\n#include <workerd/tests/bench-tools.h>\n#include <workerd/tests/test-fixture.h>\n\n// A benchmark for Response object construction to identify performance bottlenecks.\n\nnamespace workerd {\nnamespace {\n\nstruct Response: public benchmark::Fixture {\n  virtual ~Response() noexcept(true) {}\n\n  void SetUp(benchmark::State& state) noexcept(true) override {\n    fixture = kj::heap<TestFixture>();\n  }\n\n  void TearDown(benchmark::State& state) noexcept(true) override {\n    fixture = nullptr;\n  }\n\n  kj::Own<TestFixture> fixture;\n};\n\n// Benchmark: Simple string body Response (most common case)\n// Pattern: new Response(\"Hello World\")\nBENCHMARK_F(Response, simpleStringBody)(benchmark::State& state) {\n  fixture->runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    for (auto _: state) {\n      auto body = api::Body::Initializer(kj::str(\"Hello World\"));\n      benchmark::DoNotOptimize(api::Response::constructor(js, kj::mv(body), kj::none));\n    }\n  });\n}\n\n// Benchmark: Response with empty body (null)\n// Pattern: new Response(null, {status: 404})\nBENCHMARK_F(Response, nullBodyWithStatus)(benchmark::State& state) {\n  fixture->runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    for (auto _: state) {\n      api::Response::InitializerDict init;\n      init.status = 404;\n      benchmark::DoNotOptimize(api::Response::constructor(js, kj::none, kj::mv(init)));\n    }\n  });\n}\n\n// Benchmark: Response with headers\n// Pattern: new Response(\"body\", {headers: {\"Content-Type\": \"text/html\"}})\nBENCHMARK_F(Response, bodyWithHeaders)(benchmark::State& state) {\n  fixture->runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    for (auto _: state) {\n      api::Response::InitializerDict init;\n      jsg::Dict<kj::String, kj::String> headersDict;\n      headersDict.fields = kj::heapArray<jsg::Dict<kj::String, kj::String>::Field>(1);\n      headersDict.fields[0].name = kj::str(\"Content-Type\");\n      headersDict.fields[0].value = kj::str(\"text/html\");\n      init.headers = kj::mv(headersDict);\n\n      auto body = api::Body::Initializer(kj::str(\"Hello World\"));\n      benchmark::DoNotOptimize(api::Response::constructor(js, kj::mv(body), kj::mv(init)));\n    }\n  });\n}\n\n// Benchmark: Response with ArrayBuffer body\n// Pattern: new Response(arrayBuffer)\nBENCHMARK_F(Response, arrayBufferBody)(benchmark::State& state) {\n  fixture->runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    for (auto _: state) {\n      auto bytes = kj::heapArray<byte>(11);\n      memcpy(bytes.begin(), \"Hello World\", 11);\n      auto body = api::Body::Initializer(kj::mv(bytes));\n      benchmark::DoNotOptimize(api::Response::constructor(js, kj::mv(body), kj::none));\n    }\n  });\n}\n\n// Benchmark: Response.json()\n// Pattern: Response.json({key: \"value\"})\nBENCHMARK_F(Response, jsonResponse)(benchmark::State& state) {\n  fixture->runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    // Prepare object to serialize. Do this outside the loop to avoid measuring its repeated\n    // construction cost. What we want to measure is just the cost of the api::Response::json_ call.\n    auto obj = js.obj();\n    obj.set(js, \"key\"_kj, js.str(\"value\"_kj));\n    for (auto _: state) {\n      benchmark::DoNotOptimize(api::Response::json_(js, obj, kj::none));\n    }\n  });\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/tests/bench-stream-piping.c++",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// Benchmark to compare stream piping implementations:\n// 1. Existing approach (ReadableStream::pumpTo via PumpToReader) - uses JS promise-based loop\n// 2. New approach (ReadableSourceKjAdapter::pumpTo) - uses DrainingReader to pull all\n//    synchronously available data at once, then writes with vectored I/O\n//\n// Run with: bazel run --config=opt //src/workerd/tests:bench-stream-piping\n\n#include <workerd/api/streams/readable-source-adapter.h>\n#include <workerd/api/streams/standard.h>\n#include <workerd/api/streams/writable-sink.h>\n#include <workerd/api/system-streams.h>\n#include <workerd/tests/bench-tools.h>\n#include <workerd/tests/test-fixture.h>\n\n#include <kj/compat/http.h>\n\nnamespace workerd::api::streams {\nnamespace {\n\n// =============================================================================\n// Stream configuration types\n// =============================================================================\n\nenum class StreamType {\n  VALUE,             // Default ReadableStreamDefaultController\n  BYTE,              // ReadableByteStreamController\n  SLOW_VALUE,        // Value stream that produces one chunk per microtask (async)\n  IO_LATENCY_VALUE,  // Value stream that yields to KJ event loop between chunks\n  IO_LATENCY_BYTE,   // Byte stream that yields to KJ event loop between chunks\n  TIMED_VALUE,       // Value stream with configurable timer delay between chunks\n};\n\nstruct StreamConfig {\n  StreamType type = StreamType::VALUE;\n  kj::Maybe<size_t> autoAllocateChunkSize;         // Only valid for BYTE streams\n  kj::Duration chunkDelay = 0 * kj::MILLISECONDS;  // Delay between chunks for TIMED_* streams\n  double highWaterMark = 0;                        // 0 means default (pull on demand)\n};\n\n// =============================================================================\n// Test utilities\n// =============================================================================\n\n// A discarding sink that just counts bytes written (more representative of real network I/O).\nstruct DiscardingSink final: public kj::AsyncOutputStream {\n  size_t bytesWritten = 0;\n  size_t writeCount = 0;\n\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override {\n    writeCount++;\n    bytesWritten += buffer.size();\n    co_return;\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {\n    writeCount++;\n    for (auto piece: pieces) {\n      bytesWritten += piece.size();\n    }\n    co_return;\n  }\n\n  kj::Promise<void> whenWriteDisconnected() override {\n    return kj::NEVER_DONE;\n  }\n\n  void reset() {\n    bytesWritten = 0;\n    writeCount = 0;\n  }\n};\n\n// A sink that simulates network backpressure with configurable latency per write.\n// This represents real-world scenarios where the downstream connection is slower\n// than the upstream source (e.g., slow client, congested network).\nstruct LatencySink final: public kj::AsyncOutputStream {\n  kj::Timer& timer;\n  kj::Duration writeLatency;\n  size_t bytesWritten = 0;\n  size_t writeCount = 0;\n\n  LatencySink(kj::Timer& timer, kj::Duration writeLatency)\n      : timer(timer),\n        writeLatency(writeLatency) {}\n\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override {\n    writeCount++;\n    bytesWritten += buffer.size();\n    if (writeLatency > 0 * kj::MILLISECONDS) {\n      co_await timer.afterDelay(writeLatency);\n    }\n    co_return;\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {\n    writeCount++;\n    for (auto piece: pieces) {\n      bytesWritten += piece.size();\n    }\n    if (writeLatency > 0 * kj::MILLISECONDS) {\n      co_await timer.afterDelay(writeLatency);\n    }\n    co_return;\n  }\n\n  kj::Promise<void> whenWriteDisconnected() override {\n    return kj::NEVER_DONE;\n  }\n\n  void reset() {\n    bytesWritten = 0;\n    writeCount = 0;\n  }\n};\n\n// Creates a JS-backed ReadableStream with the specified configuration.\n// Uses a counter pointer similar to the unit tests in readable-source-adapter-test.c++.\nstatic size_t benchChunkCounterStatic = 0;\n\njsg::Ref<ReadableStream> createValueStream(\n    jsg::Lock& js, size_t chunkSize, size_t numChunks, double highWaterMark, size_t* counter) {\n  return ReadableStream::constructor(js,\n      UnderlyingSource{\n        .pull =\n            [chunkSize, numChunks, counter](jsg::Lock& js, auto controller) {\n    auto& c =\n        KJ_ASSERT_NONNULL(controller.template tryGet<jsg::Ref<ReadableStreamDefaultController>>());\n\n    if ((*counter)++ < numChunks) {\n      auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, chunkSize);\n      jsg::BufferSource buffer(js, kj::mv(backing));\n      buffer.asArrayPtr().fill(0xAB);\n      c->enqueue(js, buffer.getHandle(js));\n    }\n    if (*counter == numChunks) {\n      c->close(js);\n    }\n    return js.resolvedPromise();\n  },\n        .expectedLength = chunkSize * numChunks,\n      },\n      StreamQueuingStrategy{\n        .highWaterMark = highWaterMark,\n      });\n}\n\njsg::Ref<ReadableStream> createByteStream(jsg::Lock& js,\n    size_t chunkSize,\n    size_t numChunks,\n    kj::Maybe<size_t> autoAllocateChunkSize,\n    double highWaterMark,\n    size_t* counter) {\n  return ReadableStream::constructor(js,\n      UnderlyingSource{\n        .type = kj::str(\"bytes\"),\n        .autoAllocateChunkSize = autoAllocateChunkSize,\n        .pull =\n            [chunkSize, numChunks, counter](jsg::Lock& js, auto controller) {\n    auto& c =\n        KJ_ASSERT_NONNULL(controller.template tryGet<jsg::Ref<ReadableByteStreamController>>());\n\n    if ((*counter)++ < numChunks) {\n      auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, chunkSize);\n      jsg::BufferSource buffer(js, kj::mv(backing));\n      buffer.asArrayPtr().fill(0xAB);\n      c->enqueue(js, kj::mv(buffer));\n    }\n    if (*counter == numChunks) {\n      c->close(js);\n    }\n    return js.resolvedPromise();\n  },\n        .expectedLength = chunkSize * numChunks,\n      },\n      StreamQueuingStrategy{\n        .highWaterMark = highWaterMark,\n      });\n}\n\n// Creates a \"slow\" value stream that produces one chunk per microtask.\n// This simulates a stream where pull() has async work to do before data is ready.\n// The pull() function returns a promise that resolves on the next microtask,\n// and only enqueues data WHEN the promise resolves.\n//\n// NOTE: This does NOT prevent batching or trigger the adaptive read policy!\n// Microtask delays execute synchronously within the JS event loop turn, so\n// readInternal's promise chain runs to completion before returning to KJ.\n// The buffer still fills completely, achieving full batching (100 chunks → 1 write).\n// See PUMP_PERFORMANCE_ANALYSIS.md section 9 for detailed analysis.\njsg::Ref<ReadableStream> createSlowValueStream(\n    jsg::Lock& js, size_t chunkSize, size_t numChunks, double highWaterMark, size_t* counter) {\n  return ReadableStream::constructor(js,\n      UnderlyingSource{\n        .pull =\n            [chunkSize, numChunks, counter](jsg::Lock& js, auto controller) {\n    auto& c =\n        KJ_ASSERT_NONNULL(controller.template tryGet<jsg::Ref<ReadableStreamDefaultController>>());\n\n    if (*counter >= numChunks) {\n      c->close(js);\n      return js.resolvedPromise();\n    }\n\n    // Return a promise that enqueues data on the next microtask.\n    // This adds a tiny delay per chunk, but does NOT prevent batching -\n    // the entire promise chain still runs within one JS event loop turn.\n    auto cRef = c.addRef();\n    return js.resolvedPromise().then(js,\n        JSG_VISITABLE_LAMBDA(\n            (cRef = kj::mv(cRef), chunkSize, numChunks, counter), (cRef), (jsg::Lock & js) mutable {\n              if ((*counter)++ < numChunks) {\n              auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, chunkSize);\n              jsg::BufferSource buffer(js, kj::mv(backing));\n              buffer.asArrayPtr().fill(0xAB);\n              cRef->enqueue(js, buffer.getHandle(js));\n              }\n              if (*counter == numChunks) {\n              cRef->close(js);\n              }\n              return js.resolvedPromise();\n            }));\n  },\n        .expectedLength = chunkSize * numChunks,\n      },\n      StreamQueuingStrategy{\n        .highWaterMark = highWaterMark,\n      });\n}\n\n// Creates a value stream that yields to the KJ event loop between chunks.\n// This simulates a network stream (like fetch response body) where data arrives with real\n// I/O latency. Unlike the \"slow\" stream that uses microtask delays, this stream's pull()\n// returns a promise that only resolves after a KJ event loop iteration.\n//\n// This WILL cause pumpReadImpl to return early, potentially triggering the adaptive read policy.\n// See PUMP_PERFORMANCE_ANALYSIS.md section 9 for why this is different from microtask delays.\njsg::Ref<ReadableStream> createIoLatencyValueStream(\n    jsg::Lock& js, size_t chunkSize, size_t numChunks, double highWaterMark, size_t* counter) {\n  return ReadableStream::constructor(js,\n      UnderlyingSource{\n        .pull =\n            [chunkSize, numChunks, counter](jsg::Lock& js, auto controller) {\n    auto& c =\n        KJ_ASSERT_NONNULL(controller.template tryGet<jsg::Ref<ReadableStreamDefaultController>>());\n\n    if (*counter >= numChunks) {\n      c->close(js);\n      return js.resolvedPromise();\n    }\n\n    // Use IoContext.awaitIo() to wait for a KJ event loop yield.\n    // This simulates real network I/O latency where we yield to KJ between chunks.\n    // kj::evalLater() schedules on the next KJ event loop iteration.\n    auto& ioContext = IoContext::current();\n    auto cRef = c.addRef();\n    return ioContext.awaitIo(js, kj::evalLater([]() {}),\n        JSG_VISITABLE_LAMBDA(\n            (cRef = kj::mv(cRef), chunkSize, numChunks, counter), (cRef), (jsg::Lock & js) mutable {\n              if ((*counter)++ < numChunks) {\n              auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, chunkSize);\n              jsg::BufferSource buffer(js, kj::mv(backing));\n              buffer.asArrayPtr().fill(0xAB);\n              cRef->enqueue(js, buffer.getHandle(js));\n              }\n              if (*counter == numChunks) {\n              cRef->close(js);\n              }\n            }));\n  },\n        .expectedLength = chunkSize * numChunks,\n      },\n      StreamQueuingStrategy{\n        .highWaterMark = highWaterMark,\n      });\n}\n\n// Creates a byte stream that yields to the KJ event loop between chunks.\n// Same as createIoLatencyValueStream but uses ReadableByteStreamController.\njsg::Ref<ReadableStream> createIoLatencyByteStream(\n    jsg::Lock& js, size_t chunkSize, size_t numChunks, double highWaterMark, size_t* counter) {\n  return ReadableStream::constructor(js,\n      UnderlyingSource{\n        .type = kj::str(\"bytes\"),\n        .pull =\n            [chunkSize, numChunks, counter](jsg::Lock& js, auto controller) {\n    auto& c =\n        KJ_ASSERT_NONNULL(controller.template tryGet<jsg::Ref<ReadableByteStreamController>>());\n\n    if (*counter >= numChunks) {\n      c->close(js);\n      return js.resolvedPromise();\n    }\n\n    auto& ioContext = IoContext::current();\n    auto cRef = c.addRef();\n    return ioContext.awaitIo(js, kj::evalLater([]() {}),\n        JSG_VISITABLE_LAMBDA(\n            (cRef = kj::mv(cRef), chunkSize, numChunks, counter), (cRef), (jsg::Lock & js) mutable {\n              if ((*counter)++ < numChunks) {\n              auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, chunkSize);\n              jsg::BufferSource buffer(js, kj::mv(backing));\n              buffer.asArrayPtr().fill(0xAB);\n              cRef->enqueue(js, kj::mv(buffer));\n              }\n              if (*counter == numChunks) {\n              cRef->close(js);\n              }\n            }));\n  },\n        .expectedLength = chunkSize * numChunks,\n      },\n      StreamQueuingStrategy{\n        .highWaterMark = highWaterMark,\n      });\n}\n\n// Creates a value stream with actual timer-based delays between chunks.\n// This simulates real network I/O where data arrives with measurable latency.\n// Unlike evalLater() which resumes immediately, timer delays represent real wall-clock time.\n//\n// With delays, we can observe:\n// 1. How throughput scales with I/O latency\n// 2. Whether double-buffering provides real overlap benefit\n// 3. The true cost of per-chunk I/O operations\njsg::Ref<ReadableStream> createTimedValueStream(jsg::Lock& js,\n    size_t chunkSize,\n    size_t numChunks,\n    double highWaterMark,\n    kj::Duration delay,\n    size_t* counter) {\n  return ReadableStream::constructor(js,\n      UnderlyingSource{\n        .pull =\n            [chunkSize, numChunks, delay, counter](jsg::Lock& js, auto controller) {\n    auto& c =\n        KJ_ASSERT_NONNULL(controller.template tryGet<jsg::Ref<ReadableStreamDefaultController>>());\n\n    if (*counter >= numChunks) {\n      c->close(js);\n      return js.resolvedPromise();\n    }\n\n    // Use afterLimitTimeout for actual timer-based delay\n    auto& ioContext = IoContext::current();\n    auto cRef = c.addRef();\n    return ioContext.awaitIo(js, ioContext.afterLimitTimeout(delay),\n        JSG_VISITABLE_LAMBDA(\n            (cRef = kj::mv(cRef), chunkSize, numChunks, counter), (cRef), (jsg::Lock & js) mutable {\n              if ((*counter)++ < numChunks) {\n              auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, chunkSize);\n              jsg::BufferSource buffer(js, kj::mv(backing));\n              buffer.asArrayPtr().fill(0xAB);\n              cRef->enqueue(js, buffer.getHandle(js));\n              }\n              if (*counter == numChunks) {\n              cRef->close(js);\n              }\n            }));\n  },\n        .expectedLength = chunkSize * numChunks,\n      },\n      StreamQueuingStrategy{\n        .highWaterMark = highWaterMark,\n      });\n}\n\njsg::Ref<ReadableStream> createConfiguredStream(\n    jsg::Lock& js, size_t chunkSize, size_t numChunks, const StreamConfig& config) {\n  benchChunkCounterStatic = 0;\n  size_t* counter = &benchChunkCounterStatic;\n\n  switch (config.type) {\n    case StreamType::VALUE:\n      return createValueStream(js, chunkSize, numChunks, config.highWaterMark, counter);\n    case StreamType::BYTE:\n      return createByteStream(\n          js, chunkSize, numChunks, config.autoAllocateChunkSize, config.highWaterMark, counter);\n    case StreamType::SLOW_VALUE:\n      return createSlowValueStream(js, chunkSize, numChunks, config.highWaterMark, counter);\n    case StreamType::IO_LATENCY_VALUE:\n      return createIoLatencyValueStream(js, chunkSize, numChunks, config.highWaterMark, counter);\n    case StreamType::IO_LATENCY_BYTE:\n      return createIoLatencyByteStream(js, chunkSize, numChunks, config.highWaterMark, counter);\n    case StreamType::TIMED_VALUE:\n      return createTimedValueStream(\n          js, chunkSize, numChunks, config.highWaterMark, config.chunkDelay, counter);\n  }\n  KJ_UNREACHABLE;\n}\n\n// =============================================================================\n// Benchmark: New approach using ReadableSourceKjAdapter::pumpTo\n// =============================================================================\n\nstatic void benchNewApproachPumpTo(\n    benchmark::State& state, size_t chunkSize, size_t numChunks, const StreamConfig& config) {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  // Always enable spec-compliant autoAllocateChunkSize behavior for the \"New\" approach.\n  // This is the behavior we're adopting going forward. When enabled, byte streams without\n  // explicit autoAllocateChunkSize will use DEFAULT reads (16KB buffer, no ByobRequest)\n  // instead of the legacy BYOB reads (4KB buffer, ByobRequest available).\n  // The \"Existing\" benchmarks keep the legacy behavior for comparison.\n  flags.setNoAutoAllocateChunkSize(true);\n  // Enable real timers for streams that need actual timer functionality (e.g., TIMED_VALUE).\n  bool needsRealTimers = config.type == StreamType::TIMED_VALUE;\n  TestFixture fixture({.featureFlags = flags.asReader(), .useRealTimers = needsRealTimers});\n\n  DiscardingSink sink;\n  size_t expectedBytes = chunkSize * numChunks;\n\n  for (auto _: state) {\n    sink.reset();\n    kj::Own<kj::AsyncOutputStream> fakeOwn(&sink, kj::NullDisposer::instance);\n    auto writableSink = newWritableSink(kj::mv(fakeOwn));\n\n    fixture.runInIoContext([&](const TestFixture::Environment& env) {\n      auto stream = createConfiguredStream(env.js, chunkSize, numChunks, config);\n      auto adapter = kj::heap<ReadableSourceKjAdapter>(env.js, env.context, stream.addRef());\n      return adapter->pumpTo(*writableSink, EndAfterPump::YES).attach(kj::mv(adapter));\n    });\n\n    // Verify all expected bytes were written\n    KJ_ASSERT(sink.bytesWritten == expectedBytes, \"New approach: expected\", expectedBytes,\n        \"bytes but got\", sink.bytesWritten);\n  }\n\n  state.SetBytesProcessed(\n      state.iterations() * static_cast<int64_t>(chunkSize) * static_cast<int64_t>(numChunks));\n  state.counters[\"WriteOps\"] =\n      benchmark::Counter(sink.writeCount, benchmark::Counter::kAvgIterations);\n}\n\n// =============================================================================\n// Benchmark: Existing approach using ReadableStream::pumpTo (PumpToReader)\n// =============================================================================\n\nstatic void benchExistingApproachPumpTo(\n    benchmark::State& state, size_t chunkSize, size_t numChunks, const StreamConfig& config) {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  // Keep legacy autoAllocateChunkSize behavior for \"Existing\" benchmarks.\n  // This uses 4KB BYOB reads with ByobRequest available, which is the original workerd behavior.\n  // The \"New\" benchmarks use spec-compliant behavior for comparison.\n  flags.setNoAutoAllocateChunkSize(false);\n  // Enable real timers for streams that need actual timer functionality (e.g., TIMED_VALUE).\n  bool needsRealTimers = config.type == StreamType::TIMED_VALUE;\n  TestFixture fixture({.featureFlags = flags.asReader(), .useRealTimers = needsRealTimers});\n\n  DiscardingSink sink;\n  size_t expectedBytes = chunkSize * numChunks;\n\n  for (auto _: state) {\n    sink.reset();\n\n    fixture.runInIoContext([&](const TestFixture::Environment& env) {\n      auto stream = createConfiguredStream(env.js, chunkSize, numChunks, config);\n\n      kj::Own<kj::AsyncOutputStream> fakeOwn(&sink, kj::NullDisposer::instance);\n      auto writableSink = newSystemStream(kj::mv(fakeOwn), StreamEncoding::IDENTITY, env.context);\n\n      return env.context.waitForDeferredProxy(stream->pumpTo(env.js, kj::mv(writableSink), true));\n    });\n\n    // Verify all expected bytes were written\n    KJ_ASSERT(sink.bytesWritten == expectedBytes, \"Existing approach: expected\", expectedBytes,\n        \"bytes but got\", sink.bytesWritten);\n  }\n\n  state.SetBytesProcessed(\n      state.iterations() * static_cast<int64_t>(chunkSize) * static_cast<int64_t>(numChunks));\n  state.counters[\"WriteOps\"] =\n      benchmark::Counter(sink.writeCount, benchmark::Counter::kAvgIterations);\n}\n\n// =============================================================================\n// Stream configurations to benchmark\n// =============================================================================\n\n// Value stream with default highWaterMark (0)\nstatic const StreamConfig VALUE_DEFAULT{\n  .type = StreamType::VALUE,\n  .autoAllocateChunkSize = kj::none,\n  .highWaterMark = 0,\n};\n\n// Value stream with 16KB highWaterMark\nstatic const StreamConfig VALUE_HWM_16K{\n  .type = StreamType::VALUE,\n  .autoAllocateChunkSize = kj::none,\n  .highWaterMark = 16 * 1024,\n};\n\n// Byte stream without autoAllocateChunkSize, default highWaterMark\nstatic const StreamConfig BYTE_DEFAULT{\n  .type = StreamType::BYTE,\n  .autoAllocateChunkSize = kj::none,\n  .highWaterMark = 0,\n};\n\n// Byte stream with autoAllocateChunkSize=64KB (fixed), default highWaterMark\nstatic const StreamConfig BYTE_AUTO_64K{\n  .type = StreamType::BYTE,\n  .autoAllocateChunkSize = 65536,\n  .highWaterMark = 0,\n};\n\n// Byte stream without autoAllocateChunkSize, 16KB highWaterMark\nstatic const StreamConfig BYTE_HWM_16K{\n  .type = StreamType::BYTE,\n  .autoAllocateChunkSize = kj::none,\n  .highWaterMark = 16 * 1024,\n};\n\n// Byte stream with autoAllocateChunkSize=64KB, 16KB highWaterMark\nstatic const StreamConfig BYTE_AUTO_64K_HWM_16K{\n  .type = StreamType::BYTE,\n  .autoAllocateChunkSize = 65536,\n  .highWaterMark = 16 * 1024,\n};\n\n// Slow value stream (async, one chunk per microtask) - does NOT trigger adaptive read policy\n// because microtasks execute synchronously within the JS event loop turn.\nstatic const StreamConfig SLOW_VALUE_DEFAULT{\n  .type = StreamType::SLOW_VALUE,\n  .autoAllocateChunkSize = kj::none,\n  .highWaterMark = 0,\n};\n\n// I/O latency value stream - yields to KJ event loop between chunks, simulating network I/O.\n// This DOES trigger early returns from pumpReadImpl and may activate the adaptive policy.\nstatic const StreamConfig IO_LATENCY_VALUE_DEFAULT{\n  .type = StreamType::IO_LATENCY_VALUE,\n  .autoAllocateChunkSize = kj::none,\n  .highWaterMark = 0,\n};\n\n// I/O latency byte stream - same as above but using ReadableByteStreamController.\n// Tests how byte streams interact with I/O latency patterns.\nstatic const StreamConfig IO_LATENCY_BYTE_DEFAULT{\n  .type = StreamType::IO_LATENCY_BYTE,\n  .autoAllocateChunkSize = kj::none,\n  .highWaterMark = 0,\n};\n\n// Timed value streams - actual timer-based delays between chunks.\n// These simulate real network I/O with measurable latency.\n// The delay represents the time waiting for the next chunk from the network.\n\n// 10μs delay - fast network, minimal latency (e.g., local network)\nstatic const StreamConfig TIMED_VALUE_10US{\n  .type = StreamType::TIMED_VALUE,\n  .autoAllocateChunkSize = kj::none,\n  .chunkDelay = 10 * kj::MICROSECONDS,\n  .highWaterMark = 0,\n};\n\n// 100μs delay - typical datacenter latency\nstatic const StreamConfig TIMED_VALUE_100US{\n  .type = StreamType::TIMED_VALUE,\n  .autoAllocateChunkSize = kj::none,\n  .chunkDelay = 100 * kj::MICROSECONDS,\n  .highWaterMark = 0,\n};\n\n// 1ms delay - typical internet latency / slow upstream\nstatic const StreamConfig TIMED_VALUE_1MS{\n  .type = StreamType::TIMED_VALUE,\n  .autoAllocateChunkSize = kj::none,\n  .chunkDelay = 1 * kj::MILLISECONDS,\n  .highWaterMark = 0,\n};\n\n// =============================================================================\n// Chunk size configurations\n// =============================================================================\n\n// Tiny chunks (worst case for JS overhead): 64 * 256 = 16,384 bytes\nstatic constexpr size_t TINY_CHUNK_SIZE = 64;\nstatic constexpr size_t TINY_NUM_CHUNKS = 256;\n\n// Small chunks (chatty protocol pattern): 256 * 100 = 25,600 bytes\nstatic constexpr size_t SMALL_CHUNK_SIZE = 256;\nstatic constexpr size_t SMALL_NUM_CHUNKS = 100;\n\n// Medium chunks (typical HTTP response): 4096 * 100 = 409,600 bytes (~400KB)\nstatic constexpr size_t MEDIUM_CHUNK_SIZE = 4096;\nstatic constexpr size_t MEDIUM_NUM_CHUNKS = 100;\n\n// Large chunks (file transfer pattern): 65536 * 16 = 1,048,576 bytes (1MB)\nstatic constexpr size_t LARGE_CHUNK_SIZE = 65536;\nstatic constexpr size_t LARGE_NUM_CHUNKS = 16;\n\n// =============================================================================\n// Benchmark functions - Value streams\n// =============================================================================\n\n// Value stream, default HWM\nstatic void New_Tiny_Value(benchmark::State& state) {\n  benchNewApproachPumpTo(state, TINY_CHUNK_SIZE, TINY_NUM_CHUNKS, VALUE_DEFAULT);\n}\nstatic void Existing_Tiny_Value(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, TINY_CHUNK_SIZE, TINY_NUM_CHUNKS, VALUE_DEFAULT);\n}\nstatic void New_Small_Value(benchmark::State& state) {\n  benchNewApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, VALUE_DEFAULT);\n}\nstatic void Existing_Small_Value(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, VALUE_DEFAULT);\n}\nstatic void New_Medium_Value(benchmark::State& state) {\n  benchNewApproachPumpTo(state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, VALUE_DEFAULT);\n}\nstatic void Existing_Medium_Value(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, VALUE_DEFAULT);\n}\nstatic void New_Large_Value(benchmark::State& state) {\n  benchNewApproachPumpTo(state, LARGE_CHUNK_SIZE, LARGE_NUM_CHUNKS, VALUE_DEFAULT);\n}\nstatic void Existing_Large_Value(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, LARGE_CHUNK_SIZE, LARGE_NUM_CHUNKS, VALUE_DEFAULT);\n}\n\n// Value stream, 16KB HWM\nstatic void New_Tiny_Value_HWM16K(benchmark::State& state) {\n  benchNewApproachPumpTo(state, TINY_CHUNK_SIZE, TINY_NUM_CHUNKS, VALUE_HWM_16K);\n}\nstatic void Existing_Tiny_Value_HWM16K(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, TINY_CHUNK_SIZE, TINY_NUM_CHUNKS, VALUE_HWM_16K);\n}\nstatic void New_Small_Value_HWM16K(benchmark::State& state) {\n  benchNewApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, VALUE_HWM_16K);\n}\nstatic void Existing_Small_Value_HWM16K(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, VALUE_HWM_16K);\n}\nstatic void New_Medium_Value_HWM16K(benchmark::State& state) {\n  benchNewApproachPumpTo(state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, VALUE_HWM_16K);\n}\nstatic void Existing_Medium_Value_HWM16K(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, VALUE_HWM_16K);\n}\nstatic void New_Large_Value_HWM16K(benchmark::State& state) {\n  benchNewApproachPumpTo(state, LARGE_CHUNK_SIZE, LARGE_NUM_CHUNKS, VALUE_HWM_16K);\n}\nstatic void Existing_Large_Value_HWM16K(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, LARGE_CHUNK_SIZE, LARGE_NUM_CHUNKS, VALUE_HWM_16K);\n}\n\n// =============================================================================\n// Benchmark functions - Byte streams, no autoAllocate\n// =============================================================================\n\n// Byte stream, default HWM, no autoAllocate\nstatic void New_Tiny_Byte(benchmark::State& state) {\n  benchNewApproachPumpTo(state, TINY_CHUNK_SIZE, TINY_NUM_CHUNKS, BYTE_DEFAULT);\n}\nstatic void Existing_Tiny_Byte(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, TINY_CHUNK_SIZE, TINY_NUM_CHUNKS, BYTE_DEFAULT);\n}\nstatic void New_Small_Byte(benchmark::State& state) {\n  benchNewApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, BYTE_DEFAULT);\n}\nstatic void Existing_Small_Byte(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, BYTE_DEFAULT);\n}\nstatic void New_Medium_Byte(benchmark::State& state) {\n  benchNewApproachPumpTo(state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, BYTE_DEFAULT);\n}\nstatic void Existing_Medium_Byte(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, BYTE_DEFAULT);\n}\nstatic void New_Large_Byte(benchmark::State& state) {\n  benchNewApproachPumpTo(state, LARGE_CHUNK_SIZE, LARGE_NUM_CHUNKS, BYTE_DEFAULT);\n}\nstatic void Existing_Large_Byte(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, LARGE_CHUNK_SIZE, LARGE_NUM_CHUNKS, BYTE_DEFAULT);\n}\n\n// Byte stream, 16KB HWM, no autoAllocate\nstatic void New_Tiny_Byte_HWM16K(benchmark::State& state) {\n  benchNewApproachPumpTo(state, TINY_CHUNK_SIZE, TINY_NUM_CHUNKS, BYTE_HWM_16K);\n}\nstatic void Existing_Tiny_Byte_HWM16K(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, TINY_CHUNK_SIZE, TINY_NUM_CHUNKS, BYTE_HWM_16K);\n}\nstatic void New_Small_Byte_HWM16K(benchmark::State& state) {\n  benchNewApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, BYTE_HWM_16K);\n}\nstatic void Existing_Small_Byte_HWM16K(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, BYTE_HWM_16K);\n}\nstatic void New_Medium_Byte_HWM16K(benchmark::State& state) {\n  benchNewApproachPumpTo(state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, BYTE_HWM_16K);\n}\nstatic void Existing_Medium_Byte_HWM16K(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, BYTE_HWM_16K);\n}\nstatic void New_Large_Byte_HWM16K(benchmark::State& state) {\n  benchNewApproachPumpTo(state, LARGE_CHUNK_SIZE, LARGE_NUM_CHUNKS, BYTE_HWM_16K);\n}\nstatic void Existing_Large_Byte_HWM16K(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, LARGE_CHUNK_SIZE, LARGE_NUM_CHUNKS, BYTE_HWM_16K);\n}\n\n// =============================================================================\n// Benchmark functions - Byte streams with autoAllocate=64KB\n// =============================================================================\n\n// Byte stream, default HWM, autoAllocate=64KB\nstatic void New_Tiny_Byte_Auto64K(benchmark::State& state) {\n  benchNewApproachPumpTo(state, TINY_CHUNK_SIZE, TINY_NUM_CHUNKS, BYTE_AUTO_64K);\n}\nstatic void Existing_Tiny_Byte_Auto64K(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, TINY_CHUNK_SIZE, TINY_NUM_CHUNKS, BYTE_AUTO_64K);\n}\nstatic void New_Small_Byte_Auto64K(benchmark::State& state) {\n  benchNewApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, BYTE_AUTO_64K);\n}\nstatic void Existing_Small_Byte_Auto64K(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, BYTE_AUTO_64K);\n}\nstatic void New_Medium_Byte_Auto64K(benchmark::State& state) {\n  benchNewApproachPumpTo(state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, BYTE_AUTO_64K);\n}\nstatic void Existing_Medium_Byte_Auto64K(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, BYTE_AUTO_64K);\n}\nstatic void New_Large_Byte_Auto64K(benchmark::State& state) {\n  benchNewApproachPumpTo(state, LARGE_CHUNK_SIZE, LARGE_NUM_CHUNKS, BYTE_AUTO_64K);\n}\nstatic void Existing_Large_Byte_Auto64K(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, LARGE_CHUNK_SIZE, LARGE_NUM_CHUNKS, BYTE_AUTO_64K);\n}\n\n// Byte stream, 16KB HWM, autoAllocate=64KB\nstatic void New_Tiny_Byte_Auto64K_HWM16K(benchmark::State& state) {\n  benchNewApproachPumpTo(state, TINY_CHUNK_SIZE, TINY_NUM_CHUNKS, BYTE_AUTO_64K_HWM_16K);\n}\nstatic void Existing_Tiny_Byte_Auto64K_HWM16K(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, TINY_CHUNK_SIZE, TINY_NUM_CHUNKS, BYTE_AUTO_64K_HWM_16K);\n}\nstatic void New_Small_Byte_Auto64K_HWM16K(benchmark::State& state) {\n  benchNewApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, BYTE_AUTO_64K_HWM_16K);\n}\nstatic void Existing_Small_Byte_Auto64K_HWM16K(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, BYTE_AUTO_64K_HWM_16K);\n}\nstatic void New_Medium_Byte_Auto64K_HWM16K(benchmark::State& state) {\n  benchNewApproachPumpTo(state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, BYTE_AUTO_64K_HWM_16K);\n}\nstatic void Existing_Medium_Byte_Auto64K_HWM16K(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, BYTE_AUTO_64K_HWM_16K);\n}\nstatic void New_Large_Byte_Auto64K_HWM16K(benchmark::State& state) {\n  benchNewApproachPumpTo(state, LARGE_CHUNK_SIZE, LARGE_NUM_CHUNKS, BYTE_AUTO_64K_HWM_16K);\n}\nstatic void Existing_Large_Byte_Auto64K_HWM16K(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, LARGE_CHUNK_SIZE, LARGE_NUM_CHUNKS, BYTE_AUTO_64K_HWM_16K);\n}\n\n// =============================================================================\n// Benchmark functions - Slow value streams (async with microtask delays)\n// =============================================================================\n\n// Slow value stream - these produce one chunk per microtask, adding processing overhead.\n// Note: This does NOT trigger the adaptive read policy because microtask delays don't\n// cause early returns from pumpReadImpl. The policy would only activate with real I/O\n// latency that blocks the KJ event loop. See PUMP_PERFORMANCE_ANALYSIS.md section 9.\nstatic void New_Small_SlowValue(benchmark::State& state) {\n  benchNewApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, SLOW_VALUE_DEFAULT);\n}\nstatic void Existing_Small_SlowValue(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, SLOW_VALUE_DEFAULT);\n}\nstatic void New_Medium_SlowValue(benchmark::State& state) {\n  benchNewApproachPumpTo(state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, SLOW_VALUE_DEFAULT);\n}\nstatic void Existing_Medium_SlowValue(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, SLOW_VALUE_DEFAULT);\n}\n\n// =============================================================================\n// Benchmark functions - I/O latency streams (real KJ event loop yields)\n// =============================================================================\n\n// I/O latency streams yield to the KJ event loop between chunks, simulating real network I/O.\n// This tests how the adaptive read policy responds to streams with actual I/O latency,\n// unlike microtask-based \"slow\" streams which complete within one JS event loop turn.\n//\n// Key differences from SlowValue:\n// - Each chunk requires a KJ event loop iteration (not just a microtask)\n// - pumpReadImpl returns early after each chunk\n// - Adaptive policy may switch to IMMEDIATE mode after observing small reads\n\n// I/O latency value streams\nstatic void New_Small_IoLatencyValue(benchmark::State& state) {\n  benchNewApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, IO_LATENCY_VALUE_DEFAULT);\n}\nstatic void Existing_Small_IoLatencyValue(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, IO_LATENCY_VALUE_DEFAULT);\n}\nstatic void New_Medium_IoLatencyValue(benchmark::State& state) {\n  benchNewApproachPumpTo(state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, IO_LATENCY_VALUE_DEFAULT);\n}\nstatic void Existing_Medium_IoLatencyValue(benchmark::State& state) {\n  benchExistingApproachPumpTo(\n      state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, IO_LATENCY_VALUE_DEFAULT);\n}\nstatic void New_Large_IoLatencyValue(benchmark::State& state) {\n  benchNewApproachPumpTo(state, LARGE_CHUNK_SIZE, LARGE_NUM_CHUNKS, IO_LATENCY_VALUE_DEFAULT);\n}\nstatic void Existing_Large_IoLatencyValue(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, LARGE_CHUNK_SIZE, LARGE_NUM_CHUNKS, IO_LATENCY_VALUE_DEFAULT);\n}\n\n// I/O latency byte streams\nstatic void New_Small_IoLatencyByte(benchmark::State& state) {\n  benchNewApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, IO_LATENCY_BYTE_DEFAULT);\n}\nstatic void Existing_Small_IoLatencyByte(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, IO_LATENCY_BYTE_DEFAULT);\n}\nstatic void New_Medium_IoLatencyByte(benchmark::State& state) {\n  benchNewApproachPumpTo(state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, IO_LATENCY_BYTE_DEFAULT);\n}\nstatic void Existing_Medium_IoLatencyByte(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, IO_LATENCY_BYTE_DEFAULT);\n}\nstatic void New_Large_IoLatencyByte(benchmark::State& state) {\n  benchNewApproachPumpTo(state, LARGE_CHUNK_SIZE, LARGE_NUM_CHUNKS, IO_LATENCY_BYTE_DEFAULT);\n}\nstatic void Existing_Large_IoLatencyByte(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, LARGE_CHUNK_SIZE, LARGE_NUM_CHUNKS, IO_LATENCY_BYTE_DEFAULT);\n}\n\n// =============================================================================\n// Benchmark functions - Timed value streams (real timer-based delays)\n// =============================================================================\n\n// These benchmarks use actual timer delays to simulate real network I/O.\n// Unlike evalLater() which resumes immediately, these represent real wall-clock time.\n// We test with small chunks to see how latency affects batching behavior.\n\n// 10μs delay per chunk (1ms total for 100 chunks)\nstatic void New_Small_Timed10us(benchmark::State& state) {\n  benchNewApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, TIMED_VALUE_10US);\n}\nstatic void Existing_Small_Timed10us(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, TIMED_VALUE_10US);\n}\n\n// 100μs delay per chunk (10ms total for 100 chunks)\nstatic void New_Small_Timed100us(benchmark::State& state) {\n  benchNewApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, TIMED_VALUE_100US);\n}\nstatic void Existing_Small_Timed100us(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, TIMED_VALUE_100US);\n}\n\n// 1ms delay per chunk (100ms total for 100 chunks) - very slow, representative of slow network\nstatic void New_Small_Timed1ms(benchmark::State& state) {\n  benchNewApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, TIMED_VALUE_1MS);\n}\nstatic void Existing_Small_Timed1ms(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, SMALL_CHUNK_SIZE, SMALL_NUM_CHUNKS, TIMED_VALUE_1MS);\n}\n\n// Medium chunks with 100μs delay - tests larger chunk batching with latency\nstatic void New_Medium_Timed100us(benchmark::State& state) {\n  benchNewApproachPumpTo(state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, TIMED_VALUE_100US);\n}\nstatic void Existing_Medium_Timed100us(benchmark::State& state) {\n  benchExistingApproachPumpTo(state, MEDIUM_CHUNK_SIZE, MEDIUM_NUM_CHUNKS, TIMED_VALUE_100US);\n}\n\n// =============================================================================\n// Register benchmarks - organized by chunk size for easy comparison\n// =============================================================================\n\n// Tiny chunks - all configurations\nWD_BENCHMARK(New_Tiny_Value);\nWD_BENCHMARK(Existing_Tiny_Value);\nWD_BENCHMARK(New_Tiny_Value_HWM16K);\nWD_BENCHMARK(Existing_Tiny_Value_HWM16K);\nWD_BENCHMARK(New_Tiny_Byte);\nWD_BENCHMARK(Existing_Tiny_Byte);\nWD_BENCHMARK(New_Tiny_Byte_HWM16K);\nWD_BENCHMARK(Existing_Tiny_Byte_HWM16K);\nWD_BENCHMARK(New_Tiny_Byte_Auto64K);\nWD_BENCHMARK(Existing_Tiny_Byte_Auto64K);\nWD_BENCHMARK(New_Tiny_Byte_Auto64K_HWM16K);\nWD_BENCHMARK(Existing_Tiny_Byte_Auto64K_HWM16K);\n\n// Small chunks - all configurations\nWD_BENCHMARK(New_Small_Value);\nWD_BENCHMARK(Existing_Small_Value);\nWD_BENCHMARK(New_Small_Value_HWM16K);\nWD_BENCHMARK(Existing_Small_Value_HWM16K);\nWD_BENCHMARK(New_Small_Byte);\nWD_BENCHMARK(Existing_Small_Byte);\nWD_BENCHMARK(New_Small_Byte_HWM16K);\nWD_BENCHMARK(Existing_Small_Byte_HWM16K);\nWD_BENCHMARK(New_Small_Byte_Auto64K);\nWD_BENCHMARK(Existing_Small_Byte_Auto64K);\nWD_BENCHMARK(New_Small_Byte_Auto64K_HWM16K);\nWD_BENCHMARK(Existing_Small_Byte_Auto64K_HWM16K);\n\n// Medium chunks - all configurations\nWD_BENCHMARK(New_Medium_Value);\nWD_BENCHMARK(Existing_Medium_Value);\nWD_BENCHMARK(New_Medium_Value_HWM16K);\nWD_BENCHMARK(Existing_Medium_Value_HWM16K);\nWD_BENCHMARK(New_Medium_Byte);\nWD_BENCHMARK(Existing_Medium_Byte);\nWD_BENCHMARK(New_Medium_Byte_HWM16K);\nWD_BENCHMARK(Existing_Medium_Byte_HWM16K);\nWD_BENCHMARK(New_Medium_Byte_Auto64K);\nWD_BENCHMARK(Existing_Medium_Byte_Auto64K);\nWD_BENCHMARK(New_Medium_Byte_Auto64K_HWM16K);\nWD_BENCHMARK(Existing_Medium_Byte_Auto64K_HWM16K);\n\n// Large chunks - all configurations\nWD_BENCHMARK(New_Large_Value);\nWD_BENCHMARK(Existing_Large_Value);\nWD_BENCHMARK(New_Large_Value_HWM16K);\nWD_BENCHMARK(Existing_Large_Value_HWM16K);\nWD_BENCHMARK(New_Large_Byte);\nWD_BENCHMARK(Existing_Large_Byte);\nWD_BENCHMARK(New_Large_Byte_HWM16K);\nWD_BENCHMARK(Existing_Large_Byte_HWM16K);\nWD_BENCHMARK(New_Large_Byte_Auto64K);\nWD_BENCHMARK(Existing_Large_Byte_Auto64K);\nWD_BENCHMARK(New_Large_Byte_Auto64K_HWM16K);\nWD_BENCHMARK(Existing_Large_Byte_Auto64K_HWM16K);\n\n// Slow value stream - async streams with microtask delays (tests batching overhead)\nWD_BENCHMARK(New_Small_SlowValue);\nWD_BENCHMARK(Existing_Small_SlowValue);\nWD_BENCHMARK(New_Medium_SlowValue);\nWD_BENCHMARK(Existing_Medium_SlowValue);\n\n// I/O latency streams - real KJ event loop yields (simulates network I/O)\n// These test how the adaptive read policy behaves with actual I/O latency\nWD_BENCHMARK(New_Small_IoLatencyValue);\nWD_BENCHMARK(Existing_Small_IoLatencyValue);\nWD_BENCHMARK(New_Medium_IoLatencyValue);\nWD_BENCHMARK(Existing_Medium_IoLatencyValue);\nWD_BENCHMARK(New_Large_IoLatencyValue);\nWD_BENCHMARK(Existing_Large_IoLatencyValue);\nWD_BENCHMARK(New_Small_IoLatencyByte);\nWD_BENCHMARK(Existing_Small_IoLatencyByte);\nWD_BENCHMARK(New_Medium_IoLatencyByte);\nWD_BENCHMARK(Existing_Medium_IoLatencyByte);\nWD_BENCHMARK(New_Large_IoLatencyByte);\nWD_BENCHMARK(Existing_Large_IoLatencyByte);\n\n// Timed stream benchmarks - uses real timers via useRealTimers=true in SetupParams.\n// These simulate actual blocking I/O with timer delays between chunks.\nWD_BENCHMARK(New_Small_Timed10us);\nWD_BENCHMARK(Existing_Small_Timed10us);\nWD_BENCHMARK(New_Small_Timed100us);\nWD_BENCHMARK(Existing_Small_Timed100us);\nWD_BENCHMARK(New_Small_Timed1ms);\nWD_BENCHMARK(Existing_Small_Timed1ms);\nWD_BENCHMARK(New_Medium_Timed100us);\nWD_BENCHMARK(Existing_Medium_Timed100us);\n\n// =============================================================================\n// maxRead limit benchmarks - tests maxRead guard effectiveness\n// =============================================================================\n\n// These benchmarks test the maxRead limit with large finite streams.\n// They demonstrate that maxRead properly limits how much data is read in a single\n// draining read operation, even when more data is synchronously available.\n//\n// The benchmarks compare different maxRead limits to show:\n// 1. How maxRead affects batching behavior and write coalescing\n// 2. Whether smaller maxRead values add overhead\n// 3. The trade-off between limiting reads vs throughput\n\n// Configuration for streams with more chunks than we want to read\nstatic constexpr size_t LARGE_STREAM_CHUNKS = 10000;    // 10K chunks available\nstatic constexpr size_t LARGE_STREAM_CHUNK_SIZE = 256;  // 256 bytes per chunk = 2.56MB total\n\n// Stream config for a large finite value stream\nstatic const StreamConfig LARGE_VALUE_STREAM{\n  .type = StreamType::VALUE,\n  .autoAllocateChunkSize = kj::none,\n  .highWaterMark = 0,\n};\n\n// Benchmark using ReadableSourceKjAdapter::pumpTo with large streams (unlimited maxRead).\nstatic void New_LargeStream_Value(benchmark::State& state) {\n  benchNewApproachPumpTo(state, LARGE_STREAM_CHUNK_SIZE, LARGE_STREAM_CHUNKS, LARGE_VALUE_STREAM);\n}\nstatic void Existing_LargeStream_Value(benchmark::State& state) {\n  benchExistingApproachPumpTo(\n      state, LARGE_STREAM_CHUNK_SIZE, LARGE_STREAM_CHUNKS, LARGE_VALUE_STREAM);\n}\n\n// =============================================================================\n// DrainingReader maxRead limit benchmarks\n// =============================================================================\n\n// These benchmarks directly test DrainingReader::read with different maxRead limits.\n// Each iteration performs multiple reads until the stream is exhausted, allowing us\n// to measure the overhead of different maxRead limits.\n\nstatic void benchDrainingReaderMaxRead(benchmark::State& state,\n    size_t chunkSize,\n    size_t numChunks,\n    size_t maxReadLimit,\n    bool byteStream) {\n  capnp::MallocMessageBuilder message;\n  auto flags = message.initRoot<CompatibilityFlags>();\n  flags.setStreamsJavaScriptControllers(true);\n  // Use spec-compliant autoAllocateChunkSize behavior for DrainingReader benchmarks.\n  flags.setNoAutoAllocateChunkSize(true);\n  TestFixture fixture({.featureFlags = flags.asReader()});\n\n  size_t totalBytes = chunkSize * numChunks;\n  size_t readCount = 0;\n\n  for (auto _: state) {\n    benchChunkCounterStatic = 0;\n    size_t bytesRead = 0;\n    readCount = 0;\n\n    fixture.runInIoContext([&](const TestFixture::Environment& env) {\n      auto stream = byteStream\n          ? createByteStream(env.js, chunkSize, numChunks, kj::none, 0, &benchChunkCounterStatic)\n          : createValueStream(env.js, chunkSize, numChunks, 0, &benchChunkCounterStatic);\n\n      auto maybeReader = DrainingReader::create(env.js, *stream);\n      KJ_ASSERT(maybeReader != kj::none, \"Failed to create DrainingReader\");\n      auto& reader = KJ_ASSERT_NONNULL(maybeReader);\n\n      // Read in chunks limited by maxReadLimit until stream is done\n      bool done = false;\n      while (!done) {\n        auto jsPromise = reader->read(env.js, maxReadLimit)\n                             .then(env.js, [&](jsg::Lock& js, DrainingReadResult&& result) {\n          for (auto& chunk: result.chunks) {\n            bytesRead += chunk.size();\n          }\n          done = result.done;\n          readCount++;\n        });\n        // Run the promise synchronously\n        env.js.runMicrotasks();\n      }\n\n      reader->releaseLock(env.js);\n      return kj::READY_NOW;\n    });\n\n    KJ_ASSERT(bytesRead == totalBytes, \"Expected\", totalBytes, \"bytes but got\", bytesRead);\n  }\n\n  state.SetBytesProcessed(state.iterations() * static_cast<int64_t>(totalBytes));\n  state.counters[\"ReadCalls\"] = benchmark::Counter(readCount, benchmark::Counter::kAvgIterations);\n  state.counters[\"BytesPerRead\"] =\n      benchmark::Counter(totalBytes / readCount, benchmark::Counter::kAvgIterations);\n}\n\n// Value stream benchmarks with different maxRead limits\n// Using 1000 chunks of 1KB each = 1MB total data\n\n// maxRead = 16KB (small limit, many read calls)\nstatic void DrainingRead_Value_MaxRead_16KB(benchmark::State& state) {\n  benchDrainingReaderMaxRead(state, 1024, 1000, 16 * 1024, false);\n}\n\n// maxRead = 64KB\nstatic void DrainingRead_Value_MaxRead_64KB(benchmark::State& state) {\n  benchDrainingReaderMaxRead(state, 1024, 1000, 64 * 1024, false);\n}\n\n// maxRead = 256KB\nstatic void DrainingRead_Value_MaxRead_256KB(benchmark::State& state) {\n  benchDrainingReaderMaxRead(state, 1024, 1000, 256 * 1024, false);\n}\n\n// maxRead = 1MB (equals total data size)\nstatic void DrainingRead_Value_MaxRead_1MB(benchmark::State& state) {\n  benchDrainingReaderMaxRead(state, 1024, 1000, 1024 * 1024, false);\n}\n\n// maxRead = unlimited (default behavior)\nstatic void DrainingRead_Value_MaxRead_Unlimited(benchmark::State& state) {\n  benchDrainingReaderMaxRead(state, 1024, 1000, kj::maxValue, false);\n}\n\n// Byte stream benchmarks with different maxRead limits\nstatic void DrainingRead_Byte_MaxRead_16KB(benchmark::State& state) {\n  benchDrainingReaderMaxRead(state, 1024, 1000, 16 * 1024, true);\n}\n\nstatic void DrainingRead_Byte_MaxRead_64KB(benchmark::State& state) {\n  benchDrainingReaderMaxRead(state, 1024, 1000, 64 * 1024, true);\n}\n\nstatic void DrainingRead_Byte_MaxRead_256KB(benchmark::State& state) {\n  benchDrainingReaderMaxRead(state, 1024, 1000, 256 * 1024, true);\n}\n\nstatic void DrainingRead_Byte_MaxRead_1MB(benchmark::State& state) {\n  benchDrainingReaderMaxRead(state, 1024, 1000, 1024 * 1024, true);\n}\n\nstatic void DrainingRead_Byte_MaxRead_Unlimited(benchmark::State& state) {\n  benchDrainingReaderMaxRead(state, 1024, 1000, kj::maxValue, true);\n}\n\n// Small chunk benchmarks (64 bytes) - tests overhead with many small chunks\n// 16000 chunks of 64 bytes = 1MB total\nstatic void DrainingRead_SmallChunks_MaxRead_16KB(benchmark::State& state) {\n  benchDrainingReaderMaxRead(state, 64, 16000, 16 * 1024, false);\n}\n\nstatic void DrainingRead_SmallChunks_MaxRead_64KB(benchmark::State& state) {\n  benchDrainingReaderMaxRead(state, 64, 16000, 64 * 1024, false);\n}\n\nstatic void DrainingRead_SmallChunks_MaxRead_Unlimited(benchmark::State& state) {\n  benchDrainingReaderMaxRead(state, 64, 16000, kj::maxValue, false);\n}\n\n// =============================================================================\n// Large stream benchmarks - 10MB total to better exercise maxRead limits\n// =============================================================================\n\n// 10000 chunks of 1KB = 10MB total\nstatic void DrainingRead_Large_MaxRead_64KB(benchmark::State& state) {\n  benchDrainingReaderMaxRead(state, 1024, 10000, 64 * 1024, false);\n}\n\nstatic void DrainingRead_Large_MaxRead_256KB(benchmark::State& state) {\n  benchDrainingReaderMaxRead(state, 1024, 10000, 256 * 1024, false);\n}\n\nstatic void DrainingRead_Large_MaxRead_1MB(benchmark::State& state) {\n  benchDrainingReaderMaxRead(state, 1024, 10000, 1024 * 1024, false);\n}\n\nstatic void DrainingRead_Large_MaxRead_Unlimited(benchmark::State& state) {\n  benchDrainingReaderMaxRead(state, 1024, 10000, kj::maxValue, false);\n}\n\n// Register large stream benchmarks\nWD_BENCHMARK(New_LargeStream_Value);\nWD_BENCHMARK(Existing_LargeStream_Value);\n\n// Register maxRead limit benchmarks - value streams (1MB total, sync)\nWD_BENCHMARK(DrainingRead_Value_MaxRead_16KB);\nWD_BENCHMARK(DrainingRead_Value_MaxRead_64KB);\nWD_BENCHMARK(DrainingRead_Value_MaxRead_256KB);\nWD_BENCHMARK(DrainingRead_Value_MaxRead_1MB);\nWD_BENCHMARK(DrainingRead_Value_MaxRead_Unlimited);\n\n// Register maxRead limit benchmarks - byte streams (1MB total, sync)\nWD_BENCHMARK(DrainingRead_Byte_MaxRead_16KB);\nWD_BENCHMARK(DrainingRead_Byte_MaxRead_64KB);\nWD_BENCHMARK(DrainingRead_Byte_MaxRead_256KB);\nWD_BENCHMARK(DrainingRead_Byte_MaxRead_1MB);\nWD_BENCHMARK(DrainingRead_Byte_MaxRead_Unlimited);\n\n// Register small chunk benchmarks (1MB total, 64-byte chunks, sync)\nWD_BENCHMARK(DrainingRead_SmallChunks_MaxRead_16KB);\nWD_BENCHMARK(DrainingRead_SmallChunks_MaxRead_64KB);\nWD_BENCHMARK(DrainingRead_SmallChunks_MaxRead_Unlimited);\n\n// Register large stream benchmarks (10MB total, sync)\nWD_BENCHMARK(DrainingRead_Large_MaxRead_64KB);\nWD_BENCHMARK(DrainingRead_Large_MaxRead_256KB);\nWD_BENCHMARK(DrainingRead_Large_MaxRead_1MB);\nWD_BENCHMARK(DrainingRead_Large_MaxRead_Unlimited);\n\n}  // namespace\n}  // namespace workerd::api::streams\n"
  },
  {
    "path": "src/workerd/tests/bench-text-encoder.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/tests/bench-tools.h>\n#include <workerd/tests/test-fixture.h>\n\n// Benchmark for TextEncoder.encode() and TextEncoder.encodeInto() methods.\n// Tests performance across different character types (ASCII, one-byte UTF-8, two-byte UTF-8)\n// and various string lengths (32, 256, 1024, 8192 characters) to measure UTF-8 encoding overhead.\n\nnamespace workerd {\nnamespace {\n\nstruct TextEncoder: public benchmark::Fixture {\n  virtual ~TextEncoder() noexcept(true) {}\n\n  void SetUp(benchmark::State& state) noexcept(true) override {\n    TestFixture::SetupParams params = {.mainModuleSource = R\"(\n        const encoder = new TextEncoder();\n\n        export default {\n          async fetch(request) {\n            const url = new URL(request.url);\n            const len = parseInt(url.searchParams.get('len') || '256');\n            const type = url.searchParams.get('type') || 'ascii';\n            const op = url.searchParams.get('op') || 'encode';\n\n            let base = '';\n            switch (type) {\n              case 'ascii':\n                base = 'a';\n                break;\n              case 'one-byte':\n                base = '\\xff';\n                break;\n              case 'two-byte':\n                base = 'ğ';\n                break;\n            }\n\n            const input = base.repeat(len);\n\n            let result;\n            if (op === 'encode') {\n              for (let i = 0; i < 1000; i++) {\n                result = encoder.encode(input);\n              }\n              return new Response(result.length.toString());\n            } else if (op === 'encodeInto') {\n              const buffer = new Uint8Array(len * 3); // enough space for any UTF-8 encoding\n              for (let i = 0; i < 1000; i++) {\n                result = encoder.encodeInto(input, buffer);\n              }\n              return new Response(result.written.toString());\n            }\n\n            throw new Error('Invalid operation');\n          },\n        };\n      )\"_kj};\n    fixture = kj::heap<TestFixture>(kj::mv(params));\n  }\n\n  void TearDown(benchmark::State& state) noexcept(true) override {\n    fixture = nullptr;\n  }\n\n  kj::Own<TestFixture> fixture;\n};\n\n// Parameterized benchmark to avoid duplication\n// Args format: (operation, type, length)\n// operation: 0=encode, 1=encodeInto\n// type: 0=ascii, 1=one-byte, 2=two-byte\nBENCHMARK_DEFINE_F(TextEncoder, Parameterized)(benchmark::State& state) {\n  const char* op = state.range(0) == 0 ? \"encode\" : \"encodeInto\";\n  const char* type;\n  switch (state.range(1)) {\n    case 0:\n      type = \"ascii\";\n      break;\n    case 1:\n      type = \"one-byte\";\n      break;\n    case 2:\n      type = \"two-byte\";\n      break;\n    default:\n      type = \"ascii\";\n      break;\n  }\n  int64_t len = state.range(2);\n\n  auto url = kj::str(\"http://example.com?op=\", op, \"&type=\", type, \"&len=\", len);\n\n  for (auto _: state) {\n    benchmark::DoNotOptimize(fixture->runRequest(kj::HttpMethod::GET, url, \"\"_kj));\n  }\n}\n\n#define TEXT_ENCODER_BENCH(op_name, op_val, type_name, type_val, len)                              \\\n  BENCHMARK_REGISTER_F(TextEncoder, Parameterized)                                                 \\\n      ->Args({op_val, type_val, len})                                                              \\\n      ->Name(#op_name \"_\" #type_name \"_\" #len)\n\n// Note: Google Benchmark will append the arg values to the name (e.g., \"Encode_ASCII_32/0/0/32\")\n// where the trailing numbers are the actual argument values passed via ->Args():\n//   /0/0/32 = operation (0=encode, 1=encodeInto) / type (0=ascii, 1=one-byte, 2=two-byte) / length\nTEXT_ENCODER_BENCH(Encode, 0, ASCII, 0, 32);\nTEXT_ENCODER_BENCH(Encode, 0, ASCII, 0, 256);\nTEXT_ENCODER_BENCH(Encode, 0, ASCII, 0, 1024);\nTEXT_ENCODER_BENCH(Encode, 0, ASCII, 0, 8192);\nTEXT_ENCODER_BENCH(Encode, 0, OneByte, 1, 256);\nTEXT_ENCODER_BENCH(Encode, 0, OneByte, 1, 1024);\nTEXT_ENCODER_BENCH(Encode, 0, OneByte, 1, 8192);\nTEXT_ENCODER_BENCH(Encode, 0, TwoByte, 2, 256);\nTEXT_ENCODER_BENCH(Encode, 0, TwoByte, 2, 1024);\nTEXT_ENCODER_BENCH(Encode, 0, TwoByte, 2, 8192);\nTEXT_ENCODER_BENCH(EncodeInto, 1, ASCII, 0, 256);\nTEXT_ENCODER_BENCH(EncodeInto, 1, ASCII, 0, 1024);\nTEXT_ENCODER_BENCH(EncodeInto, 1, ASCII, 0, 8192);\nTEXT_ENCODER_BENCH(EncodeInto, 1, OneByte, 1, 256);\nTEXT_ENCODER_BENCH(EncodeInto, 1, OneByte, 1, 1024);\nTEXT_ENCODER_BENCH(EncodeInto, 1, OneByte, 1, 8192);\nTEXT_ENCODER_BENCH(EncodeInto, 1, TwoByte, 2, 256);\nTEXT_ENCODER_BENCH(EncodeInto, 1, TwoByte, 2, 1024);\nTEXT_ENCODER_BENCH(EncodeInto, 1, TwoByte, 2, 8192);\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/tests/bench-tools.h",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// Used to provide support tools for benchmarking. Many use cases will already be covered by the\n// microbenchmark API.\n\n#include <benchmark/benchmark.h>\n\n#include <kj/test.h>\n\n// Configure tcmalloc for deterministic benchmarks on Linux.\n// tcmalloc uses probabilistic heap sampling which can introduce variance in benchmark results.\n// WD_USE_TCMALLOC is defined when tcmalloc is enabled (Linux + use_tcmalloc flag).\n#ifdef WD_USE_TCMALLOC\n#include \"tcmalloc/malloc_extension.h\"\n\nnamespace workerd::bench {\n\nstruct TcmallocBenchmarkConfig {\n  TcmallocBenchmarkConfig() {\n    // Disable heap profiling sampling by setting interval to max value.\n    // Default is ~512KB which causes probabilistic sampling of allocations.\n    tcmalloc::MallocExtension::SetProfileSamplingInterval(std::numeric_limits<int64_t>::max());\n\n    // Disable GWP-ASan guarded sampling. A negative value disables it.\n    tcmalloc::MallocExtension::SetGuardedSamplingInterval(-1);\n\n    // Disable background memory release actions that can cause timing variance.\n    tcmalloc::MallocExtension::SetBackgroundProcessActionsEnabled(false);\n  }\n};\n\n// Global instance ensures configuration runs before main().\ninline TcmallocBenchmarkConfig tcmallocBenchmarkConfig;\n\n}  // namespace workerd::bench\n#endif  // defined(WD_USE_TCMALLOC)\n\n// Define a benchmark. Use microseconds instead of nanoseconds by default, most tests run long\n// enough to not need ns precision.\n#define WD_BENCHMARK(X) BENCHMARK(X)->Unit(benchmark::kMicrosecond)\n\n// Macro inspired by KJ_TEST() to enable benchmarking without requiring the benchmark::State\n// argument and make it easy to convert tests to benchmarks. Make sure the linker fails if tests are\n// not in anonymous namespaces.\n#define WD_BENCH(description)                                                                      \\\n  extern int KJ_CONCAT(YouMustWrapTestsInAnonymousNamespace, __COUNTER__) KJ_UNUSED;               \\\n  void KJ_UNIQUE_NAME(Bench)();                                                                    \\\n  void KJ_UNIQUE_NAME(BenchImpl)(benchmark::State & state) {                                       \\\n    for (auto _: state) {                                                                          \\\n      KJ_UNIQUE_NAME(Bench)();                                                                     \\\n    }                                                                                              \\\n  }                                                                                                \\\n  WD_BENCHMARK(KJ_UNIQUE_NAME(BenchImpl))->Name(description);                                      \\\n  void KJ_UNIQUE_NAME(Bench)()\n"
  },
  {
    "path": "src/workerd/tests/bench-util.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/setup.h>\n#include <workerd/jsg/util.h>\n#include <workerd/tests/bench-tools.h>\n#include <workerd/tests/test-fixture.h>\n\nnamespace workerd {\nnamespace {\n\n// Ref: https://developers.cloudflare.com/workers/runtime-apis/request/#the-cf-property-requestinitcfproperties\nstatic constexpr kj::StringPtr cfProperty = R\"DATA({\n  \"apps\": false,\n  \"cacheEverything\": false,\n  \"cacheKey\": \"my-cache-key\",\n  \"cacheTags\": [\"production\", \"development\"],\n  \"cacheTtl\": 3600,\n  \"cacheTtlByStatus\": { \"200-299\": 86400, \"404\": 1, \"500-599\": 0 },\n  \"image\": null,\n  \"mirage\": true,\n  \"polish\": \"lossless\",\n  \"scrapeShield\": true,\n  \"webp\": false\n})DATA\"_kjc;\n\nstatic void Util_RecursivelyFreeze(benchmark::State& state) {\n  TestFixture fixture;\n  fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    auto& js = env.js;\n    auto obj = jsg::check(v8::JSON::Parse(js.v8Context(), jsg::v8Str(js.v8Isolate, cfProperty)));\n\n    for (auto _: state) {\n      for (size_t i = 0; i < 1000; ++i) {\n        jsg::recursivelyFreeze(js.v8Context(), obj);\n        benchmark::DoNotOptimize(i);\n      }\n    }\n  });\n}\n\nWD_BENCHMARK(Util_RecursivelyFreeze);\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/tests/libreprl/libreprl.c",
    "content": "// Copyright 2020 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2019 Google LLC\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// https://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#ifndef _WIN32\n\n#ifndef _GNU_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include \"libreprl.h\"\n\n#include <assert.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <poll.h>\n#include <signal.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sched.h>\n#include <sys/mman.h>\n#include <sys/mount.h>\n#include <sys/resource.h>\n#include <sys/time.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <time.h>\n#include <unistd.h>\n\n// Well-known file descriptor numbers for reprl <-> child communication, child process side\n#define REPRL_CHILD_CTRL_IN 100\n#define REPRL_CHILD_CTRL_OUT 101\n#define REPRL_CHILD_DATA_IN 102\n#define REPRL_CHILD_DATA_OUT 103\n\n/// Maximum timeout in microseconds. Mostly just limited by the fact that the timeout in milliseconds has to fit into a 32-bit integer.\n#define REPRL_MAX_TIMEOUT_IN_MICROSECONDS ((uint64_t)(INT_MAX) * 1000)\n\nstatic size_t min(size_t x, size_t y) {\n  return x < y ? x : y;\n}\n\n#ifdef __linux__\n// This function creates the UID/GID mapping that we need inside of the user\n// namespace. This is needed such that the files we create have a proper owner\n// attached to them.\nstatic void write_id_maps(uid_t uid, gid_t gid) {\n    char setgroups_path[] = \"/proc/self/setgroups\";\n    char uid_map_path[] = \"/proc/self/uid_map\";\n    char gid_map_path[] = \"/proc/self/gid_map\";\n\n    int setgroups_fd = open(setgroups_path, O_WRONLY);\n    int uid_map_fd = open(uid_map_path, O_WRONLY);\n    int gid_map_fd = open(gid_map_path, O_WRONLY);\n\n    if (setgroups_fd == -1 || uid_map_fd == -1 || gid_map_fd == -1) {\n        fprintf(stderr, \"Error opening setgroups/uid_map/gid_map file: %s\\n\", strerror(errno));\n        _exit(-1);\n    }\n\n    // More context on this: https://lwn.net/Articles/626665/\n    dprintf(setgroups_fd, \"deny\");\n    dprintf(uid_map_fd, \"%d %d 1\", uid, uid);\n    dprintf(gid_map_fd, \"%d %d 1\", gid, gid);\n\n    close(setgroups_fd);\n    close(uid_map_fd);\n    close(gid_map_fd);\n}\n\n// Creates a tmpfs at `mount_point` in a new user namespace.\nstatic void create_tmpfs(const char* mount_point) {\n    // Get the UID and GID before we call unshare.\n    uid_t uid = getuid();\n    gid_t gid = getgid();\n\n    // We create a new user (CLONE_NEWUSER) and mount (CLONE_NEWNS)\n    // namespace here such that we can mount our own tmpfs onto\n    // mount_point that is only visible to this process.\n    if (unshare(CLONE_NEWUSER | CLONE_NEWNS) == -1) {\n        fprintf(stderr, \"unshare failed to create a new mount namespace in the child: %s\\n\", strerror(errno));\n        _exit(-1);\n    };\n\n    // Now write the UID / GID mappings\n    write_id_maps(uid, gid);\n\n    // Mount a new tmpfs onto `mount_point` this allows us to add files\n    // here that get automatically cleaned up once the process exits.\n    if (mount(\"tmpfs\", mount_point, \"tmpfs\", 0, nullptr) == -1) {\n        fprintf(stderr, \"mount failed to create a tmpfs in namespace in the child: %s\\n\", strerror(errno));\n        _exit(-1);\n    }\n}\n#endif\n\nstatic uint64_t current_usecs()\n{\n    struct timespec ts;\n    clock_gettime(CLOCK_MONOTONIC, &ts);\n    return ts.tv_sec * 1000000 + ts.tv_nsec / 1000;\n}\n\nstatic char** copy_string_array(const char** orig)\n{\n    size_t num_entries = 0;\n    for (const char** current = orig; *current; current++) {\n        num_entries += 1;\n    }\n    char** copy = (char**) calloc(num_entries + 1, sizeof(char*));\n    for (size_t i = 0; i < num_entries; i++) {\n        copy[i] = strdup(orig[i]);\n    }\n    return copy;\n}\n\nstatic void free_string_array(char** arr)\n{\n    if (!arr) return;\n    for (char** current = arr; *current; current++) {\n        free(*current);\n    }\n    free(arr);\n}\n\n// A unidirectional communication channel for larger amounts of data, up to a maximum size (REPRL_MAX_DATA_SIZE).\n// Implemented as a (RAM-backed) file for which the file descriptor is shared with the child process and which is mapped into our address space.\nstruct data_channel {\n    // File descriptor of the underlying file. Directly shared with the child process.\n    int fd;\n    // Memory mapping of the file, always of size REPRL_MAX_DATA_SIZE.\n    char* mapping;\n};\n\nstruct reprl_context {\n    // Whether reprl_initialize has been successfully performed on this context.\n    int initialized;\n\n    // Read file descriptor of the control pipe. Only valid if a child process is running (i.e. pid is nonzero).\n    int ctrl_in;\n    // Write file descriptor of the control pipe. Only valid if a child process is running (i.e. pid is nonzero).\n    int ctrl_out;\n\n    // Data channel REPRL -> Child\n    struct data_channel* data_in;\n    // Data channel Child -> REPRL\n    struct data_channel* data_out;\n\n    // Optional data channel for the child's stdout and stderr.\n    struct data_channel* child_stdout;\n    struct data_channel* child_stderr;\n\n    // PID of the child process. Will be zero if no child process is currently running.\n    pid_t pid;\n\n    // Arguments and environment for the child process.\n    char** argv;\n    char** envp;\n\n    // A malloc'd string containing a description of the last error that occurred.\n    char* last_error;\n};\n\nstatic int reprl_error(struct reprl_context* ctx, const char *format, ...)\n{\n    va_list args;\n    va_start(args, format);\n    free(ctx->last_error);\n    vasprintf(&ctx->last_error, format, args);\n    return -1;\n}\n\nstatic struct data_channel* reprl_create_data_channel(struct reprl_context* ctx)\n{\n#ifdef __linux__\n    int fd = memfd_create(\"REPRL_DATA_CHANNEL\", MFD_CLOEXEC);\n#else\n    char path[] = \"/tmp/reprl_data_channel_XXXXXXXX\";\n    int fd = mkostemp(path, O_CLOEXEC);\n    unlink(path);\n#endif\n    if (fd == -1 || ftruncate(fd, REPRL_MAX_DATA_SIZE) != 0) {\n        reprl_error(ctx, \"Failed to create data channel file: %s\", strerror(errno));\n        return nullptr;\n    }\n    char* mapping = (char*) mmap(nullptr, REPRL_MAX_DATA_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);\n    if (mapping == MAP_FAILED) {\n        reprl_error(ctx, \"Failed to mmap data channel file: %s\", strerror(errno));\n        return nullptr;\n    }\n\n    struct data_channel* channel = (struct data_channel*) malloc(sizeof(struct data_channel));\n    channel->fd = fd;\n    channel->mapping = mapping;\n    return channel;\n}\n\nstatic void reprl_destroy_data_channel(struct data_channel* channel)\n{\n    if (!channel) return;\n    close(channel->fd);\n    munmap(channel->mapping, REPRL_MAX_DATA_SIZE);\n    free(channel);\n}\n\nstatic void reprl_child_terminated(struct reprl_context* ctx)\n{\n    if (!ctx->pid) return;\n    ctx->pid = 0;\n    close(ctx->ctrl_in);\n    close(ctx->ctrl_out);\n}\n\nstatic void reprl_terminate_child(struct reprl_context* ctx)\n{\n    if (!ctx->pid) return;\n    int status;\n    kill(ctx->pid, SIGKILL);\n    waitpid(ctx->pid, &status, 0);\n    reprl_child_terminated(ctx);\n}\n\nstatic int reprl_spawn_child(struct reprl_context* ctx)\n{\n    // This is also a good time to ensure the data channel backing files don't grow too large.\n    ftruncate(ctx->data_in->fd, REPRL_MAX_DATA_SIZE);\n    ftruncate(ctx->data_out->fd, REPRL_MAX_DATA_SIZE);\n    if (ctx->child_stdout) ftruncate(ctx->child_stdout->fd, REPRL_MAX_DATA_SIZE);\n    if (ctx->child_stderr) ftruncate(ctx->child_stderr->fd, REPRL_MAX_DATA_SIZE);\n\n    int crpipe[2] = { 0, 0 };          // control pipe child -> reprl\n    int cwpipe[2] = { 0, 0 };          // control pipe reprl -> child\n\n    if (pipe(crpipe) != 0) {\n        return reprl_error(ctx, \"Could not create pipe for REPRL communication: %s\", strerror(errno));\n    }\n    if (pipe(cwpipe) != 0) {\n        close(crpipe[0]);\n        close(crpipe[1]);\n        return reprl_error(ctx, \"Could not create pipe for REPRL communication: %s\", strerror(errno));\n    }\n\n    ctx->ctrl_in = crpipe[0];\n    ctx->ctrl_out = cwpipe[1];\n    fcntl(ctx->ctrl_in, F_SETFD, FD_CLOEXEC);\n    fcntl(ctx->ctrl_out, F_SETFD, FD_CLOEXEC);\n\n#ifdef __linux__\n    // This is where we will mount our own tmpfs, this is intended to be used\n    // for targets like Chrome, where we have to pass the user data directory.\n    // Even if the target does not clean up after themselves, the tmpfs in the\n    // user namespace will be removed once the process exits. Also, every child\n    // process, i.e. fuzzing instance, can then have it's own tmpfs.\n    // This only works on Linux right now, which is where we fuzz Chrome, this\n    // won't work on any other OS.\n    const char mount_point[] = \"/tmp/fuzzilli_tmp\";\n\n    // Create the mountpoint for our tmpfs here. This is just an empty dir.\n    // We also do not really care if this directory exists, we just need it as\n    // a mountpoint.\n    if (mkdir(mount_point, 0)) {\n        if (errno != EEXIST) {\n          fprintf(stderr, \"mkdir failed to create %s to create a mountpoint: %s\\n\", mount_point, strerror(errno));\n        }\n    }\n#endif\n\n#ifdef __linux__\n    // Use vfork() on Linux as that considerably improves the fuzzer performance. See also https://github.com/googleprojectzero/fuzzilli/issues/174\n    // Due to vfork, the code executed in the child process *must not* modify any memory apart from its stack, as it will share the page table of its parent.\n    pid_t pid = vfork();\n#else\n    pid_t pid = fork();\n#endif\n    if (pid == 0) {\n        if (dup2(cwpipe[0], REPRL_CHILD_CTRL_IN) < 0 ||\n            dup2(crpipe[1], REPRL_CHILD_CTRL_OUT) < 0 ||\n            dup2(ctx->data_out->fd, REPRL_CHILD_DATA_IN) < 0 ||\n            dup2(ctx->data_in->fd, REPRL_CHILD_DATA_OUT) < 0) {\n            fprintf(stderr, \"dup2 failed in the child: %s\\n\", strerror(errno));\n            _exit(-1);\n        }\n\n#ifdef __linux__\n        // Set RLIMIT_CORE to 0, such that we don't produce core dumps. The\n        // added benefit of doing this here, in the child process, is that we\n        // can still get core dumps when Fuzzilli crashes.\n        struct rlimit core_limit;\n        core_limit.rlim_cur = 0;\n        core_limit.rlim_max = 0;\n        if (setrlimit(RLIMIT_CORE, &core_limit) < 0) {\n            fprintf(stderr, \"setrlimit failed in the child: %s\\n\", strerror(errno));\n            _exit(-1);\n        };\n#endif\n\n        // Unblock any blocked signals. It seems that libdispatch sometimes blocks delivery of certain signals.\n        sigset_t newset;\n        sigemptyset(&newset);\n        if (sigprocmask(SIG_SETMASK, &newset, nullptr) != 0) {\n            fprintf(stderr, \"sigprocmask failed in the child: %s\\n\", strerror(errno));\n            _exit(-1);\n        }\n\n        close(cwpipe[0]);\n        close(crpipe[1]);\n\n        int devnull = open(\"/dev/null\", O_RDWR);\n        dup2(devnull, 0);\n        if (ctx->child_stdout) dup2(ctx->child_stdout->fd, 1);\n        else dup2(devnull, 1);\n        if (ctx->child_stderr) dup2(ctx->child_stderr->fd, 2);\n        else dup2(devnull, 2);\n        close(devnull);\n\n#ifdef __linux__\n        // Create the tmpfs at the specific mount point here in the child process\n        // such that we have a tmpfs for this process only that will be cleaned up at process exit.\n        // This will also write into the necessary files in /proc, so we need to do this here after we've fork()'ed.\n        // This will only work on Linux, see the comment above where call mkdir.\n        create_tmpfs(mount_point);\n#endif\n\n        // close all other FDs. We try to use FD_CLOEXEC everywhere, but let's be extra sure we don't leak any fds to the child.\n        int tablesize = getdtablesize();\n        // Cap at reasonable limit - getdtablesize() can return RLIM_INFINITY which is huge\n        if (tablesize > 1024 || tablesize < 0) {\n            tablesize = 1024;\n        }\n        for (int i = 3; i < tablesize; i++) {\n            if (i == REPRL_CHILD_CTRL_IN || i == REPRL_CHILD_CTRL_OUT || i == REPRL_CHILD_DATA_IN || i == REPRL_CHILD_DATA_OUT) {\n                continue;\n            }\n            close(i);\n        }\n\n        execve(ctx->argv[0], ctx->argv, ctx->envp);\n\n        fprintf(stderr, \"Failed to execute child process %s: %s\\n\", ctx->argv[0], strerror(errno));\n        fflush(stderr);\n        _exit(-1);\n    }\n\n    close(crpipe[1]);\n    close(cwpipe[0]);\n\n    if (pid < 0) {\n        close(ctx->ctrl_in);\n        close(ctx->ctrl_out);\n        return reprl_error(ctx, \"Failed to fork: %s\", strerror(errno));\n    }\n    ctx->pid = pid;\n\n    char helo[5] = { 0 };\n    if (read(ctx->ctrl_in, helo, 4) != 4) {\n        reprl_terminate_child(ctx);\n        return reprl_error(ctx, \"Did not receive HELO message from child: %s\", strerror(errno));\n    }\n\n    if (strncmp(helo, \"HELO\", 4) != 0) {\n        reprl_terminate_child(ctx);\n        return reprl_error(ctx, \"Received invalid HELO message from child: %s\", helo);\n    }\n\n    if (write(ctx->ctrl_out, helo, 4) != 4) {\n        reprl_terminate_child(ctx);\n        return reprl_error(ctx, \"Failed to send HELO reply message to child: %s\", strerror(errno));\n    }\n\n#ifdef __linux__\n    struct rlimit core_limit = {};\n    if (prlimit(pid, RLIMIT_CORE, nullptr, &core_limit) < 0) {\n        reprl_terminate_child(ctx);\n        return reprl_error(ctx, \"prlimit failed: %s\\n\", strerror(errno));\n    }\n    if (core_limit.rlim_cur != 0 || core_limit.rlim_max != 0) {\n        reprl_terminate_child(ctx);\n        return reprl_error(ctx, \"Detected non-zero RLIMIT_CORE. Check that the child does not set RLIMIT_CORE manually.\\n\");\n    }\n#endif\n\n    return 0;\n}\n\nstruct reprl_context* reprl_create_context()\n{\n    // \"Reserve\" the well-known REPRL fds so no other fd collides with them.\n    // This would cause various kinds of issues in reprl_spawn_child.\n    // It would be enough to do this once per process in the case of multiple\n    // REPRL instances, but it's probably not worth the implementation effort.\n    int devnull = open(\"/dev/null\", O_RDWR);\n    dup2(devnull, REPRL_CHILD_CTRL_IN);\n    dup2(devnull, REPRL_CHILD_CTRL_OUT);\n    dup2(devnull, REPRL_CHILD_DATA_IN);\n    dup2(devnull, REPRL_CHILD_DATA_OUT);\n    close(devnull);\n\n    return (struct reprl_context*) calloc(1, sizeof(struct reprl_context));\n}\n\nint reprl_initialize_context(struct reprl_context* ctx, const char** argv, const char** envp, int capture_stdout, int capture_stderr)\n{\n    if (ctx->initialized) {\n        return reprl_error(ctx, \"Context is already initialized\");\n    }\n\n    // We need to ignore SIGPIPE since we could end up writing to a pipe after our child process has exited.\n    signal(SIGPIPE, SIG_IGN);\n\n    ctx->argv = copy_string_array(argv);\n    ctx->envp = copy_string_array(envp);\n\n    ctx->data_in = reprl_create_data_channel(ctx);\n    ctx->data_out = reprl_create_data_channel(ctx);\n    if (capture_stdout) {\n        ctx->child_stdout = reprl_create_data_channel(ctx);\n    }\n    if (capture_stderr) {\n        ctx->child_stderr = reprl_create_data_channel(ctx);\n    }\n    if (!ctx->data_in || !ctx->data_out || (capture_stdout && !ctx->child_stdout) || (capture_stderr && !ctx->child_stderr)) {\n        // Proper error message will have been set by reprl_create_data_channel\n        return -1;\n    }\n\n    ctx->initialized = 1;\n    return 0;\n}\n\nvoid reprl_destroy_context(struct reprl_context* ctx)\n{\n    reprl_terminate_child(ctx);\n\n    free_string_array(ctx->argv);\n    free_string_array(ctx->envp);\n\n    reprl_destroy_data_channel(ctx->data_in);\n    reprl_destroy_data_channel(ctx->data_out);\n    reprl_destroy_data_channel(ctx->child_stdout);\n    reprl_destroy_data_channel(ctx->child_stderr);\n\n    free(ctx->last_error);\n    free(ctx);\n}\n\nint reprl_execute(struct reprl_context* ctx, const char* script, uint64_t script_size, uint64_t timeout, uint64_t* execution_time, int fresh_instance)\n{\n    if (!ctx->initialized) {\n        return reprl_error(ctx, \"REPRL context is not initialized\");\n    }\n\n    if (script_size > REPRL_MAX_DATA_SIZE) {\n        return reprl_error(ctx, \"Script too large\");\n    }\n\n    if (timeout > REPRL_MAX_TIMEOUT_IN_MICROSECONDS) {\n        return reprl_error(ctx, \"Timeout too large\");\n    }\n    int timeout_ms = (int)(timeout / 1000);\n\n    // Terminate any existing instance if requested.\n    if (fresh_instance && ctx->pid) {\n        reprl_terminate_child(ctx);\n    }\n\n    // Reset file position so the child can simply read(2) and write(2) to these fds.\n    lseek(ctx->data_out->fd, 0, SEEK_SET);\n    lseek(ctx->data_in->fd, 0, SEEK_SET);\n    if (ctx->child_stdout) {\n        lseek(ctx->child_stdout->fd, 0, SEEK_SET);\n    }\n    if (ctx->child_stderr) {\n        lseek(ctx->child_stderr->fd, 0, SEEK_SET);\n    }\n\n    // Spawn a new instance if necessary.\n    if (!ctx->pid) {\n        int r = reprl_spawn_child(ctx);\n        if (r != 0) return r;\n    }\n\n    // Copy the script to the data channel.\n    memcpy(ctx->data_out->mapping, script, script_size);\n\n    fprintf(stderr, \"[libreprl] About to write 'exec' command to child (ctrl_out fd=%d, pid=%d)\\n\",\n            ctx->ctrl_out, ctx->pid);\n    fflush(stderr);\n\n    // Tell child to execute the script.\n    ssize_t write1 = write(ctx->ctrl_out, \"exec\", 4);\n    ssize_t write2 = write(ctx->ctrl_out, &script_size, 8);\n\n    fprintf(stderr, \"[libreprl] Write results: exec=%zd (expected 4), script_size=%zd (expected 8)\\n\",\n            write1, write2);\n    fflush(stderr);\n\n    if (write1 != 4 || write2 != 8) {\n        // These can fail if the child unexpectedly terminated between executions.\n        // Check for that here to be able to provide a better error message.\n        int status;\n        if (waitpid(ctx->pid, &status, WNOHANG) == ctx->pid) {\n            reprl_child_terminated(ctx);\n            if (WIFEXITED(status)) {\n                return reprl_error(ctx, \"Child unexpectedly exited with status %i between executions\", WEXITSTATUS(status));\n            } else {\n                return reprl_error(ctx, \"Child unexpectedly terminated with signal %i between executions\", WTERMSIG(status));\n            }\n        }\n        return reprl_error(ctx, \"Failed to send command to child process: %s\", strerror(errno));\n    }\n\n    fprintf(stderr, \"[libreprl] Command written successfully, now polling for response (ctrl_in fd=%d, timeout=%dms)\\n\",\n            ctx->ctrl_in, timeout_ms);\n    fflush(stderr);\n\n    // Check if child is still alive before polling\n    int check_status;\n    pid_t check = waitpid(ctx->pid, &check_status, WNOHANG);\n    if (check == ctx->pid) {\n        fprintf(stderr, \"[libreprl] WARNING: Child died before poll! status=%d\\n\", check_status);\n        fflush(stderr);\n    } else if (check < 0) {\n        fprintf(stderr, \"[libreprl] WARNING: waitpid check failed: %s\\n\", strerror(errno));\n        fflush(stderr);\n    } else {\n        fprintf(stderr, \"[libreprl] Child still alive (pid=%d), proceeding with poll\\n\", ctx->pid);\n        fflush(stderr);\n    }\n\n    // Wait for child to finish execution (or crash).\n    uint64_t start_time = current_usecs();\n    struct pollfd fds = {.fd = ctx->ctrl_in, .events = POLLIN, .revents = 0};\n    fprintf(stderr, \"[libreprl] Calling poll...\\n\");\n    fflush(stderr);\n    int res = poll(&fds, 1, timeout_ms);\n    fprintf(stderr, \"[libreprl] poll() returned %d (revents=0x%x)\\n\", res, fds.revents);\n    fflush(stderr);\n    *execution_time = current_usecs() - start_time;\n    if (res == 0) {\n        // Execution timed out. Kill child and return a timeout status.\n        reprl_terminate_child(ctx);\n        return 1 << 16;\n    } else if (res != 1) {\n        // An error occurred.\n        // We expect all signal handlers to be installed with SA_RESTART, so receiving EINTR here is unexpected and thus also an error.\n        return reprl_error(ctx, \"Failed to poll: %s\", strerror(errno));\n    }\n\n    // Poll succeeded, so there must be something to read now (either the status or EOF).\n    int status;\n    ssize_t rv = read(ctx->ctrl_in, &status, 4);\n    if (rv < 0) {\n        return reprl_error(ctx, \"Failed to read from control pipe: %s\", strerror(errno));\n    } else if (rv != 4) {\n        // Most likely, the child process crashed and closed the write end of the control pipe.\n        // Unfortunately, there probably is nothing that guarantees that waitpid() will immediately succeed now,\n        // and we also don't want to block here. So just retry waitpid() a few times...\n        int success = 0;\n        do {\n            success = waitpid(ctx->pid, &status, WNOHANG) == ctx->pid;\n            if (!success) usleep(10);\n        } while (!success && current_usecs() - start_time < timeout);\n\n        if (!success) {\n            // Wait failed, so something weird must have happened. Maybe somehow the control pipe was closed without the child exiting?\n            // Probably the best we can do is kill the child and return an error.\n            reprl_terminate_child(ctx);\n            return reprl_error(ctx, \"Child in weird state after execution\");\n        }\n\n        // Cleanup any state related to this child process.\n        reprl_child_terminated(ctx);\n\n        if (WIFEXITED(status)) {\n            status = WEXITSTATUS(status) << 8;\n        } else if (WIFSIGNALED(status)) {\n            status = WTERMSIG(status);\n        } else {\n            // This shouldn't happen, since we don't specify WUNTRACED for waitpid...\n            return reprl_error(ctx, \"Waitpid returned unexpected child state %i\", status);\n        }\n    }\n\n    // The status must be a positive number, see the status encoding format below.\n    // We also don't allow the child process to indicate a timeout. If we wanted,\n    // we could treat it as an error if the upper bits are set.\n    status &= 0xffff;\n\n    return status;\n}\n\nstatic const char* fetch_data_channel_content(struct data_channel* channel)\n{\n    if (!channel) return \"\";\n    size_t pos = lseek(channel->fd, 0, SEEK_CUR);\n    pos = min(pos, REPRL_MAX_DATA_SIZE - 1);\n    channel->mapping[pos] = 0;\n    return channel->mapping;\n}\n\nconst char* reprl_fetch_fuzzout(struct reprl_context* ctx)\n{\n    return fetch_data_channel_content(ctx->data_in);\n}\n\nconst char* reprl_fetch_stdout(struct reprl_context* ctx)\n{\n    return fetch_data_channel_content(ctx->child_stdout);\n}\n\nconst char* reprl_fetch_stderr(struct reprl_context* ctx)\n{\n    return fetch_data_channel_content(ctx->child_stderr);\n}\n\nconst char* reprl_get_last_error(struct reprl_context* ctx)\n{\n    return ctx->last_error;\n}\n\n#endif\n"
  },
  {
    "path": "src/workerd/tests/libreprl/libreprl.h",
    "content": "// Copyright 2020 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2019 Google LLC\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// https://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#ifndef LIBREPRL_H\n#define LIBREPRL_H\n\n#include <limits.h>\n#include <stdint.h>\n\n/// Maximum size for data transferred through REPRL. In particular, this is the\n/// maximum size of scripts that can be executed. Currently, this is 16MB.\n/// Executing a 16MB script file is very likely to take longer than the typical\n/// timeout, so the limit on script size shouldn't be a problem in practice.\nconst uint64_t REPRL_MAX_DATA_SIZE = 16 << 20;\n\n/// Opaque struct representing a REPRL execution context.\nstruct reprl_context;\n\n/// Allocates a new REPRL context.\n/// @return an uninitialzed REPRL context\nstruct reprl_context* reprl_create_context();\n\n/// Initializes a REPRL context.\n///\n/// @param ctx An uninitialized context\n/// @param argv The argv vector for the child processes\n/// @param envp The envp vector for the child processes\n/// @param capture_stdout Whether this REPRL context should capture the child's\n/// stdout\n/// @param capture_stderr Whether this REPRL context should capture the child's\n/// stderr\n/// @return zero in case of no errors, otherwise a negative value\nint reprl_initialize_context(struct reprl_context* ctx,\n    const char** argv,\n    const char** envp,\n    int capture_stdout,\n    int capture_stderr);\n\n/// Destroys a REPRL context, freeing all resources held by it.\n///\n/// @param ctx The context to destroy\nvoid reprl_destroy_context(struct reprl_context* ctx);\n\n/// Executes the provided script in the target process, wait for its completion,\n/// and return the result. If necessary, or if fresh_instance is true, this will\n/// automatically spawn a new instance of the target process.\n///\n/// @param ctx The REPRL context\n/// @param script The script to execute as utf-8 encoded data\n/// @param script_size Size of the script as number of bytes\n/// @param timeout The maximum allowed execution time in microseconds\n/// @param execution_time A pointer to which, if execution succeeds, the\n/// execution time in microseconds is written to\n/// @param fresh_instance if true, forces the creation of a new instance of the\n/// target\n/// @return A REPRL exit status (see below) or a negative number in case of an\n/// error\nint reprl_execute(struct reprl_context* ctx,\n    const char* script,\n    uint64_t script_size,\n    uint64_t timeout,\n    uint64_t* execution_time,\n    int fresh_instance);\n\n/// Returns true if the execution terminated due to a signal.\n///\n/// The 32bit REPRL exit status as returned by reprl_execute has the following\n/// format:\n///     [ 00000000 | did_timeout | exit_code | terminating_signal ]\n/// Only one of did_timeout, exit_code, or terminating_signal may be set at one\n/// time.\nstatic inline int RIFSIGNALED(int status) {\n  return (status & 0xff) != 0;\n}\n\n/// Returns true if the execution terminated due to a timeout.\nstatic inline int RIFTIMEDOUT(int status) {\n  return (status & 0xff0000) != 0;\n}\n\n/// Returns true if the execution finished normally.\nstatic inline int RIFEXITED(int status) {\n  return !RIFSIGNALED(status) && !RIFTIMEDOUT(status);\n}\n\n/// Returns the terminating signal in case RIFSIGNALED is true.\nstatic inline int RTERMSIG(int status) {\n  return status & 0xff;\n}\n\n/// Returns the exit status in case RIFEXITED is true.\nstatic inline int REXITSTATUS(int status) {\n  return (status >> 8) & 0xff;\n}\n\n/// Returns the stdout data of the last successful execution if the context is\n/// capturing stdout, otherwise an empty string. The output is limited to\n/// REPRL_MAX_DATA_SIZE (currently 16MB).\n///\n/// @param ctx The REPRL context\n/// @return A string pointer which is owned by the REPRL context and thus should\n/// not be freed by the caller\nconst char* reprl_fetch_stdout(struct reprl_context* ctx);\n\n/// Returns the stderr data of the last successful execution if the context is\n/// capturing stderr, otherwise an empty string. The output is limited to\n/// REPRL_MAX_DATA_SIZE (currently 16MB).\n///\n/// @param ctx The REPRL context\n/// @return A string pointer which is owned by the REPRL context and thus should\n/// not be freed by the caller\nconst char* reprl_fetch_stderr(struct reprl_context* ctx);\n\n/// Returns the fuzzout data of the last successful execution.\n/// The output is limited to REPRL_MAX_DATA_SIZE (currently 16MB).\n///\n/// @param ctx The REPRL context\n/// @return A string pointer which is owned by the REPRL context and thus should\n/// not be freed by the caller\nconst char* reprl_fetch_fuzzout(struct reprl_context* ctx);\n\n/// Returns a string describing the last error that occurred in the given\n/// context.\n///\n/// @param ctx The REPRL context\n/// @return A string pointer which is owned by the REPRL context and thus should\n/// not be freed by the caller\nconst char* reprl_get_last_error(struct reprl_context* ctx);\n\n#endif\n"
  },
  {
    "path": "src/workerd/tests/module-imports-test.js",
    "content": "import { rejects, ok } from 'node:assert';\n\nexport const test = {\n  async test() {\n    await rejects(import('node:crypto'), {\n      message: /^No such module/,\n    });\n    await rejects(import('node:buffer'), {\n      message: /^No such module/,\n    });\n  },\n};\n\nimport source wasmSource from 'wasm';\nexport const wasmSourcePhaseTest = {\n  async test() {\n    ok(wasmSource instanceof WebAssembly.Module);\n    await WebAssembly.instantiate(wasmSource, {});\n  },\n};\n\nexport const wasmModuleTest = {\n  async test() {\n    const { default: wasm } = await import('wasm');\n    ok(wasm instanceof WebAssembly.Module);\n    await WebAssembly.instantiate(wasm, {});\n  },\n};\n\nexport const dynamicWasmSourcePhaseTest = {\n  async test() {\n    await rejects(import.source('wasm'), {\n      message: /Not supported/,\n    });\n  },\n};\n\nexport const dynamicSourcePhaseErrorTest = {\n  async test() {\n    await rejects(import.source('worker'), {\n      message: /Not supported/,\n    });\n  },\n};\n"
  },
  {
    "path": "src/workerd/tests/module-imports-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"module-imports-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"module-imports-test.js\"),\n          (name = \"node:crypto\",\n           esModule = \"export const test = await import('node-internal:internal_buffer');\"),\n          (name = \"node:buffer\",\n           esModule = \"import * as buf from 'node-internal:internal_buffer'; export default buf;\"),\n          (name = \"wasm\", wasm = embed \"test.wasm\"),\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/tests/performance-test.js",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nif (typeof globalThis.performance === 'undefined') {\n  throw new Error('performance is not defined');\n}\n\nif (globalThis.performance.timeOrigin !== 0.0) {\n  throw new Error('performance.timeOrigin is not 0.0');\n}\n\nif (globalThis.performance.now() !== 0.0) {\n  throw new Error('performance.now() is not 0.0');\n}\n\nif ('addEventListener' in globalThis.performance) {\n  throw new Error('performance.addEventListener should not be defined');\n}\n\n// Performance class should not be available.\nif (typeof globalThis.Performance !== 'undefined') {\n  throw new Error('Performance should not be defined');\n}\n\nexport const test = {\n  async test(_ctrl, _env, _ctx) {\n    const start = performance.now();\n    // There should be at least some time elapsed.\n    if (start == 0.0) {\n      throw new Error('performance.now() is 0.0');\n    }\n    await scheduler.wait(500);\n    if (start == performance.now()) {\n      throw new Error('performance.now() is not increasing');\n    }\n  },\n};\n"
  },
  {
    "path": "src/workerd/tests/performance-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"performance-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"performance-test.js\")\n        ],\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/tests/test-fixture-test.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"test-fixture.h\"\n\n#include <kj/test.h>\n\n#include <memory>\n\n#if KJ_HAS_COMPILER_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__)\n#include <sanitizer/lsan_interface.h>\n#define LSAN_ENABLED 1\n#else\n#define LSAN_ENABLED 0\nstatic int __lsan_do_recoverable_leak_check() {\n  return 0;\n}\n#endif\n\nnamespace workerd {\nnamespace {\n\nKJ_TEST(\"setup/destroy\") {\n  TestFixture fixture;\n}\n\nKJ_TEST(\"single void runInIoContext run\") {\n  TestFixture fixture;\n  uint runCount = 0;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) { runCount++; });\n\n  KJ_EXPECT(runCount == 1);\n}\n\nKJ_TEST(\"single runInIoContext with promise result\") {\n  TestFixture fixture;\n  uint runCount = 0;\n\n  auto result = fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    runCount++;\n    return kj::Promise<int>(42);\n  });\n\n  KJ_EXPECT(runCount == 1);\n  KJ_EXPECT(result == 42);\n}\n\nKJ_TEST(\"single runInIoContext with immediate result\") {\n  TestFixture fixture;\n  uint runCount = 0;\n\n  auto result = fixture.runInIoContext([&](const TestFixture::Environment& env) {\n    runCount++;\n    return 42;\n  });\n\n  KJ_EXPECT(runCount == 1);\n  KJ_EXPECT(result == 42);\n}\n\nKJ_TEST(\"3 runInIoContext runs\") {\n  TestFixture fixture;\n  uint runCount = 0;\n\n  for (uint i = 0; i < 3; i++) {\n    fixture.runInIoContext([&](const TestFixture::Environment& env) { runCount++; });\n\n    KJ_EXPECT(runCount == i + 1);\n  }\n}\n\nKJ_TEST(\"2 fixtures in a row with single runInIoContext run\") {\n  uint runCount = 0;\n\n  for (uint i = 0; i < 2; i++) {\n    TestFixture fixture;\n    fixture.runInIoContext([&](const TestFixture::Environment& env) { runCount++; });\n\n    KJ_EXPECT(runCount == i + 1);\n  }\n}\n\nKJ_TEST(\"runInIoContext consuming ignored kj::Exception\") {\n  TestFixture fixture;\n  uint runCount = 0;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) -> kj::Promise<void> {\n    runCount++;\n    KJ_FAIL_REQUIRE(\"test_error\");\n  }, kj::arr(\"test_error\"_kj));\n\n  KJ_EXPECT(runCount == 1);\n}\n\nKJ_TEST(\"runInIoContext re-throwing kj::Exception\") {\n  TestFixture fixture;\n  uint runCount = 0;\n  uint exceptionCount = 0;\n\n  try {\n    fixture.runInIoContext([&](const TestFixture::Environment& env) -> kj::Promise<void> {\n      runCount++;\n      KJ_FAIL_REQUIRE(\"let_me_through\");\n    }, kj::arr(\"test_error\"_kj));\n  } catch (kj::Exception& e) {\n    KJ_EXPECT(e.getDescription() == \"let_me_through\"_kj);\n    exceptionCount++;\n  }\n\n  KJ_EXPECT(exceptionCount == 1);\n  KJ_EXPECT(runCount == 1);\n}\n\nKJ_TEST(\"runInIoContext re-throwing js exception\") {\n  TestFixture fixture;\n  uint runCount = 0;\n  uint exceptionCount = 0;\n\n  try {\n    fixture.runInIoContext([&](const TestFixture::Environment& env) -> kj::Promise<void> {\n      runCount++;\n      env.js.throwException(env.js.error(\"let_me_through\"));\n    }, kj::arr(\"test_error\"_kj));\n  } catch (kj::Exception& e) {\n    KJ_EXPECT(e.getDescription() == \"jsg.Error: let_me_through\"_kj);\n    exceptionCount++;\n  }\n\n  KJ_EXPECT(runCount == 1);\n  KJ_EXPECT(exceptionCount == 1);\n}\n\nKJ_TEST(\"runInIoContext consuming ignored js exception\") {\n  TestFixture fixture;\n  uint runCount = 0;\n\n  fixture.runInIoContext([&](const TestFixture::Environment& env) -> kj::Promise<void> {\n    runCount++;\n    env.js.throwException(env.js.error(\"test_error\"));\n  }, kj::arr(\"test_error\"_kj));\n\n  KJ_EXPECT(runCount == 1);\n}\n\nKJ_TEST(\"runRequest\") {\n  TestFixture fixture({.mainModuleSource = R\"SCRIPT(\n      export default {\n        async fetch(request) {\n          const body = await(await request.blob()).text();\n          return new Response(`${request.method} ${request.url} ${body}`, { status: 202 });\n        },\n      };\n    )SCRIPT\"_kj});\n\n  auto result = fixture.runRequest(kj::HttpMethod::POST, \"http://www.example.com\"_kj, \"TEST\"_kj);\n  KJ_EXPECT(result.statusCode == 202);\n  KJ_EXPECT(result.body == \"POST http://www.example.com TEST\"_kj);\n}\n\nKJ_TEST(\"module import failure\") {\n  KJ_EXPECT_LOG(ERROR, \"script startup threw exception\");\n\n  try {\n    TestFixture fixture({.mainModuleSource = R\"SCRIPT(\n        import * from \"bad-module\";\n\n        export default {\n          async fetch(request) {\n            return new Response(\"OK\");\n          },\n        };\n      )SCRIPT\"_kj});\n\n    KJ_FAIL_REQUIRE(\"exception expected\");\n  } catch (kj::Exception& e) {\n    KJ_EXPECT(e.getDescription() == \"script startup threw exception\"_kj);\n  }\n}\n\n// This test mimics the fuzzer pattern where a static TestFixture is reused across iterations.\n// The Rust Realm is stored in V8's embedder data. In fuzzers with incremental leak detection, this can cause false positive leak\n// reports because LSAN checks between iterations while the static TestFixture is still alive.\n//\n// Note: We use unique_ptr here because the test must properly clean up before V8System's\n// static destructor runs. Fuzzers typically use raw `new` and rely on _exit() to skip\n// static destructors, but tests must clean up properly.\nKJ_TEST(\"static fixture with multiple iterations\") {\n  static std::unique_ptr<TestFixture> fixture;\n  if (fixture == nullptr) {\n    fixture = std::make_unique<TestFixture>();\n  }\n\n  uint runCount = 0;\n\n  for (uint i = 0; i < 10; i++) {\n    fixture->runInIoContext([&](const TestFixture::Environment& env) { runCount++; });\n  }\n\n  KJ_EXPECT(runCount == 10);\n\n  if (LSAN_ENABLED) {\n    int leaks = __lsan_do_recoverable_leak_check();\n    KJ_EXPECT(leaks == 0, \"LSAN detected leaks\");\n  }\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/tests/test-fixture.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"test-fixture.h\"\n\n#include <workerd/api/actor-state.h>\n#include <workerd/api/global-scope.h>\n#include <workerd/api/memory-cache.h>\n#include <workerd/io/actor-cache.h>\n#include <workerd/io/actor-id.h>\n#include <workerd/io/io-channels.h>\n#include <workerd/io/limit-enforcer.h>\n#include <workerd/io/observer.h>\n#include <workerd/io/tracer.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/jsg/setup.h>\n#include <workerd/server/workerd-api.h>\n#include <workerd/util/autogate.h>\n#include <workerd/util/stream-utils.h>\n\n#include <algorithm>\n\nnamespace workerd {\n\nnamespace {\n\njsg::V8System testV8System;\n\nclass MockCacheClient final: public CacheClient {\n  kj::Own<kj::HttpClient> getDefault(CacheClient::SubrequestMetadata metadata) override {\n    KJ_FAIL_REQUIRE(\"Not implemented\");\n  }\n\n  kj::Own<kj::HttpClient> getNamespace(\n      kj::StringPtr name, CacheClient::SubrequestMetadata metadata) override {\n    return getDefault(kj::mv(metadata));\n  }\n};\n\nclass MockTimer final: public kj::Timer {\n  kj::TimePoint now() const override {\n    return kj::systemCoarseMonotonicClock().now();\n  }\n  kj::Promise<void> atTime(kj::TimePoint time) override {\n    return kj::NEVER_DONE;\n  }\n  kj::Promise<void> afterDelay(kj::Duration delay) override {\n    return kj::NEVER_DONE;\n  }\n};\n\nclass DummyErrorHandler final: public kj::TaskSet::ErrorHandler {\n  void taskFailed(kj::Exception&& exception) override {}\n};\n\nstruct MockTimerChannel final: public TimerChannel {\n  void syncTime() override {}\n\n  kj::Date now(kj::Maybe<kj::Date>) override {\n    return kj::systemPreciseCalendarClock().now();\n  }\n\n  kj::Promise<void> atTime(kj::Date when) override {\n    return kj::NEVER_DONE;\n  }\n\n  kj::Promise<void> afterLimitTimeout(kj::Duration t) override {\n    return kj::NEVER_DONE;\n  }\n};\n\n// A TimerChannel implementation that uses real timers from the KJ event loop.\n// Useful for tests that need actual timer functionality (e.g., benchmarks with\n// simulated I/O delays).\nstruct RealTimerChannel final: public TimerChannel {\n  explicit RealTimerChannel(kj::Timer& timer): timer(timer) {}\n\n  void syncTime() override {}\n\n  kj::Date now(kj::Maybe<kj::Date>) override {\n    return kj::systemPreciseCalendarClock().now();\n  }\n\n  kj::Promise<void> atTime(kj::Date when) override {\n    auto nowTime = kj::systemPreciseCalendarClock().now();\n    if (when <= nowTime) {\n      return kj::READY_NOW;\n    }\n    return timer.afterDelay(when - nowTime);\n  }\n\n  kj::Promise<void> afterLimitTimeout(kj::Duration t) override {\n    return timer.afterDelay(t);\n  }\n\n  kj::Timer& timer;\n};\n\nstruct DummyIoChannelFactory final: public IoChannelFactory {\n  DummyIoChannelFactory(TimerChannel& timer): timer(timer) {}\n\n  kj::Own<WorkerInterface> startSubrequest(uint channel, SubrequestMetadata metadata) override {\n    KJ_FAIL_ASSERT(\"no subrequests\");\n  }\n\n  kj::Own<SubrequestChannel> getSubrequestChannel(uint channel,\n      kj::Maybe<Frankenvalue> props,\n      kj::Maybe<VersionRequest> versionRequest) override {\n    KJ_FAIL_ASSERT(\"no subrequests\");\n  }\n\n  capnp::Capability::Client getCapability(uint channel) override {\n    KJ_FAIL_ASSERT(\"no capabilities\");\n  }\n\n  kj::Own<CacheClient> getCache() override {\n    return kj::heap<MockCacheClient>();\n  }\n\n  TimerChannel& getTimer() override {\n    return timer;\n  }\n\n  kj::Promise<void> writeLogfwdr(\n      uint channel, kj::FunctionParam<void(capnp::AnyPointer::Builder)> buildMessage) override {\n    KJ_FAIL_ASSERT(\"no log channels\");\n  }\n\n  kj::Own<ActorChannel> getGlobalActor(uint channel,\n      const ActorIdFactory::ActorId& id,\n      kj::Maybe<kj::String> locationHint,\n      ActorGetMode mode,\n      bool enableReplicaRouting,\n      ActorRoutingMode routingMode,\n      SpanParent parentSpan,\n      kj::Maybe<ActorVersion> version) override {\n    KJ_FAIL_REQUIRE(\"no actor channels\");\n  }\n\n  kj::Own<ActorChannel> getColoLocalActor(\n      uint channel, kj::StringPtr id, SpanParent parentSpan) override {\n    KJ_FAIL_REQUIRE(\"no actor channels\");\n  }\n\n  TimerChannel& timer;\n};\n\nstatic constexpr kj::StringPtr mainModuleSource = R\"SCRIPT(\n  export default {\n    fetch(request) { return new Response(\"OK\"); },\n  };\n)SCRIPT\"_kj;\nstatic constexpr kj::StringPtr mainModuleName = \"main\"_kj;\n\nstatic constexpr kj::StringPtr scriptId = \"script\"_kj;\n\nclass MockEntropySource final: public kj::EntropySource {\n public:\n  ~MockEntropySource() {}\n  void generate(kj::ArrayPtr<kj::byte> buffer) override {\n    for (kj::byte& b: buffer) {\n      b = counter++;\n    }\n  }\n\n  template <typename T>\n  T rand() {\n    T r;\n    this->generate(kj::arrayPtr(&r, 1).asBytes());\n    return r;\n  }\n\n private:\n  kj::byte counter = 0;\n};\n\nstruct MockLimitEnforcer final: public LimitEnforcer {\n  kj::Own<void> enterJs(jsg::Lock& lock, IoContext& context) override {\n    return {};\n  }\n  void topUpActor() override {}\n  void newSubrequest(bool isInHouse) override {}\n  void newKvRequest(KvOpType op) override {}\n  void newAnalyticsEngineRequest() override {}\n  kj::Promise<void> limitDrain() override {\n    return kj::NEVER_DONE;\n  }\n  kj::Promise<void> limitScheduled() override {\n    return kj::NEVER_DONE;\n  }\n  kj::Duration getAlarmLimit() override {\n    return 15 * kj::MINUTES;\n  }\n  size_t getBufferingLimit() override {\n    return kj::maxValue;\n  }\n  kj::Maybe<EventOutcome> getLimitsExceeded() override {\n    return kj::none;\n  }\n  kj::Promise<void> onLimitsExceeded() override {\n    return kj::NEVER_DONE;\n  }\n  void setCpuLimitNearlyExceededCallback(kj::Function<void(void)> cb) override {}\n  void requireLimitsNotExceeded() override {}\n  void reportMetrics(RequestObserver& requestMetrics) override {}\n  kj::Duration consumeTimeElapsedForPeriodicLogging() override {\n    return 0 * kj::SECONDS;\n  }\n};\n\nstruct MockIsolateLimitEnforcer final: public IsolateLimitEnforcer {\n  v8::Isolate::CreateParams getCreateParams() override {\n    return {};\n  }\n  void customizeIsolate(v8::Isolate* isolate) override {}\n  ActorCacheSharedLruOptions getActorCacheLruOptions() override {\n    return {.softLimit = 16 * (1ull << 20),  // 16 MiB\n      .hardLimit = 128 * (1ull << 20),       // 128 MiB\n      .staleTimeout = 30 * kj::SECONDS,\n      .dirtyListByteLimit = 8 * (1ull << 20),  // 8 MiB\n      .maxKeysPerRpc = 128,\n      .neverFlush = true};\n  }\n  kj::Own<void> enterStartupJs(\n      jsg::Lock& lock, kj::OneOf<kj::Exception, kj::Duration>&) const override {\n    return {};\n  }\n  kj::Own<void> enterStartupPython(\n      jsg::Lock& lock, kj::OneOf<kj::Exception, kj::Duration>&) const override {\n    return {};\n  }\n  kj::Own<void> enterDynamicImportJs(\n      jsg::Lock& lock, kj::OneOf<kj::Exception, kj::Duration>&) const override {\n    return {};\n  }\n  kj::Own<void> enterLoggingJs(\n      jsg::Lock& lock, kj::OneOf<kj::Exception, kj::Duration>&) const override {\n    return {};\n  }\n  kj::Own<void> enterInspectorJs(\n      jsg::Lock& loc, kj::OneOf<kj::Exception, kj::Duration>&) const override {\n    return {};\n  }\n  void completedRequest(kj::StringPtr id) const override {}\n  bool exitJs(jsg::Lock& lock) const override {\n    return false;\n  }\n  void reportMetrics(IsolateObserver& isolateMetrics) const override {}\n  kj::Maybe<size_t> checkPbkdfIterations(jsg::Lock& lock, size_t iterations) const override {\n    return kj::none;\n  }\n  bool hasExcessivelyExceededHeapLimit() const override {\n    return false;\n  }\n  const TrackedWasmInstanceList& getTrackedWasmInstances() const override {\n    return trackedWasmInstances;\n  }\n\n private:\n  TrackedWasmInstanceList trackedWasmInstances;\n};\n\nstruct MockErrorReporter final: public Worker::ValidationErrorReporter {\n  void addError(kj::String error) override {\n    KJ_FAIL_REQUIRE(\"unexpected error\", error);\n  }\n\n  void addEntrypoint(kj::Maybe<kj::StringPtr> exportName, kj::Array<kj::String> methods) override {}\n  void addActorClass(kj::StringPtr exportName) override {}\n  void addWorkflowClass(kj::StringPtr exportName, kj::Array<kj::String> methods) override {}\n};\n\ninline server::config::Worker::Reader buildConfig(\n    TestFixture::SetupParams& params, capnp::MallocMessageBuilder& arena) {\n  auto config = arena.initRoot<server::config::Worker>();\n  auto modules = config.initModules(1);\n  modules[0].setName(mainModuleName);\n  modules[0].setEsModule(params.mainModuleSource.orDefault(mainModuleSource));\n\n  // Initialize autogates with an empty config. TODO(later): allow TestFixture to accept autogate\n  // states and pass them in here.\n  //\n  // This needs to happen here because `buildConfig` is called early in the construction of\n  // `TestFixture`.\n  util::Autogate::initAutogate({});\n\n  return config;\n}\n\nstruct MemoryOutputStream final: kj::AsyncOutputStream, public kj::Refcounted {\n  kj::Vector<byte> content;\n\n  kj::Promise<void> write(kj::ArrayPtr<const byte> buffer) override {\n    content.addAll(buffer);\n    return kj::READY_NOW;\n  }\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const byte>> pieces) override {\n    KJ_FAIL_REQUIRE(\"NOT IMPLEMENTED\");\n  }\n\n  kj::Promise<void> whenWriteDisconnected() override {\n    return kj::NEVER_DONE;\n  }\n\n  kj::String str() {\n    return kj::str(content.asPtr().asChars());\n  }\n};\n\nstruct MockResponse final: public kj::HttpService::Response {\n  uint statusCode = 0;\n  kj::StringPtr statusText;\n  kj::Own<MemoryOutputStream> body = kj::refcounted<MemoryOutputStream>();\n\n  kj::Own<kj::AsyncOutputStream> send(uint statusCode,\n      kj::StringPtr statusText,\n      const kj::HttpHeaders& headers,\n      kj::Maybe<uint64_t> expectedBodySize = kj::none) override {\n    this->statusCode = statusCode;\n    this->statusText = statusText;\n    return kj::addRef(*body);\n  }\n\n  kj::Own<kj::WebSocket> acceptWebSocket(const kj::HttpHeaders& headers) override {\n    KJ_FAIL_REQUIRE(\"NOT SUPPORTED\");\n  }\n};\n\nclass MockActorLoopback: public Worker::Actor::Loopback, public kj::Refcounted {\n public:\n  kj::Own<WorkerInterface> getWorker(IoChannelFactory::SubrequestMetadata metadata) override {\n    return kj::Own<WorkerInterface>();\n  };\n\n  kj::Own<Worker::Actor::Loopback> addRef() override {\n    return kj::addRef(*this);\n  };\n};\n\n}  // namespace\n\nusing api::pyodide::PythonConfig;\n\nconst PythonConfig defaultPythonConfig{.packageDiskCacheRoot = kj::none,\n  .pyodideDiskCacheRoot = kj::none,\n  .createSnapshot = false,\n  .createBaselineSnapshot = false};\n\nTestFixture::TestFixture(SetupParams&& params)\n    : waitScope(params.waitScope),\n      config(buildConfig(params, configArena)),\n      io(params.waitScope == kj::none ? kj::Maybe(kj::setupAsyncIo())\n                                      : kj::Maybe<kj::AsyncIoContext>(kj::none)),\n      timer(kj::heap<MockTimer>()),\n      timerChannel(params.useRealTimers && io != kj::none\n              ? kj::Own<TimerChannel>(\n                    kj::heap<RealTimerChannel>(KJ_ASSERT_NONNULL(io).provider->getTimer()))\n              : kj::Own<TimerChannel>(kj::heap<MockTimerChannel>())),\n      entropySource(kj::heap<MockEntropySource>()),\n      threadContextHeaderBundle(headerTableBuilder),\n      httpOverCapnpFactory(byteStreamFactory,\n          capnp::HttpOverCapnpFactory::HeaderIdBundle(headerTableBuilder),\n          capnp::HttpOverCapnpFactory::LEVEL_2),\n      threadContext(*timer,\n          *entropySource,\n          threadContextHeaderBundle,\n          httpOverCapnpFactory,\n          byteStreamFactory,\n          false),\n      errorReporter(kj::heap<MockErrorReporter>()),\n      memoryCacheProvider(kj::heap<api::MemoryCacheProvider>(*timer)),\n      isolateGroup(v8::IsolateGroup::GetDefault()),\n      api(kj::heap<server::WorkerdApi>(testV8System,\n          params.featureFlags.orDefault(CompatibilityFlags::Reader()),\n          capnp::List<server::config::Extension>::Reader{},\n          kj::heap<MockIsolateLimitEnforcer>()->getCreateParams(),\n          isolateGroup,\n          kj::atomicRefcounted<JsgIsolateObserver>(),\n          *memoryCacheProvider,\n          defaultPythonConfig)),\n      workerIsolate(kj::atomicRefcounted<Worker::Isolate>(kj::mv(api),\n          kj::atomicRefcounted<IsolateObserver>(),\n          scriptId,\n          kj::heap<MockIsolateLimitEnforcer>(),\n          Worker::Isolate::InspectorPolicy::DISALLOW)),\n      workerScript(kj::atomicRefcounted<Worker::Script>(kj::atomicAddRef(*workerIsolate),\n          scriptId,\n          server::WorkerdApi::extractSource(mainModuleName,\n              config,\n              params.featureFlags.orDefault(CompatibilityFlags::Reader()),\n              *errorReporter),\n          IsolateObserver::StartType::COLD,\n          false,\n          kj::none,\n          kj::none,\n          SpanParent(nullptr),\n          newWorkerFileSystem(kj::heap<FsMap>(), getTmpDirectoryImpl()),\n          kj::none /* new module registry */)),\n      worker(kj::atomicRefcounted<Worker>(kj::atomicAddRef(*workerScript),\n          kj::atomicRefcounted<WorkerObserver>(),\n          [](jsg::Lock&, const Worker::Api&, v8::Local<v8::Object>, v8::Local<v8::Object>) {\n            // no bindings, nothing to do\n          },\n          IsolateObserver::StartType::COLD,\n          SpanParent(nullptr),\n          Worker::LockType(Worker::Lock::TakeSynchronously(kj::none)))),\n      errorHandler(kj::heap<DummyErrorHandler>()),\n      waitUntilTasks(*errorHandler),\n      headerTable(headerTableBuilder.build()) {\n  KJ_IF_SOME(id, params.actorId) {\n    auto makeActorCache = [](const ActorCache::SharedLru& sharedLru, OutputGate& outputGate,\n                              ActorCache::Hooks& hooks, SqliteObserver& sqliteObserver) {\n      return kj::heap<ActorCache>(\n          server::newEmptyReadOnlyActorStorage(), sharedLru, outputGate, hooks);\n    };\n    auto makeStorage = [](jsg::Lock& js, const Worker::Api& api,\n                           ActorCacheInterface& actorCache) -> jsg::Ref<api::DurableObjectStorage> {\n      return js.alloc<api::DurableObjectStorage>(\n          js, IoContext::current().addObject(actorCache), /*enableSql=*/false);\n    };\n    actor = kj::refcounted<Worker::Actor>(*worker, /*tracker=*/kj::none, kj::mv(id),\n        /*hasTransient=*/false, makeActorCache,\n        /*classname=*/kj::none, /*props=*/Frankenvalue(), makeStorage,\n        kj::refcounted<MockActorLoopback>(), *timerChannel, kj::refcounted<ActorObserver>(),\n        kj::none, kj::none);\n  }\n}\n\nvoid TestFixture::runInIoContext(kj::Function<kj::Promise<void>(const Environment&)>&& callback,\n    const kj::ArrayPtr<const kj::StringPtr> errorsToIgnore) {\n  auto ignoreDescription = [&errorsToIgnore](kj::StringPtr description) {\n    return std::any_of(errorsToIgnore.begin(), errorsToIgnore.end(),\n        [&description](auto error) { return description.contains(error); });\n  };\n\n  try {\n    runInIoContext([callback = kj::mv(callback), &ignoreDescription](\n                       const TestFixture::Environment& env) mutable -> kj::Promise<void> {\n      v8::TryCatch tryCatch(env.isolate);\n      try {\n        return callback(env);\n      } catch (jsg::JsExceptionThrown&) {\n        if (!tryCatch.CanContinue()) {\n          throw;\n        }\n        if (ignoreDescription(kj::str(tryCatch.Exception()))) {\n          return kj::READY_NOW;\n        }\n        tryCatch.ReThrow();\n        throw;\n      }\n    });\n  } catch (kj::Exception& e) {\n    if (!ignoreDescription(e.getDescription())) {\n      throw e;\n    }\n  }\n}\n\nkj::Own<IoContext::IncomingRequest> TestFixture::createIncomingRequest() {\n  auto context = kj::refcounted<IoContext>(\n      threadContext, kj::atomicAddRef(*worker), actor, kj::heap<MockLimitEnforcer>());\n  auto incomingRequest = kj::heap<IoContext::IncomingRequest>(kj::addRef(*context),\n      kj::heap<DummyIoChannelFactory>(*timerChannel), kj::refcounted<RequestObserver>(), kj::none,\n      kj::none);\n  incomingRequest->delivered();\n  return incomingRequest;\n}\n\nTestFixture::Response TestFixture::runRequest(\n    kj::HttpMethod method, kj::StringPtr url, kj::StringPtr body) {\n  kj::HttpHeaders requestHeaders(*headerTable);\n  MockResponse response;\n  auto requestBody = newMemoryInputStream(body);\n\n  runInIoContext([&](const TestFixture::Environment& env) {\n    auto& globalScope = env.lock.getGlobalScope();\n    return globalScope.request(method, url, requestHeaders, *requestBody, response, \"{}\"_kj,\n        env.lock,\n        env.lock.getExportedHandler(/*entryPointName=*/kj::none, /*versionInfo=*/kj::none,\n            /*props=*/{}, /*actor=*/kj::none),\n        /*abortSignal=*/kj::none);\n  });\n\n  return {.statusCode = response.statusCode, .body = response.body->str()};\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/tests/test-fixture.h",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/api/memory-cache.h>\n#include <workerd/io/io-context.h>\n#include <workerd/io/worker.h>\n#include <workerd/jsg/jsg.h>\n#include <workerd/server/workerd.capnp.h>\n\n#include <capnp/message.h>\n#include <kj/function.h>\n#include <kj/test.h>\n\nnamespace workerd {\n\n// TestFixture is responsible for creating workerd environment during tests.\n// All the infrastructure is started in the constructor. It is accessed through run() method.\nstruct TestFixture {\n  struct SetupParams {\n    // waitScope of outer IO loop. New IO will be set up if missing.\n    kj::Maybe<kj::WaitScope&> waitScope;\n    kj::Maybe<CompatibilityFlags::Reader> featureFlags;\n    kj::Maybe<kj::StringPtr> mainModuleSource;\n    // If set, make a stub of an Actor with the given id.\n    kj::Maybe<Worker::Actor::Id> actorId;\n    // If true, use real timers instead of mock timers that never advance.\n    // Requires waitScope to be kj::none (so that the fixture creates its own AsyncIoContext).\n    bool useRealTimers;\n  };\n\n  TestFixture(SetupParams&& params = {.useRealTimers = false});\n\n  struct V8Environment {\n    v8::Isolate* isolate;\n  };\n\n  struct Environment: public V8Environment {\n    IoContext& context;\n    Worker::Lock& lock;\n    jsg::Lock& js;\n    CompatibilityFlags::Reader features;\n  };\n\n  template <typename T>\n  struct RunReturnType {\n    using Type = T;\n  };\n  template <typename T>\n  struct RunReturnType<kj::Promise<T>> {\n    using Type = T;\n  };\n\n  // Setup the incoming request and run given callback in worker's IO context.\n  // callback should accept const Environment& parameter and return Promise<T>|void.\n  // For void callbacks run waits for their completion, for promises waits for their resolution\n  // and returns the result.\n  template <typename CallBack>\n  auto runInIoContext(CallBack&& callback)\n      -> RunReturnType<decltype(callback(kj::instance<const Environment&>()))>::Type {\n    auto request = createIncomingRequest();\n    kj::WaitScope* waitScope;\n    KJ_IF_SOME(ws, this->waitScope) {\n      waitScope = &ws;\n    } else {\n      waitScope = &KJ_REQUIRE_NONNULL(io).waitScope;\n    }\n\n    auto& context = request->getContext();\n    return context\n        .run([&](Worker::Lock& lock) {\n      // auto features = workerBundle.getFeatureFlags();\n      auto& js = jsg::Lock::from(lock.getIsolate());\n      Environment env = {{.isolate = lock.getIsolate()}, context, lock, js};\n      KJ_ASSERT(env.isolate == v8::Isolate::TryGetCurrent());\n      return callback(env);\n    }).wait(*waitScope);\n  }\n\n  // Special void version of runInIoContext that ignores exceptions with given descriptions.\n  void runInIoContext(kj::Function<kj::Promise<void>(const Environment&)>&& callback,\n      kj::ArrayPtr<const kj::StringPtr> errorsToIgnore);\n\n  struct Response {\n    uint statusCode;\n    kj::String body;\n  };\n\n  // Performs HTTP request on the default module handler, and waits for full response.\n  Response runRequest(kj::HttpMethod method, kj::StringPtr url, kj::StringPtr body);\n\n private:\n  kj::Maybe<kj::WaitScope&> waitScope;\n  capnp::MallocMessageBuilder configArena;\n  workerd::server::config::Worker::Reader config;\n  kj::Maybe<kj::AsyncIoContext> io;\n  capnp::MallocMessageBuilder workerBundleArena;\n  kj::Own<kj::Timer> timer;\n  kj::Own<TimerChannel> timerChannel;\n  kj::Own<kj::EntropySource> entropySource;\n  kj::Maybe<kj::Own<Worker::Actor>> actor;\n  capnp::ByteStreamFactory byteStreamFactory;\n  kj::HttpHeaderTable::Builder headerTableBuilder;\n  ThreadContext::HeaderIdBundle threadContextHeaderBundle;\n  capnp::HttpOverCapnpFactory httpOverCapnpFactory;\n  ThreadContext threadContext;\n  kj::Own<Worker::ValidationErrorReporter> errorReporter;\n  kj::Own<api::MemoryCacheProvider> memoryCacheProvider;\n  v8::IsolateGroup isolateGroup;\n  kj::Own<Worker::Api> api;\n  kj::Own<Worker::Isolate> workerIsolate;\n  kj::Own<Worker::Script> workerScript;\n  kj::Own<Worker> worker;\n  kj::Own<kj::TaskSet::ErrorHandler> errorHandler;\n  kj::TaskSet waitUntilTasks;\n  kj::Own<kj::HttpHeaderTable> headerTable;\n\n  kj::Own<IoContext::IncomingRequest> createIncomingRequest();\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/tests/test-reprl.c++",
    "content": "#include <limits.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include <kj/test.h>\n\n// Libreprl is a .c file so the header needs to be in an 'extern \"C\"' block.\nextern \"C\" {\n#include \"libreprl/libreprl.h\"\n}\n\n#include \"tools/cpp/runfiles/runfiles.h\"\n\nusing bazel::tools::cpp::runfiles::Runfiles;\n\nnamespace workerd {\nnamespace {\n\n#ifdef __linux__\nvoid print_splitter() {\n  printf(\"---------------------------------\\n\");\n}\n\nbool execute(struct reprl_context* ctx, const char* code) {\n  uint64_t exec_time;\n  const uint64_t SECONDS = 1000000;  // Timeout is in microseconds.\n  print_splitter();\n  printf(\"Executing: %s\\n\", code);\n  int status = reprl_execute(ctx, code, strlen(code), 1 * SECONDS, &exec_time, 0);\n  printf(\"Return code: %d\\n\", status);\n\n  const char* fuzzout = reprl_fetch_fuzzout(ctx);\n  printf(\"Fuzzout stdout:\\n%s\\n\", fuzzout);\n  fflush(stdout);\n\n  const char* stdout_output = reprl_fetch_stdout(ctx);\n  printf(\"Workerd stdout:\\n%s\\n\", stdout_output);\n\n  const char* stdout_err = reprl_fetch_stderr(ctx);\n  printf(\"Workerd stderr:\\n%s\\n\", stdout_err);\n\n  if (RIFSIGNALED(status)) {\n    printf(\"Process was terminated by signal %d\\n\", RTERMSIG(status));\n  }\n  print_splitter();\n  fflush(stdout);\n  fflush(stderr);\n\n  // Check if the process exited successfully\n  return RIFEXITED(status) && REXITSTATUS(status) == 0;\n}\n\nvoid expect_success(struct reprl_context* ctx, const char* code) {\n  if (!execute(ctx, code)) {\n    KJ_FAIL_REQUIRE(\"Execution unexpectedly failed\", code);\n  }\n}\n\nvoid expect_failure(struct reprl_context* ctx, const char* code) {\n  if (execute(ctx, code)) {\n    KJ_FAIL_REQUIRE(\"Execution unexpectedly succeeded\", code);\n  }\n}\n#endif  // __linux__\n\nKJ_TEST(\"REPRL basic functionality\") {\n#ifdef __linux__\n  std::string error;\n  std::unique_ptr<Runfiles> runfiles(Runfiles::CreateForTest(&error));\n  KJ_REQUIRE(runfiles != nullptr, \"Failed to create runfiles\", error.c_str());\n\n  auto ctx = reprl_create_context();\n  KJ_REQUIRE(ctx != nullptr, \"Failed to create REPRL context\");\n\n  const char* env[] = {\"LLVM_SYMBOLIZER=/usr/bin/llvm-symbolizer-19\", nullptr};\n\n  // Use Runfiles API to get absolute paths\n  std::string workerd_path = runfiles->Rlocation(\"workerd/src/workerd/server/workerd\");\n  // Use config.capnp which has a socket (needed to trigger fetch() which calls Stdin.reprl())\n  std::string config_path = runfiles->Rlocation(\"workerd/fuzzilli/config.capnp\");\n\n  const char* args[] = {\n    workerd_path.c_str(), \"fuzzilli\", config_path.c_str(), \"--experimental\", nullptr};\n\n  if (reprl_initialize_context(ctx, args, env, 1, 1) != 0) {\n    KJ_FAIL_REQUIRE(\"REPRL initialization failed\");\n  }\n\n  // Basic functionality test\n  expect_success(ctx, \"let greeting = \\\"Hello World!\\\";\");\n\n  // Test with console.log output\n  expect_success(ctx, \"console.log('Hello from JavaScript!');\");\n\n  // Verify that runtime exceptions can be detected\n  expect_failure(ctx, \"throw 'failure';\");\n  expect_success(ctx, \"42;\");\n\n  // Verify that existing state is properly reset between executions\n  // These tests are commented out as they may not apply to workerd's execution model\n  // expect_success(ctx, \"globalProp = 42; Object.prototype.foo = \\\"bar\\\";\");\n  // expect_success(ctx, \"if (typeof(globalProp) !== 'undefined') throw 'failure'\");\n  // expect_success(ctx, \"if (typeof(({}).foo) !== 'undefined') throw 'failure'\");\n\n  // Verify that rejected promises are properly reset between executions\n  expect_failure(ctx, \"function fail() { throw 42; }; fail()\");\n\n  // Verify that fuzzilli crash command is detected as failure\n  expect_failure(ctx, \"fuzzilli('FUZZILLI_CRASH',0);\");\n  expect_failure(ctx, \"fuzzilli('FUZZILLI_CRASH',1);\");\n  expect_failure(ctx, \"fuzzilli('FUZZILLI_CRASH',2);\");\n  expect_failure(ctx, \"fuzzilli('FUZZILLI_CRASH',3);\");\n  expect_failure(ctx, \"fuzzilli('FUZZILLI_CRASH',4);\");\n  //expect_failure(ctx, \"fuzzilli('FUZZILLI_CRASH',5);\");\n\n  // async is not failing in workerd (commented out from original)\n  // expect_failure(ctx, \"async function fail() { throw 42; }; fail()\");\n\n  reprl_destroy_context(ctx);\n#else\n  KJ_LOG(WARNING, \"REPRL tests only supported on Linux\");\n#endif\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/tests/unknown-import-assertions-test.js",
    "content": "import { rejects } from 'node:assert';\n\nexport const test = {\n  async test() {\n    await rejects(import('worker', { with: { a: 'b' } }), {\n      message: /Unrecognized import attributes/,\n    });\n  },\n};\n"
  },
  {
    "path": "src/workerd/tests/unknown-import-assertions-test.wd-test",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"unknown-import-assertions-test\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"unknown-import-assertions-test.js\")\n        ],\n        compatibilityFlags = [\n          \"nodejs_compat\",\n          \"throw_on_unrecognized_import_assertion\",\n        ]\n      )\n    ),\n  ],\n);\n"
  },
  {
    "path": "src/workerd/tools/BUILD.bazel",
    "content": "load(\"@bazel_skylib//rules:run_binary.bzl\", \"run_binary\")\nload(\"@rules_rust//rust:defs.bzl\", \"rust_binary\")\nload(\"//:build/cc_ast_dump.bzl\", \"cc_ast_dump\")\n\n# ========================================================================================\n# Parameter Name Extractor\n#\n# Extracts the parameter names of functions, methods, etc. of the runtime API,\n# since they're not encoded in the type information generated by the RTTI dump\n\ncc_ast_dump(\n    name = \"dump_api_ast\",\n    src = \"param-names-ast.c++\",\n    out = \"api.ast.json.gz\",\n    tags = [\"no-downstream\"],\n    target_compatible_with = select({\n        \"@platforms//os:windows\": [\"@platforms//:incompatible\"],\n        \"//conditions:default\": [],\n    }),\n    deps = [\n        \"//src/cloudflare\",\n        \"//src/node\",\n        \"//src/workerd/api:analytics-engine\",\n        \"//src/workerd/api:base64\",\n        \"//src/workerd/api:html-rewriter\",\n        \"//src/workerd/api:hyperdrive\",\n        \"//src/workerd/api:kv\",\n        \"//src/workerd/api:memory-cache\",\n        \"//src/workerd/api:r2\",\n        \"//src/workerd/api:streams\",\n        \"//src/workerd/api:tracing-module\",\n        \"//src/workerd/api:urlpattern\",\n        \"//src/workerd/api:urlpattern-standard\",\n        \"//src/workerd/api:worker-loader\",\n        \"//src/workerd/api:workers-module\",\n        \"//src/workerd/api/node\",\n        \"//src/workerd/io\",\n        \"//src/workerd/jsg\",\n        \"@capnp-cpp//src/capnp\",\n    ],\n)\n\nrust_binary(\n    name = \"param_extractor_bin\",\n    srcs = [\"param-extractor.rs\"],\n    tags = [\"no-downstream\"],\n    target_compatible_with = select({\n        \"@platforms//os:windows\": [\"@platforms//:incompatible\"],\n        \"//conditions:default\": [],\n    }),\n    deps = [\n        \"@crates_vendor//:anyhow\",\n        \"@crates_vendor//:clang-ast\",\n        \"@crates_vendor//:flate2\",\n        \"@crates_vendor//:pico-args\",\n        \"@crates_vendor//:serde\",\n        \"@crates_vendor//:serde_json\",\n    ],\n)\n\n# Deliberately not marking this run_binary_target(): The exec configuration compiles with\n# optimization by default, causing it to run markedly faster than the (non-opt) target\n# configuration, where param_extractor would otherwise take several minutes to run. A different\n# approach would be to set -Copt-level=3 globally, but that's not helpful for debug builds.\nrun_binary(\n    name = \"param_extractor\",\n    srcs = [\n        \":dump_api_ast\",\n    ],\n    outs = [\"param-names.json\"],\n    args = [\n        \"--input\",\n        \"$(location dump_api_ast)\",\n        \"--output\",\n        \"$(location param-names.json)\",\n    ],\n    tags = [\"no-downstream\"],\n    target_compatible_with = select({\n        \"@platforms//os:windows\": [\"@platforms//:incompatible\"],\n        \"//conditions:default\": [],\n    }),\n    tool = \"param_extractor_bin\",\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "src/workerd/tools/param-extractor.rs",
    "content": "use std::ffi::OsStr;\nuse std::fs::File;\nuse std::io::BufRead;\nuse std::io::BufReader;\nuse std::io::BufWriter;\nuse std::io::Write;\nuse std::path::Path;\n\nuse anyhow::Result;\nuse flate2::read::GzDecoder;\nuse serde::Deserialize;\nuse serde::Serialize;\n\n/// Contains the declarations we care about\n#[derive(Deserialize, PartialEq, Debug)]\nenum Clang {\n    NamespaceDecl { name: Option<String> },\n\n    // Function-like -- direct parents of parameters\n    FunctionDecl { name: Option<String> },\n    CXXMethodDecl { name: Option<String> },\n    CXXRecordDecl { name: Option<String> },\n    CXXConstructorDecl,\n\n    // Parameter names\n    ParmVarDecl { name: Option<String> },\n\n    // Everything else\n    Other { name: Option<String> },\n}\n\nimpl Clang {\n    fn is_function_like(&self) -> bool {\n        matches!(\n            *self,\n            Self::FunctionDecl { .. } | Self::CXXMethodDecl { .. } | Self::CXXConstructorDecl\n        )\n    }\n\n    fn name(&self) -> Option<&str> {\n        match self {\n            Self::NamespaceDecl { name }\n            | Self::FunctionDecl { name }\n            | Self::CXXMethodDecl { name }\n            | Self::CXXRecordDecl { name }\n            | Self::ParmVarDecl { name }\n            | Self::Other { name } => name.as_ref().map(AsRef::as_ref),\n            Self::CXXConstructorDecl => Some(\"constructor\"),\n        }\n    }\n}\n\ntype ClangNode = clang_ast::Node<Clang>;\n\nfn main() -> Result<()> {\n    let mut args = pico_args::Arguments::from_env();\n\n    let clang_ast = args.value_from_os_str(\"--input\", |path_str| {\n        let path = Path::new(path_str);\n        let file = File::open(path)?;\n        let serde = {\n            let reader: &mut dyn BufRead = {\n                if Some(\"gz\") == path.extension().and_then(OsStr::to_str) {\n                    &mut BufReader::new(GzDecoder::new(file))\n                } else {\n                    &mut BufReader::new(file)\n                }\n            };\n\n            let mut deserializer = serde_json::Deserializer::from_reader(reader);\n            // Note: serde_json doesn't support custom recursion limits, only disabling.\n            // We disable the limit to handle deeply nested AST structures (default 128 is\n            // insufficient for the clang AST dump, which can be deeply nested).\n            deserializer.disable_recursion_limit();\n            ClangNode::deserialize(&mut deserializer)\n        };\n        serde.map_err(anyhow::Error::from)\n    })?;\n\n    let value = get_parameter_names(clang_ast);\n\n    let mut writer =\n        args.value_from_os_str(\"--output\", |path| File::create(path).map(BufWriter::new))?;\n\n    serde_json::to_writer(&mut writer, &value)?;\n    writer.flush()?;\n\n    Ok(())\n}\n\nfn get_parameter_names(clang_ast: ClangNode) -> Vec<Parameter> {\n    let workerd_namespace = Clang::NamespaceDecl {\n        name: Some(\"workerd\".to_owned()),\n    };\n\n    clang_ast\n        .inner\n        .into_iter()\n        .filter(|node| node.kind == workerd_namespace)\n        .flat_map(|node| traverse_disambiguous(node, &[]))\n        .collect()\n}\n\n#[derive(Serialize, Debug)]\nstruct Parameter {\n    fully_qualified_parent_name: Vec<String>,\n    function_like_name: String,\n    index: usize,\n    name: String,\n}\n\nfn traverse_disambiguous(\n    disambiguous: ClangNode,\n    fully_qualified_parent_name: &[String],\n) -> Vec<Parameter> {\n    let disambiguous_name = disambiguous\n        .kind\n        .name()\n        .map(ToOwned::to_owned)\n        .unwrap_or_default();\n\n    disambiguous\n        .inner\n        .into_iter()\n        .flat_map(|node| {\n            let mut qualified: Vec<_> = fully_qualified_parent_name.to_vec();\n            qualified.push(disambiguous_name.clone());\n            if node.kind.is_function_like() {\n                traverse_function_like(node, &qualified)\n            } else {\n                traverse_disambiguous(node, &qualified)\n            }\n        })\n        .collect()\n}\n\nfn traverse_function_like(\n    node: ClangNode,\n    fully_qualified_parent_name: &[String],\n) -> Vec<Parameter> {\n    let function_like_name = node.kind.name().expect(\"missing name\").to_owned();\n\n    node.inner\n        .into_iter()\n        .filter_map(|child| {\n            if let Clang::ParmVarDecl { name: Some(name) } = child.kind {\n                Some(name)\n            } else {\n                None\n            }\n        })\n        .enumerate()\n        .map(|(i, param_name)| Parameter {\n            fully_qualified_parent_name: fully_qualified_parent_name.to_vec(),\n            function_like_name: function_like_name.clone(),\n            index: i,\n            name: param_name,\n        })\n        .collect()\n}\n"
  },
  {
    "path": "src/workerd/tools/param-names-ast.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// This file includes all Worker APIs, and is used to generate a Clang AST dump for later\n// lookup of parameter names for inclusion in the TS types (since RTTI doesn't include this information)\n// It must be kept up to date with the APIs exposed in src/workerd/api/rtti.c++\n#include <workerd/api/actor-state.h>\n#include <workerd/api/actor.h>\n#include <workerd/api/analytics-engine.h>\n#include <workerd/api/base64.h>\n#include <workerd/api/cache.h>\n#include <workerd/api/crypto/crypto.h>\n#include <workerd/api/encoding.h>\n#include <workerd/api/events.h>\n#include <workerd/api/eventsource.h>\n#include <workerd/api/export-loopback.h>\n#include <workerd/api/filesystem.h>\n#include <workerd/api/global-scope.h>\n#include <workerd/api/html-rewriter.h>\n#include <workerd/api/hyperdrive.h>\n#include <workerd/api/kv.h>\n#include <workerd/api/memory-cache.h>\n#include <workerd/api/messagechannel.h>\n#include <workerd/api/node/node.h>\n#include <workerd/api/queue.h>\n#include <workerd/api/r2-admin.h>\n#include <workerd/api/r2.h>\n#include <workerd/api/scheduled.h>\n#include <workerd/api/sockets.h>\n#include <workerd/api/sql.h>\n#include <workerd/api/streams.h>\n#include <workerd/api/streams/standard.h>\n#include <workerd/api/sync-kv.h>\n#include <workerd/api/trace.h>\n#include <workerd/api/tracing-module.h>\n#include <workerd/api/unsafe.h>\n#include <workerd/api/url-standard.h>\n#include <workerd/api/urlpattern-standard.h>\n#include <workerd/api/urlpattern.h>\n#include <workerd/api/worker-loader.h>\n#include <workerd/api/worker-rpc.h>\n#include <workerd/api/workers-module.h>\n#include <workerd/io/compatibility-date.h>\n#include <workerd/jsg/modules.capnp.h>\n"
  },
  {
    "path": "src/workerd/util/AGENTS.md",
    "content": "# src/workerd/util/\n\n## OVERVIEW\n\nShared utility library: data structures, SQLite wrapper, feature gating, logging, thread-local scopes. No workerd-specific API dependencies — consumed across `api/`, `io/`, `server/`.\n\n## KEY UTILITIES\n\n| File              | What                                                     | Notes                                                                                  |\n| ----------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------- |\n| `state-machine.h` | Type-safe `kj::OneOf` wrapper with transition locking    | Prevents UAF in callbacks; 7 consumers in `api/streams/`                               |\n| `sqlite.h/c++`    | Full SQLite wrapper with custom VFS over `kj::Directory` | `Regulator` controls allowed SQL; `Statement` for prepared queries                     |\n| `sqlite-kv.h`     | KV abstraction on SQLite                                 | Used by Durable Object storage                                                         |\n| `autogate.h/c++`  | Runtime feature gates                                    | `AutogateKey` enum; `isEnabled()` check; 8 active gates                                |\n| `weak-refs.h`     | `WeakRef<T>` / `AtomicWeakRef<T>`                        | Non-owning refs; `tryAddStrongRef()` or `runIfAlive(fn)` pattern                       |\n| `ring-buffer.h`   | Amortized O(1) deque                                     | Replaces `std::list` in streams                                                        |\n| `small-set.h`     | `kj::OneOf`-based set                                    | O(1) for 0–2 items, fallback to `kj::HashSet`                                          |\n| `batch-queue.h`   | Double-buffered cross-thread queue                       | Producer/consumer with mutex swap                                                      |\n| `checked-queue.h` | Safe `std::list` wrapper                                 | `pop()` returns `Maybe` instead of UB on empty                                         |\n| `strong-bool.h`   | `WD_STRONG_BOOL(Name)` macro                             | Type-safe boolean; prevents implicit conversions                                       |\n| `sentry.h`        | `LOG_EXCEPTION`, `LOG_ONCE`, `LOG_PERIODICALLY`          | `DEBUG_FATAL_RELEASE_LOG` = debug assert + release warning                             |\n| `thread-scopes.h` | Thread-local scope flags                                 | Self-described \"horrible hacks\"; `AllowV8BackgroundThreadsScope`, `MultiTenantProcess` |\n| `abortable.h`     | `newAbortableInputStream/OutputStream`                   | Wraps KJ streams with disconnect capability                                            |\n| `stream-utils.h`  | `NeuterableInputStream`, `newNullInputStream`            | Disconnectable I/O; null/identity stream factories                                     |\n| `mimetype.h`      | MIME type parser/serializer                              | `MimeType::extract()` from content-type header                                         |\n| `wait-list.h`     | Cross-request event subscription                         | Shared fulfiller list for signaling waiters                                            |\n\n## Guidelines\n\n- **No bool arguments**: use `WD_STRONG_BOOL` for type safety and readability\n- **Use `kj::Maybe` for optional values**: avoid null pointers and sentinel values\n- **Prefer composition over inheritance**: utilities should be standalone and reusable without complex class hierarchies\n- **Use `StateMachine` for state management**: original pattern has been to use `kj::OneOf` directly, and that's still acceptable for simple cases, but `StateMachine` provides additional safety guarantees and should be preferred for more complex state management\n\n## ANTI-PATTERNS\n\n- **StateMachine `forceTransitionTo()`**: bypasses terminal state protection — use only for error recovery\n- **StateMachine `underlying()`**: bypasses ALL safety (transition lock, terminal states) — last resort only\n- **StateMachine + `KJ_SWITCH_ONEOF`**: does NOT acquire transition lock — UAF risk; use `whenState<T>(fn)` instead\n- **StateMachine `deferTransitionTo()`**: first-wins semantics; second call silently ignored\n- **SQLite**: `SQLITE_MISUSE` always throws; virtual tables disallowed (except FTS5); `ATTACH`/`DETACH` forbidden; callbacks must not write\n- **ThreadScopes**: thread-local state crossing module boundaries — acknowledged hack, do not proliferate\n- **RingBuffer**: moves on grow invalidating references; iterators invalidated on push/pop; intentionally not thread-safe.\n"
  },
  {
    "path": "src/workerd/util/BUILD.bazel",
    "content": "load(\"@bazel_skylib//lib:selects.bzl\", \"selects\")\nload(\"@bazel_skylib//rules:common_settings.bzl\", \"bool_flag\")\nload(\"//:build/kj_test.bzl\", \"kj_test\")\nload(\"//:build/wd_cc_library.bzl\", \"wd_cc_library\")\n\nbool_flag(\n    name = \"use_perfetto\",\n    build_setting_default = True,\n)\n\nconfig_setting(\n    name = \"set_use_perfetto\",\n    flag_values = {\"use_perfetto\": \"True\"},\n)\n\nselects.config_setting_group(\n    name = \"really_use_perfetto\",\n    match_all = [\n        \":set_use_perfetto\",\n        \"//:is_unix\",\n    ],\n)\n\nwd_cc_library(\n    name = \"perfetto\",\n    srcs = [\"perfetto-tracing.c++\"],\n    hdrs = [\n        \"perfetto-tracing.h\",\n        \"use-perfetto-categories.h\",\n    ],\n    # HACK: We do not use the perfetto IPC system backend in any capacity. If perfetto-tracing.c++\n    # is able to inline and optimize its perfetto::Tracing::Initialize() call properly, there are no\n    # references to it anywhere in workerd and the linker can optimize it away, reducing binary\n    # sizes. Enable optimization unconditionally here so that the backend goes away.\n    # See perfetto's include/perfetto/tracing/tracing.h for more context.\n    # Alternatively, we could create an override for perfetto_build_flags.h to disable the\n    # PERFETTO_IPC and PERFETTO_SYSTEM_CONSUMER defines which also exist to facilitate link-time\n    # code elimination, but doing so results in a smaller size improvement than enabling\n    # optimization here, or no additional improvement when doing combined with enabling\n    # optimization.\n    copts = select({\n        \"@platforms//os:windows\": [\n            \"/O2\",\n            \"/clang:-O3\",\n        ],\n        \"//conditions:default\": [\"-O3\"],\n    }),\n    defines = select({\n        \":really_use_perfetto\": [\"WORKERD_USE_PERFETTO\"],\n        \"//conditions:default\": [],\n    }),\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@capnp-cpp//src/kj\",\n    ] + select({\n        \":really_use_perfetto\": [\"@perfetto//:libperfetto_client_experimental\"],\n        \"//conditions:default\": [],\n    }),\n)\n# TODO(later): Currently perfetto support is not enabled on Windows simply because the\n# perfetto bazel build fails on windows for some reason and it's currently not worth\n# the time to invest of figuring out why. If some intrepid soul wishes to figure out\n# why the Windows build is failing, we could simplify things here a bit.\n\nwd_cc_library(\n    name = \"util\",\n    srcs = [\n        \"stream-utils.c++\",\n        \"wait-list.c++\",\n    ],\n    # This is verbose, but allows us to be intentional about what we include here and e.g. avoid\n    # accidentally including test headers or having targets depend on more than they need to.\n    # TODO (cleanup): Break this up entirely.\n    hdrs = [\n        \"abortable.h\",\n        \"batch-queue.h\",\n        \"canceler.h\",\n        \"color-util.h\",\n        \"http-util.h\",\n        \"stream-utils.h\",\n        \"uncaught-exception-source.h\",\n        \"wait-list.h\",\n        \"weak-refs.h\",\n        \"xthreadnotifier.h\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":duration-exceeded-logger\",\n        \"@capnp-cpp//src/kj\",\n        \"@capnp-cpp//src/kj:kj-async\",\n        # TODO(cleanup): Only for abortable.h, factor out\n        \"@capnp-cpp//src/kj/compat:kj-http\",\n    ],\n)\n\nwd_cc_library(\n    name = \"checked-queue\",\n    hdrs = [\"checked-queue.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\"@capnp-cpp//src/kj\"],\n)\n\nwd_cc_library(\n    name = \"ring-buffer\",\n    hdrs = [\"ring-buffer.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\"@capnp-cpp//src/kj\"],\n)\n\nwd_cc_library(\n    name = \"small-set\",\n    hdrs = [\"small-set.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nwd_cc_library(\n    name = \"mimetype\",\n    srcs = [\"mimetype.c++\"],\n    hdrs = [\"mimetype.h\"],\n    implementation_deps = [\n        \":string-buffer\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":strings\",\n        \"//src/workerd/jsg:memory-tracker\",\n        \"@capnp-cpp//src/kj\",\n        \"@capnp-cpp//src/kj:kj-async\",\n    ],\n)\n\nwd_cc_library(\n    name = \"strings\",\n    srcs = [\"strings.c++\"],\n    hdrs = [\"strings.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nwd_cc_library(\n    name = \"string-buffer\",\n    hdrs = [\"string-buffer.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\":strings\"],\n)\n\nwd_cc_library(\n    name = \"strong-bool\",\n    hdrs = [\"strong-bool.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nwd_cc_library(\n    name = \"uuid\",\n    srcs = [\"uuid.c++\"],\n    hdrs = [\"uuid.h\"],\n    implementation_deps = [\n        \":entropy\",\n        \"@capnp-cpp//src/kj/compat:kj-http\",\n        \"@ssl\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nwd_cc_library(\n    name = \"test\",\n    hdrs = [\"test.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@capnp-cpp//src/kj:kj-test\",\n    ],\n)\n\nwd_cc_library(\n    name = \"account-limits\",\n    hdrs = [\"account-limits.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nwd_cc_library(\n    name = \"sqlite\",\n    srcs = [\n        \"sqlite.c++\",\n        \"sqlite-kv.c++\",\n        \"sqlite-metadata.c++\",\n    ],\n    hdrs = [\n        \"sqlite.h\",\n        \"sqlite-kv.h\",\n        \"sqlite-metadata.h\",\n    ],\n    implementation_deps = [\n        \"//src/workerd/jsg:exception\",\n        \"@sqlite3\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":account-limits\",\n        \":sentry\",\n        \"@capnp-cpp//src/kj:kj-async\",\n    ],\n)\n\nwd_cc_library(\n    name = \"test-util\",\n    testonly = True,\n    srcs = [\"capnp-mock.c++\"],\n    hdrs = [\"capnp-mock.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@capnp-cpp//src/capnp:capnpc\",\n    ],\n)\n\nwd_cc_library(\n    name = \"own-util\",\n    hdrs = [\"own-util.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\"@capnp-cpp//src/kj\"],\n)\n\nwd_cc_library(\n    name = \"sentry\",\n    hdrs = [\"sentry.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nwd_cc_library(\n    name = \"thread-scopes\",\n    srcs = [\"thread-scopes.c++\"],\n    hdrs = [\"thread-scopes.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@capnp-cpp//src/kj/compat:kj-http\",\n    ],\n)\n\nwd_cc_library(\n    name = \"autogate\",\n    srcs = [\"autogate.c++\"],\n    hdrs = [\"autogate.h\"],\n    # Make missing string repr for an autogate into a hard error\n    copts = [\"-Werror=switch\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":sentry\",\n        \":strong-bool\",\n        \"@capnp-cpp//src/capnp\",\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nwd_cc_library(\n    name = \"duration-exceeded-logger\",\n    hdrs = [\"duration-exceeded-logger.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\"@capnp-cpp//src/kj\"],\n)\n\nwd_cc_library(\n    name = \"entropy\",\n    srcs = [\"entropy.c++\"],\n    hdrs = [\"entropy.h\"],\n    implementation_deps = [\n        \"@ncrypto\",\n        \"@ssl\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\"@capnp-cpp//src/kj\"],\n)\n\nwd_cc_library(\n    name = \"completion-membrane\",\n    hdrs = [\"completion-membrane.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\"@capnp-cpp//src/capnp\"],\n)\n\nwd_cc_library(\n    name = \"exception\",\n    hdrs = [\"exception.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\"@capnp-cpp//src/kj\"],\n)\n\nwd_cc_library(\n    name = \"immediate-crash\",\n    hdrs = [\"immediate-crash.h\"],\n    visibility = [\"//visibility:public\"],\n)\n\nwd_cc_library(\n    name = \"header-validation\",\n    hdrs = [\"header-validation.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@capnp-cpp//src/kj\",\n    ],\n)\n\nwd_cc_library(\n    name = \"websocket-error-handler\",\n    srcs = [\"websocket-error-handler.c++\"],\n    hdrs = [\"websocket-error-handler.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//src/workerd/jsg:exception\",\n        \"@capnp-cpp//src/kj/compat:kj-http\",\n    ],\n)\n\nexports_files([\"autogate.h\"])\n\n[\n    kj_test(\n        src = f,\n        deps = [\n            \":util\",\n        ],\n    )\n    for f in [\n        \"batch-queue-test.c++\",\n        \"wait-list-test.c++\",\n        \"duration-exceeded-logger-test.c++\",\n    ]\n]\n\nkj_test(\n    src = \"string-buffer-test.c++\",\n    deps = [\n        \":string-buffer\",\n    ],\n)\n\nkj_test(\n    src = \"strong-bool-test.c++\",\n    deps = [\n        \":strong-bool\",\n    ],\n)\n\nkj_test(\n    src = \"mimetype-test.c++\",\n    deps = [\n        \":mimetype\",\n    ],\n)\n\nkj_test(\n    size = \"large\",\n    src = \"sqlite-test.c++\",\n    deps = [\n        \":sqlite\",\n        \"//src/workerd/io:io-gate\",\n        \"@sqlite3\",\n    ],\n)\n\nkj_test(\n    src = \"sqlite-kv-test.c++\",\n    deps = [\n        \":sqlite\",\n    ],\n)\n\nkj_test(\n    src = \"sqlite-metadata-test.c++\",\n    deps = [\n        \":sqlite\",\n    ],\n)\n\nkj_test(\n    src = \"test-test.c++\",\n    deps = [\n        \":test\",\n    ],\n)\n\nkj_test(\n    src = \"uuid-test.c++\",\n    deps = [\n        \":uuid\",\n    ],\n)\n\nkj_test(\n    src = \"checked-queue-test.c++\",\n    deps = [\":checked-queue\"],\n)\n\nkj_test(\n    src = \"ring-buffer-test.c++\",\n    deps = [\":ring-buffer\"],\n)\n\nkj_test(\n    src = \"small-set-test.c++\",\n    deps = [\n        \":small-set\",\n        \":util\",\n    ],\n)\n\nwd_cc_library(\n    name = \"state-machine\",\n    hdrs = [\"state-machine.h\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\"@capnp-cpp//src/kj\"],\n)\n\nkj_test(\n    src = \"state-machine-test.c++\",\n    deps = [\":state-machine\"],\n)\n"
  },
  {
    "path": "src/workerd/util/abortable.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n#include \"canceler.h\"\n\n#include <kj/compat/http.h>\n\nnamespace workerd {\n\ntemplate <typename T>\nclass AbortableImpl final {\n public:\n  AbortableImpl(kj::Own<T> inner, RefcountedCanceler& canceler)\n      : canceler(kj::addRef(canceler)),\n        inner(kj::mv(inner)),\n        onCancel(*(this->canceler), [this]() { this->inner = kj::none; }) {}\n\n  template <typename V, typename... Args, typename... ArgsT>\n  kj::Promise<V> wrap(kj::Promise<V> (T::*fn)(ArgsT...), Args&&... args) {\n    return wrap([&](T& inner) { return (inner.*fn)(kj::fwd<ArgsT>(args)...); });\n  }\n\n  template <typename Func>\n  auto wrap(Func fn) -> decltype(fn(kj::instance<T&>())) {\n    // Be aware that the getInner() here can throw synchronously if the\n    // canceler has already been tripped.\n    return canceler->wrap(fn(getInner()));\n  }\n\n  T& getInner() {\n    canceler->throwIfCanceled();\n    // If we get past throwIfCanceled successfully, inner should still\n    // be set. If it's not, then we've got a bug somewhere and we need\n    // to know about it.\n    return *(KJ_ASSERT_NONNULL(inner));\n  }\n\n  kj::Maybe<T&> tryGetInner() {\n    return inner;\n  }\n\n private:\n  kj::Own<RefcountedCanceler> canceler;\n  kj::Maybe<kj::Own<T>> inner;\n  RefcountedCanceler::Listener onCancel;\n};\n\n// An InputStream that can be disconnected in response to RefcountedCanceler.\n// This is similar to NeuterableInputStream in global-scope.c++ but uses an\n// external kj::Canceler to trigger the disconnect.\n// This is currently only used in fetch() requests that use an AbortSignal.\n// The AbortableInputStream is created using a RefcountedCanceler,\n// which will be triggered when the AbortSignal is triggered.\n// TODO(later): It would be good to see if both this and NeuterableInputStream\n// could be combined into a single utility.\nclass AbortableInputStream final: public kj::AsyncInputStream, public kj::Refcounted {\n public:\n  AbortableInputStream(kj::Own<kj::AsyncInputStream> inner, RefcountedCanceler& canceler)\n      : impl(kj::mv(inner), canceler) {}\n\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    kj::Promise<size_t> (kj::AsyncInputStream::*tryRead)(void*, size_t, size_t) =\n        &kj::AsyncInputStream::tryRead;\n    return impl.wrap(tryRead, buffer, minBytes, maxBytes);\n  }\n\n  kj::Maybe<uint64_t> tryGetLength() override {\n    return impl.getInner().tryGetLength();\n  }\n\n  kj::Promise<uint64_t> pumpTo(kj::AsyncOutputStream& output, uint64_t amount) override {\n    return impl.wrap(&kj::AsyncInputStream::pumpTo, output, amount);\n  }\n\n private:\n  AbortableImpl<kj::AsyncInputStream> impl;\n};\n\n// A WebSocket wrapper that can be disconnected in response to a RefcountedCanceler.\n// This is currently only used when opening a WebSocket with a fetch() request that\n// is using an AbortSignal. The AbortableWebSocket is created using the AbortSignal's\n// RefcountedCanceler, which will be triggered when the AbortSignal is triggered.\nclass AbortableWebSocket final: public kj::WebSocket, public kj::Refcounted {\n public:\n  AbortableWebSocket(kj::Own<kj::WebSocket> inner, RefcountedCanceler& canceler)\n      : impl(kj::mv(inner), canceler) {}\n\n  kj::Promise<void> send(kj::ArrayPtr<const kj::byte> message) override {\n    return impl.wrap(\n        static_cast<kj::Promise<void> (kj::WebSocket::*)(kj::ArrayPtr<const kj::byte>)>(\n            &kj::WebSocket::send),\n        message);\n  }\n\n  kj::Promise<void> send(kj::ArrayPtr<const char> message) override {\n    return impl.wrap(static_cast<kj::Promise<void> (kj::WebSocket::*)(kj::ArrayPtr<const char>)>(\n                         &kj::WebSocket::send),\n        message);\n  }\n\n  kj::Promise<void> close(uint16_t code, kj::StringPtr reason) override {\n    return impl.wrap(&kj::WebSocket::close, code, reason);\n  }\n\n  void disconnect() override {\n    KJ_IF_SOME(inner, impl.tryGetInner()) {\n      inner.disconnect();\n    }\n  }\n\n  void abort() override {\n    KJ_IF_SOME(inner, impl.tryGetInner()) {\n      inner.abort();\n    }\n  }\n\n  kj::Promise<void> whenAborted() override {\n    return impl.wrap(&kj::WebSocket::whenAborted);\n  }\n\n  kj::Promise<Message> receive(size_t maxSize = SUGGESTED_MAX_MESSAGE_SIZE) override {\n    return impl.wrap(&kj::WebSocket::receive, maxSize);\n  }\n\n  kj::Promise<void> pumpTo(kj::WebSocket& other) override {\n    return impl.wrap(&kj::WebSocket::pumpTo, other);\n  }\n\n  kj::Maybe<kj::Promise<void>> tryPumpFrom(kj::WebSocket& other) override {\n    return impl.wrap([&other](auto& inner) -> kj::Promise<void> { return other.pumpTo(inner); });\n  }\n\n  uint64_t sentByteCount() override {\n    return impl.getInner().sentByteCount();\n  }\n\n  uint64_t receivedByteCount() override {\n    return impl.getInner().receivedByteCount();\n  }\n\n  kj::Maybe<kj::String> getPreferredExtensions(ExtensionsContext ctx) override {\n    return impl.getInner().getPreferredExtensions(ctx);\n  };\n\n private:\n  AbortableImpl<kj::WebSocket> impl;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/account-limits.h",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\nnamespace workerd {\n\n// Interface for enforcing various actor related account limits.\n// Non-functional in workerd, but used to enforce free tier account limits internally.\nclass ActorAccountLimits {\n public:\n  // Throws if the associated account is no longer allowed to execute sqlite queries\n  virtual void requireActorCanExecuteQueries() const {};\n\n  // Allow default equality comparison\n  bool operator==(const ActorAccountLimits&) const = default;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/autogate.c++",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#include \"autogate.h\"\n\n#include <workerd/util/sentry.h>\n\n#include <stdlib.h>\n\n#include <capnp/message.h>\n#include <kj/common.h>\n#include <kj/debug.h>\n\nnamespace workerd::util {\n\nkj::Maybe<Autogate> globalAutogate;\n\nkj::StringPtr KJ_STRINGIFY(AutogateKey key) {\n  switch (key) {\n    case AutogateKey::TEST_WORKERD:\n      return \"test-workerd\"_kj;\n    case AutogateKey::V8_FAST_API:\n      return \"v8-fast-api\"_kj;\n    case AutogateKey::STREAMING_TAIL_WORKER:\n      return \"streaming-tail-worker\"_kj;\n    case AutogateKey::TAIL_STREAM_REFACTOR:\n      return \"tail-stream-refactor\"_kj;\n    case AutogateKey::RUST_BACKED_NODE_DNS:\n      return \"rust-backed-node-dns\"_kj;\n    case AutogateKey::RPC_USE_EXTERNAL_PUSHER:\n      return \"rpc-use-external-pusher\"_kj;\n    case AutogateKey::WASM_SHUTDOWN_SIGNAL_SHIM:\n      return \"wasm-shutdown-signal-shim\"_kj;\n    case AutogateKey::ENABLE_FAST_TEXTENCODER:\n      return \"enable-fast-textencoder\"_kj;\n    case AutogateKey::ENABLE_DRAINING_READ_ON_STANDARD_STREAMS:\n      return \"enable-draining-read-on-standard-streams\"_kj;\n    case AutogateKey::NumOfKeys:\n      KJ_FAIL_ASSERT(\"NumOfKeys should not be used in getName\");\n  }\n}\n\nAutogate::Autogate(capnp::List<capnp::Text>::Reader autogates) {\n  // gates array is zero-initialized by default.\n  for (auto name: autogates) {\n    if (!name.startsWith(\"workerd-autogate-\")) {\n      LOG_ERROR_ONCE(\"Autogate configuration includes gate with invalid prefix.\");\n      continue;\n    }\n    auto sliced = name.slice(17);\n\n    // Parse the gate name into a AutogateKey.\n    for (AutogateKey i = AutogateKey(0); i < AutogateKey::NumOfKeys;\n         i = AutogateKey(static_cast<int>(i) + 1)) {\n      if (kj::str(i) == sliced) {\n        gates[static_cast<unsigned long>(i)] = true;\n        break;\n      }\n    }\n  }\n}\n\nbool Autogate::isEnabled(AutogateKey key) {\n  KJ_IF_SOME(a, globalAutogate) {\n    return a.gates[static_cast<unsigned long>(key)];\n  }\n\n  static const bool defaultResult = getenv(\"WORKERD_ALL_AUTOGATES\") != nullptr;\n  return defaultResult;\n}\n\nvoid Autogate::initAutogate(\n    capnp::List<capnp::Text>::Reader gates, IgnoreAllAutogatesEnv ignoreEnv) {\n  // If the WORKERD_ALL_AUTOGATES env var is set, enable all gates regardless of what\n  // was passed in. This ensures the @all-autogates test variant works even when\n  // initAutogate({}) is called early (e.g. by TestFixture), which would otherwise\n  // set globalAutogate to all-false and prevent isEnabled() from reaching its env var\n  // fallback.\n  //\n  // Callers (e.g. the production server) that manage the all-autogates behavior themselves and\n  // build selective gate configs can pass IgnoreAllAutogatesEnv::YES to skip this override.\n  if (!ignoreEnv && getenv(\"WORKERD_ALL_AUTOGATES\") != nullptr) {\n    return initAllAutogates();\n  }\n  globalAutogate = Autogate(gates);\n}\n\nvoid Autogate::deinitAutogate() {\n  globalAutogate = kj::none;\n}\n\nvoid Autogate::initAllAutogates() {\n  Autogate autogate;\n  for (AutogateKey i = AutogateKey(0); i < AutogateKey::NumOfKeys;\n       i = AutogateKey(static_cast<int>(i) + 1)) {\n    autogate.gates[static_cast<unsigned long>(i)] = true;\n  }\n  globalAutogate = kj::mv(autogate);\n}\n\nvoid Autogate::initAutogateNamesForTest(std::initializer_list<kj::StringPtr> gateNames) {\n  capnp::MallocMessageBuilder message;\n  auto orphanage = message.getOrphanage();\n  auto gatesOrphan = orphanage.newOrphan<capnp::List<capnp::Text>>(gateNames.size());\n  auto gates = gatesOrphan.get();\n  size_t count = 0;\n  for (auto name: gateNames) {\n    gates.set(count++, kj::str(\"workerd-autogate-\", name));\n  }\n  Autogate::initAutogate(gates.asReader());\n}\n\n}  // namespace workerd::util\n"
  },
  {
    "path": "src/workerd/util/autogate.h",
    "content": "// Copyright (c) 2017-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <workerd/util/strong-bool.h>\n\n#include <capnp/blob.h>\n#include <capnp/list.h>\n#include <kj/string.h>\n\n#include <initializer_list>\n\nnamespace workerd::util {\n\n// When YES, initAutogate() ignores the WORKERD_ALL_AUTOGATES environment variable and uses only\n// the gates list that was explicitly passed in. This is used by the production server, which has\n// its own autogate test infrastructure that manages the all-autogates behavior and builds\n// selective gate configs (with forceOff / toggle support).\nWD_STRONG_BOOL(IgnoreAllAutogatesEnv);\n\n// Workerd-specific list of autogate keys (can also be used in internal repo).\nenum class AutogateKey {\n  TEST_WORKERD,\n  V8_FAST_API,\n  // Enables support for the streaming tail worker. Note that this is currently also guarded behind\n  // an experimental compat flag.\n  STREAMING_TAIL_WORKER,\n  // Enable refactor used to consolidate the different tail worker stream implementations.\n  TAIL_STREAM_REFACTOR,\n  // Enable Rust-backed Node.js DNS implementation\n  RUST_BACKED_NODE_DNS,\n  // Use ExternalPusher instead of StreamSink to handle streams in RPC.\n  RPC_USE_EXTERNAL_PUSHER,\n  // Enable the WebAssembly.instantiate shim that detects modules exporting __instance_signal /\n  // __instance_terminated and registers them for receiving the CPU-limit shutdown signal.\n  WASM_SHUTDOWN_SIGNAL_SHIM,\n  // Enable fast TextEncoder implementation using simdutf\n  ENABLE_FAST_TEXTENCODER,\n  // Enable draining read on standard streams\n  ENABLE_DRAINING_READ_ON_STANDARD_STREAMS,\n  NumOfKeys  // Reserved for iteration.\n};\n\n// This class allows code changes to be rolled out independent of full binary releases. It enables\n// specific code paths to be gradually rolled out via our internal tooling.\n// See the equivalent file in our internal repo for more details.\n//\n// Workerd-specific gates can be added here.\n//\n// Usage:\n//\n//     #include <workerd/util/autogate.h>\n//     Autogate::isEnabled(AutogateKey::YOUR_FEATURE_KEY)\n//\n// When making structural changes here, ensure you align them with autogate.h in the internal repo.\nclass Autogate {\n\n public:\n  static bool isEnabled(AutogateKey key);\n\n  // Creates a global Autogate and seeds it with gates that are specified in the config.\n  //\n  // This function is not thread safe, it should be called exactly once close to the start of the\n  // process before any threads are created.\n  static void initAutogate(capnp::List<capnp::Text>::Reader autogates,\n      IgnoreAllAutogatesEnv ignoreEnv = IgnoreAllAutogatesEnv::NO);\n\n  // Convenience method for bin-tests to invoke initAutogate() with an appropriate config.\n  static void initAutogateNamesForTest(std::initializer_list<kj::StringPtr> gateNames);\n\n  // Initializes all autogates to true. Used for testing with the --all-autogates flag.\n  static void initAllAutogates();\n\n  // Destroys an initialized global Autogate instance. Used only for testing.\n  static void deinitAutogate();\n\n private:\n  bool gates[static_cast<unsigned long>(AutogateKey::NumOfKeys)] = {};\n\n  Autogate() = default;\n  Autogate(capnp::List<capnp::Text>::Reader autogates);\n};\n\n// Retrieves the name of the gate.\n//\n// When adding a new gate, add it into this method as well.\nkj::StringPtr KJ_STRINGIFY(AutogateKey key);\n\n}  // namespace workerd::util\n"
  },
  {
    "path": "src/workerd/util/batch-queue-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"batch-queue.h\"\n\n#include <kj/test.h>\n\nnamespace workerd {\nnamespace {\n\nstatic constexpr auto INITIAL_CAPACITY = 8, MAX_CAPACITY = 100;\n\nKJ_TEST(\"BatchQueue basic operations\") {\n  BatchQueue<int> batchQueue{INITIAL_CAPACITY, MAX_CAPACITY};\n\n  KJ_EXPECT(batchQueue.empty());\n  KJ_EXPECT(batchQueue.size() == 0);\n\n  for ([[maybe_unused]] auto item: batchQueue.pop().asArrayPtr()) {\n    KJ_FAIL_EXPECT(\"Should have been empty\");\n  }\n\n  batchQueue.push(1);\n  KJ_EXPECT(!batchQueue.empty());\n  KJ_EXPECT(batchQueue.size() == 1);\n  batchQueue.push(2);\n  KJ_EXPECT(batchQueue.size() == 2);\n\n  int count = 0;\n  for (auto item: batchQueue.pop().asArrayPtr()) {\n    KJ_EXPECT(item == ++count);\n  }\n}\n\nKJ_TEST(\"BatchQueue::Batch clears the pop buffer when it is destroyed\") {\n  struct DestructionDetector {\n    DestructionDetector(uint& count): count(count) {}\n    ~DestructionDetector() noexcept(false) {\n      ++count;\n    }\n    KJ_DISALLOW_COPY_AND_MOVE(DestructionDetector);\n    uint& count;\n  };\n\n  BatchQueue<kj::Own<DestructionDetector>> batchQueue{INITIAL_CAPACITY, MAX_CAPACITY};\n\n  uint count = 0;\n  batchQueue.push(kj::heap<DestructionDetector>(count));\n  {\n    auto batch = batchQueue.pop();\n    KJ_EXPECT(count == 0);\n  }\n  KJ_EXPECT(count == 1);\n}\n\nKJ_TEST(\"BatchQueue throws if two pop() operations run concurrently\") {\n  BatchQueue<int> batchQueue{INITIAL_CAPACITY, MAX_CAPACITY};\n\n  batchQueue.push(123);\n  auto batch0 = batchQueue.pop();\n  KJ_EXPECT_THROW_MESSAGE(\"pop()'s previous result not yet destroyed\", batchQueue.pop());\n}\n\nKJ_TEST(\"BatchQueue uses two buffers\") {\n  BatchQueue<int> batchQueue{INITIAL_CAPACITY, MAX_CAPACITY};\n\n  batchQueue.push(123);\n  auto buffer0 = batchQueue.pop().asArrayPtr();\n  batchQueue.push(123);\n  auto buffer1 = batchQueue.pop().asArrayPtr();\n  batchQueue.push(123);\n  auto buffer2 = batchQueue.pop().asArrayPtr();\n  batchQueue.push(123);\n  auto buffer3 = batchQueue.pop().asArrayPtr();\n\n  KJ_EXPECT(buffer0.begin() != buffer1.begin());\n  KJ_EXPECT(buffer0.begin() == buffer2.begin());\n  KJ_EXPECT(buffer1.begin() == buffer3.begin());\n}\n\nKJ_TEST(\"BatchQueue reconstructs buffers if they grow above maxCapacity\") {\n  BatchQueue<int> batchQueue{INITIAL_CAPACITY, MAX_CAPACITY};\n\n  for (auto i = 0; i < MAX_CAPACITY + 1; ++i) {\n    batchQueue.push(i);\n  }\n  auto buffer0 = batchQueue.pop().asArrayPtr();\n  batchQueue.push(123);\n  auto buffer1 = batchQueue.pop().asArrayPtr();\n  batchQueue.push(123);\n  auto buffer2 = batchQueue.pop().asArrayPtr();\n  batchQueue.push(123);\n  auto buffer3 = batchQueue.pop().asArrayPtr();\n\n  KJ_EXPECT(buffer0.begin() != buffer1.begin());\n  // This next expectation is only reliable because ~Batch() constructs the next buffer before\n  // destroying the old one.\n  KJ_EXPECT(buffer0.begin() != buffer2.begin());\n  KJ_EXPECT(buffer1.begin() == buffer3.begin());\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/batch-queue.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/debug.h>\n#include <kj/vector.h>\n\n#include <utility>\n\nnamespace workerd {\n\nusing kj::uint;\n\n// A double-buffered batch queue which enforces an upper bound on buffer growth.\n//\n// Objects of this type have two buffers -- the push buffer and the pop buffer -- and support\n// `push()` and `pop()` operations. `push()` adds elements to the push buffer. `pop()` swaps the\n// push and the pop buffers and returns a RAII object which provides a view onto the pop buffer.\n// When the RAII object is destroyed, it resets the size and capacity of the pop buffer.\n//\n// This class is useful when the cost of context switching between producers and consumers is\n// high and/or when you must be able to gracefully handle bursts of pushes, such as when\n// transferring objects between threads. Note that this class implements no cross-thread\n// synchronization itself, but it can become an effective multiple-producer, single-consumer queue\n// when wrapped as a `kj::MutexGuarded<BatchQueue<T>>`.\ntemplate <typename T>\nclass BatchQueue {\n public:\n  // `initialCapacity` is the number of elements of type T for which we should allocate space in the\n  // initial buffers, and any reconstructed buffers. Buffers will be reconstructed if they are\n  // observed to grow beyond `maxCapacity` after a completed pop operation.\n  explicit BatchQueue(uint initialCapacity, uint maxCapacity)\n      : pushBuffer(initialCapacity),\n        popBuffer(initialCapacity),\n        initialCapacity(initialCapacity),\n        maxCapacity(maxCapacity) {}\n\n  // This is the return type of `pop()` (in fact, `pop()` is the only way to construct a non-empty\n  // Batch). Default-constructible, moveable, and non-copyable.\n  //\n  // A Batch can be converted to an ArrayPtr<T>. When a Batch is destroyed, it clears the pop\n  // buffer and resets the pop buffer capacity to `initialCapacity` if necessary.\n  class Batch {\n   public:\n    Batch() = default;\n    Batch(Batch&&) = default;\n    Batch& operator=(Batch&&) = default;\n    ~Batch() noexcept(false);\n    KJ_DISALLOW_COPY(Batch);\n\n    operator kj::ArrayPtr<T>() {\n      return batchQueue.map([](auto& bq) -> kj::ArrayPtr<T> {\n        return bq.popBuffer;\n      }).orDefault(nullptr);\n    }\n\n    kj::ArrayPtr<T> asArrayPtr() {\n      return *this;\n    }\n\n   private:\n    explicit Batch(BatchQueue& batchQueue): batchQueue(batchQueue) {}\n    friend BatchQueue;\n\n    kj::Maybe<BatchQueue<T>&> batchQueue;\n    // It's a Maybe so we can support move operations.\n  };\n\n  // If a batch is available, swap the buffers and return a Batch object backed by the pop buffer.\n  // The caller should destroy the Batch object as soon as they are done with it. Destruction will\n  // clear the pop buffer and, if necessary, reconstruct it to stay under `maxCapacity`.\n  //\n  // Throws if `pop()` is called again before the previous Batch object was destroyed. Note\n  // that this exception is only reliable if the previous `pop()` returned a non-empty Batch.\n  //\n  // `pop()` accesses both buffers, so it must be synchronized with `push()` operations across\n  // threads. Batch objects and `push()` access different buffers, so they require no explicit\n  // cross-thread synchronization with each other.\n  Batch pop() {\n    KJ_REQUIRE(popBuffer.empty(), \"pop()'s previous result not yet destroyed.\");\n\n    Batch batch;\n\n    if (!pushBuffer.empty()) {\n      std::swap(pushBuffer, popBuffer);\n      batch = Batch(*this);\n    }\n\n    return batch;\n  }\n\n  // Add an item to the current batch.\n  template <typename U>\n  void push(U&& value) {\n    pushBuffer.add(kj::fwd<U>(value));\n  }\n\n  auto empty() const {\n    return pushBuffer.empty();\n  }\n  auto size() const {\n    return pushBuffer.size();\n  }\n\n private:\n  kj::Vector<T> pushBuffer;\n  kj::Vector<T> popBuffer;\n  uint initialCapacity;\n  uint maxCapacity;\n};\n\n// =======================================================================================\n// Inline implementation details\n\ntemplate <typename T>\nBatchQueue<T>::Batch::~Batch() noexcept(false) {\n  KJ_IF_SOME(bq, batchQueue) {\n    bq.popBuffer.clear();\n    if (auto capacity = bq.popBuffer.capacity(); capacity > bq.maxCapacity) {\n      // Reset the queue to avoid letting it grow unbounded.\n      bq.popBuffer = kj::Vector<T>(bq.initialCapacity);\n    }\n  }\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/canceler.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n#include <kj/async.h>\n#include <kj/common.h>\n#include <kj/debug.h>\n#include <kj/function.h>\n#include <kj/list.h>\n#include <kj/refcount.h>\n\nnamespace workerd {\n\n// A simple wrapper around kj::Canceler that can be safely\n// shared by multiple objects. This is used, for instance,\n// to support fetch() requests that use an AbortSignal.\n// The AbortSignal (see api/basics.h) creates an instance\n// of RefcountedCanceler then passes references to it out\n// to various other objects that will use it to wrap their\n// Promises.\nclass RefcountedCanceler: public kj::Refcounted {\n public:\n  class Listener {\n   public:\n    explicit Listener(RefcountedCanceler& canceler, kj::Function<void()> fn)\n        : fn(kj::mv(fn)),\n          canceler(canceler) {\n      canceler.addListener(*this);\n    }\n\n    ~Listener() {\n      canceler.removeListener(*this);\n    }\n\n   private:\n    kj::Function<void()> fn;\n    RefcountedCanceler& canceler;\n    kj::ListLink<Listener> link;\n\n    friend class RefcountedCanceler;\n  };\n\n  RefcountedCanceler(kj::Maybe<kj::Exception> reason = kj::none): reason(kj::mv(reason)) {}\n\n  ~RefcountedCanceler() noexcept(false) {\n    // `listeners` has to be empty since each listener should have held a strong reference.\n    KJ_ASSERT(listeners.empty());\n\n    // RefcountedCanceler is used in use cases where we don't want to cancel by default if the\n    // canceler is destroyed, so release any remaining wrapped promises.\n    canceler.release();\n  }\n\n  KJ_DISALLOW_COPY_AND_MOVE(RefcountedCanceler);\n\n  template <typename T>\n  kj::Promise<T> wrap(kj::Promise<T> promise) {\n    KJ_IF_SOME(ex, reason) {\n      return kj::cp(ex);\n    }\n    return canceler.wrap(kj::mv(promise));\n  }\n\n  void cancel(kj::StringPtr cancelReason) {\n    if (reason == kj::none) {\n      cancel(kj::Exception(\n          kj::Exception::Type::DISCONNECTED, __FILE__, __LINE__, kj::str(cancelReason)));\n    }\n  }\n\n  void cancel(const kj::Exception& exception) {\n    if (reason == kj::none) {\n      reason = kj::cp(exception);\n      canceler.cancel(exception);\n      for (auto& listener: listeners) {\n        listener.fn();\n      }\n    }\n  }\n\n  bool isEmpty() const {\n    return canceler.isEmpty();\n  }\n\n  void throwIfCanceled() {\n    KJ_IF_SOME(ex, reason) {\n      kj::throwFatalException(kj::cp(ex));\n    }\n  }\n\n  bool isCanceled() const {\n    return reason != kj::none;\n  }\n\n  void addListener(Listener& listener) {\n    listeners.add(listener);\n  }\n\n  void removeListener(Listener& listener) {\n    listeners.remove(listener);\n  }\n\n private:\n  kj::Canceler canceler;\n  kj::Maybe<kj::Exception> reason;\n\n  kj::List<Listener, &Listener::link> listeners;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/capnp-mock.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"capnp-mock.h\"\n\nnamespace workerd {\n\nkj::String canonicalizeCapnpText(\n    capnp::StructSchema schema, kj::StringPtr text, kj::Maybe<kj::StringPtr> capName) {\n  capnp::MallocMessageBuilder message;\n  auto root = message.getRoot<capnp::DynamicStruct>(schema);\n  TEXT_CODEC.decode(text, root);\n  KJ_IF_SOME(c, capName) {\n    // Fill in dummy capability.\n    auto field = schema.getFieldByName(c);\n    root.set(field,\n        capnp::Capability::Client(KJ_EXCEPTION(FAILED, \"dummy\"))\n            .castAs<capnp::DynamicCapability>(field.getType().asInterface()));\n  }\n  return TEXT_CODEC.encode(root.asReader());\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/capnp-mock.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <capnp/dynamic.h>\n#include <capnp/message.h>\n#include <capnp/serialize-text.h>\n#include <kj/debug.h>\n#include <kj/list.h>\n#include <kj/map.h>\n#include <kj/refcount.h>\n#include <kj/source-location.h>\n\nnamespace workerd {\n\n// =======================================================================================\n// KJ assert macros that support specifying a SourceLocation. These allow our test functions\n// below to capture the caller's SourceLocation for use in errors, which is nice.\n//\n// TODO(cleanup): Move this to KJ!\n\n#ifndef KJ_REQUIRE_AT\n#define KJ_REQUIRE_AT(cond, location, ...)                                                         \\\n  if (auto _kjCondition = ::kj::_::MAGIC_ASSERT << cond) {                                         \\\n  } else                                                                                           \\\n    for (::kj::_::Debug::Fault f(location.fileName, location.lineNumber,                           \\\n             ::kj::Exception::Type::FAILED, #cond, \"_kjCondition,\" #__VA_ARGS__, _kjCondition,     \\\n             ##__VA_ARGS__);                                                                       \\\n         ; f.fatal())\n#endif\n\n#ifndef KJ_FAIL_REQUIRE_AT\n#define KJ_FAIL_REQUIRE_AT(location, ...)                                                          \\\n  for (::kj::_::Debug::Fault f(location.fileName, location.lineNumber,                             \\\n           ::kj::Exception::Type::FAILED, nullptr, #__VA_ARGS__, ##__VA_ARGS__);                   \\\n       ; f.fatal())\n#endif\n\n#ifndef KJ_REQUIRE_NONNULL_AT\n#define KJ_REQUIRE_NONNULL_AT(value, location, ...)                                                \\\n  (*({                                                                                             \\\n    auto _kj_result = ::kj::_::readMaybe(value);                                                   \\\n    if (KJ_UNLIKELY(!_kj_result)) {                                                                \\\n      ::kj::_::Debug::Fault(location.fileName, location.lineNumber, ::kj::Exception::Type::FAILED, \\\n          #value \" != nullptr\", #__VA_ARGS__, ##__VA_ARGS__)                                       \\\n          .fatal();                                                                                \\\n    }                                                                                              \\\n    kj::mv(_kj_result);                                                                            \\\n  }))\n#endif\n\n#ifndef KJ_ASSERT_AT\n#define KJ_ASSERT_AT KJ_REQUIRE_AT\n#endif\n\n#ifndef KJ_FAIL_ASSERT_AT\n#define KJ_FAIL_ASSERT_AT KJ_FAIL_REQUIRE_AT\n#endif\n\n#ifndef KJ_ASSERT_NONNULL_AT\n#define KJ_ASSERT_NONNULL_AT KJ_REQUIRE_NONNULL_AT\n#endif\n\n#ifndef KJ_LOG_AT\n#define KJ_LOG_AT(severity, location, ...)                                                         \\\n  for (bool _kj_shouldLog = ::kj::_::Debug::shouldLog(::kj::LogSeverity::severity); _kj_shouldLog; \\\n       _kj_shouldLog = false)                                                                      \\\n  ::kj::_::Debug::log(location.fileName, location.lineNumber, ::kj::LogSeverity::severity,         \\\n      #__VA_ARGS__, ##__VA_ARGS__)\n#endif\n\n// =======================================================================================\n// Cap'n Proto mocking framework\n//\n// TODO(cleanup): Move this to Cap'n Proto!\n\nconst capnp::TextCodec TEXT_CODEC;\n\nkj::String canonicalizeCapnpText(\n    capnp::StructSchema schema, kj::StringPtr text, kj::Maybe<kj::StringPtr> capName = kj::none);\n\nclass MockClient: public capnp::DynamicCapability::Client {\n public:\n  using capnp::DynamicCapability::Client::Client;\n  MockClient(capnp::DynamicCapability::Client&& client)\n      : capnp::DynamicCapability::Client(kj::mv(client)) {}\n\n  class ExpectedCall {\n   public:\n    ExpectedCall(capnp::RemotePromise<capnp::DynamicStruct> promise): promise(kj::mv(promise)) {}\n\n    void expectReturns(\n        kj::StringPtr resultsText, kj::WaitScope& ws, kj::SourceLocation location = {}) && {\n      kj::String expectedResults = canonicalizeCapnpText(promise.getSchema(), resultsText);\n      auto response = promise.wait(ws);\n      auto actualResults = TEXT_CODEC.encode(response);\n      KJ_ASSERT_AT(expectedResults == actualResults, location);\n    }\n\n    void expectThrows(kj::Exception::Type expectedType,\n        kj::StringPtr expectedMessageSubstring,\n        kj::WaitScope& ws,\n        kj::SourceLocation location = {}) {\n      promise\n          .then([&](auto&&) {\n        KJ_FAIL_ASSERT_AT(location, \"expected call to throw exception but instead it returned\",\n            expectedType, expectedMessageSubstring);\n      }, [&](kj::Exception&& e) {\n        KJ_ASSERT_AT(e.getDescription().contains(expectedMessageSubstring), location,\n            expectedMessageSubstring, e);\n        KJ_ASSERT_AT(e.getType() == expectedType, location, e);\n      }).wait(ws);\n    }\n\n   private:\n    capnp::RemotePromise<capnp::DynamicStruct> promise;\n  };\n\n  ExpectedCall call(kj::StringPtr methodName, kj::StringPtr params) {\n    auto req = newRequest(methodName);\n    TEXT_CODEC.decode(params, req);\n    return ExpectedCall(req.send());\n  }\n};\n\n// Infrastructure to mock a capability!\n//\n// TODO(cleanup): This should obviously go in Cap'n Proto!\nclass MockServer: public kj::Refcounted {\n  struct ReceivedCall;\n\n public:\n  MockServer(capnp::InterfaceSchema schema): schema(schema) {}\n\n  template <typename T>\n  struct Pair {\n    kj::Own<MockServer> mock;\n    T::Client client;\n  };\n\n  template <typename T>\n  static Pair<T> make() {\n    auto mock = kj::refcounted<MockServer>(capnp::Schema::from<T>());\n    capnp::DynamicCapability::Client client = kj::heap<Server>(*mock);\n    return {kj::mv(mock), client.as<T>()};\n  }\n\n  class ExpectedCall {\n   public:\n    ExpectedCall(ReceivedCall& received): maybeReceived(received) {\n      received.expectedCall = this;\n    }\n    ExpectedCall(ExpectedCall&& other): maybeReceived(kj::mv(other.maybeReceived)) {\n      KJ_IF_SOME(r, maybeReceived) r.expectedCall = *this;\n    }\n    ~ExpectedCall() noexcept(false) {\n      KJ_IF_SOME(r, maybeReceived) {\n        KJ_ASSERT(&KJ_ASSERT_NONNULL(r.expectedCall) == this);\n        r.expectedCall = kj::none;\n      }\n    }\n\n    ExpectedCall withParams(kj::StringPtr paramsText,\n        kj::Maybe<kj::StringPtr> capName = kj::none,\n        kj::SourceLocation location = {}) &&\n        KJ_WARN_UNUSED_RESULT {\n      // Expect that the call had the given parameters.\n\n      auto& received = getReceived(location);\n\n      kj::String expectedParams =\n          canonicalizeCapnpText(received.method.getParamType(), paramsText, capName);\n\n      auto actualParams = TEXT_CODEC.encode(received.context.getParams());\n      KJ_ASSERT_AT(expectedParams == actualParams, location);\n\n      return kj::mv(*this);\n    }\n\n    // Helper for cases where the received call is expected to invoke some callback capability.\n    //\n    // Expect that the params contain a field named `callbackName` whose type is an interface.\n    // `func()` will be invoked and passed a `MockClient` representing this capability. It can\n    // then invoke the callback as it seems fit.\n    //\n    // Note that it's explicitly OK if `func` captures a `WaitScope` and uses it. In this way,\n    // the incoming call can be delayed from returning until the callback completes.\n    template <typename Func>\n        ExpectedCall useCallback(\n            kj::StringPtr callbackName, Func&& func, kj::SourceLocation location = {}) &&\n        KJ_WARN_UNUSED_RESULT {\n      auto& received = getReceived(location);\n      func(received.context.getParams().get(callbackName).as<capnp::DynamicCapability>());\n      return kj::mv(*this);\n    }\n\n    // Causes the method to return the given result message, which is parsed from text.\n    void thenReturn(kj::StringPtr message, kj::SourceLocation location = {}) && {\n      auto& received = getReceived(location);\n      TEXT_CODEC.decode(message, received.context.getResults());\n      received.fulfiller.fulfill();\n    }\n\n    // Causes the method to return the given result message, which is parsed from text.\n    // All capabilities in the result message will be filled in, with MockServer instances\n    // returned in the hashmap.\n    kj::HashMap<kj::String, kj::Own<MockServer>> thenReturnWithMocks(\n        kj::StringPtr message, kj::SourceLocation location = {}) && {\n      auto& received = getReceived(location);\n      auto callResults = received.context.getResults();\n      auto results = kj::HashMap<kj::String, kj::Own<MockServer>>();\n      TEXT_CODEC.decode(message, callResults);\n      for (const auto& field: received.method.getResultType().getFields()) {\n        if (field.getType().isInterface()) {\n          auto name = field.getProto().getName();\n          auto mockServer = kj::refcounted<MockServer>(field.getType().asInterface());\n          callResults.set(name, kj::heap<Server>(*mockServer));\n          results.insert(kj::str(name), kj::mv(mockServer));\n        }\n      }\n\n      received.fulfiller.fulfill();\n      return kj::mv(results);\n    }\n\n    // Causes the method to throw an exception\n    void thenThrow(kj::Exception&& e, kj::SourceLocation location = {}) && {\n      auto& received = getReceived(location);\n      received.fulfiller.reject(kj::mv(e));\n    }\n\n    // Return a new mock capability. The method result type is expected to contain a single\n    // field with the given name whose type is an interface type. It will be filled in with a\n    // new mock object, and the MockServer is returned in order to set further expectations.\n    kj::Own<MockServer> returnMock(kj::StringPtr fieldName, kj::SourceLocation location = {}) && {\n      auto& received = getReceived(location);\n      auto field = received.method.getResultType().getFieldByName(fieldName);\n      auto result = kj::refcounted<MockServer>(field.getType().asInterface());\n      received.context.getResults().set(field, kj::heap<Server>(*result));\n      received.fulfiller.fulfill();\n      return result;\n    }\n\n    void expectCanceled(kj::SourceLocation location = {}) {\n      KJ_ASSERT_AT(maybeReceived == kj::none, location, \"call has not been canceled\");\n    }\n\n   private:\n    kj::Maybe<ReceivedCall&> maybeReceived;\n    ReceivedCall& getReceived(kj::SourceLocation location) {\n      return KJ_REQUIRE_NONNULL_AT(maybeReceived, location, \"call was unexpectedly canceled\");\n    }\n    friend struct ReceivedCall;\n  };\n\n  ExpectedCall expectCall(kj::StringPtr methodName,\n      kj::WaitScope& waitScope,\n      kj::SourceLocation location = {}) KJ_WARN_UNUSED_RESULT {\n    auto expectedMethod = schema.getMethodByName(methodName);\n\n    KJ_ASSERT_AT(\n        waitForEvent(waitScope), location, \"no method call was received when expected\", methodName);\n\n    KJ_ASSERT_AT(\n        !dropped, location, \"capability was dropped without making expected call\", methodName);\n\n    auto& received = receivedCalls.front();\n    receivedCalls.remove(received);\n\n    KJ_ASSERT_AT(received.method == expectedMethod, location,\n        \"a different method was called than expected\", received.method.getProto().getName(),\n        expectedMethod.getProto().getName());\n\n    return ExpectedCall(received);\n  }\n\n  void expectDropped(kj::WaitScope& waitScope, kj::SourceLocation location = {}) {\n    KJ_ASSERT_AT(waitForEvent(waitScope), location, \"capability was not dropped when expected\");\n    KJ_ASSERT_AT(\n        receivedCalls.empty(), location, receivedCalls.front().method.getProto().getName());\n\n    KJ_ASSERT(dropped);  // should always be true if receivedCalls is empty\n  }\n\n  void expectNoActivity(kj::WaitScope& waitScope, kj::SourceLocation location = {}) {\n    if (waitForEvent(waitScope)) {\n      if (!receivedCalls.empty()) {\n        KJ_FAIL_ASSERT_AT(location, \"unexpected call received\",\n            receivedCalls.front().method.getProto().getName());\n      }\n      if (dropped) {\n        KJ_FAIL_ASSERT_AT(location, \"mock capability unexpectedly dropped\");\n      }\n    }\n  }\n\n private:\n  capnp::InterfaceSchema schema;\n  kj::Maybe<kj::Own<kj::PromiseFulfiller<void>>> waiter;\n\n  struct ReceivedCall {\n    ReceivedCall(kj::PromiseFulfiller<void>& fulfiller,\n        MockServer& mock,\n        capnp::InterfaceSchema::Method method,\n        capnp::CallContext<capnp::DynamicStruct, capnp::DynamicStruct> context)\n        : fulfiller(fulfiller),\n          mock(mock),\n          method(method),\n          context(kj::mv(context)) {\n      mock.receivedCalls.add(*this);\n      KJ_IF_SOME(w, mock.waiter) {\n        w.get()->fulfill();\n      }\n    }\n    ~ReceivedCall() noexcept(false) {\n      if (link.isLinked()) {\n        mock.receivedCalls.remove(*this);\n      }\n      KJ_IF_SOME(e, expectedCall) {\n        e.maybeReceived = kj::none;\n      }\n    }\n    KJ_DISALLOW_COPY_AND_MOVE(ReceivedCall);\n\n    kj::PromiseFulfiller<void>& fulfiller;\n    MockServer& mock;\n    capnp::InterfaceSchema::Method method;\n    capnp::CallContext<capnp::DynamicStruct, capnp::DynamicStruct> context;\n    kj::ListLink<ReceivedCall> link;\n\n    kj::Maybe<ExpectedCall&> expectedCall;  // if one is attached\n  };\n\n  kj::List<ReceivedCall, &ReceivedCall::link> receivedCalls;\n  bool dropped = false;\n\n  bool waitForEvent(kj::WaitScope& waitScope) {\n    if (receivedCalls.empty() && !dropped) {\n      auto paf = kj::newPromiseAndFulfiller<void>();\n      waiter = kj::mv(paf.fulfiller);\n      if (!paf.promise.poll(waitScope)) {\n        waiter = kj::none;\n        return false;\n      }\n      paf.promise.wait(waitScope);\n    }\n    return true;\n  }\n\n  class Server final: public capnp::DynamicCapability::Server {\n   public:\n    Server(MockServer& mock)\n        : capnp::DynamicCapability::Server(mock.schema, {.allowCancellation = true}),\n          mock(kj::addRef(mock)) {}\n    ~Server() noexcept(false) {\n      mock->dropped = true;\n      KJ_IF_SOME(w, mock->waiter) {\n        w.get()->fulfill();\n      }\n    }\n\n    kj::Promise<void> call(capnp::InterfaceSchema::Method method,\n        capnp::CallContext<capnp::DynamicStruct, capnp::DynamicStruct> context) override {\n      return kj::newAdaptedPromise<void, ReceivedCall>(*mock, method, kj::mv(context));\n    }\n\n   private:\n    kj::Own<MockServer> mock;\n  };\n};\n\n// Wraps a \"capnp struct literal\". This actually just stringifies the arguments, adding enclosing\n// parentheses. The nice thing about it, though, is that you don't have to escape quotes inside\n// the literal.\n#define CAPNP(...) (\"(\" #__VA_ARGS__ \")\"_kj)\n\ntemplate <typename Schema, typename InitFunc>\nkj::String Capnp(InitFunc func) {\n  capnp::MallocMessageBuilder message;\n  auto builder = message.initRoot<Schema>();\n  func(builder);\n  return TEXT_CODEC.encode(builder.asReader());\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/checked-queue-test.c++",
    "content": "#include \"checked-queue.h\"\n\n#include <kj/test.h>\n\nnamespace workerd::util {\n\nstruct MovableNotCopyable {\n  MovableNotCopyable(int value): value(value) {}\n  MovableNotCopyable(MovableNotCopyable&&) = default;\n  MovableNotCopyable& operator=(MovableNotCopyable&&) = default;\n  KJ_DISALLOW_COPY(MovableNotCopyable);\n\n  int value = 0;\n};\n\nstruct Regular {\n  int value = 0;\n};\n\nKJ_TEST(\"CheckedQueue works - Regular\") {\n  Queue<Regular> queue;\n  KJ_ASSERT(queue.empty());\n  KJ_ASSERT(queue.size() == 0);\n  KJ_ASSERT(queue.pop() == kj::none);\n  KJ_ASSERT(queue.peek() == kj::none);\n  KJ_ASSERT(\n      queue.drainTo([](Regular&&) { KJ_FAIL_ASSERT(\"Should not be called on empty queue\"); }) == 0);\n  queue.clear();\n  queue.push(Regular{1});\n  KJ_ASSERT(!queue.empty());\n  KJ_ASSERT(queue.size() == 1);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(queue.peek()).value == 1);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(queue.peek()).value == 1);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(queue.pop()).value == 1);\n  KJ_ASSERT(queue.empty());\n  KJ_ASSERT(queue.size() == 0);\n  KJ_ASSERT(queue.pop() == kj::none);\n  KJ_ASSERT(queue.peek() == kj::none);\n  queue.push(Regular{2});\n  KJ_ASSERT(queue.drainTo([](Regular&& item) { KJ_ASSERT(item.value == 2); }) == 1);\n  KJ_ASSERT(queue.empty());\n  KJ_ASSERT(queue.size() == 0);\n}\n\nKJ_TEST(\"CheckedQueue works - MovableNotCopyable\") {\n  Queue<MovableNotCopyable> queue;\n  KJ_ASSERT(queue.empty());\n  KJ_ASSERT(queue.size() == 0);\n  KJ_ASSERT(queue.pop() == kj::none);\n  KJ_ASSERT(queue.peek() == kj::none);\n  KJ_ASSERT(queue.drainTo([](MovableNotCopyable&&) {\n    KJ_FAIL_ASSERT(\"Should not be called on empty queue\");\n  }) == 0);\n  queue.clear();\n  queue.push(MovableNotCopyable(1));\n  KJ_ASSERT(!queue.empty());\n  KJ_ASSERT(queue.size() == 1);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(queue.peek()).value == 1);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(queue.peek()).value == 1);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(queue.pop()).value == 1);\n  KJ_ASSERT(queue.empty());\n  KJ_ASSERT(queue.size() == 0);\n  KJ_ASSERT(queue.pop() == kj::none);\n  KJ_ASSERT(queue.peek() == kj::none);\n  queue.push(MovableNotCopyable(2));\n  KJ_ASSERT(queue.drainTo([](MovableNotCopyable&& item) { KJ_ASSERT(item.value == 2); }) == 1);\n  KJ_ASSERT(queue.empty());\n  KJ_ASSERT(queue.size() == 0);\n\n  queue.emplace(1);\n  KJ_ASSERT(!queue.empty());\n  KJ_ASSERT(queue.size() == 1);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(queue.peek()).value == 1);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(queue.peek()).value == 1);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(queue.pop()).value == 1);\n  KJ_ASSERT(queue.empty());\n\n  Queue<MovableNotCopyable> queue2;\n  queue2.push(MovableNotCopyable(3));\n  KJ_ASSERT(!queue2.empty());\n  KJ_ASSERT(queue2.size() == 1);\n  queue.swap(queue2);\n  KJ_ASSERT(queue.size() == 1);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(queue.peek()).value == 3);\n  KJ_ASSERT(queue2.size() == 0);\n  KJ_ASSERT(queue2.peek() == kj::none);\n\n  queue.emplace(2);\n  KJ_ASSERT(queue.size() == 2);\n  KJ_ASSERT(queue.deleteIf([](const auto& item) { return item.value == 3; }) == 1);\n  KJ_ASSERT(queue.size() == 1);\n  KJ_ASSERT(KJ_ASSERT_NONNULL(queue.peek()).value == 2);\n\n  queue.emplace(4);\n  KJ_ASSERT(queue.size() == 2);\n  KJ_ASSERT(queue.forEach([](const auto& item) {\n    KJ_ASSERT(item.value == 2);\n    return false;\n  }) == 1);\n\n  queue.emplace(5);\n  KJ_ASSERT(queue.size() == 3);\n  auto removed = KJ_ASSERT_NONNULL(queue.takeIf([](const auto& item) { return item.value == 5; }));\n  KJ_ASSERT(removed.value == 5);\n  KJ_ASSERT(queue.size() == 2);\n}\n\n}  // namespace workerd::util\n"
  },
  {
    "path": "src/workerd/util/checked-queue.h",
    "content": "#pragma once\n\n#include <kj/common.h>\n#include <kj/debug.h>\n#include <kj/exception.h>\n\n#include <concepts>\n#include <list>\n\nnamespace workerd::util {\n\ntemplate <typename T>\nconcept Movable = std::is_move_constructible_v<T> && std::is_move_assignable_v<T>;\n\n// A simple wrapper around std::list that provides a checked queue interface,\n// ensuring that items can only be moved out of the queue if they exist.\n// Members are not copyable, only movable. The intention here is to provide\n// a safe-to-use queue that avoids the pitfalls of using std::list directly\n// (such as accidentally dangling references when the list is empty but someone\n// calls front(), etc).\ntemplate <Movable T>\nclass Queue final {\n public:\n  Queue() = default;\n  Queue(Queue<T>&&) = default;\n  Queue<T>& operator=(Queue<T>&&) = default;\n  KJ_DISALLOW_COPY(Queue);\n\n  inline void push(T&& value) {\n    inner.push_back(kj::mv(value));\n  }\n\n  template <typename... Args>\n  T& emplace(Args&&... args) KJ_LIFETIMEBOUND {\n    return inner.emplace_back(kj::fwd<Args>(args)...);\n  }\n\n  // Pops the front element from the queue, moving it out.\n  // Returns kj::none if the queue is empty.\n  inline kj::Maybe<T> pop() {\n    if (inner.empty()) {\n      return kj::none;\n    }\n    T value = kj::mv(inner.front());\n    inner.pop_front();\n    // While the kj::mv below is not strictly necessary, I've\n    // included it intentionally to make it absolutely clear\n    // that value is being moved and not copied. It's ok to\n    // refactor that out if it is bothersome.\n    return kj::mv(value);\n  }\n\n  // Returns a reference to the front element without removing it.\n  // Returns kj::none if the queue is empty.\n  inline kj::Maybe<T&> peek() KJ_LIFETIMEBOUND {\n    if (inner.empty()) {\n      return kj::none;\n    }\n    return inner.front();\n  }\n\n  // Returns a reference to the front element without removing it.\n  // Returns kj::none if the queue is empty.\n  inline kj::Maybe<const T&> peek() const KJ_LIFETIMEBOUND {\n    if (inner.empty()) {\n      return kj::none;\n    }\n    return inner.front();\n  }\n\n  // Returns a reference to the last element without removing it.\n  // Returns kj::none if the queue is empty.\n  inline kj::Maybe<T&> peekBack() KJ_LIFETIMEBOUND {\n    if (inner.empty()) {\n      return kj::none;\n    }\n    return inner.back();\n  }\n\n  // Returns a reference to the last element without removing it.\n  // Returns kj::none if the queue is empty.\n  inline kj::Maybe<const T&> peekBack() const KJ_LIFETIMEBOUND {\n    if (inner.empty()) {\n      return kj::none;\n    }\n    return inner.back();\n  }\n\n  // Drains the queue, moving each element to the callback one at a time.\n  // Returns the number of elements moved.\n  inline size_t drainTo(auto callback) {\n    size_t count = 0;\n    while (!inner.empty()) {\n      callback(KJ_ASSERT_NONNULL(pop()));\n      count++;\n    }\n    return count;\n  }\n\n  // Removes elements from the queue that satisfy the given condition.\n  // Returns the number of elements removed.\n  inline size_t deleteIf(auto callback) {\n    size_t count = 0;\n    auto it = inner.begin();\n    while (it != inner.end()) {\n      if (callback(*it)) {\n        it = inner.erase(it);\n        count++;\n      } else {\n        ++it;\n      }\n    }\n    return count;\n  }\n\n  // Takes the first element in the queue that satisfies the given condition, if any.\n  inline kj::Maybe<T> takeIf(auto callback) {\n    for (auto it = inner.begin(); it != inner.end(); ++it) {\n      if (callback(*it)) {\n        T value = kj::mv(*it);\n        inner.erase(it);\n        return kj::mv(value);\n      }\n    }\n    return kj::none;\n  }\n\n  // Applies the callback to each element in the queue.\n  // Returns the number of elements processed.\n  // If the callback returns false, the iteration stops.\n  inline size_t forEach(auto callback) const {\n    size_t count = 0;\n    for (const auto& item: inner) {\n      count++;\n      if constexpr (std::is_void_v<decltype(callback(item))>) {\n        callback(item);\n      } else {\n        if (!callback(item)) break;\n      }\n    }\n    return count;\n  }\n\n  // Checks if the queue is empty.\n  inline bool empty() const {\n    return inner.empty();\n  }\n\n  // Returns the number of elements in the queue.\n  inline size_t size() const {\n    return inner.size();\n  }\n\n  // Clears the queue, removing all elements.\n  inline void clear() {\n    inner.clear();\n  }\n\n  // Swap the contents of this queue with another.\n  inline void swap(Queue<T>& other) {\n    inner.swap(other.inner);\n  }\n\n  // Delete the new and delete operators to prevent heap allocation.\n  void* operator new(size_t) = delete;\n  void operator delete(void*) = delete;\n\n private:\n  std::list<T> inner;\n};\n}  // namespace workerd::util\n"
  },
  {
    "path": "src/workerd/util/color-util.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/string.h>\n\n#include <cstdlib>\n\nnamespace workerd {\n\nenum class ColorMode {\n  // Always output colors to the console\n  ENABLED,\n  // Output colors to the console if it's a TTY\n  ENABLED_IF_TTY,\n  // Never output colors to the console\n  DISABLED\n};\n\n// Returns whether we can output color to the console. Even if this returns `true`,\n// we'll only write color codes if the output file is a TTY.\n// TODO(someday): adopt more of Node.js's checks:\n//  https://github.com/nodejs/node/blob/ac2a68c/lib/internal/tty.js#L106\nstatic ColorMode permitsColor() {\n  const char* forceColorValue = getenv(\"FORCE_COLOR\");\n  if (forceColorValue != nullptr) {\n    auto f = kj::StringPtr(forceColorValue);\n    auto enabled = f == \"\" || f == \"1\" || f == \"2\" || f == \"3\" || f == \"true\";\n    return enabled ? ColorMode::ENABLED : ColorMode::DISABLED;\n  } else {\n    auto enabled = getenv(\"NO_COLOR\") == nullptr && getenv(\"CI\") == nullptr;\n    return enabled ? ColorMode::ENABLED_IF_TTY : ColorMode::DISABLED;\n  }\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/completion-membrane.h",
    "content": "#pragma once\n\n#include <capnp/membrane.h>\n\nnamespace workerd {\n\n// A membrane applied which detects when no capabilities are held any longer, at which point it\n// fulfills a fulfiller.\n//\n// TODO(cleanup): This is generally useful, should it be part of capnp?\nclass CompletionMembrane final: public capnp::MembranePolicy, public kj::Refcounted {\n public:\n  explicit CompletionMembrane(kj::Own<kj::PromiseFulfiller<void>> doneFulfiller)\n      : doneFulfiller(kj::mv(doneFulfiller)) {}\n  ~CompletionMembrane() noexcept(false) {\n    doneFulfiller->fulfill();\n  }\n\n  kj::Maybe<capnp::Capability::Client> inboundCall(\n      uint64_t interfaceId, uint16_t methodId, capnp::Capability::Client target) override {\n    return kj::none;\n  }\n\n  kj::Maybe<capnp::Capability::Client> outboundCall(\n      uint64_t interfaceId, uint16_t methodId, capnp::Capability::Client target) override {\n    return kj::none;\n  }\n\n  kj::Own<MembranePolicy> addRef() override {\n    return kj::addRef(*this);\n  }\n\n  void reject(kj::Exception&& e) {\n    doneFulfiller->reject(kj::mv(e));\n  }\n\n private:\n  kj::Own<kj::PromiseFulfiller<void>> doneFulfiller;\n};\n\n// A membrane which revokes when some Promise is fulfilled.\n//\n// TODO(cleanup): This is generally useful, should it be part of capnp?\nclass RevokerMembrane final: public capnp::MembranePolicy, public kj::Refcounted {\n public:\n  explicit RevokerMembrane(kj::Promise<void> promise): promise(promise.fork()) {}\n\n  kj::Maybe<capnp::Capability::Client> inboundCall(\n      uint64_t interfaceId, uint16_t methodId, capnp::Capability::Client target) override {\n    return kj::none;\n  }\n\n  kj::Maybe<capnp::Capability::Client> outboundCall(\n      uint64_t interfaceId, uint16_t methodId, capnp::Capability::Client target) override {\n    return kj::none;\n  }\n\n  kj::Own<MembranePolicy> addRef() override {\n    return kj::addRef(*this);\n  }\n\n  kj::Maybe<kj::Promise<void>> onRevoked() override {\n    return promise.addBranch();\n  }\n\n private:\n  kj::ForkedPromise<void> promise;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/duration-exceeded-logger-test.c++",
    "content": "// Copyright (c) 2017-2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"duration-exceeded-logger.h\"\n\n#include <kj/test.h>\n#include <kj/timer.h>\n\nnamespace workerd::util {\nnamespace {\n\nKJ_TEST(\"Duration alert triggers when time is exceeded\") {\n  kj::TimerImpl timer(kj::origin<kj::TimePoint>());\n\n  KJ_EXPECT_LOG(WARNING, \"durationAlert Test Message; warningDuration = 10s; actualDuration = \");\n  // we don't check the actual duration emitted to avoid making the test flaky.\n  // this is OK because KJ_EXPECT_LOG just checks for substring occurrences\n  {\n    DurationExceededLogger duration(timer, 10 * kj::SECONDS, \"durationAlert Test Message\");\n    timer.advanceTo(timer.now() + 100 * kj::SECONDS);\n  }\n}\n\n}  // namespace\n}  // namespace workerd::util\n"
  },
  {
    "path": "src/workerd/util/duration-exceeded-logger.h",
    "content": "// Copyright (c) 2017-2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/debug.h>\n#include <kj/string.h>\n#include <kj/time.h>\n\nnamespace workerd::util {\n\n// This is a utility class for instantiating a timer, which will log if it's destructed after a specified time\n// This works by relying on RAII (Scope-Bound Resource Management) to check how much time has elapsed\n// when the object is destructed. This ensures that the time is checked when the timer object goes out of scope\n// thereby timing everything after the initialization of the timer, until the end of the scope where it was\n// instantiated.\nclass DurationExceededLogger {\n public:\n  DurationExceededLogger(\n      const kj::MonotonicClock& clock, kj::Duration warningDuration, kj::StringPtr logMessage)\n      : warningDuration(warningDuration),\n        logMessage(logMessage),\n        start(clock.now()),\n        clock(clock) {}\n\n  KJ_DISALLOW_COPY_AND_MOVE(DurationExceededLogger);\n\n  ~DurationExceededLogger() noexcept(false) {\n    kj::Duration actualDuration = clock.now() - start;\n    if (actualDuration >= warningDuration) {\n      KJ_LOG(WARNING, kj::str(\"NOSENTRY \", logMessage), warningDuration, actualDuration);\n    }\n  }\n\n private:\n  kj::Duration warningDuration;\n  kj::StringPtr logMessage;\n  kj::TimePoint start;\n  const kj::MonotonicClock& clock;\n};\n\n}  // namespace workerd::util\n"
  },
  {
    "path": "src/workerd/util/entropy.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"entropy.h\"\n\n#include <ncrypto.h>\n#include <openssl/crypto.h>\n#include <openssl/rand.h>\n\n#include <kj/debug.h>\n#include <kj/exception.h>\n\n#ifdef __unix__\n#include <unistd.h>\n#endif\n\nnamespace workerd {\n\nvoid getEntropy(kj::ArrayPtr<kj::byte> output) {\n  static constexpr size_t BUFFER_SIZE = 4096;\n  struct BufferState {\n    kj::FixedArray<kj::byte, BUFFER_SIZE> store;\n    kj::ArrayPtr<kj::byte> data;  // Starts empty to trigger initial fill\n#if defined(KJ_DEBUG) && defined(__unix__)\n        // Track the PID separately to detect cross-fork usage.\n    // This should be preserved across fork so we can detect PID changes.\n    pid_t lastSeenPid = 0;\n#endif\n  };\n\n  thread_local BufferState state{};\n\n#if defined(KJ_DEBUG) && defined(__unix__)\n  // Verify that getpid() hasn't changed. This code should be called strictly post-fork.\n  // If we see crashes here in tests, it means there's some pre-fork call to getEntropy()\n  // that needs to be removed.\n  pid_t currentPid = getpid();\n  if (state.lastSeenPid == 0) {\n    state.lastSeenPid = currentPid;\n  } else {\n    KJ_ASSERT(state.lastSeenPid == currentPid,\n        \"PID changed from previous call to getEntropy() - this indicates a pre-fork call \"\n        \"to getEntropy() that should be removed\",\n        state.lastSeenPid, currentPid);\n  }\n#endif\n\n  while (output != nullptr) {\n    if (state.data == nullptr) {\n      ncrypto::ClearErrorOnReturn clearErrorOnReturn;\n      if (RAND_bytes(state.store.begin(), BUFFER_SIZE) != 1) {\n        KJ_FAIL_REQUIRE(\"RAND_bytes failed to generate random data\");\n      }\n\n      state.data = state.store.asPtr();\n    }\n\n    size_t toCopy = kj::min(state.data.size(), output.size());\n    output.first(toCopy).copyFrom(state.data.first(toCopy));\n    // Zero out the source buffer after copying to prevent sensitive data from remaining in memory\n    OPENSSL_cleanse(state.data.first(toCopy).begin(), toCopy);\n    state.data = state.data.slice(toCopy);\n    output = output.slice(toCopy);\n  }\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/entropy.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/array.h>\n\nnamespace workerd {\n\n// Fills `output` with cryptographically-random bytes.\nvoid getEntropy(kj::ArrayPtr<kj::byte> output);\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/exception.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/exception.h>\n\nnamespace workerd {\n\n// If an exception is thrown for exceeding memory limits, it will contain this detail.\nconstexpr kj::Exception::DetailTypeId MEMORY_LIMIT_DETAIL_ID = 0xbaf76dd7ce5bd8cfull;\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/header-validation.h",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/common.h>\n#include <kj/parse/char.h>\n#include <kj/string.h>\n\nnamespace workerd::util {\n\ninline bool isValidHeaderValue(kj::ArrayPtr<const char> value) {\n  if (value == nullptr) return true;\n  for (auto c: value) {\n    if (c == '\\0' || c == '\\r' || c == '\\n') {\n      return false;\n    }\n  }\n  return true;\n}\n\nconstexpr auto HTTP_SEPARATOR_CHARS = kj::parse::anyOfChars(\"()<>@,;:\\\\\\\"/[]?={} \\t\");\n// RFC2616 section 2.2: https://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2\n\nconstexpr auto HTTP_TOKEN_CHARS = kj::parse::controlChar.orChar('\\x7f')\n                                      .orGroup(kj::parse::whitespaceChar)\n                                      .orGroup(HTTP_SEPARATOR_CHARS)\n                                      .invert();\n// RFC2616 section 2.2: https://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2\n// RFC2616 section 4.2: https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2\n\ninline constexpr bool isHttpWhitespace(char c) {\n  return c == '\\t' || c == '\\r' || c == '\\n' || c == ' ';\n}\nstatic_assert(isHttpWhitespace(' '));\nstatic_assert(!isHttpWhitespace('A'));\ninline constexpr bool isHttpTokenChar(char c) {\n  return HTTP_TOKEN_CHARS.contains(c);\n}\nstatic_assert(isHttpTokenChar('A'));\nstatic_assert(!isHttpTokenChar(' '));\n}  // namespace workerd::util\n"
  },
  {
    "path": "src/workerd/util/http-util.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/compat/http.h>\n#include <kj/debug.h>\n\nnamespace workerd {\n\n// Attaches the given object to a `Request` so that it lives as long as the request's properties.\n// The given object must support `kj::addRef()` (e.g. `kj::Refcount`).\ntemplate <typename T>\nkj::HttpClient::Request attachToRequest(kj::HttpClient::Request req, T&& rcAttachment) {\n  req.body = req.body.attach(kj::addRef(*rcAttachment));\n  req.response = req.response.then(\n      [rcAttachment = kj::mv(rcAttachment)](kj::HttpClient::Response&& response) mutable {\n    response.body = response.body.attach(kj::mv(rcAttachment));\n    return kj::mv(response);\n  });\n  return req;\n}\n\n// Attaches the given object to a `WebSocketResponse` promise so that it lives as long as the\n// returned response's properties.\ntemplate <typename T>\nkj::Promise<kj::HttpClient::WebSocketResponse> attachToWebSocketResponse(\n    kj::Promise<kj::HttpClient::WebSocketResponse> promise, T&& attachment) {\n  return promise.then(\n      [attachment = kj::mv(attachment)](kj::HttpClient::WebSocketResponse&& response) mutable {\n    KJ_SWITCH_ONEOF(response.webSocketOrBody) {\n      KJ_CASE_ONEOF(stream, kj::Own<kj::AsyncInputStream>) {\n        response.webSocketOrBody = stream.attach(kj::mv(attachment));\n      }\n      KJ_CASE_ONEOF(ws, kj::Own<kj::WebSocket>) {\n        response.webSocketOrBody = ws.attach(kj::mv(attachment));\n      }\n    }\n    return kj::mv(response);\n  });\n}\n\n// A Response kj::HttpService::Response implementation that records the status\n// code on the response.\nclass SimpleResponseObserver final: public kj::HttpService::Response {\n public:\n  SimpleResponseObserver(kj::uint* statusCode, kj::HttpService::Response& response)\n      : inner(response),\n        statusCode(statusCode) {}\n  KJ_DISALLOW_COPY_AND_MOVE(SimpleResponseObserver);\n\n  kj::Own<kj::AsyncOutputStream> send(kj::uint status,\n      kj::StringPtr statusText,\n      const kj::HttpHeaders& headers,\n      kj::Maybe<uint64_t> expectedBodySize) override {\n    *statusCode = status;\n    return inner.send(status, statusText, headers, expectedBodySize);\n  }\n\n  kj::Own<kj::WebSocket> acceptWebSocket(const kj::HttpHeaders& headers) override {\n    return inner.acceptWebSocket(headers);\n  }\n\n private:\n  kj::HttpService::Response& inner;\n  kj::uint* statusCode;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/immediate-crash.h",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n//\n// When testing the REPRL interface it's important to see whether Fuzzilli can\n// observe a crash.\n// The IMMEDIATE_CRASH macro is being used to test crashes in fuzzilli function\n// which can be called from within JavaScript as fuzzilli(\"FUZZILLI_CRASH\",0).\n//\n\n#pragma once\n\n#if defined(__GNUC__) || defined(__clang__)\n\n#if defined(__x86_64__) || defined(__i386__)\n\n#define TRAP_SEQUENCE1_() asm volatile(\"int3\")\n#define TRAP_SEQUENCE2_() asm volatile(\"ud2\")\n\n#elifdef __arm__\n\n#define TRAP_SEQUENCE1_() asm volatile(\"bkpt #0\")\n#define TRAP_SEQUENCE2_() asm volatile(\"udf #0\")\n\n#elifdef __aarch64__\n\n#define TRAP_SEQUENCE1_() asm volatile(\"brk #0\")\n#define TRAP_SEQUENCE2_() asm volatile(\"hlt #0\")\n\n#elifdef __powerpc64__\n\n#define TRAP_SEQUENCE1_() asm volatile(\".4byte 0x7D821008\")\n#define TRAP_SEQUENCE2_() asm volatile(\"\")\n\n#elifdef __s390x__\n\n#define TRAP_SEQUENCE1_() asm volatile(\".2byte 0x0001\")\n#define TRAP_SEQUENCE2_() asm volatile(\"\")\n\n#else\n\n#define TRAP_SEQUENCE1_() __builtin_trap()\n#define TRAP_SEQUENCE2_() asm volatile(\"\")\n\n#endif  // Architecture check\n\n#elifdef _MSC_VER\n\n#if defined(_M_X64) || defined(_M_IX86)\n\n#define TRAP_SEQUENCE1_() __debugbreak()\n#define TRAP_SEQUENCE2_()\n\n#elifdef _M_ARM64\n\n#define TRAP_SEQUENCE1_() __debugbreak()\n#define TRAP_SEQUENCE2_()\n\n#else\n\n#error No supported trap sequence for this MSVC architecture!\n\n#endif  // _MSC_VER architecture check\n\n#else\n\n#error No supported compiler!\n\n#endif  // Compiler check\n\n#define TRAP_SEQUENCE_()                                                                           \\\n  do {                                                                                             \\\n    TRAP_SEQUENCE1_();                                                                             \\\n    TRAP_SEQUENCE2_();                                                                             \\\n  } while (false)\n\n// Wrapping the trap sequence to allow its use inside constexpr functions.\n#if defined(__GNUC__) || defined(__clang__)\n\n#define WRAPPED_TRAP_SEQUENCE_()                                                                   \\\n  do {                                                                                             \\\n    [] { TRAP_SEQUENCE_(); }();                                                                    \\\n  } while (false)\n\n#else\n\n#define WRAPPED_TRAP_SEQUENCE_() TRAP_SEQUENCE_()\n\n#endif  // Compiler check for wrapping\n\n#if defined(__clang__) || defined(__GNUC__)\n\n// __builtin_unreachable() hints to the compiler that this code path is not reachable.\n#define IMMEDIATE_CRASH()                                                                          \\\n  ({                                                                                               \\\n    WRAPPED_TRAP_SEQUENCE_();                                                                      \\\n    __builtin_unreachable();                                                                       \\\n  })\n\n#else\n\n#define IMMEDIATE_CRASH() WRAPPED_TRAP_SEQUENCE_()\n\n#endif  // __clang__ or __GNUC__\n"
  },
  {
    "path": "src/workerd/util/mimetype-test.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#include \"mimetype.h\"\n\n#include <kj/test.h>\n\nnamespace workerd {\nnamespace {\n\nKJ_TEST(\"Basic MimeType parsing works\") {\n  struct TestCase {\n    kj::StringPtr input;\n    kj::StringPtr type;\n    kj::StringPtr subtype;\n    kj::StringPtr output;\n    kj::Maybe<kj::Array<MimeType::MimeParams::Entry>> params;\n  };\n  static const TestCase kTests[] = {\n    {\n      .input = \"text/plain\"_kj,\n      .type = \"text\"_kj,\n      .subtype = \"plain\"_kj,\n      .output = \"text/plain\"_kj,\n    },\n    {\n      .input = \"\\r\\t\\n TeXt/PlAiN \\t\\r\\n\"_kj,\n      .type = \"text\"_kj,\n      .subtype = \"plain\"_kj,\n      .output = \"text/plain\"_kj,\n    },\n    {.input = \"text/plain; charset=utf-8\"_kj,\n      .type = \"text\"_kj,\n      .subtype = \"plain\"_kj,\n      .output = \"text/plain;charset=utf-8\"_kj,\n      .params = kj::arr(MimeType::MimeParams::Entry{kj::str(\"charset\"), kj::str(\"utf-8\")})},\n    {.input = \"text/plain; charset=\\\"utf-8\\\"\"_kj,\n      .type = \"text\"_kj,\n      .subtype = \"plain\"_kj,\n      .output = \"text/plain;charset=utf-8\"_kj,\n      .params = kj::arr(MimeType::MimeParams::Entry{kj::str(\"charset\"), kj::str(\"utf-8\")})},\n    {.input = \"text/plain; charset=\\\"utf-8\\\"; \\r\\n\\t\"_kj,\n      .type = \"text\"_kj,\n      .subtype = \"plain\"_kj,\n      .output = \"text/plain;charset=utf-8\"_kj,\n      .params = kj::arr(MimeType::MimeParams::Entry{kj::str(\"charset\"), kj::str(\"utf-8\")})},\n    {.input = \"text/plain; charset=\\\"utf-8\\\"; \\r\\n\\ta=b\"_kj,\n      .type = \"text\"_kj,\n      .subtype = \"plain\"_kj,\n      .output = \"text/plain;charset=utf-8;a=b\"_kj,\n      .params = kj::arr(MimeType::MimeParams::Entry{kj::str(\"charset\"), kj::str(\"utf-8\")},\n          MimeType::MimeParams::Entry{kj::str(\"a\"), kj::str(\"b\")})},\n    {.input = \"text/plain; charset=utf-8; a=b;a=a\"_kj,\n      .type = \"text\"_kj,\n      .subtype = \"plain\"_kj,\n      .output = \"text/plain;charset=utf-8;a=b\"_kj,\n      .params = kj::arr(MimeType::MimeParams::Entry{kj::str(\"charset\"), kj::str(\"utf-8\")},\n          MimeType::MimeParams::Entry{kj::str(\"a\"), kj::str(\"b\")})},\n  };\n\n  for (auto& test: kTests) {\n    auto mimeType = KJ_ASSERT_NONNULL(MimeType::tryParse(test.input));\n    KJ_ASSERT(mimeType.type() == test.type);\n    KJ_ASSERT(mimeType.subtype() == test.subtype);\n    KJ_ASSERT(mimeType.toString() == test.output);\n\n    KJ_IF_SOME(params, test.params) {\n      for (auto& param: params) {\n        auto& value = KJ_ASSERT_NONNULL(mimeType.params().find(param.key));\n        KJ_ASSERT(value == param.value);\n      }\n    }\n  }\n\n  struct ErrorTestCase {\n    kj::StringPtr input;\n  };\n  static const ErrorTestCase kErrorTests[] = {\n    {\"\"},\n    {\"text\"},\n    {\"text/\"},\n    {\"/plain\"},\n    {\"/\"},\n    {\" a/\\x12\"},\n    {\" \\x12/a\"},\n    {\" text/ plain\"},\n    {\" text /plain\"},\n    {\" text / plain\"},\n    {\";charset=utf-8\"},\n    {\"javascript\"},\n  };\n\n  for (auto& test: kErrorTests) {\n    KJ_ASSERT(MimeType::tryParse(test.input) == kj::none, test.input);\n  }\n}\n\nKJ_TEST(\"Building MimeType works\") {\n  MimeType type(\"text\", \"plain\");\n\n  KJ_ASSERT(!type.addParam(\"\"_kj, \"\"_kj));\n  KJ_ASSERT(!type.addParam(\"\\x12\"_kj, \"\"_kj));\n  KJ_ASSERT(!type.addParam(\"B\"_kj, \"\\12\"_kj));\n\n  KJ_ASSERT(type.addParam(\"A\"_kj, \"b\"_kj));\n  KJ_ASSERT(type.addParam(\"Z\"_kj, \"b\"_kj));\n  type.eraseParam(\"Z\");\n\n  KJ_ASSERT(type.toString() == \"text/plain;a=b\");\n\n  KJ_ASSERT(type.params().find(\"a\"_kj) != kj::none);\n  KJ_ASSERT(type.params().find(\"b\"_kj) == kj::none);\n  KJ_ASSERT(type.params().find(\"z\"_kj) == kj::none);\n\n  // Comparing based solely on type/subtype works\n  KJ_ASSERT(MimeType::PLAINTEXT == type);\n}\n\nKJ_TEST(\"WHATWG tests\") {\n  struct Test {\n    kj::StringPtr input;\n    kj::Maybe<kj::StringPtr> output;\n  };\n\n  static const Test kTests[] = {{\n                                  .input = \"text/html;charset=gbk\"_kj,\n                                  .output = \"text/html;charset=gbk\"_kj,\n                                },\n    {\n      .input = \"TEXT/HTML;CHARSET=GBK\"_kj,\n      .output = \"text/html;charset=GBK\"_kj,\n    },\n    // Legacy comment syntax\n    {\n      .input = \"text/html;charset=gbk(\"_kj,\n      .output = \"text/html;charset=\\\"gbk(\\\"\"_kj,\n    },\n    {\n      .input = \"text/html;x=(;charset=gbk\"_kj,\n      .output = \"text/html;x=\\\"(\\\";charset=gbk\"_kj,\n    },\n    // \"Duplicate parameter\",\n    {\n      .input = \"text/html;charset=gbk;charset=windows-1255\"_kj,\n      .output = \"text/html;charset=gbk\"_kj,\n    },\n    {\n      .input = \"text/html;charset=();charset=GBK\"_kj,\n      .output = \"text/html;charset=\\\"()\\\"\"_kj,\n    },\n    // \"Spaces\",\n    {\n      .input = \"text/html;charset =gbk\"_kj,\n      .output = \"text/html\"_kj,\n    },\n    {\n      .input = \"text/html ;charset=gbk\"_kj,\n      .output = \"text/html;charset=gbk\"_kj,\n    },\n    {\n      .input = \"text/html; charset=gbk\"_kj,\n      .output = \"text/html;charset=gbk\"_kj,\n    },\n    {\n      .input = \"text/html;charset= gbk\"_kj,\n      .output = \"text/html;charset=\\\" gbk\\\"\"_kj,\n    },\n    {\n      .input = \"text/html;charset= \\\"gbk\\\"\"_kj,\n      .output = \"text/html;charset=\\\" \\\\\\\"gbk\\\\\\\"\\\"\"_kj,\n    },\n    // \"0x0B and 0x0C\",\n    {\n      .input = \"text/html;charset=\\u000Bgbk\"_kj,\n      .output = \"text/html\"_kj,\n    },\n    {\n      .input = \"text/html;charset=\\u000Cgbk\"_kj,\n      .output = \"text/html\"_kj,\n    },\n    {\n      .input = \"text/html;\\u000Bcharset=gbk\"_kj,\n      .output = \"text/html\"_kj,\n    },\n    {\n      .input = \"text/html;\\u000Ccharset=gbk\"_kj,\n      .output = \"text/html\"_kj,\n    },\n    // \"Single quotes are a token, not a delimiter\",\n    {\n      .input = \"text/html;charset='gbk'\"_kj,\n      .output = \"text/html;charset='gbk'\"_kj,\n    },\n    {\n      .input = \"text/html;charset='gbk\"_kj,\n      .output = \"text/html;charset='gbk\"_kj,\n    },\n    {\n      .input = \"text/html;charset=gbk'\"_kj,\n      .output = \"text/html;charset=gbk'\"_kj,\n    },\n    {\n      .input = \"text/html;charset=';charset=GBK\"_kj,\n      .output = \"text/html;charset='\"_kj,\n    },\n    // \"Invalid parameters\",\n    {\n      .input = \"text/html;test;charset=gbk\"_kj,\n      .output = \"text/html;charset=gbk\"_kj,\n    },\n    {\n      .input = \"text/html;test=;charset=gbk\"_kj,\n      .output = \"text/html;charset=gbk\"_kj,\n    },\n    {\n      .input = \"text/html;';charset=gbk\"_kj,\n      .output = \"text/html;charset=gbk\"_kj,\n    },\n    {\n      .input = \"text/html;\\\";charset=gbk\"_kj,\n      .output = \"text/html;charset=gbk\"_kj,\n    },\n    {\n      .input = \"text/html ; ; charset=gbk\"_kj,\n      .output = \"text/html;charset=gbk\"_kj,\n    },\n    {\n      .input = \"text/html;;;;charset=gbk\"_kj,\n      .output = \"text/html;charset=gbk\"_kj,\n    },\n    {\n      .input = \"text/html;charset= \\\"\\u007F;charset=GBK\"_kj,\n      .output = \"text/html;charset=GBK\"_kj,\n    },\n    {\n      .input = \"text/html;charset=\\\"\\u007F;charset=foo\\\";charset=GBK\"_kj,\n      .output = \"text/html;charset=GBK\"_kj,\n    },\n    // \"Double quotes\",\n    {\n      .input = \"text/html;charset=\\\"gbk\\\"\"_kj,\n      .output = \"text/html;charset=gbk\"_kj,\n    },\n    {\n      .input = \"text/html;charset=\\\"gbk\"_kj,\n      .output = \"text/html;charset=gbk\"_kj,\n    },\n    {\n      .input = \"text/html;charset=gbk\\\"\"_kj,\n      .output = \"text/html;charset=\\\"gbk\\\\\\\"\\\"\"_kj,\n    },\n    {\n      .input = \"text/html;charset=\\\" gbk\\\"\"_kj,\n      .output = \"text/html;charset=\\\" gbk\\\"\"_kj,\n    },\n    {\n      .input = \"text/html;charset=\\\"gbk \\\"\"_kj,\n      .output = \"text/html;charset=\\\"gbk \\\"\"_kj,\n    },\n    {\n      .input = \"text/html;charset=\\\"\\\\ gbk\\\"\"_kj,\n      .output = \"text/html;charset=\\\" gbk\\\"\"_kj,\n    },\n    {\n      .input = \"text/html;charset=\\\"\\\\g\\\\b\\\\k\\\"\"_kj,\n      .output = \"text/html;charset=gbk\"_kj,\n    },\n    {\n      .input = \"text/html;charset=\\\"gbk\\\"x\"_kj,\n      .output = \"text/html;charset=gbk\"_kj,\n    },\n    {\n      .input = \"text/html;charset=\\\"\\\";charset=GBK\"_kj,\n      .output = \"text/html;charset=\\\"\\\"\"_kj,\n    },\n    {\n      .input = \"text/html;charset=\\\";charset=GBK\"_kj,\n      .output = \"text/html;charset=\\\";charset=GBK\\\"\"_kj,\n    },\n    // \"Unexpected code points\",\n    {\n      .input = \"text/html;charset={gbk}\"_kj,\n      .output = \"text/html;charset=\\\"{gbk}\\\"\"_kj,\n    },\n    // \"Parameter name longer than 127\",\n    {\n      .input =\n          \"text/html;0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789=x;charset=gbk\"_kj,\n      .output =\n          \"text/html;0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789=x;charset=gbk\"_kj,\n    },\n    // \"type/subtype longer than 127\",\n    {\n      .input =\n          \"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789/0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"_kj,\n      .output =\n          \"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789/0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"_kj,\n    },\n    // \"Valid\",\n    {\n      .input =\n          \"!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz;!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\"_kj,\n      .output =\n          \"!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz/!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz;!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz=!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\"_kj,\n    },\n    // TODO(soon): Extreme edge case, we currently don't pass it but not too concerned.\n    // {\n    //   .input = \"x/x;x=\\\"\\t !\\\\\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\\u0080\\u0081\\u0082\\u0083\\u0084\\u0085\\u0086\\u0087\\u0088\\u0089\\u008A\\u008B\\u008C\\u008D\\u008E\\u008F\\u0090\\u0091\\u0092\\u0093\\u0094\\u0095\\u0096\\u0097\\u0098\\u0099\\u009A\\u009B\\u009C\\u009D\\u009E\\u009F\\u00A0\\u00A1\\u00A2\\u00A3\\u00A4\\u00A5\\u00A6\\u00A7\\u00A8\\u00A9\\u00AA\\u00AB\\u00AC\\u00AD\\u00AE\\u00AF\\u00B0\\u00B1\\u00B2\\u00B3\\u00B4\\u00B5\\u00B6\\u00B7\\u00B8\\u00B9\\u00BA\\u00BB\\u00BC\\u00BD\\u00BE\\u00BF\\u00C0\\u00C1\\u00C2\\u00C3\\u00C4\\u00C5\\u00C6\\u00C7\\u00C8\\u00C9\\u00CA\\u00CB\\u00CC\\u00CD\\u00CE\\u00CF\\u00D0\\u00D1\\u00D2\\u00D3\\u00D4\\u00D5\\u00D6\\u00D7\\u00D8\\u00D9\\u00DA\\u00DB\\u00DC\\u00DD\\u00DE\\u00DF\\u00E0\\u00E1\\u00E2\\u00E3\\u00E4\\u00E5\\u00E6\\u00E7\\u00E8\\u00E9\\u00EA\\u00EB\\u00EC\\u00ED\\u00EE\\u00EF\\u00F0\\u00F1\\u00F2\\u00F3\\u00F4\\u00F5\\u00F6\\u00F7\\u00F8\\u00F9\\u00FA\\u00FB\\u00FC\\u00FD\\u00FE\\u00FF\\\"\"_kj,\n    //   .output = \"x/x;x=\\\"\\t !\\\\\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\\u0080\\u0081\\u0082\\u0083\\u0084\\u0085\\u0086\\u0087\\u0088\\u0089\\u008A\\u008B\\u008C\\u008D\\u008E\\u008F\\u0090\\u0091\\u0092\\u0093\\u0094\\u0095\\u0096\\u0097\\u0098\\u0099\\u009A\\u009B\\u009C\\u009D\\u009E\\u009F\\u00A0\\u00A1\\u00A2\\u00A3\\u00A4\\u00A5\\u00A6\\u00A7\\u00A8\\u00A9\\u00AA\\u00AB\\u00AC\\u00AD\\u00AE\\u00AF\\u00B0\\u00B1\\u00B2\\u00B3\\u00B4\\u00B5\\u00B6\\u00B7\\u00B8\\u00B9\\u00BA\\u00BB\\u00BC\\u00BD\\u00BE\\u00BF\\u00C0\\u00C1\\u00C2\\u00C3\\u00C4\\u00C5\\u00C6\\u00C7\\u00C8\\u00C9\\u00CA\\u00CB\\u00CC\\u00CD\\u00CE\\u00CF\\u00D0\\u00D1\\u00D2\\u00D3\\u00D4\\u00D5\\u00D6\\u00D7\\u00D8\\u00D9\\u00DA\\u00DB\\u00DC\\u00DD\\u00DE\\u00DF\\u00E0\\u00E1\\u00E2\\u00E3\\u00E4\\u00E5\\u00E6\\u00E7\\u00E8\\u00E9\\u00EA\\u00EB\\u00EC\\u00ED\\u00EE\\u00EF\\u00F0\\u00F1\\u00F2\\u00F3\\u00F4\\u00F5\\u00F6\\u00F7\\u00F8\\u00F9\\u00FA\\u00FB\\u00FC\\u00FD\\u00FE\\u00FF\\\"\"_kj,\n    // },\n    // \"End-of-file handling\",\n    {\n      .input = \"x/x;test\"_kj,\n      .output = \"x/x\"_kj,\n    },\n    // TODO(soon): Another edge case, we currently don't pass it but not too concerned.\n    // {\n    //  .input = \"x/x;test=\\\"\\\\\"_kj,\n    //  .output = \"x/x;test=\\\"\\\\\\\\\\\"\"_kj,\n    // },\n    // \"Whitespace (not handled by generated-mime-types.json or above)\",\n    {\n      .input = \"x/x;x= \"_kj,\n      .output = \"x/x\"_kj,\n    },\n    {\n      .input = \"x/x;x=\\t\"_kj,\n      .output = \"x/x\"_kj,\n    },\n    {\n      .input = \"x/x\\n\\r\\t ;x=x\"_kj,\n      .output = \"x/x;x=x\"_kj,\n    },\n    {\n      .input = \"\\n\\r\\t x/x;x=x\\n\\r\\t \"_kj,\n      .output = \"x/x;x=x\"_kj,\n    },\n    {\n      .input = \"x/x;\\n\\r\\t x=x\\n\\r\\t ;x=y\"_kj,\n      .output = \"x/x;x=x\"_kj,\n    },\n    // \"Latin1\",\n    {\n      .input = \"text/html;test=\\u00FF;charset=gbk\"_kj,\n      .output = \"text/html;test=\\\"\\u00FF\\\";charset=gbk\"_kj,\n    },\n    // \">Latin1\",\n    // TODO(soon): Another edge case, we currently don't pass it but not too concerned.\n    // {\n    //   .input = \"x/x;test=\\uFFFD;x=x\"_kj,\n    //   .output = \"x/x;x=x\"_kj,\n    // },\n    // \"Failure\",\n    {\n      .input = \"\\u000Bx/x\"_kj,\n    },\n    {.input = \"\\u000Cx/x\"_kj}, {.input = \"x/x\\u000B\"_kj}, {.input = \"x/x\\u000C\"_kj},\n    {.input = \"\"_kj}, {.input = \"\\t\"_kj}, {.input = \"/\"_kj}, {.input = \"bogus\"_kj},\n    {.input = \"bogus/\"_kj}, {.input = \"bogus/ \"_kj}, {.input = \"bogus/bogus/;\"_kj},\n    {.input = \"</>\"_kj}, {.input = \"(/)\"_kj}, {.input = \"ÿ/ÿ\"_kj},\n    {.input = \"text/html(;doesnot=matter\"_kj}, {.input = \"{/}\"_kj}, {.input = \"\\u0100/\\u0100\"_kj},\n    {.input = \"text /html\"_kj}, {.input = \"text/ html\"_kj}, {.input = \"\\\"text/html\\\"\"_kj}};\n\n  for (const auto& test: kTests) {\n    KJ_IF_SOME(output, test.output) {\n      auto result = KJ_ASSERT_NONNULL(MimeType::tryParse(test.input));\n      KJ_ASSERT(result.toString() == output);\n    } else {\n      KJ_ASSERT(MimeType::tryParse(test.input) == kj::none);\n    }\n  }\n\n  KJ_ASSERT(MimeType::JSON ==\n      KJ_ASSERT_NONNULL(MimeType::tryParse(\"application/json;charset=nothing\"_kj)));\n  KJ_ASSERT(MimeType::JSON == KJ_ASSERT_NONNULL(MimeType::tryParse(\"application/json;\"_kj)));\n  KJ_ASSERT(MimeType::JSON ==\n      KJ_ASSERT_NONNULL(MimeType::tryParse(\"application/json;char=\\\"UTF-8\\\"\"_kj)));\n  KJ_ASSERT(MimeType::isJson(MimeType::JSON));\n  KJ_ASSERT(MimeType::isJson(MimeType::MANIFEST_JSON));\n  KJ_ASSERT(MimeType::isJavascript(MimeType::JAVASCRIPT));\n  KJ_ASSERT(MimeType::isJavascript(MimeType::XJAVASCRIPT));\n  KJ_ASSERT(MimeType::isJavascript(MimeType::TEXT_JAVASCRIPT));\n\n  KJ_ASSERT(MimeType::isText(MimeType::PLAINTEXT));\n  KJ_ASSERT(MimeType::isText(MimeType::JSON));\n  KJ_ASSERT(MimeType::isText(MimeType::JAVASCRIPT));\n  KJ_ASSERT(MimeType::isText(MimeType::XJAVASCRIPT));\n  KJ_ASSERT(MimeType::isText(\n      KJ_ASSERT_NONNULL(MimeType::tryParse(\"application/json; charset=\\\"utf-8\\\"\"_kj))));\n\n  auto svgMime = KJ_ASSERT_NONNULL(MimeType::tryParse(\"image/svg+xml\"_kj));\n  KJ_ASSERT(MimeType::isXml(svgMime));\n  KJ_ASSERT(MimeType::isText(svgMime));\n\n  auto svgMimeWithCharset =\n      KJ_ASSERT_NONNULL(MimeType::tryParse(\"image/svg+xml; charset=utf-8\"_kj));\n  KJ_ASSERT(MimeType::isXml(svgMimeWithCharset));\n  KJ_ASSERT(MimeType::isText(svgMimeWithCharset));\n\n  KJ_ASSERT(MimeType::isXml(MimeType::XHTML));\n\n  auto atomXml = KJ_ASSERT_NONNULL(MimeType::tryParse(\"application/atom+xml\"_kj));\n  KJ_ASSERT(MimeType::isXml(atomXml));\n\n  auto textXml = KJ_ASSERT_NONNULL(MimeType::tryParse(\"text/xml\"_kj));\n  KJ_ASSERT(MimeType::isXml(textXml));\n  auto appXml = KJ_ASSERT_NONNULL(MimeType::tryParse(\"application/xml\"_kj));\n  KJ_ASSERT(MimeType::isXml(appXml));\n  auto imageXml = KJ_ASSERT_NONNULL(MimeType::tryParse(\"image/xml\"_kj));\n  KJ_ASSERT(!MimeType::isXml(imageXml));\n}\n\nKJ_TEST(\"Extract Mime Type\") {\n  // These are taken from the fetch spec\n  // https://fetch.spec.whatwg.org/#concept-header-extract-mime-type\n  {\n    auto mimeType = KJ_ASSERT_NONNULL(MimeType::extract(\"text/plain;charset=gbk, text/html\"_kj));\n    KJ_ASSERT(mimeType == MimeType::HTML);\n  }\n\n  {\n    auto mimeType =\n        KJ_ASSERT_NONNULL(MimeType::extract(\"text/html;charset=gbk;a=b, text/html;x=y\"));\n    KJ_ASSERT(mimeType.toString() == \"text/html;x=y;charset=gbk\");\n  }\n\n  {\n    auto mimeType =\n        KJ_ASSERT_NONNULL(MimeType::extract(\"text/html;charset=gbk, x/x, text/html;x=y\"));\n    KJ_ASSERT(mimeType.toString() == \"text/html;x=y\");\n  }\n\n  {\n    auto mimeType = KJ_ASSERT_NONNULL(MimeType::extract(\"text/html, cannot parse\"));\n    KJ_ASSERT(mimeType.toString() == \"text/html\");\n  }\n\n  {\n    auto mimeType = KJ_ASSERT_NONNULL(MimeType::extract(\"text/html, */*\"));\n    KJ_ASSERT(mimeType.toString() == \"text/html\");\n  }\n\n  {\n    auto mimeType = KJ_ASSERT_NONNULL(MimeType::extract(\"text/html, \"));\n    KJ_ASSERT(mimeType.toString() == \"text/html\");\n  }\n\n  {\n    // An odd edge case where the parameter value contains an escaped quote and escaped\n    // comma in the value.\n    auto mimeType = KJ_ASSERT_NONNULL(MimeType::extract(\"text/html;a=\\\\\\\"not-quoted\\\\,, foo/bar\"));\n    KJ_ASSERT(mimeType.toString() == \"foo/bar\");\n  }\n\n  // These are taken from the web platform tests\n  struct Test {\n    kj::StringPtr input;\n    kj::StringPtr encoding;\n    kj::StringPtr result;\n  };\n\n  Test tests[] = {\n    Test{\n      .input = \", text/plain\"_kj,\n      .encoding = nullptr,\n      .result = \"text/plain\"_kj,\n    },\n    Test{\n      .input = \"text/plain, \"_kj,\n      .encoding = nullptr,\n      .result = \"text/plain\"_kj,\n    },\n    {\n      .input = \"text/html, text/plain\"_kj,\n      .encoding = nullptr,\n      .result = \"text/plain\"_kj,\n    },\n    {\n      .input = \"text/plain;charset=gbk, text/html\"_kj,\n      .encoding = nullptr,\n      .result = \"text/html\"_kj,\n    },\n    {\n      .input = \"text/plain;charset=gbk, text/html;charset=windows-1254\"_kj,\n      .encoding = \"windows-1254\"_kj,\n      .result = \"text/html;charset=windows-1254\"_kj,\n    },\n    {\n      .input = \"text/plain;charset=gbk, text/plain\"_kj,\n      .encoding = \"gbk\"_kj,\n      .result = \"text/plain;charset=gbk\"_kj,\n    },\n    {\n      .input = \"text/plain;charset=gbk, text/plain;charset=windows-1252\"_kj,\n      .encoding = \"windows-1252\"_kj,\n      .result = \"text/plain;charset=windows-1252\"_kj,\n    },\n    {\n      .input = \"text/plain;charset=gbk;x=foo, text/plain\"_kj,\n      .encoding = \"gbk\"_kj,\n      .result = \"text/plain;charset=gbk\"_kj,\n    },\n    {\n      .input = \"text/html;charset=gbk, text/plain, text/html\"_kj,\n      .encoding = nullptr,\n      .result = \"text/html\"_kj,\n    },\n    {\n      .input = \"text/plain, */*\"_kj,\n      .encoding = nullptr,\n      .result = \"text/plain\"_kj,\n    },\n    {\n      .input = \"text/html, */*\"_kj,\n      .encoding = nullptr,\n      .result = \"text/html\"_kj,\n    },\n    {\n      .input = \"*/*, text/html\"_kj,\n      .encoding = nullptr,\n      .result = \"text/html\"_kj,\n    },\n    {\n      .input = \"text/plain, */*;charset=gbk\"_kj,\n      .encoding = nullptr,\n      .result = \"text/plain\"_kj,\n    },\n    {\n      .input = \"text/html, */*;charset=gbk\"_kj,\n      .encoding = nullptr,\n      .result = \"text/html\"_kj,\n    },\n    {\n      .input = \"text/html;\\\", \\\", text/plain\"_kj,\n      .encoding = nullptr,\n      .result = \"text/plain\"_kj,\n    },\n    {\n      .input = \"text/html;charset=gbk, text/html;x=\\\",text/plain\"_kj,\n      .encoding = \"gbk\"_kj,\n      .result = \"text/html;x=\\\",text/plain\\\";charset=gbk\"_kj,\n    },\n    {\n      .input = \"text/html;x=\\\", text/plain\"_kj,\n      .encoding = nullptr,\n      .result = \"text/html;x=\\\", text/plain\\\"\"_kj,\n    },\n    {\n      .input = \"text/html;\\\", text/plain\"_kj,\n      .encoding = nullptr,\n      .result = \"text/html\"_kj,\n    },\n    {\n      .input = \"text/html;\\\", \\\\\\\", text/plain\"_kj,\n      .encoding = nullptr,\n      .result = \"text/html\"_kj,\n    },\n    {\n      // This is actually three separate Content-Type header fields concatenated together\n      // into a list. The original values are:\n      //  Content-Type: text/html;\\\"\n      //  Content-Type: \\\\\\\"\n      //  Content-Type: text/plain, \\\";charset=GBK\n      //\n      // When combined using the typical rules for combining multiple headers, the result\n      // actually ends up being just a single mime type with an invalid parameter.\n      .input = \"text/html;\\\", \\\\\\\", text/plain, \\\";charset=GBK\"_kj,\n      .encoding = \"GBK\"_kj,\n      .result = \"text/html;charset=GBK\"_kj,\n    },\n  };\n  auto ptr = kj::ArrayPtr<Test>(tests, sizeof(tests) / sizeof(Test));\n\n  for (auto& test: ptr) {\n    auto parsed = KJ_ASSERT_NONNULL(MimeType::extract(test.input));\n    KJ_ASSERT(parsed.toString() == test.result);\n    if (test.encoding != nullptr) {\n      KJ_ASSERT(KJ_ASSERT_NONNULL(parsed.params().find(\"charset\"_kj)) == test.encoding);\n    }\n  }\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/mimetype.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#include \"mimetype.h\"\n\n#include \"strings.h\"\n\n#include <workerd/util/string-buffer.h>\n\n#include <kj/debug.h>\n#include <kj/string-tree.h>\n\nnamespace workerd {\n\nnamespace {\n\nconstexpr bool isWhitespace(const char c) noexcept {\n  return (c == '\\r' || c == '\\n' || c == '\\t' || c == ' ');\n}\n\nstatic constexpr kj::FixedArray<uint8_t, 256> token_table = []() consteval {\n  kj::FixedArray<uint8_t, 256> result{};\n\n  for (uint8_t c:\n      {'!', '#', '$', '%', '&', '\\'', '*', '+', '\\\\', '-', '.', '^', '_', '`', '|', '~'}) {\n    result[c] = true;\n  }\n\n  // (c >= 'A' && c <= 'Z')\n  for (uint8_t c = 'A'; c <= 'Z'; c++) {\n    result[c] = true;\n  }\n\n  // (c >= 'a' && c <= 'z')\n  for (uint8_t c = 'a'; c <= 'z'; c++) {\n    result[c] = true;\n  }\n\n  // (c >= '0' && c <= '9')\n  for (uint8_t c = '0'; c <= '9'; c++) {\n    result[c] = true;\n  }\n\n  return result;\n}();\n\nconstexpr bool isTokenChar(const uint8_t c) noexcept {\n  return token_table[c];\n}\n\nstatic constexpr kj::FixedArray<uint8_t, 256> quoted_string_token_table = []() consteval {\n  kj::FixedArray<uint8_t, 256> result{};\n  result['\\t'] = true;\n\n  for (uint8_t c = 0x20; c <= 0x7e; c++) {\n    result[c] = true;\n  }\n\n  for (uint8_t c = 0x80; c < 255; c++) {\n    result[c] = true;\n  }\n\n  return result;\n}();\n\nconstexpr bool isQuotedStringTokenChar(const uint8_t c) noexcept {\n  return quoted_string_token_table[c];\n}\n\nkj::ArrayPtr<const char> skipWhitespace(kj::ArrayPtr<const char> str) {\n  auto ptr = str.begin();\n  auto end = str.end();\n  while (ptr != end && isWhitespace(*ptr)) {\n    ptr++;\n  }\n  return str.slice(ptr - str.begin());\n}\n\nkj::ArrayPtr<const char> trimWhitespace(kj::ArrayPtr<const char> str) {\n  auto ptr = str.end();\n  while (ptr > str.begin() && isWhitespace(*(ptr - 1))) --ptr;\n  return str.first(str.size() - (str.end() - ptr));\n}\n\nconstexpr bool hasInvalidCodepoints(kj::ArrayPtr<const char> str, auto predicate) {\n  bool has_invalid_codepoints = false;\n  for (const char ptr: str) {\n    has_invalid_codepoints |= !predicate(static_cast<uint8_t>(ptr));\n  }\n  return has_invalid_codepoints;\n}\n\nkj::Maybe<size_t> findParamDelimiter(kj::ArrayPtr<const char> str) {\n  auto ptr = str.begin();\n  while (ptr != str.end()) {\n    if (*ptr == ';' || *ptr == '=') return ptr - str.begin();\n    ++ptr;\n  }\n  return kj::none;\n}\n\nkj::String unescape(kj::ArrayPtr<const char> str) {\n  auto result = kj::strTree();\n  while (str.size() > 0) {\n    KJ_IF_SOME(pos, str.findFirst('\\\\')) {\n      result = kj::strTree(kj::mv(result), str.first(pos));\n      str = str.slice(pos + 1, str.size());\n    } else {\n      // No more backslashes\n      result = kj::strTree(kj::mv(result), str);\n      break;\n    }\n  }\n  return result.flatten();\n}\n\n}  // namespace\n\nMimeType MimeType::parse(kj::StringPtr input, ParseOptions options) {\n  return KJ_ASSERT_NONNULL(tryParse(input, options));\n}\n\nkj::Maybe<MimeType> MimeType::tryParse(kj::ArrayPtr<const char> input, ParseOptions options) {\n  return tryParseImpl(input, kj::mv(options));\n}\n\nkj::Maybe<MimeType> MimeType::tryParseImpl(kj::ArrayPtr<const char> input, ParseOptions options) {\n  // Skip leading whitespace from start\n  input = skipWhitespace(input);\n  if (input.size() == 0) return kj::none;\n\n  kj::Maybe<kj::String> maybeType;\n  // Let's try to find the solidus that separates the type and subtype\n  KJ_IF_SOME(n, input.findFirst('/')) {\n    auto typeCandidate = input.first(n);\n    if (typeCandidate.size() == 0 || hasInvalidCodepoints(typeCandidate, isTokenChar)) {\n      return kj::none;\n    }\n    maybeType = toLower(typeCandidate);\n    input = input.slice(n + 1);\n  } else {\n    // If the solidus is not found, then it's not a valid mime type\n    return kj::none;\n  }\n\n  // If there's nothing else to parse at this point, it's not a valid mime type.\n  if (input.size() == 0) return kj::none;\n\n  kj::Maybe<kj::String> maybeSubtype;\n  KJ_IF_SOME(n, input.findFirst(';')) {\n    // If a semi-colon is found, the subtype is everything up to that point\n    // minus trailing whitespace.\n    auto subtypeCandidate = trimWhitespace(input.first(n));\n    if (subtypeCandidate.size() == 0 || hasInvalidCodepoints(subtypeCandidate, isTokenChar)) {\n      return kj::none;\n    }\n    maybeSubtype = toLower(subtypeCandidate);\n    input = input.slice(n + 1);\n  } else {\n    auto subtypeCandidate = trimWhitespace(input);\n    if (subtypeCandidate.size() == 0 || hasInvalidCodepoints(subtypeCandidate, isTokenChar)) {\n      return kj::none;\n    }\n    maybeSubtype = toLower(subtypeCandidate);\n    input = input.slice(input.size());\n  }\n\n  MimeType result(kj::mv(KJ_ASSERT_NONNULL(maybeType)), mv(KJ_ASSERT_NONNULL(maybeSubtype)));\n\n  if (!(options & ParseOptions::IGNORE_PARAMS)) {\n    // Parse the parameters...\n    while (input.size() > 0) {\n      input = skipWhitespace(input);\n      if (input.size() == 0) break;\n      KJ_IF_SOME(n, findParamDelimiter(input)) {\n        // If the delimiter found is a ; then the parameter is invalid here, and\n        // we will ignore it.\n        if (input[n] == ';') {\n          input = input.slice(n + 1);\n          continue;\n        }\n        KJ_ASSERT(input[n] == '=');\n        auto nameCandidate = input.first(n);\n        input = input.slice(n + 1);\n        if (nameCandidate.size() == 0 || hasInvalidCodepoints(nameCandidate, isTokenChar)) {\n          // The name is invalid, try skipping to the next...\n          KJ_IF_SOME(p, input.findFirst(';')) {\n            input = input.slice(p + 1);\n            continue;\n          } else {\n            break;\n          }\n        }\n\n        // Check to see if the value starts off quoted or not.\n        if (*input.begin() == '\"') {\n          input = input.slice(1);\n          // Our parameter value is quoted. Next we'll scan up until the next\n          // quote or until the end of the string.\n          KJ_IF_SOME(p, input.findFirst('\"')) {\n            auto valueCandidate = input.first(p);\n            input = input.slice(p + 1);\n            if (hasInvalidCodepoints(valueCandidate, isQuotedStringTokenChar)) {\n              continue;\n            }\n            result.addParam(nameCandidate, unescape(valueCandidate));\n            KJ_IF_SOME(y, input.findFirst(';')) {\n              input = input.slice(y + 1);\n              continue;\n            }\n          } else if (!hasInvalidCodepoints(input, isQuotedStringTokenChar)) {\n            result.addParam(nameCandidate, unescape(input));\n          }\n          break;\n        } else {\n          // The parameter is not quoted. Let's scan ahead for the next semi-colon.\n          KJ_IF_SOME(p, input.findFirst(';')) {\n            auto valueCandidate = trimWhitespace(input.first(p));\n            input = input.slice(p + 1);\n            if (valueCandidate.size() > 0 &&\n                !hasInvalidCodepoints(valueCandidate, isQuotedStringTokenChar)) {\n              result.addParam(nameCandidate, valueCandidate);\n            }\n            continue;\n          } else {\n            auto valueCandidate = trimWhitespace(input);\n            if (valueCandidate.size() > 0 &&\n                !hasInvalidCodepoints(valueCandidate, isQuotedStringTokenChar)) {\n              result.addParam(nameCandidate, valueCandidate);\n            }\n          }\n          break;\n        }\n        KJ_ASSERT(false);\n      } else {\n        // If we got here, we scanned input and did not find a semi-colon or equal\n        // sign before hitting the end of the input. We treat the remaining bits as\n        // invalid and ignore them.\n        break;\n      }\n    }\n  }\n\n  return kj::mv(result);\n}\n\nMimeType::MimeType(kj::StringPtr type, kj::StringPtr subtype, kj::Maybe<MimeParams> params)\n    : MimeType(toLower(type), toLower(subtype), kj::mv(params)) {}\n\nMimeType::MimeType(kj::String type, kj::String subtype, kj::Maybe<MimeParams> params)\n    : type_(kj::mv(type)),\n      subtype_(kj::mv(subtype)) {\n  KJ_IF_SOME(p, params) {\n    params_ = kj::mv(p);\n  }\n}\n\nkj::StringPtr MimeType::type() const {\n  return type_;\n}\n\nbool MimeType::setType(kj::StringPtr type) {\n  if (type.size() == 0 || hasInvalidCodepoints(type, isTokenChar)) return false;\n  type_ = toLower(type);\n  return true;\n}\n\nkj::StringPtr MimeType::subtype() const {\n  return subtype_;\n}\n\nbool MimeType::setSubtype(kj::StringPtr type) {\n  if (type.size() == 0 || hasInvalidCodepoints(type, isTokenChar)) return false;\n  subtype_ = toLower(type);\n  return true;\n}\n\nconst MimeType::MimeParams& MimeType::params() const {\n  return params_;\n}\n\nbool MimeType::addParam(kj::ArrayPtr<const char> name, kj::ArrayPtr<const char> value) {\n  if (name.size() == 0 || hasInvalidCodepoints(name, isTokenChar) ||\n      hasInvalidCodepoints(value, isQuotedStringTokenChar)) {\n    return false;\n  }\n  params_.upsert(toLower(name), kj::str(value), [](auto&, auto&&) {});\n  return true;\n}\n\nvoid MimeType::eraseParam(kj::StringPtr name) {\n  params_.erase(toLower(name));\n}\n\nkj::String MimeType::essence() const {\n  return kj::str(type(), \"/\", subtype());\n}\n\nkj::String MimeType::paramsToString() const {\n  ToStringBuffer buffer(512);\n  paramsToString(buffer);\n  return buffer.toString();\n}\n\nvoid MimeType::paramsToString(MimeType::ToStringBuffer& buffer) const {\n  bool first = true;\n  for (auto& param: params()) {\n    buffer.append(first ? \"\" : \";\");\n    if (param.key == \"boundary\") {\n      // This is to pass a pedantic WPT test that expects a space before only the boundary parameter\n      // [1]: https://html.spec.whatwg.org/#submit-body\n      // [2]: https://github.com/web-platform-tests/wpt/pull/29554\n      buffer.append(\" \");\n    }\n\n    buffer.append(param.key, \"=\");\n    first = false;\n    if (param.value.size() == 0) {\n      buffer.append(\"\\\"\\\"\");\n    } else if (hasInvalidCodepoints(param.value, isTokenChar)) {\n      auto view = param.value.asPtr();\n      buffer.append(\"\\\"\");\n      while (view.size() > 0) {\n        KJ_IF_SOME(pos, view.findFirst('\"')) {\n          buffer.append(view.first(pos), \"\\\\\\\"\");\n          view = view.slice(pos + 1);\n        } else {\n          buffer.append(view);\n          view = view.slice(view.size());\n        }\n      }\n      buffer.append(\"\\\"\");\n    } else {\n      buffer.append(param.value);\n    }\n  }\n}\n\nkj::String MimeType::toString() const {\n  ToStringBuffer buffer(512);\n  buffer.append(type(), \"/\", subtype());\n  if (params_.size() > 0) {\n    buffer.append(\";\");\n    paramsToString(buffer);\n  }\n  return buffer.toString();\n}\n\nMimeType MimeType::clone(ParseOptions options) const {\n  MimeParams copy;\n  if (!(options & ParseOptions::IGNORE_PARAMS)) {\n    for (const auto& entry: params_) {\n      copy.insert(kj::str(entry.key), kj::str(entry.value));\n    }\n  }\n  return MimeType(kj::str(type_), kj::str(subtype_), kj::mv(copy));\n}\n\nbool MimeType::operator==(const MimeType& other) const {\n  return this == &other || (type_ == other.type_ && subtype_ == other.subtype_);\n}\n\nMimeType::operator kj::String() const {\n  return toString();\n}\n\nkj::String KJ_STRINGIFY(const MimeType& mimeType) {\n  return mimeType.toString();\n}\n\nkj::String KJ_STRINGIFY(const ConstMimeType& state) {\n  return state.toString();\n}\n\nconst MimeType MimeType::PLAINTEXT = MimeType::parse(PLAINTEXT_STRING);\nconst MimeType MimeType::PLAINTEXT_ASCII = MimeType::parse(PLAINTEXT_ASCII_STRING);\n\nkj::Maybe<MimeType> MimeType::extract(kj::StringPtr input) {\n  kj::Maybe<MimeType> mimeType;\n\n  constexpr static auto findNextSeparator = [](auto& input) -> kj::Maybe<size_t> {\n    // Scans input to find the next comma (,) that is not contained within\n    // a quoted section, returning the position of the comma or kj::none\n    // if not found.\n    for (size_t i = 0; i < input.size(); ++i) {\n      if (input[i] == '\"' && (i == 0 || input[i - 1] != '\\\\')) {\n        // Skip to the end of the quoted section\n        while (++i < input.size() && (input[i] != '\"' || input[i - 1] == '\\\\')) {}\n      } else if (input[i] == ',' && (i == 0 || input[i - 1] != '\\\\')) {\n        return i;\n      }\n    }\n    return kj::none;\n  };\n\n  constexpr static auto processPart = [](auto& mimeType, auto& part) -> kj::Maybe<MimeType> {\n    KJ_IF_SOME(parsed, tryParseImpl(part)) {\n      if (parsed == MimeType::WILDCARD) return kj::none;\n\n      KJ_IF_SOME(current, mimeType) {\n        if (current == parsed) {\n          // mimeType will be set to parsed, but if parsed does not\n          // have a charset, we will set the charset from current, if any.\n          if (parsed.params().find(\"charset\"_kj) == kj::none) {\n            KJ_IF_SOME(charset, current.params().find(\"charset\"_kj)) {\n              parsed.addParam(\"charset\"_kj, charset);\n            }\n          }\n        }\n      }\n      return kj::mv(parsed);\n    }\n\n    return kj::none;\n  };\n\n  while (input.size() > 0) {\n    KJ_IF_SOME(pos, findNextSeparator(input)) {\n      auto part = input.first(pos);\n      input = input.slice(pos + 1);\n      KJ_IF_SOME(parsed, processPart(mimeType, part)) {\n        mimeType = kj::mv(parsed);\n      } else {\n        continue;\n      }\n    } else {\n      KJ_IF_SOME(parsed, processPart(mimeType, input)) {\n        mimeType = kj::mv(parsed);\n      }\n      break;\n    }\n  }\n\n  return kj::mv(mimeType);\n}\n\nkj::String MimeType::formDataWithBoundary(kj::StringPtr boundary) {\n  ToStringBuffer buffer(128);\n  // Note that the expectation is that the boundary is already properly formed\n  // and does not need any additional quoting or escaping.\n  buffer.append(\"multipart/form-data; boundary=\", boundary);\n  return buffer.toString();\n}\n\nkj::String MimeType::formUrlEncodedWithCharset(kj::StringPtr charset) {\n  ToStringBuffer buffer(128);\n  buffer.append(\"application/x-www-form-urlencoded;charset=\", charset);\n  return buffer.toString();\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/mimetype.h",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <workerd/jsg/memory.h>\n#include <workerd/util/strings.h>\n\n#include <kj/common.h>\n#include <kj/map.h>\n#include <kj/string.h>\n\nnamespace workerd {\n\ntemplate <size_t>\nclass StringBuffer;\nclass MimeType;\nclass ConstMimeType final {\n public:\n  constexpr ConstMimeType(kj::StringPtr type, kj::StringPtr subtype)\n      : type_(type),\n        subtype_(subtype) {}\n\n  constexpr kj::StringPtr type() const {\n    return type_;\n  }\n  constexpr kj::StringPtr subtype() const {\n    return subtype_;\n  }\n\n  constexpr bool operator==(const ConstMimeType& other) const {\n    return this == &other || (type_ == other.type_ && subtype_ == other.subtype_);\n  }\n\n  bool operator==(const MimeType& other) const;\n  operator MimeType() const;\n  MimeType clone() const;\n\n  inline kj::String toString() const {\n    return kj::str(type_, \"/\", subtype_);\n  }\n\n  inline kj::String essence() const {\n    return toString();\n  }\n\n private:\n  kj::StringPtr type_;\n  kj::StringPtr subtype_;\n};\n\ntemplate <typename T>\nconcept IsMimeType = kj::isSameType<T, MimeType>() || kj::isSameType<T, ConstMimeType>();\nstatic_assert(IsMimeType<MimeType>);\nstatic_assert(IsMimeType<ConstMimeType>);\n\nclass MimeType final {\n public:\n  using MimeParams = kj::HashMap<kj::String, kj::String>;\n\n  enum ParseOptions {\n    DEFAULT,\n    IGNORE_PARAMS,\n  };\n\n  // Returning nullptr implies that the input is not a valid mime type construction.\n  // If the ParseOptions::IGNORE_PARAMS option is set then the mime type parameters\n  // will be ignored and will not be included in the parsed result.\n  static kj::Maybe<MimeType> tryParse(\n      kj::ArrayPtr<const char> input, ParseOptions options = ParseOptions::DEFAULT);\n\n  // Asserts if the input could not be parsed as a valid MimeType. tryParse should\n  // be preferred for most cases.\n  static MimeType parse(kj::StringPtr input, ParseOptions options = ParseOptions::DEFAULT);\n\n  explicit MimeType(\n      kj::StringPtr type, kj::StringPtr subtype, kj::Maybe<MimeParams> params = kj::none);\n  explicit MimeType(kj::String type, kj::String subtype, kj::Maybe<MimeParams> params = kj::none);\n\n  MimeType(MimeType&&) = default;\n  MimeType& operator=(MimeType&&) = default;\n  KJ_DISALLOW_COPY(MimeType);\n\n  kj::StringPtr type() const;\n  kj::StringPtr subtype() const;\n\n  const MimeParams& params() const;\n\n  bool setType(kj::StringPtr type);\n  bool setSubtype(kj::StringPtr type);\n  bool addParam(kj::ArrayPtr<const char> name, kj::ArrayPtr<const char> value);\n  void eraseParam(kj::StringPtr name);\n\n  // Returns only the type/subtype\n  kj::String essence() const;\n\n  // Returns the type/subtype and all params.\n  kj::String toString() const;\n\n  kj::String paramsToString() const;\n\n  // Copy this MimeType. If the IGNORE_PARAMS option is set the clone\n  // will copy only the type and subtype and will omit all of the parameters.\n  MimeType clone(ParseOptions options = ParseOptions::DEFAULT) const;\n\n  // Compares only the essence of the MimeType (type and subtype). Ignores\n  // parameters in the comparison.\n  bool operator==(const MimeType& other) const;\n\n  operator kj::String() const;\n\n  template <IsMimeType T>\n  static constexpr bool isXml(const T& mimeType) {\n    auto type = mimeType.type();\n    auto subtype = mimeType.subtype();\n    // Bare \"xml\" subtype is only valid for text/xml and application/xml.\n    // The \"+xml\" structured syntax suffix (RFC 6838 §4.2.8, RFC 6839) indicates\n    // XML-based content regardless of top-level type (e.g. image/svg+xml).\n    return ((type == \"text\" || type == \"application\") && subtype == \"xml\") ||\n        subtype.endsWith(\"+xml\");\n  }\n\n  template <IsMimeType T>\n  static constexpr bool isJson(const T& mimeType) {\n    auto type = mimeType.type();\n    auto subtype = mimeType.subtype();\n    return (type == \"text\" || type == \"application\") &&\n        (subtype == \"json\" || subtype.endsWith(\"+json\"));\n  }\n\n  template <IsMimeType T>\n  static constexpr bool isFont(const T& mimeType) {\n    auto type = mimeType.type();\n    auto subtype = mimeType.subtype();\n    return (type == \"font\" || type == \"application\") &&\n        (subtype.startsWith(\"font-\") || subtype.startsWith(\"x-font-\"));\n  }\n\n  template <IsMimeType T>\n  static constexpr bool isJavascript(const T& mimeType) {\n    return JAVASCRIPT == mimeType || XJAVASCRIPT == mimeType || TEXT_JAVASCRIPT == mimeType;\n  }\n\n  template <IsMimeType T>\n  static constexpr bool isText(const T& mimeType) {\n    auto type = mimeType.type();\n    auto subtype = mimeType.subtype();\n    return type == \"text\" || isXml(mimeType) || isJson(mimeType) || isJavascript(mimeType) ||\n        (type == \"application\" && subtype == \"dns-json\");\n  }\n\n  template <IsMimeType T>\n  static constexpr bool isImage(const T& mimeType) {\n    return mimeType.type() == \"image\";\n  }\n\n  template <IsMimeType T>\n  static constexpr bool isVideo(const T& mimeType) {\n    return mimeType.type() == \"video\";\n  }\n\n  template <IsMimeType T>\n  static constexpr bool isAudio(const T& mimeType) {\n    return mimeType.type() == \"audio\";\n  }\n\n  static const MimeType PLAINTEXT;\n  static const MimeType PLAINTEXT_ASCII;\n  static constexpr ConstMimeType JSON = ConstMimeType(\"application\"_kj, \"json\"_kj);\n  static constexpr ConstMimeType FORM_URLENCODED =\n      ConstMimeType(\"application\"_kj, \"x-www-form-urlencoded\"_kj);\n  static constexpr ConstMimeType FORM_DATA = ConstMimeType(\"multipart\"_kj, \"form-data\"_kj);\n  static constexpr ConstMimeType OCTET_STREAM = ConstMimeType(\"application\"_kj, \"octet-stream\"_kj);\n  static constexpr ConstMimeType XHTML = ConstMimeType(\"application\"_kj, \"xhtml+xml\"_kj);\n  static constexpr ConstMimeType JAVASCRIPT = ConstMimeType(\"application\"_kj, \"javascript\"_kj);\n  static constexpr ConstMimeType XJAVASCRIPT = ConstMimeType(\"application\"_kj, \"x-javascript\"_kj);\n  static constexpr ConstMimeType TEXT_JAVASCRIPT = ConstMimeType(\"text\"_kj, \"javascript\"_kj);\n  static constexpr ConstMimeType HTML = ConstMimeType(\"text\"_kj, \"html\"_kj);\n  static constexpr ConstMimeType CSS = ConstMimeType(\"text\"_kj, \"css\"_kj);\n  static constexpr ConstMimeType MANIFEST_JSON =\n      ConstMimeType(\"application\"_kj, \"manifest+json\"_kj);\n  static constexpr ConstMimeType VTT = ConstMimeType(\"text\"_kj, \"vtt\"_kj);\n  static constexpr ConstMimeType EVENT_STREAM = ConstMimeType(\"text\"_kj, \"event-stream\"_kj);\n  static constexpr ConstMimeType WILDCARD = ConstMimeType(\"*\"_kj, \"*\"_kj);\n\n  // exposed directly for performance reasons\n  static constexpr kj::StringPtr PLAINTEXT_STRING = \"text/plain;charset=UTF-8\"_kj;\n  static constexpr kj::StringPtr PLAINTEXT_ASCII_STRING = \"text/plain;charset=US-ASCII\"_kj;\n\n  static kj::String formDataWithBoundary(kj::StringPtr boundary);\n  static kj::String formUrlEncodedWithCharset(kj::StringPtr charset);\n\n  // Extracts a mime type from a concatenated list of content-type values\n  // per the algorithm defined in the fetch spec:\n  // https://fetch.spec.whatwg.org/#concept-header-extract-mime-type\n  static kj::Maybe<MimeType> extract(kj::StringPtr input);\n\n  void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {\n    tracker.trackFieldWithSize(\"type\", type_.size());\n    tracker.trackFieldWithSize(\"subtype\", subtype_.size());\n    tracker.trackFieldWithSize(\"params\", params_.size());\n  }\n\n private:\n  kj::String type_;\n  kj::String subtype_;\n  MimeParams params_;\n\n  using ToStringBuffer = StringBuffer<128>;\n  // 128 bytes will keep all reasonable mimetypes on the stack.\n\n  void paramsToString(ToStringBuffer& buffer) const;\n\n  static kj::Maybe<MimeType> tryParseImpl(\n      kj::ArrayPtr<const char> input, ParseOptions options = ParseOptions::DEFAULT);\n};\n\ninline ConstMimeType::operator MimeType() const {\n  return MimeType(type_, subtype_);\n}\n\ninline MimeType ConstMimeType::clone() const {\n  return *this;\n}\n\ninline bool ConstMimeType::operator==(const MimeType& other) const {\n  return type_ == other.type() && subtype_ == other.subtype();\n}\n\nkj::String KJ_STRINGIFY(const MimeType& state);\nkj::String KJ_STRINGIFY(const ConstMimeType& state);\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/own-util.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/array.h>\n#include <kj/refcount.h>\n#include <kj/string.h>\n\nnamespace workerd {\n\ntemplate <typename T>\ninline auto mapAddRef(kj::Maybe<kj::Own<T>>& maybe) -> kj::Maybe<kj::Own<T>> {\n  return maybe.map([](kj::Own<T>& t) { return kj::addRef(*t); });\n}\n\ntemplate <typename T>\ninline auto mapAddRef(kj::Maybe<kj::Rc<T>>& maybe) -> kj::Maybe<kj::Rc<T>> {\n  return maybe.map([](kj::Rc<T>& t) { return t.addRef(); });\n}\n\ntemplate <typename T>\ninline auto mapAddRef(const kj::Maybe<kj::Arc<T>>& maybe) -> kj::Maybe<kj::Arc<T>> {\n  return maybe.map([](const kj::Arc<T>& t) { return t.addRef(); });\n}\n\ntemplate <typename T>\ninline auto mapAddRef(kj::Maybe<T&> maybe) -> kj::Maybe<kj::Own<T>> {\n  return maybe.map([](T& t) { return kj::addRef(t); });\n}\n\ntemplate <typename T>\ninline auto mapAddRef(kj::ArrayPtr<kj::Own<T>>& array) -> kj::Array<kj::Own<T>> {\n  return KJ_MAP(t, array) { return kj::addRef(*t); };\n}\n\ntemplate <typename T>\ninline auto mapAddRef(kj::Array<kj::Own<T>>& array) -> kj::Array<kj::Own<T>> {\n  return KJ_MAP(t, array) { return kj::addRef(*t); };\n}\n\ninline auto mapCopyString(const kj::Maybe<kj::String>& string) -> kj::Maybe<kj::String> {\n  return string.map([](const kj::String& s) { return kj::str(s); });\n}\n\ninline auto mapCopyString(const kj::Maybe<kj::StringPtr>& string) -> kj::Maybe<kj::String> {\n  return string.map([](const kj::StringPtr& s) { return kj::str(s); });\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/perfetto-tracing.c++",
    "content": "#include \"perfetto-tracing.h\"\n\n#ifdef WORKERD_USE_PERFETTO\n\n#include \"protos/perfetto/config/data_source_config.gen.h\"\n#include \"protos/perfetto/config/trace_config.gen.h\"\n#include \"protos/perfetto/config/track_event/track_event_config.gen.h\"\n\n#include <fcntl.h>\n#include <perfetto/tracing/track_event_legacy.h>\n\n#include <kj/debug.h>\n#include <kj/filesystem.h>\n#include <kj/memory.h>\n#include <kj/vector.h>\n\nPERFETTO_TRACK_EVENT_STATIC_STORAGE_IN_NAMESPACE(workerd::traces);\n\nnamespace workerd {\nnamespace {\nkj::OwnFd openTraceFile(kj::StringPtr path) {\n  int fd = open(path.cStr(), O_RDWR | O_CREAT | O_TRUNC, 0600);\n  KJ_REQUIRE(fd >= 0, \"Unable to open tracing file\");\n  return kj::OwnFd(fd);\n}\n\nvoid initializePerfettoOnce() {\n  if (perfetto::Tracing::IsInitialized()) return;\n  perfetto::TracingInitArgs args;\n  args.backends |= perfetto::kInProcessBackend;\n  perfetto::Tracing::Initialize(args);\n  PerfettoSession::registerWorkerdTracks();\n}\n\nstd::unique_ptr<perfetto::TracingSession> createTracingSession(int fd, kj::StringPtr categories) {\n  initializePerfettoOnce();\n  perfetto::protos::gen::TrackEventConfig track_event_cfg;\n  track_event_cfg.add_disabled_categories(\"*\");\n\n  // The categories is a comma-separated list\n  auto cats = PerfettoSession::parseCategories(categories);\n  for (auto category: cats) {\n    auto view = std::string(category.begin(), category.size());\n    track_event_cfg.add_enabled_categories(view);\n  }\n\n  perfetto::TraceConfig cfg;\n  cfg.add_buffers()->set_size_kb(1024);  // Record up to 1 MiB.\n  auto* ds_cfg = cfg.add_data_sources()->mutable_config();\n  ds_cfg->set_name(\"track_event\");\n  ds_cfg->set_track_event_config_raw(track_event_cfg.SerializeAsString());\n  std::unique_ptr<perfetto::TracingSession> tracing_session(perfetto::Tracing::NewTrace());\n  tracing_session->Setup(cfg, fd);\n  return kj::mv(tracing_session);\n}\n}  // namespace\n\nvoid PerfettoSession::registerWorkerdTracks() {\n  static bool once = true;\n  KJ_ASSERT(once, \"workerd perfetto tracks are already registered\");\n  if (perfetto::Tracing::IsInitialized()) {\n    workerd::traces::TrackEvent::Register();\n    once = false;\n  }\n}\n\nkj::Array<kj::ArrayPtr<const char>> PerfettoSession::parseCategories(kj::StringPtr categories) {\n  kj::Vector<kj::ArrayPtr<const char>> results;\n  while (categories.size() > 0) {\n    KJ_IF_SOME(pos, categories.findFirst(',')) {\n      results.add(categories.first(pos));\n      categories = categories.slice(pos + 1);\n    } else {\n      results.add(categories);\n      categories = nullptr;\n    }\n  }\n  return results.releaseAsArray();\n}\n\nstruct PerfettoSession::Impl {\n  kj::OwnFd fd;\n  std::unique_ptr<perfetto::TracingSession> session;\n\n  Impl(kj::OwnFd dest, kj::StringPtr categories)\n      : fd(kj::mv(dest)),\n        session(createTracingSession(fd.get(), categories)) {\n    session->StartBlocking();\n  }\n};\n\nPerfettoSession::PerfettoSession(kj::StringPtr path, kj::StringPtr categories)\n    : impl(kj::heap<Impl>(openTraceFile(path), categories)) {}\n\nPerfettoSession::PerfettoSession(int fd, kj::StringPtr categories)\n    : impl(kj::heap<Impl>(kj::OwnFd(fd), categories)) {}\n\nPerfettoSession::~PerfettoSession() noexcept(false) {\n  if (impl) {\n    impl->session->FlushBlocking();\n    impl->session->StopBlocking();\n  }\n}\n\nvoid PerfettoSession::flush() {\n  if (impl) {\n    impl->session->FlushBlocking();\n  }\n}\n\n}  // namespace workerd\n\n#endif  // defined(WORKERD_USE_PERFETTO)\n"
  },
  {
    "path": "src/workerd/util/perfetto-tracing.h",
    "content": "#pragma once\n\n#ifdef WORKERD_USE_PERFETTO\n#include <kj/memory.h>\n#define PERFETTO_ENABLE_LEGACY_TRACE_EVENTS 1\n// Only include a smaller header here to keep include size under control. This approach is\n// recommended in the full perfetto header (perfetto/tracing.h).\n#include \"perfetto/tracing/track_event.h\"\n\nPERFETTO_DEFINE_CATEGORIES_IN_NAMESPACE(workerd::traces, perfetto::Category(\"workerd\"));\n\nnamespace kj {\nclass StringPtr;\nclass String;\n}  // namespace kj\n\nnamespace workerd {\n\n// The PerfettoSession initializes a Perfetto tracing session, initializing the\n// Perfetto subsystem on first initialization and writing events to the given\n// file path.\nclass PerfettoSession {\n public:\n  explicit PerfettoSession(kj::StringPtr path, kj::StringPtr categories);\n\n  // Create a PerfettoSession on an existing fd (the constructor will handle\n  // wrapping the fd in kj::AutocloseFd)\n  explicit PerfettoSession(int fd, kj::StringPtr categories);\n  PerfettoSession(PerfettoSession&&) = default;\n  PerfettoSession& operator=(PerfettoSession&&) = default;\n  KJ_DISALLOW_COPY(PerfettoSession);\n  ~PerfettoSession() noexcept(false);\n\n  void flush();\n\n  // Receives a comma-separated list of trace categories and returns an array.\n  static kj::Array<kj::ArrayPtr<const char>> parseCategories(kj::StringPtr categories);\n\n  // Called by embedders at most once to register the workerd track events.\n  static void registerWorkerdTracks();\n\n private:\n  struct Impl;\n  kj::Own<Impl> impl;\n\n  friend constexpr bool _kj_internal_isPolymorphic(PerfettoSession::Impl*);\n};\n\n#define PERFETTO_FLOW_FROM_POINTER(ptr) perfetto::Flow::FromPointer(ptr)\n#define PERFETTO_TERMINATING_FLOW_FROM_POINTER(ptr) perfetto::TerminatingFlow::FromPointer(ptr)\n#define PERFETTO_TRACK_FROM_POINTER(ptr) perfetto::Track::FromPointer(ptr)\n\nKJ_DECLARE_NON_POLYMORPHIC(PerfettoSession::Impl);\n}  // namespace workerd\n\n#else  // defined(WORKERD_USE_PERFETTO)\nstruct PerfettoNoop {};\n// We define non-op versions of the instrumentation macros here so that we can\n// still instrument and build when perfetto is not enabled.\n#define TRACE_EVENT(...)\n#define TRACE_EVENT_BEGIN(...)\n#define TRACE_EVENT_END(...)\n#define TRACE_EVENT_INSTANT(...)\n#define TRACE_COUNTER(...)\n#define TRACE_EVENT_CATEGORY_ENABLED(...) false\n#define PERFETTO_FLOW_FROM_POINTER(ptr)                                                            \\\n  PerfettoNoop {}\n#define PERFETTO_TERMINATING_FLOW_FROM_POINTER(ptr)                                                \\\n  PerfettoNoop {}\n#define PERFETTO_TRACK_FROM_POINTER(ptr)                                                           \\\n  PerfettoNoop {}\n#endif  // defined(WORKERD_USE_PERFETTO)\n"
  },
  {
    "path": "src/workerd/util/ring-buffer-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"ring-buffer.h\"\n\n#include <kj/test.h>\n\nnamespace workerd {\nnamespace {\n\nusing kj::uint;\n\nKJ_TEST(\"RingBuffer basic operations\") {\n  RingBuffer<int> buffer;\n\n  KJ_EXPECT(buffer.empty());\n  KJ_EXPECT(buffer.size() == 0);\n\n  buffer.push_back(1);\n  KJ_EXPECT(!buffer.empty());\n  KJ_EXPECT(buffer.size() == 1);\n  KJ_EXPECT(buffer.front() == 1);\n  KJ_EXPECT(buffer.back() == 1);\n\n  buffer.push_back(2);\n  KJ_EXPECT(buffer.size() == 2);\n  KJ_EXPECT(buffer.front() == 1);\n  KJ_EXPECT(buffer.back() == 2);\n\n  buffer.push_back(3);\n  KJ_EXPECT(buffer.size() == 3);\n  KJ_EXPECT(buffer.front() == 1);\n  KJ_EXPECT(buffer.back() == 3);\n\n  buffer.pop_front();\n  KJ_EXPECT(buffer.size() == 2);\n  KJ_EXPECT(buffer.front() == 2);\n  KJ_EXPECT(buffer.back() == 3);\n\n  buffer.pop_front();\n  KJ_EXPECT(buffer.size() == 1);\n  KJ_EXPECT(buffer.front() == 3);\n  KJ_EXPECT(buffer.back() == 3);\n\n  buffer.pop_front();\n  KJ_EXPECT(buffer.empty());\n  KJ_EXPECT(buffer.size() == 0);\n}\n\nKJ_TEST(\"RingBuffer push_back with move semantics\") {\n  RingBuffer<kj::String> buffer;\n\n  auto str1 = kj::heapString(\"hello\");\n  auto str2 = kj::heapString(\"world\");\n\n  buffer.push_back(kj::mv(str1));\n  buffer.push_back(kj::mv(str2));\n\n  KJ_EXPECT(buffer.size() == 2);\n  KJ_EXPECT(buffer.front() == \"hello\");\n  KJ_EXPECT(buffer.back() == \"world\");\n}\n\nKJ_TEST(\"RingBuffer push_back with copy\") {\n  RingBuffer<int> buffer;\n\n  int value = 42;\n  buffer.push_back(value);\n  KJ_EXPECT(buffer.size() == 1);\n  KJ_EXPECT(buffer.front() == 42);\n  KJ_EXPECT(value == 42);  // Original value unchanged\n}\n\nKJ_TEST(\"RingBuffer emplace_back\") {\n  struct TestStruct {\n    int a = 0;\n    TestStruct() = default;\n    TestStruct(int a): a(a) {}\n    TestStruct(const TestStruct&) = default;\n    TestStruct& operator=(const TestStruct&) = default;\n  };\n\n  RingBuffer<TestStruct> buffer;\n\n  auto& ref = buffer.emplace_back(10);\n  KJ_EXPECT(buffer.size() == 1);\n  KJ_EXPECT(ref.a == 10);\n  KJ_EXPECT(buffer.front().a == 10);\n}\n\nKJ_TEST(\"RingBuffer clear\") {\n  RingBuffer<int> buffer;\n\n  for (int i = 0; i < 10; i++) {\n    buffer.push_back(i);\n  }\n  KJ_EXPECT(buffer.size() == 10);\n\n  buffer.clear();\n  KJ_EXPECT(buffer.empty());\n  KJ_EXPECT(buffer.size() == 0);\n\n  // Should be able to use after clear\n  buffer.push_back(1);\n  KJ_EXPECT(buffer.size() == 1);\n  KJ_EXPECT(buffer.front() == 1);\n}\n\nKJ_TEST(\"RingBuffer iterator basic\") {\n  RingBuffer<int> buffer;\n\n  for (int i = 0; i < 5; i++) {\n    buffer.push_back(i);\n  }\n\n  int expected = 0;\n  // Using verbose syntax to test iterator\n  // NOLINTNEXTLINE(modernize-loop-convert)\n  for (auto it = buffer.begin(); it != buffer.end(); ++it) {\n    KJ_EXPECT(*it == expected++);\n  }\n  KJ_EXPECT(expected == 5);\n}\n\nKJ_TEST(\"RingBuffer iterator range-based for loop\") {\n  RingBuffer<int> buffer;\n\n  for (int i = 1; i <= 5; i++) {\n    buffer.push_back(i * 10);\n  }\n\n  int expected = 10;\n  for (auto& value: buffer) {\n    KJ_EXPECT(value == expected);\n    expected += 10;\n  }\n  KJ_EXPECT(expected == 60);\n}\n\nKJ_TEST(\"RingBuffer iterator modification\") {\n  RingBuffer<int> buffer;\n\n  for (int i = 0; i < 5; i++) {\n    buffer.push_back(i);\n  }\n\n  // Modify through iterator\n  for (auto& value: buffer) {\n    value *= 2;\n  }\n\n  int expected = 0;\n  for (const auto& value: buffer) {\n    KJ_EXPECT(value == expected * 2);\n    expected++;\n  }\n}\n\nKJ_TEST(\"RingBuffer const_iterator\") {\n  RingBuffer<int> buffer;\n\n  for (int i = 0; i < 5; i++) {\n    buffer.push_back(i);\n  }\n\n  const auto& constBuffer = buffer;\n\n  int expected = 0;\n  // Using verbose syntax to test iterator\n  // NOLINTNEXTLINE(modernize-loop-convert)\n  for (auto it = constBuffer.begin(); it != constBuffer.end(); ++it) {\n    KJ_EXPECT(*it == expected++);\n  }\n}\n\nKJ_TEST(\"RingBuffer iterator decrement\") {\n  RingBuffer<int> buffer;\n\n  for (int i = 0; i < 5; i++) {\n    buffer.push_back(i);\n  }\n\n  auto it = buffer.end();\n  --it;\n  KJ_EXPECT(*it == 4);\n  --it;\n  KJ_EXPECT(*it == 3);\n\n  it--;\n  KJ_EXPECT(*it == 2);\n}\n\nKJ_TEST(\"RingBuffer iterator equality\") {\n  RingBuffer<int> buffer;\n  buffer.push_back(1);\n  buffer.push_back(2);\n\n  auto it1 = buffer.begin();\n  auto it2 = buffer.begin();\n  KJ_EXPECT(it1 == it2);\n\n  ++it2;\n  KJ_EXPECT(it1 != it2);\n\n  auto end1 = buffer.end();\n  auto end2 = buffer.end();\n  KJ_EXPECT(end1 == end2);\n}\n\nKJ_TEST(\"RingBuffer iterator arrow operator\") {\n  struct Point {\n    int x;\n    int y;\n  };\n\n  RingBuffer<Point> buffer;\n  buffer.push_back(Point{1, 2});\n  buffer.push_back(Point{3, 4});\n\n  auto it = buffer.begin();\n  KJ_EXPECT(it->x == 1);\n  KJ_EXPECT(it->y == 2);\n\n  ++it;\n  KJ_EXPECT(it->x == 3);\n  KJ_EXPECT(it->y == 4);\n}\n\nKJ_TEST(\"RingBuffer growth when capacity exceeded\") {\n  RingBuffer<int, 4> buffer;  // Small initial capacity\n\n  // Fill beyond initial capacity\n  for (int i = 0; i < 10; i++) {\n    buffer.push_back(i);\n  }\n\n  KJ_EXPECT(buffer.size() == 10);\n\n  // Verify all elements are intact\n  int expected = 0;\n  for (const auto& value: buffer) {\n    KJ_EXPECT(value == expected++);\n  }\n  KJ_EXPECT(expected == 10);\n}\n\nKJ_TEST(\"RingBuffer growth maintains order across wrap-around\") {\n  RingBuffer<int, 4> buffer;\n\n  // Create wrap-around scenario: fill, then pop, then fill again\n  buffer.push_back(1);\n  buffer.push_back(2);\n  buffer.push_back(3);\n  buffer.push_back(4);\n\n  buffer.pop_front();  // Remove 1\n  buffer.pop_front();  // Remove 2\n\n  // Now head is at index 2, tail wraps around\n  buffer.push_back(5);\n  buffer.push_back(6);\n  buffer.push_back(7);  // This should trigger growth\n\n  KJ_EXPECT(buffer.size() == 5);\n\n  // Verify order is maintained: 3, 4, 5, 6, 7\n  int expected = 3;\n  for (const auto& value: buffer) {\n    KJ_EXPECT(value == expected++);\n  }\n  KJ_EXPECT(expected == 8);\n}\n\nKJ_TEST(\"RingBuffer with non-trivial types\") {\n  struct ComplexType {\n    kj::String str;\n    kj::Vector<int> vec;\n\n    ComplexType(kj::String s): str(kj::mv(s)) {\n      vec.add(1);\n      vec.add(2);\n    }\n\n    ComplexType(ComplexType&& other) = default;\n    ComplexType& operator=(ComplexType&& other) = default;\n\n    KJ_DISALLOW_COPY(ComplexType);\n  };\n\n  RingBuffer<kj::Own<ComplexType>> buffer;\n\n  buffer.push_back(kj::heap<ComplexType>(kj::str(\"first\")));\n  buffer.push_back(kj::heap<ComplexType>(kj::str(\"second\")));\n  buffer.push_back(kj::heap<ComplexType>(kj::str(\"third\")));\n\n  KJ_EXPECT(buffer.size() == 3);\n  KJ_EXPECT(buffer.front()->str == \"first\");\n  KJ_EXPECT(buffer.back()->str == \"third\");\n\n  buffer.pop_front();\n  KJ_EXPECT(buffer.front()->str == \"second\");\n}\n\nKJ_TEST(\"RingBuffer destructor calls element destructors\") {\n  struct DestructionDetector {\n    DestructionDetector(uint& count): count(count) {}\n    ~DestructionDetector() noexcept(false) {\n      ++count;\n    }\n    KJ_DISALLOW_COPY_AND_MOVE(DestructionDetector);\n    uint& count;\n  };\n\n  uint destructionCount = 0;\n\n  {\n    RingBuffer<kj::Own<DestructionDetector>> buffer;\n    buffer.push_back(kj::heap<DestructionDetector>(destructionCount));\n    buffer.push_back(kj::heap<DestructionDetector>(destructionCount));\n    buffer.push_back(kj::heap<DestructionDetector>(destructionCount));\n\n    KJ_EXPECT(destructionCount == 0);\n  }  // Buffer goes out of scope\n\n  KJ_EXPECT(destructionCount == 3);\n}\n\nKJ_TEST(\"RingBuffer pop_front calls element destructor\") {\n  struct DestructionDetector {\n    DestructionDetector(uint& count): count(count) {}\n    ~DestructionDetector() noexcept(false) {\n      ++count;\n    }\n    KJ_DISALLOW_COPY_AND_MOVE(DestructionDetector);\n    uint& count;\n  };\n\n  uint destructionCount = 0;\n\n  RingBuffer<kj::Own<DestructionDetector>> buffer;\n  buffer.push_back(kj::heap<DestructionDetector>(destructionCount));\n  buffer.push_back(kj::heap<DestructionDetector>(destructionCount));\n\n  KJ_EXPECT(destructionCount == 0);\n\n  buffer.pop_front();\n  KJ_EXPECT(destructionCount == 1);\n\n  buffer.pop_front();\n  KJ_EXPECT(destructionCount == 2);\n}\n\nKJ_TEST(\"RingBuffer clear calls all element destructors\") {\n  struct DestructionDetector {\n    DestructionDetector(uint& count): count(count) {}\n    ~DestructionDetector() noexcept(false) {\n      ++count;\n    }\n    KJ_DISALLOW_COPY_AND_MOVE(DestructionDetector);\n    uint& count;\n  };\n\n  uint destructionCount = 0;\n\n  RingBuffer<kj::Own<DestructionDetector>> buffer;\n  for (int i = 0; i < 5; i++) {\n    buffer.push_back(kj::heap<DestructionDetector>(destructionCount));\n  }\n\n  KJ_EXPECT(destructionCount == 0);\n\n  buffer.clear();\n  KJ_EXPECT(destructionCount == 5);\n  KJ_EXPECT(buffer.empty());\n}\n\nKJ_TEST(\"RingBuffer stress test - many operations\") {\n  RingBuffer<int, 4> buffer;\n\n  // Perform many mixed operations\n  for (int i = 0; i < 100; i++) {\n    buffer.push_back(i);\n  }\n\n  for (int i = 0; i < 50; i++) {\n    buffer.pop_front();\n  }\n\n  for (int i = 100; i < 150; i++) {\n    buffer.push_back(i);\n  }\n\n  KJ_EXPECT(buffer.size() == 100);\n\n  // Verify contents\n  int expected = 50;\n  for (const auto& value: buffer) {\n    KJ_EXPECT(value == expected++);\n  }\n}\n\nKJ_TEST(\"RingBuffer empty buffer iterators\") {\n  RingBuffer<int> buffer;\n\n  KJ_EXPECT(buffer.begin() == buffer.end());\n  KJ_EXPECT(buffer.cbegin() == buffer.cend());\n\n  // Range-based for should not execute\n  for ([[maybe_unused]] auto& value: buffer) {\n    KJ_FAIL_EXPECT(\"Should not iterate over empty buffer\");\n  }\n}\n\nKJ_TEST(\"RingBuffer single element\") {\n  RingBuffer<int> buffer;\n  buffer.push_back(42);\n\n  KJ_EXPECT(buffer.front() == 42);\n  KJ_EXPECT(buffer.back() == 42);\n  KJ_EXPECT(buffer.size() == 1);\n\n  int count = 0;\n  for (const auto& value: buffer) {\n    KJ_EXPECT(value == 42);\n    count++;\n  }\n  KJ_EXPECT(count == 1);\n}\n\nKJ_TEST(\"RingBuffer alternating push/pop maintains correctness\") {\n  RingBuffer<int, 4> buffer;\n\n  for (int round = 0; round < 10; round++) {\n    buffer.push_back(round * 2);\n    buffer.push_back(round * 2 + 1);\n\n    KJ_EXPECT(buffer.front() == round * 2);\n    buffer.pop_front();\n\n    KJ_EXPECT(buffer.front() == round * 2 + 1);\n    buffer.pop_front();\n\n    KJ_EXPECT(buffer.empty());\n  }\n}\n\nKJ_TEST(\"RingBuffer with custom initial capacity\") {\n  RingBuffer<int, 128> largeBuffer;\n  RingBuffer<int, 2> tinyBuffer;\n\n  // Both should work correctly regardless of initial capacity\n  for (int i = 0; i < 10; i++) {\n    largeBuffer.push_back(i);\n    tinyBuffer.push_back(i);\n  }\n\n  KJ_EXPECT(largeBuffer.size() == 10);\n  KJ_EXPECT(tinyBuffer.size() == 10);\n\n  int expected = 0;\n  for (const auto& value: largeBuffer) {\n    KJ_EXPECT(value == expected++);\n  }\n\n  expected = 0;\n  for (const auto& value: tinyBuffer) {\n    KJ_EXPECT(value == expected++);\n  }\n}\n\nKJ_TEST(\"RingBuffer front and back with wrap-around\") {\n  RingBuffer<int, 4> buffer;\n\n  buffer.push_back(1);\n  buffer.push_back(2);\n  buffer.push_back(3);\n  buffer.push_back(4);\n\n  // Create wrap-around\n  buffer.pop_front();\n  buffer.pop_front();\n  buffer.push_back(5);\n  buffer.push_back(6);\n\n  KJ_EXPECT(buffer.size() == 4);\n  KJ_EXPECT(buffer.front() == 3);\n  KJ_EXPECT(buffer.back() == 6);\n}\n\nKJ_TEST(\"RingBuffer emplace_back returns reference\") {\n  RingBuffer<int> buffer;\n\n  auto& ref1 = buffer.emplace_back(10);\n  ref1 = 20;\n\n  KJ_EXPECT(buffer.front() == 20);\n\n  buffer.emplace_back(30);\n  buffer.emplace_back(40);\n\n  auto& ref2 = buffer.emplace_back(50);\n  ref2 = 60;\n\n  KJ_EXPECT(buffer.back() == 60);\n}\n\nKJ_TEST(\"RingBuffer iterator conversion from mutable to const\") {\n  RingBuffer<int> buffer;\n  buffer.push_back(1);\n  buffer.push_back(2);\n\n  auto it = buffer.begin();\n  RingBuffer<int>::const_iterator cit = it;  // Conversion\n\n  KJ_EXPECT(*cit == 1);\n  ++cit;\n  KJ_EXPECT(*cit == 2);\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/ring-buffer.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/array.h>\n#include <kj/common.h>\n#include <kj/debug.h>\n\n#include <iterator>\n\nnamespace workerd {\n\n// A simple ring buffer with amortized O(1) push/pop at both ends and O(1) random access.\n// The initial capacity can be specified as a template parameter (default 16). The buffer\n// will grow as needed. The type T must be Moveable and/or Copyable.\n//\n// The purpose of this class is to provide a more memory-efficient alternative to std::list\n// for use in places where we need a double-ended queue with stable iterators and references\n// but avoids the memory overhead of std::list's per-node allocations and has better cache\n// locality than std::list. The trade-off is that iterators and references may be\n// invalidated when the buffer grows, which is not the case with std::list. However,\n// in our use cases, we do not rely on iterator/reference stability across growth\n// operations, so this fine.\ntemplate <typename T, size_t InitialCapacity = 16>\nclass RingBuffer final {\n public:\n  RingBuffer(): storage(kj::heapArray<kj::byte>(sizeof(T) * InitialCapacity)) {}\n\n  ~RingBuffer() {\n    clear();\n  }\n\n  RingBuffer(RingBuffer&& other) noexcept\n      : storage(kj::mv(other.storage)),\n        head(other.head),\n        tail(other.tail),\n        count(other.count),\n        generation(other.generation) {\n    other.head = 0;\n    other.tail = 0;\n    other.count = 0;\n    other.generation = 0;\n  }\n\n  RingBuffer& operator=(RingBuffer&& other) noexcept {\n    if (this != &other) {\n      clear();\n\n      storage = kj::mv(other.storage);\n      head = other.head;\n      tail = other.tail;\n      count = other.count;\n      generation = other.generation;\n\n      other.head = 0;\n      other.tail = 0;\n      other.count = 0;\n      other.generation = 0;\n    }\n    return *this;\n  }\n\n  KJ_DISALLOW_COPY(RingBuffer);\n\n  bool empty() const {\n    return count == 0;\n  }\n  size_t size() const {\n    return count;\n  }\n\n  class iterator;\n  class const_iterator;\n\n  class iterator {\n   public:\n    using iterator_category = std::bidirectional_iterator_tag;\n    using value_type = T;\n    using difference_type = std::ptrdiff_t;\n    using pointer = T*;\n    using reference = T&;\n\n    iterator(): buffer(nullptr), index(0) {}\n\n    reference operator*() const {\n      KJ_DREQUIRE(buffer != nullptr);\n      size_t physicalIndex = (buffer->head + index) % buffer->capacity();\n      return buffer->slot(physicalIndex);\n    }\n\n    pointer operator->() const {\n      KJ_DREQUIRE(buffer != nullptr);\n      size_t physicalIndex = (buffer->head + index) % buffer->capacity();\n      return &buffer->slot(physicalIndex);\n    }\n\n    iterator& operator++() {\n      KJ_DREQUIRE(buffer != nullptr);\n      ++index;\n      return *this;\n    }\n\n    iterator operator++(int) {\n      iterator tmp = *this;\n      ++(*this);\n      return tmp;\n    }\n\n    iterator& operator--() {\n      KJ_DREQUIRE(buffer != nullptr);\n      --index;\n      return *this;\n    }\n\n    iterator operator--(int) {\n      iterator tmp = *this;\n      --(*this);\n      return tmp;\n    }\n\n    bool operator==(const iterator& other) const {\n      return buffer == other.buffer && index == other.index;\n    }\n\n    bool operator!=(const iterator& other) const {\n      return !(*this == other);\n    }\n\n   private:\n    friend class RingBuffer;\n    friend class const_iterator;\n\n    iterator(RingBuffer* buf, size_t idx): buffer(buf), index(idx) {}\n\n    RingBuffer* buffer;\n    size_t index;  // Logical index (0 to count-1)\n  };\n\n  class const_iterator {\n   public:\n    using iterator_category = std::bidirectional_iterator_tag;\n    using value_type = T;\n    using difference_type = std::ptrdiff_t;\n    using pointer = const T*;\n    using reference = const T&;\n\n    const_iterator(): buffer(nullptr), index(0) {}\n\n    const_iterator(const iterator& it): buffer(it.buffer), index(it.index) {}\n\n    reference operator*() const {\n      KJ_DREQUIRE(buffer != nullptr);\n      size_t physicalIndex = (buffer->head + index) % buffer->capacity();\n      return buffer->slot(physicalIndex);\n    }\n\n    pointer operator->() const {\n      KJ_DREQUIRE(buffer != nullptr);\n      size_t physicalIndex = (buffer->head + index) % buffer->capacity();\n      return &buffer->slot(physicalIndex);\n    }\n\n    const_iterator& operator++() {\n      KJ_DREQUIRE(buffer != nullptr);\n      ++index;\n      return *this;\n    }\n\n    const_iterator operator++(int) {\n      const_iterator tmp = *this;\n      ++(*this);\n      return tmp;\n    }\n\n    const_iterator& operator--() {\n      KJ_DREQUIRE(buffer != nullptr);\n      --index;\n      return *this;\n    }\n\n    const_iterator operator--(int) {\n      const_iterator tmp = *this;\n      --(*this);\n      return tmp;\n    }\n\n    bool operator==(const const_iterator& other) const {\n      return buffer == other.buffer && index == other.index;\n    }\n\n    bool operator!=(const const_iterator& other) const {\n      return !(*this == other);\n    }\n\n   private:\n    friend class RingBuffer;\n\n    const_iterator(const RingBuffer* buf, size_t idx): buffer(buf), index(idx) {}\n\n    const RingBuffer* buffer;\n    size_t index;\n  };\n\n  iterator begin() {\n    return iterator(this, 0);\n  }\n\n  iterator end() {\n    return iterator(this, count);\n  }\n\n  const_iterator begin() const {\n    return const_iterator(this, 0);\n  }\n\n  const_iterator end() const {\n    return const_iterator(this, count);\n  }\n\n  const_iterator cbegin() const {\n    return const_iterator(this, 0);\n  }\n\n  const_iterator cend() const {\n    return const_iterator(this, count);\n  }\n\n  void push_back(T&& item) {\n    if (count == capacity()) {\n      grow();\n    }\n    // Use placement new - the slot is uninitialized raw memory\n    new (&slot(tail)) T(kj::mv(item));\n    tail = (tail + 1) % capacity();\n    count++;\n  }\n\n  void push_back(const T& item) {\n    if (count == capacity()) {\n      grow();\n    }\n    // Use placement new - the slot is uninitialized raw memory\n    new (&slot(tail)) T(item);\n    tail = (tail + 1) % capacity();\n    count++;\n  }\n\n  template <typename... Args>\n  T& emplace_back(Args&&... args) {\n    if (count == capacity()) {\n      grow();\n    }\n    // Use placement new to construct in place at the tail position\n    T* ptr = new (&slot(tail)) T(kj::fwd<Args>(args)...);\n    tail = (tail + 1) % capacity();\n    count++;\n    return *ptr;\n  }\n\n  void pop_front() {\n    KJ_DREQUIRE(count > 0);\n    slot(head).~T();\n    head = (head + 1) % capacity();\n    count--;\n    generation++;\n  }\n\n  // Returns a generation counter that is incremented each time pop_front() is called.\n  // This can be used to detect if the front of the queue has changed during async operations,\n  // since RingBuffer may relocate elements when it grows and pointer/reference comparisons\n  // are not reliable.\n  uint64_t currentGeneration() const {\n    return generation;\n  }\n\n  T& front() {\n    KJ_DREQUIRE(count > 0);\n    return slot(head);\n  }\n\n  T& back() {\n    KJ_DREQUIRE(count > 0);\n    size_t backIdx = (tail == 0) ? capacity() - 1 : tail - 1;\n    KJ_REQUIRE(backIdx < capacity());\n    return slot(backIdx);\n  }\n\n  void clear() {\n    while (count > 0) {\n      slot(head).~T();\n      head = (head + 1) % capacity();\n      count--;\n    }\n    // Reset to initial state\n    head = 0;\n    tail = 0;\n  }\n\n private:\n  kj::Array<kj::byte> storage;\n  size_t head = 0;\n  size_t tail = 0;\n  size_t count = 0;\n  uint64_t generation = 0;  // Incremented on each pop_front()\n\n  size_t capacity() const {\n    return storage.size() / sizeof(T);\n  }\n\n  T& slot(size_t index) {\n    return reinterpret_cast<T*>(storage.begin())[index];\n  }\n\n  const T& slot(size_t index) const {\n    return reinterpret_cast<const T*>(storage.begin())[index];\n  }\n\n  void grow() {\n    size_t oldCapacity = capacity();\n    size_t newCapacity = oldCapacity * 2;\n    auto newStorage = kj::heapArray<kj::byte>(sizeof(T) * newCapacity);\n    T* newSlots = reinterpret_cast<T*>(newStorage.begin());\n\n    // Move-construct elements to new storage using placement new\n    for (size_t i = 0; i < count; i++) {\n      new (&newSlots[i]) T(kj::mv(slot((head + i) % oldCapacity)));\n      slot((head + i) % oldCapacity).~T();\n    }\n\n    storage = kj::mv(newStorage);\n    head = 0;\n    tail = count;\n  }\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/sentry.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// Some utilities related to logging, particularly with respect to how Cloudflare's edge runtime's\n// Sentry integration will end up treating the logs.\n\n#include <kj/common.h>\n#include <kj/debug.h>\n#include <kj/exception.h>\n#include <kj/string.h>\n#include <kj/time.h>\n\n#include <cstdint>\n\nnamespace workerd {\n\n// Log out an exception with context but without frills. This macro excludes any variadic arguments\n// from the macro so that we do not accidentally make a more granular fingerprint. It also will only\n// take a `context` argument that is known at compile time (via constexpr assignment).\n#define LOG_EXCEPTION(context, exception)                                                          \\\n  [&](const kj::Exception& e) {                                                                    \\\n    constexpr auto sentryErrorContext = context;                                                   \\\n    KJ_LOG(ERROR, e, sentryErrorContext);                                                          \\\n  }(exception)\n\n#define ACTOR_STORAGE_OP_PREFIX \"; actorStorageOp = \"\n\ninline bool isInterestingException(const kj::Exception& e) {\n  return e.getType() != kj::Exception::Type::DISCONNECTED &&\n      e.getType() != kj::Exception::Type::OVERLOADED;\n}\n\n#define LOG_NOSENTRY(severity, ...) KJ_LOG(severity, \"NOSENTRY \" __VA_ARGS__);\n\n#define LOG_IF_INTERESTING(exception, severity, ...)                                               \\\n  if (!::workerd::isInterestingException(exception)) {                                             \\\n    LOG_NOSENTRY(severity, __VA_ARGS__);                                                           \\\n  } else {                                                                                         \\\n    KJ_LOG(severity, __VA_ARGS__);                                                                 \\\n  }\n\n// Log this to Sentry once ever per process. Typically will be better to use LOG_PERIODICALLY.\n#define LOG_ONCE(severity, ...)                                                                    \\\n  do {                                                                                             \\\n    static bool logOnce KJ_UNUSED = [&]() {                                                        \\\n      KJ_LOG(severity, __VA_ARGS__);                                                               \\\n      return true;                                                                                 \\\n    }();                                                                                           \\\n  } while (0)\n\n// Log this to Sentry once ever per process. Typically will be better to use LOG_WARNING_PERIODICALLY.\n#define LOG_WARNING_ONCE(...) LOG_ONCE(WARNING, __VA_ARGS__);\n\n// Log this to Sentry once ever per process. Typically will be better to use LOG_ERROR_PERIODICALLY.\n#define LOG_ERROR_ONCE(...) LOG_ONCE(ERROR, __VA_ARGS__);\n\n// Slightly more expensive than LOG_ONCE. Avoid putting into a hot path (e.g. within a loop)\n// where an overhead of ~hundreds of nanoseconds per evaluation to retrieve the current time would\n// be prohibitive.\n#define LOG_PERIODICALLY(severity, ...)                                                            \\\n  do {                                                                                             \\\n    static kj::TimePoint KJ_UNIQUE_NAME(lastLogged) = kj::origin<kj::TimePoint>() - 1 * kj::HOURS; \\\n    const auto KJ_UNIQUE_NAME(now) = kj::systemCoarseMonotonicClock().now();                       \\\n    const auto KJ_UNIQUE_NAME(elapsed) = KJ_UNIQUE_NAME(now) - KJ_UNIQUE_NAME(lastLogged);         \\\n    if (KJ_UNLIKELY(KJ_UNIQUE_NAME(elapsed) >= 1 * kj::HOURS)) {                                   \\\n      KJ_UNIQUE_NAME(lastLogged) = KJ_UNIQUE_NAME(now);                                            \\\n      KJ_LOG(severity, __VA_ARGS__);                                                               \\\n    }                                                                                              \\\n  } while (0)\n\n// Slightly more expensive than LOG_WARNING_ONCE. Avoid putting into a hot path (e.g. within a loop)\n// where an overhead of ~hundreds of nanoseconds per evaluation to retrieve the current time would\n// be prohibitive.\n#define LOG_WARNING_PERIODICALLY(...) LOG_PERIODICALLY(WARNING, __VA_ARGS__);\n\n// Slightly more expensive than LOG_ERROR_ONCE. Avoid putting into a hot path (e.g. within a loop)\n// where an overhead of ~hundreds of nanoseconds per evaluation to retrieve the current time would\n// be prohibitive.\n#define LOG_ERROR_PERIODICALLY(...) LOG_PERIODICALLY(ERROR, __VA_ARGS__);\n\n// The DEBUG_FATAL_RELEASE_LOG macros is for assertions that should definitely break in tests but\n// are not worth breaking production over. Instead, it logs the assertion message to sentry so that\n// we can notice the event. If your code requires that an assertion is true for safety (e.g.\n// checking if a value is not null), this is not the macro for you.\n#ifdef KJ_DEBUG\n#define DEBUG_FATAL_RELEASE_LOG(severity, ...) ([&]() noexcept { KJ_FAIL_ASSERT(__VA_ARGS__); })()\n#else\n#define DEBUG_FATAL_RELEASE_LOG(severity, ...)                                                     \\\n  do {                                                                                             \\\n    static bool logOnce KJ_UNUSED = [&]() {                                                        \\\n      KJ_LOG(severity, __VA_ARGS__);                                                               \\\n      return true;                                                                                 \\\n    }();                                                                                           \\\n  } while (0)\n#endif\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/small-set-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"small-set.h\"\n#include \"weak-refs.h\"\n\n#include <kj/refcount.h>\n#include <kj/test.h>\n\nnamespace workerd {\nnamespace {\n\n// A simple refcounted type for testing SmallSet.\n// Supports WeakRef creation for testing forEach.\nstruct TestItem final: public kj::Refcounted {\n  int value;\n  kj::Rc<WeakRef<TestItem>> selfRef;\n\n  explicit TestItem(int v)\n      : value(v),\n        selfRef(kj::rc<WeakRef<TestItem>>(kj::Badge<TestItem>{}, *this)) {}\n\n  ~TestItem() noexcept(false) {\n    selfRef->invalidate();\n  }\n\n  kj::Rc<WeakRef<TestItem>> getWeakRef() {\n    return selfRef.addRef();\n  }\n};\n\n// Helper to check if set contains an item with a given value.\nbool containsValue(SmallSet<kj::Rc<TestItem>>& set, int value) {\n  return set.containsIf([value](const kj::Rc<TestItem>& item) { return item->value == value; });\n}\n\n// Helper to remove an item with a given value.\nbool removeValue(SmallSet<kj::Rc<TestItem>>& set, int value) {\n  return set.removeIf([value](const kj::Rc<TestItem>& item) { return item->value == value; });\n}\n\n// Helper for WeakRef-based tests (used with forEach).\nbool removeWeakRefValue(SmallSet<kj::Rc<WeakRef<TestItem>>>& set, int value) {\n  return set.removeIf([value](const kj::Rc<WeakRef<TestItem>>& item) {\n    KJ_IF_SOME(ref, item->tryGet()) {\n      return ref.value == value;\n    }\n    return false;\n  });\n}\n\nKJ_TEST(\"SmallSet: empty set\") {\n  SmallSet<kj::Rc<TestItem>> set;\n  KJ_EXPECT(set.empty());\n  KJ_EXPECT(set.size() == 0);\n  KJ_EXPECT(!containsValue(set, 42));\n}\n\nKJ_TEST(\"SmallSet: add and remove single item\") {\n  SmallSet<kj::Rc<TestItem>> set;\n\n  set.add(kj::rc<TestItem>(1));\n  KJ_EXPECT(!set.empty());\n  KJ_EXPECT(set.size() == 1);\n  KJ_EXPECT(containsValue(set, 1));\n\n  KJ_EXPECT(removeValue(set, 1));\n  KJ_EXPECT(set.empty());\n  KJ_EXPECT(set.size() == 0);\n  KJ_EXPECT(!containsValue(set, 1));\n\n  // Removing again should return false\n  KJ_EXPECT(!removeValue(set, 1));\n}\n\nKJ_TEST(\"SmallSet: add and remove two items\") {\n  SmallSet<kj::Rc<TestItem>> set;\n\n  set.add(kj::rc<TestItem>(1));\n  set.add(kj::rc<TestItem>(2));\n  KJ_EXPECT(set.size() == 2);\n  KJ_EXPECT(containsValue(set, 1));\n  KJ_EXPECT(containsValue(set, 2));\n\n  KJ_EXPECT(removeValue(set, 1));\n  KJ_EXPECT(set.size() == 1);\n  KJ_EXPECT(!containsValue(set, 1));\n  KJ_EXPECT(containsValue(set, 2));\n\n  KJ_EXPECT(removeValue(set, 2));\n  KJ_EXPECT(set.empty());\n}\n\nKJ_TEST(\"SmallSet: add and remove multiple items\") {\n  SmallSet<kj::Rc<TestItem>> set;\n\n  set.add(kj::rc<TestItem>(1));\n  set.add(kj::rc<TestItem>(2));\n  set.add(kj::rc<TestItem>(3));\n  set.add(kj::rc<TestItem>(4));\n  KJ_EXPECT(set.size() == 4);\n\n  KJ_EXPECT(containsValue(set, 1));\n  KJ_EXPECT(containsValue(set, 2));\n  KJ_EXPECT(containsValue(set, 3));\n  KJ_EXPECT(containsValue(set, 4));\n\n  KJ_EXPECT(removeValue(set, 2));\n  KJ_EXPECT(set.size() == 3);\n  KJ_EXPECT(!containsValue(set, 2));\n\n  KJ_EXPECT(removeValue(set, 3));\n  KJ_EXPECT(set.size() == 2);\n  KJ_EXPECT(containsValue(set, 1));\n  KJ_EXPECT(containsValue(set, 4));\n\n  KJ_EXPECT(removeValue(set, 1));\n  KJ_EXPECT(set.size() == 1);\n  KJ_EXPECT(containsValue(set, 4));\n\n  KJ_EXPECT(removeValue(set, 4));\n  KJ_EXPECT(set.empty());\n}\n\nKJ_TEST(\"SmallSet: state transitions\") {\n  SmallSet<kj::Rc<TestItem>> set;\n\n  // None -> Single\n  set.add(kj::rc<TestItem>(1));\n  KJ_EXPECT(set.size() == 1);\n\n  // Single -> Double\n  set.add(kj::rc<TestItem>(2));\n  KJ_EXPECT(set.size() == 2);\n\n  // Double -> Multiple\n  set.add(kj::rc<TestItem>(3));\n  KJ_EXPECT(set.size() == 3);\n\n  // Multiple stays Multiple\n  set.add(kj::rc<TestItem>(4));\n  KJ_EXPECT(set.size() == 4);\n\n  // Multiple -> Multiple (one less)\n  KJ_EXPECT(removeValue(set, 4));\n  KJ_EXPECT(set.size() == 3);\n\n  // Multiple -> Double\n  KJ_EXPECT(removeValue(set, 3));\n  KJ_EXPECT(set.size() == 2);\n\n  // Double -> Single\n  KJ_EXPECT(removeValue(set, 2));\n  KJ_EXPECT(set.size() == 1);\n\n  // Single -> None\n  KJ_EXPECT(removeValue(set, 1));\n  KJ_EXPECT(set.size() == 0);\n}\n\nKJ_TEST(\"SmallSet: iteration\") {\n  SmallSet<kj::Rc<TestItem>> set;\n\n  // Empty iteration\n  int count = 0;\n  for (auto& item: set) {\n    (void)item;\n    count++;\n  }\n  KJ_EXPECT(count == 0);\n\n  // Single item\n  set.add(kj::rc<TestItem>(1));\n  count = 0;\n  for (auto& item: set) {\n    KJ_EXPECT(item->value == 1);\n    count++;\n  }\n  KJ_EXPECT(count == 1);\n\n  // Two items\n  set.add(kj::rc<TestItem>(2));\n  count = 0;\n  for (auto& item: set) {\n    KJ_EXPECT(item->value == 1 || item->value == 2);\n    count++;\n  }\n  KJ_EXPECT(count == 2);\n\n  // Multiple items\n  set.add(kj::rc<TestItem>(3));\n  count = 0;\n  for (auto& item: set) {\n    KJ_EXPECT(item->value >= 1 && item->value <= 3);\n    count++;\n  }\n  KJ_EXPECT(count == 3);\n}\n\nKJ_TEST(\"SmallSet: clear\") {\n  SmallSet<kj::Rc<TestItem>> set;\n\n  set.add(kj::rc<TestItem>(1));\n  set.add(kj::rc<TestItem>(2));\n  set.add(kj::rc<TestItem>(3));\n  KJ_EXPECT(set.size() == 3);\n\n  set.clear();\n  KJ_EXPECT(set.empty());\n  KJ_EXPECT(set.size() == 0);\n  KJ_EXPECT(!containsValue(set, 1));\n  KJ_EXPECT(!containsValue(set, 2));\n  KJ_EXPECT(!containsValue(set, 3));\n}\n\nKJ_TEST(\"SmallSet: forEach for safe iteration during modification\") {\n  // This simulates the queue.h use case where items may be removed during iteration.\n  // forEach requires WeakRef items to handle invalidation during iteration.\n  SmallSet<kj::Rc<WeakRef<TestItem>>> set;\n\n  auto item1 = kj::rc<TestItem>(1);\n  auto item2 = kj::rc<TestItem>(2);\n  auto item3 = kj::rc<TestItem>(3);\n\n  set.add(item1->getWeakRef());\n  set.add(item2->getWeakRef());\n  set.add(item3->getWeakRef());\n  KJ_EXPECT(set.size() == 3);\n\n  // forEach takes a snapshot internally, so items remain valid even if removed during iteration.\n  kj::Vector<int> foundValues;\n  set.forEach([&](TestItem& item) {\n    foundValues.add(item.value);\n    // Remove the item from the set during iteration - this is safe because\n    // forEach iterates over an internal snapshot, not the set itself.\n    removeWeakRefValue(set, item.value);\n  });\n\n  KJ_EXPECT(foundValues.size() == 3);\n  KJ_EXPECT(set.empty());\n}\n\nKJ_TEST(\"SmallSet: forEach from single state\") {\n  SmallSet<kj::Rc<WeakRef<TestItem>>> set;\n\n  auto item = kj::rc<TestItem>(42);\n  set.add(item->getWeakRef());\n\n  size_t count = 0;\n  set.forEach([&](TestItem& ref) {\n    KJ_EXPECT(ref.value == 42);\n    count++;\n  });\n  KJ_EXPECT(count == 1);\n}\n\nKJ_TEST(\"SmallSet: forEach from double state\") {\n  SmallSet<kj::Rc<WeakRef<TestItem>>> set;\n\n  auto item1 = kj::rc<TestItem>(1);\n  auto item2 = kj::rc<TestItem>(2);\n\n  set.add(item1->getWeakRef());\n  set.add(item2->getWeakRef());\n\n  kj::Vector<int> foundValues;\n  set.forEach([&](TestItem& ref) { foundValues.add(ref.value); });\n  KJ_EXPECT(foundValues.size() == 2);\n  // Order doesn't matter for set semantics, just check both values are present\n  bool found1 = false, found2 = false;\n  for (auto v: foundValues) {\n    if (v == 1) found1 = true;\n    if (v == 2) found2 = true;\n  }\n  KJ_EXPECT(found1);\n  KJ_EXPECT(found2);\n}\n\nKJ_TEST(\"SmallSet: forEach from empty state\") {\n  SmallSet<kj::Rc<WeakRef<TestItem>>> set;\n  size_t count = 0;\n  set.forEach([&](TestItem& ref) { count++; });\n  KJ_EXPECT(count == 0);\n}\n\nKJ_TEST(\"SmallSet: forEach skips invalidated WeakRefs\") {\n  // Test that forEach properly skips items whose WeakRefs have been invalidated\n  SmallSet<kj::Rc<WeakRef<TestItem>>> set;\n\n  auto item1 = kj::rc<TestItem>(1);\n  auto item2 = kj::rc<TestItem>(2);\n\n  set.add(item1->getWeakRef());\n  set.add(item2->getWeakRef());\n  KJ_EXPECT(set.size() == 2);\n\n  // Invalidate item1's weak ref by destroying item1\n  item1 = nullptr;\n\n  // forEach should only visit item2\n  kj::Vector<int> foundValues;\n  set.forEach([&](TestItem& ref) { foundValues.add(ref.value); });\n\n  KJ_EXPECT(foundValues.size() == 1);\n  KJ_EXPECT(foundValues[0] == 2);\n}\n\nKJ_TEST(\"SmallSet: reference counting works correctly\") {\n  // Verify that items are properly reference counted\n  kj::Rc<TestItem> item = kj::rc<TestItem>(42);\n  KJ_EXPECT(!item->isShared());  // Only one reference\n\n  {\n    SmallSet<kj::Rc<TestItem>> set;\n    set.add(item.addRef());\n    KJ_EXPECT(item->isShared());  // Now shared between item and set\n  }\n  // Set destroyed, only our original reference remains\n  KJ_EXPECT(!item->isShared());\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/small-set.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/common.h>\n#include <kj/debug.h>\n#include <kj/one-of.h>\n#include <kj/vector.h>\n\n#include <concepts>\n\nnamespace workerd {\n\n// Concept for types that support reference counting via addRef().\n// This matches kj::Rc<T> which has an addRef() method returning kj::Rc<T>.\ntemplate <typename T>\nconcept RefCountedSmartPtr = requires(T& t) {\n  { t.addRef() } -> std::convertible_to<T>;\n};\n\n// Concept for smart pointers to WeakRef-like types that have a tryGet() method.\n// This is used to constrain forEach() which needs to check if the ref is still valid.\ntemplate <typename T>\nconcept WeakRefSmartPtr = RefCountedSmartPtr<T> && requires(T& t) {\n  { t->tryGet() };\n};\n\n// A set-like container optimized for the common case of storing 0-2 items\n// of reference-counted smart pointer types (like kj::Rc<T>).\n//\n// This uses a kj::OneOf to avoid heap allocations for small sets.\n//\n// Performance characteristics:\n// - 0-1 items: Zero heap allocations, O(1) operations\n// - 2 items: Zero heap allocations, O(1) operations\n// - 3+ items: Single heap allocation (kj::Vector), O(n) operations\n//\n// Typical usage patterns:\n// - 99% of instances have 1 item\n// - 0.9% of instances have 2 items\n// - 0.1% of instances have 3+ items\n//\n// This is NOT a drop-in replacement for std::set because:\n// - Items are not kept in sorted order\n// - No logarithmic lookup guarantees\n// - Optimized for small sizes only\n//\n// Iterator invalidation:\n// - Iterators are invalidated when items are removed or storage state changes\n// - If iterating over items that may be removed during iteration, use releaseSnapshot()\n//   to get owned copies that remain valid even if the original is removed from the set.\n//\n// Template parameter T must be a reference-counted smart pointer type like kj::Rc<X>\n// that has an addRef() method returning the same type.\ntemplate <RefCountedSmartPtr T>\nclass SmallSet {\n public:\n  SmallSet() = default;\n  KJ_DISALLOW_COPY(SmallSet);\n  SmallSet(SmallSet&&) = default;\n  SmallSet& operator=(SmallSet&&) = default;\n\n  // Add an item to the set. The item is moved into the set.\n  // For move-only types, use containsIf() first to check for duplicates if needed.\n  void add(T item) {\n    KJ_SWITCH_ONEOF(storage) {\n      KJ_CASE_ONEOF(none, None) {\n        storage = Single(kj::mv(item));\n        return;\n      }\n      KJ_CASE_ONEOF(single, Single) {\n        storage = Double(kj::mv(single.item), kj::mv(item));\n        return;\n      }\n      KJ_CASE_ONEOF(dbl, Double) {\n        auto vec = kj::Vector<T>(4);\n        vec.add(kj::mv(dbl.first));\n        vec.add(kj::mv(dbl.second));\n        vec.add(kj::mv(item));\n        storage = kj::mv(vec);\n        return;\n      }\n      KJ_CASE_ONEOF(vec, kj::Vector<T>) {\n        vec.add(kj::mv(item));\n        return;\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  // Remove an item matching the predicate. Returns true if an item was removed.\n  // The predicate receives a const reference to each item.\n  template <typename Predicate>\n  bool removeIf(Predicate&& predicate) {\n    KJ_SWITCH_ONEOF(storage) {\n      KJ_CASE_ONEOF(none, None) {\n        return false;\n      }\n      KJ_CASE_ONEOF(single, Single) {\n        if (predicate(single.item)) {\n          storage = None();\n          return true;\n        }\n        return false;\n      }\n      KJ_CASE_ONEOF(dbl, Double) {\n        if (predicate(dbl.first)) {\n          storage = Single(kj::mv(dbl.second));\n          return true;\n        }\n        if (predicate(dbl.second)) {\n          storage = Single(kj::mv(dbl.first));\n          return true;\n        }\n        return false;\n      }\n      KJ_CASE_ONEOF(vec, kj::Vector<T>) {\n        // Find and remove the first matching item\n        for (size_t i = 0; i < vec.size(); ++i) {\n          if (predicate(vec[i])) {\n            // Remove by overwriting with last element and truncating\n            if (i < vec.size() - 1) {\n              vec[i] = kj::mv(vec.back());\n            }\n            vec.removeLast();\n\n            // Transition back to smaller state if appropriate\n            if (vec.size() == 2) {\n              storage = Double(kj::mv(vec[0]), kj::mv(vec[1]));\n            } else if (vec.size() == 1) {\n              storage = Single(kj::mv(vec[0]));\n            } else if (vec.size() == 0) {\n              storage = None();\n            }\n            // else: vec.size() >= 3, stay in Vector state\n\n            return true;\n          }\n        }\n        return false;\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  // Check if the set contains an item matching the predicate.\n  template <typename Predicate>\n  bool containsIf(Predicate&& predicate) const {\n    KJ_SWITCH_ONEOF(storage) {\n      KJ_CASE_ONEOF(none, None) {\n        return false;\n      }\n      KJ_CASE_ONEOF(single, Single) {\n        return predicate(single.item);\n      }\n      KJ_CASE_ONEOF(dbl, Double) {\n        return predicate(dbl.first) || predicate(dbl.second);\n      }\n      KJ_CASE_ONEOF(vec, kj::Vector<T>) {\n        for (auto& existing: vec) {\n          if (predicate(existing)) return true;\n        }\n        return false;\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  // Get the number of items in the set.\n  size_t size() const {\n    KJ_SWITCH_ONEOF(storage) {\n      KJ_CASE_ONEOF(none, None) {\n        return 0;\n      }\n      KJ_CASE_ONEOF(single, Single) {\n        return 1;\n      }\n      KJ_CASE_ONEOF(dbl, Double) {\n        return 2;\n      }\n      KJ_CASE_ONEOF(vec, kj::Vector<T>) {\n        return vec.size();\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n  // Check if the set is empty.\n  bool empty() const {\n    return size() == 0;\n  }\n\n  // Clear all items from the set.\n  void clear() {\n    storage = None();\n  }\n\n  // Iterate over all valid (non-invalidated) WeakRef items, calling func for each.\n  // This is safe to use even if func modifies the set (e.g., removes items).\n  //\n  // Only available when T is a smart pointer to a WeakRef-like type with tryGet() method\n  // (e.g., kj::Rc<WeakRef<X>>). The callback receives a reference to the\n  // underlying type (X&).\n  //\n  // Example:\n  //   SmallSet<kj::Rc<WeakRef<Consumer>>> consumers;\n  //   consumers.forEach([&](Consumer& c) {\n  //     c.close(js);  // Safe even if this removes other consumers\n  //   });\n  template <typename F>\n  void forEach(F&& func)\n    requires WeakRefSmartPtr<T>\n  {\n    KJ_SWITCH_ONEOF(storage) {\n      KJ_CASE_ONEOF(none, None) {\n        return;\n      }\n      KJ_CASE_ONEOF(single, Single) {\n        KJ_IF_SOME(ref, single.item->tryGet()) {\n          func(ref);\n        }\n        return;\n      }\n      KJ_CASE_ONEOF(dbl, Double) {\n        // The storage state may change during iteration if func modifies the set,\n        // so we take snapshots of the items first. Snapshotting just requires calling\n        // addRef to increment the ref counts.\n        kj::Array<T> refs = kj::arr(dbl.first.addRef(), dbl.second.addRef());\n        for (auto& item: refs) {\n          // We check tryGet on each item in case func invalidated some of them\n          // in prior iterations.\n          KJ_IF_SOME(ref, item->tryGet()) {\n            func(ref);\n          }\n        }\n        return;\n      }\n      KJ_CASE_ONEOF(vec, kj::Vector<T>) {\n        // The storage state may change during iteration if func modifies the set,\n        // so we take snapshots of the items first. Snapshotting just requires calling\n        // addRef to increment the ref counts.\n        auto snapshot = KJ_MAP(item, vec) { return item.addRef(); };\n        for (auto& item: snapshot) {\n          // We check tryGet on each item in case func invalidated some of them\n          // in prior iterations.\n          KJ_IF_SOME(ref, item->tryGet()) {\n            func(ref);\n          }\n        }\n        return;\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n\n private:\n  struct None {};\n\n  struct Single {\n    T item;\n    explicit Single(T item): item(kj::mv(item)) {}\n  };\n\n  struct Double {\n    T first;\n    T second;\n    Double(T first, T second): first(kj::mv(first)), second(kj::mv(second)) {}\n  };\n\n  using Storage = kj::OneOf<None, Single, Double, kj::Vector<T>>;\n  Storage storage = None();\n\n public:\n  // Iterator support - returns const references to items\n  class ConstIterator {\n   public:\n    ConstIterator() = default;\n\n    const T& operator*() const {\n      KJ_SWITCH_ONEOF(*storage) {\n        KJ_CASE_ONEOF(none, None) {\n          KJ_FAIL_REQUIRE(\"Dereferencing end iterator\");\n        }\n        KJ_CASE_ONEOF(single, Single) {\n          KJ_REQUIRE(index == 0, \"Invalid iterator\");\n          return single.item;\n        }\n        KJ_CASE_ONEOF(dbl, Double) {\n          KJ_REQUIRE(index < 2, \"Invalid iterator\");\n          return index == 0 ? dbl.first : dbl.second;\n        }\n        KJ_CASE_ONEOF(vec, kj::Vector<T>) {\n          KJ_REQUIRE(index < vec.size(), \"Invalid iterator\");\n          return vec[index];\n        }\n      }\n      KJ_UNREACHABLE;\n    }\n\n    ConstIterator& operator++() {\n      ++index;\n      return *this;\n    }\n\n    ConstIterator operator++(int) {\n      ConstIterator tmp = *this;\n      ++index;\n      return tmp;\n    }\n\n    bool operator==(const ConstIterator& other) const {\n      return storage == other.storage && index == other.index;\n    }\n\n    bool operator!=(const ConstIterator& other) const {\n      return !(*this == other);\n    }\n\n   private:\n    friend class SmallSet;\n\n    ConstIterator(const Storage* storage, size_t index): storage(storage), index(index) {}\n\n    const Storage* storage = nullptr;\n    size_t index = 0;\n  };\n\n  // Mutable iterator - returns mutable references to items\n  class Iterator {\n   public:\n    Iterator() = default;\n\n    T& operator*() const {\n      if (storage->template is<None>()) {\n        KJ_FAIL_REQUIRE(\"Dereferencing end iterator\");\n      } else if (storage->template is<Single>()) {\n        KJ_REQUIRE(index == 0, \"Invalid iterator\");\n        return storage->template get<Single>().item;\n      } else if (storage->template is<Double>()) {\n        KJ_REQUIRE(index < 2, \"Invalid iterator\");\n        auto& dbl = storage->template get<Double>();\n        return index == 0 ? dbl.first : dbl.second;\n      } else {\n        auto& vec = storage->template get<kj::Vector<T>>();\n        KJ_REQUIRE(index < vec.size(), \"Invalid iterator\");\n        return vec[index];\n      }\n    }\n\n    Iterator& operator++() {\n      ++index;\n      return *this;\n    }\n\n    Iterator operator++(int) {\n      Iterator tmp = *this;\n      ++index;\n      return tmp;\n    }\n\n    bool operator==(const Iterator& other) const {\n      return storage == other.storage && index == other.index;\n    }\n\n    bool operator!=(const Iterator& other) const {\n      return !(*this == other);\n    }\n\n   private:\n    friend class SmallSet;\n\n    Iterator(Storage* storage, size_t index): storage(storage), index(index) {}\n\n    Storage* storage = nullptr;\n    size_t index = 0;\n  };\n\n  Iterator begin() {\n    return Iterator(&storage, 0);\n  }\n\n  Iterator end() {\n    return Iterator(&storage, size());\n  }\n\n  ConstIterator begin() const {\n    return ConstIterator(&storage, 0);\n  }\n\n  ConstIterator end() const {\n    return ConstIterator(&storage, size());\n  }\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/sqlite-kv-test.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"sqlite-kv.h\"\n\n#include <kj/test.h>\n\nnamespace workerd {\nnamespace {\n\nKJ_TEST(\"SQLite-KV\") {\n  class TestSqliteObserver: public SqliteObserver {\n   public:\n    void addQueryStats(uint64_t read, uint64_t written) override {\n      rowsRead += read;\n      rowsWritten += written;\n    }\n\n    uint64_t rowsRead = 0;\n    uint64_t rowsWritten = 0;\n  };\n\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  TestSqliteObserver sqliteObserver;\n  SqliteDatabase db(\n      vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY, sqliteObserver);\n  SqliteKv kv(db);\n\n  kv.put(\"foo\", \"abc\"_kj.asBytes());\n  kv.put(\"bar\", \"def\"_kj.asBytes());\n  kv.put(\"baz\", \"123\"_kj.asBytes());\n  kv.put(\"qux\", \"321\"_kj.asBytes());\n\n  KJ_EXPECT(sqliteObserver.rowsWritten == 4);\n  KJ_EXPECT(sqliteObserver.rowsRead == 0);\n\n  {\n    bool called = false;\n    KJ_EXPECT(kv.get(\"foo\", [&](kj::ArrayPtr<const byte> value) {\n      KJ_EXPECT(kj::str(value.asChars()) == \"abc\");\n      called = true;\n    }));\n    KJ_EXPECT(called);\n  }\n\n  KJ_EXPECT(sqliteObserver.rowsWritten == 4);\n  KJ_EXPECT(sqliteObserver.rowsRead == 1);\n\n  {\n    bool called = false;\n    KJ_EXPECT(kv.get(\"bar\", [&](kj::ArrayPtr<const byte> value) {\n      KJ_EXPECT(kj::str(value.asChars()) == \"def\");\n      called = true;\n    }));\n    KJ_EXPECT(called);\n  }\n\n  KJ_EXPECT(sqliteObserver.rowsWritten == 4);\n  KJ_EXPECT(sqliteObserver.rowsRead == 2);\n\n  KJ_EXPECT(!kv.get(\"corge\", [&](kj::ArrayPtr<const byte> value) {\n    KJ_FAIL_EXPECT(\"should not call callback when no match\", value.asChars());\n  }));\n\n  KJ_EXPECT(sqliteObserver.rowsWritten == 4);\n  KJ_EXPECT(sqliteObserver.rowsRead == 2);\n\n  auto list = [&](auto&&... params) {\n    kj::Vector<kj::String> results;\n    auto callback = [&](kj::StringPtr key, kj::ArrayPtr<const byte> value) {\n      results.add(kj::str(key, \"=\", value.asChars()));\n    };\n\n    auto n = kv.list(params..., callback);\n    KJ_EXPECT(results.size() == n);\n    return kj::strArray(results, \", \");\n  };\n\n  constexpr auto F = SqliteKv::FORWARD;\n  constexpr auto R = SqliteKv::REVERSE;\n\n  KJ_EXPECT(list(nullptr, kj::none, kj::none, F) == \"bar=def, baz=123, foo=abc, qux=321\");\n  KJ_EXPECT(list(\"cat\"_kj, kj::none, kj::none, F) == \"foo=abc, qux=321\");\n  KJ_EXPECT(list(\"foo\"_kj, kj::none, kj::none, F) == \"foo=abc, qux=321\");\n  KJ_EXPECT(list(\"fop\"_kj, kj::none, kj::none, F) == \"qux=321\");\n  KJ_EXPECT(list(\"foo \"_kj, kj::none, kj::none, F) == \"qux=321\");\n\n  KJ_EXPECT(list(nullptr, \"cat\"_kj, kj::none, F) == \"bar=def, baz=123\");\n  KJ_EXPECT(list(nullptr, \"foo\"_kj, kj::none, F) == \"bar=def, baz=123\");\n  KJ_EXPECT(list(nullptr, \"fop\"_kj, kj::none, F) == \"bar=def, baz=123, foo=abc\");\n\n  KJ_EXPECT(list(nullptr, kj::none, 2, F) == \"bar=def, baz=123\");\n  KJ_EXPECT(list(nullptr, kj::none, 3, F) == \"bar=def, baz=123, foo=abc\");\n  KJ_EXPECT(list(\"baz\"_kj, kj::none, 2, F) == \"baz=123, foo=abc\");\n  KJ_EXPECT(list(nullptr, \"foo\"_kj, 1, F) == \"bar=def\");\n  KJ_EXPECT(list(nullptr, \"foo\"_kj, 2, F) == \"bar=def, baz=123\");\n  KJ_EXPECT(list(nullptr, \"foo\"_kj, 3, F) == \"bar=def, baz=123\");\n\n  KJ_EXPECT(list(nullptr, kj::none, kj::none, R) == \"qux=321, foo=abc, baz=123, bar=def\");\n  KJ_EXPECT(list(\"foo\"_kj, kj::none, kj::none, R) == \"qux=321, foo=abc\");\n  KJ_EXPECT(list(nullptr, \"foo\"_kj, kj::none, R) == \"baz=123, bar=def\");\n  KJ_EXPECT(list(nullptr, kj::none, 2, R) == \"qux=321, foo=abc\");\n  KJ_EXPECT(list(nullptr, \"foo\"_kj, 1, R) == \"baz=123\");\n\n  KJ_EXPECT(kv.delete_(\"baz\"));\n  KJ_EXPECT(!kv.delete_(\"corge\"));\n\n  KJ_EXPECT(list(nullptr, kj::none, kj::none, F) == \"bar=def, foo=abc, qux=321\");\n\n  // Put can overwrite.\n  kv.put(\"foo\", \"hello\"_kj.asBytes());\n  KJ_EXPECT(list(nullptr, kj::none, kj::none, F) == \"bar=def, foo=hello, qux=321\");\n\n  // deleteAll()\n  KJ_EXPECT(kv.deleteAll() == 3);\n  KJ_EXPECT(list(nullptr, kj::none, kj::none, F) == \"\");\n\n  KJ_EXPECT(!kv.get(\"bar\", [&](kj::ArrayPtr<const byte> value) {\n    KJ_FAIL_EXPECT(\"should not call callback when no match\", value.asChars());\n  }));\n\n  kv.put(\"bar\", \"ghi\"_kj.asBytes());\n  kv.put(\"corge\", \"garply\"_kj.asBytes());\n\n  KJ_EXPECT(list(nullptr, kj::none, kj::none, F) == \"bar=ghi, corge=garply\");\n\n  {\n    bool called = false;\n    KJ_EXPECT(kv.get(\"bar\", [&](kj::ArrayPtr<const byte> value) {\n      KJ_EXPECT(kj::str(value.asChars()) == \"ghi\");\n      called = true;\n    }));\n    KJ_EXPECT(called);\n  }\n}\n\nKJ_TEST(\"large key\") {\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n  SqliteKv kv(db);\n\n  // 2MB because we document a 2MB limit for SQLite Durable Objects\n  kj::String closeToLimitString = kj::heapString(2000000);\n  kv.put(closeToLimitString, \"hello\"_kj.asBytes());\n\n  // Actual limit is 2.2MB, so we test more than that to see if it throws\n  kj::String tooBigString = kj::heapString(2400000);\n\n  KJ_EXPECT_THROW_MESSAGE(\n      \"string or blob too big: SQLITE_TOOBIG\", kv.put(tooBigString, \"hello\"_kj.asBytes()));\n}\n\nKJ_TEST(\"SQLite-KV multi-put\") {\n  class TestSqliteObserver: public SqliteObserver {\n   public:\n    void addQueryStats(uint64_t read, uint64_t written) override {\n      rowsRead += read;\n      rowsWritten += written;\n    }\n\n    uint64_t rowsRead = 0;\n    uint64_t rowsWritten = 0;\n  };\n\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  TestSqliteObserver sqliteObserver;\n  SqliteDatabase db(\n      vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY, sqliteObserver);\n  SqliteKv kv(db);\n\n  // Test basic multi-put with a simple struct\n  struct KeyValue {\n    kj::StringPtr key;\n    kj::ArrayPtr<const byte> value;\n  };\n\n  kj::Vector<KeyValue> pairs;\n  pairs.add(KeyValue{\"foo\"_kj, \"abc\"_kj.asBytes()});\n  pairs.add(KeyValue{\"bar\"_kj, \"def\"_kj.asBytes()});\n  pairs.add(KeyValue{\"baz\"_kj, \"123\"_kj.asBytes()});\n\n  kv.put(pairs, {.allowUnconfirmed = false});\n\n  KJ_EXPECT(sqliteObserver.rowsWritten == 3);\n\n  // Verify all values were written correctly\n  bool called = false;\n  KJ_EXPECT(kv.get(\"foo\", [&](kj::ArrayPtr<const byte> value) {\n    KJ_EXPECT(kj::str(value.asChars()) == \"abc\");\n    called = true;\n  }));\n  KJ_EXPECT(called);\n\n  called = false;\n  KJ_EXPECT(kv.get(\"bar\", [&](kj::ArrayPtr<const byte> value) {\n    KJ_EXPECT(kj::str(value.asChars()) == \"def\");\n    called = true;\n  }));\n  KJ_EXPECT(called);\n\n  called = false;\n  KJ_EXPECT(kv.get(\"baz\", [&](kj::ArrayPtr<const byte> value) {\n    KJ_EXPECT(kj::str(value.asChars()) == \"123\");\n    called = true;\n  }));\n  KJ_EXPECT(called);\n\n  // Test multi-put overwrites existing values\n  kj::Vector<KeyValue> pairs2;\n  pairs2.add(KeyValue{\"foo\"_kj, \"xyz\"_kj.asBytes()});\n  pairs2.add(KeyValue{\"bar\"_kj, \"uvw\"_kj.asBytes()});\n\n  kv.put(pairs2, {.allowUnconfirmed = false});\n\n  called = false;\n  KJ_EXPECT(kv.get(\"foo\", [&](kj::ArrayPtr<const byte> value) {\n    KJ_EXPECT(kj::str(value.asChars()) == \"xyz\");\n    called = true;\n  }));\n  KJ_EXPECT(called);\n\n  called = false;\n  KJ_EXPECT(kv.get(\"bar\", [&](kj::ArrayPtr<const byte> value) {\n    KJ_EXPECT(kj::str(value.asChars()) == \"uvw\");\n    called = true;\n  }));\n  KJ_EXPECT(called);\n\n  // Verify other key unchanged\n  called = false;\n  KJ_EXPECT(kv.get(\"baz\", [&](kj::ArrayPtr<const byte> value) {\n    KJ_EXPECT(kj::str(value.asChars()) == \"123\");\n    called = true;\n  }));\n  KJ_EXPECT(called);\n\n  // Test empty multi-put (should succeed)\n  kj::Vector<KeyValue> emptyPairs;\n  kv.put(emptyPairs, {.allowUnconfirmed = false});\n\n  // Verify database unchanged\n  called = false;\n  KJ_EXPECT(kv.get(\"foo\", [&](kj::ArrayPtr<const byte> value) {\n    KJ_EXPECT(kj::str(value.asChars()) == \"xyz\");\n    called = true;\n  }));\n  KJ_EXPECT(called);\n}\n\nKJ_TEST(\"SQLite-KV multi-put rollback on error\") {\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n  SqliteKv kv(db);\n\n  // Pre-populate with some data\n  kv.put(\"existing\", \"value\"_kj.asBytes());\n\n  struct KeyValue {\n    kj::StringPtr key;\n    kj::ArrayPtr<const byte> value;\n  };\n\n  // Create a multi-put that will fail due to a key being too large\n  kj::Vector<KeyValue> pairs;\n  pairs.add(KeyValue{\"key1\"_kj, \"value1\"_kj.asBytes()});\n  pairs.add(KeyValue{\"key2\"_kj, \"value2\"_kj.asBytes()});\n\n  // Add a key that exceeds the limit (2.2MB actual limit)\n  kj::String tooBigString = kj::heapString(2400000);\n  pairs.add(KeyValue{tooBigString, \"value3\"_kj.asBytes()});\n\n  // The multi-put should throw\n  KJ_EXPECT_THROW_MESSAGE(\n      \"string or blob too big: SQLITE_TOOBIG\", kv.put(pairs, {.allowUnconfirmed = false}));\n\n  // Verify that the first two keys were NOT written (transaction rolled back)\n  KJ_EXPECT(!kv.get(\"key1\", [&](kj::ArrayPtr<const byte> value) {\n    KJ_FAIL_EXPECT(\"key1 should not exist after rollback\");\n  }));\n\n  KJ_EXPECT(!kv.get(\"key2\", [&](kj::ArrayPtr<const byte> value) {\n    KJ_FAIL_EXPECT(\"key2 should not exist after rollback\");\n  }));\n\n  // Verify existing data is unchanged\n  bool called = false;\n  KJ_EXPECT(kv.get(\"existing\", [&](kj::ArrayPtr<const byte> value) {\n    KJ_EXPECT(kj::str(value.asChars()) == \"value\");\n    called = true;\n  }));\n  KJ_EXPECT(called);\n}\n\nKJ_TEST(\"SQLite-KV multi-put with allowUnconfirmed\") {\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n  SqliteKv kv(db);\n\n  struct KeyValue {\n    kj::StringPtr key;\n    kj::ArrayPtr<const byte> value;\n  };\n\n  kj::Vector<KeyValue> pairs;\n  pairs.add(KeyValue{\"foo\"_kj, \"abc\"_kj.asBytes()});\n  pairs.add(KeyValue{\"bar\"_kj, \"def\"_kj.asBytes()});\n\n  // Test with allowUnconfirmed = true\n  kv.put(pairs, {.allowUnconfirmed = true});\n\n  // Verify values were written\n  bool called = false;\n  KJ_EXPECT(kv.get(\"foo\", [&](kj::ArrayPtr<const byte> value) {\n    KJ_EXPECT(kj::str(value.asChars()) == \"abc\");\n    called = true;\n  }));\n  KJ_EXPECT(called);\n\n  called = false;\n  KJ_EXPECT(kv.get(\"bar\", [&](kj::ArrayPtr<const byte> value) {\n    KJ_EXPECT(kj::str(value.asChars()) == \"def\");\n    called = true;\n  }));\n  KJ_EXPECT(called);\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/sqlite-kv.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"sqlite-kv.h\"\n\n#include <workerd/jsg/exception.h>\n\n#include <sqlite3.h>\n\nnamespace workerd {\n\nvoid SqliteKvRegulator::onError(kj::Maybe<int> sqliteErrorCode, kj::StringPtr message) const {\n  KJ_IF_SOME(ec, sqliteErrorCode) {\n    switch (ec) {\n      case SQLITE_TOOBIG:\n        // We want to return SQLITE_TOOBIG to the user since it's usually because of user error.\n        JSG_ASSERT(false, Error, message);\n        return;\n      // We don't want to return other errors to the user since they're usually our fault.\n      // In that case we do nothing because the contract of onError is not to handle the error in\n      // its entirety, but instead to optionally handle it, and do nothing otherwise.\n      // When onError does nothing, the code calling into onError is still responsible for\n      // handling the error by other means, usually by throwing a KJ exception itself.\n      default:\n        return;\n    }\n    KJ_UNREACHABLE;\n  }\n}\n\nSqliteKv::SqliteKv(SqliteDatabase& db): ResetListener(db) {\n  if (db.run(\"SELECT name FROM sqlite_master WHERE type='table' AND name='_cf_KV'\").isDone()) {\n    // The _cf_KV table doesn't exist. Defer initialization.\n    state.init<Uninitialized>(Uninitialized{});\n  } else {\n    // The KV table was initialized in the past. We can go ahead and prepare our statements.\n    // (We don't call ensureInitialized() here because the `CREATE TABLE IF NOT EXISTS` query it\n    // executes would be redundant.)\n    tableCreated = true;\n    state.init<Initialized>(db);\n  }\n}\n\nSqliteKv::~SqliteKv() noexcept(false) {\n  cancelCurrentCursor();\n}\n\nvoid SqliteKv::cancelCurrentCursor() {\n  KJ_IF_SOME(c, currentCursor) {\n    c.state = kj::none;\n    c.canceled = true;\n  }\n}\n\nSqliteKv::Initialized& SqliteKv::ensureInitialized(bool allowUnconfirmed) {\n  if (!tableCreated) {\n    db.run(SqliteDatabase::QueryOptions{.regulator = SqliteDatabase::TRUSTED,\n             .allowUnconfirmed = allowUnconfirmed},\n        R\"(\n      CREATE TABLE IF NOT EXISTS _cf_KV (\n        key TEXT PRIMARY KEY,\n        value BLOB\n      ) WITHOUT ROWID;\n    )\");\n\n    tableCreated = true;\n\n    // If we're in a transaction and it gets rolled back, we better mark that the table is actually\n    // not created anymore.\n    db.onRollback([this]() { tableCreated = false; });\n  }\n\n  KJ_SWITCH_ONEOF(state) {\n    KJ_CASE_ONEOF(uninitialized, Uninitialized) {\n      return state.init<Initialized>(db);\n    }\n    KJ_CASE_ONEOF(initialized, Initialized) {\n      return initialized;\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::Own<SqliteKv::ListCursor> SqliteKv::list(\n    KeyPtr begin, kj::Maybe<KeyPtr> end, kj::Maybe<uint> limit, Order order) {\n  if (!tableCreated) return kj::heap<ListCursor>(nullptr);\n  auto& stmts = KJ_UNWRAP_OR(state.tryGet<Initialized>(), return kj::heap<ListCursor>(nullptr));\n\n  if (order == Order::FORWARD) {\n    KJ_IF_SOME(e, end) {\n      KJ_IF_SOME(l, limit) {\n        return kj::heap<ListCursor>(kj::Badge<SqliteKv>(), *this, stmts.stmtListEndLimit, begin, e,\n            static_cast<int64_t>(l));\n      } else {\n        return kj::heap<ListCursor>(kj::Badge<SqliteKv>(), *this, stmts.stmtListEnd, begin, e);\n      }\n    } else {\n      KJ_IF_SOME(l, limit) {\n        return kj::heap<ListCursor>(\n            kj::Badge<SqliteKv>(), *this, stmts.stmtListLimit, begin, static_cast<int64_t>(l));\n      } else {\n        return kj::heap<ListCursor>(kj::Badge<SqliteKv>(), *this, stmts.stmtList, begin);\n      }\n    }\n  } else {\n    KJ_IF_SOME(e, end) {\n      KJ_IF_SOME(l, limit) {\n        return kj::heap<ListCursor>(kj::Badge<SqliteKv>(), *this, stmts.stmtListEndLimitReverse,\n            begin, e, static_cast<int64_t>(l));\n      } else {\n        return kj::heap<ListCursor>(\n            kj::Badge<SqliteKv>(), *this, stmts.stmtListEndReverse, begin, e);\n      }\n    } else {\n      KJ_IF_SOME(l, limit) {\n        return kj::heap<ListCursor>(kj::Badge<SqliteKv>(), *this, stmts.stmtListLimitReverse, begin,\n            static_cast<int64_t>(l));\n      } else {\n        return kj::heap<ListCursor>(kj::Badge<SqliteKv>(), *this, stmts.stmtListReverse, begin);\n      }\n    }\n  }\n}\n\nkj::Maybe<SqliteKv::ListCursor::KeyValuePair> SqliteKv::ListCursor::next() {\n  auto& state = KJ_UNWRAP_OR(this->state, return kj::none);\n  if (first) {\n    first = false;\n  } else {\n    state.query.nextRow();\n  }\n  if (state.query.isDone()) {\n    this->state = kj::none;\n    return kj::none;\n  }\n\n  return KeyValuePair{state.query.getText(0), state.query.getBlob(1)};\n}\n\nvoid SqliteKv::put(KeyPtr key, ValuePtr value) {\n  put(key, value, {});\n}\n\nvoid SqliteKv::put(KeyPtr key, ValuePtr value, WriteOptions options) {\n  ensureInitialized(options.allowUnconfirmed)\n      .stmtPut.run({.allowUnconfirmed = options.allowUnconfirmed}, key, value);\n}\n\nbool SqliteKv::delete_(KeyPtr key) {\n  return delete_(key, {});\n}\n\nbool SqliteKv::delete_(KeyPtr key, WriteOptions options) {\n  auto query = ensureInitialized(options.allowUnconfirmed)\n                   .stmtDelete.run({.allowUnconfirmed = options.allowUnconfirmed}, key);\n  return query.changeCount() > 0;\n}\n\nuint SqliteKv::deleteAll() {\n  // TODO(perf): Consider introducing a compatibility flag that causes deleteAll() to always return\n  //   1. Apps almost certainly don't care about the return value but historically we returned the\n  //   count of keys deleted, so now we're stuck counting the table size for no good reason.\n  uint count = tableCreated ? ensureInitialized(false).stmtCountKeys.run().getInt(0) : 0;\n  db.reset();\n  return count;\n}\n\nvoid SqliteKv::beforeSqliteReset() {\n  // We'll need to recreate the table on the next operation.\n  tableCreated = false;\n}\n\nvoid SqliteKv::rollbackMultiPut(Initialized& stmts, WriteOptions options) {\n  KJ_IF_SOME(e, kj::runCatchingExceptions([&]() {\n    // This should be rare, so we don't prepare a statement for it.\n    stmts.db.run({.regulator = stmts.regulator, .allowUnconfirmed = options.allowUnconfirmed},\n        kj::str(\"ROLLBACK TO _cf_put_multiple_savepoint\"));\n    stmts.stmtMultiPutRelease.run({.allowUnconfirmed = options.allowUnconfirmed});\n  })) {\n    KJ_LOG(WARNING, \"silencing exception encountered while rolling back multi-put\", e);\n  }\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/sqlite-kv.h",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"sqlite.h\"\n\n#include <kj/debug.h>\n#include <kj/exception.h>\n\nnamespace workerd {\n\n// Small class which is used to customize certain aspects of the underlying sql operations\n// In this case we just customize the error reporting to emit JSG user visible errors instead\n// of KJ exceptions which become internal errors.\nclass SqliteKvRegulator: public SqliteDatabase::Regulator {\n  void onError(kj::Maybe<int> sqliteErrorCode, kj::StringPtr message) const override;\n\n  // We bill for KV operations as rows read/written.\n  virtual bool shouldAddQueryStats() const override {\n    return true;\n  }\n};\n\n// Class which implements KV storage on top of SQLite. This is intended to be used for Durable\n// Object storage.\n//\n// The table is named `_cf_KV`. The naming is designed so that if the application is allowed to\n// perform direct SQL queries, we can block it from accessing any table prefixed with `_cf_`.\n// (Ideally this class would allow configuring the table name, but this would require a somewhat\n// obnoxious amount of string allocation.)\nclass SqliteKv: private SqliteDatabase::ResetListener {\n public:\n  explicit SqliteKv(SqliteDatabase& db);\n  ~SqliteKv() noexcept(false);\n\n  using KeyPtr = kj::StringPtr;\n  using ValuePtr = kj::ArrayPtr<const kj::byte>;\n\n  // Search for a match for the given key. Calls the callback function with the result (a ValuePtr)\n  // if found. This is intended to avoid the need to copy the bytes, if the caller would just parse\n  // them and drop them immediately anyway. Returns true if there was a match, false if not.\n  template <typename Func>\n  bool get(KeyPtr key, Func&& callback);\n\n  enum Order { FORWARD, REVERSE };\n\n  // Search for all known keys and values in a range, calling the callback (with KeyPtr and\n  // ValuePtr parameters) for each one seen. `end` and `limit` can be null to request no constraint\n  // be enforced.\n  template <typename Func>\n  uint list(\n      KeyPtr begin, kj::Maybe<KeyPtr> end, kj::Maybe<uint> limit, Order order, Func&& callback);\n\n  // List returning a cursor which can be iterated one at a time.\n  class ListCursor;\n  kj::Own<ListCursor> list(KeyPtr begin, kj::Maybe<KeyPtr> end, kj::Maybe<uint> limit, Order order);\n\n  struct WriteOptions {\n    bool allowUnconfirmed = false;\n  };\n\n  // Store a value into the table.\n  void put(KeyPtr key, ValuePtr value);\n  void put(KeyPtr key, ValuePtr value, WriteOptions options);\n\n  // Atomically store multiple values into the table.\n  //\n  // ArrayOfKeyValuePair should be a type that allows iteration of a struct that has two members,\n  // key and value, that can be coerced into KeyPtr and ValuePtr, respectively.  I'm using a\n  // template so that we don't have to transform (by copy) the values passed in from higher levels\n  // while also preventing this module from taking a dependency on types from higher levels.\n  template <typename ArrayOfKeyValuePair>\n  void put(ArrayOfKeyValuePair& pairs, WriteOptions options);\n\n  // Delete the key and return whether it was matched.\n  bool delete_(KeyPtr key);\n  bool delete_(KeyPtr key, WriteOptions options);\n\n  uint deleteAll();\n\n  // TODO(perf): Should we provide multi-get, multi-put, and multi-delete? It's a bit tricky to\n  //   implement them as single SQL queries, while still using prepared statements. The c-array\n  //   extension might help here, though it can only support arrays of NUL-terminated strings, not\n  //   byte blobs or strings containing NUL bytes.\n\n private:\n  struct Uninitialized {};\n\n  struct Initialized {\n    // This reference is redundant but storing it here makes the prepared statement code below\n    // easier to manage.\n    SqliteDatabase& db;\n\n    SqliteKvRegulator regulator;\n\n    SqliteDatabase::Statement stmtGet = db.prepare(regulator, R\"(\n      SELECT value FROM _cf_KV WHERE key = ?\n    )\");\n    SqliteDatabase::Statement stmtPut = db.prepare(regulator, R\"(\n      INSERT INTO _cf_KV VALUES(?, ?)\n        ON CONFLICT DO UPDATE SET value = excluded.value;\n    )\");\n    SqliteDatabase::Statement stmtDelete = db.prepare(regulator, R\"(\n      DELETE FROM _cf_KV WHERE key = ?\n    )\");\n    SqliteDatabase::Statement stmtList = db.prepare(regulator, R\"(\n      SELECT * FROM _cf_KV\n      WHERE key >= ?\n      ORDER BY key\n    )\");\n    SqliteDatabase::Statement stmtListEnd = db.prepare(regulator, R\"(\n      SELECT * FROM _cf_KV\n      WHERE key >= ? AND key < ?\n      ORDER BY key\n    )\");\n    SqliteDatabase::Statement stmtListLimit = db.prepare(regulator, R\"(\n      SELECT * FROM _cf_KV\n      WHERE key >= ?\n      ORDER BY key\n      LIMIT ?\n    )\");\n    SqliteDatabase::Statement stmtListEndLimit = db.prepare(regulator, R\"(\n      SELECT * FROM _cf_KV\n      WHERE key >= ? AND key < ?\n      ORDER BY key\n      LIMIT ?\n    )\");\n    SqliteDatabase::Statement stmtListReverse = db.prepare(regulator, R\"(\n      SELECT * FROM _cf_KV\n      WHERE key >= ?\n      ORDER BY key DESC\n    )\");\n    SqliteDatabase::Statement stmtListEndReverse = db.prepare(regulator, R\"(\n      SELECT * FROM _cf_KV\n      WHERE key >= ? AND key < ?\n      ORDER BY key DESC\n    )\");\n    SqliteDatabase::Statement stmtListLimitReverse = db.prepare(regulator, R\"(\n      SELECT * FROM _cf_KV\n      WHERE key >= ?\n      ORDER BY key DESC\n      LIMIT ?\n    )\");\n    SqliteDatabase::Statement stmtListEndLimitReverse = db.prepare(regulator, R\"(\n      SELECT * FROM _cf_KV\n      WHERE key >= ? AND key < ?\n      ORDER BY key DESC\n      LIMIT ?\n    )\");\n    SqliteDatabase::Statement stmtCountKeys = db.prepare(regulator, R\"(\n      SELECT count(*) FROM _cf_KV\n    )\");\n    SqliteDatabase::Statement stmtMultiPutSavepoint = db.prepare(regulator, R\"(\n      SAVEPOINT _cf_put_multiple_savepoint\n    )\");\n    SqliteDatabase::Statement stmtMultiPutRelease = db.prepare(regulator, R\"(\n      RELEASE _cf_put_multiple_savepoint\n    )\");\n\n    Initialized(SqliteDatabase& db): db(db) {}\n  };\n\n  kj::OneOf<Uninitialized, Initialized> state;\n\n  // Has the _cf_KV table been created? This is separate from Uninitialized/Initialized since it\n  // has to be repeated after a reset, whereas the statements do not need to be recreated.\n  bool tableCreated = false;\n\n  kj::Maybe<ListCursor&> currentCursor;\n\n  void cancelCurrentCursor();\n\n  Initialized& ensureInitialized(bool allowUnconfirmed);\n  // Make sure the KV table is created and prepared statements are ready. Not called until the\n  // first write.\n\n  void beforeSqliteReset() override;\n\n  // Helper function that rolls back a multi-put statement and swallows any exceptions that may\n  // occur during the rollback.\n  void rollbackMultiPut(Initialized& stmts, WriteOptions options);\n};\n\n// Iterator over list results.\nclass SqliteKv::ListCursor {\n public:\n  template <typename... Params>\n  ListCursor(kj::Badge<SqliteKv>, SqliteKv& parent, Params&&... params) {\n    parent.cancelCurrentCursor();\n    state.emplace(parent, kj::fwd<Params>(params)...);\n    parent.currentCursor = *this;\n  }\n  ListCursor(decltype(nullptr)) {}\n\n  template <typename Func>\n  uint forEach(Func&& callback) {\n    auto& query = KJ_UNWRAP_OR(state, return 0).query;\n    size_t count = 0;\n    while (!query.isDone()) {\n      callback(query.getText(0), query.getBlob(1));\n      query.nextRow();\n      ++count;\n    }\n    return count;\n  };\n\n  struct KeyValuePair {\n    kj::StringPtr key;\n    kj::ArrayPtr<const byte> value;\n  };\n  kj::Maybe<KeyValuePair> next();\n\n  // If true, the cursor was canceled due to a new list() operation starting. Only one list() is\n  // allowed at a time.\n  bool wasCanceled() {\n    return canceled;\n  }\n\n private:\n  struct State {\n    SqliteKv& parent;\n    SqliteDatabase::Query query;\n\n    template <typename... Params>\n    State(SqliteKv& parent, SqliteDatabase::Statement& stmt, Params&&... params)\n        : parent(parent),\n          query(stmt.run(kj::fwd<Params>(params)...)) {}\n    ~State() noexcept(false) {\n      parent.currentCursor = kj::none;\n    }\n  };\n\n  kj::Maybe<State> state;\n\n  // Are we at the beginning of the list?\n  bool first = true;\n\n  bool canceled = false;\n\n  friend class SqliteKv;\n};\n\n// =======================================================================================\n// inline implementation details\n//\n// We define these two methods as templates rather than use kj::Function since they're not too\n// complicated and avoiding the virtual call is nice. Plus in list()'s case, the actual call sites\n// pass constants for `order` so the `order ==` branch can be eliminated.\n\ntemplate <typename Func>\nbool SqliteKv::get(KeyPtr key, Func&& callback) {\n  if (!tableCreated) return false;\n  auto& stmts = KJ_UNWRAP_OR(state.tryGet<Initialized>(), return false);\n\n  auto query = stmts.stmtGet.run(key);\n\n  if (query.isDone()) {\n    return false;\n  } else {\n    callback(query.getBlob(0));\n    return true;\n  }\n}\n\ntemplate <typename Func>\nuint SqliteKv::list(\n    KeyPtr begin, kj::Maybe<KeyPtr> end, kj::Maybe<uint> limit, Order order, Func&& callback) {\n  return list(begin, end, limit, order)->forEach(kj::fwd<Func>(callback));\n}\n\ntemplate <typename ArrayOfKeyValuePair>\nvoid SqliteKv::put(ArrayOfKeyValuePair& pairs, WriteOptions options) {\n  // TODO(cleanup): This code is very similar to DurableObjectStorage::transactionSync.  Perhaps the\n  // general structure can be shared somehow?\n  auto& stmts = ensureInitialized(options.allowUnconfirmed);\n  stmts.stmtMultiPutSavepoint.run({.allowUnconfirmed = options.allowUnconfirmed});\n\n  {\n    // If any of the puts throw an exception, rollback the transaction and re-throw the exception\n    // from the put that failed.\n    KJ_ON_SCOPE_FAILURE(rollbackMultiPut(stmts, options));\n    for (const auto& pair: pairs) {\n      put(pair.key, pair.value, {.allowUnconfirmed = options.allowUnconfirmed});\n    }\n  }\n  stmts.stmtMultiPutRelease.run({.allowUnconfirmed = options.allowUnconfirmed});\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/sqlite-metadata-test.c++",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"sqlite-metadata.h\"\n\n#include <kj/test.h>\n\nnamespace workerd {\nnamespace {\n\nKJ_TEST(\"SQLite-METADATA\") {\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n  SqliteMetadata metadata(db);\n\n  // Initial state has empty alarm\n  KJ_EXPECT(metadata.getAlarm() == kj::none);\n\n  // Can set alarm to an explicit time\n  constexpr kj::Date anAlarmTime1 =\n      kj::UNIX_EPOCH + 1734099316 * kj::SECONDS + 987654321 * kj::NANOSECONDS;\n  metadata.setAlarm(anAlarmTime1, /*allowUnconfirmed=*/false);\n\n  // Can get the set alarm time\n  KJ_EXPECT(metadata.getAlarm() == anAlarmTime1);\n\n  // Can overwrite the alarm time\n  constexpr kj::Date anAlarmTime2 = anAlarmTime1 + 1 * kj::NANOSECONDS;\n  metadata.setAlarm(anAlarmTime2, /*allowUnconfirmed=*/false);\n  KJ_EXPECT(metadata.getAlarm() != anAlarmTime1);\n  KJ_EXPECT(metadata.getAlarm() == anAlarmTime2);\n\n  // Can clear alarm\n  metadata.setAlarm(kj::none, /*allowUnconfirmed=*/false);\n  KJ_EXPECT(metadata.getAlarm() == kj::none);\n\n  // Zero alarm is distinct from unset (probably not important, but just checking)\n  metadata.setAlarm(kj::UNIX_EPOCH, /*allowUnconfirmed=*/false);\n  KJ_EXPECT(metadata.getAlarm() == kj::UNIX_EPOCH);\n\n  // Can recreate table after resetting database\n  metadata.setAlarm(anAlarmTime1, /*allowUnconfirmed=*/false);\n  KJ_EXPECT(metadata.getAlarm() == anAlarmTime1);\n  db.reset();\n  KJ_EXPECT(metadata.getAlarm() == kj::none);\n  metadata.setAlarm(anAlarmTime2, /*allowUnconfirmed=*/false);\n  KJ_EXPECT(KJ_ASSERT_NONNULL(metadata.getAlarm()) == anAlarmTime2);\n\n  // Can invalidate cache after rolling back.\n  metadata.setAlarm(anAlarmTime2, /*allowUnconfirmed=*/false);\n  db.run(\"BEGIN TRANSACTION\");\n  metadata.setAlarm(anAlarmTime1, /*allowUnconfirmed=*/false);\n  KJ_EXPECT(metadata.getAlarm() == anAlarmTime1);\n  db.run(\"ROLLBACK TRANSACTION\");\n  KJ_EXPECT(metadata.getAlarm() == anAlarmTime2);\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/sqlite-metadata.c++",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"sqlite-metadata.h\"\n\n#include <kj/debug.h>\n\nnamespace workerd {\n\nSqliteMetadata::SqliteMetadata(SqliteDatabase& db): ResetListener(db) {\n  auto q = db.run(\"SELECT name FROM sqlite_master WHERE type='table' AND name='_cf_METADATA'\");\n  tableCreated = !q.isDone();\n}\n\nkj::Maybe<kj::Date> SqliteMetadata::getAlarm() {\n  if (cacheState == kj::none) {\n    cacheState = Cache{.alarmTime = getAlarmUncached()};\n  }\n  return KJ_ASSERT_NONNULL(cacheState).alarmTime;\n}\n\nbool SqliteMetadata::setAlarm(kj::Maybe<kj::Date> currentTime, bool allowUnconfirmed) {\n  KJ_IF_SOME(c, cacheState) {\n    if (c.alarmTime == currentTime) {\n      return false;\n    }\n  }\n  setAlarmUncached(currentTime, allowUnconfirmed);\n  db.onRollback([this, oldCacheState = cacheState]() { cacheState = oldCacheState; });\n  cacheState = Cache{.alarmTime = currentTime};\n  return true;\n}\n\nkj::Maybe<kj::Date> SqliteMetadata::getAlarmUncached() {\n  if (!tableCreated) {\n    return kj::none;\n  }\n\n  auto query = ensureInitialized(/*allowUnconfirmed=*/false).stmtGetAlarm.run();\n  if (query.isDone() || query.isNull(0)) {\n    return kj::none;\n  } else {\n    return kj::UNIX_EPOCH + query.getInt64(0) * kj::NANOSECONDS;\n  }\n}\n\nvoid SqliteMetadata::setAlarmUncached(kj::Maybe<kj::Date> currentTime, bool allowUnconfirmed) {\n  KJ_IF_SOME(t, currentTime) {\n    ensureInitialized(allowUnconfirmed)\n        .stmtSetAlarm.run(\n            {.allowUnconfirmed = allowUnconfirmed}, (t - kj::UNIX_EPOCH) / kj::NANOSECONDS);\n  } else {\n    // Our getter code also allows representing an empty alarm value as a\n    // missing row or table, but a null-value row seems efficient and simple.\n    ensureInitialized(allowUnconfirmed)\n        .stmtSetAlarm.run({.allowUnconfirmed = allowUnconfirmed}, nullptr);\n  }\n}\n\nkj::Maybe<uint64_t> SqliteMetadata::getLocalDevelopmentBookmark() {\n  auto query = ensureInitialized(/*allowUnconfirmed=*/false).stmtGetLocalDevelopmentBookmark.run();\n  if (query.isDone() || query.isNull(0)) {\n    return kj::none;\n  } else {\n    auto bookmark = query.getInt64(0);\n    KJ_REQUIRE(bookmark >= 0);\n    return bookmark;\n  }\n}\n\nvoid SqliteMetadata::setLocalDevelopmentBookmark(uint64_t bookmark) {\n  KJ_REQUIRE(bookmark <= static_cast<int64_t>(kj::maxValue));\n  ensureInitialized(/*allowUnconfirmed=*/false)\n      .stmtSetLocalDevelopmentBookmark.run(static_cast<int64_t>(bookmark));\n}\n\nSqliteMetadata::Initialized& SqliteMetadata::ensureInitialized(bool allowUnconfirmed) {\n  if (!tableCreated) {\n    db.run(SqliteDatabase::QueryOptions{.regulator = SqliteDatabase::TRUSTED,\n             .allowUnconfirmed = allowUnconfirmed},\n        R\"(\n      CREATE TABLE IF NOT EXISTS _cf_METADATA (\n        key INTEGER PRIMARY KEY,\n        value BLOB\n      );\n    )\");\n    tableCreated = true;\n    db.onRollback([this]() { tableCreated = false; });\n  }\n\n  KJ_SWITCH_ONEOF(dbState) {\n    KJ_CASE_ONEOF(uninitialized, Uninitialized) {\n      return dbState.init<Initialized>(db);\n    }\n    KJ_CASE_ONEOF(initialized, Initialized) {\n      return initialized;\n    }\n  }\n  KJ_UNREACHABLE;\n}\n\nvoid SqliteMetadata::beforeSqliteReset() {\n  // We'll need to recreate the table on the next operation.\n  tableCreated = false;\n  cacheState = kj::none;\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/sqlite-metadata.h",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include \"sqlite.h\"\n\nnamespace workerd {\n\n// Class which implements a simple metadata kv storage and cache on top of SQLite.  Currently used\n// to store:\n//\n// * Durable Object alarm times (hardcoded as key = 1).\n//\n// * A local development bookmark used to simulate the getCurrentBookmark API used by D1 (hardcoded\n//   as key = 2).  The local development bookmark is not used in production.\n//\n// The table is named `_cf_METADATA`. The naming is designed so that if the application is allowed to\n// perform direct SQL queries, we can block it from accessing any table prefixed with `_cf_`.\nclass SqliteMetadata final: private SqliteDatabase::ResetListener {\n public:\n  explicit SqliteMetadata(SqliteDatabase& db);\n\n  // Return currently set alarm time, or none.\n  kj::Maybe<kj::Date> getAlarm();\n\n  // Sets current alarm time, or none. Returns true if the value changed, false if it was already\n  // set to the same value.\n  bool setAlarm(kj::Maybe<kj::Date> currentTime, bool allowUnconfirmed);\n\n  // Return the current local development bookmark, or none if no bookmark has been set.\n  kj::Maybe<uint64_t> getLocalDevelopmentBookmark();\n\n  // Set the current ersatz bookmark.\n  void setLocalDevelopmentBookmark(uint64_t);\n\n private:\n  struct Uninitialized {};\n  struct Initialized {\n    SqliteDatabase& db;\n\n    SqliteDatabase::Statement stmtGetAlarm = db.prepare(R\"(\n      SELECT value FROM _cf_METADATA WHERE key = 1\n    )\");\n    SqliteDatabase::Statement stmtSetAlarm = db.prepare(R\"(\n      INSERT INTO _cf_METADATA VALUES(1, ?)\n        ON CONFLICT DO UPDATE SET value = excluded.value;\n    )\");\n\n    SqliteDatabase::Statement stmtGetLocalDevelopmentBookmark = db.prepare(R\"(\n      SELECT value FROM _cf_METADATA WHERE key = 2\n    )\");\n    SqliteDatabase::Statement stmtSetLocalDevelopmentBookmark = db.prepare(R\"(\n      INSERT INTO _cf_METADATA VALUES(2, ?)\n        ON CONFLICT DO UPDATE SET value = excluded.value;\n    )\");\n\n    Initialized(SqliteDatabase& db): db(db) {}\n  };\n\n  bool tableCreated;\n  kj::OneOf<Uninitialized, Initialized> dbState = Uninitialized{};\n\n  struct Cache {\n    kj::Maybe<kj::Date> alarmTime;\n  };\n  kj::Maybe<Cache> cacheState;\n\n  kj::Maybe<kj::Date> getAlarmUncached();\n  void setAlarmUncached(kj::Maybe<kj::Date> currentTime, bool allowUnconfirmed);\n\n  Initialized& ensureInitialized(bool allowUnconfirmed);\n  // Make sure the metadata table is created and prepared statements are ready. Not called until the\n  // first write.\n\n  // ResetListener interface:\n  void beforeSqliteReset() override;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/sqlite-test.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"sqlite.h\"\n\n#include <fcntl.h>\n\n#include <kj/refcount.h>\n#include <kj/test.h>\n#include <kj/thread.h>\n\n#include <atomic>\n#include <cerrno>\n#include <cstdint>\n#include <cstdlib>\n\n#if _WIN32\n#include <io.h>\n#else\n#include <unistd.h>\n#endif\n\nnamespace workerd {\nnamespace {\n\n// Initialize the database with some data.\nvoid setupSql(SqliteDatabase& db) {\n  // TODO(sqlite): Do this automatically and don't permit it via run().\n  db.run(\"PRAGMA journal_mode=WAL;\");\n\n  {\n    auto query = db.run(R\"(\n      CREATE TABLE people (\n        id INTEGER PRIMARY KEY,\n        name TEXT NOT NULL,\n        email TEXT NOT NULL UNIQUE\n      );\n\n      INSERT INTO people (id, name, email)\n      VALUES (?, ?, ?),\n            (?, ?, ?);\n    )\",\n        123, \"Bob\"_kj, \"bob@example.com\"_kj, 321, \"Alice\"_kj, \"alice@example.com\"_kj);\n\n    KJ_EXPECT(query.changeCount() == 2);\n  }\n}\n\n// Do some read-only queries on `db` to check that it's in the state that `setupSql()` ought to\n// have left it in.\nvoid checkSql(SqliteDatabase& db) {\n  {\n    auto query = db.run(\"SELECT * FROM people ORDER BY name\");\n\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.columnCount() == 3);\n    KJ_EXPECT(query.getInt(0) == 321);\n    KJ_EXPECT(query.getText(1) == \"Alice\");\n    KJ_EXPECT(query.getText(2) == \"alice@example.com\");\n\n    query.nextRow();\n    KJ_ASSERT(!query.isDone());\n    KJ_EXPECT(query.getInt(0) == 123);\n    KJ_EXPECT(query.getText(1) == \"Bob\");\n    KJ_EXPECT(query.getText(2) == \"bob@example.com\");\n\n    query.nextRow();\n    KJ_EXPECT(query.isDone());\n  }\n\n  {\n    auto query = db.run(\"SELECT * FROM people WHERE people.id = ?\", 123l);\n\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.columnCount() == 3);\n    KJ_EXPECT(query.getInt(0) == 123);\n    KJ_EXPECT(query.getText(1) == \"Bob\");\n    KJ_EXPECT(query.getText(2) == \"bob@example.com\");\n\n    query.nextRow();\n    KJ_EXPECT(query.isDone());\n  }\n\n  {\n    auto query = db.run(\"SELECT * FROM people WHERE people.name = ?\", \"Alice\"_kj);\n\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.columnCount() == 3);\n    KJ_EXPECT(query.getInt(0) == 321);\n    KJ_EXPECT(query.getText(1) == \"Alice\");\n    KJ_EXPECT(query.getText(2) == \"alice@example.com\");\n\n    query.nextRow();\n    KJ_EXPECT(query.isDone());\n  }\n}\n\nKJ_TEST(\"SQLite backed by in-memory directory\") {\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n\n  {\n    SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n    setupSql(db);\n    checkSql(db);\n\n    {\n      auto files = dir->listNames();\n      KJ_ASSERT(files.size() == 2);\n      KJ_EXPECT(files[0] == \"foo\");\n      KJ_EXPECT(files[1] == \"foo-wal\");\n    }\n  }\n\n  {\n    auto files = dir->listNames();\n    KJ_ASSERT(files.size() == 1);\n    KJ_EXPECT(files[0] == \"foo\");\n  }\n\n  // Open it again and make sure the data is still there!\n  {\n    SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::MODIFY);\n\n    checkSql(db);\n  }\n\n  // Check read-only-mode.\n  {\n    SqliteDatabase db(vfs, kj::Path({\"foo\"}));\n    checkSql(db);\n    KJ_EXPECT_THROW_MESSAGE(\"attempt to write a readonly database\",\n        db.run(\"INSERT INTO people (id, name, email) VALUES (?, ?, ?);\", 234, \"Carol\"_kj,\n            \"carol@example.com\"));\n  }\n}\n\nclass TempDirOnDisk {\n public:\n  TempDirOnDisk() {}\n  ~TempDirOnDisk() noexcept(false) {\n    dir = nullptr;\n    disk->getRoot().remove(path);\n  }\n\n  const kj::Directory* operator->() {\n    return dir;\n  }\n  const kj::Directory& operator*() {\n    return *dir;\n  }\n\n private:\n  kj::Own<kj::Filesystem> disk = kj::newDiskFilesystem();\n  kj::Path path = makeTmpPath();\n  kj::Own<const kj::Directory> dir = disk->getRoot().openSubdir(path, kj::WriteMode::MODIFY);\n\n  kj::Path makeTmpPath() {\n    const char* tmpDir = getenv(\"TEST_TMPDIR\");\n    kj::String pathStr =\n        kj::str(tmpDir != nullptr ? tmpDir : \"/var/tmp\", \"/workerd-sqlite-test.XXXXXX\");\n#if _WIN32\n    if (_mktemp(pathStr.begin()) == nullptr) {\n      KJ_FAIL_SYSCALL(\"_mktemp\", errno, pathStr);\n    }\n    auto path = disk->getCurrentPath().evalNative(pathStr);\n    disk->getRoot().openSubdir(\n        path, kj::WriteMode::CREATE | kj::WriteMode::MODIFY | kj::WriteMode::CREATE_PARENT);\n    return path;\n#else\n    if (mkdtemp(pathStr.begin()) == nullptr) {\n      KJ_FAIL_SYSCALL(\"mkdtemp\", errno, pathStr);\n    }\n    return disk->getCurrentPath().evalNative(pathStr);\n#endif\n  }\n};\n\nKJ_TEST(\"SQLite backed by real disk\") {\n  // Well, I made it possible to use an in-memory directory so that unit tests wouldn't have to\n  // use real disk. But now I have to test that it does actually work on real disk. So here we are,\n  // in a unit test, using real disk.\n\n  TempDirOnDisk dir;\n  SqliteDatabase::Vfs vfs(*dir);\n\n  {\n    SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n    setupSql(db);\n    checkSql(db);\n\n    {\n      auto files = dir->listNames();\n      KJ_ASSERT(files.size() == 3);\n      KJ_EXPECT(files[0] == \"foo\");\n      KJ_EXPECT(files[1] == \"foo-shm\");\n      KJ_EXPECT(files[2] == \"foo-wal\");\n    }\n  }\n\n  {\n    auto files = dir->listNames();\n    KJ_ASSERT(files.size() == 1);\n    KJ_EXPECT(files[0] == \"foo\");\n  }\n\n  // Open it again and make sure the data is still there!\n  {\n    SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::MODIFY);\n\n    checkSql(db);\n  }\n\n  // Check read-only-mode.\n  {\n    SqliteDatabase db(vfs, kj::Path({\"foo\"}));\n\n    checkSql(db);\n    KJ_EXPECT_THROW_MESSAGE(\"attempt to write a readonly database\",\n        db.run(\"INSERT INTO people (id, name, email) VALUES (?, ?, ?);\", 234, \"Carol\"_kj,\n            \"carol@example.com\"));\n  }\n}\n\n// Tests that a read-only database client picks up changes made to the database by a read/write\n// client.\nvoid doReadOnlyUpdateTest(const kj::Directory& dir) {\n  SqliteDatabase::Vfs vfs(dir);\n\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  setupSql(db);\n  checkSql(db);\n\n  SqliteDatabase rodb(vfs, kj::Path({\"foo\"}));\n  checkSql(rodb);\n\n  uint64_t startWalSize = 0;\n  {\n    auto file = KJ_ASSERT_NONNULL(dir.tryOpenFile(kj::Path({\"foo-wal\"})));\n    startWalSize = file->stat().size;\n  }\n\n  db.run(\"INSERT INTO people (id, name, email) VALUES (?, ?, ?);\", 234, \"Carol\"_kj,\n      \"carol@example.com\");\n\n  {\n    // Make sure there's we added some WAL, since that's where the read-only database will have to\n    // read new rows from.\n    auto file = KJ_ASSERT_NONNULL(dir.tryOpenFile(kj::Path({\"foo-wal\"})));\n    KJ_EXPECT(file->stat().size > startWalSize);\n  }\n\n  {\n    auto query = db.run(\"SELECT COUNT(*) FROM people\");\n    KJ_EXPECT(query.getInt(0) == 3);\n  }\n\n  {\n    auto query = rodb.run(\"SELECT COUNT(*) FROM people\");\n    KJ_EXPECT(query.getInt(0) == 3);\n  }\n}\n\nKJ_TEST(\"Read-only database picks up on changes from mutable database (in-memory)\") {\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  doReadOnlyUpdateTest(*dir);\n}\n\nKJ_TEST(\"Read-only database picks up on changes from mutable database (on-disk)\") {\n  TempDirOnDisk dir;\n  doReadOnlyUpdateTest(*dir);\n}\n\nKJ_TEST(\"In-memory read-only crash regression\") {\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n\n  {\n    SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n    setupSql(db);\n    checkSql(db);\n  }\n\n  // When using the in-memory file system, if we first create a read-only database\n  kj::Maybe<SqliteDatabase> rodb;\n  rodb.emplace(vfs, kj::Path({\"foo\"}));\n  checkSql(KJ_ASSERT_NONNULL(rodb));\n\n  // then create a read/write database\n  kj::Maybe<SqliteDatabase> db;\n  db.emplace(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n  checkSql(KJ_ASSERT_NONNULL(db));\n\n  // then write into the read/write database:\n  KJ_ASSERT_NONNULL(db).run(\"INSERT INTO people (id, name, email) VALUES (?, ?, ?);\", 234,\n      \"Carol\"_kj, \"carol@example.com\");\n\n  // we can destroy the read/write database with no problems,\n  db = kj::none;\n\n  // but we would crash when destroying the read-only database:\n  rodb = kj::none;\n}\n\n// Tests that concurrent database clients don't clobber each other. This verifies that the\n// LockManager interface is able to protect concurrent access and that our default implementation\n// works.\nvoid doLockTest(bool walMode) {\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  if (walMode) {\n    db.run(\"PRAGMA journal_mode=WAL;\");\n  }\n\n  db.run(R\"(\n    CREATE TABLE foo (\n      id INTEGER PRIMARY KEY,\n      counter INTEGER\n    );\n\n    INSERT INTO foo VALUES (0, 1)\n  )\");\n\n  static constexpr char GET_COUNT[] = \"SELECT counter FROM foo WHERE id = 0\";\n  static constexpr char INCREMENT[] = \"UPDATE foo SET counter = counter + 1 WHERE id = 0\";\n\n  KJ_EXPECT(db.run(GET_COUNT).getInt(0) == 1);\n\n  // Concurrent write allowed, as long as we're not writing at the same time.\n  // Deliberately not assigning this to a variable: We want to create a thread and join it\n  // immediately.\n  // NOLINTNEXTLINE(bugprone-unused-raii)\n  kj::Thread([&vfs = vfs]() noexcept {\n    SqliteDatabase db2(vfs, kj::Path({\"foo\"}), kj::WriteMode::MODIFY);\n    KJ_EXPECT(db2.run(GET_COUNT).getInt(0) == 1);\n    db2.run(INCREMENT);\n    KJ_EXPECT(db2.run(GET_COUNT).getInt(0) == 2);\n  });\n\n  KJ_EXPECT(db.run(GET_COUNT).getInt(0) == 2);\n\n  std::atomic<bool> stop = false;\n  std::atomic<uint> counter = 2;\n\n  {\n    // Arrange for two threads to increment in a loop simultaneously. Eventually one will fail with\n    // a conflict.\n    kj::Thread thread([&vfs = vfs, &stop, &counter]() noexcept {\n      KJ_DEFER(stop.store(true, std::memory_order_relaxed););\n      SqliteDatabase db2(vfs, kj::Path({\"foo\"}), kj::WriteMode::MODIFY);\n      while (!stop.load(std::memory_order_relaxed)) {\n        KJ_IF_SOME(e, kj::runCatchingExceptions([&]() {\n          db2.run(INCREMENT);\n          counter.fetch_add(1, std::memory_order_relaxed);\n        })) {\n          KJ_EXPECT(e.getDescription().contains(\"database is locked\"), e);\n          break;\n        }\n      }\n    });\n\n    {\n      KJ_DEFER(stop.store(true, std::memory_order_relaxed););\n\n      while (!stop.load(std::memory_order_relaxed)) {\n        KJ_IF_SOME(e, kj::runCatchingExceptions([&]() {\n          db.run(INCREMENT);\n          counter.fetch_add(1, std::memory_order_relaxed);\n        })) {\n          KJ_EXPECT(e.getDescription().contains(\"database is locked\"), e);\n          break;\n        }\n      }\n    }\n  }\n\n  // The final value should be consistent with the number of increments that succeeded.\n  KJ_EXPECT(db.run(GET_COUNT).getInt(0) == counter.load(std::memory_order_relaxed));\n}\n\nKJ_TEST(\"SQLite locks: rollback journal mode\") {\n  doLockTest(false);\n}\n\nKJ_TEST(\"SQLite locks: WAL mode\") {\n  doLockTest(true);\n}\n\nKJ_TEST(\"SQLite Regulator\") {\n  TempDirOnDisk dir;\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  class RegulatorImpl: public SqliteDatabase::Regulator {\n   public:\n    RegulatorImpl(kj::StringPtr blocked): blocked(blocked) {}\n\n    bool isAllowedName(kj::StringPtr name) const override {\n      if (alwaysFail) return false;\n      return name != blocked;\n    }\n\n    bool alwaysFail = false;\n\n   private:\n    kj::StringPtr blocked;\n  };\n\n  db.run(R\"(\n    CREATE TABLE foo(value INTEGER);\n    CREATE TABLE bar(value INTEGER);\n    INSERT INTO foo VALUES (123);\n    INSERT INTO bar VALUES (456);\n  )\");\n\n  RegulatorImpl noFoo(\"foo\");\n  RegulatorImpl noBar(\"bar\");\n\n  // We can prepare and run statements that comply with the regulator.\n  auto getFoo = db.prepare(noBar, \"SELECT value FROM foo\");\n  auto getBar = db.prepare(noFoo, \"SELECT value FROM bar\");\n\n  KJ_EXPECT(getFoo.run().getInt(0) == 123);\n  KJ_EXPECT(getBar.run().getInt(0) == 456);\n\n  // Trying to prepare a statement that violates the regulator fails.\n  KJ_EXPECT_THROW_MESSAGE(\n      \"access to foo.value is prohibited\", db.prepare(noFoo, \"SELECT value FROM foo\"));\n\n  // If we create a new table, all statements must be re-prepared, which re-runs the regulator.\n  // Make sure that works.\n  db.run(\"CREATE TABLE baz(value INTEGER)\");\n\n  KJ_EXPECT(getFoo.run().getInt(0) == 123);\n\n  // Let's screw with SQLite and make the regulator fail on re-run to see what happens.\n  noFoo.alwaysFail = true;\n  KJ_EXPECT_THROW_MESSAGE(\n      \"access to bar.value is prohibited\", KJ_EXPECT(getBar.run().getInt(0) == 456));\n}\n\nKJ_TEST(\"SQLite onWrite callback\") {\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  bool sawWrite = false;\n  db.onWrite([&](bool allowUnconfirmed) { sawWrite = true; });\n\n  setupSql(db);\n  KJ_EXPECT(sawWrite);\n  sawWrite = false;\n\n  checkSql(db);\n  KJ_EXPECT(!sawWrite);  // checkSql() only does reads\n\n  // Test for bug where the write callback would only be called for the last statement in a\n  // multi-statement execution.\n  auto q = db.run(R\"(\n    INSERT INTO people (id, name, email) VALUES (12321, \"Eve\", \"eve@example.com\");\n    SELECT COUNT(*) FROM people;\n  )\");\n  KJ_EXPECT(q.getInt(0) == 3);\n  KJ_EXPECT(sawWrite);\n}\n\nstruct RowCounts {\n  uint64_t found;\n  uint64_t read;\n  uint64_t written;\n};\n\ntemplate <typename... Params>\nRowCounts countRowsTouched(SqliteDatabase& db,\n    const SqliteDatabase::Regulator& regulator,\n    kj::StringPtr sqlCode,\n    Params... bindParams) {\n  uint64_t rowsFound = 0;\n\n  // Runs a query; retrieves and discards all the data.\n  auto query = db.run({.regulator = regulator}, sqlCode, bindParams...);\n  while (!query.isDone()) {\n    rowsFound++;\n    query.nextRow();\n  }\n\n  return {.found = rowsFound, .read = query.getRowsRead(), .written = query.getRowsWritten()};\n}\n\ntemplate <typename... Params>\nRowCounts countRowsTouched(SqliteDatabase& db, kj::StringPtr sqlCode, Params... bindParams) {\n  return countRowsTouched(db, SqliteDatabase::TRUSTED, sqlCode, kj::fwd<Params>(bindParams)...);\n}\n\nKJ_TEST(\"SQLite read row counters (basic)\") {\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  db.run(R\"(\n    CREATE TABLE things (\n      id INTEGER PRIMARY KEY,\n      unindexed_int INTEGER,\n      value TEXT\n    );\n  )\");\n\n  constexpr int dbRowCount = 1000;\n  auto insertStmt = db.prepare(\"INSERT INTO things (id, unindexed_int, value) VALUES (?, ?, ?)\");\n  for (int i = 0; i < dbRowCount; i++) {\n    auto query = insertStmt.run(i, i * 1000, kj::str(\"value\", i));\n    KJ_EXPECT(query.getRowsRead() == 1);\n    KJ_EXPECT(query.getRowsWritten() == 1);\n  }\n\n  // Sanity check that the inserts worked.\n  {\n    auto getCount = db.prepare(\"SELECT COUNT(*) FROM things\");\n    KJ_EXPECT(getCount.run().getInt(0) == dbRowCount);\n  }\n\n  // Selecting all the rows reads all the rows.\n  {\n    RowCounts stats = countRowsTouched(db, \"SELECT * FROM things\");\n    KJ_EXPECT(stats.found == dbRowCount);\n    KJ_EXPECT(stats.read == dbRowCount);\n    KJ_EXPECT(stats.written == 0);\n  }\n\n  // Selecting one row using an index reads one row.\n  {\n    RowCounts stats = countRowsTouched(db, \"SELECT * FROM things WHERE id=?\", 5);\n    KJ_EXPECT(stats.found == 1);\n    KJ_EXPECT(stats.read == 1);\n    KJ_EXPECT(stats.written == 0);\n  }\n\n  // Selecting one row using an index reads one row, even if that row is in the middle of the table.\n  {\n    RowCounts stats = countRowsTouched(db, \"SELECT * FROM things WHERE id=?\", dbRowCount / 2);\n    KJ_EXPECT(stats.found == 1);\n    KJ_EXPECT(stats.read == 1);\n    KJ_EXPECT(stats.written == 0);\n  }\n\n  // Selecting a row by an unindexed value reads the whole table.\n  {\n    RowCounts stats = countRowsTouched(db, \"SELECT * FROM things WHERE unindexed_int = ?\", 5000);\n    KJ_EXPECT(stats.found == 1);\n    KJ_EXPECT(stats.read == dbRowCount);\n    KJ_EXPECT(stats.written == 0);\n  }\n\n  // Selecting an unindexed aggregate scans all the rows, which counts as reading them.\n  {\n    RowCounts stats = countRowsTouched(db, \"SELECT MAX(unindexed_int) FROM things\");\n    KJ_EXPECT(stats.found == 1);\n    KJ_EXPECT(stats.read == dbRowCount);\n    KJ_EXPECT(stats.written == 0);\n  }\n\n  // Selecting an indexed aggregate can use the index, so it only reads the row it found.\n  {\n    RowCounts stats = countRowsTouched(db, \"SELECT MIN(id) FROM things\");\n    KJ_EXPECT(stats.found == 1);\n    KJ_EXPECT(stats.read == 1);\n    KJ_EXPECT(stats.written == 0);\n  }\n\n  // Selecting with a limit only reads the returned rows.\n  {\n    RowCounts stats = countRowsTouched(db, \"SELECT * FROM things LIMIT 5\");\n    KJ_EXPECT(stats.found == 5);\n    KJ_EXPECT(stats.read == 5);\n    KJ_EXPECT(stats.written == 0);\n  }\n}\n\nKJ_TEST(\"SQLite write row counters (basic)\") {\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  db.run(R\"(\n    CREATE TABLE things (\n      id INTEGER PRIMARY KEY\n    );\n  )\");\n\n  db.run(R\"(\n    CREATE TABLE unindexed_things (\n      id INTEGER\n    );\n  )\");\n\n  // Inserting a row counts as one row written.\n  {\n    RowCounts stats = countRowsTouched(db, \"INSERT INTO unindexed_things (id) VALUES (?)\", 1);\n    KJ_EXPECT(stats.read == 0);\n    KJ_EXPECT(stats.written == 1);\n  }\n\n  // Inserting a row into a table with a primary key will also do a read (to ensure there's no\n  // duplicate PK).\n  {\n    RowCounts stats = countRowsTouched(db, \"INSERT INTO things (id) VALUES (?)\", 1);\n    KJ_EXPECT(stats.read == 1);\n    KJ_EXPECT(stats.written == 1);\n  }\n\n  // Deleting a row counts as a write.\n  {\n    RowCounts stats = countRowsTouched(db, \"INSERT INTO things (id) VALUES (?)\", 123);\n    KJ_EXPECT(stats.written == 1);\n\n    stats = countRowsTouched(db, \"DELETE FROM things WHERE id=?\", 123);\n    KJ_EXPECT(stats.read == 1);\n    KJ_EXPECT(stats.written == 1);\n  }\n\n  // Deleting nothing is not a write.\n  {\n    RowCounts stats = countRowsTouched(db, \"DELETE FROM things WHERE id=?\", 998877112233);\n    KJ_EXPECT(stats.written == 0);\n  }\n\n  // Inserting many things is many writes.\n  {\n    db.run(\"DELETE FROM things\");\n    db.run(\"INSERT INTO things (id) VALUES (1)\");\n    db.run(\"INSERT INTO things (id) VALUES (3)\");\n    db.run(\"INSERT INTO things (id) VALUES (5)\");\n\n    RowCounts stats =\n        countRowsTouched(db, \"INSERT INTO unindexed_things (id) SELECT id FROM things\");\n    KJ_EXPECT(stats.read == 3);\n    KJ_EXPECT(stats.written == 3);\n  }\n\n  // Each updated row is a write.\n  {\n    db.run(\"DELETE FROM unindexed_things\");\n    db.run(\"INSERT INTO unindexed_things (id) VALUES (1)\");\n    db.run(\"INSERT INTO unindexed_things (id) VALUES (2)\");\n    db.run(\"INSERT INTO unindexed_things (id) VALUES (3)\");\n    db.run(\"INSERT INTO unindexed_things (id) VALUES (4)\");\n\n    RowCounts stats =\n        countRowsTouched(db, \"UPDATE unindexed_things SET id = id * 10 WHERE id >= 3\");\n    KJ_EXPECT(stats.written == 2);\n  }\n\n  // Same as above, but with an index.\n  {\n    db.run(\"DELETE FROM things\");\n    db.run(\"INSERT INTO things (id) VALUES (1)\");\n    db.run(\"INSERT INTO things (id) VALUES (2)\");\n    db.run(\"INSERT INTO things (id) VALUES (3)\");\n    db.run(\"INSERT INTO things (id) VALUES (4)\");\n\n    RowCounts stats = countRowsTouched(db, \"UPDATE things SET id = id * 10 WHERE id >= 3\");\n    KJ_EXPECT(stats.read >= 4);  // At least one read per updated row\n    KJ_EXPECT(stats.written == 2);\n  }\n}\n\nKJ_TEST(\"SQLite read/write row counters (large row insert)\") {\n  // This is used to verify reading/writing a large row (bigger than the size of one page in sqlite)\n  // results only in 1 read/row count as returned by the DB\n\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  db.run(\"CREATE TABLE large_things (id INTEGER PRIMARY KEY, large_value TEXT)\");\n\n  // SQLite's default page size is 4096 bytes\n  // So create a string significantly larger than that\n  KJ_EXPECT(db.run(\"PRAGMA page_size\").getInt(0) == 4096);\n  kj::String largeValue = kj::str(kj::repeat('A', 100000));\n\n  // Insert the large row\n  RowCounts insertStats = countRowsTouched(\n      db, \"INSERT INTO large_things (id, large_value) VALUES (?, ?)\", 1, kj::mv(largeValue));\n\n  KJ_EXPECT(insertStats.found == 0);\n  KJ_EXPECT(insertStats.read == 1);\n  KJ_EXPECT(insertStats.written == 1);\n\n  // Verify the insert\n  auto verifyStmt = db.prepare(\"SELECT COUNT(*) FROM large_things\");\n  KJ_EXPECT(verifyStmt.run().getInt(0) == 1);\n\n  // Read the large row\n  RowCounts readStats = countRowsTouched(db, \"SELECT * FROM large_things WHERE id = ?\", 1);\n  KJ_EXPECT(readStats.found == 1);\n  KJ_EXPECT(readStats.read == 1);\n  KJ_EXPECT(readStats.written == 0);\n}\n\nKJ_TEST(\"SQLite row counters with triggers\") {\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  class RegulatorImpl: public SqliteDatabase::Regulator {\n   public:\n    RegulatorImpl() = default;\n\n    bool isAllowedTrigger(kj::StringPtr name) const override {\n      // SqliteDatabase::TRUSTED doesn't let us use triggers at all.\n      return true;\n    }\n  };\n\n  RegulatorImpl regulator;\n\n  db.run(R\"(\n    CREATE TABLE things (\n      id INTEGER PRIMARY KEY\n    );\n\n    CREATE TABLE log (\n      id INTEGER,\n      verb TEXT\n    );\n\n    CREATE TRIGGER log_inserts AFTER INSERT ON things\n    BEGIN\n      insert into log (id, verb) VALUES (NEW.id, \"INSERT\");\n    END;\n\n    CREATE TRIGGER log_deletes AFTER DELETE ON things\n    BEGIN\n      insert into log (id, verb) VALUES (OLD.id, \"DELETE\");\n    END;\n  )\");\n\n  // Each insert incurs two writes: one for the row in `things` and one for the row in `log`.\n  {\n    RowCounts stats = countRowsTouched(db, regulator, \"INSERT INTO things (id) VALUES (1)\");\n    KJ_EXPECT(stats.written == 2);\n  }\n\n  // A deletion incurs two writes: one for the row and one for the log.\n  {\n    db.run({.regulator = regulator}, \"DELETE FROM things\");\n    db.run({.regulator = regulator}, \"INSERT INTO things (id) VALUES (1)\");\n    db.run({.regulator = regulator}, \"INSERT INTO things (id) VALUES (2)\");\n    db.run({.regulator = regulator}, \"INSERT INTO things (id) VALUES (3)\");\n\n    RowCounts stats = countRowsTouched(db, regulator, \"DELETE FROM things\");\n    KJ_EXPECT(stats.written == 6);\n  }\n}\n\nKJ_TEST(\"DELETE with LIMIT\") {\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  db.run(R\"(\n    CREATE TABLE things (\n      id INTEGER PRIMARY KEY\n    );\n  )\");\n\n  db.run(R\"(INSERT INTO things (id) VALUES (1))\");\n  db.run(R\"(INSERT INTO things (id) VALUES (2))\");\n  db.run(R\"(INSERT INTO things (id) VALUES (3))\");\n  db.run(R\"(INSERT INTO things (id) VALUES (4))\");\n  db.run(R\"(INSERT INTO things (id) VALUES (5))\");\n  db.run(R\"(DELETE FROM things LIMIT 2)\");\n  auto q = db.run(R\"(SELECT COUNT(*) FROM things;)\");\n  KJ_EXPECT(q.getInt(0) == 3);\n}\n\nKJ_TEST(\"reset database\") {\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  db.run(\"PRAGMA journal_mode=WAL;\");\n\n  db.run(\"CREATE TABLE things (id INTEGER PRIMARY KEY)\");\n\n  db.run(\"INSERT INTO things VALUES (123)\");\n  db.run(\"INSERT INTO things VALUES (321)\");\n\n  auto stmt = db.prepare(\"SELECT * FROM things\");\n\n  auto query = stmt.run();\n  KJ_ASSERT(!query.isDone());\n  KJ_EXPECT(query.getInt(0) == 123);\n\n  db.reset();\n  db.run(\"PRAGMA journal_mode=WAL;\");\n\n  // The query was canceled.\n  KJ_EXPECT_THROW_MESSAGE(\"query canceled because reset()\", query.nextRow());\n  KJ_EXPECT_THROW_MESSAGE(\"query canceled because reset()\", query.getInt(0));\n\n  // The statement doesn't work because the table is gone.\n  KJ_EXPECT_THROW_MESSAGE(\"no such table: things: SQLITE_ERROR\", stmt.run());\n\n  // But we can recreate it.\n  db.run(\"CREATE TABLE things (id INTEGER PRIMARY KEY)\");\n  db.run(\"INSERT INTO things VALUES (456)\");\n\n  // Now the statement works.\n  {\n    auto q2 = stmt.run();\n    KJ_ASSERT(!q2.isDone());\n    KJ_EXPECT(q2.getInt(0) == 456);\n    q2.nextRow();\n    KJ_EXPECT(q2.isDone());\n  }\n}\n\nKJ_TEST(\"SQLite observer addQueryStats\") {\n  class TestSqliteObserver: public SqliteObserver {\n   public:\n    void addQueryStats(uint64_t read, uint64_t written) override {\n      rowsRead += read;\n      rowsWritten += written;\n    }\n\n    uint64_t rowsRead = 0;\n    uint64_t rowsWritten = 0;\n  };\n\n  class TestQueryStatsRegulator: public SqliteDatabase::Regulator {\n   public:\n    bool shouldAddQueryStats() const override {\n      return true;\n    }\n  };\n\n  TempDirOnDisk dir;\n  SqliteDatabase::Vfs vfs(*dir);\n  TestSqliteObserver sqliteObserver = TestSqliteObserver();\n  TestQueryStatsRegulator regulator;\n  SqliteDatabase db(\n      vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY, sqliteObserver);\n\n  db.run(R\"(\n    CREATE TABLE things (\n      id INTEGER PRIMARY KEY\n    );\n  )\");\n\n  // There are some rows read and written when we create the db, we offset this in the test\n  int rowsReadBefore = sqliteObserver.rowsRead;\n  int rowsWrittenBefore = sqliteObserver.rowsWritten;\n  constexpr int dbRowCount = 3;\n  {\n    db.run({.regulator = regulator}, \"INSERT INTO things (id) VALUES (10)\");\n    db.run({.regulator = regulator}, \"INSERT INTO things (id) VALUES (11)\");\n    db.run({.regulator = regulator}, \"INSERT INTO things (id) VALUES (12)\");\n  }\n  KJ_EXPECT(sqliteObserver.rowsRead - rowsReadBefore == dbRowCount);\n  KJ_EXPECT(sqliteObserver.rowsWritten - rowsWrittenBefore == dbRowCount);\n\n  rowsReadBefore = sqliteObserver.rowsRead;\n  rowsWrittenBefore = sqliteObserver.rowsWritten;\n  {\n    auto getCount = db.prepare(regulator, \"SELECT COUNT(*) FROM things\");\n    KJ_EXPECT(getCount.run().getInt(0) == dbRowCount);\n  }\n  KJ_EXPECT(sqliteObserver.rowsRead - rowsReadBefore == dbRowCount);\n  KJ_EXPECT(sqliteObserver.rowsWritten - rowsWrittenBefore == 0);\n\n  // Verify if addQueryStats works correctly when we call query.nextRow()\n  rowsReadBefore = sqliteObserver.rowsRead;\n  rowsWrittenBefore = sqliteObserver.rowsWritten;\n  {\n    auto stmt = db.prepare(regulator, \"SELECT * FROM things\");\n    auto query = stmt.run();\n    KJ_ASSERT(!query.isDone());\n    while (!query.isDone()) {\n      query.nextRow();\n    }\n  }\n  KJ_EXPECT(sqliteObserver.rowsRead - rowsReadBefore == dbRowCount);\n  KJ_EXPECT(sqliteObserver.rowsWritten - rowsWrittenBefore == 0);\n\n  // Verify system queries don't affect stats.\n  rowsReadBefore = sqliteObserver.rowsRead;\n  rowsWrittenBefore = sqliteObserver.rowsWritten;\n  db.run(\"INSERT INTO things (id) VALUES (13)\");\n  {\n    auto query = db.run(\"SELECT * FROM things\");\n    while (!query.isDone()) {\n      query.nextRow();\n    }\n  }\n  KJ_EXPECT(sqliteObserver.rowsRead == rowsReadBefore);\n  KJ_EXPECT(sqliteObserver.rowsWritten == rowsWrittenBefore);\n\n  // Verify addQueryStats works correctly when db is reset\n  rowsReadBefore = sqliteObserver.rowsRead;\n  rowsWrittenBefore = sqliteObserver.rowsWritten;\n  {\n    auto query = db.run({.regulator = regulator}, \"INSERT INTO things (id) VALUES (100)\");\n    db.reset();\n  }\n  KJ_EXPECT(sqliteObserver.rowsRead - rowsReadBefore == 1);\n  KJ_EXPECT(sqliteObserver.rowsWritten - rowsWrittenBefore == 1);\n}\n\nKJ_TEST(\"SQLite observer reportQueryEvent\") {\n  class TestSqliteObserver: public SqliteObserver {\n   public:\n    int capturedEvents = 0;\n\n    void reportQueryEvent(kj::Maybe<kj::String> queryStatement,\n        uint64_t queryRowsRead,\n        uint64_t queryRowsWritten,\n        kj::Duration,\n        uint64_t dbWalBytesWritten,\n        int queryError,\n        bool isInternalQuery,\n        kj::Maybe<kj::String> queryErrorDescription) override {\n      KJ_IF_SOME(err, queryErrorDescription) {\n        KJ_ASSERT(err.contains(\"query canceled because reset()\"));\n      }\n      capturedEvents++;\n    }\n  };\n\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  TestSqliteObserver sqliteObserver;\n  SqliteDatabase db(\n      vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY, sqliteObserver);\n\n  db.run(\"PRAGMA journal_mode=WAL;\");\n\n  db.run(R\"(\n      CREATE TABLE people (\n        id INTEGER PRIMARY KEY,\n        name TEXT NOT NULL,\n        email TEXT NOT NULL UNIQUE\n      );\n\n      INSERT INTO people (id, name, email)\n      VALUES (?, ?, ?),\n             (?, ?, ?);\n    )\",\n      123, \"Bob\"_kj, \"bob@example.com\"_kj, 321, \"Alice\"_kj, \"alice@example.com\"_kj);\n\n  {\n    auto stmt = db.prepare(\"SELECT * FROM people\");\n    auto query = stmt.run();\n  }\n  {\n    // Expect 4 events so far: PRAGMA, CREATE, INSERT, SELECT.\n    KJ_ASSERT(sqliteObserver.capturedEvents == 4);\n\n    // SELECT #2 (canceled due to reset later)\n    auto stmt = db.prepare(\"SELECT * FROM people\");\n    auto query = stmt.run();\n\n    KJ_ASSERT(!query.isDone());\n    KJ_EXPECT(query.getInt(0) == 123);\n\n    db.reset();\n\n    db.run(\"PRAGMA journal_mode=WAL;\");\n\n    KJ_EXPECT_THROW_MESSAGE(\"query canceled because reset()\", query.nextRow());\n    KJ_EXPECT_THROW_MESSAGE(\"query canceled because reset()\", query.getInt(0));\n\n    // 1 more event: PRAGMA\n    KJ_ASSERT(sqliteObserver.capturedEvents == 5);\n  }\n\n  // Cancelled SELECT #2 emiited with an errorDesc after query goes out of scope\n  KJ_ASSERT(sqliteObserver.capturedEvents == 6);\n}\n\nKJ_TEST(\"SQLite failed statement reset\") {\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  db.run(R\"(\n    CREATE TABLE things (\n      id INTEGER PRIMARY KEY\n    );\n  )\");\n\n  auto stmt = db.prepare(\"INSERT INTO things VALUES (?)\");\n\n  // Run the statement a couple times.\n  stmt.run(1);\n  stmt.run(2);\n\n  // Now run it with a duplicate value, should fail.\n  KJ_EXPECT_THROW_MESSAGE(\"UNIQUE constraint failed: things.id\", stmt.run(1));\n\n  // The statement shouldn't be left broken. Run it again with a non-duplicate.\n  stmt.run(3);\n\n  // Same as above but with ValuePtrs, since these use a different path.\n  using ValuePtr = SqliteDatabase::Query::ValuePtr;\n  ValuePtr value = static_cast<int64_t>(1);\n  KJ_EXPECT_THROW_MESSAGE(\n      \"UNIQUE constraint failed: things.id\", stmt.run(kj::arrayPtr<const ValuePtr>(value)));\n  value = static_cast<int64_t>(4);\n  stmt.run(kj::arrayPtr<const ValuePtr>(value));\n\n  // Sanity check that those queries were doing something.\n  KJ_EXPECT(db.run(\"SELECT COUNT(*) FROM things\").getInt(0) == 4);\n}\n\nKJ_TEST(\"SQLite extended error codes in messages\") {\n  // Verify that error messages include named extended error codes when they differ from the\n  // primary error code.\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  db.run(R\"(\n    CREATE TABLE things (\n      id INTEGER PRIMARY KEY,\n      name TEXT NOT NULL\n    );\n  )\");\n\n  db.run(\"INSERT INTO things VALUES (1, 'alice')\");\n\n  // UNIQUE/PRIMARY KEY constraint: extended code should be SQLITE_CONSTRAINT_PRIMARYKEY.\n  KJ_EXPECT_THROW_MESSAGE(\n      \"(extended: SQLITE_CONSTRAINT_PRIMARYKEY)\", db.run(\"INSERT INTO things VALUES (1, 'bob')\"));\n\n  // NOT NULL constraint: extended code should be SQLITE_CONSTRAINT_NOTNULL.\n  KJ_EXPECT_THROW_MESSAGE(\n      \"(extended: SQLITE_CONSTRAINT_NOTNULL)\", db.run(\"INSERT INTO things VALUES (2, NULL)\"));\n\n  // Errors where extended == primary should NOT have a parenthesized suffix.\n  // SQLITE_ERROR for \"no such table\" has no extended variant.\n  try {\n    db.run(\"SELECT * FROM nonexistent\");\n    KJ_FAIL_ASSERT(\"expected exception\");\n  } catch (kj::Exception& e) {\n    auto desc = e.getDescription();\n\n    KJ_EXPECT(desc.contains(\"SQLITE_ERROR\"), desc);\n    // The message should NOT have a parenthesized extended code like \"(SQLITE_ERROR_...)\".\n    KJ_EXPECT(!desc.contains(\"(SQLITE_ERROR_\"), desc);\n  }\n}\n\nclass MockRollbackCallback {\n public:\n  kj::Function<void()> create() {\n    KJ_ASSERT(!created);\n    created = true;\n    return [this, destructor = kj::defer([this]() { destroyed = true; })]() {\n      KJ_ASSERT(!called, \"callback called multiple times?\");\n      called = true;\n    };\n  }\n\n  bool isStillLive() {\n    return !destroyed && !called;\n  }\n  bool wasRolledBack() {\n    return called && destroyed;\n  }\n  bool wasCommitted() {\n    return !called && destroyed;\n  }\n\n private:\n  bool created = false;\n  bool called = false;\n  bool destroyed = false;\n};\n\nKJ_TEST(\"SQLite onRollback\") {\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  // With no transactions open, the callback is dropped immediately.\n  {\n    MockRollbackCallback cb;\n    db.onRollback(cb.create());\n    KJ_EXPECT(cb.wasCommitted());\n  }\n\n  // Committed transactions drop the callback without invoking it.\n  {\n    db.run(\"BEGIN TRANSACTION\");\n\n    MockRollbackCallback cb;\n    db.onRollback(cb.create());\n    KJ_EXPECT(cb.isStillLive());\n\n    db.run(\"COMMIT TRANSACTION\");\n\n    KJ_EXPECT(cb.wasCommitted());\n  }\n\n  {\n    db.run(\"SAVEPOINT foo\");\n\n    MockRollbackCallback cb;\n    db.onRollback(cb.create());\n    KJ_EXPECT(cb.isStillLive());\n\n    db.run(\"RELEASE SAVEPOINT foo\");\n\n    KJ_EXPECT(cb.wasCommitted());\n  }\n\n  // Rollbacks invoke the callback.\n  {\n    db.run(\"BEGIN TRANSACTION\");\n\n    MockRollbackCallback cb;\n    db.onRollback(cb.create());\n    KJ_EXPECT(cb.isStillLive());\n\n    db.run(\"ROLLBACK TRANSACTION\");\n\n    KJ_EXPECT(cb.wasRolledBack());\n  }\n\n  {\n    db.run(\"SAVEPOINT foo\");\n\n    MockRollbackCallback cb;\n    db.onRollback(cb.create());\n    KJ_EXPECT(cb.isStillLive());\n\n    db.run(\"ROLLBACK TO SAVEPOINT foo\");\n    KJ_EXPECT(cb.wasRolledBack());\n\n    // The savepoint still exists until we release it...\n    db.run(\"RELEASE SAVEPOINT foo\");\n  }\n\n  // Prepared statements work.\n  {\n    auto begin = db.prepare(\"BEGIN TRANSACTION\");\n    auto commit = db.prepare(\"COMMIT TRANSACTION\");\n\n    // No transactions are open yet (we only prepared some statements, we didn't execute them), so\n    // the callback is dropped immediately.\n    MockRollbackCallback cb1;\n    db.onRollback(cb1.create());\n    KJ_EXPECT(cb1.wasCommitted());\n\n    begin.run();\n\n    // Now a transaction is actually open.\n    MockRollbackCallback cb2;\n    db.onRollback(cb2.create());\n    KJ_EXPECT(cb2.isStillLive());\n\n    commit.run();\n\n    KJ_EXPECT(cb2.wasCommitted());\n  }\n\n  // Make a whole stack, do partial rollbacks...\n  {\n    db.run(\"BEGIN TRANSACTION\");\n\n    MockRollbackCallback cb1;\n    db.onRollback(cb1.create());\n\n    db.run(\"SAVEPOINT foo\");\n    db.run(\"SAVEPOINT bar\");\n\n    MockRollbackCallback cb2;\n    db.onRollback(cb2.create());\n\n    db.run(\"RELEASE bar\");\n\n    KJ_EXPECT(cb1.isStillLive());\n    KJ_EXPECT(cb2.isStillLive());\n\n    db.run(\"SAVEPOINT baz\");\n    db.run(\"ROLLBACK TO baz\");\n\n    KJ_EXPECT(cb1.isStillLive());\n    KJ_EXPECT(cb2.isStillLive());\n\n    db.run(\"SAVEPOINT qux\");\n    db.run(\"ROLLBACK TO foo\");\n\n    KJ_EXPECT(cb1.isStillLive());\n    KJ_EXPECT(cb2.wasRolledBack());\n\n    db.run(\"COMMIT TRANSACTION\");\n\n    KJ_EXPECT(cb1.wasCommitted());\n  }\n}\n\nKJ_TEST(\"SQLite prepareMulti\") {\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  auto stmt = db.prepareMulti(SqliteDatabase::TRUSTED, kj::str(R\"(\n    CREATE TABLE IF NOT EXISTS things (\n      id INTEGER PRIMARY KEY AUTOINCREMENT,\n      value INTEGER\n    );\n    INSERT INTO things(value) VALUES (123);\n    INSERT INTO things(value) VALUES (456);\n    INSERT INTO things(value) VALUES (789);\n    SELECT id, value FROM things;\n  )\"));\n\n  {\n    auto query = stmt.run();\n\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.getInt(0) == 1);\n    KJ_ASSERT(query.getInt(1) == 123);\n    query.nextRow();\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.getInt(0) == 2);\n    KJ_ASSERT(query.getInt(1) == 456);\n    query.nextRow();\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.getInt(0) == 3);\n    KJ_ASSERT(query.getInt(1) == 789);\n    query.nextRow();\n\n    KJ_ASSERT(query.isDone());\n  }\n\n  {\n    auto query = stmt.run();\n\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.getInt(0) == 1);\n    KJ_ASSERT(query.getInt(1) == 123);\n    query.nextRow();\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.getInt(0) == 2);\n    KJ_ASSERT(query.getInt(1) == 456);\n    query.nextRow();\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.getInt(0) == 3);\n    KJ_ASSERT(query.getInt(1) == 789);\n    query.nextRow();\n\n    // Re-running the statement inserted duplicates, so we'll see those in the results.\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.getInt(0) == 4);\n    KJ_ASSERT(query.getInt(1) == 123);\n    query.nextRow();\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.getInt(0) == 5);\n    KJ_ASSERT(query.getInt(1) == 456);\n    query.nextRow();\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.getInt(0) == 6);\n    KJ_ASSERT(query.getInt(1) == 789);\n    query.nextRow();\n\n    KJ_ASSERT(query.isDone());\n  }\n\n  // Test resetting the database, which will force re-parsing each statement.\n  db.reset();\n\n  {\n    auto query = stmt.run();\n\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.getInt(0) == 1);\n    KJ_ASSERT(query.getInt(1) == 123);\n    query.nextRow();\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.getInt(0) == 2);\n    KJ_ASSERT(query.getInt(1) == 456);\n    query.nextRow();\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.getInt(0) == 3);\n    KJ_ASSERT(query.getInt(1) == 789);\n    query.nextRow();\n\n    KJ_ASSERT(query.isDone());\n  }\n}\n\nKJ_TEST(\"SQLite prepareMulti with failure\") {\n  // Test running a multi-line prepared statement that fails in the middle.\n\n  // TODO(soon): Currently the failure does not roll back previous lines, but we should probably\n  //   change that so it does. If/when we do that, this test will have to get more complicated:\n  //   we'll need a prepared statement that fails on one call and then succeeds on a later call,\n  //   so that we can figure out whether duplicate statements were added to the prelude, which\n  //   is the bug being checked for here.\n\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  auto stmt = db.prepareMulti(SqliteDatabase::TRUSTED, kj::str(R\"(\n    CREATE TABLE IF NOT EXISTS things (\n      id INTEGER PRIMARY KEY AUTOINCREMENT,\n      value INTEGER\n    );\n    INSERT INTO things(value) VALUES (123);\n    INSERT INTO things(id, value) VALUES (1, 456);  -- fails, duplicate primary key\n  )\"));\n\n  KJ_EXPECT_THROW_MESSAGE(\"SQLITE_CONSTRAINT\", stmt.run());\n  KJ_EXPECT_THROW_MESSAGE(\"SQLITE_CONSTRAINT\", stmt.run());\n  KJ_EXPECT_THROW_MESSAGE(\"SQLITE_CONSTRAINT\", stmt.run());\n\n  // We ran the statement three times. Each time it should have inserted a new row containing\n  // `123`, before failing on the second insert. So there should be three rows. (At one point there\n  // was a bug where the successful prefix of statements would get duplicated on each run leading\n  // to there being 1 + 2 + 3 = 6 rows here.)\n  auto query = db.run(\"SELECT COUNT(*) FROM things\");\n  KJ_ASSERT(!query.isDone());\n  KJ_EXPECT(query.getInt(0) == 3);\n}\n\nKJ_TEST(\"SQLite prepareMulti w/BEGIN TRANSACTION\") {\n  // Test running a multi-line prepared statement where a transaction state change statement\n  // appears in the middle. At one point, there was a bug causing the state not to be tracked\n  // correctly on the second (and subsequent) execution of the statement.\n\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  auto stmt = db.prepareMulti(SqliteDatabase::TRUSTED, kj::str(R\"(\n    CREATE TABLE IF NOT EXISTS things (\n      id INTEGER PRIMARY KEY AUTOINCREMENT,\n      value INTEGER\n    );\n    INSERT INTO things(value) VALUES (123);\n    BEGIN TRANSACTION;\n    INSERT INTO things(value) VALUES (456);\n    SELECT id, value FROM things;\n  )\"));\n\n  {\n    auto query = stmt.run();\n\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.getInt(0) == 1);\n    KJ_ASSERT(query.getInt(1) == 123);\n    query.nextRow();\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.getInt(0) == 2);\n    KJ_ASSERT(query.getInt(1) == 456);\n    query.nextRow();\n\n    KJ_ASSERT(query.isDone());\n  }\n\n  db.run(\"ROLLBACK\");\n\n  {\n    auto query = stmt.run();\n\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.getInt(0) == 1);\n    KJ_ASSERT(query.getInt(1) == 123);\n    query.nextRow();\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.getInt(0) == 2);\n    KJ_ASSERT(query.getInt(1) == 123);\n    query.nextRow();\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.getInt(0) == 3);\n    KJ_ASSERT(query.getInt(1) == 456);\n    query.nextRow();\n\n    KJ_ASSERT(query.isDone());\n  }\n\n  db.run(\"ROLLBACK\");\n\n  {\n    auto query = db.run(\"SELECT id, value FROM things;\");\n\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.getInt(0) == 1);\n    KJ_ASSERT(query.getInt(1) == 123);\n    query.nextRow();\n    KJ_ASSERT(!query.isDone());\n    KJ_ASSERT(query.getInt(0) == 2);\n    KJ_ASSERT(query.getInt(1) == 123);\n    query.nextRow();\n\n    KJ_ASSERT(query.isDone());\n  }\n}\n\n// =======================================================================================\n// Error pass-through test\n//\n// TODO(cleanup): There is a LOT of boilerplate here to inject an exception into the VFS. Do we\n//   need better test utils for KJ filesystem APIs?\n\n// kj::File that throws errors when written.\nclass ErrorInjectableFile final: public kj::File, public kj::AtomicRefcounted {\n public:\n  // Initialize `error` to cause writes to fail.\n  kj::Maybe<kj::Exception> error;\n\n  void write(uint64_t offset, kj::ArrayPtr<const byte> data) const override {\n    KJ_IF_SOME(e, error) {\n      kj::throwFatalException(kj::cp(e));\n    }\n    inner->write(offset, data);\n  }\n\n  // All other operations just pass through.\n  // TODO(cleanup): Do we need a FileWrapper base class in KJ?\n  Metadata stat() const override {\n    return inner->stat();\n  }\n  void sync() const override {\n    inner->sync();\n  }\n  void datasync() const override {\n    inner->datasync();\n  }\n  size_t read(uint64_t offset, kj::ArrayPtr<byte> buffer) const override {\n    return inner->read(offset, buffer);\n  }\n  kj::Array<const byte> mmap(uint64_t offset, uint64_t size) const override {\n    return inner->mmap(offset, size);\n  }\n  kj::Array<byte> mmapPrivate(uint64_t offset, uint64_t size) const override {\n    return inner->mmapPrivate(offset, size);\n  }\n  void zero(uint64_t offset, uint64_t size) const override {\n    inner->zero(offset, size);\n  }\n  void truncate(uint64_t size) const override {\n    inner->truncate(size);\n  }\n  kj::Own<const kj::WritableFileMapping> mmapWritable(\n      uint64_t offset, uint64_t size) const override {\n    return inner->mmapWritable(offset, size);\n  }\n  size_t copy(uint64_t offset,\n      const ReadableFile& from,\n      uint64_t fromOffset,\n      uint64_t size) const override {\n    return inner->copy(offset, from, fromOffset, size);\n  }\n\n private:\n  kj::Own<const kj::File> inner = kj::newInMemoryFile(kj::nullClock());\n\n  kj::Own<const FsNode> cloneFsNode() const override {\n    return kj::atomicAddRef(*this);\n  }\n};\n\n// kj::Directory that serves ErrorInjectableFiles to SQLite.\nclass ErrorInjectableDirectory final: public kj::Directory, public kj::AtomicRefcounted {\n public:\n  kj::Maybe<kj::Own<ErrorInjectableFile>> dbFile;\n  kj::Maybe<kj::Own<ErrorInjectableFile>> walFile;\n  kj::Maybe<kj::Own<ErrorInjectableFile>> journalFile;\n\n  // Map filenames to the three Maybe<File>s above.\n  kj::Maybe<kj::Own<ErrorInjectableFile>>& getSlot(kj::PathPtr path) {\n    if (path.size() == 1) {\n      kj::StringPtr name = path[0];\n      if (name == \"db\"_kj) {\n        return dbFile;\n      } else if (name == \"db-wal\"_kj) {\n        return walFile;\n      } else if (name == \"db-journal\"_kj) {\n        return journalFile;\n      }\n    }\n    KJ_FAIL_ASSERT(\"unexpected file opened\", path);\n  }\n\n  kj::Maybe<kj::Own<ErrorInjectableFile>>& getSlot(kj::PathPtr path) const {\n    // const_cast OK because it's test code\n    return const_cast<ErrorInjectableDirectory*>(this)->getSlot(path);\n  }\n\n  // ---------------------------------------------------------------------------\n  // implements kj::Directory\n\n  kj::Maybe<kj::Own<const kj::ReadableFile>> tryOpenFile(kj::PathPtr path) const override {\n    return getSlot(path).map([](kj::Own<ErrorInjectableFile>& file) { return file->clone(); });\n  }\n\n  kj::Maybe<kj::Own<const kj::File>> tryOpenFile(\n      kj::PathPtr path, kj::WriteMode mode) const override {\n    auto& slot = getSlot(path);\n\n    KJ_IF_SOME(file, slot) {\n      if (kj::has(mode, kj::WriteMode::MODIFY)) {\n        return file->clone();\n      } else {\n        return kj::none;\n      }\n    } else {\n      if (kj::has(mode, kj::WriteMode::CREATE)) {\n        return slot.emplace(kj::atomicRefcounted<ErrorInjectableFile>())->clone();\n      } else {\n        return kj::none;\n      }\n    }\n  }\n\n  bool exists(kj::PathPtr path) const override {\n    return getSlot(path) != kj::none;\n  }\n\n  bool tryRemove(kj::PathPtr path) const override {\n    auto& slot = getSlot(path);\n    bool result = slot != kj::none;\n    slot = kj::none;\n    return result;\n  }\n\n  kj::Own<const FsNode> cloneFsNode() const override {\n    KJ_UNIMPLEMENTED(\"this method is unused by SQLite\");\n  }\n  Metadata stat() const override {\n    KJ_UNIMPLEMENTED(\"this method is unused by SQLite\");\n  }\n  void sync() const override {\n    KJ_UNIMPLEMENTED(\"this method is unused by SQLite\");\n  }\n  void datasync() const override {\n    KJ_UNIMPLEMENTED(\"this method is unused by SQLite\");\n  }\n  kj::Array<kj::String> listNames() const override {\n    KJ_UNIMPLEMENTED(\"this method is unused by SQLite\");\n  }\n  kj::Array<Entry> listEntries() const override {\n    KJ_UNIMPLEMENTED(\"this method is unused by SQLite\");\n  }\n  kj::Maybe<FsNode::Metadata> tryLstat(kj::PathPtr path) const override {\n    KJ_UNIMPLEMENTED(\"this method is unused by SQLite\");\n  }\n  kj::Maybe<kj::Own<const ReadableDirectory>> tryOpenSubdir(kj::PathPtr path) const override {\n    KJ_UNIMPLEMENTED(\"this method is unused by SQLite\");\n  }\n  kj::Maybe<kj::String> tryReadlink(kj::PathPtr path) const override {\n    KJ_UNIMPLEMENTED(\"this method is unused by SQLite\");\n  }\n  kj::Own<Replacer<kj::File>> replaceFile(kj::PathPtr path, kj::WriteMode mode) const override {\n    KJ_UNIMPLEMENTED(\"this method is unused by SQLite\");\n  }\n  kj::Own<const kj::File> createTemporary() const override {\n    KJ_UNIMPLEMENTED(\"this method is unused by SQLite\");\n  }\n  kj::Maybe<kj::Own<kj::AppendableFile>> tryAppendFile(\n      kj::PathPtr path, kj::WriteMode mode) const override {\n    KJ_UNIMPLEMENTED(\"this method is unused by SQLite\");\n  }\n  kj::Maybe<kj::Own<const kj::Directory>> tryOpenSubdir(\n      kj::PathPtr path, kj::WriteMode mode) const override {\n    KJ_UNIMPLEMENTED(\"this method is unused by SQLite\");\n  }\n  kj::Own<Replacer<kj::Directory>> replaceSubdir(\n      kj::PathPtr path, kj::WriteMode mode) const override {\n    KJ_UNIMPLEMENTED(\"this method is unused by SQLite\");\n  }\n  bool trySymlink(kj::PathPtr linkpath, kj::StringPtr content, kj::WriteMode mode) const override {\n    KJ_UNIMPLEMENTED(\"this method is unused by SQLite\");\n  }\n};\n\nKJ_TEST(\"I/O exceptions pass through SQLite\") {\n  auto dir = kj::atomicRefcounted<ErrorInjectableDirectory>();\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"db\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  db.run({.regulator = SqliteDatabase::TRUSTED}, kj::str(R\"(\n    CREATE TABLE IF NOT EXISTS things (\n      id INTEGER PRIMARY KEY AUTOINCREMENT,\n      value INTEGER\n    );\n    INSERT INTO things(value) VALUES (123);\n  )\"));\n\n  // Now arrange for an error on write().\n  KJ_ASSERT_NONNULL(dir->dbFile)->error = KJ_EXCEPTION(FAILED, \"test-vfs-error\");\n\n  // It should pass through.\n  KJ_EXPECT_THROW_MESSAGE(\n      \"test-vfs-error\", db.run({.regulator = SqliteDatabase::TRUSTED}, kj::str(R\"(\n    INSERT INTO things(value) VALUES (456);\n  )\")));\n}\n\nvoid testCriticalError(const char* expectedErrorMessage,\n    kj::Function<void(SqliteDatabase&, SqliteDatabase::Vfs& vfs)> triggerErrorFn) {\n  auto dir = kj::newInMemoryDirectory(kj::nullClock());\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"foo\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  // Create a tracker to verify our callback is called\n  bool criticalErrorCallbackCalled = false;\n\n  // Register a critical error callback\n  db.onCriticalError([&](kj::StringPtr errorMessage, kj::Maybe<kj::Exception> maybeException) {\n    criticalErrorCallbackCalled = true;\n    KJ_IF_SOME(exception, maybeException) {\n      KJ_EXPECT(exception.getDescription().contains(expectedErrorMessage));\n    } else {\n      KJ_EXPECT(errorMessage.contains(expectedErrorMessage));\n    }\n  });\n\n  KJ_EXPECT(!db.observedCriticalError());\n  KJ_EXPECT_THROW_MESSAGE(expectedErrorMessage, triggerErrorFn(db, vfs));\n\n  KJ_EXPECT(criticalErrorCallbackCalled);\n  KJ_EXPECT(db.observedCriticalError());\n}\n\nKJ_TEST(\"SQLite critical error handling for SQLITE_IOERR\") {\n  auto dir = kj::atomicRefcounted<ErrorInjectableDirectory>();\n  SqliteDatabase::Vfs vfs(*dir);\n  SqliteDatabase db(vfs, kj::Path({\"db\"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  // Create a tracker to verify our callback is called\n  bool criticalErrorCallbackCalled = false;\n  // Register a critical error callback\n  db.onCriticalError([&](kj::StringPtr errorMessage, kj::Maybe<kj::Exception> maybeException) {\n    criticalErrorCallbackCalled = true;\n    KJ_IF_SOME(exception, maybeException) {\n      KJ_EXPECT(exception.getDescription().contains(\"test-vfs-error\"));\n    } else {\n      KJ_EXPECT(errorMessage.contains(\"test-vfs-error\"));\n    }\n  });\n\n  // Use a small cache size to force flushing to disk on even a small write\n  db.run({.regulator = SqliteDatabase::TRUSTED}, \"PRAGMA cache_size = 1\");  // 1 page cache\n\n  db.run({.regulator = SqliteDatabase::TRUSTED}, kj::str(R\"(\n    CREATE TABLE IF NOT EXISTS things (\n      id INTEGER PRIMARY KEY AUTOINCREMENT,\n      value INTEGER\n    );\n    INSERT INTO things(value) VALUES (123);\n  )\"));\n\n  db.run(\"BEGIN TRANSACTION\");\n\n  // Now arrange for an error on write().\n  KJ_ASSERT_NONNULL(dir->dbFile)->error = KJ_EXCEPTION(FAILED, \"test-vfs-error\");\n\n  KJ_EXPECT(!db.observedCriticalError());\n  KJ_EXPECT_THROW_MESSAGE(\n      \"test-vfs-error\", db.run({.regulator = SqliteDatabase::TRUSTED}, kj::str(R\"(\n    INSERT INTO things(value) VALUES (456);\n  )\")));\n\n  KJ_EXPECT(criticalErrorCallbackCalled);\n  KJ_EXPECT(db.observedCriticalError());\n}\n\n// No test for SQLITE_BUSY as a critical error because we haven't been able to figure out how to\n// trigger it in a way that causes an auto-rollback. It seems like an auto-rollback would only\n// happen if the transaction had already included other writes before hitting SQLITE_BUSY,\n// but if a transaction is open and has performed writes, then obviously the caller must hold\n// the lock, and so would not be expected to see SQLITE_BUSY.\n\nKJ_TEST(\"SQLite critical error handling for SQLITE_FULL\") {\n  testCriticalError(\"database or disk is full\", [](SqliteDatabase& db, SqliteDatabase::Vfs& vfs) {\n    // Set up a database with limited size\n    db.run(\"PRAGMA max_page_count = 10\");\n    db.run(\"CREATE TABLE IF NOT EXISTS test_full (id INTEGER PRIMARY KEY, data BLOB)\");\n\n    db.run(\"BEGIN TRANSACTION\");\n\n    // Create a large blob to quickly fill the database\n    auto largeData = kj::heapArray<byte>(100000, 'X');  // 100KB\n\n    // This should eventually trigger SQLITE_FULL\n    db.run({.regulator = SqliteDatabase::TRUSTED}, \"INSERT INTO test_full VALUES (?, ?)\", 1,\n        largeData.asPtr());\n  });\n}\n\nKJ_TEST(\"SQLite critical error handling for SQLITE_NOMEM\") {\n  testCriticalError(\"out of memory\", [](SqliteDatabase& db, SqliteDatabase::Vfs& vfs) {\n    db.run(\"CREATE TABLE test_nomem (id INTEGER PRIMARY KEY, data BLOB)\");\n    db.run(\n        \"CREATE TABLE test_refs (id INTEGER PRIMARY KEY, ref_id INTEGER, FOREIGN KEY(ref_id) REFERENCES test_nomem(id) ON DELETE CASCADE)\");\n\n    db.run(\"BEGIN TRANSACTION\");\n\n    db.run(\"INSERT INTO test_nomem VALUES (1, 'small data')\");\n    db.run(\"INSERT INTO test_refs VALUES (1, 1)\");\n\n    // Set SQLite's memory limit very low to trigger SQLITE_NOMEM\n    db.run(\"PRAGMA hard_heap_limit=8192\");  // 8KB limit\n\n    // Create data that will exceed the memory limit\n    auto largeData = kj::heapArray<byte>(50000, 'X');  // 50KB\n\n    db.run({.regulator = SqliteDatabase::TRUSTED}, \"INSERT INTO test_nomem VALUES (?, ?)\", 2,\n        largeData.asPtr());\n  });\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/sqlite.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"sqlite.h\"\n\n#include <workerd/util/sentry.h>\n\n#include <kj/debug.h>\n#include <kj/refcount.h>\n#include <kj/string-tree.h>\n\n#if _WIN32\n#include <windows.h>\n\n#include <kj/win32-api-version.h>\n#include <kj/windows-sanity.h>\n#else\n#include <unistd.h>\n#endif\n\n#include <fcntl.h>\n#include <sqlite3.h>\n#include <sys/stat.h>\n\n#include <kj/map.h>\n#include <kj/mutex.h>\n#include <kj/vector.h>\n\n#include <atomic>\n\n#if _WIN32\n#define strncasecmp _strnicmp\n#define strcasecmp _stricmp\n#endif\n\nnamespace workerd {\n\nnamespace {\n\n// SQLite has a function like this in its internals, but it's not exposed to library consumers.\n//\n// These error codes come from https://www.sqlite.org/rescode.html#primary_result_code_list.\nkj::String namedErrorCode(int errorCode) {\n#define LITERAL(name)                                                                              \\\n  case name:                                                                                       \\\n    return kj::str(#name);\n  switch (errorCode) {\n    LITERAL(SQLITE_OK)\n    LITERAL(SQLITE_ERROR)\n    LITERAL(SQLITE_INTERNAL)\n    LITERAL(SQLITE_PERM)\n    LITERAL(SQLITE_ABORT)\n    LITERAL(SQLITE_BUSY)\n    LITERAL(SQLITE_LOCKED)\n    LITERAL(SQLITE_NOMEM)\n    LITERAL(SQLITE_READONLY)\n    LITERAL(SQLITE_INTERRUPT)\n    LITERAL(SQLITE_IOERR)\n    LITERAL(SQLITE_CORRUPT)\n    LITERAL(SQLITE_NOTFOUND)\n    LITERAL(SQLITE_FULL)\n    LITERAL(SQLITE_CANTOPEN)\n    LITERAL(SQLITE_PROTOCOL)\n    LITERAL(SQLITE_EMPTY)\n    LITERAL(SQLITE_SCHEMA)\n    LITERAL(SQLITE_TOOBIG)\n    LITERAL(SQLITE_CONSTRAINT)\n    LITERAL(SQLITE_MISMATCH)\n    LITERAL(SQLITE_MISUSE)\n    LITERAL(SQLITE_NOLFS)\n    LITERAL(SQLITE_AUTH)\n    LITERAL(SQLITE_FORMAT)\n    LITERAL(SQLITE_RANGE)\n    LITERAL(SQLITE_NOTADB)\n    LITERAL(SQLITE_NOTICE)\n    LITERAL(SQLITE_WARNING)\n    LITERAL(SQLITE_ROW)\n    LITERAL(SQLITE_DONE)\n    default:\n      return kj::str(\"SQLITE_UNKNOWN_ERROR_CODE(\", errorCode, \")\");\n  }\n#undef LITERAL\n}\n\n// Maps extended error codes to their symbolic names.\n// See https://www.sqlite.org/rescode.html#extended_result_code_list.\nkj::Maybe<kj::String> namedExtendedErrorCode(int extendedErrorCode) {\n#define LITERAL(name)                                                                              \\\n  case name:                                                                                       \\\n    return kj::str(#name);\n  switch (extendedErrorCode) {\n    LITERAL(SQLITE_ABORT_ROLLBACK)\n    LITERAL(SQLITE_AUTH_USER)\n    LITERAL(SQLITE_BUSY_RECOVERY)\n    LITERAL(SQLITE_BUSY_SNAPSHOT)\n    LITERAL(SQLITE_BUSY_TIMEOUT)\n    LITERAL(SQLITE_CANTOPEN_CONVPATH)\n    LITERAL(SQLITE_CANTOPEN_DIRTYWAL)\n    LITERAL(SQLITE_CANTOPEN_FULLPATH)\n    LITERAL(SQLITE_CANTOPEN_ISDIR)\n    LITERAL(SQLITE_CANTOPEN_NOTEMPDIR)\n    LITERAL(SQLITE_CANTOPEN_SYMLINK)\n    LITERAL(SQLITE_CONSTRAINT_CHECK)\n    LITERAL(SQLITE_CONSTRAINT_COMMITHOOK)\n    LITERAL(SQLITE_CONSTRAINT_DATATYPE)\n    LITERAL(SQLITE_CONSTRAINT_FOREIGNKEY)\n    LITERAL(SQLITE_CONSTRAINT_FUNCTION)\n    LITERAL(SQLITE_CONSTRAINT_NOTNULL)\n    LITERAL(SQLITE_CONSTRAINT_PINNED)\n    LITERAL(SQLITE_CONSTRAINT_PRIMARYKEY)\n    LITERAL(SQLITE_CONSTRAINT_ROWID)\n    LITERAL(SQLITE_CONSTRAINT_TRIGGER)\n    LITERAL(SQLITE_CONSTRAINT_UNIQUE)\n    LITERAL(SQLITE_CONSTRAINT_VTAB)\n    LITERAL(SQLITE_CORRUPT_INDEX)\n    LITERAL(SQLITE_CORRUPT_SEQUENCE)\n    LITERAL(SQLITE_CORRUPT_VTAB)\n    LITERAL(SQLITE_ERROR_MISSING_COLLSEQ)\n    LITERAL(SQLITE_ERROR_RETRY)\n    LITERAL(SQLITE_ERROR_SNAPSHOT)\n    LITERAL(SQLITE_IOERR_ACCESS)\n    LITERAL(SQLITE_IOERR_AUTH)\n    LITERAL(SQLITE_IOERR_BEGIN_ATOMIC)\n    LITERAL(SQLITE_IOERR_BLOCKED)\n    LITERAL(SQLITE_IOERR_CHECKRESERVEDLOCK)\n    LITERAL(SQLITE_IOERR_CLOSE)\n    LITERAL(SQLITE_IOERR_COMMIT_ATOMIC)\n    LITERAL(SQLITE_IOERR_CONVPATH)\n    LITERAL(SQLITE_IOERR_CORRUPTFS)\n    LITERAL(SQLITE_IOERR_DATA)\n    LITERAL(SQLITE_IOERR_DELETE)\n    LITERAL(SQLITE_IOERR_DELETE_NOENT)\n    LITERAL(SQLITE_IOERR_DIR_CLOSE)\n    LITERAL(SQLITE_IOERR_DIR_FSYNC)\n    LITERAL(SQLITE_IOERR_FSTAT)\n    LITERAL(SQLITE_IOERR_FSYNC)\n    LITERAL(SQLITE_IOERR_GETTEMPPATH)\n    LITERAL(SQLITE_IOERR_LOCK)\n    LITERAL(SQLITE_IOERR_MMAP)\n    LITERAL(SQLITE_IOERR_NOMEM)\n    LITERAL(SQLITE_IOERR_RDLOCK)\n    LITERAL(SQLITE_IOERR_READ)\n    LITERAL(SQLITE_IOERR_ROLLBACK_ATOMIC)\n    LITERAL(SQLITE_IOERR_SEEK)\n    LITERAL(SQLITE_IOERR_SHMLOCK)\n    LITERAL(SQLITE_IOERR_SHMMAP)\n    LITERAL(SQLITE_IOERR_SHMOPEN)\n    LITERAL(SQLITE_IOERR_SHMSIZE)\n    LITERAL(SQLITE_IOERR_SHORT_READ)\n    LITERAL(SQLITE_IOERR_TRUNCATE)\n    LITERAL(SQLITE_IOERR_UNLOCK)\n    LITERAL(SQLITE_IOERR_VNODE)\n    LITERAL(SQLITE_IOERR_WRITE)\n    LITERAL(SQLITE_LOCKED_SHAREDCACHE)\n    LITERAL(SQLITE_LOCKED_VTAB)\n    LITERAL(SQLITE_NOTICE_RECOVER_ROLLBACK)\n    LITERAL(SQLITE_NOTICE_RECOVER_WAL)\n    LITERAL(SQLITE_OK_LOAD_PERMANENTLY)\n    LITERAL(SQLITE_READONLY_CANTINIT)\n    LITERAL(SQLITE_READONLY_CANTLOCK)\n    LITERAL(SQLITE_READONLY_DBMOVED)\n    LITERAL(SQLITE_READONLY_DIRECTORY)\n    LITERAL(SQLITE_READONLY_RECOVERY)\n    LITERAL(SQLITE_READONLY_ROLLBACK)\n    LITERAL(SQLITE_WARNING_AUTOINDEX)\n    default:\n      return kj::none;\n  }\n#undef LITERAL\n}\n\nconstexpr size_t RA_MAX_METRICS_QUERY_SIZE = 1024;\n\nkj::String dbErrorMessage(int errorCode, sqlite3* db) {\n  kj::StringTree msg = kj::strTree(sqlite3_errmsg(db));\n  if (int offset = sqlite3_error_offset(db); offset != -1) {\n    msg = kj::strTree(kj::mv(msg), \" at offset \", offset);\n  }\n  msg = kj::strTree(kj::mv(msg), \": \", namedErrorCode(errorCode));\n  int extendedCode = sqlite3_extended_errcode(db);\n  if (extendedCode != errorCode) {\n    KJ_IF_SOME(extendedName, namedExtendedErrorCode(extendedCode)) {\n      msg = kj::strTree(kj::mv(msg), \" (extended: \", extendedName, \")\");\n    }\n  }\n  return msg.flatten();\n}\n\n// If a VFS call throws an exception, and vfsErrorListener is non-null, the exception will\n// be placed there, otherwise it will be logged. This is used to implement pass-through of KJ\n// exceptions through SQLite.\nstatic thread_local kj::Maybe<kj::Exception>* vfsErrorListener = nullptr;\n\n// Report that in a sqlite VFS callback, an exception was caught, and SQLITE_IOERROR is being\n// returned to SQLite.\n//\n// The exception must be caught using `catch (kj::Exception& e)`, NOT using `catch (...)` followed\n// by kj::getCaughtExceptionAsKj(). This is because the latter truncates the stack trace to show\n// only the frames between the throw and the catch. We actually want to retain the full trace\n// through SQLite.\nvoid reportVfsErrorCaught(kj::Exception&& e) {\n  if (vfsErrorListener != nullptr) {\n    // Only capture the first error; assume subsequent errors are side effects.\n    if (*vfsErrorListener == kj::none) {\n      *vfsErrorListener = kj::mv(e);\n    }\n  } else {\n    LOG_EXCEPTION(\"sqliteVfsError\", e);\n  }\n}\n\n// Implements SQLITE_CALL_SCOPE.\nclass SqliteCallScope {\n public:\n  SqliteCallScope() {\n    KJ_DASSERT(vfsErrorListener == nullptr);\n    vfsErrorListener = &error;\n  }\n  ~SqliteCallScope() {\n    vfsErrorListener = nullptr;\n  }\n\n  void rethrowVfsError() {\n    KJ_IF_SOME(e, error) {\n      // Slight hack: The exception already has a stack trace attached which should include the\n      // current stack, but `kj::throwFatalException()` would re-append the current stack trace\n      // to the exception. We can avoid that by calling\n      // kj::getExceptionCallback().onFatalException() directly, which is what\n      // `throwFatalException()` does after extending the stack.\n      kj::getExceptionCallback().onFatalException(kj::mv(e));\n    }\n  }\n\n  kj::Maybe<const kj::Exception&> getException() {\n    return error;\n  }\n\n  // Hack to allow block syntax with for(); see SQLITE_CALL_SCOPE.\n  bool done = false;\n\n private:\n  kj::Maybe<kj::Exception> error;\n};\n\n}  // namespace\n\n// Like KJ_REQUIRE() but give the Regulator a chance to report the error. `errorMessage` is either\n// the return value of sqlite3_errmsg() or a string literal containing a similarly\n// application-approriate error message. A reference called `regulator` must be in-scope.\n// sqliteErrorCode is a kj::Maybe<int> and represents the error code from sqlite.\n#define SQLITE_REQUIRE(condition, sqliteErrorCode, errorMessage, ...)                              \\\n  if (!(condition)) {                                                                              \\\n    regulator.onError(sqliteErrorCode, errorMessage);                                              \\\n    KJ_FAIL_REQUIRE(\"SQLite failed\", errorMessage, ##__VA_ARGS__);                                 \\\n  }\n\n// Make a SQLite call and check the returned error code. Use this version when the call is not\n// associated with an open DB connection.\n#define SQLITE_CALL_NODB(code, ...)                                                                \\\n  do {                                                                                             \\\n    int _ec = code;                                                                                \\\n    KJ_ASSERT(                                                                                     \\\n        _ec == SQLITE_OK, kj::str(sqlite3_errstr(_ec), \": \", namedErrorCode(_ec)), ##__VA_ARGS__); \\\n  } while (false)\n\n// This version requires the scope to contain a variable named `db` which is of type sqlite3*, or\n// can convert to it.\n#define SQLITE_CALL(code, ...)                                                                     \\\n  do {                                                                                             \\\n    SqliteCallScope sqliteCallScope;                                                               \\\n    int _ec = code;                                                                                \\\n    /* SQLITE_MISUSE doesn't put error info on the database object, so check it separately */      \\\n    KJ_ASSERT(_ec != SQLITE_MISUSE, \"SQLite misused: \" #code, ##__VA_ARGS__);                      \\\n    handleCriticalError(_ec, dbErrorMessage(_ec, db), sqliteCallScope.getException());             \\\n    if (_ec == SQLITE_IOERR) sqliteCallScope.rethrowVfsError();                                    \\\n    SQLITE_REQUIRE(_ec == SQLITE_OK, _ec, dbErrorMessage(_ec, db), ##__VA_ARGS__);                 \\\n  } while (false)\n\n// Version of `SQLITE_CALL` that can be called after inspecting the error code, in case some codes\n// aren't really errors.\n//\n// Temporarily marking SQLITE_BUSY as NOSENTRY to reduce sentry volume while debugging issue.\n// TODO(soon): reenable SQLITE_BUSY sentry logging.\n#define SQLITE_CALL_FAILED(code, error, ...)                                                       \\\n  do {                                                                                             \\\n    KJ_ASSERT(error != SQLITE_MISUSE, \"SQLite misused: \" code, ##__VA_ARGS__);                     \\\n    handleCriticalError(error, dbErrorMessage(error, db), sqliteCallScope.getException());         \\\n    if (error == SQLITE_IOERR) sqliteCallScope.rethrowVfsError();                                  \\\n    SQLITE_REQUIRE(error != SQLITE_BUSY, error, kj::str(\"NOSENTRY \", dbErrorMessage(error, db)),   \\\n        ##__VA_ARGS__);                                                                            \\\n    SQLITE_REQUIRE(error == SQLITE_OK, error, dbErrorMessage(error, db), ##__VA_ARGS__);           \\\n  } while (false);\n\n// When using SQLITE_CALL_FAILED(), you must place the actual sqlite call and the\n// SQLITE_CALL_FAILED() invocation within a SQLITE_CALL_SCOPE block, in order to set up VFS error\n// capture. Example:\n//\n//   SQLITE_CALL_SCOPE {\n//     int errorCode = sqlite3_do_something();\n//     if (errorCode != SQLITE_OK) {\n//       SQLITE_CALL_FAILED(\"sqlite3_do_something()\", errorCode, \"failed to do something\");\n//     }\n//   }\n//\n// Note that if you use SQLITE_CALL(), this is handled automatically.\n#define SQLITE_CALL_SCOPE                                                                          \\\n  for (SqliteCallScope sqliteCallScope; !sqliteCallScope.done; sqliteCallScope.done = true)\n\nnamespace {\n\nvoid disposeSqlite(sqlite3_stmt* stmt) {\n  sqlite3_finalize(stmt);\n\n  // Note that any returned error code is actually the last error to occur while executing the\n  // statement. This does not really mean that finalization failed, and the error in question\n  // should have been checked and reported earlier. So, we ignore it here.\n}\n\ntemplate <typename T>\nclass SqliteDisposer: public kj::Disposer {\n public:\n  void disposeImpl(void* pointer) const override {\n    disposeSqlite(reinterpret_cast<T*>(pointer));\n  }\n};\n\ntemplate <typename T>\nkj::Own<T> ownSqlite(T* obj) {\n  static const SqliteDisposer<T> disposer;\n  return kj::Own<T>(obj, disposer);\n}\n\n#if _WIN32\n// https://github.com/capnproto/capnproto/blob/master/c%2B%2B/src/kj/filesystem-disk-win32.c%2B%2B#L255-L269\nstatic kj::Path getPathFromWin32Handle(HANDLE handle) {\n  DWORD tryLen = MAX_PATH;\n  for (;;) {\n    auto temp = kj::heapArray<wchar_t>(tryLen + 1);\n    DWORD len = GetFinalPathNameByHandleW(handle, temp.begin(), tryLen, 0);\n    if (len == 0) {\n      KJ_FAIL_WIN32(\"GetFinalPathNameByHandleW\", GetLastError());\n    }\n    if (len < temp.size()) {\n      return kj::Path::parseWin32Api(temp.first(len));\n    }\n    // Try again with new length.\n    tryLen = len;\n  }\n}\n#endif\n\nkj::Maybe<kj::StringPtr> toMaybeString(const char* cstr) {\n  if (cstr == nullptr) {\n    return kj::none;\n  } else {\n    return kj::StringPtr(cstr);\n  }\n}\n\n// We allowlist these SQLite functions.\nstatic constexpr kj::StringPtr ALLOWED_SQLITE_FUNCTIONS[] = {\n  // https://www.sqlite.org/lang_corefunc.html\n  \"abs\"_kj,\n  \"changes\"_kj,\n  \"char\"_kj,\n  \"coalesce\"_kj,\n  \"concat\"_kj,\n  \"concat_ws\"_kj,\n  \"format\"_kj,\n  \"glob\"_kj,\n  \"hex\"_kj,\n  \"ifnull\"_kj,\n  \"iif\"_kj,\n  \"instr\"_kj,\n  \"last_insert_rowid\"_kj,\n  \"length\"_kj,\n  \"like\"_kj,\n  \"likelihood\"_kj,\n  \"likely\"_kj,\n  \"load_extension\"_kj,\n  \"lower\"_kj,\n  \"ltrim\"_kj,\n  \"max_scalar\"_kj,\n  \"min_scalar\"_kj,\n  \"nullif\"_kj,\n  \"octet_length\"_kj,\n  \"printf\"_kj,\n  \"quote\"_kj,\n  \"random\"_kj,\n  \"randomblob\"_kj,\n  \"replace\"_kj,\n  \"round\"_kj,\n  \"rtrim\"_kj,\n  \"sign\"_kj,\n  \"soundex\"_kj,\n  // These functions query SQLite internals and build details in a way we'd prefer not to reveal.\n  // \"sqlite_compileoption_get\"_kj,\n  // \"sqlite_compileoption_used\"_kj,\n  // \"sqlite_offset\"_kj,\n  // \"sqlite_source_id\"_kj,\n  // \"sqlite_version\"_kj,\n  \"substr\"_kj,\n  \"substring\"_kj,\n  \"total_changes\"_kj,\n  \"trim\"_kj,\n  \"typeof\"_kj,\n  \"unhex\"_kj,\n  \"unicode\"_kj,\n  \"unlikely\"_kj,\n  \"upper\"_kj,\n  \"zeroblob\"_kj,\n\n  // https://www.sqlite.org/lang_datefunc.html\n  \"date\"_kj,\n  \"time\"_kj,\n  \"datetime\"_kj,\n  \"julianday\"_kj,\n  \"unixepoch\"_kj,\n  \"strftime\"_kj,\n  \"timediff\"_kj,\n  \"current_date\"_kj,\n  \"current_time\"_kj,\n  \"current_timestamp\"_kj,\n\n  // https://www.sqlite.org/lang_aggfunc.html\n  \"avg\"_kj,\n  \"count\"_kj,\n  \"group_concat\"_kj,\n  \"max\"_kj,\n  \"min\"_kj,\n  \"string_agg\"_kj,\n  \"sum\"_kj,\n  \"total\"_kj,\n\n  // https://www.sqlite.org/windowfunctions.html#biwinfunc\n  \"row_number\"_kj,\n  \"rank\"_kj,\n  \"dense_rank\"_kj,\n  \"percent_rank\"_kj,\n  \"cume_dist\"_kj,\n  \"ntile\"_kj,\n  \"lag\"_kj,\n  \"lead\"_kj,\n  \"first_value\"_kj,\n  \"last_value\"_kj,\n  \"nth_value\"_kj,\n\n  // https://www.sqlite.org/lang_mathfunc.html\n  \"acos\"_kj,\n  \"acosh\"_kj,\n  \"asin\"_kj,\n  \"asinh\"_kj,\n  \"atan\"_kj,\n  \"atan2\"_kj,\n  \"atanh\"_kj,\n  \"ceil\"_kj,\n  \"cos\"_kj,\n  \"cosh\"_kj,\n  \"degrees\"_kj,\n  \"exp\"_kj,\n  \"floor\"_kj,\n  \"ln\"_kj,\n  \"log\"_kj,\n  \"log2\"_kj,\n  \"mod\"_kj,\n  \"pi\"_kj,\n  \"pow\"_kj,\n  \"radians\"_kj,\n  \"sin\"_kj,\n  \"sinh\"_kj,\n  \"sqrt\"_kj,\n  \"tan\"_kj,\n  \"tanh\"_kj,\n  \"trunc\"_kj,\n\n  // https://www.sqlite.org/json1.html\n  \"json\"_kj,\n  \"jsonb\"_kj,\n  \"json_array\"_kj,\n  \"jsonb_array\"_kj,\n  \"json_array_length\"_kj,\n  \"json_extract\"_kj,\n  \"jsonb_extract\"_kj,\n  \"->\"_kj,\n  \"->>\"_kj,\n  \"json_insert\"_kj,\n  \"jsonb_insert\"_kj,\n  \"json_object\"_kj,\n  \"jsonb_object\"_kj,\n  \"json_patch\"_kj,\n  \"jsonb_patch\"_kj,\n  \"json_remove\"_kj,\n  \"jsonb_remove\"_kj,\n  \"json_replace\"_kj,\n  \"jsonb_replace\"_kj,\n  \"json_set\"_kj,\n  \"jsonb_set\"_kj,\n  \"json_type\"_kj,\n  \"json_valid\"_kj,\n  \"json_quote\"_kj,\n  \"json_group_array\"_kj,\n  \"jsonb_group_array\"_kj,\n  \"json_group_object\"_kj,\n  \"jsonb_group_object\"_kj,\n  \"json_each\"_kj,\n  \"json_tree\"_kj,\n\n  // https://www.sqlite.org/fts5.html\n  \"match\"_kj,\n  \"highlight\"_kj,\n  \"bm25\"_kj,\n  \"snippet\"_kj,\n\n  // https://www.sqlite.org/lang_altertable.html\n  // Functions declared in https://sqlite.org/src/file?name=src/alter.c&ci=trunk\n  \"sqlite_rename_column\"_kj,\n  \"sqlite_rename_table\"_kj,\n  \"sqlite_rename_test\"_kj,\n  \"sqlite_drop_column\"_kj,\n  \"sqlite_rename_quotefix\"_kj,\n};\n\nenum class PragmaSignature {\n  NO_ARG,\n  BOOLEAN,\n  OBJECT_NAME,\n  OPTIONAL_OBJECT_NAME,\n  NULL_OR_NUMBER,\n  NULL_NUMBER_OR_OBJECT_NAME\n};\nstruct PragmaInfo {\n  kj::StringPtr name;\n  PragmaSignature signature;\n};\n\n// We allowlist these SQLite pragmas (for read only, never with arguments).\n// https://www.sqlite.org/pragma.html\nstatic constexpr PragmaInfo ALLOWED_PRAGMAS[] = {{\"data_version\"_kj, PragmaSignature::NO_ARG},\n\n  // We allowlist some SQLite pragmas for changing internal state\n\n  // Toggle constraints on/off\n  {\"case_sensitive_like\"_kj, PragmaSignature::BOOLEAN},\n  {\"foreign_keys\"_kj, PragmaSignature::BOOLEAN},\n  {\"defer_foreign_keys\"_kj, PragmaSignature::BOOLEAN},\n  {\"ignore_check_constraints\"_kj, PragmaSignature::BOOLEAN},\n  {\"legacy_alter_table\"_kj, PragmaSignature::BOOLEAN},\n  {\"recursive_triggers\"_kj, PragmaSignature::BOOLEAN},\n  {\"reverse_unordered_selects\"_kj, PragmaSignature::BOOLEAN},\n\n  // Takes an argument of table name or index name, returns info about it.\n  {\"foreign_key_check\"_kj, PragmaSignature::OPTIONAL_OBJECT_NAME},\n  {\"foreign_key_list\"_kj, PragmaSignature::OBJECT_NAME},\n  {\"index_info\"_kj, PragmaSignature::OBJECT_NAME}, {\"index_list\"_kj, PragmaSignature::OBJECT_NAME},\n  {\"index_xinfo\"_kj, PragmaSignature::OBJECT_NAME},\n\n  // Takes an argument of table name/index name OR a max number of results, or nothing\n  {\"quick_check\"_kj, PragmaSignature::NULL_NUMBER_OR_OBJECT_NAME},\n\n  // Takes a number representing a bit mask or nothing to use the default mask.\n  {\"optimize\"_kj, PragmaSignature::NULL_OR_NUMBER}};\n\n}  // namespace\n\n// =======================================================================================\n\nSqliteObserver SqliteObserver::DEFAULT = SqliteObserver{};\n\nSqliteDatabase::SqliteDatabase(const Vfs& vfs,\n    kj::Path path,\n    kj::Maybe<kj::WriteMode> maybeMode,\n    SqliteObserver& sqliteObserver,\n    kj::Maybe<const ActorAccountLimits&> actorAccountLimits)\n    : vfs(vfs),\n      path(kj::mv(path)),\n      readOnly(maybeMode == kj::none),\n      sqliteObserver(sqliteObserver),\n      actorAccountLimits(actorAccountLimits) {\n  init(maybeMode);\n}\n\nvoid SqliteDatabase::init(kj::Maybe<kj::WriteMode> maybeMode) {\n  KJ_ASSERT(maybeDb == kj::none);\n  sqlite3* db = nullptr;\n\n  KJ_IF_SOME(mode, maybeMode) {\n    int flags = SQLITE_OPEN_READWRITE;\n    if (kj::has(mode, kj::WriteMode::CREATE)) {\n      flags |= SQLITE_OPEN_CREATE;\n\n      if (kj::has(mode, kj::WriteMode::CREATE_PARENT) && path.size() > 1) {\n        // SQLite isn't going to try to create the parent directory so let's try to create it now.\n        vfs.directory.openSubdir(path.parent(),\n            kj::WriteMode::CREATE | kj::WriteMode::MODIFY | kj::WriteMode::CREATE_PARENT);\n      }\n    }\n    KJ_REQUIRE(\n        kj::has(mode, kj::WriteMode::MODIFY), \"SQLite doesn't support create-exclusive mode\");\n\n    KJ_IF_SOME(rootedPath, vfs.tryAppend(path)) {\n      // If we can get the path rooted in the VFS's directory, use the system's default VFS instead\n      // TODO(bug): This doesn't honor vfs.options. (This branch is only used on Windows.)\n      SQLITE_CALL_NODB(\n          sqlite3_open_v2(rootedPath.toNativeString(true).cStr(), &db, flags, nullptr));\n    } else {\n      SQLITE_CALL_NODB(sqlite3_open_v2(path.toString().cStr(), &db, flags, vfs.getName().cStr()));\n    }\n  } else {\n    KJ_IF_SOME(rootedPath, vfs.tryAppend(path)) {\n      // If we can get the path rooted in the VFS's directory, use the system's default VFS instead\n      // TODO(bug): This doesn't honor vfs.options. (This branch is only used on Windows.)\n      SQLITE_CALL_NODB(sqlite3_open_v2(\n          rootedPath.toNativeString(true).cStr(), &db, SQLITE_OPEN_READONLY, nullptr));\n    } else {\n      SQLITE_CALL_NODB(\n          sqlite3_open_v2(path.toString().cStr(), &db, SQLITE_OPEN_READONLY, vfs.getName().cStr()));\n    }\n  }\n\n  KJ_ON_SCOPE_FAILURE(sqlite3_close_v2(db));\n\n  setupSecurity(db);\n\n  maybeDb = *db;\n}\n\nSqliteDatabase::~SqliteDatabase() noexcept(false) {\n  sqlite3* db = &KJ_UNWRAP_OR(maybeDb, return);\n\n  auto err = sqlite3_close(db);\n  if (err == SQLITE_BUSY) {\n    KJ_LOG(ERROR, \"sqlite database destroyed while dependent objects still exist\");\n    // SQLite actually provides a lazy-close API which we might as well use here instead of leaking\n    // memory.\n    err = sqlite3_close_v2(db);\n  }\n\n  KJ_REQUIRE(err == SQLITE_OK, sqlite3_errstr(err)) {\n    break;\n  }\n}\n\nSqliteDatabase::operator sqlite3*() {\n  return &KJ_ASSERT_NONNULL(maybeDb, \"previous reset() failed\");\n}\n\nbool SqliteDatabase::observedCriticalError() {\n  return criticalErrorOccurred;\n}\n\nvoid SqliteDatabase::notifyWrite(bool allowUnconfirmed) {\n  KJ_IF_SOME(cb, onWriteCallback) {\n    cb(allowUnconfirmed);\n  }\n}\n\nvoid SqliteDatabase::handleCriticalError(kj::Maybe<int> errorCode,\n    kj::StringPtr errorMessage,\n    kj::Maybe<const kj::Exception&> maybeException) {\n  KJ_IF_SOME(code, errorCode) {\n    // Only errors listed in https://www.sqlite.org/lang_transaction.html#response_to_errors_within_a_transaction\n    // should be considered here as SQLITE auto rollbacks the transaction when we hit these errors\n    if (code == SQLITE_FULL || code == SQLITE_IOERR || code == SQLITE_NOMEM ||\n        code == SQLITE_INTERRUPT) {\n\n      sqlite3* db = &KJ_ASSERT_NONNULL(maybeDb, \"previous reset() failed\");\n      // We are in a transaction\n      if (inTransaction || !savepoints.empty()) {\n        // The transaction was auto-rolledback, re-enabling the auto commit mode, so we should fail\n        if (sqlite3_get_autocommit(db) != 0) {\n          criticalErrorOccurred = true;\n          KJ_IF_SOME(cb, onCriticalErrorCallback) {\n            cb(errorMessage, maybeException);\n          }\n        }\n      }\n    }\n  }\n}\n\nkj::StringPtr SqliteDatabase::getCurrentQueryForDebug() {\n  KJ_IF_SOME(s, currentStatement) {\n    return sqlite3_normalized_sql(&s);\n  } else {\n    return \"(no statement is running)\";\n  }\n}\n\nvoid SqliteDatabase::applyChange(const StateChange& change) {\n  KJ_SWITCH_ONEOF(change) {\n    KJ_CASE_ONEOF(none, NoChange) {\n      // Nothing.\n    }\n\n    KJ_CASE_ONEOF(begin, BeginTxn) {\n      KJ_IF_SOME(name, begin.savepointName) {\n        savepoints.add(\n            Savepoint{.name = kj::str(name), .rollbackCallbackIndex = rollbackCallbacks.size()});\n      } else {\n        KJ_ASSERT(savepoints.empty(),\n            \"BEGIN TRANSACTION should have failed when savepoints are present?\");\n        KJ_ASSERT(\n            !inTransaction, \"BEGIN TRANSACTION should have failed when already in a transaction?\");\n        KJ_ASSERT(rollbackCallbacks.empty(),\n            \"we shouldn't have been keeping rollback callbacks with no transaction open!\");\n        inTransaction = true;\n      }\n    }\n\n    KJ_CASE_ONEOF(commit, CommitTxn) {\n      KJ_IF_SOME(name, commit.savepointName) {\n        // According to https://www.sqlite.org/lang_savepoint.html, releasing a savepoint also\n        // releases all later savepoints. In theory it seems like savepoints shouldn't need to\n        // be LIFO like this, but the docs say they are!\n        for (;;) {\n          KJ_ASSERT(!savepoints.empty(), \"released a savepoint that didn't exist?\");\n          auto sp = kj::mv(savepoints.back());\n          savepoints.removeLast();\n          if (sp.name == name) break;\n        }\n      } else {\n        KJ_ASSERT(inTransaction, \"COMMIT TRANSACTION without BEGIN TRANSACTION?\");\n\n        // Since BEGIN TRANSACTION cannot be nested within a savepoint, this must have released\n        // all savepoints implicitly.\n        savepoints.clear();\n        inTransaction = false;\n      }\n\n      if (savepoints.empty() && !inTransaction) {\n        // Transaction stack is empty, so the transaction is committed. We can release the rollback\n        // callbacks.\n        rollbackCallbacks.clear();\n      }\n    }\n\n    KJ_CASE_ONEOF(rollback, RollbackTxn) {\n      KJ_IF_SOME(name, rollback.savepointName) {\n        for (;;) {\n          KJ_ASSERT(!savepoints.empty(), \"released a savepoint that didn't exist?\");\n          if (savepoints.back().name == name) {\n            // Found the savepoint.\n            // Call all rollback callbacks later than the savepoint.\n            size_t index = savepoints.back().rollbackCallbackIndex;\n            KJ_ASSERT(rollbackCallbacks.size() >= index);\n            while (rollbackCallbacks.size() > index) {\n              rollbackCallbacks.back()();\n              rollbackCallbacks.removeLast();\n            }\n\n            // NOTE: Rolling back to a savepoint does not actually release the savepoint. Hence\n            //   we save this savepoint as the last item in `savepoints`. It must be released\n            //   separately.\n            break;\n          }\n\n          savepoints.removeLast();\n        }\n      } else {\n        KJ_ASSERT(inTransaction, \"ROLLBACK TRANSACTION without BEGIN TRANSACTION?\");\n\n        savepoints.clear();\n        inTransaction = false;\n\n        while (!rollbackCallbacks.empty()) {\n          rollbackCallbacks.back()();\n          rollbackCallbacks.removeLast();\n        }\n      }\n    }\n  }\n}\n\n// Set up the regulator that will be used for authorizer callbacks while preparing this\n// statement.\nSqliteDatabase::StatementAndEffect SqliteDatabase::prepareSql(const Regulator& regulator,\n    kj::StringPtr sqlCode,\n    uint prepFlags,\n    Multi multi,\n    kj::Maybe<kj::Vector<Statement>&> prelude) {\n  sqlite3* db = &KJ_ASSERT_NONNULL(maybeDb, \"previous reset() failed\");\n\n  ParseContext parseContext;\n  KJ_ASSERT(currentParseContext == kj::none, \"recursive prepareSql()?\");\n  KJ_DEFER(currentParseContext = kj::none);\n  currentParseContext = parseContext;\n\n  KJ_ASSERT(currentRegulator == kj::none,\n      \"can't prepare statements inside executeWithRegulator() callback\");\n  KJ_DEFER(currentRegulator = kj::none);\n  currentRegulator = regulator;\n\n  // If we fail, we need to discard any statements we added to the prelude, because the next time\n  // the statement runs they'll be parsed again and added again.\n  uint preludeInitialSize = 0;\n  KJ_IF_SOME(p, prelude) {\n    preludeInitialSize = p.size();\n  }\n  KJ_ON_SCOPE_FAILURE({\n    KJ_IF_SOME(p, prelude) {\n      while (p.size() > preludeInitialSize) {\n        p.removeLast();\n      }\n    } else {\n      // (else block needed to squelch spurious clang warning)\n    }\n  });\n\n  for (;;) {\n    sqlite3_stmt* result;\n    const char* tail;\n\n    SQLITE_CALL_SCOPE {\n      auto prepareResult =\n          sqlite3_prepare_v3(db, sqlCode.begin(), sqlCode.size(), prepFlags, &result, &tail);\n\n      // If we had an auth error specifically, check if we recorded a better error message during\n      // the authorizer callback.\n      if (prepareResult == SQLITE_AUTH) {\n        KJ_IF_SOME(error, parseContext.authError) {\n          // Throw the tailored auth error.\n          kj::throwFatalException(kj::mv(error));\n        }\n        // we don't have a better error, so fall back to SQLITE_CALL_FAILED below\n      }\n\n      if (prepareResult != SQLITE_OK) {\n        SQLITE_CALL_FAILED(\"sqlite3_prepare_v3\", prepareResult);\n      }\n    }\n\n    SQLITE_REQUIRE(result != nullptr, kj::none, \"SQL code did not contain a statement.\", sqlCode);\n    auto ownResult = ownSqlite(result);\n\n    while (*tail == ' ' || *tail == '\\t' || *tail == '\\n' || *tail == '\\r' || *tail == '\\v' ||\n        *tail == '\\f')\n      ++tail;\n\n    switch (multi) {\n      case SINGLE:\n        SQLITE_REQUIRE(tail == sqlCode.end(), kj::none,\n            \"A prepared SQL statement must contain only one statement.\", tail);\n        break;\n\n      case MULTI:\n        if (tail != sqlCode.end()) {\n          // There are more statements after this one, so execute this statement now.\n\n          SQLITE_REQUIRE(sqlite3_bind_parameter_count(result) == 0, kj::none,\n              \"When executing multiple SQL statements in a single call, only the last statement \"\n              \"can have parameters.\");\n\n          // Be sure to call the onWrite callback if necessary for this statement.\n          KJ_IF_SOME(cb, onWriteCallback) {\n            if (!sqlite3_stmt_readonly(result)) {\n              // The callback is allowed to invoke queries of its own, so we have to un-set the\n              // regulator and parse context while we call it.\n              currentRegulator = kj::none;\n              KJ_DEFER(currentRegulator = regulator);\n              currentParseContext = kj::none;\n              KJ_DEFER(currentParseContext = parseContext);\n              cb(false);  // prepareSql doesn't have access to allowUnconfirmed, use safe default\n            }\n          }\n\n          // This isn't the last statement in the code. Execute it immediately.\n          SQLITE_CALL_SCOPE {\n            auto start = sqliteObserver.now();\n            auto dbWalSizeBefore = sqliteObserver.getDbWalSize();\n\n            int err = sqlite3_step(result);\n\n            kj::Duration queryLatency = sqliteObserver.now() - start;\n            auto dbWalBytesWritten = sqliteObserver.getDbWalSize() - dbWalSizeBefore;\n            auto rowsRead = sqlite3_stmt_status(result, LIBSQL_STMTSTATUS_ROWS_READ, 0);\n            auto rowsWritten = sqlite3_stmt_status(result, LIBSQL_STMTSTATUS_ROWS_WRITTEN, 0);\n\n            kj::Maybe<kj::String> queryStatement;\n            kj::Maybe<kj::String> queryErrorDescription;\n            try {\n              kj::StringPtr statement = sqlite3_sql(result);\n              queryStatement = kj::heapString(\n                  statement.slice(0, kj::min(statement.size(), RA_MAX_METRICS_QUERY_SIZE)));\n            } catch (kj::Exception& e) {\n              kj::StringPtr errorDescription = e.getDescription();\n              queryErrorDescription = kj::heapString(errorDescription.slice(\n                  0, kj::min(RA_MAX_METRICS_QUERY_SIZE, errorDescription.size())));\n            }\n\n            // Report queryEvent for this statement\n            sqliteObserver.reportQueryEvent(kj::mv(queryStatement), rowsRead, rowsWritten,\n                queryLatency, dbWalBytesWritten, err, regulator.shouldAddQueryStats(),\n                kj::mv(queryErrorDescription));\n\n            if (err == SQLITE_DONE) {\n              // good\n            } else if (err == SQLITE_ROW) {\n              // Intermediate statement returned results. We will discard.\n            } else {\n              SQLITE_CALL_FAILED(\"sqlite3_step()\", err);\n            }\n          }\n\n          // Apply any state changes from executing the statement.\n          applyChange(parseContext.stateChange);\n\n          KJ_IF_SOME(p, prelude) {\n            p.add(Statement(*this, regulator,\n                StatementAndEffect{.statement = kj::mv(ownResult),\n                  .stateChange = kj::mv(parseContext.stateChange)}));\n          }\n\n          // Reset parse context for next statement.\n          parseContext = {};\n\n          // Reduce `sqlCode` to include only what we haven't already executed.\n          sqlCode = kj::StringPtr(tail, sqlCode.end());\n\n          continue;\n        }\n        break;\n    }\n\n    return {.statement = kj::mv(ownResult), .stateChange = kj::mv(parseContext.stateChange)};\n  }\n}\n\nSqliteDatabase::IngestResult SqliteDatabase::ingestSql(\n    const Regulator& regulator, kj::StringPtr sqlCode) {\n  uint64_t rowsRead = 0;\n  uint64_t rowsWritten = 0;\n  uint64_t statementCount = 0;\n\n  // While there's still some input SQL to process\n  while (sqlCode.begin() != sqlCode.end()) {\n    // And there are still valid statements:\n    auto statementLength = sqlite3_complete_length(sqlCode.begin(), 1);\n    if (!statementLength) break;\n\n    // Slice off the next valid statement SQL\n    auto nextStatement = kj::str(sqlCode.first(statementLength));\n    // Create a Query object, which will prepare & execute it\n    auto q = Query(*this, QueryOptions{.regulator = regulator}, nextStatement);\n\n    rowsRead += q.getRowsRead();\n    rowsWritten += q.getRowsWritten();\n    statementCount++;\n    sqlCode = sqlCode.slice(statementLength);\n  }\n\n  // Return the leftover buffer\n  return {.remainder = sqlCode,\n    .rowsRead = rowsRead,\n    .rowsWritten = rowsWritten,\n    .statementCount = statementCount};\n}\n\nvoid SqliteDatabase::executeWithRegulator(\n    const Regulator& regulator, kj::FunctionParam<void()> func) {\n  // currentRegulator would only be set if we're running this method while running something else\n  // with a regulator.  I'm not sure what the ramifications are, so for now, we'll just assume that\n  // we can only call executeWithRegulator when no regulator is currently set.\n  KJ_REQUIRE(currentRegulator == kj::none);\n\n  currentRegulator = regulator;\n  KJ_DEFER(currentRegulator = kj::none);\n  func();\n}\n\nvoid SqliteDatabase::reset() {\n  KJ_REQUIRE(!readOnly, \"can't reset() read-only database\");\n\n  // If transactions are open during reset(), whatever had the transaction open is going to get\n  // confused at best, or lose data at worst. Let's just not allow this.\n  KJ_REQUIRE(!inTransaction && savepoints.empty(), \"can't reset() a database during a transaction\");\n\n  // Temporarily disable the on-write callback while resetting.\n  auto writeCb = kj::mv(onWriteCallback);\n  KJ_DEFER(onWriteCallback = kj::mv(writeCb));\n\n  KJ_IF_SOME(db, maybeDb) {\n    for (auto& listener: resetListeners) {\n      listener.beforeSqliteReset();\n    }\n\n    auto err = sqlite3_close(&db);\n    KJ_REQUIRE(err == SQLITE_OK, \"can't reset() database because dependent objects still exist\",\n        sqlite3_errstr(err));\n\n    maybeDb = kj::none;\n    vfs.directory.remove(path);\n  }\n\n  KJ_ON_SCOPE_FAILURE(maybeDb = kj::none);\n  init(kj::WriteMode::CREATE | kj::WriteMode::MODIFY);\n\n  KJ_IF_SOME(resetCb, afterResetCallback) {\n    resetCb(*this);\n  }\n}\n\nbool SqliteDatabase::isAuthorized(int actionCode,\n    kj::Maybe<kj::StringPtr> param1,\n    kj::Maybe<kj::StringPtr> param2,\n    kj::Maybe<kj::StringPtr> dbName,\n    kj::Maybe<kj::StringPtr> triggerName) {\n  const Regulator& regulator = KJ_UNWRAP_OR(currentRegulator, {\n    // We're not currently preparing a statement, so we didn't expect the authorizer callback to\n    // run. We blanket-deny in this case as a precaution.\n    KJ_LOG(ERROR, \"SQLite authorizer callback invoked at unexpected time\", kj::getStackTrace());\n    return false;\n  });\n\n  KJ_IF_SOME(t, triggerName) {\n    if (!regulator.isAllowedTrigger(t)) {\n      // Log an error because it seems really suspicious if a trigger runs when it's not allowed.\n      // I want to understand if this can even happen.\n      KJ_LOG(ERROR, \"disallowed trigger somehow ran in trusted scope?\", t, kj::getStackTrace());\n\n      // TODO(security): Is it better to return SQLITE_IGNORE to ignore the trigger? I don't fully\n      //   understand the implications of SQLITE_IGNORE. The documentation mentions that in the\n      //   case of SQLITE_DELETE, it doesn't actually ignore the delete, which is weird. Hopefully\n      //   it's impossible for people to register a trigger on protected tables in the first place,\n      //   so triggers will never run.\n      return false;\n    }\n  }\n\n  // For some reason, for these two operations, SQLite sends the DB Name through as param1, with\n  // the table name (for ALTER_TABLE) in param2 instead of param1 like all other table operations.\n  // For simplicity, and because the following comment precedes sqlite3_set_authorizer in sqlite.h:\n  //\n  //     > The 5th parameter to the authorizer callback is the name of the database\n  //     > (\"main\", \"temp\", etc.) if applicable.\n  //\n  // we are treating this as an SQLite bug and swapping the values around.\n  if (actionCode == SQLITE_ALTER_TABLE || actionCode == SQLITE_DETACH) {\n    auto swap = param1;  // contains dbName\n    param1 = param2;     // contains table name (for SQLITE_ALTER_TABLE, null otherwise)\n    param2 = dbName;     // should always be null\n    dbName = swap;\n  }\n\n  KJ_IF_SOME(d, dbName) {\n    if (d == \"temp\"_kj) {\n      return isAuthorizedTemp(actionCode, param1, param2, regulator);\n    } else if (d != \"main\"_kj) {\n      // We don't allow opening multiple databases (except for 'main' and the 'temp'\n      // temporary database), as our storage engine is not designed to track multiple\n      // files on-disk.\n      return false;\n    }\n  }\n\n  if (&regulator == &TRUSTED && actionCode != SQLITE_TRANSACTION &&\n      actionCode != SQLITE_SAVEPOINT) {\n    // Everything is allowed for trusted queries. (But transactions and savepoints need special\n    // handling below.)\n    return true;\n  }\n\n  switch (actionCode) {\n      // ---------------------------------------------------------------\n      // Stuff that is (sometimes) allowed\n\n    case SQLITE_SELECT: /* NULL            NULL            */\n      // Yes, SELECT statements are allowed. (Note that if the SELECT names any tables, a separate\n      // SQLITE_READ will be authorized for each one.)\n      KJ_ASSERT(param1 == kj::none);\n      KJ_ASSERT(param2 == kj::none);\n      return true;\n\n    case SQLITE_CREATE_TABLE: /* Table Name      NULL            */\n    case SQLITE_DELETE:       /* Table Name      NULL            */\n    case SQLITE_DROP_TABLE:   /* Table Name      NULL            */\n    case SQLITE_INSERT:       /* Table Name      NULL            */\n    case SQLITE_CREATE_VIEW:  /* View Name       NULL            */\n    case SQLITE_DROP_VIEW:    /* View Name       NULL            */\n    case SQLITE_REINDEX:      /* Index Name      NULL            */\n      KJ_ASSERT(param2 == kj::none);\n      return regulator.isAllowedName(KJ_ASSERT_NONNULL(param1));\n\n    case SQLITE_ANALYZE: /* Table Name      NULL            */\n      KJ_ASSERT(param2 == kj::none);\n      // We allow all names (including names where isAllowedName() would return false) because\n      // `PRAGMA optimize` issues an ANALYZE statement with no arguments and a SQLite ANALYZE\n      // statement with no parameters will analyze all tables, including otherwise restricted\n      // tables.\n      //\n      // The ANALYZE statement records information about the distribution of rows in each index in\n      // the database in a special sqlite_stat1 table.  While the sqlite_stat1 table leaks metadata\n      // about restricted tables (like the names of indices and the sizes of those tables), the\n      // sqlite_stat1 does not contain data from the restricted tables.  As such, it's OK to allow\n      // users to ANALYZE restricted tables.\n      //\n      // Note that users can *modify* the sqlite_stat1 table, which means that they can make the\n      // query planner work in suboptimal ways by writing bogus data to the table.\n      //\n      // See https://www.sqlite.org/fileformat2.html#stat1tab for more details.\n      return true;\n\n    case SQLITE_ALTER_TABLE: /* Table Name      NULL (modified) */\n      return regulator.isAllowedName(KJ_ASSERT_NONNULL(param1));\n\n    case SQLITE_READ:   /* Table Name      Column Name     */\n    case SQLITE_UPDATE: /* Table Name      Column Name     */\n      return regulator.isAllowedName(KJ_ASSERT_NONNULL(param1));\n\n    case SQLITE_CREATE_INDEX:   /* Index Name      Table Name      */\n    case SQLITE_DROP_INDEX:     /* Index Name      Table Name      */\n    case SQLITE_CREATE_TRIGGER: /* Trigger Name    Table Name      */\n    case SQLITE_DROP_TRIGGER:   /* Trigger Name    Table Name      */\n      return regulator.isAllowedName(KJ_ASSERT_NONNULL(param1)) &&\n          regulator.isAllowedName(KJ_ASSERT_NONNULL(param2));\n\n    case SQLITE_TRANSACTION: /* Operation       NULL            */\n    {\n      if (!regulator.allowTransactions()) {\n        return false;\n      }\n\n      kj::StringPtr op = KJ_ASSERT_NONNULL(param1);\n      StateChange change;\n      if (op == \"BEGIN\") {\n        change = BeginTxn{kj::none};\n      } else if (op == \"COMMIT\") {\n        change = CommitTxn{kj::none};\n      } else if (op == \"ROLLBACK\") {\n        change = RollbackTxn{kj::none};\n      } else {\n        KJ_FAIL_ASSERT(\"unknown SQLITE_TRANSACTION op\", op);\n      }\n      KJ_IF_SOME(ctx, currentParseContext) {\n        ctx.stateChange = kj::mv(change);\n      }\n\n      KJ_ASSERT(param2 == kj::none);\n      return true;\n    }\n\n    case SQLITE_SAVEPOINT: /* Operation       Savepoint Name  */\n    {\n      kj::String name = kj::str(KJ_ASSERT_NONNULL(param2));\n      if (!regulator.allowTransactions() || !regulator.isAllowedName(name)) {\n        return false;\n      }\n\n      kj::StringPtr op = KJ_ASSERT_NONNULL(param1);\n      StateChange change;\n      if (op == \"BEGIN\") {\n        change = BeginTxn{kj::mv(name)};\n      } else if (op == \"RELEASE\") {\n        change = CommitTxn{kj::mv(name)};\n      } else if (op == \"ROLLBACK\") {\n        change = RollbackTxn{kj::mv(name)};\n      } else {\n        KJ_FAIL_ASSERT(\"unknown SQLITE_TRANSACTION op\", op);\n      }\n      KJ_IF_SOME(ctx, currentParseContext) {\n        ctx.stateChange = kj::mv(change);\n      }\n\n      return true;\n    }\n\n    case SQLITE_PRAGMA: /* Pragma Name     1st arg or NULL */\n      // We currently only permit a few pragmas.\n      {\n        kj::StringPtr pragma = KJ_ASSERT_NONNULL(param1);\n\n        if (pragma == \"table_list\") {\n          // Annoyingly, this will list internal tables. However, the existence of these tables\n          // isn't really a secret, we just don't want people to access them.\n          return true;\n          // TODO function_list & pragma_list should be authorized but return\n          // ALLOWED_SQLITE_FUNCTIONS & ALLOWED_[READ|WRITE]_PRAGMAS\n          // respectively\n        } else if (pragma == \"table_info\" || pragma == \"table_xinfo\") {\n          // Allow if the specific named table is not protected.\n          KJ_IF_SOME(name, param2) {\n            return regulator.isAllowedName(name);\n          } else {\n            return false;  // shouldn't happen?\n          }\n        }\n\n        static const kj::HashMap<kj::StringPtr, PragmaSignature> allowedPragmas = []() {\n          kj::HashMap<kj::StringPtr, PragmaSignature> result;\n          for (auto& [name, signature]: ALLOWED_PRAGMAS) {\n            result.insert(name, signature);\n          }\n          return result;\n        }();\n\n        PragmaSignature sig = KJ_UNWRAP_OR(allowedPragmas.find(pragma), return false);\n        switch (sig) {\n          case PragmaSignature::NO_ARG:\n            return param2 == kj::none;\n          case PragmaSignature::BOOLEAN: {\n            // We allow omitting the argument in order to read back the current value.\n            auto val = KJ_UNWRAP_OR(param2, return true).asArray();\n\n            // SQLite offers many different ways to express booleans...\n\n            // They can be quoted. Remove quotes if present.\n            if (val.size() >= 2 && (val.front() == '\\'' || val.front() == '\\\"') &&\n                val.back() == val.front()) {\n              val = val.slice(1, val.size() - 1);\n            }\n\n            // Compare against every possible representation. Case-insensitive!\n            return strncasecmp(val.begin(), \"true\", 4) == 0 ||\n                strncasecmp(val.begin(), \"false\", 5) == 0 ||\n                strncasecmp(val.begin(), \"yes\", 3) == 0 || strncasecmp(val.begin(), \"no\", 2) == 0 ||\n                strncasecmp(val.begin(), \"on\", 2) == 0 || strncasecmp(val.begin(), \"off\", 3) == 0 ||\n                strncasecmp(val.begin(), \"1\", 1) == 0 || strncasecmp(val.begin(), \"0\", 1) == 0;\n          }\n          case PragmaSignature::OBJECT_NAME: {\n            // Argument is required.\n            auto val = KJ_UNWRAP_OR(param2, return false);\n            return regulator.isAllowedName(val);\n          }\n          case PragmaSignature::OPTIONAL_OBJECT_NAME: {\n            auto val = KJ_UNWRAP_OR(param2, return true);\n            return regulator.isAllowedName(val);\n          }\n          case PragmaSignature::NULL_OR_NUMBER: {\n            // Argument is not required\n            auto val = KJ_UNWRAP_OR(param2, return true);\n            // val is allowed if it parses to an integer\n            return val.tryParseAs<int32_t>() != kj::none;\n          }\n          case PragmaSignature::NULL_NUMBER_OR_OBJECT_NAME: {\n            // Argument is not required\n            auto val = KJ_UNWRAP_OR(param2, return true);\n            // val is allowed if it parses to an integer\n            if (val.tryParseAs<uint>() != kj::none) return true;\n            // Otherwise, val must be the name of an object the user has access to\n            return regulator.isAllowedName(val);\n          }\n        }\n        KJ_UNREACHABLE;\n      }\n\n      return false;\n\n    case SQLITE_FUNCTION: /* NULL            Function Name   */\n    {\n      static const kj::HashSet<kj::StringPtr> allowSet = []() {\n        kj::HashSet<kj::StringPtr> result;\n        for (const kj::StringPtr& func: ALLOWED_SQLITE_FUNCTIONS) {\n          result.insert(func);\n        }\n        return result;\n      }();\n      return allowSet.contains(KJ_ASSERT_NONNULL(param2));\n    }\n\n      // ---------------------------------------------------------------\n      // Stuff that is never allowed\n\n    case SQLITE_CREATE_VTABLE: /* Table Name      Module Name     */\n    case SQLITE_DROP_VTABLE:   /* Table Name      Module Name     */\n      // Virtual tables are tables backed by some native-code callbacks.\n      // We don't support these except for FTS5 (Full Text Search) https://www.sqlite.org/fts5.html\n      // (Which also includes fts5vocab: \"[fts5vocab] is available whenever FTS5 is\")\n      {\n        KJ_IF_SOME(moduleName, param2) {\n          if (strcasecmp(moduleName.begin(), \"fts5\") == 0 ||\n              strcasecmp(moduleName.begin(), \"fts5vocab\") == 0) {\n            return true;\n          }\n        }\n        return false;\n      }\n\n    case SQLITE_ATTACH: /* Filename        NULL            */\n    case SQLITE_DETACH: /* Table Name      NULL (modified) */\n      // We do not support attached databases. It seems unlikely that we ever will.\n      return false;\n\n    case SQLITE_CREATE_TEMP_TABLE:   /* Table Name      NULL            */\n    case SQLITE_DROP_TEMP_TABLE:     /* Table Name      NULL            */\n    case SQLITE_CREATE_TEMP_INDEX:   /* Index Name      Table Name      */\n    case SQLITE_DROP_TEMP_INDEX:     /* Index Name      Table Name      */\n    case SQLITE_CREATE_TEMP_TRIGGER: /* Trigger Name    Table Name      */\n    case SQLITE_DROP_TEMP_TRIGGER:   /* Trigger Name    Table Name      */\n    case SQLITE_CREATE_TEMP_VIEW:    /* View Name       NULL            */\n    case SQLITE_DROP_TEMP_VIEW:      /* View Name       NULL            */\n      // TODO(someday): Allow temporary tables. Creating a temporary table actually causes\n      //   SQLite to open a separate temporary file to place the data in. Currently, our storage\n      //   engine has no support for this.\n      return false;\n\n    case SQLITE_RECURSIVE: /* NULL            NULL            */\n      // Recursive select, this is fine.\n      return true;\n\n    case SQLITE_COPY: /* No longer used */\n      // These are operations we simply don't support today.\n      return false;\n\n    default:\n      KJ_LOG(WARNING, \"unknown SQLite action\", actionCode);\n      return false;\n  }\n}\n\n// Temp databases have very restricted operations\nbool SqliteDatabase::isAuthorizedTemp(int actionCode,\n    const kj::Maybe<kj::StringPtr>& param1,\n    const kj::Maybe<kj::StringPtr>& param2,\n    const Regulator& regulator) {\n\n  switch (actionCode) {\n    case SQLITE_READ:   /* Table Name      Column Name     */\n    case SQLITE_UPDATE: /* Table Name      Column Name     */\n      return regulator.isAllowedName(KJ_ASSERT_NONNULL(param1));\n    default:\n      return false;\n  }\n}\n\n// Set up security restrictions.\n// See: https://www.sqlite.org/security.html\nvoid SqliteDatabase::setupSecurity(sqlite3* db) {\n  // 1. Set defensive mode.\n  SQLITE_CALL_NODB(sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, nullptr));\n\n  // 2. Reduce limits\n  // We use the suggested limits from the web site. Note that sqlite3_limit() does NOT return an\n  // error code; it returns the old limit.\n\n  // This limit is set higher than what is suggested on sqlite.org/security.html\n  // because we want to allow storing values of 1MiB, and we added some extra\n  // padding on top of that\n  sqlite3_limit(db, SQLITE_LIMIT_LENGTH, 2200000);\n  sqlite3_limit(db, SQLITE_LIMIT_SQL_LENGTH, 100000);\n  sqlite3_limit(db, SQLITE_LIMIT_COLUMN, 100);\n  sqlite3_limit(db, SQLITE_LIMIT_EXPR_DEPTH, 100);\n  // Enforces limits on UNION/UNION ALL/INTERSECT/etc\n  // https://www.sqlite.org/limits.html#max_compound_select\n  sqlite3_limit(db, SQLITE_LIMIT_COMPOUND_SELECT, 5);\n  sqlite3_limit(db, SQLITE_LIMIT_VDBE_OP, 25000);\n  // For SQLITE_LIMIT_FUNCTION_ARG we use the default instead of the \"security\" recommendation\n  // because there are too many valid use cases for large argument lists, especially json_object.\n  sqlite3_limit(db, SQLITE_LIMIT_FUNCTION_ARG, 127);\n  sqlite3_limit(db, SQLITE_LIMIT_ATTACHED, 0);\n  sqlite3_limit(db, SQLITE_LIMIT_LIKE_PATTERN_LENGTH, 50);\n  sqlite3_limit(db, SQLITE_LIMIT_VARIABLE_NUMBER, 100);\n  sqlite3_limit(db, SQLITE_LIMIT_TRIGGER_DEPTH, 10);\n  sqlite3_limit(db, SQLITE_LIMIT_WORKER_THREADS, 0);\n\n  // 3. Setup authorizer.\n  SQLITE_CALL_NODB(sqlite3_set_authorizer(db,\n      [](void* userdata, int actionCode, const char* param1, const char* param2, const char* dbName,\n          const char* triggerName) {\n    try {\n      return reinterpret_cast<SqliteDatabase*>(userdata)->isAuthorized(actionCode,\n                 toMaybeString(param1), toMaybeString(param2), toMaybeString(dbName),\n                 toMaybeString(triggerName))\n          ? SQLITE_OK\n          : SQLITE_DENY;\n    } catch (kj::Exception& e) {\n      // We'll crash if we throw to SQLite. Instead, shove the error into the parse context and\n      // report authorization denied. We'll pull it back out later.\n      KJ_IF_SOME(context, reinterpret_cast<SqliteDatabase*>(userdata)->currentParseContext) {\n        context.authError = kj::mv(e);\n      } else {\n        KJ_LOG(ERROR, e);\n      }\n      return SQLITE_DENY;\n    }\n  },\n      this));\n\n  // 4. Set a progress handler or use interrupt() to limit CPU time.\n  // This happens inside LimitEnforcer.\n\n  // 5. Limit heap size.\n  // Annoyingly, this sets a process-wide limit. We'll set 128MB \"soft\" limit (to try to control\n  // how much page caching SQLite does) and 512MB \"hard\" limit (to block DoS attacks from taking\n  // down the whole system).\n  // TODO(perf): Revisit as popularity grows. Maybe make configurable? Maybe patch SQLite to allow\n  //   these to be controlled per-database? Is page caching even all that important when the kernel\n  //   does its own page caching?\n  static bool doOnce KJ_UNUSED = []() {\n    sqlite3_soft_heap_limit64(128u << 20);\n    sqlite3_hard_heap_limit64(512u << 20);\n    return false;\n  }();\n\n  // 6. Set SQLITE_MAX_ALLOCATION_SIZE compile flag.\n  // (handled in BUILD.sqlite3)\n\n  // 7. Consider giving SQLite a fixed heap space.\n  // This is suggested mainly for embedded systems. It involves giving SQLite a fixed preallocated\n  // heap space which the library restricts itself to instead of using malloc. We probably don't\n  // want this.\n\n  // 8. Set the SQLITE_PRINTF_PRECISION_LIMIT compile flag.\n  // (handled in BUILD.sqlite3)\n}\n\nSqliteDatabase::Statement SqliteDatabase::prepare(\n    const Regulator& regulator, kj::StringPtr sqlCode) {\n  return Statement(\n      *this, regulator, prepareSql(regulator, sqlCode, SQLITE_PREPARE_PERSISTENT, SINGLE));\n}\n\nSqliteDatabase::StatementAndEffect& SqliteDatabase::Statement::prepareForExecution() {\n  for (auto& stmt: prelude) {\n    stmt.run();\n  }\n\n  KJ_IF_SOME(sqlCode, stmt.tryGet<kj::String>()) {\n    // Database was reset. Recompile the statement against the new database. (This could throw,\n    // of course, if the statement depends on tables that haven't been recreated yet.)\n    //\n    // We use the MULTI flag here in case this Statement was created by prepareMulti(). If multiple\n    // statements are parsed, they'll be added to our `prelude`, and also executed immediately.\n    stmt = db.prepareSql(regulator, sqlCode, SQLITE_PREPARE_PERSISTENT, MULTI, prelude);\n  }\n\n  return KJ_ASSERT_NONNULL(stmt.tryGet<StatementAndEffect>());\n}\n\nvoid SqliteDatabase::Statement::beforeSqliteReset() {\n  KJ_IF_SOME(prepared, stmt.tryGet<StatementAndEffect>()) {\n    // Pull the original SQL code out of the statement and store it.\n    stmt = kj::str(sqlite3_sql(prepared.statement));\n  }\n}\n\nSqliteDatabase::Query::Query(SqliteDatabase& db,\n    QueryOptions options,\n    Statement& statement,\n    kj::ArrayPtr<const ValuePtr> bindings)\n    : ResetListener(db),\n      regulator(options.regulator),\n      maybeStatement(statement.prepareForExecution()),\n      queryEvent(this->db.sqliteObserver) {\n  // If we throw from the constructor, the destructor won't run. Need to call destroy() explicitly.\n  KJ_ON_SCOPE_FAILURE(destroy());\n  init(bindings);\n}\n\nSqliteDatabase::Query::Query(SqliteDatabase& db,\n    QueryOptions options,\n    kj::StringPtr sqlCode,\n    kj::ArrayPtr<const ValuePtr> bindings)\n    : ResetListener(db),\n      regulator(options.regulator),\n      ownStatement(db.prepareSql(regulator, sqlCode, 0, MULTI)),\n      maybeStatement(ownStatement),\n      queryEvent(this->db.sqliteObserver) {\n  // If we throw from the constructor, the destructor won't run. Need to call destroy() explicitly.\n  KJ_ON_SCOPE_FAILURE(destroy());\n  init(bindings);\n}\n\nSqliteDatabase::Query::~Query() noexcept(false) {\n  destroy();\n}\n\nvoid SqliteDatabase::Query::destroy() {\n  if (regulator.shouldAddQueryStats()) {\n    //Update the db stats that we have collected for the query\n    db.sqliteObserver.addQueryStats(rowsRead, rowsWritten);\n  }\n\n  queryEvent.setQueryEventStats(rowsRead, rowsWritten, !(regulator.shouldAddQueryStats()));\n\n  try {\n    kj::StringPtr statement = sqlite3_sql(getStatementAndEffect().statement);\n    queryEvent.setQueryStatement(\n        kj::heapString(statement.slice(0, kj::min(statement.size(), RA_MAX_METRICS_QUERY_SIZE))));\n  } catch (kj::Exception& e) {\n    kj::StringPtr errorDescription = e.getDescription();\n    queryEvent.setQueryErrorDescription(kj::heapString(\n        errorDescription.slice(0, kj::min(RA_MAX_METRICS_QUERY_SIZE, errorDescription.size()))));\n  }\n\n  // We only need to reset the statement if we don't own it. If we own it, it's about to be\n  // destroyed anyway.\n  if (ownStatement.statement.get() == nullptr) {\n    KJ_IF_SOME(statement, maybeStatement) {\n      // The error code returned by sqlite3_reset() actually represents the last error encountered\n      // when stepping the statement. This doesn't mean that the reset failed.\n      sqlite3_reset(statement.statement);\n\n      // sqlite3_clear_bindings() returns int, but there is no documentation on how the return code\n      // should be interpreted, so we ignore it.\n      sqlite3_clear_bindings(statement.statement);\n\n      // Reset the rows read/written counters.\n      sqlite3_stmt_status(statement.statement, LIBSQL_STMTSTATUS_ROWS_READ, 1);\n      sqlite3_stmt_status(statement.statement, LIBSQL_STMTSTATUS_ROWS_WRITTEN, 1);\n    }\n  }\n}\n\nvoid SqliteDatabase::Query::checkRequirements(size_t size) {\n  if (regulator.shouldAddQueryStats()) {\n    KJ_IF_SOME(actorAccountLimits, db.actorAccountLimits) {\n      actorAccountLimits.requireActorCanExecuteQueries();\n    }\n  }\n\n  sqlite3_stmt* statement = getStatement();\n\n  SQLITE_REQUIRE(!sqlite3_stmt_busy(statement), kj::none,\n      \"A SQL prepared statement can only be executed once at a time.\");\n  SQLITE_REQUIRE(size == sqlite3_bind_parameter_count(statement), kj::none,\n      \"Wrong number of parameter bindings for SQL query.\");\n\n  KJ_IF_SOME(cb, db.onWriteCallback) {\n    if (!sqlite3_stmt_readonly(statement)) {\n      cb(allowUnconfirmed);\n    }\n  }\n}\n\nvoid SqliteDatabase::Query::init(kj::ArrayPtr<const ValuePtr> bindings) {\n  checkRequirements(bindings.size());\n\n  for (auto i: kj::indices(bindings)) {\n    bind(i, bindings[i]);\n  }\n\n  nextRow(/*first=*/true);\n}\n\nvoid SqliteDatabase::Query::bind(uint i, ValuePtr value) {\n  sqlite3_stmt* statement = getStatement();\n\n  KJ_SWITCH_ONEOF(value) {\n    KJ_CASE_ONEOF(blob, kj::ArrayPtr<const byte>) {\n      SQLITE_CALL(sqlite3_bind_blob(statement, i + 1, blob.begin(), blob.size(), SQLITE_STATIC));\n    }\n    KJ_CASE_ONEOF(text, kj::StringPtr) {\n      SQLITE_CALL(sqlite3_bind_text(statement, i + 1, text.begin(), text.size(), SQLITE_STATIC));\n    }\n    KJ_CASE_ONEOF(n, int64_t) {\n      SQLITE_CALL(sqlite3_bind_int64(statement, i + 1, static_cast<long long>(n)));\n    }\n    KJ_CASE_ONEOF(x, double) {\n      SQLITE_CALL(sqlite3_bind_double(statement, i + 1, x));\n    }\n    KJ_CASE_ONEOF(_, decltype(nullptr)) {\n      SQLITE_CALL(sqlite3_bind_null(statement, i + 1));\n    }\n  }\n}\n\nuint64_t SqliteDatabase::Query::getRowsRead() {\n  sqlite3_stmt* statement = getStatement();\n  KJ_REQUIRE(statement != nullptr);\n  return sqlite3_stmt_status(statement, LIBSQL_STMTSTATUS_ROWS_READ, 0);\n}\n\nuint64_t SqliteDatabase::Query::getRowsWritten() {\n  sqlite3_stmt* statement = getStatement();\n  return sqlite3_stmt_status(statement, LIBSQL_STMTSTATUS_ROWS_WRITTEN, 0);\n}\n\nvoid SqliteDatabase::Query::bind(uint i, kj::ArrayPtr<const byte> value) {\n  sqlite3_stmt* statement = getStatement();\n  SQLITE_CALL(sqlite3_bind_blob(statement, i + 1, value.begin(), value.size(), SQLITE_STATIC));\n}\n\nvoid SqliteDatabase::Query::bind(uint i, kj::StringPtr value) {\n  sqlite3_stmt* statement = getStatement();\n  SQLITE_CALL(sqlite3_bind_text(statement, i + 1, value.begin(), value.size(), SQLITE_STATIC));\n}\n\nvoid SqliteDatabase::Query::bind(uint i, long long value) {\n  sqlite3_stmt* statement = getStatement();\n  SQLITE_CALL(sqlite3_bind_int64(statement, i + 1, value));\n}\n\nvoid SqliteDatabase::Query::bind(uint i, double value) {\n  sqlite3_stmt* statement = getStatement();\n  SQLITE_CALL(sqlite3_bind_double(statement, i + 1, value));\n}\n\nvoid SqliteDatabase::Query::bind(uint i, decltype(nullptr)) {\n  sqlite3_stmt* statement = getStatement();\n  SQLITE_CALL(sqlite3_bind_null(statement, i + 1));\n}\n\nvoid SqliteDatabase::Query::nextRow(bool first) {\n  auto& statementAndEffect = getStatementAndEffect();\n  sqlite3_stmt* statement = statementAndEffect.statement;\n\n  KJ_ASSERT(db.currentStatement == kj::none, \"recursive nextRow()?\");\n  KJ_DEFER(db.currentStatement = kj::none);\n  db.currentStatement = *statement;\n\n  // The statement could be \"re-prepared\" during sqlite3_step, so we must set up the regulator.\n  KJ_ASSERT(db.currentRegulator == kj::none, \"nextRow() during prepare()?\");\n  KJ_DEFER(db.currentRegulator = kj::none);\n  db.currentRegulator = regulator;\n\n  SQLITE_CALL_SCOPE {\n    int err = sqlite3_step(statement);\n    queryEvent.setQueryResult(err);\n    // TODO(perf): This is slightly inefficient to call for every row read, but not bad enough to\n    // fix it immediately. The alternate way would be to getRowsRead/Written once when we emit it\n    // in the Dtor, and handle the case where the statement could be null when the Query gets\n    // destructed\n    rowsRead = getRowsRead();\n    rowsWritten = getRowsWritten();\n    if (err == SQLITE_DONE) {\n      done = true;\n    } else if (err != SQLITE_ROW) {\n      SQLITE_CALL_FAILED(\"sqlite3_step()\", err);\n    }\n  }\n\n  if (first) {\n    // A statement's effect is applied on the first step.\n    db.applyChange(statementAndEffect.stateChange);\n  }\n}\n\nuint SqliteDatabase::Query::changeCount() {\n  KJ_REQUIRE(done);\n  KJ_DREQUIRE(\n      columnCount() == 0, \"changeCount() can only be called on INSERT/UPDATE/DELETE queries\");\n  return sqlite3_changes(db);\n}\n\nuint SqliteDatabase::Query::columnCount() {\n  sqlite3_stmt* statement = getStatement();\n  return sqlite3_column_count(statement);\n}\n\nSqliteDatabase::Query::ValuePtr SqliteDatabase::Query::getValue(uint column) {\n  sqlite3_stmt* statement = getStatement();\n  switch (sqlite3_column_type(statement, column)) {\n    case SQLITE_INTEGER:\n      return getInt64(column);\n    case SQLITE_FLOAT:\n      return getDouble(column);\n    case SQLITE_TEXT:\n      return getText(column);\n    case SQLITE_BLOB:\n      return getBlob(column);\n    case SQLITE_NULL:\n      return nullptr;\n  }\n  KJ_UNREACHABLE;\n}\n\nkj::StringPtr SqliteDatabase::Query::getColumnName(uint column) {\n  sqlite3_stmt* statement = getStatement();\n  return sqlite3_column_name(statement, column);\n}\n\nkj::ArrayPtr<const byte> SqliteDatabase::Query::getBlob(uint column) {\n  sqlite3_stmt* statement = getStatement();\n  const byte* ptr = reinterpret_cast<const byte*>(sqlite3_column_blob(statement, column));\n  return kj::arrayPtr(ptr, sqlite3_column_bytes(statement, column));\n}\n\nkj::StringPtr SqliteDatabase::Query::getText(uint column) {\n  sqlite3_stmt* statement = getStatement();\n  const char* ptr = reinterpret_cast<const char*>(sqlite3_column_text(statement, column));\n  return kj::StringPtr(ptr, sqlite3_column_bytes(statement, column));\n}\n\nint SqliteDatabase::Query::getInt(uint column) {\n  sqlite3_stmt* statement = getStatement();\n  return sqlite3_column_int(statement, column);\n}\n\nint64_t SqliteDatabase::Query::getInt64(uint column) {\n  sqlite3_stmt* statement = getStatement();\n  return sqlite3_column_int64(statement, column);\n}\n\ndouble SqliteDatabase::Query::getDouble(uint column) {\n  sqlite3_stmt* statement = getStatement();\n  return sqlite3_column_double(statement, column);\n}\n\nbool SqliteDatabase::Query::isNull(uint column) {\n  sqlite3_stmt* statement = getStatement();\n  return sqlite3_column_type(statement, column) == SQLITE_NULL;\n}\n\nSqliteDatabase::StatementAndEffect& SqliteDatabase::Query::getStatementAndEffect() {\n  return KJ_UNWRAP_OR(maybeStatement, {\n    regulator.onError(kj::none, \"SQLite query was canceled because the database was deleted.\");\n    KJ_FAIL_REQUIRE(\"query canceled because reset() was called on the database\");\n  });\n}\n\nvoid SqliteDatabase::Query::beforeSqliteReset() {\n  // Note that if we don't own the statement, then `maybeStatement` is probably already dangling\n  // here. Luckily, we don't need to reset it or anything because the statement will be destroyed\n  // by Statement::beforeSqliteReset().\n  maybeStatement = kj::none;\n  ownStatement = {};\n}\n\n// =======================================================================================\n// VFS\n\n// -----------------------------------------------------------------------------\n// Code to wrap SQLite's native VFS so that it can be rooted in some `kj::Directory`, where that\n// directory points at a real disk directory.\n//\n// A native disk `kj::Directory` -- at least on Unix -- wraps an open file descriptor, pointing at\n// a directory. It does NOT keep track of the directory's path on disk. In fact, the directory can\n// be moved or renamed, and `kj::Directory` will continue to point at it.\n//\n// There is no portable way to query the current path of a directory. In order to open files within\n// a directory given only the directory descriptor, you must use syscalls like `openat()`, which\n// take a directory file descriptor to use as the root.\n//\n// SQLite's native VFS, however, is not openat()-aware. Luckily, it _does_ provide the ability to\n// redirect its syscalls to custom implementations. So we can intercept `open()` and make it use\n// `openat()` instead! With a little thread-local hackery, we can make sure to use the desired root\n// directory descriptor from the `kj::Directory`.\n//\n// Of course, SQLite also lets us virtualize the whole filesystem at a higher level. Why go to all\n// the bother to hack it at a low level rather than just implement an entire VFS based on the\n// `kj::Directory` interface? The problem is, SQLite's native VFS contains a ton of code to handle\n// all sorts of corner cases and do things just right. When our files are actually on real disk,\n// we want to leverage all that code. If we can just make it interpret paths differently, then we\n// can reuse the rest of the implementation.\n\n#if !_WIN32\nnamespace {\n\nstatic thread_local int currentVfsRoot = AT_FDCWD;\n// We will tell SQLite to use alternate implementations of path-oriented syscalls which use the\n// `*at()` versions of the calls with `currentVfsRoot` as the directory descriptor. When the\n// descriptor is `AT_FDCWD`, this will naturally reproduce the behavior of the non-`at()` versions.\n// We temporarily swap this for a real descriptor when our custom VFS wrapper is being invoked.\n\nstatic int replaced_open(const char* path, int flags, int mode) {\n  return openat(currentVfsRoot, path, flags, mode);\n}\nstatic int replaced_access(const char* path, int type) {\n  return faccessat(currentVfsRoot, path, type, 0);\n}\nstatic char* replaced_getcwd(char* buf, size_t size) noexcept {\n  KJ_REQUIRE(currentVfsRoot == AT_FDCWD,\n      \"SQLite custom VFS shouldn't call getcwd() because we overrode xFullPathname\");\n  return getcwd(buf, size);\n}\nstatic int replaced_stat(const char* path, struct stat* stats) {\n  return fstatat(currentVfsRoot, path, stats, 0);\n}\nstatic int replaced_unlink(const char* path) {\n  return unlinkat(currentVfsRoot, path, 0);\n}\nstatic int replaced_mkdir(const char* path, mode_t mode) {\n  return mkdirat(currentVfsRoot, path, mode);\n}\nstatic int replaced_rmdir(const char* path) {\n  return unlinkat(currentVfsRoot, path, AT_REMOVEDIR);\n}\nstatic ssize_t replaced_readlink(const char* path, char* buf, size_t len) {\n  return readlinkat(currentVfsRoot, path, buf, len);\n}\nstatic int replaced_lstat(const char* path, struct stat* stats) {\n  return fstatat(currentVfsRoot, path, stats, AT_SYMLINK_NOFOLLOW);\n}\n\n};  // namespace\n\n// The sqlite3_file implementation we use when wrapping the native filesystem.\nstruct SqliteDatabase::Vfs::WrappedNativeFileImpl: public sqlite3_file {\n  const Vfs* vfs;\n  int rootFd;\n\n  // It's expected that the wrapped sqlite_file begins in memory immediately after this object.\n  sqlite3_file* getWrapped() {\n    return reinterpret_cast<sqlite3_file*>(this + 1);\n  }\n\n  static const sqlite3_io_methods METHOD_TABLE;\n};\n\n// This completely nutso template generates wrapper functions for each of the function pointer\n// members of sqlite3_vfs. The wrapper function temporarily sets `currentVfsRoot` to the FD\n// of the directory from the SqliteDatabase::Vfs instance in use, then invokes the same function\n// on the underlying native VFS.\ntemplate <typename Result,\n    typename... Params,\n    Result (*sqlite3_vfs::*slot)(sqlite3_vfs* vfs, Params...)>\nstruct SqliteDatabase::Vfs::MethodWrapperHack<Result (*sqlite3_vfs::*)(sqlite3_vfs* vfs, Params...),\n    slot> {\n  static Result wrapper(sqlite3_vfs* vfs, Params... params) noexcept {\n    auto& self = *reinterpret_cast<SqliteDatabase::Vfs*>(vfs->pAppData);\n    KJ_ASSERT(currentVfsRoot == AT_FDCWD);\n    currentVfsRoot = self.rootFd;\n    KJ_DEFER(currentVfsRoot = AT_FDCWD);\n    return (self.native.*slot)(&self.native, params...);\n  }\n};\n\n// Specialization of MethodWrapperHack for wrapping methods of sqlite_file, aka\n// sqlite3_io_methods. Unfortunately, some file methods go back and perform filesystem ops. In\n// particular, accessing shared memory associated with a file actually opens another adjacent\n// file.\ntemplate <typename Result,\n    typename... Params,\n    Result (*sqlite3_io_methods::*slot)(sqlite3_file* file, Params...)>\nstruct SqliteDatabase::Vfs::\n    MethodWrapperHack<Result (*sqlite3_io_methods::*)(sqlite3_file* file, Params...), slot> {\n  static Result wrapper(sqlite3_file* file, Params... params) noexcept {\n    auto wrapper = static_cast<WrappedNativeFileImpl*>(file);\n    file = wrapper->getWrapped();\n    KJ_ASSERT(currentVfsRoot == AT_FDCWD);\n    currentVfsRoot = wrapper->rootFd;\n    KJ_DEFER(currentVfsRoot = AT_FDCWD);\n    return (file->pMethods->*slot)(file, params...);\n  }\n};\n\n// clang-format off\n//\n// The code below has a lot of lambdas inside struct initializers, which clang-format does not\n// handle well, making it extremely hard to read if we leave the formatter on.\n\nconst sqlite3_io_methods SqliteDatabase::Vfs::WrappedNativeFileImpl::METHOD_TABLE = {\n  .iVersion = 3,\n\n#define WRAP(name)                                                                                 \\\n  .name =                                                                                          \\\n      &MethodWrapperHack<decltype(&sqlite3_io_methods::name), &sqlite3_io_methods::name>::wrapper\n\n  WRAP(xClose),\n  WRAP(xRead),\n  WRAP(xWrite),\n  WRAP(xTruncate),\n  WRAP(xSync),\n  WRAP(xFileSize),\n  WRAP(xLock),\n  WRAP(xUnlock),\n  WRAP(xCheckReservedLock),\n  WRAP(xFileControl),\n  WRAP(xSectorSize),\n  .xDeviceCharacteristics = [](sqlite3_file* file) noexcept -> int {\n    auto wrapper = static_cast<WrappedNativeFileImpl*>(file);\n    file = wrapper->getWrapped();\n    KJ_ASSERT(currentVfsRoot == AT_FDCWD);\n    currentVfsRoot = wrapper->rootFd;\n    KJ_DEFER(currentVfsRoot = AT_FDCWD);\n    return (file->pMethods->xDeviceCharacteristics)(file) |\n        wrapper->vfs->options.deviceCharacteristics;\n  },\n\n  WRAP(xShmMap),\n  WRAP(xShmLock),\n  WRAP(xShmBarrier),\n  WRAP(xShmUnmap),\n\n  WRAP(xFetch),\n  WRAP(xUnfetch),\n#undef WRAP\n};\n\n// The native VFS gives us the ability to override its syscalls. We need to do so, in\n// particular to force them to use the *at() versions of the calls that accept a directory FD\n// to use as the root.\n//\n// Unfortunately, these overrides are global for the process, with no ability to pass down any\n// context to them. So, we stash the current root FD in `currentVfsRoot` whenever we call into\n// the native VFS. We also don't want to interfere with anything else in the process that is\n// using SQLite directly, so we make sure that when we're not specifically trying to invoke\n// our wrapper, then `currentVfsRoot` is `AT_FDCWD`, which causes the *at() syscalls to match\n// their non-at() versions.\nsqlite3_vfs SqliteDatabase::Vfs::makeWrappedNativeVfs() {\n  static bool registerOnce KJ_UNUSED = ([&]() {\n#define REPLACE_SYSCALL(name)                                                                      \\\n  native.xSetSystemCall(&native, #name, (sqlite3_syscall_ptr)replaced_##name);\n    REPLACE_SYSCALL(open);\n    REPLACE_SYSCALL(access);\n    REPLACE_SYSCALL(getcwd);\n    REPLACE_SYSCALL(stat);\n    REPLACE_SYSCALL(unlink);\n    REPLACE_SYSCALL(mkdir);\n    REPLACE_SYSCALL(rmdir);\n    REPLACE_SYSCALL(readlink);\n    REPLACE_SYSCALL(lstat);\n#undef REPLACE_SYSCALL\n\n    return true;\n  })();\n\n  // We construct a sqlite3_vfs that is basically a copy of the native VFS, except each method is\n  // wrapped so that it sets `currentVfsRoot` while running.\n  return {\n    .iVersion = kj::min(3, native.iVersion),\n    .szOsFile = native.szOsFile + static_cast<int>(sizeof(WrappedNativeFileImpl)),\n    .mxPathname = native.mxPathname,\n    .pNext = nullptr,\n    .zName = name.cStr(),\n    .pAppData = this,\n\n    .xOpen = [](sqlite3_vfs* vfs, sqlite3_filename zName, sqlite3_file* file, int flags,\n                 int* pOutFlags) -> int {\n      // We have to wrap xOpen explicitly because we need to further wrap each created file.\n      //\n      // My trick here is to prefix the native file with a second vtable. So the layout of the\n      // `sqlite3_file` that we construct is actually a simple `struct sqlite_file` (which just\n      // contains a single pointer to sqlite3_io_methods, i.e. the vtable pointer) _followed by_\n      // the regular native file structure.\n\n      auto wrapper = static_cast<WrappedNativeFileImpl*>(file);\n      file = wrapper->getWrapped();\n      file->pMethods = nullptr;\n\n      // Set up currentVfsRoot.\n      auto& self = *reinterpret_cast<const SqliteDatabase::Vfs*>(vfs->pAppData);\n      KJ_ASSERT(currentVfsRoot == AT_FDCWD);\n      currentVfsRoot = self.rootFd;\n      KJ_DEFER(currentVfsRoot = AT_FDCWD);\n\n      int result = self.native.xOpen(&self.native, zName, file, flags, pOutFlags);\n\n      // `xOpen` setting `pMethods` to non-null indicates that `xClose` is needed, i.e. the file\n      // has been constructed. We need our wrapper to match.\n      if (file->pMethods == nullptr) {\n        wrapper->pMethods = nullptr;\n      } else {\n        wrapper->pMethods = &WrappedNativeFileImpl::METHOD_TABLE;\n        wrapper->vfs = &self;\n        wrapper->rootFd = self.rootFd;\n      }\n\n      return result;\n    },\n\n#define WRAP(name)                                                                                 \\\n  .name = &MethodWrapperHack<decltype(&sqlite3_vfs::name), &sqlite3_vfs::name>::wrapper\n\n    WRAP(xDelete),\n    WRAP(xAccess),\n    .xFullPathname = [](sqlite3_vfs*, const char* zName, int nOut, char* zOut) -> int {\n      // Override xFullPathname so that it doesn't rewrite the path at all.\n      size_t len = kj::min(strlen(zName), nOut - 1);\n      memcpy(zOut, zName, len);\n      zOut[len] = 0;\n      return SQLITE_OK;\n    },\n\n    .xDlOpen = nullptr,\n    .xDlError = nullptr,\n    .xDlSym = nullptr,\n    .xDlClose = nullptr,\n    // There is no dlopenat(), but we don't need to support these anyway.\n\n    WRAP(xRandomness),\n    WRAP(xSleep),\n    WRAP(xCurrentTime),\n    WRAP(xGetLastError),\n    WRAP(xCurrentTimeInt64),\n\n    .xSetSystemCall = nullptr,\n    .xGetSystemCall = nullptr,\n    .xNextSystemCall = nullptr,\n  // We don't support further overriding syscalls.\n#undef WRAP\n  };\n}\n#endif  // #if !_WIN32\n\n// -----------------------------------------------------------------------------\n// Code to implement a true SQLite VFS based on `kj::Directory`.\n//\n// This VFS implementation actually delegates to the KJ filesystem interface for everything.\n// This is used only when given a `kj::Directory` that does NOT represent a native file, i.e.\n// one where `getFd()` returns null. This is mainly used for unit tests which want to use in-memory\n// directories.\n\n// Implementation of sqlite3_file.\n//\n// Weirdly, for sqlite3_file, SQLite uses a C++-like inheritance approach, with a separate\n// virtual table that can be shared among all files of the same type. This is different from the\n// way sqlite3_vfs works, where the function pointers are inlined into the sqlite3_vfs struct.\n// In any case, as a result, `FileImpl`, unlike `VfsImpl`, is NOT just a namespace struct, but\n// an actual instance.\nstruct SqliteDatabase::Vfs::FileImpl: public sqlite3_file {\n  const Vfs& vfs;\n  kj::Maybe<const kj::File&> writableFile;\n  kj::Own<const kj::ReadableFile> file;\n\n  kj::Maybe<kj::Own<Lock>> lock;\n  // Rather complicatedly, SQLite doesn't consider the -shm file to be a separate file that it\n  // opens via the VFS, but rather a facet of the database file itself. We implement it using an\n  // entirely different interface anyway.\n  //\n  // We leave this null if the file is not the main database file.\n\n  FileImpl(const Vfs& vfs, kj::Own<const kj::File> file, kj::Maybe<kj::Own<Lock>> lock)\n      : sqlite3_file{.pMethods = &FILE_METHOD_TABLE},\n        vfs(vfs),\n        writableFile(*file),\n        file(kj::mv(file)),\n        lock(kj::mv(lock)) {}\n  FileImpl(const Vfs& vfs, kj::Own<const kj::ReadableFile> file, kj::Maybe<kj::Own<Lock>> lock)\n      : sqlite3_file{.pMethods = &FILE_METHOD_TABLE},\n        vfs(vfs),\n        file(kj::mv(file)),\n        lock(kj::mv(lock)) {}\n\n  static const sqlite3_io_methods FILE_METHOD_TABLE;\n};\n\nconst sqlite3_io_methods SqliteDatabase::Vfs::FileImpl::FILE_METHOD_TABLE = {\n  .iVersion = 3,\n#define WRAP_METHOD(errorCode, block)                                                              \\\n  auto& self KJ_UNUSED = *static_cast<FileImpl*>(file);                                            \\\n  try block catch (kj::Exception& e) {                                                             \\\n    reportVfsErrorCaught(kj::mv(e));                                                               \\\n    return errorCode;                                                                              \\\n  }\n\n  .xClose = [](sqlite3_file* file) noexcept -> int {\n    WRAP_METHOD(SQLITE_OK, {\n      auto& self = *static_cast<FileImpl*>(file);\n\n      // Caller will free the object's memory, but knows nothing of destructors.\n      kj::dtor(self);\n\n      return SQLITE_OK;  // return value is ignored by SQLite\n    });\n  },\n\n  .xRead = [](sqlite3_file* file, void* buffer, int iAmt, sqlite3_int64 iOfst) noexcept -> int {\n    WRAP_METHOD(SQLITE_IOERR_READ, {\n      auto bytes = kj::arrayPtr(reinterpret_cast<byte*>(buffer), iAmt);\n      size_t actual = self.file->read(iOfst, bytes);\n\n      if (actual < iAmt) {\n        bytes.slice(actual).fill(0);\n        return SQLITE_IOERR_SHORT_READ;\n      } else {\n        return SQLITE_OK;\n      }\n    });\n  },\n\n  .xWrite =\n      [](sqlite3_file* file, const void* buffer, int iAmt, sqlite3_int64 iOfst) noexcept -> int {\n    WRAP_METHOD(SQLITE_IOERR_WRITE, {\n      KJ_IF_SOME(writableFile, self.writableFile) {\n        auto bytes = kj::arrayPtr(reinterpret_cast<const byte*>(buffer), iAmt);\n        writableFile.write(iOfst, bytes);\n        return SQLITE_OK;\n      } else {\n        return SQLITE_READONLY;\n      }\n    });\n  },\n\n  .xTruncate = [](sqlite3_file* file, sqlite3_int64 size) noexcept -> int {\n    WRAP_METHOD(SQLITE_IOERR_TRUNCATE, {\n      KJ_IF_SOME(writableFile, self.writableFile) {\n        writableFile.truncate(size);\n        return SQLITE_OK;\n      } else {\n        return SQLITE_READONLY;\n      }\n    });\n  },\n\n  .xSync = [](sqlite3_file* file, int flags) noexcept -> int {\n    WRAP_METHOD(SQLITE_IOERR_FSYNC, {\n      if (flags & SQLITE_SYNC_DATAONLY) {\n        self.file->datasync();\n      } else {\n        self.file->sync();\n      }\n      return SQLITE_OK;\n    });\n  },\n\n  .xFileSize = [](sqlite3_file* file, sqlite3_int64* pSize) noexcept -> int {\n    WRAP_METHOD(SQLITE_IOERR_FSTAT, {\n      *pSize = self.file->stat().size;\n      return SQLITE_OK;\n    });\n  },\n\n  .xLock = [](sqlite3_file* file, int level) noexcept -> int {\n    // Verify that our enum's values match the SQLite constants. (We didn't want to include\n    // sqlite3.h in our header, so defined a parallel enum.)\n    static_assert(Lock::UNLOCKED == SQLITE_LOCK_NONE);\n    static_assert(Lock::SHARED == SQLITE_LOCK_SHARED);\n    static_assert(Lock::RESERVED == SQLITE_LOCK_RESERVED);\n    static_assert(Lock::PENDING == SQLITE_LOCK_PENDING);\n    static_assert(Lock::EXCLUSIVE == SQLITE_LOCK_EXCLUSIVE);\n\n    WRAP_METHOD(SQLITE_IOERR_LOCK, {\n      auto& lock = *KJ_ASSERT_NONNULL(self.lock, \"xLock called on file that isn't main database?\");\n      if (lock.tryIncreaseLevel(static_cast<Lock::Level>(level))) {\n        return SQLITE_OK;\n      } else {\n        return SQLITE_BUSY;\n      }\n    });\n  },\n\n  .xUnlock = [](sqlite3_file* file, int level) noexcept -> int {\n    WRAP_METHOD(SQLITE_IOERR_UNLOCK, {\n      auto& lock = *KJ_ASSERT_NONNULL(self.lock, \"xLock called on file that isn't main database?\");\n      lock.decreaseLevel(static_cast<Lock::Level>(level));\n      return SQLITE_OK;\n    });\n  },\n\n  .xCheckReservedLock = [](sqlite3_file* file, int* pResOut) noexcept -> int {\n    WRAP_METHOD(SQLITE_IOERR_CHECKRESERVEDLOCK, {\n      auto& lock = *KJ_ASSERT_NONNULL(self.lock, \"xLock called on file that isn't main database?\");\n      *pResOut = lock.checkReservedLock();\n      return SQLITE_OK;\n    });\n  },\n\n  .xFileControl = [](sqlite3_file* file, int op, void* pArg) noexcept -> int {\n    // Apparently we can return SQLITE_NOTFOUND for controls we don't implement.\n    return SQLITE_NOTFOUND;\n  },\n  .xSectorSize = [](sqlite3_file* file) noexcept -> int {\n    // This function doesn't return a status code, it returns the size. It's largely a performance\n    // hint, I think. For in-memory file systems, it has no real meaning. 4096 is the value of\n    // SQLITE_DEFAULT_SECTOR_SIZE in the SQLite codebase, though the comments also say the result\n    // is \"almost always 512\".\n    return 4096;\n  },\n  .xDeviceCharacteristics = [](sqlite3_file* file) noexcept -> int {\n    WRAP_METHOD(SQLITE_IOERR, { return self.vfs.options.deviceCharacteristics; });\n  },\n\n  .xShmMap =\n      [](sqlite3_file* file, int iRegion, int szRegion, int bExtend, void volatile** pp) noexcept\n      -> int {\n    WRAP_METHOD(SQLITE_IOERR_SHMMAP, {\n      KJ_ASSERT(iRegion >= 0);\n      KJ_ASSERT(szRegion >= 0);\n      auto& lock = *KJ_ASSERT_NONNULL(self.lock, \"xShmMap called on file that isn't main database?\");\n\n      auto bytes = lock.getSharedMemoryRegion(iRegion, szRegion, bExtend);\n      if (bytes == nullptr) {\n        *pp = nullptr;\n      } else {\n        *pp = bytes.begin();\n      }\n      return SQLITE_OK;\n    });\n  },\n  .xShmLock = [](sqlite3_file* file, int offset, int n, int flags) noexcept -> int {\n    WRAP_METHOD(SQLITE_IOERR_SHMLOCK, {\n      auto& lock = *KJ_ASSERT_NONNULL(self.lock, \"xShmMap called on file that isn't main database?\");\n      if (flags & SQLITE_SHM_LOCK) {\n        if (flags & SQLITE_SHM_EXCLUSIVE) {\n          if (!lock.tryLockWalExclusive(offset, n)) return SQLITE_BUSY;\n        } else {\n          KJ_ASSERT(flags & SQLITE_SHM_SHARED);\n          if (!lock.tryLockWalShared(offset, n)) return SQLITE_BUSY;\n        }\n      } else {\n        KJ_ASSERT(flags & SQLITE_SHM_UNLOCK);\n        if (flags & SQLITE_SHM_EXCLUSIVE) {\n          lock.unlockWalExclusive(offset, n);\n        } else {\n          KJ_ASSERT(flags & SQLITE_SHM_SHARED);\n          lock.unlockWalShared(offset, n);\n        }\n      }\n      return SQLITE_OK;\n    });\n  },\n  .xShmBarrier = [](sqlite3_file*) noexcept -> void {\n    // I don't quite get why this is virtualized. The native implementation does\n    // __sync_synchronize() (equivalent to below, I think) and also \"for redundancy\" locks and\n    // unlocks a mutex.\n    std::atomic_thread_fence(std::memory_order_acq_rel);\n  },\n  .xShmUnmap = [](sqlite3_file* file, int deleteFlag) noexcept -> int {\n    WRAP_METHOD(SQLITE_OK, {\n      auto& lock = *KJ_ASSERT_NONNULL(self.lock, \"xShmMap called on file that isn't main database?\");\n      if (deleteFlag) {\n        lock.clearSharedMemory();\n      }\n      return SQLITE_OK;  // return value is ignored by sqlite\n    });\n  },\n\n  .xFetch = [](sqlite3_file* file, sqlite3_int64 iOfst, int iAmt, void** pp) noexcept -> int {\n    // This is essentially requesting an mmap(). kj::File supports mmap(). Great, right?\n    //\n    // Well, there's a problem. We mostly use this VFS implementation to wrap an in-memory\n    // `kj::File`. Such files support mmap by returning a pointer into the backing store. But\n    // while such a mapping exists, the backing store cannot be resized. So write()s that extend\n    // the file may fail. This does not work for SQLite's use case.\n    //\n    // So, alas, we must act like we don't support this. Luckily, SQLite has fallbacks for this.\n    *pp = nullptr;\n    return SQLITE_OK;\n  },\n  .xUnfetch = [](sqlite3_file* file, sqlite3_int64 iOfst, void* p) noexcept -> int {\n    // Shouldn't ever be called since xFetch() always produces null? But the native implementation\n    // return SQLITE_OK even when mmap is disabled so we will too.\n    return SQLITE_OK;\n  },\n#undef WRAP_METHOD\n};\n\n// SQLite VFS implementation based on abstract `kj::Directory`. This is used only when the\n// directory is NOT a true disk directory.\n//\n// This is a namespace-struct defining static methods to fill in the function pointers of\n// sqlite3_vfs.\nsqlite3_vfs SqliteDatabase::Vfs::makeKjVfs() {\n  return {\n    .iVersion = kj::min(3, native.iVersion), .szOsFile = sizeof(FileImpl),\n\n    // We have no real limit on paths but SQLite likes to allocate buffers of this size whenever\n    // doing path stuff so making it huge would be bad. The default unix implementation uses\n    // 512 as a limit so that \"should be enough for anyone\".\n    .mxPathname = 512,\n\n    .pNext = nullptr,\n    .zName = name.cStr(),\n    .pAppData = this,\n\n#define WRAP_METHOD(errorCode, block)                                                              \\\n  auto& self KJ_UNUSED = *static_cast<const SqliteDatabase::Vfs*>(vfs->pAppData);                  \\\n  try block catch (kj::Exception& e) {                                                             \\\n    KJ_LOG(ERROR, \"SQLite VFS I/O error\", e);                                                      \\\n    return errorCode;                                                                              \\\n  }\n\n    .xOpen = [](sqlite3_vfs* vfs, sqlite3_filename zName, sqlite3_file* file, int flags,\n                 int* pOutFlags) -> int {\n      WRAP_METHOD(SQLITE_CANTOPEN, {\n        auto& target = *static_cast<FileImpl*>(file);\n\n        if (flags & SQLITE_OPEN_READONLY) {\n          KJ_REQUIRE(zName != nullptr, \"readonly unnamed temporary file? what?\");\n          KJ_REQUIRE(!(flags & SQLITE_OPEN_CREATE), \"create readonly file? what?\");\n\n          auto path = kj::Path::parse(zName);\n          auto kjFile = KJ_UNWRAP_OR(self.directory.tryOpenFile(path), { return SQLITE_CANTOPEN; });\n          kj::Maybe<kj::Own<Lock>> lock;\n          if (flags & SQLITE_OPEN_MAIN_DB) {\n            lock = self.lockManager.lock(path, *kjFile);\n          }\n\n          kj::ctor(target, self, kj::mv(kjFile), kj::mv(lock));\n        } else {\n          kj::Own<const kj::File> kjFile;\n          kj::Maybe<kj::Own<Lock>> lock;\n\n          if (zName == nullptr) {\n            // Open a temp file.\n            KJ_ASSERT(flags & SQLITE_OPEN_DELETEONCLOSE);\n            KJ_ASSERT(!(flags & SQLITE_OPEN_MAIN_DB), \"main DB can't be a temporary file\");\n            kjFile = self.directory.createTemporary();\n          } else {\n            kj::WriteMode mode;\n            if (flags & SQLITE_OPEN_CREATE) {\n              if (flags & SQLITE_OPEN_EXCLUSIVE) {\n                mode = kj::WriteMode::CREATE;\n              } else {\n                mode = kj::WriteMode::CREATE | kj::WriteMode::MODIFY;\n              }\n            } else {\n              mode = kj::WriteMode::MODIFY;\n            }\n\n            auto path = kj::Path::parse(zName);\n            kjFile =\n                KJ_UNWRAP_OR(self.directory.tryOpenFile(path, mode), { return SQLITE_CANTOPEN; });\n            if (flags & SQLITE_OPEN_MAIN_DB) {\n              lock = self.lockManager.lock(path, *kjFile);\n            }\n\n            if (flags & SQLITE_OPEN_DELETEONCLOSE) {\n              self.directory.remove(path);\n            }\n          }\n\n          kj::ctor(target, self, kj::mv(kjFile), kj::mv(lock));\n        }\n\n        // In theory if read-write was requested, but failed, we should retry read-only, and then\n        // alter the pOutFlags to reflect this... I'm not going to bother.\n        if (pOutFlags != nullptr) {\n          *pOutFlags = flags;\n        }\n\n        return SQLITE_OK;\n      });\n    },\n    .xDelete = [](sqlite3_vfs* vfs, const char* zName, int syncDir) -> int {\n      WRAP_METHOD(SQLITE_IOERR_DELETE, {\n        if (self.directory.tryRemove(kj::Path::parse(zName))) {\n          return SQLITE_OK;\n        } else {\n          return SQLITE_IOERR_DELETE_NOENT;\n        }\n      });\n    },\n    .xAccess = [](sqlite3_vfs* vfs, const char* zName, int flags, int* pResOut) -> int {\n      WRAP_METHOD(SQLITE_IOERR_ACCESS, {\n        // Technically, depending on the flags, this may be checking whether the file is readable\n        // or writable, rather than just whether it exists. However, the KJ filesystem API\n        // assumes that all descendents of a writable directory are readable and writable, hence\n        // this is equivalent to checking for existence.\n        //\n        // If we were to extend the VFS so it can wrap `kj::ReadableDirectory` then in that case\n        // we would want to return false when querying writability.\n        *pResOut = self.directory.exists(kj::Path::parse(zName));\n        return SQLITE_OK;\n      });\n    },\n    .xFullPathname = [](sqlite3_vfs*, const char* zName, int nOut, char* zOut) -> int {\n      // Don't rewrite the path at all. All paths are canonical. Our path parsing will reject\n      // the existence of `.` or `..` as path components as well as leading `/`.\n      size_t len = kj::min(strlen(zName), nOut - 1);\n      memcpy(zOut, zName, len);\n      zOut[len] = 0;\n      return SQLITE_OK;\n    },\n\n    // We don't support loading shared libraries from virtual files.\n    .xDlOpen = nullptr,\n    .xDlError = nullptr,\n    .xDlSym = nullptr,\n    .xDlClose = nullptr,\n\n    // Use native implementations of these OS functions. I'm not sure why these are even part\n    // of the VFS. (Exception: xGetLastError is actually sensibly a VFS thing, but we are allowed\n    // to just not implement it.)\n    .xRandomness = native.xRandomness,\n    .xSleep = native.xSleep,\n    .xCurrentTime = native.xCurrentTime,\n    .xGetLastError = nullptr,\n    .xCurrentTimeInt64 = native.xCurrentTimeInt64,\n\n    // We don't support overriding any syscalls.\n    .xSetSystemCall = nullptr,\n    .xGetSystemCall = nullptr,\n    .xNextSystemCall = nullptr,\n\n#undef WRAP_METHOD\n  };\n};\n\n// clang-format on\n\n// -----------------------------------------------------------------------------\n\nclass SqliteDatabase::Vfs::DefaultLockManager final: public SqliteDatabase::LockManager {\n public:\n  kj::Own<Lock> lock(kj::PathPtr path, const kj::ReadableFile& mainDatabaseFile) const override {\n    return kj::heap<LockImpl>(*this, path);\n  }\n\n private:\n  class LockImpl;\n  struct LockState;\n  using LockMap = kj::HashMap<kj::PathPtr, LockState*>;\n  kj::MutexGuarded<LockMap> lockMap;\n\n  struct LockState: public kj::Refcounted {\n    // Note: The refcount of this object is protected by `lockMap`'s mutex.\n\n    struct Guarded {\n      kj::Vector<kj::Array<byte>> regions;\n\n      uint sharedLockCount = 0;\n      bool hasReserved = false;\n      bool hasPendingOrExclusive = false;\n\n      uint walLocks[Lock::WAL_LOCK_COUNT] = {0, 0, 0, 0, 0, 0, 0, 0};\n      // Each slot contains the count of shared locks, or kj::maxValue if an exclusive lock is\n      // held.\n    };\n\n    const kj::Path path;\n    kj::MutexGuarded<Guarded> guarded;\n\n    LockState(kj::Path path): path(kj::mv(path)) {}\n  };\n\n  class LockImpl final: public Lock {\n   public:\n    LockImpl(const DefaultLockManager& lockManager, kj::PathPtr path): lockManager(lockManager) {\n      auto mlock = lockManager.lockMap.lockExclusive();\n      auto& slot = mlock->findOrCreate(path, [&]() {\n        state = kj::refcounted<LockState>(path.clone());\n        return LockMap::Entry{.key = state->path, .value = state.get()};\n      });\n      if (state.get() == nullptr) {\n        state = kj::addRef(*slot);\n      }\n    }\n\n    ~LockImpl() noexcept(false) {\n      // It's important that we drop the state object under lock to ensure no other thread is\n      // in the process of grabbing it out of the map at the same time. Since we have to take a\n      // lock here anyway, `LockState` uses regular non-atomic refcounts rather than atomic.\n      auto mlock = lockManager.lockMap.lockExclusive();\n      auto stateToDrop = kj::mv(state);\n      if (!stateToDrop->isShared()) {\n        mlock->erase(stateToDrop->path);\n      }\n    }\n\n    bool tryIncreaseLevel(Level newLevel) override {\n      if (newLevel <= currentLevel) return true;\n\n      auto slock = state->guarded.lockExclusive();\n\n      if (currentLevel < SHARED) {\n        if (slock->hasPendingOrExclusive) {\n          return false;\n        }\n        ++slock->sharedLockCount;\n        currentLevel = SHARED;\n      }\n\n      if (newLevel == SHARED) {\n        return true;\n      }\n\n      if (newLevel == RESERVED) {\n        if (slock->hasReserved || slock->hasPendingOrExclusive) {\n          return false;\n        }\n        if (currentLevel == SHARED) {\n          KJ_ASSERT(slock->sharedLockCount > 0);\n          --slock->sharedLockCount;\n        }\n        slock->hasReserved = true;\n        currentLevel = RESERVED;\n        return true;\n      }\n\n      // Requesting PENDING or EXCLUSIVE. If EXCLUSIVE, we still have to transition through\n      // PENDING first, if we're not there already.\n      if (currentLevel < PENDING) {\n        if (currentLevel != RESERVED && slock->hasReserved) {\n          return false;\n        }\n        if (slock->hasPendingOrExclusive) {\n          return false;\n        }\n        if (currentLevel == SHARED) {\n          KJ_ASSERT(slock->sharedLockCount > 0);\n          --slock->sharedLockCount;\n        }\n        slock->hasReserved = false;\n        slock->hasPendingOrExclusive = true;\n        currentLevel = PENDING;\n      }\n\n      if (newLevel == EXCLUSIVE) {\n        if (slock->sharedLockCount > 0) {\n          return false;\n        }\n        currentLevel = EXCLUSIVE;\n      }\n\n      return true;\n    }\n\n    void decreaseLevel(Level newLevel) override {\n      if (newLevel >= currentLevel) return;\n      KJ_REQUIRE(newLevel <= SHARED);\n\n      auto slock = state->guarded.lockExclusive();\n      if (currentLevel >= PENDING) {\n        slock->hasPendingOrExclusive = false;\n      }\n      if (currentLevel == RESERVED) {\n        slock->hasReserved = false;\n      }\n      if (currentLevel == SHARED && newLevel == UNLOCKED) {\n        KJ_ASSERT(slock->sharedLockCount > 0);\n        --slock->sharedLockCount;\n      }\n      if (newLevel == SHARED) {\n        ++slock->sharedLockCount;\n      }\n      currentLevel = newLevel;\n    }\n\n    bool checkReservedLock() override {\n      return state->guarded.lockShared()->hasReserved;\n    }\n\n    kj::ArrayPtr<byte> getSharedMemoryRegion(uint index, uint size, bool extend) override {\n      if (extend) {\n        auto slock = state->guarded.lockExclusive();\n\n        while (index >= slock->regions.size()) {\n          auto newRegion = kj::heapArray<byte>(size);\n          newRegion.asPtr().fill(0);\n          slock->regions.add(kj::mv(newRegion));\n        }\n\n        return slock->regions[index];\n      } else {\n        auto slock = state->guarded.lockShared();\n\n        if (index >= slock->regions.size()) {\n          return nullptr;\n        } else {\n          kj::ArrayPtr<const byte> region = slock->regions[index];\n          // const_cast OK because the caller will carefully control access to shared memory.\n          return kj::arrayPtr(const_cast<byte*>(region.begin()), region.size());\n        }\n      }\n    }\n\n    void clearSharedMemory() override {\n      auto slock = state->guarded.lockExclusive();\n      slock->regions.clear();\n    }\n\n    bool tryLockWalShared(uint start, uint count) override {\n      auto slock = state->guarded.lockExclusive();\n\n      for (uint i = start; i < start + count; i++) {\n        if (slock->walLocks[i] == static_cast<uint>(kj::maxValue)) {\n          // blocked by exclusive lock\n          return false;\n        }\n      }\n      for (uint i = start; i < start + count; i++) {\n        ++slock->walLocks[i];\n      }\n      return true;\n    }\n    bool tryLockWalExclusive(uint start, uint count) override {\n      auto slock = state->guarded.lockExclusive();\n\n      for (uint i = start; i < start + count; i++) {\n        if (slock->walLocks[i] != 0) {\n          // blocked by another lock\n          return false;\n        }\n      }\n      for (uint i = start; i < start + count; i++) {\n        slock->walLocks[i] = kj::maxValue;\n      }\n      return true;\n    }\n\n    void unlockWalShared(uint start, uint count) override {\n      auto slock = state->guarded.lockExclusive();\n      for (uint i = start; i < start + count; i++) {\n        KJ_ASSERT(slock->walLocks[i] != 0);\n        --slock->walLocks[i];\n      }\n    }\n    void unlockWalExclusive(uint start, uint count) override {\n      auto slock = state->guarded.lockExclusive();\n      for (uint i = start; i < start + count; i++) {\n        KJ_REQUIRE(slock->walLocks[i] == (uint)kj::maxValue);\n        slock->walLocks[i] = 0;\n      }\n    }\n\n   private:\n    const DefaultLockManager& lockManager;\n    kj::Own<LockState> state;\n    Level currentLevel = UNLOCKED;\n  };\n};\n\nSqliteDatabase::Vfs::Vfs(const kj::Directory& directory, Options options)\n    : directory(directory),\n      ownLockManager(kj::heap<DefaultLockManager>()),\n      lockManager(*ownLockManager),\n      options(kj::mv(options)),\n      native(*sqlite3_vfs_find(nullptr)) {\n#if _WIN32\n  vfs = kj::heap(makeKjVfs());\n#else\n  KJ_IF_SOME(fd, directory.getFd()) {\n    rootFd = fd;\n    vfs = kj::heap(makeWrappedNativeVfs());\n  } else {\n    vfs = kj::heap(makeKjVfs());\n  }\n#endif\n  sqlite3_vfs_register(vfs, false);\n}\n\nSqliteDatabase::Vfs::Vfs(\n    const kj::Directory& directory, const LockManager& lockManager, Options options)\n    : directory(directory),\n      lockManager(lockManager),\n      options(kj::mv(options)),\n      native(*sqlite3_vfs_find(nullptr)),\n      // Always use KJ VFS when using a custom LockManager.\n      vfs(kj::heap(makeKjVfs())) {\n  sqlite3_vfs_register(vfs, false);\n}\n\nSqliteDatabase::Vfs::~Vfs() noexcept(false) {\n  sqlite3_vfs_unregister(vfs);\n}\n\nkj::String SqliteDatabase::Vfs::makeName() {\n  // A pointer to this object should be suitably unique. (Ugghhhh.)\n  return kj::str(\"kj-\", this);\n}\n\n#if _WIN32\nkj::Maybe<kj::Path> SqliteDatabase::Vfs::tryAppend(kj::PathPtr suffix) const {\n  auto handle = KJ_UNWRAP_OR_RETURN(directory.getWin32Handle(), nullptr);\n  auto root = getPathFromWin32Handle(handle);\n  return root.append(suffix);\n}\n#else\nkj::Maybe<kj::Path> SqliteDatabase::Vfs::tryAppend(kj::PathPtr suffix) const {\n  // TODO(someday): consider implementing this on other platforms\n  return kj::none;\n}\n#endif\n\n// =======================================================================================\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/sqlite.h",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <workerd/util/account-limits.h>\n\n#include <kj/filesystem.h>\n#include <kj/function.h>\n#include <kj/list.h>\n#include <kj/one-of.h>\n#include <kj/string.h>\n\n#include <utility>\n\nstruct sqlite3;\nstruct sqlite3_vfs;\nstruct sqlite3_stmt;\n\nKJ_DECLARE_NON_POLYMORPHIC(sqlite3_stmt);\n\nnamespace workerd {\n\nusing kj::byte;\nusing kj::uint;\n\n// Used to collect periodic metrics about queries and size of sqlite db\nclass SqliteObserver {\n public:\n  void setDbWalSize(uint64_t dbWalSize) {\n    this->dbWalSize = dbWalSize;\n  }\n  uint64_t getDbWalSize() {\n    return dbWalSize;\n  }\n  kj::TimePoint now() {\n    return monotonicClock.now();\n  }\n  virtual void addQueryStats(uint64_t rowsRead, uint64_t rowsWritten) {}\n  // The method is not used by the SqliteDatabase, it is added here for convenience\n  virtual void setSqliteStoredBytes(uint64_t sqliteStoredBytes) {}\n\n  virtual void reportQueryEvent(kj::Maybe<kj::String> queryStatement,\n      uint64_t queryRowsRead,\n      uint64_t queryRowsWritten,\n      kj::Duration queryLatency,\n      uint64_t dbWalBytesWritten,\n      int queryResult,\n      bool isInternalQuery,\n      kj::Maybe<kj::String> queryErrorDescription) {}\n\n  static SqliteObserver DEFAULT;\n\n private:\n  uint64_t dbWalSize = 0;\n  const kj::MonotonicClock& monotonicClock = kj::systemPreciseMonotonicClock();\n};\n\n// C++/KJ API for SQLite.\n//\n// In addition to providing a more modern C++ interface vs. the classic C API, this API layers\n// SQLite on top of KJ's filesystem API. This means that you can use KJ's in-memory filesystem\n// implementation in unit tests. Meanwhile, though, if you do actually give it a `kj::Directory`\n// representing a true disk directory, the real SQLite disk implementation will be used with\n// all of its features.\nclass SqliteDatabase {\n public:\n  class Vfs;\n  class Query;\n  class Statement;\n  class Lock;\n  class LockManager;\n  struct VfsOptions;\n  class Regulator;\n\n  struct QueryOptions {\n    const Regulator& regulator;\n    bool allowUnconfirmed = false;\n  };\n\n  struct IngestResult {\n    kj::StringPtr remainder;\n    uint64_t rowsRead;\n    uint64_t rowsWritten;\n    uint64_t statementCount;\n  };\n\n  SqliteDatabase(const Vfs& vfs,\n      kj::Path path,\n      kj::Maybe<kj::WriteMode> maybeMode = kj::none,\n      SqliteObserver& sqliteObserver = SqliteObserver::DEFAULT,\n      kj::Maybe<const ActorAccountLimits&> actorAccountLimits = kj::none);\n  ~SqliteDatabase() noexcept(false);\n  KJ_DISALLOW_COPY_AND_MOVE(SqliteDatabase);\n\n  // Allows a SqliteDatabase to be passed directly into SQLite API functions where `sqlite*` is\n  // expected.\n  operator sqlite3*();\n\n  // Class which regulates a SQL query, especially to control how queries created in JavaScript\n  // application code are handled.\n  //\n  // Note that any of the methods that check if actions are allowed my throw an exception instead\n  // of returning false. If they do, this exception will pass through to the caller in place of\n  // a generic \"not authorized\" exception.\n  class Regulator {\n   public:\n    // Returns whether the given name (which may be a table, index, view, etc.) is allowed to be\n    // accessed. Typically, this is used to deny access to names containing special prefixes\n    // indicating that they are privileged, like `_cf_`.\n    //\n    // This only applies to global names. Scoped names, such as column names, are not subject to\n    // authorization.\n    virtual bool isAllowedName(kj::StringPtr name) const {\n      return true;\n    }\n\n    // Returns whether a given trigger or view name should be permitted to run as a side effect of a\n    // query running under this Regulator. This is a precaution to prevent application-defined\n    // triggers from executing under a privileged regulator.\n    //\n    // TODO(someday): In theory a trigger should run with the authority level under which it was\n    //   created, but how do we track that? In practice we probably never expect triggers to run on\n    //   trusted queries.\n    virtual bool isAllowedTrigger(kj::StringPtr name) const {\n      return false;\n    }\n\n    // Report that an error occurred. `message` is the detail message constructed by SQLite. This\n    // function should typically throw an exception. If no exception is thrown, a simple KJ exception\n    // will be thrown after `onError()` returns.\n    //\n    // The purpose of this callback is to allow the JavaScript API bindings to throw a JSG exception.\n    //\n    // Note that SQLITE_MISUSE errors are NOT reported using `onError()` -- they will throw regular\n    // KJ exceptions in all cases. This is because SQLITE_MISUSE indicates a bug that could lead to\n    // undefined behavior. Such bugs are always in C++ code; JavaScript application code must be\n    // prohibited from causing such errors in the first place.\n    virtual void onError(kj::Maybe<int> sqliteErrorCode, kj::StringPtr message) const {}\n\n    // Are BEGIN TRANSACTION and SAVEPOINT statements allowed? Note that if allowed, SAVEPOINT will\n    // also be subject to `isAllowedName()` for the savepoint name. If denied, the application will\n    // not be able to create any sort of transaction.\n    //\n    // In Durable Objects, we disallow these statements because the platform provides an explicit\n    // API for transactions that is safer (e.g. it automatically rolls back on throw). Also, the\n    // platform automatically wraps every entry into the isolate lock in a transaction.\n    virtual bool allowTransactions() const {\n      return true;\n    }\n\n    // Whether or not this query's rows read and written should be recorded to the SqliteObserver\n    // when the query is done. (In other words, determines whether this query is billed.)\n    //\n    // We don't bill for TRUSTED queries since they are used internally by the system.\n    virtual bool shouldAddQueryStats() const {\n      return false;\n    }\n  };\n\n  // Use as the `Regulator&` for queries that are fully trusted. As a general rule, this should\n  // be used if and only if the SQL query is a string literal.\n  static constexpr Regulator TRUSTED;\n\n  // Prepares the given SQL code as a persistent statement that can be used across several queries.\n  // Don't use this for one-off queries; use run() instead.\n  Statement prepare(const Regulator& regulator, kj::StringPtr sqlCode);\n\n  // Prepares a statement that may actually be multiple statements (separated by semicolons).\n  // In this case, the code is not actually parsed until first executed (this implies\n  // `prepareMulti()` will never throw since it doesn't actually do anything). This lazy-parsing\n  // behavior is necessary in the case that later statements depend on the effects of earlier ones.\n  // For example, the first statement might create a table, and the next statement insert into that\n  // table. SQLite will refuse to parse the insertion statement until the table has been created,\n  // so each statement must be executed before the next can be parsed.\n  //\n  // As with exec(), the result of executing a batch of multiple statements is always the result\n  // of the last statement. The results of all other statements are discarded.\n  Statement prepareMulti(const Regulator& regulator, kj::String sqlCode);\n\n  // Convenience method to start a query. This is equivalent to `prepare(sqlCode).run(bindings...)`\n  // except:\n  // - It may be more efficient for one-off use case.\n  // - The code can include multiple statements, separated by semicolons. The bindings and returned\n  //   `Query` object are both associated with the last statement. This is particularly convenient\n  //   for doing database initialization such as creating several tables at once.\n  template <typename... Params>\n  Query run(QueryOptions options, kj::StringPtr sqlCode, Params&&... bindings);\n\n  template <size_t size>\n  Statement prepare(const char (&sqlCode)[size]);\n\n  template <size_t size>\n  Statement prepare(const Regulator& regulator, const char (&sqlCode)[size]);\n\n  // When the input is a string literal, we automatically use the TRUSTED regulator.\n  template <size_t size, typename... Params>\n  Query run(const char (&sqlCode)[size], Params&&... bindings);\n\n  // Invokes the given callback whenever a query begins which may write to the database. The\n  // callback is called just before executing the query.\n  //\n  // Durable Objects uses this to automatically begin a transaction and close the output gate.\n  //\n  // Note that the write callback is NOT called before (or at any point during) a reset(). Use the\n  // `ResetListener` mechanism or `afterReset()` instead for that case.\n  void onWrite(kj::Function<void(bool allowUnconfirmed)> callback) {\n    onWriteCallback = kj::mv(callback);\n  }\n\n  // Invokes the given callback when a \"critical error\" causes an automatic rollback during a\n  // transaction.\n  //\n  // See: https://www.sqlite.org/lang_transaction.html#response_to_errors_within_a_transaction\n  void onCriticalError(\n      kj::Function<void(kj::StringPtr errorMessage, kj::Maybe<kj::Exception> maybeException)>\n          callback) {\n    onCriticalErrorCallback = kj::mv(callback);\n  }\n\n  // Returns true if a transaction was automatically rolled due to a critical error.\n  bool observedCriticalError();\n\n  // Invoke the onWrite() callback.\n  //\n  // This is useful when the caller is about to execute a statement which SQLite considers\n  // read-only, but needs to be considered a write for our purposes. In particular, we use the\n  // onWrite callback to start automatic transactions, and we use the SAVEPOINT statement to\n  // implement explicit transactions. For synchronous transactions, the explicit transaction needs\n  // to be nested inside the automatic transaction, so we need to force an auto-transaction to\n  // start before the SAVEPOINT.\n  void notifyWrite(bool allowUnconfirmed = false);\n\n  // Get the currently-executing SQL query for debug purposes. The query is normalized to hide\n  // any literal values that might contain sensitive information. This is intended to be safe for\n  // debug logs.\n  kj::StringPtr getCurrentQueryForDebug();\n\n  // Helper to execute a chunk of SQL that may not be complete.\n  // Executes every valid statement provided, and returns the remaining portion of the input\n  // that was not processed. This is used for streaming SQL ingestion.\n  IngestResult ingestSql(const Regulator& regulator, kj::StringPtr sqlCode);\n\n  // Execute a function with the given regulator.\n  void executeWithRegulator(const Regulator& regulator, kj::FunctionParam<void()> func);\n\n  // Resets the database to an empty state by deleting the underlying database file and creating\n  // a new one in its place. This is the recommended way to \"drop database\" in SQLite, and is used\n  // to implement deleteAll() in Workers.\n  //\n  // reset() will cancel all outstanding queries (further attempts to use the cursors will throw).\n  // Prepared statements will be automatically reprepared the next time they are executed (which\n  // may throw if they depend on tables that haven't been recreated yet).\n  void reset();\n\n  // Objects that need to be notified when reset() is called may inherit `ResetListener`.\n  class ResetListener {\n   public:\n    ResetListener(SqliteDatabase& db): db(db) {\n      db.resetListeners.add(*this);\n    }\n    ~ResetListener() {\n      if (link.isLinked()) db.resetListeners.remove(*this);\n    }\n    ResetListener(ResetListener&& other): db(other.db) {\n      db.resetListeners.remove(other);\n      db.resetListeners.add(*this);\n    }\n\n    // When the database's `reset()` method is called, all listeners' `beforeSqliteReset()` will be\n    // called before actually resetting the database.\n    virtual void beforeSqliteReset() = 0;\n\n   protected:  // so that subclasses don't have to store their own copy of the `db` reference\n    SqliteDatabase& db;\n\n   private:\n    kj::ListLink<ResetListener> link;\n\n    friend class SqliteDatabase;\n  };\n\n  // Registers a callback to call after a reset completes. This can be used to do basic database\n  // initialization, e.g. set WAL mode. (To get notified *before* a reset, use `ResetListener`.)\n  //\n  // Note that the on-write callback is disabled during reset(), including while calling the\n  // after-reset callback. So, queries performed by the after-reset callback will not trigger the\n  // on-write callback.\n  void afterReset(kj::Function<void(SqliteDatabase&)> callback) {\n    afterResetCallback = kj::mv(callback);\n  }\n\n  // Register a callback which shall be called if the current transaction is rolled back. If the\n  // current transaction commits, then the callback is discarded without invoking it.\n  //\n  // This method correctly handles savepoint stacks. The callback is invoked if any savepoint\n  // currently in the stack ends up being rolled back.\n  //\n  // This is useful when implementing any sort of in-memory caching which must stay in sync with\n  // the database state. The callback can be used to invalidate the cache, or even revert it to\n  // a previous value.\n  //\n  // When a rollback occurs, callbacks are invoked in the reverse of the order in which they were\n  // registered. The database content is rolled back first, before invoking any callbacks.\n  // Callbacks may read from the database, but must not write to it.\n  void onRollback(kj::Function<void()> callback) {\n    if (inTransaction || !savepoints.empty()) {\n      rollbackCallbacks.add(kj::mv(callback));\n    }\n  }\n\n private:\n  const Vfs& vfs;\n  kj::Path path;\n  bool readOnly;\n  SqliteObserver& sqliteObserver;\n  kj::Maybe<const ActorAccountLimits&> actorAccountLimits;\n\n  // This pointer can be left null if a call to reset() failed to re-open the database.\n  kj::Maybe<sqlite3&> maybeDb;\n\n  // Set while a query is compiling.\n  kj::Maybe<const Regulator&> currentRegulator;\n\n  // Set during the *first* time a statement is being compiled, to capture information about it\n  // from the authorizer callback. It is assumed that if the statement must be re-parsed later,\n  // the same data would be gathered, so `currentParseContext` is left null on re-parse.\n  struct ParseContext;\n  kj::Maybe<ParseContext&> currentParseContext;\n\n  // Set while a statement is executing.\n  kj::Maybe<sqlite3_stmt&> currentStatement;\n\n  bool criticalErrorOccurred = false;\n  kj::Maybe<kj::Function<void(bool allowUnconfirmed)>> onWriteCallback;\n  kj::Maybe<kj::Function<void(kj::StringPtr errorMessage, kj::Maybe<kj::Exception> maybeException)>>\n      onCriticalErrorCallback;\n  kj::Maybe<kj::Function<void(SqliteDatabase&)>> afterResetCallback;\n\n  kj::List<ResetListener, &ResetListener::link> resetListeners;\n\n  // Callbacks registered with onRollback that haven't been committed nor rolled back yet.\n  kj::Vector<kj::Function<void()>> rollbackCallbacks;\n\n  struct Savepoint {\n    kj::String name;\n\n    // Size of `rollbackCallbackIndex` when this savepoint was created.\n    size_t rollbackCallbackIndex;\n  };\n\n  // Savepoints that haven't been committed nor rolled back yet.\n  kj::Vector<Savepoint> savepoints;\n\n  // True if in a BEGIN TRANSACTION transaction.\n  bool inTransaction = false;\n\n  void init(kj::Maybe<kj::WriteMode> maybeMode);\n\n  // Describes various kinds of interesting state changes which a statement might apply, which we\n  // need to track to implement the SqliteDatabse interface. In particular, we must track\n  // transactions to implement the onRollback() method.\n  struct NoChange {};\n  struct BeginTxn {\n    kj::Maybe<kj::String> savepointName;\n  };\n  struct CommitTxn {\n    kj::Maybe<kj::String> savepointName;\n  };\n  struct RollbackTxn {\n    kj::Maybe<kj::String> savepointName;\n  };\n  using StateChange = kj::OneOf<NoChange, BeginTxn, CommitTxn, RollbackTxn>;\n\n  // Called immediately after a statement executes, to update our understanding of the current\n  // state.\n  void applyChange(const StateChange& change);\n\n  void handleCriticalError(kj::Maybe<int> errorCode,\n      kj::StringPtr errorMessage,\n      kj::Maybe<const kj::Exception&> exception);\n\n  enum Multi { SINGLE, MULTI };\n\n  // A pair of a compiled statement, and a description of the interesting state changes it applies.\n  struct StatementAndEffect {\n    kj::Own<sqlite3_stmt> statement;\n    StateChange stateChange;\n  };\n\n  // Helper to call sqlite3_prepare_v3().\n  //\n  // In SINGLE mode, an exception is thrown if `sqlCode` contains multiple statements.\n  //\n  // In MULTI mode, if `sqlCode` contains multiple statements, each statement before the last one\n  // is executed immediately. The returned object represents the last statement.\n  //\n  // If `prelude` is provided, then, in MULTI mode, all statements which are executed immediately\n  // are also appended to `prelude`.\n  StatementAndEffect prepareSql(const Regulator& regulator,\n      kj::StringPtr sqlCode,\n      uint prepFlags,\n      Multi multi,\n      kj::Maybe<kj::Vector<Statement>&> prelude = kj::none);\n\n  // Implements SQLite authorizer callback, see sqlite3_set_authorizer().\n  bool isAuthorized(int actionCode,\n      kj::Maybe<kj::StringPtr> param1,\n      kj::Maybe<kj::StringPtr> param2,\n      kj::Maybe<kj::StringPtr> dbName,\n      kj::Maybe<kj::StringPtr> triggerName);\n\n  // Implements SQLite authorizer for 'temp' DB\n  bool isAuthorizedTemp(int actionCode,\n      const kj::Maybe<kj::StringPtr>& param1,\n      const kj::Maybe<kj::StringPtr>& param2,\n      const Regulator& regulator);\n\n  void setupSecurity(sqlite3* db);\n\n  struct ParseContext {\n    // What kind of state change does this statement cause, if any?\n    StateChange stateChange = NoChange();\n\n    // If the parse fails because the authorizer rejects it, it may fill in `authError` to provide\n    // a more friendly error message. This error will be thrown by the overall query. Otherwise,\n    // a generic \"not authorized\" error is thrown.\n    kj::Maybe<kj::Exception> authError;\n  };\n};\n\n// Represents a prepared SQL statement, which can be executed many times.\nclass SqliteDatabase::Statement final: private ResetListener {\n public:\n  // Convenience method to start a query. This is equivalent to:\n  //\n  //     SqliteDatabase::Query(db, statement, bindings...);\n  //\n  // `bindings` are the values to fill into `?`s in the statement. Each value in `bindings` must\n  // be one of the types of Query::ValuePtr. Alternatively, `bindings` can be a single parameter\n  // of type `ArrayPtr<const Query::ValuePtr>` to initialize bindings from an array.\n  //\n  // Any strings or byte blobs in the bindings must remain valid until the `Query` is destroyed.\n  // However, when passing `bindings` as an array, the outer array need only remain valid until\n  // this method returns.\n  template <typename... Params>\n  Query run(Params&&... bindings);\n\n  struct StatementOptions {\n    bool allowUnconfirmed = false;\n  };\n\n  template <typename... Params>\n  Query run(StatementOptions options, Params&&... bindings);\n\n private:\n  const Regulator& regulator;\n  kj::OneOf<kj::String, StatementAndEffect> stmt;\n\n  // List of statements to execute before this one. Only non-empty if this Statement was created\n  // by prepareMulti().\n  kj::Vector<Statement> prelude;\n\n  Statement(SqliteDatabase& db, const Regulator& regulator, StatementAndEffect stmt)\n      : ResetListener(db),\n        regulator(regulator),\n        stmt(kj::mv(stmt)) {}\n\n  // Lazily-parsed statement -- used by `prepareMulti()`.\n  Statement(SqliteDatabase& db, const Regulator& regulator, kj::String sqlCode)\n      : ResetListener(db),\n        regulator(regulator),\n        stmt(kj::mv(sqlCode)) {}\n\n  void beforeSqliteReset() override;\n\n  // Get the underlying StatementAndEffect, which the caller will then execute. If `prelude` is\n  // non-empty, prepareForExecution() actually executes the prelude.\n  StatementAndEffect& prepareForExecution();\n\n  friend class SqliteDatabase;\n};\n\n// Represents one SQLite query.\n//\n// Only one Query can exist at a time, for a given database. It should probably be allocated on\n// the stack.\nclass SqliteDatabase::Query final: private ResetListener {\n public:\n  using ValuePtr =\n      kj::OneOf<kj::ArrayPtr<const byte>, kj::StringPtr, int64_t, double, decltype(nullptr)>;\n\n  // Construct using Statement::run() or SqliteDatabase::run().\n\n  ~Query() noexcept(false);\n  KJ_DISALLOW_COPY_AND_MOVE(Query);\n\n  // Row IO counter.\n  uint64_t getRowsRead();\n  // Row IO counter.\n  uint64_t getRowsWritten();\n\n  // If true, there are no more rows. (When true, the methods below must not be called.)\n  bool isDone() {\n    return done;\n  }\n\n  // For INSERT, UPDATE, or DELETE queries, returns the number of rows changed. For other query\n  // types the result is undefined.\n  uint changeCount();\n\n  // Advance to the next row.\n  void nextRow() {\n    nextRow(/*first=*/false);\n  }\n\n  // How many columns does each row of the result have?\n  uint columnCount();\n\n  // Get the value at the given column, as whatever type was actually returned.\n  //\n  // Returned pointers (strings and blobs) remain valid only until either (a) nextRow() is called,\n  // or (b) a different get method is called on the same column but with a different type.\n  ValuePtr getValue(uint column);\n\n  // Get the name of a specific column.\n  kj::StringPtr getColumnName(uint column);\n\n  // Get the value at the given column, coercing it to the desired type according to SQLite rules.\n  kj::ArrayPtr<const byte> getBlob(uint column);\n\n  // Get the value at the given column, coercing it to the desired type according to SQLite rules.\n  kj::StringPtr getText(uint column);\n\n  // Get the value at the given column, coercing it to the desired type according to SQLite rules.\n  int getInt(uint column);\n\n  // Get the value at the given column, coercing it to the desired type according to SQLite rules.\n  int64_t getInt64(uint column);\n\n  // Get the value at the given column, coercing it to the desired type according to SQLite rules.\n  double getDouble(uint column);\n\n  // Get the value at the given column, coercing it to the desired type according to SQLite rules.\n  bool isNull(uint column);\n\n  kj::Maybe<kj::ArrayPtr<const byte>> getMaybeBlob(uint column) {\n    if (isNull(column)) {\n      return kj::none;\n    } else {\n      return getBlob(column);\n    }\n  }\n  kj::Maybe<kj::StringPtr> getMaybeText(uint column) {\n    if (isNull(column)) {\n      return kj::none;\n    } else {\n      return getText(column);\n    }\n  }\n  kj::Maybe<int> getMaybeInt(uint column) {\n    if (isNull(column)) {\n      return kj::none;\n    } else {\n      return getInt(column);\n    }\n  }\n  kj::Maybe<int64_t> getMaybeInt64(uint column) {\n    if (isNull(column)) {\n      return kj::none;\n    } else {\n      return getInt64(column);\n    }\n  }\n  kj::Maybe<double> getMaybeDouble(uint column) {\n    if (isNull(column)) {\n      return kj::none;\n    } else {\n      return getDouble(column);\n    }\n  }\n\n private:\n  class QueryEvent {\n   public:\n    explicit QueryEvent(SqliteObserver& sqliteObserver)\n        : observer(sqliteObserver),\n          dbWalSizeBefore(sqliteObserver.getDbWalSize()),\n          startTime(sqliteObserver.now()) {}\n\n    ~QueryEvent() noexcept(false) {\n      uint64_t dbWalSizeAfter = observer.getDbWalSize();\n      uint64_t dbWalBytesWritten = (dbWalSizeAfter - dbWalSizeBefore);\n      kj::Duration queryLatency = observer.now() - startTime;\n\n      observer.reportQueryEvent(kj::mv(queryStatement), rowsRead, rowsWritten, queryLatency,\n          dbWalBytesWritten, queryResult, isInternalQuery, kj::mv(queryErrorDescription));\n    }\n\n    void setQueryEventStats(uint64_t rowsRead, uint64_t rowsWritten, bool isInternalQuery) {\n      this->rowsRead = rowsRead;\n      this->rowsWritten = rowsWritten;\n      this->isInternalQuery = isInternalQuery;\n    }\n\n    void setQueryStatement(kj::String queryStatement) {\n      this->queryStatement = kj::mv(queryStatement);\n    }\n\n    void setQueryErrorDescription(kj::String queryErrorDescription) {\n      this->queryErrorDescription = kj::mv(queryErrorDescription);\n    }\n\n    void setQueryResult(int res) {\n      queryResult = res;\n    }\n\n   private:\n    SqliteObserver& observer;\n    kj::Maybe<kj::String> queryStatement = kj::none;\n    bool isInternalQuery = false;\n    uint64_t dbWalSizeBefore;\n    kj::TimePoint startTime;\n    uint64_t rowsRead = 0;\n    uint64_t rowsWritten = 0;\n    int queryResult = 0;\n    kj::Maybe<kj::String> queryErrorDescription = kj::none;\n  };\n\n  const Regulator& regulator;\n  StatementAndEffect ownStatement;                // for one-off queries\n  kj::Maybe<StatementAndEffect&> maybeStatement;  // null if database was reset\n  bool done = false;\n  QueryEvent queryEvent;\n\n  // Storing the rowsRead and rowsWritten here to use in cases where a DB is reset.\n  // When the DB is reset, getRowdRead and getRowsWritten will fail as the statement they\n  // refer to gets destroyed as part of the reset process.\n  uint64_t rowsRead = 0;\n  uint64_t rowsWritten = 0;\n\n  // Whether this query allows unconfirmed writes.\n  bool allowUnconfirmed = false;\n\n  friend class SqliteDatabase;\n\n  Query(SqliteDatabase& db,\n      QueryOptions options,\n      Statement& statement,\n      kj::ArrayPtr<const ValuePtr> bindings);\n  Query(SqliteDatabase& db,\n      QueryOptions options,\n      kj::StringPtr sqlCode,\n      kj::ArrayPtr<const ValuePtr> bindings);\n  template <typename... Params>\n  Query(SqliteDatabase& db, QueryOptions options, Statement& statement, Params&&... bindings)\n      : ResetListener(db),\n        regulator(options.regulator),\n        maybeStatement(statement.prepareForExecution()),\n        queryEvent(this->db.sqliteObserver),\n        allowUnconfirmed(options.allowUnconfirmed) {\n    // If we throw from the constructor, the destructor won't run. Need to call destroy()\n    // explicitly.\n    KJ_ON_SCOPE_FAILURE(destroy());\n    bindAll(std::index_sequence_for<Params...>(), kj::fwd<Params>(bindings)...);\n  }\n  template <typename... Params>\n  Query(SqliteDatabase& db, QueryOptions options, kj::StringPtr sqlCode, Params&&... bindings)\n      : ResetListener(db),\n        regulator(options.regulator),\n        ownStatement(db.prepareSql(regulator, sqlCode, 0, MULTI)),\n        maybeStatement(ownStatement),\n        queryEvent(this->db.sqliteObserver),\n        allowUnconfirmed(options.allowUnconfirmed) {\n    // If we throw from the constructor, the destructor won't run. Need to call destroy()\n    // explicitly.\n    KJ_ON_SCOPE_FAILURE(destroy());\n    bindAll(std::index_sequence_for<Params...>(), kj::fwd<Params>(bindings)...);\n  }\n\n  void checkRequirements(size_t size);\n\n  void init(kj::ArrayPtr<const ValuePtr> bindings);\n  void destroy();\n\n  void bind(uint column, ValuePtr value);\n  void bind(uint column, kj::ArrayPtr<const byte> value);\n  void bind(uint column, kj::StringPtr value);\n  void bind(uint column, long long value);\n  void bind(uint column, double value);\n  void bind(uint column, decltype(nullptr));\n\n  void handleCriticalError(kj::Maybe<int> errorCode,\n      kj::StringPtr errorMessage,\n      kj::Maybe<const kj::Exception&> maybeException) {\n    db.handleCriticalError(errorCode, errorMessage, maybeException);\n  }\n\n  // Some reasonable automatic conversions.\n\n  inline void bind(uint column, int value) {\n    bind(column, static_cast<long long>(value));\n  }\n  inline void bind(uint column, uint value) {\n    bind(column, static_cast<long long>(value));\n  }\n  inline void bind(uint column, long value) {\n    bind(column, static_cast<long long>(value));\n  }\n  inline void bind(uint column, float value) {\n    bind(column, static_cast<double>(value));\n  }\n\n  template <typename... T, size_t... i>\n  void bindAll(std::index_sequence<i...>, T&&... value) {\n    checkRequirements(sizeof...(T));\n    (bind(i, kj::fwd<T>(value)), ...);\n    nextRow(/*first=*/true);\n  }\n\n  StatementAndEffect& getStatementAndEffect();\n  sqlite3_stmt* getStatement() {\n    return getStatementAndEffect().statement;\n  }\n\n  void beforeSqliteReset() override;\n\n  void nextRow(bool first);\n};\n\n// Options affecting SqliteDatabase::Vfs onstructor.\nstruct SqliteDatabase::VfsOptions {\n\n  // Value that should be returned by the SQLite VFS's xDeviceCharacteristics method. This is\n  // a combination of SQLITE_IOCAP_* flags which can improve performance if the device is known\n  // to provide certain guarantees.\n  //\n  // SQLite's default filesystem driver sets this to 0 on unix. On Windows, it sets the\n  // SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN flag. SQLite also lets the application enable\n  // SQLITE_IOCAP_POWERSAFE_OVERWRITE explicitly via the SQLITE_FCNTL_POWERSAFE_OVERWRITE file\n  // control, or the `?psow=1` URL parameter. It is believed that almost all modern disks support\n  // powersafe overwrite, and being able to assume this significantly improves performance.\n  // See: https://www.sqlite.org/psow.html Because it's almost always desirable, this\n  // implementation enables powersafe overwrite by default.\n  //\n  // Note that when the underlying directory is a real disk directory, then this implementation\n  // will fall back to the native VFS implementation. In that case, the options you set here will\n  // be ORed with the ones set by the underlying VFS.\n  int deviceCharacteristics = 0x00001000;  // = SQLITE_FCNTL_POWERSAFE_OVERWRITE\n};\n\n// Implements a SQLite VFS based on a KJ directory.\n//\n// If the directory is detected to be a disk directory (i.e. getFd() or getWin32Handle() returns\n// non-null), this VFS implementation will actually delegate to the built-in one. This ensures\n// feature-parity for production use.\n//\n// If the directory is not a disk directory, then the VFS will actually use the KJ APIs, but\n// some features will be missing. Most importantly, as of this writing, KJ filesystem APIs do\n// not support locks, so all locking will be ignored.\n//\n// An instance of `Vfs` can safely be used across multiple threads.\nclass SqliteDatabase::Vfs {\n public:\n  // Pretend `Options` is declared nested here. Due to a C++ quirk, we cannot actually declare it\n  // nested while having default-initialized parameters of this type.\n  using Options = VfsOptions;\n\n  // Create a VFS backed by the given kj::Directory.\n  //\n  // If the directory is a real disk directory (i.e. getFd() returns non-null), then this will\n  // use SQLite's native filesystem implementation AND locking implementation. This is what you\n  // want when opening a database that could simultaneously be opened by other programs which may\n  // not be using this wrapper library.\n  //\n  // If the directory is NOT a real disk directory, this constructor will only arrange to do\n  // locking between clients that use the same Vfs object. This makes sense for in-memory temporary\n  // filesystems and other cases where the application can ensure all clients are using the same\n  // Vfs. If, somehow, the same database file is opened for write via two different `Vfs` instances,\n  // it will likely become corrupted.\n  explicit Vfs(const kj::Directory& directory, Options options = {});\n\n  // Create a VFS with custom lock management.\n  //\n  // Unlike the other constructor, this version never uses SQLite's native VFS implementation.\n  // `lockManager` will be responsible for coordinating access between multiple concurrent clients\n  // of the same database.\n  explicit Vfs(\n      const kj::Directory& directory, const LockManager& lockManager, Options options = {});\n\n  ~Vfs() noexcept(false);\n\n  // Unfortunately, all SQLite VFSes must be registered in a global list with unique names, and\n  // then the _name_ must be passed to sqlite3_open_v2() to use it when opening a database. This is\n  // dumb, you should instead be able to simply pass the sqlite3_vfs* when opening the database,\n  // but this is the way it is. To work around this, each VFS is assigned an auto-generated unique\n  // name.\n  //\n  // TODO(cleanup): Patch SQLite to allow passing the pointer in?\n  kj::StringPtr getName() const {\n    return name;\n  }\n\n  KJ_DISALLOW_COPY_AND_MOVE(Vfs);\n\n private:\n  const kj::Directory& directory;\n  kj::Own<LockManager> ownLockManager;\n  const LockManager& lockManager;\n  Options options;\n\n  // Value returned by getName();\n  kj::String name = makeName();\n\n  sqlite3_vfs& native;       // the system's default VFS implementation\n  kj::Own<sqlite3_vfs> vfs;  // our VFS\n\n  // Result of `directory.getFd()`, if it returns non-null. Cached here for convenience.\n  int rootFd = -1;\n\n  template <typename T, T slot>\n  struct MethodWrapperHack;\n\n  struct WrappedNativeFileImpl;\n  // Create a VFS definition that wraps the native VFS implementation except that it treats our\n  // `directory` as the root. Requires that the directory is a real disk directory (and `rootFd`\n  // is filled in).\n  sqlite3_vfs makeWrappedNativeVfs();\n\n  struct FileImpl;\n  // Create a VFS definition that actually delegates to the KJ filesystem.\n  sqlite3_vfs makeKjVfs();\n\n  // Create the value returned by `getName()`. Called once at construction time and cached in\n  // `name`.\n  kj::String makeName();\n\n  // Tries to create a new path by appending the given path to this VFS's root directory path.\n  // This allows us to use the system's default VFS implementation, without wrapping, by passing\n  // the result of this function to sqlite3_open_v2().\n  //\n  // Unfortunately, this requires getting a file path from a kj::Directory. On Windows, we can use\n  // the GetFinalPathNameByHandleW() API. On Unix, there's no portable way to do this.\n  kj::Maybe<kj::Path> tryAppend(kj::PathPtr suffix) const;\n\n  friend class SqliteDatabase;\n  class DefaultLockManager;\n};\n\nclass SqliteDatabase::LockManager {\n public:\n  // Obtain a lock for the given database path. The main database file is also provided in case\n  // it is useful. This method only creates the `Lock` object; it's level starts out as UNLOCKED,\n  // meaning no actual lock is held yet.\n  //\n  // `lock()` is only invoked for main database files. SQLite opens other files (journal, WAL); no\n  // `Lock` object is obtained for these.\n  //\n  // If the same database file is opened multiple times via the same `Vfs`, a separate `Lock`\n  // will be obtained each time, so that these locks can coordinate between databases in the\n  // same process. Since typically these databases would be in separate threads, the `lock()`\n  // method is thread-safe (hence `const`). However, a `Lock` instance itself is only accessed\n  // from the calling thread.\n  virtual kj::Own<Lock> lock(kj::PathPtr path, const kj::ReadableFile& mainDatabaseFile) const = 0;\n};\n\n// Implements file locks and shared memory space used to coordination between clients of a\n// particular database. It is expected that if the database is accessible from other processes,\n// this object will coordinate with them.\n//\n// When using a Vfs based on a regular disk directory, this class isn't used; instead, SQLite's\n// native implementation kicks in, which is based on advisory file locks at the OS level, as well\n// as mmapped shared memory from a file next to the database with suffix `-shm`.\nclass SqliteDatabase::Lock {\n public:\n  // The main database can be locked at one of these levels.\n  //\n  // See the SQLite documentation for an explanation of lock levels:\n  //     https://www.sqlite.org/lockingv3.html\n  //\n  // Note, however, that this locking scheme is mostly unused in WAL mode, which everyone should\n  // be using now. In WAL mode, clients almost always have only a `SHARED` lock. It is increased\n  // to `EXCLUSIVE` only when shutting down the database, in order to safely delete the WAL and\n  // WAL-index (-shm) files.\n  //\n  // (The values of this enum correspond to the SQLITE_LOCK_* constants, but we're trying to\n  // avoid including sqlite's header here.)\n  enum Level { UNLOCKED, SHARED, RESERVED, PENDING, EXCLUSIVE };\n\n  // Increase the lock's level. Returns false if the requested level is not available. This\n  // method never blocks; SQLite takes care of retrying if needed. Per SQLite docs, if an attempt\n  // to request an EXCLUSIVE lock fails because of other shared locks (but not other exclusive\n  // locks), the lock will still have transitioned to the PENDING state, which prevents new shared\n  // locks from being taken.\n  //\n  // The Lock starts an level UNLOCKED.\n  virtual bool tryIncreaseLevel(Level level) = 0;\n\n  // Reduce the lock's level. `level` is either UNLOCKED or SHARED.\n  virtual void decreaseLevel(Level level) = 0;\n\n  // Check if any client has a RESERVED lock on the database.\n  virtual bool checkReservedLock() = 0;\n\n  // Get a shared memory region. All regions have the same size, so `size` will be the same for\n  // every call. If `index` exceeds the number of regions that exist so far, and `extend` is false,\n  // this returns an empty array, but if `extend` is true, all regions through the given index are\n  // created (containing zeros).\n  //\n  // The returned array is valid until the object is destroyed, or clearSharedMemory() is called.\n  virtual kj::ArrayPtr<byte> getSharedMemoryRegion(uint index, uint size, bool extend) = 0;\n\n  // Deletes all shared memory regions.\n  //\n  // Called when shutting down the last database client or converting away from WAL mode. The\n  // caller will obtain an exclusive lock before calling this.\n  //\n  // The LockManager is also allowed to discard shared memory automatically any time it knows for\n  // sure that there are no clients.\n  virtual void clearSharedMemory() = 0;\n\n  // Attempt to obtain shared or exclusive locks for the given WAL-mode lock indices, which are in\n  // the range [0, WAL_LOCK_COUNT). Returns true if the locks were successfully obtained (for all\n  // of them), false if at least one lock wasn't available (in which case no change was made). A\n  // shared lock can be obtained as long as there are no exclusive locks. An exclusive lock can be\n  // obtained as long as there are no other locks of any kind.\n  //\n  // The caller may request a shared lock multiple times, in which case it is expected to unlock\n  // the same number of times.\n  virtual bool tryLockWalShared(uint start, uint count) = 0;\n\n  // Attempt to obtain shared or exclusive locks for the given WAL-mode lock indices, which are in\n  // the range [0, WAL_LOCK_COUNT). Returns true if the locks were successfully obtained (for all\n  // of them), false if at least one lock wasn't available (in which case no change was made). A\n  // shared lock can be obtained as long as there are no exclusive locks. An exclusive lock can be\n  // obtained as long as there are no other locks of any kind.\n  //\n  // The caller may request a shared lock multiple times, in which case it is expected to unlock\n  // the same number of times.\n  virtual bool tryLockWalExclusive(uint start, uint count) = 0;\n\n  // Release a previously-obtained WAL-mode lock.\n  virtual void unlockWalShared(uint start, uint count) = 0;\n\n  // Release a previously-obtained WAL-mode lock.\n  virtual void unlockWalExclusive(uint start, uint count) = 0;\n\n  // There are exactly this many WAL-mode locks.\n  static constexpr uint WAL_LOCK_COUNT = 8;\n\n  // Lock names as defined by https://www.sqlite.org/walformat.html#wal_locks\n  static constexpr uint WAL_WRITE_LOCK = 0;\n  static constexpr uint WAL_CKPT_LOCK = 1;\n  static constexpr uint WAL_RECOVER_LOCK = 2;\n  static constexpr uint WAL_READ_LOCK_BASE = 3;\n\n  // There are exactly this may WAL-mode read-mark locks.\n  static constexpr uint WAL_READ_LOCK_COUNT = WAL_LOCK_COUNT - WAL_READ_LOCK_BASE;\n\n  // SQLite sets aside bytes [120, 128) of the first shared memory region for use by the WAL locking\n  // implementation. SQLite will never touch these bytes. This may or may not be needed by your\n  // implementation. SQLite's native implementation on Windows acquires locks on these specific\n  // bytes because Windows file locks are mandatory, meaning they actually block concurrent reads\n  // and writes. SQLite really wants \"advisory\" locks which block other locks but don't actually\n  // block reads and writes. So, it applies the mandatory locks to these bytes which are never\n  // otherwise read nor written.\n  static constexpr uint RESERVED_LOCK_BYTES_OFFSET = 120;\n};\n\ntemplate <typename... Params>\nSqliteDatabase::Query SqliteDatabase::run(\n    QueryOptions options, kj::StringPtr sqlCode, Params&&... params) {\n  return Query(*this, options, sqlCode, kj::fwd<Params>(params)...);\n}\n\ntemplate <typename... Params>\nSqliteDatabase::Query SqliteDatabase::Statement::run(Params&&... params) {\n  return Query(db, QueryOptions{.regulator = regulator}, *this, kj::fwd<Params>(params)...);\n}\n\ntemplate <typename... Params>\nSqliteDatabase::Query SqliteDatabase::Statement::run(StatementOptions options, Params&&... params) {\n  return Query(db, {.regulator = regulator, .allowUnconfirmed = options.allowUnconfirmed}, *this,\n      kj::fwd<Params>(params)...);\n}\n\ntemplate <size_t size, typename... Params>\nSqliteDatabase::Query SqliteDatabase::run(const char (&sqlCode)[size], Params&&... params) {\n  return Query(*this, QueryOptions{.regulator = TRUSTED}, sqlCode, kj::fwd<Params>(params)...);\n}\n\ntemplate <size_t size>\nSqliteDatabase::Statement SqliteDatabase::prepare(const char (&sqlCode)[size]) {\n  return prepare(TRUSTED, kj::StringPtr(sqlCode, size - 1));\n}\ntemplate <size_t size>\nSqliteDatabase::Statement SqliteDatabase::prepare(\n    const Regulator& regulator, const char (&sqlCode)[size]) {\n  return prepare(regulator, kj::StringPtr(sqlCode, size - 1));\n}\n\ninline SqliteDatabase::Statement SqliteDatabase::prepareMulti(\n    const Regulator& regulator, kj::String sqlCode) {\n  return Statement(*this, regulator, kj::mv(sqlCode));\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/state-machine-test.c++",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"state-machine.h\"\n\n#include <kj/test.h>\n\n// Entire test file was Claude-generated initially.\n\nnamespace workerd {\nnamespace {\n\n// =============================================================================\n// Test State Types\n// =============================================================================\n\nstruct Idle {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"idle\"_kj;\n  bool initialized = false;\n};\n\nstruct Running {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"running\"_kj;\n  kj::String taskName;\n  int progress = 0;\n\n  Running() = default;\n  explicit Running(kj::String name): taskName(kj::mv(name)) {}\n};\n\nstruct Completed {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"completed\"_kj;\n  int result;\n\n  explicit Completed(int r): result(r) {}\n};\n\nstruct Failed {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"failed\"_kj;\n  kj::String error;\n\n  explicit Failed(kj::String err): error(kj::mv(err)) {}\n};\n\n// =============================================================================\n// Basic StateMachine Tests\n// =============================================================================\n\nKJ_TEST(\"StateMachine: basic state checks\") {\n  auto machine = StateMachine<Idle, Running, Completed, Failed>::create<Idle>();\n\n  // Initialized to Idle via create()\n  KJ_EXPECT(machine.isInitialized());\n  KJ_EXPECT(machine.is<Idle>());\n  KJ_EXPECT(!machine.is<Running>());\n}\n\nKJ_TEST(\"StateMachine: state data access\") {\n  auto machine =\n      StateMachine<Idle, Running, Completed, Failed>::create<Running>(kj::str(\"my-task\"));\n\n  KJ_EXPECT(machine.is<Running>());\n  auto& running = machine.getUnsafe<Running>();\n  KJ_EXPECT(running.taskName == \"my-task\");\n  KJ_EXPECT(running.progress == 0);\n\n  // Modify state data\n  running.progress = 50;\n  KJ_EXPECT(machine.getUnsafe<Running>().progress == 50);\n}\n\nKJ_TEST(\"StateMachine: tryGet returns none for wrong state\") {\n  auto machine = StateMachine<Idle, Running, Completed, Failed>::create<Idle>();\n\n  // tryGet for correct state\n  KJ_IF_SOME(idle, machine.tryGetUnsafe<Idle>()) {\n    KJ_EXPECT(!idle.initialized);\n  } else {\n    KJ_FAIL_EXPECT(\"Should have gotten Idle state\");\n  }\n\n  // tryGet for wrong state\n  KJ_EXPECT(machine.tryGetUnsafe<Running>() == kj::none);\n  KJ_EXPECT(machine.tryGetUnsafe<Completed>() == kj::none);\n}\n\nKJ_TEST(\"StateMachine: isAnyOf checks multiple states\") {\n  auto machine = StateMachine<Idle, Running, Completed, Failed>::create<Completed>(42);\n\n  // Use local variables to avoid KJ_EXPECT macro parsing issues with template brackets\n  bool isCompletedOrFailed = machine.isAnyOf<Completed, Failed>();\n  bool isIdleOrRunning = machine.isAnyOf<Idle, Running>();\n  KJ_EXPECT(isCompletedOrFailed);\n  KJ_EXPECT(!isIdleOrRunning);\n\n  machine.transitionTo<Failed>(kj::str(\"error\"));\n  isCompletedOrFailed = machine.isAnyOf<Completed, Failed>();\n  isIdleOrRunning = machine.isAnyOf<Idle, Running>();\n  KJ_EXPECT(isCompletedOrFailed);\n  KJ_EXPECT(!isIdleOrRunning);\n}\n\nKJ_TEST(\"StateMachine: transitionFromTo with precondition\") {\n  auto machine = StateMachine<Idle, Running, Completed, Failed>::create<Idle>();\n\n  // Transition from wrong state fails\n  auto result1 = machine.transitionFromTo<Running, Completed>(42);\n  KJ_EXPECT(result1 == kj::none);\n  KJ_EXPECT(machine.is<Idle>());  // Still in Idle\n\n  // Transition from correct state succeeds\n  machine.transitionTo<Running>(kj::str(\"task\"));\n  auto result2 = machine.transitionFromTo<Running, Completed>(100);\n  KJ_EXPECT(result2 != kj::none);\n  KJ_EXPECT(machine.is<Completed>());\n  KJ_EXPECT(machine.getUnsafe<Completed>().result == 100);\n}\n\nKJ_TEST(\"StateMachine: factory create\") {\n  auto machine = StateMachine<Idle, Running, Completed, Failed>::create<Running>(kj::str(\"task\"));\n  KJ_EXPECT(machine.is<Running>());\n  KJ_EXPECT(machine.getUnsafe<Running>().taskName == \"task\");\n}\n\n// Tests for uninitialized state behavior have been removed since the default\n// constructor is now private and state machines must be created via create<>().\n\nKJ_TEST(\"StateMachine: works with KJ_SWITCH_ONEOF\") {\n  auto machine = StateMachine<Idle, Running, Completed, Failed>::create<Running>(kj::str(\"test\"));\n\n  kj::String result;\n  KJ_SWITCH_ONEOF(machine) {\n    KJ_CASE_ONEOF(idle, Idle) {\n      result = kj::str(\"idle\");\n    }\n    KJ_CASE_ONEOF(running, Running) {\n      result = kj::str(\"running: \", running.taskName);\n    }\n    KJ_CASE_ONEOF(completed, Completed) {\n      result = kj::str(\"completed: \", completed.result);\n    }\n    KJ_CASE_ONEOF(failed, Failed) {\n      result = kj::str(\"failed: \", failed.error);\n    }\n  }\n\n  KJ_EXPECT(result == \"running: test\");\n}\n\nKJ_TEST(\"StateMachine: currentStateName introspection\") {\n  auto machine = StateMachine<Idle, Running, Completed, Failed>::create<Idle>();\n\n  // Each state\n  KJ_EXPECT(machine.currentStateName() == \"idle\"_kj);\n\n  machine.transitionTo<Running>(kj::str(\"task\"));\n  KJ_EXPECT(machine.currentStateName() == \"running\"_kj);\n\n  machine.transitionTo<Completed>(42);\n  KJ_EXPECT(machine.currentStateName() == \"completed\"_kj);\n\n  machine.transitionTo<Failed>(kj::str(\"error\"));\n  KJ_EXPECT(machine.currentStateName() == \"failed\"_kj);\n}\n\n// =============================================================================\n// Memory Safety Tests\n// =============================================================================\n\nKJ_TEST(\"StateMachine: whenState provides safe scoped access\") {\n  auto machine = StateMachine<Idle, Running, Completed, Failed>::create<Running>(kj::str(\"task\"));\n\n  // whenState returns result and locks transitions\n  auto result = machine.whenState<Running>([](Running& r) { return r.taskName.size(); });\n  KJ_EXPECT(result != kj::none);\n  KJ_EXPECT(KJ_ASSERT_NONNULL(result) == 4);\n\n  // Returns none for wrong state\n  auto result2 = machine.whenState<Idle>([](Idle& i) { return i.initialized; });\n  KJ_EXPECT(result2 == kj::none);\n}\n\nKJ_TEST(\"StateMachine: whenState blocks transitions during callback\") {\n  auto machine = StateMachine<Idle, Running, Completed, Failed>::create<Running>(kj::str(\"task\"));\n\n  // Cannot transition while locked\n  auto tryTransitionInCallback = [&]() {\n    machine.whenState<Running>([&](Running&) {\n      // Attempting to transition while locked should throw\n      machine.transitionTo<Completed>(42);\n    });\n  };\n  KJ_EXPECT_THROW_MESSAGE(\"transitions are locked\", tryTransitionInCallback());\n\n  // State should still be Running (transition was blocked)\n  KJ_EXPECT(machine.is<Running>());\n}\n\nKJ_TEST(\"StateMachine: transition lock count is tracked\") {\n  auto machine = StateMachine<Idle, Running, Completed, Failed>::create<Idle>();\n\n  KJ_EXPECT(!machine.isTransitionLocked());\n\n  {\n    auto lock1 = machine.acquireTransitionLock();\n    KJ_EXPECT(machine.isTransitionLocked());\n\n    {\n      auto lock2 = machine.acquireTransitionLock();\n      KJ_EXPECT(machine.isTransitionLocked());\n    }\n\n    // Still locked after inner lock released\n    KJ_EXPECT(machine.isTransitionLocked());\n  }\n\n  // Fully unlocked\n  KJ_EXPECT(!machine.isTransitionLocked());\n}\n\nKJ_TEST(\"StateMachine: void whenState returns bool\") {\n  auto machine = StateMachine<Idle, Running, Completed, Failed>::create<Running>(kj::str(\"task\"));\n\n  bool executed = false;\n\n  // void callback returns true when executed\n  bool result = machine.whenState<Running>([&](Running&) { executed = true; });\n  KJ_EXPECT(result == true);\n  KJ_EXPECT(executed);\n\n  // void callback returns false when not in state\n  executed = false;\n  bool result2 = machine.whenState<Idle>([&](Idle&) { executed = true; });\n  KJ_EXPECT(result2 == false);\n  KJ_EXPECT(!executed);\n}\n\n// =============================================================================\n// StateMachine Tests\n// =============================================================================\n\n// Test state types for resource lifecycle tests (TerminalStates, ErrorState, ActiveState, etc.)\nstruct Active {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"active\"_kj;\n  kj::String resourceName;\n  explicit Active(kj::String name): resourceName(kj::mv(name)) {}\n};\n\nstruct Closed {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"closed\"_kj;\n};\n\nstruct Errored {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"errored\"_kj;\n  kj::String reason;\n  explicit Errored(kj::String r): reason(kj::mv(r)) {}\n};\n\nKJ_TEST(\"StateMachine: basic usage without specs\") {\n  auto machine = StateMachine<Active, Closed, Errored>::create<Active>(kj::str(\"resource\"));\n\n  // Basic state operations work\n  KJ_EXPECT(machine.isInitialized());\n  KJ_EXPECT(machine.is<Active>());\n  KJ_EXPECT(machine.getUnsafe<Active>().resourceName == \"resource\");\n\n  machine.transitionTo<Closed>();\n  KJ_EXPECT(machine.is<Closed>());\n\n  // Can transition back (no terminal enforcement without spec)\n  machine.transitionTo<Active>(kj::str(\"another\"));\n  KJ_EXPECT(machine.is<Active>());\n}\n\n// Tests for uninitialized state behavior have been removed since the default\n// constructor is now private and state machines must be created via create<>().\n\nKJ_TEST(\"StateMachine: with TerminalStates spec\") {\n  auto machine =\n      StateMachine<TerminalStates<Closed, Errored>, Active, Closed, Errored>::create<Active>(\n          kj::str(\"resource\"));\n  KJ_EXPECT(!machine.isTerminal());\n\n  machine.transitionTo<Closed>();\n  KJ_EXPECT(machine.isTerminal());\n\n  // Cannot transition from terminal state\n  auto tryTransition = [&]() { machine.transitionTo<Active>(kj::str(\"another\")); };\n  KJ_EXPECT_THROW_MESSAGE(\"Cannot transition from terminal state\", tryTransition());\n\n  // But forceTransitionTo works\n  machine.forceTransitionTo<Active>(kj::str(\"forced\"));\n  KJ_EXPECT(machine.is<Active>());\n}\n\nKJ_TEST(\"StateMachine: with ErrorState spec\") {\n  auto machine = StateMachine<ErrorState<Errored>, Active, Closed, Errored>::create<Active>(\n      kj::str(\"resource\"));\n  KJ_EXPECT(!machine.isErrored());\n  KJ_EXPECT(machine.tryGetErrorUnsafe() == kj::none);\n\n  machine.transitionTo<Errored>(kj::str(\"something went wrong\"));\n  KJ_EXPECT(machine.isErrored());\n\n  KJ_IF_SOME(err, machine.tryGetErrorUnsafe()) {\n    KJ_EXPECT(err.reason == \"something went wrong\");\n  } else {\n    KJ_FAIL_EXPECT(\"Should have gotten error\");\n  }\n\n  KJ_EXPECT(machine.getErrorUnsafe().reason == \"something went wrong\");\n}\n\nKJ_TEST(\"StateMachine: with ActiveState spec\") {\n  auto machine = StateMachine<ActiveState<Active>, Active, Closed, Errored>::create<Active>(\n      kj::str(\"resource\"));\n  KJ_EXPECT(machine.isActive());\n  KJ_EXPECT(!machine.isInactive());\n\n  KJ_IF_SOME(active, machine.tryGetActiveUnsafe()) {\n    KJ_EXPECT(active.resourceName == \"resource\");\n  } else {\n    KJ_FAIL_EXPECT(\"Should be active\");\n  }\n\n  // whenActive executes and returns value\n  auto result = machine.whenActive([](Active& a) { return a.resourceName.size(); });\n  KJ_EXPECT(result != kj::none);\n  KJ_EXPECT(KJ_ASSERT_NONNULL(result) == 8);  // \"resource\"\n\n  machine.transitionTo<Closed>();\n  KJ_EXPECT(!machine.isActive());\n  KJ_EXPECT(machine.isInactive());\n\n  // whenActive returns none when not active\n  auto result2 = machine.whenActive([](Active& a) { return a.resourceName.size(); });\n  KJ_EXPECT(result2 == kj::none);\n}\n\nKJ_TEST(\"StateMachine: whenActiveOr\") {\n  auto machine = StateMachine<ActiveState<Active>, Active, Closed, Errored>::create<Active>(\n      kj::str(\"resource\"));\n\n  // whenActiveOr executes when active\n  auto result = machine.whenActiveOr([](Active& a) { return a.resourceName.size(); }, 0ul);\n  KJ_EXPECT(result == 8);\n\n  // After close, returns default\n  machine.transitionTo<Closed>();\n  auto result2 = machine.whenActiveOr([](Active& a) { return a.resourceName.size(); }, 999ul);\n  KJ_EXPECT(result2 == 999);\n}\n\nKJ_TEST(\"StateMachine: requireActiveUnsafe\") {\n  auto machine = StateMachine<ActiveState<Active>, Active, Closed, Errored>::create<Active>(\n      kj::str(\"resource\"));\n\n  // requireActiveUnsafeUnsafe returns reference when active\n  auto& active = machine.requireActiveUnsafe();\n  KJ_EXPECT(active.resourceName == \"resource\");\n\n  // requireActiveUnsafe with custom message works when active\n  auto& active2 = machine.requireActiveUnsafe(\"Custom message\");\n  KJ_EXPECT(active2.resourceName == \"resource\");\n\n  machine.transitionTo<Closed>();\n\n  // requireActiveUnsafe throws when not active\n  KJ_EXPECT_THROW_MESSAGE(\n      \"State machine is not in the active state\", (void)machine.requireActiveUnsafe());\n\n  // requireActiveUnsafe throws custom message when not active\n  KJ_EXPECT_THROW_MESSAGE(\n      \"Stream is closed\", (void)machine.requireActiveUnsafe(\"Stream is closed\"));\n}\n\nKJ_TEST(\"StateMachine: with PendingStates spec\") {\n  auto machine =\n      StateMachine<PendingStates<Closed, Errored>, Active, Closed, Errored>::create<Active>(\n          kj::str(\"resource\"));\n\n  // Start an operation\n  machine.beginOperation();\n  KJ_EXPECT(machine.hasOperationInProgress());\n\n  // Defer a close\n  bool immediate = machine.deferTransitionTo<Closed>();\n  KJ_EXPECT(!immediate);            // Deferred\n  KJ_EXPECT(machine.is<Active>());  // Still active\n  KJ_EXPECT(machine.hasPendingState());\n  KJ_EXPECT(machine.pendingStateIs<Closed>());\n  KJ_EXPECT(machine.isOrPending<Closed>());\n\n  // End operation - pending state applied\n  bool applied = machine.endOperation();\n  KJ_EXPECT(applied);\n  KJ_EXPECT(machine.is<Closed>());\n  KJ_EXPECT(!machine.hasPendingState());\n}\n\nKJ_TEST(\"StateMachine: with PendingStates scoped operation\") {\n  auto machine =\n      StateMachine<PendingStates<Closed, Errored>, Active, Closed, Errored>::create<Active>(\n          kj::str(\"resource\"));\n\n  {\n    auto scope = machine.scopedOperation();\n    KJ_EXPECT(machine.hasOperationInProgress());\n\n    auto _ KJ_UNUSED = machine.deferTransitionTo<Closed>();\n    KJ_EXPECT(machine.is<Active>());  // Still active in scope\n  }\n\n  // Scope ended, pending state applied\n  KJ_EXPECT(machine.is<Closed>());\n}\n\nKJ_TEST(\"StateMachine: full-featured stream-like usage\") {\n  // This demonstrates the common stream pattern with all features\n  auto machine = StateMachine<TerminalStates<Closed, Errored>, ErrorState<Errored>,\n      ActiveState<Active>, PendingStates<Closed, Errored>, Active, Closed,\n      Errored>::create<Active>(kj::str(\"http-body\"));\n  KJ_EXPECT(machine.isActive());\n  KJ_EXPECT(!machine.isTerminal());\n  KJ_EXPECT(!machine.isErrored());\n\n  // Safe access with whenActive\n  machine.whenActive([](Active& a) { a.resourceName = kj::str(\"modified\"); });\n  KJ_EXPECT(machine.getUnsafe<Active>().resourceName == \"modified\");\n\n  // Start a read operation\n  machine.beginOperation();\n\n  // Close is requested mid-operation - deferred\n  auto deferred KJ_UNUSED = machine.deferTransitionTo<Closed>();\n  KJ_EXPECT(machine.isActive());  // Still active!\n  KJ_EXPECT(machine.isOrPending<Closed>());\n  KJ_EXPECT(!machine.isTerminal());  // Not terminal yet\n\n  // End operation - close applied\n  auto applied KJ_UNUSED = machine.endOperation();\n  KJ_EXPECT(machine.is<Closed>());\n  KJ_EXPECT(machine.isTerminal());\n  KJ_EXPECT(!machine.isActive());\n  KJ_EXPECT(machine.isInactive());\n\n  // Cannot transition from terminal\n  auto tryTransition = [&]() { machine.transitionTo<Active>(kj::str(\"x\")); };\n  KJ_EXPECT_THROW_MESSAGE(\"Cannot transition from terminal state\", tryTransition());\n}\n\nKJ_TEST(\"StateMachine: KJ_SWITCH_ONEOF works\") {\n  auto machine = StateMachine<Active, Closed, Errored>::create<Active>(kj::str(\"test\"));\n\n  kj::String result;\n  KJ_SWITCH_ONEOF(machine) {\n    KJ_CASE_ONEOF(active, Active) {\n      result = kj::str(\"active: \", active.resourceName);\n    }\n    KJ_CASE_ONEOF(closed, Closed) {\n      result = kj::str(\"closed\");\n    }\n    KJ_CASE_ONEOF(errored, Errored) {\n      result = kj::str(\"errored: \", errored.reason);\n    }\n  }\n\n  KJ_EXPECT(result == \"active: test\");\n}\n\nKJ_TEST(\"StateMachine: whenState locks transitions\") {\n  auto machine = StateMachine<Active, Closed, Errored>::create<Active>(kj::str(\"resource\"));\n\n  // Cannot transition while locked\n  auto tryTransitionInCallback = [&]() {\n    machine.whenState<Active>([&](Active&) { machine.transitionTo<Closed>(); });\n  };\n  KJ_EXPECT_THROW_MESSAGE(\"transitions are locked\", tryTransitionInCallback());\n\n  // State unchanged\n  KJ_EXPECT(machine.is<Active>());\n}\n\nKJ_TEST(\"StateMachine: currentStateName\") {\n  auto machine = StateMachine<Active, Closed, Errored>::create<Active>(kj::str(\"x\"));\n  KJ_EXPECT(machine.currentStateName() == \"active\"_kj);\n\n  machine.transitionTo<Closed>();\n  KJ_EXPECT(machine.currentStateName() == \"closed\"_kj);\n\n  machine.transitionTo<Errored>(kj::str(\"err\"));\n  KJ_EXPECT(machine.currentStateName() == \"errored\"_kj);\n}\n\nKJ_TEST(\"StateMachine: const whenState works\") {\n  auto machine = StateMachine<Active, Closed, Errored>::create<Active>(kj::str(\"resource\"));\n\n  const auto& constMachine = machine;\n\n  // Const whenState works and returns value\n  auto result =\n      constMachine.whenState<Active>([](const Active& a) { return a.resourceName.size(); });\n  KJ_EXPECT(result != kj::none);\n  KJ_EXPECT(KJ_ASSERT_NONNULL(result) == 8);  // \"resource\"\n\n  // Const whenState returns none for wrong state\n  auto result2 = constMachine.whenState<Closed>([](const Closed&) { return 42; });\n  KJ_EXPECT(result2 == kj::none);\n}\n\nKJ_TEST(\"StateMachine: deferTransitionTo respects terminal states\") {\n  auto machine = StateMachine<TerminalStates<Closed, Errored>, PendingStates<Closed, Errored>,\n      Active, Closed, Errored>::create<Active>(kj::str(\"resource\"));\n\n  // Close the machine (terminal state)\n  machine.transitionTo<Closed>();\n  KJ_EXPECT(machine.isTerminal());\n\n  // deferTransitionTo should also fail from terminal state\n  auto tryDeferTransition = [&]() {\n    auto _ KJ_UNUSED = machine.deferTransitionTo<Errored>(kj::str(\"error\"));\n  };\n  KJ_EXPECT_THROW_MESSAGE(\"Cannot transition from terminal state\", tryDeferTransition());\n}\n\n// =============================================================================\n// Streams Integration Example\n// =============================================================================\n// This demonstrates how StateMachine could replace the separate\n// state + readState pattern found in ReadableStreamInternalController.\n\nnamespace stream_integration_example {\n\n// Simulated stream source (like ReadableStreamSource)\nstruct MockSource {\n  bool dataAvailable = true;\n\n  kj::Maybe<kj::String> read() {\n    if (dataAvailable) {\n      dataAvailable = false;\n      return kj::str(\"data chunk\");\n    }\n    return kj::none;\n  }\n};\n\n// State types matching the streams pattern\nstruct Readable {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"readable\"_kj;\n  kj::Own<MockSource> source;\n\n  explicit Readable(kj::Own<MockSource> s): source(kj::mv(s)) {}\n};\n\nstruct StreamClosed {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"closed\"_kj;\n};\n\nstruct StreamErrored {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"errored\"_kj;\n  kj::String reason;\n\n  explicit StreamErrored(kj::String r): reason(kj::mv(r)) {}\n};\n\n// Lock states (separate state machine in the real code)\nstruct Unlocked {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"unlocked\"_kj;\n};\n\nstruct Locked {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"locked\"_kj;\n};\n\nstruct ReaderLocked {\n  static constexpr kj::StringPtr NAME KJ_UNUSED = \"reader_locked\"_kj;\n  uint32_t readerId;\n  explicit ReaderLocked(uint32_t id): readerId(id) {}\n};\n\n// The full-featured state machine type for stream data state\nusing StreamDataState = StateMachine<TerminalStates<StreamClosed, StreamErrored>,\n    ErrorState<StreamErrored>,\n    ActiveState<Readable>,\n    PendingStates<StreamClosed, StreamErrored>,\n    Readable,\n    StreamClosed,\n    StreamErrored>;\n\n// Lock state machine (simpler)\nusing StreamLockState = StateMachine<Unlocked, Locked, ReaderLocked>;\n\n// Simulated controller showing combined usage\nclass MockReadableStreamController {\n public:\n  MockReadableStreamController()\n      : dataState(StreamDataState::create<Readable>(kj::heap<MockSource>())),\n        lockState(StreamLockState::create<Unlocked>()) {}\n\n  explicit MockReadableStreamController(kj::Own<MockSource> source)\n      : dataState(StreamDataState::create<Readable>(kj::mv(source))),\n        lockState(StreamLockState::create<Unlocked>()) {}\n\n  bool isReadable() const {\n    return dataState.isActive();\n  }\n\n  bool isClosedOrErrored() const {\n    return dataState.isTerminal();\n  }\n\n  bool isErrored() const {\n    return dataState.isErrored();\n  }\n\n  bool isLocked() const {\n    return !lockState.is<Unlocked>();\n  }\n\n  kj::Maybe<kj::String> read() {\n    // Only read if in readable state and not already reading\n    if (!dataState.isActive()) {\n      return kj::none;\n    }\n\n    // Start read operation (defers close/error during read)\n    auto op = dataState.scopedOperation();\n\n    // Safe access to source\n    KJ_IF_SOME(result, dataState.whenActive([](Readable& r) -> kj::Maybe<kj::String> {\n      return r.source->read();\n    })) {\n      return kj::mv(result);\n    }\n    return kj::none;\n  }\n\n  void close() {\n    if (dataState.isTerminal()) return;\n\n    // If operation in progress, defer the close\n    auto _ KJ_UNUSED = dataState.deferTransitionTo<StreamClosed>();\n  }\n\n  void error(kj::String reason) {\n    if (dataState.isTerminal()) return;\n\n    // Error takes precedence - force even if operation in progress\n    dataState.forceTransitionTo<StreamErrored>(kj::mv(reason));\n  }\n\n  bool acquireReaderLock(uint32_t readerId) {\n    if (isLocked()) return false;\n    lockState.transitionTo<ReaderLocked>(readerId);\n    return true;\n  }\n\n  void releaseReaderLock() {\n    lockState.transitionTo<Unlocked>();\n  }\n\n private:\n  StreamDataState dataState;\n  StreamLockState lockState;\n};\n\n}  // namespace stream_integration_example\n\nKJ_TEST(\"StateMachine: stream integration example - basic flow\") {\n  using namespace stream_integration_example;\n\n  MockReadableStreamController controller(kj::heap<MockSource>());\n\n  KJ_EXPECT(controller.isReadable());\n  KJ_EXPECT(!controller.isClosedOrErrored());\n  KJ_EXPECT(!controller.isLocked());\n\n  // Acquire reader lock\n  KJ_EXPECT(controller.acquireReaderLock(123));\n  KJ_EXPECT(controller.isLocked());\n\n  // Read data\n  auto chunk1 = controller.read();\n  KJ_EXPECT(chunk1 != kj::none);\n  KJ_EXPECT(KJ_ASSERT_NONNULL(chunk1) == \"data chunk\");\n\n  // Second read returns none (source exhausted)\n  auto chunk2 = controller.read();\n  KJ_EXPECT(chunk2 == kj::none);\n\n  // Close the stream\n  controller.close();\n  KJ_EXPECT(!controller.isReadable());\n  KJ_EXPECT(controller.isClosedOrErrored());\n\n  // Release lock\n  controller.releaseReaderLock();\n  KJ_EXPECT(!controller.isLocked());\n}\n\nKJ_TEST(\"StateMachine: stream integration example - close during read\") {\n  using namespace stream_integration_example;\n\n  MockReadableStreamController controller(kj::heap<MockSource>());\n\n  // This test demonstrates that if close() is called during a read operation,\n  // the close is deferred until the read completes.\n  //\n  // In a real implementation, this would be more complex with async operations,\n  // but the pattern is the same.\n\n  // Simulate close being called while readable (no operation in progress)\n  controller.close();\n  KJ_EXPECT(controller.isClosedOrErrored());\n}\n\nKJ_TEST(\"StateMachine: stream integration example - error handling\") {\n  using namespace stream_integration_example;\n\n  MockReadableStreamController controller(kj::heap<MockSource>());\n\n  // Error the stream\n  controller.error(kj::str(\"Network failure\"));\n\n  KJ_EXPECT(!controller.isReadable());\n  KJ_EXPECT(controller.isClosedOrErrored());\n  KJ_EXPECT(controller.isErrored());\n\n  // Reads after error return none\n  auto chunk = controller.read();\n  KJ_EXPECT(chunk == kj::none);\n}\n\n// =============================================================================\n// StateMachine Additional API Tests\n// =============================================================================\n\nKJ_TEST(\"StateMachine: visit method\") {\n  auto machine = StateMachine<Active, Closed, Errored>::create<Active>(kj::str(\"resource\"));\n\n  // Visit with return value - note: visitor must return the same type for all states\n  size_t result = machine.visit([](auto& s) -> size_t {\n    using S = std::decay_t<decltype(s)>;\n    if constexpr (std::is_same_v<S, Active>) {\n      return s.resourceName.size();\n    } else if constexpr (std::is_same_v<S, Closed>) {\n      return 0;\n    } else {\n      return s.reason.size();\n    }\n  });\n  KJ_EXPECT(result == 8);  // \"resource\"\n\n  machine.transitionTo<Closed>();\n  result = machine.visit([](auto& s) -> size_t {\n    using S = std::decay_t<decltype(s)>;\n    if constexpr (std::is_same_v<S, Active>) {\n      return s.resourceName.size();\n    } else if constexpr (std::is_same_v<S, Closed>) {\n      return 0;\n    } else {\n      return s.reason.size();\n    }\n  });\n  KJ_EXPECT(result == 0);\n}\n\nKJ_TEST(\"StateMachine: visit const method\") {\n  auto machine = StateMachine<Active, Closed, Errored>::create<Active>(kj::str(\"test\"));\n\n  const auto& constMachine = machine;\n  size_t result = constMachine.visit([](const auto& s) -> size_t {\n    using S = std::decay_t<decltype(s)>;\n    if constexpr (std::is_same_v<S, Active>) {\n      return 1;\n    } else if constexpr (std::is_same_v<S, Closed>) {\n      return 2;\n    } else {\n      return 3;\n    }\n  });\n  KJ_EXPECT(result == 1);\n}\n\nKJ_TEST(\"StateMachine: underlying accessor\") {\n  auto machine = StateMachine<Active, Closed, Errored>::create<Active>(kj::str(\"resource\"));\n\n  // Access underlying kj::OneOf\n  auto& underlying = machine.underlying();\n  KJ_EXPECT(underlying.is<Active>());\n  KJ_EXPECT(underlying.get<Active>().resourceName == \"resource\"_kj);\n\n  // Const access\n  const auto& constMachine = machine;\n  const auto& constUnderlying = constMachine.underlying();\n  KJ_EXPECT(constUnderlying.is<Active>());\n}\n\nKJ_TEST(\"StateMachine: applyPendingStateImpl respects terminal\") {\n  // When we force-transition to a terminal state during an operation,\n  // the pending state should be discarded on endOperation.\n  auto machine = StateMachine<TerminalStates<Closed, Errored>, PendingStates<Closed, Errored>,\n      Active, Closed, Errored>::create<Active>(kj::str(\"resource\"));\n\n  // Start an operation\n  machine.beginOperation();\n\n  // Request a deferred close\n  auto _ KJ_UNUSED = machine.deferTransitionTo<Closed>();\n  KJ_EXPECT(machine.hasPendingState());\n  KJ_EXPECT(machine.is<Active>());\n\n  // Force transition to error (terminal state) while operation is in progress\n  machine.forceTransitionTo<Errored>(kj::str(\"forced error\"));\n  KJ_EXPECT(machine.is<Errored>());\n\n  // End operation - pending Close should be discarded since we're in terminal state\n  bool pendingApplied = machine.endOperation();\n  KJ_EXPECT(!pendingApplied);             // Pending was discarded, not applied\n  KJ_EXPECT(machine.is<Errored>());       // Still in errored state\n  KJ_EXPECT(!machine.hasPendingState());  // Pending was cleared\n}\n\nKJ_TEST(\"StateMachine: endOperation inside whenState throws\") {\n  // This test verifies that ending an operation (which could apply a pending state)\n  // inside a whenState() callback throws an error. This prevents UAF where a\n  // transition invalidates the reference being used in the callback.\n  auto machine =\n      StateMachine<PendingStates<Closed, Errored>, Active, Closed, Errored>::create<Active>(\n          kj::str(\"resource\"));\n\n  // This pattern would cause UAF without the safety check:\n  //   whenState gets reference to Active\n  //   scopedOperation ends, applies pending state -> Active is destroyed\n  //   callback continues using destroyed Active reference\n  auto tryUnsafePattern = [&]() {\n    machine.whenState<Active>([&](Active&) {\n      {\n        auto op = machine.scopedOperation();\n        auto _ KJ_UNUSED = machine.deferTransitionTo<Closed>();\n      }  // op destroyed here - endOperation() would apply pending state\n    });\n  };\n\n  KJ_EXPECT_THROW_MESSAGE(\"transitions are locked\", tryUnsafePattern());\n\n  // Verify the machine is still in a valid state (transition was blocked)\n  KJ_EXPECT(machine.is<Active>());\n}\n\nKJ_TEST(\"StateMachine: endOperation outside whenState works\") {\n  // Verify the correct pattern still works: end operations outside whenState\n  auto machine =\n      StateMachine<PendingStates<Closed, Errored>, Active, Closed, Errored>::create<Active>(\n          kj::str(\"resource\"));\n\n  {\n    auto op = machine.scopedOperation();\n    machine.whenState<Active>([&](Active& a) {\n      // Safe to use 'a' here - no operation ending in this scope\n      KJ_EXPECT(a.resourceName == \"resource\");\n    });\n    auto _ KJ_UNUSED = machine.deferTransitionTo<Closed>();\n  }  // op ends here, OUTSIDE any whenState callback - safe!\n\n  KJ_EXPECT(machine.is<Closed>());\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/state-machine.h",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n// MSVC uses a different attribute name for no_unique_address\n#if _MSC_VER\n#define WD_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]\n#else\n#define WD_NO_UNIQUE_ADDRESS [[no_unique_address]]\n#endif\n\n// State Machine Abstraction built on kj::OneOf.\n// TODO(later): If this proves useful, consider moving it into kj itself as there\n// are no workerd-specific dependencies.\n//\n// Entire implementation was Claude-generated initially.\n//\n// Most of the detailed doc comments here are largely intended to be used by agents\n// and tooling. Human readers may prefer to just skip to the actual code.\n//\n// This header provides utilities for building type-safe state machines using kj::OneOf.\n// It addresses common patterns found throughout the workerd codebase with improvements\n// that provide tangible benefits over raw kj::OneOf usage.\n//\n// =============================================================================\n// WHY USE THIS INSTEAD OF RAW kj::OneOf?\n// =============================================================================\n//\n// Throughout workerd, we use kj::OneOf as a state machine to track the lifecycle\n// of streams, readers, writers, and other resources. A typical pattern looks like:\n//\n//   kj::OneOf<Readable, Closed, kj::Exception> state;\n//\n//   void read() {\n//     KJ_SWITCH_ONEOF(state) {\n//       KJ_CASE_ONEOF(readable, Readable) {\n//         auto data = readable.source->read();  // Get reference to state\n//         processData(data);                    // Call some function...\n//         readable.source->advance();           // Use reference again - UAF!\n//       }\n//       KJ_CASE_ONEOF(closed, Closed) { ... }\n//       KJ_CASE_ONEOF(err, kj::Exception) { ... }\n//     }\n//   }\n//\n// THE PROBLEM: Use-After-Free (UAF) from unsound state-transitions\n//\n// The `readable` reference points into the kj::OneOf's internal storage. If ANY\n// code path between obtaining that reference and using it triggers a state\n// transition (even indirectly through callbacks, promise continuations, or\n// nested calls), the reference becomes dangling:\n//\n//   KJ_CASE_ONEOF(readable, Readable) {\n//     readable.source->read();    // This might call back into our code...\n//                                 // ...which might call close()...\n//                                 // ...which does state.init<Closed>()\n//     readable.buffer.size();     // UAF! readable is now destroyed\n//   }\n//\n// This is particularly insidious because:\n//   1. The bug may not manifest in simple tests\n//   2. It depends on complex callback chains that are hard to reason about\n//   3. It causes memory corruption that may crash much later\n//   4. ASAN/valgrind may not catch it if the memory is quickly reused\n//\n// HOW StateMachine HELPS:\n//\n// 1. TRANSITION LOCKING via whenState()/whenActive():\n//\n//    state.whenState<Readable>([](Readable& r) {\n//      r.source->read();         // If this tries to transition...\n//      r.buffer.size();          // ...it throws instead of UAF\n//    });\n//\n//    The callback holds a \"transition lock\" - any attempt to transition the\n//    state machine while the lock is held will throw an exception instead of\n//    silently corrupting memory. This turns silent UAF into a loud, debuggable\n//    failure.\n//\n// 2. DEFERRED TRANSITIONS for async operations:\n//\n//    When code legitimately needs to transition during an operation (e.g.,\n//    a read discovers EOF and needs to close), use deferred transitions:\n//\n//    {\n//      auto op = state.scopedOperation();\n//      state.whenActive([&](Readable& r) {\n//        if (r.source->atEof()) {\n//          state.deferTransitionTo<Closed>();  // Queued, not immediate\n//        }\n//      });\n//    }  // Transition happens here, after callback completes safely\n//\n// 3. TERMINAL STATE ENFORCEMENT:\n//\n//    Once a stream is Closed or Errored, it should never transition back to\n//    Readable. Raw kj::OneOf allows this silently:\n//\n//      state.init<Closed>();\n//      state.init<Readable>(...);  // Oops - zombie stream!\n//\n//    StateMachine with TerminalStates<> will throw if you attempt this,\n//    catching the bug immediately.\n//\n// 4. SEMANTIC HELPERS:\n//\n//    Instead of: state.is<kj::Exception>() || state.is<Closed>()\n//    Write:      state.isTerminal()  or  state.isInactive()\n//\n//    Instead of: KJ_IF_SOME(e, state.tryGetUnsafe<kj::Exception>()) { ... }\n//    Write:      KJ_IF_SOME(e, state.tryGetErrorUnsafe()) { ... }\n//\n// WHEN TO USE:\n//\n//   - Simple state tracking: StateMachine<States...> is fine\n//   - Resource lifecycle (streams, handles): Use TerminalStates + PendingStates\n//   - Migrating existing code: See MIGRATION GUIDE section below\n//\n// =============================================================================\n// STATE MACHINE\n// =============================================================================\n//\n// StateMachine supports composable features via spec types:\n//\n//   // Simple (no specs)\n//   StateMachine<Idle, Running, Done> basic;\n//\n//   // With terminal state enforcement\n//   StateMachine<TerminalStates<Done>, Idle, Running, Done> withTerminal;\n//\n//   // With error extraction helpers\n//   StateMachine<ErrorState<Errored>, Active, Closed, Errored> withError;\n//\n//   // With deferred transitions\n//   StateMachine<PendingStates<Closed, Errored>, Active, Closed, Errored> withDefer;\n//\n//   // Full-featured (combine any specs)\n//   StateMachine<\n//       TerminalStates<Closed, Errored>,\n//       ErrorState<Errored>,\n//       ActiveState<Active>,\n//       PendingStates<Closed, Errored>,\n//       Active, Closed, Errored\n//   > fullyFeatured;\n//\n// Available spec types:\n//   - TerminalStates<Ts...>  - States that cannot be transitioned FROM\n//                              Enables: isTerminal()\n//   - ErrorState<T>          - Designates the error state type\n//                              Enables: isErrored(), tryGetErrorUnsafe(), getErrorUnsafe()\n//   - ActiveState<T>         - Designates the active/working state type\n//                              Enables: isActive(), isInactive(), whenActive(), whenActiveOr(),\n//                                       tryGetActiveUnsafe(), requireActiveUnsafe()\n//   - PendingStates<Ts...>   - States that can be deferred during operations\n//                              Enables: beginOperation(), endOperation(), deferTransitionTo(), etc.\n//\n// NAMING CONVENTIONS:\n//   - isTerminal()  = current state is in TerminalStates (enforces no outgoing transitions)\n//   - isInactive()  = current state is NOT the ActiveState (semantic \"done\" state)\n//\n// =============================================================================\n// MEMORY SAFETY\n// =============================================================================\n//\n// THREAD SAFETY: State machines are NOT thread-safe. All operations on a\n// single state machine instance must be performed from the same thread.\n// If you need concurrent access, use external synchronization.\n//\n// This utility provides protections against common memory safety issues:\n//\n// 1. TRANSITION LOCKING: The state machine can be locked during callbacks to\n//    prevent transitions that would invalidate references:\n//\n//      machine.whenState<Active>([](Active& a) {\n//        // machine.transitionTo<Closed>();  // Would fail - locked!\n//        a.resource->read();  // Safe - Active cannot be destroyed\n//      });\n//\n// 2. TRANSITION LOCK ENFORCEMENT: The machine tracks active transition locks\n//    and throws if a transition is attempted while locks are held.\n//\n// 3. SAFE ACCESS PATTERNS: Prefer whenState() and whenActive() over get()\n//    to ensure references don't outlive their validity.\n//\n// UNSAFE PATTERNS TO AVOID:\n//\n//   // DON'T: Store references from getUnsafe() across transitions\n//   Active& active = machine.getUnsafe<Active>();\n//   machine.transitionTo<Closed>();  // active is now dangling!\n//\n//   // DO: Use whenState() for safe scoped access\n//   machine.whenState<Active>([](Active& a) {\n//     // a is guaranteed valid for the duration of the callback\n//   });\n//\n//   // DON'T: Transition inside a callback (will fail if locked)\n//   machine.whenState<Active>([&](Active& a) {\n//     machine.transitionTo<Closed>();  // Fails!\n//   });\n//\n//   // DO: Return a value and transition after\n//   auto result = machine.whenState<Active>([](Active& a) {\n//     return a.computeSomething();\n//   });\n//   machine.transitionTo<Closed>();\n//\n// =============================================================================\n// QUICK START\n// =============================================================================\n//\n// Define your state types (add NAME for introspection):\n//\n//   struct Readable {\n//     static constexpr kj::StringPtr NAME = \"readable\"_kj;\n//     kj::Own<Source> source;\n//   };\n//   struct Closed { static constexpr kj::StringPtr NAME = \"closed\"_kj; };\n//   struct Errored {\n//     static constexpr kj::StringPtr NAME = \"errored\"_kj;\n//     jsg::Value error;\n//   };\n//\n// Basic state machine with safe access:\n//\n//   StateMachine<Readable, Closed, Errored> state;\n//   state.transitionTo<Readable>(...);\n//\n//   // RECOMMENDED: Use whenState() for safe scoped access\n//   state.whenState<Readable>([](Readable& r) {\n//     r.source->read();  // Safe - transitions blocked during callback\n//   });\n//\n//   // Or with a return value\n//   auto size = state.whenState<Readable>([](Readable& r) {\n//     return r.source->size();\n//   });  // Returns kj::Maybe<size_t>\n//\n// Stream-like state machine (common pattern in workerd):\n//\n//   StateMachine<\n//       TerminalStates<Closed, Errored>,\n//       ErrorState<Errored>,\n//       ActiveState<Readable>,\n//       PendingStates<Closed, Errored>,\n//       Readable, Closed, Errored\n//   > state;\n//\n//   state.transitionTo<Readable>(...);\n//\n//   // Safe access with whenActive()\n//   state.whenActive([](Readable& r) {\n//     r.source->doSomething();  // Transitions blocked\n//   });\n//\n//   // Error checking\n//   if (state.isErrored()) { ... }\n//   KJ_IF_SOME(err, state.tryGetErrorUnsafe()) { ... }\n//\n//   // Deferred transitions during operations\n//   state.beginOperation();\n//   state.deferTransitionTo<Closed>();  // Deferred until operation ends\n//   state.endOperation();               // Now transitions to Closed\n//\n//   // Terminal enforcement\n//   state.transitionTo<Closed>();\n//   state.transitionTo<Readable>(...);  // FAILS - can't leave terminal state\n//\n// =============================================================================\n// MIGRATION GUIDE: From kj::OneOf to StateMachine\n// =============================================================================\n//\n// This section describes how to migrate existing kj::OneOf state management\n// to use these StateMachine utilities.\n//\n// STEP 1: Add NAME constants to state types\n// -----------------------------------------\n// StateMachine provides currentStateName() for debugging. Add NAME to states:\n//\n//   // Before:\n//   struct Closed {};\n//\n//   // After:\n//   struct Closed {\n//     static constexpr kj::StringPtr NAME = \"Closed\"_kj;\n//   };\n//\n// STEP 2: Replace kj::OneOf with appropriate StateMachine\n// --------------------------------------------------------\n//\n//   // Before:\n//   kj::OneOf<Closed, Errored, Readable> state;\n//\n//   // After (basic):\n//   StateMachine<Closed, Errored, Readable> state;\n//\n//   // After (with features):\n//   StateMachine<\n//       TerminalStates<Closed, Errored>,\n//       ErrorState<Errored>,\n//       ActiveState<Readable>,\n//       Closed, Errored, Readable\n//   > state;\n//\n// STEP 3: Update state assignments to use transitionTo()\n// ------------------------------------------------------\n//\n//   // Before:\n//   state = Closed{};\n//   state = Errored{kj::mv(error)};\n//\n//   // After:\n//   state.transitionTo<Closed>();\n//   state.transitionTo<Errored>(kj::mv(error));\n//\n// STEP 4: Update state checks\n// ---------------------------\n//\n//   // Before:\n//   if (state.is<Closed>() || state.is<Errored>()) { ... }\n//   if (state.is<Errored>()) { ... }\n//\n//   // After (with ActiveState<Readable>):\n//   if (state.isInactive()) { ... }  // Not in active state\n//\n//   // After (with ErrorState<Errored>):\n//   if (state.isErrored()) { ... }\n//\n// STEP 5: Replace unsafe get() with safe access patterns\n// ------------------------------------------------------\n//\n//   // Before (unsafe - reference may dangle if callback transitions):\n//   KJ_SWITCH_ONEOF(state) {\n//     KJ_CASE_ONEOF(readable, Readable) {\n//       readable.source->read();  // May be unsafe\n//     }\n//   }\n//\n//   // After (safe - transitions blocked during callback):\n//   state.whenActive([](Readable& r) {\n//     r.source->read();  // Safe\n//   });\n//\n//   // Or for specific state:\n//   state.whenState<Readable>([](Readable& r) {\n//     r.source->read();\n//   });\n//\n// STEP 6: Replace manual deferred-transition bookkeeping\n// ------------------------------------------------------\n// If you have code that tracks pending operations and defers close/error:\n//\n//   // Before:\n//   bool closing = false;\n//   int pendingOps = 0;\n//\n//   void startOp() { pendingOps++; }\n//   void endOp() {\n//     if (--pendingOps == 0 && closing) doClose();\n//   }\n//   void close() {\n//     if (pendingOps > 0) { closing = true; return; }\n//     doClose();\n//   }\n//\n//   // After (with PendingStates<Closed>):\n//   void startOp() { state.beginOperation(); }\n//   void endOp() { state.endOperation(); }  // Auto-applies pending\n//   void close() { state.deferTransitionTo<Closed>(); }\n//\n//   // Or with RAII:\n//   void doWork() {\n//     auto op = state.scopedOperation();\n//     // ... work ...\n//   }  // endOperation() called automatically\n//\n// STEP 7: Update visitForGc\n// -------------------------\n//\n//   // Before:\n//   void visitForGc(jsg::GcVisitor& visitor) {\n//     KJ_SWITCH_ONEOF(state) {\n//       KJ_CASE_ONEOF(e, Errored) { visitor.visit(e.reason); }\n//       // ...\n//     }\n//   }\n//\n//   // After:\n//   void visitForGc(jsg::GcVisitor& visitor) {\n//     state.visitForGc(visitor);  // Visits all GC-able states automatically\n//   }\n//\n// STEP 8: KJ_SWITCH_ONEOF still works\n// -----------------------------------\n// If you need to keep KJ_SWITCH_ONEOF for complex logic:\n//\n//   KJ_SWITCH_ONEOF(state.underlying()) {\n//     KJ_CASE_ONEOF(r, Readable) { ... }\n//     KJ_CASE_ONEOF(c, Closed) { ... }\n//     KJ_CASE_ONEOF(e, Errored) { ... }\n//   }\n//\n// Or use the visitor pattern:\n//\n//   state.visit([](auto& s) {\n//     using S = kj::Decay<decltype(s)>;\n//     if constexpr (kj::isSameType<S, Readable>()) { ... }\n//     else if constexpr (kj::isSameType<S, Closed>()) { ... }\n//     else { ... }\n//   });\n//\n// =============================================================================\n\n#include <kj/common.h>\n#include <kj/debug.h>\n#include <kj/one-of.h>\n#include <kj/string.h>\n\n#include <concepts>\n#include <tuple>\n#include <type_traits>\n#include <utility>\n\nnamespace workerd {\n\n// =============================================================================\n// Type Traits and Helpers\n// =============================================================================\n\nnamespace _ {  // private\n\n// Helper to check if a type is in a parameter pack\ntemplate <typename T, typename... Ts>\ninline constexpr bool isOneOf = false;\n\ntemplate <typename T, typename First, typename... Rest>\ninline constexpr bool isOneOf<T, First, Rest...> =\n    kj::isSameType<T, First>() || isOneOf<T, Rest...>;\n\n// Concept: type has a static NAME member of type kj::StringPtr\ntemplate <typename T>\nconcept HasStateName = requires {\n  { T::NAME } -> std::convertible_to<kj::StringPtr>;\n};\n\n// Get state name, using NAME if available, otherwise a placeholder\ntemplate <typename T>\nconstexpr kj::StringPtr getStateName() {\n  if constexpr (HasStateName<T>) {\n    return T::NAME;\n  } else {\n    return \"(unnamed)\"_kj;\n  }\n}\n\n}  // namespace _\n\n// =============================================================================\n// Spec Types for Composable Features\n// =============================================================================\n\n// Marker type to specify terminal states (cannot transition FROM these)\ntemplate <typename... Ts>\nstruct TerminalStates {\n  template <typename T>\n  static constexpr bool contains = _::isOneOf<T, Ts...>;\n\n  template <typename Machine>\n  static bool isTerminal(const Machine& machine) {\n    return (machine.template is<Ts>() || ...);\n  }\n};\n\n// Marker type to specify the error state (enables isErrored(), tryGetErrorUnsafe(), etc.)\n// Note: Error states are implicitly terminal - you cannot transition out of an error state\n// using normal transitions. Use forceTransitionTo() if you need to reset from an error.\ntemplate <typename T>\nstruct ErrorState {\n  using Type = T;\n};\n\n// Marker type to specify the active state (enables isActive(), whenActive(), etc.)\ntemplate <typename T>\nstruct ActiveState {\n  using Type = T;\n};\n\n// Marker type to specify which states can be pending/deferred\ntemplate <typename... Ts>\nstruct PendingStates {\n  template <typename T>\n  static constexpr bool contains = _::isOneOf<T, Ts...>;\n};\n\n// =============================================================================\n// Spec Detection Traits\n// =============================================================================\n\nnamespace _ {  // private\n\n// Helper to detect template instantiations\ntemplate <typename T, template <typename...> class Template>\ninline constexpr bool isInstanceOf = false;\n\ntemplate <template <typename...> class Template, typename... Args>\ninline constexpr bool isInstanceOf<Template<Args...>, Template> = true;\n\n// Spec detection using template matching\ntemplate <typename T>\ninline constexpr bool isTerminalStatesSpec = isInstanceOf<T, TerminalStates>;\n\ntemplate <typename T>\ninline constexpr bool isErrorStateSpec = isInstanceOf<T, ErrorState>;\n\ntemplate <typename T>\ninline constexpr bool isActiveStateSpec = isInstanceOf<T, ActiveState>;\n\ntemplate <typename T>\ninline constexpr bool isPendingStatesSpec = isInstanceOf<T, PendingStates>;\n\n// Check if a type is any spec type\ntemplate <typename T>\ninline constexpr bool isSpec = isTerminalStatesSpec<T> || isErrorStateSpec<T> ||\n    isActiveStateSpec<T> || isPendingStatesSpec<T>;\n\n// Filter out specs from a type list, keeping only actual states\ntemplate <typename... Ts>\nstruct FilterStates_;\n\ntemplate <>\nstruct FilterStates_<> {\n  using Type = std::tuple<>;\n};\n\ntemplate <typename First, typename... Rest>\nstruct FilterStates_<First, Rest...> {\n  using RestFiltered = FilterStates_<Rest...>::Type;\n  using Type = std::conditional_t<isSpec<First>,\n      RestFiltered,\n      decltype(std::tuple_cat(kj::instance<std::tuple<First>>(), kj::instance<RestFiltered>()))>;\n};\n\ntemplate <typename... Ts>\nusing FilterStates = FilterStates_<Ts...>::Type;\n\n// Convert tuple to kj::OneOf\ntemplate <typename Tuple>\nstruct TupleToOneOf_;\n\ntemplate <typename... Ts>\nstruct TupleToOneOf_<std::tuple<Ts...>> {\n  using Type = kj::OneOf<Ts...>;\n};\n\ntemplate <typename Tuple>\nusing TupleToOneOf = TupleToOneOf_<Tuple>::Type;\n\n// Generic spec finder - finds the first type matching a predicate\ntemplate <template <typename> class Pred, typename... Ts>\nstruct FindSpecWhere {\n  using Type = void;  // Not found\n};\n\ntemplate <template <typename> class Pred, typename First, typename... Rest>\nstruct FindSpecWhere<Pred, First, Rest...> {\n  using Type =\n      std::conditional_t<Pred<First>::value, First, typename FindSpecWhere<Pred, Rest...>::Type>;\n};\n\n// Predicate wrappers for each spec type\ntemplate <typename T>\nstruct IsErrorStateSpec {\n  static constexpr bool value = isErrorStateSpec<T>;\n};\ntemplate <typename T>\nstruct IsActiveStateSpec {\n  static constexpr bool value = isActiveStateSpec<T>;\n};\ntemplate <typename T>\nstruct IsTerminalStatesSpec {\n  static constexpr bool value = isTerminalStatesSpec<T>;\n};\ntemplate <typename T>\nstruct IsPendingStatesSpec {\n  static constexpr bool value = isPendingStatesSpec<T>;\n};\n\n// Convenient aliases for finding each spec type\ntemplate <typename... Ts>\nusing FindErrorStateSpec = FindSpecWhere<IsErrorStateSpec, Ts...>;\ntemplate <typename... Ts>\nusing FindActiveStateSpec = FindSpecWhere<IsActiveStateSpec, Ts...>;\ntemplate <typename... Ts>\nusing FindTerminalStatesSpec = FindSpecWhere<IsTerminalStatesSpec, Ts...>;\ntemplate <typename... Ts>\nusing FindPendingStatesSpec = FindSpecWhere<IsPendingStatesSpec, Ts...>;\n\n// Check if a type is in a tuple (type list)\ntemplate <typename T, typename Tuple>\ninline constexpr bool isInTuple = false;\n\ntemplate <typename T, typename... Ts>\ninline constexpr bool isInTuple<T, std::tuple<Ts...>> = (kj::isSameType<T, Ts>() || ...);\n\n// Placeholder type used when a feature is disabled\n// This is needed because C++ doesn't allow references to void\nstruct PlaceholderType {};\n\n// Empty struct for [[no_unique_address]] optimization\n// Unlike char, this can actually be zero-sized when used with [[no_unique_address]]\nstruct Empty {};\n\n// Helper to extract ::Type from a spec, or PlaceholderType if spec is void\ntemplate <typename Spec>\nstruct ExtractSpecType_ {\n  using Type = Spec::Type;\n};\n\ntemplate <>\nstruct ExtractSpecType_<void> {\n  using Type = PlaceholderType;\n};\n\ntemplate <typename Spec>\nusing ExtractSpecType = ExtractSpecType_<Spec>::Type;\n\n// Generic spec counter using fold expression\ntemplate <template <typename> class Pred, typename... Ts>\ninline constexpr size_t countSpecsWhere = ((Pred<Ts>::value ? 1 : 0) + ... + 0);\n\n// Convenient aliases for counting each spec type\ntemplate <typename... Ts>\ninline constexpr size_t countErrorStateSpecs = countSpecsWhere<IsErrorStateSpec, Ts...>;\ntemplate <typename... Ts>\ninline constexpr size_t countActiveStateSpecs = countSpecsWhere<IsActiveStateSpec, Ts...>;\ntemplate <typename... Ts>\ninline constexpr size_t countTerminalStatesSpecs = countSpecsWhere<IsTerminalStatesSpec, Ts...>;\ntemplate <typename... Ts>\ninline constexpr size_t countPendingStatesSpecs = countSpecsWhere<IsPendingStatesSpec, Ts...>;\n\n// Validate that all types in a TerminalStates spec are actual state types\ntemplate <typename StatesTuple, typename... TerminalTs>\nstruct ValidateTerminalStates {\n  static constexpr bool allValid = (isInTuple<TerminalTs, StatesTuple> && ...);\n  static_assert(allValid || sizeof...(TerminalTs) == 0,\n      \"All types in TerminalStates<...> must be actual state types in the state machine\");\n};\n\n// Validate that all types in a PendingStates spec are actual state types\ntemplate <typename StatesTuple, typename... PendingTs>\nstruct ValidatePendingStates {\n  static constexpr bool allValid = (isInTuple<PendingTs, StatesTuple> && ...);\n  static_assert(allValid || sizeof...(PendingTs) == 0,\n      \"All types in PendingStates<...> must be actual state types in the state machine\");\n};\n\n// Helper to extract types from TerminalStates for validation\ntemplate <typename StatesTuple, typename TerminalSpec>\nstruct ValidateTerminalSpec {\n  static constexpr bool valid = true;  // Default: no terminal spec, nothing to validate\n};\n\ntemplate <typename StatesTuple, typename... Ts>\nstruct ValidateTerminalSpec<StatesTuple, TerminalStates<Ts...>> {\n  static constexpr bool valid = ValidateTerminalStates<StatesTuple, Ts...>::allValid;\n};\n\n// Helper to extract types from PendingStates for validation\ntemplate <typename StatesTuple, typename PendingSpec>\nstruct ValidatePendingSpec {\n  static constexpr bool valid = true;  // Default: no pending spec, nothing to validate\n};\n\ntemplate <typename StatesTuple, typename... Ts>\nstruct ValidatePendingSpec<StatesTuple, PendingStates<Ts...>> {\n  static constexpr bool valid = ValidatePendingStates<StatesTuple, Ts...>::allValid;\n};\n\n}  // namespace _\n\n// =============================================================================\n// Transition Lock\n// =============================================================================\n\n// RAII guard that prevents state transitions while in scope.\n// This is used to ensure references to state data remain valid.\n//\n// LIFETIME REQUIREMENTS:\n// The TransitionLock holds a reference to the state machine. The state machine\n// MUST outlive the TransitionLock. Destroying the state machine while a\n// TransitionLock exists will result in undefined behavior (use-after-free).\n//\n// CORRECT USAGE:\n//   {\n//     auto lock = machine.acquireTransitionLock();\n//     // ... use state data safely ...\n//   }  // lock destroyed, then machine can be safely destroyed\n//\n// INCORRECT USAGE:\n//   auto lock = machine.acquireTransitionLock();\n//   machine = StateMachine{};  // BUG: lock still holds reference to old machine!\n//\n// TODO(someday): Consider adding tryGet<S>() and get<S>() accessor methods to provide\n// safe state access while locked. This would enable patterns like:\n//\n//   auto lock = state.acquireTransitionLock();\n//   KJ_IF_SOME(open, lock.tryGet<Open>()) { ... }\n//\n// Could also explore a WD_IF_STATE macro for KJ_IF_SOME-style ergonomics. If we add\n// accessors here, we may want to support deferred transitions (queued until lock\n// release), but this raises design questions about conditional transitions.\n//\n// The relationship between TransitionLock (for safe state access) and OperationScope\n// (for pending operation tracking with deferred transitions) also needs clarification.\ntemplate <typename Machine>\nclass TransitionLock {\n public:\n  explicit TransitionLock(Machine& m): machine(m) {\n    machine.lockTransitions();\n  }\n\n  ~TransitionLock() {\n    machine.unlockTransitions();\n  }\n\n  KJ_DISALLOW_COPY_AND_MOVE(TransitionLock);\n\n private:\n  Machine& machine;\n};\n\n// =============================================================================\n// State Name Trait\n// =============================================================================\n\n// Add NAME to your state types for introspection support:\n//\n//   struct Closed {\n//     static constexpr kj::StringPtr NAME = \"closed\"_kj;\n//   };\n//\n//   struct Errored {\n//     jsg::Value error;\n//     static constexpr kj::StringPtr NAME = \"errored\"_kj;\n//   };\n\n// Forward declaration\ntemplate <typename... Args>\nclass StateMachine;\n\n// =============================================================================\n// State Machine\n// =============================================================================\n\n// A unified state machine that supports all features via spec types.\n// Features are conditionally enabled based on which specs are provided.\n//\n// Usage:\n//   // Simple (no specs)\n//   StateMachine<Idle, Running, Done> simple;\n//\n//   // With terminal states\n//   StateMachine<TerminalStates<Done>, Idle, Running, Done> withTerminal;\n//\n//   // Full-featured (stream pattern)\n//   StateMachine<\n//       TerminalStates<Closed, Errored>,\n//       ErrorState<Errored>,\n//       ActiveState<Readable>,\n//       PendingStates<Closed, Errored>,\n//       Readable, Closed, Errored\n//   > stream;\n//\n// All features from separate classes are available when their spec is provided:\n//   - TerminalStates<...> -> isTerminal(), enforces no transitions from terminal\n//   - ErrorState<T> -> isErrored(), tryGetErrorUnsafe(), getErrorUnsafe()\n//   - ActiveState<T> -> isActive(), isInactive(), whenActive(), tryGetActiveUnsafe()\n//   - PendingStates<...> -> beginOperation(), endOperation(), deferTransitionTo(), etc.\n\ntemplate <typename... Args>\nclass StateMachine {\n public:\n  // Extract specs from Args\n  using TerminalSpec = _::FindTerminalStatesSpec<Args...>::Type;\n  using ErrorSpec = _::FindErrorStateSpec<Args...>::Type;\n  using ActiveSpec = _::FindActiveStateSpec<Args...>::Type;\n  using PendingSpec = _::FindPendingStatesSpec<Args...>::Type;\n\n  // Filter out specs to get actual states\n  using StatesTuple = _::FilterStates<Args...>;\n  using StateUnion = _::TupleToOneOf<StatesTuple>;\n  static constexpr size_t STATE_COUNT = std::tuple_size_v<StatesTuple>;\n\n  // Feature detection\n  static constexpr bool HAS_TERMINAL = !std::is_void_v<TerminalSpec>;\n  static constexpr bool HAS_ERROR = !std::is_void_v<ErrorSpec>;\n  static constexpr bool HAS_ACTIVE = !std::is_void_v<ActiveSpec>;\n  static constexpr bool HAS_PENDING = !std::is_void_v<PendingSpec>;\n\n  // Get the error state type (PlaceholderType if not specified)\n  // Uses helper to avoid accessing ::Type on void\n  using ErrorStateType = _::ExtractSpecType<ErrorSpec>;\n  using ActiveStateType = _::ExtractSpecType<ActiveSpec>;\n\n private:\n  // ==========================================================================\n  // Compile-time validation\n  // ==========================================================================\n\n  // Detect duplicate specs\n  static_assert(_::countTerminalStatesSpecs<Args...> <= 1,\n      \"Multiple TerminalStates<...> specs provided. Only one is allowed.\");\n  static_assert(_::countErrorStateSpecs<Args...> <= 1,\n      \"Multiple ErrorState<...> specs provided. Only one is allowed.\");\n  static_assert(_::countActiveStateSpecs<Args...> <= 1,\n      \"Multiple ActiveState<...> specs provided. Only one is allowed.\");\n  static_assert(_::countPendingStatesSpecs<Args...> <= 1,\n      \"Multiple PendingStates<...> specs provided. Only one is allowed.\");\n\n  // Validate that spec types reference actual states\n  static consteval bool validateErrorSpec() {\n    if constexpr (HAS_ERROR) {\n      static_assert(_::isInTuple<ErrorStateType, StatesTuple>,\n          \"ErrorState<T> must reference a type that is one of the state machine's states\");\n    }\n    return true;\n  }\n\n  static consteval bool validateActiveSpec() {\n    if constexpr (HAS_ACTIVE) {\n      static_assert(_::isInTuple<ActiveStateType, StatesTuple>,\n          \"ActiveState<T> must reference a type that is one of the state machine's states\");\n    }\n    return true;\n  }\n\n  static consteval bool validateTerminalSpec() {\n    if constexpr (HAS_TERMINAL) {\n      static_assert(_::ValidateTerminalSpec<StatesTuple, TerminalSpec>::valid,\n          \"All types in TerminalStates<...> must be actual state types\");\n    }\n    return true;\n  }\n\n  static consteval bool validatePendingSpec() {\n    if constexpr (HAS_PENDING) {\n      static_assert(_::ValidatePendingSpec<StatesTuple, PendingSpec>::valid,\n          \"All types in PendingStates<...> must be actual state types\");\n    }\n    return true;\n  }\n\n  // Force validation at class instantiation time\n  static_assert(validateErrorSpec(), \"ErrorState validation failed\");\n  static_assert(validateActiveSpec(), \"ActiveState validation failed\");\n  static_assert(validateTerminalSpec(), \"TerminalStates validation failed\");\n  static_assert(validatePendingSpec(), \"PendingStates validation failed\");\n\n public:\n  // ==========================================================================\n  // Constructors and assignment\n  // ==========================================================================\n\n  // Default constructor is private - use StateMachine::create<State>(...) instead.\n  // This ensures all state machines are properly initialized.\n\n  // Destructor checks for outstanding locks\n  ~StateMachine() {\n    KJ_DASSERT(transitionLockCount == 0, \"StateMachine destroyed while transition locks are held\");\n  }\n\n  // Move operations - both source and destination must not have locks held\n  StateMachine(StateMachine&& other) noexcept: state(kj::mv(other.state)), transitionLockCount(0) {\n    KJ_DASSERT(other.transitionLockCount == 0,\n        \"Cannot move from StateMachine while transition locks are held\");\n    if constexpr (HAS_PENDING) {\n      operationCount = other.operationCount;\n      pendingState = kj::mv(other.pendingState);\n      other.operationCount = 0;\n    }\n  }\n\n  StateMachine& operator=(StateMachine&& other) noexcept {\n    KJ_DASSERT(transitionLockCount == 0,\n        \"Cannot move-assign to StateMachine while transition locks are held\");\n    KJ_DASSERT(other.transitionLockCount == 0,\n        \"Cannot move from StateMachine while transition locks are held\");\n    state = kj::mv(other.state);\n    if constexpr (HAS_PENDING) {\n      operationCount = other.operationCount;\n      pendingState = kj::mv(other.pendingState);\n      other.operationCount = 0;\n    }\n    return *this;\n  }\n\n  // State machines are generally not copyable - they're owned by classes\n  // that typically aren't copyable either (e.g., stream controllers).\n  KJ_DISALLOW_COPY(StateMachine);\n\n  // Factory function for clearer initialization\n  template <typename S, typename... TArgs>\n  static StateMachine create(TArgs&&... args)\n    requires(_::isInTuple<S, StatesTuple>)\n  {\n    StateMachine m;\n    m.state.template init<S>(kj::fwd<TArgs>(args)...);\n    return m;\n  }\n\n  // ---------------------------------------------------------------------------\n  // Core State Queries (always available)\n  // ---------------------------------------------------------------------------\n\n  template <typename S>\n  bool is() const\n    requires(_::isInTuple<S, StatesTuple>)\n  {\n    return state.template is<S>();\n  }\n\n  template <typename... Ss>\n  bool isAnyOf() const\n    requires((_::isInTuple<Ss, StatesTuple>) && ...)\n  {\n    return (is<Ss>() || ...);\n  }\n\n  // Check if the machine is initialized (not in the null state).\n  // Call transitionTo<>() to initialize the state machine.\n  bool isInitialized() const {\n    return !(state == nullptr);\n  }\n\n  // Assert that the machine is initialized, with a clear error message.\n  void requireInitialized() const {\n    KJ_REQUIRE(isInitialized(),\n        \"State machine used before initialization. Call transitionTo<InitialState>() first.\");\n  }\n\n  // ---------------------------------------------------------------------------\n  // Core State Access (always available)\n  // ---------------------------------------------------------------------------\n  //\n  // NAMING CONVENTION: Methods with \"Unsafe\" suffix return raw references to\n  // state data without any protection against use-after-free. These references\n  // can dangle if a state transition occurs while the reference is held.\n  //\n  // The \"Unsafe\" suffix serves as a visual warning at every call site,\n  // encouraging developers to:\n  //   1. Use safe alternatives (whenState(), whenActive()) when possible\n  //   2. Carefully audit code paths that could trigger transitions\n  //   3. Keep the reference's lifetime as short as possible\n  //\n  // Safe alternatives:\n  //   - whenState<S>(callback)  - Locks transitions during callback\n  //   - whenActive(callback)    - Locks transitions, only runs if active\n  //   - acquireTransitionLock() - RAII lock for manual control\n\n  template <typename S>\n  S& getUnsafe() KJ_LIFETIMEBOUND\n    requires(_::isInTuple<S, StatesTuple>)\n  {\n    requireInitialized();\n    KJ_REQUIRE(is<S>(), \"State machine is not in the expected state\");\n    return state.template get<S>();\n  }\n\n  template <typename S>\n  const S& getUnsafe() const KJ_LIFETIMEBOUND\n    requires(_::isInTuple<S, StatesTuple>)\n  {\n    requireInitialized();\n    KJ_REQUIRE(is<S>(), \"State machine is not in the expected state\");\n    return state.template get<S>();\n  }\n\n  template <typename S>\n  kj::Maybe<S&> tryGetUnsafe() KJ_LIFETIMEBOUND\n    requires(_::isInTuple<S, StatesTuple>)\n  {\n    return state.template tryGet<S>();\n  }\n\n  template <typename S>\n  kj::Maybe<const S&> tryGetUnsafe() const KJ_LIFETIMEBOUND\n    requires(_::isInTuple<S, StatesTuple>)\n  {\n    return state.template tryGet<S>();\n  }\n\n  // ---------------------------------------------------------------------------\n  // Transition Locking (always available)\n  // ---------------------------------------------------------------------------\n\n  bool isTransitionLocked() const {\n    return transitionLockCount > 0;\n  }\n\n  void lockTransitions() {\n    ++transitionLockCount;\n  }\n\n  void unlockTransitions() {\n    KJ_DASSERT(transitionLockCount > 0, \"Transition lock underflow\");\n    --transitionLockCount;\n  }\n\n  TransitionLock<StateMachine> acquireTransitionLock() {\n    return TransitionLock<StateMachine>(*this);\n  }\n\n  // ---------------------------------------------------------------------------\n  // Safe State Access with Locking (always available)\n  // ---------------------------------------------------------------------------\n\n  // Execute a function with the current state, locking transitions.\n  // This is the SAFEST way to access state data as it prevents\n  // use-after-free by blocking transitions during the callback.\n  //\n  // Returns the function's result wrapped in Maybe (none if not in state).\n  // For void functions, returns true if executed, false if not in state.\n  template <typename S, typename Func>\n  auto whenState(\n      Func&& func) -> std::conditional_t<std::is_void_v<decltype(func(kj::instance<S&>()))>,\n                       bool,\n                       kj::Maybe<decltype(func(kj::instance<S&>()))>>\n    requires(_::isInTuple<S, StatesTuple>)\n  {\n    if (!is<S>()) {\n      if constexpr (std::is_void_v<decltype(func(kj::instance<S&>()))>) {\n        return false;\n      } else {\n        return kj::none;\n      }\n    }\n\n    auto lock = acquireTransitionLock();\n    if constexpr (std::is_void_v<decltype(func(kj::instance<S&>()))>) {\n      func(state.template get<S>());\n      return true;\n    } else {\n      return func(state.template get<S>());\n    }\n  }\n\n  // Const version for read-only access\n  template <typename S, typename Func>\n  auto whenState(Func&& func) const\n      -> std::conditional_t<std::is_void_v<decltype(func(kj::instance<const S&>()))>,\n          bool,\n          kj::Maybe<decltype(func(kj::instance<const S&>()))>>\n    requires(_::isInTuple<S, StatesTuple>)\n  {\n    if (!is<S>()) {\n      if constexpr (std::is_void_v<decltype(func(kj::instance<const S&>()))>) {\n        return false;\n      } else {\n        return kj::none;\n      }\n    }\n\n    // Note: We still acquire the lock for consistency, even though const\n    // methods shouldn't transition. This catches bugs where someone\n    // tries to transition through a captured non-const reference.\n    ++transitionLockCount;\n    KJ_DEFER(--transitionLockCount);\n    if constexpr (std::is_void_v<decltype(func(kj::instance<const S&>()))>) {\n      func(state.template get<S>());\n      return true;\n    } else {\n      return func(state.template get<S>());\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n  // Visitor Pattern (always available)\n  // ---------------------------------------------------------------------------\n\n  // Visit the current state with a generic lambda.\n  // The lambda must be able to accept any state type.\n  //\n  // Usage:\n  //   state.visit([](auto& s) {\n  //     // s is a reference to the current state\n  //   });\n  //\n  // Or with explicit type handling:\n  //   state.visit([](auto& s) {\n  //     using S = kj::Decay<decltype(s)>;\n  //     if constexpr (kj::isSameType<S, Readable>()) { ... }\n  //   });\n  template <typename Visitor>\n  decltype(auto) visit(Visitor&& visitor) {\n    return visitImpl(kj::fwd<Visitor>(visitor), std::make_index_sequence<STATE_COUNT>{});\n  }\n\n  template <typename Visitor>\n  decltype(auto) visit(Visitor&& visitor) const {\n    return visitConstImpl(kj::fwd<Visitor>(visitor), std::make_index_sequence<STATE_COUNT>{});\n  }\n\n  // ---------------------------------------------------------------------------\n  // State Transitions (always available, but terminal-aware if spec provided)\n  // ---------------------------------------------------------------------------\n\n  template <typename S, typename... TArgs>\n  S& transitionTo(TArgs&&... args) KJ_LIFETIMEBOUND\n    requires(_::isInTuple<S, StatesTuple>)\n  {\n    requireUnlocked();\n    if constexpr (HAS_TERMINAL || HAS_ERROR) {\n      KJ_REQUIRE(!isTerminal(), \"Cannot transition from terminal state\");\n    }\n    if constexpr (HAS_PENDING) {\n      clearPendingState();\n    }\n    return state.template init<S>(kj::fwd<TArgs>(args)...);\n  }\n\n  // Force transition bypassing terminal state protection.\n  //\n  // WARNING: This bypasses terminal state protection! Use sparingly and only\n  // for legitimate cleanup/reset scenarios. If you find yourself using this\n  // frequently, reconsider whether your state should actually be terminal.\n  //\n  // Legitimate uses:\n  //   - Resetting a state machine for reuse\n  //   - Cleanup during destruction\n  //   - Test fixtures\n  //\n  // Suspicious uses (reconsider your design):\n  //   - Regular business logic transitions\n  //   - \"Retry\" or \"restart\" operations\n  template <typename S, typename... TArgs>\n  S& forceTransitionTo(TArgs&&... args) KJ_LIFETIMEBOUND\n    requires(_::isInTuple<S, StatesTuple>)\n  {\n    requireUnlocked();\n    if constexpr (HAS_PENDING) {\n      clearPendingState();\n    }\n    return state.template init<S>(kj::fwd<TArgs>(args)...);\n  }\n\n  // Conditionally transition from one state to another.\n  // If the current state is From, transitions to To and returns a reference to the new state.\n  // If the current state is NOT From, does nothing and returns kj::none.\n  // This is useful for atomic \"check and transition\" operations.\n  template <typename From, typename To, typename... TArgs>\n  KJ_WARN_UNUSED_RESULT kj::Maybe<To&> transitionFromTo(TArgs&&... args) KJ_LIFETIMEBOUND\n    requires(_::isInTuple<From, StatesTuple>) && (_::isInTuple<To, StatesTuple>)\n  {\n    requireUnlocked();\n    if (!is<From>()) {\n      return kj::none;\n    }\n    if constexpr (HAS_TERMINAL || HAS_ERROR) {\n      KJ_REQUIRE(!isTerminal(), \"Cannot transition from terminal state\");\n    }\n    if constexpr (HAS_PENDING) {\n      clearPendingState();\n    }\n    return state.template init<To>(kj::fwd<TArgs>(args)...);\n  }\n\n  // ---------------------------------------------------------------------------\n  // State Introspection (always available)\n  // ---------------------------------------------------------------------------\n\n  kj::StringPtr currentStateName() const {\n    kj::StringPtr result = \"(uninitialized)\"_kj;\n    visitStateNames([&result]<typename S>(const S&) { result = _::getStateName<S>(); });\n    return result;\n  }\n\n  // ---------------------------------------------------------------------------\n  // Terminal State Features (enabled when TerminalStates<...> or ErrorState<T> is provided)\n  // ---------------------------------------------------------------------------\n\n  // Check if currently in a terminal state (no further transitions allowed).\n  // Note: Error states are implicitly terminal - you cannot transition out of an error state.\n  bool isTerminal() const\n    requires(HAS_TERMINAL || HAS_ERROR)\n  {\n    bool terminal = false;\n    if constexpr (HAS_TERMINAL) {\n      terminal = TerminalSpec::isTerminal(*this);\n    }\n    if constexpr (HAS_ERROR) {\n      terminal = terminal || is<ErrorStateType>();\n    }\n    return terminal;\n  }\n\n  // ---------------------------------------------------------------------------\n  // Error State Features (enabled when ErrorState<T> is provided)\n  // ---------------------------------------------------------------------------\n\n  // Check if currently in the error state.\n  bool isErrored() const\n    requires(HAS_ERROR)\n  {\n    return is<ErrorStateType>();\n  }\n\n  // Get the error state if currently errored.\n  //\n  // WARNING: Returns an UNLOCKED reference - can dangle if the machine transitions.\n  kj::Maybe<ErrorStateType&> tryGetErrorUnsafe() KJ_LIFETIMEBOUND\n    requires(HAS_ERROR)\n  {\n    return tryGetUnsafe<ErrorStateType>();\n  }\n\n  kj::Maybe<const ErrorStateType&> tryGetErrorUnsafe() const KJ_LIFETIMEBOUND\n    requires(HAS_ERROR)\n  {\n    return tryGetUnsafe<ErrorStateType>();\n  }\n\n  // Get the error state, asserting we are errored.\n  //\n  // WARNING: Returns an UNLOCKED reference - can dangle if the machine transitions.\n  ErrorStateType& getErrorUnsafe() KJ_LIFETIMEBOUND\n    requires(HAS_ERROR)\n  {\n    return getUnsafe<ErrorStateType>();\n  }\n\n  const ErrorStateType& getErrorUnsafe() const KJ_LIFETIMEBOUND\n    requires(HAS_ERROR)\n  {\n    return getUnsafe<ErrorStateType>();\n  }\n\n  // ---------------------------------------------------------------------------\n  // Active State Features (enabled when ActiveState<T> is provided)\n  // ---------------------------------------------------------------------------\n\n  // Check if currently in the active state.\n  bool isActive() const\n    requires(HAS_ACTIVE)\n  {\n    return is<ActiveStateType>();\n  }\n\n  // Returns true if not in the active state (i.e., closed, errored, or any non-active state).\n  // Note: This is different from isTerminal() which checks if transitions are blocked.\n  bool isInactive() const\n    requires(HAS_ACTIVE)\n  {\n    return !isActive();\n  }\n\n  // Get the active state if currently active.\n  //\n  // WARNING: Returns an UNLOCKED reference - can dangle if the machine transitions.\n  // Prefer whenActive() for safe access with locked transitions.\n  kj::Maybe<ActiveStateType&> tryGetActiveUnsafe() KJ_LIFETIMEBOUND\n    requires(HAS_ACTIVE)\n  {\n    return tryGetUnsafe<ActiveStateType>();\n  }\n\n  kj::Maybe<const ActiveStateType&> tryGetActiveUnsafe() const KJ_LIFETIMEBOUND\n    requires(HAS_ACTIVE)\n  {\n    return tryGetUnsafe<ActiveStateType>();\n  }\n\n  // Get the active state, throwing KJ_REQUIRE if not active.\n  //\n  // WARNING: Returns an UNLOCKED reference - can dangle if the machine transitions.\n  ActiveStateType& requireActiveUnsafe(kj::StringPtr message = nullptr) KJ_LIFETIMEBOUND\n    requires(HAS_ACTIVE)\n  {\n    if (message == nullptr) {\n      message = \"State machine is not in the active state\"_kj;\n    }\n    KJ_REQUIRE(isActive(), message);\n    return state.template get<ActiveStateType>();\n  }\n\n  const ActiveStateType& requireActiveUnsafe(kj::StringPtr message = nullptr) const KJ_LIFETIMEBOUND\n    requires(HAS_ACTIVE)\n  {\n    if (message == nullptr) {\n      message = \"State machine is not in the active state\"_kj;\n    }\n    KJ_REQUIRE(isActive(), message);\n    return state.template get<ActiveStateType>();\n  }\n\n  // Execute a function only if in the active state.\n  // LOCKS TRANSITIONS during callback execution to prevent use-after-free.\n  // Returns the function's result wrapped in Maybe, or none if not active.\n  // For void functions, returns true if executed, false if not active.\n  template <typename Func>\n  auto whenActive(Func&& func)\n      -> std::conditional_t<std::is_void_v<decltype(func(kj::instance<ActiveStateType&>()))>,\n          bool,\n          kj::Maybe<decltype(func(kj::instance<ActiveStateType&>()))>>\n    requires(HAS_ACTIVE)\n  {\n    return whenState<ActiveStateType>(kj::fwd<Func>(func));\n  }\n\n  template <typename Func>\n  auto whenActive(Func&& func) const\n      -> std::conditional_t<std::is_void_v<decltype(func(kj::instance<const ActiveStateType&>()))>,\n          bool,\n          kj::Maybe<decltype(func(kj::instance<const ActiveStateType&>()))>>\n    requires(HAS_ACTIVE)\n  {\n    return whenState<ActiveStateType>(kj::fwd<Func>(func));\n  }\n\n  // Execute a function if active, or return a default value.\n  // LOCKS TRANSITIONS during callback execution.\n  template <typename Func, typename Default>\n  auto whenActiveOr(\n      Func&& func, Default&& defaultValue) -> decltype(func(kj::instance<ActiveStateType&>()))\n    requires(HAS_ACTIVE)\n  {\n    if (!isActive()) {\n      return kj::fwd<Default>(defaultValue);\n    }\n    auto lock = acquireTransitionLock();\n    return func(state.template get<ActiveStateType>());\n  }\n\n  template <typename Func, typename Default>\n  auto whenActiveOr(Func&& func,\n      Default&& defaultValue) const -> decltype(func(kj::instance<const ActiveStateType&>()))\n    requires(HAS_ACTIVE)\n  {\n    if (!isActive()) {\n      return kj::fwd<Default>(defaultValue);\n    }\n    ++transitionLockCount;\n    KJ_DEFER(--transitionLockCount);\n    return func(state.template get<ActiveStateType>());\n  }\n\n  // ---------------------------------------------------------------------------\n  // Pending State Features (enabled when PendingStates<...> is provided)\n  // ---------------------------------------------------------------------------\n  //\n  // RECOMMENDATION: Prefer scopedOperation() RAII guard over manual\n  // beginOperation()/endOperation() calls. Manual calls are error-prone:\n  //\n  //   void badExample() {\n  //     machine.beginOperation();\n  //     if (condition) return;  // BUG: leaks operation count!\n  //     machine.endOperation();\n  //   }\n  //\n  //   void goodExample() {\n  //     auto op = machine.scopedOperation();\n  //     if (condition) return;  // OK: destructor calls endOperation()\n  //   }\n  //\n  //   void exampleWithEarlyEnd() {\n  //     auto op = machine.scopedOperation();\n  //     // ... do work ...\n  //     if (op.end()) {  // End early and check if transition occurred\n  //       // A pending state was applied\n  //     }\n  //   }  // destructor is now a no-op\n  //\n  // Manual beginOperation()/endOperation() may still be appropriate when:\n  //   - You need different exception handling (e.g., clearPendingState() before endOperation())\n  //   - You need to conditionally execute callbacks after the pending state is applied\n\n  // Mark that an operation is starting. While operations are in progress,\n  // certain transitions (via deferTransitionTo) will be deferred rather than\n  // applied immediately. Prefer scopedOperation() for automatic cleanup.\n  void beginOperation()\n    requires(HAS_PENDING)\n  {\n    ++operationCount;\n  }\n\n  // Mark that an operation has completed. If no more operations are pending\n  // and there's a deferred state transition, it will be applied.\n  // Returns true if a pending state was applied.\n  // Prefer scopedOperation() for automatic cleanup.\n  KJ_WARN_UNUSED_RESULT bool endOperation()\n    requires(HAS_PENDING)\n  {\n    KJ_REQUIRE(operationCount > 0, \"endOperation() called without matching beginOperation()\");\n    --operationCount;\n\n    if (operationCount == 0 && hasPendingState()) {\n      applyPendingStateImpl();\n      return true;\n    }\n    return false;\n  }\n\n  // Check if any operations are currently in progress.\n  bool hasOperationInProgress() const\n    requires(HAS_PENDING)\n  {\n    return operationCount > 0;\n  }\n\n  // Check if there's a pending state transition waiting to be applied.\n  bool hasPendingState() const\n    requires(HAS_PENDING)\n  {\n    return !(pendingState == nullptr);\n  }\n\n  // Check if a specific state is pending.\n  template <typename S>\n  bool pendingStateIs() const\n    requires(HAS_PENDING) && (PendingSpec::template contains<S>)\n  {\n    return pendingState.template is<S>();\n  }\n\n  // Get the pending state if it matches the specified type.\n  //\n  // WARNING: Returns an UNLOCKED reference - can dangle if the pending state is applied.\n  template <typename S>\n  kj::Maybe<S&> tryGetPendingStateUnsafe() KJ_LIFETIMEBOUND\n    requires(HAS_PENDING) && (PendingSpec::template contains<S>)\n  {\n    return pendingState.template tryGet<S>();\n  }\n\n  template <typename S>\n  kj::Maybe<const S&> tryGetPendingStateUnsafe() const KJ_LIFETIMEBOUND\n    requires(HAS_PENDING) && (PendingSpec::template contains<S>)\n  {\n    return pendingState.template tryGet<S>();\n  }\n\n  // Clear any pending state without applying it.\n  void clearPendingState()\n    requires(HAS_PENDING)\n  {\n    pendingState = StateUnion();\n  }\n\n  // Transition to a pending state. If no operation is in progress, the\n  // transition happens immediately. Otherwise, it's deferred until all\n  // operations complete.\n  //\n  // Returns true if the transition happened immediately, false if deferred.\n  //\n  // IMPORTANT: First-wins semantics! If a pending state is already set, this\n  // call is SILENTLY IGNORED. The first deferred transition wins:\n  //\n  //   machine.beginOperation();\n  //   machine.deferTransitionTo<Closed>();   // This one wins\n  //   machine.deferTransitionTo<Errored>(e); // IGNORED - Closed already pending!\n  //   machine.endOperation();                // Transitions to Closed, not Errored\n  //\n  // If you need error to take precedence over close, you must either:\n  //   1. Use forceTransitionTo<Errored>() which bypasses deferral, or\n  //   2. Check hasPendingState() before deferring, or\n  //   3. Use clearPendingState() first to override\n  template <typename S, typename... TArgs>\n  KJ_WARN_UNUSED_RESULT bool deferTransitionTo(TArgs&&... args)\n    requires(HAS_PENDING) && (PendingSpec::template contains<S>)\n  {\n    requireUnlocked();\n\n    // Check terminal state if applicable (same as transitionTo)\n    if constexpr (HAS_TERMINAL || HAS_ERROR) {\n      KJ_REQUIRE(!isTerminal(), \"Cannot transition from terminal state\");\n    }\n\n    if (operationCount == 0) {\n      // No operation in progress, transition immediately\n      state.template init<S>(kj::fwd<TArgs>(args)...);\n      return true;\n    } else {\n      // Operation in progress, defer the transition (first wins)\n      if (pendingState == nullptr) {\n        pendingState.template init<S>(kj::fwd<TArgs>(args)...);\n      }\n      return false;\n    }\n  }\n\n  // Check if the machine is in state S OR has S pending.\n  // Useful for \"is closed or closing\" type checks.\n  template <typename S>\n  bool isOrPending() const\n    requires(HAS_PENDING) && (_::isInTuple<S, StatesTuple>)\n  {\n    if (is<S>()) {\n      return true;\n    }\n    if constexpr (PendingSpec::template contains<S>) {\n      return pendingState.template is<S>();\n    }\n    return false;\n  }\n\n  // Get the name of the pending state (or \"(none)\" if no pending state).\n  kj::StringPtr pendingStateName() const\n    requires(HAS_PENDING)\n  {\n    if (pendingState == nullptr) {\n      return \"(none)\"_kj;\n    }\n    kj::StringPtr result = \"(unknown)\"_kj;\n    visitPendingStates([&result]<typename S>(const S&) { result = _::getStateName<S>(); });\n    return result;\n  }\n\n  // RAII guard for operation tracking.\n  //\n  // EXCEPTION SAFETY: If endOperation() triggers a pending state transition\n  // and the state constructor throws, the exception will propagate from the\n  // destructor. This is generally acceptable since state machine corruption\n  // is unrecoverable, but be aware when using this in exception-sensitive code.\n  //\n  // TODO(maybe): Currently, OperationScope does not check for transition locks at\n  // construction time - it only throws when endOperation() tries to apply a pending\n  // state while locks are held. This allows legitimate interleaved patterns like:\n  // start operation -> acquire lock -> read state -> release lock -> end operation.\n  // However, if TransitionLock and OperationScope become the only public APIs for\n  // mutating their respective counts (i.e., beginOperation()/endOperation() and\n  // lockTransitions()/unlockTransitions() are made private or removed), it might be\n  // reasonable to throw at construction time, making the error easier to diagnose.\n  class OperationScope {\n   public:\n    explicit OperationScope(StateMachine& m): machine(&m) {\n      m.beginOperation();\n    }\n\n    ~OperationScope() noexcept(false) {\n      // Note: endOperation() may throw if pending state constructor throws.\n      // We mark this noexcept(false) to be explicit about this.\n      KJ_IF_SOME(m, machine) {\n        auto applied KJ_UNUSED = m.endOperation();\n      }\n    }\n\n    OperationScope(const OperationScope&) = delete;\n    OperationScope& operator=(const OperationScope&) = delete;\n    OperationScope(OperationScope&&) = delete;\n    OperationScope& operator=(OperationScope&&) = delete;\n\n    // End the operation early, returning whether a pending state was applied.\n    // After calling end(), the destructor becomes a no-op.\n    // Similar to kj::Locked<T>::unlock().\n    KJ_WARN_UNUSED_RESULT bool end() {\n      KJ_IF_SOME(m, machine) {\n        machine = kj::none;\n        return m.endOperation();\n      }\n      return false;\n    }\n\n   private:\n    kj::Maybe<StateMachine&> machine;\n  };\n\n  OperationScope scopedOperation()\n    requires(HAS_PENDING)\n  {\n    return OperationScope(*this);\n  }\n\n  // ---------------------------------------------------------------------------\n  // GC Visitation Support\n  // ---------------------------------------------------------------------------\n\n  // Visit the current state for garbage collection.\n  // The visitor should have a visit() method that accepts references to\n  // GC-visitable types (like jsg::GcVisitor).\n  //\n  // Usage in a class with a state machine member:\n  //   void visitForGc(jsg::GcVisitor& visitor) {\n  //     state.visitForGc(visitor);\n  //   }\n  //\n  // The visitor's visit() method will be called with the current state.\n  // If the state type doesn't support GC visitation, the visit() call\n  // will be a no-op (assuming the visitor handles non-visitable types).\n  template <typename Visitor>\n  void visitForGc(Visitor& visitor) {\n    visitForGcImpl(visitor, std::make_index_sequence<STATE_COUNT>{});\n  }\n\n  template <typename Visitor>\n  void visitForGc(Visitor& visitor) const {\n    visitForGcImpl(visitor, std::make_index_sequence<STATE_COUNT>{});\n  }\n\n  // ---------------------------------------------------------------------------\n  // Interop (use sparingly - bypasses safety features)\n  // ---------------------------------------------------------------------------\n\n  // Access the underlying kj::OneOf for interop with existing code.\n  //\n  // WARNING: Use this sparingly! The returned reference bypasses ALL safety\n  // features of the state machine:\n  //   - No transition locking (references can dangle)\n  //   - No terminal state enforcement\n  //   - No pending state handling\n  //   - Modifications won't trigger pending state application\n  //\n  // This is primarily useful for:\n  // - Migrating existing code to use StateMachine\n  // - Implementing new patterns that the state machine doesn't support yet\n  // - Interfacing with APIs that expect kj::OneOf directly\n  //\n  // STRONGLY PREFER: whenState(), transitionTo(), and other type-safe methods.\n  // TODO(later): Revisit whether these should be kept.\n  StateUnion& underlying() KJ_LIFETIMEBOUND {\n    return state;\n  }\n  const StateUnion& underlying() const KJ_LIFETIMEBOUND {\n    return state;\n  }\n\n  // For use with KJ_SWITCH_ONEOF.\n  //\n  // WARNING: KJ_SWITCH_ONEOF does NOT acquire a transition lock! References\n  // obtained inside KJ_CASE_ONEOF blocks can become dangling if any code\n  // in that block triggers a state transition:\n  //\n  //   KJ_SWITCH_ONEOF(machine) {\n  //     KJ_CASE_ONEOF(active, Active) {\n  //       someFunction();  // If this transitions machine...\n  //       active.foo();    // ...this is UAF!\n  //     }\n  //   }\n  //\n  // For safe access, use whenState() instead:\n  //\n  //   machine.whenState<Active>([](Active& active) {\n  //     someFunction();  // If this tries to transition, it throws\n  //     active.foo();    // Safe - transitions are locked\n  //   });\n  auto _switchSubject() & {\n    requireInitialized();\n    return state._switchSubject();\n  }\n  auto _switchSubject() const& {\n    requireInitialized();\n    return state._switchSubject();\n  }\n  auto _switchSubject() && {\n    requireInitialized();\n    return kj::mv(state)._switchSubject();\n  }\n\n private:\n  // Private default constructor - use create<State>() factory function instead.\n  // Making this private ensures state machines are always initialized.\n  StateMachine() = default;\n\n  StateUnion state;\n\n  // Counter for detecting illegal transitions from within whenState()/whenActiveOr() callbacks.\n  // Marked mutable because const methods use it for internal bookkeeping while not changing\n  // the logical state (i.e., which state the machine is in). This class is NOT thread-safe;\n  // callers are responsible for synchronization if needed. The const qualifier on methods\n  // indicates \"does not transition the state machine\", not \"thread-safe\".\n  mutable uint32_t transitionLockCount = 0;\n\n  // Pending state support (only allocated when HAS_PENDING is true)\n  // Using _::Empty instead of char for proper [[no_unique_address]] optimization\n  WD_NO_UNIQUE_ADDRESS std::conditional_t<HAS_PENDING, StateUnion, _::Empty> pendingState{};\n  WD_NO_UNIQUE_ADDRESS std::conditional_t<HAS_PENDING, uint32_t, _::Empty> operationCount{};\n\n  void requireUnlocked() const {\n    KJ_REQUIRE(transitionLockCount == 0,\n        \"Cannot transition state machine while transitions are locked. \"\n        \"This usually means you're trying to transition inside a whenState() callback.\");\n  }\n\n  // Helper for currentStateName()\n  template <typename Visitor>\n  void visitStateNames(Visitor&& visitor) const {\n    visitStateNamesImpl(kj::fwd<Visitor>(visitor), std::make_index_sequence<STATE_COUNT>{});\n  }\n\n  template <typename Visitor, size_t... Is>\n  void visitStateNamesImpl(Visitor&& visitor, std::index_sequence<Is...>) const {\n    auto tryVisit = [&]<size_t I>() {\n      using S = std::tuple_element_t<I, StatesTuple>;\n      if (state.template is<S>()) {\n        visitor.template operator()<S>(state.template get<S>());\n      }\n    };\n    (tryVisit.template operator()<Is>(), ...);\n  }\n\n  // Helper for visit() - non-const version\n  template <typename Visitor, size_t... Is>\n  decltype(auto) visitImpl(Visitor&& visitor, std::index_sequence<Is...>) {\n    KJ_REQUIRE(isInitialized(), \"Cannot visit uninitialized state machine\");\n\n    // Use common_type to handle visitors that return compatible but different types\n    using ReturnType = std::common_type_t<decltype(visitor(\n        kj::instance<std::tuple_element_t<Is, StatesTuple>&>()))...>;\n\n    if constexpr (std::is_void_v<ReturnType>) {\n      auto tryVisit = [&]<size_t I>() {\n        using S = std::tuple_element_t<I, StatesTuple>;\n        if (state.template is<S>()) {\n          visitor(state.template get<S>());\n        }\n      };\n      (tryVisit.template operator()<Is>(), ...);\n    } else {\n      ReturnType result{};\n      auto tryVisit = [&]<size_t I>() {\n        using S = std::tuple_element_t<I, StatesTuple>;\n        if (state.template is<S>()) {\n          result = visitor(state.template get<S>());\n        }\n      };\n      (tryVisit.template operator()<Is>(), ...);\n      return result;\n    }\n  }\n\n  // Helper for visit() - const version\n  template <typename Visitor, size_t... Is>\n  decltype(auto) visitConstImpl(Visitor&& visitor, std::index_sequence<Is...>) const {\n    KJ_REQUIRE(isInitialized(), \"Cannot visit uninitialized state machine\");\n\n    using ReturnType = std::common_type_t<decltype(visitor(\n        kj::instance<const std::tuple_element_t<Is, StatesTuple>&>()))...>;\n\n    if constexpr (std::is_void_v<ReturnType>) {\n      auto tryVisit = [&]<size_t I>() {\n        using S = std::tuple_element_t<I, StatesTuple>;\n        if (state.template is<S>()) {\n          visitor(state.template get<S>());\n        }\n      };\n      (tryVisit.template operator()<Is>(), ...);\n    } else {\n      ReturnType result{};\n      auto tryVisit = [&]<size_t I>() {\n        using S = std::tuple_element_t<I, StatesTuple>;\n        if (state.template is<S>()) {\n          result = visitor(state.template get<S>());\n        }\n      };\n      (tryVisit.template operator()<Is>(), ...);\n      return result;\n    }\n  }\n\n  void applyPendingStateImpl()\n    requires(HAS_PENDING)\n  {\n    // Applying a pending state is a transition, so we must not be locked.\n    // This prevents UAF when endOperation() is called inside a whenState() callback:\n    //\n    //   machine.whenState<Active>([&](Active& a) {\n    //     {\n    //       auto op = machine.scopedOperation();\n    //       machine.deferTransitionTo<Closed>();\n    //     }  // op destroyed here - would transition while 'a' is still in use!\n    //     a.doSomething();  // UAF if transition happened above\n    //   });\n    //\n    // With this check, the above code will throw instead of causing UAF.\n    requireUnlocked();\n\n    // Check terminal state if applicable - don't apply pending state if we're\n    // already in a terminal state (this can happen if a forceTransitionTo was\n    // used to reach a terminal state while an operation was in progress).\n    if constexpr (HAS_TERMINAL || HAS_ERROR) {\n      if (isTerminal()) {\n        // Already in terminal state, discard the pending state\n        pendingState = StateUnion();\n        return;\n      }\n    }\n\n    visitPendingStates([this]<typename S>(S& s) { this->state.template init<S>(kj::mv(s)); });\n    pendingState = StateUnion();\n  }\n\n  template <typename Visitor>\n  void visitPendingStates(Visitor&& visitor) const\n    requires(HAS_PENDING)\n  {\n    visitPendingStatesImpl(kj::fwd<Visitor>(visitor), std::make_index_sequence<STATE_COUNT>{});\n  }\n\n  template <typename Visitor>\n  void visitPendingStates(Visitor&& visitor)\n    requires(HAS_PENDING)\n  {\n    visitPendingStatesImpl(kj::fwd<Visitor>(visitor), std::make_index_sequence<STATE_COUNT>{});\n  }\n\n  template <typename Visitor, size_t... Is>\n  void visitPendingStatesImpl(Visitor&& visitor, std::index_sequence<Is...>) const\n    requires(HAS_PENDING)\n  {\n    auto tryVisit = [&]<size_t I>() {\n      using S = std::tuple_element_t<I, StatesTuple>;\n      if (pendingState.template is<S>()) {\n        visitor.template operator()<S>(pendingState.template get<S>());\n      }\n    };\n    (tryVisit.template operator()<Is>(), ...);\n  }\n\n  template <typename Visitor, size_t... Is>\n  void visitPendingStatesImpl(Visitor&& visitor, std::index_sequence<Is...>)\n    requires(HAS_PENDING)\n  {\n    auto tryVisit = [&]<size_t I>() {\n      using S = std::tuple_element_t<I, StatesTuple>;\n      if (pendingState.template is<S>()) {\n        visitor.template operator()<S>(pendingState.template get<S>());\n      }\n    };\n    (tryVisit.template operator()<Is>(), ...);\n  }\n\n  // TODO(later): If we decide to ever move state-machine.h into kj, then the visitForGc\n  // details will need to be revisited since those are specific to workerd.\n  // The reasons we can't support this in the regular visit() public API are:\n  // * Need to support uninitialized states\n  // * Need to support visitors which don't implement overloads for all state types\n  // * Need to support visitors with visit() functions instead of operator()\n  // Points 1 and 2 could perhaps be encapsulated in a public API named something like weakVisit(),\n  // and point 3 could be taken care of by saying \"Your visitor must have either a visit() or\n  // operator(), but not both.\"\n  // For now, tho, we will just keep this here and we can revisit later.\n\n  // Helper for visitForGc - visits the current state if the visitor can handle it\n  template <typename Visitor, size_t... Is>\n  void visitForGcImpl(Visitor& visitor, std::index_sequence<Is...>) {\n    auto tryVisit = [&]<size_t I>(StateUnion& s) {\n      using S = std::tuple_element_t<I, StatesTuple>;\n      if (s.template is<S>()) {\n        // Only call visit if the visitor can handle this type\n        if constexpr (requires { visitor.visit(s.template get<S>()); }) {\n          visitor.visit(s.template get<S>());\n        }\n      }\n    };\n    (tryVisit.template operator()<Is>(state), ...);\n    // Also visit pending state if present\n    if constexpr (HAS_PENDING) {\n      if (hasPendingState()) {\n        (tryVisit.template operator()<Is>(pendingState), ...);\n      }\n    }\n  }\n\n  template <typename Visitor, size_t... Is>\n  void visitForGcImpl(Visitor& visitor, std::index_sequence<Is...>) const {\n    auto tryVisit = [&]<size_t I>(const StateUnion& s) {\n      using S = std::tuple_element_t<I, StatesTuple>;\n      if (s.template is<S>()) {\n        // Only call visit if the visitor can handle this type\n        if constexpr (requires { visitor.visit(s.template get<S>()); }) {\n          visitor.visit(s.template get<S>());\n        }\n      }\n    };\n    (tryVisit.template operator()<Is>(state), ...);\n    // Also visit pending state if present\n    if constexpr (HAS_PENDING) {\n      if (hasPendingState()) {\n        (tryVisit.template operator()<Is>(pendingState), ...);\n      }\n    }\n  }\n};\n\n}  // namespace workerd\n\n// =============================================================================\n// DETAILED USAGE EXAMPLES\n// =============================================================================\n//\n// Example 1: Basic Resource State Machine (Streams Pattern)\n// ---------------------------------------------------------\n//\n//   struct Open {\n//     static constexpr kj::StringPtr NAME = \"open\"_kj;\n//     kj::Own<kj::AsyncInputStream> stream;\n//   };\n//\n//   struct Closed {\n//     static constexpr kj::StringPtr NAME = \"closed\"_kj;\n//   };\n//\n//   // Full-featured stream state machine (actual pattern used in streams code)\n//   using StreamState = StateMachine<\n//       TerminalStates<Closed, kj::Exception>,  // Cannot transition out of these\n//       ErrorState<kj::Exception>,              // Enables tryGetErrorUnsafe(), isErrored()\n//       ActiveState<Open>,                      // Enables tryGetActiveUnsafe(), isActive()\n//       Open, Closed, kj::Exception>;\n//\n//   StreamState state;\n//   state.transitionTo<Open>(kj::mv(stream));\n//\n//   // Check state\n//   if (state.isActive()) { ... }\n//   if (state.isTerminal()) { ... }  // Closed or errored\n//\n//   // COMMON PATTERN: tryGetActiveUnsafe() with KJ_IF_SOME\n//   // This is the most frequently used pattern in actual streams code.\n//   // It works well with early returns and coroutines.\n//   KJ_IF_SOME(open, state.tryGetActiveUnsafe()) {\n//     // CAUTION: Don't transition state in this scope!\n//     co_return co_await open.stream->read(buffer);\n//   }\n//\n//   // ALTERNATIVE: whenActive() for safe access (transitions locked)\n//   // Use when the callback might indirectly trigger state transitions.\n//   state.whenActive([](Open& open) {\n//     open.stream->doSomething();  // Safe - transitions blocked\n//   });\n//\n//   // Error checking\n//   KJ_IF_SOME(exception, state.tryGetErrorUnsafe()) {\n//     kj::throwFatalException(kj::cp(exception));\n//   }\n//\n// Example 2: Terminal State Enforcement\n// -------------------------------------\n//\n//   StateMachine<\n//       TerminalStates<Closed, kj::Exception>,\n//       Open, Closed, kj::Exception\n//   > state;\n//\n//   state.transitionTo<Open>(...);\n//\n//   // This works\n//   state.transitionTo<Closed>();\n//\n//   // This throws! Cannot leave terminal state\n//   state.transitionTo<Open>(...);  // KJ_REQUIRE fails\n//\n//   // For cleanup/reset, use forceTransitionTo\n//   state.forceTransitionTo<Open>(...);  // Bypasses terminal check\n//\n// Example 3: Error State Helpers\n// ------------------------------\n//\n//   StateMachine<ErrorState<kj::Exception>, Open, Closed, kj::Exception> state;\n//\n//   // Old pattern (verbose):\n//   KJ_IF_SOME(err, state.tryGetUnsafe<kj::Exception>()) {\n//     kj::throwFatalException(kj::cp(err));\n//   }\n//\n//   // New pattern (cleaner):\n//   KJ_IF_SOME(err, state.tryGetErrorUnsafe()) {\n//     kj::throwFatalException(kj::cp(err));\n//   }\n//\n//   // Or check first:\n//   if (state.isErrored()) {\n//     auto& err = state.getErrorUnsafe();\n//   }\n//\n// Example 4: State Introspection for Debugging\n// --------------------------------------------\n//\n//   struct Active { static constexpr kj::StringPtr NAME = \"active\"_kj; };\n//   struct Paused { static constexpr kj::StringPtr NAME = \"paused\"_kj; };\n//   struct Done { static constexpr kj::StringPtr NAME = \"done\"_kj; };\n//\n//   StateMachine<Active, Paused, Done> state;\n//   state.transitionTo<Active>();\n//\n//   // Get current state name for logging/debugging\n//   kj::StringPtr name = state.currentStateName();  // \"active\"\n//\n//   // Use in inspectState for JS visibility\n//   jsg::JsString inspectState(jsg::Lock& js) {\n//     return js.strIntern(state.currentStateName());\n//   }\n//\n// Example 5: Lock State Machine (no terminal states)\n// --------------------------------------------------\n//\n//   struct ReaderLocked {\n//     static constexpr kj::StringPtr NAME = \"reader_locked\"_kj;\n//   };\n//   struct Unlocked {\n//     static constexpr kj::StringPtr NAME = \"unlocked\"_kj;\n//   };\n//   struct Locked {\n//     static constexpr kj::StringPtr NAME = \"locked\"_kj;\n//   };\n//\n//   // No TerminalStates - locks can always be released\n//   using LockState = StateMachine<Unlocked, Locked, ReaderLocked>;\n//\n//   LockState lockState;\n//   lockState.transitionTo<Unlocked>();\n//\n//   // Acquire lock\n//   if (lockState.is<Unlocked>()) {\n//     lockState.transitionTo<ReaderLocked>();\n//   }\n//\n//   // Release lock - always allowed\n//   lockState.transitionTo<Unlocked>();\n//\n// Example 6: Safe State Access with whenState()\n// ---------------------------------------------\n//\n//   StateMachine<Active, Paused, Done> state;\n//   state.transitionTo<Active>();\n//\n//   // SAFE: whenState() locks transitions during callback\n//   auto result = state.whenState<Active>([](Active& a) {\n//     return a.computeResult();  // a is guaranteed valid\n//   });  // Returns kj::Maybe<ResultType>\n//\n//   // Handle result after callback (transitions now allowed)\n//   KJ_IF_SOME(r, result) {\n//     state.transitionTo<Done>(kj::mv(r));\n//   }\n//\n//   // whenActiveOr() provides a default for non-active states\n//   size_t count = state.whenActiveOr(\n//       [](Active& a) { return a.itemCount; },\n//       size_t{0});  // Default if not active\n//\n// Example 7: Manual Transition Locking\n// ------------------------------------\n//\n//   StateMachine<Active, Paused, Done> state;\n//   state.transitionTo<Active>();\n//\n//   // For complex operations that need multiple state accesses\n//   {\n//     auto lock = state.acquireTransitionLock();\n//\n//     // All transitions blocked while lock is held\n//     auto& active = state.getUnsafe<Active>();\n//     active.doStep1();\n//     active.doStep2();\n//     active.doStep3();\n//\n//   }  // lock released, transitions now allowed\n//\n//   state.transitionTo<Done>();\n//\n// Example 8: Deferred State Transitions\n// -------------------------------------\n//\n//   // For deferring close/error until pending operations complete\n//   StateMachine<\n//       TerminalStates<Closed, Errored>,\n//       PendingStates<Closed, Errored>,  // States that can be deferred\n//       Active, Closed, Errored\n//   > state;\n//\n//   state.transitionTo<Active>();\n//\n//   // Start an operation\n//   state.beginOperation();  // Or: auto scope = state.scopedOperation();\n//\n//   // Close is requested, but we're mid-operation - defer it\n//   state.deferTransitionTo<Closed>();\n//\n//   KJ_EXPECT(state.is<Active>());         // Still active!\n//   KJ_EXPECT(state.hasPendingState());    // Close is pending\n//\n//   // Complete the operation - pending state is auto-applied\n//   state.endOperation();\n//   KJ_EXPECT(state.is<Closed>());         // Now closed!\n//\n//   // Common pattern for streams:\n//   void doRead(jsg::Lock& js) {\n//     auto scope = state.scopedOperation();  // RAII operation tracking\n//\n//     if (state.hasPendingState()) {\n//       // Don't start new work, we're shutting down\n//       return;\n//     }\n//\n//     // ... do the read ...\n//   }  // Operation ends, pending state applied if any\n//\n// Example 9: Visitor Pattern\n// --------------------------\n//\n//   StateMachine<Active, Paused, Done> state;\n//\n//   // Generic visitor (does NOT lock transitions)\n//   state.visit([](auto& s) {\n//     using S = kj::Decay<decltype(s)>;\n//     if constexpr (kj::isSameType<S, Active>()) {\n//       // Handle active\n//     } else if constexpr (kj::isSameType<S, Paused>()) {\n//       // Handle paused\n//     } else {\n//       // Handle done\n//     }\n//   });\n//\n// =============================================================================\n// ACTUAL USAGE PATTERNS FROM STREAMS CODE\n// =============================================================================\n//\n// The streams code uses StateMachine extensively. Here are the actual patterns:\n//\n// Common state machine declaration:\n// ---------------------------------\n//   using StreamState = StateMachine<\n//       TerminalStates<Closed, kj::Exception>,\n//       ErrorState<kj::Exception>,\n//       ActiveState<Open>,\n//       Open, Closed, kj::Exception>;\n//\n// Most common access pattern (tryGetActiveUnsafe + KJ_IF_SOME):\n// -------------------------------------------------------------\n//   // This pattern is used 100+ times in streams code because it works\n//   // well with coroutines and early returns.\n//   KJ_IF_SOME(open, state.tryGetActiveUnsafe()) {\n//     co_return co_await open.stream->read(buffer);\n//   }\n//   // Falls through if not in active state\n//\n// Error checking pattern:\n// -----------------------\n//   KJ_IF_SOME(exception, state.tryGetErrorUnsafe()) {\n//     output.abort(kj::cp(exception));\n//     kj::throwFatalException(kj::cp(exception));\n//   }\n//\n// Simple state checks:\n// --------------------\n//   if (state.is<Closed>()) { co_return 0; }\n//   if (state.isActive()) { ... }\n//   if (state.isTerminal()) { ... }\n//\n// whenActiveOr for default values:\n// --------------------------------\n//   return state.whenActiveOr(\n//       [](Queue& q) { return q.getConsumerCount(); },\n//       size_t{0});\n//\n"
  },
  {
    "path": "src/workerd/util/stream-utils.c++",
    "content": "#include \"stream-utils.h\"\n\n#include <kj/common.h>\n#include <kj/debug.h>\n#include <kj/exception.h>\n#include <kj/one-of.h>\n\nnamespace workerd {\n\nnamespace {\n\n// An AsyncInputStream implementation that reads from an in-memory buffer.\n// This is optimized for the case where the entire contents are available\n// up-front, so it doesn't do any dynamic memory allocation or copying.\n// It also supports optimized teeing when the backing storage is provided.\nclass MemoryInputStream final: public kj::AsyncInputStream {\n private:\n  struct OwnedBacking: public kj::Refcounted {\n    kj::Own<void> backing;\n    OwnedBacking(kj::Own<void>&& backing): backing(kj::mv(backing)) {}\n  };\n\n public:\n  MemoryInputStream(\n      kj::ArrayPtr<const kj::byte> data, kj::Maybe<kj::Own<void>> maybeBacking = kj::none)\n      : data(data),\n        // Note that we don't actually check that maybeBacking actually owns the\n        // memory that `data` points to. It is the caller's responsibility to ensure\n        // this is the case if they want teeing to be safely supported.\n        ownedBacking(maybeBacking.map(\n            [](kj::Own<void>& backing) mutable { return kj::rc<OwnedBacking>(kj::mv(backing)); })) {\n  }\n  MemoryInputStream(kj::ArrayPtr<const kj::byte> data, kj::Rc<OwnedBacking> ownedBacking)\n      : data(data),\n        ownedBacking(kj::mv(ownedBacking)) {}\n\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    auto ptr = kj::arrayPtr<kj::byte>(static_cast<kj::byte*>(buffer), maxBytes);\n    size_t toRead = kj::min(data.size(), ptr.size());\n    if (toRead == 0) return toRead;\n    ptr.first(toRead).copyFrom(data.first(toRead));\n    data = data.slice(toRead);\n    return toRead;\n  }\n\n  kj::Maybe<uint64_t> tryGetLength() override {\n    return data.size();\n  }\n\n  kj::Promise<uint64_t> pumpTo(kj::AsyncOutputStream& output, uint64_t amount) override {\n    // An optimized pumpTo... we know we have all the data right here. We can\n    // just write it all at once up to `amount`.\n    uint64_t toRead = kj::min(data.size(), amount);\n    if (toRead == 0) {\n      co_return toRead;\n    }\n    co_await output.write(data.first(toRead));\n    data = data.slice(toRead);\n    co_return toRead;\n  }\n\n  kj::Maybe<kj::Own<AsyncInputStream>> tryTee(uint64_t limit = kj::maxValue) override {\n    // If a MemoryInputStream is holding onto backing storage, then we can safely\n    // tee it here, allowing us to avoid the default tee implementation which needs\n    // additional buffering. Tee'ing just becomes a matter of sharing the backing\n    // storage and the data slice directly. This allows us to avoid any additional\n    // buffering in unread stream branches since all of the data is already in memory\n    // anyway. If we're not holding onto the backing storage, then we cannot safely\n    // assume that the a tee branch will safely be able to read the data, so we'll\n    // fall back to the default kj::newTee implementation.\n    KJ_IF_SOME(owned, ownedBacking) {\n      return kj::heap<MemoryInputStream>(data, owned.addRef());\n    }\n    return kj::none;\n  }\n\n private:\n  kj::ArrayPtr<const kj::byte> data;\n  kj::Maybe<kj::Rc<OwnedBacking>> ownedBacking;\n};\n\nclass NeuterableInputStreamImpl final: public NeuterableInputStream {\n public:\n  NeuterableInputStreamImpl(kj::AsyncInputStream& inner): inner(&inner) {}\n\n  void neuter(kj::Exception exception) override {\n    if (inner.is<kj::AsyncInputStream*>()) {\n      inner = kj::cp(exception);\n      if (!canceler.isEmpty()) {\n        canceler.cancel(kj::mv(exception));\n      }\n    }\n  }\n\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    return canceler.wrap(getStream().tryRead(buffer, minBytes, maxBytes));\n  }\n  kj::Maybe<uint64_t> tryGetLength() override {\n    return getStream().tryGetLength();\n  }\n  kj::Promise<uint64_t> pumpTo(kj::AsyncOutputStream& output, uint64_t amount) override {\n    return canceler.wrap(getStream().pumpTo(output, amount));\n  }\n\n private:\n  kj::OneOf<kj::AsyncInputStream*, kj::Exception> inner;\n  kj::Canceler canceler;\n\n  kj::AsyncInputStream& getStream() {\n    KJ_SWITCH_ONEOF(inner) {\n      KJ_CASE_ONEOF(stream, kj::AsyncInputStream*) {\n        return *stream;\n      }\n      KJ_CASE_ONEOF(exception, kj::Exception) {\n        kj::throwFatalException(kj::cp(exception));\n      }\n    }\n    KJ_UNREACHABLE;\n  }\n};\n\nclass NeuterableIoStreamImpl final: public NeuterableIoStream {\n public:\n  NeuterableIoStreamImpl(kj::AsyncIoStream& inner): inner(&inner) {}\n\n  void neuter(kj::Exception reason) override {\n    if (inner.is<kj::AsyncIoStream*>()) {\n      inner = kj::cp(reason);\n      if (!canceler.isEmpty()) {\n        canceler.cancel(kj::mv(reason));\n      }\n    }\n  }\n\n  // AsyncInputStream\n\n  kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {\n    return canceler.wrap(getStream().tryRead(buffer, minBytes, maxBytes));\n  }\n  kj::Maybe<uint64_t> tryGetLength() override {\n    return getStream().tryGetLength();\n  }\n  kj::Promise<uint64_t> pumpTo(kj::AsyncOutputStream& output, uint64_t amount) override {\n    return canceler.wrap(getStream().pumpTo(output, amount));\n  }\n\n  // AsyncOutputStream\n\n  kj::Promise<void> write(kj::ArrayPtr<const kj::byte> buffer) override {\n    return canceler.wrap(getStream().write(buffer));\n  }\n  kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const kj::byte>> pieces) override {\n    return canceler.wrap(getStream().write(pieces));\n  }\n  kj::Maybe<kj::Promise<uint64_t>> tryPumpFrom(\n      kj::AsyncInputStream& input, uint64_t amount) override {\n    return getStream().tryPumpFrom(input, amount).map([this](kj::Promise<uint64_t> promise) {\n      return canceler.wrap(kj::mv(promise));\n    });\n  }\n  kj::Promise<void> whenWriteDisconnected() override {\n    return canceler.wrap(getStream().whenWriteDisconnected());\n  }\n\n  // AsyncIoStream\n\n  void shutdownWrite() override {\n    getStream().shutdownWrite();\n  };\n  void abortRead() override {\n    getStream().abortRead();\n  }\n  void getsockopt(int level, int option, void* value, kj::uint* length) override {\n    getStream().getsockopt(level, option, value, length);\n  }\n  void setsockopt(int level, int option, const void* value, kj::uint length) override {\n    getStream().setsockopt(level, option, value, length);\n  }\n  void getsockname(struct sockaddr* addr, kj::uint* length) override {\n    getStream().getsockname(addr, length);\n  }\n  void getpeername(struct sockaddr* addr, kj::uint* length) override {\n    getStream().getpeername(addr, length);\n  }\n  virtual kj::Maybe<int> getFd() const override {\n    return getStream().getFd();\n  }\n\n private:\n  kj::OneOf<kj::AsyncIoStream*, kj::Exception> inner;\n  kj::Canceler canceler;\n\n  kj::AsyncIoStream& getStream() {\n    KJ_IF_SOME(stream, inner.tryGet<kj::AsyncIoStream*>()) {\n      return *stream;\n    }\n    kj::throwFatalException(kj::cp(inner.get<kj::Exception>()));\n  }\n  kj::AsyncIoStream& getStream() const {\n    KJ_IF_SOME(stream, inner.tryGet<kj::AsyncIoStream*>()) {\n      return *stream;\n    }\n    kj::throwFatalException(kj::cp(inner.get<kj::Exception>()));\n  }\n};\n\n// The kj::NullStream instance is stateless, discards all writes, and returns\n// EOF on all reads. We can, therefore, safely share a single static global\n// instance instead of allocating a new one each time.\nstatic kj::NullStream nullStream{};\n\n}  // namespace\n\nkj::AsyncOutputStream& getGlobalNullOutputStream() {\n  return nullStream;\n}\n\nkj::Own<kj::AsyncIoStream> newNullIoStream() {\n  return kj::Own<kj::AsyncIoStream>(&nullStream, kj::NullDisposer::instance);\n}\n\nkj::Own<kj::AsyncInputStream> newNullInputStream() {\n  return kj::Own<kj::AsyncInputStream>(&nullStream, kj::NullDisposer::instance);\n}\n\nkj::Own<kj::AsyncOutputStream> newNullOutputStream() {\n  return kj::Own<kj::AsyncOutputStream>(&nullStream, kj::NullDisposer::instance);\n}\n\nkj::Own<kj::AsyncInputStream> newMemoryInputStream(\n    kj::ArrayPtr<const kj::byte> data, kj::Maybe<kj::Own<void>> maybeBacking) {\n  return kj::heap<MemoryInputStream>(data, kj::mv(maybeBacking));\n}\n\nkj::Own<kj::AsyncInputStream> newMemoryInputStream(\n    kj::StringPtr data, kj::Maybe<kj::Own<void>> maybeBacking) {\n  return kj::heap<MemoryInputStream>(data.asBytes(), kj::mv(maybeBacking));\n}\n\nkj::Own<NeuterableInputStream> newNeuterableInputStream(kj::AsyncInputStream& inner) {\n  return kj::refcounted<NeuterableInputStreamImpl>(inner);\n}\n\nkj::Own<NeuterableIoStream> newNeuterableIoStream(kj::AsyncIoStream& inner) {\n  return kj::heap<NeuterableIoStreamImpl>(inner);\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/stream-utils.h",
    "content": "#pragma once\n\n#include <kj/async-io.h>\n\nnamespace workerd {\n\nkj::Own<kj::AsyncIoStream> newNullIoStream();\nkj::Own<kj::AsyncInputStream> newNullInputStream();\nkj::Own<kj::AsyncOutputStream> newNullOutputStream();\n\n// Get a shared global null output stream (singleton, thread-safe)\nkj::AsyncOutputStream& getGlobalNullOutputStream();\n\n// When maybeBacking is provided, it is held onto by the MemoryInputStream\n// using a kj::Rc<...> so that teeing the stream can share ownership of\n// the backing storage without the need for any additional buffering. If\n// the backing storage is not provided, then optimized teeing of the stream\n// will not be supported and the implementation will return a kj::none from\n// tryTee().\nkj::Own<kj::AsyncInputStream> newMemoryInputStream(\n    kj::ArrayPtr<const kj::byte>, kj::Maybe<kj::Own<void>> maybeBacking = kj::none);\nkj::Own<kj::AsyncInputStream> newMemoryInputStream(\n    kj::StringPtr, kj::Maybe<kj::Own<void>> maybeBacking = kj::none);\n\n// An InputStream that can be disconnected.\nclass NeuterableInputStream: public kj::AsyncInputStream, public kj::Refcounted {\n public:\n  virtual void neuter(kj::Exception ex) = 0;\n};\n\nclass NeuterableIoStream: public kj::AsyncIoStream {\n public:\n  virtual void neuter(kj::Exception ex) = 0;\n};\n\n// Until kj::AsyncOutputStream has an end() method of its own... We\n// provide this subclass that adds it.\nclass EndableAsyncOutputStream: public kj::AsyncOutputStream {\n public:\n  // By default, end() is a no-op. Subclasses may override.\n  virtual kj::Promise<void> end() {\n    co_return;\n  }\n};\n\nkj::Own<NeuterableInputStream> newNeuterableInputStream(kj::AsyncInputStream&);\nkj::Own<NeuterableIoStream> newNeuterableIoStream(kj::AsyncIoStream&);\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/string-buffer-test.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"string-buffer.h\"\n\n#include <kj/test.h>\n\nnamespace workerd {\nnamespace {\n\nKJ_TEST(\"append StringPtr\") {\n  StringBuffer<100> buffer(100);\n  buffer.append(\"abcdef\"_kj);\n  KJ_EXPECT(\"abcdef\"_kj == buffer.toString());\n}\n\nKJ_TEST(\"append String\") {\n  StringBuffer<100> buffer(100);\n  auto str = kj::heapString(\"abc\"_kj);\n  buffer.append(str);\n  KJ_EXPECT(\"abc\"_kj == buffer.toString());\n}\n\nKJ_TEST(\"append char array\") {\n  StringBuffer<100> buffer(100);\n  auto str = kj::heapString(\"abc\");\n  buffer.append(str);\n  KJ_EXPECT(\"abc\"_kj == buffer.toString());\n}\n\nKJ_TEST(\"overflow\") {\n  StringBuffer<10> buffer(11);\n\n  for (auto i = 0; i < 100; i++) {\n    // 3 character will test all sorts of boundary conditions\n    // with 11-bytes heap chunks.\n    buffer.append(\"abc\");\n  }\n  KJ_EXPECT(buffer.toString().size() == 300);\n  KJ_EXPECT(\n      \"abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc\"\n      \"abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc\"\n      \"abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc\"\n      \"abcabcabcabcabcabcabc\"_kj == buffer.toString());\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/string-buffer.h",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <kj/string.h>\n#include <kj/vector.h>\n\n#include <cstring>\n\nnamespace workerd {\n\n// String buffer optimized for appending a lot of strings together.\n// Allocates StackSize chunk on the stack and uses that until full.\n// Keeps allocating new chunks of at least HeapChunkSize as needed.\n// Doesn't perform any heap allocations if string stays within\n// StackSize bytes (without \\0)\ntemplate <size_t StackSize>\nclass StringBuffer {\n\n public:\n  KJ_DISALLOW_COPY_AND_MOVE(StringBuffer);\n\n  explicit StringBuffer(size_t heapChunkSize)\n      : heapChunkSize(heapChunkSize),\n        tail(&arr[0]),\n        cap(StackSize) {}\n\n  void append() {}\n\n  template <typename First, typename... Rest>\n  void append(First&& first, Rest&&... rest) {\n    appendImpl(kj::fwd<First>(first));\n    append(kj::fwd<Rest>(rest)...);\n  }\n\n  kj::String toString() {\n    auto result = kj::heapString(len);\n    copyTo(result.begin());\n    return result;\n  }\n\n private:\n  // minimum heap chunk size\n  const size_t heapChunkSize;\n\n  // chunk on the stack\n  char arr[StackSize];\n\n  // on the heap chunks\n  kj::Vector<kj::Array<char>> chunks;\n\n  // points after the last used bytes in current chunk\n  char* tail;\n\n  // number of bytes available in current chunk\n  size_t cap;\n\n  // total length of the data appended so far\n  size_t len = 0;\n\n  void appendImpl(const char* ptr, size_t size) {\n    size_t toCopy = kj::min(size, cap);\n    memcpy(tail, ptr, toCopy);\n    tail += toCopy;\n    cap -= toCopy;\n\n    if (toCopy != size) {\n      // prepare new chunk\n      size_t remaining = size - toCopy;\n      size_t chunkSize = kj::max(remaining, heapChunkSize);  // don't chunk large strings\n      auto chunk = kj::heapArray<char>(chunkSize);\n\n      // copy the rest of the string to the new chunk\n      memcpy(chunk.begin(), ptr + toCopy, remaining);\n      tail = chunk.begin() + remaining;\n      cap = chunk.size() - remaining;\n\n      chunks.add(kj::mv(chunk));\n    }\n\n    len += size;\n  }\n\n  void appendImpl(const kj::StringPtr& str) {\n    appendImpl(str.begin(), str.size());\n  }\n\n  template <size_t size>\n  void appendImpl(const char (&arr)[size]) {\n    appendImpl(arr, size - 1 /* assume 0-terminated strings */);\n  }\n\n  inline void appendImpl(const kj::ArrayPtr<const char>& arr) {\n    appendImpl(arr.begin(), arr.size());\n  }\n\n  inline void appendImpl(const kj::String& str) {\n    appendImpl(str.asPtr());\n  }\n\n  void copyTo(char* dest) {\n    // copy stack portion first\n    size_t onStack = kj::min(len, StackSize);\n    memcpy(dest, arr, onStack);\n    dest += onStack;\n\n    // copy from heap chunks\n    if (onStack < len) {\n      size_t remaining = len - onStack;\n      for (auto& chunk: chunks) {\n        size_t inChunk = kj::min(remaining, chunk.size());  // last chunk won't be full\n        memcpy(dest, chunk.begin(), inChunk);\n        dest += inChunk;\n        remaining -= inChunk;\n      }\n\n      KJ_IREQUIRE(remaining == 0);\n    }\n  }\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/strings.c++",
    "content": "#include \"strings.h\"\n\nnamespace workerd {\nnamespace {\nconstexpr uint64_t broadcast(uint8_t v) noexcept {\n  return 0x101010101010101ull * v;\n}\n\n// SWAR routine designed to convert ASCII uppercase letters to lowercase.\n// Let's process 8 bytes (64 bits) at a time using a single 64-bit int,\n// treating as 8 parallel 8-bit values.\n// PS: This will enable the use of auto-vectorization.\nconstexpr void toLowerAscii(char* input, size_t length) noexcept {\n  constexpr const uint64_t broadcast_80 = broadcast(0x80);\n  constexpr const uint64_t broadcast_Ap = broadcast(128 - 'A');\n  constexpr const uint64_t broadcast_Zp = broadcast(128 - 'Z' - 1);\n  size_t i = 0;\n  for (; i + 7 < length; i += 8) {\n    uint64_t word{};\n    memcpy(&word, input + i, sizeof(word));\n    word ^= (((word + broadcast_Ap) ^ (word + broadcast_Zp)) & broadcast_80) >> 2;\n    memcpy(input + i, &word, sizeof(word));\n  }\n  if (i < length) {\n    uint64_t word{};\n    memcpy(&word, input + i, length - i);\n    word ^= (((word + broadcast_Ap) ^ (word + broadcast_Zp)) & broadcast_80) >> 2;\n    memcpy(input + i, &word, length - i);\n  }\n}\n\nconstexpr void toUpperAscii(char* input, size_t length) noexcept {\n  constexpr const uint64_t broadcast_80 = broadcast(0x80);\n  constexpr const uint64_t broadcast_ap = broadcast(128 - 'a');\n  constexpr const uint64_t broadcast_zp = broadcast(128 - 'z' - 1);\n  size_t i = 0;\n  for (; i + 7 < length; i += 8) {\n    uint64_t word{};\n    memcpy(&word, input + i, sizeof(word));\n    word ^= (((word + broadcast_ap) ^ (word + broadcast_zp)) & broadcast_80) >> 2;\n    memcpy(input + i, &word, sizeof(word));\n  }\n  if (i < length) {\n    uint64_t word{};\n    memcpy(&word, input + i, length - i);\n    word ^= (((word + broadcast_ap) ^ (word + broadcast_zp)) & broadcast_80) >> 2;\n    memcpy(input + i, &word, length - i);\n  }\n}\n}  // namespace\n\nkj::String toLower(kj::String&& str) {\n  toLowerAscii(str.begin(), str.size());\n  return kj::mv(str);\n}\n\nkj::String toUpper(kj::String&& str) {\n  toUpperAscii(str.begin(), str.size());\n  return kj::mv(str);\n}\n\nkj::String toLower(kj::ArrayPtr<const char> ptr) {\n  return toLower(kj::str(ptr));\n}\n\nkj::String toUpper(kj::ArrayPtr<const char> ptr) {\n  return toUpper(kj::str(ptr));\n}\n\nkj::ArrayPtr<const char> trimLeadingAndTrailingWhitespace(kj::ArrayPtr<const char> ptr) {\n  size_t start = 0;\n  auto end = ptr.size();\n  while (start < end && isAsciiWhitespace(ptr[start])) {\n    start++;\n  }\n  while (end > start && isAsciiWhitespace(ptr[end - 1])) {\n    end--;\n  }\n  return ptr.slice(start, end).asChars();\n}\n\nkj::ArrayPtr<const char> trimTailingWhitespace(kj::ArrayPtr<const char> ptr) {\n  auto end = ptr.size();\n  while (end > 0 && isAsciiWhitespace(ptr[end - 1])) {\n    end--;\n  }\n  return ptr.first(end).asChars();\n}\n\nkj::Array<kj::byte> stripInnerWhitespace(kj::ArrayPtr<kj::byte> input) {\n  auto result = kj::heapArray<kj::byte>(input.size());\n  size_t len = 0;\n  for (const kj::byte c: input) {\n    if (!isAsciiWhitespace(c)) {\n      result[len++] = c;\n    }\n  }\n  return result.first(len).attach(kj::mv(result));\n};\n\nbool strcaseeq(kj::ArrayPtr<const char> a, kj::ArrayPtr<const char> b) {\n  // This could likely be optimized further but this is more than sufficient for now.\n  if (a.size() != b.size()) return false;\n  for (size_t i = 0; i < a.size(); ++i) {\n    char ca = a[i];\n    char cb = b[i];\n    // Convert to lowercase for comparison\n    if (isAlphaUpper(ca)) ca += 32;\n    if (isAlphaUpper(cb)) cb += 32;\n    if (ca != cb) return false;\n  }\n  return true;\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/strings.h",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <kj/string.h>\n\n#include <cstdint>\n\nnamespace workerd {\n\nenum CharAttributeFlag : uint8_t {\n  NONE = 0,\n  ALPHA = 1 << 0,\n  DIGIT = 1 << 1,\n  HEX = 1 << 2,\n  ASCII = 1 << 3,\n  ASCII_WHITESPACE = 1 << 4,\n  UPPER_CASE = 1 << 5,\n  LOWER_CASE = 1 << 6,\n  SEPARATOR = 1 << 7,\n};\n\n// Construct a lookup table for various interesting character properties.\nconstexpr kj::FixedArray<uint8_t, 256> kCharLookupTable = []() consteval {\n  kj::FixedArray<uint8_t, 256> result{};\n  for (uint8_t c = 'A'; c <= 'Z'; c++) {\n    if (c <= 'F') {\n      result[c] |= CharAttributeFlag::HEX;\n      result[c + 0x20] |= CharAttributeFlag::HEX;\n    }\n    result[c] |= CharAttributeFlag::ALPHA | CharAttributeFlag::UPPER_CASE;\n    result[c + 0x20] |= CharAttributeFlag::ALPHA | CharAttributeFlag::LOWER_CASE;\n  }\n  for (uint8_t c = '0'; c <= '9'; c++) {\n    result[c] |= CharAttributeFlag::DIGIT | CharAttributeFlag::HEX;\n  }\n  for (uint8_t c = 0; c <= 0x7f; c++) {\n    result[c] |= CharAttributeFlag::ASCII;\n  }\n  for (uint8_t c: {0x09, 0x0a, 0x0c, 0x0d, 0x20}) {\n    result[c] |= CharAttributeFlag::ASCII_WHITESPACE;\n  }\n  result['+'] |= CharAttributeFlag::SEPARATOR;\n  result['-'] |= CharAttributeFlag::SEPARATOR;\n  result['_'] |= CharAttributeFlag::SEPARATOR;\n  return result;\n}();\n\nconstexpr bool isAlpha(const kj::byte c) noexcept {\n  return kCharLookupTable[c] & CharAttributeFlag::ALPHA;\n}\n\nconstexpr bool isDigit(const kj::byte c) noexcept {\n  return kCharLookupTable[c] & CharAttributeFlag::DIGIT;\n}\n\n// Check if `c` is the ASCII code of a hexadecimal digit.\nconstexpr bool isHexDigit(const kj::byte c) noexcept {\n  return kCharLookupTable[c] & CharAttributeFlag::HEX;\n}\n\nconstexpr bool isAscii(const kj::byte c) noexcept {\n  return kCharLookupTable[c] & CharAttributeFlag::ASCII;\n}\n\nconstexpr bool isAsciiWhitespace(const kj::byte c) noexcept {\n  return kCharLookupTable[c] & CharAttributeFlag::ASCII_WHITESPACE;\n}\n\nconstexpr bool isAlphaUpper(const kj::byte c) noexcept {\n  return kCharLookupTable[c] & CharAttributeFlag::UPPER_CASE;\n}\n\nconstexpr bool isAlphaLower(const kj::byte c) noexcept {\n  return kCharLookupTable[c] & CharAttributeFlag::LOWER_CASE;\n}\n\n// TODO(later): If kj::ArrayPtr ever supports a constexpr [] operator,\n// make this function constexpr.\nbool strcaseeq(kj::ArrayPtr<const char> a, kj::ArrayPtr<const char> b);\n\n// Convert ASCII alpha characters in the given string to lowercase in place.\nkj::String toLower(kj::String&& str);\n\n// Convert ASCII alpha characters in the given string to uppercase in place.\nkj::String toUpper(kj::String&& str);\n\n// Copy the input and convert ASCII alpha characters in the given string to lowercase.\nkj::String toLower(kj::ArrayPtr<const char> ptr);\nkj::String toUpper(kj::ArrayPtr<const char> ptr);\n\nkj::ArrayPtr<const char> trimLeadingAndTrailingWhitespace(kj::ArrayPtr<const char> ptr);\nkj::ArrayPtr<const char> trimTailingWhitespace(kj::ArrayPtr<const char> ptr);\nkj::Array<kj::byte> stripInnerWhitespace(kj::ArrayPtr<kj::byte> input);\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/strong-bool-test.c++",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"strong-bool.h\"\n\n#include <kj/test.h>\n\n#include <type_traits>\n\nnamespace workerd {\n\nWD_STRONG_BOOL(Strongbad);\nWD_STRONG_BOOL(Burninator);\n\nconstexpr Strongbad giveStrongbad() {\n  return Strongbad::NO;\n}\nconstexpr Burninator giveBurninator() {\n  return Burninator::YES;\n}\nconstexpr void takeStrongbad(Strongbad strongbadValue) {}\nconstexpr void takeBurninator(Burninator burninatorValue) {}\n\n#define CAN_DECLARE(DECLARATION, TYPE)                                                             \\\n  {                                                                                                \\\n    /* wrap it in a lambda to make it embeddable inside functions */                               \\\n    constexpr auto can_declare = [&]<typename T>() -> bool {                                       \\\n      /* Using a template to delay compilation into the requires */                                \\\n      static_assert(requires(T t) { DECLARATION; }, \"Can declare\");                                \\\n      return true;                                                                                 \\\n    };                                                                                             \\\n    KJ_ASSERT(can_declare.template operator()<TYPE>(), \"Can declare\"); /* Easier to debug */       \\\n  }\n\n#define CAN_NOT_DECLARE(DECLARATION, TYPE)                                                         \\\n  {                                                                                                \\\n    /* wrap it in a lambda to make it embeddable inside functions */                               \\\n    constexpr auto can_not_declare = [&]<typename T>() -> bool {                                   \\\n      /* Using a template to delay compilation into the requires */                                \\\n      static_assert(!requires(T t) { DECLARATION; }, \"Can not declare\");                           \\\n      return false;                                                                                \\\n    };                                                                                             \\\n    KJ_ASSERT(!can_not_declare.template operator()<TYPE>(), \"Can not declare\");                    \\\n  }\n\nKJ_TEST(\"WD_STRONG_BOOL compile failures\") {\n  [[maybe_unused]] Strongbad strongbadValue{Strongbad::NO};\n  [[maybe_unused]] Burninator burninatorValue{Burninator::YES};\n  [[maybe_unused]] bool booleanValue = false;\n  [[maybe_unused]] int integerValue = 123;\n\n  // No uninitialized values\n  //Strongbad uninitialized;\n  CAN_DECLARE(T{}, bool);\n  CAN_NOT_DECLARE(T{}, Strongbad);\n  static_assert(!std::is_default_constructible_v<Strongbad>, \"Should fail\");\n\n  // No implicit conversion from bool or int\n  //strongbad = false;\n  CAN_DECLARE(t = false, bool);\n  CAN_NOT_DECLARE(t = false, Strongbad);\n  //strongbad = 123;\n  CAN_DECLARE(t = 123, bool);\n  CAN_NOT_DECLARE(t = 123, Strongbad);\n  //strongbad = booleanValue;\n  CAN_DECLARE(t = booleanValue, int);\n  CAN_NOT_DECLARE(t = 123, Strongbad);\n  //strongbad = integerValue;\n  CAN_DECLARE(t = integerValue, bool);\n  CAN_NOT_DECLARE(t = integerValue, Strongbad);\n  //Strongbad s1 = false;\n  static_assert(\n      !std::is_convertible_v<Strongbad, bool>, \"Should not be implicitly convertible from bool\");\n  //Strongbad s2 = 123;\n  static_assert(\n      !std::is_convertible_v<Strongbad, int>, \"Should not be implicitly convertible from int\");\n\n  // No implicit conversion to bool or int\n  //booleanValue = strongbadValue;\n  CAN_NOT_DECLARE(t = strongbadValue, bool);\n  CAN_NOT_DECLARE(t = Strongbad::YES, bool);\n  //integerValue = strongbadValue;\n  CAN_NOT_DECLARE(t = strongbadValue, int);\n  CAN_NOT_DECLARE(t = Strongbad::YES, int);\n  //bool b = strongbadValue;\n  //bool b = Strongbad::YES;\n  static_assert(\n      !std::is_convertible_v<bool, Strongbad>, \"Should not be implicitly convertible to Strongbad\");\n  //int i = strongbadValue;\n  //int i = Strongbad::NO;\n  static_assert(\n      !std::is_convertible_v<int, Strongbad>, \"Should not be implicitly convertible to Strongbad\");\n\n  // No implicit conversion between strong bools.\n  //strongbadValue = burninatorValue;\n  CAN_NOT_DECLARE(t = burninatorValue, Strongbad);\n  //if (strongbadValue == burninatorValue) {}\n  CAN_NOT_DECLARE(t == burninatorValue, Strongbad);\n\n  //takeBurninator(giveStrongbad());\n  CAN_NOT_DECLARE(takeBurninator(t), Strongbad);\n  //takeStrongbad(giveBurninator());\n  CAN_NOT_DECLARE(takeStrongbad(t), Burninator);\n}\n\nKJ_TEST(\"WD_STRONG_BOOL can be explicitly converted to and from `bool`\") {\n  Strongbad strongbadNo{Strongbad::NO};\n  Strongbad strongbadYes{Strongbad::YES};\n\n  auto booleanValue = strongbadNo.toBool();\n  static_assert(kj::isSameType<decltype(booleanValue), bool>());\n\n  auto strongbadNo2 = Strongbad(booleanValue);\n  static_assert(kj::isSameType<decltype(strongbadNo2), Strongbad>());\n\n  // Literals can be explicitly converted in both directions, too.\n  static_assert(kj::isSameType<decltype(Strongbad::NO.toBool()), bool>());\n  static_assert(kj::isSameType<decltype(Strongbad(false)), Strongbad>());\n\n  // Can't use static_assert because they're not constexpr. We'll test constexpr elsewhere.\n  KJ_EXPECT(!strongbadNo.toBool());\n  KJ_EXPECT(strongbadYes.toBool());\n  KJ_EXPECT(Strongbad(false) == Strongbad::NO);\n  KJ_EXPECT(Strongbad(true) == Strongbad::YES);\n}\n\nKJ_TEST(\"WD_STRONG_BOOL can be contextually converted to `bool`\") {\n  Strongbad strongbadNo{Strongbad::NO};\n  Burninator burninatorYes{Burninator::YES};\n\n  // It's a Strongbad ...\n  static_assert(kj::isSameType<decltype(strongbadNo), Strongbad>());\n  static_assert(kj::isSameType<decltype(Strongbad::NO), const Strongbad>());\n  static_assert(kj::isSameType<decltype(Strongbad::YES), const Strongbad>());\n\n  // ... until you use it in an explicitly boolean context.\n  static_assert(kj::isSameType<decltype(!strongbadNo), bool>());\n  static_assert(kj::isSameType<decltype(strongbadNo && burninatorYes), bool>());\n  static_assert(kj::isSameType<decltype(strongbadNo || burninatorYes), bool>());\n\n  // These are all contextually converted to `bool`s.\n  if (strongbadNo) {}\n  if (strongbadNo && burninatorYes) {}\n  if (strongbadNo || burninatorYes) {}\n\n  // Can't use static_assert because they're not constexpr. We'll test constexpr elsewhere.\n  KJ_EXPECT(!strongbadNo);\n  KJ_EXPECT(!Strongbad::NO);\n\n  // TODO(someday): KJ magic asserts are not a boolean context :(\n  KJ_EXPECT(!!burninatorYes);\n  KJ_EXPECT(!!Strongbad::YES);\n}\n\nKJ_TEST(\"WD_STRONG_BOOL is constexpr\") {\n  if constexpr (constexpr auto s = giveStrongbad()) {\n    static_assert(kj::isSameType<decltype(s), const Strongbad>());\n  }\n\n  constexpr Strongbad strongbadValue{Strongbad::NO};\n  if constexpr (strongbadValue) {}\n  if constexpr (Strongbad(true)) {}\n  if constexpr (Strongbad::NO.toBool()) {}\n  if constexpr (Strongbad::NO || Strongbad::YES) {}\n  if constexpr (Strongbad::NO && Strongbad::YES) {}\n  [[maybe_unused]] constexpr auto order = Strongbad::YES <=> Strongbad::NO;\n\n  static_assert(!strongbadValue);\n}\n\nKJ_TEST(\"WD_STRONG_BOOL comparison operators\") {\n  constexpr Strongbad strongbadNo{Strongbad::NO};\n  constexpr Strongbad strongbadYes{Strongbad::YES};\n\n  if constexpr (strongbadNo == strongbadYes) {}\n  if constexpr (strongbadNo != strongbadYes) {}\n  if constexpr (strongbadNo < strongbadYes) {}\n  if constexpr (strongbadNo > strongbadYes) {}\n  if constexpr (strongbadNo <= strongbadYes) {}\n  if constexpr (strongbadNo >= strongbadYes) {}\n\n  // NOLINTBEGIN(misc-redundant-expression)\n  static_assert(strongbadNo == strongbadNo);\n  static_assert(strongbadYes == strongbadYes);\n  static_assert(!(strongbadNo != strongbadNo));\n  static_assert(!(strongbadYes != strongbadYes));\n  static_assert(!(strongbadNo < strongbadNo));\n  static_assert(!(strongbadYes < strongbadYes));\n  static_assert(strongbadNo < strongbadYes);\n  static_assert(strongbadYes > strongbadNo);\n  static_assert(strongbadYes >= strongbadNo);\n  static_assert(strongbadNo <= strongbadYes);\n  static_assert(!(strongbadYes <= strongbadNo));\n  static_assert(!(strongbadNo >= strongbadYes));\n  // NOLINTEND(misc-redundant-expression)\n}\n\nKJ_TEST(\"WD_STRONG_BOOL logical operators\") {\n  constexpr Strongbad strongbadNo{Strongbad::NO};\n  constexpr Strongbad strongbadYes{Strongbad::YES};\n\n  static_assert(kj::isSameType<decltype(strongbadNo && strongbadYes), Strongbad>());\n  static_assert(kj::isSameType<decltype(strongbadNo || strongbadYes), Strongbad>());\n\n  // Logical operators with contextual conversion\n  if (strongbadNo && strongbadYes) {}\n  if (strongbadNo || strongbadYes) {}\n\n  // NOLINTBEGIN(misc-redundant-expression)\n  static_assert((strongbadNo && strongbadNo) == Strongbad::NO);\n  static_assert((strongbadNo && strongbadYes) == Strongbad::NO);\n  static_assert((strongbadYes && strongbadNo) == Strongbad::NO);\n  static_assert((strongbadYes && strongbadYes) == Strongbad::YES);\n  static_assert((strongbadNo || strongbadNo) == Strongbad::NO);\n  static_assert((strongbadNo || strongbadYes) == Strongbad::YES);\n  static_assert((strongbadYes || strongbadNo) == Strongbad::YES);\n  static_assert((strongbadYes || strongbadYes) == Strongbad::YES);\n  // NOLINTEND(misc-redundant-expression)\n}\n\nKJ_TEST(\"WD_STRONG_BOOL can be stringified\") {\n  constexpr Strongbad strongbadNo{Strongbad::NO};\n  constexpr Strongbad strongbadYes{Strongbad::YES};\n  constexpr auto stringNo = kj::_::STR * strongbadNo;\n  constexpr auto stringYes = kj::_::STR * strongbadYes;\n  static_assert(stringNo == \"Strongbad::NO\"_kjc);\n  static_assert(stringYes == \"Strongbad::YES\"_kjc);\n\n  static_assert(kj::_::STR * Burninator::NO == \"Burninator::NO\"_kjc);\n  static_assert(kj::_::STR * Burninator::YES == \"Burninator::YES\"_kjc);\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/strong-bool.h",
    "content": "// Copyright (c) 2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n#pragma once\n\n#include <kj/string.h>\n\n#include <compare>\n#include <cstdint>\n\nnamespace workerd {\n\n// WD_STRONG_BOOL(StrongBool) defines a class type, `StrongBool`, which acts like a boolean flag,\n// but with greater type safety.\n//\n// `StrongBool` has the following restrictions:\n// - No default constructor: you must explicitly initialize values\n// - No possibility for uninitialized values\n// - No implicit conversion to or from boolean values\n//\n// `StrongBool` supports the following explicit and contextual boolean conversions:\n// - Explicit conversion from boolean values: `StrongBool(true)`, `StrongBool(false)`\n// - Explicit conversion to boolean values: `bool(StrongBool::YES)`, `StrongBool::NO.toBool()`\n// - Contextual boolean conversion: `if (strongBool)`, `while (strongBool)`, `!strongBool`\n//\n// The `.toBool()` function exists to make safe, explicit conversion to `bool` more convenient,\n// avoiding the verbosity of `static_cast` and the risk of C-style/functional casts.\n//\n// `StrongBool` supports the full suite of comparison and logical operators. Note that ! is\n// supported via contextual conversion (`explicit operator bool()`), rather than `operator!()`.\n//\n// Logical operators (&& and ||) preserve the type of their operands when the operands are the same\n// `StrongBool` type. Otherwise, contextual boolean conversion applies, and the operands will be\n// converted to `bool` before the operator is invoked.\n#define WD_STRONG_BOOL(Type)                                                                       \\\n  class Type final {                                                                               \\\n   public:                                                                                         \\\n    static const Type NO;                                                                          \\\n    static const Type YES;                                                                         \\\n    constexpr explicit Type(bool booleanValue): value(booleanValue ? Value::YES : Value::NO) {}    \\\n    constexpr explicit operator bool() const {                                                     \\\n      return toBool();                                                                             \\\n    }                                                                                              \\\n    constexpr bool toBool() const {                                                                \\\n      return value == YES;                                                                         \\\n    }                                                                                              \\\n    constexpr auto operator<=>(const Type&) const = default;                                       \\\n    constexpr Type operator&&(const Type& other) const {                                           \\\n      return Type(value == YES && other.value == YES);                                             \\\n    }                                                                                              \\\n    constexpr Type operator||(const Type& other) const {                                           \\\n      return Type(value == YES || other.value == YES);                                             \\\n    }                                                                                              \\\n                                                                                                   \\\n   private:                                                                                        \\\n    enum class Value : std::uint8_t { NO, YES };                                                   \\\n    constexpr Type(Value value): value(value) {}                                                   \\\n    Value value;                                                                                   \\\n  };                                                                                               \\\n  constexpr inline kj::LiteralStringConst KJ_STRINGIFY(Type value) {                               \\\n    return value ? #Type \"::YES\"_kjc : #Type \"::NO\"_kjc;                                           \\\n  }                                                                                                \\\n  inline constexpr Type Type::NO{Type::Value::NO};                                                 \\\n  inline constexpr Type Type::YES {                                                                \\\n    Type::Value::YES                                                                               \\\n  }\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/test-test.c++",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"test.h\"\n\nnamespace workerd {\nnamespace {\n\nKJ_TEST(\"can check thrown exception type and message\") {\n\n  WD_EXPECT_THROW(KJ_EXCEPTION(DISCONNECTED, \"foo\"),\n      kj::throwRecoverableException(KJ_EXCEPTION(DISCONNECTED, \"foo\")));\n\n  {\n    KJ_EXPECT_LOG(ERROR, \"exception description didn't match\");\n\n    WD_EXPECT_THROW(KJ_EXCEPTION(DISCONNECTED, \"bar\"),\n        kj::throwRecoverableException(KJ_EXCEPTION(DISCONNECTED, \"foo\")));\n  }\n\n  {\n    KJ_EXPECT_LOG(ERROR, \"code threw wrong exception type\");\n\n    WD_EXPECT_THROW(KJ_EXCEPTION(UNIMPLEMENTED, \"foo\"),\n        kj::throwRecoverableException(KJ_EXCEPTION(DISCONNECTED, \"foo\")));\n  }\n\n  {\n    KJ_EXPECT_LOG(ERROR, \"code did not throw\");\n\n    WD_EXPECT_THROW(KJ_EXCEPTION(DISCONNECTED, \"foo\"), {});\n  }\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/test.h",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n#include <kj/test.h>\n\nnamespace workerd {\n\n// Checks that code throws a kj::Exception matching the type and message of the given exception.\n//\n// Note: Performs no special handling for JsExceptionThrown, so tests that need to explicitly\n// detect thrown JS exceptions will probably want to use a separate macro.\n#define WD_EXPECT_THROW(expException, code, ...)                                                   \\\n  do {                                                                                             \\\n    /* NOLINTNEXTLINE(performance-unnecessary-copy-initialization) */                              \\\n    auto expExcObj = expException;                                                                 \\\n    KJ_IF_SOME(e, ::kj::runCatchingExceptions([&]() { (void)({ code; }); })) {                     \\\n      KJ_EXPECT(e.getType() == expExcObj.getType(), \"code threw wrong exception type: \" #code, e,  \\\n          ##__VA_ARGS__);                                                                          \\\n      KJ_EXPECT(e.getDescription() == expExcObj.getDescription(),                                  \\\n          \"exception description didn't match\", e, ##__VA_ARGS__);                                 \\\n    } else {                                                                                       \\\n      KJ_FAIL_EXPECT(\"code did not throw: \" #code, ##__VA_ARGS__);                                 \\\n    }                                                                                              \\\n  } while (false)\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/thread-scopes.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"thread-scopes.h\"\n\n#include <kj/debug.h>\n\n#include <atomic>\n\nnamespace workerd {\n\nusing kj::uint;\n\nnamespace {\n\nthread_local uint allowV8BackgroundThreadScopeCount = 0;\n\nbool multiTenantProcess = false;\nbool predictableMode = false;\n\n// This variable is read in signal handlers, so use atomic stores and compiler barriers as\n// needed in regular code. Atomic loads are unnecessary, because we're not synchronizing with\n// other threads.\nthread_local ThreadProgressCounter* activeProgressCounter = nullptr;\n\n}  // namespace\n\nAllowV8BackgroundThreadsScope::AllowV8BackgroundThreadsScope() {\n  ++allowV8BackgroundThreadScopeCount;\n}\n\nAllowV8BackgroundThreadsScope::~AllowV8BackgroundThreadsScope() noexcept(false) {\n  --allowV8BackgroundThreadScopeCount;\n}\n\nbool AllowV8BackgroundThreadsScope::isActive() {\n  return allowV8BackgroundThreadScopeCount > 0;\n}\n\nbool isMultiTenantProcess() {\n  return multiTenantProcess;\n}\n\nvoid setMultiTenantProcess() {\n  multiTenantProcess = true;\n}\n\nbool isPredictableModeForTest() {\n  return predictableMode;\n}\n\nvoid setPredictableModeForTest() {\n  predictableMode = true;\n}\n\nThreadProgressCounter::ThreadProgressCounter(uint64_t& counter)\n    : savedValue(__atomic_load_n(&counter, __ATOMIC_RELAXED)),\n      counter(counter) {\n  if (activeProgressCounter == nullptr) {\n    // Release compiler barrier guarantees we're initialized before signal handlers can see us.\n    std::atomic_signal_fence(std::memory_order_release);\n    __atomic_store_n(&activeProgressCounter, this, __ATOMIC_RELAXED);\n  } else {\n    // Another progress counter is active on this thread, likely meaning we reentered.\n  }\n}\n\nThreadProgressCounter::~ThreadProgressCounter() noexcept(false) {\n  auto& self = KJ_ASSERT_NONNULL(\n      activeProgressCounter, \"~ProgressCounter() with no active progress counter.\");\n  if (&self == this) {\n    // Acquire compiler barrier to prevent any teardown from leaking above this nullification.\n    KJ_DEFER({\n      __atomic_store_n(&activeProgressCounter, nullptr, __ATOMIC_RELAXED);\n      std::atomic_signal_fence(std::memory_order_acquire);\n    });\n  } else {\n    // Nothing to do, tearing down reentered progress counter.\n  }\n}\n\nbool ThreadProgressCounter::hasProgress() {\n  KJ_IF_SOME(progressCounter, activeProgressCounter) {\n    // The counter itself may be incremented by any thread, but there's no real synchronization\n    // concern, so we can use relaxed memory ordering. If the machine is so bogged down that a\n    // stale value causes a false positive, then crashing seems reasonable.\n    auto currentValue = __atomic_load_n(&progressCounter.counter, __ATOMIC_RELAXED);\n\n    // `savedValue` is only ever accessed by our own thread, so no need for atomics here.\n    if (progressCounter.savedValue != currentValue) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nvoid ThreadProgressCounter::acknowledgeProgress() {\n  KJ_IF_SOME(progressCounter, activeProgressCounter) {\n    progressCounter.savedValue = __atomic_load_n(&progressCounter.counter, __ATOMIC_RELAXED);\n  }\n}\n\n// ======================================================================================\n\nnamespace {\nthread_local uint warnAboutIsolateLockScopeCount = 0;\n}  // namespace\n\nWarnAboutIsolateLockScope::WarnAboutIsolateLockScope() {\n  ++warnAboutIsolateLockScopeCount;\n}\n\nWarnAboutIsolateLockScope::~WarnAboutIsolateLockScope() noexcept(false) {\n  if (!released) release();\n}\n\nWarnAboutIsolateLockScope::WarnAboutIsolateLockScope(WarnAboutIsolateLockScope&& other)\n    : released(other.released) {\n  other.released = true;\n}\n\nvoid WarnAboutIsolateLockScope::release() {\n  if (!released) {\n    --warnAboutIsolateLockScopeCount;\n    released = true;\n  }\n}\n\nvoid WarnAboutIsolateLockScope::maybeWarn() {\n  if (warnAboutIsolateLockScopeCount > 0) {\n    KJ_LOG(WARNING, \"taking isolate lock at a bad time\", kj::getStackTrace());\n  }\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/thread-scopes.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n// This file contains several horrible hacks involving setting a thread-local value within some\n// scope in the call stack, and then being able to check the value from deeper in the stack,\n// without passing down an object. We use this pattern to signal hints across modules that do not\n// directly call each other, where it would be excessively inconvenient to pass the value down the\n// stack, perhaps because there is code in between that we do not control (e.g., V8).\n//\n// This is an anti-pattern and these should be considered HORRIBLE HACKS... but they get their jobs\n// done for the time being.\n\n#include <kj/common.h>\n\n#include <cinttypes>\n\nnamespace workerd {\n\n// Normally, we prohibit V8 worker threads, but in some cases it's useful to temporarily allow\n// them. Create this on the stack to temporarily allow V8 code running in the current thread to\n// spawn worker threads.\n//\n// In particular this is used when loading Wasm modules, to properly enable Liftoff and Tier-up.\nclass AllowV8BackgroundThreadsScope {\n public:\n  AllowV8BackgroundThreadsScope();\n  ~AllowV8BackgroundThreadsScope() noexcept(false);\n\n  static bool isActive();\n\n  KJ_DISALLOW_COPY_AND_MOVE(AllowV8BackgroundThreadsScope);\n};\n\n// Tracks whether the process hosts isolates from multiple parties that don't know about each\n// other. In such a case, we must take additional precautions against Spectre, and prohibit\n// functionality which cannot be made Spectre-safe.\n//\n// (Note that simply turning this on is NOT sufficient to enable Spectre protection. Instead, this\n// is mostly used as a safeguard to *disable* functionality that is known not to be Spectre-safe.)\n//\n// This is actually a process-level flag rather than thread-level. Once a process becomes\n// multi-tenant it cannot go back, since secrets could persist in memory.\nbool isMultiTenantProcess();\n\n// Tracks whether the process hosts isolates from multiple parties that don't know about each\n// other. In such a case, we must take additional precautions against Spectre, and prohibit\n// functionality which cannot be made Spectre-safe.\n//\n// (Note that simply turning this on is NOT sufficient to enable Spectre protection. Instead, this\n// is mostly used as a safeguard to *disable* functionality that is known not to be Spectre-safe.)\n//\n// This is actually a process-level flag rather than thread-level. Once a process becomes\n// multi-tenant it cannot go back, since secrets could persist in memory.\nvoid setMultiTenantProcess();\n\n// Tracks whether the process should run in \"predictable mode\" for testing purposes. This causes\n// random number generators to return static results instead, changes some timers to return zero,\n// etc. This should only be used in tests.\nbool isPredictableModeForTest();\n\n// Tracks whether the process should run in \"predictable mode\" for testing purposes. This causes\n// random number generators to return static results instead, changes some timers to return zero,\n// etc. This should only be used in tests.\nvoid setPredictableModeForTest();\n\n// RAII class which allows the thread's active watchdog to observe forward progress through\n// changes in a uint64_t. Use this in places where your code cannot call Watchdog::checkIn() and\n// may block for longer than the watchdog timeout, but can still observe forward progress.\nclass ThreadProgressCounter {\n public:\n  // When a ProgressCounter is instantiated, it saves the current value of `counter`. When\n  // Watchdog::tryHandleSignal() is called with an active ProgressCounter on the thread, the\n  // function compares this saved value with the (possibly updated) current value. If they differ,\n  // we consider the process to have exhibited forward progress. Note that we don't make any\n  // assumption of a less-than relationship between consecutive counter values -- you could use\n  // random values if you want.\n  //\n  // It is expected that all read/write operations to `counter` are atomic.\n  //\n  // ProgressCounters are reentrant, like v8::Lockers.\n  explicit ThreadProgressCounter(uint64_t& counter);\n\n  ~ThreadProgressCounter() noexcept(false);\n  KJ_DISALLOW_COPY_AND_MOVE(ThreadProgressCounter);\n\n  // Returns true if progress has been made since the last call to update().\n  static bool hasProgress();\n\n  // Updates the saved progress value so that hasProgress() now returns false until the next time\n  // the counter is updated.\n  static void acknowledgeProgress();\n\n private:\n  uint64_t savedValue;\n  uint64_t& counter;\n\n  friend class Watchdog;\n};\n\n// ======================================================================================\n\n// Create on stack in scopes where any attempt to take an isolate lock should log a warning.\n// Isolate locks can block for a relatively long time, so we especially try to avoid taking\n// them while any other locks are held.\nclass WarnAboutIsolateLockScope {\n public:\n  WarnAboutIsolateLockScope();\n  ~WarnAboutIsolateLockScope() noexcept(false);\n  KJ_DISALLOW_COPY(WarnAboutIsolateLockScope);\n  WarnAboutIsolateLockScope(WarnAboutIsolateLockScope&&);\n  void release();\n\n  static void maybeWarn();\n\n private:\n  bool released = false;\n};\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/uncaught-exception-source.h",
    "content": "#pragma once\n\n#include <kj/string.h>\n\nnamespace workerd {\n\nenum class UncaughtExceptionSource {\n  INTERNAL,\n  INTERNAL_ASYNC,\n  // We catch, log, and rethrow some exceptions at these intermediate levels, in case higher-level\n  // handlers fail.\n\n  ASYNC_TASK,\n  REQUEST_HANDLER,\n  TRACE_HANDLER,\n  ALARM_HANDLER,\n};\n\ninline kj::StringPtr KJ_STRINGIFY(UncaughtExceptionSource value) {\n  switch (value) {\n    case UncaughtExceptionSource::INTERNAL:\n      return \"Uncaught\"_kj;\n    case UncaughtExceptionSource::INTERNAL_ASYNC:\n      return \"Uncaught (in promise)\"_kj;\n    case UncaughtExceptionSource::ASYNC_TASK:\n      return \"Uncaught (async)\"_kj;\n    case UncaughtExceptionSource::REQUEST_HANDLER:\n      return \"Uncaught (in response)\"_kj;\n    case UncaughtExceptionSource::TRACE_HANDLER:\n      return \"Uncaught (in trace)\"_kj;\n    case UncaughtExceptionSource::ALARM_HANDLER:\n      return \"Uncaught (in alarm)\"_kj;\n  };\n  KJ_UNREACHABLE;\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/use-perfetto-categories.h",
    "content": "#pragma once\n\n#include <workerd/util/perfetto-tracing.h>\n\n// This header is to be imported by any translation unit (c++ file) that\n// is instrumenting with perfetto traces (e.g. TRACE_EVENT). Header files\n// that include instrumentation but are imported by embedders should instead\n// use PERFETTO_USE_CATEGORY_FROM_NAMESPACE_SCOPED within function or block\n// scopes that are to be instrumented.\n\n#ifdef WORKERD_USE_PERFETTO\nPERFETTO_USE_CATEGORIES_FROM_NAMESPACE(workerd::traces);\n#endif\n"
  },
  {
    "path": "src/workerd/util/uuid-test.c++",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"uuid.h\"\n\n#include <kj/test.h>\n\nnamespace workerd {\nnamespace {\n\n#define ASSERT_VALID_AND_EQUAL(upper, lower, str)                                                  \\\n  do {                                                                                             \\\n    auto a = KJ_ASSERT_NONNULL(UUID::fromUpperLower(upper, lower));                                \\\n    auto b = KJ_ASSERT_NONNULL(UUID::fromString(str));                                             \\\n    KJ_ASSERT(a.getUpper() == upper);                                                              \\\n    KJ_ASSERT(a.getLower() == lower);                                                              \\\n    KJ_ASSERT(b.getUpper() == upper);                                                              \\\n    KJ_ASSERT(b.getLower() == lower);                                                              \\\n    KJ_ASSERT(a == b);                                                                             \\\n  } while (false)\n\nKJ_TEST(\"Valid UUIDs\") {\n  ASSERT_VALID_AND_EQUAL(\n      72340172838076673ull, 1157442765409226768ull, \"01010101-0101-0101-1010-101010101010\");\n  ASSERT_VALID_AND_EQUAL(\n      81985529216486895ull, 81985529216486895ull, \"01234567-89ab-cdef-0123-456789abcdef\");\n  ASSERT_VALID_AND_EQUAL(\n      16045690984833335023ull, 16045690984833335023ull, \"deadbeef-dead-beef-dead-beefdeadbeef\");\n}\n\nKJ_TEST(\"Null UUIDs\") {\n  KJ_EXPECT(UUID::fromUpperLower(0ull, 0ull) == kj::none);\n  KJ_EXPECT(UUID::fromString(\"00000000-0000-0000-0000-000000000000\") == kj::none);\n}\n\nKJ_TEST(\"Invalid UUIDs\") {\n  KJ_EXPECT(UUID::fromString(\"\") == kj::none);\n  KJ_EXPECT(UUID::fromString(\"foo\") == kj::none);\n  KJ_EXPECT(UUID::fromString(\"+_{};'<>?,.`/'!@#$%^&*()\") == kj::none);\n  KJ_EXPECT(UUID::fromString(\"101010101-0101-0101-1010-101010101010\") == kj::none);\n  KJ_EXPECT(UUID::fromString(\"01010101-10101-0101-1010-101010101010\") == kj::none);\n  KJ_EXPECT(UUID::fromString(\"01010101-0101-10101-1010-101010101010\") == kj::none);\n  KJ_EXPECT(UUID::fromString(\"01010101-0101-0101-10101-101010101010\") == kj::none);\n  KJ_EXPECT(UUID::fromString(\"01010101-0101-0101-1010-1010101010101\") == kj::none);\n  KJ_EXPECT(UUID::fromString(\"01010101-0101-0101-1010-101010101010-\") == kj::none);\n  KJ_EXPECT(UUID::fromString(\"01010101-0101-0101-1010-10101010101-\") == kj::none);\n  KJ_EXPECT(UUID::fromString(\"01010101-0101-0101-1010-10101010101g\") == kj::none);\n  KJ_EXPECT(UUID::fromString(\"0123456789abcdef0123456789abcdef\") == kj::none);\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/uuid.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"uuid.h\"\n\n#include <workerd/util/entropy.h>\n\n#include <kj/compat/http.h>\n#include <kj/debug.h>\n\n#include <cstdlib>\n\nnamespace workerd {\nnamespace {\nconstexpr char HEX_DIGITS[] = \"0123456789abcdef\";\n}  // namespace\n\nkj::String randomUUID(kj::Maybe<kj::EntropySource&> optionalEntropySource) {\n  kj::FixedArray<kj::byte, 16> buffer;\n\n  KJ_IF_SOME(entropySource, optionalEntropySource) {\n    entropySource.generate(buffer);\n  } else {\n    getEntropy(buffer);\n  }\n  buffer[6] = static_cast<kj::byte>((buffer[6] & 0x0f) | 0x40);\n  buffer[8] = static_cast<kj::byte>((buffer[8] & 0x3f) | 0x80);\n\n#define HEX(b) (char)(HEX_DIGITS[(b >> 4) & 0xf]), (char)(HEX_DIGITS[b & 0xf])\n\n  // The format for Random UUID's is established in\n  // https://www.rfc-editor.org/rfc/rfc4122.txt\n  // xxxxxxxx-xxxx-Axxx-Bxxx-xxxxxxxxxxxx\n  //\n  // The sequence is 16 hex-encoded random bytes\n  // divided into 1 4-byte, 3 2-byte, and 1 6-byte\n  // groups. The four most significant bits of the\n  // 7th byte (A) are set to 0100xxxx (0x40), while\n  // the two most significant bits of the 9th byte (B)\n  // are set to 10xxxxxx (0x80). These are the key bits\n  // that identify the type and version of the uuid.\n  // All other bits are random. That ends up meaning\n  // that in the serialized uuid, the first character\n  // of the third grouping is always a 4, and the first\n  // character of the fourth grouping is always either\n  // an a, b, 8, or 9.\n\n  // clang-format off\n  return kj::String(kj::arr<char>(\n    HEX(buffer[0]),\n    HEX(buffer[1]),\n    HEX(buffer[2]),\n    HEX(buffer[3]),\n    '-',\n    HEX(buffer[4]),\n    HEX(buffer[5]),\n    '-',\n    HEX(buffer[6]),\n    HEX(buffer[7]),\n    '-',\n    HEX(buffer[8]),\n    HEX(buffer[9]),\n    '-',\n    HEX(buffer[10]),\n    HEX(buffer[11]),\n    HEX(buffer[12]),\n    HEX(buffer[13]),\n    HEX(buffer[14]),\n    HEX(buffer[15]),\n    '\\0'\n  ));\n  // clang-format on\n\n#undef HEX\n}\n\nkj::Maybe<UUID> UUID::fromUpperLower(uint64_t upper, uint64_t lower) {\n  if (upper == 0 && lower == 0) {\n    return kj::none;\n  }\n  return UUID(upper, lower);\n}\n\nkj::Maybe<UUID> UUID::fromString(kj::StringPtr str) {\n  if (str.size() != 36u) {\n    return kj::none;\n  }\n  uint64_t upper = 0;\n  uint64_t lower = 0;\n  auto begin = str.cStr();\n  char* p;\n  upper += (strtoull(begin, &p, 16) << 32u);\n  if (p - begin != 8 || *p != '-') {\n    return kj::none;\n  }\n  upper += (strtoull(++p, &p, 16) << 16u);\n  if (p - begin != 13 || *p != '-') {\n    return kj::none;\n  }\n  upper += (strtoull(++p, &p, 16));\n  if (p - begin != 18 || *p != '-') {\n    return kj::none;\n  }\n  lower += (strtoull(++p, &p, 16) << 48u);\n  if (p - begin != 23 || *p != '-') {\n    return kj::none;\n  }\n  lower += (strtoull(++p, &p, 16));\n  if (p - begin != 36) {\n    return kj::none;\n  }\n  if (upper == 0 && lower == 0) {\n    return kj::none;\n  }\n  return UUID(upper, lower);\n}\n\nkj::String UUID::toString() const {\n  // clang-format off\n  return kj::str(\n    HEX_DIGITS[(upper >> 60u) & 0xf],\n    HEX_DIGITS[(upper >> 56u) & 0xf],\n    HEX_DIGITS[(upper >> 52u) & 0xf],\n    HEX_DIGITS[(upper >> 48u) & 0xf],\n    HEX_DIGITS[(upper >> 44u) & 0xf],\n    HEX_DIGITS[(upper >> 40u) & 0xf],\n    HEX_DIGITS[(upper >> 36u) & 0xf],\n    HEX_DIGITS[(upper >> 32u) & 0xf],\n    '-',\n    HEX_DIGITS[(upper >> 28u) & 0xf],\n    HEX_DIGITS[(upper >> 24u) & 0xf],\n    HEX_DIGITS[(upper >> 20u) & 0xf],\n    HEX_DIGITS[(upper >> 16u) & 0xf],\n    '-',\n    HEX_DIGITS[(upper >> 12u) & 0xf],\n    HEX_DIGITS[(upper >>  8u) & 0xf],\n    HEX_DIGITS[(upper >>  4u) & 0xf],\n    HEX_DIGITS[(upper >>  0u) & 0xf],\n    '-',\n    HEX_DIGITS[(lower >> 60u) & 0xf],\n    HEX_DIGITS[(lower >> 56u) & 0xf],\n    HEX_DIGITS[(lower >> 52u) & 0xf],\n    HEX_DIGITS[(lower >> 48u) & 0xf],\n    '-',\n    HEX_DIGITS[(lower >> 44u) & 0xf],\n    HEX_DIGITS[(lower >> 40u) & 0xf],\n    HEX_DIGITS[(lower >> 36u) & 0xf],\n    HEX_DIGITS[(lower >> 32u) & 0xf],\n    HEX_DIGITS[(lower >> 28u) & 0xf],\n    HEX_DIGITS[(lower >> 24u) & 0xf],\n    HEX_DIGITS[(lower >> 20u) & 0xf],\n    HEX_DIGITS[(lower >> 16u) & 0xf],\n    HEX_DIGITS[(lower >> 12u) & 0xf],\n    HEX_DIGITS[(lower >>  8u) & 0xf],\n    HEX_DIGITS[(lower >>  4u) & 0xf],\n    HEX_DIGITS[(lower >>  0u) & 0xf]\n  );\n  // clang-format on\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/uuid.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/hash.h>\n#include <kj/string.h>\n\n#include <cstdint>\n\nnamespace kj {\nclass EntropySource;\n}\n\nnamespace workerd {\n\n// Generates a random version 4 UUID using the given entropy source or a default\n// secure random number generator. Unless you pass in a predictable entropy\n// source, it is safe to assume that the output of this function is unique.\nkj::String randomUUID(kj::Maybe<kj::EntropySource&> optionalEntropySource);\n\n// A 128-bit universally unique identifier (UUID).\n//\n// A UUID can be created from and converted between between two formats:\n// 1. Upper/lower format: an \"upper\" field representing the most significant bits and \"lower\" field\n//    representings the least significant bits.\n// 2. Stringified 8-4-4-4-12 hex format.\n//\n// A \"null UUID\" (a UUID with a value of 0) is considered invalid and is not possible to create.\nclass UUID {\n public:\n  // Create a UUID from upper and lower parts. If the UUID would be null, return kj::none.\n  //\n  // For example, creating a UUID from upper and lower values of 81985529216486895 and\n  // 81985529216486895 respectively yields a UUID which stringifies to\n  // \"01234567-89ab-cdef-0123-456789abcdef\".\n  static kj::Maybe<UUID> fromUpperLower(uint64_t upper, uint64_t lower);\n\n  // Create a UUID from 8-4-4-4-12 hex format. If the provided string is not valid, or the UUID\n  // would be null, return kj::none.\n  static kj::Maybe<UUID> fromString(kj::StringPtr str);\n\n  uint64_t getUpper() const {\n    return upper;\n  }\n\n  uint64_t getLower() const {\n    return lower;\n  }\n\n  // Stringify the UUID to 8-4-4-4-12 hex format.\n  //\n  // Note that this is NOT just a debugging API. Its behavior is relied upon to implement\n  // user-facing APIs.\n  kj::String toString() const;\n\n  bool operator==(const UUID& other) const {\n    return upper == other.upper && lower == other.lower;\n  }\n\n  size_t hashCode() const {\n    return kj::hashCode(upper, lower);\n  }\n\n private:\n  uint64_t upper;\n  uint64_t lower;\n\n  UUID(uint64_t upper, uint64_t lower): upper(upper), lower(lower) {}\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/wait-list-test.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"wait-list.h\"\n\n#include <kj/test.h>\n#include <kj/thread.h>\n\nnamespace workerd {\nnamespace {\n\nKJ_TEST(\"CrossThreadWaitList\") {\n  auto doTest = [](const CrossThreadWaitList& list) {\n    kj::MutexGuarded<uint> ready;\n\n    auto threadFunc = [&]() noexcept {\n      kj::EventLoop loop;\n      kj::WaitScope ws(loop);\n\n      auto promise1 = list.addWaiter();\n      auto promise2 = list.addWaiter();\n\n      KJ_ASSERT(!promise1.poll(ws));\n      KJ_ASSERT(!promise2.poll(ws));\n      KJ_ASSERT(!list.isDone());\n\n      (*ready.lockExclusive())++;\n\n      promise1.wait(ws);\n      promise2.wait(ws);\n\n      KJ_ASSERT(list.isDone());\n    };\n\n    kj::Thread waiter1(threadFunc);\n    kj::Thread waiter2(threadFunc);\n    kj::Thread waiter3(threadFunc);\n\n    kj::Thread sender([&]() {\n      ready.when([](uint val) { return val == 3; }, [&](uint) {});\n      list.fulfill();\n    });\n  };\n\n  {\n    CrossThreadWaitList list;\n    doTest(list);\n  }\n  {\n    CrossThreadWaitList list({.useThreadLocalOptimization = true});\n    doTest(list);\n  }\n}\n\nKJ_TEST(\"CrossThreadWaitList exceptions\") {\n  auto doTest = [](const CrossThreadWaitList& list) {\n    kj::MutexGuarded<uint> ready;\n\n    auto threadFunc = [&]() noexcept {\n      kj::EventLoop loop;\n      kj::WaitScope ws(loop);\n\n      auto promise1 = list.addWaiter();\n      auto promise2 = list.addWaiter();\n\n      KJ_ASSERT(!promise1.poll(ws));\n      KJ_ASSERT(!promise2.poll(ws));\n      KJ_ASSERT(!list.isDone());\n\n      (*ready.lockExclusive())++;\n\n      promise1\n          .then([]() { KJ_FAIL_REQUIRE(\"didn't throw\"); }, [](kj::Exception&& e) {\n        KJ_ASSERT(e.getDescription() == \"foo\");\n      }).wait(ws);\n      promise2\n          .then([]() { KJ_FAIL_REQUIRE(\"didn't throw\"); }, [](kj::Exception&& e) {\n        KJ_ASSERT(e.getDescription() == \"foo\");\n      }).wait(ws);\n\n      KJ_ASSERT(list.isDone());\n    };\n\n    kj::Thread waiter1(threadFunc);\n    kj::Thread waiter2(threadFunc);\n    kj::Thread waiter3(threadFunc);\n\n    kj::Thread sender([&]() {\n      ready.when([](uint val) { return val == 3; }, [&](uint) {});\n      list.reject(KJ_EXCEPTION(FAILED, \"foo\"));\n    });\n  };\n\n  {\n    CrossThreadWaitList list;\n    doTest(list);\n  }\n  {\n    CrossThreadWaitList list({.useThreadLocalOptimization = true});\n    doTest(list);\n  }\n}\n\n}  // namespace\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/wait-list.c++",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#include \"wait-list.h\"\n\n#include <kj/debug.h>\n\nnamespace workerd {\n\nnamespace {\n// Optimization: If the same wait list is waited multiple times in the same thread, we want to\n// share the signal rather than send two cross-thread signals.\nstatic const kj::EventLoopLocal<CrossThreadWaitList::WaiterMap> threadLocalWaiters;\n\nvoid END_WAIT_LIST_CANCELER_STACK_START_CANCELEE_STACK() {}\n}  // namespace\n\nCrossThreadWaitList::CrossThreadWaitList(Options options)\n    : state(kj::atomicRefcounted<State>(options)) {}\n\nvoid CrossThreadWaitList::destroyed() {\n  if (!createdFulfiller) state->lostFulfiller();\n}\n\nCrossThreadWaitList::Waiter::Waiter(\n    const State& state, kj::Own<kj::CrossThreadPromiseFulfiller<void>> fulfillerArg)\n    : state(kj::atomicAddRef(state)),\n      fulfiller(kj::mv(fulfillerArg)) {\n  auto lock = state.waiters.lockExclusive();\n  if (__atomic_load_n(&state.done, __ATOMIC_ACQUIRE)) {\n    KJ_IF_SOME(e, state.exception) {\n      fulfiller->reject(kj::cp(e));\n    } else {\n      fulfiller->fulfill();\n    }\n  } else {\n    lock->add(*this);\n  }\n}\nCrossThreadWaitList::Waiter::~Waiter() noexcept(false) {\n  if (__atomic_load_n(&unlinked, __ATOMIC_ACQUIRE)) {\n    // No need to take a lock, already unlinked.\n    KJ_ASSERT(!link.isLinked());\n  } else {\n    auto lock = state->waiters.lockExclusive();\n    if (link.isLinked()) {\n      lock->remove(*this);\n    }\n  }\n\n  if (state->useThreadLocalOptimization) {\n    auto& entry = KJ_ASSERT_NONNULL(threadLocalWaiters->findEntry(state.get()));\n    KJ_ASSERT(entry.value == this);\n    threadLocalWaiters->erase(entry);\n  }\n}\n\nkj::Promise<void> CrossThreadWaitList::addWaiter() const {\n  if (__atomic_load_n(&state->done, __ATOMIC_ACQUIRE)) {\n    KJ_IF_SOME(e, state->exception) {\n      return kj::cp(e);\n    } else {\n      return kj::READY_NOW;\n    }\n  }\n\n  if (state->useThreadLocalOptimization) {\n    kj::Own<Waiter> ownWaiter;\n\n    auto& waiter = threadLocalWaiters->findOrCreate(\n        state.get(), [&]() -> CrossThreadWaitList::WaiterMap::Entry {\n      auto paf = kj::newPromiseAndCrossThreadFulfiller<void>();\n      ownWaiter = kj::refcounted<Waiter>(*state, kj::mv(paf.fulfiller));\n      ownWaiter->forkedPromise = paf.promise.fork();\n      return {state.get(), ownWaiter.get()};\n    });\n\n    if (ownWaiter.get() == nullptr) {\n      ownWaiter = kj::addRef(*waiter);\n    }\n\n    return waiter->forkedPromise.addBranch().attach(kj::mv(ownWaiter));\n  } else {\n    // No refcounting, no forked promise.\n    auto paf = kj::newPromiseAndCrossThreadFulfiller<void>();\n    auto waiter = kj::heap<Waiter>(*state, kj::mv(paf.fulfiller));\n    return paf.promise.attach(kj::mv(waiter));\n  }\n}\n\nkj::Own<kj::CrossThreadPromiseFulfiller<void>> CrossThreadWaitList::makeSeparateFulfiller() {\n  class FulfillerImpl final: public kj::CrossThreadPromiseFulfiller<void> {\n   public:\n    FulfillerImpl(kj::Own<const State> state): state(kj::mv(state)) {}\n    ~FulfillerImpl() noexcept(false) {\n      state->lostFulfiller();\n    }\n    void fulfill(kj::_::Void&&) const override {\n      state->fulfill();\n    }\n    void reject(kj::Exception&& exception) const override {\n      state->reject(kj::mv(exception));\n    }\n    bool isWaiting() const override {\n      // Note that it would be incorrect for isWaiting() to return false when `done` is false\n      // even if the waiter list is empty, because the waiter list could become non-empty later.\n      // In theory if we could determine that there will never be a waiter, then isWaiting()\n      // could return false.\n      return !__atomic_load_n(&state->done, __ATOMIC_ACQUIRE);\n    }\n\n   private:\n    kj::Own<const State> state;\n  };\n\n  KJ_REQUIRE(!createdFulfiller, \"makeSeparateFulfiller() can only be called once\");\n  createdFulfiller = true;\n  return kj::heap<FulfillerImpl>(kj::atomicAddRef(*state));\n}\n\nvoid CrossThreadWaitList::State::fulfill() const {\n  if (__atomic_load_n(&done, __ATOMIC_ACQUIRE)) return;\n  auto lock = waiters.lockExclusive();\n  if (done) return;\n  __atomic_store_n(&done, true, __ATOMIC_RELEASE);\n\n  for (auto& waiter: *lock) {\n    lock->remove(waiter);\n    waiter.fulfiller->fulfill();\n    __atomic_store_n(&waiter.unlinked, true, __ATOMIC_RELEASE);\n  }\n}\n\nvoid CrossThreadWaitList::State::reject(kj::Exception&& e) const {\n  if (__atomic_load_n(&done, __ATOMIC_ACQUIRE)) return;\n  auto lock = waiters.lockExclusive();\n  if (done) return;\n  auto& exceptionRef = exception.emplace(kj::mv(e));\n  __atomic_store_n(&done, true, __ATOMIC_RELEASE);\n\n  for (auto& waiter: *lock) {\n    lock->remove(waiter);\n    waiter.fulfiller->reject(kj::cp(exceptionRef));\n    __atomic_store_n(&waiter.unlinked, true, __ATOMIC_RELEASE);\n  }\n}\n\nvoid CrossThreadWaitList::State::lostFulfiller() const {\n  if (__atomic_load_n(&done, __ATOMIC_ACQUIRE)) return;\n  auto lock = waiters.lockExclusive();\n  if (done) return;\n  auto& exceptionRef = exception.emplace(kj::getDestructionReason(\n      reinterpret_cast<void*>(&END_WAIT_LIST_CANCELER_STACK_START_CANCELEE_STACK),\n      kj::Exception::Type::FAILED, __FILE__, __LINE__, \"wait list was never fulfilled\"_kj));\n  __atomic_store_n(&done, true, __ATOMIC_RELEASE);\n\n  if (!lock->empty()) {\n    for (auto& waiter: *lock) {\n      lock->remove(waiter);\n      waiter.fulfiller->reject(kj::cp(exceptionRef));\n      __atomic_store_n(&waiter.unlinked, true, __ATOMIC_RELEASE);\n    }\n  }\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/wait-list.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/async.h>\n#include <kj/list.h>\n#include <kj/map.h>\n#include <kj/mutex.h>\n\nnamespace workerd {\n\nusing kj::uint;\n\n// A class that allows multiple threads to wait for an event, and for any thread to later trigger\n// that event. This is like using kj::newPromiseAndCrossThreadFulfiller<void>() and forking the\n// promise, except:\n// * Normally, a ForkedPromise's addBranch() can only be called in the thread that created the\n//   fork. `CrossThreadWaitList` can be awaited from any thread.\n// * CrossThreadWaitList is one object, not a promise/fulfiller pair. In many use cases, this\n//   turns out to be most convenient. But if you want a separate fulfiller, you can call the\n//   `makeSeparateFulfiller()` method.\nclass CrossThreadWaitList {\n public:\n  struct Options {\n    // Enable this if it is common for there to be multiple waiters in the same thread. This avoids\n    // sending multiple cross-thread signals in this case, instead sending one signal that all\n    // waiters in the thread wait on.\n    bool useThreadLocalOptimization = false;\n  };\n\n  CrossThreadWaitList(): CrossThreadWaitList(Options()) {}\n  CrossThreadWaitList(Options options);\n  CrossThreadWaitList(CrossThreadWaitList&& other) = default;\n  ~CrossThreadWaitList() noexcept(false) {\n    // Check if moved away.\n    if (state.get() != nullptr) destroyed();\n  }\n\n  kj::Promise<void> addWaiter() const;\n\n  // Wake all current *and future* waiters.\n  void fulfill() const {\n    KJ_IREQUIRE(!createdFulfiller);\n    state->fulfill();\n  }\n\n  // Causes all past and future `addWaiter()` calls to reject with the given exception.\n  void reject(kj::Exception&& e) const {\n    KJ_IREQUIRE(!createdFulfiller);\n    state->reject(kj::mv(e));\n  }\n\n  // Has `fulfill()` or `reject()` been called? Of course, the caller should consider if\n  // `fulfill()` might be called in another thread concurrently.\n  bool isDone() const {\n    return __atomic_load_n(&state->done, __ATOMIC_ACQUIRE);\n  }\n\n  // Creates a PromiseFulfiller that will fulfill this wait list. Once this is called, it is no\n  // longer the CrossThreadWaitList's responsibility to fulfill the waiters.\n  //\n  // Arguably, we should always make people create a PromiseFulfiller-CrossThreadWaitList pair,\n  // like kj::newPromiseAndFulfiller, instead of having methods directly on CrossThreadWaitList\n  // to fulfill/reject. However, in practice, in many use cases the fulfiller would be stored\n  // right next to the wait list, so it's convenient to let people opt into having two parts\n  // explicitly.\n  kj::Own<kj::CrossThreadPromiseFulfiller<void>> makeSeparateFulfiller();\n\n private:\n  // Forward declare our private structs so we can name the Map for public use in the source file.\n  struct State;\n  struct Waiter;\n\n public:\n  using WaiterMap = kj::HashMap<const CrossThreadWaitList::State*, Waiter*>;\n\n private:\n  struct Waiter: public kj::Refcounted {\n    Waiter(const State& state, kj::Own<kj::CrossThreadPromiseFulfiller<void>> fulfiller);\n    ~Waiter() noexcept(false);\n\n    kj::Own<const State> state;\n    kj::Own<kj::CrossThreadPromiseFulfiller<void>> fulfiller;\n\n    // Protected by list mutex.\n    kj::ListLink<Waiter> link;\n\n    // Optimization: This is atomically set true when the waiter is removed from the list so that\n    // we don't have to redundantly take the lock.\n    bool unlinked = false;\n\n    // Only initialized if useThreadLocalOptimization is enabled.\n    kj::ForkedPromise<void> forkedPromise = nullptr;\n  };\n\n  struct State: public kj::AtomicRefcounted {\n    kj::MutexGuarded<kj::List<Waiter, &Waiter::link>> waiters;\n\n    const bool useThreadLocalOptimization = false;\n\n    // Atomically set true at the start of fulfill() or reject(). This can be checked before taking\n    // the lock, but if false, it must be checked again after taking the lock, to avoid a race.\n    mutable bool done = false;\n\n    // If `done` is true due to `reject()` being called, this is the exception. This field\n    // does not change after `done` is set true.\n    mutable kj::Maybe<kj::Exception> exception;\n\n    bool wakeNext() const;\n    void fulfill() const;\n    void reject(kj::Exception&& e) const;\n    void lostFulfiller() const;\n\n    explicit State(const Options& options)\n        : useThreadLocalOptimization(options.useThreadLocalOptimization) {}\n  };\n\n  kj::Own<const State> state;\n  bool createdFulfiller = false;\n\n  void destroyed();\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/weak-refs.h",
    "content": "#pragma once\n\n#include <kj/mutex.h>\n#include <kj/refcount.h>\n\nnamespace workerd {\n\n// Represents a weak reference back to an object that code can use as an indirect pointer when\n// they want to be able to race destruction safely. A caller wishing to use a weak reference to\n// the object should acquire a strong reference. It's always safe to invoke `tryAddStrongRef` to\n// try to obtain a strong reference of the underlying object. This is because the Object's\n// destructor will explicitly clear the underlying pointer that would be dereferenced by\n// `tryAddStrongRef`. This means that after the refcount reaches 0, `tryAddStrongRef` is always\n// still safe to invoke even if the underlying object memory has been deallocated (provided\n// ownership of the weak object reference is retained).\n// T must itself extend from kj::AtomicRefcounted\ntemplate <typename T>\nclass AtomicWeakRef final: public kj::AtomicRefcounted {\n public:\n  inline static kj::Own<const AtomicWeakRef<T>> wrap(T* this_) {\n    return kj::atomicRefcounted<AtomicWeakRef<T>>(this_);\n  }\n\n  inline explicit AtomicWeakRef(T* thisArg): this_(thisArg) {}\n\n  // This tries to materialize a strong reference to the owner. It will fail if the owner's\n  // refcount has already dropped to 0. As discussed in the class, the lifetime of this weak\n  // reference can exceed the lifetime of the object it's tracking.\n  inline kj::Maybe<kj::Own<const T>> tryAddStrongRef() const {\n    auto lock = this_.lockShared();\n    if (*lock == nullptr) return kj::none;\n    return kj::atomicAddRefWeak(**lock);\n  }\n\n  inline kj::Own<const AtomicWeakRef<T>> addRef() const {\n    return kj::atomicAddRef(*this);\n  }\n\n private:\n  kj::MutexGuarded<const T*> this_;\n\n  // This is invoked by the owner destructor to clear the pointer. That means that any racing\n  // code will never try to invoke `atomicAddRefWeak` on the instance any more. Any code racing\n  // in between the refcount dropping to 0 and the invalidation getting invoked will still fail\n  // to acquire a strong reference. Any code acquiring a strong reference prior to the refcount\n  // dropping to 0 will prevent invalidation until that extra reference is dropped.\n  inline void invalidate() const {\n    *this_.lockExclusive() = nullptr;\n  }\n\n  friend T;\n};\n\n// A WeakRef is a weak reference to a thing. Note that because T may not itself be ref-counted,\n// we cannot follow the usual pattern of a weak reference that potentially converts to a strong\n// reference. Instead, intended usage looks like so:\n// ```\n//   kj::Own<WeakRef<Foo>> weakFoo = getWeakRefSomehow();\n//\n//   auto wasValid = weak->runIfAlive([](Foo& thing){\n//     // Use thing\n//   });\n// ```\n//\n// TODO(cleanup): It would eventually be nice to replace kj::Own<WeakRef<T>> with a\n// kj::WeakOwn<T> type with the same basic characteristics.\ntemplate <typename T>\nclass WeakRef final: public kj::Refcounted {\n public:\n  inline WeakRef(kj::Badge<T>, T& thing): maybeThing(thing) {}\n\n  // The use of the kj::Badge<T> in the constructor ensures that the initial instances\n  // of WeakRef<T> can only be created within an instance of T. The instance T is responsible\n  // for creating the initial refcounted kj::Own<WeakRef<T>>, and is responsible for calling\n  // invalidate() in the destructor.\n\n  KJ_DISALLOW_COPY_AND_MOVE(WeakRef);\n\n  // Run the functor and return true if the context is alive, otherwise return false. Note that\n  // since the `IoContext` might not be alive for any async continuation, we do not provide\n  // a `kj::Maybe<IoContext&> tryGet()` function. You are expected to invoke this function\n  // again in the next continuation to re-check if the `IoContext` is still around.\n  template <typename F>\n  inline bool runIfAlive(F&& f) const {\n    KJ_IF_SOME(thing, maybeThing) {\n      kj::fwd<F>(f)(thing);\n      return true;\n    }\n\n    return false;\n  }\n\n  // Note: This is safe to call on a const WeakRef because the WeakRef doesn't own\n  // the target - it just observes it. The returned reference allows mutation of\n  // the target, which is intentional (the target's lifetime is managed elsewhere).\n  inline kj::Maybe<T&> tryGet() const {\n    KJ_IF_SOME(thing, maybeThing) {\n      return thing;\n    }\n    return kj::none;\n  }\n  inline kj::Own<WeakRef> addRef() {\n    return kj::addRef(*this);\n  }\n  inline bool isValid() const {\n    return maybeThing != kj::none;\n  }\n\n private:\n  friend T;\n\n  inline void invalidate() {\n    maybeThing = kj::none;\n  }\n\n  kj::Maybe<T&> maybeThing;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/websocket-error-handler.c++",
    "content": "#include \"websocket-error-handler.h\"\n\n#include <workerd/jsg/exception.h>\n\n#include <kj/common.h>\n#include <kj/exception.h>\n#include <kj/string.h>\n\nnamespace workerd {\n\nkj::Exception JsgifyWebSocketErrors::handleWebSocketProtocolError(\n    kj::WebSocket::ProtocolError protocolError) {\n  kj::Exception baseExc =\n      kj::WebSocketErrorHandler::handleWebSocketProtocolError(kj::mv(protocolError));\n  auto newDescription = kj::str(JSG_EXCEPTION(Error), \": \", baseExc.getDescription());\n  return kj::Exception(\n      baseExc.getType(), baseExc.getFile(), baseExc.getLine(), kj::mv(newDescription));\n}\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/websocket-error-handler.h",
    "content": "#pragma once\n\n#include <kj/compat/http.h>\n#include <kj/exception.h>\n\nnamespace workerd {\nclass JsgifyWebSocketErrors final: public kj::WebSocketErrorHandler {\n public:\n  // Does what kj::WebSocketErrorHandler does, but formatted as a JSG exception.\n  kj::Exception handleWebSocketProtocolError(kj::WebSocket::ProtocolError protocolError) override;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/workerd/util/xthreadnotifier.h",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n#pragma once\n\n#include <kj/async.h>\n#include <kj/mutex.h>\n\nnamespace workerd {\n\nclass XThreadNotifier final: public kj::AtomicRefcounted {\n  // Class encapsulating the ability to notify a waiting thread from other threads.\n  //\n  // TODO(cleanup): Can this be consolidated with wait-list.h?\n  //\n  // TODO(cleanup): This could be a lot simpler if only it were possible to cancel\n  //   an executor.executeAsync() promise from an arbitrary thread. Then, if the inspector\n  //   session was destroyed in its thread while a cross-thread notification was in-flight, it\n  //   could cancel that notification directly.\n public:\n  static inline kj::Own<XThreadNotifier> create() {\n    return kj::atomicRefcounted<XThreadNotifier>();\n  }\n\n  XThreadNotifier(): paf(kj::newPromiseAndCrossThreadFulfiller<void>()) {}\n\n  kj::Promise<void> awaitNotification() {\n    auto promise = kj::mv(paf.lockExclusive()->promise);\n    co_await promise;\n    auto lockedPaf = paf.lockExclusive();\n    auto nextPaf = kj::newPromiseAndCrossThreadFulfiller<void>();\n    lockedPaf->promise = kj::mv(nextPaf.promise);\n    lockedPaf->fulfiller = kj::mv(nextPaf.fulfiller);\n  }\n\n  void notify() const {\n    paf.lockExclusive()->fulfiller->fulfill();\n  }\n\n private:\n  kj::MutexGuarded<kj::PromiseCrossThreadFulfillerPair<void>> paf;\n};\n\n}  // namespace workerd\n"
  },
  {
    "path": "src/wpt/BUILD.bazel",
    "content": "load(\"//:build/wd_ts_project.bzl\", \"wd_ts_project\")\nload(\"//:build/wpt_test.bzl\", \"wpt_test\")\n\nsrcs = glob(\n    [\n        \"**/*.ts\",\n    ],\n)\n\nfilegroup(\n    name = \"harness@js\",\n    srcs = [file.removesuffix(\".ts\") + \".js\" for file in glob([\"harness/*.ts\"])],\n)\n\nwd_ts_project(\n    name = \"wpt-all@tsproject\",\n    srcs = srcs,\n    eslintrc_json = \"eslint.config.mjs\",\n    source_map = False,\n    tsconfig_json = \"tsconfig.json\",\n    deps = [\n        \"//:node_modules/@types/node\",\n    ],\n)\n\nwpt_test(\n    name = \"url\",\n    config = \"url-test.ts\",\n    wpt_directory = \"@wpt//:url@module\",\n)\n\nwpt_test(\n    name = \"urlpattern\",\n    # We use a fixed compat date because new URLPattern implementation\n    # will be the default one on a later date.\n    compat_date = \"2025-04-09\",\n    config = \"urlpattern-test.ts\",\n    wpt_directory = \"@wpt//:urlpattern@module\",\n)\n\nwpt_test(\n    name = \"urlpattern-standard\",\n    config = \"urlpattern-standard-test.ts\",\n    wpt_directory = \"@wpt//:urlpattern@module\",\n)\n\nwpt_test(\n    name = \"dom/abort\",\n    config = \"dom/abort-test.ts\",\n    wpt_directory = \"@wpt//:dom/abort@module\",\n)\n\nwpt_test(\n    name = \"dom/events\",\n    config = \"dom/events-test.ts\",\n    wpt_directory = \"@wpt//:dom/events@module\",\n)\n\nwpt_test(\n    name = \"fetch/api\",\n    size = \"large\",\n    compat_flags = [\"strip_bom_in_read_all_text\"],\n    config = \"fetch/api-test.ts\",\n    start_server = True,\n    target_compatible_with = select({\n        # TODO(later): Provide a Windows version of the sidecar script we wrote to invoke wptserve.\n        # Currently we only have a Unix shell script.\n        \"@platforms//os:windows\": [\"@platforms//:incompatible\"],\n        \"//conditions:default\": [],\n    }),\n    wpt_directory = \"@wpt//:fetch/api@module\",\n)\n\nwpt_test(\n    name = \"encoding\",\n    compat_flags = [\n        \"encoder_stream_spec_compliant_backpressure\",\n        \"fixup-transform-stream-backpressure\",\n        \"text_decoder_cjk_decoder\",\n        \"transformstream_enable_standard_constructor\",\n    ],\n    config = \"encoding-test.ts\",\n    wpt_directory = \"@wpt//:encoding@module\",\n)\n\nwpt_test(\n    name = \"compression\",\n    config = \"compression-test.ts\",\n    start_server = True,\n    target_compatible_with = select({\n        \"@platforms//os:windows\": [\"@platforms//:incompatible\"],\n        \"//conditions:default\": [],\n    }),\n    wpt_directory = \"@wpt//:compression@module\",\n)\n\nwpt_test(\n    name = \"WebCryptoAPI\",\n    size = \"enormous\",\n    compat_flags = [\"disable_fast_jsg_struct\"],\n    config = \"WebCryptoAPI-test.ts\",\n    target_compatible_with = select({\n        # Too slow on Windows\n        \"@platforms//os:windows\": [\"@platforms//:incompatible\"],\n        \"//conditions:default\": [],\n    }),\n    wpt_directory = \"@wpt//:WebCryptoAPI@module\",\n)\n\nwpt_test(\n    name = \"streams\",\n    size = \"large\",\n    compat_flags = [\"writable_stream_spec_compliant_writer\"],\n    config = \"streams-test.ts\",\n    wpt_directory = \"@wpt//:streams@module\",\n)\n\nwpt_test(\n    name = \"fs\",\n    size = \"large\",\n    compat_flags = [\"enable_web_file_system\"],\n    config = \"fs-test.ts\",\n    wpt_directory = \"@wpt//:fs@module\",\n)\n\nwpt_test(\n    name = \"performance-timeline\",\n    config = \"performance-timeline-test.ts\",\n    wpt_directory = \"@wpt//:performance-timeline@module\",\n)\n\nwpt_test(\n    name = \"websockets\",\n    size = \"large\",\n    compat_flags = [\n        \"websocket_standard_binary_type\",\n        \"web_socket_auto_reply_to_close\",\n    ],\n    config = \"websockets-test.ts\",\n    start_server = True,\n    target_compatible_with = select({\n        # TODO(later): Provide a Windows version of the sidecar script we wrote to invoke wptserve.\n        # Currently we only have a Unix shell script.\n        \"@platforms//os:windows\": [\"@platforms//:incompatible\"],\n        \"//conditions:default\": [],\n    }),\n    wpt_directory = \"@wpt//:websockets@module\",\n)\n\nwpt_test(\n    name = \"eventsource\",\n    size = \"large\",\n    config = \"eventsource-test.ts\",\n    start_server = True,\n    target_compatible_with = select({\n        # TODO(later): Provide a Windows version of the sidecar script we wrote to invoke wptserve.\n        # Currently we only have a Unix shell script.\n        \"@platforms//os:windows\": [\"@platforms//:incompatible\"],\n        \"//conditions:default\": [],\n    }),\n    wpt_directory = \"@wpt//:eventsource@module\",\n)\n\nwpt_test(\n    name = \"webidl\",\n    config = \"webidl-test.ts\",\n    wpt_directory = \"@wpt//:webidl@module\",\n)\n"
  },
  {
    "path": "src/wpt/WebCryptoAPI-test.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { type TestRunnerConfig } from 'harness/harness';\n\nexport default {\n  'algorithm-discards-context.https.window.js': {\n    comment: 'Secure context is only relevant in browsers',\n    omittedTests: true,\n  },\n  'crypto_key_cached_slots.https.any.js': {\n    comment: 'Investigate this',\n    expectedFailures: [\n      'CryptoKey.algorithm getter returns cached object',\n      'CryptoKey.usages getter returns cached object',\n    ],\n  },\n\n  'derive_bits_keys/argon2.js': {\n    comment: 'Argon2 is not supported',\n    omittedTests: true,\n  },\n  'derive_bits_keys/argon2_vectors.js': {\n    comment: 'Argon2 is not supported',\n    omittedTests: true,\n  },\n  'derive_bits_keys/cfrg_curves_bits.js': {},\n  'derive_bits_keys/cfrg_curves_bits_curve25519.https.any.js': {},\n  'derive_bits_keys/cfrg_curves_bits_fixtures.js': {},\n  'derive_bits_keys/cfrg_curves_keys.js': {},\n  'derive_bits_keys/cfrg_curves_keys_curve25519.https.any.js': {},\n  'derive_bits_keys/derive_key_and_encrypt.https.any.js': {},\n  'derive_bits_keys/derive_key_and_encrypt.js': {},\n  'derive_bits_keys/derived_bits_length.https.any.js': {},\n  'derive_bits_keys/derived_bits_length.js': {},\n  'derive_bits_keys/derived_bits_length_testcases.js': {\n    comment:\n      \"This is a resource file but it's not in the resources/ directory; no tests in here\",\n    omittedTests: true,\n  },\n  'derive_bits_keys/derived_bits_length_vectors.js': {},\n  'derive_bits_keys/ecdh_bits.https.any.js': {},\n  'derive_bits_keys/ecdh_bits.js': {},\n  'derive_bits_keys/ecdh_keys.https.any.js': {},\n  'derive_bits_keys/ecdh_keys.js': {},\n  'derive_bits_keys/hkdf.https.any.js': {\n    comment: 'Cannot cope with this many iterations, keeps timing out',\n    omittedTests: [/with 100000 iterations/],\n  },\n  'derive_bits_keys/hkdf.js': {},\n  'derive_bits_keys/hkdf_vectors.js': {},\n  'derive_bits_keys/pbkdf2.https.any.js': {\n    comment: 'Cannot cope with this many iterations, keeps timing out',\n    omittedTests: [/with 100000 iterations/],\n  },\n  'derive_bits_keys/pbkdf2.js': {},\n  'derive_bits_keys/pbkdf2_vectors.js': {},\n\n  'digest/digest.https.any.js': {\n    comment: 'They expect TypeError, we have NotSupportedError',\n    expectedFailures: [\n      'empty algorithm object with empty',\n      'empty algorithm object with short',\n      'empty algorithm object with medium',\n      'empty algorithm object with long',\n    ],\n  },\n\n  'encap_decap/ml_kem_encap_decap.js': {\n    comment: 'ML-KEM (post-quantum key encapsulation) is not supported',\n    omittedTests: true,\n  },\n  'encap_decap/ml_kem_vectors.js': {\n    comment: 'ML-KEM (post-quantum key encapsulation) is not supported',\n    omittedTests: true,\n  },\n\n  'encrypt_decrypt/aes.js': {},\n  'encrypt_decrypt/aes_cbc.https.any.js': {},\n  'encrypt_decrypt/aes_cbc_vectors.js': {},\n  'encrypt_decrypt/aes_ctr.https.any.js': {},\n  'encrypt_decrypt/aes_ctr_vectors.js': {},\n  'encrypt_decrypt/aes_gcm.https.any.js': {},\n  'encrypt_decrypt/aes_gcm_256_iv.https.any.js': {},\n  'encrypt_decrypt/aes_gcm_256_iv_fixtures.js': {},\n  'encrypt_decrypt/aes_gcm_96_iv_fixtures.js': {},\n  'encrypt_decrypt/aes_gcm_vectors.js': {},\n  'encrypt_decrypt/aes_ocb_fixtures.js': {\n    comment: 'AES-OCB is not supported',\n    omittedTests: true,\n  },\n  'encrypt_decrypt/aes_ocb_vectors.js': {\n    comment: 'AES-OCB is not supported',\n    omittedTests: true,\n  },\n  'encrypt_decrypt/rsa.js': {},\n  'encrypt_decrypt/rsa_oaep.https.any.js': {},\n  'encrypt_decrypt/rsa_vectors.js': {},\n\n  'generateKey/failures.js': {},\n  'generateKey/failures_AES-CBC.https.any.js': {\n    comment: 'Wrong type of error returned',\n    expectedFailures: [/^(Empty|Bad) algorithm:/],\n  },\n  'generateKey/failures_AES-CTR.https.any.js': {\n    comment: 'Wrong type of error returned',\n    expectedFailures: [/^(Empty|Bad) algorithm:/],\n  },\n  'generateKey/failures_AES-GCM.https.any.js': {\n    comment: 'Wrong type of error returned',\n    expectedFailures: [/^(Empty|Bad) algorithm:/],\n  },\n  'generateKey/failures_AES-KW.https.any.js': {\n    comment: 'Wrong type of error returned',\n    expectedFailures: [/^(Empty|Bad) algorithm:/],\n  },\n  'generateKey/failures_ECDH.https.any.js': {\n    comment: 'Wrong type of error returned',\n    expectedFailures: [/^(Empty|Bad) algorithm:/],\n  },\n  'generateKey/failures_ECDSA.https.any.js': {\n    comment: 'Wrong type of error returned',\n    expectedFailures: [/^(Empty|Bad) algorithm:/],\n  },\n  'generateKey/failures_Ed25519.https.any.js': {\n    comment: 'Wrong type of error returned',\n    expectedFailures: [/^(Empty|Bad) algorithm:/],\n  },\n  'generateKey/failures_HMAC.https.any.js': {\n    comment: 'Wrong type of error returned',\n    expectedFailures: [/^(Empty|Bad) algorithm:/],\n  },\n  'generateKey/failures_RSA-OAEP.https.any.js': {\n    comment: 'Wrong type of error returned',\n    expectedFailures: [/^(Empty|Bad) algorithm:/],\n  },\n  'generateKey/failures_RSA-PSS.https.any.js': {\n    comment: 'Wrong type of error returned',\n    expectedFailures: [/^(Empty|Bad) algorithm:/],\n  },\n  'generateKey/failures_RSASSA-PKCS1-v1_5.https.any.js': {\n    comment: 'Wrong type of error returned',\n    expectedFailures: [/^(Empty|Bad) algorithm:/],\n  },\n  'generateKey/failures_X25519.https.any.js': {\n    comment: 'Wrong type of error returned',\n    expectedFailures: [/^(Empty|Bad) algorithm:/],\n  },\n  'generateKey/successes.js': {},\n  'generateKey/successes_AES-CBC.https.any.js': {\n    comment: 'TODO investigate this',\n    expectedFailures: [/^undefined: /],\n  },\n  'generateKey/successes_AES-CTR.https.any.js': {\n    comment: 'TODO investigate this',\n    expectedFailures: [/^undefined: /],\n  },\n  'generateKey/successes_AES-GCM.https.any.js': {\n    comment: 'TODO investigate this',\n    expectedFailures: [/^undefined: /],\n  },\n  'generateKey/successes_AES-KW.https.any.js': {\n    comment: 'TODO investigate this',\n    expectedFailures: [/^undefined: /],\n  },\n  'generateKey/successes_ECDH.https.any.js': {\n    comment: 'TODO investigate this',\n    expectedFailures: [/^undefined: /],\n  },\n  'generateKey/successes_ECDSA.https.any.js': {\n    comment: 'TODO investigate this',\n    expectedFailures: [/^undefined: /],\n  },\n  'generateKey/successes_Ed25519.https.any.js': {\n    comment: 'TODO investigate this',\n    expectedFailures: [/^undefined: /],\n  },\n  'generateKey/successes_HMAC.https.any.js': {\n    comment: 'TODO investigate this',\n    expectedFailures: [/^undefined: /],\n  },\n  'generateKey/successes_RSA-OAEP.https.any.js': {\n    comment: 'TODO investigate this',\n    expectedFailures: [/^undefined: /],\n  },\n  'generateKey/successes_RSA-PSS.https.any.js': {\n    comment: 'TODO investigate this',\n    expectedFailures: [/^undefined: /],\n  },\n  'generateKey/successes_RSASSA-PKCS1-v1_5.https.any.js': {\n    comment: 'TODO investigate this',\n    expectedFailures: [/^undefined: /],\n  },\n  'generateKey/successes_X25519.https.any.js': {\n    comment: 'TODO investigate this',\n    expectedFailures: [/^undefined: /],\n  },\n\n  'getRandomValues.any.js': {},\n  'historical.any.js': {\n    comment: 'Secure context is only relevant to browsers',\n    omittedTests: [\n      'Non-secure context window does not have access to crypto.subtle',\n      'Non-secure context window does not have access to SubtleCrypto',\n      'Non-secure context window does not have access to CryptoKey',\n    ],\n  },\n  'idlharness.https.any.js': {\n    comment:\n      'IDL tests fail because Workers exposes globals differently than browsers (not as own properties of self)',\n    expectedFailures: [\n      'CryptoKey interface: attribute type',\n      'CryptoKey interface: attribute extractable',\n      'CryptoKey interface: attribute algorithm',\n      'CryptoKey interface: attribute usages',\n      'SubtleCrypto interface: operation encrypt(AlgorithmIdentifier, CryptoKey, BufferSource)',\n      'SubtleCrypto interface: operation decrypt(AlgorithmIdentifier, CryptoKey, BufferSource)',\n      'SubtleCrypto interface: operation sign(AlgorithmIdentifier, CryptoKey, BufferSource)',\n      'SubtleCrypto interface: operation verify(AlgorithmIdentifier, CryptoKey, BufferSource, BufferSource)',\n      'SubtleCrypto interface: operation digest(AlgorithmIdentifier, BufferSource)',\n      'SubtleCrypto interface: operation generateKey(AlgorithmIdentifier, boolean, sequence<KeyUsage>)',\n      'SubtleCrypto interface: operation deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, sequence<KeyUsage>)',\n      'SubtleCrypto interface: operation deriveBits(AlgorithmIdentifier, CryptoKey, optional unsigned long?)',\n      'SubtleCrypto interface: operation importKey(KeyFormat, (BufferSource or JsonWebKey), AlgorithmIdentifier, boolean, sequence<KeyUsage>)',\n      'SubtleCrypto interface: operation exportKey(KeyFormat, CryptoKey)',\n      'SubtleCrypto interface: operation wrapKey(KeyFormat, CryptoKey, CryptoKey, AlgorithmIdentifier)',\n      'SubtleCrypto interface: operation unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, sequence<KeyUsage>)',\n      'Window interface: attribute crypto',\n    ],\n  },\n\n  'import_export/ML-DSA_importKey.js': {\n    comment: 'ML-DSA (post-quantum signature algorithm) is not supported',\n    omittedTests: true,\n  },\n  'import_export/ML-DSA_importKey_fixtures.js': {\n    comment: 'ML-DSA (post-quantum signature algorithm) is not supported',\n    omittedTests: true,\n  },\n  'import_export/ML-KEM_importKey.js': {\n    comment: 'ML-KEM (post-quantum key encapsulation) is not supported',\n    omittedTests: true,\n  },\n  'import_export/ML-KEM_importKey_fixtures.js': {\n    comment: 'ML-KEM (post-quantum key encapsulation) is not supported',\n    omittedTests: true,\n  },\n  'import_export/crashtests/importKey-unsettled-promise.https.any.js': {},\n  'import_export/ec_importKey.https.any.js': {},\n  'import_export/ec_importKey_failures_ECDH.https.any.js': {\n    comment:\n      'OpenSSL call failed: EC_POINT_set_affine_coordinates_GFp(group, point, bigX, bigY, nullptr);',\n    expectedFailures: [\n      /^Bad key length:/,\n      /^Missing JWK 'crv' parameter:/,\n      \"Invalid 'crv' field: importKey(jwk(private), {name: ECDH, namedCurve: P-256}, true, [deriveKey, deriveBits])\",\n      \"Invalid 'crv' field: importKey(jwk(private), {name: ECDH, namedCurve: P-384}, true, [deriveKey, deriveBits])\",\n      \"Invalid 'crv' field: importKey(jwk(private), {name: ECDH, namedCurve: P-521}, true, [deriveKey, deriveBits])\",\n    ],\n  },\n  'import_export/ec_importKey_failures_ECDSA.https.any.js': {\n    comment:\n      'OpenSSL call failed: EC_POINT_set_affine_coordinates_GFp(group, point, bigX, bigY, nullptr);',\n    expectedFailures: [\n      /^Bad key length:/,\n      /^Missing JWK 'crv' parameter:/,\n      \"Invalid 'crv' field: importKey(jwk(private), {name: ECDSA, namedCurve: P-256}, true, [sign])\",\n      \"Invalid 'crv' field: importKey(jwk(private), {name: ECDSA, namedCurve: P-384}, true, [sign])\",\n      \"Invalid 'crv' field: importKey(jwk(private), {name: ECDSA, namedCurve: P-521}, true, [sign])\",\n    ],\n  },\n  'import_export/ec_importKey_failures_fixtures.js': {},\n  'import_export/importKey_failures.js': {},\n  'import_export/okp_importKey.js': {},\n  'import_export/okp_importKey_Ed25519.https.any.js': {\n    comment: 'Investigate this',\n    expectedFailures: [\n      'Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify])',\n      'Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [verify])',\n      'Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [])',\n      'Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [])',\n      'Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify, verify])',\n      'Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [verify, verify])',\n      'Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign])',\n      'Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(crv, d, x, kty), Ed25519, true, [sign])',\n      'Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign, sign])',\n      'Good parameters with JWK alg Ed25519: Ed25519 (jwk, object(crv, d, x, kty), Ed25519, true, [sign, sign])',\n      'Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify])',\n      'Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [verify])',\n      'Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [])',\n      'Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [])',\n      'Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(kty, crv, x), {name: Ed25519}, true, [verify, verify])',\n      'Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(kty, crv, x), Ed25519, true, [verify, verify])',\n      'Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign])',\n      'Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(crv, d, x, kty), Ed25519, true, [sign])',\n      'Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(crv, d, x, kty), {name: Ed25519}, true, [sign, sign])',\n      'Good parameters with JWK alg EdDSA: Ed25519 (jwk, object(crv, d, x, kty), Ed25519, true, [sign, sign])',\n    ],\n  },\n  'import_export/okp_importKey_X25519.https.any.js': {},\n  'import_export/okp_importKey_failures_Ed25519.https.any.js': {\n    comment:\n      'To be investigated - workerd does not reject these invalid key pairs',\n    expectedFailures: [\n      /Invalid key pair: importKey\\(jwk\\(private\\), .*, true, \\[sign\\]\\)/,\n      /Invalid key pair: importKey\\(jwk\\(private\\), .*, true, \\[sign, sign\\]\\)/,\n      /Invalid 'crv' field: importKey\\(jwk\\(private\\), .*, true, \\[sign\\]\\)/,\n      /Invalid 'crv' field: importKey\\(jwk \\(public\\) , .*, true, \\[verify\\]\\)/,\n    ],\n  },\n  'import_export/okp_importKey_failures_X25519.https.any.js': {\n    comment:\n      'To be investigated - workerd does not reject these invalid key pairs',\n    expectedFailures: [\n      /Invalid key pair: importKey\\(jwk\\(private\\), .*, true, \\[deriveKey\\]\\)/,\n      /Invalid key pair: importKey\\(jwk\\(private\\), .*, true, \\[deriveBits, deriveKey\\]\\)/,\n      /Invalid key pair: importKey\\(jwk\\(private\\), .*, true, \\[deriveBits\\]\\)/,\n      /Invalid key pair: importKey\\(jwk\\(private\\), .*, true, \\[deriveKey, deriveBits, deriveKey, deriveBits\\]\\)/,\n      /Invalid 'crv' field: importKey\\(jwk\\(private\\), .*, true, \\[deriveKey, deriveBits\\]\\)/,\n      /Invalid 'crv' field: importKey\\(jwk \\(public\\) , .*, true, \\[\\]\\)/,\n    ],\n  },\n  'import_export/okp_importKey_failures_fixtures.js': {},\n  'import_export/okp_importKey_fixtures.js': {},\n  'import_export/rsa_importKey.https.any.js': {},\n  'import_export/symmetric_importKey.https.any.js': {},\n  'import_export/symmetric_importKey.js': {},\n\n  'randomUUID.https.any.js': {},\n\n  'sign_verify/ecdsa.https.any.js': {},\n  'sign_verify/ecdsa.js': {},\n  'sign_verify/ecdsa_vectors.js': {},\n  'sign_verify/eddsa.js': {},\n  'sign_verify/eddsa_curve25519.https.any.js': {\n    comment: 'To be investigated',\n    expectedFailures: [\n      'EdDSA Ed25519 verification failure due to shortened signature',\n    ],\n  },\n  'sign_verify/eddsa_small_order_points.https.any.js': {\n    comment: 'To be investigated',\n    expectedFailures: [\n      'Ed25519 Verification checks with small-order key of order - Test 0',\n      'Ed25519 Verification checks with small-order key of order - Test 1',\n      'Ed25519 Verification checks with small-order key of order - Test 2',\n      'Ed25519 Verification checks with small-order key of order - Test 7',\n      'Ed25519 Verification checks with small-order key of order - Test 11',\n      'Ed25519 Verification checks with small-order key of order - Test 12',\n      'Ed25519 Verification checks with small-order key of order - Test 13',\n    ],\n  },\n  'sign_verify/eddsa_small_order_points.js': {},\n  'sign_verify/eddsa_vectors.js': {},\n  'sign_verify/hmac.https.any.js': {},\n  'sign_verify/hmac.js': {},\n  'sign_verify/hmac_vectors.js': {},\n  'sign_verify/kmac.js': {\n    comment: 'KMAC is not supported',\n    omittedTests: true,\n  },\n  'sign_verify/kmac_vectors.js': {\n    comment: 'KMAC is not supported',\n    omittedTests: true,\n  },\n  'sign_verify/mldsa.js': {\n    comment: 'ML-DSA (post-quantum signature algorithm) is not supported',\n    omittedTests: true,\n  },\n  'sign_verify/mldsa_vectors.js': {\n    comment: 'ML-DSA (post-quantum signature algorithm) is not supported',\n    omittedTests: true,\n  },\n  'sign_verify/rsa.js': {},\n  'sign_verify/rsa_pkcs.https.any.js': {},\n  'sign_verify/rsa_pkcs_vectors.js': {},\n  'sign_verify/rsa_pss.https.any.js': {},\n  'sign_verify/rsa_pss_vectors.js': {},\n\n  'util/helpers.js': {},\n  'util/worker-report-crypto-subtle-presence.js': {\n    comment: 'ReferenceError: postMessage is not defined',\n    disabledTests: true,\n  },\n\n  'wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.js': {},\n  'wrapKey_unwrapKey/wrapKey_unwrapKey_vectors.js': {},\n} satisfies TestRunnerConfig;\n"
  },
  {
    "path": "src/wpt/compression-test.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { type TestRunnerConfig } from 'harness/harness';\n\nexport default {\n  'compression-bad-chunks.any.js': {\n    comment: 'Test times out - needs investigation',\n    disabledTests: true,\n  },\n  'compression-constructor-error.any.js': {},\n  'compression-including-empty-chunk.any.js': {\n    comment: 'brotli compression is not supported',\n    expectedFailures: [\n      \"the result of compressing [,Hello,Hello] with brotli should be 'HelloHello'\",\n      \"the result of compressing [Hello,,Hello] with brotli should be 'HelloHello'\",\n      \"the result of compressing [Hello,Hello,] with brotli should be 'HelloHello'\",\n    ],\n  },\n  'compression-large-flush-output.any.js': {\n    comment: 'brotli compression is not supported',\n    expectedFailures: ['brotli compression with large flush output'],\n  },\n  'compression-multiple-chunks.any.js': {\n    comment: 'brotli compression is not supported',\n    expectedFailures: [/compressing \\d+ chunks with brotli should work/],\n  },\n  'compression-output-length.any.js': {\n    comment: 'brotli compression is not supported',\n    expectedFailures: [\n      'the length of brotli data should be shorter than that of the original data',\n    ],\n  },\n  'compression-stream.any.js': {\n    comment: 'brotli compression is not supported',\n    expectedFailures: [\n      /brotli .* data should be reinflated back to its origin/,\n    ],\n  },\n  'compression-with-detach.window.js': {},\n  'decompression-bad-chunks.any.js': {\n    comment: 'brotli compression is not supported',\n    expectedFailures: [/brotli/],\n  },\n  'decompression-buffersource.any.js': {\n    comment: 'brotli compression is not supported',\n    expectedFailures: [/brotli/],\n  },\n  'decompression-constructor-error.any.js': {\n    comment:\n      'brotli compression is not supported - these pass because brotli throws',\n  },\n  'decompression-correct-input.any.js': {\n    comment: 'brotli compression is not supported',\n    expectedFailures: [/.*brotli.*/],\n  },\n  'decompression-corrupt-input.any.js': {\n    comment: 'brotli compression is not supported',\n    expectedFailures: [/brotli/],\n  },\n  'decompression-empty-input.any.js': {\n    comment: 'brotli compression is not supported',\n    expectedFailures: [/.*brotli.*/],\n  },\n  'decompression-extra-input.any.js': {\n    comment:\n      'Extra padding tests fail - workerd handles trailing data differently',\n    expectedFailures: [\n      'decompressing deflate input with extra pad should still give the output',\n      'decompressing gzip input with extra pad should still give the output',\n      'decompressing deflate-raw input with extra pad should still give the output',\n      /brotli/,\n    ],\n  },\n  'decompression-split-chunk.any.js': {\n    comment: 'brotli compression is not supported',\n    expectedFailures: [/.*brotli/],\n  },\n  'decompression-uint8array-output.any.js': {\n    comment: 'brotli compression is not supported',\n    expectedFailures: [\n      'decompressing brotli output should give Uint8Array chunks',\n    ],\n  },\n  'decompression-with-detach.window.js': {\n    comment: 'Detach test fails - needs investigation',\n    expectedFailures: [\n      'data should be correctly decompressed even if input is detached partway',\n    ],\n  },\n  'idlharness.https.any.js': {\n    comment:\n      'Workers expose globals differently than browsers - readable/writable attribute tests still fail',\n    expectedFailures: [\n      'CompressionStream interface: existence and properties of interface prototype object',\n      'DecompressionStream interface: existence and properties of interface prototype object',\n    ],\n  },\n  'third_party/pako/pako_inflate.min.js': {},\n} satisfies TestRunnerConfig;\n"
  },
  {
    "path": "src/wpt/dom/abort-test.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { type TestRunnerConfig } from 'harness/harness';\n\nexport default {\n  'AbortSignal.any.js': {},\n  'abort-signal-any.any.js': {\n    comment: 'Order of event firing should be investigated.',\n    expectedFailures: [\n      'Abort events for AbortSignal.any() signals fire in the right order (using AbortController)',\n    ],\n  },\n  'event.any.js': {},\n  'timeout-shadowrealm.any.js': {\n    comment: 'Enable when ShadowRealm is implemented',\n    disabledTests: true,\n  },\n  'timeout.any.js': {},\n} satisfies TestRunnerConfig;\n"
  },
  {
    "path": "src/wpt/dom/events-test.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { type TestRunnerConfig } from 'harness/harness';\n\nexport default {\n  'AddEventListenerOptions-once.any.js': {},\n  'AddEventListenerOptions-passive.any.js': {\n    comment: 'TODO investigate this',\n    expectedFailures: [\n      'preventDefault should be ignored if-and-only-if the passive option is true',\n      'passive behavior of one listener should be unaffected by the presence of other listeners',\n      'Equivalence of option values',\n      'returnValue should be ignored if-and-only-if the passive option is true',\n    ],\n  },\n  'AddEventListenerOptions-signal.any.js': {\n    comment: 'capture is not relevant outside of the DOM',\n    omittedTests: [\n      'Passing an AbortSignal to addEventListener works with the capture flag',\n    ],\n  },\n  'Event-constructors.any.js': {\n    comment: 'We force timeStamp to 0.0 to avoid timing side-channels',\n    expectedFailures: [/^$/],\n  },\n  'Event-dispatch-listener-order.window.js': {\n    comment: 'Test requires DOM',\n    // There is a single test, whose name is the empty string\n    omittedTests: [''],\n  },\n  'Event-isTrusted.any.js': {\n    comment: 'isTrusted must be an own property, not a prototype property',\n    // With the pedantic_wpt compat flag we actually set the isTrusted property\n    // to be an own property. However, it's an own property that uses a value\n    // in the descriptor rather than a getter and the test is specifically looking\n    // for a getter for some reason. That's way more pedantic than we want to\n    // deal with so just omit the test for now.\n    omittedTests: [''],\n  },\n  'EventListener-addEventListener.sub.window.js': {\n    comment: 'Only relevant to browsers',\n    omittedTests: [\n      \"EventListener.addEventListener doesn't throw when a cross origin object is passed in.\",\n    ],\n  },\n  'EventTarget-add-remove-listener.any.js': {},\n  'EventTarget-addEventListener.any.js': {},\n  'EventTarget-constructible.any.js': {},\n  'EventTarget-removeEventListener.any.js': {\n    comment: 'capture is not relevant outside of the DOM',\n    expectedFailures: ['removing a null event listener should succeed'],\n  },\n  'event-global-extra.window.js': {\n    comment: 'Test is DOM-specific',\n    disabledTests: true,\n  },\n  'event-global-set-before-handleEvent-lookup.window.js': {\n    comment: 'window is not defined',\n    expectedFailures: [\"window.event is set before 'handleEvent' lookup\"],\n  },\n  'event-global.worker.js': {\n    comment: 'ReferenceError: importScripts is not defined',\n    disabledTests: true,\n  },\n  'legacy-pre-activation-behavior.window.js': {\n    comment: 'Only relevant to browsers',\n    omittedTests: ['Use NONE phase during legacy-pre-activation behavior'],\n  },\n  'relatedTarget.window.js': {\n    comment: 'Test is DOM-specific',\n    omittedTests: true,\n  },\n  'scrolling/scroll_support.js': {\n    comment: 'Only used by HTML files',\n    omittedTests: true,\n  },\n  'scrolling/scrollend-user-scroll-common.js': {\n    comment: 'Only used by HTML files',\n    omittedTests: true,\n  },\n} satisfies TestRunnerConfig;\n"
  },
  {
    "path": "src/wpt/encoding-test.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport path from 'node:path';\nimport { getBindingPath } from 'harness/common';\nimport { type TestRunnerConfig } from 'harness/harness';\n\nfunction loadWptResource(relativePath: string): void {\n  const bindingPath = getBindingPath(\n    path.dirname(globalThis.state.testFileName),\n    relativePath\n  );\n  const code = globalThis.state.env[bindingPath];\n  if (typeof code !== 'string') {\n    throw new Error(\n      `Test file ${bindingPath} not found. Update wpt_test.bzl to handle this case.`\n    );\n  }\n  globalThis.state.env.unsafe.eval(code);\n}\n\nexport default {\n  'api-basics.any.js': {},\n  'api-invalid-label.any.js': {},\n  'api-replacement-encodings.any.js': {},\n  'api-surrogates-utf8.any.js': {},\n  'encodeInto.any.js': {\n    comment: 'Requires MessageChannel.postMessage transfer list support',\n    expectedFailures: ['encodeInto() and a detached output buffer'],\n  },\n  'idlharness.any.js': {\n    comment:\n      'Workers expose globals differently than browsers - readable/writable attribute tests still fail',\n    expectedFailures: [\n      'TextDecoderStream interface: existence and properties of interface prototype object',\n      'TextEncoderStream interface: existence and properties of interface prototype object',\n    ],\n  },\n  'iso-2022-jp-decoder.any.js': {},\n  'legacy-mb-japanese/euc-jp/eucjp-decoder.js': {},\n  'legacy-mb-japanese/euc-jp/eucjp-encoder.js': {\n    before: (): void => {\n      loadWptResource('./jis0208_index.js');\n      loadWptResource('./jis0212_index.js');\n    },\n  },\n  'legacy-mb-japanese/euc-jp/jis0208_index.js': {},\n  'legacy-mb-japanese/euc-jp/jis0212_index.js': {},\n  'legacy-mb-japanese/iso-2022-jp/iso2022jp-decoder.js': {},\n  'legacy-mb-japanese/iso-2022-jp/iso2022jp-encoder.js': {\n    before: (): void => {\n      loadWptResource('./jis0208_index.js');\n    },\n  },\n  'legacy-mb-japanese/iso-2022-jp/jis0208_index.js': {},\n  'legacy-mb-japanese/shift_jis/jis0208_index.js': {},\n  'legacy-mb-japanese/shift_jis/sjis-decoder.js': {},\n  'legacy-mb-japanese/shift_jis/sjis-encoder.js': {\n    before: (): void => {\n      loadWptResource('./jis0208_index.js');\n    },\n  },\n  'legacy-mb-korean/euc-kr/euckr-decoder.js': {},\n  'legacy-mb-korean/euc-kr/euckr-encoder.js': {\n    before: (): void => {\n      loadWptResource('./euckr_index.js');\n    },\n  },\n  'legacy-mb-korean/euc-kr/euckr_index.js': {},\n  'legacy-mb-schinese/gb18030/gb18030-decoder.any.js': {},\n  'legacy-mb-schinese/gbk/gbk-decoder.any.js': {},\n  'legacy-mb-tchinese/big5/big5-decoder.js': {},\n  'legacy-mb-tchinese/big5/big5-encoder.js': {\n    before: (): void => {\n      loadWptResource('./big5_index.js');\n    },\n  },\n  'legacy-mb-tchinese/big5/big5_index.js': {},\n  'replacement-encodings.any.js': {\n    comment: 'XMLHttpRequest is not defined',\n    expectedFailures: [\n      'csiso2022kr - non-empty input decodes to one replacement character.',\n      'csiso2022kr - empty input decodes to empty output.',\n      'hz-gb-2312 - non-empty input decodes to one replacement character.',\n      'hz-gb-2312 - empty input decodes to empty output.',\n      'iso-2022-cn - non-empty input decodes to one replacement character.',\n      'iso-2022-cn - empty input decodes to empty output.',\n      'iso-2022-cn-ext - non-empty input decodes to one replacement character.',\n      'iso-2022-cn-ext - empty input decodes to empty output.',\n      'iso-2022-kr - non-empty input decodes to one replacement character.',\n      'iso-2022-kr - empty input decodes to empty output.',\n      'replacement - non-empty input decodes to one replacement character.',\n      'replacement - empty input decodes to empty output.',\n    ],\n  },\n  'single-byte-decoder.window.js': {\n    comment: 'TODO(soon): Investigate these',\n    expectedFailures: [\n      'IBM866: 866 (XMLHttpRequest)',\n      'IBM866: 866 (document.characterSet and document.inputEncoding)',\n      'IBM866: cp866 (XMLHttpRequest)',\n      'IBM866: cp866 (document.characterSet and document.inputEncoding)',\n      'IBM866: csibm866 (XMLHttpRequest)',\n      'IBM866: csibm866 (document.characterSet and document.inputEncoding)',\n      'IBM866: ibm866 (XMLHttpRequest)',\n      'IBM866: ibm866 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-2: csisolatin2 (XMLHttpRequest)',\n      'ISO-8859-2: csisolatin2 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-2: iso-8859-2 (XMLHttpRequest)',\n      'ISO-8859-2: iso-8859-2 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-2: iso-ir-101 (XMLHttpRequest)',\n      'ISO-8859-2: iso-ir-101 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-2: iso8859-2 (XMLHttpRequest)',\n      'ISO-8859-2: iso8859-2 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-2: iso88592 (XMLHttpRequest)',\n      'ISO-8859-2: iso88592 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-2: iso_8859-2 (XMLHttpRequest)',\n      'ISO-8859-2: iso_8859-2 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-2: iso_8859-2:1987 (XMLHttpRequest)',\n      'ISO-8859-2: iso_8859-2:1987 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-2: l2 (XMLHttpRequest)',\n      'ISO-8859-2: l2 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-2: latin2 (XMLHttpRequest)',\n      'ISO-8859-2: latin2 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-3: csisolatin3 (XMLHttpRequest)',\n      'ISO-8859-3: csisolatin3 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-3: iso-8859-3 (XMLHttpRequest)',\n      'ISO-8859-3: iso-8859-3 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-3: iso-ir-109 (XMLHttpRequest)',\n      'ISO-8859-3: iso-ir-109 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-3: iso8859-3 (XMLHttpRequest)',\n      'ISO-8859-3: iso8859-3 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-3: iso88593 (XMLHttpRequest)',\n      'ISO-8859-3: iso88593 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-3: iso_8859-3 (XMLHttpRequest)',\n      'ISO-8859-3: iso_8859-3 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-3: iso_8859-3:1988 (XMLHttpRequest)',\n      'ISO-8859-3: iso_8859-3:1988 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-3: l3 (XMLHttpRequest)',\n      'ISO-8859-3: l3 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-3: latin3 (XMLHttpRequest)',\n      'ISO-8859-3: latin3 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-4: csisolatin4 (XMLHttpRequest)',\n      'ISO-8859-4: csisolatin4 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-4: iso-8859-4 (XMLHttpRequest)',\n      'ISO-8859-4: iso-8859-4 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-4: iso-ir-110 (XMLHttpRequest)',\n      'ISO-8859-4: iso-ir-110 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-4: iso8859-4 (XMLHttpRequest)',\n      'ISO-8859-4: iso8859-4 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-4: iso88594 (XMLHttpRequest)',\n      'ISO-8859-4: iso88594 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-4: iso_8859-4 (XMLHttpRequest)',\n      'ISO-8859-4: iso_8859-4 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-4: iso_8859-4:1988 (XMLHttpRequest)',\n      'ISO-8859-4: iso_8859-4:1988 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-4: l4 (XMLHttpRequest)',\n      'ISO-8859-4: l4 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-4: latin4 (XMLHttpRequest)',\n      'ISO-8859-4: latin4 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-5: csisolatincyrillic (XMLHttpRequest)',\n      'ISO-8859-5: csisolatincyrillic (document.characterSet and document.inputEncoding)',\n      'ISO-8859-5: cyrillic (XMLHttpRequest)',\n      'ISO-8859-5: cyrillic (document.characterSet and document.inputEncoding)',\n      'ISO-8859-5: iso-8859-5 (XMLHttpRequest)',\n      'ISO-8859-5: iso-8859-5 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-5: iso-ir-144 (XMLHttpRequest)',\n      'ISO-8859-5: iso-ir-144 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-5: iso8859-5 (XMLHttpRequest)',\n      'ISO-8859-5: iso8859-5 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-5: iso88595 (XMLHttpRequest)',\n      'ISO-8859-5: iso88595 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-5: iso_8859-5 (XMLHttpRequest)',\n      'ISO-8859-5: iso_8859-5 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-5: iso_8859-5:1988 (XMLHttpRequest)',\n      'ISO-8859-5: iso_8859-5:1988 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-6: arabic (XMLHttpRequest)',\n      'ISO-8859-6: arabic (document.characterSet and document.inputEncoding)',\n      'ISO-8859-6: asmo-708 (XMLHttpRequest)',\n      'ISO-8859-6: asmo-708 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-6: csiso88596e (XMLHttpRequest)',\n      'ISO-8859-6: csiso88596e (document.characterSet and document.inputEncoding)',\n      'ISO-8859-6: csiso88596i (XMLHttpRequest)',\n      'ISO-8859-6: csiso88596i (document.characterSet and document.inputEncoding)',\n      'ISO-8859-6: csisolatinarabic (XMLHttpRequest)',\n      'ISO-8859-6: csisolatinarabic (document.characterSet and document.inputEncoding)',\n      'ISO-8859-6: ecma-114 (XMLHttpRequest)',\n      'ISO-8859-6: ecma-114 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-6: iso-8859-6 (XMLHttpRequest)',\n      'ISO-8859-6: iso-8859-6 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-6: iso-8859-6-e (XMLHttpRequest)',\n      'ISO-8859-6: iso-8859-6-e (document.characterSet and document.inputEncoding)',\n      'ISO-8859-6: iso-8859-6-i (XMLHttpRequest)',\n      'ISO-8859-6: iso-8859-6-i (document.characterSet and document.inputEncoding)',\n      'ISO-8859-6: iso-ir-127 (XMLHttpRequest)',\n      'ISO-8859-6: iso-ir-127 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-6: iso8859-6 (XMLHttpRequest)',\n      'ISO-8859-6: iso8859-6 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-6: iso88596 (XMLHttpRequest)',\n      'ISO-8859-6: iso88596 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-6: iso_8859-6 (XMLHttpRequest)',\n      'ISO-8859-6: iso_8859-6 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-6: iso_8859-6:1987 (XMLHttpRequest)',\n      'ISO-8859-6: iso_8859-6:1987 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-7: csisolatingreek (XMLHttpRequest)',\n      'ISO-8859-7: csisolatingreek (document.characterSet and document.inputEncoding)',\n      'ISO-8859-7: ecma-118 (XMLHttpRequest)',\n      'ISO-8859-7: ecma-118 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-7: elot_928 (XMLHttpRequest)',\n      'ISO-8859-7: elot_928 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-7: greek (XMLHttpRequest)',\n      'ISO-8859-7: greek (document.characterSet and document.inputEncoding)',\n      'ISO-8859-7: greek8 (XMLHttpRequest)',\n      'ISO-8859-7: greek8 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-7: iso-8859-7 (XMLHttpRequest)',\n      'ISO-8859-7: iso-8859-7 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-7: iso-ir-126 (XMLHttpRequest)',\n      'ISO-8859-7: iso-ir-126 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-7: iso8859-7 (XMLHttpRequest)',\n      'ISO-8859-7: iso8859-7 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-7: iso88597 (XMLHttpRequest)',\n      'ISO-8859-7: iso88597 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-7: iso_8859-7 (XMLHttpRequest)',\n      'ISO-8859-7: iso_8859-7 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-7: iso_8859-7:1987 (XMLHttpRequest)',\n      'ISO-8859-7: iso_8859-7:1987 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-7: sun_eu_greek (XMLHttpRequest)',\n      'ISO-8859-7: sun_eu_greek (document.characterSet and document.inputEncoding)',\n      'ISO-8859-8: csiso88598e (XMLHttpRequest)',\n      'ISO-8859-8: csiso88598e (document.characterSet and document.inputEncoding)',\n      'ISO-8859-8: csisolatinhebrew (XMLHttpRequest)',\n      'ISO-8859-8: csisolatinhebrew (document.characterSet and document.inputEncoding)',\n      'ISO-8859-8: hebrew (XMLHttpRequest)',\n      'ISO-8859-8: hebrew (document.characterSet and document.inputEncoding)',\n      'ISO-8859-8: iso-8859-8 (XMLHttpRequest)',\n      'ISO-8859-8: iso-8859-8 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-8: iso-8859-8-e (XMLHttpRequest)',\n      'ISO-8859-8: iso-8859-8-e (document.characterSet and document.inputEncoding)',\n      'ISO-8859-8: iso-ir-138 (XMLHttpRequest)',\n      'ISO-8859-8: iso-ir-138 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-8: iso8859-8 (XMLHttpRequest)',\n      'ISO-8859-8: iso8859-8 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-8: iso88598 (XMLHttpRequest)',\n      'ISO-8859-8: iso88598 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-8: iso_8859-8 (XMLHttpRequest)',\n      'ISO-8859-8: iso_8859-8 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-8: iso_8859-8:1988 (XMLHttpRequest)',\n      'ISO-8859-8: iso_8859-8:1988 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-8: visual (XMLHttpRequest)',\n      'ISO-8859-8: visual (document.characterSet and document.inputEncoding)',\n      'ISO-8859-8-I: csiso88598i (XMLHttpRequest)',\n      'ISO-8859-8-I: csiso88598i (document.characterSet and document.inputEncoding)',\n      'ISO-8859-8-I: iso-8859-8-i (XMLHttpRequest)',\n      'ISO-8859-8-I: iso-8859-8-i (document.characterSet and document.inputEncoding)',\n      'ISO-8859-8-I: logical (XMLHttpRequest)',\n      'ISO-8859-8-I: logical (document.characterSet and document.inputEncoding)',\n      'ISO-8859-10: csisolatin6 (XMLHttpRequest)',\n      'ISO-8859-10: csisolatin6 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-10: iso-8859-10 (XMLHttpRequest)',\n      'ISO-8859-10: iso-8859-10 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-10: iso-ir-157 (XMLHttpRequest)',\n      'ISO-8859-10: iso-ir-157 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-10: iso8859-10 (XMLHttpRequest)',\n      'ISO-8859-10: iso8859-10 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-10: iso885910 (XMLHttpRequest)',\n      'ISO-8859-10: iso885910 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-10: l6 (XMLHttpRequest)',\n      'ISO-8859-10: l6 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-10: latin6 (XMLHttpRequest)',\n      'ISO-8859-10: latin6 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-13: iso-8859-13 (XMLHttpRequest)',\n      'ISO-8859-13: iso-8859-13 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-13: iso8859-13 (XMLHttpRequest)',\n      'ISO-8859-13: iso8859-13 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-13: iso885913 (XMLHttpRequest)',\n      'ISO-8859-13: iso885913 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-14: iso-8859-14 (XMLHttpRequest)',\n      'ISO-8859-14: iso-8859-14 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-14: iso8859-14 (XMLHttpRequest)',\n      'ISO-8859-14: iso8859-14 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-14: iso885914 (XMLHttpRequest)',\n      'ISO-8859-14: iso885914 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-15: csisolatin9 (XMLHttpRequest)',\n      'ISO-8859-15: csisolatin9 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-15: iso-8859-15 (XMLHttpRequest)',\n      'ISO-8859-15: iso-8859-15 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-15: iso8859-15 (XMLHttpRequest)',\n      'ISO-8859-15: iso8859-15 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-15: iso885915 (XMLHttpRequest)',\n      'ISO-8859-15: iso885915 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-15: iso_8859-15 (XMLHttpRequest)',\n      'ISO-8859-15: iso_8859-15 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-15: l9 (XMLHttpRequest)',\n      'ISO-8859-15: l9 (document.characterSet and document.inputEncoding)',\n      'ISO-8859-16: iso-8859-16 (XMLHttpRequest)',\n      'ISO-8859-16: iso-8859-16 (document.characterSet and document.inputEncoding)',\n      'KOI8-R: cskoi8r (XMLHttpRequest)',\n      'KOI8-R: cskoi8r (document.characterSet and document.inputEncoding)',\n      'KOI8-R: koi (XMLHttpRequest)',\n      'KOI8-R: koi (document.characterSet and document.inputEncoding)',\n      'KOI8-R: koi8 (XMLHttpRequest)',\n      'KOI8-R: koi8 (document.characterSet and document.inputEncoding)',\n      'KOI8-R: koi8-r (XMLHttpRequest)',\n      'KOI8-R: koi8-r (document.characterSet and document.inputEncoding)',\n      'KOI8-R: koi8_r (XMLHttpRequest)',\n      'KOI8-R: koi8_r (document.characterSet and document.inputEncoding)',\n      'KOI8-U: koi8-ru (XMLHttpRequest)',\n      'KOI8-U: koi8-ru (document.characterSet and document.inputEncoding)',\n      'KOI8-U: koi8-u (XMLHttpRequest)',\n      'KOI8-U: koi8-u (document.characterSet and document.inputEncoding)',\n      'macintosh: csmacintosh (XMLHttpRequest)',\n      'macintosh: csmacintosh (document.characterSet and document.inputEncoding)',\n      'macintosh: mac (XMLHttpRequest)',\n      'macintosh: mac (document.characterSet and document.inputEncoding)',\n      'macintosh: macintosh (XMLHttpRequest)',\n      'macintosh: macintosh (document.characterSet and document.inputEncoding)',\n      'macintosh: x-mac-roman (XMLHttpRequest)',\n      'macintosh: x-mac-roman (document.characterSet and document.inputEncoding)',\n      'windows-874: dos-874 (XMLHttpRequest)',\n      'windows-874: dos-874 (document.characterSet and document.inputEncoding)',\n      'windows-874: iso-8859-11 (XMLHttpRequest)',\n      'windows-874: iso-8859-11 (document.characterSet and document.inputEncoding)',\n      'windows-874: iso8859-11 (XMLHttpRequest)',\n      'windows-874: iso8859-11 (document.characterSet and document.inputEncoding)',\n      'windows-874: iso885911 (XMLHttpRequest)',\n      'windows-874: iso885911 (document.characterSet and document.inputEncoding)',\n      'windows-874: tis-620 (XMLHttpRequest)',\n      'windows-874: tis-620 (document.characterSet and document.inputEncoding)',\n      'windows-874: windows-874 (XMLHttpRequest)',\n      'windows-874: windows-874 (document.characterSet and document.inputEncoding)',\n      'windows-1250: cp1250 (XMLHttpRequest)',\n      'windows-1250: cp1250 (document.characterSet and document.inputEncoding)',\n      'windows-1250: windows-1250 (XMLHttpRequest)',\n      'windows-1250: windows-1250 (document.characterSet and document.inputEncoding)',\n      'windows-1250: x-cp1250 (XMLHttpRequest)',\n      'windows-1250: x-cp1250 (document.characterSet and document.inputEncoding)',\n      'windows-1251: cp1251 (XMLHttpRequest)',\n      'windows-1251: cp1251 (document.characterSet and document.inputEncoding)',\n      'windows-1251: windows-1251 (XMLHttpRequest)',\n      'windows-1251: windows-1251 (document.characterSet and document.inputEncoding)',\n      'windows-1251: x-cp1251 (XMLHttpRequest)',\n      'windows-1251: x-cp1251 (document.characterSet and document.inputEncoding)',\n      'windows-1252: ansi_x3.4-1968 (XMLHttpRequest)',\n      'windows-1252: ansi_x3.4-1968 (document.characterSet and document.inputEncoding)',\n      'windows-1252: ascii (XMLHttpRequest)',\n      'windows-1252: ascii (document.characterSet and document.inputEncoding)',\n      'windows-1252: cp1252 (XMLHttpRequest)',\n      'windows-1252: cp1252 (document.characterSet and document.inputEncoding)',\n      'windows-1252: cp819 (XMLHttpRequest)',\n      'windows-1252: cp819 (document.characterSet and document.inputEncoding)',\n      'windows-1252: csisolatin1 (XMLHttpRequest)',\n      'windows-1252: csisolatin1 (document.characterSet and document.inputEncoding)',\n      'windows-1252: ibm819 (XMLHttpRequest)',\n      'windows-1252: ibm819 (document.characterSet and document.inputEncoding)',\n      'windows-1252: iso-8859-1 (XMLHttpRequest)',\n      'windows-1252: iso-8859-1 (document.characterSet and document.inputEncoding)',\n      'windows-1252: iso-ir-100 (XMLHttpRequest)',\n      'windows-1252: iso-ir-100 (document.characterSet and document.inputEncoding)',\n      'windows-1252: iso8859-1 (XMLHttpRequest)',\n      'windows-1252: iso8859-1 (document.characterSet and document.inputEncoding)',\n      'windows-1252: iso88591 (XMLHttpRequest)',\n      'windows-1252: iso88591 (document.characterSet and document.inputEncoding)',\n      'windows-1252: iso_8859-1 (XMLHttpRequest)',\n      'windows-1252: iso_8859-1 (document.characterSet and document.inputEncoding)',\n      'windows-1252: iso_8859-1:1987 (XMLHttpRequest)',\n      'windows-1252: iso_8859-1:1987 (document.characterSet and document.inputEncoding)',\n      'windows-1252: l1 (XMLHttpRequest)',\n      'windows-1252: l1 (document.characterSet and document.inputEncoding)',\n      'windows-1252: latin1 (XMLHttpRequest)',\n      'windows-1252: latin1 (document.characterSet and document.inputEncoding)',\n      'windows-1252: us-ascii (XMLHttpRequest)',\n      'windows-1252: us-ascii (document.characterSet and document.inputEncoding)',\n      'windows-1252: windows-1252 (XMLHttpRequest)',\n      'windows-1252: windows-1252 (document.characterSet and document.inputEncoding)',\n      'windows-1252: x-cp1252 (XMLHttpRequest)',\n      'windows-1252: x-cp1252 (document.characterSet and document.inputEncoding)',\n      'windows-1253: cp1253 (XMLHttpRequest)',\n      'windows-1253: cp1253 (document.characterSet and document.inputEncoding)',\n      'windows-1253: windows-1253 (XMLHttpRequest)',\n      'windows-1253: windows-1253 (document.characterSet and document.inputEncoding)',\n      'windows-1253: x-cp1253 (XMLHttpRequest)',\n      'windows-1253: x-cp1253 (document.characterSet and document.inputEncoding)',\n      'windows-1254: cp1254 (XMLHttpRequest)',\n      'windows-1254: cp1254 (document.characterSet and document.inputEncoding)',\n      'windows-1254: csisolatin5 (XMLHttpRequest)',\n      'windows-1254: csisolatin5 (document.characterSet and document.inputEncoding)',\n      'windows-1254: iso-8859-9 (XMLHttpRequest)',\n      'windows-1254: iso-8859-9 (document.characterSet and document.inputEncoding)',\n      'windows-1254: iso-ir-148 (XMLHttpRequest)',\n      'windows-1254: iso-ir-148 (document.characterSet and document.inputEncoding)',\n      'windows-1254: iso8859-9 (XMLHttpRequest)',\n      'windows-1254: iso8859-9 (document.characterSet and document.inputEncoding)',\n      'windows-1254: iso88599 (XMLHttpRequest)',\n      'windows-1254: iso88599 (document.characterSet and document.inputEncoding)',\n      'windows-1254: iso_8859-9 (XMLHttpRequest)',\n      'windows-1254: iso_8859-9 (document.characterSet and document.inputEncoding)',\n      'windows-1254: iso_8859-9:1989 (XMLHttpRequest)',\n      'windows-1254: iso_8859-9:1989 (document.characterSet and document.inputEncoding)',\n      'windows-1254: l5 (XMLHttpRequest)',\n      'windows-1254: l5 (document.characterSet and document.inputEncoding)',\n      'windows-1254: latin5 (XMLHttpRequest)',\n      'windows-1254: latin5 (document.characterSet and document.inputEncoding)',\n      'windows-1254: windows-1254 (XMLHttpRequest)',\n      'windows-1254: windows-1254 (document.characterSet and document.inputEncoding)',\n      'windows-1254: x-cp1254 (XMLHttpRequest)',\n      'windows-1254: x-cp1254 (document.characterSet and document.inputEncoding)',\n      'windows-1255: cp1255 (XMLHttpRequest)',\n      'windows-1255: cp1255 (document.characterSet and document.inputEncoding)',\n      'windows-1255: windows-1255 (XMLHttpRequest)',\n      'windows-1255: windows-1255 (document.characterSet and document.inputEncoding)',\n      'windows-1255: x-cp1255 (XMLHttpRequest)',\n      'windows-1255: x-cp1255 (document.characterSet and document.inputEncoding)',\n      'windows-1256: cp1256 (XMLHttpRequest)',\n      'windows-1256: cp1256 (document.characterSet and document.inputEncoding)',\n      'windows-1256: windows-1256 (XMLHttpRequest)',\n      'windows-1256: windows-1256 (document.characterSet and document.inputEncoding)',\n      'windows-1256: x-cp1256 (XMLHttpRequest)',\n      'windows-1256: x-cp1256 (document.characterSet and document.inputEncoding)',\n      'windows-1257: cp1257 (XMLHttpRequest)',\n      'windows-1257: cp1257 (document.characterSet and document.inputEncoding)',\n      'windows-1257: windows-1257 (XMLHttpRequest)',\n      'windows-1257: windows-1257 (document.characterSet and document.inputEncoding)',\n      'windows-1257: x-cp1257 (XMLHttpRequest)',\n      'windows-1257: x-cp1257 (document.characterSet and document.inputEncoding)',\n      'windows-1258: cp1258 (XMLHttpRequest)',\n      'windows-1258: cp1258 (document.characterSet and document.inputEncoding)',\n      'windows-1258: windows-1258 (XMLHttpRequest)',\n      'windows-1258: windows-1258 (document.characterSet and document.inputEncoding)',\n      'windows-1258: x-cp1258 (XMLHttpRequest)',\n      'windows-1258: x-cp1258 (document.characterSet and document.inputEncoding)',\n      'x-mac-cyrillic: x-mac-cyrillic (XMLHttpRequest)',\n      'x-mac-cyrillic: x-mac-cyrillic (document.characterSet and document.inputEncoding)',\n      'x-mac-cyrillic: x-mac-ukrainian (XMLHttpRequest)',\n      'x-mac-cyrillic: x-mac-ukrainian (document.characterSet and document.inputEncoding)',\n    ],\n  },\n  'streams/backpressure.any.js': {},\n  'streams/decode-attributes.any.js': {},\n  'streams/decode-bad-chunks.any.js': {},\n  'streams/decode-ignore-bom.any.js': {},\n  'streams/decode-incomplete-input.any.js': {},\n  'streams/decode-non-utf8.any.js': {},\n  'streams/decode-split-character.any.js': {},\n  'streams/decode-utf8.any.js': {\n    comment: 'Enable once MessageChannel is implemented',\n    expectedFailures: [\n      'decoding a transferred Uint8Array chunk should give no output',\n      'decoding a transferred ArrayBuffer chunk should give no output',\n    ],\n  },\n  'streams/encode-bad-chunks.any.js': {},\n  'streams/encode-utf8.any.js': {},\n  'streams/invalid-realm.window.js': {\n    comment: 'Enable when ShadowRealm is supported',\n    expectedFailures: [\n      'TextDecoderStream: write in detached realm should succeed',\n      'TextEncoderStream: write in detached realm should succeed',\n      'TextEncoderStream: close in detached realm should succeed',\n      'TextDecoderStream: close in detached realm should succeed',\n    ],\n  },\n  'streams/readable-writable-properties.any.js': {},\n  'streams/realms.window.js': {\n    comment: 'ReferenceError: window is not defined',\n    omittedTests: true,\n  },\n  'textdecoder-arguments.any.js': {},\n  'textdecoder-byte-order-marks.any.js': {},\n  'textdecoder-copy.any.js': {},\n  'textdecoder-eof.any.js': {},\n  'textdecoder-fatal-single-byte.any.js': {},\n  'textdecoder-fatal-streaming.any.js': {},\n  'textdecoder-fatal.any.js': {},\n  'textdecoder-ignorebom.any.js': {},\n  'textdecoder-labels.any.js': {},\n  'textdecoder-streaming.any.js': {},\n  'textdecoder-utf16-surrogates.any.js': {},\n  'textencoder-constructor-non-utf.any.js': {},\n  'textencoder-utf16-surrogates.any.js': {},\n  'unsupported-encodings.any.js': {\n    comment: 'XMLHttpRequest is not defined',\n    expectedFailures: [\n      'UTF-7 should not be supported',\n      'utf-7 should not be supported',\n      'UTF-32 with BOM should decode as UTF-16LE',\n      'UTF-32 with no BOM should decode as UTF-8',\n      'utf-32 with BOM should decode as UTF-16LE',\n      'utf-32 with no BOM should decode as UTF-8',\n      'UTF-32LE with BOM should decode as UTF-16LE',\n      'UTF-32LE with no BOM should decode as UTF-8',\n      'utf-32le with BOM should decode as UTF-16LE',\n      'utf-32le with no BOM should decode as UTF-8',\n      'UTF-32be with no BOM should decode as UTF-8',\n      'UTF-32be with BOM should decode as UTF-8',\n      'utf-32be with no BOM should decode as UTF-8',\n      'utf-32be with BOM should decode as UTF-8',\n    ],\n  },\n  'unsupported-labels.window.js': {\n    comment:\n      'Browser-only test that requires document/iframe for HTML charset inheritance detection',\n    disabledTests: true,\n  },\n} satisfies TestRunnerConfig;\n"
  },
  {
    "path": "src/wpt/eslint.config.mjs",
    "content": "import { baseConfig } from '../../tools/base.eslint.config.mjs';\n\nexport default [\n  ...baseConfig(),\n  {\n    files: ['src/wpt/**/*-test.ts'],\n    rules: {\n      'sort-keys': 'error',\n    },\n  },\n];\n"
  },
  {
    "path": "src/wpt/eventsource-test.ts",
    "content": "// Copyright (c) 2017-2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { type TestRunnerConfig } from 'harness/harness';\n\nexport default {\n  'dedicated-worker/eventsource-close.js': {\n    comment: 'Uses postMessage which is not available in workerd',\n    omittedTests: true,\n  },\n  'dedicated-worker/eventsource-close2.js': {\n    comment: 'Uses self.close() which is not available in workerd',\n    omittedTests: true,\n  },\n  'dedicated-worker/eventsource-constructor-no-new.any.js': {},\n  'dedicated-worker/eventsource-constructor-non-same-origin.js': {\n    comment: 'Uses postMessage which is not available in workerd',\n    omittedTests: true,\n  },\n  'dedicated-worker/eventsource-constructor-url-bogus.js': {\n    comment: 'Uses postMessage which is not available in workerd',\n    omittedTests: true,\n  },\n  'dedicated-worker/eventsource-eventtarget.worker.js': {\n    comment: 'Uses importScripts which is not available in workerd',\n    omittedTests: true,\n  },\n  'dedicated-worker/eventsource-onmesage.js': {\n    comment: 'Uses postMessage which is not available in workerd',\n    omittedTests: true,\n  },\n  'dedicated-worker/eventsource-onopen.js': {\n    comment: 'Uses postMessage which is not available in workerd',\n    omittedTests: true,\n  },\n  'dedicated-worker/eventsource-prototype.js': {\n    comment: 'Uses postMessage which is not available in workerd',\n    omittedTests: true,\n  },\n  'dedicated-worker/eventsource-url.js': {\n    comment: 'Uses postMessage which is not available in workerd',\n    omittedTests: true,\n  },\n  'event-data.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'eventsource-close.window.js': {\n    comment: 'Window-specific tests are not supported',\n    omittedTests: true,\n  },\n  'eventsource-constructor-document-domain.window.js': {\n    comment: 'Window-specific tests are not supported',\n    omittedTests: true,\n  },\n  'eventsource-constructor-empty-url.any.js': {\n    comment:\n      'workerd throws SyntaxError for empty URL instead of resolving to self.location',\n    expectedFailures: ['EventSource constructor with an empty url.'],\n  },\n  'eventsource-constructor-non-same-origin.window.js': {\n    comment: 'Window-specific tests are not supported',\n    omittedTests: true,\n  },\n  'eventsource-constructor-stringify.window.js': {\n    comment: 'Window-specific tests are not supported',\n    omittedTests: true,\n  },\n  'eventsource-constructor-url-bogus.any.js': {},\n  'eventsource-cross-origin.window.js': {\n    comment: 'Window-specific tests are not supported',\n    omittedTests: true,\n  },\n  'eventsource-eventtarget.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'eventsource-onmessage-trusted.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'eventsource-onmessage.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'eventsource-onopen.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'eventsource-prototype.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'eventsource-reconnect.window.js': {\n    comment: 'Window-specific tests are not supported',\n    omittedTests: true,\n  },\n  'eventsource-request-cancellation.window.js': {\n    comment: 'Window-specific tests are not supported',\n    omittedTests: true,\n  },\n  'eventsource-url.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-bom-2.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-bom.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-comments.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-data-before-final-empty-line.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-field-data.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-field-event-empty.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-field-event.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-field-id-2.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-field-id-3.window.js': {\n    comment: 'Window-specific tests are not supported',\n    omittedTests: true,\n  },\n  'format-field-id-null.window.js': {\n    comment: 'Window-specific tests are not supported',\n    omittedTests: true,\n  },\n  'format-field-id.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-field-parsing.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-field-retry-bogus.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-field-retry-empty.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-field-retry.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-field-unknown.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-leading-space.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-mime-bogus.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-mime-trailing-semicolon.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-mime-valid-bogus.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-newlines.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-null-character.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'format-utf-8.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'request-accept.any.js': {\n    comment: 'EventSource does not support relative URLs in workerd',\n    expectedFailures: true,\n  },\n  'request-cache-control.any.js': {\n    comment: 'Test times out due to relative URL handling',\n    omittedTests: true,\n  },\n  'request-credentials.window.js': {\n    comment: 'Window-specific tests are not supported',\n    omittedTests: true,\n  },\n  'request-redirect.window.js': {\n    comment: 'Window-specific tests are not supported',\n    omittedTests: true,\n  },\n  'request-status-error.window.js': {\n    comment: 'Window-specific tests are not supported',\n    omittedTests: true,\n  },\n  'shared-worker/eventsource-close.js': {\n    comment: 'SharedWorker tests are not applicable to workerd',\n    omittedTests: true,\n  },\n  'shared-worker/eventsource-constructor-non-same-origin.js': {\n    comment: 'SharedWorker tests are not applicable to workerd',\n    omittedTests: true,\n  },\n  'shared-worker/eventsource-constructor-url-bogus.js': {\n    comment: 'SharedWorker tests are not applicable to workerd',\n    omittedTests: true,\n  },\n  'shared-worker/eventsource-eventtarget.js': {\n    comment: 'SharedWorker tests are not applicable to workerd',\n    omittedTests: true,\n  },\n  'shared-worker/eventsource-onmesage.js': {\n    comment: 'SharedWorker tests are not applicable to workerd',\n    omittedTests: true,\n  },\n  'shared-worker/eventsource-onopen.js': {\n    comment: 'SharedWorker tests are not applicable to workerd',\n    omittedTests: true,\n  },\n  'shared-worker/eventsource-prototype.js': {\n    comment: 'SharedWorker tests are not applicable to workerd',\n    omittedTests: true,\n  },\n  'shared-worker/eventsource-url.js': {\n    comment: 'SharedWorker tests are not applicable to workerd',\n    omittedTests: true,\n  },\n} satisfies TestRunnerConfig;\n"
  },
  {
    "path": "src/wpt/fetch/api-test.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { type TestRunnerConfig } from 'harness/harness';\n\nexport default {\n  'abort/cache.https.any.js': {\n    comment: 'Not implemented',\n    expectedFailures: [\n      'Signals are not stored in the cache API',\n      \"Signals are not stored in the cache API, even if they're already aborted\",\n    ],\n  },\n  'abort/general.any.js': {\n    comment: 'See individual tests',\n    disabledTests: [\n      // Flaky since 2025-06-09. To be investigated.\n      'Stream errors once aborted, after reading. Underlying connection closed.',\n      // Flaky since 2025-07-25. To be investigated.\n      'Stream errors once aborted. Underlying connection closed.',\n    ],\n    expectedFailures: [\n      // The fetch promise still resolves for some reason\n      'Aborting rejects with AbortError',\n      'Aborting rejects with abort reason',\n      'Already aborted signal rejects immediately',\n\n      // Doesn't reject\n      'response.arrayBuffer() rejects if already aborted',\n      'response.blob() rejects if already aborted',\n      'response.bytes() rejects if already aborted',\n      'response.json() rejects if already aborted',\n      'response.text() rejects if already aborted',\n      'Call text() twice on aborted response',\n\n      // Instead throws TypeError: Parsing a Body as FormData requires a Content-Type header.\n      'response.formData() rejects if already aborted',\n\n      // ReadableStream.cancel was not called synchronously at the right time\n      'Readable stream synchronously cancels with AbortError if aborted before reading',\n\n      // Cloned request needs to have a different signal object, that's in the same state as the original\n      'Signal state is cloned',\n      'Signal on request object should also have abort reason',\n      'Signal on request object',\n    ],\n  },\n  'abort/request.any.js': {},\n\n  'basic/accept-header.any.js': {\n    comment: 'Response.type must be basic',\n    expectedFailures: [\n      \"Request through fetch should have 'accept' header with value '*/*'\",\n      \"Request through fetch should have 'accept' header with value 'custom/*'\",\n      \"Request through fetch should have 'accept-language' header with value 'bzh'\",\n      \"Request through fetch should have a 'accept-language' header\",\n    ],\n  },\n  'basic/conditional-get.any.js': {\n    comment:\n      \"This test is too browser specific. It's assuming a request was served from cache vs received by server.\",\n    omittedTests: ['Testing conditional GET with ETags'],\n  },\n  'basic/error-after-response.any.js': {\n    comment:\n      'Stream disconnected prematurely and a dropped promise when faced with intentionally bad chunked encoding from WPT',\n    disabledTests: true,\n  },\n  'basic/gc.any.js': {},\n  'basic/header-value-combining.any.js': {\n    comment:\n      \"Stream disconnected prematurely and a dropped promise. Not yet sure what is triggering about WPT's output\",\n    disabledTests: true,\n  },\n  'basic/header-value-null-byte.any.js': {\n    comment: 'We should return a nicer TypeError instead of \"internal error\"',\n    expectedFailures: ['Ensure fetch() rejects null bytes in headers'],\n  },\n  'basic/historical.any.js': {\n    comment: 'This test expects us not to implement getAll',\n    expectedFailures: ['Headers object no longer has a getAll() method'],\n  },\n  'basic/http-response-code.any.js': {},\n  'basic/integrity.sub.any.js': {\n    comment: 'Integrity is not implemented',\n    disabledTests: true,\n  },\n  'basic/keepalive.any.js': {\n    comment: 'Keepalive is not implemented',\n    disabledTests: true,\n  },\n  'basic/mediasource.window.js': {\n    comment: 'MediaSource not implemented. It is DOM-specific',\n    omittedTests: ['Cannot fetch blob: URL from a MediaSource'],\n  },\n  'basic/mode-no-cors.sub.any.js': {\n    comment: 'Request.mode is not relevant to us',\n    disabledTests: true,\n  },\n  'basic/mode-same-origin.any.js': {\n    comment: 'Request.mode is not relevant to us',\n    disabledTests: true,\n  },\n  'basic/referrer.any.js': {\n    comment: 'Referrer is not implemented',\n    omittedTests: true,\n  },\n  'basic/request-forbidden-headers.any.js': {\n    comment: 'We do not have forbidden headers',\n    omittedTests: true,\n  },\n  'basic/request-head.any.js': {},\n  'basic/request-headers-case.any.js': {\n    comment: 'To be investigated',\n    expectedFailures: [\n      'Multiple headers with the same name, different case (THIS-IS-A-TEST first)',\n      'Multiple headers with the same name, different case (THIS-is-A-test first)',\n    ],\n  },\n  'basic/request-headers-nonascii.any.js': {},\n  'basic/request-headers.any.js': {\n    comment: 'Reasons listed per test case',\n    expectedFailures: [\n      // Float16Array not implemented\n      'Fetch with POST with Float16Array body',\n      // Fake HTTP method names not accepted\n      'Fetch with Chicken',\n      'Fetch with Chicken with body',\n      'Fetch with TacO and mode \"same-origin\" needs an Origin header',\n      'Fetch with TacO and mode \"cors\" needs an Origin header',\n      // WPT is expecting us to insert some kind of User-Agent\n      'Fetch with POST with Blob body with mime type',\n      'Fetch with PUT without body',\n      'Fetch with POST with URLSearchParams body',\n      'Fetch with POST with FormData body',\n      // WPT expects Origin header but we don't support CORS\n      'Fetch with PUT and mode \"same-origin\" needs an Origin header',\n      'Fetch with PUT with body',\n      'Fetch with POST and mode \"no-cors\" needs an Origin header',\n      // WPT is expecting us to insert some kind of User-Agent\n      'Fetch with GET',\n      'Fetch with POST with Blob body',\n      'Fetch with POST with Float64Array body',\n      'Fetch with POST with text body',\n      'Fetch with POST with ArrayBuffer body',\n      'Fetch with POST with Float32Array body',\n      'Fetch with POST with Uint8Array body',\n      'Fetch with POST with Int8Array body',\n      'Fetch with POST without body',\n      'Fetch with HEAD',\n      'Fetch with POST and mode \"same-origin\" needs an Origin header',\n      'Fetch with POST with DataView body',\n      // Response.type must be basic\n      'Fetch with GET and mode \"cors\" does not need an Origin header',\n    ],\n  },\n  'basic/request-referrer.any.js': {\n    comment: 'Referrer is not implemented',\n    omittedTests: true,\n  },\n  'basic/request-upload.any.js': {\n    comment: 'Appears to corrupt the state of workerd',\n    disabledTests: true,\n  },\n  'basic/request-upload.h2.any.js': {\n    comment: 'Enable if HTTP/2 is implemented',\n    disabledTests: true,\n  },\n  'basic/response-null-body.any.js': {\n    comment:\n      'Response begins with hello-worldHTTP/1.1 which will lead to invalid protocol errors coming up on other tests later on',\n    disabledTests: true,\n  },\n  'basic/response-url.sub.any.js': {},\n  'basic/scheme-about.any.js': {},\n  'basic/scheme-blob.sub.any.js': {\n    comment: 'URL.createObjectURL() is not implemented',\n    disabledTests: true,\n  },\n  'basic/scheme-data.any.js': {\n    comment: 'Response.type must be basic',\n    expectedFailures: [\n      'Fetching [HEAD] data:,response%27s%20body is OK',\n      'Fetching data:,response%27s%20body is OK',\n      'Fetching data:,response%27s%20body is OK (same-origin)',\n      'Fetching data:,response%27s%20body is OK (cors)',\n      'Fetching data:text/plain;base64,cmVzcG9uc2UncyBib[...] is OK',\n      'Fetching data:image/png;base64,cmVzcG9uc2UncyBib2[...] is OK',\n      'Fetching [POST] data:,response%27s%20body is OK',\n    ],\n  },\n  'basic/scheme-others.sub.any.js': {},\n  'basic/status.h2.any.js': {\n    comment: 'Enable if HTTP/2 is implemented',\n    disabledTests: true,\n  },\n  'basic/stream-response.any.js': {},\n  'basic/stream-safe-creation.any.js': {},\n  'basic/text-utf8.any.js': {},\n\n  'body/cloned-any.js': {\n    comment:\n      'We need to actually clone the body instead of taking a reference to it.',\n    expectedFailures: ['TypedArray is cloned', 'ArrayBuffer is cloned'],\n  },\n  'body/formdata.any.js': {},\n  'body/mime-type.any.js': {},\n\n  'cors/cors-basic.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'cors/cors-cookies-redirect.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'cors/cors-cookies.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'cors/cors-expose-star.sub.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'cors/cors-filtering.sub.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'cors/cors-keepalive.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'cors/cors-multiple-origins.sub.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'cors/cors-no-preflight.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'cors/cors-origin.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'cors/cors-preflight-cache.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'cors/cors-preflight-not-cors-safelisted.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'cors/cors-preflight-redirect.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'cors/cors-preflight-referrer.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'cors/cors-preflight-response-validation.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'cors/cors-preflight-star.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'cors/cors-preflight-status.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'cors/cors-preflight.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'cors/cors-redirect-credentials.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'cors/cors-redirect-preflight.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'cors/cors-redirect.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n\n  'crashtests/huge-fetch.any.js': {},\n\n  'credentials/authentication-basic.any.js': {\n    comment: 'Response.type must be basic',\n    expectedFailures: [\n      'User-added Authorization header with include mode',\n      'User-added Authorization header with omit mode',\n      'User-added Authorization header with same-origin mode',\n      'User-added bogus Authorization header with omit mode',\n    ],\n  },\n  'credentials/authentication-redirection.any.js': {\n    comment:\n      'Even though the actual bug was fixed (Authorization now stripped), these tests are failing due to certificate problems',\n    expectedFailures: [\n      'getAuthorizationHeaderValue - same origin redirection',\n      'getAuthorizationHeaderValue - cross origin redirection',\n    ],\n  },\n  'credentials/cookies.any.js': {\n    comment: 'Request.credentials is not implemented',\n    disabledTests: true,\n  },\n\n  'headers/header-setcookie.any.js': {\n    comment: '(1, 2) To be investigated, (3) CORS is not implemented',\n    expectedFailures: [\n      'Headers iterator is correctly updated with set-cookie changes',\n      'Headers iterator is correctly updated with set-cookie changes #2',\n      'Set-Cookie is a forbidden response header',\n    ],\n  },\n  'headers/header-values-normalize.any.js': {\n    comment: 'XMLHttpRequest is not supported',\n    expectedFailures: [\n      'XMLHttpRequest with value %00',\n      'XMLHttpRequest with value %01',\n      'XMLHttpRequest with value %02',\n      'XMLHttpRequest with value %03',\n      'XMLHttpRequest with value %04',\n      'XMLHttpRequest with value %05',\n      'XMLHttpRequest with value %06',\n      'XMLHttpRequest with value %07',\n      'XMLHttpRequest with value %08',\n      'XMLHttpRequest with value %09',\n      'XMLHttpRequest with value %0A',\n      'XMLHttpRequest with value %0D',\n      'XMLHttpRequest with value %0E',\n      'XMLHttpRequest with value %0F',\n      'XMLHttpRequest with value %10',\n      'XMLHttpRequest with value %11',\n      'XMLHttpRequest with value %12',\n      'XMLHttpRequest with value %13',\n      'XMLHttpRequest with value %14',\n      'XMLHttpRequest with value %15',\n      'XMLHttpRequest with value %16',\n      'XMLHttpRequest with value %17',\n      'XMLHttpRequest with value %18',\n      'XMLHttpRequest with value %19',\n      'XMLHttpRequest with value %1A',\n      'XMLHttpRequest with value %1B',\n      'XMLHttpRequest with value %1C',\n      'XMLHttpRequest with value %1D',\n      'XMLHttpRequest with value %1E',\n      'XMLHttpRequest with value %1F',\n      'XMLHttpRequest with value %20',\n    ],\n  },\n  'headers/header-values.any.js': {\n    comment: 'XMLHTTPRequest is not implemented',\n    expectedFailures: [\n      'XMLHttpRequest with value x%00x needs to throw',\n      'XMLHttpRequest with value x%0Ax needs to throw',\n      'XMLHttpRequest with value x%0Dx needs to throw',\n      'XMLHttpRequest with all valid values',\n    ],\n  },\n  'headers/headers-basic.any.js': {\n    comment: 'Investigate our Headers implementation',\n    expectedFailures: [\n      'Create headers with existing headers with custom iterator',\n      'Iteration skips elements removed while iterating',\n      'Removing elements already iterated over causes an element to be skipped during iteration',\n      'Appending a value pair during iteration causes it to be reached during iteration',\n      'Prepending a value pair before the current element position causes it to be skipped during iteration and adds the current element a second time',\n    ],\n  },\n  'headers/headers-casing.any.js': {},\n  'headers/headers-combine.any.js': {},\n  'headers/headers-errors.any.js': {\n    comment: 'Our validation of header names is too lax',\n    expectedFailures: [\n      'Create headers giving bad header name as init argument',\n      'Create headers giving bad header value as init argument',\n      'Check headers get with an invalid name invalidĀ',\n      'Check headers delete with an invalid name invalidĀ',\n      'Check headers has with an invalid name invalidĀ',\n      'Check headers set with an invalid name invalidĀ',\n      'Check headers set with an invalid value invalidĀ',\n      'Check headers append with an invalid name invalidĀ',\n      'Check headers append with an invalid name [object Object]',\n      'Check headers append with an invalid value invalidĀ',\n    ],\n  },\n  'headers/headers-no-cors.any.js': {\n    comment: 'Request.mode is not relevant',\n    disabledTests: true,\n  },\n  'headers/headers-normalize.any.js': {},\n  'headers/headers-record.any.js': {\n    comment:\n      'This test checks the exact order of operations in JS involved in accessing headers. Our implementation is in C++ instead.',\n    omittedTests: true,\n  },\n  'headers/headers-structure.any.js': {},\n\n  'idlharness.https.any.js': {\n    comment:\n      'Workers expose globals differently than browsers - these interface tests fail',\n    expectedFailures: [\n      // Headers interface tests\n      /^Headers interface/,\n      // Request interface tests\n      /^Request interface/,\n      // Response interface tests\n      /^Response interface/,\n      // FetchLaterResult interface tests (not implemented)\n      /^FetchLaterResult interface/,\n      // Window interface tests (not applicable to workers)\n      /^Window interface/,\n    ],\n  },\n\n  'policies/csp-blocked.js': {\n    comment: 'CSP is not implemented',\n    omittedTests: true,\n  },\n  'policies/nested-policy.js': {\n    comment: 'CSP is not implemented',\n    omittedTests: true,\n  },\n  'policies/referrer-no-referrer.js': {\n    comment: 'CSP is not implemented',\n    omittedTests: true,\n  },\n  'policies/referrer-origin-when-cross-origin.js': {\n    comment: 'CSP is not implemented',\n    omittedTests: true,\n  },\n  'policies/referrer-origin.js': {\n    comment: 'CSP is not implemented',\n    omittedTests: true,\n  },\n  'policies/referrer-unsafe-url.js': {\n    comment: 'CSP is not implemented',\n    omittedTests: true,\n  },\n\n  'redirect/redirect-back-to-original-origin.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'redirect/redirect-count.any.js': {},\n  'redirect/redirect-empty-location.any.js': {\n    comment:\n      'Fix our handling of empty Location header. Even when fixed, tests will still fail due to CORS stuff',\n    expectedFailures: [\n      'redirect response with empty Location, manual mode',\n      'redirect response with empty Location, follow mode',\n    ],\n  },\n  'redirect/redirect-keepalive.any.js': {\n    comment: 'Keepalive is not implemented',\n    disabledTests: true,\n  },\n  'redirect/redirect-keepalive.https.any.js': {\n    comment: 'Keepalive is not implemented',\n    disabledTests: true,\n  },\n  'redirect/redirect-location.any.js': {\n    comment:\n      'Status is expected to be 0 in a browser to avoid leaking info. We do not implement CORS',\n    omittedTests: [/Redirect 3\\d\\d in \"manual\" mode .*/],\n  },\n  'redirect/redirect-method.any.js': {\n    comment: 'Reasons listed per case',\n    expectedFailures: [\n      // Made up method name\n      'Redirect 303 with TESTING',\n      // Content-type header not cleared\n      'Redirect 301 with POST',\n      'Redirect 303 with POST',\n      'Redirect 302 with POST',\n      'Redirect 303 with HEAD',\n      // Response.type must be basic\n      'Redirect 302 with GET',\n      'Redirect 307 with HEAD',\n      'Redirect 301 with GET',\n      'Redirect 303 with GET',\n      'Redirect 307 with POST (string body)',\n      'Redirect 307 with POST (blob body)',\n      'Redirect 301 with HEAD',\n      'Redirect 302 with HEAD',\n      'Redirect 307 with GET',\n    ],\n  },\n  'redirect/redirect-mode.any.js': {\n    comment: \"This test contains stuff besides CORS. Don't omit it all.\",\n    omittedTests: [\n      /(same|cross)-origin redirect 3\\d\\d in manual redirect and (no-cors|cors) mode/,\n      /cross-origin redirect 3\\d\\d in follow redirect and no-cors mode/,\n      'manual redirect with a CORS error should be rejected',\n    ],\n  },\n  'redirect/redirect-origin.any.js': {\n    comment: 'CORS is not implemented',\n    omittedTests: true,\n  },\n  'redirect/redirect-referrer-override.any.js': {\n    comment: 'Referer is not implemented',\n    omittedTests: true,\n  },\n  'redirect/redirect-referrer.any.js': {\n    comment: 'Referrer is not implemented',\n    omittedTests: true,\n  },\n  'redirect/redirect-schemes.any.js': {},\n  'redirect/redirect-to-dataurl.any.js': {},\n  'redirect/redirect-upload.h2.any.js': {\n    comment: 'Do we support HTTP 2?',\n    omittedTests: true,\n  },\n\n  'request/forbidden-method.any.js': {\n    comment: 'We do not have forbidden methods',\n    omittedTests: true,\n  },\n  'request/multi-globals/construct-in-detached-frame.window.js': {\n    comment: \"We don't support detached realms\",\n    omittedTests: true,\n  },\n  'request/request-bad-port.any.js': {},\n  'request/request-cache-default-conditional.any.js': {\n    comment: 'Unsupported cache mode: default',\n    expectedFailures: [\n      'RequestCache \"default\" mode with an If-Modified-Since header (following a request without additional headers) is treated similarly to \"no-store\" with Etag and stale response',\n      'RequestCache \"default\" mode with an If-Modified-Since header (following a request without additional headers) is treated similarly to \"no-store\" with Last-Modified and stale response',\n      'RequestCache \"default\" mode with an If-Modified-Since header (following a request without additional headers) is treated similarly to \"no-store\" with Etag and fresh response',\n      'RequestCache \"default\" mode with an If-Modified-Since header (following a request without additional headers) is treated similarly to \"no-store\" with Last-Modified and fresh response',\n      'RequestCache \"default\" mode with an If-Modified-Since header is treated similarly to \"no-store\" with Etag and stale response',\n      'RequestCache \"default\" mode with an If-Modified-Since header is treated similarly to \"no-store\" with Last-Modified and stale response',\n      'RequestCache \"default\" mode with an If-Modified-Since header is treated similarly to \"no-store\" with Etag and fresh response',\n      'RequestCache \"default\" mode with an If-Modified-Since header is treated similarly to \"no-store\" with Last-Modified and fresh response',\n      'RequestCache \"default\" mode with an If-None-Match header (following a request without additional headers) is treated similarly to \"no-store\" with Etag and stale response',\n      'RequestCache \"default\" mode with an If-None-Match header (following a request without additional headers) is treated similarly to \"no-store\" with Last-Modified and stale response',\n      'RequestCache \"default\" mode with an If-None-Match header (following a request without additional headers) is treated similarly to \"no-store\" with Etag and fresh response',\n      'RequestCache \"default\" mode with an If-None-Match header (following a request without additional headers) is treated similarly to \"no-store\" with Last-Modified and fresh response',\n      'RequestCache \"default\" mode with an If-None-Match header is treated similarly to \"no-store\" with Etag and stale response',\n      'RequestCache \"default\" mode with an If-None-Match header is treated similarly to \"no-store\" with Last-Modified and stale response',\n      'RequestCache \"default\" mode with an If-None-Match header is treated similarly to \"no-store\" with Etag and fresh response',\n      'RequestCache \"default\" mode with an If-None-Match header is treated similarly to \"no-store\" with Last-Modified and fresh response',\n      'RequestCache \"default\" mode with an If-Unmodified-Since header (following a request without additional headers) is treated similarly to \"no-store\" with Etag and stale response',\n      'RequestCache \"default\" mode with an If-Unmodified-Since header (following a request without additional headers) is treated similarly to \"no-store\" with Last-Modified and stale response',\n      'RequestCache \"default\" mode with an If-Unmodified-Since header (following a request without additional headers) is treated similarly to \"no-store\" with Etag and fresh response',\n      'RequestCache \"default\" mode with an If-Unmodified-Since header (following a request without additional headers) is treated similarly to \"no-store\" with Last-Modified and fresh response',\n      'RequestCache \"default\" mode with an If-Unmodified-Since header is treated similarly to \"no-store\" with Etag and stale response',\n      'RequestCache \"default\" mode with an If-Unmodified-Since header is treated similarly to \"no-store\" with Last-Modified and stale response',\n      'RequestCache \"default\" mode with an If-Unmodified-Since header is treated similarly to \"no-store\" with Etag and fresh response',\n      'RequestCache \"default\" mode with an If-Unmodified-Since header is treated similarly to \"no-store\" with Last-Modified and fresh response',\n      'RequestCache \"default\" mode with an If-Match header (following a request without additional headers) is treated similarly to \"no-store\" with Etag and stale response',\n      'RequestCache \"default\" mode with an If-Match header (following a request without additional headers) is treated similarly to \"no-store\" with Last-Modified and stale response',\n      'RequestCache \"default\" mode with an If-Match header (following a request without additional headers) is treated similarly to \"no-store\" with Etag and fresh response',\n      'RequestCache \"default\" mode with an If-Match header (following a request without additional headers) is treated similarly to \"no-store\" with Last-Modified and fresh response',\n      'RequestCache \"default\" mode with an If-Match header is treated similarly to \"no-store\" with Etag and stale response',\n      'RequestCache \"default\" mode with an If-Match header is treated similarly to \"no-store\" with Last-Modified and stale response',\n      'RequestCache \"default\" mode with an If-Match header is treated similarly to \"no-store\" with Etag and fresh response',\n      'RequestCache \"default\" mode with an If-Match header is treated similarly to \"no-store\" with Last-Modified and fresh response',\n      'RequestCache \"default\" mode with an If-Range header (following a request without additional headers) is treated similarly to \"no-store\" with Etag and stale response',\n      'RequestCache \"default\" mode with an If-Range header (following a request without additional headers) is treated similarly to \"no-store\" with Last-Modified and stale response',\n      'RequestCache \"default\" mode with an If-Range header (following a request without additional headers) is treated similarly to \"no-store\" with Etag and fresh response',\n      'RequestCache \"default\" mode with an If-Range header (following a request without additional headers) is treated similarly to \"no-store\" with Last-Modified and fresh response',\n      'RequestCache \"default\" mode with an If-Range header is treated similarly to \"no-store\" with Etag and stale response',\n      'RequestCache \"default\" mode with an If-Range header is treated similarly to \"no-store\" with Last-Modified and stale response',\n      'RequestCache \"default\" mode with an If-Range header is treated similarly to \"no-store\" with Etag and fresh response',\n      'RequestCache \"default\" mode with an If-Range header is treated similarly to \"no-store\" with Last-Modified and fresh response',\n    ],\n  },\n  'request/request-cache-default.any.js': {\n    comment: \"All fail with 'Unsupported cache mode: default' even no-store\",\n    expectedFailures: [\n      'RequestCache \"default\" mode checks the cache for previously cached content and goes to the network for stale responses with Etag and stale response',\n      'RequestCache \"default\" mode checks the cache for previously cached content and goes to the network for stale responses with Last-Modified and stale response',\n      'RequestCache \"default\" mode checks the cache for previously cached content and avoids going to the network if a fresh response exists with Etag and fresh response',\n      'RequestCache \"default\" mode checks the cache for previously cached content and avoids going to the network if a fresh response exists with Last-Modified and fresh response',\n      'Responses with the \"Cache-Control: no-store\" header are not stored in the cache with Etag and stale response',\n      'Responses with the \"Cache-Control: no-store\" header are not stored in the cache with Last-Modified and stale response',\n      'Responses with the \"Cache-Control: no-store\" header are not stored in the cache with Etag and fresh response',\n      'Responses with the \"Cache-Control: no-store\" header are not stored in the cache with Last-Modified and fresh response',\n    ],\n  },\n  'request/request-cache-force-cache.any.js': {\n    comment: 'Unsupported cache mode: default',\n    expectedFailures: [\n      'RequestCache \"force-cache\" mode checks the cache for previously cached content and avoid revalidation for stale responses with Etag and stale response',\n      'RequestCache \"force-cache\" mode checks the cache for previously cached content and avoid revalidation for stale responses with Last-Modified and stale response',\n      'RequestCache \"force-cache\" mode checks the cache for previously cached content and avoid revalidation for fresh responses with Etag and fresh response',\n      'RequestCache \"force-cache\" mode checks the cache for previously cached content and avoid revalidation for fresh responses with Last-Modified and fresh response',\n      'RequestCache \"force-cache\" mode checks the cache for previously cached content and goes to the network if a cached response is not found with Etag and stale response',\n      'RequestCache \"force-cache\" mode checks the cache for previously cached content and goes to the network if a cached response is not found with Last-Modified and stale response',\n      'RequestCache \"force-cache\" mode checks the cache for previously cached content and goes to the network if a cached response is not found with Etag and fresh response',\n      'RequestCache \"force-cache\" mode checks the cache for previously cached content and goes to the network if a cached response is not found with Last-Modified and fresh response',\n      'RequestCache \"force-cache\" mode checks the cache for previously cached content and goes to the network if a cached response would vary with Etag and stale response',\n      'RequestCache \"force-cache\" mode checks the cache for previously cached content and goes to the network if a cached response would vary with Last-Modified and stale response',\n      'RequestCache \"force-cache\" mode checks the cache for previously cached content and goes to the network if a cached response would vary with Etag and fresh response',\n      'RequestCache \"force-cache\" mode checks the cache for previously cached content and goes to the network if a cached response would vary with Last-Modified and fresh response',\n      'RequestCache \"force-cache\" stores the response in the cache if it goes to the network with Etag and stale response',\n      'RequestCache \"force-cache\" stores the response in the cache if it goes to the network with Last-Modified and stale response',\n      'RequestCache \"force-cache\" stores the response in the cache if it goes to the network with Etag and fresh response',\n      'RequestCache \"force-cache\" stores the response in the cache if it goes to the network with Last-Modified and fresh response',\n    ],\n  },\n  'request/request-cache-no-cache.any.js': {\n    comment: 'Unsupported cache mode: default',\n    expectedFailures: [\n      'RequestCache \"no-cache\" mode revalidates stale responses found in the cache with Etag and stale response',\n      'RequestCache \"no-cache\" mode revalidates stale responses found in the cache with Last-Modified and stale response',\n      'RequestCache \"no-cache\" mode revalidates fresh responses found in the cache with Etag and fresh response',\n      'RequestCache \"no-cache\" mode revalidates fresh responses found in the cache with Last-Modified and fresh response',\n    ],\n  },\n  'request/request-cache-no-store.any.js': {\n    comment: 'Unsupported cache mode: default',\n    expectedFailures: [\n      'RequestCache \"no-store\" mode does not check the cache for previously cached content and goes to the network regardless with Etag and stale response',\n      'RequestCache \"no-store\" mode does not check the cache for previously cached content and goes to the network regardless with Last-Modified and stale response',\n      'RequestCache \"no-store\" mode does not check the cache for previously cached content and goes to the network regardless with Etag and fresh response',\n      'RequestCache \"no-store\" mode does not check the cache for previously cached content and goes to the network regardless with Last-Modified and fresh response',\n      'RequestCache \"no-store\" mode does not store the response in the cache with Etag and stale response',\n      'RequestCache \"no-store\" mode does not store the response in the cache with Last-Modified and stale response',\n      'RequestCache \"no-store\" mode does not store the response in the cache with Etag and fresh response',\n      'RequestCache \"no-store\" mode does not store the response in the cache with Last-Modified and fresh response',\n    ],\n  },\n  'request/request-cache-only-if-cached.any.js': {\n    comment: 'Unsupported cache mode: default',\n    expectedFailures: [\n      'RequestCache \"only-if-cached\" mode checks the cache for previously cached content and avoids revalidation for stale responses with Etag and stale response',\n      'RequestCache \"only-if-cached\" mode checks the cache for previously cached content and avoids revalidation for stale responses with Last-Modified and stale response',\n      'RequestCache \"only-if-cached\" mode checks the cache for previously cached content and avoids revalidation for fresh responses with Etag and fresh response',\n      'RequestCache \"only-if-cached\" mode checks the cache for previously cached content and avoids revalidation for fresh responses with Last-Modified and fresh response',\n      'RequestCache \"only-if-cached\" (with \"same-origin\") uses cached same-origin redirects to same-origin content with Etag and fresh response',\n      'RequestCache \"only-if-cached\" (with \"same-origin\") uses cached same-origin redirects to same-origin content with Last-Modified and fresh response',\n      'RequestCache \"only-if-cached\" (with \"same-origin\") uses cached same-origin redirects to same-origin content with Etag and stale response',\n      'RequestCache \"only-if-cached\" (with \"same-origin\") uses cached same-origin redirects to same-origin content with Last-Modified and stale response',\n      'RequestCache \"only-if-cached\" (with \"same-origin\") does not follow redirects across origins and rejects with Etag and fresh response',\n      'RequestCache \"only-if-cached\" (with \"same-origin\") does not follow redirects across origins and rejects with Last-Modified and fresh response',\n      'RequestCache \"only-if-cached\" (with \"same-origin\") does not follow redirects across origins and rejects with Etag and stale response',\n      'RequestCache \"only-if-cached\" (with \"same-origin\") does not follow redirects across origins and rejects with Last-Modified and stale response',\n    ],\n  },\n  'request/request-cache-reload.any.js': {\n    comment: 'Unsupported cache mode: default',\n    expectedFailures: [\n      'RequestCache \"reload\" mode does not check the cache for previously cached content and goes to the network regardless with Etag and stale response',\n      'RequestCache \"reload\" mode does not check the cache for previously cached content and goes to the network regardless with Last-Modified and stale response',\n      'RequestCache \"reload\" mode does not check the cache for previously cached content and goes to the network regardless with Etag and fresh response',\n      'RequestCache \"reload\" mode does not check the cache for previously cached content and goes to the network regardless with Last-Modified and fresh response',\n      'RequestCache \"reload\" mode does store the response in the cache with Etag and stale response',\n      'RequestCache \"reload\" mode does store the response in the cache with Last-Modified and stale response',\n      'RequestCache \"reload\" mode does store the response in the cache with Etag and fresh response',\n      'RequestCache \"reload\" mode does store the response in the cache with Last-Modified and fresh response',\n      'RequestCache \"reload\" mode does store the response in the cache even if a previous response is already stored with Etag and stale response',\n      'RequestCache \"reload\" mode does store the response in the cache even if a previous response is already stored with Last-Modified and stale response',\n      'RequestCache \"reload\" mode does store the response in the cache even if a previous response is already stored with Etag and fresh response',\n      'RequestCache \"reload\" mode does store the response in the cache even if a previous response is already stored with Last-Modified and fresh response',\n    ],\n  },\n  'request/request-cache.js': {},\n  'request/request-constructor-init-body-override.any.js': {},\n  'request/request-consume-empty.any.js': {\n    comment:\n      'We seem to be returning the boundary value as text but WPT expects no value',\n    expectedFailures: ['Consume empty FormData request body as text'],\n  },\n  'request/request-consume.any.js': {},\n  'request/request-disturbed.any.js': {\n    comment:\n      \"(1) To be investigated, (2) They're literally passing URL, the constructor, as the URL!?!\",\n    expectedFailures: [\n      'Input request used for creating new request became disturbed even if body is not used',\n      'Request construction failure should not set \"bodyUsed\"',\n    ],\n  },\n  'request/request-error.any.js': {\n    comment:\n      'These tests would require us to throw errors for some invalid situations that we just ignore',\n    expectedFailures: [\n      \"RequestInit's window is not null\",\n      'Input URL has credentials',\n      \"RequestInit's mode is navigate\",\n      \"RequestInit's referrer is invalid\",\n      \"RequestInit's method is forbidden\",\n      \"RequestInit's mode is no-cors and method is not simple\",\n      'Bad referrerPolicy init parameter value',\n      'Bad mode init parameter value',\n      'Bad credentials init parameter value',\n      'Request with cache mode: only-if-cached and fetch mode: same-origin',\n    ],\n  },\n  'request/request-error.js': {},\n  'request/request-headers.any.js': {\n    comment: 'Neither CORS nor header filtering is implemented',\n    omittedTests: true,\n  },\n  'request/request-init-002.any.js': {},\n  'request/request-init-contenttype.any.js': {},\n  'request/request-init-priority.any.js': {\n    comment: 'Request.priority is not implemented',\n    disabledTests: true,\n  },\n  'request/request-init-stream.any.js': {\n    comment: 'Most of these are because duplex is not implemented',\n    expectedFailures: [\n      'Constructing a Request with a stream holds the original object.',\n      'It is error to omit .duplex when the body is a ReadableStream.',\n      \"It is error to set .duplex = 'full' when the body is null.\",\n      \"It is error to set .duplex = 'full' when the body is a string.\",\n      \"It is error to set .duplex = 'full' when the body is a Uint8Array.\",\n      \"It is error to set .duplex = 'full' when the body is a Blob.\",\n      \"It is error to set .duplex = 'full' when the body is a ReadableStream.\",\n    ],\n  },\n  'request/request-keepalive.any.js': {\n    comment: 'keepalive is not implemented',\n    disabledTests: true,\n  },\n  'request/request-structure.any.js': {\n    comment: 'Unimplemented or partially implemented fields',\n    expectedFailures: [\n      'Check destination attribute',\n      'Check referrer attribute',\n      'Check referrerPolicy attribute',\n      'Check mode attribute',\n      'Check credentials attribute',\n      'Check cache attribute',\n      'Check isReloadNavigation attribute',\n      'Check isHistoryNavigation attribute',\n      'Check duplex attribute',\n    ],\n  },\n\n  'response/json.any.js': {\n    comment:\n      'UTF-16 test requires /xhr/resources/utf16-bom.json which is in a different test module',\n    expectedFailures: ['Ensure UTF-16 results in an error'],\n  },\n  'response/response-arraybuffer-realm.window.js': {\n    comment: 'Skipped because it involves iframes',\n    omittedTests: true,\n  },\n  'response/response-blob-realm.any.js': {\n    comment: 'Skipped because it involves iframes',\n    omittedTests: true,\n  },\n  'response/response-cancel-stream.any.js': {},\n  'response/response-clone-iframe.window.js': {\n    comment: 'Skipped because it involves iframes',\n    omittedTests: true,\n  },\n  'response/response-clone.any.js': {\n    comment: 'TODO Investigate this',\n    expectedFailures: [\n      \"Check Response's clone with default values, without body\",\n      'Check response clone use structureClone for teed ReadableStreams (Int8Arraychunk)',\n      'Check response clone use structureClone for teed ReadableStreams (Int16Arraychunk)',\n      'Check response clone use structureClone for teed ReadableStreams (Int32Arraychunk)',\n      'Check response clone use structureClone for teed ReadableStreams (ArrayBufferchunk)',\n      'Check response clone use structureClone for teed ReadableStreams (Uint8Arraychunk)',\n      'Check response clone use structureClone for teed ReadableStreams (Uint8ClampedArraychunk)',\n      'Check response clone use structureClone for teed ReadableStreams (Uint16Arraychunk)',\n      'Check response clone use structureClone for teed ReadableStreams (Uint32Arraychunk)',\n      'Check response clone use structureClone for teed ReadableStreams (BigInt64Arraychunk)',\n      'Check response clone use structureClone for teed ReadableStreams (BigUint64Arraychunk)',\n      'Check response clone use structureClone for teed ReadableStreams (Float16Arraychunk)',\n      'Check response clone use structureClone for teed ReadableStreams (Float32Arraychunk)',\n      'Check response clone use structureClone for teed ReadableStreams (Float64Arraychunk)',\n      'Check response clone use structureClone for teed ReadableStreams (DataViewchunk)',\n    ],\n  },\n  'response/response-consume-empty.any.js': {\n    comment:\n      'We seem to be returning the boundary value as text but WPT expects no value',\n    expectedFailures: ['Consume empty FormData response body as text'],\n  },\n  'response/response-consume-stream.any.js': {},\n  'response/response-error-from-stream.any.js': {\n    comment: 'We have TypeError, they want pull Error',\n    expectedFailures: [\n      'ReadableStream start() Error propagates to Response.formData() Promise',\n      'ReadableStream pull() Error propagates to Response.formData() Promise',\n    ],\n  },\n  'response/response-error.any.js': {\n    comment: 'Likely just missing validation',\n    expectedFailures: [\"Throws TypeError when responseInit's statusText is Ā\"],\n  },\n  'response/response-from-stream.any.js': {\n    comment: 'Missing expected exception (TypeError)',\n    expectedFailures: [\n      'Constructing a Response with a stream on which getReader() is called',\n    ],\n  },\n  'response/response-headers-guard.any.js': {\n    comment: 'Likely just missing validation',\n    expectedFailures: ['Ensure response headers are immutable'],\n  },\n  'response/response-init-001.any.js': {\n    comment: 'statusText should be inited to OK',\n    expectedFailures: ['Check default value for statusText attribute'],\n  },\n  'response/response-init-002.any.js': {},\n  'response/response-init-contenttype.any.js': {},\n  'response/response-static-error.any.js': {\n    comment:\n      'We need to make Headers immutable when constructing Response.error()',\n    expectedFailures: [\n      \"the 'guard' of the Headers instance should be immutable\",\n    ],\n  },\n  'response/response-static-json.any.js': {\n    comment: 'statusText does not match the status code',\n    expectedFailures: [\n      // For this test specifically: failed to throw on non-encodable data\n      'Check static json() throws when data is not encodable',\n      'Check response returned by static json() with init undefined',\n      'Check response returned by static json() with init {\"status\":400}',\n      'Check response returned by static json() with init {\"headers\":{}}',\n      'Check response returned by static json() with init {\"headers\":{\"content-type\":\"foo/bar\"}}',\n      'Check response returned by static json() with init {\"headers\":{\"x-foo\":\"bar\"}}',\n    ],\n  },\n  'response/response-static-redirect.any.js': {\n    comment: 'statusText does not match the status code',\n    expectedFailures: [\n      'Check default redirect response',\n      'Check response returned by static method redirect(), status = 301',\n      'Check response returned by static method redirect(), status = 302',\n      'Check response returned by static method redirect(), status = 303',\n      'Check response returned by static method redirect(), status = 307',\n      'Check response returned by static method redirect(), status = 308',\n    ],\n  },\n  'response/response-stream-bad-chunk.any.js': {},\n  'response/response-stream-disturbed-1.any.js': {},\n  'response/response-stream-disturbed-2.any.js': {},\n  'response/response-stream-disturbed-3.any.js': {},\n  'response/response-stream-disturbed-4.any.js': {},\n  'response/response-stream-disturbed-5.any.js': {},\n  'response/response-stream-disturbed-6.any.js': {},\n  'response/response-stream-disturbed-by-pipe.any.js': {},\n  'response/response-stream-disturbed-util.js': {},\n  'response/response-stream-with-broken-then.any.js': {\n    comment:\n      'Triggers an internal error: promise.h:103: failed: expected Wrappable::tryUnwrapOpaque(isolate, handle) != nullptr',\n    expectedFailures: [\n      'Attempt to inject {done: false, value: bye} via Object.prototype.then.',\n      'Attempt to inject value: undefined via Object.prototype.then.',\n      'Attempt to inject undefined via Object.prototype.then.',\n      'Attempt to inject 8.2 via Object.prototype.then.',\n    ],\n  },\n} satisfies TestRunnerConfig;\n"
  },
  {
    "path": "src/wpt/fs-test.ts",
    "content": "// Copyright (c) 2017-2025 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { type TestRunnerConfig } from 'harness/harness';\n\nconst root = await navigator.storage.getDirectory();\nconst tmp = await root.getDirectoryHandle('tmp');\n// The root is read-only, so we need to use a writable subdirectory\n// to actually run the tests.\nnavigator.storage.getDirectory =\n  async (): Promise<FileSystemDirectoryHandle> => {\n    // Let's create a new random tmp subdirectory for each test run to avoid interference\n    return tmp.getDirectoryHandle(crypto.randomUUID(), { create: true });\n  };\n\nexport default {\n  'FileSystemBaseHandle-IndexedDB.https.any.js': {\n    comment: 'IndexedDB is not implemented in workers',\n    disabledTests: true,\n  },\n  'FileSystemBaseHandle-buckets.https.any.js': {\n    comment: 'StorageBuckets is not implemented in workers',\n    disabledTests: true,\n  },\n  'FileSystemBaseHandle-getUniqueId.https.any.js': {\n    comment: '...',\n    expectedFailures: [],\n  },\n  'FileSystemBaseHandle-isSameEntry.https.any.js': {\n    comment: '...',\n    expectedFailures: [],\n  },\n  'FileSystemBaseHandle-postMessage-BroadcastChannel.https.window.js': {\n    comment: 'BroadcastChannel is not implemented in workers',\n    disabledTests: true,\n  },\n  'FileSystemBaseHandle-postMessage-Error.https.window.js': {\n    comment: 'postMessage is not implemented in workers',\n    disabledTests: true,\n  },\n  'FileSystemBaseHandle-postMessage-MessagePort-frames.https.window.js': {\n    comment: 'postMessage and MessagePort are not implemented in workers',\n    disabledTests: true,\n  },\n  'FileSystemBaseHandle-postMessage-MessagePort-windows.https.window.js': {\n    comment: 'postMessage and MessagePort are not implemented in workers',\n    disabledTests: true,\n  },\n  'FileSystemBaseHandle-postMessage-MessagePort-workers.https.window.js': {\n    comment: 'postMessage and MessagePort are not implemented in workers',\n    disabledTests: true,\n  },\n  'FileSystemBaseHandle-postMessage-frames.https.window.js': {\n    comment: 'Frames are not implemented in workers',\n    disabledTests: true,\n  },\n  'FileSystemBaseHandle-postMessage-windows.https.window.js': {\n    comment: 'postMessage is not implemented in workers',\n    disabledTests: true,\n  },\n  'FileSystemBaseHandle-postMessage-workers.https.window.js': {\n    comment: 'postMessage is not implemented in workers',\n    disabledTests: true,\n  },\n  'FileSystemBaseHandle-remove.https.any.js': {\n    comment: '...',\n    disabledTests: [\n      // Intentionally unsupported\n      'can remove the root of a sandbox file system',\n    ],\n  },\n  'FileSystemDirectoryHandle-getDirectoryHandle.https.any.js': {\n    comment: '...',\n    expectedFailures: [],\n  },\n  'FileSystemDirectoryHandle-getFileHandle.https.any.js': {\n    comment: '...',\n    expectedFailures: [],\n  },\n  'FileSystemDirectoryHandle-iteration.https.any.js': {\n    comment: '...',\n    expectedFailures: [],\n  },\n  'FileSystemDirectoryHandle-removeEntry.https.any.js': {\n    comment: '...',\n    expectedFailures: [],\n  },\n  'FileSystemDirectoryHandle-resolve.https.any.js': {\n    comment:\n      'We currently do not implement the resolve() method on directories',\n    disabledTests: [\n      'Resolve returns empty array for same directory',\n      'Resolve returns correct path',\n      'Resolve returns correct path with non-ascii characters',\n      'Resolve returns null when entry is not a child',\n    ],\n  },\n  'FileSystemFileHandle-create-sync-access-handle.https.window.js': {\n    comment: 'We do not yet implement sync access handles',\n    disabledTests: ['Attempt to create a sync access handle.'],\n  },\n  'FileSystemFileHandle-getFile.https.any.js': {\n    comment: '...',\n    expectedFailures: [\n      // TODO(node-fs): We currently do not implement time stamp modification\n      // of files via webfs so this test is expected to fail. This is temporary\n      // until we fully implement these bits, so leaving this as \"expectedFailures\".\n      'getFile() returns last modified time',\n    ],\n  },\n  'FileSystemFileHandle-move.https.any.js': {\n    comment:\n      'We do not currently implement the move() method on file system handles',\n    expectedFailures: [\n      // TODO(node-fs): Implement move and enable these tests\n      'move(name) to rename a file',\n      'get a handle to a moved file',\n      'move(name) to rename a file the same name',\n      'move(\"\") to rename a file fails',\n      'move(name) can be called multiple times',\n      'move(name) with a name with path separators should fail',\n      'move(name) while the file has an open writable fails',\n      'move(name) while the destination file has an open writable fails',\n      'move(name) can overwrite an existing file',\n      'move(dir, name) to rename a file',\n      'move(dir, name) to rename a file the same name',\n      'move(dir) to move a file to a new directory',\n      'move(dir, \"\") to move a file to a new directory fails',\n      'move(dir, name) to move a file to a new directory',\n      'move(dir) can be called multiple times',\n      'move(dir, name) can be called multiple times',\n      'move(dir, name) with a name with invalid characters should fail',\n      'move(dir) while the file has an open writable fails',\n      'move(dir, name) while the file has an open writable fails',\n      'move(dir) while the destination file has an open writable fails',\n      'move(dir) can overwrite an existing file',\n      'move(dir, name) while the destination file has an open writable fails',\n      'move(dir, name) can overwrite an existing file',\n      'FileSystemFileHandles are references, not paths',\n    ],\n  },\n  'FileSystemSyncAccessHandle-close.https.worker.js': {\n    comment: 'We currently do not implement sync access handles',\n    disabledTests: true,\n  },\n  'FileSystemSyncAccessHandle-flush.https.worker.js': {\n    comment: 'We currently do not implement sync access handles',\n    disabledTests: true,\n  },\n  'FileSystemSyncAccessHandle-getSize.https.worker.js': {\n    comment: 'We currently do not implement sync access handles',\n    disabledTests: true,\n  },\n  'FileSystemSyncAccessHandle-read-write.https.worker.js': {\n    comment: 'We currently do not implement sync access handles',\n    disabledTests: true,\n  },\n  'FileSystemSyncAccessHandle-truncate.https.worker.js': {\n    comment: 'We currently do not implement sync access handles',\n    disabledTests: true,\n  },\n  'FileSystemWritableFileStream-piped.https.any.js': {\n    comment: 'The test requires streams/resources/recording-streams.js',\n    // TODO(node-fs): These tests require a resource file from the streams wpt\n    // tests...\n    disabledTests: true,\n  },\n  'FileSystemWritableFileStream-write.https.any.js': {\n    comment: '...',\n    disabledTests: [\n      // We don't support this case. In the test a Blob is created from a\n      // file that is then removed, then that Blob is written to a file.\n      // Because our blob contains a copy of the original file's data\n      // and is not a live-reference, the expected error does not occur.\n      'write() with an invalid blob to an empty file should reject',\n    ],\n  },\n  'FileSystemWritableFileStream.https.any.js': {\n    comment:\n      'There is currently a bug in the web platform tests that causes this to fail',\n    expectedFailures: [\n      'createWritable() can be called on two handles representing the same file',\n    ],\n  },\n  'idlharness.https.any.js': {\n    comment:\n      'Remaining IDL failures: prototype chain issues, operation .length mismatches',\n    expectedFailures: [\n      'FileSystemHandle interface: operation isSameEntry(FileSystemHandle)',\n      'FileSystemFileHandle interface: existence and properties of interface object',\n      'FileSystemFileHandle interface: operation getFile()',\n      'FileSystemFileHandle interface: operation createWritable(optional FileSystemCreateWritableOptions)',\n      'FileSystemDirectoryHandle interface: existence and properties of interface object',\n      'FileSystemDirectoryHandle interface: operation getFileHandle(USVString, optional FileSystemGetFileOptions)',\n      'FileSystemDirectoryHandle interface: operation getDirectoryHandle(USVString, optional FileSystemGetDirectoryOptions)',\n      'FileSystemDirectoryHandle interface: operation removeEntry(USVString, optional FileSystemRemoveOptions)',\n      'FileSystemDirectoryHandle interface: operation resolve(FileSystemHandle)',\n      'FileSystemWritableFileStream interface: existence and properties of interface object',\n      'FileSystemWritableFileStream interface: operation write(FileSystemWriteChunkType)',\n      'FileSystemWritableFileStream interface: operation seek(unsigned long long)',\n      'FileSystemWritableFileStream interface: operation truncate(unsigned long long)',\n      'StorageManager interface: operation getDirectory()',\n    ],\n  },\n  'opaque-origin.https.window.js': {\n    comment: 'Not relevant to workers',\n    disabledTests: true,\n  },\n  'root-name.https.any.js': {\n    comment:\n      'Our test harness does not actually expose the root directory to the tests',\n    disabledTests: [\n      'getDirectory returns a directory whose name is the empty string',\n    ],\n  },\n\n  'script-tests/FileSystemBaseHandle-IndexedDB.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemBaseHandle-buckets.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemBaseHandle-getUniqueId.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemBaseHandle-isSameEntry.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemBaseHandle-postMessage-BroadcastChannel.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemBaseHandle-postMessage-Error.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemBaseHandle-postMessage-MessagePort-frames.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemBaseHandle-postMessage-MessagePort-windows.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemBaseHandle-postMessage-MessagePort-workers.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemBaseHandle-postMessage-frames.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemBaseHandle-postMessage-windows.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemBaseHandle-postMessage-workers.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemBaseHandle-remove.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemDirectoryHandle-getDirectoryHandle.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemDirectoryHandle-getFileHandle.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemDirectoryHandle-iteration.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemDirectoryHandle-removeEntry.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemDirectoryHandle-resolve.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemFileHandle-create-sync-access-handle.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemFileHandle-getFile.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemFileHandle-move.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemObserver-writable-file-stream.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemObserver.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemSyncAccessHandle-flush.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemWritableFileStream-piped.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemWritableFileStream-write.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n  'script-tests/FileSystemWritableFileStream.js': {\n    comment: 'script-test files are imported and tested above',\n    disabledTests: true,\n  },\n} satisfies TestRunnerConfig;\n"
  },
  {
    "path": "src/wpt/harness/assertions.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright © web-platform-tests contributors. BSD license\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport {\n  strictEqual,\n  notStrictEqual,\n  deepStrictEqual,\n  ok,\n  throws,\n  fail,\n  rejects,\n  match,\n  type AssertPredicate,\n} from 'node:assert';\n\nimport { type Test } from './test';\nimport { sanitize_unpaired_surrogates } from './common';\n\ndeclare global {\n  var AssertionError: new (message: string) => Error;\n\n  function assert_equals(a: unknown, b: unknown, message?: string): void;\n  function assert_not_equals(a: unknown, b: unknown, message?: string): void;\n  function assert_true(val: unknown, message?: string): void;\n  function assert_false(val: unknown, message?: string): void;\n  function assert_array_equals(\n    actual: unknown[],\n    expected: unknown[],\n    description?: string\n  ): void;\n  function assert_object_equals(a: unknown, b: unknown, message?: string): void;\n  function assert_implements(condition: unknown, description?: string): void;\n  function assert_implements_optional(\n    condition: unknown,\n    description?: string\n  ): void;\n  function assert_unreached(description?: string): void;\n  function assert_throws_js(\n    constructor: AssertPredicate,\n    func: ThrowingFn,\n    description?: string\n  ): void;\n  function assert_throws_exactly(\n    exception: AssertPredicate,\n    fn: ThrowingFn,\n    description?: string\n  ): void;\n  function assert_throws_dom(\n    type: number | string,\n    funcOrConstructor: ThrowingFn | typeof DOMException,\n    descriptionOrFunc: string | ThrowingFn,\n    maybeDescription?: string\n  ): void;\n  function assert_throws_quotaexceedederror(\n    func: ThrowingFn,\n    description?: string\n  ): void;\n  function promise_rejects_dom(\n    test: Test,\n    type: number | string,\n    promiseOrConstructor: Promise<unknown> | typeof DOMException,\n    descriptionOrPromise: Promise<unknown> | string,\n    maybeDescription?: string\n  ): Promise<unknown>;\n\n  function assert_own_property(\n    object: object,\n    property_name: string | symbol,\n    description?: string\n  ): void;\n  function assert_not_own_property(\n    object: object,\n    property_name: string | symbol,\n    description?: string\n  ): void;\n  function promise_rejects_js(\n    test: Test,\n    constructor: typeof Error,\n    promise: Promise<unknown>,\n    description?: string\n  ): Promise<void>;\n  function assert_regexp_match(\n    actual: string,\n    expected: RegExp,\n    description?: string\n  ): void;\n  function assert_greater_than(\n    actual: number,\n    expected: number,\n    description?: string\n  ): void;\n\n  function assert_greater_than_equal(\n    actual: number,\n    expected: number,\n    description?: string\n  ): void;\n\n  function assert_less_than(\n    actual: number,\n    expected: number,\n    description?: string\n  ): void;\n\n  function assert_less_than_equal(\n    actual: number,\n    expected: number,\n    description?: string\n  ): void;\n\n  function promise_rejects_exactly(\n    test: Test,\n    exception: typeof Error,\n    promise: Promise<unknown>,\n    description?: string\n  ): Promise<void>;\n\n  function assert_in_array(\n    actual: unknown,\n    expected: unknown[],\n    description?: string\n  ): void;\n\n  function assert_class_string(\n    object: unknown,\n    class_string: string,\n    description?: string\n  ): void;\n\n  function assert_inherits(\n    object: unknown,\n    property_name: string,\n    description?: string\n  ): void;\n\n  function assert_idl_attribute(\n    object: unknown,\n    property_name: string,\n    description?: string\n  ): void;\n\n  function assert_readonly(\n    object: Record<PropertyKey, unknown>,\n    property_name: string,\n    description?: string\n  ): void;\n}\n\ntype ThrowingFn = () => unknown;\n\n/**\n * Exception type that represents a failing assert.\n * NOTE: This a custom error type defined by WPT - it's not the same as node:assert's AssertionError\n * @param message - Error message.\n */\ndeclare class AssertionError extends Error {}\n\nfunction AssertionError(this: AssertionError, message: string): void {\n  if (typeof message == 'string') {\n    message = sanitize_unpaired_surrogates(message);\n  }\n  this.message = message;\n}\n\n// eslint-disable-next-line  @typescript-eslint/no-unsafe-assignment -- eslint doesn't like \"old-style\" classes. Code is copied from WPT\nAssertionError.prototype = Object.create(Error.prototype);\n\nglobalThis.AssertionError = AssertionError;\n\ndeclare class OptionalFeatureUnsupportedError extends AssertionError {}\nfunction OptionalFeatureUnsupportedError(\n  this: OptionalFeatureUnsupportedError,\n  message: string\n): void {\n  AssertionError.call(this, message);\n}\n\n// eslint-disable-next-line  @typescript-eslint/no-unsafe-assignment -- eslint doesn't like \"old-style\" classes. Code is copied from WPT\nOptionalFeatureUnsupportedError.prototype = Object.create(\n  AssertionError.prototype\n);\n\nglobalThis.assert_equals = (a, b, message): void => {\n  strictEqual(a, b, message);\n};\n\nglobalThis.assert_not_equals = (a, b, message): void => {\n  notStrictEqual(a, b, message);\n};\n\nglobalThis.assert_true = (val, message): void => {\n  strictEqual(val, true, message);\n};\n\nglobalThis.assert_false = (val, message): void => {\n  strictEqual(val, false, message);\n};\n\n/**\n * Assert that ``actual`` and ``expected`` are both arrays, and that the array properties of\n * ``actual`` and ``expected`` are all the same value (as for :js:func:`assert_equals`).\n *\n * @param actual - Test array.\n * @param expected - Array that is expected to contain the same values as ``actual``.\n * @param [description] - Description of the condition being tested.\n */\nglobalThis.assert_array_equals = (actual, expected, description): void => {\n  strictEqual(actual.length, expected.length, description);\n\n  for (let i = 0; i < actual.length; i++) {\n    strictEqual(\n      Object.prototype.hasOwnProperty.call(actual, i),\n      Object.prototype.hasOwnProperty.call(expected, i),\n      description\n    );\n    strictEqual(actual[i], expected[i], description);\n  }\n};\n\nglobalThis.assert_object_equals = (a, b, message): void => {\n  deepStrictEqual(a, b, message);\n};\n\n/**\n * Assert that a feature is implemented, based on a 'truthy' condition.\n *\n * This function should be used to early-exit from tests in which there is\n * no point continuing without support for a non-optional spec or spec\n * feature. For example:\n *\n *     assert_implements(window.Foo, 'Foo is not supported');\n *\n * @param condition The truthy value to test\n * @param [description] Error description for the case that the condition is not truthy.\n */\nglobalThis.assert_implements = (condition, description): void => {\n  ok(!!condition, description);\n};\n\n/**\n * Assert that an optional feature is implemented, based on a 'truthy' condition.\n *\n * This function should be used to early-exit from tests in which there is\n * no point continuing without support for an explicitly optional spec or\n * spec feature. For example:\n *\n *     assert_implements_optional(video.canPlayType(\"video/webm\"),\n *                                \"webm video playback not supported\");\n *\n * @param condition The truthy value to test\n * @param [description] Error description for the case that the condition is not truthy.\n */\nglobalThis.assert_implements_optional = (condition, description): void => {\n  if (!condition) {\n    throw new OptionalFeatureUnsupportedError(description ?? '');\n  }\n};\n\n/**\n * Asserts if called. Used to ensure that a specific code path is\n * not taken e.g. that an error event isn't fired.\n *\n * @param [description] - Description of the condition being tested.\n */\nglobalThis.assert_unreached = (description): void => {\n  ok(false, `Reached unreachable code: ${description ?? 'undefined'}`);\n};\n\n/**\n * Assert a JS Error with the expected constructor is thrown.\n *\n * @param constructor The expected exception constructor.\n * @param func Function which should throw.\n * @param [description] Error description for the case that the error is not thrown.\n */\nglobalThis.assert_throws_js = (constructor, func, description): void => {\n  throws(\n    () => {\n      func.call(this);\n    },\n    constructor,\n    description\n  );\n};\n\n/**\n * Assert the provided value is thrown.\n *\n * @param exception The expected exception.\n * @param fn Function which should throw.\n * @param [description] Error description for the case that the error is not thrown.\n */\nglobalThis.assert_throws_exactly = (exception, fn, description): void => {\n  try {\n    fn.call(this);\n  } catch (err) {\n    strictEqual(\n      err,\n      exception,\n      description ?? \"Thrown exception doesn't match expected value\"\n    );\n    return;\n  }\n\n  fail(description ?? 'No exception was thrown');\n};\n\n// Maps legacy DOMException code names (e.g. \"INVALID_ACCESS_ERR\") to modern names\n// (e.g. \"InvalidAccessError\"). Matches the upstream WPT testharness.js.\nconst codename_name_map: Record<string, string> = {\n  INDEX_SIZE_ERR: 'IndexSizeError',\n  HIERARCHY_REQUEST_ERR: 'HierarchyRequestError',\n  WRONG_DOCUMENT_ERR: 'WrongDocumentError',\n  INVALID_CHARACTER_ERR: 'InvalidCharacterError',\n  NO_MODIFICATION_ALLOWED_ERR: 'NoModificationAllowedError',\n  NOT_FOUND_ERR: 'NotFoundError',\n  NOT_SUPPORTED_ERR: 'NotSupportedError',\n  INUSE_ATTRIBUTE_ERR: 'InUseAttributeError',\n  INVALID_STATE_ERR: 'InvalidStateError',\n  SYNTAX_ERR: 'SyntaxError',\n  INVALID_MODIFICATION_ERR: 'InvalidModificationError',\n  NAMESPACE_ERR: 'NamespaceError',\n  INVALID_ACCESS_ERR: 'InvalidAccessError',\n  TYPE_MISMATCH_ERR: 'TypeMismatchError',\n  SECURITY_ERR: 'SecurityError',\n  NETWORK_ERR: 'NetworkError',\n  ABORT_ERR: 'AbortError',\n  URL_MISMATCH_ERR: 'URLMismatchError',\n  QUOTA_EXCEEDED_ERR: 'QuotaExceededError',\n  TIMEOUT_ERR: 'TimeoutError',\n  INVALID_NODE_TYPE_ERR: 'InvalidNodeTypeError',\n  DATA_CLONE_ERR: 'DataCloneError',\n};\n\n// Maps modern DOMException names to their legacy numeric codes.\nconst name_code_map: Record<string, number> = {\n  IndexSizeError: 1,\n  HierarchyRequestError: 3,\n  WrongDocumentError: 4,\n  InvalidCharacterError: 5,\n  NoModificationAllowedError: 7,\n  NotFoundError: 8,\n  NotSupportedError: 9,\n  InUseAttributeError: 10,\n  InvalidStateError: 11,\n  SyntaxError: 12,\n  InvalidModificationError: 13,\n  NamespaceError: 14,\n  InvalidAccessError: 15,\n  TypeMismatchError: 17,\n  SecurityError: 18,\n  NetworkError: 19,\n  AbortError: 20,\n  URLMismatchError: 21,\n  QuotaExceededError: 22,\n  TimeoutError: 23,\n  InvalidNodeTypeError: 24,\n  DataCloneError: 25,\n  // Modern exceptions with code 0\n  EncodingError: 0,\n  NotReadableError: 0,\n  UnknownError: 0,\n  ConstraintError: 0,\n  DataError: 0,\n  TransactionInactiveError: 0,\n  ReadOnlyError: 0,\n  VersionError: 0,\n  OperationError: 0,\n  NotAllowedError: 0,\n  OptOutError: 0,\n};\n\nconst code_name_map: Record<number, string> = {};\nfor (const key in name_code_map) {\n  if ((name_code_map[key] as number) > 0) {\n    code_name_map[name_code_map[key] as number] = key;\n  }\n}\n\n// Helper for assert_throws_dom_impl assertions. Throws a WPT AssertionError\n// (not node:assert's AssertionError) so instanceof checks work correctly.\nfunction assert_dom(\n  condition: boolean,\n  assertion_type: string,\n  description: string | undefined,\n  message: string\n): void {\n  if (condition) return;\n  const prefix = description ? `${description}: ` : '';\n  throw new AssertionError(`${assertion_type}: ${prefix}${message}`);\n}\n\n/**\n * Internal implementation of assert_throws_dom, matching the upstream WPT\n * testharness.js assert_throws_dom_impl. Uses its own try/catch rather than\n * node:assert.throws() so that legacy DOMException code names are mapped to\n * modern names and error properties are checked individually with clear\n * messages.\n */\nfunction assert_throws_dom_impl(\n  type: number | string,\n  func: ThrowingFn,\n  description: string,\n  assertion_type: string,\n  constructor: typeof DOMException\n): void {\n  try {\n    func();\n    assert_dom(false, assertion_type, description, 'function did not throw');\n  } catch (e) {\n    if (e instanceof AssertionError) {\n      throw e;\n    }\n\n    // Basic sanity-checks on the thrown exception.\n    assert_dom(\n      typeof e === 'object',\n      assertion_type,\n      description,\n      `thrown value is not an object (got ${typeof e})`\n    );\n    assert_dom(e !== null, assertion_type, description, 'thrown value is null');\n    assert_dom(\n      typeof type === 'number' || typeof type === 'string',\n      assertion_type,\n      description,\n      'type is not a number or string'\n    );\n\n    const required_props: Record<string, unknown> = {};\n    let name: string;\n\n    if (typeof type === 'number') {\n      assert_dom(\n        type in code_name_map,\n        assertion_type,\n        description,\n        `Test bug: unrecognized DOMException code \"${type}\" passed to ${assertion_type}()`\n      );\n      name = code_name_map[type] as string;\n      required_props['code'] = type;\n    } else {\n      // Map legacy code names (e.g. \"INVALID_ACCESS_ERR\") to modern names\n      name =\n        type in codename_name_map ? (codename_name_map[type] as string) : type;\n      assert_dom(\n        name in name_code_map,\n        assertion_type,\n        description,\n        `Test bug: unrecognized DOMException code name or name \"${type}\" passed to ${assertion_type}()`\n      );\n      required_props['code'] = name_code_map[name];\n    }\n\n    if (\n      required_props['code'] === 0 ||\n      ('name' in (e as object) &&\n        (e as { name: string }).name !==\n          (e as { name: string }).name.toUpperCase() &&\n        (e as { name: string }).name !== 'DOMException')\n    ) {\n      // New style exception: also test the name property.\n      required_props['name'] = name;\n    }\n\n    for (const prop in required_props) {\n      assert_dom(\n        prop in (e as object) &&\n          (e as Record<string, unknown>)[prop] == required_props[prop],\n        assertion_type,\n        description,\n        `thrown exception is not a DOMException ${type}: property ${prop} is equal to ${(e as Record<string, unknown>)[prop]}, expected ${required_props[prop]}`\n      );\n    }\n\n    // Check that the exception is from the right global. This check is last\n    // so more specific, and more informative, checks on the properties can\n    // happen in case a totally incorrect exception is thrown.\n    assert_dom(\n      (e as object).constructor === constructor,\n      assertion_type,\n      description,\n      'thrown exception from the wrong global'\n    );\n  }\n}\n\n/**\n * Assert a DOMException with the expected type is thrown.\n *\n * There are two ways of calling assert_throws_dom:\n *\n * 1) If the DOMException is expected to come from the current global, the\n * second argument should be the function expected to throw and a third,\n * optional, argument is the assertion description.\n *\n * 2) If the DOMException is expected to come from some other global, the\n * second argument should be the DOMException constructor from that global,\n * the third argument the function expected to throw, and the fourth, optional,\n * argument the assertion description.\n *\n * @param type - The expected exception name or\n * code.  See the `table of names and codes\n * <https://webidl.spec.whatwg.org/#dfn-error-names-table>`_. If a\n * number is passed it should be one of the numeric code values in\n * that table (e.g. 3, 4, etc).  If a string is passed it can\n * either be an exception name (e.g. \"HierarchyRequestError\",\n * \"WrongDocumentError\") or the name of the corresponding error\n * code (e.g. \"``HIERARCHY_REQUEST_ERR``\", \"``WRONG_DOCUMENT_ERR``\").\n * @param descriptionOrFunc - The function expected to\n * throw (if the exception comes from another global), or the\n * optional description of the condition being tested (if the\n * exception comes from the current global).\n * @param [maybeDescription] - Description of the condition\n * being tested (if the exception comes from another global).\n *\n */\nglobalThis.assert_throws_dom = (\n  type,\n  funcOrConstructor,\n  descriptionOrFunc,\n  maybeDescription\n): void => {\n  let constructor: typeof DOMException;\n  let func: ThrowingFn;\n  let description: string;\n\n  if (funcOrConstructor.name === 'DOMException') {\n    constructor = funcOrConstructor as typeof DOMException;\n    func = descriptionOrFunc as ThrowingFn;\n    description = maybeDescription as string;\n  } else {\n    constructor = DOMException;\n    func = funcOrConstructor as ThrowingFn;\n    description = descriptionOrFunc as string;\n    ok(\n      maybeDescription === undefined,\n      'Too many args passed to no-constructor version of assert_throws_dom'\n    );\n  }\n\n  assert_throws_dom_impl(\n    type,\n    func,\n    description,\n    'assert_throws_dom',\n    constructor\n  );\n};\n\n/**\n * Assert a QuotaExceededError DOMException is thrown.\n *\n * This is a convenience wrapper around assert_throws_dom for the common case\n * of checking for QuotaExceededError.\n *\n * @param func - Function which should throw.\n * @param [description] - Description of the condition being tested.\n */\nglobalThis.assert_throws_quotaexceedederror = (func, description): void => {\n  assert_throws_dom('QuotaExceededError', func, description ?? '');\n};\n\n/**\n * Assert that a Promise is rejected with the right DOMException.\n *\n * For the remaining arguments, there are two ways of calling\n * promise_rejects_dom:\n *\n * 1) If the DOMException is expected to come from the current global, the\n * third argument should be the promise expected to reject, and a fourth,\n * optional, argument is the assertion description.\n *\n * 2) If the DOMException is expected to come from some other global, the\n * third argument should be the DOMException constructor from that global,\n * the fourth argument the promise expected to reject, and the fifth,\n * optional, argument the assertion description.\n *\n * @param _test - the `Test` to use for the assertion.\n * @param type - See documentation for\n * `assert_throws_dom <#assert_throws_dom>`_.\n * @param promiseOrConstructor - Either the constructor\n * for the expected exception (if the exception comes from another\n * global), or the promise that's expected to reject (if the\n * exception comes from the current global).\n * @param descriptionOrPromise - Either the\n * promise that's expected to reject (if the exception comes from\n * another global), or the optional description of the condition\n * being tested (if the exception comes from the current global).\n * @param [maybeDescription] - Description of the condition\n * being tested (if the exception comes from another global).\n *\n */\nglobalThis.promise_rejects_dom = (\n  _test,\n  type,\n  promiseOrConstructor,\n  descriptionOrPromise,\n  maybeDescription\n): Promise<unknown> => {\n  let constructor, promise, description;\n  if (\n    typeof promiseOrConstructor === 'function' &&\n    promiseOrConstructor.name === 'DOMException'\n  ) {\n    constructor = promiseOrConstructor;\n    promise = descriptionOrPromise as Promise<unknown>;\n    description = maybeDescription as string;\n  } else {\n    constructor = DOMException;\n    promise = promiseOrConstructor as Promise<unknown>;\n    description = descriptionOrPromise as string;\n    strictEqual(\n      maybeDescription,\n      undefined,\n      'Too many args passed to no-constructor version of promise_rejects_dom, or accidentally explicitly passed undefined'\n    );\n  }\n\n  return promise\n    .then(() => {\n      assert_unreached('Should have rejected: ' + description);\n    })\n    .catch(function (e: unknown) {\n      assert_throws_dom(\n        type,\n        constructor,\n        function () {\n          throw e;\n        },\n        description\n      );\n    });\n};\n\n/**\n * Assert that ``object`` has an own property with name ``property_name``.\n *\n * @param object - Object that should have the given property.\n * @param property_name - Property name to test.\n * @param [description] - Description of the condition being tested.\n */\nglobalThis.assert_own_property = (object, property_name, description): void => {\n  ok(\n    Object.prototype.hasOwnProperty.call(object, property_name),\n    `expected property ${String(property_name)} missing on object: ` +\n      (description ?? '')\n  );\n};\n\n/**\n * Assert that ``object`` does not have an own property with name ``property_name``.\n *\n * @param object - Object that should not have the given property.\n * @param property_name - Property name to test.\n * @param [description] - Description of the condition being tested.\n */\nglobalThis.assert_not_own_property = (\n  object,\n  property_name,\n  description\n): void => {\n  ok(\n    !Object.prototype.hasOwnProperty.call(object, property_name),\n    `unexpected property ${String(property_name)} is found on object: ` +\n      (description ?? '')\n  );\n};\n\n/**\n * Assert that a Promise is rejected with the right ECMAScript exception.\n *\n * @param test - the `Test` to use for the assertion.\n * @param constructor - The expected exception constructor.\n * @param promise - The promise that's expected to\n * reject with the given exception.\n * @param [description] Error message to add to assert in case of\n *                               failure.\n */\nglobalThis.promise_rejects_js = async (\n  _test,\n  constructor,\n  promise,\n  description\n): Promise<void> => {\n  return rejects(promise, constructor, description);\n};\n\n/**\n * Assert that ``actual`` matches the RegExp ``expected``.\n *\n * @param actual - Test string.\n * @param expected - RegExp ``actual`` must match.\n * @param [description] - Description of the condition being tested.\n */\nglobalThis.assert_regexp_match = (actual, expected, description): void => {\n  match(actual, expected, description);\n};\n\n/**\n * Assert that ``actual`` is a number greater than ``expected``.\n *\n * @param actual - Test value.\n * @param expected - Number that ``actual`` must be greater than.\n * @param [description] - Description of the condition being tested.\n */\nglobalThis.assert_greater_than = (actual, expected, description): void => {\n  ok(actual > expected, description);\n};\n\n/**\n * Assert that ``actual`` is a number greater than or equal to ``expected``.\n *\n * @param actual - Test value.\n * @param expected - Number that ``actual`` must be greater than or equal to.\n * @param [description] - Description of the condition being tested.\n */\nglobalThis.assert_greater_than_equal = (\n  actual,\n  expected,\n  description\n): void => {\n  ok(actual >= expected, description);\n};\n\n/**\n * Assert that ``actual`` is a number less than ``expected``.\n *\n * @param actual - Test value.\n * @param expected - Number that ``actual`` must be less than.\n * @param [description] - Description of the condition being tested.\n */\nglobalThis.assert_less_than = (actual, expected, description): void => {\n  ok(actual < expected, description);\n};\n\n/**\n * Assert that ``actual`` is a number less than or equal to ``expected``.\n *\n * @param actual - Test value.\n * @param expected - Number that ``actual`` must be less than or equal to.\n * @param [description] - Description of the condition being tested.\n */\nglobalThis.assert_less_than_equal = (actual, expected, description): void => {\n  ok(actual <= expected, description);\n};\n\n/**\n * Assert that a Promise is rejected with the provided value.\n *\n * @param test - the `Test` to use for the assertion.\n * @param exception - The expected value of the rejected promise.\n * @param promise - The promise that's expected to\n * reject.\n * @param [description] Error message to add to assert in case of\n *                               failure.\n */\nglobalThis.promise_rejects_exactly = (\n  _test,\n  exception,\n  promise,\n  description\n): Promise<void> => {\n  return promise\n    .then(() => {\n      assert_unreached(`Should have rejected: ${description}`);\n    })\n    .catch((exc: unknown) => {\n      assert_throws_exactly(\n        exception,\n        () => {\n          throw exc;\n        },\n        description\n      );\n    });\n};\n\n/**\n * Assert that ``expected`` is an array and ``actual`` is one of the members.\n * This is implemented using ``indexOf``, so doesn't handle NaN or ±0 correctly.\n *\n * @param actual - Test value.\n * @param expected - An array that ``actual`` is expected to\n * be a member of.\n * @param [description] - Description of the condition being tested.\n */\nglobalThis.assert_in_array = (actual, expected, description): void => {\n  notStrictEqual(\n    expected.indexOf(actual),\n    -1,\n    `assert_in_array ${description}: value ${actual} not in array ${expected}`\n  );\n};\n\n/**\n * Assert that ``object``'s class string (from ``Object.prototype.toString``)\n * matches the expected value.\n *\n * @param object - The object to check.\n * @param class_string - Expected class string (without the ``[object ...]`` wrapper).\n * @param [description] - Description of the condition being tested.\n */\nglobalThis.assert_class_string = (object, class_string, description): void => {\n  const actual = {}.toString.call(object);\n  const expected = `[object ${class_string}]`;\n  strictEqual(actual, expected, description);\n};\n\n/**\n * Assert that ``object`` does not have an own property named ``property_name``\n * but that ``property_name`` is accessible through the prototype chain.\n *\n * @param object - Object to test.\n * @param property_name - Property that should be inherited.\n * @param [description] - Description of the condition being tested.\n */\nglobalThis.assert_inherits = (object, property_name, description): void => {\n  ok(\n    (typeof object === 'object' && object !== null) ||\n      typeof object === 'function',\n    `assert_inherits: ${description ?? ''}: provided value is not an object`\n  );\n\n  ok(\n    'hasOwnProperty' in (object as object),\n    `assert_inherits: ${description ?? ''}: provided value has no hasOwnProperty method`\n  );\n\n  ok(\n    !Object.prototype.hasOwnProperty.call(object, property_name),\n    `assert_inherits: ${description ?? ''}: property ${property_name} found on object, expected in prototype chain`\n  );\n\n  ok(\n    property_name in (object as object),\n    `assert_inherits: ${description ?? ''}: property ${property_name} not found in prototype chain`\n  );\n};\n\n/**\n * Alias for ``assert_inherits``. Asserts that the given property is an IDL\n * attribute (i.e., is inherited via the prototype chain, not an own property).\n */\nglobalThis.assert_idl_attribute = (\n  object: unknown,\n  property_name: string,\n  description?: string\n): void => {\n  assert_inherits(object, property_name, description);\n};\n\n/**\n * Assert that ``object`` has a property ``property_name`` that is read-only\n * according to its property descriptor.  Walks the prototype chain to find\n * the descriptor, matching upstream WPT testharness.js behaviour.\n *\n * @param object - Object to test.\n * @param property_name - Name of the property that should be read-only.\n * @param [description] - Description of the condition being tested.\n */\nglobalThis.assert_readonly = (object, property_name, description): void => {\n  ok(\n    property_name in object,\n    `assert_readonly: ${description ?? ''}: property ${property_name} not found`\n  );\n\n  // Walk the prototype chain, same as upstream testharness.js.\n  let desc: PropertyDescriptor | undefined;\n  let current: object | null = object;\n  while (\n    current !== null &&\n    (desc = Object.getOwnPropertyDescriptor(current, property_name)) ===\n      undefined\n  ) {\n    current = Object.getPrototypeOf(current) as object | null;\n  }\n\n  ok(\n    desc !== undefined,\n    `assert_readonly: ${description ?? ''}: could not find a descriptor for property ${property_name}`\n  );\n  if ('value' in desc) {\n    strictEqual(\n      desc.writable,\n      false,\n      `assert_readonly: ${description ?? ''}: descriptor [[Writable]] expected false got ${desc.writable}`\n    );\n  } else if ('get' in desc || 'set' in desc) {\n    ok(\n      desc.set === undefined,\n      `assert_readonly: ${description ?? ''}: property ${property_name} is an accessor property with a [[Set]] attribute`\n    );\n  } else {\n    fail(\n      `assert_readonly: ${description ?? ''}: Object.getOwnPropertyDescriptor must return a fully populated property descriptor`\n    );\n  }\n};\n"
  },
  {
    "path": "src/wpt/harness/common.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright © web-platform-tests contributors. BSD license\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport path from 'node:path';\n\nexport type UnknownFunc = (...args: unknown[]) => unknown;\nexport type TestFn = UnknownFunc;\nexport type PromiseTestFn = () => Promise<unknown>;\n\nexport class FilterList {\n  // Matches any input\n  #matchesAll: boolean = false;\n\n  // List of strings to match exactly\n  #strings: Set<string> = new Set();\n\n  // List of regexps to match against\n  #regexps: RegExp[] = [];\n\n  // Regexes which never matched any of the inputs\n  // We keep this set so we can warn the user about this.\n  #unmatchedRegexps: Set<RegExp> = new Set();\n\n  constructor(filters: (string | RegExp)[] | true | undefined) {\n    if (filters === undefined) {\n      return;\n    }\n\n    if (filters === true) {\n      this.#matchesAll = true;\n      return;\n    }\n\n    for (const filter of filters) {\n      if (typeof filter === 'string') {\n        this.#strings.add(filter);\n      } else {\n        this.#regexps.push(filter);\n      }\n    }\n\n    this.#unmatchedRegexps = new Set(this.#regexps);\n  }\n\n  has(input: string): boolean {\n    if (this.#matchesAll) {\n      return true;\n    }\n\n    if (this.#strings.has(input)) {\n      return true;\n    }\n\n    return this.#regexps.some((r) => r.test(input));\n  }\n\n  delete(input: string): boolean {\n    if (this.#matchesAll) {\n      return true;\n    }\n\n    if (this.#strings.delete(input)) {\n      return true;\n    }\n\n    const maybeMatch = this.#regexps.find((r) => r.test(input));\n\n    if (maybeMatch !== undefined) {\n      this.#unmatchedRegexps.delete(maybeMatch);\n      return true;\n    }\n\n    return false;\n  }\n\n  getUnmatched(): Set<string | RegExp> {\n    return this.#strings.union(this.#unmatchedRegexps);\n  }\n}\n\nexport function sanitize_unpaired_surrogates(str: string): string {\n  // Test logs will be exported to XML, so we must escape any characters that\n  // are forbidden in an XML CDATA section, namely \"[...] the surrogate blocks,\n  // FFFE, and FFFF\".\n  // See https://www.w3.org/TR/REC-xml/#NT-Char\n\n  return str\n    .replace(\n      /([\\ud800-\\udbff]+)(?![\\udc00-\\udfff])|(^|[^\\ud800-\\udbff])([\\udc00-\\udfff]+)/g,\n      function (_, low?: string, prefix?: string, high?: string) {\n        let output = prefix || ''; // prefix may be undefined\n        const string: string = low || high || ''; // only one of these alternates can match\n\n        for (const ch of string) {\n          output += code_unit_str(ch);\n        }\n        return output;\n      }\n    )\n    .replace(/(\\uffff|\\ufffe)/g, function (_, invalid_chars?: string) {\n      let output = '';\n\n      for (const ch of invalid_chars || '') {\n        output += code_unit_str(ch);\n      }\n      return output;\n    });\n}\n\nfunction code_unit_str(char: string): string {\n  return 'U+' + char.charCodeAt(0).toString(16);\n}\n\nexport type HostInfo = {\n  REMOTE_HOST: string;\n  HTTP_ORIGIN: string;\n  HTTP_REMOTE_ORIGIN: string;\n  HTTPS_ORIGIN: string;\n  ORIGIN: string;\n  HTTPS_REMOTE_ORIGIN: string;\n  HTTP_PORT: string;\n  HTTPS_PORT: string;\n  WS_PORT: string;\n  WSS_PORT: string;\n};\n\nexport function getHostInfo(): HostInfo {\n  const httpUrl = globalThis.state.testUrl;\n\n  const httpsUrl = new URL(httpUrl);\n  httpsUrl.protocol = 'https';\n\n  // If the environment variable HTTPS_PORT is set, the wpt server is running as a sidecar.\n  // Update the URL's port so we can connect to it\n  httpsUrl.port = globalThis.state.env.HTTPS_PORT ?? '';\n\n  return {\n    REMOTE_HOST: httpUrl.hostname,\n    HTTP_ORIGIN: httpUrl.origin,\n    ORIGIN: httpUrl.origin,\n    HTTP_REMOTE_ORIGIN: httpUrl.origin,\n    HTTPS_ORIGIN: httpsUrl.origin,\n    HTTPS_REMOTE_ORIGIN: httpsUrl.origin,\n    HTTP_PORT: httpUrl.port,\n    HTTPS_PORT: httpsUrl.port,\n    WS_PORT: (globalThis.state.env.WS_PORT as string | undefined) ?? '',\n    WSS_PORT: (globalThis.state.env.WSS_PORT as string | undefined) ?? '',\n  };\n}\n\nexport function getBindingPath(base: string, rawPath: string): string {\n  if (path.isAbsolute(rawPath)) {\n    return rawPath;\n  }\n\n  return path.relative('/', path.resolve(base, rawPath));\n}\n"
  },
  {
    "path": "src/wpt/harness/globals.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright © web-platform-tests contributors. BSD license\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport path from 'node:path';\nimport { getBindingPath } from './common';\n\n// @ts-expect-error We're just exposing enough stuff for the tests to pass; it's not a perfect match\nglobalThis.self = globalThis;\n\n// Some WPT tests reference addEventListener at the top level during file evaluation.\n// workerd doesn't have addEventListener on the global (it uses module exports instead),\n// so we provide a no-op stub to allow test files to load without throwing.\n// Tests that actually need addEventListener functionality should be marked as omitted.\nglobalThis.addEventListener = (): void => {};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access --  We're just exposing enough stuff for the tests to pass; it's not a perfect match\nglobalThis.Window = Object.getPrototypeOf(globalThis).constructor;\n\nconst realFetch = globalThis.fetch;\nconst realRequest = globalThis.Request;\n\nfunction relativizeUrl(input: URL | string): string {\n  return new URL(input, globalThis.state.testUrl).href;\n}\n\nfunction relativizeRequest(\n  input: RequestInfo | URL,\n  init?: RequestInit\n): Request {\n  if (input instanceof Request) {\n    return new realRequest(\n      relativizeRequest(input.url),\n      new realRequest(input, init)\n    );\n  } else if (input instanceof URL) {\n    return new realRequest(relativizeUrl(input), init);\n  } else {\n    return new realRequest(relativizeUrl(input), init);\n  }\n}\n\nglobalThis.Request = class _Request extends Request {\n  constructor(input: RequestInfo | URL, init?: RequestInit) {\n    super(relativizeRequest(input, init));\n  }\n};\n\nglobalThis.Response = class _Response extends Response {\n  static override redirect(url: string | URL, status?: number): Response {\n    return super.redirect(relativizeUrl(url), status);\n  }\n};\n\nglobalThis.fetch = async (\n  input: RequestInfo | URL,\n  init?: RequestInit\n): Promise<Response> => {\n  if (typeof input === 'string' && input.endsWith('.json')) {\n    const relativePath = getBindingPath(\n      path.dirname(globalThis.state.testFileName),\n      input\n    );\n    // WPT sometimes uses fetch to load a resource file, we \"serve\" this from the bindings\n    const exports: unknown = globalThis.state.env[relativePath];\n    if (exports === undefined) {\n      throw new Error(`Unable to load resources file ${input} from bindings`);\n    }\n\n    return new Response(JSON.stringify(exports));\n  }\n  return realFetch(relativizeRequest(input, init));\n};\n\nclass _Location {\n  get ancestorOrigins(): DOMStringList {\n    return {\n      length: 0,\n      item(_index: number): string | null {\n        return null;\n      },\n      contains(_string: string): boolean {\n        return false;\n      },\n    };\n  }\n\n  get hash(): string {\n    return globalThis.state.testUrl.hash;\n  }\n\n  get host(): string {\n    return globalThis.state.testUrl.host;\n  }\n\n  get hostname(): string {\n    return globalThis.state.testUrl.hostname;\n  }\n\n  get href(): string {\n    return globalThis.state.testUrl.href;\n  }\n\n  get origin(): string {\n    return globalThis.state.testUrl.origin;\n  }\n\n  get pathname(): string {\n    return globalThis.state.testUrl.pathname;\n  }\n\n  get port(): string {\n    return globalThis.state.testUrl.port;\n  }\n\n  get protocol(): string {\n    return globalThis.state.testUrl.protocol;\n  }\n\n  get search(): string {\n    return globalThis.state.testUrl.search;\n  }\n\n  assign(url: string): void {\n    globalThis.state.testUrl = new URL(url);\n  }\n\n  reload(): void {}\n\n  replace(url: string): void {\n    globalThis.state.testUrl = new URL(url);\n  }\n\n  toString(): string {\n    return globalThis.state.testUrl.href;\n  }\n}\n\nglobalThis.location = new _Location();\n"
  },
  {
    "path": "src/wpt/harness/harness.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright © web-platform-tests contributors. BSD license\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport { default as path } from 'node:path';\nimport {\n  FilterList,\n  type UnknownFunc,\n  sanitize_unpaired_surrogates,\n  getHostInfo,\n  getBindingPath,\n} from './common';\n\nimport { Test } from './test';\n\n// These imports introduce functions into the global scope, so that WPT tests can call them\n\n// Test, promise_test, async_test, test\nimport './test';\n\n// WPT assert_ functions\nimport './assertions';\n\n// Other functions from the WPT harness that need to be exposed to tests\nimport './utils';\n\n// Modifications we need to make to global types for testing\nimport './globals';\n\ntype CommonOptions = {\n  // Brief explanation of why these options were set for a test\n  comment?: string;\n  // Print information about each subtest within this test as it runs\n  verbose?: boolean;\n  // Function executed before running the test\n  before?: () => void;\n  // Function executed after running the test\n  after?: () => void;\n  // Function that modifies the test code before it's run\n  replace?: (code: string) => string;\n  // Execute only this test\n  only?: boolean;\n  // If true, evaluate the test code within a top-level block. Otherwise, the test runs within the\n  // scope of an anonymous function.\n  runInGlobalScope?: boolean;\n};\n\ntype SuccessOptions = {\n  expectedFailures?: undefined;\n  disabledTests?: undefined;\n  omittedTests?: undefined;\n};\n\ntype ErrorOptions = {\n  // A comment is mandatory when there are expected failure, skipped tests or excluded tests\n  comment: string;\n\n  // Known errors when running the tests\n  expectedFailures?: (string | RegExp)[] | true;\n\n  // Tests that cannot be run for now, either due to harness limitations, workerd bugs or\n  // features not yet implemented.\n  disabledTests?: (string | RegExp)[] | true;\n\n  // Tests that we have decided not to use. These are omitted from results and coverage\n  // calculations.\n  omittedTests?: (string | RegExp)[] | true;\n};\n\ntype TestRunnerOptions = CommonOptions & (SuccessOptions | ErrorOptions);\n\nexport type TestRunnerConfig = {\n  [key: string]: TestRunnerOptions;\n};\n\ntype Env = {\n  unsafe: { eval: (code: string) => unknown };\n  SIDECAR_HOSTNAME: string | null;\n  HTTP_PORT: string | null;\n  HTTPS_PORT: string | null;\n  [key: string]: unknown;\n};\n\ntype TestCase = {\n  test(_: unknown, env: Env): Promise<void> | void;\n};\n\n// Singleton object used to pass test state between the runner and the harness functions available\n// to the evaled  WPT test code.\nclass RunnerState {\n  // URL corresponding to the current test file, based on the WPT directory structure.\n  testUrl: URL;\n\n  // Filename of the current test. Used in error messages.\n  testFileName: string;\n\n  // The worker environment. Used to allow fetching resources in the test.\n  env: Env;\n\n  // Makes test options available from assertion functions\n  options: TestRunnerOptions;\n\n  // A test is pushed to this list as soon as it is discovered\n  subtests: Test[] = [];\n\n  // Callbacks to be run once the entire test is done.\n  completionCallbacks: UnknownFunc[] = [];\n\n  constructor(\n    testUrl: URL,\n    testFileName: string,\n    env: Env,\n    options: TestRunnerOptions\n  ) {\n    this.testUrl = testUrl;\n    this.testFileName = testFileName;\n    this.env = env;\n    this.options = options;\n  }\n\n  async validate(): Promise<void> {\n    // Exception handling is set up on every promise in the test function that created it.\n    await Promise.all(this.subtests.map((t) => t.promise ?? Promise.resolve()));\n\n    for (const cleanFn of this.completionCallbacks) {\n      cleanFn();\n    }\n\n    const expectedFailures = new FilterList(this.options.expectedFailures);\n    const unexpectedFailures = [];\n\n    for (const subtest of this.subtests) {\n      const err = subtest.error;\n\n      if (err instanceof Error) {\n        if (!expectedFailures.delete(err.message)) {\n          err.message = sanitize_unpaired_surrogates(err.message);\n          console.warn(err);\n          unexpectedFailures.push(err.message);\n        } else if (this.options.verbose) {\n          err.message = sanitize_unpaired_surrogates(err.message);\n          console.warn('Expected failure: ', err);\n        }\n      }\n    }\n\n    if (unexpectedFailures.length > 0) {\n      console.error(\n        'The following tests unexpectedly failed:',\n        JSON.stringify(unexpectedFailures, null, 2)\n      );\n    }\n\n    const unexpectedSuccess = expectedFailures.getUnmatched();\n    // TODO(soon): Once we have a list of successful assertions, we should throw an error if a\n    // regex also matches successful tests. This can be done once we implement wpt.fyi report\n    // generation.\n\n    if (unexpectedSuccess.size > 0) {\n      console.error(\n        'The following tests were expected to fail but instead succeeded:',\n        JSON.stringify(\n          [...unexpectedSuccess].map((v) => v.toString()),\n          null,\n          2\n        )\n      );\n    }\n\n    if (unexpectedFailures.length > 0 || unexpectedSuccess.size > 0) {\n      throw new Error(\n        `${this.testFileName} failed. Please update the test config.`\n      );\n    }\n  }\n}\n\ndeclare global {\n  // Current RunnerState\n  var state: RunnerState;\n  // All RunnerStates (to get results later)\n  var results: { [file: string]: RunnerState };\n}\n\nconst COLORS = {\n  FOREGROUND: 30,\n  BACKGROUND: 40,\n  BLACK: 0,\n  RED: 1,\n  GREEN: 2,\n  YELLOW: 3,\n  BLUE: 4,\n  MAGENTA: 5,\n  CYAN: 6,\n  WHITE: 7,\n};\n\nfunction colorPrint(\n  fn: (...args: unknown[]) => void,\n  color: number\n): (...args: unknown[]) => void {\n  return (...args: unknown[]): void => {\n    fn(`\\x1b[${color}m`, ...args, '\\x1b[0m');\n  };\n}\n\nglobalThis.console.info = colorPrint(\n  console.info,\n  COLORS.FOREGROUND + COLORS.BLUE\n);\nglobalThis.console.warn = colorPrint(\n  console.warn,\n  COLORS.FOREGROUND + COLORS.YELLOW\n);\nglobalThis.console.error = colorPrint(\n  console.error,\n  COLORS.FOREGROUND + COLORS.RED\n);\n\nclass WPTMetadata {\n  // Specifies the Javascript global scopes for the test. (Not currently supported)\n  global: string[] = [];\n  // Specifies additional JS scripts to execute.\n  scripts: string[] = [];\n  // Adjusts the timeout for the tests in this file. (Not currently supported)\n  timeout?: string;\n  // Sets a human-readable title for the entire test file. (Not currently supported.)\n  title?: string;\n  // Specifies a variant of this test, which can be used in subsetTestByKey (Not currently supported.)\n  variants: URLSearchParams[] = [];\n}\n\nfunction parseWptMetadata(code: string): WPTMetadata {\n  const meta = new WPTMetadata();\n\n  for (const { groups } of code.matchAll(\n    /\\/\\/ META: (?<key>\\w+)=(?<value>.+)/g\n  )) {\n    if (!groups || !groups.key || !groups.value) {\n      continue;\n    }\n\n    switch (groups.key) {\n      case 'global':\n        meta.global = groups.value.split(',');\n        break;\n\n      case 'script': {\n        meta.scripts.push(groups.value);\n        break;\n      }\n\n      case 'timeout':\n        meta.timeout = groups.value;\n        break;\n\n      case 'title':\n        meta.title = groups.value;\n        break;\n\n      case 'variant':\n        meta.variants.push(new URLSearchParams(groups.value));\n        break;\n    }\n  }\n\n  return meta;\n}\n\nconst EXCLUDED_PATHS = new Set([\n  // Implemented in harness.ts\n  '/common/subset-tests-by-key.js',\n  '/common/subset-tests.js',\n  '/resources/utils.js',\n  '/common/utils.js',\n  '/common/get-host-info.sub.js',\n  '/common/sab.js',\n]);\n\nfunction replaceInterpolation(code: string): string {\n  const hostInfo = getHostInfo();\n\n  return code\n    .replace(/\\{\\{host\\}\\}/g, hostInfo.REMOTE_HOST)\n    .replace(/\\{\\{hosts\\[alt\\]\\[www\\]\\}\\}/g, hostInfo.REMOTE_HOST)\n    .replace(/\\{\\{ports\\[http\\]\\[0\\]\\}\\}/g, hostInfo.HTTP_PORT)\n    .replace(/\\{\\{ports\\[http\\]\\[1\\]\\}\\}/g, hostInfo.HTTP_PORT)\n    .replace(/\\{\\{ports\\[https\\]\\[0\\]\\}\\}/g, hostInfo.HTTPS_PORT)\n    .replace(/\\{\\{ports\\[ws\\]\\[0\\]\\}\\}/g, hostInfo.WS_PORT)\n    .replace(/\\{\\{ports\\[wss\\]\\[0\\]\\}\\}/g, hostInfo.WSS_PORT)\n    .replace(/\\{\\{ports\\[h2\\]\\[0\\]\\}\\}/g, hostInfo.HTTPS_PORT);\n}\n\nfunction getCodeAtPath(\n  env: Env,\n  base: string,\n  rawPath: string,\n  replace?: (code: string) => string\n): string {\n  const bindingPath = getBindingPath(base, rawPath);\n\n  if (EXCLUDED_PATHS.has(bindingPath)) {\n    return '';\n  }\n\n  if (typeof env[bindingPath] != 'string') {\n    // We only have access to the files explicitly declared in the .wd-test, not the full WPT\n    // checkout, so it's possible for tests to include things we can't load.\n    throw new Error(\n      `Test file ${bindingPath} not found. Update wpt_test.bzl to handle this case.`\n    );\n  }\n\n  let code = env[bindingPath];\n  if (replace) {\n    code = replace(code);\n  }\n\n  return replaceInterpolation(code);\n}\n\nfunction evalAsBlock(\n  env: Env,\n  runInGlobalScope: boolean,\n  files: string[]\n): void {\n  const code = files.join('\\n');\n\n  if (runInGlobalScope) {\n    // In this mode, the scope of const, let and function declarations is limited to stop tests\n    // from interfering with each other.\n    env.unsafe.eval(`{ ${code} }`);\n  } else {\n    // Additionally, use an IIFE to limit the scope of var and manipulation of the global scope\n    env.unsafe.eval(`(function () { ${code} })();`);\n  }\n}\n\ntype Runner = {\n  run: (file: string) => TestCase | Record<string, never>;\n  printResults: () => TestCase;\n};\n\nexport function createRunner(\n  config: TestRunnerConfig,\n  moduleBase: string,\n  allTestFiles: string[]\n): Runner {\n  const testsNotFound = new Set(Object.keys(config)).difference(\n    new Set(allTestFiles)\n  );\n\n  if (testsNotFound.size > 0) {\n    throw new Error(\n      `Configuration was provided for the following tests that have not been found in the WPT repo: ${[...testsNotFound].join(', ')}`\n    );\n  }\n\n  const onlyFlagUsed = Object.values(config).some((options) => options.only);\n  globalThis.results = {};\n\n  return {\n    run(file: string): TestCase | Record<string, never> {\n      if (onlyFlagUsed && !config[file]?.only) {\n        // Return an empty object to avoid printing extra output from all the other disabled test cases.\n        return {};\n      }\n\n      return {\n        async test(_: unknown, env: Env): Promise<void> {\n          return runTest(config[file], env, moduleBase, file);\n        },\n      };\n    },\n    printResults(): TestCase {\n      return {\n        test(_: unknown, env: Env): void {\n          printResults(config, allTestFiles, moduleBase, env);\n        },\n      };\n    },\n  };\n}\n\nasync function runTest(\n  options: TestRunnerOptions | undefined,\n  env: Env,\n  moduleBase: string,\n  file: string\n): Promise<void> {\n  if (!options) {\n    throw new Error(\n      `Missing test configuration for ${file}. Specify '${file}': {} for default options.`\n    );\n  }\n\n  const testUrl = new URL(\n    path.join(moduleBase, file),\n    `http://${env.SIDECAR_HOSTNAME ?? 'localhost'}`\n  );\n\n  // If the environment variable HTTP_PORT is set, the wpt server is running as a sidecar.\n  // Update the URL's port so we can connect to it\n  testUrl.port = env.HTTP_PORT ?? '';\n\n  globalThis.state = new RunnerState(testUrl, file, env, options);\n  globalThis.results[file] = globalThis.state;\n\n  if (options.disabledTests === true) {\n    console.warn(\n      `All tests in ${file} have been disabled because \"${options.comment}\".`\n    );\n    return;\n  }\n\n  if (options.omittedTests === true) {\n    console.warn(\n      `All tests in ${file} have been omitted because \"${options.comment}\".`\n    );\n    return;\n  }\n\n  const meta = parseWptMetadata(String(env[file]));\n\n  if (options.before) {\n    options.before();\n  }\n\n  const files = [];\n\n  for (const script of meta.scripts) {\n    files.push(getCodeAtPath(env, path.dirname(file), script));\n  }\n\n  files.push(getCodeAtPath(env, './', file, options.replace));\n  evalAsBlock(env, options.runInGlobalScope ?? false, files);\n\n  if (options.after) {\n    options.after();\n  }\n\n  // The WPT IDL harness (idlharness.js) uses eval() inside async promise callbacks\n  // (e.g., to create test objects via `eval('new URL(\"http://foo\")')`).  The workerd\n  // runtime only allows eval() while env.unsafe.eval() is on the call stack (the\n  // evalAllowed flag is reset once env.unsafe.eval returns).  Async test callbacks\n  // run when state.validate() awaits their promises, so native eval() would fail.\n  // Install the shim here — after evalAsBlock and options.after have finished — so\n  // it covers exactly the async-callback phase.\n  const originalEval = globalThis.eval;\n  globalThis.eval = ((code: string): unknown =>\n    env.unsafe.eval(code)) as typeof globalThis.eval;\n\n  try {\n    await globalThis.state.validate();\n  } finally {\n    globalThis.eval = originalEval;\n  }\n}\n\nfunction printResults(\n  config: TestRunnerConfig,\n  allTestFiles: string[],\n  moduleBase: string,\n  env: Env\n): void {\n  const results: string[] = [];\n\n  if (env.GEN_TEST_CONFIG) {\n    results.push(generateConfig(config, allTestFiles, moduleBase));\n  }\n\n  if (env.GEN_TEST_REPORT) {\n    results.push(generateReport(config));\n  }\n\n  if (env.GEN_TEST_STATS) {\n    results.push(generateStats(moduleBase, config));\n  }\n\n  console.log(results.join('\\n\\n***\\n\\n'));\n}\n\nfunction generateConfig(\n  config: TestRunnerConfig,\n  allTestFiles: string[],\n  moduleBase: string\n): string {\n  const generatedConfig: TestRunnerConfig = {};\n  for (const file of allTestFiles) {\n    // Copy config if the user has created any so far, else initialize to blank\n    generatedConfig[file] = config[file] ?? {};\n  }\n\n  return `\\x1b[1;7;35m*** Copy this config to src/wpt/${moduleBase}-test.ts ***\\x1b[0m\n\n// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { type TestRunnerConfig } from 'harness/harness';\n\nexport default ${JSON.stringify(generatedConfig, null, 2)} satisfies TestRunnerConfig;`;\n}\n\nclass WPTTestResult {\n  test: string;\n  status: 'OK' | 'ERROR';\n  subtests: WPTSubtestResult[] = [];\n  // TODO(soon): Track elapsed time\n  duration: number = 0;\n\n  constructor(result: RunnerState, options: TestRunnerOptions) {\n    this.test = WPTTestResult.#getTestNameFromUrl(result.testUrl);\n    this.status = options.disabledTests === true ? 'ERROR' : 'OK';\n    this.subtests = result.subtests.map((r) => new WPTSubtestResult(r));\n  }\n\n  static #getTestNameFromUrl(testUrl: URL): string {\n    const testNameUrl = new URL(testUrl);\n    testNameUrl.pathname = testNameUrl.pathname.replace('.js', '.html');\n    return testNameUrl.href.slice(testNameUrl.origin.length);\n  }\n}\n\nclass WPTSubtestResult {\n  name: string;\n  status: 'PASS' | 'FAIL';\n  message?: string;\n  isExpectedFailure?: true;\n\n  constructor(result: Test) {\n    this.name = sanitize_unpaired_surrogates(result.name);\n    if (result.error instanceof Error) {\n      this.message = result.error.message;\n      // TODO(soon): This is true in main, but not necessarily if you run a report in local dev\n      this.isExpectedFailure = true;\n      this.status = 'FAIL';\n    } else if (result.error === 'DISABLED') {\n      this.isExpectedFailure = true;\n      this.status = 'FAIL';\n    } else {\n      this.status = 'PASS';\n    }\n  }\n}\n\nfunction generateReport(config: TestRunnerConfig): string {\n  const results: WPTTestResult[] = [];\n  for (const [file, options] of Object.entries(config)) {\n    const testResult = globalThis.results[file];\n    if (!testResult) {\n      throw new Error(\n        `Unable to find test results for ${file}. This is probably a harness bug`\n      );\n    }\n\n    results.push(new WPTTestResult(testResult, options));\n  }\n\n  return JSON.stringify({ results }, null, 2);\n}\n\nclass Stats {\n  moduleBase: string;\n  coverage = new CoverageStats();\n  pass = new PassStats();\n\n  constructor(moduleBase: string) {\n    this.moduleBase = moduleBase;\n  }\n\n  toList(): unknown[] {\n    return [this.moduleBase, ...this.coverage.toList(), ...this.pass.toList()];\n  }\n}\nclass CoverageStats {\n  ok: number = 0;\n  disabled: number = 0;\n\n  get total(): number {\n    return this.ok + this.disabled;\n  }\n\n  get ok_percent(): number {\n    return (this.ok / this.total) * 100;\n  }\n\n  toList(): unknown[] {\n    return [this.ok, this.disabled, this.total, this.ok_percent.toFixed(0)];\n  }\n}\n\nclass PassStats {\n  pass: number = 0;\n  fail: number = 0;\n  disabled: number = 0;\n\n  get total(): number {\n    return this.pass + this.fail + this.disabled;\n  }\n\n  get pass_percent(): number {\n    return (this.pass / this.total) * 100;\n  }\n\n  toList(): unknown[] {\n    return [\n      this.pass,\n      this.fail,\n      this.disabled,\n      this.total,\n      this.pass_percent.toFixed(0),\n    ];\n  }\n}\n\nfunction generateStats(moduleBase: string, config: TestRunnerConfig): string {\n  const stats = new Stats(moduleBase);\n\n  for (const [file, options] of Object.entries(config)) {\n    if (options.disabledTests === true) {\n      stats.coverage.disabled++;\n    } else if (options.omittedTests !== true) {\n      stats.coverage.ok++;\n    }\n\n    const testResult = globalThis.results[file];\n\n    if (!testResult) {\n      throw new Error(\n        `Unable to find test results for ${file}. This is probably a harness bug`\n      );\n    }\n\n    for (const subtestResult of testResult.subtests) {\n      if (subtestResult.error === 'DISABLED') {\n        stats.pass.disabled++;\n      } else if (subtestResult.error instanceof Error) {\n        stats.pass.fail++;\n      } else if (subtestResult.error === undefined) {\n        stats.pass.pass++;\n      }\n    }\n  }\n\n  return JSON.stringify(stats.toList());\n}\n"
  },
  {
    "path": "src/wpt/harness/test.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright © web-platform-tests contributors. BSD license\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport {\n  FilterList,\n  type UnknownFunc,\n  type TestFn,\n  type PromiseTestFn,\n} from './common';\n\ndeclare global {\n  function promise_test(\n    func: PromiseTestFn,\n    name?: string,\n    properties?: unknown\n  ): void;\n  function async_test(\n    func: TestFn | string,\n    name?: string,\n    properties?: unknown\n  ): Test;\n  function test(func: TestFn, name?: string, properties?: unknown): void;\n}\n\ntype TestErrorType = Error | 'OMITTED' | 'DISABLED' | undefined;\n\n/**\n * A single subtest. A Test is not constructed directly but via the\n * :js:func:`test`, :js:func:`async_test` or :js:func:`promise_test` functions.\n *\n * @param name - This must be unique in a given file and must be\n * invariant between runs.\n *\n */\n/* eslint-disable @typescript-eslint/no-this-alias -- WPT allows for overriding the this environment for a step but defaults to the Test class */\nexport class Test {\n  static Phases = {\n    INITIAL: 0,\n    STARTED: 1,\n    HAS_RESULT: 2,\n    CLEANING: 3,\n    COMPLETE: 4,\n  } as const;\n\n  name: string;\n  properties: unknown;\n  phase: (typeof Test.Phases)[keyof typeof Test.Phases];\n  cleanup_callbacks: UnknownFunc[] = [];\n\n  error: TestErrorType = undefined;\n\n  // If this test is asynchronous, stores a promise that resolves on test completion\n  promise?: Promise<void>;\n\n  constructor(name: string, properties?: unknown) {\n    this.name = name;\n    this.properties = properties;\n    this.phase = Test.Phases.INITIAL;\n  }\n\n  /**\n   * Run a single step of an ongoing test.\n   *\n   * @param func - Callback function to run as a step. If\n   * this throws an :js:func:`AssertionError`, or any other\n   * exception, the :js:class:`Test` status is set to ``FAIL``.\n   * @param [this_obj] - The object to use as the this\n   * value when calling ``func``. Defaults to the  :js:class:`Test` object.\n   */\n  step(func: UnknownFunc, this_obj?: object, ...rest: unknown[]): unknown {\n    if (this.phase > Test.Phases.STARTED) {\n      return undefined;\n    }\n\n    if (arguments.length === 1) {\n      this_obj = this;\n    }\n\n    try {\n      return func.call(this_obj, ...rest);\n    } catch (err) {\n      if (this.phase >= Test.Phases.HAS_RESULT) {\n        return undefined;\n      }\n\n      this.error = new AggregateError([err], this.name);\n      this.error.stack = '';\n      this.done();\n    }\n\n    return undefined;\n  }\n\n  /**\n   * Wrap a function so that it runs as a step of the current test.\n   *\n   * This allows creating a callback function that will run as a\n   * test step.\n   *\n   * @example\n   * let t = async_test(\"Example\");\n   * onload = t.step_func(e => {\n   *   assert_equals(e.name, \"load\");\n   *   // Mark the test as complete.\n   *   t.done();\n   * })\n   *\n   * @param func - Function to run as a step. If this\n   * throws an :js:func:`AssertionError`, or any other exception,\n   * the :js:class:`Test` status is set to ``FAIL``.\n   * @param [this_obj] - The object to use as the this\n   * value when calling ``func``. Defaults to the :js:class:`Test` object.\n   */\n  step_func(func: UnknownFunc, this_obj?: object): UnknownFunc {\n    if (arguments.length === 1) {\n      this_obj = this;\n    }\n\n    return (...params: unknown[]) => this.step(func, this_obj, ...params);\n  }\n\n  /**\n   * Wrap a function so that it runs as a step of the current test,\n   * and automatically marks the test as complete if the function\n   * returns without error.\n   *\n   * @param func - Function to run as a step. If this\n   * throws an :js:func:`AssertionError`, or any other exception,\n   * the :js:class:`Test` status is set to ``FAIL``. If it returns\n   * without error the status is set to ``PASS``.\n   * @param [this_obj] - The object to use as the this\n   * value when calling `func`. Defaults to the :js:class:`Test` object.\n   */\n  step_func_done(func?: UnknownFunc, this_obj?: object): UnknownFunc {\n    if (arguments.length === 1) {\n      this_obj = this;\n    }\n\n    return (...params: unknown[]) => {\n      if (func) {\n        this.step(func, this_obj, ...params);\n      }\n\n      this.done();\n    };\n  }\n\n  /**\n   * Return a function that automatically sets the current test to\n   * ``FAIL`` if it's called.\n   *\n   * @param [description] - Error message to add to assert\n   * in case of failure.\n   *\n   */\n  unreached_func(description?: string): UnknownFunc {\n    return this.step_func(() => {\n      assert_unreached(description);\n    });\n  }\n\n  /**\n   * Run a function as a step of the test after a given timeout.\n   *\n   * In general it's encouraged to use :js:func:`Test.step_wait` or\n   * :js:func:`step_wait_func` in preference to this function where possible,\n   * as they provide better test performance.\n   *\n   * @param func - Function to run as a test\n   * step.\n   * @param timeout - Time in ms to wait before running the\n   * test step.\n   *\n   */\n  step_timeout(\n    func: UnknownFunc,\n    timeout: number,\n    ...rest: unknown[]\n  ): ReturnType<typeof setTimeout> {\n    return setTimeout(\n      this.step_func(() => func(...rest)),\n      timeout\n    );\n  }\n\n  add_cleanup(func: UnknownFunc): void {\n    this.cleanup_callbacks.push(func);\n  }\n\n  done(): void {\n    if (this.phase >= Test.Phases.CLEANING) {\n      return;\n    }\n\n    this.cleanup();\n  }\n\n  cleanup(): void {\n    // TODO(soon): Cleanup functions can also return a promise instead of being synchronous, but we don't need this for any tests currently.\n    for (const cleanFn of this.cleanup_callbacks) {\n      cleanFn();\n    }\n    this.phase = Test.Phases.COMPLETE;\n  }\n}\n\n/* eslint-enable @typescript-eslint/no-this-alias */\nclass SkippedTest extends Test {\n  constructor(name: string, reason: TestErrorType) {\n    super(name);\n    this.error = reason;\n  }\n\n  override step(\n    _func: UnknownFunc,\n    _this_obj?: object,\n    ..._rest: unknown[]\n  ): unknown {\n    return undefined;\n  }\n}\n\nclass PromiseTest extends Test {\n  // TODO(soon): Extract out promise_test specific behaviour to make code easier to understand.\n}\n\nglobalThis.promise_test = (func, name, properties): void => {\n  if (maybeAddSkippedTest(name ?? '')) {\n    return;\n  }\n\n  const testCase = new PromiseTest(name ?? '', properties);\n  globalThis.state.subtests.push(testCase);\n\n  const promise = testCase.step(func, testCase, testCase);\n\n  if (!(promise instanceof Promise)) {\n    // The functions passed to promise_test are expected to return a Promise,\n    // but are not required to be async functions. That means they could throw\n    // an error immediately when run.\n\n    if (!testCase.error) {\n      testCase.error = new Error('Unexpected value returned from promise_test');\n    }\n\n    return;\n  }\n\n  testCase.promise = promise\n    .then(() => {\n      testCase.done();\n    })\n    .catch((err: unknown) => {\n      testCase.error = Object.assign(new AggregateError([err], name), {\n        stack: '',\n      });\n    });\n};\n\nclass AsyncTest extends Test {\n  #resolve: () => void;\n\n  constructor(name: string, properties: unknown) {\n    super(name, properties);\n\n    // eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- void is being used as a valid generic in this context\n    const { promise, resolve } = Promise.withResolvers<void>();\n    this.promise = promise;\n    this.#resolve = resolve;\n  }\n\n  override done(): void {\n    super.done();\n    this.#resolve();\n  }\n}\n\nglobalThis.async_test = (func, name, properties): Test => {\n  // async_test can be called in two ways:\n  // 1. async_test(func, name, properties) - func is a TestFn\n  // 2. async_test(name, properties) - just creates a test with the given name\n  let testName: string;\n  let testFunc: TestFn | undefined;\n\n  if (typeof func === 'string') {\n    // async_test(name, properties) signature\n    testName = func;\n    testFunc = undefined;\n    // name parameter is actually properties in this case\n    properties = name;\n  } else {\n    // async_test(func, name, properties) signature\n    testName = name ?? '';\n    testFunc = func;\n  }\n\n  if (maybeAddSkippedTest(testName)) {\n    // Return a dummy test object for skipped tests\n    return new SkippedTest(testName, 'DISABLED');\n  }\n\n  const testCase = new AsyncTest(testName, properties);\n  globalThis.state.subtests.push(testCase);\n\n  if (testFunc) {\n    testCase.step(testFunc, testCase, testCase);\n  }\n\n  return testCase;\n};\n\n/**\n * Create a synchronous test\n *\n * @param func - Test function. This is executed\n * immediately. If it returns without error, the test status is\n * set to ``PASS``. If it throws an :js:class:`AssertionError`, or\n * any other exception, the test status is set to ``FAIL``\n * (typically from an `assert` function).\n * @param name - Test name. This must be unique in a\n * given file and must be invariant between runs.\n */\nglobalThis.test = (func, name, properties): void => {\n  if (maybeAddSkippedTest(name ?? '')) {\n    return;\n  }\n\n  const testCase = new Test(name ?? '', properties);\n  globalThis.state.subtests.push(testCase);\n\n  testCase.step(func, testCase, testCase);\n  testCase.done();\n};\n\nfunction maybeAddSkippedTest(message: string): boolean {\n  const disabledTests = new FilterList(globalThis.state.options.disabledTests);\n\n  if (disabledTests.has(message)) {\n    globalThis.state.subtests.push(new SkippedTest(message, 'DISABLED'));\n    return true;\n  }\n\n  const omittedTests = new FilterList(globalThis.state.options.omittedTests);\n\n  if (omittedTests.has(message)) {\n    globalThis.state.subtests.push(new SkippedTest(message, 'OMITTED'));\n    return true;\n  }\n\n  if (globalThis.state.options.verbose) {\n    console.info('run', message);\n  }\n\n  return false;\n}\n"
  },
  {
    "path": "src/wpt/harness/utils.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n// Copyright © web-platform-tests contributors. BSD license\n// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport crypto from 'node:crypto';\nimport {\n  type UnknownFunc,\n  type TestFn,\n  type PromiseTestFn,\n  type HostInfo,\n  getHostInfo,\n} from './common';\n\ndeclare global {\n  var GLOBAL: {\n    isWindow(): boolean;\n    isWorker(): boolean;\n    isShadowRealm(): boolean;\n  };\n\n  function done(): undefined;\n  function subsetTestByKey<T>(\n    _key: string,\n    testType: (\n      testCallback: TestFn | PromiseTestFn | string,\n      testMessage?: string\n    ) => T,\n    testCallback: TestFn | PromiseTestFn | string,\n    testMessage?: string\n  ): T;\n  function subsetTest(\n    testType: TestRunnerFn,\n    testCallback: TestFn | PromiseTestFn,\n    testMessage: string\n  ): void;\n  // Used by idlharness.js to determine if a subtest should be run\n  function shouldRunSubTest(name?: string): boolean;\n\n  function step_timeout(\n    func: UnknownFunc,\n    timeout: number,\n    ...rest: unknown[]\n  ): ReturnType<typeof setTimeout>;\n\n  function get_host_info(): HostInfo;\n  function token(): string;\n  function setup(func: UnknownFunc | Record<string, unknown>): void;\n  function add_completion_callback(func: UnknownFunc): void;\n  // Provided by /common/gc.js\n  function garbageCollect(): Promise<void>;\n  function format_value(val: unknown): string;\n  function createBuffer(\n    type: 'ArrayBuffer' | 'SharedArrayBuffer',\n    length: number,\n    opts: { maxByteLength?: number } | undefined\n  ): ArrayBuffer | SharedArrayBuffer;\n  // Used by idlharness.js to fetch IDL files\n  function fetch_spec(spec: string): Promise<{ spec: string; idl: string }>;\n}\n\ntype TestRunnerFn = (callback: TestFn | PromiseTestFn, message: string) => void;\n\nglobalThis.get_host_info = (): HostInfo => {\n  return getHostInfo();\n};\n\nglobalThis.GLOBAL = {\n  isWindow(): boolean {\n    return false;\n  },\n  isWorker(): boolean {\n    return false;\n  },\n  isShadowRealm(): boolean {\n    return false;\n  },\n};\n\nglobalThis.done = (): undefined => undefined;\n\nglobalThis.subsetTestByKey = <T>(\n  _key: string,\n  testType: (testCallback: TestFn | PromiseTestFn, testMessage: string) => T,\n  testCallback: TestFn | PromiseTestFn | string,\n  testMessage?: string\n): T => {\n  // This function is designed to allow selecting only certain tests when\n  // running in a browser, by changing the query string. We'll always run\n  // all the tests.\n  return testType(\n    testCallback as TestFn | PromiseTestFn,\n    testMessage as string\n  );\n};\n\n// Used by idlharness.js to determine if a subtest should be run.\n// We always run all subtests.\nglobalThis.shouldRunSubTest = (): boolean => true;\n\nglobalThis.subsetTest = (testType, testCallback, testMessage): void => {\n  // This function is designed to allow selecting only certain tests when\n  // running in a browser, by changing the query string. We'll always run\n  // all the tests.\n\n  // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression -- We are emulating WPT's existing interface which always passes through the returned value\n  return testType(testCallback, testMessage);\n};\n\n/**\n * Global version of :js:func:`Test.step_timeout` for use in single page tests.\n *\n * @param func - Function to run after the timeout\n * @param timeout - Time in ms to wait before running the\n * test step. The actual wait time is ``timeout`` x\n * ``timeout_multiplier``.\n */\nglobalThis.step_timeout = (\n  func: UnknownFunc,\n  timeout: number,\n  ...rest: unknown[]\n): ReturnType<typeof setTimeout> => {\n  return setTimeout(() => func(...rest), timeout);\n};\n\nglobalThis.token = (): string => {\n  return crypto.randomUUID();\n};\n\nglobalThis.setup = (func): void => {\n  if (typeof func === 'object') {\n    return;\n  }\n\n  func();\n};\n\nglobalThis.add_completion_callback = (func: UnknownFunc): void => {\n  globalThis.state.completionCallbacks.push(func);\n};\n\nglobalThis.format_value = (val): string => {\n  return JSON.stringify(val, null, 2);\n};\n\nglobalThis.createBuffer = (\n  type,\n  length,\n  _opts\n): ArrayBuffer | SharedArrayBuffer => {\n  switch (type) {\n    case 'ArrayBuffer':\n      return new ArrayBuffer(length);\n    case 'SharedArrayBuffer':\n      return new SharedArrayBuffer(length);\n    default:\n      throw new TypeError(`Unsupported buffer type: ${type}`);\n  }\n};\n\n/**\n * fetch_spec is used by idlharness.js to fetch IDL files.\n * This implementation reads from the embedded bindings instead of using fetch().\n */\nglobalThis.fetch_spec = (\n  spec: string\n): Promise<{ spec: string; idl: string }> => {\n  const path = `/interfaces/${spec}.idl`;\n  const idl = globalThis.state.env[path];\n  if (typeof idl !== 'string') {\n    return Promise.reject(\n      new Error(`Error fetching ${path}: IDL file not found in bindings`)\n    );\n  }\n  return Promise.resolve({ spec, idl });\n};\n"
  },
  {
    "path": "src/wpt/performance-timeline-test.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { type TestRunnerConfig } from 'harness/harness';\n\nexport default {\n  'buffered-flag-after-timeout.any.js': {\n    comment: 'This is not yet implemented',\n    disabledTests: [\n      'PerformanceObserver with buffered flag sees entry after timeout',\n    ],\n  },\n  'buffered-flag-observer.any.js': {\n    comment: 'This is not yet implemented',\n    disabledTests: [\n      'PerformanceObserver with buffered flag should see past and future entries.',\n    ],\n  },\n  'case-sensitivity.any.js': {\n    comment: 'This is not yet implemented',\n    disabledTests: [\n      'getEntriesByType values are case sensitive',\n      'getEntriesByName values are case sensitive',\n      'observe() and case sensitivity for types/entryTypes and buffered.',\n    ],\n  },\n  'droppedentriescount.any.js': {\n    comment: 'This is not yet implemented',\n    disabledTests: [\n      'Dropped entries count is 0 when there are no dropped entries of relevant type.',\n      'Dropped entries correctly counted with multiple types.',\n      'Dropped entries counted even if observer was not registered at the time.',\n      'Dropped entries only surfaced on the first callback.',\n      'Dropped entries surfaced after an observe() call!',\n    ],\n  },\n  'idlharness.any.js': {\n    comment: 'idl_test setup hangs the Worker runtime',\n    disabledTests: ['idl_test setup'],\n  },\n  'multiple-buffered-flag-observers.any.js': {\n    comment: 'This is not yet implemented',\n    omittedTests: [\n      'Multiple PerformanceObservers with buffered flag see all entries',\n    ],\n  },\n  'navigation-id.helper.js': {},\n  'not-restored-reasons/abort-block-bfcache.window.js': {\n    comment: 'This is not yet implemented',\n    omittedTests: ['aborting a parser should block bfcache.'],\n  },\n  'not-restored-reasons/test-helper.js': {\n    comment:\n      'Test file /html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js not found. Update wpt_test.bzl to handle this case.',\n    omittedTests: true,\n  },\n  'observer-buffered-false.any.js': {\n    comment: 'This is not yet implemented',\n    disabledTests: [\n      'PerformanceObserver without buffered flag set to false cannot see past entries.',\n    ],\n  },\n  'performanceentry-tojson.any.js': {\n    comment: 'This is not yet implemented',\n    disabledTests: ['Test toJSON() in PerformanceEntry'],\n  },\n  'performanceobservers.js': {},\n  'po-callback-mutate.any.js': {\n    comment: 'This is not yet implemented',\n    disabledTests: [\n      'PerformanceObserver modifications inside callback should update filtering and not clear buffer',\n    ],\n  },\n  'po-disconnect-removes-observed-types.any.js': {\n    comment: 'This is not yet implemented',\n    disabledTests: [\n      'Types observed are forgotten when disconnect() is called.',\n    ],\n  },\n  'po-disconnect.any.js': {\n    comment: 'This is not yet implemented',\n    disabledTests: [\n      'disconnected callbacks must not be invoked',\n      'disconnecting an unconnected observer is a no-op',\n      'An observer disconnected after a mark must not have its callback invoked',\n    ],\n  },\n  'po-entries-sort.any.js': {\n    comment: 'This is not yet implemented',\n    disabledTests: [\n      'getEntries, getEntriesByType, getEntriesByName sort order',\n    ],\n  },\n  'po-getentries.any.js': {\n    comment: 'This is not yet implemented',\n    disabledTests: ['getEntries, getEntriesByType and getEntriesByName work'],\n  },\n  'po-mark-measure.any.js': {\n    comment: 'This is not yet implemented',\n    disabledTests: [\n      'entries are observable',\n      'mark entries are observable',\n      'measure entries are observable',\n    ],\n  },\n  'po-observe-repeated-type.any.js': {\n    comment: 'This is not yet implemented',\n    disabledTests: [\n      \"Two calls of observe() with the same 'type' cause override.\",\n    ],\n  },\n  'po-observe-type.any.js': {\n    comment: 'This is not yet implemented',\n    disabledTests: [\n      \"Calling observe() without 'type' or 'entryTypes' throws a TypeError\",\n      'Calling observe() with entryTypes and then type should throw an InvalidModificationError',\n      'Calling observe() with type and then entryTypes should throw an InvalidModificationError',\n      'Calling observe() with type and entryTypes should throw a TypeError',\n      'Passing in unknown values to type does throw an exception.',\n      'observe() with different type values stacks.',\n    ],\n  },\n  'po-observe.any.js': {\n    comment: 'This is not yet implemented',\n    disabledTests: [\n      'entryTypes must be a sequence or throw a TypeError',\n      'Empty sequence entryTypes does not throw an exception.',\n      'Unknown entryTypes do not throw an exception.',\n      'Filter unsupported entryType entryType names within the entryTypes sequence',\n      'Check observer callback parameter and this values',\n      'replace observer if already present',\n    ],\n  },\n  'po-takeRecords.any.js': {\n    comment: 'This is not yet implemented',\n    disabledTests: [\"Test PerformanceObserver's takeRecords()\"],\n  },\n  'supportedEntryTypes.any.js': {\n    comment:\n      'Test expect non-empty array that is cached. Our implementation does not cache it yet.',\n    disabledTests: [\n      'supportedEntryTypes exists and returns entries in alphabetical order',\n      'supportedEntryTypes caches result',\n    ],\n  },\n  'webtiming-resolution.any.js': {\n    comment: 'We intentionally fail on these',\n    disabledTests: [\n      'Verifies the resolution of performance.now() is at least 5 microseconds.',\n      'Verifies the resolution of entry.startTime is at least 5 microseconds.',\n    ],\n  },\n} satisfies TestRunnerConfig;\n"
  },
  {
    "path": "src/wpt/streams-test.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { type TestRunnerConfig } from 'harness/harness';\n\nexport default {\n  'idlharness.any.js': {\n    comment:\n      'Some IDL operation/attribute tests still fail due to missing method .length or promise-returning signatures',\n    expectedFailures: [\n      'ReadableStream interface: operation cancel(optional any)',\n      'ReadableStream interface: operation pipeTo(WritableStream, optional StreamPipeOptions)',\n      'ReadableStreamDefaultReader interface: operation read()',\n      'ReadableStreamDefaultReader interface: attribute closed',\n      'ReadableStreamDefaultReader interface: operation cancel(optional any)',\n      'ReadableStreamBYOBReader interface: operation read(ArrayBufferView, optional ReadableStreamBYOBReaderReadOptions)',\n      'ReadableStreamBYOBReader interface: attribute closed',\n      'ReadableStreamBYOBReader interface: operation cancel(optional any)',\n      'ReadableStreamDefaultController interface: operation error(optional any)',\n      'ReadableByteStreamController interface: operation error(optional any)',\n      'WritableStream interface: operation abort(optional any)',\n      'WritableStream interface: operation close()',\n      'WritableStreamDefaultWriter interface: attribute closed',\n      'WritableStreamDefaultWriter interface: attribute ready',\n      'WritableStreamDefaultWriter interface: operation abort(optional any)',\n      'WritableStreamDefaultWriter interface: operation close()',\n      'WritableStreamDefaultWriter interface: operation write(optional any)',\n      'TransformStreamDefaultController interface: operation enqueue(optional any)',\n      'TransformStreamDefaultController interface: operation error(optional any)',\n    ],\n  },\n\n  'piping/abort.any.js': {\n    comment: 'To be investigated',\n    expectedFailures: [\n      \"(reason: 'null') all pending writes should complete on abort\",\n      \"(reason: 'undefined') all pending writes should complete on abort\",\n      \"(reason: 'error1: error1') all pending writes should complete on abort\",\n      'abort signal takes priority over errored writable',\n      'a rejection from underlyingSource.cancel() should be returned by pipeTo()',\n      'a rejection from underlyingSink.abort() should be preferred to one from underlyingSource.cancel()',\n      'abort should do nothing after the readable is errored, even with pending writes',\n      'abort should do nothing after the writable is errored',\n      'pipeTo on a teed readable byte stream should only be aborted when both branches are aborted',\n      \"(reason: 'null') underlyingSource.cancel() should called when abort, even with pending pull\",\n      \"(reason: 'undefined') underlyingSource.cancel() should called when abort, even with pending pull\",\n      \"(reason: 'error1: error1') underlyingSource.cancel() should called when abort, even with pending pull\",\n    ],\n  },\n  'piping/close-propagation-backward.any.js': {\n    comment: 'A hanging Promise was canceled.',\n    disabledTests: true,\n  },\n  'piping/close-propagation-forward.any.js': {},\n  'piping/error-propagation-backward.any.js': {\n    comment: 'A hanging Promise was canceled.',\n    disabledTests: true,\n  },\n  'piping/error-propagation-forward.any.js': {\n    comment: 'To be investigated',\n    expectedFailures: [\n      'Errors must be propagated forward: starts errored; preventAbort = true (truthy)',\n      'Errors must be propagated forward: starts errored; preventAbort = a (truthy)',\n      'Errors must be propagated forward: starts errored; preventAbort = 1 (truthy)',\n      'Errors must be propagated forward: starts errored; preventAbort = Symbol() (truthy)',\n      'Errors must be propagated forward: starts errored; preventAbort = [object Object] (truthy)',\n      'Errors must be propagated forward: starts errored; preventAbort = true, preventCancel = true',\n      'Errors must be propagated forward: starts errored; preventAbort = true, preventCancel = true, preventClose = true',\n      'Errors must be propagated forward: starts errored; preventAbort = false; rejected abort promise',\n      'Errors must be propagated forward: starts errored; preventAbort = false; fulfilled abort promise',\n      'Errors must be propagated forward: starts errored; preventAbort = undefined (falsy); fulfilled abort promise',\n      'Errors must be propagated forward: starts errored; preventAbort = null (falsy); fulfilled abort promise',\n      'Errors must be propagated forward: starts errored; preventAbort = false (falsy); fulfilled abort promise',\n      'Errors must be propagated forward: starts errored; preventAbort = 0 (falsy); fulfilled abort promise',\n      'Errors must be propagated forward: starts errored; preventAbort = -0 (falsy); fulfilled abort promise',\n      'Errors must be propagated forward: starts errored; preventAbort = NaN (falsy); fulfilled abort promise',\n      'Errors must be propagated forward: starts errored; preventAbort =  (falsy); fulfilled abort promise',\n      'Errors must be propagated forward: shutdown must not occur until the final write completes; becomes errored after first write',\n      'Errors must be propagated forward: shutdown must not occur until the final write completes; becomes errored after first write; preventAbort = true',\n      'Errors must be propagated forward: becomes errored after one chunk; dest never desires chunks; preventAbort = false; fulfilled abort promise',\n      'Errors must be propagated forward: becomes errored after one chunk; dest never desires chunks; preventAbort = false; rejected abort promise',\n      'Errors must be propagated forward: becomes errored after one chunk; dest never desires chunks; preventAbort = true',\n    ],\n  },\n  'piping/flow-control.any.js': {\n    comment: 'To be investigated',\n    expectedFailures: [\n      'Piping from a non-empty ReadableStream into a WritableStream that does not desire chunks',\n      'Piping from a non-empty ReadableStream into a WritableStream that does not desire chunks, but then does',\n      'Piping from a ReadableStream to a WritableStream that desires more chunks before finishing with previous ones',\n    ],\n  },\n  'piping/general-addition.any.js': {},\n  'piping/general.any.js': {\n    comment:\n      'Illegal invocation: function called with incorrect `this` reference.',\n    expectedFailures: [\n      'pipeTo must check the brand of its ReadableStream this value',\n    ],\n  },\n  'piping/multiple-propagation.any.js': {\n    comment: 'TypeError: Cannot close a writer that is already being closed',\n    expectedFailures: [\n      'Piping from a closed readable stream to a closed writable stream',\n    ],\n  },\n  'piping/pipe-through.any.js': {\n    comment: 'Windows has different property access order',\n    expectedFailures:\n      process.platform === 'win32'\n        ? ['pipeThrough() should throw if readable/writable getters throw']\n        : [],\n  },\n  'piping/then-interception.any.js': {\n    comment:\n      'failed: expected Wrappable::tryUnwrapOpaque(isolate, handle) != nullptr',\n    expectedFailures: [\n      'piping should not be observable',\n      'tee should not be observable',\n    ],\n  },\n  'piping/throwing-options.any.js': {},\n  'piping/transform-streams.any.js': {},\n\n  'queuing-strategies-size-function-per-global.window.js': {\n    comment: 'document is not defined',\n    disabledTests: true,\n  },\n  'queuing-strategies.any.js': {\n    comment: 'Likely missing validation',\n    expectedFailures: [\n      'CountQueuingStrategy: Constructor behaves as expected with strange arguments',\n      'CountQueuingStrategy: size is the same function across all instances',\n      'CountQueuingStrategy: size should have the right name',\n      'CountQueuingStrategy: size should not have a prototype property',\n      'ByteLengthQueuingStrategy: Constructor behaves as expected with strange arguments',\n      'ByteLengthQueuingStrategy: size is the same function across all instances',\n      'ByteLengthQueuingStrategy: size should have the right name',\n      'ByteLengthQueuingStrategy: size should not have a prototype property',\n      'CountQueuingStrategy: size should not be a constructor',\n      'ByteLengthQueuingStrategy: size should not be a constructor',\n      'ByteLengthQueuingStrategy: size should have the right length',\n      'ByteLengthQueuingStrategy: size behaves as expected with strange arguments',\n    ],\n  },\n\n  'readable-byte-streams/bad-buffers-and-views.any.js': {},\n  'readable-byte-streams/construct-byob-request.any.js': {},\n  'readable-byte-streams/crashtests/tee-locked-stream.any.js': {},\n  'readable-byte-streams/enqueue-with-detached-buffer.any.js': {},\n  'readable-byte-streams/general.any.js': {\n    comment: 'See individual comments',\n    expectedFailures: [\n      // TODO(conform): The spec expects that errors thrown synchronously in the start\n      // algorithm should cause the ReadableStream constructor to throw. We currently\n      // don't do that but we do error the stream.\n      // assert_throws_js(Error, () => new ReadableStream({ start() { throw new Error(); }, type:'bytes' }),\n      //     'start() can throw an exception with type: bytes');\n      'ReadableStream with byte source: start() throws an exception',\n      // TODO(conform): The spec expects pull not to have been called yet, but as an optimization\n      // since start is not provided we treat is synchronously and pull proactively, making this\n      // next check invalid.\n      // assert_equals(pullCount, 0, 'No pull as start() just finished and is not yet reflected to the state of the stream');\n      'ReadableStream with byte source: Automatic pull() after start()',\n      // TODO(conform): The spec expects pull not to have been called yet, but as an optimization\n      // since start is not provided we treat is synchronously and pull proactively, making this\n      // next check invalid.\n      //assert_equals(pullCount, 0, 'No pull as start() just finished and is not yet reflected to the state of the stream');\n      'ReadableStream with byte source: Automatic pull() after start() and read()',\n      'ReadableStream with byte source: autoAllocateChunkSize',\n      'ReadableStream with byte source: Automatic pull() after start() and read(view)',\n      'ReadableStream with byte source: Respond to pull() by enqueue() asynchronously',\n      'ReadableStream with byte source: Respond to multiple pull() by separate enqueue()',\n      'ReadableStream with byte source: read() twice, then enqueue() twice',\n      // TODO(conform): The spec would not expect pull to be called because of the close,\n      // but because our implementation calls pull immediately on the first read, we\n      // differ slightly here.\n      // assert_unreached(\"pull() should not have been called\");\n      // TODO(conform): The spec would allow the byobRequest to still be used here, but\n      // our implementation throws when accessed after close.\n      // controller.byobRequest.respond(0);\n      'ReadableStream with byte source: Multiple read(view), close() and respond()',\n      'ReadableStream constructor should not accept a strategy with a size defined if type is \"bytes\"',\n      'ReadableStream with byte source: enqueue(), getReader(), then read()',\n      // TODO(conform): This is a case where our implementation intentionally\n      // diverges from the spec due to the tee backpressure implementation.\n      // Specifically, the input view is a Uint16Array with one element --\n      // meaning it expects us to provide 2 bytes. The enqueue() only gives\n      // it one byte. Because of how we handle these internally, the read will\n      // not be fulfilled until another byte is provided, but the byobRequest\n      // still is invalidated. In the standard, the byobRequest would still\n      // be valid here.\n      //\n      // Generally speaking, in our implementation, using enqueue() and byobRequest\n      // together is not something that should be done.\n      'ReadableStream with byte source: cancel() with partially filled pending pull() request',\n      \"ReadableStream with byte source: Push source that doesn't understand pull signal\",\n      'ReadableStream with byte source: enqueue() with Uint16Array, getReader(), then read()',\n      // TODO(conform): Our implementation ends up immediately calling pull\n      // when the read() is called, before the cancel() is able to run. The\n      // spec expects the cancel to happen first.\n      //assert_unreached(\"pull should not have been called\");\n      // TODO(conform): The spec expects the result.value here to be undefined since the read\n      // is canceled. Our impl returns an empty ArrayBuffer...\n      //assert_equals(result.value, undefined, 'result.value');\n      'ReadableStream with byte source: getReader(), read(view), then cancel()',\n      'ReadableStream with byte source: read(view) with Uint32Array, then fill it by multiple enqueue() calls',\n      'ReadableStream with byte source: enqueue(), read(view) partially, then read()',\n      'ReadableStream with byte source: read(view), then respond() and close() in pull()',\n      // TODO(conform): The spec expects the read to fail here. Instead, we end up cancelling\n      // it with a zero-length result, with the subsequent read marked as done.\n      'ReadableStream with byte source: read(view) with Uint16Array on close()-d stream with 1 byte enqueue()-d must fail',\n      // TODO(conform): Per the spec, desiredSize should be zero here\n      // but since we are handling the backpressure a bit differently\n      // it won't be zero until the actual read is resolved.\n      //desiredSize = controller.desiredSize;\n      'ReadableStream with byte source: enqueue() 3 byte, getReader(), then read(view) with 2-element Uint16Array',\n      'ReadableStream with byte source: Throwing in pull in response to read() must be ignored if the stream is errored in it',\n      'ReadableStream with byte source: Throwing in pull function must error the stream',\n      // TODO(conform): We handle things a bit differently here from the spec. The spec\n      // would have the enqueue() complete replace the byobRequest.view while we use it\n      // and fill it with the data from the enqueue. This means the following check is\n      // not valid in our implementation.\n      // assert_array_equals([...new Uint8Array(view1.buffer)], [1, 2, 3], 'first result.value.buffer');\n      'ReadableStream with byte source: enqueue() discards auto-allocated BYOB request',\n      // TODO(conform): Calling releaseLock() should cancel the pending reads. It currently does not.\n      'ReadableStream with byte source: releaseLock() with pending read(view), read(view) on second reader, respond()',\n\n      // TODO(conform): Calling releaseLock() should cancel the pending reads. It currently does not.\n      'ReadableStream with byte source: releaseLock() with pending read(view), read(view) on second reader with 1 element Uint16Array, respond(1)',\n\n      // TODO(conform): Calling releaseLock() should cancel the pending reads. It currently does not.\n      'ReadableStream with byte source: releaseLock() with pending read(view), read(view) on second reader with 2 element Uint8Array, respond(3)',\n\n      // TODO(conform): Calling releaseLock() should cancel the pending reads. It currently does not.\n      'ReadableStream with byte source: releaseLock() with pending read(view), read(view) on second reader, respondWithNewView()',\n\n      // TODO(conform): Calling releaseLock() should cancel the pending reads. It currently does not.\n      'ReadableStream with byte source: autoAllocateChunkSize, releaseLock() with pending read(), read() on second reader, respond()',\n\n      // TODO(conform): Calling releaseLock() should cancel the pending reads. It currently does not.\n      'ReadableStream with byte source: autoAllocateChunkSize, releaseLock() with pending read(), read() on second reader, enqueue()',\n\n      // TODO(conform): Calling releaseLock() should cancel the pending reads. It currently does not.\n      'ReadableStream with byte source: autoAllocateChunkSize, releaseLock() with pending read(), read(view) on second reader, respond()',\n      // TODO(conform): The spec allows a byob read to be fulfilled incrementally over multiple\n      // respond calls, we currently do not.\n      'ReadableStream with byte source: read(view) with 1 element Uint16Array, respond(1), releaseLock(), read(view) on second reader with 1 element Uint16Array, respond(1)',\n      // TODO(conform): The spec allows a byob read to be fulfilled incrementally over multiple\n      // respond calls, we currently do not.\n      'ReadableStream with byte source: read(view) with 1 element Uint16Array, respond(1), releaseLock(), read() on second reader, enqueue()',\n      // TODO: investigate this\n      'ReadableStream with byte source: A stream must be errored if close()-d before fulfilling read(view) with Uint16Array',\n      // TODO: investigate this\n      'ReadableStream with byte source: Multiple read(view), big enqueue()',\n      // TODO: investigate this\n      'ReadableStream with byte source: Multiple read(view) and multiple enqueue()',\n    ],\n  },\n  'readable-byte-streams/non-transferable-buffers.any.js': {},\n  'readable-byte-streams/patched-global.any.js': {\n    comment: 'TODO investigate this',\n    expectedFailures: [\n      'Patched then() sees byobRequest after filling all pending pull-into descriptors',\n    ],\n    runInGlobalScope: true,\n  },\n  'readable-byte-streams/read-min.any.js': {\n    comment: 'A hanging Promise was canceled.',\n    disabledTests: true,\n  },\n  'readable-byte-streams/respond-after-enqueue.any.js': {\n    comment: 'To be investigated',\n    expectedFailures: [\n      'byobRequest.respond() after enqueue() with double read should not crash',\n    ],\n  },\n  'readable-byte-streams/tee.any.js': {\n    comment: 'To be investigated',\n    expectedFailures: [\n      'ReadableStream teeing with byte source: pull with default reader, then pull with BYOB reader',\n      'ReadableStream teeing with byte source: chunks should be cloned for each branch',\n      'ReadableStream teeing with byte source: reading an array with a byte offset should clone correctly',\n      'ReadableStream teeing with byte source: chunks for BYOB requests from branch 1 should be cloned to branch 2',\n      'ReadableStream teeing with byte source: canceling both branches should aggregate the cancel reasons into an array',\n      'ReadableStream teeing with byte source: canceling both branches in reverse order should aggregate the cancel reasons into an array',\n      'ReadableStream teeing with byte source: pull with BYOB reader, then pull with default reader',\n      'ReadableStream teeing with byte source: failing to cancel the original stream should cause cancel() to reject on branches',\n      'ReadableStream teeing with byte source: should be able to read one branch to the end without affecting the other',\n      'ReadableStream teeing with byte source: canceling branch1 should not impact branch2',\n      'ReadableStream teeing with byte source: canceling branch2 should not impact branch1',\n      'ReadableStream teeing with byte source: canceling both branches in sequence with delay',\n      'ReadableStream teeing with byte source: failing to cancel when canceling both branches in sequence with delay',\n      'ReadableStream teeing with byte source: enqueue() and close() while both branches are pulling',\n      'ReadableStream teeing with byte source: stops pulling when original stream errors while both branches are reading',\n      'ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch1, cancel branch2',\n      'ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch2, cancel branch1',\n      'ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch2, enqueue to branch1',\n      'ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch1, respond to branch2',\n      'ReadableStream teeing with byte source: read from branch1 with default reader, then close while branch2 has pending BYOB read',\n      'ReadableStream teeing with byte source: read from branch2 with default reader, then close while branch1 has pending BYOB read',\n    ],\n  },\n  'readable-byte-streams/templated.any.js': {\n    comment: 'To be investigated',\n    expectedFailures: [\n      'ReadableStream with byte source (empty) BYOB reader: canceling via the reader should cause the reader to act closed',\n    ],\n  },\n\n  'readable-streams/async-iterator.any.js': {\n    comment: 'To be investigated',\n    expectedFailures: [\n      'Async iterator instances should have the correct list of properties',\n      'return(); next() [no awaiting]',\n      'return(); return() [no awaiting]',\n      'return(); next() with delayed cancel() [no awaiting]',\n      'next() that succeeds; return()',\n      'next() that succeeds; next() that reports an error(); next() [no awaiting]',\n      'next() that succeeds; next() that reports an error(); return() [no awaiting]',\n      'next() that succeeds; return() [no awaiting]',\n      'next() that succeeds; next() that reports an error; next()',\n      'next() that succeeds; next() that reports an error(); return()',\n    ],\n  },\n  'readable-streams/bad-strategies.any.js': {\n    comment: 'See individual comments',\n    expectedFailures: [\n      // TODO(conform): While we do error the stream, the spec expects us to throw the error\n      // thrown in the size() function here. We currently do not.\n      'Readable stream: strategy.size errors the stream and then throws',\n      // TODO(conform): While we do error the stream, the spec expects us to throw the error\n      // thrown in the size() function here. We currently do not.\n      'Readable stream: strategy.size errors the stream and then returns Infinity',\n      // TODO(conform): The spec expects this to be a TypeError, we currently throw\n      // a RangeError instead\n      'Readable stream: invalid strategy.highWaterMark',\n      // TODO(conform): We currently do not error when the size function returns the wrong\n      // value. The spec expects us to. We do properly error the strea\n      'Readable stream: invalid strategy.size return value',\n      'Readable stream: invalid strategy.size return value when pulling',\n    ],\n  },\n  'readable-streams/bad-underlying-sources.any.js': {\n    comment: 'See individual comments',\n    disabledTests: [\n      // TODO(conform): The spec expects pull to be called twice when the stream is created and\n      // a single read happens. We currently only call it once in this case, so we have to read\n      // again to trigger the error case.\n      'Underlying source pull: throwing method (second pull)',\n      // TODO(conform): The spec expects pull() to be called twice when the stream is\n      // constructed and the first read occurs, we currently only call it once, so to\n      // trigger the error, we perform a read again.\n      'read should not error if it dequeues and pull() throws',\n    ],\n    expectedFailures: [\n      // TODO(conform): If the start function throws synchronously, the constructor\n      // should throw, per the spec. Currently we only error the stream and do not\n      // throw synchronously here.\n      'Underlying source start: throwing method',\n      // TODO(conform): The spec says that a second call to error should be a non-op.\n      // We currently treat it an an error.\n      'Underlying source: calling error twice should not throw',\n      // TODO(conform): The spec says that calling error() after close() should be a non-op.\n      // We currently treat it as an error.\n      'Underlying source: calling error after close should not throw',\n    ],\n  },\n  'readable-streams/cancel.any.js': {\n    comment: 'See detailed explanation in comments',\n    disabledTests: [\n      // underlyingSource is converted in prose in the method body, whereas queuingStrategy is done at the IDL layer.\n      // So the queuingStrategy exception should be encountered first.\n      // TODO(conform): We currently handle these differently and end up throwing error1 instead.\n      'ReadableStream cancellation: underlyingSource.cancel() should called, even with pending pull',\n    ],\n  },\n  'readable-streams/constructor.any.js': {\n    comment: 'They want us to validate the args and throw in a different order',\n    expectedFailures:\n      process.platform === 'win32'\n        ? []\n        : [\n            'underlyingSource argument should be converted after queuingStrategy argument',\n          ],\n  },\n  'readable-streams/count-queuing-strategy-integration.any.js': {},\n  'readable-streams/crashtests/empty.js': {},\n  'readable-streams/crashtests/garbage-collection.any.js': {},\n  'readable-streams/crashtests/strategy-worker.js': {\n    comment: 'ReferenceError: importScripts is not defined',\n    disabledTests: true,\n  },\n  'readable-streams/cross-realm-crash.window.js': {\n    comment: 'document is not defined',\n    expectedFailures: [\n      'should not crash on reading from stream cancelled in destroyed realm',\n    ],\n  },\n  'readable-streams/default-reader.any.js': {\n    comment: 'See individual comments',\n    expectedFailures: [\n      // TODO(conform): When releaseLock() is called, the spec expects the readers original\n      // closed promise to be replaced. The original one should be resolved, but the new\n      // one should be rejected. We currently do not replace the closed promise in this case.\n      'closed is replaced when stream closes and reader releases its lock',\n      // TODO(conform): When releaseLock() is called, the spec expects the readers original\n      // closed promise to be replaced. In this case, the original one should reject with\n      // theError, while the second should reject indicating that it was acquired after\n      // releasing the lock. We currently do not replace the closed promise in this case.\n      // assert_not_equals(promise1, promise2, '.closed should be replaced');\n      'closed is replaced when stream errors and reader releases its lock',\n      // TODO(conform): The spec allows error to be called with no argument at all, treating\n      // it as undefined, currently we require that undefined is passed explicitly.\n      'ReadableStreamDefaultReader closed promise should be rejected with undefined if that is the error',\n    ],\n  },\n  'readable-streams/floating-point-total-queue-size.any.js': {\n    comment: 'Queue size needs to use double math',\n    expectedFailures: [\n      'Floating point arithmetic must manifest near NUMBER.MAX_SAFE_INTEGER (total ends up positive)',\n      'Floating point arithmetic must manifest near 0 (total ends up positive, but clamped)',\n      'Floating point arithmetic must manifest near 0 (total ends up positive, and not clamped)',\n      'Floating point arithmetic must manifest near 0 (total ends up zero)',\n    ],\n  },\n  'readable-streams/from.any.js': {},\n  'readable-streams/garbage-collection.any.js': {\n    comment: 'See comments on individual tests',\n    disabledTests: [\n      // A hanging promise was cancelled\n      'ReadableStream closed promise should reject even if stream and reader JS references are lost',\n      'Garbage-collecting a ReadableStreamDefaultReader should not unlock its stream',\n      'A ReadableStream and its reader should not be garbage collected while there is a read promise pending',\n    ],\n    expectedFailures: [\n      // Failed to execute 'error' on 'ReadableStreamDefaultController': parameter 1 is not of type 'Value'\n      'ReadableStreamController methods should continue working properly when scripts lose their reference to the readable stream',\n    ],\n  },\n  'readable-streams/general.any.js': {\n    comment: 'See individual comments',\n    expectedFailures: [\n      // TODO(conform): We currently allow `new ReadableStream(null)`...\n      \"ReadableStream can't be constructed with garbage\",\n      // TODO(conform): We currently allow the empty type value\n      \"ReadableStream can't be constructed with an invalid type\",\n      // TODO(conform): The spec expects us to call pull an extra time here despite. [Despite what? -NP]\n      'ReadableStream: should pull after start, and after every read',\n      // TODO(conform): The standard generally anticipates that the closed\n      // promise rejection will happen before the read promise rejection.\n      // We don't follow that ordering currently.\n      'ReadableStream: if pull rejects, it should error the stream',\n      // TODO(conform): The read above is fulfilled by the c.enqueue() in the start algorithm.\n      // The spec expects us to call pull() again to prime the queue again for the next read\n      // but we currently do not. We only pull when we get another read\n      'ReadableStream: should only call pull once on a non-empty stream read from after start fulfills',\n      // TODO(conform): The spec expects us to call pull twice even tho we've only had a\n      // single read. We currently wait to pull again only when another read occurs.\n      'ReadableStream: should call pull in reaction to read()ing the last chunk, if not draining',\n      // TODO(conform): The spec expects us to call pull twice even tho we've only had a single\n      // read. We currently only call it when we have an actual read to fulfill.\n      \"ReadableStream: should not call pull until the previous pull call's promise fulfills\",\n    ],\n  },\n  'readable-streams/owning-type-message-port.any.js': {\n    comment: 'Enable once MessageChannel/MessagePort is implemented',\n    expectedFailures: [\n      'Transferred MessageChannel works as expected',\n      'Second branch of owning ReadableStream tee should end up into errors with transfer only values',\n    ],\n  },\n  'readable-streams/owning-type-video-frame.any.js': {\n    comment: 'VideoFrame is not implemented',\n    expectedFailures: [\n      'ReadableStream of type owning should close serialized chunks',\n      'ReadableStream of type owning should transfer JS chunks with transferred values',\n      'ReadableStream of type owning should error when trying to enqueue not serializable values',\n      'ReadableStream of type owning should clone serializable objects when teeing',\n      'ReadableStream of type owning should clone JS Objects with serializables when teeing',\n    ],\n  },\n  'readable-streams/owning-type.any.js': {\n    comment: \"Type 'owning' is not implemented\",\n    expectedFailures: [\n      'ReadableStream can be constructed with owning type',\n      'ReadableStream of type owning should call start with a ReadableStreamDefaultController',\n      'ReadableStream should be able to call enqueue with an empty transfer list',\n      'ReadableStream of type owning should transfer enqueued chunks',\n    ],\n  },\n  'readable-streams/patched-global.any.js': {\n    runInGlobalScope: true,\n  },\n  'readable-streams/read-task-handling.window.js': {\n    comment: 'document is not defined',\n    disabledTests: true,\n  },\n  'readable-streams/reentrant-strategies.any.js': {\n    comment: 'See individual comments',\n    expectedFailures: [\n      // TODO(conform): In this edge case, the spec expects the chunk to still be successfully\n      // enqueued even tho the stream gets closed. We currently throw in this case. Whether or\n      // not that ultimately matters is something up for debate since in either case the chunk\n      // cannot be read.\n      'close() inside size() should not crash',\n      // TODO(conform): Like the case above, the spec expects us to still successfully enqueue\n      // the chunk here. Unlike the previous case, we should still be able to read this chunk\n      // so this is a case we should definitely support.\n      'close request inside size() should work',\n      // TODO(conform): The spec expects us to still enqueue the value but the read() should still\n      // reject.\n      'error() inside size() should work',\n      // TODO(conform): The spec expects the enqueue() to still go through without an error\n      // here but we currently throw an error here.\n      'cancel() inside size() should work',\n      // TODO(conform): We currently fail this test. Need to investigate why\n      'read() inside of size() should behave as expected',\n    ],\n  },\n  'readable-streams/tee.any.js': {\n    comment: 'To be investigated',\n    expectedFailures: [\n      'ReadableStream teeing: errors in the source should propagate to both branches',\n      'ReadableStreamTee should only pull enough to fill the emptiest queue',\n      'ReadableStreamTee stops pulling when original stream errors while branch 1 is reading',\n      'ReadableStreamTee stops pulling when original stream errors while branch 2 is reading',\n      'ReadableStreamTee stops pulling when original stream errors while both branches are reading',\n      'ReadableStream teeing: canceling both branches should aggregate the cancel reasons into an array',\n      'ReadableStream teeing: canceling both branches in reverse order should aggregate the cancel reasons into an array',\n      'ReadableStream teeing: failing to cancel the original stream should cause cancel() to reject on branches',\n      'ReadableStream teeing: failing to cancel when canceling both branches in sequence with delay',\n      'ReadableStreamTee should not pull more chunks than can fit in the branch queue',\n    ],\n  },\n  'readable-streams/templated.any.js': {\n    comment: 'To be investigated',\n    disabledTests: [\n      // TODO(soon): This test appears to mess up the state of the workerd test case itself. Investigate why.\n      'ReadableStream reader (closed via cancel after getting reader): closed should fulfill with undefined',\n    ],\n    expectedFailures: [\n      'ReadableStream reader (closed before getting reader): releasing the lock should cause closed to reject and change identity',\n      'ReadableStream reader (closed after getting reader): releasing the lock should cause closed to reject and change identity',\n      'ReadableStream reader (closed via cancel after getting reader): releasing the lock should cause closed to reject and change identity',\n      'ReadableStream (errored via returning a rejected promise in start) reader: releasing the lock should cause closed to reject and change identity',\n      'ReadableStream reader (errored before getting reader): releasing the lock should cause closed to reject and change identity',\n      'ReadableStream reader (errored after getting reader): releasing the lock should cause closed to reject and change identity',\n    ],\n  },\n\n  'transferable/deserialize-error.window.js': {\n    comment: 'ReferenceError: document is not defined',\n    disabledTests: true,\n  },\n  'transferable/transfer-with-messageport.window.js': {\n    comment: 'Enable once MessagePort is supported.',\n    expectedFailures: [\n      'ReadableStream must not be serializable',\n      'WritableStream must not be serializable',\n      'TransformStream must not be serializable',\n      'Transferring a MessagePort with a ReadableStream should set `.ports`',\n      'Transferring a MessagePort with a WritableStream should set `.ports`',\n      'Transferring a MessagePort with a TransformStream should set `.ports`',\n      'Transferring a MessagePort with a ReadableStream should set `.ports`, advanced',\n      'Transferring a MessagePort with a WritableStream should set `.ports`, advanced',\n      'Transferring a MessagePort with a TransformStream should set `.ports`, advanced',\n      'Transferring a MessagePort with multiple streams should set `.ports`',\n    ],\n  },\n  'transferable/transform-stream-members.any.js': {\n    comment: 'Appears to be about the wrong type of error',\n    expectedFailures: [\n      'Transferring [object TransformStream],[object ReadableStream] should fail',\n      'Transferring [object ReadableStream],[object TransformStream] should fail',\n      'Transferring [object TransformStream],[object WritableStream] should fail',\n      'Transferring [object WritableStream],[object TransformStream] should fail',\n    ],\n  },\n\n  'transform-streams/backpressure.any.js': {\n    comment: 'A hanging Promise was canceled.',\n    disabledTests: true,\n  },\n  'transform-streams/cancel.any.js': {\n    comment: 'To be investigated',\n    expectedFailures: [\n      'readable.cancel() and a parallel writable.close() should reject if a transformer.cancel() calls controller.error()',\n      'writable.abort() and readable.cancel() should reject if a transformer.cancel() calls controller.error()',\n      'writable.abort() should not call cancel() again when already called from readable.cancel()',\n    ],\n  },\n  'transform-streams/errors.any.js': {\n    comment: 'To be investigated',\n    disabledTests: [\n      // TODO(soon): This test appears to mess up the state of the workerd test case itself. Investigate why.\n      'an exception from transform() should error the stream if terminate has been requested but not completed',\n    ],\n    expectedFailures: [\n      'TransformStream constructor should throw when start does',\n      'when strategy.size throws inside start(), the constructor should throw the same error',\n      'when strategy.size calls controller.error() then throws, the constructor should throw the first error',\n      'controller.error() should do nothing after a transformer method has thrown an exception',\n      'controller.error() should close writable immediately after readable.cancel()',\n      'erroring during write with backpressure should result in the write failing',\n    ],\n  },\n  'transform-streams/flush.any.js': {\n    comment: 'To be investigated',\n    expectedFailures: [\n      'error() during flush should cause writer.close() to reject',\n    ],\n  },\n  'transform-streams/general.any.js': {\n    comment: 'To be investigated',\n    expectedFailures: [\n      'it should be possible to call transform() synchronously',\n      'specifying a defined readableType should throw',\n      'specifying a defined writableType should throw',\n      'terminate() should abort writable immediately after readable.cancel()',\n    ],\n  },\n  'transform-streams/lipfuzz.any.js': {},\n  'transform-streams/patched-global.any.js': {\n    runInGlobalScope: true,\n  },\n  'transform-streams/properties.any.js': {\n    comment: 'The value cannot be converted because it is not an integer.',\n    expectedFailures: [\n      'transformer method start should be called with the right number of arguments',\n      \"transformer method start should be called even when it's located on the prototype chain\",\n      'transformer method transform should be called with the right number of arguments',\n      \"transformer method transform should be called even when it's located on the prototype chain\",\n      'transformer method flush should be called with the right number of arguments',\n      \"transformer method flush should be called even when it's located on the prototype chain\",\n    ],\n  },\n  'transform-streams/reentrant-strategies.any.js': {\n    comment: 'To be investigated',\n    expectedFailures: [\n      'enqueue() inside size() should work',\n      'terminate() inside size() should work',\n      'error() inside size() should work',\n      'readable cancel() inside size() should work',\n      'read() inside of size() should work',\n      'writer.write() inside size() should work',\n      'synchronous writer.write() inside size() should work',\n      'writer.close() inside size() should work',\n      'writer.abort() inside size() should work',\n    ],\n  },\n  'transform-streams/strategies.any.js': {\n    comment: 'To be investigated',\n    disabledTests: [\n      // TODO(soon): This test appears to mess up the state of the workerd test case itself. Investigate why.\n      'default readable strategy should be equivalent to { highWaterMark: 0 }',\n    ],\n    expectedFailures: [\n      'writable should have the correct size() function',\n      'a RangeError should be thrown for an invalid highWaterMark',\n      'a bad readableStrategy size function should error the stream on enqueue even when transformer.transform() catches the exception',\n      'a bad readableStrategy size function should cause writer.write() to reject on an identity transform',\n    ],\n  },\n  'transform-streams/terminate.any.js': {\n    comment: 'To be investigated',\n    expectedFailures: [\n      'controller.error() after controller.terminate() with queued chunk should error the readable',\n    ],\n  },\n\n  'writable-streams/aborting.any.js': {\n    comment: 'This is mostly nitpickiness about the type of error',\n    expectedFailures: [\n      \"Aborting a WritableStream before it starts should cause the writer's unsettled ready promise to reject\",\n      \"WritableStream if sink's abort throws, the promise returned by multiple writer.abort()s is the same and rejects\",\n      'when calling abort() twice on the same stream, both should give the same promise that fulfills with undefined',\n      'Aborting a WritableStream causes any outstanding write() promises to be rejected with the reason supplied',\n      'Aborting a WritableStream puts it in an errored state with the error passed to abort()',\n      'sink abort() should not be called if stream was erroring due to bad strategy before abort() was called',\n      'writer.abort() while there is an in-flight write, and then finish the write with rejection',\n      'writer.abort(), controller.error() while there is an in-flight write, and then finish the write',\n      'writer.abort(), controller.error() while there is an in-flight close, and then finish the close',\n      'controller.error(), writer.abort() while there is an in-flight write, and then finish the write',\n      'controller.error(), writer.abort() while there is an in-flight close, and then finish the close',\n    ],\n  },\n  'writable-streams/bad-strategies.any.js': {\n    comment: 'We have TypeError, they want RangeError',\n    expectedFailures: [\n      'Writable stream: invalid strategy.highWaterMark',\n      'Writable stream: invalid strategy.size return value',\n    ],\n  },\n  'writable-streams/bad-underlying-sinks.any.js': {\n    comment: 'To be investigated',\n    expectedFailures: [\n      'start: errors in start cause WritableStream constructor to throw',\n      'write: returning a rejected promise (second write) should cause writer write() and ready to reject',\n    ],\n  },\n  'writable-streams/byte-length-queuing-strategy.any.js': {},\n  'writable-streams/close.any.js': {},\n  'writable-streams/constructor.any.js': {\n    comment: 'These are mostly about validation of params',\n    expectedFailures:\n      process.platform === 'win32'\n        ? [\n            'WritableStream should be writable and ready should fulfill immediately if the strategy does not apply backpressure',\n          ]\n        : [\n            'WritableStream should be writable and ready should fulfill immediately if the strategy does not apply backpressure',\n            'underlyingSink argument should be converted after queuingStrategy argument',\n          ],\n  },\n  'writable-streams/count-queuing-strategy.any.js': {},\n  'writable-streams/crashtests/garbage-collection.any.js': {},\n  'writable-streams/error.any.js': {},\n  'writable-streams/floating-point-total-queue-size.any.js': {\n    comment: 'Seems we should be using a double for queue size',\n    expectedFailures: [\n      'Floating point arithmetic must manifest near NUMBER.MAX_SAFE_INTEGER (total ends up positive)',\n      'Floating point arithmetic must manifest near 0 (total ends up positive, and not clamped)',\n      'Floating point arithmetic must manifest near 0 (total ends up zero)',\n    ],\n  },\n  'writable-streams/garbage-collection.any.js': {},\n  'writable-streams/general.any.js': {},\n  'writable-streams/properties.any.js': {},\n  'writable-streams/reentrant-strategy.any.js': {},\n  'writable-streams/start.any.js': {\n    comment: 'To be investigated',\n    expectedFailures: [\n      \"underlying sink's write or close should not be called if start throws\",\n    ],\n  },\n  'writable-streams/write.any.js': {},\n} satisfies TestRunnerConfig;\n"
  },
  {
    "path": "src/wpt/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tools/base.tsconfig.json\",\n  \"compilerOptions\": {\n    \"types\": [\"@types/node\"],\n    \"lib\": [\"ESNext\", \"dom\"],\n    \"isolatedModules\": false,\n    \"verbatimModuleSyntax\": false,\n    \"paths\": {\n      \"harness/*\": [\"./harness/*\"]\n    }\n  },\n  \"include\": [\"**/*.ts\"]\n}\n"
  },
  {
    "path": "src/wpt/url-test.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { type TestRunnerConfig } from 'harness/harness';\n\nexport default {\n  'IdnaTestV2-removed.window.js': {},\n  'IdnaTestV2.window.js': {},\n  'historical.any.js': {},\n  'idlharness.any.js': {\n    comment:\n      'IDL tests fail because Workers exposes globals differently than browsers (not as own properties of self)',\n    expectedFailures: [\n      'URLSearchParams interface: iterable<USVString, USVString>',\n    ],\n  },\n  'javascript-urls.window.js': {\n    comment: 'Implement `globalThis.document`',\n    expectedFailures: [\n      'javascript: URL that fails to parse due to invalid host',\n      'javascript: URL that fails to parse due to invalid host and has a U+0009 in scheme',\n      'javascript: URL without an opaque path',\n      'javascript: URL containing a JavaScript string split over path and query',\n      'javascript: URL containing a JavaScript string split over path and query and has a U+000A in scheme',\n      'javascript: URL with extra slashes at the start',\n    ],\n  },\n  'percent-encoding.window.js': {\n    comment:\n      'Implement test code modification feature to allow running this test without document',\n    disabledTests: true,\n  },\n  'toascii.window.js': {\n    replace: (code): string =>\n      code.replace(/\\[\"url\", \"a\", \"area\"\\]/, '[ \"url\" ]'),\n  },\n  'url-constructor.any.js': {},\n  'url-origin.any.js': {},\n  'url-searchparams.any.js': {},\n  'url-setters-a-area.window.js': {\n    comment:\n      'Excluded because it uses the same test data as url-setters.any.js',\n    // Node does the same: https://github.com/nodejs/node/blob/5ab7c4c5b01e7579fd436000232f0f0484289d44/test/wpt/status/url.json#L24\n    omittedTests: true,\n  },\n  'url-setters-stripping.any.js': {},\n  'url-setters.any.js': {},\n  'url-statics-canparse.any.js': {},\n  'url-statics-parse.any.js': {},\n  'url-tojson.any.js': {},\n  'urlencoded-parser.any.js': {\n    comment:\n      'Requests fail due to HTTP method \"LADIDA\", responses fail due to shift_jis encoding',\n    expectedFailures: [\n      /request.formData\\(\\) with input:/,\n      /response.formData\\(\\) with input:/,\n    ],\n  },\n  'urlsearchparams-append.any.js': {},\n  'urlsearchparams-constructor.any.js': {\n    comment: 'Fix this eventually',\n    expectedFailures: ['URLSearchParams constructor, DOMException as argument'],\n  },\n  'urlsearchparams-delete.any.js': {},\n  'urlsearchparams-foreach.any.js': {},\n  'urlsearchparams-get.any.js': {},\n  'urlsearchparams-getall.any.js': {},\n  'urlsearchparams-has.any.js': {},\n  'urlsearchparams-set.any.js': {},\n  'urlsearchparams-size.any.js': {},\n  'urlsearchparams-sort.any.js': {},\n  'urlsearchparams-stringifier.any.js': {},\n} satisfies TestRunnerConfig;\n"
  },
  {
    "path": "src/wpt/urlpattern-standard-test.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { type TestRunnerConfig } from 'harness/harness';\n\nexport default {\n  'urlpattern-hasregexpgroups.any.js': {},\n  'urlpattern.any.js': {},\n  'urlpattern.https.any.js': {\n    comment: 'Test cases are identical to urlpattern.any.js.',\n    omittedTests: true,\n  },\n} satisfies TestRunnerConfig;\n"
  },
  {
    "path": "src/wpt/urlpattern-test.ts",
    "content": "// Copyright (c) 2017-2022 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { type TestRunnerConfig } from 'harness/harness';\n\nexport default {\n  'urlpattern-hasregexpgroups.any.js': {\n    comment: 'urlpattern implementation will soon be replaced with ada-url',\n    expectedFailures: [\n      // Each of these *ought* to pass. They are included here because we\n      // know they currently do not. Each needs to be investigated.\n      '', // This file consists of one unnamed subtest\n    ],\n  },\n  'urlpattern.any.js': {\n    comment: 'urlpattern implementation will soon be replaced with ada-url',\n    expectedFailures: [\n      // Each of these *ought* to pass. They are included here because we\n      // know they currently do not. Each needs to be investigated.\n      'Pattern: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com?query#hash\"}] Inputs: [{\"pathname\":\"/foo/bar\"}]',\n      'Pattern: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com?query#hash\"}] Inputs: [{\"hostname\":\"example.com\",\"pathname\":\"/foo/bar\"}]',\n      'Pattern: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com?query#hash\"}] Inputs: [{\"protocol\":\"https\",\"hostname\":\"example.com\",\"pathname\":\"/foo/bar\"}]',\n      'Pattern: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com\"}] Inputs: [{\"protocol\":\"https\",\"hostname\":\"example.com\",\"pathname\":\"/foo/bar\"}]',\n      'Pattern: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com\"}] Inputs: [{\"protocol\":\"https\",\"hostname\":\"example.com\",\"pathname\":\"/foo/bar/baz\"}]',\n      'Pattern: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com?query#hash\"}] Inputs: [{\"protocol\":\"https\",\"hostname\":\"example.com\",\"pathname\":\"/foo/bar\",\"search\":\"otherquery\",\"hash\":\"otherhash\"}]',\n      'Pattern: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com\"}] Inputs: [{\"protocol\":\"https\",\"hostname\":\"example.com\",\"pathname\":\"/foo/bar\",\"search\":\"otherquery\",\"hash\":\"otherhash\"}]',\n      'Pattern: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com?otherquery#otherhash\"}] Inputs: [{\"protocol\":\"https\",\"hostname\":\"example.com\",\"pathname\":\"/foo/bar\",\"search\":\"otherquery\",\"hash\":\"otherhash\"}]',\n      'Pattern: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com?query#hash\"}] Inputs: [\"https://example.com/foo/bar\"]',\n      'Pattern: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com?query#hash\"}] Inputs: [\"https://example.com/foo/bar?otherquery#otherhash\"]',\n      'Pattern: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com?query#hash\"}] Inputs: [\"https://example.com/foo/bar?query#hash\"]',\n      'Pattern: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com?query#hash\"}] Inputs: [\"https://example.com/foo/bar/baz\"]',\n      'Pattern: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com?query#hash\"}] Inputs: [\"https://other.com/foo/bar\"]',\n      'Pattern: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com?query#hash\"}] Inputs: [\"http://other.com/foo/bar\"]',\n      'Pattern: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com?query#hash\"}] Inputs: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com\"}]',\n      'Pattern: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com?query#hash\"}] Inputs: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com?query#hash\"}]',\n      'Pattern: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com?query#hash\"}] Inputs: [{\"pathname\":\"/foo/bar/baz\",\"baseURL\":\"https://example.com\"}]',\n      'Pattern: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com?query#hash\"}] Inputs: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://other.com\"}]',\n      'Pattern: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"https://example.com?query#hash\"}] Inputs: [{\"pathname\":\"/foo/bar\",\"baseURL\":\"http://example.com\"}]',\n      'Pattern: [{\"pathname\":\"/foo/:bar?\"}] Inputs: [{\"pathname\":\"/foo\"}]',\n      'Pattern: [{\"pathname\":\"/foo/:bar*\"}] Inputs: [{\"pathname\":\"/foo\"}]',\n      'Pattern: [{\"pathname\":\"/foo/(.*)?\"}] Inputs: [{\"pathname\":\"/foo\"}]',\n      'Pattern: [{\"pathname\":\"/foo/*?\"}] Inputs: [{\"pathname\":\"/foo\"}]',\n      'Pattern: [{\"pathname\":\"/foo/(.*)*\"}] Inputs: [{\"pathname\":\"/foo\"}]',\n      'Pattern: [{\"pathname\":\"/foo/**\"}] Inputs: [{\"pathname\":\"/foo\"}]',\n      'Pattern: [{\"protocol\":\"(.*)\"}] Inputs: [{\"protocol\":\"café\"}]',\n      'Pattern: [{\"hostname\":\"xn--caf-dma.com\"}] Inputs: [{\"hostname\":\"café.com\"}]',\n      'Pattern: [{\"hostname\":\"café.com\"}] Inputs: [{\"hostname\":\"café.com\"}]',\n      'Pattern: [{\"protocol\":\"http\",\"port\":\"80\"}] Inputs: [{\"protocol\":\"http\",\"port\":\"80\"}]',\n      'Pattern: [{\"port\":\"(.*)\"}] Inputs: [{\"port\":\"invalid80\"}]',\n      'Pattern: [{\"pathname\":\"/foo/bar\"}] Inputs: [{\"pathname\":\"foo/bar\"}]',\n      'Pattern: [{\"pathname\":\"./foo/bar\",\"baseURL\":\"https://example.com\"}] Inputs: [{\"pathname\":\"foo/bar\",\"baseURL\":\"https://example.com\"}]',\n      'Pattern: [{\"pathname\":\"\",\"baseURL\":\"https://example.com\"}] Inputs: [{\"pathname\":\"/\",\"baseURL\":\"https://example.com\"}]',\n      'Pattern: [{\"pathname\":\"{/bar}\",\"baseURL\":\"https://example.com/foo/\"}] Inputs: [{\"pathname\":\"./bar\",\"baseURL\":\"https://example.com/foo/\"}]',\n      'Pattern: [{\"pathname\":\"\\\\\\\\/bar\",\"baseURL\":\"https://example.com/foo/\"}] Inputs: [{\"pathname\":\"./bar\",\"baseURL\":\"https://example.com/foo/\"}]',\n      'Pattern: [{\"pathname\":\"b\",\"baseURL\":\"https://example.com/foo/\"}] Inputs: [{\"pathname\":\"./b\",\"baseURL\":\"https://example.com/foo/\"}]',\n      'Pattern: [{\"pathname\":\"foo/bar\",\"baseURL\":\"https://example.com\"}] Inputs: [\"https://example.com/foo/bar\"]',\n      'Pattern: [{\"pathname\":\":name.html\",\"baseURL\":\"https://example.com\"}] Inputs: [\"https://example.com/foo.html\"]',\n      'Pattern: [{\"protocol\":\"javascript\",\"pathname\":\"var x = 1;\"}] Inputs: [{\"protocol\":\"javascript\",\"pathname\":\"var x = 1;\"}]',\n      'Pattern: [{\"protocol\":\"(data|javascript)\",\"pathname\":\"var x = 1;\"}] Inputs: [{\"protocol\":\"javascript\",\"pathname\":\"var x = 1;\"}]',\n      'Pattern: [{\"pathname\":\"var x = 1;\"}] Inputs: [{\"pathname\":\"var x = 1;\"}]',\n      'Pattern: [\"https://example.com:8080/foo?bar#baz\"] Inputs: [{\"pathname\":\"/foo\",\"search\":\"bar\",\"hash\":\"baz\",\"baseURL\":\"https://example.com:8080\"}]',\n      'Pattern: [\"/foo?bar#baz\",\"https://example.com:8080\"] Inputs: [{\"pathname\":\"/foo\",\"search\":\"bar\",\"hash\":\"baz\",\"baseURL\":\"https://example.com:8080\"}]',\n      'Pattern: [\"http{s}?://{*.}?example.com/:product/:endpoint\"] Inputs: [\"https://sub.example.com/foo/bar\"]',\n      'Pattern: [\"https://example.com?foo\"] Inputs: [\"https://example.com/?foo\"]',\n      'Pattern: [\"https://example.com#foo\"] Inputs: [\"https://example.com/#foo\"]',\n      'Pattern: [\"https://example.com:8080?foo\"] Inputs: [\"https://example.com:8080/?foo\"]',\n      'Pattern: [\"https://example.com:8080#foo\"] Inputs: [\"https://example.com:8080/#foo\"]',\n      'Pattern: [\"https://example.com/?foo\"] Inputs: [\"https://example.com/?foo\"]',\n      'Pattern: [\"https://example.com/#foo\"] Inputs: [\"https://example.com/#foo\"]',\n      'Pattern: [\"https://example.com/*?foo\"] Inputs: [\"https://example.com/?foo\"]',\n      'Pattern: [\"https://example.com/*\\\\\\\\?foo\"] Inputs: [\"https://example.com/?foo\"]',\n      'Pattern: [\"https://example.com/:name?foo\"] Inputs: [\"https://example.com/bar?foo\"]',\n      'Pattern: [\"https://example.com/:name\\\\\\\\?foo\"] Inputs: [\"https://example.com/bar?foo\"]',\n      'Pattern: [\"https://example.com/(bar)?foo\"] Inputs: [\"https://example.com/bar?foo\"]',\n      'Pattern: [\"https://example.com/(bar)\\\\\\\\?foo\"] Inputs: [\"https://example.com/bar?foo\"]',\n      'Pattern: [\"https://example.com/{bar}?foo\"] Inputs: [\"https://example.com/bar?foo\"]',\n      'Pattern: [\"https://example.com/{bar}\\\\\\\\?foo\"] Inputs: [\"https://example.com/bar?foo\"]',\n      'Pattern: [\"https://example.com/\"] Inputs: [\"https://example.com:8080/\"]',\n      'Pattern: [\"data\\\\\\\\:foobar\"] Inputs: [\"data:foobar\"]',\n      'Pattern: [\"https://{sub.}?example.com/foo\"] Inputs: [\"https://example.com/foo\"]',\n      'Pattern: [\"https://(sub.)?example.com/foo\"] Inputs: [\"https://example.com/foo\"]',\n      'Pattern: [\"https://(sub.)?example(.com/)foo\"] Inputs: [\"https://example.com/foo\"]',\n      'Pattern: [\"https://(sub(?:.))?example.com/foo\"] Inputs: [\"https://example.com/foo\"]',\n      'Pattern: [\"file:///foo/bar\"] Inputs: [\"file:///foo/bar\"]',\n      'Pattern: [\"data:\"] Inputs: [\"data:\"]',\n      'Pattern: [\"foo://bar\"] Inputs: [\"foo://bad_url_browser_interop\"]',\n      'Pattern: [\"https://example.com/foo?bar#baz\"] Inputs: [{\"protocol\":\"https:\",\"search\":\"?bar\",\"hash\":\"#baz\",\"baseURL\":\"http://example.com/foo\"}]',\n      'Pattern: [\"?bar#baz\",\"https://example.com/foo\"] Inputs: [\"?bar#baz\",\"https://example.com/foo\"]',\n      'Pattern: [\"?bar\",\"https://example.com/foo#baz\"] Inputs: [\"?bar\",\"https://example.com/foo#snafu\"]',\n      'Pattern: [\"#baz\",\"https://example.com/foo?bar\"] Inputs: [\"#baz\",\"https://example.com/foo?bar\"]',\n      'Pattern: [\"#baz\",\"https://example.com/foo\"] Inputs: [\"#baz\",\"https://example.com/foo\"]',\n      'Pattern: [\"https://foo\\\\\\\\:bar@example.com\"] Inputs: [\"https://foo:bar@example.com\"]',\n      'Pattern: [\"https://foo@example.com\"] Inputs: [\"https://foo@example.com\"]',\n      'Pattern: [\"https://\\\\\\\\:bar@example.com\"] Inputs: [\"https://:bar@example.com\"]',\n      'Pattern: [\"https://:user::pass@example.com\"] Inputs: [\"https://foo:bar@example.com\"]',\n      'Pattern: [\"https\\\\\\\\:foo\\\\\\\\:bar@example.com\"] Inputs: [\"https:foo:bar@example.com\"]',\n      'Pattern: [\"data\\\\\\\\:foo\\\\\\\\:bar@example.com\"] Inputs: [\"data:foo:bar@example.com\"]',\n      'Pattern: [\"https://foo{\\\\\\\\:}bar@example.com\"] Inputs: [\"https://foo:bar@example.com\"]',\n      'Pattern: [\"data{\\\\\\\\:}channel.html\",\"https://example.com\"] Inputs: [\"https://example.com/data:channel.html\"]',\n      'Pattern: [\"http://[\\\\\\\\:\\\\\\\\:1]/\"] Inputs: [\"http://[::1]/\"]',\n      'Pattern: [\"http://[\\\\\\\\:\\\\\\\\:1]:8080/\"] Inputs: [\"http://[::1]:8080/\"]',\n      'Pattern: [\"http://[\\\\\\\\:\\\\\\\\:a]/\"] Inputs: [\"http://[::a]/\"]',\n      'Pattern: [\"http://[:address]/\"] Inputs: [\"http://[::1]/\"]',\n      'Pattern: [\"http://[\\\\\\\\:\\\\\\\\:AB\\\\\\\\::num]/\"] Inputs: [\"http://[::ab:1]/\"]',\n      'Pattern: [{\"hostname\":\"[\\\\\\\\:\\\\\\\\:AB\\\\\\\\::num]\"}] Inputs: [{\"hostname\":\"[::ab:1]\"}]',\n      'Pattern: [\"data\\\\\\\\:text/javascript,let x = 100/:tens?5;\"] Inputs: [\"data:text/javascript,let x = 100/5;\"]',\n      'Pattern: [{\"pathname\":\"/foo\"},\"https://example.com\"] Inputs: undefined',\n      'Pattern: [{\"pathname\":\":name*\"}] Inputs: [{\"pathname\":\"foobar\"}]',\n      'Pattern: [{\"pathname\":\":name+\"}] Inputs: [{\"pathname\":\"foobar\"}]',\n      'Pattern: [{\"pathname\":\":name\"}] Inputs: [{\"pathname\":\"foobar\"}]',\n      'Pattern: [{\"pathname\":\"(foo)(.*)\"}] Inputs: [{\"pathname\":\"foobarbaz\"}]',\n      'Pattern: [{\"pathname\":\"{(foo)bar}(.*)\"}] Inputs: [{\"pathname\":\"foobarbaz\"}]',\n      'Pattern: [{\"pathname\":\"(foo)?(.*)\"}] Inputs: [{\"pathname\":\"foobarbaz\"}]',\n      'Pattern: [{\"pathname\":\"{:foo}(.*)\"}] Inputs: [{\"pathname\":\"foobarbaz\"}]',\n      'Pattern: [{\"pathname\":\"{:foo}(barbaz)\"}] Inputs: [{\"pathname\":\"foobarbaz\"}]',\n      'Pattern: [{\"pathname\":\"{:foo}{(.*)}\"}] Inputs: [{\"pathname\":\"foobarbaz\"}]',\n      'Pattern: [{\"pathname\":\"{:foo}{bar(.*)}\"}] Inputs: [{\"pathname\":\"foobarbaz\"}]',\n      'Pattern: [{\"pathname\":\"{:foo}:bar(.*)\"}] Inputs: [{\"pathname\":\"foobarbaz\"}]',\n      'Pattern: [{\"pathname\":\"{:foo}?(.*)\"}] Inputs: [{\"pathname\":\"foobarbaz\"}]',\n      'Pattern: [{\"pathname\":\"{:foo\\\\\\\\bar}\"}] Inputs: [{\"pathname\":\"foobar\"}]',\n      'Pattern: [{\"pathname\":\"{:foo\\\\\\\\.bar}\"}] Inputs: [{\"pathname\":\"foo.bar\"}]',\n      'Pattern: [{\"pathname\":\"{:foo(foo)bar}\"}] Inputs: [{\"pathname\":\"foobar\"}]',\n      'Pattern: [{\"pathname\":\"{:foo}bar\"}] Inputs: [{\"pathname\":\"foobar\"}]',\n      'Pattern: [{\"pathname\":\":foo\\\\\\\\bar\"}] Inputs: [{\"pathname\":\"foobar\"}]',\n      'Pattern: [{\"pathname\":\":foo{}(.*)\"}] Inputs: [{\"pathname\":\"foobar\"}]',\n      'Pattern: [{\"pathname\":\":foo{}bar\"}] Inputs: [{\"pathname\":\"foobar\"}]',\n      'Pattern: [{\"pathname\":\":foo{}?bar\"}] Inputs: [{\"pathname\":\"foobar\"}]',\n      'Pattern: [{\"pathname\":\"*{}**?\"}] Inputs: [{\"pathname\":\"foobar\"}]',\n      'Pattern: [{\"pathname\":\":foo(baz)(.*)\"}] Inputs: [{\"pathname\":\"bazbar\"}]',\n      'Pattern: [{\"pathname\":\":foo(baz)bar\"}] Inputs: [{\"pathname\":\"bazbar\"}]',\n      'Pattern: [{\"pathname\":\"*/*\"}] Inputs: [{\"pathname\":\"foo/bar\"}]',\n      'Pattern: [{\"pathname\":\"*\\\\\\\\/*\"}] Inputs: [{\"pathname\":\"foo/bar\"}]',\n      'Pattern: [{\"pathname\":\"*/{*}\"}] Inputs: [{\"pathname\":\"foo/bar\"}]',\n      'Pattern: [{\"pathname\":\"./foo\"}] Inputs: [{\"pathname\":\"./foo\"}]',\n      'Pattern: [{\"pathname\":\"../foo\"}] Inputs: [{\"pathname\":\"../foo\"}]',\n      'Pattern: [{\"pathname\":\":foo./\"}] Inputs: [{\"pathname\":\"bar./\"}]',\n      'Pattern: [{\"pathname\":\":foo../\"}] Inputs: [{\"pathname\":\"bar../\"}]',\n      'Pattern: [{\"pathname\":\"/:foo\\\\\\\\bar\"}] Inputs: [{\"pathname\":\"/bazbar\"}]',\n      'Pattern: [\"https://example.com:8080/foo?bar#baz\",{\"ignoreCase\":true}] Inputs: [{\"pathname\":\"/FOO\",\"search\":\"BAR\",\"hash\":\"BAZ\",\"baseURL\":\"https://example.com:8080\"}]',\n      'Pattern: [\"/foo?bar#baz\",\"https://example.com:8080\",{\"ignoreCase\":true}] Inputs: [{\"pathname\":\"/FOO\",\"search\":\"BAR\",\"hash\":\"BAZ\",\"baseURL\":\"https://example.com:8080\"}]',\n      'Pattern: [{\"search\":\"foo\",\"baseURL\":\"https://example.com/a/+/b\"}] Inputs: [{\"search\":\"foo\",\"baseURL\":\"https://example.com/a/+/b\"}]',\n      'Pattern: [{\"hash\":\"foo\",\"baseURL\":\"https://example.com/?q=*&v=?&hmm={}&umm=()\"}] Inputs: [{\"hash\":\"foo\",\"baseURL\":\"https://example.com/?q=*&v=?&hmm={}&umm=()\"}]',\n      'Pattern: [\"#foo\",\"https://example.com/?q=*&v=?&hmm={}&umm=()\"] Inputs: [\"https://example.com/?q=*&v=?&hmm={}&umm=()#foo\"]',\n      'Pattern: [{\"pathname\":\"/([[a-z]--a])\"}] Inputs: [{\"pathname\":\"/a\"}]',\n      'Pattern: [{\"pathname\":\"/([[a-z]--a])\"}] Inputs: [{\"pathname\":\"/z\"}]',\n      'Pattern: [{\"pathname\":\"/([\\\\\\\\d&&[0-1]])\"}] Inputs: [{\"pathname\":\"/0\"}]',\n      'Pattern: [{\"pathname\":\"/([\\\\\\\\d&&[0-1]])\"}] Inputs: [{\"pathname\":\"/3\"}]',\n      'Pattern: [\"http://🚲.com/\"] Inputs: [\"http://🚲.com/\"]',\n      'Pattern: [{\"pathname\":\"\\\\ud83d \\\\udeb2\"}] Inputs: []',\n      'Pattern: [{\"pathname\":\"test/:a𐑐b\"}] Inputs: [{\"pathname\":\"test/foo\"}]',\n      'Pattern: [{\"hostname\":\"bad#hostname\"}] Inputs: [{\"hostname\":\"bad\"}]',\n      'Pattern: [{\"hostname\":\"bad/hostname\"}] Inputs: [{\"hostname\":\"bad\"}]',\n      'Pattern: [{\"hostname\":\"bad\\\\\\\\\\\\\\\\hostname\"}] Inputs: [{\"hostname\":\"badhostname\"}]',\n      'Pattern: [{\"hostname\":\"bad\\\\nhostname\"}] Inputs: [{\"hostname\":\"badhostname\"}]',\n      'Pattern: [{\"hostname\":\"bad\\\\rhostname\"}] Inputs: [{\"hostname\":\"badhostname\"}]',\n      'Pattern: [{\"hostname\":\"bad\\\\thostname\"}] Inputs: [{\"hostname\":\"badhostname\"}]',\n      'Pattern: [{\"port\":\"80\"}] Inputs: [{\"port\":\"8\\\\t0\"}]',\n      'Pattern: [{\"port\":\"80\"}] Inputs: [{\"port\":\"80x\"}]',\n      'Pattern: [{\"port\":\"80\"}] Inputs: [{\"port\":\"80?x\"}]',\n      'Pattern: [{\"port\":\"80\"}] Inputs: [{\"port\":\"80\\\\\\\\x\"}]',\n      'Pattern: [\"https://{sub.}?example{.com/}foo\"] Inputs: [\"https://example.com/foo\"]',\n      'Pattern: [{\"protocol\":\"http\",\"hostname\":\"example.com/ignoredpath\"}] Inputs: [\"http://example.com/\"]',\n      'Pattern: [{\"protocol\":\"http\",\"hostname\":\"example.com\\\\\\\\?ignoredsearch\"}] Inputs: [\"http://example.com/\"]',\n      'Pattern: [{\"protocol\":\"http\",\"hostname\":\"example.com#ignoredhash\"}] Inputs: [\"http://example.com/\"]',\n      'Pattern: [\"https://www.example.com/*\"] Inputs: [\"https://www.example.com/x\"]',\n      'Pattern: [\"https://www.example.com/*\"] Inputs: [\"https://www.example.com/xyz\"]',\n      'Pattern: [\"https://www.example.com/*\"] Inputs: [\"https://www.example.com/example\"]',\n      'Pattern: [\"https://www.example.com/*\"] Inputs: [\"https://www.example.com/text\"]',\n      'Pattern: [\"https://www.example.com/*\"] Inputs: [\"https://www.example.com/path/with/x\"]',\n    ],\n  },\n  'urlpattern.https.any.js': {\n    comment: 'Test cases are identical to urlpattern.any.js.',\n    omittedTests: true,\n  },\n} satisfies TestRunnerConfig;\n"
  },
  {
    "path": "src/wpt/webidl-test.ts",
    "content": "// Copyright (c) 2017-2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { type TestRunnerConfig } from 'harness/harness';\n\nexport default {\n  'ecmascript-binding/builtin-function-properties.any.js': {},\n  'ecmascript-binding/class-string-interface.any.js': {\n    comment: '@@toStringTag property descriptor mismatch',\n    expectedFailures: [\n      '@@toStringTag exists on the prototype with the appropriate descriptor',\n    ],\n  },\n  'ecmascript-binding/class-string-iterator-prototype-object.any.js': {\n    comment: 'Iterator @@toStringTag is different in workerd',\n    expectedFailures: [\n      '@@toStringTag exists with the appropriate descriptor',\n      'Object.prototype.toString',\n      'Object.prototype.toString applied to a null-prototype instance',\n    ],\n  },\n  'ecmascript-binding/class-string-named-properties-object.window.js': {\n    comment: 'Window-specific test',\n    omittedTests: true,\n  },\n  'ecmascript-binding/es-exceptions/DOMException-constants.any.js': {\n    comment: 'DOMException constants have wrong configurable descriptor',\n    expectedFailures: true,\n  },\n  'ecmascript-binding/es-exceptions/DOMException-constructor-and-prototype.any.js':\n    {\n      comment: 'DOMException property descriptors differ from spec',\n      expectedFailures: true,\n    },\n  'ecmascript-binding/es-exceptions/DOMException-constructor-behavior.any.js': {\n    comment: 'DOMException property inheritance differs from spec',\n    expectedFailures: true,\n  },\n  'ecmascript-binding/es-exceptions/DOMException-custom-bindings.any.js': {\n    comment: 'DOMException property descriptors differ from spec',\n    expectedFailures: true,\n  },\n  'ecmascript-binding/es-exceptions/DOMException-is-error.any.js': {\n    comment: 'DOMException.prototype not in Error.prototype chain',\n    expectedFailures: [''],\n  },\n  'ecmascript-binding/global-immutable-prototype.any.js': {\n    comment: 'globalThis prototype is unconfigurable in workerd',\n    expectedFailures: true,\n  },\n  'ecmascript-binding/global-mutable-prototype.any.js': {\n    comment: 'globalThis prototype is unconfigurable in workerd',\n    expectedFailures: true,\n  },\n  'ecmascript-binding/global-object-implicit-this-value.any.js': {\n    comment:\n      'Script fails to load - addEventListener is not defined in workerd',\n    expectedFailures: [\n      \"Global object's getter throws when called on incompatible object\",\n      \"Global object's setter throws when called on incompatible object\",\n      \"Global object's operation throws when called on incompatible object\",\n      \"Global object's getter works when called on null / undefined\",\n      \"Global object's setter works when called on null / undefined\",\n      \"Global object's operation works when called on null / undefined\",\n    ],\n  },\n  'ecmascript-binding/legacy-factor-function-subclass.window.js': {\n    comment: 'Window-specific test',\n    omittedTests: true,\n  },\n  'ecmascript-binding/legacy-factory-function-builtin-properties.window.js': {\n    comment: 'Window-specific test',\n    omittedTests: true,\n  },\n  'ecmascript-binding/legacy-platform-object/helper.js': {},\n  'ecmascript-binding/no-regexp-special-casing.any.js': {\n    comment: 'self.addEventListener is not available in workerd',\n    expectedFailures: [\n      'Conversion to a sequence works',\n      'Can be used as an object implementing a callback interface',\n    ],\n  },\n  'ecmascript-binding/observable-array-no-leak-of-internals.window.js': {\n    comment: 'Window-specific test',\n    omittedTests: true,\n  },\n  'ecmascript-binding/observable-array-ownkeys.window.js': {\n    comment: 'Window-specific test',\n    omittedTests: true,\n  },\n  'ecmascript-binding/support/create-realm.js': {},\n  'idlharness.any.js': {\n    comment:\n      'Remaining IDL failures: DOMException attribute/inherit checks and QuotaExceededError (not implemented)',\n    expectedFailures: [\n      // DOMException attributes (name, message, code) use instance properties instead of\n      // prototype accessors, so both the attribute descriptor checks and inherit checks fail\n      'DOMException interface: attribute name',\n      'DOMException interface: attribute message',\n      'DOMException interface: attribute code',\n      /^DOMException interface: new DOMException.*must inherit property/,\n      // QuotaExceededError is not implemented\n      /^QuotaExceededError interface/,\n    ],\n  },\n} satisfies TestRunnerConfig;\n"
  },
  {
    "path": "src/wpt/websockets-test.ts",
    "content": "// Copyright (c) 2017-2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { type TestRunnerConfig } from 'harness/harness';\n\n// The WPT WebSocket tests use addEventListener with useCapture=true, which workerd\n// doesn't support. This function removes the useCapture argument from those calls.\n// Pattern: addEventListener('event', handler, true) -> addEventListener('event', handler)\nfunction removeUseCapture(code: string): string {\n  return code.replace(/,\\s*true\\s*\\)/g, ')');\n}\n\nexport default {\n  'Close-1000-reason.any.js': {\n    replace: removeUseCapture,\n  },\n  'Close-1000-verify-code.any.js': {\n    replace: removeUseCapture,\n  },\n  'Close-1000.any.js': {\n    replace: removeUseCapture,\n  },\n  'Close-1005-verify-code.any.js': {\n    replace: removeUseCapture,\n  },\n  'Close-1005.any.js': {\n    replace: (code: string): string => {\n      code = removeUseCapture(code);\n      // The test expects close(1005) to throw, leaving the WebSocket open.\n      // In workerd, the open connection's read loop is registered as a\n      // waitUntil task that keeps the IoContext alive indefinitely.\n      // Add a cleanup to close the WebSocket when the test completes so the\n      // connection is properly shut down.\n      return code.replace(\n        'var isOpenCalled = false;',\n        'var isOpenCalled = false;\\ntest.add_cleanup(function() { wsocket.close(); });'\n      );\n    },\n  },\n  'Close-2999-reason.any.js': {\n    replace: (code: string): string => {\n      code = removeUseCapture(code);\n      // The test expects close(2999) to throw, leaving the WebSocket open.\n      // Add a cleanup to close the WebSocket when the test completes so the\n      // connection is properly shut down.\n      return code.replace(\n        'var wsocket = CreateWebSocket(false, false);',\n        'var wsocket = CreateWebSocket(false, false);\\ntest.add_cleanup(function() { wsocket.close(); });'\n      );\n    },\n  },\n  'Close-3000-reason.any.js': {\n    replace: removeUseCapture,\n  },\n  'Close-3000-verify-code.any.js': {\n    replace: removeUseCapture,\n  },\n  'Close-4999-reason.any.js': {\n    replace: removeUseCapture,\n  },\n  'Close-Reason-124Bytes.any.js': {\n    replace: (code: string): string => {\n      code = removeUseCapture(code);\n      // The test expects close(1000, longReason) to throw SYNTAX_ERR, leaving\n      // the WebSocket still open. Add a cleanup to close the WebSocket when the\n      // test completes so the connection is properly shut down.\n      return code.replace(\n        'var isOpenCalled = false;',\n        'var isOpenCalled = false;\\ntest.add_cleanup(function() { wsocket.close(); });'\n      );\n    },\n  },\n  'Close-delayed.any.js': {\n    replace: removeUseCapture,\n  },\n  'Close-onlyReason.any.js': {\n    replace: (code: string): string => {\n      code = removeUseCapture(code);\n      // The test expects close(\"reason\") to throw, leaving the WebSocket open.\n      // Add a cleanup to close the WebSocket when the test completes so the\n      // connection is properly shut down.\n      return code.replace(\n        'var wsocket = CreateWebSocket(false, false);',\n        'var wsocket = CreateWebSocket(false, false);\\ntest.add_cleanup(function() { wsocket.close(); });'\n      );\n    },\n  },\n  'Close-readyState-Closed.any.js': {\n    replace: removeUseCapture,\n  },\n  'Close-readyState-Closing.any.js': {\n    replace: removeUseCapture,\n  },\n  'Close-reason-unpaired-surrogates.any.js': {\n    replace: removeUseCapture,\n  },\n  'Close-server-initiated-close.any.js': {\n    replace: removeUseCapture,\n  },\n  'Close-undefined.any.js': {\n    replace: removeUseCapture,\n  },\n  'Create-asciiSep-protocol-string.any.js': {},\n  'Create-blocked-port.any.js': {\n    comment: 'Port blocking works differently in workerd',\n    disabledTests: true,\n  },\n  'Create-extensions-empty.any.js': {\n    comment: 'workerd WebSocket.extensions is null instead of empty string',\n    expectedFailures: [\n      \"Create WebSocket - wsocket.extensions should be set to '' after connection is established - Connection should be closed\",\n    ],\n    replace: removeUseCapture,\n  },\n  'Create-http-urls.any.js': {\n    comment: 'workerd requires ws/wss scheme, not http/https',\n    expectedFailures: ['WebSocket: ensure both HTTP schemes are supported'],\n  },\n  'Create-invalid-urls.any.js': {},\n  'Create-non-absolute-url.any.js': {\n    comment: 'workerd throws SyntaxError for non-absolute URLs',\n    expectedFailures: [\n      'Create WebSocket - Pass a non absolute URL: test',\n      'Create WebSocket - Pass a non absolute URL: ?',\n      'Create WebSocket - Pass a non absolute URL: null',\n      'Create WebSocket - Pass a non absolute URL: 123',\n    ],\n  },\n  'Create-nonAscii-protocol-string.any.js': {},\n  'Create-on-worker-shutdown.any.js': {\n    comment: 'Worker shutdown behavior is different in workerd',\n    disabledTests: true,\n  },\n  'Create-protocol-with-space.any.js': {},\n  'Create-protocols-repeated-case-insensitive.any.js': {\n    comment:\n      'workerd does not throw for case-insensitive duplicate protocols (allows \"Echo\" and \"echo\")',\n    expectedFailures: [\n      'Create WebSocket - Pass a valid URL and an array of protocol strings with repeated values but different case - SYNTAX_ERR is thrown',\n    ],\n  },\n  'Create-protocols-repeated.any.js': {},\n  'Create-url-with-space.any.js': {},\n  'Create-valid-url-array-protocols.any.js': {\n    replace: removeUseCapture,\n  },\n  'Create-valid-url-binaryType-blob.any.js': {\n    replace: removeUseCapture,\n  },\n  'Create-valid-url-protocol-empty.any.js': {\n    replace: removeUseCapture,\n  },\n  'Create-valid-url-protocol-setCorrectly.any.js': {\n    replace: removeUseCapture,\n  },\n  'Create-valid-url-protocol-string.any.js': {\n    replace: removeUseCapture,\n  },\n  'Create-valid-url-protocol.any.js': {\n    replace: removeUseCapture,\n  },\n  'Create-valid-url.any.js': {\n    replace: removeUseCapture,\n  },\n  'Send-0byte-data.any.js': {\n    comment:\n      'workerd returns 0 instead of undefined for empty message data; test hangs',\n    disabledTests: true,\n  },\n  'Send-65K-data.any.js': {\n    comment:\n      'workerd returns byte count instead of undefined for bufferedAmount; test hangs',\n    disabledTests: true,\n  },\n  'Send-before-open.any.js': {\n    comment:\n      'workerd throws TypeError instead of InvalidStateError DOMException for send() before open',\n    expectedFailures: [\n      'Send data on a WebSocket before connection is opened - INVALID_STATE_ERR is returned',\n    ],\n  },\n  // The following Send tests all check bufferedAmount which returns byte count in workerd\n  // instead of undefined as expected by the spec. They also hang after failures.\n  'Send-binary-65K-arraybuffer.any.js': {\n    comment:\n      'bufferedAmount returns byte count instead of undefined; test hangs',\n    disabledTests: true,\n  },\n  'Send-binary-arraybuffer.any.js': {\n    comment:\n      'bufferedAmount returns byte count instead of undefined; test hangs',\n    disabledTests: true,\n  },\n  'Send-binary-arraybufferview-float16.any.js': {\n    comment:\n      'bufferedAmount returns byte count instead of undefined; test hangs',\n    disabledTests: true,\n  },\n  'Send-binary-arraybufferview-float32.any.js': {\n    comment:\n      'bufferedAmount returns byte count instead of undefined; test hangs',\n    disabledTests: true,\n  },\n  'Send-binary-arraybufferview-float64.any.js': {\n    comment:\n      'bufferedAmount returns byte count instead of undefined; test hangs',\n    disabledTests: true,\n  },\n  'Send-binary-arraybufferview-int16-offset.any.js': {\n    comment:\n      'bufferedAmount returns byte count instead of undefined; test hangs',\n    disabledTests: true,\n  },\n  'Send-binary-arraybufferview-int32.any.js': {\n    comment:\n      'bufferedAmount returns byte count instead of undefined; test hangs',\n    disabledTests: true,\n  },\n  'Send-binary-arraybufferview-int8.any.js': {\n    comment:\n      'bufferedAmount returns byte count instead of undefined; test hangs',\n    disabledTests: true,\n  },\n  'Send-binary-arraybufferview-uint16-offset-length.any.js': {\n    comment:\n      'bufferedAmount returns byte count instead of undefined; test hangs',\n    disabledTests: true,\n  },\n  'Send-binary-arraybufferview-uint32-offset.any.js': {\n    comment:\n      'bufferedAmount returns byte count instead of undefined; test hangs',\n    disabledTests: true,\n  },\n  'Send-binary-arraybufferview-uint8-offset-length.any.js': {\n    comment:\n      'bufferedAmount returns byte count instead of undefined; test hangs',\n    disabledTests: true,\n  },\n  'Send-binary-arraybufferview-uint8-offset.any.js': {\n    comment:\n      'bufferedAmount returns byte count instead of undefined; test hangs',\n    disabledTests: true,\n  },\n  'Send-binary-blob.any.js': {\n    comment:\n      'bufferedAmount returns byte count instead of undefined; test hangs',\n    disabledTests: true,\n  },\n  'Send-data.any.js': {\n    comment:\n      'bufferedAmount returns byte count instead of undefined; test hangs',\n    disabledTests: true,\n  },\n  'Send-data.worker.js': {\n    comment:\n      'bufferedAmount returns byte count instead of undefined; test hangs',\n    disabledTests: true,\n  },\n  'Send-null.any.js': {\n    comment:\n      'bufferedAmount returns byte count instead of undefined; test hangs',\n    disabledTests: true,\n  },\n  'Send-paired-surrogates.any.js': {\n    comment:\n      'bufferedAmount returns byte count instead of undefined; test hangs',\n    disabledTests: true,\n  },\n  'Send-unicode-data.any.js': {\n    comment:\n      'bufferedAmount returns byte count instead of undefined; test hangs',\n    disabledTests: true,\n  },\n  'Send-unpaired-surrogates.any.js': {\n    comment:\n      'bufferedAmount returns byte count instead of undefined; test hangs',\n    disabledTests: true,\n  },\n  'back-forward-cache-with-closed-websocket-connection.window.js': {\n    comment: 'Back/forward cache tests are browser-specific',\n    omittedTests: true,\n  },\n  'back-forward-cache-with-open-websocket-connection-and-close-it-in-pagehide.window.js':\n    {\n      comment: 'Back/forward cache tests are browser-specific',\n      omittedTests: true,\n    },\n  'back-forward-cache-with-open-websocket-connection.window.js': {\n    comment: 'Back/forward cache tests are browser-specific',\n    omittedTests: true,\n  },\n  'basic-auth.any.js': {\n    comment: 'Basic authentication for WebSockets is not supported',\n    disabledTests: true,\n  },\n  'binaryType-wrong-value.any.js': {\n    replace: removeUseCapture,\n  },\n  'bufferedAmount-unchanged-by-sync-xhr.any.js': {\n    comment: 'Synchronous XHR is not supported in Workers',\n    omittedTests: true,\n  },\n  'close-invalid.any.js': {\n    replace: removeUseCapture,\n  },\n  'constants.sub.js': {},\n  'constructor.any.js': {},\n  'cookies/support/websocket-cookies-helper.sub.js': {\n    comment: 'Cookie support helper, not an actual test',\n    omittedTests: true,\n  },\n  'eventhandlers.any.js': {\n    comment: 'TreatNonCallableAsNull behavior differs from spec',\n    expectedFailures: [\n      'Event handler for open should have [TreatNonCallableAsNull]',\n      'Event handler for error should have [TreatNonCallableAsNull]',\n      'Event handler for close should have [TreatNonCallableAsNull]',\n      'Event handler for message should have [TreatNonCallableAsNull]',\n    ],\n  },\n  'idlharness.any.js': {\n    comment:\n      'Some interface/attribute tests still fail due to event handler and inherited property checks',\n    expectedFailures: [\n      'WebSocket interface: existence and properties of interface object',\n      'WebSocket interface: attribute bufferedAmount',\n      'WebSocket interface: attribute onopen',\n      'WebSocket interface: attribute onerror',\n      'WebSocket interface: attribute onclose',\n      'WebSocket interface: attribute onmessage',\n      'WebSocket interface: new WebSocket(\"ws://invalid\") must inherit property \"bufferedAmount\" with the proper type',\n      'WebSocket interface: new WebSocket(\"ws://invalid\") must inherit property \"onopen\" with the proper type',\n      'WebSocket interface: new WebSocket(\"ws://invalid\") must inherit property \"onerror\" with the proper type',\n      'WebSocket interface: new WebSocket(\"ws://invalid\") must inherit property \"onclose\" with the proper type',\n      'WebSocket interface: new WebSocket(\"ws://invalid\") must inherit property \"onmessage\" with the proper type',\n      'CloseEvent interface: existence and properties of interface object',\n      'CloseEvent interface: attribute wasClean',\n      'CloseEvent interface: attribute code',\n      'CloseEvent interface: attribute reason',\n      'CloseEvent interface: new CloseEvent(\"close\") must inherit property \"wasClean\" with the proper type',\n      'CloseEvent interface: new CloseEvent(\"close\") must inherit property \"code\" with the proper type',\n      'CloseEvent interface: new CloseEvent(\"close\") must inherit property \"reason\" with the proper type',\n    ],\n  },\n  'interfaces/WebSocket/close/close-connecting-async.any.js': {\n    comment:\n      'readyState is CONNECTING (1) instead of CLOSING (2) after close()',\n    expectedFailures: [\n      'close event should be fired asynchronously when WebSocket is connecting',\n    ],\n  },\n  'mixed-content.https.any.js': {\n    comment: 'Mixed content checks are browser-specific',\n    omittedTests: true,\n  },\n  'opening-handshake/003-sets-origin.worker.js': {\n    comment: 'importScripts is not available in Workers',\n    omittedTests: true,\n  },\n  'referrer.any.js': {\n    comment: 'Referrer behavior is different in workers',\n    disabledTests: true,\n  },\n  'remove-own-iframe-during-onerror.window.js': {\n    comment: 'iframe tests are browser-specific',\n    omittedTests: true,\n  },\n  'send-many-64K-messages-with-backpressure.any.js': {\n    replace: removeUseCapture,\n  },\n} satisfies TestRunnerConfig;\n"
  },
  {
    "path": "tools/BUILD.bazel",
    "content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_library\", \"js_test\")\nload(\"@aspect_rules_ts//ts:defs.bzl\", \"ts_config\")\nload(\"@bazel_skylib//rules:native_binary.bzl\", \"native_binary\")\n\njs_library(\n    name = \"base-eslint\",\n    srcs = [\n        \"base.eslint.config.mjs\",\n        \"custom-eslint-rules.mjs\",\n    ],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//:node_modules/@eslint/js\",\n        \"//:node_modules/@types/node\",\n        \"//:node_modules/eslint\",\n        \"//:node_modules/typescript\",\n        \"//:node_modules/typescript-eslint\",\n    ],\n)\n\njs_test(\n    name = \"custom-eslint-rules-test\",\n    size = \"small\",\n    data = [\n        \":base-eslint\",\n    ],\n    entry_point = \"custom-eslint-rules.test.mjs\",\n    tags = [\"js-test\"],\n)\n\nts_config(\n    name = \"base-tsconfig\",\n    src = \"base.tsconfig.json\",\n    visibility = [\"//visibility:public\"],\n)\n\nnative_binary(\n    name = \"wasm-tools\",\n    src = select(\n        {\n            \"@bazel_tools//src/conditions:linux_x86_64\": \"@wasm_tools_linux_x64//:wasm-tools\",\n            \"@bazel_tools//src/conditions:linux_aarch64\": \"@wasm_tools_linux_arm64//:wasm-tools\",\n            \"@bazel_tools//src/conditions:darwin_arm64\": \"@wasm_tools_macos_arm64//:wasm-tools\",\n            \"@bazel_tools//src/conditions:darwin_x86_64\": \"@wasm_tools_macos_x64//:wasm-tools\",\n            \"@bazel_tools//src/conditions:windows_x64\": \"@wasm_tools_windows_x64//:wasm-tools.exe\",\n        },\n    ),\n    out = \"wasm-tools\",\n    visibility = [\"//visibility:public\"],\n)\n\nnative_binary(\n    name = \"clang-tidy\",\n    src = select(\n        {\n            \"@bazel_tools//src/conditions:linux_x86_64\": \"@clang_tidy_linux_amd64//file:downloaded\",\n            \"@bazel_tools//src/conditions:linux_aarch64\": \"@clang_tidy_linux_arm64//file:downloaded\",\n            \"@bazel_tools//src/conditions:darwin_arm64\": \"@clang_tidy_darwin_arm64//file:downloaded\",\n            \"@bazel_tools//src/conditions:windows_x64\": \"@clang_tidy_windows_amd64//file:downloaded\",\n        },\n    ),\n    out = \"clang_tidy\",\n    tags = [\"manual\"],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "tools/base.eslint.config.mjs",
    "content": "import { defineConfig } from \"eslint/config\";\nimport eslint from '@eslint/js';\nimport tseslint from 'typescript-eslint';\nimport customRules from './custom-eslint-rules.mjs';\n\n/**\n * @returns {import('eslint/config').Config}\n */\nexport function baseConfig() {\n  return defineConfig([\n    eslint.configs.recommended,\n    ...tseslint.configs.strictTypeChecked,\n    {\n      plugins: {\n        workerd: customRules,\n      },\n      rules: {\n        'workerd/require-copyright-header': 'error',\n      },\n    },\n    {\n      languageOptions: {\n        parserOptions: {\n          ecmaVersion: 'latest',\n          sourceType: 'module',\n          projectService: true,\n          tsconfigRootDir: import.meta.dirname,\n          jsDocParsingMode: 'all',\n        },\n      },\n      rules: {\n        '@typescript-eslint/explicit-function-return-type': 'error',\n        '@typescript-eslint/explicit-member-accessibility': [\n          'error',\n          { accessibility: 'no-public' },\n        ],\n        '@typescript-eslint/explicit-module-boundary-types': 'off',\n        '@typescript-eslint/no-require-imports': 'error',\n        '@typescript-eslint/prefer-enum-initializers': 'error',\n        '@typescript-eslint/restrict-template-expressions': 'off',\n        '@typescript-eslint/no-non-null-assertion': 'error',\n        '@typescript-eslint/no-extraneous-class': 'off',\n        '@typescript-eslint/unified-signatures': 'off',\n        '@typescript-eslint/no-unused-vars': [\n          'error',\n          {\n            args: 'all',\n            argsIgnorePattern: '^_',\n            caughtErrors: 'all',\n            caughtErrorsIgnorePattern: '^_',\n            destructuredArrayIgnorePattern: '^_',\n            varsIgnorePattern: '^_',\n            ignoreRestSiblings: true,\n          },\n        ],\n        'no-restricted-syntax': [\n          'error',\n          {\n            selector: \"MethodDefinition[accessibility='private']\",\n            message:\n              \"Use private field syntax (#) instead of 'private' keyword for methods\",\n          },\n          {\n            selector:\n              \"PropertyDefinition[accessibility='private']:not([computed=true])\",\n            message:\n              \"Use private field syntax (#) instead of 'private' keyword for simple properties\",\n          },\n          {\n            selector: \"TSParameterProperty[accessibility='private']\",\n            message:\n              \"Use private field syntax (#) instead of 'private' keyword for constructor parameters\",\n          },\n        ],\n      },\n    },\n    {\n      files: ['**/*.js', '**/*.mjs', '**/*.cjs'],\n      ...tseslint.configs.disableTypeChecked,\n    },\n    {\n      files: ['**/*.js', '**/*.mjs', '**/*.cjs'],\n      rules: {\n        '@typescript-eslint/explicit-function-return-type': 'off',\n      },\n    },\n  ]);\n}\n"
  },
  {
    "path": "tools/base.tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"ESNext\", \"dom\", \"Webworker.Iterable\"],\n    \"alwaysStrict\": true,\n    \"strict\": true,\n    \"strictNullChecks\": true,\n    \"allowJs\": true,\n    \"allowUnreachableCode\": false,\n    \"allowUnusedLabels\": false,\n    \"exactOptionalPropertyTypes\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noImplicitOverride\": true,\n    \"noImplicitReturns\": true,\n    \"noPropertyAccessFromIndexSignature\": false,\n    \"noUncheckedIndexedAccess\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"esModuleInterop\": true,\n    \"moduleResolution\": \"node\",\n    \"erasableSyntaxOnly\": true,\n    \"skipLibCheck\": true,\n    \"moduleDetection\": \"force\",\n    \"isolatedModules\": true,\n    \"verbatimModuleSyntax\": true,\n    \"types\": []\n  },\n  \"exclude\": [\"node_modules\", \"**/*.mjs\"]\n}\n"
  },
  {
    "path": "tools/cross/format.json",
    "content": "[\n  {\n    \"directory\": \"src\",\n    \"globs\": [\"*.c++\", \"*.h\"],\n    \"formatter\": \"clang-format\"\n  },\n  {\n    \"directory\": \"src\",\n    \"globs\": [\"*.js\", \"*.ts\", \"*.cjs\", \"*.ejs\", \"*.mjs\", \"*.json\"],\n    \"formatter\": \"prettier\"\n  },\n  {\n    \"directory\": \"fuzzilli\",\n    \"globs\": [\"*.js\"],\n    \"formatter\": \"prettier\"\n  },\n  {\n    \"directory\": \".\",\n    \"globs\": [\"*.py\"],\n    \"formatter\": \"ruff\"\n  },\n  {\n    \"directory\": \".\",\n    \"globs\": [\"*.bzl\", \"BUILD\", \"BUILD.*\", \"*.bazel\"],\n    \"formatter\": \"buildifier\"\n  },\n  {\n    \"directory\": \"src\",\n    \"globs\": [\"*.rs\"],\n    \"formatter\": \"rustfmt\"\n  }\n]\n"
  },
  {
    "path": "tools/cross/format.py",
    "content": "#!/usr/bin/env python3\n\nimport json\nimport logging\nimport os\nimport subprocess\nfrom argparse import ArgumentParser, Namespace\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\nfrom dataclasses import dataclass\nfrom pathlib import Path\nfrom sys import exit\nfrom typing import Callable, Optional\n\n# This file is symlinked into the internal repo as tools/format.py, so the root may be two levels up\n# or three.\nROOT = Path(__file__).parents[1]\nif not (ROOT / \".git\").exists():\n    ROOT = ROOT.parent\nBAZEL_BIN = ROOT / \"bazel-bin\"\n\n\ndef parse_args() -> Namespace:\n    parser = ArgumentParser()\n    parser.add_argument(\n        \"--check\",\n        help=\"only check for files requiring formatting; don't actually format them\",\n        action=\"store_true\",\n        default=False,\n    )\n    subparsers = parser.add_subparsers(dest=\"subcommand\")\n    git_parser = subparsers.add_parser(\n        \"git\", help=\"Apply format to changes tracked by git\"\n    )\n    git_parser.add_argument(\n        \"--source\",\n        help=(\n            \"consider files modified in the specified commit-ish; \"\n            \"if not specified, defaults to all changes in the working directory\"\n        ),\n        type=str,\n        required=False,\n        default=None,\n    )\n    git_parser.add_argument(\n        \"--target\",\n        help=\"consider files modified since the specified commit-ish; defaults to HEAD\",\n        type=str,\n        required=False,\n        default=\"HEAD\",\n    )\n    git_parser.add_argument(\n        \"--staged\",\n        help=\"consider files with staged modifications only\",\n        action=\"store_true\",\n        default=False,\n    )\n    options = parser.parse_args()\n    if (\n        options.subcommand == \"git\"\n        and options.staged\n        and (options.source is not None or options.target != \"HEAD\")\n    ):\n        logging.error(\n            \"--staged cannot be used with --source or --target; \"\n            \"use --staged with --source=HEAD\"\n        )\n        exit(1)\n    return options\n\n\ndef filter_files_by_globs(\n    files: list[Path], dir_path: Path, globs: tuple[str, ...], excludes: tuple[str, ...]\n) -> list[Path]:\n    return [\n        file\n        for file in files\n        if file.is_relative_to(dir_path)\n        and matches_any_glob(globs, file)\n        and not relative_to_any(excludes, file)\n    ]\n\n\ndef relative_to_any(excludes: tuple[str, ...], file: Path) -> bool:\n    return any(file.is_relative_to(exclude) for exclude in excludes)\n\n\ndef matches_any_glob(globs: tuple[str, ...], file: Path) -> bool:\n    return any(file.match(glob) for glob in globs)\n\n\ndef _ensure_bazel_tool(tool_name: str, build_target: str | None = None) -> Path:\n    \"\"\"Ensure a bazel-built formatter tool exists and return its path.\"\"\"\n    tool_suffix = Path(\"build\") / \"deps\" / \"formatters\" / tool_name\n    internal_tool_path = BAZEL_BIN / \"external\" / \"+dep_workerd+workerd\" / tool_suffix\n    workerd_tool_path = BAZEL_BIN / tool_suffix\n\n    if internal_tool_path.exists():\n        return internal_tool_path\n    if workerd_tool_path.exists():\n        return workerd_tool_path\n\n    # Tool not cached; build it once.\n    if build_target is None:\n        build_target = f\"@workerd//build/deps/formatters:{tool_name}@rule\"\n    download_result = subprocess.run([\"bazel\", \"build\", build_target])\n    if download_result.returncode != 0:\n        raise RuntimeError(f\"Failed to download {tool_name}\")\n\n    if internal_tool_path.exists():\n        return internal_tool_path\n    return workerd_tool_path\n\n\ndef run_bazel_tool(\n    tool_name: str, args: list[str], build_target: str | None = None\n) -> subprocess.CompletedProcess:\n    tool_path = _ensure_bazel_tool(tool_name, build_target)\n    return subprocess.run([tool_path, *args], cwd=ROOT)\n\n\ndef _run_parallel(\n    run_fn: Callable, files: list[Path], cmd: list, max_workers: int = 16\n) -> bool:\n    \"\"\"Split files across parallel invocations of run_fn(cmd + chunk).\"\"\"\n    n_workers = min(os.cpu_count() or 1, len(files), max_workers)\n    if n_workers <= 1:\n        return run_fn(cmd + files).returncode == 0\n\n    chunks = [files[i::n_workers] for i in range(n_workers)]\n    with ThreadPoolExecutor(max_workers=n_workers) as pool:\n        results = list(pool.map(lambda chunk: run_fn(cmd + chunk), chunks))\n    return all(r.returncode == 0 for r in results)\n\n\ndef clang_format(files: list[Path], check: bool = False) -> bool:\n    cmd = [\"--dry-run\", \"--Werror\"] if check else [\"-i\"]\n    tool = _ensure_bazel_tool(\"clang-format\")\n    return _run_parallel(\n        lambda args: subprocess.run([tool, *args], cwd=ROOT), files, cmd\n    )\n\n\ndef prettier(files: list[Path], check: bool = False) -> bool:\n    PRETTIER = BAZEL_BIN / \"node_modules/prettier/bin/prettier.cjs\"\n\n    if not PRETTIER.exists():\n        subprocess.run([\"bazel\", \"build\", \"//:node_modules/prettier\"])\n    cmd = [PRETTIER, \"--log-level=warn\", \"--check\" if check else \"--write\"]\n    return _run_parallel(\n        lambda args: subprocess.run(args, cwd=ROOT), files, cmd, max_workers=8\n    )\n\n\ndef buildifier(files: list[Path], check: bool = False) -> bool:\n    cmd = [\"--mode=check\" if check else \"--mode=fix\"]\n    result = run_bazel_tool(\"buildifier\", cmd + files)\n    return result.returncode == 0\n\n\ndef rustfmt(files: list[Path], check: bool = False) -> bool:\n    if not files:\n        return True\n    cmd = [\"--edition\", \"2024\"]\n    if check:\n        cmd.append(\"--check\")\n    result = run_bazel_tool(\"rustfmt\", cmd + files)\n    return result.returncode == 0\n\n\ndef ruff(files: list[Path], check: bool = False) -> bool:\n    if not files:\n        return True\n\n    cmd = [\"check\"]\n    if not check:\n        cmd.append(\"--fix\")\n    result1 = run_bazel_tool(\"ruff\", cmd + files)\n\n    # format\n    cmd = [\"format\"]\n    if check:\n        cmd.append(\"--diff\")\n\n    result2 = run_bazel_tool(\"ruff\", cmd + files)\n    return result1.returncode == 0 and result2.returncode == 0\n\n\ndef git_get_modified_files(\n    target: str, source: Optional[str], staged: bool\n) -> list[Path]:\n    if staged:\n        files_in_diff = subprocess.check_output(\n            [\"git\", \"diff\", \"--diff-filter=d\", \"--name-only\", \"--cached\"],\n            encoding=\"utf-8\",\n            cwd=ROOT,\n        ).splitlines()\n        return [Path(file) for file in files_in_diff]\n    else:\n        merge_base = subprocess.check_output(\n            [\"git\", \"merge-base\", target, source or \"HEAD\"],\n            encoding=\"utf-8\",\n            cwd=ROOT,\n        ).strip()\n        files_in_diff = subprocess.check_output(\n            [\"git\", \"diff\", \"--diff-filter=d\", \"--name-only\", merge_base]\n            + ([source] if source else []),\n            encoding=\"utf-8\",\n            cwd=ROOT,\n        ).splitlines()\n        return [Path(file) for file in files_in_diff]\n\n\ndef git_get_all_files() -> list[Path]:\n    files = subprocess.check_output(\n        [\"git\", \"ls-files\", \"--cached\", \"--others\", \"--exclude-standard\"],\n        encoding=\"utf-8\",\n        cwd=ROOT,\n    ).splitlines()\n    return [Path(file) for file in files]\n\n\n@dataclass\nclass FormatConfig:\n    directory: str\n    globs: tuple[str, ...]\n    formatter: str\n    excludes: tuple[str, ...] = ()\n\n\nFORMATTERS = {\n    \"clang-format\": clang_format,\n    \"prettier\": prettier,\n    \"ruff\": ruff,\n    \"buildifier\": buildifier,\n    \"rustfmt\": rustfmt,\n}\n\n\ndef format(config: FormatConfig, files: list[Path], check: bool) -> tuple[bool, str]:\n    matching_files = filter_files_by_globs(\n        files, Path(config.directory), config.globs, config.excludes\n    )\n\n    if not matching_files:\n        return (\n            True,\n            f\"No matching files for {config.directory} ({', '.join(config.globs)})\",\n        )\n\n    result = FORMATTERS[config.formatter](matching_files, check)\n    message = (\n        f\"{len(matching_files)} files in {config.directory} ({', '.join(config.globs)})\"\n    )\n    return (\n        result,\n        f\"{'Checked' if check else 'Formatted'} {message}\",\n    )\n\n\ndef main() -> None:\n    options = parse_args()\n\n    if options.subcommand == \"git\":\n        files = git_get_modified_files(options.target, options.source, options.staged)\n    else:\n        files = git_get_all_files()\n\n    with (Path(__file__).parent / \"format.json\").open() as fp:\n        configs = json.load(fp, object_hook=lambda o: FormatConfig(**o))\n\n    # Ensure all required tools are downloaded before running formatters in\n    # parallel.  Otherwise a slow `bazel build` for a missing tool races with\n    # the other formatters and its output gets interleaved.\n    needed_formatters = set()\n    for config in configs:\n        matched = filter_files_by_globs(\n            files, Path(config.directory), config.globs, config.excludes\n        )\n        if matched:\n            needed_formatters.add(config.formatter)\n    for name in needed_formatters:\n        if name in (\"clang-format\", \"buildifier\", \"ruff\", \"rustfmt\"):\n            _ensure_bazel_tool(name)\n\n    all_ok = True\n\n    with ThreadPoolExecutor() as executor:\n        future_to_config = {\n            executor.submit(format, config, files, options.check): config\n            for config in configs\n        }\n        for future in as_completed(future_to_config):\n            config = future_to_config[future]\n            try:\n                result, message = future.result()\n                all_ok &= result\n                logging.info(message)\n            except Exception:\n                logging.exception(\n                    f\"Formatter for {config.directory} generated an exception\"\n                )\n                all_ok = False\n\n    if not all_ok:\n        logging.error(\n            \"Code has linting issues. Fix with python ./tools/cross/format.py\"\n        )\n        exit(1)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "tools/cross/internal_build.py",
    "content": "import argparse\nimport sys\nimport time\n\nimport requests\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser()\n\n    parser.add_argument(\"pr_id\", help=\"Pull Request ID\")\n    parser.add_argument(\"merge_sha\", help=\"Merge Commit SHA\")\n    parser.add_argument(\"head_sha\", help=\"HEAD Commit SHA\")\n    parser.add_argument(\"run_attempt\", help=\"# of Run Attempt\")\n    parser.add_argument(\"branch_name\", help=\"PR's Branch Name\")\n    parser.add_argument(\"URL\", help=\"URL to submit build task\")\n    parser.add_argument(\"client_id\", help=\"CF Access client id\")\n    parser.add_argument(\"secret\", help=\"CF Access client secret\")\n\n    return parser.parse_args()\n\n\nif __name__ == \"__main__\":\n    args = parse_args()\n\n    # Submit build job\n    headers = {\n        \"CF-Access-Client-Id\": args.client_id,\n        \"CF-Access-Client-Secret\": args.secret,\n    }\n\n    payload = {\n        \"pr_id\": args.pr_id,\n        \"merge_commit_sha\": args.merge_sha,\n        \"head_commit_sha\": args.head_sha,\n        \"run_attempt\": args.run_attempt,\n        \"branch_name\": args.branch_name,\n    }\n\n    workflow_id = \"\"\n    try:\n        resp = requests.post(args.URL, headers=headers, json=payload)\n        resp.raise_for_status()\n\n        workflow_id = resp.json()[\"workflow_id\"]\n    except Exception as err:\n        print(f\"Unexpected error {err=}, {type(err)=}, exiting.\")\n        sys.exit(1)\n\n    print(\"Internal build submitted.\")\n\n    time.sleep(30)\n\n    # Poll build status\n    failed_requests = 0\n    FAILED_REQUEST_LIMIT = 10\n\n    while failed_requests < FAILED_REQUEST_LIMIT:\n        try:\n            resp = requests.get(args.URL, headers=headers, params={\"id\": workflow_id})\n            resp.raise_for_status()\n\n            status = resp.json()[\"status\"]\n            if status == \"errored\":\n                print(\"Internal build failed.\")\n                print(resp.json()[\"error\"][\"message\"])\n                print(\n                    \"If you are a Cloudflare employee, please check your internal\"\n                    \" branch and refer this doc for further details:\"\n                    \" https://cflare.co/workerd-internal-build\"\n                )\n                print(\n                    \"If you are an external contributor, please ask the auto-assigned\"\n                    \" reviewers to check the internal build failure.\"\n                )\n                sys.exit(1)\n            elif status == \"complete\":\n                break\n\n            print(\"Waiting for build to finish..\")\n\n        except Exception as err:\n            print(f\"Unexpected error {err=}, {type(err)=}\")\n            failed_requests = failed_requests + 1\n        time.sleep(30)\n\n    if failed_requests == FAILED_REQUEST_LIMIT:\n        print(f\"{failed_requests=} == {FAILED_REQUEST_LIMIT=}, exiting.\")\n        sys.exit(1)\n\n    print(\"Internal build succeeded.\")\n"
  },
  {
    "path": "tools/cross/wpt_logs.py",
    "content": "#!/usr/bin/env python3\nimport argparse\nimport json\nimport re\nimport subprocess\nimport sys\nimport time\nfrom dataclasses import dataclass\nfrom pathlib import Path\nfrom typing import Any, Optional\nfrom xml.etree import ElementTree\n\nEXCLUDED_TESTS = {\n    # The WPT compliant implementation is in urlpattern-standard@\n    \"urlpattern@\"\n}\n\n\n@dataclass\nclass Options:\n    config: bool\n    report: bool\n    stats: bool\n\n\ndef main() -> None:\n    cmd = argparse.ArgumentParser()\n    cmd.add_argument(\"--update-config\", action=\"store_true\")\n    cmd.add_argument(\"--write-report\", type=Path)\n    cmd.add_argument(\"--print-stats\", action=\"store_true\")\n    cmd.add_argument(\"logs_dir\", type=Path)\n\n    args = cmd.parse_args()\n    options = Options(args.update_config, bool(args.write_report), args.print_stats)\n    logs = parse_logs(args.logs_dir, options)\n\n    if args.print_stats:\n        print(stats_table(logs))\n\n    if args.write_report:\n        # TODO(soon): Elapsed time will not be accurate. Figure out if it matters to us, and how to fix.\n        now = int(time.time())\n        time_interval = TimeInterval(now, now)\n\n        report = wpt_report(logs, time_interval)\n        if report:\n            with args.write_report.open(\"w\") as fp:\n                json.dump(report, fp, indent=2)\n\n    # TODO(soon): Implement the config option to update test configs\n\n\n@dataclass\nclass TimeInterval:\n    start: int = 0\n    end: int = 0\n\n\n@dataclass\nclass Log:\n    # TypeScript test config used to configure WPT tests\n    config: Optional[str] = None\n\n    # JSON report in WPT's format\n    report: Optional[dict[str, Any]] = None\n\n    # Summary stats in JSON\n    stats: Optional[list[Any]] = None\n\n\ndef parse_logs(logs_dir: Path, options: Options) -> list[Log]:\n    return [\n        parse_log(log_file, options)\n        for log_file in sorted(logs_dir.glob(\"**/src/wpt/**/*@/test.xml\"))\n    ]\n\n\ndef parse_log(log_file: Path, options: Options) -> Log:\n    log = Log()\n\n    if log_file.parent.name in EXCLUDED_TESTS:\n        return log\n\n    parser = ElementTree.XMLParser(encoding=\"utf-8\")\n    root = ElementTree.parse(log_file, parser=parser)\n    text = root.find(\"testsuite\").find(\"system-out\").text\n\n    start_log = re.search(r\"\\[ TEST \\] .*:zzz_results\\n\", text)\n    if not start_log:\n        return log\n\n    end_log = re.search(r\"^.*\\[ (PASS|FAIL) \\].*:zzz_results\", text, re.MULTILINE)\n\n    if not end_log:\n        return log\n\n    results_log = text[start_log.end(0) : end_log.start(0)]\n    items = results_log.split(\"***\")\n\n    if options.config:\n        # Removes a message for the user that we don't need\n        items.pop(0)\n        items.pop(0)\n        log.config = items.pop(0).strip()\n\n    if options.report:\n        log.report = json.loads(items.pop(0))\n\n    if options.stats:\n        log.stats = json.loads(items.pop(0))\n\n    return log\n\n\ndef stats_table(logs: list[Log]) -> str:\n    cells = []\n\n    for log in logs:\n        if not log.stats:\n            continue\n\n        cells.append(\n            [f\"<td>{log.stats[0]}</td>\"]\n            + [f\"<td align='right'>{value}</td>\" for value in log.stats[1:]]\n        )\n\n    if not cells:\n        return \"\"\n\n    cells_html = \"\\n\".join(f\"<tr>{' '.join(row)}</tr>\" for row in cells)\n\n    return f\"\"\"## WPT statistics\n\n<table>\n    <tr>\n        <th>Module</th>\n        <th colspan=\"4\">Coverage</th>\n        <th colspan=\"5\">Pass</th>\n    </tr>\n    <tr>\n        <th></th>\n        <th>OK</th>\n        <th>Disabled</th>\n        <th>Total</th>\n        <th>% OK</th>\n        <th>Pass</th>\n        <th>Fail</th>\n        <th>Disabled</th>\n        <th>Total</th>\n        <th>% Pass</th>\n    </tr>\n    {cells_html}\n</table>\n\nThis table shows how workerd performs for each listed [Web Platform Tests](https://github.com/web-platform-tests/wpt) module.\"\"\"\n\n\ndef cmd_output(cmd: list[str]) -> str:\n    return subprocess.run(cmd, capture_output=True, check=True).stdout.decode().strip()\n\n\ndef get_os() -> str:\n    \"\"\"\n    Return one of three expected values\n    <https://github.com/web-platform-tests/wpt/blob/1c6ff12/tools/wptrunner/wptrunner/tests/test_update.py#L953-L958>\n    \"\"\"\n\n    if sys.platform == \"darwin\":\n        return \"mac\"\n    elif sys.platform == \"linux\":\n        return \"linux\"\n    elif sys.platform == \"win32\":\n        return \"win\"\n    else:\n        raise ValueError(\"Unsupported os type\")\n\n\ndef wpt_report(logs: list[Log], time_interval: TimeInterval) -> dict[str, Any]:\n    all_results = [\n        result for log in logs if log.report for result in log.report[\"results\"]\n    ]\n\n    if not all_results:\n        return {}\n\n    return {\n        \"time_start\": time_interval.start,\n        \"time_end\": time_interval.end,\n        \"run_info\": {\n            \"product\": \"workerd\",\n            \"browser_channel\": \"experimental\",\n            \"browser_version\": cmd_output([\"git\", \"describe\", \"--tags\", \"--always\"]),\n            \"revision\": cmd_output([\"git\", \"rev-parse\", \"HEAD\"]),\n            \"os\": get_os(),\n        },\n        \"results\": all_results,\n    }\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "tools/custom-eslint-rules.mjs",
    "content": "/**\n * ESLint rule to require a Cloudflare copyright header at the top of every file.\n *\n * Requires files to start with:\n *   // Copyright (c) <year or year-range> Cloudflare, Inc.\n *   // Licensed under the Apache 2.0 license found in the LICENSE file or at:\n *   //     https://opensource.org/licenses/Apache-2.0\n */\nexport const requireCopyrightHeader = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description:\n        'Require a Cloudflare copyright header at the top of every file',\n    },\n    messages: {\n      missingHeader:\n        'File is missing the Cloudflare copyright header. Add the following at the top of the file:\\n// Copyright (c) {{year}} Cloudflare, Inc.\\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\\n//     https://opensource.org/licenses/Apache-2.0',\n    },\n    fixable: 'code',\n    schema: [],\n  },\n  create(context) {\n    return {\n      Program(node) {\n        const sourceText = context.sourceCode.getText();\n        const headerPattern =\n          /^\\/\\/ Copyright \\(c\\) \\d{4}(-\\d{4})? Cloudflare, Inc\\.\\n\\/\\/ Licensed under the Apache 2\\.0 license found in the LICENSE file or at:\\n\\/\\/     https:\\/\\/opensource\\.org\\/licenses\\/Apache-2\\.0/;\n\n        if (!headerPattern.test(sourceText)) {\n          const year = new Date().getFullYear();\n          context.report({\n            node,\n            messageId: 'missingHeader',\n            data: { year: String(year) },\n            fix(fixer) {\n              const header =\n                `// Copyright (c) ${year} Cloudflare, Inc.\\n` +\n                '// Licensed under the Apache 2.0 license found in the LICENSE file or at:\\n' +\n                '//     https://opensource.org/licenses/Apache-2.0\\n';\n              return fixer.insertTextBefore(node, header);\n            },\n          });\n        }\n      },\n    };\n  },\n};\n\n/**\n * ESLint rule to disallow re-exporting a namespace import as default.\n *\n * Disallows patterns like:\n *   import * as _default from 'some-module';\n *   export default _default;\n *\n * Instead, use:\n *   import { var1, var2 } from 'some-module';\n *   export * from 'some-module';\n *   export default { var1, var2 };\n *\n * @see https://github.com/cloudflare/workerd/issues/5844\n */\nexport const noExportDefaultOfImportStar = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description:\n        'Disallow exporting a namespace import as the default export',\n    },\n    messages: {\n      noExportDefaultOfImportStar:\n        \"Do not re-export a namespace import as default. Use 'import { var1, var2 } from \\\"{{source}}\\\"', 'export * from \\\"{{source}}\\\"', and 'export default { var1, var2 }' instead.\",\n    },\n    schema: [],\n  },\n  create(context) {\n    const namespaceImports = new Map();\n\n    return {\n      ImportDeclaration(node) {\n        // Look for: import * as X from '...'\n        for (const specifier of node.specifiers) {\n          if (specifier.type === 'ImportNamespaceSpecifier') {\n            namespaceImports.set(specifier.local.name, node.source.value);\n          }\n        }\n      },\n\n      ExportDefaultDeclaration(node) {\n        // Look for: export default X (where X is an identifier)\n        if (node.declaration && node.declaration.type === 'Identifier') {\n          const exportedName = node.declaration.name;\n          const source = namespaceImports.get(exportedName);\n\n          if (source !== undefined) {\n            context.report({\n              node,\n              messageId: 'noExportDefaultOfImportStar',\n              data: { source },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n\nexport default {\n  rules: {\n    'require-copyright-header': requireCopyrightHeader,\n    'no-export-default-of-import-star': noExportDefaultOfImportStar,\n  },\n};\n"
  },
  {
    "path": "tools/custom-eslint-rules.test.mjs",
    "content": "/**\n * Tests for custom ESLint rules.\n */\nimport { RuleTester } from 'eslint';\nimport { describe, it } from 'node:test';\nimport { noExportDefaultOfImportStar } from './custom-eslint-rules.mjs';\n\n// Create a new RuleTester instance\nconst ruleTester = new RuleTester({\n  languageOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\ndescribe('no-export-default-of-import-star', () => {\n  it('should validate the rule', () => {\n    ruleTester.run('no-export-default-of-import-star', noExportDefaultOfImportStar, {\n      valid: [\n        // Valid: Regular named import\n        {\n          code: `\n            import { foo } from 'some-module';\n            export default foo;\n          `,\n        },\n        // Valid: Inline default export\n        {\n          code: `\n            export default { foo: 'bar' };\n          `,\n        },\n        // Valid: Export namespace import as named export\n        {\n          code: `\n            import * as foo from 'some-module';\n            export { foo };\n          `,\n        },\n        // Valid: Namespace import used for something other than export default\n        {\n          code: `\n            import * as foo from 'some-module';\n            const bar = foo;\n          `,\n        },\n        // Valid: Export default with non-identifier\n        {\n          code: `\n            import * as foo from 'some-module';\n            export default foo.bar;\n          `,\n        },\n        // Valid: Re-export all\n        {\n          code: `\n            export * from 'some-module';\n          `,\n        },\n        // Valid: Named imports with object literal default export\n        {\n          code: `\n            import { var1, var2 } from 'some-module';\n            export * from 'some-module';\n            export default { var1, var2 };\n          `,\n        },\n      ],\n\n      invalid: [\n        // Invalid: Export namespace import as default\n        {\n          code: `\n            import * as foo from 'some-module';\n            export default foo;\n          `,\n          errors: [\n            {\n              messageId: 'noExportDefaultOfImportStar',\n              data: { source: 'some-module' },\n            },\n          ],\n        },\n        // Invalid: Common pattern with _default\n        {\n          code: `\n            import * as _default from 'some-module';\n            export default _default;\n          `,\n          errors: [\n            {\n              messageId: 'noExportDefaultOfImportStar',\n              data: { source: 'some-module' },\n            },\n          ],\n        },\n        // Invalid: Namespace import with scoped package\n        {\n          code: `\n            import * as myModule from '@scope/package';\n            export default myModule;\n          `,\n          errors: [\n            {\n              messageId: 'noExportDefaultOfImportStar',\n              data: { source: '@scope/package' },\n            },\n          ],\n        },\n        // Invalid: Multiple imports, only one violates rule\n        {\n          code: `\n            import { named } from 'module1';\n            import * as ns from 'module2';\n            export default ns;\n          `,\n          errors: [\n            {\n              messageId: 'noExportDefaultOfImportStar',\n              data: { source: 'module2' },\n            },\n          ],\n        },\n      ],\n    });\n  });\n});\n"
  },
  {
    "path": "tools/intellij/.managed.bazelproject",
    "content": "# For more documentation, please visit https://ij.bazel.build/docs/project-views.html\n\ndirectories:\n  .\n  -.devcontainer\n  -.vscode\n  -docs\n  -githooks\n  -samples\n\nderive_targets_from_directories: true\n\nadditional_languages:\n  typescript\n  python\n  javascript\n\ntest_sources:\n  src/workerd/tests/*\n  src/workerd/api/node/tests/*\n  src/workerd/api/tests/*\n  src/workerd/api/*-test.js\n  src/workerd/api/*-test.wd-test\n  src/workerd/api/*-test.c++\n"
  },
  {
    "path": "tools/unix/apply-big-move.sh",
    "content": "#! /bin/bash\n#\n# This script applies the most recent \"big change\".\n#\n# This script is designed so that it can help you rebase an in-flight PR across this change. Once\n# the big move has been merged, you may simply run this script inside your branch, and it will\n# take care of updating your change in order to avoid any merge conflicts.\n#\n# How it does this:\n# 1. Rebases your change to the commit just before the Big Move.\n# 2. Applies the same logical changes that the Big Move did, but now applies this to the changes\n#    on your branch too.\n# 3. Rewrites git history so that your commits are now based on the commit immediately after the\n#    Big Move.\n#\n# To use the script, just run it in your branch. It'll take care of everything, and you'll end up\n# with the changes in your branch all based on the commit after The Big Move.\n\nset -euo pipefail\n\nAFTER_MOVE_TAG=after-big-move-1\nBEFORE_MOVE_TAG=before-big-move-1\nNEXT_BIG_MOVE=before-big-move-2\nORIG_BRANCH=$(git branch --show-current)\n\nfail() {\n  echo -e '\\e[0;1;31mFAILED:\\e[0m '\"$@\" >&2\n  exit 1\n}\n\ncheck_big_move_dependencies() {\n  source \"$(dirname -- $BASH_SOURCE)/find-python3.sh\"\n  PYTHON_PATH=$(get_python3)\n  if [[ -z \"$PYTHON_PATH\" ]]; then\n    fail \"Cannot find python3. Run install python3 first.\"\n  fi\n}\n\napply_big_move() {\n  echo \"Applying format...\"\n\n  source \"$(dirname -- $BASH_SOURCE)/find-python3.sh\"\n  PYTHON_PATH=$(get_python3)\n  $PYTHON_PATH \"$(dirname -- $BASH_SOURCE)/../cross/format.py\"\n\n  echo \"Changes applied successfully.\"\n}\n\nrebase_and_rerun() {\n  # Rebase on top of tag $1 and then run the script again.\n\n  BASE=$1\n\n  echo -e '\\e[0;1;34mRebasing onto tag '\"$BASE\"'...\\e[0m'\n  git rebase $BASE || \\\n      fail \"Rebase had conflicts. Please resolve them and run the script again.\"\n  git submodule update\n\n  # Make sure we're using the correct version of the script by starting it over.\n  exec ./apply-big-move.sh\n}\n\ncheck_for_next_move() {\n  # Check if there's a new \"Big Move\" in the future and, if so, prompt the user to apply it too.\n\n  if git describe $NEXT_BIG_MOVE >/dev/null 2>&1; then\n    while true; do\n      echo -en '\\e[0;1;33mIt looks like there is another big move after this one. Should we apply it now? [y/n]\\e[0m '\n      read -n1 CHOICE\n      echo\n      case \"$CHOICE\" in\n        y | Y )\n          rebase_and_rerun $NEXT_BIG_MOVE\n          ;;\n        n | N )\n          echo \"OK. When you are ready, please run the script again to apply the next move.\"\n          exit 0\n          ;;\n      esac\n      echo \"??? Please press either y or n.\"\n    done\n  fi\n}\n\nmain() {\n  if [ \"x$(git status --untracked-files=no --porcelain)\" != \"x\" ]; then\n    fail \"You have uncommitted changes. Please commit or discard them first.\"\n  fi\n\n  check_big_move_dependencies\n\n  # Use --apply to just apply the Big Move changes to the current tree without committing or\n  # rewriting any history. This is intended to be used to create the Big Move commit in the first\n  # place.\n  if [ \"${1:-}\" = \"--apply\" ]; then\n    apply_big_move\n    exit 0\n  fi\n\n  # Make sure our tags are up-to-date so we can find the relevant Big Move tags.\n  echo \"Fetching tags...\"\n  git fetch origin --tags --force\n\n  # Check that the Big Move is actually ready. (This script will be committed to the repo before\n  # the Big Move itself is, so this checks if someone is running the script too early.)\n  if ! git describe $BEFORE_MOVE_TAG >/dev/null 2>&1; then\n    fail \"The Big Move hasn't happened yet (tags not found).\"\n  fi\n\n  # Check if we already applied The Big Move in this branch, as indicated by $AFTER_MOVE_TAG being\n  # in our history.\n  if git merge-base --is-ancestor $AFTER_MOVE_TAG HEAD; then\n    # This branch already includes The Big Move, but maybe there is a future Big Move and the\n    # user was actually intending to apply that?\n    if git describe $NEXT_BIG_MOVE >/dev/null 2>&1; then\n      # Indeed there is, let's skip forward to that.\n      rebase_and_rerun $NEXT_BIG_MOVE\n    fi\n\n    echo \"The Big Move has already been applied to this branch.\"\n    exit 0\n  fi\n\n  # Check if $BEFORE_MOVE_TAG is in this branch's history. If not, we need to rebase first.\n  if ! git merge-base --is-ancestor $BEFORE_MOVE_TAG HEAD; then\n    # Branch is not yet based on $BEFORE_MOVE_TAG, so rebase onto it.\n    rebase_and_rerun $BEFORE_MOVE_TAG\n    fail \"(can't get here -- rebase_and_rerun should not return)\"\n  fi\n\n  # Get the list of commits that we need to rebase over The Big Move.\n  COMMITS=($(git log --reverse --format='%H' $BEFORE_MOVE_TAG..HEAD))\n\n  # Checkout $AFTER_MOVE_TAG in order to start building on it.\n  git checkout -q $AFTER_MOVE_TAG\n  git submodule update\n\n  # Apply each commit.\n  for COMMIT in ${COMMITS[@]}; do\n    git log -1 --format='%CblueRewriting commit %h:%Creset %s' $COMMIT\n\n    # Update the working tree to match the code from the source commit.\n    git checkout $COMMIT .\n    git submodule update\n\n    # Apply the Big Move on top of that.\n    apply_big_move\n\n    # Commit to edgeworker (including updating the workerd submodule), reusing the old commit\n    # message. It's possible that the diff is now empty, in which case we skip this commit, much\n    # like `git rebase` does by default.\n    if git commit -a --dry-run > /dev/null 2>&1; then\n      git commit -aC $COMMIT\n    fi\n  done\n\n  # Record the final commit.\n  FINAL_COMMIT=$(git log -1 --format=%H)\n\n  # Check out the original branch, and reset it to the final commit.\n  git checkout -q \"$ORIG_BRANCH\"\n  git reset --hard \"$FINAL_COMMIT\"\n\n  # Success!\n  echo -e '\\e[0;1;32mSUCCESS:\\e[0m Applied Big Move.'\n\n  # Check if there's another move upcoming that we should apply next.\n  check_for_next_move\n\n  exit 0\n}\n\n# Ensure that if the file changes while bash is reading it, bash won't go off the rails. Normally,\n# bash reads one line from the file at a time and then executes it before reading the next.\n# Wrapping the whole script body in a function ensures that bash buffers it in memory. Placing\n# `exit 0` on the same line ensures that bash won't attempt to read anything more after the\n# function returns.\n#\n# This ensures that if further changes are made to this file before the Big Move actually happens,\n# then the rebase command that rebases to the commit before the Big Move won't confuse bash.\nmain \"$@\"; exit 0\n"
  },
  {
    "path": "tools/unix/clangd-check.sh",
    "content": "#!/bin/bash\n\n# A script to scans workerd sources with clangd to look for issues\n# that make affect language services.\n\noutput_dir=$(mktemp -d)\ntop_of_tree=$(git rev-parse --show-toplevel)\ndeclare -a job_pids\n\n# Guess at number of CPUs (could be probed, but portable requires work).\njobs_max=8\n\n# Check bash version to see if `wait -n` is supported. This allows waiting on first of any\n# process specified as an argument to wait to exit rather than waiting for all the processes\n# specified to exit. (ie batch vs stream).\nbash_major=$(echo $BASH_VERSION | sed -e 's/\\..*/0000/')\nbash_minor=$(echo $BASH_VERSION | sed -e 's/^[^.]*\\.//' -e 's/\\..*//')\nbash_linear=$(($bash_major + $bash_minor))\nif [ ${bash_linear} -ge 50001 ]; then\n  wait_args=\"-n\"\nfi\n\ncd \"${top_of_tree}\"\nfor i in $(find . -name '*.h' -o -name '*.c++'); do\n    if [ ${#job_pids[*]} -eq ${jobs_max} ]; then\n        # macOS inparticular is stuck on an older version of bash and does not support `wait -n`\n        # here so child processes will run as batch waiting for all to complete there.\n        wait ${wait_args} \"${job_pids[@]}\"\n        for job_pid in \"${job_pids[@]}\"; do\n          if ! kill -0 ${job_pid} 2>/dev/null ; then\n            unset job_pids[${job_pid}]\n          fi\n        done\n    fi\n    j=${output_dir}/$(echo $i | sed  -e 's@^\\./@@' -e s@/@_@g)\n    echo -n Scanning $i =\\> $j\n    clangd --check=$i 1>$j 2>&1 &\n    echo \" [$!]\"\n    job_pids[$!]=$!\ndone\n\necho \"Checking for broken includes.\"\ngrep \"not found\" ${output_dir}/*\n\ncat <<EOF\n\nYou may also want to inspect the output files for other issues.\n\nPlease update compile_flags.txt to resolve any issues found.\nEOF\n"
  },
  {
    "path": "tools/unix/create-external.sh",
    "content": "#!/bin/sh\n\n# Create a symlink to Bazel's external/ directory. This simplifies the\n# paths provided to clangd in compile_flags.txt for language server\n# use.\n\noutput_path=$(bazel info output_path)\nworkspace=$(bazel info workspace)\nexternal=\"${workspace}/external\"\nln -sfF \"${output_path}/../../../external\" \"${external}\"\n\n# Temporary warning that compile_commands.json exists and will\n# interfere with the intended clangd setup.\ncompile_commands=\"${workspace}/compile_commands.json\"\nif [ -f \"compile_commands.json\" ] ; then\n  cat<<COMPILE_COMMANDS_WARNING\nWARNING: This workspace has a compile_commands.json file, but workerd\nhas moved to using compile_flags.txt for clangd instead. To improve\ncode completion and navigation in your editor, consider running:\n\n  rm \"${compile_commands}\"\n\nCOMPILE_COMMANDS_WARNING\nfi\n"
  },
  {
    "path": "tools/unix/create-internal-pr.sh",
    "content": "#!/usr/bin/env bash\n# This tool will open a PR to update workerd in Cloudflare's internal repository\n# to match the current branch.\n\nset -euo pipefail\n\nif [ -z \"${EDGEWORKER_HOME+x}\" ]; then\n\tEDGEWORKER_HOME=\"$(git rev-parse --show-toplevel)/../edgeworker\"\nfi\n\nBRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)\nSYNC_BRANCH_NAME=sync/$BRANCH_NAME\n\ncd $EDGEWORKER_HOME\ngit submodule update --init --recursive\ngit fetch origin master\n\nif git rev-parse --verify $SYNC_BRANCH_NAME; then\n\tBRANCH_EXISTS=1\nelse\n\tBRANCH_EXISTS=0\nfi\n\n\nif [ $BRANCH_EXISTS -eq 1 ]; then\n\tgit checkout $SYNC_BRANCH_NAME\nelse\n\tgit checkout -b $SYNC_BRANCH_NAME origin/master\nfi\n\ngit -C deps/workerd fetch origin $BRANCH_NAME\ngit -C deps/workerd checkout origin/$BRANCH_NAME\ngit add deps/workerd\n\nif [ $BRANCH_EXISTS -eq 1 ]; then\n\tgit commit --no-verify --amend\n\tgit push --no-verify --force origin HEAD\nelse\n\tgit commit --no-verify -m \"Sync with $BRANCH_NAME\"\n\tGIT_OUT=\"$(git push --no-verify origin HEAD 2>&1)\"\n\techo \"$GIT_OUT\"\n\topen $(echo \"$GIT_OUT\" | sed -n '/remote: *http/ s/remote: *\\(.*\\)/\\1/p')\nfi\n"
  },
  {
    "path": "tools/unix/find-python3.sh",
    "content": "\nget_python3() {\n  # Search for python3\n  python=$(which python3 2>/dev/null)\n\n  # If not found, search for python\n  if [ -z \"$python\" ]; then\n    python=$(which python 2>/dev/null)\n    if [ -n \"$python\" ]; then\n      local python_version=$($python -V 2>&1 | head -n 1 | cut -d ' ' -f 2- | cut -d '.' -f1)\n      if [ \"$python_version\" != \"3\" ]; then\n        unset python\n      fi\n    fi\n  fi\n\n  if [ -z \"$python\" ]; then\n    return\n  fi\n  echo \"$python\"\n}\n"
  },
  {
    "path": "tools/unix/fix-copyrights.sh",
    "content": "#!/bin/sh\n\n# Copyright (c) ${date_range} Cloudflare, Inc.\n# Licensed under the Apache 2.0 license found in the LICENSE file or at:\n#     https://opensource.org/licenses/Apache-2.0\n\nSCRIPT_DIR=$(realpath $(dirname \"$0\"))/../..\nCURRENT_YEAR=$(date +\"%Y\")\n\nfunction copyright() {\n  local source_file=\"$1\"\n  local date_range=\"$2\"\n  local wip_file=$(mktemp -t $(basename \"${source_file}\"))\n  cat <<EOF > \"${wip_file}\"\n// Copyright (c) ${date_range} Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nEOF\n  cat \"${source_file}\" >> \"${wip_file}\"\n  mv \"${wip_file}\" \"${source_file}\"\n}\n\ncd \"${SCRIPT_DIR}\"\nfor source_file in $(find . -name '*.c++' -o -name '*.h' -o -name '*.ts' -o -name '*.js') ; do\n  if grep -Liq copyright \"${source_file}\" ; then\n    continue\n  fi\n  if [[ \"${source_file}\" = *\"node\"* ]]; then\n    continue\n  fi\n  commit=$(git log --oneline --format=\"%as %h %s\" --reverse ${source_file} | head -1)\n  commit_date=$(echo ${commit}| sed -e 's/ .*//')\n  commit_year=$(echo ${commit_date}| sed -e 's/-.*//')\n  if [ \"${commit_date}\" = \"2022-09-13\" -o \"${commit_date}\" = \"2022-09-19\" ]; then\n    copyright \"${source_file}\" \"2017-2023\"\n  elif [ \"${commit_year}\" = \"${CURRENT_YEAR}\" ]; then\n    copyright \"${source_file}\" \"${CURRENT_YEAR}\"\n  else\n    copyright \"${source_file}\" \"${commit_year}-${CURRENT_YEAR}\"\n  fi\ndone\n"
  },
  {
    "path": "tools/unix/new-test.sh",
    "content": "#!/usr/bin/env bash\n\nset -euo pipefail\n\nBAZEL_TARGET=\"$1\"\nREPO_ROOT=\"$(git rev-parse --show-toplevel)\"\n\n# Sloppily convert the Bazel target to a FS path\nTEST_PATH=\"$REPO_ROOT/$(echo $BAZEL_TARGET | sed s_:_/_g | sed s_//__)\"\nTEST_BASENAME=$(basename $TEST_PATH)\nTEST_DIRNAME=$(dirname $TEST_PATH)\n\ncat << EOF > $TEST_PATH.wd-test\nusing Workerd = import \"/workerd/workerd.capnp\";\n\nconst unitTests :Workerd.Config = (\n  services = [\n    ( name = \"$TEST_BASENAME\",\n      worker = (\n        modules = [\n          (name = \"worker\", esModule = embed \"$TEST_BASENAME.js\")\n        ],\n       compatibilityFlags = [\"nodejs_compat_v2\"],\n      )\n    ),\n  ],\n);\nEOF\n\ngit add $TEST_PATH.wd-test\n\ncat << EOF > $TEST_PATH.js\nexport const test = {\n  test() {\n    // ...\n  },\n};\nEOF\n\ngit add $TEST_PATH.js\n\ncat << EOF >> $TEST_DIRNAME/BUILD.bazel\n\nwd_test(\n    src = \"$TEST_BASENAME.wd-test\",\n    args = [\"--experimental\"],\n    data = [\"$TEST_BASENAME.js\"],\n)\nEOF\n\ngit add $TEST_DIRNAME/BUILD.bazel\n"
  },
  {
    "path": "tools/unix/upload-python-bundle.sh",
    "content": "#!/usr/bin/env bash\n\nBRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)\n\ngh workflow run 105611493 \\\n    --field pyodide=$1 \\\n    --field pyodideRevision=$2 \\\n    --field backport=$3 \\\n    -r $BRANCH_NAME\n"
  },
  {
    "path": "tools/unix/workspace-status.sh",
    "content": "#!/usr/bin/env bash\n\n# This script is hooked into bazel via the --workspace_status_command in .bazelrc. It\n# runs during each build.\n\nscript_dir=$(dirname \"$0\")\ninside_work_tree=$(git rev-parse --is-inside-work-tree 2>/dev/null)\n\n# Check for issues that may affect developer workspace\n\n# Logs an issue\n# $1 = description\nfunction issue_detected() {\n  let issue_count=issue_count+1\n  cat >&2 <<EOF\nWorkspace issue detected #${issue_count}\n\n$1\n\nEOF\n}\n\n# Check githooks\nGITHOOKS_MSG=$(cat<<EOF\n  It looks like the git config option core.hooksPath is not set. The workerd\n  repository uses hooks stored in githooks/ to check for common mistakes\n  and prevent production breakages.\n\n  To set up the hooks, please run:\n\n    git config core.hooksPath githooks\nEOF\n)\nif [ \"${inside_work_tree}\" = \"true\" ] && [ \"$EUID\" -ne 0 ] && [ -z \"`git config core.hooksPath`\" ]; then\n  issue_detected \"${GITHOOKS_MSG}\"\nfi\n"
  },
  {
    "path": "tools/update_node_version.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nScript to update Node.js version in node-version.h based on GitHub releases.\n\nThis script:\n1. Fetches Node.js version tags from GitHub\n2. Finds the maximum even major version\n3. Looks for the latest version in the (max-2) series\n4. Updates the node-version.h file if needed\n\"\"\"\n\nimport json\nimport re\nimport sys\nimport urllib.request\nfrom pathlib import Path\n\n\ndef fetch_nodejs_tags():\n    \"\"\"Fetch Node.js version tags from GitHub API.\"\"\"\n    url = \"https://api.github.com/repos/nodejs/node/tags\"\n    headers = {\n        \"User-Agent\": \"workerd-node-version-updater\",\n        \"Accept\": \"application/vnd.github.v3+json\",\n    }\n\n    all_tags = []\n    page = 1\n\n    while True:\n        req = urllib.request.Request(f\"{url}?page={page}&per_page=100\", headers=headers)\n        try:\n            with urllib.request.urlopen(req) as response:\n                data = json.loads(response.read().decode())\n                if not data:\n                    break\n                all_tags.extend(data)\n                page += 1\n        except Exception as e:\n            print(f\"Error fetching tags: {e}\", file=sys.stderr)\n            sys.exit(1)\n\n    return all_tags\n\n\ndef parse_version(version_str):\n    \"\"\"Parse a version string like 'v22.17.0' into (major, minor, patch).\"\"\"\n    match = re.match(r\"^v(\\d+)\\.(\\d+)\\.(\\d+)$\", version_str)\n    if match:\n        return tuple(map(int, match.groups()))\n    return None\n\n\ndef find_target_version(tags):\n    \"\"\"Find the target Node.js version based on the algorithm.\"\"\"\n    # Extract valid versions\n    versions = []\n    for tag in tags:\n        version = parse_version(tag[\"name\"])\n        if version:\n            versions.append((version, tag[\"name\"]))\n\n    if not versions:\n        print(\"No valid versions found\", file=sys.stderr)\n        sys.exit(1)\n\n    # Sort versions\n    versions.sort(reverse=True)\n\n    # Find maximum even major version\n    max_even_major = None\n    for (major, _minor, _patch), _ in versions:\n        if major % 2 == 0:\n            max_even_major = major\n            break\n\n    if max_even_major is None:\n        print(\"No even major version found\", file=sys.stderr)\n        sys.exit(1)\n\n    # Calculate target major version (max_even - 2)\n    target_major = max_even_major - 2\n\n    # Find latest version in target major series\n    for (major, _minor, _patch), tag_name in versions:\n        if major == target_major:\n            return tag_name[1:]  # Remove 'v' prefix\n\n    print(f\"No version found for major version {target_major}\", file=sys.stderr)\n    sys.exit(1)\n\n\ndef update_header_file(file_path, new_version):\n    \"\"\"Update the node-version.h file with the new version.\"\"\"\n    path = Path(file_path)\n    content = path.read_text()\n\n    # Replace the version string\n    # Match: static constexpr kj::StringPtr nodeVersion = \"X.Y.Z\"_kj;\n    pattern = r'static constexpr kj::StringPtr nodeVersion = \"[^\"]+\"_kj;'\n    replacement = f'static constexpr kj::StringPtr nodeVersion = \"{new_version}\"_kj;'\n    new_content = re.sub(pattern, replacement, content)\n\n    if new_content != content:\n        path.write_text(new_content)\n        return True\n    return False\n\n\ndef main():\n    if len(sys.argv) != 2:\n        print(\"Usage: update_node_version.py <output_file>\", file=sys.stderr)\n        sys.exit(1)\n\n    output_file = sys.argv[1]\n\n    print(\"Fetching Node.js versions from GitHub...\")\n    tags = fetch_nodejs_tags()\n\n    print(\"Finding target version...\")\n    target_version = find_target_version(tags)\n    print(f\"Target version: {target_version}\")\n\n    # Update the header file\n    if update_header_file(output_file, target_version):\n        print(f\"Updated {output_file} with version {target_version}\")\n    else:\n        print(f\"No changes needed - already at version {target_version}\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "tools/update_opencode_version.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nScript to update the pinned opencode version in Bonk CI workflow files.\n\nThis script:\n1. Fetches the latest opencode-ai version from the npm registry\n2. Scans all workflow files in .github/workflows/ for opencode_version pins\n3. Updates them to the latest version\n\"\"\"\n\nimport json\nimport re\nimport sys\nimport urllib.request\nfrom pathlib import Path\n\nWORKFLOW_DIR = Path(__file__).resolve().parent.parent / \".github\" / \"workflows\"\n\n# Matches opencode_version: 'X.Y.Z' or opencode_version: \"X.Y.Z\"\nOPENCODE_VERSION_RE = re.compile(\n    r\"\"\"(opencode_version:\\s*)([\"'])([^\"']*)\\2\"\"\",\n)\n\n\ndef fetch_latest_version():\n    \"\"\"Fetch the latest opencode-ai version from the npm registry.\"\"\"\n    url = \"https://registry.npmjs.org/opencode-ai/latest\"\n    headers = {\n        \"User-Agent\": \"workerd-opencode-version-updater\",\n        \"Accept\": \"application/json\",\n    }\n\n    req = urllib.request.Request(url, headers=headers)\n    try:\n        with urllib.request.urlopen(req) as response:\n            data = json.loads(response.read().decode())\n            return data[\"version\"]\n    except Exception as e:\n        print(f\"Error fetching latest opencode-ai version: {e}\", file=sys.stderr)\n        sys.exit(1)\n\n\ndef update_workflow_file(path, new_version):\n    \"\"\"Update the opencode_version field in a workflow file.\n\n    Returns True if the file was changed.\n    \"\"\"\n    content = path.read_text()\n\n    # Replace the version while preserving the original quote style.\n    new_content = OPENCODE_VERSION_RE.sub(\n        rf\"\\g<1>\\g<2>{new_version}\\2\",\n        content,\n    )\n\n    if new_content != content:\n        path.write_text(new_content)\n        return True\n    return False\n\n\ndef find_workflow_files():\n    \"\"\"Find all workflow files that contain an opencode_version pin.\"\"\"\n    return [\n        path\n        for path in sorted(WORKFLOW_DIR.glob(\"*.yml\"))\n        if OPENCODE_VERSION_RE.search(path.read_text())\n    ]\n\n\ndef main():\n    print(\"Fetching latest opencode-ai version from npm...\")\n    latest = fetch_latest_version()\n    print(f\"Latest version: {latest}\")\n\n    workflow_files = find_workflow_files()\n    if not workflow_files:\n        print(\"No workflow files with opencode_version pins found\", file=sys.stderr)\n        sys.exit(1)\n\n    changed = False\n    for path in workflow_files:\n        relpath = path.relative_to(WORKFLOW_DIR.parent.parent)\n        if update_workflow_file(path, latest):\n            print(f\"Updated {relpath} to {latest}\")\n            changed = True\n        else:\n            print(f\"No changes needed in {relpath}\")\n\n    if not changed:\n        print(f\"Already at latest version ({latest})\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "tools/windows/bazel-env.bat",
    "content": "@echo off\necho.* Environment configured for workerd builds with bazel\necho.\n\nrem Set environment variables for bazel to use when invoked from\nrem the command-line.\nset \"BAZEL_LLVM=C:\\Program Files\\LLVM\"\nset BAZEL_SH=C:\\msys64\\usr\\bin\\bash.exe\nset \"BAZEL_VC=C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\"\nset BAZEL_WINSDK_FULL_VERSION=10.0.22000.0\n"
  },
  {
    "path": "tools/windows/create-external.bat",
    "content": "@ECHO off\nSETLOCAL EnableDelayedExpansion\n\n@REM cmd.exe script to ensure external/ directory is present for clangd.\n\nFOR /f %%i IN ('bazel info output_path') do SET \"output_path=%%i\"\nFOR /f %%i IN ('bazel info workspace') do SET \"workspace=%%i\"\nSET \"external=%output_path%\\..\\..\\..\\external\"\n\n@REM Delete the convenience junction external if it exists (it maybe stale if it does).\nrmdir  \"%workspace%\\external\" 2>NUL\n\n@REM Create the convenience junction external anew for easy access to sources and for\n@REM VSCode to be able to open files at points of error when compilation fails.\nMKLINK /J \"%workspace%\\external\" \"%external%\"\n\nSET \"compile_commands=%workspace%\\compile_commands.json\"\nIF EXIST \"%compile_commands%\" (\n  ECHO.WARNING: This workspace has a compile_commands.json file, but workerd\n  ECHO.has moved to using compile_flags.txt for clangd instead. To improve\n  ECHO.code completion and navigation in your editor, consider running:\n  ECHO.\n  ECHO.  DEL /q \"%compile_commands%\"\n  ECHO.\n)\n"
  },
  {
    "path": "tools/windows/install-deps.bat",
    "content": "@echo off\nsetlocal enabledelayedexpansion\n\nrem Check this script is being run from an elevated cmd binary.\nnet session > NUL 2>&1\nif %ERRORLEVEL% NEQ 0 (\n  echo This script requires administrator privileges.\n  exit /b 1\n)\n\ncls\n\ngoto :InstallDependencies\n\n:AddToUserPathInEnvironment\nrem Function to add an element to the users PATH as defined in HCU\\Environment.\nsetlocal\nset \"PATH_TO_ADD=%~1\"\nfor /F \"usebackq tokens=3 skip=2\" %%P IN (`reg query \"HKEY_CURRENT_USER\\Environment\" /v PATH`) do set \"USERPATH=%%P\"\nsetx PATH \"%PATH_TO_ADD%;!USERPATH!\"\necho Added %PATH_TO_ADD% to default PATH.\nendlocal\nexit /b 0\n\n:InstallDependencies\n\necho.Workerd dependency installation\necho.-------------------------------\necho.\necho.This script will configure your Windows machine for building workerd with\necho.bazel on Windows.\necho.\necho.Some of the steps will open windows for you to approve.\necho.\necho.* Step 1: Enable developer mode features.\necho.\nrem This is based on https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging\nreg add HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock /v AllowDevelopmentWithoutDevLicense /t REG_DWORD /d 1 /f\n\necho.\necho.* Step 2: Enable 8.3 name support on this machine.\nfsutil 8dot3name set 0\n\nrem Install Visual Studio Code Community edition with Desktop development with C++ package.\nrem ----------------------------------------------------------------------------------------\nrem\nrem The config in vsconfig.json is the config for \"Desktop development with C++\".\nrem\nrem The config is determined by first installing Visual Studio Community Edition on your\nrem devbox. Then run `c:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\setup.exe` and click\nrem `More -> Export Configuration Settings`, select an config file to write, then `Review Details`\nrem and select \"Desktop development with C++\".\necho.\necho.* Step 3: Install Visual Studio Code Community edition with Desktop development with C++ package.\nwinget install \"Microsoft.VisualStudio.2022.Community\" --override \"install --config %~dp0\\vsconfig.json\"\n\necho.\necho.* Step 4: Install Python 3.\nwinget install Python3 -v 3.12.1 --override \"/passive PrependPath=1\"\n@rem bazel requires a python3.exe binary, create a symlink for it.\nmklink \"%LOCALAPPDATA%\\Programs\\Python\\Python312\\python3.exe\" \"%LOCALAPPDATA%\\Programs\\Python\\Python312\\python.exe\"\n\necho.\necho.* Step 5: Install msys2.\nwinget install msys2.msys2\nsetx BAZEL_SH \"C:\\msys64\\usr\\bin\\bash.exe\"\n\necho.\necho.* Step 6 Install msys2 tools suggested for bazel, from https://bazel.build/install/windows.\n@rem Invoking pacman like this reports an error updating GNU info files, no idea how to fix.\nC:\\msys64\\usr\\bin\\bash -c \"/usr/bin/pacman -S --noconfirm zip unzip patch diffutils\"\ncall :AddToUserPathInEnvironment C:\\msys64\\usr\\bin\n\necho.\necho.* Step 7: Install LLVM compiler toolchain.\n@rem The MSVC build frequently breaks in some ways when the LLVM version is updated, we may have to\n@rem manually install a different version again soon.\n@rem winget install \"LLVM\" --version 19.1.7\n\necho.\necho.* Step 8: Install bazelisk as %LOCALAPPDATA%\\Programs\\bazelisk\\bazel.exe.\nwinget install bazelisk -l \"%LOCALAPPDATA%\\Programs\\bazelisk\" -r \"bazel.exe\"\ncall :AddToUserPathInEnvironment \"%LOCALAPPDATA%\\Programs\\bazelisk\"\n\necho.\necho.* Setup complete.\nset /p GO=\"Press enter to exit.\"\nexit 0\n"
  },
  {
    "path": "tools/windows/vsconfig.json",
    "content": "{\n  \"version\": \"1.0\",\n  \"components\": [\n    \"Microsoft.VisualStudio.Component.CoreEditor\",\n    \"Microsoft.VisualStudio.Workload.CoreEditor\",\n    \"Microsoft.VisualStudio.Component.TypeScript.TSServer\",\n    \"Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions\",\n    \"Microsoft.VisualStudio.Component.JavaScript.TypeScript\",\n    \"Microsoft.VisualStudio.Component.Roslyn.Compiler\",\n    \"Microsoft.Component.MSBuild\",\n    \"Microsoft.VisualStudio.Component.Roslyn.LanguageServices\",\n    \"Microsoft.VisualStudio.Component.TextTemplating\",\n    \"Microsoft.VisualStudio.Component.NuGet\",\n    \"Microsoft.VisualStudio.Component.Debugger.JustInTime\",\n    \"Component.Microsoft.VisualStudio.LiveShare.2022\",\n    \"Microsoft.VisualStudio.Component.IntelliCode\",\n    \"Microsoft.VisualStudio.Component.VC.CoreIde\",\n    \"Microsoft.VisualStudio.Component.VC.Tools.x86.x64\",\n    \"Microsoft.VisualStudio.Component.Graphics.Tools\",\n    \"Microsoft.VisualStudio.Component.VC.DiagnosticTools\",\n    \"Microsoft.VisualStudio.Component.Windows11SDK.22000\",\n    \"Microsoft.VisualStudio.Component.VC.ATL\",\n    \"Microsoft.VisualStudio.Component.SecurityIssueAnalysis\",\n    \"Microsoft.VisualStudio.Component.VC.Redist.14.Latest\",\n    \"Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core\",\n    \"Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions.CMake\",\n    \"Microsoft.VisualStudio.Component.VC.CMake.Project\",\n    \"Microsoft.VisualStudio.Component.VC.TestAdapterForBoostTest\",\n    \"Microsoft.VisualStudio.Component.VC.TestAdapterForGoogleTest\",\n    \"Microsoft.VisualStudio.Component.VC.ASAN\",\n    \"Microsoft.VisualStudio.Workload.NativeDesktop\"\n  ]\n}"
  },
  {
    "path": "tools/windows/workspace-status.cmd",
    "content": "@setlocal enabledelayedexpansion\n@echo off\n\n@rem find git.exe\nfor /F \"tokens=* USEBACKQ\" %%F in (`where git.exe`) do (\n  set \"GIT=%%F\"\n  break\n)\n\n@rem Git's bash is located at %GITINSTALLDIR%\\bin\\bash.exe\n@rem Git's git.exe is typically located at %GITINSTALLDIR%\\cmd\\git.exe or\n@rem %GITINSTALLDIR%\\mingw64\\bin\\git.exe.\nset \"_GITEXEDIR=%GIT:\\cmd\\git.exe=%\"\nset \"GITEXEDIR=%_GITEXEDIR:\\mingw64\\bin\\git.exe=%\"\nset \"BASH=%GITEXEDIR%\\bin\\bash.exe\"\n\n@rem Change to the directory of this script (tools/windows) to\n@rem run bash script in (tools/unix).\ncd \"%~dp0\"\n\"%BASH%\" -c ../unix/workspace-status.sh\n"
  },
  {
    "path": "types/AGENTS.md",
    "content": "# types/\n\n## OVERVIEW\n\nGenerates `@cloudflare/workers-types` `.d.ts` files from C++ RTTI (via `jsg/rtti.capnp`) + hand-written `defines/*.d.ts`. A workerd-hosted Worker extracts RTTI at runtime per compat-date; TypeScript transforms post-process into ambient and importable outputs. CI validates `generated-snapshot/` matches.\n\n## PIPELINE\n\n1. **RTTI extraction**: `src/worker/` runs inside workerd, imports binary RTTI via `workerd:rtti` + param names from `param-extractor.rs`\n2. **Generation**: `src/generator/` converts Cap'n Proto structures to TS AST nodes (walks types, members, methods recursively via `collectIncluded`)\n3. **Transforms** (ordered, in `src/index.ts`): iterators -> overrides/defines -> global scope extraction -> class-to-interface -> comments -> onmessage declaration; then second pass: import-resolve -> ambient. Importable variant runs separately.\n4. **Defines concatenation**: `defines/*.d.ts` appended after transforms (AI, D1, R2, RPC, Vectorize, etc.)\n5. **Build**: `scripts/build-types.ts` iterates compat-date entrypoints (oldest through experimental), type-checks output against TS libs\n\n## KEY DIRECTORIES\n\n| Directory             | Purpose                                                                          |\n| --------------------- | -------------------------------------------------------------------------------- |\n| `src/generator/`      | RTTI-to-TS-AST: `structure.ts` (classes/interfaces), `type.ts` (type mapping)    |\n| `src/transforms/`     | 11 post-processing TS transformer factories                                      |\n| `src/worker/`         | Workerd-hosted entry; imports RTTI binary + virtual modules                      |\n| `defines/`            | Hand-written `.d.ts` for APIs not expressible via C++ RTTI (~25 files)           |\n| `generated-snapshot/` | Checked-in `latest/` + `experimental/` output; CI diff-checked                   |\n| `scripts/`            | `build-types.ts` (full generation), `build-worker.ts` (worker bundle)            |\n| `test/`               | Vitest specs for generator, transforms, print; type-check tests in `test/types/` |\n"
  },
  {
    "path": "types/BUILD.bazel",
    "content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_binary\", \"js_run_binary\")\nload(\"//:build/wd_ts_project.bzl\", \"wd_ts_project\")\nload(\"//:build/wd_ts_test.bzl\", \"wd_ts_test\")\nload(\"//:build/wd_ts_type_test.bzl\", \"wd_ts_type_test\")\n\nwd_ts_project(\n    name = \"types_lib\",\n    srcs = glob(\n        [\n            \"src/**/*\",\n            \"scripts/*.ts\",\n        ],\n        exclude = [\"src/worker/**/*\"],\n    ),\n    composite = True,\n    eslintrc_json = \"eslint.config.mjs\",\n    tsconfig_json = \"//types:tsconfig.json\",\n    deps = [\n        \"//:node_modules/@types/node\",\n        \"//:node_modules/@workerd/jsg\",\n        \"//:node_modules/capnp-es\",\n        \"//:node_modules/esbuild\",\n        \"//:node_modules/prettier\",\n        \"//:node_modules/typescript\",\n    ],\n)\n\njs_binary(\n    name = \"build_types_bin\",\n    data = [\n        \":types_lib\",\n    ],\n    entry_point = \"scripts/build-types.js\",\n    tags = [\"manual\"],\n)\n\njs_run_binary(\n    name = \"types\",\n    srcs = [\n        \"scripts/config.capnp\",\n        \":types_worker\",\n        \"//:node_modules/prettier\",\n        \"//src/workerd/server:workerd\",\n    ],\n    out_dirs = [\"definitions\"],\n    silent_on_success = False,  # Always enable logging for debugging\n    # Exclude from wildcard builds (//...) - type generation is expensive and only needed\n    # for the check-snapshot job which explicitly builds //types.\n    tags = [\"manual\"],\n    tool = \":build_types_bin\",\n    visibility = [\"//visibility:public\"],\n)\n\njs_binary(\n    name = \"build_worker_bin\",\n    data = [\n        \":types_lib\",\n    ],\n    entry_point = \"scripts/build-worker.js\",\n    node_options = [\"--enable-source-maps\"],\n    tags = [\"manual\"],\n)\n\njs_run_binary(\n    name = \"types_worker\",\n    srcs = [\n        \"//:node_modules/@workerd/jsg\",\n        \"//:node_modules/capnp-es\",\n        \"//:node_modules/esbuild\",\n        \"//:node_modules/typescript\",\n        \"//src/workerd/tools:param_extractor\",\n    ] + glob(\n        [\n            \"src/**/*.ts\",\n            \"defines/**/*.ts\",\n        ],\n        exclude = [\"src/cli/**/*\"],\n    ),\n    outs = [\"dist/index.mjs\"],\n    # Only used by //types:types which is also manual\n    tags = [\"manual\"],\n    tool = \":build_worker_bin\",\n)\n\n[\n    wd_ts_test(\n        src = src,\n        composite = True,\n        node_options = [\"--enable-source-maps\"],\n        tsconfig_json = \"//types:tsconfig.json\",\n        deps = [\":types_lib\"],\n    )\n    for src in glob([\"test/**/*.spec.ts\"])\n]\n\n[\n    wd_ts_type_test(src = src)\n    for src in glob([\"test/types/**/*.ts\"])\n]\n"
  },
  {
    "path": "types/README.md",
    "content": "# Workers Types Generator\n\nThis directory contains scripts for automatically generating TypeScript types\nfrom [JSG RTTI](../src/workerd/jsg/rtti.h).\n\n## Generating Types\n\n```shell\n# Generates types to `../bazel-bin/types/api.d.ts`\n$ bazel build //types:types\n```\n\n## Developing Generator Scripts\n\nWhen developing generator scripts with an IDE, you'll need to install\ndependencies yourself for integrated type checking and completions to work.\n\n```shell\n# Generates JSG RTTI Cap’n Proto JavaScript/TypeScript files\n$ bazel build //src/workerd/jsg:rtti_capnp_js\n# Install dependencies (note pnpm is required by https://github.com/aspect-build/rules_js)\n$ pnpm install\n# Generates types to `../bazel-bin/types/api.d.ts`\n$ bazel build //types:types\n# Run tests\n$ bazel test //types:all\n```\n\n## Structure\n\n- `src/generator`: generating TypeScript AST nodes from JSG RTTI\n- `src/transforms`: post-processing TypeScript AST transforms\n- `src/index.ts`: main entrypoint\n- `src/{print,program}.ts`: helpers for printing nodes and creating programs\n- `defines`: additional TypeScript-only definitions that don't correspond to\n  `workerd` runtime APIs, appended to the end of outputs\n\n## Updates types\n\n```shell\njust generate-types\n```\n"
  },
  {
    "path": "types/defines/ai-search.d.ts",
    "content": "// AI Search V2 API Error Interfaces\nexport interface AiSearchInternalError extends Error {}\nexport interface AiSearchNotFoundError extends Error {}\nexport interface AiSearchNameNotSetError extends Error {}\n\n// AI Search V2 Request Types\nexport type AiSearchSearchRequest = {\n  messages: Array<{\n    role: 'system' | 'developer' | 'user' | 'assistant' | 'tool';\n    content: string | null;\n  }>;\n  ai_search_options?: {\n    retrieval?: {\n      retrieval_type?: 'vector' | 'keyword' | 'hybrid';\n      /** Match threshold (0-1, default 0.4) */\n      match_threshold?: number;\n      /** Maximum number of results (1-50, default 10) */\n      max_num_results?: number;\n      filters?: VectorizeVectorMetadataFilter;\n      /** Context expansion (0-3, default 0) */\n      context_expansion?: number;\n      [key: string]: unknown;\n    };\n    query_rewrite?: {\n      enabled?: boolean;\n      model?: string;\n      rewrite_prompt?: string;\n      [key: string]: unknown;\n    };\n    reranking?: {\n      /** Enable reranking (default false) */\n      enabled?: boolean;\n      model?: '@cf/baai/bge-reranker-base' | '';\n      /** Match threshold (0-1, default 0.4) */\n      match_threshold?: number;\n      [key: string]: unknown;\n    };\n    [key: string]: unknown;\n  };\n};\n\nexport type AiSearchChatCompletionsRequest = {\n  messages: Array<{\n    role: 'system' | 'developer' | 'user' | 'assistant' | 'tool';\n    content: string | null;\n  }>;\n  model?: string;\n  stream?: boolean;\n  ai_search_options?: {\n    retrieval?: {\n      retrieval_type?: 'vector' | 'keyword' | 'hybrid';\n      match_threshold?: number;\n      max_num_results?: number;\n      filters?: VectorizeVectorMetadataFilter;\n      context_expansion?: number;\n      [key: string]: unknown;\n    };\n    query_rewrite?: {\n      enabled?: boolean;\n      model?: string;\n      rewrite_prompt?: string;\n      [key: string]: unknown;\n    };\n    reranking?: {\n      enabled?: boolean;\n      model?: '@cf/baai/bge-reranker-base' | '';\n      match_threshold?: number;\n      [key: string]: unknown;\n    };\n    [key: string]: unknown;\n  };\n  [key: string]: unknown;\n};\n\n// AI Search V2 Response Types\nexport type AiSearchSearchResponse = {\n  search_query: string;\n  chunks: Array<{\n    id: string;\n    type: string;\n    /** Match score (0-1) */\n    score: number;\n    text: string;\n    item: {\n      timestamp?: number;\n      key: string;\n      metadata?: Record<string, unknown>;\n    };\n    scoring_details?: {\n      /** Keyword match score (0-1) */\n      keyword_score?: number;\n      /** Vector similarity score (0-1) */\n      vector_score?: number;\n    };\n  }>;\n};\n\nexport type AiSearchListResponse = Array<{\n  id: string;\n  internal_id?: string;\n  account_id?: string;\n  account_tag?: string;\n  /** Whether the instance is enabled (default true) */\n  enable?: boolean;\n  type?: 'r2' | 'web-crawler';\n  source?: string;\n  [key: string]: unknown;\n}>;\n\nexport type AiSearchConfig = {\n  /** Instance ID (1-32 chars, pattern: ^[a-z0-9_]+(?:-[a-z0-9_]+)*$) */\n  id: string;\n  type: 'r2' | 'web-crawler';\n  source: string;\n  source_params?: object;\n  /** Token ID (UUID format) */\n  token_id?: string;\n  ai_gateway_id?: string;\n  /** Enable query rewriting (default false) */\n  rewrite_query?: boolean;\n  /** Enable reranking (default false) */\n  reranking?: boolean;\n  embedding_model?: string;\n  ai_search_model?: string;\n};\n\nexport type AiSearchInstance = {\n  id: string;\n  enable?: boolean;\n  type?: 'r2' | 'web-crawler';\n  source?: string;\n  [key: string]: unknown;\n};\n\n// AI Search Instance Service - Instance-level operations\nexport declare abstract class AiSearchInstanceService {\n  /**\n   * Search the AI Search instance for relevant chunks.\n   * @param params Search request with messages and AI search options\n   * @returns Search response with matching chunks\n   */\n  search(params: AiSearchSearchRequest): Promise<AiSearchSearchResponse>;\n\n  /**\n   * Generate chat completions with AI Search context.\n   * @param params Chat completions request with optional streaming\n   * @returns Response object (if streaming) or chat completion result\n   */\n  chatCompletions(\n    params: AiSearchChatCompletionsRequest\n  ): Promise<Response | object>;\n\n  /**\n   * Delete this AI Search instance.\n   */\n  delete(): Promise<void>;\n}\n\n// AI Search Account Service - Account-level operations\nexport declare abstract class AiSearchAccountService {\n  /**\n   * List all AI Search instances in the account.\n   * @returns Array of AI Search instances\n   */\n  list(): Promise<AiSearchListResponse>;\n\n  /**\n   * Get an AI Search instance by ID.\n   * @param name Instance ID\n   * @returns Instance service for performing operations\n   */\n  get(name: string): AiSearchInstanceService;\n\n  /**\n   * Create a new AI Search instance.\n   * @param config Instance configuration\n   * @returns Instance service for performing operations\n   */\n  create(config: AiSearchConfig): Promise<AiSearchInstanceService>;\n}\n"
  },
  {
    "path": "types/defines/ai.d.ts",
    "content": "export type AiImageClassificationInput = {\n  image: number[];\n};\nexport type AiImageClassificationOutput = {\n  score?: number;\n  label?: string;\n}[];\nexport declare abstract class BaseAiImageClassification {\n  inputs: AiImageClassificationInput;\n  postProcessedOutputs: AiImageClassificationOutput;\n}\nexport type AiImageToTextInput = {\n  image: number[];\n  prompt?: string;\n  max_tokens?: number;\n  temperature?: number;\n  top_p?: number;\n  top_k?: number;\n  seed?: number;\n  repetition_penalty?: number;\n  frequency_penalty?: number;\n  presence_penalty?: number;\n  raw?: boolean;\n  messages?: RoleScopedChatInput[];\n};\nexport type AiImageToTextOutput = {\n  description: string;\n};\nexport declare abstract class BaseAiImageToText {\n  inputs: AiImageToTextInput;\n  postProcessedOutputs: AiImageToTextOutput;\n}\nexport type AiImageTextToTextInput = {\n  image: string;\n  prompt?: string;\n  max_tokens?: number;\n  temperature?: number;\n  ignore_eos?: boolean;\n  top_p?: number;\n  top_k?: number;\n  seed?: number;\n  repetition_penalty?: number;\n  frequency_penalty?: number;\n  presence_penalty?: number;\n  raw?: boolean;\n  messages?: RoleScopedChatInput[];\n};\nexport type AiImageTextToTextOutput = {\n  description: string;\n};\nexport declare abstract class BaseAiImageTextToText {\n  inputs: AiImageTextToTextInput;\n  postProcessedOutputs: AiImageTextToTextOutput;\n}\nexport type AiMultimodalEmbeddingsInput = {\n  image: string;\n  text: string[];\n};\nexport type AiIMultimodalEmbeddingsOutput = {\n  data: number[][];\n  shape: number[];\n};\nexport declare abstract class BaseAiMultimodalEmbeddings {\n  inputs: AiImageTextToTextInput;\n  postProcessedOutputs: AiImageTextToTextOutput;\n}\nexport type AiObjectDetectionInput = {\n  image: number[];\n};\nexport type AiObjectDetectionOutput = {\n  score?: number;\n  label?: string;\n}[];\nexport declare abstract class BaseAiObjectDetection {\n  inputs: AiObjectDetectionInput;\n  postProcessedOutputs: AiObjectDetectionOutput;\n}\nexport type AiSentenceSimilarityInput = {\n  source: string;\n  sentences: string[];\n};\nexport type AiSentenceSimilarityOutput = number[];\nexport declare abstract class BaseAiSentenceSimilarity {\n  inputs: AiSentenceSimilarityInput;\n  postProcessedOutputs: AiSentenceSimilarityOutput;\n}\nexport type AiAutomaticSpeechRecognitionInput = {\n  audio: number[];\n};\nexport type AiAutomaticSpeechRecognitionOutput = {\n  text?: string;\n  words?: {\n    word: string;\n    start: number;\n    end: number;\n  }[];\n  vtt?: string;\n};\nexport declare abstract class BaseAiAutomaticSpeechRecognition {\n  inputs: AiAutomaticSpeechRecognitionInput;\n  postProcessedOutputs: AiAutomaticSpeechRecognitionOutput;\n}\nexport type AiSummarizationInput = {\n  input_text: string;\n  max_length?: number;\n};\nexport type AiSummarizationOutput = {\n  summary: string;\n};\nexport declare abstract class BaseAiSummarization {\n  inputs: AiSummarizationInput;\n  postProcessedOutputs: AiSummarizationOutput;\n}\nexport type AiTextClassificationInput = {\n  text: string;\n};\nexport type AiTextClassificationOutput = {\n  score?: number;\n  label?: string;\n}[];\nexport declare abstract class BaseAiTextClassification {\n  inputs: AiTextClassificationInput;\n  postProcessedOutputs: AiTextClassificationOutput;\n}\nexport type AiTextEmbeddingsInput = {\n  text: string | string[];\n};\nexport type AiTextEmbeddingsOutput = {\n  shape: number[];\n  data: number[][];\n};\nexport declare abstract class BaseAiTextEmbeddings {\n  inputs: AiTextEmbeddingsInput;\n  postProcessedOutputs: AiTextEmbeddingsOutput;\n}\nexport type RoleScopedChatInput = {\n  role: \"user\" | \"assistant\" | \"system\" | \"tool\" | (string & NonNullable<unknown>);\n  content: string;\n  name?: string;\n};\nexport type AiTextGenerationToolLegacyInput = {\n  name: string;\n  description: string;\n  parameters?: {\n    type: \"object\" | (string & NonNullable<unknown>);\n    properties: {\n      [key: string]: {\n        type: string;\n        description?: string;\n      };\n    };\n    required: string[];\n  };\n};\nexport type AiTextGenerationToolInput = {\n  type: \"function\" | (string & NonNullable<unknown>);\n  function: {\n    name: string;\n    description: string;\n    parameters?: {\n      type: \"object\" | (string & NonNullable<unknown>);\n      properties: {\n        [key: string]: {\n          type: string;\n          description?: string;\n        };\n      };\n      required: string[];\n    };\n  };\n};\nexport type AiTextGenerationFunctionsInput = {\n  name: string;\n  code: string;\n};\nexport type AiTextGenerationResponseFormat = {\n  type: string;\n  json_schema?: any;\n};\nexport type AiTextGenerationInput = {\n  prompt?: string;\n  raw?: boolean;\n  stream?: boolean;\n  max_tokens?: number;\n  temperature?: number;\n  top_p?: number;\n  top_k?: number;\n  seed?: number;\n  repetition_penalty?: number;\n  frequency_penalty?: number;\n  presence_penalty?: number;\n  messages?: RoleScopedChatInput[];\n  response_format?: AiTextGenerationResponseFormat;\n  tools?: AiTextGenerationToolInput[] | AiTextGenerationToolLegacyInput[] | (object & NonNullable<unknown>);\n  functions?: AiTextGenerationFunctionsInput[];\n};\nexport type AiTextGenerationToolLegacyOutput = {\n  name: string;\n  arguments: unknown;\n};\nexport type AiTextGenerationToolOutput = {\n  id: string;\n  type: \"function\";\n  function: {\n    name: string;\n    arguments: string;\n  };\n};\nexport type UsageTags = {\n  prompt_tokens: number;\n  completion_tokens: number;\n  total_tokens: number;\n};\nexport type AiTextGenerationOutput = {\n  response?: string;\n  tool_calls?: AiTextGenerationToolLegacyOutput[] & AiTextGenerationToolOutput[];\n  usage?: UsageTags;\n};\nexport declare abstract class BaseAiTextGeneration {\n  inputs: AiTextGenerationInput;\n  postProcessedOutputs: AiTextGenerationOutput;\n}\nexport type AiTextToSpeechInput = {\n  prompt: string;\n  lang?: string;\n};\nexport type AiTextToSpeechOutput =\n  | Uint8Array\n  | {\n      audio: string;\n    };\nexport declare abstract class BaseAiTextToSpeech {\n  inputs: AiTextToSpeechInput;\n  postProcessedOutputs: AiTextToSpeechOutput;\n}\nexport type AiTextToImageInput = {\n  prompt: string;\n  negative_prompt?: string;\n  height?: number;\n  width?: number;\n  image?: number[];\n  image_b64?: string;\n  mask?: number[];\n  num_steps?: number;\n  strength?: number;\n  guidance?: number;\n  seed?: number;\n};\nexport type AiTextToImageOutput = ReadableStream<Uint8Array>;\nexport declare abstract class BaseAiTextToImage {\n  inputs: AiTextToImageInput;\n  postProcessedOutputs: AiTextToImageOutput;\n}\nexport type AiTranslationInput = {\n  text: string;\n  target_lang: string;\n  source_lang?: string;\n};\nexport type AiTranslationOutput = {\n  translated_text?: string;\n};\nexport declare abstract class BaseAiTranslation {\n  inputs: AiTranslationInput;\n  postProcessedOutputs: AiTranslationOutput;\n}\n/**\n * Workers AI support for OpenAI's Chat Completions API\n */\nexport type ChatCompletionContentPartText = {\n  type: \"text\";\n  text: string;\n};\nexport type ChatCompletionContentPartImage = {\n  type: \"image_url\";\n  image_url: {\n    url: string;\n    detail?: \"auto\" | \"low\" | \"high\";\n  };\n};\nexport type ChatCompletionContentPartInputAudio = {\n  type: \"input_audio\";\n  input_audio: {\n    /** Base64 encoded audio data. */\n    data: string;\n    format: \"wav\" | \"mp3\";\n  };\n};\nexport type ChatCompletionContentPartFile = {\n  type: \"file\";\n  file: {\n    /** Base64 encoded file data. */\n    file_data?: string;\n    /** The ID of an uploaded file. */\n    file_id?: string;\n    filename?: string;\n  };\n};\nexport type ChatCompletionContentPartRefusal = {\n  type: \"refusal\";\n  refusal: string;\n};\nexport type ChatCompletionContentPart =\n  | ChatCompletionContentPartText\n  | ChatCompletionContentPartImage\n  | ChatCompletionContentPartInputAudio\n  | ChatCompletionContentPartFile;\nexport type FunctionDefinition = {\n  name: string;\n  description?: string;\n  parameters?: Record<string, unknown>;\n  strict?: boolean | null;\n};\nexport type ChatCompletionFunctionTool = {\n  type: \"function\";\n  function: FunctionDefinition;\n};\nexport type ChatCompletionCustomToolGrammarFormat = {\n  type: \"grammar\";\n  grammar: {\n    definition: string;\n    syntax: \"lark\" | \"regex\";\n  };\n};\nexport type ChatCompletionCustomToolTextFormat = {\n  type: \"text\";\n};\nexport type ChatCompletionCustomToolFormat = ChatCompletionCustomToolTextFormat | ChatCompletionCustomToolGrammarFormat;\nexport type ChatCompletionCustomTool = {\n  type: \"custom\";\n  custom: {\n    name: string;\n    description?: string;\n    format?: ChatCompletionCustomToolFormat;\n  };\n};\nexport type ChatCompletionTool = ChatCompletionFunctionTool | ChatCompletionCustomTool;\nexport type ChatCompletionMessageFunctionToolCall = {\n  id: string;\n  type: \"function\";\n  function: {\n    name: string;\n    /** JSON-encoded arguments string. */\n    arguments: string;\n  };\n};\nexport type ChatCompletionMessageCustomToolCall = {\n  id: string;\n  type: \"custom\";\n  custom: {\n    name: string;\n    input: string;\n  };\n};\nexport type ChatCompletionMessageToolCall = ChatCompletionMessageFunctionToolCall | ChatCompletionMessageCustomToolCall;\nexport type ChatCompletionToolChoiceFunction = {\n  type: \"function\";\n  function: {\n    name: string;\n  };\n};\nexport type ChatCompletionToolChoiceCustom = {\n  type: \"custom\";\n  custom: {\n    name: string;\n  };\n};\nexport type ChatCompletionToolChoiceAllowedTools = {\n  type: \"allowed_tools\";\n  allowed_tools: {\n    mode: \"auto\" | \"required\";\n    tools: Array<Record<string, unknown>>;\n  };\n};\nexport type ChatCompletionToolChoiceOption =\n  | \"none\"\n  | \"auto\"\n  | \"required\"\n  | ChatCompletionToolChoiceFunction\n  | ChatCompletionToolChoiceCustom\n  | ChatCompletionToolChoiceAllowedTools;\nexport type DeveloperMessage = {\n  role: \"developer\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n  name?: string;\n};\nexport type SystemMessage = {\n  role: \"system\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n  name?: string;\n};\n/**\n * Permissive merged content part used inside UserMessage arrays.\n *\n * Cabidela has a limitation where anyOf/oneOf with enum-based discrimination\n * inside nested array items does not correctly match different branches for\n * different array elements, so the schema uses a single merged object.\n */\nexport type UserMessageContentPart = {\n  type: \"text\" | \"image_url\" | \"input_audio\" | \"file\";\n  text?: string;\n  image_url?: {\n    url?: string;\n    detail?: \"auto\" | \"low\" | \"high\";\n  };\n  input_audio?: {\n    data?: string;\n    format?: \"wav\" | \"mp3\";\n  };\n  file?: {\n    file_data?: string;\n    file_id?: string;\n    filename?: string;\n  };\n};\nexport type UserMessage = {\n  role: \"user\";\n  content: string | Array<UserMessageContentPart>;\n  name?: string;\n};\nexport type AssistantMessageContentPart = {\n  type: \"text\" | \"refusal\";\n  text?: string;\n  refusal?: string;\n};\nexport type AssistantMessage = {\n  role: \"assistant\";\n  content?: string | null | Array<AssistantMessageContentPart>;\n  refusal?: string | null;\n  name?: string;\n  audio?: {\n    id: string;\n  };\n  tool_calls?: Array<ChatCompletionMessageToolCall>;\n  function_call?: {\n    name: string;\n    arguments: string;\n  };\n};\nexport type ToolMessage = {\n  role: \"tool\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n  tool_call_id: string;\n};\nexport type FunctionMessage = {\n  role: \"function\";\n  content: string;\n  name: string;\n};\nexport type ChatCompletionMessageParam =\n  | DeveloperMessage\n  | SystemMessage\n  | UserMessage\n  | AssistantMessage\n  | ToolMessage\n  | FunctionMessage;\nexport type ChatCompletionsResponseFormatText = {\n  type: \"text\";\n};\nexport type ChatCompletionsResponseFormatJSONObject = {\n  type: \"json_object\";\n};\nexport type ResponseFormatJSONSchema = {\n  type: \"json_schema\";\n  json_schema: {\n    name: string;\n    description?: string;\n    schema?: Record<string, unknown>;\n    strict?: boolean | null;\n  };\n};\nexport type ResponseFormat =\n  | ChatCompletionsResponseFormatText\n  | ChatCompletionsResponseFormatJSONObject\n  | ResponseFormatJSONSchema;\nexport type ChatCompletionsStreamOptions = {\n  include_usage?: boolean;\n  include_obfuscation?: boolean;\n};\nexport type PredictionContent = {\n  type: \"content\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n};\nexport type AudioParams = {\n  voice:\n    | string\n    | {\n        id: string;\n      };\n  format: \"wav\" | \"aac\" | \"mp3\" | \"flac\" | \"opus\" | \"pcm16\";\n};\nexport type WebSearchUserLocation = {\n  type: \"approximate\";\n  approximate: {\n    city?: string;\n    country?: string;\n    region?: string;\n    timezone?: string;\n  };\n};\nexport type WebSearchOptions = {\n  search_context_size?: \"low\" | \"medium\" | \"high\";\n  user_location?: WebSearchUserLocation;\n};\nexport type ChatTemplateKwargs = {\n  /** Whether to enable reasoning, enabled by default. */\n  enable_thinking?: boolean;\n  /** If false, preserves reasoning context between turns. */\n  clear_thinking?: boolean;\n};\n/** Shared optional properties used by both Prompt and Messages input branches. */\nexport type ChatCompletionsCommonOptions = {\n  model?: string;\n  audio?: AudioParams;\n  frequency_penalty?: number | null;\n  logit_bias?: Record<string, unknown> | null;\n  logprobs?: boolean | null;\n  top_logprobs?: number | null;\n  max_tokens?: number | null;\n  max_completion_tokens?: number | null;\n  metadata?: Record<string, unknown> | null;\n  modalities?: Array<\"text\" | \"audio\"> | null;\n  n?: number | null;\n  parallel_tool_calls?: boolean;\n  prediction?: PredictionContent;\n  presence_penalty?: number | null;\n  reasoning_effort?: \"low\" | \"medium\" | \"high\" | null;\n  chat_template_kwargs?: ChatTemplateKwargs;\n  response_format?: ResponseFormat;\n  seed?: number | null;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n  stop?: string | Array<string> | null;\n  store?: boolean | null;\n  stream?: boolean | null;\n  stream_options?: ChatCompletionsStreamOptions;\n  temperature?: number | null;\n  tool_choice?: ChatCompletionToolChoiceOption;\n  tools?: Array<ChatCompletionTool>;\n  top_p?: number | null;\n  user?: string;\n  web_search_options?: WebSearchOptions;\n  function_call?:\n    | \"none\"\n    | \"auto\"\n    | {\n        name: string;\n      };\n  functions?: Array<FunctionDefinition>;\n};\nexport type PromptTokensDetails = {\n  cached_tokens?: number;\n  audio_tokens?: number;\n};\nexport type CompletionTokensDetails = {\n  reasoning_tokens?: number;\n  audio_tokens?: number;\n  accepted_prediction_tokens?: number;\n  rejected_prediction_tokens?: number;\n};\nexport type CompletionUsage = {\n  prompt_tokens: number;\n  completion_tokens: number;\n  total_tokens: number;\n  prompt_tokens_details?: PromptTokensDetails;\n  completion_tokens_details?: CompletionTokensDetails;\n};\nexport type ChatCompletionTopLogprob = {\n  token: string;\n  logprob: number;\n  bytes: Array<number> | null;\n};\nexport type ChatCompletionTokenLogprob = {\n  token: string;\n  logprob: number;\n  bytes: Array<number> | null;\n  top_logprobs: Array<ChatCompletionTopLogprob>;\n};\nexport type ChatCompletionAudio = {\n  id: string;\n  /** Base64 encoded audio bytes. */\n  data: string;\n  expires_at: number;\n  transcript: string;\n};\nexport type ChatCompletionUrlCitation = {\n  type: \"url_citation\";\n  url_citation: {\n    url: string;\n    title: string;\n    start_index: number;\n    end_index: number;\n  };\n};\nexport type ChatCompletionResponseMessage = {\n  role: \"assistant\";\n  content: string | null;\n  refusal: string | null;\n  annotations?: Array<ChatCompletionUrlCitation>;\n  audio?: ChatCompletionAudio;\n  tool_calls?: Array<ChatCompletionMessageToolCall>;\n  function_call?: {\n    name: string;\n    arguments: string;\n  } | null;\n};\nexport type ChatCompletionLogprobs = {\n  content: Array<ChatCompletionTokenLogprob> | null;\n  refusal?: Array<ChatCompletionTokenLogprob> | null;\n};\nexport type ChatCompletionChoice = {\n  index: number;\n  message: ChatCompletionResponseMessage;\n  finish_reason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" | \"function_call\";\n  logprobs: ChatCompletionLogprobs | null;\n};\nexport type ChatCompletionsPromptInput = {\n  prompt: string;\n} & ChatCompletionsCommonOptions;\nexport type ChatCompletionsMessagesInput = {\n  messages: Array<ChatCompletionMessageParam>;\n} & ChatCompletionsCommonOptions;\nexport type ChatCompletionsOutput = {\n  id: string;\n  object: string;\n  created: number;\n  model: string;\n  choices: Array<ChatCompletionChoice>;\n  usage?: CompletionUsage;\n  system_fingerprint?: string | null;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n};\n/**\n * Workers AI support for OpenAI's Responses API\n * Reference: https://github.com/openai/openai-node/blob/master/src/resources/responses/responses.ts\n *\n * It's a stripped down version from its source.\n * It currently supports basic function calling, json mode and accepts images as input.\n *\n * It does not include types for WebSearch, CodeInterpreter, FileInputs, MCP, CustomTools.\n * We plan to add those incrementally as model + platform capabilities evolve.\n */\nexport type ResponsesInput = {\n  background?: boolean | null;\n  conversation?: string | ResponseConversationParam | null;\n  include?: Array<ResponseIncludable> | null;\n  input?: string | ResponseInput;\n  instructions?: string | null;\n  max_output_tokens?: number | null;\n  parallel_tool_calls?: boolean | null;\n  previous_response_id?: string | null;\n  prompt_cache_key?: string;\n  reasoning?: Reasoning | null;\n  safety_identifier?: string;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n  stream?: boolean | null;\n  stream_options?: StreamOptions | null;\n  temperature?: number | null;\n  text?: ResponseTextConfig;\n  tool_choice?: ToolChoiceOptions | ToolChoiceFunction;\n  tools?: Array<Tool>;\n  top_p?: number | null;\n  truncation?: \"auto\" | \"disabled\" | null;\n};\nexport type ResponsesOutput = {\n  id?: string;\n  created_at?: number;\n  output_text?: string;\n  error?: ResponseError | null;\n  incomplete_details?: ResponseIncompleteDetails | null;\n  instructions?: string | Array<ResponseInputItem> | null;\n  object?: \"response\";\n  output?: Array<ResponseOutputItem>;\n  parallel_tool_calls?: boolean;\n  temperature?: number | null;\n  tool_choice?: ToolChoiceOptions | ToolChoiceFunction;\n  tools?: Array<Tool>;\n  top_p?: number | null;\n  max_output_tokens?: number | null;\n  previous_response_id?: string | null;\n  prompt?: ResponsePrompt | null;\n  reasoning?: Reasoning | null;\n  safety_identifier?: string;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n  status?: ResponseStatus;\n  text?: ResponseTextConfig;\n  truncation?: \"auto\" | \"disabled\" | null;\n  usage?: ResponseUsage;\n};\nexport type EasyInputMessage = {\n  content: string | ResponseInputMessageContentList;\n  role: \"user\" | \"assistant\" | \"system\" | \"developer\";\n  type?: \"message\";\n};\nexport type ResponsesFunctionTool = {\n  name: string;\n  parameters: {\n    [key: string]: unknown;\n  } | null;\n  strict: boolean | null;\n  type: \"function\";\n  description?: string | null;\n};\nexport type ResponseIncompleteDetails = {\n  reason?: \"max_output_tokens\" | \"content_filter\";\n};\nexport type ResponsePrompt = {\n  id: string;\n  variables?: {\n    [key: string]: string | ResponseInputText | ResponseInputImage;\n  } | null;\n  version?: string | null;\n};\nexport type Reasoning = {\n  effort?: ReasoningEffort | null;\n  generate_summary?: \"auto\" | \"concise\" | \"detailed\" | null;\n  summary?: \"auto\" | \"concise\" | \"detailed\" | null;\n};\nexport type ResponseContent =\n  | ResponseInputText\n  | ResponseInputImage\n  | ResponseOutputText\n  | ResponseOutputRefusal\n  | ResponseContentReasoningText;\nexport type ResponseContentReasoningText = {\n  text: string;\n  type: \"reasoning_text\";\n};\nexport type ResponseConversationParam = {\n  id: string;\n};\nexport type ResponseCreatedEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.created\";\n};\nexport type ResponseCustomToolCallOutput = {\n  call_id: string;\n  output: string | Array<ResponseInputText | ResponseInputImage>;\n  type: \"custom_tool_call_output\";\n  id?: string;\n};\nexport type ResponseError = {\n  code:\n    | \"server_error\"\n    | \"rate_limit_exceeded\"\n    | \"invalid_prompt\"\n    | \"vector_store_timeout\"\n    | \"invalid_image\"\n    | \"invalid_image_format\"\n    | \"invalid_base64_image\"\n    | \"invalid_image_url\"\n    | \"image_too_large\"\n    | \"image_too_small\"\n    | \"image_parse_error\"\n    | \"image_content_policy_violation\"\n    | \"invalid_image_mode\"\n    | \"image_file_too_large\"\n    | \"unsupported_image_media_type\"\n    | \"empty_image_file\"\n    | \"failed_to_download_image\"\n    | \"image_file_not_found\";\n  message: string;\n};\nexport type ResponseErrorEvent = {\n  code: string | null;\n  message: string;\n  param: string | null;\n  sequence_number: number;\n  type: \"error\";\n};\nexport type ResponseFailedEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.failed\";\n};\nexport type ResponseFormatText = {\n  type: \"text\";\n};\nexport type ResponseFormatJSONObject = {\n  type: \"json_object\";\n};\nexport type ResponseFormatTextConfig =\n  | ResponseFormatText\n  | ResponseFormatTextJSONSchemaConfig\n  | ResponseFormatJSONObject;\nexport type ResponseFormatTextJSONSchemaConfig = {\n  name: string;\n  schema: {\n    [key: string]: unknown;\n  };\n  type: \"json_schema\";\n  description?: string;\n  strict?: boolean | null;\n};\nexport type ResponseFunctionCallArgumentsDeltaEvent = {\n  delta: string;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.function_call_arguments.delta\";\n};\nexport type ResponseFunctionCallArgumentsDoneEvent = {\n  arguments: string;\n  item_id: string;\n  name: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.function_call_arguments.done\";\n};\nexport type ResponseFunctionCallOutputItem = ResponseInputTextContent | ResponseInputImageContent;\nexport type ResponseFunctionCallOutputItemList = Array<ResponseFunctionCallOutputItem>;\nexport type ResponseFunctionToolCall = {\n  arguments: string;\n  call_id: string;\n  name: string;\n  type: \"function_call\";\n  id?: string;\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n};\nexport interface ResponseFunctionToolCallItem extends ResponseFunctionToolCall {\n  id: string;\n}\nexport type ResponseFunctionToolCallOutputItem = {\n  id: string;\n  call_id: string;\n  output: string | Array<ResponseInputText | ResponseInputImage>;\n  type: \"function_call_output\";\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n};\nexport type ResponseIncludable = \"message.input_image.image_url\" | \"message.output_text.logprobs\";\nexport type ResponseIncompleteEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.incomplete\";\n};\nexport type ResponseInput = Array<ResponseInputItem>;\nexport type ResponseInputContent = ResponseInputText | ResponseInputImage;\nexport type ResponseInputImage = {\n  detail: \"low\" | \"high\" | \"auto\";\n  type: \"input_image\";\n  /**\n   * Base64 encoded image\n   */\n  image_url?: string | null;\n};\nexport type ResponseInputImageContent = {\n  type: \"input_image\";\n  detail?: \"low\" | \"high\" | \"auto\" | null;\n  /**\n   * Base64 encoded image\n   */\n  image_url?: string | null;\n};\nexport type ResponseInputItem =\n  | EasyInputMessage\n  | ResponseInputItemMessage\n  | ResponseOutputMessage\n  | ResponseFunctionToolCall\n  | ResponseInputItemFunctionCallOutput\n  | ResponseReasoningItem;\nexport type ResponseInputItemFunctionCallOutput = {\n  call_id: string;\n  output: string | ResponseFunctionCallOutputItemList;\n  type: \"function_call_output\";\n  id?: string | null;\n  status?: \"in_progress\" | \"completed\" | \"incomplete\" | null;\n};\nexport type ResponseInputItemMessage = {\n  content: ResponseInputMessageContentList;\n  role: \"user\" | \"system\" | \"developer\";\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n  type?: \"message\";\n};\nexport type ResponseInputMessageContentList = Array<ResponseInputContent>;\nexport type ResponseInputMessageItem = {\n  id: string;\n  content: ResponseInputMessageContentList;\n  role: \"user\" | \"system\" | \"developer\";\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n  type?: \"message\";\n};\nexport type ResponseInputText = {\n  text: string;\n  type: \"input_text\";\n};\nexport type ResponseInputTextContent = {\n  text: string;\n  type: \"input_text\";\n};\nexport type ResponseItem =\n  | ResponseInputMessageItem\n  | ResponseOutputMessage\n  | ResponseFunctionToolCallItem\n  | ResponseFunctionToolCallOutputItem;\nexport type ResponseOutputItem = ResponseOutputMessage | ResponseFunctionToolCall | ResponseReasoningItem;\nexport type ResponseOutputItemAddedEvent = {\n  item: ResponseOutputItem;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.output_item.added\";\n};\nexport type ResponseOutputItemDoneEvent = {\n  item: ResponseOutputItem;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.output_item.done\";\n};\nexport type ResponseOutputMessage = {\n  id: string;\n  content: Array<ResponseOutputText | ResponseOutputRefusal>;\n  role: \"assistant\";\n  status: \"in_progress\" | \"completed\" | \"incomplete\";\n  type: \"message\";\n};\nexport type ResponseOutputRefusal = {\n  refusal: string;\n  type: \"refusal\";\n};\nexport type ResponseOutputText = {\n  text: string;\n  type: \"output_text\";\n  logprobs?: Array<Logprob>;\n};\nexport type ResponseReasoningItem = {\n  id: string;\n  summary: Array<ResponseReasoningSummaryItem>;\n  type: \"reasoning\";\n  content?: Array<ResponseReasoningContentItem>;\n  encrypted_content?: string | null;\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n};\nexport type ResponseReasoningSummaryItem = {\n  text: string;\n  type: \"summary_text\";\n};\nexport type ResponseReasoningContentItem = {\n  text: string;\n  type: \"reasoning_text\";\n};\nexport type ResponseReasoningTextDeltaEvent = {\n  content_index: number;\n  delta: string;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.reasoning_text.delta\";\n};\nexport type ResponseReasoningTextDoneEvent = {\n  content_index: number;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  text: string;\n  type: \"response.reasoning_text.done\";\n};\nexport type ResponseRefusalDeltaEvent = {\n  content_index: number;\n  delta: string;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.refusal.delta\";\n};\nexport type ResponseRefusalDoneEvent = {\n  content_index: number;\n  item_id: string;\n  output_index: number;\n  refusal: string;\n  sequence_number: number;\n  type: \"response.refusal.done\";\n};\nexport type ResponseStatus = \"completed\" | \"failed\" | \"in_progress\" | \"cancelled\" | \"queued\" | \"incomplete\";\nexport type ResponseStreamEvent =\n  | ResponseCompletedEvent\n  | ResponseCreatedEvent\n  | ResponseErrorEvent\n  | ResponseFunctionCallArgumentsDeltaEvent\n  | ResponseFunctionCallArgumentsDoneEvent\n  | ResponseFailedEvent\n  | ResponseIncompleteEvent\n  | ResponseOutputItemAddedEvent\n  | ResponseOutputItemDoneEvent\n  | ResponseReasoningTextDeltaEvent\n  | ResponseReasoningTextDoneEvent\n  | ResponseRefusalDeltaEvent\n  | ResponseRefusalDoneEvent\n  | ResponseTextDeltaEvent\n  | ResponseTextDoneEvent;\nexport type ResponseCompletedEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.completed\";\n};\nexport type ResponseTextConfig = {\n  format?: ResponseFormatTextConfig;\n  verbosity?: \"low\" | \"medium\" | \"high\" | null;\n};\nexport type ResponseTextDeltaEvent = {\n  content_index: number;\n  delta: string;\n  item_id: string;\n  logprobs: Array<Logprob>;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.output_text.delta\";\n};\nexport type ResponseTextDoneEvent = {\n  content_index: number;\n  item_id: string;\n  logprobs: Array<Logprob>;\n  output_index: number;\n  sequence_number: number;\n  text: string;\n  type: \"response.output_text.done\";\n};\nexport type Logprob = {\n  token: string;\n  logprob: number;\n  top_logprobs?: Array<TopLogprob>;\n};\nexport type TopLogprob = {\n  token?: string;\n  logprob?: number;\n};\nexport type ResponseUsage = {\n  input_tokens: number;\n  output_tokens: number;\n  total_tokens: number;\n};\nexport type Tool = ResponsesFunctionTool;\nexport type ToolChoiceFunction = {\n  name: string;\n  type: \"function\";\n};\nexport type ToolChoiceOptions = \"none\";\nexport type ReasoningEffort = \"minimal\" | \"low\" | \"medium\" | \"high\" | null;\nexport type StreamOptions = {\n  include_obfuscation?: boolean;\n};\n/** Marks keys from T that aren't in U as optional never */\nexport type Without<T, U> = {\n  [P in Exclude<keyof T, keyof U>]?: never;\n};\n/** Either T or U, but not both (mutually exclusive) */\nexport type XOR<T, U> = (T & Without<U, T>) | (U & Without<T, U>);\nexport type Ai_Cf_Baai_Bge_Base_En_V1_5_Input =\n  | {\n      text: string | string[];\n      /**\n       * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        text: string | string[];\n        /**\n         * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n         */\n        pooling?: \"mean\" | \"cls\";\n      }[];\n    };\nexport type Ai_Cf_Baai_Bge_Base_En_V1_5_Output =\n  | {\n      shape?: number[];\n      /**\n       * Embeddings of the requested text values\n       */\n      data?: number[][];\n      /**\n       * The pooling method used in the embedding process.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | Ai_Cf_Baai_Bge_Base_En_V1_5_AsyncResponse;\nexport interface Ai_Cf_Baai_Bge_Base_En_V1_5_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Baai_Bge_Base_En_V1_5 {\n  inputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Output;\n}\nexport type Ai_Cf_Openai_Whisper_Input =\n  | string\n  | {\n      /**\n       * An array of integers that represent the audio data constrained to 8-bit unsigned integer values\n       */\n      audio: number[];\n    };\nexport interface Ai_Cf_Openai_Whisper_Output {\n  /**\n   * The transcription\n   */\n  text: string;\n  word_count?: number;\n  words?: {\n    word?: string;\n    /**\n     * The second this word begins in the recording\n     */\n    start?: number;\n    /**\n     * The ending second when the word completes\n     */\n    end?: number;\n  }[];\n  vtt?: string;\n}\nexport declare abstract class Base_Ai_Cf_Openai_Whisper {\n  inputs: Ai_Cf_Openai_Whisper_Input;\n  postProcessedOutputs: Ai_Cf_Openai_Whisper_Output;\n}\nexport type Ai_Cf_Meta_M2M100_1_2B_Input =\n  | {\n      /**\n       * The text to be translated\n       */\n      text: string;\n      /**\n       * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified\n       */\n      source_lang?: string;\n      /**\n       * The language code to translate the text into (e.g., 'es' for Spanish)\n       */\n      target_lang: string;\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        /**\n         * The text to be translated\n         */\n        text: string;\n        /**\n         * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified\n         */\n        source_lang?: string;\n        /**\n         * The language code to translate the text into (e.g., 'es' for Spanish)\n         */\n        target_lang: string;\n      }[];\n    };\nexport type Ai_Cf_Meta_M2M100_1_2B_Output =\n  | {\n      /**\n       * The translated text in the target language\n       */\n      translated_text?: string;\n    }\n  | Ai_Cf_Meta_M2M100_1_2B_AsyncResponse;\nexport interface Ai_Cf_Meta_M2M100_1_2B_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Meta_M2M100_1_2B {\n  inputs: Ai_Cf_Meta_M2M100_1_2B_Input;\n  postProcessedOutputs: Ai_Cf_Meta_M2M100_1_2B_Output;\n}\nexport type Ai_Cf_Baai_Bge_Small_En_V1_5_Input =\n  | {\n      text: string | string[];\n      /**\n       * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        text: string | string[];\n        /**\n         * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n         */\n        pooling?: \"mean\" | \"cls\";\n      }[];\n    };\nexport type Ai_Cf_Baai_Bge_Small_En_V1_5_Output =\n  | {\n      shape?: number[];\n      /**\n       * Embeddings of the requested text values\n       */\n      data?: number[][];\n      /**\n       * The pooling method used in the embedding process.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | Ai_Cf_Baai_Bge_Small_En_V1_5_AsyncResponse;\nexport interface Ai_Cf_Baai_Bge_Small_En_V1_5_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Baai_Bge_Small_En_V1_5 {\n  inputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Output;\n}\nexport type Ai_Cf_Baai_Bge_Large_En_V1_5_Input =\n  | {\n      text: string | string[];\n      /**\n       * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        text: string | string[];\n        /**\n         * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n         */\n        pooling?: \"mean\" | \"cls\";\n      }[];\n    };\nexport type Ai_Cf_Baai_Bge_Large_En_V1_5_Output =\n  | {\n      shape?: number[];\n      /**\n       * Embeddings of the requested text values\n       */\n      data?: number[][];\n      /**\n       * The pooling method used in the embedding process.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | Ai_Cf_Baai_Bge_Large_En_V1_5_AsyncResponse;\nexport interface Ai_Cf_Baai_Bge_Large_En_V1_5_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Baai_Bge_Large_En_V1_5 {\n  inputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Output;\n}\nexport type Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input =\n  | string\n  | {\n      /**\n       * The input text prompt for the model to generate a response.\n       */\n      prompt?: string;\n      /**\n       * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n       */\n      raw?: boolean;\n      /**\n       * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n       */\n      top_p?: number;\n      /**\n       * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n       */\n      top_k?: number;\n      /**\n       * Random seed for reproducibility of the generation.\n       */\n      seed?: number;\n      /**\n       * Penalty for repeated tokens; higher values discourage repetition.\n       */\n      repetition_penalty?: number;\n      /**\n       * Decreases the likelihood of the model repeating the same lines verbatim.\n       */\n      frequency_penalty?: number;\n      /**\n       * Increases the likelihood of the model introducing new topics.\n       */\n      presence_penalty?: number;\n      image: number[] | (string & NonNullable<unknown>);\n      /**\n       * The maximum number of tokens to generate in the response.\n       */\n      max_tokens?: number;\n    };\nexport interface Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output {\n  description?: string;\n}\nexport declare abstract class Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M {\n  inputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input;\n  postProcessedOutputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output;\n}\nexport type Ai_Cf_Openai_Whisper_Tiny_En_Input =\n  | string\n  | {\n      /**\n       * An array of integers that represent the audio data constrained to 8-bit unsigned integer values\n       */\n      audio: number[];\n    };\nexport interface Ai_Cf_Openai_Whisper_Tiny_En_Output {\n  /**\n   * The transcription\n   */\n  text: string;\n  word_count?: number;\n  words?: {\n    word?: string;\n    /**\n     * The second this word begins in the recording\n     */\n    start?: number;\n    /**\n     * The ending second when the word completes\n     */\n    end?: number;\n  }[];\n  vtt?: string;\n}\nexport declare abstract class Base_Ai_Cf_Openai_Whisper_Tiny_En {\n  inputs: Ai_Cf_Openai_Whisper_Tiny_En_Input;\n  postProcessedOutputs: Ai_Cf_Openai_Whisper_Tiny_En_Output;\n}\nexport interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input {\n  audio:\n    | string\n    | {\n        body?: object;\n        contentType?: string;\n      };\n  /**\n   * Supported tasks are 'translate' or 'transcribe'.\n   */\n  task?: string;\n  /**\n   * The language of the audio being transcribed or translated.\n   */\n  language?: string;\n  /**\n   * Preprocess the audio with a voice activity detection model.\n   */\n  vad_filter?: boolean;\n  /**\n   * A text prompt to help provide context to the model on the contents of the audio.\n   */\n  initial_prompt?: string;\n  /**\n   * The prefix appended to the beginning of the output of the transcription and can guide the transcription result.\n   */\n  prefix?: string;\n  /**\n   * The number of beams to use in beam search decoding. Higher values may improve accuracy at the cost of speed.\n   */\n  beam_size?: number;\n  /**\n   * Whether to condition on previous text during transcription. Setting to false may help prevent hallucination loops.\n   */\n  condition_on_previous_text?: boolean;\n  /**\n   * Threshold for detecting no-speech segments. Segments with no-speech probability above this value are skipped.\n   */\n  no_speech_threshold?: number;\n  /**\n   * Threshold for filtering out segments with high compression ratio, which often indicate repetitive or hallucinated text.\n   */\n  compression_ratio_threshold?: number;\n  /**\n   * Threshold for filtering out segments with low average log probability, indicating low confidence.\n   */\n  log_prob_threshold?: number;\n  /**\n   * Optional threshold (in seconds) to skip silent periods that may cause hallucinations.\n   */\n  hallucination_silence_threshold?: number;\n}\nexport interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output {\n  transcription_info?: {\n    /**\n     * The language of the audio being transcribed or translated.\n     */\n    language?: string;\n    /**\n     * The confidence level or probability of the detected language being accurate, represented as a decimal between 0 and 1.\n     */\n    language_probability?: number;\n    /**\n     * The total duration of the original audio file, in seconds.\n     */\n    duration?: number;\n    /**\n     * The duration of the audio after applying Voice Activity Detection (VAD) to remove silent or irrelevant sections, in seconds.\n     */\n    duration_after_vad?: number;\n  };\n  /**\n   * The complete transcription of the audio.\n   */\n  text: string;\n  /**\n   * The total number of words in the transcription.\n   */\n  word_count?: number;\n  segments?: {\n    /**\n     * The starting time of the segment within the audio, in seconds.\n     */\n    start?: number;\n    /**\n     * The ending time of the segment within the audio, in seconds.\n     */\n    end?: number;\n    /**\n     * The transcription of the segment.\n     */\n    text?: string;\n    /**\n     * The temperature used in the decoding process, controlling randomness in predictions. Lower values result in more deterministic outputs.\n     */\n    temperature?: number;\n    /**\n     * The average log probability of the predictions for the words in this segment, indicating overall confidence.\n     */\n    avg_logprob?: number;\n    /**\n     * The compression ratio of the input to the output, measuring how much the text was compressed during the transcription process.\n     */\n    compression_ratio?: number;\n    /**\n     * The probability that the segment contains no speech, represented as a decimal between 0 and 1.\n     */\n    no_speech_prob?: number;\n    words?: {\n      /**\n       * The individual word transcribed from the audio.\n       */\n      word?: string;\n      /**\n       * The starting time of the word within the audio, in seconds.\n       */\n      start?: number;\n      /**\n       * The ending time of the word within the audio, in seconds.\n       */\n      end?: number;\n    }[];\n  }[];\n  /**\n   * The transcription in WebVTT format, which includes timing and text information for use in subtitles.\n   */\n  vtt?: string;\n}\nexport declare abstract class Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo {\n  inputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input;\n  postProcessedOutputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output;\n}\nexport type Ai_Cf_Baai_Bge_M3_Input =\n  | Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts\n  | Ai_Cf_Baai_Bge_M3_Input_Embedding\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: (Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts_1 | Ai_Cf_Baai_Bge_M3_Input_Embedding_1)[];\n    };\nexport interface Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts {\n  /**\n   * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts\n   */\n  query?: string;\n  /**\n   * List of provided contexts. Note that the index in this array is important, as the response will refer to it.\n   */\n  contexts: {\n    /**\n     * One of the provided context content\n     */\n    text?: string;\n  }[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\nexport interface Ai_Cf_Baai_Bge_M3_Input_Embedding {\n  text: string | string[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\nexport interface Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts_1 {\n  /**\n   * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts\n   */\n  query?: string;\n  /**\n   * List of provided contexts. Note that the index in this array is important, as the response will refer to it.\n   */\n  contexts: {\n    /**\n     * One of the provided context content\n     */\n    text?: string;\n  }[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\nexport interface Ai_Cf_Baai_Bge_M3_Input_Embedding_1 {\n  text: string | string[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\nexport type Ai_Cf_Baai_Bge_M3_Output =\n  | Ai_Cf_Baai_Bge_M3_Output_Query\n  | Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts\n  | Ai_Cf_Baai_Bge_M3_Output_Embedding\n  | Ai_Cf_Baai_Bge_M3_AsyncResponse;\nexport interface Ai_Cf_Baai_Bge_M3_Output_Query {\n  response?: {\n    /**\n     * Index of the context in the request\n     */\n    id?: number;\n    /**\n     * Score of the context under the index.\n     */\n    score?: number;\n  }[];\n}\nexport interface Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts {\n  response?: number[][];\n  shape?: number[];\n  /**\n   * The pooling method used in the embedding process.\n   */\n  pooling?: \"mean\" | \"cls\";\n}\nexport interface Ai_Cf_Baai_Bge_M3_Output_Embedding {\n  shape?: number[];\n  /**\n   * Embeddings of the requested text values\n   */\n  data?: number[][];\n  /**\n   * The pooling method used in the embedding process.\n   */\n  pooling?: \"mean\" | \"cls\";\n}\nexport interface Ai_Cf_Baai_Bge_M3_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Baai_Bge_M3 {\n  inputs: Ai_Cf_Baai_Bge_M3_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_M3_Output;\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input {\n  /**\n   * A text description of the image you want to generate.\n   */\n  prompt: string;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer.\n   */\n  steps?: number;\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output {\n  /**\n   * The generated image in Base64 format.\n   */\n  image?: string;\n}\nexport declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output;\n}\nexport type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input =\n  | Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Prompt\n  | Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Messages;\nexport interface Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  image?: number[] | (string & NonNullable<unknown>);\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n}\nexport interface Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  image?: number[] | (string & NonNullable<unknown>);\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * If true, the response will be streamed back incrementally.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response?: string;\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\nexport declare abstract class Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct {\n  inputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output;\n}\nexport type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input =\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Async_Batch;\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Async_Batch {\n  requests?: {\n    /**\n     * User-supplied reference. This field will be present in the response as well it can be used to reference the request and response. It's NOT validated to be unique.\n     */\n    external_reference?: string;\n    /**\n     * Prompt for the text generation model\n     */\n    prompt?: string;\n    /**\n     * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n     */\n    stream?: boolean;\n    /**\n     * The maximum number of tokens to generate in the response.\n     */\n    max_tokens?: number;\n    /**\n     * Controls the randomness of the output; higher values produce more random results.\n     */\n    temperature?: number;\n    /**\n     * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n     */\n    top_p?: number;\n    /**\n     * Random seed for reproducibility of the generation.\n     */\n    seed?: number;\n    /**\n     * Penalty for repeated tokens; higher values discourage repetition.\n     */\n    repetition_penalty?: number;\n    /**\n     * Decreases the likelihood of the model repeating the same lines verbatim.\n     */\n    frequency_penalty?: number;\n    /**\n     * Increases the likelihood of the model introducing new topics.\n     */\n    presence_penalty?: number;\n    response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_2;\n  }[];\n}\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_2 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output =\n  | {\n      /**\n       * The generated text response from the model\n       */\n      response: string;\n      /**\n       * Usage statistics for the inference request\n       */\n      usage?: {\n        /**\n         * Total number of tokens in input\n         */\n        prompt_tokens?: number;\n        /**\n         * Total number of tokens in output\n         */\n        completion_tokens?: number;\n        /**\n         * Total number of input and output tokens\n         */\n        total_tokens?: number;\n      };\n      /**\n       * An array of tool calls requests made during the response generation\n       */\n      tool_calls?: {\n        /**\n         * The arguments passed to be passed to the tool call request\n         */\n        arguments?: object;\n        /**\n         * The name of the tool to be called\n         */\n        name?: string;\n      }[];\n    }\n  | string\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_AsyncResponse;\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast {\n  inputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output;\n}\nexport interface Ai_Cf_Meta_Llama_Guard_3_8B_Input {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender must alternate between 'user' and 'assistant'.\n     */\n    role: \"user\" | \"assistant\";\n    /**\n     * The content of the message as a string.\n     */\n    content: string;\n  }[];\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Dictate the output format of the generated response.\n   */\n  response_format?: {\n    /**\n     * Set to json_object to process and output generated text as JSON.\n     */\n    type?: string;\n  };\n}\nexport interface Ai_Cf_Meta_Llama_Guard_3_8B_Output {\n  response?:\n    | string\n    | {\n        /**\n         * Whether the conversation is safe or not.\n         */\n        safe?: boolean;\n        /**\n         * A list of what hazard categories predicted for the conversation, if the conversation is deemed unsafe.\n         */\n        categories?: string[];\n      };\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n}\nexport declare abstract class Base_Ai_Cf_Meta_Llama_Guard_3_8B {\n  inputs: Ai_Cf_Meta_Llama_Guard_3_8B_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_Guard_3_8B_Output;\n}\nexport interface Ai_Cf_Baai_Bge_Reranker_Base_Input {\n  /**\n   * A query you wish to perform against the provided contexts.\n   */\n  /**\n   * Number of returned results starting with the best score.\n   */\n  top_k?: number;\n  /**\n   * List of provided contexts. Note that the index in this array is important, as the response will refer to it.\n   */\n  contexts: {\n    /**\n     * One of the provided context content\n     */\n    text?: string;\n  }[];\n}\nexport interface Ai_Cf_Baai_Bge_Reranker_Base_Output {\n  response?: {\n    /**\n     * Index of the context in the request\n     */\n    id?: number;\n    /**\n     * Score of the context under the index.\n     */\n    score?: number;\n  }[];\n}\nexport declare abstract class Base_Ai_Cf_Baai_Bge_Reranker_Base {\n  inputs: Ai_Cf_Baai_Bge_Reranker_Base_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Reranker_Base_Output;\n}\nexport type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input =\n  | Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Prompt\n  | Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Messages;\nexport interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    /**\n     * The content of the message as a string.\n     */\n    content: string;\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\nexport declare abstract class Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct {\n  inputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output;\n}\nexport type Ai_Cf_Qwen_Qwq_32B_Input = Ai_Cf_Qwen_Qwq_32B_Prompt | Ai_Cf_Qwen_Qwq_32B_Messages;\nexport interface Ai_Cf_Qwen_Qwq_32B_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwq_32B_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport type Ai_Cf_Qwen_Qwq_32B_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\nexport declare abstract class Base_Ai_Cf_Qwen_Qwq_32B {\n  inputs: Ai_Cf_Qwen_Qwq_32B_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwq_32B_Output;\n}\nexport type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input =\n  | Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Prompt\n  | Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Messages;\nexport interface Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\nexport declare abstract class Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct {\n  inputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output;\n}\nexport type Ai_Cf_Google_Gemma_3_12B_It_Input =\n  | Ai_Cf_Google_Gemma_3_12B_It_Prompt\n  | Ai_Cf_Google_Gemma_3_12B_It_Messages;\nexport interface Ai_Cf_Google_Gemma_3_12B_It_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Google_Gemma_3_12B_It_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport type Ai_Cf_Google_Gemma_3_12B_It_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\nexport declare abstract class Base_Ai_Cf_Google_Gemma_3_12B_It {\n  inputs: Ai_Cf_Google_Gemma_3_12B_It_Input;\n  postProcessedOutputs: Ai_Cf_Google_Gemma_3_12B_It_Output;\n}\nexport type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input =\n  | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt\n  | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages\n  | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Async_Batch;\nexport interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Async_Batch {\n  requests: (\n    | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt_Inner\n    | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages_Inner\n  )[];\n}\nexport interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt_Inner {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages_Inner {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The tool call id.\n     */\n    id?: string;\n    /**\n     * Specifies the type of tool (e.g., 'function').\n     */\n    type?: string;\n    /**\n     * Details of the function tool.\n     */\n    function?: {\n      /**\n       * The name of the tool to be called\n       */\n      name?: string;\n      /**\n       * The arguments passed to be passed to the tool call request\n       */\n      arguments?: object;\n    };\n  }[];\n};\nexport declare abstract class Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct {\n  inputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output;\n}\nexport type Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Input =\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Async_Batch;\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Async_Batch {\n  requests: (Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt_1 | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages_1)[];\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt_1 {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_2;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_2 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages_1 {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_3;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_3 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport type Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Output =\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Chat_Completion_Response\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Text_Completion_Response\n  | string\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_AsyncResponse;\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Chat_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"chat.completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index?: number;\n    /**\n     * The message generated by the model\n     */\n    message?: {\n      /**\n       * Role of the message author\n       */\n      role: string;\n      /**\n       * The content of the message\n       */\n      content: string;\n      /**\n       * Internal reasoning content (if available)\n       */\n      reasoning_content?: string;\n      /**\n       * Tool calls made by the assistant\n       */\n      tool_calls?: {\n        /**\n         * Unique identifier for the tool call\n         */\n        id: string;\n        /**\n         * Type of tool call\n         */\n        type: \"function\";\n        function: {\n          /**\n           * Name of the function to call\n           */\n          name: string;\n          /**\n           * JSON string of arguments for the function\n           */\n          arguments: string;\n        };\n      }[];\n    };\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason?: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * Log probabilities for the prompt (if requested)\n   */\n  prompt_logprobs?: {} | null;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Text_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"text_completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index: number;\n    /**\n     * The generated text completion\n     */\n    text: string;\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n    /**\n     * Log probabilities for the prompt (if requested)\n     */\n    prompt_logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8 {\n  inputs: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Output;\n}\nexport interface Ai_Cf_Deepgram_Nova_3_Input {\n  audio: {\n    body: object;\n    contentType: string;\n  };\n  /**\n   * Sets how the model will interpret strings submitted to the custom_topic param. When strict, the model will only return topics submitted using the custom_topic param. When extended, the model will return its own detected topics in addition to those submitted using the custom_topic param.\n   */\n  custom_topic_mode?: \"extended\" | \"strict\";\n  /**\n   * Custom topics you want the model to detect within your input audio or text if present Submit up to 100\n   */\n  custom_topic?: string;\n  /**\n   * Sets how the model will interpret intents submitted to the custom_intent param. When strict, the model will only return intents submitted using the custom_intent param. When extended, the model will return its own detected intents in addition those submitted using the custom_intents param\n   */\n  custom_intent_mode?: \"extended\" | \"strict\";\n  /**\n   * Custom intents you want the model to detect within your input audio if present\n   */\n  custom_intent?: string;\n  /**\n   * Identifies and extracts key entities from content in submitted audio\n   */\n  detect_entities?: boolean;\n  /**\n   * Identifies the dominant language spoken in submitted audio\n   */\n  detect_language?: boolean;\n  /**\n   * Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0\n   */\n  diarize?: boolean;\n  /**\n   * Identify and extract key entities from content in submitted audio\n   */\n  dictation?: boolean;\n  /**\n   * Specify the expected encoding of your submitted audio\n   */\n  encoding?: \"linear16\" | \"flac\" | \"mulaw\" | \"amr-nb\" | \"amr-wb\" | \"opus\" | \"speex\" | \"g729\";\n  /**\n   * Arbitrary key-value pairs that are attached to the API response for usage in downstream processing\n   */\n  extra?: string;\n  /**\n   * Filler Words can help transcribe interruptions in your audio, like 'uh' and 'um'\n   */\n  filler_words?: boolean;\n  /**\n   * Key term prompting can boost or suppress specialized terminology and brands.\n   */\n  keyterm?: string;\n  /**\n   * Keywords can boost or suppress specialized terminology and brands.\n   */\n  keywords?: string;\n  /**\n   * The BCP-47 language tag that hints at the primary spoken language. Depending on the Model and API endpoint you choose only certain languages are available.\n   */\n  language?: string;\n  /**\n   * Spoken measurements will be converted to their corresponding abbreviations.\n   */\n  measurements?: boolean;\n  /**\n   * Opts out requests from the Deepgram Model Improvement Program. Refer to our Docs for pricing impacts before setting this to true. https://dpgr.am/deepgram-mip.\n   */\n  mip_opt_out?: boolean;\n  /**\n   * Mode of operation for the model representing broad area of topic that will be talked about in the supplied audio\n   */\n  mode?: \"general\" | \"medical\" | \"finance\";\n  /**\n   * Transcribe each audio channel independently.\n   */\n  multichannel?: boolean;\n  /**\n   * Numerals converts numbers from written format to numerical format.\n   */\n  numerals?: boolean;\n  /**\n   * Splits audio into paragraphs to improve transcript readability.\n   */\n  paragraphs?: boolean;\n  /**\n   * Profanity Filter looks for recognized profanity and converts it to the nearest recognized non-profane word or removes it from the transcript completely.\n   */\n  profanity_filter?: boolean;\n  /**\n   * Add punctuation and capitalization to the transcript.\n   */\n  punctuate?: boolean;\n  /**\n   * Redaction removes sensitive information from your transcripts.\n   */\n  redact?: string;\n  /**\n   * Search for terms or phrases in submitted audio and replaces them.\n   */\n  replace?: string;\n  /**\n   * Search for terms or phrases in submitted audio.\n   */\n  search?: string;\n  /**\n   * Recognizes the sentiment throughout a transcript or text.\n   */\n  sentiment?: boolean;\n  /**\n   * Apply formatting to transcript output. When set to true, additional formatting will be applied to transcripts to improve readability.\n   */\n  smart_format?: boolean;\n  /**\n   * Detect topics throughout a transcript or text.\n   */\n  topics?: boolean;\n  /**\n   * Segments speech into meaningful semantic units.\n   */\n  utterances?: boolean;\n  /**\n   * Seconds to wait before detecting a pause between words in submitted audio.\n   */\n  utt_split?: number;\n  /**\n   * The number of channels in the submitted audio\n   */\n  channels?: number;\n  /**\n   * Specifies whether the streaming endpoint should provide ongoing transcription updates as more audio is received. When set to true, the endpoint sends continuous updates, meaning transcription results may evolve over time. Note: Supported only for webosockets.\n   */\n  interim_results?: boolean;\n  /**\n   * Indicates how long model will wait to detect whether a speaker has finished speaking or pauses for a significant period of time. When set to a value, the streaming endpoint immediately finalizes the transcription for the processed time range and returns the transcript with a speech_final parameter set to true. Can also be set to false to disable endpointing\n   */\n  endpointing?: string;\n  /**\n   * Indicates that speech has started. You'll begin receiving Speech Started messages upon speech starting. Note: Supported only for webosockets.\n   */\n  vad_events?: boolean;\n  /**\n   * Indicates how long model will wait to send an UtteranceEnd message after a word has been transcribed. Use with interim_results. Note: Supported only for webosockets.\n   */\n  utterance_end_ms?: boolean;\n}\nexport interface Ai_Cf_Deepgram_Nova_3_Output {\n  results?: {\n    channels?: {\n      alternatives?: {\n        confidence?: number;\n        transcript?: string;\n        words?: {\n          confidence?: number;\n          end?: number;\n          start?: number;\n          word?: string;\n        }[];\n      }[];\n    }[];\n    summary?: {\n      result?: string;\n      short?: string;\n    };\n    sentiments?: {\n      segments?: {\n        text?: string;\n        start_word?: number;\n        end_word?: number;\n        sentiment?: string;\n        sentiment_score?: number;\n      }[];\n      average?: {\n        sentiment?: string;\n        sentiment_score?: number;\n      };\n    };\n  };\n}\nexport declare abstract class Base_Ai_Cf_Deepgram_Nova_3 {\n  inputs: Ai_Cf_Deepgram_Nova_3_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Nova_3_Output;\n}\nexport interface Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Input {\n  queries?: string | string[];\n  /**\n   * Optional instruction for the task\n   */\n  instruction?: string;\n  documents?: string | string[];\n  text?: string | string[];\n}\nexport interface Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Output {\n  data?: number[][];\n  shape?: number[];\n}\nexport declare abstract class Base_Ai_Cf_Qwen_Qwen3_Embedding_0_6B {\n  inputs: Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Output;\n}\nexport type Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Input =\n  | {\n      /**\n       * readable stream with audio data and content-type specified for that data\n       */\n      audio: {\n        body: object;\n        contentType: string;\n      };\n      /**\n       * type of data PCM data that's sent to the inference server as raw array\n       */\n      dtype?: \"uint8\" | \"float32\" | \"float64\";\n    }\n  | {\n      /**\n       * base64 encoded audio data\n       */\n      audio: string;\n      /**\n       * type of data PCM data that's sent to the inference server as raw array\n       */\n      dtype?: \"uint8\" | \"float32\" | \"float64\";\n    };\nexport interface Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Output {\n  /**\n   * if true, end-of-turn was detected\n   */\n  is_complete?: boolean;\n  /**\n   * probability of the end-of-turn detection\n   */\n  probability?: number;\n}\nexport declare abstract class Base_Ai_Cf_Pipecat_Ai_Smart_Turn_V2 {\n  inputs: Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Input;\n  postProcessedOutputs: Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Output;\n}\nexport declare abstract class Base_Ai_Cf_Openai_Gpt_Oss_120B {\n  inputs: XOR<ResponsesInput, ChatCompletionsInput>;\n  postProcessedOutputs: XOR<ResponsesOutput, ChatCompletionsOutput>;\n}\nexport declare abstract class Base_Ai_Cf_Openai_Gpt_Oss_20B {\n  inputs: XOR<ResponsesInput, ChatCompletionsInput>;\n  postProcessedOutputs: XOR<ResponsesOutput, ChatCompletionsOutput>;\n}\nexport interface Ai_Cf_Leonardo_Phoenix_1_0_Input {\n  /**\n   * A text description of the image you want to generate.\n   */\n  prompt: string;\n  /**\n   * Controls how closely the generated image should adhere to the prompt; higher values make the image more aligned with the prompt\n   */\n  guidance?: number;\n  /**\n   * Random seed for reproducibility of the image generation\n   */\n  seed?: number;\n  /**\n   * The height of the generated image in pixels\n   */\n  height?: number;\n  /**\n   * The width of the generated image in pixels\n   */\n  width?: number;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer\n   */\n  num_steps?: number;\n  /**\n   * Specify what to exclude from the generated images\n   */\n  negative_prompt?: string;\n}\n/**\n * The generated image in JPEG format\n */\nexport type Ai_Cf_Leonardo_Phoenix_1_0_Output = string;\nexport declare abstract class Base_Ai_Cf_Leonardo_Phoenix_1_0 {\n  inputs: Ai_Cf_Leonardo_Phoenix_1_0_Input;\n  postProcessedOutputs: Ai_Cf_Leonardo_Phoenix_1_0_Output;\n}\nexport interface Ai_Cf_Leonardo_Lucid_Origin_Input {\n  /**\n   * A text description of the image you want to generate.\n   */\n  prompt: string;\n  /**\n   * Controls how closely the generated image should adhere to the prompt; higher values make the image more aligned with the prompt\n   */\n  guidance?: number;\n  /**\n   * Random seed for reproducibility of the image generation\n   */\n  seed?: number;\n  /**\n   * The height of the generated image in pixels\n   */\n  height?: number;\n  /**\n   * The width of the generated image in pixels\n   */\n  width?: number;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer\n   */\n  num_steps?: number;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer\n   */\n  steps?: number;\n}\nexport interface Ai_Cf_Leonardo_Lucid_Origin_Output {\n  /**\n   * The generated image in Base64 format.\n   */\n  image?: string;\n}\nexport declare abstract class Base_Ai_Cf_Leonardo_Lucid_Origin {\n  inputs: Ai_Cf_Leonardo_Lucid_Origin_Input;\n  postProcessedOutputs: Ai_Cf_Leonardo_Lucid_Origin_Output;\n}\nexport interface Ai_Cf_Deepgram_Aura_1_Input {\n  /**\n   * Speaker used to produce the audio.\n   */\n  speaker?:\n    | \"angus\"\n    | \"asteria\"\n    | \"arcas\"\n    | \"orion\"\n    | \"orpheus\"\n    | \"athena\"\n    | \"luna\"\n    | \"zeus\"\n    | \"perseus\"\n    | \"helios\"\n    | \"hera\"\n    | \"stella\";\n  /**\n   * Encoding of the output audio.\n   */\n  encoding?: \"linear16\" | \"flac\" | \"mulaw\" | \"alaw\" | \"mp3\" | \"opus\" | \"aac\";\n  /**\n   * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type..\n   */\n  container?: \"none\" | \"wav\" | \"ogg\";\n  /**\n   * The text content to be converted to speech\n   */\n  text: string;\n  /**\n   * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable\n   */\n  sample_rate?: number;\n  /**\n   * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type.\n   */\n  bit_rate?: number;\n}\n/**\n * The generated audio in MP3 format\n */\nexport type Ai_Cf_Deepgram_Aura_1_Output = string;\nexport declare abstract class Base_Ai_Cf_Deepgram_Aura_1 {\n  inputs: Ai_Cf_Deepgram_Aura_1_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Aura_1_Output;\n}\nexport interface Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Input {\n  /**\n   * Input text to translate. Can be a single string or a list of strings.\n   */\n  text: string | string[];\n  /**\n   * Target langauge to translate to\n   */\n  target_language:\n    | \"asm_Beng\"\n    | \"awa_Deva\"\n    | \"ben_Beng\"\n    | \"bho_Deva\"\n    | \"brx_Deva\"\n    | \"doi_Deva\"\n    | \"eng_Latn\"\n    | \"gom_Deva\"\n    | \"gon_Deva\"\n    | \"guj_Gujr\"\n    | \"hin_Deva\"\n    | \"hne_Deva\"\n    | \"kan_Knda\"\n    | \"kas_Arab\"\n    | \"kas_Deva\"\n    | \"kha_Latn\"\n    | \"lus_Latn\"\n    | \"mag_Deva\"\n    | \"mai_Deva\"\n    | \"mal_Mlym\"\n    | \"mar_Deva\"\n    | \"mni_Beng\"\n    | \"mni_Mtei\"\n    | \"npi_Deva\"\n    | \"ory_Orya\"\n    | \"pan_Guru\"\n    | \"san_Deva\"\n    | \"sat_Olck\"\n    | \"snd_Arab\"\n    | \"snd_Deva\"\n    | \"tam_Taml\"\n    | \"tel_Telu\"\n    | \"urd_Arab\"\n    | \"unr_Deva\";\n}\nexport interface Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Output {\n  /**\n   * Translated texts\n   */\n  translations: string[];\n}\nexport declare abstract class Base_Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B {\n  inputs: Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Input;\n  postProcessedOutputs: Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Output;\n}\nexport type Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Input =\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Async_Batch;\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Async_Batch {\n  requests: (\n    | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt_1\n    | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages_1\n  )[];\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt_1 {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_2;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_2 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages_1 {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_3;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_3 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport type Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Output =\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Chat_Completion_Response\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Text_Completion_Response\n  | string\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_AsyncResponse;\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Chat_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"chat.completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index?: number;\n    /**\n     * The message generated by the model\n     */\n    message?: {\n      /**\n       * Role of the message author\n       */\n      role: string;\n      /**\n       * The content of the message\n       */\n      content: string;\n      /**\n       * Internal reasoning content (if available)\n       */\n      reasoning_content?: string;\n      /**\n       * Tool calls made by the assistant\n       */\n      tool_calls?: {\n        /**\n         * Unique identifier for the tool call\n         */\n        id: string;\n        /**\n         * Type of tool call\n         */\n        type: \"function\";\n        function: {\n          /**\n           * Name of the function to call\n           */\n          name: string;\n          /**\n           * JSON string of arguments for the function\n           */\n          arguments: string;\n        };\n      }[];\n    };\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason?: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * Log probabilities for the prompt (if requested)\n   */\n  prompt_logprobs?: {} | null;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Text_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"text_completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index: number;\n    /**\n     * The generated text completion\n     */\n    text: string;\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n    /**\n     * Log probabilities for the prompt (if requested)\n     */\n    prompt_logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It {\n  inputs: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Input;\n  postProcessedOutputs: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Output;\n}\nexport interface Ai_Cf_Pfnet_Plamo_Embedding_1B_Input {\n  /**\n   * Input text to embed. Can be a single string or a list of strings.\n   */\n  text: string | string[];\n}\nexport interface Ai_Cf_Pfnet_Plamo_Embedding_1B_Output {\n  /**\n   * Embedding vectors, where each vector is a list of floats.\n   */\n  data: number[][];\n  /**\n   * Shape of the embedding data as [number_of_embeddings, embedding_dimension].\n   *\n   * @minItems 2\n   * @maxItems 2\n   */\n  shape: [number, number];\n}\nexport declare abstract class Base_Ai_Cf_Pfnet_Plamo_Embedding_1B {\n  inputs: Ai_Cf_Pfnet_Plamo_Embedding_1B_Input;\n  postProcessedOutputs: Ai_Cf_Pfnet_Plamo_Embedding_1B_Output;\n}\nexport interface Ai_Cf_Deepgram_Flux_Input {\n  /**\n   * Encoding of the audio stream. Currently only supports raw signed little-endian 16-bit PCM.\n   */\n  encoding: \"linear16\";\n  /**\n   * Sample rate of the audio stream in Hz.\n   */\n  sample_rate: string;\n  /**\n   * End-of-turn confidence required to fire an eager end-of-turn event. When set, enables EagerEndOfTurn and TurnResumed events. Valid Values 0.3 - 0.9.\n   */\n  eager_eot_threshold?: string;\n  /**\n   * End-of-turn confidence required to finish a turn. Valid Values 0.5 - 0.9.\n   */\n  eot_threshold?: string;\n  /**\n   * A turn will be finished when this much time has passed after speech, regardless of EOT confidence.\n   */\n  eot_timeout_ms?: string;\n  /**\n   * Keyterm prompting can improve recognition of specialized terminology. Pass multiple keyterm query parameters to boost multiple keyterms.\n   */\n  keyterm?: string;\n  /**\n   * Opts out requests from the Deepgram Model Improvement Program. Refer to Deepgram Docs for pricing impacts before setting this to true. https://dpgr.am/deepgram-mip\n   */\n  mip_opt_out?: \"true\" | \"false\";\n  /**\n   * Label your requests for the purpose of identification during usage reporting\n   */\n  tag?: string;\n}\n/**\n * Output will be returned as websocket messages.\n */\nexport interface Ai_Cf_Deepgram_Flux_Output {\n  /**\n   * The unique identifier of the request (uuid)\n   */\n  request_id?: string;\n  /**\n   * Starts at 0 and increments for each message the server sends to the client.\n   */\n  sequence_id?: number;\n  /**\n   * The type of event being reported.\n   */\n  event?: \"Update\" | \"StartOfTurn\" | \"EagerEndOfTurn\" | \"TurnResumed\" | \"EndOfTurn\";\n  /**\n   * The index of the current turn\n   */\n  turn_index?: number;\n  /**\n   * Start time in seconds of the audio range that was transcribed\n   */\n  audio_window_start?: number;\n  /**\n   * End time in seconds of the audio range that was transcribed\n   */\n  audio_window_end?: number;\n  /**\n   * Text that was said over the course of the current turn\n   */\n  transcript?: string;\n  /**\n   * The words in the transcript\n   */\n  words?: {\n    /**\n     * The individual punctuated, properly-cased word from the transcript\n     */\n    word: string;\n    /**\n     * Confidence that this word was transcribed correctly\n     */\n    confidence: number;\n  }[];\n  /**\n   * Confidence that no more speech is coming in this turn\n   */\n  end_of_turn_confidence?: number;\n}\nexport declare abstract class Base_Ai_Cf_Deepgram_Flux {\n  inputs: Ai_Cf_Deepgram_Flux_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Flux_Output;\n}\nexport interface Ai_Cf_Deepgram_Aura_2_En_Input {\n  /**\n   * Speaker used to produce the audio.\n   */\n  speaker?:\n    | \"amalthea\"\n    | \"andromeda\"\n    | \"apollo\"\n    | \"arcas\"\n    | \"aries\"\n    | \"asteria\"\n    | \"athena\"\n    | \"atlas\"\n    | \"aurora\"\n    | \"callista\"\n    | \"cora\"\n    | \"cordelia\"\n    | \"delia\"\n    | \"draco\"\n    | \"electra\"\n    | \"harmonia\"\n    | \"helena\"\n    | \"hera\"\n    | \"hermes\"\n    | \"hyperion\"\n    | \"iris\"\n    | \"janus\"\n    | \"juno\"\n    | \"jupiter\"\n    | \"luna\"\n    | \"mars\"\n    | \"minerva\"\n    | \"neptune\"\n    | \"odysseus\"\n    | \"ophelia\"\n    | \"orion\"\n    | \"orpheus\"\n    | \"pandora\"\n    | \"phoebe\"\n    | \"pluto\"\n    | \"saturn\"\n    | \"thalia\"\n    | \"theia\"\n    | \"vesta\"\n    | \"zeus\";\n  /**\n   * Encoding of the output audio.\n   */\n  encoding?: \"linear16\" | \"flac\" | \"mulaw\" | \"alaw\" | \"mp3\" | \"opus\" | \"aac\";\n  /**\n   * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type..\n   */\n  container?: \"none\" | \"wav\" | \"ogg\";\n  /**\n   * The text content to be converted to speech\n   */\n  text: string;\n  /**\n   * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable\n   */\n  sample_rate?: number;\n  /**\n   * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type.\n   */\n  bit_rate?: number;\n}\n/**\n * The generated audio in MP3 format\n */\nexport type Ai_Cf_Deepgram_Aura_2_En_Output = string;\nexport declare abstract class Base_Ai_Cf_Deepgram_Aura_2_En {\n  inputs: Ai_Cf_Deepgram_Aura_2_En_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Aura_2_En_Output;\n}\nexport interface Ai_Cf_Deepgram_Aura_2_Es_Input {\n  /**\n   * Speaker used to produce the audio.\n   */\n  speaker?:\n    | \"sirio\"\n    | \"nestor\"\n    | \"carina\"\n    | \"celeste\"\n    | \"alvaro\"\n    | \"diana\"\n    | \"aquila\"\n    | \"selena\"\n    | \"estrella\"\n    | \"javier\";\n  /**\n   * Encoding of the output audio.\n   */\n  encoding?: \"linear16\" | \"flac\" | \"mulaw\" | \"alaw\" | \"mp3\" | \"opus\" | \"aac\";\n  /**\n   * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type..\n   */\n  container?: \"none\" | \"wav\" | \"ogg\";\n  /**\n   * The text content to be converted to speech\n   */\n  text: string;\n  /**\n   * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable\n   */\n  sample_rate?: number;\n  /**\n   * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type.\n   */\n  bit_rate?: number;\n}\n/**\n * The generated audio in MP3 format\n */\nexport type Ai_Cf_Deepgram_Aura_2_Es_Output = string;\nexport declare abstract class Base_Ai_Cf_Deepgram_Aura_2_Es {\n  inputs: Ai_Cf_Deepgram_Aura_2_Es_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Aura_2_Es_Output;\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Input {\n  multipart: {\n    body?: object;\n    contentType?: string;\n  };\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Output {\n  /**\n   * Generated image as Base64 string.\n   */\n  image?: string;\n}\nexport declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Dev {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Output;\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Input {\n  multipart: {\n    body?: object;\n    contentType?: string;\n  };\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Output {\n  /**\n   * Generated image as Base64 string.\n   */\n  image?: string;\n}\nexport declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Output;\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Input {\n  multipart: {\n    body?: object;\n    contentType?: string;\n  };\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Output {\n  /**\n   * Generated image as Base64 string.\n   */\n  image?: string;\n}\nexport declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Output;\n}\nexport declare abstract class Base_Ai_Cf_Zai_Org_Glm_4_7_Flash {\n  inputs: ChatCompletionsInput;\n  postProcessedOutputs: ChatCompletionsOutput;\n}\nexport declare abstract class Base_Ai_Cf_Moonshotai_Kimi_K2_5 {\n  inputs: ChatCompletionsInput;\n  postProcessedOutputs: ChatCompletionsOutput;\n}\nexport declare abstract class Base_Ai_Cf_Nvidia_Nemotron_3_120B_A12B {\n  inputs: ChatCompletionsInput;\n  postProcessedOutputs: ChatCompletionsOutput;\n}\nexport interface AiModels {\n  \"@cf/huggingface/distilbert-sst-2-int8\": BaseAiTextClassification;\n  \"@cf/stabilityai/stable-diffusion-xl-base-1.0\": BaseAiTextToImage;\n  \"@cf/runwayml/stable-diffusion-v1-5-inpainting\": BaseAiTextToImage;\n  \"@cf/runwayml/stable-diffusion-v1-5-img2img\": BaseAiTextToImage;\n  \"@cf/lykon/dreamshaper-8-lcm\": BaseAiTextToImage;\n  \"@cf/bytedance/stable-diffusion-xl-lightning\": BaseAiTextToImage;\n  \"@cf/myshell-ai/melotts\": BaseAiTextToSpeech;\n  \"@cf/google/embeddinggemma-300m\": BaseAiTextEmbeddings;\n  \"@cf/microsoft/resnet-50\": BaseAiImageClassification;\n  \"@cf/meta/llama-2-7b-chat-int8\": BaseAiTextGeneration;\n  \"@cf/mistral/mistral-7b-instruct-v0.1\": BaseAiTextGeneration;\n  \"@cf/meta/llama-2-7b-chat-fp16\": BaseAiTextGeneration;\n  \"@hf/thebloke/llama-2-13b-chat-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/mistral-7b-instruct-v0.1-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/zephyr-7b-beta-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/openhermes-2.5-mistral-7b-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/neural-chat-7b-v3-1-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/deepseek-coder-6.7b-base-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/deepseek-coder-6.7b-instruct-awq\": BaseAiTextGeneration;\n  \"@cf/deepseek-ai/deepseek-math-7b-instruct\": BaseAiTextGeneration;\n  \"@cf/defog/sqlcoder-7b-2\": BaseAiTextGeneration;\n  \"@cf/openchat/openchat-3.5-0106\": BaseAiTextGeneration;\n  \"@cf/tiiuae/falcon-7b-instruct\": BaseAiTextGeneration;\n  \"@cf/thebloke/discolm-german-7b-v1-awq\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-0.5b-chat\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-7b-chat-awq\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-14b-chat-awq\": BaseAiTextGeneration;\n  \"@cf/tinyllama/tinyllama-1.1b-chat-v1.0\": BaseAiTextGeneration;\n  \"@cf/microsoft/phi-2\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-1.8b-chat\": BaseAiTextGeneration;\n  \"@cf/mistral/mistral-7b-instruct-v0.2-lora\": BaseAiTextGeneration;\n  \"@hf/nousresearch/hermes-2-pro-mistral-7b\": BaseAiTextGeneration;\n  \"@hf/nexusflow/starling-lm-7b-beta\": BaseAiTextGeneration;\n  \"@hf/google/gemma-7b-it\": BaseAiTextGeneration;\n  \"@cf/meta-llama/llama-2-7b-chat-hf-lora\": BaseAiTextGeneration;\n  \"@cf/google/gemma-2b-it-lora\": BaseAiTextGeneration;\n  \"@cf/google/gemma-7b-it-lora\": BaseAiTextGeneration;\n  \"@hf/mistral/mistral-7b-instruct-v0.2\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3-8b-instruct\": BaseAiTextGeneration;\n  \"@cf/fblgit/una-cybertron-7b-v2-bf16\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3-8b-instruct-awq\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.1-8b-instruct-fp8\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.1-8b-instruct-awq\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.2-3b-instruct\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.2-1b-instruct\": BaseAiTextGeneration;\n  \"@cf/deepseek-ai/deepseek-r1-distill-qwen-32b\": BaseAiTextGeneration;\n  \"@cf/ibm-granite/granite-4.0-h-micro\": BaseAiTextGeneration;\n  \"@cf/facebook/bart-large-cnn\": BaseAiSummarization;\n  \"@cf/llava-hf/llava-1.5-7b-hf\": BaseAiImageToText;\n  \"@cf/baai/bge-base-en-v1.5\": Base_Ai_Cf_Baai_Bge_Base_En_V1_5;\n  \"@cf/openai/whisper\": Base_Ai_Cf_Openai_Whisper;\n  \"@cf/meta/m2m100-1.2b\": Base_Ai_Cf_Meta_M2M100_1_2B;\n  \"@cf/baai/bge-small-en-v1.5\": Base_Ai_Cf_Baai_Bge_Small_En_V1_5;\n  \"@cf/baai/bge-large-en-v1.5\": Base_Ai_Cf_Baai_Bge_Large_En_V1_5;\n  \"@cf/unum/uform-gen2-qwen-500m\": Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M;\n  \"@cf/openai/whisper-tiny-en\": Base_Ai_Cf_Openai_Whisper_Tiny_En;\n  \"@cf/openai/whisper-large-v3-turbo\": Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo;\n  \"@cf/baai/bge-m3\": Base_Ai_Cf_Baai_Bge_M3;\n  \"@cf/black-forest-labs/flux-1-schnell\": Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell;\n  \"@cf/meta/llama-3.2-11b-vision-instruct\": Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct;\n  \"@cf/meta/llama-3.3-70b-instruct-fp8-fast\": Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast;\n  \"@cf/meta/llama-guard-3-8b\": Base_Ai_Cf_Meta_Llama_Guard_3_8B;\n  \"@cf/baai/bge-reranker-base\": Base_Ai_Cf_Baai_Bge_Reranker_Base;\n  \"@cf/qwen/qwen2.5-coder-32b-instruct\": Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct;\n  \"@cf/qwen/qwq-32b\": Base_Ai_Cf_Qwen_Qwq_32B;\n  \"@cf/mistralai/mistral-small-3.1-24b-instruct\": Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct;\n  \"@cf/google/gemma-3-12b-it\": Base_Ai_Cf_Google_Gemma_3_12B_It;\n  \"@cf/meta/llama-4-scout-17b-16e-instruct\": Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct;\n  \"@cf/qwen/qwen3-30b-a3b-fp8\": Base_Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8;\n  \"@cf/deepgram/nova-3\": Base_Ai_Cf_Deepgram_Nova_3;\n  \"@cf/qwen/qwen3-embedding-0.6b\": Base_Ai_Cf_Qwen_Qwen3_Embedding_0_6B;\n  \"@cf/pipecat-ai/smart-turn-v2\": Base_Ai_Cf_Pipecat_Ai_Smart_Turn_V2;\n  \"@cf/openai/gpt-oss-120b\": Base_Ai_Cf_Openai_Gpt_Oss_120B;\n  \"@cf/openai/gpt-oss-20b\": Base_Ai_Cf_Openai_Gpt_Oss_20B;\n  \"@cf/leonardo/phoenix-1.0\": Base_Ai_Cf_Leonardo_Phoenix_1_0;\n  \"@cf/leonardo/lucid-origin\": Base_Ai_Cf_Leonardo_Lucid_Origin;\n  \"@cf/deepgram/aura-1\": Base_Ai_Cf_Deepgram_Aura_1;\n  \"@cf/ai4bharat/indictrans2-en-indic-1B\": Base_Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B;\n  \"@cf/aisingapore/gemma-sea-lion-v4-27b-it\": Base_Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It;\n  \"@cf/pfnet/plamo-embedding-1b\": Base_Ai_Cf_Pfnet_Plamo_Embedding_1B;\n  \"@cf/deepgram/flux\": Base_Ai_Cf_Deepgram_Flux;\n  \"@cf/deepgram/aura-2-en\": Base_Ai_Cf_Deepgram_Aura_2_En;\n  \"@cf/deepgram/aura-2-es\": Base_Ai_Cf_Deepgram_Aura_2_Es;\n  \"@cf/black-forest-labs/flux-2-dev\": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Dev;\n  \"@cf/black-forest-labs/flux-2-klein-4b\": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B;\n  \"@cf/black-forest-labs/flux-2-klein-9b\": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B;\n  \"@cf/zai-org/glm-4.7-flash\": Base_Ai_Cf_Zai_Org_Glm_4_7_Flash;\n  \"@cf/moonshotai/kimi-k2.5\": Base_Ai_Cf_Moonshotai_Kimi_K2_5;\n  \"@cf/nvidia/nemotron-3-120b-a12b\": Base_Ai_Cf_Nvidia_Nemotron_3_120B_A12B;\n}\nexport type AiOptions = {\n  /**\n   * Send requests as an asynchronous batch job, only works for supported models\n   * https://developers.cloudflare.com/workers-ai/features/batch-api\n   */\n  queueRequest?: boolean;\n  /**\n   * Establish websocket connections, only works for supported models\n   */\n  websocket?: boolean;\n  /**\n   * Tag your requests to group and view them in Cloudflare dashboard.\n   *\n   * Rules:\n   * Tags must only contain letters, numbers, and the symbols: : - . / @\n   * Each tag can have maximum 50 characters.\n   * Maximum 5 tags are allowed each request.\n   * Duplicate tags will removed.\n   */\n  tags?: string[];\n  gateway?: GatewayOptions;\n  returnRawResponse?: boolean;\n  prefix?: string;\n  extraHeaders?: object;\n};\nexport type AiModelsSearchParams = {\n  author?: string;\n  hide_experimental?: boolean;\n  page?: number;\n  per_page?: number;\n  search?: string;\n  source?: number;\n  task?: string;\n};\nexport type AiModelsSearchObject = {\n  id: string;\n  source: number;\n  name: string;\n  description: string;\n  task: {\n    id: string;\n    name: string;\n    description: string;\n  };\n  tags: string[];\n  properties: {\n    property_id: string;\n    value: string;\n  }[];\n};\nexport type ChatCompletionsBase = XOR<ChatCompletionsPromptInput, ChatCompletionsMessagesInput>;\nexport type ChatCompletionsInput = XOR<\n  ChatCompletionsBase,\n  {\n    requests: ChatCompletionsBase[];\n  }\n>;\nexport interface InferenceUpstreamError extends Error {}\nexport interface AiInternalError extends Error {}\nexport type AiModelListType = Record<string, any>;\nexport declare abstract class Ai<AiModelList extends AiModelListType = AiModels> {\n  aiGatewayLogId: string | null;\n  gateway(gatewayId: string): AiGateway;\n\n  /**\n   * Access the AI Search API for managing AI-powered search instances.\n   *\n   * This is the new API that replaces AutoRAG with better namespace separation:\n   * - Account-level operations: `list()`, `create()`\n   * - Instance-level operations: `get(id).search()`, `get(id).chatCompletions()`, `get(id).delete()`\n   *\n   * @example\n   * ```typescript\n   * // List all AI Search instances\n   * const instances = await env.AI.aiSearch.list();\n   *\n   * // Search an instance\n   * const results = await env.AI.aiSearch.get('my-search').search({\n   *   messages: [{ role: 'user', content: 'What is the policy?' }],\n   *   ai_search_options: {\n   *     retrieval: { max_num_results: 10 }\n   *   }\n   * });\n   *\n   * // Generate chat completions with AI Search context\n   * const response = await env.AI.aiSearch.get('my-search').chatCompletions({\n   *   messages: [{ role: 'user', content: 'What is the policy?' }],\n   *   model: '@cf/meta/llama-3.3-70b-instruct-fp8-fast'\n   * });\n   * ```\n   */\n  aiSearch(): AiSearchAccountService;\n\n  /**\n   * @deprecated AutoRAG has been replaced by AI Search.\n   * Use `env.AI.aiSearch` instead for better API design and new features.\n   *\n   * Migration guide:\n   * - `env.AI.autorag().list()` → `env.AI.aiSearch.list()`\n   * - `env.AI.autorag('id').search({ query: '...' })` → `env.AI.aiSearch.get('id').search({ messages: [{ role: 'user', content: '...' }] })`\n   * - `env.AI.autorag('id').aiSearch(...)` → `env.AI.aiSearch.get('id').chatCompletions(...)`\n   *\n   * Note: The old API continues to work for backwards compatibility, but new projects should use AI Search.\n   *\n   * @see AiSearchAccountService\n   * @param autoragId Optional instance ID (omit for account-level operations)\n   */\n  autorag(autoragId: string): AutoRAG;\n  run<Name extends keyof AiModelList, Options extends AiOptions, InputOptions extends AiModelList[Name][\"inputs\"]>(\n    model: Name,\n    inputs: InputOptions,\n    options?: Options,\n  ): Promise<\n    Options extends\n      | {\n          returnRawResponse: true;\n        }\n      | {\n          websocket: true;\n        }\n      ? Response\n      : InputOptions extends {\n            stream: true;\n          }\n        ? ReadableStream\n        : AiModelList[Name][\"postProcessedOutputs\"]\n  >;\n  models(params?: AiModelsSearchParams): Promise<AiModelsSearchObject[]>;\n  toMarkdown(): ToMarkdownService;\n  toMarkdown(\n    files: MarkdownDocument[],\n    options?: ConversionRequestOptions,\n  ): Promise<ConversionResponse[]>;\n  toMarkdown(\n    files: MarkdownDocument,\n    options?: ConversionRequestOptions,\n  ): Promise<ConversionResponse>;\n}\n"
  },
  {
    "path": "types/defines/aig.d.ts",
    "content": "type GatewayRetries = {\n  maxAttempts?: 1 | 2 | 3 | 4 | 5;\n  retryDelayMs?: number;\n  backoff?: 'constant' | 'linear' | 'exponential';\n};\n\nexport type GatewayOptions = {\n  id: string;\n  cacheKey?: string;\n  cacheTtl?: number;\n  skipCache?: boolean;\n  metadata?: Record<string, number | string | boolean | null | bigint>;\n  collectLog?: boolean;\n  eventId?: string;\n  requestTimeoutMs?: number;\n  retries?: GatewayRetries;\n};\n\nexport type UniversalGatewayOptions = Exclude<GatewayOptions, 'id'> & {\n  /**\n   ** @deprecated\n   */\n  id?: string;\n};\n\nexport type AiGatewayPatchLog = {\n  score?: number | null;\n  feedback?: -1 | 1 | null;\n  metadata?: Record<string, number | string | boolean | null | bigint> | null;\n};\n\nexport type AiGatewayLog = {\n  id: string;\n  provider: string;\n  model: string;\n  model_type?: string;\n  path: string;\n  duration: number;\n  request_type?: string;\n  request_content_type?: string;\n  status_code: number;\n  response_content_type?: string;\n  success: boolean;\n  cached: boolean;\n  tokens_in?: number;\n  tokens_out?: number;\n  metadata?: Record<string, number | string | boolean | null | bigint>;\n  step?: number;\n  cost?: number;\n  custom_cost?: boolean;\n  request_size: number;\n  request_head?: string;\n  request_head_complete: boolean;\n  response_size: number;\n  response_head?: string;\n  response_head_complete: boolean;\n  created_at: Date;\n};\n\nexport type AIGatewayProviders =\n  | 'workers-ai'\n  | 'anthropic'\n  | 'aws-bedrock'\n  | 'azure-openai'\n  | 'google-vertex-ai'\n  | 'huggingface'\n  | 'openai'\n  | 'perplexity-ai'\n  | 'replicate'\n  | 'groq'\n  | 'cohere'\n  | 'google-ai-studio'\n  | 'mistral'\n  | 'grok'\n  | 'openrouter'\n  | 'deepseek'\n  | 'cerebras'\n  | 'cartesia'\n  | 'elevenlabs'\n  | 'adobe-firefly';\n\nexport type AIGatewayHeaders = {\n  'cf-aig-metadata':\n    | Record<string, number | string | boolean | null | bigint>\n    | string;\n  'cf-aig-custom-cost':\n    | { per_token_in?: number; per_token_out?: number }\n    | { total_cost?: number }\n    | string;\n  'cf-aig-cache-ttl': number | string;\n  'cf-aig-skip-cache': boolean | string;\n  'cf-aig-cache-key': string;\n  'cf-aig-event-id': string;\n  'cf-aig-request-timeout': number | string;\n  'cf-aig-max-attempts': number | string;\n  'cf-aig-retry-delay': number | string;\n  'cf-aig-backoff': string;\n  'cf-aig-collect-log': boolean | string;\n  Authorization: string;\n  'Content-Type': string;\n  [key: string]: string | number | boolean | object;\n};\n\nexport type AIGatewayUniversalRequest = {\n  provider: AIGatewayProviders | string; // eslint-disable-line\n  endpoint: string;\n  headers: Partial<AIGatewayHeaders>;\n  query: unknown;\n};\n\nexport interface AiGatewayInternalError extends Error {}\nexport interface AiGatewayLogNotFound extends Error {}\n\nexport declare abstract class AiGateway {\n  patchLog(logId: string, data: AiGatewayPatchLog): Promise<void>;\n  getLog(logId: string): Promise<AiGatewayLog>;\n  run(\n    data: AIGatewayUniversalRequest | AIGatewayUniversalRequest[],\n    options?: { gateway?: UniversalGatewayOptions; extraHeaders?: object }\n  ): Promise<Response>;\n  getUrl(provider?: AIGatewayProviders | string): Promise<string>; // eslint-disable-line\n}\n"
  },
  {
    "path": "types/defines/autorag.d.ts",
    "content": "/**\n * @deprecated AutoRAG has been replaced by AI Search. Use AiSearchInternalError instead.\n * @see AiSearchInternalError\n */\nexport interface AutoRAGInternalError extends Error {}\n\n/**\n * @deprecated AutoRAG has been replaced by AI Search. Use AiSearchNotFoundError instead.\n * @see AiSearchNotFoundError\n */\nexport interface AutoRAGNotFoundError extends Error {}\n\n/**\n * @deprecated This error type is no longer used in the AI Search API.\n */\nexport interface AutoRAGUnauthorizedError extends Error {}\n\n/**\n * @deprecated AutoRAG has been replaced by AI Search. Use AiSearchNameNotSetError instead.\n * @see AiSearchNameNotSetError\n */\nexport interface AutoRAGNameNotSetError extends Error {}\n\nexport type ComparisonFilter = {\n  key: string;\n  type: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte';\n  value: string | number | boolean;\n};\n\nexport type CompoundFilter = {\n  type: 'and' | 'or';\n  filters: ComparisonFilter[];\n};\n\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchSearchRequest with the new API instead.\n * @see AiSearchSearchRequest\n */\nexport type AutoRagSearchRequest = {\n  query: string;\n  filters?: CompoundFilter | ComparisonFilter;\n  max_num_results?: number;\n  ranking_options?: {\n    ranker?: string;\n    score_threshold?: number;\n  };\n  reranking?: {\n    enabled?: boolean;\n    model?: string;\n  };\n  rewrite_query?: boolean;\n};\n\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchChatCompletionsRequest with the new API instead.\n * @see AiSearchChatCompletionsRequest\n */\nexport type AutoRagAiSearchRequest = AutoRagSearchRequest & {\n  stream?: boolean;\n  system_prompt?: string;\n};\n\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchChatCompletionsRequest with stream: true instead.\n * @see AiSearchChatCompletionsRequest\n */\nexport type AutoRagAiSearchRequestStreaming = Omit<\n  AutoRagAiSearchRequest,\n  'stream'\n> & {\n  stream: true;\n};\n\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchSearchResponse with the new API instead.\n * @see AiSearchSearchResponse\n */\nexport type AutoRagSearchResponse = {\n  object: 'vector_store.search_results.page';\n  search_query: string;\n  data: {\n    file_id: string;\n    filename: string;\n    score: number;\n    attributes: Record<string, string | number | boolean | null>;\n    content: {\n      type: 'text';\n      text: string;\n    }[];\n  }[];\n  has_more: boolean;\n  next_page: string | null;\n};\n\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchListResponse with the new API instead.\n * @see AiSearchListResponse\n */\nexport type AutoRagListResponse = {\n  id: string;\n  enable: boolean;\n  type: string;\n  source: string;\n  vectorize_name: string;\n  paused: boolean;\n  status: string;\n}[];\n\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * The new API returns different response formats for chat completions.\n */\nexport type AutoRagAiSearchResponse = AutoRagSearchResponse & {\n  response: string;\n};\n\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use the new AI Search API instead: `env.AI.aiSearch`\n *\n * Migration guide:\n * - `env.AI.autorag().list()` → `env.AI.aiSearch.list()`\n * - `env.AI.autorag('id').search(...)` → `env.AI.aiSearch.get('id').search(...)`\n * - `env.AI.autorag('id').aiSearch(...)` → `env.AI.aiSearch.get('id').chatCompletions(...)`\n *\n * @see AiSearchAccountService\n * @see AiSearchInstanceService\n */\nexport declare abstract class AutoRAG {\n  /**\n   * @deprecated Use `env.AI.aiSearch.list()` instead.\n   * @see AiSearchAccountService.list\n   */\n  list(): Promise<AutoRagListResponse>;\n\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).search(...)` instead.\n   * Note: The new API uses a messages array instead of a query string.\n   * @see AiSearchInstanceService.search\n   */\n  search(params: AutoRagSearchRequest): Promise<AutoRagSearchResponse>;\n\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).chatCompletions(...)` instead.\n   * @see AiSearchInstanceService.chatCompletions\n   */\n  aiSearch(params: AutoRagAiSearchRequestStreaming): Promise<Response>;\n\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).chatCompletions(...)` instead.\n   * @see AiSearchInstanceService.chatCompletions\n   */\n  aiSearch(params: AutoRagAiSearchRequest): Promise<AutoRagAiSearchResponse>;\n\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).chatCompletions(...)` instead.\n   * @see AiSearchInstanceService.chatCompletions\n   */\n  aiSearch(\n    params: AutoRagAiSearchRequest\n  ): Promise<AutoRagAiSearchResponse | Response>;\n}\n"
  },
  {
    "path": "types/defines/cf.d.ts",
    "content": "interface BasicImageTransformations {\n  /**\n   * Maximum width in image pixels. The value must be an integer.\n   */\n  width?: number;\n  /**\n   * Maximum height in image pixels. The value must be an integer.\n   */\n  height?: number;\n  /**\n   * Resizing mode as a string. It affects interpretation of width and height\n   * options:\n   *  - scale-down: Similar to contain, but the image is never enlarged. If\n   *    the image is larger than given width or height, it will be resized.\n   *    Otherwise its original size will be kept.\n   *  - contain: Resizes to maximum size that fits within the given width and\n   *    height. If only a single dimension is given (e.g. only width), the\n   *    image will be shrunk or enlarged to exactly match that dimension.\n   *    Aspect ratio is always preserved.\n   *  - cover: Resizes (shrinks or enlarges) to fill the entire area of width\n   *    and height. If the image has an aspect ratio different from the ratio\n   *    of width and height, it will be cropped to fit.\n   *  - crop: The image will be shrunk and cropped to fit within the area\n   *    specified by width and height. The image will not be enlarged. For images\n   *    smaller than the given dimensions it's the same as scale-down. For\n   *    images larger than the given dimensions, it's the same as cover.\n   *    See also trim.\n   *  - pad: Resizes to the maximum size that fits within the given width and\n   *    height, and then fills the remaining area with a background color\n   *    (white by default). Use of this mode is not recommended, as the same\n   *    effect can be more efficiently achieved with the contain mode and the\n   *    CSS object-fit: contain property.\n   *  - squeeze: Stretches and deforms to the width and height given, even if it\n   *    breaks aspect ratio\n   */\n  fit?: \"scale-down\" | \"contain\" | \"cover\" | \"crop\" | \"pad\" | \"squeeze\";\n  /**\n   * Image segmentation using artificial intelligence models. Sets pixels not\n   * within selected segment area to transparent e.g \"foreground\" sets every\n   * background pixel as transparent.\n   */\n  segment?: \"foreground\";\n  /**\n   * When cropping with fit: \"cover\", this defines the side or point that should\n   * be left uncropped. The value is either a string\n   * \"left\", \"right\", \"top\", \"bottom\", \"auto\", or \"center\" (the default),\n   * or an object {x, y} containing focal point coordinates in the original\n   * image expressed as fractions ranging from 0.0 (top or left) to 1.0\n   * (bottom or right), 0.5 being the center. {fit: \"cover\", gravity: \"top\"} will\n   * crop bottom or left and right sides as necessary, but won’t crop anything\n   * from the top. {fit: \"cover\", gravity: {x:0.5, y:0.2}} will crop each side to\n   * preserve as much as possible around a point at 20% of the height of the\n   * source image.\n   */\n  gravity?:\n    | 'face'\n    | 'left'\n    | 'right'\n    | 'top'\n    | 'bottom'\n    | 'center'\n    | 'auto'\n    | 'entropy'\n    | BasicImageTransformationsGravityCoordinates;\n  /**\n   * Background color to add underneath the image. Applies only to images with\n   * transparency (such as PNG). Accepts any CSS color (#RRGGBB, rgba(…),\n   * hsl(…), etc.)\n   */\n  background?: string;\n  /**\n   * Number of degrees (90, 180, 270) to rotate the image by. width and height\n   * options refer to axes after rotation.\n   */\n  rotate?: 0 | 90 | 180 | 270 | 360;\n}\n\ninterface BasicImageTransformationsGravityCoordinates {\n  x?: number;\n  y?: number;\n  mode?: 'remainder' | 'box-center';\n}\n\n/**\n * In addition to the properties you can set in the RequestInit dict\n * that you pass as an argument to the Request constructor, you can\n * set certain properties of a `cf` object to control how Cloudflare\n * features are applied to that new Request.\n *\n * Note: Currently, these properties cannot be tested in the\n * playground.\n */\ninterface RequestInitCfProperties extends Record<string, unknown> {\n  cacheEverything?: boolean;\n  /**\n   * A request's cache key is what determines if two requests are\n   * \"the same\" for caching purposes. If a request has the same cache key\n   * as some previous request, then we can serve the same cached response for\n   * both. (e.g. 'some-key')\n   *\n   * Only available for Enterprise customers.\n   */\n  cacheKey?: string;\n  /**\n   * This allows you to append additional Cache-Tag response headers\n   * to the origin response without modifications to the origin server.\n   * This will allow for greater control over the Purge by Cache Tag feature\n   * utilizing changes only in the Workers process.\n   *\n   * Only available for Enterprise customers.\n   */\n  cacheTags?: string[];\n  /**\n   * Force response to be cached for a given number of seconds. (e.g. 300)\n   */\n  cacheTtl?: number;\n  /**\n   * Force response to be cached for a given number of seconds based on the Origin status code.\n   * (e.g. { '200-299': 86400, '404': 1, '500-599': 0 })\n   */\n  cacheTtlByStatus?: Record<string, number>;\n  scrapeShield?: boolean;\n  apps?: boolean;\n  image?: RequestInitCfPropertiesImage;\n  minify?: RequestInitCfPropertiesImageMinify;\n  mirage?: boolean;\n  polish?: \"lossy\" | \"lossless\" | \"off\";\n  r2?: RequestInitCfPropertiesR2;\n  /**\n   * Redirects the request to an alternate origin server. You can use this,\n   * for example, to implement load balancing across several origins.\n   * (e.g.us-east.example.com)\n   *\n   * Note - For security reasons, the hostname set in resolveOverride must\n   * be proxied on the same Cloudflare zone of the incoming request.\n   * Otherwise, the setting is ignored. CNAME hosts are allowed, so to\n   * resolve to a host under a different domain or a DNS only domain first\n   * declare a CNAME record within your own zone’s DNS mapping to the\n   * external hostname, set proxy on Cloudflare, then set resolveOverride\n   * to point to that CNAME record.\n   */\n  resolveOverride?: string;\n}\n\ninterface RequestInitCfPropertiesImageDraw extends BasicImageTransformations {\n  /**\n   * Absolute URL of the image file to use for the drawing. It can be any of\n   * the supported file formats. For drawing of watermarks or non-rectangular\n   * overlays we recommend using PNG or WebP images.\n   */\n  url: string;\n  /**\n   * Floating-point number between 0 (transparent) and 1 (opaque).\n   * For example, opacity: 0.5 makes overlay semitransparent.\n   */\n  opacity?: number;\n  /**\n   * - If set to true, the overlay image will be tiled to cover the entire\n   *   area. This is useful for stock-photo-like watermarks.\n   * - If set to \"x\", the overlay image will be tiled horizontally only\n   *   (form a line).\n   * - If set to \"y\", the overlay image will be tiled vertically only\n   *   (form a line).\n   */\n  repeat?: true | \"x\" | \"y\";\n  /**\n   * Position of the overlay image relative to a given edge. Each property is\n   * an offset in pixels. 0 aligns exactly to the edge. For example, left: 10\n   * positions left side of the overlay 10 pixels from the left edge of the\n   * image it's drawn over. bottom: 0 aligns bottom of the overlay with bottom\n   * of the background image.\n   *\n   * Setting both left & right, or both top & bottom is an error.\n   *\n   * If no position is specified, the image will be centered.\n   */\n  top?: number;\n  left?: number;\n  bottom?: number;\n  right?: number;\n}\n\ninterface RequestInitCfPropertiesImage extends BasicImageTransformations {\n  /**\n   * Device Pixel Ratio. Default 1. Multiplier for width/height that makes it\n   * easier to specify higher-DPI sizes in <img srcset>.\n   */\n  dpr?: number;\n  /**\n   * Allows you to trim your image. Takes dpr into account and is performed before\n   * resizing or rotation.\n   *\n   * It can be used as:\n   * - left, top, right, bottom - it will specify the number of pixels to cut\n   *   off each side\n   * - width, height - the width/height you'd like to end up with - can be used\n   *   in combination with the properties above\n   * - border - this will automatically trim the surroundings of an image based on\n   *   it's color. It consists of three properties:\n   *    - color: rgb or hex representation of the color you wish to trim (todo: verify the rgba bit)\n   *    - tolerance: difference from color to treat as color\n   *    - keep: the number of pixels of border to keep\n   */\n  trim?: \"border\" | {\n    top?: number;\n    bottom?: number;\n    left?: number;\n    right?: number;\n    width?: number;\n    height?: number;\n    border?:\n      | boolean\n      | {\n          color?: string;\n          tolerance?: number;\n          keep?: number;\n        };\n  };\n  /**\n   * Quality setting from 1-100 (useful values are in 60-90 range). Lower values\n   * make images look worse, but load faster. The default is 85. It applies only\n   * to JPEG and WebP images. It doesn’t have any effect on PNG.\n   */\n  quality?: number | \"low\" | \"medium-low\" | \"medium-high\" | \"high\";\n  /**\n   * Output format to generate. It can be:\n   *  - avif: generate images in AVIF format.\n   *  - webp: generate images in Google WebP format. Set quality to 100 to get\n   *    the WebP-lossless format.\n   *  - json: instead of generating an image, outputs information about the\n   *    image, in JSON format. The JSON object will contain image size\n   *    (before and after resizing), source image’s MIME type, file size, etc.\n   * - jpeg: generate images in JPEG format.\n   * - png: generate images in PNG format.\n   */\n  format?: \"avif\" | \"webp\" | \"json\" | \"jpeg\" | \"png\" | \"baseline-jpeg\" | \"png-force\" | \"svg\";\n  /**\n   * Whether to preserve animation frames from input files. Default is true.\n   * Setting it to false reduces animations to still images. This setting is\n   * recommended when enlarging images or processing arbitrary user content,\n   * because large GIF animations can weigh tens or even hundreds of megabytes.\n   * It is also useful to set anim:false when using format:\"json\" to get the\n   * response quicker without the number of frames.\n   */\n  anim?: boolean;\n  /**\n   * What EXIF data should be preserved in the output image. Note that EXIF\n   * rotation and embedded color profiles are always applied (\"baked in\" into\n   * the image), and aren't affected by this option. Note that if the Polish\n   * feature is enabled, all metadata may have been removed already and this\n   * option may have no effect.\n   *  - keep: Preserve most of EXIF metadata, including GPS location if there's\n   *    any.\n   *  - copyright: Only keep the copyright tag, and discard everything else.\n   *    This is the default behavior for JPEG files.\n   *  - none: Discard all invisible EXIF metadata. Currently WebP and PNG\n   *    output formats always discard metadata.\n   */\n  metadata?: \"keep\" | \"copyright\" | \"none\";\n  /**\n   * Strength of sharpening filter to apply to the image. Floating-point\n   * number between 0 (no sharpening, default) and 10 (maximum). 1.0 is a\n   * recommended value for downscaled images.\n   */\n  sharpen?: number;\n  /**\n   * Radius of a blur filter (approximate gaussian). Maximum supported radius\n   * is 250.\n   */\n  blur?: number;\n  /**\n   * Overlays are drawn in the order they appear in the array (last array\n   * entry is the topmost layer).\n   */\n  draw?: RequestInitCfPropertiesImageDraw[];\n  /**\n   * Fetching image from authenticated origin. Setting this property will\n   * pass authentication headers (Authorization, Cookie, etc.) through to\n   * the origin.\n   */\n  \"origin-auth\"?: \"share-publicly\";\n  /**\n   * Adds a border around the image. The border is added after resizing. Border\n   * width takes dpr into account, and can be specified either using a single\n   * width property, or individually for each side.\n   */\n  border?:\n    | {\n        color: string;\n        width: number;\n      }\n    | {\n        color: string;\n        top: number;\n        right: number;\n        bottom: number;\n        left: number;\n      };\n  /**\n   * Increase brightness by a factor. A value of 1.0 equals no change, a value\n   * of 0.5 equals half brightness, and a value of 2.0 equals twice as bright.\n   * 0 is ignored.\n   */\n  brightness?: number;\n  /**\n   * Increase contrast by a factor. A value of 1.0 equals no change, a value of\n   * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is\n   * ignored.\n   */\n  contrast?: number;\n  /**\n   * Increase exposure by a factor. A value of 1.0 equals no change, a value of\n   * 0.5 darkens the image, and a value of 2.0 lightens the image. 0 is ignored.\n   */\n  gamma?: number;\n\n  /**\n   * Increase contrast by a factor. A value of 1.0 equals no change, a value of\n   * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is\n   * ignored.\n   */\n  saturation?: number;\n\n  /**\n   * Flips the images horizontally, vertically, or both. Flipping is applied before\n   * rotation, so if you apply flip=h,rotate=90 then the image will be flipped\n   * horizontally, then rotated by 90 degrees.\n   */\n  flip?: 'h' | 'v' | 'hv',\n\n  /**\n   * Slightly reduces latency on a cache miss by selecting a\n   * quickest-to-compress file format, at a cost of increased file size and\n   * lower image quality. It will usually override the format option and choose\n   * JPEG over WebP or AVIF. We do not recommend using this option, except in\n   * unusual circumstances like resizing uncacheable dynamically-generated\n   * images.\n   */\n  compression?: \"fast\";\n}\n\ninterface RequestInitCfPropertiesImageMinify {\n  javascript?: boolean;\n  css?: boolean;\n  html?: boolean;\n}\n\ninterface RequestInitCfPropertiesR2 {\n  /**\n   * Colo id of bucket that an object is stored in\n   */\n  bucketColoId?: number;\n}\n\n/**\n * Request metadata provided by Cloudflare's edge.\n */\ntype IncomingRequestCfProperties<HostMetadata = unknown> =\n  IncomingRequestCfPropertiesBase &\n    IncomingRequestCfPropertiesBotManagementEnterprise &\n    IncomingRequestCfPropertiesCloudflareForSaaSEnterprise<HostMetadata> &\n    IncomingRequestCfPropertiesGeographicInformation &\n    IncomingRequestCfPropertiesCloudflareAccessOrApiShield;\n\ninterface IncomingRequestCfPropertiesBase extends Record<string, unknown> {\n  /**\n   * [ASN](https://www.iana.org/assignments/as-numbers/as-numbers.xhtml) of the incoming request.\n   *\n   * @example 395747\n   */\n  asn?: number;\n  /**\n   * The organization which owns the ASN of the incoming request.\n   *\n   * @example \"Google Cloud\"\n   */\n  asOrganization?: string;\n  /**\n   * The original value of the `Accept-Encoding` header if Cloudflare modified it.\n   *\n   * @example \"gzip, deflate, br\"\n   */\n  clientAcceptEncoding?: string;\n  /**\n   * The number of milliseconds it took for the request to reach your worker.\n   *\n   * @example 22\n   */\n  clientTcpRtt?: number;\n  /**\n   * The three-letter [IATA](https://en.wikipedia.org/wiki/IATA_airport_code)\n   * airport code of the data center that the request hit.\n   *\n   * @example \"DFW\"\n   */\n  colo: string;\n  /**\n   * Represents the upstream's response to a\n   * [TCP `keepalive` message](https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html)\n   * from cloudflare.\n   *\n   * For workers with no upstream, this will always be `1`.\n   *\n   * @example 3\n   */\n  edgeRequestKeepAliveStatus: IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus;\n  /**\n   * The HTTP Protocol the request used.\n   *\n   * @example \"HTTP/2\"\n   */\n  httpProtocol: string;\n  /**\n   * The browser-requested prioritization information in the request object.\n   *\n   * If no information was set, defaults to the empty string `\"\"`\n   *\n   * @example \"weight=192;exclusive=0;group=3;group-weight=127\"\n   * @default \"\"\n   */\n  requestPriority: string;\n  /**\n   * The TLS version of the connection to Cloudflare.\n   * In requests served over plaintext (without TLS), this property is the empty string `\"\"`.\n   *\n   * @example \"TLSv1.3\"\n   */\n  tlsVersion: string;\n  /**\n   * The cipher for the connection to Cloudflare.\n   * In requests served over plaintext (without TLS), this property is the empty string `\"\"`.\n   *\n   * @example \"AEAD-AES128-GCM-SHA256\"\n   */\n  tlsCipher: string;\n  /**\n   * Metadata containing the [`HELLO`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2) and [`FINISHED`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9) messages from this request's TLS handshake.\n   *\n   * If the incoming request was served over plaintext (without TLS) this field is undefined.\n   */\n  tlsExportedAuthenticator?: IncomingRequestCfPropertiesExportedAuthenticatorMetadata;\n}\n\ninterface IncomingRequestCfPropertiesBotManagementBase {\n  /**\n   * Cloudflare’s [level of certainty](https://developers.cloudflare.com/bots/concepts/bot-score/) that a request comes from a bot,\n   * represented as an integer percentage between `1` (almost certainly a bot) and `99` (almost certainly human).\n   *\n   * @example 54\n   */\n  score: number;\n  /**\n   * A boolean value that is true if the request comes from a good bot, like Google or Bing.\n   * Most customers choose to allow this traffic. For more details, see [Traffic from known bots](https://developers.cloudflare.com/firewall/known-issues-and-faq/#how-does-firewall-rules-handle-traffic-from-known-bots).\n   */\n  verifiedBot: boolean;\n  /**\n   * A boolean value that is true if the request originates from a\n   * Cloudflare-verified proxy service.\n   */\n  corporateProxy: boolean;\n  /**\n   * A boolean value that's true if the request matches [file extensions](https://developers.cloudflare.com/bots/reference/static-resources/) for many types of static resources.\n   */\n  staticResource: boolean;\n  /**\n   * List of IDs that correlate to the Bot Management heuristic detections made on a request (you can have multiple heuristic detections on the same request).\n   */\n  detectionIds: number[];\n}\n\ninterface IncomingRequestCfPropertiesBotManagement {\n  /**\n   * Results of Cloudflare's Bot Management analysis\n   */\n  botManagement: IncomingRequestCfPropertiesBotManagementBase;\n  /**\n   * Duplicate of `botManagement.score`.\n   *\n   * @deprecated\n   */\n  clientTrustScore: number;\n}\n\ninterface IncomingRequestCfPropertiesBotManagementEnterprise\n  extends IncomingRequestCfPropertiesBotManagement {\n  /**\n   * Results of Cloudflare's Bot Management analysis\n   */\n  botManagement: IncomingRequestCfPropertiesBotManagementBase & {\n    /**\n     * A [JA3 Fingerprint](https://developers.cloudflare.com/bots/concepts/ja3-fingerprint/) to help profile specific SSL/TLS clients\n     * across different destination IPs, Ports, and X509 certificates.\n     */\n    ja3Hash: string;\n  };\n}\n\ninterface IncomingRequestCfPropertiesCloudflareForSaaSEnterprise<HostMetadata> {\n  /**\n   * Custom metadata set per-host in [Cloudflare for SaaS](https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/).\n   *\n   * This field is only present if you have Cloudflare for SaaS enabled on your account\n   * and you have followed the [required steps to enable it]((https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/domain-support/custom-metadata/)).\n   */\n  hostMetadata?: HostMetadata;\n}\n\ninterface IncomingRequestCfPropertiesCloudflareAccessOrApiShield {\n  /**\n   * Information about the client certificate presented to Cloudflare.\n   *\n   * This is populated when the incoming request is served over TLS using\n   * either Cloudflare Access or API Shield (mTLS)\n   * and the presented SSL certificate has a valid\n   * [Certificate Serial Number](https://ldapwiki.com/wiki/Certificate%20Serial%20Number)\n   * (i.e., not `null` or `\"\"`).\n   *\n   * Otherwise, a set of placeholder values are used.\n   *\n   * The property `certPresented` will be set to `\"1\"` when\n   * the object is populated (i.e. the above conditions were met).\n   */\n  tlsClientAuth:\n    | IncomingRequestCfPropertiesTLSClientAuth\n    | IncomingRequestCfPropertiesTLSClientAuthPlaceholder;\n}\n\n/**\n * Metadata about the request's TLS handshake\n */\ninterface IncomingRequestCfPropertiesExportedAuthenticatorMetadata {\n  /**\n   * The client's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal\n   *\n   * @example \"44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d\"\n   */\n  clientHandshake: string;\n  /**\n   * The server's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal\n   *\n   * @example \"44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d\"\n   */\n  serverHandshake: string;\n  /**\n   * The client's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal\n   *\n   * @example \"084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b\"\n   */\n  clientFinished: string;\n  /**\n   * The server's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal\n   *\n   * @example \"084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b\"\n   */\n  serverFinished: string;\n}\n\n/**\n * Geographic data about the request's origin.\n */\ninterface IncomingRequestCfPropertiesGeographicInformation {\n  /**\n   * The [ISO 3166-1 Alpha 2](https://www.iso.org/iso-3166-country-codes.html) country code the request originated from.\n   *\n   * If your worker is [configured to accept TOR connections](https://support.cloudflare.com/hc/en-us/articles/203306930-Understanding-Cloudflare-Tor-support-and-Onion-Routing), this may also be `\"T1\"`, indicating a request that originated over TOR.\n   *\n   * If Cloudflare is unable to determine where the request originated this property is omitted.\n   *\n   * The country code `\"T1\"` is used for requests originating on TOR.\n   *\n   * @example \"GB\"\n   */\n  country?: Iso3166Alpha2Code | \"T1\";\n  /**\n   * If present, this property indicates that the request originated in the EU\n   *\n   * @example \"1\"\n   */\n  isEUCountry?: \"1\";\n  /**\n   * A two-letter code indicating the continent the request originated from.\n   *\n   * @example \"AN\"\n   */\n  continent?: ContinentCode;\n  /**\n   * The city the request originated from\n   *\n   * @example \"Austin\"\n   */\n  city?: string;\n  /**\n   * Postal code of the incoming request\n   *\n   * @example \"78701\"\n   */\n  postalCode?: string;\n  /**\n   * Latitude of the incoming request\n   *\n   * @example \"30.27130\"\n   */\n  latitude?: string;\n  /**\n   * Longitude of the incoming request\n   *\n   * @example \"-97.74260\"\n   */\n  longitude?: string;\n  /**\n   * Timezone of the incoming request\n   *\n   * @example \"America/Chicago\"\n   */\n  timezone?: string;\n  /**\n   * If known, the ISO 3166-2 name for the first level region associated with\n   * the IP address of the incoming request\n   *\n   * @example \"Texas\"\n   */\n  region?: string;\n  /**\n   * If known, the ISO 3166-2 code for the first-level region associated with\n   * the IP address of the incoming request\n   *\n   * @example \"TX\"\n   */\n  regionCode?: string;\n  /**\n   * Metro code (DMA) of the incoming request\n   *\n   * @example \"635\"\n   */\n  metroCode?: string;\n}\n\n/** Data about the incoming request's TLS certificate */\ninterface IncomingRequestCfPropertiesTLSClientAuth {\n  /** Always `\"1\"`, indicating that the certificate was presented */\n  certPresented: \"1\";\n  /**\n   * Result of certificate verification.\n   *\n   * @example \"FAILED:self signed certificate\"\n   */\n  certVerified: Exclude<CertVerificationStatus, \"NONE\">;\n  /** The presented certificate's revokation status.\n   *\n   * - A value of `\"1\"` indicates the certificate has been revoked\n   * - A value of `\"0\"` indicates the certificate has not been revoked\n   */\n  certRevoked: \"1\" | \"0\";\n  /**\n   * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html)\n   *\n   * @example \"CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certIssuerDN: string;\n  /**\n   * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html)\n   *\n   * @example \"CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certSubjectDN: string;\n  /**\n   * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted)\n   *\n   * @example \"CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certIssuerDNRFC2253: string;\n  /**\n   * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted)\n   *\n   * @example \"CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certSubjectDNRFC2253: string;\n  /** The certificate issuer's distinguished name (legacy policies) */\n  certIssuerDNLegacy: string;\n  /** The certificate subject's distinguished name (legacy policies) */\n  certSubjectDNLegacy: string;\n  /**\n   * The certificate's serial number\n   *\n   * @example \"00936EACBE07F201DF\"\n   */\n  certSerial: string;\n  /**\n   * The certificate issuer's serial number\n   *\n   * @example \"2489002934BDFEA34\"\n   */\n  certIssuerSerial: string;\n  /**\n   * The certificate's Subject Key Identifier\n   *\n   * @example \"BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4\"\n   */\n  certSKI: string;\n  /**\n   * The certificate issuer's Subject Key Identifier\n   *\n   * @example \"BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4\"\n   */\n  certIssuerSKI: string;\n  /**\n   * The certificate's SHA-1 fingerprint\n   *\n   * @example \"6b9109f323999e52259cda7373ff0b4d26bd232e\"\n   */\n  certFingerprintSHA1: string;\n  /**\n   * The certificate's SHA-256 fingerprint\n   *\n   * @example \"acf77cf37b4156a2708e34c4eb755f9b5dbbe5ebb55adfec8f11493438d19e6ad3f157f81fa3b98278453d5652b0c1fd1d71e5695ae4d709803a4d3f39de9dea\"\n   */\n  certFingerprintSHA256: string;\n  /**\n   * The effective starting date of the certificate\n   *\n   * @example \"Dec 22 19:39:00 2018 GMT\"\n   */\n  certNotBefore: string;\n  /**\n   * The effective expiration date of the certificate\n   *\n   * @example \"Dec 22 19:39:00 2018 GMT\"\n   */\n  certNotAfter: string;\n}\n\n/** Placeholder values for TLS Client Authorization */\ninterface IncomingRequestCfPropertiesTLSClientAuthPlaceholder {\n  certPresented: \"0\";\n  certVerified: \"NONE\";\n  certRevoked: \"0\";\n  certIssuerDN: \"\";\n  certSubjectDN: \"\";\n  certIssuerDNRFC2253: \"\";\n  certSubjectDNRFC2253: \"\";\n  certIssuerDNLegacy: \"\";\n  certSubjectDNLegacy: \"\";\n  certSerial: \"\";\n  certIssuerSerial: \"\";\n  certSKI: \"\";\n  certIssuerSKI: \"\";\n  certFingerprintSHA1: \"\";\n  certFingerprintSHA256: \"\";\n  certNotBefore: \"\";\n  certNotAfter: \"\";\n}\n\n/** Possible outcomes of TLS verification */\ndeclare type CertVerificationStatus =\n  /** Authentication succeeded */\n  | \"SUCCESS\"\n  /** No certificate was presented */\n  | \"NONE\"\n  /** Failed because the certificate was self-signed */\n  | \"FAILED:self signed certificate\"\n  /** Failed because the certificate failed a trust chain check */\n  | \"FAILED:unable to verify the first certificate\"\n  /** Failed because the certificate not yet valid */\n  | \"FAILED:certificate is not yet valid\"\n  /** Failed because the certificate is expired */\n  | \"FAILED:certificate has expired\"\n  /** Failed for another unspecified reason */\n  | \"FAILED\";\n\n/**\n * An upstream endpoint's response to a TCP `keepalive` message from Cloudflare.\n */\ndeclare type IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus =\n  | 0 /** Unknown */\n  | 1 /** no keepalives (not found) */\n  | 2 /** no connection re-use, opening keepalive connection failed */\n  | 3 /** no connection re-use, keepalive accepted and saved */\n  | 4 /** connection re-use, refused by the origin server (`TCP FIN`) */\n  | 5; /** connection re-use, accepted by the origin server */\n\n/** ISO 3166-1 Alpha-2 codes */\ndeclare type Iso3166Alpha2Code =\n  | \"AD\"\n  | \"AE\"\n  | \"AF\"\n  | \"AG\"\n  | \"AI\"\n  | \"AL\"\n  | \"AM\"\n  | \"AO\"\n  | \"AQ\"\n  | \"AR\"\n  | \"AS\"\n  | \"AT\"\n  | \"AU\"\n  | \"AW\"\n  | \"AX\"\n  | \"AZ\"\n  | \"BA\"\n  | \"BB\"\n  | \"BD\"\n  | \"BE\"\n  | \"BF\"\n  | \"BG\"\n  | \"BH\"\n  | \"BI\"\n  | \"BJ\"\n  | \"BL\"\n  | \"BM\"\n  | \"BN\"\n  | \"BO\"\n  | \"BQ\"\n  | \"BR\"\n  | \"BS\"\n  | \"BT\"\n  | \"BV\"\n  | \"BW\"\n  | \"BY\"\n  | \"BZ\"\n  | \"CA\"\n  | \"CC\"\n  | \"CD\"\n  | \"CF\"\n  | \"CG\"\n  | \"CH\"\n  | \"CI\"\n  | \"CK\"\n  | \"CL\"\n  | \"CM\"\n  | \"CN\"\n  | \"CO\"\n  | \"CR\"\n  | \"CU\"\n  | \"CV\"\n  | \"CW\"\n  | \"CX\"\n  | \"CY\"\n  | \"CZ\"\n  | \"DE\"\n  | \"DJ\"\n  | \"DK\"\n  | \"DM\"\n  | \"DO\"\n  | \"DZ\"\n  | \"EC\"\n  | \"EE\"\n  | \"EG\"\n  | \"EH\"\n  | \"ER\"\n  | \"ES\"\n  | \"ET\"\n  | \"FI\"\n  | \"FJ\"\n  | \"FK\"\n  | \"FM\"\n  | \"FO\"\n  | \"FR\"\n  | \"GA\"\n  | \"GB\"\n  | \"GD\"\n  | \"GE\"\n  | \"GF\"\n  | \"GG\"\n  | \"GH\"\n  | \"GI\"\n  | \"GL\"\n  | \"GM\"\n  | \"GN\"\n  | \"GP\"\n  | \"GQ\"\n  | \"GR\"\n  | \"GS\"\n  | \"GT\"\n  | \"GU\"\n  | \"GW\"\n  | \"GY\"\n  | \"HK\"\n  | \"HM\"\n  | \"HN\"\n  | \"HR\"\n  | \"HT\"\n  | \"HU\"\n  | \"ID\"\n  | \"IE\"\n  | \"IL\"\n  | \"IM\"\n  | \"IN\"\n  | \"IO\"\n  | \"IQ\"\n  | \"IR\"\n  | \"IS\"\n  | \"IT\"\n  | \"JE\"\n  | \"JM\"\n  | \"JO\"\n  | \"JP\"\n  | \"KE\"\n  | \"KG\"\n  | \"KH\"\n  | \"KI\"\n  | \"KM\"\n  | \"KN\"\n  | \"KP\"\n  | \"KR\"\n  | \"KW\"\n  | \"KY\"\n  | \"KZ\"\n  | \"LA\"\n  | \"LB\"\n  | \"LC\"\n  | \"LI\"\n  | \"LK\"\n  | \"LR\"\n  | \"LS\"\n  | \"LT\"\n  | \"LU\"\n  | \"LV\"\n  | \"LY\"\n  | \"MA\"\n  | \"MC\"\n  | \"MD\"\n  | \"ME\"\n  | \"MF\"\n  | \"MG\"\n  | \"MH\"\n  | \"MK\"\n  | \"ML\"\n  | \"MM\"\n  | \"MN\"\n  | \"MO\"\n  | \"MP\"\n  | \"MQ\"\n  | \"MR\"\n  | \"MS\"\n  | \"MT\"\n  | \"MU\"\n  | \"MV\"\n  | \"MW\"\n  | \"MX\"\n  | \"MY\"\n  | \"MZ\"\n  | \"NA\"\n  | \"NC\"\n  | \"NE\"\n  | \"NF\"\n  | \"NG\"\n  | \"NI\"\n  | \"NL\"\n  | \"NO\"\n  | \"NP\"\n  | \"NR\"\n  | \"NU\"\n  | \"NZ\"\n  | \"OM\"\n  | \"PA\"\n  | \"PE\"\n  | \"PF\"\n  | \"PG\"\n  | \"PH\"\n  | \"PK\"\n  | \"PL\"\n  | \"PM\"\n  | \"PN\"\n  | \"PR\"\n  | \"PS\"\n  | \"PT\"\n  | \"PW\"\n  | \"PY\"\n  | \"QA\"\n  | \"RE\"\n  | \"RO\"\n  | \"RS\"\n  | \"RU\"\n  | \"RW\"\n  | \"SA\"\n  | \"SB\"\n  | \"SC\"\n  | \"SD\"\n  | \"SE\"\n  | \"SG\"\n  | \"SH\"\n  | \"SI\"\n  | \"SJ\"\n  | \"SK\"\n  | \"SL\"\n  | \"SM\"\n  | \"SN\"\n  | \"SO\"\n  | \"SR\"\n  | \"SS\"\n  | \"ST\"\n  | \"SV\"\n  | \"SX\"\n  | \"SY\"\n  | \"SZ\"\n  | \"TC\"\n  | \"TD\"\n  | \"TF\"\n  | \"TG\"\n  | \"TH\"\n  | \"TJ\"\n  | \"TK\"\n  | \"TL\"\n  | \"TM\"\n  | \"TN\"\n  | \"TO\"\n  | \"TR\"\n  | \"TT\"\n  | \"TV\"\n  | \"TW\"\n  | \"TZ\"\n  | \"UA\"\n  | \"UG\"\n  | \"UM\"\n  | \"US\"\n  | \"UY\"\n  | \"UZ\"\n  | \"VA\"\n  | \"VC\"\n  | \"VE\"\n  | \"VG\"\n  | \"VI\"\n  | \"VN\"\n  | \"VU\"\n  | \"WF\"\n  | \"WS\"\n  | \"YE\"\n  | \"YT\"\n  | \"ZA\"\n  | \"ZM\"\n  | \"ZW\";\n\n/** The 2-letter continent codes Cloudflare uses */\ndeclare type ContinentCode = \"AF\" | \"AN\" | \"AS\" | \"EU\" | \"NA\" | \"OC\" | \"SA\";\n\ntype CfProperties<HostMetadata = unknown> =\n  | IncomingRequestCfProperties<HostMetadata>\n  | RequestInitCfProperties;\n"
  },
  {
    "path": "types/defines/d1.d.ts",
    "content": "interface D1Meta {\n  duration: number;\n  size_after: number;\n  rows_read: number;\n  rows_written: number;\n  last_row_id: number;\n  changed_db: boolean;\n  changes: number;\n\n  /**\n   * The region of the database instance that executed the query.\n   */\n  served_by_region?: string;\n\n  /**\n   * The three letters airport code of the colo that executed the query.\n   */\n  served_by_colo?: string;\n\n  /**\n   * True if-and-only-if the database instance that executed the query was the primary.\n   */\n  served_by_primary?: boolean;\n\n  timings?: {\n    /**\n     * The duration of the SQL query execution by the database instance. It doesn't include any network time.\n     */\n    sql_duration_ms: number;\n  };\n\n  /**\n   * Number of total attempts to execute the query, due to automatic retries.\n   * Note: All other fields in the response like `timings` only apply to the last attempt.\n   */\n  total_attempts?: number;\n}\n\ninterface D1Response {\n  success: true;\n  meta: D1Meta & Record<string, unknown>;\n  error?: never;\n}\n\ntype D1Result<T = unknown> = D1Response & {\n  results: T[];\n};\n\ninterface D1ExecResult {\n  count: number;\n  duration: number;\n}\n\ntype D1SessionConstraint =\n  // Indicates that the first query should go to the primary, and the rest queries\n  // using the same D1DatabaseSession will go to any replica that is consistent with\n  // the bookmark maintained by the session (returned by the first query).\n  | 'first-primary'\n  // Indicates that the first query can go anywhere (primary or replica), and the rest queries\n  // using the same D1DatabaseSession will go to any replica that is consistent with\n  // the bookmark maintained by the session (returned by the first query).\n  | 'first-unconstrained';\ntype D1SessionBookmark = string;\n\ndeclare abstract class D1Database {\n  prepare(query: string): D1PreparedStatement;\n  batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;\n  exec(query: string): Promise<D1ExecResult>;\n\n  /**\n   * Creates a new D1 Session anchored at the given constraint or the bookmark.\n   * All queries executed using the created session will have sequential consistency,\n   * meaning that all writes done through the session will be visible in subsequent reads.\n   *\n   * @param constraintOrBookmark Either the session constraint or the explicit bookmark to anchor the created session.\n   */\n  withSession(\n    constraintOrBookmark?: D1SessionBookmark | D1SessionConstraint\n  ): D1DatabaseSession;\n\n  /**\n   * @deprecated dump() will be removed soon, only applies to deprecated alpha v1 databases.\n   */\n  dump(): Promise<ArrayBuffer>;\n}\n\ndeclare abstract class D1DatabaseSession {\n  prepare(query: string): D1PreparedStatement;\n  batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;\n\n  /**\n   * @returns The latest session bookmark across all executed queries on the session.\n   *          If no query has been executed yet, `null` is returned.\n   */\n  getBookmark(): D1SessionBookmark | null;\n}\n\ndeclare abstract class D1PreparedStatement {\n  bind(...values: unknown[]): D1PreparedStatement;\n  first<T = unknown>(colName: string): Promise<T | null>;\n  first<T = Record<string, unknown>>(): Promise<T | null>;\n  run<T = Record<string, unknown>>(): Promise<D1Result<T>>;\n  all<T = Record<string, unknown>>(): Promise<D1Result<T>>;\n  raw<T = unknown[]>(options: {\n    columnNames: true;\n  }): Promise<[string[], ...T[]]>;\n  raw<T = unknown[]>(options?: { columnNames?: false }): Promise<T[]>;\n}\n"
  },
  {
    "path": "types/defines/disposable.d.ts",
    "content": "// `Disposable` was added to TypeScript's standard lib types in version 5.2.\n// To support older TypeScript versions, define an empty `Disposable` interface.\n// Users won't be able to use `using`/`Symbol.dispose` without upgrading to 5.2,\n// but this will ensure type checking on older versions still passes.\n// TypeScript's interface merging will ensure our empty interface is effectively\n// ignored when `Disposable` is included in the standard lib.\ninterface Disposable {}\n"
  },
  {
    "path": "types/defines/email.d.ts",
    "content": "/**\n * The returned data after sending an email\n */\ninterface EmailSendResult {\n  /**\n   * The Email Message ID\n   */\n  messageId: string;\n}\n\n/**\n * An email message that can be sent from a Worker.\n */\ninterface EmailMessage {\n  /**\n   * Envelope From attribute of the email message.\n   */\n  readonly from: string;\n  /**\n   * Envelope To attribute of the email message.\n   */\n  readonly to: string;\n}\n\n/**\n * An email message that is sent to a consumer Worker and can be rejected/forwarded.\n */\ninterface ForwardableEmailMessage extends EmailMessage {\n  /**\n   * Stream of the email message content.\n   */\n  readonly raw: ReadableStream<Uint8Array>;\n  /**\n   * An [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers).\n   */\n  readonly headers: Headers;\n  /**\n   * Size of the email message content.\n   */\n  readonly rawSize: number;\n  /**\n   * Reject this email message by returning a permanent SMTP error back to the connecting client including the given reason.\n   * @param reason The reject reason.\n   * @returns void\n   */\n  setReject(reason: string): void;\n  /**\n   * Forward this email message to a verified destination address of the account.\n   * @param rcptTo Verified destination address.\n   * @param headers A [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers).\n   * @returns A promise that resolves when the email message is forwarded.\n   */\n  forward(rcptTo: string, headers?: Headers): Promise<EmailSendResult>;\n  /**\n   * Reply to the sender of this email message with a new EmailMessage object.\n   * @param message The reply message.\n   * @returns A promise that resolves when the email message is replied.\n   */\n  reply(message: EmailMessage): Promise<EmailSendResult>;\n}\n\n/** A file attachment for an email message */\ntype EmailAttachment =\n\t| { disposition: 'inline'; contentId: string; filename: string; type: string; content: string | ArrayBuffer | ArrayBufferView }\n\t| { disposition: 'attachment'; contentId?: undefined; filename: string; type: string; content: string | ArrayBuffer | ArrayBufferView };\n\n/** An Email Address */\ninterface EmailAddress {\n\tname: string;\n\temail: string;\n}\n\n/**\n * A binding that allows a Worker to send email messages.\n */\ninterface SendEmail {\n  send(message: EmailMessage): Promise<EmailSendResult>;\n  send(builder: {\n\t\tfrom: string | EmailAddress;\n\t\tto: string | string[];\n\t\tsubject: string;\n\t\treplyTo?: string | EmailAddress;\n\t\tcc?: string | string[];\n\t\tbcc?: string | string[];\n\t\theaders?: Record<string, string>;\n\t\ttext?: string;\n\t\thtml?: string;\n\t\tattachments?: EmailAttachment[];\n\t}): Promise<EmailSendResult>;\n}\n\ndeclare abstract class EmailEvent extends ExtendableEvent {\n  readonly message: ForwardableEmailMessage;\n}\n\ndeclare type EmailExportedHandler<Env = unknown, Props = unknown> = (\n  message: ForwardableEmailMessage,\n  env: Env,\n  ctx: ExecutionContext<Props>\n) => void | Promise<void>;\n\ndeclare module \"cloudflare:email\" {\n  let _EmailMessage: {\n    prototype: EmailMessage;\n    new (from: string, to: string, raw: ReadableStream | string): EmailMessage;\n  };\n  export { _EmailMessage as EmailMessage };\n}\n"
  },
  {
    "path": "types/defines/hello-world.d.ts",
    "content": "/**\n * Hello World binding to serve as an explanatory example. DO NOT USE\n */\ninterface HelloWorldBinding {\n  /**\n   * Retrieve the current stored value\n   */\n  get(): Promise<{ value: string, ms?: number }>;\n  /**\n   * Set a new stored value\n   */\n  set(value: string): Promise<void>;\n}\n"
  },
  {
    "path": "types/defines/hyperdrive.d.ts",
    "content": "interface Hyperdrive {\n  /**\n   * Connect directly to Hyperdrive as if it's your database, returning a TCP socket.\n   *\n   * Calling this method returns an identical socket to if you call\n   * `connect(\"host:port\")` using the `host` and `port` fields from this object.\n   * Pick whichever approach works better with your preferred DB client library.\n   *\n   * Note that this socket is not yet authenticated -- it's expected that your\n   * code (or preferably, the client library of your choice) will authenticate\n   * using the information in this class's readonly fields.\n   */\n  connect(): Socket;\n\n  /**\n   * A valid DB connection string that can be passed straight into the typical\n   * client library/driver/ORM. This will typically be the easiest way to use\n   * Hyperdrive.\n   */\n  readonly connectionString: string;\n\n  /*\n   * A randomly generated hostname that is only valid within the context of the\n   * currently running Worker which, when passed into `connect()` function from\n   * the \"cloudflare:sockets\" module, will connect to the Hyperdrive instance\n   * for your database.\n   */\n  readonly host: string;\n  /*\n   * The port that must be paired the the host field when connecting.\n   */\n  readonly port: number;\n  /*\n   * The username to use when authenticating to your database via Hyperdrive.\n   * Unlike the host and password, this will be the same every time \n   */\n  readonly user: string;\n  /*\n   * The randomly generated password to use when authenticating to your\n   * database via Hyperdrive. Like the host field, this password is only valid\n   * within the context of the currently running Worker instance from which\n   * it's read.\n   */\n  readonly password: string;\n  /*\n   * The name of the database to connect to.\n   */\n  readonly database: string;\n}\n"
  },
  {
    "path": "types/defines/images.d.ts",
    "content": "// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\ntype ImageInfoResponse =\n  | { format: 'image/svg+xml' }\n  | {\n      format: string;\n      fileSize: number;\n      width: number;\n      height: number;\n    };\n\ntype ImageTransform = {\n  width?: number;\n  height?: number;\n  background?: string;\n  blur?: number;\n  border?:\n    | {\n        color?: string;\n        width?: number;\n      }\n    | {\n        top?: number;\n        bottom?: number;\n        left?: number;\n        right?: number;\n      };\n  brightness?: number;\n  contrast?: number;\n  fit?: 'scale-down' | 'contain' | 'pad' | 'squeeze' | 'cover' | 'crop';\n  flip?: 'h' | 'v' | 'hv';\n  gamma?: number;\n  segment?: 'foreground';\n  gravity?:\n    | 'face'\n    | 'left'\n    | 'right'\n    | 'top'\n    | 'bottom'\n    | 'center'\n    | 'auto'\n    | 'entropy'\n    | {\n        x?: number;\n        y?: number;\n        mode: 'remainder' | 'box-center';\n      };\n  rotate?: 0 | 90 | 180 | 270;\n  saturation?: number;\n  sharpen?: number;\n  trim?:\n    | 'border'\n    | {\n        top?: number;\n        bottom?: number;\n        left?: number;\n        right?: number;\n        width?: number;\n        height?: number;\n        border?:\n          | boolean\n          | {\n              color?: string;\n              tolerance?: number;\n              keep?: number;\n            };\n      };\n};\n\ntype ImageDrawOptions = {\n  opacity?: number;\n  repeat?: boolean | string;\n  top?: number;\n  left?: number;\n  bottom?: number;\n  right?: number;\n};\n\ntype ImageInputOptions = {\n  encoding?: 'base64';\n};\n\ntype ImageOutputOptions = {\n  format:\n    | 'image/jpeg'\n    | 'image/png'\n    | 'image/gif'\n    | 'image/webp'\n    | 'image/avif'\n    | 'rgb'\n    | 'rgba';\n  quality?: number;\n  background?: string;\n  anim?: boolean;\n};\n\ninterface ImageMetadata {\n  id: string;\n  filename?: string;\n  uploaded?: string;\n  requireSignedURLs: boolean;\n  meta?: Record<string, unknown>;\n  variants: string[];\n  draft?: boolean;\n  creator?: string;\n}\n\ninterface ImageUploadOptions {\n  id?: string;\n  filename?: string;\n  requireSignedURLs?: boolean;\n  metadata?: Record<string, unknown>;\n  creator?: string;\n  encoding?: 'base64';\n}\n\ninterface ImageUpdateOptions {\n  requireSignedURLs?: boolean;\n  metadata?: Record<string, unknown>;\n  creator?: string;\n}\n\ninterface ImageListOptions {\n  limit?: number;\n  cursor?: string;\n  sortOrder?: 'asc' | 'desc';\n  creator?: string;\n}\n\ninterface ImageList {\n  images: ImageMetadata[];\n  cursor?: string;\n  listComplete: boolean;\n}\n\ninterface HostedImagesBinding {\n  /**\n   * Get detailed metadata for a hosted image\n   * @param imageId The ID of the image (UUID or custom ID)\n   * @returns Image metadata, or null if not found\n   */\n  details(imageId: string): Promise<ImageMetadata | null>;\n\n  /**\n   * Get the raw image data for a hosted image\n   * @param imageId The ID of the image (UUID or custom ID)\n   * @returns ReadableStream of image bytes, or null if not found\n   */\n  image(imageId: string): Promise<ReadableStream<Uint8Array> | null>;\n\n  /**\n   * Upload a new hosted image\n   * @param image The image file to upload\n   * @param options Upload configuration\n   * @returns Metadata for the uploaded image\n   * @throws {@link ImagesError} if upload fails\n   */\n  upload(\n    image: ReadableStream<Uint8Array> | ArrayBuffer,\n    options?: ImageUploadOptions\n  ): Promise<ImageMetadata>;\n\n  /**\n   * Update hosted image metadata\n   * @param imageId The ID of the image\n   * @param options Properties to update\n   * @returns Updated image metadata\n   * @throws {@link ImagesError} if update fails\n   */\n  update(imageId: string, options: ImageUpdateOptions): Promise<ImageMetadata>;\n\n  /**\n   * Delete a hosted image\n   * @param imageId The ID of the image\n   * @returns True if deleted, false if not found\n   */\n  delete(imageId: string): Promise<boolean>;\n\n  /**\n   * List hosted images with pagination\n   * @param options List configuration\n   * @returns List of images with pagination info\n   * @throws {@link ImagesError} if list fails\n   */\n  list(options?: ImageListOptions): Promise<ImageList>;\n}\n\ninterface ImagesBinding {\n  /**\n   * Get image metadata (type, width and height)\n   * @throws {@link ImagesError} with code 9412 if input is not an image\n   * @param stream The image bytes\n   */\n  info(\n    stream: ReadableStream<Uint8Array>,\n    options?: ImageInputOptions\n  ): Promise<ImageInfoResponse>;\n  /**\n   * Begin applying a series of transformations to an image\n   * @param stream The image bytes\n   * @returns A transform handle\n   */\n  input(\n    stream: ReadableStream<Uint8Array>,\n    options?: ImageInputOptions\n  ): ImageTransformer;\n\n  /**\n   * Access hosted images CRUD operations\n   */\n  readonly hosted: HostedImagesBinding;\n}\n\ninterface ImageTransformer {\n  /**\n   * Apply transform next, returning a transform handle.\n   * You can then apply more transformations, draw, or retrieve the output.\n   * @param transform\n   */\n  transform(transform: ImageTransform): ImageTransformer;\n\n  /**\n   * Draw an image on this transformer, returning a transform handle.\n   * You can then apply more transformations, draw, or retrieve the output.\n   * @param image The image (or transformer that will give the image) to draw\n   * @param options The options configuring how to draw the image\n   */\n  draw(\n    image: ReadableStream<Uint8Array> | ImageTransformer,\n    options?: ImageDrawOptions\n  ): ImageTransformer;\n\n  /**\n   * Retrieve the image that results from applying the transforms to the\n   * provided input\n   * @param options Options that apply to the output e.g. output format\n   */\n  output(options: ImageOutputOptions): Promise<ImageTransformationResult>;\n}\n\ntype ImageTransformationOutputOptions = {\n  encoding?: 'base64';\n};\n\ninterface ImageTransformationResult {\n  /**\n   * The image as a response, ready to store in cache or return to users\n   */\n  response(): Response;\n  /**\n   * The content type of the returned image\n   */\n  contentType(): string;\n  /**\n   * The bytes of the response\n   */\n  image(options?: ImageTransformationOutputOptions): ReadableStream<Uint8Array>;\n}\n\ninterface ImagesError extends Error {\n  readonly code: number;\n  readonly message: string;\n  readonly stack?: string;\n}\n"
  },
  {
    "path": "types/defines/media.d.ts",
    "content": "/**\n * Media binding for transforming media streams.\n * Provides the entry point for media transformation operations.\n */\ninterface MediaBinding {\n\t/**\n\t * Creates a media transformer from an input stream.\n\t * @param media - The input media bytes\n\t * @returns A MediaTransformer instance for applying transformations\n\t */\n\tinput(media: ReadableStream<Uint8Array>): MediaTransformer\n}\n\n/**\n * Media transformer for applying transformation operations to media content.\n * Handles sizing, fitting, and other input transformation parameters.\n */\ninterface MediaTransformer {\n\t/**\n\t * Applies transformation options to the media content.\n\t * @param transform - Configuration for how the media should be transformed\n\t * @returns A generator for producing the transformed media output\n\t */\n\ttransform(\n\t\ttransform?: MediaTransformationInputOptions\n\t): MediaTransformationGenerator\n\n\t/**\n\t * Generates the final media output with specified options.\n\t * @param output - Configuration for the output format and parameters\n\t * @returns The final transformation result containing the transformed media\n\t */\n\toutput(\n\t\toutput?: MediaTransformationOutputOptions\n\t): MediaTransformationResult\n}\n\n/**\n * Generator for producing media transformation results.\n * Configures the output format and parameters for the transformed media.\n */\ninterface MediaTransformationGenerator {\n\t/**\n\t * Generates the final media output with specified options.\n\t * @param output - Configuration for the output format and parameters\n\t * @returns The final transformation result containing the transformed media\n\t */\n\toutput(\n\t\toutput?: MediaTransformationOutputOptions\n\t): MediaTransformationResult\n}\n\n/**\n * Result of a media transformation operation.\n * Provides multiple ways to access the transformed media content.\n */\ninterface MediaTransformationResult {\n\t/**\n\t * Returns the transformed media as a readable stream of bytes.\n\t * @returns A promise containing a readable stream with the transformed media\n\t */\n\tmedia(): Promise<ReadableStream<Uint8Array>>\n\t/**\n\t * Returns the transformed media as an HTTP response object.\n\t * @returns The transformed media as a Promise<Response>, ready to store in cache or return to users\n\t */\n\tresponse(): Promise<Response>\n\t/**\n\t * Returns the MIME type of the transformed media.\n\t * @returns A promise containing the content type string (e.g., 'image/jpeg', 'video/mp4')\n\t */\n\tcontentType(): Promise<string>\n}\n\n/**\n * Configuration options for transforming media input.\n * Controls how the media should be resized and fitted.\n */\ntype MediaTransformationInputOptions = {\n\t/** How the media should be resized to fit the specified dimensions */\n\tfit?: 'contain' | 'cover' | 'scale-down'\n\t/** Target width in pixels */\n\twidth?: number\n\t/** Target height in pixels */\n\theight?: number\n}\n\n/**\n * Configuration options for Media Transformations output.\n * Controls the format, timing, and type of the generated output.\n */\ntype MediaTransformationOutputOptions = {\n\t/**\n\t * Output mode determining the type of media to generate\n\t */\n\tmode?: 'video' | 'spritesheet' | 'frame' | 'audio'\n\t/** Whether to include audio in the output */\n\taudio?: boolean\n\t/**\n\t * Starting timestamp for frame extraction or start time for clips. (e.g. '2s').\n\t */\n\ttime?: string\n\t/**\n\t * Duration for video clips, audio extraction, and spritesheet generation (e.g. '5s').\n\t */\n\tduration?: string\n\t/**\n\t * Number of frames in the spritesheet.\n\t */\n\timageCount?: number\n\t/**\n\t * Output format for the generated media.\n\t */\n\tformat?: 'jpg' | 'png' | 'm4a'\n}\n\n/**\n * Error object for media transformation operations.\n * Extends the standard Error interface with additional media-specific information.\n */\ninterface MediaError extends Error {\n  readonly code: number;\n  readonly message: string;\n  readonly stack?: string;\n}\n\n"
  },
  {
    "path": "types/defines/node.d.ts",
    "content": "declare module 'cloudflare:node' {\n  interface NodeStyleServer {\n    listen(...args: unknown[]): this;\n    address(): { port?: number | null | undefined };\n  }\n\n  export function httpServerHandler(port: number): ExportedHandler;\n  export function httpServerHandler(options: { port: number }): ExportedHandler;\n  export function httpServerHandler(server: NodeStyleServer): ExportedHandler;\n}\n"
  },
  {
    "path": "types/defines/pages.d.ts",
    "content": "type Params<P extends string = any> = Record<P, string | string[]>;\n\ntype EventContext<Env, P extends string, Data> = {\n  request: Request<unknown, IncomingRequestCfProperties<unknown>>;\n  functionPath: string;\n  waitUntil: (promise: Promise<any>) => void;\n  passThroughOnException: () => void;\n  next: (input?: Request | string, init?: RequestInit) => Promise<Response>;\n  env: Env & { ASSETS: { fetch: typeof fetch } };\n  params: Params<P>;\n  data: Data;\n};\n\ntype PagesFunction<\n  Env = unknown,\n  Params extends string = any,\n  Data extends Record<string, unknown> = Record<string, unknown>\n> = (context: EventContext<Env, Params, Data>) => Response | Promise<Response>;\n\ntype EventPluginContext<Env, P extends string, Data, PluginArgs> = {\n  request: Request<unknown, IncomingRequestCfProperties<unknown>>;\n  functionPath: string;\n  waitUntil: (promise: Promise<any>) => void;\n  passThroughOnException: () => void;\n  next: (input?: Request | string, init?: RequestInit) => Promise<Response>;\n  env: Env & { ASSETS: { fetch: typeof fetch } };\n  params: Params<P>;\n  data: Data;\n  pluginArgs: PluginArgs;\n};\n\ntype PagesPluginFunction<\n  Env = unknown,\n  Params extends string = any,\n  Data extends Record<string, unknown> = Record<string, unknown>,\n  PluginArgs = unknown\n> = (\n  context: EventPluginContext<Env, Params, Data, PluginArgs>\n) => Response | Promise<Response>;\n\ndeclare module \"assets:*\" {\n  export const onRequest: PagesFunction;\n}\n"
  },
  {
    "path": "types/defines/pipelines.d.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\ndeclare module \"cloudflare:pipelines\" {\n  export abstract class PipelineTransformationEntrypoint<Env = unknown, I extends PipelineRecord = PipelineRecord, O extends PipelineRecord = PipelineRecord> {\n    protected env: Env;\n    protected ctx: ExecutionContext;\n    constructor(ctx: ExecutionContext, env: Env);\n\n    /**\n     * run receives an array of PipelineRecord which can be\n     * transformed and returned to the pipeline\n     * @param records Incoming records from the pipeline to be transformed\n     * @param metadata Information about the specific pipeline calling the transformation entrypoint\n     * @returns A promise containing the transformed PipelineRecord array\n     */\n    public run(records: I[], metadata: PipelineBatchMetadata): Promise<O[]>;\n  }\n  export type PipelineRecord = Record<string, unknown>\n  export type PipelineBatchMetadata = {\n    pipelineId: string;\n    pipelineName: string;\n  }\n  export interface Pipeline<T extends PipelineRecord = PipelineRecord> {\n    /**\n     * The Pipeline interface represents the type of a binding to a Pipeline\n     *\n     * @param records The records to send to the pipeline\n     */\n    send(records: T[]): Promise<void>\n  }\n}\n"
  },
  {
    "path": "types/defines/pubsub.d.ts",
    "content": "// PubSubMessage represents an incoming PubSub message.\n// The message includes metadata about the broker, the client, and the payload\n// itself.\n// https://developers.cloudflare.com/pub-sub/\ninterface PubSubMessage {\n    // Message ID\n    readonly mid: number;\n    // MQTT broker FQDN in the form mqtts://BROKER.NAMESPACE.cloudflarepubsub.com:PORT\n    readonly broker: string;\n    // The MQTT topic the message was sent on.\n    readonly topic: string;\n    // The client ID of the client that published this message.\n    readonly clientId: string;\n    // The unique identifier (JWT ID) used by the client to authenticate, if token\n    // auth was used.\n    readonly jti?: string;\n    // A Unix timestamp (seconds from Jan 1, 1970), set when the Pub/Sub Broker\n    // received the message from the client.\n    readonly receivedAt: number;\n    // An (optional) string with the MIME type of the payload, if set by the\n    // client.\n    readonly contentType: string;\n    // Set to 1 when the payload is a UTF-8 string\n    // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901063\n    readonly payloadFormatIndicator: number;\n    // Pub/Sub (MQTT) payloads can be UTF-8 strings, or byte arrays.\n    // You can use payloadFormatIndicator to inspect this before decoding.\n    payload: string | Uint8Array;\n}\n\n// JsonWebKey extended by kid parameter\ninterface JsonWebKeyWithKid extends JsonWebKey {\n    // Key Identifier of the JWK\n    readonly kid: string;\n}\n"
  },
  {
    "path": "types/defines/ratelimit.d.ts",
    "content": "interface RateLimitOptions {\n  key: string\n}\n\ninterface RateLimitOutcome {\n  success: boolean\n}\n\ninterface RateLimit {\n  /**\n   * Rate limit a request based on the provided options.\n   * @see https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/\n   * @returns A promise that resolves with the outcome of the rate limit.\n   */\n  limit(options: RateLimitOptions): Promise<RateLimitOutcome>;\n}\n"
  },
  {
    "path": "types/defines/rpc.d.ts",
    "content": "// Namespace for RPC utility types. Unfortunately, we can't use a `module` here as these types need\n// to referenced by `Fetcher`. This is included in the \"importable\" version of the types which\n// strips all `module` blocks.\ndeclare namespace Rpc {\n  // Branded types for identifying `WorkerEntrypoint`/`DurableObject`/`Target`s.\n  // TypeScript uses *structural* typing meaning anything with the same shape as type `T` is a `T`.\n  // For the classes exported by `cloudflare:workers` we want *nominal* typing (i.e. we only want to\n  // accept `WorkerEntrypoint` from `cloudflare:workers`, not any other class with the same shape)\n  export const __RPC_STUB_BRAND: '__RPC_STUB_BRAND';\n  export const __RPC_TARGET_BRAND: '__RPC_TARGET_BRAND';\n  export const __WORKER_ENTRYPOINT_BRAND: '__WORKER_ENTRYPOINT_BRAND';\n  export const __DURABLE_OBJECT_BRAND: '__DURABLE_OBJECT_BRAND';\n  export const __WORKFLOW_ENTRYPOINT_BRAND: '__WORKFLOW_ENTRYPOINT_BRAND';\n  export interface RpcTargetBranded {\n    [__RPC_TARGET_BRAND]: never;\n  }\n  export interface WorkerEntrypointBranded {\n    [__WORKER_ENTRYPOINT_BRAND]: never;\n  }\n  export interface DurableObjectBranded {\n    [__DURABLE_OBJECT_BRAND]: never;\n  }\n  export interface WorkflowEntrypointBranded {\n    [__WORKFLOW_ENTRYPOINT_BRAND]: never;\n  }\n  export type EntrypointBranded =\n    | WorkerEntrypointBranded\n    | DurableObjectBranded\n    | WorkflowEntrypointBranded;\n\n  // Types that can be used through `Stub`s\n  export type Stubable = RpcTargetBranded | ((...args: any[]) => any);\n\n  // Types that can be passed over RPC\n  // The reason for using a generic type here is to build a serializable subset of structured\n  //   cloneable composite types. This allows types defined with the \"interface\" keyword to pass the\n  //   serializable check as well. Otherwise, only types defined with the \"type\" keyword would pass.\n  type Serializable<T> =\n    // Structured cloneables\n    | BaseType\n    // Structured cloneable composites\n    | Map<\n        T extends Map<infer U, unknown> ? Serializable<U> : never,\n        T extends Map<unknown, infer U> ? Serializable<U> : never\n      >\n    | Set<T extends Set<infer U> ? Serializable<U> : never>\n    | ReadonlyArray<T extends ReadonlyArray<infer U> ? Serializable<U> : never>\n    | {\n        [K in keyof T]: K extends number | string ? Serializable<T[K]> : never;\n      }\n    // Special types\n    | Stub<Stubable>\n    // Serialized as stubs, see `Stubify`\n    | Stubable;\n\n  // Base type for all RPC stubs, including common memory management methods.\n  // `T` is used as a marker type for unwrapping `Stub`s later.\n  interface StubBase<T extends Stubable> extends Disposable {\n    [__RPC_STUB_BRAND]: T;\n    dup(): this;\n  }\n  export type Stub<T extends Stubable> = Provider<T> & StubBase<T>;\n\n  // This represents all the types that can be sent as-is over an RPC boundary\n  type BaseType =\n    | void\n    | undefined\n    | null\n    | boolean\n    | number\n    | bigint\n    | string\n    | TypedArray\n    | ArrayBuffer\n    | DataView\n    | Date\n    | Error\n    | RegExp\n    | ReadableStream<Uint8Array>\n    | WritableStream<Uint8Array>\n    | Request\n    | Response\n    | Headers;\n  // Recursively rewrite all `Stubable` types with `Stub`s\n  // prettier-ignore\n  type Stubify<T> =\n    T extends Stubable ? Stub<T>\n    : T extends Map<infer K, infer V> ? Map<Stubify<K>, Stubify<V>>\n    : T extends Set<infer V> ? Set<Stubify<V>>\n    : T extends Array<infer V> ? Array<Stubify<V>>\n    : T extends ReadonlyArray<infer V> ? ReadonlyArray<Stubify<V>>\n    : T extends BaseType ? T\n    // When using \"unknown\" instead of \"any\", interfaces are not stubified.\n    : T extends { [key: string | number]: any } ? { [K in keyof T]: Stubify<T[K]> }\n    : T;\n\n  // Recursively rewrite all `Stub<T>`s with the corresponding `T`s.\n  // Note we use `StubBase` instead of `Stub` here to avoid circular dependencies:\n  // `Stub` depends on `Provider`, which depends on `Unstubify`, which would depend on `Stub`.\n  // prettier-ignore\n  type Unstubify<T> =\n    T extends StubBase<infer V> ? V\n    : T extends Map<infer K, infer V> ? Map<Unstubify<K>, Unstubify<V>>\n    : T extends Set<infer V> ? Set<Unstubify<V>>\n    : T extends Array<infer V> ? Array<Unstubify<V>>\n    : T extends ReadonlyArray<infer V> ? ReadonlyArray<Unstubify<V>>\n    : T extends BaseType ? T\n    : T extends { [key: string | number]: unknown } ? { [K in keyof T]: Unstubify<T[K]> }\n    : T;\n  type UnstubifyAll<A extends any[]> = { [I in keyof A]: Unstubify<A[I]> };\n\n  // Utility type for adding `Provider`/`Disposable`s to `object` types only.\n  // Note `unknown & T` is equivalent to `T`.\n  type MaybeProvider<T> = T extends object ? Provider<T> : unknown;\n  type MaybeDisposable<T> = T extends object ? Disposable : unknown;\n\n  // Type for method return or property on an RPC interface.\n  // - Stubable types are replaced by stubs.\n  // - Serializable types are passed by value, with stubable types replaced by stubs\n  //   and a top-level `Disposer`.\n  // Everything else can't be passed over PRC.\n  // Technically, we use custom thenables here, but they quack like `Promise`s.\n  // Intersecting with `(Maybe)Provider` allows pipelining.\n  // prettier-ignore\n  type Result<R> =\n    R extends Stubable ? Promise<Stub<R>> & Provider<R>\n    : R extends Serializable<R> ? Promise<Stubify<R> & MaybeDisposable<R>> & MaybeProvider<R>\n    : never;\n\n  // Type for method or property on an RPC interface.\n  // For methods, unwrap `Stub`s in parameters, and rewrite returns to be `Result`s.\n  // Unwrapping `Stub`s allows calling with `Stubable` arguments.\n  // For properties, rewrite types to be `Result`s.\n  // In each case, unwrap `Promise`s.\n  type MethodOrProperty<V> = V extends (...args: infer P) => infer R\n    ? (...args: UnstubifyAll<P>) => Result<Awaited<R>>\n    : Result<Awaited<V>>;\n\n  // Type for the callable part of an `Provider` if `T` is callable.\n  // This is intersected with methods/properties.\n  type MaybeCallableProvider<T> = T extends (...args: any[]) => any\n    ? MethodOrProperty<T>\n    : unknown;\n\n  // Base type for all other types providing RPC-like interfaces.\n  // Rewrites all methods/properties to be `MethodOrProperty`s, while preserving callable types.\n  // `Reserved` names (e.g. stub method names like `dup()`) and symbols can't be accessed over RPC.\n  export type Provider<\n    T extends object,\n    Reserved extends string = never,\n  > = MaybeCallableProvider<T> & Pick<\n    {\n      [K in keyof T]: MethodOrProperty<T[K]>;\n    },\n    Exclude<\n      keyof T,\n      Reserved | symbol | keyof StubBase<never>\n    >\n  >\n}\n\ndeclare namespace Cloudflare {\n  // Type of `env`.\n  //\n  // The specific project can extend `Env` by redeclaring it in project-specific files. Typescript\n  // will merge all declarations.\n  //\n  // You can use `wrangler types` to generate the `Env` type automatically.\n  interface Env {}\n\n  // Project-specific parameters used to inform types.\n  //\n  // This interface is, again, intended to be declared in project-specific files, and then that\n  // declaration will be merged with this one.\n  //\n  // A project should have a declaration like this:\n  //\n  //     interface GlobalProps {\n  //       // Declares the main module's exports. Used to populate Cloudflare.Exports aka the type\n  //       // of `ctx.exports`.\n  //       mainModule: typeof import(\"my-main-module\");\n  //\n  //       // Declares which of the main module's exports are configured with durable storage, and\n  //       // thus should behave as Durable Object namsepace bindings.\n  //       durableNamespaces: \"MyDurableObject\" | \"AnotherDurableObject\";\n  //     }\n  //\n  // You can use `wrangler types` to generate `GlobalProps` automatically.\n  interface GlobalProps {}\n\n  // Evaluates to the type of a property in GlobalProps, defaulting to `Default` if it is not\n  // present.\n  type GlobalProp<K extends string, Default> =\n      K extends keyof GlobalProps ? GlobalProps[K] : Default;\n\n  // The type of the program's main module exports, if known. Requires `GlobalProps` to declare the\n  // `mainModule` property.\n  type MainModule = GlobalProp<\"mainModule\", {}>;\n\n  // The type of ctx.exports, which contains loopback bindings for all top-level exports.\n  type Exports = {\n    [K in keyof MainModule]:\n        & LoopbackForExport<MainModule[K]>\n\n        // If the export is listed in `durableNamespaces`, then it is also a\n        // DurableObjectNamespace.\n        & (K extends GlobalProp<\"durableNamespaces\", never>\n            ?  MainModule[K] extends new (...args: any[]) => infer DoInstance\n                ? DoInstance extends Rpc.DurableObjectBranded\n                    ? DurableObjectNamespace<DoInstance>\n                    : DurableObjectNamespace<undefined>\n                : DurableObjectNamespace<undefined>\n            : {});\n  };\n}\n\ndeclare namespace CloudflareWorkersModule {\n  export type RpcStub<T extends Rpc.Stubable> = Rpc.Stub<T>;\n  export const RpcStub: {\n    new <T extends Rpc.Stubable>(value: T): Rpc.Stub<T>;\n  };\n\n  export abstract class RpcTarget implements Rpc.RpcTargetBranded {\n    [Rpc.__RPC_TARGET_BRAND]: never;\n  }\n\n  // `protected` fields don't appear in `keyof`s, so can't be accessed over RPC\n\n  export abstract class WorkerEntrypoint<\n    Env = Cloudflare.Env,\n    Props = {},\n  > implements Rpc.WorkerEntrypointBranded\n  {\n    [Rpc.__WORKER_ENTRYPOINT_BRAND]: never;\n\n    protected ctx: ExecutionContext<Props>;\n    protected env: Env;\n    constructor(ctx: ExecutionContext, env: Env);\n\n    email?(message: ForwardableEmailMessage): void | Promise<void>;\n    fetch?(request: Request): Response | Promise<Response>;\n    queue?(batch: MessageBatch<unknown>): void | Promise<void>;\n    scheduled?(controller: ScheduledController): void | Promise<void>;\n    tail?(events: TraceItem[]): void | Promise<void>;\n    tailStream?(event: TailStream.TailEvent<TailStream.Onset>): TailStream.TailEventHandlerType | Promise<TailStream.TailEventHandlerType>;\n    test?(controller: TestController): void | Promise<void>;\n    trace?(traces: TraceItem[]): void | Promise<void>;\n  }\n\n  export abstract class DurableObject<\n    Env = Cloudflare.Env,\n    Props = {},\n  > implements Rpc.DurableObjectBranded\n  {\n    [Rpc.__DURABLE_OBJECT_BRAND]: never;\n\n    protected ctx: DurableObjectState<Props>;\n    protected env: Env;\n    constructor(ctx: DurableObjectState, env: Env);\n\n    alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise<void>;\n    fetch?(request: Request): Response | Promise<Response>;\n    webSocketMessage?(\n      ws: WebSocket,\n      message: string | ArrayBuffer\n    ): void | Promise<void>;\n    webSocketClose?(\n      ws: WebSocket,\n      code: number,\n      reason: string,\n      wasClean: boolean\n    ): void | Promise<void>;\n    webSocketError?(ws: WebSocket, error: unknown): void | Promise<void>;\n  }\n\n  export type WorkflowDurationLabel =\n    | 'second'\n    | 'minute'\n    | 'hour'\n    | 'day'\n    | 'week'\n    | 'month'\n    | 'year';\n\n  export type WorkflowSleepDuration =\n    | `${number} ${WorkflowDurationLabel}${'s' | ''}`\n    | number;\n\n  export type WorkflowDelayDuration = WorkflowSleepDuration;\n\n  export type WorkflowTimeoutDuration = WorkflowSleepDuration;\n\n  export type WorkflowRetentionDuration = WorkflowSleepDuration;\n\n  export type WorkflowBackoff = 'constant' | 'linear' | 'exponential';\n\n  export type WorkflowStepConfig = {\n    retries?: {\n      limit: number;\n      delay: WorkflowDelayDuration | number;\n      backoff?: WorkflowBackoff;\n    };\n    timeout?: WorkflowTimeoutDuration | number;\n  };\n\n  export type WorkflowEvent<T> = {\n    payload: Readonly<T>;\n    timestamp: Date;\n    instanceId: string;\n  };\n\n  export type WorkflowStepEvent<T> = {\n    payload: Readonly<T>;\n    timestamp: Date;\n    type: string;\n  };\n\n  export type WorkflowStepContext = {\n    attempt: number;\n  };\n\n  export abstract class WorkflowStep {\n    do<T extends Rpc.Serializable<T>>(\n      name: string,\n      callback: (ctx: WorkflowStepContext) => Promise<T>\n    ): Promise<T>;\n    do<T extends Rpc.Serializable<T>>(\n      name: string,\n      config: WorkflowStepConfig,\n      callback: (ctx: WorkflowStepContext) => Promise<T>\n    ): Promise<T>;\n    sleep: (name: string, duration: WorkflowSleepDuration) => Promise<void>;\n    sleepUntil: (name: string, timestamp: Date | number) => Promise<void>;\n    waitForEvent<T extends Rpc.Serializable<T>>(\n      name: string,\n      options: {\n        type: string;\n        timeout?: WorkflowTimeoutDuration | number;\n      }\n    ): Promise<WorkflowStepEvent<T>>;\n  }\n\n  export type WorkflowInstanceStatus =\n    | 'queued'\n    | 'running'\n    | 'paused'\n    | 'errored'\n    | 'terminated'\n    | 'complete'\n    | 'waiting'\n    | 'waitingForPause'\n    | 'unknown';\n\n  export abstract class WorkflowEntrypoint<\n    Env = unknown,\n    T extends Rpc.Serializable<T> | unknown = unknown,\n  > implements Rpc.WorkflowEntrypointBranded\n  {\n    [Rpc.__WORKFLOW_ENTRYPOINT_BRAND]: never;\n\n    protected ctx: ExecutionContext;\n    protected env: Env;\n\n    constructor(ctx: ExecutionContext, env: Env);\n\n    run(\n      event: Readonly<WorkflowEvent<T>>,\n      step: WorkflowStep\n    ): Promise<unknown>;\n  }\n\n  export function waitUntil(promise: Promise<unknown>): void;\n\n  export function withEnv(newEnv: unknown, fn: () => unknown): unknown;\n  export function withExports(\n    newExports: unknown,\n    fn: () => unknown\n  ): unknown;\n  export function withEnvAndExports(\n    newEnv: unknown,\n    newExports: unknown,\n    fn: () => unknown\n  ): unknown;\n\n  export const env: Cloudflare.Env;\n  export const exports: Cloudflare.Exports;\n}\n\ndeclare module 'cloudflare:workers' {\n  export = CloudflareWorkersModule;\n}\n"
  },
  {
    "path": "types/defines/secrets-store.d.ts",
    "content": "interface SecretsStoreSecret {\n  /**\n   * Get a secret from the Secrets Store, returning a string of the secret value\n   * if it exists, or throws an error if it does not exist\n   */\n  get(): Promise<string>;\n}\n"
  },
  {
    "path": "types/defines/sockets.d.ts",
    "content": "declare module \"cloudflare:sockets\" {\n  function _connect(address: string | SocketAddress, options?: SocketOptions): Socket;\n  export { _connect as connect };\n}\n"
  },
  {
    "path": "types/defines/stream.d.ts",
    "content": "/**\n * Binding entrypoint for Cloudflare Stream.\n *\n * Usage:\n * - Binding-level operations:\n *   `await env.STREAM.videos.upload`\n *   `await env.STREAM.videos.createDirectUpload`\n *   `await env.STREAM.videos.*`\n *   `await env.STREAM.watermarks.*`\n * - Per-video operations:\n *   `await env.STREAM.video(id).downloads.*`\n *   `await env.STREAM.video(id).captions.*`\n *\n * Example usage:\n * ```ts\n * await env.STREAM.video(id).downloads.generate();\n *\n * const video = env.STREAM.video(id)\n * const captions = video.captions.list();\n * const videoDetails = video.details()\n * ```\n */\ninterface StreamBinding {\n  /**\n   * Returns a handle scoped to a single video for per-video operations.\n   * @param id The unique identifier for the video.\n   * @returns A handle for per-video operations.\n   */\n  video(id: string): StreamVideoHandle;\n  /**\n   * Uploads a new video from a provided URL.\n   * @param url The URL to upload from.\n   * @param params Optional upload parameters.\n   * @returns The uploaded video details.\n   * @throws {BadRequestError} if the upload parameter is invalid or the URL is invalid\n   * @throws {QuotaReachedError} if the account storage capacity is exceeded\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {RateLimitedError} if the server received too many requests\n   * @throws {AlreadyUploadedError} if a video was already uploaded to this URL\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  upload(url: string, params?: StreamUrlUploadParams): Promise<StreamVideo>;\n  /**\n   * Creates a direct upload that allows video uploads without an API key.\n   * @param params Parameters for the direct upload\n   * @returns The direct upload details.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {RateLimitedError} if the server received too many requests\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  createDirectUpload(\n    params: StreamDirectUploadCreateParams\n  ): Promise<StreamDirectUpload>;\n\n  videos: StreamVideos;\n  watermarks: StreamWatermarks;\n}\n\n/**\n * Handle for operations scoped to a single Stream video.\n */\ninterface StreamVideoHandle {\n  /**\n   * The unique identifier for the video.\n   */\n  id: string;\n  /**\n   * Get a full videos details\n   * @returns The full video details.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  details(): Promise<StreamVideo>;\n  /**\n   * Update details for a single video.\n   * @param params The fields to update for the video.\n   * @returns The updated video details.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  update(params: StreamUpdateVideoParams): Promise<StreamVideo>;\n  /**\n   * Deletes a video and its copies from Cloudflare Stream.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(): Promise<void>;\n  /**\n   * Creates a signed URL token for a video.\n   * @returns The signed token that was created.\n   * @throws {InternalError} if the signing key cannot be retrieved or the token cannot be signed\n   */\n  generateToken(): Promise<string>;\n\n  downloads: StreamScopedDownloads;\n  captions: StreamScopedCaptions;\n}\n\ninterface StreamVideo {\n  /**\n   * The unique identifier for the video.\n   */\n  id: string;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator: string | null;\n  /**\n   * The thumbnail URL for the video.\n   */\n  thumbnail: string;\n  /**\n   * The thumbnail timestamp percentage.\n   */\n  thumbnailTimestampPct: number;\n  /**\n   * Indicates whether the video is ready to stream.\n   */\n  readyToStream: boolean;\n  /**\n   * The date and time the video became ready to stream.\n   */\n  readyToStreamAt: string | null;\n  /**\n   * Processing status information.\n   */\n  status: StreamVideoStatus;\n  /**\n   * A user modifiable key-value store.\n   */\n  meta: Record<string, string>;\n  /**\n   * The date and time the video was created.\n   */\n  created: string;\n  /**\n   * The date and time the video was last modified.\n   */\n  modified: string;\n  /**\n   * The date and time at which the video will be deleted.\n   */\n  scheduledDeletion: string | null;\n  /**\n   * The size of the video in bytes.\n   */\n  size: number;\n  /**\n   * The preview URL for the video.\n   */\n  preview?: string;\n  /**\n   * Origins allowed to display the video.\n   */\n  allowedOrigins: Array<string>;\n  /**\n   * Indicates whether signed URLs are required.\n   */\n  requireSignedURLs: boolean | null;\n  /**\n   * The date and time the video was uploaded.\n   */\n  uploaded: string | null;\n  /**\n   * The date and time when the upload URL expires.\n   */\n  uploadExpiry: string | null;\n  /**\n   * The maximum size in bytes for direct uploads.\n   */\n  maxSizeBytes: number | null;\n  /**\n   * The maximum duration in seconds for direct uploads.\n   */\n  maxDurationSeconds: number | null;\n  /**\n   * The video duration in seconds. -1 indicates unknown.\n   */\n  duration: number;\n  /**\n   * Input metadata for the original upload.\n   */\n  input: StreamVideoInput;\n  /**\n   * Playback URLs for the video.\n   */\n  hlsPlaybackUrl: string;\n  dashPlaybackUrl: string;\n  /**\n   * The watermark applied to the video, if any.\n   */\n  watermark: StreamWatermark | null;\n  /**\n   * The live input id associated with the video, if any.\n   */\n  liveInputId?: string | null;\n  /**\n   * The source video id if this is a clip.\n   */\n  clippedFromId: string | null;\n  /**\n   * Public details associated with the video.\n   */\n  publicDetails: StreamPublicDetails | null;\n}\n\ntype StreamVideoStatus = {\n  /**\n   * The current processing state.\n   */\n  state: string;\n  /**\n   * The current processing step.\n   */\n  step?: string;\n  /**\n   * The percent complete as a string.\n   */\n  pctComplete?: string;\n  /**\n   * An error reason code, if applicable.\n   */\n  errorReasonCode: string;\n  /**\n   * An error reason text, if applicable.\n   */\n  errorReasonText: string;\n};\n\ntype StreamVideoInput = {\n  /**\n   * The input width in pixels.\n   */\n  width: number;\n  /**\n   * The input height in pixels.\n   */\n  height: number;\n};\n\ntype StreamPublicDetails = {\n  /**\n   * The public title for the video.\n   */\n  title: string | null;\n  /**\n   * The public share link.\n   */\n  share_link: string | null;\n  /**\n   * The public channel link.\n   */\n  channel_link: string | null;\n  /**\n   * The public logo URL.\n   */\n  logo: string | null;\n};\n\ntype StreamDirectUpload = {\n  /**\n   * The URL an unauthenticated upload can use for a single multipart request.\n   */\n  uploadURL: string;\n  /**\n   * A Cloudflare-generated unique identifier for a media item.\n   */\n  id: string;\n  /**\n   * The watermark profile applied to the upload.\n   */\n  watermark: StreamWatermark | null;\n  /**\n   * The scheduled deletion time, if any.\n   */\n  scheduledDeletion: string | null;\n};\n\ntype StreamDirectUploadCreateParams = {\n  /**\n   * The maximum duration in seconds for a video upload.\n   */\n  maxDurationSeconds: number;\n  /**\n   * The date and time after upload when videos will not be accepted.\n   */\n  expiry?: string;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator?: string;\n  /**\n   * A user modifiable key-value store used to reference other systems of record for\n   * managing videos.\n   */\n  meta?: Record<string, string>;\n  /**\n   * Lists the origins allowed to display the video.\n   */\n  allowedOrigins?: Array<string>;\n  /**\n   * Indicates whether the video can be accessed using the id. When set to `true`,\n   * a signed token must be generated with a signing key to view the video.\n   */\n  requireSignedURLs?: boolean;\n  /**\n   * The thumbnail timestamp percentage.\n   */\n  thumbnailTimestampPct?: number;\n  /**\n   * The date and time at which the video will be deleted. Include `null` to remove\n   * a scheduled deletion.\n   */\n  scheduledDeletion?: string | null;\n  /**\n   * The watermark profile to apply.\n   */\n  watermark?: StreamDirectUploadWatermark;\n};\n\ntype StreamDirectUploadWatermark = {\n  /**\n   * The unique identifier for the watermark profile.\n   */\n  id: string;\n};\n\ntype StreamUrlUploadParams = {\n  /**\n   * Lists the origins allowed to display the video. Enter allowed origin\n   * domains in an array and use `*` for wildcard subdomains. Empty arrays allow the\n   * video to be viewed on any origin.\n   */\n  allowedOrigins?: Array<string>;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator?: string;\n  /**\n   * A user modifiable key-value store used to reference other systems of\n   * record for managing videos.\n   */\n  meta?: Record<string, string>;\n  /**\n   * Indicates whether the video can be a accessed using the id. When\n   * set to `true`, a signed token must be generated with a signing key to view the\n   * video.\n   */\n  requireSignedURLs?: boolean;\n  /**\n   * Indicates the date and time at which the video will be deleted. Omit\n   * the field to indicate no change, or include with a `null` value to remove an\n   * existing scheduled deletion. If specified, must be at least 30 days from upload\n   * time.\n   */\n  scheduledDeletion?: string | null;\n  /**\n   * The timestamp for a thumbnail image calculated as a percentage value\n   * of the video's duration. To convert from a second-wise timestamp to a\n   * percentage, divide the desired timestamp by the total duration of the video. If\n   * this value is not set, the default thumbnail image is taken from 0s of the\n   * video.\n   */\n  thumbnailTimestampPct?: number;\n  /**\n   * The identifier for the watermark profile\n   */\n  watermarkId?: string;\n};\n\ninterface StreamScopedCaptions {\n  /**\n   * Uploads the caption or subtitle file to the endpoint for a specific BCP47 language.\n   * One caption or subtitle file per language is allowed.\n   * @param language The BCP 47 language tag for the caption or subtitle.\n   * @param file The caption or subtitle file to upload.\n   * @returns The created caption entry.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the language or file is invalid\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  upload(language: string, file: File): Promise<StreamCaption>;\n  /**\n   * Generate captions or subtitles for the provided language via AI.\n   * @param language The BCP 47 language tag to generate.\n   * @returns The generated caption entry.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the language is invalid\n   * @throws {StreamError} if a generated caption already exists\n   * @throws {StreamError} if the video duration is too long\n   * @throws {StreamError} if the video is missing audio\n   * @throws {StreamError} if the requested language is not supported\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(language: string): Promise<StreamCaption>;\n  /**\n   * Lists the captions or subtitles.\n   * Use the language parameter to filter by a specific language.\n   * @param language The optional BCP 47 language tag to filter by.\n   * @returns The list of captions or subtitles.\n   * @throws {NotFoundError} if the video or caption is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  list(language?: string): Promise<StreamCaption[]>;\n  /**\n   * Removes the captions or subtitles from a video.\n   * @param language The BCP 47 language tag to remove.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the video or caption is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(language: string): Promise<void>;\n}\n\ninterface StreamScopedDownloads {\n  /**\n   * Generates a download for a video when a video is ready to view. Available\n   * types are `default` and `audio`. Defaults to `default` when omitted.\n   * @param downloadType The download type to create.\n   * @returns The current downloads for the video.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the download type is invalid\n   * @throws {StreamError} if the video duration is too long to generate a download\n   * @throws {StreamError} if the video is not ready to stream\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(\n    downloadType?: StreamDownloadType\n  ): Promise<StreamDownloadGetResponse>;\n  /**\n   * Lists the downloads created for a video.\n   * @returns The current downloads for the video.\n   * @throws {NotFoundError} if the video or downloads are not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  get(): Promise<StreamDownloadGetResponse>;\n  /**\n   * Delete the downloads for a video. Available types are `default` and `audio`.\n   * Defaults to `default` when omitted.\n   * @param downloadType The download type to delete.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the video or downloads are not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(downloadType?: StreamDownloadType): Promise<void>;\n}\n\ninterface StreamVideos {\n  /**\n   * Lists all videos in a users account.\n   * @returns The list of videos.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  list(params?: StreamVideosListParams): Promise<StreamVideo[]>;\n}\n\ninterface StreamWatermarks {\n  /**\n   * Generate a new watermark profile\n   * @param file The image file to upload\n   * @param params The watermark creation parameters.\n   * @returns The created watermark profile.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InvalidURLError} if the URL is invalid\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {TooManyWatermarksError} if the number of allowed watermarks is reached\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(\n    file: File,\n    params: StreamWatermarkCreateParams\n  ): Promise<StreamWatermark>;\n  /**\n   * Generate a new watermark profile\n   * @param url The image url to upload\n   * @param params The watermark creation parameters.\n   * @returns The created watermark profile.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InvalidURLError} if the URL is invalid\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {TooManyWatermarksError} if the number of allowed watermarks is reached\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(\n    url: string,\n    params: StreamWatermarkCreateParams\n  ): Promise<StreamWatermark>;\n  /**\n   * Lists all watermark profiles for an account.\n   * @returns The list of watermark profiles.\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  list(): Promise<StreamWatermark[]>;\n  /**\n   * Retrieves details for a single watermark profile.\n   * @param watermarkId The watermark profile identifier.\n   * @returns The watermark profile details.\n   * @throws {NotFoundError} if the watermark is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  get(watermarkId: string): Promise<StreamWatermark>;\n  /**\n   * Deletes a watermark profile.\n   * @param watermarkId The watermark profile identifier.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the watermark is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(watermarkId: string): Promise<void>;\n}\n\ntype StreamUpdateVideoParams = {\n  /**\n   * Lists the origins allowed to display the video. Enter allowed origin\n   * domains in an array and use `*` for wildcard subdomains. Empty arrays allow the\n   * video to be viewed on any origin.\n   */\n  allowedOrigins?: Array<string>;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator?: string;\n  /**\n   * The maximum duration in seconds for a video upload. Can be set for a\n   * video that is not yet uploaded to limit its duration. Uploads that exceed the\n   * specified duration will fail during processing. A value of `-1` means the value\n   * is unknown.\n   */\n  maxDurationSeconds?: number;\n  /**\n   * A user modifiable key-value store used to reference other systems of\n   * record for managing videos.\n   */\n  meta?: Record<string, string>;\n  /**\n   * Indicates whether the video can be a accessed using the id. When\n   * set to `true`, a signed token must be generated with a signing key to view the\n   * video.\n   */\n  requireSignedURLs?: boolean;\n  /**\n   * Indicates the date and time at which the video will be deleted. Omit\n   * the field to indicate no change, or include with a `null` value to remove an\n   * existing scheduled deletion. If specified, must be at least 30 days from upload\n   * time.\n   */\n  scheduledDeletion?: string | null;\n  /**\n   * The timestamp for a thumbnail image calculated as a percentage value\n   * of the video's duration. To convert from a second-wise timestamp to a\n   * percentage, divide the desired timestamp by the total duration of the video. If\n   * this value is not set, the default thumbnail image is taken from 0s of the\n   * video.\n   */\n  thumbnailTimestampPct?: number;\n};\n\ntype StreamCaption = {\n  /**\n   * Whether the caption was generated via AI.\n   */\n  generated?: boolean;\n  /**\n   * The language label displayed in the native language to users.\n   */\n  label: string;\n  /**\n   * The language tag in BCP 47 format.\n   */\n  language: string;\n  /**\n   * The status of a generated caption.\n   */\n  status?: 'ready' | 'inprogress' | 'error';\n};\n\ntype StreamDownloadStatus = 'ready' | 'inprogress' | 'error';\n\ntype StreamDownloadType = 'default' | 'audio';\n\ntype StreamDownload = {\n  /**\n   * Indicates the progress as a percentage between 0 and 100.\n   */\n  percentComplete: number;\n  /**\n   * The status of a generated download.\n   */\n  status: StreamDownloadStatus;\n  /**\n   * The URL to access the generated download.\n   */\n  url?: string;\n};\n\n/**\n * An object with download type keys. Each key is optional and only present if that\n * download type has been created.\n */\ntype StreamDownloadGetResponse = {\n  /**\n   * The audio-only download. Only present if this download type has been created.\n   */\n  audio?: StreamDownload;\n  /**\n   * The default video download. Only present if this download type has been created.\n   */\n  default?: StreamDownload;\n};\n\ntype StreamWatermarkPosition =\n  | 'upperRight'\n  | 'upperLeft'\n  | 'lowerLeft'\n  | 'lowerRight'\n  | 'center';\n\ntype StreamWatermark = {\n  /**\n   * The unique identifier for a watermark profile.\n   */\n  id: string;\n  /**\n   * The size of the image in bytes.\n   */\n  size: number;\n  /**\n   * The height of the image in pixels.\n   */\n  height: number;\n  /**\n   * The width of the image in pixels.\n   */\n  width: number;\n  /**\n   * The date and a time a watermark profile was created.\n   */\n  created: string;\n  /**\n   * The source URL for a downloaded image. If the watermark profile was created via\n   * direct upload, this field is null.\n   */\n  downloadedFrom: string | null;\n  /**\n   * A short description of the watermark profile.\n   */\n  name: string;\n  /**\n   * The translucency of the image. A value of `0.0` makes the image completely\n   * transparent, and `1.0` makes the image completely opaque. Note that if the image\n   * is already semi-transparent, setting this to `1.0` will not make the image\n   * completely opaque.\n   */\n  opacity: number;\n  /**\n   * The whitespace between the adjacent edges (determined by position) of the video\n   * and the image. `0.0` indicates no padding, and `1.0` indicates a fully padded\n   * video width or length, as determined by the algorithm.\n   */\n  padding: number;\n  /**\n   * The size of the image relative to the overall size of the video. This parameter\n   * will adapt to horizontal and vertical videos automatically. `0.0` indicates no\n   * scaling (use the size of the image as-is), and `1.0 `fills the entire video.\n   */\n  scale: number;\n  /**\n   * The location of the image. Valid positions are: `upperRight`, `upperLeft`,\n   * `lowerLeft`, `lowerRight`, and `center`. Note that `center` ignores the\n   * `padding` parameter.\n   */\n  position: StreamWatermarkPosition;\n};\n\ntype StreamWatermarkCreateParams = {\n  /**\n   * A short description of the watermark profile.\n   */\n  name?: string;\n  /**\n   * The translucency of the image. A value of `0.0` makes the image completely\n   * transparent, and `1.0` makes the image completely opaque. Note that if the\n   * image is already semi-transparent, setting this to `1.0` will not make the\n   * image completely opaque.\n   */\n  opacity?: number;\n  /**\n   * The whitespace between the adjacent edges (determined by position) of the\n   * video and the image. `0.0` indicates no padding, and `1.0` indicates a fully\n   * padded video width or length, as determined by the algorithm.\n   */\n  padding?: number;\n  /**\n   * The size of the image relative to the overall size of the video. This\n   * parameter will adapt to horizontal and vertical videos automatically. `0.0`\n   * indicates no scaling (use the size of the image as-is), and `1.0 `fills the\n   * entire video.\n   */\n  scale?: number;\n  /**\n   * The location of the image.\n   */\n  position?: StreamWatermarkPosition;\n};\n\ntype StreamVideosListParams = {\n  /**\n   * The maximum number of videos to return.\n   */\n  limit?: number;\n  /**\n   * Return videos created before this timestamp.\n   * (RFC3339/RFC3339Nano)\n   */\n  before?: string;\n  /**\n   * Comparison operator for the `before` field.\n   * @default 'lt'\n   */\n  beforeComp?: StreamPaginationComparison;\n  /**\n   * Return videos created after this timestamp.\n   * (RFC3339/RFC3339Nano)\n   */\n  after?: string;\n  /**\n   * Comparison operator for the `after` field.\n   * @default 'gte'\n   */\n  afterComp?: StreamPaginationComparison;\n};\n\ntype StreamPaginationComparison = 'eq' | 'gt' | 'gte' | 'lt' | 'lte';\n\n/**\n * Error object for Stream binding operations.\n */\ninterface StreamError extends Error {\n  readonly code: number;\n  readonly statusCode: number;\n  readonly message: string;\n  readonly stack?: string;\n}\n\ninterface InternalError extends StreamError {\n  name: 'InternalError';\n}\n\ninterface BadRequestError extends StreamError {\n  name: 'BadRequestError';\n}\n\ninterface NotFoundError extends StreamError {\n  name: 'NotFoundError';\n}\n\ninterface ForbiddenError extends StreamError {\n  name: 'ForbiddenError';\n}\n\ninterface RateLimitedError extends StreamError {\n  name: 'RateLimitedError';\n}\n\ninterface QuotaReachedError extends StreamError {\n  name: 'QuotaReachedError';\n}\n\ninterface MaxFileSizeError extends StreamError {\n  name: 'MaxFileSizeError';\n}\n\ninterface InvalidURLError extends StreamError {\n  name: 'InvalidURLError';\n}\n\ninterface AlreadyUploadedError extends StreamError {\n  name: 'AlreadyUploadedError';\n}\n\ninterface TooManyWatermarksError extends StreamError {\n  name: 'TooManyWatermarksError';\n}\n"
  },
  {
    "path": "types/defines/to-markdown.d.ts",
    "content": "export type MarkdownDocument = {\n  name: string;\n  blob: Blob;\n}\n\nexport type ConversionResponse = {\n  id: string;\n  name: string;\n  mimeType: string;\n  format: 'markdown';\n  tokens: number;\n  data: string;\n} | {\n  id: string;\n  name: string;\n  mimeType: string;\n  format: 'error';\n  error: string;\n};\n\nexport type ImageConversionOptions = {\n  descriptionLanguage?: 'en' | 'es' | 'fr' | 'it' | 'pt' | 'de';\n}\n\nexport type EmbeddedImageConversionOptions = ImageConversionOptions & {\n  convert?: boolean;\n  maxConvertedImages?: number;\n};\n\nexport type ConversionOptions = {\n  html?: {\n    images?: EmbeddedImageConversionOptions & { convertOGImage?: boolean };\n    hostname?: string;\n    cssSelector?: string;\n  },\n  docx?: {\n    images?: EmbeddedImageConversionOptions;\n  },\n  image?: ImageConversionOptions;\n  pdf?: {\n    images?: EmbeddedImageConversionOptions;\n    metadata?: boolean;\n  },\n};\n\nexport type ConversionRequestOptions = {\n  gateway?: GatewayOptions;\n  extraHeaders?: object;\n  conversionOptions?: ConversionOptions;\n};\n\nexport type SupportedFileFormat = {\n  mimeType: string;\n  extension: string;\n};\n\nexport declare abstract class ToMarkdownService {\n  transform(\n    files: MarkdownDocument[],\n    options?: ConversionRequestOptions\n  ): Promise<ConversionResponse[]>;\n  transform(\n    files: MarkdownDocument,\n    options?: ConversionRequestOptions\n  ): Promise<ConversionResponse>;\n  supported(): Promise<SupportedFileFormat[]>\n}\n"
  },
  {
    "path": "types/defines/trace.d.ts",
    "content": "declare namespace TailStream {\n\ninterface Header {\n  readonly name: string;\n  readonly value: string;\n}\n\ninterface FetchEventInfo {\n  readonly type: \"fetch\";\n  readonly method: string;\n  readonly url: string;\n  readonly cfJson?: object;\n  readonly headers: Header[];\n}\n\ninterface JsRpcEventInfo {\n  readonly type: \"jsrpc\";\n}\n\ninterface ScheduledEventInfo {\n  readonly type: \"scheduled\";\n  readonly scheduledTime: Date;\n  readonly cron: string;\n}\n\ninterface AlarmEventInfo {\n  readonly type: \"alarm\";\n  readonly scheduledTime: Date;\n}\n\ninterface QueueEventInfo {\n  readonly type: \"queue\";\n  readonly queueName: string;\n  readonly batchSize: number;\n}\n\ninterface EmailEventInfo {\n  readonly type: \"email\";\n  readonly mailFrom: string;\n  readonly rcptTo: string;\n  readonly rawSize: number;\n}\n\ninterface TraceEventInfo {\n  readonly type: \"trace\";\n  readonly traces: (string | null)[];\n}\n\ninterface HibernatableWebSocketEventInfoMessage {\n  readonly type: \"message\";\n}\ninterface HibernatableWebSocketEventInfoError {\n  readonly type: \"error\";\n}\ninterface HibernatableWebSocketEventInfoClose {\n  readonly type: \"close\";\n  readonly code: number;\n  readonly wasClean: boolean;\n}\n\ninterface HibernatableWebSocketEventInfo {\n  readonly type: \"hibernatableWebSocket\";\n  readonly info: HibernatableWebSocketEventInfoClose |\n                 HibernatableWebSocketEventInfoError |\n                 HibernatableWebSocketEventInfoMessage;\n}\n\ninterface CustomEventInfo {\n  readonly type: \"custom\";\n}\n\ninterface FetchResponseInfo {\n  readonly type: \"fetch\";\n  readonly statusCode: number;\n}\n\ntype EventOutcome = \"ok\" | \"canceled\" | \"exception\" | \"unknown\" | \"killSwitch\" |\n                    \"daemonDown\" | \"exceededCpu\" | \"exceededMemory\" | \"loadShed\" |\n                    \"responseStreamDisconnected\" | \"scriptNotFound\";\n\ninterface ScriptVersion {\n  readonly id: string;\n  readonly tag?: string;\n  readonly message?: string;\n}\n\ninterface Onset {\n  readonly type: \"onset\";\n  readonly attributes: Attribute[];\n  // id for the span being opened by this Onset event.\n  readonly spanId: string;\n  readonly dispatchNamespace?: string;\n  readonly entrypoint?: string;\n  readonly executionModel: string;\n  readonly scriptName?: string;\n  readonly scriptTags?: string[];\n  readonly scriptVersion?: ScriptVersion;\n  readonly info: FetchEventInfo | JsRpcEventInfo | ScheduledEventInfo |\n                 AlarmEventInfo | QueueEventInfo | EmailEventInfo |\n                 TraceEventInfo | HibernatableWebSocketEventInfo |\n                 CustomEventInfo;\n}\n\ninterface Outcome {\n  readonly type: \"outcome\";\n  readonly outcome: EventOutcome;\n  readonly cpuTime: number;\n  readonly wallTime: number;\n}\n\ninterface SpanOpen {\n  readonly type: \"spanOpen\";\n  readonly name: string;\n  // id for the span being opened by this SpanOpen event.\n  readonly spanId: string;\n  readonly info?: FetchEventInfo | JsRpcEventInfo | Attributes;\n}\n\ninterface SpanClose {\n  readonly type: \"spanClose\";\n  readonly outcome: EventOutcome;\n}\n\ninterface DiagnosticChannelEvent {\n  readonly type: \"diagnosticChannel\";\n  readonly channel: string;\n  readonly message: any;\n}\n\ninterface Exception {\n  readonly type: \"exception\";\n  readonly name: string;\n  readonly message: string;\n  readonly stack?: string;\n}\n\ninterface Log {\n  readonly type: \"log\";\n  readonly level: \"debug\" | \"error\" | \"info\" | \"log\" | \"warn\";\n  readonly message: object;\n}\n\ninterface DroppedEventsDiagnostic {\n  readonly diagnosticsType: \"droppedEvents\";\n  readonly count: number;\n}\n\ninterface StreamDiagnostic {\n  readonly type: 'streamDiagnostic';\n  // To add new diagnostic types, define a new interface and add it to this union type.\n  readonly diagnostic: DroppedEventsDiagnostic;\n}\n\n// This marks the worker handler return information.\n// This is separate from Outcome because the worker invocation can live for a long time after\n// returning. For example - Websockets that return an http upgrade response but then continue\n// streaming information or SSE http connections.\ninterface Return {\n  readonly type: \"return\";\n  readonly info?: FetchResponseInfo;\n}\n\ninterface Attribute {\n  readonly name: string;\n  readonly value: string | string[] | boolean | boolean[] | number | number[] | bigint | bigint[];\n}\n\ninterface Attributes {\n  readonly type: \"attributes\";\n  readonly info: Attribute[];\n}\n\ntype EventType =\n  | Onset\n  | Outcome\n  | SpanOpen\n  | SpanClose\n  | DiagnosticChannelEvent\n  | Exception\n  | Log\n  | StreamDiagnostic\n  | Return\n  | Attributes;\n\n// Context in which this trace event lives.\ninterface SpanContext {\n  // Single id for the entire top-level invocation\n  // This should be a new traceId for the first worker stage invoked in the eyeball request and then\n  // same-account service-bindings should reuse the same traceId but cross-account service-bindings\n  // should use a new traceId.\n  readonly traceId: string;\n  // spanId in which this event is handled\n  // for Onset and SpanOpen events this would be the parent span id\n  // for Outcome and SpanClose these this would be the span id of the opening Onset and SpanOpen events\n  // For Hibernate and Mark this would be the span under which they were emitted.\n  // spanId is not set ONLY if:\n  //  1. This is an Onset event\n  //  2. We are not inheriting any SpanContext. (e.g. this is a cross-account service binding or a new top-level invocation)\n  readonly spanId?: string;\n}\n\ninterface TailEvent<Event extends EventType> {\n  // invocation id of the currently invoked worker stage.\n  // invocation id will always be unique to every Onset event and will be the same until the Outcome event.\n  readonly invocationId: string;\n  // Inherited spanContext for this event.\n  readonly spanContext: SpanContext;\n  readonly timestamp: Date;\n  readonly sequence: number;\n  readonly event: Event;\n}\n\ntype TailEventHandler<Event extends EventType = EventType> = (\n  event: TailEvent<Event>\n) => void | Promise<void>;\n\ntype TailEventHandlerObject = {\n  outcome?: TailEventHandler<Outcome>;\n  spanOpen?: TailEventHandler<SpanOpen>;\n  spanClose?: TailEventHandler<SpanClose>;\n  diagnosticChannel?: TailEventHandler<DiagnosticChannelEvent>;\n  exception?: TailEventHandler<Exception>;\n  log?: TailEventHandler<Log>;\n  return?: TailEventHandler<Return>;\n  attributes?: TailEventHandler<Attributes>;\n};\n\ntype TailEventHandlerType = TailEventHandler | TailEventHandlerObject;\n}\n"
  },
  {
    "path": "types/defines/vectorize.d.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n/**\n * Data types supported for holding vector metadata.\n */\ntype VectorizeVectorMetadataValue = string | number | boolean | string[];\n/**\n * Additional information to associate with a vector.\n */\ntype VectorizeVectorMetadata =\n  | VectorizeVectorMetadataValue\n  | Record<string, VectorizeVectorMetadataValue>;\n\ntype VectorFloatArray = Float32Array | Float64Array;\n\ninterface VectorizeError {\n  code?: number;\n  error: string;\n}\n\n/**\n * Comparison logic/operation to use for metadata filtering.\n *\n * This list is expected to grow as support for more operations are released.\n */\ntype VectorizeVectorMetadataFilterOp =\n  | '$eq'\n  | '$ne'\n  | '$lt'\n  | '$lte'\n  | '$gt'\n  | '$gte';\ntype VectorizeVectorMetadataFilterCollectionOp = '$in' | '$nin';\n\n/**\n * Filter criteria for vector metadata used to limit the retrieved query result set.\n */\ntype VectorizeVectorMetadataFilter = {\n  [field: string]:\n    | Exclude<VectorizeVectorMetadataValue, string[]>\n    | null\n    | {\n        [Op in VectorizeVectorMetadataFilterOp]?: Exclude<\n          VectorizeVectorMetadataValue,\n          string[]\n        > | null;\n      }\n    | {\n        [Op in VectorizeVectorMetadataFilterCollectionOp]?: Exclude<\n          VectorizeVectorMetadataValue,\n          string[]\n        >[];\n      };\n};\n\n/**\n * Supported distance metrics for an index.\n * Distance metrics determine how other \"similar\" vectors are determined.\n */\ntype VectorizeDistanceMetric = \"euclidean\" | \"cosine\" | \"dot-product\";\n\n/**\n * Metadata return levels for a Vectorize query.\n *\n * Default to \"none\".\n *\n * @property all      Full metadata for the vector return set, including all fields (including those un-indexed) without truncation. This is a more expensive retrieval, as it requires additional fetching & reading of un-indexed data.\n * @property indexed  Return all metadata fields configured for indexing in the vector return set. This level of retrieval is \"free\" in that no additional overhead is incurred returning this data. However, note that indexed metadata is subject to truncation (especially for larger strings).\n * @property none     No indexed metadata will be returned.\n */\ntype VectorizeMetadataRetrievalLevel = \"all\" | \"indexed\" | \"none\";\n\ninterface VectorizeQueryOptions {\n  topK?: number;\n  namespace?: string;\n  returnValues?: boolean;\n  returnMetadata?: boolean | VectorizeMetadataRetrievalLevel;\n  filter?: VectorizeVectorMetadataFilter;\n}\n\n/**\n * Information about the configuration of an index.\n */\ntype VectorizeIndexConfig =\n  | {\n      dimensions: number;\n      metric: VectorizeDistanceMetric;\n    }\n  | {\n      preset: string; // keep this generic, as we'll be adding more presets in the future and this is only in a read capacity\n    };\n\n/**\n * Metadata about an existing index.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link VectorizeIndexInfo} for its post-beta equivalent.\n */\ninterface VectorizeIndexDetails {\n  /** The unique ID of the index */\n  readonly id: string;\n  /** The name of the index. */\n  name: string;\n  /** (optional) A human readable description for the index. */\n  description?: string;\n  /** The index configuration, including the dimension size and distance metric. */\n  config: VectorizeIndexConfig;\n  /** The number of records containing vectors within the index. */\n  vectorsCount: number;\n}\n\n/**\n * Metadata about an existing index.\n */\ninterface VectorizeIndexInfo {\n  /** The number of records containing vectors within the index. */\n  vectorCount: number;\n  /** Number of dimensions the index has been configured for. */\n  dimensions: number;\n  /** ISO 8601 datetime of the last processed mutation on in the index. All changes before this mutation will be reflected in the index state. */\n  processedUpToDatetime: number;\n  /** UUIDv4 of the last mutation processed by the index. All changes before this mutation will be reflected in the index state. */\n  processedUpToMutation: number;\n}\n\n/**\n * Represents a single vector value set along with its associated metadata.\n */\ninterface VectorizeVector {\n  /** The ID for the vector. This can be user-defined, and must be unique. It should uniquely identify the object, and is best set based on the ID of what the vector represents. */\n  id: string;\n  /** The vector values */\n  values: VectorFloatArray | number[];\n  /** The namespace this vector belongs to. */\n  namespace?: string;\n  /** Metadata associated with the vector. Includes the values of other fields and potentially additional details. */\n  metadata?: Record<string, VectorizeVectorMetadata>;\n}\n\n/**\n * Represents a matched vector for a query along with its score and (if specified) the matching vector information.\n */\ntype VectorizeMatch = Pick<Partial<VectorizeVector>, \"values\"> &\n  Omit<VectorizeVector, \"values\"> & {\n    /** The score or rank for similarity, when returned as a result */\n    score: number;\n  };\n\n/**\n * A set of matching {@link VectorizeMatch} for a particular query.\n */\ninterface VectorizeMatches {\n  matches: VectorizeMatch[];\n  count: number;\n}\n\n/**\n * Results of an operation that performed a mutation on a set of vectors.\n * Here, `ids` is a list of vectors that were successfully processed.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link VectorizeAsyncMutation} for its post-beta equivalent.\n */\ninterface VectorizeVectorMutation {\n  /* List of ids of vectors that were successfully processed. */\n  ids: string[];\n  /* Total count of the number of processed vectors. */\n  count: number;\n}\n\n/**\n * Result type indicating a mutation on the Vectorize Index.\n * Actual mutations are processed async where the `mutationId` is the unique identifier for the operation.\n */\ninterface VectorizeAsyncMutation {\n  /** The unique identifier for the async mutation operation containing the changeset. */\n  mutationId: string;\n}\n\n/**\n * A Vectorize Vector Search Index for querying vectors/embeddings.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link Vectorize} for its new implementation.\n */\ndeclare abstract class VectorizeIndex {\n  /**\n   * Get information about the currently bound index.\n   * @returns A promise that resolves with information about the current index.\n   */\n  public describe(): Promise<VectorizeIndexDetails>;\n  /**\n   * Use the provided vector to perform a similarity search across the index.\n   * @param vector Input vector that will be used to drive the similarity search.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  public query(\n    vector: VectorFloatArray | number[],\n    options?: VectorizeQueryOptions\n  ): Promise<VectorizeMatches>;\n  /**\n   * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown.\n   * @param vectors List of vectors that will be inserted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed.\n   */\n  public insert(vectors: VectorizeVector[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values.\n   * @param vectors List of vectors that will be upserted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed.\n   */\n  public upsert(vectors: VectorizeVector[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Delete a list of vectors with a matching id.\n   * @param ids List of vector ids that should be deleted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed (and thus deleted).\n   */\n  public deleteByIds(ids: string[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Get a list of vectors with a matching id.\n   * @param ids List of vector ids that should be returned.\n   * @returns A promise that resolves with the raw unscored vectors matching the id set.\n   */\n  public getByIds(ids: string[]): Promise<VectorizeVector[]>;\n}\n\n/**\n * A Vectorize Vector Search Index for querying vectors/embeddings.\n *\n * Mutations in this version are async, returning a mutation id.\n */\ndeclare abstract class Vectorize {\n  /**\n   * Get information about the currently bound index.\n   * @returns A promise that resolves with information about the current index.\n   */\n  public describe(): Promise<VectorizeIndexInfo>;\n  /**\n   * Use the provided vector to perform a similarity search across the index.\n   * @param vector Input vector that will be used to drive the similarity search.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  public query(\n    vector: VectorFloatArray | number[],\n    options?: VectorizeQueryOptions\n  ): Promise<VectorizeMatches>;\n  /**\n   * Use the provided vector-id to perform a similarity search across the index.\n   * @param vectorId Id for a vector in the index against which the index should be queried.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  public queryById(\n    vectorId: string,\n    options?: VectorizeQueryOptions\n  ): Promise<VectorizeMatches>;\n  /**\n   * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown.\n   * @param vectors List of vectors that will be inserted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the insert changeset.\n   */\n  public insert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values.\n   * @param vectors List of vectors that will be upserted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the upsert changeset.\n   */\n  public upsert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Delete a list of vectors with a matching id.\n   * @param ids List of vector ids that should be deleted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the delete changeset.\n   */\n  public deleteByIds(ids: string[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Get a list of vectors with a matching id.\n   * @param ids List of vector ids that should be returned.\n   * @returns A promise that resolves with the raw unscored vectors matching the id set.\n   */\n  public getByIds(ids: string[]): Promise<VectorizeVector[]>;\n}\n"
  },
  {
    "path": "types/defines/versions.d.ts",
    "content": "/**\n * The interface for \"version_metadata\" binding\n * providing metadata about the Worker Version using this binding.\n */\nexport type WorkerVersionMetadata = {\n  /** The ID of the Worker Version using this binding */\n  id: string;\n  /** The tag of the Worker Version using this binding */\n  tag: string;\n  /** The timestamp of when the Worker Version was uploaded */\n  timestamp: string;\n}\n"
  },
  {
    "path": "types/defines/wfp.d.ts",
    "content": "interface DynamicDispatchLimits {\n  /**\n   * Limit CPU time in milliseconds.\n   */\n  cpuMs?: number;\n  /**\n   * Limit number of subrequests.\n   */\n  subRequests?: number;\n}\n\ninterface DynamicDispatchOptions {\n  /**\n   * Limit resources of invoked Worker script.\n   */\n  limits?: DynamicDispatchLimits\n  /**\n   * Arguments for outbound Worker script, if configured.\n   */\n  outbound?: {[key: string]: any}\n}\n\ninterface DispatchNamespace {\n  /**\n  * @param name Name of the Worker script.\n  * @param args Arguments to Worker script.\n  * @param options Options for Dynamic Dispatch invocation.\n  * @returns A Fetcher object that allows you to send requests to the Worker script.\n  * @throws If the Worker script does not exist in this dispatch namespace, an error will be thrown.\n  */\n  get(name: string, args?: {[key: string]: any}, options?: DynamicDispatchOptions ): Fetcher;\n}\n"
  },
  {
    "path": "types/defines/workflows.d.ts",
    "content": "declare module 'cloudflare:workflows' {\n  /**\n   * NonRetryableError allows for a user to throw a fatal error\n   * that makes a Workflow instance fail immediately without triggering a retry\n   */\n  export class NonRetryableError extends Error {\n    public constructor(message: string, name?: string);\n  }\n}\n\ndeclare abstract class Workflow<PARAMS = unknown> {\n  /**\n   * Get a handle to an existing instance of the Workflow.\n   * @param id Id for the instance of this Workflow\n   * @returns A promise that resolves with a handle for the Instance\n   */\n  public get(id: string): Promise<WorkflowInstance>;\n\n  /**\n   * Create a new instance and return a handle to it. If a provided id exists, an error will be thrown.\n   * @param options Options when creating an instance including id and params\n   * @returns A promise that resolves with a handle for the Instance\n   */\n  public create(\n    options?: WorkflowInstanceCreateOptions<PARAMS>\n  ): Promise<WorkflowInstance>;\n\n  /**\n   * Create a batch of instances and return handle for all of them. If a provided id exists, an error will be thrown.\n   * `createBatch` is limited at 100 instances at a time or when the RPC limit for the batch (1MiB) is reached.\n   * @param batch List of Options when creating an instance including name and params\n   * @returns A promise that resolves with a list of handles for the created instances.\n   */\n  public createBatch(\n    batch: WorkflowInstanceCreateOptions<PARAMS>[]\n  ): Promise<WorkflowInstance[]>;\n}\n\ntype WorkflowDurationLabel =\n  | 'second'\n  | 'minute'\n  | 'hour'\n  | 'day'\n  | 'week'\n  | 'month'\n  | 'year';\n\ntype WorkflowSleepDuration =\n  | `${number} ${WorkflowDurationLabel}${'s' | ''}`\n  | number;\n\ntype WorkflowRetentionDuration = WorkflowSleepDuration;\n\ninterface WorkflowInstanceCreateOptions<PARAMS = unknown> {\n  /**\n   * An id for your Workflow instance. Must be unique within the Workflow.\n   */\n  id?: string;\n  /**\n   * The event payload the Workflow instance is triggered with\n   */\n  params?: PARAMS;\n  /**\n   * The retention policy for Workflow instance.\n   * Defaults to the maximum retention period available for the owner's account.\n   */\n  retention?: {\n    successRetention?: WorkflowRetentionDuration,\n    errorRetention?: WorkflowRetentionDuration,\n  };\n}\n\ntype InstanceStatus = {\n  status:\n    | 'queued' // means that instance is waiting to be started (see concurrency limits)\n    | 'running'\n    | 'paused'\n    | 'errored'\n    | 'terminated' // user terminated the instance while it was running\n    | 'complete'\n    | 'waiting' // instance is hibernating and waiting for sleep or event to finish\n    | 'waitingForPause' // instance is finishing the current work to pause\n    | 'unknown';\n  error?: {\n    name: string;\n    message: string;\n  };\n  output?: unknown;\n};\n\ninterface WorkflowError {\n  code?: number;\n  message: string;\n}\n\ndeclare abstract class WorkflowInstance {\n  public id: string;\n\n  /**\n   * Pause the instance.\n   */\n  public pause(): Promise<void>;\n\n  /**\n   * Resume the instance. If it is already running, an error will be thrown.\n   */\n  public resume(): Promise<void>;\n\n  /**\n   * Terminate the instance. If it is errored, terminated or complete, an error will be thrown.\n   */\n  public terminate(): Promise<void>;\n\n  /**\n   * Restart the instance.\n   */\n  public restart(): Promise<void>;\n\n  /**\n   * Returns the current status of the instance.\n   */\n  public status(): Promise<InstanceStatus>;\n\n  /**\n   * Send an event to this instance.\n   */\n  public sendEvent({\n    type,\n    payload,\n  }: {\n    type: string;\n    payload: unknown;\n  }): Promise<void>;\n}\n"
  },
  {
    "path": "types/eslint.config.mjs",
    "content": "import { baseConfig } from '../tools/base.eslint.config.mjs';\n\nexport default [\n  ...baseConfig(),\n  {\n    rules: {\n      '@typescript-eslint/ban-ts-comment': 'off',\n      '@typescript-eslint/no-non-null-assertion': 'off',\n      '@typescript-eslint/no-explicit-any': 'off',\n      '@typescript-eslint/no-empty-function': 'off',\n      '@typescript-eslint/no-floating-promises': 'off',\n      '@typescript-eslint/no-unused-vars': [\n        'error',\n        { argsIgnorePattern: '^_' },\n      ],\n      // TODO(soon): Enable the following eslint rules.\n      // The following rules require Typescript changes\n      '@typescript-eslint/no-unnecessary-condition': 'off',\n      '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'off',\n    },\n  },\n];\n"
  },
  {
    "path": "types/generated-snapshot/experimental/index.d.ts",
    "content": "/*! *****************************************************************************\nCopyright (c) Cloudflare. All rights reserved.\nCopyright (c) Microsoft Corporation. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at http://www.apache.org/licenses/LICENSE-2.0\nTHIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\nKIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED\nWARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,\nMERCHANTABLITY OR NON-INFRINGEMENT.\nSee the Apache Version 2.0 License for specific language governing permissions\nand limitations under the License.\n***************************************************************************** */\n/* eslint-disable */\n// noinspection JSUnusedGlobalSymbols\ndeclare var onmessage: never;\n/**\n * The **`DOMException`** interface represents an abnormal event (called an **exception**) that occurs as a result of calling a method or accessing a property of a web API.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException)\n */\ndeclare class DOMException extends Error {\n  constructor(message?: string, name?: string);\n  /**\n   * The **`message`** read-only property of the a message or description associated with the given error name.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/message)\n   */\n  readonly message: string;\n  /**\n   * The **`name`** read-only property of the one of the strings associated with an error name.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/name)\n   */\n  readonly name: string;\n  /**\n   * The **`code`** read-only property of the DOMException interface returns one of the legacy error code constants, or `0` if none match.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/code)\n   */\n  readonly code: number;\n  static readonly INDEX_SIZE_ERR: number;\n  static readonly DOMSTRING_SIZE_ERR: number;\n  static readonly HIERARCHY_REQUEST_ERR: number;\n  static readonly WRONG_DOCUMENT_ERR: number;\n  static readonly INVALID_CHARACTER_ERR: number;\n  static readonly NO_DATA_ALLOWED_ERR: number;\n  static readonly NO_MODIFICATION_ALLOWED_ERR: number;\n  static readonly NOT_FOUND_ERR: number;\n  static readonly NOT_SUPPORTED_ERR: number;\n  static readonly INUSE_ATTRIBUTE_ERR: number;\n  static readonly INVALID_STATE_ERR: number;\n  static readonly SYNTAX_ERR: number;\n  static readonly INVALID_MODIFICATION_ERR: number;\n  static readonly NAMESPACE_ERR: number;\n  static readonly INVALID_ACCESS_ERR: number;\n  static readonly VALIDATION_ERR: number;\n  static readonly TYPE_MISMATCH_ERR: number;\n  static readonly SECURITY_ERR: number;\n  static readonly NETWORK_ERR: number;\n  static readonly ABORT_ERR: number;\n  static readonly URL_MISMATCH_ERR: number;\n  static readonly QUOTA_EXCEEDED_ERR: number;\n  static readonly TIMEOUT_ERR: number;\n  static readonly INVALID_NODE_TYPE_ERR: number;\n  static readonly DATA_CLONE_ERR: number;\n  get stack(): any;\n  set stack(value: any);\n}\ntype WorkerGlobalScopeEventMap = {\n  fetch: FetchEvent;\n  scheduled: ScheduledEvent;\n  queue: QueueEvent;\n  unhandledrejection: PromiseRejectionEvent;\n  rejectionhandled: PromiseRejectionEvent;\n};\ndeclare abstract class WorkerGlobalScope extends EventTarget<WorkerGlobalScopeEventMap> {\n  EventTarget: typeof EventTarget;\n}\n/* The **`console`** object provides access to the debugging console (e.g., the Web console in Firefox). *\n * The **`console`** object provides access to the debugging console (e.g., the Web console in Firefox).\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console)\n */\ninterface Console {\n  \"assert\"(condition?: boolean, ...data: any[]): void;\n  /**\n   * The **`console.clear()`** static method clears the console if possible.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/clear_static)\n   */\n  clear(): void;\n  /**\n   * The **`console.count()`** static method logs the number of times that this particular call to `count()` has been called.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/count_static)\n   */\n  count(label?: string): void;\n  /**\n   * The **`console.countReset()`** static method resets counter used with console/count_static.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/countReset_static)\n   */\n  countReset(label?: string): void;\n  /**\n   * The **`console.debug()`** static method outputs a message to the console at the 'debug' log level.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/debug_static)\n   */\n  debug(...data: any[]): void;\n  /**\n   * The **`console.dir()`** static method displays a list of the properties of the specified JavaScript object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dir_static)\n   */\n  dir(item?: any, options?: any): void;\n  /**\n   * The **`console.dirxml()`** static method displays an interactive tree of the descendant elements of the specified XML/HTML element.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dirxml_static)\n   */\n  dirxml(...data: any[]): void;\n  /**\n   * The **`console.error()`** static method outputs a message to the console at the 'error' log level.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/error_static)\n   */\n  error(...data: any[]): void;\n  /**\n   * The **`console.group()`** static method creates a new inline group in the Web console log, causing any subsequent console messages to be indented by an additional level, until console/groupEnd_static is called.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/group_static)\n   */\n  group(...data: any[]): void;\n  /**\n   * The **`console.groupCollapsed()`** static method creates a new inline group in the console.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupCollapsed_static)\n   */\n  groupCollapsed(...data: any[]): void;\n  /**\n   * The **`console.groupEnd()`** static method exits the current inline group in the console.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupEnd_static)\n   */\n  groupEnd(): void;\n  /**\n   * The **`console.info()`** static method outputs a message to the console at the 'info' log level.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/info_static)\n   */\n  info(...data: any[]): void;\n  /**\n   * The **`console.log()`** static method outputs a message to the console.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)\n   */\n  log(...data: any[]): void;\n  /**\n   * The **`console.table()`** static method displays tabular data as a table.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/table_static)\n   */\n  table(tabularData?: any, properties?: string[]): void;\n  /**\n   * The **`console.time()`** static method starts a timer you can use to track how long an operation takes.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/time_static)\n   */\n  time(label?: string): void;\n  /**\n   * The **`console.timeEnd()`** static method stops a timer that was previously started by calling console/time_static.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeEnd_static)\n   */\n  timeEnd(label?: string): void;\n  /**\n   * The **`console.timeLog()`** static method logs the current value of a timer that was previously started by calling console/time_static.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeLog_static)\n   */\n  timeLog(label?: string, ...data: any[]): void;\n  timeStamp(label?: string): void;\n  /**\n   * The **`console.trace()`** static method outputs a stack trace to the console.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/trace_static)\n   */\n  trace(...data: any[]): void;\n  /**\n   * The **`console.warn()`** static method outputs a warning message to the console at the 'warning' log level.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/warn_static)\n   */\n  warn(...data: any[]): void;\n}\ndeclare const console: Console;\ntype BufferSource = ArrayBufferView | ArrayBuffer;\ntype TypedArray =\n  | Int8Array\n  | Uint8Array\n  | Uint8ClampedArray\n  | Int16Array\n  | Uint16Array\n  | Int32Array\n  | Uint32Array\n  | Float32Array\n  | Float64Array\n  | BigInt64Array\n  | BigUint64Array;\ndeclare namespace WebAssembly {\n  class CompileError extends Error {\n    constructor(message?: string);\n  }\n  class RuntimeError extends Error {\n    constructor(message?: string);\n  }\n  type ValueType =\n    | \"anyfunc\"\n    | \"externref\"\n    | \"f32\"\n    | \"f64\"\n    | \"i32\"\n    | \"i64\"\n    | \"v128\";\n  interface GlobalDescriptor {\n    value: ValueType;\n    mutable?: boolean;\n  }\n  class Global {\n    constructor(descriptor: GlobalDescriptor, value?: any);\n    value: any;\n    valueOf(): any;\n  }\n  type ImportValue = ExportValue | number;\n  type ModuleImports = Record<string, ImportValue>;\n  type Imports = Record<string, ModuleImports>;\n  type ExportValue = Function | Global | Memory | Table;\n  type Exports = Record<string, ExportValue>;\n  class Instance {\n    constructor(module: Module, imports?: Imports);\n    readonly exports: Exports;\n  }\n  interface MemoryDescriptor {\n    initial: number;\n    maximum?: number;\n    shared?: boolean;\n  }\n  class Memory {\n    constructor(descriptor: MemoryDescriptor);\n    readonly buffer: ArrayBuffer;\n    grow(delta: number): number;\n  }\n  type ImportExportKind = \"function\" | \"global\" | \"memory\" | \"table\";\n  interface ModuleExportDescriptor {\n    kind: ImportExportKind;\n    name: string;\n  }\n  interface ModuleImportDescriptor {\n    kind: ImportExportKind;\n    module: string;\n    name: string;\n  }\n  abstract class Module {\n    static customSections(module: Module, sectionName: string): ArrayBuffer[];\n    static exports(module: Module): ModuleExportDescriptor[];\n    static imports(module: Module): ModuleImportDescriptor[];\n  }\n  type TableKind = \"anyfunc\" | \"externref\";\n  interface TableDescriptor {\n    element: TableKind;\n    initial: number;\n    maximum?: number;\n  }\n  class Table {\n    constructor(descriptor: TableDescriptor, value?: any);\n    readonly length: number;\n    get(index: number): any;\n    grow(delta: number, value?: any): number;\n    set(index: number, value?: any): void;\n  }\n  function instantiate(module: Module, imports?: Imports): Promise<Instance>;\n  function validate(bytes: BufferSource): boolean;\n}\n/**\n * The **`ServiceWorkerGlobalScope`** interface of the Service Worker API represents the global execution context of a service worker.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope)\n */\ninterface ServiceWorkerGlobalScope extends WorkerGlobalScope {\n  DOMException: typeof DOMException;\n  WorkerGlobalScope: typeof WorkerGlobalScope;\n  EventTarget: typeof EventTarget;\n  btoa(data: string): string;\n  atob(data: string): string;\n  setTimeout(callback: (...args: any[]) => void, msDelay?: number): number;\n  setTimeout<Args extends any[]>(\n    callback: (...args: Args) => void,\n    msDelay?: number,\n    ...args: Args\n  ): number;\n  clearTimeout(timeoutId: number | null): void;\n  setInterval(callback: (...args: any[]) => void, msDelay?: number): number;\n  setInterval<Args extends any[]>(\n    callback: (...args: Args) => void,\n    msDelay?: number,\n    ...args: Args\n  ): number;\n  clearInterval(timeoutId: number | null): void;\n  queueMicrotask(task: Function): void;\n  structuredClone<T>(value: T, options?: StructuredSerializeOptions): T;\n  reportError(error: any): void;\n  fetch(\n    input: RequestInfo | URL,\n    init?: RequestInit<RequestInitCfProperties>,\n  ): Promise<Response>;\n  self: ServiceWorkerGlobalScope;\n  crypto: Crypto;\n  caches: CacheStorage;\n  scheduler: Scheduler;\n  performance: Performance;\n  Cloudflare: Cloudflare;\n  readonly origin: string;\n  Event: typeof Event;\n  ExtendableEvent: typeof ExtendableEvent;\n  CustomEvent: typeof CustomEvent;\n  PromiseRejectionEvent: typeof PromiseRejectionEvent;\n  FetchEvent: typeof FetchEvent;\n  TailEvent: typeof TailEvent;\n  TraceEvent: typeof TailEvent;\n  ScheduledEvent: typeof ScheduledEvent;\n  MessageEvent: typeof MessageEvent;\n  CloseEvent: typeof CloseEvent;\n  ReadableStreamDefaultReader: typeof ReadableStreamDefaultReader;\n  ReadableStreamBYOBReader: typeof ReadableStreamBYOBReader;\n  ReadableStream: typeof ReadableStream;\n  WritableStream: typeof WritableStream;\n  WritableStreamDefaultWriter: typeof WritableStreamDefaultWriter;\n  TransformStream: typeof TransformStream;\n  ByteLengthQueuingStrategy: typeof ByteLengthQueuingStrategy;\n  CountQueuingStrategy: typeof CountQueuingStrategy;\n  ErrorEvent: typeof ErrorEvent;\n  MessageChannel: typeof MessageChannel;\n  MessagePort: typeof MessagePort;\n  FileSystemHandle: typeof FileSystemHandle;\n  FileSystemFileHandle: typeof FileSystemFileHandle;\n  FileSystemDirectoryHandle: typeof FileSystemDirectoryHandle;\n  FileSystemWritableFileStream: typeof FileSystemWritableFileStream;\n  StorageManager: typeof StorageManager;\n  EventSource: typeof EventSource;\n  ReadableStreamBYOBRequest: typeof ReadableStreamBYOBRequest;\n  ReadableStreamDefaultController: typeof ReadableStreamDefaultController;\n  ReadableByteStreamController: typeof ReadableByteStreamController;\n  WritableStreamDefaultController: typeof WritableStreamDefaultController;\n  TransformStreamDefaultController: typeof TransformStreamDefaultController;\n  CompressionStream: typeof CompressionStream;\n  DecompressionStream: typeof DecompressionStream;\n  TextEncoderStream: typeof TextEncoderStream;\n  TextDecoderStream: typeof TextDecoderStream;\n  Headers: typeof Headers;\n  Body: typeof Body;\n  Request: typeof Request;\n  Response: typeof Response;\n  WebSocket: typeof WebSocket;\n  WebSocketPair: typeof WebSocketPair;\n  WebSocketRequestResponsePair: typeof WebSocketRequestResponsePair;\n  AbortController: typeof AbortController;\n  AbortSignal: typeof AbortSignal;\n  TextDecoder: typeof TextDecoder;\n  TextEncoder: typeof TextEncoder;\n  navigator: Navigator;\n  Navigator: typeof Navigator;\n  URL: typeof URL;\n  URLSearchParams: typeof URLSearchParams;\n  URLPattern: typeof URLPattern;\n  Blob: typeof Blob;\n  File: typeof File;\n  FormData: typeof FormData;\n  Crypto: typeof Crypto;\n  SubtleCrypto: typeof SubtleCrypto;\n  CryptoKey: typeof CryptoKey;\n  CacheStorage: typeof CacheStorage;\n  Cache: typeof Cache;\n  FixedLengthStream: typeof FixedLengthStream;\n  IdentityTransformStream: typeof IdentityTransformStream;\n  HTMLRewriter: typeof HTMLRewriter;\n  Performance: typeof Performance;\n  PerformanceEntry: typeof PerformanceEntry;\n  PerformanceMark: typeof PerformanceMark;\n  PerformanceMeasure: typeof PerformanceMeasure;\n  PerformanceResourceTiming: typeof PerformanceResourceTiming;\n  PerformanceObserver: typeof PerformanceObserver;\n  PerformanceObserverEntryList: typeof PerformanceObserverEntryList;\n}\ndeclare function addEventListener<Type extends keyof WorkerGlobalScopeEventMap>(\n  type: Type,\n  handler: EventListenerOrEventListenerObject<WorkerGlobalScopeEventMap[Type]>,\n  options?: EventTargetAddEventListenerOptions | boolean,\n): void;\ndeclare function removeEventListener<\n  Type extends keyof WorkerGlobalScopeEventMap,\n>(\n  type: Type,\n  handler: EventListenerOrEventListenerObject<WorkerGlobalScopeEventMap[Type]>,\n  options?: EventTargetEventListenerOptions | boolean,\n): void;\n/**\n * The **`dispatchEvent()`** method of the EventTarget sends an Event to the object, (synchronously) invoking the affected event listeners in the appropriate order.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent)\n */\ndeclare function dispatchEvent(\n  event: WorkerGlobalScopeEventMap[keyof WorkerGlobalScopeEventMap],\n): boolean;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/btoa) */\ndeclare function btoa(data: string): string;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/atob) */\ndeclare function atob(data: string): string;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */\ndeclare function setTimeout(\n  callback: (...args: any[]) => void,\n  msDelay?: number,\n): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */\ndeclare function setTimeout<Args extends any[]>(\n  callback: (...args: Args) => void,\n  msDelay?: number,\n  ...args: Args\n): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearTimeout) */\ndeclare function clearTimeout(timeoutId: number | null): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */\ndeclare function setInterval(\n  callback: (...args: any[]) => void,\n  msDelay?: number,\n): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */\ndeclare function setInterval<Args extends any[]>(\n  callback: (...args: Args) => void,\n  msDelay?: number,\n  ...args: Args\n): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearInterval) */\ndeclare function clearInterval(timeoutId: number | null): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/queueMicrotask) */\ndeclare function queueMicrotask(task: Function): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/structuredClone) */\ndeclare function structuredClone<T>(\n  value: T,\n  options?: StructuredSerializeOptions,\n): T;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/reportError) */\ndeclare function reportError(error: any): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch) */\ndeclare function fetch(\n  input: RequestInfo | URL,\n  init?: RequestInit<RequestInitCfProperties>,\n): Promise<Response>;\ndeclare const self: ServiceWorkerGlobalScope;\n/**\n * The Web Crypto API provides a set of low-level functions for common cryptographic tasks.\n * The Workers runtime implements the full surface of this API, but with some differences in\n * the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms)\n * compared to those implemented in most browsers.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/)\n */\ndeclare const crypto: Crypto;\n/**\n * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)\n */\ndeclare const caches: CacheStorage;\ndeclare const scheduler: Scheduler;\n/**\n * The Workers runtime supports a subset of the Performance API, used to measure timing and performance,\n * as well as timing of subrequests and other operations.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/)\n */\ndeclare const performance: Performance;\ndeclare const Cloudflare: Cloudflare;\ndeclare const origin: string;\ndeclare const navigator: Navigator;\ninterface TestController {}\ninterface ExecutionContext<Props = unknown> {\n  waitUntil(promise: Promise<any>): void;\n  passThroughOnException(): void;\n  readonly exports: Cloudflare.Exports;\n  readonly props: Props;\n  readonly version?: {\n    readonly metadata?: {\n      readonly id: string;\n    };\n    readonly cohort?: string;\n    readonly key?: string;\n    readonly override?: string;\n  };\n  abort(reason?: any): void;\n}\ntype ExportedHandlerFetchHandler<\n  Env = unknown,\n  CfHostMetadata = unknown,\n  Props = unknown,\n> = (\n  request: Request<CfHostMetadata, IncomingRequestCfProperties<CfHostMetadata>>,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => Response | Promise<Response>;\ntype ExportedHandlerTailHandler<Env = unknown, Props = unknown> = (\n  events: TraceItem[],\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\ntype ExportedHandlerTraceHandler<Env = unknown, Props = unknown> = (\n  traces: TraceItem[],\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\ntype ExportedHandlerTailStreamHandler<Env = unknown, Props = unknown> = (\n  event: TailStream.TailEvent<TailStream.Onset>,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => TailStream.TailEventHandlerType | Promise<TailStream.TailEventHandlerType>;\ntype ExportedHandlerScheduledHandler<Env = unknown, Props = unknown> = (\n  controller: ScheduledController,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\ntype ExportedHandlerQueueHandler<\n  Env = unknown,\n  Message = unknown,\n  Props = unknown,\n> = (\n  batch: MessageBatch<Message>,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\ntype ExportedHandlerTestHandler<Env = unknown, Props = unknown> = (\n  controller: TestController,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\ninterface ExportedHandler<\n  Env = unknown,\n  QueueHandlerMessage = unknown,\n  CfHostMetadata = unknown,\n  Props = unknown,\n> {\n  fetch?: ExportedHandlerFetchHandler<Env, CfHostMetadata, Props>;\n  tail?: ExportedHandlerTailHandler<Env, Props>;\n  trace?: ExportedHandlerTraceHandler<Env, Props>;\n  tailStream?: ExportedHandlerTailStreamHandler<Env, Props>;\n  scheduled?: ExportedHandlerScheduledHandler<Env, Props>;\n  test?: ExportedHandlerTestHandler<Env, Props>;\n  email?: EmailExportedHandler<Env, Props>;\n  queue?: ExportedHandlerQueueHandler<Env, QueueHandlerMessage, Props>;\n}\ninterface StructuredSerializeOptions {\n  transfer?: any[];\n}\ndeclare abstract class Navigator {\n  sendBeacon(url: string, body?: BodyInit): boolean;\n  readonly userAgent: string;\n  readonly hardwareConcurrency: number;\n  readonly language: string;\n  readonly languages: string[];\n  readonly storage: StorageManager;\n}\ninterface AlarmInvocationInfo {\n  readonly isRetry: boolean;\n  readonly retryCount: number;\n}\ninterface Cloudflare {\n  readonly compatibilityFlags: Record<string, boolean>;\n}\ndeclare abstract class ColoLocalActorNamespace {\n  get(actorId: string): Fetcher;\n}\ninterface DurableObject {\n  fetch(request: Request): Response | Promise<Response>;\n  alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise<void>;\n  webSocketMessage?(\n    ws: WebSocket,\n    message: string | ArrayBuffer,\n  ): void | Promise<void>;\n  webSocketClose?(\n    ws: WebSocket,\n    code: number,\n    reason: string,\n    wasClean: boolean,\n  ): void | Promise<void>;\n  webSocketError?(ws: WebSocket, error: unknown): void | Promise<void>;\n}\ntype DurableObjectStub<\n  T extends Rpc.DurableObjectBranded | undefined = undefined,\n> = Fetcher<\n  T,\n  \"alarm\" | \"webSocketMessage\" | \"webSocketClose\" | \"webSocketError\"\n> & {\n  readonly id: DurableObjectId;\n  readonly name?: string;\n};\ninterface DurableObjectId {\n  toString(): string;\n  equals(other: DurableObjectId): boolean;\n  readonly name?: string;\n  readonly jurisdiction?: string;\n}\ndeclare abstract class DurableObjectNamespace<\n  T extends Rpc.DurableObjectBranded | undefined = undefined,\n> {\n  newUniqueId(\n    options?: DurableObjectNamespaceNewUniqueIdOptions,\n  ): DurableObjectId;\n  idFromName(name: string): DurableObjectId;\n  idFromString(id: string): DurableObjectId;\n  get(\n    id: DurableObjectId,\n    options?: DurableObjectNamespaceGetDurableObjectOptions,\n  ): DurableObjectStub<T>;\n  getByName(\n    name: string,\n    options?: DurableObjectNamespaceGetDurableObjectOptions,\n  ): DurableObjectStub<T>;\n  getExisting(\n    id: DurableObjectId,\n    options?: DurableObjectNamespaceGetDurableObjectOptions,\n  ): DurableObjectStub<T>;\n  jurisdiction(\n    jurisdiction: DurableObjectJurisdiction,\n  ): DurableObjectNamespace<T>;\n}\ntype DurableObjectJurisdiction = \"eu\" | \"fedramp\" | \"fedramp-high\";\ninterface DurableObjectNamespaceNewUniqueIdOptions {\n  jurisdiction?: DurableObjectJurisdiction;\n}\ntype DurableObjectLocationHint =\n  | \"wnam\"\n  | \"enam\"\n  | \"sam\"\n  | \"weur\"\n  | \"eeur\"\n  | \"apac\"\n  | \"oc\"\n  | \"afr\"\n  | \"me\";\ntype DurableObjectRoutingMode = \"primary-only\";\ninterface DurableObjectNamespaceGetDurableObjectOptions {\n  locationHint?: DurableObjectLocationHint;\n  routingMode?: DurableObjectRoutingMode;\n  version?: {\n    cohort?: string;\n  };\n}\ninterface DurableObjectClass<\n  _T extends Rpc.DurableObjectBranded | undefined = undefined,\n> {}\ninterface DurableObjectNamespaceGetDurableObjectOptionsVersionOptions {\n  cohort?: string;\n}\ninterface DurableObjectState<Props = unknown> {\n  waitUntil(promise: Promise<any>): void;\n  readonly exports: Cloudflare.Exports;\n  readonly props: Props;\n  readonly id: DurableObjectId;\n  readonly storage: DurableObjectStorage;\n  container?: Container;\n  facets: DurableObjectFacets;\n  version?: DurableObjectStateVersion;\n  blockConcurrencyWhile<T>(callback: () => Promise<T>): Promise<T>;\n  acceptWebSocket(ws: WebSocket, tags?: string[]): void;\n  getWebSockets(tag?: string): WebSocket[];\n  setWebSocketAutoResponse(maybeReqResp?: WebSocketRequestResponsePair): void;\n  getWebSocketAutoResponse(): WebSocketRequestResponsePair | null;\n  getWebSocketAutoResponseTimestamp(ws: WebSocket): Date | null;\n  setHibernatableWebSocketEventTimeout(timeoutMs?: number): void;\n  getHibernatableWebSocketEventTimeout(): number | null;\n  getTags(ws: WebSocket): string[];\n  abort(reason?: string): void;\n}\ninterface DurableObjectTransaction {\n  get<T = unknown>(\n    key: string,\n    options?: DurableObjectGetOptions,\n  ): Promise<T | undefined>;\n  get<T = unknown>(\n    keys: string[],\n    options?: DurableObjectGetOptions,\n  ): Promise<Map<string, T>>;\n  list<T = unknown>(\n    options?: DurableObjectListOptions,\n  ): Promise<Map<string, T>>;\n  put<T>(\n    key: string,\n    value: T,\n    options?: DurableObjectPutOptions,\n  ): Promise<void>;\n  put<T>(\n    entries: Record<string, T>,\n    options?: DurableObjectPutOptions,\n  ): Promise<void>;\n  delete(key: string, options?: DurableObjectPutOptions): Promise<boolean>;\n  delete(keys: string[], options?: DurableObjectPutOptions): Promise<number>;\n  rollback(): void;\n  getAlarm(options?: DurableObjectGetAlarmOptions): Promise<number | null>;\n  setAlarm(\n    scheduledTime: number | Date,\n    options?: DurableObjectSetAlarmOptions,\n  ): Promise<void>;\n  deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise<void>;\n}\ninterface DurableObjectStorage {\n  get<T = unknown>(\n    key: string,\n    options?: DurableObjectGetOptions,\n  ): Promise<T | undefined>;\n  get<T = unknown>(\n    keys: string[],\n    options?: DurableObjectGetOptions,\n  ): Promise<Map<string, T>>;\n  list<T = unknown>(\n    options?: DurableObjectListOptions,\n  ): Promise<Map<string, T>>;\n  put<T>(\n    key: string,\n    value: T,\n    options?: DurableObjectPutOptions,\n  ): Promise<void>;\n  put<T>(\n    entries: Record<string, T>,\n    options?: DurableObjectPutOptions,\n  ): Promise<void>;\n  delete(key: string, options?: DurableObjectPutOptions): Promise<boolean>;\n  delete(keys: string[], options?: DurableObjectPutOptions): Promise<number>;\n  deleteAll(options?: DurableObjectPutOptions): Promise<void>;\n  transaction<T>(\n    closure: (txn: DurableObjectTransaction) => Promise<T>,\n  ): Promise<T>;\n  getAlarm(options?: DurableObjectGetAlarmOptions): Promise<number | null>;\n  setAlarm(\n    scheduledTime: number | Date,\n    options?: DurableObjectSetAlarmOptions,\n  ): Promise<void>;\n  deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise<void>;\n  sync(): Promise<void>;\n  sql: SqlStorage;\n  kv: SyncKvStorage;\n  transactionSync<T>(closure: () => T): T;\n  getCurrentBookmark(): Promise<string>;\n  getBookmarkForTime(timestamp: number | Date): Promise<string>;\n  onNextSessionRestoreBookmark(bookmark: string): Promise<string>;\n  waitForBookmark(bookmark: string): Promise<void>;\n  readonly primary?: DurableObjectStub;\n  ensureReplicas(): void;\n  disableReplicas(): void;\n}\ninterface DurableObjectListOptions {\n  start?: string;\n  startAfter?: string;\n  end?: string;\n  prefix?: string;\n  reverse?: boolean;\n  limit?: number;\n  allowConcurrency?: boolean;\n  noCache?: boolean;\n}\ninterface DurableObjectGetOptions {\n  allowConcurrency?: boolean;\n  noCache?: boolean;\n}\ninterface DurableObjectGetAlarmOptions {\n  allowConcurrency?: boolean;\n}\ninterface DurableObjectPutOptions {\n  allowConcurrency?: boolean;\n  allowUnconfirmed?: boolean;\n  noCache?: boolean;\n}\ninterface DurableObjectSetAlarmOptions {\n  allowConcurrency?: boolean;\n  allowUnconfirmed?: boolean;\n}\ndeclare class WebSocketRequestResponsePair {\n  constructor(request: string, response: string);\n  get request(): string;\n  get response(): string;\n}\ninterface DurableObjectFacets {\n  get<T extends Rpc.DurableObjectBranded | undefined = undefined>(\n    name: string,\n    getStartupOptions: () =>\n      | FacetStartupOptions<T>\n      | Promise<FacetStartupOptions<T>>,\n  ): Fetcher<T>;\n  abort(name: string, reason: any): void;\n  delete(name: string): void;\n}\ninterface FacetStartupOptions<\n  T extends Rpc.DurableObjectBranded | undefined = undefined,\n> {\n  id?: DurableObjectId | string;\n  class: DurableObjectClass<T>;\n}\ninterface DurableObjectStateVersion {\n  cohort?: string;\n}\ninterface AnalyticsEngineDataset {\n  writeDataPoint(event?: AnalyticsEngineDataPoint): void;\n}\ninterface AnalyticsEngineDataPoint {\n  indexes?: ((ArrayBuffer | string) | null)[];\n  doubles?: number[];\n  blobs?: ((ArrayBuffer | string) | null)[];\n}\n/**\n * The **`Event`** interface represents an event which takes place on an `EventTarget`.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event)\n */\ndeclare class Event {\n  constructor(type: string, init?: EventInit);\n  /**\n   * The **`type`** read-only property of the Event interface returns a string containing the event's type.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/type)\n   */\n  get type(): string;\n  /**\n   * The **`eventPhase`** read-only property of the being evaluated.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/eventPhase)\n   */\n  get eventPhase(): number;\n  /**\n   * The read-only **`composed`** property of the or not the event will propagate across the shadow DOM boundary into the standard DOM.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composed)\n   */\n  get composed(): boolean;\n  /**\n   * The **`bubbles`** read-only property of the Event interface indicates whether the event bubbles up through the DOM tree or not.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/bubbles)\n   */\n  get bubbles(): boolean;\n  /**\n   * The **`cancelable`** read-only property of the Event interface indicates whether the event can be canceled, and therefore prevented as if the event never happened.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelable)\n   */\n  get cancelable(): boolean;\n  /**\n   * The **`defaultPrevented`** read-only property of the Event interface returns a boolean value indicating whether or not the call to Event.preventDefault() canceled the event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/defaultPrevented)\n   */\n  get defaultPrevented(): boolean;\n  /**\n   * The Event property **`returnValue`** indicates whether the default action for this event has been prevented or not.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/returnValue)\n   */\n  get returnValue(): boolean;\n  /**\n   * The **`currentTarget`** read-only property of the Event interface identifies the element to which the event handler has been attached.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/currentTarget)\n   */\n  get currentTarget(): EventTarget | null;\n  /**\n   * The read-only **`target`** property of the dispatched.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/target)\n   */\n  get target(): EventTarget | undefined;\n  /**\n   * The deprecated **`Event.srcElement`** is an alias for the Event.target property.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/srcElement)\n   */\n  get srcElement(): EventTarget | undefined;\n  /**\n   * The **`timeStamp`** read-only property of the Event interface returns the time (in milliseconds) at which the event was created.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/timeStamp)\n   */\n  get timeStamp(): number;\n  /**\n   * The **`isTrusted`** read-only property of the when the event was generated by the user agent (including via user actions and programmatic methods such as HTMLElement.focus()), and `false` when the event was dispatched via The only exception is the `click` event, which initializes the `isTrusted` property to `false` in user agents.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/isTrusted)\n   */\n  readonly isTrusted: boolean;\n  /**\n   * The **`cancelBubble`** property of the Event interface is deprecated.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble)\n   */\n  get cancelBubble(): boolean;\n  /**\n   * The **`cancelBubble`** property of the Event interface is deprecated.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble)\n   */\n  set cancelBubble(value: boolean);\n  /**\n   * The **`stopImmediatePropagation()`** method of the If several listeners are attached to the same element for the same event type, they are called in the order in which they were added.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopImmediatePropagation)\n   */\n  stopImmediatePropagation(): void;\n  /**\n   * The **`preventDefault()`** method of the Event interface tells the user agent that if the event does not get explicitly handled, its default action should not be taken as it normally would be.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/preventDefault)\n   */\n  preventDefault(): void;\n  /**\n   * The **`stopPropagation()`** method of the Event interface prevents further propagation of the current event in the capturing and bubbling phases.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopPropagation)\n   */\n  stopPropagation(): void;\n  /**\n   * The **`composedPath()`** method of the Event interface returns the event's path which is an array of the objects on which listeners will be invoked.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composedPath)\n   */\n  composedPath(): EventTarget[];\n  static readonly NONE: number;\n  static readonly CAPTURING_PHASE: number;\n  static readonly AT_TARGET: number;\n  static readonly BUBBLING_PHASE: number;\n}\ninterface EventInit {\n  bubbles?: boolean;\n  cancelable?: boolean;\n  composed?: boolean;\n}\ntype EventListener<EventType extends Event = Event> = (\n  event: EventType,\n) => void;\ninterface EventListenerObject<EventType extends Event = Event> {\n  handleEvent(event: EventType): void;\n}\ntype EventListenerOrEventListenerObject<EventType extends Event = Event> =\n  | EventListener<EventType>\n  | EventListenerObject<EventType>;\n/**\n * The **`EventTarget`** interface is implemented by objects that can receive events and may have listeners for them.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget)\n */\ndeclare class EventTarget<\n  EventMap extends Record<string, Event> = Record<string, Event>,\n> {\n  constructor();\n  /**\n   * The **`addEventListener()`** method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)\n   */\n  addEventListener<Type extends keyof EventMap>(\n    type: Type,\n    handler: EventListenerOrEventListenerObject<EventMap[Type]>,\n    options?: EventTargetAddEventListenerOptions | boolean,\n  ): void;\n  /**\n   * The **`removeEventListener()`** method of the EventTarget interface removes an event listener previously registered with EventTarget.addEventListener() from the target.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener)\n   */\n  removeEventListener<Type extends keyof EventMap>(\n    type: Type,\n    handler: EventListenerOrEventListenerObject<EventMap[Type]>,\n    options?: EventTargetEventListenerOptions | boolean,\n  ): void;\n  /**\n   * The **`dispatchEvent()`** method of the EventTarget sends an Event to the object, (synchronously) invoking the affected event listeners in the appropriate order.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent)\n   */\n  dispatchEvent(event: EventMap[keyof EventMap]): boolean;\n}\ninterface EventTargetEventListenerOptions {\n  capture?: boolean;\n}\ninterface EventTargetAddEventListenerOptions {\n  capture?: boolean;\n  passive?: boolean;\n  once?: boolean;\n  signal?: AbortSignal;\n}\ninterface EventTargetHandlerObject {\n  handleEvent: (event: Event) => any | undefined;\n}\n/**\n * The **`AbortController`** interface represents a controller object that allows you to abort one or more Web requests as and when desired.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController)\n */\ndeclare class AbortController {\n  constructor();\n  /**\n   * The **`signal`** read-only property of the AbortController interface returns an AbortSignal object instance, which can be used to communicate with/abort an asynchronous operation as desired.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/signal)\n   */\n  get signal(): AbortSignal;\n  /**\n   * The **`abort()`** method of the AbortController interface aborts an asynchronous operation before it has completed.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/abort)\n   */\n  abort(reason?: any): void;\n}\n/**\n * The **`AbortSignal`** interface represents a signal object that allows you to communicate with an asynchronous operation (such as a fetch request) and abort it if required via an AbortController object.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal)\n */\ndeclare abstract class AbortSignal extends EventTarget {\n  /**\n   * The **`AbortSignal.abort()`** static method returns an AbortSignal that is already set as aborted (and which does not trigger an AbortSignal/abort_event event).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_static)\n   */\n  static abort(reason?: any): AbortSignal;\n  /**\n   * The **`AbortSignal.timeout()`** static method returns an AbortSignal that will automatically abort after a specified time.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/timeout_static)\n   */\n  static timeout(delay: number): AbortSignal;\n  /**\n   * The **`AbortSignal.any()`** static method takes an iterable of abort signals and returns an AbortSignal.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/any_static)\n   */\n  static any(signals: AbortSignal[]): AbortSignal;\n  /**\n   * The **`aborted`** read-only property returns a value that indicates whether the asynchronous operations the signal is communicating with are aborted (`true`) or not (`false`).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/aborted)\n   */\n  get aborted(): boolean;\n  /**\n   * The **`reason`** read-only property returns a JavaScript value that indicates the abort reason.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/reason)\n   */\n  get reason(): any;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */\n  get onabort(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */\n  set onabort(value: any | null);\n  /**\n   * The **`throwIfAborted()`** method throws the signal's abort AbortSignal.reason if the signal has been aborted; otherwise it does nothing.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/throwIfAborted)\n   */\n  throwIfAborted(): void;\n}\ninterface Scheduler {\n  wait(delay: number, maybeOptions?: SchedulerWaitOptions): Promise<void>;\n}\ninterface SchedulerWaitOptions {\n  signal?: AbortSignal;\n}\n/**\n * The **`ExtendableEvent`** interface extends the lifetime of the `install` and `activate` events dispatched on the global scope as part of the service worker lifecycle.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent)\n */\ndeclare abstract class ExtendableEvent extends Event {\n  /**\n   * The **`ExtendableEvent.waitUntil()`** method tells the event dispatcher that work is ongoing.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent/waitUntil)\n   */\n  waitUntil(promise: Promise<any>): void;\n}\n/**\n * The **`CustomEvent`** interface represents events initialized by an application for any purpose.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent)\n */\ndeclare class CustomEvent<T = any> extends Event {\n  constructor(type: string, init?: CustomEventCustomEventInit);\n  /**\n   * The read-only **`detail`** property of the CustomEvent interface returns any data passed when initializing the event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent/detail)\n   */\n  get detail(): T;\n}\ninterface CustomEventCustomEventInit {\n  bubbles?: boolean;\n  cancelable?: boolean;\n  composed?: boolean;\n  detail?: any;\n}\n/**\n * The **`Blob`** interface represents a blob, which is a file-like object of immutable, raw data; they can be read as text or binary data, or converted into a ReadableStream so its methods can be used for processing the data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob)\n */\ndeclare class Blob {\n  constructor(\n    type?: ((ArrayBuffer | ArrayBufferView) | string | Blob)[],\n    options?: BlobOptions,\n  );\n  /**\n   * The **`size`** read-only property of the Blob interface returns the size of the Blob or File in bytes.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size)\n   */\n  get size(): number;\n  /**\n   * The **`type`** read-only property of the Blob interface returns the MIME type of the file.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type)\n   */\n  get type(): string;\n  /**\n   * The **`slice()`** method of the Blob interface creates and returns a new `Blob` object which contains data from a subset of the blob on which it's called.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice)\n   */\n  slice(start?: number, end?: number, type?: string): Blob;\n  /**\n   * The **`arrayBuffer()`** method of the Blob interface returns a Promise that resolves with the contents of the blob as binary data contained in an ArrayBuffer.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/arrayBuffer)\n   */\n  arrayBuffer(): Promise<ArrayBuffer>;\n  /**\n   * The **`bytes()`** method of the Blob interface returns a Promise that resolves with a Uint8Array containing the contents of the blob as an array of bytes.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/bytes)\n   */\n  bytes(): Promise<Uint8Array>;\n  /**\n   * The **`text()`** method of the string containing the contents of the blob, interpreted as UTF-8.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text)\n   */\n  text(): Promise<string>;\n  /**\n   * The **`stream()`** method of the Blob interface returns a ReadableStream which upon reading returns the data contained within the `Blob`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/stream)\n   */\n  stream(): ReadableStream;\n}\ninterface BlobOptions {\n  type?: string;\n}\n/**\n * The **`File`** interface provides information about files and allows JavaScript in a web page to access their content.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File)\n */\ndeclare class File extends Blob {\n  constructor(\n    bits: ((ArrayBuffer | ArrayBufferView) | string | Blob)[] | undefined,\n    name: string,\n    options?: FileOptions,\n  );\n  /**\n   * The **`name`** read-only property of the File interface returns the name of the file represented by a File object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name)\n   */\n  get name(): string;\n  /**\n   * The **`lastModified`** read-only property of the File interface provides the last modified date of the file as the number of milliseconds since the Unix epoch (January 1, 1970 at midnight).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified)\n   */\n  get lastModified(): number;\n}\ninterface FileOptions {\n  type?: string;\n  lastModified?: number;\n}\n/**\n * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)\n */\ndeclare abstract class CacheStorage {\n  /**\n   * The **`open()`** method of the the Cache object matching the `cacheName`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CacheStorage/open)\n   */\n  open(cacheName: string): Promise<Cache>;\n  readonly default: Cache;\n}\n/**\n * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)\n */\ndeclare abstract class Cache {\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#delete) */\n  delete(\n    request: RequestInfo | URL,\n    options?: CacheQueryOptions,\n  ): Promise<boolean>;\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#match) */\n  match(\n    request: RequestInfo | URL,\n    options?: CacheQueryOptions,\n  ): Promise<Response | undefined>;\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#put) */\n  put(request: RequestInfo | URL, response: Response): Promise<void>;\n}\ninterface CacheQueryOptions {\n  ignoreMethod?: boolean;\n}\n/**\n * The Web Crypto API provides a set of low-level functions for common cryptographic tasks.\n * The Workers runtime implements the full surface of this API, but with some differences in\n * the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms)\n * compared to those implemented in most browsers.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/)\n */\ndeclare abstract class Crypto {\n  /**\n   * The **`Crypto.subtle`** read-only property returns a cryptographic operations.\n   * Available only in secure contexts.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/subtle)\n   */\n  get subtle(): SubtleCrypto;\n  /**\n   * The **`Crypto.getRandomValues()`** method lets you get cryptographically strong random values.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/getRandomValues)\n   */\n  getRandomValues<\n    T extends\n      | Int8Array\n      | Uint8Array\n      | Int16Array\n      | Uint16Array\n      | Int32Array\n      | Uint32Array\n      | BigInt64Array\n      | BigUint64Array,\n  >(buffer: T): T;\n  /**\n   * The **`randomUUID()`** method of the Crypto interface is used to generate a v4 UUID using a cryptographically secure random number generator.\n   * Available only in secure contexts.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/randomUUID)\n   */\n  randomUUID(): string;\n  DigestStream: typeof DigestStream;\n}\n/**\n * The **`SubtleCrypto`** interface of the Web Crypto API provides a number of low-level cryptographic functions.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto)\n */\ndeclare abstract class SubtleCrypto {\n  /**\n   * The **`encrypt()`** method of the SubtleCrypto interface encrypts data.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/encrypt)\n   */\n  encrypt(\n    algorithm: string | SubtleCryptoEncryptAlgorithm,\n    key: CryptoKey,\n    plainText: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`decrypt()`** method of the SubtleCrypto interface decrypts some encrypted data.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/decrypt)\n   */\n  decrypt(\n    algorithm: string | SubtleCryptoEncryptAlgorithm,\n    key: CryptoKey,\n    cipherText: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`sign()`** method of the SubtleCrypto interface generates a digital signature.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/sign)\n   */\n  sign(\n    algorithm: string | SubtleCryptoSignAlgorithm,\n    key: CryptoKey,\n    data: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`verify()`** method of the SubtleCrypto interface verifies a digital signature.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/verify)\n   */\n  verify(\n    algorithm: string | SubtleCryptoSignAlgorithm,\n    key: CryptoKey,\n    signature: ArrayBuffer | ArrayBufferView,\n    data: ArrayBuffer | ArrayBufferView,\n  ): Promise<boolean>;\n  /**\n   * The **`digest()`** method of the SubtleCrypto interface generates a _digest_ of the given data, using the specified hash function.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/digest)\n   */\n  digest(\n    algorithm: string | SubtleCryptoHashAlgorithm,\n    data: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`generateKey()`** method of the SubtleCrypto interface is used to generate a new key (for symmetric algorithms) or key pair (for public-key algorithms).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/generateKey)\n   */\n  generateKey(\n    algorithm: string | SubtleCryptoGenerateKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey | CryptoKeyPair>;\n  /**\n   * The **`deriveKey()`** method of the SubtleCrypto interface can be used to derive a secret key from a master key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveKey)\n   */\n  deriveKey(\n    algorithm: string | SubtleCryptoDeriveKeyAlgorithm,\n    baseKey: CryptoKey,\n    derivedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey>;\n  /**\n   * The **`deriveBits()`** method of the key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveBits)\n   */\n  deriveBits(\n    algorithm: string | SubtleCryptoDeriveKeyAlgorithm,\n    baseKey: CryptoKey,\n    length?: number | null,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`importKey()`** method of the SubtleCrypto interface imports a key: that is, it takes as input a key in an external, portable format and gives you a CryptoKey object that you can use in the Web Crypto API.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/importKey)\n   */\n  importKey(\n    format: string,\n    keyData: (ArrayBuffer | ArrayBufferView) | JsonWebKey,\n    algorithm: string | SubtleCryptoImportKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey>;\n  /**\n   * The **`exportKey()`** method of the SubtleCrypto interface exports a key: that is, it takes as input a CryptoKey object and gives you the key in an external, portable format.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/exportKey)\n   */\n  exportKey(format: string, key: CryptoKey): Promise<ArrayBuffer | JsonWebKey>;\n  /**\n   * The **`wrapKey()`** method of the SubtleCrypto interface 'wraps' a key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/wrapKey)\n   */\n  wrapKey(\n    format: string,\n    key: CryptoKey,\n    wrappingKey: CryptoKey,\n    wrapAlgorithm: string | SubtleCryptoEncryptAlgorithm,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`unwrapKey()`** method of the SubtleCrypto interface 'unwraps' a key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/unwrapKey)\n   */\n  unwrapKey(\n    format: string,\n    wrappedKey: ArrayBuffer | ArrayBufferView,\n    unwrappingKey: CryptoKey,\n    unwrapAlgorithm: string | SubtleCryptoEncryptAlgorithm,\n    unwrappedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey>;\n  timingSafeEqual(\n    a: ArrayBuffer | ArrayBufferView,\n    b: ArrayBuffer | ArrayBufferView,\n  ): boolean;\n}\n/**\n * The **`CryptoKey`** interface of the Web Crypto API represents a cryptographic key obtained from one of the SubtleCrypto methods SubtleCrypto.generateKey, SubtleCrypto.deriveKey, SubtleCrypto.importKey, or SubtleCrypto.unwrapKey.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey)\n */\ndeclare abstract class CryptoKey {\n  /**\n   * The read-only **`type`** property of the CryptoKey interface indicates which kind of key is represented by the object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/type)\n   */\n  readonly type: string;\n  /**\n   * The read-only **`extractable`** property of the CryptoKey interface indicates whether or not the key may be extracted using `SubtleCrypto.exportKey()` or `SubtleCrypto.wrapKey()`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/extractable)\n   */\n  readonly extractable: boolean;\n  /**\n   * The read-only **`algorithm`** property of the CryptoKey interface returns an object describing the algorithm for which this key can be used, and any associated extra parameters.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/algorithm)\n   */\n  readonly algorithm:\n    | CryptoKeyKeyAlgorithm\n    | CryptoKeyAesKeyAlgorithm\n    | CryptoKeyHmacKeyAlgorithm\n    | CryptoKeyRsaKeyAlgorithm\n    | CryptoKeyEllipticKeyAlgorithm\n    | CryptoKeyArbitraryKeyAlgorithm;\n  /**\n   * The read-only **`usages`** property of the CryptoKey interface indicates what can be done with the key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/usages)\n   */\n  readonly usages: string[];\n}\ninterface CryptoKeyPair {\n  publicKey: CryptoKey;\n  privateKey: CryptoKey;\n}\ninterface JsonWebKey {\n  kty: string;\n  use?: string;\n  key_ops?: string[];\n  alg?: string;\n  ext?: boolean;\n  crv?: string;\n  x?: string;\n  y?: string;\n  d?: string;\n  n?: string;\n  e?: string;\n  p?: string;\n  q?: string;\n  dp?: string;\n  dq?: string;\n  qi?: string;\n  oth?: RsaOtherPrimesInfo[];\n  k?: string;\n}\ninterface RsaOtherPrimesInfo {\n  r?: string;\n  d?: string;\n  t?: string;\n}\ninterface SubtleCryptoDeriveKeyAlgorithm {\n  name: string;\n  salt?: ArrayBuffer | ArrayBufferView;\n  iterations?: number;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  $public?: CryptoKey;\n  info?: ArrayBuffer | ArrayBufferView;\n}\ninterface SubtleCryptoEncryptAlgorithm {\n  name: string;\n  iv?: ArrayBuffer | ArrayBufferView;\n  additionalData?: ArrayBuffer | ArrayBufferView;\n  tagLength?: number;\n  counter?: ArrayBuffer | ArrayBufferView;\n  length?: number;\n  label?: ArrayBuffer | ArrayBufferView;\n}\ninterface SubtleCryptoGenerateKeyAlgorithm {\n  name: string;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  modulusLength?: number;\n  publicExponent?: ArrayBuffer | ArrayBufferView;\n  length?: number;\n  namedCurve?: string;\n}\ninterface SubtleCryptoHashAlgorithm {\n  name: string;\n}\ninterface SubtleCryptoImportKeyAlgorithm {\n  name: string;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  length?: number;\n  namedCurve?: string;\n  compressed?: boolean;\n}\ninterface SubtleCryptoSignAlgorithm {\n  name: string;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  dataLength?: number;\n  saltLength?: number;\n}\ninterface CryptoKeyKeyAlgorithm {\n  name: string;\n}\ninterface CryptoKeyAesKeyAlgorithm {\n  name: string;\n  length: number;\n}\ninterface CryptoKeyHmacKeyAlgorithm {\n  name: string;\n  hash: CryptoKeyKeyAlgorithm;\n  length: number;\n}\ninterface CryptoKeyRsaKeyAlgorithm {\n  name: string;\n  modulusLength: number;\n  publicExponent: ArrayBuffer | ArrayBufferView;\n  hash?: CryptoKeyKeyAlgorithm;\n}\ninterface CryptoKeyEllipticKeyAlgorithm {\n  name: string;\n  namedCurve: string;\n}\ninterface CryptoKeyArbitraryKeyAlgorithm {\n  name: string;\n  hash?: CryptoKeyKeyAlgorithm;\n  namedCurve?: string;\n  length?: number;\n}\ndeclare class DigestStream extends WritableStream<\n  ArrayBuffer | ArrayBufferView\n> {\n  constructor(algorithm: string | SubtleCryptoHashAlgorithm);\n  readonly digest: Promise<ArrayBuffer>;\n  get bytesWritten(): number | bigint;\n}\n/**\n * The **`TextDecoder`** interface represents a decoder for a specific text encoding, such as `UTF-8`, `ISO-8859-2`, `KOI8-R`, `GBK`, etc.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder)\n */\ndeclare class TextDecoder {\n  constructor(label?: string, options?: TextDecoderConstructorOptions);\n  /**\n   * The **`TextDecoder.decode()`** method returns a string containing text decoded from the buffer passed as a parameter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder/decode)\n   */\n  decode(\n    input?: ArrayBuffer | ArrayBufferView,\n    options?: TextDecoderDecodeOptions,\n  ): string;\n  get encoding(): string;\n  get fatal(): boolean;\n  get ignoreBOM(): boolean;\n}\n/**\n * The **`TextEncoder`** interface takes a stream of code points as input and emits a stream of UTF-8 bytes.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder)\n */\ndeclare class TextEncoder {\n  constructor();\n  /**\n   * The **`TextEncoder.encode()`** method takes a string as input, and returns a Global_Objects/Uint8Array containing the text given in parameters encoded with the specific method for that TextEncoder object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encode)\n   */\n  encode(input?: string): Uint8Array;\n  /**\n   * The **`TextEncoder.encodeInto()`** method takes a string to encode and a destination Uint8Array to put resulting UTF-8 encoded text into, and returns a dictionary object indicating the progress of the encoding.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encodeInto)\n   */\n  encodeInto(input: string, buffer: Uint8Array): TextEncoderEncodeIntoResult;\n  get encoding(): string;\n}\ninterface TextDecoderConstructorOptions {\n  fatal: boolean;\n  ignoreBOM: boolean;\n}\ninterface TextDecoderDecodeOptions {\n  stream: boolean;\n}\ninterface TextEncoderEncodeIntoResult {\n  read: number;\n  written: number;\n}\n/**\n * The **`ErrorEvent`** interface represents events providing information related to errors in scripts or in files.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent)\n */\ndeclare class ErrorEvent extends Event {\n  constructor(type: string, init?: ErrorEventErrorEventInit);\n  /**\n   * The **`filename`** read-only property of the ErrorEvent interface returns a string containing the name of the script file in which the error occurred.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/filename)\n   */\n  get filename(): string;\n  /**\n   * The **`message`** read-only property of the ErrorEvent interface returns a string containing a human-readable error message describing the problem.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/message)\n   */\n  get message(): string;\n  /**\n   * The **`lineno`** read-only property of the ErrorEvent interface returns an integer containing the line number of the script file on which the error occurred.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/lineno)\n   */\n  get lineno(): number;\n  /**\n   * The **`colno`** read-only property of the ErrorEvent interface returns an integer containing the column number of the script file on which the error occurred.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/colno)\n   */\n  get colno(): number;\n  /**\n   * The **`error`** read-only property of the ErrorEvent interface returns a JavaScript value, such as an Error or DOMException, representing the error associated with this event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/error)\n   */\n  get error(): any;\n}\ninterface ErrorEventErrorEventInit {\n  message?: string;\n  filename?: string;\n  lineno?: number;\n  colno?: number;\n  error?: any;\n}\n/**\n * The **`MessageEvent`** interface represents a message received by a target object.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent)\n */\ndeclare class MessageEvent extends Event {\n  constructor(type: string, initializer: MessageEventInit);\n  /**\n   * The **`data`** read-only property of the The data sent by the message emitter; this can be any data type, depending on what originated this event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/data)\n   */\n  readonly data: any;\n  /**\n   * The **`origin`** read-only property of the origin of the message emitter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/origin)\n   */\n  readonly origin: string | null;\n  /**\n   * The **`lastEventId`** read-only property of the unique ID for the event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/lastEventId)\n   */\n  readonly lastEventId: string;\n  /**\n   * The **`source`** read-only property of the a WindowProxy, MessagePort, or a `MessageEventSource` (which can be a WindowProxy, message emitter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/source)\n   */\n  readonly source: MessagePort | null;\n  /**\n   * The **`ports`** read-only property of the containing all MessagePort objects sent with the message, in order.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/ports)\n   */\n  readonly ports: MessagePort[];\n}\ninterface MessageEventInit {\n  data: ArrayBuffer | string;\n}\n/**\n * The **`PromiseRejectionEvent`** interface represents events which are sent to the global script context when JavaScript Promises are rejected.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent)\n */\ndeclare abstract class PromiseRejectionEvent extends Event {\n  /**\n   * The PromiseRejectionEvent interface's **`promise`** read-only property indicates the JavaScript rejected.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/promise)\n   */\n  readonly promise: Promise<any>;\n  /**\n   * The PromiseRejectionEvent **`reason`** read-only property is any JavaScript value or Object which provides the reason passed into Promise.reject().\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/reason)\n   */\n  readonly reason: any;\n}\n/**\n * The **`FormData`** interface provides a way to construct a set of key/value pairs representing form fields and their values, which can be sent using the Window/fetch, XMLHttpRequest.send() or navigator.sendBeacon() methods.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData)\n */\ndeclare class FormData {\n  constructor();\n  /**\n   * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append)\n   */\n  append(name: string, value: string | Blob): void;\n  /**\n   * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append)\n   */\n  append(name: string, value: string): void;\n  /**\n   * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append)\n   */\n  append(name: string, value: Blob, filename?: string): void;\n  /**\n   * The **`delete()`** method of the FormData interface deletes a key and its value(s) from a `FormData` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/delete)\n   */\n  delete(name: string): void;\n  /**\n   * The **`get()`** method of the FormData interface returns the first value associated with a given key from within a `FormData` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/get)\n   */\n  get(name: string): (File | string) | null;\n  /**\n   * The **`getAll()`** method of the FormData interface returns all the values associated with a given key from within a `FormData` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/getAll)\n   */\n  getAll(name: string): (File | string)[];\n  /**\n   * The **`has()`** method of the FormData interface returns whether a `FormData` object contains a certain key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/has)\n   */\n  has(name: string): boolean;\n  /**\n   * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set)\n   */\n  set(name: string, value: string | Blob): void;\n  /**\n   * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set)\n   */\n  set(name: string, value: string): void;\n  /**\n   * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set)\n   */\n  set(name: string, value: Blob, filename?: string): void;\n  /* Returns an array of key, value pairs for every entry in the list. */\n  entries(): IterableIterator<[key: string, value: File | string]>;\n  /* Returns a list of keys in the list. */\n  keys(): IterableIterator<string>;\n  /* Returns a list of values in the list. */\n  values(): IterableIterator<File | string>;\n  forEach<This = unknown>(\n    callback: (\n      this: This,\n      value: File | string,\n      key: string,\n      parent: FormData,\n    ) => void,\n    thisArg?: This,\n  ): void;\n  [Symbol.iterator](): IterableIterator<[key: string, value: File | string]>;\n}\ninterface ContentOptions {\n  html?: boolean;\n}\ndeclare class HTMLRewriter {\n  constructor();\n  on(\n    selector: string,\n    handlers: HTMLRewriterElementContentHandlers,\n  ): HTMLRewriter;\n  onDocument(handlers: HTMLRewriterDocumentContentHandlers): HTMLRewriter;\n  transform(response: Response): Response;\n}\ninterface HTMLRewriterElementContentHandlers {\n  element?(element: Element): void | Promise<void>;\n  comments?(comment: Comment): void | Promise<void>;\n  text?(element: Text): void | Promise<void>;\n}\ninterface HTMLRewriterDocumentContentHandlers {\n  doctype?(doctype: Doctype): void | Promise<void>;\n  comments?(comment: Comment): void | Promise<void>;\n  text?(text: Text): void | Promise<void>;\n  end?(end: DocumentEnd): void | Promise<void>;\n}\ninterface Doctype {\n  readonly name: string | null;\n  readonly publicId: string | null;\n  readonly systemId: string | null;\n}\ninterface Element {\n  tagName: string;\n  readonly attributes: IterableIterator<string[]>;\n  readonly removed: boolean;\n  readonly namespaceURI: string;\n  getAttribute(name: string): string | null;\n  hasAttribute(name: string): boolean;\n  setAttribute(name: string, value: string): Element;\n  removeAttribute(name: string): Element;\n  before(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  after(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  prepend(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  append(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  replace(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  remove(): Element;\n  removeAndKeepContent(): Element;\n  setInnerContent(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  onEndTag(handler: (tag: EndTag) => void | Promise<void>): void;\n}\ninterface EndTag {\n  name: string;\n  before(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): EndTag;\n  after(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): EndTag;\n  remove(): EndTag;\n}\ninterface Comment {\n  text: string;\n  readonly removed: boolean;\n  before(content: string, options?: ContentOptions): Comment;\n  after(content: string, options?: ContentOptions): Comment;\n  replace(content: string, options?: ContentOptions): Comment;\n  remove(): Comment;\n}\ninterface Text {\n  readonly text: string;\n  readonly lastInTextNode: boolean;\n  readonly removed: boolean;\n  before(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Text;\n  after(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Text;\n  replace(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Text;\n  remove(): Text;\n}\ninterface DocumentEnd {\n  append(content: string, options?: ContentOptions): DocumentEnd;\n}\n/**\n * This is the event type for `fetch` events dispatched on the ServiceWorkerGlobalScope.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent)\n */\ndeclare abstract class FetchEvent extends ExtendableEvent {\n  /**\n   * The **`request`** read-only property of the the event handler.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/request)\n   */\n  readonly request: Request;\n  /**\n   * The **`respondWith()`** method of allows you to provide a promise for a Response yourself.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/respondWith)\n   */\n  respondWith(promise: Response | Promise<Response>): void;\n  passThroughOnException(): void;\n}\ntype HeadersInit =\n  | Headers\n  | Iterable<Iterable<string>>\n  | Record<string, string>;\n/**\n * The **`Headers`** interface of the Fetch API allows you to perform various actions on HTTP request and response headers.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers)\n */\ndeclare class Headers {\n  constructor(init?: HeadersInit);\n  /**\n   * The **`get()`** method of the Headers interface returns a byte string of all the values of a header within a `Headers` object with a given name.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/get)\n   */\n  get(name: string): string | null;\n  getAll(name: string): string[];\n  /**\n   * The **`getSetCookie()`** method of the Headers interface returns an array containing the values of all Set-Cookie headers associated with a response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/getSetCookie)\n   */\n  getSetCookie(): string[];\n  /**\n   * The **`has()`** method of the Headers interface returns a boolean stating whether a `Headers` object contains a certain header.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/has)\n   */\n  has(name: string): boolean;\n  /**\n   * The **`set()`** method of the Headers interface sets a new value for an existing header inside a `Headers` object, or adds the header if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/set)\n   */\n  set(name: string, value: string): void;\n  /**\n   * The **`append()`** method of the Headers interface appends a new value onto an existing header inside a `Headers` object, or adds the header if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/append)\n   */\n  append(name: string, value: string): void;\n  /**\n   * The **`delete()`** method of the Headers interface deletes a header from the current `Headers` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/delete)\n   */\n  delete(name: string): void;\n  forEach<This = unknown>(\n    callback: (this: This, value: string, key: string, parent: Headers) => void,\n    thisArg?: This,\n  ): void;\n  /* Returns an iterator allowing to go through all key/value pairs contained in this object. */\n  entries(): IterableIterator<[key: string, value: string]>;\n  /* Returns an iterator allowing to go through all keys of the key/value pairs contained in this object. */\n  keys(): IterableIterator<string>;\n  /* Returns an iterator allowing to go through all values of the key/value pairs contained in this object. */\n  values(): IterableIterator<string>;\n  [Symbol.iterator](): IterableIterator<[key: string, value: string]>;\n}\ntype BodyInit =\n  | ReadableStream<Uint8Array>\n  | string\n  | ArrayBuffer\n  | ArrayBufferView\n  | Blob\n  | URLSearchParams\n  | FormData\n  | Iterable<ArrayBuffer | ArrayBufferView>\n  | AsyncIterable<ArrayBuffer | ArrayBufferView>;\ndeclare abstract class Body {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body) */\n  get body(): ReadableStream | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) */\n  get bodyUsed(): boolean;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/arrayBuffer) */\n  arrayBuffer(): Promise<ArrayBuffer>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bytes) */\n  bytes(): Promise<Uint8Array>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/text) */\n  text(): Promise<string>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */\n  json<T>(): Promise<T>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/formData) */\n  formData(): Promise<FormData>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/blob) */\n  blob(): Promise<Blob>;\n}\n/**\n * The **`Response`** interface of the Fetch API represents the response to a request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response)\n */\ndeclare var Response: {\n  prototype: Response;\n  new (body?: BodyInit | null, init?: ResponseInit): Response;\n  error(): Response;\n  redirect(url: string, status?: number): Response;\n  json(any: any, maybeInit?: ResponseInit | Response): Response;\n};\n/**\n * The **`Response`** interface of the Fetch API represents the response to a request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response)\n */\ninterface Response extends Body {\n  /**\n   * The **`clone()`** method of the Response interface creates a clone of a response object, identical in every way, but stored in a different variable.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/clone)\n   */\n  clone(): Response;\n  /**\n   * The **`status`** read-only property of the Response interface contains the HTTP status codes of the response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/status)\n   */\n  status: number;\n  /**\n   * The **`statusText`** read-only property of the Response interface contains the status message corresponding to the HTTP status code in Response.status.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/statusText)\n   */\n  statusText: string;\n  /**\n   * The **`headers`** read-only property of the with the response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/headers)\n   */\n  headers: Headers;\n  /**\n   * The **`ok`** read-only property of the Response interface contains a Boolean stating whether the response was successful (status in the range 200-299) or not.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/ok)\n   */\n  ok: boolean;\n  /**\n   * The **`redirected`** read-only property of the Response interface indicates whether or not the response is the result of a request you made which was redirected.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/redirected)\n   */\n  redirected: boolean;\n  /**\n   * The **`url`** read-only property of the Response interface contains the URL of the response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/url)\n   */\n  url: string;\n  webSocket: WebSocket | null;\n  cf: any | undefined;\n  /**\n   * The **`type`** read-only property of the Response interface contains the type of the response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/type)\n   */\n  type: \"default\" | \"error\";\n}\ninterface ResponseInit {\n  status?: number;\n  statusText?: string;\n  headers?: HeadersInit;\n  cf?: any;\n  webSocket?: WebSocket | null;\n  encodeBody?: \"automatic\" | \"manual\";\n}\ntype RequestInfo<CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>> =\n  | Request<CfHostMetadata, Cf>\n  | string;\n/**\n * The **`Request`** interface of the Fetch API represents a resource request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request)\n */\ndeclare var Request: {\n  prototype: Request;\n  new <CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>>(\n    input: RequestInfo<CfProperties> | URL,\n    init?: RequestInit<Cf>,\n  ): Request<CfHostMetadata, Cf>;\n};\n/**\n * The **`Request`** interface of the Fetch API represents a resource request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request)\n */\ninterface Request<\n  CfHostMetadata = unknown,\n  Cf = CfProperties<CfHostMetadata>,\n> extends Body {\n  /**\n   * The **`clone()`** method of the Request interface creates a copy of the current `Request` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/clone)\n   */\n  clone(): Request<CfHostMetadata, Cf>;\n  /**\n   * The **`method`** read-only property of the `POST`, etc.) A String indicating the method of the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/method)\n   */\n  method: string;\n  /**\n   * The **`url`** read-only property of the Request interface contains the URL of the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/url)\n   */\n  url: string;\n  /**\n   * The **`headers`** read-only property of the with the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/headers)\n   */\n  headers: Headers;\n  /**\n   * The **`redirect`** read-only property of the Request interface contains the mode for how redirects are handled.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/redirect)\n   */\n  redirect: string;\n  fetcher: Fetcher | null;\n  /**\n   * The read-only **`signal`** property of the Request interface returns the AbortSignal associated with the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/signal)\n   */\n  signal: AbortSignal;\n  cf?: Cf;\n  /**\n   * The **`integrity`** read-only property of the Request interface contains the subresource integrity value of the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/integrity)\n   */\n  integrity: string;\n  /**\n   * The **`keepalive`** read-only property of the Request interface contains the request's `keepalive` setting (`true` or `false`), which indicates whether the browser will keep the associated request alive if the page that initiated it is unloaded before the request is complete.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/keepalive)\n   */\n  keepalive: boolean;\n  /**\n   * The **`cache`** read-only property of the Request interface contains the cache mode of the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/cache)\n   */\n  cache?: \"no-store\" | \"no-cache\" | \"reload\";\n}\ninterface RequestInit<Cf = CfProperties> {\n  /* A string to set request's method. */\n  method?: string;\n  /* A Headers object, an object literal, or an array of two-item arrays to set request's headers. */\n  headers?: HeadersInit;\n  /* A BodyInit object or null to set request's body. */\n  body?: BodyInit | null;\n  /* A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */\n  redirect?: string;\n  fetcher?: Fetcher | null;\n  cf?: Cf;\n  /* A string indicating how the request will interact with the browser's cache to set request's cache. */\n  cache?: \"no-store\" | \"no-cache\" | \"reload\";\n  /* A cryptographic hash of the resource to be fetched by request. Sets request's integrity. */\n  integrity?: string;\n  /* An AbortSignal to set request's signal. */\n  signal?: AbortSignal | null;\n  encodeResponseBody?: \"automatic\" | \"manual\";\n}\ntype Service<\n  T extends\n    | (new (...args: any[]) => Rpc.WorkerEntrypointBranded)\n    | Rpc.WorkerEntrypointBranded\n    | ExportedHandler<any, any, any>\n    | undefined = undefined,\n> = T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded\n  ? Fetcher<InstanceType<T>>\n  : T extends Rpc.WorkerEntrypointBranded\n    ? Fetcher<T>\n    : T extends Exclude<Rpc.EntrypointBranded, Rpc.WorkerEntrypointBranded>\n      ? never\n      : Fetcher<undefined>;\ntype Fetcher<\n  T extends Rpc.EntrypointBranded | undefined = undefined,\n  Reserved extends string = never,\n> = (T extends Rpc.EntrypointBranded\n  ? Rpc.Provider<T, Reserved | \"fetch\" | \"connect\" | \"queue\" | \"scheduled\">\n  : unknown) & {\n  fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;\n  connect(address: SocketAddress | string, options?: SocketOptions): Socket;\n  queue(\n    queueName: string,\n    messages: ServiceBindingQueueMessage[],\n  ): Promise<FetcherQueueResult>;\n  scheduled(options?: FetcherScheduledOptions): Promise<FetcherScheduledResult>;\n};\ninterface FetcherScheduledOptions {\n  scheduledTime?: Date;\n  cron?: string;\n}\ninterface FetcherScheduledResult {\n  outcome: string;\n  noRetry: boolean;\n}\ninterface FetcherQueueResult {\n  outcome: string;\n  ackAll: boolean;\n  retryBatch: QueueRetryBatch;\n  explicitAcks: string[];\n  retryMessages: QueueRetryMessage[];\n}\ntype ServiceBindingQueueMessage<Body = unknown> = {\n  id: string;\n  timestamp: Date;\n  attempts: number;\n} & (\n  | {\n      body: Body;\n    }\n  | {\n      serializedBody: ArrayBuffer | ArrayBufferView;\n    }\n);\ninterface KVNamespaceListKey<Metadata, Key extends string = string> {\n  name: Key;\n  expiration?: number;\n  metadata?: Metadata;\n}\ntype KVNamespaceListResult<Metadata, Key extends string = string> =\n  | {\n      list_complete: false;\n      keys: KVNamespaceListKey<Metadata, Key>[];\n      cursor: string;\n      cacheStatus: string | null;\n    }\n  | {\n      list_complete: true;\n      keys: KVNamespaceListKey<Metadata, Key>[];\n      cacheStatus: string | null;\n    };\ninterface KVNamespace<Key extends string = string> {\n  get(\n    key: Key,\n    options?: Partial<KVNamespaceGetOptions<undefined>>,\n  ): Promise<string | null>;\n  get(key: Key, type: \"text\"): Promise<string | null>;\n  get<ExpectedValue = unknown>(\n    key: Key,\n    type: \"json\",\n  ): Promise<ExpectedValue | null>;\n  get(key: Key, type: \"arrayBuffer\"): Promise<ArrayBuffer | null>;\n  get(key: Key, type: \"stream\"): Promise<ReadableStream | null>;\n  get(\n    key: Key,\n    options?: KVNamespaceGetOptions<\"text\">,\n  ): Promise<string | null>;\n  get<ExpectedValue = unknown>(\n    key: Key,\n    options?: KVNamespaceGetOptions<\"json\">,\n  ): Promise<ExpectedValue | null>;\n  get(\n    key: Key,\n    options?: KVNamespaceGetOptions<\"arrayBuffer\">,\n  ): Promise<ArrayBuffer | null>;\n  get(\n    key: Key,\n    options?: KVNamespaceGetOptions<\"stream\">,\n  ): Promise<ReadableStream | null>;\n  get(key: Array<Key>, type: \"text\"): Promise<Map<string, string | null>>;\n  get<ExpectedValue = unknown>(\n    key: Array<Key>,\n    type: \"json\",\n  ): Promise<Map<string, ExpectedValue | null>>;\n  get(\n    key: Array<Key>,\n    options?: Partial<KVNamespaceGetOptions<undefined>>,\n  ): Promise<Map<string, string | null>>;\n  get(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<\"text\">,\n  ): Promise<Map<string, string | null>>;\n  get<ExpectedValue = unknown>(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<\"json\">,\n  ): Promise<Map<string, ExpectedValue | null>>;\n  list<Metadata = unknown>(\n    options?: KVNamespaceListOptions,\n  ): Promise<KVNamespaceListResult<Metadata, Key>>;\n  put(\n    key: Key,\n    value: string | ArrayBuffer | ArrayBufferView | ReadableStream,\n    options?: KVNamespacePutOptions,\n  ): Promise<void>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options?: Partial<KVNamespaceGetOptions<undefined>>,\n  ): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    type: \"text\",\n  ): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Key,\n    type: \"json\",\n  ): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    type: \"arrayBuffer\",\n  ): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    type: \"stream\",\n  ): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<\"text\">,\n  ): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<\"json\">,\n  ): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<\"arrayBuffer\">,\n  ): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<\"stream\">,\n  ): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Array<Key>,\n    type: \"text\",\n  ): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Array<Key>,\n    type: \"json\",\n  ): Promise<\n    Map<string, KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>\n  >;\n  getWithMetadata<Metadata = unknown>(\n    key: Array<Key>,\n    options?: Partial<KVNamespaceGetOptions<undefined>>,\n  ): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<\"text\">,\n  ): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<\"json\">,\n  ): Promise<\n    Map<string, KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>\n  >;\n  delete(key: Key): Promise<void>;\n  deleteBulk(keys: Key | Key[]): Promise<void>;\n}\ninterface KVNamespaceListOptions {\n  limit?: number;\n  prefix?: string | null;\n  cursor?: string | null;\n}\ninterface KVNamespaceGetOptions<Type> {\n  type: Type;\n  cacheTtl?: number;\n}\ninterface KVNamespacePutOptions {\n  expiration?: number;\n  expirationTtl?: number;\n  metadata?: any | null;\n}\ninterface KVNamespaceGetWithMetadataResult<Value, Metadata> {\n  value: Value | null;\n  metadata: Metadata | null;\n  cacheStatus: string | null;\n}\ntype QueueContentType = \"text\" | \"bytes\" | \"json\" | \"v8\";\ninterface Queue<Body = unknown> {\n  send(message: Body, options?: QueueSendOptions): Promise<void>;\n  sendBatch(\n    messages: Iterable<MessageSendRequest<Body>>,\n    options?: QueueSendBatchOptions,\n  ): Promise<void>;\n}\ninterface QueueSendOptions {\n  contentType?: QueueContentType;\n  delaySeconds?: number;\n}\ninterface QueueSendBatchOptions {\n  delaySeconds?: number;\n}\ninterface MessageSendRequest<Body = unknown> {\n  body: Body;\n  contentType?: QueueContentType;\n  delaySeconds?: number;\n}\ninterface QueueRetryBatch {\n  retry: boolean;\n  delaySeconds?: number;\n}\ninterface QueueRetryMessage {\n  msgId: string;\n  delaySeconds?: number;\n}\ninterface QueueRetryOptions {\n  delaySeconds?: number;\n}\ninterface Message<Body = unknown> {\n  readonly id: string;\n  readonly timestamp: Date;\n  readonly body: Body;\n  readonly attempts: number;\n  retry(options?: QueueRetryOptions): void;\n  ack(): void;\n}\ninterface QueueEvent<Body = unknown> extends ExtendableEvent {\n  readonly messages: readonly Message<Body>[];\n  readonly queue: string;\n  retryAll(options?: QueueRetryOptions): void;\n  ackAll(): void;\n}\ninterface MessageBatch<Body = unknown> {\n  readonly messages: readonly Message<Body>[];\n  readonly queue: string;\n  retryAll(options?: QueueRetryOptions): void;\n  ackAll(): void;\n}\ninterface R2Error extends Error {\n  readonly name: string;\n  readonly code: number;\n  readonly message: string;\n  readonly action: string;\n  readonly stack: any;\n}\ninterface R2ListOptions {\n  limit?: number;\n  prefix?: string;\n  cursor?: string;\n  delimiter?: string;\n  startAfter?: string;\n  include?: (\"httpMetadata\" | \"customMetadata\")[];\n}\ndeclare abstract class R2Bucket {\n  head(key: string): Promise<R2Object | null>;\n  get(\n    key: string,\n    options: R2GetOptions & {\n      onlyIf: R2Conditional | Headers;\n    },\n  ): Promise<R2ObjectBody | R2Object | null>;\n  get(key: string, options?: R2GetOptions): Promise<R2ObjectBody | null>;\n  put(\n    key: string,\n    value:\n      | ReadableStream\n      | ArrayBuffer\n      | ArrayBufferView\n      | string\n      | null\n      | Blob,\n    options?: R2PutOptions & {\n      onlyIf: R2Conditional | Headers;\n    },\n  ): Promise<R2Object | null>;\n  put(\n    key: string,\n    value:\n      | ReadableStream\n      | ArrayBuffer\n      | ArrayBufferView\n      | string\n      | null\n      | Blob,\n    options?: R2PutOptions,\n  ): Promise<R2Object>;\n  createMultipartUpload(\n    key: string,\n    options?: R2MultipartOptions,\n  ): Promise<R2MultipartUpload>;\n  resumeMultipartUpload(key: string, uploadId: string): R2MultipartUpload;\n  delete(keys: string | string[]): Promise<void>;\n  list(options?: R2ListOptions): Promise<R2Objects>;\n}\ninterface R2MultipartUpload {\n  readonly key: string;\n  readonly uploadId: string;\n  uploadPart(\n    partNumber: number,\n    value: ReadableStream | (ArrayBuffer | ArrayBufferView) | string | Blob,\n    options?: R2UploadPartOptions,\n  ): Promise<R2UploadedPart>;\n  abort(): Promise<void>;\n  complete(uploadedParts: R2UploadedPart[]): Promise<R2Object>;\n}\ninterface R2UploadedPart {\n  partNumber: number;\n  etag: string;\n}\ndeclare abstract class R2Object {\n  readonly key: string;\n  readonly version: string;\n  readonly size: number;\n  readonly etag: string;\n  readonly httpEtag: string;\n  readonly checksums: R2Checksums;\n  readonly uploaded: Date;\n  readonly httpMetadata?: R2HTTPMetadata;\n  readonly customMetadata?: Record<string, string>;\n  readonly range?: R2Range;\n  readonly storageClass: string;\n  readonly ssecKeyMd5?: string;\n  writeHttpMetadata(headers: Headers): void;\n}\ninterface R2ObjectBody extends R2Object {\n  get body(): ReadableStream;\n  get bodyUsed(): boolean;\n  arrayBuffer(): Promise<ArrayBuffer>;\n  bytes(): Promise<Uint8Array>;\n  text(): Promise<string>;\n  json<T>(): Promise<T>;\n  blob(): Promise<Blob>;\n}\ntype R2Range =\n  | {\n      offset: number;\n      length?: number;\n    }\n  | {\n      offset?: number;\n      length: number;\n    }\n  | {\n      suffix: number;\n    };\ninterface R2Conditional {\n  etagMatches?: string;\n  etagDoesNotMatch?: string;\n  uploadedBefore?: Date;\n  uploadedAfter?: Date;\n  secondsGranularity?: boolean;\n}\ninterface R2GetOptions {\n  onlyIf?: R2Conditional | Headers;\n  range?: R2Range | Headers;\n  ssecKey?: ArrayBuffer | string;\n}\ninterface R2PutOptions {\n  onlyIf?: R2Conditional | Headers;\n  httpMetadata?: R2HTTPMetadata | Headers;\n  customMetadata?: Record<string, string>;\n  md5?: (ArrayBuffer | ArrayBufferView) | string;\n  sha1?: (ArrayBuffer | ArrayBufferView) | string;\n  sha256?: (ArrayBuffer | ArrayBufferView) | string;\n  sha384?: (ArrayBuffer | ArrayBufferView) | string;\n  sha512?: (ArrayBuffer | ArrayBufferView) | string;\n  storageClass?: string;\n  ssecKey?: ArrayBuffer | string;\n}\ninterface R2MultipartOptions {\n  httpMetadata?: R2HTTPMetadata | Headers;\n  customMetadata?: Record<string, string>;\n  storageClass?: string;\n  ssecKey?: ArrayBuffer | string;\n}\ninterface R2Checksums {\n  readonly md5?: ArrayBuffer;\n  readonly sha1?: ArrayBuffer;\n  readonly sha256?: ArrayBuffer;\n  readonly sha384?: ArrayBuffer;\n  readonly sha512?: ArrayBuffer;\n  toJSON(): R2StringChecksums;\n}\ninterface R2StringChecksums {\n  md5?: string;\n  sha1?: string;\n  sha256?: string;\n  sha384?: string;\n  sha512?: string;\n}\ninterface R2HTTPMetadata {\n  contentType?: string;\n  contentLanguage?: string;\n  contentDisposition?: string;\n  contentEncoding?: string;\n  cacheControl?: string;\n  cacheExpiry?: Date;\n}\ntype R2Objects = {\n  objects: R2Object[];\n  delimitedPrefixes: string[];\n} & (\n  | {\n      truncated: true;\n      cursor: string;\n    }\n  | {\n      truncated: false;\n    }\n);\ninterface R2UploadPartOptions {\n  ssecKey?: ArrayBuffer | string;\n}\ndeclare abstract class JsRpcPromise {\n  then(handler: Function, errorHandler?: Function): any;\n  catch(errorHandler: Function): any;\n  finally(onFinally: Function): any;\n}\ndeclare abstract class JsRpcProperty {\n  then(handler: Function, errorHandler?: Function): any;\n  catch(errorHandler: Function): any;\n  finally(onFinally: Function): any;\n}\ndeclare abstract class ScheduledEvent extends ExtendableEvent {\n  readonly scheduledTime: number;\n  readonly cron: string;\n  noRetry(): void;\n}\ninterface ScheduledController {\n  readonly scheduledTime: number;\n  readonly cron: string;\n  noRetry(): void;\n}\ninterface QueuingStrategy<T = any> {\n  highWaterMark?: number | bigint;\n  size?: (chunk: T) => number | bigint;\n}\ninterface UnderlyingSink<W = any> {\n  type?: string;\n  start?: (controller: WritableStreamDefaultController) => void | Promise<void>;\n  write?: (\n    chunk: W,\n    controller: WritableStreamDefaultController,\n  ) => void | Promise<void>;\n  abort?: (reason: any) => void | Promise<void>;\n  close?: () => void | Promise<void>;\n}\ninterface UnderlyingByteSource {\n  type: \"bytes\";\n  autoAllocateChunkSize?: number;\n  start?: (controller: ReadableByteStreamController) => void | Promise<void>;\n  pull?: (controller: ReadableByteStreamController) => void | Promise<void>;\n  cancel?: (reason: any) => void | Promise<void>;\n}\ninterface UnderlyingSource<R = any> {\n  type?: \"\" | undefined;\n  start?: (\n    controller: ReadableStreamDefaultController<R>,\n  ) => void | Promise<void>;\n  pull?: (\n    controller: ReadableStreamDefaultController<R>,\n  ) => void | Promise<void>;\n  cancel?: (reason: any) => void | Promise<void>;\n  expectedLength?: number | bigint;\n}\ninterface Transformer<I = any, O = any> {\n  readableType?: string;\n  writableType?: string;\n  start?: (\n    controller: TransformStreamDefaultController<O>,\n  ) => void | Promise<void>;\n  transform?: (\n    chunk: I,\n    controller: TransformStreamDefaultController<O>,\n  ) => void | Promise<void>;\n  flush?: (\n    controller: TransformStreamDefaultController<O>,\n  ) => void | Promise<void>;\n  cancel?: (reason: any) => void | Promise<void>;\n  expectedLength?: number;\n}\ninterface StreamPipeOptions {\n  preventAbort?: boolean;\n  preventCancel?: boolean;\n  /**\n   * Pipes this readable stream to a given writable stream destination. The way in which the piping process behaves under various error conditions can be customized with a number of passed options. It returns a promise that fulfills when the piping process completes successfully, or rejects if any errors were encountered.\n   *\n   * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader.\n   *\n   * Errors and closures of the source and destination streams propagate as follows:\n   *\n   * An error in this source readable stream will abort destination, unless preventAbort is truthy. The returned promise will be rejected with the source's error, or with any error that occurs during aborting the destination.\n   *\n   * An error in destination will cancel this source readable stream, unless preventCancel is truthy. The returned promise will be rejected with the destination's error, or with any error that occurs during canceling the source.\n   *\n   * When this source readable stream closes, destination will be closed, unless preventClose is truthy. The returned promise will be fulfilled once this process completes, unless an error is encountered while closing the destination, in which case it will be rejected with that error.\n   *\n   * If destination starts out closed or closing, this source readable stream will be canceled, unless preventCancel is true. The returned promise will be rejected with an error indicating piping to a closed stream failed, or with any error that occurs during canceling the source.\n   *\n   * The signal option can be set to an AbortSignal to allow aborting an ongoing pipe operation via the corresponding AbortController. In this case, this source readable stream will be canceled, and destination aborted, unless the respective options preventCancel or preventAbort are set.\n   */\n  preventClose?: boolean;\n  signal?: AbortSignal;\n}\ntype ReadableStreamReadResult<R = any> =\n  | {\n      done: false;\n      value: R;\n    }\n  | {\n      done: true;\n      value?: undefined;\n    };\n/**\n * The `ReadableStream` interface of the Streams API represents a readable stream of byte data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream)\n */\ninterface ReadableStream<R = any> {\n  /**\n   * The **`locked`** read-only property of the ReadableStream interface returns whether or not the readable stream is locked to a reader.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/locked)\n   */\n  get locked(): boolean;\n  /**\n   * The **`cancel()`** method of the ReadableStream interface returns a Promise that resolves when the stream is canceled.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/cancel)\n   */\n  cancel(reason?: any): Promise<void>;\n  /**\n   * The **`getReader()`** method of the ReadableStream interface creates a reader and locks the stream to it.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader)\n   */\n  getReader(): ReadableStreamDefaultReader<R>;\n  /**\n   * The **`getReader()`** method of the ReadableStream interface creates a reader and locks the stream to it.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader)\n   */\n  getReader(options: ReadableStreamGetReaderOptions): ReadableStreamBYOBReader;\n  /**\n   * The **`pipeThrough()`** method of the ReadableStream interface provides a chainable way of piping the current stream through a transform stream or any other writable/readable pair.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeThrough)\n   */\n  pipeThrough<T>(\n    transform: ReadableWritablePair<T, R>,\n    options?: StreamPipeOptions,\n  ): ReadableStream<T>;\n  /**\n   * The **`pipeTo()`** method of the ReadableStream interface pipes the current `ReadableStream` to a given WritableStream and returns a Promise that fulfills when the piping process completes successfully, or rejects if any errors were encountered.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeTo)\n   */\n  pipeTo(\n    destination: WritableStream<R>,\n    options?: StreamPipeOptions,\n  ): Promise<void>;\n  /**\n   * The **`tee()`** method of the two-element array containing the two resulting branches as new ReadableStream instances.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/tee)\n   */\n  tee(): [ReadableStream<R>, ReadableStream<R>];\n  values(options?: ReadableStreamValuesOptions): AsyncIterableIterator<R>;\n  [Symbol.asyncIterator](\n    options?: ReadableStreamValuesOptions,\n  ): AsyncIterableIterator<R>;\n}\n/**\n * The `ReadableStream` interface of the Streams API represents a readable stream of byte data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream)\n */\ndeclare const ReadableStream: {\n  prototype: ReadableStream;\n  new (\n    underlyingSource: UnderlyingByteSource,\n    strategy?: QueuingStrategy<Uint8Array>,\n  ): ReadableStream<Uint8Array>;\n  new <R = any>(\n    underlyingSource?: UnderlyingSource<R>,\n    strategy?: QueuingStrategy<R>,\n  ): ReadableStream<R>;\n};\n/**\n * The **`ReadableStreamDefaultReader`** interface of the Streams API represents a default reader that can be used to read stream data supplied from a network (such as a fetch request).\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader)\n */\ndeclare class ReadableStreamDefaultReader<R = any> {\n  constructor(stream: ReadableStream);\n  get closed(): Promise<void>;\n  cancel(reason?: any): Promise<void>;\n  /**\n   * The **`read()`** method of the ReadableStreamDefaultReader interface returns a Promise providing access to the next chunk in the stream's internal queue.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/read)\n   */\n  read(): Promise<ReadableStreamReadResult<R>>;\n  /**\n   * The **`releaseLock()`** method of the ReadableStreamDefaultReader interface releases the reader's lock on the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/releaseLock)\n   */\n  releaseLock(): void;\n}\n/**\n * The `ReadableStreamBYOBReader` interface of the Streams API defines a reader for a ReadableStream that supports zero-copy reading from an underlying byte source.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader)\n */\ndeclare class ReadableStreamBYOBReader {\n  constructor(stream: ReadableStream);\n  get closed(): Promise<void>;\n  cancel(reason?: any): Promise<void>;\n  /**\n   * The **`read()`** method of the ReadableStreamBYOBReader interface is used to read data into a view on a user-supplied buffer from an associated readable byte stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/read)\n   */\n  read<T extends ArrayBufferView>(\n    view: T,\n  ): Promise<ReadableStreamReadResult<T>>;\n  /**\n   * The **`releaseLock()`** method of the ReadableStreamBYOBReader interface releases the reader's lock on the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/releaseLock)\n   */\n  releaseLock(): void;\n  readAtLeast<T extends ArrayBufferView>(\n    minElements: number,\n    view: T,\n  ): Promise<ReadableStreamReadResult<T>>;\n}\ninterface ReadableStreamBYOBReaderReadableStreamBYOBReaderReadOptions {\n  min?: number;\n}\ninterface ReadableStreamGetReaderOptions {\n  /**\n   * Creates a ReadableStreamBYOBReader and locks the stream to the new reader.\n   *\n   * This call behaves the same way as the no-argument variant, except that it only works on readable byte streams, i.e. streams which were constructed specifically with the ability to handle \"bring your own buffer\" reading. The returned BYOB reader provides the ability to directly read individual chunks from the stream via its read() method, into developer-supplied buffers, allowing more precise control over allocation.\n   */\n  mode: \"byob\";\n}\n/**\n * The **`ReadableStreamBYOBRequest`** interface of the Streams API represents a 'pull request' for data from an underlying source that will made as a zero-copy transfer to a consumer (bypassing the stream's internal queues).\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest)\n */\ndeclare abstract class ReadableStreamBYOBRequest {\n  /**\n   * The **`view`** getter property of the ReadableStreamBYOBRequest interface returns the current view.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/view)\n   */\n  get view(): Uint8Array | null;\n  /**\n   * The **`respond()`** method of the ReadableStreamBYOBRequest interface is used to signal to the associated readable byte stream that the specified number of bytes were written into the ReadableStreamBYOBRequest.view.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respond)\n   */\n  respond(bytesWritten: number): void;\n  /**\n   * The **`respondWithNewView()`** method of the ReadableStreamBYOBRequest interface specifies a new view that the consumer of the associated readable byte stream should write to instead of ReadableStreamBYOBRequest.view.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respondWithNewView)\n   */\n  respondWithNewView(view: ArrayBuffer | ArrayBufferView): void;\n  get atLeast(): number | null;\n}\n/**\n * The **`ReadableStreamDefaultController`** interface of the Streams API represents a controller allowing control of a ReadableStream's state and internal queue.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController)\n */\ndeclare abstract class ReadableStreamDefaultController<R = any> {\n  /**\n   * The **`desiredSize`** read-only property of the required to fill the stream's internal queue.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/desiredSize)\n   */\n  get desiredSize(): number | null;\n  /**\n   * The **`close()`** method of the ReadableStreamDefaultController interface closes the associated stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/close)\n   */\n  close(): void;\n  /**\n   * The **`enqueue()`** method of the ```js-nolint enqueue(chunk) ``` - `chunk` - : The chunk to enqueue.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/enqueue)\n   */\n  enqueue(chunk?: R): void;\n  /**\n   * The **`error()`** method of the with the associated stream to error.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/error)\n   */\n  error(reason: any): void;\n}\n/**\n * The **`ReadableByteStreamController`** interface of the Streams API represents a controller for a readable byte stream.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController)\n */\ndeclare abstract class ReadableByteStreamController {\n  /**\n   * The **`byobRequest`** read-only property of the ReadableByteStreamController interface returns the current BYOB request, or `null` if there are no pending requests.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/byobRequest)\n   */\n  get byobRequest(): ReadableStreamBYOBRequest | null;\n  /**\n   * The **`desiredSize`** read-only property of the ReadableByteStreamController interface returns the number of bytes required to fill the stream's internal queue to its 'desired size'.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/desiredSize)\n   */\n  get desiredSize(): number | null;\n  /**\n   * The **`close()`** method of the ReadableByteStreamController interface closes the associated stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/close)\n   */\n  close(): void;\n  /**\n   * The **`enqueue()`** method of the ReadableByteStreamController interface enqueues a given chunk on the associated readable byte stream (the chunk is copied into the stream's internal queues).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/enqueue)\n   */\n  enqueue(chunk: ArrayBuffer | ArrayBufferView): void;\n  /**\n   * The **`error()`** method of the ReadableByteStreamController interface causes any future interactions with the associated stream to error with the specified reason.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/error)\n   */\n  error(reason: any): void;\n}\n/**\n * The **`WritableStreamDefaultController`** interface of the Streams API represents a controller allowing control of a WritableStream's state.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController)\n */\ndeclare abstract class WritableStreamDefaultController {\n  /**\n   * The read-only **`signal`** property of the WritableStreamDefaultController interface returns the AbortSignal associated with the controller.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/signal)\n   */\n  get signal(): AbortSignal;\n  /**\n   * The **`error()`** method of the with the associated stream to error.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/error)\n   */\n  error(reason?: any): void;\n}\n/**\n * The **`TransformStreamDefaultController`** interface of the Streams API provides methods to manipulate the associated ReadableStream and WritableStream.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController)\n */\ndeclare abstract class TransformStreamDefaultController<O = any> {\n  /**\n   * The **`desiredSize`** read-only property of the TransformStreamDefaultController interface returns the desired size to fill the queue of the associated ReadableStream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/desiredSize)\n   */\n  get desiredSize(): number | null;\n  /**\n   * The **`enqueue()`** method of the TransformStreamDefaultController interface enqueues the given chunk in the readable side of the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/enqueue)\n   */\n  enqueue(chunk?: O): void;\n  /**\n   * The **`error()`** method of the TransformStreamDefaultController interface errors both sides of the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/error)\n   */\n  error(reason: any): void;\n  /**\n   * The **`terminate()`** method of the TransformStreamDefaultController interface closes the readable side and errors the writable side of the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/terminate)\n   */\n  terminate(): void;\n}\ninterface ReadableWritablePair<R = any, W = any> {\n  readable: ReadableStream<R>;\n  /**\n   * Provides a convenient, chainable way of piping this readable stream through a transform stream (or any other { writable, readable } pair). It simply pipes the stream into the writable side of the supplied pair, and returns the readable side for further use.\n   *\n   * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader.\n   */\n  writable: WritableStream<W>;\n}\n/**\n * The **`WritableStream`** interface of the Streams API provides a standard abstraction for writing streaming data to a destination, known as a sink.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream)\n */\ndeclare class WritableStream<W = any> {\n  constructor(\n    underlyingSink?: UnderlyingSink,\n    queuingStrategy?: QueuingStrategy,\n  );\n  /**\n   * The **`locked`** read-only property of the WritableStream interface returns a boolean indicating whether the `WritableStream` is locked to a writer.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/locked)\n   */\n  get locked(): boolean;\n  /**\n   * The **`abort()`** method of the WritableStream interface aborts the stream, signaling that the producer can no longer successfully write to the stream and it is to be immediately moved to an error state, with any queued writes discarded.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/abort)\n   */\n  abort(reason?: any): Promise<void>;\n  /**\n   * The **`close()`** method of the WritableStream interface closes the associated stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/close)\n   */\n  close(): Promise<void>;\n  /**\n   * The **`getWriter()`** method of the WritableStream interface returns a new instance of WritableStreamDefaultWriter and locks the stream to that instance.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/getWriter)\n   */\n  getWriter(): WritableStreamDefaultWriter<W>;\n}\n/**\n * The **`WritableStreamDefaultWriter`** interface of the Streams API is the object returned by WritableStream.getWriter() and once created locks the writer to the `WritableStream` ensuring that no other streams can write to the underlying sink.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter)\n */\ndeclare class WritableStreamDefaultWriter<W = any> {\n  constructor(stream: WritableStream);\n  /**\n   * The **`closed`** read-only property of the the stream errors or the writer's lock is released.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/closed)\n   */\n  get closed(): Promise<void>;\n  /**\n   * The **`ready`** read-only property of the that resolves when the desired size of the stream's internal queue transitions from non-positive to positive, signaling that it is no longer applying backpressure.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/ready)\n   */\n  get ready(): Promise<void>;\n  /**\n   * The **`desiredSize`** read-only property of the to fill the stream's internal queue.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/desiredSize)\n   */\n  get desiredSize(): number | null;\n  /**\n   * The **`abort()`** method of the the producer can no longer successfully write to the stream and it is to be immediately moved to an error state, with any queued writes discarded.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/abort)\n   */\n  abort(reason?: any): Promise<void>;\n  /**\n   * The **`close()`** method of the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/close)\n   */\n  close(): Promise<void>;\n  /**\n   * The **`write()`** method of the operation.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/write)\n   */\n  write(chunk?: W): Promise<void>;\n  /**\n   * The **`releaseLock()`** method of the corresponding stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/releaseLock)\n   */\n  releaseLock(): void;\n}\n/**\n * The **`TransformStream`** interface of the Streams API represents a concrete implementation of the pipe chain _transform stream_ concept.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream)\n */\ndeclare class TransformStream<I = any, O = any> {\n  constructor(\n    transformer?: Transformer<I, O>,\n    writableStrategy?: QueuingStrategy<I>,\n    readableStrategy?: QueuingStrategy<O>,\n  );\n  /**\n   * The **`readable`** read-only property of the TransformStream interface returns the ReadableStream instance controlled by this `TransformStream`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/readable)\n   */\n  get readable(): ReadableStream<O>;\n  /**\n   * The **`writable`** read-only property of the TransformStream interface returns the WritableStream instance controlled by this `TransformStream`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/writable)\n   */\n  get writable(): WritableStream<I>;\n}\ndeclare class FixedLengthStream extends IdentityTransformStream {\n  constructor(\n    expectedLength: number | bigint,\n    queuingStrategy?: IdentityTransformStreamQueuingStrategy,\n  );\n}\ndeclare class IdentityTransformStream extends TransformStream<\n  ArrayBuffer | ArrayBufferView,\n  Uint8Array\n> {\n  constructor(queuingStrategy?: IdentityTransformStreamQueuingStrategy);\n}\ninterface IdentityTransformStreamQueuingStrategy {\n  highWaterMark?: number | bigint;\n}\ninterface ReadableStreamValuesOptions {\n  preventCancel?: boolean;\n}\n/**\n * The **`CompressionStream`** interface of the Compression Streams API is an API for compressing a stream of data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CompressionStream)\n */\ndeclare class CompressionStream extends TransformStream<\n  ArrayBuffer | ArrayBufferView,\n  Uint8Array\n> {\n  constructor(format: \"gzip\" | \"deflate\" | \"deflate-raw\");\n}\n/**\n * The **`DecompressionStream`** interface of the Compression Streams API is an API for decompressing a stream of data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DecompressionStream)\n */\ndeclare class DecompressionStream extends TransformStream<\n  ArrayBuffer | ArrayBufferView,\n  Uint8Array\n> {\n  constructor(format: \"gzip\" | \"deflate\" | \"deflate-raw\");\n}\n/**\n * The **`TextEncoderStream`** interface of the Encoding API converts a stream of strings into bytes in the UTF-8 encoding.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoderStream)\n */\ndeclare class TextEncoderStream extends TransformStream<string, Uint8Array> {\n  constructor();\n  get encoding(): string;\n}\n/**\n * The **`TextDecoderStream`** interface of the Encoding API converts a stream of text in a binary encoding, such as UTF-8 etc., to a stream of strings.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoderStream)\n */\ndeclare class TextDecoderStream extends TransformStream<\n  ArrayBuffer | ArrayBufferView,\n  string\n> {\n  constructor(label?: string, options?: TextDecoderStreamTextDecoderStreamInit);\n  get encoding(): string;\n  get fatal(): boolean;\n  get ignoreBOM(): boolean;\n}\ninterface TextDecoderStreamTextDecoderStreamInit {\n  fatal?: boolean;\n  ignoreBOM?: boolean;\n}\n/**\n * The **`ByteLengthQueuingStrategy`** interface of the Streams API provides a built-in byte length queuing strategy that can be used when constructing streams.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy)\n */\ndeclare class ByteLengthQueuingStrategy implements QueuingStrategy<ArrayBufferView> {\n  constructor(init: QueuingStrategyInit);\n  /**\n   * The read-only **`ByteLengthQueuingStrategy.highWaterMark`** property returns the total number of bytes that can be contained in the internal queue before backpressure is applied.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/highWaterMark)\n   */\n  get highWaterMark(): number;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/size) */\n  get size(): (chunk?: any) => number;\n}\n/**\n * The **`CountQueuingStrategy`** interface of the Streams API provides a built-in chunk counting queuing strategy that can be used when constructing streams.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy)\n */\ndeclare class CountQueuingStrategy implements QueuingStrategy {\n  constructor(init: QueuingStrategyInit);\n  /**\n   * The read-only **`CountQueuingStrategy.highWaterMark`** property returns the total number of chunks that can be contained in the internal queue before backpressure is applied.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/highWaterMark)\n   */\n  get highWaterMark(): number;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/size) */\n  get size(): (chunk?: any) => number;\n}\ninterface QueuingStrategyInit {\n  /**\n   * Creates a new ByteLengthQueuingStrategy with the provided high water mark.\n   *\n   * Note that the provided high water mark will not be validated ahead of time. Instead, if it is negative, NaN, or not a number, the resulting ByteLengthQueuingStrategy will cause the corresponding stream constructor to throw.\n   */\n  highWaterMark: number;\n}\ninterface ScriptVersion {\n  id?: string;\n  tag?: string;\n  message?: string;\n}\ndeclare abstract class TailEvent extends ExtendableEvent {\n  readonly events: TraceItem[];\n  readonly traces: TraceItem[];\n}\ninterface TraceItem {\n  readonly event:\n    | (\n        | TraceItemFetchEventInfo\n        | TraceItemJsRpcEventInfo\n        | TraceItemScheduledEventInfo\n        | TraceItemAlarmEventInfo\n        | TraceItemQueueEventInfo\n        | TraceItemEmailEventInfo\n        | TraceItemTailEventInfo\n        | TraceItemCustomEventInfo\n        | TraceItemHibernatableWebSocketEventInfo\n      )\n    | null;\n  readonly eventTimestamp: number | null;\n  readonly logs: TraceLog[];\n  readonly exceptions: TraceException[];\n  readonly diagnosticsChannelEvents: TraceDiagnosticChannelEvent[];\n  readonly scriptName: string | null;\n  readonly entrypoint?: string;\n  readonly scriptVersion?: ScriptVersion;\n  readonly dispatchNamespace?: string;\n  readonly scriptTags?: string[];\n  readonly tailAttributes?: Record<string, boolean | number | string>;\n  readonly durableObjectId?: string;\n  readonly outcome: string;\n  readonly executionModel: string;\n  readonly truncated: boolean;\n  readonly cpuTime: number;\n  readonly wallTime: number;\n}\ninterface TraceItemAlarmEventInfo {\n  readonly scheduledTime: Date;\n}\ninterface TraceItemCustomEventInfo {}\ninterface TraceItemScheduledEventInfo {\n  readonly scheduledTime: number;\n  readonly cron: string;\n}\ninterface TraceItemQueueEventInfo {\n  readonly queue: string;\n  readonly batchSize: number;\n}\ninterface TraceItemEmailEventInfo {\n  readonly mailFrom: string;\n  readonly rcptTo: string;\n  readonly rawSize: number;\n}\ninterface TraceItemTailEventInfo {\n  readonly consumedEvents: TraceItemTailEventInfoTailItem[];\n}\ninterface TraceItemTailEventInfoTailItem {\n  readonly scriptName: string | null;\n}\ninterface TraceItemFetchEventInfo {\n  readonly response?: TraceItemFetchEventInfoResponse;\n  readonly request: TraceItemFetchEventInfoRequest;\n}\ninterface TraceItemFetchEventInfoRequest {\n  readonly cf?: any;\n  readonly headers: Record<string, string>;\n  readonly method: string;\n  readonly url: string;\n  getUnredacted(): TraceItemFetchEventInfoRequest;\n}\ninterface TraceItemFetchEventInfoResponse {\n  readonly status: number;\n}\ninterface TraceItemJsRpcEventInfo {\n  readonly rpcMethod: string;\n}\ninterface TraceItemHibernatableWebSocketEventInfo {\n  readonly getWebSocketEvent:\n    | TraceItemHibernatableWebSocketEventInfoMessage\n    | TraceItemHibernatableWebSocketEventInfoClose\n    | TraceItemHibernatableWebSocketEventInfoError;\n}\ninterface TraceItemHibernatableWebSocketEventInfoMessage {\n  readonly webSocketEventType: string;\n}\ninterface TraceItemHibernatableWebSocketEventInfoClose {\n  readonly webSocketEventType: string;\n  readonly code: number;\n  readonly wasClean: boolean;\n}\ninterface TraceItemHibernatableWebSocketEventInfoError {\n  readonly webSocketEventType: string;\n}\ninterface TraceLog {\n  readonly timestamp: number;\n  readonly level: string;\n  readonly message: any;\n}\ninterface TraceException {\n  readonly timestamp: number;\n  readonly message: string;\n  readonly name: string;\n  readonly stack?: string;\n}\ninterface TraceDiagnosticChannelEvent {\n  readonly timestamp: number;\n  readonly channel: string;\n  readonly message: any;\n}\ninterface TraceMetrics {\n  readonly cpuTime: number;\n  readonly wallTime: number;\n}\ninterface UnsafeTraceMetrics {\n  fromTrace(item: TraceItem): TraceMetrics;\n}\n/**\n * The **`URL`** interface is used to parse, construct, normalize, and encode URL.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL)\n */\ndeclare class URL {\n  constructor(url: string | URL, base?: string | URL);\n  /**\n   * The **`origin`** read-only property of the URL interface returns a string containing the Unicode serialization of the origin of the represented URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/origin)\n   */\n  get origin(): string;\n  /**\n   * The **`href`** property of the URL interface is a string containing the whole URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href)\n   */\n  get href(): string;\n  /**\n   * The **`href`** property of the URL interface is a string containing the whole URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href)\n   */\n  set href(value: string);\n  /**\n   * The **`protocol`** property of the URL interface is a string containing the protocol or scheme of the URL, including the final `':'`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol)\n   */\n  get protocol(): string;\n  /**\n   * The **`protocol`** property of the URL interface is a string containing the protocol or scheme of the URL, including the final `':'`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol)\n   */\n  set protocol(value: string);\n  /**\n   * The **`username`** property of the URL interface is a string containing the username component of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username)\n   */\n  get username(): string;\n  /**\n   * The **`username`** property of the URL interface is a string containing the username component of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username)\n   */\n  set username(value: string);\n  /**\n   * The **`password`** property of the URL interface is a string containing the password component of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password)\n   */\n  get password(): string;\n  /**\n   * The **`password`** property of the URL interface is a string containing the password component of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password)\n   */\n  set password(value: string);\n  /**\n   * The **`host`** property of the URL interface is a string containing the host, which is the URL.hostname, and then, if the port of the URL is nonempty, a `':'`, followed by the URL.port of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host)\n   */\n  get host(): string;\n  /**\n   * The **`host`** property of the URL interface is a string containing the host, which is the URL.hostname, and then, if the port of the URL is nonempty, a `':'`, followed by the URL.port of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host)\n   */\n  set host(value: string);\n  /**\n   * The **`hostname`** property of the URL interface is a string containing either the domain name or IP address of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname)\n   */\n  get hostname(): string;\n  /**\n   * The **`hostname`** property of the URL interface is a string containing either the domain name or IP address of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname)\n   */\n  set hostname(value: string);\n  /**\n   * The **`port`** property of the URL interface is a string containing the port number of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port)\n   */\n  get port(): string;\n  /**\n   * The **`port`** property of the URL interface is a string containing the port number of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port)\n   */\n  set port(value: string);\n  /**\n   * The **`pathname`** property of the URL interface represents a location in a hierarchical structure.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname)\n   */\n  get pathname(): string;\n  /**\n   * The **`pathname`** property of the URL interface represents a location in a hierarchical structure.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname)\n   */\n  set pathname(value: string);\n  /**\n   * The **`search`** property of the URL interface is a search string, also called a _query string_, that is a string containing a `'?'` followed by the parameters of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search)\n   */\n  get search(): string;\n  /**\n   * The **`search`** property of the URL interface is a search string, also called a _query string_, that is a string containing a `'?'` followed by the parameters of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search)\n   */\n  set search(value: string);\n  /**\n   * The **`hash`** property of the URL interface is a string containing a `'#'` followed by the fragment identifier of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash)\n   */\n  get hash(): string;\n  /**\n   * The **`hash`** property of the URL interface is a string containing a `'#'` followed by the fragment identifier of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash)\n   */\n  set hash(value: string);\n  /**\n   * The **`searchParams`** read-only property of the access to the [MISSING: httpmethod('GET')] decoded query arguments contained in the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/searchParams)\n   */\n  get searchParams(): URLSearchParams;\n  /**\n   * The **`toJSON()`** method of the URL interface returns a string containing a serialized version of the URL, although in practice it seems to have the same effect as ```js-nolint toJSON() ``` None.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/toJSON)\n   */\n  toJSON(): string;\n  /*function toString() { [native code] }*/\n  toString(): string;\n  /**\n   * The **`URL.canParse()`** static method of the URL interface returns a boolean indicating whether or not an absolute URL, or a relative URL combined with a base URL, are parsable and valid.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/canParse_static)\n   */\n  static canParse(url: string, base?: string): boolean;\n  /**\n   * The **`URL.parse()`** static method of the URL interface returns a newly created URL object representing the URL defined by the parameters.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/parse_static)\n   */\n  static parse(url: string, base?: string): URL | null;\n  /**\n   * The **`createObjectURL()`** static method of the URL interface creates a string containing a URL representing the object given in the parameter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/createObjectURL_static)\n   */\n  static createObjectURL(object: File | Blob): string;\n  /**\n   * The **`revokeObjectURL()`** static method of the URL interface releases an existing object URL which was previously created by calling Call this method when you've finished using an object URL to let the browser know not to keep the reference to the file any longer.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/revokeObjectURL_static)\n   */\n  static revokeObjectURL(object_url: string): void;\n}\n/**\n * The **`URLSearchParams`** interface defines utility methods to work with the query string of a URL.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams)\n */\ndeclare class URLSearchParams {\n  constructor(\n    init?: Iterable<Iterable<string>> | Record<string, string> | string,\n  );\n  /**\n   * The **`size`** read-only property of the URLSearchParams interface indicates the total number of search parameter entries.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/size)\n   */\n  get size(): number;\n  /**\n   * The **`append()`** method of the URLSearchParams interface appends a specified key/value pair as a new search parameter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/append)\n   */\n  append(name: string, value: string): void;\n  /**\n   * The **`delete()`** method of the URLSearchParams interface deletes specified parameters and their associated value(s) from the list of all search parameters.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/delete)\n   */\n  delete(name: string, value?: string): void;\n  /**\n   * The **`get()`** method of the URLSearchParams interface returns the first value associated to the given search parameter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/get)\n   */\n  get(name: string): string | null;\n  /**\n   * The **`getAll()`** method of the URLSearchParams interface returns all the values associated with a given search parameter as an array.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/getAll)\n   */\n  getAll(name: string): string[];\n  /**\n   * The **`has()`** method of the URLSearchParams interface returns a boolean value that indicates whether the specified parameter is in the search parameters.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/has)\n   */\n  has(name: string, value?: string): boolean;\n  /**\n   * The **`set()`** method of the URLSearchParams interface sets the value associated with a given search parameter to the given value.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/set)\n   */\n  set(name: string, value: string): void;\n  /**\n   * The **`URLSearchParams.sort()`** method sorts all key/value pairs contained in this object in place and returns `undefined`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/sort)\n   */\n  sort(): void;\n  /* Returns an array of key, value pairs for every entry in the search params. */\n  entries(): IterableIterator<[key: string, value: string]>;\n  /* Returns a list of keys in the search params. */\n  keys(): IterableIterator<string>;\n  /* Returns a list of values in the search params. */\n  values(): IterableIterator<string>;\n  forEach<This = unknown>(\n    callback: (\n      this: This,\n      value: string,\n      key: string,\n      parent: URLSearchParams,\n    ) => void,\n    thisArg?: This,\n  ): void;\n  /*function toString() { [native code] }*/\n  toString(): string;\n  [Symbol.iterator](): IterableIterator<[key: string, value: string]>;\n}\ndeclare class URLPattern {\n  constructor(\n    input?: string | URLPatternInit,\n    baseURL?: string | URLPatternOptions,\n    patternOptions?: URLPatternOptions,\n  );\n  get protocol(): string;\n  get username(): string;\n  get password(): string;\n  get hostname(): string;\n  get port(): string;\n  get pathname(): string;\n  get search(): string;\n  get hash(): string;\n  get hasRegExpGroups(): boolean;\n  test(input?: string | URLPatternInit, baseURL?: string): boolean;\n  exec(\n    input?: string | URLPatternInit,\n    baseURL?: string,\n  ): URLPatternResult | null;\n}\ninterface URLPatternInit {\n  protocol?: string;\n  username?: string;\n  password?: string;\n  hostname?: string;\n  port?: string;\n  pathname?: string;\n  search?: string;\n  hash?: string;\n  baseURL?: string;\n}\ninterface URLPatternComponentResult {\n  input: string;\n  groups: Record<string, string>;\n}\ninterface URLPatternResult {\n  inputs: (string | URLPatternInit)[];\n  protocol: URLPatternComponentResult;\n  username: URLPatternComponentResult;\n  password: URLPatternComponentResult;\n  hostname: URLPatternComponentResult;\n  port: URLPatternComponentResult;\n  pathname: URLPatternComponentResult;\n  search: URLPatternComponentResult;\n  hash: URLPatternComponentResult;\n}\ninterface URLPatternOptions {\n  ignoreCase?: boolean;\n}\n/**\n * A `CloseEvent` is sent to clients using WebSockets when the connection is closed.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent)\n */\ndeclare class CloseEvent extends Event {\n  constructor(type: string, initializer?: CloseEventInit);\n  /**\n   * The **`code`** read-only property of the CloseEvent interface returns a WebSocket connection close code indicating the reason the connection was closed.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/code)\n   */\n  readonly code: number;\n  /**\n   * The **`reason`** read-only property of the CloseEvent interface returns the WebSocket connection close reason the server gave for closing the connection; that is, a concise human-readable prose explanation for the closure.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/reason)\n   */\n  readonly reason: string;\n  /**\n   * The **`wasClean`** read-only property of the CloseEvent interface returns `true` if the connection closed cleanly.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/wasClean)\n   */\n  readonly wasClean: boolean;\n}\ninterface CloseEventInit {\n  code?: number;\n  reason?: string;\n  wasClean?: boolean;\n}\ntype WebSocketEventMap = {\n  close: CloseEvent;\n  message: MessageEvent;\n  open: Event;\n  error: ErrorEvent;\n};\n/**\n * The `WebSocket` object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket)\n */\ndeclare var WebSocket: {\n  prototype: WebSocket;\n  new (url: string, protocols?: string[] | string): WebSocket;\n  readonly READY_STATE_CONNECTING: number;\n  readonly CONNECTING: number;\n  readonly READY_STATE_OPEN: number;\n  readonly OPEN: number;\n  readonly READY_STATE_CLOSING: number;\n  readonly CLOSING: number;\n  readonly READY_STATE_CLOSED: number;\n  readonly CLOSED: number;\n};\n/**\n * The `WebSocket` object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket)\n */\ninterface WebSocket extends EventTarget<WebSocketEventMap> {\n  accept(options?: WebSocketAcceptOptions): void;\n  /**\n   * The **`WebSocket.send()`** method enqueues the specified data to be transmitted to the server over the WebSocket connection, increasing the value of `bufferedAmount` by the number of bytes needed to contain the data.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/send)\n   */\n  send(message: (ArrayBuffer | ArrayBufferView) | string): void;\n  /**\n   * The **`WebSocket.close()`** method closes the already `CLOSED`, this method does nothing.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/close)\n   */\n  close(code?: number, reason?: string): void;\n  serializeAttachment(attachment: any): void;\n  deserializeAttachment(): any | null;\n  /**\n   * The **`WebSocket.readyState`** read-only property returns the current state of the WebSocket connection.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/readyState)\n   */\n  readyState: number;\n  /**\n   * The **`WebSocket.url`** read-only property returns the absolute URL of the WebSocket as resolved by the constructor.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/url)\n   */\n  url: string | null;\n  /**\n   * The **`WebSocket.protocol`** read-only property returns the name of the sub-protocol the server selected; this will be one of the strings specified in the `protocols` parameter when creating the WebSocket object, or the empty string if no connection is established.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/protocol)\n   */\n  protocol: string | null;\n  /**\n   * The **`WebSocket.extensions`** read-only property returns the extensions selected by the server.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/extensions)\n   */\n  extensions: string | null;\n  /**\n   * The **`WebSocket.binaryType`** property controls the type of binary data being received over the WebSocket connection.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/binaryType)\n   */\n  binaryType: \"blob\" | \"arraybuffer\";\n}\ninterface WebSocketAcceptOptions {\n  /**\n   * When set to `true`, receiving a server-initiated WebSocket Close frame will not\n   * automatically send a reciprocal Close frame, leaving the connection in a half-open\n   * state. This is useful for proxying scenarios where you need to coordinate closing\n   * both sides independently. Defaults to `false` when the\n   * `no_web_socket_half_open_by_default` compatibility flag is enabled.\n   */\n  allowHalfOpen?: boolean;\n}\ndeclare const WebSocketPair: {\n  new (): {\n    0: WebSocket;\n    1: WebSocket;\n  };\n};\ninterface SqlStorage {\n  exec<T extends Record<string, SqlStorageValue>>(\n    query: string,\n    ...bindings: any[]\n  ): SqlStorageCursor<T>;\n  prepare(query: string): SqlStorageStatement;\n  ingest(query: string): SqlStorageIngestResult;\n  setMaxPageCountForTest(count: number): void;\n  get databaseSize(): number;\n  Cursor: typeof SqlStorageCursor;\n  Statement: typeof SqlStorageStatement;\n}\ndeclare abstract class SqlStorageStatement {}\ntype SqlStorageValue = ArrayBuffer | string | number | null;\ndeclare abstract class SqlStorageCursor<\n  T extends Record<string, SqlStorageValue>,\n> {\n  next():\n    | {\n        done?: false;\n        value: T;\n      }\n    | {\n        done: true;\n        value?: never;\n      };\n  toArray(): T[];\n  one(): T;\n  raw<U extends SqlStorageValue[]>(): IterableIterator<U>;\n  columnNames: string[];\n  get rowsRead(): number;\n  get rowsWritten(): number;\n  get reusedCachedQueryForTest(): boolean;\n  [Symbol.iterator](): IterableIterator<T>;\n}\ninterface SqlStorageIngestResult {\n  remainder: string;\n  rowsRead: number;\n  rowsWritten: number;\n  statementCount: number;\n}\ninterface Socket {\n  get readable(): ReadableStream;\n  get writable(): WritableStream;\n  get closed(): Promise<void>;\n  get opened(): Promise<SocketInfo>;\n  get upgraded(): boolean;\n  get secureTransport(): \"on\" | \"off\" | \"starttls\";\n  close(): Promise<void>;\n  startTls(options?: TlsOptions): Socket;\n}\ninterface SocketOptions {\n  secureTransport?: string;\n  allowHalfOpen: boolean;\n  highWaterMark?: number | bigint;\n}\ninterface SocketAddress {\n  hostname: string;\n  port: number;\n}\ninterface TlsOptions {\n  expectedServerHostname?: string;\n}\ninterface SocketInfo {\n  remoteAddress?: string;\n  localAddress?: string;\n}\n/**\n * The **`EventSource`** interface is web content's interface to server-sent events.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource)\n */\ndeclare class EventSource extends EventTarget {\n  constructor(url: string, init?: EventSourceEventSourceInit);\n  /**\n   * The **`close()`** method of the EventSource interface closes the connection, if one is made, and sets the ```js-nolint close() ``` None.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close)\n   */\n  close(): void;\n  /**\n   * The **`url`** read-only property of the URL of the source.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url)\n   */\n  get url(): string;\n  /**\n   * The **`withCredentials`** read-only property of the the `EventSource` object was instantiated with CORS credentials set.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials)\n   */\n  get withCredentials(): boolean;\n  /**\n   * The **`readyState`** read-only property of the connection.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState)\n   */\n  get readyState(): number;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */\n  get onopen(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */\n  set onopen(value: any | null);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */\n  get onmessage(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */\n  set onmessage(value: any | null);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */\n  get onerror(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */\n  set onerror(value: any | null);\n  static readonly CONNECTING: number;\n  static readonly OPEN: number;\n  static readonly CLOSED: number;\n  static from(stream: ReadableStream): EventSource;\n}\ninterface EventSourceEventSourceInit {\n  withCredentials?: boolean;\n  fetcher?: Fetcher;\n}\ninterface Container {\n  get running(): boolean;\n  start(options?: ContainerStartupOptions): void;\n  monitor(): Promise<void>;\n  destroy(error?: any): Promise<void>;\n  signal(signo: number): void;\n  getTcpPort(port: number): Fetcher;\n  setInactivityTimeout(durationMs: number | bigint): Promise<void>;\n  interceptOutboundHttp(addr: string, binding: Fetcher): Promise<void>;\n  interceptAllOutboundHttp(binding: Fetcher): Promise<void>;\n}\ninterface ContainerStartupOptions {\n  entrypoint?: string[];\n  enableInternet: boolean;\n  env?: Record<string, string>;\n  hardTimeout?: number | bigint;\n  labels?: Record<string, string>;\n}\n/**\n * The **`FileSystemHandle`** interface of the File System API is an object which represents a file or directory entry.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemHandle)\n */\ndeclare abstract class FileSystemHandle {\n  /**\n   * The **`kind`** read-only property of the `'file'` if the associated entry is a file or `'directory'`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemHandle/kind)\n   */\n  get kind(): string;\n  /**\n   * The **`name`** read-only property of the handle.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemHandle/name)\n   */\n  get name(): string;\n  /**\n   * The **`isSameEntry()`** method of the ```js-nolint isSameEntry(fileSystemHandle) ``` - FileSystemHandle - : The `FileSystemHandle` to match against the handle on which the method is invoked.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemHandle/isSameEntry)\n   */\n  isSameEntry(other: FileSystemHandle): Promise<boolean>;\n  getUniqueId(): Promise<string>;\n  remove(options?: FileSystemHandleRemoveOptions): Promise<void>;\n}\n/**\n * The **`FileSystemFileHandle`** interface of the File System API represents a handle to a file system entry.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle)\n */\ndeclare abstract class FileSystemFileHandle extends FileSystemHandle {\n  /**\n   * The **`getFile()`** method of the If the file on disk changes or is removed after this method is called, the returned ```js-nolint getFile() ``` None.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle/getFile)\n   */\n  getFile(): Promise<File>;\n  /**\n   * The **`createWritable()`** method of the FileSystemFileHandle interface creates a FileSystemWritableFileStream that can be used to write to a file.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle/createWritable)\n   */\n  createWritable(\n    options?: FileSystemFileHandleFileSystemCreateWritableOptions,\n  ): Promise<FileSystemWritableFileStream>;\n}\n/**\n * The **`FileSystemDirectoryHandle`** interface of the File System API provides a handle to a file system directory.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle)\n */\ndeclare abstract class FileSystemDirectoryHandle extends FileSystemHandle {\n  /**\n   * The **`getFileHandle()`** method of the directory the method is called.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/getFileHandle)\n   */\n  getFileHandle(\n    name: string,\n    options?: FileSystemDirectoryHandleFileSystemGetFileOptions,\n  ): Promise<FileSystemFileHandle>;\n  /**\n   * The **`getDirectoryHandle()`** method of the within the directory handle on which the method is called.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/getDirectoryHandle)\n   */\n  getDirectoryHandle(\n    name: string,\n    options?: FileSystemDirectoryHandleFileSystemGetDirectoryOptions,\n  ): Promise<FileSystemDirectoryHandle>;\n  /**\n   * The **`removeEntry()`** method of the directory handle contains a file or directory called the name specified.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/removeEntry)\n   */\n  removeEntry(\n    name: string,\n    options?: FileSystemDirectoryHandleFileSystemRemoveOptions,\n  ): Promise<void>;\n  /**\n   * The **`resolve()`** method of the directory names from the parent handle to the specified child entry, with the name of the child entry as the last array item.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/resolve)\n   */\n  resolve(possibleDescendant: FileSystemHandle): Promise<string[]>;\n  entries(): AsyncIterableIterator<[string, FileSystemHandle]>;\n  keys(): AsyncIterableIterator<string>;\n  values(): AsyncIterableIterator<FileSystemHandle>;\n  forEach(\n    callback: (\n      param0: string,\n      param1: FileSystemHandle,\n      param2: FileSystemDirectoryHandle,\n    ) => void,\n    thisArg?: any,\n  ): void;\n  [Symbol.asyncIterator](): AsyncIterableIterator<[string, FileSystemHandle]>;\n}\n/**\n * The **`FileSystemWritableFileStream`** interface of the File System API is a WritableStream object with additional convenience methods, which operates on a single file on disk.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemWritableFileStream)\n */\ndeclare abstract class FileSystemWritableFileStream extends WritableStream {\n  /**\n   * The **`write()`** method of the FileSystemWritableFileStream interface writes content into the file the method is called on, at the current file cursor offset.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemWritableFileStream/write)\n   */\n  write(\n    data:\n      | Blob\n      | (ArrayBuffer | ArrayBufferView)\n      | string\n      | FileSystemFileWriteParams,\n  ): Promise<void>;\n  /**\n   * The **`seek()`** method of the FileSystemWritableFileStream interface updates the current file cursor offset to the position (in bytes) specified when calling the method.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemWritableFileStream/seek)\n   */\n  seek(position: number): Promise<void>;\n  /**\n   * The **`truncate()`** method of the FileSystemWritableFileStream interface resizes the file associated with the stream to the specified size in bytes.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemWritableFileStream/truncate)\n   */\n  truncate(size: number): Promise<void>;\n}\n/**\n * The **`StorageManager`** interface of the Storage API provides an interface for managing persistence permissions and estimating available storage.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/StorageManager)\n */\ndeclare abstract class StorageManager {\n  /**\n   * The **`getDirectory()`** method of the StorageManager interface is used to obtain a reference to a FileSystemDirectoryHandle object allowing access to a directory and its contents, stored in the origin private file system (OPFS).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/StorageManager/getDirectory)\n   */\n  getDirectory(): Promise<FileSystemDirectoryHandle>;\n}\ninterface FileSystemFileHandleFileSystemCreateWritableOptions {\n  keepExistingData?: boolean;\n}\ninterface FileSystemDirectoryHandleFileSystemGetFileOptions {\n  create: boolean;\n}\ninterface FileSystemDirectoryHandleFileSystemGetDirectoryOptions {\n  create: boolean;\n}\ninterface FileSystemDirectoryHandleFileSystemRemoveOptions {\n  recursive: boolean;\n}\ninterface FileSystemFileWriteParams {\n  type: string;\n  size?: number;\n  position?: number;\n  data?: (Blob | (ArrayBuffer | ArrayBufferView) | string) | null;\n}\ninterface FileSystemHandleRemoveOptions {\n  recursive?: boolean;\n}\n/**\n * The **`MessagePort`** interface of the Channel Messaging API represents one of the two ports of a MessageChannel, allowing messages to be sent from one port and listening out for them arriving at the other.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort)\n */\ndeclare abstract class MessagePort extends EventTarget {\n  /**\n   * The **`postMessage()`** method of the transfers ownership of objects to other browsing contexts.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/postMessage)\n   */\n  postMessage(\n    data?: any,\n    options?: any[] | MessagePortPostMessageOptions,\n  ): void;\n  /**\n   * The **`close()`** method of the MessagePort interface disconnects the port, so it is no longer active.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/close)\n   */\n  close(): void;\n  /**\n   * The **`start()`** method of the MessagePort interface starts the sending of messages queued on the port.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/start)\n   */\n  start(): void;\n  get onmessage(): any | null;\n  set onmessage(value: any | null);\n}\n/**\n * The **`MessageChannel`** interface of the Channel Messaging API allows us to create a new message channel and send data through it via its two MessagePort properties.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel)\n */\ndeclare class MessageChannel {\n  constructor();\n  /**\n   * The **`port1`** read-only property of the the port attached to the context that originated the channel.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel/port1)\n   */\n  readonly port1: MessagePort;\n  /**\n   * The **`port2`** read-only property of the the port attached to the context at the other end of the channel, which the message is initially sent to.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel/port2)\n   */\n  readonly port2: MessagePort;\n}\ninterface MessagePortPostMessageOptions {\n  transfer?: any[];\n}\ntype LoopbackForExport<\n  T extends\n    | (new (...args: any[]) => Rpc.EntrypointBranded)\n    | ExportedHandler<any, any, any>\n    | undefined = undefined,\n> = T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded\n  ? LoopbackServiceStub<InstanceType<T>>\n  : T extends new (...args: any[]) => Rpc.DurableObjectBranded\n    ? LoopbackDurableObjectClass<InstanceType<T>>\n    : T extends ExportedHandler<any, any, any>\n      ? LoopbackServiceStub<undefined>\n      : undefined;\ntype LoopbackServiceStub<\n  T extends Rpc.WorkerEntrypointBranded | undefined = undefined,\n> = Fetcher<T> &\n  (T extends CloudflareWorkersModule.WorkerEntrypoint<any, infer Props>\n    ? (opts: {\n        props?: Props;\n        version?: {\n          cohort?: string | null;\n        };\n      }) => Fetcher<T>\n    : (opts: {\n        props?: any;\n        version?: {\n          cohort?: string | null;\n        };\n      }) => Fetcher<T>);\ntype LoopbackDurableObjectClass<\n  T extends Rpc.DurableObjectBranded | undefined = undefined,\n> = DurableObjectClass<T> &\n  (T extends CloudflareWorkersModule.DurableObject<any, infer Props>\n    ? (opts: { props?: Props }) => DurableObjectClass<T>\n    : (opts: { props?: any }) => DurableObjectClass<T>);\ninterface LoopbackDurableObjectNamespace extends DurableObjectNamespace {}\ninterface LoopbackColoLocalActorNamespace extends ColoLocalActorNamespace {}\ninterface SyncKvStorage {\n  get<T = unknown>(key: string): T | undefined;\n  list<T = unknown>(options?: SyncKvListOptions): Iterable<[string, T]>;\n  put<T>(key: string, value: T): void;\n  delete(key: string): boolean;\n}\ninterface SyncKvListOptions {\n  start?: string;\n  startAfter?: string;\n  end?: string;\n  prefix?: string;\n  reverse?: boolean;\n  limit?: number;\n}\ninterface WorkerStub {\n  getEntrypoint<T extends Rpc.WorkerEntrypointBranded | undefined>(\n    name?: string,\n    options?: WorkerStubEntrypointOptions,\n  ): Fetcher<T>;\n  getDurableObjectClass<T extends Rpc.DurableObjectBranded | undefined>(\n    name?: string,\n    options?: WorkerStubEntrypointOptions,\n  ): DurableObjectClass<T>;\n}\ninterface WorkerStubEntrypointOptions {\n  props?: any;\n}\ninterface WorkerLoader {\n  get(\n    name: string | null,\n    getCode: () => WorkerLoaderWorkerCode | Promise<WorkerLoaderWorkerCode>,\n  ): WorkerStub;\n  load(code: WorkerLoaderWorkerCode): WorkerStub;\n}\ninterface WorkerLoaderModule {\n  js?: string;\n  cjs?: string;\n  text?: string;\n  data?: ArrayBuffer;\n  json?: any;\n  py?: string;\n  wasm?: ArrayBuffer;\n}\ninterface WorkerLoaderWorkerCode {\n  compatibilityDate: string;\n  compatibilityFlags?: string[];\n  allowExperimental?: boolean;\n  mainModule: string;\n  modules: Record<string, WorkerLoaderModule | string>;\n  env?: any;\n  globalOutbound?: Fetcher | null;\n  tails?: Fetcher[];\n  streamingTails?: Fetcher[];\n}\n/**\n * The Workers runtime supports a subset of the Performance API, used to measure timing and performance,\n * as well as timing of subrequests and other operations.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/)\n */\ndeclare abstract class Performance extends EventTarget {\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancetimeorigin) */\n  get timeOrigin(): number;\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancenow) */\n  now(): number;\n  get eventCounts(): EventCounts;\n  /**\n   * The **`clearMarks()`** method removes all or specific PerformanceMark objects from the browser's performance timeline.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/clearMarks)\n   */\n  clearMarks(name?: string): void;\n  /**\n   * The **`clearMeasures()`** method removes all or specific PerformanceMeasure objects from the browser's performance timeline.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/clearMeasures)\n   */\n  clearMeasures(name?: string): void;\n  /**\n   * The **`clearResourceTimings()`** method removes all performance entries with an PerformanceEntry.entryType of `'resource'` from the browser's performance timeline and sets the size of the performance resource data buffer to zero.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/clearResourceTimings)\n   */\n  clearResourceTimings(): void;\n  /**\n   * The **`getEntries()`** method returns an array of all PerformanceEntry objects currently present in the performance timeline.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/getEntries)\n   */\n  getEntries(): PerformanceEntry[];\n  /**\n   * The **`getEntriesByName()`** method returns an array of PerformanceEntry objects currently present in the performance timeline with the given _name_ and _type_.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/getEntriesByName)\n   */\n  getEntriesByName(name: string, type?: string): PerformanceEntry[];\n  /**\n   * The **`getEntriesByType()`** method returns an array of PerformanceEntry objects currently present in the performance timeline for a given _type_.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/getEntriesByType)\n   */\n  getEntriesByType(type: string): PerformanceEntry[];\n  /**\n   * The **`mark()`** method creates a named PerformanceMark object representing a high resolution timestamp marker in the browser's performance timeline.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/mark)\n   */\n  mark(name: string, options?: PerformanceMarkOptions): PerformanceMark;\n  /**\n   * The **`measure()`** method creates a named PerformanceMeasure object representing a time measurement between two marks in the browser's performance timeline.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/measure)\n   */\n  measure(\n    measureName: string,\n    measureOptionsOrStartMark?: PerformanceMeasureOptions | string,\n    maybeEndMark?: string,\n  ): PerformanceMeasure;\n  /**\n   * The **`setResourceTimingBufferSize()`** method sets the desired size of the browser's resource timing buffer which stores the `'resource'` performance entries.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/setResourceTimingBufferSize)\n   */\n  setResourceTimingBufferSize(size: number): void;\n  /**\n   * The **`toJSON()`** method of the Performance interface is a Serialization; it returns a JSON representation of the Performance object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/toJSON)\n   */\n  toJSON(): object;\n  get nodeTiming(): PerformanceNodeTiming;\n  eventLoopUtilization(): PerformanceEventLoopUtilization;\n  markResourceTiming(): void;\n  timerify(fn: () => void): () => void;\n}\ninterface PerformanceEventLoopUtilization {\n  idle: number;\n  active: number;\n  utilization: number;\n}\ninterface PerformanceNodeTiming extends PerformanceEntry {\n  readonly nodeStart: number;\n  readonly v8Start: number;\n  readonly bootstrapComplete: number;\n  readonly environment: number;\n  readonly loopStart: number;\n  readonly loopExit: number;\n  readonly idleTime: number;\n  readonly uvMetricsInfo: UvMetricsInfo;\n  toJSON(): object;\n}\ninterface UvMetricsInfo {\n  loopCount: number;\n  events: number;\n  eventsWaiting: number;\n}\n/**\n * **`PerformanceMark`** is an interface for PerformanceEntry objects with an PerformanceEntry.entryType of `'mark'`.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceMark)\n */\ndeclare class PerformanceMark extends PerformanceEntry {\n  constructor(name: string, maybeOptions?: PerformanceMarkOptions);\n  /**\n   * The read-only **`detail`** property returns arbitrary metadata that was included in the mark upon construction (either when using Performance.mark or the PerformanceMark.PerformanceMark constructor).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceMark/detail)\n   */\n  get detail(): any;\n  toJSON(): object;\n}\n/**\n * **`PerformanceMeasure`** is an _abstract_ interface for PerformanceEntry objects with an PerformanceEntry.entryType of `'measure'`.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceMeasure)\n */\ndeclare abstract class PerformanceMeasure extends PerformanceEntry {\n  /**\n   * The read-only **`detail`** property returns arbitrary metadata that was included in the mark upon construction (when using Performance.measure.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceMeasure/detail)\n   */\n  get detail(): any;\n  toJSON(): object;\n}\ninterface PerformanceMarkOptions {\n  detail?: any;\n  startTime?: number;\n}\ninterface PerformanceMeasureOptions {\n  detail?: any;\n  start?: number;\n  duration?: number;\n  end?: number;\n}\n/**\n * The **`PerformanceObserverEntryList`** interface is a list of PerformanceEntry that were explicitly observed via the PerformanceObserver.observe method.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceObserverEntryList)\n */\ndeclare abstract class PerformanceObserverEntryList {\n  /**\n   * The **`getEntries()`** method of the PerformanceObserverEntryList interface returns a list of explicitly observed PerformanceEntry objects.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceObserverEntryList/getEntries)\n   */\n  getEntries(): PerformanceEntry[];\n  /**\n   * The **`getEntriesByType()`** method of the PerformanceObserverEntryList returns a list of explicitly _observed_ PerformanceEntry objects for a given PerformanceEntry.entryType.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceObserverEntryList/getEntriesByType)\n   */\n  getEntriesByType(type: string): PerformanceEntry[];\n  /**\n   * The **`getEntriesByName()`** method of the PerformanceObserverEntryList interface returns a list of explicitly observed PerformanceEntry objects for a given PerformanceEntry.name and PerformanceEntry.entryType.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceObserverEntryList/getEntriesByName)\n   */\n  getEntriesByName(name: string, type?: string): PerformanceEntry[];\n}\n/**\n * The **`PerformanceEntry`** object encapsulates a single performance metric that is part of the browser's performance timeline.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceEntry)\n */\ndeclare abstract class PerformanceEntry {\n  /**\n   * The read-only **`name`** property of the PerformanceEntry interface is a string representing the name for a performance entry.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceEntry/name)\n   */\n  get name(): string;\n  /**\n   * The read-only **`entryType`** property returns a string representing the type of performance metric that this entry represents.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceEntry/entryType)\n   */\n  get entryType(): string;\n  /**\n   * The read-only **`startTime`** property returns the first DOMHighResTimeStamp recorded for this PerformanceEntry.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceEntry/startTime)\n   */\n  get startTime(): number;\n  /**\n   * The read-only **`duration`** property returns a DOMHighResTimeStamp that is the duration of the PerformanceEntry.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceEntry/duration)\n   */\n  get duration(): number;\n  /**\n   * The **`toJSON()`** method is a Serialization; it returns a JSON representation of the PerformanceEntry object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceEntry/toJSON)\n   */\n  toJSON(): object;\n}\n/**\n * The **`PerformanceResourceTiming`** interface enables retrieval and analysis of detailed network timing data regarding the loading of an application's resources.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming)\n */\ndeclare abstract class PerformanceResourceTiming extends PerformanceEntry {\n  /**\n   * The **`connectEnd`** read-only property returns the DOMHighResTimeStamp immediately after the browser finishes establishing the connection to the server to retrieve the resource.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/connectEnd)\n   */\n  get connectEnd(): number;\n  /**\n   * The **`connectStart`** read-only property returns the DOMHighResTimeStamp immediately before the user agent starts establishing the connection to the server to retrieve the resource.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/connectStart)\n   */\n  get connectStart(): number;\n  /**\n   * The **`decodedBodySize`** read-only property returns the size (in octets) received from the fetch (HTTP or cache) of the message body after removing any applied content encoding (like gzip or Brotli).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/decodedBodySize)\n   */\n  get decodedBodySize(): number;\n  /**\n   * The **`domainLookupEnd`** read-only property returns the DOMHighResTimeStamp immediately after the browser finishes the domain-name lookup for the resource.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/domainLookupEnd)\n   */\n  get domainLookupEnd(): number;\n  /**\n   * The **`domainLookupStart`** read-only property returns the DOMHighResTimeStamp immediately before the browser starts the domain name lookup for the resource.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/domainLookupStart)\n   */\n  get domainLookupStart(): number;\n  /**\n   * The **`encodedBodySize`** read-only property represents the size (in octets) received from the fetch (HTTP or cache) of the payload body before removing any applied content encodings (like gzip or Brotli).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/encodedBodySize)\n   */\n  get encodedBodySize(): number;\n  /**\n   * The **`fetchStart`** read-only property represents a DOMHighResTimeStamp immediately before the browser starts to fetch the resource.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/fetchStart)\n   */\n  get fetchStart(): number;\n  /**\n   * The **`initiatorType`** read-only property is a string representing web platform feature that initiated the resource load.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/initiatorType)\n   */\n  get initiatorType(): string;\n  /**\n   * The **`nextHopProtocol`** read-only property is a string representing the network protocol used to fetch the resource, as identified by the ALPN Protocol ID (RFC7301).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/nextHopProtocol)\n   */\n  get nextHopProtocol(): string;\n  /**\n   * The **`redirectEnd`** read-only property returns a DOMHighResTimeStamp immediately after receiving the last byte of the response of the last redirect.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/redirectEnd)\n   */\n  get redirectEnd(): number;\n  /**\n   * The **`redirectStart`** read-only property returns a DOMHighResTimeStamp representing the start time of the fetch which that initiates the redirect.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/redirectStart)\n   */\n  get redirectStart(): number;\n  /**\n   * The **`requestStart`** read-only property returns a DOMHighResTimeStamp of the time immediately before the browser starts requesting the resource from the server, cache, or local resource.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/requestStart)\n   */\n  get requestStart(): number;\n  /**\n   * The **`responseEnd`** read-only property returns a DOMHighResTimeStamp immediately after the browser receives the last byte of the resource or immediately before the transport connection is closed, whichever comes first.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/responseEnd)\n   */\n  get responseEnd(): number;\n  /**\n   * The **`responseStart`** read-only property returns a DOMHighResTimeStamp immediately after the browser receives the first byte of the response from the server, cache, or local resource.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/responseStart)\n   */\n  get responseStart(): number;\n  /**\n   * The **`responseStatus`** read-only property represents the HTTP response status code returned when fetching the resource.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/responseStatus)\n   */\n  get responseStatus(): number;\n  /**\n   * The **`secureConnectionStart`** read-only property returns a DOMHighResTimeStamp immediately before the browser starts the handshake process to secure the current connection.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/secureConnectionStart)\n   */\n  get secureConnectionStart(): number | undefined;\n  /**\n   * The **`transferSize`** read-only property represents the size (in octets) of the fetched resource.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/transferSize)\n   */\n  get transferSize(): number;\n  /**\n   * The **`workerStart`** read-only property of the PerformanceResourceTiming interface returns a The `workerStart` property can have the following values: - A DOMHighResTimeStamp.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/workerStart)\n   */\n  get workerStart(): number;\n}\n/**\n * The **`PerformanceObserver`** interface is used to observe performance measurement events and be notified of new PerformanceEntry as they are recorded in the browser's _performance timeline_.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceObserver)\n */\ndeclare class PerformanceObserver {\n  constructor(callback: any);\n  /**\n   * The **`disconnect()`** method of the PerformanceObserver interface is used to stop the performance observer from receiving any PerformanceEntry events.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceObserver/disconnect)\n   */\n  disconnect(): void;\n  /**\n   * The **`observe()`** method of the **PerformanceObserver** interface is used to specify the set of performance entry types to observe.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceObserver/observe)\n   */\n  observe(options?: PerformanceObserverObserveOptions): void;\n  /**\n   * The **`takeRecords()`** method of the PerformanceObserver interface returns the current list of PerformanceEntry objects stored in the performance observer, emptying it out.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceObserver/takeRecords)\n   */\n  takeRecords(): PerformanceEntry[];\n  readonly supportedEntryTypes: string[];\n}\ninterface PerformanceObserverObserveOptions {\n  buffered?: boolean;\n  durationThreshold?: number;\n  entryTypes?: string[];\n  type?: string;\n}\ninterface EventCounts {\n  get size(): number;\n  get(eventType: string): number | undefined;\n  has(eventType: string): boolean;\n  entries(): IterableIterator<string[]>;\n  keys(): IterableIterator<string>;\n  values(): IterableIterator<number>;\n  forEach(\n    param1: (param0: number, param1: string, param2: EventCounts) => void,\n    param2?: any,\n  ): void;\n  [Symbol.iterator](): IterableIterator<string[]>;\n}\n// AI Search V2 API Error Interfaces\ninterface AiSearchInternalError extends Error {}\ninterface AiSearchNotFoundError extends Error {}\ninterface AiSearchNameNotSetError extends Error {}\n// AI Search V2 Request Types\ntype AiSearchSearchRequest = {\n  messages: Array<{\n    role: \"system\" | \"developer\" | \"user\" | \"assistant\" | \"tool\";\n    content: string | null;\n  }>;\n  ai_search_options?: {\n    retrieval?: {\n      retrieval_type?: \"vector\" | \"keyword\" | \"hybrid\";\n      /** Match threshold (0-1, default 0.4) */\n      match_threshold?: number;\n      /** Maximum number of results (1-50, default 10) */\n      max_num_results?: number;\n      filters?: VectorizeVectorMetadataFilter;\n      /** Context expansion (0-3, default 0) */\n      context_expansion?: number;\n      [key: string]: unknown;\n    };\n    query_rewrite?: {\n      enabled?: boolean;\n      model?: string;\n      rewrite_prompt?: string;\n      [key: string]: unknown;\n    };\n    reranking?: {\n      /** Enable reranking (default false) */\n      enabled?: boolean;\n      model?: \"@cf/baai/bge-reranker-base\" | \"\";\n      /** Match threshold (0-1, default 0.4) */\n      match_threshold?: number;\n      [key: string]: unknown;\n    };\n    [key: string]: unknown;\n  };\n};\ntype AiSearchChatCompletionsRequest = {\n  messages: Array<{\n    role: \"system\" | \"developer\" | \"user\" | \"assistant\" | \"tool\";\n    content: string | null;\n  }>;\n  model?: string;\n  stream?: boolean;\n  ai_search_options?: {\n    retrieval?: {\n      retrieval_type?: \"vector\" | \"keyword\" | \"hybrid\";\n      match_threshold?: number;\n      max_num_results?: number;\n      filters?: VectorizeVectorMetadataFilter;\n      context_expansion?: number;\n      [key: string]: unknown;\n    };\n    query_rewrite?: {\n      enabled?: boolean;\n      model?: string;\n      rewrite_prompt?: string;\n      [key: string]: unknown;\n    };\n    reranking?: {\n      enabled?: boolean;\n      model?: \"@cf/baai/bge-reranker-base\" | \"\";\n      match_threshold?: number;\n      [key: string]: unknown;\n    };\n    [key: string]: unknown;\n  };\n  [key: string]: unknown;\n};\n// AI Search V2 Response Types\ntype AiSearchSearchResponse = {\n  search_query: string;\n  chunks: Array<{\n    id: string;\n    type: string;\n    /** Match score (0-1) */\n    score: number;\n    text: string;\n    item: {\n      timestamp?: number;\n      key: string;\n      metadata?: Record<string, unknown>;\n    };\n    scoring_details?: {\n      /** Keyword match score (0-1) */\n      keyword_score?: number;\n      /** Vector similarity score (0-1) */\n      vector_score?: number;\n    };\n  }>;\n};\ntype AiSearchListResponse = Array<{\n  id: string;\n  internal_id?: string;\n  account_id?: string;\n  account_tag?: string;\n  /** Whether the instance is enabled (default true) */\n  enable?: boolean;\n  type?: \"r2\" | \"web-crawler\";\n  source?: string;\n  [key: string]: unknown;\n}>;\ntype AiSearchConfig = {\n  /** Instance ID (1-32 chars, pattern: ^[a-z0-9_]+(?:-[a-z0-9_]+)*$) */\n  id: string;\n  type: \"r2\" | \"web-crawler\";\n  source: string;\n  source_params?: object;\n  /** Token ID (UUID format) */\n  token_id?: string;\n  ai_gateway_id?: string;\n  /** Enable query rewriting (default false) */\n  rewrite_query?: boolean;\n  /** Enable reranking (default false) */\n  reranking?: boolean;\n  embedding_model?: string;\n  ai_search_model?: string;\n};\ntype AiSearchInstance = {\n  id: string;\n  enable?: boolean;\n  type?: \"r2\" | \"web-crawler\";\n  source?: string;\n  [key: string]: unknown;\n};\n// AI Search Instance Service - Instance-level operations\ndeclare abstract class AiSearchInstanceService {\n  /**\n   * Search the AI Search instance for relevant chunks.\n   * @param params Search request with messages and AI search options\n   * @returns Search response with matching chunks\n   */\n  search(params: AiSearchSearchRequest): Promise<AiSearchSearchResponse>;\n  /**\n   * Generate chat completions with AI Search context.\n   * @param params Chat completions request with optional streaming\n   * @returns Response object (if streaming) or chat completion result\n   */\n  chatCompletions(\n    params: AiSearchChatCompletionsRequest,\n  ): Promise<Response | object>;\n  /**\n   * Delete this AI Search instance.\n   */\n  delete(): Promise<void>;\n}\n// AI Search Account Service - Account-level operations\ndeclare abstract class AiSearchAccountService {\n  /**\n   * List all AI Search instances in the account.\n   * @returns Array of AI Search instances\n   */\n  list(): Promise<AiSearchListResponse>;\n  /**\n   * Get an AI Search instance by ID.\n   * @param name Instance ID\n   * @returns Instance service for performing operations\n   */\n  get(name: string): AiSearchInstanceService;\n  /**\n   * Create a new AI Search instance.\n   * @param config Instance configuration\n   * @returns Instance service for performing operations\n   */\n  create(config: AiSearchConfig): Promise<AiSearchInstanceService>;\n}\ntype AiImageClassificationInput = {\n  image: number[];\n};\ntype AiImageClassificationOutput = {\n  score?: number;\n  label?: string;\n}[];\ndeclare abstract class BaseAiImageClassification {\n  inputs: AiImageClassificationInput;\n  postProcessedOutputs: AiImageClassificationOutput;\n}\ntype AiImageToTextInput = {\n  image: number[];\n  prompt?: string;\n  max_tokens?: number;\n  temperature?: number;\n  top_p?: number;\n  top_k?: number;\n  seed?: number;\n  repetition_penalty?: number;\n  frequency_penalty?: number;\n  presence_penalty?: number;\n  raw?: boolean;\n  messages?: RoleScopedChatInput[];\n};\ntype AiImageToTextOutput = {\n  description: string;\n};\ndeclare abstract class BaseAiImageToText {\n  inputs: AiImageToTextInput;\n  postProcessedOutputs: AiImageToTextOutput;\n}\ntype AiImageTextToTextInput = {\n  image: string;\n  prompt?: string;\n  max_tokens?: number;\n  temperature?: number;\n  ignore_eos?: boolean;\n  top_p?: number;\n  top_k?: number;\n  seed?: number;\n  repetition_penalty?: number;\n  frequency_penalty?: number;\n  presence_penalty?: number;\n  raw?: boolean;\n  messages?: RoleScopedChatInput[];\n};\ntype AiImageTextToTextOutput = {\n  description: string;\n};\ndeclare abstract class BaseAiImageTextToText {\n  inputs: AiImageTextToTextInput;\n  postProcessedOutputs: AiImageTextToTextOutput;\n}\ntype AiMultimodalEmbeddingsInput = {\n  image: string;\n  text: string[];\n};\ntype AiIMultimodalEmbeddingsOutput = {\n  data: number[][];\n  shape: number[];\n};\ndeclare abstract class BaseAiMultimodalEmbeddings {\n  inputs: AiImageTextToTextInput;\n  postProcessedOutputs: AiImageTextToTextOutput;\n}\ntype AiObjectDetectionInput = {\n  image: number[];\n};\ntype AiObjectDetectionOutput = {\n  score?: number;\n  label?: string;\n}[];\ndeclare abstract class BaseAiObjectDetection {\n  inputs: AiObjectDetectionInput;\n  postProcessedOutputs: AiObjectDetectionOutput;\n}\ntype AiSentenceSimilarityInput = {\n  source: string;\n  sentences: string[];\n};\ntype AiSentenceSimilarityOutput = number[];\ndeclare abstract class BaseAiSentenceSimilarity {\n  inputs: AiSentenceSimilarityInput;\n  postProcessedOutputs: AiSentenceSimilarityOutput;\n}\ntype AiAutomaticSpeechRecognitionInput = {\n  audio: number[];\n};\ntype AiAutomaticSpeechRecognitionOutput = {\n  text?: string;\n  words?: {\n    word: string;\n    start: number;\n    end: number;\n  }[];\n  vtt?: string;\n};\ndeclare abstract class BaseAiAutomaticSpeechRecognition {\n  inputs: AiAutomaticSpeechRecognitionInput;\n  postProcessedOutputs: AiAutomaticSpeechRecognitionOutput;\n}\ntype AiSummarizationInput = {\n  input_text: string;\n  max_length?: number;\n};\ntype AiSummarizationOutput = {\n  summary: string;\n};\ndeclare abstract class BaseAiSummarization {\n  inputs: AiSummarizationInput;\n  postProcessedOutputs: AiSummarizationOutput;\n}\ntype AiTextClassificationInput = {\n  text: string;\n};\ntype AiTextClassificationOutput = {\n  score?: number;\n  label?: string;\n}[];\ndeclare abstract class BaseAiTextClassification {\n  inputs: AiTextClassificationInput;\n  postProcessedOutputs: AiTextClassificationOutput;\n}\ntype AiTextEmbeddingsInput = {\n  text: string | string[];\n};\ntype AiTextEmbeddingsOutput = {\n  shape: number[];\n  data: number[][];\n};\ndeclare abstract class BaseAiTextEmbeddings {\n  inputs: AiTextEmbeddingsInput;\n  postProcessedOutputs: AiTextEmbeddingsOutput;\n}\ntype RoleScopedChatInput = {\n  role:\n    | \"user\"\n    | \"assistant\"\n    | \"system\"\n    | \"tool\"\n    | (string & NonNullable<unknown>);\n  content: string;\n  name?: string;\n};\ntype AiTextGenerationToolLegacyInput = {\n  name: string;\n  description: string;\n  parameters?: {\n    type: \"object\" | (string & NonNullable<unknown>);\n    properties: {\n      [key: string]: {\n        type: string;\n        description?: string;\n      };\n    };\n    required: string[];\n  };\n};\ntype AiTextGenerationToolInput = {\n  type: \"function\" | (string & NonNullable<unknown>);\n  function: {\n    name: string;\n    description: string;\n    parameters?: {\n      type: \"object\" | (string & NonNullable<unknown>);\n      properties: {\n        [key: string]: {\n          type: string;\n          description?: string;\n        };\n      };\n      required: string[];\n    };\n  };\n};\ntype AiTextGenerationFunctionsInput = {\n  name: string;\n  code: string;\n};\ntype AiTextGenerationResponseFormat = {\n  type: string;\n  json_schema?: any;\n};\ntype AiTextGenerationInput = {\n  prompt?: string;\n  raw?: boolean;\n  stream?: boolean;\n  max_tokens?: number;\n  temperature?: number;\n  top_p?: number;\n  top_k?: number;\n  seed?: number;\n  repetition_penalty?: number;\n  frequency_penalty?: number;\n  presence_penalty?: number;\n  messages?: RoleScopedChatInput[];\n  response_format?: AiTextGenerationResponseFormat;\n  tools?:\n    | AiTextGenerationToolInput[]\n    | AiTextGenerationToolLegacyInput[]\n    | (object & NonNullable<unknown>);\n  functions?: AiTextGenerationFunctionsInput[];\n};\ntype AiTextGenerationToolLegacyOutput = {\n  name: string;\n  arguments: unknown;\n};\ntype AiTextGenerationToolOutput = {\n  id: string;\n  type: \"function\";\n  function: {\n    name: string;\n    arguments: string;\n  };\n};\ntype UsageTags = {\n  prompt_tokens: number;\n  completion_tokens: number;\n  total_tokens: number;\n};\ntype AiTextGenerationOutput = {\n  response?: string;\n  tool_calls?: AiTextGenerationToolLegacyOutput[] &\n    AiTextGenerationToolOutput[];\n  usage?: UsageTags;\n};\ndeclare abstract class BaseAiTextGeneration {\n  inputs: AiTextGenerationInput;\n  postProcessedOutputs: AiTextGenerationOutput;\n}\ntype AiTextToSpeechInput = {\n  prompt: string;\n  lang?: string;\n};\ntype AiTextToSpeechOutput =\n  | Uint8Array\n  | {\n      audio: string;\n    };\ndeclare abstract class BaseAiTextToSpeech {\n  inputs: AiTextToSpeechInput;\n  postProcessedOutputs: AiTextToSpeechOutput;\n}\ntype AiTextToImageInput = {\n  prompt: string;\n  negative_prompt?: string;\n  height?: number;\n  width?: number;\n  image?: number[];\n  image_b64?: string;\n  mask?: number[];\n  num_steps?: number;\n  strength?: number;\n  guidance?: number;\n  seed?: number;\n};\ntype AiTextToImageOutput = ReadableStream<Uint8Array>;\ndeclare abstract class BaseAiTextToImage {\n  inputs: AiTextToImageInput;\n  postProcessedOutputs: AiTextToImageOutput;\n}\ntype AiTranslationInput = {\n  text: string;\n  target_lang: string;\n  source_lang?: string;\n};\ntype AiTranslationOutput = {\n  translated_text?: string;\n};\ndeclare abstract class BaseAiTranslation {\n  inputs: AiTranslationInput;\n  postProcessedOutputs: AiTranslationOutput;\n}\n/**\n * Workers AI support for OpenAI's Chat Completions API\n */\ntype ChatCompletionContentPartText = {\n  type: \"text\";\n  text: string;\n};\ntype ChatCompletionContentPartImage = {\n  type: \"image_url\";\n  image_url: {\n    url: string;\n    detail?: \"auto\" | \"low\" | \"high\";\n  };\n};\ntype ChatCompletionContentPartInputAudio = {\n  type: \"input_audio\";\n  input_audio: {\n    /** Base64 encoded audio data. */\n    data: string;\n    format: \"wav\" | \"mp3\";\n  };\n};\ntype ChatCompletionContentPartFile = {\n  type: \"file\";\n  file: {\n    /** Base64 encoded file data. */\n    file_data?: string;\n    /** The ID of an uploaded file. */\n    file_id?: string;\n    filename?: string;\n  };\n};\ntype ChatCompletionContentPartRefusal = {\n  type: \"refusal\";\n  refusal: string;\n};\ntype ChatCompletionContentPart =\n  | ChatCompletionContentPartText\n  | ChatCompletionContentPartImage\n  | ChatCompletionContentPartInputAudio\n  | ChatCompletionContentPartFile;\ntype FunctionDefinition = {\n  name: string;\n  description?: string;\n  parameters?: Record<string, unknown>;\n  strict?: boolean | null;\n};\ntype ChatCompletionFunctionTool = {\n  type: \"function\";\n  function: FunctionDefinition;\n};\ntype ChatCompletionCustomToolGrammarFormat = {\n  type: \"grammar\";\n  grammar: {\n    definition: string;\n    syntax: \"lark\" | \"regex\";\n  };\n};\ntype ChatCompletionCustomToolTextFormat = {\n  type: \"text\";\n};\ntype ChatCompletionCustomToolFormat =\n  | ChatCompletionCustomToolTextFormat\n  | ChatCompletionCustomToolGrammarFormat;\ntype ChatCompletionCustomTool = {\n  type: \"custom\";\n  custom: {\n    name: string;\n    description?: string;\n    format?: ChatCompletionCustomToolFormat;\n  };\n};\ntype ChatCompletionTool = ChatCompletionFunctionTool | ChatCompletionCustomTool;\ntype ChatCompletionMessageFunctionToolCall = {\n  id: string;\n  type: \"function\";\n  function: {\n    name: string;\n    /** JSON-encoded arguments string. */\n    arguments: string;\n  };\n};\ntype ChatCompletionMessageCustomToolCall = {\n  id: string;\n  type: \"custom\";\n  custom: {\n    name: string;\n    input: string;\n  };\n};\ntype ChatCompletionMessageToolCall =\n  | ChatCompletionMessageFunctionToolCall\n  | ChatCompletionMessageCustomToolCall;\ntype ChatCompletionToolChoiceFunction = {\n  type: \"function\";\n  function: {\n    name: string;\n  };\n};\ntype ChatCompletionToolChoiceCustom = {\n  type: \"custom\";\n  custom: {\n    name: string;\n  };\n};\ntype ChatCompletionToolChoiceAllowedTools = {\n  type: \"allowed_tools\";\n  allowed_tools: {\n    mode: \"auto\" | \"required\";\n    tools: Array<Record<string, unknown>>;\n  };\n};\ntype ChatCompletionToolChoiceOption =\n  | \"none\"\n  | \"auto\"\n  | \"required\"\n  | ChatCompletionToolChoiceFunction\n  | ChatCompletionToolChoiceCustom\n  | ChatCompletionToolChoiceAllowedTools;\ntype DeveloperMessage = {\n  role: \"developer\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n  name?: string;\n};\ntype SystemMessage = {\n  role: \"system\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n  name?: string;\n};\n/**\n * Permissive merged content part used inside UserMessage arrays.\n *\n * Cabidela has a limitation where anyOf/oneOf with enum-based discrimination\n * inside nested array items does not correctly match different branches for\n * different array elements, so the schema uses a single merged object.\n */\ntype UserMessageContentPart = {\n  type: \"text\" | \"image_url\" | \"input_audio\" | \"file\";\n  text?: string;\n  image_url?: {\n    url?: string;\n    detail?: \"auto\" | \"low\" | \"high\";\n  };\n  input_audio?: {\n    data?: string;\n    format?: \"wav\" | \"mp3\";\n  };\n  file?: {\n    file_data?: string;\n    file_id?: string;\n    filename?: string;\n  };\n};\ntype UserMessage = {\n  role: \"user\";\n  content: string | Array<UserMessageContentPart>;\n  name?: string;\n};\ntype AssistantMessageContentPart = {\n  type: \"text\" | \"refusal\";\n  text?: string;\n  refusal?: string;\n};\ntype AssistantMessage = {\n  role: \"assistant\";\n  content?: string | null | Array<AssistantMessageContentPart>;\n  refusal?: string | null;\n  name?: string;\n  audio?: {\n    id: string;\n  };\n  tool_calls?: Array<ChatCompletionMessageToolCall>;\n  function_call?: {\n    name: string;\n    arguments: string;\n  };\n};\ntype ToolMessage = {\n  role: \"tool\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n  tool_call_id: string;\n};\ntype FunctionMessage = {\n  role: \"function\";\n  content: string;\n  name: string;\n};\ntype ChatCompletionMessageParam =\n  | DeveloperMessage\n  | SystemMessage\n  | UserMessage\n  | AssistantMessage\n  | ToolMessage\n  | FunctionMessage;\ntype ChatCompletionsResponseFormatText = {\n  type: \"text\";\n};\ntype ChatCompletionsResponseFormatJSONObject = {\n  type: \"json_object\";\n};\ntype ResponseFormatJSONSchema = {\n  type: \"json_schema\";\n  json_schema: {\n    name: string;\n    description?: string;\n    schema?: Record<string, unknown>;\n    strict?: boolean | null;\n  };\n};\ntype ResponseFormat =\n  | ChatCompletionsResponseFormatText\n  | ChatCompletionsResponseFormatJSONObject\n  | ResponseFormatJSONSchema;\ntype ChatCompletionsStreamOptions = {\n  include_usage?: boolean;\n  include_obfuscation?: boolean;\n};\ntype PredictionContent = {\n  type: \"content\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n};\ntype AudioParams = {\n  voice:\n    | string\n    | {\n        id: string;\n      };\n  format: \"wav\" | \"aac\" | \"mp3\" | \"flac\" | \"opus\" | \"pcm16\";\n};\ntype WebSearchUserLocation = {\n  type: \"approximate\";\n  approximate: {\n    city?: string;\n    country?: string;\n    region?: string;\n    timezone?: string;\n  };\n};\ntype WebSearchOptions = {\n  search_context_size?: \"low\" | \"medium\" | \"high\";\n  user_location?: WebSearchUserLocation;\n};\ntype ChatTemplateKwargs = {\n  /** Whether to enable reasoning, enabled by default. */\n  enable_thinking?: boolean;\n  /** If false, preserves reasoning context between turns. */\n  clear_thinking?: boolean;\n};\n/** Shared optional properties used by both Prompt and Messages input branches. */\ntype ChatCompletionsCommonOptions = {\n  model?: string;\n  audio?: AudioParams;\n  frequency_penalty?: number | null;\n  logit_bias?: Record<string, unknown> | null;\n  logprobs?: boolean | null;\n  top_logprobs?: number | null;\n  max_tokens?: number | null;\n  max_completion_tokens?: number | null;\n  metadata?: Record<string, unknown> | null;\n  modalities?: Array<\"text\" | \"audio\"> | null;\n  n?: number | null;\n  parallel_tool_calls?: boolean;\n  prediction?: PredictionContent;\n  presence_penalty?: number | null;\n  reasoning_effort?: \"low\" | \"medium\" | \"high\" | null;\n  chat_template_kwargs?: ChatTemplateKwargs;\n  response_format?: ResponseFormat;\n  seed?: number | null;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n  stop?: string | Array<string> | null;\n  store?: boolean | null;\n  stream?: boolean | null;\n  stream_options?: ChatCompletionsStreamOptions;\n  temperature?: number | null;\n  tool_choice?: ChatCompletionToolChoiceOption;\n  tools?: Array<ChatCompletionTool>;\n  top_p?: number | null;\n  user?: string;\n  web_search_options?: WebSearchOptions;\n  function_call?:\n    | \"none\"\n    | \"auto\"\n    | {\n        name: string;\n      };\n  functions?: Array<FunctionDefinition>;\n};\ntype PromptTokensDetails = {\n  cached_tokens?: number;\n  audio_tokens?: number;\n};\ntype CompletionTokensDetails = {\n  reasoning_tokens?: number;\n  audio_tokens?: number;\n  accepted_prediction_tokens?: number;\n  rejected_prediction_tokens?: number;\n};\ntype CompletionUsage = {\n  prompt_tokens: number;\n  completion_tokens: number;\n  total_tokens: number;\n  prompt_tokens_details?: PromptTokensDetails;\n  completion_tokens_details?: CompletionTokensDetails;\n};\ntype ChatCompletionTopLogprob = {\n  token: string;\n  logprob: number;\n  bytes: Array<number> | null;\n};\ntype ChatCompletionTokenLogprob = {\n  token: string;\n  logprob: number;\n  bytes: Array<number> | null;\n  top_logprobs: Array<ChatCompletionTopLogprob>;\n};\ntype ChatCompletionAudio = {\n  id: string;\n  /** Base64 encoded audio bytes. */\n  data: string;\n  expires_at: number;\n  transcript: string;\n};\ntype ChatCompletionUrlCitation = {\n  type: \"url_citation\";\n  url_citation: {\n    url: string;\n    title: string;\n    start_index: number;\n    end_index: number;\n  };\n};\ntype ChatCompletionResponseMessage = {\n  role: \"assistant\";\n  content: string | null;\n  refusal: string | null;\n  annotations?: Array<ChatCompletionUrlCitation>;\n  audio?: ChatCompletionAudio;\n  tool_calls?: Array<ChatCompletionMessageToolCall>;\n  function_call?: {\n    name: string;\n    arguments: string;\n  } | null;\n};\ntype ChatCompletionLogprobs = {\n  content: Array<ChatCompletionTokenLogprob> | null;\n  refusal?: Array<ChatCompletionTokenLogprob> | null;\n};\ntype ChatCompletionChoice = {\n  index: number;\n  message: ChatCompletionResponseMessage;\n  finish_reason:\n    | \"stop\"\n    | \"length\"\n    | \"tool_calls\"\n    | \"content_filter\"\n    | \"function_call\";\n  logprobs: ChatCompletionLogprobs | null;\n};\ntype ChatCompletionsPromptInput = {\n  prompt: string;\n} & ChatCompletionsCommonOptions;\ntype ChatCompletionsMessagesInput = {\n  messages: Array<ChatCompletionMessageParam>;\n} & ChatCompletionsCommonOptions;\ntype ChatCompletionsOutput = {\n  id: string;\n  object: string;\n  created: number;\n  model: string;\n  choices: Array<ChatCompletionChoice>;\n  usage?: CompletionUsage;\n  system_fingerprint?: string | null;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n};\n/**\n * Workers AI support for OpenAI's Responses API\n * Reference: https://github.com/openai/openai-node/blob/master/src/resources/responses/responses.ts\n *\n * It's a stripped down version from its source.\n * It currently supports basic function calling, json mode and accepts images as input.\n *\n * It does not include types for WebSearch, CodeInterpreter, FileInputs, MCP, CustomTools.\n * We plan to add those incrementally as model + platform capabilities evolve.\n */\ntype ResponsesInput = {\n  background?: boolean | null;\n  conversation?: string | ResponseConversationParam | null;\n  include?: Array<ResponseIncludable> | null;\n  input?: string | ResponseInput;\n  instructions?: string | null;\n  max_output_tokens?: number | null;\n  parallel_tool_calls?: boolean | null;\n  previous_response_id?: string | null;\n  prompt_cache_key?: string;\n  reasoning?: Reasoning | null;\n  safety_identifier?: string;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n  stream?: boolean | null;\n  stream_options?: StreamOptions | null;\n  temperature?: number | null;\n  text?: ResponseTextConfig;\n  tool_choice?: ToolChoiceOptions | ToolChoiceFunction;\n  tools?: Array<Tool>;\n  top_p?: number | null;\n  truncation?: \"auto\" | \"disabled\" | null;\n};\ntype ResponsesOutput = {\n  id?: string;\n  created_at?: number;\n  output_text?: string;\n  error?: ResponseError | null;\n  incomplete_details?: ResponseIncompleteDetails | null;\n  instructions?: string | Array<ResponseInputItem> | null;\n  object?: \"response\";\n  output?: Array<ResponseOutputItem>;\n  parallel_tool_calls?: boolean;\n  temperature?: number | null;\n  tool_choice?: ToolChoiceOptions | ToolChoiceFunction;\n  tools?: Array<Tool>;\n  top_p?: number | null;\n  max_output_tokens?: number | null;\n  previous_response_id?: string | null;\n  prompt?: ResponsePrompt | null;\n  reasoning?: Reasoning | null;\n  safety_identifier?: string;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n  status?: ResponseStatus;\n  text?: ResponseTextConfig;\n  truncation?: \"auto\" | \"disabled\" | null;\n  usage?: ResponseUsage;\n};\ntype EasyInputMessage = {\n  content: string | ResponseInputMessageContentList;\n  role: \"user\" | \"assistant\" | \"system\" | \"developer\";\n  type?: \"message\";\n};\ntype ResponsesFunctionTool = {\n  name: string;\n  parameters: {\n    [key: string]: unknown;\n  } | null;\n  strict: boolean | null;\n  type: \"function\";\n  description?: string | null;\n};\ntype ResponseIncompleteDetails = {\n  reason?: \"max_output_tokens\" | \"content_filter\";\n};\ntype ResponsePrompt = {\n  id: string;\n  variables?: {\n    [key: string]: string | ResponseInputText | ResponseInputImage;\n  } | null;\n  version?: string | null;\n};\ntype Reasoning = {\n  effort?: ReasoningEffort | null;\n  generate_summary?: \"auto\" | \"concise\" | \"detailed\" | null;\n  summary?: \"auto\" | \"concise\" | \"detailed\" | null;\n};\ntype ResponseContent =\n  | ResponseInputText\n  | ResponseInputImage\n  | ResponseOutputText\n  | ResponseOutputRefusal\n  | ResponseContentReasoningText;\ntype ResponseContentReasoningText = {\n  text: string;\n  type: \"reasoning_text\";\n};\ntype ResponseConversationParam = {\n  id: string;\n};\ntype ResponseCreatedEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.created\";\n};\ntype ResponseCustomToolCallOutput = {\n  call_id: string;\n  output: string | Array<ResponseInputText | ResponseInputImage>;\n  type: \"custom_tool_call_output\";\n  id?: string;\n};\ntype ResponseError = {\n  code:\n    | \"server_error\"\n    | \"rate_limit_exceeded\"\n    | \"invalid_prompt\"\n    | \"vector_store_timeout\"\n    | \"invalid_image\"\n    | \"invalid_image_format\"\n    | \"invalid_base64_image\"\n    | \"invalid_image_url\"\n    | \"image_too_large\"\n    | \"image_too_small\"\n    | \"image_parse_error\"\n    | \"image_content_policy_violation\"\n    | \"invalid_image_mode\"\n    | \"image_file_too_large\"\n    | \"unsupported_image_media_type\"\n    | \"empty_image_file\"\n    | \"failed_to_download_image\"\n    | \"image_file_not_found\";\n  message: string;\n};\ntype ResponseErrorEvent = {\n  code: string | null;\n  message: string;\n  param: string | null;\n  sequence_number: number;\n  type: \"error\";\n};\ntype ResponseFailedEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.failed\";\n};\ntype ResponseFormatText = {\n  type: \"text\";\n};\ntype ResponseFormatJSONObject = {\n  type: \"json_object\";\n};\ntype ResponseFormatTextConfig =\n  | ResponseFormatText\n  | ResponseFormatTextJSONSchemaConfig\n  | ResponseFormatJSONObject;\ntype ResponseFormatTextJSONSchemaConfig = {\n  name: string;\n  schema: {\n    [key: string]: unknown;\n  };\n  type: \"json_schema\";\n  description?: string;\n  strict?: boolean | null;\n};\ntype ResponseFunctionCallArgumentsDeltaEvent = {\n  delta: string;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.function_call_arguments.delta\";\n};\ntype ResponseFunctionCallArgumentsDoneEvent = {\n  arguments: string;\n  item_id: string;\n  name: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.function_call_arguments.done\";\n};\ntype ResponseFunctionCallOutputItem =\n  | ResponseInputTextContent\n  | ResponseInputImageContent;\ntype ResponseFunctionCallOutputItemList = Array<ResponseFunctionCallOutputItem>;\ntype ResponseFunctionToolCall = {\n  arguments: string;\n  call_id: string;\n  name: string;\n  type: \"function_call\";\n  id?: string;\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n};\ninterface ResponseFunctionToolCallItem extends ResponseFunctionToolCall {\n  id: string;\n}\ntype ResponseFunctionToolCallOutputItem = {\n  id: string;\n  call_id: string;\n  output: string | Array<ResponseInputText | ResponseInputImage>;\n  type: \"function_call_output\";\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n};\ntype ResponseIncludable =\n  | \"message.input_image.image_url\"\n  | \"message.output_text.logprobs\";\ntype ResponseIncompleteEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.incomplete\";\n};\ntype ResponseInput = Array<ResponseInputItem>;\ntype ResponseInputContent = ResponseInputText | ResponseInputImage;\ntype ResponseInputImage = {\n  detail: \"low\" | \"high\" | \"auto\";\n  type: \"input_image\";\n  /**\n   * Base64 encoded image\n   */\n  image_url?: string | null;\n};\ntype ResponseInputImageContent = {\n  type: \"input_image\";\n  detail?: \"low\" | \"high\" | \"auto\" | null;\n  /**\n   * Base64 encoded image\n   */\n  image_url?: string | null;\n};\ntype ResponseInputItem =\n  | EasyInputMessage\n  | ResponseInputItemMessage\n  | ResponseOutputMessage\n  | ResponseFunctionToolCall\n  | ResponseInputItemFunctionCallOutput\n  | ResponseReasoningItem;\ntype ResponseInputItemFunctionCallOutput = {\n  call_id: string;\n  output: string | ResponseFunctionCallOutputItemList;\n  type: \"function_call_output\";\n  id?: string | null;\n  status?: \"in_progress\" | \"completed\" | \"incomplete\" | null;\n};\ntype ResponseInputItemMessage = {\n  content: ResponseInputMessageContentList;\n  role: \"user\" | \"system\" | \"developer\";\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n  type?: \"message\";\n};\ntype ResponseInputMessageContentList = Array<ResponseInputContent>;\ntype ResponseInputMessageItem = {\n  id: string;\n  content: ResponseInputMessageContentList;\n  role: \"user\" | \"system\" | \"developer\";\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n  type?: \"message\";\n};\ntype ResponseInputText = {\n  text: string;\n  type: \"input_text\";\n};\ntype ResponseInputTextContent = {\n  text: string;\n  type: \"input_text\";\n};\ntype ResponseItem =\n  | ResponseInputMessageItem\n  | ResponseOutputMessage\n  | ResponseFunctionToolCallItem\n  | ResponseFunctionToolCallOutputItem;\ntype ResponseOutputItem =\n  | ResponseOutputMessage\n  | ResponseFunctionToolCall\n  | ResponseReasoningItem;\ntype ResponseOutputItemAddedEvent = {\n  item: ResponseOutputItem;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.output_item.added\";\n};\ntype ResponseOutputItemDoneEvent = {\n  item: ResponseOutputItem;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.output_item.done\";\n};\ntype ResponseOutputMessage = {\n  id: string;\n  content: Array<ResponseOutputText | ResponseOutputRefusal>;\n  role: \"assistant\";\n  status: \"in_progress\" | \"completed\" | \"incomplete\";\n  type: \"message\";\n};\ntype ResponseOutputRefusal = {\n  refusal: string;\n  type: \"refusal\";\n};\ntype ResponseOutputText = {\n  text: string;\n  type: \"output_text\";\n  logprobs?: Array<Logprob>;\n};\ntype ResponseReasoningItem = {\n  id: string;\n  summary: Array<ResponseReasoningSummaryItem>;\n  type: \"reasoning\";\n  content?: Array<ResponseReasoningContentItem>;\n  encrypted_content?: string | null;\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n};\ntype ResponseReasoningSummaryItem = {\n  text: string;\n  type: \"summary_text\";\n};\ntype ResponseReasoningContentItem = {\n  text: string;\n  type: \"reasoning_text\";\n};\ntype ResponseReasoningTextDeltaEvent = {\n  content_index: number;\n  delta: string;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.reasoning_text.delta\";\n};\ntype ResponseReasoningTextDoneEvent = {\n  content_index: number;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  text: string;\n  type: \"response.reasoning_text.done\";\n};\ntype ResponseRefusalDeltaEvent = {\n  content_index: number;\n  delta: string;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.refusal.delta\";\n};\ntype ResponseRefusalDoneEvent = {\n  content_index: number;\n  item_id: string;\n  output_index: number;\n  refusal: string;\n  sequence_number: number;\n  type: \"response.refusal.done\";\n};\ntype ResponseStatus =\n  | \"completed\"\n  | \"failed\"\n  | \"in_progress\"\n  | \"cancelled\"\n  | \"queued\"\n  | \"incomplete\";\ntype ResponseStreamEvent =\n  | ResponseCompletedEvent\n  | ResponseCreatedEvent\n  | ResponseErrorEvent\n  | ResponseFunctionCallArgumentsDeltaEvent\n  | ResponseFunctionCallArgumentsDoneEvent\n  | ResponseFailedEvent\n  | ResponseIncompleteEvent\n  | ResponseOutputItemAddedEvent\n  | ResponseOutputItemDoneEvent\n  | ResponseReasoningTextDeltaEvent\n  | ResponseReasoningTextDoneEvent\n  | ResponseRefusalDeltaEvent\n  | ResponseRefusalDoneEvent\n  | ResponseTextDeltaEvent\n  | ResponseTextDoneEvent;\ntype ResponseCompletedEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.completed\";\n};\ntype ResponseTextConfig = {\n  format?: ResponseFormatTextConfig;\n  verbosity?: \"low\" | \"medium\" | \"high\" | null;\n};\ntype ResponseTextDeltaEvent = {\n  content_index: number;\n  delta: string;\n  item_id: string;\n  logprobs: Array<Logprob>;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.output_text.delta\";\n};\ntype ResponseTextDoneEvent = {\n  content_index: number;\n  item_id: string;\n  logprobs: Array<Logprob>;\n  output_index: number;\n  sequence_number: number;\n  text: string;\n  type: \"response.output_text.done\";\n};\ntype Logprob = {\n  token: string;\n  logprob: number;\n  top_logprobs?: Array<TopLogprob>;\n};\ntype TopLogprob = {\n  token?: string;\n  logprob?: number;\n};\ntype ResponseUsage = {\n  input_tokens: number;\n  output_tokens: number;\n  total_tokens: number;\n};\ntype Tool = ResponsesFunctionTool;\ntype ToolChoiceFunction = {\n  name: string;\n  type: \"function\";\n};\ntype ToolChoiceOptions = \"none\";\ntype ReasoningEffort = \"minimal\" | \"low\" | \"medium\" | \"high\" | null;\ntype StreamOptions = {\n  include_obfuscation?: boolean;\n};\n/** Marks keys from T that aren't in U as optional never */\ntype Without<T, U> = {\n  [P in Exclude<keyof T, keyof U>]?: never;\n};\n/** Either T or U, but not both (mutually exclusive) */\ntype XOR<T, U> = (T & Without<U, T>) | (U & Without<T, U>);\ntype Ai_Cf_Baai_Bge_Base_En_V1_5_Input =\n  | {\n      text: string | string[];\n      /**\n       * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        text: string | string[];\n        /**\n         * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n         */\n        pooling?: \"mean\" | \"cls\";\n      }[];\n    };\ntype Ai_Cf_Baai_Bge_Base_En_V1_5_Output =\n  | {\n      shape?: number[];\n      /**\n       * Embeddings of the requested text values\n       */\n      data?: number[][];\n      /**\n       * The pooling method used in the embedding process.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | Ai_Cf_Baai_Bge_Base_En_V1_5_AsyncResponse;\ninterface Ai_Cf_Baai_Bge_Base_En_V1_5_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\ndeclare abstract class Base_Ai_Cf_Baai_Bge_Base_En_V1_5 {\n  inputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Output;\n}\ntype Ai_Cf_Openai_Whisper_Input =\n  | string\n  | {\n      /**\n       * An array of integers that represent the audio data constrained to 8-bit unsigned integer values\n       */\n      audio: number[];\n    };\ninterface Ai_Cf_Openai_Whisper_Output {\n  /**\n   * The transcription\n   */\n  text: string;\n  word_count?: number;\n  words?: {\n    word?: string;\n    /**\n     * The second this word begins in the recording\n     */\n    start?: number;\n    /**\n     * The ending second when the word completes\n     */\n    end?: number;\n  }[];\n  vtt?: string;\n}\ndeclare abstract class Base_Ai_Cf_Openai_Whisper {\n  inputs: Ai_Cf_Openai_Whisper_Input;\n  postProcessedOutputs: Ai_Cf_Openai_Whisper_Output;\n}\ntype Ai_Cf_Meta_M2M100_1_2B_Input =\n  | {\n      /**\n       * The text to be translated\n       */\n      text: string;\n      /**\n       * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified\n       */\n      source_lang?: string;\n      /**\n       * The language code to translate the text into (e.g., 'es' for Spanish)\n       */\n      target_lang: string;\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        /**\n         * The text to be translated\n         */\n        text: string;\n        /**\n         * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified\n         */\n        source_lang?: string;\n        /**\n         * The language code to translate the text into (e.g., 'es' for Spanish)\n         */\n        target_lang: string;\n      }[];\n    };\ntype Ai_Cf_Meta_M2M100_1_2B_Output =\n  | {\n      /**\n       * The translated text in the target language\n       */\n      translated_text?: string;\n    }\n  | Ai_Cf_Meta_M2M100_1_2B_AsyncResponse;\ninterface Ai_Cf_Meta_M2M100_1_2B_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\ndeclare abstract class Base_Ai_Cf_Meta_M2M100_1_2B {\n  inputs: Ai_Cf_Meta_M2M100_1_2B_Input;\n  postProcessedOutputs: Ai_Cf_Meta_M2M100_1_2B_Output;\n}\ntype Ai_Cf_Baai_Bge_Small_En_V1_5_Input =\n  | {\n      text: string | string[];\n      /**\n       * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        text: string | string[];\n        /**\n         * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n         */\n        pooling?: \"mean\" | \"cls\";\n      }[];\n    };\ntype Ai_Cf_Baai_Bge_Small_En_V1_5_Output =\n  | {\n      shape?: number[];\n      /**\n       * Embeddings of the requested text values\n       */\n      data?: number[][];\n      /**\n       * The pooling method used in the embedding process.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | Ai_Cf_Baai_Bge_Small_En_V1_5_AsyncResponse;\ninterface Ai_Cf_Baai_Bge_Small_En_V1_5_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\ndeclare abstract class Base_Ai_Cf_Baai_Bge_Small_En_V1_5 {\n  inputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Output;\n}\ntype Ai_Cf_Baai_Bge_Large_En_V1_5_Input =\n  | {\n      text: string | string[];\n      /**\n       * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        text: string | string[];\n        /**\n         * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n         */\n        pooling?: \"mean\" | \"cls\";\n      }[];\n    };\ntype Ai_Cf_Baai_Bge_Large_En_V1_5_Output =\n  | {\n      shape?: number[];\n      /**\n       * Embeddings of the requested text values\n       */\n      data?: number[][];\n      /**\n       * The pooling method used in the embedding process.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | Ai_Cf_Baai_Bge_Large_En_V1_5_AsyncResponse;\ninterface Ai_Cf_Baai_Bge_Large_En_V1_5_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\ndeclare abstract class Base_Ai_Cf_Baai_Bge_Large_En_V1_5 {\n  inputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Output;\n}\ntype Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input =\n  | string\n  | {\n      /**\n       * The input text prompt for the model to generate a response.\n       */\n      prompt?: string;\n      /**\n       * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n       */\n      raw?: boolean;\n      /**\n       * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n       */\n      top_p?: number;\n      /**\n       * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n       */\n      top_k?: number;\n      /**\n       * Random seed for reproducibility of the generation.\n       */\n      seed?: number;\n      /**\n       * Penalty for repeated tokens; higher values discourage repetition.\n       */\n      repetition_penalty?: number;\n      /**\n       * Decreases the likelihood of the model repeating the same lines verbatim.\n       */\n      frequency_penalty?: number;\n      /**\n       * Increases the likelihood of the model introducing new topics.\n       */\n      presence_penalty?: number;\n      image: number[] | (string & NonNullable<unknown>);\n      /**\n       * The maximum number of tokens to generate in the response.\n       */\n      max_tokens?: number;\n    };\ninterface Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output {\n  description?: string;\n}\ndeclare abstract class Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M {\n  inputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input;\n  postProcessedOutputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output;\n}\ntype Ai_Cf_Openai_Whisper_Tiny_En_Input =\n  | string\n  | {\n      /**\n       * An array of integers that represent the audio data constrained to 8-bit unsigned integer values\n       */\n      audio: number[];\n    };\ninterface Ai_Cf_Openai_Whisper_Tiny_En_Output {\n  /**\n   * The transcription\n   */\n  text: string;\n  word_count?: number;\n  words?: {\n    word?: string;\n    /**\n     * The second this word begins in the recording\n     */\n    start?: number;\n    /**\n     * The ending second when the word completes\n     */\n    end?: number;\n  }[];\n  vtt?: string;\n}\ndeclare abstract class Base_Ai_Cf_Openai_Whisper_Tiny_En {\n  inputs: Ai_Cf_Openai_Whisper_Tiny_En_Input;\n  postProcessedOutputs: Ai_Cf_Openai_Whisper_Tiny_En_Output;\n}\ninterface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input {\n  audio:\n    | string\n    | {\n        body?: object;\n        contentType?: string;\n      };\n  /**\n   * Supported tasks are 'translate' or 'transcribe'.\n   */\n  task?: string;\n  /**\n   * The language of the audio being transcribed or translated.\n   */\n  language?: string;\n  /**\n   * Preprocess the audio with a voice activity detection model.\n   */\n  vad_filter?: boolean;\n  /**\n   * A text prompt to help provide context to the model on the contents of the audio.\n   */\n  initial_prompt?: string;\n  /**\n   * The prefix appended to the beginning of the output of the transcription and can guide the transcription result.\n   */\n  prefix?: string;\n  /**\n   * The number of beams to use in beam search decoding. Higher values may improve accuracy at the cost of speed.\n   */\n  beam_size?: number;\n  /**\n   * Whether to condition on previous text during transcription. Setting to false may help prevent hallucination loops.\n   */\n  condition_on_previous_text?: boolean;\n  /**\n   * Threshold for detecting no-speech segments. Segments with no-speech probability above this value are skipped.\n   */\n  no_speech_threshold?: number;\n  /**\n   * Threshold for filtering out segments with high compression ratio, which often indicate repetitive or hallucinated text.\n   */\n  compression_ratio_threshold?: number;\n  /**\n   * Threshold for filtering out segments with low average log probability, indicating low confidence.\n   */\n  log_prob_threshold?: number;\n  /**\n   * Optional threshold (in seconds) to skip silent periods that may cause hallucinations.\n   */\n  hallucination_silence_threshold?: number;\n}\ninterface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output {\n  transcription_info?: {\n    /**\n     * The language of the audio being transcribed or translated.\n     */\n    language?: string;\n    /**\n     * The confidence level or probability of the detected language being accurate, represented as a decimal between 0 and 1.\n     */\n    language_probability?: number;\n    /**\n     * The total duration of the original audio file, in seconds.\n     */\n    duration?: number;\n    /**\n     * The duration of the audio after applying Voice Activity Detection (VAD) to remove silent or irrelevant sections, in seconds.\n     */\n    duration_after_vad?: number;\n  };\n  /**\n   * The complete transcription of the audio.\n   */\n  text: string;\n  /**\n   * The total number of words in the transcription.\n   */\n  word_count?: number;\n  segments?: {\n    /**\n     * The starting time of the segment within the audio, in seconds.\n     */\n    start?: number;\n    /**\n     * The ending time of the segment within the audio, in seconds.\n     */\n    end?: number;\n    /**\n     * The transcription of the segment.\n     */\n    text?: string;\n    /**\n     * The temperature used in the decoding process, controlling randomness in predictions. Lower values result in more deterministic outputs.\n     */\n    temperature?: number;\n    /**\n     * The average log probability of the predictions for the words in this segment, indicating overall confidence.\n     */\n    avg_logprob?: number;\n    /**\n     * The compression ratio of the input to the output, measuring how much the text was compressed during the transcription process.\n     */\n    compression_ratio?: number;\n    /**\n     * The probability that the segment contains no speech, represented as a decimal between 0 and 1.\n     */\n    no_speech_prob?: number;\n    words?: {\n      /**\n       * The individual word transcribed from the audio.\n       */\n      word?: string;\n      /**\n       * The starting time of the word within the audio, in seconds.\n       */\n      start?: number;\n      /**\n       * The ending time of the word within the audio, in seconds.\n       */\n      end?: number;\n    }[];\n  }[];\n  /**\n   * The transcription in WebVTT format, which includes timing and text information for use in subtitles.\n   */\n  vtt?: string;\n}\ndeclare abstract class Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo {\n  inputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input;\n  postProcessedOutputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output;\n}\ntype Ai_Cf_Baai_Bge_M3_Input =\n  | Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts\n  | Ai_Cf_Baai_Bge_M3_Input_Embedding\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: (\n        | Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts_1\n        | Ai_Cf_Baai_Bge_M3_Input_Embedding_1\n      )[];\n    };\ninterface Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts {\n  /**\n   * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts\n   */\n  query?: string;\n  /**\n   * List of provided contexts. Note that the index in this array is important, as the response will refer to it.\n   */\n  contexts: {\n    /**\n     * One of the provided context content\n     */\n    text?: string;\n  }[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\ninterface Ai_Cf_Baai_Bge_M3_Input_Embedding {\n  text: string | string[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\ninterface Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts_1 {\n  /**\n   * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts\n   */\n  query?: string;\n  /**\n   * List of provided contexts. Note that the index in this array is important, as the response will refer to it.\n   */\n  contexts: {\n    /**\n     * One of the provided context content\n     */\n    text?: string;\n  }[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\ninterface Ai_Cf_Baai_Bge_M3_Input_Embedding_1 {\n  text: string | string[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\ntype Ai_Cf_Baai_Bge_M3_Output =\n  | Ai_Cf_Baai_Bge_M3_Output_Query\n  | Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts\n  | Ai_Cf_Baai_Bge_M3_Output_Embedding\n  | Ai_Cf_Baai_Bge_M3_AsyncResponse;\ninterface Ai_Cf_Baai_Bge_M3_Output_Query {\n  response?: {\n    /**\n     * Index of the context in the request\n     */\n    id?: number;\n    /**\n     * Score of the context under the index.\n     */\n    score?: number;\n  }[];\n}\ninterface Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts {\n  response?: number[][];\n  shape?: number[];\n  /**\n   * The pooling method used in the embedding process.\n   */\n  pooling?: \"mean\" | \"cls\";\n}\ninterface Ai_Cf_Baai_Bge_M3_Output_Embedding {\n  shape?: number[];\n  /**\n   * Embeddings of the requested text values\n   */\n  data?: number[][];\n  /**\n   * The pooling method used in the embedding process.\n   */\n  pooling?: \"mean\" | \"cls\";\n}\ninterface Ai_Cf_Baai_Bge_M3_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\ndeclare abstract class Base_Ai_Cf_Baai_Bge_M3 {\n  inputs: Ai_Cf_Baai_Bge_M3_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_M3_Output;\n}\ninterface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input {\n  /**\n   * A text description of the image you want to generate.\n   */\n  prompt: string;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer.\n   */\n  steps?: number;\n}\ninterface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output {\n  /**\n   * The generated image in Base64 format.\n   */\n  image?: string;\n}\ndeclare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output;\n}\ntype Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input =\n  | Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Prompt\n  | Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Messages;\ninterface Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  image?: number[] | (string & NonNullable<unknown>);\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n}\ninterface Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  image?: number[] | (string & NonNullable<unknown>);\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * If true, the response will be streamed back incrementally.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ntype Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response?: string;\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\ndeclare abstract class Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct {\n  inputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output;\n}\ntype Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input =\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Async_Batch;\ninterface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Async_Batch {\n  requests?: {\n    /**\n     * User-supplied reference. This field will be present in the response as well it can be used to reference the request and response. It's NOT validated to be unique.\n     */\n    external_reference?: string;\n    /**\n     * Prompt for the text generation model\n     */\n    prompt?: string;\n    /**\n     * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n     */\n    stream?: boolean;\n    /**\n     * The maximum number of tokens to generate in the response.\n     */\n    max_tokens?: number;\n    /**\n     * Controls the randomness of the output; higher values produce more random results.\n     */\n    temperature?: number;\n    /**\n     * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n     */\n    top_p?: number;\n    /**\n     * Random seed for reproducibility of the generation.\n     */\n    seed?: number;\n    /**\n     * Penalty for repeated tokens; higher values discourage repetition.\n     */\n    repetition_penalty?: number;\n    /**\n     * Decreases the likelihood of the model repeating the same lines verbatim.\n     */\n    frequency_penalty?: number;\n    /**\n     * Increases the likelihood of the model introducing new topics.\n     */\n    presence_penalty?: number;\n    response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_2;\n  }[];\n}\ninterface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_2 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ntype Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output =\n  | {\n      /**\n       * The generated text response from the model\n       */\n      response: string;\n      /**\n       * Usage statistics for the inference request\n       */\n      usage?: {\n        /**\n         * Total number of tokens in input\n         */\n        prompt_tokens?: number;\n        /**\n         * Total number of tokens in output\n         */\n        completion_tokens?: number;\n        /**\n         * Total number of input and output tokens\n         */\n        total_tokens?: number;\n      };\n      /**\n       * An array of tool calls requests made during the response generation\n       */\n      tool_calls?: {\n        /**\n         * The arguments passed to be passed to the tool call request\n         */\n        arguments?: object;\n        /**\n         * The name of the tool to be called\n         */\n        name?: string;\n      }[];\n    }\n  | string\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_AsyncResponse;\ninterface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\ndeclare abstract class Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast {\n  inputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output;\n}\ninterface Ai_Cf_Meta_Llama_Guard_3_8B_Input {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender must alternate between 'user' and 'assistant'.\n     */\n    role: \"user\" | \"assistant\";\n    /**\n     * The content of the message as a string.\n     */\n    content: string;\n  }[];\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Dictate the output format of the generated response.\n   */\n  response_format?: {\n    /**\n     * Set to json_object to process and output generated text as JSON.\n     */\n    type?: string;\n  };\n}\ninterface Ai_Cf_Meta_Llama_Guard_3_8B_Output {\n  response?:\n    | string\n    | {\n        /**\n         * Whether the conversation is safe or not.\n         */\n        safe?: boolean;\n        /**\n         * A list of what hazard categories predicted for the conversation, if the conversation is deemed unsafe.\n         */\n        categories?: string[];\n      };\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n}\ndeclare abstract class Base_Ai_Cf_Meta_Llama_Guard_3_8B {\n  inputs: Ai_Cf_Meta_Llama_Guard_3_8B_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_Guard_3_8B_Output;\n}\ninterface Ai_Cf_Baai_Bge_Reranker_Base_Input {\n  /**\n   * A query you wish to perform against the provided contexts.\n   */\n  /**\n   * Number of returned results starting with the best score.\n   */\n  top_k?: number;\n  /**\n   * List of provided contexts. Note that the index in this array is important, as the response will refer to it.\n   */\n  contexts: {\n    /**\n     * One of the provided context content\n     */\n    text?: string;\n  }[];\n}\ninterface Ai_Cf_Baai_Bge_Reranker_Base_Output {\n  response?: {\n    /**\n     * Index of the context in the request\n     */\n    id?: number;\n    /**\n     * Score of the context under the index.\n     */\n    score?: number;\n  }[];\n}\ndeclare abstract class Base_Ai_Cf_Baai_Bge_Reranker_Base {\n  inputs: Ai_Cf_Baai_Bge_Reranker_Base_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Reranker_Base_Output;\n}\ntype Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input =\n  | Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Prompt\n  | Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Messages;\ninterface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    /**\n     * The content of the message as a string.\n     */\n    content: string;\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ntype Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\ndeclare abstract class Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct {\n  inputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output;\n}\ntype Ai_Cf_Qwen_Qwq_32B_Input =\n  | Ai_Cf_Qwen_Qwq_32B_Prompt\n  | Ai_Cf_Qwen_Qwq_32B_Messages;\ninterface Ai_Cf_Qwen_Qwq_32B_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Qwen_Qwq_32B_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ntype Ai_Cf_Qwen_Qwq_32B_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\ndeclare abstract class Base_Ai_Cf_Qwen_Qwq_32B {\n  inputs: Ai_Cf_Qwen_Qwq_32B_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwq_32B_Output;\n}\ntype Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input =\n  | Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Prompt\n  | Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Messages;\ninterface Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ntype Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\ndeclare abstract class Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct {\n  inputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output;\n}\ntype Ai_Cf_Google_Gemma_3_12B_It_Input =\n  | Ai_Cf_Google_Gemma_3_12B_It_Prompt\n  | Ai_Cf_Google_Gemma_3_12B_It_Messages;\ninterface Ai_Cf_Google_Gemma_3_12B_It_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Google_Gemma_3_12B_It_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ntype Ai_Cf_Google_Gemma_3_12B_It_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\ndeclare abstract class Base_Ai_Cf_Google_Gemma_3_12B_It {\n  inputs: Ai_Cf_Google_Gemma_3_12B_It_Input;\n  postProcessedOutputs: Ai_Cf_Google_Gemma_3_12B_It_Output;\n}\ntype Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input =\n  | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt\n  | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages\n  | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Async_Batch;\ninterface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Async_Batch {\n  requests: (\n    | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt_Inner\n    | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages_Inner\n  )[];\n}\ninterface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt_Inner {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages_Inner {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ntype Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The tool call id.\n     */\n    id?: string;\n    /**\n     * Specifies the type of tool (e.g., 'function').\n     */\n    type?: string;\n    /**\n     * Details of the function tool.\n     */\n    function?: {\n      /**\n       * The name of the tool to be called\n       */\n      name?: string;\n      /**\n       * The arguments passed to be passed to the tool call request\n       */\n      arguments?: object;\n    };\n  }[];\n};\ndeclare abstract class Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct {\n  inputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output;\n}\ntype Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Input =\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Async_Batch;\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Async_Batch {\n  requests: (\n    | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt_1\n    | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages_1\n  )[];\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt_1 {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_2;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_2 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages_1 {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_3;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_3 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ntype Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Output =\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Chat_Completion_Response\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Text_Completion_Response\n  | string\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_AsyncResponse;\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Chat_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"chat.completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index?: number;\n    /**\n     * The message generated by the model\n     */\n    message?: {\n      /**\n       * Role of the message author\n       */\n      role: string;\n      /**\n       * The content of the message\n       */\n      content: string;\n      /**\n       * Internal reasoning content (if available)\n       */\n      reasoning_content?: string;\n      /**\n       * Tool calls made by the assistant\n       */\n      tool_calls?: {\n        /**\n         * Unique identifier for the tool call\n         */\n        id: string;\n        /**\n         * Type of tool call\n         */\n        type: \"function\";\n        function: {\n          /**\n           * Name of the function to call\n           */\n          name: string;\n          /**\n           * JSON string of arguments for the function\n           */\n          arguments: string;\n        };\n      }[];\n    };\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason?: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * Log probabilities for the prompt (if requested)\n   */\n  prompt_logprobs?: {} | null;\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Text_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"text_completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index: number;\n    /**\n     * The generated text completion\n     */\n    text: string;\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n    /**\n     * Log probabilities for the prompt (if requested)\n     */\n    prompt_logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\ndeclare abstract class Base_Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8 {\n  inputs: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Output;\n}\ninterface Ai_Cf_Deepgram_Nova_3_Input {\n  audio: {\n    body: object;\n    contentType: string;\n  };\n  /**\n   * Sets how the model will interpret strings submitted to the custom_topic param. When strict, the model will only return topics submitted using the custom_topic param. When extended, the model will return its own detected topics in addition to those submitted using the custom_topic param.\n   */\n  custom_topic_mode?: \"extended\" | \"strict\";\n  /**\n   * Custom topics you want the model to detect within your input audio or text if present Submit up to 100\n   */\n  custom_topic?: string;\n  /**\n   * Sets how the model will interpret intents submitted to the custom_intent param. When strict, the model will only return intents submitted using the custom_intent param. When extended, the model will return its own detected intents in addition those submitted using the custom_intents param\n   */\n  custom_intent_mode?: \"extended\" | \"strict\";\n  /**\n   * Custom intents you want the model to detect within your input audio if present\n   */\n  custom_intent?: string;\n  /**\n   * Identifies and extracts key entities from content in submitted audio\n   */\n  detect_entities?: boolean;\n  /**\n   * Identifies the dominant language spoken in submitted audio\n   */\n  detect_language?: boolean;\n  /**\n   * Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0\n   */\n  diarize?: boolean;\n  /**\n   * Identify and extract key entities from content in submitted audio\n   */\n  dictation?: boolean;\n  /**\n   * Specify the expected encoding of your submitted audio\n   */\n  encoding?:\n    | \"linear16\"\n    | \"flac\"\n    | \"mulaw\"\n    | \"amr-nb\"\n    | \"amr-wb\"\n    | \"opus\"\n    | \"speex\"\n    | \"g729\";\n  /**\n   * Arbitrary key-value pairs that are attached to the API response for usage in downstream processing\n   */\n  extra?: string;\n  /**\n   * Filler Words can help transcribe interruptions in your audio, like 'uh' and 'um'\n   */\n  filler_words?: boolean;\n  /**\n   * Key term prompting can boost or suppress specialized terminology and brands.\n   */\n  keyterm?: string;\n  /**\n   * Keywords can boost or suppress specialized terminology and brands.\n   */\n  keywords?: string;\n  /**\n   * The BCP-47 language tag that hints at the primary spoken language. Depending on the Model and API endpoint you choose only certain languages are available.\n   */\n  language?: string;\n  /**\n   * Spoken measurements will be converted to their corresponding abbreviations.\n   */\n  measurements?: boolean;\n  /**\n   * Opts out requests from the Deepgram Model Improvement Program. Refer to our Docs for pricing impacts before setting this to true. https://dpgr.am/deepgram-mip.\n   */\n  mip_opt_out?: boolean;\n  /**\n   * Mode of operation for the model representing broad area of topic that will be talked about in the supplied audio\n   */\n  mode?: \"general\" | \"medical\" | \"finance\";\n  /**\n   * Transcribe each audio channel independently.\n   */\n  multichannel?: boolean;\n  /**\n   * Numerals converts numbers from written format to numerical format.\n   */\n  numerals?: boolean;\n  /**\n   * Splits audio into paragraphs to improve transcript readability.\n   */\n  paragraphs?: boolean;\n  /**\n   * Profanity Filter looks for recognized profanity and converts it to the nearest recognized non-profane word or removes it from the transcript completely.\n   */\n  profanity_filter?: boolean;\n  /**\n   * Add punctuation and capitalization to the transcript.\n   */\n  punctuate?: boolean;\n  /**\n   * Redaction removes sensitive information from your transcripts.\n   */\n  redact?: string;\n  /**\n   * Search for terms or phrases in submitted audio and replaces them.\n   */\n  replace?: string;\n  /**\n   * Search for terms or phrases in submitted audio.\n   */\n  search?: string;\n  /**\n   * Recognizes the sentiment throughout a transcript or text.\n   */\n  sentiment?: boolean;\n  /**\n   * Apply formatting to transcript output. When set to true, additional formatting will be applied to transcripts to improve readability.\n   */\n  smart_format?: boolean;\n  /**\n   * Detect topics throughout a transcript or text.\n   */\n  topics?: boolean;\n  /**\n   * Segments speech into meaningful semantic units.\n   */\n  utterances?: boolean;\n  /**\n   * Seconds to wait before detecting a pause between words in submitted audio.\n   */\n  utt_split?: number;\n  /**\n   * The number of channels in the submitted audio\n   */\n  channels?: number;\n  /**\n   * Specifies whether the streaming endpoint should provide ongoing transcription updates as more audio is received. When set to true, the endpoint sends continuous updates, meaning transcription results may evolve over time. Note: Supported only for webosockets.\n   */\n  interim_results?: boolean;\n  /**\n   * Indicates how long model will wait to detect whether a speaker has finished speaking or pauses for a significant period of time. When set to a value, the streaming endpoint immediately finalizes the transcription for the processed time range and returns the transcript with a speech_final parameter set to true. Can also be set to false to disable endpointing\n   */\n  endpointing?: string;\n  /**\n   * Indicates that speech has started. You'll begin receiving Speech Started messages upon speech starting. Note: Supported only for webosockets.\n   */\n  vad_events?: boolean;\n  /**\n   * Indicates how long model will wait to send an UtteranceEnd message after a word has been transcribed. Use with interim_results. Note: Supported only for webosockets.\n   */\n  utterance_end_ms?: boolean;\n}\ninterface Ai_Cf_Deepgram_Nova_3_Output {\n  results?: {\n    channels?: {\n      alternatives?: {\n        confidence?: number;\n        transcript?: string;\n        words?: {\n          confidence?: number;\n          end?: number;\n          start?: number;\n          word?: string;\n        }[];\n      }[];\n    }[];\n    summary?: {\n      result?: string;\n      short?: string;\n    };\n    sentiments?: {\n      segments?: {\n        text?: string;\n        start_word?: number;\n        end_word?: number;\n        sentiment?: string;\n        sentiment_score?: number;\n      }[];\n      average?: {\n        sentiment?: string;\n        sentiment_score?: number;\n      };\n    };\n  };\n}\ndeclare abstract class Base_Ai_Cf_Deepgram_Nova_3 {\n  inputs: Ai_Cf_Deepgram_Nova_3_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Nova_3_Output;\n}\ninterface Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Input {\n  queries?: string | string[];\n  /**\n   * Optional instruction for the task\n   */\n  instruction?: string;\n  documents?: string | string[];\n  text?: string | string[];\n}\ninterface Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Output {\n  data?: number[][];\n  shape?: number[];\n}\ndeclare abstract class Base_Ai_Cf_Qwen_Qwen3_Embedding_0_6B {\n  inputs: Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Output;\n}\ntype Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Input =\n  | {\n      /**\n       * readable stream with audio data and content-type specified for that data\n       */\n      audio: {\n        body: object;\n        contentType: string;\n      };\n      /**\n       * type of data PCM data that's sent to the inference server as raw array\n       */\n      dtype?: \"uint8\" | \"float32\" | \"float64\";\n    }\n  | {\n      /**\n       * base64 encoded audio data\n       */\n      audio: string;\n      /**\n       * type of data PCM data that's sent to the inference server as raw array\n       */\n      dtype?: \"uint8\" | \"float32\" | \"float64\";\n    };\ninterface Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Output {\n  /**\n   * if true, end-of-turn was detected\n   */\n  is_complete?: boolean;\n  /**\n   * probability of the end-of-turn detection\n   */\n  probability?: number;\n}\ndeclare abstract class Base_Ai_Cf_Pipecat_Ai_Smart_Turn_V2 {\n  inputs: Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Input;\n  postProcessedOutputs: Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Output;\n}\ndeclare abstract class Base_Ai_Cf_Openai_Gpt_Oss_120B {\n  inputs: XOR<ResponsesInput, ChatCompletionsInput>;\n  postProcessedOutputs: XOR<ResponsesOutput, ChatCompletionsOutput>;\n}\ndeclare abstract class Base_Ai_Cf_Openai_Gpt_Oss_20B {\n  inputs: XOR<ResponsesInput, ChatCompletionsInput>;\n  postProcessedOutputs: XOR<ResponsesOutput, ChatCompletionsOutput>;\n}\ninterface Ai_Cf_Leonardo_Phoenix_1_0_Input {\n  /**\n   * A text description of the image you want to generate.\n   */\n  prompt: string;\n  /**\n   * Controls how closely the generated image should adhere to the prompt; higher values make the image more aligned with the prompt\n   */\n  guidance?: number;\n  /**\n   * Random seed for reproducibility of the image generation\n   */\n  seed?: number;\n  /**\n   * The height of the generated image in pixels\n   */\n  height?: number;\n  /**\n   * The width of the generated image in pixels\n   */\n  width?: number;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer\n   */\n  num_steps?: number;\n  /**\n   * Specify what to exclude from the generated images\n   */\n  negative_prompt?: string;\n}\n/**\n * The generated image in JPEG format\n */\ntype Ai_Cf_Leonardo_Phoenix_1_0_Output = string;\ndeclare abstract class Base_Ai_Cf_Leonardo_Phoenix_1_0 {\n  inputs: Ai_Cf_Leonardo_Phoenix_1_0_Input;\n  postProcessedOutputs: Ai_Cf_Leonardo_Phoenix_1_0_Output;\n}\ninterface Ai_Cf_Leonardo_Lucid_Origin_Input {\n  /**\n   * A text description of the image you want to generate.\n   */\n  prompt: string;\n  /**\n   * Controls how closely the generated image should adhere to the prompt; higher values make the image more aligned with the prompt\n   */\n  guidance?: number;\n  /**\n   * Random seed for reproducibility of the image generation\n   */\n  seed?: number;\n  /**\n   * The height of the generated image in pixels\n   */\n  height?: number;\n  /**\n   * The width of the generated image in pixels\n   */\n  width?: number;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer\n   */\n  num_steps?: number;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer\n   */\n  steps?: number;\n}\ninterface Ai_Cf_Leonardo_Lucid_Origin_Output {\n  /**\n   * The generated image in Base64 format.\n   */\n  image?: string;\n}\ndeclare abstract class Base_Ai_Cf_Leonardo_Lucid_Origin {\n  inputs: Ai_Cf_Leonardo_Lucid_Origin_Input;\n  postProcessedOutputs: Ai_Cf_Leonardo_Lucid_Origin_Output;\n}\ninterface Ai_Cf_Deepgram_Aura_1_Input {\n  /**\n   * Speaker used to produce the audio.\n   */\n  speaker?:\n    | \"angus\"\n    | \"asteria\"\n    | \"arcas\"\n    | \"orion\"\n    | \"orpheus\"\n    | \"athena\"\n    | \"luna\"\n    | \"zeus\"\n    | \"perseus\"\n    | \"helios\"\n    | \"hera\"\n    | \"stella\";\n  /**\n   * Encoding of the output audio.\n   */\n  encoding?: \"linear16\" | \"flac\" | \"mulaw\" | \"alaw\" | \"mp3\" | \"opus\" | \"aac\";\n  /**\n   * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type..\n   */\n  container?: \"none\" | \"wav\" | \"ogg\";\n  /**\n   * The text content to be converted to speech\n   */\n  text: string;\n  /**\n   * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable\n   */\n  sample_rate?: number;\n  /**\n   * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type.\n   */\n  bit_rate?: number;\n}\n/**\n * The generated audio in MP3 format\n */\ntype Ai_Cf_Deepgram_Aura_1_Output = string;\ndeclare abstract class Base_Ai_Cf_Deepgram_Aura_1 {\n  inputs: Ai_Cf_Deepgram_Aura_1_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Aura_1_Output;\n}\ninterface Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Input {\n  /**\n   * Input text to translate. Can be a single string or a list of strings.\n   */\n  text: string | string[];\n  /**\n   * Target langauge to translate to\n   */\n  target_language:\n    | \"asm_Beng\"\n    | \"awa_Deva\"\n    | \"ben_Beng\"\n    | \"bho_Deva\"\n    | \"brx_Deva\"\n    | \"doi_Deva\"\n    | \"eng_Latn\"\n    | \"gom_Deva\"\n    | \"gon_Deva\"\n    | \"guj_Gujr\"\n    | \"hin_Deva\"\n    | \"hne_Deva\"\n    | \"kan_Knda\"\n    | \"kas_Arab\"\n    | \"kas_Deva\"\n    | \"kha_Latn\"\n    | \"lus_Latn\"\n    | \"mag_Deva\"\n    | \"mai_Deva\"\n    | \"mal_Mlym\"\n    | \"mar_Deva\"\n    | \"mni_Beng\"\n    | \"mni_Mtei\"\n    | \"npi_Deva\"\n    | \"ory_Orya\"\n    | \"pan_Guru\"\n    | \"san_Deva\"\n    | \"sat_Olck\"\n    | \"snd_Arab\"\n    | \"snd_Deva\"\n    | \"tam_Taml\"\n    | \"tel_Telu\"\n    | \"urd_Arab\"\n    | \"unr_Deva\";\n}\ninterface Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Output {\n  /**\n   * Translated texts\n   */\n  translations: string[];\n}\ndeclare abstract class Base_Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B {\n  inputs: Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Input;\n  postProcessedOutputs: Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Output;\n}\ntype Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Input =\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Async_Batch;\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Async_Batch {\n  requests: (\n    | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt_1\n    | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages_1\n  )[];\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt_1 {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_2;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_2 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages_1 {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_3;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_3 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ntype Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Output =\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Chat_Completion_Response\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Text_Completion_Response\n  | string\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_AsyncResponse;\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Chat_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"chat.completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index?: number;\n    /**\n     * The message generated by the model\n     */\n    message?: {\n      /**\n       * Role of the message author\n       */\n      role: string;\n      /**\n       * The content of the message\n       */\n      content: string;\n      /**\n       * Internal reasoning content (if available)\n       */\n      reasoning_content?: string;\n      /**\n       * Tool calls made by the assistant\n       */\n      tool_calls?: {\n        /**\n         * Unique identifier for the tool call\n         */\n        id: string;\n        /**\n         * Type of tool call\n         */\n        type: \"function\";\n        function: {\n          /**\n           * Name of the function to call\n           */\n          name: string;\n          /**\n           * JSON string of arguments for the function\n           */\n          arguments: string;\n        };\n      }[];\n    };\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason?: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * Log probabilities for the prompt (if requested)\n   */\n  prompt_logprobs?: {} | null;\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Text_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"text_completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index: number;\n    /**\n     * The generated text completion\n     */\n    text: string;\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n    /**\n     * Log probabilities for the prompt (if requested)\n     */\n    prompt_logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\ndeclare abstract class Base_Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It {\n  inputs: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Input;\n  postProcessedOutputs: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Output;\n}\ninterface Ai_Cf_Pfnet_Plamo_Embedding_1B_Input {\n  /**\n   * Input text to embed. Can be a single string or a list of strings.\n   */\n  text: string | string[];\n}\ninterface Ai_Cf_Pfnet_Plamo_Embedding_1B_Output {\n  /**\n   * Embedding vectors, where each vector is a list of floats.\n   */\n  data: number[][];\n  /**\n   * Shape of the embedding data as [number_of_embeddings, embedding_dimension].\n   *\n   * @minItems 2\n   * @maxItems 2\n   */\n  shape: [number, number];\n}\ndeclare abstract class Base_Ai_Cf_Pfnet_Plamo_Embedding_1B {\n  inputs: Ai_Cf_Pfnet_Plamo_Embedding_1B_Input;\n  postProcessedOutputs: Ai_Cf_Pfnet_Plamo_Embedding_1B_Output;\n}\ninterface Ai_Cf_Deepgram_Flux_Input {\n  /**\n   * Encoding of the audio stream. Currently only supports raw signed little-endian 16-bit PCM.\n   */\n  encoding: \"linear16\";\n  /**\n   * Sample rate of the audio stream in Hz.\n   */\n  sample_rate: string;\n  /**\n   * End-of-turn confidence required to fire an eager end-of-turn event. When set, enables EagerEndOfTurn and TurnResumed events. Valid Values 0.3 - 0.9.\n   */\n  eager_eot_threshold?: string;\n  /**\n   * End-of-turn confidence required to finish a turn. Valid Values 0.5 - 0.9.\n   */\n  eot_threshold?: string;\n  /**\n   * A turn will be finished when this much time has passed after speech, regardless of EOT confidence.\n   */\n  eot_timeout_ms?: string;\n  /**\n   * Keyterm prompting can improve recognition of specialized terminology. Pass multiple keyterm query parameters to boost multiple keyterms.\n   */\n  keyterm?: string;\n  /**\n   * Opts out requests from the Deepgram Model Improvement Program. Refer to Deepgram Docs for pricing impacts before setting this to true. https://dpgr.am/deepgram-mip\n   */\n  mip_opt_out?: \"true\" | \"false\";\n  /**\n   * Label your requests for the purpose of identification during usage reporting\n   */\n  tag?: string;\n}\n/**\n * Output will be returned as websocket messages.\n */\ninterface Ai_Cf_Deepgram_Flux_Output {\n  /**\n   * The unique identifier of the request (uuid)\n   */\n  request_id?: string;\n  /**\n   * Starts at 0 and increments for each message the server sends to the client.\n   */\n  sequence_id?: number;\n  /**\n   * The type of event being reported.\n   */\n  event?:\n    | \"Update\"\n    | \"StartOfTurn\"\n    | \"EagerEndOfTurn\"\n    | \"TurnResumed\"\n    | \"EndOfTurn\";\n  /**\n   * The index of the current turn\n   */\n  turn_index?: number;\n  /**\n   * Start time in seconds of the audio range that was transcribed\n   */\n  audio_window_start?: number;\n  /**\n   * End time in seconds of the audio range that was transcribed\n   */\n  audio_window_end?: number;\n  /**\n   * Text that was said over the course of the current turn\n   */\n  transcript?: string;\n  /**\n   * The words in the transcript\n   */\n  words?: {\n    /**\n     * The individual punctuated, properly-cased word from the transcript\n     */\n    word: string;\n    /**\n     * Confidence that this word was transcribed correctly\n     */\n    confidence: number;\n  }[];\n  /**\n   * Confidence that no more speech is coming in this turn\n   */\n  end_of_turn_confidence?: number;\n}\ndeclare abstract class Base_Ai_Cf_Deepgram_Flux {\n  inputs: Ai_Cf_Deepgram_Flux_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Flux_Output;\n}\ninterface Ai_Cf_Deepgram_Aura_2_En_Input {\n  /**\n   * Speaker used to produce the audio.\n   */\n  speaker?:\n    | \"amalthea\"\n    | \"andromeda\"\n    | \"apollo\"\n    | \"arcas\"\n    | \"aries\"\n    | \"asteria\"\n    | \"athena\"\n    | \"atlas\"\n    | \"aurora\"\n    | \"callista\"\n    | \"cora\"\n    | \"cordelia\"\n    | \"delia\"\n    | \"draco\"\n    | \"electra\"\n    | \"harmonia\"\n    | \"helena\"\n    | \"hera\"\n    | \"hermes\"\n    | \"hyperion\"\n    | \"iris\"\n    | \"janus\"\n    | \"juno\"\n    | \"jupiter\"\n    | \"luna\"\n    | \"mars\"\n    | \"minerva\"\n    | \"neptune\"\n    | \"odysseus\"\n    | \"ophelia\"\n    | \"orion\"\n    | \"orpheus\"\n    | \"pandora\"\n    | \"phoebe\"\n    | \"pluto\"\n    | \"saturn\"\n    | \"thalia\"\n    | \"theia\"\n    | \"vesta\"\n    | \"zeus\";\n  /**\n   * Encoding of the output audio.\n   */\n  encoding?: \"linear16\" | \"flac\" | \"mulaw\" | \"alaw\" | \"mp3\" | \"opus\" | \"aac\";\n  /**\n   * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type..\n   */\n  container?: \"none\" | \"wav\" | \"ogg\";\n  /**\n   * The text content to be converted to speech\n   */\n  text: string;\n  /**\n   * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable\n   */\n  sample_rate?: number;\n  /**\n   * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type.\n   */\n  bit_rate?: number;\n}\n/**\n * The generated audio in MP3 format\n */\ntype Ai_Cf_Deepgram_Aura_2_En_Output = string;\ndeclare abstract class Base_Ai_Cf_Deepgram_Aura_2_En {\n  inputs: Ai_Cf_Deepgram_Aura_2_En_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Aura_2_En_Output;\n}\ninterface Ai_Cf_Deepgram_Aura_2_Es_Input {\n  /**\n   * Speaker used to produce the audio.\n   */\n  speaker?:\n    | \"sirio\"\n    | \"nestor\"\n    | \"carina\"\n    | \"celeste\"\n    | \"alvaro\"\n    | \"diana\"\n    | \"aquila\"\n    | \"selena\"\n    | \"estrella\"\n    | \"javier\";\n  /**\n   * Encoding of the output audio.\n   */\n  encoding?: \"linear16\" | \"flac\" | \"mulaw\" | \"alaw\" | \"mp3\" | \"opus\" | \"aac\";\n  /**\n   * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type..\n   */\n  container?: \"none\" | \"wav\" | \"ogg\";\n  /**\n   * The text content to be converted to speech\n   */\n  text: string;\n  /**\n   * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable\n   */\n  sample_rate?: number;\n  /**\n   * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type.\n   */\n  bit_rate?: number;\n}\n/**\n * The generated audio in MP3 format\n */\ntype Ai_Cf_Deepgram_Aura_2_Es_Output = string;\ndeclare abstract class Base_Ai_Cf_Deepgram_Aura_2_Es {\n  inputs: Ai_Cf_Deepgram_Aura_2_Es_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Aura_2_Es_Output;\n}\ninterface Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Input {\n  multipart: {\n    body?: object;\n    contentType?: string;\n  };\n}\ninterface Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Output {\n  /**\n   * Generated image as Base64 string.\n   */\n  image?: string;\n}\ndeclare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Dev {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Output;\n}\ninterface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Input {\n  multipart: {\n    body?: object;\n    contentType?: string;\n  };\n}\ninterface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Output {\n  /**\n   * Generated image as Base64 string.\n   */\n  image?: string;\n}\ndeclare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Output;\n}\ninterface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Input {\n  multipart: {\n    body?: object;\n    contentType?: string;\n  };\n}\ninterface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Output {\n  /**\n   * Generated image as Base64 string.\n   */\n  image?: string;\n}\ndeclare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Output;\n}\ndeclare abstract class Base_Ai_Cf_Zai_Org_Glm_4_7_Flash {\n  inputs: ChatCompletionsInput;\n  postProcessedOutputs: ChatCompletionsOutput;\n}\ndeclare abstract class Base_Ai_Cf_Moonshotai_Kimi_K2_5 {\n  inputs: ChatCompletionsInput;\n  postProcessedOutputs: ChatCompletionsOutput;\n}\ndeclare abstract class Base_Ai_Cf_Nvidia_Nemotron_3_120B_A12B {\n  inputs: ChatCompletionsInput;\n  postProcessedOutputs: ChatCompletionsOutput;\n}\ninterface AiModels {\n  \"@cf/huggingface/distilbert-sst-2-int8\": BaseAiTextClassification;\n  \"@cf/stabilityai/stable-diffusion-xl-base-1.0\": BaseAiTextToImage;\n  \"@cf/runwayml/stable-diffusion-v1-5-inpainting\": BaseAiTextToImage;\n  \"@cf/runwayml/stable-diffusion-v1-5-img2img\": BaseAiTextToImage;\n  \"@cf/lykon/dreamshaper-8-lcm\": BaseAiTextToImage;\n  \"@cf/bytedance/stable-diffusion-xl-lightning\": BaseAiTextToImage;\n  \"@cf/myshell-ai/melotts\": BaseAiTextToSpeech;\n  \"@cf/google/embeddinggemma-300m\": BaseAiTextEmbeddings;\n  \"@cf/microsoft/resnet-50\": BaseAiImageClassification;\n  \"@cf/meta/llama-2-7b-chat-int8\": BaseAiTextGeneration;\n  \"@cf/mistral/mistral-7b-instruct-v0.1\": BaseAiTextGeneration;\n  \"@cf/meta/llama-2-7b-chat-fp16\": BaseAiTextGeneration;\n  \"@hf/thebloke/llama-2-13b-chat-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/mistral-7b-instruct-v0.1-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/zephyr-7b-beta-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/openhermes-2.5-mistral-7b-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/neural-chat-7b-v3-1-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/deepseek-coder-6.7b-base-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/deepseek-coder-6.7b-instruct-awq\": BaseAiTextGeneration;\n  \"@cf/deepseek-ai/deepseek-math-7b-instruct\": BaseAiTextGeneration;\n  \"@cf/defog/sqlcoder-7b-2\": BaseAiTextGeneration;\n  \"@cf/openchat/openchat-3.5-0106\": BaseAiTextGeneration;\n  \"@cf/tiiuae/falcon-7b-instruct\": BaseAiTextGeneration;\n  \"@cf/thebloke/discolm-german-7b-v1-awq\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-0.5b-chat\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-7b-chat-awq\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-14b-chat-awq\": BaseAiTextGeneration;\n  \"@cf/tinyllama/tinyllama-1.1b-chat-v1.0\": BaseAiTextGeneration;\n  \"@cf/microsoft/phi-2\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-1.8b-chat\": BaseAiTextGeneration;\n  \"@cf/mistral/mistral-7b-instruct-v0.2-lora\": BaseAiTextGeneration;\n  \"@hf/nousresearch/hermes-2-pro-mistral-7b\": BaseAiTextGeneration;\n  \"@hf/nexusflow/starling-lm-7b-beta\": BaseAiTextGeneration;\n  \"@hf/google/gemma-7b-it\": BaseAiTextGeneration;\n  \"@cf/meta-llama/llama-2-7b-chat-hf-lora\": BaseAiTextGeneration;\n  \"@cf/google/gemma-2b-it-lora\": BaseAiTextGeneration;\n  \"@cf/google/gemma-7b-it-lora\": BaseAiTextGeneration;\n  \"@hf/mistral/mistral-7b-instruct-v0.2\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3-8b-instruct\": BaseAiTextGeneration;\n  \"@cf/fblgit/una-cybertron-7b-v2-bf16\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3-8b-instruct-awq\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.1-8b-instruct-fp8\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.1-8b-instruct-awq\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.2-3b-instruct\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.2-1b-instruct\": BaseAiTextGeneration;\n  \"@cf/deepseek-ai/deepseek-r1-distill-qwen-32b\": BaseAiTextGeneration;\n  \"@cf/ibm-granite/granite-4.0-h-micro\": BaseAiTextGeneration;\n  \"@cf/facebook/bart-large-cnn\": BaseAiSummarization;\n  \"@cf/llava-hf/llava-1.5-7b-hf\": BaseAiImageToText;\n  \"@cf/baai/bge-base-en-v1.5\": Base_Ai_Cf_Baai_Bge_Base_En_V1_5;\n  \"@cf/openai/whisper\": Base_Ai_Cf_Openai_Whisper;\n  \"@cf/meta/m2m100-1.2b\": Base_Ai_Cf_Meta_M2M100_1_2B;\n  \"@cf/baai/bge-small-en-v1.5\": Base_Ai_Cf_Baai_Bge_Small_En_V1_5;\n  \"@cf/baai/bge-large-en-v1.5\": Base_Ai_Cf_Baai_Bge_Large_En_V1_5;\n  \"@cf/unum/uform-gen2-qwen-500m\": Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M;\n  \"@cf/openai/whisper-tiny-en\": Base_Ai_Cf_Openai_Whisper_Tiny_En;\n  \"@cf/openai/whisper-large-v3-turbo\": Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo;\n  \"@cf/baai/bge-m3\": Base_Ai_Cf_Baai_Bge_M3;\n  \"@cf/black-forest-labs/flux-1-schnell\": Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell;\n  \"@cf/meta/llama-3.2-11b-vision-instruct\": Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct;\n  \"@cf/meta/llama-3.3-70b-instruct-fp8-fast\": Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast;\n  \"@cf/meta/llama-guard-3-8b\": Base_Ai_Cf_Meta_Llama_Guard_3_8B;\n  \"@cf/baai/bge-reranker-base\": Base_Ai_Cf_Baai_Bge_Reranker_Base;\n  \"@cf/qwen/qwen2.5-coder-32b-instruct\": Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct;\n  \"@cf/qwen/qwq-32b\": Base_Ai_Cf_Qwen_Qwq_32B;\n  \"@cf/mistralai/mistral-small-3.1-24b-instruct\": Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct;\n  \"@cf/google/gemma-3-12b-it\": Base_Ai_Cf_Google_Gemma_3_12B_It;\n  \"@cf/meta/llama-4-scout-17b-16e-instruct\": Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct;\n  \"@cf/qwen/qwen3-30b-a3b-fp8\": Base_Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8;\n  \"@cf/deepgram/nova-3\": Base_Ai_Cf_Deepgram_Nova_3;\n  \"@cf/qwen/qwen3-embedding-0.6b\": Base_Ai_Cf_Qwen_Qwen3_Embedding_0_6B;\n  \"@cf/pipecat-ai/smart-turn-v2\": Base_Ai_Cf_Pipecat_Ai_Smart_Turn_V2;\n  \"@cf/openai/gpt-oss-120b\": Base_Ai_Cf_Openai_Gpt_Oss_120B;\n  \"@cf/openai/gpt-oss-20b\": Base_Ai_Cf_Openai_Gpt_Oss_20B;\n  \"@cf/leonardo/phoenix-1.0\": Base_Ai_Cf_Leonardo_Phoenix_1_0;\n  \"@cf/leonardo/lucid-origin\": Base_Ai_Cf_Leonardo_Lucid_Origin;\n  \"@cf/deepgram/aura-1\": Base_Ai_Cf_Deepgram_Aura_1;\n  \"@cf/ai4bharat/indictrans2-en-indic-1B\": Base_Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B;\n  \"@cf/aisingapore/gemma-sea-lion-v4-27b-it\": Base_Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It;\n  \"@cf/pfnet/plamo-embedding-1b\": Base_Ai_Cf_Pfnet_Plamo_Embedding_1B;\n  \"@cf/deepgram/flux\": Base_Ai_Cf_Deepgram_Flux;\n  \"@cf/deepgram/aura-2-en\": Base_Ai_Cf_Deepgram_Aura_2_En;\n  \"@cf/deepgram/aura-2-es\": Base_Ai_Cf_Deepgram_Aura_2_Es;\n  \"@cf/black-forest-labs/flux-2-dev\": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Dev;\n  \"@cf/black-forest-labs/flux-2-klein-4b\": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B;\n  \"@cf/black-forest-labs/flux-2-klein-9b\": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B;\n  \"@cf/zai-org/glm-4.7-flash\": Base_Ai_Cf_Zai_Org_Glm_4_7_Flash;\n  \"@cf/moonshotai/kimi-k2.5\": Base_Ai_Cf_Moonshotai_Kimi_K2_5;\n  \"@cf/nvidia/nemotron-3-120b-a12b\": Base_Ai_Cf_Nvidia_Nemotron_3_120B_A12B;\n}\ntype AiOptions = {\n  /**\n   * Send requests as an asynchronous batch job, only works for supported models\n   * https://developers.cloudflare.com/workers-ai/features/batch-api\n   */\n  queueRequest?: boolean;\n  /**\n   * Establish websocket connections, only works for supported models\n   */\n  websocket?: boolean;\n  /**\n   * Tag your requests to group and view them in Cloudflare dashboard.\n   *\n   * Rules:\n   * Tags must only contain letters, numbers, and the symbols: : - . / @\n   * Each tag can have maximum 50 characters.\n   * Maximum 5 tags are allowed each request.\n   * Duplicate tags will removed.\n   */\n  tags?: string[];\n  gateway?: GatewayOptions;\n  returnRawResponse?: boolean;\n  prefix?: string;\n  extraHeaders?: object;\n};\ntype AiModelsSearchParams = {\n  author?: string;\n  hide_experimental?: boolean;\n  page?: number;\n  per_page?: number;\n  search?: string;\n  source?: number;\n  task?: string;\n};\ntype AiModelsSearchObject = {\n  id: string;\n  source: number;\n  name: string;\n  description: string;\n  task: {\n    id: string;\n    name: string;\n    description: string;\n  };\n  tags: string[];\n  properties: {\n    property_id: string;\n    value: string;\n  }[];\n};\ntype ChatCompletionsBase = XOR<\n  ChatCompletionsPromptInput,\n  ChatCompletionsMessagesInput\n>;\ntype ChatCompletionsInput = XOR<\n  ChatCompletionsBase,\n  {\n    requests: ChatCompletionsBase[];\n  }\n>;\ninterface InferenceUpstreamError extends Error {}\ninterface AiInternalError extends Error {}\ntype AiModelListType = Record<string, any>;\ndeclare abstract class Ai<AiModelList extends AiModelListType = AiModels> {\n  aiGatewayLogId: string | null;\n  gateway(gatewayId: string): AiGateway;\n  /**\n   * Access the AI Search API for managing AI-powered search instances.\n   *\n   * This is the new API that replaces AutoRAG with better namespace separation:\n   * - Account-level operations: `list()`, `create()`\n   * - Instance-level operations: `get(id).search()`, `get(id).chatCompletions()`, `get(id).delete()`\n   *\n   * @example\n   * ```typescript\n   * // List all AI Search instances\n   * const instances = await env.AI.aiSearch.list();\n   *\n   * // Search an instance\n   * const results = await env.AI.aiSearch.get('my-search').search({\n   *   messages: [{ role: 'user', content: 'What is the policy?' }],\n   *   ai_search_options: {\n   *     retrieval: { max_num_results: 10 }\n   *   }\n   * });\n   *\n   * // Generate chat completions with AI Search context\n   * const response = await env.AI.aiSearch.get('my-search').chatCompletions({\n   *   messages: [{ role: 'user', content: 'What is the policy?' }],\n   *   model: '@cf/meta/llama-3.3-70b-instruct-fp8-fast'\n   * });\n   * ```\n   */\n  aiSearch(): AiSearchAccountService;\n  /**\n   * @deprecated AutoRAG has been replaced by AI Search.\n   * Use `env.AI.aiSearch` instead for better API design and new features.\n   *\n   * Migration guide:\n   * - `env.AI.autorag().list()` → `env.AI.aiSearch.list()`\n   * - `env.AI.autorag('id').search({ query: '...' })` → `env.AI.aiSearch.get('id').search({ messages: [{ role: 'user', content: '...' }] })`\n   * - `env.AI.autorag('id').aiSearch(...)` → `env.AI.aiSearch.get('id').chatCompletions(...)`\n   *\n   * Note: The old API continues to work for backwards compatibility, but new projects should use AI Search.\n   *\n   * @see AiSearchAccountService\n   * @param autoragId Optional instance ID (omit for account-level operations)\n   */\n  autorag(autoragId: string): AutoRAG;\n  run<\n    Name extends keyof AiModelList,\n    Options extends AiOptions,\n    InputOptions extends AiModelList[Name][\"inputs\"],\n  >(\n    model: Name,\n    inputs: InputOptions,\n    options?: Options,\n  ): Promise<\n    Options extends\n      | {\n          returnRawResponse: true;\n        }\n      | {\n          websocket: true;\n        }\n      ? Response\n      : InputOptions extends {\n            stream: true;\n          }\n        ? ReadableStream\n        : AiModelList[Name][\"postProcessedOutputs\"]\n  >;\n  models(params?: AiModelsSearchParams): Promise<AiModelsSearchObject[]>;\n  toMarkdown(): ToMarkdownService;\n  toMarkdown(\n    files: MarkdownDocument[],\n    options?: ConversionRequestOptions,\n  ): Promise<ConversionResponse[]>;\n  toMarkdown(\n    files: MarkdownDocument,\n    options?: ConversionRequestOptions,\n  ): Promise<ConversionResponse>;\n}\ntype GatewayRetries = {\n  maxAttempts?: 1 | 2 | 3 | 4 | 5;\n  retryDelayMs?: number;\n  backoff?: \"constant\" | \"linear\" | \"exponential\";\n};\ntype GatewayOptions = {\n  id: string;\n  cacheKey?: string;\n  cacheTtl?: number;\n  skipCache?: boolean;\n  metadata?: Record<string, number | string | boolean | null | bigint>;\n  collectLog?: boolean;\n  eventId?: string;\n  requestTimeoutMs?: number;\n  retries?: GatewayRetries;\n};\ntype UniversalGatewayOptions = Exclude<GatewayOptions, \"id\"> & {\n  /**\n   ** @deprecated\n   */\n  id?: string;\n};\ntype AiGatewayPatchLog = {\n  score?: number | null;\n  feedback?: -1 | 1 | null;\n  metadata?: Record<string, number | string | boolean | null | bigint> | null;\n};\ntype AiGatewayLog = {\n  id: string;\n  provider: string;\n  model: string;\n  model_type?: string;\n  path: string;\n  duration: number;\n  request_type?: string;\n  request_content_type?: string;\n  status_code: number;\n  response_content_type?: string;\n  success: boolean;\n  cached: boolean;\n  tokens_in?: number;\n  tokens_out?: number;\n  metadata?: Record<string, number | string | boolean | null | bigint>;\n  step?: number;\n  cost?: number;\n  custom_cost?: boolean;\n  request_size: number;\n  request_head?: string;\n  request_head_complete: boolean;\n  response_size: number;\n  response_head?: string;\n  response_head_complete: boolean;\n  created_at: Date;\n};\ntype AIGatewayProviders =\n  | \"workers-ai\"\n  | \"anthropic\"\n  | \"aws-bedrock\"\n  | \"azure-openai\"\n  | \"google-vertex-ai\"\n  | \"huggingface\"\n  | \"openai\"\n  | \"perplexity-ai\"\n  | \"replicate\"\n  | \"groq\"\n  | \"cohere\"\n  | \"google-ai-studio\"\n  | \"mistral\"\n  | \"grok\"\n  | \"openrouter\"\n  | \"deepseek\"\n  | \"cerebras\"\n  | \"cartesia\"\n  | \"elevenlabs\"\n  | \"adobe-firefly\";\ntype AIGatewayHeaders = {\n  \"cf-aig-metadata\":\n    | Record<string, number | string | boolean | null | bigint>\n    | string;\n  \"cf-aig-custom-cost\":\n    | {\n        per_token_in?: number;\n        per_token_out?: number;\n      }\n    | {\n        total_cost?: number;\n      }\n    | string;\n  \"cf-aig-cache-ttl\": number | string;\n  \"cf-aig-skip-cache\": boolean | string;\n  \"cf-aig-cache-key\": string;\n  \"cf-aig-event-id\": string;\n  \"cf-aig-request-timeout\": number | string;\n  \"cf-aig-max-attempts\": number | string;\n  \"cf-aig-retry-delay\": number | string;\n  \"cf-aig-backoff\": string;\n  \"cf-aig-collect-log\": boolean | string;\n  Authorization: string;\n  \"Content-Type\": string;\n  [key: string]: string | number | boolean | object;\n};\ntype AIGatewayUniversalRequest = {\n  provider: AIGatewayProviders | string; // eslint-disable-line\n  endpoint: string;\n  headers: Partial<AIGatewayHeaders>;\n  query: unknown;\n};\ninterface AiGatewayInternalError extends Error {}\ninterface AiGatewayLogNotFound extends Error {}\ndeclare abstract class AiGateway {\n  patchLog(logId: string, data: AiGatewayPatchLog): Promise<void>;\n  getLog(logId: string): Promise<AiGatewayLog>;\n  run(\n    data: AIGatewayUniversalRequest | AIGatewayUniversalRequest[],\n    options?: {\n      gateway?: UniversalGatewayOptions;\n      extraHeaders?: object;\n    },\n  ): Promise<Response>;\n  getUrl(provider?: AIGatewayProviders | string): Promise<string>; // eslint-disable-line\n}\n/**\n * @deprecated AutoRAG has been replaced by AI Search. Use AiSearchInternalError instead.\n * @see AiSearchInternalError\n */\ninterface AutoRAGInternalError extends Error {}\n/**\n * @deprecated AutoRAG has been replaced by AI Search. Use AiSearchNotFoundError instead.\n * @see AiSearchNotFoundError\n */\ninterface AutoRAGNotFoundError extends Error {}\n/**\n * @deprecated This error type is no longer used in the AI Search API.\n */\ninterface AutoRAGUnauthorizedError extends Error {}\n/**\n * @deprecated AutoRAG has been replaced by AI Search. Use AiSearchNameNotSetError instead.\n * @see AiSearchNameNotSetError\n */\ninterface AutoRAGNameNotSetError extends Error {}\ntype ComparisonFilter = {\n  key: string;\n  type: \"eq\" | \"ne\" | \"gt\" | \"gte\" | \"lt\" | \"lte\";\n  value: string | number | boolean;\n};\ntype CompoundFilter = {\n  type: \"and\" | \"or\";\n  filters: ComparisonFilter[];\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchSearchRequest with the new API instead.\n * @see AiSearchSearchRequest\n */\ntype AutoRagSearchRequest = {\n  query: string;\n  filters?: CompoundFilter | ComparisonFilter;\n  max_num_results?: number;\n  ranking_options?: {\n    ranker?: string;\n    score_threshold?: number;\n  };\n  reranking?: {\n    enabled?: boolean;\n    model?: string;\n  };\n  rewrite_query?: boolean;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchChatCompletionsRequest with the new API instead.\n * @see AiSearchChatCompletionsRequest\n */\ntype AutoRagAiSearchRequest = AutoRagSearchRequest & {\n  stream?: boolean;\n  system_prompt?: string;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchChatCompletionsRequest with stream: true instead.\n * @see AiSearchChatCompletionsRequest\n */\ntype AutoRagAiSearchRequestStreaming = Omit<\n  AutoRagAiSearchRequest,\n  \"stream\"\n> & {\n  stream: true;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchSearchResponse with the new API instead.\n * @see AiSearchSearchResponse\n */\ntype AutoRagSearchResponse = {\n  object: \"vector_store.search_results.page\";\n  search_query: string;\n  data: {\n    file_id: string;\n    filename: string;\n    score: number;\n    attributes: Record<string, string | number | boolean | null>;\n    content: {\n      type: \"text\";\n      text: string;\n    }[];\n  }[];\n  has_more: boolean;\n  next_page: string | null;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchListResponse with the new API instead.\n * @see AiSearchListResponse\n */\ntype AutoRagListResponse = {\n  id: string;\n  enable: boolean;\n  type: string;\n  source: string;\n  vectorize_name: string;\n  paused: boolean;\n  status: string;\n}[];\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * The new API returns different response formats for chat completions.\n */\ntype AutoRagAiSearchResponse = AutoRagSearchResponse & {\n  response: string;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use the new AI Search API instead: `env.AI.aiSearch`\n *\n * Migration guide:\n * - `env.AI.autorag().list()` → `env.AI.aiSearch.list()`\n * - `env.AI.autorag('id').search(...)` → `env.AI.aiSearch.get('id').search(...)`\n * - `env.AI.autorag('id').aiSearch(...)` → `env.AI.aiSearch.get('id').chatCompletions(...)`\n *\n * @see AiSearchAccountService\n * @see AiSearchInstanceService\n */\ndeclare abstract class AutoRAG {\n  /**\n   * @deprecated Use `env.AI.aiSearch.list()` instead.\n   * @see AiSearchAccountService.list\n   */\n  list(): Promise<AutoRagListResponse>;\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).search(...)` instead.\n   * Note: The new API uses a messages array instead of a query string.\n   * @see AiSearchInstanceService.search\n   */\n  search(params: AutoRagSearchRequest): Promise<AutoRagSearchResponse>;\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).chatCompletions(...)` instead.\n   * @see AiSearchInstanceService.chatCompletions\n   */\n  aiSearch(params: AutoRagAiSearchRequestStreaming): Promise<Response>;\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).chatCompletions(...)` instead.\n   * @see AiSearchInstanceService.chatCompletions\n   */\n  aiSearch(params: AutoRagAiSearchRequest): Promise<AutoRagAiSearchResponse>;\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).chatCompletions(...)` instead.\n   * @see AiSearchInstanceService.chatCompletions\n   */\n  aiSearch(\n    params: AutoRagAiSearchRequest,\n  ): Promise<AutoRagAiSearchResponse | Response>;\n}\ninterface BasicImageTransformations {\n  /**\n   * Maximum width in image pixels. The value must be an integer.\n   */\n  width?: number;\n  /**\n   * Maximum height in image pixels. The value must be an integer.\n   */\n  height?: number;\n  /**\n   * Resizing mode as a string. It affects interpretation of width and height\n   * options:\n   *  - scale-down: Similar to contain, but the image is never enlarged. If\n   *    the image is larger than given width or height, it will be resized.\n   *    Otherwise its original size will be kept.\n   *  - contain: Resizes to maximum size that fits within the given width and\n   *    height. If only a single dimension is given (e.g. only width), the\n   *    image will be shrunk or enlarged to exactly match that dimension.\n   *    Aspect ratio is always preserved.\n   *  - cover: Resizes (shrinks or enlarges) to fill the entire area of width\n   *    and height. If the image has an aspect ratio different from the ratio\n   *    of width and height, it will be cropped to fit.\n   *  - crop: The image will be shrunk and cropped to fit within the area\n   *    specified by width and height. The image will not be enlarged. For images\n   *    smaller than the given dimensions it's the same as scale-down. For\n   *    images larger than the given dimensions, it's the same as cover.\n   *    See also trim.\n   *  - pad: Resizes to the maximum size that fits within the given width and\n   *    height, and then fills the remaining area with a background color\n   *    (white by default). Use of this mode is not recommended, as the same\n   *    effect can be more efficiently achieved with the contain mode and the\n   *    CSS object-fit: contain property.\n   *  - squeeze: Stretches and deforms to the width and height given, even if it\n   *    breaks aspect ratio\n   */\n  fit?: \"scale-down\" | \"contain\" | \"cover\" | \"crop\" | \"pad\" | \"squeeze\";\n  /**\n   * Image segmentation using artificial intelligence models. Sets pixels not\n   * within selected segment area to transparent e.g \"foreground\" sets every\n   * background pixel as transparent.\n   */\n  segment?: \"foreground\";\n  /**\n   * When cropping with fit: \"cover\", this defines the side or point that should\n   * be left uncropped. The value is either a string\n   * \"left\", \"right\", \"top\", \"bottom\", \"auto\", or \"center\" (the default),\n   * or an object {x, y} containing focal point coordinates in the original\n   * image expressed as fractions ranging from 0.0 (top or left) to 1.0\n   * (bottom or right), 0.5 being the center. {fit: \"cover\", gravity: \"top\"} will\n   * crop bottom or left and right sides as necessary, but won’t crop anything\n   * from the top. {fit: \"cover\", gravity: {x:0.5, y:0.2}} will crop each side to\n   * preserve as much as possible around a point at 20% of the height of the\n   * source image.\n   */\n  gravity?:\n    | \"face\"\n    | \"left\"\n    | \"right\"\n    | \"top\"\n    | \"bottom\"\n    | \"center\"\n    | \"auto\"\n    | \"entropy\"\n    | BasicImageTransformationsGravityCoordinates;\n  /**\n   * Background color to add underneath the image. Applies only to images with\n   * transparency (such as PNG). Accepts any CSS color (#RRGGBB, rgba(…),\n   * hsl(…), etc.)\n   */\n  background?: string;\n  /**\n   * Number of degrees (90, 180, 270) to rotate the image by. width and height\n   * options refer to axes after rotation.\n   */\n  rotate?: 0 | 90 | 180 | 270 | 360;\n}\ninterface BasicImageTransformationsGravityCoordinates {\n  x?: number;\n  y?: number;\n  mode?: \"remainder\" | \"box-center\";\n}\n/**\n * In addition to the properties you can set in the RequestInit dict\n * that you pass as an argument to the Request constructor, you can\n * set certain properties of a `cf` object to control how Cloudflare\n * features are applied to that new Request.\n *\n * Note: Currently, these properties cannot be tested in the\n * playground.\n */\ninterface RequestInitCfProperties extends Record<string, unknown> {\n  cacheEverything?: boolean;\n  /**\n   * A request's cache key is what determines if two requests are\n   * \"the same\" for caching purposes. If a request has the same cache key\n   * as some previous request, then we can serve the same cached response for\n   * both. (e.g. 'some-key')\n   *\n   * Only available for Enterprise customers.\n   */\n  cacheKey?: string;\n  /**\n   * This allows you to append additional Cache-Tag response headers\n   * to the origin response without modifications to the origin server.\n   * This will allow for greater control over the Purge by Cache Tag feature\n   * utilizing changes only in the Workers process.\n   *\n   * Only available for Enterprise customers.\n   */\n  cacheTags?: string[];\n  /**\n   * Force response to be cached for a given number of seconds. (e.g. 300)\n   */\n  cacheTtl?: number;\n  /**\n   * Force response to be cached for a given number of seconds based on the Origin status code.\n   * (e.g. { '200-299': 86400, '404': 1, '500-599': 0 })\n   */\n  cacheTtlByStatus?: Record<string, number>;\n  scrapeShield?: boolean;\n  apps?: boolean;\n  image?: RequestInitCfPropertiesImage;\n  minify?: RequestInitCfPropertiesImageMinify;\n  mirage?: boolean;\n  polish?: \"lossy\" | \"lossless\" | \"off\";\n  r2?: RequestInitCfPropertiesR2;\n  /**\n   * Redirects the request to an alternate origin server. You can use this,\n   * for example, to implement load balancing across several origins.\n   * (e.g.us-east.example.com)\n   *\n   * Note - For security reasons, the hostname set in resolveOverride must\n   * be proxied on the same Cloudflare zone of the incoming request.\n   * Otherwise, the setting is ignored. CNAME hosts are allowed, so to\n   * resolve to a host under a different domain or a DNS only domain first\n   * declare a CNAME record within your own zone’s DNS mapping to the\n   * external hostname, set proxy on Cloudflare, then set resolveOverride\n   * to point to that CNAME record.\n   */\n  resolveOverride?: string;\n}\ninterface RequestInitCfPropertiesImageDraw extends BasicImageTransformations {\n  /**\n   * Absolute URL of the image file to use for the drawing. It can be any of\n   * the supported file formats. For drawing of watermarks or non-rectangular\n   * overlays we recommend using PNG or WebP images.\n   */\n  url: string;\n  /**\n   * Floating-point number between 0 (transparent) and 1 (opaque).\n   * For example, opacity: 0.5 makes overlay semitransparent.\n   */\n  opacity?: number;\n  /**\n   * - If set to true, the overlay image will be tiled to cover the entire\n   *   area. This is useful for stock-photo-like watermarks.\n   * - If set to \"x\", the overlay image will be tiled horizontally only\n   *   (form a line).\n   * - If set to \"y\", the overlay image will be tiled vertically only\n   *   (form a line).\n   */\n  repeat?: true | \"x\" | \"y\";\n  /**\n   * Position of the overlay image relative to a given edge. Each property is\n   * an offset in pixels. 0 aligns exactly to the edge. For example, left: 10\n   * positions left side of the overlay 10 pixels from the left edge of the\n   * image it's drawn over. bottom: 0 aligns bottom of the overlay with bottom\n   * of the background image.\n   *\n   * Setting both left & right, or both top & bottom is an error.\n   *\n   * If no position is specified, the image will be centered.\n   */\n  top?: number;\n  left?: number;\n  bottom?: number;\n  right?: number;\n}\ninterface RequestInitCfPropertiesImage extends BasicImageTransformations {\n  /**\n   * Device Pixel Ratio. Default 1. Multiplier for width/height that makes it\n   * easier to specify higher-DPI sizes in <img srcset>.\n   */\n  dpr?: number;\n  /**\n   * Allows you to trim your image. Takes dpr into account and is performed before\n   * resizing or rotation.\n   *\n   * It can be used as:\n   * - left, top, right, bottom - it will specify the number of pixels to cut\n   *   off each side\n   * - width, height - the width/height you'd like to end up with - can be used\n   *   in combination with the properties above\n   * - border - this will automatically trim the surroundings of an image based on\n   *   it's color. It consists of three properties:\n   *    - color: rgb or hex representation of the color you wish to trim (todo: verify the rgba bit)\n   *    - tolerance: difference from color to treat as color\n   *    - keep: the number of pixels of border to keep\n   */\n  trim?:\n    | \"border\"\n    | {\n        top?: number;\n        bottom?: number;\n        left?: number;\n        right?: number;\n        width?: number;\n        height?: number;\n        border?:\n          | boolean\n          | {\n              color?: string;\n              tolerance?: number;\n              keep?: number;\n            };\n      };\n  /**\n   * Quality setting from 1-100 (useful values are in 60-90 range). Lower values\n   * make images look worse, but load faster. The default is 85. It applies only\n   * to JPEG and WebP images. It doesn’t have any effect on PNG.\n   */\n  quality?: number | \"low\" | \"medium-low\" | \"medium-high\" | \"high\";\n  /**\n   * Output format to generate. It can be:\n   *  - avif: generate images in AVIF format.\n   *  - webp: generate images in Google WebP format. Set quality to 100 to get\n   *    the WebP-lossless format.\n   *  - json: instead of generating an image, outputs information about the\n   *    image, in JSON format. The JSON object will contain image size\n   *    (before and after resizing), source image’s MIME type, file size, etc.\n   * - jpeg: generate images in JPEG format.\n   * - png: generate images in PNG format.\n   */\n  format?:\n    | \"avif\"\n    | \"webp\"\n    | \"json\"\n    | \"jpeg\"\n    | \"png\"\n    | \"baseline-jpeg\"\n    | \"png-force\"\n    | \"svg\";\n  /**\n   * Whether to preserve animation frames from input files. Default is true.\n   * Setting it to false reduces animations to still images. This setting is\n   * recommended when enlarging images or processing arbitrary user content,\n   * because large GIF animations can weigh tens or even hundreds of megabytes.\n   * It is also useful to set anim:false when using format:\"json\" to get the\n   * response quicker without the number of frames.\n   */\n  anim?: boolean;\n  /**\n   * What EXIF data should be preserved in the output image. Note that EXIF\n   * rotation and embedded color profiles are always applied (\"baked in\" into\n   * the image), and aren't affected by this option. Note that if the Polish\n   * feature is enabled, all metadata may have been removed already and this\n   * option may have no effect.\n   *  - keep: Preserve most of EXIF metadata, including GPS location if there's\n   *    any.\n   *  - copyright: Only keep the copyright tag, and discard everything else.\n   *    This is the default behavior for JPEG files.\n   *  - none: Discard all invisible EXIF metadata. Currently WebP and PNG\n   *    output formats always discard metadata.\n   */\n  metadata?: \"keep\" | \"copyright\" | \"none\";\n  /**\n   * Strength of sharpening filter to apply to the image. Floating-point\n   * number between 0 (no sharpening, default) and 10 (maximum). 1.0 is a\n   * recommended value for downscaled images.\n   */\n  sharpen?: number;\n  /**\n   * Radius of a blur filter (approximate gaussian). Maximum supported radius\n   * is 250.\n   */\n  blur?: number;\n  /**\n   * Overlays are drawn in the order they appear in the array (last array\n   * entry is the topmost layer).\n   */\n  draw?: RequestInitCfPropertiesImageDraw[];\n  /**\n   * Fetching image from authenticated origin. Setting this property will\n   * pass authentication headers (Authorization, Cookie, etc.) through to\n   * the origin.\n   */\n  \"origin-auth\"?: \"share-publicly\";\n  /**\n   * Adds a border around the image. The border is added after resizing. Border\n   * width takes dpr into account, and can be specified either using a single\n   * width property, or individually for each side.\n   */\n  border?:\n    | {\n        color: string;\n        width: number;\n      }\n    | {\n        color: string;\n        top: number;\n        right: number;\n        bottom: number;\n        left: number;\n      };\n  /**\n   * Increase brightness by a factor. A value of 1.0 equals no change, a value\n   * of 0.5 equals half brightness, and a value of 2.0 equals twice as bright.\n   * 0 is ignored.\n   */\n  brightness?: number;\n  /**\n   * Increase contrast by a factor. A value of 1.0 equals no change, a value of\n   * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is\n   * ignored.\n   */\n  contrast?: number;\n  /**\n   * Increase exposure by a factor. A value of 1.0 equals no change, a value of\n   * 0.5 darkens the image, and a value of 2.0 lightens the image. 0 is ignored.\n   */\n  gamma?: number;\n  /**\n   * Increase contrast by a factor. A value of 1.0 equals no change, a value of\n   * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is\n   * ignored.\n   */\n  saturation?: number;\n  /**\n   * Flips the images horizontally, vertically, or both. Flipping is applied before\n   * rotation, so if you apply flip=h,rotate=90 then the image will be flipped\n   * horizontally, then rotated by 90 degrees.\n   */\n  flip?: \"h\" | \"v\" | \"hv\";\n  /**\n   * Slightly reduces latency on a cache miss by selecting a\n   * quickest-to-compress file format, at a cost of increased file size and\n   * lower image quality. It will usually override the format option and choose\n   * JPEG over WebP or AVIF. We do not recommend using this option, except in\n   * unusual circumstances like resizing uncacheable dynamically-generated\n   * images.\n   */\n  compression?: \"fast\";\n}\ninterface RequestInitCfPropertiesImageMinify {\n  javascript?: boolean;\n  css?: boolean;\n  html?: boolean;\n}\ninterface RequestInitCfPropertiesR2 {\n  /**\n   * Colo id of bucket that an object is stored in\n   */\n  bucketColoId?: number;\n}\n/**\n * Request metadata provided by Cloudflare's edge.\n */\ntype IncomingRequestCfProperties<HostMetadata = unknown> =\n  IncomingRequestCfPropertiesBase &\n    IncomingRequestCfPropertiesBotManagementEnterprise &\n    IncomingRequestCfPropertiesCloudflareForSaaSEnterprise<HostMetadata> &\n    IncomingRequestCfPropertiesGeographicInformation &\n    IncomingRequestCfPropertiesCloudflareAccessOrApiShield;\ninterface IncomingRequestCfPropertiesBase extends Record<string, unknown> {\n  /**\n   * [ASN](https://www.iana.org/assignments/as-numbers/as-numbers.xhtml) of the incoming request.\n   *\n   * @example 395747\n   */\n  asn?: number;\n  /**\n   * The organization which owns the ASN of the incoming request.\n   *\n   * @example \"Google Cloud\"\n   */\n  asOrganization?: string;\n  /**\n   * The original value of the `Accept-Encoding` header if Cloudflare modified it.\n   *\n   * @example \"gzip, deflate, br\"\n   */\n  clientAcceptEncoding?: string;\n  /**\n   * The number of milliseconds it took for the request to reach your worker.\n   *\n   * @example 22\n   */\n  clientTcpRtt?: number;\n  /**\n   * The three-letter [IATA](https://en.wikipedia.org/wiki/IATA_airport_code)\n   * airport code of the data center that the request hit.\n   *\n   * @example \"DFW\"\n   */\n  colo: string;\n  /**\n   * Represents the upstream's response to a\n   * [TCP `keepalive` message](https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html)\n   * from cloudflare.\n   *\n   * For workers with no upstream, this will always be `1`.\n   *\n   * @example 3\n   */\n  edgeRequestKeepAliveStatus: IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus;\n  /**\n   * The HTTP Protocol the request used.\n   *\n   * @example \"HTTP/2\"\n   */\n  httpProtocol: string;\n  /**\n   * The browser-requested prioritization information in the request object.\n   *\n   * If no information was set, defaults to the empty string `\"\"`\n   *\n   * @example \"weight=192;exclusive=0;group=3;group-weight=127\"\n   * @default \"\"\n   */\n  requestPriority: string;\n  /**\n   * The TLS version of the connection to Cloudflare.\n   * In requests served over plaintext (without TLS), this property is the empty string `\"\"`.\n   *\n   * @example \"TLSv1.3\"\n   */\n  tlsVersion: string;\n  /**\n   * The cipher for the connection to Cloudflare.\n   * In requests served over plaintext (without TLS), this property is the empty string `\"\"`.\n   *\n   * @example \"AEAD-AES128-GCM-SHA256\"\n   */\n  tlsCipher: string;\n  /**\n   * Metadata containing the [`HELLO`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2) and [`FINISHED`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9) messages from this request's TLS handshake.\n   *\n   * If the incoming request was served over plaintext (without TLS) this field is undefined.\n   */\n  tlsExportedAuthenticator?: IncomingRequestCfPropertiesExportedAuthenticatorMetadata;\n}\ninterface IncomingRequestCfPropertiesBotManagementBase {\n  /**\n   * Cloudflare’s [level of certainty](https://developers.cloudflare.com/bots/concepts/bot-score/) that a request comes from a bot,\n   * represented as an integer percentage between `1` (almost certainly a bot) and `99` (almost certainly human).\n   *\n   * @example 54\n   */\n  score: number;\n  /**\n   * A boolean value that is true if the request comes from a good bot, like Google or Bing.\n   * Most customers choose to allow this traffic. For more details, see [Traffic from known bots](https://developers.cloudflare.com/firewall/known-issues-and-faq/#how-does-firewall-rules-handle-traffic-from-known-bots).\n   */\n  verifiedBot: boolean;\n  /**\n   * A boolean value that is true if the request originates from a\n   * Cloudflare-verified proxy service.\n   */\n  corporateProxy: boolean;\n  /**\n   * A boolean value that's true if the request matches [file extensions](https://developers.cloudflare.com/bots/reference/static-resources/) for many types of static resources.\n   */\n  staticResource: boolean;\n  /**\n   * List of IDs that correlate to the Bot Management heuristic detections made on a request (you can have multiple heuristic detections on the same request).\n   */\n  detectionIds: number[];\n}\ninterface IncomingRequestCfPropertiesBotManagement {\n  /**\n   * Results of Cloudflare's Bot Management analysis\n   */\n  botManagement: IncomingRequestCfPropertiesBotManagementBase;\n  /**\n   * Duplicate of `botManagement.score`.\n   *\n   * @deprecated\n   */\n  clientTrustScore: number;\n}\ninterface IncomingRequestCfPropertiesBotManagementEnterprise extends IncomingRequestCfPropertiesBotManagement {\n  /**\n   * Results of Cloudflare's Bot Management analysis\n   */\n  botManagement: IncomingRequestCfPropertiesBotManagementBase & {\n    /**\n     * A [JA3 Fingerprint](https://developers.cloudflare.com/bots/concepts/ja3-fingerprint/) to help profile specific SSL/TLS clients\n     * across different destination IPs, Ports, and X509 certificates.\n     */\n    ja3Hash: string;\n  };\n}\ninterface IncomingRequestCfPropertiesCloudflareForSaaSEnterprise<HostMetadata> {\n  /**\n   * Custom metadata set per-host in [Cloudflare for SaaS](https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/).\n   *\n   * This field is only present if you have Cloudflare for SaaS enabled on your account\n   * and you have followed the [required steps to enable it]((https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/domain-support/custom-metadata/)).\n   */\n  hostMetadata?: HostMetadata;\n}\ninterface IncomingRequestCfPropertiesCloudflareAccessOrApiShield {\n  /**\n   * Information about the client certificate presented to Cloudflare.\n   *\n   * This is populated when the incoming request is served over TLS using\n   * either Cloudflare Access or API Shield (mTLS)\n   * and the presented SSL certificate has a valid\n   * [Certificate Serial Number](https://ldapwiki.com/wiki/Certificate%20Serial%20Number)\n   * (i.e., not `null` or `\"\"`).\n   *\n   * Otherwise, a set of placeholder values are used.\n   *\n   * The property `certPresented` will be set to `\"1\"` when\n   * the object is populated (i.e. the above conditions were met).\n   */\n  tlsClientAuth:\n    | IncomingRequestCfPropertiesTLSClientAuth\n    | IncomingRequestCfPropertiesTLSClientAuthPlaceholder;\n}\n/**\n * Metadata about the request's TLS handshake\n */\ninterface IncomingRequestCfPropertiesExportedAuthenticatorMetadata {\n  /**\n   * The client's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal\n   *\n   * @example \"44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d\"\n   */\n  clientHandshake: string;\n  /**\n   * The server's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal\n   *\n   * @example \"44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d\"\n   */\n  serverHandshake: string;\n  /**\n   * The client's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal\n   *\n   * @example \"084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b\"\n   */\n  clientFinished: string;\n  /**\n   * The server's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal\n   *\n   * @example \"084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b\"\n   */\n  serverFinished: string;\n}\n/**\n * Geographic data about the request's origin.\n */\ninterface IncomingRequestCfPropertiesGeographicInformation {\n  /**\n   * The [ISO 3166-1 Alpha 2](https://www.iso.org/iso-3166-country-codes.html) country code the request originated from.\n   *\n   * If your worker is [configured to accept TOR connections](https://support.cloudflare.com/hc/en-us/articles/203306930-Understanding-Cloudflare-Tor-support-and-Onion-Routing), this may also be `\"T1\"`, indicating a request that originated over TOR.\n   *\n   * If Cloudflare is unable to determine where the request originated this property is omitted.\n   *\n   * The country code `\"T1\"` is used for requests originating on TOR.\n   *\n   * @example \"GB\"\n   */\n  country?: Iso3166Alpha2Code | \"T1\";\n  /**\n   * If present, this property indicates that the request originated in the EU\n   *\n   * @example \"1\"\n   */\n  isEUCountry?: \"1\";\n  /**\n   * A two-letter code indicating the continent the request originated from.\n   *\n   * @example \"AN\"\n   */\n  continent?: ContinentCode;\n  /**\n   * The city the request originated from\n   *\n   * @example \"Austin\"\n   */\n  city?: string;\n  /**\n   * Postal code of the incoming request\n   *\n   * @example \"78701\"\n   */\n  postalCode?: string;\n  /**\n   * Latitude of the incoming request\n   *\n   * @example \"30.27130\"\n   */\n  latitude?: string;\n  /**\n   * Longitude of the incoming request\n   *\n   * @example \"-97.74260\"\n   */\n  longitude?: string;\n  /**\n   * Timezone of the incoming request\n   *\n   * @example \"America/Chicago\"\n   */\n  timezone?: string;\n  /**\n   * If known, the ISO 3166-2 name for the first level region associated with\n   * the IP address of the incoming request\n   *\n   * @example \"Texas\"\n   */\n  region?: string;\n  /**\n   * If known, the ISO 3166-2 code for the first-level region associated with\n   * the IP address of the incoming request\n   *\n   * @example \"TX\"\n   */\n  regionCode?: string;\n  /**\n   * Metro code (DMA) of the incoming request\n   *\n   * @example \"635\"\n   */\n  metroCode?: string;\n}\n/** Data about the incoming request's TLS certificate */\ninterface IncomingRequestCfPropertiesTLSClientAuth {\n  /** Always `\"1\"`, indicating that the certificate was presented */\n  certPresented: \"1\";\n  /**\n   * Result of certificate verification.\n   *\n   * @example \"FAILED:self signed certificate\"\n   */\n  certVerified: Exclude<CertVerificationStatus, \"NONE\">;\n  /** The presented certificate's revokation status.\n   *\n   * - A value of `\"1\"` indicates the certificate has been revoked\n   * - A value of `\"0\"` indicates the certificate has not been revoked\n   */\n  certRevoked: \"1\" | \"0\";\n  /**\n   * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html)\n   *\n   * @example \"CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certIssuerDN: string;\n  /**\n   * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html)\n   *\n   * @example \"CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certSubjectDN: string;\n  /**\n   * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted)\n   *\n   * @example \"CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certIssuerDNRFC2253: string;\n  /**\n   * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted)\n   *\n   * @example \"CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certSubjectDNRFC2253: string;\n  /** The certificate issuer's distinguished name (legacy policies) */\n  certIssuerDNLegacy: string;\n  /** The certificate subject's distinguished name (legacy policies) */\n  certSubjectDNLegacy: string;\n  /**\n   * The certificate's serial number\n   *\n   * @example \"00936EACBE07F201DF\"\n   */\n  certSerial: string;\n  /**\n   * The certificate issuer's serial number\n   *\n   * @example \"2489002934BDFEA34\"\n   */\n  certIssuerSerial: string;\n  /**\n   * The certificate's Subject Key Identifier\n   *\n   * @example \"BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4\"\n   */\n  certSKI: string;\n  /**\n   * The certificate issuer's Subject Key Identifier\n   *\n   * @example \"BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4\"\n   */\n  certIssuerSKI: string;\n  /**\n   * The certificate's SHA-1 fingerprint\n   *\n   * @example \"6b9109f323999e52259cda7373ff0b4d26bd232e\"\n   */\n  certFingerprintSHA1: string;\n  /**\n   * The certificate's SHA-256 fingerprint\n   *\n   * @example \"acf77cf37b4156a2708e34c4eb755f9b5dbbe5ebb55adfec8f11493438d19e6ad3f157f81fa3b98278453d5652b0c1fd1d71e5695ae4d709803a4d3f39de9dea\"\n   */\n  certFingerprintSHA256: string;\n  /**\n   * The effective starting date of the certificate\n   *\n   * @example \"Dec 22 19:39:00 2018 GMT\"\n   */\n  certNotBefore: string;\n  /**\n   * The effective expiration date of the certificate\n   *\n   * @example \"Dec 22 19:39:00 2018 GMT\"\n   */\n  certNotAfter: string;\n}\n/** Placeholder values for TLS Client Authorization */\ninterface IncomingRequestCfPropertiesTLSClientAuthPlaceholder {\n  certPresented: \"0\";\n  certVerified: \"NONE\";\n  certRevoked: \"0\";\n  certIssuerDN: \"\";\n  certSubjectDN: \"\";\n  certIssuerDNRFC2253: \"\";\n  certSubjectDNRFC2253: \"\";\n  certIssuerDNLegacy: \"\";\n  certSubjectDNLegacy: \"\";\n  certSerial: \"\";\n  certIssuerSerial: \"\";\n  certSKI: \"\";\n  certIssuerSKI: \"\";\n  certFingerprintSHA1: \"\";\n  certFingerprintSHA256: \"\";\n  certNotBefore: \"\";\n  certNotAfter: \"\";\n}\n/** Possible outcomes of TLS verification */\ndeclare type CertVerificationStatus =\n  /** Authentication succeeded */\n  | \"SUCCESS\"\n  /** No certificate was presented */\n  | \"NONE\"\n  /** Failed because the certificate was self-signed */\n  | \"FAILED:self signed certificate\"\n  /** Failed because the certificate failed a trust chain check */\n  | \"FAILED:unable to verify the first certificate\"\n  /** Failed because the certificate not yet valid */\n  | \"FAILED:certificate is not yet valid\"\n  /** Failed because the certificate is expired */\n  | \"FAILED:certificate has expired\"\n  /** Failed for another unspecified reason */\n  | \"FAILED\";\n/**\n * An upstream endpoint's response to a TCP `keepalive` message from Cloudflare.\n */\ndeclare type IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus =\n  | 0 /** Unknown */\n  | 1 /** no keepalives (not found) */\n  | 2 /** no connection re-use, opening keepalive connection failed */\n  | 3 /** no connection re-use, keepalive accepted and saved */\n  | 4 /** connection re-use, refused by the origin server (`TCP FIN`) */\n  | 5; /** connection re-use, accepted by the origin server */\n/** ISO 3166-1 Alpha-2 codes */\ndeclare type Iso3166Alpha2Code =\n  | \"AD\"\n  | \"AE\"\n  | \"AF\"\n  | \"AG\"\n  | \"AI\"\n  | \"AL\"\n  | \"AM\"\n  | \"AO\"\n  | \"AQ\"\n  | \"AR\"\n  | \"AS\"\n  | \"AT\"\n  | \"AU\"\n  | \"AW\"\n  | \"AX\"\n  | \"AZ\"\n  | \"BA\"\n  | \"BB\"\n  | \"BD\"\n  | \"BE\"\n  | \"BF\"\n  | \"BG\"\n  | \"BH\"\n  | \"BI\"\n  | \"BJ\"\n  | \"BL\"\n  | \"BM\"\n  | \"BN\"\n  | \"BO\"\n  | \"BQ\"\n  | \"BR\"\n  | \"BS\"\n  | \"BT\"\n  | \"BV\"\n  | \"BW\"\n  | \"BY\"\n  | \"BZ\"\n  | \"CA\"\n  | \"CC\"\n  | \"CD\"\n  | \"CF\"\n  | \"CG\"\n  | \"CH\"\n  | \"CI\"\n  | \"CK\"\n  | \"CL\"\n  | \"CM\"\n  | \"CN\"\n  | \"CO\"\n  | \"CR\"\n  | \"CU\"\n  | \"CV\"\n  | \"CW\"\n  | \"CX\"\n  | \"CY\"\n  | \"CZ\"\n  | \"DE\"\n  | \"DJ\"\n  | \"DK\"\n  | \"DM\"\n  | \"DO\"\n  | \"DZ\"\n  | \"EC\"\n  | \"EE\"\n  | \"EG\"\n  | \"EH\"\n  | \"ER\"\n  | \"ES\"\n  | \"ET\"\n  | \"FI\"\n  | \"FJ\"\n  | \"FK\"\n  | \"FM\"\n  | \"FO\"\n  | \"FR\"\n  | \"GA\"\n  | \"GB\"\n  | \"GD\"\n  | \"GE\"\n  | \"GF\"\n  | \"GG\"\n  | \"GH\"\n  | \"GI\"\n  | \"GL\"\n  | \"GM\"\n  | \"GN\"\n  | \"GP\"\n  | \"GQ\"\n  | \"GR\"\n  | \"GS\"\n  | \"GT\"\n  | \"GU\"\n  | \"GW\"\n  | \"GY\"\n  | \"HK\"\n  | \"HM\"\n  | \"HN\"\n  | \"HR\"\n  | \"HT\"\n  | \"HU\"\n  | \"ID\"\n  | \"IE\"\n  | \"IL\"\n  | \"IM\"\n  | \"IN\"\n  | \"IO\"\n  | \"IQ\"\n  | \"IR\"\n  | \"IS\"\n  | \"IT\"\n  | \"JE\"\n  | \"JM\"\n  | \"JO\"\n  | \"JP\"\n  | \"KE\"\n  | \"KG\"\n  | \"KH\"\n  | \"KI\"\n  | \"KM\"\n  | \"KN\"\n  | \"KP\"\n  | \"KR\"\n  | \"KW\"\n  | \"KY\"\n  | \"KZ\"\n  | \"LA\"\n  | \"LB\"\n  | \"LC\"\n  | \"LI\"\n  | \"LK\"\n  | \"LR\"\n  | \"LS\"\n  | \"LT\"\n  | \"LU\"\n  | \"LV\"\n  | \"LY\"\n  | \"MA\"\n  | \"MC\"\n  | \"MD\"\n  | \"ME\"\n  | \"MF\"\n  | \"MG\"\n  | \"MH\"\n  | \"MK\"\n  | \"ML\"\n  | \"MM\"\n  | \"MN\"\n  | \"MO\"\n  | \"MP\"\n  | \"MQ\"\n  | \"MR\"\n  | \"MS\"\n  | \"MT\"\n  | \"MU\"\n  | \"MV\"\n  | \"MW\"\n  | \"MX\"\n  | \"MY\"\n  | \"MZ\"\n  | \"NA\"\n  | \"NC\"\n  | \"NE\"\n  | \"NF\"\n  | \"NG\"\n  | \"NI\"\n  | \"NL\"\n  | \"NO\"\n  | \"NP\"\n  | \"NR\"\n  | \"NU\"\n  | \"NZ\"\n  | \"OM\"\n  | \"PA\"\n  | \"PE\"\n  | \"PF\"\n  | \"PG\"\n  | \"PH\"\n  | \"PK\"\n  | \"PL\"\n  | \"PM\"\n  | \"PN\"\n  | \"PR\"\n  | \"PS\"\n  | \"PT\"\n  | \"PW\"\n  | \"PY\"\n  | \"QA\"\n  | \"RE\"\n  | \"RO\"\n  | \"RS\"\n  | \"RU\"\n  | \"RW\"\n  | \"SA\"\n  | \"SB\"\n  | \"SC\"\n  | \"SD\"\n  | \"SE\"\n  | \"SG\"\n  | \"SH\"\n  | \"SI\"\n  | \"SJ\"\n  | \"SK\"\n  | \"SL\"\n  | \"SM\"\n  | \"SN\"\n  | \"SO\"\n  | \"SR\"\n  | \"SS\"\n  | \"ST\"\n  | \"SV\"\n  | \"SX\"\n  | \"SY\"\n  | \"SZ\"\n  | \"TC\"\n  | \"TD\"\n  | \"TF\"\n  | \"TG\"\n  | \"TH\"\n  | \"TJ\"\n  | \"TK\"\n  | \"TL\"\n  | \"TM\"\n  | \"TN\"\n  | \"TO\"\n  | \"TR\"\n  | \"TT\"\n  | \"TV\"\n  | \"TW\"\n  | \"TZ\"\n  | \"UA\"\n  | \"UG\"\n  | \"UM\"\n  | \"US\"\n  | \"UY\"\n  | \"UZ\"\n  | \"VA\"\n  | \"VC\"\n  | \"VE\"\n  | \"VG\"\n  | \"VI\"\n  | \"VN\"\n  | \"VU\"\n  | \"WF\"\n  | \"WS\"\n  | \"YE\"\n  | \"YT\"\n  | \"ZA\"\n  | \"ZM\"\n  | \"ZW\";\n/** The 2-letter continent codes Cloudflare uses */\ndeclare type ContinentCode = \"AF\" | \"AN\" | \"AS\" | \"EU\" | \"NA\" | \"OC\" | \"SA\";\ntype CfProperties<HostMetadata = unknown> =\n  | IncomingRequestCfProperties<HostMetadata>\n  | RequestInitCfProperties;\ninterface D1Meta {\n  duration: number;\n  size_after: number;\n  rows_read: number;\n  rows_written: number;\n  last_row_id: number;\n  changed_db: boolean;\n  changes: number;\n  /**\n   * The region of the database instance that executed the query.\n   */\n  served_by_region?: string;\n  /**\n   * The three letters airport code of the colo that executed the query.\n   */\n  served_by_colo?: string;\n  /**\n   * True if-and-only-if the database instance that executed the query was the primary.\n   */\n  served_by_primary?: boolean;\n  timings?: {\n    /**\n     * The duration of the SQL query execution by the database instance. It doesn't include any network time.\n     */\n    sql_duration_ms: number;\n  };\n  /**\n   * Number of total attempts to execute the query, due to automatic retries.\n   * Note: All other fields in the response like `timings` only apply to the last attempt.\n   */\n  total_attempts?: number;\n}\ninterface D1Response {\n  success: true;\n  meta: D1Meta & Record<string, unknown>;\n  error?: never;\n}\ntype D1Result<T = unknown> = D1Response & {\n  results: T[];\n};\ninterface D1ExecResult {\n  count: number;\n  duration: number;\n}\ntype D1SessionConstraint =\n  // Indicates that the first query should go to the primary, and the rest queries\n  // using the same D1DatabaseSession will go to any replica that is consistent with\n  // the bookmark maintained by the session (returned by the first query).\n  | \"first-primary\"\n  // Indicates that the first query can go anywhere (primary or replica), and the rest queries\n  // using the same D1DatabaseSession will go to any replica that is consistent with\n  // the bookmark maintained by the session (returned by the first query).\n  | \"first-unconstrained\";\ntype D1SessionBookmark = string;\ndeclare abstract class D1Database {\n  prepare(query: string): D1PreparedStatement;\n  batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;\n  exec(query: string): Promise<D1ExecResult>;\n  /**\n   * Creates a new D1 Session anchored at the given constraint or the bookmark.\n   * All queries executed using the created session will have sequential consistency,\n   * meaning that all writes done through the session will be visible in subsequent reads.\n   *\n   * @param constraintOrBookmark Either the session constraint or the explicit bookmark to anchor the created session.\n   */\n  withSession(\n    constraintOrBookmark?: D1SessionBookmark | D1SessionConstraint,\n  ): D1DatabaseSession;\n  /**\n   * @deprecated dump() will be removed soon, only applies to deprecated alpha v1 databases.\n   */\n  dump(): Promise<ArrayBuffer>;\n}\ndeclare abstract class D1DatabaseSession {\n  prepare(query: string): D1PreparedStatement;\n  batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;\n  /**\n   * @returns The latest session bookmark across all executed queries on the session.\n   *          If no query has been executed yet, `null` is returned.\n   */\n  getBookmark(): D1SessionBookmark | null;\n}\ndeclare abstract class D1PreparedStatement {\n  bind(...values: unknown[]): D1PreparedStatement;\n  first<T = unknown>(colName: string): Promise<T | null>;\n  first<T = Record<string, unknown>>(): Promise<T | null>;\n  run<T = Record<string, unknown>>(): Promise<D1Result<T>>;\n  all<T = Record<string, unknown>>(): Promise<D1Result<T>>;\n  raw<T = unknown[]>(options: {\n    columnNames: true;\n  }): Promise<[string[], ...T[]]>;\n  raw<T = unknown[]>(options?: { columnNames?: false }): Promise<T[]>;\n}\n// `Disposable` was added to TypeScript's standard lib types in version 5.2.\n// To support older TypeScript versions, define an empty `Disposable` interface.\n// Users won't be able to use `using`/`Symbol.dispose` without upgrading to 5.2,\n// but this will ensure type checking on older versions still passes.\n// TypeScript's interface merging will ensure our empty interface is effectively\n// ignored when `Disposable` is included in the standard lib.\ninterface Disposable {}\n/**\n * The returned data after sending an email\n */\ninterface EmailSendResult {\n  /**\n   * The Email Message ID\n   */\n  messageId: string;\n}\n/**\n * An email message that can be sent from a Worker.\n */\ninterface EmailMessage {\n  /**\n   * Envelope From attribute of the email message.\n   */\n  readonly from: string;\n  /**\n   * Envelope To attribute of the email message.\n   */\n  readonly to: string;\n}\n/**\n * An email message that is sent to a consumer Worker and can be rejected/forwarded.\n */\ninterface ForwardableEmailMessage extends EmailMessage {\n  /**\n   * Stream of the email message content.\n   */\n  readonly raw: ReadableStream<Uint8Array>;\n  /**\n   * An [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers).\n   */\n  readonly headers: Headers;\n  /**\n   * Size of the email message content.\n   */\n  readonly rawSize: number;\n  /**\n   * Reject this email message by returning a permanent SMTP error back to the connecting client including the given reason.\n   * @param reason The reject reason.\n   * @returns void\n   */\n  setReject(reason: string): void;\n  /**\n   * Forward this email message to a verified destination address of the account.\n   * @param rcptTo Verified destination address.\n   * @param headers A [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers).\n   * @returns A promise that resolves when the email message is forwarded.\n   */\n  forward(rcptTo: string, headers?: Headers): Promise<EmailSendResult>;\n  /**\n   * Reply to the sender of this email message with a new EmailMessage object.\n   * @param message The reply message.\n   * @returns A promise that resolves when the email message is replied.\n   */\n  reply(message: EmailMessage): Promise<EmailSendResult>;\n}\n/** A file attachment for an email message */\ntype EmailAttachment =\n  | {\n      disposition: \"inline\";\n      contentId: string;\n      filename: string;\n      type: string;\n      content: string | ArrayBuffer | ArrayBufferView;\n    }\n  | {\n      disposition: \"attachment\";\n      contentId?: undefined;\n      filename: string;\n      type: string;\n      content: string | ArrayBuffer | ArrayBufferView;\n    };\n/** An Email Address */\ninterface EmailAddress {\n  name: string;\n  email: string;\n}\n/**\n * A binding that allows a Worker to send email messages.\n */\ninterface SendEmail {\n  send(message: EmailMessage): Promise<EmailSendResult>;\n  send(builder: {\n    from: string | EmailAddress;\n    to: string | string[];\n    subject: string;\n    replyTo?: string | EmailAddress;\n    cc?: string | string[];\n    bcc?: string | string[];\n    headers?: Record<string, string>;\n    text?: string;\n    html?: string;\n    attachments?: EmailAttachment[];\n  }): Promise<EmailSendResult>;\n}\ndeclare abstract class EmailEvent extends ExtendableEvent {\n  readonly message: ForwardableEmailMessage;\n}\ndeclare type EmailExportedHandler<Env = unknown, Props = unknown> = (\n  message: ForwardableEmailMessage,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\ndeclare module \"cloudflare:email\" {\n  let _EmailMessage: {\n    prototype: EmailMessage;\n    new (from: string, to: string, raw: ReadableStream | string): EmailMessage;\n  };\n  export { _EmailMessage as EmailMessage };\n}\n/**\n * Hello World binding to serve as an explanatory example. DO NOT USE\n */\ninterface HelloWorldBinding {\n  /**\n   * Retrieve the current stored value\n   */\n  get(): Promise<{\n    value: string;\n    ms?: number;\n  }>;\n  /**\n   * Set a new stored value\n   */\n  set(value: string): Promise<void>;\n}\ninterface Hyperdrive {\n  /**\n   * Connect directly to Hyperdrive as if it's your database, returning a TCP socket.\n   *\n   * Calling this method returns an identical socket to if you call\n   * `connect(\"host:port\")` using the `host` and `port` fields from this object.\n   * Pick whichever approach works better with your preferred DB client library.\n   *\n   * Note that this socket is not yet authenticated -- it's expected that your\n   * code (or preferably, the client library of your choice) will authenticate\n   * using the information in this class's readonly fields.\n   */\n  connect(): Socket;\n  /**\n   * A valid DB connection string that can be passed straight into the typical\n   * client library/driver/ORM. This will typically be the easiest way to use\n   * Hyperdrive.\n   */\n  readonly connectionString: string;\n  /*\n   * A randomly generated hostname that is only valid within the context of the\n   * currently running Worker which, when passed into `connect()` function from\n   * the \"cloudflare:sockets\" module, will connect to the Hyperdrive instance\n   * for your database.\n   */\n  readonly host: string;\n  /*\n   * The port that must be paired the the host field when connecting.\n   */\n  readonly port: number;\n  /*\n   * The username to use when authenticating to your database via Hyperdrive.\n   * Unlike the host and password, this will be the same every time\n   */\n  readonly user: string;\n  /*\n   * The randomly generated password to use when authenticating to your\n   * database via Hyperdrive. Like the host field, this password is only valid\n   * within the context of the currently running Worker instance from which\n   * it's read.\n   */\n  readonly password: string;\n  /*\n   * The name of the database to connect to.\n   */\n  readonly database: string;\n}\n// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\ntype ImageInfoResponse =\n  | {\n      format: \"image/svg+xml\";\n    }\n  | {\n      format: string;\n      fileSize: number;\n      width: number;\n      height: number;\n    };\ntype ImageTransform = {\n  width?: number;\n  height?: number;\n  background?: string;\n  blur?: number;\n  border?:\n    | {\n        color?: string;\n        width?: number;\n      }\n    | {\n        top?: number;\n        bottom?: number;\n        left?: number;\n        right?: number;\n      };\n  brightness?: number;\n  contrast?: number;\n  fit?: \"scale-down\" | \"contain\" | \"pad\" | \"squeeze\" | \"cover\" | \"crop\";\n  flip?: \"h\" | \"v\" | \"hv\";\n  gamma?: number;\n  segment?: \"foreground\";\n  gravity?:\n    | \"face\"\n    | \"left\"\n    | \"right\"\n    | \"top\"\n    | \"bottom\"\n    | \"center\"\n    | \"auto\"\n    | \"entropy\"\n    | {\n        x?: number;\n        y?: number;\n        mode: \"remainder\" | \"box-center\";\n      };\n  rotate?: 0 | 90 | 180 | 270;\n  saturation?: number;\n  sharpen?: number;\n  trim?:\n    | \"border\"\n    | {\n        top?: number;\n        bottom?: number;\n        left?: number;\n        right?: number;\n        width?: number;\n        height?: number;\n        border?:\n          | boolean\n          | {\n              color?: string;\n              tolerance?: number;\n              keep?: number;\n            };\n      };\n};\ntype ImageDrawOptions = {\n  opacity?: number;\n  repeat?: boolean | string;\n  top?: number;\n  left?: number;\n  bottom?: number;\n  right?: number;\n};\ntype ImageInputOptions = {\n  encoding?: \"base64\";\n};\ntype ImageOutputOptions = {\n  format:\n    | \"image/jpeg\"\n    | \"image/png\"\n    | \"image/gif\"\n    | \"image/webp\"\n    | \"image/avif\"\n    | \"rgb\"\n    | \"rgba\";\n  quality?: number;\n  background?: string;\n  anim?: boolean;\n};\ninterface ImageMetadata {\n  id: string;\n  filename?: string;\n  uploaded?: string;\n  requireSignedURLs: boolean;\n  meta?: Record<string, unknown>;\n  variants: string[];\n  draft?: boolean;\n  creator?: string;\n}\ninterface ImageUploadOptions {\n  id?: string;\n  filename?: string;\n  requireSignedURLs?: boolean;\n  metadata?: Record<string, unknown>;\n  creator?: string;\n  encoding?: \"base64\";\n}\ninterface ImageUpdateOptions {\n  requireSignedURLs?: boolean;\n  metadata?: Record<string, unknown>;\n  creator?: string;\n}\ninterface ImageListOptions {\n  limit?: number;\n  cursor?: string;\n  sortOrder?: \"asc\" | \"desc\";\n  creator?: string;\n}\ninterface ImageList {\n  images: ImageMetadata[];\n  cursor?: string;\n  listComplete: boolean;\n}\ninterface HostedImagesBinding {\n  /**\n   * Get detailed metadata for a hosted image\n   * @param imageId The ID of the image (UUID or custom ID)\n   * @returns Image metadata, or null if not found\n   */\n  details(imageId: string): Promise<ImageMetadata | null>;\n  /**\n   * Get the raw image data for a hosted image\n   * @param imageId The ID of the image (UUID or custom ID)\n   * @returns ReadableStream of image bytes, or null if not found\n   */\n  image(imageId: string): Promise<ReadableStream<Uint8Array> | null>;\n  /**\n   * Upload a new hosted image\n   * @param image The image file to upload\n   * @param options Upload configuration\n   * @returns Metadata for the uploaded image\n   * @throws {@link ImagesError} if upload fails\n   */\n  upload(\n    image: ReadableStream<Uint8Array> | ArrayBuffer,\n    options?: ImageUploadOptions,\n  ): Promise<ImageMetadata>;\n  /**\n   * Update hosted image metadata\n   * @param imageId The ID of the image\n   * @param options Properties to update\n   * @returns Updated image metadata\n   * @throws {@link ImagesError} if update fails\n   */\n  update(imageId: string, options: ImageUpdateOptions): Promise<ImageMetadata>;\n  /**\n   * Delete a hosted image\n   * @param imageId The ID of the image\n   * @returns True if deleted, false if not found\n   */\n  delete(imageId: string): Promise<boolean>;\n  /**\n   * List hosted images with pagination\n   * @param options List configuration\n   * @returns List of images with pagination info\n   * @throws {@link ImagesError} if list fails\n   */\n  list(options?: ImageListOptions): Promise<ImageList>;\n}\ninterface ImagesBinding {\n  /**\n   * Get image metadata (type, width and height)\n   * @throws {@link ImagesError} with code 9412 if input is not an image\n   * @param stream The image bytes\n   */\n  info(\n    stream: ReadableStream<Uint8Array>,\n    options?: ImageInputOptions,\n  ): Promise<ImageInfoResponse>;\n  /**\n   * Begin applying a series of transformations to an image\n   * @param stream The image bytes\n   * @returns A transform handle\n   */\n  input(\n    stream: ReadableStream<Uint8Array>,\n    options?: ImageInputOptions,\n  ): ImageTransformer;\n  /**\n   * Access hosted images CRUD operations\n   */\n  readonly hosted: HostedImagesBinding;\n}\ninterface ImageTransformer {\n  /**\n   * Apply transform next, returning a transform handle.\n   * You can then apply more transformations, draw, or retrieve the output.\n   * @param transform\n   */\n  transform(transform: ImageTransform): ImageTransformer;\n  /**\n   * Draw an image on this transformer, returning a transform handle.\n   * You can then apply more transformations, draw, or retrieve the output.\n   * @param image The image (or transformer that will give the image) to draw\n   * @param options The options configuring how to draw the image\n   */\n  draw(\n    image: ReadableStream<Uint8Array> | ImageTransformer,\n    options?: ImageDrawOptions,\n  ): ImageTransformer;\n  /**\n   * Retrieve the image that results from applying the transforms to the\n   * provided input\n   * @param options Options that apply to the output e.g. output format\n   */\n  output(options: ImageOutputOptions): Promise<ImageTransformationResult>;\n}\ntype ImageTransformationOutputOptions = {\n  encoding?: \"base64\";\n};\ninterface ImageTransformationResult {\n  /**\n   * The image as a response, ready to store in cache or return to users\n   */\n  response(): Response;\n  /**\n   * The content type of the returned image\n   */\n  contentType(): string;\n  /**\n   * The bytes of the response\n   */\n  image(options?: ImageTransformationOutputOptions): ReadableStream<Uint8Array>;\n}\ninterface ImagesError extends Error {\n  readonly code: number;\n  readonly message: string;\n  readonly stack?: string;\n}\n/**\n * Media binding for transforming media streams.\n * Provides the entry point for media transformation operations.\n */\ninterface MediaBinding {\n  /**\n   * Creates a media transformer from an input stream.\n   * @param media - The input media bytes\n   * @returns A MediaTransformer instance for applying transformations\n   */\n  input(media: ReadableStream<Uint8Array>): MediaTransformer;\n}\n/**\n * Media transformer for applying transformation operations to media content.\n * Handles sizing, fitting, and other input transformation parameters.\n */\ninterface MediaTransformer {\n  /**\n   * Applies transformation options to the media content.\n   * @param transform - Configuration for how the media should be transformed\n   * @returns A generator for producing the transformed media output\n   */\n  transform(\n    transform?: MediaTransformationInputOptions,\n  ): MediaTransformationGenerator;\n  /**\n   * Generates the final media output with specified options.\n   * @param output - Configuration for the output format and parameters\n   * @returns The final transformation result containing the transformed media\n   */\n  output(output?: MediaTransformationOutputOptions): MediaTransformationResult;\n}\n/**\n * Generator for producing media transformation results.\n * Configures the output format and parameters for the transformed media.\n */\ninterface MediaTransformationGenerator {\n  /**\n   * Generates the final media output with specified options.\n   * @param output - Configuration for the output format and parameters\n   * @returns The final transformation result containing the transformed media\n   */\n  output(output?: MediaTransformationOutputOptions): MediaTransformationResult;\n}\n/**\n * Result of a media transformation operation.\n * Provides multiple ways to access the transformed media content.\n */\ninterface MediaTransformationResult {\n  /**\n   * Returns the transformed media as a readable stream of bytes.\n   * @returns A promise containing a readable stream with the transformed media\n   */\n  media(): Promise<ReadableStream<Uint8Array>>;\n  /**\n   * Returns the transformed media as an HTTP response object.\n   * @returns The transformed media as a Promise<Response>, ready to store in cache or return to users\n   */\n  response(): Promise<Response>;\n  /**\n   * Returns the MIME type of the transformed media.\n   * @returns A promise containing the content type string (e.g., 'image/jpeg', 'video/mp4')\n   */\n  contentType(): Promise<string>;\n}\n/**\n * Configuration options for transforming media input.\n * Controls how the media should be resized and fitted.\n */\ntype MediaTransformationInputOptions = {\n  /** How the media should be resized to fit the specified dimensions */\n  fit?: \"contain\" | \"cover\" | \"scale-down\";\n  /** Target width in pixels */\n  width?: number;\n  /** Target height in pixels */\n  height?: number;\n};\n/**\n * Configuration options for Media Transformations output.\n * Controls the format, timing, and type of the generated output.\n */\ntype MediaTransformationOutputOptions = {\n  /**\n   * Output mode determining the type of media to generate\n   */\n  mode?: \"video\" | \"spritesheet\" | \"frame\" | \"audio\";\n  /** Whether to include audio in the output */\n  audio?: boolean;\n  /**\n   * Starting timestamp for frame extraction or start time for clips. (e.g. '2s').\n   */\n  time?: string;\n  /**\n   * Duration for video clips, audio extraction, and spritesheet generation (e.g. '5s').\n   */\n  duration?: string;\n  /**\n   * Number of frames in the spritesheet.\n   */\n  imageCount?: number;\n  /**\n   * Output format for the generated media.\n   */\n  format?: \"jpg\" | \"png\" | \"m4a\";\n};\n/**\n * Error object for media transformation operations.\n * Extends the standard Error interface with additional media-specific information.\n */\ninterface MediaError extends Error {\n  readonly code: number;\n  readonly message: string;\n  readonly stack?: string;\n}\ndeclare module \"cloudflare:node\" {\n  interface NodeStyleServer {\n    listen(...args: unknown[]): this;\n    address(): {\n      port?: number | null | undefined;\n    };\n  }\n  export function httpServerHandler(port: number): ExportedHandler;\n  export function httpServerHandler(options: { port: number }): ExportedHandler;\n  export function httpServerHandler(server: NodeStyleServer): ExportedHandler;\n}\ntype Params<P extends string = any> = Record<P, string | string[]>;\ntype EventContext<Env, P extends string, Data> = {\n  request: Request<unknown, IncomingRequestCfProperties<unknown>>;\n  functionPath: string;\n  waitUntil: (promise: Promise<any>) => void;\n  passThroughOnException: () => void;\n  next: (input?: Request | string, init?: RequestInit) => Promise<Response>;\n  env: Env & {\n    ASSETS: {\n      fetch: typeof fetch;\n    };\n  };\n  params: Params<P>;\n  data: Data;\n};\ntype PagesFunction<\n  Env = unknown,\n  Params extends string = any,\n  Data extends Record<string, unknown> = Record<string, unknown>,\n> = (context: EventContext<Env, Params, Data>) => Response | Promise<Response>;\ntype EventPluginContext<Env, P extends string, Data, PluginArgs> = {\n  request: Request<unknown, IncomingRequestCfProperties<unknown>>;\n  functionPath: string;\n  waitUntil: (promise: Promise<any>) => void;\n  passThroughOnException: () => void;\n  next: (input?: Request | string, init?: RequestInit) => Promise<Response>;\n  env: Env & {\n    ASSETS: {\n      fetch: typeof fetch;\n    };\n  };\n  params: Params<P>;\n  data: Data;\n  pluginArgs: PluginArgs;\n};\ntype PagesPluginFunction<\n  Env = unknown,\n  Params extends string = any,\n  Data extends Record<string, unknown> = Record<string, unknown>,\n  PluginArgs = unknown,\n> = (\n  context: EventPluginContext<Env, Params, Data, PluginArgs>,\n) => Response | Promise<Response>;\ndeclare module \"assets:*\" {\n  export const onRequest: PagesFunction;\n}\n// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\ndeclare module \"cloudflare:pipelines\" {\n  export abstract class PipelineTransformationEntrypoint<\n    Env = unknown,\n    I extends PipelineRecord = PipelineRecord,\n    O extends PipelineRecord = PipelineRecord,\n  > {\n    protected env: Env;\n    protected ctx: ExecutionContext;\n    constructor(ctx: ExecutionContext, env: Env);\n    /**\n     * run receives an array of PipelineRecord which can be\n     * transformed and returned to the pipeline\n     * @param records Incoming records from the pipeline to be transformed\n     * @param metadata Information about the specific pipeline calling the transformation entrypoint\n     * @returns A promise containing the transformed PipelineRecord array\n     */\n    public run(records: I[], metadata: PipelineBatchMetadata): Promise<O[]>;\n  }\n  export type PipelineRecord = Record<string, unknown>;\n  export type PipelineBatchMetadata = {\n    pipelineId: string;\n    pipelineName: string;\n  };\n  export interface Pipeline<T extends PipelineRecord = PipelineRecord> {\n    /**\n     * The Pipeline interface represents the type of a binding to a Pipeline\n     *\n     * @param records The records to send to the pipeline\n     */\n    send(records: T[]): Promise<void>;\n  }\n}\n// PubSubMessage represents an incoming PubSub message.\n// The message includes metadata about the broker, the client, and the payload\n// itself.\n// https://developers.cloudflare.com/pub-sub/\ninterface PubSubMessage {\n  // Message ID\n  readonly mid: number;\n  // MQTT broker FQDN in the form mqtts://BROKER.NAMESPACE.cloudflarepubsub.com:PORT\n  readonly broker: string;\n  // The MQTT topic the message was sent on.\n  readonly topic: string;\n  // The client ID of the client that published this message.\n  readonly clientId: string;\n  // The unique identifier (JWT ID) used by the client to authenticate, if token\n  // auth was used.\n  readonly jti?: string;\n  // A Unix timestamp (seconds from Jan 1, 1970), set when the Pub/Sub Broker\n  // received the message from the client.\n  readonly receivedAt: number;\n  // An (optional) string with the MIME type of the payload, if set by the\n  // client.\n  readonly contentType: string;\n  // Set to 1 when the payload is a UTF-8 string\n  // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901063\n  readonly payloadFormatIndicator: number;\n  // Pub/Sub (MQTT) payloads can be UTF-8 strings, or byte arrays.\n  // You can use payloadFormatIndicator to inspect this before decoding.\n  payload: string | Uint8Array;\n}\n// JsonWebKey extended by kid parameter\ninterface JsonWebKeyWithKid extends JsonWebKey {\n  // Key Identifier of the JWK\n  readonly kid: string;\n}\ninterface RateLimitOptions {\n  key: string;\n}\ninterface RateLimitOutcome {\n  success: boolean;\n}\ninterface RateLimit {\n  /**\n   * Rate limit a request based on the provided options.\n   * @see https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/\n   * @returns A promise that resolves with the outcome of the rate limit.\n   */\n  limit(options: RateLimitOptions): Promise<RateLimitOutcome>;\n}\n// Namespace for RPC utility types. Unfortunately, we can't use a `module` here as these types need\n// to referenced by `Fetcher`. This is included in the \"importable\" version of the types which\n// strips all `module` blocks.\ndeclare namespace Rpc {\n  // Branded types for identifying `WorkerEntrypoint`/`DurableObject`/`Target`s.\n  // TypeScript uses *structural* typing meaning anything with the same shape as type `T` is a `T`.\n  // For the classes exported by `cloudflare:workers` we want *nominal* typing (i.e. we only want to\n  // accept `WorkerEntrypoint` from `cloudflare:workers`, not any other class with the same shape)\n  export const __RPC_STUB_BRAND: \"__RPC_STUB_BRAND\";\n  export const __RPC_TARGET_BRAND: \"__RPC_TARGET_BRAND\";\n  export const __WORKER_ENTRYPOINT_BRAND: \"__WORKER_ENTRYPOINT_BRAND\";\n  export const __DURABLE_OBJECT_BRAND: \"__DURABLE_OBJECT_BRAND\";\n  export const __WORKFLOW_ENTRYPOINT_BRAND: \"__WORKFLOW_ENTRYPOINT_BRAND\";\n  export interface RpcTargetBranded {\n    [__RPC_TARGET_BRAND]: never;\n  }\n  export interface WorkerEntrypointBranded {\n    [__WORKER_ENTRYPOINT_BRAND]: never;\n  }\n  export interface DurableObjectBranded {\n    [__DURABLE_OBJECT_BRAND]: never;\n  }\n  export interface WorkflowEntrypointBranded {\n    [__WORKFLOW_ENTRYPOINT_BRAND]: never;\n  }\n  export type EntrypointBranded =\n    | WorkerEntrypointBranded\n    | DurableObjectBranded\n    | WorkflowEntrypointBranded;\n  // Types that can be used through `Stub`s\n  export type Stubable = RpcTargetBranded | ((...args: any[]) => any);\n  // Types that can be passed over RPC\n  // The reason for using a generic type here is to build a serializable subset of structured\n  //   cloneable composite types. This allows types defined with the \"interface\" keyword to pass the\n  //   serializable check as well. Otherwise, only types defined with the \"type\" keyword would pass.\n  type Serializable<T> =\n    // Structured cloneables\n    | BaseType\n    // Structured cloneable composites\n    | Map<\n        T extends Map<infer U, unknown> ? Serializable<U> : never,\n        T extends Map<unknown, infer U> ? Serializable<U> : never\n      >\n    | Set<T extends Set<infer U> ? Serializable<U> : never>\n    | ReadonlyArray<T extends ReadonlyArray<infer U> ? Serializable<U> : never>\n    | {\n        [K in keyof T]: K extends number | string ? Serializable<T[K]> : never;\n      }\n    // Special types\n    | Stub<Stubable>\n    // Serialized as stubs, see `Stubify`\n    | Stubable;\n  // Base type for all RPC stubs, including common memory management methods.\n  // `T` is used as a marker type for unwrapping `Stub`s later.\n  interface StubBase<T extends Stubable> extends Disposable {\n    [__RPC_STUB_BRAND]: T;\n    dup(): this;\n  }\n  export type Stub<T extends Stubable> = Provider<T> & StubBase<T>;\n  // This represents all the types that can be sent as-is over an RPC boundary\n  type BaseType =\n    | void\n    | undefined\n    | null\n    | boolean\n    | number\n    | bigint\n    | string\n    | TypedArray\n    | ArrayBuffer\n    | DataView\n    | Date\n    | Error\n    | RegExp\n    | ReadableStream<Uint8Array>\n    | WritableStream<Uint8Array>\n    | Request\n    | Response\n    | Headers;\n  // Recursively rewrite all `Stubable` types with `Stub`s\n  type Stubify<T> = T extends Stubable\n    ? Stub<T>\n    : T extends Map<infer K, infer V>\n      ? Map<Stubify<K>, Stubify<V>>\n      : T extends Set<infer V>\n        ? Set<Stubify<V>>\n        : T extends Array<infer V>\n          ? Array<Stubify<V>>\n          : T extends ReadonlyArray<infer V>\n            ? ReadonlyArray<Stubify<V>>\n            : T extends BaseType\n              ? T\n              : T extends {\n                    [key: string | number]: any;\n                  }\n                ? {\n                    [K in keyof T]: Stubify<T[K]>;\n                  }\n                : T;\n  // Recursively rewrite all `Stub<T>`s with the corresponding `T`s.\n  // Note we use `StubBase` instead of `Stub` here to avoid circular dependencies:\n  // `Stub` depends on `Provider`, which depends on `Unstubify`, which would depend on `Stub`.\n  type Unstubify<T> =\n    T extends StubBase<infer V>\n      ? V\n      : T extends Map<infer K, infer V>\n        ? Map<Unstubify<K>, Unstubify<V>>\n        : T extends Set<infer V>\n          ? Set<Unstubify<V>>\n          : T extends Array<infer V>\n            ? Array<Unstubify<V>>\n            : T extends ReadonlyArray<infer V>\n              ? ReadonlyArray<Unstubify<V>>\n              : T extends BaseType\n                ? T\n                : T extends {\n                      [key: string | number]: unknown;\n                    }\n                  ? {\n                      [K in keyof T]: Unstubify<T[K]>;\n                    }\n                  : T;\n  type UnstubifyAll<A extends any[]> = {\n    [I in keyof A]: Unstubify<A[I]>;\n  };\n  // Utility type for adding `Provider`/`Disposable`s to `object` types only.\n  // Note `unknown & T` is equivalent to `T`.\n  type MaybeProvider<T> = T extends object ? Provider<T> : unknown;\n  type MaybeDisposable<T> = T extends object ? Disposable : unknown;\n  // Type for method return or property on an RPC interface.\n  // - Stubable types are replaced by stubs.\n  // - Serializable types are passed by value, with stubable types replaced by stubs\n  //   and a top-level `Disposer`.\n  // Everything else can't be passed over PRC.\n  // Technically, we use custom thenables here, but they quack like `Promise`s.\n  // Intersecting with `(Maybe)Provider` allows pipelining.\n  type Result<R> = R extends Stubable\n    ? Promise<Stub<R>> & Provider<R>\n    : R extends Serializable<R>\n      ? Promise<Stubify<R> & MaybeDisposable<R>> & MaybeProvider<R>\n      : never;\n  // Type for method or property on an RPC interface.\n  // For methods, unwrap `Stub`s in parameters, and rewrite returns to be `Result`s.\n  // Unwrapping `Stub`s allows calling with `Stubable` arguments.\n  // For properties, rewrite types to be `Result`s.\n  // In each case, unwrap `Promise`s.\n  type MethodOrProperty<V> = V extends (...args: infer P) => infer R\n    ? (...args: UnstubifyAll<P>) => Result<Awaited<R>>\n    : Result<Awaited<V>>;\n  // Type for the callable part of an `Provider` if `T` is callable.\n  // This is intersected with methods/properties.\n  type MaybeCallableProvider<T> = T extends (...args: any[]) => any\n    ? MethodOrProperty<T>\n    : unknown;\n  // Base type for all other types providing RPC-like interfaces.\n  // Rewrites all methods/properties to be `MethodOrProperty`s, while preserving callable types.\n  // `Reserved` names (e.g. stub method names like `dup()`) and symbols can't be accessed over RPC.\n  export type Provider<\n    T extends object,\n    Reserved extends string = never,\n  > = MaybeCallableProvider<T> &\n    Pick<\n      {\n        [K in keyof T]: MethodOrProperty<T[K]>;\n      },\n      Exclude<keyof T, Reserved | symbol | keyof StubBase<never>>\n    >;\n}\ndeclare namespace Cloudflare {\n  // Type of `env`.\n  //\n  // The specific project can extend `Env` by redeclaring it in project-specific files. Typescript\n  // will merge all declarations.\n  //\n  // You can use `wrangler types` to generate the `Env` type automatically.\n  interface Env {}\n  // Project-specific parameters used to inform types.\n  //\n  // This interface is, again, intended to be declared in project-specific files, and then that\n  // declaration will be merged with this one.\n  //\n  // A project should have a declaration like this:\n  //\n  //     interface GlobalProps {\n  //       // Declares the main module's exports. Used to populate Cloudflare.Exports aka the type\n  //       // of `ctx.exports`.\n  //       mainModule: typeof import(\"my-main-module\");\n  //\n  //       // Declares which of the main module's exports are configured with durable storage, and\n  //       // thus should behave as Durable Object namsepace bindings.\n  //       durableNamespaces: \"MyDurableObject\" | \"AnotherDurableObject\";\n  //     }\n  //\n  // You can use `wrangler types` to generate `GlobalProps` automatically.\n  interface GlobalProps {}\n  // Evaluates to the type of a property in GlobalProps, defaulting to `Default` if it is not\n  // present.\n  type GlobalProp<K extends string, Default> = K extends keyof GlobalProps\n    ? GlobalProps[K]\n    : Default;\n  // The type of the program's main module exports, if known. Requires `GlobalProps` to declare the\n  // `mainModule` property.\n  type MainModule = GlobalProp<\"mainModule\", {}>;\n  // The type of ctx.exports, which contains loopback bindings for all top-level exports.\n  type Exports = {\n    [K in keyof MainModule]: LoopbackForExport<MainModule[K]> &\n      // If the export is listed in `durableNamespaces`, then it is also a\n      // DurableObjectNamespace.\n      (K extends GlobalProp<\"durableNamespaces\", never>\n        ? MainModule[K] extends new (...args: any[]) => infer DoInstance\n          ? DoInstance extends Rpc.DurableObjectBranded\n            ? DurableObjectNamespace<DoInstance>\n            : DurableObjectNamespace<undefined>\n          : DurableObjectNamespace<undefined>\n        : {});\n  };\n}\ndeclare namespace CloudflareWorkersModule {\n  export type RpcStub<T extends Rpc.Stubable> = Rpc.Stub<T>;\n  export const RpcStub: {\n    new <T extends Rpc.Stubable>(value: T): Rpc.Stub<T>;\n  };\n  export abstract class RpcTarget implements Rpc.RpcTargetBranded {\n    [Rpc.__RPC_TARGET_BRAND]: never;\n  }\n  // `protected` fields don't appear in `keyof`s, so can't be accessed over RPC\n  export abstract class WorkerEntrypoint<Env = Cloudflare.Env, Props = {}>\n    implements Rpc.WorkerEntrypointBranded\n  {\n    [Rpc.__WORKER_ENTRYPOINT_BRAND]: never;\n    protected ctx: ExecutionContext<Props>;\n    protected env: Env;\n    constructor(ctx: ExecutionContext, env: Env);\n    email?(message: ForwardableEmailMessage): void | Promise<void>;\n    fetch?(request: Request): Response | Promise<Response>;\n    queue?(batch: MessageBatch<unknown>): void | Promise<void>;\n    scheduled?(controller: ScheduledController): void | Promise<void>;\n    tail?(events: TraceItem[]): void | Promise<void>;\n    tailStream?(\n      event: TailStream.TailEvent<TailStream.Onset>,\n    ):\n      | TailStream.TailEventHandlerType\n      | Promise<TailStream.TailEventHandlerType>;\n    test?(controller: TestController): void | Promise<void>;\n    trace?(traces: TraceItem[]): void | Promise<void>;\n  }\n  export abstract class DurableObject<Env = Cloudflare.Env, Props = {}>\n    implements Rpc.DurableObjectBranded\n  {\n    [Rpc.__DURABLE_OBJECT_BRAND]: never;\n    protected ctx: DurableObjectState<Props>;\n    protected env: Env;\n    constructor(ctx: DurableObjectState, env: Env);\n    alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise<void>;\n    fetch?(request: Request): Response | Promise<Response>;\n    webSocketMessage?(\n      ws: WebSocket,\n      message: string | ArrayBuffer,\n    ): void | Promise<void>;\n    webSocketClose?(\n      ws: WebSocket,\n      code: number,\n      reason: string,\n      wasClean: boolean,\n    ): void | Promise<void>;\n    webSocketError?(ws: WebSocket, error: unknown): void | Promise<void>;\n  }\n  export type WorkflowDurationLabel =\n    | \"second\"\n    | \"minute\"\n    | \"hour\"\n    | \"day\"\n    | \"week\"\n    | \"month\"\n    | \"year\";\n  export type WorkflowSleepDuration =\n    | `${number} ${WorkflowDurationLabel}${\"s\" | \"\"}`\n    | number;\n  export type WorkflowDelayDuration = WorkflowSleepDuration;\n  export type WorkflowTimeoutDuration = WorkflowSleepDuration;\n  export type WorkflowRetentionDuration = WorkflowSleepDuration;\n  export type WorkflowBackoff = \"constant\" | \"linear\" | \"exponential\";\n  export type WorkflowStepConfig = {\n    retries?: {\n      limit: number;\n      delay: WorkflowDelayDuration | number;\n      backoff?: WorkflowBackoff;\n    };\n    timeout?: WorkflowTimeoutDuration | number;\n  };\n  export type WorkflowEvent<T> = {\n    payload: Readonly<T>;\n    timestamp: Date;\n    instanceId: string;\n  };\n  export type WorkflowStepEvent<T> = {\n    payload: Readonly<T>;\n    timestamp: Date;\n    type: string;\n  };\n  export type WorkflowStepContext = {\n    attempt: number;\n  };\n  export abstract class WorkflowStep {\n    do<T extends Rpc.Serializable<T>>(\n      name: string,\n      callback: (ctx: WorkflowStepContext) => Promise<T>,\n    ): Promise<T>;\n    do<T extends Rpc.Serializable<T>>(\n      name: string,\n      config: WorkflowStepConfig,\n      callback: (ctx: WorkflowStepContext) => Promise<T>,\n    ): Promise<T>;\n    sleep: (name: string, duration: WorkflowSleepDuration) => Promise<void>;\n    sleepUntil: (name: string, timestamp: Date | number) => Promise<void>;\n    waitForEvent<T extends Rpc.Serializable<T>>(\n      name: string,\n      options: {\n        type: string;\n        timeout?: WorkflowTimeoutDuration | number;\n      },\n    ): Promise<WorkflowStepEvent<T>>;\n  }\n  export type WorkflowInstanceStatus =\n    | \"queued\"\n    | \"running\"\n    | \"paused\"\n    | \"errored\"\n    | \"terminated\"\n    | \"complete\"\n    | \"waiting\"\n    | \"waitingForPause\"\n    | \"unknown\";\n  export abstract class WorkflowEntrypoint<\n    Env = unknown,\n    T extends Rpc.Serializable<T> | unknown = unknown,\n  >\n    implements Rpc.WorkflowEntrypointBranded\n  {\n    [Rpc.__WORKFLOW_ENTRYPOINT_BRAND]: never;\n    protected ctx: ExecutionContext;\n    protected env: Env;\n    constructor(ctx: ExecutionContext, env: Env);\n    run(\n      event: Readonly<WorkflowEvent<T>>,\n      step: WorkflowStep,\n    ): Promise<unknown>;\n  }\n  export function waitUntil(promise: Promise<unknown>): void;\n  export function withEnv(newEnv: unknown, fn: () => unknown): unknown;\n  export function withExports(newExports: unknown, fn: () => unknown): unknown;\n  export function withEnvAndExports(\n    newEnv: unknown,\n    newExports: unknown,\n    fn: () => unknown,\n  ): unknown;\n  export const env: Cloudflare.Env;\n  export const exports: Cloudflare.Exports;\n}\ndeclare module \"cloudflare:workers\" {\n  export = CloudflareWorkersModule;\n}\ninterface SecretsStoreSecret {\n  /**\n   * Get a secret from the Secrets Store, returning a string of the secret value\n   * if it exists, or throws an error if it does not exist\n   */\n  get(): Promise<string>;\n}\ndeclare module \"cloudflare:sockets\" {\n  function _connect(\n    address: string | SocketAddress,\n    options?: SocketOptions,\n  ): Socket;\n  export { _connect as connect };\n}\n/**\n * Binding entrypoint for Cloudflare Stream.\n *\n * Usage:\n * - Binding-level operations:\n *   `await env.STREAM.videos.upload`\n *   `await env.STREAM.videos.createDirectUpload`\n *   `await env.STREAM.videos.*`\n *   `await env.STREAM.watermarks.*`\n * - Per-video operations:\n *   `await env.STREAM.video(id).downloads.*`\n *   `await env.STREAM.video(id).captions.*`\n *\n * Example usage:\n * ```ts\n * await env.STREAM.video(id).downloads.generate();\n *\n * const video = env.STREAM.video(id)\n * const captions = video.captions.list();\n * const videoDetails = video.details()\n * ```\n */\ninterface StreamBinding {\n  /**\n   * Returns a handle scoped to a single video for per-video operations.\n   * @param id The unique identifier for the video.\n   * @returns A handle for per-video operations.\n   */\n  video(id: string): StreamVideoHandle;\n  /**\n   * Uploads a new video from a provided URL.\n   * @param url The URL to upload from.\n   * @param params Optional upload parameters.\n   * @returns The uploaded video details.\n   * @throws {BadRequestError} if the upload parameter is invalid or the URL is invalid\n   * @throws {QuotaReachedError} if the account storage capacity is exceeded\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {RateLimitedError} if the server received too many requests\n   * @throws {AlreadyUploadedError} if a video was already uploaded to this URL\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  upload(url: string, params?: StreamUrlUploadParams): Promise<StreamVideo>;\n  /**\n   * Creates a direct upload that allows video uploads without an API key.\n   * @param params Parameters for the direct upload\n   * @returns The direct upload details.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {RateLimitedError} if the server received too many requests\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  createDirectUpload(\n    params: StreamDirectUploadCreateParams,\n  ): Promise<StreamDirectUpload>;\n  videos: StreamVideos;\n  watermarks: StreamWatermarks;\n}\n/**\n * Handle for operations scoped to a single Stream video.\n */\ninterface StreamVideoHandle {\n  /**\n   * The unique identifier for the video.\n   */\n  id: string;\n  /**\n   * Get a full videos details\n   * @returns The full video details.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  details(): Promise<StreamVideo>;\n  /**\n   * Update details for a single video.\n   * @param params The fields to update for the video.\n   * @returns The updated video details.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  update(params: StreamUpdateVideoParams): Promise<StreamVideo>;\n  /**\n   * Deletes a video and its copies from Cloudflare Stream.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(): Promise<void>;\n  /**\n   * Creates a signed URL token for a video.\n   * @returns The signed token that was created.\n   * @throws {InternalError} if the signing key cannot be retrieved or the token cannot be signed\n   */\n  generateToken(): Promise<string>;\n  downloads: StreamScopedDownloads;\n  captions: StreamScopedCaptions;\n}\ninterface StreamVideo {\n  /**\n   * The unique identifier for the video.\n   */\n  id: string;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator: string | null;\n  /**\n   * The thumbnail URL for the video.\n   */\n  thumbnail: string;\n  /**\n   * The thumbnail timestamp percentage.\n   */\n  thumbnailTimestampPct: number;\n  /**\n   * Indicates whether the video is ready to stream.\n   */\n  readyToStream: boolean;\n  /**\n   * The date and time the video became ready to stream.\n   */\n  readyToStreamAt: string | null;\n  /**\n   * Processing status information.\n   */\n  status: StreamVideoStatus;\n  /**\n   * A user modifiable key-value store.\n   */\n  meta: Record<string, string>;\n  /**\n   * The date and time the video was created.\n   */\n  created: string;\n  /**\n   * The date and time the video was last modified.\n   */\n  modified: string;\n  /**\n   * The date and time at which the video will be deleted.\n   */\n  scheduledDeletion: string | null;\n  /**\n   * The size of the video in bytes.\n   */\n  size: number;\n  /**\n   * The preview URL for the video.\n   */\n  preview?: string;\n  /**\n   * Origins allowed to display the video.\n   */\n  allowedOrigins: Array<string>;\n  /**\n   * Indicates whether signed URLs are required.\n   */\n  requireSignedURLs: boolean | null;\n  /**\n   * The date and time the video was uploaded.\n   */\n  uploaded: string | null;\n  /**\n   * The date and time when the upload URL expires.\n   */\n  uploadExpiry: string | null;\n  /**\n   * The maximum size in bytes for direct uploads.\n   */\n  maxSizeBytes: number | null;\n  /**\n   * The maximum duration in seconds for direct uploads.\n   */\n  maxDurationSeconds: number | null;\n  /**\n   * The video duration in seconds. -1 indicates unknown.\n   */\n  duration: number;\n  /**\n   * Input metadata for the original upload.\n   */\n  input: StreamVideoInput;\n  /**\n   * Playback URLs for the video.\n   */\n  hlsPlaybackUrl: string;\n  dashPlaybackUrl: string;\n  /**\n   * The watermark applied to the video, if any.\n   */\n  watermark: StreamWatermark | null;\n  /**\n   * The live input id associated with the video, if any.\n   */\n  liveInputId?: string | null;\n  /**\n   * The source video id if this is a clip.\n   */\n  clippedFromId: string | null;\n  /**\n   * Public details associated with the video.\n   */\n  publicDetails: StreamPublicDetails | null;\n}\ntype StreamVideoStatus = {\n  /**\n   * The current processing state.\n   */\n  state: string;\n  /**\n   * The current processing step.\n   */\n  step?: string;\n  /**\n   * The percent complete as a string.\n   */\n  pctComplete?: string;\n  /**\n   * An error reason code, if applicable.\n   */\n  errorReasonCode: string;\n  /**\n   * An error reason text, if applicable.\n   */\n  errorReasonText: string;\n};\ntype StreamVideoInput = {\n  /**\n   * The input width in pixels.\n   */\n  width: number;\n  /**\n   * The input height in pixels.\n   */\n  height: number;\n};\ntype StreamPublicDetails = {\n  /**\n   * The public title for the video.\n   */\n  title: string | null;\n  /**\n   * The public share link.\n   */\n  share_link: string | null;\n  /**\n   * The public channel link.\n   */\n  channel_link: string | null;\n  /**\n   * The public logo URL.\n   */\n  logo: string | null;\n};\ntype StreamDirectUpload = {\n  /**\n   * The URL an unauthenticated upload can use for a single multipart request.\n   */\n  uploadURL: string;\n  /**\n   * A Cloudflare-generated unique identifier for a media item.\n   */\n  id: string;\n  /**\n   * The watermark profile applied to the upload.\n   */\n  watermark: StreamWatermark | null;\n  /**\n   * The scheduled deletion time, if any.\n   */\n  scheduledDeletion: string | null;\n};\ntype StreamDirectUploadCreateParams = {\n  /**\n   * The maximum duration in seconds for a video upload.\n   */\n  maxDurationSeconds: number;\n  /**\n   * The date and time after upload when videos will not be accepted.\n   */\n  expiry?: string;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator?: string;\n  /**\n   * A user modifiable key-value store used to reference other systems of record for\n   * managing videos.\n   */\n  meta?: Record<string, string>;\n  /**\n   * Lists the origins allowed to display the video.\n   */\n  allowedOrigins?: Array<string>;\n  /**\n   * Indicates whether the video can be accessed using the id. When set to `true`,\n   * a signed token must be generated with a signing key to view the video.\n   */\n  requireSignedURLs?: boolean;\n  /**\n   * The thumbnail timestamp percentage.\n   */\n  thumbnailTimestampPct?: number;\n  /**\n   * The date and time at which the video will be deleted. Include `null` to remove\n   * a scheduled deletion.\n   */\n  scheduledDeletion?: string | null;\n  /**\n   * The watermark profile to apply.\n   */\n  watermark?: StreamDirectUploadWatermark;\n};\ntype StreamDirectUploadWatermark = {\n  /**\n   * The unique identifier for the watermark profile.\n   */\n  id: string;\n};\ntype StreamUrlUploadParams = {\n  /**\n   * Lists the origins allowed to display the video. Enter allowed origin\n   * domains in an array and use `*` for wildcard subdomains. Empty arrays allow the\n   * video to be viewed on any origin.\n   */\n  allowedOrigins?: Array<string>;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator?: string;\n  /**\n   * A user modifiable key-value store used to reference other systems of\n   * record for managing videos.\n   */\n  meta?: Record<string, string>;\n  /**\n   * Indicates whether the video can be a accessed using the id. When\n   * set to `true`, a signed token must be generated with a signing key to view the\n   * video.\n   */\n  requireSignedURLs?: boolean;\n  /**\n   * Indicates the date and time at which the video will be deleted. Omit\n   * the field to indicate no change, or include with a `null` value to remove an\n   * existing scheduled deletion. If specified, must be at least 30 days from upload\n   * time.\n   */\n  scheduledDeletion?: string | null;\n  /**\n   * The timestamp for a thumbnail image calculated as a percentage value\n   * of the video's duration. To convert from a second-wise timestamp to a\n   * percentage, divide the desired timestamp by the total duration of the video. If\n   * this value is not set, the default thumbnail image is taken from 0s of the\n   * video.\n   */\n  thumbnailTimestampPct?: number;\n  /**\n   * The identifier for the watermark profile\n   */\n  watermarkId?: string;\n};\ninterface StreamScopedCaptions {\n  /**\n   * Uploads the caption or subtitle file to the endpoint for a specific BCP47 language.\n   * One caption or subtitle file per language is allowed.\n   * @param language The BCP 47 language tag for the caption or subtitle.\n   * @param file The caption or subtitle file to upload.\n   * @returns The created caption entry.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the language or file is invalid\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  upload(language: string, file: File): Promise<StreamCaption>;\n  /**\n   * Generate captions or subtitles for the provided language via AI.\n   * @param language The BCP 47 language tag to generate.\n   * @returns The generated caption entry.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the language is invalid\n   * @throws {StreamError} if a generated caption already exists\n   * @throws {StreamError} if the video duration is too long\n   * @throws {StreamError} if the video is missing audio\n   * @throws {StreamError} if the requested language is not supported\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(language: string): Promise<StreamCaption>;\n  /**\n   * Lists the captions or subtitles.\n   * Use the language parameter to filter by a specific language.\n   * @param language The optional BCP 47 language tag to filter by.\n   * @returns The list of captions or subtitles.\n   * @throws {NotFoundError} if the video or caption is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  list(language?: string): Promise<StreamCaption[]>;\n  /**\n   * Removes the captions or subtitles from a video.\n   * @param language The BCP 47 language tag to remove.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the video or caption is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(language: string): Promise<void>;\n}\ninterface StreamScopedDownloads {\n  /**\n   * Generates a download for a video when a video is ready to view. Available\n   * types are `default` and `audio`. Defaults to `default` when omitted.\n   * @param downloadType The download type to create.\n   * @returns The current downloads for the video.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the download type is invalid\n   * @throws {StreamError} if the video duration is too long to generate a download\n   * @throws {StreamError} if the video is not ready to stream\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(\n    downloadType?: StreamDownloadType,\n  ): Promise<StreamDownloadGetResponse>;\n  /**\n   * Lists the downloads created for a video.\n   * @returns The current downloads for the video.\n   * @throws {NotFoundError} if the video or downloads are not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  get(): Promise<StreamDownloadGetResponse>;\n  /**\n   * Delete the downloads for a video. Available types are `default` and `audio`.\n   * Defaults to `default` when omitted.\n   * @param downloadType The download type to delete.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the video or downloads are not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(downloadType?: StreamDownloadType): Promise<void>;\n}\ninterface StreamVideos {\n  /**\n   * Lists all videos in a users account.\n   * @returns The list of videos.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  list(params?: StreamVideosListParams): Promise<StreamVideo[]>;\n}\ninterface StreamWatermarks {\n  /**\n   * Generate a new watermark profile\n   * @param file The image file to upload\n   * @param params The watermark creation parameters.\n   * @returns The created watermark profile.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InvalidURLError} if the URL is invalid\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {TooManyWatermarksError} if the number of allowed watermarks is reached\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(\n    file: File,\n    params: StreamWatermarkCreateParams,\n  ): Promise<StreamWatermark>;\n  /**\n   * Generate a new watermark profile\n   * @param url The image url to upload\n   * @param params The watermark creation parameters.\n   * @returns The created watermark profile.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InvalidURLError} if the URL is invalid\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {TooManyWatermarksError} if the number of allowed watermarks is reached\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(\n    url: string,\n    params: StreamWatermarkCreateParams,\n  ): Promise<StreamWatermark>;\n  /**\n   * Lists all watermark profiles for an account.\n   * @returns The list of watermark profiles.\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  list(): Promise<StreamWatermark[]>;\n  /**\n   * Retrieves details for a single watermark profile.\n   * @param watermarkId The watermark profile identifier.\n   * @returns The watermark profile details.\n   * @throws {NotFoundError} if the watermark is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  get(watermarkId: string): Promise<StreamWatermark>;\n  /**\n   * Deletes a watermark profile.\n   * @param watermarkId The watermark profile identifier.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the watermark is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(watermarkId: string): Promise<void>;\n}\ntype StreamUpdateVideoParams = {\n  /**\n   * Lists the origins allowed to display the video. Enter allowed origin\n   * domains in an array and use `*` for wildcard subdomains. Empty arrays allow the\n   * video to be viewed on any origin.\n   */\n  allowedOrigins?: Array<string>;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator?: string;\n  /**\n   * The maximum duration in seconds for a video upload. Can be set for a\n   * video that is not yet uploaded to limit its duration. Uploads that exceed the\n   * specified duration will fail during processing. A value of `-1` means the value\n   * is unknown.\n   */\n  maxDurationSeconds?: number;\n  /**\n   * A user modifiable key-value store used to reference other systems of\n   * record for managing videos.\n   */\n  meta?: Record<string, string>;\n  /**\n   * Indicates whether the video can be a accessed using the id. When\n   * set to `true`, a signed token must be generated with a signing key to view the\n   * video.\n   */\n  requireSignedURLs?: boolean;\n  /**\n   * Indicates the date and time at which the video will be deleted. Omit\n   * the field to indicate no change, or include with a `null` value to remove an\n   * existing scheduled deletion. If specified, must be at least 30 days from upload\n   * time.\n   */\n  scheduledDeletion?: string | null;\n  /**\n   * The timestamp for a thumbnail image calculated as a percentage value\n   * of the video's duration. To convert from a second-wise timestamp to a\n   * percentage, divide the desired timestamp by the total duration of the video. If\n   * this value is not set, the default thumbnail image is taken from 0s of the\n   * video.\n   */\n  thumbnailTimestampPct?: number;\n};\ntype StreamCaption = {\n  /**\n   * Whether the caption was generated via AI.\n   */\n  generated?: boolean;\n  /**\n   * The language label displayed in the native language to users.\n   */\n  label: string;\n  /**\n   * The language tag in BCP 47 format.\n   */\n  language: string;\n  /**\n   * The status of a generated caption.\n   */\n  status?: \"ready\" | \"inprogress\" | \"error\";\n};\ntype StreamDownloadStatus = \"ready\" | \"inprogress\" | \"error\";\ntype StreamDownloadType = \"default\" | \"audio\";\ntype StreamDownload = {\n  /**\n   * Indicates the progress as a percentage between 0 and 100.\n   */\n  percentComplete: number;\n  /**\n   * The status of a generated download.\n   */\n  status: StreamDownloadStatus;\n  /**\n   * The URL to access the generated download.\n   */\n  url?: string;\n};\n/**\n * An object with download type keys. Each key is optional and only present if that\n * download type has been created.\n */\ntype StreamDownloadGetResponse = {\n  /**\n   * The audio-only download. Only present if this download type has been created.\n   */\n  audio?: StreamDownload;\n  /**\n   * The default video download. Only present if this download type has been created.\n   */\n  default?: StreamDownload;\n};\ntype StreamWatermarkPosition =\n  | \"upperRight\"\n  | \"upperLeft\"\n  | \"lowerLeft\"\n  | \"lowerRight\"\n  | \"center\";\ntype StreamWatermark = {\n  /**\n   * The unique identifier for a watermark profile.\n   */\n  id: string;\n  /**\n   * The size of the image in bytes.\n   */\n  size: number;\n  /**\n   * The height of the image in pixels.\n   */\n  height: number;\n  /**\n   * The width of the image in pixels.\n   */\n  width: number;\n  /**\n   * The date and a time a watermark profile was created.\n   */\n  created: string;\n  /**\n   * The source URL for a downloaded image. If the watermark profile was created via\n   * direct upload, this field is null.\n   */\n  downloadedFrom: string | null;\n  /**\n   * A short description of the watermark profile.\n   */\n  name: string;\n  /**\n   * The translucency of the image. A value of `0.0` makes the image completely\n   * transparent, and `1.0` makes the image completely opaque. Note that if the image\n   * is already semi-transparent, setting this to `1.0` will not make the image\n   * completely opaque.\n   */\n  opacity: number;\n  /**\n   * The whitespace between the adjacent edges (determined by position) of the video\n   * and the image. `0.0` indicates no padding, and `1.0` indicates a fully padded\n   * video width or length, as determined by the algorithm.\n   */\n  padding: number;\n  /**\n   * The size of the image relative to the overall size of the video. This parameter\n   * will adapt to horizontal and vertical videos automatically. `0.0` indicates no\n   * scaling (use the size of the image as-is), and `1.0 `fills the entire video.\n   */\n  scale: number;\n  /**\n   * The location of the image. Valid positions are: `upperRight`, `upperLeft`,\n   * `lowerLeft`, `lowerRight`, and `center`. Note that `center` ignores the\n   * `padding` parameter.\n   */\n  position: StreamWatermarkPosition;\n};\ntype StreamWatermarkCreateParams = {\n  /**\n   * A short description of the watermark profile.\n   */\n  name?: string;\n  /**\n   * The translucency of the image. A value of `0.0` makes the image completely\n   * transparent, and `1.0` makes the image completely opaque. Note that if the\n   * image is already semi-transparent, setting this to `1.0` will not make the\n   * image completely opaque.\n   */\n  opacity?: number;\n  /**\n   * The whitespace between the adjacent edges (determined by position) of the\n   * video and the image. `0.0` indicates no padding, and `1.0` indicates a fully\n   * padded video width or length, as determined by the algorithm.\n   */\n  padding?: number;\n  /**\n   * The size of the image relative to the overall size of the video. This\n   * parameter will adapt to horizontal and vertical videos automatically. `0.0`\n   * indicates no scaling (use the size of the image as-is), and `1.0 `fills the\n   * entire video.\n   */\n  scale?: number;\n  /**\n   * The location of the image.\n   */\n  position?: StreamWatermarkPosition;\n};\ntype StreamVideosListParams = {\n  /**\n   * The maximum number of videos to return.\n   */\n  limit?: number;\n  /**\n   * Return videos created before this timestamp.\n   * (RFC3339/RFC3339Nano)\n   */\n  before?: string;\n  /**\n   * Comparison operator for the `before` field.\n   * @default 'lt'\n   */\n  beforeComp?: StreamPaginationComparison;\n  /**\n   * Return videos created after this timestamp.\n   * (RFC3339/RFC3339Nano)\n   */\n  after?: string;\n  /**\n   * Comparison operator for the `after` field.\n   * @default 'gte'\n   */\n  afterComp?: StreamPaginationComparison;\n};\ntype StreamPaginationComparison = \"eq\" | \"gt\" | \"gte\" | \"lt\" | \"lte\";\n/**\n * Error object for Stream binding operations.\n */\ninterface StreamError extends Error {\n  readonly code: number;\n  readonly statusCode: number;\n  readonly message: string;\n  readonly stack?: string;\n}\ninterface InternalError extends StreamError {\n  name: \"InternalError\";\n}\ninterface BadRequestError extends StreamError {\n  name: \"BadRequestError\";\n}\ninterface NotFoundError extends StreamError {\n  name: \"NotFoundError\";\n}\ninterface ForbiddenError extends StreamError {\n  name: \"ForbiddenError\";\n}\ninterface RateLimitedError extends StreamError {\n  name: \"RateLimitedError\";\n}\ninterface QuotaReachedError extends StreamError {\n  name: \"QuotaReachedError\";\n}\ninterface MaxFileSizeError extends StreamError {\n  name: \"MaxFileSizeError\";\n}\ninterface InvalidURLError extends StreamError {\n  name: \"InvalidURLError\";\n}\ninterface AlreadyUploadedError extends StreamError {\n  name: \"AlreadyUploadedError\";\n}\ninterface TooManyWatermarksError extends StreamError {\n  name: \"TooManyWatermarksError\";\n}\ntype MarkdownDocument = {\n  name: string;\n  blob: Blob;\n};\ntype ConversionResponse =\n  | {\n      id: string;\n      name: string;\n      mimeType: string;\n      format: \"markdown\";\n      tokens: number;\n      data: string;\n    }\n  | {\n      id: string;\n      name: string;\n      mimeType: string;\n      format: \"error\";\n      error: string;\n    };\ntype ImageConversionOptions = {\n  descriptionLanguage?: \"en\" | \"es\" | \"fr\" | \"it\" | \"pt\" | \"de\";\n};\ntype EmbeddedImageConversionOptions = ImageConversionOptions & {\n  convert?: boolean;\n  maxConvertedImages?: number;\n};\ntype ConversionOptions = {\n  html?: {\n    images?: EmbeddedImageConversionOptions & {\n      convertOGImage?: boolean;\n    };\n    hostname?: string;\n    cssSelector?: string;\n  };\n  docx?: {\n    images?: EmbeddedImageConversionOptions;\n  };\n  image?: ImageConversionOptions;\n  pdf?: {\n    images?: EmbeddedImageConversionOptions;\n    metadata?: boolean;\n  };\n};\ntype ConversionRequestOptions = {\n  gateway?: GatewayOptions;\n  extraHeaders?: object;\n  conversionOptions?: ConversionOptions;\n};\ntype SupportedFileFormat = {\n  mimeType: string;\n  extension: string;\n};\ndeclare abstract class ToMarkdownService {\n  transform(\n    files: MarkdownDocument[],\n    options?: ConversionRequestOptions,\n  ): Promise<ConversionResponse[]>;\n  transform(\n    files: MarkdownDocument,\n    options?: ConversionRequestOptions,\n  ): Promise<ConversionResponse>;\n  supported(): Promise<SupportedFileFormat[]>;\n}\ndeclare namespace TailStream {\n  interface Header {\n    readonly name: string;\n    readonly value: string;\n  }\n  interface FetchEventInfo {\n    readonly type: \"fetch\";\n    readonly method: string;\n    readonly url: string;\n    readonly cfJson?: object;\n    readonly headers: Header[];\n  }\n  interface JsRpcEventInfo {\n    readonly type: \"jsrpc\";\n  }\n  interface ScheduledEventInfo {\n    readonly type: \"scheduled\";\n    readonly scheduledTime: Date;\n    readonly cron: string;\n  }\n  interface AlarmEventInfo {\n    readonly type: \"alarm\";\n    readonly scheduledTime: Date;\n  }\n  interface QueueEventInfo {\n    readonly type: \"queue\";\n    readonly queueName: string;\n    readonly batchSize: number;\n  }\n  interface EmailEventInfo {\n    readonly type: \"email\";\n    readonly mailFrom: string;\n    readonly rcptTo: string;\n    readonly rawSize: number;\n  }\n  interface TraceEventInfo {\n    readonly type: \"trace\";\n    readonly traces: (string | null)[];\n  }\n  interface HibernatableWebSocketEventInfoMessage {\n    readonly type: \"message\";\n  }\n  interface HibernatableWebSocketEventInfoError {\n    readonly type: \"error\";\n  }\n  interface HibernatableWebSocketEventInfoClose {\n    readonly type: \"close\";\n    readonly code: number;\n    readonly wasClean: boolean;\n  }\n  interface HibernatableWebSocketEventInfo {\n    readonly type: \"hibernatableWebSocket\";\n    readonly info:\n      | HibernatableWebSocketEventInfoClose\n      | HibernatableWebSocketEventInfoError\n      | HibernatableWebSocketEventInfoMessage;\n  }\n  interface CustomEventInfo {\n    readonly type: \"custom\";\n  }\n  interface FetchResponseInfo {\n    readonly type: \"fetch\";\n    readonly statusCode: number;\n  }\n  type EventOutcome =\n    | \"ok\"\n    | \"canceled\"\n    | \"exception\"\n    | \"unknown\"\n    | \"killSwitch\"\n    | \"daemonDown\"\n    | \"exceededCpu\"\n    | \"exceededMemory\"\n    | \"loadShed\"\n    | \"responseStreamDisconnected\"\n    | \"scriptNotFound\";\n  interface ScriptVersion {\n    readonly id: string;\n    readonly tag?: string;\n    readonly message?: string;\n  }\n  interface Onset {\n    readonly type: \"onset\";\n    readonly attributes: Attribute[];\n    // id for the span being opened by this Onset event.\n    readonly spanId: string;\n    readonly dispatchNamespace?: string;\n    readonly entrypoint?: string;\n    readonly executionModel: string;\n    readonly scriptName?: string;\n    readonly scriptTags?: string[];\n    readonly scriptVersion?: ScriptVersion;\n    readonly info:\n      | FetchEventInfo\n      | JsRpcEventInfo\n      | ScheduledEventInfo\n      | AlarmEventInfo\n      | QueueEventInfo\n      | EmailEventInfo\n      | TraceEventInfo\n      | HibernatableWebSocketEventInfo\n      | CustomEventInfo;\n  }\n  interface Outcome {\n    readonly type: \"outcome\";\n    readonly outcome: EventOutcome;\n    readonly cpuTime: number;\n    readonly wallTime: number;\n  }\n  interface SpanOpen {\n    readonly type: \"spanOpen\";\n    readonly name: string;\n    // id for the span being opened by this SpanOpen event.\n    readonly spanId: string;\n    readonly info?: FetchEventInfo | JsRpcEventInfo | Attributes;\n  }\n  interface SpanClose {\n    readonly type: \"spanClose\";\n    readonly outcome: EventOutcome;\n  }\n  interface DiagnosticChannelEvent {\n    readonly type: \"diagnosticChannel\";\n    readonly channel: string;\n    readonly message: any;\n  }\n  interface Exception {\n    readonly type: \"exception\";\n    readonly name: string;\n    readonly message: string;\n    readonly stack?: string;\n  }\n  interface Log {\n    readonly type: \"log\";\n    readonly level: \"debug\" | \"error\" | \"info\" | \"log\" | \"warn\";\n    readonly message: object;\n  }\n  interface DroppedEventsDiagnostic {\n    readonly diagnosticsType: \"droppedEvents\";\n    readonly count: number;\n  }\n  interface StreamDiagnostic {\n    readonly type: \"streamDiagnostic\";\n    // To add new diagnostic types, define a new interface and add it to this union type.\n    readonly diagnostic: DroppedEventsDiagnostic;\n  }\n  // This marks the worker handler return information.\n  // This is separate from Outcome because the worker invocation can live for a long time after\n  // returning. For example - Websockets that return an http upgrade response but then continue\n  // streaming information or SSE http connections.\n  interface Return {\n    readonly type: \"return\";\n    readonly info?: FetchResponseInfo;\n  }\n  interface Attribute {\n    readonly name: string;\n    readonly value:\n      | string\n      | string[]\n      | boolean\n      | boolean[]\n      | number\n      | number[]\n      | bigint\n      | bigint[];\n  }\n  interface Attributes {\n    readonly type: \"attributes\";\n    readonly info: Attribute[];\n  }\n  type EventType =\n    | Onset\n    | Outcome\n    | SpanOpen\n    | SpanClose\n    | DiagnosticChannelEvent\n    | Exception\n    | Log\n    | StreamDiagnostic\n    | Return\n    | Attributes;\n  // Context in which this trace event lives.\n  interface SpanContext {\n    // Single id for the entire top-level invocation\n    // This should be a new traceId for the first worker stage invoked in the eyeball request and then\n    // same-account service-bindings should reuse the same traceId but cross-account service-bindings\n    // should use a new traceId.\n    readonly traceId: string;\n    // spanId in which this event is handled\n    // for Onset and SpanOpen events this would be the parent span id\n    // for Outcome and SpanClose these this would be the span id of the opening Onset and SpanOpen events\n    // For Hibernate and Mark this would be the span under which they were emitted.\n    // spanId is not set ONLY if:\n    //  1. This is an Onset event\n    //  2. We are not inheriting any SpanContext. (e.g. this is a cross-account service binding or a new top-level invocation)\n    readonly spanId?: string;\n  }\n  interface TailEvent<Event extends EventType> {\n    // invocation id of the currently invoked worker stage.\n    // invocation id will always be unique to every Onset event and will be the same until the Outcome event.\n    readonly invocationId: string;\n    // Inherited spanContext for this event.\n    readonly spanContext: SpanContext;\n    readonly timestamp: Date;\n    readonly sequence: number;\n    readonly event: Event;\n  }\n  type TailEventHandler<Event extends EventType = EventType> = (\n    event: TailEvent<Event>,\n  ) => void | Promise<void>;\n  type TailEventHandlerObject = {\n    outcome?: TailEventHandler<Outcome>;\n    spanOpen?: TailEventHandler<SpanOpen>;\n    spanClose?: TailEventHandler<SpanClose>;\n    diagnosticChannel?: TailEventHandler<DiagnosticChannelEvent>;\n    exception?: TailEventHandler<Exception>;\n    log?: TailEventHandler<Log>;\n    return?: TailEventHandler<Return>;\n    attributes?: TailEventHandler<Attributes>;\n  };\n  type TailEventHandlerType = TailEventHandler | TailEventHandlerObject;\n}\n// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n/**\n * Data types supported for holding vector metadata.\n */\ntype VectorizeVectorMetadataValue = string | number | boolean | string[];\n/**\n * Additional information to associate with a vector.\n */\ntype VectorizeVectorMetadata =\n  | VectorizeVectorMetadataValue\n  | Record<string, VectorizeVectorMetadataValue>;\ntype VectorFloatArray = Float32Array | Float64Array;\ninterface VectorizeError {\n  code?: number;\n  error: string;\n}\n/**\n * Comparison logic/operation to use for metadata filtering.\n *\n * This list is expected to grow as support for more operations are released.\n */\ntype VectorizeVectorMetadataFilterOp =\n  | \"$eq\"\n  | \"$ne\"\n  | \"$lt\"\n  | \"$lte\"\n  | \"$gt\"\n  | \"$gte\";\ntype VectorizeVectorMetadataFilterCollectionOp = \"$in\" | \"$nin\";\n/**\n * Filter criteria for vector metadata used to limit the retrieved query result set.\n */\ntype VectorizeVectorMetadataFilter = {\n  [field: string]:\n    | Exclude<VectorizeVectorMetadataValue, string[]>\n    | null\n    | {\n        [Op in VectorizeVectorMetadataFilterOp]?: Exclude<\n          VectorizeVectorMetadataValue,\n          string[]\n        > | null;\n      }\n    | {\n        [Op in VectorizeVectorMetadataFilterCollectionOp]?: Exclude<\n          VectorizeVectorMetadataValue,\n          string[]\n        >[];\n      };\n};\n/**\n * Supported distance metrics for an index.\n * Distance metrics determine how other \"similar\" vectors are determined.\n */\ntype VectorizeDistanceMetric = \"euclidean\" | \"cosine\" | \"dot-product\";\n/**\n * Metadata return levels for a Vectorize query.\n *\n * Default to \"none\".\n *\n * @property all      Full metadata for the vector return set, including all fields (including those un-indexed) without truncation. This is a more expensive retrieval, as it requires additional fetching & reading of un-indexed data.\n * @property indexed  Return all metadata fields configured for indexing in the vector return set. This level of retrieval is \"free\" in that no additional overhead is incurred returning this data. However, note that indexed metadata is subject to truncation (especially for larger strings).\n * @property none     No indexed metadata will be returned.\n */\ntype VectorizeMetadataRetrievalLevel = \"all\" | \"indexed\" | \"none\";\ninterface VectorizeQueryOptions {\n  topK?: number;\n  namespace?: string;\n  returnValues?: boolean;\n  returnMetadata?: boolean | VectorizeMetadataRetrievalLevel;\n  filter?: VectorizeVectorMetadataFilter;\n}\n/**\n * Information about the configuration of an index.\n */\ntype VectorizeIndexConfig =\n  | {\n      dimensions: number;\n      metric: VectorizeDistanceMetric;\n    }\n  | {\n      preset: string; // keep this generic, as we'll be adding more presets in the future and this is only in a read capacity\n    };\n/**\n * Metadata about an existing index.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link VectorizeIndexInfo} for its post-beta equivalent.\n */\ninterface VectorizeIndexDetails {\n  /** The unique ID of the index */\n  readonly id: string;\n  /** The name of the index. */\n  name: string;\n  /** (optional) A human readable description for the index. */\n  description?: string;\n  /** The index configuration, including the dimension size and distance metric. */\n  config: VectorizeIndexConfig;\n  /** The number of records containing vectors within the index. */\n  vectorsCount: number;\n}\n/**\n * Metadata about an existing index.\n */\ninterface VectorizeIndexInfo {\n  /** The number of records containing vectors within the index. */\n  vectorCount: number;\n  /** Number of dimensions the index has been configured for. */\n  dimensions: number;\n  /** ISO 8601 datetime of the last processed mutation on in the index. All changes before this mutation will be reflected in the index state. */\n  processedUpToDatetime: number;\n  /** UUIDv4 of the last mutation processed by the index. All changes before this mutation will be reflected in the index state. */\n  processedUpToMutation: number;\n}\n/**\n * Represents a single vector value set along with its associated metadata.\n */\ninterface VectorizeVector {\n  /** The ID for the vector. This can be user-defined, and must be unique. It should uniquely identify the object, and is best set based on the ID of what the vector represents. */\n  id: string;\n  /** The vector values */\n  values: VectorFloatArray | number[];\n  /** The namespace this vector belongs to. */\n  namespace?: string;\n  /** Metadata associated with the vector. Includes the values of other fields and potentially additional details. */\n  metadata?: Record<string, VectorizeVectorMetadata>;\n}\n/**\n * Represents a matched vector for a query along with its score and (if specified) the matching vector information.\n */\ntype VectorizeMatch = Pick<Partial<VectorizeVector>, \"values\"> &\n  Omit<VectorizeVector, \"values\"> & {\n    /** The score or rank for similarity, when returned as a result */\n    score: number;\n  };\n/**\n * A set of matching {@link VectorizeMatch} for a particular query.\n */\ninterface VectorizeMatches {\n  matches: VectorizeMatch[];\n  count: number;\n}\n/**\n * Results of an operation that performed a mutation on a set of vectors.\n * Here, `ids` is a list of vectors that were successfully processed.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link VectorizeAsyncMutation} for its post-beta equivalent.\n */\ninterface VectorizeVectorMutation {\n  /* List of ids of vectors that were successfully processed. */\n  ids: string[];\n  /* Total count of the number of processed vectors. */\n  count: number;\n}\n/**\n * Result type indicating a mutation on the Vectorize Index.\n * Actual mutations are processed async where the `mutationId` is the unique identifier for the operation.\n */\ninterface VectorizeAsyncMutation {\n  /** The unique identifier for the async mutation operation containing the changeset. */\n  mutationId: string;\n}\n/**\n * A Vectorize Vector Search Index for querying vectors/embeddings.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link Vectorize} for its new implementation.\n */\ndeclare abstract class VectorizeIndex {\n  /**\n   * Get information about the currently bound index.\n   * @returns A promise that resolves with information about the current index.\n   */\n  public describe(): Promise<VectorizeIndexDetails>;\n  /**\n   * Use the provided vector to perform a similarity search across the index.\n   * @param vector Input vector that will be used to drive the similarity search.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  public query(\n    vector: VectorFloatArray | number[],\n    options?: VectorizeQueryOptions,\n  ): Promise<VectorizeMatches>;\n  /**\n   * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown.\n   * @param vectors List of vectors that will be inserted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed.\n   */\n  public insert(vectors: VectorizeVector[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values.\n   * @param vectors List of vectors that will be upserted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed.\n   */\n  public upsert(vectors: VectorizeVector[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Delete a list of vectors with a matching id.\n   * @param ids List of vector ids that should be deleted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed (and thus deleted).\n   */\n  public deleteByIds(ids: string[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Get a list of vectors with a matching id.\n   * @param ids List of vector ids that should be returned.\n   * @returns A promise that resolves with the raw unscored vectors matching the id set.\n   */\n  public getByIds(ids: string[]): Promise<VectorizeVector[]>;\n}\n/**\n * A Vectorize Vector Search Index for querying vectors/embeddings.\n *\n * Mutations in this version are async, returning a mutation id.\n */\ndeclare abstract class Vectorize {\n  /**\n   * Get information about the currently bound index.\n   * @returns A promise that resolves with information about the current index.\n   */\n  public describe(): Promise<VectorizeIndexInfo>;\n  /**\n   * Use the provided vector to perform a similarity search across the index.\n   * @param vector Input vector that will be used to drive the similarity search.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  public query(\n    vector: VectorFloatArray | number[],\n    options?: VectorizeQueryOptions,\n  ): Promise<VectorizeMatches>;\n  /**\n   * Use the provided vector-id to perform a similarity search across the index.\n   * @param vectorId Id for a vector in the index against which the index should be queried.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  public queryById(\n    vectorId: string,\n    options?: VectorizeQueryOptions,\n  ): Promise<VectorizeMatches>;\n  /**\n   * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown.\n   * @param vectors List of vectors that will be inserted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the insert changeset.\n   */\n  public insert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values.\n   * @param vectors List of vectors that will be upserted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the upsert changeset.\n   */\n  public upsert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Delete a list of vectors with a matching id.\n   * @param ids List of vector ids that should be deleted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the delete changeset.\n   */\n  public deleteByIds(ids: string[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Get a list of vectors with a matching id.\n   * @param ids List of vector ids that should be returned.\n   * @returns A promise that resolves with the raw unscored vectors matching the id set.\n   */\n  public getByIds(ids: string[]): Promise<VectorizeVector[]>;\n}\n/**\n * The interface for \"version_metadata\" binding\n * providing metadata about the Worker Version using this binding.\n */\ntype WorkerVersionMetadata = {\n  /** The ID of the Worker Version using this binding */\n  id: string;\n  /** The tag of the Worker Version using this binding */\n  tag: string;\n  /** The timestamp of when the Worker Version was uploaded */\n  timestamp: string;\n};\ninterface DynamicDispatchLimits {\n  /**\n   * Limit CPU time in milliseconds.\n   */\n  cpuMs?: number;\n  /**\n   * Limit number of subrequests.\n   */\n  subRequests?: number;\n}\ninterface DynamicDispatchOptions {\n  /**\n   * Limit resources of invoked Worker script.\n   */\n  limits?: DynamicDispatchLimits;\n  /**\n   * Arguments for outbound Worker script, if configured.\n   */\n  outbound?: {\n    [key: string]: any;\n  };\n}\ninterface DispatchNamespace {\n  /**\n   * @param name Name of the Worker script.\n   * @param args Arguments to Worker script.\n   * @param options Options for Dynamic Dispatch invocation.\n   * @returns A Fetcher object that allows you to send requests to the Worker script.\n   * @throws If the Worker script does not exist in this dispatch namespace, an error will be thrown.\n   */\n  get(\n    name: string,\n    args?: {\n      [key: string]: any;\n    },\n    options?: DynamicDispatchOptions,\n  ): Fetcher;\n}\ndeclare module \"cloudflare:workflows\" {\n  /**\n   * NonRetryableError allows for a user to throw a fatal error\n   * that makes a Workflow instance fail immediately without triggering a retry\n   */\n  export class NonRetryableError extends Error {\n    public constructor(message: string, name?: string);\n  }\n}\ndeclare abstract class Workflow<PARAMS = unknown> {\n  /**\n   * Get a handle to an existing instance of the Workflow.\n   * @param id Id for the instance of this Workflow\n   * @returns A promise that resolves with a handle for the Instance\n   */\n  public get(id: string): Promise<WorkflowInstance>;\n  /**\n   * Create a new instance and return a handle to it. If a provided id exists, an error will be thrown.\n   * @param options Options when creating an instance including id and params\n   * @returns A promise that resolves with a handle for the Instance\n   */\n  public create(\n    options?: WorkflowInstanceCreateOptions<PARAMS>,\n  ): Promise<WorkflowInstance>;\n  /**\n   * Create a batch of instances and return handle for all of them. If a provided id exists, an error will be thrown.\n   * `createBatch` is limited at 100 instances at a time or when the RPC limit for the batch (1MiB) is reached.\n   * @param batch List of Options when creating an instance including name and params\n   * @returns A promise that resolves with a list of handles for the created instances.\n   */\n  public createBatch(\n    batch: WorkflowInstanceCreateOptions<PARAMS>[],\n  ): Promise<WorkflowInstance[]>;\n}\ntype WorkflowDurationLabel =\n  | \"second\"\n  | \"minute\"\n  | \"hour\"\n  | \"day\"\n  | \"week\"\n  | \"month\"\n  | \"year\";\ntype WorkflowSleepDuration =\n  | `${number} ${WorkflowDurationLabel}${\"s\" | \"\"}`\n  | number;\ntype WorkflowRetentionDuration = WorkflowSleepDuration;\ninterface WorkflowInstanceCreateOptions<PARAMS = unknown> {\n  /**\n   * An id for your Workflow instance. Must be unique within the Workflow.\n   */\n  id?: string;\n  /**\n   * The event payload the Workflow instance is triggered with\n   */\n  params?: PARAMS;\n  /**\n   * The retention policy for Workflow instance.\n   * Defaults to the maximum retention period available for the owner's account.\n   */\n  retention?: {\n    successRetention?: WorkflowRetentionDuration;\n    errorRetention?: WorkflowRetentionDuration;\n  };\n}\ntype InstanceStatus = {\n  status:\n    | \"queued\" // means that instance is waiting to be started (see concurrency limits)\n    | \"running\"\n    | \"paused\"\n    | \"errored\"\n    | \"terminated\" // user terminated the instance while it was running\n    | \"complete\"\n    | \"waiting\" // instance is hibernating and waiting for sleep or event to finish\n    | \"waitingForPause\" // instance is finishing the current work to pause\n    | \"unknown\";\n  error?: {\n    name: string;\n    message: string;\n  };\n  output?: unknown;\n};\ninterface WorkflowError {\n  code?: number;\n  message: string;\n}\ndeclare abstract class WorkflowInstance {\n  public id: string;\n  /**\n   * Pause the instance.\n   */\n  public pause(): Promise<void>;\n  /**\n   * Resume the instance. If it is already running, an error will be thrown.\n   */\n  public resume(): Promise<void>;\n  /**\n   * Terminate the instance. If it is errored, terminated or complete, an error will be thrown.\n   */\n  public terminate(): Promise<void>;\n  /**\n   * Restart the instance.\n   */\n  public restart(): Promise<void>;\n  /**\n   * Returns the current status of the instance.\n   */\n  public status(): Promise<InstanceStatus>;\n  /**\n   * Send an event to this instance.\n   */\n  public sendEvent({\n    type,\n    payload,\n  }: {\n    type: string;\n    payload: unknown;\n  }): Promise<void>;\n}\n"
  },
  {
    "path": "types/generated-snapshot/experimental/index.ts",
    "content": "/*! *****************************************************************************\nCopyright (c) Cloudflare. All rights reserved.\nCopyright (c) Microsoft Corporation. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at http://www.apache.org/licenses/LICENSE-2.0\nTHIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\nKIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED\nWARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,\nMERCHANTABLITY OR NON-INFRINGEMENT.\nSee the Apache Version 2.0 License for specific language governing permissions\nand limitations under the License.\n***************************************************************************** */\n/* eslint-disable */\n// noinspection JSUnusedGlobalSymbols\nexport declare var onmessage: never;\n/**\n * The **`DOMException`** interface represents an abnormal event (called an **exception**) that occurs as a result of calling a method or accessing a property of a web API.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException)\n */\nexport declare class DOMException extends Error {\n  constructor(message?: string, name?: string);\n  /**\n   * The **`message`** read-only property of the a message or description associated with the given error name.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/message)\n   */\n  readonly message: string;\n  /**\n   * The **`name`** read-only property of the one of the strings associated with an error name.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/name)\n   */\n  readonly name: string;\n  /**\n   * The **`code`** read-only property of the DOMException interface returns one of the legacy error code constants, or `0` if none match.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/code)\n   */\n  readonly code: number;\n  static readonly INDEX_SIZE_ERR: number;\n  static readonly DOMSTRING_SIZE_ERR: number;\n  static readonly HIERARCHY_REQUEST_ERR: number;\n  static readonly WRONG_DOCUMENT_ERR: number;\n  static readonly INVALID_CHARACTER_ERR: number;\n  static readonly NO_DATA_ALLOWED_ERR: number;\n  static readonly NO_MODIFICATION_ALLOWED_ERR: number;\n  static readonly NOT_FOUND_ERR: number;\n  static readonly NOT_SUPPORTED_ERR: number;\n  static readonly INUSE_ATTRIBUTE_ERR: number;\n  static readonly INVALID_STATE_ERR: number;\n  static readonly SYNTAX_ERR: number;\n  static readonly INVALID_MODIFICATION_ERR: number;\n  static readonly NAMESPACE_ERR: number;\n  static readonly INVALID_ACCESS_ERR: number;\n  static readonly VALIDATION_ERR: number;\n  static readonly TYPE_MISMATCH_ERR: number;\n  static readonly SECURITY_ERR: number;\n  static readonly NETWORK_ERR: number;\n  static readonly ABORT_ERR: number;\n  static readonly URL_MISMATCH_ERR: number;\n  static readonly QUOTA_EXCEEDED_ERR: number;\n  static readonly TIMEOUT_ERR: number;\n  static readonly INVALID_NODE_TYPE_ERR: number;\n  static readonly DATA_CLONE_ERR: number;\n  get stack(): any;\n  set stack(value: any);\n}\nexport type WorkerGlobalScopeEventMap = {\n  fetch: FetchEvent;\n  scheduled: ScheduledEvent;\n  queue: QueueEvent;\n  unhandledrejection: PromiseRejectionEvent;\n  rejectionhandled: PromiseRejectionEvent;\n};\nexport declare abstract class WorkerGlobalScope extends EventTarget<WorkerGlobalScopeEventMap> {\n  EventTarget: typeof EventTarget;\n}\n/* The **`console`** object provides access to the debugging console (e.g., the Web console in Firefox). *\n * The **`console`** object provides access to the debugging console (e.g., the Web console in Firefox).\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console)\n */\nexport interface Console {\n  \"assert\"(condition?: boolean, ...data: any[]): void;\n  /**\n   * The **`console.clear()`** static method clears the console if possible.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/clear_static)\n   */\n  clear(): void;\n  /**\n   * The **`console.count()`** static method logs the number of times that this particular call to `count()` has been called.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/count_static)\n   */\n  count(label?: string): void;\n  /**\n   * The **`console.countReset()`** static method resets counter used with console/count_static.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/countReset_static)\n   */\n  countReset(label?: string): void;\n  /**\n   * The **`console.debug()`** static method outputs a message to the console at the 'debug' log level.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/debug_static)\n   */\n  debug(...data: any[]): void;\n  /**\n   * The **`console.dir()`** static method displays a list of the properties of the specified JavaScript object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dir_static)\n   */\n  dir(item?: any, options?: any): void;\n  /**\n   * The **`console.dirxml()`** static method displays an interactive tree of the descendant elements of the specified XML/HTML element.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dirxml_static)\n   */\n  dirxml(...data: any[]): void;\n  /**\n   * The **`console.error()`** static method outputs a message to the console at the 'error' log level.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/error_static)\n   */\n  error(...data: any[]): void;\n  /**\n   * The **`console.group()`** static method creates a new inline group in the Web console log, causing any subsequent console messages to be indented by an additional level, until console/groupEnd_static is called.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/group_static)\n   */\n  group(...data: any[]): void;\n  /**\n   * The **`console.groupCollapsed()`** static method creates a new inline group in the console.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupCollapsed_static)\n   */\n  groupCollapsed(...data: any[]): void;\n  /**\n   * The **`console.groupEnd()`** static method exits the current inline group in the console.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupEnd_static)\n   */\n  groupEnd(): void;\n  /**\n   * The **`console.info()`** static method outputs a message to the console at the 'info' log level.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/info_static)\n   */\n  info(...data: any[]): void;\n  /**\n   * The **`console.log()`** static method outputs a message to the console.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)\n   */\n  log(...data: any[]): void;\n  /**\n   * The **`console.table()`** static method displays tabular data as a table.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/table_static)\n   */\n  table(tabularData?: any, properties?: string[]): void;\n  /**\n   * The **`console.time()`** static method starts a timer you can use to track how long an operation takes.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/time_static)\n   */\n  time(label?: string): void;\n  /**\n   * The **`console.timeEnd()`** static method stops a timer that was previously started by calling console/time_static.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeEnd_static)\n   */\n  timeEnd(label?: string): void;\n  /**\n   * The **`console.timeLog()`** static method logs the current value of a timer that was previously started by calling console/time_static.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeLog_static)\n   */\n  timeLog(label?: string, ...data: any[]): void;\n  timeStamp(label?: string): void;\n  /**\n   * The **`console.trace()`** static method outputs a stack trace to the console.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/trace_static)\n   */\n  trace(...data: any[]): void;\n  /**\n   * The **`console.warn()`** static method outputs a warning message to the console at the 'warning' log level.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/warn_static)\n   */\n  warn(...data: any[]): void;\n}\nexport declare const console: Console;\nexport type BufferSource = ArrayBufferView | ArrayBuffer;\nexport type TypedArray =\n  | Int8Array\n  | Uint8Array\n  | Uint8ClampedArray\n  | Int16Array\n  | Uint16Array\n  | Int32Array\n  | Uint32Array\n  | Float32Array\n  | Float64Array\n  | BigInt64Array\n  | BigUint64Array;\nexport declare namespace WebAssembly {\n  class CompileError extends Error {\n    constructor(message?: string);\n  }\n  class RuntimeError extends Error {\n    constructor(message?: string);\n  }\n  type ValueType =\n    | \"anyfunc\"\n    | \"externref\"\n    | \"f32\"\n    | \"f64\"\n    | \"i32\"\n    | \"i64\"\n    | \"v128\";\n  interface GlobalDescriptor {\n    value: ValueType;\n    mutable?: boolean;\n  }\n  class Global {\n    constructor(descriptor: GlobalDescriptor, value?: any);\n    value: any;\n    valueOf(): any;\n  }\n  type ImportValue = ExportValue | number;\n  type ModuleImports = Record<string, ImportValue>;\n  type Imports = Record<string, ModuleImports>;\n  type ExportValue = Function | Global | Memory | Table;\n  type Exports = Record<string, ExportValue>;\n  class Instance {\n    constructor(module: Module, imports?: Imports);\n    readonly exports: Exports;\n  }\n  interface MemoryDescriptor {\n    initial: number;\n    maximum?: number;\n    shared?: boolean;\n  }\n  class Memory {\n    constructor(descriptor: MemoryDescriptor);\n    readonly buffer: ArrayBuffer;\n    grow(delta: number): number;\n  }\n  type ImportExportKind = \"function\" | \"global\" | \"memory\" | \"table\";\n  interface ModuleExportDescriptor {\n    kind: ImportExportKind;\n    name: string;\n  }\n  interface ModuleImportDescriptor {\n    kind: ImportExportKind;\n    module: string;\n    name: string;\n  }\n  abstract class Module {\n    static customSections(module: Module, sectionName: string): ArrayBuffer[];\n    static exports(module: Module): ModuleExportDescriptor[];\n    static imports(module: Module): ModuleImportDescriptor[];\n  }\n  type TableKind = \"anyfunc\" | \"externref\";\n  interface TableDescriptor {\n    element: TableKind;\n    initial: number;\n    maximum?: number;\n  }\n  class Table {\n    constructor(descriptor: TableDescriptor, value?: any);\n    readonly length: number;\n    get(index: number): any;\n    grow(delta: number, value?: any): number;\n    set(index: number, value?: any): void;\n  }\n  function instantiate(module: Module, imports?: Imports): Promise<Instance>;\n  function validate(bytes: BufferSource): boolean;\n}\n/**\n * The **`ServiceWorkerGlobalScope`** interface of the Service Worker API represents the global execution context of a service worker.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope)\n */\nexport interface ServiceWorkerGlobalScope extends WorkerGlobalScope {\n  DOMException: typeof DOMException;\n  WorkerGlobalScope: typeof WorkerGlobalScope;\n  EventTarget: typeof EventTarget;\n  btoa(data: string): string;\n  atob(data: string): string;\n  setTimeout(callback: (...args: any[]) => void, msDelay?: number): number;\n  setTimeout<Args extends any[]>(\n    callback: (...args: Args) => void,\n    msDelay?: number,\n    ...args: Args\n  ): number;\n  clearTimeout(timeoutId: number | null): void;\n  setInterval(callback: (...args: any[]) => void, msDelay?: number): number;\n  setInterval<Args extends any[]>(\n    callback: (...args: Args) => void,\n    msDelay?: number,\n    ...args: Args\n  ): number;\n  clearInterval(timeoutId: number | null): void;\n  queueMicrotask(task: Function): void;\n  structuredClone<T>(value: T, options?: StructuredSerializeOptions): T;\n  reportError(error: any): void;\n  fetch(\n    input: RequestInfo | URL,\n    init?: RequestInit<RequestInitCfProperties>,\n  ): Promise<Response>;\n  self: ServiceWorkerGlobalScope;\n  crypto: Crypto;\n  caches: CacheStorage;\n  scheduler: Scheduler;\n  performance: Performance;\n  Cloudflare: Cloudflare;\n  readonly origin: string;\n  Event: typeof Event;\n  ExtendableEvent: typeof ExtendableEvent;\n  CustomEvent: typeof CustomEvent;\n  PromiseRejectionEvent: typeof PromiseRejectionEvent;\n  FetchEvent: typeof FetchEvent;\n  TailEvent: typeof TailEvent;\n  TraceEvent: typeof TailEvent;\n  ScheduledEvent: typeof ScheduledEvent;\n  MessageEvent: typeof MessageEvent;\n  CloseEvent: typeof CloseEvent;\n  ReadableStreamDefaultReader: typeof ReadableStreamDefaultReader;\n  ReadableStreamBYOBReader: typeof ReadableStreamBYOBReader;\n  ReadableStream: typeof ReadableStream;\n  WritableStream: typeof WritableStream;\n  WritableStreamDefaultWriter: typeof WritableStreamDefaultWriter;\n  TransformStream: typeof TransformStream;\n  ByteLengthQueuingStrategy: typeof ByteLengthQueuingStrategy;\n  CountQueuingStrategy: typeof CountQueuingStrategy;\n  ErrorEvent: typeof ErrorEvent;\n  MessageChannel: typeof MessageChannel;\n  MessagePort: typeof MessagePort;\n  FileSystemHandle: typeof FileSystemHandle;\n  FileSystemFileHandle: typeof FileSystemFileHandle;\n  FileSystemDirectoryHandle: typeof FileSystemDirectoryHandle;\n  FileSystemWritableFileStream: typeof FileSystemWritableFileStream;\n  StorageManager: typeof StorageManager;\n  EventSource: typeof EventSource;\n  ReadableStreamBYOBRequest: typeof ReadableStreamBYOBRequest;\n  ReadableStreamDefaultController: typeof ReadableStreamDefaultController;\n  ReadableByteStreamController: typeof ReadableByteStreamController;\n  WritableStreamDefaultController: typeof WritableStreamDefaultController;\n  TransformStreamDefaultController: typeof TransformStreamDefaultController;\n  CompressionStream: typeof CompressionStream;\n  DecompressionStream: typeof DecompressionStream;\n  TextEncoderStream: typeof TextEncoderStream;\n  TextDecoderStream: typeof TextDecoderStream;\n  Headers: typeof Headers;\n  Body: typeof Body;\n  Request: typeof Request;\n  Response: typeof Response;\n  WebSocket: typeof WebSocket;\n  WebSocketPair: typeof WebSocketPair;\n  WebSocketRequestResponsePair: typeof WebSocketRequestResponsePair;\n  AbortController: typeof AbortController;\n  AbortSignal: typeof AbortSignal;\n  TextDecoder: typeof TextDecoder;\n  TextEncoder: typeof TextEncoder;\n  navigator: Navigator;\n  Navigator: typeof Navigator;\n  URL: typeof URL;\n  URLSearchParams: typeof URLSearchParams;\n  URLPattern: typeof URLPattern;\n  Blob: typeof Blob;\n  File: typeof File;\n  FormData: typeof FormData;\n  Crypto: typeof Crypto;\n  SubtleCrypto: typeof SubtleCrypto;\n  CryptoKey: typeof CryptoKey;\n  CacheStorage: typeof CacheStorage;\n  Cache: typeof Cache;\n  FixedLengthStream: typeof FixedLengthStream;\n  IdentityTransformStream: typeof IdentityTransformStream;\n  HTMLRewriter: typeof HTMLRewriter;\n  Performance: typeof Performance;\n  PerformanceEntry: typeof PerformanceEntry;\n  PerformanceMark: typeof PerformanceMark;\n  PerformanceMeasure: typeof PerformanceMeasure;\n  PerformanceResourceTiming: typeof PerformanceResourceTiming;\n  PerformanceObserver: typeof PerformanceObserver;\n  PerformanceObserverEntryList: typeof PerformanceObserverEntryList;\n}\nexport declare function addEventListener<\n  Type extends keyof WorkerGlobalScopeEventMap,\n>(\n  type: Type,\n  handler: EventListenerOrEventListenerObject<WorkerGlobalScopeEventMap[Type]>,\n  options?: EventTargetAddEventListenerOptions | boolean,\n): void;\nexport declare function removeEventListener<\n  Type extends keyof WorkerGlobalScopeEventMap,\n>(\n  type: Type,\n  handler: EventListenerOrEventListenerObject<WorkerGlobalScopeEventMap[Type]>,\n  options?: EventTargetEventListenerOptions | boolean,\n): void;\n/**\n * The **`dispatchEvent()`** method of the EventTarget sends an Event to the object, (synchronously) invoking the affected event listeners in the appropriate order.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent)\n */\nexport declare function dispatchEvent(\n  event: WorkerGlobalScopeEventMap[keyof WorkerGlobalScopeEventMap],\n): boolean;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/btoa) */\nexport declare function btoa(data: string): string;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/atob) */\nexport declare function atob(data: string): string;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */\nexport declare function setTimeout(\n  callback: (...args: any[]) => void,\n  msDelay?: number,\n): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */\nexport declare function setTimeout<Args extends any[]>(\n  callback: (...args: Args) => void,\n  msDelay?: number,\n  ...args: Args\n): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearTimeout) */\nexport declare function clearTimeout(timeoutId: number | null): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */\nexport declare function setInterval(\n  callback: (...args: any[]) => void,\n  msDelay?: number,\n): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */\nexport declare function setInterval<Args extends any[]>(\n  callback: (...args: Args) => void,\n  msDelay?: number,\n  ...args: Args\n): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearInterval) */\nexport declare function clearInterval(timeoutId: number | null): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/queueMicrotask) */\nexport declare function queueMicrotask(task: Function): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/structuredClone) */\nexport declare function structuredClone<T>(\n  value: T,\n  options?: StructuredSerializeOptions,\n): T;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/reportError) */\nexport declare function reportError(error: any): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch) */\nexport declare function fetch(\n  input: RequestInfo | URL,\n  init?: RequestInit<RequestInitCfProperties>,\n): Promise<Response>;\nexport declare const self: ServiceWorkerGlobalScope;\n/**\n * The Web Crypto API provides a set of low-level functions for common cryptographic tasks.\n * The Workers runtime implements the full surface of this API, but with some differences in\n * the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms)\n * compared to those implemented in most browsers.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/)\n */\nexport declare const crypto: Crypto;\n/**\n * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)\n */\nexport declare const caches: CacheStorage;\nexport declare const scheduler: Scheduler;\n/**\n * The Workers runtime supports a subset of the Performance API, used to measure timing and performance,\n * as well as timing of subrequests and other operations.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/)\n */\nexport declare const performance: Performance;\nexport declare const Cloudflare: Cloudflare;\nexport declare const origin: string;\nexport declare const navigator: Navigator;\nexport interface TestController {}\nexport interface ExecutionContext<Props = unknown> {\n  waitUntil(promise: Promise<any>): void;\n  passThroughOnException(): void;\n  readonly exports: Cloudflare.Exports;\n  readonly props: Props;\n  readonly version?: {\n    readonly metadata?: {\n      readonly id: string;\n    };\n    readonly cohort?: string;\n    readonly key?: string;\n    readonly override?: string;\n  };\n  abort(reason?: any): void;\n}\nexport type ExportedHandlerFetchHandler<\n  Env = unknown,\n  CfHostMetadata = unknown,\n  Props = unknown,\n> = (\n  request: Request<CfHostMetadata, IncomingRequestCfProperties<CfHostMetadata>>,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => Response | Promise<Response>;\nexport type ExportedHandlerTailHandler<Env = unknown, Props = unknown> = (\n  events: TraceItem[],\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\nexport type ExportedHandlerTraceHandler<Env = unknown, Props = unknown> = (\n  traces: TraceItem[],\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\nexport type ExportedHandlerTailStreamHandler<Env = unknown, Props = unknown> = (\n  event: TailStream.TailEvent<TailStream.Onset>,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => TailStream.TailEventHandlerType | Promise<TailStream.TailEventHandlerType>;\nexport type ExportedHandlerScheduledHandler<Env = unknown, Props = unknown> = (\n  controller: ScheduledController,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\nexport type ExportedHandlerQueueHandler<\n  Env = unknown,\n  Message = unknown,\n  Props = unknown,\n> = (\n  batch: MessageBatch<Message>,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\nexport type ExportedHandlerTestHandler<Env = unknown, Props = unknown> = (\n  controller: TestController,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\nexport interface ExportedHandler<\n  Env = unknown,\n  QueueHandlerMessage = unknown,\n  CfHostMetadata = unknown,\n  Props = unknown,\n> {\n  fetch?: ExportedHandlerFetchHandler<Env, CfHostMetadata, Props>;\n  tail?: ExportedHandlerTailHandler<Env, Props>;\n  trace?: ExportedHandlerTraceHandler<Env, Props>;\n  tailStream?: ExportedHandlerTailStreamHandler<Env, Props>;\n  scheduled?: ExportedHandlerScheduledHandler<Env, Props>;\n  test?: ExportedHandlerTestHandler<Env, Props>;\n  email?: EmailExportedHandler<Env, Props>;\n  queue?: ExportedHandlerQueueHandler<Env, QueueHandlerMessage, Props>;\n}\nexport interface StructuredSerializeOptions {\n  transfer?: any[];\n}\nexport declare abstract class Navigator {\n  sendBeacon(url: string, body?: BodyInit): boolean;\n  readonly userAgent: string;\n  readonly hardwareConcurrency: number;\n  readonly language: string;\n  readonly languages: string[];\n  readonly storage: StorageManager;\n}\nexport interface AlarmInvocationInfo {\n  readonly isRetry: boolean;\n  readonly retryCount: number;\n}\nexport interface Cloudflare {\n  readonly compatibilityFlags: Record<string, boolean>;\n}\nexport declare abstract class ColoLocalActorNamespace {\n  get(actorId: string): Fetcher;\n}\nexport interface DurableObject {\n  fetch(request: Request): Response | Promise<Response>;\n  alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise<void>;\n  webSocketMessage?(\n    ws: WebSocket,\n    message: string | ArrayBuffer,\n  ): void | Promise<void>;\n  webSocketClose?(\n    ws: WebSocket,\n    code: number,\n    reason: string,\n    wasClean: boolean,\n  ): void | Promise<void>;\n  webSocketError?(ws: WebSocket, error: unknown): void | Promise<void>;\n}\nexport type DurableObjectStub<\n  T extends Rpc.DurableObjectBranded | undefined = undefined,\n> = Fetcher<\n  T,\n  \"alarm\" | \"webSocketMessage\" | \"webSocketClose\" | \"webSocketError\"\n> & {\n  readonly id: DurableObjectId;\n  readonly name?: string;\n};\nexport interface DurableObjectId {\n  toString(): string;\n  equals(other: DurableObjectId): boolean;\n  readonly name?: string;\n  readonly jurisdiction?: string;\n}\nexport declare abstract class DurableObjectNamespace<\n  T extends Rpc.DurableObjectBranded | undefined = undefined,\n> {\n  newUniqueId(\n    options?: DurableObjectNamespaceNewUniqueIdOptions,\n  ): DurableObjectId;\n  idFromName(name: string): DurableObjectId;\n  idFromString(id: string): DurableObjectId;\n  get(\n    id: DurableObjectId,\n    options?: DurableObjectNamespaceGetDurableObjectOptions,\n  ): DurableObjectStub<T>;\n  getByName(\n    name: string,\n    options?: DurableObjectNamespaceGetDurableObjectOptions,\n  ): DurableObjectStub<T>;\n  getExisting(\n    id: DurableObjectId,\n    options?: DurableObjectNamespaceGetDurableObjectOptions,\n  ): DurableObjectStub<T>;\n  jurisdiction(\n    jurisdiction: DurableObjectJurisdiction,\n  ): DurableObjectNamespace<T>;\n}\nexport type DurableObjectJurisdiction = \"eu\" | \"fedramp\" | \"fedramp-high\";\nexport interface DurableObjectNamespaceNewUniqueIdOptions {\n  jurisdiction?: DurableObjectJurisdiction;\n}\nexport type DurableObjectLocationHint =\n  | \"wnam\"\n  | \"enam\"\n  | \"sam\"\n  | \"weur\"\n  | \"eeur\"\n  | \"apac\"\n  | \"oc\"\n  | \"afr\"\n  | \"me\";\nexport type DurableObjectRoutingMode = \"primary-only\";\nexport interface DurableObjectNamespaceGetDurableObjectOptions {\n  locationHint?: DurableObjectLocationHint;\n  routingMode?: DurableObjectRoutingMode;\n  version?: {\n    cohort?: string;\n  };\n}\nexport interface DurableObjectClass<\n  _T extends Rpc.DurableObjectBranded | undefined = undefined,\n> {}\nexport interface DurableObjectNamespaceGetDurableObjectOptionsVersionOptions {\n  cohort?: string;\n}\nexport interface DurableObjectState<Props = unknown> {\n  waitUntil(promise: Promise<any>): void;\n  readonly exports: Cloudflare.Exports;\n  readonly props: Props;\n  readonly id: DurableObjectId;\n  readonly storage: DurableObjectStorage;\n  container?: Container;\n  facets: DurableObjectFacets;\n  version?: DurableObjectStateVersion;\n  blockConcurrencyWhile<T>(callback: () => Promise<T>): Promise<T>;\n  acceptWebSocket(ws: WebSocket, tags?: string[]): void;\n  getWebSockets(tag?: string): WebSocket[];\n  setWebSocketAutoResponse(maybeReqResp?: WebSocketRequestResponsePair): void;\n  getWebSocketAutoResponse(): WebSocketRequestResponsePair | null;\n  getWebSocketAutoResponseTimestamp(ws: WebSocket): Date | null;\n  setHibernatableWebSocketEventTimeout(timeoutMs?: number): void;\n  getHibernatableWebSocketEventTimeout(): number | null;\n  getTags(ws: WebSocket): string[];\n  abort(reason?: string): void;\n}\nexport interface DurableObjectTransaction {\n  get<T = unknown>(\n    key: string,\n    options?: DurableObjectGetOptions,\n  ): Promise<T | undefined>;\n  get<T = unknown>(\n    keys: string[],\n    options?: DurableObjectGetOptions,\n  ): Promise<Map<string, T>>;\n  list<T = unknown>(\n    options?: DurableObjectListOptions,\n  ): Promise<Map<string, T>>;\n  put<T>(\n    key: string,\n    value: T,\n    options?: DurableObjectPutOptions,\n  ): Promise<void>;\n  put<T>(\n    entries: Record<string, T>,\n    options?: DurableObjectPutOptions,\n  ): Promise<void>;\n  delete(key: string, options?: DurableObjectPutOptions): Promise<boolean>;\n  delete(keys: string[], options?: DurableObjectPutOptions): Promise<number>;\n  rollback(): void;\n  getAlarm(options?: DurableObjectGetAlarmOptions): Promise<number | null>;\n  setAlarm(\n    scheduledTime: number | Date,\n    options?: DurableObjectSetAlarmOptions,\n  ): Promise<void>;\n  deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise<void>;\n}\nexport interface DurableObjectStorage {\n  get<T = unknown>(\n    key: string,\n    options?: DurableObjectGetOptions,\n  ): Promise<T | undefined>;\n  get<T = unknown>(\n    keys: string[],\n    options?: DurableObjectGetOptions,\n  ): Promise<Map<string, T>>;\n  list<T = unknown>(\n    options?: DurableObjectListOptions,\n  ): Promise<Map<string, T>>;\n  put<T>(\n    key: string,\n    value: T,\n    options?: DurableObjectPutOptions,\n  ): Promise<void>;\n  put<T>(\n    entries: Record<string, T>,\n    options?: DurableObjectPutOptions,\n  ): Promise<void>;\n  delete(key: string, options?: DurableObjectPutOptions): Promise<boolean>;\n  delete(keys: string[], options?: DurableObjectPutOptions): Promise<number>;\n  deleteAll(options?: DurableObjectPutOptions): Promise<void>;\n  transaction<T>(\n    closure: (txn: DurableObjectTransaction) => Promise<T>,\n  ): Promise<T>;\n  getAlarm(options?: DurableObjectGetAlarmOptions): Promise<number | null>;\n  setAlarm(\n    scheduledTime: number | Date,\n    options?: DurableObjectSetAlarmOptions,\n  ): Promise<void>;\n  deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise<void>;\n  sync(): Promise<void>;\n  sql: SqlStorage;\n  kv: SyncKvStorage;\n  transactionSync<T>(closure: () => T): T;\n  getCurrentBookmark(): Promise<string>;\n  getBookmarkForTime(timestamp: number | Date): Promise<string>;\n  onNextSessionRestoreBookmark(bookmark: string): Promise<string>;\n  waitForBookmark(bookmark: string): Promise<void>;\n  readonly primary?: DurableObjectStub;\n  ensureReplicas(): void;\n  disableReplicas(): void;\n}\nexport interface DurableObjectListOptions {\n  start?: string;\n  startAfter?: string;\n  end?: string;\n  prefix?: string;\n  reverse?: boolean;\n  limit?: number;\n  allowConcurrency?: boolean;\n  noCache?: boolean;\n}\nexport interface DurableObjectGetOptions {\n  allowConcurrency?: boolean;\n  noCache?: boolean;\n}\nexport interface DurableObjectGetAlarmOptions {\n  allowConcurrency?: boolean;\n}\nexport interface DurableObjectPutOptions {\n  allowConcurrency?: boolean;\n  allowUnconfirmed?: boolean;\n  noCache?: boolean;\n}\nexport interface DurableObjectSetAlarmOptions {\n  allowConcurrency?: boolean;\n  allowUnconfirmed?: boolean;\n}\nexport declare class WebSocketRequestResponsePair {\n  constructor(request: string, response: string);\n  get request(): string;\n  get response(): string;\n}\nexport interface DurableObjectFacets {\n  get<T extends Rpc.DurableObjectBranded | undefined = undefined>(\n    name: string,\n    getStartupOptions: () =>\n      | FacetStartupOptions<T>\n      | Promise<FacetStartupOptions<T>>,\n  ): Fetcher<T>;\n  abort(name: string, reason: any): void;\n  delete(name: string): void;\n}\nexport interface FacetStartupOptions<\n  T extends Rpc.DurableObjectBranded | undefined = undefined,\n> {\n  id?: DurableObjectId | string;\n  class: DurableObjectClass<T>;\n}\nexport interface DurableObjectStateVersion {\n  cohort?: string;\n}\nexport interface AnalyticsEngineDataset {\n  writeDataPoint(event?: AnalyticsEngineDataPoint): void;\n}\nexport interface AnalyticsEngineDataPoint {\n  indexes?: ((ArrayBuffer | string) | null)[];\n  doubles?: number[];\n  blobs?: ((ArrayBuffer | string) | null)[];\n}\n/**\n * The **`Event`** interface represents an event which takes place on an `EventTarget`.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event)\n */\nexport declare class Event {\n  constructor(type: string, init?: EventInit);\n  /**\n   * The **`type`** read-only property of the Event interface returns a string containing the event's type.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/type)\n   */\n  get type(): string;\n  /**\n   * The **`eventPhase`** read-only property of the being evaluated.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/eventPhase)\n   */\n  get eventPhase(): number;\n  /**\n   * The read-only **`composed`** property of the or not the event will propagate across the shadow DOM boundary into the standard DOM.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composed)\n   */\n  get composed(): boolean;\n  /**\n   * The **`bubbles`** read-only property of the Event interface indicates whether the event bubbles up through the DOM tree or not.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/bubbles)\n   */\n  get bubbles(): boolean;\n  /**\n   * The **`cancelable`** read-only property of the Event interface indicates whether the event can be canceled, and therefore prevented as if the event never happened.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelable)\n   */\n  get cancelable(): boolean;\n  /**\n   * The **`defaultPrevented`** read-only property of the Event interface returns a boolean value indicating whether or not the call to Event.preventDefault() canceled the event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/defaultPrevented)\n   */\n  get defaultPrevented(): boolean;\n  /**\n   * The Event property **`returnValue`** indicates whether the default action for this event has been prevented or not.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/returnValue)\n   */\n  get returnValue(): boolean;\n  /**\n   * The **`currentTarget`** read-only property of the Event interface identifies the element to which the event handler has been attached.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/currentTarget)\n   */\n  get currentTarget(): EventTarget | null;\n  /**\n   * The read-only **`target`** property of the dispatched.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/target)\n   */\n  get target(): EventTarget | undefined;\n  /**\n   * The deprecated **`Event.srcElement`** is an alias for the Event.target property.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/srcElement)\n   */\n  get srcElement(): EventTarget | undefined;\n  /**\n   * The **`timeStamp`** read-only property of the Event interface returns the time (in milliseconds) at which the event was created.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/timeStamp)\n   */\n  get timeStamp(): number;\n  /**\n   * The **`isTrusted`** read-only property of the when the event was generated by the user agent (including via user actions and programmatic methods such as HTMLElement.focus()), and `false` when the event was dispatched via The only exception is the `click` event, which initializes the `isTrusted` property to `false` in user agents.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/isTrusted)\n   */\n  readonly isTrusted: boolean;\n  /**\n   * The **`cancelBubble`** property of the Event interface is deprecated.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble)\n   */\n  get cancelBubble(): boolean;\n  /**\n   * The **`cancelBubble`** property of the Event interface is deprecated.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble)\n   */\n  set cancelBubble(value: boolean);\n  /**\n   * The **`stopImmediatePropagation()`** method of the If several listeners are attached to the same element for the same event type, they are called in the order in which they were added.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopImmediatePropagation)\n   */\n  stopImmediatePropagation(): void;\n  /**\n   * The **`preventDefault()`** method of the Event interface tells the user agent that if the event does not get explicitly handled, its default action should not be taken as it normally would be.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/preventDefault)\n   */\n  preventDefault(): void;\n  /**\n   * The **`stopPropagation()`** method of the Event interface prevents further propagation of the current event in the capturing and bubbling phases.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopPropagation)\n   */\n  stopPropagation(): void;\n  /**\n   * The **`composedPath()`** method of the Event interface returns the event's path which is an array of the objects on which listeners will be invoked.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composedPath)\n   */\n  composedPath(): EventTarget[];\n  static readonly NONE: number;\n  static readonly CAPTURING_PHASE: number;\n  static readonly AT_TARGET: number;\n  static readonly BUBBLING_PHASE: number;\n}\nexport interface EventInit {\n  bubbles?: boolean;\n  cancelable?: boolean;\n  composed?: boolean;\n}\nexport type EventListener<EventType extends Event = Event> = (\n  event: EventType,\n) => void;\nexport interface EventListenerObject<EventType extends Event = Event> {\n  handleEvent(event: EventType): void;\n}\nexport type EventListenerOrEventListenerObject<\n  EventType extends Event = Event,\n> = EventListener<EventType> | EventListenerObject<EventType>;\n/**\n * The **`EventTarget`** interface is implemented by objects that can receive events and may have listeners for them.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget)\n */\nexport declare class EventTarget<\n  EventMap extends Record<string, Event> = Record<string, Event>,\n> {\n  constructor();\n  /**\n   * The **`addEventListener()`** method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)\n   */\n  addEventListener<Type extends keyof EventMap>(\n    type: Type,\n    handler: EventListenerOrEventListenerObject<EventMap[Type]>,\n    options?: EventTargetAddEventListenerOptions | boolean,\n  ): void;\n  /**\n   * The **`removeEventListener()`** method of the EventTarget interface removes an event listener previously registered with EventTarget.addEventListener() from the target.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener)\n   */\n  removeEventListener<Type extends keyof EventMap>(\n    type: Type,\n    handler: EventListenerOrEventListenerObject<EventMap[Type]>,\n    options?: EventTargetEventListenerOptions | boolean,\n  ): void;\n  /**\n   * The **`dispatchEvent()`** method of the EventTarget sends an Event to the object, (synchronously) invoking the affected event listeners in the appropriate order.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent)\n   */\n  dispatchEvent(event: EventMap[keyof EventMap]): boolean;\n}\nexport interface EventTargetEventListenerOptions {\n  capture?: boolean;\n}\nexport interface EventTargetAddEventListenerOptions {\n  capture?: boolean;\n  passive?: boolean;\n  once?: boolean;\n  signal?: AbortSignal;\n}\nexport interface EventTargetHandlerObject {\n  handleEvent: (event: Event) => any | undefined;\n}\n/**\n * The **`AbortController`** interface represents a controller object that allows you to abort one or more Web requests as and when desired.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController)\n */\nexport declare class AbortController {\n  constructor();\n  /**\n   * The **`signal`** read-only property of the AbortController interface returns an AbortSignal object instance, which can be used to communicate with/abort an asynchronous operation as desired.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/signal)\n   */\n  get signal(): AbortSignal;\n  /**\n   * The **`abort()`** method of the AbortController interface aborts an asynchronous operation before it has completed.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/abort)\n   */\n  abort(reason?: any): void;\n}\n/**\n * The **`AbortSignal`** interface represents a signal object that allows you to communicate with an asynchronous operation (such as a fetch request) and abort it if required via an AbortController object.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal)\n */\nexport declare abstract class AbortSignal extends EventTarget {\n  /**\n   * The **`AbortSignal.abort()`** static method returns an AbortSignal that is already set as aborted (and which does not trigger an AbortSignal/abort_event event).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_static)\n   */\n  static abort(reason?: any): AbortSignal;\n  /**\n   * The **`AbortSignal.timeout()`** static method returns an AbortSignal that will automatically abort after a specified time.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/timeout_static)\n   */\n  static timeout(delay: number): AbortSignal;\n  /**\n   * The **`AbortSignal.any()`** static method takes an iterable of abort signals and returns an AbortSignal.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/any_static)\n   */\n  static any(signals: AbortSignal[]): AbortSignal;\n  /**\n   * The **`aborted`** read-only property returns a value that indicates whether the asynchronous operations the signal is communicating with are aborted (`true`) or not (`false`).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/aborted)\n   */\n  get aborted(): boolean;\n  /**\n   * The **`reason`** read-only property returns a JavaScript value that indicates the abort reason.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/reason)\n   */\n  get reason(): any;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */\n  get onabort(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */\n  set onabort(value: any | null);\n  /**\n   * The **`throwIfAborted()`** method throws the signal's abort AbortSignal.reason if the signal has been aborted; otherwise it does nothing.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/throwIfAborted)\n   */\n  throwIfAborted(): void;\n}\nexport interface Scheduler {\n  wait(delay: number, maybeOptions?: SchedulerWaitOptions): Promise<void>;\n}\nexport interface SchedulerWaitOptions {\n  signal?: AbortSignal;\n}\n/**\n * The **`ExtendableEvent`** interface extends the lifetime of the `install` and `activate` events dispatched on the global scope as part of the service worker lifecycle.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent)\n */\nexport declare abstract class ExtendableEvent extends Event {\n  /**\n   * The **`ExtendableEvent.waitUntil()`** method tells the event dispatcher that work is ongoing.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent/waitUntil)\n   */\n  waitUntil(promise: Promise<any>): void;\n}\n/**\n * The **`CustomEvent`** interface represents events initialized by an application for any purpose.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent)\n */\nexport declare class CustomEvent<T = any> extends Event {\n  constructor(type: string, init?: CustomEventCustomEventInit);\n  /**\n   * The read-only **`detail`** property of the CustomEvent interface returns any data passed when initializing the event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent/detail)\n   */\n  get detail(): T;\n}\nexport interface CustomEventCustomEventInit {\n  bubbles?: boolean;\n  cancelable?: boolean;\n  composed?: boolean;\n  detail?: any;\n}\n/**\n * The **`Blob`** interface represents a blob, which is a file-like object of immutable, raw data; they can be read as text or binary data, or converted into a ReadableStream so its methods can be used for processing the data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob)\n */\nexport declare class Blob {\n  constructor(\n    type?: ((ArrayBuffer | ArrayBufferView) | string | Blob)[],\n    options?: BlobOptions,\n  );\n  /**\n   * The **`size`** read-only property of the Blob interface returns the size of the Blob or File in bytes.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size)\n   */\n  get size(): number;\n  /**\n   * The **`type`** read-only property of the Blob interface returns the MIME type of the file.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type)\n   */\n  get type(): string;\n  /**\n   * The **`slice()`** method of the Blob interface creates and returns a new `Blob` object which contains data from a subset of the blob on which it's called.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice)\n   */\n  slice(start?: number, end?: number, type?: string): Blob;\n  /**\n   * The **`arrayBuffer()`** method of the Blob interface returns a Promise that resolves with the contents of the blob as binary data contained in an ArrayBuffer.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/arrayBuffer)\n   */\n  arrayBuffer(): Promise<ArrayBuffer>;\n  /**\n   * The **`bytes()`** method of the Blob interface returns a Promise that resolves with a Uint8Array containing the contents of the blob as an array of bytes.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/bytes)\n   */\n  bytes(): Promise<Uint8Array>;\n  /**\n   * The **`text()`** method of the string containing the contents of the blob, interpreted as UTF-8.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text)\n   */\n  text(): Promise<string>;\n  /**\n   * The **`stream()`** method of the Blob interface returns a ReadableStream which upon reading returns the data contained within the `Blob`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/stream)\n   */\n  stream(): ReadableStream;\n}\nexport interface BlobOptions {\n  type?: string;\n}\n/**\n * The **`File`** interface provides information about files and allows JavaScript in a web page to access their content.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File)\n */\nexport declare class File extends Blob {\n  constructor(\n    bits: ((ArrayBuffer | ArrayBufferView) | string | Blob)[] | undefined,\n    name: string,\n    options?: FileOptions,\n  );\n  /**\n   * The **`name`** read-only property of the File interface returns the name of the file represented by a File object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name)\n   */\n  get name(): string;\n  /**\n   * The **`lastModified`** read-only property of the File interface provides the last modified date of the file as the number of milliseconds since the Unix epoch (January 1, 1970 at midnight).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified)\n   */\n  get lastModified(): number;\n}\nexport interface FileOptions {\n  type?: string;\n  lastModified?: number;\n}\n/**\n * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)\n */\nexport declare abstract class CacheStorage {\n  /**\n   * The **`open()`** method of the the Cache object matching the `cacheName`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CacheStorage/open)\n   */\n  open(cacheName: string): Promise<Cache>;\n  readonly default: Cache;\n}\n/**\n * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)\n */\nexport declare abstract class Cache {\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#delete) */\n  delete(\n    request: RequestInfo | URL,\n    options?: CacheQueryOptions,\n  ): Promise<boolean>;\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#match) */\n  match(\n    request: RequestInfo | URL,\n    options?: CacheQueryOptions,\n  ): Promise<Response | undefined>;\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#put) */\n  put(request: RequestInfo | URL, response: Response): Promise<void>;\n}\nexport interface CacheQueryOptions {\n  ignoreMethod?: boolean;\n}\n/**\n * The Web Crypto API provides a set of low-level functions for common cryptographic tasks.\n * The Workers runtime implements the full surface of this API, but with some differences in\n * the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms)\n * compared to those implemented in most browsers.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/)\n */\nexport declare abstract class Crypto {\n  /**\n   * The **`Crypto.subtle`** read-only property returns a cryptographic operations.\n   * Available only in secure contexts.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/subtle)\n   */\n  get subtle(): SubtleCrypto;\n  /**\n   * The **`Crypto.getRandomValues()`** method lets you get cryptographically strong random values.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/getRandomValues)\n   */\n  getRandomValues<\n    T extends\n      | Int8Array\n      | Uint8Array\n      | Int16Array\n      | Uint16Array\n      | Int32Array\n      | Uint32Array\n      | BigInt64Array\n      | BigUint64Array,\n  >(buffer: T): T;\n  /**\n   * The **`randomUUID()`** method of the Crypto interface is used to generate a v4 UUID using a cryptographically secure random number generator.\n   * Available only in secure contexts.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/randomUUID)\n   */\n  randomUUID(): string;\n  DigestStream: typeof DigestStream;\n}\n/**\n * The **`SubtleCrypto`** interface of the Web Crypto API provides a number of low-level cryptographic functions.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto)\n */\nexport declare abstract class SubtleCrypto {\n  /**\n   * The **`encrypt()`** method of the SubtleCrypto interface encrypts data.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/encrypt)\n   */\n  encrypt(\n    algorithm: string | SubtleCryptoEncryptAlgorithm,\n    key: CryptoKey,\n    plainText: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`decrypt()`** method of the SubtleCrypto interface decrypts some encrypted data.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/decrypt)\n   */\n  decrypt(\n    algorithm: string | SubtleCryptoEncryptAlgorithm,\n    key: CryptoKey,\n    cipherText: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`sign()`** method of the SubtleCrypto interface generates a digital signature.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/sign)\n   */\n  sign(\n    algorithm: string | SubtleCryptoSignAlgorithm,\n    key: CryptoKey,\n    data: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`verify()`** method of the SubtleCrypto interface verifies a digital signature.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/verify)\n   */\n  verify(\n    algorithm: string | SubtleCryptoSignAlgorithm,\n    key: CryptoKey,\n    signature: ArrayBuffer | ArrayBufferView,\n    data: ArrayBuffer | ArrayBufferView,\n  ): Promise<boolean>;\n  /**\n   * The **`digest()`** method of the SubtleCrypto interface generates a _digest_ of the given data, using the specified hash function.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/digest)\n   */\n  digest(\n    algorithm: string | SubtleCryptoHashAlgorithm,\n    data: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`generateKey()`** method of the SubtleCrypto interface is used to generate a new key (for symmetric algorithms) or key pair (for public-key algorithms).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/generateKey)\n   */\n  generateKey(\n    algorithm: string | SubtleCryptoGenerateKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey | CryptoKeyPair>;\n  /**\n   * The **`deriveKey()`** method of the SubtleCrypto interface can be used to derive a secret key from a master key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveKey)\n   */\n  deriveKey(\n    algorithm: string | SubtleCryptoDeriveKeyAlgorithm,\n    baseKey: CryptoKey,\n    derivedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey>;\n  /**\n   * The **`deriveBits()`** method of the key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveBits)\n   */\n  deriveBits(\n    algorithm: string | SubtleCryptoDeriveKeyAlgorithm,\n    baseKey: CryptoKey,\n    length?: number | null,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`importKey()`** method of the SubtleCrypto interface imports a key: that is, it takes as input a key in an external, portable format and gives you a CryptoKey object that you can use in the Web Crypto API.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/importKey)\n   */\n  importKey(\n    format: string,\n    keyData: (ArrayBuffer | ArrayBufferView) | JsonWebKey,\n    algorithm: string | SubtleCryptoImportKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey>;\n  /**\n   * The **`exportKey()`** method of the SubtleCrypto interface exports a key: that is, it takes as input a CryptoKey object and gives you the key in an external, portable format.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/exportKey)\n   */\n  exportKey(format: string, key: CryptoKey): Promise<ArrayBuffer | JsonWebKey>;\n  /**\n   * The **`wrapKey()`** method of the SubtleCrypto interface 'wraps' a key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/wrapKey)\n   */\n  wrapKey(\n    format: string,\n    key: CryptoKey,\n    wrappingKey: CryptoKey,\n    wrapAlgorithm: string | SubtleCryptoEncryptAlgorithm,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`unwrapKey()`** method of the SubtleCrypto interface 'unwraps' a key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/unwrapKey)\n   */\n  unwrapKey(\n    format: string,\n    wrappedKey: ArrayBuffer | ArrayBufferView,\n    unwrappingKey: CryptoKey,\n    unwrapAlgorithm: string | SubtleCryptoEncryptAlgorithm,\n    unwrappedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey>;\n  timingSafeEqual(\n    a: ArrayBuffer | ArrayBufferView,\n    b: ArrayBuffer | ArrayBufferView,\n  ): boolean;\n}\n/**\n * The **`CryptoKey`** interface of the Web Crypto API represents a cryptographic key obtained from one of the SubtleCrypto methods SubtleCrypto.generateKey, SubtleCrypto.deriveKey, SubtleCrypto.importKey, or SubtleCrypto.unwrapKey.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey)\n */\nexport declare abstract class CryptoKey {\n  /**\n   * The read-only **`type`** property of the CryptoKey interface indicates which kind of key is represented by the object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/type)\n   */\n  readonly type: string;\n  /**\n   * The read-only **`extractable`** property of the CryptoKey interface indicates whether or not the key may be extracted using `SubtleCrypto.exportKey()` or `SubtleCrypto.wrapKey()`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/extractable)\n   */\n  readonly extractable: boolean;\n  /**\n   * The read-only **`algorithm`** property of the CryptoKey interface returns an object describing the algorithm for which this key can be used, and any associated extra parameters.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/algorithm)\n   */\n  readonly algorithm:\n    | CryptoKeyKeyAlgorithm\n    | CryptoKeyAesKeyAlgorithm\n    | CryptoKeyHmacKeyAlgorithm\n    | CryptoKeyRsaKeyAlgorithm\n    | CryptoKeyEllipticKeyAlgorithm\n    | CryptoKeyArbitraryKeyAlgorithm;\n  /**\n   * The read-only **`usages`** property of the CryptoKey interface indicates what can be done with the key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/usages)\n   */\n  readonly usages: string[];\n}\nexport interface CryptoKeyPair {\n  publicKey: CryptoKey;\n  privateKey: CryptoKey;\n}\nexport interface JsonWebKey {\n  kty: string;\n  use?: string;\n  key_ops?: string[];\n  alg?: string;\n  ext?: boolean;\n  crv?: string;\n  x?: string;\n  y?: string;\n  d?: string;\n  n?: string;\n  e?: string;\n  p?: string;\n  q?: string;\n  dp?: string;\n  dq?: string;\n  qi?: string;\n  oth?: RsaOtherPrimesInfo[];\n  k?: string;\n}\nexport interface RsaOtherPrimesInfo {\n  r?: string;\n  d?: string;\n  t?: string;\n}\nexport interface SubtleCryptoDeriveKeyAlgorithm {\n  name: string;\n  salt?: ArrayBuffer | ArrayBufferView;\n  iterations?: number;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  $public?: CryptoKey;\n  info?: ArrayBuffer | ArrayBufferView;\n}\nexport interface SubtleCryptoEncryptAlgorithm {\n  name: string;\n  iv?: ArrayBuffer | ArrayBufferView;\n  additionalData?: ArrayBuffer | ArrayBufferView;\n  tagLength?: number;\n  counter?: ArrayBuffer | ArrayBufferView;\n  length?: number;\n  label?: ArrayBuffer | ArrayBufferView;\n}\nexport interface SubtleCryptoGenerateKeyAlgorithm {\n  name: string;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  modulusLength?: number;\n  publicExponent?: ArrayBuffer | ArrayBufferView;\n  length?: number;\n  namedCurve?: string;\n}\nexport interface SubtleCryptoHashAlgorithm {\n  name: string;\n}\nexport interface SubtleCryptoImportKeyAlgorithm {\n  name: string;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  length?: number;\n  namedCurve?: string;\n  compressed?: boolean;\n}\nexport interface SubtleCryptoSignAlgorithm {\n  name: string;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  dataLength?: number;\n  saltLength?: number;\n}\nexport interface CryptoKeyKeyAlgorithm {\n  name: string;\n}\nexport interface CryptoKeyAesKeyAlgorithm {\n  name: string;\n  length: number;\n}\nexport interface CryptoKeyHmacKeyAlgorithm {\n  name: string;\n  hash: CryptoKeyKeyAlgorithm;\n  length: number;\n}\nexport interface CryptoKeyRsaKeyAlgorithm {\n  name: string;\n  modulusLength: number;\n  publicExponent: ArrayBuffer | ArrayBufferView;\n  hash?: CryptoKeyKeyAlgorithm;\n}\nexport interface CryptoKeyEllipticKeyAlgorithm {\n  name: string;\n  namedCurve: string;\n}\nexport interface CryptoKeyArbitraryKeyAlgorithm {\n  name: string;\n  hash?: CryptoKeyKeyAlgorithm;\n  namedCurve?: string;\n  length?: number;\n}\nexport declare class DigestStream extends WritableStream<\n  ArrayBuffer | ArrayBufferView\n> {\n  constructor(algorithm: string | SubtleCryptoHashAlgorithm);\n  readonly digest: Promise<ArrayBuffer>;\n  get bytesWritten(): number | bigint;\n}\n/**\n * The **`TextDecoder`** interface represents a decoder for a specific text encoding, such as `UTF-8`, `ISO-8859-2`, `KOI8-R`, `GBK`, etc.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder)\n */\nexport declare class TextDecoder {\n  constructor(label?: string, options?: TextDecoderConstructorOptions);\n  /**\n   * The **`TextDecoder.decode()`** method returns a string containing text decoded from the buffer passed as a parameter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder/decode)\n   */\n  decode(\n    input?: ArrayBuffer | ArrayBufferView,\n    options?: TextDecoderDecodeOptions,\n  ): string;\n  get encoding(): string;\n  get fatal(): boolean;\n  get ignoreBOM(): boolean;\n}\n/**\n * The **`TextEncoder`** interface takes a stream of code points as input and emits a stream of UTF-8 bytes.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder)\n */\nexport declare class TextEncoder {\n  constructor();\n  /**\n   * The **`TextEncoder.encode()`** method takes a string as input, and returns a Global_Objects/Uint8Array containing the text given in parameters encoded with the specific method for that TextEncoder object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encode)\n   */\n  encode(input?: string): Uint8Array;\n  /**\n   * The **`TextEncoder.encodeInto()`** method takes a string to encode and a destination Uint8Array to put resulting UTF-8 encoded text into, and returns a dictionary object indicating the progress of the encoding.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encodeInto)\n   */\n  encodeInto(input: string, buffer: Uint8Array): TextEncoderEncodeIntoResult;\n  get encoding(): string;\n}\nexport interface TextDecoderConstructorOptions {\n  fatal: boolean;\n  ignoreBOM: boolean;\n}\nexport interface TextDecoderDecodeOptions {\n  stream: boolean;\n}\nexport interface TextEncoderEncodeIntoResult {\n  read: number;\n  written: number;\n}\n/**\n * The **`ErrorEvent`** interface represents events providing information related to errors in scripts or in files.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent)\n */\nexport declare class ErrorEvent extends Event {\n  constructor(type: string, init?: ErrorEventErrorEventInit);\n  /**\n   * The **`filename`** read-only property of the ErrorEvent interface returns a string containing the name of the script file in which the error occurred.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/filename)\n   */\n  get filename(): string;\n  /**\n   * The **`message`** read-only property of the ErrorEvent interface returns a string containing a human-readable error message describing the problem.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/message)\n   */\n  get message(): string;\n  /**\n   * The **`lineno`** read-only property of the ErrorEvent interface returns an integer containing the line number of the script file on which the error occurred.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/lineno)\n   */\n  get lineno(): number;\n  /**\n   * The **`colno`** read-only property of the ErrorEvent interface returns an integer containing the column number of the script file on which the error occurred.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/colno)\n   */\n  get colno(): number;\n  /**\n   * The **`error`** read-only property of the ErrorEvent interface returns a JavaScript value, such as an Error or DOMException, representing the error associated with this event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/error)\n   */\n  get error(): any;\n}\nexport interface ErrorEventErrorEventInit {\n  message?: string;\n  filename?: string;\n  lineno?: number;\n  colno?: number;\n  error?: any;\n}\n/**\n * The **`MessageEvent`** interface represents a message received by a target object.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent)\n */\nexport declare class MessageEvent extends Event {\n  constructor(type: string, initializer: MessageEventInit);\n  /**\n   * The **`data`** read-only property of the The data sent by the message emitter; this can be any data type, depending on what originated this event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/data)\n   */\n  readonly data: any;\n  /**\n   * The **`origin`** read-only property of the origin of the message emitter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/origin)\n   */\n  readonly origin: string | null;\n  /**\n   * The **`lastEventId`** read-only property of the unique ID for the event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/lastEventId)\n   */\n  readonly lastEventId: string;\n  /**\n   * The **`source`** read-only property of the a WindowProxy, MessagePort, or a `MessageEventSource` (which can be a WindowProxy, message emitter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/source)\n   */\n  readonly source: MessagePort | null;\n  /**\n   * The **`ports`** read-only property of the containing all MessagePort objects sent with the message, in order.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/ports)\n   */\n  readonly ports: MessagePort[];\n}\nexport interface MessageEventInit {\n  data: ArrayBuffer | string;\n}\n/**\n * The **`PromiseRejectionEvent`** interface represents events which are sent to the global script context when JavaScript Promises are rejected.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent)\n */\nexport declare abstract class PromiseRejectionEvent extends Event {\n  /**\n   * The PromiseRejectionEvent interface's **`promise`** read-only property indicates the JavaScript rejected.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/promise)\n   */\n  readonly promise: Promise<any>;\n  /**\n   * The PromiseRejectionEvent **`reason`** read-only property is any JavaScript value or Object which provides the reason passed into Promise.reject().\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/reason)\n   */\n  readonly reason: any;\n}\n/**\n * The **`FormData`** interface provides a way to construct a set of key/value pairs representing form fields and their values, which can be sent using the Window/fetch, XMLHttpRequest.send() or navigator.sendBeacon() methods.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData)\n */\nexport declare class FormData {\n  constructor();\n  /**\n   * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append)\n   */\n  append(name: string, value: string | Blob): void;\n  /**\n   * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append)\n   */\n  append(name: string, value: string): void;\n  /**\n   * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append)\n   */\n  append(name: string, value: Blob, filename?: string): void;\n  /**\n   * The **`delete()`** method of the FormData interface deletes a key and its value(s) from a `FormData` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/delete)\n   */\n  delete(name: string): void;\n  /**\n   * The **`get()`** method of the FormData interface returns the first value associated with a given key from within a `FormData` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/get)\n   */\n  get(name: string): (File | string) | null;\n  /**\n   * The **`getAll()`** method of the FormData interface returns all the values associated with a given key from within a `FormData` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/getAll)\n   */\n  getAll(name: string): (File | string)[];\n  /**\n   * The **`has()`** method of the FormData interface returns whether a `FormData` object contains a certain key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/has)\n   */\n  has(name: string): boolean;\n  /**\n   * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set)\n   */\n  set(name: string, value: string | Blob): void;\n  /**\n   * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set)\n   */\n  set(name: string, value: string): void;\n  /**\n   * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set)\n   */\n  set(name: string, value: Blob, filename?: string): void;\n  /* Returns an array of key, value pairs for every entry in the list. */\n  entries(): IterableIterator<[key: string, value: File | string]>;\n  /* Returns a list of keys in the list. */\n  keys(): IterableIterator<string>;\n  /* Returns a list of values in the list. */\n  values(): IterableIterator<File | string>;\n  forEach<This = unknown>(\n    callback: (\n      this: This,\n      value: File | string,\n      key: string,\n      parent: FormData,\n    ) => void,\n    thisArg?: This,\n  ): void;\n  [Symbol.iterator](): IterableIterator<[key: string, value: File | string]>;\n}\nexport interface ContentOptions {\n  html?: boolean;\n}\nexport declare class HTMLRewriter {\n  constructor();\n  on(\n    selector: string,\n    handlers: HTMLRewriterElementContentHandlers,\n  ): HTMLRewriter;\n  onDocument(handlers: HTMLRewriterDocumentContentHandlers): HTMLRewriter;\n  transform(response: Response): Response;\n}\nexport interface HTMLRewriterElementContentHandlers {\n  element?(element: Element): void | Promise<void>;\n  comments?(comment: Comment): void | Promise<void>;\n  text?(element: Text): void | Promise<void>;\n}\nexport interface HTMLRewriterDocumentContentHandlers {\n  doctype?(doctype: Doctype): void | Promise<void>;\n  comments?(comment: Comment): void | Promise<void>;\n  text?(text: Text): void | Promise<void>;\n  end?(end: DocumentEnd): void | Promise<void>;\n}\nexport interface Doctype {\n  readonly name: string | null;\n  readonly publicId: string | null;\n  readonly systemId: string | null;\n}\nexport interface Element {\n  tagName: string;\n  readonly attributes: IterableIterator<string[]>;\n  readonly removed: boolean;\n  readonly namespaceURI: string;\n  getAttribute(name: string): string | null;\n  hasAttribute(name: string): boolean;\n  setAttribute(name: string, value: string): Element;\n  removeAttribute(name: string): Element;\n  before(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  after(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  prepend(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  append(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  replace(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  remove(): Element;\n  removeAndKeepContent(): Element;\n  setInnerContent(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  onEndTag(handler: (tag: EndTag) => void | Promise<void>): void;\n}\nexport interface EndTag {\n  name: string;\n  before(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): EndTag;\n  after(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): EndTag;\n  remove(): EndTag;\n}\nexport interface Comment {\n  text: string;\n  readonly removed: boolean;\n  before(content: string, options?: ContentOptions): Comment;\n  after(content: string, options?: ContentOptions): Comment;\n  replace(content: string, options?: ContentOptions): Comment;\n  remove(): Comment;\n}\nexport interface Text {\n  readonly text: string;\n  readonly lastInTextNode: boolean;\n  readonly removed: boolean;\n  before(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Text;\n  after(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Text;\n  replace(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Text;\n  remove(): Text;\n}\nexport interface DocumentEnd {\n  append(content: string, options?: ContentOptions): DocumentEnd;\n}\n/**\n * This is the event type for `fetch` events dispatched on the ServiceWorkerGlobalScope.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent)\n */\nexport declare abstract class FetchEvent extends ExtendableEvent {\n  /**\n   * The **`request`** read-only property of the the event handler.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/request)\n   */\n  readonly request: Request;\n  /**\n   * The **`respondWith()`** method of allows you to provide a promise for a Response yourself.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/respondWith)\n   */\n  respondWith(promise: Response | Promise<Response>): void;\n  passThroughOnException(): void;\n}\nexport type HeadersInit =\n  | Headers\n  | Iterable<Iterable<string>>\n  | Record<string, string>;\n/**\n * The **`Headers`** interface of the Fetch API allows you to perform various actions on HTTP request and response headers.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers)\n */\nexport declare class Headers {\n  constructor(init?: HeadersInit);\n  /**\n   * The **`get()`** method of the Headers interface returns a byte string of all the values of a header within a `Headers` object with a given name.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/get)\n   */\n  get(name: string): string | null;\n  getAll(name: string): string[];\n  /**\n   * The **`getSetCookie()`** method of the Headers interface returns an array containing the values of all Set-Cookie headers associated with a response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/getSetCookie)\n   */\n  getSetCookie(): string[];\n  /**\n   * The **`has()`** method of the Headers interface returns a boolean stating whether a `Headers` object contains a certain header.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/has)\n   */\n  has(name: string): boolean;\n  /**\n   * The **`set()`** method of the Headers interface sets a new value for an existing header inside a `Headers` object, or adds the header if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/set)\n   */\n  set(name: string, value: string): void;\n  /**\n   * The **`append()`** method of the Headers interface appends a new value onto an existing header inside a `Headers` object, or adds the header if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/append)\n   */\n  append(name: string, value: string): void;\n  /**\n   * The **`delete()`** method of the Headers interface deletes a header from the current `Headers` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/delete)\n   */\n  delete(name: string): void;\n  forEach<This = unknown>(\n    callback: (this: This, value: string, key: string, parent: Headers) => void,\n    thisArg?: This,\n  ): void;\n  /* Returns an iterator allowing to go through all key/value pairs contained in this object. */\n  entries(): IterableIterator<[key: string, value: string]>;\n  /* Returns an iterator allowing to go through all keys of the key/value pairs contained in this object. */\n  keys(): IterableIterator<string>;\n  /* Returns an iterator allowing to go through all values of the key/value pairs contained in this object. */\n  values(): IterableIterator<string>;\n  [Symbol.iterator](): IterableIterator<[key: string, value: string]>;\n}\nexport type BodyInit =\n  | ReadableStream<Uint8Array>\n  | string\n  | ArrayBuffer\n  | ArrayBufferView\n  | Blob\n  | URLSearchParams\n  | FormData\n  | Iterable<ArrayBuffer | ArrayBufferView>\n  | AsyncIterable<ArrayBuffer | ArrayBufferView>;\nexport declare abstract class Body {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body) */\n  get body(): ReadableStream | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) */\n  get bodyUsed(): boolean;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/arrayBuffer) */\n  arrayBuffer(): Promise<ArrayBuffer>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bytes) */\n  bytes(): Promise<Uint8Array>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/text) */\n  text(): Promise<string>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */\n  json<T>(): Promise<T>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/formData) */\n  formData(): Promise<FormData>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/blob) */\n  blob(): Promise<Blob>;\n}\n/**\n * The **`Response`** interface of the Fetch API represents the response to a request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response)\n */\nexport declare var Response: {\n  prototype: Response;\n  new (body?: BodyInit | null, init?: ResponseInit): Response;\n  error(): Response;\n  redirect(url: string, status?: number): Response;\n  json(any: any, maybeInit?: ResponseInit | Response): Response;\n};\n/**\n * The **`Response`** interface of the Fetch API represents the response to a request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response)\n */\nexport interface Response extends Body {\n  /**\n   * The **`clone()`** method of the Response interface creates a clone of a response object, identical in every way, but stored in a different variable.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/clone)\n   */\n  clone(): Response;\n  /**\n   * The **`status`** read-only property of the Response interface contains the HTTP status codes of the response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/status)\n   */\n  status: number;\n  /**\n   * The **`statusText`** read-only property of the Response interface contains the status message corresponding to the HTTP status code in Response.status.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/statusText)\n   */\n  statusText: string;\n  /**\n   * The **`headers`** read-only property of the with the response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/headers)\n   */\n  headers: Headers;\n  /**\n   * The **`ok`** read-only property of the Response interface contains a Boolean stating whether the response was successful (status in the range 200-299) or not.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/ok)\n   */\n  ok: boolean;\n  /**\n   * The **`redirected`** read-only property of the Response interface indicates whether or not the response is the result of a request you made which was redirected.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/redirected)\n   */\n  redirected: boolean;\n  /**\n   * The **`url`** read-only property of the Response interface contains the URL of the response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/url)\n   */\n  url: string;\n  webSocket: WebSocket | null;\n  cf: any | undefined;\n  /**\n   * The **`type`** read-only property of the Response interface contains the type of the response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/type)\n   */\n  type: \"default\" | \"error\";\n}\nexport interface ResponseInit {\n  status?: number;\n  statusText?: string;\n  headers?: HeadersInit;\n  cf?: any;\n  webSocket?: WebSocket | null;\n  encodeBody?: \"automatic\" | \"manual\";\n}\nexport type RequestInfo<\n  CfHostMetadata = unknown,\n  Cf = CfProperties<CfHostMetadata>,\n> = Request<CfHostMetadata, Cf> | string;\n/**\n * The **`Request`** interface of the Fetch API represents a resource request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request)\n */\nexport declare var Request: {\n  prototype: Request;\n  new <CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>>(\n    input: RequestInfo<CfProperties> | URL,\n    init?: RequestInit<Cf>,\n  ): Request<CfHostMetadata, Cf>;\n};\n/**\n * The **`Request`** interface of the Fetch API represents a resource request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request)\n */\nexport interface Request<\n  CfHostMetadata = unknown,\n  Cf = CfProperties<CfHostMetadata>,\n> extends Body {\n  /**\n   * The **`clone()`** method of the Request interface creates a copy of the current `Request` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/clone)\n   */\n  clone(): Request<CfHostMetadata, Cf>;\n  /**\n   * The **`method`** read-only property of the `POST`, etc.) A String indicating the method of the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/method)\n   */\n  method: string;\n  /**\n   * The **`url`** read-only property of the Request interface contains the URL of the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/url)\n   */\n  url: string;\n  /**\n   * The **`headers`** read-only property of the with the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/headers)\n   */\n  headers: Headers;\n  /**\n   * The **`redirect`** read-only property of the Request interface contains the mode for how redirects are handled.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/redirect)\n   */\n  redirect: string;\n  fetcher: Fetcher | null;\n  /**\n   * The read-only **`signal`** property of the Request interface returns the AbortSignal associated with the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/signal)\n   */\n  signal: AbortSignal;\n  cf?: Cf;\n  /**\n   * The **`integrity`** read-only property of the Request interface contains the subresource integrity value of the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/integrity)\n   */\n  integrity: string;\n  /**\n   * The **`keepalive`** read-only property of the Request interface contains the request's `keepalive` setting (`true` or `false`), which indicates whether the browser will keep the associated request alive if the page that initiated it is unloaded before the request is complete.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/keepalive)\n   */\n  keepalive: boolean;\n  /**\n   * The **`cache`** read-only property of the Request interface contains the cache mode of the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/cache)\n   */\n  cache?: \"no-store\" | \"no-cache\" | \"reload\";\n}\nexport interface RequestInit<Cf = CfProperties> {\n  /* A string to set request's method. */\n  method?: string;\n  /* A Headers object, an object literal, or an array of two-item arrays to set request's headers. */\n  headers?: HeadersInit;\n  /* A BodyInit object or null to set request's body. */\n  body?: BodyInit | null;\n  /* A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */\n  redirect?: string;\n  fetcher?: Fetcher | null;\n  cf?: Cf;\n  /* A string indicating how the request will interact with the browser's cache to set request's cache. */\n  cache?: \"no-store\" | \"no-cache\" | \"reload\";\n  /* A cryptographic hash of the resource to be fetched by request. Sets request's integrity. */\n  integrity?: string;\n  /* An AbortSignal to set request's signal. */\n  signal?: AbortSignal | null;\n  encodeResponseBody?: \"automatic\" | \"manual\";\n}\nexport type Service<\n  T extends\n    | (new (...args: any[]) => Rpc.WorkerEntrypointBranded)\n    | Rpc.WorkerEntrypointBranded\n    | ExportedHandler<any, any, any>\n    | undefined = undefined,\n> = T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded\n  ? Fetcher<InstanceType<T>>\n  : T extends Rpc.WorkerEntrypointBranded\n    ? Fetcher<T>\n    : T extends Exclude<Rpc.EntrypointBranded, Rpc.WorkerEntrypointBranded>\n      ? never\n      : Fetcher<undefined>;\nexport type Fetcher<\n  T extends Rpc.EntrypointBranded | undefined = undefined,\n  Reserved extends string = never,\n> = (T extends Rpc.EntrypointBranded\n  ? Rpc.Provider<T, Reserved | \"fetch\" | \"connect\" | \"queue\" | \"scheduled\">\n  : unknown) & {\n  fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;\n  connect(address: SocketAddress | string, options?: SocketOptions): Socket;\n  queue(\n    queueName: string,\n    messages: ServiceBindingQueueMessage[],\n  ): Promise<FetcherQueueResult>;\n  scheduled(options?: FetcherScheduledOptions): Promise<FetcherScheduledResult>;\n};\nexport interface FetcherScheduledOptions {\n  scheduledTime?: Date;\n  cron?: string;\n}\nexport interface FetcherScheduledResult {\n  outcome: string;\n  noRetry: boolean;\n}\nexport interface FetcherQueueResult {\n  outcome: string;\n  ackAll: boolean;\n  retryBatch: QueueRetryBatch;\n  explicitAcks: string[];\n  retryMessages: QueueRetryMessage[];\n}\nexport type ServiceBindingQueueMessage<Body = unknown> = {\n  id: string;\n  timestamp: Date;\n  attempts: number;\n} & (\n  | {\n      body: Body;\n    }\n  | {\n      serializedBody: ArrayBuffer | ArrayBufferView;\n    }\n);\nexport interface KVNamespaceListKey<Metadata, Key extends string = string> {\n  name: Key;\n  expiration?: number;\n  metadata?: Metadata;\n}\nexport type KVNamespaceListResult<Metadata, Key extends string = string> =\n  | {\n      list_complete: false;\n      keys: KVNamespaceListKey<Metadata, Key>[];\n      cursor: string;\n      cacheStatus: string | null;\n    }\n  | {\n      list_complete: true;\n      keys: KVNamespaceListKey<Metadata, Key>[];\n      cacheStatus: string | null;\n    };\nexport interface KVNamespace<Key extends string = string> {\n  get(\n    key: Key,\n    options?: Partial<KVNamespaceGetOptions<undefined>>,\n  ): Promise<string | null>;\n  get(key: Key, type: \"text\"): Promise<string | null>;\n  get<ExpectedValue = unknown>(\n    key: Key,\n    type: \"json\",\n  ): Promise<ExpectedValue | null>;\n  get(key: Key, type: \"arrayBuffer\"): Promise<ArrayBuffer | null>;\n  get(key: Key, type: \"stream\"): Promise<ReadableStream | null>;\n  get(\n    key: Key,\n    options?: KVNamespaceGetOptions<\"text\">,\n  ): Promise<string | null>;\n  get<ExpectedValue = unknown>(\n    key: Key,\n    options?: KVNamespaceGetOptions<\"json\">,\n  ): Promise<ExpectedValue | null>;\n  get(\n    key: Key,\n    options?: KVNamespaceGetOptions<\"arrayBuffer\">,\n  ): Promise<ArrayBuffer | null>;\n  get(\n    key: Key,\n    options?: KVNamespaceGetOptions<\"stream\">,\n  ): Promise<ReadableStream | null>;\n  get(key: Array<Key>, type: \"text\"): Promise<Map<string, string | null>>;\n  get<ExpectedValue = unknown>(\n    key: Array<Key>,\n    type: \"json\",\n  ): Promise<Map<string, ExpectedValue | null>>;\n  get(\n    key: Array<Key>,\n    options?: Partial<KVNamespaceGetOptions<undefined>>,\n  ): Promise<Map<string, string | null>>;\n  get(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<\"text\">,\n  ): Promise<Map<string, string | null>>;\n  get<ExpectedValue = unknown>(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<\"json\">,\n  ): Promise<Map<string, ExpectedValue | null>>;\n  list<Metadata = unknown>(\n    options?: KVNamespaceListOptions,\n  ): Promise<KVNamespaceListResult<Metadata, Key>>;\n  put(\n    key: Key,\n    value: string | ArrayBuffer | ArrayBufferView | ReadableStream,\n    options?: KVNamespacePutOptions,\n  ): Promise<void>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options?: Partial<KVNamespaceGetOptions<undefined>>,\n  ): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    type: \"text\",\n  ): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Key,\n    type: \"json\",\n  ): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    type: \"arrayBuffer\",\n  ): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    type: \"stream\",\n  ): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<\"text\">,\n  ): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<\"json\">,\n  ): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<\"arrayBuffer\">,\n  ): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<\"stream\">,\n  ): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Array<Key>,\n    type: \"text\",\n  ): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Array<Key>,\n    type: \"json\",\n  ): Promise<\n    Map<string, KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>\n  >;\n  getWithMetadata<Metadata = unknown>(\n    key: Array<Key>,\n    options?: Partial<KVNamespaceGetOptions<undefined>>,\n  ): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<\"text\">,\n  ): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<\"json\">,\n  ): Promise<\n    Map<string, KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>\n  >;\n  delete(key: Key): Promise<void>;\n  deleteBulk(keys: Key | Key[]): Promise<void>;\n}\nexport interface KVNamespaceListOptions {\n  limit?: number;\n  prefix?: string | null;\n  cursor?: string | null;\n}\nexport interface KVNamespaceGetOptions<Type> {\n  type: Type;\n  cacheTtl?: number;\n}\nexport interface KVNamespacePutOptions {\n  expiration?: number;\n  expirationTtl?: number;\n  metadata?: any | null;\n}\nexport interface KVNamespaceGetWithMetadataResult<Value, Metadata> {\n  value: Value | null;\n  metadata: Metadata | null;\n  cacheStatus: string | null;\n}\nexport type QueueContentType = \"text\" | \"bytes\" | \"json\" | \"v8\";\nexport interface Queue<Body = unknown> {\n  send(message: Body, options?: QueueSendOptions): Promise<void>;\n  sendBatch(\n    messages: Iterable<MessageSendRequest<Body>>,\n    options?: QueueSendBatchOptions,\n  ): Promise<void>;\n}\nexport interface QueueSendOptions {\n  contentType?: QueueContentType;\n  delaySeconds?: number;\n}\nexport interface QueueSendBatchOptions {\n  delaySeconds?: number;\n}\nexport interface MessageSendRequest<Body = unknown> {\n  body: Body;\n  contentType?: QueueContentType;\n  delaySeconds?: number;\n}\nexport interface QueueRetryBatch {\n  retry: boolean;\n  delaySeconds?: number;\n}\nexport interface QueueRetryMessage {\n  msgId: string;\n  delaySeconds?: number;\n}\nexport interface QueueRetryOptions {\n  delaySeconds?: number;\n}\nexport interface Message<Body = unknown> {\n  readonly id: string;\n  readonly timestamp: Date;\n  readonly body: Body;\n  readonly attempts: number;\n  retry(options?: QueueRetryOptions): void;\n  ack(): void;\n}\nexport interface QueueEvent<Body = unknown> extends ExtendableEvent {\n  readonly messages: readonly Message<Body>[];\n  readonly queue: string;\n  retryAll(options?: QueueRetryOptions): void;\n  ackAll(): void;\n}\nexport interface MessageBatch<Body = unknown> {\n  readonly messages: readonly Message<Body>[];\n  readonly queue: string;\n  retryAll(options?: QueueRetryOptions): void;\n  ackAll(): void;\n}\nexport interface R2Error extends Error {\n  readonly name: string;\n  readonly code: number;\n  readonly message: string;\n  readonly action: string;\n  readonly stack: any;\n}\nexport interface R2ListOptions {\n  limit?: number;\n  prefix?: string;\n  cursor?: string;\n  delimiter?: string;\n  startAfter?: string;\n  include?: (\"httpMetadata\" | \"customMetadata\")[];\n}\nexport declare abstract class R2Bucket {\n  head(key: string): Promise<R2Object | null>;\n  get(\n    key: string,\n    options: R2GetOptions & {\n      onlyIf: R2Conditional | Headers;\n    },\n  ): Promise<R2ObjectBody | R2Object | null>;\n  get(key: string, options?: R2GetOptions): Promise<R2ObjectBody | null>;\n  put(\n    key: string,\n    value:\n      | ReadableStream\n      | ArrayBuffer\n      | ArrayBufferView\n      | string\n      | null\n      | Blob,\n    options?: R2PutOptions & {\n      onlyIf: R2Conditional | Headers;\n    },\n  ): Promise<R2Object | null>;\n  put(\n    key: string,\n    value:\n      | ReadableStream\n      | ArrayBuffer\n      | ArrayBufferView\n      | string\n      | null\n      | Blob,\n    options?: R2PutOptions,\n  ): Promise<R2Object>;\n  createMultipartUpload(\n    key: string,\n    options?: R2MultipartOptions,\n  ): Promise<R2MultipartUpload>;\n  resumeMultipartUpload(key: string, uploadId: string): R2MultipartUpload;\n  delete(keys: string | string[]): Promise<void>;\n  list(options?: R2ListOptions): Promise<R2Objects>;\n}\nexport interface R2MultipartUpload {\n  readonly key: string;\n  readonly uploadId: string;\n  uploadPart(\n    partNumber: number,\n    value: ReadableStream | (ArrayBuffer | ArrayBufferView) | string | Blob,\n    options?: R2UploadPartOptions,\n  ): Promise<R2UploadedPart>;\n  abort(): Promise<void>;\n  complete(uploadedParts: R2UploadedPart[]): Promise<R2Object>;\n}\nexport interface R2UploadedPart {\n  partNumber: number;\n  etag: string;\n}\nexport declare abstract class R2Object {\n  readonly key: string;\n  readonly version: string;\n  readonly size: number;\n  readonly etag: string;\n  readonly httpEtag: string;\n  readonly checksums: R2Checksums;\n  readonly uploaded: Date;\n  readonly httpMetadata?: R2HTTPMetadata;\n  readonly customMetadata?: Record<string, string>;\n  readonly range?: R2Range;\n  readonly storageClass: string;\n  readonly ssecKeyMd5?: string;\n  writeHttpMetadata(headers: Headers): void;\n}\nexport interface R2ObjectBody extends R2Object {\n  get body(): ReadableStream;\n  get bodyUsed(): boolean;\n  arrayBuffer(): Promise<ArrayBuffer>;\n  bytes(): Promise<Uint8Array>;\n  text(): Promise<string>;\n  json<T>(): Promise<T>;\n  blob(): Promise<Blob>;\n}\nexport type R2Range =\n  | {\n      offset: number;\n      length?: number;\n    }\n  | {\n      offset?: number;\n      length: number;\n    }\n  | {\n      suffix: number;\n    };\nexport interface R2Conditional {\n  etagMatches?: string;\n  etagDoesNotMatch?: string;\n  uploadedBefore?: Date;\n  uploadedAfter?: Date;\n  secondsGranularity?: boolean;\n}\nexport interface R2GetOptions {\n  onlyIf?: R2Conditional | Headers;\n  range?: R2Range | Headers;\n  ssecKey?: ArrayBuffer | string;\n}\nexport interface R2PutOptions {\n  onlyIf?: R2Conditional | Headers;\n  httpMetadata?: R2HTTPMetadata | Headers;\n  customMetadata?: Record<string, string>;\n  md5?: (ArrayBuffer | ArrayBufferView) | string;\n  sha1?: (ArrayBuffer | ArrayBufferView) | string;\n  sha256?: (ArrayBuffer | ArrayBufferView) | string;\n  sha384?: (ArrayBuffer | ArrayBufferView) | string;\n  sha512?: (ArrayBuffer | ArrayBufferView) | string;\n  storageClass?: string;\n  ssecKey?: ArrayBuffer | string;\n}\nexport interface R2MultipartOptions {\n  httpMetadata?: R2HTTPMetadata | Headers;\n  customMetadata?: Record<string, string>;\n  storageClass?: string;\n  ssecKey?: ArrayBuffer | string;\n}\nexport interface R2Checksums {\n  readonly md5?: ArrayBuffer;\n  readonly sha1?: ArrayBuffer;\n  readonly sha256?: ArrayBuffer;\n  readonly sha384?: ArrayBuffer;\n  readonly sha512?: ArrayBuffer;\n  toJSON(): R2StringChecksums;\n}\nexport interface R2StringChecksums {\n  md5?: string;\n  sha1?: string;\n  sha256?: string;\n  sha384?: string;\n  sha512?: string;\n}\nexport interface R2HTTPMetadata {\n  contentType?: string;\n  contentLanguage?: string;\n  contentDisposition?: string;\n  contentEncoding?: string;\n  cacheControl?: string;\n  cacheExpiry?: Date;\n}\nexport type R2Objects = {\n  objects: R2Object[];\n  delimitedPrefixes: string[];\n} & (\n  | {\n      truncated: true;\n      cursor: string;\n    }\n  | {\n      truncated: false;\n    }\n);\nexport interface R2UploadPartOptions {\n  ssecKey?: ArrayBuffer | string;\n}\nexport declare abstract class JsRpcPromise {\n  then(handler: Function, errorHandler?: Function): any;\n  catch(errorHandler: Function): any;\n  finally(onFinally: Function): any;\n}\nexport declare abstract class JsRpcProperty {\n  then(handler: Function, errorHandler?: Function): any;\n  catch(errorHandler: Function): any;\n  finally(onFinally: Function): any;\n}\nexport declare abstract class ScheduledEvent extends ExtendableEvent {\n  readonly scheduledTime: number;\n  readonly cron: string;\n  noRetry(): void;\n}\nexport interface ScheduledController {\n  readonly scheduledTime: number;\n  readonly cron: string;\n  noRetry(): void;\n}\nexport interface QueuingStrategy<T = any> {\n  highWaterMark?: number | bigint;\n  size?: (chunk: T) => number | bigint;\n}\nexport interface UnderlyingSink<W = any> {\n  type?: string;\n  start?: (controller: WritableStreamDefaultController) => void | Promise<void>;\n  write?: (\n    chunk: W,\n    controller: WritableStreamDefaultController,\n  ) => void | Promise<void>;\n  abort?: (reason: any) => void | Promise<void>;\n  close?: () => void | Promise<void>;\n}\nexport interface UnderlyingByteSource {\n  type: \"bytes\";\n  autoAllocateChunkSize?: number;\n  start?: (controller: ReadableByteStreamController) => void | Promise<void>;\n  pull?: (controller: ReadableByteStreamController) => void | Promise<void>;\n  cancel?: (reason: any) => void | Promise<void>;\n}\nexport interface UnderlyingSource<R = any> {\n  type?: \"\" | undefined;\n  start?: (\n    controller: ReadableStreamDefaultController<R>,\n  ) => void | Promise<void>;\n  pull?: (\n    controller: ReadableStreamDefaultController<R>,\n  ) => void | Promise<void>;\n  cancel?: (reason: any) => void | Promise<void>;\n  expectedLength?: number | bigint;\n}\nexport interface Transformer<I = any, O = any> {\n  readableType?: string;\n  writableType?: string;\n  start?: (\n    controller: TransformStreamDefaultController<O>,\n  ) => void | Promise<void>;\n  transform?: (\n    chunk: I,\n    controller: TransformStreamDefaultController<O>,\n  ) => void | Promise<void>;\n  flush?: (\n    controller: TransformStreamDefaultController<O>,\n  ) => void | Promise<void>;\n  cancel?: (reason: any) => void | Promise<void>;\n  expectedLength?: number;\n}\nexport interface StreamPipeOptions {\n  preventAbort?: boolean;\n  preventCancel?: boolean;\n  /**\n   * Pipes this readable stream to a given writable stream destination. The way in which the piping process behaves under various error conditions can be customized with a number of passed options. It returns a promise that fulfills when the piping process completes successfully, or rejects if any errors were encountered.\n   *\n   * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader.\n   *\n   * Errors and closures of the source and destination streams propagate as follows:\n   *\n   * An error in this source readable stream will abort destination, unless preventAbort is truthy. The returned promise will be rejected with the source's error, or with any error that occurs during aborting the destination.\n   *\n   * An error in destination will cancel this source readable stream, unless preventCancel is truthy. The returned promise will be rejected with the destination's error, or with any error that occurs during canceling the source.\n   *\n   * When this source readable stream closes, destination will be closed, unless preventClose is truthy. The returned promise will be fulfilled once this process completes, unless an error is encountered while closing the destination, in which case it will be rejected with that error.\n   *\n   * If destination starts out closed or closing, this source readable stream will be canceled, unless preventCancel is true. The returned promise will be rejected with an error indicating piping to a closed stream failed, or with any error that occurs during canceling the source.\n   *\n   * The signal option can be set to an AbortSignal to allow aborting an ongoing pipe operation via the corresponding AbortController. In this case, this source readable stream will be canceled, and destination aborted, unless the respective options preventCancel or preventAbort are set.\n   */\n  preventClose?: boolean;\n  signal?: AbortSignal;\n}\nexport type ReadableStreamReadResult<R = any> =\n  | {\n      done: false;\n      value: R;\n    }\n  | {\n      done: true;\n      value?: undefined;\n    };\n/**\n * The `ReadableStream` interface of the Streams API represents a readable stream of byte data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream)\n */\nexport interface ReadableStream<R = any> {\n  /**\n   * The **`locked`** read-only property of the ReadableStream interface returns whether or not the readable stream is locked to a reader.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/locked)\n   */\n  get locked(): boolean;\n  /**\n   * The **`cancel()`** method of the ReadableStream interface returns a Promise that resolves when the stream is canceled.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/cancel)\n   */\n  cancel(reason?: any): Promise<void>;\n  /**\n   * The **`getReader()`** method of the ReadableStream interface creates a reader and locks the stream to it.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader)\n   */\n  getReader(): ReadableStreamDefaultReader<R>;\n  /**\n   * The **`getReader()`** method of the ReadableStream interface creates a reader and locks the stream to it.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader)\n   */\n  getReader(options: ReadableStreamGetReaderOptions): ReadableStreamBYOBReader;\n  /**\n   * The **`pipeThrough()`** method of the ReadableStream interface provides a chainable way of piping the current stream through a transform stream or any other writable/readable pair.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeThrough)\n   */\n  pipeThrough<T>(\n    transform: ReadableWritablePair<T, R>,\n    options?: StreamPipeOptions,\n  ): ReadableStream<T>;\n  /**\n   * The **`pipeTo()`** method of the ReadableStream interface pipes the current `ReadableStream` to a given WritableStream and returns a Promise that fulfills when the piping process completes successfully, or rejects if any errors were encountered.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeTo)\n   */\n  pipeTo(\n    destination: WritableStream<R>,\n    options?: StreamPipeOptions,\n  ): Promise<void>;\n  /**\n   * The **`tee()`** method of the two-element array containing the two resulting branches as new ReadableStream instances.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/tee)\n   */\n  tee(): [ReadableStream<R>, ReadableStream<R>];\n  values(options?: ReadableStreamValuesOptions): AsyncIterableIterator<R>;\n  [Symbol.asyncIterator](\n    options?: ReadableStreamValuesOptions,\n  ): AsyncIterableIterator<R>;\n}\n/**\n * The `ReadableStream` interface of the Streams API represents a readable stream of byte data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream)\n */\nexport declare const ReadableStream: {\n  prototype: ReadableStream;\n  new (\n    underlyingSource: UnderlyingByteSource,\n    strategy?: QueuingStrategy<Uint8Array>,\n  ): ReadableStream<Uint8Array>;\n  new <R = any>(\n    underlyingSource?: UnderlyingSource<R>,\n    strategy?: QueuingStrategy<R>,\n  ): ReadableStream<R>;\n};\n/**\n * The **`ReadableStreamDefaultReader`** interface of the Streams API represents a default reader that can be used to read stream data supplied from a network (such as a fetch request).\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader)\n */\nexport declare class ReadableStreamDefaultReader<R = any> {\n  constructor(stream: ReadableStream);\n  get closed(): Promise<void>;\n  cancel(reason?: any): Promise<void>;\n  /**\n   * The **`read()`** method of the ReadableStreamDefaultReader interface returns a Promise providing access to the next chunk in the stream's internal queue.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/read)\n   */\n  read(): Promise<ReadableStreamReadResult<R>>;\n  /**\n   * The **`releaseLock()`** method of the ReadableStreamDefaultReader interface releases the reader's lock on the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/releaseLock)\n   */\n  releaseLock(): void;\n}\n/**\n * The `ReadableStreamBYOBReader` interface of the Streams API defines a reader for a ReadableStream that supports zero-copy reading from an underlying byte source.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader)\n */\nexport declare class ReadableStreamBYOBReader {\n  constructor(stream: ReadableStream);\n  get closed(): Promise<void>;\n  cancel(reason?: any): Promise<void>;\n  /**\n   * The **`read()`** method of the ReadableStreamBYOBReader interface is used to read data into a view on a user-supplied buffer from an associated readable byte stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/read)\n   */\n  read<T extends ArrayBufferView>(\n    view: T,\n  ): Promise<ReadableStreamReadResult<T>>;\n  /**\n   * The **`releaseLock()`** method of the ReadableStreamBYOBReader interface releases the reader's lock on the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/releaseLock)\n   */\n  releaseLock(): void;\n  readAtLeast<T extends ArrayBufferView>(\n    minElements: number,\n    view: T,\n  ): Promise<ReadableStreamReadResult<T>>;\n}\nexport interface ReadableStreamBYOBReaderReadableStreamBYOBReaderReadOptions {\n  min?: number;\n}\nexport interface ReadableStreamGetReaderOptions {\n  /**\n   * Creates a ReadableStreamBYOBReader and locks the stream to the new reader.\n   *\n   * This call behaves the same way as the no-argument variant, except that it only works on readable byte streams, i.e. streams which were constructed specifically with the ability to handle \"bring your own buffer\" reading. The returned BYOB reader provides the ability to directly read individual chunks from the stream via its read() method, into developer-supplied buffers, allowing more precise control over allocation.\n   */\n  mode: \"byob\";\n}\n/**\n * The **`ReadableStreamBYOBRequest`** interface of the Streams API represents a 'pull request' for data from an underlying source that will made as a zero-copy transfer to a consumer (bypassing the stream's internal queues).\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest)\n */\nexport declare abstract class ReadableStreamBYOBRequest {\n  /**\n   * The **`view`** getter property of the ReadableStreamBYOBRequest interface returns the current view.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/view)\n   */\n  get view(): Uint8Array | null;\n  /**\n   * The **`respond()`** method of the ReadableStreamBYOBRequest interface is used to signal to the associated readable byte stream that the specified number of bytes were written into the ReadableStreamBYOBRequest.view.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respond)\n   */\n  respond(bytesWritten: number): void;\n  /**\n   * The **`respondWithNewView()`** method of the ReadableStreamBYOBRequest interface specifies a new view that the consumer of the associated readable byte stream should write to instead of ReadableStreamBYOBRequest.view.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respondWithNewView)\n   */\n  respondWithNewView(view: ArrayBuffer | ArrayBufferView): void;\n  get atLeast(): number | null;\n}\n/**\n * The **`ReadableStreamDefaultController`** interface of the Streams API represents a controller allowing control of a ReadableStream's state and internal queue.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController)\n */\nexport declare abstract class ReadableStreamDefaultController<R = any> {\n  /**\n   * The **`desiredSize`** read-only property of the required to fill the stream's internal queue.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/desiredSize)\n   */\n  get desiredSize(): number | null;\n  /**\n   * The **`close()`** method of the ReadableStreamDefaultController interface closes the associated stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/close)\n   */\n  close(): void;\n  /**\n   * The **`enqueue()`** method of the ```js-nolint enqueue(chunk) ``` - `chunk` - : The chunk to enqueue.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/enqueue)\n   */\n  enqueue(chunk?: R): void;\n  /**\n   * The **`error()`** method of the with the associated stream to error.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/error)\n   */\n  error(reason: any): void;\n}\n/**\n * The **`ReadableByteStreamController`** interface of the Streams API represents a controller for a readable byte stream.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController)\n */\nexport declare abstract class ReadableByteStreamController {\n  /**\n   * The **`byobRequest`** read-only property of the ReadableByteStreamController interface returns the current BYOB request, or `null` if there are no pending requests.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/byobRequest)\n   */\n  get byobRequest(): ReadableStreamBYOBRequest | null;\n  /**\n   * The **`desiredSize`** read-only property of the ReadableByteStreamController interface returns the number of bytes required to fill the stream's internal queue to its 'desired size'.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/desiredSize)\n   */\n  get desiredSize(): number | null;\n  /**\n   * The **`close()`** method of the ReadableByteStreamController interface closes the associated stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/close)\n   */\n  close(): void;\n  /**\n   * The **`enqueue()`** method of the ReadableByteStreamController interface enqueues a given chunk on the associated readable byte stream (the chunk is copied into the stream's internal queues).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/enqueue)\n   */\n  enqueue(chunk: ArrayBuffer | ArrayBufferView): void;\n  /**\n   * The **`error()`** method of the ReadableByteStreamController interface causes any future interactions with the associated stream to error with the specified reason.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/error)\n   */\n  error(reason: any): void;\n}\n/**\n * The **`WritableStreamDefaultController`** interface of the Streams API represents a controller allowing control of a WritableStream's state.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController)\n */\nexport declare abstract class WritableStreamDefaultController {\n  /**\n   * The read-only **`signal`** property of the WritableStreamDefaultController interface returns the AbortSignal associated with the controller.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/signal)\n   */\n  get signal(): AbortSignal;\n  /**\n   * The **`error()`** method of the with the associated stream to error.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/error)\n   */\n  error(reason?: any): void;\n}\n/**\n * The **`TransformStreamDefaultController`** interface of the Streams API provides methods to manipulate the associated ReadableStream and WritableStream.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController)\n */\nexport declare abstract class TransformStreamDefaultController<O = any> {\n  /**\n   * The **`desiredSize`** read-only property of the TransformStreamDefaultController interface returns the desired size to fill the queue of the associated ReadableStream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/desiredSize)\n   */\n  get desiredSize(): number | null;\n  /**\n   * The **`enqueue()`** method of the TransformStreamDefaultController interface enqueues the given chunk in the readable side of the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/enqueue)\n   */\n  enqueue(chunk?: O): void;\n  /**\n   * The **`error()`** method of the TransformStreamDefaultController interface errors both sides of the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/error)\n   */\n  error(reason: any): void;\n  /**\n   * The **`terminate()`** method of the TransformStreamDefaultController interface closes the readable side and errors the writable side of the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/terminate)\n   */\n  terminate(): void;\n}\nexport interface ReadableWritablePair<R = any, W = any> {\n  readable: ReadableStream<R>;\n  /**\n   * Provides a convenient, chainable way of piping this readable stream through a transform stream (or any other { writable, readable } pair). It simply pipes the stream into the writable side of the supplied pair, and returns the readable side for further use.\n   *\n   * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader.\n   */\n  writable: WritableStream<W>;\n}\n/**\n * The **`WritableStream`** interface of the Streams API provides a standard abstraction for writing streaming data to a destination, known as a sink.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream)\n */\nexport declare class WritableStream<W = any> {\n  constructor(\n    underlyingSink?: UnderlyingSink,\n    queuingStrategy?: QueuingStrategy,\n  );\n  /**\n   * The **`locked`** read-only property of the WritableStream interface returns a boolean indicating whether the `WritableStream` is locked to a writer.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/locked)\n   */\n  get locked(): boolean;\n  /**\n   * The **`abort()`** method of the WritableStream interface aborts the stream, signaling that the producer can no longer successfully write to the stream and it is to be immediately moved to an error state, with any queued writes discarded.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/abort)\n   */\n  abort(reason?: any): Promise<void>;\n  /**\n   * The **`close()`** method of the WritableStream interface closes the associated stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/close)\n   */\n  close(): Promise<void>;\n  /**\n   * The **`getWriter()`** method of the WritableStream interface returns a new instance of WritableStreamDefaultWriter and locks the stream to that instance.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/getWriter)\n   */\n  getWriter(): WritableStreamDefaultWriter<W>;\n}\n/**\n * The **`WritableStreamDefaultWriter`** interface of the Streams API is the object returned by WritableStream.getWriter() and once created locks the writer to the `WritableStream` ensuring that no other streams can write to the underlying sink.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter)\n */\nexport declare class WritableStreamDefaultWriter<W = any> {\n  constructor(stream: WritableStream);\n  /**\n   * The **`closed`** read-only property of the the stream errors or the writer's lock is released.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/closed)\n   */\n  get closed(): Promise<void>;\n  /**\n   * The **`ready`** read-only property of the that resolves when the desired size of the stream's internal queue transitions from non-positive to positive, signaling that it is no longer applying backpressure.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/ready)\n   */\n  get ready(): Promise<void>;\n  /**\n   * The **`desiredSize`** read-only property of the to fill the stream's internal queue.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/desiredSize)\n   */\n  get desiredSize(): number | null;\n  /**\n   * The **`abort()`** method of the the producer can no longer successfully write to the stream and it is to be immediately moved to an error state, with any queued writes discarded.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/abort)\n   */\n  abort(reason?: any): Promise<void>;\n  /**\n   * The **`close()`** method of the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/close)\n   */\n  close(): Promise<void>;\n  /**\n   * The **`write()`** method of the operation.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/write)\n   */\n  write(chunk?: W): Promise<void>;\n  /**\n   * The **`releaseLock()`** method of the corresponding stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/releaseLock)\n   */\n  releaseLock(): void;\n}\n/**\n * The **`TransformStream`** interface of the Streams API represents a concrete implementation of the pipe chain _transform stream_ concept.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream)\n */\nexport declare class TransformStream<I = any, O = any> {\n  constructor(\n    transformer?: Transformer<I, O>,\n    writableStrategy?: QueuingStrategy<I>,\n    readableStrategy?: QueuingStrategy<O>,\n  );\n  /**\n   * The **`readable`** read-only property of the TransformStream interface returns the ReadableStream instance controlled by this `TransformStream`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/readable)\n   */\n  get readable(): ReadableStream<O>;\n  /**\n   * The **`writable`** read-only property of the TransformStream interface returns the WritableStream instance controlled by this `TransformStream`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/writable)\n   */\n  get writable(): WritableStream<I>;\n}\nexport declare class FixedLengthStream extends IdentityTransformStream {\n  constructor(\n    expectedLength: number | bigint,\n    queuingStrategy?: IdentityTransformStreamQueuingStrategy,\n  );\n}\nexport declare class IdentityTransformStream extends TransformStream<\n  ArrayBuffer | ArrayBufferView,\n  Uint8Array\n> {\n  constructor(queuingStrategy?: IdentityTransformStreamQueuingStrategy);\n}\nexport interface IdentityTransformStreamQueuingStrategy {\n  highWaterMark?: number | bigint;\n}\nexport interface ReadableStreamValuesOptions {\n  preventCancel?: boolean;\n}\n/**\n * The **`CompressionStream`** interface of the Compression Streams API is an API for compressing a stream of data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CompressionStream)\n */\nexport declare class CompressionStream extends TransformStream<\n  ArrayBuffer | ArrayBufferView,\n  Uint8Array\n> {\n  constructor(format: \"gzip\" | \"deflate\" | \"deflate-raw\");\n}\n/**\n * The **`DecompressionStream`** interface of the Compression Streams API is an API for decompressing a stream of data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DecompressionStream)\n */\nexport declare class DecompressionStream extends TransformStream<\n  ArrayBuffer | ArrayBufferView,\n  Uint8Array\n> {\n  constructor(format: \"gzip\" | \"deflate\" | \"deflate-raw\");\n}\n/**\n * The **`TextEncoderStream`** interface of the Encoding API converts a stream of strings into bytes in the UTF-8 encoding.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoderStream)\n */\nexport declare class TextEncoderStream extends TransformStream<\n  string,\n  Uint8Array\n> {\n  constructor();\n  get encoding(): string;\n}\n/**\n * The **`TextDecoderStream`** interface of the Encoding API converts a stream of text in a binary encoding, such as UTF-8 etc., to a stream of strings.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoderStream)\n */\nexport declare class TextDecoderStream extends TransformStream<\n  ArrayBuffer | ArrayBufferView,\n  string\n> {\n  constructor(label?: string, options?: TextDecoderStreamTextDecoderStreamInit);\n  get encoding(): string;\n  get fatal(): boolean;\n  get ignoreBOM(): boolean;\n}\nexport interface TextDecoderStreamTextDecoderStreamInit {\n  fatal?: boolean;\n  ignoreBOM?: boolean;\n}\n/**\n * The **`ByteLengthQueuingStrategy`** interface of the Streams API provides a built-in byte length queuing strategy that can be used when constructing streams.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy)\n */\nexport declare class ByteLengthQueuingStrategy implements QueuingStrategy<ArrayBufferView> {\n  constructor(init: QueuingStrategyInit);\n  /**\n   * The read-only **`ByteLengthQueuingStrategy.highWaterMark`** property returns the total number of bytes that can be contained in the internal queue before backpressure is applied.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/highWaterMark)\n   */\n  get highWaterMark(): number;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/size) */\n  get size(): (chunk?: any) => number;\n}\n/**\n * The **`CountQueuingStrategy`** interface of the Streams API provides a built-in chunk counting queuing strategy that can be used when constructing streams.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy)\n */\nexport declare class CountQueuingStrategy implements QueuingStrategy {\n  constructor(init: QueuingStrategyInit);\n  /**\n   * The read-only **`CountQueuingStrategy.highWaterMark`** property returns the total number of chunks that can be contained in the internal queue before backpressure is applied.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/highWaterMark)\n   */\n  get highWaterMark(): number;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/size) */\n  get size(): (chunk?: any) => number;\n}\nexport interface QueuingStrategyInit {\n  /**\n   * Creates a new ByteLengthQueuingStrategy with the provided high water mark.\n   *\n   * Note that the provided high water mark will not be validated ahead of time. Instead, if it is negative, NaN, or not a number, the resulting ByteLengthQueuingStrategy will cause the corresponding stream constructor to throw.\n   */\n  highWaterMark: number;\n}\nexport interface ScriptVersion {\n  id?: string;\n  tag?: string;\n  message?: string;\n}\nexport declare abstract class TailEvent extends ExtendableEvent {\n  readonly events: TraceItem[];\n  readonly traces: TraceItem[];\n}\nexport interface TraceItem {\n  readonly event:\n    | (\n        | TraceItemFetchEventInfo\n        | TraceItemJsRpcEventInfo\n        | TraceItemScheduledEventInfo\n        | TraceItemAlarmEventInfo\n        | TraceItemQueueEventInfo\n        | TraceItemEmailEventInfo\n        | TraceItemTailEventInfo\n        | TraceItemCustomEventInfo\n        | TraceItemHibernatableWebSocketEventInfo\n      )\n    | null;\n  readonly eventTimestamp: number | null;\n  readonly logs: TraceLog[];\n  readonly exceptions: TraceException[];\n  readonly diagnosticsChannelEvents: TraceDiagnosticChannelEvent[];\n  readonly scriptName: string | null;\n  readonly entrypoint?: string;\n  readonly scriptVersion?: ScriptVersion;\n  readonly dispatchNamespace?: string;\n  readonly scriptTags?: string[];\n  readonly tailAttributes?: Record<string, boolean | number | string>;\n  readonly durableObjectId?: string;\n  readonly outcome: string;\n  readonly executionModel: string;\n  readonly truncated: boolean;\n  readonly cpuTime: number;\n  readonly wallTime: number;\n}\nexport interface TraceItemAlarmEventInfo {\n  readonly scheduledTime: Date;\n}\nexport interface TraceItemCustomEventInfo {}\nexport interface TraceItemScheduledEventInfo {\n  readonly scheduledTime: number;\n  readonly cron: string;\n}\nexport interface TraceItemQueueEventInfo {\n  readonly queue: string;\n  readonly batchSize: number;\n}\nexport interface TraceItemEmailEventInfo {\n  readonly mailFrom: string;\n  readonly rcptTo: string;\n  readonly rawSize: number;\n}\nexport interface TraceItemTailEventInfo {\n  readonly consumedEvents: TraceItemTailEventInfoTailItem[];\n}\nexport interface TraceItemTailEventInfoTailItem {\n  readonly scriptName: string | null;\n}\nexport interface TraceItemFetchEventInfo {\n  readonly response?: TraceItemFetchEventInfoResponse;\n  readonly request: TraceItemFetchEventInfoRequest;\n}\nexport interface TraceItemFetchEventInfoRequest {\n  readonly cf?: any;\n  readonly headers: Record<string, string>;\n  readonly method: string;\n  readonly url: string;\n  getUnredacted(): TraceItemFetchEventInfoRequest;\n}\nexport interface TraceItemFetchEventInfoResponse {\n  readonly status: number;\n}\nexport interface TraceItemJsRpcEventInfo {\n  readonly rpcMethod: string;\n}\nexport interface TraceItemHibernatableWebSocketEventInfo {\n  readonly getWebSocketEvent:\n    | TraceItemHibernatableWebSocketEventInfoMessage\n    | TraceItemHibernatableWebSocketEventInfoClose\n    | TraceItemHibernatableWebSocketEventInfoError;\n}\nexport interface TraceItemHibernatableWebSocketEventInfoMessage {\n  readonly webSocketEventType: string;\n}\nexport interface TraceItemHibernatableWebSocketEventInfoClose {\n  readonly webSocketEventType: string;\n  readonly code: number;\n  readonly wasClean: boolean;\n}\nexport interface TraceItemHibernatableWebSocketEventInfoError {\n  readonly webSocketEventType: string;\n}\nexport interface TraceLog {\n  readonly timestamp: number;\n  readonly level: string;\n  readonly message: any;\n}\nexport interface TraceException {\n  readonly timestamp: number;\n  readonly message: string;\n  readonly name: string;\n  readonly stack?: string;\n}\nexport interface TraceDiagnosticChannelEvent {\n  readonly timestamp: number;\n  readonly channel: string;\n  readonly message: any;\n}\nexport interface TraceMetrics {\n  readonly cpuTime: number;\n  readonly wallTime: number;\n}\nexport interface UnsafeTraceMetrics {\n  fromTrace(item: TraceItem): TraceMetrics;\n}\n/**\n * The **`URL`** interface is used to parse, construct, normalize, and encode URL.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL)\n */\nexport declare class URL {\n  constructor(url: string | URL, base?: string | URL);\n  /**\n   * The **`origin`** read-only property of the URL interface returns a string containing the Unicode serialization of the origin of the represented URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/origin)\n   */\n  get origin(): string;\n  /**\n   * The **`href`** property of the URL interface is a string containing the whole URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href)\n   */\n  get href(): string;\n  /**\n   * The **`href`** property of the URL interface is a string containing the whole URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href)\n   */\n  set href(value: string);\n  /**\n   * The **`protocol`** property of the URL interface is a string containing the protocol or scheme of the URL, including the final `':'`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol)\n   */\n  get protocol(): string;\n  /**\n   * The **`protocol`** property of the URL interface is a string containing the protocol or scheme of the URL, including the final `':'`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol)\n   */\n  set protocol(value: string);\n  /**\n   * The **`username`** property of the URL interface is a string containing the username component of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username)\n   */\n  get username(): string;\n  /**\n   * The **`username`** property of the URL interface is a string containing the username component of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username)\n   */\n  set username(value: string);\n  /**\n   * The **`password`** property of the URL interface is a string containing the password component of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password)\n   */\n  get password(): string;\n  /**\n   * The **`password`** property of the URL interface is a string containing the password component of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password)\n   */\n  set password(value: string);\n  /**\n   * The **`host`** property of the URL interface is a string containing the host, which is the URL.hostname, and then, if the port of the URL is nonempty, a `':'`, followed by the URL.port of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host)\n   */\n  get host(): string;\n  /**\n   * The **`host`** property of the URL interface is a string containing the host, which is the URL.hostname, and then, if the port of the URL is nonempty, a `':'`, followed by the URL.port of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host)\n   */\n  set host(value: string);\n  /**\n   * The **`hostname`** property of the URL interface is a string containing either the domain name or IP address of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname)\n   */\n  get hostname(): string;\n  /**\n   * The **`hostname`** property of the URL interface is a string containing either the domain name or IP address of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname)\n   */\n  set hostname(value: string);\n  /**\n   * The **`port`** property of the URL interface is a string containing the port number of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port)\n   */\n  get port(): string;\n  /**\n   * The **`port`** property of the URL interface is a string containing the port number of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port)\n   */\n  set port(value: string);\n  /**\n   * The **`pathname`** property of the URL interface represents a location in a hierarchical structure.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname)\n   */\n  get pathname(): string;\n  /**\n   * The **`pathname`** property of the URL interface represents a location in a hierarchical structure.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname)\n   */\n  set pathname(value: string);\n  /**\n   * The **`search`** property of the URL interface is a search string, also called a _query string_, that is a string containing a `'?'` followed by the parameters of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search)\n   */\n  get search(): string;\n  /**\n   * The **`search`** property of the URL interface is a search string, also called a _query string_, that is a string containing a `'?'` followed by the parameters of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search)\n   */\n  set search(value: string);\n  /**\n   * The **`hash`** property of the URL interface is a string containing a `'#'` followed by the fragment identifier of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash)\n   */\n  get hash(): string;\n  /**\n   * The **`hash`** property of the URL interface is a string containing a `'#'` followed by the fragment identifier of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash)\n   */\n  set hash(value: string);\n  /**\n   * The **`searchParams`** read-only property of the access to the [MISSING: httpmethod('GET')] decoded query arguments contained in the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/searchParams)\n   */\n  get searchParams(): URLSearchParams;\n  /**\n   * The **`toJSON()`** method of the URL interface returns a string containing a serialized version of the URL, although in practice it seems to have the same effect as ```js-nolint toJSON() ``` None.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/toJSON)\n   */\n  toJSON(): string;\n  /*function toString() { [native code] }*/\n  toString(): string;\n  /**\n   * The **`URL.canParse()`** static method of the URL interface returns a boolean indicating whether or not an absolute URL, or a relative URL combined with a base URL, are parsable and valid.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/canParse_static)\n   */\n  static canParse(url: string, base?: string): boolean;\n  /**\n   * The **`URL.parse()`** static method of the URL interface returns a newly created URL object representing the URL defined by the parameters.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/parse_static)\n   */\n  static parse(url: string, base?: string): URL | null;\n  /**\n   * The **`createObjectURL()`** static method of the URL interface creates a string containing a URL representing the object given in the parameter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/createObjectURL_static)\n   */\n  static createObjectURL(object: File | Blob): string;\n  /**\n   * The **`revokeObjectURL()`** static method of the URL interface releases an existing object URL which was previously created by calling Call this method when you've finished using an object URL to let the browser know not to keep the reference to the file any longer.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/revokeObjectURL_static)\n   */\n  static revokeObjectURL(object_url: string): void;\n}\n/**\n * The **`URLSearchParams`** interface defines utility methods to work with the query string of a URL.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams)\n */\nexport declare class URLSearchParams {\n  constructor(\n    init?: Iterable<Iterable<string>> | Record<string, string> | string,\n  );\n  /**\n   * The **`size`** read-only property of the URLSearchParams interface indicates the total number of search parameter entries.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/size)\n   */\n  get size(): number;\n  /**\n   * The **`append()`** method of the URLSearchParams interface appends a specified key/value pair as a new search parameter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/append)\n   */\n  append(name: string, value: string): void;\n  /**\n   * The **`delete()`** method of the URLSearchParams interface deletes specified parameters and their associated value(s) from the list of all search parameters.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/delete)\n   */\n  delete(name: string, value?: string): void;\n  /**\n   * The **`get()`** method of the URLSearchParams interface returns the first value associated to the given search parameter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/get)\n   */\n  get(name: string): string | null;\n  /**\n   * The **`getAll()`** method of the URLSearchParams interface returns all the values associated with a given search parameter as an array.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/getAll)\n   */\n  getAll(name: string): string[];\n  /**\n   * The **`has()`** method of the URLSearchParams interface returns a boolean value that indicates whether the specified parameter is in the search parameters.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/has)\n   */\n  has(name: string, value?: string): boolean;\n  /**\n   * The **`set()`** method of the URLSearchParams interface sets the value associated with a given search parameter to the given value.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/set)\n   */\n  set(name: string, value: string): void;\n  /**\n   * The **`URLSearchParams.sort()`** method sorts all key/value pairs contained in this object in place and returns `undefined`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/sort)\n   */\n  sort(): void;\n  /* Returns an array of key, value pairs for every entry in the search params. */\n  entries(): IterableIterator<[key: string, value: string]>;\n  /* Returns a list of keys in the search params. */\n  keys(): IterableIterator<string>;\n  /* Returns a list of values in the search params. */\n  values(): IterableIterator<string>;\n  forEach<This = unknown>(\n    callback: (\n      this: This,\n      value: string,\n      key: string,\n      parent: URLSearchParams,\n    ) => void,\n    thisArg?: This,\n  ): void;\n  /*function toString() { [native code] }*/\n  toString(): string;\n  [Symbol.iterator](): IterableIterator<[key: string, value: string]>;\n}\nexport declare class URLPattern {\n  constructor(\n    input?: string | URLPatternInit,\n    baseURL?: string | URLPatternOptions,\n    patternOptions?: URLPatternOptions,\n  );\n  get protocol(): string;\n  get username(): string;\n  get password(): string;\n  get hostname(): string;\n  get port(): string;\n  get pathname(): string;\n  get search(): string;\n  get hash(): string;\n  get hasRegExpGroups(): boolean;\n  test(input?: string | URLPatternInit, baseURL?: string): boolean;\n  exec(\n    input?: string | URLPatternInit,\n    baseURL?: string,\n  ): URLPatternResult | null;\n}\nexport interface URLPatternInit {\n  protocol?: string;\n  username?: string;\n  password?: string;\n  hostname?: string;\n  port?: string;\n  pathname?: string;\n  search?: string;\n  hash?: string;\n  baseURL?: string;\n}\nexport interface URLPatternComponentResult {\n  input: string;\n  groups: Record<string, string>;\n}\nexport interface URLPatternResult {\n  inputs: (string | URLPatternInit)[];\n  protocol: URLPatternComponentResult;\n  username: URLPatternComponentResult;\n  password: URLPatternComponentResult;\n  hostname: URLPatternComponentResult;\n  port: URLPatternComponentResult;\n  pathname: URLPatternComponentResult;\n  search: URLPatternComponentResult;\n  hash: URLPatternComponentResult;\n}\nexport interface URLPatternOptions {\n  ignoreCase?: boolean;\n}\n/**\n * A `CloseEvent` is sent to clients using WebSockets when the connection is closed.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent)\n */\nexport declare class CloseEvent extends Event {\n  constructor(type: string, initializer?: CloseEventInit);\n  /**\n   * The **`code`** read-only property of the CloseEvent interface returns a WebSocket connection close code indicating the reason the connection was closed.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/code)\n   */\n  readonly code: number;\n  /**\n   * The **`reason`** read-only property of the CloseEvent interface returns the WebSocket connection close reason the server gave for closing the connection; that is, a concise human-readable prose explanation for the closure.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/reason)\n   */\n  readonly reason: string;\n  /**\n   * The **`wasClean`** read-only property of the CloseEvent interface returns `true` if the connection closed cleanly.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/wasClean)\n   */\n  readonly wasClean: boolean;\n}\nexport interface CloseEventInit {\n  code?: number;\n  reason?: string;\n  wasClean?: boolean;\n}\nexport type WebSocketEventMap = {\n  close: CloseEvent;\n  message: MessageEvent;\n  open: Event;\n  error: ErrorEvent;\n};\n/**\n * The `WebSocket` object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket)\n */\nexport declare var WebSocket: {\n  prototype: WebSocket;\n  new (url: string, protocols?: string[] | string): WebSocket;\n  readonly READY_STATE_CONNECTING: number;\n  readonly CONNECTING: number;\n  readonly READY_STATE_OPEN: number;\n  readonly OPEN: number;\n  readonly READY_STATE_CLOSING: number;\n  readonly CLOSING: number;\n  readonly READY_STATE_CLOSED: number;\n  readonly CLOSED: number;\n};\n/**\n * The `WebSocket` object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket)\n */\nexport interface WebSocket extends EventTarget<WebSocketEventMap> {\n  accept(options?: WebSocketAcceptOptions): void;\n  /**\n   * The **`WebSocket.send()`** method enqueues the specified data to be transmitted to the server over the WebSocket connection, increasing the value of `bufferedAmount` by the number of bytes needed to contain the data.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/send)\n   */\n  send(message: (ArrayBuffer | ArrayBufferView) | string): void;\n  /**\n   * The **`WebSocket.close()`** method closes the already `CLOSED`, this method does nothing.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/close)\n   */\n  close(code?: number, reason?: string): void;\n  serializeAttachment(attachment: any): void;\n  deserializeAttachment(): any | null;\n  /**\n   * The **`WebSocket.readyState`** read-only property returns the current state of the WebSocket connection.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/readyState)\n   */\n  readyState: number;\n  /**\n   * The **`WebSocket.url`** read-only property returns the absolute URL of the WebSocket as resolved by the constructor.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/url)\n   */\n  url: string | null;\n  /**\n   * The **`WebSocket.protocol`** read-only property returns the name of the sub-protocol the server selected; this will be one of the strings specified in the `protocols` parameter when creating the WebSocket object, or the empty string if no connection is established.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/protocol)\n   */\n  protocol: string | null;\n  /**\n   * The **`WebSocket.extensions`** read-only property returns the extensions selected by the server.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/extensions)\n   */\n  extensions: string | null;\n  /**\n   * The **`WebSocket.binaryType`** property controls the type of binary data being received over the WebSocket connection.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/binaryType)\n   */\n  binaryType: \"blob\" | \"arraybuffer\";\n}\nexport interface WebSocketAcceptOptions {\n  /**\n   * When set to `true`, receiving a server-initiated WebSocket Close frame will not\n   * automatically send a reciprocal Close frame, leaving the connection in a half-open\n   * state. This is useful for proxying scenarios where you need to coordinate closing\n   * both sides independently. Defaults to `false` when the\n   * `no_web_socket_half_open_by_default` compatibility flag is enabled.\n   */\n  allowHalfOpen?: boolean;\n}\nexport declare const WebSocketPair: {\n  new (): {\n    0: WebSocket;\n    1: WebSocket;\n  };\n};\nexport interface SqlStorage {\n  exec<T extends Record<string, SqlStorageValue>>(\n    query: string,\n    ...bindings: any[]\n  ): SqlStorageCursor<T>;\n  prepare(query: string): SqlStorageStatement;\n  ingest(query: string): SqlStorageIngestResult;\n  setMaxPageCountForTest(count: number): void;\n  get databaseSize(): number;\n  Cursor: typeof SqlStorageCursor;\n  Statement: typeof SqlStorageStatement;\n}\nexport declare abstract class SqlStorageStatement {}\nexport type SqlStorageValue = ArrayBuffer | string | number | null;\nexport declare abstract class SqlStorageCursor<\n  T extends Record<string, SqlStorageValue>,\n> {\n  next():\n    | {\n        done?: false;\n        value: T;\n      }\n    | {\n        done: true;\n        value?: never;\n      };\n  toArray(): T[];\n  one(): T;\n  raw<U extends SqlStorageValue[]>(): IterableIterator<U>;\n  columnNames: string[];\n  get rowsRead(): number;\n  get rowsWritten(): number;\n  get reusedCachedQueryForTest(): boolean;\n  [Symbol.iterator](): IterableIterator<T>;\n}\nexport interface SqlStorageIngestResult {\n  remainder: string;\n  rowsRead: number;\n  rowsWritten: number;\n  statementCount: number;\n}\nexport interface Socket {\n  get readable(): ReadableStream;\n  get writable(): WritableStream;\n  get closed(): Promise<void>;\n  get opened(): Promise<SocketInfo>;\n  get upgraded(): boolean;\n  get secureTransport(): \"on\" | \"off\" | \"starttls\";\n  close(): Promise<void>;\n  startTls(options?: TlsOptions): Socket;\n}\nexport interface SocketOptions {\n  secureTransport?: string;\n  allowHalfOpen: boolean;\n  highWaterMark?: number | bigint;\n}\nexport interface SocketAddress {\n  hostname: string;\n  port: number;\n}\nexport interface TlsOptions {\n  expectedServerHostname?: string;\n}\nexport interface SocketInfo {\n  remoteAddress?: string;\n  localAddress?: string;\n}\n/**\n * The **`EventSource`** interface is web content's interface to server-sent events.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource)\n */\nexport declare class EventSource extends EventTarget {\n  constructor(url: string, init?: EventSourceEventSourceInit);\n  /**\n   * The **`close()`** method of the EventSource interface closes the connection, if one is made, and sets the ```js-nolint close() ``` None.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close)\n   */\n  close(): void;\n  /**\n   * The **`url`** read-only property of the URL of the source.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url)\n   */\n  get url(): string;\n  /**\n   * The **`withCredentials`** read-only property of the the `EventSource` object was instantiated with CORS credentials set.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials)\n   */\n  get withCredentials(): boolean;\n  /**\n   * The **`readyState`** read-only property of the connection.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState)\n   */\n  get readyState(): number;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */\n  get onopen(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */\n  set onopen(value: any | null);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */\n  get onmessage(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */\n  set onmessage(value: any | null);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */\n  get onerror(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */\n  set onerror(value: any | null);\n  static readonly CONNECTING: number;\n  static readonly OPEN: number;\n  static readonly CLOSED: number;\n  static from(stream: ReadableStream): EventSource;\n}\nexport interface EventSourceEventSourceInit {\n  withCredentials?: boolean;\n  fetcher?: Fetcher;\n}\nexport interface Container {\n  get running(): boolean;\n  start(options?: ContainerStartupOptions): void;\n  monitor(): Promise<void>;\n  destroy(error?: any): Promise<void>;\n  signal(signo: number): void;\n  getTcpPort(port: number): Fetcher;\n  setInactivityTimeout(durationMs: number | bigint): Promise<void>;\n  interceptOutboundHttp(addr: string, binding: Fetcher): Promise<void>;\n  interceptAllOutboundHttp(binding: Fetcher): Promise<void>;\n}\nexport interface ContainerStartupOptions {\n  entrypoint?: string[];\n  enableInternet: boolean;\n  env?: Record<string, string>;\n  hardTimeout?: number | bigint;\n  labels?: Record<string, string>;\n}\n/**\n * The **`FileSystemHandle`** interface of the File System API is an object which represents a file or directory entry.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemHandle)\n */\nexport declare abstract class FileSystemHandle {\n  /**\n   * The **`kind`** read-only property of the `'file'` if the associated entry is a file or `'directory'`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemHandle/kind)\n   */\n  get kind(): string;\n  /**\n   * The **`name`** read-only property of the handle.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemHandle/name)\n   */\n  get name(): string;\n  /**\n   * The **`isSameEntry()`** method of the ```js-nolint isSameEntry(fileSystemHandle) ``` - FileSystemHandle - : The `FileSystemHandle` to match against the handle on which the method is invoked.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemHandle/isSameEntry)\n   */\n  isSameEntry(other: FileSystemHandle): Promise<boolean>;\n  getUniqueId(): Promise<string>;\n  remove(options?: FileSystemHandleRemoveOptions): Promise<void>;\n}\n/**\n * The **`FileSystemFileHandle`** interface of the File System API represents a handle to a file system entry.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle)\n */\nexport declare abstract class FileSystemFileHandle extends FileSystemHandle {\n  /**\n   * The **`getFile()`** method of the If the file on disk changes or is removed after this method is called, the returned ```js-nolint getFile() ``` None.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle/getFile)\n   */\n  getFile(): Promise<File>;\n  /**\n   * The **`createWritable()`** method of the FileSystemFileHandle interface creates a FileSystemWritableFileStream that can be used to write to a file.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle/createWritable)\n   */\n  createWritable(\n    options?: FileSystemFileHandleFileSystemCreateWritableOptions,\n  ): Promise<FileSystemWritableFileStream>;\n}\n/**\n * The **`FileSystemDirectoryHandle`** interface of the File System API provides a handle to a file system directory.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle)\n */\nexport declare abstract class FileSystemDirectoryHandle extends FileSystemHandle {\n  /**\n   * The **`getFileHandle()`** method of the directory the method is called.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/getFileHandle)\n   */\n  getFileHandle(\n    name: string,\n    options?: FileSystemDirectoryHandleFileSystemGetFileOptions,\n  ): Promise<FileSystemFileHandle>;\n  /**\n   * The **`getDirectoryHandle()`** method of the within the directory handle on which the method is called.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/getDirectoryHandle)\n   */\n  getDirectoryHandle(\n    name: string,\n    options?: FileSystemDirectoryHandleFileSystemGetDirectoryOptions,\n  ): Promise<FileSystemDirectoryHandle>;\n  /**\n   * The **`removeEntry()`** method of the directory handle contains a file or directory called the name specified.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/removeEntry)\n   */\n  removeEntry(\n    name: string,\n    options?: FileSystemDirectoryHandleFileSystemRemoveOptions,\n  ): Promise<void>;\n  /**\n   * The **`resolve()`** method of the directory names from the parent handle to the specified child entry, with the name of the child entry as the last array item.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/resolve)\n   */\n  resolve(possibleDescendant: FileSystemHandle): Promise<string[]>;\n  entries(): AsyncIterableIterator<[string, FileSystemHandle]>;\n  keys(): AsyncIterableIterator<string>;\n  values(): AsyncIterableIterator<FileSystemHandle>;\n  forEach(\n    callback: (\n      param0: string,\n      param1: FileSystemHandle,\n      param2: FileSystemDirectoryHandle,\n    ) => void,\n    thisArg?: any,\n  ): void;\n  [Symbol.asyncIterator](): AsyncIterableIterator<[string, FileSystemHandle]>;\n}\n/**\n * The **`FileSystemWritableFileStream`** interface of the File System API is a WritableStream object with additional convenience methods, which operates on a single file on disk.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemWritableFileStream)\n */\nexport declare abstract class FileSystemWritableFileStream extends WritableStream {\n  /**\n   * The **`write()`** method of the FileSystemWritableFileStream interface writes content into the file the method is called on, at the current file cursor offset.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemWritableFileStream/write)\n   */\n  write(\n    data:\n      | Blob\n      | (ArrayBuffer | ArrayBufferView)\n      | string\n      | FileSystemFileWriteParams,\n  ): Promise<void>;\n  /**\n   * The **`seek()`** method of the FileSystemWritableFileStream interface updates the current file cursor offset to the position (in bytes) specified when calling the method.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemWritableFileStream/seek)\n   */\n  seek(position: number): Promise<void>;\n  /**\n   * The **`truncate()`** method of the FileSystemWritableFileStream interface resizes the file associated with the stream to the specified size in bytes.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FileSystemWritableFileStream/truncate)\n   */\n  truncate(size: number): Promise<void>;\n}\n/**\n * The **`StorageManager`** interface of the Storage API provides an interface for managing persistence permissions and estimating available storage.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/StorageManager)\n */\nexport declare abstract class StorageManager {\n  /**\n   * The **`getDirectory()`** method of the StorageManager interface is used to obtain a reference to a FileSystemDirectoryHandle object allowing access to a directory and its contents, stored in the origin private file system (OPFS).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/StorageManager/getDirectory)\n   */\n  getDirectory(): Promise<FileSystemDirectoryHandle>;\n}\nexport interface FileSystemFileHandleFileSystemCreateWritableOptions {\n  keepExistingData?: boolean;\n}\nexport interface FileSystemDirectoryHandleFileSystemGetFileOptions {\n  create: boolean;\n}\nexport interface FileSystemDirectoryHandleFileSystemGetDirectoryOptions {\n  create: boolean;\n}\nexport interface FileSystemDirectoryHandleFileSystemRemoveOptions {\n  recursive: boolean;\n}\nexport interface FileSystemFileWriteParams {\n  type: string;\n  size?: number;\n  position?: number;\n  data?: (Blob | (ArrayBuffer | ArrayBufferView) | string) | null;\n}\nexport interface FileSystemHandleRemoveOptions {\n  recursive?: boolean;\n}\n/**\n * The **`MessagePort`** interface of the Channel Messaging API represents one of the two ports of a MessageChannel, allowing messages to be sent from one port and listening out for them arriving at the other.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort)\n */\nexport declare abstract class MessagePort extends EventTarget {\n  /**\n   * The **`postMessage()`** method of the transfers ownership of objects to other browsing contexts.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/postMessage)\n   */\n  postMessage(\n    data?: any,\n    options?: any[] | MessagePortPostMessageOptions,\n  ): void;\n  /**\n   * The **`close()`** method of the MessagePort interface disconnects the port, so it is no longer active.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/close)\n   */\n  close(): void;\n  /**\n   * The **`start()`** method of the MessagePort interface starts the sending of messages queued on the port.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/start)\n   */\n  start(): void;\n  get onmessage(): any | null;\n  set onmessage(value: any | null);\n}\n/**\n * The **`MessageChannel`** interface of the Channel Messaging API allows us to create a new message channel and send data through it via its two MessagePort properties.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel)\n */\nexport declare class MessageChannel {\n  constructor();\n  /**\n   * The **`port1`** read-only property of the the port attached to the context that originated the channel.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel/port1)\n   */\n  readonly port1: MessagePort;\n  /**\n   * The **`port2`** read-only property of the the port attached to the context at the other end of the channel, which the message is initially sent to.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel/port2)\n   */\n  readonly port2: MessagePort;\n}\nexport interface MessagePortPostMessageOptions {\n  transfer?: any[];\n}\nexport type LoopbackForExport<\n  T extends\n    | (new (...args: any[]) => Rpc.EntrypointBranded)\n    | ExportedHandler<any, any, any>\n    | undefined = undefined,\n> = T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded\n  ? LoopbackServiceStub<InstanceType<T>>\n  : T extends new (...args: any[]) => Rpc.DurableObjectBranded\n    ? LoopbackDurableObjectClass<InstanceType<T>>\n    : T extends ExportedHandler<any, any, any>\n      ? LoopbackServiceStub<undefined>\n      : undefined;\nexport type LoopbackServiceStub<\n  T extends Rpc.WorkerEntrypointBranded | undefined = undefined,\n> = Fetcher<T> &\n  (T extends CloudflareWorkersModule.WorkerEntrypoint<any, infer Props>\n    ? (opts: {\n        props?: Props;\n        version?: {\n          cohort?: string | null;\n        };\n      }) => Fetcher<T>\n    : (opts: {\n        props?: any;\n        version?: {\n          cohort?: string | null;\n        };\n      }) => Fetcher<T>);\nexport type LoopbackDurableObjectClass<\n  T extends Rpc.DurableObjectBranded | undefined = undefined,\n> = DurableObjectClass<T> &\n  (T extends CloudflareWorkersModule.DurableObject<any, infer Props>\n    ? (opts: { props?: Props }) => DurableObjectClass<T>\n    : (opts: { props?: any }) => DurableObjectClass<T>);\nexport interface LoopbackDurableObjectNamespace extends DurableObjectNamespace {}\nexport interface LoopbackColoLocalActorNamespace extends ColoLocalActorNamespace {}\nexport interface SyncKvStorage {\n  get<T = unknown>(key: string): T | undefined;\n  list<T = unknown>(options?: SyncKvListOptions): Iterable<[string, T]>;\n  put<T>(key: string, value: T): void;\n  delete(key: string): boolean;\n}\nexport interface SyncKvListOptions {\n  start?: string;\n  startAfter?: string;\n  end?: string;\n  prefix?: string;\n  reverse?: boolean;\n  limit?: number;\n}\nexport interface WorkerStub {\n  getEntrypoint<T extends Rpc.WorkerEntrypointBranded | undefined>(\n    name?: string,\n    options?: WorkerStubEntrypointOptions,\n  ): Fetcher<T>;\n  getDurableObjectClass<T extends Rpc.DurableObjectBranded | undefined>(\n    name?: string,\n    options?: WorkerStubEntrypointOptions,\n  ): DurableObjectClass<T>;\n}\nexport interface WorkerStubEntrypointOptions {\n  props?: any;\n}\nexport interface WorkerLoader {\n  get(\n    name: string | null,\n    getCode: () => WorkerLoaderWorkerCode | Promise<WorkerLoaderWorkerCode>,\n  ): WorkerStub;\n  load(code: WorkerLoaderWorkerCode): WorkerStub;\n}\nexport interface WorkerLoaderModule {\n  js?: string;\n  cjs?: string;\n  text?: string;\n  data?: ArrayBuffer;\n  json?: any;\n  py?: string;\n  wasm?: ArrayBuffer;\n}\nexport interface WorkerLoaderWorkerCode {\n  compatibilityDate: string;\n  compatibilityFlags?: string[];\n  allowExperimental?: boolean;\n  mainModule: string;\n  modules: Record<string, WorkerLoaderModule | string>;\n  env?: any;\n  globalOutbound?: Fetcher | null;\n  tails?: Fetcher[];\n  streamingTails?: Fetcher[];\n}\n/**\n * The Workers runtime supports a subset of the Performance API, used to measure timing and performance,\n * as well as timing of subrequests and other operations.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/)\n */\nexport declare abstract class Performance extends EventTarget {\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancetimeorigin) */\n  get timeOrigin(): number;\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancenow) */\n  now(): number;\n  get eventCounts(): EventCounts;\n  /**\n   * The **`clearMarks()`** method removes all or specific PerformanceMark objects from the browser's performance timeline.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/clearMarks)\n   */\n  clearMarks(name?: string): void;\n  /**\n   * The **`clearMeasures()`** method removes all or specific PerformanceMeasure objects from the browser's performance timeline.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/clearMeasures)\n   */\n  clearMeasures(name?: string): void;\n  /**\n   * The **`clearResourceTimings()`** method removes all performance entries with an PerformanceEntry.entryType of `'resource'` from the browser's performance timeline and sets the size of the performance resource data buffer to zero.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/clearResourceTimings)\n   */\n  clearResourceTimings(): void;\n  /**\n   * The **`getEntries()`** method returns an array of all PerformanceEntry objects currently present in the performance timeline.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/getEntries)\n   */\n  getEntries(): PerformanceEntry[];\n  /**\n   * The **`getEntriesByName()`** method returns an array of PerformanceEntry objects currently present in the performance timeline with the given _name_ and _type_.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/getEntriesByName)\n   */\n  getEntriesByName(name: string, type?: string): PerformanceEntry[];\n  /**\n   * The **`getEntriesByType()`** method returns an array of PerformanceEntry objects currently present in the performance timeline for a given _type_.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/getEntriesByType)\n   */\n  getEntriesByType(type: string): PerformanceEntry[];\n  /**\n   * The **`mark()`** method creates a named PerformanceMark object representing a high resolution timestamp marker in the browser's performance timeline.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/mark)\n   */\n  mark(name: string, options?: PerformanceMarkOptions): PerformanceMark;\n  /**\n   * The **`measure()`** method creates a named PerformanceMeasure object representing a time measurement between two marks in the browser's performance timeline.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/measure)\n   */\n  measure(\n    measureName: string,\n    measureOptionsOrStartMark?: PerformanceMeasureOptions | string,\n    maybeEndMark?: string,\n  ): PerformanceMeasure;\n  /**\n   * The **`setResourceTimingBufferSize()`** method sets the desired size of the browser's resource timing buffer which stores the `'resource'` performance entries.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/setResourceTimingBufferSize)\n   */\n  setResourceTimingBufferSize(size: number): void;\n  /**\n   * The **`toJSON()`** method of the Performance interface is a Serialization; it returns a JSON representation of the Performance object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/toJSON)\n   */\n  toJSON(): object;\n  get nodeTiming(): PerformanceNodeTiming;\n  eventLoopUtilization(): PerformanceEventLoopUtilization;\n  markResourceTiming(): void;\n  timerify(fn: () => void): () => void;\n}\nexport interface PerformanceEventLoopUtilization {\n  idle: number;\n  active: number;\n  utilization: number;\n}\nexport interface PerformanceNodeTiming extends PerformanceEntry {\n  readonly nodeStart: number;\n  readonly v8Start: number;\n  readonly bootstrapComplete: number;\n  readonly environment: number;\n  readonly loopStart: number;\n  readonly loopExit: number;\n  readonly idleTime: number;\n  readonly uvMetricsInfo: UvMetricsInfo;\n  toJSON(): object;\n}\nexport interface UvMetricsInfo {\n  loopCount: number;\n  events: number;\n  eventsWaiting: number;\n}\n/**\n * **`PerformanceMark`** is an interface for PerformanceEntry objects with an PerformanceEntry.entryType of `'mark'`.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceMark)\n */\nexport declare class PerformanceMark extends PerformanceEntry {\n  constructor(name: string, maybeOptions?: PerformanceMarkOptions);\n  /**\n   * The read-only **`detail`** property returns arbitrary metadata that was included in the mark upon construction (either when using Performance.mark or the PerformanceMark.PerformanceMark constructor).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceMark/detail)\n   */\n  get detail(): any;\n  toJSON(): object;\n}\n/**\n * **`PerformanceMeasure`** is an _abstract_ interface for PerformanceEntry objects with an PerformanceEntry.entryType of `'measure'`.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceMeasure)\n */\nexport declare abstract class PerformanceMeasure extends PerformanceEntry {\n  /**\n   * The read-only **`detail`** property returns arbitrary metadata that was included in the mark upon construction (when using Performance.measure.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceMeasure/detail)\n   */\n  get detail(): any;\n  toJSON(): object;\n}\nexport interface PerformanceMarkOptions {\n  detail?: any;\n  startTime?: number;\n}\nexport interface PerformanceMeasureOptions {\n  detail?: any;\n  start?: number;\n  duration?: number;\n  end?: number;\n}\n/**\n * The **`PerformanceObserverEntryList`** interface is a list of PerformanceEntry that were explicitly observed via the PerformanceObserver.observe method.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceObserverEntryList)\n */\nexport declare abstract class PerformanceObserverEntryList {\n  /**\n   * The **`getEntries()`** method of the PerformanceObserverEntryList interface returns a list of explicitly observed PerformanceEntry objects.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceObserverEntryList/getEntries)\n   */\n  getEntries(): PerformanceEntry[];\n  /**\n   * The **`getEntriesByType()`** method of the PerformanceObserverEntryList returns a list of explicitly _observed_ PerformanceEntry objects for a given PerformanceEntry.entryType.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceObserverEntryList/getEntriesByType)\n   */\n  getEntriesByType(type: string): PerformanceEntry[];\n  /**\n   * The **`getEntriesByName()`** method of the PerformanceObserverEntryList interface returns a list of explicitly observed PerformanceEntry objects for a given PerformanceEntry.name and PerformanceEntry.entryType.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceObserverEntryList/getEntriesByName)\n   */\n  getEntriesByName(name: string, type?: string): PerformanceEntry[];\n}\n/**\n * The **`PerformanceEntry`** object encapsulates a single performance metric that is part of the browser's performance timeline.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceEntry)\n */\nexport declare abstract class PerformanceEntry {\n  /**\n   * The read-only **`name`** property of the PerformanceEntry interface is a string representing the name for a performance entry.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceEntry/name)\n   */\n  get name(): string;\n  /**\n   * The read-only **`entryType`** property returns a string representing the type of performance metric that this entry represents.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceEntry/entryType)\n   */\n  get entryType(): string;\n  /**\n   * The read-only **`startTime`** property returns the first DOMHighResTimeStamp recorded for this PerformanceEntry.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceEntry/startTime)\n   */\n  get startTime(): number;\n  /**\n   * The read-only **`duration`** property returns a DOMHighResTimeStamp that is the duration of the PerformanceEntry.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceEntry/duration)\n   */\n  get duration(): number;\n  /**\n   * The **`toJSON()`** method is a Serialization; it returns a JSON representation of the PerformanceEntry object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceEntry/toJSON)\n   */\n  toJSON(): object;\n}\n/**\n * The **`PerformanceResourceTiming`** interface enables retrieval and analysis of detailed network timing data regarding the loading of an application's resources.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming)\n */\nexport declare abstract class PerformanceResourceTiming extends PerformanceEntry {\n  /**\n   * The **`connectEnd`** read-only property returns the DOMHighResTimeStamp immediately after the browser finishes establishing the connection to the server to retrieve the resource.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/connectEnd)\n   */\n  get connectEnd(): number;\n  /**\n   * The **`connectStart`** read-only property returns the DOMHighResTimeStamp immediately before the user agent starts establishing the connection to the server to retrieve the resource.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/connectStart)\n   */\n  get connectStart(): number;\n  /**\n   * The **`decodedBodySize`** read-only property returns the size (in octets) received from the fetch (HTTP or cache) of the message body after removing any applied content encoding (like gzip or Brotli).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/decodedBodySize)\n   */\n  get decodedBodySize(): number;\n  /**\n   * The **`domainLookupEnd`** read-only property returns the DOMHighResTimeStamp immediately after the browser finishes the domain-name lookup for the resource.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/domainLookupEnd)\n   */\n  get domainLookupEnd(): number;\n  /**\n   * The **`domainLookupStart`** read-only property returns the DOMHighResTimeStamp immediately before the browser starts the domain name lookup for the resource.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/domainLookupStart)\n   */\n  get domainLookupStart(): number;\n  /**\n   * The **`encodedBodySize`** read-only property represents the size (in octets) received from the fetch (HTTP or cache) of the payload body before removing any applied content encodings (like gzip or Brotli).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/encodedBodySize)\n   */\n  get encodedBodySize(): number;\n  /**\n   * The **`fetchStart`** read-only property represents a DOMHighResTimeStamp immediately before the browser starts to fetch the resource.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/fetchStart)\n   */\n  get fetchStart(): number;\n  /**\n   * The **`initiatorType`** read-only property is a string representing web platform feature that initiated the resource load.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/initiatorType)\n   */\n  get initiatorType(): string;\n  /**\n   * The **`nextHopProtocol`** read-only property is a string representing the network protocol used to fetch the resource, as identified by the ALPN Protocol ID (RFC7301).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/nextHopProtocol)\n   */\n  get nextHopProtocol(): string;\n  /**\n   * The **`redirectEnd`** read-only property returns a DOMHighResTimeStamp immediately after receiving the last byte of the response of the last redirect.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/redirectEnd)\n   */\n  get redirectEnd(): number;\n  /**\n   * The **`redirectStart`** read-only property returns a DOMHighResTimeStamp representing the start time of the fetch which that initiates the redirect.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/redirectStart)\n   */\n  get redirectStart(): number;\n  /**\n   * The **`requestStart`** read-only property returns a DOMHighResTimeStamp of the time immediately before the browser starts requesting the resource from the server, cache, or local resource.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/requestStart)\n   */\n  get requestStart(): number;\n  /**\n   * The **`responseEnd`** read-only property returns a DOMHighResTimeStamp immediately after the browser receives the last byte of the resource or immediately before the transport connection is closed, whichever comes first.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/responseEnd)\n   */\n  get responseEnd(): number;\n  /**\n   * The **`responseStart`** read-only property returns a DOMHighResTimeStamp immediately after the browser receives the first byte of the response from the server, cache, or local resource.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/responseStart)\n   */\n  get responseStart(): number;\n  /**\n   * The **`responseStatus`** read-only property represents the HTTP response status code returned when fetching the resource.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/responseStatus)\n   */\n  get responseStatus(): number;\n  /**\n   * The **`secureConnectionStart`** read-only property returns a DOMHighResTimeStamp immediately before the browser starts the handshake process to secure the current connection.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/secureConnectionStart)\n   */\n  get secureConnectionStart(): number | undefined;\n  /**\n   * The **`transferSize`** read-only property represents the size (in octets) of the fetched resource.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/transferSize)\n   */\n  get transferSize(): number;\n  /**\n   * The **`workerStart`** read-only property of the PerformanceResourceTiming interface returns a The `workerStart` property can have the following values: - A DOMHighResTimeStamp.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/workerStart)\n   */\n  get workerStart(): number;\n}\n/**\n * The **`PerformanceObserver`** interface is used to observe performance measurement events and be notified of new PerformanceEntry as they are recorded in the browser's _performance timeline_.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceObserver)\n */\nexport declare class PerformanceObserver {\n  constructor(callback: any);\n  /**\n   * The **`disconnect()`** method of the PerformanceObserver interface is used to stop the performance observer from receiving any PerformanceEntry events.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceObserver/disconnect)\n   */\n  disconnect(): void;\n  /**\n   * The **`observe()`** method of the **PerformanceObserver** interface is used to specify the set of performance entry types to observe.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceObserver/observe)\n   */\n  observe(options?: PerformanceObserverObserveOptions): void;\n  /**\n   * The **`takeRecords()`** method of the PerformanceObserver interface returns the current list of PerformanceEntry objects stored in the performance observer, emptying it out.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PerformanceObserver/takeRecords)\n   */\n  takeRecords(): PerformanceEntry[];\n  readonly supportedEntryTypes: string[];\n}\nexport interface PerformanceObserverObserveOptions {\n  buffered?: boolean;\n  durationThreshold?: number;\n  entryTypes?: string[];\n  type?: string;\n}\nexport interface EventCounts {\n  get size(): number;\n  get(eventType: string): number | undefined;\n  has(eventType: string): boolean;\n  entries(): IterableIterator<string[]>;\n  keys(): IterableIterator<string>;\n  values(): IterableIterator<number>;\n  forEach(\n    param1: (param0: number, param1: string, param2: EventCounts) => void,\n    param2?: any,\n  ): void;\n  [Symbol.iterator](): IterableIterator<string[]>;\n}\n// AI Search V2 API Error Interfaces\nexport interface AiSearchInternalError extends Error {}\nexport interface AiSearchNotFoundError extends Error {}\nexport interface AiSearchNameNotSetError extends Error {}\n// AI Search V2 Request Types\nexport type AiSearchSearchRequest = {\n  messages: Array<{\n    role: \"system\" | \"developer\" | \"user\" | \"assistant\" | \"tool\";\n    content: string | null;\n  }>;\n  ai_search_options?: {\n    retrieval?: {\n      retrieval_type?: \"vector\" | \"keyword\" | \"hybrid\";\n      /** Match threshold (0-1, default 0.4) */\n      match_threshold?: number;\n      /** Maximum number of results (1-50, default 10) */\n      max_num_results?: number;\n      filters?: VectorizeVectorMetadataFilter;\n      /** Context expansion (0-3, default 0) */\n      context_expansion?: number;\n      [key: string]: unknown;\n    };\n    query_rewrite?: {\n      enabled?: boolean;\n      model?: string;\n      rewrite_prompt?: string;\n      [key: string]: unknown;\n    };\n    reranking?: {\n      /** Enable reranking (default false) */\n      enabled?: boolean;\n      model?: \"@cf/baai/bge-reranker-base\" | \"\";\n      /** Match threshold (0-1, default 0.4) */\n      match_threshold?: number;\n      [key: string]: unknown;\n    };\n    [key: string]: unknown;\n  };\n};\nexport type AiSearchChatCompletionsRequest = {\n  messages: Array<{\n    role: \"system\" | \"developer\" | \"user\" | \"assistant\" | \"tool\";\n    content: string | null;\n  }>;\n  model?: string;\n  stream?: boolean;\n  ai_search_options?: {\n    retrieval?: {\n      retrieval_type?: \"vector\" | \"keyword\" | \"hybrid\";\n      match_threshold?: number;\n      max_num_results?: number;\n      filters?: VectorizeVectorMetadataFilter;\n      context_expansion?: number;\n      [key: string]: unknown;\n    };\n    query_rewrite?: {\n      enabled?: boolean;\n      model?: string;\n      rewrite_prompt?: string;\n      [key: string]: unknown;\n    };\n    reranking?: {\n      enabled?: boolean;\n      model?: \"@cf/baai/bge-reranker-base\" | \"\";\n      match_threshold?: number;\n      [key: string]: unknown;\n    };\n    [key: string]: unknown;\n  };\n  [key: string]: unknown;\n};\n// AI Search V2 Response Types\nexport type AiSearchSearchResponse = {\n  search_query: string;\n  chunks: Array<{\n    id: string;\n    type: string;\n    /** Match score (0-1) */\n    score: number;\n    text: string;\n    item: {\n      timestamp?: number;\n      key: string;\n      metadata?: Record<string, unknown>;\n    };\n    scoring_details?: {\n      /** Keyword match score (0-1) */\n      keyword_score?: number;\n      /** Vector similarity score (0-1) */\n      vector_score?: number;\n    };\n  }>;\n};\nexport type AiSearchListResponse = Array<{\n  id: string;\n  internal_id?: string;\n  account_id?: string;\n  account_tag?: string;\n  /** Whether the instance is enabled (default true) */\n  enable?: boolean;\n  type?: \"r2\" | \"web-crawler\";\n  source?: string;\n  [key: string]: unknown;\n}>;\nexport type AiSearchConfig = {\n  /** Instance ID (1-32 chars, pattern: ^[a-z0-9_]+(?:-[a-z0-9_]+)*$) */\n  id: string;\n  type: \"r2\" | \"web-crawler\";\n  source: string;\n  source_params?: object;\n  /** Token ID (UUID format) */\n  token_id?: string;\n  ai_gateway_id?: string;\n  /** Enable query rewriting (default false) */\n  rewrite_query?: boolean;\n  /** Enable reranking (default false) */\n  reranking?: boolean;\n  embedding_model?: string;\n  ai_search_model?: string;\n};\nexport type AiSearchInstance = {\n  id: string;\n  enable?: boolean;\n  type?: \"r2\" | \"web-crawler\";\n  source?: string;\n  [key: string]: unknown;\n};\n// AI Search Instance Service - Instance-level operations\nexport declare abstract class AiSearchInstanceService {\n  /**\n   * Search the AI Search instance for relevant chunks.\n   * @param params Search request with messages and AI search options\n   * @returns Search response with matching chunks\n   */\n  search(params: AiSearchSearchRequest): Promise<AiSearchSearchResponse>;\n  /**\n   * Generate chat completions with AI Search context.\n   * @param params Chat completions request with optional streaming\n   * @returns Response object (if streaming) or chat completion result\n   */\n  chatCompletions(\n    params: AiSearchChatCompletionsRequest,\n  ): Promise<Response | object>;\n  /**\n   * Delete this AI Search instance.\n   */\n  delete(): Promise<void>;\n}\n// AI Search Account Service - Account-level operations\nexport declare abstract class AiSearchAccountService {\n  /**\n   * List all AI Search instances in the account.\n   * @returns Array of AI Search instances\n   */\n  list(): Promise<AiSearchListResponse>;\n  /**\n   * Get an AI Search instance by ID.\n   * @param name Instance ID\n   * @returns Instance service for performing operations\n   */\n  get(name: string): AiSearchInstanceService;\n  /**\n   * Create a new AI Search instance.\n   * @param config Instance configuration\n   * @returns Instance service for performing operations\n   */\n  create(config: AiSearchConfig): Promise<AiSearchInstanceService>;\n}\nexport type AiImageClassificationInput = {\n  image: number[];\n};\nexport type AiImageClassificationOutput = {\n  score?: number;\n  label?: string;\n}[];\nexport declare abstract class BaseAiImageClassification {\n  inputs: AiImageClassificationInput;\n  postProcessedOutputs: AiImageClassificationOutput;\n}\nexport type AiImageToTextInput = {\n  image: number[];\n  prompt?: string;\n  max_tokens?: number;\n  temperature?: number;\n  top_p?: number;\n  top_k?: number;\n  seed?: number;\n  repetition_penalty?: number;\n  frequency_penalty?: number;\n  presence_penalty?: number;\n  raw?: boolean;\n  messages?: RoleScopedChatInput[];\n};\nexport type AiImageToTextOutput = {\n  description: string;\n};\nexport declare abstract class BaseAiImageToText {\n  inputs: AiImageToTextInput;\n  postProcessedOutputs: AiImageToTextOutput;\n}\nexport type AiImageTextToTextInput = {\n  image: string;\n  prompt?: string;\n  max_tokens?: number;\n  temperature?: number;\n  ignore_eos?: boolean;\n  top_p?: number;\n  top_k?: number;\n  seed?: number;\n  repetition_penalty?: number;\n  frequency_penalty?: number;\n  presence_penalty?: number;\n  raw?: boolean;\n  messages?: RoleScopedChatInput[];\n};\nexport type AiImageTextToTextOutput = {\n  description: string;\n};\nexport declare abstract class BaseAiImageTextToText {\n  inputs: AiImageTextToTextInput;\n  postProcessedOutputs: AiImageTextToTextOutput;\n}\nexport type AiMultimodalEmbeddingsInput = {\n  image: string;\n  text: string[];\n};\nexport type AiIMultimodalEmbeddingsOutput = {\n  data: number[][];\n  shape: number[];\n};\nexport declare abstract class BaseAiMultimodalEmbeddings {\n  inputs: AiImageTextToTextInput;\n  postProcessedOutputs: AiImageTextToTextOutput;\n}\nexport type AiObjectDetectionInput = {\n  image: number[];\n};\nexport type AiObjectDetectionOutput = {\n  score?: number;\n  label?: string;\n}[];\nexport declare abstract class BaseAiObjectDetection {\n  inputs: AiObjectDetectionInput;\n  postProcessedOutputs: AiObjectDetectionOutput;\n}\nexport type AiSentenceSimilarityInput = {\n  source: string;\n  sentences: string[];\n};\nexport type AiSentenceSimilarityOutput = number[];\nexport declare abstract class BaseAiSentenceSimilarity {\n  inputs: AiSentenceSimilarityInput;\n  postProcessedOutputs: AiSentenceSimilarityOutput;\n}\nexport type AiAutomaticSpeechRecognitionInput = {\n  audio: number[];\n};\nexport type AiAutomaticSpeechRecognitionOutput = {\n  text?: string;\n  words?: {\n    word: string;\n    start: number;\n    end: number;\n  }[];\n  vtt?: string;\n};\nexport declare abstract class BaseAiAutomaticSpeechRecognition {\n  inputs: AiAutomaticSpeechRecognitionInput;\n  postProcessedOutputs: AiAutomaticSpeechRecognitionOutput;\n}\nexport type AiSummarizationInput = {\n  input_text: string;\n  max_length?: number;\n};\nexport type AiSummarizationOutput = {\n  summary: string;\n};\nexport declare abstract class BaseAiSummarization {\n  inputs: AiSummarizationInput;\n  postProcessedOutputs: AiSummarizationOutput;\n}\nexport type AiTextClassificationInput = {\n  text: string;\n};\nexport type AiTextClassificationOutput = {\n  score?: number;\n  label?: string;\n}[];\nexport declare abstract class BaseAiTextClassification {\n  inputs: AiTextClassificationInput;\n  postProcessedOutputs: AiTextClassificationOutput;\n}\nexport type AiTextEmbeddingsInput = {\n  text: string | string[];\n};\nexport type AiTextEmbeddingsOutput = {\n  shape: number[];\n  data: number[][];\n};\nexport declare abstract class BaseAiTextEmbeddings {\n  inputs: AiTextEmbeddingsInput;\n  postProcessedOutputs: AiTextEmbeddingsOutput;\n}\nexport type RoleScopedChatInput = {\n  role:\n    | \"user\"\n    | \"assistant\"\n    | \"system\"\n    | \"tool\"\n    | (string & NonNullable<unknown>);\n  content: string;\n  name?: string;\n};\nexport type AiTextGenerationToolLegacyInput = {\n  name: string;\n  description: string;\n  parameters?: {\n    type: \"object\" | (string & NonNullable<unknown>);\n    properties: {\n      [key: string]: {\n        type: string;\n        description?: string;\n      };\n    };\n    required: string[];\n  };\n};\nexport type AiTextGenerationToolInput = {\n  type: \"function\" | (string & NonNullable<unknown>);\n  function: {\n    name: string;\n    description: string;\n    parameters?: {\n      type: \"object\" | (string & NonNullable<unknown>);\n      properties: {\n        [key: string]: {\n          type: string;\n          description?: string;\n        };\n      };\n      required: string[];\n    };\n  };\n};\nexport type AiTextGenerationFunctionsInput = {\n  name: string;\n  code: string;\n};\nexport type AiTextGenerationResponseFormat = {\n  type: string;\n  json_schema?: any;\n};\nexport type AiTextGenerationInput = {\n  prompt?: string;\n  raw?: boolean;\n  stream?: boolean;\n  max_tokens?: number;\n  temperature?: number;\n  top_p?: number;\n  top_k?: number;\n  seed?: number;\n  repetition_penalty?: number;\n  frequency_penalty?: number;\n  presence_penalty?: number;\n  messages?: RoleScopedChatInput[];\n  response_format?: AiTextGenerationResponseFormat;\n  tools?:\n    | AiTextGenerationToolInput[]\n    | AiTextGenerationToolLegacyInput[]\n    | (object & NonNullable<unknown>);\n  functions?: AiTextGenerationFunctionsInput[];\n};\nexport type AiTextGenerationToolLegacyOutput = {\n  name: string;\n  arguments: unknown;\n};\nexport type AiTextGenerationToolOutput = {\n  id: string;\n  type: \"function\";\n  function: {\n    name: string;\n    arguments: string;\n  };\n};\nexport type UsageTags = {\n  prompt_tokens: number;\n  completion_tokens: number;\n  total_tokens: number;\n};\nexport type AiTextGenerationOutput = {\n  response?: string;\n  tool_calls?: AiTextGenerationToolLegacyOutput[] &\n    AiTextGenerationToolOutput[];\n  usage?: UsageTags;\n};\nexport declare abstract class BaseAiTextGeneration {\n  inputs: AiTextGenerationInput;\n  postProcessedOutputs: AiTextGenerationOutput;\n}\nexport type AiTextToSpeechInput = {\n  prompt: string;\n  lang?: string;\n};\nexport type AiTextToSpeechOutput =\n  | Uint8Array\n  | {\n      audio: string;\n    };\nexport declare abstract class BaseAiTextToSpeech {\n  inputs: AiTextToSpeechInput;\n  postProcessedOutputs: AiTextToSpeechOutput;\n}\nexport type AiTextToImageInput = {\n  prompt: string;\n  negative_prompt?: string;\n  height?: number;\n  width?: number;\n  image?: number[];\n  image_b64?: string;\n  mask?: number[];\n  num_steps?: number;\n  strength?: number;\n  guidance?: number;\n  seed?: number;\n};\nexport type AiTextToImageOutput = ReadableStream<Uint8Array>;\nexport declare abstract class BaseAiTextToImage {\n  inputs: AiTextToImageInput;\n  postProcessedOutputs: AiTextToImageOutput;\n}\nexport type AiTranslationInput = {\n  text: string;\n  target_lang: string;\n  source_lang?: string;\n};\nexport type AiTranslationOutput = {\n  translated_text?: string;\n};\nexport declare abstract class BaseAiTranslation {\n  inputs: AiTranslationInput;\n  postProcessedOutputs: AiTranslationOutput;\n}\n/**\n * Workers AI support for OpenAI's Chat Completions API\n */\nexport type ChatCompletionContentPartText = {\n  type: \"text\";\n  text: string;\n};\nexport type ChatCompletionContentPartImage = {\n  type: \"image_url\";\n  image_url: {\n    url: string;\n    detail?: \"auto\" | \"low\" | \"high\";\n  };\n};\nexport type ChatCompletionContentPartInputAudio = {\n  type: \"input_audio\";\n  input_audio: {\n    /** Base64 encoded audio data. */\n    data: string;\n    format: \"wav\" | \"mp3\";\n  };\n};\nexport type ChatCompletionContentPartFile = {\n  type: \"file\";\n  file: {\n    /** Base64 encoded file data. */\n    file_data?: string;\n    /** The ID of an uploaded file. */\n    file_id?: string;\n    filename?: string;\n  };\n};\nexport type ChatCompletionContentPartRefusal = {\n  type: \"refusal\";\n  refusal: string;\n};\nexport type ChatCompletionContentPart =\n  | ChatCompletionContentPartText\n  | ChatCompletionContentPartImage\n  | ChatCompletionContentPartInputAudio\n  | ChatCompletionContentPartFile;\nexport type FunctionDefinition = {\n  name: string;\n  description?: string;\n  parameters?: Record<string, unknown>;\n  strict?: boolean | null;\n};\nexport type ChatCompletionFunctionTool = {\n  type: \"function\";\n  function: FunctionDefinition;\n};\nexport type ChatCompletionCustomToolGrammarFormat = {\n  type: \"grammar\";\n  grammar: {\n    definition: string;\n    syntax: \"lark\" | \"regex\";\n  };\n};\nexport type ChatCompletionCustomToolTextFormat = {\n  type: \"text\";\n};\nexport type ChatCompletionCustomToolFormat =\n  | ChatCompletionCustomToolTextFormat\n  | ChatCompletionCustomToolGrammarFormat;\nexport type ChatCompletionCustomTool = {\n  type: \"custom\";\n  custom: {\n    name: string;\n    description?: string;\n    format?: ChatCompletionCustomToolFormat;\n  };\n};\nexport type ChatCompletionTool =\n  | ChatCompletionFunctionTool\n  | ChatCompletionCustomTool;\nexport type ChatCompletionMessageFunctionToolCall = {\n  id: string;\n  type: \"function\";\n  function: {\n    name: string;\n    /** JSON-encoded arguments string. */\n    arguments: string;\n  };\n};\nexport type ChatCompletionMessageCustomToolCall = {\n  id: string;\n  type: \"custom\";\n  custom: {\n    name: string;\n    input: string;\n  };\n};\nexport type ChatCompletionMessageToolCall =\n  | ChatCompletionMessageFunctionToolCall\n  | ChatCompletionMessageCustomToolCall;\nexport type ChatCompletionToolChoiceFunction = {\n  type: \"function\";\n  function: {\n    name: string;\n  };\n};\nexport type ChatCompletionToolChoiceCustom = {\n  type: \"custom\";\n  custom: {\n    name: string;\n  };\n};\nexport type ChatCompletionToolChoiceAllowedTools = {\n  type: \"allowed_tools\";\n  allowed_tools: {\n    mode: \"auto\" | \"required\";\n    tools: Array<Record<string, unknown>>;\n  };\n};\nexport type ChatCompletionToolChoiceOption =\n  | \"none\"\n  | \"auto\"\n  | \"required\"\n  | ChatCompletionToolChoiceFunction\n  | ChatCompletionToolChoiceCustom\n  | ChatCompletionToolChoiceAllowedTools;\nexport type DeveloperMessage = {\n  role: \"developer\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n  name?: string;\n};\nexport type SystemMessage = {\n  role: \"system\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n  name?: string;\n};\n/**\n * Permissive merged content part used inside UserMessage arrays.\n *\n * Cabidela has a limitation where anyOf/oneOf with enum-based discrimination\n * inside nested array items does not correctly match different branches for\n * different array elements, so the schema uses a single merged object.\n */\nexport type UserMessageContentPart = {\n  type: \"text\" | \"image_url\" | \"input_audio\" | \"file\";\n  text?: string;\n  image_url?: {\n    url?: string;\n    detail?: \"auto\" | \"low\" | \"high\";\n  };\n  input_audio?: {\n    data?: string;\n    format?: \"wav\" | \"mp3\";\n  };\n  file?: {\n    file_data?: string;\n    file_id?: string;\n    filename?: string;\n  };\n};\nexport type UserMessage = {\n  role: \"user\";\n  content: string | Array<UserMessageContentPart>;\n  name?: string;\n};\nexport type AssistantMessageContentPart = {\n  type: \"text\" | \"refusal\";\n  text?: string;\n  refusal?: string;\n};\nexport type AssistantMessage = {\n  role: \"assistant\";\n  content?: string | null | Array<AssistantMessageContentPart>;\n  refusal?: string | null;\n  name?: string;\n  audio?: {\n    id: string;\n  };\n  tool_calls?: Array<ChatCompletionMessageToolCall>;\n  function_call?: {\n    name: string;\n    arguments: string;\n  };\n};\nexport type ToolMessage = {\n  role: \"tool\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n  tool_call_id: string;\n};\nexport type FunctionMessage = {\n  role: \"function\";\n  content: string;\n  name: string;\n};\nexport type ChatCompletionMessageParam =\n  | DeveloperMessage\n  | SystemMessage\n  | UserMessage\n  | AssistantMessage\n  | ToolMessage\n  | FunctionMessage;\nexport type ChatCompletionsResponseFormatText = {\n  type: \"text\";\n};\nexport type ChatCompletionsResponseFormatJSONObject = {\n  type: \"json_object\";\n};\nexport type ResponseFormatJSONSchema = {\n  type: \"json_schema\";\n  json_schema: {\n    name: string;\n    description?: string;\n    schema?: Record<string, unknown>;\n    strict?: boolean | null;\n  };\n};\nexport type ResponseFormat =\n  | ChatCompletionsResponseFormatText\n  | ChatCompletionsResponseFormatJSONObject\n  | ResponseFormatJSONSchema;\nexport type ChatCompletionsStreamOptions = {\n  include_usage?: boolean;\n  include_obfuscation?: boolean;\n};\nexport type PredictionContent = {\n  type: \"content\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n};\nexport type AudioParams = {\n  voice:\n    | string\n    | {\n        id: string;\n      };\n  format: \"wav\" | \"aac\" | \"mp3\" | \"flac\" | \"opus\" | \"pcm16\";\n};\nexport type WebSearchUserLocation = {\n  type: \"approximate\";\n  approximate: {\n    city?: string;\n    country?: string;\n    region?: string;\n    timezone?: string;\n  };\n};\nexport type WebSearchOptions = {\n  search_context_size?: \"low\" | \"medium\" | \"high\";\n  user_location?: WebSearchUserLocation;\n};\nexport type ChatTemplateKwargs = {\n  /** Whether to enable reasoning, enabled by default. */\n  enable_thinking?: boolean;\n  /** If false, preserves reasoning context between turns. */\n  clear_thinking?: boolean;\n};\n/** Shared optional properties used by both Prompt and Messages input branches. */\nexport type ChatCompletionsCommonOptions = {\n  model?: string;\n  audio?: AudioParams;\n  frequency_penalty?: number | null;\n  logit_bias?: Record<string, unknown> | null;\n  logprobs?: boolean | null;\n  top_logprobs?: number | null;\n  max_tokens?: number | null;\n  max_completion_tokens?: number | null;\n  metadata?: Record<string, unknown> | null;\n  modalities?: Array<\"text\" | \"audio\"> | null;\n  n?: number | null;\n  parallel_tool_calls?: boolean;\n  prediction?: PredictionContent;\n  presence_penalty?: number | null;\n  reasoning_effort?: \"low\" | \"medium\" | \"high\" | null;\n  chat_template_kwargs?: ChatTemplateKwargs;\n  response_format?: ResponseFormat;\n  seed?: number | null;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n  stop?: string | Array<string> | null;\n  store?: boolean | null;\n  stream?: boolean | null;\n  stream_options?: ChatCompletionsStreamOptions;\n  temperature?: number | null;\n  tool_choice?: ChatCompletionToolChoiceOption;\n  tools?: Array<ChatCompletionTool>;\n  top_p?: number | null;\n  user?: string;\n  web_search_options?: WebSearchOptions;\n  function_call?:\n    | \"none\"\n    | \"auto\"\n    | {\n        name: string;\n      };\n  functions?: Array<FunctionDefinition>;\n};\nexport type PromptTokensDetails = {\n  cached_tokens?: number;\n  audio_tokens?: number;\n};\nexport type CompletionTokensDetails = {\n  reasoning_tokens?: number;\n  audio_tokens?: number;\n  accepted_prediction_tokens?: number;\n  rejected_prediction_tokens?: number;\n};\nexport type CompletionUsage = {\n  prompt_tokens: number;\n  completion_tokens: number;\n  total_tokens: number;\n  prompt_tokens_details?: PromptTokensDetails;\n  completion_tokens_details?: CompletionTokensDetails;\n};\nexport type ChatCompletionTopLogprob = {\n  token: string;\n  logprob: number;\n  bytes: Array<number> | null;\n};\nexport type ChatCompletionTokenLogprob = {\n  token: string;\n  logprob: number;\n  bytes: Array<number> | null;\n  top_logprobs: Array<ChatCompletionTopLogprob>;\n};\nexport type ChatCompletionAudio = {\n  id: string;\n  /** Base64 encoded audio bytes. */\n  data: string;\n  expires_at: number;\n  transcript: string;\n};\nexport type ChatCompletionUrlCitation = {\n  type: \"url_citation\";\n  url_citation: {\n    url: string;\n    title: string;\n    start_index: number;\n    end_index: number;\n  };\n};\nexport type ChatCompletionResponseMessage = {\n  role: \"assistant\";\n  content: string | null;\n  refusal: string | null;\n  annotations?: Array<ChatCompletionUrlCitation>;\n  audio?: ChatCompletionAudio;\n  tool_calls?: Array<ChatCompletionMessageToolCall>;\n  function_call?: {\n    name: string;\n    arguments: string;\n  } | null;\n};\nexport type ChatCompletionLogprobs = {\n  content: Array<ChatCompletionTokenLogprob> | null;\n  refusal?: Array<ChatCompletionTokenLogprob> | null;\n};\nexport type ChatCompletionChoice = {\n  index: number;\n  message: ChatCompletionResponseMessage;\n  finish_reason:\n    | \"stop\"\n    | \"length\"\n    | \"tool_calls\"\n    | \"content_filter\"\n    | \"function_call\";\n  logprobs: ChatCompletionLogprobs | null;\n};\nexport type ChatCompletionsPromptInput = {\n  prompt: string;\n} & ChatCompletionsCommonOptions;\nexport type ChatCompletionsMessagesInput = {\n  messages: Array<ChatCompletionMessageParam>;\n} & ChatCompletionsCommonOptions;\nexport type ChatCompletionsOutput = {\n  id: string;\n  object: string;\n  created: number;\n  model: string;\n  choices: Array<ChatCompletionChoice>;\n  usage?: CompletionUsage;\n  system_fingerprint?: string | null;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n};\n/**\n * Workers AI support for OpenAI's Responses API\n * Reference: https://github.com/openai/openai-node/blob/master/src/resources/responses/responses.ts\n *\n * It's a stripped down version from its source.\n * It currently supports basic function calling, json mode and accepts images as input.\n *\n * It does not include types for WebSearch, CodeInterpreter, FileInputs, MCP, CustomTools.\n * We plan to add those incrementally as model + platform capabilities evolve.\n */\nexport type ResponsesInput = {\n  background?: boolean | null;\n  conversation?: string | ResponseConversationParam | null;\n  include?: Array<ResponseIncludable> | null;\n  input?: string | ResponseInput;\n  instructions?: string | null;\n  max_output_tokens?: number | null;\n  parallel_tool_calls?: boolean | null;\n  previous_response_id?: string | null;\n  prompt_cache_key?: string;\n  reasoning?: Reasoning | null;\n  safety_identifier?: string;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n  stream?: boolean | null;\n  stream_options?: StreamOptions | null;\n  temperature?: number | null;\n  text?: ResponseTextConfig;\n  tool_choice?: ToolChoiceOptions | ToolChoiceFunction;\n  tools?: Array<Tool>;\n  top_p?: number | null;\n  truncation?: \"auto\" | \"disabled\" | null;\n};\nexport type ResponsesOutput = {\n  id?: string;\n  created_at?: number;\n  output_text?: string;\n  error?: ResponseError | null;\n  incomplete_details?: ResponseIncompleteDetails | null;\n  instructions?: string | Array<ResponseInputItem> | null;\n  object?: \"response\";\n  output?: Array<ResponseOutputItem>;\n  parallel_tool_calls?: boolean;\n  temperature?: number | null;\n  tool_choice?: ToolChoiceOptions | ToolChoiceFunction;\n  tools?: Array<Tool>;\n  top_p?: number | null;\n  max_output_tokens?: number | null;\n  previous_response_id?: string | null;\n  prompt?: ResponsePrompt | null;\n  reasoning?: Reasoning | null;\n  safety_identifier?: string;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n  status?: ResponseStatus;\n  text?: ResponseTextConfig;\n  truncation?: \"auto\" | \"disabled\" | null;\n  usage?: ResponseUsage;\n};\nexport type EasyInputMessage = {\n  content: string | ResponseInputMessageContentList;\n  role: \"user\" | \"assistant\" | \"system\" | \"developer\";\n  type?: \"message\";\n};\nexport type ResponsesFunctionTool = {\n  name: string;\n  parameters: {\n    [key: string]: unknown;\n  } | null;\n  strict: boolean | null;\n  type: \"function\";\n  description?: string | null;\n};\nexport type ResponseIncompleteDetails = {\n  reason?: \"max_output_tokens\" | \"content_filter\";\n};\nexport type ResponsePrompt = {\n  id: string;\n  variables?: {\n    [key: string]: string | ResponseInputText | ResponseInputImage;\n  } | null;\n  version?: string | null;\n};\nexport type Reasoning = {\n  effort?: ReasoningEffort | null;\n  generate_summary?: \"auto\" | \"concise\" | \"detailed\" | null;\n  summary?: \"auto\" | \"concise\" | \"detailed\" | null;\n};\nexport type ResponseContent =\n  | ResponseInputText\n  | ResponseInputImage\n  | ResponseOutputText\n  | ResponseOutputRefusal\n  | ResponseContentReasoningText;\nexport type ResponseContentReasoningText = {\n  text: string;\n  type: \"reasoning_text\";\n};\nexport type ResponseConversationParam = {\n  id: string;\n};\nexport type ResponseCreatedEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.created\";\n};\nexport type ResponseCustomToolCallOutput = {\n  call_id: string;\n  output: string | Array<ResponseInputText | ResponseInputImage>;\n  type: \"custom_tool_call_output\";\n  id?: string;\n};\nexport type ResponseError = {\n  code:\n    | \"server_error\"\n    | \"rate_limit_exceeded\"\n    | \"invalid_prompt\"\n    | \"vector_store_timeout\"\n    | \"invalid_image\"\n    | \"invalid_image_format\"\n    | \"invalid_base64_image\"\n    | \"invalid_image_url\"\n    | \"image_too_large\"\n    | \"image_too_small\"\n    | \"image_parse_error\"\n    | \"image_content_policy_violation\"\n    | \"invalid_image_mode\"\n    | \"image_file_too_large\"\n    | \"unsupported_image_media_type\"\n    | \"empty_image_file\"\n    | \"failed_to_download_image\"\n    | \"image_file_not_found\";\n  message: string;\n};\nexport type ResponseErrorEvent = {\n  code: string | null;\n  message: string;\n  param: string | null;\n  sequence_number: number;\n  type: \"error\";\n};\nexport type ResponseFailedEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.failed\";\n};\nexport type ResponseFormatText = {\n  type: \"text\";\n};\nexport type ResponseFormatJSONObject = {\n  type: \"json_object\";\n};\nexport type ResponseFormatTextConfig =\n  | ResponseFormatText\n  | ResponseFormatTextJSONSchemaConfig\n  | ResponseFormatJSONObject;\nexport type ResponseFormatTextJSONSchemaConfig = {\n  name: string;\n  schema: {\n    [key: string]: unknown;\n  };\n  type: \"json_schema\";\n  description?: string;\n  strict?: boolean | null;\n};\nexport type ResponseFunctionCallArgumentsDeltaEvent = {\n  delta: string;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.function_call_arguments.delta\";\n};\nexport type ResponseFunctionCallArgumentsDoneEvent = {\n  arguments: string;\n  item_id: string;\n  name: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.function_call_arguments.done\";\n};\nexport type ResponseFunctionCallOutputItem =\n  | ResponseInputTextContent\n  | ResponseInputImageContent;\nexport type ResponseFunctionCallOutputItemList =\n  Array<ResponseFunctionCallOutputItem>;\nexport type ResponseFunctionToolCall = {\n  arguments: string;\n  call_id: string;\n  name: string;\n  type: \"function_call\";\n  id?: string;\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n};\nexport interface ResponseFunctionToolCallItem extends ResponseFunctionToolCall {\n  id: string;\n}\nexport type ResponseFunctionToolCallOutputItem = {\n  id: string;\n  call_id: string;\n  output: string | Array<ResponseInputText | ResponseInputImage>;\n  type: \"function_call_output\";\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n};\nexport type ResponseIncludable =\n  | \"message.input_image.image_url\"\n  | \"message.output_text.logprobs\";\nexport type ResponseIncompleteEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.incomplete\";\n};\nexport type ResponseInput = Array<ResponseInputItem>;\nexport type ResponseInputContent = ResponseInputText | ResponseInputImage;\nexport type ResponseInputImage = {\n  detail: \"low\" | \"high\" | \"auto\";\n  type: \"input_image\";\n  /**\n   * Base64 encoded image\n   */\n  image_url?: string | null;\n};\nexport type ResponseInputImageContent = {\n  type: \"input_image\";\n  detail?: \"low\" | \"high\" | \"auto\" | null;\n  /**\n   * Base64 encoded image\n   */\n  image_url?: string | null;\n};\nexport type ResponseInputItem =\n  | EasyInputMessage\n  | ResponseInputItemMessage\n  | ResponseOutputMessage\n  | ResponseFunctionToolCall\n  | ResponseInputItemFunctionCallOutput\n  | ResponseReasoningItem;\nexport type ResponseInputItemFunctionCallOutput = {\n  call_id: string;\n  output: string | ResponseFunctionCallOutputItemList;\n  type: \"function_call_output\";\n  id?: string | null;\n  status?: \"in_progress\" | \"completed\" | \"incomplete\" | null;\n};\nexport type ResponseInputItemMessage = {\n  content: ResponseInputMessageContentList;\n  role: \"user\" | \"system\" | \"developer\";\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n  type?: \"message\";\n};\nexport type ResponseInputMessageContentList = Array<ResponseInputContent>;\nexport type ResponseInputMessageItem = {\n  id: string;\n  content: ResponseInputMessageContentList;\n  role: \"user\" | \"system\" | \"developer\";\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n  type?: \"message\";\n};\nexport type ResponseInputText = {\n  text: string;\n  type: \"input_text\";\n};\nexport type ResponseInputTextContent = {\n  text: string;\n  type: \"input_text\";\n};\nexport type ResponseItem =\n  | ResponseInputMessageItem\n  | ResponseOutputMessage\n  | ResponseFunctionToolCallItem\n  | ResponseFunctionToolCallOutputItem;\nexport type ResponseOutputItem =\n  | ResponseOutputMessage\n  | ResponseFunctionToolCall\n  | ResponseReasoningItem;\nexport type ResponseOutputItemAddedEvent = {\n  item: ResponseOutputItem;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.output_item.added\";\n};\nexport type ResponseOutputItemDoneEvent = {\n  item: ResponseOutputItem;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.output_item.done\";\n};\nexport type ResponseOutputMessage = {\n  id: string;\n  content: Array<ResponseOutputText | ResponseOutputRefusal>;\n  role: \"assistant\";\n  status: \"in_progress\" | \"completed\" | \"incomplete\";\n  type: \"message\";\n};\nexport type ResponseOutputRefusal = {\n  refusal: string;\n  type: \"refusal\";\n};\nexport type ResponseOutputText = {\n  text: string;\n  type: \"output_text\";\n  logprobs?: Array<Logprob>;\n};\nexport type ResponseReasoningItem = {\n  id: string;\n  summary: Array<ResponseReasoningSummaryItem>;\n  type: \"reasoning\";\n  content?: Array<ResponseReasoningContentItem>;\n  encrypted_content?: string | null;\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n};\nexport type ResponseReasoningSummaryItem = {\n  text: string;\n  type: \"summary_text\";\n};\nexport type ResponseReasoningContentItem = {\n  text: string;\n  type: \"reasoning_text\";\n};\nexport type ResponseReasoningTextDeltaEvent = {\n  content_index: number;\n  delta: string;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.reasoning_text.delta\";\n};\nexport type ResponseReasoningTextDoneEvent = {\n  content_index: number;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  text: string;\n  type: \"response.reasoning_text.done\";\n};\nexport type ResponseRefusalDeltaEvent = {\n  content_index: number;\n  delta: string;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.refusal.delta\";\n};\nexport type ResponseRefusalDoneEvent = {\n  content_index: number;\n  item_id: string;\n  output_index: number;\n  refusal: string;\n  sequence_number: number;\n  type: \"response.refusal.done\";\n};\nexport type ResponseStatus =\n  | \"completed\"\n  | \"failed\"\n  | \"in_progress\"\n  | \"cancelled\"\n  | \"queued\"\n  | \"incomplete\";\nexport type ResponseStreamEvent =\n  | ResponseCompletedEvent\n  | ResponseCreatedEvent\n  | ResponseErrorEvent\n  | ResponseFunctionCallArgumentsDeltaEvent\n  | ResponseFunctionCallArgumentsDoneEvent\n  | ResponseFailedEvent\n  | ResponseIncompleteEvent\n  | ResponseOutputItemAddedEvent\n  | ResponseOutputItemDoneEvent\n  | ResponseReasoningTextDeltaEvent\n  | ResponseReasoningTextDoneEvent\n  | ResponseRefusalDeltaEvent\n  | ResponseRefusalDoneEvent\n  | ResponseTextDeltaEvent\n  | ResponseTextDoneEvent;\nexport type ResponseCompletedEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.completed\";\n};\nexport type ResponseTextConfig = {\n  format?: ResponseFormatTextConfig;\n  verbosity?: \"low\" | \"medium\" | \"high\" | null;\n};\nexport type ResponseTextDeltaEvent = {\n  content_index: number;\n  delta: string;\n  item_id: string;\n  logprobs: Array<Logprob>;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.output_text.delta\";\n};\nexport type ResponseTextDoneEvent = {\n  content_index: number;\n  item_id: string;\n  logprobs: Array<Logprob>;\n  output_index: number;\n  sequence_number: number;\n  text: string;\n  type: \"response.output_text.done\";\n};\nexport type Logprob = {\n  token: string;\n  logprob: number;\n  top_logprobs?: Array<TopLogprob>;\n};\nexport type TopLogprob = {\n  token?: string;\n  logprob?: number;\n};\nexport type ResponseUsage = {\n  input_tokens: number;\n  output_tokens: number;\n  total_tokens: number;\n};\nexport type Tool = ResponsesFunctionTool;\nexport type ToolChoiceFunction = {\n  name: string;\n  type: \"function\";\n};\nexport type ToolChoiceOptions = \"none\";\nexport type ReasoningEffort = \"minimal\" | \"low\" | \"medium\" | \"high\" | null;\nexport type StreamOptions = {\n  include_obfuscation?: boolean;\n};\n/** Marks keys from T that aren't in U as optional never */\nexport type Without<T, U> = {\n  [P in Exclude<keyof T, keyof U>]?: never;\n};\n/** Either T or U, but not both (mutually exclusive) */\nexport type XOR<T, U> = (T & Without<U, T>) | (U & Without<T, U>);\nexport type Ai_Cf_Baai_Bge_Base_En_V1_5_Input =\n  | {\n      text: string | string[];\n      /**\n       * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        text: string | string[];\n        /**\n         * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n         */\n        pooling?: \"mean\" | \"cls\";\n      }[];\n    };\nexport type Ai_Cf_Baai_Bge_Base_En_V1_5_Output =\n  | {\n      shape?: number[];\n      /**\n       * Embeddings of the requested text values\n       */\n      data?: number[][];\n      /**\n       * The pooling method used in the embedding process.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | Ai_Cf_Baai_Bge_Base_En_V1_5_AsyncResponse;\nexport interface Ai_Cf_Baai_Bge_Base_En_V1_5_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Baai_Bge_Base_En_V1_5 {\n  inputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Output;\n}\nexport type Ai_Cf_Openai_Whisper_Input =\n  | string\n  | {\n      /**\n       * An array of integers that represent the audio data constrained to 8-bit unsigned integer values\n       */\n      audio: number[];\n    };\nexport interface Ai_Cf_Openai_Whisper_Output {\n  /**\n   * The transcription\n   */\n  text: string;\n  word_count?: number;\n  words?: {\n    word?: string;\n    /**\n     * The second this word begins in the recording\n     */\n    start?: number;\n    /**\n     * The ending second when the word completes\n     */\n    end?: number;\n  }[];\n  vtt?: string;\n}\nexport declare abstract class Base_Ai_Cf_Openai_Whisper {\n  inputs: Ai_Cf_Openai_Whisper_Input;\n  postProcessedOutputs: Ai_Cf_Openai_Whisper_Output;\n}\nexport type Ai_Cf_Meta_M2M100_1_2B_Input =\n  | {\n      /**\n       * The text to be translated\n       */\n      text: string;\n      /**\n       * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified\n       */\n      source_lang?: string;\n      /**\n       * The language code to translate the text into (e.g., 'es' for Spanish)\n       */\n      target_lang: string;\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        /**\n         * The text to be translated\n         */\n        text: string;\n        /**\n         * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified\n         */\n        source_lang?: string;\n        /**\n         * The language code to translate the text into (e.g., 'es' for Spanish)\n         */\n        target_lang: string;\n      }[];\n    };\nexport type Ai_Cf_Meta_M2M100_1_2B_Output =\n  | {\n      /**\n       * The translated text in the target language\n       */\n      translated_text?: string;\n    }\n  | Ai_Cf_Meta_M2M100_1_2B_AsyncResponse;\nexport interface Ai_Cf_Meta_M2M100_1_2B_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Meta_M2M100_1_2B {\n  inputs: Ai_Cf_Meta_M2M100_1_2B_Input;\n  postProcessedOutputs: Ai_Cf_Meta_M2M100_1_2B_Output;\n}\nexport type Ai_Cf_Baai_Bge_Small_En_V1_5_Input =\n  | {\n      text: string | string[];\n      /**\n       * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        text: string | string[];\n        /**\n         * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n         */\n        pooling?: \"mean\" | \"cls\";\n      }[];\n    };\nexport type Ai_Cf_Baai_Bge_Small_En_V1_5_Output =\n  | {\n      shape?: number[];\n      /**\n       * Embeddings of the requested text values\n       */\n      data?: number[][];\n      /**\n       * The pooling method used in the embedding process.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | Ai_Cf_Baai_Bge_Small_En_V1_5_AsyncResponse;\nexport interface Ai_Cf_Baai_Bge_Small_En_V1_5_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Baai_Bge_Small_En_V1_5 {\n  inputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Output;\n}\nexport type Ai_Cf_Baai_Bge_Large_En_V1_5_Input =\n  | {\n      text: string | string[];\n      /**\n       * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        text: string | string[];\n        /**\n         * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n         */\n        pooling?: \"mean\" | \"cls\";\n      }[];\n    };\nexport type Ai_Cf_Baai_Bge_Large_En_V1_5_Output =\n  | {\n      shape?: number[];\n      /**\n       * Embeddings of the requested text values\n       */\n      data?: number[][];\n      /**\n       * The pooling method used in the embedding process.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | Ai_Cf_Baai_Bge_Large_En_V1_5_AsyncResponse;\nexport interface Ai_Cf_Baai_Bge_Large_En_V1_5_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Baai_Bge_Large_En_V1_5 {\n  inputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Output;\n}\nexport type Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input =\n  | string\n  | {\n      /**\n       * The input text prompt for the model to generate a response.\n       */\n      prompt?: string;\n      /**\n       * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n       */\n      raw?: boolean;\n      /**\n       * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n       */\n      top_p?: number;\n      /**\n       * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n       */\n      top_k?: number;\n      /**\n       * Random seed for reproducibility of the generation.\n       */\n      seed?: number;\n      /**\n       * Penalty for repeated tokens; higher values discourage repetition.\n       */\n      repetition_penalty?: number;\n      /**\n       * Decreases the likelihood of the model repeating the same lines verbatim.\n       */\n      frequency_penalty?: number;\n      /**\n       * Increases the likelihood of the model introducing new topics.\n       */\n      presence_penalty?: number;\n      image: number[] | (string & NonNullable<unknown>);\n      /**\n       * The maximum number of tokens to generate in the response.\n       */\n      max_tokens?: number;\n    };\nexport interface Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output {\n  description?: string;\n}\nexport declare abstract class Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M {\n  inputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input;\n  postProcessedOutputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output;\n}\nexport type Ai_Cf_Openai_Whisper_Tiny_En_Input =\n  | string\n  | {\n      /**\n       * An array of integers that represent the audio data constrained to 8-bit unsigned integer values\n       */\n      audio: number[];\n    };\nexport interface Ai_Cf_Openai_Whisper_Tiny_En_Output {\n  /**\n   * The transcription\n   */\n  text: string;\n  word_count?: number;\n  words?: {\n    word?: string;\n    /**\n     * The second this word begins in the recording\n     */\n    start?: number;\n    /**\n     * The ending second when the word completes\n     */\n    end?: number;\n  }[];\n  vtt?: string;\n}\nexport declare abstract class Base_Ai_Cf_Openai_Whisper_Tiny_En {\n  inputs: Ai_Cf_Openai_Whisper_Tiny_En_Input;\n  postProcessedOutputs: Ai_Cf_Openai_Whisper_Tiny_En_Output;\n}\nexport interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input {\n  audio:\n    | string\n    | {\n        body?: object;\n        contentType?: string;\n      };\n  /**\n   * Supported tasks are 'translate' or 'transcribe'.\n   */\n  task?: string;\n  /**\n   * The language of the audio being transcribed or translated.\n   */\n  language?: string;\n  /**\n   * Preprocess the audio with a voice activity detection model.\n   */\n  vad_filter?: boolean;\n  /**\n   * A text prompt to help provide context to the model on the contents of the audio.\n   */\n  initial_prompt?: string;\n  /**\n   * The prefix appended to the beginning of the output of the transcription and can guide the transcription result.\n   */\n  prefix?: string;\n  /**\n   * The number of beams to use in beam search decoding. Higher values may improve accuracy at the cost of speed.\n   */\n  beam_size?: number;\n  /**\n   * Whether to condition on previous text during transcription. Setting to false may help prevent hallucination loops.\n   */\n  condition_on_previous_text?: boolean;\n  /**\n   * Threshold for detecting no-speech segments. Segments with no-speech probability above this value are skipped.\n   */\n  no_speech_threshold?: number;\n  /**\n   * Threshold for filtering out segments with high compression ratio, which often indicate repetitive or hallucinated text.\n   */\n  compression_ratio_threshold?: number;\n  /**\n   * Threshold for filtering out segments with low average log probability, indicating low confidence.\n   */\n  log_prob_threshold?: number;\n  /**\n   * Optional threshold (in seconds) to skip silent periods that may cause hallucinations.\n   */\n  hallucination_silence_threshold?: number;\n}\nexport interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output {\n  transcription_info?: {\n    /**\n     * The language of the audio being transcribed or translated.\n     */\n    language?: string;\n    /**\n     * The confidence level or probability of the detected language being accurate, represented as a decimal between 0 and 1.\n     */\n    language_probability?: number;\n    /**\n     * The total duration of the original audio file, in seconds.\n     */\n    duration?: number;\n    /**\n     * The duration of the audio after applying Voice Activity Detection (VAD) to remove silent or irrelevant sections, in seconds.\n     */\n    duration_after_vad?: number;\n  };\n  /**\n   * The complete transcription of the audio.\n   */\n  text: string;\n  /**\n   * The total number of words in the transcription.\n   */\n  word_count?: number;\n  segments?: {\n    /**\n     * The starting time of the segment within the audio, in seconds.\n     */\n    start?: number;\n    /**\n     * The ending time of the segment within the audio, in seconds.\n     */\n    end?: number;\n    /**\n     * The transcription of the segment.\n     */\n    text?: string;\n    /**\n     * The temperature used in the decoding process, controlling randomness in predictions. Lower values result in more deterministic outputs.\n     */\n    temperature?: number;\n    /**\n     * The average log probability of the predictions for the words in this segment, indicating overall confidence.\n     */\n    avg_logprob?: number;\n    /**\n     * The compression ratio of the input to the output, measuring how much the text was compressed during the transcription process.\n     */\n    compression_ratio?: number;\n    /**\n     * The probability that the segment contains no speech, represented as a decimal between 0 and 1.\n     */\n    no_speech_prob?: number;\n    words?: {\n      /**\n       * The individual word transcribed from the audio.\n       */\n      word?: string;\n      /**\n       * The starting time of the word within the audio, in seconds.\n       */\n      start?: number;\n      /**\n       * The ending time of the word within the audio, in seconds.\n       */\n      end?: number;\n    }[];\n  }[];\n  /**\n   * The transcription in WebVTT format, which includes timing and text information for use in subtitles.\n   */\n  vtt?: string;\n}\nexport declare abstract class Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo {\n  inputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input;\n  postProcessedOutputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output;\n}\nexport type Ai_Cf_Baai_Bge_M3_Input =\n  | Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts\n  | Ai_Cf_Baai_Bge_M3_Input_Embedding\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: (\n        | Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts_1\n        | Ai_Cf_Baai_Bge_M3_Input_Embedding_1\n      )[];\n    };\nexport interface Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts {\n  /**\n   * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts\n   */\n  query?: string;\n  /**\n   * List of provided contexts. Note that the index in this array is important, as the response will refer to it.\n   */\n  contexts: {\n    /**\n     * One of the provided context content\n     */\n    text?: string;\n  }[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\nexport interface Ai_Cf_Baai_Bge_M3_Input_Embedding {\n  text: string | string[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\nexport interface Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts_1 {\n  /**\n   * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts\n   */\n  query?: string;\n  /**\n   * List of provided contexts. Note that the index in this array is important, as the response will refer to it.\n   */\n  contexts: {\n    /**\n     * One of the provided context content\n     */\n    text?: string;\n  }[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\nexport interface Ai_Cf_Baai_Bge_M3_Input_Embedding_1 {\n  text: string | string[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\nexport type Ai_Cf_Baai_Bge_M3_Output =\n  | Ai_Cf_Baai_Bge_M3_Output_Query\n  | Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts\n  | Ai_Cf_Baai_Bge_M3_Output_Embedding\n  | Ai_Cf_Baai_Bge_M3_AsyncResponse;\nexport interface Ai_Cf_Baai_Bge_M3_Output_Query {\n  response?: {\n    /**\n     * Index of the context in the request\n     */\n    id?: number;\n    /**\n     * Score of the context under the index.\n     */\n    score?: number;\n  }[];\n}\nexport interface Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts {\n  response?: number[][];\n  shape?: number[];\n  /**\n   * The pooling method used in the embedding process.\n   */\n  pooling?: \"mean\" | \"cls\";\n}\nexport interface Ai_Cf_Baai_Bge_M3_Output_Embedding {\n  shape?: number[];\n  /**\n   * Embeddings of the requested text values\n   */\n  data?: number[][];\n  /**\n   * The pooling method used in the embedding process.\n   */\n  pooling?: \"mean\" | \"cls\";\n}\nexport interface Ai_Cf_Baai_Bge_M3_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Baai_Bge_M3 {\n  inputs: Ai_Cf_Baai_Bge_M3_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_M3_Output;\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input {\n  /**\n   * A text description of the image you want to generate.\n   */\n  prompt: string;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer.\n   */\n  steps?: number;\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output {\n  /**\n   * The generated image in Base64 format.\n   */\n  image?: string;\n}\nexport declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output;\n}\nexport type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input =\n  | Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Prompt\n  | Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Messages;\nexport interface Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  image?: number[] | (string & NonNullable<unknown>);\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n}\nexport interface Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  image?: number[] | (string & NonNullable<unknown>);\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * If true, the response will be streamed back incrementally.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response?: string;\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\nexport declare abstract class Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct {\n  inputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output;\n}\nexport type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input =\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Async_Batch;\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Async_Batch {\n  requests?: {\n    /**\n     * User-supplied reference. This field will be present in the response as well it can be used to reference the request and response. It's NOT validated to be unique.\n     */\n    external_reference?: string;\n    /**\n     * Prompt for the text generation model\n     */\n    prompt?: string;\n    /**\n     * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n     */\n    stream?: boolean;\n    /**\n     * The maximum number of tokens to generate in the response.\n     */\n    max_tokens?: number;\n    /**\n     * Controls the randomness of the output; higher values produce more random results.\n     */\n    temperature?: number;\n    /**\n     * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n     */\n    top_p?: number;\n    /**\n     * Random seed for reproducibility of the generation.\n     */\n    seed?: number;\n    /**\n     * Penalty for repeated tokens; higher values discourage repetition.\n     */\n    repetition_penalty?: number;\n    /**\n     * Decreases the likelihood of the model repeating the same lines verbatim.\n     */\n    frequency_penalty?: number;\n    /**\n     * Increases the likelihood of the model introducing new topics.\n     */\n    presence_penalty?: number;\n    response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_2;\n  }[];\n}\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_2 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output =\n  | {\n      /**\n       * The generated text response from the model\n       */\n      response: string;\n      /**\n       * Usage statistics for the inference request\n       */\n      usage?: {\n        /**\n         * Total number of tokens in input\n         */\n        prompt_tokens?: number;\n        /**\n         * Total number of tokens in output\n         */\n        completion_tokens?: number;\n        /**\n         * Total number of input and output tokens\n         */\n        total_tokens?: number;\n      };\n      /**\n       * An array of tool calls requests made during the response generation\n       */\n      tool_calls?: {\n        /**\n         * The arguments passed to be passed to the tool call request\n         */\n        arguments?: object;\n        /**\n         * The name of the tool to be called\n         */\n        name?: string;\n      }[];\n    }\n  | string\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_AsyncResponse;\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast {\n  inputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output;\n}\nexport interface Ai_Cf_Meta_Llama_Guard_3_8B_Input {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender must alternate between 'user' and 'assistant'.\n     */\n    role: \"user\" | \"assistant\";\n    /**\n     * The content of the message as a string.\n     */\n    content: string;\n  }[];\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Dictate the output format of the generated response.\n   */\n  response_format?: {\n    /**\n     * Set to json_object to process and output generated text as JSON.\n     */\n    type?: string;\n  };\n}\nexport interface Ai_Cf_Meta_Llama_Guard_3_8B_Output {\n  response?:\n    | string\n    | {\n        /**\n         * Whether the conversation is safe or not.\n         */\n        safe?: boolean;\n        /**\n         * A list of what hazard categories predicted for the conversation, if the conversation is deemed unsafe.\n         */\n        categories?: string[];\n      };\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n}\nexport declare abstract class Base_Ai_Cf_Meta_Llama_Guard_3_8B {\n  inputs: Ai_Cf_Meta_Llama_Guard_3_8B_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_Guard_3_8B_Output;\n}\nexport interface Ai_Cf_Baai_Bge_Reranker_Base_Input {\n  /**\n   * A query you wish to perform against the provided contexts.\n   */\n  /**\n   * Number of returned results starting with the best score.\n   */\n  top_k?: number;\n  /**\n   * List of provided contexts. Note that the index in this array is important, as the response will refer to it.\n   */\n  contexts: {\n    /**\n     * One of the provided context content\n     */\n    text?: string;\n  }[];\n}\nexport interface Ai_Cf_Baai_Bge_Reranker_Base_Output {\n  response?: {\n    /**\n     * Index of the context in the request\n     */\n    id?: number;\n    /**\n     * Score of the context under the index.\n     */\n    score?: number;\n  }[];\n}\nexport declare abstract class Base_Ai_Cf_Baai_Bge_Reranker_Base {\n  inputs: Ai_Cf_Baai_Bge_Reranker_Base_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Reranker_Base_Output;\n}\nexport type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input =\n  | Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Prompt\n  | Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Messages;\nexport interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    /**\n     * The content of the message as a string.\n     */\n    content: string;\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\nexport declare abstract class Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct {\n  inputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output;\n}\nexport type Ai_Cf_Qwen_Qwq_32B_Input =\n  | Ai_Cf_Qwen_Qwq_32B_Prompt\n  | Ai_Cf_Qwen_Qwq_32B_Messages;\nexport interface Ai_Cf_Qwen_Qwq_32B_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwq_32B_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport type Ai_Cf_Qwen_Qwq_32B_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\nexport declare abstract class Base_Ai_Cf_Qwen_Qwq_32B {\n  inputs: Ai_Cf_Qwen_Qwq_32B_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwq_32B_Output;\n}\nexport type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input =\n  | Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Prompt\n  | Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Messages;\nexport interface Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\nexport declare abstract class Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct {\n  inputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output;\n}\nexport type Ai_Cf_Google_Gemma_3_12B_It_Input =\n  | Ai_Cf_Google_Gemma_3_12B_It_Prompt\n  | Ai_Cf_Google_Gemma_3_12B_It_Messages;\nexport interface Ai_Cf_Google_Gemma_3_12B_It_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Google_Gemma_3_12B_It_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport type Ai_Cf_Google_Gemma_3_12B_It_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\nexport declare abstract class Base_Ai_Cf_Google_Gemma_3_12B_It {\n  inputs: Ai_Cf_Google_Gemma_3_12B_It_Input;\n  postProcessedOutputs: Ai_Cf_Google_Gemma_3_12B_It_Output;\n}\nexport type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input =\n  | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt\n  | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages\n  | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Async_Batch;\nexport interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Async_Batch {\n  requests: (\n    | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt_Inner\n    | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages_Inner\n  )[];\n}\nexport interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt_Inner {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages_Inner {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The tool call id.\n     */\n    id?: string;\n    /**\n     * Specifies the type of tool (e.g., 'function').\n     */\n    type?: string;\n    /**\n     * Details of the function tool.\n     */\n    function?: {\n      /**\n       * The name of the tool to be called\n       */\n      name?: string;\n      /**\n       * The arguments passed to be passed to the tool call request\n       */\n      arguments?: object;\n    };\n  }[];\n};\nexport declare abstract class Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct {\n  inputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output;\n}\nexport type Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Input =\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Async_Batch;\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Async_Batch {\n  requests: (\n    | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt_1\n    | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages_1\n  )[];\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt_1 {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_2;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_2 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages_1 {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_3;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_3 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport type Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Output =\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Chat_Completion_Response\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Text_Completion_Response\n  | string\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_AsyncResponse;\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Chat_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"chat.completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index?: number;\n    /**\n     * The message generated by the model\n     */\n    message?: {\n      /**\n       * Role of the message author\n       */\n      role: string;\n      /**\n       * The content of the message\n       */\n      content: string;\n      /**\n       * Internal reasoning content (if available)\n       */\n      reasoning_content?: string;\n      /**\n       * Tool calls made by the assistant\n       */\n      tool_calls?: {\n        /**\n         * Unique identifier for the tool call\n         */\n        id: string;\n        /**\n         * Type of tool call\n         */\n        type: \"function\";\n        function: {\n          /**\n           * Name of the function to call\n           */\n          name: string;\n          /**\n           * JSON string of arguments for the function\n           */\n          arguments: string;\n        };\n      }[];\n    };\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason?: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * Log probabilities for the prompt (if requested)\n   */\n  prompt_logprobs?: {} | null;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Text_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"text_completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index: number;\n    /**\n     * The generated text completion\n     */\n    text: string;\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n    /**\n     * Log probabilities for the prompt (if requested)\n     */\n    prompt_logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8 {\n  inputs: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Output;\n}\nexport interface Ai_Cf_Deepgram_Nova_3_Input {\n  audio: {\n    body: object;\n    contentType: string;\n  };\n  /**\n   * Sets how the model will interpret strings submitted to the custom_topic param. When strict, the model will only return topics submitted using the custom_topic param. When extended, the model will return its own detected topics in addition to those submitted using the custom_topic param.\n   */\n  custom_topic_mode?: \"extended\" | \"strict\";\n  /**\n   * Custom topics you want the model to detect within your input audio or text if present Submit up to 100\n   */\n  custom_topic?: string;\n  /**\n   * Sets how the model will interpret intents submitted to the custom_intent param. When strict, the model will only return intents submitted using the custom_intent param. When extended, the model will return its own detected intents in addition those submitted using the custom_intents param\n   */\n  custom_intent_mode?: \"extended\" | \"strict\";\n  /**\n   * Custom intents you want the model to detect within your input audio if present\n   */\n  custom_intent?: string;\n  /**\n   * Identifies and extracts key entities from content in submitted audio\n   */\n  detect_entities?: boolean;\n  /**\n   * Identifies the dominant language spoken in submitted audio\n   */\n  detect_language?: boolean;\n  /**\n   * Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0\n   */\n  diarize?: boolean;\n  /**\n   * Identify and extract key entities from content in submitted audio\n   */\n  dictation?: boolean;\n  /**\n   * Specify the expected encoding of your submitted audio\n   */\n  encoding?:\n    | \"linear16\"\n    | \"flac\"\n    | \"mulaw\"\n    | \"amr-nb\"\n    | \"amr-wb\"\n    | \"opus\"\n    | \"speex\"\n    | \"g729\";\n  /**\n   * Arbitrary key-value pairs that are attached to the API response for usage in downstream processing\n   */\n  extra?: string;\n  /**\n   * Filler Words can help transcribe interruptions in your audio, like 'uh' and 'um'\n   */\n  filler_words?: boolean;\n  /**\n   * Key term prompting can boost or suppress specialized terminology and brands.\n   */\n  keyterm?: string;\n  /**\n   * Keywords can boost or suppress specialized terminology and brands.\n   */\n  keywords?: string;\n  /**\n   * The BCP-47 language tag that hints at the primary spoken language. Depending on the Model and API endpoint you choose only certain languages are available.\n   */\n  language?: string;\n  /**\n   * Spoken measurements will be converted to their corresponding abbreviations.\n   */\n  measurements?: boolean;\n  /**\n   * Opts out requests from the Deepgram Model Improvement Program. Refer to our Docs for pricing impacts before setting this to true. https://dpgr.am/deepgram-mip.\n   */\n  mip_opt_out?: boolean;\n  /**\n   * Mode of operation for the model representing broad area of topic that will be talked about in the supplied audio\n   */\n  mode?: \"general\" | \"medical\" | \"finance\";\n  /**\n   * Transcribe each audio channel independently.\n   */\n  multichannel?: boolean;\n  /**\n   * Numerals converts numbers from written format to numerical format.\n   */\n  numerals?: boolean;\n  /**\n   * Splits audio into paragraphs to improve transcript readability.\n   */\n  paragraphs?: boolean;\n  /**\n   * Profanity Filter looks for recognized profanity and converts it to the nearest recognized non-profane word or removes it from the transcript completely.\n   */\n  profanity_filter?: boolean;\n  /**\n   * Add punctuation and capitalization to the transcript.\n   */\n  punctuate?: boolean;\n  /**\n   * Redaction removes sensitive information from your transcripts.\n   */\n  redact?: string;\n  /**\n   * Search for terms or phrases in submitted audio and replaces them.\n   */\n  replace?: string;\n  /**\n   * Search for terms or phrases in submitted audio.\n   */\n  search?: string;\n  /**\n   * Recognizes the sentiment throughout a transcript or text.\n   */\n  sentiment?: boolean;\n  /**\n   * Apply formatting to transcript output. When set to true, additional formatting will be applied to transcripts to improve readability.\n   */\n  smart_format?: boolean;\n  /**\n   * Detect topics throughout a transcript or text.\n   */\n  topics?: boolean;\n  /**\n   * Segments speech into meaningful semantic units.\n   */\n  utterances?: boolean;\n  /**\n   * Seconds to wait before detecting a pause between words in submitted audio.\n   */\n  utt_split?: number;\n  /**\n   * The number of channels in the submitted audio\n   */\n  channels?: number;\n  /**\n   * Specifies whether the streaming endpoint should provide ongoing transcription updates as more audio is received. When set to true, the endpoint sends continuous updates, meaning transcription results may evolve over time. Note: Supported only for webosockets.\n   */\n  interim_results?: boolean;\n  /**\n   * Indicates how long model will wait to detect whether a speaker has finished speaking or pauses for a significant period of time. When set to a value, the streaming endpoint immediately finalizes the transcription for the processed time range and returns the transcript with a speech_final parameter set to true. Can also be set to false to disable endpointing\n   */\n  endpointing?: string;\n  /**\n   * Indicates that speech has started. You'll begin receiving Speech Started messages upon speech starting. Note: Supported only for webosockets.\n   */\n  vad_events?: boolean;\n  /**\n   * Indicates how long model will wait to send an UtteranceEnd message after a word has been transcribed. Use with interim_results. Note: Supported only for webosockets.\n   */\n  utterance_end_ms?: boolean;\n}\nexport interface Ai_Cf_Deepgram_Nova_3_Output {\n  results?: {\n    channels?: {\n      alternatives?: {\n        confidence?: number;\n        transcript?: string;\n        words?: {\n          confidence?: number;\n          end?: number;\n          start?: number;\n          word?: string;\n        }[];\n      }[];\n    }[];\n    summary?: {\n      result?: string;\n      short?: string;\n    };\n    sentiments?: {\n      segments?: {\n        text?: string;\n        start_word?: number;\n        end_word?: number;\n        sentiment?: string;\n        sentiment_score?: number;\n      }[];\n      average?: {\n        sentiment?: string;\n        sentiment_score?: number;\n      };\n    };\n  };\n}\nexport declare abstract class Base_Ai_Cf_Deepgram_Nova_3 {\n  inputs: Ai_Cf_Deepgram_Nova_3_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Nova_3_Output;\n}\nexport interface Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Input {\n  queries?: string | string[];\n  /**\n   * Optional instruction for the task\n   */\n  instruction?: string;\n  documents?: string | string[];\n  text?: string | string[];\n}\nexport interface Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Output {\n  data?: number[][];\n  shape?: number[];\n}\nexport declare abstract class Base_Ai_Cf_Qwen_Qwen3_Embedding_0_6B {\n  inputs: Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Output;\n}\nexport type Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Input =\n  | {\n      /**\n       * readable stream with audio data and content-type specified for that data\n       */\n      audio: {\n        body: object;\n        contentType: string;\n      };\n      /**\n       * type of data PCM data that's sent to the inference server as raw array\n       */\n      dtype?: \"uint8\" | \"float32\" | \"float64\";\n    }\n  | {\n      /**\n       * base64 encoded audio data\n       */\n      audio: string;\n      /**\n       * type of data PCM data that's sent to the inference server as raw array\n       */\n      dtype?: \"uint8\" | \"float32\" | \"float64\";\n    };\nexport interface Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Output {\n  /**\n   * if true, end-of-turn was detected\n   */\n  is_complete?: boolean;\n  /**\n   * probability of the end-of-turn detection\n   */\n  probability?: number;\n}\nexport declare abstract class Base_Ai_Cf_Pipecat_Ai_Smart_Turn_V2 {\n  inputs: Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Input;\n  postProcessedOutputs: Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Output;\n}\nexport declare abstract class Base_Ai_Cf_Openai_Gpt_Oss_120B {\n  inputs: XOR<ResponsesInput, ChatCompletionsInput>;\n  postProcessedOutputs: XOR<ResponsesOutput, ChatCompletionsOutput>;\n}\nexport declare abstract class Base_Ai_Cf_Openai_Gpt_Oss_20B {\n  inputs: XOR<ResponsesInput, ChatCompletionsInput>;\n  postProcessedOutputs: XOR<ResponsesOutput, ChatCompletionsOutput>;\n}\nexport interface Ai_Cf_Leonardo_Phoenix_1_0_Input {\n  /**\n   * A text description of the image you want to generate.\n   */\n  prompt: string;\n  /**\n   * Controls how closely the generated image should adhere to the prompt; higher values make the image more aligned with the prompt\n   */\n  guidance?: number;\n  /**\n   * Random seed for reproducibility of the image generation\n   */\n  seed?: number;\n  /**\n   * The height of the generated image in pixels\n   */\n  height?: number;\n  /**\n   * The width of the generated image in pixels\n   */\n  width?: number;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer\n   */\n  num_steps?: number;\n  /**\n   * Specify what to exclude from the generated images\n   */\n  negative_prompt?: string;\n}\n/**\n * The generated image in JPEG format\n */\nexport type Ai_Cf_Leonardo_Phoenix_1_0_Output = string;\nexport declare abstract class Base_Ai_Cf_Leonardo_Phoenix_1_0 {\n  inputs: Ai_Cf_Leonardo_Phoenix_1_0_Input;\n  postProcessedOutputs: Ai_Cf_Leonardo_Phoenix_1_0_Output;\n}\nexport interface Ai_Cf_Leonardo_Lucid_Origin_Input {\n  /**\n   * A text description of the image you want to generate.\n   */\n  prompt: string;\n  /**\n   * Controls how closely the generated image should adhere to the prompt; higher values make the image more aligned with the prompt\n   */\n  guidance?: number;\n  /**\n   * Random seed for reproducibility of the image generation\n   */\n  seed?: number;\n  /**\n   * The height of the generated image in pixels\n   */\n  height?: number;\n  /**\n   * The width of the generated image in pixels\n   */\n  width?: number;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer\n   */\n  num_steps?: number;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer\n   */\n  steps?: number;\n}\nexport interface Ai_Cf_Leonardo_Lucid_Origin_Output {\n  /**\n   * The generated image in Base64 format.\n   */\n  image?: string;\n}\nexport declare abstract class Base_Ai_Cf_Leonardo_Lucid_Origin {\n  inputs: Ai_Cf_Leonardo_Lucid_Origin_Input;\n  postProcessedOutputs: Ai_Cf_Leonardo_Lucid_Origin_Output;\n}\nexport interface Ai_Cf_Deepgram_Aura_1_Input {\n  /**\n   * Speaker used to produce the audio.\n   */\n  speaker?:\n    | \"angus\"\n    | \"asteria\"\n    | \"arcas\"\n    | \"orion\"\n    | \"orpheus\"\n    | \"athena\"\n    | \"luna\"\n    | \"zeus\"\n    | \"perseus\"\n    | \"helios\"\n    | \"hera\"\n    | \"stella\";\n  /**\n   * Encoding of the output audio.\n   */\n  encoding?: \"linear16\" | \"flac\" | \"mulaw\" | \"alaw\" | \"mp3\" | \"opus\" | \"aac\";\n  /**\n   * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type..\n   */\n  container?: \"none\" | \"wav\" | \"ogg\";\n  /**\n   * The text content to be converted to speech\n   */\n  text: string;\n  /**\n   * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable\n   */\n  sample_rate?: number;\n  /**\n   * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type.\n   */\n  bit_rate?: number;\n}\n/**\n * The generated audio in MP3 format\n */\nexport type Ai_Cf_Deepgram_Aura_1_Output = string;\nexport declare abstract class Base_Ai_Cf_Deepgram_Aura_1 {\n  inputs: Ai_Cf_Deepgram_Aura_1_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Aura_1_Output;\n}\nexport interface Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Input {\n  /**\n   * Input text to translate. Can be a single string or a list of strings.\n   */\n  text: string | string[];\n  /**\n   * Target langauge to translate to\n   */\n  target_language:\n    | \"asm_Beng\"\n    | \"awa_Deva\"\n    | \"ben_Beng\"\n    | \"bho_Deva\"\n    | \"brx_Deva\"\n    | \"doi_Deva\"\n    | \"eng_Latn\"\n    | \"gom_Deva\"\n    | \"gon_Deva\"\n    | \"guj_Gujr\"\n    | \"hin_Deva\"\n    | \"hne_Deva\"\n    | \"kan_Knda\"\n    | \"kas_Arab\"\n    | \"kas_Deva\"\n    | \"kha_Latn\"\n    | \"lus_Latn\"\n    | \"mag_Deva\"\n    | \"mai_Deva\"\n    | \"mal_Mlym\"\n    | \"mar_Deva\"\n    | \"mni_Beng\"\n    | \"mni_Mtei\"\n    | \"npi_Deva\"\n    | \"ory_Orya\"\n    | \"pan_Guru\"\n    | \"san_Deva\"\n    | \"sat_Olck\"\n    | \"snd_Arab\"\n    | \"snd_Deva\"\n    | \"tam_Taml\"\n    | \"tel_Telu\"\n    | \"urd_Arab\"\n    | \"unr_Deva\";\n}\nexport interface Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Output {\n  /**\n   * Translated texts\n   */\n  translations: string[];\n}\nexport declare abstract class Base_Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B {\n  inputs: Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Input;\n  postProcessedOutputs: Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Output;\n}\nexport type Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Input =\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Async_Batch;\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Async_Batch {\n  requests: (\n    | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt_1\n    | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages_1\n  )[];\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt_1 {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_2;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_2 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages_1 {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_3;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_3 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport type Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Output =\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Chat_Completion_Response\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Text_Completion_Response\n  | string\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_AsyncResponse;\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Chat_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"chat.completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index?: number;\n    /**\n     * The message generated by the model\n     */\n    message?: {\n      /**\n       * Role of the message author\n       */\n      role: string;\n      /**\n       * The content of the message\n       */\n      content: string;\n      /**\n       * Internal reasoning content (if available)\n       */\n      reasoning_content?: string;\n      /**\n       * Tool calls made by the assistant\n       */\n      tool_calls?: {\n        /**\n         * Unique identifier for the tool call\n         */\n        id: string;\n        /**\n         * Type of tool call\n         */\n        type: \"function\";\n        function: {\n          /**\n           * Name of the function to call\n           */\n          name: string;\n          /**\n           * JSON string of arguments for the function\n           */\n          arguments: string;\n        };\n      }[];\n    };\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason?: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * Log probabilities for the prompt (if requested)\n   */\n  prompt_logprobs?: {} | null;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Text_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"text_completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index: number;\n    /**\n     * The generated text completion\n     */\n    text: string;\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n    /**\n     * Log probabilities for the prompt (if requested)\n     */\n    prompt_logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It {\n  inputs: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Input;\n  postProcessedOutputs: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Output;\n}\nexport interface Ai_Cf_Pfnet_Plamo_Embedding_1B_Input {\n  /**\n   * Input text to embed. Can be a single string or a list of strings.\n   */\n  text: string | string[];\n}\nexport interface Ai_Cf_Pfnet_Plamo_Embedding_1B_Output {\n  /**\n   * Embedding vectors, where each vector is a list of floats.\n   */\n  data: number[][];\n  /**\n   * Shape of the embedding data as [number_of_embeddings, embedding_dimension].\n   *\n   * @minItems 2\n   * @maxItems 2\n   */\n  shape: [number, number];\n}\nexport declare abstract class Base_Ai_Cf_Pfnet_Plamo_Embedding_1B {\n  inputs: Ai_Cf_Pfnet_Plamo_Embedding_1B_Input;\n  postProcessedOutputs: Ai_Cf_Pfnet_Plamo_Embedding_1B_Output;\n}\nexport interface Ai_Cf_Deepgram_Flux_Input {\n  /**\n   * Encoding of the audio stream. Currently only supports raw signed little-endian 16-bit PCM.\n   */\n  encoding: \"linear16\";\n  /**\n   * Sample rate of the audio stream in Hz.\n   */\n  sample_rate: string;\n  /**\n   * End-of-turn confidence required to fire an eager end-of-turn event. When set, enables EagerEndOfTurn and TurnResumed events. Valid Values 0.3 - 0.9.\n   */\n  eager_eot_threshold?: string;\n  /**\n   * End-of-turn confidence required to finish a turn. Valid Values 0.5 - 0.9.\n   */\n  eot_threshold?: string;\n  /**\n   * A turn will be finished when this much time has passed after speech, regardless of EOT confidence.\n   */\n  eot_timeout_ms?: string;\n  /**\n   * Keyterm prompting can improve recognition of specialized terminology. Pass multiple keyterm query parameters to boost multiple keyterms.\n   */\n  keyterm?: string;\n  /**\n   * Opts out requests from the Deepgram Model Improvement Program. Refer to Deepgram Docs for pricing impacts before setting this to true. https://dpgr.am/deepgram-mip\n   */\n  mip_opt_out?: \"true\" | \"false\";\n  /**\n   * Label your requests for the purpose of identification during usage reporting\n   */\n  tag?: string;\n}\n/**\n * Output will be returned as websocket messages.\n */\nexport interface Ai_Cf_Deepgram_Flux_Output {\n  /**\n   * The unique identifier of the request (uuid)\n   */\n  request_id?: string;\n  /**\n   * Starts at 0 and increments for each message the server sends to the client.\n   */\n  sequence_id?: number;\n  /**\n   * The type of event being reported.\n   */\n  event?:\n    | \"Update\"\n    | \"StartOfTurn\"\n    | \"EagerEndOfTurn\"\n    | \"TurnResumed\"\n    | \"EndOfTurn\";\n  /**\n   * The index of the current turn\n   */\n  turn_index?: number;\n  /**\n   * Start time in seconds of the audio range that was transcribed\n   */\n  audio_window_start?: number;\n  /**\n   * End time in seconds of the audio range that was transcribed\n   */\n  audio_window_end?: number;\n  /**\n   * Text that was said over the course of the current turn\n   */\n  transcript?: string;\n  /**\n   * The words in the transcript\n   */\n  words?: {\n    /**\n     * The individual punctuated, properly-cased word from the transcript\n     */\n    word: string;\n    /**\n     * Confidence that this word was transcribed correctly\n     */\n    confidence: number;\n  }[];\n  /**\n   * Confidence that no more speech is coming in this turn\n   */\n  end_of_turn_confidence?: number;\n}\nexport declare abstract class Base_Ai_Cf_Deepgram_Flux {\n  inputs: Ai_Cf_Deepgram_Flux_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Flux_Output;\n}\nexport interface Ai_Cf_Deepgram_Aura_2_En_Input {\n  /**\n   * Speaker used to produce the audio.\n   */\n  speaker?:\n    | \"amalthea\"\n    | \"andromeda\"\n    | \"apollo\"\n    | \"arcas\"\n    | \"aries\"\n    | \"asteria\"\n    | \"athena\"\n    | \"atlas\"\n    | \"aurora\"\n    | \"callista\"\n    | \"cora\"\n    | \"cordelia\"\n    | \"delia\"\n    | \"draco\"\n    | \"electra\"\n    | \"harmonia\"\n    | \"helena\"\n    | \"hera\"\n    | \"hermes\"\n    | \"hyperion\"\n    | \"iris\"\n    | \"janus\"\n    | \"juno\"\n    | \"jupiter\"\n    | \"luna\"\n    | \"mars\"\n    | \"minerva\"\n    | \"neptune\"\n    | \"odysseus\"\n    | \"ophelia\"\n    | \"orion\"\n    | \"orpheus\"\n    | \"pandora\"\n    | \"phoebe\"\n    | \"pluto\"\n    | \"saturn\"\n    | \"thalia\"\n    | \"theia\"\n    | \"vesta\"\n    | \"zeus\";\n  /**\n   * Encoding of the output audio.\n   */\n  encoding?: \"linear16\" | \"flac\" | \"mulaw\" | \"alaw\" | \"mp3\" | \"opus\" | \"aac\";\n  /**\n   * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type..\n   */\n  container?: \"none\" | \"wav\" | \"ogg\";\n  /**\n   * The text content to be converted to speech\n   */\n  text: string;\n  /**\n   * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable\n   */\n  sample_rate?: number;\n  /**\n   * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type.\n   */\n  bit_rate?: number;\n}\n/**\n * The generated audio in MP3 format\n */\nexport type Ai_Cf_Deepgram_Aura_2_En_Output = string;\nexport declare abstract class Base_Ai_Cf_Deepgram_Aura_2_En {\n  inputs: Ai_Cf_Deepgram_Aura_2_En_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Aura_2_En_Output;\n}\nexport interface Ai_Cf_Deepgram_Aura_2_Es_Input {\n  /**\n   * Speaker used to produce the audio.\n   */\n  speaker?:\n    | \"sirio\"\n    | \"nestor\"\n    | \"carina\"\n    | \"celeste\"\n    | \"alvaro\"\n    | \"diana\"\n    | \"aquila\"\n    | \"selena\"\n    | \"estrella\"\n    | \"javier\";\n  /**\n   * Encoding of the output audio.\n   */\n  encoding?: \"linear16\" | \"flac\" | \"mulaw\" | \"alaw\" | \"mp3\" | \"opus\" | \"aac\";\n  /**\n   * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type..\n   */\n  container?: \"none\" | \"wav\" | \"ogg\";\n  /**\n   * The text content to be converted to speech\n   */\n  text: string;\n  /**\n   * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable\n   */\n  sample_rate?: number;\n  /**\n   * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type.\n   */\n  bit_rate?: number;\n}\n/**\n * The generated audio in MP3 format\n */\nexport type Ai_Cf_Deepgram_Aura_2_Es_Output = string;\nexport declare abstract class Base_Ai_Cf_Deepgram_Aura_2_Es {\n  inputs: Ai_Cf_Deepgram_Aura_2_Es_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Aura_2_Es_Output;\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Input {\n  multipart: {\n    body?: object;\n    contentType?: string;\n  };\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Output {\n  /**\n   * Generated image as Base64 string.\n   */\n  image?: string;\n}\nexport declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Dev {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Output;\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Input {\n  multipart: {\n    body?: object;\n    contentType?: string;\n  };\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Output {\n  /**\n   * Generated image as Base64 string.\n   */\n  image?: string;\n}\nexport declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Output;\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Input {\n  multipart: {\n    body?: object;\n    contentType?: string;\n  };\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Output {\n  /**\n   * Generated image as Base64 string.\n   */\n  image?: string;\n}\nexport declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Output;\n}\nexport declare abstract class Base_Ai_Cf_Zai_Org_Glm_4_7_Flash {\n  inputs: ChatCompletionsInput;\n  postProcessedOutputs: ChatCompletionsOutput;\n}\nexport declare abstract class Base_Ai_Cf_Moonshotai_Kimi_K2_5 {\n  inputs: ChatCompletionsInput;\n  postProcessedOutputs: ChatCompletionsOutput;\n}\nexport declare abstract class Base_Ai_Cf_Nvidia_Nemotron_3_120B_A12B {\n  inputs: ChatCompletionsInput;\n  postProcessedOutputs: ChatCompletionsOutput;\n}\nexport interface AiModels {\n  \"@cf/huggingface/distilbert-sst-2-int8\": BaseAiTextClassification;\n  \"@cf/stabilityai/stable-diffusion-xl-base-1.0\": BaseAiTextToImage;\n  \"@cf/runwayml/stable-diffusion-v1-5-inpainting\": BaseAiTextToImage;\n  \"@cf/runwayml/stable-diffusion-v1-5-img2img\": BaseAiTextToImage;\n  \"@cf/lykon/dreamshaper-8-lcm\": BaseAiTextToImage;\n  \"@cf/bytedance/stable-diffusion-xl-lightning\": BaseAiTextToImage;\n  \"@cf/myshell-ai/melotts\": BaseAiTextToSpeech;\n  \"@cf/google/embeddinggemma-300m\": BaseAiTextEmbeddings;\n  \"@cf/microsoft/resnet-50\": BaseAiImageClassification;\n  \"@cf/meta/llama-2-7b-chat-int8\": BaseAiTextGeneration;\n  \"@cf/mistral/mistral-7b-instruct-v0.1\": BaseAiTextGeneration;\n  \"@cf/meta/llama-2-7b-chat-fp16\": BaseAiTextGeneration;\n  \"@hf/thebloke/llama-2-13b-chat-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/mistral-7b-instruct-v0.1-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/zephyr-7b-beta-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/openhermes-2.5-mistral-7b-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/neural-chat-7b-v3-1-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/deepseek-coder-6.7b-base-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/deepseek-coder-6.7b-instruct-awq\": BaseAiTextGeneration;\n  \"@cf/deepseek-ai/deepseek-math-7b-instruct\": BaseAiTextGeneration;\n  \"@cf/defog/sqlcoder-7b-2\": BaseAiTextGeneration;\n  \"@cf/openchat/openchat-3.5-0106\": BaseAiTextGeneration;\n  \"@cf/tiiuae/falcon-7b-instruct\": BaseAiTextGeneration;\n  \"@cf/thebloke/discolm-german-7b-v1-awq\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-0.5b-chat\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-7b-chat-awq\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-14b-chat-awq\": BaseAiTextGeneration;\n  \"@cf/tinyllama/tinyllama-1.1b-chat-v1.0\": BaseAiTextGeneration;\n  \"@cf/microsoft/phi-2\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-1.8b-chat\": BaseAiTextGeneration;\n  \"@cf/mistral/mistral-7b-instruct-v0.2-lora\": BaseAiTextGeneration;\n  \"@hf/nousresearch/hermes-2-pro-mistral-7b\": BaseAiTextGeneration;\n  \"@hf/nexusflow/starling-lm-7b-beta\": BaseAiTextGeneration;\n  \"@hf/google/gemma-7b-it\": BaseAiTextGeneration;\n  \"@cf/meta-llama/llama-2-7b-chat-hf-lora\": BaseAiTextGeneration;\n  \"@cf/google/gemma-2b-it-lora\": BaseAiTextGeneration;\n  \"@cf/google/gemma-7b-it-lora\": BaseAiTextGeneration;\n  \"@hf/mistral/mistral-7b-instruct-v0.2\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3-8b-instruct\": BaseAiTextGeneration;\n  \"@cf/fblgit/una-cybertron-7b-v2-bf16\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3-8b-instruct-awq\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.1-8b-instruct-fp8\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.1-8b-instruct-awq\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.2-3b-instruct\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.2-1b-instruct\": BaseAiTextGeneration;\n  \"@cf/deepseek-ai/deepseek-r1-distill-qwen-32b\": BaseAiTextGeneration;\n  \"@cf/ibm-granite/granite-4.0-h-micro\": BaseAiTextGeneration;\n  \"@cf/facebook/bart-large-cnn\": BaseAiSummarization;\n  \"@cf/llava-hf/llava-1.5-7b-hf\": BaseAiImageToText;\n  \"@cf/baai/bge-base-en-v1.5\": Base_Ai_Cf_Baai_Bge_Base_En_V1_5;\n  \"@cf/openai/whisper\": Base_Ai_Cf_Openai_Whisper;\n  \"@cf/meta/m2m100-1.2b\": Base_Ai_Cf_Meta_M2M100_1_2B;\n  \"@cf/baai/bge-small-en-v1.5\": Base_Ai_Cf_Baai_Bge_Small_En_V1_5;\n  \"@cf/baai/bge-large-en-v1.5\": Base_Ai_Cf_Baai_Bge_Large_En_V1_5;\n  \"@cf/unum/uform-gen2-qwen-500m\": Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M;\n  \"@cf/openai/whisper-tiny-en\": Base_Ai_Cf_Openai_Whisper_Tiny_En;\n  \"@cf/openai/whisper-large-v3-turbo\": Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo;\n  \"@cf/baai/bge-m3\": Base_Ai_Cf_Baai_Bge_M3;\n  \"@cf/black-forest-labs/flux-1-schnell\": Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell;\n  \"@cf/meta/llama-3.2-11b-vision-instruct\": Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct;\n  \"@cf/meta/llama-3.3-70b-instruct-fp8-fast\": Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast;\n  \"@cf/meta/llama-guard-3-8b\": Base_Ai_Cf_Meta_Llama_Guard_3_8B;\n  \"@cf/baai/bge-reranker-base\": Base_Ai_Cf_Baai_Bge_Reranker_Base;\n  \"@cf/qwen/qwen2.5-coder-32b-instruct\": Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct;\n  \"@cf/qwen/qwq-32b\": Base_Ai_Cf_Qwen_Qwq_32B;\n  \"@cf/mistralai/mistral-small-3.1-24b-instruct\": Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct;\n  \"@cf/google/gemma-3-12b-it\": Base_Ai_Cf_Google_Gemma_3_12B_It;\n  \"@cf/meta/llama-4-scout-17b-16e-instruct\": Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct;\n  \"@cf/qwen/qwen3-30b-a3b-fp8\": Base_Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8;\n  \"@cf/deepgram/nova-3\": Base_Ai_Cf_Deepgram_Nova_3;\n  \"@cf/qwen/qwen3-embedding-0.6b\": Base_Ai_Cf_Qwen_Qwen3_Embedding_0_6B;\n  \"@cf/pipecat-ai/smart-turn-v2\": Base_Ai_Cf_Pipecat_Ai_Smart_Turn_V2;\n  \"@cf/openai/gpt-oss-120b\": Base_Ai_Cf_Openai_Gpt_Oss_120B;\n  \"@cf/openai/gpt-oss-20b\": Base_Ai_Cf_Openai_Gpt_Oss_20B;\n  \"@cf/leonardo/phoenix-1.0\": Base_Ai_Cf_Leonardo_Phoenix_1_0;\n  \"@cf/leonardo/lucid-origin\": Base_Ai_Cf_Leonardo_Lucid_Origin;\n  \"@cf/deepgram/aura-1\": Base_Ai_Cf_Deepgram_Aura_1;\n  \"@cf/ai4bharat/indictrans2-en-indic-1B\": Base_Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B;\n  \"@cf/aisingapore/gemma-sea-lion-v4-27b-it\": Base_Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It;\n  \"@cf/pfnet/plamo-embedding-1b\": Base_Ai_Cf_Pfnet_Plamo_Embedding_1B;\n  \"@cf/deepgram/flux\": Base_Ai_Cf_Deepgram_Flux;\n  \"@cf/deepgram/aura-2-en\": Base_Ai_Cf_Deepgram_Aura_2_En;\n  \"@cf/deepgram/aura-2-es\": Base_Ai_Cf_Deepgram_Aura_2_Es;\n  \"@cf/black-forest-labs/flux-2-dev\": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Dev;\n  \"@cf/black-forest-labs/flux-2-klein-4b\": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B;\n  \"@cf/black-forest-labs/flux-2-klein-9b\": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B;\n  \"@cf/zai-org/glm-4.7-flash\": Base_Ai_Cf_Zai_Org_Glm_4_7_Flash;\n  \"@cf/moonshotai/kimi-k2.5\": Base_Ai_Cf_Moonshotai_Kimi_K2_5;\n  \"@cf/nvidia/nemotron-3-120b-a12b\": Base_Ai_Cf_Nvidia_Nemotron_3_120B_A12B;\n}\nexport type AiOptions = {\n  /**\n   * Send requests as an asynchronous batch job, only works for supported models\n   * https://developers.cloudflare.com/workers-ai/features/batch-api\n   */\n  queueRequest?: boolean;\n  /**\n   * Establish websocket connections, only works for supported models\n   */\n  websocket?: boolean;\n  /**\n   * Tag your requests to group and view them in Cloudflare dashboard.\n   *\n   * Rules:\n   * Tags must only contain letters, numbers, and the symbols: : - . / @\n   * Each tag can have maximum 50 characters.\n   * Maximum 5 tags are allowed each request.\n   * Duplicate tags will removed.\n   */\n  tags?: string[];\n  gateway?: GatewayOptions;\n  returnRawResponse?: boolean;\n  prefix?: string;\n  extraHeaders?: object;\n};\nexport type AiModelsSearchParams = {\n  author?: string;\n  hide_experimental?: boolean;\n  page?: number;\n  per_page?: number;\n  search?: string;\n  source?: number;\n  task?: string;\n};\nexport type AiModelsSearchObject = {\n  id: string;\n  source: number;\n  name: string;\n  description: string;\n  task: {\n    id: string;\n    name: string;\n    description: string;\n  };\n  tags: string[];\n  properties: {\n    property_id: string;\n    value: string;\n  }[];\n};\nexport type ChatCompletionsBase = XOR<\n  ChatCompletionsPromptInput,\n  ChatCompletionsMessagesInput\n>;\nexport type ChatCompletionsInput = XOR<\n  ChatCompletionsBase,\n  {\n    requests: ChatCompletionsBase[];\n  }\n>;\nexport interface InferenceUpstreamError extends Error {}\nexport interface AiInternalError extends Error {}\nexport type AiModelListType = Record<string, any>;\nexport declare abstract class Ai<\n  AiModelList extends AiModelListType = AiModels,\n> {\n  aiGatewayLogId: string | null;\n  gateway(gatewayId: string): AiGateway;\n  /**\n   * Access the AI Search API for managing AI-powered search instances.\n   *\n   * This is the new API that replaces AutoRAG with better namespace separation:\n   * - Account-level operations: `list()`, `create()`\n   * - Instance-level operations: `get(id).search()`, `get(id).chatCompletions()`, `get(id).delete()`\n   *\n   * @example\n   * ```typescript\n   * // List all AI Search instances\n   * const instances = await env.AI.aiSearch.list();\n   *\n   * // Search an instance\n   * const results = await env.AI.aiSearch.get('my-search').search({\n   *   messages: [{ role: 'user', content: 'What is the policy?' }],\n   *   ai_search_options: {\n   *     retrieval: { max_num_results: 10 }\n   *   }\n   * });\n   *\n   * // Generate chat completions with AI Search context\n   * const response = await env.AI.aiSearch.get('my-search').chatCompletions({\n   *   messages: [{ role: 'user', content: 'What is the policy?' }],\n   *   model: '@cf/meta/llama-3.3-70b-instruct-fp8-fast'\n   * });\n   * ```\n   */\n  aiSearch(): AiSearchAccountService;\n  /**\n   * @deprecated AutoRAG has been replaced by AI Search.\n   * Use `env.AI.aiSearch` instead for better API design and new features.\n   *\n   * Migration guide:\n   * - `env.AI.autorag().list()` → `env.AI.aiSearch.list()`\n   * - `env.AI.autorag('id').search({ query: '...' })` → `env.AI.aiSearch.get('id').search({ messages: [{ role: 'user', content: '...' }] })`\n   * - `env.AI.autorag('id').aiSearch(...)` → `env.AI.aiSearch.get('id').chatCompletions(...)`\n   *\n   * Note: The old API continues to work for backwards compatibility, but new projects should use AI Search.\n   *\n   * @see AiSearchAccountService\n   * @param autoragId Optional instance ID (omit for account-level operations)\n   */\n  autorag(autoragId: string): AutoRAG;\n  run<\n    Name extends keyof AiModelList,\n    Options extends AiOptions,\n    InputOptions extends AiModelList[Name][\"inputs\"],\n  >(\n    model: Name,\n    inputs: InputOptions,\n    options?: Options,\n  ): Promise<\n    Options extends\n      | {\n          returnRawResponse: true;\n        }\n      | {\n          websocket: true;\n        }\n      ? Response\n      : InputOptions extends {\n            stream: true;\n          }\n        ? ReadableStream\n        : AiModelList[Name][\"postProcessedOutputs\"]\n  >;\n  models(params?: AiModelsSearchParams): Promise<AiModelsSearchObject[]>;\n  toMarkdown(): ToMarkdownService;\n  toMarkdown(\n    files: MarkdownDocument[],\n    options?: ConversionRequestOptions,\n  ): Promise<ConversionResponse[]>;\n  toMarkdown(\n    files: MarkdownDocument,\n    options?: ConversionRequestOptions,\n  ): Promise<ConversionResponse>;\n}\nexport type GatewayRetries = {\n  maxAttempts?: 1 | 2 | 3 | 4 | 5;\n  retryDelayMs?: number;\n  backoff?: \"constant\" | \"linear\" | \"exponential\";\n};\nexport type GatewayOptions = {\n  id: string;\n  cacheKey?: string;\n  cacheTtl?: number;\n  skipCache?: boolean;\n  metadata?: Record<string, number | string | boolean | null | bigint>;\n  collectLog?: boolean;\n  eventId?: string;\n  requestTimeoutMs?: number;\n  retries?: GatewayRetries;\n};\nexport type UniversalGatewayOptions = Exclude<GatewayOptions, \"id\"> & {\n  /**\n   ** @deprecated\n   */\n  id?: string;\n};\nexport type AiGatewayPatchLog = {\n  score?: number | null;\n  feedback?: -1 | 1 | null;\n  metadata?: Record<string, number | string | boolean | null | bigint> | null;\n};\nexport type AiGatewayLog = {\n  id: string;\n  provider: string;\n  model: string;\n  model_type?: string;\n  path: string;\n  duration: number;\n  request_type?: string;\n  request_content_type?: string;\n  status_code: number;\n  response_content_type?: string;\n  success: boolean;\n  cached: boolean;\n  tokens_in?: number;\n  tokens_out?: number;\n  metadata?: Record<string, number | string | boolean | null | bigint>;\n  step?: number;\n  cost?: number;\n  custom_cost?: boolean;\n  request_size: number;\n  request_head?: string;\n  request_head_complete: boolean;\n  response_size: number;\n  response_head?: string;\n  response_head_complete: boolean;\n  created_at: Date;\n};\nexport type AIGatewayProviders =\n  | \"workers-ai\"\n  | \"anthropic\"\n  | \"aws-bedrock\"\n  | \"azure-openai\"\n  | \"google-vertex-ai\"\n  | \"huggingface\"\n  | \"openai\"\n  | \"perplexity-ai\"\n  | \"replicate\"\n  | \"groq\"\n  | \"cohere\"\n  | \"google-ai-studio\"\n  | \"mistral\"\n  | \"grok\"\n  | \"openrouter\"\n  | \"deepseek\"\n  | \"cerebras\"\n  | \"cartesia\"\n  | \"elevenlabs\"\n  | \"adobe-firefly\";\nexport type AIGatewayHeaders = {\n  \"cf-aig-metadata\":\n    | Record<string, number | string | boolean | null | bigint>\n    | string;\n  \"cf-aig-custom-cost\":\n    | {\n        per_token_in?: number;\n        per_token_out?: number;\n      }\n    | {\n        total_cost?: number;\n      }\n    | string;\n  \"cf-aig-cache-ttl\": number | string;\n  \"cf-aig-skip-cache\": boolean | string;\n  \"cf-aig-cache-key\": string;\n  \"cf-aig-event-id\": string;\n  \"cf-aig-request-timeout\": number | string;\n  \"cf-aig-max-attempts\": number | string;\n  \"cf-aig-retry-delay\": number | string;\n  \"cf-aig-backoff\": string;\n  \"cf-aig-collect-log\": boolean | string;\n  Authorization: string;\n  \"Content-Type\": string;\n  [key: string]: string | number | boolean | object;\n};\nexport type AIGatewayUniversalRequest = {\n  provider: AIGatewayProviders | string; // eslint-disable-line\n  endpoint: string;\n  headers: Partial<AIGatewayHeaders>;\n  query: unknown;\n};\nexport interface AiGatewayInternalError extends Error {}\nexport interface AiGatewayLogNotFound extends Error {}\nexport declare abstract class AiGateway {\n  patchLog(logId: string, data: AiGatewayPatchLog): Promise<void>;\n  getLog(logId: string): Promise<AiGatewayLog>;\n  run(\n    data: AIGatewayUniversalRequest | AIGatewayUniversalRequest[],\n    options?: {\n      gateway?: UniversalGatewayOptions;\n      extraHeaders?: object;\n    },\n  ): Promise<Response>;\n  getUrl(provider?: AIGatewayProviders | string): Promise<string>; // eslint-disable-line\n}\n/**\n * @deprecated AutoRAG has been replaced by AI Search. Use AiSearchInternalError instead.\n * @see AiSearchInternalError\n */\nexport interface AutoRAGInternalError extends Error {}\n/**\n * @deprecated AutoRAG has been replaced by AI Search. Use AiSearchNotFoundError instead.\n * @see AiSearchNotFoundError\n */\nexport interface AutoRAGNotFoundError extends Error {}\n/**\n * @deprecated This error type is no longer used in the AI Search API.\n */\nexport interface AutoRAGUnauthorizedError extends Error {}\n/**\n * @deprecated AutoRAG has been replaced by AI Search. Use AiSearchNameNotSetError instead.\n * @see AiSearchNameNotSetError\n */\nexport interface AutoRAGNameNotSetError extends Error {}\nexport type ComparisonFilter = {\n  key: string;\n  type: \"eq\" | \"ne\" | \"gt\" | \"gte\" | \"lt\" | \"lte\";\n  value: string | number | boolean;\n};\nexport type CompoundFilter = {\n  type: \"and\" | \"or\";\n  filters: ComparisonFilter[];\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchSearchRequest with the new API instead.\n * @see AiSearchSearchRequest\n */\nexport type AutoRagSearchRequest = {\n  query: string;\n  filters?: CompoundFilter | ComparisonFilter;\n  max_num_results?: number;\n  ranking_options?: {\n    ranker?: string;\n    score_threshold?: number;\n  };\n  reranking?: {\n    enabled?: boolean;\n    model?: string;\n  };\n  rewrite_query?: boolean;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchChatCompletionsRequest with the new API instead.\n * @see AiSearchChatCompletionsRequest\n */\nexport type AutoRagAiSearchRequest = AutoRagSearchRequest & {\n  stream?: boolean;\n  system_prompt?: string;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchChatCompletionsRequest with stream: true instead.\n * @see AiSearchChatCompletionsRequest\n */\nexport type AutoRagAiSearchRequestStreaming = Omit<\n  AutoRagAiSearchRequest,\n  \"stream\"\n> & {\n  stream: true;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchSearchResponse with the new API instead.\n * @see AiSearchSearchResponse\n */\nexport type AutoRagSearchResponse = {\n  object: \"vector_store.search_results.page\";\n  search_query: string;\n  data: {\n    file_id: string;\n    filename: string;\n    score: number;\n    attributes: Record<string, string | number | boolean | null>;\n    content: {\n      type: \"text\";\n      text: string;\n    }[];\n  }[];\n  has_more: boolean;\n  next_page: string | null;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchListResponse with the new API instead.\n * @see AiSearchListResponse\n */\nexport type AutoRagListResponse = {\n  id: string;\n  enable: boolean;\n  type: string;\n  source: string;\n  vectorize_name: string;\n  paused: boolean;\n  status: string;\n}[];\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * The new API returns different response formats for chat completions.\n */\nexport type AutoRagAiSearchResponse = AutoRagSearchResponse & {\n  response: string;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use the new AI Search API instead: `env.AI.aiSearch`\n *\n * Migration guide:\n * - `env.AI.autorag().list()` → `env.AI.aiSearch.list()`\n * - `env.AI.autorag('id').search(...)` → `env.AI.aiSearch.get('id').search(...)`\n * - `env.AI.autorag('id').aiSearch(...)` → `env.AI.aiSearch.get('id').chatCompletions(...)`\n *\n * @see AiSearchAccountService\n * @see AiSearchInstanceService\n */\nexport declare abstract class AutoRAG {\n  /**\n   * @deprecated Use `env.AI.aiSearch.list()` instead.\n   * @see AiSearchAccountService.list\n   */\n  list(): Promise<AutoRagListResponse>;\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).search(...)` instead.\n   * Note: The new API uses a messages array instead of a query string.\n   * @see AiSearchInstanceService.search\n   */\n  search(params: AutoRagSearchRequest): Promise<AutoRagSearchResponse>;\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).chatCompletions(...)` instead.\n   * @see AiSearchInstanceService.chatCompletions\n   */\n  aiSearch(params: AutoRagAiSearchRequestStreaming): Promise<Response>;\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).chatCompletions(...)` instead.\n   * @see AiSearchInstanceService.chatCompletions\n   */\n  aiSearch(params: AutoRagAiSearchRequest): Promise<AutoRagAiSearchResponse>;\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).chatCompletions(...)` instead.\n   * @see AiSearchInstanceService.chatCompletions\n   */\n  aiSearch(\n    params: AutoRagAiSearchRequest,\n  ): Promise<AutoRagAiSearchResponse | Response>;\n}\nexport interface BasicImageTransformations {\n  /**\n   * Maximum width in image pixels. The value must be an integer.\n   */\n  width?: number;\n  /**\n   * Maximum height in image pixels. The value must be an integer.\n   */\n  height?: number;\n  /**\n   * Resizing mode as a string. It affects interpretation of width and height\n   * options:\n   *  - scale-down: Similar to contain, but the image is never enlarged. If\n   *    the image is larger than given width or height, it will be resized.\n   *    Otherwise its original size will be kept.\n   *  - contain: Resizes to maximum size that fits within the given width and\n   *    height. If only a single dimension is given (e.g. only width), the\n   *    image will be shrunk or enlarged to exactly match that dimension.\n   *    Aspect ratio is always preserved.\n   *  - cover: Resizes (shrinks or enlarges) to fill the entire area of width\n   *    and height. If the image has an aspect ratio different from the ratio\n   *    of width and height, it will be cropped to fit.\n   *  - crop: The image will be shrunk and cropped to fit within the area\n   *    specified by width and height. The image will not be enlarged. For images\n   *    smaller than the given dimensions it's the same as scale-down. For\n   *    images larger than the given dimensions, it's the same as cover.\n   *    See also trim.\n   *  - pad: Resizes to the maximum size that fits within the given width and\n   *    height, and then fills the remaining area with a background color\n   *    (white by default). Use of this mode is not recommended, as the same\n   *    effect can be more efficiently achieved with the contain mode and the\n   *    CSS object-fit: contain property.\n   *  - squeeze: Stretches and deforms to the width and height given, even if it\n   *    breaks aspect ratio\n   */\n  fit?: \"scale-down\" | \"contain\" | \"cover\" | \"crop\" | \"pad\" | \"squeeze\";\n  /**\n   * Image segmentation using artificial intelligence models. Sets pixels not\n   * within selected segment area to transparent e.g \"foreground\" sets every\n   * background pixel as transparent.\n   */\n  segment?: \"foreground\";\n  /**\n   * When cropping with fit: \"cover\", this defines the side or point that should\n   * be left uncropped. The value is either a string\n   * \"left\", \"right\", \"top\", \"bottom\", \"auto\", or \"center\" (the default),\n   * or an object {x, y} containing focal point coordinates in the original\n   * image expressed as fractions ranging from 0.0 (top or left) to 1.0\n   * (bottom or right), 0.5 being the center. {fit: \"cover\", gravity: \"top\"} will\n   * crop bottom or left and right sides as necessary, but won’t crop anything\n   * from the top. {fit: \"cover\", gravity: {x:0.5, y:0.2}} will crop each side to\n   * preserve as much as possible around a point at 20% of the height of the\n   * source image.\n   */\n  gravity?:\n    | \"face\"\n    | \"left\"\n    | \"right\"\n    | \"top\"\n    | \"bottom\"\n    | \"center\"\n    | \"auto\"\n    | \"entropy\"\n    | BasicImageTransformationsGravityCoordinates;\n  /**\n   * Background color to add underneath the image. Applies only to images with\n   * transparency (such as PNG). Accepts any CSS color (#RRGGBB, rgba(…),\n   * hsl(…), etc.)\n   */\n  background?: string;\n  /**\n   * Number of degrees (90, 180, 270) to rotate the image by. width and height\n   * options refer to axes after rotation.\n   */\n  rotate?: 0 | 90 | 180 | 270 | 360;\n}\nexport interface BasicImageTransformationsGravityCoordinates {\n  x?: number;\n  y?: number;\n  mode?: \"remainder\" | \"box-center\";\n}\n/**\n * In addition to the properties you can set in the RequestInit dict\n * that you pass as an argument to the Request constructor, you can\n * set certain properties of a `cf` object to control how Cloudflare\n * features are applied to that new Request.\n *\n * Note: Currently, these properties cannot be tested in the\n * playground.\n */\nexport interface RequestInitCfProperties extends Record<string, unknown> {\n  cacheEverything?: boolean;\n  /**\n   * A request's cache key is what determines if two requests are\n   * \"the same\" for caching purposes. If a request has the same cache key\n   * as some previous request, then we can serve the same cached response for\n   * both. (e.g. 'some-key')\n   *\n   * Only available for Enterprise customers.\n   */\n  cacheKey?: string;\n  /**\n   * This allows you to append additional Cache-Tag response headers\n   * to the origin response without modifications to the origin server.\n   * This will allow for greater control over the Purge by Cache Tag feature\n   * utilizing changes only in the Workers process.\n   *\n   * Only available for Enterprise customers.\n   */\n  cacheTags?: string[];\n  /**\n   * Force response to be cached for a given number of seconds. (e.g. 300)\n   */\n  cacheTtl?: number;\n  /**\n   * Force response to be cached for a given number of seconds based on the Origin status code.\n   * (e.g. { '200-299': 86400, '404': 1, '500-599': 0 })\n   */\n  cacheTtlByStatus?: Record<string, number>;\n  scrapeShield?: boolean;\n  apps?: boolean;\n  image?: RequestInitCfPropertiesImage;\n  minify?: RequestInitCfPropertiesImageMinify;\n  mirage?: boolean;\n  polish?: \"lossy\" | \"lossless\" | \"off\";\n  r2?: RequestInitCfPropertiesR2;\n  /**\n   * Redirects the request to an alternate origin server. You can use this,\n   * for example, to implement load balancing across several origins.\n   * (e.g.us-east.example.com)\n   *\n   * Note - For security reasons, the hostname set in resolveOverride must\n   * be proxied on the same Cloudflare zone of the incoming request.\n   * Otherwise, the setting is ignored. CNAME hosts are allowed, so to\n   * resolve to a host under a different domain or a DNS only domain first\n   * declare a CNAME record within your own zone’s DNS mapping to the\n   * external hostname, set proxy on Cloudflare, then set resolveOverride\n   * to point to that CNAME record.\n   */\n  resolveOverride?: string;\n}\nexport interface RequestInitCfPropertiesImageDraw extends BasicImageTransformations {\n  /**\n   * Absolute URL of the image file to use for the drawing. It can be any of\n   * the supported file formats. For drawing of watermarks or non-rectangular\n   * overlays we recommend using PNG or WebP images.\n   */\n  url: string;\n  /**\n   * Floating-point number between 0 (transparent) and 1 (opaque).\n   * For example, opacity: 0.5 makes overlay semitransparent.\n   */\n  opacity?: number;\n  /**\n   * - If set to true, the overlay image will be tiled to cover the entire\n   *   area. This is useful for stock-photo-like watermarks.\n   * - If set to \"x\", the overlay image will be tiled horizontally only\n   *   (form a line).\n   * - If set to \"y\", the overlay image will be tiled vertically only\n   *   (form a line).\n   */\n  repeat?: true | \"x\" | \"y\";\n  /**\n   * Position of the overlay image relative to a given edge. Each property is\n   * an offset in pixels. 0 aligns exactly to the edge. For example, left: 10\n   * positions left side of the overlay 10 pixels from the left edge of the\n   * image it's drawn over. bottom: 0 aligns bottom of the overlay with bottom\n   * of the background image.\n   *\n   * Setting both left & right, or both top & bottom is an error.\n   *\n   * If no position is specified, the image will be centered.\n   */\n  top?: number;\n  left?: number;\n  bottom?: number;\n  right?: number;\n}\nexport interface RequestInitCfPropertiesImage extends BasicImageTransformations {\n  /**\n   * Device Pixel Ratio. Default 1. Multiplier for width/height that makes it\n   * easier to specify higher-DPI sizes in <img srcset>.\n   */\n  dpr?: number;\n  /**\n   * Allows you to trim your image. Takes dpr into account and is performed before\n   * resizing or rotation.\n   *\n   * It can be used as:\n   * - left, top, right, bottom - it will specify the number of pixels to cut\n   *   off each side\n   * - width, height - the width/height you'd like to end up with - can be used\n   *   in combination with the properties above\n   * - border - this will automatically trim the surroundings of an image based on\n   *   it's color. It consists of three properties:\n   *    - color: rgb or hex representation of the color you wish to trim (todo: verify the rgba bit)\n   *    - tolerance: difference from color to treat as color\n   *    - keep: the number of pixels of border to keep\n   */\n  trim?:\n    | \"border\"\n    | {\n        top?: number;\n        bottom?: number;\n        left?: number;\n        right?: number;\n        width?: number;\n        height?: number;\n        border?:\n          | boolean\n          | {\n              color?: string;\n              tolerance?: number;\n              keep?: number;\n            };\n      };\n  /**\n   * Quality setting from 1-100 (useful values are in 60-90 range). Lower values\n   * make images look worse, but load faster. The default is 85. It applies only\n   * to JPEG and WebP images. It doesn’t have any effect on PNG.\n   */\n  quality?: number | \"low\" | \"medium-low\" | \"medium-high\" | \"high\";\n  /**\n   * Output format to generate. It can be:\n   *  - avif: generate images in AVIF format.\n   *  - webp: generate images in Google WebP format. Set quality to 100 to get\n   *    the WebP-lossless format.\n   *  - json: instead of generating an image, outputs information about the\n   *    image, in JSON format. The JSON object will contain image size\n   *    (before and after resizing), source image’s MIME type, file size, etc.\n   * - jpeg: generate images in JPEG format.\n   * - png: generate images in PNG format.\n   */\n  format?:\n    | \"avif\"\n    | \"webp\"\n    | \"json\"\n    | \"jpeg\"\n    | \"png\"\n    | \"baseline-jpeg\"\n    | \"png-force\"\n    | \"svg\";\n  /**\n   * Whether to preserve animation frames from input files. Default is true.\n   * Setting it to false reduces animations to still images. This setting is\n   * recommended when enlarging images or processing arbitrary user content,\n   * because large GIF animations can weigh tens or even hundreds of megabytes.\n   * It is also useful to set anim:false when using format:\"json\" to get the\n   * response quicker without the number of frames.\n   */\n  anim?: boolean;\n  /**\n   * What EXIF data should be preserved in the output image. Note that EXIF\n   * rotation and embedded color profiles are always applied (\"baked in\" into\n   * the image), and aren't affected by this option. Note that if the Polish\n   * feature is enabled, all metadata may have been removed already and this\n   * option may have no effect.\n   *  - keep: Preserve most of EXIF metadata, including GPS location if there's\n   *    any.\n   *  - copyright: Only keep the copyright tag, and discard everything else.\n   *    This is the default behavior for JPEG files.\n   *  - none: Discard all invisible EXIF metadata. Currently WebP and PNG\n   *    output formats always discard metadata.\n   */\n  metadata?: \"keep\" | \"copyright\" | \"none\";\n  /**\n   * Strength of sharpening filter to apply to the image. Floating-point\n   * number between 0 (no sharpening, default) and 10 (maximum). 1.0 is a\n   * recommended value for downscaled images.\n   */\n  sharpen?: number;\n  /**\n   * Radius of a blur filter (approximate gaussian). Maximum supported radius\n   * is 250.\n   */\n  blur?: number;\n  /**\n   * Overlays are drawn in the order they appear in the array (last array\n   * entry is the topmost layer).\n   */\n  draw?: RequestInitCfPropertiesImageDraw[];\n  /**\n   * Fetching image from authenticated origin. Setting this property will\n   * pass authentication headers (Authorization, Cookie, etc.) through to\n   * the origin.\n   */\n  \"origin-auth\"?: \"share-publicly\";\n  /**\n   * Adds a border around the image. The border is added after resizing. Border\n   * width takes dpr into account, and can be specified either using a single\n   * width property, or individually for each side.\n   */\n  border?:\n    | {\n        color: string;\n        width: number;\n      }\n    | {\n        color: string;\n        top: number;\n        right: number;\n        bottom: number;\n        left: number;\n      };\n  /**\n   * Increase brightness by a factor. A value of 1.0 equals no change, a value\n   * of 0.5 equals half brightness, and a value of 2.0 equals twice as bright.\n   * 0 is ignored.\n   */\n  brightness?: number;\n  /**\n   * Increase contrast by a factor. A value of 1.0 equals no change, a value of\n   * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is\n   * ignored.\n   */\n  contrast?: number;\n  /**\n   * Increase exposure by a factor. A value of 1.0 equals no change, a value of\n   * 0.5 darkens the image, and a value of 2.0 lightens the image. 0 is ignored.\n   */\n  gamma?: number;\n  /**\n   * Increase contrast by a factor. A value of 1.0 equals no change, a value of\n   * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is\n   * ignored.\n   */\n  saturation?: number;\n  /**\n   * Flips the images horizontally, vertically, or both. Flipping is applied before\n   * rotation, so if you apply flip=h,rotate=90 then the image will be flipped\n   * horizontally, then rotated by 90 degrees.\n   */\n  flip?: \"h\" | \"v\" | \"hv\";\n  /**\n   * Slightly reduces latency on a cache miss by selecting a\n   * quickest-to-compress file format, at a cost of increased file size and\n   * lower image quality. It will usually override the format option and choose\n   * JPEG over WebP or AVIF. We do not recommend using this option, except in\n   * unusual circumstances like resizing uncacheable dynamically-generated\n   * images.\n   */\n  compression?: \"fast\";\n}\nexport interface RequestInitCfPropertiesImageMinify {\n  javascript?: boolean;\n  css?: boolean;\n  html?: boolean;\n}\nexport interface RequestInitCfPropertiesR2 {\n  /**\n   * Colo id of bucket that an object is stored in\n   */\n  bucketColoId?: number;\n}\n/**\n * Request metadata provided by Cloudflare's edge.\n */\nexport type IncomingRequestCfProperties<HostMetadata = unknown> =\n  IncomingRequestCfPropertiesBase &\n    IncomingRequestCfPropertiesBotManagementEnterprise &\n    IncomingRequestCfPropertiesCloudflareForSaaSEnterprise<HostMetadata> &\n    IncomingRequestCfPropertiesGeographicInformation &\n    IncomingRequestCfPropertiesCloudflareAccessOrApiShield;\nexport interface IncomingRequestCfPropertiesBase extends Record<\n  string,\n  unknown\n> {\n  /**\n   * [ASN](https://www.iana.org/assignments/as-numbers/as-numbers.xhtml) of the incoming request.\n   *\n   * @example 395747\n   */\n  asn?: number;\n  /**\n   * The organization which owns the ASN of the incoming request.\n   *\n   * @example \"Google Cloud\"\n   */\n  asOrganization?: string;\n  /**\n   * The original value of the `Accept-Encoding` header if Cloudflare modified it.\n   *\n   * @example \"gzip, deflate, br\"\n   */\n  clientAcceptEncoding?: string;\n  /**\n   * The number of milliseconds it took for the request to reach your worker.\n   *\n   * @example 22\n   */\n  clientTcpRtt?: number;\n  /**\n   * The three-letter [IATA](https://en.wikipedia.org/wiki/IATA_airport_code)\n   * airport code of the data center that the request hit.\n   *\n   * @example \"DFW\"\n   */\n  colo: string;\n  /**\n   * Represents the upstream's response to a\n   * [TCP `keepalive` message](https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html)\n   * from cloudflare.\n   *\n   * For workers with no upstream, this will always be `1`.\n   *\n   * @example 3\n   */\n  edgeRequestKeepAliveStatus: IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus;\n  /**\n   * The HTTP Protocol the request used.\n   *\n   * @example \"HTTP/2\"\n   */\n  httpProtocol: string;\n  /**\n   * The browser-requested prioritization information in the request object.\n   *\n   * If no information was set, defaults to the empty string `\"\"`\n   *\n   * @example \"weight=192;exclusive=0;group=3;group-weight=127\"\n   * @default \"\"\n   */\n  requestPriority: string;\n  /**\n   * The TLS version of the connection to Cloudflare.\n   * In requests served over plaintext (without TLS), this property is the empty string `\"\"`.\n   *\n   * @example \"TLSv1.3\"\n   */\n  tlsVersion: string;\n  /**\n   * The cipher for the connection to Cloudflare.\n   * In requests served over plaintext (without TLS), this property is the empty string `\"\"`.\n   *\n   * @example \"AEAD-AES128-GCM-SHA256\"\n   */\n  tlsCipher: string;\n  /**\n   * Metadata containing the [`HELLO`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2) and [`FINISHED`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9) messages from this request's TLS handshake.\n   *\n   * If the incoming request was served over plaintext (without TLS) this field is undefined.\n   */\n  tlsExportedAuthenticator?: IncomingRequestCfPropertiesExportedAuthenticatorMetadata;\n}\nexport interface IncomingRequestCfPropertiesBotManagementBase {\n  /**\n   * Cloudflare’s [level of certainty](https://developers.cloudflare.com/bots/concepts/bot-score/) that a request comes from a bot,\n   * represented as an integer percentage between `1` (almost certainly a bot) and `99` (almost certainly human).\n   *\n   * @example 54\n   */\n  score: number;\n  /**\n   * A boolean value that is true if the request comes from a good bot, like Google or Bing.\n   * Most customers choose to allow this traffic. For more details, see [Traffic from known bots](https://developers.cloudflare.com/firewall/known-issues-and-faq/#how-does-firewall-rules-handle-traffic-from-known-bots).\n   */\n  verifiedBot: boolean;\n  /**\n   * A boolean value that is true if the request originates from a\n   * Cloudflare-verified proxy service.\n   */\n  corporateProxy: boolean;\n  /**\n   * A boolean value that's true if the request matches [file extensions](https://developers.cloudflare.com/bots/reference/static-resources/) for many types of static resources.\n   */\n  staticResource: boolean;\n  /**\n   * List of IDs that correlate to the Bot Management heuristic detections made on a request (you can have multiple heuristic detections on the same request).\n   */\n  detectionIds: number[];\n}\nexport interface IncomingRequestCfPropertiesBotManagement {\n  /**\n   * Results of Cloudflare's Bot Management analysis\n   */\n  botManagement: IncomingRequestCfPropertiesBotManagementBase;\n  /**\n   * Duplicate of `botManagement.score`.\n   *\n   * @deprecated\n   */\n  clientTrustScore: number;\n}\nexport interface IncomingRequestCfPropertiesBotManagementEnterprise extends IncomingRequestCfPropertiesBotManagement {\n  /**\n   * Results of Cloudflare's Bot Management analysis\n   */\n  botManagement: IncomingRequestCfPropertiesBotManagementBase & {\n    /**\n     * A [JA3 Fingerprint](https://developers.cloudflare.com/bots/concepts/ja3-fingerprint/) to help profile specific SSL/TLS clients\n     * across different destination IPs, Ports, and X509 certificates.\n     */\n    ja3Hash: string;\n  };\n}\nexport interface IncomingRequestCfPropertiesCloudflareForSaaSEnterprise<\n  HostMetadata,\n> {\n  /**\n   * Custom metadata set per-host in [Cloudflare for SaaS](https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/).\n   *\n   * This field is only present if you have Cloudflare for SaaS enabled on your account\n   * and you have followed the [required steps to enable it]((https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/domain-support/custom-metadata/)).\n   */\n  hostMetadata?: HostMetadata;\n}\nexport interface IncomingRequestCfPropertiesCloudflareAccessOrApiShield {\n  /**\n   * Information about the client certificate presented to Cloudflare.\n   *\n   * This is populated when the incoming request is served over TLS using\n   * either Cloudflare Access or API Shield (mTLS)\n   * and the presented SSL certificate has a valid\n   * [Certificate Serial Number](https://ldapwiki.com/wiki/Certificate%20Serial%20Number)\n   * (i.e., not `null` or `\"\"`).\n   *\n   * Otherwise, a set of placeholder values are used.\n   *\n   * The property `certPresented` will be set to `\"1\"` when\n   * the object is populated (i.e. the above conditions were met).\n   */\n  tlsClientAuth:\n    | IncomingRequestCfPropertiesTLSClientAuth\n    | IncomingRequestCfPropertiesTLSClientAuthPlaceholder;\n}\n/**\n * Metadata about the request's TLS handshake\n */\nexport interface IncomingRequestCfPropertiesExportedAuthenticatorMetadata {\n  /**\n   * The client's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal\n   *\n   * @example \"44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d\"\n   */\n  clientHandshake: string;\n  /**\n   * The server's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal\n   *\n   * @example \"44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d\"\n   */\n  serverHandshake: string;\n  /**\n   * The client's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal\n   *\n   * @example \"084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b\"\n   */\n  clientFinished: string;\n  /**\n   * The server's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal\n   *\n   * @example \"084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b\"\n   */\n  serverFinished: string;\n}\n/**\n * Geographic data about the request's origin.\n */\nexport interface IncomingRequestCfPropertiesGeographicInformation {\n  /**\n   * The [ISO 3166-1 Alpha 2](https://www.iso.org/iso-3166-country-codes.html) country code the request originated from.\n   *\n   * If your worker is [configured to accept TOR connections](https://support.cloudflare.com/hc/en-us/articles/203306930-Understanding-Cloudflare-Tor-support-and-Onion-Routing), this may also be `\"T1\"`, indicating a request that originated over TOR.\n   *\n   * If Cloudflare is unable to determine where the request originated this property is omitted.\n   *\n   * The country code `\"T1\"` is used for requests originating on TOR.\n   *\n   * @example \"GB\"\n   */\n  country?: Iso3166Alpha2Code | \"T1\";\n  /**\n   * If present, this property indicates that the request originated in the EU\n   *\n   * @example \"1\"\n   */\n  isEUCountry?: \"1\";\n  /**\n   * A two-letter code indicating the continent the request originated from.\n   *\n   * @example \"AN\"\n   */\n  continent?: ContinentCode;\n  /**\n   * The city the request originated from\n   *\n   * @example \"Austin\"\n   */\n  city?: string;\n  /**\n   * Postal code of the incoming request\n   *\n   * @example \"78701\"\n   */\n  postalCode?: string;\n  /**\n   * Latitude of the incoming request\n   *\n   * @example \"30.27130\"\n   */\n  latitude?: string;\n  /**\n   * Longitude of the incoming request\n   *\n   * @example \"-97.74260\"\n   */\n  longitude?: string;\n  /**\n   * Timezone of the incoming request\n   *\n   * @example \"America/Chicago\"\n   */\n  timezone?: string;\n  /**\n   * If known, the ISO 3166-2 name for the first level region associated with\n   * the IP address of the incoming request\n   *\n   * @example \"Texas\"\n   */\n  region?: string;\n  /**\n   * If known, the ISO 3166-2 code for the first-level region associated with\n   * the IP address of the incoming request\n   *\n   * @example \"TX\"\n   */\n  regionCode?: string;\n  /**\n   * Metro code (DMA) of the incoming request\n   *\n   * @example \"635\"\n   */\n  metroCode?: string;\n}\n/** Data about the incoming request's TLS certificate */\nexport interface IncomingRequestCfPropertiesTLSClientAuth {\n  /** Always `\"1\"`, indicating that the certificate was presented */\n  certPresented: \"1\";\n  /**\n   * Result of certificate verification.\n   *\n   * @example \"FAILED:self signed certificate\"\n   */\n  certVerified: Exclude<CertVerificationStatus, \"NONE\">;\n  /** The presented certificate's revokation status.\n   *\n   * - A value of `\"1\"` indicates the certificate has been revoked\n   * - A value of `\"0\"` indicates the certificate has not been revoked\n   */\n  certRevoked: \"1\" | \"0\";\n  /**\n   * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html)\n   *\n   * @example \"CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certIssuerDN: string;\n  /**\n   * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html)\n   *\n   * @example \"CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certSubjectDN: string;\n  /**\n   * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted)\n   *\n   * @example \"CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certIssuerDNRFC2253: string;\n  /**\n   * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted)\n   *\n   * @example \"CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certSubjectDNRFC2253: string;\n  /** The certificate issuer's distinguished name (legacy policies) */\n  certIssuerDNLegacy: string;\n  /** The certificate subject's distinguished name (legacy policies) */\n  certSubjectDNLegacy: string;\n  /**\n   * The certificate's serial number\n   *\n   * @example \"00936EACBE07F201DF\"\n   */\n  certSerial: string;\n  /**\n   * The certificate issuer's serial number\n   *\n   * @example \"2489002934BDFEA34\"\n   */\n  certIssuerSerial: string;\n  /**\n   * The certificate's Subject Key Identifier\n   *\n   * @example \"BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4\"\n   */\n  certSKI: string;\n  /**\n   * The certificate issuer's Subject Key Identifier\n   *\n   * @example \"BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4\"\n   */\n  certIssuerSKI: string;\n  /**\n   * The certificate's SHA-1 fingerprint\n   *\n   * @example \"6b9109f323999e52259cda7373ff0b4d26bd232e\"\n   */\n  certFingerprintSHA1: string;\n  /**\n   * The certificate's SHA-256 fingerprint\n   *\n   * @example \"acf77cf37b4156a2708e34c4eb755f9b5dbbe5ebb55adfec8f11493438d19e6ad3f157f81fa3b98278453d5652b0c1fd1d71e5695ae4d709803a4d3f39de9dea\"\n   */\n  certFingerprintSHA256: string;\n  /**\n   * The effective starting date of the certificate\n   *\n   * @example \"Dec 22 19:39:00 2018 GMT\"\n   */\n  certNotBefore: string;\n  /**\n   * The effective expiration date of the certificate\n   *\n   * @example \"Dec 22 19:39:00 2018 GMT\"\n   */\n  certNotAfter: string;\n}\n/** Placeholder values for TLS Client Authorization */\nexport interface IncomingRequestCfPropertiesTLSClientAuthPlaceholder {\n  certPresented: \"0\";\n  certVerified: \"NONE\";\n  certRevoked: \"0\";\n  certIssuerDN: \"\";\n  certSubjectDN: \"\";\n  certIssuerDNRFC2253: \"\";\n  certSubjectDNRFC2253: \"\";\n  certIssuerDNLegacy: \"\";\n  certSubjectDNLegacy: \"\";\n  certSerial: \"\";\n  certIssuerSerial: \"\";\n  certSKI: \"\";\n  certIssuerSKI: \"\";\n  certFingerprintSHA1: \"\";\n  certFingerprintSHA256: \"\";\n  certNotBefore: \"\";\n  certNotAfter: \"\";\n}\n/** Possible outcomes of TLS verification */\nexport declare type CertVerificationStatus =\n  /** Authentication succeeded */\n  | \"SUCCESS\"\n  /** No certificate was presented */\n  | \"NONE\"\n  /** Failed because the certificate was self-signed */\n  | \"FAILED:self signed certificate\"\n  /** Failed because the certificate failed a trust chain check */\n  | \"FAILED:unable to verify the first certificate\"\n  /** Failed because the certificate not yet valid */\n  | \"FAILED:certificate is not yet valid\"\n  /** Failed because the certificate is expired */\n  | \"FAILED:certificate has expired\"\n  /** Failed for another unspecified reason */\n  | \"FAILED\";\n/**\n * An upstream endpoint's response to a TCP `keepalive` message from Cloudflare.\n */\nexport declare type IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus =\n  | 0 /** Unknown */\n  | 1 /** no keepalives (not found) */\n  | 2 /** no connection re-use, opening keepalive connection failed */\n  | 3 /** no connection re-use, keepalive accepted and saved */\n  | 4 /** connection re-use, refused by the origin server (`TCP FIN`) */\n  | 5; /** connection re-use, accepted by the origin server */\n/** ISO 3166-1 Alpha-2 codes */\nexport declare type Iso3166Alpha2Code =\n  | \"AD\"\n  | \"AE\"\n  | \"AF\"\n  | \"AG\"\n  | \"AI\"\n  | \"AL\"\n  | \"AM\"\n  | \"AO\"\n  | \"AQ\"\n  | \"AR\"\n  | \"AS\"\n  | \"AT\"\n  | \"AU\"\n  | \"AW\"\n  | \"AX\"\n  | \"AZ\"\n  | \"BA\"\n  | \"BB\"\n  | \"BD\"\n  | \"BE\"\n  | \"BF\"\n  | \"BG\"\n  | \"BH\"\n  | \"BI\"\n  | \"BJ\"\n  | \"BL\"\n  | \"BM\"\n  | \"BN\"\n  | \"BO\"\n  | \"BQ\"\n  | \"BR\"\n  | \"BS\"\n  | \"BT\"\n  | \"BV\"\n  | \"BW\"\n  | \"BY\"\n  | \"BZ\"\n  | \"CA\"\n  | \"CC\"\n  | \"CD\"\n  | \"CF\"\n  | \"CG\"\n  | \"CH\"\n  | \"CI\"\n  | \"CK\"\n  | \"CL\"\n  | \"CM\"\n  | \"CN\"\n  | \"CO\"\n  | \"CR\"\n  | \"CU\"\n  | \"CV\"\n  | \"CW\"\n  | \"CX\"\n  | \"CY\"\n  | \"CZ\"\n  | \"DE\"\n  | \"DJ\"\n  | \"DK\"\n  | \"DM\"\n  | \"DO\"\n  | \"DZ\"\n  | \"EC\"\n  | \"EE\"\n  | \"EG\"\n  | \"EH\"\n  | \"ER\"\n  | \"ES\"\n  | \"ET\"\n  | \"FI\"\n  | \"FJ\"\n  | \"FK\"\n  | \"FM\"\n  | \"FO\"\n  | \"FR\"\n  | \"GA\"\n  | \"GB\"\n  | \"GD\"\n  | \"GE\"\n  | \"GF\"\n  | \"GG\"\n  | \"GH\"\n  | \"GI\"\n  | \"GL\"\n  | \"GM\"\n  | \"GN\"\n  | \"GP\"\n  | \"GQ\"\n  | \"GR\"\n  | \"GS\"\n  | \"GT\"\n  | \"GU\"\n  | \"GW\"\n  | \"GY\"\n  | \"HK\"\n  | \"HM\"\n  | \"HN\"\n  | \"HR\"\n  | \"HT\"\n  | \"HU\"\n  | \"ID\"\n  | \"IE\"\n  | \"IL\"\n  | \"IM\"\n  | \"IN\"\n  | \"IO\"\n  | \"IQ\"\n  | \"IR\"\n  | \"IS\"\n  | \"IT\"\n  | \"JE\"\n  | \"JM\"\n  | \"JO\"\n  | \"JP\"\n  | \"KE\"\n  | \"KG\"\n  | \"KH\"\n  | \"KI\"\n  | \"KM\"\n  | \"KN\"\n  | \"KP\"\n  | \"KR\"\n  | \"KW\"\n  | \"KY\"\n  | \"KZ\"\n  | \"LA\"\n  | \"LB\"\n  | \"LC\"\n  | \"LI\"\n  | \"LK\"\n  | \"LR\"\n  | \"LS\"\n  | \"LT\"\n  | \"LU\"\n  | \"LV\"\n  | \"LY\"\n  | \"MA\"\n  | \"MC\"\n  | \"MD\"\n  | \"ME\"\n  | \"MF\"\n  | \"MG\"\n  | \"MH\"\n  | \"MK\"\n  | \"ML\"\n  | \"MM\"\n  | \"MN\"\n  | \"MO\"\n  | \"MP\"\n  | \"MQ\"\n  | \"MR\"\n  | \"MS\"\n  | \"MT\"\n  | \"MU\"\n  | \"MV\"\n  | \"MW\"\n  | \"MX\"\n  | \"MY\"\n  | \"MZ\"\n  | \"NA\"\n  | \"NC\"\n  | \"NE\"\n  | \"NF\"\n  | \"NG\"\n  | \"NI\"\n  | \"NL\"\n  | \"NO\"\n  | \"NP\"\n  | \"NR\"\n  | \"NU\"\n  | \"NZ\"\n  | \"OM\"\n  | \"PA\"\n  | \"PE\"\n  | \"PF\"\n  | \"PG\"\n  | \"PH\"\n  | \"PK\"\n  | \"PL\"\n  | \"PM\"\n  | \"PN\"\n  | \"PR\"\n  | \"PS\"\n  | \"PT\"\n  | \"PW\"\n  | \"PY\"\n  | \"QA\"\n  | \"RE\"\n  | \"RO\"\n  | \"RS\"\n  | \"RU\"\n  | \"RW\"\n  | \"SA\"\n  | \"SB\"\n  | \"SC\"\n  | \"SD\"\n  | \"SE\"\n  | \"SG\"\n  | \"SH\"\n  | \"SI\"\n  | \"SJ\"\n  | \"SK\"\n  | \"SL\"\n  | \"SM\"\n  | \"SN\"\n  | \"SO\"\n  | \"SR\"\n  | \"SS\"\n  | \"ST\"\n  | \"SV\"\n  | \"SX\"\n  | \"SY\"\n  | \"SZ\"\n  | \"TC\"\n  | \"TD\"\n  | \"TF\"\n  | \"TG\"\n  | \"TH\"\n  | \"TJ\"\n  | \"TK\"\n  | \"TL\"\n  | \"TM\"\n  | \"TN\"\n  | \"TO\"\n  | \"TR\"\n  | \"TT\"\n  | \"TV\"\n  | \"TW\"\n  | \"TZ\"\n  | \"UA\"\n  | \"UG\"\n  | \"UM\"\n  | \"US\"\n  | \"UY\"\n  | \"UZ\"\n  | \"VA\"\n  | \"VC\"\n  | \"VE\"\n  | \"VG\"\n  | \"VI\"\n  | \"VN\"\n  | \"VU\"\n  | \"WF\"\n  | \"WS\"\n  | \"YE\"\n  | \"YT\"\n  | \"ZA\"\n  | \"ZM\"\n  | \"ZW\";\n/** The 2-letter continent codes Cloudflare uses */\nexport declare type ContinentCode =\n  | \"AF\"\n  | \"AN\"\n  | \"AS\"\n  | \"EU\"\n  | \"NA\"\n  | \"OC\"\n  | \"SA\";\nexport type CfProperties<HostMetadata = unknown> =\n  | IncomingRequestCfProperties<HostMetadata>\n  | RequestInitCfProperties;\nexport interface D1Meta {\n  duration: number;\n  size_after: number;\n  rows_read: number;\n  rows_written: number;\n  last_row_id: number;\n  changed_db: boolean;\n  changes: number;\n  /**\n   * The region of the database instance that executed the query.\n   */\n  served_by_region?: string;\n  /**\n   * The three letters airport code of the colo that executed the query.\n   */\n  served_by_colo?: string;\n  /**\n   * True if-and-only-if the database instance that executed the query was the primary.\n   */\n  served_by_primary?: boolean;\n  timings?: {\n    /**\n     * The duration of the SQL query execution by the database instance. It doesn't include any network time.\n     */\n    sql_duration_ms: number;\n  };\n  /**\n   * Number of total attempts to execute the query, due to automatic retries.\n   * Note: All other fields in the response like `timings` only apply to the last attempt.\n   */\n  total_attempts?: number;\n}\nexport interface D1Response {\n  success: true;\n  meta: D1Meta & Record<string, unknown>;\n  error?: never;\n}\nexport type D1Result<T = unknown> = D1Response & {\n  results: T[];\n};\nexport interface D1ExecResult {\n  count: number;\n  duration: number;\n}\nexport type D1SessionConstraint =\n  // Indicates that the first query should go to the primary, and the rest queries\n  // using the same D1DatabaseSession will go to any replica that is consistent with\n  // the bookmark maintained by the session (returned by the first query).\n  | \"first-primary\"\n  // Indicates that the first query can go anywhere (primary or replica), and the rest queries\n  // using the same D1DatabaseSession will go to any replica that is consistent with\n  // the bookmark maintained by the session (returned by the first query).\n  | \"first-unconstrained\";\nexport type D1SessionBookmark = string;\nexport declare abstract class D1Database {\n  prepare(query: string): D1PreparedStatement;\n  batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;\n  exec(query: string): Promise<D1ExecResult>;\n  /**\n   * Creates a new D1 Session anchored at the given constraint or the bookmark.\n   * All queries executed using the created session will have sequential consistency,\n   * meaning that all writes done through the session will be visible in subsequent reads.\n   *\n   * @param constraintOrBookmark Either the session constraint or the explicit bookmark to anchor the created session.\n   */\n  withSession(\n    constraintOrBookmark?: D1SessionBookmark | D1SessionConstraint,\n  ): D1DatabaseSession;\n  /**\n   * @deprecated dump() will be removed soon, only applies to deprecated alpha v1 databases.\n   */\n  dump(): Promise<ArrayBuffer>;\n}\nexport declare abstract class D1DatabaseSession {\n  prepare(query: string): D1PreparedStatement;\n  batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;\n  /**\n   * @returns The latest session bookmark across all executed queries on the session.\n   *          If no query has been executed yet, `null` is returned.\n   */\n  getBookmark(): D1SessionBookmark | null;\n}\nexport declare abstract class D1PreparedStatement {\n  bind(...values: unknown[]): D1PreparedStatement;\n  first<T = unknown>(colName: string): Promise<T | null>;\n  first<T = Record<string, unknown>>(): Promise<T | null>;\n  run<T = Record<string, unknown>>(): Promise<D1Result<T>>;\n  all<T = Record<string, unknown>>(): Promise<D1Result<T>>;\n  raw<T = unknown[]>(options: {\n    columnNames: true;\n  }): Promise<[string[], ...T[]]>;\n  raw<T = unknown[]>(options?: { columnNames?: false }): Promise<T[]>;\n}\n// `Disposable` was added to TypeScript's standard lib types in version 5.2.\n// To support older TypeScript versions, define an empty `Disposable` interface.\n// Users won't be able to use `using`/`Symbol.dispose` without upgrading to 5.2,\n// but this will ensure type checking on older versions still passes.\n// TypeScript's interface merging will ensure our empty interface is effectively\n// ignored when `Disposable` is included in the standard lib.\nexport interface Disposable {}\n/**\n * The returned data after sending an email\n */\nexport interface EmailSendResult {\n  /**\n   * The Email Message ID\n   */\n  messageId: string;\n}\n/**\n * An email message that can be sent from a Worker.\n */\nexport interface EmailMessage {\n  /**\n   * Envelope From attribute of the email message.\n   */\n  readonly from: string;\n  /**\n   * Envelope To attribute of the email message.\n   */\n  readonly to: string;\n}\n/**\n * An email message that is sent to a consumer Worker and can be rejected/forwarded.\n */\nexport interface ForwardableEmailMessage extends EmailMessage {\n  /**\n   * Stream of the email message content.\n   */\n  readonly raw: ReadableStream<Uint8Array>;\n  /**\n   * An [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers).\n   */\n  readonly headers: Headers;\n  /**\n   * Size of the email message content.\n   */\n  readonly rawSize: number;\n  /**\n   * Reject this email message by returning a permanent SMTP error back to the connecting client including the given reason.\n   * @param reason The reject reason.\n   * @returns void\n   */\n  setReject(reason: string): void;\n  /**\n   * Forward this email message to a verified destination address of the account.\n   * @param rcptTo Verified destination address.\n   * @param headers A [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers).\n   * @returns A promise that resolves when the email message is forwarded.\n   */\n  forward(rcptTo: string, headers?: Headers): Promise<EmailSendResult>;\n  /**\n   * Reply to the sender of this email message with a new EmailMessage object.\n   * @param message The reply message.\n   * @returns A promise that resolves when the email message is replied.\n   */\n  reply(message: EmailMessage): Promise<EmailSendResult>;\n}\n/** A file attachment for an email message */\nexport type EmailAttachment =\n  | {\n      disposition: \"inline\";\n      contentId: string;\n      filename: string;\n      type: string;\n      content: string | ArrayBuffer | ArrayBufferView;\n    }\n  | {\n      disposition: \"attachment\";\n      contentId?: undefined;\n      filename: string;\n      type: string;\n      content: string | ArrayBuffer | ArrayBufferView;\n    };\n/** An Email Address */\nexport interface EmailAddress {\n  name: string;\n  email: string;\n}\n/**\n * A binding that allows a Worker to send email messages.\n */\nexport interface SendEmail {\n  send(message: EmailMessage): Promise<EmailSendResult>;\n  send(builder: {\n    from: string | EmailAddress;\n    to: string | string[];\n    subject: string;\n    replyTo?: string | EmailAddress;\n    cc?: string | string[];\n    bcc?: string | string[];\n    headers?: Record<string, string>;\n    text?: string;\n    html?: string;\n    attachments?: EmailAttachment[];\n  }): Promise<EmailSendResult>;\n}\nexport declare abstract class EmailEvent extends ExtendableEvent {\n  readonly message: ForwardableEmailMessage;\n}\nexport declare type EmailExportedHandler<Env = unknown, Props = unknown> = (\n  message: ForwardableEmailMessage,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\n/**\n * Hello World binding to serve as an explanatory example. DO NOT USE\n */\nexport interface HelloWorldBinding {\n  /**\n   * Retrieve the current stored value\n   */\n  get(): Promise<{\n    value: string;\n    ms?: number;\n  }>;\n  /**\n   * Set a new stored value\n   */\n  set(value: string): Promise<void>;\n}\nexport interface Hyperdrive {\n  /**\n   * Connect directly to Hyperdrive as if it's your database, returning a TCP socket.\n   *\n   * Calling this method returns an identical socket to if you call\n   * `connect(\"host:port\")` using the `host` and `port` fields from this object.\n   * Pick whichever approach works better with your preferred DB client library.\n   *\n   * Note that this socket is not yet authenticated -- it's expected that your\n   * code (or preferably, the client library of your choice) will authenticate\n   * using the information in this class's readonly fields.\n   */\n  connect(): Socket;\n  /**\n   * A valid DB connection string that can be passed straight into the typical\n   * client library/driver/ORM. This will typically be the easiest way to use\n   * Hyperdrive.\n   */\n  readonly connectionString: string;\n  /*\n   * A randomly generated hostname that is only valid within the context of the\n   * currently running Worker which, when passed into `connect()` function from\n   * the \"cloudflare:sockets\" module, will connect to the Hyperdrive instance\n   * for your database.\n   */\n  readonly host: string;\n  /*\n   * The port that must be paired the the host field when connecting.\n   */\n  readonly port: number;\n  /*\n   * The username to use when authenticating to your database via Hyperdrive.\n   * Unlike the host and password, this will be the same every time\n   */\n  readonly user: string;\n  /*\n   * The randomly generated password to use when authenticating to your\n   * database via Hyperdrive. Like the host field, this password is only valid\n   * within the context of the currently running Worker instance from which\n   * it's read.\n   */\n  readonly password: string;\n  /*\n   * The name of the database to connect to.\n   */\n  readonly database: string;\n}\n// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nexport type ImageInfoResponse =\n  | {\n      format: \"image/svg+xml\";\n    }\n  | {\n      format: string;\n      fileSize: number;\n      width: number;\n      height: number;\n    };\nexport type ImageTransform = {\n  width?: number;\n  height?: number;\n  background?: string;\n  blur?: number;\n  border?:\n    | {\n        color?: string;\n        width?: number;\n      }\n    | {\n        top?: number;\n        bottom?: number;\n        left?: number;\n        right?: number;\n      };\n  brightness?: number;\n  contrast?: number;\n  fit?: \"scale-down\" | \"contain\" | \"pad\" | \"squeeze\" | \"cover\" | \"crop\";\n  flip?: \"h\" | \"v\" | \"hv\";\n  gamma?: number;\n  segment?: \"foreground\";\n  gravity?:\n    | \"face\"\n    | \"left\"\n    | \"right\"\n    | \"top\"\n    | \"bottom\"\n    | \"center\"\n    | \"auto\"\n    | \"entropy\"\n    | {\n        x?: number;\n        y?: number;\n        mode: \"remainder\" | \"box-center\";\n      };\n  rotate?: 0 | 90 | 180 | 270;\n  saturation?: number;\n  sharpen?: number;\n  trim?:\n    | \"border\"\n    | {\n        top?: number;\n        bottom?: number;\n        left?: number;\n        right?: number;\n        width?: number;\n        height?: number;\n        border?:\n          | boolean\n          | {\n              color?: string;\n              tolerance?: number;\n              keep?: number;\n            };\n      };\n};\nexport type ImageDrawOptions = {\n  opacity?: number;\n  repeat?: boolean | string;\n  top?: number;\n  left?: number;\n  bottom?: number;\n  right?: number;\n};\nexport type ImageInputOptions = {\n  encoding?: \"base64\";\n};\nexport type ImageOutputOptions = {\n  format:\n    | \"image/jpeg\"\n    | \"image/png\"\n    | \"image/gif\"\n    | \"image/webp\"\n    | \"image/avif\"\n    | \"rgb\"\n    | \"rgba\";\n  quality?: number;\n  background?: string;\n  anim?: boolean;\n};\nexport interface ImageMetadata {\n  id: string;\n  filename?: string;\n  uploaded?: string;\n  requireSignedURLs: boolean;\n  meta?: Record<string, unknown>;\n  variants: string[];\n  draft?: boolean;\n  creator?: string;\n}\nexport interface ImageUploadOptions {\n  id?: string;\n  filename?: string;\n  requireSignedURLs?: boolean;\n  metadata?: Record<string, unknown>;\n  creator?: string;\n  encoding?: \"base64\";\n}\nexport interface ImageUpdateOptions {\n  requireSignedURLs?: boolean;\n  metadata?: Record<string, unknown>;\n  creator?: string;\n}\nexport interface ImageListOptions {\n  limit?: number;\n  cursor?: string;\n  sortOrder?: \"asc\" | \"desc\";\n  creator?: string;\n}\nexport interface ImageList {\n  images: ImageMetadata[];\n  cursor?: string;\n  listComplete: boolean;\n}\nexport interface HostedImagesBinding {\n  /**\n   * Get detailed metadata for a hosted image\n   * @param imageId The ID of the image (UUID or custom ID)\n   * @returns Image metadata, or null if not found\n   */\n  details(imageId: string): Promise<ImageMetadata | null>;\n  /**\n   * Get the raw image data for a hosted image\n   * @param imageId The ID of the image (UUID or custom ID)\n   * @returns ReadableStream of image bytes, or null if not found\n   */\n  image(imageId: string): Promise<ReadableStream<Uint8Array> | null>;\n  /**\n   * Upload a new hosted image\n   * @param image The image file to upload\n   * @param options Upload configuration\n   * @returns Metadata for the uploaded image\n   * @throws {@link ImagesError} if upload fails\n   */\n  upload(\n    image: ReadableStream<Uint8Array> | ArrayBuffer,\n    options?: ImageUploadOptions,\n  ): Promise<ImageMetadata>;\n  /**\n   * Update hosted image metadata\n   * @param imageId The ID of the image\n   * @param options Properties to update\n   * @returns Updated image metadata\n   * @throws {@link ImagesError} if update fails\n   */\n  update(imageId: string, options: ImageUpdateOptions): Promise<ImageMetadata>;\n  /**\n   * Delete a hosted image\n   * @param imageId The ID of the image\n   * @returns True if deleted, false if not found\n   */\n  delete(imageId: string): Promise<boolean>;\n  /**\n   * List hosted images with pagination\n   * @param options List configuration\n   * @returns List of images with pagination info\n   * @throws {@link ImagesError} if list fails\n   */\n  list(options?: ImageListOptions): Promise<ImageList>;\n}\nexport interface ImagesBinding {\n  /**\n   * Get image metadata (type, width and height)\n   * @throws {@link ImagesError} with code 9412 if input is not an image\n   * @param stream The image bytes\n   */\n  info(\n    stream: ReadableStream<Uint8Array>,\n    options?: ImageInputOptions,\n  ): Promise<ImageInfoResponse>;\n  /**\n   * Begin applying a series of transformations to an image\n   * @param stream The image bytes\n   * @returns A transform handle\n   */\n  input(\n    stream: ReadableStream<Uint8Array>,\n    options?: ImageInputOptions,\n  ): ImageTransformer;\n  /**\n   * Access hosted images CRUD operations\n   */\n  readonly hosted: HostedImagesBinding;\n}\nexport interface ImageTransformer {\n  /**\n   * Apply transform next, returning a transform handle.\n   * You can then apply more transformations, draw, or retrieve the output.\n   * @param transform\n   */\n  transform(transform: ImageTransform): ImageTransformer;\n  /**\n   * Draw an image on this transformer, returning a transform handle.\n   * You can then apply more transformations, draw, or retrieve the output.\n   * @param image The image (or transformer that will give the image) to draw\n   * @param options The options configuring how to draw the image\n   */\n  draw(\n    image: ReadableStream<Uint8Array> | ImageTransformer,\n    options?: ImageDrawOptions,\n  ): ImageTransformer;\n  /**\n   * Retrieve the image that results from applying the transforms to the\n   * provided input\n   * @param options Options that apply to the output e.g. output format\n   */\n  output(options: ImageOutputOptions): Promise<ImageTransformationResult>;\n}\nexport type ImageTransformationOutputOptions = {\n  encoding?: \"base64\";\n};\nexport interface ImageTransformationResult {\n  /**\n   * The image as a response, ready to store in cache or return to users\n   */\n  response(): Response;\n  /**\n   * The content type of the returned image\n   */\n  contentType(): string;\n  /**\n   * The bytes of the response\n   */\n  image(options?: ImageTransformationOutputOptions): ReadableStream<Uint8Array>;\n}\nexport interface ImagesError extends Error {\n  readonly code: number;\n  readonly message: string;\n  readonly stack?: string;\n}\n/**\n * Media binding for transforming media streams.\n * Provides the entry point for media transformation operations.\n */\nexport interface MediaBinding {\n  /**\n   * Creates a media transformer from an input stream.\n   * @param media - The input media bytes\n   * @returns A MediaTransformer instance for applying transformations\n   */\n  input(media: ReadableStream<Uint8Array>): MediaTransformer;\n}\n/**\n * Media transformer for applying transformation operations to media content.\n * Handles sizing, fitting, and other input transformation parameters.\n */\nexport interface MediaTransformer {\n  /**\n   * Applies transformation options to the media content.\n   * @param transform - Configuration for how the media should be transformed\n   * @returns A generator for producing the transformed media output\n   */\n  transform(\n    transform?: MediaTransformationInputOptions,\n  ): MediaTransformationGenerator;\n  /**\n   * Generates the final media output with specified options.\n   * @param output - Configuration for the output format and parameters\n   * @returns The final transformation result containing the transformed media\n   */\n  output(output?: MediaTransformationOutputOptions): MediaTransformationResult;\n}\n/**\n * Generator for producing media transformation results.\n * Configures the output format and parameters for the transformed media.\n */\nexport interface MediaTransformationGenerator {\n  /**\n   * Generates the final media output with specified options.\n   * @param output - Configuration for the output format and parameters\n   * @returns The final transformation result containing the transformed media\n   */\n  output(output?: MediaTransformationOutputOptions): MediaTransformationResult;\n}\n/**\n * Result of a media transformation operation.\n * Provides multiple ways to access the transformed media content.\n */\nexport interface MediaTransformationResult {\n  /**\n   * Returns the transformed media as a readable stream of bytes.\n   * @returns A promise containing a readable stream with the transformed media\n   */\n  media(): Promise<ReadableStream<Uint8Array>>;\n  /**\n   * Returns the transformed media as an HTTP response object.\n   * @returns The transformed media as a Promise<Response>, ready to store in cache or return to users\n   */\n  response(): Promise<Response>;\n  /**\n   * Returns the MIME type of the transformed media.\n   * @returns A promise containing the content type string (e.g., 'image/jpeg', 'video/mp4')\n   */\n  contentType(): Promise<string>;\n}\n/**\n * Configuration options for transforming media input.\n * Controls how the media should be resized and fitted.\n */\nexport type MediaTransformationInputOptions = {\n  /** How the media should be resized to fit the specified dimensions */\n  fit?: \"contain\" | \"cover\" | \"scale-down\";\n  /** Target width in pixels */\n  width?: number;\n  /** Target height in pixels */\n  height?: number;\n};\n/**\n * Configuration options for Media Transformations output.\n * Controls the format, timing, and type of the generated output.\n */\nexport type MediaTransformationOutputOptions = {\n  /**\n   * Output mode determining the type of media to generate\n   */\n  mode?: \"video\" | \"spritesheet\" | \"frame\" | \"audio\";\n  /** Whether to include audio in the output */\n  audio?: boolean;\n  /**\n   * Starting timestamp for frame extraction or start time for clips. (e.g. '2s').\n   */\n  time?: string;\n  /**\n   * Duration for video clips, audio extraction, and spritesheet generation (e.g. '5s').\n   */\n  duration?: string;\n  /**\n   * Number of frames in the spritesheet.\n   */\n  imageCount?: number;\n  /**\n   * Output format for the generated media.\n   */\n  format?: \"jpg\" | \"png\" | \"m4a\";\n};\n/**\n * Error object for media transformation operations.\n * Extends the standard Error interface with additional media-specific information.\n */\nexport interface MediaError extends Error {\n  readonly code: number;\n  readonly message: string;\n  readonly stack?: string;\n}\nexport type Params<P extends string = any> = Record<P, string | string[]>;\nexport type EventContext<Env, P extends string, Data> = {\n  request: Request<unknown, IncomingRequestCfProperties<unknown>>;\n  functionPath: string;\n  waitUntil: (promise: Promise<any>) => void;\n  passThroughOnException: () => void;\n  next: (input?: Request | string, init?: RequestInit) => Promise<Response>;\n  env: Env & {\n    ASSETS: {\n      fetch: typeof fetch;\n    };\n  };\n  params: Params<P>;\n  data: Data;\n};\nexport type PagesFunction<\n  Env = unknown,\n  Params extends string = any,\n  Data extends Record<string, unknown> = Record<string, unknown>,\n> = (context: EventContext<Env, Params, Data>) => Response | Promise<Response>;\nexport type EventPluginContext<Env, P extends string, Data, PluginArgs> = {\n  request: Request<unknown, IncomingRequestCfProperties<unknown>>;\n  functionPath: string;\n  waitUntil: (promise: Promise<any>) => void;\n  passThroughOnException: () => void;\n  next: (input?: Request | string, init?: RequestInit) => Promise<Response>;\n  env: Env & {\n    ASSETS: {\n      fetch: typeof fetch;\n    };\n  };\n  params: Params<P>;\n  data: Data;\n  pluginArgs: PluginArgs;\n};\nexport type PagesPluginFunction<\n  Env = unknown,\n  Params extends string = any,\n  Data extends Record<string, unknown> = Record<string, unknown>,\n  PluginArgs = unknown,\n> = (\n  context: EventPluginContext<Env, Params, Data, PluginArgs>,\n) => Response | Promise<Response>;\n// PubSubMessage represents an incoming PubSub message.\n// The message includes metadata about the broker, the client, and the payload\n// itself.\n// https://developers.cloudflare.com/pub-sub/\nexport interface PubSubMessage {\n  // Message ID\n  readonly mid: number;\n  // MQTT broker FQDN in the form mqtts://BROKER.NAMESPACE.cloudflarepubsub.com:PORT\n  readonly broker: string;\n  // The MQTT topic the message was sent on.\n  readonly topic: string;\n  // The client ID of the client that published this message.\n  readonly clientId: string;\n  // The unique identifier (JWT ID) used by the client to authenticate, if token\n  // auth was used.\n  readonly jti?: string;\n  // A Unix timestamp (seconds from Jan 1, 1970), set when the Pub/Sub Broker\n  // received the message from the client.\n  readonly receivedAt: number;\n  // An (optional) string with the MIME type of the payload, if set by the\n  // client.\n  readonly contentType: string;\n  // Set to 1 when the payload is a UTF-8 string\n  // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901063\n  readonly payloadFormatIndicator: number;\n  // Pub/Sub (MQTT) payloads can be UTF-8 strings, or byte arrays.\n  // You can use payloadFormatIndicator to inspect this before decoding.\n  payload: string | Uint8Array;\n}\n// JsonWebKey extended by kid parameter\nexport interface JsonWebKeyWithKid extends JsonWebKey {\n  // Key Identifier of the JWK\n  readonly kid: string;\n}\nexport interface RateLimitOptions {\n  key: string;\n}\nexport interface RateLimitOutcome {\n  success: boolean;\n}\nexport interface RateLimit {\n  /**\n   * Rate limit a request based on the provided options.\n   * @see https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/\n   * @returns A promise that resolves with the outcome of the rate limit.\n   */\n  limit(options: RateLimitOptions): Promise<RateLimitOutcome>;\n}\n// Namespace for RPC utility types. Unfortunately, we can't use a `module` here as these types need\n// to referenced by `Fetcher`. This is included in the \"importable\" version of the types which\n// strips all `module` blocks.\nexport declare namespace Rpc {\n  // Branded types for identifying `WorkerEntrypoint`/`DurableObject`/`Target`s.\n  // TypeScript uses *structural* typing meaning anything with the same shape as type `T` is a `T`.\n  // For the classes exported by `cloudflare:workers` we want *nominal* typing (i.e. we only want to\n  // accept `WorkerEntrypoint` from `cloudflare:workers`, not any other class with the same shape)\n  export const __RPC_STUB_BRAND: \"__RPC_STUB_BRAND\";\n  export const __RPC_TARGET_BRAND: \"__RPC_TARGET_BRAND\";\n  export const __WORKER_ENTRYPOINT_BRAND: \"__WORKER_ENTRYPOINT_BRAND\";\n  export const __DURABLE_OBJECT_BRAND: \"__DURABLE_OBJECT_BRAND\";\n  export const __WORKFLOW_ENTRYPOINT_BRAND: \"__WORKFLOW_ENTRYPOINT_BRAND\";\n  export interface RpcTargetBranded {\n    [__RPC_TARGET_BRAND]: never;\n  }\n  export interface WorkerEntrypointBranded {\n    [__WORKER_ENTRYPOINT_BRAND]: never;\n  }\n  export interface DurableObjectBranded {\n    [__DURABLE_OBJECT_BRAND]: never;\n  }\n  export interface WorkflowEntrypointBranded {\n    [__WORKFLOW_ENTRYPOINT_BRAND]: never;\n  }\n  export type EntrypointBranded =\n    | WorkerEntrypointBranded\n    | DurableObjectBranded\n    | WorkflowEntrypointBranded;\n  // Types that can be used through `Stub`s\n  export type Stubable = RpcTargetBranded | ((...args: any[]) => any);\n  // Types that can be passed over RPC\n  // The reason for using a generic type here is to build a serializable subset of structured\n  //   cloneable composite types. This allows types defined with the \"interface\" keyword to pass the\n  //   serializable check as well. Otherwise, only types defined with the \"type\" keyword would pass.\n  type Serializable<T> =\n    // Structured cloneables\n    | BaseType\n    // Structured cloneable composites\n    | Map<\n        T extends Map<infer U, unknown> ? Serializable<U> : never,\n        T extends Map<unknown, infer U> ? Serializable<U> : never\n      >\n    | Set<T extends Set<infer U> ? Serializable<U> : never>\n    | ReadonlyArray<T extends ReadonlyArray<infer U> ? Serializable<U> : never>\n    | {\n        [K in keyof T]: K extends number | string ? Serializable<T[K]> : never;\n      }\n    // Special types\n    | Stub<Stubable>\n    // Serialized as stubs, see `Stubify`\n    | Stubable;\n  // Base type for all RPC stubs, including common memory management methods.\n  // `T` is used as a marker type for unwrapping `Stub`s later.\n  interface StubBase<T extends Stubable> extends Disposable {\n    [__RPC_STUB_BRAND]: T;\n    dup(): this;\n  }\n  export type Stub<T extends Stubable> = Provider<T> & StubBase<T>;\n  // This represents all the types that can be sent as-is over an RPC boundary\n  type BaseType =\n    | void\n    | undefined\n    | null\n    | boolean\n    | number\n    | bigint\n    | string\n    | TypedArray\n    | ArrayBuffer\n    | DataView\n    | Date\n    | Error\n    | RegExp\n    | ReadableStream<Uint8Array>\n    | WritableStream<Uint8Array>\n    | Request\n    | Response\n    | Headers;\n  // Recursively rewrite all `Stubable` types with `Stub`s\n  type Stubify<T> = T extends Stubable\n    ? Stub<T>\n    : T extends Map<infer K, infer V>\n      ? Map<Stubify<K>, Stubify<V>>\n      : T extends Set<infer V>\n        ? Set<Stubify<V>>\n        : T extends Array<infer V>\n          ? Array<Stubify<V>>\n          : T extends ReadonlyArray<infer V>\n            ? ReadonlyArray<Stubify<V>>\n            : T extends BaseType\n              ? T\n              : T extends {\n                    [key: string | number]: any;\n                  }\n                ? {\n                    [K in keyof T]: Stubify<T[K]>;\n                  }\n                : T;\n  // Recursively rewrite all `Stub<T>`s with the corresponding `T`s.\n  // Note we use `StubBase` instead of `Stub` here to avoid circular dependencies:\n  // `Stub` depends on `Provider`, which depends on `Unstubify`, which would depend on `Stub`.\n  type Unstubify<T> =\n    T extends StubBase<infer V>\n      ? V\n      : T extends Map<infer K, infer V>\n        ? Map<Unstubify<K>, Unstubify<V>>\n        : T extends Set<infer V>\n          ? Set<Unstubify<V>>\n          : T extends Array<infer V>\n            ? Array<Unstubify<V>>\n            : T extends ReadonlyArray<infer V>\n              ? ReadonlyArray<Unstubify<V>>\n              : T extends BaseType\n                ? T\n                : T extends {\n                      [key: string | number]: unknown;\n                    }\n                  ? {\n                      [K in keyof T]: Unstubify<T[K]>;\n                    }\n                  : T;\n  type UnstubifyAll<A extends any[]> = {\n    [I in keyof A]: Unstubify<A[I]>;\n  };\n  // Utility type for adding `Provider`/`Disposable`s to `object` types only.\n  // Note `unknown & T` is equivalent to `T`.\n  type MaybeProvider<T> = T extends object ? Provider<T> : unknown;\n  type MaybeDisposable<T> = T extends object ? Disposable : unknown;\n  // Type for method return or property on an RPC interface.\n  // - Stubable types are replaced by stubs.\n  // - Serializable types are passed by value, with stubable types replaced by stubs\n  //   and a top-level `Disposer`.\n  // Everything else can't be passed over PRC.\n  // Technically, we use custom thenables here, but they quack like `Promise`s.\n  // Intersecting with `(Maybe)Provider` allows pipelining.\n  type Result<R> = R extends Stubable\n    ? Promise<Stub<R>> & Provider<R>\n    : R extends Serializable<R>\n      ? Promise<Stubify<R> & MaybeDisposable<R>> & MaybeProvider<R>\n      : never;\n  // Type for method or property on an RPC interface.\n  // For methods, unwrap `Stub`s in parameters, and rewrite returns to be `Result`s.\n  // Unwrapping `Stub`s allows calling with `Stubable` arguments.\n  // For properties, rewrite types to be `Result`s.\n  // In each case, unwrap `Promise`s.\n  type MethodOrProperty<V> = V extends (...args: infer P) => infer R\n    ? (...args: UnstubifyAll<P>) => Result<Awaited<R>>\n    : Result<Awaited<V>>;\n  // Type for the callable part of an `Provider` if `T` is callable.\n  // This is intersected with methods/properties.\n  type MaybeCallableProvider<T> = T extends (...args: any[]) => any\n    ? MethodOrProperty<T>\n    : unknown;\n  // Base type for all other types providing RPC-like interfaces.\n  // Rewrites all methods/properties to be `MethodOrProperty`s, while preserving callable types.\n  // `Reserved` names (e.g. stub method names like `dup()`) and symbols can't be accessed over RPC.\n  export type Provider<\n    T extends object,\n    Reserved extends string = never,\n  > = MaybeCallableProvider<T> &\n    Pick<\n      {\n        [K in keyof T]: MethodOrProperty<T[K]>;\n      },\n      Exclude<keyof T, Reserved | symbol | keyof StubBase<never>>\n    >;\n}\nexport declare namespace Cloudflare {\n  // Type of `env`.\n  //\n  // The specific project can extend `Env` by redeclaring it in project-specific files. Typescript\n  // will merge all declarations.\n  //\n  // You can use `wrangler types` to generate the `Env` type automatically.\n  interface Env {}\n  // Project-specific parameters used to inform types.\n  //\n  // This interface is, again, intended to be declared in project-specific files, and then that\n  // declaration will be merged with this one.\n  //\n  // A project should have a declaration like this:\n  //\n  //     interface GlobalProps {\n  //       // Declares the main module's exports. Used to populate Cloudflare.Exports aka the type\n  //       // of `ctx.exports`.\n  //       mainModule: typeof import(\"my-main-module\");\n  //\n  //       // Declares which of the main module's exports are configured with durable storage, and\n  //       // thus should behave as Durable Object namsepace bindings.\n  //       durableNamespaces: \"MyDurableObject\" | \"AnotherDurableObject\";\n  //     }\n  //\n  // You can use `wrangler types` to generate `GlobalProps` automatically.\n  interface GlobalProps {}\n  // Evaluates to the type of a property in GlobalProps, defaulting to `Default` if it is not\n  // present.\n  type GlobalProp<K extends string, Default> = K extends keyof GlobalProps\n    ? GlobalProps[K]\n    : Default;\n  // The type of the program's main module exports, if known. Requires `GlobalProps` to declare the\n  // `mainModule` property.\n  type MainModule = GlobalProp<\"mainModule\", {}>;\n  // The type of ctx.exports, which contains loopback bindings for all top-level exports.\n  type Exports = {\n    [K in keyof MainModule]: LoopbackForExport<MainModule[K]> &\n      // If the export is listed in `durableNamespaces`, then it is also a\n      // DurableObjectNamespace.\n      (K extends GlobalProp<\"durableNamespaces\", never>\n        ? MainModule[K] extends new (...args: any[]) => infer DoInstance\n          ? DoInstance extends Rpc.DurableObjectBranded\n            ? DurableObjectNamespace<DoInstance>\n            : DurableObjectNamespace<undefined>\n          : DurableObjectNamespace<undefined>\n        : {});\n  };\n}\nexport declare namespace CloudflareWorkersModule {\n  export type RpcStub<T extends Rpc.Stubable> = Rpc.Stub<T>;\n  export const RpcStub: {\n    new <T extends Rpc.Stubable>(value: T): Rpc.Stub<T>;\n  };\n  export abstract class RpcTarget implements Rpc.RpcTargetBranded {\n    [Rpc.__RPC_TARGET_BRAND]: never;\n  }\n  // `protected` fields don't appear in `keyof`s, so can't be accessed over RPC\n  export abstract class WorkerEntrypoint<Env = Cloudflare.Env, Props = {}>\n    implements Rpc.WorkerEntrypointBranded\n  {\n    [Rpc.__WORKER_ENTRYPOINT_BRAND]: never;\n    protected ctx: ExecutionContext<Props>;\n    protected env: Env;\n    constructor(ctx: ExecutionContext, env: Env);\n    email?(message: ForwardableEmailMessage): void | Promise<void>;\n    fetch?(request: Request): Response | Promise<Response>;\n    queue?(batch: MessageBatch<unknown>): void | Promise<void>;\n    scheduled?(controller: ScheduledController): void | Promise<void>;\n    tail?(events: TraceItem[]): void | Promise<void>;\n    tailStream?(\n      event: TailStream.TailEvent<TailStream.Onset>,\n    ):\n      | TailStream.TailEventHandlerType\n      | Promise<TailStream.TailEventHandlerType>;\n    test?(controller: TestController): void | Promise<void>;\n    trace?(traces: TraceItem[]): void | Promise<void>;\n  }\n  export abstract class DurableObject<Env = Cloudflare.Env, Props = {}>\n    implements Rpc.DurableObjectBranded\n  {\n    [Rpc.__DURABLE_OBJECT_BRAND]: never;\n    protected ctx: DurableObjectState<Props>;\n    protected env: Env;\n    constructor(ctx: DurableObjectState, env: Env);\n    alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise<void>;\n    fetch?(request: Request): Response | Promise<Response>;\n    webSocketMessage?(\n      ws: WebSocket,\n      message: string | ArrayBuffer,\n    ): void | Promise<void>;\n    webSocketClose?(\n      ws: WebSocket,\n      code: number,\n      reason: string,\n      wasClean: boolean,\n    ): void | Promise<void>;\n    webSocketError?(ws: WebSocket, error: unknown): void | Promise<void>;\n  }\n  export type WorkflowDurationLabel =\n    | \"second\"\n    | \"minute\"\n    | \"hour\"\n    | \"day\"\n    | \"week\"\n    | \"month\"\n    | \"year\";\n  export type WorkflowSleepDuration =\n    | `${number} ${WorkflowDurationLabel}${\"s\" | \"\"}`\n    | number;\n  export type WorkflowDelayDuration = WorkflowSleepDuration;\n  export type WorkflowTimeoutDuration = WorkflowSleepDuration;\n  export type WorkflowRetentionDuration = WorkflowSleepDuration;\n  export type WorkflowBackoff = \"constant\" | \"linear\" | \"exponential\";\n  export type WorkflowStepConfig = {\n    retries?: {\n      limit: number;\n      delay: WorkflowDelayDuration | number;\n      backoff?: WorkflowBackoff;\n    };\n    timeout?: WorkflowTimeoutDuration | number;\n  };\n  export type WorkflowEvent<T> = {\n    payload: Readonly<T>;\n    timestamp: Date;\n    instanceId: string;\n  };\n  export type WorkflowStepEvent<T> = {\n    payload: Readonly<T>;\n    timestamp: Date;\n    type: string;\n  };\n  export type WorkflowStepContext = {\n    attempt: number;\n  };\n  export abstract class WorkflowStep {\n    do<T extends Rpc.Serializable<T>>(\n      name: string,\n      callback: (ctx: WorkflowStepContext) => Promise<T>,\n    ): Promise<T>;\n    do<T extends Rpc.Serializable<T>>(\n      name: string,\n      config: WorkflowStepConfig,\n      callback: (ctx: WorkflowStepContext) => Promise<T>,\n    ): Promise<T>;\n    sleep: (name: string, duration: WorkflowSleepDuration) => Promise<void>;\n    sleepUntil: (name: string, timestamp: Date | number) => Promise<void>;\n    waitForEvent<T extends Rpc.Serializable<T>>(\n      name: string,\n      options: {\n        type: string;\n        timeout?: WorkflowTimeoutDuration | number;\n      },\n    ): Promise<WorkflowStepEvent<T>>;\n  }\n  export type WorkflowInstanceStatus =\n    | \"queued\"\n    | \"running\"\n    | \"paused\"\n    | \"errored\"\n    | \"terminated\"\n    | \"complete\"\n    | \"waiting\"\n    | \"waitingForPause\"\n    | \"unknown\";\n  export abstract class WorkflowEntrypoint<\n    Env = unknown,\n    T extends Rpc.Serializable<T> | unknown = unknown,\n  >\n    implements Rpc.WorkflowEntrypointBranded\n  {\n    [Rpc.__WORKFLOW_ENTRYPOINT_BRAND]: never;\n    protected ctx: ExecutionContext;\n    protected env: Env;\n    constructor(ctx: ExecutionContext, env: Env);\n    run(\n      event: Readonly<WorkflowEvent<T>>,\n      step: WorkflowStep,\n    ): Promise<unknown>;\n  }\n  export function waitUntil(promise: Promise<unknown>): void;\n  export function withEnv(newEnv: unknown, fn: () => unknown): unknown;\n  export function withExports(newExports: unknown, fn: () => unknown): unknown;\n  export function withEnvAndExports(\n    newEnv: unknown,\n    newExports: unknown,\n    fn: () => unknown,\n  ): unknown;\n  export const env: Cloudflare.Env;\n  export const exports: Cloudflare.Exports;\n}\nexport interface SecretsStoreSecret {\n  /**\n   * Get a secret from the Secrets Store, returning a string of the secret value\n   * if it exists, or throws an error if it does not exist\n   */\n  get(): Promise<string>;\n}\n/**\n * Binding entrypoint for Cloudflare Stream.\n *\n * Usage:\n * - Binding-level operations:\n *   `await env.STREAM.videos.upload`\n *   `await env.STREAM.videos.createDirectUpload`\n *   `await env.STREAM.videos.*`\n *   `await env.STREAM.watermarks.*`\n * - Per-video operations:\n *   `await env.STREAM.video(id).downloads.*`\n *   `await env.STREAM.video(id).captions.*`\n *\n * Example usage:\n * ```ts\n * await env.STREAM.video(id).downloads.generate();\n *\n * const video = env.STREAM.video(id)\n * const captions = video.captions.list();\n * const videoDetails = video.details()\n * ```\n */\nexport interface StreamBinding {\n  /**\n   * Returns a handle scoped to a single video for per-video operations.\n   * @param id The unique identifier for the video.\n   * @returns A handle for per-video operations.\n   */\n  video(id: string): StreamVideoHandle;\n  /**\n   * Uploads a new video from a provided URL.\n   * @param url The URL to upload from.\n   * @param params Optional upload parameters.\n   * @returns The uploaded video details.\n   * @throws {BadRequestError} if the upload parameter is invalid or the URL is invalid\n   * @throws {QuotaReachedError} if the account storage capacity is exceeded\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {RateLimitedError} if the server received too many requests\n   * @throws {AlreadyUploadedError} if a video was already uploaded to this URL\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  upload(url: string, params?: StreamUrlUploadParams): Promise<StreamVideo>;\n  /**\n   * Creates a direct upload that allows video uploads without an API key.\n   * @param params Parameters for the direct upload\n   * @returns The direct upload details.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {RateLimitedError} if the server received too many requests\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  createDirectUpload(\n    params: StreamDirectUploadCreateParams,\n  ): Promise<StreamDirectUpload>;\n  videos: StreamVideos;\n  watermarks: StreamWatermarks;\n}\n/**\n * Handle for operations scoped to a single Stream video.\n */\nexport interface StreamVideoHandle {\n  /**\n   * The unique identifier for the video.\n   */\n  id: string;\n  /**\n   * Get a full videos details\n   * @returns The full video details.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  details(): Promise<StreamVideo>;\n  /**\n   * Update details for a single video.\n   * @param params The fields to update for the video.\n   * @returns The updated video details.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  update(params: StreamUpdateVideoParams): Promise<StreamVideo>;\n  /**\n   * Deletes a video and its copies from Cloudflare Stream.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(): Promise<void>;\n  /**\n   * Creates a signed URL token for a video.\n   * @returns The signed token that was created.\n   * @throws {InternalError} if the signing key cannot be retrieved or the token cannot be signed\n   */\n  generateToken(): Promise<string>;\n  downloads: StreamScopedDownloads;\n  captions: StreamScopedCaptions;\n}\nexport interface StreamVideo {\n  /**\n   * The unique identifier for the video.\n   */\n  id: string;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator: string | null;\n  /**\n   * The thumbnail URL for the video.\n   */\n  thumbnail: string;\n  /**\n   * The thumbnail timestamp percentage.\n   */\n  thumbnailTimestampPct: number;\n  /**\n   * Indicates whether the video is ready to stream.\n   */\n  readyToStream: boolean;\n  /**\n   * The date and time the video became ready to stream.\n   */\n  readyToStreamAt: string | null;\n  /**\n   * Processing status information.\n   */\n  status: StreamVideoStatus;\n  /**\n   * A user modifiable key-value store.\n   */\n  meta: Record<string, string>;\n  /**\n   * The date and time the video was created.\n   */\n  created: string;\n  /**\n   * The date and time the video was last modified.\n   */\n  modified: string;\n  /**\n   * The date and time at which the video will be deleted.\n   */\n  scheduledDeletion: string | null;\n  /**\n   * The size of the video in bytes.\n   */\n  size: number;\n  /**\n   * The preview URL for the video.\n   */\n  preview?: string;\n  /**\n   * Origins allowed to display the video.\n   */\n  allowedOrigins: Array<string>;\n  /**\n   * Indicates whether signed URLs are required.\n   */\n  requireSignedURLs: boolean | null;\n  /**\n   * The date and time the video was uploaded.\n   */\n  uploaded: string | null;\n  /**\n   * The date and time when the upload URL expires.\n   */\n  uploadExpiry: string | null;\n  /**\n   * The maximum size in bytes for direct uploads.\n   */\n  maxSizeBytes: number | null;\n  /**\n   * The maximum duration in seconds for direct uploads.\n   */\n  maxDurationSeconds: number | null;\n  /**\n   * The video duration in seconds. -1 indicates unknown.\n   */\n  duration: number;\n  /**\n   * Input metadata for the original upload.\n   */\n  input: StreamVideoInput;\n  /**\n   * Playback URLs for the video.\n   */\n  hlsPlaybackUrl: string;\n  dashPlaybackUrl: string;\n  /**\n   * The watermark applied to the video, if any.\n   */\n  watermark: StreamWatermark | null;\n  /**\n   * The live input id associated with the video, if any.\n   */\n  liveInputId?: string | null;\n  /**\n   * The source video id if this is a clip.\n   */\n  clippedFromId: string | null;\n  /**\n   * Public details associated with the video.\n   */\n  publicDetails: StreamPublicDetails | null;\n}\nexport type StreamVideoStatus = {\n  /**\n   * The current processing state.\n   */\n  state: string;\n  /**\n   * The current processing step.\n   */\n  step?: string;\n  /**\n   * The percent complete as a string.\n   */\n  pctComplete?: string;\n  /**\n   * An error reason code, if applicable.\n   */\n  errorReasonCode: string;\n  /**\n   * An error reason text, if applicable.\n   */\n  errorReasonText: string;\n};\nexport type StreamVideoInput = {\n  /**\n   * The input width in pixels.\n   */\n  width: number;\n  /**\n   * The input height in pixels.\n   */\n  height: number;\n};\nexport type StreamPublicDetails = {\n  /**\n   * The public title for the video.\n   */\n  title: string | null;\n  /**\n   * The public share link.\n   */\n  share_link: string | null;\n  /**\n   * The public channel link.\n   */\n  channel_link: string | null;\n  /**\n   * The public logo URL.\n   */\n  logo: string | null;\n};\nexport type StreamDirectUpload = {\n  /**\n   * The URL an unauthenticated upload can use for a single multipart request.\n   */\n  uploadURL: string;\n  /**\n   * A Cloudflare-generated unique identifier for a media item.\n   */\n  id: string;\n  /**\n   * The watermark profile applied to the upload.\n   */\n  watermark: StreamWatermark | null;\n  /**\n   * The scheduled deletion time, if any.\n   */\n  scheduledDeletion: string | null;\n};\nexport type StreamDirectUploadCreateParams = {\n  /**\n   * The maximum duration in seconds for a video upload.\n   */\n  maxDurationSeconds: number;\n  /**\n   * The date and time after upload when videos will not be accepted.\n   */\n  expiry?: string;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator?: string;\n  /**\n   * A user modifiable key-value store used to reference other systems of record for\n   * managing videos.\n   */\n  meta?: Record<string, string>;\n  /**\n   * Lists the origins allowed to display the video.\n   */\n  allowedOrigins?: Array<string>;\n  /**\n   * Indicates whether the video can be accessed using the id. When set to `true`,\n   * a signed token must be generated with a signing key to view the video.\n   */\n  requireSignedURLs?: boolean;\n  /**\n   * The thumbnail timestamp percentage.\n   */\n  thumbnailTimestampPct?: number;\n  /**\n   * The date and time at which the video will be deleted. Include `null` to remove\n   * a scheduled deletion.\n   */\n  scheduledDeletion?: string | null;\n  /**\n   * The watermark profile to apply.\n   */\n  watermark?: StreamDirectUploadWatermark;\n};\nexport type StreamDirectUploadWatermark = {\n  /**\n   * The unique identifier for the watermark profile.\n   */\n  id: string;\n};\nexport type StreamUrlUploadParams = {\n  /**\n   * Lists the origins allowed to display the video. Enter allowed origin\n   * domains in an array and use `*` for wildcard subdomains. Empty arrays allow the\n   * video to be viewed on any origin.\n   */\n  allowedOrigins?: Array<string>;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator?: string;\n  /**\n   * A user modifiable key-value store used to reference other systems of\n   * record for managing videos.\n   */\n  meta?: Record<string, string>;\n  /**\n   * Indicates whether the video can be a accessed using the id. When\n   * set to `true`, a signed token must be generated with a signing key to view the\n   * video.\n   */\n  requireSignedURLs?: boolean;\n  /**\n   * Indicates the date and time at which the video will be deleted. Omit\n   * the field to indicate no change, or include with a `null` value to remove an\n   * existing scheduled deletion. If specified, must be at least 30 days from upload\n   * time.\n   */\n  scheduledDeletion?: string | null;\n  /**\n   * The timestamp for a thumbnail image calculated as a percentage value\n   * of the video's duration. To convert from a second-wise timestamp to a\n   * percentage, divide the desired timestamp by the total duration of the video. If\n   * this value is not set, the default thumbnail image is taken from 0s of the\n   * video.\n   */\n  thumbnailTimestampPct?: number;\n  /**\n   * The identifier for the watermark profile\n   */\n  watermarkId?: string;\n};\nexport interface StreamScopedCaptions {\n  /**\n   * Uploads the caption or subtitle file to the endpoint for a specific BCP47 language.\n   * One caption or subtitle file per language is allowed.\n   * @param language The BCP 47 language tag for the caption or subtitle.\n   * @param file The caption or subtitle file to upload.\n   * @returns The created caption entry.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the language or file is invalid\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  upload(language: string, file: File): Promise<StreamCaption>;\n  /**\n   * Generate captions or subtitles for the provided language via AI.\n   * @param language The BCP 47 language tag to generate.\n   * @returns The generated caption entry.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the language is invalid\n   * @throws {StreamError} if a generated caption already exists\n   * @throws {StreamError} if the video duration is too long\n   * @throws {StreamError} if the video is missing audio\n   * @throws {StreamError} if the requested language is not supported\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(language: string): Promise<StreamCaption>;\n  /**\n   * Lists the captions or subtitles.\n   * Use the language parameter to filter by a specific language.\n   * @param language The optional BCP 47 language tag to filter by.\n   * @returns The list of captions or subtitles.\n   * @throws {NotFoundError} if the video or caption is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  list(language?: string): Promise<StreamCaption[]>;\n  /**\n   * Removes the captions or subtitles from a video.\n   * @param language The BCP 47 language tag to remove.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the video or caption is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(language: string): Promise<void>;\n}\nexport interface StreamScopedDownloads {\n  /**\n   * Generates a download for a video when a video is ready to view. Available\n   * types are `default` and `audio`. Defaults to `default` when omitted.\n   * @param downloadType The download type to create.\n   * @returns The current downloads for the video.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the download type is invalid\n   * @throws {StreamError} if the video duration is too long to generate a download\n   * @throws {StreamError} if the video is not ready to stream\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(\n    downloadType?: StreamDownloadType,\n  ): Promise<StreamDownloadGetResponse>;\n  /**\n   * Lists the downloads created for a video.\n   * @returns The current downloads for the video.\n   * @throws {NotFoundError} if the video or downloads are not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  get(): Promise<StreamDownloadGetResponse>;\n  /**\n   * Delete the downloads for a video. Available types are `default` and `audio`.\n   * Defaults to `default` when omitted.\n   * @param downloadType The download type to delete.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the video or downloads are not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(downloadType?: StreamDownloadType): Promise<void>;\n}\nexport interface StreamVideos {\n  /**\n   * Lists all videos in a users account.\n   * @returns The list of videos.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  list(params?: StreamVideosListParams): Promise<StreamVideo[]>;\n}\nexport interface StreamWatermarks {\n  /**\n   * Generate a new watermark profile\n   * @param file The image file to upload\n   * @param params The watermark creation parameters.\n   * @returns The created watermark profile.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InvalidURLError} if the URL is invalid\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {TooManyWatermarksError} if the number of allowed watermarks is reached\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(\n    file: File,\n    params: StreamWatermarkCreateParams,\n  ): Promise<StreamWatermark>;\n  /**\n   * Generate a new watermark profile\n   * @param url The image url to upload\n   * @param params The watermark creation parameters.\n   * @returns The created watermark profile.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InvalidURLError} if the URL is invalid\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {TooManyWatermarksError} if the number of allowed watermarks is reached\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(\n    url: string,\n    params: StreamWatermarkCreateParams,\n  ): Promise<StreamWatermark>;\n  /**\n   * Lists all watermark profiles for an account.\n   * @returns The list of watermark profiles.\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  list(): Promise<StreamWatermark[]>;\n  /**\n   * Retrieves details for a single watermark profile.\n   * @param watermarkId The watermark profile identifier.\n   * @returns The watermark profile details.\n   * @throws {NotFoundError} if the watermark is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  get(watermarkId: string): Promise<StreamWatermark>;\n  /**\n   * Deletes a watermark profile.\n   * @param watermarkId The watermark profile identifier.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the watermark is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(watermarkId: string): Promise<void>;\n}\nexport type StreamUpdateVideoParams = {\n  /**\n   * Lists the origins allowed to display the video. Enter allowed origin\n   * domains in an array and use `*` for wildcard subdomains. Empty arrays allow the\n   * video to be viewed on any origin.\n   */\n  allowedOrigins?: Array<string>;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator?: string;\n  /**\n   * The maximum duration in seconds for a video upload. Can be set for a\n   * video that is not yet uploaded to limit its duration. Uploads that exceed the\n   * specified duration will fail during processing. A value of `-1` means the value\n   * is unknown.\n   */\n  maxDurationSeconds?: number;\n  /**\n   * A user modifiable key-value store used to reference other systems of\n   * record for managing videos.\n   */\n  meta?: Record<string, string>;\n  /**\n   * Indicates whether the video can be a accessed using the id. When\n   * set to `true`, a signed token must be generated with a signing key to view the\n   * video.\n   */\n  requireSignedURLs?: boolean;\n  /**\n   * Indicates the date and time at which the video will be deleted. Omit\n   * the field to indicate no change, or include with a `null` value to remove an\n   * existing scheduled deletion. If specified, must be at least 30 days from upload\n   * time.\n   */\n  scheduledDeletion?: string | null;\n  /**\n   * The timestamp for a thumbnail image calculated as a percentage value\n   * of the video's duration. To convert from a second-wise timestamp to a\n   * percentage, divide the desired timestamp by the total duration of the video. If\n   * this value is not set, the default thumbnail image is taken from 0s of the\n   * video.\n   */\n  thumbnailTimestampPct?: number;\n};\nexport type StreamCaption = {\n  /**\n   * Whether the caption was generated via AI.\n   */\n  generated?: boolean;\n  /**\n   * The language label displayed in the native language to users.\n   */\n  label: string;\n  /**\n   * The language tag in BCP 47 format.\n   */\n  language: string;\n  /**\n   * The status of a generated caption.\n   */\n  status?: \"ready\" | \"inprogress\" | \"error\";\n};\nexport type StreamDownloadStatus = \"ready\" | \"inprogress\" | \"error\";\nexport type StreamDownloadType = \"default\" | \"audio\";\nexport type StreamDownload = {\n  /**\n   * Indicates the progress as a percentage between 0 and 100.\n   */\n  percentComplete: number;\n  /**\n   * The status of a generated download.\n   */\n  status: StreamDownloadStatus;\n  /**\n   * The URL to access the generated download.\n   */\n  url?: string;\n};\n/**\n * An object with download type keys. Each key is optional and only present if that\n * download type has been created.\n */\nexport type StreamDownloadGetResponse = {\n  /**\n   * The audio-only download. Only present if this download type has been created.\n   */\n  audio?: StreamDownload;\n  /**\n   * The default video download. Only present if this download type has been created.\n   */\n  default?: StreamDownload;\n};\nexport type StreamWatermarkPosition =\n  | \"upperRight\"\n  | \"upperLeft\"\n  | \"lowerLeft\"\n  | \"lowerRight\"\n  | \"center\";\nexport type StreamWatermark = {\n  /**\n   * The unique identifier for a watermark profile.\n   */\n  id: string;\n  /**\n   * The size of the image in bytes.\n   */\n  size: number;\n  /**\n   * The height of the image in pixels.\n   */\n  height: number;\n  /**\n   * The width of the image in pixels.\n   */\n  width: number;\n  /**\n   * The date and a time a watermark profile was created.\n   */\n  created: string;\n  /**\n   * The source URL for a downloaded image. If the watermark profile was created via\n   * direct upload, this field is null.\n   */\n  downloadedFrom: string | null;\n  /**\n   * A short description of the watermark profile.\n   */\n  name: string;\n  /**\n   * The translucency of the image. A value of `0.0` makes the image completely\n   * transparent, and `1.0` makes the image completely opaque. Note that if the image\n   * is already semi-transparent, setting this to `1.0` will not make the image\n   * completely opaque.\n   */\n  opacity: number;\n  /**\n   * The whitespace between the adjacent edges (determined by position) of the video\n   * and the image. `0.0` indicates no padding, and `1.0` indicates a fully padded\n   * video width or length, as determined by the algorithm.\n   */\n  padding: number;\n  /**\n   * The size of the image relative to the overall size of the video. This parameter\n   * will adapt to horizontal and vertical videos automatically. `0.0` indicates no\n   * scaling (use the size of the image as-is), and `1.0 `fills the entire video.\n   */\n  scale: number;\n  /**\n   * The location of the image. Valid positions are: `upperRight`, `upperLeft`,\n   * `lowerLeft`, `lowerRight`, and `center`. Note that `center` ignores the\n   * `padding` parameter.\n   */\n  position: StreamWatermarkPosition;\n};\nexport type StreamWatermarkCreateParams = {\n  /**\n   * A short description of the watermark profile.\n   */\n  name?: string;\n  /**\n   * The translucency of the image. A value of `0.0` makes the image completely\n   * transparent, and `1.0` makes the image completely opaque. Note that if the\n   * image is already semi-transparent, setting this to `1.0` will not make the\n   * image completely opaque.\n   */\n  opacity?: number;\n  /**\n   * The whitespace between the adjacent edges (determined by position) of the\n   * video and the image. `0.0` indicates no padding, and `1.0` indicates a fully\n   * padded video width or length, as determined by the algorithm.\n   */\n  padding?: number;\n  /**\n   * The size of the image relative to the overall size of the video. This\n   * parameter will adapt to horizontal and vertical videos automatically. `0.0`\n   * indicates no scaling (use the size of the image as-is), and `1.0 `fills the\n   * entire video.\n   */\n  scale?: number;\n  /**\n   * The location of the image.\n   */\n  position?: StreamWatermarkPosition;\n};\nexport type StreamVideosListParams = {\n  /**\n   * The maximum number of videos to return.\n   */\n  limit?: number;\n  /**\n   * Return videos created before this timestamp.\n   * (RFC3339/RFC3339Nano)\n   */\n  before?: string;\n  /**\n   * Comparison operator for the `before` field.\n   * @default 'lt'\n   */\n  beforeComp?: StreamPaginationComparison;\n  /**\n   * Return videos created after this timestamp.\n   * (RFC3339/RFC3339Nano)\n   */\n  after?: string;\n  /**\n   * Comparison operator for the `after` field.\n   * @default 'gte'\n   */\n  afterComp?: StreamPaginationComparison;\n};\nexport type StreamPaginationComparison = \"eq\" | \"gt\" | \"gte\" | \"lt\" | \"lte\";\n/**\n * Error object for Stream binding operations.\n */\nexport interface StreamError extends Error {\n  readonly code: number;\n  readonly statusCode: number;\n  readonly message: string;\n  readonly stack?: string;\n}\nexport interface InternalError extends StreamError {\n  name: \"InternalError\";\n}\nexport interface BadRequestError extends StreamError {\n  name: \"BadRequestError\";\n}\nexport interface NotFoundError extends StreamError {\n  name: \"NotFoundError\";\n}\nexport interface ForbiddenError extends StreamError {\n  name: \"ForbiddenError\";\n}\nexport interface RateLimitedError extends StreamError {\n  name: \"RateLimitedError\";\n}\nexport interface QuotaReachedError extends StreamError {\n  name: \"QuotaReachedError\";\n}\nexport interface MaxFileSizeError extends StreamError {\n  name: \"MaxFileSizeError\";\n}\nexport interface InvalidURLError extends StreamError {\n  name: \"InvalidURLError\";\n}\nexport interface AlreadyUploadedError extends StreamError {\n  name: \"AlreadyUploadedError\";\n}\nexport interface TooManyWatermarksError extends StreamError {\n  name: \"TooManyWatermarksError\";\n}\nexport type MarkdownDocument = {\n  name: string;\n  blob: Blob;\n};\nexport type ConversionResponse =\n  | {\n      id: string;\n      name: string;\n      mimeType: string;\n      format: \"markdown\";\n      tokens: number;\n      data: string;\n    }\n  | {\n      id: string;\n      name: string;\n      mimeType: string;\n      format: \"error\";\n      error: string;\n    };\nexport type ImageConversionOptions = {\n  descriptionLanguage?: \"en\" | \"es\" | \"fr\" | \"it\" | \"pt\" | \"de\";\n};\nexport type EmbeddedImageConversionOptions = ImageConversionOptions & {\n  convert?: boolean;\n  maxConvertedImages?: number;\n};\nexport type ConversionOptions = {\n  html?: {\n    images?: EmbeddedImageConversionOptions & {\n      convertOGImage?: boolean;\n    };\n    hostname?: string;\n    cssSelector?: string;\n  };\n  docx?: {\n    images?: EmbeddedImageConversionOptions;\n  };\n  image?: ImageConversionOptions;\n  pdf?: {\n    images?: EmbeddedImageConversionOptions;\n    metadata?: boolean;\n  };\n};\nexport type ConversionRequestOptions = {\n  gateway?: GatewayOptions;\n  extraHeaders?: object;\n  conversionOptions?: ConversionOptions;\n};\nexport type SupportedFileFormat = {\n  mimeType: string;\n  extension: string;\n};\nexport declare abstract class ToMarkdownService {\n  transform(\n    files: MarkdownDocument[],\n    options?: ConversionRequestOptions,\n  ): Promise<ConversionResponse[]>;\n  transform(\n    files: MarkdownDocument,\n    options?: ConversionRequestOptions,\n  ): Promise<ConversionResponse>;\n  supported(): Promise<SupportedFileFormat[]>;\n}\nexport declare namespace TailStream {\n  interface Header {\n    readonly name: string;\n    readonly value: string;\n  }\n  interface FetchEventInfo {\n    readonly type: \"fetch\";\n    readonly method: string;\n    readonly url: string;\n    readonly cfJson?: object;\n    readonly headers: Header[];\n  }\n  interface JsRpcEventInfo {\n    readonly type: \"jsrpc\";\n  }\n  interface ScheduledEventInfo {\n    readonly type: \"scheduled\";\n    readonly scheduledTime: Date;\n    readonly cron: string;\n  }\n  interface AlarmEventInfo {\n    readonly type: \"alarm\";\n    readonly scheduledTime: Date;\n  }\n  interface QueueEventInfo {\n    readonly type: \"queue\";\n    readonly queueName: string;\n    readonly batchSize: number;\n  }\n  interface EmailEventInfo {\n    readonly type: \"email\";\n    readonly mailFrom: string;\n    readonly rcptTo: string;\n    readonly rawSize: number;\n  }\n  interface TraceEventInfo {\n    readonly type: \"trace\";\n    readonly traces: (string | null)[];\n  }\n  interface HibernatableWebSocketEventInfoMessage {\n    readonly type: \"message\";\n  }\n  interface HibernatableWebSocketEventInfoError {\n    readonly type: \"error\";\n  }\n  interface HibernatableWebSocketEventInfoClose {\n    readonly type: \"close\";\n    readonly code: number;\n    readonly wasClean: boolean;\n  }\n  interface HibernatableWebSocketEventInfo {\n    readonly type: \"hibernatableWebSocket\";\n    readonly info:\n      | HibernatableWebSocketEventInfoClose\n      | HibernatableWebSocketEventInfoError\n      | HibernatableWebSocketEventInfoMessage;\n  }\n  interface CustomEventInfo {\n    readonly type: \"custom\";\n  }\n  interface FetchResponseInfo {\n    readonly type: \"fetch\";\n    readonly statusCode: number;\n  }\n  type EventOutcome =\n    | \"ok\"\n    | \"canceled\"\n    | \"exception\"\n    | \"unknown\"\n    | \"killSwitch\"\n    | \"daemonDown\"\n    | \"exceededCpu\"\n    | \"exceededMemory\"\n    | \"loadShed\"\n    | \"responseStreamDisconnected\"\n    | \"scriptNotFound\";\n  interface ScriptVersion {\n    readonly id: string;\n    readonly tag?: string;\n    readonly message?: string;\n  }\n  interface Onset {\n    readonly type: \"onset\";\n    readonly attributes: Attribute[];\n    // id for the span being opened by this Onset event.\n    readonly spanId: string;\n    readonly dispatchNamespace?: string;\n    readonly entrypoint?: string;\n    readonly executionModel: string;\n    readonly scriptName?: string;\n    readonly scriptTags?: string[];\n    readonly scriptVersion?: ScriptVersion;\n    readonly info:\n      | FetchEventInfo\n      | JsRpcEventInfo\n      | ScheduledEventInfo\n      | AlarmEventInfo\n      | QueueEventInfo\n      | EmailEventInfo\n      | TraceEventInfo\n      | HibernatableWebSocketEventInfo\n      | CustomEventInfo;\n  }\n  interface Outcome {\n    readonly type: \"outcome\";\n    readonly outcome: EventOutcome;\n    readonly cpuTime: number;\n    readonly wallTime: number;\n  }\n  interface SpanOpen {\n    readonly type: \"spanOpen\";\n    readonly name: string;\n    // id for the span being opened by this SpanOpen event.\n    readonly spanId: string;\n    readonly info?: FetchEventInfo | JsRpcEventInfo | Attributes;\n  }\n  interface SpanClose {\n    readonly type: \"spanClose\";\n    readonly outcome: EventOutcome;\n  }\n  interface DiagnosticChannelEvent {\n    readonly type: \"diagnosticChannel\";\n    readonly channel: string;\n    readonly message: any;\n  }\n  interface Exception {\n    readonly type: \"exception\";\n    readonly name: string;\n    readonly message: string;\n    readonly stack?: string;\n  }\n  interface Log {\n    readonly type: \"log\";\n    readonly level: \"debug\" | \"error\" | \"info\" | \"log\" | \"warn\";\n    readonly message: object;\n  }\n  interface DroppedEventsDiagnostic {\n    readonly diagnosticsType: \"droppedEvents\";\n    readonly count: number;\n  }\n  interface StreamDiagnostic {\n    readonly type: \"streamDiagnostic\";\n    // To add new diagnostic types, define a new interface and add it to this union type.\n    readonly diagnostic: DroppedEventsDiagnostic;\n  }\n  // This marks the worker handler return information.\n  // This is separate from Outcome because the worker invocation can live for a long time after\n  // returning. For example - Websockets that return an http upgrade response but then continue\n  // streaming information or SSE http connections.\n  interface Return {\n    readonly type: \"return\";\n    readonly info?: FetchResponseInfo;\n  }\n  interface Attribute {\n    readonly name: string;\n    readonly value:\n      | string\n      | string[]\n      | boolean\n      | boolean[]\n      | number\n      | number[]\n      | bigint\n      | bigint[];\n  }\n  interface Attributes {\n    readonly type: \"attributes\";\n    readonly info: Attribute[];\n  }\n  type EventType =\n    | Onset\n    | Outcome\n    | SpanOpen\n    | SpanClose\n    | DiagnosticChannelEvent\n    | Exception\n    | Log\n    | StreamDiagnostic\n    | Return\n    | Attributes;\n  // Context in which this trace event lives.\n  interface SpanContext {\n    // Single id for the entire top-level invocation\n    // This should be a new traceId for the first worker stage invoked in the eyeball request and then\n    // same-account service-bindings should reuse the same traceId but cross-account service-bindings\n    // should use a new traceId.\n    readonly traceId: string;\n    // spanId in which this event is handled\n    // for Onset and SpanOpen events this would be the parent span id\n    // for Outcome and SpanClose these this would be the span id of the opening Onset and SpanOpen events\n    // For Hibernate and Mark this would be the span under which they were emitted.\n    // spanId is not set ONLY if:\n    //  1. This is an Onset event\n    //  2. We are not inheriting any SpanContext. (e.g. this is a cross-account service binding or a new top-level invocation)\n    readonly spanId?: string;\n  }\n  interface TailEvent<Event extends EventType> {\n    // invocation id of the currently invoked worker stage.\n    // invocation id will always be unique to every Onset event and will be the same until the Outcome event.\n    readonly invocationId: string;\n    // Inherited spanContext for this event.\n    readonly spanContext: SpanContext;\n    readonly timestamp: Date;\n    readonly sequence: number;\n    readonly event: Event;\n  }\n  type TailEventHandler<Event extends EventType = EventType> = (\n    event: TailEvent<Event>,\n  ) => void | Promise<void>;\n  type TailEventHandlerObject = {\n    outcome?: TailEventHandler<Outcome>;\n    spanOpen?: TailEventHandler<SpanOpen>;\n    spanClose?: TailEventHandler<SpanClose>;\n    diagnosticChannel?: TailEventHandler<DiagnosticChannelEvent>;\n    exception?: TailEventHandler<Exception>;\n    log?: TailEventHandler<Log>;\n    return?: TailEventHandler<Return>;\n    attributes?: TailEventHandler<Attributes>;\n  };\n  type TailEventHandlerType = TailEventHandler | TailEventHandlerObject;\n}\n// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n/**\n * Data types supported for holding vector metadata.\n */\nexport type VectorizeVectorMetadataValue = string | number | boolean | string[];\n/**\n * Additional information to associate with a vector.\n */\nexport type VectorizeVectorMetadata =\n  | VectorizeVectorMetadataValue\n  | Record<string, VectorizeVectorMetadataValue>;\nexport type VectorFloatArray = Float32Array | Float64Array;\nexport interface VectorizeError {\n  code?: number;\n  error: string;\n}\n/**\n * Comparison logic/operation to use for metadata filtering.\n *\n * This list is expected to grow as support for more operations are released.\n */\nexport type VectorizeVectorMetadataFilterOp =\n  | \"$eq\"\n  | \"$ne\"\n  | \"$lt\"\n  | \"$lte\"\n  | \"$gt\"\n  | \"$gte\";\nexport type VectorizeVectorMetadataFilterCollectionOp = \"$in\" | \"$nin\";\n/**\n * Filter criteria for vector metadata used to limit the retrieved query result set.\n */\nexport type VectorizeVectorMetadataFilter = {\n  [field: string]:\n    | Exclude<VectorizeVectorMetadataValue, string[]>\n    | null\n    | {\n        [Op in VectorizeVectorMetadataFilterOp]?: Exclude<\n          VectorizeVectorMetadataValue,\n          string[]\n        > | null;\n      }\n    | {\n        [Op in VectorizeVectorMetadataFilterCollectionOp]?: Exclude<\n          VectorizeVectorMetadataValue,\n          string[]\n        >[];\n      };\n};\n/**\n * Supported distance metrics for an index.\n * Distance metrics determine how other \"similar\" vectors are determined.\n */\nexport type VectorizeDistanceMetric = \"euclidean\" | \"cosine\" | \"dot-product\";\n/**\n * Metadata return levels for a Vectorize query.\n *\n * Default to \"none\".\n *\n * @property all      Full metadata for the vector return set, including all fields (including those un-indexed) without truncation. This is a more expensive retrieval, as it requires additional fetching & reading of un-indexed data.\n * @property indexed  Return all metadata fields configured for indexing in the vector return set. This level of retrieval is \"free\" in that no additional overhead is incurred returning this data. However, note that indexed metadata is subject to truncation (especially for larger strings).\n * @property none     No indexed metadata will be returned.\n */\nexport type VectorizeMetadataRetrievalLevel = \"all\" | \"indexed\" | \"none\";\nexport interface VectorizeQueryOptions {\n  topK?: number;\n  namespace?: string;\n  returnValues?: boolean;\n  returnMetadata?: boolean | VectorizeMetadataRetrievalLevel;\n  filter?: VectorizeVectorMetadataFilter;\n}\n/**\n * Information about the configuration of an index.\n */\nexport type VectorizeIndexConfig =\n  | {\n      dimensions: number;\n      metric: VectorizeDistanceMetric;\n    }\n  | {\n      preset: string; // keep this generic, as we'll be adding more presets in the future and this is only in a read capacity\n    };\n/**\n * Metadata about an existing index.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link VectorizeIndexInfo} for its post-beta equivalent.\n */\nexport interface VectorizeIndexDetails {\n  /** The unique ID of the index */\n  readonly id: string;\n  /** The name of the index. */\n  name: string;\n  /** (optional) A human readable description for the index. */\n  description?: string;\n  /** The index configuration, including the dimension size and distance metric. */\n  config: VectorizeIndexConfig;\n  /** The number of records containing vectors within the index. */\n  vectorsCount: number;\n}\n/**\n * Metadata about an existing index.\n */\nexport interface VectorizeIndexInfo {\n  /** The number of records containing vectors within the index. */\n  vectorCount: number;\n  /** Number of dimensions the index has been configured for. */\n  dimensions: number;\n  /** ISO 8601 datetime of the last processed mutation on in the index. All changes before this mutation will be reflected in the index state. */\n  processedUpToDatetime: number;\n  /** UUIDv4 of the last mutation processed by the index. All changes before this mutation will be reflected in the index state. */\n  processedUpToMutation: number;\n}\n/**\n * Represents a single vector value set along with its associated metadata.\n */\nexport interface VectorizeVector {\n  /** The ID for the vector. This can be user-defined, and must be unique. It should uniquely identify the object, and is best set based on the ID of what the vector represents. */\n  id: string;\n  /** The vector values */\n  values: VectorFloatArray | number[];\n  /** The namespace this vector belongs to. */\n  namespace?: string;\n  /** Metadata associated with the vector. Includes the values of other fields and potentially additional details. */\n  metadata?: Record<string, VectorizeVectorMetadata>;\n}\n/**\n * Represents a matched vector for a query along with its score and (if specified) the matching vector information.\n */\nexport type VectorizeMatch = Pick<Partial<VectorizeVector>, \"values\"> &\n  Omit<VectorizeVector, \"values\"> & {\n    /** The score or rank for similarity, when returned as a result */\n    score: number;\n  };\n/**\n * A set of matching {@link VectorizeMatch} for a particular query.\n */\nexport interface VectorizeMatches {\n  matches: VectorizeMatch[];\n  count: number;\n}\n/**\n * Results of an operation that performed a mutation on a set of vectors.\n * Here, `ids` is a list of vectors that were successfully processed.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link VectorizeAsyncMutation} for its post-beta equivalent.\n */\nexport interface VectorizeVectorMutation {\n  /* List of ids of vectors that were successfully processed. */\n  ids: string[];\n  /* Total count of the number of processed vectors. */\n  count: number;\n}\n/**\n * Result type indicating a mutation on the Vectorize Index.\n * Actual mutations are processed async where the `mutationId` is the unique identifier for the operation.\n */\nexport interface VectorizeAsyncMutation {\n  /** The unique identifier for the async mutation operation containing the changeset. */\n  mutationId: string;\n}\n/**\n * A Vectorize Vector Search Index for querying vectors/embeddings.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link Vectorize} for its new implementation.\n */\nexport declare abstract class VectorizeIndex {\n  /**\n   * Get information about the currently bound index.\n   * @returns A promise that resolves with information about the current index.\n   */\n  public describe(): Promise<VectorizeIndexDetails>;\n  /**\n   * Use the provided vector to perform a similarity search across the index.\n   * @param vector Input vector that will be used to drive the similarity search.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  public query(\n    vector: VectorFloatArray | number[],\n    options?: VectorizeQueryOptions,\n  ): Promise<VectorizeMatches>;\n  /**\n   * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown.\n   * @param vectors List of vectors that will be inserted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed.\n   */\n  public insert(vectors: VectorizeVector[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values.\n   * @param vectors List of vectors that will be upserted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed.\n   */\n  public upsert(vectors: VectorizeVector[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Delete a list of vectors with a matching id.\n   * @param ids List of vector ids that should be deleted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed (and thus deleted).\n   */\n  public deleteByIds(ids: string[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Get a list of vectors with a matching id.\n   * @param ids List of vector ids that should be returned.\n   * @returns A promise that resolves with the raw unscored vectors matching the id set.\n   */\n  public getByIds(ids: string[]): Promise<VectorizeVector[]>;\n}\n/**\n * A Vectorize Vector Search Index for querying vectors/embeddings.\n *\n * Mutations in this version are async, returning a mutation id.\n */\nexport declare abstract class Vectorize {\n  /**\n   * Get information about the currently bound index.\n   * @returns A promise that resolves with information about the current index.\n   */\n  public describe(): Promise<VectorizeIndexInfo>;\n  /**\n   * Use the provided vector to perform a similarity search across the index.\n   * @param vector Input vector that will be used to drive the similarity search.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  public query(\n    vector: VectorFloatArray | number[],\n    options?: VectorizeQueryOptions,\n  ): Promise<VectorizeMatches>;\n  /**\n   * Use the provided vector-id to perform a similarity search across the index.\n   * @param vectorId Id for a vector in the index against which the index should be queried.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  public queryById(\n    vectorId: string,\n    options?: VectorizeQueryOptions,\n  ): Promise<VectorizeMatches>;\n  /**\n   * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown.\n   * @param vectors List of vectors that will be inserted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the insert changeset.\n   */\n  public insert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values.\n   * @param vectors List of vectors that will be upserted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the upsert changeset.\n   */\n  public upsert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Delete a list of vectors with a matching id.\n   * @param ids List of vector ids that should be deleted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the delete changeset.\n   */\n  public deleteByIds(ids: string[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Get a list of vectors with a matching id.\n   * @param ids List of vector ids that should be returned.\n   * @returns A promise that resolves with the raw unscored vectors matching the id set.\n   */\n  public getByIds(ids: string[]): Promise<VectorizeVector[]>;\n}\n/**\n * The interface for \"version_metadata\" binding\n * providing metadata about the Worker Version using this binding.\n */\nexport type WorkerVersionMetadata = {\n  /** The ID of the Worker Version using this binding */\n  id: string;\n  /** The tag of the Worker Version using this binding */\n  tag: string;\n  /** The timestamp of when the Worker Version was uploaded */\n  timestamp: string;\n};\nexport interface DynamicDispatchLimits {\n  /**\n   * Limit CPU time in milliseconds.\n   */\n  cpuMs?: number;\n  /**\n   * Limit number of subrequests.\n   */\n  subRequests?: number;\n}\nexport interface DynamicDispatchOptions {\n  /**\n   * Limit resources of invoked Worker script.\n   */\n  limits?: DynamicDispatchLimits;\n  /**\n   * Arguments for outbound Worker script, if configured.\n   */\n  outbound?: {\n    [key: string]: any;\n  };\n}\nexport interface DispatchNamespace {\n  /**\n   * @param name Name of the Worker script.\n   * @param args Arguments to Worker script.\n   * @param options Options for Dynamic Dispatch invocation.\n   * @returns A Fetcher object that allows you to send requests to the Worker script.\n   * @throws If the Worker script does not exist in this dispatch namespace, an error will be thrown.\n   */\n  get(\n    name: string,\n    args?: {\n      [key: string]: any;\n    },\n    options?: DynamicDispatchOptions,\n  ): Fetcher;\n}\nexport declare abstract class Workflow<PARAMS = unknown> {\n  /**\n   * Get a handle to an existing instance of the Workflow.\n   * @param id Id for the instance of this Workflow\n   * @returns A promise that resolves with a handle for the Instance\n   */\n  public get(id: string): Promise<WorkflowInstance>;\n  /**\n   * Create a new instance and return a handle to it. If a provided id exists, an error will be thrown.\n   * @param options Options when creating an instance including id and params\n   * @returns A promise that resolves with a handle for the Instance\n   */\n  public create(\n    options?: WorkflowInstanceCreateOptions<PARAMS>,\n  ): Promise<WorkflowInstance>;\n  /**\n   * Create a batch of instances and return handle for all of them. If a provided id exists, an error will be thrown.\n   * `createBatch` is limited at 100 instances at a time or when the RPC limit for the batch (1MiB) is reached.\n   * @param batch List of Options when creating an instance including name and params\n   * @returns A promise that resolves with a list of handles for the created instances.\n   */\n  public createBatch(\n    batch: WorkflowInstanceCreateOptions<PARAMS>[],\n  ): Promise<WorkflowInstance[]>;\n}\nexport type WorkflowDurationLabel =\n  | \"second\"\n  | \"minute\"\n  | \"hour\"\n  | \"day\"\n  | \"week\"\n  | \"month\"\n  | \"year\";\nexport type WorkflowSleepDuration =\n  | `${number} ${WorkflowDurationLabel}${\"s\" | \"\"}`\n  | number;\nexport type WorkflowRetentionDuration = WorkflowSleepDuration;\nexport interface WorkflowInstanceCreateOptions<PARAMS = unknown> {\n  /**\n   * An id for your Workflow instance. Must be unique within the Workflow.\n   */\n  id?: string;\n  /**\n   * The event payload the Workflow instance is triggered with\n   */\n  params?: PARAMS;\n  /**\n   * The retention policy for Workflow instance.\n   * Defaults to the maximum retention period available for the owner's account.\n   */\n  retention?: {\n    successRetention?: WorkflowRetentionDuration;\n    errorRetention?: WorkflowRetentionDuration;\n  };\n}\nexport type InstanceStatus = {\n  status:\n    | \"queued\" // means that instance is waiting to be started (see concurrency limits)\n    | \"running\"\n    | \"paused\"\n    | \"errored\"\n    | \"terminated\" // user terminated the instance while it was running\n    | \"complete\"\n    | \"waiting\" // instance is hibernating and waiting for sleep or event to finish\n    | \"waitingForPause\" // instance is finishing the current work to pause\n    | \"unknown\";\n  error?: {\n    name: string;\n    message: string;\n  };\n  output?: unknown;\n};\nexport interface WorkflowError {\n  code?: number;\n  message: string;\n}\nexport declare abstract class WorkflowInstance {\n  public id: string;\n  /**\n   * Pause the instance.\n   */\n  public pause(): Promise<void>;\n  /**\n   * Resume the instance. If it is already running, an error will be thrown.\n   */\n  public resume(): Promise<void>;\n  /**\n   * Terminate the instance. If it is errored, terminated or complete, an error will be thrown.\n   */\n  public terminate(): Promise<void>;\n  /**\n   * Restart the instance.\n   */\n  public restart(): Promise<void>;\n  /**\n   * Returns the current status of the instance.\n   */\n  public status(): Promise<InstanceStatus>;\n  /**\n   * Send an event to this instance.\n   */\n  public sendEvent({\n    type,\n    payload,\n  }: {\n    type: string;\n    payload: unknown;\n  }): Promise<void>;\n}\n"
  },
  {
    "path": "types/generated-snapshot/latest/index.d.ts",
    "content": "/*! *****************************************************************************\nCopyright (c) Cloudflare. All rights reserved.\nCopyright (c) Microsoft Corporation. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at http://www.apache.org/licenses/LICENSE-2.0\nTHIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\nKIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED\nWARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,\nMERCHANTABLITY OR NON-INFRINGEMENT.\nSee the Apache Version 2.0 License for specific language governing permissions\nand limitations under the License.\n***************************************************************************** */\n/* eslint-disable */\n// noinspection JSUnusedGlobalSymbols\ndeclare var onmessage: never;\n/**\n * The **`DOMException`** interface represents an abnormal event (called an **exception**) that occurs as a result of calling a method or accessing a property of a web API.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException)\n */\ndeclare class DOMException extends Error {\n  constructor(message?: string, name?: string);\n  /**\n   * The **`message`** read-only property of the a message or description associated with the given error name.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/message)\n   */\n  readonly message: string;\n  /**\n   * The **`name`** read-only property of the one of the strings associated with an error name.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/name)\n   */\n  readonly name: string;\n  /**\n   * The **`code`** read-only property of the DOMException interface returns one of the legacy error code constants, or `0` if none match.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/code)\n   */\n  readonly code: number;\n  static readonly INDEX_SIZE_ERR: number;\n  static readonly DOMSTRING_SIZE_ERR: number;\n  static readonly HIERARCHY_REQUEST_ERR: number;\n  static readonly WRONG_DOCUMENT_ERR: number;\n  static readonly INVALID_CHARACTER_ERR: number;\n  static readonly NO_DATA_ALLOWED_ERR: number;\n  static readonly NO_MODIFICATION_ALLOWED_ERR: number;\n  static readonly NOT_FOUND_ERR: number;\n  static readonly NOT_SUPPORTED_ERR: number;\n  static readonly INUSE_ATTRIBUTE_ERR: number;\n  static readonly INVALID_STATE_ERR: number;\n  static readonly SYNTAX_ERR: number;\n  static readonly INVALID_MODIFICATION_ERR: number;\n  static readonly NAMESPACE_ERR: number;\n  static readonly INVALID_ACCESS_ERR: number;\n  static readonly VALIDATION_ERR: number;\n  static readonly TYPE_MISMATCH_ERR: number;\n  static readonly SECURITY_ERR: number;\n  static readonly NETWORK_ERR: number;\n  static readonly ABORT_ERR: number;\n  static readonly URL_MISMATCH_ERR: number;\n  static readonly QUOTA_EXCEEDED_ERR: number;\n  static readonly TIMEOUT_ERR: number;\n  static readonly INVALID_NODE_TYPE_ERR: number;\n  static readonly DATA_CLONE_ERR: number;\n  get stack(): any;\n  set stack(value: any);\n}\ntype WorkerGlobalScopeEventMap = {\n  fetch: FetchEvent;\n  scheduled: ScheduledEvent;\n  queue: QueueEvent;\n  unhandledrejection: PromiseRejectionEvent;\n  rejectionhandled: PromiseRejectionEvent;\n};\ndeclare abstract class WorkerGlobalScope extends EventTarget<WorkerGlobalScopeEventMap> {\n  EventTarget: typeof EventTarget;\n}\n/* The **`console`** object provides access to the debugging console (e.g., the Web console in Firefox). *\n * The **`console`** object provides access to the debugging console (e.g., the Web console in Firefox).\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console)\n */\ninterface Console {\n  \"assert\"(condition?: boolean, ...data: any[]): void;\n  /**\n   * The **`console.clear()`** static method clears the console if possible.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/clear_static)\n   */\n  clear(): void;\n  /**\n   * The **`console.count()`** static method logs the number of times that this particular call to `count()` has been called.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/count_static)\n   */\n  count(label?: string): void;\n  /**\n   * The **`console.countReset()`** static method resets counter used with console/count_static.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/countReset_static)\n   */\n  countReset(label?: string): void;\n  /**\n   * The **`console.debug()`** static method outputs a message to the console at the 'debug' log level.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/debug_static)\n   */\n  debug(...data: any[]): void;\n  /**\n   * The **`console.dir()`** static method displays a list of the properties of the specified JavaScript object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dir_static)\n   */\n  dir(item?: any, options?: any): void;\n  /**\n   * The **`console.dirxml()`** static method displays an interactive tree of the descendant elements of the specified XML/HTML element.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dirxml_static)\n   */\n  dirxml(...data: any[]): void;\n  /**\n   * The **`console.error()`** static method outputs a message to the console at the 'error' log level.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/error_static)\n   */\n  error(...data: any[]): void;\n  /**\n   * The **`console.group()`** static method creates a new inline group in the Web console log, causing any subsequent console messages to be indented by an additional level, until console/groupEnd_static is called.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/group_static)\n   */\n  group(...data: any[]): void;\n  /**\n   * The **`console.groupCollapsed()`** static method creates a new inline group in the console.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupCollapsed_static)\n   */\n  groupCollapsed(...data: any[]): void;\n  /**\n   * The **`console.groupEnd()`** static method exits the current inline group in the console.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupEnd_static)\n   */\n  groupEnd(): void;\n  /**\n   * The **`console.info()`** static method outputs a message to the console at the 'info' log level.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/info_static)\n   */\n  info(...data: any[]): void;\n  /**\n   * The **`console.log()`** static method outputs a message to the console.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)\n   */\n  log(...data: any[]): void;\n  /**\n   * The **`console.table()`** static method displays tabular data as a table.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/table_static)\n   */\n  table(tabularData?: any, properties?: string[]): void;\n  /**\n   * The **`console.time()`** static method starts a timer you can use to track how long an operation takes.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/time_static)\n   */\n  time(label?: string): void;\n  /**\n   * The **`console.timeEnd()`** static method stops a timer that was previously started by calling console/time_static.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeEnd_static)\n   */\n  timeEnd(label?: string): void;\n  /**\n   * The **`console.timeLog()`** static method logs the current value of a timer that was previously started by calling console/time_static.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeLog_static)\n   */\n  timeLog(label?: string, ...data: any[]): void;\n  timeStamp(label?: string): void;\n  /**\n   * The **`console.trace()`** static method outputs a stack trace to the console.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/trace_static)\n   */\n  trace(...data: any[]): void;\n  /**\n   * The **`console.warn()`** static method outputs a warning message to the console at the 'warning' log level.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/warn_static)\n   */\n  warn(...data: any[]): void;\n}\ndeclare const console: Console;\ntype BufferSource = ArrayBufferView | ArrayBuffer;\ntype TypedArray =\n  | Int8Array\n  | Uint8Array\n  | Uint8ClampedArray\n  | Int16Array\n  | Uint16Array\n  | Int32Array\n  | Uint32Array\n  | Float32Array\n  | Float64Array\n  | BigInt64Array\n  | BigUint64Array;\ndeclare namespace WebAssembly {\n  class CompileError extends Error {\n    constructor(message?: string);\n  }\n  class RuntimeError extends Error {\n    constructor(message?: string);\n  }\n  type ValueType =\n    | \"anyfunc\"\n    | \"externref\"\n    | \"f32\"\n    | \"f64\"\n    | \"i32\"\n    | \"i64\"\n    | \"v128\";\n  interface GlobalDescriptor {\n    value: ValueType;\n    mutable?: boolean;\n  }\n  class Global {\n    constructor(descriptor: GlobalDescriptor, value?: any);\n    value: any;\n    valueOf(): any;\n  }\n  type ImportValue = ExportValue | number;\n  type ModuleImports = Record<string, ImportValue>;\n  type Imports = Record<string, ModuleImports>;\n  type ExportValue = Function | Global | Memory | Table;\n  type Exports = Record<string, ExportValue>;\n  class Instance {\n    constructor(module: Module, imports?: Imports);\n    readonly exports: Exports;\n  }\n  interface MemoryDescriptor {\n    initial: number;\n    maximum?: number;\n    shared?: boolean;\n  }\n  class Memory {\n    constructor(descriptor: MemoryDescriptor);\n    readonly buffer: ArrayBuffer;\n    grow(delta: number): number;\n  }\n  type ImportExportKind = \"function\" | \"global\" | \"memory\" | \"table\";\n  interface ModuleExportDescriptor {\n    kind: ImportExportKind;\n    name: string;\n  }\n  interface ModuleImportDescriptor {\n    kind: ImportExportKind;\n    module: string;\n    name: string;\n  }\n  abstract class Module {\n    static customSections(module: Module, sectionName: string): ArrayBuffer[];\n    static exports(module: Module): ModuleExportDescriptor[];\n    static imports(module: Module): ModuleImportDescriptor[];\n  }\n  type TableKind = \"anyfunc\" | \"externref\";\n  interface TableDescriptor {\n    element: TableKind;\n    initial: number;\n    maximum?: number;\n  }\n  class Table {\n    constructor(descriptor: TableDescriptor, value?: any);\n    readonly length: number;\n    get(index: number): any;\n    grow(delta: number, value?: any): number;\n    set(index: number, value?: any): void;\n  }\n  function instantiate(module: Module, imports?: Imports): Promise<Instance>;\n  function validate(bytes: BufferSource): boolean;\n}\n/**\n * The **`ServiceWorkerGlobalScope`** interface of the Service Worker API represents the global execution context of a service worker.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope)\n */\ninterface ServiceWorkerGlobalScope extends WorkerGlobalScope {\n  DOMException: typeof DOMException;\n  WorkerGlobalScope: typeof WorkerGlobalScope;\n  btoa(data: string): string;\n  atob(data: string): string;\n  setTimeout(callback: (...args: any[]) => void, msDelay?: number): number;\n  setTimeout<Args extends any[]>(\n    callback: (...args: Args) => void,\n    msDelay?: number,\n    ...args: Args\n  ): number;\n  clearTimeout(timeoutId: number | null): void;\n  setInterval(callback: (...args: any[]) => void, msDelay?: number): number;\n  setInterval<Args extends any[]>(\n    callback: (...args: Args) => void,\n    msDelay?: number,\n    ...args: Args\n  ): number;\n  clearInterval(timeoutId: number | null): void;\n  queueMicrotask(task: Function): void;\n  structuredClone<T>(value: T, options?: StructuredSerializeOptions): T;\n  reportError(error: any): void;\n  fetch(\n    input: RequestInfo | URL,\n    init?: RequestInit<RequestInitCfProperties>,\n  ): Promise<Response>;\n  self: ServiceWorkerGlobalScope;\n  crypto: Crypto;\n  caches: CacheStorage;\n  scheduler: Scheduler;\n  performance: Performance;\n  Cloudflare: Cloudflare;\n  readonly origin: string;\n  Event: typeof Event;\n  ExtendableEvent: typeof ExtendableEvent;\n  CustomEvent: typeof CustomEvent;\n  PromiseRejectionEvent: typeof PromiseRejectionEvent;\n  FetchEvent: typeof FetchEvent;\n  TailEvent: typeof TailEvent;\n  TraceEvent: typeof TailEvent;\n  ScheduledEvent: typeof ScheduledEvent;\n  MessageEvent: typeof MessageEvent;\n  CloseEvent: typeof CloseEvent;\n  ReadableStreamDefaultReader: typeof ReadableStreamDefaultReader;\n  ReadableStreamBYOBReader: typeof ReadableStreamBYOBReader;\n  ReadableStream: typeof ReadableStream;\n  WritableStream: typeof WritableStream;\n  WritableStreamDefaultWriter: typeof WritableStreamDefaultWriter;\n  TransformStream: typeof TransformStream;\n  ByteLengthQueuingStrategy: typeof ByteLengthQueuingStrategy;\n  CountQueuingStrategy: typeof CountQueuingStrategy;\n  ErrorEvent: typeof ErrorEvent;\n  MessageChannel: typeof MessageChannel;\n  MessagePort: typeof MessagePort;\n  EventSource: typeof EventSource;\n  ReadableStreamBYOBRequest: typeof ReadableStreamBYOBRequest;\n  ReadableStreamDefaultController: typeof ReadableStreamDefaultController;\n  ReadableByteStreamController: typeof ReadableByteStreamController;\n  WritableStreamDefaultController: typeof WritableStreamDefaultController;\n  TransformStreamDefaultController: typeof TransformStreamDefaultController;\n  CompressionStream: typeof CompressionStream;\n  DecompressionStream: typeof DecompressionStream;\n  TextEncoderStream: typeof TextEncoderStream;\n  TextDecoderStream: typeof TextDecoderStream;\n  Headers: typeof Headers;\n  Body: typeof Body;\n  Request: typeof Request;\n  Response: typeof Response;\n  WebSocket: typeof WebSocket;\n  WebSocketPair: typeof WebSocketPair;\n  WebSocketRequestResponsePair: typeof WebSocketRequestResponsePair;\n  AbortController: typeof AbortController;\n  AbortSignal: typeof AbortSignal;\n  TextDecoder: typeof TextDecoder;\n  TextEncoder: typeof TextEncoder;\n  navigator: Navigator;\n  Navigator: typeof Navigator;\n  URL: typeof URL;\n  URLSearchParams: typeof URLSearchParams;\n  URLPattern: typeof URLPattern;\n  Blob: typeof Blob;\n  File: typeof File;\n  FormData: typeof FormData;\n  Crypto: typeof Crypto;\n  SubtleCrypto: typeof SubtleCrypto;\n  CryptoKey: typeof CryptoKey;\n  CacheStorage: typeof CacheStorage;\n  Cache: typeof Cache;\n  FixedLengthStream: typeof FixedLengthStream;\n  IdentityTransformStream: typeof IdentityTransformStream;\n  HTMLRewriter: typeof HTMLRewriter;\n}\ndeclare function addEventListener<Type extends keyof WorkerGlobalScopeEventMap>(\n  type: Type,\n  handler: EventListenerOrEventListenerObject<WorkerGlobalScopeEventMap[Type]>,\n  options?: EventTargetAddEventListenerOptions | boolean,\n): void;\ndeclare function removeEventListener<\n  Type extends keyof WorkerGlobalScopeEventMap,\n>(\n  type: Type,\n  handler: EventListenerOrEventListenerObject<WorkerGlobalScopeEventMap[Type]>,\n  options?: EventTargetEventListenerOptions | boolean,\n): void;\n/**\n * The **`dispatchEvent()`** method of the EventTarget sends an Event to the object, (synchronously) invoking the affected event listeners in the appropriate order.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent)\n */\ndeclare function dispatchEvent(\n  event: WorkerGlobalScopeEventMap[keyof WorkerGlobalScopeEventMap],\n): boolean;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/btoa) */\ndeclare function btoa(data: string): string;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/atob) */\ndeclare function atob(data: string): string;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */\ndeclare function setTimeout(\n  callback: (...args: any[]) => void,\n  msDelay?: number,\n): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */\ndeclare function setTimeout<Args extends any[]>(\n  callback: (...args: Args) => void,\n  msDelay?: number,\n  ...args: Args\n): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearTimeout) */\ndeclare function clearTimeout(timeoutId: number | null): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */\ndeclare function setInterval(\n  callback: (...args: any[]) => void,\n  msDelay?: number,\n): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */\ndeclare function setInterval<Args extends any[]>(\n  callback: (...args: Args) => void,\n  msDelay?: number,\n  ...args: Args\n): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearInterval) */\ndeclare function clearInterval(timeoutId: number | null): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/queueMicrotask) */\ndeclare function queueMicrotask(task: Function): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/structuredClone) */\ndeclare function structuredClone<T>(\n  value: T,\n  options?: StructuredSerializeOptions,\n): T;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/reportError) */\ndeclare function reportError(error: any): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch) */\ndeclare function fetch(\n  input: RequestInfo | URL,\n  init?: RequestInit<RequestInitCfProperties>,\n): Promise<Response>;\ndeclare const self: ServiceWorkerGlobalScope;\n/**\n * The Web Crypto API provides a set of low-level functions for common cryptographic tasks.\n * The Workers runtime implements the full surface of this API, but with some differences in\n * the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms)\n * compared to those implemented in most browsers.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/)\n */\ndeclare const crypto: Crypto;\n/**\n * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)\n */\ndeclare const caches: CacheStorage;\ndeclare const scheduler: Scheduler;\n/**\n * The Workers runtime supports a subset of the Performance API, used to measure timing and performance,\n * as well as timing of subrequests and other operations.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/)\n */\ndeclare const performance: Performance;\ndeclare const Cloudflare: Cloudflare;\ndeclare const origin: string;\ndeclare const navigator: Navigator;\ninterface TestController {}\ninterface ExecutionContext<Props = unknown> {\n  waitUntil(promise: Promise<any>): void;\n  passThroughOnException(): void;\n  readonly exports: Cloudflare.Exports;\n  readonly props: Props;\n}\ntype ExportedHandlerFetchHandler<\n  Env = unknown,\n  CfHostMetadata = unknown,\n  Props = unknown,\n> = (\n  request: Request<CfHostMetadata, IncomingRequestCfProperties<CfHostMetadata>>,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => Response | Promise<Response>;\ntype ExportedHandlerTailHandler<Env = unknown, Props = unknown> = (\n  events: TraceItem[],\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\ntype ExportedHandlerTraceHandler<Env = unknown, Props = unknown> = (\n  traces: TraceItem[],\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\ntype ExportedHandlerTailStreamHandler<Env = unknown, Props = unknown> = (\n  event: TailStream.TailEvent<TailStream.Onset>,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => TailStream.TailEventHandlerType | Promise<TailStream.TailEventHandlerType>;\ntype ExportedHandlerScheduledHandler<Env = unknown, Props = unknown> = (\n  controller: ScheduledController,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\ntype ExportedHandlerQueueHandler<\n  Env = unknown,\n  Message = unknown,\n  Props = unknown,\n> = (\n  batch: MessageBatch<Message>,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\ntype ExportedHandlerTestHandler<Env = unknown, Props = unknown> = (\n  controller: TestController,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\ninterface ExportedHandler<\n  Env = unknown,\n  QueueHandlerMessage = unknown,\n  CfHostMetadata = unknown,\n  Props = unknown,\n> {\n  fetch?: ExportedHandlerFetchHandler<Env, CfHostMetadata, Props>;\n  tail?: ExportedHandlerTailHandler<Env, Props>;\n  trace?: ExportedHandlerTraceHandler<Env, Props>;\n  tailStream?: ExportedHandlerTailStreamHandler<Env, Props>;\n  scheduled?: ExportedHandlerScheduledHandler<Env, Props>;\n  test?: ExportedHandlerTestHandler<Env, Props>;\n  email?: EmailExportedHandler<Env, Props>;\n  queue?: ExportedHandlerQueueHandler<Env, QueueHandlerMessage, Props>;\n}\ninterface StructuredSerializeOptions {\n  transfer?: any[];\n}\ndeclare abstract class Navigator {\n  sendBeacon(url: string, body?: BodyInit): boolean;\n  readonly userAgent: string;\n  readonly hardwareConcurrency: number;\n  readonly language: string;\n  readonly languages: string[];\n}\ninterface AlarmInvocationInfo {\n  readonly isRetry: boolean;\n  readonly retryCount: number;\n}\ninterface Cloudflare {\n  readonly compatibilityFlags: Record<string, boolean>;\n}\ninterface DurableObject {\n  fetch(request: Request): Response | Promise<Response>;\n  alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise<void>;\n  webSocketMessage?(\n    ws: WebSocket,\n    message: string | ArrayBuffer,\n  ): void | Promise<void>;\n  webSocketClose?(\n    ws: WebSocket,\n    code: number,\n    reason: string,\n    wasClean: boolean,\n  ): void | Promise<void>;\n  webSocketError?(ws: WebSocket, error: unknown): void | Promise<void>;\n}\ntype DurableObjectStub<\n  T extends Rpc.DurableObjectBranded | undefined = undefined,\n> = Fetcher<\n  T,\n  \"alarm\" | \"webSocketMessage\" | \"webSocketClose\" | \"webSocketError\"\n> & {\n  readonly id: DurableObjectId;\n  readonly name?: string;\n};\ninterface DurableObjectId {\n  toString(): string;\n  equals(other: DurableObjectId): boolean;\n  readonly name?: string;\n  readonly jurisdiction?: string;\n}\ndeclare abstract class DurableObjectNamespace<\n  T extends Rpc.DurableObjectBranded | undefined = undefined,\n> {\n  newUniqueId(\n    options?: DurableObjectNamespaceNewUniqueIdOptions,\n  ): DurableObjectId;\n  idFromName(name: string): DurableObjectId;\n  idFromString(id: string): DurableObjectId;\n  get(\n    id: DurableObjectId,\n    options?: DurableObjectNamespaceGetDurableObjectOptions,\n  ): DurableObjectStub<T>;\n  getByName(\n    name: string,\n    options?: DurableObjectNamespaceGetDurableObjectOptions,\n  ): DurableObjectStub<T>;\n  jurisdiction(\n    jurisdiction: DurableObjectJurisdiction,\n  ): DurableObjectNamespace<T>;\n}\ntype DurableObjectJurisdiction = \"eu\" | \"fedramp\" | \"fedramp-high\";\ninterface DurableObjectNamespaceNewUniqueIdOptions {\n  jurisdiction?: DurableObjectJurisdiction;\n}\ntype DurableObjectLocationHint =\n  | \"wnam\"\n  | \"enam\"\n  | \"sam\"\n  | \"weur\"\n  | \"eeur\"\n  | \"apac\"\n  | \"oc\"\n  | \"afr\"\n  | \"me\";\ntype DurableObjectRoutingMode = \"primary-only\";\ninterface DurableObjectNamespaceGetDurableObjectOptions {\n  locationHint?: DurableObjectLocationHint;\n  routingMode?: DurableObjectRoutingMode;\n}\ninterface DurableObjectClass<\n  _T extends Rpc.DurableObjectBranded | undefined = undefined,\n> {}\ninterface DurableObjectState<Props = unknown> {\n  waitUntil(promise: Promise<any>): void;\n  readonly exports: Cloudflare.Exports;\n  readonly props: Props;\n  readonly id: DurableObjectId;\n  readonly storage: DurableObjectStorage;\n  container?: Container;\n  blockConcurrencyWhile<T>(callback: () => Promise<T>): Promise<T>;\n  acceptWebSocket(ws: WebSocket, tags?: string[]): void;\n  getWebSockets(tag?: string): WebSocket[];\n  setWebSocketAutoResponse(maybeReqResp?: WebSocketRequestResponsePair): void;\n  getWebSocketAutoResponse(): WebSocketRequestResponsePair | null;\n  getWebSocketAutoResponseTimestamp(ws: WebSocket): Date | null;\n  setHibernatableWebSocketEventTimeout(timeoutMs?: number): void;\n  getHibernatableWebSocketEventTimeout(): number | null;\n  getTags(ws: WebSocket): string[];\n  abort(reason?: string): void;\n}\ninterface DurableObjectTransaction {\n  get<T = unknown>(\n    key: string,\n    options?: DurableObjectGetOptions,\n  ): Promise<T | undefined>;\n  get<T = unknown>(\n    keys: string[],\n    options?: DurableObjectGetOptions,\n  ): Promise<Map<string, T>>;\n  list<T = unknown>(\n    options?: DurableObjectListOptions,\n  ): Promise<Map<string, T>>;\n  put<T>(\n    key: string,\n    value: T,\n    options?: DurableObjectPutOptions,\n  ): Promise<void>;\n  put<T>(\n    entries: Record<string, T>,\n    options?: DurableObjectPutOptions,\n  ): Promise<void>;\n  delete(key: string, options?: DurableObjectPutOptions): Promise<boolean>;\n  delete(keys: string[], options?: DurableObjectPutOptions): Promise<number>;\n  rollback(): void;\n  getAlarm(options?: DurableObjectGetAlarmOptions): Promise<number | null>;\n  setAlarm(\n    scheduledTime: number | Date,\n    options?: DurableObjectSetAlarmOptions,\n  ): Promise<void>;\n  deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise<void>;\n}\ninterface DurableObjectStorage {\n  get<T = unknown>(\n    key: string,\n    options?: DurableObjectGetOptions,\n  ): Promise<T | undefined>;\n  get<T = unknown>(\n    keys: string[],\n    options?: DurableObjectGetOptions,\n  ): Promise<Map<string, T>>;\n  list<T = unknown>(\n    options?: DurableObjectListOptions,\n  ): Promise<Map<string, T>>;\n  put<T>(\n    key: string,\n    value: T,\n    options?: DurableObjectPutOptions,\n  ): Promise<void>;\n  put<T>(\n    entries: Record<string, T>,\n    options?: DurableObjectPutOptions,\n  ): Promise<void>;\n  delete(key: string, options?: DurableObjectPutOptions): Promise<boolean>;\n  delete(keys: string[], options?: DurableObjectPutOptions): Promise<number>;\n  deleteAll(options?: DurableObjectPutOptions): Promise<void>;\n  transaction<T>(\n    closure: (txn: DurableObjectTransaction) => Promise<T>,\n  ): Promise<T>;\n  getAlarm(options?: DurableObjectGetAlarmOptions): Promise<number | null>;\n  setAlarm(\n    scheduledTime: number | Date,\n    options?: DurableObjectSetAlarmOptions,\n  ): Promise<void>;\n  deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise<void>;\n  sync(): Promise<void>;\n  sql: SqlStorage;\n  kv: SyncKvStorage;\n  transactionSync<T>(closure: () => T): T;\n  getCurrentBookmark(): Promise<string>;\n  getBookmarkForTime(timestamp: number | Date): Promise<string>;\n  onNextSessionRestoreBookmark(bookmark: string): Promise<string>;\n}\ninterface DurableObjectListOptions {\n  start?: string;\n  startAfter?: string;\n  end?: string;\n  prefix?: string;\n  reverse?: boolean;\n  limit?: number;\n  allowConcurrency?: boolean;\n  noCache?: boolean;\n}\ninterface DurableObjectGetOptions {\n  allowConcurrency?: boolean;\n  noCache?: boolean;\n}\ninterface DurableObjectGetAlarmOptions {\n  allowConcurrency?: boolean;\n}\ninterface DurableObjectPutOptions {\n  allowConcurrency?: boolean;\n  allowUnconfirmed?: boolean;\n  noCache?: boolean;\n}\ninterface DurableObjectSetAlarmOptions {\n  allowConcurrency?: boolean;\n  allowUnconfirmed?: boolean;\n}\ndeclare class WebSocketRequestResponsePair {\n  constructor(request: string, response: string);\n  get request(): string;\n  get response(): string;\n}\ninterface AnalyticsEngineDataset {\n  writeDataPoint(event?: AnalyticsEngineDataPoint): void;\n}\ninterface AnalyticsEngineDataPoint {\n  indexes?: ((ArrayBuffer | string) | null)[];\n  doubles?: number[];\n  blobs?: ((ArrayBuffer | string) | null)[];\n}\n/**\n * The **`Event`** interface represents an event which takes place on an `EventTarget`.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event)\n */\ndeclare class Event {\n  constructor(type: string, init?: EventInit);\n  /**\n   * The **`type`** read-only property of the Event interface returns a string containing the event's type.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/type)\n   */\n  get type(): string;\n  /**\n   * The **`eventPhase`** read-only property of the being evaluated.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/eventPhase)\n   */\n  get eventPhase(): number;\n  /**\n   * The read-only **`composed`** property of the or not the event will propagate across the shadow DOM boundary into the standard DOM.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composed)\n   */\n  get composed(): boolean;\n  /**\n   * The **`bubbles`** read-only property of the Event interface indicates whether the event bubbles up through the DOM tree or not.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/bubbles)\n   */\n  get bubbles(): boolean;\n  /**\n   * The **`cancelable`** read-only property of the Event interface indicates whether the event can be canceled, and therefore prevented as if the event never happened.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelable)\n   */\n  get cancelable(): boolean;\n  /**\n   * The **`defaultPrevented`** read-only property of the Event interface returns a boolean value indicating whether or not the call to Event.preventDefault() canceled the event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/defaultPrevented)\n   */\n  get defaultPrevented(): boolean;\n  /**\n   * The Event property **`returnValue`** indicates whether the default action for this event has been prevented or not.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/returnValue)\n   */\n  get returnValue(): boolean;\n  /**\n   * The **`currentTarget`** read-only property of the Event interface identifies the element to which the event handler has been attached.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/currentTarget)\n   */\n  get currentTarget(): EventTarget | undefined;\n  /**\n   * The read-only **`target`** property of the dispatched.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/target)\n   */\n  get target(): EventTarget | undefined;\n  /**\n   * The deprecated **`Event.srcElement`** is an alias for the Event.target property.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/srcElement)\n   */\n  get srcElement(): EventTarget | undefined;\n  /**\n   * The **`timeStamp`** read-only property of the Event interface returns the time (in milliseconds) at which the event was created.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/timeStamp)\n   */\n  get timeStamp(): number;\n  /**\n   * The **`isTrusted`** read-only property of the when the event was generated by the user agent (including via user actions and programmatic methods such as HTMLElement.focus()), and `false` when the event was dispatched via The only exception is the `click` event, which initializes the `isTrusted` property to `false` in user agents.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/isTrusted)\n   */\n  get isTrusted(): boolean;\n  /**\n   * The **`cancelBubble`** property of the Event interface is deprecated.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble)\n   */\n  get cancelBubble(): boolean;\n  /**\n   * The **`cancelBubble`** property of the Event interface is deprecated.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble)\n   */\n  set cancelBubble(value: boolean);\n  /**\n   * The **`stopImmediatePropagation()`** method of the If several listeners are attached to the same element for the same event type, they are called in the order in which they were added.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopImmediatePropagation)\n   */\n  stopImmediatePropagation(): void;\n  /**\n   * The **`preventDefault()`** method of the Event interface tells the user agent that if the event does not get explicitly handled, its default action should not be taken as it normally would be.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/preventDefault)\n   */\n  preventDefault(): void;\n  /**\n   * The **`stopPropagation()`** method of the Event interface prevents further propagation of the current event in the capturing and bubbling phases.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopPropagation)\n   */\n  stopPropagation(): void;\n  /**\n   * The **`composedPath()`** method of the Event interface returns the event's path which is an array of the objects on which listeners will be invoked.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composedPath)\n   */\n  composedPath(): EventTarget[];\n  static readonly NONE: number;\n  static readonly CAPTURING_PHASE: number;\n  static readonly AT_TARGET: number;\n  static readonly BUBBLING_PHASE: number;\n}\ninterface EventInit {\n  bubbles?: boolean;\n  cancelable?: boolean;\n  composed?: boolean;\n}\ntype EventListener<EventType extends Event = Event> = (\n  event: EventType,\n) => void;\ninterface EventListenerObject<EventType extends Event = Event> {\n  handleEvent(event: EventType): void;\n}\ntype EventListenerOrEventListenerObject<EventType extends Event = Event> =\n  | EventListener<EventType>\n  | EventListenerObject<EventType>;\n/**\n * The **`EventTarget`** interface is implemented by objects that can receive events and may have listeners for them.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget)\n */\ndeclare class EventTarget<\n  EventMap extends Record<string, Event> = Record<string, Event>,\n> {\n  constructor();\n  /**\n   * The **`addEventListener()`** method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)\n   */\n  addEventListener<Type extends keyof EventMap>(\n    type: Type,\n    handler: EventListenerOrEventListenerObject<EventMap[Type]>,\n    options?: EventTargetAddEventListenerOptions | boolean,\n  ): void;\n  /**\n   * The **`removeEventListener()`** method of the EventTarget interface removes an event listener previously registered with EventTarget.addEventListener() from the target.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener)\n   */\n  removeEventListener<Type extends keyof EventMap>(\n    type: Type,\n    handler: EventListenerOrEventListenerObject<EventMap[Type]>,\n    options?: EventTargetEventListenerOptions | boolean,\n  ): void;\n  /**\n   * The **`dispatchEvent()`** method of the EventTarget sends an Event to the object, (synchronously) invoking the affected event listeners in the appropriate order.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent)\n   */\n  dispatchEvent(event: EventMap[keyof EventMap]): boolean;\n}\ninterface EventTargetEventListenerOptions {\n  capture?: boolean;\n}\ninterface EventTargetAddEventListenerOptions {\n  capture?: boolean;\n  passive?: boolean;\n  once?: boolean;\n  signal?: AbortSignal;\n}\ninterface EventTargetHandlerObject {\n  handleEvent: (event: Event) => any | undefined;\n}\n/**\n * The **`AbortController`** interface represents a controller object that allows you to abort one or more Web requests as and when desired.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController)\n */\ndeclare class AbortController {\n  constructor();\n  /**\n   * The **`signal`** read-only property of the AbortController interface returns an AbortSignal object instance, which can be used to communicate with/abort an asynchronous operation as desired.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/signal)\n   */\n  get signal(): AbortSignal;\n  /**\n   * The **`abort()`** method of the AbortController interface aborts an asynchronous operation before it has completed.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/abort)\n   */\n  abort(reason?: any): void;\n}\n/**\n * The **`AbortSignal`** interface represents a signal object that allows you to communicate with an asynchronous operation (such as a fetch request) and abort it if required via an AbortController object.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal)\n */\ndeclare abstract class AbortSignal extends EventTarget {\n  /**\n   * The **`AbortSignal.abort()`** static method returns an AbortSignal that is already set as aborted (and which does not trigger an AbortSignal/abort_event event).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_static)\n   */\n  static abort(reason?: any): AbortSignal;\n  /**\n   * The **`AbortSignal.timeout()`** static method returns an AbortSignal that will automatically abort after a specified time.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/timeout_static)\n   */\n  static timeout(delay: number): AbortSignal;\n  /**\n   * The **`AbortSignal.any()`** static method takes an iterable of abort signals and returns an AbortSignal.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/any_static)\n   */\n  static any(signals: AbortSignal[]): AbortSignal;\n  /**\n   * The **`aborted`** read-only property returns a value that indicates whether the asynchronous operations the signal is communicating with are aborted (`true`) or not (`false`).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/aborted)\n   */\n  get aborted(): boolean;\n  /**\n   * The **`reason`** read-only property returns a JavaScript value that indicates the abort reason.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/reason)\n   */\n  get reason(): any;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */\n  get onabort(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */\n  set onabort(value: any | null);\n  /**\n   * The **`throwIfAborted()`** method throws the signal's abort AbortSignal.reason if the signal has been aborted; otherwise it does nothing.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/throwIfAborted)\n   */\n  throwIfAborted(): void;\n}\ninterface Scheduler {\n  wait(delay: number, maybeOptions?: SchedulerWaitOptions): Promise<void>;\n}\ninterface SchedulerWaitOptions {\n  signal?: AbortSignal;\n}\n/**\n * The **`ExtendableEvent`** interface extends the lifetime of the `install` and `activate` events dispatched on the global scope as part of the service worker lifecycle.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent)\n */\ndeclare abstract class ExtendableEvent extends Event {\n  /**\n   * The **`ExtendableEvent.waitUntil()`** method tells the event dispatcher that work is ongoing.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent/waitUntil)\n   */\n  waitUntil(promise: Promise<any>): void;\n}\n/**\n * The **`CustomEvent`** interface represents events initialized by an application for any purpose.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent)\n */\ndeclare class CustomEvent<T = any> extends Event {\n  constructor(type: string, init?: CustomEventCustomEventInit);\n  /**\n   * The read-only **`detail`** property of the CustomEvent interface returns any data passed when initializing the event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent/detail)\n   */\n  get detail(): T;\n}\ninterface CustomEventCustomEventInit {\n  bubbles?: boolean;\n  cancelable?: boolean;\n  composed?: boolean;\n  detail?: any;\n}\n/**\n * The **`Blob`** interface represents a blob, which is a file-like object of immutable, raw data; they can be read as text or binary data, or converted into a ReadableStream so its methods can be used for processing the data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob)\n */\ndeclare class Blob {\n  constructor(\n    type?: ((ArrayBuffer | ArrayBufferView) | string | Blob)[],\n    options?: BlobOptions,\n  );\n  /**\n   * The **`size`** read-only property of the Blob interface returns the size of the Blob or File in bytes.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size)\n   */\n  get size(): number;\n  /**\n   * The **`type`** read-only property of the Blob interface returns the MIME type of the file.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type)\n   */\n  get type(): string;\n  /**\n   * The **`slice()`** method of the Blob interface creates and returns a new `Blob` object which contains data from a subset of the blob on which it's called.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice)\n   */\n  slice(start?: number, end?: number, type?: string): Blob;\n  /**\n   * The **`arrayBuffer()`** method of the Blob interface returns a Promise that resolves with the contents of the blob as binary data contained in an ArrayBuffer.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/arrayBuffer)\n   */\n  arrayBuffer(): Promise<ArrayBuffer>;\n  /**\n   * The **`bytes()`** method of the Blob interface returns a Promise that resolves with a Uint8Array containing the contents of the blob as an array of bytes.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/bytes)\n   */\n  bytes(): Promise<Uint8Array>;\n  /**\n   * The **`text()`** method of the string containing the contents of the blob, interpreted as UTF-8.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text)\n   */\n  text(): Promise<string>;\n  /**\n   * The **`stream()`** method of the Blob interface returns a ReadableStream which upon reading returns the data contained within the `Blob`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/stream)\n   */\n  stream(): ReadableStream;\n}\ninterface BlobOptions {\n  type?: string;\n}\n/**\n * The **`File`** interface provides information about files and allows JavaScript in a web page to access their content.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File)\n */\ndeclare class File extends Blob {\n  constructor(\n    bits: ((ArrayBuffer | ArrayBufferView) | string | Blob)[] | undefined,\n    name: string,\n    options?: FileOptions,\n  );\n  /**\n   * The **`name`** read-only property of the File interface returns the name of the file represented by a File object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name)\n   */\n  get name(): string;\n  /**\n   * The **`lastModified`** read-only property of the File interface provides the last modified date of the file as the number of milliseconds since the Unix epoch (January 1, 1970 at midnight).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified)\n   */\n  get lastModified(): number;\n}\ninterface FileOptions {\n  type?: string;\n  lastModified?: number;\n}\n/**\n * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)\n */\ndeclare abstract class CacheStorage {\n  /**\n   * The **`open()`** method of the the Cache object matching the `cacheName`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CacheStorage/open)\n   */\n  open(cacheName: string): Promise<Cache>;\n  readonly default: Cache;\n}\n/**\n * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)\n */\ndeclare abstract class Cache {\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#delete) */\n  delete(\n    request: RequestInfo | URL,\n    options?: CacheQueryOptions,\n  ): Promise<boolean>;\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#match) */\n  match(\n    request: RequestInfo | URL,\n    options?: CacheQueryOptions,\n  ): Promise<Response | undefined>;\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#put) */\n  put(request: RequestInfo | URL, response: Response): Promise<void>;\n}\ninterface CacheQueryOptions {\n  ignoreMethod?: boolean;\n}\n/**\n * The Web Crypto API provides a set of low-level functions for common cryptographic tasks.\n * The Workers runtime implements the full surface of this API, but with some differences in\n * the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms)\n * compared to those implemented in most browsers.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/)\n */\ndeclare abstract class Crypto {\n  /**\n   * The **`Crypto.subtle`** read-only property returns a cryptographic operations.\n   * Available only in secure contexts.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/subtle)\n   */\n  get subtle(): SubtleCrypto;\n  /**\n   * The **`Crypto.getRandomValues()`** method lets you get cryptographically strong random values.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/getRandomValues)\n   */\n  getRandomValues<\n    T extends\n      | Int8Array\n      | Uint8Array\n      | Int16Array\n      | Uint16Array\n      | Int32Array\n      | Uint32Array\n      | BigInt64Array\n      | BigUint64Array,\n  >(buffer: T): T;\n  /**\n   * The **`randomUUID()`** method of the Crypto interface is used to generate a v4 UUID using a cryptographically secure random number generator.\n   * Available only in secure contexts.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/randomUUID)\n   */\n  randomUUID(): string;\n  DigestStream: typeof DigestStream;\n}\n/**\n * The **`SubtleCrypto`** interface of the Web Crypto API provides a number of low-level cryptographic functions.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto)\n */\ndeclare abstract class SubtleCrypto {\n  /**\n   * The **`encrypt()`** method of the SubtleCrypto interface encrypts data.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/encrypt)\n   */\n  encrypt(\n    algorithm: string | SubtleCryptoEncryptAlgorithm,\n    key: CryptoKey,\n    plainText: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`decrypt()`** method of the SubtleCrypto interface decrypts some encrypted data.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/decrypt)\n   */\n  decrypt(\n    algorithm: string | SubtleCryptoEncryptAlgorithm,\n    key: CryptoKey,\n    cipherText: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`sign()`** method of the SubtleCrypto interface generates a digital signature.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/sign)\n   */\n  sign(\n    algorithm: string | SubtleCryptoSignAlgorithm,\n    key: CryptoKey,\n    data: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`verify()`** method of the SubtleCrypto interface verifies a digital signature.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/verify)\n   */\n  verify(\n    algorithm: string | SubtleCryptoSignAlgorithm,\n    key: CryptoKey,\n    signature: ArrayBuffer | ArrayBufferView,\n    data: ArrayBuffer | ArrayBufferView,\n  ): Promise<boolean>;\n  /**\n   * The **`digest()`** method of the SubtleCrypto interface generates a _digest_ of the given data, using the specified hash function.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/digest)\n   */\n  digest(\n    algorithm: string | SubtleCryptoHashAlgorithm,\n    data: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`generateKey()`** method of the SubtleCrypto interface is used to generate a new key (for symmetric algorithms) or key pair (for public-key algorithms).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/generateKey)\n   */\n  generateKey(\n    algorithm: string | SubtleCryptoGenerateKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey | CryptoKeyPair>;\n  /**\n   * The **`deriveKey()`** method of the SubtleCrypto interface can be used to derive a secret key from a master key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveKey)\n   */\n  deriveKey(\n    algorithm: string | SubtleCryptoDeriveKeyAlgorithm,\n    baseKey: CryptoKey,\n    derivedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey>;\n  /**\n   * The **`deriveBits()`** method of the key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveBits)\n   */\n  deriveBits(\n    algorithm: string | SubtleCryptoDeriveKeyAlgorithm,\n    baseKey: CryptoKey,\n    length?: number | null,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`importKey()`** method of the SubtleCrypto interface imports a key: that is, it takes as input a key in an external, portable format and gives you a CryptoKey object that you can use in the Web Crypto API.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/importKey)\n   */\n  importKey(\n    format: string,\n    keyData: (ArrayBuffer | ArrayBufferView) | JsonWebKey,\n    algorithm: string | SubtleCryptoImportKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey>;\n  /**\n   * The **`exportKey()`** method of the SubtleCrypto interface exports a key: that is, it takes as input a CryptoKey object and gives you the key in an external, portable format.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/exportKey)\n   */\n  exportKey(format: string, key: CryptoKey): Promise<ArrayBuffer | JsonWebKey>;\n  /**\n   * The **`wrapKey()`** method of the SubtleCrypto interface 'wraps' a key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/wrapKey)\n   */\n  wrapKey(\n    format: string,\n    key: CryptoKey,\n    wrappingKey: CryptoKey,\n    wrapAlgorithm: string | SubtleCryptoEncryptAlgorithm,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`unwrapKey()`** method of the SubtleCrypto interface 'unwraps' a key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/unwrapKey)\n   */\n  unwrapKey(\n    format: string,\n    wrappedKey: ArrayBuffer | ArrayBufferView,\n    unwrappingKey: CryptoKey,\n    unwrapAlgorithm: string | SubtleCryptoEncryptAlgorithm,\n    unwrappedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey>;\n  timingSafeEqual(\n    a: ArrayBuffer | ArrayBufferView,\n    b: ArrayBuffer | ArrayBufferView,\n  ): boolean;\n}\n/**\n * The **`CryptoKey`** interface of the Web Crypto API represents a cryptographic key obtained from one of the SubtleCrypto methods SubtleCrypto.generateKey, SubtleCrypto.deriveKey, SubtleCrypto.importKey, or SubtleCrypto.unwrapKey.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey)\n */\ndeclare abstract class CryptoKey {\n  /**\n   * The read-only **`type`** property of the CryptoKey interface indicates which kind of key is represented by the object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/type)\n   */\n  readonly type: string;\n  /**\n   * The read-only **`extractable`** property of the CryptoKey interface indicates whether or not the key may be extracted using `SubtleCrypto.exportKey()` or `SubtleCrypto.wrapKey()`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/extractable)\n   */\n  readonly extractable: boolean;\n  /**\n   * The read-only **`algorithm`** property of the CryptoKey interface returns an object describing the algorithm for which this key can be used, and any associated extra parameters.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/algorithm)\n   */\n  readonly algorithm:\n    | CryptoKeyKeyAlgorithm\n    | CryptoKeyAesKeyAlgorithm\n    | CryptoKeyHmacKeyAlgorithm\n    | CryptoKeyRsaKeyAlgorithm\n    | CryptoKeyEllipticKeyAlgorithm\n    | CryptoKeyArbitraryKeyAlgorithm;\n  /**\n   * The read-only **`usages`** property of the CryptoKey interface indicates what can be done with the key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/usages)\n   */\n  readonly usages: string[];\n}\ninterface CryptoKeyPair {\n  publicKey: CryptoKey;\n  privateKey: CryptoKey;\n}\ninterface JsonWebKey {\n  kty: string;\n  use?: string;\n  key_ops?: string[];\n  alg?: string;\n  ext?: boolean;\n  crv?: string;\n  x?: string;\n  y?: string;\n  d?: string;\n  n?: string;\n  e?: string;\n  p?: string;\n  q?: string;\n  dp?: string;\n  dq?: string;\n  qi?: string;\n  oth?: RsaOtherPrimesInfo[];\n  k?: string;\n}\ninterface RsaOtherPrimesInfo {\n  r?: string;\n  d?: string;\n  t?: string;\n}\ninterface SubtleCryptoDeriveKeyAlgorithm {\n  name: string;\n  salt?: ArrayBuffer | ArrayBufferView;\n  iterations?: number;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  $public?: CryptoKey;\n  info?: ArrayBuffer | ArrayBufferView;\n}\ninterface SubtleCryptoEncryptAlgorithm {\n  name: string;\n  iv?: ArrayBuffer | ArrayBufferView;\n  additionalData?: ArrayBuffer | ArrayBufferView;\n  tagLength?: number;\n  counter?: ArrayBuffer | ArrayBufferView;\n  length?: number;\n  label?: ArrayBuffer | ArrayBufferView;\n}\ninterface SubtleCryptoGenerateKeyAlgorithm {\n  name: string;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  modulusLength?: number;\n  publicExponent?: ArrayBuffer | ArrayBufferView;\n  length?: number;\n  namedCurve?: string;\n}\ninterface SubtleCryptoHashAlgorithm {\n  name: string;\n}\ninterface SubtleCryptoImportKeyAlgorithm {\n  name: string;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  length?: number;\n  namedCurve?: string;\n  compressed?: boolean;\n}\ninterface SubtleCryptoSignAlgorithm {\n  name: string;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  dataLength?: number;\n  saltLength?: number;\n}\ninterface CryptoKeyKeyAlgorithm {\n  name: string;\n}\ninterface CryptoKeyAesKeyAlgorithm {\n  name: string;\n  length: number;\n}\ninterface CryptoKeyHmacKeyAlgorithm {\n  name: string;\n  hash: CryptoKeyKeyAlgorithm;\n  length: number;\n}\ninterface CryptoKeyRsaKeyAlgorithm {\n  name: string;\n  modulusLength: number;\n  publicExponent: ArrayBuffer | ArrayBufferView;\n  hash?: CryptoKeyKeyAlgorithm;\n}\ninterface CryptoKeyEllipticKeyAlgorithm {\n  name: string;\n  namedCurve: string;\n}\ninterface CryptoKeyArbitraryKeyAlgorithm {\n  name: string;\n  hash?: CryptoKeyKeyAlgorithm;\n  namedCurve?: string;\n  length?: number;\n}\ndeclare class DigestStream extends WritableStream<\n  ArrayBuffer | ArrayBufferView\n> {\n  constructor(algorithm: string | SubtleCryptoHashAlgorithm);\n  readonly digest: Promise<ArrayBuffer>;\n  get bytesWritten(): number | bigint;\n}\n/**\n * The **`TextDecoder`** interface represents a decoder for a specific text encoding, such as `UTF-8`, `ISO-8859-2`, `KOI8-R`, `GBK`, etc.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder)\n */\ndeclare class TextDecoder {\n  constructor(label?: string, options?: TextDecoderConstructorOptions);\n  /**\n   * The **`TextDecoder.decode()`** method returns a string containing text decoded from the buffer passed as a parameter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder/decode)\n   */\n  decode(\n    input?: ArrayBuffer | ArrayBufferView,\n    options?: TextDecoderDecodeOptions,\n  ): string;\n  get encoding(): string;\n  get fatal(): boolean;\n  get ignoreBOM(): boolean;\n}\n/**\n * The **`TextEncoder`** interface takes a stream of code points as input and emits a stream of UTF-8 bytes.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder)\n */\ndeclare class TextEncoder {\n  constructor();\n  /**\n   * The **`TextEncoder.encode()`** method takes a string as input, and returns a Global_Objects/Uint8Array containing the text given in parameters encoded with the specific method for that TextEncoder object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encode)\n   */\n  encode(input?: string): Uint8Array;\n  /**\n   * The **`TextEncoder.encodeInto()`** method takes a string to encode and a destination Uint8Array to put resulting UTF-8 encoded text into, and returns a dictionary object indicating the progress of the encoding.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encodeInto)\n   */\n  encodeInto(input: string, buffer: Uint8Array): TextEncoderEncodeIntoResult;\n  get encoding(): string;\n}\ninterface TextDecoderConstructorOptions {\n  fatal: boolean;\n  ignoreBOM: boolean;\n}\ninterface TextDecoderDecodeOptions {\n  stream: boolean;\n}\ninterface TextEncoderEncodeIntoResult {\n  read: number;\n  written: number;\n}\n/**\n * The **`ErrorEvent`** interface represents events providing information related to errors in scripts or in files.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent)\n */\ndeclare class ErrorEvent extends Event {\n  constructor(type: string, init?: ErrorEventErrorEventInit);\n  /**\n   * The **`filename`** read-only property of the ErrorEvent interface returns a string containing the name of the script file in which the error occurred.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/filename)\n   */\n  get filename(): string;\n  /**\n   * The **`message`** read-only property of the ErrorEvent interface returns a string containing a human-readable error message describing the problem.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/message)\n   */\n  get message(): string;\n  /**\n   * The **`lineno`** read-only property of the ErrorEvent interface returns an integer containing the line number of the script file on which the error occurred.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/lineno)\n   */\n  get lineno(): number;\n  /**\n   * The **`colno`** read-only property of the ErrorEvent interface returns an integer containing the column number of the script file on which the error occurred.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/colno)\n   */\n  get colno(): number;\n  /**\n   * The **`error`** read-only property of the ErrorEvent interface returns a JavaScript value, such as an Error or DOMException, representing the error associated with this event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/error)\n   */\n  get error(): any;\n}\ninterface ErrorEventErrorEventInit {\n  message?: string;\n  filename?: string;\n  lineno?: number;\n  colno?: number;\n  error?: any;\n}\n/**\n * The **`MessageEvent`** interface represents a message received by a target object.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent)\n */\ndeclare class MessageEvent extends Event {\n  constructor(type: string, initializer: MessageEventInit);\n  /**\n   * The **`data`** read-only property of the The data sent by the message emitter; this can be any data type, depending on what originated this event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/data)\n   */\n  readonly data: any;\n  /**\n   * The **`origin`** read-only property of the origin of the message emitter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/origin)\n   */\n  readonly origin: string | null;\n  /**\n   * The **`lastEventId`** read-only property of the unique ID for the event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/lastEventId)\n   */\n  readonly lastEventId: string;\n  /**\n   * The **`source`** read-only property of the a WindowProxy, MessagePort, or a `MessageEventSource` (which can be a WindowProxy, message emitter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/source)\n   */\n  readonly source: MessagePort | null;\n  /**\n   * The **`ports`** read-only property of the containing all MessagePort objects sent with the message, in order.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/ports)\n   */\n  readonly ports: MessagePort[];\n}\ninterface MessageEventInit {\n  data: ArrayBuffer | string;\n}\n/**\n * The **`PromiseRejectionEvent`** interface represents events which are sent to the global script context when JavaScript Promises are rejected.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent)\n */\ndeclare abstract class PromiseRejectionEvent extends Event {\n  /**\n   * The PromiseRejectionEvent interface's **`promise`** read-only property indicates the JavaScript rejected.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/promise)\n   */\n  readonly promise: Promise<any>;\n  /**\n   * The PromiseRejectionEvent **`reason`** read-only property is any JavaScript value or Object which provides the reason passed into Promise.reject().\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/reason)\n   */\n  readonly reason: any;\n}\n/**\n * The **`FormData`** interface provides a way to construct a set of key/value pairs representing form fields and their values, which can be sent using the Window/fetch, XMLHttpRequest.send() or navigator.sendBeacon() methods.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData)\n */\ndeclare class FormData {\n  constructor();\n  /**\n   * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append)\n   */\n  append(name: string, value: string | Blob): void;\n  /**\n   * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append)\n   */\n  append(name: string, value: string): void;\n  /**\n   * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append)\n   */\n  append(name: string, value: Blob, filename?: string): void;\n  /**\n   * The **`delete()`** method of the FormData interface deletes a key and its value(s) from a `FormData` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/delete)\n   */\n  delete(name: string): void;\n  /**\n   * The **`get()`** method of the FormData interface returns the first value associated with a given key from within a `FormData` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/get)\n   */\n  get(name: string): (File | string) | null;\n  /**\n   * The **`getAll()`** method of the FormData interface returns all the values associated with a given key from within a `FormData` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/getAll)\n   */\n  getAll(name: string): (File | string)[];\n  /**\n   * The **`has()`** method of the FormData interface returns whether a `FormData` object contains a certain key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/has)\n   */\n  has(name: string): boolean;\n  /**\n   * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set)\n   */\n  set(name: string, value: string | Blob): void;\n  /**\n   * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set)\n   */\n  set(name: string, value: string): void;\n  /**\n   * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set)\n   */\n  set(name: string, value: Blob, filename?: string): void;\n  /* Returns an array of key, value pairs for every entry in the list. */\n  entries(): IterableIterator<[key: string, value: File | string]>;\n  /* Returns a list of keys in the list. */\n  keys(): IterableIterator<string>;\n  /* Returns a list of values in the list. */\n  values(): IterableIterator<File | string>;\n  forEach<This = unknown>(\n    callback: (\n      this: This,\n      value: File | string,\n      key: string,\n      parent: FormData,\n    ) => void,\n    thisArg?: This,\n  ): void;\n  [Symbol.iterator](): IterableIterator<[key: string, value: File | string]>;\n}\ninterface ContentOptions {\n  html?: boolean;\n}\ndeclare class HTMLRewriter {\n  constructor();\n  on(\n    selector: string,\n    handlers: HTMLRewriterElementContentHandlers,\n  ): HTMLRewriter;\n  onDocument(handlers: HTMLRewriterDocumentContentHandlers): HTMLRewriter;\n  transform(response: Response): Response;\n}\ninterface HTMLRewriterElementContentHandlers {\n  element?(element: Element): void | Promise<void>;\n  comments?(comment: Comment): void | Promise<void>;\n  text?(element: Text): void | Promise<void>;\n}\ninterface HTMLRewriterDocumentContentHandlers {\n  doctype?(doctype: Doctype): void | Promise<void>;\n  comments?(comment: Comment): void | Promise<void>;\n  text?(text: Text): void | Promise<void>;\n  end?(end: DocumentEnd): void | Promise<void>;\n}\ninterface Doctype {\n  readonly name: string | null;\n  readonly publicId: string | null;\n  readonly systemId: string | null;\n}\ninterface Element {\n  tagName: string;\n  readonly attributes: IterableIterator<string[]>;\n  readonly removed: boolean;\n  readonly namespaceURI: string;\n  getAttribute(name: string): string | null;\n  hasAttribute(name: string): boolean;\n  setAttribute(name: string, value: string): Element;\n  removeAttribute(name: string): Element;\n  before(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  after(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  prepend(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  append(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  replace(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  remove(): Element;\n  removeAndKeepContent(): Element;\n  setInnerContent(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  onEndTag(handler: (tag: EndTag) => void | Promise<void>): void;\n}\ninterface EndTag {\n  name: string;\n  before(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): EndTag;\n  after(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): EndTag;\n  remove(): EndTag;\n}\ninterface Comment {\n  text: string;\n  readonly removed: boolean;\n  before(content: string, options?: ContentOptions): Comment;\n  after(content: string, options?: ContentOptions): Comment;\n  replace(content: string, options?: ContentOptions): Comment;\n  remove(): Comment;\n}\ninterface Text {\n  readonly text: string;\n  readonly lastInTextNode: boolean;\n  readonly removed: boolean;\n  before(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Text;\n  after(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Text;\n  replace(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Text;\n  remove(): Text;\n}\ninterface DocumentEnd {\n  append(content: string, options?: ContentOptions): DocumentEnd;\n}\n/**\n * This is the event type for `fetch` events dispatched on the ServiceWorkerGlobalScope.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent)\n */\ndeclare abstract class FetchEvent extends ExtendableEvent {\n  /**\n   * The **`request`** read-only property of the the event handler.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/request)\n   */\n  readonly request: Request;\n  /**\n   * The **`respondWith()`** method of allows you to provide a promise for a Response yourself.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/respondWith)\n   */\n  respondWith(promise: Response | Promise<Response>): void;\n  passThroughOnException(): void;\n}\ntype HeadersInit =\n  | Headers\n  | Iterable<Iterable<string>>\n  | Record<string, string>;\n/**\n * The **`Headers`** interface of the Fetch API allows you to perform various actions on HTTP request and response headers.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers)\n */\ndeclare class Headers {\n  constructor(init?: HeadersInit);\n  /**\n   * The **`get()`** method of the Headers interface returns a byte string of all the values of a header within a `Headers` object with a given name.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/get)\n   */\n  get(name: string): string | null;\n  getAll(name: string): string[];\n  /**\n   * The **`getSetCookie()`** method of the Headers interface returns an array containing the values of all Set-Cookie headers associated with a response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/getSetCookie)\n   */\n  getSetCookie(): string[];\n  /**\n   * The **`has()`** method of the Headers interface returns a boolean stating whether a `Headers` object contains a certain header.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/has)\n   */\n  has(name: string): boolean;\n  /**\n   * The **`set()`** method of the Headers interface sets a new value for an existing header inside a `Headers` object, or adds the header if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/set)\n   */\n  set(name: string, value: string): void;\n  /**\n   * The **`append()`** method of the Headers interface appends a new value onto an existing header inside a `Headers` object, or adds the header if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/append)\n   */\n  append(name: string, value: string): void;\n  /**\n   * The **`delete()`** method of the Headers interface deletes a header from the current `Headers` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/delete)\n   */\n  delete(name: string): void;\n  forEach<This = unknown>(\n    callback: (this: This, value: string, key: string, parent: Headers) => void,\n    thisArg?: This,\n  ): void;\n  /* Returns an iterator allowing to go through all key/value pairs contained in this object. */\n  entries(): IterableIterator<[key: string, value: string]>;\n  /* Returns an iterator allowing to go through all keys of the key/value pairs contained in this object. */\n  keys(): IterableIterator<string>;\n  /* Returns an iterator allowing to go through all values of the key/value pairs contained in this object. */\n  values(): IterableIterator<string>;\n  [Symbol.iterator](): IterableIterator<[key: string, value: string]>;\n}\ntype BodyInit =\n  | ReadableStream<Uint8Array>\n  | string\n  | ArrayBuffer\n  | ArrayBufferView\n  | Blob\n  | URLSearchParams\n  | FormData\n  | Iterable<ArrayBuffer | ArrayBufferView>\n  | AsyncIterable<ArrayBuffer | ArrayBufferView>;\ndeclare abstract class Body {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body) */\n  get body(): ReadableStream | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) */\n  get bodyUsed(): boolean;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/arrayBuffer) */\n  arrayBuffer(): Promise<ArrayBuffer>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bytes) */\n  bytes(): Promise<Uint8Array>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/text) */\n  text(): Promise<string>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */\n  json<T>(): Promise<T>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/formData) */\n  formData(): Promise<FormData>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/blob) */\n  blob(): Promise<Blob>;\n}\n/**\n * The **`Response`** interface of the Fetch API represents the response to a request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response)\n */\ndeclare var Response: {\n  prototype: Response;\n  new (body?: BodyInit | null, init?: ResponseInit): Response;\n  error(): Response;\n  redirect(url: string, status?: number): Response;\n  json(any: any, maybeInit?: ResponseInit | Response): Response;\n};\n/**\n * The **`Response`** interface of the Fetch API represents the response to a request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response)\n */\ninterface Response extends Body {\n  /**\n   * The **`clone()`** method of the Response interface creates a clone of a response object, identical in every way, but stored in a different variable.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/clone)\n   */\n  clone(): Response;\n  /**\n   * The **`status`** read-only property of the Response interface contains the HTTP status codes of the response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/status)\n   */\n  status: number;\n  /**\n   * The **`statusText`** read-only property of the Response interface contains the status message corresponding to the HTTP status code in Response.status.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/statusText)\n   */\n  statusText: string;\n  /**\n   * The **`headers`** read-only property of the with the response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/headers)\n   */\n  headers: Headers;\n  /**\n   * The **`ok`** read-only property of the Response interface contains a Boolean stating whether the response was successful (status in the range 200-299) or not.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/ok)\n   */\n  ok: boolean;\n  /**\n   * The **`redirected`** read-only property of the Response interface indicates whether or not the response is the result of a request you made which was redirected.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/redirected)\n   */\n  redirected: boolean;\n  /**\n   * The **`url`** read-only property of the Response interface contains the URL of the response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/url)\n   */\n  url: string;\n  webSocket: WebSocket | null;\n  cf: any | undefined;\n  /**\n   * The **`type`** read-only property of the Response interface contains the type of the response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/type)\n   */\n  type: \"default\" | \"error\";\n}\ninterface ResponseInit {\n  status?: number;\n  statusText?: string;\n  headers?: HeadersInit;\n  cf?: any;\n  webSocket?: WebSocket | null;\n  encodeBody?: \"automatic\" | \"manual\";\n}\ntype RequestInfo<CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>> =\n  | Request<CfHostMetadata, Cf>\n  | string;\n/**\n * The **`Request`** interface of the Fetch API represents a resource request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request)\n */\ndeclare var Request: {\n  prototype: Request;\n  new <CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>>(\n    input: RequestInfo<CfProperties> | URL,\n    init?: RequestInit<Cf>,\n  ): Request<CfHostMetadata, Cf>;\n};\n/**\n * The **`Request`** interface of the Fetch API represents a resource request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request)\n */\ninterface Request<\n  CfHostMetadata = unknown,\n  Cf = CfProperties<CfHostMetadata>,\n> extends Body {\n  /**\n   * The **`clone()`** method of the Request interface creates a copy of the current `Request` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/clone)\n   */\n  clone(): Request<CfHostMetadata, Cf>;\n  /**\n   * The **`method`** read-only property of the `POST`, etc.) A String indicating the method of the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/method)\n   */\n  method: string;\n  /**\n   * The **`url`** read-only property of the Request interface contains the URL of the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/url)\n   */\n  url: string;\n  /**\n   * The **`headers`** read-only property of the with the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/headers)\n   */\n  headers: Headers;\n  /**\n   * The **`redirect`** read-only property of the Request interface contains the mode for how redirects are handled.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/redirect)\n   */\n  redirect: string;\n  fetcher: Fetcher | null;\n  /**\n   * The read-only **`signal`** property of the Request interface returns the AbortSignal associated with the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/signal)\n   */\n  signal: AbortSignal;\n  cf?: Cf;\n  /**\n   * The **`integrity`** read-only property of the Request interface contains the subresource integrity value of the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/integrity)\n   */\n  integrity: string;\n  /**\n   * The **`keepalive`** read-only property of the Request interface contains the request's `keepalive` setting (`true` or `false`), which indicates whether the browser will keep the associated request alive if the page that initiated it is unloaded before the request is complete.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/keepalive)\n   */\n  keepalive: boolean;\n  /**\n   * The **`cache`** read-only property of the Request interface contains the cache mode of the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/cache)\n   */\n  cache?: \"no-store\" | \"no-cache\";\n}\ninterface RequestInit<Cf = CfProperties> {\n  /* A string to set request's method. */\n  method?: string;\n  /* A Headers object, an object literal, or an array of two-item arrays to set request's headers. */\n  headers?: HeadersInit;\n  /* A BodyInit object or null to set request's body. */\n  body?: BodyInit | null;\n  /* A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */\n  redirect?: string;\n  fetcher?: Fetcher | null;\n  cf?: Cf;\n  /* A string indicating how the request will interact with the browser's cache to set request's cache. */\n  cache?: \"no-store\" | \"no-cache\";\n  /* A cryptographic hash of the resource to be fetched by request. Sets request's integrity. */\n  integrity?: string;\n  /* An AbortSignal to set request's signal. */\n  signal?: AbortSignal | null;\n  encodeResponseBody?: \"automatic\" | \"manual\";\n}\ntype Service<\n  T extends\n    | (new (...args: any[]) => Rpc.WorkerEntrypointBranded)\n    | Rpc.WorkerEntrypointBranded\n    | ExportedHandler<any, any, any>\n    | undefined = undefined,\n> = T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded\n  ? Fetcher<InstanceType<T>>\n  : T extends Rpc.WorkerEntrypointBranded\n    ? Fetcher<T>\n    : T extends Exclude<Rpc.EntrypointBranded, Rpc.WorkerEntrypointBranded>\n      ? never\n      : Fetcher<undefined>;\ntype Fetcher<\n  T extends Rpc.EntrypointBranded | undefined = undefined,\n  Reserved extends string = never,\n> = (T extends Rpc.EntrypointBranded\n  ? Rpc.Provider<T, Reserved | \"fetch\" | \"connect\">\n  : unknown) & {\n  fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;\n  connect(address: SocketAddress | string, options?: SocketOptions): Socket;\n};\ninterface KVNamespaceListKey<Metadata, Key extends string = string> {\n  name: Key;\n  expiration?: number;\n  metadata?: Metadata;\n}\ntype KVNamespaceListResult<Metadata, Key extends string = string> =\n  | {\n      list_complete: false;\n      keys: KVNamespaceListKey<Metadata, Key>[];\n      cursor: string;\n      cacheStatus: string | null;\n    }\n  | {\n      list_complete: true;\n      keys: KVNamespaceListKey<Metadata, Key>[];\n      cacheStatus: string | null;\n    };\ninterface KVNamespace<Key extends string = string> {\n  get(\n    key: Key,\n    options?: Partial<KVNamespaceGetOptions<undefined>>,\n  ): Promise<string | null>;\n  get(key: Key, type: \"text\"): Promise<string | null>;\n  get<ExpectedValue = unknown>(\n    key: Key,\n    type: \"json\",\n  ): Promise<ExpectedValue | null>;\n  get(key: Key, type: \"arrayBuffer\"): Promise<ArrayBuffer | null>;\n  get(key: Key, type: \"stream\"): Promise<ReadableStream | null>;\n  get(\n    key: Key,\n    options?: KVNamespaceGetOptions<\"text\">,\n  ): Promise<string | null>;\n  get<ExpectedValue = unknown>(\n    key: Key,\n    options?: KVNamespaceGetOptions<\"json\">,\n  ): Promise<ExpectedValue | null>;\n  get(\n    key: Key,\n    options?: KVNamespaceGetOptions<\"arrayBuffer\">,\n  ): Promise<ArrayBuffer | null>;\n  get(\n    key: Key,\n    options?: KVNamespaceGetOptions<\"stream\">,\n  ): Promise<ReadableStream | null>;\n  get(key: Array<Key>, type: \"text\"): Promise<Map<string, string | null>>;\n  get<ExpectedValue = unknown>(\n    key: Array<Key>,\n    type: \"json\",\n  ): Promise<Map<string, ExpectedValue | null>>;\n  get(\n    key: Array<Key>,\n    options?: Partial<KVNamespaceGetOptions<undefined>>,\n  ): Promise<Map<string, string | null>>;\n  get(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<\"text\">,\n  ): Promise<Map<string, string | null>>;\n  get<ExpectedValue = unknown>(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<\"json\">,\n  ): Promise<Map<string, ExpectedValue | null>>;\n  list<Metadata = unknown>(\n    options?: KVNamespaceListOptions,\n  ): Promise<KVNamespaceListResult<Metadata, Key>>;\n  put(\n    key: Key,\n    value: string | ArrayBuffer | ArrayBufferView | ReadableStream,\n    options?: KVNamespacePutOptions,\n  ): Promise<void>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options?: Partial<KVNamespaceGetOptions<undefined>>,\n  ): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    type: \"text\",\n  ): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Key,\n    type: \"json\",\n  ): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    type: \"arrayBuffer\",\n  ): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    type: \"stream\",\n  ): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<\"text\">,\n  ): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<\"json\">,\n  ): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<\"arrayBuffer\">,\n  ): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<\"stream\">,\n  ): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Array<Key>,\n    type: \"text\",\n  ): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Array<Key>,\n    type: \"json\",\n  ): Promise<\n    Map<string, KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>\n  >;\n  getWithMetadata<Metadata = unknown>(\n    key: Array<Key>,\n    options?: Partial<KVNamespaceGetOptions<undefined>>,\n  ): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<\"text\">,\n  ): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<\"json\">,\n  ): Promise<\n    Map<string, KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>\n  >;\n  delete(key: Key): Promise<void>;\n}\ninterface KVNamespaceListOptions {\n  limit?: number;\n  prefix?: string | null;\n  cursor?: string | null;\n}\ninterface KVNamespaceGetOptions<Type> {\n  type: Type;\n  cacheTtl?: number;\n}\ninterface KVNamespacePutOptions {\n  expiration?: number;\n  expirationTtl?: number;\n  metadata?: any | null;\n}\ninterface KVNamespaceGetWithMetadataResult<Value, Metadata> {\n  value: Value | null;\n  metadata: Metadata | null;\n  cacheStatus: string | null;\n}\ntype QueueContentType = \"text\" | \"bytes\" | \"json\" | \"v8\";\ninterface Queue<Body = unknown> {\n  send(message: Body, options?: QueueSendOptions): Promise<void>;\n  sendBatch(\n    messages: Iterable<MessageSendRequest<Body>>,\n    options?: QueueSendBatchOptions,\n  ): Promise<void>;\n}\ninterface QueueSendOptions {\n  contentType?: QueueContentType;\n  delaySeconds?: number;\n}\ninterface QueueSendBatchOptions {\n  delaySeconds?: number;\n}\ninterface MessageSendRequest<Body = unknown> {\n  body: Body;\n  contentType?: QueueContentType;\n  delaySeconds?: number;\n}\ninterface QueueRetryOptions {\n  delaySeconds?: number;\n}\ninterface Message<Body = unknown> {\n  readonly id: string;\n  readonly timestamp: Date;\n  readonly body: Body;\n  readonly attempts: number;\n  retry(options?: QueueRetryOptions): void;\n  ack(): void;\n}\ninterface QueueEvent<Body = unknown> extends ExtendableEvent {\n  readonly messages: readonly Message<Body>[];\n  readonly queue: string;\n  retryAll(options?: QueueRetryOptions): void;\n  ackAll(): void;\n}\ninterface MessageBatch<Body = unknown> {\n  readonly messages: readonly Message<Body>[];\n  readonly queue: string;\n  retryAll(options?: QueueRetryOptions): void;\n  ackAll(): void;\n}\ninterface R2Error extends Error {\n  readonly name: string;\n  readonly code: number;\n  readonly message: string;\n  readonly action: string;\n  readonly stack: any;\n}\ninterface R2ListOptions {\n  limit?: number;\n  prefix?: string;\n  cursor?: string;\n  delimiter?: string;\n  startAfter?: string;\n  include?: (\"httpMetadata\" | \"customMetadata\")[];\n}\ndeclare abstract class R2Bucket {\n  head(key: string): Promise<R2Object | null>;\n  get(\n    key: string,\n    options: R2GetOptions & {\n      onlyIf: R2Conditional | Headers;\n    },\n  ): Promise<R2ObjectBody | R2Object | null>;\n  get(key: string, options?: R2GetOptions): Promise<R2ObjectBody | null>;\n  put(\n    key: string,\n    value:\n      | ReadableStream\n      | ArrayBuffer\n      | ArrayBufferView\n      | string\n      | null\n      | Blob,\n    options?: R2PutOptions & {\n      onlyIf: R2Conditional | Headers;\n    },\n  ): Promise<R2Object | null>;\n  put(\n    key: string,\n    value:\n      | ReadableStream\n      | ArrayBuffer\n      | ArrayBufferView\n      | string\n      | null\n      | Blob,\n    options?: R2PutOptions,\n  ): Promise<R2Object>;\n  createMultipartUpload(\n    key: string,\n    options?: R2MultipartOptions,\n  ): Promise<R2MultipartUpload>;\n  resumeMultipartUpload(key: string, uploadId: string): R2MultipartUpload;\n  delete(keys: string | string[]): Promise<void>;\n  list(options?: R2ListOptions): Promise<R2Objects>;\n}\ninterface R2MultipartUpload {\n  readonly key: string;\n  readonly uploadId: string;\n  uploadPart(\n    partNumber: number,\n    value: ReadableStream | (ArrayBuffer | ArrayBufferView) | string | Blob,\n    options?: R2UploadPartOptions,\n  ): Promise<R2UploadedPart>;\n  abort(): Promise<void>;\n  complete(uploadedParts: R2UploadedPart[]): Promise<R2Object>;\n}\ninterface R2UploadedPart {\n  partNumber: number;\n  etag: string;\n}\ndeclare abstract class R2Object {\n  readonly key: string;\n  readonly version: string;\n  readonly size: number;\n  readonly etag: string;\n  readonly httpEtag: string;\n  readonly checksums: R2Checksums;\n  readonly uploaded: Date;\n  readonly httpMetadata?: R2HTTPMetadata;\n  readonly customMetadata?: Record<string, string>;\n  readonly range?: R2Range;\n  readonly storageClass: string;\n  readonly ssecKeyMd5?: string;\n  writeHttpMetadata(headers: Headers): void;\n}\ninterface R2ObjectBody extends R2Object {\n  get body(): ReadableStream;\n  get bodyUsed(): boolean;\n  arrayBuffer(): Promise<ArrayBuffer>;\n  bytes(): Promise<Uint8Array>;\n  text(): Promise<string>;\n  json<T>(): Promise<T>;\n  blob(): Promise<Blob>;\n}\ntype R2Range =\n  | {\n      offset: number;\n      length?: number;\n    }\n  | {\n      offset?: number;\n      length: number;\n    }\n  | {\n      suffix: number;\n    };\ninterface R2Conditional {\n  etagMatches?: string;\n  etagDoesNotMatch?: string;\n  uploadedBefore?: Date;\n  uploadedAfter?: Date;\n  secondsGranularity?: boolean;\n}\ninterface R2GetOptions {\n  onlyIf?: R2Conditional | Headers;\n  range?: R2Range | Headers;\n  ssecKey?: ArrayBuffer | string;\n}\ninterface R2PutOptions {\n  onlyIf?: R2Conditional | Headers;\n  httpMetadata?: R2HTTPMetadata | Headers;\n  customMetadata?: Record<string, string>;\n  md5?: (ArrayBuffer | ArrayBufferView) | string;\n  sha1?: (ArrayBuffer | ArrayBufferView) | string;\n  sha256?: (ArrayBuffer | ArrayBufferView) | string;\n  sha384?: (ArrayBuffer | ArrayBufferView) | string;\n  sha512?: (ArrayBuffer | ArrayBufferView) | string;\n  storageClass?: string;\n  ssecKey?: ArrayBuffer | string;\n}\ninterface R2MultipartOptions {\n  httpMetadata?: R2HTTPMetadata | Headers;\n  customMetadata?: Record<string, string>;\n  storageClass?: string;\n  ssecKey?: ArrayBuffer | string;\n}\ninterface R2Checksums {\n  readonly md5?: ArrayBuffer;\n  readonly sha1?: ArrayBuffer;\n  readonly sha256?: ArrayBuffer;\n  readonly sha384?: ArrayBuffer;\n  readonly sha512?: ArrayBuffer;\n  toJSON(): R2StringChecksums;\n}\ninterface R2StringChecksums {\n  md5?: string;\n  sha1?: string;\n  sha256?: string;\n  sha384?: string;\n  sha512?: string;\n}\ninterface R2HTTPMetadata {\n  contentType?: string;\n  contentLanguage?: string;\n  contentDisposition?: string;\n  contentEncoding?: string;\n  cacheControl?: string;\n  cacheExpiry?: Date;\n}\ntype R2Objects = {\n  objects: R2Object[];\n  delimitedPrefixes: string[];\n} & (\n  | {\n      truncated: true;\n      cursor: string;\n    }\n  | {\n      truncated: false;\n    }\n);\ninterface R2UploadPartOptions {\n  ssecKey?: ArrayBuffer | string;\n}\ndeclare abstract class ScheduledEvent extends ExtendableEvent {\n  readonly scheduledTime: number;\n  readonly cron: string;\n  noRetry(): void;\n}\ninterface ScheduledController {\n  readonly scheduledTime: number;\n  readonly cron: string;\n  noRetry(): void;\n}\ninterface QueuingStrategy<T = any> {\n  highWaterMark?: number | bigint;\n  size?: (chunk: T) => number | bigint;\n}\ninterface UnderlyingSink<W = any> {\n  type?: string;\n  start?: (controller: WritableStreamDefaultController) => void | Promise<void>;\n  write?: (\n    chunk: W,\n    controller: WritableStreamDefaultController,\n  ) => void | Promise<void>;\n  abort?: (reason: any) => void | Promise<void>;\n  close?: () => void | Promise<void>;\n}\ninterface UnderlyingByteSource {\n  type: \"bytes\";\n  autoAllocateChunkSize?: number;\n  start?: (controller: ReadableByteStreamController) => void | Promise<void>;\n  pull?: (controller: ReadableByteStreamController) => void | Promise<void>;\n  cancel?: (reason: any) => void | Promise<void>;\n}\ninterface UnderlyingSource<R = any> {\n  type?: \"\" | undefined;\n  start?: (\n    controller: ReadableStreamDefaultController<R>,\n  ) => void | Promise<void>;\n  pull?: (\n    controller: ReadableStreamDefaultController<R>,\n  ) => void | Promise<void>;\n  cancel?: (reason: any) => void | Promise<void>;\n  expectedLength?: number | bigint;\n}\ninterface Transformer<I = any, O = any> {\n  readableType?: string;\n  writableType?: string;\n  start?: (\n    controller: TransformStreamDefaultController<O>,\n  ) => void | Promise<void>;\n  transform?: (\n    chunk: I,\n    controller: TransformStreamDefaultController<O>,\n  ) => void | Promise<void>;\n  flush?: (\n    controller: TransformStreamDefaultController<O>,\n  ) => void | Promise<void>;\n  cancel?: (reason: any) => void | Promise<void>;\n  expectedLength?: number;\n}\ninterface StreamPipeOptions {\n  preventAbort?: boolean;\n  preventCancel?: boolean;\n  /**\n   * Pipes this readable stream to a given writable stream destination. The way in which the piping process behaves under various error conditions can be customized with a number of passed options. It returns a promise that fulfills when the piping process completes successfully, or rejects if any errors were encountered.\n   *\n   * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader.\n   *\n   * Errors and closures of the source and destination streams propagate as follows:\n   *\n   * An error in this source readable stream will abort destination, unless preventAbort is truthy. The returned promise will be rejected with the source's error, or with any error that occurs during aborting the destination.\n   *\n   * An error in destination will cancel this source readable stream, unless preventCancel is truthy. The returned promise will be rejected with the destination's error, or with any error that occurs during canceling the source.\n   *\n   * When this source readable stream closes, destination will be closed, unless preventClose is truthy. The returned promise will be fulfilled once this process completes, unless an error is encountered while closing the destination, in which case it will be rejected with that error.\n   *\n   * If destination starts out closed or closing, this source readable stream will be canceled, unless preventCancel is true. The returned promise will be rejected with an error indicating piping to a closed stream failed, or with any error that occurs during canceling the source.\n   *\n   * The signal option can be set to an AbortSignal to allow aborting an ongoing pipe operation via the corresponding AbortController. In this case, this source readable stream will be canceled, and destination aborted, unless the respective options preventCancel or preventAbort are set.\n   */\n  preventClose?: boolean;\n  signal?: AbortSignal;\n}\ntype ReadableStreamReadResult<R = any> =\n  | {\n      done: false;\n      value: R;\n    }\n  | {\n      done: true;\n      value?: undefined;\n    };\n/**\n * The `ReadableStream` interface of the Streams API represents a readable stream of byte data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream)\n */\ninterface ReadableStream<R = any> {\n  /**\n   * The **`locked`** read-only property of the ReadableStream interface returns whether or not the readable stream is locked to a reader.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/locked)\n   */\n  get locked(): boolean;\n  /**\n   * The **`cancel()`** method of the ReadableStream interface returns a Promise that resolves when the stream is canceled.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/cancel)\n   */\n  cancel(reason?: any): Promise<void>;\n  /**\n   * The **`getReader()`** method of the ReadableStream interface creates a reader and locks the stream to it.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader)\n   */\n  getReader(): ReadableStreamDefaultReader<R>;\n  /**\n   * The **`getReader()`** method of the ReadableStream interface creates a reader and locks the stream to it.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader)\n   */\n  getReader(options: ReadableStreamGetReaderOptions): ReadableStreamBYOBReader;\n  /**\n   * The **`pipeThrough()`** method of the ReadableStream interface provides a chainable way of piping the current stream through a transform stream or any other writable/readable pair.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeThrough)\n   */\n  pipeThrough<T>(\n    transform: ReadableWritablePair<T, R>,\n    options?: StreamPipeOptions,\n  ): ReadableStream<T>;\n  /**\n   * The **`pipeTo()`** method of the ReadableStream interface pipes the current `ReadableStream` to a given WritableStream and returns a Promise that fulfills when the piping process completes successfully, or rejects if any errors were encountered.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeTo)\n   */\n  pipeTo(\n    destination: WritableStream<R>,\n    options?: StreamPipeOptions,\n  ): Promise<void>;\n  /**\n   * The **`tee()`** method of the two-element array containing the two resulting branches as new ReadableStream instances.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/tee)\n   */\n  tee(): [ReadableStream<R>, ReadableStream<R>];\n  values(options?: ReadableStreamValuesOptions): AsyncIterableIterator<R>;\n  [Symbol.asyncIterator](\n    options?: ReadableStreamValuesOptions,\n  ): AsyncIterableIterator<R>;\n}\n/**\n * The `ReadableStream` interface of the Streams API represents a readable stream of byte data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream)\n */\ndeclare const ReadableStream: {\n  prototype: ReadableStream;\n  new (\n    underlyingSource: UnderlyingByteSource,\n    strategy?: QueuingStrategy<Uint8Array>,\n  ): ReadableStream<Uint8Array>;\n  new <R = any>(\n    underlyingSource?: UnderlyingSource<R>,\n    strategy?: QueuingStrategy<R>,\n  ): ReadableStream<R>;\n};\n/**\n * The **`ReadableStreamDefaultReader`** interface of the Streams API represents a default reader that can be used to read stream data supplied from a network (such as a fetch request).\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader)\n */\ndeclare class ReadableStreamDefaultReader<R = any> {\n  constructor(stream: ReadableStream);\n  get closed(): Promise<void>;\n  cancel(reason?: any): Promise<void>;\n  /**\n   * The **`read()`** method of the ReadableStreamDefaultReader interface returns a Promise providing access to the next chunk in the stream's internal queue.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/read)\n   */\n  read(): Promise<ReadableStreamReadResult<R>>;\n  /**\n   * The **`releaseLock()`** method of the ReadableStreamDefaultReader interface releases the reader's lock on the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/releaseLock)\n   */\n  releaseLock(): void;\n}\n/**\n * The `ReadableStreamBYOBReader` interface of the Streams API defines a reader for a ReadableStream that supports zero-copy reading from an underlying byte source.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader)\n */\ndeclare class ReadableStreamBYOBReader {\n  constructor(stream: ReadableStream);\n  get closed(): Promise<void>;\n  cancel(reason?: any): Promise<void>;\n  /**\n   * The **`read()`** method of the ReadableStreamBYOBReader interface is used to read data into a view on a user-supplied buffer from an associated readable byte stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/read)\n   */\n  read<T extends ArrayBufferView>(\n    view: T,\n  ): Promise<ReadableStreamReadResult<T>>;\n  /**\n   * The **`releaseLock()`** method of the ReadableStreamBYOBReader interface releases the reader's lock on the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/releaseLock)\n   */\n  releaseLock(): void;\n  readAtLeast<T extends ArrayBufferView>(\n    minElements: number,\n    view: T,\n  ): Promise<ReadableStreamReadResult<T>>;\n}\ninterface ReadableStreamBYOBReaderReadableStreamBYOBReaderReadOptions {\n  min?: number;\n}\ninterface ReadableStreamGetReaderOptions {\n  /**\n   * Creates a ReadableStreamBYOBReader and locks the stream to the new reader.\n   *\n   * This call behaves the same way as the no-argument variant, except that it only works on readable byte streams, i.e. streams which were constructed specifically with the ability to handle \"bring your own buffer\" reading. The returned BYOB reader provides the ability to directly read individual chunks from the stream via its read() method, into developer-supplied buffers, allowing more precise control over allocation.\n   */\n  mode: \"byob\";\n}\n/**\n * The **`ReadableStreamBYOBRequest`** interface of the Streams API represents a 'pull request' for data from an underlying source that will made as a zero-copy transfer to a consumer (bypassing the stream's internal queues).\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest)\n */\ndeclare abstract class ReadableStreamBYOBRequest {\n  /**\n   * The **`view`** getter property of the ReadableStreamBYOBRequest interface returns the current view.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/view)\n   */\n  get view(): Uint8Array | null;\n  /**\n   * The **`respond()`** method of the ReadableStreamBYOBRequest interface is used to signal to the associated readable byte stream that the specified number of bytes were written into the ReadableStreamBYOBRequest.view.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respond)\n   */\n  respond(bytesWritten: number): void;\n  /**\n   * The **`respondWithNewView()`** method of the ReadableStreamBYOBRequest interface specifies a new view that the consumer of the associated readable byte stream should write to instead of ReadableStreamBYOBRequest.view.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respondWithNewView)\n   */\n  respondWithNewView(view: ArrayBuffer | ArrayBufferView): void;\n  get atLeast(): number | null;\n}\n/**\n * The **`ReadableStreamDefaultController`** interface of the Streams API represents a controller allowing control of a ReadableStream's state and internal queue.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController)\n */\ndeclare abstract class ReadableStreamDefaultController<R = any> {\n  /**\n   * The **`desiredSize`** read-only property of the required to fill the stream's internal queue.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/desiredSize)\n   */\n  get desiredSize(): number | null;\n  /**\n   * The **`close()`** method of the ReadableStreamDefaultController interface closes the associated stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/close)\n   */\n  close(): void;\n  /**\n   * The **`enqueue()`** method of the ```js-nolint enqueue(chunk) ``` - `chunk` - : The chunk to enqueue.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/enqueue)\n   */\n  enqueue(chunk?: R): void;\n  /**\n   * The **`error()`** method of the with the associated stream to error.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/error)\n   */\n  error(reason: any): void;\n}\n/**\n * The **`ReadableByteStreamController`** interface of the Streams API represents a controller for a readable byte stream.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController)\n */\ndeclare abstract class ReadableByteStreamController {\n  /**\n   * The **`byobRequest`** read-only property of the ReadableByteStreamController interface returns the current BYOB request, or `null` if there are no pending requests.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/byobRequest)\n   */\n  get byobRequest(): ReadableStreamBYOBRequest | null;\n  /**\n   * The **`desiredSize`** read-only property of the ReadableByteStreamController interface returns the number of bytes required to fill the stream's internal queue to its 'desired size'.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/desiredSize)\n   */\n  get desiredSize(): number | null;\n  /**\n   * The **`close()`** method of the ReadableByteStreamController interface closes the associated stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/close)\n   */\n  close(): void;\n  /**\n   * The **`enqueue()`** method of the ReadableByteStreamController interface enqueues a given chunk on the associated readable byte stream (the chunk is copied into the stream's internal queues).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/enqueue)\n   */\n  enqueue(chunk: ArrayBuffer | ArrayBufferView): void;\n  /**\n   * The **`error()`** method of the ReadableByteStreamController interface causes any future interactions with the associated stream to error with the specified reason.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/error)\n   */\n  error(reason: any): void;\n}\n/**\n * The **`WritableStreamDefaultController`** interface of the Streams API represents a controller allowing control of a WritableStream's state.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController)\n */\ndeclare abstract class WritableStreamDefaultController {\n  /**\n   * The read-only **`signal`** property of the WritableStreamDefaultController interface returns the AbortSignal associated with the controller.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/signal)\n   */\n  get signal(): AbortSignal;\n  /**\n   * The **`error()`** method of the with the associated stream to error.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/error)\n   */\n  error(reason?: any): void;\n}\n/**\n * The **`TransformStreamDefaultController`** interface of the Streams API provides methods to manipulate the associated ReadableStream and WritableStream.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController)\n */\ndeclare abstract class TransformStreamDefaultController<O = any> {\n  /**\n   * The **`desiredSize`** read-only property of the TransformStreamDefaultController interface returns the desired size to fill the queue of the associated ReadableStream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/desiredSize)\n   */\n  get desiredSize(): number | null;\n  /**\n   * The **`enqueue()`** method of the TransformStreamDefaultController interface enqueues the given chunk in the readable side of the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/enqueue)\n   */\n  enqueue(chunk?: O): void;\n  /**\n   * The **`error()`** method of the TransformStreamDefaultController interface errors both sides of the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/error)\n   */\n  error(reason: any): void;\n  /**\n   * The **`terminate()`** method of the TransformStreamDefaultController interface closes the readable side and errors the writable side of the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/terminate)\n   */\n  terminate(): void;\n}\ninterface ReadableWritablePair<R = any, W = any> {\n  readable: ReadableStream<R>;\n  /**\n   * Provides a convenient, chainable way of piping this readable stream through a transform stream (or any other { writable, readable } pair). It simply pipes the stream into the writable side of the supplied pair, and returns the readable side for further use.\n   *\n   * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader.\n   */\n  writable: WritableStream<W>;\n}\n/**\n * The **`WritableStream`** interface of the Streams API provides a standard abstraction for writing streaming data to a destination, known as a sink.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream)\n */\ndeclare class WritableStream<W = any> {\n  constructor(\n    underlyingSink?: UnderlyingSink,\n    queuingStrategy?: QueuingStrategy,\n  );\n  /**\n   * The **`locked`** read-only property of the WritableStream interface returns a boolean indicating whether the `WritableStream` is locked to a writer.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/locked)\n   */\n  get locked(): boolean;\n  /**\n   * The **`abort()`** method of the WritableStream interface aborts the stream, signaling that the producer can no longer successfully write to the stream and it is to be immediately moved to an error state, with any queued writes discarded.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/abort)\n   */\n  abort(reason?: any): Promise<void>;\n  /**\n   * The **`close()`** method of the WritableStream interface closes the associated stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/close)\n   */\n  close(): Promise<void>;\n  /**\n   * The **`getWriter()`** method of the WritableStream interface returns a new instance of WritableStreamDefaultWriter and locks the stream to that instance.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/getWriter)\n   */\n  getWriter(): WritableStreamDefaultWriter<W>;\n}\n/**\n * The **`WritableStreamDefaultWriter`** interface of the Streams API is the object returned by WritableStream.getWriter() and once created locks the writer to the `WritableStream` ensuring that no other streams can write to the underlying sink.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter)\n */\ndeclare class WritableStreamDefaultWriter<W = any> {\n  constructor(stream: WritableStream);\n  /**\n   * The **`closed`** read-only property of the the stream errors or the writer's lock is released.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/closed)\n   */\n  get closed(): Promise<void>;\n  /**\n   * The **`ready`** read-only property of the that resolves when the desired size of the stream's internal queue transitions from non-positive to positive, signaling that it is no longer applying backpressure.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/ready)\n   */\n  get ready(): Promise<void>;\n  /**\n   * The **`desiredSize`** read-only property of the to fill the stream's internal queue.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/desiredSize)\n   */\n  get desiredSize(): number | null;\n  /**\n   * The **`abort()`** method of the the producer can no longer successfully write to the stream and it is to be immediately moved to an error state, with any queued writes discarded.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/abort)\n   */\n  abort(reason?: any): Promise<void>;\n  /**\n   * The **`close()`** method of the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/close)\n   */\n  close(): Promise<void>;\n  /**\n   * The **`write()`** method of the operation.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/write)\n   */\n  write(chunk?: W): Promise<void>;\n  /**\n   * The **`releaseLock()`** method of the corresponding stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/releaseLock)\n   */\n  releaseLock(): void;\n}\n/**\n * The **`TransformStream`** interface of the Streams API represents a concrete implementation of the pipe chain _transform stream_ concept.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream)\n */\ndeclare class TransformStream<I = any, O = any> {\n  constructor(\n    transformer?: Transformer<I, O>,\n    writableStrategy?: QueuingStrategy<I>,\n    readableStrategy?: QueuingStrategy<O>,\n  );\n  /**\n   * The **`readable`** read-only property of the TransformStream interface returns the ReadableStream instance controlled by this `TransformStream`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/readable)\n   */\n  get readable(): ReadableStream<O>;\n  /**\n   * The **`writable`** read-only property of the TransformStream interface returns the WritableStream instance controlled by this `TransformStream`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/writable)\n   */\n  get writable(): WritableStream<I>;\n}\ndeclare class FixedLengthStream extends IdentityTransformStream {\n  constructor(\n    expectedLength: number | bigint,\n    queuingStrategy?: IdentityTransformStreamQueuingStrategy,\n  );\n}\ndeclare class IdentityTransformStream extends TransformStream<\n  ArrayBuffer | ArrayBufferView,\n  Uint8Array\n> {\n  constructor(queuingStrategy?: IdentityTransformStreamQueuingStrategy);\n}\ninterface IdentityTransformStreamQueuingStrategy {\n  highWaterMark?: number | bigint;\n}\ninterface ReadableStreamValuesOptions {\n  preventCancel?: boolean;\n}\n/**\n * The **`CompressionStream`** interface of the Compression Streams API is an API for compressing a stream of data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CompressionStream)\n */\ndeclare class CompressionStream extends TransformStream<\n  ArrayBuffer | ArrayBufferView,\n  Uint8Array\n> {\n  constructor(format: \"gzip\" | \"deflate\" | \"deflate-raw\");\n}\n/**\n * The **`DecompressionStream`** interface of the Compression Streams API is an API for decompressing a stream of data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DecompressionStream)\n */\ndeclare class DecompressionStream extends TransformStream<\n  ArrayBuffer | ArrayBufferView,\n  Uint8Array\n> {\n  constructor(format: \"gzip\" | \"deflate\" | \"deflate-raw\");\n}\n/**\n * The **`TextEncoderStream`** interface of the Encoding API converts a stream of strings into bytes in the UTF-8 encoding.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoderStream)\n */\ndeclare class TextEncoderStream extends TransformStream<string, Uint8Array> {\n  constructor();\n  get encoding(): string;\n}\n/**\n * The **`TextDecoderStream`** interface of the Encoding API converts a stream of text in a binary encoding, such as UTF-8 etc., to a stream of strings.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoderStream)\n */\ndeclare class TextDecoderStream extends TransformStream<\n  ArrayBuffer | ArrayBufferView,\n  string\n> {\n  constructor(label?: string, options?: TextDecoderStreamTextDecoderStreamInit);\n  get encoding(): string;\n  get fatal(): boolean;\n  get ignoreBOM(): boolean;\n}\ninterface TextDecoderStreamTextDecoderStreamInit {\n  fatal?: boolean;\n  ignoreBOM?: boolean;\n}\n/**\n * The **`ByteLengthQueuingStrategy`** interface of the Streams API provides a built-in byte length queuing strategy that can be used when constructing streams.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy)\n */\ndeclare class ByteLengthQueuingStrategy implements QueuingStrategy<ArrayBufferView> {\n  constructor(init: QueuingStrategyInit);\n  /**\n   * The read-only **`ByteLengthQueuingStrategy.highWaterMark`** property returns the total number of bytes that can be contained in the internal queue before backpressure is applied.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/highWaterMark)\n   */\n  get highWaterMark(): number;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/size) */\n  get size(): (chunk?: any) => number;\n}\n/**\n * The **`CountQueuingStrategy`** interface of the Streams API provides a built-in chunk counting queuing strategy that can be used when constructing streams.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy)\n */\ndeclare class CountQueuingStrategy implements QueuingStrategy {\n  constructor(init: QueuingStrategyInit);\n  /**\n   * The read-only **`CountQueuingStrategy.highWaterMark`** property returns the total number of chunks that can be contained in the internal queue before backpressure is applied.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/highWaterMark)\n   */\n  get highWaterMark(): number;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/size) */\n  get size(): (chunk?: any) => number;\n}\ninterface QueuingStrategyInit {\n  /**\n   * Creates a new ByteLengthQueuingStrategy with the provided high water mark.\n   *\n   * Note that the provided high water mark will not be validated ahead of time. Instead, if it is negative, NaN, or not a number, the resulting ByteLengthQueuingStrategy will cause the corresponding stream constructor to throw.\n   */\n  highWaterMark: number;\n}\ninterface ScriptVersion {\n  id?: string;\n  tag?: string;\n  message?: string;\n}\ndeclare abstract class TailEvent extends ExtendableEvent {\n  readonly events: TraceItem[];\n  readonly traces: TraceItem[];\n}\ninterface TraceItem {\n  readonly event:\n    | (\n        | TraceItemFetchEventInfo\n        | TraceItemJsRpcEventInfo\n        | TraceItemScheduledEventInfo\n        | TraceItemAlarmEventInfo\n        | TraceItemQueueEventInfo\n        | TraceItemEmailEventInfo\n        | TraceItemTailEventInfo\n        | TraceItemCustomEventInfo\n        | TraceItemHibernatableWebSocketEventInfo\n      )\n    | null;\n  readonly eventTimestamp: number | null;\n  readonly logs: TraceLog[];\n  readonly exceptions: TraceException[];\n  readonly diagnosticsChannelEvents: TraceDiagnosticChannelEvent[];\n  readonly scriptName: string | null;\n  readonly entrypoint?: string;\n  readonly scriptVersion?: ScriptVersion;\n  readonly dispatchNamespace?: string;\n  readonly scriptTags?: string[];\n  readonly tailAttributes?: Record<string, boolean | number | string>;\n  readonly durableObjectId?: string;\n  readonly outcome: string;\n  readonly executionModel: string;\n  readonly truncated: boolean;\n  readonly cpuTime: number;\n  readonly wallTime: number;\n}\ninterface TraceItemAlarmEventInfo {\n  readonly scheduledTime: Date;\n}\ninterface TraceItemCustomEventInfo {}\ninterface TraceItemScheduledEventInfo {\n  readonly scheduledTime: number;\n  readonly cron: string;\n}\ninterface TraceItemQueueEventInfo {\n  readonly queue: string;\n  readonly batchSize: number;\n}\ninterface TraceItemEmailEventInfo {\n  readonly mailFrom: string;\n  readonly rcptTo: string;\n  readonly rawSize: number;\n}\ninterface TraceItemTailEventInfo {\n  readonly consumedEvents: TraceItemTailEventInfoTailItem[];\n}\ninterface TraceItemTailEventInfoTailItem {\n  readonly scriptName: string | null;\n}\ninterface TraceItemFetchEventInfo {\n  readonly response?: TraceItemFetchEventInfoResponse;\n  readonly request: TraceItemFetchEventInfoRequest;\n}\ninterface TraceItemFetchEventInfoRequest {\n  readonly cf?: any;\n  readonly headers: Record<string, string>;\n  readonly method: string;\n  readonly url: string;\n  getUnredacted(): TraceItemFetchEventInfoRequest;\n}\ninterface TraceItemFetchEventInfoResponse {\n  readonly status: number;\n}\ninterface TraceItemJsRpcEventInfo {\n  readonly rpcMethod: string;\n}\ninterface TraceItemHibernatableWebSocketEventInfo {\n  readonly getWebSocketEvent:\n    | TraceItemHibernatableWebSocketEventInfoMessage\n    | TraceItemHibernatableWebSocketEventInfoClose\n    | TraceItemHibernatableWebSocketEventInfoError;\n}\ninterface TraceItemHibernatableWebSocketEventInfoMessage {\n  readonly webSocketEventType: string;\n}\ninterface TraceItemHibernatableWebSocketEventInfoClose {\n  readonly webSocketEventType: string;\n  readonly code: number;\n  readonly wasClean: boolean;\n}\ninterface TraceItemHibernatableWebSocketEventInfoError {\n  readonly webSocketEventType: string;\n}\ninterface TraceLog {\n  readonly timestamp: number;\n  readonly level: string;\n  readonly message: any;\n}\ninterface TraceException {\n  readonly timestamp: number;\n  readonly message: string;\n  readonly name: string;\n  readonly stack?: string;\n}\ninterface TraceDiagnosticChannelEvent {\n  readonly timestamp: number;\n  readonly channel: string;\n  readonly message: any;\n}\ninterface TraceMetrics {\n  readonly cpuTime: number;\n  readonly wallTime: number;\n}\ninterface UnsafeTraceMetrics {\n  fromTrace(item: TraceItem): TraceMetrics;\n}\n/**\n * The **`URL`** interface is used to parse, construct, normalize, and encode URL.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL)\n */\ndeclare class URL {\n  constructor(url: string | URL, base?: string | URL);\n  /**\n   * The **`origin`** read-only property of the URL interface returns a string containing the Unicode serialization of the origin of the represented URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/origin)\n   */\n  get origin(): string;\n  /**\n   * The **`href`** property of the URL interface is a string containing the whole URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href)\n   */\n  get href(): string;\n  /**\n   * The **`href`** property of the URL interface is a string containing the whole URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href)\n   */\n  set href(value: string);\n  /**\n   * The **`protocol`** property of the URL interface is a string containing the protocol or scheme of the URL, including the final `':'`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol)\n   */\n  get protocol(): string;\n  /**\n   * The **`protocol`** property of the URL interface is a string containing the protocol or scheme of the URL, including the final `':'`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol)\n   */\n  set protocol(value: string);\n  /**\n   * The **`username`** property of the URL interface is a string containing the username component of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username)\n   */\n  get username(): string;\n  /**\n   * The **`username`** property of the URL interface is a string containing the username component of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username)\n   */\n  set username(value: string);\n  /**\n   * The **`password`** property of the URL interface is a string containing the password component of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password)\n   */\n  get password(): string;\n  /**\n   * The **`password`** property of the URL interface is a string containing the password component of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password)\n   */\n  set password(value: string);\n  /**\n   * The **`host`** property of the URL interface is a string containing the host, which is the URL.hostname, and then, if the port of the URL is nonempty, a `':'`, followed by the URL.port of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host)\n   */\n  get host(): string;\n  /**\n   * The **`host`** property of the URL interface is a string containing the host, which is the URL.hostname, and then, if the port of the URL is nonempty, a `':'`, followed by the URL.port of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host)\n   */\n  set host(value: string);\n  /**\n   * The **`hostname`** property of the URL interface is a string containing either the domain name or IP address of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname)\n   */\n  get hostname(): string;\n  /**\n   * The **`hostname`** property of the URL interface is a string containing either the domain name or IP address of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname)\n   */\n  set hostname(value: string);\n  /**\n   * The **`port`** property of the URL interface is a string containing the port number of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port)\n   */\n  get port(): string;\n  /**\n   * The **`port`** property of the URL interface is a string containing the port number of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port)\n   */\n  set port(value: string);\n  /**\n   * The **`pathname`** property of the URL interface represents a location in a hierarchical structure.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname)\n   */\n  get pathname(): string;\n  /**\n   * The **`pathname`** property of the URL interface represents a location in a hierarchical structure.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname)\n   */\n  set pathname(value: string);\n  /**\n   * The **`search`** property of the URL interface is a search string, also called a _query string_, that is a string containing a `'?'` followed by the parameters of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search)\n   */\n  get search(): string;\n  /**\n   * The **`search`** property of the URL interface is a search string, also called a _query string_, that is a string containing a `'?'` followed by the parameters of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search)\n   */\n  set search(value: string);\n  /**\n   * The **`hash`** property of the URL interface is a string containing a `'#'` followed by the fragment identifier of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash)\n   */\n  get hash(): string;\n  /**\n   * The **`hash`** property of the URL interface is a string containing a `'#'` followed by the fragment identifier of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash)\n   */\n  set hash(value: string);\n  /**\n   * The **`searchParams`** read-only property of the access to the [MISSING: httpmethod('GET')] decoded query arguments contained in the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/searchParams)\n   */\n  get searchParams(): URLSearchParams;\n  /**\n   * The **`toJSON()`** method of the URL interface returns a string containing a serialized version of the URL, although in practice it seems to have the same effect as ```js-nolint toJSON() ``` None.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/toJSON)\n   */\n  toJSON(): string;\n  /*function toString() { [native code] }*/\n  toString(): string;\n  /**\n   * The **`URL.canParse()`** static method of the URL interface returns a boolean indicating whether or not an absolute URL, or a relative URL combined with a base URL, are parsable and valid.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/canParse_static)\n   */\n  static canParse(url: string, base?: string): boolean;\n  /**\n   * The **`URL.parse()`** static method of the URL interface returns a newly created URL object representing the URL defined by the parameters.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/parse_static)\n   */\n  static parse(url: string, base?: string): URL | null;\n  /**\n   * The **`createObjectURL()`** static method of the URL interface creates a string containing a URL representing the object given in the parameter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/createObjectURL_static)\n   */\n  static createObjectURL(object: File | Blob): string;\n  /**\n   * The **`revokeObjectURL()`** static method of the URL interface releases an existing object URL which was previously created by calling Call this method when you've finished using an object URL to let the browser know not to keep the reference to the file any longer.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/revokeObjectURL_static)\n   */\n  static revokeObjectURL(object_url: string): void;\n}\n/**\n * The **`URLSearchParams`** interface defines utility methods to work with the query string of a URL.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams)\n */\ndeclare class URLSearchParams {\n  constructor(\n    init?: Iterable<Iterable<string>> | Record<string, string> | string,\n  );\n  /**\n   * The **`size`** read-only property of the URLSearchParams interface indicates the total number of search parameter entries.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/size)\n   */\n  get size(): number;\n  /**\n   * The **`append()`** method of the URLSearchParams interface appends a specified key/value pair as a new search parameter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/append)\n   */\n  append(name: string, value: string): void;\n  /**\n   * The **`delete()`** method of the URLSearchParams interface deletes specified parameters and their associated value(s) from the list of all search parameters.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/delete)\n   */\n  delete(name: string, value?: string): void;\n  /**\n   * The **`get()`** method of the URLSearchParams interface returns the first value associated to the given search parameter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/get)\n   */\n  get(name: string): string | null;\n  /**\n   * The **`getAll()`** method of the URLSearchParams interface returns all the values associated with a given search parameter as an array.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/getAll)\n   */\n  getAll(name: string): string[];\n  /**\n   * The **`has()`** method of the URLSearchParams interface returns a boolean value that indicates whether the specified parameter is in the search parameters.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/has)\n   */\n  has(name: string, value?: string): boolean;\n  /**\n   * The **`set()`** method of the URLSearchParams interface sets the value associated with a given search parameter to the given value.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/set)\n   */\n  set(name: string, value: string): void;\n  /**\n   * The **`URLSearchParams.sort()`** method sorts all key/value pairs contained in this object in place and returns `undefined`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/sort)\n   */\n  sort(): void;\n  /* Returns an array of key, value pairs for every entry in the search params. */\n  entries(): IterableIterator<[key: string, value: string]>;\n  /* Returns a list of keys in the search params. */\n  keys(): IterableIterator<string>;\n  /* Returns a list of values in the search params. */\n  values(): IterableIterator<string>;\n  forEach<This = unknown>(\n    callback: (\n      this: This,\n      value: string,\n      key: string,\n      parent: URLSearchParams,\n    ) => void,\n    thisArg?: This,\n  ): void;\n  /*function toString() { [native code] }*/\n  toString(): string;\n  [Symbol.iterator](): IterableIterator<[key: string, value: string]>;\n}\ndeclare class URLPattern {\n  constructor(\n    input?: string | URLPatternInit,\n    baseURL?: string | URLPatternOptions,\n    patternOptions?: URLPatternOptions,\n  );\n  get protocol(): string;\n  get username(): string;\n  get password(): string;\n  get hostname(): string;\n  get port(): string;\n  get pathname(): string;\n  get search(): string;\n  get hash(): string;\n  get hasRegExpGroups(): boolean;\n  test(input?: string | URLPatternInit, baseURL?: string): boolean;\n  exec(\n    input?: string | URLPatternInit,\n    baseURL?: string,\n  ): URLPatternResult | null;\n}\ninterface URLPatternInit {\n  protocol?: string;\n  username?: string;\n  password?: string;\n  hostname?: string;\n  port?: string;\n  pathname?: string;\n  search?: string;\n  hash?: string;\n  baseURL?: string;\n}\ninterface URLPatternComponentResult {\n  input: string;\n  groups: Record<string, string>;\n}\ninterface URLPatternResult {\n  inputs: (string | URLPatternInit)[];\n  protocol: URLPatternComponentResult;\n  username: URLPatternComponentResult;\n  password: URLPatternComponentResult;\n  hostname: URLPatternComponentResult;\n  port: URLPatternComponentResult;\n  pathname: URLPatternComponentResult;\n  search: URLPatternComponentResult;\n  hash: URLPatternComponentResult;\n}\ninterface URLPatternOptions {\n  ignoreCase?: boolean;\n}\n/**\n * A `CloseEvent` is sent to clients using WebSockets when the connection is closed.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent)\n */\ndeclare class CloseEvent extends Event {\n  constructor(type: string, initializer?: CloseEventInit);\n  /**\n   * The **`code`** read-only property of the CloseEvent interface returns a WebSocket connection close code indicating the reason the connection was closed.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/code)\n   */\n  readonly code: number;\n  /**\n   * The **`reason`** read-only property of the CloseEvent interface returns the WebSocket connection close reason the server gave for closing the connection; that is, a concise human-readable prose explanation for the closure.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/reason)\n   */\n  readonly reason: string;\n  /**\n   * The **`wasClean`** read-only property of the CloseEvent interface returns `true` if the connection closed cleanly.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/wasClean)\n   */\n  readonly wasClean: boolean;\n}\ninterface CloseEventInit {\n  code?: number;\n  reason?: string;\n  wasClean?: boolean;\n}\ntype WebSocketEventMap = {\n  close: CloseEvent;\n  message: MessageEvent;\n  open: Event;\n  error: ErrorEvent;\n};\n/**\n * The `WebSocket` object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket)\n */\ndeclare var WebSocket: {\n  prototype: WebSocket;\n  new (url: string, protocols?: string[] | string): WebSocket;\n  readonly READY_STATE_CONNECTING: number;\n  readonly CONNECTING: number;\n  readonly READY_STATE_OPEN: number;\n  readonly OPEN: number;\n  readonly READY_STATE_CLOSING: number;\n  readonly CLOSING: number;\n  readonly READY_STATE_CLOSED: number;\n  readonly CLOSED: number;\n};\n/**\n * The `WebSocket` object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket)\n */\ninterface WebSocket extends EventTarget<WebSocketEventMap> {\n  accept(options?: WebSocketAcceptOptions): void;\n  /**\n   * The **`WebSocket.send()`** method enqueues the specified data to be transmitted to the server over the WebSocket connection, increasing the value of `bufferedAmount` by the number of bytes needed to contain the data.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/send)\n   */\n  send(message: (ArrayBuffer | ArrayBufferView) | string): void;\n  /**\n   * The **`WebSocket.close()`** method closes the already `CLOSED`, this method does nothing.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/close)\n   */\n  close(code?: number, reason?: string): void;\n  serializeAttachment(attachment: any): void;\n  deserializeAttachment(): any | null;\n  /**\n   * The **`WebSocket.readyState`** read-only property returns the current state of the WebSocket connection.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/readyState)\n   */\n  readyState: number;\n  /**\n   * The **`WebSocket.url`** read-only property returns the absolute URL of the WebSocket as resolved by the constructor.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/url)\n   */\n  url: string | null;\n  /**\n   * The **`WebSocket.protocol`** read-only property returns the name of the sub-protocol the server selected; this will be one of the strings specified in the `protocols` parameter when creating the WebSocket object, or the empty string if no connection is established.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/protocol)\n   */\n  protocol: string | null;\n  /**\n   * The **`WebSocket.extensions`** read-only property returns the extensions selected by the server.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/extensions)\n   */\n  extensions: string | null;\n  /**\n   * The **`WebSocket.binaryType`** property controls the type of binary data being received over the WebSocket connection.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/binaryType)\n   */\n  binaryType: \"blob\" | \"arraybuffer\";\n}\ninterface WebSocketAcceptOptions {\n  /**\n   * When set to `true`, receiving a server-initiated WebSocket Close frame will not\n   * automatically send a reciprocal Close frame, leaving the connection in a half-open\n   * state. This is useful for proxying scenarios where you need to coordinate closing\n   * both sides independently. Defaults to `false` when the\n   * `no_web_socket_half_open_by_default` compatibility flag is enabled.\n   */\n  allowHalfOpen?: boolean;\n}\ndeclare const WebSocketPair: {\n  new (): {\n    0: WebSocket;\n    1: WebSocket;\n  };\n};\ninterface SqlStorage {\n  exec<T extends Record<string, SqlStorageValue>>(\n    query: string,\n    ...bindings: any[]\n  ): SqlStorageCursor<T>;\n  get databaseSize(): number;\n  Cursor: typeof SqlStorageCursor;\n  Statement: typeof SqlStorageStatement;\n}\ndeclare abstract class SqlStorageStatement {}\ntype SqlStorageValue = ArrayBuffer | string | number | null;\ndeclare abstract class SqlStorageCursor<\n  T extends Record<string, SqlStorageValue>,\n> {\n  next():\n    | {\n        done?: false;\n        value: T;\n      }\n    | {\n        done: true;\n        value?: never;\n      };\n  toArray(): T[];\n  one(): T;\n  raw<U extends SqlStorageValue[]>(): IterableIterator<U>;\n  columnNames: string[];\n  get rowsRead(): number;\n  get rowsWritten(): number;\n  [Symbol.iterator](): IterableIterator<T>;\n}\ninterface Socket {\n  get readable(): ReadableStream;\n  get writable(): WritableStream;\n  get closed(): Promise<void>;\n  get opened(): Promise<SocketInfo>;\n  get upgraded(): boolean;\n  get secureTransport(): \"on\" | \"off\" | \"starttls\";\n  close(): Promise<void>;\n  startTls(options?: TlsOptions): Socket;\n}\ninterface SocketOptions {\n  secureTransport?: string;\n  allowHalfOpen: boolean;\n  highWaterMark?: number | bigint;\n}\ninterface SocketAddress {\n  hostname: string;\n  port: number;\n}\ninterface TlsOptions {\n  expectedServerHostname?: string;\n}\ninterface SocketInfo {\n  remoteAddress?: string;\n  localAddress?: string;\n}\n/**\n * The **`EventSource`** interface is web content's interface to server-sent events.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource)\n */\ndeclare class EventSource extends EventTarget {\n  constructor(url: string, init?: EventSourceEventSourceInit);\n  /**\n   * The **`close()`** method of the EventSource interface closes the connection, if one is made, and sets the ```js-nolint close() ``` None.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close)\n   */\n  close(): void;\n  /**\n   * The **`url`** read-only property of the URL of the source.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url)\n   */\n  get url(): string;\n  /**\n   * The **`withCredentials`** read-only property of the the `EventSource` object was instantiated with CORS credentials set.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials)\n   */\n  get withCredentials(): boolean;\n  /**\n   * The **`readyState`** read-only property of the connection.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState)\n   */\n  get readyState(): number;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */\n  get onopen(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */\n  set onopen(value: any | null);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */\n  get onmessage(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */\n  set onmessage(value: any | null);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */\n  get onerror(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */\n  set onerror(value: any | null);\n  static readonly CONNECTING: number;\n  static readonly OPEN: number;\n  static readonly CLOSED: number;\n  static from(stream: ReadableStream): EventSource;\n}\ninterface EventSourceEventSourceInit {\n  withCredentials?: boolean;\n  fetcher?: Fetcher;\n}\ninterface Container {\n  get running(): boolean;\n  start(options?: ContainerStartupOptions): void;\n  monitor(): Promise<void>;\n  destroy(error?: any): Promise<void>;\n  signal(signo: number): void;\n  getTcpPort(port: number): Fetcher;\n  setInactivityTimeout(durationMs: number | bigint): Promise<void>;\n  interceptOutboundHttp(addr: string, binding: Fetcher): Promise<void>;\n  interceptAllOutboundHttp(binding: Fetcher): Promise<void>;\n}\ninterface ContainerStartupOptions {\n  entrypoint?: string[];\n  enableInternet: boolean;\n  env?: Record<string, string>;\n  hardTimeout?: number | bigint;\n  labels?: Record<string, string>;\n}\n/**\n * The **`MessagePort`** interface of the Channel Messaging API represents one of the two ports of a MessageChannel, allowing messages to be sent from one port and listening out for them arriving at the other.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort)\n */\ndeclare abstract class MessagePort extends EventTarget {\n  /**\n   * The **`postMessage()`** method of the transfers ownership of objects to other browsing contexts.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/postMessage)\n   */\n  postMessage(\n    data?: any,\n    options?: any[] | MessagePortPostMessageOptions,\n  ): void;\n  /**\n   * The **`close()`** method of the MessagePort interface disconnects the port, so it is no longer active.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/close)\n   */\n  close(): void;\n  /**\n   * The **`start()`** method of the MessagePort interface starts the sending of messages queued on the port.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/start)\n   */\n  start(): void;\n  get onmessage(): any | null;\n  set onmessage(value: any | null);\n}\n/**\n * The **`MessageChannel`** interface of the Channel Messaging API allows us to create a new message channel and send data through it via its two MessagePort properties.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel)\n */\ndeclare class MessageChannel {\n  constructor();\n  /**\n   * The **`port1`** read-only property of the the port attached to the context that originated the channel.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel/port1)\n   */\n  readonly port1: MessagePort;\n  /**\n   * The **`port2`** read-only property of the the port attached to the context at the other end of the channel, which the message is initially sent to.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel/port2)\n   */\n  readonly port2: MessagePort;\n}\ninterface MessagePortPostMessageOptions {\n  transfer?: any[];\n}\ntype LoopbackForExport<\n  T extends\n    | (new (...args: any[]) => Rpc.EntrypointBranded)\n    | ExportedHandler<any, any, any>\n    | undefined = undefined,\n> = T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded\n  ? LoopbackServiceStub<InstanceType<T>>\n  : T extends new (...args: any[]) => Rpc.DurableObjectBranded\n    ? LoopbackDurableObjectClass<InstanceType<T>>\n    : T extends ExportedHandler<any, any, any>\n      ? LoopbackServiceStub<undefined>\n      : undefined;\ntype LoopbackServiceStub<\n  T extends Rpc.WorkerEntrypointBranded | undefined = undefined,\n> = Fetcher<T> &\n  (T extends CloudflareWorkersModule.WorkerEntrypoint<any, infer Props>\n    ? (opts: { props?: Props }) => Fetcher<T>\n    : (opts: { props?: any }) => Fetcher<T>);\ntype LoopbackDurableObjectClass<\n  T extends Rpc.DurableObjectBranded | undefined = undefined,\n> = DurableObjectClass<T> &\n  (T extends CloudflareWorkersModule.DurableObject<any, infer Props>\n    ? (opts: { props?: Props }) => DurableObjectClass<T>\n    : (opts: { props?: any }) => DurableObjectClass<T>);\ninterface SyncKvStorage {\n  get<T = unknown>(key: string): T | undefined;\n  list<T = unknown>(options?: SyncKvListOptions): Iterable<[string, T]>;\n  put<T>(key: string, value: T): void;\n  delete(key: string): boolean;\n}\ninterface SyncKvListOptions {\n  start?: string;\n  startAfter?: string;\n  end?: string;\n  prefix?: string;\n  reverse?: boolean;\n  limit?: number;\n}\ninterface WorkerStub {\n  getEntrypoint<T extends Rpc.WorkerEntrypointBranded | undefined>(\n    name?: string,\n    options?: WorkerStubEntrypointOptions,\n  ): Fetcher<T>;\n}\ninterface WorkerStubEntrypointOptions {\n  props?: any;\n}\ninterface WorkerLoader {\n  get(\n    name: string | null,\n    getCode: () => WorkerLoaderWorkerCode | Promise<WorkerLoaderWorkerCode>,\n  ): WorkerStub;\n  load(code: WorkerLoaderWorkerCode): WorkerStub;\n}\ninterface WorkerLoaderModule {\n  js?: string;\n  cjs?: string;\n  text?: string;\n  data?: ArrayBuffer;\n  json?: any;\n  py?: string;\n  wasm?: ArrayBuffer;\n}\ninterface WorkerLoaderWorkerCode {\n  compatibilityDate: string;\n  compatibilityFlags?: string[];\n  allowExperimental?: boolean;\n  mainModule: string;\n  modules: Record<string, WorkerLoaderModule | string>;\n  env?: any;\n  globalOutbound?: Fetcher | null;\n  tails?: Fetcher[];\n  streamingTails?: Fetcher[];\n}\n/**\n * The Workers runtime supports a subset of the Performance API, used to measure timing and performance,\n * as well as timing of subrequests and other operations.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/)\n */\ndeclare abstract class Performance {\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancetimeorigin) */\n  get timeOrigin(): number;\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancenow) */\n  now(): number;\n  /**\n   * The **`toJSON()`** method of the Performance interface is a Serialization; it returns a JSON representation of the Performance object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/toJSON)\n   */\n  toJSON(): object;\n}\n// AI Search V2 API Error Interfaces\ninterface AiSearchInternalError extends Error {}\ninterface AiSearchNotFoundError extends Error {}\ninterface AiSearchNameNotSetError extends Error {}\n// AI Search V2 Request Types\ntype AiSearchSearchRequest = {\n  messages: Array<{\n    role: \"system\" | \"developer\" | \"user\" | \"assistant\" | \"tool\";\n    content: string | null;\n  }>;\n  ai_search_options?: {\n    retrieval?: {\n      retrieval_type?: \"vector\" | \"keyword\" | \"hybrid\";\n      /** Match threshold (0-1, default 0.4) */\n      match_threshold?: number;\n      /** Maximum number of results (1-50, default 10) */\n      max_num_results?: number;\n      filters?: VectorizeVectorMetadataFilter;\n      /** Context expansion (0-3, default 0) */\n      context_expansion?: number;\n      [key: string]: unknown;\n    };\n    query_rewrite?: {\n      enabled?: boolean;\n      model?: string;\n      rewrite_prompt?: string;\n      [key: string]: unknown;\n    };\n    reranking?: {\n      /** Enable reranking (default false) */\n      enabled?: boolean;\n      model?: \"@cf/baai/bge-reranker-base\" | \"\";\n      /** Match threshold (0-1, default 0.4) */\n      match_threshold?: number;\n      [key: string]: unknown;\n    };\n    [key: string]: unknown;\n  };\n};\ntype AiSearchChatCompletionsRequest = {\n  messages: Array<{\n    role: \"system\" | \"developer\" | \"user\" | \"assistant\" | \"tool\";\n    content: string | null;\n  }>;\n  model?: string;\n  stream?: boolean;\n  ai_search_options?: {\n    retrieval?: {\n      retrieval_type?: \"vector\" | \"keyword\" | \"hybrid\";\n      match_threshold?: number;\n      max_num_results?: number;\n      filters?: VectorizeVectorMetadataFilter;\n      context_expansion?: number;\n      [key: string]: unknown;\n    };\n    query_rewrite?: {\n      enabled?: boolean;\n      model?: string;\n      rewrite_prompt?: string;\n      [key: string]: unknown;\n    };\n    reranking?: {\n      enabled?: boolean;\n      model?: \"@cf/baai/bge-reranker-base\" | \"\";\n      match_threshold?: number;\n      [key: string]: unknown;\n    };\n    [key: string]: unknown;\n  };\n  [key: string]: unknown;\n};\n// AI Search V2 Response Types\ntype AiSearchSearchResponse = {\n  search_query: string;\n  chunks: Array<{\n    id: string;\n    type: string;\n    /** Match score (0-1) */\n    score: number;\n    text: string;\n    item: {\n      timestamp?: number;\n      key: string;\n      metadata?: Record<string, unknown>;\n    };\n    scoring_details?: {\n      /** Keyword match score (0-1) */\n      keyword_score?: number;\n      /** Vector similarity score (0-1) */\n      vector_score?: number;\n    };\n  }>;\n};\ntype AiSearchListResponse = Array<{\n  id: string;\n  internal_id?: string;\n  account_id?: string;\n  account_tag?: string;\n  /** Whether the instance is enabled (default true) */\n  enable?: boolean;\n  type?: \"r2\" | \"web-crawler\";\n  source?: string;\n  [key: string]: unknown;\n}>;\ntype AiSearchConfig = {\n  /** Instance ID (1-32 chars, pattern: ^[a-z0-9_]+(?:-[a-z0-9_]+)*$) */\n  id: string;\n  type: \"r2\" | \"web-crawler\";\n  source: string;\n  source_params?: object;\n  /** Token ID (UUID format) */\n  token_id?: string;\n  ai_gateway_id?: string;\n  /** Enable query rewriting (default false) */\n  rewrite_query?: boolean;\n  /** Enable reranking (default false) */\n  reranking?: boolean;\n  embedding_model?: string;\n  ai_search_model?: string;\n};\ntype AiSearchInstance = {\n  id: string;\n  enable?: boolean;\n  type?: \"r2\" | \"web-crawler\";\n  source?: string;\n  [key: string]: unknown;\n};\n// AI Search Instance Service - Instance-level operations\ndeclare abstract class AiSearchInstanceService {\n  /**\n   * Search the AI Search instance for relevant chunks.\n   * @param params Search request with messages and AI search options\n   * @returns Search response with matching chunks\n   */\n  search(params: AiSearchSearchRequest): Promise<AiSearchSearchResponse>;\n  /**\n   * Generate chat completions with AI Search context.\n   * @param params Chat completions request with optional streaming\n   * @returns Response object (if streaming) or chat completion result\n   */\n  chatCompletions(\n    params: AiSearchChatCompletionsRequest,\n  ): Promise<Response | object>;\n  /**\n   * Delete this AI Search instance.\n   */\n  delete(): Promise<void>;\n}\n// AI Search Account Service - Account-level operations\ndeclare abstract class AiSearchAccountService {\n  /**\n   * List all AI Search instances in the account.\n   * @returns Array of AI Search instances\n   */\n  list(): Promise<AiSearchListResponse>;\n  /**\n   * Get an AI Search instance by ID.\n   * @param name Instance ID\n   * @returns Instance service for performing operations\n   */\n  get(name: string): AiSearchInstanceService;\n  /**\n   * Create a new AI Search instance.\n   * @param config Instance configuration\n   * @returns Instance service for performing operations\n   */\n  create(config: AiSearchConfig): Promise<AiSearchInstanceService>;\n}\ntype AiImageClassificationInput = {\n  image: number[];\n};\ntype AiImageClassificationOutput = {\n  score?: number;\n  label?: string;\n}[];\ndeclare abstract class BaseAiImageClassification {\n  inputs: AiImageClassificationInput;\n  postProcessedOutputs: AiImageClassificationOutput;\n}\ntype AiImageToTextInput = {\n  image: number[];\n  prompt?: string;\n  max_tokens?: number;\n  temperature?: number;\n  top_p?: number;\n  top_k?: number;\n  seed?: number;\n  repetition_penalty?: number;\n  frequency_penalty?: number;\n  presence_penalty?: number;\n  raw?: boolean;\n  messages?: RoleScopedChatInput[];\n};\ntype AiImageToTextOutput = {\n  description: string;\n};\ndeclare abstract class BaseAiImageToText {\n  inputs: AiImageToTextInput;\n  postProcessedOutputs: AiImageToTextOutput;\n}\ntype AiImageTextToTextInput = {\n  image: string;\n  prompt?: string;\n  max_tokens?: number;\n  temperature?: number;\n  ignore_eos?: boolean;\n  top_p?: number;\n  top_k?: number;\n  seed?: number;\n  repetition_penalty?: number;\n  frequency_penalty?: number;\n  presence_penalty?: number;\n  raw?: boolean;\n  messages?: RoleScopedChatInput[];\n};\ntype AiImageTextToTextOutput = {\n  description: string;\n};\ndeclare abstract class BaseAiImageTextToText {\n  inputs: AiImageTextToTextInput;\n  postProcessedOutputs: AiImageTextToTextOutput;\n}\ntype AiMultimodalEmbeddingsInput = {\n  image: string;\n  text: string[];\n};\ntype AiIMultimodalEmbeddingsOutput = {\n  data: number[][];\n  shape: number[];\n};\ndeclare abstract class BaseAiMultimodalEmbeddings {\n  inputs: AiImageTextToTextInput;\n  postProcessedOutputs: AiImageTextToTextOutput;\n}\ntype AiObjectDetectionInput = {\n  image: number[];\n};\ntype AiObjectDetectionOutput = {\n  score?: number;\n  label?: string;\n}[];\ndeclare abstract class BaseAiObjectDetection {\n  inputs: AiObjectDetectionInput;\n  postProcessedOutputs: AiObjectDetectionOutput;\n}\ntype AiSentenceSimilarityInput = {\n  source: string;\n  sentences: string[];\n};\ntype AiSentenceSimilarityOutput = number[];\ndeclare abstract class BaseAiSentenceSimilarity {\n  inputs: AiSentenceSimilarityInput;\n  postProcessedOutputs: AiSentenceSimilarityOutput;\n}\ntype AiAutomaticSpeechRecognitionInput = {\n  audio: number[];\n};\ntype AiAutomaticSpeechRecognitionOutput = {\n  text?: string;\n  words?: {\n    word: string;\n    start: number;\n    end: number;\n  }[];\n  vtt?: string;\n};\ndeclare abstract class BaseAiAutomaticSpeechRecognition {\n  inputs: AiAutomaticSpeechRecognitionInput;\n  postProcessedOutputs: AiAutomaticSpeechRecognitionOutput;\n}\ntype AiSummarizationInput = {\n  input_text: string;\n  max_length?: number;\n};\ntype AiSummarizationOutput = {\n  summary: string;\n};\ndeclare abstract class BaseAiSummarization {\n  inputs: AiSummarizationInput;\n  postProcessedOutputs: AiSummarizationOutput;\n}\ntype AiTextClassificationInput = {\n  text: string;\n};\ntype AiTextClassificationOutput = {\n  score?: number;\n  label?: string;\n}[];\ndeclare abstract class BaseAiTextClassification {\n  inputs: AiTextClassificationInput;\n  postProcessedOutputs: AiTextClassificationOutput;\n}\ntype AiTextEmbeddingsInput = {\n  text: string | string[];\n};\ntype AiTextEmbeddingsOutput = {\n  shape: number[];\n  data: number[][];\n};\ndeclare abstract class BaseAiTextEmbeddings {\n  inputs: AiTextEmbeddingsInput;\n  postProcessedOutputs: AiTextEmbeddingsOutput;\n}\ntype RoleScopedChatInput = {\n  role:\n    | \"user\"\n    | \"assistant\"\n    | \"system\"\n    | \"tool\"\n    | (string & NonNullable<unknown>);\n  content: string;\n  name?: string;\n};\ntype AiTextGenerationToolLegacyInput = {\n  name: string;\n  description: string;\n  parameters?: {\n    type: \"object\" | (string & NonNullable<unknown>);\n    properties: {\n      [key: string]: {\n        type: string;\n        description?: string;\n      };\n    };\n    required: string[];\n  };\n};\ntype AiTextGenerationToolInput = {\n  type: \"function\" | (string & NonNullable<unknown>);\n  function: {\n    name: string;\n    description: string;\n    parameters?: {\n      type: \"object\" | (string & NonNullable<unknown>);\n      properties: {\n        [key: string]: {\n          type: string;\n          description?: string;\n        };\n      };\n      required: string[];\n    };\n  };\n};\ntype AiTextGenerationFunctionsInput = {\n  name: string;\n  code: string;\n};\ntype AiTextGenerationResponseFormat = {\n  type: string;\n  json_schema?: any;\n};\ntype AiTextGenerationInput = {\n  prompt?: string;\n  raw?: boolean;\n  stream?: boolean;\n  max_tokens?: number;\n  temperature?: number;\n  top_p?: number;\n  top_k?: number;\n  seed?: number;\n  repetition_penalty?: number;\n  frequency_penalty?: number;\n  presence_penalty?: number;\n  messages?: RoleScopedChatInput[];\n  response_format?: AiTextGenerationResponseFormat;\n  tools?:\n    | AiTextGenerationToolInput[]\n    | AiTextGenerationToolLegacyInput[]\n    | (object & NonNullable<unknown>);\n  functions?: AiTextGenerationFunctionsInput[];\n};\ntype AiTextGenerationToolLegacyOutput = {\n  name: string;\n  arguments: unknown;\n};\ntype AiTextGenerationToolOutput = {\n  id: string;\n  type: \"function\";\n  function: {\n    name: string;\n    arguments: string;\n  };\n};\ntype UsageTags = {\n  prompt_tokens: number;\n  completion_tokens: number;\n  total_tokens: number;\n};\ntype AiTextGenerationOutput = {\n  response?: string;\n  tool_calls?: AiTextGenerationToolLegacyOutput[] &\n    AiTextGenerationToolOutput[];\n  usage?: UsageTags;\n};\ndeclare abstract class BaseAiTextGeneration {\n  inputs: AiTextGenerationInput;\n  postProcessedOutputs: AiTextGenerationOutput;\n}\ntype AiTextToSpeechInput = {\n  prompt: string;\n  lang?: string;\n};\ntype AiTextToSpeechOutput =\n  | Uint8Array\n  | {\n      audio: string;\n    };\ndeclare abstract class BaseAiTextToSpeech {\n  inputs: AiTextToSpeechInput;\n  postProcessedOutputs: AiTextToSpeechOutput;\n}\ntype AiTextToImageInput = {\n  prompt: string;\n  negative_prompt?: string;\n  height?: number;\n  width?: number;\n  image?: number[];\n  image_b64?: string;\n  mask?: number[];\n  num_steps?: number;\n  strength?: number;\n  guidance?: number;\n  seed?: number;\n};\ntype AiTextToImageOutput = ReadableStream<Uint8Array>;\ndeclare abstract class BaseAiTextToImage {\n  inputs: AiTextToImageInput;\n  postProcessedOutputs: AiTextToImageOutput;\n}\ntype AiTranslationInput = {\n  text: string;\n  target_lang: string;\n  source_lang?: string;\n};\ntype AiTranslationOutput = {\n  translated_text?: string;\n};\ndeclare abstract class BaseAiTranslation {\n  inputs: AiTranslationInput;\n  postProcessedOutputs: AiTranslationOutput;\n}\n/**\n * Workers AI support for OpenAI's Chat Completions API\n */\ntype ChatCompletionContentPartText = {\n  type: \"text\";\n  text: string;\n};\ntype ChatCompletionContentPartImage = {\n  type: \"image_url\";\n  image_url: {\n    url: string;\n    detail?: \"auto\" | \"low\" | \"high\";\n  };\n};\ntype ChatCompletionContentPartInputAudio = {\n  type: \"input_audio\";\n  input_audio: {\n    /** Base64 encoded audio data. */\n    data: string;\n    format: \"wav\" | \"mp3\";\n  };\n};\ntype ChatCompletionContentPartFile = {\n  type: \"file\";\n  file: {\n    /** Base64 encoded file data. */\n    file_data?: string;\n    /** The ID of an uploaded file. */\n    file_id?: string;\n    filename?: string;\n  };\n};\ntype ChatCompletionContentPartRefusal = {\n  type: \"refusal\";\n  refusal: string;\n};\ntype ChatCompletionContentPart =\n  | ChatCompletionContentPartText\n  | ChatCompletionContentPartImage\n  | ChatCompletionContentPartInputAudio\n  | ChatCompletionContentPartFile;\ntype FunctionDefinition = {\n  name: string;\n  description?: string;\n  parameters?: Record<string, unknown>;\n  strict?: boolean | null;\n};\ntype ChatCompletionFunctionTool = {\n  type: \"function\";\n  function: FunctionDefinition;\n};\ntype ChatCompletionCustomToolGrammarFormat = {\n  type: \"grammar\";\n  grammar: {\n    definition: string;\n    syntax: \"lark\" | \"regex\";\n  };\n};\ntype ChatCompletionCustomToolTextFormat = {\n  type: \"text\";\n};\ntype ChatCompletionCustomToolFormat =\n  | ChatCompletionCustomToolTextFormat\n  | ChatCompletionCustomToolGrammarFormat;\ntype ChatCompletionCustomTool = {\n  type: \"custom\";\n  custom: {\n    name: string;\n    description?: string;\n    format?: ChatCompletionCustomToolFormat;\n  };\n};\ntype ChatCompletionTool = ChatCompletionFunctionTool | ChatCompletionCustomTool;\ntype ChatCompletionMessageFunctionToolCall = {\n  id: string;\n  type: \"function\";\n  function: {\n    name: string;\n    /** JSON-encoded arguments string. */\n    arguments: string;\n  };\n};\ntype ChatCompletionMessageCustomToolCall = {\n  id: string;\n  type: \"custom\";\n  custom: {\n    name: string;\n    input: string;\n  };\n};\ntype ChatCompletionMessageToolCall =\n  | ChatCompletionMessageFunctionToolCall\n  | ChatCompletionMessageCustomToolCall;\ntype ChatCompletionToolChoiceFunction = {\n  type: \"function\";\n  function: {\n    name: string;\n  };\n};\ntype ChatCompletionToolChoiceCustom = {\n  type: \"custom\";\n  custom: {\n    name: string;\n  };\n};\ntype ChatCompletionToolChoiceAllowedTools = {\n  type: \"allowed_tools\";\n  allowed_tools: {\n    mode: \"auto\" | \"required\";\n    tools: Array<Record<string, unknown>>;\n  };\n};\ntype ChatCompletionToolChoiceOption =\n  | \"none\"\n  | \"auto\"\n  | \"required\"\n  | ChatCompletionToolChoiceFunction\n  | ChatCompletionToolChoiceCustom\n  | ChatCompletionToolChoiceAllowedTools;\ntype DeveloperMessage = {\n  role: \"developer\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n  name?: string;\n};\ntype SystemMessage = {\n  role: \"system\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n  name?: string;\n};\n/**\n * Permissive merged content part used inside UserMessage arrays.\n *\n * Cabidela has a limitation where anyOf/oneOf with enum-based discrimination\n * inside nested array items does not correctly match different branches for\n * different array elements, so the schema uses a single merged object.\n */\ntype UserMessageContentPart = {\n  type: \"text\" | \"image_url\" | \"input_audio\" | \"file\";\n  text?: string;\n  image_url?: {\n    url?: string;\n    detail?: \"auto\" | \"low\" | \"high\";\n  };\n  input_audio?: {\n    data?: string;\n    format?: \"wav\" | \"mp3\";\n  };\n  file?: {\n    file_data?: string;\n    file_id?: string;\n    filename?: string;\n  };\n};\ntype UserMessage = {\n  role: \"user\";\n  content: string | Array<UserMessageContentPart>;\n  name?: string;\n};\ntype AssistantMessageContentPart = {\n  type: \"text\" | \"refusal\";\n  text?: string;\n  refusal?: string;\n};\ntype AssistantMessage = {\n  role: \"assistant\";\n  content?: string | null | Array<AssistantMessageContentPart>;\n  refusal?: string | null;\n  name?: string;\n  audio?: {\n    id: string;\n  };\n  tool_calls?: Array<ChatCompletionMessageToolCall>;\n  function_call?: {\n    name: string;\n    arguments: string;\n  };\n};\ntype ToolMessage = {\n  role: \"tool\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n  tool_call_id: string;\n};\ntype FunctionMessage = {\n  role: \"function\";\n  content: string;\n  name: string;\n};\ntype ChatCompletionMessageParam =\n  | DeveloperMessage\n  | SystemMessage\n  | UserMessage\n  | AssistantMessage\n  | ToolMessage\n  | FunctionMessage;\ntype ChatCompletionsResponseFormatText = {\n  type: \"text\";\n};\ntype ChatCompletionsResponseFormatJSONObject = {\n  type: \"json_object\";\n};\ntype ResponseFormatJSONSchema = {\n  type: \"json_schema\";\n  json_schema: {\n    name: string;\n    description?: string;\n    schema?: Record<string, unknown>;\n    strict?: boolean | null;\n  };\n};\ntype ResponseFormat =\n  | ChatCompletionsResponseFormatText\n  | ChatCompletionsResponseFormatJSONObject\n  | ResponseFormatJSONSchema;\ntype ChatCompletionsStreamOptions = {\n  include_usage?: boolean;\n  include_obfuscation?: boolean;\n};\ntype PredictionContent = {\n  type: \"content\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n};\ntype AudioParams = {\n  voice:\n    | string\n    | {\n        id: string;\n      };\n  format: \"wav\" | \"aac\" | \"mp3\" | \"flac\" | \"opus\" | \"pcm16\";\n};\ntype WebSearchUserLocation = {\n  type: \"approximate\";\n  approximate: {\n    city?: string;\n    country?: string;\n    region?: string;\n    timezone?: string;\n  };\n};\ntype WebSearchOptions = {\n  search_context_size?: \"low\" | \"medium\" | \"high\";\n  user_location?: WebSearchUserLocation;\n};\ntype ChatTemplateKwargs = {\n  /** Whether to enable reasoning, enabled by default. */\n  enable_thinking?: boolean;\n  /** If false, preserves reasoning context between turns. */\n  clear_thinking?: boolean;\n};\n/** Shared optional properties used by both Prompt and Messages input branches. */\ntype ChatCompletionsCommonOptions = {\n  model?: string;\n  audio?: AudioParams;\n  frequency_penalty?: number | null;\n  logit_bias?: Record<string, unknown> | null;\n  logprobs?: boolean | null;\n  top_logprobs?: number | null;\n  max_tokens?: number | null;\n  max_completion_tokens?: number | null;\n  metadata?: Record<string, unknown> | null;\n  modalities?: Array<\"text\" | \"audio\"> | null;\n  n?: number | null;\n  parallel_tool_calls?: boolean;\n  prediction?: PredictionContent;\n  presence_penalty?: number | null;\n  reasoning_effort?: \"low\" | \"medium\" | \"high\" | null;\n  chat_template_kwargs?: ChatTemplateKwargs;\n  response_format?: ResponseFormat;\n  seed?: number | null;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n  stop?: string | Array<string> | null;\n  store?: boolean | null;\n  stream?: boolean | null;\n  stream_options?: ChatCompletionsStreamOptions;\n  temperature?: number | null;\n  tool_choice?: ChatCompletionToolChoiceOption;\n  tools?: Array<ChatCompletionTool>;\n  top_p?: number | null;\n  user?: string;\n  web_search_options?: WebSearchOptions;\n  function_call?:\n    | \"none\"\n    | \"auto\"\n    | {\n        name: string;\n      };\n  functions?: Array<FunctionDefinition>;\n};\ntype PromptTokensDetails = {\n  cached_tokens?: number;\n  audio_tokens?: number;\n};\ntype CompletionTokensDetails = {\n  reasoning_tokens?: number;\n  audio_tokens?: number;\n  accepted_prediction_tokens?: number;\n  rejected_prediction_tokens?: number;\n};\ntype CompletionUsage = {\n  prompt_tokens: number;\n  completion_tokens: number;\n  total_tokens: number;\n  prompt_tokens_details?: PromptTokensDetails;\n  completion_tokens_details?: CompletionTokensDetails;\n};\ntype ChatCompletionTopLogprob = {\n  token: string;\n  logprob: number;\n  bytes: Array<number> | null;\n};\ntype ChatCompletionTokenLogprob = {\n  token: string;\n  logprob: number;\n  bytes: Array<number> | null;\n  top_logprobs: Array<ChatCompletionTopLogprob>;\n};\ntype ChatCompletionAudio = {\n  id: string;\n  /** Base64 encoded audio bytes. */\n  data: string;\n  expires_at: number;\n  transcript: string;\n};\ntype ChatCompletionUrlCitation = {\n  type: \"url_citation\";\n  url_citation: {\n    url: string;\n    title: string;\n    start_index: number;\n    end_index: number;\n  };\n};\ntype ChatCompletionResponseMessage = {\n  role: \"assistant\";\n  content: string | null;\n  refusal: string | null;\n  annotations?: Array<ChatCompletionUrlCitation>;\n  audio?: ChatCompletionAudio;\n  tool_calls?: Array<ChatCompletionMessageToolCall>;\n  function_call?: {\n    name: string;\n    arguments: string;\n  } | null;\n};\ntype ChatCompletionLogprobs = {\n  content: Array<ChatCompletionTokenLogprob> | null;\n  refusal?: Array<ChatCompletionTokenLogprob> | null;\n};\ntype ChatCompletionChoice = {\n  index: number;\n  message: ChatCompletionResponseMessage;\n  finish_reason:\n    | \"stop\"\n    | \"length\"\n    | \"tool_calls\"\n    | \"content_filter\"\n    | \"function_call\";\n  logprobs: ChatCompletionLogprobs | null;\n};\ntype ChatCompletionsPromptInput = {\n  prompt: string;\n} & ChatCompletionsCommonOptions;\ntype ChatCompletionsMessagesInput = {\n  messages: Array<ChatCompletionMessageParam>;\n} & ChatCompletionsCommonOptions;\ntype ChatCompletionsOutput = {\n  id: string;\n  object: string;\n  created: number;\n  model: string;\n  choices: Array<ChatCompletionChoice>;\n  usage?: CompletionUsage;\n  system_fingerprint?: string | null;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n};\n/**\n * Workers AI support for OpenAI's Responses API\n * Reference: https://github.com/openai/openai-node/blob/master/src/resources/responses/responses.ts\n *\n * It's a stripped down version from its source.\n * It currently supports basic function calling, json mode and accepts images as input.\n *\n * It does not include types for WebSearch, CodeInterpreter, FileInputs, MCP, CustomTools.\n * We plan to add those incrementally as model + platform capabilities evolve.\n */\ntype ResponsesInput = {\n  background?: boolean | null;\n  conversation?: string | ResponseConversationParam | null;\n  include?: Array<ResponseIncludable> | null;\n  input?: string | ResponseInput;\n  instructions?: string | null;\n  max_output_tokens?: number | null;\n  parallel_tool_calls?: boolean | null;\n  previous_response_id?: string | null;\n  prompt_cache_key?: string;\n  reasoning?: Reasoning | null;\n  safety_identifier?: string;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n  stream?: boolean | null;\n  stream_options?: StreamOptions | null;\n  temperature?: number | null;\n  text?: ResponseTextConfig;\n  tool_choice?: ToolChoiceOptions | ToolChoiceFunction;\n  tools?: Array<Tool>;\n  top_p?: number | null;\n  truncation?: \"auto\" | \"disabled\" | null;\n};\ntype ResponsesOutput = {\n  id?: string;\n  created_at?: number;\n  output_text?: string;\n  error?: ResponseError | null;\n  incomplete_details?: ResponseIncompleteDetails | null;\n  instructions?: string | Array<ResponseInputItem> | null;\n  object?: \"response\";\n  output?: Array<ResponseOutputItem>;\n  parallel_tool_calls?: boolean;\n  temperature?: number | null;\n  tool_choice?: ToolChoiceOptions | ToolChoiceFunction;\n  tools?: Array<Tool>;\n  top_p?: number | null;\n  max_output_tokens?: number | null;\n  previous_response_id?: string | null;\n  prompt?: ResponsePrompt | null;\n  reasoning?: Reasoning | null;\n  safety_identifier?: string;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n  status?: ResponseStatus;\n  text?: ResponseTextConfig;\n  truncation?: \"auto\" | \"disabled\" | null;\n  usage?: ResponseUsage;\n};\ntype EasyInputMessage = {\n  content: string | ResponseInputMessageContentList;\n  role: \"user\" | \"assistant\" | \"system\" | \"developer\";\n  type?: \"message\";\n};\ntype ResponsesFunctionTool = {\n  name: string;\n  parameters: {\n    [key: string]: unknown;\n  } | null;\n  strict: boolean | null;\n  type: \"function\";\n  description?: string | null;\n};\ntype ResponseIncompleteDetails = {\n  reason?: \"max_output_tokens\" | \"content_filter\";\n};\ntype ResponsePrompt = {\n  id: string;\n  variables?: {\n    [key: string]: string | ResponseInputText | ResponseInputImage;\n  } | null;\n  version?: string | null;\n};\ntype Reasoning = {\n  effort?: ReasoningEffort | null;\n  generate_summary?: \"auto\" | \"concise\" | \"detailed\" | null;\n  summary?: \"auto\" | \"concise\" | \"detailed\" | null;\n};\ntype ResponseContent =\n  | ResponseInputText\n  | ResponseInputImage\n  | ResponseOutputText\n  | ResponseOutputRefusal\n  | ResponseContentReasoningText;\ntype ResponseContentReasoningText = {\n  text: string;\n  type: \"reasoning_text\";\n};\ntype ResponseConversationParam = {\n  id: string;\n};\ntype ResponseCreatedEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.created\";\n};\ntype ResponseCustomToolCallOutput = {\n  call_id: string;\n  output: string | Array<ResponseInputText | ResponseInputImage>;\n  type: \"custom_tool_call_output\";\n  id?: string;\n};\ntype ResponseError = {\n  code:\n    | \"server_error\"\n    | \"rate_limit_exceeded\"\n    | \"invalid_prompt\"\n    | \"vector_store_timeout\"\n    | \"invalid_image\"\n    | \"invalid_image_format\"\n    | \"invalid_base64_image\"\n    | \"invalid_image_url\"\n    | \"image_too_large\"\n    | \"image_too_small\"\n    | \"image_parse_error\"\n    | \"image_content_policy_violation\"\n    | \"invalid_image_mode\"\n    | \"image_file_too_large\"\n    | \"unsupported_image_media_type\"\n    | \"empty_image_file\"\n    | \"failed_to_download_image\"\n    | \"image_file_not_found\";\n  message: string;\n};\ntype ResponseErrorEvent = {\n  code: string | null;\n  message: string;\n  param: string | null;\n  sequence_number: number;\n  type: \"error\";\n};\ntype ResponseFailedEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.failed\";\n};\ntype ResponseFormatText = {\n  type: \"text\";\n};\ntype ResponseFormatJSONObject = {\n  type: \"json_object\";\n};\ntype ResponseFormatTextConfig =\n  | ResponseFormatText\n  | ResponseFormatTextJSONSchemaConfig\n  | ResponseFormatJSONObject;\ntype ResponseFormatTextJSONSchemaConfig = {\n  name: string;\n  schema: {\n    [key: string]: unknown;\n  };\n  type: \"json_schema\";\n  description?: string;\n  strict?: boolean | null;\n};\ntype ResponseFunctionCallArgumentsDeltaEvent = {\n  delta: string;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.function_call_arguments.delta\";\n};\ntype ResponseFunctionCallArgumentsDoneEvent = {\n  arguments: string;\n  item_id: string;\n  name: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.function_call_arguments.done\";\n};\ntype ResponseFunctionCallOutputItem =\n  | ResponseInputTextContent\n  | ResponseInputImageContent;\ntype ResponseFunctionCallOutputItemList = Array<ResponseFunctionCallOutputItem>;\ntype ResponseFunctionToolCall = {\n  arguments: string;\n  call_id: string;\n  name: string;\n  type: \"function_call\";\n  id?: string;\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n};\ninterface ResponseFunctionToolCallItem extends ResponseFunctionToolCall {\n  id: string;\n}\ntype ResponseFunctionToolCallOutputItem = {\n  id: string;\n  call_id: string;\n  output: string | Array<ResponseInputText | ResponseInputImage>;\n  type: \"function_call_output\";\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n};\ntype ResponseIncludable =\n  | \"message.input_image.image_url\"\n  | \"message.output_text.logprobs\";\ntype ResponseIncompleteEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.incomplete\";\n};\ntype ResponseInput = Array<ResponseInputItem>;\ntype ResponseInputContent = ResponseInputText | ResponseInputImage;\ntype ResponseInputImage = {\n  detail: \"low\" | \"high\" | \"auto\";\n  type: \"input_image\";\n  /**\n   * Base64 encoded image\n   */\n  image_url?: string | null;\n};\ntype ResponseInputImageContent = {\n  type: \"input_image\";\n  detail?: \"low\" | \"high\" | \"auto\" | null;\n  /**\n   * Base64 encoded image\n   */\n  image_url?: string | null;\n};\ntype ResponseInputItem =\n  | EasyInputMessage\n  | ResponseInputItemMessage\n  | ResponseOutputMessage\n  | ResponseFunctionToolCall\n  | ResponseInputItemFunctionCallOutput\n  | ResponseReasoningItem;\ntype ResponseInputItemFunctionCallOutput = {\n  call_id: string;\n  output: string | ResponseFunctionCallOutputItemList;\n  type: \"function_call_output\";\n  id?: string | null;\n  status?: \"in_progress\" | \"completed\" | \"incomplete\" | null;\n};\ntype ResponseInputItemMessage = {\n  content: ResponseInputMessageContentList;\n  role: \"user\" | \"system\" | \"developer\";\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n  type?: \"message\";\n};\ntype ResponseInputMessageContentList = Array<ResponseInputContent>;\ntype ResponseInputMessageItem = {\n  id: string;\n  content: ResponseInputMessageContentList;\n  role: \"user\" | \"system\" | \"developer\";\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n  type?: \"message\";\n};\ntype ResponseInputText = {\n  text: string;\n  type: \"input_text\";\n};\ntype ResponseInputTextContent = {\n  text: string;\n  type: \"input_text\";\n};\ntype ResponseItem =\n  | ResponseInputMessageItem\n  | ResponseOutputMessage\n  | ResponseFunctionToolCallItem\n  | ResponseFunctionToolCallOutputItem;\ntype ResponseOutputItem =\n  | ResponseOutputMessage\n  | ResponseFunctionToolCall\n  | ResponseReasoningItem;\ntype ResponseOutputItemAddedEvent = {\n  item: ResponseOutputItem;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.output_item.added\";\n};\ntype ResponseOutputItemDoneEvent = {\n  item: ResponseOutputItem;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.output_item.done\";\n};\ntype ResponseOutputMessage = {\n  id: string;\n  content: Array<ResponseOutputText | ResponseOutputRefusal>;\n  role: \"assistant\";\n  status: \"in_progress\" | \"completed\" | \"incomplete\";\n  type: \"message\";\n};\ntype ResponseOutputRefusal = {\n  refusal: string;\n  type: \"refusal\";\n};\ntype ResponseOutputText = {\n  text: string;\n  type: \"output_text\";\n  logprobs?: Array<Logprob>;\n};\ntype ResponseReasoningItem = {\n  id: string;\n  summary: Array<ResponseReasoningSummaryItem>;\n  type: \"reasoning\";\n  content?: Array<ResponseReasoningContentItem>;\n  encrypted_content?: string | null;\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n};\ntype ResponseReasoningSummaryItem = {\n  text: string;\n  type: \"summary_text\";\n};\ntype ResponseReasoningContentItem = {\n  text: string;\n  type: \"reasoning_text\";\n};\ntype ResponseReasoningTextDeltaEvent = {\n  content_index: number;\n  delta: string;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.reasoning_text.delta\";\n};\ntype ResponseReasoningTextDoneEvent = {\n  content_index: number;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  text: string;\n  type: \"response.reasoning_text.done\";\n};\ntype ResponseRefusalDeltaEvent = {\n  content_index: number;\n  delta: string;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.refusal.delta\";\n};\ntype ResponseRefusalDoneEvent = {\n  content_index: number;\n  item_id: string;\n  output_index: number;\n  refusal: string;\n  sequence_number: number;\n  type: \"response.refusal.done\";\n};\ntype ResponseStatus =\n  | \"completed\"\n  | \"failed\"\n  | \"in_progress\"\n  | \"cancelled\"\n  | \"queued\"\n  | \"incomplete\";\ntype ResponseStreamEvent =\n  | ResponseCompletedEvent\n  | ResponseCreatedEvent\n  | ResponseErrorEvent\n  | ResponseFunctionCallArgumentsDeltaEvent\n  | ResponseFunctionCallArgumentsDoneEvent\n  | ResponseFailedEvent\n  | ResponseIncompleteEvent\n  | ResponseOutputItemAddedEvent\n  | ResponseOutputItemDoneEvent\n  | ResponseReasoningTextDeltaEvent\n  | ResponseReasoningTextDoneEvent\n  | ResponseRefusalDeltaEvent\n  | ResponseRefusalDoneEvent\n  | ResponseTextDeltaEvent\n  | ResponseTextDoneEvent;\ntype ResponseCompletedEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.completed\";\n};\ntype ResponseTextConfig = {\n  format?: ResponseFormatTextConfig;\n  verbosity?: \"low\" | \"medium\" | \"high\" | null;\n};\ntype ResponseTextDeltaEvent = {\n  content_index: number;\n  delta: string;\n  item_id: string;\n  logprobs: Array<Logprob>;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.output_text.delta\";\n};\ntype ResponseTextDoneEvent = {\n  content_index: number;\n  item_id: string;\n  logprobs: Array<Logprob>;\n  output_index: number;\n  sequence_number: number;\n  text: string;\n  type: \"response.output_text.done\";\n};\ntype Logprob = {\n  token: string;\n  logprob: number;\n  top_logprobs?: Array<TopLogprob>;\n};\ntype TopLogprob = {\n  token?: string;\n  logprob?: number;\n};\ntype ResponseUsage = {\n  input_tokens: number;\n  output_tokens: number;\n  total_tokens: number;\n};\ntype Tool = ResponsesFunctionTool;\ntype ToolChoiceFunction = {\n  name: string;\n  type: \"function\";\n};\ntype ToolChoiceOptions = \"none\";\ntype ReasoningEffort = \"minimal\" | \"low\" | \"medium\" | \"high\" | null;\ntype StreamOptions = {\n  include_obfuscation?: boolean;\n};\n/** Marks keys from T that aren't in U as optional never */\ntype Without<T, U> = {\n  [P in Exclude<keyof T, keyof U>]?: never;\n};\n/** Either T or U, but not both (mutually exclusive) */\ntype XOR<T, U> = (T & Without<U, T>) | (U & Without<T, U>);\ntype Ai_Cf_Baai_Bge_Base_En_V1_5_Input =\n  | {\n      text: string | string[];\n      /**\n       * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        text: string | string[];\n        /**\n         * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n         */\n        pooling?: \"mean\" | \"cls\";\n      }[];\n    };\ntype Ai_Cf_Baai_Bge_Base_En_V1_5_Output =\n  | {\n      shape?: number[];\n      /**\n       * Embeddings of the requested text values\n       */\n      data?: number[][];\n      /**\n       * The pooling method used in the embedding process.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | Ai_Cf_Baai_Bge_Base_En_V1_5_AsyncResponse;\ninterface Ai_Cf_Baai_Bge_Base_En_V1_5_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\ndeclare abstract class Base_Ai_Cf_Baai_Bge_Base_En_V1_5 {\n  inputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Output;\n}\ntype Ai_Cf_Openai_Whisper_Input =\n  | string\n  | {\n      /**\n       * An array of integers that represent the audio data constrained to 8-bit unsigned integer values\n       */\n      audio: number[];\n    };\ninterface Ai_Cf_Openai_Whisper_Output {\n  /**\n   * The transcription\n   */\n  text: string;\n  word_count?: number;\n  words?: {\n    word?: string;\n    /**\n     * The second this word begins in the recording\n     */\n    start?: number;\n    /**\n     * The ending second when the word completes\n     */\n    end?: number;\n  }[];\n  vtt?: string;\n}\ndeclare abstract class Base_Ai_Cf_Openai_Whisper {\n  inputs: Ai_Cf_Openai_Whisper_Input;\n  postProcessedOutputs: Ai_Cf_Openai_Whisper_Output;\n}\ntype Ai_Cf_Meta_M2M100_1_2B_Input =\n  | {\n      /**\n       * The text to be translated\n       */\n      text: string;\n      /**\n       * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified\n       */\n      source_lang?: string;\n      /**\n       * The language code to translate the text into (e.g., 'es' for Spanish)\n       */\n      target_lang: string;\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        /**\n         * The text to be translated\n         */\n        text: string;\n        /**\n         * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified\n         */\n        source_lang?: string;\n        /**\n         * The language code to translate the text into (e.g., 'es' for Spanish)\n         */\n        target_lang: string;\n      }[];\n    };\ntype Ai_Cf_Meta_M2M100_1_2B_Output =\n  | {\n      /**\n       * The translated text in the target language\n       */\n      translated_text?: string;\n    }\n  | Ai_Cf_Meta_M2M100_1_2B_AsyncResponse;\ninterface Ai_Cf_Meta_M2M100_1_2B_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\ndeclare abstract class Base_Ai_Cf_Meta_M2M100_1_2B {\n  inputs: Ai_Cf_Meta_M2M100_1_2B_Input;\n  postProcessedOutputs: Ai_Cf_Meta_M2M100_1_2B_Output;\n}\ntype Ai_Cf_Baai_Bge_Small_En_V1_5_Input =\n  | {\n      text: string | string[];\n      /**\n       * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        text: string | string[];\n        /**\n         * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n         */\n        pooling?: \"mean\" | \"cls\";\n      }[];\n    };\ntype Ai_Cf_Baai_Bge_Small_En_V1_5_Output =\n  | {\n      shape?: number[];\n      /**\n       * Embeddings of the requested text values\n       */\n      data?: number[][];\n      /**\n       * The pooling method used in the embedding process.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | Ai_Cf_Baai_Bge_Small_En_V1_5_AsyncResponse;\ninterface Ai_Cf_Baai_Bge_Small_En_V1_5_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\ndeclare abstract class Base_Ai_Cf_Baai_Bge_Small_En_V1_5 {\n  inputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Output;\n}\ntype Ai_Cf_Baai_Bge_Large_En_V1_5_Input =\n  | {\n      text: string | string[];\n      /**\n       * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        text: string | string[];\n        /**\n         * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n         */\n        pooling?: \"mean\" | \"cls\";\n      }[];\n    };\ntype Ai_Cf_Baai_Bge_Large_En_V1_5_Output =\n  | {\n      shape?: number[];\n      /**\n       * Embeddings of the requested text values\n       */\n      data?: number[][];\n      /**\n       * The pooling method used in the embedding process.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | Ai_Cf_Baai_Bge_Large_En_V1_5_AsyncResponse;\ninterface Ai_Cf_Baai_Bge_Large_En_V1_5_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\ndeclare abstract class Base_Ai_Cf_Baai_Bge_Large_En_V1_5 {\n  inputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Output;\n}\ntype Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input =\n  | string\n  | {\n      /**\n       * The input text prompt for the model to generate a response.\n       */\n      prompt?: string;\n      /**\n       * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n       */\n      raw?: boolean;\n      /**\n       * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n       */\n      top_p?: number;\n      /**\n       * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n       */\n      top_k?: number;\n      /**\n       * Random seed for reproducibility of the generation.\n       */\n      seed?: number;\n      /**\n       * Penalty for repeated tokens; higher values discourage repetition.\n       */\n      repetition_penalty?: number;\n      /**\n       * Decreases the likelihood of the model repeating the same lines verbatim.\n       */\n      frequency_penalty?: number;\n      /**\n       * Increases the likelihood of the model introducing new topics.\n       */\n      presence_penalty?: number;\n      image: number[] | (string & NonNullable<unknown>);\n      /**\n       * The maximum number of tokens to generate in the response.\n       */\n      max_tokens?: number;\n    };\ninterface Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output {\n  description?: string;\n}\ndeclare abstract class Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M {\n  inputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input;\n  postProcessedOutputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output;\n}\ntype Ai_Cf_Openai_Whisper_Tiny_En_Input =\n  | string\n  | {\n      /**\n       * An array of integers that represent the audio data constrained to 8-bit unsigned integer values\n       */\n      audio: number[];\n    };\ninterface Ai_Cf_Openai_Whisper_Tiny_En_Output {\n  /**\n   * The transcription\n   */\n  text: string;\n  word_count?: number;\n  words?: {\n    word?: string;\n    /**\n     * The second this word begins in the recording\n     */\n    start?: number;\n    /**\n     * The ending second when the word completes\n     */\n    end?: number;\n  }[];\n  vtt?: string;\n}\ndeclare abstract class Base_Ai_Cf_Openai_Whisper_Tiny_En {\n  inputs: Ai_Cf_Openai_Whisper_Tiny_En_Input;\n  postProcessedOutputs: Ai_Cf_Openai_Whisper_Tiny_En_Output;\n}\ninterface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input {\n  audio:\n    | string\n    | {\n        body?: object;\n        contentType?: string;\n      };\n  /**\n   * Supported tasks are 'translate' or 'transcribe'.\n   */\n  task?: string;\n  /**\n   * The language of the audio being transcribed or translated.\n   */\n  language?: string;\n  /**\n   * Preprocess the audio with a voice activity detection model.\n   */\n  vad_filter?: boolean;\n  /**\n   * A text prompt to help provide context to the model on the contents of the audio.\n   */\n  initial_prompt?: string;\n  /**\n   * The prefix appended to the beginning of the output of the transcription and can guide the transcription result.\n   */\n  prefix?: string;\n  /**\n   * The number of beams to use in beam search decoding. Higher values may improve accuracy at the cost of speed.\n   */\n  beam_size?: number;\n  /**\n   * Whether to condition on previous text during transcription. Setting to false may help prevent hallucination loops.\n   */\n  condition_on_previous_text?: boolean;\n  /**\n   * Threshold for detecting no-speech segments. Segments with no-speech probability above this value are skipped.\n   */\n  no_speech_threshold?: number;\n  /**\n   * Threshold for filtering out segments with high compression ratio, which often indicate repetitive or hallucinated text.\n   */\n  compression_ratio_threshold?: number;\n  /**\n   * Threshold for filtering out segments with low average log probability, indicating low confidence.\n   */\n  log_prob_threshold?: number;\n  /**\n   * Optional threshold (in seconds) to skip silent periods that may cause hallucinations.\n   */\n  hallucination_silence_threshold?: number;\n}\ninterface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output {\n  transcription_info?: {\n    /**\n     * The language of the audio being transcribed or translated.\n     */\n    language?: string;\n    /**\n     * The confidence level or probability of the detected language being accurate, represented as a decimal between 0 and 1.\n     */\n    language_probability?: number;\n    /**\n     * The total duration of the original audio file, in seconds.\n     */\n    duration?: number;\n    /**\n     * The duration of the audio after applying Voice Activity Detection (VAD) to remove silent or irrelevant sections, in seconds.\n     */\n    duration_after_vad?: number;\n  };\n  /**\n   * The complete transcription of the audio.\n   */\n  text: string;\n  /**\n   * The total number of words in the transcription.\n   */\n  word_count?: number;\n  segments?: {\n    /**\n     * The starting time of the segment within the audio, in seconds.\n     */\n    start?: number;\n    /**\n     * The ending time of the segment within the audio, in seconds.\n     */\n    end?: number;\n    /**\n     * The transcription of the segment.\n     */\n    text?: string;\n    /**\n     * The temperature used in the decoding process, controlling randomness in predictions. Lower values result in more deterministic outputs.\n     */\n    temperature?: number;\n    /**\n     * The average log probability of the predictions for the words in this segment, indicating overall confidence.\n     */\n    avg_logprob?: number;\n    /**\n     * The compression ratio of the input to the output, measuring how much the text was compressed during the transcription process.\n     */\n    compression_ratio?: number;\n    /**\n     * The probability that the segment contains no speech, represented as a decimal between 0 and 1.\n     */\n    no_speech_prob?: number;\n    words?: {\n      /**\n       * The individual word transcribed from the audio.\n       */\n      word?: string;\n      /**\n       * The starting time of the word within the audio, in seconds.\n       */\n      start?: number;\n      /**\n       * The ending time of the word within the audio, in seconds.\n       */\n      end?: number;\n    }[];\n  }[];\n  /**\n   * The transcription in WebVTT format, which includes timing and text information for use in subtitles.\n   */\n  vtt?: string;\n}\ndeclare abstract class Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo {\n  inputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input;\n  postProcessedOutputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output;\n}\ntype Ai_Cf_Baai_Bge_M3_Input =\n  | Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts\n  | Ai_Cf_Baai_Bge_M3_Input_Embedding\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: (\n        | Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts_1\n        | Ai_Cf_Baai_Bge_M3_Input_Embedding_1\n      )[];\n    };\ninterface Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts {\n  /**\n   * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts\n   */\n  query?: string;\n  /**\n   * List of provided contexts. Note that the index in this array is important, as the response will refer to it.\n   */\n  contexts: {\n    /**\n     * One of the provided context content\n     */\n    text?: string;\n  }[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\ninterface Ai_Cf_Baai_Bge_M3_Input_Embedding {\n  text: string | string[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\ninterface Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts_1 {\n  /**\n   * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts\n   */\n  query?: string;\n  /**\n   * List of provided contexts. Note that the index in this array is important, as the response will refer to it.\n   */\n  contexts: {\n    /**\n     * One of the provided context content\n     */\n    text?: string;\n  }[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\ninterface Ai_Cf_Baai_Bge_M3_Input_Embedding_1 {\n  text: string | string[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\ntype Ai_Cf_Baai_Bge_M3_Output =\n  | Ai_Cf_Baai_Bge_M3_Output_Query\n  | Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts\n  | Ai_Cf_Baai_Bge_M3_Output_Embedding\n  | Ai_Cf_Baai_Bge_M3_AsyncResponse;\ninterface Ai_Cf_Baai_Bge_M3_Output_Query {\n  response?: {\n    /**\n     * Index of the context in the request\n     */\n    id?: number;\n    /**\n     * Score of the context under the index.\n     */\n    score?: number;\n  }[];\n}\ninterface Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts {\n  response?: number[][];\n  shape?: number[];\n  /**\n   * The pooling method used in the embedding process.\n   */\n  pooling?: \"mean\" | \"cls\";\n}\ninterface Ai_Cf_Baai_Bge_M3_Output_Embedding {\n  shape?: number[];\n  /**\n   * Embeddings of the requested text values\n   */\n  data?: number[][];\n  /**\n   * The pooling method used in the embedding process.\n   */\n  pooling?: \"mean\" | \"cls\";\n}\ninterface Ai_Cf_Baai_Bge_M3_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\ndeclare abstract class Base_Ai_Cf_Baai_Bge_M3 {\n  inputs: Ai_Cf_Baai_Bge_M3_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_M3_Output;\n}\ninterface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input {\n  /**\n   * A text description of the image you want to generate.\n   */\n  prompt: string;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer.\n   */\n  steps?: number;\n}\ninterface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output {\n  /**\n   * The generated image in Base64 format.\n   */\n  image?: string;\n}\ndeclare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output;\n}\ntype Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input =\n  | Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Prompt\n  | Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Messages;\ninterface Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  image?: number[] | (string & NonNullable<unknown>);\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n}\ninterface Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  image?: number[] | (string & NonNullable<unknown>);\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * If true, the response will be streamed back incrementally.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ntype Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response?: string;\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\ndeclare abstract class Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct {\n  inputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output;\n}\ntype Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input =\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Async_Batch;\ninterface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Async_Batch {\n  requests?: {\n    /**\n     * User-supplied reference. This field will be present in the response as well it can be used to reference the request and response. It's NOT validated to be unique.\n     */\n    external_reference?: string;\n    /**\n     * Prompt for the text generation model\n     */\n    prompt?: string;\n    /**\n     * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n     */\n    stream?: boolean;\n    /**\n     * The maximum number of tokens to generate in the response.\n     */\n    max_tokens?: number;\n    /**\n     * Controls the randomness of the output; higher values produce more random results.\n     */\n    temperature?: number;\n    /**\n     * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n     */\n    top_p?: number;\n    /**\n     * Random seed for reproducibility of the generation.\n     */\n    seed?: number;\n    /**\n     * Penalty for repeated tokens; higher values discourage repetition.\n     */\n    repetition_penalty?: number;\n    /**\n     * Decreases the likelihood of the model repeating the same lines verbatim.\n     */\n    frequency_penalty?: number;\n    /**\n     * Increases the likelihood of the model introducing new topics.\n     */\n    presence_penalty?: number;\n    response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_2;\n  }[];\n}\ninterface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_2 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ntype Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output =\n  | {\n      /**\n       * The generated text response from the model\n       */\n      response: string;\n      /**\n       * Usage statistics for the inference request\n       */\n      usage?: {\n        /**\n         * Total number of tokens in input\n         */\n        prompt_tokens?: number;\n        /**\n         * Total number of tokens in output\n         */\n        completion_tokens?: number;\n        /**\n         * Total number of input and output tokens\n         */\n        total_tokens?: number;\n      };\n      /**\n       * An array of tool calls requests made during the response generation\n       */\n      tool_calls?: {\n        /**\n         * The arguments passed to be passed to the tool call request\n         */\n        arguments?: object;\n        /**\n         * The name of the tool to be called\n         */\n        name?: string;\n      }[];\n    }\n  | string\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_AsyncResponse;\ninterface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\ndeclare abstract class Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast {\n  inputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output;\n}\ninterface Ai_Cf_Meta_Llama_Guard_3_8B_Input {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender must alternate between 'user' and 'assistant'.\n     */\n    role: \"user\" | \"assistant\";\n    /**\n     * The content of the message as a string.\n     */\n    content: string;\n  }[];\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Dictate the output format of the generated response.\n   */\n  response_format?: {\n    /**\n     * Set to json_object to process and output generated text as JSON.\n     */\n    type?: string;\n  };\n}\ninterface Ai_Cf_Meta_Llama_Guard_3_8B_Output {\n  response?:\n    | string\n    | {\n        /**\n         * Whether the conversation is safe or not.\n         */\n        safe?: boolean;\n        /**\n         * A list of what hazard categories predicted for the conversation, if the conversation is deemed unsafe.\n         */\n        categories?: string[];\n      };\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n}\ndeclare abstract class Base_Ai_Cf_Meta_Llama_Guard_3_8B {\n  inputs: Ai_Cf_Meta_Llama_Guard_3_8B_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_Guard_3_8B_Output;\n}\ninterface Ai_Cf_Baai_Bge_Reranker_Base_Input {\n  /**\n   * A query you wish to perform against the provided contexts.\n   */\n  /**\n   * Number of returned results starting with the best score.\n   */\n  top_k?: number;\n  /**\n   * List of provided contexts. Note that the index in this array is important, as the response will refer to it.\n   */\n  contexts: {\n    /**\n     * One of the provided context content\n     */\n    text?: string;\n  }[];\n}\ninterface Ai_Cf_Baai_Bge_Reranker_Base_Output {\n  response?: {\n    /**\n     * Index of the context in the request\n     */\n    id?: number;\n    /**\n     * Score of the context under the index.\n     */\n    score?: number;\n  }[];\n}\ndeclare abstract class Base_Ai_Cf_Baai_Bge_Reranker_Base {\n  inputs: Ai_Cf_Baai_Bge_Reranker_Base_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Reranker_Base_Output;\n}\ntype Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input =\n  | Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Prompt\n  | Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Messages;\ninterface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    /**\n     * The content of the message as a string.\n     */\n    content: string;\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ntype Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\ndeclare abstract class Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct {\n  inputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output;\n}\ntype Ai_Cf_Qwen_Qwq_32B_Input =\n  | Ai_Cf_Qwen_Qwq_32B_Prompt\n  | Ai_Cf_Qwen_Qwq_32B_Messages;\ninterface Ai_Cf_Qwen_Qwq_32B_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Qwen_Qwq_32B_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ntype Ai_Cf_Qwen_Qwq_32B_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\ndeclare abstract class Base_Ai_Cf_Qwen_Qwq_32B {\n  inputs: Ai_Cf_Qwen_Qwq_32B_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwq_32B_Output;\n}\ntype Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input =\n  | Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Prompt\n  | Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Messages;\ninterface Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ntype Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\ndeclare abstract class Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct {\n  inputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output;\n}\ntype Ai_Cf_Google_Gemma_3_12B_It_Input =\n  | Ai_Cf_Google_Gemma_3_12B_It_Prompt\n  | Ai_Cf_Google_Gemma_3_12B_It_Messages;\ninterface Ai_Cf_Google_Gemma_3_12B_It_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Google_Gemma_3_12B_It_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ntype Ai_Cf_Google_Gemma_3_12B_It_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\ndeclare abstract class Base_Ai_Cf_Google_Gemma_3_12B_It {\n  inputs: Ai_Cf_Google_Gemma_3_12B_It_Input;\n  postProcessedOutputs: Ai_Cf_Google_Gemma_3_12B_It_Output;\n}\ntype Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input =\n  | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt\n  | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages\n  | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Async_Batch;\ninterface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Async_Batch {\n  requests: (\n    | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt_Inner\n    | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages_Inner\n  )[];\n}\ninterface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt_Inner {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages_Inner {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ntype Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The tool call id.\n     */\n    id?: string;\n    /**\n     * Specifies the type of tool (e.g., 'function').\n     */\n    type?: string;\n    /**\n     * Details of the function tool.\n     */\n    function?: {\n      /**\n       * The name of the tool to be called\n       */\n      name?: string;\n      /**\n       * The arguments passed to be passed to the tool call request\n       */\n      arguments?: object;\n    };\n  }[];\n};\ndeclare abstract class Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct {\n  inputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output;\n}\ntype Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Input =\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Async_Batch;\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Async_Batch {\n  requests: (\n    | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt_1\n    | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages_1\n  )[];\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt_1 {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_2;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_2 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages_1 {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_3;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_3 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ntype Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Output =\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Chat_Completion_Response\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Text_Completion_Response\n  | string\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_AsyncResponse;\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Chat_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"chat.completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index?: number;\n    /**\n     * The message generated by the model\n     */\n    message?: {\n      /**\n       * Role of the message author\n       */\n      role: string;\n      /**\n       * The content of the message\n       */\n      content: string;\n      /**\n       * Internal reasoning content (if available)\n       */\n      reasoning_content?: string;\n      /**\n       * Tool calls made by the assistant\n       */\n      tool_calls?: {\n        /**\n         * Unique identifier for the tool call\n         */\n        id: string;\n        /**\n         * Type of tool call\n         */\n        type: \"function\";\n        function: {\n          /**\n           * Name of the function to call\n           */\n          name: string;\n          /**\n           * JSON string of arguments for the function\n           */\n          arguments: string;\n        };\n      }[];\n    };\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason?: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * Log probabilities for the prompt (if requested)\n   */\n  prompt_logprobs?: {} | null;\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Text_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"text_completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index: number;\n    /**\n     * The generated text completion\n     */\n    text: string;\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n    /**\n     * Log probabilities for the prompt (if requested)\n     */\n    prompt_logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n}\ninterface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\ndeclare abstract class Base_Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8 {\n  inputs: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Output;\n}\ninterface Ai_Cf_Deepgram_Nova_3_Input {\n  audio: {\n    body: object;\n    contentType: string;\n  };\n  /**\n   * Sets how the model will interpret strings submitted to the custom_topic param. When strict, the model will only return topics submitted using the custom_topic param. When extended, the model will return its own detected topics in addition to those submitted using the custom_topic param.\n   */\n  custom_topic_mode?: \"extended\" | \"strict\";\n  /**\n   * Custom topics you want the model to detect within your input audio or text if present Submit up to 100\n   */\n  custom_topic?: string;\n  /**\n   * Sets how the model will interpret intents submitted to the custom_intent param. When strict, the model will only return intents submitted using the custom_intent param. When extended, the model will return its own detected intents in addition those submitted using the custom_intents param\n   */\n  custom_intent_mode?: \"extended\" | \"strict\";\n  /**\n   * Custom intents you want the model to detect within your input audio if present\n   */\n  custom_intent?: string;\n  /**\n   * Identifies and extracts key entities from content in submitted audio\n   */\n  detect_entities?: boolean;\n  /**\n   * Identifies the dominant language spoken in submitted audio\n   */\n  detect_language?: boolean;\n  /**\n   * Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0\n   */\n  diarize?: boolean;\n  /**\n   * Identify and extract key entities from content in submitted audio\n   */\n  dictation?: boolean;\n  /**\n   * Specify the expected encoding of your submitted audio\n   */\n  encoding?:\n    | \"linear16\"\n    | \"flac\"\n    | \"mulaw\"\n    | \"amr-nb\"\n    | \"amr-wb\"\n    | \"opus\"\n    | \"speex\"\n    | \"g729\";\n  /**\n   * Arbitrary key-value pairs that are attached to the API response for usage in downstream processing\n   */\n  extra?: string;\n  /**\n   * Filler Words can help transcribe interruptions in your audio, like 'uh' and 'um'\n   */\n  filler_words?: boolean;\n  /**\n   * Key term prompting can boost or suppress specialized terminology and brands.\n   */\n  keyterm?: string;\n  /**\n   * Keywords can boost or suppress specialized terminology and brands.\n   */\n  keywords?: string;\n  /**\n   * The BCP-47 language tag that hints at the primary spoken language. Depending on the Model and API endpoint you choose only certain languages are available.\n   */\n  language?: string;\n  /**\n   * Spoken measurements will be converted to their corresponding abbreviations.\n   */\n  measurements?: boolean;\n  /**\n   * Opts out requests from the Deepgram Model Improvement Program. Refer to our Docs for pricing impacts before setting this to true. https://dpgr.am/deepgram-mip.\n   */\n  mip_opt_out?: boolean;\n  /**\n   * Mode of operation for the model representing broad area of topic that will be talked about in the supplied audio\n   */\n  mode?: \"general\" | \"medical\" | \"finance\";\n  /**\n   * Transcribe each audio channel independently.\n   */\n  multichannel?: boolean;\n  /**\n   * Numerals converts numbers from written format to numerical format.\n   */\n  numerals?: boolean;\n  /**\n   * Splits audio into paragraphs to improve transcript readability.\n   */\n  paragraphs?: boolean;\n  /**\n   * Profanity Filter looks for recognized profanity and converts it to the nearest recognized non-profane word or removes it from the transcript completely.\n   */\n  profanity_filter?: boolean;\n  /**\n   * Add punctuation and capitalization to the transcript.\n   */\n  punctuate?: boolean;\n  /**\n   * Redaction removes sensitive information from your transcripts.\n   */\n  redact?: string;\n  /**\n   * Search for terms or phrases in submitted audio and replaces them.\n   */\n  replace?: string;\n  /**\n   * Search for terms or phrases in submitted audio.\n   */\n  search?: string;\n  /**\n   * Recognizes the sentiment throughout a transcript or text.\n   */\n  sentiment?: boolean;\n  /**\n   * Apply formatting to transcript output. When set to true, additional formatting will be applied to transcripts to improve readability.\n   */\n  smart_format?: boolean;\n  /**\n   * Detect topics throughout a transcript or text.\n   */\n  topics?: boolean;\n  /**\n   * Segments speech into meaningful semantic units.\n   */\n  utterances?: boolean;\n  /**\n   * Seconds to wait before detecting a pause between words in submitted audio.\n   */\n  utt_split?: number;\n  /**\n   * The number of channels in the submitted audio\n   */\n  channels?: number;\n  /**\n   * Specifies whether the streaming endpoint should provide ongoing transcription updates as more audio is received. When set to true, the endpoint sends continuous updates, meaning transcription results may evolve over time. Note: Supported only for webosockets.\n   */\n  interim_results?: boolean;\n  /**\n   * Indicates how long model will wait to detect whether a speaker has finished speaking or pauses for a significant period of time. When set to a value, the streaming endpoint immediately finalizes the transcription for the processed time range and returns the transcript with a speech_final parameter set to true. Can also be set to false to disable endpointing\n   */\n  endpointing?: string;\n  /**\n   * Indicates that speech has started. You'll begin receiving Speech Started messages upon speech starting. Note: Supported only for webosockets.\n   */\n  vad_events?: boolean;\n  /**\n   * Indicates how long model will wait to send an UtteranceEnd message after a word has been transcribed. Use with interim_results. Note: Supported only for webosockets.\n   */\n  utterance_end_ms?: boolean;\n}\ninterface Ai_Cf_Deepgram_Nova_3_Output {\n  results?: {\n    channels?: {\n      alternatives?: {\n        confidence?: number;\n        transcript?: string;\n        words?: {\n          confidence?: number;\n          end?: number;\n          start?: number;\n          word?: string;\n        }[];\n      }[];\n    }[];\n    summary?: {\n      result?: string;\n      short?: string;\n    };\n    sentiments?: {\n      segments?: {\n        text?: string;\n        start_word?: number;\n        end_word?: number;\n        sentiment?: string;\n        sentiment_score?: number;\n      }[];\n      average?: {\n        sentiment?: string;\n        sentiment_score?: number;\n      };\n    };\n  };\n}\ndeclare abstract class Base_Ai_Cf_Deepgram_Nova_3 {\n  inputs: Ai_Cf_Deepgram_Nova_3_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Nova_3_Output;\n}\ninterface Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Input {\n  queries?: string | string[];\n  /**\n   * Optional instruction for the task\n   */\n  instruction?: string;\n  documents?: string | string[];\n  text?: string | string[];\n}\ninterface Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Output {\n  data?: number[][];\n  shape?: number[];\n}\ndeclare abstract class Base_Ai_Cf_Qwen_Qwen3_Embedding_0_6B {\n  inputs: Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Output;\n}\ntype Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Input =\n  | {\n      /**\n       * readable stream with audio data and content-type specified for that data\n       */\n      audio: {\n        body: object;\n        contentType: string;\n      };\n      /**\n       * type of data PCM data that's sent to the inference server as raw array\n       */\n      dtype?: \"uint8\" | \"float32\" | \"float64\";\n    }\n  | {\n      /**\n       * base64 encoded audio data\n       */\n      audio: string;\n      /**\n       * type of data PCM data that's sent to the inference server as raw array\n       */\n      dtype?: \"uint8\" | \"float32\" | \"float64\";\n    };\ninterface Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Output {\n  /**\n   * if true, end-of-turn was detected\n   */\n  is_complete?: boolean;\n  /**\n   * probability of the end-of-turn detection\n   */\n  probability?: number;\n}\ndeclare abstract class Base_Ai_Cf_Pipecat_Ai_Smart_Turn_V2 {\n  inputs: Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Input;\n  postProcessedOutputs: Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Output;\n}\ndeclare abstract class Base_Ai_Cf_Openai_Gpt_Oss_120B {\n  inputs: XOR<ResponsesInput, ChatCompletionsInput>;\n  postProcessedOutputs: XOR<ResponsesOutput, ChatCompletionsOutput>;\n}\ndeclare abstract class Base_Ai_Cf_Openai_Gpt_Oss_20B {\n  inputs: XOR<ResponsesInput, ChatCompletionsInput>;\n  postProcessedOutputs: XOR<ResponsesOutput, ChatCompletionsOutput>;\n}\ninterface Ai_Cf_Leonardo_Phoenix_1_0_Input {\n  /**\n   * A text description of the image you want to generate.\n   */\n  prompt: string;\n  /**\n   * Controls how closely the generated image should adhere to the prompt; higher values make the image more aligned with the prompt\n   */\n  guidance?: number;\n  /**\n   * Random seed for reproducibility of the image generation\n   */\n  seed?: number;\n  /**\n   * The height of the generated image in pixels\n   */\n  height?: number;\n  /**\n   * The width of the generated image in pixels\n   */\n  width?: number;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer\n   */\n  num_steps?: number;\n  /**\n   * Specify what to exclude from the generated images\n   */\n  negative_prompt?: string;\n}\n/**\n * The generated image in JPEG format\n */\ntype Ai_Cf_Leonardo_Phoenix_1_0_Output = string;\ndeclare abstract class Base_Ai_Cf_Leonardo_Phoenix_1_0 {\n  inputs: Ai_Cf_Leonardo_Phoenix_1_0_Input;\n  postProcessedOutputs: Ai_Cf_Leonardo_Phoenix_1_0_Output;\n}\ninterface Ai_Cf_Leonardo_Lucid_Origin_Input {\n  /**\n   * A text description of the image you want to generate.\n   */\n  prompt: string;\n  /**\n   * Controls how closely the generated image should adhere to the prompt; higher values make the image more aligned with the prompt\n   */\n  guidance?: number;\n  /**\n   * Random seed for reproducibility of the image generation\n   */\n  seed?: number;\n  /**\n   * The height of the generated image in pixels\n   */\n  height?: number;\n  /**\n   * The width of the generated image in pixels\n   */\n  width?: number;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer\n   */\n  num_steps?: number;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer\n   */\n  steps?: number;\n}\ninterface Ai_Cf_Leonardo_Lucid_Origin_Output {\n  /**\n   * The generated image in Base64 format.\n   */\n  image?: string;\n}\ndeclare abstract class Base_Ai_Cf_Leonardo_Lucid_Origin {\n  inputs: Ai_Cf_Leonardo_Lucid_Origin_Input;\n  postProcessedOutputs: Ai_Cf_Leonardo_Lucid_Origin_Output;\n}\ninterface Ai_Cf_Deepgram_Aura_1_Input {\n  /**\n   * Speaker used to produce the audio.\n   */\n  speaker?:\n    | \"angus\"\n    | \"asteria\"\n    | \"arcas\"\n    | \"orion\"\n    | \"orpheus\"\n    | \"athena\"\n    | \"luna\"\n    | \"zeus\"\n    | \"perseus\"\n    | \"helios\"\n    | \"hera\"\n    | \"stella\";\n  /**\n   * Encoding of the output audio.\n   */\n  encoding?: \"linear16\" | \"flac\" | \"mulaw\" | \"alaw\" | \"mp3\" | \"opus\" | \"aac\";\n  /**\n   * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type..\n   */\n  container?: \"none\" | \"wav\" | \"ogg\";\n  /**\n   * The text content to be converted to speech\n   */\n  text: string;\n  /**\n   * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable\n   */\n  sample_rate?: number;\n  /**\n   * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type.\n   */\n  bit_rate?: number;\n}\n/**\n * The generated audio in MP3 format\n */\ntype Ai_Cf_Deepgram_Aura_1_Output = string;\ndeclare abstract class Base_Ai_Cf_Deepgram_Aura_1 {\n  inputs: Ai_Cf_Deepgram_Aura_1_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Aura_1_Output;\n}\ninterface Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Input {\n  /**\n   * Input text to translate. Can be a single string or a list of strings.\n   */\n  text: string | string[];\n  /**\n   * Target langauge to translate to\n   */\n  target_language:\n    | \"asm_Beng\"\n    | \"awa_Deva\"\n    | \"ben_Beng\"\n    | \"bho_Deva\"\n    | \"brx_Deva\"\n    | \"doi_Deva\"\n    | \"eng_Latn\"\n    | \"gom_Deva\"\n    | \"gon_Deva\"\n    | \"guj_Gujr\"\n    | \"hin_Deva\"\n    | \"hne_Deva\"\n    | \"kan_Knda\"\n    | \"kas_Arab\"\n    | \"kas_Deva\"\n    | \"kha_Latn\"\n    | \"lus_Latn\"\n    | \"mag_Deva\"\n    | \"mai_Deva\"\n    | \"mal_Mlym\"\n    | \"mar_Deva\"\n    | \"mni_Beng\"\n    | \"mni_Mtei\"\n    | \"npi_Deva\"\n    | \"ory_Orya\"\n    | \"pan_Guru\"\n    | \"san_Deva\"\n    | \"sat_Olck\"\n    | \"snd_Arab\"\n    | \"snd_Deva\"\n    | \"tam_Taml\"\n    | \"tel_Telu\"\n    | \"urd_Arab\"\n    | \"unr_Deva\";\n}\ninterface Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Output {\n  /**\n   * Translated texts\n   */\n  translations: string[];\n}\ndeclare abstract class Base_Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B {\n  inputs: Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Input;\n  postProcessedOutputs: Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Output;\n}\ntype Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Input =\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Async_Batch;\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Async_Batch {\n  requests: (\n    | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt_1\n    | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages_1\n  )[];\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt_1 {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_2;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_2 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages_1 {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_3;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_3 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\ntype Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Output =\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Chat_Completion_Response\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Text_Completion_Response\n  | string\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_AsyncResponse;\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Chat_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"chat.completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index?: number;\n    /**\n     * The message generated by the model\n     */\n    message?: {\n      /**\n       * Role of the message author\n       */\n      role: string;\n      /**\n       * The content of the message\n       */\n      content: string;\n      /**\n       * Internal reasoning content (if available)\n       */\n      reasoning_content?: string;\n      /**\n       * Tool calls made by the assistant\n       */\n      tool_calls?: {\n        /**\n         * Unique identifier for the tool call\n         */\n        id: string;\n        /**\n         * Type of tool call\n         */\n        type: \"function\";\n        function: {\n          /**\n           * Name of the function to call\n           */\n          name: string;\n          /**\n           * JSON string of arguments for the function\n           */\n          arguments: string;\n        };\n      }[];\n    };\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason?: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * Log probabilities for the prompt (if requested)\n   */\n  prompt_logprobs?: {} | null;\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Text_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"text_completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index: number;\n    /**\n     * The generated text completion\n     */\n    text: string;\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n    /**\n     * Log probabilities for the prompt (if requested)\n     */\n    prompt_logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n}\ninterface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\ndeclare abstract class Base_Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It {\n  inputs: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Input;\n  postProcessedOutputs: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Output;\n}\ninterface Ai_Cf_Pfnet_Plamo_Embedding_1B_Input {\n  /**\n   * Input text to embed. Can be a single string or a list of strings.\n   */\n  text: string | string[];\n}\ninterface Ai_Cf_Pfnet_Plamo_Embedding_1B_Output {\n  /**\n   * Embedding vectors, where each vector is a list of floats.\n   */\n  data: number[][];\n  /**\n   * Shape of the embedding data as [number_of_embeddings, embedding_dimension].\n   *\n   * @minItems 2\n   * @maxItems 2\n   */\n  shape: [number, number];\n}\ndeclare abstract class Base_Ai_Cf_Pfnet_Plamo_Embedding_1B {\n  inputs: Ai_Cf_Pfnet_Plamo_Embedding_1B_Input;\n  postProcessedOutputs: Ai_Cf_Pfnet_Plamo_Embedding_1B_Output;\n}\ninterface Ai_Cf_Deepgram_Flux_Input {\n  /**\n   * Encoding of the audio stream. Currently only supports raw signed little-endian 16-bit PCM.\n   */\n  encoding: \"linear16\";\n  /**\n   * Sample rate of the audio stream in Hz.\n   */\n  sample_rate: string;\n  /**\n   * End-of-turn confidence required to fire an eager end-of-turn event. When set, enables EagerEndOfTurn and TurnResumed events. Valid Values 0.3 - 0.9.\n   */\n  eager_eot_threshold?: string;\n  /**\n   * End-of-turn confidence required to finish a turn. Valid Values 0.5 - 0.9.\n   */\n  eot_threshold?: string;\n  /**\n   * A turn will be finished when this much time has passed after speech, regardless of EOT confidence.\n   */\n  eot_timeout_ms?: string;\n  /**\n   * Keyterm prompting can improve recognition of specialized terminology. Pass multiple keyterm query parameters to boost multiple keyterms.\n   */\n  keyterm?: string;\n  /**\n   * Opts out requests from the Deepgram Model Improvement Program. Refer to Deepgram Docs for pricing impacts before setting this to true. https://dpgr.am/deepgram-mip\n   */\n  mip_opt_out?: \"true\" | \"false\";\n  /**\n   * Label your requests for the purpose of identification during usage reporting\n   */\n  tag?: string;\n}\n/**\n * Output will be returned as websocket messages.\n */\ninterface Ai_Cf_Deepgram_Flux_Output {\n  /**\n   * The unique identifier of the request (uuid)\n   */\n  request_id?: string;\n  /**\n   * Starts at 0 and increments for each message the server sends to the client.\n   */\n  sequence_id?: number;\n  /**\n   * The type of event being reported.\n   */\n  event?:\n    | \"Update\"\n    | \"StartOfTurn\"\n    | \"EagerEndOfTurn\"\n    | \"TurnResumed\"\n    | \"EndOfTurn\";\n  /**\n   * The index of the current turn\n   */\n  turn_index?: number;\n  /**\n   * Start time in seconds of the audio range that was transcribed\n   */\n  audio_window_start?: number;\n  /**\n   * End time in seconds of the audio range that was transcribed\n   */\n  audio_window_end?: number;\n  /**\n   * Text that was said over the course of the current turn\n   */\n  transcript?: string;\n  /**\n   * The words in the transcript\n   */\n  words?: {\n    /**\n     * The individual punctuated, properly-cased word from the transcript\n     */\n    word: string;\n    /**\n     * Confidence that this word was transcribed correctly\n     */\n    confidence: number;\n  }[];\n  /**\n   * Confidence that no more speech is coming in this turn\n   */\n  end_of_turn_confidence?: number;\n}\ndeclare abstract class Base_Ai_Cf_Deepgram_Flux {\n  inputs: Ai_Cf_Deepgram_Flux_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Flux_Output;\n}\ninterface Ai_Cf_Deepgram_Aura_2_En_Input {\n  /**\n   * Speaker used to produce the audio.\n   */\n  speaker?:\n    | \"amalthea\"\n    | \"andromeda\"\n    | \"apollo\"\n    | \"arcas\"\n    | \"aries\"\n    | \"asteria\"\n    | \"athena\"\n    | \"atlas\"\n    | \"aurora\"\n    | \"callista\"\n    | \"cora\"\n    | \"cordelia\"\n    | \"delia\"\n    | \"draco\"\n    | \"electra\"\n    | \"harmonia\"\n    | \"helena\"\n    | \"hera\"\n    | \"hermes\"\n    | \"hyperion\"\n    | \"iris\"\n    | \"janus\"\n    | \"juno\"\n    | \"jupiter\"\n    | \"luna\"\n    | \"mars\"\n    | \"minerva\"\n    | \"neptune\"\n    | \"odysseus\"\n    | \"ophelia\"\n    | \"orion\"\n    | \"orpheus\"\n    | \"pandora\"\n    | \"phoebe\"\n    | \"pluto\"\n    | \"saturn\"\n    | \"thalia\"\n    | \"theia\"\n    | \"vesta\"\n    | \"zeus\";\n  /**\n   * Encoding of the output audio.\n   */\n  encoding?: \"linear16\" | \"flac\" | \"mulaw\" | \"alaw\" | \"mp3\" | \"opus\" | \"aac\";\n  /**\n   * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type..\n   */\n  container?: \"none\" | \"wav\" | \"ogg\";\n  /**\n   * The text content to be converted to speech\n   */\n  text: string;\n  /**\n   * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable\n   */\n  sample_rate?: number;\n  /**\n   * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type.\n   */\n  bit_rate?: number;\n}\n/**\n * The generated audio in MP3 format\n */\ntype Ai_Cf_Deepgram_Aura_2_En_Output = string;\ndeclare abstract class Base_Ai_Cf_Deepgram_Aura_2_En {\n  inputs: Ai_Cf_Deepgram_Aura_2_En_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Aura_2_En_Output;\n}\ninterface Ai_Cf_Deepgram_Aura_2_Es_Input {\n  /**\n   * Speaker used to produce the audio.\n   */\n  speaker?:\n    | \"sirio\"\n    | \"nestor\"\n    | \"carina\"\n    | \"celeste\"\n    | \"alvaro\"\n    | \"diana\"\n    | \"aquila\"\n    | \"selena\"\n    | \"estrella\"\n    | \"javier\";\n  /**\n   * Encoding of the output audio.\n   */\n  encoding?: \"linear16\" | \"flac\" | \"mulaw\" | \"alaw\" | \"mp3\" | \"opus\" | \"aac\";\n  /**\n   * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type..\n   */\n  container?: \"none\" | \"wav\" | \"ogg\";\n  /**\n   * The text content to be converted to speech\n   */\n  text: string;\n  /**\n   * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable\n   */\n  sample_rate?: number;\n  /**\n   * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type.\n   */\n  bit_rate?: number;\n}\n/**\n * The generated audio in MP3 format\n */\ntype Ai_Cf_Deepgram_Aura_2_Es_Output = string;\ndeclare abstract class Base_Ai_Cf_Deepgram_Aura_2_Es {\n  inputs: Ai_Cf_Deepgram_Aura_2_Es_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Aura_2_Es_Output;\n}\ninterface Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Input {\n  multipart: {\n    body?: object;\n    contentType?: string;\n  };\n}\ninterface Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Output {\n  /**\n   * Generated image as Base64 string.\n   */\n  image?: string;\n}\ndeclare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Dev {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Output;\n}\ninterface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Input {\n  multipart: {\n    body?: object;\n    contentType?: string;\n  };\n}\ninterface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Output {\n  /**\n   * Generated image as Base64 string.\n   */\n  image?: string;\n}\ndeclare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Output;\n}\ninterface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Input {\n  multipart: {\n    body?: object;\n    contentType?: string;\n  };\n}\ninterface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Output {\n  /**\n   * Generated image as Base64 string.\n   */\n  image?: string;\n}\ndeclare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Output;\n}\ndeclare abstract class Base_Ai_Cf_Zai_Org_Glm_4_7_Flash {\n  inputs: ChatCompletionsInput;\n  postProcessedOutputs: ChatCompletionsOutput;\n}\ndeclare abstract class Base_Ai_Cf_Moonshotai_Kimi_K2_5 {\n  inputs: ChatCompletionsInput;\n  postProcessedOutputs: ChatCompletionsOutput;\n}\ndeclare abstract class Base_Ai_Cf_Nvidia_Nemotron_3_120B_A12B {\n  inputs: ChatCompletionsInput;\n  postProcessedOutputs: ChatCompletionsOutput;\n}\ninterface AiModels {\n  \"@cf/huggingface/distilbert-sst-2-int8\": BaseAiTextClassification;\n  \"@cf/stabilityai/stable-diffusion-xl-base-1.0\": BaseAiTextToImage;\n  \"@cf/runwayml/stable-diffusion-v1-5-inpainting\": BaseAiTextToImage;\n  \"@cf/runwayml/stable-diffusion-v1-5-img2img\": BaseAiTextToImage;\n  \"@cf/lykon/dreamshaper-8-lcm\": BaseAiTextToImage;\n  \"@cf/bytedance/stable-diffusion-xl-lightning\": BaseAiTextToImage;\n  \"@cf/myshell-ai/melotts\": BaseAiTextToSpeech;\n  \"@cf/google/embeddinggemma-300m\": BaseAiTextEmbeddings;\n  \"@cf/microsoft/resnet-50\": BaseAiImageClassification;\n  \"@cf/meta/llama-2-7b-chat-int8\": BaseAiTextGeneration;\n  \"@cf/mistral/mistral-7b-instruct-v0.1\": BaseAiTextGeneration;\n  \"@cf/meta/llama-2-7b-chat-fp16\": BaseAiTextGeneration;\n  \"@hf/thebloke/llama-2-13b-chat-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/mistral-7b-instruct-v0.1-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/zephyr-7b-beta-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/openhermes-2.5-mistral-7b-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/neural-chat-7b-v3-1-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/deepseek-coder-6.7b-base-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/deepseek-coder-6.7b-instruct-awq\": BaseAiTextGeneration;\n  \"@cf/deepseek-ai/deepseek-math-7b-instruct\": BaseAiTextGeneration;\n  \"@cf/defog/sqlcoder-7b-2\": BaseAiTextGeneration;\n  \"@cf/openchat/openchat-3.5-0106\": BaseAiTextGeneration;\n  \"@cf/tiiuae/falcon-7b-instruct\": BaseAiTextGeneration;\n  \"@cf/thebloke/discolm-german-7b-v1-awq\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-0.5b-chat\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-7b-chat-awq\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-14b-chat-awq\": BaseAiTextGeneration;\n  \"@cf/tinyllama/tinyllama-1.1b-chat-v1.0\": BaseAiTextGeneration;\n  \"@cf/microsoft/phi-2\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-1.8b-chat\": BaseAiTextGeneration;\n  \"@cf/mistral/mistral-7b-instruct-v0.2-lora\": BaseAiTextGeneration;\n  \"@hf/nousresearch/hermes-2-pro-mistral-7b\": BaseAiTextGeneration;\n  \"@hf/nexusflow/starling-lm-7b-beta\": BaseAiTextGeneration;\n  \"@hf/google/gemma-7b-it\": BaseAiTextGeneration;\n  \"@cf/meta-llama/llama-2-7b-chat-hf-lora\": BaseAiTextGeneration;\n  \"@cf/google/gemma-2b-it-lora\": BaseAiTextGeneration;\n  \"@cf/google/gemma-7b-it-lora\": BaseAiTextGeneration;\n  \"@hf/mistral/mistral-7b-instruct-v0.2\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3-8b-instruct\": BaseAiTextGeneration;\n  \"@cf/fblgit/una-cybertron-7b-v2-bf16\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3-8b-instruct-awq\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.1-8b-instruct-fp8\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.1-8b-instruct-awq\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.2-3b-instruct\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.2-1b-instruct\": BaseAiTextGeneration;\n  \"@cf/deepseek-ai/deepseek-r1-distill-qwen-32b\": BaseAiTextGeneration;\n  \"@cf/ibm-granite/granite-4.0-h-micro\": BaseAiTextGeneration;\n  \"@cf/facebook/bart-large-cnn\": BaseAiSummarization;\n  \"@cf/llava-hf/llava-1.5-7b-hf\": BaseAiImageToText;\n  \"@cf/baai/bge-base-en-v1.5\": Base_Ai_Cf_Baai_Bge_Base_En_V1_5;\n  \"@cf/openai/whisper\": Base_Ai_Cf_Openai_Whisper;\n  \"@cf/meta/m2m100-1.2b\": Base_Ai_Cf_Meta_M2M100_1_2B;\n  \"@cf/baai/bge-small-en-v1.5\": Base_Ai_Cf_Baai_Bge_Small_En_V1_5;\n  \"@cf/baai/bge-large-en-v1.5\": Base_Ai_Cf_Baai_Bge_Large_En_V1_5;\n  \"@cf/unum/uform-gen2-qwen-500m\": Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M;\n  \"@cf/openai/whisper-tiny-en\": Base_Ai_Cf_Openai_Whisper_Tiny_En;\n  \"@cf/openai/whisper-large-v3-turbo\": Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo;\n  \"@cf/baai/bge-m3\": Base_Ai_Cf_Baai_Bge_M3;\n  \"@cf/black-forest-labs/flux-1-schnell\": Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell;\n  \"@cf/meta/llama-3.2-11b-vision-instruct\": Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct;\n  \"@cf/meta/llama-3.3-70b-instruct-fp8-fast\": Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast;\n  \"@cf/meta/llama-guard-3-8b\": Base_Ai_Cf_Meta_Llama_Guard_3_8B;\n  \"@cf/baai/bge-reranker-base\": Base_Ai_Cf_Baai_Bge_Reranker_Base;\n  \"@cf/qwen/qwen2.5-coder-32b-instruct\": Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct;\n  \"@cf/qwen/qwq-32b\": Base_Ai_Cf_Qwen_Qwq_32B;\n  \"@cf/mistralai/mistral-small-3.1-24b-instruct\": Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct;\n  \"@cf/google/gemma-3-12b-it\": Base_Ai_Cf_Google_Gemma_3_12B_It;\n  \"@cf/meta/llama-4-scout-17b-16e-instruct\": Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct;\n  \"@cf/qwen/qwen3-30b-a3b-fp8\": Base_Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8;\n  \"@cf/deepgram/nova-3\": Base_Ai_Cf_Deepgram_Nova_3;\n  \"@cf/qwen/qwen3-embedding-0.6b\": Base_Ai_Cf_Qwen_Qwen3_Embedding_0_6B;\n  \"@cf/pipecat-ai/smart-turn-v2\": Base_Ai_Cf_Pipecat_Ai_Smart_Turn_V2;\n  \"@cf/openai/gpt-oss-120b\": Base_Ai_Cf_Openai_Gpt_Oss_120B;\n  \"@cf/openai/gpt-oss-20b\": Base_Ai_Cf_Openai_Gpt_Oss_20B;\n  \"@cf/leonardo/phoenix-1.0\": Base_Ai_Cf_Leonardo_Phoenix_1_0;\n  \"@cf/leonardo/lucid-origin\": Base_Ai_Cf_Leonardo_Lucid_Origin;\n  \"@cf/deepgram/aura-1\": Base_Ai_Cf_Deepgram_Aura_1;\n  \"@cf/ai4bharat/indictrans2-en-indic-1B\": Base_Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B;\n  \"@cf/aisingapore/gemma-sea-lion-v4-27b-it\": Base_Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It;\n  \"@cf/pfnet/plamo-embedding-1b\": Base_Ai_Cf_Pfnet_Plamo_Embedding_1B;\n  \"@cf/deepgram/flux\": Base_Ai_Cf_Deepgram_Flux;\n  \"@cf/deepgram/aura-2-en\": Base_Ai_Cf_Deepgram_Aura_2_En;\n  \"@cf/deepgram/aura-2-es\": Base_Ai_Cf_Deepgram_Aura_2_Es;\n  \"@cf/black-forest-labs/flux-2-dev\": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Dev;\n  \"@cf/black-forest-labs/flux-2-klein-4b\": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B;\n  \"@cf/black-forest-labs/flux-2-klein-9b\": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B;\n  \"@cf/zai-org/glm-4.7-flash\": Base_Ai_Cf_Zai_Org_Glm_4_7_Flash;\n  \"@cf/moonshotai/kimi-k2.5\": Base_Ai_Cf_Moonshotai_Kimi_K2_5;\n  \"@cf/nvidia/nemotron-3-120b-a12b\": Base_Ai_Cf_Nvidia_Nemotron_3_120B_A12B;\n}\ntype AiOptions = {\n  /**\n   * Send requests as an asynchronous batch job, only works for supported models\n   * https://developers.cloudflare.com/workers-ai/features/batch-api\n   */\n  queueRequest?: boolean;\n  /**\n   * Establish websocket connections, only works for supported models\n   */\n  websocket?: boolean;\n  /**\n   * Tag your requests to group and view them in Cloudflare dashboard.\n   *\n   * Rules:\n   * Tags must only contain letters, numbers, and the symbols: : - . / @\n   * Each tag can have maximum 50 characters.\n   * Maximum 5 tags are allowed each request.\n   * Duplicate tags will removed.\n   */\n  tags?: string[];\n  gateway?: GatewayOptions;\n  returnRawResponse?: boolean;\n  prefix?: string;\n  extraHeaders?: object;\n};\ntype AiModelsSearchParams = {\n  author?: string;\n  hide_experimental?: boolean;\n  page?: number;\n  per_page?: number;\n  search?: string;\n  source?: number;\n  task?: string;\n};\ntype AiModelsSearchObject = {\n  id: string;\n  source: number;\n  name: string;\n  description: string;\n  task: {\n    id: string;\n    name: string;\n    description: string;\n  };\n  tags: string[];\n  properties: {\n    property_id: string;\n    value: string;\n  }[];\n};\ntype ChatCompletionsBase = XOR<\n  ChatCompletionsPromptInput,\n  ChatCompletionsMessagesInput\n>;\ntype ChatCompletionsInput = XOR<\n  ChatCompletionsBase,\n  {\n    requests: ChatCompletionsBase[];\n  }\n>;\ninterface InferenceUpstreamError extends Error {}\ninterface AiInternalError extends Error {}\ntype AiModelListType = Record<string, any>;\ndeclare abstract class Ai<AiModelList extends AiModelListType = AiModels> {\n  aiGatewayLogId: string | null;\n  gateway(gatewayId: string): AiGateway;\n  /**\n   * Access the AI Search API for managing AI-powered search instances.\n   *\n   * This is the new API that replaces AutoRAG with better namespace separation:\n   * - Account-level operations: `list()`, `create()`\n   * - Instance-level operations: `get(id).search()`, `get(id).chatCompletions()`, `get(id).delete()`\n   *\n   * @example\n   * ```typescript\n   * // List all AI Search instances\n   * const instances = await env.AI.aiSearch.list();\n   *\n   * // Search an instance\n   * const results = await env.AI.aiSearch.get('my-search').search({\n   *   messages: [{ role: 'user', content: 'What is the policy?' }],\n   *   ai_search_options: {\n   *     retrieval: { max_num_results: 10 }\n   *   }\n   * });\n   *\n   * // Generate chat completions with AI Search context\n   * const response = await env.AI.aiSearch.get('my-search').chatCompletions({\n   *   messages: [{ role: 'user', content: 'What is the policy?' }],\n   *   model: '@cf/meta/llama-3.3-70b-instruct-fp8-fast'\n   * });\n   * ```\n   */\n  aiSearch(): AiSearchAccountService;\n  /**\n   * @deprecated AutoRAG has been replaced by AI Search.\n   * Use `env.AI.aiSearch` instead for better API design and new features.\n   *\n   * Migration guide:\n   * - `env.AI.autorag().list()` → `env.AI.aiSearch.list()`\n   * - `env.AI.autorag('id').search({ query: '...' })` → `env.AI.aiSearch.get('id').search({ messages: [{ role: 'user', content: '...' }] })`\n   * - `env.AI.autorag('id').aiSearch(...)` → `env.AI.aiSearch.get('id').chatCompletions(...)`\n   *\n   * Note: The old API continues to work for backwards compatibility, but new projects should use AI Search.\n   *\n   * @see AiSearchAccountService\n   * @param autoragId Optional instance ID (omit for account-level operations)\n   */\n  autorag(autoragId: string): AutoRAG;\n  run<\n    Name extends keyof AiModelList,\n    Options extends AiOptions,\n    InputOptions extends AiModelList[Name][\"inputs\"],\n  >(\n    model: Name,\n    inputs: InputOptions,\n    options?: Options,\n  ): Promise<\n    Options extends\n      | {\n          returnRawResponse: true;\n        }\n      | {\n          websocket: true;\n        }\n      ? Response\n      : InputOptions extends {\n            stream: true;\n          }\n        ? ReadableStream\n        : AiModelList[Name][\"postProcessedOutputs\"]\n  >;\n  models(params?: AiModelsSearchParams): Promise<AiModelsSearchObject[]>;\n  toMarkdown(): ToMarkdownService;\n  toMarkdown(\n    files: MarkdownDocument[],\n    options?: ConversionRequestOptions,\n  ): Promise<ConversionResponse[]>;\n  toMarkdown(\n    files: MarkdownDocument,\n    options?: ConversionRequestOptions,\n  ): Promise<ConversionResponse>;\n}\ntype GatewayRetries = {\n  maxAttempts?: 1 | 2 | 3 | 4 | 5;\n  retryDelayMs?: number;\n  backoff?: \"constant\" | \"linear\" | \"exponential\";\n};\ntype GatewayOptions = {\n  id: string;\n  cacheKey?: string;\n  cacheTtl?: number;\n  skipCache?: boolean;\n  metadata?: Record<string, number | string | boolean | null | bigint>;\n  collectLog?: boolean;\n  eventId?: string;\n  requestTimeoutMs?: number;\n  retries?: GatewayRetries;\n};\ntype UniversalGatewayOptions = Exclude<GatewayOptions, \"id\"> & {\n  /**\n   ** @deprecated\n   */\n  id?: string;\n};\ntype AiGatewayPatchLog = {\n  score?: number | null;\n  feedback?: -1 | 1 | null;\n  metadata?: Record<string, number | string | boolean | null | bigint> | null;\n};\ntype AiGatewayLog = {\n  id: string;\n  provider: string;\n  model: string;\n  model_type?: string;\n  path: string;\n  duration: number;\n  request_type?: string;\n  request_content_type?: string;\n  status_code: number;\n  response_content_type?: string;\n  success: boolean;\n  cached: boolean;\n  tokens_in?: number;\n  tokens_out?: number;\n  metadata?: Record<string, number | string | boolean | null | bigint>;\n  step?: number;\n  cost?: number;\n  custom_cost?: boolean;\n  request_size: number;\n  request_head?: string;\n  request_head_complete: boolean;\n  response_size: number;\n  response_head?: string;\n  response_head_complete: boolean;\n  created_at: Date;\n};\ntype AIGatewayProviders =\n  | \"workers-ai\"\n  | \"anthropic\"\n  | \"aws-bedrock\"\n  | \"azure-openai\"\n  | \"google-vertex-ai\"\n  | \"huggingface\"\n  | \"openai\"\n  | \"perplexity-ai\"\n  | \"replicate\"\n  | \"groq\"\n  | \"cohere\"\n  | \"google-ai-studio\"\n  | \"mistral\"\n  | \"grok\"\n  | \"openrouter\"\n  | \"deepseek\"\n  | \"cerebras\"\n  | \"cartesia\"\n  | \"elevenlabs\"\n  | \"adobe-firefly\";\ntype AIGatewayHeaders = {\n  \"cf-aig-metadata\":\n    | Record<string, number | string | boolean | null | bigint>\n    | string;\n  \"cf-aig-custom-cost\":\n    | {\n        per_token_in?: number;\n        per_token_out?: number;\n      }\n    | {\n        total_cost?: number;\n      }\n    | string;\n  \"cf-aig-cache-ttl\": number | string;\n  \"cf-aig-skip-cache\": boolean | string;\n  \"cf-aig-cache-key\": string;\n  \"cf-aig-event-id\": string;\n  \"cf-aig-request-timeout\": number | string;\n  \"cf-aig-max-attempts\": number | string;\n  \"cf-aig-retry-delay\": number | string;\n  \"cf-aig-backoff\": string;\n  \"cf-aig-collect-log\": boolean | string;\n  Authorization: string;\n  \"Content-Type\": string;\n  [key: string]: string | number | boolean | object;\n};\ntype AIGatewayUniversalRequest = {\n  provider: AIGatewayProviders | string; // eslint-disable-line\n  endpoint: string;\n  headers: Partial<AIGatewayHeaders>;\n  query: unknown;\n};\ninterface AiGatewayInternalError extends Error {}\ninterface AiGatewayLogNotFound extends Error {}\ndeclare abstract class AiGateway {\n  patchLog(logId: string, data: AiGatewayPatchLog): Promise<void>;\n  getLog(logId: string): Promise<AiGatewayLog>;\n  run(\n    data: AIGatewayUniversalRequest | AIGatewayUniversalRequest[],\n    options?: {\n      gateway?: UniversalGatewayOptions;\n      extraHeaders?: object;\n    },\n  ): Promise<Response>;\n  getUrl(provider?: AIGatewayProviders | string): Promise<string>; // eslint-disable-line\n}\n/**\n * @deprecated AutoRAG has been replaced by AI Search. Use AiSearchInternalError instead.\n * @see AiSearchInternalError\n */\ninterface AutoRAGInternalError extends Error {}\n/**\n * @deprecated AutoRAG has been replaced by AI Search. Use AiSearchNotFoundError instead.\n * @see AiSearchNotFoundError\n */\ninterface AutoRAGNotFoundError extends Error {}\n/**\n * @deprecated This error type is no longer used in the AI Search API.\n */\ninterface AutoRAGUnauthorizedError extends Error {}\n/**\n * @deprecated AutoRAG has been replaced by AI Search. Use AiSearchNameNotSetError instead.\n * @see AiSearchNameNotSetError\n */\ninterface AutoRAGNameNotSetError extends Error {}\ntype ComparisonFilter = {\n  key: string;\n  type: \"eq\" | \"ne\" | \"gt\" | \"gte\" | \"lt\" | \"lte\";\n  value: string | number | boolean;\n};\ntype CompoundFilter = {\n  type: \"and\" | \"or\";\n  filters: ComparisonFilter[];\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchSearchRequest with the new API instead.\n * @see AiSearchSearchRequest\n */\ntype AutoRagSearchRequest = {\n  query: string;\n  filters?: CompoundFilter | ComparisonFilter;\n  max_num_results?: number;\n  ranking_options?: {\n    ranker?: string;\n    score_threshold?: number;\n  };\n  reranking?: {\n    enabled?: boolean;\n    model?: string;\n  };\n  rewrite_query?: boolean;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchChatCompletionsRequest with the new API instead.\n * @see AiSearchChatCompletionsRequest\n */\ntype AutoRagAiSearchRequest = AutoRagSearchRequest & {\n  stream?: boolean;\n  system_prompt?: string;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchChatCompletionsRequest with stream: true instead.\n * @see AiSearchChatCompletionsRequest\n */\ntype AutoRagAiSearchRequestStreaming = Omit<\n  AutoRagAiSearchRequest,\n  \"stream\"\n> & {\n  stream: true;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchSearchResponse with the new API instead.\n * @see AiSearchSearchResponse\n */\ntype AutoRagSearchResponse = {\n  object: \"vector_store.search_results.page\";\n  search_query: string;\n  data: {\n    file_id: string;\n    filename: string;\n    score: number;\n    attributes: Record<string, string | number | boolean | null>;\n    content: {\n      type: \"text\";\n      text: string;\n    }[];\n  }[];\n  has_more: boolean;\n  next_page: string | null;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchListResponse with the new API instead.\n * @see AiSearchListResponse\n */\ntype AutoRagListResponse = {\n  id: string;\n  enable: boolean;\n  type: string;\n  source: string;\n  vectorize_name: string;\n  paused: boolean;\n  status: string;\n}[];\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * The new API returns different response formats for chat completions.\n */\ntype AutoRagAiSearchResponse = AutoRagSearchResponse & {\n  response: string;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use the new AI Search API instead: `env.AI.aiSearch`\n *\n * Migration guide:\n * - `env.AI.autorag().list()` → `env.AI.aiSearch.list()`\n * - `env.AI.autorag('id').search(...)` → `env.AI.aiSearch.get('id').search(...)`\n * - `env.AI.autorag('id').aiSearch(...)` → `env.AI.aiSearch.get('id').chatCompletions(...)`\n *\n * @see AiSearchAccountService\n * @see AiSearchInstanceService\n */\ndeclare abstract class AutoRAG {\n  /**\n   * @deprecated Use `env.AI.aiSearch.list()` instead.\n   * @see AiSearchAccountService.list\n   */\n  list(): Promise<AutoRagListResponse>;\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).search(...)` instead.\n   * Note: The new API uses a messages array instead of a query string.\n   * @see AiSearchInstanceService.search\n   */\n  search(params: AutoRagSearchRequest): Promise<AutoRagSearchResponse>;\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).chatCompletions(...)` instead.\n   * @see AiSearchInstanceService.chatCompletions\n   */\n  aiSearch(params: AutoRagAiSearchRequestStreaming): Promise<Response>;\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).chatCompletions(...)` instead.\n   * @see AiSearchInstanceService.chatCompletions\n   */\n  aiSearch(params: AutoRagAiSearchRequest): Promise<AutoRagAiSearchResponse>;\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).chatCompletions(...)` instead.\n   * @see AiSearchInstanceService.chatCompletions\n   */\n  aiSearch(\n    params: AutoRagAiSearchRequest,\n  ): Promise<AutoRagAiSearchResponse | Response>;\n}\ninterface BasicImageTransformations {\n  /**\n   * Maximum width in image pixels. The value must be an integer.\n   */\n  width?: number;\n  /**\n   * Maximum height in image pixels. The value must be an integer.\n   */\n  height?: number;\n  /**\n   * Resizing mode as a string. It affects interpretation of width and height\n   * options:\n   *  - scale-down: Similar to contain, but the image is never enlarged. If\n   *    the image is larger than given width or height, it will be resized.\n   *    Otherwise its original size will be kept.\n   *  - contain: Resizes to maximum size that fits within the given width and\n   *    height. If only a single dimension is given (e.g. only width), the\n   *    image will be shrunk or enlarged to exactly match that dimension.\n   *    Aspect ratio is always preserved.\n   *  - cover: Resizes (shrinks or enlarges) to fill the entire area of width\n   *    and height. If the image has an aspect ratio different from the ratio\n   *    of width and height, it will be cropped to fit.\n   *  - crop: The image will be shrunk and cropped to fit within the area\n   *    specified by width and height. The image will not be enlarged. For images\n   *    smaller than the given dimensions it's the same as scale-down. For\n   *    images larger than the given dimensions, it's the same as cover.\n   *    See also trim.\n   *  - pad: Resizes to the maximum size that fits within the given width and\n   *    height, and then fills the remaining area with a background color\n   *    (white by default). Use of this mode is not recommended, as the same\n   *    effect can be more efficiently achieved with the contain mode and the\n   *    CSS object-fit: contain property.\n   *  - squeeze: Stretches and deforms to the width and height given, even if it\n   *    breaks aspect ratio\n   */\n  fit?: \"scale-down\" | \"contain\" | \"cover\" | \"crop\" | \"pad\" | \"squeeze\";\n  /**\n   * Image segmentation using artificial intelligence models. Sets pixels not\n   * within selected segment area to transparent e.g \"foreground\" sets every\n   * background pixel as transparent.\n   */\n  segment?: \"foreground\";\n  /**\n   * When cropping with fit: \"cover\", this defines the side or point that should\n   * be left uncropped. The value is either a string\n   * \"left\", \"right\", \"top\", \"bottom\", \"auto\", or \"center\" (the default),\n   * or an object {x, y} containing focal point coordinates in the original\n   * image expressed as fractions ranging from 0.0 (top or left) to 1.0\n   * (bottom or right), 0.5 being the center. {fit: \"cover\", gravity: \"top\"} will\n   * crop bottom or left and right sides as necessary, but won’t crop anything\n   * from the top. {fit: \"cover\", gravity: {x:0.5, y:0.2}} will crop each side to\n   * preserve as much as possible around a point at 20% of the height of the\n   * source image.\n   */\n  gravity?:\n    | \"face\"\n    | \"left\"\n    | \"right\"\n    | \"top\"\n    | \"bottom\"\n    | \"center\"\n    | \"auto\"\n    | \"entropy\"\n    | BasicImageTransformationsGravityCoordinates;\n  /**\n   * Background color to add underneath the image. Applies only to images with\n   * transparency (such as PNG). Accepts any CSS color (#RRGGBB, rgba(…),\n   * hsl(…), etc.)\n   */\n  background?: string;\n  /**\n   * Number of degrees (90, 180, 270) to rotate the image by. width and height\n   * options refer to axes after rotation.\n   */\n  rotate?: 0 | 90 | 180 | 270 | 360;\n}\ninterface BasicImageTransformationsGravityCoordinates {\n  x?: number;\n  y?: number;\n  mode?: \"remainder\" | \"box-center\";\n}\n/**\n * In addition to the properties you can set in the RequestInit dict\n * that you pass as an argument to the Request constructor, you can\n * set certain properties of a `cf` object to control how Cloudflare\n * features are applied to that new Request.\n *\n * Note: Currently, these properties cannot be tested in the\n * playground.\n */\ninterface RequestInitCfProperties extends Record<string, unknown> {\n  cacheEverything?: boolean;\n  /**\n   * A request's cache key is what determines if two requests are\n   * \"the same\" for caching purposes. If a request has the same cache key\n   * as some previous request, then we can serve the same cached response for\n   * both. (e.g. 'some-key')\n   *\n   * Only available for Enterprise customers.\n   */\n  cacheKey?: string;\n  /**\n   * This allows you to append additional Cache-Tag response headers\n   * to the origin response without modifications to the origin server.\n   * This will allow for greater control over the Purge by Cache Tag feature\n   * utilizing changes only in the Workers process.\n   *\n   * Only available for Enterprise customers.\n   */\n  cacheTags?: string[];\n  /**\n   * Force response to be cached for a given number of seconds. (e.g. 300)\n   */\n  cacheTtl?: number;\n  /**\n   * Force response to be cached for a given number of seconds based on the Origin status code.\n   * (e.g. { '200-299': 86400, '404': 1, '500-599': 0 })\n   */\n  cacheTtlByStatus?: Record<string, number>;\n  scrapeShield?: boolean;\n  apps?: boolean;\n  image?: RequestInitCfPropertiesImage;\n  minify?: RequestInitCfPropertiesImageMinify;\n  mirage?: boolean;\n  polish?: \"lossy\" | \"lossless\" | \"off\";\n  r2?: RequestInitCfPropertiesR2;\n  /**\n   * Redirects the request to an alternate origin server. You can use this,\n   * for example, to implement load balancing across several origins.\n   * (e.g.us-east.example.com)\n   *\n   * Note - For security reasons, the hostname set in resolveOverride must\n   * be proxied on the same Cloudflare zone of the incoming request.\n   * Otherwise, the setting is ignored. CNAME hosts are allowed, so to\n   * resolve to a host under a different domain or a DNS only domain first\n   * declare a CNAME record within your own zone’s DNS mapping to the\n   * external hostname, set proxy on Cloudflare, then set resolveOverride\n   * to point to that CNAME record.\n   */\n  resolveOverride?: string;\n}\ninterface RequestInitCfPropertiesImageDraw extends BasicImageTransformations {\n  /**\n   * Absolute URL of the image file to use for the drawing. It can be any of\n   * the supported file formats. For drawing of watermarks or non-rectangular\n   * overlays we recommend using PNG or WebP images.\n   */\n  url: string;\n  /**\n   * Floating-point number between 0 (transparent) and 1 (opaque).\n   * For example, opacity: 0.5 makes overlay semitransparent.\n   */\n  opacity?: number;\n  /**\n   * - If set to true, the overlay image will be tiled to cover the entire\n   *   area. This is useful for stock-photo-like watermarks.\n   * - If set to \"x\", the overlay image will be tiled horizontally only\n   *   (form a line).\n   * - If set to \"y\", the overlay image will be tiled vertically only\n   *   (form a line).\n   */\n  repeat?: true | \"x\" | \"y\";\n  /**\n   * Position of the overlay image relative to a given edge. Each property is\n   * an offset in pixels. 0 aligns exactly to the edge. For example, left: 10\n   * positions left side of the overlay 10 pixels from the left edge of the\n   * image it's drawn over. bottom: 0 aligns bottom of the overlay with bottom\n   * of the background image.\n   *\n   * Setting both left & right, or both top & bottom is an error.\n   *\n   * If no position is specified, the image will be centered.\n   */\n  top?: number;\n  left?: number;\n  bottom?: number;\n  right?: number;\n}\ninterface RequestInitCfPropertiesImage extends BasicImageTransformations {\n  /**\n   * Device Pixel Ratio. Default 1. Multiplier for width/height that makes it\n   * easier to specify higher-DPI sizes in <img srcset>.\n   */\n  dpr?: number;\n  /**\n   * Allows you to trim your image. Takes dpr into account and is performed before\n   * resizing or rotation.\n   *\n   * It can be used as:\n   * - left, top, right, bottom - it will specify the number of pixels to cut\n   *   off each side\n   * - width, height - the width/height you'd like to end up with - can be used\n   *   in combination with the properties above\n   * - border - this will automatically trim the surroundings of an image based on\n   *   it's color. It consists of three properties:\n   *    - color: rgb or hex representation of the color you wish to trim (todo: verify the rgba bit)\n   *    - tolerance: difference from color to treat as color\n   *    - keep: the number of pixels of border to keep\n   */\n  trim?:\n    | \"border\"\n    | {\n        top?: number;\n        bottom?: number;\n        left?: number;\n        right?: number;\n        width?: number;\n        height?: number;\n        border?:\n          | boolean\n          | {\n              color?: string;\n              tolerance?: number;\n              keep?: number;\n            };\n      };\n  /**\n   * Quality setting from 1-100 (useful values are in 60-90 range). Lower values\n   * make images look worse, but load faster. The default is 85. It applies only\n   * to JPEG and WebP images. It doesn’t have any effect on PNG.\n   */\n  quality?: number | \"low\" | \"medium-low\" | \"medium-high\" | \"high\";\n  /**\n   * Output format to generate. It can be:\n   *  - avif: generate images in AVIF format.\n   *  - webp: generate images in Google WebP format. Set quality to 100 to get\n   *    the WebP-lossless format.\n   *  - json: instead of generating an image, outputs information about the\n   *    image, in JSON format. The JSON object will contain image size\n   *    (before and after resizing), source image’s MIME type, file size, etc.\n   * - jpeg: generate images in JPEG format.\n   * - png: generate images in PNG format.\n   */\n  format?:\n    | \"avif\"\n    | \"webp\"\n    | \"json\"\n    | \"jpeg\"\n    | \"png\"\n    | \"baseline-jpeg\"\n    | \"png-force\"\n    | \"svg\";\n  /**\n   * Whether to preserve animation frames from input files. Default is true.\n   * Setting it to false reduces animations to still images. This setting is\n   * recommended when enlarging images or processing arbitrary user content,\n   * because large GIF animations can weigh tens or even hundreds of megabytes.\n   * It is also useful to set anim:false when using format:\"json\" to get the\n   * response quicker without the number of frames.\n   */\n  anim?: boolean;\n  /**\n   * What EXIF data should be preserved in the output image. Note that EXIF\n   * rotation and embedded color profiles are always applied (\"baked in\" into\n   * the image), and aren't affected by this option. Note that if the Polish\n   * feature is enabled, all metadata may have been removed already and this\n   * option may have no effect.\n   *  - keep: Preserve most of EXIF metadata, including GPS location if there's\n   *    any.\n   *  - copyright: Only keep the copyright tag, and discard everything else.\n   *    This is the default behavior for JPEG files.\n   *  - none: Discard all invisible EXIF metadata. Currently WebP and PNG\n   *    output formats always discard metadata.\n   */\n  metadata?: \"keep\" | \"copyright\" | \"none\";\n  /**\n   * Strength of sharpening filter to apply to the image. Floating-point\n   * number between 0 (no sharpening, default) and 10 (maximum). 1.0 is a\n   * recommended value for downscaled images.\n   */\n  sharpen?: number;\n  /**\n   * Radius of a blur filter (approximate gaussian). Maximum supported radius\n   * is 250.\n   */\n  blur?: number;\n  /**\n   * Overlays are drawn in the order they appear in the array (last array\n   * entry is the topmost layer).\n   */\n  draw?: RequestInitCfPropertiesImageDraw[];\n  /**\n   * Fetching image from authenticated origin. Setting this property will\n   * pass authentication headers (Authorization, Cookie, etc.) through to\n   * the origin.\n   */\n  \"origin-auth\"?: \"share-publicly\";\n  /**\n   * Adds a border around the image. The border is added after resizing. Border\n   * width takes dpr into account, and can be specified either using a single\n   * width property, or individually for each side.\n   */\n  border?:\n    | {\n        color: string;\n        width: number;\n      }\n    | {\n        color: string;\n        top: number;\n        right: number;\n        bottom: number;\n        left: number;\n      };\n  /**\n   * Increase brightness by a factor. A value of 1.0 equals no change, a value\n   * of 0.5 equals half brightness, and a value of 2.0 equals twice as bright.\n   * 0 is ignored.\n   */\n  brightness?: number;\n  /**\n   * Increase contrast by a factor. A value of 1.0 equals no change, a value of\n   * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is\n   * ignored.\n   */\n  contrast?: number;\n  /**\n   * Increase exposure by a factor. A value of 1.0 equals no change, a value of\n   * 0.5 darkens the image, and a value of 2.0 lightens the image. 0 is ignored.\n   */\n  gamma?: number;\n  /**\n   * Increase contrast by a factor. A value of 1.0 equals no change, a value of\n   * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is\n   * ignored.\n   */\n  saturation?: number;\n  /**\n   * Flips the images horizontally, vertically, or both. Flipping is applied before\n   * rotation, so if you apply flip=h,rotate=90 then the image will be flipped\n   * horizontally, then rotated by 90 degrees.\n   */\n  flip?: \"h\" | \"v\" | \"hv\";\n  /**\n   * Slightly reduces latency on a cache miss by selecting a\n   * quickest-to-compress file format, at a cost of increased file size and\n   * lower image quality. It will usually override the format option and choose\n   * JPEG over WebP or AVIF. We do not recommend using this option, except in\n   * unusual circumstances like resizing uncacheable dynamically-generated\n   * images.\n   */\n  compression?: \"fast\";\n}\ninterface RequestInitCfPropertiesImageMinify {\n  javascript?: boolean;\n  css?: boolean;\n  html?: boolean;\n}\ninterface RequestInitCfPropertiesR2 {\n  /**\n   * Colo id of bucket that an object is stored in\n   */\n  bucketColoId?: number;\n}\n/**\n * Request metadata provided by Cloudflare's edge.\n */\ntype IncomingRequestCfProperties<HostMetadata = unknown> =\n  IncomingRequestCfPropertiesBase &\n    IncomingRequestCfPropertiesBotManagementEnterprise &\n    IncomingRequestCfPropertiesCloudflareForSaaSEnterprise<HostMetadata> &\n    IncomingRequestCfPropertiesGeographicInformation &\n    IncomingRequestCfPropertiesCloudflareAccessOrApiShield;\ninterface IncomingRequestCfPropertiesBase extends Record<string, unknown> {\n  /**\n   * [ASN](https://www.iana.org/assignments/as-numbers/as-numbers.xhtml) of the incoming request.\n   *\n   * @example 395747\n   */\n  asn?: number;\n  /**\n   * The organization which owns the ASN of the incoming request.\n   *\n   * @example \"Google Cloud\"\n   */\n  asOrganization?: string;\n  /**\n   * The original value of the `Accept-Encoding` header if Cloudflare modified it.\n   *\n   * @example \"gzip, deflate, br\"\n   */\n  clientAcceptEncoding?: string;\n  /**\n   * The number of milliseconds it took for the request to reach your worker.\n   *\n   * @example 22\n   */\n  clientTcpRtt?: number;\n  /**\n   * The three-letter [IATA](https://en.wikipedia.org/wiki/IATA_airport_code)\n   * airport code of the data center that the request hit.\n   *\n   * @example \"DFW\"\n   */\n  colo: string;\n  /**\n   * Represents the upstream's response to a\n   * [TCP `keepalive` message](https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html)\n   * from cloudflare.\n   *\n   * For workers with no upstream, this will always be `1`.\n   *\n   * @example 3\n   */\n  edgeRequestKeepAliveStatus: IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus;\n  /**\n   * The HTTP Protocol the request used.\n   *\n   * @example \"HTTP/2\"\n   */\n  httpProtocol: string;\n  /**\n   * The browser-requested prioritization information in the request object.\n   *\n   * If no information was set, defaults to the empty string `\"\"`\n   *\n   * @example \"weight=192;exclusive=0;group=3;group-weight=127\"\n   * @default \"\"\n   */\n  requestPriority: string;\n  /**\n   * The TLS version of the connection to Cloudflare.\n   * In requests served over plaintext (without TLS), this property is the empty string `\"\"`.\n   *\n   * @example \"TLSv1.3\"\n   */\n  tlsVersion: string;\n  /**\n   * The cipher for the connection to Cloudflare.\n   * In requests served over plaintext (without TLS), this property is the empty string `\"\"`.\n   *\n   * @example \"AEAD-AES128-GCM-SHA256\"\n   */\n  tlsCipher: string;\n  /**\n   * Metadata containing the [`HELLO`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2) and [`FINISHED`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9) messages from this request's TLS handshake.\n   *\n   * If the incoming request was served over plaintext (without TLS) this field is undefined.\n   */\n  tlsExportedAuthenticator?: IncomingRequestCfPropertiesExportedAuthenticatorMetadata;\n}\ninterface IncomingRequestCfPropertiesBotManagementBase {\n  /**\n   * Cloudflare’s [level of certainty](https://developers.cloudflare.com/bots/concepts/bot-score/) that a request comes from a bot,\n   * represented as an integer percentage between `1` (almost certainly a bot) and `99` (almost certainly human).\n   *\n   * @example 54\n   */\n  score: number;\n  /**\n   * A boolean value that is true if the request comes from a good bot, like Google or Bing.\n   * Most customers choose to allow this traffic. For more details, see [Traffic from known bots](https://developers.cloudflare.com/firewall/known-issues-and-faq/#how-does-firewall-rules-handle-traffic-from-known-bots).\n   */\n  verifiedBot: boolean;\n  /**\n   * A boolean value that is true if the request originates from a\n   * Cloudflare-verified proxy service.\n   */\n  corporateProxy: boolean;\n  /**\n   * A boolean value that's true if the request matches [file extensions](https://developers.cloudflare.com/bots/reference/static-resources/) for many types of static resources.\n   */\n  staticResource: boolean;\n  /**\n   * List of IDs that correlate to the Bot Management heuristic detections made on a request (you can have multiple heuristic detections on the same request).\n   */\n  detectionIds: number[];\n}\ninterface IncomingRequestCfPropertiesBotManagement {\n  /**\n   * Results of Cloudflare's Bot Management analysis\n   */\n  botManagement: IncomingRequestCfPropertiesBotManagementBase;\n  /**\n   * Duplicate of `botManagement.score`.\n   *\n   * @deprecated\n   */\n  clientTrustScore: number;\n}\ninterface IncomingRequestCfPropertiesBotManagementEnterprise extends IncomingRequestCfPropertiesBotManagement {\n  /**\n   * Results of Cloudflare's Bot Management analysis\n   */\n  botManagement: IncomingRequestCfPropertiesBotManagementBase & {\n    /**\n     * A [JA3 Fingerprint](https://developers.cloudflare.com/bots/concepts/ja3-fingerprint/) to help profile specific SSL/TLS clients\n     * across different destination IPs, Ports, and X509 certificates.\n     */\n    ja3Hash: string;\n  };\n}\ninterface IncomingRequestCfPropertiesCloudflareForSaaSEnterprise<HostMetadata> {\n  /**\n   * Custom metadata set per-host in [Cloudflare for SaaS](https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/).\n   *\n   * This field is only present if you have Cloudflare for SaaS enabled on your account\n   * and you have followed the [required steps to enable it]((https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/domain-support/custom-metadata/)).\n   */\n  hostMetadata?: HostMetadata;\n}\ninterface IncomingRequestCfPropertiesCloudflareAccessOrApiShield {\n  /**\n   * Information about the client certificate presented to Cloudflare.\n   *\n   * This is populated when the incoming request is served over TLS using\n   * either Cloudflare Access or API Shield (mTLS)\n   * and the presented SSL certificate has a valid\n   * [Certificate Serial Number](https://ldapwiki.com/wiki/Certificate%20Serial%20Number)\n   * (i.e., not `null` or `\"\"`).\n   *\n   * Otherwise, a set of placeholder values are used.\n   *\n   * The property `certPresented` will be set to `\"1\"` when\n   * the object is populated (i.e. the above conditions were met).\n   */\n  tlsClientAuth:\n    | IncomingRequestCfPropertiesTLSClientAuth\n    | IncomingRequestCfPropertiesTLSClientAuthPlaceholder;\n}\n/**\n * Metadata about the request's TLS handshake\n */\ninterface IncomingRequestCfPropertiesExportedAuthenticatorMetadata {\n  /**\n   * The client's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal\n   *\n   * @example \"44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d\"\n   */\n  clientHandshake: string;\n  /**\n   * The server's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal\n   *\n   * @example \"44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d\"\n   */\n  serverHandshake: string;\n  /**\n   * The client's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal\n   *\n   * @example \"084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b\"\n   */\n  clientFinished: string;\n  /**\n   * The server's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal\n   *\n   * @example \"084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b\"\n   */\n  serverFinished: string;\n}\n/**\n * Geographic data about the request's origin.\n */\ninterface IncomingRequestCfPropertiesGeographicInformation {\n  /**\n   * The [ISO 3166-1 Alpha 2](https://www.iso.org/iso-3166-country-codes.html) country code the request originated from.\n   *\n   * If your worker is [configured to accept TOR connections](https://support.cloudflare.com/hc/en-us/articles/203306930-Understanding-Cloudflare-Tor-support-and-Onion-Routing), this may also be `\"T1\"`, indicating a request that originated over TOR.\n   *\n   * If Cloudflare is unable to determine where the request originated this property is omitted.\n   *\n   * The country code `\"T1\"` is used for requests originating on TOR.\n   *\n   * @example \"GB\"\n   */\n  country?: Iso3166Alpha2Code | \"T1\";\n  /**\n   * If present, this property indicates that the request originated in the EU\n   *\n   * @example \"1\"\n   */\n  isEUCountry?: \"1\";\n  /**\n   * A two-letter code indicating the continent the request originated from.\n   *\n   * @example \"AN\"\n   */\n  continent?: ContinentCode;\n  /**\n   * The city the request originated from\n   *\n   * @example \"Austin\"\n   */\n  city?: string;\n  /**\n   * Postal code of the incoming request\n   *\n   * @example \"78701\"\n   */\n  postalCode?: string;\n  /**\n   * Latitude of the incoming request\n   *\n   * @example \"30.27130\"\n   */\n  latitude?: string;\n  /**\n   * Longitude of the incoming request\n   *\n   * @example \"-97.74260\"\n   */\n  longitude?: string;\n  /**\n   * Timezone of the incoming request\n   *\n   * @example \"America/Chicago\"\n   */\n  timezone?: string;\n  /**\n   * If known, the ISO 3166-2 name for the first level region associated with\n   * the IP address of the incoming request\n   *\n   * @example \"Texas\"\n   */\n  region?: string;\n  /**\n   * If known, the ISO 3166-2 code for the first-level region associated with\n   * the IP address of the incoming request\n   *\n   * @example \"TX\"\n   */\n  regionCode?: string;\n  /**\n   * Metro code (DMA) of the incoming request\n   *\n   * @example \"635\"\n   */\n  metroCode?: string;\n}\n/** Data about the incoming request's TLS certificate */\ninterface IncomingRequestCfPropertiesTLSClientAuth {\n  /** Always `\"1\"`, indicating that the certificate was presented */\n  certPresented: \"1\";\n  /**\n   * Result of certificate verification.\n   *\n   * @example \"FAILED:self signed certificate\"\n   */\n  certVerified: Exclude<CertVerificationStatus, \"NONE\">;\n  /** The presented certificate's revokation status.\n   *\n   * - A value of `\"1\"` indicates the certificate has been revoked\n   * - A value of `\"0\"` indicates the certificate has not been revoked\n   */\n  certRevoked: \"1\" | \"0\";\n  /**\n   * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html)\n   *\n   * @example \"CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certIssuerDN: string;\n  /**\n   * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html)\n   *\n   * @example \"CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certSubjectDN: string;\n  /**\n   * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted)\n   *\n   * @example \"CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certIssuerDNRFC2253: string;\n  /**\n   * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted)\n   *\n   * @example \"CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certSubjectDNRFC2253: string;\n  /** The certificate issuer's distinguished name (legacy policies) */\n  certIssuerDNLegacy: string;\n  /** The certificate subject's distinguished name (legacy policies) */\n  certSubjectDNLegacy: string;\n  /**\n   * The certificate's serial number\n   *\n   * @example \"00936EACBE07F201DF\"\n   */\n  certSerial: string;\n  /**\n   * The certificate issuer's serial number\n   *\n   * @example \"2489002934BDFEA34\"\n   */\n  certIssuerSerial: string;\n  /**\n   * The certificate's Subject Key Identifier\n   *\n   * @example \"BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4\"\n   */\n  certSKI: string;\n  /**\n   * The certificate issuer's Subject Key Identifier\n   *\n   * @example \"BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4\"\n   */\n  certIssuerSKI: string;\n  /**\n   * The certificate's SHA-1 fingerprint\n   *\n   * @example \"6b9109f323999e52259cda7373ff0b4d26bd232e\"\n   */\n  certFingerprintSHA1: string;\n  /**\n   * The certificate's SHA-256 fingerprint\n   *\n   * @example \"acf77cf37b4156a2708e34c4eb755f9b5dbbe5ebb55adfec8f11493438d19e6ad3f157f81fa3b98278453d5652b0c1fd1d71e5695ae4d709803a4d3f39de9dea\"\n   */\n  certFingerprintSHA256: string;\n  /**\n   * The effective starting date of the certificate\n   *\n   * @example \"Dec 22 19:39:00 2018 GMT\"\n   */\n  certNotBefore: string;\n  /**\n   * The effective expiration date of the certificate\n   *\n   * @example \"Dec 22 19:39:00 2018 GMT\"\n   */\n  certNotAfter: string;\n}\n/** Placeholder values for TLS Client Authorization */\ninterface IncomingRequestCfPropertiesTLSClientAuthPlaceholder {\n  certPresented: \"0\";\n  certVerified: \"NONE\";\n  certRevoked: \"0\";\n  certIssuerDN: \"\";\n  certSubjectDN: \"\";\n  certIssuerDNRFC2253: \"\";\n  certSubjectDNRFC2253: \"\";\n  certIssuerDNLegacy: \"\";\n  certSubjectDNLegacy: \"\";\n  certSerial: \"\";\n  certIssuerSerial: \"\";\n  certSKI: \"\";\n  certIssuerSKI: \"\";\n  certFingerprintSHA1: \"\";\n  certFingerprintSHA256: \"\";\n  certNotBefore: \"\";\n  certNotAfter: \"\";\n}\n/** Possible outcomes of TLS verification */\ndeclare type CertVerificationStatus =\n  /** Authentication succeeded */\n  | \"SUCCESS\"\n  /** No certificate was presented */\n  | \"NONE\"\n  /** Failed because the certificate was self-signed */\n  | \"FAILED:self signed certificate\"\n  /** Failed because the certificate failed a trust chain check */\n  | \"FAILED:unable to verify the first certificate\"\n  /** Failed because the certificate not yet valid */\n  | \"FAILED:certificate is not yet valid\"\n  /** Failed because the certificate is expired */\n  | \"FAILED:certificate has expired\"\n  /** Failed for another unspecified reason */\n  | \"FAILED\";\n/**\n * An upstream endpoint's response to a TCP `keepalive` message from Cloudflare.\n */\ndeclare type IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus =\n  | 0 /** Unknown */\n  | 1 /** no keepalives (not found) */\n  | 2 /** no connection re-use, opening keepalive connection failed */\n  | 3 /** no connection re-use, keepalive accepted and saved */\n  | 4 /** connection re-use, refused by the origin server (`TCP FIN`) */\n  | 5; /** connection re-use, accepted by the origin server */\n/** ISO 3166-1 Alpha-2 codes */\ndeclare type Iso3166Alpha2Code =\n  | \"AD\"\n  | \"AE\"\n  | \"AF\"\n  | \"AG\"\n  | \"AI\"\n  | \"AL\"\n  | \"AM\"\n  | \"AO\"\n  | \"AQ\"\n  | \"AR\"\n  | \"AS\"\n  | \"AT\"\n  | \"AU\"\n  | \"AW\"\n  | \"AX\"\n  | \"AZ\"\n  | \"BA\"\n  | \"BB\"\n  | \"BD\"\n  | \"BE\"\n  | \"BF\"\n  | \"BG\"\n  | \"BH\"\n  | \"BI\"\n  | \"BJ\"\n  | \"BL\"\n  | \"BM\"\n  | \"BN\"\n  | \"BO\"\n  | \"BQ\"\n  | \"BR\"\n  | \"BS\"\n  | \"BT\"\n  | \"BV\"\n  | \"BW\"\n  | \"BY\"\n  | \"BZ\"\n  | \"CA\"\n  | \"CC\"\n  | \"CD\"\n  | \"CF\"\n  | \"CG\"\n  | \"CH\"\n  | \"CI\"\n  | \"CK\"\n  | \"CL\"\n  | \"CM\"\n  | \"CN\"\n  | \"CO\"\n  | \"CR\"\n  | \"CU\"\n  | \"CV\"\n  | \"CW\"\n  | \"CX\"\n  | \"CY\"\n  | \"CZ\"\n  | \"DE\"\n  | \"DJ\"\n  | \"DK\"\n  | \"DM\"\n  | \"DO\"\n  | \"DZ\"\n  | \"EC\"\n  | \"EE\"\n  | \"EG\"\n  | \"EH\"\n  | \"ER\"\n  | \"ES\"\n  | \"ET\"\n  | \"FI\"\n  | \"FJ\"\n  | \"FK\"\n  | \"FM\"\n  | \"FO\"\n  | \"FR\"\n  | \"GA\"\n  | \"GB\"\n  | \"GD\"\n  | \"GE\"\n  | \"GF\"\n  | \"GG\"\n  | \"GH\"\n  | \"GI\"\n  | \"GL\"\n  | \"GM\"\n  | \"GN\"\n  | \"GP\"\n  | \"GQ\"\n  | \"GR\"\n  | \"GS\"\n  | \"GT\"\n  | \"GU\"\n  | \"GW\"\n  | \"GY\"\n  | \"HK\"\n  | \"HM\"\n  | \"HN\"\n  | \"HR\"\n  | \"HT\"\n  | \"HU\"\n  | \"ID\"\n  | \"IE\"\n  | \"IL\"\n  | \"IM\"\n  | \"IN\"\n  | \"IO\"\n  | \"IQ\"\n  | \"IR\"\n  | \"IS\"\n  | \"IT\"\n  | \"JE\"\n  | \"JM\"\n  | \"JO\"\n  | \"JP\"\n  | \"KE\"\n  | \"KG\"\n  | \"KH\"\n  | \"KI\"\n  | \"KM\"\n  | \"KN\"\n  | \"KP\"\n  | \"KR\"\n  | \"KW\"\n  | \"KY\"\n  | \"KZ\"\n  | \"LA\"\n  | \"LB\"\n  | \"LC\"\n  | \"LI\"\n  | \"LK\"\n  | \"LR\"\n  | \"LS\"\n  | \"LT\"\n  | \"LU\"\n  | \"LV\"\n  | \"LY\"\n  | \"MA\"\n  | \"MC\"\n  | \"MD\"\n  | \"ME\"\n  | \"MF\"\n  | \"MG\"\n  | \"MH\"\n  | \"MK\"\n  | \"ML\"\n  | \"MM\"\n  | \"MN\"\n  | \"MO\"\n  | \"MP\"\n  | \"MQ\"\n  | \"MR\"\n  | \"MS\"\n  | \"MT\"\n  | \"MU\"\n  | \"MV\"\n  | \"MW\"\n  | \"MX\"\n  | \"MY\"\n  | \"MZ\"\n  | \"NA\"\n  | \"NC\"\n  | \"NE\"\n  | \"NF\"\n  | \"NG\"\n  | \"NI\"\n  | \"NL\"\n  | \"NO\"\n  | \"NP\"\n  | \"NR\"\n  | \"NU\"\n  | \"NZ\"\n  | \"OM\"\n  | \"PA\"\n  | \"PE\"\n  | \"PF\"\n  | \"PG\"\n  | \"PH\"\n  | \"PK\"\n  | \"PL\"\n  | \"PM\"\n  | \"PN\"\n  | \"PR\"\n  | \"PS\"\n  | \"PT\"\n  | \"PW\"\n  | \"PY\"\n  | \"QA\"\n  | \"RE\"\n  | \"RO\"\n  | \"RS\"\n  | \"RU\"\n  | \"RW\"\n  | \"SA\"\n  | \"SB\"\n  | \"SC\"\n  | \"SD\"\n  | \"SE\"\n  | \"SG\"\n  | \"SH\"\n  | \"SI\"\n  | \"SJ\"\n  | \"SK\"\n  | \"SL\"\n  | \"SM\"\n  | \"SN\"\n  | \"SO\"\n  | \"SR\"\n  | \"SS\"\n  | \"ST\"\n  | \"SV\"\n  | \"SX\"\n  | \"SY\"\n  | \"SZ\"\n  | \"TC\"\n  | \"TD\"\n  | \"TF\"\n  | \"TG\"\n  | \"TH\"\n  | \"TJ\"\n  | \"TK\"\n  | \"TL\"\n  | \"TM\"\n  | \"TN\"\n  | \"TO\"\n  | \"TR\"\n  | \"TT\"\n  | \"TV\"\n  | \"TW\"\n  | \"TZ\"\n  | \"UA\"\n  | \"UG\"\n  | \"UM\"\n  | \"US\"\n  | \"UY\"\n  | \"UZ\"\n  | \"VA\"\n  | \"VC\"\n  | \"VE\"\n  | \"VG\"\n  | \"VI\"\n  | \"VN\"\n  | \"VU\"\n  | \"WF\"\n  | \"WS\"\n  | \"YE\"\n  | \"YT\"\n  | \"ZA\"\n  | \"ZM\"\n  | \"ZW\";\n/** The 2-letter continent codes Cloudflare uses */\ndeclare type ContinentCode = \"AF\" | \"AN\" | \"AS\" | \"EU\" | \"NA\" | \"OC\" | \"SA\";\ntype CfProperties<HostMetadata = unknown> =\n  | IncomingRequestCfProperties<HostMetadata>\n  | RequestInitCfProperties;\ninterface D1Meta {\n  duration: number;\n  size_after: number;\n  rows_read: number;\n  rows_written: number;\n  last_row_id: number;\n  changed_db: boolean;\n  changes: number;\n  /**\n   * The region of the database instance that executed the query.\n   */\n  served_by_region?: string;\n  /**\n   * The three letters airport code of the colo that executed the query.\n   */\n  served_by_colo?: string;\n  /**\n   * True if-and-only-if the database instance that executed the query was the primary.\n   */\n  served_by_primary?: boolean;\n  timings?: {\n    /**\n     * The duration of the SQL query execution by the database instance. It doesn't include any network time.\n     */\n    sql_duration_ms: number;\n  };\n  /**\n   * Number of total attempts to execute the query, due to automatic retries.\n   * Note: All other fields in the response like `timings` only apply to the last attempt.\n   */\n  total_attempts?: number;\n}\ninterface D1Response {\n  success: true;\n  meta: D1Meta & Record<string, unknown>;\n  error?: never;\n}\ntype D1Result<T = unknown> = D1Response & {\n  results: T[];\n};\ninterface D1ExecResult {\n  count: number;\n  duration: number;\n}\ntype D1SessionConstraint =\n  // Indicates that the first query should go to the primary, and the rest queries\n  // using the same D1DatabaseSession will go to any replica that is consistent with\n  // the bookmark maintained by the session (returned by the first query).\n  | \"first-primary\"\n  // Indicates that the first query can go anywhere (primary or replica), and the rest queries\n  // using the same D1DatabaseSession will go to any replica that is consistent with\n  // the bookmark maintained by the session (returned by the first query).\n  | \"first-unconstrained\";\ntype D1SessionBookmark = string;\ndeclare abstract class D1Database {\n  prepare(query: string): D1PreparedStatement;\n  batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;\n  exec(query: string): Promise<D1ExecResult>;\n  /**\n   * Creates a new D1 Session anchored at the given constraint or the bookmark.\n   * All queries executed using the created session will have sequential consistency,\n   * meaning that all writes done through the session will be visible in subsequent reads.\n   *\n   * @param constraintOrBookmark Either the session constraint or the explicit bookmark to anchor the created session.\n   */\n  withSession(\n    constraintOrBookmark?: D1SessionBookmark | D1SessionConstraint,\n  ): D1DatabaseSession;\n  /**\n   * @deprecated dump() will be removed soon, only applies to deprecated alpha v1 databases.\n   */\n  dump(): Promise<ArrayBuffer>;\n}\ndeclare abstract class D1DatabaseSession {\n  prepare(query: string): D1PreparedStatement;\n  batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;\n  /**\n   * @returns The latest session bookmark across all executed queries on the session.\n   *          If no query has been executed yet, `null` is returned.\n   */\n  getBookmark(): D1SessionBookmark | null;\n}\ndeclare abstract class D1PreparedStatement {\n  bind(...values: unknown[]): D1PreparedStatement;\n  first<T = unknown>(colName: string): Promise<T | null>;\n  first<T = Record<string, unknown>>(): Promise<T | null>;\n  run<T = Record<string, unknown>>(): Promise<D1Result<T>>;\n  all<T = Record<string, unknown>>(): Promise<D1Result<T>>;\n  raw<T = unknown[]>(options: {\n    columnNames: true;\n  }): Promise<[string[], ...T[]]>;\n  raw<T = unknown[]>(options?: { columnNames?: false }): Promise<T[]>;\n}\n// `Disposable` was added to TypeScript's standard lib types in version 5.2.\n// To support older TypeScript versions, define an empty `Disposable` interface.\n// Users won't be able to use `using`/`Symbol.dispose` without upgrading to 5.2,\n// but this will ensure type checking on older versions still passes.\n// TypeScript's interface merging will ensure our empty interface is effectively\n// ignored when `Disposable` is included in the standard lib.\ninterface Disposable {}\n/**\n * The returned data after sending an email\n */\ninterface EmailSendResult {\n  /**\n   * The Email Message ID\n   */\n  messageId: string;\n}\n/**\n * An email message that can be sent from a Worker.\n */\ninterface EmailMessage {\n  /**\n   * Envelope From attribute of the email message.\n   */\n  readonly from: string;\n  /**\n   * Envelope To attribute of the email message.\n   */\n  readonly to: string;\n}\n/**\n * An email message that is sent to a consumer Worker and can be rejected/forwarded.\n */\ninterface ForwardableEmailMessage extends EmailMessage {\n  /**\n   * Stream of the email message content.\n   */\n  readonly raw: ReadableStream<Uint8Array>;\n  /**\n   * An [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers).\n   */\n  readonly headers: Headers;\n  /**\n   * Size of the email message content.\n   */\n  readonly rawSize: number;\n  /**\n   * Reject this email message by returning a permanent SMTP error back to the connecting client including the given reason.\n   * @param reason The reject reason.\n   * @returns void\n   */\n  setReject(reason: string): void;\n  /**\n   * Forward this email message to a verified destination address of the account.\n   * @param rcptTo Verified destination address.\n   * @param headers A [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers).\n   * @returns A promise that resolves when the email message is forwarded.\n   */\n  forward(rcptTo: string, headers?: Headers): Promise<EmailSendResult>;\n  /**\n   * Reply to the sender of this email message with a new EmailMessage object.\n   * @param message The reply message.\n   * @returns A promise that resolves when the email message is replied.\n   */\n  reply(message: EmailMessage): Promise<EmailSendResult>;\n}\n/** A file attachment for an email message */\ntype EmailAttachment =\n  | {\n      disposition: \"inline\";\n      contentId: string;\n      filename: string;\n      type: string;\n      content: string | ArrayBuffer | ArrayBufferView;\n    }\n  | {\n      disposition: \"attachment\";\n      contentId?: undefined;\n      filename: string;\n      type: string;\n      content: string | ArrayBuffer | ArrayBufferView;\n    };\n/** An Email Address */\ninterface EmailAddress {\n  name: string;\n  email: string;\n}\n/**\n * A binding that allows a Worker to send email messages.\n */\ninterface SendEmail {\n  send(message: EmailMessage): Promise<EmailSendResult>;\n  send(builder: {\n    from: string | EmailAddress;\n    to: string | string[];\n    subject: string;\n    replyTo?: string | EmailAddress;\n    cc?: string | string[];\n    bcc?: string | string[];\n    headers?: Record<string, string>;\n    text?: string;\n    html?: string;\n    attachments?: EmailAttachment[];\n  }): Promise<EmailSendResult>;\n}\ndeclare abstract class EmailEvent extends ExtendableEvent {\n  readonly message: ForwardableEmailMessage;\n}\ndeclare type EmailExportedHandler<Env = unknown, Props = unknown> = (\n  message: ForwardableEmailMessage,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\ndeclare module \"cloudflare:email\" {\n  let _EmailMessage: {\n    prototype: EmailMessage;\n    new (from: string, to: string, raw: ReadableStream | string): EmailMessage;\n  };\n  export { _EmailMessage as EmailMessage };\n}\n/**\n * Hello World binding to serve as an explanatory example. DO NOT USE\n */\ninterface HelloWorldBinding {\n  /**\n   * Retrieve the current stored value\n   */\n  get(): Promise<{\n    value: string;\n    ms?: number;\n  }>;\n  /**\n   * Set a new stored value\n   */\n  set(value: string): Promise<void>;\n}\ninterface Hyperdrive {\n  /**\n   * Connect directly to Hyperdrive as if it's your database, returning a TCP socket.\n   *\n   * Calling this method returns an identical socket to if you call\n   * `connect(\"host:port\")` using the `host` and `port` fields from this object.\n   * Pick whichever approach works better with your preferred DB client library.\n   *\n   * Note that this socket is not yet authenticated -- it's expected that your\n   * code (or preferably, the client library of your choice) will authenticate\n   * using the information in this class's readonly fields.\n   */\n  connect(): Socket;\n  /**\n   * A valid DB connection string that can be passed straight into the typical\n   * client library/driver/ORM. This will typically be the easiest way to use\n   * Hyperdrive.\n   */\n  readonly connectionString: string;\n  /*\n   * A randomly generated hostname that is only valid within the context of the\n   * currently running Worker which, when passed into `connect()` function from\n   * the \"cloudflare:sockets\" module, will connect to the Hyperdrive instance\n   * for your database.\n   */\n  readonly host: string;\n  /*\n   * The port that must be paired the the host field when connecting.\n   */\n  readonly port: number;\n  /*\n   * The username to use when authenticating to your database via Hyperdrive.\n   * Unlike the host and password, this will be the same every time\n   */\n  readonly user: string;\n  /*\n   * The randomly generated password to use when authenticating to your\n   * database via Hyperdrive. Like the host field, this password is only valid\n   * within the context of the currently running Worker instance from which\n   * it's read.\n   */\n  readonly password: string;\n  /*\n   * The name of the database to connect to.\n   */\n  readonly database: string;\n}\n// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\ntype ImageInfoResponse =\n  | {\n      format: \"image/svg+xml\";\n    }\n  | {\n      format: string;\n      fileSize: number;\n      width: number;\n      height: number;\n    };\ntype ImageTransform = {\n  width?: number;\n  height?: number;\n  background?: string;\n  blur?: number;\n  border?:\n    | {\n        color?: string;\n        width?: number;\n      }\n    | {\n        top?: number;\n        bottom?: number;\n        left?: number;\n        right?: number;\n      };\n  brightness?: number;\n  contrast?: number;\n  fit?: \"scale-down\" | \"contain\" | \"pad\" | \"squeeze\" | \"cover\" | \"crop\";\n  flip?: \"h\" | \"v\" | \"hv\";\n  gamma?: number;\n  segment?: \"foreground\";\n  gravity?:\n    | \"face\"\n    | \"left\"\n    | \"right\"\n    | \"top\"\n    | \"bottom\"\n    | \"center\"\n    | \"auto\"\n    | \"entropy\"\n    | {\n        x?: number;\n        y?: number;\n        mode: \"remainder\" | \"box-center\";\n      };\n  rotate?: 0 | 90 | 180 | 270;\n  saturation?: number;\n  sharpen?: number;\n  trim?:\n    | \"border\"\n    | {\n        top?: number;\n        bottom?: number;\n        left?: number;\n        right?: number;\n        width?: number;\n        height?: number;\n        border?:\n          | boolean\n          | {\n              color?: string;\n              tolerance?: number;\n              keep?: number;\n            };\n      };\n};\ntype ImageDrawOptions = {\n  opacity?: number;\n  repeat?: boolean | string;\n  top?: number;\n  left?: number;\n  bottom?: number;\n  right?: number;\n};\ntype ImageInputOptions = {\n  encoding?: \"base64\";\n};\ntype ImageOutputOptions = {\n  format:\n    | \"image/jpeg\"\n    | \"image/png\"\n    | \"image/gif\"\n    | \"image/webp\"\n    | \"image/avif\"\n    | \"rgb\"\n    | \"rgba\";\n  quality?: number;\n  background?: string;\n  anim?: boolean;\n};\ninterface ImageMetadata {\n  id: string;\n  filename?: string;\n  uploaded?: string;\n  requireSignedURLs: boolean;\n  meta?: Record<string, unknown>;\n  variants: string[];\n  draft?: boolean;\n  creator?: string;\n}\ninterface ImageUploadOptions {\n  id?: string;\n  filename?: string;\n  requireSignedURLs?: boolean;\n  metadata?: Record<string, unknown>;\n  creator?: string;\n  encoding?: \"base64\";\n}\ninterface ImageUpdateOptions {\n  requireSignedURLs?: boolean;\n  metadata?: Record<string, unknown>;\n  creator?: string;\n}\ninterface ImageListOptions {\n  limit?: number;\n  cursor?: string;\n  sortOrder?: \"asc\" | \"desc\";\n  creator?: string;\n}\ninterface ImageList {\n  images: ImageMetadata[];\n  cursor?: string;\n  listComplete: boolean;\n}\ninterface HostedImagesBinding {\n  /**\n   * Get detailed metadata for a hosted image\n   * @param imageId The ID of the image (UUID or custom ID)\n   * @returns Image metadata, or null if not found\n   */\n  details(imageId: string): Promise<ImageMetadata | null>;\n  /**\n   * Get the raw image data for a hosted image\n   * @param imageId The ID of the image (UUID or custom ID)\n   * @returns ReadableStream of image bytes, or null if not found\n   */\n  image(imageId: string): Promise<ReadableStream<Uint8Array> | null>;\n  /**\n   * Upload a new hosted image\n   * @param image The image file to upload\n   * @param options Upload configuration\n   * @returns Metadata for the uploaded image\n   * @throws {@link ImagesError} if upload fails\n   */\n  upload(\n    image: ReadableStream<Uint8Array> | ArrayBuffer,\n    options?: ImageUploadOptions,\n  ): Promise<ImageMetadata>;\n  /**\n   * Update hosted image metadata\n   * @param imageId The ID of the image\n   * @param options Properties to update\n   * @returns Updated image metadata\n   * @throws {@link ImagesError} if update fails\n   */\n  update(imageId: string, options: ImageUpdateOptions): Promise<ImageMetadata>;\n  /**\n   * Delete a hosted image\n   * @param imageId The ID of the image\n   * @returns True if deleted, false if not found\n   */\n  delete(imageId: string): Promise<boolean>;\n  /**\n   * List hosted images with pagination\n   * @param options List configuration\n   * @returns List of images with pagination info\n   * @throws {@link ImagesError} if list fails\n   */\n  list(options?: ImageListOptions): Promise<ImageList>;\n}\ninterface ImagesBinding {\n  /**\n   * Get image metadata (type, width and height)\n   * @throws {@link ImagesError} with code 9412 if input is not an image\n   * @param stream The image bytes\n   */\n  info(\n    stream: ReadableStream<Uint8Array>,\n    options?: ImageInputOptions,\n  ): Promise<ImageInfoResponse>;\n  /**\n   * Begin applying a series of transformations to an image\n   * @param stream The image bytes\n   * @returns A transform handle\n   */\n  input(\n    stream: ReadableStream<Uint8Array>,\n    options?: ImageInputOptions,\n  ): ImageTransformer;\n  /**\n   * Access hosted images CRUD operations\n   */\n  readonly hosted: HostedImagesBinding;\n}\ninterface ImageTransformer {\n  /**\n   * Apply transform next, returning a transform handle.\n   * You can then apply more transformations, draw, or retrieve the output.\n   * @param transform\n   */\n  transform(transform: ImageTransform): ImageTransformer;\n  /**\n   * Draw an image on this transformer, returning a transform handle.\n   * You can then apply more transformations, draw, or retrieve the output.\n   * @param image The image (or transformer that will give the image) to draw\n   * @param options The options configuring how to draw the image\n   */\n  draw(\n    image: ReadableStream<Uint8Array> | ImageTransformer,\n    options?: ImageDrawOptions,\n  ): ImageTransformer;\n  /**\n   * Retrieve the image that results from applying the transforms to the\n   * provided input\n   * @param options Options that apply to the output e.g. output format\n   */\n  output(options: ImageOutputOptions): Promise<ImageTransformationResult>;\n}\ntype ImageTransformationOutputOptions = {\n  encoding?: \"base64\";\n};\ninterface ImageTransformationResult {\n  /**\n   * The image as a response, ready to store in cache or return to users\n   */\n  response(): Response;\n  /**\n   * The content type of the returned image\n   */\n  contentType(): string;\n  /**\n   * The bytes of the response\n   */\n  image(options?: ImageTransformationOutputOptions): ReadableStream<Uint8Array>;\n}\ninterface ImagesError extends Error {\n  readonly code: number;\n  readonly message: string;\n  readonly stack?: string;\n}\n/**\n * Media binding for transforming media streams.\n * Provides the entry point for media transformation operations.\n */\ninterface MediaBinding {\n  /**\n   * Creates a media transformer from an input stream.\n   * @param media - The input media bytes\n   * @returns A MediaTransformer instance for applying transformations\n   */\n  input(media: ReadableStream<Uint8Array>): MediaTransformer;\n}\n/**\n * Media transformer for applying transformation operations to media content.\n * Handles sizing, fitting, and other input transformation parameters.\n */\ninterface MediaTransformer {\n  /**\n   * Applies transformation options to the media content.\n   * @param transform - Configuration for how the media should be transformed\n   * @returns A generator for producing the transformed media output\n   */\n  transform(\n    transform?: MediaTransformationInputOptions,\n  ): MediaTransformationGenerator;\n  /**\n   * Generates the final media output with specified options.\n   * @param output - Configuration for the output format and parameters\n   * @returns The final transformation result containing the transformed media\n   */\n  output(output?: MediaTransformationOutputOptions): MediaTransformationResult;\n}\n/**\n * Generator for producing media transformation results.\n * Configures the output format and parameters for the transformed media.\n */\ninterface MediaTransformationGenerator {\n  /**\n   * Generates the final media output with specified options.\n   * @param output - Configuration for the output format and parameters\n   * @returns The final transformation result containing the transformed media\n   */\n  output(output?: MediaTransformationOutputOptions): MediaTransformationResult;\n}\n/**\n * Result of a media transformation operation.\n * Provides multiple ways to access the transformed media content.\n */\ninterface MediaTransformationResult {\n  /**\n   * Returns the transformed media as a readable stream of bytes.\n   * @returns A promise containing a readable stream with the transformed media\n   */\n  media(): Promise<ReadableStream<Uint8Array>>;\n  /**\n   * Returns the transformed media as an HTTP response object.\n   * @returns The transformed media as a Promise<Response>, ready to store in cache or return to users\n   */\n  response(): Promise<Response>;\n  /**\n   * Returns the MIME type of the transformed media.\n   * @returns A promise containing the content type string (e.g., 'image/jpeg', 'video/mp4')\n   */\n  contentType(): Promise<string>;\n}\n/**\n * Configuration options for transforming media input.\n * Controls how the media should be resized and fitted.\n */\ntype MediaTransformationInputOptions = {\n  /** How the media should be resized to fit the specified dimensions */\n  fit?: \"contain\" | \"cover\" | \"scale-down\";\n  /** Target width in pixels */\n  width?: number;\n  /** Target height in pixels */\n  height?: number;\n};\n/**\n * Configuration options for Media Transformations output.\n * Controls the format, timing, and type of the generated output.\n */\ntype MediaTransformationOutputOptions = {\n  /**\n   * Output mode determining the type of media to generate\n   */\n  mode?: \"video\" | \"spritesheet\" | \"frame\" | \"audio\";\n  /** Whether to include audio in the output */\n  audio?: boolean;\n  /**\n   * Starting timestamp for frame extraction or start time for clips. (e.g. '2s').\n   */\n  time?: string;\n  /**\n   * Duration for video clips, audio extraction, and spritesheet generation (e.g. '5s').\n   */\n  duration?: string;\n  /**\n   * Number of frames in the spritesheet.\n   */\n  imageCount?: number;\n  /**\n   * Output format for the generated media.\n   */\n  format?: \"jpg\" | \"png\" | \"m4a\";\n};\n/**\n * Error object for media transformation operations.\n * Extends the standard Error interface with additional media-specific information.\n */\ninterface MediaError extends Error {\n  readonly code: number;\n  readonly message: string;\n  readonly stack?: string;\n}\ndeclare module \"cloudflare:node\" {\n  interface NodeStyleServer {\n    listen(...args: unknown[]): this;\n    address(): {\n      port?: number | null | undefined;\n    };\n  }\n  export function httpServerHandler(port: number): ExportedHandler;\n  export function httpServerHandler(options: { port: number }): ExportedHandler;\n  export function httpServerHandler(server: NodeStyleServer): ExportedHandler;\n}\ntype Params<P extends string = any> = Record<P, string | string[]>;\ntype EventContext<Env, P extends string, Data> = {\n  request: Request<unknown, IncomingRequestCfProperties<unknown>>;\n  functionPath: string;\n  waitUntil: (promise: Promise<any>) => void;\n  passThroughOnException: () => void;\n  next: (input?: Request | string, init?: RequestInit) => Promise<Response>;\n  env: Env & {\n    ASSETS: {\n      fetch: typeof fetch;\n    };\n  };\n  params: Params<P>;\n  data: Data;\n};\ntype PagesFunction<\n  Env = unknown,\n  Params extends string = any,\n  Data extends Record<string, unknown> = Record<string, unknown>,\n> = (context: EventContext<Env, Params, Data>) => Response | Promise<Response>;\ntype EventPluginContext<Env, P extends string, Data, PluginArgs> = {\n  request: Request<unknown, IncomingRequestCfProperties<unknown>>;\n  functionPath: string;\n  waitUntil: (promise: Promise<any>) => void;\n  passThroughOnException: () => void;\n  next: (input?: Request | string, init?: RequestInit) => Promise<Response>;\n  env: Env & {\n    ASSETS: {\n      fetch: typeof fetch;\n    };\n  };\n  params: Params<P>;\n  data: Data;\n  pluginArgs: PluginArgs;\n};\ntype PagesPluginFunction<\n  Env = unknown,\n  Params extends string = any,\n  Data extends Record<string, unknown> = Record<string, unknown>,\n  PluginArgs = unknown,\n> = (\n  context: EventPluginContext<Env, Params, Data, PluginArgs>,\n) => Response | Promise<Response>;\ndeclare module \"assets:*\" {\n  export const onRequest: PagesFunction;\n}\n// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\ndeclare module \"cloudflare:pipelines\" {\n  export abstract class PipelineTransformationEntrypoint<\n    Env = unknown,\n    I extends PipelineRecord = PipelineRecord,\n    O extends PipelineRecord = PipelineRecord,\n  > {\n    protected env: Env;\n    protected ctx: ExecutionContext;\n    constructor(ctx: ExecutionContext, env: Env);\n    /**\n     * run receives an array of PipelineRecord which can be\n     * transformed and returned to the pipeline\n     * @param records Incoming records from the pipeline to be transformed\n     * @param metadata Information about the specific pipeline calling the transformation entrypoint\n     * @returns A promise containing the transformed PipelineRecord array\n     */\n    public run(records: I[], metadata: PipelineBatchMetadata): Promise<O[]>;\n  }\n  export type PipelineRecord = Record<string, unknown>;\n  export type PipelineBatchMetadata = {\n    pipelineId: string;\n    pipelineName: string;\n  };\n  export interface Pipeline<T extends PipelineRecord = PipelineRecord> {\n    /**\n     * The Pipeline interface represents the type of a binding to a Pipeline\n     *\n     * @param records The records to send to the pipeline\n     */\n    send(records: T[]): Promise<void>;\n  }\n}\n// PubSubMessage represents an incoming PubSub message.\n// The message includes metadata about the broker, the client, and the payload\n// itself.\n// https://developers.cloudflare.com/pub-sub/\ninterface PubSubMessage {\n  // Message ID\n  readonly mid: number;\n  // MQTT broker FQDN in the form mqtts://BROKER.NAMESPACE.cloudflarepubsub.com:PORT\n  readonly broker: string;\n  // The MQTT topic the message was sent on.\n  readonly topic: string;\n  // The client ID of the client that published this message.\n  readonly clientId: string;\n  // The unique identifier (JWT ID) used by the client to authenticate, if token\n  // auth was used.\n  readonly jti?: string;\n  // A Unix timestamp (seconds from Jan 1, 1970), set when the Pub/Sub Broker\n  // received the message from the client.\n  readonly receivedAt: number;\n  // An (optional) string with the MIME type of the payload, if set by the\n  // client.\n  readonly contentType: string;\n  // Set to 1 when the payload is a UTF-8 string\n  // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901063\n  readonly payloadFormatIndicator: number;\n  // Pub/Sub (MQTT) payloads can be UTF-8 strings, or byte arrays.\n  // You can use payloadFormatIndicator to inspect this before decoding.\n  payload: string | Uint8Array;\n}\n// JsonWebKey extended by kid parameter\ninterface JsonWebKeyWithKid extends JsonWebKey {\n  // Key Identifier of the JWK\n  readonly kid: string;\n}\ninterface RateLimitOptions {\n  key: string;\n}\ninterface RateLimitOutcome {\n  success: boolean;\n}\ninterface RateLimit {\n  /**\n   * Rate limit a request based on the provided options.\n   * @see https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/\n   * @returns A promise that resolves with the outcome of the rate limit.\n   */\n  limit(options: RateLimitOptions): Promise<RateLimitOutcome>;\n}\n// Namespace for RPC utility types. Unfortunately, we can't use a `module` here as these types need\n// to referenced by `Fetcher`. This is included in the \"importable\" version of the types which\n// strips all `module` blocks.\ndeclare namespace Rpc {\n  // Branded types for identifying `WorkerEntrypoint`/`DurableObject`/`Target`s.\n  // TypeScript uses *structural* typing meaning anything with the same shape as type `T` is a `T`.\n  // For the classes exported by `cloudflare:workers` we want *nominal* typing (i.e. we only want to\n  // accept `WorkerEntrypoint` from `cloudflare:workers`, not any other class with the same shape)\n  export const __RPC_STUB_BRAND: \"__RPC_STUB_BRAND\";\n  export const __RPC_TARGET_BRAND: \"__RPC_TARGET_BRAND\";\n  export const __WORKER_ENTRYPOINT_BRAND: \"__WORKER_ENTRYPOINT_BRAND\";\n  export const __DURABLE_OBJECT_BRAND: \"__DURABLE_OBJECT_BRAND\";\n  export const __WORKFLOW_ENTRYPOINT_BRAND: \"__WORKFLOW_ENTRYPOINT_BRAND\";\n  export interface RpcTargetBranded {\n    [__RPC_TARGET_BRAND]: never;\n  }\n  export interface WorkerEntrypointBranded {\n    [__WORKER_ENTRYPOINT_BRAND]: never;\n  }\n  export interface DurableObjectBranded {\n    [__DURABLE_OBJECT_BRAND]: never;\n  }\n  export interface WorkflowEntrypointBranded {\n    [__WORKFLOW_ENTRYPOINT_BRAND]: never;\n  }\n  export type EntrypointBranded =\n    | WorkerEntrypointBranded\n    | DurableObjectBranded\n    | WorkflowEntrypointBranded;\n  // Types that can be used through `Stub`s\n  export type Stubable = RpcTargetBranded | ((...args: any[]) => any);\n  // Types that can be passed over RPC\n  // The reason for using a generic type here is to build a serializable subset of structured\n  //   cloneable composite types. This allows types defined with the \"interface\" keyword to pass the\n  //   serializable check as well. Otherwise, only types defined with the \"type\" keyword would pass.\n  type Serializable<T> =\n    // Structured cloneables\n    | BaseType\n    // Structured cloneable composites\n    | Map<\n        T extends Map<infer U, unknown> ? Serializable<U> : never,\n        T extends Map<unknown, infer U> ? Serializable<U> : never\n      >\n    | Set<T extends Set<infer U> ? Serializable<U> : never>\n    | ReadonlyArray<T extends ReadonlyArray<infer U> ? Serializable<U> : never>\n    | {\n        [K in keyof T]: K extends number | string ? Serializable<T[K]> : never;\n      }\n    // Special types\n    | Stub<Stubable>\n    // Serialized as stubs, see `Stubify`\n    | Stubable;\n  // Base type for all RPC stubs, including common memory management methods.\n  // `T` is used as a marker type for unwrapping `Stub`s later.\n  interface StubBase<T extends Stubable> extends Disposable {\n    [__RPC_STUB_BRAND]: T;\n    dup(): this;\n  }\n  export type Stub<T extends Stubable> = Provider<T> & StubBase<T>;\n  // This represents all the types that can be sent as-is over an RPC boundary\n  type BaseType =\n    | void\n    | undefined\n    | null\n    | boolean\n    | number\n    | bigint\n    | string\n    | TypedArray\n    | ArrayBuffer\n    | DataView\n    | Date\n    | Error\n    | RegExp\n    | ReadableStream<Uint8Array>\n    | WritableStream<Uint8Array>\n    | Request\n    | Response\n    | Headers;\n  // Recursively rewrite all `Stubable` types with `Stub`s\n  type Stubify<T> = T extends Stubable\n    ? Stub<T>\n    : T extends Map<infer K, infer V>\n      ? Map<Stubify<K>, Stubify<V>>\n      : T extends Set<infer V>\n        ? Set<Stubify<V>>\n        : T extends Array<infer V>\n          ? Array<Stubify<V>>\n          : T extends ReadonlyArray<infer V>\n            ? ReadonlyArray<Stubify<V>>\n            : T extends BaseType\n              ? T\n              : T extends {\n                    [key: string | number]: any;\n                  }\n                ? {\n                    [K in keyof T]: Stubify<T[K]>;\n                  }\n                : T;\n  // Recursively rewrite all `Stub<T>`s with the corresponding `T`s.\n  // Note we use `StubBase` instead of `Stub` here to avoid circular dependencies:\n  // `Stub` depends on `Provider`, which depends on `Unstubify`, which would depend on `Stub`.\n  type Unstubify<T> =\n    T extends StubBase<infer V>\n      ? V\n      : T extends Map<infer K, infer V>\n        ? Map<Unstubify<K>, Unstubify<V>>\n        : T extends Set<infer V>\n          ? Set<Unstubify<V>>\n          : T extends Array<infer V>\n            ? Array<Unstubify<V>>\n            : T extends ReadonlyArray<infer V>\n              ? ReadonlyArray<Unstubify<V>>\n              : T extends BaseType\n                ? T\n                : T extends {\n                      [key: string | number]: unknown;\n                    }\n                  ? {\n                      [K in keyof T]: Unstubify<T[K]>;\n                    }\n                  : T;\n  type UnstubifyAll<A extends any[]> = {\n    [I in keyof A]: Unstubify<A[I]>;\n  };\n  // Utility type for adding `Provider`/`Disposable`s to `object` types only.\n  // Note `unknown & T` is equivalent to `T`.\n  type MaybeProvider<T> = T extends object ? Provider<T> : unknown;\n  type MaybeDisposable<T> = T extends object ? Disposable : unknown;\n  // Type for method return or property on an RPC interface.\n  // - Stubable types are replaced by stubs.\n  // - Serializable types are passed by value, with stubable types replaced by stubs\n  //   and a top-level `Disposer`.\n  // Everything else can't be passed over PRC.\n  // Technically, we use custom thenables here, but they quack like `Promise`s.\n  // Intersecting with `(Maybe)Provider` allows pipelining.\n  type Result<R> = R extends Stubable\n    ? Promise<Stub<R>> & Provider<R>\n    : R extends Serializable<R>\n      ? Promise<Stubify<R> & MaybeDisposable<R>> & MaybeProvider<R>\n      : never;\n  // Type for method or property on an RPC interface.\n  // For methods, unwrap `Stub`s in parameters, and rewrite returns to be `Result`s.\n  // Unwrapping `Stub`s allows calling with `Stubable` arguments.\n  // For properties, rewrite types to be `Result`s.\n  // In each case, unwrap `Promise`s.\n  type MethodOrProperty<V> = V extends (...args: infer P) => infer R\n    ? (...args: UnstubifyAll<P>) => Result<Awaited<R>>\n    : Result<Awaited<V>>;\n  // Type for the callable part of an `Provider` if `T` is callable.\n  // This is intersected with methods/properties.\n  type MaybeCallableProvider<T> = T extends (...args: any[]) => any\n    ? MethodOrProperty<T>\n    : unknown;\n  // Base type for all other types providing RPC-like interfaces.\n  // Rewrites all methods/properties to be `MethodOrProperty`s, while preserving callable types.\n  // `Reserved` names (e.g. stub method names like `dup()`) and symbols can't be accessed over RPC.\n  export type Provider<\n    T extends object,\n    Reserved extends string = never,\n  > = MaybeCallableProvider<T> &\n    Pick<\n      {\n        [K in keyof T]: MethodOrProperty<T[K]>;\n      },\n      Exclude<keyof T, Reserved | symbol | keyof StubBase<never>>\n    >;\n}\ndeclare namespace Cloudflare {\n  // Type of `env`.\n  //\n  // The specific project can extend `Env` by redeclaring it in project-specific files. Typescript\n  // will merge all declarations.\n  //\n  // You can use `wrangler types` to generate the `Env` type automatically.\n  interface Env {}\n  // Project-specific parameters used to inform types.\n  //\n  // This interface is, again, intended to be declared in project-specific files, and then that\n  // declaration will be merged with this one.\n  //\n  // A project should have a declaration like this:\n  //\n  //     interface GlobalProps {\n  //       // Declares the main module's exports. Used to populate Cloudflare.Exports aka the type\n  //       // of `ctx.exports`.\n  //       mainModule: typeof import(\"my-main-module\");\n  //\n  //       // Declares which of the main module's exports are configured with durable storage, and\n  //       // thus should behave as Durable Object namsepace bindings.\n  //       durableNamespaces: \"MyDurableObject\" | \"AnotherDurableObject\";\n  //     }\n  //\n  // You can use `wrangler types` to generate `GlobalProps` automatically.\n  interface GlobalProps {}\n  // Evaluates to the type of a property in GlobalProps, defaulting to `Default` if it is not\n  // present.\n  type GlobalProp<K extends string, Default> = K extends keyof GlobalProps\n    ? GlobalProps[K]\n    : Default;\n  // The type of the program's main module exports, if known. Requires `GlobalProps` to declare the\n  // `mainModule` property.\n  type MainModule = GlobalProp<\"mainModule\", {}>;\n  // The type of ctx.exports, which contains loopback bindings for all top-level exports.\n  type Exports = {\n    [K in keyof MainModule]: LoopbackForExport<MainModule[K]> &\n      // If the export is listed in `durableNamespaces`, then it is also a\n      // DurableObjectNamespace.\n      (K extends GlobalProp<\"durableNamespaces\", never>\n        ? MainModule[K] extends new (...args: any[]) => infer DoInstance\n          ? DoInstance extends Rpc.DurableObjectBranded\n            ? DurableObjectNamespace<DoInstance>\n            : DurableObjectNamespace<undefined>\n          : DurableObjectNamespace<undefined>\n        : {});\n  };\n}\ndeclare namespace CloudflareWorkersModule {\n  export type RpcStub<T extends Rpc.Stubable> = Rpc.Stub<T>;\n  export const RpcStub: {\n    new <T extends Rpc.Stubable>(value: T): Rpc.Stub<T>;\n  };\n  export abstract class RpcTarget implements Rpc.RpcTargetBranded {\n    [Rpc.__RPC_TARGET_BRAND]: never;\n  }\n  // `protected` fields don't appear in `keyof`s, so can't be accessed over RPC\n  export abstract class WorkerEntrypoint<Env = Cloudflare.Env, Props = {}>\n    implements Rpc.WorkerEntrypointBranded\n  {\n    [Rpc.__WORKER_ENTRYPOINT_BRAND]: never;\n    protected ctx: ExecutionContext<Props>;\n    protected env: Env;\n    constructor(ctx: ExecutionContext, env: Env);\n    email?(message: ForwardableEmailMessage): void | Promise<void>;\n    fetch?(request: Request): Response | Promise<Response>;\n    queue?(batch: MessageBatch<unknown>): void | Promise<void>;\n    scheduled?(controller: ScheduledController): void | Promise<void>;\n    tail?(events: TraceItem[]): void | Promise<void>;\n    tailStream?(\n      event: TailStream.TailEvent<TailStream.Onset>,\n    ):\n      | TailStream.TailEventHandlerType\n      | Promise<TailStream.TailEventHandlerType>;\n    test?(controller: TestController): void | Promise<void>;\n    trace?(traces: TraceItem[]): void | Promise<void>;\n  }\n  export abstract class DurableObject<Env = Cloudflare.Env, Props = {}>\n    implements Rpc.DurableObjectBranded\n  {\n    [Rpc.__DURABLE_OBJECT_BRAND]: never;\n    protected ctx: DurableObjectState<Props>;\n    protected env: Env;\n    constructor(ctx: DurableObjectState, env: Env);\n    alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise<void>;\n    fetch?(request: Request): Response | Promise<Response>;\n    webSocketMessage?(\n      ws: WebSocket,\n      message: string | ArrayBuffer,\n    ): void | Promise<void>;\n    webSocketClose?(\n      ws: WebSocket,\n      code: number,\n      reason: string,\n      wasClean: boolean,\n    ): void | Promise<void>;\n    webSocketError?(ws: WebSocket, error: unknown): void | Promise<void>;\n  }\n  export type WorkflowDurationLabel =\n    | \"second\"\n    | \"minute\"\n    | \"hour\"\n    | \"day\"\n    | \"week\"\n    | \"month\"\n    | \"year\";\n  export type WorkflowSleepDuration =\n    | `${number} ${WorkflowDurationLabel}${\"s\" | \"\"}`\n    | number;\n  export type WorkflowDelayDuration = WorkflowSleepDuration;\n  export type WorkflowTimeoutDuration = WorkflowSleepDuration;\n  export type WorkflowRetentionDuration = WorkflowSleepDuration;\n  export type WorkflowBackoff = \"constant\" | \"linear\" | \"exponential\";\n  export type WorkflowStepConfig = {\n    retries?: {\n      limit: number;\n      delay: WorkflowDelayDuration | number;\n      backoff?: WorkflowBackoff;\n    };\n    timeout?: WorkflowTimeoutDuration | number;\n  };\n  export type WorkflowEvent<T> = {\n    payload: Readonly<T>;\n    timestamp: Date;\n    instanceId: string;\n  };\n  export type WorkflowStepEvent<T> = {\n    payload: Readonly<T>;\n    timestamp: Date;\n    type: string;\n  };\n  export type WorkflowStepContext = {\n    attempt: number;\n  };\n  export abstract class WorkflowStep {\n    do<T extends Rpc.Serializable<T>>(\n      name: string,\n      callback: (ctx: WorkflowStepContext) => Promise<T>,\n    ): Promise<T>;\n    do<T extends Rpc.Serializable<T>>(\n      name: string,\n      config: WorkflowStepConfig,\n      callback: (ctx: WorkflowStepContext) => Promise<T>,\n    ): Promise<T>;\n    sleep: (name: string, duration: WorkflowSleepDuration) => Promise<void>;\n    sleepUntil: (name: string, timestamp: Date | number) => Promise<void>;\n    waitForEvent<T extends Rpc.Serializable<T>>(\n      name: string,\n      options: {\n        type: string;\n        timeout?: WorkflowTimeoutDuration | number;\n      },\n    ): Promise<WorkflowStepEvent<T>>;\n  }\n  export type WorkflowInstanceStatus =\n    | \"queued\"\n    | \"running\"\n    | \"paused\"\n    | \"errored\"\n    | \"terminated\"\n    | \"complete\"\n    | \"waiting\"\n    | \"waitingForPause\"\n    | \"unknown\";\n  export abstract class WorkflowEntrypoint<\n    Env = unknown,\n    T extends Rpc.Serializable<T> | unknown = unknown,\n  >\n    implements Rpc.WorkflowEntrypointBranded\n  {\n    [Rpc.__WORKFLOW_ENTRYPOINT_BRAND]: never;\n    protected ctx: ExecutionContext;\n    protected env: Env;\n    constructor(ctx: ExecutionContext, env: Env);\n    run(\n      event: Readonly<WorkflowEvent<T>>,\n      step: WorkflowStep,\n    ): Promise<unknown>;\n  }\n  export function waitUntil(promise: Promise<unknown>): void;\n  export function withEnv(newEnv: unknown, fn: () => unknown): unknown;\n  export function withExports(newExports: unknown, fn: () => unknown): unknown;\n  export function withEnvAndExports(\n    newEnv: unknown,\n    newExports: unknown,\n    fn: () => unknown,\n  ): unknown;\n  export const env: Cloudflare.Env;\n  export const exports: Cloudflare.Exports;\n}\ndeclare module \"cloudflare:workers\" {\n  export = CloudflareWorkersModule;\n}\ninterface SecretsStoreSecret {\n  /**\n   * Get a secret from the Secrets Store, returning a string of the secret value\n   * if it exists, or throws an error if it does not exist\n   */\n  get(): Promise<string>;\n}\ndeclare module \"cloudflare:sockets\" {\n  function _connect(\n    address: string | SocketAddress,\n    options?: SocketOptions,\n  ): Socket;\n  export { _connect as connect };\n}\n/**\n * Binding entrypoint for Cloudflare Stream.\n *\n * Usage:\n * - Binding-level operations:\n *   `await env.STREAM.videos.upload`\n *   `await env.STREAM.videos.createDirectUpload`\n *   `await env.STREAM.videos.*`\n *   `await env.STREAM.watermarks.*`\n * - Per-video operations:\n *   `await env.STREAM.video(id).downloads.*`\n *   `await env.STREAM.video(id).captions.*`\n *\n * Example usage:\n * ```ts\n * await env.STREAM.video(id).downloads.generate();\n *\n * const video = env.STREAM.video(id)\n * const captions = video.captions.list();\n * const videoDetails = video.details()\n * ```\n */\ninterface StreamBinding {\n  /**\n   * Returns a handle scoped to a single video for per-video operations.\n   * @param id The unique identifier for the video.\n   * @returns A handle for per-video operations.\n   */\n  video(id: string): StreamVideoHandle;\n  /**\n   * Uploads a new video from a provided URL.\n   * @param url The URL to upload from.\n   * @param params Optional upload parameters.\n   * @returns The uploaded video details.\n   * @throws {BadRequestError} if the upload parameter is invalid or the URL is invalid\n   * @throws {QuotaReachedError} if the account storage capacity is exceeded\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {RateLimitedError} if the server received too many requests\n   * @throws {AlreadyUploadedError} if a video was already uploaded to this URL\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  upload(url: string, params?: StreamUrlUploadParams): Promise<StreamVideo>;\n  /**\n   * Creates a direct upload that allows video uploads without an API key.\n   * @param params Parameters for the direct upload\n   * @returns The direct upload details.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {RateLimitedError} if the server received too many requests\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  createDirectUpload(\n    params: StreamDirectUploadCreateParams,\n  ): Promise<StreamDirectUpload>;\n  videos: StreamVideos;\n  watermarks: StreamWatermarks;\n}\n/**\n * Handle for operations scoped to a single Stream video.\n */\ninterface StreamVideoHandle {\n  /**\n   * The unique identifier for the video.\n   */\n  id: string;\n  /**\n   * Get a full videos details\n   * @returns The full video details.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  details(): Promise<StreamVideo>;\n  /**\n   * Update details for a single video.\n   * @param params The fields to update for the video.\n   * @returns The updated video details.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  update(params: StreamUpdateVideoParams): Promise<StreamVideo>;\n  /**\n   * Deletes a video and its copies from Cloudflare Stream.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(): Promise<void>;\n  /**\n   * Creates a signed URL token for a video.\n   * @returns The signed token that was created.\n   * @throws {InternalError} if the signing key cannot be retrieved or the token cannot be signed\n   */\n  generateToken(): Promise<string>;\n  downloads: StreamScopedDownloads;\n  captions: StreamScopedCaptions;\n}\ninterface StreamVideo {\n  /**\n   * The unique identifier for the video.\n   */\n  id: string;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator: string | null;\n  /**\n   * The thumbnail URL for the video.\n   */\n  thumbnail: string;\n  /**\n   * The thumbnail timestamp percentage.\n   */\n  thumbnailTimestampPct: number;\n  /**\n   * Indicates whether the video is ready to stream.\n   */\n  readyToStream: boolean;\n  /**\n   * The date and time the video became ready to stream.\n   */\n  readyToStreamAt: string | null;\n  /**\n   * Processing status information.\n   */\n  status: StreamVideoStatus;\n  /**\n   * A user modifiable key-value store.\n   */\n  meta: Record<string, string>;\n  /**\n   * The date and time the video was created.\n   */\n  created: string;\n  /**\n   * The date and time the video was last modified.\n   */\n  modified: string;\n  /**\n   * The date and time at which the video will be deleted.\n   */\n  scheduledDeletion: string | null;\n  /**\n   * The size of the video in bytes.\n   */\n  size: number;\n  /**\n   * The preview URL for the video.\n   */\n  preview?: string;\n  /**\n   * Origins allowed to display the video.\n   */\n  allowedOrigins: Array<string>;\n  /**\n   * Indicates whether signed URLs are required.\n   */\n  requireSignedURLs: boolean | null;\n  /**\n   * The date and time the video was uploaded.\n   */\n  uploaded: string | null;\n  /**\n   * The date and time when the upload URL expires.\n   */\n  uploadExpiry: string | null;\n  /**\n   * The maximum size in bytes for direct uploads.\n   */\n  maxSizeBytes: number | null;\n  /**\n   * The maximum duration in seconds for direct uploads.\n   */\n  maxDurationSeconds: number | null;\n  /**\n   * The video duration in seconds. -1 indicates unknown.\n   */\n  duration: number;\n  /**\n   * Input metadata for the original upload.\n   */\n  input: StreamVideoInput;\n  /**\n   * Playback URLs for the video.\n   */\n  hlsPlaybackUrl: string;\n  dashPlaybackUrl: string;\n  /**\n   * The watermark applied to the video, if any.\n   */\n  watermark: StreamWatermark | null;\n  /**\n   * The live input id associated with the video, if any.\n   */\n  liveInputId?: string | null;\n  /**\n   * The source video id if this is a clip.\n   */\n  clippedFromId: string | null;\n  /**\n   * Public details associated with the video.\n   */\n  publicDetails: StreamPublicDetails | null;\n}\ntype StreamVideoStatus = {\n  /**\n   * The current processing state.\n   */\n  state: string;\n  /**\n   * The current processing step.\n   */\n  step?: string;\n  /**\n   * The percent complete as a string.\n   */\n  pctComplete?: string;\n  /**\n   * An error reason code, if applicable.\n   */\n  errorReasonCode: string;\n  /**\n   * An error reason text, if applicable.\n   */\n  errorReasonText: string;\n};\ntype StreamVideoInput = {\n  /**\n   * The input width in pixels.\n   */\n  width: number;\n  /**\n   * The input height in pixels.\n   */\n  height: number;\n};\ntype StreamPublicDetails = {\n  /**\n   * The public title for the video.\n   */\n  title: string | null;\n  /**\n   * The public share link.\n   */\n  share_link: string | null;\n  /**\n   * The public channel link.\n   */\n  channel_link: string | null;\n  /**\n   * The public logo URL.\n   */\n  logo: string | null;\n};\ntype StreamDirectUpload = {\n  /**\n   * The URL an unauthenticated upload can use for a single multipart request.\n   */\n  uploadURL: string;\n  /**\n   * A Cloudflare-generated unique identifier for a media item.\n   */\n  id: string;\n  /**\n   * The watermark profile applied to the upload.\n   */\n  watermark: StreamWatermark | null;\n  /**\n   * The scheduled deletion time, if any.\n   */\n  scheduledDeletion: string | null;\n};\ntype StreamDirectUploadCreateParams = {\n  /**\n   * The maximum duration in seconds for a video upload.\n   */\n  maxDurationSeconds: number;\n  /**\n   * The date and time after upload when videos will not be accepted.\n   */\n  expiry?: string;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator?: string;\n  /**\n   * A user modifiable key-value store used to reference other systems of record for\n   * managing videos.\n   */\n  meta?: Record<string, string>;\n  /**\n   * Lists the origins allowed to display the video.\n   */\n  allowedOrigins?: Array<string>;\n  /**\n   * Indicates whether the video can be accessed using the id. When set to `true`,\n   * a signed token must be generated with a signing key to view the video.\n   */\n  requireSignedURLs?: boolean;\n  /**\n   * The thumbnail timestamp percentage.\n   */\n  thumbnailTimestampPct?: number;\n  /**\n   * The date and time at which the video will be deleted. Include `null` to remove\n   * a scheduled deletion.\n   */\n  scheduledDeletion?: string | null;\n  /**\n   * The watermark profile to apply.\n   */\n  watermark?: StreamDirectUploadWatermark;\n};\ntype StreamDirectUploadWatermark = {\n  /**\n   * The unique identifier for the watermark profile.\n   */\n  id: string;\n};\ntype StreamUrlUploadParams = {\n  /**\n   * Lists the origins allowed to display the video. Enter allowed origin\n   * domains in an array and use `*` for wildcard subdomains. Empty arrays allow the\n   * video to be viewed on any origin.\n   */\n  allowedOrigins?: Array<string>;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator?: string;\n  /**\n   * A user modifiable key-value store used to reference other systems of\n   * record for managing videos.\n   */\n  meta?: Record<string, string>;\n  /**\n   * Indicates whether the video can be a accessed using the id. When\n   * set to `true`, a signed token must be generated with a signing key to view the\n   * video.\n   */\n  requireSignedURLs?: boolean;\n  /**\n   * Indicates the date and time at which the video will be deleted. Omit\n   * the field to indicate no change, or include with a `null` value to remove an\n   * existing scheduled deletion. If specified, must be at least 30 days from upload\n   * time.\n   */\n  scheduledDeletion?: string | null;\n  /**\n   * The timestamp for a thumbnail image calculated as a percentage value\n   * of the video's duration. To convert from a second-wise timestamp to a\n   * percentage, divide the desired timestamp by the total duration of the video. If\n   * this value is not set, the default thumbnail image is taken from 0s of the\n   * video.\n   */\n  thumbnailTimestampPct?: number;\n  /**\n   * The identifier for the watermark profile\n   */\n  watermarkId?: string;\n};\ninterface StreamScopedCaptions {\n  /**\n   * Uploads the caption or subtitle file to the endpoint for a specific BCP47 language.\n   * One caption or subtitle file per language is allowed.\n   * @param language The BCP 47 language tag for the caption or subtitle.\n   * @param file The caption or subtitle file to upload.\n   * @returns The created caption entry.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the language or file is invalid\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  upload(language: string, file: File): Promise<StreamCaption>;\n  /**\n   * Generate captions or subtitles for the provided language via AI.\n   * @param language The BCP 47 language tag to generate.\n   * @returns The generated caption entry.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the language is invalid\n   * @throws {StreamError} if a generated caption already exists\n   * @throws {StreamError} if the video duration is too long\n   * @throws {StreamError} if the video is missing audio\n   * @throws {StreamError} if the requested language is not supported\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(language: string): Promise<StreamCaption>;\n  /**\n   * Lists the captions or subtitles.\n   * Use the language parameter to filter by a specific language.\n   * @param language The optional BCP 47 language tag to filter by.\n   * @returns The list of captions or subtitles.\n   * @throws {NotFoundError} if the video or caption is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  list(language?: string): Promise<StreamCaption[]>;\n  /**\n   * Removes the captions or subtitles from a video.\n   * @param language The BCP 47 language tag to remove.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the video or caption is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(language: string): Promise<void>;\n}\ninterface StreamScopedDownloads {\n  /**\n   * Generates a download for a video when a video is ready to view. Available\n   * types are `default` and `audio`. Defaults to `default` when omitted.\n   * @param downloadType The download type to create.\n   * @returns The current downloads for the video.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the download type is invalid\n   * @throws {StreamError} if the video duration is too long to generate a download\n   * @throws {StreamError} if the video is not ready to stream\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(\n    downloadType?: StreamDownloadType,\n  ): Promise<StreamDownloadGetResponse>;\n  /**\n   * Lists the downloads created for a video.\n   * @returns The current downloads for the video.\n   * @throws {NotFoundError} if the video or downloads are not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  get(): Promise<StreamDownloadGetResponse>;\n  /**\n   * Delete the downloads for a video. Available types are `default` and `audio`.\n   * Defaults to `default` when omitted.\n   * @param downloadType The download type to delete.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the video or downloads are not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(downloadType?: StreamDownloadType): Promise<void>;\n}\ninterface StreamVideos {\n  /**\n   * Lists all videos in a users account.\n   * @returns The list of videos.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  list(params?: StreamVideosListParams): Promise<StreamVideo[]>;\n}\ninterface StreamWatermarks {\n  /**\n   * Generate a new watermark profile\n   * @param file The image file to upload\n   * @param params The watermark creation parameters.\n   * @returns The created watermark profile.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InvalidURLError} if the URL is invalid\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {TooManyWatermarksError} if the number of allowed watermarks is reached\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(\n    file: File,\n    params: StreamWatermarkCreateParams,\n  ): Promise<StreamWatermark>;\n  /**\n   * Generate a new watermark profile\n   * @param url The image url to upload\n   * @param params The watermark creation parameters.\n   * @returns The created watermark profile.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InvalidURLError} if the URL is invalid\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {TooManyWatermarksError} if the number of allowed watermarks is reached\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(\n    url: string,\n    params: StreamWatermarkCreateParams,\n  ): Promise<StreamWatermark>;\n  /**\n   * Lists all watermark profiles for an account.\n   * @returns The list of watermark profiles.\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  list(): Promise<StreamWatermark[]>;\n  /**\n   * Retrieves details for a single watermark profile.\n   * @param watermarkId The watermark profile identifier.\n   * @returns The watermark profile details.\n   * @throws {NotFoundError} if the watermark is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  get(watermarkId: string): Promise<StreamWatermark>;\n  /**\n   * Deletes a watermark profile.\n   * @param watermarkId The watermark profile identifier.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the watermark is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(watermarkId: string): Promise<void>;\n}\ntype StreamUpdateVideoParams = {\n  /**\n   * Lists the origins allowed to display the video. Enter allowed origin\n   * domains in an array and use `*` for wildcard subdomains. Empty arrays allow the\n   * video to be viewed on any origin.\n   */\n  allowedOrigins?: Array<string>;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator?: string;\n  /**\n   * The maximum duration in seconds for a video upload. Can be set for a\n   * video that is not yet uploaded to limit its duration. Uploads that exceed the\n   * specified duration will fail during processing. A value of `-1` means the value\n   * is unknown.\n   */\n  maxDurationSeconds?: number;\n  /**\n   * A user modifiable key-value store used to reference other systems of\n   * record for managing videos.\n   */\n  meta?: Record<string, string>;\n  /**\n   * Indicates whether the video can be a accessed using the id. When\n   * set to `true`, a signed token must be generated with a signing key to view the\n   * video.\n   */\n  requireSignedURLs?: boolean;\n  /**\n   * Indicates the date and time at which the video will be deleted. Omit\n   * the field to indicate no change, or include with a `null` value to remove an\n   * existing scheduled deletion. If specified, must be at least 30 days from upload\n   * time.\n   */\n  scheduledDeletion?: string | null;\n  /**\n   * The timestamp for a thumbnail image calculated as a percentage value\n   * of the video's duration. To convert from a second-wise timestamp to a\n   * percentage, divide the desired timestamp by the total duration of the video. If\n   * this value is not set, the default thumbnail image is taken from 0s of the\n   * video.\n   */\n  thumbnailTimestampPct?: number;\n};\ntype StreamCaption = {\n  /**\n   * Whether the caption was generated via AI.\n   */\n  generated?: boolean;\n  /**\n   * The language label displayed in the native language to users.\n   */\n  label: string;\n  /**\n   * The language tag in BCP 47 format.\n   */\n  language: string;\n  /**\n   * The status of a generated caption.\n   */\n  status?: \"ready\" | \"inprogress\" | \"error\";\n};\ntype StreamDownloadStatus = \"ready\" | \"inprogress\" | \"error\";\ntype StreamDownloadType = \"default\" | \"audio\";\ntype StreamDownload = {\n  /**\n   * Indicates the progress as a percentage between 0 and 100.\n   */\n  percentComplete: number;\n  /**\n   * The status of a generated download.\n   */\n  status: StreamDownloadStatus;\n  /**\n   * The URL to access the generated download.\n   */\n  url?: string;\n};\n/**\n * An object with download type keys. Each key is optional and only present if that\n * download type has been created.\n */\ntype StreamDownloadGetResponse = {\n  /**\n   * The audio-only download. Only present if this download type has been created.\n   */\n  audio?: StreamDownload;\n  /**\n   * The default video download. Only present if this download type has been created.\n   */\n  default?: StreamDownload;\n};\ntype StreamWatermarkPosition =\n  | \"upperRight\"\n  | \"upperLeft\"\n  | \"lowerLeft\"\n  | \"lowerRight\"\n  | \"center\";\ntype StreamWatermark = {\n  /**\n   * The unique identifier for a watermark profile.\n   */\n  id: string;\n  /**\n   * The size of the image in bytes.\n   */\n  size: number;\n  /**\n   * The height of the image in pixels.\n   */\n  height: number;\n  /**\n   * The width of the image in pixels.\n   */\n  width: number;\n  /**\n   * The date and a time a watermark profile was created.\n   */\n  created: string;\n  /**\n   * The source URL for a downloaded image. If the watermark profile was created via\n   * direct upload, this field is null.\n   */\n  downloadedFrom: string | null;\n  /**\n   * A short description of the watermark profile.\n   */\n  name: string;\n  /**\n   * The translucency of the image. A value of `0.0` makes the image completely\n   * transparent, and `1.0` makes the image completely opaque. Note that if the image\n   * is already semi-transparent, setting this to `1.0` will not make the image\n   * completely opaque.\n   */\n  opacity: number;\n  /**\n   * The whitespace between the adjacent edges (determined by position) of the video\n   * and the image. `0.0` indicates no padding, and `1.0` indicates a fully padded\n   * video width or length, as determined by the algorithm.\n   */\n  padding: number;\n  /**\n   * The size of the image relative to the overall size of the video. This parameter\n   * will adapt to horizontal and vertical videos automatically. `0.0` indicates no\n   * scaling (use the size of the image as-is), and `1.0 `fills the entire video.\n   */\n  scale: number;\n  /**\n   * The location of the image. Valid positions are: `upperRight`, `upperLeft`,\n   * `lowerLeft`, `lowerRight`, and `center`. Note that `center` ignores the\n   * `padding` parameter.\n   */\n  position: StreamWatermarkPosition;\n};\ntype StreamWatermarkCreateParams = {\n  /**\n   * A short description of the watermark profile.\n   */\n  name?: string;\n  /**\n   * The translucency of the image. A value of `0.0` makes the image completely\n   * transparent, and `1.0` makes the image completely opaque. Note that if the\n   * image is already semi-transparent, setting this to `1.0` will not make the\n   * image completely opaque.\n   */\n  opacity?: number;\n  /**\n   * The whitespace between the adjacent edges (determined by position) of the\n   * video and the image. `0.0` indicates no padding, and `1.0` indicates a fully\n   * padded video width or length, as determined by the algorithm.\n   */\n  padding?: number;\n  /**\n   * The size of the image relative to the overall size of the video. This\n   * parameter will adapt to horizontal and vertical videos automatically. `0.0`\n   * indicates no scaling (use the size of the image as-is), and `1.0 `fills the\n   * entire video.\n   */\n  scale?: number;\n  /**\n   * The location of the image.\n   */\n  position?: StreamWatermarkPosition;\n};\ntype StreamVideosListParams = {\n  /**\n   * The maximum number of videos to return.\n   */\n  limit?: number;\n  /**\n   * Return videos created before this timestamp.\n   * (RFC3339/RFC3339Nano)\n   */\n  before?: string;\n  /**\n   * Comparison operator for the `before` field.\n   * @default 'lt'\n   */\n  beforeComp?: StreamPaginationComparison;\n  /**\n   * Return videos created after this timestamp.\n   * (RFC3339/RFC3339Nano)\n   */\n  after?: string;\n  /**\n   * Comparison operator for the `after` field.\n   * @default 'gte'\n   */\n  afterComp?: StreamPaginationComparison;\n};\ntype StreamPaginationComparison = \"eq\" | \"gt\" | \"gte\" | \"lt\" | \"lte\";\n/**\n * Error object for Stream binding operations.\n */\ninterface StreamError extends Error {\n  readonly code: number;\n  readonly statusCode: number;\n  readonly message: string;\n  readonly stack?: string;\n}\ninterface InternalError extends StreamError {\n  name: \"InternalError\";\n}\ninterface BadRequestError extends StreamError {\n  name: \"BadRequestError\";\n}\ninterface NotFoundError extends StreamError {\n  name: \"NotFoundError\";\n}\ninterface ForbiddenError extends StreamError {\n  name: \"ForbiddenError\";\n}\ninterface RateLimitedError extends StreamError {\n  name: \"RateLimitedError\";\n}\ninterface QuotaReachedError extends StreamError {\n  name: \"QuotaReachedError\";\n}\ninterface MaxFileSizeError extends StreamError {\n  name: \"MaxFileSizeError\";\n}\ninterface InvalidURLError extends StreamError {\n  name: \"InvalidURLError\";\n}\ninterface AlreadyUploadedError extends StreamError {\n  name: \"AlreadyUploadedError\";\n}\ninterface TooManyWatermarksError extends StreamError {\n  name: \"TooManyWatermarksError\";\n}\ntype MarkdownDocument = {\n  name: string;\n  blob: Blob;\n};\ntype ConversionResponse =\n  | {\n      id: string;\n      name: string;\n      mimeType: string;\n      format: \"markdown\";\n      tokens: number;\n      data: string;\n    }\n  | {\n      id: string;\n      name: string;\n      mimeType: string;\n      format: \"error\";\n      error: string;\n    };\ntype ImageConversionOptions = {\n  descriptionLanguage?: \"en\" | \"es\" | \"fr\" | \"it\" | \"pt\" | \"de\";\n};\ntype EmbeddedImageConversionOptions = ImageConversionOptions & {\n  convert?: boolean;\n  maxConvertedImages?: number;\n};\ntype ConversionOptions = {\n  html?: {\n    images?: EmbeddedImageConversionOptions & {\n      convertOGImage?: boolean;\n    };\n    hostname?: string;\n    cssSelector?: string;\n  };\n  docx?: {\n    images?: EmbeddedImageConversionOptions;\n  };\n  image?: ImageConversionOptions;\n  pdf?: {\n    images?: EmbeddedImageConversionOptions;\n    metadata?: boolean;\n  };\n};\ntype ConversionRequestOptions = {\n  gateway?: GatewayOptions;\n  extraHeaders?: object;\n  conversionOptions?: ConversionOptions;\n};\ntype SupportedFileFormat = {\n  mimeType: string;\n  extension: string;\n};\ndeclare abstract class ToMarkdownService {\n  transform(\n    files: MarkdownDocument[],\n    options?: ConversionRequestOptions,\n  ): Promise<ConversionResponse[]>;\n  transform(\n    files: MarkdownDocument,\n    options?: ConversionRequestOptions,\n  ): Promise<ConversionResponse>;\n  supported(): Promise<SupportedFileFormat[]>;\n}\ndeclare namespace TailStream {\n  interface Header {\n    readonly name: string;\n    readonly value: string;\n  }\n  interface FetchEventInfo {\n    readonly type: \"fetch\";\n    readonly method: string;\n    readonly url: string;\n    readonly cfJson?: object;\n    readonly headers: Header[];\n  }\n  interface JsRpcEventInfo {\n    readonly type: \"jsrpc\";\n  }\n  interface ScheduledEventInfo {\n    readonly type: \"scheduled\";\n    readonly scheduledTime: Date;\n    readonly cron: string;\n  }\n  interface AlarmEventInfo {\n    readonly type: \"alarm\";\n    readonly scheduledTime: Date;\n  }\n  interface QueueEventInfo {\n    readonly type: \"queue\";\n    readonly queueName: string;\n    readonly batchSize: number;\n  }\n  interface EmailEventInfo {\n    readonly type: \"email\";\n    readonly mailFrom: string;\n    readonly rcptTo: string;\n    readonly rawSize: number;\n  }\n  interface TraceEventInfo {\n    readonly type: \"trace\";\n    readonly traces: (string | null)[];\n  }\n  interface HibernatableWebSocketEventInfoMessage {\n    readonly type: \"message\";\n  }\n  interface HibernatableWebSocketEventInfoError {\n    readonly type: \"error\";\n  }\n  interface HibernatableWebSocketEventInfoClose {\n    readonly type: \"close\";\n    readonly code: number;\n    readonly wasClean: boolean;\n  }\n  interface HibernatableWebSocketEventInfo {\n    readonly type: \"hibernatableWebSocket\";\n    readonly info:\n      | HibernatableWebSocketEventInfoClose\n      | HibernatableWebSocketEventInfoError\n      | HibernatableWebSocketEventInfoMessage;\n  }\n  interface CustomEventInfo {\n    readonly type: \"custom\";\n  }\n  interface FetchResponseInfo {\n    readonly type: \"fetch\";\n    readonly statusCode: number;\n  }\n  type EventOutcome =\n    | \"ok\"\n    | \"canceled\"\n    | \"exception\"\n    | \"unknown\"\n    | \"killSwitch\"\n    | \"daemonDown\"\n    | \"exceededCpu\"\n    | \"exceededMemory\"\n    | \"loadShed\"\n    | \"responseStreamDisconnected\"\n    | \"scriptNotFound\";\n  interface ScriptVersion {\n    readonly id: string;\n    readonly tag?: string;\n    readonly message?: string;\n  }\n  interface Onset {\n    readonly type: \"onset\";\n    readonly attributes: Attribute[];\n    // id for the span being opened by this Onset event.\n    readonly spanId: string;\n    readonly dispatchNamespace?: string;\n    readonly entrypoint?: string;\n    readonly executionModel: string;\n    readonly scriptName?: string;\n    readonly scriptTags?: string[];\n    readonly scriptVersion?: ScriptVersion;\n    readonly info:\n      | FetchEventInfo\n      | JsRpcEventInfo\n      | ScheduledEventInfo\n      | AlarmEventInfo\n      | QueueEventInfo\n      | EmailEventInfo\n      | TraceEventInfo\n      | HibernatableWebSocketEventInfo\n      | CustomEventInfo;\n  }\n  interface Outcome {\n    readonly type: \"outcome\";\n    readonly outcome: EventOutcome;\n    readonly cpuTime: number;\n    readonly wallTime: number;\n  }\n  interface SpanOpen {\n    readonly type: \"spanOpen\";\n    readonly name: string;\n    // id for the span being opened by this SpanOpen event.\n    readonly spanId: string;\n    readonly info?: FetchEventInfo | JsRpcEventInfo | Attributes;\n  }\n  interface SpanClose {\n    readonly type: \"spanClose\";\n    readonly outcome: EventOutcome;\n  }\n  interface DiagnosticChannelEvent {\n    readonly type: \"diagnosticChannel\";\n    readonly channel: string;\n    readonly message: any;\n  }\n  interface Exception {\n    readonly type: \"exception\";\n    readonly name: string;\n    readonly message: string;\n    readonly stack?: string;\n  }\n  interface Log {\n    readonly type: \"log\";\n    readonly level: \"debug\" | \"error\" | \"info\" | \"log\" | \"warn\";\n    readonly message: object;\n  }\n  interface DroppedEventsDiagnostic {\n    readonly diagnosticsType: \"droppedEvents\";\n    readonly count: number;\n  }\n  interface StreamDiagnostic {\n    readonly type: \"streamDiagnostic\";\n    // To add new diagnostic types, define a new interface and add it to this union type.\n    readonly diagnostic: DroppedEventsDiagnostic;\n  }\n  // This marks the worker handler return information.\n  // This is separate from Outcome because the worker invocation can live for a long time after\n  // returning. For example - Websockets that return an http upgrade response but then continue\n  // streaming information or SSE http connections.\n  interface Return {\n    readonly type: \"return\";\n    readonly info?: FetchResponseInfo;\n  }\n  interface Attribute {\n    readonly name: string;\n    readonly value:\n      | string\n      | string[]\n      | boolean\n      | boolean[]\n      | number\n      | number[]\n      | bigint\n      | bigint[];\n  }\n  interface Attributes {\n    readonly type: \"attributes\";\n    readonly info: Attribute[];\n  }\n  type EventType =\n    | Onset\n    | Outcome\n    | SpanOpen\n    | SpanClose\n    | DiagnosticChannelEvent\n    | Exception\n    | Log\n    | StreamDiagnostic\n    | Return\n    | Attributes;\n  // Context in which this trace event lives.\n  interface SpanContext {\n    // Single id for the entire top-level invocation\n    // This should be a new traceId for the first worker stage invoked in the eyeball request and then\n    // same-account service-bindings should reuse the same traceId but cross-account service-bindings\n    // should use a new traceId.\n    readonly traceId: string;\n    // spanId in which this event is handled\n    // for Onset and SpanOpen events this would be the parent span id\n    // for Outcome and SpanClose these this would be the span id of the opening Onset and SpanOpen events\n    // For Hibernate and Mark this would be the span under which they were emitted.\n    // spanId is not set ONLY if:\n    //  1. This is an Onset event\n    //  2. We are not inheriting any SpanContext. (e.g. this is a cross-account service binding or a new top-level invocation)\n    readonly spanId?: string;\n  }\n  interface TailEvent<Event extends EventType> {\n    // invocation id of the currently invoked worker stage.\n    // invocation id will always be unique to every Onset event and will be the same until the Outcome event.\n    readonly invocationId: string;\n    // Inherited spanContext for this event.\n    readonly spanContext: SpanContext;\n    readonly timestamp: Date;\n    readonly sequence: number;\n    readonly event: Event;\n  }\n  type TailEventHandler<Event extends EventType = EventType> = (\n    event: TailEvent<Event>,\n  ) => void | Promise<void>;\n  type TailEventHandlerObject = {\n    outcome?: TailEventHandler<Outcome>;\n    spanOpen?: TailEventHandler<SpanOpen>;\n    spanClose?: TailEventHandler<SpanClose>;\n    diagnosticChannel?: TailEventHandler<DiagnosticChannelEvent>;\n    exception?: TailEventHandler<Exception>;\n    log?: TailEventHandler<Log>;\n    return?: TailEventHandler<Return>;\n    attributes?: TailEventHandler<Attributes>;\n  };\n  type TailEventHandlerType = TailEventHandler | TailEventHandlerObject;\n}\n// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n/**\n * Data types supported for holding vector metadata.\n */\ntype VectorizeVectorMetadataValue = string | number | boolean | string[];\n/**\n * Additional information to associate with a vector.\n */\ntype VectorizeVectorMetadata =\n  | VectorizeVectorMetadataValue\n  | Record<string, VectorizeVectorMetadataValue>;\ntype VectorFloatArray = Float32Array | Float64Array;\ninterface VectorizeError {\n  code?: number;\n  error: string;\n}\n/**\n * Comparison logic/operation to use for metadata filtering.\n *\n * This list is expected to grow as support for more operations are released.\n */\ntype VectorizeVectorMetadataFilterOp =\n  | \"$eq\"\n  | \"$ne\"\n  | \"$lt\"\n  | \"$lte\"\n  | \"$gt\"\n  | \"$gte\";\ntype VectorizeVectorMetadataFilterCollectionOp = \"$in\" | \"$nin\";\n/**\n * Filter criteria for vector metadata used to limit the retrieved query result set.\n */\ntype VectorizeVectorMetadataFilter = {\n  [field: string]:\n    | Exclude<VectorizeVectorMetadataValue, string[]>\n    | null\n    | {\n        [Op in VectorizeVectorMetadataFilterOp]?: Exclude<\n          VectorizeVectorMetadataValue,\n          string[]\n        > | null;\n      }\n    | {\n        [Op in VectorizeVectorMetadataFilterCollectionOp]?: Exclude<\n          VectorizeVectorMetadataValue,\n          string[]\n        >[];\n      };\n};\n/**\n * Supported distance metrics for an index.\n * Distance metrics determine how other \"similar\" vectors are determined.\n */\ntype VectorizeDistanceMetric = \"euclidean\" | \"cosine\" | \"dot-product\";\n/**\n * Metadata return levels for a Vectorize query.\n *\n * Default to \"none\".\n *\n * @property all      Full metadata for the vector return set, including all fields (including those un-indexed) without truncation. This is a more expensive retrieval, as it requires additional fetching & reading of un-indexed data.\n * @property indexed  Return all metadata fields configured for indexing in the vector return set. This level of retrieval is \"free\" in that no additional overhead is incurred returning this data. However, note that indexed metadata is subject to truncation (especially for larger strings).\n * @property none     No indexed metadata will be returned.\n */\ntype VectorizeMetadataRetrievalLevel = \"all\" | \"indexed\" | \"none\";\ninterface VectorizeQueryOptions {\n  topK?: number;\n  namespace?: string;\n  returnValues?: boolean;\n  returnMetadata?: boolean | VectorizeMetadataRetrievalLevel;\n  filter?: VectorizeVectorMetadataFilter;\n}\n/**\n * Information about the configuration of an index.\n */\ntype VectorizeIndexConfig =\n  | {\n      dimensions: number;\n      metric: VectorizeDistanceMetric;\n    }\n  | {\n      preset: string; // keep this generic, as we'll be adding more presets in the future and this is only in a read capacity\n    };\n/**\n * Metadata about an existing index.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link VectorizeIndexInfo} for its post-beta equivalent.\n */\ninterface VectorizeIndexDetails {\n  /** The unique ID of the index */\n  readonly id: string;\n  /** The name of the index. */\n  name: string;\n  /** (optional) A human readable description for the index. */\n  description?: string;\n  /** The index configuration, including the dimension size and distance metric. */\n  config: VectorizeIndexConfig;\n  /** The number of records containing vectors within the index. */\n  vectorsCount: number;\n}\n/**\n * Metadata about an existing index.\n */\ninterface VectorizeIndexInfo {\n  /** The number of records containing vectors within the index. */\n  vectorCount: number;\n  /** Number of dimensions the index has been configured for. */\n  dimensions: number;\n  /** ISO 8601 datetime of the last processed mutation on in the index. All changes before this mutation will be reflected in the index state. */\n  processedUpToDatetime: number;\n  /** UUIDv4 of the last mutation processed by the index. All changes before this mutation will be reflected in the index state. */\n  processedUpToMutation: number;\n}\n/**\n * Represents a single vector value set along with its associated metadata.\n */\ninterface VectorizeVector {\n  /** The ID for the vector. This can be user-defined, and must be unique. It should uniquely identify the object, and is best set based on the ID of what the vector represents. */\n  id: string;\n  /** The vector values */\n  values: VectorFloatArray | number[];\n  /** The namespace this vector belongs to. */\n  namespace?: string;\n  /** Metadata associated with the vector. Includes the values of other fields and potentially additional details. */\n  metadata?: Record<string, VectorizeVectorMetadata>;\n}\n/**\n * Represents a matched vector for a query along with its score and (if specified) the matching vector information.\n */\ntype VectorizeMatch = Pick<Partial<VectorizeVector>, \"values\"> &\n  Omit<VectorizeVector, \"values\"> & {\n    /** The score or rank for similarity, when returned as a result */\n    score: number;\n  };\n/**\n * A set of matching {@link VectorizeMatch} for a particular query.\n */\ninterface VectorizeMatches {\n  matches: VectorizeMatch[];\n  count: number;\n}\n/**\n * Results of an operation that performed a mutation on a set of vectors.\n * Here, `ids` is a list of vectors that were successfully processed.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link VectorizeAsyncMutation} for its post-beta equivalent.\n */\ninterface VectorizeVectorMutation {\n  /* List of ids of vectors that were successfully processed. */\n  ids: string[];\n  /* Total count of the number of processed vectors. */\n  count: number;\n}\n/**\n * Result type indicating a mutation on the Vectorize Index.\n * Actual mutations are processed async where the `mutationId` is the unique identifier for the operation.\n */\ninterface VectorizeAsyncMutation {\n  /** The unique identifier for the async mutation operation containing the changeset. */\n  mutationId: string;\n}\n/**\n * A Vectorize Vector Search Index for querying vectors/embeddings.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link Vectorize} for its new implementation.\n */\ndeclare abstract class VectorizeIndex {\n  /**\n   * Get information about the currently bound index.\n   * @returns A promise that resolves with information about the current index.\n   */\n  public describe(): Promise<VectorizeIndexDetails>;\n  /**\n   * Use the provided vector to perform a similarity search across the index.\n   * @param vector Input vector that will be used to drive the similarity search.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  public query(\n    vector: VectorFloatArray | number[],\n    options?: VectorizeQueryOptions,\n  ): Promise<VectorizeMatches>;\n  /**\n   * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown.\n   * @param vectors List of vectors that will be inserted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed.\n   */\n  public insert(vectors: VectorizeVector[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values.\n   * @param vectors List of vectors that will be upserted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed.\n   */\n  public upsert(vectors: VectorizeVector[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Delete a list of vectors with a matching id.\n   * @param ids List of vector ids that should be deleted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed (and thus deleted).\n   */\n  public deleteByIds(ids: string[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Get a list of vectors with a matching id.\n   * @param ids List of vector ids that should be returned.\n   * @returns A promise that resolves with the raw unscored vectors matching the id set.\n   */\n  public getByIds(ids: string[]): Promise<VectorizeVector[]>;\n}\n/**\n * A Vectorize Vector Search Index for querying vectors/embeddings.\n *\n * Mutations in this version are async, returning a mutation id.\n */\ndeclare abstract class Vectorize {\n  /**\n   * Get information about the currently bound index.\n   * @returns A promise that resolves with information about the current index.\n   */\n  public describe(): Promise<VectorizeIndexInfo>;\n  /**\n   * Use the provided vector to perform a similarity search across the index.\n   * @param vector Input vector that will be used to drive the similarity search.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  public query(\n    vector: VectorFloatArray | number[],\n    options?: VectorizeQueryOptions,\n  ): Promise<VectorizeMatches>;\n  /**\n   * Use the provided vector-id to perform a similarity search across the index.\n   * @param vectorId Id for a vector in the index against which the index should be queried.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  public queryById(\n    vectorId: string,\n    options?: VectorizeQueryOptions,\n  ): Promise<VectorizeMatches>;\n  /**\n   * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown.\n   * @param vectors List of vectors that will be inserted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the insert changeset.\n   */\n  public insert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values.\n   * @param vectors List of vectors that will be upserted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the upsert changeset.\n   */\n  public upsert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Delete a list of vectors with a matching id.\n   * @param ids List of vector ids that should be deleted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the delete changeset.\n   */\n  public deleteByIds(ids: string[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Get a list of vectors with a matching id.\n   * @param ids List of vector ids that should be returned.\n   * @returns A promise that resolves with the raw unscored vectors matching the id set.\n   */\n  public getByIds(ids: string[]): Promise<VectorizeVector[]>;\n}\n/**\n * The interface for \"version_metadata\" binding\n * providing metadata about the Worker Version using this binding.\n */\ntype WorkerVersionMetadata = {\n  /** The ID of the Worker Version using this binding */\n  id: string;\n  /** The tag of the Worker Version using this binding */\n  tag: string;\n  /** The timestamp of when the Worker Version was uploaded */\n  timestamp: string;\n};\ninterface DynamicDispatchLimits {\n  /**\n   * Limit CPU time in milliseconds.\n   */\n  cpuMs?: number;\n  /**\n   * Limit number of subrequests.\n   */\n  subRequests?: number;\n}\ninterface DynamicDispatchOptions {\n  /**\n   * Limit resources of invoked Worker script.\n   */\n  limits?: DynamicDispatchLimits;\n  /**\n   * Arguments for outbound Worker script, if configured.\n   */\n  outbound?: {\n    [key: string]: any;\n  };\n}\ninterface DispatchNamespace {\n  /**\n   * @param name Name of the Worker script.\n   * @param args Arguments to Worker script.\n   * @param options Options for Dynamic Dispatch invocation.\n   * @returns A Fetcher object that allows you to send requests to the Worker script.\n   * @throws If the Worker script does not exist in this dispatch namespace, an error will be thrown.\n   */\n  get(\n    name: string,\n    args?: {\n      [key: string]: any;\n    },\n    options?: DynamicDispatchOptions,\n  ): Fetcher;\n}\ndeclare module \"cloudflare:workflows\" {\n  /**\n   * NonRetryableError allows for a user to throw a fatal error\n   * that makes a Workflow instance fail immediately without triggering a retry\n   */\n  export class NonRetryableError extends Error {\n    public constructor(message: string, name?: string);\n  }\n}\ndeclare abstract class Workflow<PARAMS = unknown> {\n  /**\n   * Get a handle to an existing instance of the Workflow.\n   * @param id Id for the instance of this Workflow\n   * @returns A promise that resolves with a handle for the Instance\n   */\n  public get(id: string): Promise<WorkflowInstance>;\n  /**\n   * Create a new instance and return a handle to it. If a provided id exists, an error will be thrown.\n   * @param options Options when creating an instance including id and params\n   * @returns A promise that resolves with a handle for the Instance\n   */\n  public create(\n    options?: WorkflowInstanceCreateOptions<PARAMS>,\n  ): Promise<WorkflowInstance>;\n  /**\n   * Create a batch of instances and return handle for all of them. If a provided id exists, an error will be thrown.\n   * `createBatch` is limited at 100 instances at a time or when the RPC limit for the batch (1MiB) is reached.\n   * @param batch List of Options when creating an instance including name and params\n   * @returns A promise that resolves with a list of handles for the created instances.\n   */\n  public createBatch(\n    batch: WorkflowInstanceCreateOptions<PARAMS>[],\n  ): Promise<WorkflowInstance[]>;\n}\ntype WorkflowDurationLabel =\n  | \"second\"\n  | \"minute\"\n  | \"hour\"\n  | \"day\"\n  | \"week\"\n  | \"month\"\n  | \"year\";\ntype WorkflowSleepDuration =\n  | `${number} ${WorkflowDurationLabel}${\"s\" | \"\"}`\n  | number;\ntype WorkflowRetentionDuration = WorkflowSleepDuration;\ninterface WorkflowInstanceCreateOptions<PARAMS = unknown> {\n  /**\n   * An id for your Workflow instance. Must be unique within the Workflow.\n   */\n  id?: string;\n  /**\n   * The event payload the Workflow instance is triggered with\n   */\n  params?: PARAMS;\n  /**\n   * The retention policy for Workflow instance.\n   * Defaults to the maximum retention period available for the owner's account.\n   */\n  retention?: {\n    successRetention?: WorkflowRetentionDuration;\n    errorRetention?: WorkflowRetentionDuration;\n  };\n}\ntype InstanceStatus = {\n  status:\n    | \"queued\" // means that instance is waiting to be started (see concurrency limits)\n    | \"running\"\n    | \"paused\"\n    | \"errored\"\n    | \"terminated\" // user terminated the instance while it was running\n    | \"complete\"\n    | \"waiting\" // instance is hibernating and waiting for sleep or event to finish\n    | \"waitingForPause\" // instance is finishing the current work to pause\n    | \"unknown\";\n  error?: {\n    name: string;\n    message: string;\n  };\n  output?: unknown;\n};\ninterface WorkflowError {\n  code?: number;\n  message: string;\n}\ndeclare abstract class WorkflowInstance {\n  public id: string;\n  /**\n   * Pause the instance.\n   */\n  public pause(): Promise<void>;\n  /**\n   * Resume the instance. If it is already running, an error will be thrown.\n   */\n  public resume(): Promise<void>;\n  /**\n   * Terminate the instance. If it is errored, terminated or complete, an error will be thrown.\n   */\n  public terminate(): Promise<void>;\n  /**\n   * Restart the instance.\n   */\n  public restart(): Promise<void>;\n  /**\n   * Returns the current status of the instance.\n   */\n  public status(): Promise<InstanceStatus>;\n  /**\n   * Send an event to this instance.\n   */\n  public sendEvent({\n    type,\n    payload,\n  }: {\n    type: string;\n    payload: unknown;\n  }): Promise<void>;\n}\n"
  },
  {
    "path": "types/generated-snapshot/latest/index.ts",
    "content": "/*! *****************************************************************************\nCopyright (c) Cloudflare. All rights reserved.\nCopyright (c) Microsoft Corporation. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at http://www.apache.org/licenses/LICENSE-2.0\nTHIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\nKIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED\nWARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,\nMERCHANTABLITY OR NON-INFRINGEMENT.\nSee the Apache Version 2.0 License for specific language governing permissions\nand limitations under the License.\n***************************************************************************** */\n/* eslint-disable */\n// noinspection JSUnusedGlobalSymbols\nexport declare var onmessage: never;\n/**\n * The **`DOMException`** interface represents an abnormal event (called an **exception**) that occurs as a result of calling a method or accessing a property of a web API.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException)\n */\nexport declare class DOMException extends Error {\n  constructor(message?: string, name?: string);\n  /**\n   * The **`message`** read-only property of the a message or description associated with the given error name.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/message)\n   */\n  readonly message: string;\n  /**\n   * The **`name`** read-only property of the one of the strings associated with an error name.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/name)\n   */\n  readonly name: string;\n  /**\n   * The **`code`** read-only property of the DOMException interface returns one of the legacy error code constants, or `0` if none match.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/code)\n   */\n  readonly code: number;\n  static readonly INDEX_SIZE_ERR: number;\n  static readonly DOMSTRING_SIZE_ERR: number;\n  static readonly HIERARCHY_REQUEST_ERR: number;\n  static readonly WRONG_DOCUMENT_ERR: number;\n  static readonly INVALID_CHARACTER_ERR: number;\n  static readonly NO_DATA_ALLOWED_ERR: number;\n  static readonly NO_MODIFICATION_ALLOWED_ERR: number;\n  static readonly NOT_FOUND_ERR: number;\n  static readonly NOT_SUPPORTED_ERR: number;\n  static readonly INUSE_ATTRIBUTE_ERR: number;\n  static readonly INVALID_STATE_ERR: number;\n  static readonly SYNTAX_ERR: number;\n  static readonly INVALID_MODIFICATION_ERR: number;\n  static readonly NAMESPACE_ERR: number;\n  static readonly INVALID_ACCESS_ERR: number;\n  static readonly VALIDATION_ERR: number;\n  static readonly TYPE_MISMATCH_ERR: number;\n  static readonly SECURITY_ERR: number;\n  static readonly NETWORK_ERR: number;\n  static readonly ABORT_ERR: number;\n  static readonly URL_MISMATCH_ERR: number;\n  static readonly QUOTA_EXCEEDED_ERR: number;\n  static readonly TIMEOUT_ERR: number;\n  static readonly INVALID_NODE_TYPE_ERR: number;\n  static readonly DATA_CLONE_ERR: number;\n  get stack(): any;\n  set stack(value: any);\n}\nexport type WorkerGlobalScopeEventMap = {\n  fetch: FetchEvent;\n  scheduled: ScheduledEvent;\n  queue: QueueEvent;\n  unhandledrejection: PromiseRejectionEvent;\n  rejectionhandled: PromiseRejectionEvent;\n};\nexport declare abstract class WorkerGlobalScope extends EventTarget<WorkerGlobalScopeEventMap> {\n  EventTarget: typeof EventTarget;\n}\n/* The **`console`** object provides access to the debugging console (e.g., the Web console in Firefox). *\n * The **`console`** object provides access to the debugging console (e.g., the Web console in Firefox).\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console)\n */\nexport interface Console {\n  \"assert\"(condition?: boolean, ...data: any[]): void;\n  /**\n   * The **`console.clear()`** static method clears the console if possible.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/clear_static)\n   */\n  clear(): void;\n  /**\n   * The **`console.count()`** static method logs the number of times that this particular call to `count()` has been called.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/count_static)\n   */\n  count(label?: string): void;\n  /**\n   * The **`console.countReset()`** static method resets counter used with console/count_static.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/countReset_static)\n   */\n  countReset(label?: string): void;\n  /**\n   * The **`console.debug()`** static method outputs a message to the console at the 'debug' log level.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/debug_static)\n   */\n  debug(...data: any[]): void;\n  /**\n   * The **`console.dir()`** static method displays a list of the properties of the specified JavaScript object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dir_static)\n   */\n  dir(item?: any, options?: any): void;\n  /**\n   * The **`console.dirxml()`** static method displays an interactive tree of the descendant elements of the specified XML/HTML element.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dirxml_static)\n   */\n  dirxml(...data: any[]): void;\n  /**\n   * The **`console.error()`** static method outputs a message to the console at the 'error' log level.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/error_static)\n   */\n  error(...data: any[]): void;\n  /**\n   * The **`console.group()`** static method creates a new inline group in the Web console log, causing any subsequent console messages to be indented by an additional level, until console/groupEnd_static is called.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/group_static)\n   */\n  group(...data: any[]): void;\n  /**\n   * The **`console.groupCollapsed()`** static method creates a new inline group in the console.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupCollapsed_static)\n   */\n  groupCollapsed(...data: any[]): void;\n  /**\n   * The **`console.groupEnd()`** static method exits the current inline group in the console.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupEnd_static)\n   */\n  groupEnd(): void;\n  /**\n   * The **`console.info()`** static method outputs a message to the console at the 'info' log level.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/info_static)\n   */\n  info(...data: any[]): void;\n  /**\n   * The **`console.log()`** static method outputs a message to the console.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)\n   */\n  log(...data: any[]): void;\n  /**\n   * The **`console.table()`** static method displays tabular data as a table.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/table_static)\n   */\n  table(tabularData?: any, properties?: string[]): void;\n  /**\n   * The **`console.time()`** static method starts a timer you can use to track how long an operation takes.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/time_static)\n   */\n  time(label?: string): void;\n  /**\n   * The **`console.timeEnd()`** static method stops a timer that was previously started by calling console/time_static.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeEnd_static)\n   */\n  timeEnd(label?: string): void;\n  /**\n   * The **`console.timeLog()`** static method logs the current value of a timer that was previously started by calling console/time_static.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeLog_static)\n   */\n  timeLog(label?: string, ...data: any[]): void;\n  timeStamp(label?: string): void;\n  /**\n   * The **`console.trace()`** static method outputs a stack trace to the console.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/trace_static)\n   */\n  trace(...data: any[]): void;\n  /**\n   * The **`console.warn()`** static method outputs a warning message to the console at the 'warning' log level.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/warn_static)\n   */\n  warn(...data: any[]): void;\n}\nexport declare const console: Console;\nexport type BufferSource = ArrayBufferView | ArrayBuffer;\nexport type TypedArray =\n  | Int8Array\n  | Uint8Array\n  | Uint8ClampedArray\n  | Int16Array\n  | Uint16Array\n  | Int32Array\n  | Uint32Array\n  | Float32Array\n  | Float64Array\n  | BigInt64Array\n  | BigUint64Array;\nexport declare namespace WebAssembly {\n  class CompileError extends Error {\n    constructor(message?: string);\n  }\n  class RuntimeError extends Error {\n    constructor(message?: string);\n  }\n  type ValueType =\n    | \"anyfunc\"\n    | \"externref\"\n    | \"f32\"\n    | \"f64\"\n    | \"i32\"\n    | \"i64\"\n    | \"v128\";\n  interface GlobalDescriptor {\n    value: ValueType;\n    mutable?: boolean;\n  }\n  class Global {\n    constructor(descriptor: GlobalDescriptor, value?: any);\n    value: any;\n    valueOf(): any;\n  }\n  type ImportValue = ExportValue | number;\n  type ModuleImports = Record<string, ImportValue>;\n  type Imports = Record<string, ModuleImports>;\n  type ExportValue = Function | Global | Memory | Table;\n  type Exports = Record<string, ExportValue>;\n  class Instance {\n    constructor(module: Module, imports?: Imports);\n    readonly exports: Exports;\n  }\n  interface MemoryDescriptor {\n    initial: number;\n    maximum?: number;\n    shared?: boolean;\n  }\n  class Memory {\n    constructor(descriptor: MemoryDescriptor);\n    readonly buffer: ArrayBuffer;\n    grow(delta: number): number;\n  }\n  type ImportExportKind = \"function\" | \"global\" | \"memory\" | \"table\";\n  interface ModuleExportDescriptor {\n    kind: ImportExportKind;\n    name: string;\n  }\n  interface ModuleImportDescriptor {\n    kind: ImportExportKind;\n    module: string;\n    name: string;\n  }\n  abstract class Module {\n    static customSections(module: Module, sectionName: string): ArrayBuffer[];\n    static exports(module: Module): ModuleExportDescriptor[];\n    static imports(module: Module): ModuleImportDescriptor[];\n  }\n  type TableKind = \"anyfunc\" | \"externref\";\n  interface TableDescriptor {\n    element: TableKind;\n    initial: number;\n    maximum?: number;\n  }\n  class Table {\n    constructor(descriptor: TableDescriptor, value?: any);\n    readonly length: number;\n    get(index: number): any;\n    grow(delta: number, value?: any): number;\n    set(index: number, value?: any): void;\n  }\n  function instantiate(module: Module, imports?: Imports): Promise<Instance>;\n  function validate(bytes: BufferSource): boolean;\n}\n/**\n * The **`ServiceWorkerGlobalScope`** interface of the Service Worker API represents the global execution context of a service worker.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope)\n */\nexport interface ServiceWorkerGlobalScope extends WorkerGlobalScope {\n  DOMException: typeof DOMException;\n  WorkerGlobalScope: typeof WorkerGlobalScope;\n  btoa(data: string): string;\n  atob(data: string): string;\n  setTimeout(callback: (...args: any[]) => void, msDelay?: number): number;\n  setTimeout<Args extends any[]>(\n    callback: (...args: Args) => void,\n    msDelay?: number,\n    ...args: Args\n  ): number;\n  clearTimeout(timeoutId: number | null): void;\n  setInterval(callback: (...args: any[]) => void, msDelay?: number): number;\n  setInterval<Args extends any[]>(\n    callback: (...args: Args) => void,\n    msDelay?: number,\n    ...args: Args\n  ): number;\n  clearInterval(timeoutId: number | null): void;\n  queueMicrotask(task: Function): void;\n  structuredClone<T>(value: T, options?: StructuredSerializeOptions): T;\n  reportError(error: any): void;\n  fetch(\n    input: RequestInfo | URL,\n    init?: RequestInit<RequestInitCfProperties>,\n  ): Promise<Response>;\n  self: ServiceWorkerGlobalScope;\n  crypto: Crypto;\n  caches: CacheStorage;\n  scheduler: Scheduler;\n  performance: Performance;\n  Cloudflare: Cloudflare;\n  readonly origin: string;\n  Event: typeof Event;\n  ExtendableEvent: typeof ExtendableEvent;\n  CustomEvent: typeof CustomEvent;\n  PromiseRejectionEvent: typeof PromiseRejectionEvent;\n  FetchEvent: typeof FetchEvent;\n  TailEvent: typeof TailEvent;\n  TraceEvent: typeof TailEvent;\n  ScheduledEvent: typeof ScheduledEvent;\n  MessageEvent: typeof MessageEvent;\n  CloseEvent: typeof CloseEvent;\n  ReadableStreamDefaultReader: typeof ReadableStreamDefaultReader;\n  ReadableStreamBYOBReader: typeof ReadableStreamBYOBReader;\n  ReadableStream: typeof ReadableStream;\n  WritableStream: typeof WritableStream;\n  WritableStreamDefaultWriter: typeof WritableStreamDefaultWriter;\n  TransformStream: typeof TransformStream;\n  ByteLengthQueuingStrategy: typeof ByteLengthQueuingStrategy;\n  CountQueuingStrategy: typeof CountQueuingStrategy;\n  ErrorEvent: typeof ErrorEvent;\n  MessageChannel: typeof MessageChannel;\n  MessagePort: typeof MessagePort;\n  EventSource: typeof EventSource;\n  ReadableStreamBYOBRequest: typeof ReadableStreamBYOBRequest;\n  ReadableStreamDefaultController: typeof ReadableStreamDefaultController;\n  ReadableByteStreamController: typeof ReadableByteStreamController;\n  WritableStreamDefaultController: typeof WritableStreamDefaultController;\n  TransformStreamDefaultController: typeof TransformStreamDefaultController;\n  CompressionStream: typeof CompressionStream;\n  DecompressionStream: typeof DecompressionStream;\n  TextEncoderStream: typeof TextEncoderStream;\n  TextDecoderStream: typeof TextDecoderStream;\n  Headers: typeof Headers;\n  Body: typeof Body;\n  Request: typeof Request;\n  Response: typeof Response;\n  WebSocket: typeof WebSocket;\n  WebSocketPair: typeof WebSocketPair;\n  WebSocketRequestResponsePair: typeof WebSocketRequestResponsePair;\n  AbortController: typeof AbortController;\n  AbortSignal: typeof AbortSignal;\n  TextDecoder: typeof TextDecoder;\n  TextEncoder: typeof TextEncoder;\n  navigator: Navigator;\n  Navigator: typeof Navigator;\n  URL: typeof URL;\n  URLSearchParams: typeof URLSearchParams;\n  URLPattern: typeof URLPattern;\n  Blob: typeof Blob;\n  File: typeof File;\n  FormData: typeof FormData;\n  Crypto: typeof Crypto;\n  SubtleCrypto: typeof SubtleCrypto;\n  CryptoKey: typeof CryptoKey;\n  CacheStorage: typeof CacheStorage;\n  Cache: typeof Cache;\n  FixedLengthStream: typeof FixedLengthStream;\n  IdentityTransformStream: typeof IdentityTransformStream;\n  HTMLRewriter: typeof HTMLRewriter;\n}\nexport declare function addEventListener<\n  Type extends keyof WorkerGlobalScopeEventMap,\n>(\n  type: Type,\n  handler: EventListenerOrEventListenerObject<WorkerGlobalScopeEventMap[Type]>,\n  options?: EventTargetAddEventListenerOptions | boolean,\n): void;\nexport declare function removeEventListener<\n  Type extends keyof WorkerGlobalScopeEventMap,\n>(\n  type: Type,\n  handler: EventListenerOrEventListenerObject<WorkerGlobalScopeEventMap[Type]>,\n  options?: EventTargetEventListenerOptions | boolean,\n): void;\n/**\n * The **`dispatchEvent()`** method of the EventTarget sends an Event to the object, (synchronously) invoking the affected event listeners in the appropriate order.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent)\n */\nexport declare function dispatchEvent(\n  event: WorkerGlobalScopeEventMap[keyof WorkerGlobalScopeEventMap],\n): boolean;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/btoa) */\nexport declare function btoa(data: string): string;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/atob) */\nexport declare function atob(data: string): string;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */\nexport declare function setTimeout(\n  callback: (...args: any[]) => void,\n  msDelay?: number,\n): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */\nexport declare function setTimeout<Args extends any[]>(\n  callback: (...args: Args) => void,\n  msDelay?: number,\n  ...args: Args\n): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearTimeout) */\nexport declare function clearTimeout(timeoutId: number | null): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */\nexport declare function setInterval(\n  callback: (...args: any[]) => void,\n  msDelay?: number,\n): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */\nexport declare function setInterval<Args extends any[]>(\n  callback: (...args: Args) => void,\n  msDelay?: number,\n  ...args: Args\n): number;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearInterval) */\nexport declare function clearInterval(timeoutId: number | null): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/queueMicrotask) */\nexport declare function queueMicrotask(task: Function): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/structuredClone) */\nexport declare function structuredClone<T>(\n  value: T,\n  options?: StructuredSerializeOptions,\n): T;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/reportError) */\nexport declare function reportError(error: any): void;\n/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch) */\nexport declare function fetch(\n  input: RequestInfo | URL,\n  init?: RequestInit<RequestInitCfProperties>,\n): Promise<Response>;\nexport declare const self: ServiceWorkerGlobalScope;\n/**\n * The Web Crypto API provides a set of low-level functions for common cryptographic tasks.\n * The Workers runtime implements the full surface of this API, but with some differences in\n * the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms)\n * compared to those implemented in most browsers.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/)\n */\nexport declare const crypto: Crypto;\n/**\n * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)\n */\nexport declare const caches: CacheStorage;\nexport declare const scheduler: Scheduler;\n/**\n * The Workers runtime supports a subset of the Performance API, used to measure timing and performance,\n * as well as timing of subrequests and other operations.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/)\n */\nexport declare const performance: Performance;\nexport declare const Cloudflare: Cloudflare;\nexport declare const origin: string;\nexport declare const navigator: Navigator;\nexport interface TestController {}\nexport interface ExecutionContext<Props = unknown> {\n  waitUntil(promise: Promise<any>): void;\n  passThroughOnException(): void;\n  readonly exports: Cloudflare.Exports;\n  readonly props: Props;\n}\nexport type ExportedHandlerFetchHandler<\n  Env = unknown,\n  CfHostMetadata = unknown,\n  Props = unknown,\n> = (\n  request: Request<CfHostMetadata, IncomingRequestCfProperties<CfHostMetadata>>,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => Response | Promise<Response>;\nexport type ExportedHandlerTailHandler<Env = unknown, Props = unknown> = (\n  events: TraceItem[],\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\nexport type ExportedHandlerTraceHandler<Env = unknown, Props = unknown> = (\n  traces: TraceItem[],\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\nexport type ExportedHandlerTailStreamHandler<Env = unknown, Props = unknown> = (\n  event: TailStream.TailEvent<TailStream.Onset>,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => TailStream.TailEventHandlerType | Promise<TailStream.TailEventHandlerType>;\nexport type ExportedHandlerScheduledHandler<Env = unknown, Props = unknown> = (\n  controller: ScheduledController,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\nexport type ExportedHandlerQueueHandler<\n  Env = unknown,\n  Message = unknown,\n  Props = unknown,\n> = (\n  batch: MessageBatch<Message>,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\nexport type ExportedHandlerTestHandler<Env = unknown, Props = unknown> = (\n  controller: TestController,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\nexport interface ExportedHandler<\n  Env = unknown,\n  QueueHandlerMessage = unknown,\n  CfHostMetadata = unknown,\n  Props = unknown,\n> {\n  fetch?: ExportedHandlerFetchHandler<Env, CfHostMetadata, Props>;\n  tail?: ExportedHandlerTailHandler<Env, Props>;\n  trace?: ExportedHandlerTraceHandler<Env, Props>;\n  tailStream?: ExportedHandlerTailStreamHandler<Env, Props>;\n  scheduled?: ExportedHandlerScheduledHandler<Env, Props>;\n  test?: ExportedHandlerTestHandler<Env, Props>;\n  email?: EmailExportedHandler<Env, Props>;\n  queue?: ExportedHandlerQueueHandler<Env, QueueHandlerMessage, Props>;\n}\nexport interface StructuredSerializeOptions {\n  transfer?: any[];\n}\nexport declare abstract class Navigator {\n  sendBeacon(url: string, body?: BodyInit): boolean;\n  readonly userAgent: string;\n  readonly hardwareConcurrency: number;\n  readonly language: string;\n  readonly languages: string[];\n}\nexport interface AlarmInvocationInfo {\n  readonly isRetry: boolean;\n  readonly retryCount: number;\n}\nexport interface Cloudflare {\n  readonly compatibilityFlags: Record<string, boolean>;\n}\nexport interface DurableObject {\n  fetch(request: Request): Response | Promise<Response>;\n  alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise<void>;\n  webSocketMessage?(\n    ws: WebSocket,\n    message: string | ArrayBuffer,\n  ): void | Promise<void>;\n  webSocketClose?(\n    ws: WebSocket,\n    code: number,\n    reason: string,\n    wasClean: boolean,\n  ): void | Promise<void>;\n  webSocketError?(ws: WebSocket, error: unknown): void | Promise<void>;\n}\nexport type DurableObjectStub<\n  T extends Rpc.DurableObjectBranded | undefined = undefined,\n> = Fetcher<\n  T,\n  \"alarm\" | \"webSocketMessage\" | \"webSocketClose\" | \"webSocketError\"\n> & {\n  readonly id: DurableObjectId;\n  readonly name?: string;\n};\nexport interface DurableObjectId {\n  toString(): string;\n  equals(other: DurableObjectId): boolean;\n  readonly name?: string;\n  readonly jurisdiction?: string;\n}\nexport declare abstract class DurableObjectNamespace<\n  T extends Rpc.DurableObjectBranded | undefined = undefined,\n> {\n  newUniqueId(\n    options?: DurableObjectNamespaceNewUniqueIdOptions,\n  ): DurableObjectId;\n  idFromName(name: string): DurableObjectId;\n  idFromString(id: string): DurableObjectId;\n  get(\n    id: DurableObjectId,\n    options?: DurableObjectNamespaceGetDurableObjectOptions,\n  ): DurableObjectStub<T>;\n  getByName(\n    name: string,\n    options?: DurableObjectNamespaceGetDurableObjectOptions,\n  ): DurableObjectStub<T>;\n  jurisdiction(\n    jurisdiction: DurableObjectJurisdiction,\n  ): DurableObjectNamespace<T>;\n}\nexport type DurableObjectJurisdiction = \"eu\" | \"fedramp\" | \"fedramp-high\";\nexport interface DurableObjectNamespaceNewUniqueIdOptions {\n  jurisdiction?: DurableObjectJurisdiction;\n}\nexport type DurableObjectLocationHint =\n  | \"wnam\"\n  | \"enam\"\n  | \"sam\"\n  | \"weur\"\n  | \"eeur\"\n  | \"apac\"\n  | \"oc\"\n  | \"afr\"\n  | \"me\";\nexport type DurableObjectRoutingMode = \"primary-only\";\nexport interface DurableObjectNamespaceGetDurableObjectOptions {\n  locationHint?: DurableObjectLocationHint;\n  routingMode?: DurableObjectRoutingMode;\n}\nexport interface DurableObjectClass<\n  _T extends Rpc.DurableObjectBranded | undefined = undefined,\n> {}\nexport interface DurableObjectState<Props = unknown> {\n  waitUntil(promise: Promise<any>): void;\n  readonly exports: Cloudflare.Exports;\n  readonly props: Props;\n  readonly id: DurableObjectId;\n  readonly storage: DurableObjectStorage;\n  container?: Container;\n  blockConcurrencyWhile<T>(callback: () => Promise<T>): Promise<T>;\n  acceptWebSocket(ws: WebSocket, tags?: string[]): void;\n  getWebSockets(tag?: string): WebSocket[];\n  setWebSocketAutoResponse(maybeReqResp?: WebSocketRequestResponsePair): void;\n  getWebSocketAutoResponse(): WebSocketRequestResponsePair | null;\n  getWebSocketAutoResponseTimestamp(ws: WebSocket): Date | null;\n  setHibernatableWebSocketEventTimeout(timeoutMs?: number): void;\n  getHibernatableWebSocketEventTimeout(): number | null;\n  getTags(ws: WebSocket): string[];\n  abort(reason?: string): void;\n}\nexport interface DurableObjectTransaction {\n  get<T = unknown>(\n    key: string,\n    options?: DurableObjectGetOptions,\n  ): Promise<T | undefined>;\n  get<T = unknown>(\n    keys: string[],\n    options?: DurableObjectGetOptions,\n  ): Promise<Map<string, T>>;\n  list<T = unknown>(\n    options?: DurableObjectListOptions,\n  ): Promise<Map<string, T>>;\n  put<T>(\n    key: string,\n    value: T,\n    options?: DurableObjectPutOptions,\n  ): Promise<void>;\n  put<T>(\n    entries: Record<string, T>,\n    options?: DurableObjectPutOptions,\n  ): Promise<void>;\n  delete(key: string, options?: DurableObjectPutOptions): Promise<boolean>;\n  delete(keys: string[], options?: DurableObjectPutOptions): Promise<number>;\n  rollback(): void;\n  getAlarm(options?: DurableObjectGetAlarmOptions): Promise<number | null>;\n  setAlarm(\n    scheduledTime: number | Date,\n    options?: DurableObjectSetAlarmOptions,\n  ): Promise<void>;\n  deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise<void>;\n}\nexport interface DurableObjectStorage {\n  get<T = unknown>(\n    key: string,\n    options?: DurableObjectGetOptions,\n  ): Promise<T | undefined>;\n  get<T = unknown>(\n    keys: string[],\n    options?: DurableObjectGetOptions,\n  ): Promise<Map<string, T>>;\n  list<T = unknown>(\n    options?: DurableObjectListOptions,\n  ): Promise<Map<string, T>>;\n  put<T>(\n    key: string,\n    value: T,\n    options?: DurableObjectPutOptions,\n  ): Promise<void>;\n  put<T>(\n    entries: Record<string, T>,\n    options?: DurableObjectPutOptions,\n  ): Promise<void>;\n  delete(key: string, options?: DurableObjectPutOptions): Promise<boolean>;\n  delete(keys: string[], options?: DurableObjectPutOptions): Promise<number>;\n  deleteAll(options?: DurableObjectPutOptions): Promise<void>;\n  transaction<T>(\n    closure: (txn: DurableObjectTransaction) => Promise<T>,\n  ): Promise<T>;\n  getAlarm(options?: DurableObjectGetAlarmOptions): Promise<number | null>;\n  setAlarm(\n    scheduledTime: number | Date,\n    options?: DurableObjectSetAlarmOptions,\n  ): Promise<void>;\n  deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise<void>;\n  sync(): Promise<void>;\n  sql: SqlStorage;\n  kv: SyncKvStorage;\n  transactionSync<T>(closure: () => T): T;\n  getCurrentBookmark(): Promise<string>;\n  getBookmarkForTime(timestamp: number | Date): Promise<string>;\n  onNextSessionRestoreBookmark(bookmark: string): Promise<string>;\n}\nexport interface DurableObjectListOptions {\n  start?: string;\n  startAfter?: string;\n  end?: string;\n  prefix?: string;\n  reverse?: boolean;\n  limit?: number;\n  allowConcurrency?: boolean;\n  noCache?: boolean;\n}\nexport interface DurableObjectGetOptions {\n  allowConcurrency?: boolean;\n  noCache?: boolean;\n}\nexport interface DurableObjectGetAlarmOptions {\n  allowConcurrency?: boolean;\n}\nexport interface DurableObjectPutOptions {\n  allowConcurrency?: boolean;\n  allowUnconfirmed?: boolean;\n  noCache?: boolean;\n}\nexport interface DurableObjectSetAlarmOptions {\n  allowConcurrency?: boolean;\n  allowUnconfirmed?: boolean;\n}\nexport declare class WebSocketRequestResponsePair {\n  constructor(request: string, response: string);\n  get request(): string;\n  get response(): string;\n}\nexport interface AnalyticsEngineDataset {\n  writeDataPoint(event?: AnalyticsEngineDataPoint): void;\n}\nexport interface AnalyticsEngineDataPoint {\n  indexes?: ((ArrayBuffer | string) | null)[];\n  doubles?: number[];\n  blobs?: ((ArrayBuffer | string) | null)[];\n}\n/**\n * The **`Event`** interface represents an event which takes place on an `EventTarget`.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event)\n */\nexport declare class Event {\n  constructor(type: string, init?: EventInit);\n  /**\n   * The **`type`** read-only property of the Event interface returns a string containing the event's type.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/type)\n   */\n  get type(): string;\n  /**\n   * The **`eventPhase`** read-only property of the being evaluated.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/eventPhase)\n   */\n  get eventPhase(): number;\n  /**\n   * The read-only **`composed`** property of the or not the event will propagate across the shadow DOM boundary into the standard DOM.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composed)\n   */\n  get composed(): boolean;\n  /**\n   * The **`bubbles`** read-only property of the Event interface indicates whether the event bubbles up through the DOM tree or not.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/bubbles)\n   */\n  get bubbles(): boolean;\n  /**\n   * The **`cancelable`** read-only property of the Event interface indicates whether the event can be canceled, and therefore prevented as if the event never happened.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelable)\n   */\n  get cancelable(): boolean;\n  /**\n   * The **`defaultPrevented`** read-only property of the Event interface returns a boolean value indicating whether or not the call to Event.preventDefault() canceled the event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/defaultPrevented)\n   */\n  get defaultPrevented(): boolean;\n  /**\n   * The Event property **`returnValue`** indicates whether the default action for this event has been prevented or not.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/returnValue)\n   */\n  get returnValue(): boolean;\n  /**\n   * The **`currentTarget`** read-only property of the Event interface identifies the element to which the event handler has been attached.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/currentTarget)\n   */\n  get currentTarget(): EventTarget | undefined;\n  /**\n   * The read-only **`target`** property of the dispatched.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/target)\n   */\n  get target(): EventTarget | undefined;\n  /**\n   * The deprecated **`Event.srcElement`** is an alias for the Event.target property.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/srcElement)\n   */\n  get srcElement(): EventTarget | undefined;\n  /**\n   * The **`timeStamp`** read-only property of the Event interface returns the time (in milliseconds) at which the event was created.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/timeStamp)\n   */\n  get timeStamp(): number;\n  /**\n   * The **`isTrusted`** read-only property of the when the event was generated by the user agent (including via user actions and programmatic methods such as HTMLElement.focus()), and `false` when the event was dispatched via The only exception is the `click` event, which initializes the `isTrusted` property to `false` in user agents.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/isTrusted)\n   */\n  get isTrusted(): boolean;\n  /**\n   * The **`cancelBubble`** property of the Event interface is deprecated.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble)\n   */\n  get cancelBubble(): boolean;\n  /**\n   * The **`cancelBubble`** property of the Event interface is deprecated.\n   * @deprecated\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble)\n   */\n  set cancelBubble(value: boolean);\n  /**\n   * The **`stopImmediatePropagation()`** method of the If several listeners are attached to the same element for the same event type, they are called in the order in which they were added.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopImmediatePropagation)\n   */\n  stopImmediatePropagation(): void;\n  /**\n   * The **`preventDefault()`** method of the Event interface tells the user agent that if the event does not get explicitly handled, its default action should not be taken as it normally would be.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/preventDefault)\n   */\n  preventDefault(): void;\n  /**\n   * The **`stopPropagation()`** method of the Event interface prevents further propagation of the current event in the capturing and bubbling phases.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopPropagation)\n   */\n  stopPropagation(): void;\n  /**\n   * The **`composedPath()`** method of the Event interface returns the event's path which is an array of the objects on which listeners will be invoked.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composedPath)\n   */\n  composedPath(): EventTarget[];\n  static readonly NONE: number;\n  static readonly CAPTURING_PHASE: number;\n  static readonly AT_TARGET: number;\n  static readonly BUBBLING_PHASE: number;\n}\nexport interface EventInit {\n  bubbles?: boolean;\n  cancelable?: boolean;\n  composed?: boolean;\n}\nexport type EventListener<EventType extends Event = Event> = (\n  event: EventType,\n) => void;\nexport interface EventListenerObject<EventType extends Event = Event> {\n  handleEvent(event: EventType): void;\n}\nexport type EventListenerOrEventListenerObject<\n  EventType extends Event = Event,\n> = EventListener<EventType> | EventListenerObject<EventType>;\n/**\n * The **`EventTarget`** interface is implemented by objects that can receive events and may have listeners for them.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget)\n */\nexport declare class EventTarget<\n  EventMap extends Record<string, Event> = Record<string, Event>,\n> {\n  constructor();\n  /**\n   * The **`addEventListener()`** method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)\n   */\n  addEventListener<Type extends keyof EventMap>(\n    type: Type,\n    handler: EventListenerOrEventListenerObject<EventMap[Type]>,\n    options?: EventTargetAddEventListenerOptions | boolean,\n  ): void;\n  /**\n   * The **`removeEventListener()`** method of the EventTarget interface removes an event listener previously registered with EventTarget.addEventListener() from the target.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener)\n   */\n  removeEventListener<Type extends keyof EventMap>(\n    type: Type,\n    handler: EventListenerOrEventListenerObject<EventMap[Type]>,\n    options?: EventTargetEventListenerOptions | boolean,\n  ): void;\n  /**\n   * The **`dispatchEvent()`** method of the EventTarget sends an Event to the object, (synchronously) invoking the affected event listeners in the appropriate order.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent)\n   */\n  dispatchEvent(event: EventMap[keyof EventMap]): boolean;\n}\nexport interface EventTargetEventListenerOptions {\n  capture?: boolean;\n}\nexport interface EventTargetAddEventListenerOptions {\n  capture?: boolean;\n  passive?: boolean;\n  once?: boolean;\n  signal?: AbortSignal;\n}\nexport interface EventTargetHandlerObject {\n  handleEvent: (event: Event) => any | undefined;\n}\n/**\n * The **`AbortController`** interface represents a controller object that allows you to abort one or more Web requests as and when desired.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController)\n */\nexport declare class AbortController {\n  constructor();\n  /**\n   * The **`signal`** read-only property of the AbortController interface returns an AbortSignal object instance, which can be used to communicate with/abort an asynchronous operation as desired.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/signal)\n   */\n  get signal(): AbortSignal;\n  /**\n   * The **`abort()`** method of the AbortController interface aborts an asynchronous operation before it has completed.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/abort)\n   */\n  abort(reason?: any): void;\n}\n/**\n * The **`AbortSignal`** interface represents a signal object that allows you to communicate with an asynchronous operation (such as a fetch request) and abort it if required via an AbortController object.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal)\n */\nexport declare abstract class AbortSignal extends EventTarget {\n  /**\n   * The **`AbortSignal.abort()`** static method returns an AbortSignal that is already set as aborted (and which does not trigger an AbortSignal/abort_event event).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_static)\n   */\n  static abort(reason?: any): AbortSignal;\n  /**\n   * The **`AbortSignal.timeout()`** static method returns an AbortSignal that will automatically abort after a specified time.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/timeout_static)\n   */\n  static timeout(delay: number): AbortSignal;\n  /**\n   * The **`AbortSignal.any()`** static method takes an iterable of abort signals and returns an AbortSignal.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/any_static)\n   */\n  static any(signals: AbortSignal[]): AbortSignal;\n  /**\n   * The **`aborted`** read-only property returns a value that indicates whether the asynchronous operations the signal is communicating with are aborted (`true`) or not (`false`).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/aborted)\n   */\n  get aborted(): boolean;\n  /**\n   * The **`reason`** read-only property returns a JavaScript value that indicates the abort reason.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/reason)\n   */\n  get reason(): any;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */\n  get onabort(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */\n  set onabort(value: any | null);\n  /**\n   * The **`throwIfAborted()`** method throws the signal's abort AbortSignal.reason if the signal has been aborted; otherwise it does nothing.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/throwIfAborted)\n   */\n  throwIfAborted(): void;\n}\nexport interface Scheduler {\n  wait(delay: number, maybeOptions?: SchedulerWaitOptions): Promise<void>;\n}\nexport interface SchedulerWaitOptions {\n  signal?: AbortSignal;\n}\n/**\n * The **`ExtendableEvent`** interface extends the lifetime of the `install` and `activate` events dispatched on the global scope as part of the service worker lifecycle.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent)\n */\nexport declare abstract class ExtendableEvent extends Event {\n  /**\n   * The **`ExtendableEvent.waitUntil()`** method tells the event dispatcher that work is ongoing.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent/waitUntil)\n   */\n  waitUntil(promise: Promise<any>): void;\n}\n/**\n * The **`CustomEvent`** interface represents events initialized by an application for any purpose.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent)\n */\nexport declare class CustomEvent<T = any> extends Event {\n  constructor(type: string, init?: CustomEventCustomEventInit);\n  /**\n   * The read-only **`detail`** property of the CustomEvent interface returns any data passed when initializing the event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent/detail)\n   */\n  get detail(): T;\n}\nexport interface CustomEventCustomEventInit {\n  bubbles?: boolean;\n  cancelable?: boolean;\n  composed?: boolean;\n  detail?: any;\n}\n/**\n * The **`Blob`** interface represents a blob, which is a file-like object of immutable, raw data; they can be read as text or binary data, or converted into a ReadableStream so its methods can be used for processing the data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob)\n */\nexport declare class Blob {\n  constructor(\n    type?: ((ArrayBuffer | ArrayBufferView) | string | Blob)[],\n    options?: BlobOptions,\n  );\n  /**\n   * The **`size`** read-only property of the Blob interface returns the size of the Blob or File in bytes.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size)\n   */\n  get size(): number;\n  /**\n   * The **`type`** read-only property of the Blob interface returns the MIME type of the file.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type)\n   */\n  get type(): string;\n  /**\n   * The **`slice()`** method of the Blob interface creates and returns a new `Blob` object which contains data from a subset of the blob on which it's called.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice)\n   */\n  slice(start?: number, end?: number, type?: string): Blob;\n  /**\n   * The **`arrayBuffer()`** method of the Blob interface returns a Promise that resolves with the contents of the blob as binary data contained in an ArrayBuffer.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/arrayBuffer)\n   */\n  arrayBuffer(): Promise<ArrayBuffer>;\n  /**\n   * The **`bytes()`** method of the Blob interface returns a Promise that resolves with a Uint8Array containing the contents of the blob as an array of bytes.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/bytes)\n   */\n  bytes(): Promise<Uint8Array>;\n  /**\n   * The **`text()`** method of the string containing the contents of the blob, interpreted as UTF-8.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text)\n   */\n  text(): Promise<string>;\n  /**\n   * The **`stream()`** method of the Blob interface returns a ReadableStream which upon reading returns the data contained within the `Blob`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/stream)\n   */\n  stream(): ReadableStream;\n}\nexport interface BlobOptions {\n  type?: string;\n}\n/**\n * The **`File`** interface provides information about files and allows JavaScript in a web page to access their content.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File)\n */\nexport declare class File extends Blob {\n  constructor(\n    bits: ((ArrayBuffer | ArrayBufferView) | string | Blob)[] | undefined,\n    name: string,\n    options?: FileOptions,\n  );\n  /**\n   * The **`name`** read-only property of the File interface returns the name of the file represented by a File object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name)\n   */\n  get name(): string;\n  /**\n   * The **`lastModified`** read-only property of the File interface provides the last modified date of the file as the number of milliseconds since the Unix epoch (January 1, 1970 at midnight).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified)\n   */\n  get lastModified(): number;\n}\nexport interface FileOptions {\n  type?: string;\n  lastModified?: number;\n}\n/**\n * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)\n */\nexport declare abstract class CacheStorage {\n  /**\n   * The **`open()`** method of the the Cache object matching the `cacheName`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CacheStorage/open)\n   */\n  open(cacheName: string): Promise<Cache>;\n  readonly default: Cache;\n}\n/**\n * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)\n */\nexport declare abstract class Cache {\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#delete) */\n  delete(\n    request: RequestInfo | URL,\n    options?: CacheQueryOptions,\n  ): Promise<boolean>;\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#match) */\n  match(\n    request: RequestInfo | URL,\n    options?: CacheQueryOptions,\n  ): Promise<Response | undefined>;\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#put) */\n  put(request: RequestInfo | URL, response: Response): Promise<void>;\n}\nexport interface CacheQueryOptions {\n  ignoreMethod?: boolean;\n}\n/**\n * The Web Crypto API provides a set of low-level functions for common cryptographic tasks.\n * The Workers runtime implements the full surface of this API, but with some differences in\n * the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms)\n * compared to those implemented in most browsers.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/)\n */\nexport declare abstract class Crypto {\n  /**\n   * The **`Crypto.subtle`** read-only property returns a cryptographic operations.\n   * Available only in secure contexts.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/subtle)\n   */\n  get subtle(): SubtleCrypto;\n  /**\n   * The **`Crypto.getRandomValues()`** method lets you get cryptographically strong random values.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/getRandomValues)\n   */\n  getRandomValues<\n    T extends\n      | Int8Array\n      | Uint8Array\n      | Int16Array\n      | Uint16Array\n      | Int32Array\n      | Uint32Array\n      | BigInt64Array\n      | BigUint64Array,\n  >(buffer: T): T;\n  /**\n   * The **`randomUUID()`** method of the Crypto interface is used to generate a v4 UUID using a cryptographically secure random number generator.\n   * Available only in secure contexts.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/randomUUID)\n   */\n  randomUUID(): string;\n  DigestStream: typeof DigestStream;\n}\n/**\n * The **`SubtleCrypto`** interface of the Web Crypto API provides a number of low-level cryptographic functions.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto)\n */\nexport declare abstract class SubtleCrypto {\n  /**\n   * The **`encrypt()`** method of the SubtleCrypto interface encrypts data.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/encrypt)\n   */\n  encrypt(\n    algorithm: string | SubtleCryptoEncryptAlgorithm,\n    key: CryptoKey,\n    plainText: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`decrypt()`** method of the SubtleCrypto interface decrypts some encrypted data.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/decrypt)\n   */\n  decrypt(\n    algorithm: string | SubtleCryptoEncryptAlgorithm,\n    key: CryptoKey,\n    cipherText: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`sign()`** method of the SubtleCrypto interface generates a digital signature.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/sign)\n   */\n  sign(\n    algorithm: string | SubtleCryptoSignAlgorithm,\n    key: CryptoKey,\n    data: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`verify()`** method of the SubtleCrypto interface verifies a digital signature.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/verify)\n   */\n  verify(\n    algorithm: string | SubtleCryptoSignAlgorithm,\n    key: CryptoKey,\n    signature: ArrayBuffer | ArrayBufferView,\n    data: ArrayBuffer | ArrayBufferView,\n  ): Promise<boolean>;\n  /**\n   * The **`digest()`** method of the SubtleCrypto interface generates a _digest_ of the given data, using the specified hash function.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/digest)\n   */\n  digest(\n    algorithm: string | SubtleCryptoHashAlgorithm,\n    data: ArrayBuffer | ArrayBufferView,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`generateKey()`** method of the SubtleCrypto interface is used to generate a new key (for symmetric algorithms) or key pair (for public-key algorithms).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/generateKey)\n   */\n  generateKey(\n    algorithm: string | SubtleCryptoGenerateKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey | CryptoKeyPair>;\n  /**\n   * The **`deriveKey()`** method of the SubtleCrypto interface can be used to derive a secret key from a master key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveKey)\n   */\n  deriveKey(\n    algorithm: string | SubtleCryptoDeriveKeyAlgorithm,\n    baseKey: CryptoKey,\n    derivedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey>;\n  /**\n   * The **`deriveBits()`** method of the key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveBits)\n   */\n  deriveBits(\n    algorithm: string | SubtleCryptoDeriveKeyAlgorithm,\n    baseKey: CryptoKey,\n    length?: number | null,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`importKey()`** method of the SubtleCrypto interface imports a key: that is, it takes as input a key in an external, portable format and gives you a CryptoKey object that you can use in the Web Crypto API.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/importKey)\n   */\n  importKey(\n    format: string,\n    keyData: (ArrayBuffer | ArrayBufferView) | JsonWebKey,\n    algorithm: string | SubtleCryptoImportKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey>;\n  /**\n   * The **`exportKey()`** method of the SubtleCrypto interface exports a key: that is, it takes as input a CryptoKey object and gives you the key in an external, portable format.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/exportKey)\n   */\n  exportKey(format: string, key: CryptoKey): Promise<ArrayBuffer | JsonWebKey>;\n  /**\n   * The **`wrapKey()`** method of the SubtleCrypto interface 'wraps' a key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/wrapKey)\n   */\n  wrapKey(\n    format: string,\n    key: CryptoKey,\n    wrappingKey: CryptoKey,\n    wrapAlgorithm: string | SubtleCryptoEncryptAlgorithm,\n  ): Promise<ArrayBuffer>;\n  /**\n   * The **`unwrapKey()`** method of the SubtleCrypto interface 'unwraps' a key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/unwrapKey)\n   */\n  unwrapKey(\n    format: string,\n    wrappedKey: ArrayBuffer | ArrayBufferView,\n    unwrappingKey: CryptoKey,\n    unwrapAlgorithm: string | SubtleCryptoEncryptAlgorithm,\n    unwrappedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm,\n    extractable: boolean,\n    keyUsages: string[],\n  ): Promise<CryptoKey>;\n  timingSafeEqual(\n    a: ArrayBuffer | ArrayBufferView,\n    b: ArrayBuffer | ArrayBufferView,\n  ): boolean;\n}\n/**\n * The **`CryptoKey`** interface of the Web Crypto API represents a cryptographic key obtained from one of the SubtleCrypto methods SubtleCrypto.generateKey, SubtleCrypto.deriveKey, SubtleCrypto.importKey, or SubtleCrypto.unwrapKey.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey)\n */\nexport declare abstract class CryptoKey {\n  /**\n   * The read-only **`type`** property of the CryptoKey interface indicates which kind of key is represented by the object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/type)\n   */\n  readonly type: string;\n  /**\n   * The read-only **`extractable`** property of the CryptoKey interface indicates whether or not the key may be extracted using `SubtleCrypto.exportKey()` or `SubtleCrypto.wrapKey()`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/extractable)\n   */\n  readonly extractable: boolean;\n  /**\n   * The read-only **`algorithm`** property of the CryptoKey interface returns an object describing the algorithm for which this key can be used, and any associated extra parameters.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/algorithm)\n   */\n  readonly algorithm:\n    | CryptoKeyKeyAlgorithm\n    | CryptoKeyAesKeyAlgorithm\n    | CryptoKeyHmacKeyAlgorithm\n    | CryptoKeyRsaKeyAlgorithm\n    | CryptoKeyEllipticKeyAlgorithm\n    | CryptoKeyArbitraryKeyAlgorithm;\n  /**\n   * The read-only **`usages`** property of the CryptoKey interface indicates what can be done with the key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/usages)\n   */\n  readonly usages: string[];\n}\nexport interface CryptoKeyPair {\n  publicKey: CryptoKey;\n  privateKey: CryptoKey;\n}\nexport interface JsonWebKey {\n  kty: string;\n  use?: string;\n  key_ops?: string[];\n  alg?: string;\n  ext?: boolean;\n  crv?: string;\n  x?: string;\n  y?: string;\n  d?: string;\n  n?: string;\n  e?: string;\n  p?: string;\n  q?: string;\n  dp?: string;\n  dq?: string;\n  qi?: string;\n  oth?: RsaOtherPrimesInfo[];\n  k?: string;\n}\nexport interface RsaOtherPrimesInfo {\n  r?: string;\n  d?: string;\n  t?: string;\n}\nexport interface SubtleCryptoDeriveKeyAlgorithm {\n  name: string;\n  salt?: ArrayBuffer | ArrayBufferView;\n  iterations?: number;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  $public?: CryptoKey;\n  info?: ArrayBuffer | ArrayBufferView;\n}\nexport interface SubtleCryptoEncryptAlgorithm {\n  name: string;\n  iv?: ArrayBuffer | ArrayBufferView;\n  additionalData?: ArrayBuffer | ArrayBufferView;\n  tagLength?: number;\n  counter?: ArrayBuffer | ArrayBufferView;\n  length?: number;\n  label?: ArrayBuffer | ArrayBufferView;\n}\nexport interface SubtleCryptoGenerateKeyAlgorithm {\n  name: string;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  modulusLength?: number;\n  publicExponent?: ArrayBuffer | ArrayBufferView;\n  length?: number;\n  namedCurve?: string;\n}\nexport interface SubtleCryptoHashAlgorithm {\n  name: string;\n}\nexport interface SubtleCryptoImportKeyAlgorithm {\n  name: string;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  length?: number;\n  namedCurve?: string;\n  compressed?: boolean;\n}\nexport interface SubtleCryptoSignAlgorithm {\n  name: string;\n  hash?: string | SubtleCryptoHashAlgorithm;\n  dataLength?: number;\n  saltLength?: number;\n}\nexport interface CryptoKeyKeyAlgorithm {\n  name: string;\n}\nexport interface CryptoKeyAesKeyAlgorithm {\n  name: string;\n  length: number;\n}\nexport interface CryptoKeyHmacKeyAlgorithm {\n  name: string;\n  hash: CryptoKeyKeyAlgorithm;\n  length: number;\n}\nexport interface CryptoKeyRsaKeyAlgorithm {\n  name: string;\n  modulusLength: number;\n  publicExponent: ArrayBuffer | ArrayBufferView;\n  hash?: CryptoKeyKeyAlgorithm;\n}\nexport interface CryptoKeyEllipticKeyAlgorithm {\n  name: string;\n  namedCurve: string;\n}\nexport interface CryptoKeyArbitraryKeyAlgorithm {\n  name: string;\n  hash?: CryptoKeyKeyAlgorithm;\n  namedCurve?: string;\n  length?: number;\n}\nexport declare class DigestStream extends WritableStream<\n  ArrayBuffer | ArrayBufferView\n> {\n  constructor(algorithm: string | SubtleCryptoHashAlgorithm);\n  readonly digest: Promise<ArrayBuffer>;\n  get bytesWritten(): number | bigint;\n}\n/**\n * The **`TextDecoder`** interface represents a decoder for a specific text encoding, such as `UTF-8`, `ISO-8859-2`, `KOI8-R`, `GBK`, etc.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder)\n */\nexport declare class TextDecoder {\n  constructor(label?: string, options?: TextDecoderConstructorOptions);\n  /**\n   * The **`TextDecoder.decode()`** method returns a string containing text decoded from the buffer passed as a parameter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder/decode)\n   */\n  decode(\n    input?: ArrayBuffer | ArrayBufferView,\n    options?: TextDecoderDecodeOptions,\n  ): string;\n  get encoding(): string;\n  get fatal(): boolean;\n  get ignoreBOM(): boolean;\n}\n/**\n * The **`TextEncoder`** interface takes a stream of code points as input and emits a stream of UTF-8 bytes.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder)\n */\nexport declare class TextEncoder {\n  constructor();\n  /**\n   * The **`TextEncoder.encode()`** method takes a string as input, and returns a Global_Objects/Uint8Array containing the text given in parameters encoded with the specific method for that TextEncoder object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encode)\n   */\n  encode(input?: string): Uint8Array;\n  /**\n   * The **`TextEncoder.encodeInto()`** method takes a string to encode and a destination Uint8Array to put resulting UTF-8 encoded text into, and returns a dictionary object indicating the progress of the encoding.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encodeInto)\n   */\n  encodeInto(input: string, buffer: Uint8Array): TextEncoderEncodeIntoResult;\n  get encoding(): string;\n}\nexport interface TextDecoderConstructorOptions {\n  fatal: boolean;\n  ignoreBOM: boolean;\n}\nexport interface TextDecoderDecodeOptions {\n  stream: boolean;\n}\nexport interface TextEncoderEncodeIntoResult {\n  read: number;\n  written: number;\n}\n/**\n * The **`ErrorEvent`** interface represents events providing information related to errors in scripts or in files.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent)\n */\nexport declare class ErrorEvent extends Event {\n  constructor(type: string, init?: ErrorEventErrorEventInit);\n  /**\n   * The **`filename`** read-only property of the ErrorEvent interface returns a string containing the name of the script file in which the error occurred.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/filename)\n   */\n  get filename(): string;\n  /**\n   * The **`message`** read-only property of the ErrorEvent interface returns a string containing a human-readable error message describing the problem.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/message)\n   */\n  get message(): string;\n  /**\n   * The **`lineno`** read-only property of the ErrorEvent interface returns an integer containing the line number of the script file on which the error occurred.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/lineno)\n   */\n  get lineno(): number;\n  /**\n   * The **`colno`** read-only property of the ErrorEvent interface returns an integer containing the column number of the script file on which the error occurred.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/colno)\n   */\n  get colno(): number;\n  /**\n   * The **`error`** read-only property of the ErrorEvent interface returns a JavaScript value, such as an Error or DOMException, representing the error associated with this event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/error)\n   */\n  get error(): any;\n}\nexport interface ErrorEventErrorEventInit {\n  message?: string;\n  filename?: string;\n  lineno?: number;\n  colno?: number;\n  error?: any;\n}\n/**\n * The **`MessageEvent`** interface represents a message received by a target object.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent)\n */\nexport declare class MessageEvent extends Event {\n  constructor(type: string, initializer: MessageEventInit);\n  /**\n   * The **`data`** read-only property of the The data sent by the message emitter; this can be any data type, depending on what originated this event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/data)\n   */\n  readonly data: any;\n  /**\n   * The **`origin`** read-only property of the origin of the message emitter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/origin)\n   */\n  readonly origin: string | null;\n  /**\n   * The **`lastEventId`** read-only property of the unique ID for the event.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/lastEventId)\n   */\n  readonly lastEventId: string;\n  /**\n   * The **`source`** read-only property of the a WindowProxy, MessagePort, or a `MessageEventSource` (which can be a WindowProxy, message emitter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/source)\n   */\n  readonly source: MessagePort | null;\n  /**\n   * The **`ports`** read-only property of the containing all MessagePort objects sent with the message, in order.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/ports)\n   */\n  readonly ports: MessagePort[];\n}\nexport interface MessageEventInit {\n  data: ArrayBuffer | string;\n}\n/**\n * The **`PromiseRejectionEvent`** interface represents events which are sent to the global script context when JavaScript Promises are rejected.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent)\n */\nexport declare abstract class PromiseRejectionEvent extends Event {\n  /**\n   * The PromiseRejectionEvent interface's **`promise`** read-only property indicates the JavaScript rejected.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/promise)\n   */\n  readonly promise: Promise<any>;\n  /**\n   * The PromiseRejectionEvent **`reason`** read-only property is any JavaScript value or Object which provides the reason passed into Promise.reject().\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/reason)\n   */\n  readonly reason: any;\n}\n/**\n * The **`FormData`** interface provides a way to construct a set of key/value pairs representing form fields and their values, which can be sent using the Window/fetch, XMLHttpRequest.send() or navigator.sendBeacon() methods.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData)\n */\nexport declare class FormData {\n  constructor();\n  /**\n   * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append)\n   */\n  append(name: string, value: string | Blob): void;\n  /**\n   * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append)\n   */\n  append(name: string, value: string): void;\n  /**\n   * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append)\n   */\n  append(name: string, value: Blob, filename?: string): void;\n  /**\n   * The **`delete()`** method of the FormData interface deletes a key and its value(s) from a `FormData` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/delete)\n   */\n  delete(name: string): void;\n  /**\n   * The **`get()`** method of the FormData interface returns the first value associated with a given key from within a `FormData` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/get)\n   */\n  get(name: string): (File | string) | null;\n  /**\n   * The **`getAll()`** method of the FormData interface returns all the values associated with a given key from within a `FormData` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/getAll)\n   */\n  getAll(name: string): (File | string)[];\n  /**\n   * The **`has()`** method of the FormData interface returns whether a `FormData` object contains a certain key.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/has)\n   */\n  has(name: string): boolean;\n  /**\n   * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set)\n   */\n  set(name: string, value: string | Blob): void;\n  /**\n   * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set)\n   */\n  set(name: string, value: string): void;\n  /**\n   * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set)\n   */\n  set(name: string, value: Blob, filename?: string): void;\n  /* Returns an array of key, value pairs for every entry in the list. */\n  entries(): IterableIterator<[key: string, value: File | string]>;\n  /* Returns a list of keys in the list. */\n  keys(): IterableIterator<string>;\n  /* Returns a list of values in the list. */\n  values(): IterableIterator<File | string>;\n  forEach<This = unknown>(\n    callback: (\n      this: This,\n      value: File | string,\n      key: string,\n      parent: FormData,\n    ) => void,\n    thisArg?: This,\n  ): void;\n  [Symbol.iterator](): IterableIterator<[key: string, value: File | string]>;\n}\nexport interface ContentOptions {\n  html?: boolean;\n}\nexport declare class HTMLRewriter {\n  constructor();\n  on(\n    selector: string,\n    handlers: HTMLRewriterElementContentHandlers,\n  ): HTMLRewriter;\n  onDocument(handlers: HTMLRewriterDocumentContentHandlers): HTMLRewriter;\n  transform(response: Response): Response;\n}\nexport interface HTMLRewriterElementContentHandlers {\n  element?(element: Element): void | Promise<void>;\n  comments?(comment: Comment): void | Promise<void>;\n  text?(element: Text): void | Promise<void>;\n}\nexport interface HTMLRewriterDocumentContentHandlers {\n  doctype?(doctype: Doctype): void | Promise<void>;\n  comments?(comment: Comment): void | Promise<void>;\n  text?(text: Text): void | Promise<void>;\n  end?(end: DocumentEnd): void | Promise<void>;\n}\nexport interface Doctype {\n  readonly name: string | null;\n  readonly publicId: string | null;\n  readonly systemId: string | null;\n}\nexport interface Element {\n  tagName: string;\n  readonly attributes: IterableIterator<string[]>;\n  readonly removed: boolean;\n  readonly namespaceURI: string;\n  getAttribute(name: string): string | null;\n  hasAttribute(name: string): boolean;\n  setAttribute(name: string, value: string): Element;\n  removeAttribute(name: string): Element;\n  before(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  after(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  prepend(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  append(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  replace(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  remove(): Element;\n  removeAndKeepContent(): Element;\n  setInnerContent(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Element;\n  onEndTag(handler: (tag: EndTag) => void | Promise<void>): void;\n}\nexport interface EndTag {\n  name: string;\n  before(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): EndTag;\n  after(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): EndTag;\n  remove(): EndTag;\n}\nexport interface Comment {\n  text: string;\n  readonly removed: boolean;\n  before(content: string, options?: ContentOptions): Comment;\n  after(content: string, options?: ContentOptions): Comment;\n  replace(content: string, options?: ContentOptions): Comment;\n  remove(): Comment;\n}\nexport interface Text {\n  readonly text: string;\n  readonly lastInTextNode: boolean;\n  readonly removed: boolean;\n  before(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Text;\n  after(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Text;\n  replace(\n    content: string | ReadableStream | Response,\n    options?: ContentOptions,\n  ): Text;\n  remove(): Text;\n}\nexport interface DocumentEnd {\n  append(content: string, options?: ContentOptions): DocumentEnd;\n}\n/**\n * This is the event type for `fetch` events dispatched on the ServiceWorkerGlobalScope.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent)\n */\nexport declare abstract class FetchEvent extends ExtendableEvent {\n  /**\n   * The **`request`** read-only property of the the event handler.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/request)\n   */\n  readonly request: Request;\n  /**\n   * The **`respondWith()`** method of allows you to provide a promise for a Response yourself.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/respondWith)\n   */\n  respondWith(promise: Response | Promise<Response>): void;\n  passThroughOnException(): void;\n}\nexport type HeadersInit =\n  | Headers\n  | Iterable<Iterable<string>>\n  | Record<string, string>;\n/**\n * The **`Headers`** interface of the Fetch API allows you to perform various actions on HTTP request and response headers.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers)\n */\nexport declare class Headers {\n  constructor(init?: HeadersInit);\n  /**\n   * The **`get()`** method of the Headers interface returns a byte string of all the values of a header within a `Headers` object with a given name.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/get)\n   */\n  get(name: string): string | null;\n  getAll(name: string): string[];\n  /**\n   * The **`getSetCookie()`** method of the Headers interface returns an array containing the values of all Set-Cookie headers associated with a response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/getSetCookie)\n   */\n  getSetCookie(): string[];\n  /**\n   * The **`has()`** method of the Headers interface returns a boolean stating whether a `Headers` object contains a certain header.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/has)\n   */\n  has(name: string): boolean;\n  /**\n   * The **`set()`** method of the Headers interface sets a new value for an existing header inside a `Headers` object, or adds the header if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/set)\n   */\n  set(name: string, value: string): void;\n  /**\n   * The **`append()`** method of the Headers interface appends a new value onto an existing header inside a `Headers` object, or adds the header if it does not already exist.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/append)\n   */\n  append(name: string, value: string): void;\n  /**\n   * The **`delete()`** method of the Headers interface deletes a header from the current `Headers` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/delete)\n   */\n  delete(name: string): void;\n  forEach<This = unknown>(\n    callback: (this: This, value: string, key: string, parent: Headers) => void,\n    thisArg?: This,\n  ): void;\n  /* Returns an iterator allowing to go through all key/value pairs contained in this object. */\n  entries(): IterableIterator<[key: string, value: string]>;\n  /* Returns an iterator allowing to go through all keys of the key/value pairs contained in this object. */\n  keys(): IterableIterator<string>;\n  /* Returns an iterator allowing to go through all values of the key/value pairs contained in this object. */\n  values(): IterableIterator<string>;\n  [Symbol.iterator](): IterableIterator<[key: string, value: string]>;\n}\nexport type BodyInit =\n  | ReadableStream<Uint8Array>\n  | string\n  | ArrayBuffer\n  | ArrayBufferView\n  | Blob\n  | URLSearchParams\n  | FormData\n  | Iterable<ArrayBuffer | ArrayBufferView>\n  | AsyncIterable<ArrayBuffer | ArrayBufferView>;\nexport declare abstract class Body {\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body) */\n  get body(): ReadableStream | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) */\n  get bodyUsed(): boolean;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/arrayBuffer) */\n  arrayBuffer(): Promise<ArrayBuffer>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bytes) */\n  bytes(): Promise<Uint8Array>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/text) */\n  text(): Promise<string>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */\n  json<T>(): Promise<T>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/formData) */\n  formData(): Promise<FormData>;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/blob) */\n  blob(): Promise<Blob>;\n}\n/**\n * The **`Response`** interface of the Fetch API represents the response to a request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response)\n */\nexport declare var Response: {\n  prototype: Response;\n  new (body?: BodyInit | null, init?: ResponseInit): Response;\n  error(): Response;\n  redirect(url: string, status?: number): Response;\n  json(any: any, maybeInit?: ResponseInit | Response): Response;\n};\n/**\n * The **`Response`** interface of the Fetch API represents the response to a request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response)\n */\nexport interface Response extends Body {\n  /**\n   * The **`clone()`** method of the Response interface creates a clone of a response object, identical in every way, but stored in a different variable.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/clone)\n   */\n  clone(): Response;\n  /**\n   * The **`status`** read-only property of the Response interface contains the HTTP status codes of the response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/status)\n   */\n  status: number;\n  /**\n   * The **`statusText`** read-only property of the Response interface contains the status message corresponding to the HTTP status code in Response.status.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/statusText)\n   */\n  statusText: string;\n  /**\n   * The **`headers`** read-only property of the with the response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/headers)\n   */\n  headers: Headers;\n  /**\n   * The **`ok`** read-only property of the Response interface contains a Boolean stating whether the response was successful (status in the range 200-299) or not.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/ok)\n   */\n  ok: boolean;\n  /**\n   * The **`redirected`** read-only property of the Response interface indicates whether or not the response is the result of a request you made which was redirected.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/redirected)\n   */\n  redirected: boolean;\n  /**\n   * The **`url`** read-only property of the Response interface contains the URL of the response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/url)\n   */\n  url: string;\n  webSocket: WebSocket | null;\n  cf: any | undefined;\n  /**\n   * The **`type`** read-only property of the Response interface contains the type of the response.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/type)\n   */\n  type: \"default\" | \"error\";\n}\nexport interface ResponseInit {\n  status?: number;\n  statusText?: string;\n  headers?: HeadersInit;\n  cf?: any;\n  webSocket?: WebSocket | null;\n  encodeBody?: \"automatic\" | \"manual\";\n}\nexport type RequestInfo<\n  CfHostMetadata = unknown,\n  Cf = CfProperties<CfHostMetadata>,\n> = Request<CfHostMetadata, Cf> | string;\n/**\n * The **`Request`** interface of the Fetch API represents a resource request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request)\n */\nexport declare var Request: {\n  prototype: Request;\n  new <CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>>(\n    input: RequestInfo<CfProperties> | URL,\n    init?: RequestInit<Cf>,\n  ): Request<CfHostMetadata, Cf>;\n};\n/**\n * The **`Request`** interface of the Fetch API represents a resource request.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request)\n */\nexport interface Request<\n  CfHostMetadata = unknown,\n  Cf = CfProperties<CfHostMetadata>,\n> extends Body {\n  /**\n   * The **`clone()`** method of the Request interface creates a copy of the current `Request` object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/clone)\n   */\n  clone(): Request<CfHostMetadata, Cf>;\n  /**\n   * The **`method`** read-only property of the `POST`, etc.) A String indicating the method of the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/method)\n   */\n  method: string;\n  /**\n   * The **`url`** read-only property of the Request interface contains the URL of the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/url)\n   */\n  url: string;\n  /**\n   * The **`headers`** read-only property of the with the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/headers)\n   */\n  headers: Headers;\n  /**\n   * The **`redirect`** read-only property of the Request interface contains the mode for how redirects are handled.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/redirect)\n   */\n  redirect: string;\n  fetcher: Fetcher | null;\n  /**\n   * The read-only **`signal`** property of the Request interface returns the AbortSignal associated with the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/signal)\n   */\n  signal: AbortSignal;\n  cf?: Cf;\n  /**\n   * The **`integrity`** read-only property of the Request interface contains the subresource integrity value of the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/integrity)\n   */\n  integrity: string;\n  /**\n   * The **`keepalive`** read-only property of the Request interface contains the request's `keepalive` setting (`true` or `false`), which indicates whether the browser will keep the associated request alive if the page that initiated it is unloaded before the request is complete.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/keepalive)\n   */\n  keepalive: boolean;\n  /**\n   * The **`cache`** read-only property of the Request interface contains the cache mode of the request.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/cache)\n   */\n  cache?: \"no-store\" | \"no-cache\";\n}\nexport interface RequestInit<Cf = CfProperties> {\n  /* A string to set request's method. */\n  method?: string;\n  /* A Headers object, an object literal, or an array of two-item arrays to set request's headers. */\n  headers?: HeadersInit;\n  /* A BodyInit object or null to set request's body. */\n  body?: BodyInit | null;\n  /* A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */\n  redirect?: string;\n  fetcher?: Fetcher | null;\n  cf?: Cf;\n  /* A string indicating how the request will interact with the browser's cache to set request's cache. */\n  cache?: \"no-store\" | \"no-cache\";\n  /* A cryptographic hash of the resource to be fetched by request. Sets request's integrity. */\n  integrity?: string;\n  /* An AbortSignal to set request's signal. */\n  signal?: AbortSignal | null;\n  encodeResponseBody?: \"automatic\" | \"manual\";\n}\nexport type Service<\n  T extends\n    | (new (...args: any[]) => Rpc.WorkerEntrypointBranded)\n    | Rpc.WorkerEntrypointBranded\n    | ExportedHandler<any, any, any>\n    | undefined = undefined,\n> = T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded\n  ? Fetcher<InstanceType<T>>\n  : T extends Rpc.WorkerEntrypointBranded\n    ? Fetcher<T>\n    : T extends Exclude<Rpc.EntrypointBranded, Rpc.WorkerEntrypointBranded>\n      ? never\n      : Fetcher<undefined>;\nexport type Fetcher<\n  T extends Rpc.EntrypointBranded | undefined = undefined,\n  Reserved extends string = never,\n> = (T extends Rpc.EntrypointBranded\n  ? Rpc.Provider<T, Reserved | \"fetch\" | \"connect\">\n  : unknown) & {\n  fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;\n  connect(address: SocketAddress | string, options?: SocketOptions): Socket;\n};\nexport interface KVNamespaceListKey<Metadata, Key extends string = string> {\n  name: Key;\n  expiration?: number;\n  metadata?: Metadata;\n}\nexport type KVNamespaceListResult<Metadata, Key extends string = string> =\n  | {\n      list_complete: false;\n      keys: KVNamespaceListKey<Metadata, Key>[];\n      cursor: string;\n      cacheStatus: string | null;\n    }\n  | {\n      list_complete: true;\n      keys: KVNamespaceListKey<Metadata, Key>[];\n      cacheStatus: string | null;\n    };\nexport interface KVNamespace<Key extends string = string> {\n  get(\n    key: Key,\n    options?: Partial<KVNamespaceGetOptions<undefined>>,\n  ): Promise<string | null>;\n  get(key: Key, type: \"text\"): Promise<string | null>;\n  get<ExpectedValue = unknown>(\n    key: Key,\n    type: \"json\",\n  ): Promise<ExpectedValue | null>;\n  get(key: Key, type: \"arrayBuffer\"): Promise<ArrayBuffer | null>;\n  get(key: Key, type: \"stream\"): Promise<ReadableStream | null>;\n  get(\n    key: Key,\n    options?: KVNamespaceGetOptions<\"text\">,\n  ): Promise<string | null>;\n  get<ExpectedValue = unknown>(\n    key: Key,\n    options?: KVNamespaceGetOptions<\"json\">,\n  ): Promise<ExpectedValue | null>;\n  get(\n    key: Key,\n    options?: KVNamespaceGetOptions<\"arrayBuffer\">,\n  ): Promise<ArrayBuffer | null>;\n  get(\n    key: Key,\n    options?: KVNamespaceGetOptions<\"stream\">,\n  ): Promise<ReadableStream | null>;\n  get(key: Array<Key>, type: \"text\"): Promise<Map<string, string | null>>;\n  get<ExpectedValue = unknown>(\n    key: Array<Key>,\n    type: \"json\",\n  ): Promise<Map<string, ExpectedValue | null>>;\n  get(\n    key: Array<Key>,\n    options?: Partial<KVNamespaceGetOptions<undefined>>,\n  ): Promise<Map<string, string | null>>;\n  get(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<\"text\">,\n  ): Promise<Map<string, string | null>>;\n  get<ExpectedValue = unknown>(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<\"json\">,\n  ): Promise<Map<string, ExpectedValue | null>>;\n  list<Metadata = unknown>(\n    options?: KVNamespaceListOptions,\n  ): Promise<KVNamespaceListResult<Metadata, Key>>;\n  put(\n    key: Key,\n    value: string | ArrayBuffer | ArrayBufferView | ReadableStream,\n    options?: KVNamespacePutOptions,\n  ): Promise<void>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options?: Partial<KVNamespaceGetOptions<undefined>>,\n  ): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    type: \"text\",\n  ): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Key,\n    type: \"json\",\n  ): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    type: \"arrayBuffer\",\n  ): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    type: \"stream\",\n  ): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<\"text\">,\n  ): Promise<KVNamespaceGetWithMetadataResult<string, Metadata>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<\"json\">,\n  ): Promise<KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<\"arrayBuffer\">,\n  ): Promise<KVNamespaceGetWithMetadataResult<ArrayBuffer, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Key,\n    options: KVNamespaceGetOptions<\"stream\">,\n  ): Promise<KVNamespaceGetWithMetadataResult<ReadableStream, Metadata>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Array<Key>,\n    type: \"text\",\n  ): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Array<Key>,\n    type: \"json\",\n  ): Promise<\n    Map<string, KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>\n  >;\n  getWithMetadata<Metadata = unknown>(\n    key: Array<Key>,\n    options?: Partial<KVNamespaceGetOptions<undefined>>,\n  ): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>>;\n  getWithMetadata<Metadata = unknown>(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<\"text\">,\n  ): Promise<Map<string, KVNamespaceGetWithMetadataResult<string, Metadata>>>;\n  getWithMetadata<ExpectedValue = unknown, Metadata = unknown>(\n    key: Array<Key>,\n    options?: KVNamespaceGetOptions<\"json\">,\n  ): Promise<\n    Map<string, KVNamespaceGetWithMetadataResult<ExpectedValue, Metadata>>\n  >;\n  delete(key: Key): Promise<void>;\n}\nexport interface KVNamespaceListOptions {\n  limit?: number;\n  prefix?: string | null;\n  cursor?: string | null;\n}\nexport interface KVNamespaceGetOptions<Type> {\n  type: Type;\n  cacheTtl?: number;\n}\nexport interface KVNamespacePutOptions {\n  expiration?: number;\n  expirationTtl?: number;\n  metadata?: any | null;\n}\nexport interface KVNamespaceGetWithMetadataResult<Value, Metadata> {\n  value: Value | null;\n  metadata: Metadata | null;\n  cacheStatus: string | null;\n}\nexport type QueueContentType = \"text\" | \"bytes\" | \"json\" | \"v8\";\nexport interface Queue<Body = unknown> {\n  send(message: Body, options?: QueueSendOptions): Promise<void>;\n  sendBatch(\n    messages: Iterable<MessageSendRequest<Body>>,\n    options?: QueueSendBatchOptions,\n  ): Promise<void>;\n}\nexport interface QueueSendOptions {\n  contentType?: QueueContentType;\n  delaySeconds?: number;\n}\nexport interface QueueSendBatchOptions {\n  delaySeconds?: number;\n}\nexport interface MessageSendRequest<Body = unknown> {\n  body: Body;\n  contentType?: QueueContentType;\n  delaySeconds?: number;\n}\nexport interface QueueRetryOptions {\n  delaySeconds?: number;\n}\nexport interface Message<Body = unknown> {\n  readonly id: string;\n  readonly timestamp: Date;\n  readonly body: Body;\n  readonly attempts: number;\n  retry(options?: QueueRetryOptions): void;\n  ack(): void;\n}\nexport interface QueueEvent<Body = unknown> extends ExtendableEvent {\n  readonly messages: readonly Message<Body>[];\n  readonly queue: string;\n  retryAll(options?: QueueRetryOptions): void;\n  ackAll(): void;\n}\nexport interface MessageBatch<Body = unknown> {\n  readonly messages: readonly Message<Body>[];\n  readonly queue: string;\n  retryAll(options?: QueueRetryOptions): void;\n  ackAll(): void;\n}\nexport interface R2Error extends Error {\n  readonly name: string;\n  readonly code: number;\n  readonly message: string;\n  readonly action: string;\n  readonly stack: any;\n}\nexport interface R2ListOptions {\n  limit?: number;\n  prefix?: string;\n  cursor?: string;\n  delimiter?: string;\n  startAfter?: string;\n  include?: (\"httpMetadata\" | \"customMetadata\")[];\n}\nexport declare abstract class R2Bucket {\n  head(key: string): Promise<R2Object | null>;\n  get(\n    key: string,\n    options: R2GetOptions & {\n      onlyIf: R2Conditional | Headers;\n    },\n  ): Promise<R2ObjectBody | R2Object | null>;\n  get(key: string, options?: R2GetOptions): Promise<R2ObjectBody | null>;\n  put(\n    key: string,\n    value:\n      | ReadableStream\n      | ArrayBuffer\n      | ArrayBufferView\n      | string\n      | null\n      | Blob,\n    options?: R2PutOptions & {\n      onlyIf: R2Conditional | Headers;\n    },\n  ): Promise<R2Object | null>;\n  put(\n    key: string,\n    value:\n      | ReadableStream\n      | ArrayBuffer\n      | ArrayBufferView\n      | string\n      | null\n      | Blob,\n    options?: R2PutOptions,\n  ): Promise<R2Object>;\n  createMultipartUpload(\n    key: string,\n    options?: R2MultipartOptions,\n  ): Promise<R2MultipartUpload>;\n  resumeMultipartUpload(key: string, uploadId: string): R2MultipartUpload;\n  delete(keys: string | string[]): Promise<void>;\n  list(options?: R2ListOptions): Promise<R2Objects>;\n}\nexport interface R2MultipartUpload {\n  readonly key: string;\n  readonly uploadId: string;\n  uploadPart(\n    partNumber: number,\n    value: ReadableStream | (ArrayBuffer | ArrayBufferView) | string | Blob,\n    options?: R2UploadPartOptions,\n  ): Promise<R2UploadedPart>;\n  abort(): Promise<void>;\n  complete(uploadedParts: R2UploadedPart[]): Promise<R2Object>;\n}\nexport interface R2UploadedPart {\n  partNumber: number;\n  etag: string;\n}\nexport declare abstract class R2Object {\n  readonly key: string;\n  readonly version: string;\n  readonly size: number;\n  readonly etag: string;\n  readonly httpEtag: string;\n  readonly checksums: R2Checksums;\n  readonly uploaded: Date;\n  readonly httpMetadata?: R2HTTPMetadata;\n  readonly customMetadata?: Record<string, string>;\n  readonly range?: R2Range;\n  readonly storageClass: string;\n  readonly ssecKeyMd5?: string;\n  writeHttpMetadata(headers: Headers): void;\n}\nexport interface R2ObjectBody extends R2Object {\n  get body(): ReadableStream;\n  get bodyUsed(): boolean;\n  arrayBuffer(): Promise<ArrayBuffer>;\n  bytes(): Promise<Uint8Array>;\n  text(): Promise<string>;\n  json<T>(): Promise<T>;\n  blob(): Promise<Blob>;\n}\nexport type R2Range =\n  | {\n      offset: number;\n      length?: number;\n    }\n  | {\n      offset?: number;\n      length: number;\n    }\n  | {\n      suffix: number;\n    };\nexport interface R2Conditional {\n  etagMatches?: string;\n  etagDoesNotMatch?: string;\n  uploadedBefore?: Date;\n  uploadedAfter?: Date;\n  secondsGranularity?: boolean;\n}\nexport interface R2GetOptions {\n  onlyIf?: R2Conditional | Headers;\n  range?: R2Range | Headers;\n  ssecKey?: ArrayBuffer | string;\n}\nexport interface R2PutOptions {\n  onlyIf?: R2Conditional | Headers;\n  httpMetadata?: R2HTTPMetadata | Headers;\n  customMetadata?: Record<string, string>;\n  md5?: (ArrayBuffer | ArrayBufferView) | string;\n  sha1?: (ArrayBuffer | ArrayBufferView) | string;\n  sha256?: (ArrayBuffer | ArrayBufferView) | string;\n  sha384?: (ArrayBuffer | ArrayBufferView) | string;\n  sha512?: (ArrayBuffer | ArrayBufferView) | string;\n  storageClass?: string;\n  ssecKey?: ArrayBuffer | string;\n}\nexport interface R2MultipartOptions {\n  httpMetadata?: R2HTTPMetadata | Headers;\n  customMetadata?: Record<string, string>;\n  storageClass?: string;\n  ssecKey?: ArrayBuffer | string;\n}\nexport interface R2Checksums {\n  readonly md5?: ArrayBuffer;\n  readonly sha1?: ArrayBuffer;\n  readonly sha256?: ArrayBuffer;\n  readonly sha384?: ArrayBuffer;\n  readonly sha512?: ArrayBuffer;\n  toJSON(): R2StringChecksums;\n}\nexport interface R2StringChecksums {\n  md5?: string;\n  sha1?: string;\n  sha256?: string;\n  sha384?: string;\n  sha512?: string;\n}\nexport interface R2HTTPMetadata {\n  contentType?: string;\n  contentLanguage?: string;\n  contentDisposition?: string;\n  contentEncoding?: string;\n  cacheControl?: string;\n  cacheExpiry?: Date;\n}\nexport type R2Objects = {\n  objects: R2Object[];\n  delimitedPrefixes: string[];\n} & (\n  | {\n      truncated: true;\n      cursor: string;\n    }\n  | {\n      truncated: false;\n    }\n);\nexport interface R2UploadPartOptions {\n  ssecKey?: ArrayBuffer | string;\n}\nexport declare abstract class ScheduledEvent extends ExtendableEvent {\n  readonly scheduledTime: number;\n  readonly cron: string;\n  noRetry(): void;\n}\nexport interface ScheduledController {\n  readonly scheduledTime: number;\n  readonly cron: string;\n  noRetry(): void;\n}\nexport interface QueuingStrategy<T = any> {\n  highWaterMark?: number | bigint;\n  size?: (chunk: T) => number | bigint;\n}\nexport interface UnderlyingSink<W = any> {\n  type?: string;\n  start?: (controller: WritableStreamDefaultController) => void | Promise<void>;\n  write?: (\n    chunk: W,\n    controller: WritableStreamDefaultController,\n  ) => void | Promise<void>;\n  abort?: (reason: any) => void | Promise<void>;\n  close?: () => void | Promise<void>;\n}\nexport interface UnderlyingByteSource {\n  type: \"bytes\";\n  autoAllocateChunkSize?: number;\n  start?: (controller: ReadableByteStreamController) => void | Promise<void>;\n  pull?: (controller: ReadableByteStreamController) => void | Promise<void>;\n  cancel?: (reason: any) => void | Promise<void>;\n}\nexport interface UnderlyingSource<R = any> {\n  type?: \"\" | undefined;\n  start?: (\n    controller: ReadableStreamDefaultController<R>,\n  ) => void | Promise<void>;\n  pull?: (\n    controller: ReadableStreamDefaultController<R>,\n  ) => void | Promise<void>;\n  cancel?: (reason: any) => void | Promise<void>;\n  expectedLength?: number | bigint;\n}\nexport interface Transformer<I = any, O = any> {\n  readableType?: string;\n  writableType?: string;\n  start?: (\n    controller: TransformStreamDefaultController<O>,\n  ) => void | Promise<void>;\n  transform?: (\n    chunk: I,\n    controller: TransformStreamDefaultController<O>,\n  ) => void | Promise<void>;\n  flush?: (\n    controller: TransformStreamDefaultController<O>,\n  ) => void | Promise<void>;\n  cancel?: (reason: any) => void | Promise<void>;\n  expectedLength?: number;\n}\nexport interface StreamPipeOptions {\n  preventAbort?: boolean;\n  preventCancel?: boolean;\n  /**\n   * Pipes this readable stream to a given writable stream destination. The way in which the piping process behaves under various error conditions can be customized with a number of passed options. It returns a promise that fulfills when the piping process completes successfully, or rejects if any errors were encountered.\n   *\n   * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader.\n   *\n   * Errors and closures of the source and destination streams propagate as follows:\n   *\n   * An error in this source readable stream will abort destination, unless preventAbort is truthy. The returned promise will be rejected with the source's error, or with any error that occurs during aborting the destination.\n   *\n   * An error in destination will cancel this source readable stream, unless preventCancel is truthy. The returned promise will be rejected with the destination's error, or with any error that occurs during canceling the source.\n   *\n   * When this source readable stream closes, destination will be closed, unless preventClose is truthy. The returned promise will be fulfilled once this process completes, unless an error is encountered while closing the destination, in which case it will be rejected with that error.\n   *\n   * If destination starts out closed or closing, this source readable stream will be canceled, unless preventCancel is true. The returned promise will be rejected with an error indicating piping to a closed stream failed, or with any error that occurs during canceling the source.\n   *\n   * The signal option can be set to an AbortSignal to allow aborting an ongoing pipe operation via the corresponding AbortController. In this case, this source readable stream will be canceled, and destination aborted, unless the respective options preventCancel or preventAbort are set.\n   */\n  preventClose?: boolean;\n  signal?: AbortSignal;\n}\nexport type ReadableStreamReadResult<R = any> =\n  | {\n      done: false;\n      value: R;\n    }\n  | {\n      done: true;\n      value?: undefined;\n    };\n/**\n * The `ReadableStream` interface of the Streams API represents a readable stream of byte data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream)\n */\nexport interface ReadableStream<R = any> {\n  /**\n   * The **`locked`** read-only property of the ReadableStream interface returns whether or not the readable stream is locked to a reader.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/locked)\n   */\n  get locked(): boolean;\n  /**\n   * The **`cancel()`** method of the ReadableStream interface returns a Promise that resolves when the stream is canceled.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/cancel)\n   */\n  cancel(reason?: any): Promise<void>;\n  /**\n   * The **`getReader()`** method of the ReadableStream interface creates a reader and locks the stream to it.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader)\n   */\n  getReader(): ReadableStreamDefaultReader<R>;\n  /**\n   * The **`getReader()`** method of the ReadableStream interface creates a reader and locks the stream to it.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader)\n   */\n  getReader(options: ReadableStreamGetReaderOptions): ReadableStreamBYOBReader;\n  /**\n   * The **`pipeThrough()`** method of the ReadableStream interface provides a chainable way of piping the current stream through a transform stream or any other writable/readable pair.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeThrough)\n   */\n  pipeThrough<T>(\n    transform: ReadableWritablePair<T, R>,\n    options?: StreamPipeOptions,\n  ): ReadableStream<T>;\n  /**\n   * The **`pipeTo()`** method of the ReadableStream interface pipes the current `ReadableStream` to a given WritableStream and returns a Promise that fulfills when the piping process completes successfully, or rejects if any errors were encountered.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeTo)\n   */\n  pipeTo(\n    destination: WritableStream<R>,\n    options?: StreamPipeOptions,\n  ): Promise<void>;\n  /**\n   * The **`tee()`** method of the two-element array containing the two resulting branches as new ReadableStream instances.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/tee)\n   */\n  tee(): [ReadableStream<R>, ReadableStream<R>];\n  values(options?: ReadableStreamValuesOptions): AsyncIterableIterator<R>;\n  [Symbol.asyncIterator](\n    options?: ReadableStreamValuesOptions,\n  ): AsyncIterableIterator<R>;\n}\n/**\n * The `ReadableStream` interface of the Streams API represents a readable stream of byte data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream)\n */\nexport declare const ReadableStream: {\n  prototype: ReadableStream;\n  new (\n    underlyingSource: UnderlyingByteSource,\n    strategy?: QueuingStrategy<Uint8Array>,\n  ): ReadableStream<Uint8Array>;\n  new <R = any>(\n    underlyingSource?: UnderlyingSource<R>,\n    strategy?: QueuingStrategy<R>,\n  ): ReadableStream<R>;\n};\n/**\n * The **`ReadableStreamDefaultReader`** interface of the Streams API represents a default reader that can be used to read stream data supplied from a network (such as a fetch request).\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader)\n */\nexport declare class ReadableStreamDefaultReader<R = any> {\n  constructor(stream: ReadableStream);\n  get closed(): Promise<void>;\n  cancel(reason?: any): Promise<void>;\n  /**\n   * The **`read()`** method of the ReadableStreamDefaultReader interface returns a Promise providing access to the next chunk in the stream's internal queue.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/read)\n   */\n  read(): Promise<ReadableStreamReadResult<R>>;\n  /**\n   * The **`releaseLock()`** method of the ReadableStreamDefaultReader interface releases the reader's lock on the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/releaseLock)\n   */\n  releaseLock(): void;\n}\n/**\n * The `ReadableStreamBYOBReader` interface of the Streams API defines a reader for a ReadableStream that supports zero-copy reading from an underlying byte source.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader)\n */\nexport declare class ReadableStreamBYOBReader {\n  constructor(stream: ReadableStream);\n  get closed(): Promise<void>;\n  cancel(reason?: any): Promise<void>;\n  /**\n   * The **`read()`** method of the ReadableStreamBYOBReader interface is used to read data into a view on a user-supplied buffer from an associated readable byte stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/read)\n   */\n  read<T extends ArrayBufferView>(\n    view: T,\n  ): Promise<ReadableStreamReadResult<T>>;\n  /**\n   * The **`releaseLock()`** method of the ReadableStreamBYOBReader interface releases the reader's lock on the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/releaseLock)\n   */\n  releaseLock(): void;\n  readAtLeast<T extends ArrayBufferView>(\n    minElements: number,\n    view: T,\n  ): Promise<ReadableStreamReadResult<T>>;\n}\nexport interface ReadableStreamBYOBReaderReadableStreamBYOBReaderReadOptions {\n  min?: number;\n}\nexport interface ReadableStreamGetReaderOptions {\n  /**\n   * Creates a ReadableStreamBYOBReader and locks the stream to the new reader.\n   *\n   * This call behaves the same way as the no-argument variant, except that it only works on readable byte streams, i.e. streams which were constructed specifically with the ability to handle \"bring your own buffer\" reading. The returned BYOB reader provides the ability to directly read individual chunks from the stream via its read() method, into developer-supplied buffers, allowing more precise control over allocation.\n   */\n  mode: \"byob\";\n}\n/**\n * The **`ReadableStreamBYOBRequest`** interface of the Streams API represents a 'pull request' for data from an underlying source that will made as a zero-copy transfer to a consumer (bypassing the stream's internal queues).\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest)\n */\nexport declare abstract class ReadableStreamBYOBRequest {\n  /**\n   * The **`view`** getter property of the ReadableStreamBYOBRequest interface returns the current view.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/view)\n   */\n  get view(): Uint8Array | null;\n  /**\n   * The **`respond()`** method of the ReadableStreamBYOBRequest interface is used to signal to the associated readable byte stream that the specified number of bytes were written into the ReadableStreamBYOBRequest.view.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respond)\n   */\n  respond(bytesWritten: number): void;\n  /**\n   * The **`respondWithNewView()`** method of the ReadableStreamBYOBRequest interface specifies a new view that the consumer of the associated readable byte stream should write to instead of ReadableStreamBYOBRequest.view.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respondWithNewView)\n   */\n  respondWithNewView(view: ArrayBuffer | ArrayBufferView): void;\n  get atLeast(): number | null;\n}\n/**\n * The **`ReadableStreamDefaultController`** interface of the Streams API represents a controller allowing control of a ReadableStream's state and internal queue.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController)\n */\nexport declare abstract class ReadableStreamDefaultController<R = any> {\n  /**\n   * The **`desiredSize`** read-only property of the required to fill the stream's internal queue.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/desiredSize)\n   */\n  get desiredSize(): number | null;\n  /**\n   * The **`close()`** method of the ReadableStreamDefaultController interface closes the associated stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/close)\n   */\n  close(): void;\n  /**\n   * The **`enqueue()`** method of the ```js-nolint enqueue(chunk) ``` - `chunk` - : The chunk to enqueue.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/enqueue)\n   */\n  enqueue(chunk?: R): void;\n  /**\n   * The **`error()`** method of the with the associated stream to error.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/error)\n   */\n  error(reason: any): void;\n}\n/**\n * The **`ReadableByteStreamController`** interface of the Streams API represents a controller for a readable byte stream.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController)\n */\nexport declare abstract class ReadableByteStreamController {\n  /**\n   * The **`byobRequest`** read-only property of the ReadableByteStreamController interface returns the current BYOB request, or `null` if there are no pending requests.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/byobRequest)\n   */\n  get byobRequest(): ReadableStreamBYOBRequest | null;\n  /**\n   * The **`desiredSize`** read-only property of the ReadableByteStreamController interface returns the number of bytes required to fill the stream's internal queue to its 'desired size'.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/desiredSize)\n   */\n  get desiredSize(): number | null;\n  /**\n   * The **`close()`** method of the ReadableByteStreamController interface closes the associated stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/close)\n   */\n  close(): void;\n  /**\n   * The **`enqueue()`** method of the ReadableByteStreamController interface enqueues a given chunk on the associated readable byte stream (the chunk is copied into the stream's internal queues).\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/enqueue)\n   */\n  enqueue(chunk: ArrayBuffer | ArrayBufferView): void;\n  /**\n   * The **`error()`** method of the ReadableByteStreamController interface causes any future interactions with the associated stream to error with the specified reason.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/error)\n   */\n  error(reason: any): void;\n}\n/**\n * The **`WritableStreamDefaultController`** interface of the Streams API represents a controller allowing control of a WritableStream's state.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController)\n */\nexport declare abstract class WritableStreamDefaultController {\n  /**\n   * The read-only **`signal`** property of the WritableStreamDefaultController interface returns the AbortSignal associated with the controller.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/signal)\n   */\n  get signal(): AbortSignal;\n  /**\n   * The **`error()`** method of the with the associated stream to error.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/error)\n   */\n  error(reason?: any): void;\n}\n/**\n * The **`TransformStreamDefaultController`** interface of the Streams API provides methods to manipulate the associated ReadableStream and WritableStream.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController)\n */\nexport declare abstract class TransformStreamDefaultController<O = any> {\n  /**\n   * The **`desiredSize`** read-only property of the TransformStreamDefaultController interface returns the desired size to fill the queue of the associated ReadableStream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/desiredSize)\n   */\n  get desiredSize(): number | null;\n  /**\n   * The **`enqueue()`** method of the TransformStreamDefaultController interface enqueues the given chunk in the readable side of the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/enqueue)\n   */\n  enqueue(chunk?: O): void;\n  /**\n   * The **`error()`** method of the TransformStreamDefaultController interface errors both sides of the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/error)\n   */\n  error(reason: any): void;\n  /**\n   * The **`terminate()`** method of the TransformStreamDefaultController interface closes the readable side and errors the writable side of the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/terminate)\n   */\n  terminate(): void;\n}\nexport interface ReadableWritablePair<R = any, W = any> {\n  readable: ReadableStream<R>;\n  /**\n   * Provides a convenient, chainable way of piping this readable stream through a transform stream (or any other { writable, readable } pair). It simply pipes the stream into the writable side of the supplied pair, and returns the readable side for further use.\n   *\n   * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader.\n   */\n  writable: WritableStream<W>;\n}\n/**\n * The **`WritableStream`** interface of the Streams API provides a standard abstraction for writing streaming data to a destination, known as a sink.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream)\n */\nexport declare class WritableStream<W = any> {\n  constructor(\n    underlyingSink?: UnderlyingSink,\n    queuingStrategy?: QueuingStrategy,\n  );\n  /**\n   * The **`locked`** read-only property of the WritableStream interface returns a boolean indicating whether the `WritableStream` is locked to a writer.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/locked)\n   */\n  get locked(): boolean;\n  /**\n   * The **`abort()`** method of the WritableStream interface aborts the stream, signaling that the producer can no longer successfully write to the stream and it is to be immediately moved to an error state, with any queued writes discarded.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/abort)\n   */\n  abort(reason?: any): Promise<void>;\n  /**\n   * The **`close()`** method of the WritableStream interface closes the associated stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/close)\n   */\n  close(): Promise<void>;\n  /**\n   * The **`getWriter()`** method of the WritableStream interface returns a new instance of WritableStreamDefaultWriter and locks the stream to that instance.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/getWriter)\n   */\n  getWriter(): WritableStreamDefaultWriter<W>;\n}\n/**\n * The **`WritableStreamDefaultWriter`** interface of the Streams API is the object returned by WritableStream.getWriter() and once created locks the writer to the `WritableStream` ensuring that no other streams can write to the underlying sink.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter)\n */\nexport declare class WritableStreamDefaultWriter<W = any> {\n  constructor(stream: WritableStream);\n  /**\n   * The **`closed`** read-only property of the the stream errors or the writer's lock is released.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/closed)\n   */\n  get closed(): Promise<void>;\n  /**\n   * The **`ready`** read-only property of the that resolves when the desired size of the stream's internal queue transitions from non-positive to positive, signaling that it is no longer applying backpressure.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/ready)\n   */\n  get ready(): Promise<void>;\n  /**\n   * The **`desiredSize`** read-only property of the to fill the stream's internal queue.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/desiredSize)\n   */\n  get desiredSize(): number | null;\n  /**\n   * The **`abort()`** method of the the producer can no longer successfully write to the stream and it is to be immediately moved to an error state, with any queued writes discarded.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/abort)\n   */\n  abort(reason?: any): Promise<void>;\n  /**\n   * The **`close()`** method of the stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/close)\n   */\n  close(): Promise<void>;\n  /**\n   * The **`write()`** method of the operation.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/write)\n   */\n  write(chunk?: W): Promise<void>;\n  /**\n   * The **`releaseLock()`** method of the corresponding stream.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/releaseLock)\n   */\n  releaseLock(): void;\n}\n/**\n * The **`TransformStream`** interface of the Streams API represents a concrete implementation of the pipe chain _transform stream_ concept.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream)\n */\nexport declare class TransformStream<I = any, O = any> {\n  constructor(\n    transformer?: Transformer<I, O>,\n    writableStrategy?: QueuingStrategy<I>,\n    readableStrategy?: QueuingStrategy<O>,\n  );\n  /**\n   * The **`readable`** read-only property of the TransformStream interface returns the ReadableStream instance controlled by this `TransformStream`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/readable)\n   */\n  get readable(): ReadableStream<O>;\n  /**\n   * The **`writable`** read-only property of the TransformStream interface returns the WritableStream instance controlled by this `TransformStream`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/writable)\n   */\n  get writable(): WritableStream<I>;\n}\nexport declare class FixedLengthStream extends IdentityTransformStream {\n  constructor(\n    expectedLength: number | bigint,\n    queuingStrategy?: IdentityTransformStreamQueuingStrategy,\n  );\n}\nexport declare class IdentityTransformStream extends TransformStream<\n  ArrayBuffer | ArrayBufferView,\n  Uint8Array\n> {\n  constructor(queuingStrategy?: IdentityTransformStreamQueuingStrategy);\n}\nexport interface IdentityTransformStreamQueuingStrategy {\n  highWaterMark?: number | bigint;\n}\nexport interface ReadableStreamValuesOptions {\n  preventCancel?: boolean;\n}\n/**\n * The **`CompressionStream`** interface of the Compression Streams API is an API for compressing a stream of data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CompressionStream)\n */\nexport declare class CompressionStream extends TransformStream<\n  ArrayBuffer | ArrayBufferView,\n  Uint8Array\n> {\n  constructor(format: \"gzip\" | \"deflate\" | \"deflate-raw\");\n}\n/**\n * The **`DecompressionStream`** interface of the Compression Streams API is an API for decompressing a stream of data.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DecompressionStream)\n */\nexport declare class DecompressionStream extends TransformStream<\n  ArrayBuffer | ArrayBufferView,\n  Uint8Array\n> {\n  constructor(format: \"gzip\" | \"deflate\" | \"deflate-raw\");\n}\n/**\n * The **`TextEncoderStream`** interface of the Encoding API converts a stream of strings into bytes in the UTF-8 encoding.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoderStream)\n */\nexport declare class TextEncoderStream extends TransformStream<\n  string,\n  Uint8Array\n> {\n  constructor();\n  get encoding(): string;\n}\n/**\n * The **`TextDecoderStream`** interface of the Encoding API converts a stream of text in a binary encoding, such as UTF-8 etc., to a stream of strings.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoderStream)\n */\nexport declare class TextDecoderStream extends TransformStream<\n  ArrayBuffer | ArrayBufferView,\n  string\n> {\n  constructor(label?: string, options?: TextDecoderStreamTextDecoderStreamInit);\n  get encoding(): string;\n  get fatal(): boolean;\n  get ignoreBOM(): boolean;\n}\nexport interface TextDecoderStreamTextDecoderStreamInit {\n  fatal?: boolean;\n  ignoreBOM?: boolean;\n}\n/**\n * The **`ByteLengthQueuingStrategy`** interface of the Streams API provides a built-in byte length queuing strategy that can be used when constructing streams.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy)\n */\nexport declare class ByteLengthQueuingStrategy implements QueuingStrategy<ArrayBufferView> {\n  constructor(init: QueuingStrategyInit);\n  /**\n   * The read-only **`ByteLengthQueuingStrategy.highWaterMark`** property returns the total number of bytes that can be contained in the internal queue before backpressure is applied.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/highWaterMark)\n   */\n  get highWaterMark(): number;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/size) */\n  get size(): (chunk?: any) => number;\n}\n/**\n * The **`CountQueuingStrategy`** interface of the Streams API provides a built-in chunk counting queuing strategy that can be used when constructing streams.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy)\n */\nexport declare class CountQueuingStrategy implements QueuingStrategy {\n  constructor(init: QueuingStrategyInit);\n  /**\n   * The read-only **`CountQueuingStrategy.highWaterMark`** property returns the total number of chunks that can be contained in the internal queue before backpressure is applied.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/highWaterMark)\n   */\n  get highWaterMark(): number;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/size) */\n  get size(): (chunk?: any) => number;\n}\nexport interface QueuingStrategyInit {\n  /**\n   * Creates a new ByteLengthQueuingStrategy with the provided high water mark.\n   *\n   * Note that the provided high water mark will not be validated ahead of time. Instead, if it is negative, NaN, or not a number, the resulting ByteLengthQueuingStrategy will cause the corresponding stream constructor to throw.\n   */\n  highWaterMark: number;\n}\nexport interface ScriptVersion {\n  id?: string;\n  tag?: string;\n  message?: string;\n}\nexport declare abstract class TailEvent extends ExtendableEvent {\n  readonly events: TraceItem[];\n  readonly traces: TraceItem[];\n}\nexport interface TraceItem {\n  readonly event:\n    | (\n        | TraceItemFetchEventInfo\n        | TraceItemJsRpcEventInfo\n        | TraceItemScheduledEventInfo\n        | TraceItemAlarmEventInfo\n        | TraceItemQueueEventInfo\n        | TraceItemEmailEventInfo\n        | TraceItemTailEventInfo\n        | TraceItemCustomEventInfo\n        | TraceItemHibernatableWebSocketEventInfo\n      )\n    | null;\n  readonly eventTimestamp: number | null;\n  readonly logs: TraceLog[];\n  readonly exceptions: TraceException[];\n  readonly diagnosticsChannelEvents: TraceDiagnosticChannelEvent[];\n  readonly scriptName: string | null;\n  readonly entrypoint?: string;\n  readonly scriptVersion?: ScriptVersion;\n  readonly dispatchNamespace?: string;\n  readonly scriptTags?: string[];\n  readonly tailAttributes?: Record<string, boolean | number | string>;\n  readonly durableObjectId?: string;\n  readonly outcome: string;\n  readonly executionModel: string;\n  readonly truncated: boolean;\n  readonly cpuTime: number;\n  readonly wallTime: number;\n}\nexport interface TraceItemAlarmEventInfo {\n  readonly scheduledTime: Date;\n}\nexport interface TraceItemCustomEventInfo {}\nexport interface TraceItemScheduledEventInfo {\n  readonly scheduledTime: number;\n  readonly cron: string;\n}\nexport interface TraceItemQueueEventInfo {\n  readonly queue: string;\n  readonly batchSize: number;\n}\nexport interface TraceItemEmailEventInfo {\n  readonly mailFrom: string;\n  readonly rcptTo: string;\n  readonly rawSize: number;\n}\nexport interface TraceItemTailEventInfo {\n  readonly consumedEvents: TraceItemTailEventInfoTailItem[];\n}\nexport interface TraceItemTailEventInfoTailItem {\n  readonly scriptName: string | null;\n}\nexport interface TraceItemFetchEventInfo {\n  readonly response?: TraceItemFetchEventInfoResponse;\n  readonly request: TraceItemFetchEventInfoRequest;\n}\nexport interface TraceItemFetchEventInfoRequest {\n  readonly cf?: any;\n  readonly headers: Record<string, string>;\n  readonly method: string;\n  readonly url: string;\n  getUnredacted(): TraceItemFetchEventInfoRequest;\n}\nexport interface TraceItemFetchEventInfoResponse {\n  readonly status: number;\n}\nexport interface TraceItemJsRpcEventInfo {\n  readonly rpcMethod: string;\n}\nexport interface TraceItemHibernatableWebSocketEventInfo {\n  readonly getWebSocketEvent:\n    | TraceItemHibernatableWebSocketEventInfoMessage\n    | TraceItemHibernatableWebSocketEventInfoClose\n    | TraceItemHibernatableWebSocketEventInfoError;\n}\nexport interface TraceItemHibernatableWebSocketEventInfoMessage {\n  readonly webSocketEventType: string;\n}\nexport interface TraceItemHibernatableWebSocketEventInfoClose {\n  readonly webSocketEventType: string;\n  readonly code: number;\n  readonly wasClean: boolean;\n}\nexport interface TraceItemHibernatableWebSocketEventInfoError {\n  readonly webSocketEventType: string;\n}\nexport interface TraceLog {\n  readonly timestamp: number;\n  readonly level: string;\n  readonly message: any;\n}\nexport interface TraceException {\n  readonly timestamp: number;\n  readonly message: string;\n  readonly name: string;\n  readonly stack?: string;\n}\nexport interface TraceDiagnosticChannelEvent {\n  readonly timestamp: number;\n  readonly channel: string;\n  readonly message: any;\n}\nexport interface TraceMetrics {\n  readonly cpuTime: number;\n  readonly wallTime: number;\n}\nexport interface UnsafeTraceMetrics {\n  fromTrace(item: TraceItem): TraceMetrics;\n}\n/**\n * The **`URL`** interface is used to parse, construct, normalize, and encode URL.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL)\n */\nexport declare class URL {\n  constructor(url: string | URL, base?: string | URL);\n  /**\n   * The **`origin`** read-only property of the URL interface returns a string containing the Unicode serialization of the origin of the represented URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/origin)\n   */\n  get origin(): string;\n  /**\n   * The **`href`** property of the URL interface is a string containing the whole URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href)\n   */\n  get href(): string;\n  /**\n   * The **`href`** property of the URL interface is a string containing the whole URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href)\n   */\n  set href(value: string);\n  /**\n   * The **`protocol`** property of the URL interface is a string containing the protocol or scheme of the URL, including the final `':'`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol)\n   */\n  get protocol(): string;\n  /**\n   * The **`protocol`** property of the URL interface is a string containing the protocol or scheme of the URL, including the final `':'`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol)\n   */\n  set protocol(value: string);\n  /**\n   * The **`username`** property of the URL interface is a string containing the username component of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username)\n   */\n  get username(): string;\n  /**\n   * The **`username`** property of the URL interface is a string containing the username component of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username)\n   */\n  set username(value: string);\n  /**\n   * The **`password`** property of the URL interface is a string containing the password component of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password)\n   */\n  get password(): string;\n  /**\n   * The **`password`** property of the URL interface is a string containing the password component of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password)\n   */\n  set password(value: string);\n  /**\n   * The **`host`** property of the URL interface is a string containing the host, which is the URL.hostname, and then, if the port of the URL is nonempty, a `':'`, followed by the URL.port of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host)\n   */\n  get host(): string;\n  /**\n   * The **`host`** property of the URL interface is a string containing the host, which is the URL.hostname, and then, if the port of the URL is nonempty, a `':'`, followed by the URL.port of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host)\n   */\n  set host(value: string);\n  /**\n   * The **`hostname`** property of the URL interface is a string containing either the domain name or IP address of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname)\n   */\n  get hostname(): string;\n  /**\n   * The **`hostname`** property of the URL interface is a string containing either the domain name or IP address of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname)\n   */\n  set hostname(value: string);\n  /**\n   * The **`port`** property of the URL interface is a string containing the port number of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port)\n   */\n  get port(): string;\n  /**\n   * The **`port`** property of the URL interface is a string containing the port number of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port)\n   */\n  set port(value: string);\n  /**\n   * The **`pathname`** property of the URL interface represents a location in a hierarchical structure.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname)\n   */\n  get pathname(): string;\n  /**\n   * The **`pathname`** property of the URL interface represents a location in a hierarchical structure.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname)\n   */\n  set pathname(value: string);\n  /**\n   * The **`search`** property of the URL interface is a search string, also called a _query string_, that is a string containing a `'?'` followed by the parameters of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search)\n   */\n  get search(): string;\n  /**\n   * The **`search`** property of the URL interface is a search string, also called a _query string_, that is a string containing a `'?'` followed by the parameters of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search)\n   */\n  set search(value: string);\n  /**\n   * The **`hash`** property of the URL interface is a string containing a `'#'` followed by the fragment identifier of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash)\n   */\n  get hash(): string;\n  /**\n   * The **`hash`** property of the URL interface is a string containing a `'#'` followed by the fragment identifier of the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash)\n   */\n  set hash(value: string);\n  /**\n   * The **`searchParams`** read-only property of the access to the [MISSING: httpmethod('GET')] decoded query arguments contained in the URL.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/searchParams)\n   */\n  get searchParams(): URLSearchParams;\n  /**\n   * The **`toJSON()`** method of the URL interface returns a string containing a serialized version of the URL, although in practice it seems to have the same effect as ```js-nolint toJSON() ``` None.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/toJSON)\n   */\n  toJSON(): string;\n  /*function toString() { [native code] }*/\n  toString(): string;\n  /**\n   * The **`URL.canParse()`** static method of the URL interface returns a boolean indicating whether or not an absolute URL, or a relative URL combined with a base URL, are parsable and valid.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/canParse_static)\n   */\n  static canParse(url: string, base?: string): boolean;\n  /**\n   * The **`URL.parse()`** static method of the URL interface returns a newly created URL object representing the URL defined by the parameters.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/parse_static)\n   */\n  static parse(url: string, base?: string): URL | null;\n  /**\n   * The **`createObjectURL()`** static method of the URL interface creates a string containing a URL representing the object given in the parameter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/createObjectURL_static)\n   */\n  static createObjectURL(object: File | Blob): string;\n  /**\n   * The **`revokeObjectURL()`** static method of the URL interface releases an existing object URL which was previously created by calling Call this method when you've finished using an object URL to let the browser know not to keep the reference to the file any longer.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/revokeObjectURL_static)\n   */\n  static revokeObjectURL(object_url: string): void;\n}\n/**\n * The **`URLSearchParams`** interface defines utility methods to work with the query string of a URL.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams)\n */\nexport declare class URLSearchParams {\n  constructor(\n    init?: Iterable<Iterable<string>> | Record<string, string> | string,\n  );\n  /**\n   * The **`size`** read-only property of the URLSearchParams interface indicates the total number of search parameter entries.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/size)\n   */\n  get size(): number;\n  /**\n   * The **`append()`** method of the URLSearchParams interface appends a specified key/value pair as a new search parameter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/append)\n   */\n  append(name: string, value: string): void;\n  /**\n   * The **`delete()`** method of the URLSearchParams interface deletes specified parameters and their associated value(s) from the list of all search parameters.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/delete)\n   */\n  delete(name: string, value?: string): void;\n  /**\n   * The **`get()`** method of the URLSearchParams interface returns the first value associated to the given search parameter.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/get)\n   */\n  get(name: string): string | null;\n  /**\n   * The **`getAll()`** method of the URLSearchParams interface returns all the values associated with a given search parameter as an array.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/getAll)\n   */\n  getAll(name: string): string[];\n  /**\n   * The **`has()`** method of the URLSearchParams interface returns a boolean value that indicates whether the specified parameter is in the search parameters.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/has)\n   */\n  has(name: string, value?: string): boolean;\n  /**\n   * The **`set()`** method of the URLSearchParams interface sets the value associated with a given search parameter to the given value.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/set)\n   */\n  set(name: string, value: string): void;\n  /**\n   * The **`URLSearchParams.sort()`** method sorts all key/value pairs contained in this object in place and returns `undefined`.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/sort)\n   */\n  sort(): void;\n  /* Returns an array of key, value pairs for every entry in the search params. */\n  entries(): IterableIterator<[key: string, value: string]>;\n  /* Returns a list of keys in the search params. */\n  keys(): IterableIterator<string>;\n  /* Returns a list of values in the search params. */\n  values(): IterableIterator<string>;\n  forEach<This = unknown>(\n    callback: (\n      this: This,\n      value: string,\n      key: string,\n      parent: URLSearchParams,\n    ) => void,\n    thisArg?: This,\n  ): void;\n  /*function toString() { [native code] }*/\n  toString(): string;\n  [Symbol.iterator](): IterableIterator<[key: string, value: string]>;\n}\nexport declare class URLPattern {\n  constructor(\n    input?: string | URLPatternInit,\n    baseURL?: string | URLPatternOptions,\n    patternOptions?: URLPatternOptions,\n  );\n  get protocol(): string;\n  get username(): string;\n  get password(): string;\n  get hostname(): string;\n  get port(): string;\n  get pathname(): string;\n  get search(): string;\n  get hash(): string;\n  get hasRegExpGroups(): boolean;\n  test(input?: string | URLPatternInit, baseURL?: string): boolean;\n  exec(\n    input?: string | URLPatternInit,\n    baseURL?: string,\n  ): URLPatternResult | null;\n}\nexport interface URLPatternInit {\n  protocol?: string;\n  username?: string;\n  password?: string;\n  hostname?: string;\n  port?: string;\n  pathname?: string;\n  search?: string;\n  hash?: string;\n  baseURL?: string;\n}\nexport interface URLPatternComponentResult {\n  input: string;\n  groups: Record<string, string>;\n}\nexport interface URLPatternResult {\n  inputs: (string | URLPatternInit)[];\n  protocol: URLPatternComponentResult;\n  username: URLPatternComponentResult;\n  password: URLPatternComponentResult;\n  hostname: URLPatternComponentResult;\n  port: URLPatternComponentResult;\n  pathname: URLPatternComponentResult;\n  search: URLPatternComponentResult;\n  hash: URLPatternComponentResult;\n}\nexport interface URLPatternOptions {\n  ignoreCase?: boolean;\n}\n/**\n * A `CloseEvent` is sent to clients using WebSockets when the connection is closed.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent)\n */\nexport declare class CloseEvent extends Event {\n  constructor(type: string, initializer?: CloseEventInit);\n  /**\n   * The **`code`** read-only property of the CloseEvent interface returns a WebSocket connection close code indicating the reason the connection was closed.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/code)\n   */\n  readonly code: number;\n  /**\n   * The **`reason`** read-only property of the CloseEvent interface returns the WebSocket connection close reason the server gave for closing the connection; that is, a concise human-readable prose explanation for the closure.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/reason)\n   */\n  readonly reason: string;\n  /**\n   * The **`wasClean`** read-only property of the CloseEvent interface returns `true` if the connection closed cleanly.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/wasClean)\n   */\n  readonly wasClean: boolean;\n}\nexport interface CloseEventInit {\n  code?: number;\n  reason?: string;\n  wasClean?: boolean;\n}\nexport type WebSocketEventMap = {\n  close: CloseEvent;\n  message: MessageEvent;\n  open: Event;\n  error: ErrorEvent;\n};\n/**\n * The `WebSocket` object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket)\n */\nexport declare var WebSocket: {\n  prototype: WebSocket;\n  new (url: string, protocols?: string[] | string): WebSocket;\n  readonly READY_STATE_CONNECTING: number;\n  readonly CONNECTING: number;\n  readonly READY_STATE_OPEN: number;\n  readonly OPEN: number;\n  readonly READY_STATE_CLOSING: number;\n  readonly CLOSING: number;\n  readonly READY_STATE_CLOSED: number;\n  readonly CLOSED: number;\n};\n/**\n * The `WebSocket` object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket)\n */\nexport interface WebSocket extends EventTarget<WebSocketEventMap> {\n  accept(options?: WebSocketAcceptOptions): void;\n  /**\n   * The **`WebSocket.send()`** method enqueues the specified data to be transmitted to the server over the WebSocket connection, increasing the value of `bufferedAmount` by the number of bytes needed to contain the data.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/send)\n   */\n  send(message: (ArrayBuffer | ArrayBufferView) | string): void;\n  /**\n   * The **`WebSocket.close()`** method closes the already `CLOSED`, this method does nothing.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/close)\n   */\n  close(code?: number, reason?: string): void;\n  serializeAttachment(attachment: any): void;\n  deserializeAttachment(): any | null;\n  /**\n   * The **`WebSocket.readyState`** read-only property returns the current state of the WebSocket connection.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/readyState)\n   */\n  readyState: number;\n  /**\n   * The **`WebSocket.url`** read-only property returns the absolute URL of the WebSocket as resolved by the constructor.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/url)\n   */\n  url: string | null;\n  /**\n   * The **`WebSocket.protocol`** read-only property returns the name of the sub-protocol the server selected; this will be one of the strings specified in the `protocols` parameter when creating the WebSocket object, or the empty string if no connection is established.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/protocol)\n   */\n  protocol: string | null;\n  /**\n   * The **`WebSocket.extensions`** read-only property returns the extensions selected by the server.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/extensions)\n   */\n  extensions: string | null;\n  /**\n   * The **`WebSocket.binaryType`** property controls the type of binary data being received over the WebSocket connection.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/binaryType)\n   */\n  binaryType: \"blob\" | \"arraybuffer\";\n}\nexport interface WebSocketAcceptOptions {\n  /**\n   * When set to `true`, receiving a server-initiated WebSocket Close frame will not\n   * automatically send a reciprocal Close frame, leaving the connection in a half-open\n   * state. This is useful for proxying scenarios where you need to coordinate closing\n   * both sides independently. Defaults to `false` when the\n   * `no_web_socket_half_open_by_default` compatibility flag is enabled.\n   */\n  allowHalfOpen?: boolean;\n}\nexport declare const WebSocketPair: {\n  new (): {\n    0: WebSocket;\n    1: WebSocket;\n  };\n};\nexport interface SqlStorage {\n  exec<T extends Record<string, SqlStorageValue>>(\n    query: string,\n    ...bindings: any[]\n  ): SqlStorageCursor<T>;\n  get databaseSize(): number;\n  Cursor: typeof SqlStorageCursor;\n  Statement: typeof SqlStorageStatement;\n}\nexport declare abstract class SqlStorageStatement {}\nexport type SqlStorageValue = ArrayBuffer | string | number | null;\nexport declare abstract class SqlStorageCursor<\n  T extends Record<string, SqlStorageValue>,\n> {\n  next():\n    | {\n        done?: false;\n        value: T;\n      }\n    | {\n        done: true;\n        value?: never;\n      };\n  toArray(): T[];\n  one(): T;\n  raw<U extends SqlStorageValue[]>(): IterableIterator<U>;\n  columnNames: string[];\n  get rowsRead(): number;\n  get rowsWritten(): number;\n  [Symbol.iterator](): IterableIterator<T>;\n}\nexport interface Socket {\n  get readable(): ReadableStream;\n  get writable(): WritableStream;\n  get closed(): Promise<void>;\n  get opened(): Promise<SocketInfo>;\n  get upgraded(): boolean;\n  get secureTransport(): \"on\" | \"off\" | \"starttls\";\n  close(): Promise<void>;\n  startTls(options?: TlsOptions): Socket;\n}\nexport interface SocketOptions {\n  secureTransport?: string;\n  allowHalfOpen: boolean;\n  highWaterMark?: number | bigint;\n}\nexport interface SocketAddress {\n  hostname: string;\n  port: number;\n}\nexport interface TlsOptions {\n  expectedServerHostname?: string;\n}\nexport interface SocketInfo {\n  remoteAddress?: string;\n  localAddress?: string;\n}\n/**\n * The **`EventSource`** interface is web content's interface to server-sent events.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource)\n */\nexport declare class EventSource extends EventTarget {\n  constructor(url: string, init?: EventSourceEventSourceInit);\n  /**\n   * The **`close()`** method of the EventSource interface closes the connection, if one is made, and sets the ```js-nolint close() ``` None.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close)\n   */\n  close(): void;\n  /**\n   * The **`url`** read-only property of the URL of the source.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url)\n   */\n  get url(): string;\n  /**\n   * The **`withCredentials`** read-only property of the the `EventSource` object was instantiated with CORS credentials set.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials)\n   */\n  get withCredentials(): boolean;\n  /**\n   * The **`readyState`** read-only property of the connection.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState)\n   */\n  get readyState(): number;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */\n  get onopen(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */\n  set onopen(value: any | null);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */\n  get onmessage(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */\n  set onmessage(value: any | null);\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */\n  get onerror(): any | null;\n  /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */\n  set onerror(value: any | null);\n  static readonly CONNECTING: number;\n  static readonly OPEN: number;\n  static readonly CLOSED: number;\n  static from(stream: ReadableStream): EventSource;\n}\nexport interface EventSourceEventSourceInit {\n  withCredentials?: boolean;\n  fetcher?: Fetcher;\n}\nexport interface Container {\n  get running(): boolean;\n  start(options?: ContainerStartupOptions): void;\n  monitor(): Promise<void>;\n  destroy(error?: any): Promise<void>;\n  signal(signo: number): void;\n  getTcpPort(port: number): Fetcher;\n  setInactivityTimeout(durationMs: number | bigint): Promise<void>;\n  interceptOutboundHttp(addr: string, binding: Fetcher): Promise<void>;\n  interceptAllOutboundHttp(binding: Fetcher): Promise<void>;\n}\nexport interface ContainerStartupOptions {\n  entrypoint?: string[];\n  enableInternet: boolean;\n  env?: Record<string, string>;\n  hardTimeout?: number | bigint;\n  labels?: Record<string, string>;\n}\n/**\n * The **`MessagePort`** interface of the Channel Messaging API represents one of the two ports of a MessageChannel, allowing messages to be sent from one port and listening out for them arriving at the other.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort)\n */\nexport declare abstract class MessagePort extends EventTarget {\n  /**\n   * The **`postMessage()`** method of the transfers ownership of objects to other browsing contexts.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/postMessage)\n   */\n  postMessage(\n    data?: any,\n    options?: any[] | MessagePortPostMessageOptions,\n  ): void;\n  /**\n   * The **`close()`** method of the MessagePort interface disconnects the port, so it is no longer active.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/close)\n   */\n  close(): void;\n  /**\n   * The **`start()`** method of the MessagePort interface starts the sending of messages queued on the port.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/start)\n   */\n  start(): void;\n  get onmessage(): any | null;\n  set onmessage(value: any | null);\n}\n/**\n * The **`MessageChannel`** interface of the Channel Messaging API allows us to create a new message channel and send data through it via its two MessagePort properties.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel)\n */\nexport declare class MessageChannel {\n  constructor();\n  /**\n   * The **`port1`** read-only property of the the port attached to the context that originated the channel.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel/port1)\n   */\n  readonly port1: MessagePort;\n  /**\n   * The **`port2`** read-only property of the the port attached to the context at the other end of the channel, which the message is initially sent to.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel/port2)\n   */\n  readonly port2: MessagePort;\n}\nexport interface MessagePortPostMessageOptions {\n  transfer?: any[];\n}\nexport type LoopbackForExport<\n  T extends\n    | (new (...args: any[]) => Rpc.EntrypointBranded)\n    | ExportedHandler<any, any, any>\n    | undefined = undefined,\n> = T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded\n  ? LoopbackServiceStub<InstanceType<T>>\n  : T extends new (...args: any[]) => Rpc.DurableObjectBranded\n    ? LoopbackDurableObjectClass<InstanceType<T>>\n    : T extends ExportedHandler<any, any, any>\n      ? LoopbackServiceStub<undefined>\n      : undefined;\nexport type LoopbackServiceStub<\n  T extends Rpc.WorkerEntrypointBranded | undefined = undefined,\n> = Fetcher<T> &\n  (T extends CloudflareWorkersModule.WorkerEntrypoint<any, infer Props>\n    ? (opts: { props?: Props }) => Fetcher<T>\n    : (opts: { props?: any }) => Fetcher<T>);\nexport type LoopbackDurableObjectClass<\n  T extends Rpc.DurableObjectBranded | undefined = undefined,\n> = DurableObjectClass<T> &\n  (T extends CloudflareWorkersModule.DurableObject<any, infer Props>\n    ? (opts: { props?: Props }) => DurableObjectClass<T>\n    : (opts: { props?: any }) => DurableObjectClass<T>);\nexport interface SyncKvStorage {\n  get<T = unknown>(key: string): T | undefined;\n  list<T = unknown>(options?: SyncKvListOptions): Iterable<[string, T]>;\n  put<T>(key: string, value: T): void;\n  delete(key: string): boolean;\n}\nexport interface SyncKvListOptions {\n  start?: string;\n  startAfter?: string;\n  end?: string;\n  prefix?: string;\n  reverse?: boolean;\n  limit?: number;\n}\nexport interface WorkerStub {\n  getEntrypoint<T extends Rpc.WorkerEntrypointBranded | undefined>(\n    name?: string,\n    options?: WorkerStubEntrypointOptions,\n  ): Fetcher<T>;\n}\nexport interface WorkerStubEntrypointOptions {\n  props?: any;\n}\nexport interface WorkerLoader {\n  get(\n    name: string | null,\n    getCode: () => WorkerLoaderWorkerCode | Promise<WorkerLoaderWorkerCode>,\n  ): WorkerStub;\n  load(code: WorkerLoaderWorkerCode): WorkerStub;\n}\nexport interface WorkerLoaderModule {\n  js?: string;\n  cjs?: string;\n  text?: string;\n  data?: ArrayBuffer;\n  json?: any;\n  py?: string;\n  wasm?: ArrayBuffer;\n}\nexport interface WorkerLoaderWorkerCode {\n  compatibilityDate: string;\n  compatibilityFlags?: string[];\n  allowExperimental?: boolean;\n  mainModule: string;\n  modules: Record<string, WorkerLoaderModule | string>;\n  env?: any;\n  globalOutbound?: Fetcher | null;\n  tails?: Fetcher[];\n  streamingTails?: Fetcher[];\n}\n/**\n * The Workers runtime supports a subset of the Performance API, used to measure timing and performance,\n * as well as timing of subrequests and other operations.\n *\n * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/)\n */\nexport declare abstract class Performance {\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancetimeorigin) */\n  get timeOrigin(): number;\n  /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancenow) */\n  now(): number;\n  /**\n   * The **`toJSON()`** method of the Performance interface is a Serialization; it returns a JSON representation of the Performance object.\n   *\n   * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/toJSON)\n   */\n  toJSON(): object;\n}\n// AI Search V2 API Error Interfaces\nexport interface AiSearchInternalError extends Error {}\nexport interface AiSearchNotFoundError extends Error {}\nexport interface AiSearchNameNotSetError extends Error {}\n// AI Search V2 Request Types\nexport type AiSearchSearchRequest = {\n  messages: Array<{\n    role: \"system\" | \"developer\" | \"user\" | \"assistant\" | \"tool\";\n    content: string | null;\n  }>;\n  ai_search_options?: {\n    retrieval?: {\n      retrieval_type?: \"vector\" | \"keyword\" | \"hybrid\";\n      /** Match threshold (0-1, default 0.4) */\n      match_threshold?: number;\n      /** Maximum number of results (1-50, default 10) */\n      max_num_results?: number;\n      filters?: VectorizeVectorMetadataFilter;\n      /** Context expansion (0-3, default 0) */\n      context_expansion?: number;\n      [key: string]: unknown;\n    };\n    query_rewrite?: {\n      enabled?: boolean;\n      model?: string;\n      rewrite_prompt?: string;\n      [key: string]: unknown;\n    };\n    reranking?: {\n      /** Enable reranking (default false) */\n      enabled?: boolean;\n      model?: \"@cf/baai/bge-reranker-base\" | \"\";\n      /** Match threshold (0-1, default 0.4) */\n      match_threshold?: number;\n      [key: string]: unknown;\n    };\n    [key: string]: unknown;\n  };\n};\nexport type AiSearchChatCompletionsRequest = {\n  messages: Array<{\n    role: \"system\" | \"developer\" | \"user\" | \"assistant\" | \"tool\";\n    content: string | null;\n  }>;\n  model?: string;\n  stream?: boolean;\n  ai_search_options?: {\n    retrieval?: {\n      retrieval_type?: \"vector\" | \"keyword\" | \"hybrid\";\n      match_threshold?: number;\n      max_num_results?: number;\n      filters?: VectorizeVectorMetadataFilter;\n      context_expansion?: number;\n      [key: string]: unknown;\n    };\n    query_rewrite?: {\n      enabled?: boolean;\n      model?: string;\n      rewrite_prompt?: string;\n      [key: string]: unknown;\n    };\n    reranking?: {\n      enabled?: boolean;\n      model?: \"@cf/baai/bge-reranker-base\" | \"\";\n      match_threshold?: number;\n      [key: string]: unknown;\n    };\n    [key: string]: unknown;\n  };\n  [key: string]: unknown;\n};\n// AI Search V2 Response Types\nexport type AiSearchSearchResponse = {\n  search_query: string;\n  chunks: Array<{\n    id: string;\n    type: string;\n    /** Match score (0-1) */\n    score: number;\n    text: string;\n    item: {\n      timestamp?: number;\n      key: string;\n      metadata?: Record<string, unknown>;\n    };\n    scoring_details?: {\n      /** Keyword match score (0-1) */\n      keyword_score?: number;\n      /** Vector similarity score (0-1) */\n      vector_score?: number;\n    };\n  }>;\n};\nexport type AiSearchListResponse = Array<{\n  id: string;\n  internal_id?: string;\n  account_id?: string;\n  account_tag?: string;\n  /** Whether the instance is enabled (default true) */\n  enable?: boolean;\n  type?: \"r2\" | \"web-crawler\";\n  source?: string;\n  [key: string]: unknown;\n}>;\nexport type AiSearchConfig = {\n  /** Instance ID (1-32 chars, pattern: ^[a-z0-9_]+(?:-[a-z0-9_]+)*$) */\n  id: string;\n  type: \"r2\" | \"web-crawler\";\n  source: string;\n  source_params?: object;\n  /** Token ID (UUID format) */\n  token_id?: string;\n  ai_gateway_id?: string;\n  /** Enable query rewriting (default false) */\n  rewrite_query?: boolean;\n  /** Enable reranking (default false) */\n  reranking?: boolean;\n  embedding_model?: string;\n  ai_search_model?: string;\n};\nexport type AiSearchInstance = {\n  id: string;\n  enable?: boolean;\n  type?: \"r2\" | \"web-crawler\";\n  source?: string;\n  [key: string]: unknown;\n};\n// AI Search Instance Service - Instance-level operations\nexport declare abstract class AiSearchInstanceService {\n  /**\n   * Search the AI Search instance for relevant chunks.\n   * @param params Search request with messages and AI search options\n   * @returns Search response with matching chunks\n   */\n  search(params: AiSearchSearchRequest): Promise<AiSearchSearchResponse>;\n  /**\n   * Generate chat completions with AI Search context.\n   * @param params Chat completions request with optional streaming\n   * @returns Response object (if streaming) or chat completion result\n   */\n  chatCompletions(\n    params: AiSearchChatCompletionsRequest,\n  ): Promise<Response | object>;\n  /**\n   * Delete this AI Search instance.\n   */\n  delete(): Promise<void>;\n}\n// AI Search Account Service - Account-level operations\nexport declare abstract class AiSearchAccountService {\n  /**\n   * List all AI Search instances in the account.\n   * @returns Array of AI Search instances\n   */\n  list(): Promise<AiSearchListResponse>;\n  /**\n   * Get an AI Search instance by ID.\n   * @param name Instance ID\n   * @returns Instance service for performing operations\n   */\n  get(name: string): AiSearchInstanceService;\n  /**\n   * Create a new AI Search instance.\n   * @param config Instance configuration\n   * @returns Instance service for performing operations\n   */\n  create(config: AiSearchConfig): Promise<AiSearchInstanceService>;\n}\nexport type AiImageClassificationInput = {\n  image: number[];\n};\nexport type AiImageClassificationOutput = {\n  score?: number;\n  label?: string;\n}[];\nexport declare abstract class BaseAiImageClassification {\n  inputs: AiImageClassificationInput;\n  postProcessedOutputs: AiImageClassificationOutput;\n}\nexport type AiImageToTextInput = {\n  image: number[];\n  prompt?: string;\n  max_tokens?: number;\n  temperature?: number;\n  top_p?: number;\n  top_k?: number;\n  seed?: number;\n  repetition_penalty?: number;\n  frequency_penalty?: number;\n  presence_penalty?: number;\n  raw?: boolean;\n  messages?: RoleScopedChatInput[];\n};\nexport type AiImageToTextOutput = {\n  description: string;\n};\nexport declare abstract class BaseAiImageToText {\n  inputs: AiImageToTextInput;\n  postProcessedOutputs: AiImageToTextOutput;\n}\nexport type AiImageTextToTextInput = {\n  image: string;\n  prompt?: string;\n  max_tokens?: number;\n  temperature?: number;\n  ignore_eos?: boolean;\n  top_p?: number;\n  top_k?: number;\n  seed?: number;\n  repetition_penalty?: number;\n  frequency_penalty?: number;\n  presence_penalty?: number;\n  raw?: boolean;\n  messages?: RoleScopedChatInput[];\n};\nexport type AiImageTextToTextOutput = {\n  description: string;\n};\nexport declare abstract class BaseAiImageTextToText {\n  inputs: AiImageTextToTextInput;\n  postProcessedOutputs: AiImageTextToTextOutput;\n}\nexport type AiMultimodalEmbeddingsInput = {\n  image: string;\n  text: string[];\n};\nexport type AiIMultimodalEmbeddingsOutput = {\n  data: number[][];\n  shape: number[];\n};\nexport declare abstract class BaseAiMultimodalEmbeddings {\n  inputs: AiImageTextToTextInput;\n  postProcessedOutputs: AiImageTextToTextOutput;\n}\nexport type AiObjectDetectionInput = {\n  image: number[];\n};\nexport type AiObjectDetectionOutput = {\n  score?: number;\n  label?: string;\n}[];\nexport declare abstract class BaseAiObjectDetection {\n  inputs: AiObjectDetectionInput;\n  postProcessedOutputs: AiObjectDetectionOutput;\n}\nexport type AiSentenceSimilarityInput = {\n  source: string;\n  sentences: string[];\n};\nexport type AiSentenceSimilarityOutput = number[];\nexport declare abstract class BaseAiSentenceSimilarity {\n  inputs: AiSentenceSimilarityInput;\n  postProcessedOutputs: AiSentenceSimilarityOutput;\n}\nexport type AiAutomaticSpeechRecognitionInput = {\n  audio: number[];\n};\nexport type AiAutomaticSpeechRecognitionOutput = {\n  text?: string;\n  words?: {\n    word: string;\n    start: number;\n    end: number;\n  }[];\n  vtt?: string;\n};\nexport declare abstract class BaseAiAutomaticSpeechRecognition {\n  inputs: AiAutomaticSpeechRecognitionInput;\n  postProcessedOutputs: AiAutomaticSpeechRecognitionOutput;\n}\nexport type AiSummarizationInput = {\n  input_text: string;\n  max_length?: number;\n};\nexport type AiSummarizationOutput = {\n  summary: string;\n};\nexport declare abstract class BaseAiSummarization {\n  inputs: AiSummarizationInput;\n  postProcessedOutputs: AiSummarizationOutput;\n}\nexport type AiTextClassificationInput = {\n  text: string;\n};\nexport type AiTextClassificationOutput = {\n  score?: number;\n  label?: string;\n}[];\nexport declare abstract class BaseAiTextClassification {\n  inputs: AiTextClassificationInput;\n  postProcessedOutputs: AiTextClassificationOutput;\n}\nexport type AiTextEmbeddingsInput = {\n  text: string | string[];\n};\nexport type AiTextEmbeddingsOutput = {\n  shape: number[];\n  data: number[][];\n};\nexport declare abstract class BaseAiTextEmbeddings {\n  inputs: AiTextEmbeddingsInput;\n  postProcessedOutputs: AiTextEmbeddingsOutput;\n}\nexport type RoleScopedChatInput = {\n  role:\n    | \"user\"\n    | \"assistant\"\n    | \"system\"\n    | \"tool\"\n    | (string & NonNullable<unknown>);\n  content: string;\n  name?: string;\n};\nexport type AiTextGenerationToolLegacyInput = {\n  name: string;\n  description: string;\n  parameters?: {\n    type: \"object\" | (string & NonNullable<unknown>);\n    properties: {\n      [key: string]: {\n        type: string;\n        description?: string;\n      };\n    };\n    required: string[];\n  };\n};\nexport type AiTextGenerationToolInput = {\n  type: \"function\" | (string & NonNullable<unknown>);\n  function: {\n    name: string;\n    description: string;\n    parameters?: {\n      type: \"object\" | (string & NonNullable<unknown>);\n      properties: {\n        [key: string]: {\n          type: string;\n          description?: string;\n        };\n      };\n      required: string[];\n    };\n  };\n};\nexport type AiTextGenerationFunctionsInput = {\n  name: string;\n  code: string;\n};\nexport type AiTextGenerationResponseFormat = {\n  type: string;\n  json_schema?: any;\n};\nexport type AiTextGenerationInput = {\n  prompt?: string;\n  raw?: boolean;\n  stream?: boolean;\n  max_tokens?: number;\n  temperature?: number;\n  top_p?: number;\n  top_k?: number;\n  seed?: number;\n  repetition_penalty?: number;\n  frequency_penalty?: number;\n  presence_penalty?: number;\n  messages?: RoleScopedChatInput[];\n  response_format?: AiTextGenerationResponseFormat;\n  tools?:\n    | AiTextGenerationToolInput[]\n    | AiTextGenerationToolLegacyInput[]\n    | (object & NonNullable<unknown>);\n  functions?: AiTextGenerationFunctionsInput[];\n};\nexport type AiTextGenerationToolLegacyOutput = {\n  name: string;\n  arguments: unknown;\n};\nexport type AiTextGenerationToolOutput = {\n  id: string;\n  type: \"function\";\n  function: {\n    name: string;\n    arguments: string;\n  };\n};\nexport type UsageTags = {\n  prompt_tokens: number;\n  completion_tokens: number;\n  total_tokens: number;\n};\nexport type AiTextGenerationOutput = {\n  response?: string;\n  tool_calls?: AiTextGenerationToolLegacyOutput[] &\n    AiTextGenerationToolOutput[];\n  usage?: UsageTags;\n};\nexport declare abstract class BaseAiTextGeneration {\n  inputs: AiTextGenerationInput;\n  postProcessedOutputs: AiTextGenerationOutput;\n}\nexport type AiTextToSpeechInput = {\n  prompt: string;\n  lang?: string;\n};\nexport type AiTextToSpeechOutput =\n  | Uint8Array\n  | {\n      audio: string;\n    };\nexport declare abstract class BaseAiTextToSpeech {\n  inputs: AiTextToSpeechInput;\n  postProcessedOutputs: AiTextToSpeechOutput;\n}\nexport type AiTextToImageInput = {\n  prompt: string;\n  negative_prompt?: string;\n  height?: number;\n  width?: number;\n  image?: number[];\n  image_b64?: string;\n  mask?: number[];\n  num_steps?: number;\n  strength?: number;\n  guidance?: number;\n  seed?: number;\n};\nexport type AiTextToImageOutput = ReadableStream<Uint8Array>;\nexport declare abstract class BaseAiTextToImage {\n  inputs: AiTextToImageInput;\n  postProcessedOutputs: AiTextToImageOutput;\n}\nexport type AiTranslationInput = {\n  text: string;\n  target_lang: string;\n  source_lang?: string;\n};\nexport type AiTranslationOutput = {\n  translated_text?: string;\n};\nexport declare abstract class BaseAiTranslation {\n  inputs: AiTranslationInput;\n  postProcessedOutputs: AiTranslationOutput;\n}\n/**\n * Workers AI support for OpenAI's Chat Completions API\n */\nexport type ChatCompletionContentPartText = {\n  type: \"text\";\n  text: string;\n};\nexport type ChatCompletionContentPartImage = {\n  type: \"image_url\";\n  image_url: {\n    url: string;\n    detail?: \"auto\" | \"low\" | \"high\";\n  };\n};\nexport type ChatCompletionContentPartInputAudio = {\n  type: \"input_audio\";\n  input_audio: {\n    /** Base64 encoded audio data. */\n    data: string;\n    format: \"wav\" | \"mp3\";\n  };\n};\nexport type ChatCompletionContentPartFile = {\n  type: \"file\";\n  file: {\n    /** Base64 encoded file data. */\n    file_data?: string;\n    /** The ID of an uploaded file. */\n    file_id?: string;\n    filename?: string;\n  };\n};\nexport type ChatCompletionContentPartRefusal = {\n  type: \"refusal\";\n  refusal: string;\n};\nexport type ChatCompletionContentPart =\n  | ChatCompletionContentPartText\n  | ChatCompletionContentPartImage\n  | ChatCompletionContentPartInputAudio\n  | ChatCompletionContentPartFile;\nexport type FunctionDefinition = {\n  name: string;\n  description?: string;\n  parameters?: Record<string, unknown>;\n  strict?: boolean | null;\n};\nexport type ChatCompletionFunctionTool = {\n  type: \"function\";\n  function: FunctionDefinition;\n};\nexport type ChatCompletionCustomToolGrammarFormat = {\n  type: \"grammar\";\n  grammar: {\n    definition: string;\n    syntax: \"lark\" | \"regex\";\n  };\n};\nexport type ChatCompletionCustomToolTextFormat = {\n  type: \"text\";\n};\nexport type ChatCompletionCustomToolFormat =\n  | ChatCompletionCustomToolTextFormat\n  | ChatCompletionCustomToolGrammarFormat;\nexport type ChatCompletionCustomTool = {\n  type: \"custom\";\n  custom: {\n    name: string;\n    description?: string;\n    format?: ChatCompletionCustomToolFormat;\n  };\n};\nexport type ChatCompletionTool =\n  | ChatCompletionFunctionTool\n  | ChatCompletionCustomTool;\nexport type ChatCompletionMessageFunctionToolCall = {\n  id: string;\n  type: \"function\";\n  function: {\n    name: string;\n    /** JSON-encoded arguments string. */\n    arguments: string;\n  };\n};\nexport type ChatCompletionMessageCustomToolCall = {\n  id: string;\n  type: \"custom\";\n  custom: {\n    name: string;\n    input: string;\n  };\n};\nexport type ChatCompletionMessageToolCall =\n  | ChatCompletionMessageFunctionToolCall\n  | ChatCompletionMessageCustomToolCall;\nexport type ChatCompletionToolChoiceFunction = {\n  type: \"function\";\n  function: {\n    name: string;\n  };\n};\nexport type ChatCompletionToolChoiceCustom = {\n  type: \"custom\";\n  custom: {\n    name: string;\n  };\n};\nexport type ChatCompletionToolChoiceAllowedTools = {\n  type: \"allowed_tools\";\n  allowed_tools: {\n    mode: \"auto\" | \"required\";\n    tools: Array<Record<string, unknown>>;\n  };\n};\nexport type ChatCompletionToolChoiceOption =\n  | \"none\"\n  | \"auto\"\n  | \"required\"\n  | ChatCompletionToolChoiceFunction\n  | ChatCompletionToolChoiceCustom\n  | ChatCompletionToolChoiceAllowedTools;\nexport type DeveloperMessage = {\n  role: \"developer\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n  name?: string;\n};\nexport type SystemMessage = {\n  role: \"system\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n  name?: string;\n};\n/**\n * Permissive merged content part used inside UserMessage arrays.\n *\n * Cabidela has a limitation where anyOf/oneOf with enum-based discrimination\n * inside nested array items does not correctly match different branches for\n * different array elements, so the schema uses a single merged object.\n */\nexport type UserMessageContentPart = {\n  type: \"text\" | \"image_url\" | \"input_audio\" | \"file\";\n  text?: string;\n  image_url?: {\n    url?: string;\n    detail?: \"auto\" | \"low\" | \"high\";\n  };\n  input_audio?: {\n    data?: string;\n    format?: \"wav\" | \"mp3\";\n  };\n  file?: {\n    file_data?: string;\n    file_id?: string;\n    filename?: string;\n  };\n};\nexport type UserMessage = {\n  role: \"user\";\n  content: string | Array<UserMessageContentPart>;\n  name?: string;\n};\nexport type AssistantMessageContentPart = {\n  type: \"text\" | \"refusal\";\n  text?: string;\n  refusal?: string;\n};\nexport type AssistantMessage = {\n  role: \"assistant\";\n  content?: string | null | Array<AssistantMessageContentPart>;\n  refusal?: string | null;\n  name?: string;\n  audio?: {\n    id: string;\n  };\n  tool_calls?: Array<ChatCompletionMessageToolCall>;\n  function_call?: {\n    name: string;\n    arguments: string;\n  };\n};\nexport type ToolMessage = {\n  role: \"tool\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n  tool_call_id: string;\n};\nexport type FunctionMessage = {\n  role: \"function\";\n  content: string;\n  name: string;\n};\nexport type ChatCompletionMessageParam =\n  | DeveloperMessage\n  | SystemMessage\n  | UserMessage\n  | AssistantMessage\n  | ToolMessage\n  | FunctionMessage;\nexport type ChatCompletionsResponseFormatText = {\n  type: \"text\";\n};\nexport type ChatCompletionsResponseFormatJSONObject = {\n  type: \"json_object\";\n};\nexport type ResponseFormatJSONSchema = {\n  type: \"json_schema\";\n  json_schema: {\n    name: string;\n    description?: string;\n    schema?: Record<string, unknown>;\n    strict?: boolean | null;\n  };\n};\nexport type ResponseFormat =\n  | ChatCompletionsResponseFormatText\n  | ChatCompletionsResponseFormatJSONObject\n  | ResponseFormatJSONSchema;\nexport type ChatCompletionsStreamOptions = {\n  include_usage?: boolean;\n  include_obfuscation?: boolean;\n};\nexport type PredictionContent = {\n  type: \"content\";\n  content:\n    | string\n    | Array<{\n        type: \"text\";\n        text: string;\n      }>;\n};\nexport type AudioParams = {\n  voice:\n    | string\n    | {\n        id: string;\n      };\n  format: \"wav\" | \"aac\" | \"mp3\" | \"flac\" | \"opus\" | \"pcm16\";\n};\nexport type WebSearchUserLocation = {\n  type: \"approximate\";\n  approximate: {\n    city?: string;\n    country?: string;\n    region?: string;\n    timezone?: string;\n  };\n};\nexport type WebSearchOptions = {\n  search_context_size?: \"low\" | \"medium\" | \"high\";\n  user_location?: WebSearchUserLocation;\n};\nexport type ChatTemplateKwargs = {\n  /** Whether to enable reasoning, enabled by default. */\n  enable_thinking?: boolean;\n  /** If false, preserves reasoning context between turns. */\n  clear_thinking?: boolean;\n};\n/** Shared optional properties used by both Prompt and Messages input branches. */\nexport type ChatCompletionsCommonOptions = {\n  model?: string;\n  audio?: AudioParams;\n  frequency_penalty?: number | null;\n  logit_bias?: Record<string, unknown> | null;\n  logprobs?: boolean | null;\n  top_logprobs?: number | null;\n  max_tokens?: number | null;\n  max_completion_tokens?: number | null;\n  metadata?: Record<string, unknown> | null;\n  modalities?: Array<\"text\" | \"audio\"> | null;\n  n?: number | null;\n  parallel_tool_calls?: boolean;\n  prediction?: PredictionContent;\n  presence_penalty?: number | null;\n  reasoning_effort?: \"low\" | \"medium\" | \"high\" | null;\n  chat_template_kwargs?: ChatTemplateKwargs;\n  response_format?: ResponseFormat;\n  seed?: number | null;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n  stop?: string | Array<string> | null;\n  store?: boolean | null;\n  stream?: boolean | null;\n  stream_options?: ChatCompletionsStreamOptions;\n  temperature?: number | null;\n  tool_choice?: ChatCompletionToolChoiceOption;\n  tools?: Array<ChatCompletionTool>;\n  top_p?: number | null;\n  user?: string;\n  web_search_options?: WebSearchOptions;\n  function_call?:\n    | \"none\"\n    | \"auto\"\n    | {\n        name: string;\n      };\n  functions?: Array<FunctionDefinition>;\n};\nexport type PromptTokensDetails = {\n  cached_tokens?: number;\n  audio_tokens?: number;\n};\nexport type CompletionTokensDetails = {\n  reasoning_tokens?: number;\n  audio_tokens?: number;\n  accepted_prediction_tokens?: number;\n  rejected_prediction_tokens?: number;\n};\nexport type CompletionUsage = {\n  prompt_tokens: number;\n  completion_tokens: number;\n  total_tokens: number;\n  prompt_tokens_details?: PromptTokensDetails;\n  completion_tokens_details?: CompletionTokensDetails;\n};\nexport type ChatCompletionTopLogprob = {\n  token: string;\n  logprob: number;\n  bytes: Array<number> | null;\n};\nexport type ChatCompletionTokenLogprob = {\n  token: string;\n  logprob: number;\n  bytes: Array<number> | null;\n  top_logprobs: Array<ChatCompletionTopLogprob>;\n};\nexport type ChatCompletionAudio = {\n  id: string;\n  /** Base64 encoded audio bytes. */\n  data: string;\n  expires_at: number;\n  transcript: string;\n};\nexport type ChatCompletionUrlCitation = {\n  type: \"url_citation\";\n  url_citation: {\n    url: string;\n    title: string;\n    start_index: number;\n    end_index: number;\n  };\n};\nexport type ChatCompletionResponseMessage = {\n  role: \"assistant\";\n  content: string | null;\n  refusal: string | null;\n  annotations?: Array<ChatCompletionUrlCitation>;\n  audio?: ChatCompletionAudio;\n  tool_calls?: Array<ChatCompletionMessageToolCall>;\n  function_call?: {\n    name: string;\n    arguments: string;\n  } | null;\n};\nexport type ChatCompletionLogprobs = {\n  content: Array<ChatCompletionTokenLogprob> | null;\n  refusal?: Array<ChatCompletionTokenLogprob> | null;\n};\nexport type ChatCompletionChoice = {\n  index: number;\n  message: ChatCompletionResponseMessage;\n  finish_reason:\n    | \"stop\"\n    | \"length\"\n    | \"tool_calls\"\n    | \"content_filter\"\n    | \"function_call\";\n  logprobs: ChatCompletionLogprobs | null;\n};\nexport type ChatCompletionsPromptInput = {\n  prompt: string;\n} & ChatCompletionsCommonOptions;\nexport type ChatCompletionsMessagesInput = {\n  messages: Array<ChatCompletionMessageParam>;\n} & ChatCompletionsCommonOptions;\nexport type ChatCompletionsOutput = {\n  id: string;\n  object: string;\n  created: number;\n  model: string;\n  choices: Array<ChatCompletionChoice>;\n  usage?: CompletionUsage;\n  system_fingerprint?: string | null;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n};\n/**\n * Workers AI support for OpenAI's Responses API\n * Reference: https://github.com/openai/openai-node/blob/master/src/resources/responses/responses.ts\n *\n * It's a stripped down version from its source.\n * It currently supports basic function calling, json mode and accepts images as input.\n *\n * It does not include types for WebSearch, CodeInterpreter, FileInputs, MCP, CustomTools.\n * We plan to add those incrementally as model + platform capabilities evolve.\n */\nexport type ResponsesInput = {\n  background?: boolean | null;\n  conversation?: string | ResponseConversationParam | null;\n  include?: Array<ResponseIncludable> | null;\n  input?: string | ResponseInput;\n  instructions?: string | null;\n  max_output_tokens?: number | null;\n  parallel_tool_calls?: boolean | null;\n  previous_response_id?: string | null;\n  prompt_cache_key?: string;\n  reasoning?: Reasoning | null;\n  safety_identifier?: string;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n  stream?: boolean | null;\n  stream_options?: StreamOptions | null;\n  temperature?: number | null;\n  text?: ResponseTextConfig;\n  tool_choice?: ToolChoiceOptions | ToolChoiceFunction;\n  tools?: Array<Tool>;\n  top_p?: number | null;\n  truncation?: \"auto\" | \"disabled\" | null;\n};\nexport type ResponsesOutput = {\n  id?: string;\n  created_at?: number;\n  output_text?: string;\n  error?: ResponseError | null;\n  incomplete_details?: ResponseIncompleteDetails | null;\n  instructions?: string | Array<ResponseInputItem> | null;\n  object?: \"response\";\n  output?: Array<ResponseOutputItem>;\n  parallel_tool_calls?: boolean;\n  temperature?: number | null;\n  tool_choice?: ToolChoiceOptions | ToolChoiceFunction;\n  tools?: Array<Tool>;\n  top_p?: number | null;\n  max_output_tokens?: number | null;\n  previous_response_id?: string | null;\n  prompt?: ResponsePrompt | null;\n  reasoning?: Reasoning | null;\n  safety_identifier?: string;\n  service_tier?: \"auto\" | \"default\" | \"flex\" | \"scale\" | \"priority\" | null;\n  status?: ResponseStatus;\n  text?: ResponseTextConfig;\n  truncation?: \"auto\" | \"disabled\" | null;\n  usage?: ResponseUsage;\n};\nexport type EasyInputMessage = {\n  content: string | ResponseInputMessageContentList;\n  role: \"user\" | \"assistant\" | \"system\" | \"developer\";\n  type?: \"message\";\n};\nexport type ResponsesFunctionTool = {\n  name: string;\n  parameters: {\n    [key: string]: unknown;\n  } | null;\n  strict: boolean | null;\n  type: \"function\";\n  description?: string | null;\n};\nexport type ResponseIncompleteDetails = {\n  reason?: \"max_output_tokens\" | \"content_filter\";\n};\nexport type ResponsePrompt = {\n  id: string;\n  variables?: {\n    [key: string]: string | ResponseInputText | ResponseInputImage;\n  } | null;\n  version?: string | null;\n};\nexport type Reasoning = {\n  effort?: ReasoningEffort | null;\n  generate_summary?: \"auto\" | \"concise\" | \"detailed\" | null;\n  summary?: \"auto\" | \"concise\" | \"detailed\" | null;\n};\nexport type ResponseContent =\n  | ResponseInputText\n  | ResponseInputImage\n  | ResponseOutputText\n  | ResponseOutputRefusal\n  | ResponseContentReasoningText;\nexport type ResponseContentReasoningText = {\n  text: string;\n  type: \"reasoning_text\";\n};\nexport type ResponseConversationParam = {\n  id: string;\n};\nexport type ResponseCreatedEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.created\";\n};\nexport type ResponseCustomToolCallOutput = {\n  call_id: string;\n  output: string | Array<ResponseInputText | ResponseInputImage>;\n  type: \"custom_tool_call_output\";\n  id?: string;\n};\nexport type ResponseError = {\n  code:\n    | \"server_error\"\n    | \"rate_limit_exceeded\"\n    | \"invalid_prompt\"\n    | \"vector_store_timeout\"\n    | \"invalid_image\"\n    | \"invalid_image_format\"\n    | \"invalid_base64_image\"\n    | \"invalid_image_url\"\n    | \"image_too_large\"\n    | \"image_too_small\"\n    | \"image_parse_error\"\n    | \"image_content_policy_violation\"\n    | \"invalid_image_mode\"\n    | \"image_file_too_large\"\n    | \"unsupported_image_media_type\"\n    | \"empty_image_file\"\n    | \"failed_to_download_image\"\n    | \"image_file_not_found\";\n  message: string;\n};\nexport type ResponseErrorEvent = {\n  code: string | null;\n  message: string;\n  param: string | null;\n  sequence_number: number;\n  type: \"error\";\n};\nexport type ResponseFailedEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.failed\";\n};\nexport type ResponseFormatText = {\n  type: \"text\";\n};\nexport type ResponseFormatJSONObject = {\n  type: \"json_object\";\n};\nexport type ResponseFormatTextConfig =\n  | ResponseFormatText\n  | ResponseFormatTextJSONSchemaConfig\n  | ResponseFormatJSONObject;\nexport type ResponseFormatTextJSONSchemaConfig = {\n  name: string;\n  schema: {\n    [key: string]: unknown;\n  };\n  type: \"json_schema\";\n  description?: string;\n  strict?: boolean | null;\n};\nexport type ResponseFunctionCallArgumentsDeltaEvent = {\n  delta: string;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.function_call_arguments.delta\";\n};\nexport type ResponseFunctionCallArgumentsDoneEvent = {\n  arguments: string;\n  item_id: string;\n  name: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.function_call_arguments.done\";\n};\nexport type ResponseFunctionCallOutputItem =\n  | ResponseInputTextContent\n  | ResponseInputImageContent;\nexport type ResponseFunctionCallOutputItemList =\n  Array<ResponseFunctionCallOutputItem>;\nexport type ResponseFunctionToolCall = {\n  arguments: string;\n  call_id: string;\n  name: string;\n  type: \"function_call\";\n  id?: string;\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n};\nexport interface ResponseFunctionToolCallItem extends ResponseFunctionToolCall {\n  id: string;\n}\nexport type ResponseFunctionToolCallOutputItem = {\n  id: string;\n  call_id: string;\n  output: string | Array<ResponseInputText | ResponseInputImage>;\n  type: \"function_call_output\";\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n};\nexport type ResponseIncludable =\n  | \"message.input_image.image_url\"\n  | \"message.output_text.logprobs\";\nexport type ResponseIncompleteEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.incomplete\";\n};\nexport type ResponseInput = Array<ResponseInputItem>;\nexport type ResponseInputContent = ResponseInputText | ResponseInputImage;\nexport type ResponseInputImage = {\n  detail: \"low\" | \"high\" | \"auto\";\n  type: \"input_image\";\n  /**\n   * Base64 encoded image\n   */\n  image_url?: string | null;\n};\nexport type ResponseInputImageContent = {\n  type: \"input_image\";\n  detail?: \"low\" | \"high\" | \"auto\" | null;\n  /**\n   * Base64 encoded image\n   */\n  image_url?: string | null;\n};\nexport type ResponseInputItem =\n  | EasyInputMessage\n  | ResponseInputItemMessage\n  | ResponseOutputMessage\n  | ResponseFunctionToolCall\n  | ResponseInputItemFunctionCallOutput\n  | ResponseReasoningItem;\nexport type ResponseInputItemFunctionCallOutput = {\n  call_id: string;\n  output: string | ResponseFunctionCallOutputItemList;\n  type: \"function_call_output\";\n  id?: string | null;\n  status?: \"in_progress\" | \"completed\" | \"incomplete\" | null;\n};\nexport type ResponseInputItemMessage = {\n  content: ResponseInputMessageContentList;\n  role: \"user\" | \"system\" | \"developer\";\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n  type?: \"message\";\n};\nexport type ResponseInputMessageContentList = Array<ResponseInputContent>;\nexport type ResponseInputMessageItem = {\n  id: string;\n  content: ResponseInputMessageContentList;\n  role: \"user\" | \"system\" | \"developer\";\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n  type?: \"message\";\n};\nexport type ResponseInputText = {\n  text: string;\n  type: \"input_text\";\n};\nexport type ResponseInputTextContent = {\n  text: string;\n  type: \"input_text\";\n};\nexport type ResponseItem =\n  | ResponseInputMessageItem\n  | ResponseOutputMessage\n  | ResponseFunctionToolCallItem\n  | ResponseFunctionToolCallOutputItem;\nexport type ResponseOutputItem =\n  | ResponseOutputMessage\n  | ResponseFunctionToolCall\n  | ResponseReasoningItem;\nexport type ResponseOutputItemAddedEvent = {\n  item: ResponseOutputItem;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.output_item.added\";\n};\nexport type ResponseOutputItemDoneEvent = {\n  item: ResponseOutputItem;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.output_item.done\";\n};\nexport type ResponseOutputMessage = {\n  id: string;\n  content: Array<ResponseOutputText | ResponseOutputRefusal>;\n  role: \"assistant\";\n  status: \"in_progress\" | \"completed\" | \"incomplete\";\n  type: \"message\";\n};\nexport type ResponseOutputRefusal = {\n  refusal: string;\n  type: \"refusal\";\n};\nexport type ResponseOutputText = {\n  text: string;\n  type: \"output_text\";\n  logprobs?: Array<Logprob>;\n};\nexport type ResponseReasoningItem = {\n  id: string;\n  summary: Array<ResponseReasoningSummaryItem>;\n  type: \"reasoning\";\n  content?: Array<ResponseReasoningContentItem>;\n  encrypted_content?: string | null;\n  status?: \"in_progress\" | \"completed\" | \"incomplete\";\n};\nexport type ResponseReasoningSummaryItem = {\n  text: string;\n  type: \"summary_text\";\n};\nexport type ResponseReasoningContentItem = {\n  text: string;\n  type: \"reasoning_text\";\n};\nexport type ResponseReasoningTextDeltaEvent = {\n  content_index: number;\n  delta: string;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.reasoning_text.delta\";\n};\nexport type ResponseReasoningTextDoneEvent = {\n  content_index: number;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  text: string;\n  type: \"response.reasoning_text.done\";\n};\nexport type ResponseRefusalDeltaEvent = {\n  content_index: number;\n  delta: string;\n  item_id: string;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.refusal.delta\";\n};\nexport type ResponseRefusalDoneEvent = {\n  content_index: number;\n  item_id: string;\n  output_index: number;\n  refusal: string;\n  sequence_number: number;\n  type: \"response.refusal.done\";\n};\nexport type ResponseStatus =\n  | \"completed\"\n  | \"failed\"\n  | \"in_progress\"\n  | \"cancelled\"\n  | \"queued\"\n  | \"incomplete\";\nexport type ResponseStreamEvent =\n  | ResponseCompletedEvent\n  | ResponseCreatedEvent\n  | ResponseErrorEvent\n  | ResponseFunctionCallArgumentsDeltaEvent\n  | ResponseFunctionCallArgumentsDoneEvent\n  | ResponseFailedEvent\n  | ResponseIncompleteEvent\n  | ResponseOutputItemAddedEvent\n  | ResponseOutputItemDoneEvent\n  | ResponseReasoningTextDeltaEvent\n  | ResponseReasoningTextDoneEvent\n  | ResponseRefusalDeltaEvent\n  | ResponseRefusalDoneEvent\n  | ResponseTextDeltaEvent\n  | ResponseTextDoneEvent;\nexport type ResponseCompletedEvent = {\n  response: Response;\n  sequence_number: number;\n  type: \"response.completed\";\n};\nexport type ResponseTextConfig = {\n  format?: ResponseFormatTextConfig;\n  verbosity?: \"low\" | \"medium\" | \"high\" | null;\n};\nexport type ResponseTextDeltaEvent = {\n  content_index: number;\n  delta: string;\n  item_id: string;\n  logprobs: Array<Logprob>;\n  output_index: number;\n  sequence_number: number;\n  type: \"response.output_text.delta\";\n};\nexport type ResponseTextDoneEvent = {\n  content_index: number;\n  item_id: string;\n  logprobs: Array<Logprob>;\n  output_index: number;\n  sequence_number: number;\n  text: string;\n  type: \"response.output_text.done\";\n};\nexport type Logprob = {\n  token: string;\n  logprob: number;\n  top_logprobs?: Array<TopLogprob>;\n};\nexport type TopLogprob = {\n  token?: string;\n  logprob?: number;\n};\nexport type ResponseUsage = {\n  input_tokens: number;\n  output_tokens: number;\n  total_tokens: number;\n};\nexport type Tool = ResponsesFunctionTool;\nexport type ToolChoiceFunction = {\n  name: string;\n  type: \"function\";\n};\nexport type ToolChoiceOptions = \"none\";\nexport type ReasoningEffort = \"minimal\" | \"low\" | \"medium\" | \"high\" | null;\nexport type StreamOptions = {\n  include_obfuscation?: boolean;\n};\n/** Marks keys from T that aren't in U as optional never */\nexport type Without<T, U> = {\n  [P in Exclude<keyof T, keyof U>]?: never;\n};\n/** Either T or U, but not both (mutually exclusive) */\nexport type XOR<T, U> = (T & Without<U, T>) | (U & Without<T, U>);\nexport type Ai_Cf_Baai_Bge_Base_En_V1_5_Input =\n  | {\n      text: string | string[];\n      /**\n       * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        text: string | string[];\n        /**\n         * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n         */\n        pooling?: \"mean\" | \"cls\";\n      }[];\n    };\nexport type Ai_Cf_Baai_Bge_Base_En_V1_5_Output =\n  | {\n      shape?: number[];\n      /**\n       * Embeddings of the requested text values\n       */\n      data?: number[][];\n      /**\n       * The pooling method used in the embedding process.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | Ai_Cf_Baai_Bge_Base_En_V1_5_AsyncResponse;\nexport interface Ai_Cf_Baai_Bge_Base_En_V1_5_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Baai_Bge_Base_En_V1_5 {\n  inputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Output;\n}\nexport type Ai_Cf_Openai_Whisper_Input =\n  | string\n  | {\n      /**\n       * An array of integers that represent the audio data constrained to 8-bit unsigned integer values\n       */\n      audio: number[];\n    };\nexport interface Ai_Cf_Openai_Whisper_Output {\n  /**\n   * The transcription\n   */\n  text: string;\n  word_count?: number;\n  words?: {\n    word?: string;\n    /**\n     * The second this word begins in the recording\n     */\n    start?: number;\n    /**\n     * The ending second when the word completes\n     */\n    end?: number;\n  }[];\n  vtt?: string;\n}\nexport declare abstract class Base_Ai_Cf_Openai_Whisper {\n  inputs: Ai_Cf_Openai_Whisper_Input;\n  postProcessedOutputs: Ai_Cf_Openai_Whisper_Output;\n}\nexport type Ai_Cf_Meta_M2M100_1_2B_Input =\n  | {\n      /**\n       * The text to be translated\n       */\n      text: string;\n      /**\n       * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified\n       */\n      source_lang?: string;\n      /**\n       * The language code to translate the text into (e.g., 'es' for Spanish)\n       */\n      target_lang: string;\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        /**\n         * The text to be translated\n         */\n        text: string;\n        /**\n         * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified\n         */\n        source_lang?: string;\n        /**\n         * The language code to translate the text into (e.g., 'es' for Spanish)\n         */\n        target_lang: string;\n      }[];\n    };\nexport type Ai_Cf_Meta_M2M100_1_2B_Output =\n  | {\n      /**\n       * The translated text in the target language\n       */\n      translated_text?: string;\n    }\n  | Ai_Cf_Meta_M2M100_1_2B_AsyncResponse;\nexport interface Ai_Cf_Meta_M2M100_1_2B_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Meta_M2M100_1_2B {\n  inputs: Ai_Cf_Meta_M2M100_1_2B_Input;\n  postProcessedOutputs: Ai_Cf_Meta_M2M100_1_2B_Output;\n}\nexport type Ai_Cf_Baai_Bge_Small_En_V1_5_Input =\n  | {\n      text: string | string[];\n      /**\n       * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        text: string | string[];\n        /**\n         * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n         */\n        pooling?: \"mean\" | \"cls\";\n      }[];\n    };\nexport type Ai_Cf_Baai_Bge_Small_En_V1_5_Output =\n  | {\n      shape?: number[];\n      /**\n       * Embeddings of the requested text values\n       */\n      data?: number[][];\n      /**\n       * The pooling method used in the embedding process.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | Ai_Cf_Baai_Bge_Small_En_V1_5_AsyncResponse;\nexport interface Ai_Cf_Baai_Bge_Small_En_V1_5_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Baai_Bge_Small_En_V1_5 {\n  inputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Output;\n}\nexport type Ai_Cf_Baai_Bge_Large_En_V1_5_Input =\n  | {\n      text: string | string[];\n      /**\n       * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: {\n        text: string | string[];\n        /**\n         * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy.\n         */\n        pooling?: \"mean\" | \"cls\";\n      }[];\n    };\nexport type Ai_Cf_Baai_Bge_Large_En_V1_5_Output =\n  | {\n      shape?: number[];\n      /**\n       * Embeddings of the requested text values\n       */\n      data?: number[][];\n      /**\n       * The pooling method used in the embedding process.\n       */\n      pooling?: \"mean\" | \"cls\";\n    }\n  | Ai_Cf_Baai_Bge_Large_En_V1_5_AsyncResponse;\nexport interface Ai_Cf_Baai_Bge_Large_En_V1_5_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Baai_Bge_Large_En_V1_5 {\n  inputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Output;\n}\nexport type Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input =\n  | string\n  | {\n      /**\n       * The input text prompt for the model to generate a response.\n       */\n      prompt?: string;\n      /**\n       * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n       */\n      raw?: boolean;\n      /**\n       * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n       */\n      top_p?: number;\n      /**\n       * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n       */\n      top_k?: number;\n      /**\n       * Random seed for reproducibility of the generation.\n       */\n      seed?: number;\n      /**\n       * Penalty for repeated tokens; higher values discourage repetition.\n       */\n      repetition_penalty?: number;\n      /**\n       * Decreases the likelihood of the model repeating the same lines verbatim.\n       */\n      frequency_penalty?: number;\n      /**\n       * Increases the likelihood of the model introducing new topics.\n       */\n      presence_penalty?: number;\n      image: number[] | (string & NonNullable<unknown>);\n      /**\n       * The maximum number of tokens to generate in the response.\n       */\n      max_tokens?: number;\n    };\nexport interface Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output {\n  description?: string;\n}\nexport declare abstract class Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M {\n  inputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input;\n  postProcessedOutputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output;\n}\nexport type Ai_Cf_Openai_Whisper_Tiny_En_Input =\n  | string\n  | {\n      /**\n       * An array of integers that represent the audio data constrained to 8-bit unsigned integer values\n       */\n      audio: number[];\n    };\nexport interface Ai_Cf_Openai_Whisper_Tiny_En_Output {\n  /**\n   * The transcription\n   */\n  text: string;\n  word_count?: number;\n  words?: {\n    word?: string;\n    /**\n     * The second this word begins in the recording\n     */\n    start?: number;\n    /**\n     * The ending second when the word completes\n     */\n    end?: number;\n  }[];\n  vtt?: string;\n}\nexport declare abstract class Base_Ai_Cf_Openai_Whisper_Tiny_En {\n  inputs: Ai_Cf_Openai_Whisper_Tiny_En_Input;\n  postProcessedOutputs: Ai_Cf_Openai_Whisper_Tiny_En_Output;\n}\nexport interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input {\n  audio:\n    | string\n    | {\n        body?: object;\n        contentType?: string;\n      };\n  /**\n   * Supported tasks are 'translate' or 'transcribe'.\n   */\n  task?: string;\n  /**\n   * The language of the audio being transcribed or translated.\n   */\n  language?: string;\n  /**\n   * Preprocess the audio with a voice activity detection model.\n   */\n  vad_filter?: boolean;\n  /**\n   * A text prompt to help provide context to the model on the contents of the audio.\n   */\n  initial_prompt?: string;\n  /**\n   * The prefix appended to the beginning of the output of the transcription and can guide the transcription result.\n   */\n  prefix?: string;\n  /**\n   * The number of beams to use in beam search decoding. Higher values may improve accuracy at the cost of speed.\n   */\n  beam_size?: number;\n  /**\n   * Whether to condition on previous text during transcription. Setting to false may help prevent hallucination loops.\n   */\n  condition_on_previous_text?: boolean;\n  /**\n   * Threshold for detecting no-speech segments. Segments with no-speech probability above this value are skipped.\n   */\n  no_speech_threshold?: number;\n  /**\n   * Threshold for filtering out segments with high compression ratio, which often indicate repetitive or hallucinated text.\n   */\n  compression_ratio_threshold?: number;\n  /**\n   * Threshold for filtering out segments with low average log probability, indicating low confidence.\n   */\n  log_prob_threshold?: number;\n  /**\n   * Optional threshold (in seconds) to skip silent periods that may cause hallucinations.\n   */\n  hallucination_silence_threshold?: number;\n}\nexport interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output {\n  transcription_info?: {\n    /**\n     * The language of the audio being transcribed or translated.\n     */\n    language?: string;\n    /**\n     * The confidence level or probability of the detected language being accurate, represented as a decimal between 0 and 1.\n     */\n    language_probability?: number;\n    /**\n     * The total duration of the original audio file, in seconds.\n     */\n    duration?: number;\n    /**\n     * The duration of the audio after applying Voice Activity Detection (VAD) to remove silent or irrelevant sections, in seconds.\n     */\n    duration_after_vad?: number;\n  };\n  /**\n   * The complete transcription of the audio.\n   */\n  text: string;\n  /**\n   * The total number of words in the transcription.\n   */\n  word_count?: number;\n  segments?: {\n    /**\n     * The starting time of the segment within the audio, in seconds.\n     */\n    start?: number;\n    /**\n     * The ending time of the segment within the audio, in seconds.\n     */\n    end?: number;\n    /**\n     * The transcription of the segment.\n     */\n    text?: string;\n    /**\n     * The temperature used in the decoding process, controlling randomness in predictions. Lower values result in more deterministic outputs.\n     */\n    temperature?: number;\n    /**\n     * The average log probability of the predictions for the words in this segment, indicating overall confidence.\n     */\n    avg_logprob?: number;\n    /**\n     * The compression ratio of the input to the output, measuring how much the text was compressed during the transcription process.\n     */\n    compression_ratio?: number;\n    /**\n     * The probability that the segment contains no speech, represented as a decimal between 0 and 1.\n     */\n    no_speech_prob?: number;\n    words?: {\n      /**\n       * The individual word transcribed from the audio.\n       */\n      word?: string;\n      /**\n       * The starting time of the word within the audio, in seconds.\n       */\n      start?: number;\n      /**\n       * The ending time of the word within the audio, in seconds.\n       */\n      end?: number;\n    }[];\n  }[];\n  /**\n   * The transcription in WebVTT format, which includes timing and text information for use in subtitles.\n   */\n  vtt?: string;\n}\nexport declare abstract class Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo {\n  inputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input;\n  postProcessedOutputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output;\n}\nexport type Ai_Cf_Baai_Bge_M3_Input =\n  | Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts\n  | Ai_Cf_Baai_Bge_M3_Input_Embedding\n  | {\n      /**\n       * Batch of the embeddings requests to run using async-queue\n       */\n      requests: (\n        | Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts_1\n        | Ai_Cf_Baai_Bge_M3_Input_Embedding_1\n      )[];\n    };\nexport interface Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts {\n  /**\n   * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts\n   */\n  query?: string;\n  /**\n   * List of provided contexts. Note that the index in this array is important, as the response will refer to it.\n   */\n  contexts: {\n    /**\n     * One of the provided context content\n     */\n    text?: string;\n  }[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\nexport interface Ai_Cf_Baai_Bge_M3_Input_Embedding {\n  text: string | string[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\nexport interface Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts_1 {\n  /**\n   * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts\n   */\n  query?: string;\n  /**\n   * List of provided contexts. Note that the index in this array is important, as the response will refer to it.\n   */\n  contexts: {\n    /**\n     * One of the provided context content\n     */\n    text?: string;\n  }[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\nexport interface Ai_Cf_Baai_Bge_M3_Input_Embedding_1 {\n  text: string | string[];\n  /**\n   * When provided with too long context should the model error out or truncate the context to fit?\n   */\n  truncate_inputs?: boolean;\n}\nexport type Ai_Cf_Baai_Bge_M3_Output =\n  | Ai_Cf_Baai_Bge_M3_Output_Query\n  | Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts\n  | Ai_Cf_Baai_Bge_M3_Output_Embedding\n  | Ai_Cf_Baai_Bge_M3_AsyncResponse;\nexport interface Ai_Cf_Baai_Bge_M3_Output_Query {\n  response?: {\n    /**\n     * Index of the context in the request\n     */\n    id?: number;\n    /**\n     * Score of the context under the index.\n     */\n    score?: number;\n  }[];\n}\nexport interface Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts {\n  response?: number[][];\n  shape?: number[];\n  /**\n   * The pooling method used in the embedding process.\n   */\n  pooling?: \"mean\" | \"cls\";\n}\nexport interface Ai_Cf_Baai_Bge_M3_Output_Embedding {\n  shape?: number[];\n  /**\n   * Embeddings of the requested text values\n   */\n  data?: number[][];\n  /**\n   * The pooling method used in the embedding process.\n   */\n  pooling?: \"mean\" | \"cls\";\n}\nexport interface Ai_Cf_Baai_Bge_M3_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Baai_Bge_M3 {\n  inputs: Ai_Cf_Baai_Bge_M3_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_M3_Output;\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input {\n  /**\n   * A text description of the image you want to generate.\n   */\n  prompt: string;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer.\n   */\n  steps?: number;\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output {\n  /**\n   * The generated image in Base64 format.\n   */\n  image?: string;\n}\nexport declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output;\n}\nexport type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input =\n  | Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Prompt\n  | Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Messages;\nexport interface Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  image?: number[] | (string & NonNullable<unknown>);\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n}\nexport interface Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  image?: number[] | (string & NonNullable<unknown>);\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * If true, the response will be streamed back incrementally.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response?: string;\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\nexport declare abstract class Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct {\n  inputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output;\n}\nexport type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input =\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Async_Batch;\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Async_Batch {\n  requests?: {\n    /**\n     * User-supplied reference. This field will be present in the response as well it can be used to reference the request and response. It's NOT validated to be unique.\n     */\n    external_reference?: string;\n    /**\n     * Prompt for the text generation model\n     */\n    prompt?: string;\n    /**\n     * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n     */\n    stream?: boolean;\n    /**\n     * The maximum number of tokens to generate in the response.\n     */\n    max_tokens?: number;\n    /**\n     * Controls the randomness of the output; higher values produce more random results.\n     */\n    temperature?: number;\n    /**\n     * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n     */\n    top_p?: number;\n    /**\n     * Random seed for reproducibility of the generation.\n     */\n    seed?: number;\n    /**\n     * Penalty for repeated tokens; higher values discourage repetition.\n     */\n    repetition_penalty?: number;\n    /**\n     * Decreases the likelihood of the model repeating the same lines verbatim.\n     */\n    frequency_penalty?: number;\n    /**\n     * Increases the likelihood of the model introducing new topics.\n     */\n    presence_penalty?: number;\n    response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_2;\n  }[];\n}\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_2 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output =\n  | {\n      /**\n       * The generated text response from the model\n       */\n      response: string;\n      /**\n       * Usage statistics for the inference request\n       */\n      usage?: {\n        /**\n         * Total number of tokens in input\n         */\n        prompt_tokens?: number;\n        /**\n         * Total number of tokens in output\n         */\n        completion_tokens?: number;\n        /**\n         * Total number of input and output tokens\n         */\n        total_tokens?: number;\n      };\n      /**\n       * An array of tool calls requests made during the response generation\n       */\n      tool_calls?: {\n        /**\n         * The arguments passed to be passed to the tool call request\n         */\n        arguments?: object;\n        /**\n         * The name of the tool to be called\n         */\n        name?: string;\n      }[];\n    }\n  | string\n  | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_AsyncResponse;\nexport interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast {\n  inputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output;\n}\nexport interface Ai_Cf_Meta_Llama_Guard_3_8B_Input {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender must alternate between 'user' and 'assistant'.\n     */\n    role: \"user\" | \"assistant\";\n    /**\n     * The content of the message as a string.\n     */\n    content: string;\n  }[];\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Dictate the output format of the generated response.\n   */\n  response_format?: {\n    /**\n     * Set to json_object to process and output generated text as JSON.\n     */\n    type?: string;\n  };\n}\nexport interface Ai_Cf_Meta_Llama_Guard_3_8B_Output {\n  response?:\n    | string\n    | {\n        /**\n         * Whether the conversation is safe or not.\n         */\n        safe?: boolean;\n        /**\n         * A list of what hazard categories predicted for the conversation, if the conversation is deemed unsafe.\n         */\n        categories?: string[];\n      };\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n}\nexport declare abstract class Base_Ai_Cf_Meta_Llama_Guard_3_8B {\n  inputs: Ai_Cf_Meta_Llama_Guard_3_8B_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_Guard_3_8B_Output;\n}\nexport interface Ai_Cf_Baai_Bge_Reranker_Base_Input {\n  /**\n   * A query you wish to perform against the provided contexts.\n   */\n  /**\n   * Number of returned results starting with the best score.\n   */\n  top_k?: number;\n  /**\n   * List of provided contexts. Note that the index in this array is important, as the response will refer to it.\n   */\n  contexts: {\n    /**\n     * One of the provided context content\n     */\n    text?: string;\n  }[];\n}\nexport interface Ai_Cf_Baai_Bge_Reranker_Base_Output {\n  response?: {\n    /**\n     * Index of the context in the request\n     */\n    id?: number;\n    /**\n     * Score of the context under the index.\n     */\n    score?: number;\n  }[];\n}\nexport declare abstract class Base_Ai_Cf_Baai_Bge_Reranker_Base {\n  inputs: Ai_Cf_Baai_Bge_Reranker_Base_Input;\n  postProcessedOutputs: Ai_Cf_Baai_Bge_Reranker_Base_Output;\n}\nexport type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input =\n  | Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Prompt\n  | Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Messages;\nexport interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    /**\n     * The content of the message as a string.\n     */\n    content: string;\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\nexport declare abstract class Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct {\n  inputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output;\n}\nexport type Ai_Cf_Qwen_Qwq_32B_Input =\n  | Ai_Cf_Qwen_Qwq_32B_Prompt\n  | Ai_Cf_Qwen_Qwq_32B_Messages;\nexport interface Ai_Cf_Qwen_Qwq_32B_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwq_32B_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport type Ai_Cf_Qwen_Qwq_32B_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\nexport declare abstract class Base_Ai_Cf_Qwen_Qwq_32B {\n  inputs: Ai_Cf_Qwen_Qwq_32B_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwq_32B_Output;\n}\nexport type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input =\n  | Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Prompt\n  | Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Messages;\nexport interface Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\nexport declare abstract class Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct {\n  inputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output;\n}\nexport type Ai_Cf_Google_Gemma_3_12B_It_Input =\n  | Ai_Cf_Google_Gemma_3_12B_It_Prompt\n  | Ai_Cf_Google_Gemma_3_12B_It_Messages;\nexport interface Ai_Cf_Google_Gemma_3_12B_It_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Google_Gemma_3_12B_It_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport type Ai_Cf_Google_Gemma_3_12B_It_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The arguments passed to be passed to the tool call request\n     */\n    arguments?: object;\n    /**\n     * The name of the tool to be called\n     */\n    name?: string;\n  }[];\n};\nexport declare abstract class Base_Ai_Cf_Google_Gemma_3_12B_It {\n  inputs: Ai_Cf_Google_Gemma_3_12B_It_Input;\n  postProcessedOutputs: Ai_Cf_Google_Gemma_3_12B_It_Output;\n}\nexport type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input =\n  | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt\n  | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages\n  | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Async_Batch;\nexport interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Async_Batch {\n  requests: (\n    | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt_Inner\n    | Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages_Inner\n  )[];\n}\nexport interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Prompt_Inner {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * JSON schema that should be fulfilled for the response.\n   */\n  guided_json?: object;\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages_Inner {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role?: string;\n    /**\n     * The tool call id. If you don't know what to put here you can fall back to 000000001\n     */\n    tool_call_id?: string;\n    content?:\n      | string\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        }[]\n      | {\n          /**\n           * Type of the content provided\n           */\n          type?: string;\n          text?: string;\n          image_url?: {\n            /**\n             * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted\n             */\n            url?: string;\n          };\n        };\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode;\n  /**\n   * JSON schema that should be fufilled for the response.\n   */\n  guided_json?: object;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output = {\n  /**\n   * The generated text response from the model\n   */\n  response: string;\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * An array of tool calls requests made during the response generation\n   */\n  tool_calls?: {\n    /**\n     * The tool call id.\n     */\n    id?: string;\n    /**\n     * Specifies the type of tool (e.g., 'function').\n     */\n    type?: string;\n    /**\n     * Details of the function tool.\n     */\n    function?: {\n      /**\n       * The name of the tool to be called\n       */\n      name?: string;\n      /**\n       * The arguments passed to be passed to the tool call request\n       */\n      arguments?: object;\n    };\n  }[];\n};\nexport declare abstract class Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct {\n  inputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input;\n  postProcessedOutputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output;\n}\nexport type Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Input =\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Async_Batch;\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Async_Batch {\n  requests: (\n    | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt_1\n    | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages_1\n  )[];\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Prompt_1 {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_2;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_2 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages_1 {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_3;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_3 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport type Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Output =\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Chat_Completion_Response\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Text_Completion_Response\n  | string\n  | Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_AsyncResponse;\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Chat_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"chat.completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index?: number;\n    /**\n     * The message generated by the model\n     */\n    message?: {\n      /**\n       * Role of the message author\n       */\n      role: string;\n      /**\n       * The content of the message\n       */\n      content: string;\n      /**\n       * Internal reasoning content (if available)\n       */\n      reasoning_content?: string;\n      /**\n       * Tool calls made by the assistant\n       */\n      tool_calls?: {\n        /**\n         * Unique identifier for the tool call\n         */\n        id: string;\n        /**\n         * Type of tool call\n         */\n        type: \"function\";\n        function: {\n          /**\n           * Name of the function to call\n           */\n          name: string;\n          /**\n           * JSON string of arguments for the function\n           */\n          arguments: string;\n        };\n      }[];\n    };\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason?: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * Log probabilities for the prompt (if requested)\n   */\n  prompt_logprobs?: {} | null;\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Text_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"text_completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index: number;\n    /**\n     * The generated text completion\n     */\n    text: string;\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n    /**\n     * Log probabilities for the prompt (if requested)\n     */\n    prompt_logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n}\nexport interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8 {\n  inputs: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Output;\n}\nexport interface Ai_Cf_Deepgram_Nova_3_Input {\n  audio: {\n    body: object;\n    contentType: string;\n  };\n  /**\n   * Sets how the model will interpret strings submitted to the custom_topic param. When strict, the model will only return topics submitted using the custom_topic param. When extended, the model will return its own detected topics in addition to those submitted using the custom_topic param.\n   */\n  custom_topic_mode?: \"extended\" | \"strict\";\n  /**\n   * Custom topics you want the model to detect within your input audio or text if present Submit up to 100\n   */\n  custom_topic?: string;\n  /**\n   * Sets how the model will interpret intents submitted to the custom_intent param. When strict, the model will only return intents submitted using the custom_intent param. When extended, the model will return its own detected intents in addition those submitted using the custom_intents param\n   */\n  custom_intent_mode?: \"extended\" | \"strict\";\n  /**\n   * Custom intents you want the model to detect within your input audio if present\n   */\n  custom_intent?: string;\n  /**\n   * Identifies and extracts key entities from content in submitted audio\n   */\n  detect_entities?: boolean;\n  /**\n   * Identifies the dominant language spoken in submitted audio\n   */\n  detect_language?: boolean;\n  /**\n   * Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0\n   */\n  diarize?: boolean;\n  /**\n   * Identify and extract key entities from content in submitted audio\n   */\n  dictation?: boolean;\n  /**\n   * Specify the expected encoding of your submitted audio\n   */\n  encoding?:\n    | \"linear16\"\n    | \"flac\"\n    | \"mulaw\"\n    | \"amr-nb\"\n    | \"amr-wb\"\n    | \"opus\"\n    | \"speex\"\n    | \"g729\";\n  /**\n   * Arbitrary key-value pairs that are attached to the API response for usage in downstream processing\n   */\n  extra?: string;\n  /**\n   * Filler Words can help transcribe interruptions in your audio, like 'uh' and 'um'\n   */\n  filler_words?: boolean;\n  /**\n   * Key term prompting can boost or suppress specialized terminology and brands.\n   */\n  keyterm?: string;\n  /**\n   * Keywords can boost or suppress specialized terminology and brands.\n   */\n  keywords?: string;\n  /**\n   * The BCP-47 language tag that hints at the primary spoken language. Depending on the Model and API endpoint you choose only certain languages are available.\n   */\n  language?: string;\n  /**\n   * Spoken measurements will be converted to their corresponding abbreviations.\n   */\n  measurements?: boolean;\n  /**\n   * Opts out requests from the Deepgram Model Improvement Program. Refer to our Docs for pricing impacts before setting this to true. https://dpgr.am/deepgram-mip.\n   */\n  mip_opt_out?: boolean;\n  /**\n   * Mode of operation for the model representing broad area of topic that will be talked about in the supplied audio\n   */\n  mode?: \"general\" | \"medical\" | \"finance\";\n  /**\n   * Transcribe each audio channel independently.\n   */\n  multichannel?: boolean;\n  /**\n   * Numerals converts numbers from written format to numerical format.\n   */\n  numerals?: boolean;\n  /**\n   * Splits audio into paragraphs to improve transcript readability.\n   */\n  paragraphs?: boolean;\n  /**\n   * Profanity Filter looks for recognized profanity and converts it to the nearest recognized non-profane word or removes it from the transcript completely.\n   */\n  profanity_filter?: boolean;\n  /**\n   * Add punctuation and capitalization to the transcript.\n   */\n  punctuate?: boolean;\n  /**\n   * Redaction removes sensitive information from your transcripts.\n   */\n  redact?: string;\n  /**\n   * Search for terms or phrases in submitted audio and replaces them.\n   */\n  replace?: string;\n  /**\n   * Search for terms or phrases in submitted audio.\n   */\n  search?: string;\n  /**\n   * Recognizes the sentiment throughout a transcript or text.\n   */\n  sentiment?: boolean;\n  /**\n   * Apply formatting to transcript output. When set to true, additional formatting will be applied to transcripts to improve readability.\n   */\n  smart_format?: boolean;\n  /**\n   * Detect topics throughout a transcript or text.\n   */\n  topics?: boolean;\n  /**\n   * Segments speech into meaningful semantic units.\n   */\n  utterances?: boolean;\n  /**\n   * Seconds to wait before detecting a pause between words in submitted audio.\n   */\n  utt_split?: number;\n  /**\n   * The number of channels in the submitted audio\n   */\n  channels?: number;\n  /**\n   * Specifies whether the streaming endpoint should provide ongoing transcription updates as more audio is received. When set to true, the endpoint sends continuous updates, meaning transcription results may evolve over time. Note: Supported only for webosockets.\n   */\n  interim_results?: boolean;\n  /**\n   * Indicates how long model will wait to detect whether a speaker has finished speaking or pauses for a significant period of time. When set to a value, the streaming endpoint immediately finalizes the transcription for the processed time range and returns the transcript with a speech_final parameter set to true. Can also be set to false to disable endpointing\n   */\n  endpointing?: string;\n  /**\n   * Indicates that speech has started. You'll begin receiving Speech Started messages upon speech starting. Note: Supported only for webosockets.\n   */\n  vad_events?: boolean;\n  /**\n   * Indicates how long model will wait to send an UtteranceEnd message after a word has been transcribed. Use with interim_results. Note: Supported only for webosockets.\n   */\n  utterance_end_ms?: boolean;\n}\nexport interface Ai_Cf_Deepgram_Nova_3_Output {\n  results?: {\n    channels?: {\n      alternatives?: {\n        confidence?: number;\n        transcript?: string;\n        words?: {\n          confidence?: number;\n          end?: number;\n          start?: number;\n          word?: string;\n        }[];\n      }[];\n    }[];\n    summary?: {\n      result?: string;\n      short?: string;\n    };\n    sentiments?: {\n      segments?: {\n        text?: string;\n        start_word?: number;\n        end_word?: number;\n        sentiment?: string;\n        sentiment_score?: number;\n      }[];\n      average?: {\n        sentiment?: string;\n        sentiment_score?: number;\n      };\n    };\n  };\n}\nexport declare abstract class Base_Ai_Cf_Deepgram_Nova_3 {\n  inputs: Ai_Cf_Deepgram_Nova_3_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Nova_3_Output;\n}\nexport interface Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Input {\n  queries?: string | string[];\n  /**\n   * Optional instruction for the task\n   */\n  instruction?: string;\n  documents?: string | string[];\n  text?: string | string[];\n}\nexport interface Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Output {\n  data?: number[][];\n  shape?: number[];\n}\nexport declare abstract class Base_Ai_Cf_Qwen_Qwen3_Embedding_0_6B {\n  inputs: Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Input;\n  postProcessedOutputs: Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Output;\n}\nexport type Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Input =\n  | {\n      /**\n       * readable stream with audio data and content-type specified for that data\n       */\n      audio: {\n        body: object;\n        contentType: string;\n      };\n      /**\n       * type of data PCM data that's sent to the inference server as raw array\n       */\n      dtype?: \"uint8\" | \"float32\" | \"float64\";\n    }\n  | {\n      /**\n       * base64 encoded audio data\n       */\n      audio: string;\n      /**\n       * type of data PCM data that's sent to the inference server as raw array\n       */\n      dtype?: \"uint8\" | \"float32\" | \"float64\";\n    };\nexport interface Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Output {\n  /**\n   * if true, end-of-turn was detected\n   */\n  is_complete?: boolean;\n  /**\n   * probability of the end-of-turn detection\n   */\n  probability?: number;\n}\nexport declare abstract class Base_Ai_Cf_Pipecat_Ai_Smart_Turn_V2 {\n  inputs: Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Input;\n  postProcessedOutputs: Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Output;\n}\nexport declare abstract class Base_Ai_Cf_Openai_Gpt_Oss_120B {\n  inputs: XOR<ResponsesInput, ChatCompletionsInput>;\n  postProcessedOutputs: XOR<ResponsesOutput, ChatCompletionsOutput>;\n}\nexport declare abstract class Base_Ai_Cf_Openai_Gpt_Oss_20B {\n  inputs: XOR<ResponsesInput, ChatCompletionsInput>;\n  postProcessedOutputs: XOR<ResponsesOutput, ChatCompletionsOutput>;\n}\nexport interface Ai_Cf_Leonardo_Phoenix_1_0_Input {\n  /**\n   * A text description of the image you want to generate.\n   */\n  prompt: string;\n  /**\n   * Controls how closely the generated image should adhere to the prompt; higher values make the image more aligned with the prompt\n   */\n  guidance?: number;\n  /**\n   * Random seed for reproducibility of the image generation\n   */\n  seed?: number;\n  /**\n   * The height of the generated image in pixels\n   */\n  height?: number;\n  /**\n   * The width of the generated image in pixels\n   */\n  width?: number;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer\n   */\n  num_steps?: number;\n  /**\n   * Specify what to exclude from the generated images\n   */\n  negative_prompt?: string;\n}\n/**\n * The generated image in JPEG format\n */\nexport type Ai_Cf_Leonardo_Phoenix_1_0_Output = string;\nexport declare abstract class Base_Ai_Cf_Leonardo_Phoenix_1_0 {\n  inputs: Ai_Cf_Leonardo_Phoenix_1_0_Input;\n  postProcessedOutputs: Ai_Cf_Leonardo_Phoenix_1_0_Output;\n}\nexport interface Ai_Cf_Leonardo_Lucid_Origin_Input {\n  /**\n   * A text description of the image you want to generate.\n   */\n  prompt: string;\n  /**\n   * Controls how closely the generated image should adhere to the prompt; higher values make the image more aligned with the prompt\n   */\n  guidance?: number;\n  /**\n   * Random seed for reproducibility of the image generation\n   */\n  seed?: number;\n  /**\n   * The height of the generated image in pixels\n   */\n  height?: number;\n  /**\n   * The width of the generated image in pixels\n   */\n  width?: number;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer\n   */\n  num_steps?: number;\n  /**\n   * The number of diffusion steps; higher values can improve quality but take longer\n   */\n  steps?: number;\n}\nexport interface Ai_Cf_Leonardo_Lucid_Origin_Output {\n  /**\n   * The generated image in Base64 format.\n   */\n  image?: string;\n}\nexport declare abstract class Base_Ai_Cf_Leonardo_Lucid_Origin {\n  inputs: Ai_Cf_Leonardo_Lucid_Origin_Input;\n  postProcessedOutputs: Ai_Cf_Leonardo_Lucid_Origin_Output;\n}\nexport interface Ai_Cf_Deepgram_Aura_1_Input {\n  /**\n   * Speaker used to produce the audio.\n   */\n  speaker?:\n    | \"angus\"\n    | \"asteria\"\n    | \"arcas\"\n    | \"orion\"\n    | \"orpheus\"\n    | \"athena\"\n    | \"luna\"\n    | \"zeus\"\n    | \"perseus\"\n    | \"helios\"\n    | \"hera\"\n    | \"stella\";\n  /**\n   * Encoding of the output audio.\n   */\n  encoding?: \"linear16\" | \"flac\" | \"mulaw\" | \"alaw\" | \"mp3\" | \"opus\" | \"aac\";\n  /**\n   * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type..\n   */\n  container?: \"none\" | \"wav\" | \"ogg\";\n  /**\n   * The text content to be converted to speech\n   */\n  text: string;\n  /**\n   * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable\n   */\n  sample_rate?: number;\n  /**\n   * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type.\n   */\n  bit_rate?: number;\n}\n/**\n * The generated audio in MP3 format\n */\nexport type Ai_Cf_Deepgram_Aura_1_Output = string;\nexport declare abstract class Base_Ai_Cf_Deepgram_Aura_1 {\n  inputs: Ai_Cf_Deepgram_Aura_1_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Aura_1_Output;\n}\nexport interface Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Input {\n  /**\n   * Input text to translate. Can be a single string or a list of strings.\n   */\n  text: string | string[];\n  /**\n   * Target langauge to translate to\n   */\n  target_language:\n    | \"asm_Beng\"\n    | \"awa_Deva\"\n    | \"ben_Beng\"\n    | \"bho_Deva\"\n    | \"brx_Deva\"\n    | \"doi_Deva\"\n    | \"eng_Latn\"\n    | \"gom_Deva\"\n    | \"gon_Deva\"\n    | \"guj_Gujr\"\n    | \"hin_Deva\"\n    | \"hne_Deva\"\n    | \"kan_Knda\"\n    | \"kas_Arab\"\n    | \"kas_Deva\"\n    | \"kha_Latn\"\n    | \"lus_Latn\"\n    | \"mag_Deva\"\n    | \"mai_Deva\"\n    | \"mal_Mlym\"\n    | \"mar_Deva\"\n    | \"mni_Beng\"\n    | \"mni_Mtei\"\n    | \"npi_Deva\"\n    | \"ory_Orya\"\n    | \"pan_Guru\"\n    | \"san_Deva\"\n    | \"sat_Olck\"\n    | \"snd_Arab\"\n    | \"snd_Deva\"\n    | \"tam_Taml\"\n    | \"tel_Telu\"\n    | \"urd_Arab\"\n    | \"unr_Deva\";\n}\nexport interface Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Output {\n  /**\n   * Translated texts\n   */\n  translations: string[];\n}\nexport declare abstract class Base_Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B {\n  inputs: Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Input;\n  postProcessedOutputs: Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Output;\n}\nexport type Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Input =\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Async_Batch;\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_1;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_1 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Async_Batch {\n  requests: (\n    | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt_1\n    | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages_1\n  )[];\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Prompt_1 {\n  /**\n   * The input text prompt for the model to generate a response.\n   */\n  prompt: string;\n  /**\n   * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model.\n   */\n  lora?: string;\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_2;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_2 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages_1 {\n  /**\n   * An array of message objects representing the conversation history.\n   */\n  messages: {\n    /**\n     * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool').\n     */\n    role: string;\n    content:\n      | string\n      | {\n          /**\n           * Type of the content (text)\n           */\n          type?: string;\n          /**\n           * Text content\n           */\n          text?: string;\n        }[];\n  }[];\n  functions?: {\n    name: string;\n    code: string;\n  }[];\n  /**\n   * A list of tools available for the assistant to use.\n   */\n  tools?: (\n    | {\n        /**\n         * The name of the tool. More descriptive the better.\n         */\n        name: string;\n        /**\n         * A brief description of what the tool does.\n         */\n        description: string;\n        /**\n         * Schema defining the parameters accepted by the tool.\n         */\n        parameters: {\n          /**\n           * The type of the parameters object (usually 'object').\n           */\n          type: string;\n          /**\n           * List of required parameter names.\n           */\n          required?: string[];\n          /**\n           * Definitions of each parameter.\n           */\n          properties: {\n            [k: string]: {\n              /**\n               * The data type of the parameter.\n               */\n              type: string;\n              /**\n               * A description of the expected parameter.\n               */\n              description: string;\n            };\n          };\n        };\n      }\n    | {\n        /**\n         * Specifies the type of tool (e.g., 'function').\n         */\n        type: string;\n        /**\n         * Details of the function tool.\n         */\n        function: {\n          /**\n           * The name of the function.\n           */\n          name: string;\n          /**\n           * A brief description of what the function does.\n           */\n          description: string;\n          /**\n           * Schema defining the parameters accepted by the function.\n           */\n          parameters: {\n            /**\n             * The type of the parameters object (usually 'object').\n             */\n            type: string;\n            /**\n             * List of required parameter names.\n             */\n            required?: string[];\n            /**\n             * Definitions of each parameter.\n             */\n            properties: {\n              [k: string]: {\n                /**\n                 * The data type of the parameter.\n                 */\n                type: string;\n                /**\n                 * A description of the expected parameter.\n                 */\n                description: string;\n              };\n            };\n          };\n        };\n      }\n  )[];\n  response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_3;\n  /**\n   * If true, a chat template is not applied and you must adhere to the specific model's expected formatting.\n   */\n  raw?: boolean;\n  /**\n   * If true, the response will be streamed back incrementally using SSE, Server Sent Events.\n   */\n  stream?: boolean;\n  /**\n   * The maximum number of tokens to generate in the response.\n   */\n  max_tokens?: number;\n  /**\n   * Controls the randomness of the output; higher values produce more random results.\n   */\n  temperature?: number;\n  /**\n   * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses.\n   */\n  top_p?: number;\n  /**\n   * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises.\n   */\n  top_k?: number;\n  /**\n   * Random seed for reproducibility of the generation.\n   */\n  seed?: number;\n  /**\n   * Penalty for repeated tokens; higher values discourage repetition.\n   */\n  repetition_penalty?: number;\n  /**\n   * Decreases the likelihood of the model repeating the same lines verbatim.\n   */\n  frequency_penalty?: number;\n  /**\n   * Increases the likelihood of the model introducing new topics.\n   */\n  presence_penalty?: number;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_3 {\n  type?: \"json_object\" | \"json_schema\";\n  json_schema?: unknown;\n}\nexport type Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Output =\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Chat_Completion_Response\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Text_Completion_Response\n  | string\n  | Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_AsyncResponse;\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Chat_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"chat.completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index?: number;\n    /**\n     * The message generated by the model\n     */\n    message?: {\n      /**\n       * Role of the message author\n       */\n      role: string;\n      /**\n       * The content of the message\n       */\n      content: string;\n      /**\n       * Internal reasoning content (if available)\n       */\n      reasoning_content?: string;\n      /**\n       * Tool calls made by the assistant\n       */\n      tool_calls?: {\n        /**\n         * Unique identifier for the tool call\n         */\n        id: string;\n        /**\n         * Type of tool call\n         */\n        type: \"function\";\n        function: {\n          /**\n           * Name of the function to call\n           */\n          name: string;\n          /**\n           * JSON string of arguments for the function\n           */\n          arguments: string;\n        };\n      }[];\n    };\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason?: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n  /**\n   * Log probabilities for the prompt (if requested)\n   */\n  prompt_logprobs?: {} | null;\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Text_Completion_Response {\n  /**\n   * Unique identifier for the completion\n   */\n  id?: string;\n  /**\n   * Object type identifier\n   */\n  object?: \"text_completion\";\n  /**\n   * Unix timestamp of when the completion was created\n   */\n  created?: number;\n  /**\n   * Model used for the completion\n   */\n  model?: string;\n  /**\n   * List of completion choices\n   */\n  choices?: {\n    /**\n     * Index of the choice in the list\n     */\n    index: number;\n    /**\n     * The generated text completion\n     */\n    text: string;\n    /**\n     * Reason why the model stopped generating\n     */\n    finish_reason: string;\n    /**\n     * Stop reason (may be null)\n     */\n    stop_reason?: string | null;\n    /**\n     * Log probabilities (if requested)\n     */\n    logprobs?: {} | null;\n    /**\n     * Log probabilities for the prompt (if requested)\n     */\n    prompt_logprobs?: {} | null;\n  }[];\n  /**\n   * Usage statistics for the inference request\n   */\n  usage?: {\n    /**\n     * Total number of tokens in input\n     */\n    prompt_tokens?: number;\n    /**\n     * Total number of tokens in output\n     */\n    completion_tokens?: number;\n    /**\n     * Total number of input and output tokens\n     */\n    total_tokens?: number;\n  };\n}\nexport interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_AsyncResponse {\n  /**\n   * The async request id that can be used to obtain the results.\n   */\n  request_id?: string;\n}\nexport declare abstract class Base_Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It {\n  inputs: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Input;\n  postProcessedOutputs: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Output;\n}\nexport interface Ai_Cf_Pfnet_Plamo_Embedding_1B_Input {\n  /**\n   * Input text to embed. Can be a single string or a list of strings.\n   */\n  text: string | string[];\n}\nexport interface Ai_Cf_Pfnet_Plamo_Embedding_1B_Output {\n  /**\n   * Embedding vectors, where each vector is a list of floats.\n   */\n  data: number[][];\n  /**\n   * Shape of the embedding data as [number_of_embeddings, embedding_dimension].\n   *\n   * @minItems 2\n   * @maxItems 2\n   */\n  shape: [number, number];\n}\nexport declare abstract class Base_Ai_Cf_Pfnet_Plamo_Embedding_1B {\n  inputs: Ai_Cf_Pfnet_Plamo_Embedding_1B_Input;\n  postProcessedOutputs: Ai_Cf_Pfnet_Plamo_Embedding_1B_Output;\n}\nexport interface Ai_Cf_Deepgram_Flux_Input {\n  /**\n   * Encoding of the audio stream. Currently only supports raw signed little-endian 16-bit PCM.\n   */\n  encoding: \"linear16\";\n  /**\n   * Sample rate of the audio stream in Hz.\n   */\n  sample_rate: string;\n  /**\n   * End-of-turn confidence required to fire an eager end-of-turn event. When set, enables EagerEndOfTurn and TurnResumed events. Valid Values 0.3 - 0.9.\n   */\n  eager_eot_threshold?: string;\n  /**\n   * End-of-turn confidence required to finish a turn. Valid Values 0.5 - 0.9.\n   */\n  eot_threshold?: string;\n  /**\n   * A turn will be finished when this much time has passed after speech, regardless of EOT confidence.\n   */\n  eot_timeout_ms?: string;\n  /**\n   * Keyterm prompting can improve recognition of specialized terminology. Pass multiple keyterm query parameters to boost multiple keyterms.\n   */\n  keyterm?: string;\n  /**\n   * Opts out requests from the Deepgram Model Improvement Program. Refer to Deepgram Docs for pricing impacts before setting this to true. https://dpgr.am/deepgram-mip\n   */\n  mip_opt_out?: \"true\" | \"false\";\n  /**\n   * Label your requests for the purpose of identification during usage reporting\n   */\n  tag?: string;\n}\n/**\n * Output will be returned as websocket messages.\n */\nexport interface Ai_Cf_Deepgram_Flux_Output {\n  /**\n   * The unique identifier of the request (uuid)\n   */\n  request_id?: string;\n  /**\n   * Starts at 0 and increments for each message the server sends to the client.\n   */\n  sequence_id?: number;\n  /**\n   * The type of event being reported.\n   */\n  event?:\n    | \"Update\"\n    | \"StartOfTurn\"\n    | \"EagerEndOfTurn\"\n    | \"TurnResumed\"\n    | \"EndOfTurn\";\n  /**\n   * The index of the current turn\n   */\n  turn_index?: number;\n  /**\n   * Start time in seconds of the audio range that was transcribed\n   */\n  audio_window_start?: number;\n  /**\n   * End time in seconds of the audio range that was transcribed\n   */\n  audio_window_end?: number;\n  /**\n   * Text that was said over the course of the current turn\n   */\n  transcript?: string;\n  /**\n   * The words in the transcript\n   */\n  words?: {\n    /**\n     * The individual punctuated, properly-cased word from the transcript\n     */\n    word: string;\n    /**\n     * Confidence that this word was transcribed correctly\n     */\n    confidence: number;\n  }[];\n  /**\n   * Confidence that no more speech is coming in this turn\n   */\n  end_of_turn_confidence?: number;\n}\nexport declare abstract class Base_Ai_Cf_Deepgram_Flux {\n  inputs: Ai_Cf_Deepgram_Flux_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Flux_Output;\n}\nexport interface Ai_Cf_Deepgram_Aura_2_En_Input {\n  /**\n   * Speaker used to produce the audio.\n   */\n  speaker?:\n    | \"amalthea\"\n    | \"andromeda\"\n    | \"apollo\"\n    | \"arcas\"\n    | \"aries\"\n    | \"asteria\"\n    | \"athena\"\n    | \"atlas\"\n    | \"aurora\"\n    | \"callista\"\n    | \"cora\"\n    | \"cordelia\"\n    | \"delia\"\n    | \"draco\"\n    | \"electra\"\n    | \"harmonia\"\n    | \"helena\"\n    | \"hera\"\n    | \"hermes\"\n    | \"hyperion\"\n    | \"iris\"\n    | \"janus\"\n    | \"juno\"\n    | \"jupiter\"\n    | \"luna\"\n    | \"mars\"\n    | \"minerva\"\n    | \"neptune\"\n    | \"odysseus\"\n    | \"ophelia\"\n    | \"orion\"\n    | \"orpheus\"\n    | \"pandora\"\n    | \"phoebe\"\n    | \"pluto\"\n    | \"saturn\"\n    | \"thalia\"\n    | \"theia\"\n    | \"vesta\"\n    | \"zeus\";\n  /**\n   * Encoding of the output audio.\n   */\n  encoding?: \"linear16\" | \"flac\" | \"mulaw\" | \"alaw\" | \"mp3\" | \"opus\" | \"aac\";\n  /**\n   * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type..\n   */\n  container?: \"none\" | \"wav\" | \"ogg\";\n  /**\n   * The text content to be converted to speech\n   */\n  text: string;\n  /**\n   * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable\n   */\n  sample_rate?: number;\n  /**\n   * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type.\n   */\n  bit_rate?: number;\n}\n/**\n * The generated audio in MP3 format\n */\nexport type Ai_Cf_Deepgram_Aura_2_En_Output = string;\nexport declare abstract class Base_Ai_Cf_Deepgram_Aura_2_En {\n  inputs: Ai_Cf_Deepgram_Aura_2_En_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Aura_2_En_Output;\n}\nexport interface Ai_Cf_Deepgram_Aura_2_Es_Input {\n  /**\n   * Speaker used to produce the audio.\n   */\n  speaker?:\n    | \"sirio\"\n    | \"nestor\"\n    | \"carina\"\n    | \"celeste\"\n    | \"alvaro\"\n    | \"diana\"\n    | \"aquila\"\n    | \"selena\"\n    | \"estrella\"\n    | \"javier\";\n  /**\n   * Encoding of the output audio.\n   */\n  encoding?: \"linear16\" | \"flac\" | \"mulaw\" | \"alaw\" | \"mp3\" | \"opus\" | \"aac\";\n  /**\n   * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type..\n   */\n  container?: \"none\" | \"wav\" | \"ogg\";\n  /**\n   * The text content to be converted to speech\n   */\n  text: string;\n  /**\n   * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable\n   */\n  sample_rate?: number;\n  /**\n   * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type.\n   */\n  bit_rate?: number;\n}\n/**\n * The generated audio in MP3 format\n */\nexport type Ai_Cf_Deepgram_Aura_2_Es_Output = string;\nexport declare abstract class Base_Ai_Cf_Deepgram_Aura_2_Es {\n  inputs: Ai_Cf_Deepgram_Aura_2_Es_Input;\n  postProcessedOutputs: Ai_Cf_Deepgram_Aura_2_Es_Output;\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Input {\n  multipart: {\n    body?: object;\n    contentType?: string;\n  };\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Output {\n  /**\n   * Generated image as Base64 string.\n   */\n  image?: string;\n}\nexport declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Dev {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Output;\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Input {\n  multipart: {\n    body?: object;\n    contentType?: string;\n  };\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Output {\n  /**\n   * Generated image as Base64 string.\n   */\n  image?: string;\n}\nexport declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Output;\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Input {\n  multipart: {\n    body?: object;\n    contentType?: string;\n  };\n}\nexport interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Output {\n  /**\n   * Generated image as Base64 string.\n   */\n  image?: string;\n}\nexport declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B {\n  inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Input;\n  postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Output;\n}\nexport declare abstract class Base_Ai_Cf_Zai_Org_Glm_4_7_Flash {\n  inputs: ChatCompletionsInput;\n  postProcessedOutputs: ChatCompletionsOutput;\n}\nexport declare abstract class Base_Ai_Cf_Moonshotai_Kimi_K2_5 {\n  inputs: ChatCompletionsInput;\n  postProcessedOutputs: ChatCompletionsOutput;\n}\nexport declare abstract class Base_Ai_Cf_Nvidia_Nemotron_3_120B_A12B {\n  inputs: ChatCompletionsInput;\n  postProcessedOutputs: ChatCompletionsOutput;\n}\nexport interface AiModels {\n  \"@cf/huggingface/distilbert-sst-2-int8\": BaseAiTextClassification;\n  \"@cf/stabilityai/stable-diffusion-xl-base-1.0\": BaseAiTextToImage;\n  \"@cf/runwayml/stable-diffusion-v1-5-inpainting\": BaseAiTextToImage;\n  \"@cf/runwayml/stable-diffusion-v1-5-img2img\": BaseAiTextToImage;\n  \"@cf/lykon/dreamshaper-8-lcm\": BaseAiTextToImage;\n  \"@cf/bytedance/stable-diffusion-xl-lightning\": BaseAiTextToImage;\n  \"@cf/myshell-ai/melotts\": BaseAiTextToSpeech;\n  \"@cf/google/embeddinggemma-300m\": BaseAiTextEmbeddings;\n  \"@cf/microsoft/resnet-50\": BaseAiImageClassification;\n  \"@cf/meta/llama-2-7b-chat-int8\": BaseAiTextGeneration;\n  \"@cf/mistral/mistral-7b-instruct-v0.1\": BaseAiTextGeneration;\n  \"@cf/meta/llama-2-7b-chat-fp16\": BaseAiTextGeneration;\n  \"@hf/thebloke/llama-2-13b-chat-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/mistral-7b-instruct-v0.1-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/zephyr-7b-beta-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/openhermes-2.5-mistral-7b-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/neural-chat-7b-v3-1-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/deepseek-coder-6.7b-base-awq\": BaseAiTextGeneration;\n  \"@hf/thebloke/deepseek-coder-6.7b-instruct-awq\": BaseAiTextGeneration;\n  \"@cf/deepseek-ai/deepseek-math-7b-instruct\": BaseAiTextGeneration;\n  \"@cf/defog/sqlcoder-7b-2\": BaseAiTextGeneration;\n  \"@cf/openchat/openchat-3.5-0106\": BaseAiTextGeneration;\n  \"@cf/tiiuae/falcon-7b-instruct\": BaseAiTextGeneration;\n  \"@cf/thebloke/discolm-german-7b-v1-awq\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-0.5b-chat\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-7b-chat-awq\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-14b-chat-awq\": BaseAiTextGeneration;\n  \"@cf/tinyllama/tinyllama-1.1b-chat-v1.0\": BaseAiTextGeneration;\n  \"@cf/microsoft/phi-2\": BaseAiTextGeneration;\n  \"@cf/qwen/qwen1.5-1.8b-chat\": BaseAiTextGeneration;\n  \"@cf/mistral/mistral-7b-instruct-v0.2-lora\": BaseAiTextGeneration;\n  \"@hf/nousresearch/hermes-2-pro-mistral-7b\": BaseAiTextGeneration;\n  \"@hf/nexusflow/starling-lm-7b-beta\": BaseAiTextGeneration;\n  \"@hf/google/gemma-7b-it\": BaseAiTextGeneration;\n  \"@cf/meta-llama/llama-2-7b-chat-hf-lora\": BaseAiTextGeneration;\n  \"@cf/google/gemma-2b-it-lora\": BaseAiTextGeneration;\n  \"@cf/google/gemma-7b-it-lora\": BaseAiTextGeneration;\n  \"@hf/mistral/mistral-7b-instruct-v0.2\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3-8b-instruct\": BaseAiTextGeneration;\n  \"@cf/fblgit/una-cybertron-7b-v2-bf16\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3-8b-instruct-awq\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.1-8b-instruct-fp8\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.1-8b-instruct-awq\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.2-3b-instruct\": BaseAiTextGeneration;\n  \"@cf/meta/llama-3.2-1b-instruct\": BaseAiTextGeneration;\n  \"@cf/deepseek-ai/deepseek-r1-distill-qwen-32b\": BaseAiTextGeneration;\n  \"@cf/ibm-granite/granite-4.0-h-micro\": BaseAiTextGeneration;\n  \"@cf/facebook/bart-large-cnn\": BaseAiSummarization;\n  \"@cf/llava-hf/llava-1.5-7b-hf\": BaseAiImageToText;\n  \"@cf/baai/bge-base-en-v1.5\": Base_Ai_Cf_Baai_Bge_Base_En_V1_5;\n  \"@cf/openai/whisper\": Base_Ai_Cf_Openai_Whisper;\n  \"@cf/meta/m2m100-1.2b\": Base_Ai_Cf_Meta_M2M100_1_2B;\n  \"@cf/baai/bge-small-en-v1.5\": Base_Ai_Cf_Baai_Bge_Small_En_V1_5;\n  \"@cf/baai/bge-large-en-v1.5\": Base_Ai_Cf_Baai_Bge_Large_En_V1_5;\n  \"@cf/unum/uform-gen2-qwen-500m\": Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M;\n  \"@cf/openai/whisper-tiny-en\": Base_Ai_Cf_Openai_Whisper_Tiny_En;\n  \"@cf/openai/whisper-large-v3-turbo\": Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo;\n  \"@cf/baai/bge-m3\": Base_Ai_Cf_Baai_Bge_M3;\n  \"@cf/black-forest-labs/flux-1-schnell\": Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell;\n  \"@cf/meta/llama-3.2-11b-vision-instruct\": Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct;\n  \"@cf/meta/llama-3.3-70b-instruct-fp8-fast\": Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast;\n  \"@cf/meta/llama-guard-3-8b\": Base_Ai_Cf_Meta_Llama_Guard_3_8B;\n  \"@cf/baai/bge-reranker-base\": Base_Ai_Cf_Baai_Bge_Reranker_Base;\n  \"@cf/qwen/qwen2.5-coder-32b-instruct\": Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct;\n  \"@cf/qwen/qwq-32b\": Base_Ai_Cf_Qwen_Qwq_32B;\n  \"@cf/mistralai/mistral-small-3.1-24b-instruct\": Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct;\n  \"@cf/google/gemma-3-12b-it\": Base_Ai_Cf_Google_Gemma_3_12B_It;\n  \"@cf/meta/llama-4-scout-17b-16e-instruct\": Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct;\n  \"@cf/qwen/qwen3-30b-a3b-fp8\": Base_Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8;\n  \"@cf/deepgram/nova-3\": Base_Ai_Cf_Deepgram_Nova_3;\n  \"@cf/qwen/qwen3-embedding-0.6b\": Base_Ai_Cf_Qwen_Qwen3_Embedding_0_6B;\n  \"@cf/pipecat-ai/smart-turn-v2\": Base_Ai_Cf_Pipecat_Ai_Smart_Turn_V2;\n  \"@cf/openai/gpt-oss-120b\": Base_Ai_Cf_Openai_Gpt_Oss_120B;\n  \"@cf/openai/gpt-oss-20b\": Base_Ai_Cf_Openai_Gpt_Oss_20B;\n  \"@cf/leonardo/phoenix-1.0\": Base_Ai_Cf_Leonardo_Phoenix_1_0;\n  \"@cf/leonardo/lucid-origin\": Base_Ai_Cf_Leonardo_Lucid_Origin;\n  \"@cf/deepgram/aura-1\": Base_Ai_Cf_Deepgram_Aura_1;\n  \"@cf/ai4bharat/indictrans2-en-indic-1B\": Base_Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B;\n  \"@cf/aisingapore/gemma-sea-lion-v4-27b-it\": Base_Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It;\n  \"@cf/pfnet/plamo-embedding-1b\": Base_Ai_Cf_Pfnet_Plamo_Embedding_1B;\n  \"@cf/deepgram/flux\": Base_Ai_Cf_Deepgram_Flux;\n  \"@cf/deepgram/aura-2-en\": Base_Ai_Cf_Deepgram_Aura_2_En;\n  \"@cf/deepgram/aura-2-es\": Base_Ai_Cf_Deepgram_Aura_2_Es;\n  \"@cf/black-forest-labs/flux-2-dev\": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Dev;\n  \"@cf/black-forest-labs/flux-2-klein-4b\": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B;\n  \"@cf/black-forest-labs/flux-2-klein-9b\": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B;\n  \"@cf/zai-org/glm-4.7-flash\": Base_Ai_Cf_Zai_Org_Glm_4_7_Flash;\n  \"@cf/moonshotai/kimi-k2.5\": Base_Ai_Cf_Moonshotai_Kimi_K2_5;\n  \"@cf/nvidia/nemotron-3-120b-a12b\": Base_Ai_Cf_Nvidia_Nemotron_3_120B_A12B;\n}\nexport type AiOptions = {\n  /**\n   * Send requests as an asynchronous batch job, only works for supported models\n   * https://developers.cloudflare.com/workers-ai/features/batch-api\n   */\n  queueRequest?: boolean;\n  /**\n   * Establish websocket connections, only works for supported models\n   */\n  websocket?: boolean;\n  /**\n   * Tag your requests to group and view them in Cloudflare dashboard.\n   *\n   * Rules:\n   * Tags must only contain letters, numbers, and the symbols: : - . / @\n   * Each tag can have maximum 50 characters.\n   * Maximum 5 tags are allowed each request.\n   * Duplicate tags will removed.\n   */\n  tags?: string[];\n  gateway?: GatewayOptions;\n  returnRawResponse?: boolean;\n  prefix?: string;\n  extraHeaders?: object;\n};\nexport type AiModelsSearchParams = {\n  author?: string;\n  hide_experimental?: boolean;\n  page?: number;\n  per_page?: number;\n  search?: string;\n  source?: number;\n  task?: string;\n};\nexport type AiModelsSearchObject = {\n  id: string;\n  source: number;\n  name: string;\n  description: string;\n  task: {\n    id: string;\n    name: string;\n    description: string;\n  };\n  tags: string[];\n  properties: {\n    property_id: string;\n    value: string;\n  }[];\n};\nexport type ChatCompletionsBase = XOR<\n  ChatCompletionsPromptInput,\n  ChatCompletionsMessagesInput\n>;\nexport type ChatCompletionsInput = XOR<\n  ChatCompletionsBase,\n  {\n    requests: ChatCompletionsBase[];\n  }\n>;\nexport interface InferenceUpstreamError extends Error {}\nexport interface AiInternalError extends Error {}\nexport type AiModelListType = Record<string, any>;\nexport declare abstract class Ai<\n  AiModelList extends AiModelListType = AiModels,\n> {\n  aiGatewayLogId: string | null;\n  gateway(gatewayId: string): AiGateway;\n  /**\n   * Access the AI Search API for managing AI-powered search instances.\n   *\n   * This is the new API that replaces AutoRAG with better namespace separation:\n   * - Account-level operations: `list()`, `create()`\n   * - Instance-level operations: `get(id).search()`, `get(id).chatCompletions()`, `get(id).delete()`\n   *\n   * @example\n   * ```typescript\n   * // List all AI Search instances\n   * const instances = await env.AI.aiSearch.list();\n   *\n   * // Search an instance\n   * const results = await env.AI.aiSearch.get('my-search').search({\n   *   messages: [{ role: 'user', content: 'What is the policy?' }],\n   *   ai_search_options: {\n   *     retrieval: { max_num_results: 10 }\n   *   }\n   * });\n   *\n   * // Generate chat completions with AI Search context\n   * const response = await env.AI.aiSearch.get('my-search').chatCompletions({\n   *   messages: [{ role: 'user', content: 'What is the policy?' }],\n   *   model: '@cf/meta/llama-3.3-70b-instruct-fp8-fast'\n   * });\n   * ```\n   */\n  aiSearch(): AiSearchAccountService;\n  /**\n   * @deprecated AutoRAG has been replaced by AI Search.\n   * Use `env.AI.aiSearch` instead for better API design and new features.\n   *\n   * Migration guide:\n   * - `env.AI.autorag().list()` → `env.AI.aiSearch.list()`\n   * - `env.AI.autorag('id').search({ query: '...' })` → `env.AI.aiSearch.get('id').search({ messages: [{ role: 'user', content: '...' }] })`\n   * - `env.AI.autorag('id').aiSearch(...)` → `env.AI.aiSearch.get('id').chatCompletions(...)`\n   *\n   * Note: The old API continues to work for backwards compatibility, but new projects should use AI Search.\n   *\n   * @see AiSearchAccountService\n   * @param autoragId Optional instance ID (omit for account-level operations)\n   */\n  autorag(autoragId: string): AutoRAG;\n  run<\n    Name extends keyof AiModelList,\n    Options extends AiOptions,\n    InputOptions extends AiModelList[Name][\"inputs\"],\n  >(\n    model: Name,\n    inputs: InputOptions,\n    options?: Options,\n  ): Promise<\n    Options extends\n      | {\n          returnRawResponse: true;\n        }\n      | {\n          websocket: true;\n        }\n      ? Response\n      : InputOptions extends {\n            stream: true;\n          }\n        ? ReadableStream\n        : AiModelList[Name][\"postProcessedOutputs\"]\n  >;\n  models(params?: AiModelsSearchParams): Promise<AiModelsSearchObject[]>;\n  toMarkdown(): ToMarkdownService;\n  toMarkdown(\n    files: MarkdownDocument[],\n    options?: ConversionRequestOptions,\n  ): Promise<ConversionResponse[]>;\n  toMarkdown(\n    files: MarkdownDocument,\n    options?: ConversionRequestOptions,\n  ): Promise<ConversionResponse>;\n}\nexport type GatewayRetries = {\n  maxAttempts?: 1 | 2 | 3 | 4 | 5;\n  retryDelayMs?: number;\n  backoff?: \"constant\" | \"linear\" | \"exponential\";\n};\nexport type GatewayOptions = {\n  id: string;\n  cacheKey?: string;\n  cacheTtl?: number;\n  skipCache?: boolean;\n  metadata?: Record<string, number | string | boolean | null | bigint>;\n  collectLog?: boolean;\n  eventId?: string;\n  requestTimeoutMs?: number;\n  retries?: GatewayRetries;\n};\nexport type UniversalGatewayOptions = Exclude<GatewayOptions, \"id\"> & {\n  /**\n   ** @deprecated\n   */\n  id?: string;\n};\nexport type AiGatewayPatchLog = {\n  score?: number | null;\n  feedback?: -1 | 1 | null;\n  metadata?: Record<string, number | string | boolean | null | bigint> | null;\n};\nexport type AiGatewayLog = {\n  id: string;\n  provider: string;\n  model: string;\n  model_type?: string;\n  path: string;\n  duration: number;\n  request_type?: string;\n  request_content_type?: string;\n  status_code: number;\n  response_content_type?: string;\n  success: boolean;\n  cached: boolean;\n  tokens_in?: number;\n  tokens_out?: number;\n  metadata?: Record<string, number | string | boolean | null | bigint>;\n  step?: number;\n  cost?: number;\n  custom_cost?: boolean;\n  request_size: number;\n  request_head?: string;\n  request_head_complete: boolean;\n  response_size: number;\n  response_head?: string;\n  response_head_complete: boolean;\n  created_at: Date;\n};\nexport type AIGatewayProviders =\n  | \"workers-ai\"\n  | \"anthropic\"\n  | \"aws-bedrock\"\n  | \"azure-openai\"\n  | \"google-vertex-ai\"\n  | \"huggingface\"\n  | \"openai\"\n  | \"perplexity-ai\"\n  | \"replicate\"\n  | \"groq\"\n  | \"cohere\"\n  | \"google-ai-studio\"\n  | \"mistral\"\n  | \"grok\"\n  | \"openrouter\"\n  | \"deepseek\"\n  | \"cerebras\"\n  | \"cartesia\"\n  | \"elevenlabs\"\n  | \"adobe-firefly\";\nexport type AIGatewayHeaders = {\n  \"cf-aig-metadata\":\n    | Record<string, number | string | boolean | null | bigint>\n    | string;\n  \"cf-aig-custom-cost\":\n    | {\n        per_token_in?: number;\n        per_token_out?: number;\n      }\n    | {\n        total_cost?: number;\n      }\n    | string;\n  \"cf-aig-cache-ttl\": number | string;\n  \"cf-aig-skip-cache\": boolean | string;\n  \"cf-aig-cache-key\": string;\n  \"cf-aig-event-id\": string;\n  \"cf-aig-request-timeout\": number | string;\n  \"cf-aig-max-attempts\": number | string;\n  \"cf-aig-retry-delay\": number | string;\n  \"cf-aig-backoff\": string;\n  \"cf-aig-collect-log\": boolean | string;\n  Authorization: string;\n  \"Content-Type\": string;\n  [key: string]: string | number | boolean | object;\n};\nexport type AIGatewayUniversalRequest = {\n  provider: AIGatewayProviders | string; // eslint-disable-line\n  endpoint: string;\n  headers: Partial<AIGatewayHeaders>;\n  query: unknown;\n};\nexport interface AiGatewayInternalError extends Error {}\nexport interface AiGatewayLogNotFound extends Error {}\nexport declare abstract class AiGateway {\n  patchLog(logId: string, data: AiGatewayPatchLog): Promise<void>;\n  getLog(logId: string): Promise<AiGatewayLog>;\n  run(\n    data: AIGatewayUniversalRequest | AIGatewayUniversalRequest[],\n    options?: {\n      gateway?: UniversalGatewayOptions;\n      extraHeaders?: object;\n    },\n  ): Promise<Response>;\n  getUrl(provider?: AIGatewayProviders | string): Promise<string>; // eslint-disable-line\n}\n/**\n * @deprecated AutoRAG has been replaced by AI Search. Use AiSearchInternalError instead.\n * @see AiSearchInternalError\n */\nexport interface AutoRAGInternalError extends Error {}\n/**\n * @deprecated AutoRAG has been replaced by AI Search. Use AiSearchNotFoundError instead.\n * @see AiSearchNotFoundError\n */\nexport interface AutoRAGNotFoundError extends Error {}\n/**\n * @deprecated This error type is no longer used in the AI Search API.\n */\nexport interface AutoRAGUnauthorizedError extends Error {}\n/**\n * @deprecated AutoRAG has been replaced by AI Search. Use AiSearchNameNotSetError instead.\n * @see AiSearchNameNotSetError\n */\nexport interface AutoRAGNameNotSetError extends Error {}\nexport type ComparisonFilter = {\n  key: string;\n  type: \"eq\" | \"ne\" | \"gt\" | \"gte\" | \"lt\" | \"lte\";\n  value: string | number | boolean;\n};\nexport type CompoundFilter = {\n  type: \"and\" | \"or\";\n  filters: ComparisonFilter[];\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchSearchRequest with the new API instead.\n * @see AiSearchSearchRequest\n */\nexport type AutoRagSearchRequest = {\n  query: string;\n  filters?: CompoundFilter | ComparisonFilter;\n  max_num_results?: number;\n  ranking_options?: {\n    ranker?: string;\n    score_threshold?: number;\n  };\n  reranking?: {\n    enabled?: boolean;\n    model?: string;\n  };\n  rewrite_query?: boolean;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchChatCompletionsRequest with the new API instead.\n * @see AiSearchChatCompletionsRequest\n */\nexport type AutoRagAiSearchRequest = AutoRagSearchRequest & {\n  stream?: boolean;\n  system_prompt?: string;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchChatCompletionsRequest with stream: true instead.\n * @see AiSearchChatCompletionsRequest\n */\nexport type AutoRagAiSearchRequestStreaming = Omit<\n  AutoRagAiSearchRequest,\n  \"stream\"\n> & {\n  stream: true;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchSearchResponse with the new API instead.\n * @see AiSearchSearchResponse\n */\nexport type AutoRagSearchResponse = {\n  object: \"vector_store.search_results.page\";\n  search_query: string;\n  data: {\n    file_id: string;\n    filename: string;\n    score: number;\n    attributes: Record<string, string | number | boolean | null>;\n    content: {\n      type: \"text\";\n      text: string;\n    }[];\n  }[];\n  has_more: boolean;\n  next_page: string | null;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use AiSearchListResponse with the new API instead.\n * @see AiSearchListResponse\n */\nexport type AutoRagListResponse = {\n  id: string;\n  enable: boolean;\n  type: string;\n  source: string;\n  vectorize_name: string;\n  paused: boolean;\n  status: string;\n}[];\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * The new API returns different response formats for chat completions.\n */\nexport type AutoRagAiSearchResponse = AutoRagSearchResponse & {\n  response: string;\n};\n/**\n * @deprecated AutoRAG has been replaced by AI Search.\n * Use the new AI Search API instead: `env.AI.aiSearch`\n *\n * Migration guide:\n * - `env.AI.autorag().list()` → `env.AI.aiSearch.list()`\n * - `env.AI.autorag('id').search(...)` → `env.AI.aiSearch.get('id').search(...)`\n * - `env.AI.autorag('id').aiSearch(...)` → `env.AI.aiSearch.get('id').chatCompletions(...)`\n *\n * @see AiSearchAccountService\n * @see AiSearchInstanceService\n */\nexport declare abstract class AutoRAG {\n  /**\n   * @deprecated Use `env.AI.aiSearch.list()` instead.\n   * @see AiSearchAccountService.list\n   */\n  list(): Promise<AutoRagListResponse>;\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).search(...)` instead.\n   * Note: The new API uses a messages array instead of a query string.\n   * @see AiSearchInstanceService.search\n   */\n  search(params: AutoRagSearchRequest): Promise<AutoRagSearchResponse>;\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).chatCompletions(...)` instead.\n   * @see AiSearchInstanceService.chatCompletions\n   */\n  aiSearch(params: AutoRagAiSearchRequestStreaming): Promise<Response>;\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).chatCompletions(...)` instead.\n   * @see AiSearchInstanceService.chatCompletions\n   */\n  aiSearch(params: AutoRagAiSearchRequest): Promise<AutoRagAiSearchResponse>;\n  /**\n   * @deprecated Use `env.AI.aiSearch.get(id).chatCompletions(...)` instead.\n   * @see AiSearchInstanceService.chatCompletions\n   */\n  aiSearch(\n    params: AutoRagAiSearchRequest,\n  ): Promise<AutoRagAiSearchResponse | Response>;\n}\nexport interface BasicImageTransformations {\n  /**\n   * Maximum width in image pixels. The value must be an integer.\n   */\n  width?: number;\n  /**\n   * Maximum height in image pixels. The value must be an integer.\n   */\n  height?: number;\n  /**\n   * Resizing mode as a string. It affects interpretation of width and height\n   * options:\n   *  - scale-down: Similar to contain, but the image is never enlarged. If\n   *    the image is larger than given width or height, it will be resized.\n   *    Otherwise its original size will be kept.\n   *  - contain: Resizes to maximum size that fits within the given width and\n   *    height. If only a single dimension is given (e.g. only width), the\n   *    image will be shrunk or enlarged to exactly match that dimension.\n   *    Aspect ratio is always preserved.\n   *  - cover: Resizes (shrinks or enlarges) to fill the entire area of width\n   *    and height. If the image has an aspect ratio different from the ratio\n   *    of width and height, it will be cropped to fit.\n   *  - crop: The image will be shrunk and cropped to fit within the area\n   *    specified by width and height. The image will not be enlarged. For images\n   *    smaller than the given dimensions it's the same as scale-down. For\n   *    images larger than the given dimensions, it's the same as cover.\n   *    See also trim.\n   *  - pad: Resizes to the maximum size that fits within the given width and\n   *    height, and then fills the remaining area with a background color\n   *    (white by default). Use of this mode is not recommended, as the same\n   *    effect can be more efficiently achieved with the contain mode and the\n   *    CSS object-fit: contain property.\n   *  - squeeze: Stretches and deforms to the width and height given, even if it\n   *    breaks aspect ratio\n   */\n  fit?: \"scale-down\" | \"contain\" | \"cover\" | \"crop\" | \"pad\" | \"squeeze\";\n  /**\n   * Image segmentation using artificial intelligence models. Sets pixels not\n   * within selected segment area to transparent e.g \"foreground\" sets every\n   * background pixel as transparent.\n   */\n  segment?: \"foreground\";\n  /**\n   * When cropping with fit: \"cover\", this defines the side or point that should\n   * be left uncropped. The value is either a string\n   * \"left\", \"right\", \"top\", \"bottom\", \"auto\", or \"center\" (the default),\n   * or an object {x, y} containing focal point coordinates in the original\n   * image expressed as fractions ranging from 0.0 (top or left) to 1.0\n   * (bottom or right), 0.5 being the center. {fit: \"cover\", gravity: \"top\"} will\n   * crop bottom or left and right sides as necessary, but won’t crop anything\n   * from the top. {fit: \"cover\", gravity: {x:0.5, y:0.2}} will crop each side to\n   * preserve as much as possible around a point at 20% of the height of the\n   * source image.\n   */\n  gravity?:\n    | \"face\"\n    | \"left\"\n    | \"right\"\n    | \"top\"\n    | \"bottom\"\n    | \"center\"\n    | \"auto\"\n    | \"entropy\"\n    | BasicImageTransformationsGravityCoordinates;\n  /**\n   * Background color to add underneath the image. Applies only to images with\n   * transparency (such as PNG). Accepts any CSS color (#RRGGBB, rgba(…),\n   * hsl(…), etc.)\n   */\n  background?: string;\n  /**\n   * Number of degrees (90, 180, 270) to rotate the image by. width and height\n   * options refer to axes after rotation.\n   */\n  rotate?: 0 | 90 | 180 | 270 | 360;\n}\nexport interface BasicImageTransformationsGravityCoordinates {\n  x?: number;\n  y?: number;\n  mode?: \"remainder\" | \"box-center\";\n}\n/**\n * In addition to the properties you can set in the RequestInit dict\n * that you pass as an argument to the Request constructor, you can\n * set certain properties of a `cf` object to control how Cloudflare\n * features are applied to that new Request.\n *\n * Note: Currently, these properties cannot be tested in the\n * playground.\n */\nexport interface RequestInitCfProperties extends Record<string, unknown> {\n  cacheEverything?: boolean;\n  /**\n   * A request's cache key is what determines if two requests are\n   * \"the same\" for caching purposes. If a request has the same cache key\n   * as some previous request, then we can serve the same cached response for\n   * both. (e.g. 'some-key')\n   *\n   * Only available for Enterprise customers.\n   */\n  cacheKey?: string;\n  /**\n   * This allows you to append additional Cache-Tag response headers\n   * to the origin response without modifications to the origin server.\n   * This will allow for greater control over the Purge by Cache Tag feature\n   * utilizing changes only in the Workers process.\n   *\n   * Only available for Enterprise customers.\n   */\n  cacheTags?: string[];\n  /**\n   * Force response to be cached for a given number of seconds. (e.g. 300)\n   */\n  cacheTtl?: number;\n  /**\n   * Force response to be cached for a given number of seconds based on the Origin status code.\n   * (e.g. { '200-299': 86400, '404': 1, '500-599': 0 })\n   */\n  cacheTtlByStatus?: Record<string, number>;\n  scrapeShield?: boolean;\n  apps?: boolean;\n  image?: RequestInitCfPropertiesImage;\n  minify?: RequestInitCfPropertiesImageMinify;\n  mirage?: boolean;\n  polish?: \"lossy\" | \"lossless\" | \"off\";\n  r2?: RequestInitCfPropertiesR2;\n  /**\n   * Redirects the request to an alternate origin server. You can use this,\n   * for example, to implement load balancing across several origins.\n   * (e.g.us-east.example.com)\n   *\n   * Note - For security reasons, the hostname set in resolveOverride must\n   * be proxied on the same Cloudflare zone of the incoming request.\n   * Otherwise, the setting is ignored. CNAME hosts are allowed, so to\n   * resolve to a host under a different domain or a DNS only domain first\n   * declare a CNAME record within your own zone’s DNS mapping to the\n   * external hostname, set proxy on Cloudflare, then set resolveOverride\n   * to point to that CNAME record.\n   */\n  resolveOverride?: string;\n}\nexport interface RequestInitCfPropertiesImageDraw extends BasicImageTransformations {\n  /**\n   * Absolute URL of the image file to use for the drawing. It can be any of\n   * the supported file formats. For drawing of watermarks or non-rectangular\n   * overlays we recommend using PNG or WebP images.\n   */\n  url: string;\n  /**\n   * Floating-point number between 0 (transparent) and 1 (opaque).\n   * For example, opacity: 0.5 makes overlay semitransparent.\n   */\n  opacity?: number;\n  /**\n   * - If set to true, the overlay image will be tiled to cover the entire\n   *   area. This is useful for stock-photo-like watermarks.\n   * - If set to \"x\", the overlay image will be tiled horizontally only\n   *   (form a line).\n   * - If set to \"y\", the overlay image will be tiled vertically only\n   *   (form a line).\n   */\n  repeat?: true | \"x\" | \"y\";\n  /**\n   * Position of the overlay image relative to a given edge. Each property is\n   * an offset in pixels. 0 aligns exactly to the edge. For example, left: 10\n   * positions left side of the overlay 10 pixels from the left edge of the\n   * image it's drawn over. bottom: 0 aligns bottom of the overlay with bottom\n   * of the background image.\n   *\n   * Setting both left & right, or both top & bottom is an error.\n   *\n   * If no position is specified, the image will be centered.\n   */\n  top?: number;\n  left?: number;\n  bottom?: number;\n  right?: number;\n}\nexport interface RequestInitCfPropertiesImage extends BasicImageTransformations {\n  /**\n   * Device Pixel Ratio. Default 1. Multiplier for width/height that makes it\n   * easier to specify higher-DPI sizes in <img srcset>.\n   */\n  dpr?: number;\n  /**\n   * Allows you to trim your image. Takes dpr into account and is performed before\n   * resizing or rotation.\n   *\n   * It can be used as:\n   * - left, top, right, bottom - it will specify the number of pixels to cut\n   *   off each side\n   * - width, height - the width/height you'd like to end up with - can be used\n   *   in combination with the properties above\n   * - border - this will automatically trim the surroundings of an image based on\n   *   it's color. It consists of three properties:\n   *    - color: rgb or hex representation of the color you wish to trim (todo: verify the rgba bit)\n   *    - tolerance: difference from color to treat as color\n   *    - keep: the number of pixels of border to keep\n   */\n  trim?:\n    | \"border\"\n    | {\n        top?: number;\n        bottom?: number;\n        left?: number;\n        right?: number;\n        width?: number;\n        height?: number;\n        border?:\n          | boolean\n          | {\n              color?: string;\n              tolerance?: number;\n              keep?: number;\n            };\n      };\n  /**\n   * Quality setting from 1-100 (useful values are in 60-90 range). Lower values\n   * make images look worse, but load faster. The default is 85. It applies only\n   * to JPEG and WebP images. It doesn’t have any effect on PNG.\n   */\n  quality?: number | \"low\" | \"medium-low\" | \"medium-high\" | \"high\";\n  /**\n   * Output format to generate. It can be:\n   *  - avif: generate images in AVIF format.\n   *  - webp: generate images in Google WebP format. Set quality to 100 to get\n   *    the WebP-lossless format.\n   *  - json: instead of generating an image, outputs information about the\n   *    image, in JSON format. The JSON object will contain image size\n   *    (before and after resizing), source image’s MIME type, file size, etc.\n   * - jpeg: generate images in JPEG format.\n   * - png: generate images in PNG format.\n   */\n  format?:\n    | \"avif\"\n    | \"webp\"\n    | \"json\"\n    | \"jpeg\"\n    | \"png\"\n    | \"baseline-jpeg\"\n    | \"png-force\"\n    | \"svg\";\n  /**\n   * Whether to preserve animation frames from input files. Default is true.\n   * Setting it to false reduces animations to still images. This setting is\n   * recommended when enlarging images or processing arbitrary user content,\n   * because large GIF animations can weigh tens or even hundreds of megabytes.\n   * It is also useful to set anim:false when using format:\"json\" to get the\n   * response quicker without the number of frames.\n   */\n  anim?: boolean;\n  /**\n   * What EXIF data should be preserved in the output image. Note that EXIF\n   * rotation and embedded color profiles are always applied (\"baked in\" into\n   * the image), and aren't affected by this option. Note that if the Polish\n   * feature is enabled, all metadata may have been removed already and this\n   * option may have no effect.\n   *  - keep: Preserve most of EXIF metadata, including GPS location if there's\n   *    any.\n   *  - copyright: Only keep the copyright tag, and discard everything else.\n   *    This is the default behavior for JPEG files.\n   *  - none: Discard all invisible EXIF metadata. Currently WebP and PNG\n   *    output formats always discard metadata.\n   */\n  metadata?: \"keep\" | \"copyright\" | \"none\";\n  /**\n   * Strength of sharpening filter to apply to the image. Floating-point\n   * number between 0 (no sharpening, default) and 10 (maximum). 1.0 is a\n   * recommended value for downscaled images.\n   */\n  sharpen?: number;\n  /**\n   * Radius of a blur filter (approximate gaussian). Maximum supported radius\n   * is 250.\n   */\n  blur?: number;\n  /**\n   * Overlays are drawn in the order they appear in the array (last array\n   * entry is the topmost layer).\n   */\n  draw?: RequestInitCfPropertiesImageDraw[];\n  /**\n   * Fetching image from authenticated origin. Setting this property will\n   * pass authentication headers (Authorization, Cookie, etc.) through to\n   * the origin.\n   */\n  \"origin-auth\"?: \"share-publicly\";\n  /**\n   * Adds a border around the image. The border is added after resizing. Border\n   * width takes dpr into account, and can be specified either using a single\n   * width property, or individually for each side.\n   */\n  border?:\n    | {\n        color: string;\n        width: number;\n      }\n    | {\n        color: string;\n        top: number;\n        right: number;\n        bottom: number;\n        left: number;\n      };\n  /**\n   * Increase brightness by a factor. A value of 1.0 equals no change, a value\n   * of 0.5 equals half brightness, and a value of 2.0 equals twice as bright.\n   * 0 is ignored.\n   */\n  brightness?: number;\n  /**\n   * Increase contrast by a factor. A value of 1.0 equals no change, a value of\n   * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is\n   * ignored.\n   */\n  contrast?: number;\n  /**\n   * Increase exposure by a factor. A value of 1.0 equals no change, a value of\n   * 0.5 darkens the image, and a value of 2.0 lightens the image. 0 is ignored.\n   */\n  gamma?: number;\n  /**\n   * Increase contrast by a factor. A value of 1.0 equals no change, a value of\n   * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is\n   * ignored.\n   */\n  saturation?: number;\n  /**\n   * Flips the images horizontally, vertically, or both. Flipping is applied before\n   * rotation, so if you apply flip=h,rotate=90 then the image will be flipped\n   * horizontally, then rotated by 90 degrees.\n   */\n  flip?: \"h\" | \"v\" | \"hv\";\n  /**\n   * Slightly reduces latency on a cache miss by selecting a\n   * quickest-to-compress file format, at a cost of increased file size and\n   * lower image quality. It will usually override the format option and choose\n   * JPEG over WebP or AVIF. We do not recommend using this option, except in\n   * unusual circumstances like resizing uncacheable dynamically-generated\n   * images.\n   */\n  compression?: \"fast\";\n}\nexport interface RequestInitCfPropertiesImageMinify {\n  javascript?: boolean;\n  css?: boolean;\n  html?: boolean;\n}\nexport interface RequestInitCfPropertiesR2 {\n  /**\n   * Colo id of bucket that an object is stored in\n   */\n  bucketColoId?: number;\n}\n/**\n * Request metadata provided by Cloudflare's edge.\n */\nexport type IncomingRequestCfProperties<HostMetadata = unknown> =\n  IncomingRequestCfPropertiesBase &\n    IncomingRequestCfPropertiesBotManagementEnterprise &\n    IncomingRequestCfPropertiesCloudflareForSaaSEnterprise<HostMetadata> &\n    IncomingRequestCfPropertiesGeographicInformation &\n    IncomingRequestCfPropertiesCloudflareAccessOrApiShield;\nexport interface IncomingRequestCfPropertiesBase extends Record<\n  string,\n  unknown\n> {\n  /**\n   * [ASN](https://www.iana.org/assignments/as-numbers/as-numbers.xhtml) of the incoming request.\n   *\n   * @example 395747\n   */\n  asn?: number;\n  /**\n   * The organization which owns the ASN of the incoming request.\n   *\n   * @example \"Google Cloud\"\n   */\n  asOrganization?: string;\n  /**\n   * The original value of the `Accept-Encoding` header if Cloudflare modified it.\n   *\n   * @example \"gzip, deflate, br\"\n   */\n  clientAcceptEncoding?: string;\n  /**\n   * The number of milliseconds it took for the request to reach your worker.\n   *\n   * @example 22\n   */\n  clientTcpRtt?: number;\n  /**\n   * The three-letter [IATA](https://en.wikipedia.org/wiki/IATA_airport_code)\n   * airport code of the data center that the request hit.\n   *\n   * @example \"DFW\"\n   */\n  colo: string;\n  /**\n   * Represents the upstream's response to a\n   * [TCP `keepalive` message](https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html)\n   * from cloudflare.\n   *\n   * For workers with no upstream, this will always be `1`.\n   *\n   * @example 3\n   */\n  edgeRequestKeepAliveStatus: IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus;\n  /**\n   * The HTTP Protocol the request used.\n   *\n   * @example \"HTTP/2\"\n   */\n  httpProtocol: string;\n  /**\n   * The browser-requested prioritization information in the request object.\n   *\n   * If no information was set, defaults to the empty string `\"\"`\n   *\n   * @example \"weight=192;exclusive=0;group=3;group-weight=127\"\n   * @default \"\"\n   */\n  requestPriority: string;\n  /**\n   * The TLS version of the connection to Cloudflare.\n   * In requests served over plaintext (without TLS), this property is the empty string `\"\"`.\n   *\n   * @example \"TLSv1.3\"\n   */\n  tlsVersion: string;\n  /**\n   * The cipher for the connection to Cloudflare.\n   * In requests served over plaintext (without TLS), this property is the empty string `\"\"`.\n   *\n   * @example \"AEAD-AES128-GCM-SHA256\"\n   */\n  tlsCipher: string;\n  /**\n   * Metadata containing the [`HELLO`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2) and [`FINISHED`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9) messages from this request's TLS handshake.\n   *\n   * If the incoming request was served over plaintext (without TLS) this field is undefined.\n   */\n  tlsExportedAuthenticator?: IncomingRequestCfPropertiesExportedAuthenticatorMetadata;\n}\nexport interface IncomingRequestCfPropertiesBotManagementBase {\n  /**\n   * Cloudflare’s [level of certainty](https://developers.cloudflare.com/bots/concepts/bot-score/) that a request comes from a bot,\n   * represented as an integer percentage between `1` (almost certainly a bot) and `99` (almost certainly human).\n   *\n   * @example 54\n   */\n  score: number;\n  /**\n   * A boolean value that is true if the request comes from a good bot, like Google or Bing.\n   * Most customers choose to allow this traffic. For more details, see [Traffic from known bots](https://developers.cloudflare.com/firewall/known-issues-and-faq/#how-does-firewall-rules-handle-traffic-from-known-bots).\n   */\n  verifiedBot: boolean;\n  /**\n   * A boolean value that is true if the request originates from a\n   * Cloudflare-verified proxy service.\n   */\n  corporateProxy: boolean;\n  /**\n   * A boolean value that's true if the request matches [file extensions](https://developers.cloudflare.com/bots/reference/static-resources/) for many types of static resources.\n   */\n  staticResource: boolean;\n  /**\n   * List of IDs that correlate to the Bot Management heuristic detections made on a request (you can have multiple heuristic detections on the same request).\n   */\n  detectionIds: number[];\n}\nexport interface IncomingRequestCfPropertiesBotManagement {\n  /**\n   * Results of Cloudflare's Bot Management analysis\n   */\n  botManagement: IncomingRequestCfPropertiesBotManagementBase;\n  /**\n   * Duplicate of `botManagement.score`.\n   *\n   * @deprecated\n   */\n  clientTrustScore: number;\n}\nexport interface IncomingRequestCfPropertiesBotManagementEnterprise extends IncomingRequestCfPropertiesBotManagement {\n  /**\n   * Results of Cloudflare's Bot Management analysis\n   */\n  botManagement: IncomingRequestCfPropertiesBotManagementBase & {\n    /**\n     * A [JA3 Fingerprint](https://developers.cloudflare.com/bots/concepts/ja3-fingerprint/) to help profile specific SSL/TLS clients\n     * across different destination IPs, Ports, and X509 certificates.\n     */\n    ja3Hash: string;\n  };\n}\nexport interface IncomingRequestCfPropertiesCloudflareForSaaSEnterprise<\n  HostMetadata,\n> {\n  /**\n   * Custom metadata set per-host in [Cloudflare for SaaS](https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/).\n   *\n   * This field is only present if you have Cloudflare for SaaS enabled on your account\n   * and you have followed the [required steps to enable it]((https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/domain-support/custom-metadata/)).\n   */\n  hostMetadata?: HostMetadata;\n}\nexport interface IncomingRequestCfPropertiesCloudflareAccessOrApiShield {\n  /**\n   * Information about the client certificate presented to Cloudflare.\n   *\n   * This is populated when the incoming request is served over TLS using\n   * either Cloudflare Access or API Shield (mTLS)\n   * and the presented SSL certificate has a valid\n   * [Certificate Serial Number](https://ldapwiki.com/wiki/Certificate%20Serial%20Number)\n   * (i.e., not `null` or `\"\"`).\n   *\n   * Otherwise, a set of placeholder values are used.\n   *\n   * The property `certPresented` will be set to `\"1\"` when\n   * the object is populated (i.e. the above conditions were met).\n   */\n  tlsClientAuth:\n    | IncomingRequestCfPropertiesTLSClientAuth\n    | IncomingRequestCfPropertiesTLSClientAuthPlaceholder;\n}\n/**\n * Metadata about the request's TLS handshake\n */\nexport interface IncomingRequestCfPropertiesExportedAuthenticatorMetadata {\n  /**\n   * The client's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal\n   *\n   * @example \"44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d\"\n   */\n  clientHandshake: string;\n  /**\n   * The server's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal\n   *\n   * @example \"44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d\"\n   */\n  serverHandshake: string;\n  /**\n   * The client's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal\n   *\n   * @example \"084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b\"\n   */\n  clientFinished: string;\n  /**\n   * The server's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal\n   *\n   * @example \"084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b\"\n   */\n  serverFinished: string;\n}\n/**\n * Geographic data about the request's origin.\n */\nexport interface IncomingRequestCfPropertiesGeographicInformation {\n  /**\n   * The [ISO 3166-1 Alpha 2](https://www.iso.org/iso-3166-country-codes.html) country code the request originated from.\n   *\n   * If your worker is [configured to accept TOR connections](https://support.cloudflare.com/hc/en-us/articles/203306930-Understanding-Cloudflare-Tor-support-and-Onion-Routing), this may also be `\"T1\"`, indicating a request that originated over TOR.\n   *\n   * If Cloudflare is unable to determine where the request originated this property is omitted.\n   *\n   * The country code `\"T1\"` is used for requests originating on TOR.\n   *\n   * @example \"GB\"\n   */\n  country?: Iso3166Alpha2Code | \"T1\";\n  /**\n   * If present, this property indicates that the request originated in the EU\n   *\n   * @example \"1\"\n   */\n  isEUCountry?: \"1\";\n  /**\n   * A two-letter code indicating the continent the request originated from.\n   *\n   * @example \"AN\"\n   */\n  continent?: ContinentCode;\n  /**\n   * The city the request originated from\n   *\n   * @example \"Austin\"\n   */\n  city?: string;\n  /**\n   * Postal code of the incoming request\n   *\n   * @example \"78701\"\n   */\n  postalCode?: string;\n  /**\n   * Latitude of the incoming request\n   *\n   * @example \"30.27130\"\n   */\n  latitude?: string;\n  /**\n   * Longitude of the incoming request\n   *\n   * @example \"-97.74260\"\n   */\n  longitude?: string;\n  /**\n   * Timezone of the incoming request\n   *\n   * @example \"America/Chicago\"\n   */\n  timezone?: string;\n  /**\n   * If known, the ISO 3166-2 name for the first level region associated with\n   * the IP address of the incoming request\n   *\n   * @example \"Texas\"\n   */\n  region?: string;\n  /**\n   * If known, the ISO 3166-2 code for the first-level region associated with\n   * the IP address of the incoming request\n   *\n   * @example \"TX\"\n   */\n  regionCode?: string;\n  /**\n   * Metro code (DMA) of the incoming request\n   *\n   * @example \"635\"\n   */\n  metroCode?: string;\n}\n/** Data about the incoming request's TLS certificate */\nexport interface IncomingRequestCfPropertiesTLSClientAuth {\n  /** Always `\"1\"`, indicating that the certificate was presented */\n  certPresented: \"1\";\n  /**\n   * Result of certificate verification.\n   *\n   * @example \"FAILED:self signed certificate\"\n   */\n  certVerified: Exclude<CertVerificationStatus, \"NONE\">;\n  /** The presented certificate's revokation status.\n   *\n   * - A value of `\"1\"` indicates the certificate has been revoked\n   * - A value of `\"0\"` indicates the certificate has not been revoked\n   */\n  certRevoked: \"1\" | \"0\";\n  /**\n   * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html)\n   *\n   * @example \"CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certIssuerDN: string;\n  /**\n   * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html)\n   *\n   * @example \"CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certSubjectDN: string;\n  /**\n   * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted)\n   *\n   * @example \"CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certIssuerDNRFC2253: string;\n  /**\n   * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted)\n   *\n   * @example \"CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare\"\n   */\n  certSubjectDNRFC2253: string;\n  /** The certificate issuer's distinguished name (legacy policies) */\n  certIssuerDNLegacy: string;\n  /** The certificate subject's distinguished name (legacy policies) */\n  certSubjectDNLegacy: string;\n  /**\n   * The certificate's serial number\n   *\n   * @example \"00936EACBE07F201DF\"\n   */\n  certSerial: string;\n  /**\n   * The certificate issuer's serial number\n   *\n   * @example \"2489002934BDFEA34\"\n   */\n  certIssuerSerial: string;\n  /**\n   * The certificate's Subject Key Identifier\n   *\n   * @example \"BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4\"\n   */\n  certSKI: string;\n  /**\n   * The certificate issuer's Subject Key Identifier\n   *\n   * @example \"BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4\"\n   */\n  certIssuerSKI: string;\n  /**\n   * The certificate's SHA-1 fingerprint\n   *\n   * @example \"6b9109f323999e52259cda7373ff0b4d26bd232e\"\n   */\n  certFingerprintSHA1: string;\n  /**\n   * The certificate's SHA-256 fingerprint\n   *\n   * @example \"acf77cf37b4156a2708e34c4eb755f9b5dbbe5ebb55adfec8f11493438d19e6ad3f157f81fa3b98278453d5652b0c1fd1d71e5695ae4d709803a4d3f39de9dea\"\n   */\n  certFingerprintSHA256: string;\n  /**\n   * The effective starting date of the certificate\n   *\n   * @example \"Dec 22 19:39:00 2018 GMT\"\n   */\n  certNotBefore: string;\n  /**\n   * The effective expiration date of the certificate\n   *\n   * @example \"Dec 22 19:39:00 2018 GMT\"\n   */\n  certNotAfter: string;\n}\n/** Placeholder values for TLS Client Authorization */\nexport interface IncomingRequestCfPropertiesTLSClientAuthPlaceholder {\n  certPresented: \"0\";\n  certVerified: \"NONE\";\n  certRevoked: \"0\";\n  certIssuerDN: \"\";\n  certSubjectDN: \"\";\n  certIssuerDNRFC2253: \"\";\n  certSubjectDNRFC2253: \"\";\n  certIssuerDNLegacy: \"\";\n  certSubjectDNLegacy: \"\";\n  certSerial: \"\";\n  certIssuerSerial: \"\";\n  certSKI: \"\";\n  certIssuerSKI: \"\";\n  certFingerprintSHA1: \"\";\n  certFingerprintSHA256: \"\";\n  certNotBefore: \"\";\n  certNotAfter: \"\";\n}\n/** Possible outcomes of TLS verification */\nexport declare type CertVerificationStatus =\n  /** Authentication succeeded */\n  | \"SUCCESS\"\n  /** No certificate was presented */\n  | \"NONE\"\n  /** Failed because the certificate was self-signed */\n  | \"FAILED:self signed certificate\"\n  /** Failed because the certificate failed a trust chain check */\n  | \"FAILED:unable to verify the first certificate\"\n  /** Failed because the certificate not yet valid */\n  | \"FAILED:certificate is not yet valid\"\n  /** Failed because the certificate is expired */\n  | \"FAILED:certificate has expired\"\n  /** Failed for another unspecified reason */\n  | \"FAILED\";\n/**\n * An upstream endpoint's response to a TCP `keepalive` message from Cloudflare.\n */\nexport declare type IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus =\n  | 0 /** Unknown */\n  | 1 /** no keepalives (not found) */\n  | 2 /** no connection re-use, opening keepalive connection failed */\n  | 3 /** no connection re-use, keepalive accepted and saved */\n  | 4 /** connection re-use, refused by the origin server (`TCP FIN`) */\n  | 5; /** connection re-use, accepted by the origin server */\n/** ISO 3166-1 Alpha-2 codes */\nexport declare type Iso3166Alpha2Code =\n  | \"AD\"\n  | \"AE\"\n  | \"AF\"\n  | \"AG\"\n  | \"AI\"\n  | \"AL\"\n  | \"AM\"\n  | \"AO\"\n  | \"AQ\"\n  | \"AR\"\n  | \"AS\"\n  | \"AT\"\n  | \"AU\"\n  | \"AW\"\n  | \"AX\"\n  | \"AZ\"\n  | \"BA\"\n  | \"BB\"\n  | \"BD\"\n  | \"BE\"\n  | \"BF\"\n  | \"BG\"\n  | \"BH\"\n  | \"BI\"\n  | \"BJ\"\n  | \"BL\"\n  | \"BM\"\n  | \"BN\"\n  | \"BO\"\n  | \"BQ\"\n  | \"BR\"\n  | \"BS\"\n  | \"BT\"\n  | \"BV\"\n  | \"BW\"\n  | \"BY\"\n  | \"BZ\"\n  | \"CA\"\n  | \"CC\"\n  | \"CD\"\n  | \"CF\"\n  | \"CG\"\n  | \"CH\"\n  | \"CI\"\n  | \"CK\"\n  | \"CL\"\n  | \"CM\"\n  | \"CN\"\n  | \"CO\"\n  | \"CR\"\n  | \"CU\"\n  | \"CV\"\n  | \"CW\"\n  | \"CX\"\n  | \"CY\"\n  | \"CZ\"\n  | \"DE\"\n  | \"DJ\"\n  | \"DK\"\n  | \"DM\"\n  | \"DO\"\n  | \"DZ\"\n  | \"EC\"\n  | \"EE\"\n  | \"EG\"\n  | \"EH\"\n  | \"ER\"\n  | \"ES\"\n  | \"ET\"\n  | \"FI\"\n  | \"FJ\"\n  | \"FK\"\n  | \"FM\"\n  | \"FO\"\n  | \"FR\"\n  | \"GA\"\n  | \"GB\"\n  | \"GD\"\n  | \"GE\"\n  | \"GF\"\n  | \"GG\"\n  | \"GH\"\n  | \"GI\"\n  | \"GL\"\n  | \"GM\"\n  | \"GN\"\n  | \"GP\"\n  | \"GQ\"\n  | \"GR\"\n  | \"GS\"\n  | \"GT\"\n  | \"GU\"\n  | \"GW\"\n  | \"GY\"\n  | \"HK\"\n  | \"HM\"\n  | \"HN\"\n  | \"HR\"\n  | \"HT\"\n  | \"HU\"\n  | \"ID\"\n  | \"IE\"\n  | \"IL\"\n  | \"IM\"\n  | \"IN\"\n  | \"IO\"\n  | \"IQ\"\n  | \"IR\"\n  | \"IS\"\n  | \"IT\"\n  | \"JE\"\n  | \"JM\"\n  | \"JO\"\n  | \"JP\"\n  | \"KE\"\n  | \"KG\"\n  | \"KH\"\n  | \"KI\"\n  | \"KM\"\n  | \"KN\"\n  | \"KP\"\n  | \"KR\"\n  | \"KW\"\n  | \"KY\"\n  | \"KZ\"\n  | \"LA\"\n  | \"LB\"\n  | \"LC\"\n  | \"LI\"\n  | \"LK\"\n  | \"LR\"\n  | \"LS\"\n  | \"LT\"\n  | \"LU\"\n  | \"LV\"\n  | \"LY\"\n  | \"MA\"\n  | \"MC\"\n  | \"MD\"\n  | \"ME\"\n  | \"MF\"\n  | \"MG\"\n  | \"MH\"\n  | \"MK\"\n  | \"ML\"\n  | \"MM\"\n  | \"MN\"\n  | \"MO\"\n  | \"MP\"\n  | \"MQ\"\n  | \"MR\"\n  | \"MS\"\n  | \"MT\"\n  | \"MU\"\n  | \"MV\"\n  | \"MW\"\n  | \"MX\"\n  | \"MY\"\n  | \"MZ\"\n  | \"NA\"\n  | \"NC\"\n  | \"NE\"\n  | \"NF\"\n  | \"NG\"\n  | \"NI\"\n  | \"NL\"\n  | \"NO\"\n  | \"NP\"\n  | \"NR\"\n  | \"NU\"\n  | \"NZ\"\n  | \"OM\"\n  | \"PA\"\n  | \"PE\"\n  | \"PF\"\n  | \"PG\"\n  | \"PH\"\n  | \"PK\"\n  | \"PL\"\n  | \"PM\"\n  | \"PN\"\n  | \"PR\"\n  | \"PS\"\n  | \"PT\"\n  | \"PW\"\n  | \"PY\"\n  | \"QA\"\n  | \"RE\"\n  | \"RO\"\n  | \"RS\"\n  | \"RU\"\n  | \"RW\"\n  | \"SA\"\n  | \"SB\"\n  | \"SC\"\n  | \"SD\"\n  | \"SE\"\n  | \"SG\"\n  | \"SH\"\n  | \"SI\"\n  | \"SJ\"\n  | \"SK\"\n  | \"SL\"\n  | \"SM\"\n  | \"SN\"\n  | \"SO\"\n  | \"SR\"\n  | \"SS\"\n  | \"ST\"\n  | \"SV\"\n  | \"SX\"\n  | \"SY\"\n  | \"SZ\"\n  | \"TC\"\n  | \"TD\"\n  | \"TF\"\n  | \"TG\"\n  | \"TH\"\n  | \"TJ\"\n  | \"TK\"\n  | \"TL\"\n  | \"TM\"\n  | \"TN\"\n  | \"TO\"\n  | \"TR\"\n  | \"TT\"\n  | \"TV\"\n  | \"TW\"\n  | \"TZ\"\n  | \"UA\"\n  | \"UG\"\n  | \"UM\"\n  | \"US\"\n  | \"UY\"\n  | \"UZ\"\n  | \"VA\"\n  | \"VC\"\n  | \"VE\"\n  | \"VG\"\n  | \"VI\"\n  | \"VN\"\n  | \"VU\"\n  | \"WF\"\n  | \"WS\"\n  | \"YE\"\n  | \"YT\"\n  | \"ZA\"\n  | \"ZM\"\n  | \"ZW\";\n/** The 2-letter continent codes Cloudflare uses */\nexport declare type ContinentCode =\n  | \"AF\"\n  | \"AN\"\n  | \"AS\"\n  | \"EU\"\n  | \"NA\"\n  | \"OC\"\n  | \"SA\";\nexport type CfProperties<HostMetadata = unknown> =\n  | IncomingRequestCfProperties<HostMetadata>\n  | RequestInitCfProperties;\nexport interface D1Meta {\n  duration: number;\n  size_after: number;\n  rows_read: number;\n  rows_written: number;\n  last_row_id: number;\n  changed_db: boolean;\n  changes: number;\n  /**\n   * The region of the database instance that executed the query.\n   */\n  served_by_region?: string;\n  /**\n   * The three letters airport code of the colo that executed the query.\n   */\n  served_by_colo?: string;\n  /**\n   * True if-and-only-if the database instance that executed the query was the primary.\n   */\n  served_by_primary?: boolean;\n  timings?: {\n    /**\n     * The duration of the SQL query execution by the database instance. It doesn't include any network time.\n     */\n    sql_duration_ms: number;\n  };\n  /**\n   * Number of total attempts to execute the query, due to automatic retries.\n   * Note: All other fields in the response like `timings` only apply to the last attempt.\n   */\n  total_attempts?: number;\n}\nexport interface D1Response {\n  success: true;\n  meta: D1Meta & Record<string, unknown>;\n  error?: never;\n}\nexport type D1Result<T = unknown> = D1Response & {\n  results: T[];\n};\nexport interface D1ExecResult {\n  count: number;\n  duration: number;\n}\nexport type D1SessionConstraint =\n  // Indicates that the first query should go to the primary, and the rest queries\n  // using the same D1DatabaseSession will go to any replica that is consistent with\n  // the bookmark maintained by the session (returned by the first query).\n  | \"first-primary\"\n  // Indicates that the first query can go anywhere (primary or replica), and the rest queries\n  // using the same D1DatabaseSession will go to any replica that is consistent with\n  // the bookmark maintained by the session (returned by the first query).\n  | \"first-unconstrained\";\nexport type D1SessionBookmark = string;\nexport declare abstract class D1Database {\n  prepare(query: string): D1PreparedStatement;\n  batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;\n  exec(query: string): Promise<D1ExecResult>;\n  /**\n   * Creates a new D1 Session anchored at the given constraint or the bookmark.\n   * All queries executed using the created session will have sequential consistency,\n   * meaning that all writes done through the session will be visible in subsequent reads.\n   *\n   * @param constraintOrBookmark Either the session constraint or the explicit bookmark to anchor the created session.\n   */\n  withSession(\n    constraintOrBookmark?: D1SessionBookmark | D1SessionConstraint,\n  ): D1DatabaseSession;\n  /**\n   * @deprecated dump() will be removed soon, only applies to deprecated alpha v1 databases.\n   */\n  dump(): Promise<ArrayBuffer>;\n}\nexport declare abstract class D1DatabaseSession {\n  prepare(query: string): D1PreparedStatement;\n  batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;\n  /**\n   * @returns The latest session bookmark across all executed queries on the session.\n   *          If no query has been executed yet, `null` is returned.\n   */\n  getBookmark(): D1SessionBookmark | null;\n}\nexport declare abstract class D1PreparedStatement {\n  bind(...values: unknown[]): D1PreparedStatement;\n  first<T = unknown>(colName: string): Promise<T | null>;\n  first<T = Record<string, unknown>>(): Promise<T | null>;\n  run<T = Record<string, unknown>>(): Promise<D1Result<T>>;\n  all<T = Record<string, unknown>>(): Promise<D1Result<T>>;\n  raw<T = unknown[]>(options: {\n    columnNames: true;\n  }): Promise<[string[], ...T[]]>;\n  raw<T = unknown[]>(options?: { columnNames?: false }): Promise<T[]>;\n}\n// `Disposable` was added to TypeScript's standard lib types in version 5.2.\n// To support older TypeScript versions, define an empty `Disposable` interface.\n// Users won't be able to use `using`/`Symbol.dispose` without upgrading to 5.2,\n// but this will ensure type checking on older versions still passes.\n// TypeScript's interface merging will ensure our empty interface is effectively\n// ignored when `Disposable` is included in the standard lib.\nexport interface Disposable {}\n/**\n * The returned data after sending an email\n */\nexport interface EmailSendResult {\n  /**\n   * The Email Message ID\n   */\n  messageId: string;\n}\n/**\n * An email message that can be sent from a Worker.\n */\nexport interface EmailMessage {\n  /**\n   * Envelope From attribute of the email message.\n   */\n  readonly from: string;\n  /**\n   * Envelope To attribute of the email message.\n   */\n  readonly to: string;\n}\n/**\n * An email message that is sent to a consumer Worker and can be rejected/forwarded.\n */\nexport interface ForwardableEmailMessage extends EmailMessage {\n  /**\n   * Stream of the email message content.\n   */\n  readonly raw: ReadableStream<Uint8Array>;\n  /**\n   * An [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers).\n   */\n  readonly headers: Headers;\n  /**\n   * Size of the email message content.\n   */\n  readonly rawSize: number;\n  /**\n   * Reject this email message by returning a permanent SMTP error back to the connecting client including the given reason.\n   * @param reason The reject reason.\n   * @returns void\n   */\n  setReject(reason: string): void;\n  /**\n   * Forward this email message to a verified destination address of the account.\n   * @param rcptTo Verified destination address.\n   * @param headers A [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers).\n   * @returns A promise that resolves when the email message is forwarded.\n   */\n  forward(rcptTo: string, headers?: Headers): Promise<EmailSendResult>;\n  /**\n   * Reply to the sender of this email message with a new EmailMessage object.\n   * @param message The reply message.\n   * @returns A promise that resolves when the email message is replied.\n   */\n  reply(message: EmailMessage): Promise<EmailSendResult>;\n}\n/** A file attachment for an email message */\nexport type EmailAttachment =\n  | {\n      disposition: \"inline\";\n      contentId: string;\n      filename: string;\n      type: string;\n      content: string | ArrayBuffer | ArrayBufferView;\n    }\n  | {\n      disposition: \"attachment\";\n      contentId?: undefined;\n      filename: string;\n      type: string;\n      content: string | ArrayBuffer | ArrayBufferView;\n    };\n/** An Email Address */\nexport interface EmailAddress {\n  name: string;\n  email: string;\n}\n/**\n * A binding that allows a Worker to send email messages.\n */\nexport interface SendEmail {\n  send(message: EmailMessage): Promise<EmailSendResult>;\n  send(builder: {\n    from: string | EmailAddress;\n    to: string | string[];\n    subject: string;\n    replyTo?: string | EmailAddress;\n    cc?: string | string[];\n    bcc?: string | string[];\n    headers?: Record<string, string>;\n    text?: string;\n    html?: string;\n    attachments?: EmailAttachment[];\n  }): Promise<EmailSendResult>;\n}\nexport declare abstract class EmailEvent extends ExtendableEvent {\n  readonly message: ForwardableEmailMessage;\n}\nexport declare type EmailExportedHandler<Env = unknown, Props = unknown> = (\n  message: ForwardableEmailMessage,\n  env: Env,\n  ctx: ExecutionContext<Props>,\n) => void | Promise<void>;\n/**\n * Hello World binding to serve as an explanatory example. DO NOT USE\n */\nexport interface HelloWorldBinding {\n  /**\n   * Retrieve the current stored value\n   */\n  get(): Promise<{\n    value: string;\n    ms?: number;\n  }>;\n  /**\n   * Set a new stored value\n   */\n  set(value: string): Promise<void>;\n}\nexport interface Hyperdrive {\n  /**\n   * Connect directly to Hyperdrive as if it's your database, returning a TCP socket.\n   *\n   * Calling this method returns an identical socket to if you call\n   * `connect(\"host:port\")` using the `host` and `port` fields from this object.\n   * Pick whichever approach works better with your preferred DB client library.\n   *\n   * Note that this socket is not yet authenticated -- it's expected that your\n   * code (or preferably, the client library of your choice) will authenticate\n   * using the information in this class's readonly fields.\n   */\n  connect(): Socket;\n  /**\n   * A valid DB connection string that can be passed straight into the typical\n   * client library/driver/ORM. This will typically be the easiest way to use\n   * Hyperdrive.\n   */\n  readonly connectionString: string;\n  /*\n   * A randomly generated hostname that is only valid within the context of the\n   * currently running Worker which, when passed into `connect()` function from\n   * the \"cloudflare:sockets\" module, will connect to the Hyperdrive instance\n   * for your database.\n   */\n  readonly host: string;\n  /*\n   * The port that must be paired the the host field when connecting.\n   */\n  readonly port: number;\n  /*\n   * The username to use when authenticating to your database via Hyperdrive.\n   * Unlike the host and password, this will be the same every time\n   */\n  readonly user: string;\n  /*\n   * The randomly generated password to use when authenticating to your\n   * database via Hyperdrive. Like the host field, this password is only valid\n   * within the context of the currently running Worker instance from which\n   * it's read.\n   */\n  readonly password: string;\n  /*\n   * The name of the database to connect to.\n   */\n  readonly database: string;\n}\n// Copyright (c) 2024 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nexport type ImageInfoResponse =\n  | {\n      format: \"image/svg+xml\";\n    }\n  | {\n      format: string;\n      fileSize: number;\n      width: number;\n      height: number;\n    };\nexport type ImageTransform = {\n  width?: number;\n  height?: number;\n  background?: string;\n  blur?: number;\n  border?:\n    | {\n        color?: string;\n        width?: number;\n      }\n    | {\n        top?: number;\n        bottom?: number;\n        left?: number;\n        right?: number;\n      };\n  brightness?: number;\n  contrast?: number;\n  fit?: \"scale-down\" | \"contain\" | \"pad\" | \"squeeze\" | \"cover\" | \"crop\";\n  flip?: \"h\" | \"v\" | \"hv\";\n  gamma?: number;\n  segment?: \"foreground\";\n  gravity?:\n    | \"face\"\n    | \"left\"\n    | \"right\"\n    | \"top\"\n    | \"bottom\"\n    | \"center\"\n    | \"auto\"\n    | \"entropy\"\n    | {\n        x?: number;\n        y?: number;\n        mode: \"remainder\" | \"box-center\";\n      };\n  rotate?: 0 | 90 | 180 | 270;\n  saturation?: number;\n  sharpen?: number;\n  trim?:\n    | \"border\"\n    | {\n        top?: number;\n        bottom?: number;\n        left?: number;\n        right?: number;\n        width?: number;\n        height?: number;\n        border?:\n          | boolean\n          | {\n              color?: string;\n              tolerance?: number;\n              keep?: number;\n            };\n      };\n};\nexport type ImageDrawOptions = {\n  opacity?: number;\n  repeat?: boolean | string;\n  top?: number;\n  left?: number;\n  bottom?: number;\n  right?: number;\n};\nexport type ImageInputOptions = {\n  encoding?: \"base64\";\n};\nexport type ImageOutputOptions = {\n  format:\n    | \"image/jpeg\"\n    | \"image/png\"\n    | \"image/gif\"\n    | \"image/webp\"\n    | \"image/avif\"\n    | \"rgb\"\n    | \"rgba\";\n  quality?: number;\n  background?: string;\n  anim?: boolean;\n};\nexport interface ImageMetadata {\n  id: string;\n  filename?: string;\n  uploaded?: string;\n  requireSignedURLs: boolean;\n  meta?: Record<string, unknown>;\n  variants: string[];\n  draft?: boolean;\n  creator?: string;\n}\nexport interface ImageUploadOptions {\n  id?: string;\n  filename?: string;\n  requireSignedURLs?: boolean;\n  metadata?: Record<string, unknown>;\n  creator?: string;\n  encoding?: \"base64\";\n}\nexport interface ImageUpdateOptions {\n  requireSignedURLs?: boolean;\n  metadata?: Record<string, unknown>;\n  creator?: string;\n}\nexport interface ImageListOptions {\n  limit?: number;\n  cursor?: string;\n  sortOrder?: \"asc\" | \"desc\";\n  creator?: string;\n}\nexport interface ImageList {\n  images: ImageMetadata[];\n  cursor?: string;\n  listComplete: boolean;\n}\nexport interface HostedImagesBinding {\n  /**\n   * Get detailed metadata for a hosted image\n   * @param imageId The ID of the image (UUID or custom ID)\n   * @returns Image metadata, or null if not found\n   */\n  details(imageId: string): Promise<ImageMetadata | null>;\n  /**\n   * Get the raw image data for a hosted image\n   * @param imageId The ID of the image (UUID or custom ID)\n   * @returns ReadableStream of image bytes, or null if not found\n   */\n  image(imageId: string): Promise<ReadableStream<Uint8Array> | null>;\n  /**\n   * Upload a new hosted image\n   * @param image The image file to upload\n   * @param options Upload configuration\n   * @returns Metadata for the uploaded image\n   * @throws {@link ImagesError} if upload fails\n   */\n  upload(\n    image: ReadableStream<Uint8Array> | ArrayBuffer,\n    options?: ImageUploadOptions,\n  ): Promise<ImageMetadata>;\n  /**\n   * Update hosted image metadata\n   * @param imageId The ID of the image\n   * @param options Properties to update\n   * @returns Updated image metadata\n   * @throws {@link ImagesError} if update fails\n   */\n  update(imageId: string, options: ImageUpdateOptions): Promise<ImageMetadata>;\n  /**\n   * Delete a hosted image\n   * @param imageId The ID of the image\n   * @returns True if deleted, false if not found\n   */\n  delete(imageId: string): Promise<boolean>;\n  /**\n   * List hosted images with pagination\n   * @param options List configuration\n   * @returns List of images with pagination info\n   * @throws {@link ImagesError} if list fails\n   */\n  list(options?: ImageListOptions): Promise<ImageList>;\n}\nexport interface ImagesBinding {\n  /**\n   * Get image metadata (type, width and height)\n   * @throws {@link ImagesError} with code 9412 if input is not an image\n   * @param stream The image bytes\n   */\n  info(\n    stream: ReadableStream<Uint8Array>,\n    options?: ImageInputOptions,\n  ): Promise<ImageInfoResponse>;\n  /**\n   * Begin applying a series of transformations to an image\n   * @param stream The image bytes\n   * @returns A transform handle\n   */\n  input(\n    stream: ReadableStream<Uint8Array>,\n    options?: ImageInputOptions,\n  ): ImageTransformer;\n  /**\n   * Access hosted images CRUD operations\n   */\n  readonly hosted: HostedImagesBinding;\n}\nexport interface ImageTransformer {\n  /**\n   * Apply transform next, returning a transform handle.\n   * You can then apply more transformations, draw, or retrieve the output.\n   * @param transform\n   */\n  transform(transform: ImageTransform): ImageTransformer;\n  /**\n   * Draw an image on this transformer, returning a transform handle.\n   * You can then apply more transformations, draw, or retrieve the output.\n   * @param image The image (or transformer that will give the image) to draw\n   * @param options The options configuring how to draw the image\n   */\n  draw(\n    image: ReadableStream<Uint8Array> | ImageTransformer,\n    options?: ImageDrawOptions,\n  ): ImageTransformer;\n  /**\n   * Retrieve the image that results from applying the transforms to the\n   * provided input\n   * @param options Options that apply to the output e.g. output format\n   */\n  output(options: ImageOutputOptions): Promise<ImageTransformationResult>;\n}\nexport type ImageTransformationOutputOptions = {\n  encoding?: \"base64\";\n};\nexport interface ImageTransformationResult {\n  /**\n   * The image as a response, ready to store in cache or return to users\n   */\n  response(): Response;\n  /**\n   * The content type of the returned image\n   */\n  contentType(): string;\n  /**\n   * The bytes of the response\n   */\n  image(options?: ImageTransformationOutputOptions): ReadableStream<Uint8Array>;\n}\nexport interface ImagesError extends Error {\n  readonly code: number;\n  readonly message: string;\n  readonly stack?: string;\n}\n/**\n * Media binding for transforming media streams.\n * Provides the entry point for media transformation operations.\n */\nexport interface MediaBinding {\n  /**\n   * Creates a media transformer from an input stream.\n   * @param media - The input media bytes\n   * @returns A MediaTransformer instance for applying transformations\n   */\n  input(media: ReadableStream<Uint8Array>): MediaTransformer;\n}\n/**\n * Media transformer for applying transformation operations to media content.\n * Handles sizing, fitting, and other input transformation parameters.\n */\nexport interface MediaTransformer {\n  /**\n   * Applies transformation options to the media content.\n   * @param transform - Configuration for how the media should be transformed\n   * @returns A generator for producing the transformed media output\n   */\n  transform(\n    transform?: MediaTransformationInputOptions,\n  ): MediaTransformationGenerator;\n  /**\n   * Generates the final media output with specified options.\n   * @param output - Configuration for the output format and parameters\n   * @returns The final transformation result containing the transformed media\n   */\n  output(output?: MediaTransformationOutputOptions): MediaTransformationResult;\n}\n/**\n * Generator for producing media transformation results.\n * Configures the output format and parameters for the transformed media.\n */\nexport interface MediaTransformationGenerator {\n  /**\n   * Generates the final media output with specified options.\n   * @param output - Configuration for the output format and parameters\n   * @returns The final transformation result containing the transformed media\n   */\n  output(output?: MediaTransformationOutputOptions): MediaTransformationResult;\n}\n/**\n * Result of a media transformation operation.\n * Provides multiple ways to access the transformed media content.\n */\nexport interface MediaTransformationResult {\n  /**\n   * Returns the transformed media as a readable stream of bytes.\n   * @returns A promise containing a readable stream with the transformed media\n   */\n  media(): Promise<ReadableStream<Uint8Array>>;\n  /**\n   * Returns the transformed media as an HTTP response object.\n   * @returns The transformed media as a Promise<Response>, ready to store in cache or return to users\n   */\n  response(): Promise<Response>;\n  /**\n   * Returns the MIME type of the transformed media.\n   * @returns A promise containing the content type string (e.g., 'image/jpeg', 'video/mp4')\n   */\n  contentType(): Promise<string>;\n}\n/**\n * Configuration options for transforming media input.\n * Controls how the media should be resized and fitted.\n */\nexport type MediaTransformationInputOptions = {\n  /** How the media should be resized to fit the specified dimensions */\n  fit?: \"contain\" | \"cover\" | \"scale-down\";\n  /** Target width in pixels */\n  width?: number;\n  /** Target height in pixels */\n  height?: number;\n};\n/**\n * Configuration options for Media Transformations output.\n * Controls the format, timing, and type of the generated output.\n */\nexport type MediaTransformationOutputOptions = {\n  /**\n   * Output mode determining the type of media to generate\n   */\n  mode?: \"video\" | \"spritesheet\" | \"frame\" | \"audio\";\n  /** Whether to include audio in the output */\n  audio?: boolean;\n  /**\n   * Starting timestamp for frame extraction or start time for clips. (e.g. '2s').\n   */\n  time?: string;\n  /**\n   * Duration for video clips, audio extraction, and spritesheet generation (e.g. '5s').\n   */\n  duration?: string;\n  /**\n   * Number of frames in the spritesheet.\n   */\n  imageCount?: number;\n  /**\n   * Output format for the generated media.\n   */\n  format?: \"jpg\" | \"png\" | \"m4a\";\n};\n/**\n * Error object for media transformation operations.\n * Extends the standard Error interface with additional media-specific information.\n */\nexport interface MediaError extends Error {\n  readonly code: number;\n  readonly message: string;\n  readonly stack?: string;\n}\nexport type Params<P extends string = any> = Record<P, string | string[]>;\nexport type EventContext<Env, P extends string, Data> = {\n  request: Request<unknown, IncomingRequestCfProperties<unknown>>;\n  functionPath: string;\n  waitUntil: (promise: Promise<any>) => void;\n  passThroughOnException: () => void;\n  next: (input?: Request | string, init?: RequestInit) => Promise<Response>;\n  env: Env & {\n    ASSETS: {\n      fetch: typeof fetch;\n    };\n  };\n  params: Params<P>;\n  data: Data;\n};\nexport type PagesFunction<\n  Env = unknown,\n  Params extends string = any,\n  Data extends Record<string, unknown> = Record<string, unknown>,\n> = (context: EventContext<Env, Params, Data>) => Response | Promise<Response>;\nexport type EventPluginContext<Env, P extends string, Data, PluginArgs> = {\n  request: Request<unknown, IncomingRequestCfProperties<unknown>>;\n  functionPath: string;\n  waitUntil: (promise: Promise<any>) => void;\n  passThroughOnException: () => void;\n  next: (input?: Request | string, init?: RequestInit) => Promise<Response>;\n  env: Env & {\n    ASSETS: {\n      fetch: typeof fetch;\n    };\n  };\n  params: Params<P>;\n  data: Data;\n  pluginArgs: PluginArgs;\n};\nexport type PagesPluginFunction<\n  Env = unknown,\n  Params extends string = any,\n  Data extends Record<string, unknown> = Record<string, unknown>,\n  PluginArgs = unknown,\n> = (\n  context: EventPluginContext<Env, Params, Data, PluginArgs>,\n) => Response | Promise<Response>;\n// PubSubMessage represents an incoming PubSub message.\n// The message includes metadata about the broker, the client, and the payload\n// itself.\n// https://developers.cloudflare.com/pub-sub/\nexport interface PubSubMessage {\n  // Message ID\n  readonly mid: number;\n  // MQTT broker FQDN in the form mqtts://BROKER.NAMESPACE.cloudflarepubsub.com:PORT\n  readonly broker: string;\n  // The MQTT topic the message was sent on.\n  readonly topic: string;\n  // The client ID of the client that published this message.\n  readonly clientId: string;\n  // The unique identifier (JWT ID) used by the client to authenticate, if token\n  // auth was used.\n  readonly jti?: string;\n  // A Unix timestamp (seconds from Jan 1, 1970), set when the Pub/Sub Broker\n  // received the message from the client.\n  readonly receivedAt: number;\n  // An (optional) string with the MIME type of the payload, if set by the\n  // client.\n  readonly contentType: string;\n  // Set to 1 when the payload is a UTF-8 string\n  // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901063\n  readonly payloadFormatIndicator: number;\n  // Pub/Sub (MQTT) payloads can be UTF-8 strings, or byte arrays.\n  // You can use payloadFormatIndicator to inspect this before decoding.\n  payload: string | Uint8Array;\n}\n// JsonWebKey extended by kid parameter\nexport interface JsonWebKeyWithKid extends JsonWebKey {\n  // Key Identifier of the JWK\n  readonly kid: string;\n}\nexport interface RateLimitOptions {\n  key: string;\n}\nexport interface RateLimitOutcome {\n  success: boolean;\n}\nexport interface RateLimit {\n  /**\n   * Rate limit a request based on the provided options.\n   * @see https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/\n   * @returns A promise that resolves with the outcome of the rate limit.\n   */\n  limit(options: RateLimitOptions): Promise<RateLimitOutcome>;\n}\n// Namespace for RPC utility types. Unfortunately, we can't use a `module` here as these types need\n// to referenced by `Fetcher`. This is included in the \"importable\" version of the types which\n// strips all `module` blocks.\nexport declare namespace Rpc {\n  // Branded types for identifying `WorkerEntrypoint`/`DurableObject`/`Target`s.\n  // TypeScript uses *structural* typing meaning anything with the same shape as type `T` is a `T`.\n  // For the classes exported by `cloudflare:workers` we want *nominal* typing (i.e. we only want to\n  // accept `WorkerEntrypoint` from `cloudflare:workers`, not any other class with the same shape)\n  export const __RPC_STUB_BRAND: \"__RPC_STUB_BRAND\";\n  export const __RPC_TARGET_BRAND: \"__RPC_TARGET_BRAND\";\n  export const __WORKER_ENTRYPOINT_BRAND: \"__WORKER_ENTRYPOINT_BRAND\";\n  export const __DURABLE_OBJECT_BRAND: \"__DURABLE_OBJECT_BRAND\";\n  export const __WORKFLOW_ENTRYPOINT_BRAND: \"__WORKFLOW_ENTRYPOINT_BRAND\";\n  export interface RpcTargetBranded {\n    [__RPC_TARGET_BRAND]: never;\n  }\n  export interface WorkerEntrypointBranded {\n    [__WORKER_ENTRYPOINT_BRAND]: never;\n  }\n  export interface DurableObjectBranded {\n    [__DURABLE_OBJECT_BRAND]: never;\n  }\n  export interface WorkflowEntrypointBranded {\n    [__WORKFLOW_ENTRYPOINT_BRAND]: never;\n  }\n  export type EntrypointBranded =\n    | WorkerEntrypointBranded\n    | DurableObjectBranded\n    | WorkflowEntrypointBranded;\n  // Types that can be used through `Stub`s\n  export type Stubable = RpcTargetBranded | ((...args: any[]) => any);\n  // Types that can be passed over RPC\n  // The reason for using a generic type here is to build a serializable subset of structured\n  //   cloneable composite types. This allows types defined with the \"interface\" keyword to pass the\n  //   serializable check as well. Otherwise, only types defined with the \"type\" keyword would pass.\n  type Serializable<T> =\n    // Structured cloneables\n    | BaseType\n    // Structured cloneable composites\n    | Map<\n        T extends Map<infer U, unknown> ? Serializable<U> : never,\n        T extends Map<unknown, infer U> ? Serializable<U> : never\n      >\n    | Set<T extends Set<infer U> ? Serializable<U> : never>\n    | ReadonlyArray<T extends ReadonlyArray<infer U> ? Serializable<U> : never>\n    | {\n        [K in keyof T]: K extends number | string ? Serializable<T[K]> : never;\n      }\n    // Special types\n    | Stub<Stubable>\n    // Serialized as stubs, see `Stubify`\n    | Stubable;\n  // Base type for all RPC stubs, including common memory management methods.\n  // `T` is used as a marker type for unwrapping `Stub`s later.\n  interface StubBase<T extends Stubable> extends Disposable {\n    [__RPC_STUB_BRAND]: T;\n    dup(): this;\n  }\n  export type Stub<T extends Stubable> = Provider<T> & StubBase<T>;\n  // This represents all the types that can be sent as-is over an RPC boundary\n  type BaseType =\n    | void\n    | undefined\n    | null\n    | boolean\n    | number\n    | bigint\n    | string\n    | TypedArray\n    | ArrayBuffer\n    | DataView\n    | Date\n    | Error\n    | RegExp\n    | ReadableStream<Uint8Array>\n    | WritableStream<Uint8Array>\n    | Request\n    | Response\n    | Headers;\n  // Recursively rewrite all `Stubable` types with `Stub`s\n  type Stubify<T> = T extends Stubable\n    ? Stub<T>\n    : T extends Map<infer K, infer V>\n      ? Map<Stubify<K>, Stubify<V>>\n      : T extends Set<infer V>\n        ? Set<Stubify<V>>\n        : T extends Array<infer V>\n          ? Array<Stubify<V>>\n          : T extends ReadonlyArray<infer V>\n            ? ReadonlyArray<Stubify<V>>\n            : T extends BaseType\n              ? T\n              : T extends {\n                    [key: string | number]: any;\n                  }\n                ? {\n                    [K in keyof T]: Stubify<T[K]>;\n                  }\n                : T;\n  // Recursively rewrite all `Stub<T>`s with the corresponding `T`s.\n  // Note we use `StubBase` instead of `Stub` here to avoid circular dependencies:\n  // `Stub` depends on `Provider`, which depends on `Unstubify`, which would depend on `Stub`.\n  type Unstubify<T> =\n    T extends StubBase<infer V>\n      ? V\n      : T extends Map<infer K, infer V>\n        ? Map<Unstubify<K>, Unstubify<V>>\n        : T extends Set<infer V>\n          ? Set<Unstubify<V>>\n          : T extends Array<infer V>\n            ? Array<Unstubify<V>>\n            : T extends ReadonlyArray<infer V>\n              ? ReadonlyArray<Unstubify<V>>\n              : T extends BaseType\n                ? T\n                : T extends {\n                      [key: string | number]: unknown;\n                    }\n                  ? {\n                      [K in keyof T]: Unstubify<T[K]>;\n                    }\n                  : T;\n  type UnstubifyAll<A extends any[]> = {\n    [I in keyof A]: Unstubify<A[I]>;\n  };\n  // Utility type for adding `Provider`/`Disposable`s to `object` types only.\n  // Note `unknown & T` is equivalent to `T`.\n  type MaybeProvider<T> = T extends object ? Provider<T> : unknown;\n  type MaybeDisposable<T> = T extends object ? Disposable : unknown;\n  // Type for method return or property on an RPC interface.\n  // - Stubable types are replaced by stubs.\n  // - Serializable types are passed by value, with stubable types replaced by stubs\n  //   and a top-level `Disposer`.\n  // Everything else can't be passed over PRC.\n  // Technically, we use custom thenables here, but they quack like `Promise`s.\n  // Intersecting with `(Maybe)Provider` allows pipelining.\n  type Result<R> = R extends Stubable\n    ? Promise<Stub<R>> & Provider<R>\n    : R extends Serializable<R>\n      ? Promise<Stubify<R> & MaybeDisposable<R>> & MaybeProvider<R>\n      : never;\n  // Type for method or property on an RPC interface.\n  // For methods, unwrap `Stub`s in parameters, and rewrite returns to be `Result`s.\n  // Unwrapping `Stub`s allows calling with `Stubable` arguments.\n  // For properties, rewrite types to be `Result`s.\n  // In each case, unwrap `Promise`s.\n  type MethodOrProperty<V> = V extends (...args: infer P) => infer R\n    ? (...args: UnstubifyAll<P>) => Result<Awaited<R>>\n    : Result<Awaited<V>>;\n  // Type for the callable part of an `Provider` if `T` is callable.\n  // This is intersected with methods/properties.\n  type MaybeCallableProvider<T> = T extends (...args: any[]) => any\n    ? MethodOrProperty<T>\n    : unknown;\n  // Base type for all other types providing RPC-like interfaces.\n  // Rewrites all methods/properties to be `MethodOrProperty`s, while preserving callable types.\n  // `Reserved` names (e.g. stub method names like `dup()`) and symbols can't be accessed over RPC.\n  export type Provider<\n    T extends object,\n    Reserved extends string = never,\n  > = MaybeCallableProvider<T> &\n    Pick<\n      {\n        [K in keyof T]: MethodOrProperty<T[K]>;\n      },\n      Exclude<keyof T, Reserved | symbol | keyof StubBase<never>>\n    >;\n}\nexport declare namespace Cloudflare {\n  // Type of `env`.\n  //\n  // The specific project can extend `Env` by redeclaring it in project-specific files. Typescript\n  // will merge all declarations.\n  //\n  // You can use `wrangler types` to generate the `Env` type automatically.\n  interface Env {}\n  // Project-specific parameters used to inform types.\n  //\n  // This interface is, again, intended to be declared in project-specific files, and then that\n  // declaration will be merged with this one.\n  //\n  // A project should have a declaration like this:\n  //\n  //     interface GlobalProps {\n  //       // Declares the main module's exports. Used to populate Cloudflare.Exports aka the type\n  //       // of `ctx.exports`.\n  //       mainModule: typeof import(\"my-main-module\");\n  //\n  //       // Declares which of the main module's exports are configured with durable storage, and\n  //       // thus should behave as Durable Object namsepace bindings.\n  //       durableNamespaces: \"MyDurableObject\" | \"AnotherDurableObject\";\n  //     }\n  //\n  // You can use `wrangler types` to generate `GlobalProps` automatically.\n  interface GlobalProps {}\n  // Evaluates to the type of a property in GlobalProps, defaulting to `Default` if it is not\n  // present.\n  type GlobalProp<K extends string, Default> = K extends keyof GlobalProps\n    ? GlobalProps[K]\n    : Default;\n  // The type of the program's main module exports, if known. Requires `GlobalProps` to declare the\n  // `mainModule` property.\n  type MainModule = GlobalProp<\"mainModule\", {}>;\n  // The type of ctx.exports, which contains loopback bindings for all top-level exports.\n  type Exports = {\n    [K in keyof MainModule]: LoopbackForExport<MainModule[K]> &\n      // If the export is listed in `durableNamespaces`, then it is also a\n      // DurableObjectNamespace.\n      (K extends GlobalProp<\"durableNamespaces\", never>\n        ? MainModule[K] extends new (...args: any[]) => infer DoInstance\n          ? DoInstance extends Rpc.DurableObjectBranded\n            ? DurableObjectNamespace<DoInstance>\n            : DurableObjectNamespace<undefined>\n          : DurableObjectNamespace<undefined>\n        : {});\n  };\n}\nexport declare namespace CloudflareWorkersModule {\n  export type RpcStub<T extends Rpc.Stubable> = Rpc.Stub<T>;\n  export const RpcStub: {\n    new <T extends Rpc.Stubable>(value: T): Rpc.Stub<T>;\n  };\n  export abstract class RpcTarget implements Rpc.RpcTargetBranded {\n    [Rpc.__RPC_TARGET_BRAND]: never;\n  }\n  // `protected` fields don't appear in `keyof`s, so can't be accessed over RPC\n  export abstract class WorkerEntrypoint<Env = Cloudflare.Env, Props = {}>\n    implements Rpc.WorkerEntrypointBranded\n  {\n    [Rpc.__WORKER_ENTRYPOINT_BRAND]: never;\n    protected ctx: ExecutionContext<Props>;\n    protected env: Env;\n    constructor(ctx: ExecutionContext, env: Env);\n    email?(message: ForwardableEmailMessage): void | Promise<void>;\n    fetch?(request: Request): Response | Promise<Response>;\n    queue?(batch: MessageBatch<unknown>): void | Promise<void>;\n    scheduled?(controller: ScheduledController): void | Promise<void>;\n    tail?(events: TraceItem[]): void | Promise<void>;\n    tailStream?(\n      event: TailStream.TailEvent<TailStream.Onset>,\n    ):\n      | TailStream.TailEventHandlerType\n      | Promise<TailStream.TailEventHandlerType>;\n    test?(controller: TestController): void | Promise<void>;\n    trace?(traces: TraceItem[]): void | Promise<void>;\n  }\n  export abstract class DurableObject<Env = Cloudflare.Env, Props = {}>\n    implements Rpc.DurableObjectBranded\n  {\n    [Rpc.__DURABLE_OBJECT_BRAND]: never;\n    protected ctx: DurableObjectState<Props>;\n    protected env: Env;\n    constructor(ctx: DurableObjectState, env: Env);\n    alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise<void>;\n    fetch?(request: Request): Response | Promise<Response>;\n    webSocketMessage?(\n      ws: WebSocket,\n      message: string | ArrayBuffer,\n    ): void | Promise<void>;\n    webSocketClose?(\n      ws: WebSocket,\n      code: number,\n      reason: string,\n      wasClean: boolean,\n    ): void | Promise<void>;\n    webSocketError?(ws: WebSocket, error: unknown): void | Promise<void>;\n  }\n  export type WorkflowDurationLabel =\n    | \"second\"\n    | \"minute\"\n    | \"hour\"\n    | \"day\"\n    | \"week\"\n    | \"month\"\n    | \"year\";\n  export type WorkflowSleepDuration =\n    | `${number} ${WorkflowDurationLabel}${\"s\" | \"\"}`\n    | number;\n  export type WorkflowDelayDuration = WorkflowSleepDuration;\n  export type WorkflowTimeoutDuration = WorkflowSleepDuration;\n  export type WorkflowRetentionDuration = WorkflowSleepDuration;\n  export type WorkflowBackoff = \"constant\" | \"linear\" | \"exponential\";\n  export type WorkflowStepConfig = {\n    retries?: {\n      limit: number;\n      delay: WorkflowDelayDuration | number;\n      backoff?: WorkflowBackoff;\n    };\n    timeout?: WorkflowTimeoutDuration | number;\n  };\n  export type WorkflowEvent<T> = {\n    payload: Readonly<T>;\n    timestamp: Date;\n    instanceId: string;\n  };\n  export type WorkflowStepEvent<T> = {\n    payload: Readonly<T>;\n    timestamp: Date;\n    type: string;\n  };\n  export type WorkflowStepContext = {\n    attempt: number;\n  };\n  export abstract class WorkflowStep {\n    do<T extends Rpc.Serializable<T>>(\n      name: string,\n      callback: (ctx: WorkflowStepContext) => Promise<T>,\n    ): Promise<T>;\n    do<T extends Rpc.Serializable<T>>(\n      name: string,\n      config: WorkflowStepConfig,\n      callback: (ctx: WorkflowStepContext) => Promise<T>,\n    ): Promise<T>;\n    sleep: (name: string, duration: WorkflowSleepDuration) => Promise<void>;\n    sleepUntil: (name: string, timestamp: Date | number) => Promise<void>;\n    waitForEvent<T extends Rpc.Serializable<T>>(\n      name: string,\n      options: {\n        type: string;\n        timeout?: WorkflowTimeoutDuration | number;\n      },\n    ): Promise<WorkflowStepEvent<T>>;\n  }\n  export type WorkflowInstanceStatus =\n    | \"queued\"\n    | \"running\"\n    | \"paused\"\n    | \"errored\"\n    | \"terminated\"\n    | \"complete\"\n    | \"waiting\"\n    | \"waitingForPause\"\n    | \"unknown\";\n  export abstract class WorkflowEntrypoint<\n    Env = unknown,\n    T extends Rpc.Serializable<T> | unknown = unknown,\n  >\n    implements Rpc.WorkflowEntrypointBranded\n  {\n    [Rpc.__WORKFLOW_ENTRYPOINT_BRAND]: never;\n    protected ctx: ExecutionContext;\n    protected env: Env;\n    constructor(ctx: ExecutionContext, env: Env);\n    run(\n      event: Readonly<WorkflowEvent<T>>,\n      step: WorkflowStep,\n    ): Promise<unknown>;\n  }\n  export function waitUntil(promise: Promise<unknown>): void;\n  export function withEnv(newEnv: unknown, fn: () => unknown): unknown;\n  export function withExports(newExports: unknown, fn: () => unknown): unknown;\n  export function withEnvAndExports(\n    newEnv: unknown,\n    newExports: unknown,\n    fn: () => unknown,\n  ): unknown;\n  export const env: Cloudflare.Env;\n  export const exports: Cloudflare.Exports;\n}\nexport interface SecretsStoreSecret {\n  /**\n   * Get a secret from the Secrets Store, returning a string of the secret value\n   * if it exists, or throws an error if it does not exist\n   */\n  get(): Promise<string>;\n}\n/**\n * Binding entrypoint for Cloudflare Stream.\n *\n * Usage:\n * - Binding-level operations:\n *   `await env.STREAM.videos.upload`\n *   `await env.STREAM.videos.createDirectUpload`\n *   `await env.STREAM.videos.*`\n *   `await env.STREAM.watermarks.*`\n * - Per-video operations:\n *   `await env.STREAM.video(id).downloads.*`\n *   `await env.STREAM.video(id).captions.*`\n *\n * Example usage:\n * ```ts\n * await env.STREAM.video(id).downloads.generate();\n *\n * const video = env.STREAM.video(id)\n * const captions = video.captions.list();\n * const videoDetails = video.details()\n * ```\n */\nexport interface StreamBinding {\n  /**\n   * Returns a handle scoped to a single video for per-video operations.\n   * @param id The unique identifier for the video.\n   * @returns A handle for per-video operations.\n   */\n  video(id: string): StreamVideoHandle;\n  /**\n   * Uploads a new video from a provided URL.\n   * @param url The URL to upload from.\n   * @param params Optional upload parameters.\n   * @returns The uploaded video details.\n   * @throws {BadRequestError} if the upload parameter is invalid or the URL is invalid\n   * @throws {QuotaReachedError} if the account storage capacity is exceeded\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {RateLimitedError} if the server received too many requests\n   * @throws {AlreadyUploadedError} if a video was already uploaded to this URL\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  upload(url: string, params?: StreamUrlUploadParams): Promise<StreamVideo>;\n  /**\n   * Creates a direct upload that allows video uploads without an API key.\n   * @param params Parameters for the direct upload\n   * @returns The direct upload details.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {RateLimitedError} if the server received too many requests\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  createDirectUpload(\n    params: StreamDirectUploadCreateParams,\n  ): Promise<StreamDirectUpload>;\n  videos: StreamVideos;\n  watermarks: StreamWatermarks;\n}\n/**\n * Handle for operations scoped to a single Stream video.\n */\nexport interface StreamVideoHandle {\n  /**\n   * The unique identifier for the video.\n   */\n  id: string;\n  /**\n   * Get a full videos details\n   * @returns The full video details.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  details(): Promise<StreamVideo>;\n  /**\n   * Update details for a single video.\n   * @param params The fields to update for the video.\n   * @returns The updated video details.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  update(params: StreamUpdateVideoParams): Promise<StreamVideo>;\n  /**\n   * Deletes a video and its copies from Cloudflare Stream.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(): Promise<void>;\n  /**\n   * Creates a signed URL token for a video.\n   * @returns The signed token that was created.\n   * @throws {InternalError} if the signing key cannot be retrieved or the token cannot be signed\n   */\n  generateToken(): Promise<string>;\n  downloads: StreamScopedDownloads;\n  captions: StreamScopedCaptions;\n}\nexport interface StreamVideo {\n  /**\n   * The unique identifier for the video.\n   */\n  id: string;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator: string | null;\n  /**\n   * The thumbnail URL for the video.\n   */\n  thumbnail: string;\n  /**\n   * The thumbnail timestamp percentage.\n   */\n  thumbnailTimestampPct: number;\n  /**\n   * Indicates whether the video is ready to stream.\n   */\n  readyToStream: boolean;\n  /**\n   * The date and time the video became ready to stream.\n   */\n  readyToStreamAt: string | null;\n  /**\n   * Processing status information.\n   */\n  status: StreamVideoStatus;\n  /**\n   * A user modifiable key-value store.\n   */\n  meta: Record<string, string>;\n  /**\n   * The date and time the video was created.\n   */\n  created: string;\n  /**\n   * The date and time the video was last modified.\n   */\n  modified: string;\n  /**\n   * The date and time at which the video will be deleted.\n   */\n  scheduledDeletion: string | null;\n  /**\n   * The size of the video in bytes.\n   */\n  size: number;\n  /**\n   * The preview URL for the video.\n   */\n  preview?: string;\n  /**\n   * Origins allowed to display the video.\n   */\n  allowedOrigins: Array<string>;\n  /**\n   * Indicates whether signed URLs are required.\n   */\n  requireSignedURLs: boolean | null;\n  /**\n   * The date and time the video was uploaded.\n   */\n  uploaded: string | null;\n  /**\n   * The date and time when the upload URL expires.\n   */\n  uploadExpiry: string | null;\n  /**\n   * The maximum size in bytes for direct uploads.\n   */\n  maxSizeBytes: number | null;\n  /**\n   * The maximum duration in seconds for direct uploads.\n   */\n  maxDurationSeconds: number | null;\n  /**\n   * The video duration in seconds. -1 indicates unknown.\n   */\n  duration: number;\n  /**\n   * Input metadata for the original upload.\n   */\n  input: StreamVideoInput;\n  /**\n   * Playback URLs for the video.\n   */\n  hlsPlaybackUrl: string;\n  dashPlaybackUrl: string;\n  /**\n   * The watermark applied to the video, if any.\n   */\n  watermark: StreamWatermark | null;\n  /**\n   * The live input id associated with the video, if any.\n   */\n  liveInputId?: string | null;\n  /**\n   * The source video id if this is a clip.\n   */\n  clippedFromId: string | null;\n  /**\n   * Public details associated with the video.\n   */\n  publicDetails: StreamPublicDetails | null;\n}\nexport type StreamVideoStatus = {\n  /**\n   * The current processing state.\n   */\n  state: string;\n  /**\n   * The current processing step.\n   */\n  step?: string;\n  /**\n   * The percent complete as a string.\n   */\n  pctComplete?: string;\n  /**\n   * An error reason code, if applicable.\n   */\n  errorReasonCode: string;\n  /**\n   * An error reason text, if applicable.\n   */\n  errorReasonText: string;\n};\nexport type StreamVideoInput = {\n  /**\n   * The input width in pixels.\n   */\n  width: number;\n  /**\n   * The input height in pixels.\n   */\n  height: number;\n};\nexport type StreamPublicDetails = {\n  /**\n   * The public title for the video.\n   */\n  title: string | null;\n  /**\n   * The public share link.\n   */\n  share_link: string | null;\n  /**\n   * The public channel link.\n   */\n  channel_link: string | null;\n  /**\n   * The public logo URL.\n   */\n  logo: string | null;\n};\nexport type StreamDirectUpload = {\n  /**\n   * The URL an unauthenticated upload can use for a single multipart request.\n   */\n  uploadURL: string;\n  /**\n   * A Cloudflare-generated unique identifier for a media item.\n   */\n  id: string;\n  /**\n   * The watermark profile applied to the upload.\n   */\n  watermark: StreamWatermark | null;\n  /**\n   * The scheduled deletion time, if any.\n   */\n  scheduledDeletion: string | null;\n};\nexport type StreamDirectUploadCreateParams = {\n  /**\n   * The maximum duration in seconds for a video upload.\n   */\n  maxDurationSeconds: number;\n  /**\n   * The date and time after upload when videos will not be accepted.\n   */\n  expiry?: string;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator?: string;\n  /**\n   * A user modifiable key-value store used to reference other systems of record for\n   * managing videos.\n   */\n  meta?: Record<string, string>;\n  /**\n   * Lists the origins allowed to display the video.\n   */\n  allowedOrigins?: Array<string>;\n  /**\n   * Indicates whether the video can be accessed using the id. When set to `true`,\n   * a signed token must be generated with a signing key to view the video.\n   */\n  requireSignedURLs?: boolean;\n  /**\n   * The thumbnail timestamp percentage.\n   */\n  thumbnailTimestampPct?: number;\n  /**\n   * The date and time at which the video will be deleted. Include `null` to remove\n   * a scheduled deletion.\n   */\n  scheduledDeletion?: string | null;\n  /**\n   * The watermark profile to apply.\n   */\n  watermark?: StreamDirectUploadWatermark;\n};\nexport type StreamDirectUploadWatermark = {\n  /**\n   * The unique identifier for the watermark profile.\n   */\n  id: string;\n};\nexport type StreamUrlUploadParams = {\n  /**\n   * Lists the origins allowed to display the video. Enter allowed origin\n   * domains in an array and use `*` for wildcard subdomains. Empty arrays allow the\n   * video to be viewed on any origin.\n   */\n  allowedOrigins?: Array<string>;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator?: string;\n  /**\n   * A user modifiable key-value store used to reference other systems of\n   * record for managing videos.\n   */\n  meta?: Record<string, string>;\n  /**\n   * Indicates whether the video can be a accessed using the id. When\n   * set to `true`, a signed token must be generated with a signing key to view the\n   * video.\n   */\n  requireSignedURLs?: boolean;\n  /**\n   * Indicates the date and time at which the video will be deleted. Omit\n   * the field to indicate no change, or include with a `null` value to remove an\n   * existing scheduled deletion. If specified, must be at least 30 days from upload\n   * time.\n   */\n  scheduledDeletion?: string | null;\n  /**\n   * The timestamp for a thumbnail image calculated as a percentage value\n   * of the video's duration. To convert from a second-wise timestamp to a\n   * percentage, divide the desired timestamp by the total duration of the video. If\n   * this value is not set, the default thumbnail image is taken from 0s of the\n   * video.\n   */\n  thumbnailTimestampPct?: number;\n  /**\n   * The identifier for the watermark profile\n   */\n  watermarkId?: string;\n};\nexport interface StreamScopedCaptions {\n  /**\n   * Uploads the caption or subtitle file to the endpoint for a specific BCP47 language.\n   * One caption or subtitle file per language is allowed.\n   * @param language The BCP 47 language tag for the caption or subtitle.\n   * @param file The caption or subtitle file to upload.\n   * @returns The created caption entry.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the language or file is invalid\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  upload(language: string, file: File): Promise<StreamCaption>;\n  /**\n   * Generate captions or subtitles for the provided language via AI.\n   * @param language The BCP 47 language tag to generate.\n   * @returns The generated caption entry.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the language is invalid\n   * @throws {StreamError} if a generated caption already exists\n   * @throws {StreamError} if the video duration is too long\n   * @throws {StreamError} if the video is missing audio\n   * @throws {StreamError} if the requested language is not supported\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(language: string): Promise<StreamCaption>;\n  /**\n   * Lists the captions or subtitles.\n   * Use the language parameter to filter by a specific language.\n   * @param language The optional BCP 47 language tag to filter by.\n   * @returns The list of captions or subtitles.\n   * @throws {NotFoundError} if the video or caption is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  list(language?: string): Promise<StreamCaption[]>;\n  /**\n   * Removes the captions or subtitles from a video.\n   * @param language The BCP 47 language tag to remove.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the video or caption is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(language: string): Promise<void>;\n}\nexport interface StreamScopedDownloads {\n  /**\n   * Generates a download for a video when a video is ready to view. Available\n   * types are `default` and `audio`. Defaults to `default` when omitted.\n   * @param downloadType The download type to create.\n   * @returns The current downloads for the video.\n   * @throws {NotFoundError} if the video is not found\n   * @throws {BadRequestError} if the download type is invalid\n   * @throws {StreamError} if the video duration is too long to generate a download\n   * @throws {StreamError} if the video is not ready to stream\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(\n    downloadType?: StreamDownloadType,\n  ): Promise<StreamDownloadGetResponse>;\n  /**\n   * Lists the downloads created for a video.\n   * @returns The current downloads for the video.\n   * @throws {NotFoundError} if the video or downloads are not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  get(): Promise<StreamDownloadGetResponse>;\n  /**\n   * Delete the downloads for a video. Available types are `default` and `audio`.\n   * Defaults to `default` when omitted.\n   * @param downloadType The download type to delete.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the video or downloads are not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(downloadType?: StreamDownloadType): Promise<void>;\n}\nexport interface StreamVideos {\n  /**\n   * Lists all videos in a users account.\n   * @returns The list of videos.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  list(params?: StreamVideosListParams): Promise<StreamVideo[]>;\n}\nexport interface StreamWatermarks {\n  /**\n   * Generate a new watermark profile\n   * @param file The image file to upload\n   * @param params The watermark creation parameters.\n   * @returns The created watermark profile.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InvalidURLError} if the URL is invalid\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {TooManyWatermarksError} if the number of allowed watermarks is reached\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(\n    file: File,\n    params: StreamWatermarkCreateParams,\n  ): Promise<StreamWatermark>;\n  /**\n   * Generate a new watermark profile\n   * @param url The image url to upload\n   * @param params The watermark creation parameters.\n   * @returns The created watermark profile.\n   * @throws {BadRequestError} if the parameters are invalid\n   * @throws {InvalidURLError} if the URL is invalid\n   * @throws {MaxFileSizeError} if the file size is too large\n   * @throws {TooManyWatermarksError} if the number of allowed watermarks is reached\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  generate(\n    url: string,\n    params: StreamWatermarkCreateParams,\n  ): Promise<StreamWatermark>;\n  /**\n   * Lists all watermark profiles for an account.\n   * @returns The list of watermark profiles.\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  list(): Promise<StreamWatermark[]>;\n  /**\n   * Retrieves details for a single watermark profile.\n   * @param watermarkId The watermark profile identifier.\n   * @returns The watermark profile details.\n   * @throws {NotFoundError} if the watermark is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  get(watermarkId: string): Promise<StreamWatermark>;\n  /**\n   * Deletes a watermark profile.\n   * @param watermarkId The watermark profile identifier.\n   * @returns A promise that resolves when deletion completes.\n   * @throws {NotFoundError} if the watermark is not found\n   * @throws {InternalError} if an unexpected error occurs\n   */\n  delete(watermarkId: string): Promise<void>;\n}\nexport type StreamUpdateVideoParams = {\n  /**\n   * Lists the origins allowed to display the video. Enter allowed origin\n   * domains in an array and use `*` for wildcard subdomains. Empty arrays allow the\n   * video to be viewed on any origin.\n   */\n  allowedOrigins?: Array<string>;\n  /**\n   * A user-defined identifier for the media creator.\n   */\n  creator?: string;\n  /**\n   * The maximum duration in seconds for a video upload. Can be set for a\n   * video that is not yet uploaded to limit its duration. Uploads that exceed the\n   * specified duration will fail during processing. A value of `-1` means the value\n   * is unknown.\n   */\n  maxDurationSeconds?: number;\n  /**\n   * A user modifiable key-value store used to reference other systems of\n   * record for managing videos.\n   */\n  meta?: Record<string, string>;\n  /**\n   * Indicates whether the video can be a accessed using the id. When\n   * set to `true`, a signed token must be generated with a signing key to view the\n   * video.\n   */\n  requireSignedURLs?: boolean;\n  /**\n   * Indicates the date and time at which the video will be deleted. Omit\n   * the field to indicate no change, or include with a `null` value to remove an\n   * existing scheduled deletion. If specified, must be at least 30 days from upload\n   * time.\n   */\n  scheduledDeletion?: string | null;\n  /**\n   * The timestamp for a thumbnail image calculated as a percentage value\n   * of the video's duration. To convert from a second-wise timestamp to a\n   * percentage, divide the desired timestamp by the total duration of the video. If\n   * this value is not set, the default thumbnail image is taken from 0s of the\n   * video.\n   */\n  thumbnailTimestampPct?: number;\n};\nexport type StreamCaption = {\n  /**\n   * Whether the caption was generated via AI.\n   */\n  generated?: boolean;\n  /**\n   * The language label displayed in the native language to users.\n   */\n  label: string;\n  /**\n   * The language tag in BCP 47 format.\n   */\n  language: string;\n  /**\n   * The status of a generated caption.\n   */\n  status?: \"ready\" | \"inprogress\" | \"error\";\n};\nexport type StreamDownloadStatus = \"ready\" | \"inprogress\" | \"error\";\nexport type StreamDownloadType = \"default\" | \"audio\";\nexport type StreamDownload = {\n  /**\n   * Indicates the progress as a percentage between 0 and 100.\n   */\n  percentComplete: number;\n  /**\n   * The status of a generated download.\n   */\n  status: StreamDownloadStatus;\n  /**\n   * The URL to access the generated download.\n   */\n  url?: string;\n};\n/**\n * An object with download type keys. Each key is optional and only present if that\n * download type has been created.\n */\nexport type StreamDownloadGetResponse = {\n  /**\n   * The audio-only download. Only present if this download type has been created.\n   */\n  audio?: StreamDownload;\n  /**\n   * The default video download. Only present if this download type has been created.\n   */\n  default?: StreamDownload;\n};\nexport type StreamWatermarkPosition =\n  | \"upperRight\"\n  | \"upperLeft\"\n  | \"lowerLeft\"\n  | \"lowerRight\"\n  | \"center\";\nexport type StreamWatermark = {\n  /**\n   * The unique identifier for a watermark profile.\n   */\n  id: string;\n  /**\n   * The size of the image in bytes.\n   */\n  size: number;\n  /**\n   * The height of the image in pixels.\n   */\n  height: number;\n  /**\n   * The width of the image in pixels.\n   */\n  width: number;\n  /**\n   * The date and a time a watermark profile was created.\n   */\n  created: string;\n  /**\n   * The source URL for a downloaded image. If the watermark profile was created via\n   * direct upload, this field is null.\n   */\n  downloadedFrom: string | null;\n  /**\n   * A short description of the watermark profile.\n   */\n  name: string;\n  /**\n   * The translucency of the image. A value of `0.0` makes the image completely\n   * transparent, and `1.0` makes the image completely opaque. Note that if the image\n   * is already semi-transparent, setting this to `1.0` will not make the image\n   * completely opaque.\n   */\n  opacity: number;\n  /**\n   * The whitespace between the adjacent edges (determined by position) of the video\n   * and the image. `0.0` indicates no padding, and `1.0` indicates a fully padded\n   * video width or length, as determined by the algorithm.\n   */\n  padding: number;\n  /**\n   * The size of the image relative to the overall size of the video. This parameter\n   * will adapt to horizontal and vertical videos automatically. `0.0` indicates no\n   * scaling (use the size of the image as-is), and `1.0 `fills the entire video.\n   */\n  scale: number;\n  /**\n   * The location of the image. Valid positions are: `upperRight`, `upperLeft`,\n   * `lowerLeft`, `lowerRight`, and `center`. Note that `center` ignores the\n   * `padding` parameter.\n   */\n  position: StreamWatermarkPosition;\n};\nexport type StreamWatermarkCreateParams = {\n  /**\n   * A short description of the watermark profile.\n   */\n  name?: string;\n  /**\n   * The translucency of the image. A value of `0.0` makes the image completely\n   * transparent, and `1.0` makes the image completely opaque. Note that if the\n   * image is already semi-transparent, setting this to `1.0` will not make the\n   * image completely opaque.\n   */\n  opacity?: number;\n  /**\n   * The whitespace between the adjacent edges (determined by position) of the\n   * video and the image. `0.0` indicates no padding, and `1.0` indicates a fully\n   * padded video width or length, as determined by the algorithm.\n   */\n  padding?: number;\n  /**\n   * The size of the image relative to the overall size of the video. This\n   * parameter will adapt to horizontal and vertical videos automatically. `0.0`\n   * indicates no scaling (use the size of the image as-is), and `1.0 `fills the\n   * entire video.\n   */\n  scale?: number;\n  /**\n   * The location of the image.\n   */\n  position?: StreamWatermarkPosition;\n};\nexport type StreamVideosListParams = {\n  /**\n   * The maximum number of videos to return.\n   */\n  limit?: number;\n  /**\n   * Return videos created before this timestamp.\n   * (RFC3339/RFC3339Nano)\n   */\n  before?: string;\n  /**\n   * Comparison operator for the `before` field.\n   * @default 'lt'\n   */\n  beforeComp?: StreamPaginationComparison;\n  /**\n   * Return videos created after this timestamp.\n   * (RFC3339/RFC3339Nano)\n   */\n  after?: string;\n  /**\n   * Comparison operator for the `after` field.\n   * @default 'gte'\n   */\n  afterComp?: StreamPaginationComparison;\n};\nexport type StreamPaginationComparison = \"eq\" | \"gt\" | \"gte\" | \"lt\" | \"lte\";\n/**\n * Error object for Stream binding operations.\n */\nexport interface StreamError extends Error {\n  readonly code: number;\n  readonly statusCode: number;\n  readonly message: string;\n  readonly stack?: string;\n}\nexport interface InternalError extends StreamError {\n  name: \"InternalError\";\n}\nexport interface BadRequestError extends StreamError {\n  name: \"BadRequestError\";\n}\nexport interface NotFoundError extends StreamError {\n  name: \"NotFoundError\";\n}\nexport interface ForbiddenError extends StreamError {\n  name: \"ForbiddenError\";\n}\nexport interface RateLimitedError extends StreamError {\n  name: \"RateLimitedError\";\n}\nexport interface QuotaReachedError extends StreamError {\n  name: \"QuotaReachedError\";\n}\nexport interface MaxFileSizeError extends StreamError {\n  name: \"MaxFileSizeError\";\n}\nexport interface InvalidURLError extends StreamError {\n  name: \"InvalidURLError\";\n}\nexport interface AlreadyUploadedError extends StreamError {\n  name: \"AlreadyUploadedError\";\n}\nexport interface TooManyWatermarksError extends StreamError {\n  name: \"TooManyWatermarksError\";\n}\nexport type MarkdownDocument = {\n  name: string;\n  blob: Blob;\n};\nexport type ConversionResponse =\n  | {\n      id: string;\n      name: string;\n      mimeType: string;\n      format: \"markdown\";\n      tokens: number;\n      data: string;\n    }\n  | {\n      id: string;\n      name: string;\n      mimeType: string;\n      format: \"error\";\n      error: string;\n    };\nexport type ImageConversionOptions = {\n  descriptionLanguage?: \"en\" | \"es\" | \"fr\" | \"it\" | \"pt\" | \"de\";\n};\nexport type EmbeddedImageConversionOptions = ImageConversionOptions & {\n  convert?: boolean;\n  maxConvertedImages?: number;\n};\nexport type ConversionOptions = {\n  html?: {\n    images?: EmbeddedImageConversionOptions & {\n      convertOGImage?: boolean;\n    };\n    hostname?: string;\n    cssSelector?: string;\n  };\n  docx?: {\n    images?: EmbeddedImageConversionOptions;\n  };\n  image?: ImageConversionOptions;\n  pdf?: {\n    images?: EmbeddedImageConversionOptions;\n    metadata?: boolean;\n  };\n};\nexport type ConversionRequestOptions = {\n  gateway?: GatewayOptions;\n  extraHeaders?: object;\n  conversionOptions?: ConversionOptions;\n};\nexport type SupportedFileFormat = {\n  mimeType: string;\n  extension: string;\n};\nexport declare abstract class ToMarkdownService {\n  transform(\n    files: MarkdownDocument[],\n    options?: ConversionRequestOptions,\n  ): Promise<ConversionResponse[]>;\n  transform(\n    files: MarkdownDocument,\n    options?: ConversionRequestOptions,\n  ): Promise<ConversionResponse>;\n  supported(): Promise<SupportedFileFormat[]>;\n}\nexport declare namespace TailStream {\n  interface Header {\n    readonly name: string;\n    readonly value: string;\n  }\n  interface FetchEventInfo {\n    readonly type: \"fetch\";\n    readonly method: string;\n    readonly url: string;\n    readonly cfJson?: object;\n    readonly headers: Header[];\n  }\n  interface JsRpcEventInfo {\n    readonly type: \"jsrpc\";\n  }\n  interface ScheduledEventInfo {\n    readonly type: \"scheduled\";\n    readonly scheduledTime: Date;\n    readonly cron: string;\n  }\n  interface AlarmEventInfo {\n    readonly type: \"alarm\";\n    readonly scheduledTime: Date;\n  }\n  interface QueueEventInfo {\n    readonly type: \"queue\";\n    readonly queueName: string;\n    readonly batchSize: number;\n  }\n  interface EmailEventInfo {\n    readonly type: \"email\";\n    readonly mailFrom: string;\n    readonly rcptTo: string;\n    readonly rawSize: number;\n  }\n  interface TraceEventInfo {\n    readonly type: \"trace\";\n    readonly traces: (string | null)[];\n  }\n  interface HibernatableWebSocketEventInfoMessage {\n    readonly type: \"message\";\n  }\n  interface HibernatableWebSocketEventInfoError {\n    readonly type: \"error\";\n  }\n  interface HibernatableWebSocketEventInfoClose {\n    readonly type: \"close\";\n    readonly code: number;\n    readonly wasClean: boolean;\n  }\n  interface HibernatableWebSocketEventInfo {\n    readonly type: \"hibernatableWebSocket\";\n    readonly info:\n      | HibernatableWebSocketEventInfoClose\n      | HibernatableWebSocketEventInfoError\n      | HibernatableWebSocketEventInfoMessage;\n  }\n  interface CustomEventInfo {\n    readonly type: \"custom\";\n  }\n  interface FetchResponseInfo {\n    readonly type: \"fetch\";\n    readonly statusCode: number;\n  }\n  type EventOutcome =\n    | \"ok\"\n    | \"canceled\"\n    | \"exception\"\n    | \"unknown\"\n    | \"killSwitch\"\n    | \"daemonDown\"\n    | \"exceededCpu\"\n    | \"exceededMemory\"\n    | \"loadShed\"\n    | \"responseStreamDisconnected\"\n    | \"scriptNotFound\";\n  interface ScriptVersion {\n    readonly id: string;\n    readonly tag?: string;\n    readonly message?: string;\n  }\n  interface Onset {\n    readonly type: \"onset\";\n    readonly attributes: Attribute[];\n    // id for the span being opened by this Onset event.\n    readonly spanId: string;\n    readonly dispatchNamespace?: string;\n    readonly entrypoint?: string;\n    readonly executionModel: string;\n    readonly scriptName?: string;\n    readonly scriptTags?: string[];\n    readonly scriptVersion?: ScriptVersion;\n    readonly info:\n      | FetchEventInfo\n      | JsRpcEventInfo\n      | ScheduledEventInfo\n      | AlarmEventInfo\n      | QueueEventInfo\n      | EmailEventInfo\n      | TraceEventInfo\n      | HibernatableWebSocketEventInfo\n      | CustomEventInfo;\n  }\n  interface Outcome {\n    readonly type: \"outcome\";\n    readonly outcome: EventOutcome;\n    readonly cpuTime: number;\n    readonly wallTime: number;\n  }\n  interface SpanOpen {\n    readonly type: \"spanOpen\";\n    readonly name: string;\n    // id for the span being opened by this SpanOpen event.\n    readonly spanId: string;\n    readonly info?: FetchEventInfo | JsRpcEventInfo | Attributes;\n  }\n  interface SpanClose {\n    readonly type: \"spanClose\";\n    readonly outcome: EventOutcome;\n  }\n  interface DiagnosticChannelEvent {\n    readonly type: \"diagnosticChannel\";\n    readonly channel: string;\n    readonly message: any;\n  }\n  interface Exception {\n    readonly type: \"exception\";\n    readonly name: string;\n    readonly message: string;\n    readonly stack?: string;\n  }\n  interface Log {\n    readonly type: \"log\";\n    readonly level: \"debug\" | \"error\" | \"info\" | \"log\" | \"warn\";\n    readonly message: object;\n  }\n  interface DroppedEventsDiagnostic {\n    readonly diagnosticsType: \"droppedEvents\";\n    readonly count: number;\n  }\n  interface StreamDiagnostic {\n    readonly type: \"streamDiagnostic\";\n    // To add new diagnostic types, define a new interface and add it to this union type.\n    readonly diagnostic: DroppedEventsDiagnostic;\n  }\n  // This marks the worker handler return information.\n  // This is separate from Outcome because the worker invocation can live for a long time after\n  // returning. For example - Websockets that return an http upgrade response but then continue\n  // streaming information or SSE http connections.\n  interface Return {\n    readonly type: \"return\";\n    readonly info?: FetchResponseInfo;\n  }\n  interface Attribute {\n    readonly name: string;\n    readonly value:\n      | string\n      | string[]\n      | boolean\n      | boolean[]\n      | number\n      | number[]\n      | bigint\n      | bigint[];\n  }\n  interface Attributes {\n    readonly type: \"attributes\";\n    readonly info: Attribute[];\n  }\n  type EventType =\n    | Onset\n    | Outcome\n    | SpanOpen\n    | SpanClose\n    | DiagnosticChannelEvent\n    | Exception\n    | Log\n    | StreamDiagnostic\n    | Return\n    | Attributes;\n  // Context in which this trace event lives.\n  interface SpanContext {\n    // Single id for the entire top-level invocation\n    // This should be a new traceId for the first worker stage invoked in the eyeball request and then\n    // same-account service-bindings should reuse the same traceId but cross-account service-bindings\n    // should use a new traceId.\n    readonly traceId: string;\n    // spanId in which this event is handled\n    // for Onset and SpanOpen events this would be the parent span id\n    // for Outcome and SpanClose these this would be the span id of the opening Onset and SpanOpen events\n    // For Hibernate and Mark this would be the span under which they were emitted.\n    // spanId is not set ONLY if:\n    //  1. This is an Onset event\n    //  2. We are not inheriting any SpanContext. (e.g. this is a cross-account service binding or a new top-level invocation)\n    readonly spanId?: string;\n  }\n  interface TailEvent<Event extends EventType> {\n    // invocation id of the currently invoked worker stage.\n    // invocation id will always be unique to every Onset event and will be the same until the Outcome event.\n    readonly invocationId: string;\n    // Inherited spanContext for this event.\n    readonly spanContext: SpanContext;\n    readonly timestamp: Date;\n    readonly sequence: number;\n    readonly event: Event;\n  }\n  type TailEventHandler<Event extends EventType = EventType> = (\n    event: TailEvent<Event>,\n  ) => void | Promise<void>;\n  type TailEventHandlerObject = {\n    outcome?: TailEventHandler<Outcome>;\n    spanOpen?: TailEventHandler<SpanOpen>;\n    spanClose?: TailEventHandler<SpanClose>;\n    diagnosticChannel?: TailEventHandler<DiagnosticChannelEvent>;\n    exception?: TailEventHandler<Exception>;\n    log?: TailEventHandler<Log>;\n    return?: TailEventHandler<Return>;\n    attributes?: TailEventHandler<Attributes>;\n  };\n  type TailEventHandlerType = TailEventHandler | TailEventHandlerObject;\n}\n// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n/**\n * Data types supported for holding vector metadata.\n */\nexport type VectorizeVectorMetadataValue = string | number | boolean | string[];\n/**\n * Additional information to associate with a vector.\n */\nexport type VectorizeVectorMetadata =\n  | VectorizeVectorMetadataValue\n  | Record<string, VectorizeVectorMetadataValue>;\nexport type VectorFloatArray = Float32Array | Float64Array;\nexport interface VectorizeError {\n  code?: number;\n  error: string;\n}\n/**\n * Comparison logic/operation to use for metadata filtering.\n *\n * This list is expected to grow as support for more operations are released.\n */\nexport type VectorizeVectorMetadataFilterOp =\n  | \"$eq\"\n  | \"$ne\"\n  | \"$lt\"\n  | \"$lte\"\n  | \"$gt\"\n  | \"$gte\";\nexport type VectorizeVectorMetadataFilterCollectionOp = \"$in\" | \"$nin\";\n/**\n * Filter criteria for vector metadata used to limit the retrieved query result set.\n */\nexport type VectorizeVectorMetadataFilter = {\n  [field: string]:\n    | Exclude<VectorizeVectorMetadataValue, string[]>\n    | null\n    | {\n        [Op in VectorizeVectorMetadataFilterOp]?: Exclude<\n          VectorizeVectorMetadataValue,\n          string[]\n        > | null;\n      }\n    | {\n        [Op in VectorizeVectorMetadataFilterCollectionOp]?: Exclude<\n          VectorizeVectorMetadataValue,\n          string[]\n        >[];\n      };\n};\n/**\n * Supported distance metrics for an index.\n * Distance metrics determine how other \"similar\" vectors are determined.\n */\nexport type VectorizeDistanceMetric = \"euclidean\" | \"cosine\" | \"dot-product\";\n/**\n * Metadata return levels for a Vectorize query.\n *\n * Default to \"none\".\n *\n * @property all      Full metadata for the vector return set, including all fields (including those un-indexed) without truncation. This is a more expensive retrieval, as it requires additional fetching & reading of un-indexed data.\n * @property indexed  Return all metadata fields configured for indexing in the vector return set. This level of retrieval is \"free\" in that no additional overhead is incurred returning this data. However, note that indexed metadata is subject to truncation (especially for larger strings).\n * @property none     No indexed metadata will be returned.\n */\nexport type VectorizeMetadataRetrievalLevel = \"all\" | \"indexed\" | \"none\";\nexport interface VectorizeQueryOptions {\n  topK?: number;\n  namespace?: string;\n  returnValues?: boolean;\n  returnMetadata?: boolean | VectorizeMetadataRetrievalLevel;\n  filter?: VectorizeVectorMetadataFilter;\n}\n/**\n * Information about the configuration of an index.\n */\nexport type VectorizeIndexConfig =\n  | {\n      dimensions: number;\n      metric: VectorizeDistanceMetric;\n    }\n  | {\n      preset: string; // keep this generic, as we'll be adding more presets in the future and this is only in a read capacity\n    };\n/**\n * Metadata about an existing index.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link VectorizeIndexInfo} for its post-beta equivalent.\n */\nexport interface VectorizeIndexDetails {\n  /** The unique ID of the index */\n  readonly id: string;\n  /** The name of the index. */\n  name: string;\n  /** (optional) A human readable description for the index. */\n  description?: string;\n  /** The index configuration, including the dimension size and distance metric. */\n  config: VectorizeIndexConfig;\n  /** The number of records containing vectors within the index. */\n  vectorsCount: number;\n}\n/**\n * Metadata about an existing index.\n */\nexport interface VectorizeIndexInfo {\n  /** The number of records containing vectors within the index. */\n  vectorCount: number;\n  /** Number of dimensions the index has been configured for. */\n  dimensions: number;\n  /** ISO 8601 datetime of the last processed mutation on in the index. All changes before this mutation will be reflected in the index state. */\n  processedUpToDatetime: number;\n  /** UUIDv4 of the last mutation processed by the index. All changes before this mutation will be reflected in the index state. */\n  processedUpToMutation: number;\n}\n/**\n * Represents a single vector value set along with its associated metadata.\n */\nexport interface VectorizeVector {\n  /** The ID for the vector. This can be user-defined, and must be unique. It should uniquely identify the object, and is best set based on the ID of what the vector represents. */\n  id: string;\n  /** The vector values */\n  values: VectorFloatArray | number[];\n  /** The namespace this vector belongs to. */\n  namespace?: string;\n  /** Metadata associated with the vector. Includes the values of other fields and potentially additional details. */\n  metadata?: Record<string, VectorizeVectorMetadata>;\n}\n/**\n * Represents a matched vector for a query along with its score and (if specified) the matching vector information.\n */\nexport type VectorizeMatch = Pick<Partial<VectorizeVector>, \"values\"> &\n  Omit<VectorizeVector, \"values\"> & {\n    /** The score or rank for similarity, when returned as a result */\n    score: number;\n  };\n/**\n * A set of matching {@link VectorizeMatch} for a particular query.\n */\nexport interface VectorizeMatches {\n  matches: VectorizeMatch[];\n  count: number;\n}\n/**\n * Results of an operation that performed a mutation on a set of vectors.\n * Here, `ids` is a list of vectors that were successfully processed.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link VectorizeAsyncMutation} for its post-beta equivalent.\n */\nexport interface VectorizeVectorMutation {\n  /* List of ids of vectors that were successfully processed. */\n  ids: string[];\n  /* Total count of the number of processed vectors. */\n  count: number;\n}\n/**\n * Result type indicating a mutation on the Vectorize Index.\n * Actual mutations are processed async where the `mutationId` is the unique identifier for the operation.\n */\nexport interface VectorizeAsyncMutation {\n  /** The unique identifier for the async mutation operation containing the changeset. */\n  mutationId: string;\n}\n/**\n * A Vectorize Vector Search Index for querying vectors/embeddings.\n *\n * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released.\n * See {@link Vectorize} for its new implementation.\n */\nexport declare abstract class VectorizeIndex {\n  /**\n   * Get information about the currently bound index.\n   * @returns A promise that resolves with information about the current index.\n   */\n  public describe(): Promise<VectorizeIndexDetails>;\n  /**\n   * Use the provided vector to perform a similarity search across the index.\n   * @param vector Input vector that will be used to drive the similarity search.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  public query(\n    vector: VectorFloatArray | number[],\n    options?: VectorizeQueryOptions,\n  ): Promise<VectorizeMatches>;\n  /**\n   * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown.\n   * @param vectors List of vectors that will be inserted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed.\n   */\n  public insert(vectors: VectorizeVector[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values.\n   * @param vectors List of vectors that will be upserted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed.\n   */\n  public upsert(vectors: VectorizeVector[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Delete a list of vectors with a matching id.\n   * @param ids List of vector ids that should be deleted.\n   * @returns A promise that resolves with the ids & count of records that were successfully processed (and thus deleted).\n   */\n  public deleteByIds(ids: string[]): Promise<VectorizeVectorMutation>;\n  /**\n   * Get a list of vectors with a matching id.\n   * @param ids List of vector ids that should be returned.\n   * @returns A promise that resolves with the raw unscored vectors matching the id set.\n   */\n  public getByIds(ids: string[]): Promise<VectorizeVector[]>;\n}\n/**\n * A Vectorize Vector Search Index for querying vectors/embeddings.\n *\n * Mutations in this version are async, returning a mutation id.\n */\nexport declare abstract class Vectorize {\n  /**\n   * Get information about the currently bound index.\n   * @returns A promise that resolves with information about the current index.\n   */\n  public describe(): Promise<VectorizeIndexInfo>;\n  /**\n   * Use the provided vector to perform a similarity search across the index.\n   * @param vector Input vector that will be used to drive the similarity search.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  public query(\n    vector: VectorFloatArray | number[],\n    options?: VectorizeQueryOptions,\n  ): Promise<VectorizeMatches>;\n  /**\n   * Use the provided vector-id to perform a similarity search across the index.\n   * @param vectorId Id for a vector in the index against which the index should be queried.\n   * @param options Configuration options to massage the returned data.\n   * @returns A promise that resolves with matched and scored vectors.\n   */\n  public queryById(\n    vectorId: string,\n    options?: VectorizeQueryOptions,\n  ): Promise<VectorizeMatches>;\n  /**\n   * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown.\n   * @param vectors List of vectors that will be inserted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the insert changeset.\n   */\n  public insert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values.\n   * @param vectors List of vectors that will be upserted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the upsert changeset.\n   */\n  public upsert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Delete a list of vectors with a matching id.\n   * @param ids List of vector ids that should be deleted.\n   * @returns A promise that resolves with a unique identifier of a mutation containing the delete changeset.\n   */\n  public deleteByIds(ids: string[]): Promise<VectorizeAsyncMutation>;\n  /**\n   * Get a list of vectors with a matching id.\n   * @param ids List of vector ids that should be returned.\n   * @returns A promise that resolves with the raw unscored vectors matching the id set.\n   */\n  public getByIds(ids: string[]): Promise<VectorizeVector[]>;\n}\n/**\n * The interface for \"version_metadata\" binding\n * providing metadata about the Worker Version using this binding.\n */\nexport type WorkerVersionMetadata = {\n  /** The ID of the Worker Version using this binding */\n  id: string;\n  /** The tag of the Worker Version using this binding */\n  tag: string;\n  /** The timestamp of when the Worker Version was uploaded */\n  timestamp: string;\n};\nexport interface DynamicDispatchLimits {\n  /**\n   * Limit CPU time in milliseconds.\n   */\n  cpuMs?: number;\n  /**\n   * Limit number of subrequests.\n   */\n  subRequests?: number;\n}\nexport interface DynamicDispatchOptions {\n  /**\n   * Limit resources of invoked Worker script.\n   */\n  limits?: DynamicDispatchLimits;\n  /**\n   * Arguments for outbound Worker script, if configured.\n   */\n  outbound?: {\n    [key: string]: any;\n  };\n}\nexport interface DispatchNamespace {\n  /**\n   * @param name Name of the Worker script.\n   * @param args Arguments to Worker script.\n   * @param options Options for Dynamic Dispatch invocation.\n   * @returns A Fetcher object that allows you to send requests to the Worker script.\n   * @throws If the Worker script does not exist in this dispatch namespace, an error will be thrown.\n   */\n  get(\n    name: string,\n    args?: {\n      [key: string]: any;\n    },\n    options?: DynamicDispatchOptions,\n  ): Fetcher;\n}\nexport declare abstract class Workflow<PARAMS = unknown> {\n  /**\n   * Get a handle to an existing instance of the Workflow.\n   * @param id Id for the instance of this Workflow\n   * @returns A promise that resolves with a handle for the Instance\n   */\n  public get(id: string): Promise<WorkflowInstance>;\n  /**\n   * Create a new instance and return a handle to it. If a provided id exists, an error will be thrown.\n   * @param options Options when creating an instance including id and params\n   * @returns A promise that resolves with a handle for the Instance\n   */\n  public create(\n    options?: WorkflowInstanceCreateOptions<PARAMS>,\n  ): Promise<WorkflowInstance>;\n  /**\n   * Create a batch of instances and return handle for all of them. If a provided id exists, an error will be thrown.\n   * `createBatch` is limited at 100 instances at a time or when the RPC limit for the batch (1MiB) is reached.\n   * @param batch List of Options when creating an instance including name and params\n   * @returns A promise that resolves with a list of handles for the created instances.\n   */\n  public createBatch(\n    batch: WorkflowInstanceCreateOptions<PARAMS>[],\n  ): Promise<WorkflowInstance[]>;\n}\nexport type WorkflowDurationLabel =\n  | \"second\"\n  | \"minute\"\n  | \"hour\"\n  | \"day\"\n  | \"week\"\n  | \"month\"\n  | \"year\";\nexport type WorkflowSleepDuration =\n  | `${number} ${WorkflowDurationLabel}${\"s\" | \"\"}`\n  | number;\nexport type WorkflowRetentionDuration = WorkflowSleepDuration;\nexport interface WorkflowInstanceCreateOptions<PARAMS = unknown> {\n  /**\n   * An id for your Workflow instance. Must be unique within the Workflow.\n   */\n  id?: string;\n  /**\n   * The event payload the Workflow instance is triggered with\n   */\n  params?: PARAMS;\n  /**\n   * The retention policy for Workflow instance.\n   * Defaults to the maximum retention period available for the owner's account.\n   */\n  retention?: {\n    successRetention?: WorkflowRetentionDuration;\n    errorRetention?: WorkflowRetentionDuration;\n  };\n}\nexport type InstanceStatus = {\n  status:\n    | \"queued\" // means that instance is waiting to be started (see concurrency limits)\n    | \"running\"\n    | \"paused\"\n    | \"errored\"\n    | \"terminated\" // user terminated the instance while it was running\n    | \"complete\"\n    | \"waiting\" // instance is hibernating and waiting for sleep or event to finish\n    | \"waitingForPause\" // instance is finishing the current work to pause\n    | \"unknown\";\n  error?: {\n    name: string;\n    message: string;\n  };\n  output?: unknown;\n};\nexport interface WorkflowError {\n  code?: number;\n  message: string;\n}\nexport declare abstract class WorkflowInstance {\n  public id: string;\n  /**\n   * Pause the instance.\n   */\n  public pause(): Promise<void>;\n  /**\n   * Resume the instance. If it is already running, an error will be thrown.\n   */\n  public resume(): Promise<void>;\n  /**\n   * Terminate the instance. If it is errored, terminated or complete, an error will be thrown.\n   */\n  public terminate(): Promise<void>;\n  /**\n   * Restart the instance.\n   */\n  public restart(): Promise<void>;\n  /**\n   * Returns the current status of the instance.\n   */\n  public status(): Promise<InstanceStatus>;\n  /**\n   * Send an event to this instance.\n   */\n  public sendEvent({\n    type,\n    payload,\n  }: {\n    type: string;\n    payload: unknown;\n  }): Promise<void>;\n}\n"
  },
  {
    "path": "types/scripts/build-types.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from \"node:assert\";\nimport childProcess from \"node:child_process\";\nimport events from \"node:events\";\nimport { readFileSync, readdirSync } from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport ts from \"typescript\";\nimport { SourcesMap, createMemoryProgram } from \"../src/program\";\nimport { getFilePath } from \"../src/utils\";\n\nconst OUTPUT_PATH = getFilePath(\"types/definitions\");\nconst ENTRYPOINTS = [\n  { compatDate: \"2021-01-01\", name: \"oldest\" },\n  // https://developers.cloudflare.com/workers/platform/compatibility-dates/#formdata-parsing-supports-file\n  { compatDate: \"2021-11-03\" },\n  // https://developers.cloudflare.com/workers/platform/compatibility-dates/#settersgetters-on-api-object-prototypes\n  { compatDate: \"2022-01-31\" },\n  // https://developers.cloudflare.com/workers/platform/compatibility-dates/#global-navigator\n  { compatDate: \"2022-03-21\" },\n  // https://developers.cloudflare.com/workers/platform/compatibility-dates/#r2-bucket-list-respects-the-include-option\n  { compatDate: \"2022-08-04\" },\n  // https://developers.cloudflare.com/workers/platform/compatibility-dates/#new-url-parser-implementation\n  { compatDate: \"2022-10-31\" },\n  // https://developers.cloudflare.com/workers/platform/compatibility-dates/#streams-constructors\n  // https://developers.cloudflare.com/workers/platform/compatibility-dates/#compliant-transformstream-constructor\n  { compatDate: \"2022-11-30\" },\n  // https://github.com/cloudflare/workerd/blob/fcb6f33d10c71975cb2ce68dbf1924a1eeadbd8a/src/workerd/io/compatibility-date.capnp#L275-L280 (http_headers_getsetcookie)\n  { compatDate: \"2023-03-01\" },\n  // https://github.com/cloudflare/workerd/blob/fcb6f33d10c71975cb2ce68dbf1924a1eeadbd8a/src/workerd/io/compatibility-date.capnp#L307-L312 (urlsearchparams_delete_has_value_arg)\n  { compatDate: \"2023-07-01\" },\n  // Latest compatibility date with experimental features\n  { compatDate: \"2999-12-31\", name: \"latest\" },\n  // Latest compatibility date with experimental features\n  { compatDate: \"experimental\" },\n];\n\n/**\n * Copy all TS lib files into the memory filesystem. We only use lib.esnext\n * but since TS lib files all reference each other to various extents it's\n * easier to add them all and let TS figure out which ones it actually needs to load.\n * This function uses the current local installation of TS as a source for lib files\n */\nlet cachedLibFiles: SourcesMap | null = null;\nfunction loadLibFiles(): SourcesMap {\n  if (cachedLibFiles != null) {\n    return cachedLibFiles;\n  }\n\n  const libLocation = path.dirname(require.resolve(\"typescript\"));\n  const libFiles = readdirSync(libLocation).filter(\n    (file) => file.startsWith(\"lib.\") && file.endsWith(\".d.ts\")\n  );\n  const lib: SourcesMap = new Map();\n  for (const file of libFiles) {\n    lib.set(\n      `/node_modules/typescript/lib/${file}`,\n      readFileSync(path.join(libLocation, file), \"utf-8\")\n    );\n  }\n\n  cachedLibFiles = lib;\n  return lib;\n}\n\nfunction checkDiagnostics(sources: SourcesMap): void {\n  const program = createMemoryProgram(\n    sources,\n    undefined,\n    {\n      noEmit: true,\n      lib: [\"lib.esnext.d.ts\"],\n      types: [],\n      noUnusedParameters: true,\n    },\n    loadLibFiles()\n  );\n\n  const emitResult = program.emit();\n\n  const allDiagnostics = ts\n    .getPreEmitDiagnostics(program)\n    .concat(emitResult.diagnostics);\n\n  allDiagnostics.forEach((diagnostic) => {\n    if (diagnostic.file) {\n      const { line, character } = ts.getLineAndCharacterOfPosition(\n        diagnostic.file,\n        diagnostic.start\n      );\n      const message = ts.flattenDiagnosticMessageText(\n        diagnostic.messageText,\n        \"\\n\"\n      );\n      console.log(\n        `${diagnostic.file.fileName}:${line + 1}:${character + 1} : ${message}`\n      );\n    } else {\n      console.log(\n        ts.flattenDiagnosticMessageText(diagnostic.messageText, \"\\n\")\n      );\n    }\n  });\n\n  assert(allDiagnostics.length === 0, \"TypeScript failed to compile!\");\n}\n\nfunction spawnWorkerd(\n  configPath: string\n): Promise<{ url: URL; kill: () => Promise<void> }> {\n  return new Promise((resolve) => {\n    const workerdProcess = childProcess.spawn(\n      getFilePath(\"src/workerd/server/workerd\"),\n      [\"serve\", \"--verbose\", \"--experimental\", \"--control-fd=3\", configPath],\n      { stdio: [\"inherit\", \"inherit\", \"inherit\", \"pipe\"] }\n    );\n    const exitPromise = events.once(workerdProcess, \"exit\");\n    workerdProcess.stdio[3]?.on(\"data\", (chunk: Buffer): void => {\n      const message = JSON.parse(chunk.toString().trim()) as {\n        event: string\n        port: number\n      };\n      assert.strictEqual(message.event, \"listen\");\n      resolve({\n        url: new URL(`http://127.0.0.1:${message.port}`),\n        async kill() {\n          workerdProcess.kill(\"SIGTERM\");\n          await exitPromise;\n        },\n      });\n    });\n  });\n}\n\nasync function buildEntrypoint(\n  entrypoint: (typeof ENTRYPOINTS)[number],\n  workerUrl: URL\n): Promise<{ name: string; files: Array<{ fileName: string; content: string }> }> {\n  const url = new URL(`/${entrypoint.compatDate}.bundle`, workerUrl);\n  const response = await fetch(url);\n  if (!response.ok) throw new Error(await response.text());\n  const bundle = await response.formData();\n\n  const name = entrypoint.name ?? entrypoint.compatDate;\n  const entrypointPath = path.join(OUTPUT_PATH, name);\n  await fs.mkdir(entrypointPath, { recursive: true });\n\n  const files: Array<{ fileName: string; content: string }> = [];\n\n  const filePromises: Promise<void>[] = [];\n\n  for (const [fileName, definitions] of bundle) {\n    assert(typeof definitions === \"string\");\n    const prettierIgnoreRegexp = /^\\s*\\/\\/\\s*prettier-ignore\\s*\\n/gm;\n    const typings = definitions.replaceAll(prettierIgnoreRegexp, \"\");\n\n    files.push({ fileName, content: typings });\n    filePromises.push(fs.writeFile(path.join(entrypointPath, fileName), typings));\n  }\n\n  // Write all files in parallel (without prettier formatting)\n  await Promise.all(filePromises);\n\n  return { name, files };\n}\n\nasync function buildAllEntrypoints(workerUrl: URL): Promise<void> {\n  const allEntrypoints = await Promise.all(\n    ENTRYPOINTS.map(entrypoint => buildEntrypoint(entrypoint, workerUrl))\n  );\n\n  // Format all TypeScript files with a single Prettier CLI call using exact same defaults as API\n  const prettierPath = require.resolve(\"prettier/bin/prettier.cjs\");\n  childProcess.execSync(`${prettierPath} \"${OUTPUT_PATH}/**/*.ts\" --write --parser=typescript`);\n\n  for (const { files } of allEntrypoints) {\n    const entrypointFiles = new SourcesMap();\n    for (const { fileName, content } of files) {\n      entrypointFiles.set(`/$virtual/${fileName}`, content);\n    }\n\n    if (entrypointFiles.size > 0) {\n      checkDiagnostics(entrypointFiles);\n    }\n  }\n}\nexport async function main(): Promise<void> {\n  const worker = await spawnWorkerd(getFilePath(\"types/scripts/config.capnp\"));\n  try {\n    await buildAllEntrypoints(worker.url);\n  } finally {\n    await worker.kill();\n  }\n}\n\n// Outputting to a CommonJS module so can't use top-level await\nif (require.main === module) void main();\n"
  },
  {
    "path": "types/scripts/build-worker.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from \"node:assert\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { build, type PluginBuild, type OnResolveArgs, type OnLoadArgs } from \"esbuild\";\nimport { CommentsData } from \"src/transforms\";\nimport cloudflareComments from \"../src/cloudflare\";\nimport { collateStandardComments } from \"../src/standards\";\nimport { getFilePath } from \"../src/utils\";\n\nasync function readPath(rootPath: string): Promise<string> {\n  try {\n    return await fs.readFile(rootPath, \"utf8\");\n  } catch (e) {\n    if (!(e && typeof e === \"object\" && \"code\" in e && e.code === \"EISDIR\"))\n      throw e;\n    const fileNames = await fs.readdir(rootPath);\n    const contentsPromises = fileNames.map((fileName) => {\n      const filePath = path.join(rootPath, fileName);\n      return readPath(filePath);\n    });\n    const contents = await Promise.all(contentsPromises);\n    return contents.join(\"\\n\");\n  }\n}\n\nasync function readParamNames(): Promise<Record<string, Record<string, string[]>>> {\n  // Support methods defined in parent classes\n  const additionalClassNames: [string, string][] = [\n    [\"DurableObjectStorageOperations\", \"DurableObjectStorage\"],\n    [\"DurableObjectStorageOperations\", \"DurableObjectTransaction\"],\n  ];\n\n  const data = await fs.readFile(getFilePath(\"src/workerd/tools/param-names.json\"), \"utf8\");\n  const recordArray = JSON.parse(data) as {\n    fully_qualified_parent_name: string[];\n    function_like_name: string;\n    index: number;\n    name: string;\n  }[];\n\n  function registerApi(\n    structureName: string,\n    record: (typeof recordArray)[number]\n  ): void {\n    let functionName: string = record.function_like_name;\n    if (functionName.endsWith(\"_\")) functionName = functionName.slice(0, -1);\n    // `constructor` is a reserved property name\n    if (functionName === \"constructor\") functionName = `$${functionName}`;\n\n    result[structureName] ??= {};\n    const structureRecord = result[structureName];\n\n    structureRecord[functionName] ??= [];\n    const functionArray = structureRecord[functionName];\n    functionArray[record.index] = record.name;\n  }\n\n  const result: Record<string, Record<string, string[]>> = {};\n  for (const record of recordArray) {\n    const structureName: string = record.fully_qualified_parent_name\n      .filter(Boolean)\n      .join(\"::\");\n\n    registerApi(structureName, record);\n\n    for (const [className, renamedClass] of additionalClassNames) {\n      if (structureName.includes(className)) {\n        registerApi(structureName.replace(className, renamedClass), record);\n      }\n    }\n  }\n  return result;\n}\n\nexport function readComments(): CommentsData {\n  const comments = collateStandardComments(\n    path.join(\n      path.dirname(require.resolve(\"typescript\")),\n      \"lib.webworker.d.ts\"\n    ),\n    path.join(\n      path.dirname(require.resolve(\"typescript\")),\n      \"lib.webworker.iterable.d.ts\"\n    )\n  );\n\n  // We want to deep merge here so that our comment overrides can be very targeted\n  for (const [name, members] of Object.entries(cloudflareComments)) {\n    comments[name] ??= {};\n    for (const [member, comment] of Object.entries(members)) {\n      const apiEntry = comments[name];\n      assert(apiEntry !== undefined);\n      apiEntry[member] = comment as string;\n    }\n  }\n  return comments;\n}\n\nif (require.main === module)\n  void build({\n    logLevel: \"info\",\n    format: \"esm\",\n    target: \"esnext\",\n    external: [\"node:*\", \"workerd:*\"],\n    bundle: true,\n    minify: true,\n    outdir: getFilePath(\"types/dist\"),\n    outExtension: { \".js\": \".mjs\" },\n    entryPoints: [getFilePath(\"types/src/worker/index.ts\")],\n    plugins: [\n      {\n        name: \"raw\",\n        setup(build: PluginBuild): void {\n          build.onResolve({ filter: /^raw:/ }, (args: OnResolveArgs): {\n            namespace: string,\n            path: string\n          } => {\n            const resolved = path.resolve(args.resolveDir, args.path.slice(4));\n            return { namespace: \"raw\", path: resolved };\n          });\n          build.onLoad({ namespace: \"raw\", filter: /.*/ }, async (args: OnLoadArgs) => {\n            const contents = await readPath(args.path);\n            return { contents, loader: \"text\" };\n          });\n        },\n      },\n      {\n        name: \"virtual\",\n        setup(build: PluginBuild): void {\n          build.onResolve({ filter: /^virtual:/ }, (args: OnResolveArgs) => {\n            return {\n              namespace: \"virtual\",\n              path: args.path.substring(\"virtual:\".length),\n            };\n          });\n          build.onLoad({ namespace: \"virtual\", filter: /.*/ }, async (args: OnLoadArgs) => {\n            if (args.path === \"param-names.json\") {\n              const contents = await readParamNames();\n              return { contents: JSON.stringify(contents), loader: \"json\" };\n            }\n            if (args.path === \"comments.json\") {\n              const comments = readComments();\n              return { contents: JSON.stringify(comments), loader: \"json\" };\n            }\n          });\n        },\n      },\n    ],\n  });\n"
  },
  {
    "path": "types/scripts/config.capnp",
    "content": "using Workerd = import \"/workerd/workerd.capnp\";\n\nconst config :Workerd.Config = (\n  services = [\n    ( name = \"main\", worker = .worker ),\n  ],\n  sockets = [\n    ( name = \"http\", address = \"127.0.0.1:0\", http = (), service = \"main\" ),\n  ]\n);\n\nconst worker :Workerd.Worker = (\n  compatibilityDate = \"2024-01-01\",\n  compatibilityFlags = [\"nodejs_compat\", \"rtti_api\"],\n  modules = [\n    ( name = \"./index.mjs\", esModule = embed \"../dist/index.mjs\" )\n  ],\n);\n"
  },
  {
    "path": "types/src/cloudflare.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// This file extends `standards.ts` with specific comments overrides for Cloudflare Workers APIs\n// that aren't adequately described by a standard .d.ts file\n\nimport { CommentsData } from \"./transforms\";\nexport default {\n  caches: {\n    $: `*\n* The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.\n*\n* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)\n`,\n  },\n  CacheStorage: {\n    $: `*\n* The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.\n*\n* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)\n`,\n  },\n  Cache: {\n    $: `*\n* The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.\n*\n* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)\n`,\n    delete: ` [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#delete) `,\n    match: ` [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#match) `,\n    put: ` [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#put) `,\n  },\n  crypto: {\n    $: `*\n* The Web Crypto API provides a set of low-level functions for common cryptographic tasks.\n* The Workers runtime implements the full surface of this API, but with some differences in\n* the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms)\n* compared to those implemented in most browsers.\n*\n* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/)\n`,\n  },\n  Crypto: {\n    $: `*\n* The Web Crypto API provides a set of low-level functions for common cryptographic tasks.\n* The Workers runtime implements the full surface of this API, but with some differences in\n* the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms)\n* compared to those implemented in most browsers.\n*\n* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/)\n`,\n  },\n  performance: {\n    $: `*\n* The Workers runtime supports a subset of the Performance API, used to measure timing and performance,\n* as well as timing of subrequests and other operations.\n*\n* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/)\n`,\n  },\n  Performance: {\n    $: `*\n* The Workers runtime supports a subset of the Performance API, used to measure timing and performance,\n* as well as timing of subrequests and other operations.\n*\n* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/)\n`,\n    timeOrigin: ` [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancetimeorigin) `,\n    now: ` [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancenow) `,\n  },\n  self: {\n    $: undefined,\n  },\n  navigator: {\n    $: undefined,\n  },\n  origin: {\n    $: undefined,\n  },\n  WorkerGlobalScope: {\n    $: undefined,\n  },\n  WebSocketAcceptOptions: {\n    allowHalfOpen: `*\n * When set to \\`true\\`, receiving a server-initiated WebSocket Close frame will not\n * automatically send a reciprocal Close frame, leaving the connection in a half-open\n * state. This is useful for proxying scenarios where you need to coordinate closing\n * both sides independently. Defaults to \\`false\\` when the\n * \\`no_web_socket_half_open_by_default\\` compatibility flag is enabled.\n `,\n  },\n} satisfies CommentsData;\n"
  },
  {
    "path": "types/src/generator/index.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'node:assert';\nimport {\n  FunctionType,\n  Member,\n  Member_Which,\n  Method,\n  Structure,\n  StructureGroups,\n  Type,\n  Type_Which,\n} from '@workerd/jsg/rtti';\nimport ts from 'typescript';\nimport { createStructureNode } from './structure';\n\nexport { getTypeName } from './type';\n\nexport type StructureMap = Map<string, Structure>;\n// Builds a lookup table mapping type names to structures\nfunction collectStructureMap(root: StructureGroups): StructureMap {\n  const map = new Map<string, Structure>();\n  root.groups.forEach((group) => {\n    group.structures.forEach((structure) => {\n      map.set(structure.fullyQualifiedName, structure);\n    });\n  });\n  return map;\n}\n\n// Builds a set containing the names of structures that should be included\n// in the definitions, because they are referenced by root types or any of their\n// children. A struct/resource type is marked as a root type using a\n// `JSG_(STRUCT_)TS_ROOT` macro.\n//\n// We need to do this as some types should only be included in the definitions\n// when certain compatibility flags are enabled (e.g. `Navigator`,\n// standards-compliant `URL`). However, these types are always included in\n// the `*_TYPES` macros.\nfunction collectIncluded(map: StructureMap, root?: string): Set<string> {\n  const included = new Set<string>();\n\n  function visitType(type: Type): void {\n    switch (type.which()) {\n      case Type_Which.PROMISE: {\n        visitType(type.promise.value);\n        return;\n      }\n      case Type_Which.STRUCTURE: {\n        const name = type.structure.fullyQualifiedName;\n        const structure = map.get(name);\n        assert(structure !== undefined, `Unknown structure type: ${name}`);\n        {\n          visitStructure(structure);\n          return;\n        }\n      }\n      case Type_Which.ARRAY: {\n        visitType(type.array.element);\n        return;\n      }\n      case Type_Which.MAYBE: {\n        visitType(type.maybe.value);\n        return;\n      }\n      case Type_Which.DICT: {\n        const dict = type.dict;\n        visitType(dict.key);\n        visitType(dict.value);\n        return;\n      }\n      case Type_Which.ONE_OF: {\n        type.oneOf.variants.forEach(visitType);\n        return;\n      }\n      case Type_Which.FUNCTION: {\n        visitFunction(type.function);\n        return;\n      }\n    }\n  }\n\n  function visitFunction(func: FunctionType | Method): void {\n    func.args.forEach(visitType);\n    visitType(func.returnType);\n  }\n\n  function visitMember(member: Member): void {\n    switch (member.which()) {\n      case Member_Which.METHOD: {\n        visitFunction(member.method);\n        return;\n      }\n      case Member_Which.PROPERTY: {\n        visitType(member.property.type);\n        return;\n      }\n      case Member_Which.NESTED: {\n        visitStructure(member.nested.structure);\n        return;\n      }\n      case Member_Which.CONSTRUCTOR: {\n        member.$constructor.args.forEach(visitType);\n        return;\n      }\n    }\n  }\n\n  function visitStructure(structure: Structure): void {\n    const name = structure.fullyQualifiedName;\n    if (included.has(name)) return;\n    included.add(name);\n    structure.members.forEach(visitMember);\n    if (structure._hasExtends()) {\n      visitType(structure.extends);\n    }\n    if (structure._hasIterator()) {\n      visitFunction(structure.iterator);\n    }\n    if (structure._hasAsyncIterator()) {\n      visitFunction(structure.asyncIterator);\n    }\n  }\n\n  if (root === undefined) {\n    // If no root was specified, visit all structures with\n    // `JSG_(STRUCT_)TS_ROOT` macros\n    for (const structure of map.values()) {\n      if (structure.tsRoot) visitStructure(structure);\n    }\n  } else {\n    // Otherwise, visit just that root\n    const structure = map.get(root);\n    assert(structure !== undefined, `Unknown root: ${root}`);\n    visitStructure(structure);\n  }\n\n  return included;\n}\n\n// Builds a set containing the names of structures that must be declared as\n// `class`es rather than `interface`s because they either:\n// 1) Get inherited by another class (`class` `extends` requires another `class`)\n// 2) Are constructible (`constructor(...)`s can only appear in `class`es)\n// 3) Have `static` methods (`static`s can only appear in `class`es)\n// 4) Are a nested type (users could call `instanceof` with the type)\nfunction collectClasses(map: StructureMap): Set<string> {\n  const classes = new Set<string>();\n  for (const structure of map.values()) {\n    // 1) Add all classes inherited by this class\n    if (structure._hasExtends()) {\n      const extendsType = structure.extends;\n      if (extendsType._isStructure) {\n        classes.add(extendsType.structure.fullyQualifiedName);\n      }\n    }\n\n    structure.members.forEach((member) => {\n      // 2) Add this class if it's constructible\n      if (member._isConstructor) {\n        classes.add(structure.fullyQualifiedName);\n      }\n      // 3) Add this class if it contains static methods\n      if (member._isMethod && member.method.static) {\n        classes.add(structure.fullyQualifiedName);\n      }\n      // 4) Add all nested types defined by this class\n      if (member._isNested) {\n        classes.add(member.nested.structure.fullyQualifiedName);\n      }\n    });\n  }\n  return classes;\n}\n\nexport function generateDefinitions(root: StructureGroups): {\n  nodes: ts.Statement[];\n  structureMap: StructureMap;\n} {\n  const structureMap = collectStructureMap(root);\n  const globalIncluded = collectIncluded(structureMap);\n  const classes = collectClasses(structureMap);\n\n  // Can't use `flatMap()` here as `getGroups()` returns a `capnp.List`\n  const nodes = root.groups.map((group) => {\n    const structureNodes: ts.Statement[] = [];\n    group.structures.forEach((structure) => {\n      const name = structure.fullyQualifiedName;\n      if (globalIncluded.has(name)) {\n        const asClass = classes.has(name);\n        structureNodes.push(createStructureNode(structure, { asClass }));\n      }\n    });\n    return structureNodes;\n  });\n  const flatNodes = nodes.flat();\n\n  return { nodes: flatNodes, structureMap };\n}\n\nexport function collectTypeScriptModules(root: StructureGroups): string {\n  let result = '';\n\n  root.modules.forEach((module) => {\n    if (!module._isTsDeclarations) return;\n    const declarations = module.tsDeclarations\n      // Looks for any lines starting with `///`, which indicates a TypeScript\n      // Triple-Slash Directive (https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html)\n      .replaceAll(/^\\/\\/\\/.+$/gm, (match) => {\n        assert.strictEqual(\n          match,\n          '/// <reference types=\"@workerd/types-internal\" />',\n          `Unexpected triple-slash directive, got ${match}`\n        );\n        return '';\n      });\n\n    result += `declare module \"${module.specifier}\" {\\n${declarations}\\n}\\n`;\n  });\n\n  return result;\n}\n"
  },
  {
    "path": "types/src/generator/parameter-names.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport type ParameterNamesData = Record<\n  /* fullyQualifiedParentName */ string,\n  Record</* functionName */ string, string[] | undefined> | undefined\n>;\n\nlet data: ParameterNamesData | undefined;\nexport function installParameterNames(newData: ParameterNamesData): void {\n  data = newData;\n}\n\nconst reservedKeywords = [\"function\", \"number\", \"string\"];\n\nexport function getParameterName(\n  fullyQualifiedParentName: string,\n  functionName: string,\n  index: number\n): string {\n  // `constructor` is a reserved property name\n  if (functionName === \"constructor\") functionName = `$${functionName}`;\n  const name = data?.[fullyQualifiedParentName]?.[functionName]?.[index];\n  if (name === undefined) return `param${index}`;\n  if (reservedKeywords.includes(name)) return `$${name}`;\n  return name;\n}\n"
  },
  {
    "path": "types/src/generator/structure.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// TODO(soon): Fallthrough have false positives here. Investigate this.\n/* eslint-disable no-fallthrough */\n\nimport assert from 'node:assert';\nimport {\n  Constant,\n  Member,\n  Member_Nested,\n  Member_Which,\n  Method,\n  Property,\n  Structure,\n} from '@workerd/jsg/rtti';\nimport ts, { factory as f } from 'typescript';\nimport { printNode } from '../print';\nimport {\n  createParamDeclarationNodes,\n  createTypeNode,\n  getTypeName,\n  isUnsatisfiable,\n  maybeUnwrapOptional,\n} from './type';\n\nexport const FULLY_QUALIFIED_NAME_PREFIX = 'fqn$';\n\nexport function createMethodPartial(\n  fullyQualifiedParentName: string,\n  method: Method\n): [ts.Modifier[], string, ts.ParameterDeclaration[], ts.TypeNode] {\n  const modifiers: ts.Modifier[] = [];\n  if (method.static) {\n    modifiers.push(f.createToken(ts.SyntaxKind.StaticKeyword));\n  }\n  const name = method.name;\n  const params = createParamDeclarationNodes(\n    fullyQualifiedParentName,\n    name,\n    method.args.toArray(),\n    /* forMethod */ true\n  );\n  const result = createTypeNode(method.returnType);\n  return [modifiers, name, params, result];\n}\n\nfunction createIteratorMethodPartial(\n  fullyQualifiedParentName: string,\n  method: Method,\n  isAsync: boolean\n): [ts.Modifier[], ts.PropertyName, ts.ParameterDeclaration[], ts.TypeNode] {\n  const [modifiers, , params, result] = createMethodPartial(\n    fullyQualifiedParentName,\n    method\n  );\n  const symbolIteratorExpression = f.createPropertyAccessExpression(\n    f.createIdentifier('Symbol'),\n    isAsync ? 'asyncIterator' : 'iterator'\n  );\n  const name = f.createComputedPropertyName(symbolIteratorExpression);\n  return [modifiers, name, params, result];\n}\n\nfunction createInstancePropertyPartial(\n  prop: Property\n): [ts.Modifier[], string, ts.QuestionToken | undefined, ts.TypeNode] {\n  assert(!prop.prototype);\n  const modifiers: ts.Modifier[] = [];\n  if (prop.readonly) {\n    modifiers.push(f.createToken(ts.SyntaxKind.ReadonlyKeyword));\n  }\n  const name = prop.name;\n  let value = createTypeNode(prop.type);\n\n  // If this is an optional type, use an optional property with a `?`\n  let questionToken: ts.QuestionToken | undefined;\n  const unwrappedValue = maybeUnwrapOptional(value);\n  if (unwrappedValue !== undefined) {\n    value = unwrappedValue;\n    questionToken = f.createToken(ts.SyntaxKind.QuestionToken);\n  }\n\n  return [modifiers, name, questionToken, value];\n}\n\nfunction createPrototypeProperty(\n  prop: Property\n):\n  | ts.GetAccessorDeclaration\n  | [ts.GetAccessorDeclaration, ts.SetAccessorDeclaration] {\n  assert(prop.prototype);\n  const name = prop.name;\n  const value = createTypeNode(prop.type);\n\n  const getter = f.createGetAccessorDeclaration(\n    /* modifiers */ undefined,\n    name,\n    /* params */ [],\n    value,\n    /* body */ undefined\n  );\n\n  if (prop.readonly) {\n    return getter;\n  } else {\n    const param = f.createParameterDeclaration(\n      /* modifiers */ undefined,\n      /* dotDotToken */ undefined,\n      'value',\n      /* questionToken */ undefined,\n      value\n    );\n    const setter = f.createSetAccessorDeclaration(\n      /* modifiers */ undefined,\n      name,\n      [param],\n      /* body */ undefined\n    );\n    return [getter, setter];\n  }\n}\n\nfunction createNestedPartial(nested: Member_Nested): [string, ts.TypeNode] {\n  const name = nested.name;\n  const targetName = getTypeName(nested.structure);\n  const value = f.createTypeQueryNode(f.createIdentifier(targetName));\n  // Custom `name` will be empty string if omitted, so `??` wouldn't work here\n  return [name || targetName, value];\n}\n\nfunction createConstantPartial(\n  constant: Constant\n): [ts.Modifier[], string, ts.TypeNode] {\n  const modifiers: ts.Modifier[] = [\n    f.createToken(ts.SyntaxKind.StaticKeyword),\n    f.createToken(ts.SyntaxKind.ReadonlyKeyword),\n  ];\n  const name = constant.name;\n  // Rather than using the constant value as a literal type here, just type them\n  // as `number` to encourage people to use them as constants. This is also what\n  // TypeScript does in its official lib types.\n  const valueNode = f.createTypeReferenceNode('number');\n  return [modifiers, name, valueNode];\n}\n\nfunction createInterfaceMemberNode(\n  fullyQualifiedInterfaceName: string,\n  member: Member\n): ts.TypeElement | ts.TypeElement[] {\n  let modifiers: ts.Modifier[];\n  let name: string;\n  let params: ts.ParameterDeclaration[];\n  let result: ts.TypeNode;\n  let questionToken: ts.QuestionToken | undefined;\n\n  const which = member.which();\n  // noinspection FallThroughInSwitchStatementJS\n  switch (which) {\n    case Member_Which.METHOD: {\n      const method = member.method;\n      [modifiers, name, params, result] = createMethodPartial(\n        fullyQualifiedInterfaceName,\n        method\n      );\n      return f.createMethodSignature(\n        modifiers,\n        name,\n        /* questionToken */ undefined,\n        /* typeParams */ undefined,\n        params,\n        result\n      );\n    }\n    case Member_Which.PROPERTY: {\n      const prop = member.property;\n      if (prop.prototype) {\n        return createPrototypeProperty(prop);\n      } else {\n        [modifiers, name, questionToken, result] =\n          createInstancePropertyPartial(prop);\n        return f.createPropertySignature(\n          modifiers,\n          name,\n          questionToken,\n          result\n        );\n      }\n    }\n    case Member_Which.NESTED: {\n      const nested = member.nested;\n      [name, result] = createNestedPartial(nested);\n      return f.createPropertySignature(\n        /* modifiers */ undefined,\n        name,\n        /* questionToken */ undefined,\n        result\n      );\n    }\n    case Member_Which.CONSTANT: {\n      const constant = member.constant;\n      [modifiers, name, result] = createConstantPartial(constant);\n      return f.createPropertySignature(\n        [f.createToken(ts.SyntaxKind.ReadonlyKeyword)],\n        name,\n        /* questionToken */ undefined,\n        result\n      );\n    }\n    case Member_Which.CONSTRUCTOR: {\n      assert.fail('Unexpected constructor member inside interface');\n    }\n    default: {\n      assert.fail(`Unknown member: ${which satisfies never}`);\n    }\n  }\n}\n\nfunction createIteratorInterfaceMemberNode(\n  fullyQualifiedInterfaceName: string,\n  method: Method,\n  isAsync: boolean\n): ts.TypeElement {\n  const [modifiers, name, params, result] = createIteratorMethodPartial(\n    fullyQualifiedInterfaceName,\n    method,\n    isAsync\n  );\n  return f.createMethodSignature(\n    modifiers,\n    name,\n    /* questionToken */ undefined,\n    /* typeParams */ undefined,\n    params,\n    result\n  );\n}\n\nfunction createClassMemberNode(\n  fullyQualifiedClassName: string,\n  member: Member\n): ts.ClassElement | ts.ClassElement[] {\n  let modifiers: ts.Modifier[];\n  let name: string;\n  let params: ts.ParameterDeclaration[];\n  let result: ts.TypeNode;\n  let questionToken: ts.QuestionToken | undefined;\n\n  const which = member.which();\n  switch (which) {\n    case Member_Which.METHOD: {\n      const method = member.method;\n      [modifiers, name, params, result] = createMethodPartial(\n        fullyQualifiedClassName,\n        method\n      );\n      return f.createMethodDeclaration(\n        modifiers,\n        /* asteriskToken */ undefined,\n        name,\n        /* questionToken */ undefined,\n        /* typeParameters */ undefined,\n        params,\n        result,\n        /* body */ undefined\n      );\n    }\n    case Member_Which.PROPERTY: {\n      const prop = member.property;\n      if (prop.prototype) {\n        return createPrototypeProperty(prop);\n      } else {\n        [modifiers, name, questionToken, result] =\n          createInstancePropertyPartial(prop);\n        return f.createPropertyDeclaration(\n          modifiers,\n          name,\n          questionToken,\n          result,\n          /* initializer */ undefined\n        );\n      }\n    }\n    case Member_Which.NESTED: {\n      const nested = member.nested;\n      [name, result] = createNestedPartial(nested);\n      return f.createPropertyDeclaration(\n        /* modifiers */ undefined,\n        name,\n        /* questionToken */ undefined,\n        result,\n        /* initializer */ undefined\n      );\n    }\n    case Member_Which.CONSTANT: {\n      const constant = member.constant;\n      [modifiers, name, result] = createConstantPartial(constant);\n      return f.createPropertyDeclaration(\n        modifiers,\n        name,\n        /* questionToken */ undefined,\n        result,\n        /* initializer */ undefined\n      );\n    }\n    case Member_Which.CONSTRUCTOR: {\n      const constructor = member.$constructor;\n      params = createParamDeclarationNodes(\n        fullyQualifiedClassName,\n        'constructor',\n        constructor.args.toArray(),\n        /* forMethod */ true\n      );\n      return f.createConstructorDeclaration(\n        /* modifiers */ undefined,\n        params,\n        /* body */ undefined\n      );\n    }\n    default:\n      assert.fail(`Unknown member: ${which satisfies never}`);\n  }\n}\n\nfunction createIteratorClassMemberNode(\n  fullyQualifiedClassName: string,\n  method: Method,\n  isAsync: boolean\n): ts.ClassElement {\n  const [modifiers, name, params, result] = createIteratorMethodPartial(\n    fullyQualifiedClassName,\n    method,\n    isAsync\n  );\n  return f.createMethodDeclaration(\n    modifiers,\n    /* asteriskToken */ undefined,\n    name,\n    /* questionToken */ undefined,\n    /* typeParams */ undefined,\n    params,\n    result,\n    /* body */ undefined\n  );\n}\n\n// Remove all properties with type `never` and methods with return type `never`\nfunction filterUnimplementedProperties<\n  T extends ts.TypeElement | ts.ClassElement,\n>(members: T[]): T[] {\n  return members.filter((member) => {\n    // Could collapse these `if` statements, but this is much clearer\n    if (\n      ts.isPropertySignature(member) ||\n      ts.isPropertyDeclaration(member) ||\n      ts.isGetAccessorDeclaration(member) ||\n      ts.isSetAccessorDeclaration(member) ||\n      ts.isMethodSignature(member) ||\n      ts.isMethodDeclaration(member)\n    ) {\n      if (member.type !== undefined && isUnsatisfiable(member.type)) {\n        return false;\n      }\n    }\n    return true;\n  });\n}\n\nexport interface CreateStructureNodeOptions {\n  asClass: boolean;\n  ambientContext?: boolean;\n}\nexport function createStructureNode(\n  structure: Structure,\n  opts: CreateStructureNodeOptions\n): ts.InterfaceDeclaration | ts.ClassDeclaration {\n  const { asClass, ambientContext } = opts;\n  const modifiers: ts.Modifier[] = [];\n  const name = getTypeName(structure);\n  const fullyQualifiedName = structure.fullyQualifiedName;\n\n  const heritage: ts.HeritageClause[] = [];\n  if (structure._hasExtends()) {\n    const typeNode = createTypeNode(structure.extends);\n    assert(\n      ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName),\n      `Expected type reference, got \"${printNode(typeNode)}\"`\n    );\n    const expr = f.createExpressionWithTypeArguments(\n      typeNode.typeName,\n      typeNode.typeArguments\n    );\n    heritage.push(f.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [expr]));\n  }\n\n  const members = structure.members;\n  if (asClass) {\n    // Should only add `declare` if we're not already in an ambient context\n    if (!ambientContext) {\n      modifiers.push(f.createToken(ts.SyntaxKind.DeclareKeyword));\n    }\n\n    // Can't use `flatMap()` here as `members` is a `capnp.List`\n    const classMembers = members\n      .map((member) => createClassMemberNode(fullyQualifiedName, member))\n      .flat();\n\n    const constructorIndex = classMembers.findIndex((member) =>\n      ts.isConstructorDeclaration(member)\n    );\n    if (constructorIndex === -1) {\n      // If this class doesn't have a constructor, it must be `abstract`, as we\n      // never rely on the implicit default constructor. If a class can be\n      // constructed using the empty constructor, it always defines it.\n      modifiers.push(f.createToken(ts.SyntaxKind.AbstractKeyword));\n    } else {\n      // Otherwise, ensure that the constructor always comes first\n      classMembers.unshift(...classMembers.splice(constructorIndex, 1));\n    }\n\n    // Add iterator members\n    if (structure._hasIterator()) {\n      const iterator = structure.iterator;\n      classMembers.push(\n        createIteratorClassMemberNode(fullyQualifiedName, iterator, false)\n      );\n    }\n    if (structure._hasAsyncIterator()) {\n      const iterator = structure.asyncIterator;\n      classMembers.push(\n        createIteratorClassMemberNode(fullyQualifiedName, iterator, true)\n      );\n    }\n\n    return f.createClassDeclaration(\n      modifiers,\n      name,\n      /* typeParams */ undefined,\n      heritage,\n      filterUnimplementedProperties(classMembers)\n    );\n  } else {\n    // Can't use `flatMap()` here as `members` is a `capnp.List`\n    const interfaceMembers = members\n      .map((member) => createInterfaceMemberNode(fullyQualifiedName, member))\n      .flat();\n\n    // Add iterator members\n    if (structure._hasIterator()) {\n      const iterator = structure.iterator;\n      interfaceMembers.push(\n        createIteratorInterfaceMemberNode(fullyQualifiedName, iterator, false)\n      );\n    }\n    if (structure._hasAsyncIterator()) {\n      const iterator = structure.asyncIterator;\n      interfaceMembers.push(\n        createIteratorInterfaceMemberNode(fullyQualifiedName, iterator, true)\n      );\n    }\n\n    return f.createInterfaceDeclaration(\n      modifiers,\n      name,\n      /* typeParams */ undefined,\n      heritage,\n      filterUnimplementedProperties(interfaceMembers)\n    );\n  }\n}\n"
  },
  {
    "path": "types/src/generator/type.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\n// TODO(soon): Fallthrough have false positives here. Investigate this.\n/* eslint-disable no-fallthrough */\n\nimport assert from 'node:assert';\nimport {\n  ArrayType,\n  BuiltinType_Type,\n  JsgImplType_Type,\n  MaybeType,\n  NumberType,\n  Structure,\n  StructureType,\n  Type,\n  Type_Which,\n} from '@workerd/jsg/rtti';\nimport ts, { factory as f } from 'typescript';\nimport { printNode } from '../print';\nimport { getParameterName } from './parameter-names';\n\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findLastIndex\nexport function findLastIndex<T>(\n  array: T[],\n  predicate: (value: T, index: number, array: T[]) => unknown\n): number {\n  for (let i = array.length - 1; i >= 0; i--) {\n    if (predicate(array[i], i, array)) return i;\n  }\n  return -1;\n}\n\n// If `typeNode` has the shape `T | undefined`, returns `T`, otherwise returns\n// `undefined`.\nexport function maybeUnwrapOptional(\n  typeNode: ts.TypeNode\n): ts.TypeNode | undefined {\n  if (\n    ts.isUnionTypeNode(typeNode) &&\n    typeNode.types.length === 2 &&\n    ts.isTypeReferenceNode(typeNode.types[1]) &&\n    ts.isIdentifier(typeNode.types[1].typeName) &&\n    typeNode.types[1].typeName.escapedText === 'undefined' // eslint-disable-line @typescript-eslint/no-unsafe-enum-comparison\n  ) {\n    return typeNode.types[0];\n  }\n  return undefined;\n}\n\n// Returns `true` iff this maybe type represents `T | null`, not `T | undefined`\nfunction isNullMaybe(maybe: MaybeType): boolean {\n  // https://github.com/cloudflare/workerd/blob/33e692f2216704b7226c8c59b1455eefedf79068/src/workerd/jsg/jsg.h#L220-L221\n  return maybe.name === 'kj::Maybe';\n}\n\n// Returns `true` iff this number type represents a character\nfunction isCharNumber(number: NumberType): boolean {\n  // https://github.com/cloudflare/workerd/blob/33e692f2216704b7226c8c59b1455eefedf79068/src/workerd/jsg/rtti.h#L158\n  const name = number.name;\n  return name === 'char';\n}\n\n// Returns `true` iff this number type represents a byte\nfunction isByteNumber(number: NumberType): boolean {\n  // https://github.com/cloudflare/workerd/blob/33e692f2216704b7226c8c59b1455eefedf79068/src/workerd/jsg/rtti.h#L160\n  const name = number.name;\n  return name === 'unsigned char';\n}\n\n// Returns `true` iff this number type represents `number | bigint`\nfunction isBigNumber(number: NumberType): boolean {\n  // https://github.com/cloudflare/workerd/blob/33e692f2216704b7226c8c59b1455eefedf79068/src/workerd/jsg/README.md?plain=1#L56-L82\n  // https://github.com/cloudflare/workerd/blob/33e692f2216704b7226c8c59b1455eefedf79068/src/workerd/jsg/rtti.h#L157-L167\n  const name = number.name;\n  return (\n    name === 'long' ||\n    name === 'unsigned long' ||\n    name === 'long long' ||\n    name === 'unsigned long long' ||\n    name === 'jsg::JsBigInt'\n  );\n}\n\n// Returns `true` iff this array type represents a pointer to an array\nfunction isArrayPointer(array: ArrayType): boolean {\n  return array.name === 'kj::ArrayPtr';\n}\n\n// Returns `true` iff this array type represents an iterable\nfunction isIterable(array: ArrayType): boolean {\n  // https://github.com/cloudflare/workerd/blob/33e692f2216704b7226c8c59b1455eefedf79068/src/workerd/jsg/README.md?plain=1#L185-L186\n  return array.name === 'jsg::Sequence';\n}\n\n// Returns `true` iff `typeNode` is `never`\nexport function isUnsatisfiable(typeNode: ts.TypeNode): boolean {\n  const isNeverTypeReference =\n    ts.isTypeReferenceNode(typeNode) &&\n    ts.isIdentifier(typeNode.typeName) &&\n    typeNode.typeName.text === 'never';\n  const isNeverKeyword =\n    ts.isToken(typeNode) && typeNode.kind == ts.SyntaxKind.NeverKeyword;\n  return isNeverTypeReference || isNeverKeyword;\n}\n\n// Strings to replace in fully-qualified structure names with nothing\n// `workerd` references APIs by fully qualified names such as `workerd::api::Whatever`\n// This wouldn't be all that user-friendly as a type, and so this regex captures all the\n// parts of an API name that should be removed when turning an API into a TS type\n// For instance, this turns `workerd::api::Whatever` into `Whatever`\n// If any new namespaced APIs are added, they should be added to this regex.\n// If they're _not_ added to this regex, a sane-ish fallback will be used.\n// For instance, a new hypothetical API called `workerd::api::magic::MakeASpell` would be\n// `magicMakeASpell`.\nconst replaceEmpty =\n  /^workerd::api::public_beta::|^workerd::api::urlpattern::|^workerd::api::node::|^workerd::api::|^workerd::jsg::|::|[ >]/g;\n// Strings to replace in fully-qualified structure names with an underscore\nconst replaceUnderscore = /[<,]/g;\nexport function getTypeName(\n  structure: Structure | StructureType | /* fullyQualifiedName */ string\n): string {\n  let name: string;\n  if (typeof structure === 'string') {\n    assert(\n      structure.includes('::'),\n      `Expected fully-qualified structure name, got \"${structure}\"`\n    );\n    name = structure;\n  } else {\n    name = structure.fullyQualifiedName;\n  }\n  name = name.replace(replaceEmpty, '');\n  name = name.replace(replaceUnderscore, '_');\n  return name;\n}\n\nexport function createParamDeclarationNodes(\n  fullyQualifiedParentName: string,\n  name: string,\n  args: Type[],\n  forMethod = false\n): ts.ParameterDeclaration[] {\n  // Find the index of the last required parameter, all optional before this\n  // will use the `| undefined` syntax, as opposed to a `?` token.\n  const lastRequiredParameter = findLastIndex(args, (type) => {\n    // Could simplify this to a single return, but this reads clearer\n    if (type._isMaybe && !isNullMaybe(type.maybe)) {\n      // `type` is `T | undefined` so optional\n      return false;\n    }\n    // noinspection RedundantIfStatementJS\n    if (type._isJsgImpl) {\n      // `type` is varargs or internal implementation type so optional\n      return false;\n    }\n    return true;\n  });\n\n  // `args` may include internal implementation types that shouldn't appear\n  // in parameters. Therefore, we may end up with fewer params than args.\n  const params: ts.ParameterDeclaration[] = [];\n\n  for (let i = 0; i < args.length; i++) {\n    const arg = args[i];\n    let typeNode = createTypeNode(\n      arg,\n      true, // Always allow coercion in function params\n      forMethod // Allow additional coercion in method params\n    );\n\n    let dotDotDotToken: ts.DotDotDotToken | undefined;\n    let questionToken: ts.QuestionToken | undefined;\n\n    const which = arg.which();\n    if (which === Type_Which.MAYBE) {\n      // If this is an optional type, and we don't have any required args\n      // left, use an optional parameter with a `?`\n      const unwrappedTypeNode = maybeUnwrapOptional(typeNode);\n      if (unwrappedTypeNode !== undefined && i > lastRequiredParameter) {\n        typeNode = unwrappedTypeNode;\n        questionToken = f.createToken(ts.SyntaxKind.QuestionToken);\n      }\n    } else if (which === Type_Which.JSG_IMPL) {\n      if (arg.jsgImpl.type === JsgImplType_Type.JSG_VARARGS) {\n        // If this is a varargs type, make sure we include `...`\n        assert(\n          ts.isArrayTypeNode(typeNode),\n          `Expected \"T[]\", got \"${printNode(typeNode)}\"`\n        );\n        dotDotDotToken = f.createToken(ts.SyntaxKind.DotDotDotToken);\n      } else if (isUnsatisfiable(typeNode)) {\n        // If this is an internal implementation type, omit it, and skip to\n        // the next arg\n        continue;\n      }\n    }\n\n    const param = f.createParameterDeclaration(\n      /* modifiers */ undefined,\n      dotDotDotToken,\n      getParameterName(fullyQualifiedParentName, name, i),\n      questionToken,\n      typeNode\n    );\n    params.push(param);\n  }\n\n  return params;\n}\n\nexport function createTypeNode(\n  type: Type,\n  allowCoercion = false,\n  allowMethodParameterCoercion = false\n): ts.TypeNode {\n  // If `allowMethodParameterCoercion` is set, `allowCoercion` must be set too.\n  // `allowMethodParameterCoercion` enables additional coercions for C++ method\n  // parameters.\n  assert(\n    !allowMethodParameterCoercion || allowCoercion,\n    `\"allowMethodParameterCoercion\" requires \"allowCoercion\"`\n  );\n\n  const which = type.which();\n  // noinspection FallThroughInSwitchStatementJS\n  switch (which) {\n    case Type_Which.UNKNOWN:\n      return f.createTypeReferenceNode('any');\n    case Type_Which.VOIDT:\n      return f.createTypeReferenceNode('void');\n    case Type_Which.BOOLT:\n      return f.createTypeReferenceNode('boolean');\n    case Type_Which.NUMBER: {\n      const number = type.number;\n      if (isBigNumber(number)) {\n        return f.createUnionTypeNode([\n          f.createTypeReferenceNode('number'),\n          f.createTypeReferenceNode('bigint'),\n        ]);\n      } else {\n        return f.createTypeReferenceNode('number');\n      }\n    }\n    case Type_Which.PROMISE: {\n      const value = type.promise.value;\n\n      if (allowMethodParameterCoercion && value.which() === Type_Which.VOIDT) {\n        // For C++ method parameters, treat `Promise<void>` as `Promise<any>`.\n        // We don't use `allowCoercion` here, as we want stream callback return\n        // types to be `Promise<void>` so they match official TypeScript types:\n        // https://github.com/microsoft/TypeScript/blob/f1288c33a1594046dcb4bad2ecdda80a1b035bb7/lib/lib.webworker.d.ts#L5987-L6025\n        return f.createTypeReferenceNode('Promise', [\n          f.createTypeReferenceNode('any'),\n        ]);\n      }\n\n      const valueType = createTypeNode(value, allowCoercion);\n      const promiseType = f.createTypeReferenceNode('Promise', [valueType]);\n      if (allowCoercion) {\n        return f.createUnionTypeNode([valueType, promiseType]);\n      } else {\n        return promiseType;\n      }\n    }\n    case Type_Which.STRUCTURE:\n      return f.createTypeReferenceNode(getTypeName(type.structure));\n    case Type_Which.STRING:\n      return f.createTypeReferenceNode('string');\n    case Type_Which.OBJECT:\n      return f.createTypeReferenceNode('any');\n    case Type_Which.ARRAY: {\n      const array = type.array;\n      const element = array.element;\n      if (element._isNumber && isCharNumber(element.number)) {\n        return f.createTypeReferenceNode('string');\n      } else if (element._isNumber && isByteNumber(element.number)) {\n        // If the array element is a `byte`...\n        if (allowCoercion) {\n          // When coercion is enabled (e.g. method param), `kj::Array<byte>` and\n          // `kj::ArrayPtr<byte>` both mean `ArrayBuffer | ArrayBufferView`\n          return f.createUnionTypeNode([\n            f.createTypeReferenceNode('ArrayBuffer'),\n            f.createTypeReferenceNode('ArrayBufferView'),\n          ]);\n        } else {\n          // When coercion is disabled, `kj::ArrayPtr<byte>` corresponds to\n          // `ArrayBufferView`, whereas `kj::Array<byte>` is `ArrayBuffer`\n          return f.createTypeReferenceNode(\n            isArrayPointer(array) ? 'ArrayBufferView' : 'ArrayBuffer'\n          );\n        }\n      } else if (isIterable(array) && allowCoercion) {\n        // If this is a `jsg::Sequence` parameter, it should accept any iterable\n        return f.createTypeReferenceNode('Iterable', [\n          createTypeNode(element, allowCoercion),\n        ]);\n      } else {\n        // Otherwise, return a regular array\n        return f.createArrayTypeNode(createTypeNode(element, allowCoercion));\n      }\n    }\n    case Type_Which.MAYBE: {\n      const maybe = type.maybe;\n      const alternative = isNullMaybe(maybe) ? 'null' : 'undefined';\n      return f.createUnionTypeNode([\n        createTypeNode(maybe.value, allowCoercion),\n        f.createTypeReferenceNode(alternative),\n      ]);\n    }\n    case Type_Which.DICT: {\n      const dict = type.dict;\n      return f.createTypeReferenceNode('Record', [\n        createTypeNode(dict.key, allowCoercion),\n        createTypeNode(dict.value, allowCoercion),\n      ]);\n    }\n    case Type_Which.ONE_OF: {\n      const variants = type.oneOf.variants.map((variant) =>\n        createTypeNode(variant, allowCoercion)\n      );\n      return f.createUnionTypeNode(variants);\n    }\n    case Type_Which.BUILTIN: {\n      const builtin = type.builtin.type;\n      switch (builtin) {\n        case BuiltinType_Type.V8UINT8ARRAY:\n          return f.createTypeReferenceNode('Uint8Array');\n        case BuiltinType_Type.V8ARRAY_BUFFER_VIEW:\n          return f.createTypeReferenceNode('ArrayBufferView');\n        case BuiltinType_Type.V8ARRAY_BUFFER:\n          return f.createTypeReferenceNode('ArrayBuffer');\n        case BuiltinType_Type.JSG_BUFFER_SOURCE:\n          return f.createUnionTypeNode([\n            f.createTypeReferenceNode('ArrayBuffer'),\n            f.createTypeReferenceNode('ArrayBufferView'),\n          ]);\n        case BuiltinType_Type.KJ_DATE:\n          if (allowCoercion) {\n            return f.createUnionTypeNode([\n              f.createTypeReferenceNode('number'),\n              f.createTypeReferenceNode('Date'),\n            ]);\n          } else {\n            return f.createTypeReferenceNode('Date');\n          }\n        case BuiltinType_Type.V8FUNCTION:\n          return f.createTypeReferenceNode('Function');\n        default:\n          assert.fail(`Unknown builtin type: ${builtin satisfies never}`);\n      }\n    }\n    case Type_Which.INTRINSIC: {\n      const intrinsic = type.intrinsic.name;\n      switch (intrinsic) {\n        case 'v8::kErrorPrototype':\n          return f.createTypeReferenceNode('Error');\n        case 'v8::kIteratorPrototype':\n          return f.createTypeReferenceNode('Iterator', [\n            f.createTypeReferenceNode('unknown'),\n          ]);\n        case 'v8::kAsyncIteratorPrototype':\n          return f.createTypeReferenceNode('AsyncIterator', [\n            f.createTypeReferenceNode('unknown'),\n          ]);\n        default:\n          assert.fail(`Unknown intrinsic type: ${intrinsic}`);\n      }\n    }\n    case Type_Which.FUNCTION: {\n      const func = type.function;\n      const params = createParamDeclarationNodes(\n        'FUNCTION_TODO',\n        'FUNCTION_TODO',\n        func.args.toArray()\n      );\n      const result = createTypeNode(\n        func.returnType,\n        true // Always allow coercion in callback functions\n      );\n      return f.createFunctionTypeNode(\n        /* typeParams */ undefined,\n        params,\n        result\n      );\n    }\n    case Type_Which.JSG_IMPL: {\n      const impl = type.jsgImpl.type;\n      switch (impl) {\n        case JsgImplType_Type.CONFIGURATION:\n        case JsgImplType_Type.V8ISOLATE:\n        case JsgImplType_Type.JSG_LOCK:\n        case JsgImplType_Type.JSG_TYPE_HANDLER:\n        case JsgImplType_Type.JSG_UNIMPLEMENTED:\n        case JsgImplType_Type.JSG_SELF_REF:\n        case JsgImplType_Type.V8FUNCTION_CALLBACK_INFO:\n        case JsgImplType_Type.V8PROPERTY_CALLBACK_INFO:\n          // All these types should be omitted from function parameters\n          return f.createTypeReferenceNode('never');\n        case JsgImplType_Type.JSG_VARARGS:\n          return f.createArrayTypeNode(f.createTypeReferenceNode('any'));\n        case JsgImplType_Type.JSG_NAME:\n          return f.createTypeReferenceNode('PropertyKey');\n        default:\n          assert.fail(\n            `Unknown JSG implementation type: ${impl satisfies never}`\n          );\n      }\n    }\n    case Type_Which.JS_BUILTIN: {\n      // TODO(soon): implement\n      assert.fail('`JS_BUILTIN`s are not yet supported');\n    }\n    default: {\n      assert.fail(`Unknown type: ${which satisfies never}`);\n    }\n  }\n}\n"
  },
  {
    "path": "types/src/index.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'node:assert';\nimport { StructureGroups } from '@workerd/jsg/rtti';\nimport ts from 'typescript';\nimport { generateDefinitions } from './generator';\nimport { printNodeList, printer } from './print';\nimport { SourcesMap, createMemoryProgram } from './program';\nimport {\n  CommentsData,\n  compileOverridesDefines,\n  createAmbientTransformer,\n  createCommentsTransformer,\n  createGlobalScopeTransformer,\n  createImportResolveTransformer,\n  createImportableTransformer,\n  createIteratorTransformer,\n  createOverrideDefineTransformer,\n} from './transforms';\nimport { createClassToInterfaceTransformer } from './transforms/class-to-interface';\nimport { createAddOnMessageDeclarationTransformer } from './transforms/onmessage-declaration';\n\nconst definitionsHeader = `/*! *****************************************************************************\nCopyright (c) Cloudflare. All rights reserved.\nCopyright (c) Microsoft Corporation. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at http://www.apache.org/licenses/LICENSE-2.0\nTHIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\nKIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED\nWARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,\nMERCHANTABLITY OR NON-INFRINGEMENT.\nSee the Apache Version 2.0 License for specific language governing permissions\nand limitations under the License.\n***************************************************************************** */\n/* eslint-disable */\n// noinspection JSUnusedGlobalSymbols\n`;\n\nfunction transform(\n  sources: SourcesMap,\n  sourcePath: string,\n  transforms: (\n    program: ts.Program,\n    checker: ts.TypeChecker\n  ) => ts.TransformerFactory<ts.SourceFile>[]\n): string {\n  const program = createMemoryProgram(sources);\n  const checker = program.getTypeChecker();\n  const sourceFile = program.getSourceFile(sourcePath);\n  assert(sourceFile !== undefined);\n  const result = ts.transform(sourceFile, transforms(program, checker));\n  assert.strictEqual(result.transformed.length, 1);\n  return printer.printFile(result.transformed[0]);\n}\n\nexport function printDefinitions(\n  root: StructureGroups,\n  commentData: CommentsData,\n  extraDefinitions: string\n): { ambient: string; importable: string } {\n  // Generate TypeScript nodes from capnp request\n  const { nodes } = generateDefinitions(root);\n\n  // Assemble partial overrides and defines to valid TypeScript source files\n  const [sources, replacements] = compileOverridesDefines(root);\n  // Add source file containing generated nodes\n  const sourcePath = '/$virtual/source.ts';\n  let source = printNodeList(nodes);\n  sources.set(sourcePath, printNodeList(nodes));\n\n  // Run post-processing transforms on program\n  source = transform(sources, sourcePath, (program, checker) => [\n    // Run iterator transformer before overrides so iterator-like interfaces are\n    // still removed if they're replaced in overrides\n    createIteratorTransformer(checker),\n    createOverrideDefineTransformer(program, replacements),\n    // Run global scope transformer after overrides so members added in\n    // overrides are extracted\n    createGlobalScopeTransformer(checker),\n    createClassToInterfaceTransformer(['Request', 'Response', 'WebSocket']),\n    // TODO: enable this once we've figured out how not to expose internal modules\n    // createInternalNamespaceTransformer(root, structureMap),\n    createCommentsTransformer(commentData),\n    createAddOnMessageDeclarationTransformer(),\n  ]);\n\n  // TODO: enable this once we've figured out how not to expose internal modules\n  // source += collectTypeScriptModules(root) + extraDefinitions;\n  source += extraDefinitions;\n\n  // We need the type checker to respect our updated definitions after applying\n  // overrides (e.g. to find the correct nodes when traversing heritage), so\n  // rebuild the program to re-run type checking. We also want to include our\n  // additional definitions.\n  source = transform(new SourcesMap([[sourcePath, source]]), sourcePath, () => [\n    createImportResolveTransformer(),\n    createAmbientTransformer(),\n  ]);\n\n  const importable = transform(\n    new SourcesMap([[sourcePath, source]]),\n    sourcePath,\n    () => [createImportableTransformer()]\n  );\n\n  // Print program to string\n  return {\n    ambient: definitionsHeader + source,\n    importable: definitionsHeader + importable,\n  };\n}\n"
  },
  {
    "path": "types/src/print.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport ts, { factory as f } from \"typescript\";\n\nconst placeholderFile = ts.createSourceFile(\n  \"placeholder.ts\", // File name doesn't matter here\n  \"\",\n  ts.ScriptTarget.ESNext,\n  false,\n  ts.ScriptKind.TS\n);\nexport const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });\n\nexport function printNode(node: ts.Node): string {\n  return printer.printNode(ts.EmitHint.Unspecified, node, placeholderFile);\n}\n\nexport function printNodeList(nodes: ts.Node[]): string {\n  return printer.printList(\n    ts.ListFormat.MultiLine,\n    f.createNodeArray(nodes),\n    placeholderFile\n  );\n}\n"
  },
  {
    "path": "types/src/program.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from \"node:assert\";\nimport ts from \"typescript\";\n\nexport const SourcesMap = Map<string, string>;\nexport type SourcesMap = InstanceType<typeof SourcesMap>;\n\nconst defaultCompilerOpts = ts.getDefaultCompilerOptions();\n\n// Creates a TypeScript program from in-memory source files. Accepts a Map of\n// fully-resolved \"virtual\" paths to source code.\nexport function createMemoryProgram(\n  sources: SourcesMap,\n  host?: ts.CompilerHost,\n  compilerOpts = defaultCompilerOpts,\n  // Provide additional lib files to TypeScript. These should all be prefixed with\n  // `/node_modules/typescript/lib` (e.g. `/node_modules/typescript/lib/lib.esnext.d.ts`)\n  lib?: SourcesMap\n): ts.Program {\n  const sourceFiles = new Map<string, ts.SourceFile>();\n  for (const [sourcePath, source] of [...sources, ...(lib ?? [])]) {\n    const sourceFile = ts.createSourceFile(\n      sourcePath,\n      source,\n      ts.ScriptTarget.ESNext,\n      false,\n      ts.ScriptKind.TS\n    );\n    sourceFiles.set(sourcePath, sourceFile);\n  }\n\n  host ??= {\n    getCurrentDirectory(): string {\n      return \"/\";\n    },\n    getCanonicalFileName(fileName: string): string {\n      return fileName;\n    },\n    getDefaultLibFileName(_options: ts.CompilerOptions): string {\n      return \"\";\n    },\n    getDefaultLibLocation(): string {\n      return \"/node_modules/typescript/lib\";\n    },\n    getNewLine(): string {\n      return \"\\n\";\n    },\n    useCaseSensitiveFileNames(): boolean {\n      return true;\n    },\n    fileExists(fileName: string): boolean {\n      return sourceFiles.has(fileName);\n    },\n    readFile(_path: string): string | undefined {\n      assert.fail(\"readFile() not implemented\");\n    },\n    writeFile(\n      _path: string,\n      _data: string,\n      _writeByteOrderMark?: boolean\n    ): void {\n      assert.fail(\"writeFile() not implemented\");\n    },\n    getSourceFile(\n      fileName: string,\n      _languageVersionOrOptions: ts.ScriptTarget | ts.CreateSourceFileOptions,\n      _onError?: (message: string) => void,\n      _shouldCreateNewSourceFile?: boolean\n    ): ts.SourceFile | undefined {\n      return sourceFiles.get(fileName);\n    },\n  };\n\n  const rootNames = Array.from(sources.keys());\n  return ts.createProgram(rootNames, compilerOpts, host);\n}\n"
  },
  {
    "path": "types/src/standards.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from \"node:assert\";\nimport { readFileSync } from \"node:fs\";\nimport * as ts from \"typescript\";\nimport { createMemoryProgram } from \"./program\";\nimport { CommentsData } from \"./transforms\";\n\n// Collate comments from standards-based .d.ts files (e.g. lib.webworker.d.ts)\nexport function collateStandardComments(\n  ...standardTypes: string[]\n): CommentsData {\n  const combinedLibPath = \"/$virtual/standards.d.ts\";\n  const combinedLibContents: string = standardTypes\n    .map(\n      (s) =>\n        // Remove the Microsoft copyright notices from the file, to prevent them being copied in as TS comments\n        readFileSync(s, \"utf-8\").split(`/////////////////////////////`)[2]\n    )\n    .join(\"\\n\");\n\n  const program = createMemoryProgram(\n    new Map([[combinedLibPath, combinedLibContents]])\n  );\n\n  const combinedLibFile = program.getSourceFile(combinedLibPath);\n\n  assert(combinedLibFile !== undefined);\n\n  const result: CommentsData = {};\n  const recordComments = (node: ts.Node, name: string, memberName?: string): void => {\n    const ranges = ts.getLeadingCommentRanges(\n      combinedLibContents,\n      node.getFullStart()\n    );\n    if (ranges === undefined) return;\n\n    const nodeResult = (result[name] ??= {});\n    const key = memberName ?? \"$\";\n    nodeResult[key] ??= \"\";\n\n    for (const range of ranges) {\n      let text = combinedLibContents.slice(range.pos + 2, range.end);\n      // All lib.*.d.ts file use multiline comment syntax (/** ... */).\n      // For the avoidance of doubt and to make sure the following parsing code is valid,\n      // assert that only multiline comments are included.\n      assert(\n        range.kind === ts.SyntaxKind.MultiLineCommentTrivia,\n        \"Unexpected single-line comment in a standards .d.ts file\"\n      );\n      text = text\n        // Remove the multiline comment end\n        .slice(0, text.length - 2)\n        // Remove multiline comment prefix lines (e.g. `   * ` -> ` * `)\n        .replaceAll(/^\\s+/gm, \" \");\n\n      // Let's make sure comments that are actually only 1 line (usually a link to MDN) don't start\n      // with two * characters (e.g. `/** [MDN Reference] ... */` -> `/* [MDN Reference] ... */`)\n      if (!text.includes(\"\\n\") && text.startsWith(\"*\")) {\n        text = text.slice(1);\n      }\n\n      // Because we load multiple lib files, some types are included multiple times.\n      // This simple check makes sure that we don't add a doc comment twice\n      if (!nodeResult[key]?.includes?.(text)) {\n        nodeResult[key] += text;\n      }\n    }\n  };\n\n  ts.forEachChild(combinedLibFile, (node) => {\n    if (ts.isInterfaceDeclaration(node)) {\n      // Prototype properties/methods exist on interfaces, for example:\n      // ```ts\n      // /** ... */\n      // interface AbortSignal extends EventTarget {\n      //   /** ... */\n      //   readonly aborted: boolean;\n      // }\n      // ```\n      recordComments(node, node.name.text);\n      for (const member of node.members) {\n        if (member.name === undefined) continue;\n        if (!ts.isIdentifier(member.name)) continue;\n        recordComments(member, node.name.text, member.name.text);\n      }\n    } else if (ts.isFunctionDeclaration(node)) {\n      if (node.name !== undefined) recordComments(node, node.name.text);\n    } else if (ts.isVariableStatement(node)) {\n      if (node.declarationList.declarations.length > 1) return;\n      const declaration = node.declarationList.declarations[0];\n      const name = declaration.name;\n      if (!ts.isIdentifier(name)) return;\n      recordComments(node, name.text);\n\n      if (\n        declaration.type !== undefined &&\n        ts.isTypeLiteralNode(declaration.type)\n      ) {\n        // Static properties/methods exist on type literals, for example:\n        // ```ts\n        // declare var AbortSignal: {\n        //   prototype: AbortSignal;\n        //   new(): AbortSignal;\n        //   /** ... */\n        //   abort(reason?: any): AbortSignal;\n        // };\n        // ```\n        for (const member of declaration.type.members) {\n          if (member.name === undefined) continue;\n          if (!ts.isIdentifier(member.name)) continue;\n          recordComments(member, name.text, `static:${member.name.text}`);\n        }\n      }\n    }\n  });\n\n  return result;\n}\n"
  },
  {
    "path": "types/src/transforms/ambient.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport ts from \"typescript\";\nimport { ensureStatementModifiers } from \"./helpers\";\n\n// This ensures that all top-level nodes have the `declare` keyword, but no\n// nodes inside `declare modules` blocks do. It also ensures no top-level\n// nodes are `export`ed.\n//\n// ```ts\n// export class A { ... }\n// declare module \"thing\" {\n//   declare class B { ... }\n// }\n// ```\n//\n// --- transforms to --->\n//\n// ```ts\n// declare class A { ... }\n// declare module \"thing\" {\n//   class B { ... }\n// }\n// ```\nexport function createAmbientTransformer(): ts.TransformerFactory<ts.SourceFile> {\n  return (ctx) => {\n    return (node) => {\n      const visitor = createVisitor(ctx);\n      return ts.visitEachChild(node, visitor, ctx);\n    };\n  };\n}\n\nfunction createVisitor(ctx: ts.TransformationContext): ts.Visitor {\n  const moduleBlockChildVisitor: ts.Visitor = (node) => {\n    return ensureStatementModifiers(ctx, node, { declare: false });\n  };\n  const moduleDeclarationVisitor: ts.Visitor = (node) => {\n    if (ts.isModuleBlock(node)) {\n      return ts.visitEachChild(node, moduleBlockChildVisitor, ctx);\n    }\n    return node;\n  };\n  return (node) => {\n    if (\n      ts.isModuleDeclaration(node) &&\n      (node.flags & ts.NodeFlags.Namespace) === 0\n    ) {\n      return ts.visitEachChild(node, moduleDeclarationVisitor, ctx);\n    }\n    return ensureStatementModifiers(ctx, node, {\n      export: false,\n      declare: true,\n    });\n  };\n}\n"
  },
  {
    "path": "types/src/transforms/class-to-interface.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport ts from \"typescript\";\n\n/**\n * Transforms an array of classes to an interface/variable pair, preserving the ability to construct the class\n * and call static methods.\n *\n * @example\n *\n * class MyClass<T> {\n *   constructor(str: string): MyClass<T>;\n *   prop: T;\n *   method(): void {}\n *   static staticMethod(str?: string): void {}\n * }\n *\n * // Becomes this:\n *\n * declare var MyClass: {\n *   prototype: MyClass;\n *   new <T>(str: string): MyClass<T>;\n *   staticMethod(str?: string): void;\n * }\n * interface MyClass<T = void, U = void> {\n *   prop: T;\n *   method(): U;\n * }\n *\n * NB:\n *   1. Generics are preserved and provided to the `new` method on the var declaration.\n *   2. Static methods are added to the var declaration instead of the interface.\n */\nexport function createClassToInterfaceTransformer(\n  classNames: string[]\n): ts.TransformerFactory<ts.SourceFile> {\n  return (context) => {\n    const visitor: ts.Visitor = (node) => {\n      if (\n        ts.isClassDeclaration(node) &&\n        node.name &&\n        classNames.includes(node.name.text)\n      ) {\n        return transformClassToInterface(node, context);\n      }\n      return ts.visitEachChild(node, visitor, context);\n    };\n\n    return (sourceFile) => {\n      const transformedNodes = ts.visitNodes(sourceFile.statements, visitor);\n      const filteredNodes = transformedNodes.filter(ts.isStatement);\n      return context.factory.updateSourceFile(sourceFile, filteredNodes);\n    };\n  };\n}\n\n/**\n * Transforms a TypeScript class declaration into an interface and a variable declaration.\n * Used where you want to separate the type definition (interface)\n * from the runtime representation (variable declaration) of a class.\n */\nfunction transformClassToInterface(\n  node: ts.ClassDeclaration,\n  context: ts.TransformationContext\n): ts.Statement[] {\n  const interfaceDeclaration = createInterfaceDeclaration(node, context);\n  const varDeclaration = createVariableDeclaration(node, context);\n  return [varDeclaration, interfaceDeclaration];\n}\n\n/**\n * Creates an interface declaration from a class declaration.\n * Extracts class members and converts them into interface members,\n * preserving access modifiers and type parameters.\n */\nfunction createInterfaceDeclaration(\n  node: ts.ClassDeclaration,\n  context: ts.TransformationContext\n): ts.InterfaceDeclaration {\n  const interfaceMembers = transformClassMembers(node.members, context, false);\n  return context.factory.createInterfaceDeclaration(\n    getAccessModifiers(ts.getModifiers(node)),\n    node.name,\n    node.typeParameters,\n    node.heritageClauses,\n    interfaceMembers\n  );\n}\n\n/**\n * Transforms class members into interface type elements.\n * Filters and converts class elements into a format suitable for interfaces,\n * optionally including static members.\n */\nfunction transformClassMembers(\n  members: ts.NodeArray<ts.ClassElement>,\n  context: ts.TransformationContext,\n  includeStatic: boolean\n): ts.TypeElement[] {\n  return members\n    .map((member) =>\n      transformClassMemberToInterface(member, context, includeStatic)\n    )\n    .filter((member): member is ts.TypeElement => member !== undefined);\n}\n\n/**\n * Transforms a single class member into an interface element.\n * Handles different types of class elements, such as properties and methods,\n * and applies access modifiers appropriately.\n *\n * @example\n * // Given the following class declarations:\n *\n * myMethod(): void {}\n * private myPrivateProperty: string;\n *\n * // The function will produce an interface declarations similar to:\n *\n * myMethod(): void;\n *\n * Note: Private members like `myPrivateProperty` are not included in the interface.\n */\nfunction transformClassMemberToInterface(\n  member: ts.ClassElement,\n  context: ts.TransformationContext,\n  includeStatic: boolean\n): ts.TypeElement | undefined {\n  const modifiers = ts.canHaveModifiers(member)\n    ? ts.getModifiers(member)\n    : undefined;\n  const isStatic =\n    modifiers?.some((mod) => mod.kind === ts.SyntaxKind.StaticKeyword) ?? false;\n\n  if (isStatic !== includeStatic) {\n    return undefined;\n  }\n\n  const isPrivate =\n    modifiers?.some((mod) => mod.kind === ts.SyntaxKind.PrivateKeyword) ??\n    false;\n\n  if (isPrivate) {\n    return undefined;\n  }\n\n  const accessModifiers = getAccessModifiers(modifiers);\n\n  if (ts.isPropertyDeclaration(member)) {\n    return createPropertySignature(member, accessModifiers, context);\n  } else if (ts.isMethodDeclaration(member)) {\n    return createMethodSignature(member, accessModifiers, context);\n  } else if (ts.isGetAccessor(member)) {\n    return createGetAccessorSignature(member, accessModifiers, context);\n  } else if (ts.isSetAccessor(member) || ts.isConstructorDeclaration(member)) {\n    return undefined;\n  }\n\n  console.warn(`Unhandled member type: ${ts.SyntaxKind[member.kind]}`);\n  return undefined;\n}\n\n/**\n * Creates a property signature for an interface from a class property declaration.\n * Preserves access modifiers and optionality.\n *\n * @example\n * // Given a TypeScript class property declaration:\n *\n * public optionalProp?: string;\n *\n * // The `createPropertySignature` function will produce an interface property signature:\n *\n * optionalProp?: string;\n */\nfunction createPropertySignature(\n  member: ts.PropertyDeclaration,\n  modifiers: ts.Modifier[] | undefined,\n  context: ts.TransformationContext\n): ts.PropertySignature {\n  return context.factory.createPropertySignature(\n    modifiers,\n    member.name,\n    member.questionToken,\n    member.type\n  );\n}\n\n/**\n * Creates a method signature for an interface from a class method declaration.\n * Handles method parameters and return types.\n *\n * @example\n * // Given a TypeScript class method declaration:\n *\n * public doSomething(param: number): string {\n *   return param.toString();\n * }\n *\n * // The `createMethodSignature` function will produce an interface method signature:\n *\n * doSomething(param: number): string;\n */\nfunction createMethodSignature(\n  member: ts.MethodDeclaration,\n  modifiers: ts.Modifier[] | undefined,\n  context: ts.TransformationContext\n): ts.MethodSignature {\n  return context.factory.createMethodSignature(\n    modifiers,\n    member.name,\n    member.questionToken,\n    member.typeParameters,\n    member.parameters,\n    member.type\n  );\n}\n\n/**\n * Creates a property signature for an interface from a class `get` accessor declaration.\n * Used to represent getter methods as properties in interfaces.\n *\n * @example\n * // Given a TypeScript class with a getter:\n *\n * get value(): number {\n *   return 42;\n * }\n *\n * // The `createGetAccessorSignature` function will produce an interface property signature:\n *\n * value: number;\n */\nfunction createGetAccessorSignature(\n  member: ts.GetAccessorDeclaration,\n  modifiers: ts.Modifier[] | undefined,\n  context: ts.TransformationContext\n): ts.PropertySignature {\n  return context.factory.createPropertySignature(\n    modifiers,\n    member.name,\n    undefined,\n    member.type\n  );\n}\n\n/**\n * Creates a variable declaration for a class, representing its runtime type.\n * Declares a variable with the class name and its associated type.\n *\n * @example\n * // Given a TypeScript class declaration:\n *\n * class Example {\n *   static staticMethod(): void {}\n *   constructor(public value: number) {}\n * }\n *\n * // The `createVariableDeclaration` function will produce a variable declaration:\n *\n * declare var Example: {\n *   prototype: Example;\n *   new (value: number): Example;\n *   staticMethod(): void;\n * };\n */\nfunction createVariableDeclaration(\n  node: ts.ClassDeclaration,\n  context: ts.TransformationContext\n): ts.VariableStatement {\n  return context.factory.createVariableStatement(\n    [context.factory.createModifier(ts.SyntaxKind.DeclareKeyword)],\n    context.factory.createVariableDeclarationList(\n      [\n        context.factory.createVariableDeclaration(\n          node.name,\n          undefined,\n          createClassType(node, context)\n        ),\n      ],\n      ts.NodeFlags.None\n    )\n  );\n}\n\n/**\n * Creates a type literal node representing the static members and prototype of a class.\n * Used to define the type structure of a class's static side.\n *\n * @example\n * // Given a TypeScript class with static members:\n *\n * class Example {\n *   constructor(public value: number) {}\n *   static staticMethod(): void {}\n * }\n *\n * // The `createClassType` function will produce a type literal node:\n *\n * {\n *   prototype: Example;\n *   new (value: number): Example;\n *   staticMethod(): void;\n * }\n */\nfunction createClassType(\n  node: ts.ClassDeclaration,\n  context: ts.TransformationContext\n): ts.TypeLiteralNode {\n  const staticMembers = transformClassMembers(node.members, context, true);\n  return context.factory.createTypeLiteralNode([\n    createPrototypeProperty(node, context),\n    createConstructSignature(node, context),\n    ...staticMembers,\n  ]);\n}\n\n/**\n * Creates a construct signature for a class, representing its constructor.\n * Includes type parameters and parameter types in the signature.\n *\n * @example\n * // Given a TypeScript class constructor:\n *\n * class Example<T> {\n *   constructor(public value: T) {}\n * }\n *\n * // The `createConstructSignature` function will produce a construct signature:\n *\n * new <T>(value: T): Example<T>;\n */\nfunction createConstructSignature(\n  node: ts.ClassDeclaration,\n  context: ts.TransformationContext\n): ts.ConstructSignatureDeclaration {\n  const constructorDeclaration = node.members.find(ts.isConstructorDeclaration);\n  const typeParameters = node.typeParameters;\n\n  const returnType = context.factory.createTypeReferenceNode(\n    node.name,\n    typeParameters?.map((param) =>\n      context.factory.createTypeReferenceNode(param.name, undefined)\n    )\n  );\n\n  return context.factory.createConstructSignature(\n    typeParameters,\n    constructorDeclaration?.parameters ?? [],\n    returnType\n  );\n}\n\n/**\n * Creates a property signature for the prototype property of a class.\n * Used to represent the prototype chain in the class type.\n *\n * @example\n * // Given a TypeScript class:\n *\n * class Example {}\n *\n * // The `createPrototypeProperty` function will produce a property signature:\n *\n * prototype: Example;\n */\nfunction createPrototypeProperty(\n  node: ts.ClassDeclaration,\n  context: ts.TransformationContext\n): ts.PropertySignature {\n  return context.factory.createPropertySignature(\n    undefined,\n    \"prototype\",\n    undefined,\n    context.factory.createTypeReferenceNode(node.name, undefined)\n  );\n}\n\n/**\n * Filters and returns the access modifiers applicable to a class member.\n * Extracts modifiers such as readonly, public, protected, and private.\n */\nfunction getAccessModifiers(\n  modifiers: readonly ts.Modifier[] | undefined\n): ts.Modifier[] | undefined {\n  return modifiers?.filter(\n    (mod) =>\n      mod.kind === ts.SyntaxKind.ReadonlyKeyword ||\n      mod.kind === ts.SyntaxKind.PublicKeyword ||\n      mod.kind === ts.SyntaxKind.ProtectedKeyword ||\n      mod.kind === ts.SyntaxKind.PrivateKeyword\n  );\n}\n"
  },
  {
    "path": "types/src/transforms/comments.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\nimport ts from \"typescript\";\nimport { hasModifier } from \"./helpers\";\n\n// Refer to `collateStandardComments()` in `standards.ts` for construction\nexport type CommentsData = Record<\n  /* globalName */ string,\n  // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents\n  | Record</* root */ \"$\" | /* memberName */ string, string | undefined>\n  | undefined\n>;\n\nexport function createCommentsTransformer(\n  comments: CommentsData\n): ts.TransformerFactory<ts.SourceFile> {\n  return (ctx) => {\n    return (node) => {\n      const visitor = createVisitor(comments);\n      return ts.visitEachChild(node, visitor, ctx);\n    };\n  };\n}\n\nfunction maybeAddComment(node: ts.Node, comment?: string): void {\n  if (comment === undefined) return;\n  ts.addSyntheticLeadingComment(\n    node,\n    ts.SyntaxKind.MultiLineCommentTrivia,\n    comment,\n    /* hasTrailingNewLine */ true\n  );\n}\n\nfunction createVisitor(data: CommentsData): ts.Visitor {\n  return (node) => {\n    if (\n      (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) &&\n      node.name !== undefined &&\n      ts.isIdentifier(node.name)\n    ) {\n      const comments = data[node.name.text];\n      maybeAddComment(node, comments?.[\"$\"]);\n      for (const member of node.members) {\n        if (member.name !== undefined && ts.isIdentifier(member.name)) {\n          let key = member.name.text;\n\n          const isStatic =\n            ts.canHaveModifiers(member) &&\n            hasModifier(ts.getModifiers(member), ts.SyntaxKind.StaticKeyword);\n          if (isStatic) key = `static:${key}`;\n\n          maybeAddComment(member, comments?.[key]);\n        }\n      }\n    }\n\n    if (\n      ts.isFunctionDeclaration(node) &&\n      node.name !== undefined &&\n      ts.isIdentifier(node.name)\n    ) {\n      const comments = data[node.name.text];\n      maybeAddComment(node, comments?.[\"$\"]);\n    }\n\n    if (\n      ts.isVariableStatement(node) &&\n      node.declarationList.declarations.length === 1\n    ) {\n      const declaration = node.declarationList.declarations[0];\n      const name = declaration.name;\n      if (ts.isIdentifier(name)) {\n        const comments = data[name.text];\n        maybeAddComment(node, comments?.[\"$\"]);\n      }\n    }\n\n    return node;\n  };\n}\n"
  },
  {
    "path": "types/src/transforms/globals.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from \"node:assert\";\nimport ts from \"typescript\";\n\n// Copies all properties of `ServiceWorkerGlobalScope` and its superclasses into\n// the global scope:\n//\n// ```ts\n// export declare class EventTarget {\n//   constructor();\n//   addEventListener(...): ...;\n// }\n// export declare abstract WorkerGlobalScope extends EventTarget {\n//   ...\n// }\n// export interface ServiceWorkerGlobalScope extends WorkerGlobalScope {\n//   DOMException: typeof DOMException;\n//   btoa(value: string): string;\n//   crypto: Crypto;\n//   ...\n// }\n// ```\n//\n// --- transforms to --->\n//\n// ```ts\n// export declare class EventTarget { ... }\n// export declare abstract WorkerGlobalScope extends EventTarget { ... }\n// export interface ServiceWorkerGlobalScope extends WorkerGlobalScope { ... }\n//\n// export declare function addEventListener(...): ...;\n// export declare function btoa(value: string): string;\n// export declare const crypto: Crypto;\n// ```\nexport function createGlobalScopeTransformer(\n  checker: ts.TypeChecker\n): ts.TransformerFactory<ts.SourceFile> {\n  return (ctx) => {\n    return (node) => {\n      const visitor = createGlobalScopeVisitor(ctx, checker);\n      return ts.visitEachChild(node, visitor, ctx);\n    };\n  };\n}\n\n// Copy type nodes everywhere they are referenced\nfunction createInlineVisitor(\n  ctx: ts.TransformationContext,\n  inlines: Map<string, ts.TypeNode>\n): ts.Visitor<ts.Node, ts.Node> {\n  // If there's nothing to inline, just return identity visitor\n  if (inlines.size === 0) return (node) => node;\n\n  const visitor: ts.Visitor<ts.Node, ts.Node> = (node) => {\n    // Recursively visit all nodes\n    node = ts.visitEachChild(node, visitor, ctx);\n\n    // Inline all matching type references\n    if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName)) {\n      const inline = inlines.get(node.typeName.text);\n      if (inline !== undefined) return inline;\n    }\n\n    return node;\n  };\n  return visitor;\n}\n\n// Call with each potential method/property that could be extracted into a\n// global function/const.\nexport function maybeExtractGlobalNode(\n  ctx: ts.TransformationContext,\n  node: ts.Node,\n  modifiers?: readonly ts.ModifierLike[]\n): ts.Statement | undefined {\n  if (\n    (ts.isMethodSignature(node) || ts.isMethodDeclaration(node)) &&\n    ts.isIdentifier(node.name)\n  ) {\n    return ctx.factory.createFunctionDeclaration(\n      modifiers,\n      /* asteriskToken */ undefined,\n      node.name,\n      node.typeParameters,\n      node.parameters,\n      node.type,\n      /* body */ undefined\n    );\n  }\n  if (\n    (ts.isPropertySignature(node) ||\n      ts.isPropertyDeclaration(node) ||\n      ts.isGetAccessorDeclaration(node)) &&\n    ts.isIdentifier(node.name)\n  ) {\n    assert(node.type !== undefined);\n    // Don't create global nodes for nested types, they'll already be there\n    if (!ts.isTypeQueryNode(node.type)) {\n      const varDeclaration = ctx.factory.createVariableDeclaration(\n        node.name,\n        /* exclamationToken */ undefined,\n        node.type\n      );\n      const varDeclarationList = ctx.factory.createVariableDeclarationList(\n        [varDeclaration],\n        ts.NodeFlags.Const // Use `const` instead of `var`\n      );\n      return ctx.factory.createVariableStatement(modifiers, varDeclarationList);\n    }\n  }\n}\n\nfunction createGlobalScopeVisitor(\n  ctx: ts.TransformationContext,\n  checker: ts.TypeChecker\n): ts.Visitor {\n  // Called with each class/interface that should have its methods/properties\n  // extracted into global functions/consts. Recursively visits superclasses.\n  function extractGlobalNodes(\n    node: ts.InterfaceDeclaration | ts.ClassDeclaration,\n    typeArgs?: ts.NodeArray<ts.TypeNode>\n  ): ts.Node[] {\n    const nodes: ts.Node[] = [];\n\n    // If this declaration has type parameters, we'll need to inline them when\n    // extracting members.\n    const typeArgInlines = new Map<string, ts.TypeNode>();\n    if (node.typeParameters) {\n      assert(\n        node.typeParameters.length === typeArgs?.length,\n        `Expected ${node.typeParameters.length} type argument(s), got ${typeArgs?.length}`\n      );\n      node.typeParameters.forEach((typeParam, index) => {\n        typeArgInlines.set(typeParam.name.text, typeArgs[index]);\n      });\n    }\n    const inlineVisitor = createInlineVisitor(ctx, typeArgInlines);\n\n    // Recursively extract from all superclasses\n    if (node.heritageClauses !== undefined) {\n      for (let clause of node.heritageClauses) {\n        // Handle case where type param appears in heritage clause:\n        // ```ts\n        // class A<T> {}     // ↓\n        // class B<T> extends A<T> {}\n        // class C extends B<string> {}\n        // ```\n        clause = ts.visitNode(clause, inlineVisitor, ts.isHeritageClause);\n\n        for (const superType of clause.types) {\n          const superTypeSymbol = checker.getSymbolAtLocation(\n            superType.expression\n          );\n          assert(superTypeSymbol !== undefined);\n          const superTypeDeclarations = superTypeSymbol.getDeclarations();\n          assert.strictEqual(superTypeDeclarations?.length, 1);\n          const superTypeDeclaration = superTypeDeclarations[0];\n          assert(\n            ts.isInterfaceDeclaration(superTypeDeclaration) ||\n              ts.isClassDeclaration(superTypeDeclaration)\n          );\n          nodes.push(\n            // Pass any defined type arguments for inlining in extracted nodes\n            // (e.g. `...extends EventTarget<WorkerGlobalScopeEventMap>`).\n            ...extractGlobalNodes(superTypeDeclaration, superType.typeArguments)\n          );\n        }\n      }\n    }\n\n    // Extract methods/properties\n    const modifiers: ts.Modifier[] = [\n      ctx.factory.createToken(ts.SyntaxKind.DeclareKeyword),\n    ];\n    for (const member of node.members) {\n      const maybeNode = maybeExtractGlobalNode(ctx, member, modifiers);\n      if (maybeNode !== undefined) {\n        nodes.push(ts.visitNode(maybeNode, inlineVisitor));\n      }\n    }\n\n    return nodes;\n  }\n\n  // Finds the `ServiceWorkerGlobalScope` declaration, calls\n  // `extractGlobalNodes` with it, and inserts all extracted nodes.\n  const serviceWorkerGlobalScopeVisitor: ts.Visitor = (node) => {\n    if (\n      (ts.isInterfaceDeclaration(node) || ts.isClassDeclaration(node)) &&\n      node.name !== undefined &&\n      node.name.text === \"ServiceWorkerGlobalScope\"\n    ) {\n      return [node, ...extractGlobalNodes(node)];\n    }\n    return node;\n  };\n  return serviceWorkerGlobalScopeVisitor;\n}\n"
  },
  {
    "path": "types/src/transforms/helpers.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from \"node:assert\";\nimport ts from \"typescript\";\nimport { printNode } from \"../print\";\n\n// Checks whether the modifiers array contains a modifier of the specified kind\nexport function hasModifier(\n  modifiers: ReadonlyArray<ts.Modifier> | undefined,\n  kind: ts.Modifier[\"kind\"]\n): boolean {\n  if (modifiers === undefined) return false;\n  return modifiers.some((modifier) => modifier.kind === kind);\n}\n\n// Ensure a modifiers array has the specified modifier, inserting it at the\n// start if it doesn't.\nexport function ensureModifier(\n  ctx: ts.TransformationContext,\n  modifiers: ReadonlyArray<ts.Modifier> | undefined,\n  ensure: ts.SyntaxKind.ExportKeyword | ts.SyntaxKind.DeclareKeyword\n): ReadonlyArray<ts.Modifier> {\n  // If modifiers already contains the required modifier, return it as is...\n  if (modifiers !== undefined && hasModifier(modifiers, ensure)) {\n    return modifiers;\n  }\n  // ...otherwise, add the modifier to the start of the array\n  return [ctx.factory.createToken(ensure), ...(modifiers ?? [])];\n}\n\n// Ensure a modifiers array doesn't have the specified modifier\nexport function ensureNoModifier(\n  _ctx: ts.TransformationContext,\n  modifiers: ReadonlyArray<ts.Modifier> | undefined,\n  ensure: ts.SyntaxKind.ExportKeyword | ts.SyntaxKind.DeclareKeyword\n): ReadonlyArray<ts.Modifier> {\n  // If modifiers already doesn't contain the required modifier, return it as is...\n  if (modifiers !== undefined && !hasModifier(modifiers, ensure)) {\n    return modifiers;\n  }\n  // ...otherwise, remove the modifier\n  return modifiers?.filter((m) => m.kind !== ensure) ?? [];\n}\n\nexport interface ModifierRequirements {\n  export?: boolean;\n  declare?: boolean;\n}\n// Ensures a node satisfies the specified modifier requirements\nfunction ensureModifierRequirements(\n  ctx: ts.TransformationContext,\n  node: ts.HasModifiers,\n  reqs: ModifierRequirements\n): ReadonlyArray<ts.Modifier> | undefined {\n  let modifiers = ts.getModifiers(node);\n  if (reqs.declare !== undefined) {\n    modifiers = (reqs.declare ? ensureModifier : ensureNoModifier)(\n      ctx,\n      modifiers,\n      ts.SyntaxKind.DeclareKeyword\n    );\n  }\n  if (reqs.export !== undefined) {\n    modifiers = (reqs.export ? ensureModifier : ensureNoModifier)(\n      ctx,\n      modifiers,\n      ts.SyntaxKind.ExportKeyword\n    );\n  }\n  return modifiers;\n}\n\n// Make sure replacement node is `export`ed, with the `declare` modifier if it's\n// a class, variable or function declaration.\n// If the `noExport` option is set, only ensure `declare` modifiers\nexport function ensureStatementModifiers(\n  ctx: ts.TransformationContext,\n  node: ts.Node,\n  reqs: ModifierRequirements\n): ts.Statement {\n  if (ts.isClassDeclaration(node)) {\n    return ctx.factory.updateClassDeclaration(\n      node,\n      ensureModifierRequirements(ctx, node, reqs),\n      node.name,\n      node.typeParameters,\n      node.heritageClauses,\n      node.members\n    );\n  }\n  if (ts.isInterfaceDeclaration(node)) {\n    return ctx.factory.updateInterfaceDeclaration(\n      node,\n      ensureModifierRequirements(ctx, node, { ...reqs, declare: undefined }),\n      node.name,\n      node.typeParameters,\n      node.heritageClauses,\n      node.members\n    );\n  }\n  if (ts.isEnumDeclaration(node)) {\n    return ctx.factory.updateEnumDeclaration(\n      node,\n      ensureModifierRequirements(ctx, node, reqs),\n      node.name,\n      node.members\n    );\n  }\n  if (ts.isTypeAliasDeclaration(node)) {\n    return ctx.factory.updateTypeAliasDeclaration(\n      node,\n      ensureModifierRequirements(ctx, node, { ...reqs, declare: undefined }),\n      node.name,\n      node.typeParameters,\n      node.type\n    );\n  }\n  if (ts.isVariableStatement(node)) {\n    return ctx.factory.updateVariableStatement(\n      node,\n      ensureModifierRequirements(ctx, node, reqs),\n      node.declarationList\n    );\n  }\n  if (ts.isFunctionDeclaration(node)) {\n    return ctx.factory.updateFunctionDeclaration(\n      node,\n      ensureModifierRequirements(ctx, node, reqs),\n      node.asteriskToken,\n      node.name,\n      node.typeParameters,\n      node.parameters,\n      node.type,\n      node.body\n    );\n  }\n  if (ts.isModuleDeclaration(node)) {\n    return ctx.factory.updateModuleDeclaration(\n      node,\n      ensureModifierRequirements(ctx, node, reqs),\n      node.name,\n      node.body\n    );\n  }\n  if (\n    ts.isImportDeclaration(node) ||\n    ts.isImportEqualsDeclaration(node) ||\n    ts.isExportDeclaration(node) ||\n    ts.isExportAssignment(node)\n  ) {\n    return node;\n  }\n  assert.fail(`Expected statement, got \"${printNode(node)}\"`);\n}\n"
  },
  {
    "path": "types/src/transforms/import-resolve.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from \"node:assert\";\nimport ts from \"typescript\";\n\n// Adapted from https://github.com/cloudflare/workerd/blob/2182afdd8ca9ac35fb18b76205308fabd5000d01/src/node/tsconfig.json#L27-L32.\n// Maps import specifier patterns to target path pattern. Sorted by target path\n// specificity (highest to lowest). When un-resolving target paths to import\n// specifiers, the most specific will be checked first. Note all target paths\n// must be absolute (i.e. start with \"/\").\nconst TSCONFIG_PATHS: Record<string, string> = {\n  \"node-internal:*\": \"/internal/*\",\n  \"node:*\": \"/*\",\n};\n\n// Resolves all relative imports in `declare module` blocks constructed from\n// `*.d.ts` file contents, using `TSCONFIG_PATHS` to convert between specifiers\n// and on-disk paths:\n//\n// ```ts\n// declare module \"node:crypto\" {\n//   const _default: {\n//     DiffieHellman: new (key: import(\"./internal/crypto\").ArrayLike, ...) => ...;\n//     Hmac: new (..., options?: import(\"./internal/streams_transform\").TransformOptions) => ...;\n//   };\n//   ...\n// }\n// declare module \"node-internal:events\" {\n//   export namespace EventEmitter {\n//     var on: typeof import(\"./events\").on;\n//   }\n// }\n// ```\n//\n// --- transforms to --->\n//\n// ```ts\n// declare module \"node:crypto\" {\n//   const _default: {\n//     DiffieHellman: new (key: import(\"node-internal:crypto\").ArrayLike, ...) => ...;\n//     Hmac: new (..., options?: import(\"node-internal:streams_transform\").TransformOptions) => ...;\n//   };\n//   ...\n// }\n// declare module \"node-internal:events\" {\n//   export namespace EventEmitter {\n//     var on: typeof import(\"node-internal:events\").on;\n//   }\n// }\n// ```\nexport function createImportResolveTransformer(): ts.TransformerFactory<ts.SourceFile> {\n  return (ctx) => {\n    return (node) => {\n      const visitor = createImportResolveVisitor(ctx);\n      return ts.visitEachChild(node, visitor, ctx);\n    };\n  };\n}\n\n// `RegExp` in wildcard rule may optionally contain single capturing group\ntype WildcardRule = [/* from */ RegExp, /* to */ string];\nfunction entryWildcardRule([from, to]: [string, string]): WildcardRule {\n  return [new RegExp(`^${from.replace(\"*\", \"(.+)\")}$`), to];\n}\nfunction maybeApplyWildcardRule(rules: WildcardRule[], text: string): string | undefined {\n  for (const [from, to] of rules) {\n    const match = from.exec(text);\n    if (match === null) continue;\n    return match.at(1) === undefined ? to : to.replaceAll(\"*\", match[1]);\n  }\n}\n\nconst pathEntries = Object.entries(TSCONFIG_PATHS);\nconst pathResolveRules = pathEntries.map(entryWildcardRule);\nconst pathUnresolveRules = pathEntries.map(([from, to]) =>\n  entryWildcardRule([to, from])\n);\n\nfunction createImportResolveVisitor(ctx: ts.TransformationContext): ts.Visitor {\n  return (node) => {\n    // Visit all `declare module \"<specifier>\"` nodes\n    if (\n      ts.isModuleDeclaration(node) &&\n      (node.flags & ts.NodeFlags.Namespace) === 0 &&\n      ts.isStringLiteral(node.name)\n    ) {\n      const specifier = node.name.text;\n      const maybePath = maybeApplyWildcardRule(pathResolveRules, specifier);\n      // If we don't know the path of this module, we won't be able to do any\n      // resolving, so return it as is\n      if (maybePath === undefined) return node;\n      const moduleVisitor = createModuleImportResolveVisitor(ctx, maybePath);\n      return ts.visitEachChild(node, moduleVisitor, ctx);\n    }\n\n    return node;\n  };\n}\n\nfunction createModuleImportResolveVisitor(\n  ctx: ts.TransformationContext,\n  referencingPath: string\n): ts.Visitor {\n  assert(referencingPath.startsWith(\"/\"), \"Expected absolute referencing path\");\n  // `file:` protocol isn't important here, just need something for a valid URL\n  const referencingURL = new URL(referencingPath, \"file:\");\n\n  const visitor: ts.Visitor = (node) => {\n    if (\n      ts.isImportTypeNode(node) &&\n      ts.isLiteralTypeNode(node.argument) &&\n      ts.isStringLiteral(node.argument.literal)\n    ) {\n      const relativeSpecifier = node.argument.literal.text;\n      // If import isn't relative, no need to resolve it, so leave it as is\n      if (!relativeSpecifier.startsWith(\".\")) return node;\n      // Resolve specifier relative to referencing module\n      const resolvedURL = new URL(relativeSpecifier, referencingURL);\n      const resolvedPath = resolvedURL.pathname;\n      // Convert resolved path back to specifier\n      const specifier = maybeApplyWildcardRule(\n        pathUnresolveRules,\n        resolvedPath\n      );\n      assert(\n        specifier !== undefined,\n        `Unable to find matching specifier rule for path: \"${resolvedPath}\"`\n      );\n\n      // Update import with new specifier\n      const argument = ctx.factory.updateLiteralTypeNode(\n        node.argument,\n        ctx.factory.createStringLiteral(specifier)\n      );\n      return ctx.factory.updateImportTypeNode(\n        node,\n        argument,\n        node.attributes,\n        node.qualifier,\n        node.typeArguments,\n        node.isTypeOf\n      );\n    }\n\n    // Recursively visit all nodes (don't need to do this first as visitor never\n    // creates any new import type nodes)\n    return ts.visitEachChild(node, visitor, ctx);\n  };\n  return visitor;\n}\n"
  },
  {
    "path": "types/src/transforms/importable.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport ts from \"typescript\";\nimport { ensureStatementModifiers } from \"./helpers\";\n\n// This ensures that all top-level nodes are `export`ed, and removes\n// `declare module` blocks.\n//\n// ```ts\n// declare class A { ... }\n// declare module \"thing\" {\n//   class B { ... }\n// }\n// ```\n//\n// --- transforms to --->\n//\n// ```ts\n// export declare class A { ... }\n// ```\nexport function createImportableTransformer(): ts.TransformerFactory<ts.SourceFile> {\n  return (ctx) => {\n    return (node) => {\n      const visitor = createVisitor(ctx);\n      return ts.visitEachChild(node, visitor, ctx);\n    };\n  };\n}\n\nfunction createVisitor(ctx: ts.TransformationContext): ts.Visitor {\n  return (node: ts.Node) => {\n    // Remove `module` declarations (e.g. `declare module \"assets:*\" {...}`) as\n    // these can't be `export`ed, and don't really make sense in non-ambient\n    // declarations\n    if (\n      ts.isModuleDeclaration(node) &&\n      (node.flags & ts.NodeFlags.Namespace) === 0\n    ) {\n      return;\n    }\n    return ensureStatementModifiers(ctx, node, { export: true });\n  };\n}\n"
  },
  {
    "path": "types/src/transforms/index.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nexport * from \"./ambient\";\nexport * from \"./comments\";\nexport * from \"./globals\";\nexport * from \"./import-resolve\";\nexport * from \"./importable\";\nexport * from \"./internal-namespace\";\nexport * from \"./iterators\";\nexport * from \"./overrides\";\n"
  },
  {
    "path": "types/src/transforms/internal-namespace.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'node:assert';\nimport { Structure, StructureGroups } from '@workerd/jsg/rtti';\nimport ts from 'typescript';\nimport { StructureMap, getTypeName } from '../generator';\nimport { maybeExtractGlobalNode } from './globals';\nimport { ensureStatementModifiers } from './helpers';\nimport { createRenameVisitor } from './overrides';\n\n// Moves all members (excluding imports) of internal `declare module` blocks\n// into namespaces that are then `export default`ed:\n//\n// ```ts\n// declare module \"node-internal:diagnostics_channel\" {\n//   import _internal1 from \"node-internal:async_hooks\";\n//   import AsyncLocalStorage = _internal1.AsyncLocalStorage;\n//   interface DiagnosticsChannelModule {\n//     readonly property: boolean;\n//     channel<T>(key: PropertyKey): Channel<T>;\n//     bindStore(store: AsyncLocalStorage, ...): void;\n//     Channel: typeof Channel;\n//   }\n//   abstract class Channel<T> {\n//     ...\n//   }\n// }\n// ```\n//\n// --- transforms to --->\n//\n// ```ts\n// declare module \"node-internal:diagnostics_channel\" {\n//   import _internal1 from \"node-internal:async_hooks\";\n//   import AsyncLocalStorage = _internal1.AsyncLocalStorage;\n//   namespace _default {\n//     const property: boolean;\n//     function channel<T>(key: PropertyKey): Channel<T>;\n//     function bindStore(store: AsyncLocalStorage, ...): void;\n//     abstract class Channel<T> {\n//       ...\n//     }\n//   }\n//   export default _default;\n// }\n// ```\n//\n// Note we can't just generate the `namespace _default { ... }` when initially\n// building the AST from RTTI, as the overrides/defines transformer only\n// operates on `interface`/ `class`es, not bare `const`/`function`s.\n//\n// An alternative to exporting a `namespace` would be to export an instance of\n// the root module type:\n//\n// ```\n// declare module \"node-internal:diagnostics_channel\" {\n//   ...\n//   const _default: DiagnosticsChannelModule;\n//   export default _default;\n// }\n// ```\n//\n// Whilst this correctly exports *values*, it doesn't export *types*.\n// Importantly, it erases type parameters from generic types (i.e. `Channel<T>`\n// becomes `Channel`, with `T`s becoming `unknown`).\nexport function createInternalNamespaceTransformer(\n  root: StructureGroups,\n  structureMap: StructureMap\n): ts.TransformerFactory<ts.SourceFile> {\n  return (ctx) => {\n    return (node) => {\n      const moduleStructures = collectInternalModuleStructures(\n        root,\n        structureMap\n      );\n      const visitor = createInternalNamespaceVisitor(moduleStructures, ctx);\n      return ts.visitEachChild(node, visitor, ctx);\n    };\n  };\n}\n\nfunction collectInternalModuleStructures(\n  root: StructureGroups,\n  structureMap: StructureMap\n): Map<string, Structure> {\n  const moduleRoots = new Map</* specifier */ string, Structure>(); // TODO: add members here as well?\n  root.modules.forEach((module) => {\n    if (!module._isStructureName) return;\n    const structure = structureMap.get(module.structureName);\n    assert(structure !== undefined, 'Structure is undefined');\n    const specifier = module.specifier;\n    moduleRoots.set(specifier, structure);\n  });\n  return moduleRoots;\n}\n\nfunction createInternalNamespaceVisitor(\n  moduleRoots: ReturnType<typeof collectInternalModuleStructures>,\n  ctx: ts.TransformationContext\n): ts.Visitor {\n  const visitor: ts.Visitor = (node) => {\n    if (\n      ts.isModuleDeclaration(node) &&\n      (node.flags & ts.NodeFlags.Namespace) === 0 &&\n      ts.isStringLiteral(node.name) &&\n      node.body !== undefined &&\n      ts.isModuleBlock(node.body)\n    ) {\n      // This transformer should only be called on types generated from C++.\n      // Therefore, all `declare modules` represent internal modules.\n      const moduleRoot = moduleRoots.get(node.name.text);\n      assert(\n        moduleRoot !== undefined,\n        `Expected \"${node.name.text}\" to be an internal module`\n      );\n      const moduleRootName = getTypeName(moduleRoot);\n\n      // Ensure all nested types have the correct name. We do this after\n      // overrides as some module members would otherwise have the same name as\n      // global types (e.g. `cloudflare:workers` `DurableObjectBase` and\n      // `DurableObject`)\n      const renames = new Map</* from */ string, /* to */ string>();\n      moduleRoot.members.forEach((member) => {\n        if (member._isNested) {\n          const nested = member.nested;\n          const generatedName = getTypeName(nested.structure);\n          const actualName = nested.name;\n          if (generatedName !== actualName) {\n            renames.set(generatedName, actualName);\n          }\n        }\n      });\n\n      // Filter array of statements to keep at the top-level of the module, and\n      // build array of statements to include in default namespace export\n      const namespaceStatements: ts.Statement[] = [];\n      const moduleStatements = node.body.statements.filter((statement) => {\n        if (\n          ts.isImportDeclaration(statement) ||\n          ts.isImportEqualsDeclaration(statement)\n        ) {\n          // Keep import statements at top-level of module\n          return true;\n        } else if (\n          ts.isInterfaceDeclaration(statement) &&\n          statement.name.text === moduleRootName\n        ) {\n          // Extract out properties and functions from the internal module root\n          // type. Note internal module root types never have constructors, so\n          // should always be interfaces. Internal module root types should\n          // never have type parameters too, so we don't need to worry about\n          // inlining type arguments, unlike the global scope visitor.\n          for (const member of statement.members) {\n            const maybeNode = maybeExtractGlobalNode(ctx, member);\n            if (maybeNode !== undefined) namespaceStatements.push(maybeNode);\n          }\n          // Remove the root type from the module top-level\n          return false;\n        } else {\n          // Assume all other class/interface definitions are nested types that\n          // should be included in the default namespace export...\n          namespaceStatements.push(\n            ensureStatementModifiers(ctx, statement, { declare: false })\n          );\n          // ...and removed from the module top-level\n          return false;\n        }\n      });\n\n      // Add default namespace export to top-level statements\n      const defaultIdentifier = ctx.factory.createIdentifier('_default');\n      const namespaceBody = ctx.factory.createModuleBlock(namespaceStatements);\n      const namespaceDeclaration = ctx.factory.createModuleDeclaration(\n        /* modifiers */ undefined,\n        defaultIdentifier,\n        namespaceBody,\n        ts.NodeFlags.Namespace\n      );\n      const exportStatement = ctx.factory.createExportAssignment(\n        /* modifiers */ undefined,\n        /* isExportEquals */ undefined,\n        defaultIdentifier\n      );\n      moduleStatements.push(namespaceDeclaration, exportStatement);\n\n      // Return updated module declaration with new top-level statements\n      let body = ctx.factory.updateModuleBlock(node.body, moduleStatements);\n      if (renames.size > 0) {\n        const renameVisitor = createRenameVisitor(\n          ctx,\n          renames,\n          /* renameClassesInterfaces */ true\n        );\n        body = ts.visitEachChild(body, renameVisitor, ctx);\n      }\n      return ctx.factory.updateModuleDeclaration(\n        node,\n        node.modifiers,\n        node.name,\n        body\n      );\n    }\n\n    return node;\n  };\n  return visitor;\n}\n"
  },
  {
    "path": "types/src/transforms/iterators.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from \"node:assert\";\nimport ts from \"typescript\";\nimport { printNode } from \"../print\";\n\n// Replaces custom Iterator-like interfaces with built-in `Iterator` types:\n//\n// ```ts\n// export class Thing {\n//   readonly things: ThingIterator;\n//   asyncThings(): AsyncThingIterator;\n// }\n//\n// export interface ThingIterator extends Iterator {\n//   next(): ThingIteratorNext;\n//   [Symbol.iterator](): any;\n// }\n// export interface ThingIteratorNext {\n//   done: boolean;\n//   value?: string;\n// }\n//\n// export interface AsyncThingIterator extends AsyncIterator {\n//   next(): Promise<AsyncThingIteratorNext>;\n//   return(value?: any): Promise<AsyncThingIteratorNext>;\n//   [Symbol.asyncIterator](): any;\n// }\n// export interface AsyncThingIteratorNext {\n//   done: boolean;\n//   value?: number;\n// }\n// ```\n//\n// --- transforms to --->\n//\n// ```ts\n// export class Thing {\n//   readonly things: IterableIterator<string>;\n//   asyncThings(): AsyncIterableIterator<number>;\n// }\n// ```\nexport function createIteratorTransformer(\n  checker: ts.TypeChecker\n): ts.TransformerFactory<ts.SourceFile> {\n  return (ctx) => {\n    return (node) => {\n      const iteratorCtx: IteratorTransformContext = {\n        types: new Map(),\n        nextInterfaces: new Set(),\n      };\n      const v1 = createIteratorDeclarationsVisitor(ctx, checker, iteratorCtx);\n      const v2 = createIteratorUsagesVisitor(ctx, checker, iteratorCtx);\n      node = ts.visitEachChild(node, v1, ctx);\n      return ts.visitEachChild(node, v2, ctx);\n    };\n  };\n}\n\ninterface IteratorTransformContext {\n  // Maps iterator-like interfaces to built-in `Iterator` types\n  types: Map<ts.Symbol, ts.TypeNode>;\n  // Set of iterator-next interfaces to remove\n  nextInterfaces: Set<ts.Symbol>;\n}\n\n// Find all interfaces extending `Iterator`, record their next value type,\n// and remove them. Also record the names of next interfaces for removal.\nfunction createIteratorDeclarationsVisitor(\n  ctx: ts.TransformationContext,\n  checker: ts.TypeChecker,\n  iteratorCtx: IteratorTransformContext\n): ts.Visitor {\n  const visitor: ts.Visitor = (node) => {\n    // Visit interfaces inside module declarations too\n    if (ts.isModuleDeclaration(node) || ts.isModuleBody(node)) {\n      return ts.visitEachChild(node, visitor, ctx);\n    }\n\n    if (ts.isInterfaceDeclaration(node)) {\n      // Check if interface extends `Iterator`\n      const extendsNode = node.heritageClauses?.[0];\n      if (\n        extendsNode?.token === ts.SyntaxKind.ExtendsKeyword &&\n        extendsNode.types.length === 1 &&\n        ts.isIdentifier(extendsNode.types[0].expression) &&\n        (extendsNode.types[0].expression.text === \"Iterator\" ||\n          extendsNode.types[0].expression.text === \"AsyncIterator\")\n      ) {\n        const isAsync = extendsNode.types[0].expression.text !== \"Iterator\";\n        // Check `node` has one of the following shapes:\n        // ```ts\n        // export interface ThingIterator extends Iterator {\n        //   next(): ThingIteratorNext;\n        //   [Symbol.iterator](): any;\n        // }\n        // export interface AsyncThingIterator extends AsyncIterator {\n        //   next(): Promise<AsyncThingIteratorNext>;\n        //   return(value?: any): Promise<AsyncThingIteratorNext>;\n        //   [Symbol.asyncIterator](): any;\n        // }\n        // ```\n        let nextTypeNode: ts.TypeNode | undefined;\n        for (const member of node.members) {\n          if (\n            ts.isMethodSignature(member) &&\n            ts.isIdentifier(member.name) &&\n            member.name.text === \"next\" &&\n            member.type !== undefined\n          ) {\n            nextTypeNode = member.type;\n          }\n        }\n        assert(\n          nextTypeNode !== undefined,\n          `Expected iterator-like interface, got \"${printNode(node)}\"`\n        );\n\n        // Extract `IteratorBase_ThingIterator_...Next` type\n        if (isAsync) {\n          // Unwrap Promise type\n          assert(\n            ts.isTypeReferenceNode(nextTypeNode) &&\n              ts.isIdentifier(nextTypeNode.typeName) &&\n              nextTypeNode.typeName.text === \"Promise\" &&\n              nextTypeNode.typeArguments?.length === 1,\n            `Expected Promise, got \"${printNode(nextTypeNode)}\"`\n          );\n          nextTypeNode = nextTypeNode.typeArguments[0];\n        }\n\n        // Check `IteratorBase_ThingIterator_...Next` has the following shape,\n        // and extract the `value?: T` declaration\n        // ```ts\n        // export interface ThingIteratorNext {\n        //   done: boolean;\n        //   value?: string;\n        // }\n        // ```\n        const nextType = checker.getTypeFromTypeNode(nextTypeNode);\n        const nextTypeSymbol = nextType.getSymbol();\n        assert(nextTypeSymbol?.members !== undefined);\n        let nextValueSymbol: ts.Symbol | undefined;\n        nextTypeSymbol.members.forEach((value, key) => {\n          // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison\n          if (key === \"value\") nextValueSymbol = value;\n        });\n        assert(nextValueSymbol !== undefined);\n        const nextValueDeclarations = nextValueSymbol.getDeclarations();\n        assert.strictEqual(nextValueDeclarations?.length, 1);\n        const nextValueDeclaration = nextValueDeclarations[0];\n        assert(ts.isPropertySignature(nextValueDeclaration));\n        // Mark this interface for removal\n        iteratorCtx.nextInterfaces.add(nextTypeSymbol);\n\n        // Extract `value`'s type\n        const nextValueType = nextValueDeclaration.type;\n        assert(nextValueType !== undefined);\n\n        // Record this iterator type...\n        const nodeType = checker.getTypeAtLocation(node);\n        const nodeSymbol = nodeType.getSymbol();\n        assert(nodeSymbol !== undefined);\n        const iteratorType = ctx.factory.createTypeReferenceNode(\n          isAsync ? \"AsyncIterableIterator\" : \"IterableIterator\",\n          [nextValueType]\n        );\n        iteratorCtx.types.set(nodeSymbol, iteratorType);\n        // ...and remove the node by returning `undefined`\n        return;\n      }\n    }\n\n    return node;\n  };\n  return visitor;\n}\n\n// Replace uses of iterator interfaces with built-in iterator type.\n// Also remove all previously recorded next interfaces.\nfunction createIteratorUsagesVisitor(\n  ctx: ts.TransformationContext,\n  checker: ts.TypeChecker,\n  iteratorCtx: IteratorTransformContext\n): ts.Visitor {\n  // Find the built-in iterator type associated with a method's return type\n  // or property's type\n  function findIteratorType(\n    node:\n      | ts.MethodSignature\n      | ts.MethodDeclaration\n      | ts.PropertySignature\n      | ts.PropertyDeclaration\n      | ts.GetAccessorDeclaration\n  ): ts.TypeNode | undefined {\n    if (node.type === undefined) return;\n    const type = checker.getTypeFromTypeNode(node.type);\n    const typeSymbol = type.getSymbol();\n    if (typeSymbol !== undefined) return iteratorCtx.types.get(typeSymbol);\n  }\n\n  const visitor: ts.Visitor = (node) => {\n    // Remove all next interfaces by returning `undefined`\n    if (ts.isInterfaceDeclaration(node)) {\n      const type = checker.getTypeAtLocation(node);\n      const symbol = type.getSymbol();\n      if (symbol !== undefined && iteratorCtx.nextInterfaces.has(symbol)) {\n        return;\n      }\n    }\n\n    // Visit all interface/class/module declaration children\n    if (\n      ts.isInterfaceDeclaration(node) ||\n      ts.isClassDeclaration(node) ||\n      ts.isModuleDeclaration(node) ||\n      ts.isModuleBody(node)\n    ) {\n      return ts.visitEachChild(node, visitor, ctx);\n    }\n\n    // Replace all method return types and property types referencing iterators\n    // with the built-in type\n    if (ts.isMethodSignature(node)) {\n      const iteratorType = findIteratorType(node);\n      if (iteratorType !== undefined) {\n        return ctx.factory.updateMethodSignature(\n          node,\n          node.modifiers,\n          node.name,\n          node.questionToken,\n          node.typeParameters,\n          node.parameters,\n          iteratorType\n        );\n      }\n    }\n    if (ts.isMethodDeclaration(node)) {\n      const iteratorType = findIteratorType(node);\n      if (iteratorType !== undefined) {\n        return ctx.factory.updateMethodDeclaration(\n          node,\n          node.modifiers,\n          node.asteriskToken,\n          node.name,\n          node.questionToken,\n          node.typeParameters,\n          node.parameters,\n          iteratorType,\n          node.body\n        );\n      }\n    }\n    if (ts.isPropertySignature(node)) {\n      const iteratorType = findIteratorType(node);\n      if (iteratorType !== undefined) {\n        return ctx.factory.updatePropertySignature(\n          node,\n          node.modifiers,\n          node.name,\n          node.questionToken,\n          iteratorType\n        );\n      }\n    }\n    if (ts.isPropertyDeclaration(node)) {\n      const iteratorType = findIteratorType(node);\n      if (iteratorType !== undefined) {\n        return ctx.factory.updatePropertyDeclaration(\n          node,\n          node.modifiers,\n          node.name,\n          node.questionToken ?? node.exclamationToken,\n          iteratorType,\n          node.initializer\n        );\n      }\n    }\n    if (ts.isGetAccessorDeclaration(node)) {\n      const iteratorType = findIteratorType(node);\n      if (iteratorType !== undefined) {\n        return ctx.factory.updateGetAccessorDeclaration(\n          node,\n          node.modifiers,\n          node.name,\n          node.parameters,\n          iteratorType,\n          node.body\n        );\n      }\n    }\n\n    return node;\n  };\n  return visitor;\n}\n"
  },
  {
    "path": "types/src/transforms/onmessage-declaration.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport ts from \"typescript\";\n\nexport function createAddOnMessageDeclarationTransformer(): ts.TransformerFactory<ts.SourceFile> {\n  return (context) => {\n    return (sourceFile) => {\n      // Create the new variable declaration\n      const onMessageDeclaration = context.factory.createVariableStatement(\n        [context.factory.createModifier(ts.SyntaxKind.DeclareKeyword)],\n        context.factory.createVariableDeclarationList(\n          [\n            context.factory.createVariableDeclaration(\n              \"onmessage\",\n              undefined,\n              context.factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword)\n            ),\n          ],\n          ts.NodeFlags.None\n        )\n      );\n\n      // Prepend the new declaration to the source file\n      const updatedStatements = ts.factory.createNodeArray([\n        onMessageDeclaration,\n        ...sourceFile.statements,\n      ]);\n\n      // Return the updated source file\n      return ts.factory.updateSourceFile(sourceFile, updatedStatements);\n    };\n  };\n}\n"
  },
  {
    "path": "types/src/transforms/overrides/compiler.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'node:assert';\nimport path from 'node:path';\nimport { StructureGroups } from '@workerd/jsg/rtti';\nimport ts from 'typescript';\nimport { getTypeName } from '../../generator';\nimport { SourcesMap } from '../../program';\n\n// If an override matches this RegExp, it will replace the existing definition\nconst keywordReplace =\n  /^export |^declare |^type |^abstract |^class |^interface |^enum |^const |^var |^function /;\n// If an override matches this RegExp, it will have `class ${name} ` prefixed\nconst keywordHeritage = /^extends |^implements /;\n\nfunction compileOverride(\n  name: string,\n  override: string\n): [compiled: string, isReplacement: boolean] {\n  // If this override is a complete type replacement, return it as is\n  // Examples:\n  // - `const WebSocketPair: { new (): { 0: WebSocket; 1: WebSocket }; }`\n  // - `type ReadableStreamReadResult<R = any> = { done: false, value: R; } | { done: true; value?: undefined; }`\n  // - `type TransactionOptions = never` (deletes definition)\n  if (keywordReplace.test(override)) return [override, true];\n\n  // Fix up overrides, so they can be parsed as TypeScript source files. Whilst\n  // we convert all overrides to classes, this type classification is ignored\n  // when merging. Classes just support all possible forms of override (extends,\n  // implements, constructors, (static) properties/methods).\n  if (keywordHeritage.test(override) || override.startsWith('{')) {\n    // Use existing name and type classification, may merge members\n    // Examples:\n    // - `extends EventTarget<WorkerGlobalScopeEventMap>`\n    // - `extends TransformStream<ArrayBuffer | ArrayBufferView, Uint8Array> { constructor(format: \"gzip\" | \"deflate\" | \"deflate-raw\"); }`\n    // - `{ json<T>(): Promise<T>; }`\n    override = `class ${name} ${override}`;\n  } else if (override.startsWith('<')) {\n    // Use existing name and type classification, may merge members\n    // Examples:\n    // - `<R = any> { read(): Promise<ReadableStreamReadResult<R>>; }`\n    override = `class ${name}${override}`;\n  } else {\n    // Use existing type classification, may rename and merge members\n    // Examples:\n    // - `KVNamespaceGetOptions<Type> { type: Type; }`\n    // - `KVNamespaceListOptions` (just rename definition)\n    // - `WorkerGlobalScope extends EventTarget<WorkerGlobalScopeEventMap>`\n    override = `class ${override}`;\n  }\n  // Purely heritage and rename overrides don't need to define any members, but\n  // they still need to be valid classes for parsing.\n  if (!override.endsWith('}')) {\n    override = `${override} {}`;\n  }\n\n  return [override, false];\n}\n\nconst overridesPath = '/$virtual/overrides';\nconst definesPath = '/$virtual/defines';\n\n// Converts and collects all overrides and defines as TypeScript source files.\n// Also returns a set of definitions that should be replaced by their override.\nexport function compileOverridesDefines(\n  root: StructureGroups\n): [sources: SourcesMap, replacements: Set<string>] {\n  const sources = new SourcesMap();\n  // Types that need their definition completely replaced by their override\n  const replacements = new Set<string>();\n\n  root.groups.forEach((group) => {\n    group.structures.forEach((structure) => {\n      const name = getTypeName(structure);\n      const override = structure.tsOverride.trim();\n      const define = structure.tsDefine.trim();\n\n      if (override !== '') {\n        const [compiled, isReplacement] = compileOverride(name, override);\n        sources.set(path.join(overridesPath, name + '.ts'), compiled);\n        if (isReplacement) replacements.add(name);\n      }\n      if (define !== '') {\n        sources.set(path.join(definesPath, name + '.ts'), define);\n      }\n    });\n  });\n\n  return [sources, replacements];\n}\n\n// Try to find override for structure using name obtained from `getTypeName()`\nexport function maybeGetOverride(\n  program: ts.Program,\n  name: string\n): ts.Statement | undefined {\n  const sourcePath = path.join(overridesPath, name + '.ts');\n  const sourceFile = program.getSourceFile(sourcePath);\n  if (sourceFile !== undefined) {\n    assert.strictEqual(sourceFile.statements.length, 1);\n    return sourceFile.statements[0];\n  }\n}\n\n// Try to find defines for structure using name obtained from `getTypeName()`\nexport function maybeGetDefines(\n  program: ts.Program,\n  name: string\n): ts.NodeArray<ts.Statement> | undefined {\n  const sourcePath = path.join(definesPath, name + '.ts');\n  const sourceFile = program.getSourceFile(sourcePath);\n  return sourceFile?.statements;\n}\n"
  },
  {
    "path": "types/src/transforms/overrides/index.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from \"node:assert\";\nimport ts from \"typescript\";\nimport { isUnsatisfiable } from \"../../generator/type\";\nimport { printNode } from \"../../print\";\nimport { ensureStatementModifiers, hasModifier } from \"../helpers\";\nimport { maybeGetDefines, maybeGetOverride } from \"./compiler\";\n\nexport { compileOverridesDefines } from \"./compiler\";\n\n// Applies handwritten partial TypeScript overrides to generate types to improve\n// output fidelity. Also applies type renames and inserts additional handwritten\n// definitions for non-generated types.\n//\n// See the `JSG_TS_OVERRIDE` macro's documentation in `src/workerd/jsg/jsg.h`\n// for a full explanation of override rules and examples.\n//\n// `compileOverridesDefines()` must be used to compile overrides and defines\n// into valid TypeScript source files. These should be included in the *same*\n// TypeScript `Program` as the source file being transformed. This `Program`,\n// along with the set of types that should be fully-replaced (also returned from\n// `compileOverridesDefines()`), should be passed to this transformer factory.\n//\n// ```ts\n// export declare class A {\n//   thing: string;\n// }\n// export declare class B {\n//   get(key: string, type: string): Promise<any>;\n//   put(key: string, value: string): Promise<void>;\n// }\n// ```\n//\n// ...with the following overrides and defines...\n//\n// - `A`'s override: `RenamedA<Type extends string> { thing: Type; }`\n// - `B`'s override: `{\n//     get(key: string, type: \"text\"): Promise<string | null>;\n//     get(key: string, type: \"arrayBuffer\"): Promise<ArrayBuffer | null>;\n//   }`\n// - `B`'s define: `interface C { foo: A; }`\n//\n// --- transforms to --->\n//\n// ```ts\n// export declare class RenamedA<Type extends string> {\n//   thing: Type;\n// }\n// export interface C {\n//   foo: RenamedA;\n// }\n// export declare class B {\n//   get(key: string, type: \"text\"): Promise<string | null>;\n//   get(key: string, type: \"arrayBuffer\"): Promise<ArrayBuffer | null>;\n//   put(key: string, value: string): Promise<void>;\n// }\n// ```\nexport function createOverrideDefineTransformer(\n  program: ts.Program,\n  replacements: Set<string>\n): ts.TransformerFactory<ts.SourceFile> {\n  return (ctx) => {\n    return (node) => {\n      const overrideCtx: OverrideTransformContext = {\n        program,\n        replacements,\n        renames: new Map<string, string>(),\n      };\n      const v1 = createOverrideDefineVisitor(ctx, overrideCtx);\n      const v2 = createRenameVisitor(ctx, overrideCtx.renames);\n      node = ts.visitEachChild(node, v1, ctx);\n      return ts.visitEachChild(node, v2, ctx);\n    };\n  };\n}\n\ninterface OverrideTransformContext {\n  program: ts.Program;\n  replacements: Set<string>;\n  renames: Map</* from */ string, /* to */ string>;\n}\n\n// Gets an identifying label for this member, shared between method overloads\nfunction getMemberKey(member: ts.ClassElement | ts.TypeElement): string {\n  if (ts.isConstructorDeclaration(member)) return \"constructor$\";\n\n  const name = member.name;\n  assert(\n    name !== undefined,\n    `Expected named member, got \"${printNode(member)}\"`\n  );\n\n  // Put static and instance members in different namespaces. For example, this\n  // allows instance methods to be overridden without affecting static methods\n  // of the same name.\n  const isStatic =\n    ts.canHaveModifiers(member) &&\n    hasModifier(ts.getModifiers(member), ts.SyntaxKind.StaticKeyword);\n  const keyNamespace = isStatic ? \"static$\" : \"instance$\";\n\n  if (\n    ts.isIdentifier(name) ||\n    ts.isStringLiteral(name) ||\n    ts.isNumericLiteral(name)\n  ) {\n    return keyNamespace + name.text;\n  }\n  if (ts.isComputedPropertyName(name)) {\n    const expression = name.expression;\n    if (ts.isStringLiteral(expression) || ts.isNumericLiteral(expression)) {\n      return keyNamespace + expression.text;\n    }\n  }\n  return keyNamespace + printNode(name);\n}\n\n// Groups override members by their identifying labels from `getMemberKey()`\nfunction groupMembersByKey<Member extends ts.ClassElement | ts.TypeElement>(\n  members: ts.NodeArray<Member>\n): Map<string, Member[]> {\n  const result = new Map<string, Member[]>();\n  members.forEach((member) => {\n    const key = getMemberKey(member);\n    let array = result.get(key);\n    if (array === undefined) result.set(key, (array = []));\n    array.push(member);\n  });\n  return result;\n}\n\n// Returns the index of a member in `members` with the specified `key`, or -1 if\n// none exists\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters\nfunction findMemberIndex<Member extends ts.ClassElement | ts.TypeElement>(\n  members: Member[],\n  key: string,\n  fromIndex = 0\n): number {\n  return members.findIndex(\n    (member, index) => fromIndex <= index && getMemberKey(member) === key\n  );\n}\n\n// Merges generated members with overrides according to the following rules:\n// 1. Members in the override but not in the generated type are inserted\n// 2. If an override has the same key as a member in the generated type, the\n//   generated member is removed, and the override is inserted instead\n// 3. If an override member property is declared type `never`, it is not\n//   inserted, but its presence may remove the generated member (as per 2)\nfunction mergeMembers<Member extends ts.ClassElement | ts.TypeElement>(\n  generated: ts.NodeArray<Member>,\n  overrides: ts.NodeArray<ts.ClassElement>,\n  transformer: (member: ts.ClassElement) => Member\n): Member[] {\n  const result = [...generated];\n  const grouped = groupMembersByKey(overrides);\n  for (const [key, overrideMembers] of grouped) {\n    const filteredOverrideMembers = overrideMembers.filter((member) => {\n      // Filter out `never` typed properties\n      if (ts.isPropertyDeclaration(member) && member.type !== undefined) {\n        return !isUnsatisfiable(member.type);\n      }\n      // Include all other members\n      return true;\n    });\n    // Transform all class elements into the correct member type. If `Member` is\n    // `ts.ClassElement` already, `transformer` will be the identify function.\n    const transformedOverrideMembers = filteredOverrideMembers.map(transformer);\n\n    // Try to find index of existing generated member with same key\n    const index = findMemberIndex(result, key);\n    if (index === -1) {\n      // If the member couldn't be found, insert overrides at the end\n      result.push(...transformedOverrideMembers);\n    } else {\n      const member = result[index];\n      const nextIndex = findMemberIndex(result, key, index + 1);\n      if (\n        ts.isGetAccessorDeclaration(member) ||\n        ts.isSetAccessorDeclaration(member)\n      ) {\n        // If this is a getter/setter, it's possible there's one other\n        // getter/setter with the same key.\n        if (nextIndex !== -1) {\n          // Make sure this other member was a getter/setter\n          const nextMember = result[nextIndex];\n          assert(\n            ts.isGetAccessorDeclaration(nextMember) ||\n              ts.isSetAccessorDeclaration(nextMember),\n            `Expected getter/setter, got \"${printNode(nextMember)}\"`\n          );\n\n          // Remove the other getter/setter. Because `nextIndex > index`, we'll\n          // still be able to `splice(index)` later on.\n          assert(nextIndex > index);\n          result.splice(nextIndex, /* deleteCount */ 1);\n\n          // Make sure this was the only other member with this key\n          const nextNextIndex = findMemberIndex(result, key, nextIndex + 1);\n          assert(nextNextIndex === -1);\n        }\n      } else {\n        // Otherwise, make sure this was the only generated member with this key\n        assert(nextIndex === -1);\n      }\n\n      // Remove the member at that index and replace it with overrides\n      result.splice(index, /* deleteCount */ 1, ...transformedOverrideMembers);\n    }\n  }\n  return result;\n}\n\n// Converts class members to interface members where possible. Used as a\n// transformer when merging override members (which will always be class\n// members) into an interface.\nfunction classToTypeElement(\n  ctx: ts.TransformationContext,\n  member: ts.ClassElement\n): ts.TypeElement {\n  if (ts.isMethodDeclaration(member)) {\n    return ctx.factory.createMethodSignature(\n      ts.getModifiers(member),\n      member.name,\n      member.questionToken,\n      member.typeParameters,\n      member.parameters,\n      member.type\n    );\n  }\n  if (ts.isPropertyDeclaration(member)) {\n    return ctx.factory.createPropertySignature(\n      ts.getModifiers(member),\n      member.name,\n      member.questionToken,\n      member.type\n    );\n  }\n  if (\n    ts.isGetAccessorDeclaration(member) ||\n    ts.isSetAccessorDeclaration(member) ||\n    ts.isIndexSignatureDeclaration(member)\n  ) {\n    return member;\n  }\n  assert.fail(\n    `Expected interface-compatible member, got \"${printNode(member)}\".\nYou'll need to define a full-replacement override to a \"class\" if you wish to insert this member (i.e. \"JSG_TS_OVERRIDE(class MyClass { <all_members> })\").`\n  );\n}\n\n// Finds and applies the override (if any) for a node, returning the new\n// potentially overridden node\nfunction applyOverride<\n  Node extends ts.ClassDeclaration | ts.InterfaceDeclaration,\n>(\n  ctx: ts.TransformationContext,\n  overrideCtx: OverrideTransformContext,\n  node: Node,\n  updateDeclaration: (node: Node, override: ts.ClassDeclaration) => Node\n): ts.Node {\n  assert(node.name !== undefined);\n  const name = node.name.text;\n  const override = maybeGetOverride(overrideCtx.program, name);\n  const isReplacement = overrideCtx.replacements.has(name);\n\n  // Full-type replacement may rename type too, so record renames now\n  if (override !== undefined) {\n    // If override's name is different to the node's name, rename it later\n    const overrideIdentifier = maybeGetStatementName(override);\n    if (overrideIdentifier !== undefined) {\n      const overrideName = overrideIdentifier.text;\n      if (name !== overrideName) overrideCtx.renames.set(name, overrideName);\n    }\n  }\n\n  if (isReplacement) {\n    assert(override !== undefined);\n    return ensureStatementModifiers(ctx, override, {\n      declare: true,\n      export: false,\n    });\n  } else if (override !== undefined) {\n    // Merge override into declaration. Whilst we convert all non-replacement\n    // overrides to classes, this type classification is ignored when merging.\n    // Classes just support all possible forms of override. See `./compiler.ts`\n    // `compileOverride()` for details.\n    assert(ts.isClassDeclaration(override));\n    return updateDeclaration(node, override);\n  } else {\n    // No override, so return the node as is\n    return node;\n  }\n}\n\n// Apply all overrides, insert defines, and record type renames\nfunction createOverrideDefineVisitor(\n  ctx: ts.TransformationContext,\n  overrideCtx: OverrideTransformContext\n): ts.Visitor {\n  // Copies all string and numeric literals. Without this, garbage would be\n  // inserted in locations of literals instead.\n  // TODO(soon): work out why this happens, something to do with source ranges\n  //  and invalid source files/programs maybe?\n  const copyLiteralsVisitor: ts.Visitor<ts.Node, ts.Node> = (node) => {\n    node = ts.visitEachChild(node, copyLiteralsVisitor, ctx);\n    if (ts.isStringLiteral(node)) {\n      return ctx.factory.createStringLiteral(node.text);\n    }\n    if (ts.isNumericLiteral(node)) {\n      return ctx.factory.createNumericLiteral(node.text);\n    }\n    return node;\n  };\n\n  const visitor: ts.Visitor = (node) => {\n    // Visit classes and interfaces inside module declarations too\n    if (ts.isModuleDeclaration(node) || ts.isModuleBody(node)) {\n      return ts.visitEachChild(node, visitor, ctx);\n    }\n\n    let defines: ts.NodeArray<ts.Statement> | undefined;\n\n    if (ts.isClassDeclaration(node) && node.name !== undefined) {\n      defines = maybeGetDefines(overrideCtx.program, node.name.text);\n      node = applyOverride(ctx, overrideCtx, node, (node, override) => {\n        return ctx.factory.updateClassDeclaration(\n          node,\n          node.modifiers,\n          override.name,\n          override.typeParameters ?? node.typeParameters,\n          override.heritageClauses ?? node.heritageClauses,\n          mergeMembers(node.members, override.members, (member) => member)\n        );\n      });\n    } else if (ts.isInterfaceDeclaration(node)) {\n      defines = maybeGetDefines(overrideCtx.program, node.name.text);\n      node = applyOverride(ctx, overrideCtx, node, (node, override) => {\n        assert(override.name !== undefined);\n        return ctx.factory.updateInterfaceDeclaration(\n          node,\n          node.modifiers,\n          override.name,\n          override.typeParameters ?? node.typeParameters,\n          override.heritageClauses ?? node.heritageClauses,\n          mergeMembers(node.members, override.members, (member) =>\n            classToTypeElement(ctx, member)\n          )\n        );\n      });\n    }\n\n    // Process node and defines if defined\n    node = ts.visitNode(node, copyLiteralsVisitor);\n    defines = ts.visitNodes(defines, copyLiteralsVisitor, ts.isStatement);\n    defines = ts.visitNodes(\n      defines,\n      (node) =>\n        ensureStatementModifiers(ctx, node, { declare: true, export: false }),\n      ts.isStatement\n    );\n\n    if (ts.isTypeAliasDeclaration(node) && isUnsatisfiable(node.type)) {\n      // If node was overridden to `type T = never`, delete it, and just insert\n      // defines if any\n      return defines === undefined ? undefined : [...defines];\n    } else {\n      // Otherwise, return potentially overridden node, inserting defines if any\n      // before node\n      return defines == undefined ? node : [...defines, node];\n    }\n  };\n  return visitor;\n}\n\n// Apply previously-recorded type renames to all type references\nexport function createRenameVisitor(\n  ctx: ts.TransformationContext,\n  renames: Map</* from */ string, /* to */ string>,\n  renameClassesInterfaces = false\n): ts.Visitor {\n  const visitor: ts.Visitor = (node) => {\n    // Recursively visit all nodes\n    node = ts.visitEachChild(node, visitor, ctx);\n\n    // Rename all type references\n    if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName)) {\n      const rename = renames.get(node.typeName.text);\n      if (rename !== undefined) {\n        return ctx.factory.updateTypeReferenceNode(\n          node,\n          ctx.factory.createIdentifier(rename),\n          node.typeArguments\n        );\n      }\n    }\n\n    // Rename all type queries (e.g. nested types)\n    if (ts.isTypeQueryNode(node) && ts.isIdentifier(node.exprName)) {\n      const rename = renames.get(node.exprName.text);\n      if (rename !== undefined) {\n        return ctx.factory.updateTypeQueryNode(\n          node,\n          ctx.factory.createIdentifier(rename),\n          node.typeArguments\n        );\n      }\n    }\n\n    // Rename all expressions with type arguments (e.g. heritage clauses)\n    if (\n      ts.isExpressionWithTypeArguments(node) &&\n      ts.isIdentifier(node.expression)\n    ) {\n      const rename = renames.get(node.expression.text);\n      if (rename !== undefined) {\n        return ctx.factory.updateExpressionWithTypeArguments(\n          node,\n          ctx.factory.createIdentifier(rename),\n          node.typeArguments\n        );\n      }\n    }\n\n    // Rename all class and interface names\n    if (renameClassesInterfaces) {\n      if (\n        ts.isClassDeclaration(node) &&\n        node.name !== undefined &&\n        ts.isIdentifier(node.name)\n      ) {\n        const rename = renames.get(node.name.text);\n        if (rename !== undefined) {\n          return ctx.factory.updateClassDeclaration(\n            node,\n            node.modifiers,\n            ctx.factory.createIdentifier(rename),\n            node.typeParameters,\n            node.heritageClauses,\n            node.members\n          );\n        }\n      }\n      if (ts.isInterfaceDeclaration(node) && ts.isIdentifier(node.name)) {\n        const rename = renames.get(node.name.text);\n        if (rename !== undefined) {\n          return ctx.factory.updateInterfaceDeclaration(\n            node,\n            node.modifiers,\n            ctx.factory.createIdentifier(rename),\n            node.typeParameters,\n            node.heritageClauses,\n            node.members\n          );\n        }\n      }\n    }\n\n    return node;\n  };\n  return visitor;\n}\n\n// Returns a statement's identifier if it has one. This is used to get the\n// name to rename too (if any), hence we ignore variables and functions, since\n// replacing a reference to a type with a referencing to a variable/function is\n// an error.\nfunction maybeGetStatementName(node: ts.Statement): ts.Identifier | undefined {\n  if (\n    ts.isClassDeclaration(node) ||\n    ts.isInterfaceDeclaration(node) ||\n    ts.isEnumDeclaration(node) ||\n    ts.isTypeAliasDeclaration(node)\n  ) {\n    return node.name;\n  }\n}\n"
  },
  {
    "path": "types/src/utils.ts",
    "content": "// Copyright (c) 2026 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\n\n// When building types from the upstream repo all paths need to be prepended by\n// external/+dep_workerd+workerd/\nexport function getFilePath(f: string): string {\n  if (existsSync(\"external/+dep_workerd+workerd\")) {\n    return path.join(\"external\", \"+dep_workerd+workerd\", f);\n  } else {\n    return f;\n  }\n}\n"
  },
  {
    "path": "types/src/worker/index.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport { StructureGroups } from '@workerd/jsg/rtti';\nimport { Message } from 'capnp-es';\nimport extraDefinitions from 'raw:../../defines';\nimport comments from 'virtual:comments.json';\nimport paramNames from 'virtual:param-names.json';\nimport rtti from 'workerd:rtti';\nimport { installParameterNames } from '../generator/parameter-names';\nimport { printDefinitions } from '../index';\n\ninstallParameterNames(paramNames);\n/**\n * Worker for generating TypeScript types based on an arbitrary compatibility date\n * and optional compatibility flags. Accepts paths of the form  `/<compat-date>(+<compat_flag>)*`\n * (e.g. `/2024-01-01+nodejs_compat+no_global_navigator`)\n */\nexport default {\n  async fetch(request): Promise<Response> {\n    try {\n      // Ensure pathname matches expected pattern\n      let { pathname } = new URL(request.url);\n      if (\n        !/^\\/\\d{4}-\\d{2}-\\d{2}\\+?/.test(pathname) &&\n        !pathname.startsWith('/experimental')\n      ) {\n        return new Response('Not Found', { status: 404 });\n      }\n\n      // Extract response format\n      let format: 'ambient' | 'importable' | 'bundle' = 'ambient';\n      if (pathname.endsWith('.d.ts')) {\n        pathname = pathname.slice(0, -'.d.ts'.length);\n      } else if (pathname.endsWith('.ts')) {\n        pathname = pathname.slice(0, -'.ts'.length);\n        format = 'importable';\n      } else if (pathname.endsWith('.bundle')) {\n        pathname = pathname.slice(0, -'.bundle'.length);\n        format = 'bundle';\n      }\n\n      // Export RTTI for specified compatibility settings\n      const [compatDate, ...compatFlags] = pathname.substring(1).split('+');\n      let rttiCapnpBuffer: ArrayBuffer;\n      if (compatDate === 'experimental') {\n        rttiCapnpBuffer = rtti.exportExperimentalTypes();\n      } else {\n        rttiCapnpBuffer = rtti.exportTypes(compatDate, compatFlags);\n      }\n\n      const message = new Message(rttiCapnpBuffer, /* packed */ false);\n      const root = message.getRoot(StructureGroups);\n\n      // Generate and return types in correct format\n      const { ambient, importable } = printDefinitions(\n        root,\n        comments,\n        extraDefinitions\n      );\n\n      if (format === 'ambient') return new Response(ambient);\n\n      if (format === 'importable') return new Response(importable);\n\n      const bundle = new FormData();\n      bundle.set('index.d.ts', ambient);\n      bundle.set('index.ts', importable);\n      return new Response(bundle);\n    } catch (e) {\n      return new Response(e.stack, { status: 500 });\n    }\n  },\n} satisfies ExportedHandler;\n"
  },
  {
    "path": "types/src/worker/raw.d.ts",
    "content": "declare module \"raw:*\" {\n  const text: string;\n  export { text as default };\n}\n"
  },
  {
    "path": "types/src/worker/rtti.d.ts",
    "content": "declare module \"workerd:rtti\" {\n  const api: {\n    exportExperimentalTypes(): ArrayBuffer;\n    exportTypes(compatDate: string, compatFlags: string[]): ArrayBuffer;\n  };\n  export { api as default };\n}\n"
  },
  {
    "path": "types/src/worker/virtual.d.ts",
    "content": "declare module \"virtual:comments.json\" {\n  const data: import(\"../transforms\").CommentsData;\n  export { data as default };\n}\n\ndeclare module \"virtual:param-names.json\" {\n  const data: import(\"../generator/parameter-names\").ParameterNamesData;\n  export { data as default };\n}\n"
  },
  {
    "path": "types/test/generator/index.spec.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'assert';\nimport { test } from 'node:test';\nimport {\n  Member_Nested,\n  Structure,\n  StructureGroups,\n  StructureGroups_StructureGroup,\n  Type,\n} from '@workerd/jsg/rtti';\nimport { Message } from 'capnp-es';\nimport { generateDefinitions } from '../../src/generator';\nimport { printNodeList } from '../../src/print';\n\n// Initializes a structure group containing `targets` targets to reference.\n// Returns a function to point a type at an identified target.\nfunction initAsReferencableTypesGroup(\n  group: StructureGroups_StructureGroup,\n  targets: number\n): (id: number, type: Type | Member_Nested) => void {\n  group.name = 'referenced';\n  const structures = group._initStructures(targets);\n  for (let i = 0; i < targets; i++) {\n    const structure = structures.get(i);\n    structure.name = `Thing${i}`;\n    structure.fullyQualifiedName = `workerd::api::Thing${i}`;\n  }\n  return function initAsReferencedType(id, type) {\n    assert(0 <= id && id < targets);\n    const structure = type._initStructure();\n    structure.name = `Thing${id}`;\n    structure.fullyQualifiedName = `workerd::api::Thing${id}`;\n  };\n}\n\ntest('generateDefinitions: only includes referenced types from roots', () => {\n  const root = new Message().initRoot(StructureGroups);\n  const groups = root._initGroups(2);\n\n  // Generate group containing definitions for referencing by next group\n  const initAsReferencedType = initAsReferencableTypesGroup(groups.get(0), 24);\n\n  // Generate group containing definitions with each possible type of visitable\n  // type\n  const group = groups.get(1);\n  group.name = 'definitions';\n  const structures = group._initStructures(4);\n\n  const root1 = structures.get(0);\n  root1.name = 'Root1';\n  root1.fullyQualifiedName = 'workerd::api::Root1';\n  root1.tsRoot = true;\n  {\n    const members = root1._initMembers(7);\n\n    let prop = members.get(0)._initProperty();\n    prop.name = 'promise';\n    initAsReferencedType(0, prop._initType()._initPromise()._initValue());\n\n    prop = members.get(1)._initProperty();\n    prop.name = 'structure';\n    initAsReferencedType(1, prop._initType());\n\n    prop = members.get(2)._initProperty();\n    prop.name = 'array';\n    const array = prop._initType()._initArray();\n    array.name = 'kj::Array';\n    initAsReferencedType(2, array._initElement());\n\n    prop = members.get(3)._initProperty();\n    prop.name = 'maybe';\n    const maybe = prop._initType()._initMaybe();\n    maybe.name = 'jsg::Optional';\n    initAsReferencedType(3, maybe._initValue());\n\n    prop = members.get(4)._initProperty();\n    prop.name = 'dict';\n    const dict = prop._initType()._initDict();\n    initAsReferencedType(4, dict._initKey());\n    initAsReferencedType(5, dict._initValue());\n\n    prop = members.get(5)._initProperty();\n    prop.name = 'variants';\n    const variants = prop._initType()._initOneOf()._initVariants(3);\n    initAsReferencedType(6, variants.get(0));\n    initAsReferencedType(7, variants.get(1));\n    initAsReferencedType(8, variants.get(2));\n\n    prop = members.get(6)._initProperty();\n    prop.name = 'function';\n    const func = prop._initType()._initFunction();\n    initAsReferencedType(9, func._initArgs(1).get(0));\n    initAsReferencedType(10, func._initReturnType());\n  }\n\n  const nested = structures.get(1);\n  function initAsNestedStructure(structure: Structure): void {\n    structure.name = 'Nested';\n    structure.fullyQualifiedName = 'workerd::api::Nested';\n    const members = structure._initMembers(1);\n    const prop = members.get(0)._initProperty();\n    prop.name = 'nestedProp';\n    initAsReferencedType(11, prop._initType());\n  }\n  initAsNestedStructure(nested);\n\n  const root2 = structures.get(2);\n  root2.name = 'Root2';\n  root2.fullyQualifiedName = 'workerd::api::Root2';\n  root2.tsRoot = true;\n  {\n    const members = root2._initMembers(3);\n\n    const method = members.get(0)._initMethod();\n    method.name = 'method';\n    initAsReferencedType(12, method._initArgs(1).get(0));\n    initAsReferencedType(13, method._initReturnType());\n\n    const nested = members.get(1)._initNested();\n    nested.name = 'Nested';\n    initAsNestedStructure(nested._initStructure());\n\n    const constructor = members.get(2)._initConstructor();\n    initAsReferencedType(14, constructor._initArgs(1).get(0));\n  }\n  const iterator = root2._initIterator();\n  initAsReferencedType(15, iterator._initArgs(1).get(0));\n  initAsReferencedType(16, iterator._initReturnType());\n  const asyncIterator = root2._initAsyncIterator();\n  initAsReferencedType(17, asyncIterator._initArgs(1).get(0));\n  initAsReferencedType(18, asyncIterator._initReturnType());\n  initAsReferencedType(19, root2._initExtends());\n\n  // Types referenced by non-roots shouldn't be included\n  const nonRoot = structures.get(3);\n  nonRoot.name = 'NonRoot';\n  nonRoot.fullyQualifiedName = 'workerd::api::NonRoot';\n  const members = nonRoot._initMembers(1);\n  const prop = members.get(0)._initProperty();\n  prop.name = 'nonRootProp';\n  initAsReferencedType(20, prop._initType());\n\n  const referencedInterfaces = Array.from(Array(19))\n    .map((_, i) => `interface Thing${i} {\\n}`)\n    .join('\\n');\n  const { nodes } = generateDefinitions(root);\n  assert.strictEqual(\n    printNodeList(nodes),\n    `${referencedInterfaces}\ndeclare abstract class Thing19 {\n}\ninterface Root1 {\n    promise: Promise<Thing0>;\n    structure: Thing1;\n    array: Thing2[];\n    maybe?: Thing3;\n    dict: Record<Thing4, Thing5>;\n    variants: Thing6 | Thing7 | Thing8;\n    function: (param0: Thing9) => Thing10;\n}\ndeclare abstract class Nested {\n    nestedProp: Thing11;\n}\ndeclare class Root2 extends Thing19 {\n    constructor(param0: Thing14);\n    method(param0: Thing12): Thing13;\n    Nested: typeof Nested;\n    [Symbol.iterator](param0: Thing15): Thing16;\n    [Symbol.asyncIterator](param0: Thing17): Thing18;\n}\n`\n  );\n});\n\ntest('generateDefinitions: only generates classes if required', () => {\n  const root = new Message().initRoot(StructureGroups);\n  const groups = root._initGroups(2);\n\n  // Generate group containing definitions for referencing by next group\n  const initAsReferencedType = initAsReferencableTypesGroup(groups.get(0), 2);\n\n  // Generate group containing definitions with each possible class requirement\n  const group = groups.get(1);\n  group.name = 'definitions';\n  const structures = group._initStructures(4);\n\n  const root1 = structures.get(0);\n  root1.name = 'Root1';\n  root1.fullyQualifiedName = 'workerd::api::Root1';\n  root1.tsRoot = true;\n  // Thing0 should be a class as it's a nested type\n  {\n    const members = root1._initMembers(1);\n    const nested = members.get(0)._initNested();\n    nested.name = 'Thing0';\n    initAsReferencedType(0, nested);\n  }\n\n  const root2 = structures.get(1);\n  root2.name = 'Root2';\n  root2.fullyQualifiedName = 'workerd::api::Root2';\n  root2.tsRoot = true;\n  {\n    const members = root2._initMembers(1);\n    // ExportedHandler should be a class as it's constructible\n    members.get(0)._initConstructor();\n  }\n\n  const root3 = structures.get(2);\n  root3.name = 'Root3';\n  root3.fullyQualifiedName = 'workerd::api::Root3';\n  root3.tsRoot = true;\n  {\n    const members = root3._initMembers(1);\n    const method = members.get(0)._initMethod();\n    method.name = 'method';\n    // DurableObjectNamespace should be a class as it contains static methods\n    method.static = true;\n    method._initReturnType().voidt = true;\n  }\n\n  const root4 = structures.get(3);\n  root4.name = 'Root4';\n  root4.fullyQualifiedName = 'workerd::api::Root4';\n  root4.tsRoot = true;\n  // Thing1 should be a class as its inherited\n  initAsReferencedType(1, root4._initExtends());\n\n  const { nodes } = generateDefinitions(root);\n  assert.strictEqual(\n    printNodeList(nodes),\n    `declare abstract class Thing0 {\n}\ndeclare abstract class Thing1 {\n}\ninterface Root1 {\n    Thing0: typeof Thing0;\n}\ndeclare class Root2 {\n    constructor();\n}\ndeclare abstract class Root3 {\n    static method(): void;\n}\ninterface Root4 extends Thing1 {\n}\n`\n  );\n});\n"
  },
  {
    "path": "types/test/generator/structure.spec.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'assert';\nimport { test } from 'node:test';\nimport { JsgImplType_Type, Structure } from '@workerd/jsg/rtti';\nimport { Message } from 'capnp-es';\nimport { createStructureNode } from '../../src/generator/structure';\nimport { printNode } from '../../src/print';\n\ntest('createStructureNode: method members', () => {\n  const structure = new Message().initRoot(Structure);\n  structure.name = 'Methods';\n  structure.fullyQualifiedName = 'workerd::api::Methods';\n\n  const members = structure._initMembers(3);\n\n  let method = members.get(0)._initMethod();\n  method.name = 'one';\n  {\n    const args = method._initArgs(3);\n    args.get(0).boolt = true;\n    args.get(1)._initPromise()._initValue().voidt = true;\n    const maybe = args.get(2)._initMaybe();\n    maybe.name = 'jsg::Optional';\n    maybe._initValue()._initNumber().name = 'int';\n  }\n  method._initReturnType().voidt = true;\n\n  method = members.get(1)._initMethod();\n  method.name = 'two';\n  method._initReturnType()._initJsgImpl().type =\n    JsgImplType_Type.JSG_UNIMPLEMENTED;\n\n  method = members.get(2)._initMethod();\n  method.name = 'three';\n  {\n    const args = method._initArgs(1);\n    args.get(0)._initJsgImpl().type = JsgImplType_Type.JSG_VARARGS;\n  }\n  method._initReturnType().boolt = true;\n\n  // Note method with unimplemented return is omitted\n  assert.strictEqual(\n    printNode(createStructureNode(structure, { asClass: false })),\n    `interface Methods {\n    one(param0: boolean, param1: Promise<any>, param2?: number): void;\n    three(...param0: any[]): boolean;\n}`\n  );\n\n  method.static = true;\n  assert.strictEqual(\n    printNode(createStructureNode(structure, { asClass: true })),\n    `declare abstract class Methods {\n    one(param0: boolean, param1: Promise<any>, param2?: number): void;\n    static three(...param0: any[]): boolean;\n}`\n  );\n});\n\ntest('createStructureNode: property members', () => {\n  const structure = new Message().initRoot(Structure);\n  structure.name = 'Properties';\n  structure.fullyQualifiedName = 'workerd::api::Properties';\n\n  const members = structure._initMembers(8);\n\n  let prop = members.get(0)._initProperty();\n  prop.name = 'one';\n  prop._initType().boolt = true;\n\n  prop = members.get(1)._initProperty();\n  prop.name = 'two';\n  prop._initType()._initNumber().name = 'int';\n  prop.readonly = true;\n\n  prop = members.get(2)._initProperty();\n  prop.name = 'three';\n  {\n    const maybe = prop._initType()._initMaybe();\n    maybe.name = 'jsg::Optional';\n    maybe._initValue().boolt = true;\n  }\n  prop.readonly = true;\n\n  prop = members.get(3)._initProperty();\n  prop.name = 'four';\n  prop._initType()._initJsgImpl().type = JsgImplType_Type.JSG_UNIMPLEMENTED;\n  prop.readonly = true;\n\n  prop = members.get(4)._initProperty();\n  prop.name = 'five';\n  prop._initType().boolt = true;\n  prop.prototype = true;\n\n  prop = members.get(5)._initProperty();\n  prop.name = 'six';\n  prop._initType()._initString().name = 'kj::String';\n  prop.readonly = true;\n  prop.prototype = true;\n\n  prop = members.get(6)._initProperty();\n  prop.name = 'seven';\n  {\n    const maybe = prop._initType()._initMaybe();\n    maybe.name = 'jsg::Optional';\n    maybe._initValue()._initNumber().name = 'short';\n  }\n  prop.readonly = true;\n  prop.prototype = true;\n\n  prop = members.get(7)._initProperty();\n  prop.name = 'eight';\n  prop._initType()._initJsgImpl().type = JsgImplType_Type.JSG_UNIMPLEMENTED;\n  prop.readonly = true;\n  prop.prototype = true;\n\n  // Note unimplemented properties omitted\n  assert.strictEqual(\n    printNode(createStructureNode(structure, { asClass: false })),\n    `interface Properties {\n    one: boolean;\n    readonly two: number;\n    readonly three?: boolean;\n    get five(): boolean;\n    set five(value: boolean);\n    get six(): string;\n    get seven(): number | undefined;\n}`\n  );\n  assert.strictEqual(\n    printNode(createStructureNode(structure, { asClass: true })),\n    `declare abstract class Properties {\n    one: boolean;\n    readonly two: number;\n    readonly three?: boolean;\n    get five(): boolean;\n    set five(value: boolean);\n    get six(): string;\n    get seven(): number | undefined;\n}`\n  );\n});\n\ntest('createStructureNode: nested type members', () => {\n  const structure = new Message().initRoot(Structure);\n  structure.name = 'Nested';\n  structure.fullyQualifiedName = 'workerd::api::Nested';\n\n  const members = structure._initMembers(2);\n\n  let nested = members.get(0)._initNested();\n  let nestedStructure = nested._initStructure();\n  nestedStructure.name = 'Thing';\n  nestedStructure.fullyQualifiedName = 'workerd::api::Thing';\n\n  nested = members.get(1)._initNested();\n  nestedStructure = nested._initStructure();\n  nestedStructure.name = 'OtherThing';\n  nestedStructure.fullyQualifiedName = 'workerd::api::OtherThing';\n  nested.name = 'RenamedThing';\n\n  assert.strictEqual(\n    printNode(createStructureNode(structure, { asClass: false })),\n    `interface Nested {\n    Thing: typeof Thing;\n    RenamedThing: typeof OtherThing;\n}`\n  );\n  assert.strictEqual(\n    printNode(createStructureNode(structure, { asClass: true })),\n    `declare abstract class Nested {\n    Thing: typeof Thing;\n    RenamedThing: typeof OtherThing;\n}`\n  );\n});\n\ntest('createStructureNode: constant members', () => {\n  const structure = new Message().initRoot(Structure);\n  structure.name = 'Constants';\n  structure.fullyQualifiedName = 'workerd::api::Constants';\n\n  const members = structure._initMembers(1);\n\n  const constant = members.get(0)._initConstant();\n  constant.name = 'THING';\n  constant.value = BigInt(42);\n\n  assert.strictEqual(\n    printNode(createStructureNode(structure, { asClass: true })),\n    `declare abstract class Constants {\n    static readonly THING: number;\n}`\n  );\n});\n\ntest('createStructureNode: iterator members', () => {\n  const structure = new Message().initRoot(Structure);\n  structure.name = 'Iterators';\n  structure.fullyQualifiedName = 'workerd::api::Iterators';\n\n  structure.iterable = true;\n  const iterator = structure._initIterator();\n  {\n    const args = iterator._initArgs(1);\n    const maybe = args.get(0)._initMaybe();\n    maybe.name = 'jsg::Optional';\n    const optionsStructure = maybe._initValue()._initStructure();\n    optionsStructure.name = 'ThingOptions';\n    optionsStructure.fullyQualifiedName = 'workerd::api::ThingOptions';\n  }\n  let returnStructure = iterator._initReturnType()._initStructure();\n  returnStructure.name = 'ThingIterator';\n  returnStructure.fullyQualifiedName = 'workerd::api::ThingIterator';\n\n  structure.asyncIterable = true;\n  const asyncIterator = structure._initAsyncIterator();\n  {\n    const args = asyncIterator._initArgs(1);\n    const maybe = args.get(0)._initMaybe();\n    maybe.name = 'jsg::Optional';\n    const optionsStructure = maybe._initValue()._initStructure();\n    optionsStructure.name = 'AsyncThingOptions';\n    optionsStructure.fullyQualifiedName = 'workerd::api::AsyncThingOptions';\n  }\n  returnStructure = asyncIterator._initReturnType()._initStructure();\n  returnStructure.name = 'AsyncThingIterator';\n  returnStructure.fullyQualifiedName = 'workerd::api::AsyncThingIterator';\n\n  assert.strictEqual(\n    printNode(createStructureNode(structure, { asClass: false })),\n    `interface Iterators {\n    [Symbol.iterator](param0?: ThingOptions): ThingIterator;\n    [Symbol.asyncIterator](param0?: AsyncThingOptions): AsyncThingIterator;\n}`\n  );\n  assert.strictEqual(\n    printNode(createStructureNode(structure, { asClass: true })),\n    `declare abstract class Iterators {\n    [Symbol.iterator](param0?: ThingOptions): ThingIterator;\n    [Symbol.asyncIterator](param0?: AsyncThingOptions): AsyncThingIterator;\n}`\n  );\n});\n\ntest('createStructureNode: constructors', () => {\n  const structure = new Message().initRoot(Structure);\n  structure.name = 'Constructor';\n  structure.fullyQualifiedName = 'workerd::api::Constructor';\n\n  const members = structure._initMembers(1);\n\n  const constructor = members.get(0)._initConstructor();\n  {\n    const args = constructor._initArgs(4);\n    let maybe = args.get(0)._initMaybe();\n    maybe.name = 'jsg::Optional';\n    maybe._initValue().boolt = true;\n    args.get(1)._initString().name = 'kj::String';\n    args.get(2)._initPromise()._initValue().voidt = true;\n    maybe = args.get(3)._initMaybe();\n    maybe.name = 'jsg::Optional';\n    maybe._initValue()._initNumber().name = 'int';\n  }\n\n  assert.strictEqual(\n    printNode(createStructureNode(structure, { asClass: true })),\n    `declare class Constructor {\n    constructor(param0: boolean | undefined, param1: string, param2: Promise<any>, param3?: number);\n}`\n  );\n});\n\ntest('createStructureNode: extends', () => {\n  const structure = new Message().initRoot(Structure);\n  structure.name = 'Extends';\n  structure.fullyQualifiedName = 'workerd::api::Extends';\n\n  const extendsStructure = structure._initExtends()._initStructure();\n  extendsStructure.name = 'Base';\n  extendsStructure.fullyQualifiedName = 'workerd::api::Base';\n\n  assert.strictEqual(\n    printNode(createStructureNode(structure, { asClass: true })),\n    `declare abstract class Extends extends Base {\n}`\n  );\n});\n"
  },
  {
    "path": "types/test/generator/type.spec.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'assert';\nimport { test } from 'node:test';\nimport { BuiltinType_Type, JsgImplType_Type, Type } from '@workerd/jsg/rtti';\nimport { Message } from 'capnp-es';\nimport { createTypeNode } from '../../src/generator/type';\nimport { printNode } from '../../src/print';\n\ntest('createTypeNode: primitive types', () => {\n  const type = new Message().initRoot(Type);\n\n  type.unknown = true;\n  assert.strictEqual(printNode(createTypeNode(type)), 'any');\n  type.object = true;\n  assert.strictEqual(printNode(createTypeNode(type)), 'any');\n\n  type.voidt = true;\n  assert.strictEqual(printNode(createTypeNode(type)), 'void');\n\n  type.boolt = true;\n  assert.strictEqual(printNode(createTypeNode(type)), 'boolean');\n\n  type._initNumber().name = 'int';\n  assert.strictEqual(printNode(createTypeNode(type)), 'number');\n  type.number.name = 'long';\n  assert.strictEqual(printNode(createTypeNode(type)), 'number | bigint');\n\n  type._initString().name = 'kj::String';\n  assert.strictEqual(printNode(createTypeNode(type)), 'string');\n\n  const structure = type._initStructure();\n  structure.name = 'KvNamespace';\n  structure.fullyQualifiedName = 'workerd::api::KvNamespace';\n  assert.strictEqual(printNode(createTypeNode(type)), 'KvNamespace');\n});\n\ntest('createTypeNode: builtin types', () => {\n  const type = new Message().initRoot(Type);\n  const builtin = type._initBuiltin();\n\n  builtin.type = BuiltinType_Type.V8UINT8ARRAY;\n  assert.strictEqual(printNode(createTypeNode(type)), 'Uint8Array');\n\n  builtin.type = BuiltinType_Type.V8ARRAY_BUFFER_VIEW;\n  assert.strictEqual(printNode(createTypeNode(type)), 'ArrayBufferView');\n\n  builtin.type = BuiltinType_Type.JSG_BUFFER_SOURCE;\n  assert.strictEqual(\n    printNode(createTypeNode(type)),\n    'ArrayBuffer | ArrayBufferView'\n  );\n\n  builtin.type = BuiltinType_Type.KJ_DATE;\n  assert.strictEqual(printNode(createTypeNode(type)), 'Date');\n\n  builtin.type = BuiltinType_Type.V8FUNCTION;\n  assert.strictEqual(printNode(createTypeNode(type)), 'Function');\n\n  const intrinsic = type._initIntrinsic();\n\n  intrinsic.name = 'v8::kErrorPrototype';\n  assert.strictEqual(printNode(createTypeNode(type)), 'Error');\n\n  intrinsic.name = 'v8::kIteratorPrototype';\n  assert.strictEqual(printNode(createTypeNode(type)), 'Iterator<unknown>');\n\n  intrinsic.name = 'v8::kAsyncIteratorPrototype';\n  assert.strictEqual(printNode(createTypeNode(type)), 'AsyncIterator<unknown>');\n});\n\ntest('createTypeNode: generic types', () => {\n  const type = new Message().initRoot(Type);\n\n  type._initPromise()._initValue().voidt = true;\n  assert.strictEqual(printNode(createTypeNode(type)), 'Promise<void>');\n  type._initPromise()._initValue().voidt = true;\n  assert.strictEqual(\n    printNode(createTypeNode(type, true)),\n    'void | Promise<void>'\n  );\n  assert.strictEqual(\n    printNode(createTypeNode(type, true, true)),\n    'Promise<any>'\n  );\n\n  const maybe = type._initMaybe();\n  maybe._initValue().boolt = true;\n  maybe.name = 'jsg::Optional';\n  assert.strictEqual(printNode(createTypeNode(type)), 'boolean | undefined');\n  maybe.name = 'jsg::LenientOptional';\n  assert.strictEqual(printNode(createTypeNode(type)), 'boolean | undefined');\n  maybe.name = 'kj::Maybe';\n  assert.strictEqual(printNode(createTypeNode(type)), 'boolean | null');\n\n  const dict = type._initDict();\n  dict._initKey()._initString().name = 'kj::StringPtr';\n  dict._initValue()._initNumber().name = 'short';\n  assert.strictEqual(printNode(createTypeNode(type)), 'Record<string, number>');\n\n  const variants = type._initOneOf()._initVariants(3);\n  variants.get(0).voidt = true;\n  variants.get(1)._initNumber().name = 'unsigned short';\n  variants.get(2)._initString().name = 'kj::String';\n  assert.strictEqual(printNode(createTypeNode(type)), 'void | number | string');\n});\n\ntest('createTypeNode: array types', () => {\n  const type = new Message().initRoot(Type);\n  const array = type._initArray();\n\n  // Regular array\n  array.name = 'kj::Array';\n  array._initElement()._initString().name = 'kj::String';\n  assert.strictEqual(printNode(createTypeNode(type)), 'string[]');\n  assert.strictEqual(printNode(createTypeNode(type, true)), 'string[]');\n  // Iterable\n  array.name = 'jsg::Sequence';\n  array._initElement()._initString().name = 'kj::String';\n  assert.strictEqual(printNode(createTypeNode(type)), 'string[]');\n  assert.strictEqual(printNode(createTypeNode(type, true)), 'Iterable<string>');\n\n  // Numeric arrays\n  array.name = 'kj::Array';\n  array._initElement()._initNumber().name = 'int';\n  assert.strictEqual(printNode(createTypeNode(type)), 'number[]');\n  // If element is a char, then this is a string\n  array.name = 'kj::ArrayPtr';\n  array._initElement()._initNumber().name = 'char';\n  assert.strictEqual(printNode(createTypeNode(type)), 'string');\n  // If element is a byte, then this is an ArrayBuffer, ArrayBufferView or both\n  array.name = 'kj::Array';\n  array._initElement()._initNumber().name = 'unsigned char';\n  assert.strictEqual(printNode(createTypeNode(type)), 'ArrayBuffer');\n  assert.strictEqual(\n    printNode(createTypeNode(type, true)),\n    'ArrayBuffer | ArrayBufferView'\n  );\n  array.name = 'kj::ArrayPtr';\n  array._initElement()._initNumber().name = 'unsigned char';\n  assert.strictEqual(printNode(createTypeNode(type)), 'ArrayBufferView');\n  assert.strictEqual(\n    printNode(createTypeNode(type, true)),\n    'ArrayBuffer | ArrayBufferView'\n  );\n});\n\ntest('createTypeNode: function types', () => {\n  const message = new Message();\n\n  // (a: boolean, b: number | undefined, d: string, c?: any) => void\n  let type = message.initRoot(Type);\n  let func = type._initFunction();\n  let args = func._initArgs(5);\n  args.get(0).boolt = true;\n  args.get(1)._initMaybe()._initValue()._initNumber().name = 'int';\n  args.get(2)._initString().name = 'kj::String';\n  args.get(3)._initMaybe()._initValue().object = true;\n  args.get(4)._initJsgImpl().type = JsgImplType_Type.V8ISOLATE;\n  func._initReturnType().voidt = true;\n  let typeNode = createTypeNode(type);\n  assert.strictEqual(\n    printNode(typeNode),\n    '(param0: boolean, param1: number | undefined, param2: string, param3?: any) => void'\n  );\n\n  // (a?: string, ...b: any[]) => Promise<void>\n  type = message.initRoot(Type);\n  func = type._initFunction();\n  args = func._initArgs(3);\n  args.get(0)._initJsgImpl().type = JsgImplType_Type.JSG_TYPE_HANDLER;\n  args.get(1)._initMaybe()._initValue()._initString().name = 'kj::String';\n  args.get(2)._initJsgImpl().type = JsgImplType_Type.JSG_VARARGS;\n  func._initReturnType()._initPromise()._initValue().voidt = true;\n  typeNode = createTypeNode(type);\n  assert.strictEqual(\n    printNode(typeNode),\n    '(param1?: string, ...param2: any[]) => void | Promise<void>'\n  );\n});\n\ntest('createTypeNode: implementation types', () => {\n  const type = new Message().initRoot(Type);\n  const impl = type._initJsgImpl();\n\n  const implTypes: JsgImplType_Type[] = Object.values(type).filter(\n    (member) => typeof member === 'number'\n  ) as JsgImplType_Type[];\n  for (const implType of implTypes) {\n    // VARARGS and NAME are the only types we care about which will be tested\n    // with function types, the rest should be ignored\n    if (\n      implType === JsgImplType_Type.JSG_VARARGS ||\n      implType === JsgImplType_Type.JSG_NAME\n    ) {\n      continue;\n    }\n    impl.type = implType;\n    assert.strictEqual(printNode(createTypeNode(type)), 'never');\n  }\n\n  impl.type = JsgImplType_Type.JSG_NAME;\n  assert.strictEqual(printNode(createTypeNode(type)), 'PropertyKey');\n});\n"
  },
  {
    "path": "types/test/index.spec.ts",
    "content": "import assert from 'assert';\nimport fs from 'fs/promises';\nimport { test } from 'node:test';\nimport path from 'path';\nimport { BuiltinType_Type, StructureGroups } from '@workerd/jsg/rtti';\nimport { Message } from 'capnp-es';\nimport { printDefinitions } from '../src';\nimport { readComments } from '../scripts/build-worker';\n\ntest('main: generates types', async () => {\n  const message = new Message();\n  const root = message.initRoot(StructureGroups);\n  const groups = root._initGroups(1);\n  const group = groups.get(0);\n  group.name = 'definitions';\n  const structures = group._initStructures(5);\n\n  const eventTarget = structures.get(0);\n  eventTarget.name = 'EventTarget';\n  eventTarget.fullyQualifiedName = 'workerd::api::EventTarget';\n  {\n    const members = eventTarget._initMembers(2);\n    members.get(0)._initConstructor();\n    const method = members.get(1)._initMethod();\n    method.name = 'addEventListener';\n    {\n      const args = method._initArgs(2);\n      args.get(0)._initString().name = 'kj::String';\n      args.get(1)._initBuiltin().type = BuiltinType_Type.V8FUNCTION;\n    }\n    method._initReturnType().voidt = true;\n  }\n  eventTarget.tsDefine = 'interface Event {}';\n  eventTarget.tsOverride = `<EventMap extends Record<string, Event> = Record<string, Event>> {\n    addEventListener<Type extends keyof EventMap>(type: Type, handler: (event: EventMap[Type]) => void): void;\n  }`;\n\n  const workerGlobalScope = structures.get(1);\n  workerGlobalScope.name = 'WorkerGlobalScope';\n  workerGlobalScope.fullyQualifiedName = 'workerd::api::WorkerGlobalScope';\n  let extendsStructure = workerGlobalScope._initExtends()._initStructure();\n  extendsStructure.name = 'EventTarget';\n  extendsStructure.fullyQualifiedName = 'workerd::api::EventTarget';\n  workerGlobalScope.tsDefine = `type WorkerGlobalScopeEventMap = {\n    fetch: Event;\n    scheduled: Event;\n  }`;\n  workerGlobalScope.tsOverride =\n    'extends EventTarget<WorkerGlobalScopeEventMap>';\n\n  const serviceWorkerGlobalScope = structures.get(2);\n  serviceWorkerGlobalScope.name = 'ServiceWorkerGlobalScope';\n  serviceWorkerGlobalScope.fullyQualifiedName =\n    'workerd::api::ServiceWorkerGlobalScope';\n  extendsStructure = serviceWorkerGlobalScope._initExtends()._initStructure();\n  extendsStructure.name = 'WorkerGlobalScope';\n  extendsStructure.fullyQualifiedName = 'workerd::api::WorkerGlobalScope';\n  serviceWorkerGlobalScope.tsRoot = true;\n  {\n    const members = serviceWorkerGlobalScope._initMembers(2);\n\n    // Test that global extraction is performed after iterator processing\n    const method = members.get(0)._initMethod();\n    method.name = 'things';\n    const methodArgs = method._initArgs(1);\n    methodArgs.get(0).boolt = true;\n    const methodReturn = method._initReturnType()._initStructure();\n    methodReturn.name = 'ThingIterator';\n    methodReturn.fullyQualifiedName = 'workerd::api::ThingIterator';\n\n    const prop = members.get(1)._initProperty();\n    prop.name = 'prop';\n    prop.readonly = true;\n    prop.prototype = true;\n    prop._initType()._initPromise()._initValue()._initNumber().name = 'int';\n  }\n\n  const iterator = structures.get(3);\n  iterator.name = 'ThingIterator';\n  iterator.fullyQualifiedName = 'workerd::api::ThingIterator';\n  iterator._initExtends()._initIntrinsic().name = 'v8::kIteratorPrototype';\n  iterator.iterable = true;\n  {\n    const members = iterator._initMembers(1);\n    const nextMethod = members.get(0)._initMethod();\n    nextMethod.name = 'next';\n    const nextStruct = nextMethod._initReturnType()._initStructure();\n    nextStruct.name = 'ThingIteratorNext';\n    nextStruct.fullyQualifiedName = 'workerd::api::ThingIteratorNext';\n    const iteratorMethod = iterator._initIterator();\n    iteratorMethod._initReturnType().unknown = true;\n  }\n  const iteratorNext = structures.get(4);\n  iteratorNext.name = 'ThingIteratorNext';\n  iteratorNext.fullyQualifiedName = 'workerd::api::ThingIteratorNext';\n  {\n    const members = iteratorNext._initMembers(2);\n    const doneProp = members.get(0)._initProperty();\n    doneProp.name = 'done';\n    doneProp._initType().boolt = true;\n    const valueProp = members.get(1)._initProperty();\n    valueProp.name = 'value';\n    const valueType = valueProp._initType()._initMaybe();\n    valueType.name = 'jsg::Optional';\n    valueType._initValue()._initString().name = 'kj::String';\n  }\n\n  // https://bazel.build/reference/test-encyclopedia#initial-conditions\n  const tmpPath = process.env.TEST_TMPDIR;\n  assert(tmpPath !== undefined);\n  const definitionsDir = path.join(tmpPath, 'definitions');\n  await fs.mkdir(definitionsDir);\n  const inputDir = path.join(tmpPath, 'capnp');\n  await fs.mkdir(inputDir);\n  const inputPath = path.join(inputDir, 'types.api.capnp.bin');\n  const comments = readComments();\n\n  await fs.writeFile(inputPath, new Uint8Array(message.toArrayBuffer()));\n  const { ambient } = printDefinitions(\n    message.getRoot(StructureGroups),\n    comments,\n    ''\n  );\n\n  assert.strictEqual(\n    ambient,\n    `/*! *****************************************************************************\nCopyright (c) Cloudflare. All rights reserved.\nCopyright (c) Microsoft Corporation. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License. You may obtain a copy of the\nLicense at http://www.apache.org/licenses/LICENSE-2.0\nTHIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\nKIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED\nWARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,\nMERCHANTABLITY OR NON-INFRINGEMENT.\nSee the Apache Version 2.0 License for specific language governing permissions\nand limitations under the License.\n***************************************************************************** */\n/* eslint-disable */\n// noinspection JSUnusedGlobalSymbols\ndeclare var onmessage: never;\n/**\n * The **\\`Event\\`** interface represents an event which takes place on an \\`EventTarget\\`.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event)\n */\ninterface Event {\n}\n/**\n * The **\\`EventTarget\\`** interface is implemented by objects that can receive events and may have listeners for them.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget)\n */\ndeclare class EventTarget<EventMap extends Record<string, Event> = Record<string, Event>> {\n    constructor();\n    /**\n     * The **\\`addEventListener()\\`** method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.\n     *\n     * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)\n     */\n    addEventListener<Type extends keyof EventMap>(type: Type, handler: (event: EventMap[Type]) => void): void;\n}\ntype WorkerGlobalScopeEventMap = {\n    fetch: Event;\n    scheduled: Event;\n};\ndeclare abstract class WorkerGlobalScope extends EventTarget<WorkerGlobalScopeEventMap> {\n}\n/**\n * The **\\`ServiceWorkerGlobalScope\\`** interface of the Service Worker API represents the global execution context of a service worker.\n * Available only in secure contexts.\n *\n * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope)\n */\ninterface ServiceWorkerGlobalScope extends WorkerGlobalScope {\n    things(param0: boolean): IterableIterator<string>;\n    get prop(): Promise<number>;\n}\ndeclare function addEventListener<Type extends keyof WorkerGlobalScopeEventMap>(type: Type, handler: (event: WorkerGlobalScopeEventMap[Type]) => void): void;\ndeclare function things(param0: boolean): IterableIterator<string>;\ndeclare const prop: Promise<number>;\n`\n  );\n});\n"
  },
  {
    "path": "types/test/print.spec.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'assert';\nimport { test } from 'node:test';\nimport ts, { factory as f } from 'typescript';\nimport { printNode, printNodeList } from '../src/print';\n\ntest('printNode: prints type', () => {\n  const type = f.createTypeReferenceNode('Promise', [\n    f.createTypeReferenceNode('void'),\n  ]);\n  assert.strictEqual(printNode(type), 'Promise<void>');\n});\n\ntest('printNode: prints interface', () => {\n  const property = f.createPropertySignature(\n    [f.createToken(ts.SyntaxKind.ReadonlyKeyword)],\n    'thing',\n    f.createToken(ts.SyntaxKind.QuestionToken),\n    f.createTypeReferenceNode('T')\n  );\n\n  const typeParam = f.createTypeParameterDeclaration(\n    /* modifiers */ undefined,\n    'T',\n    f.createTypeReferenceNode('string')\n  );\n  const declaration = f.createInterfaceDeclaration(\n    [f.createToken(ts.SyntaxKind.ExportKeyword)],\n    'Test',\n    [typeParam],\n    /* heritageClauses */ undefined,\n    [property]\n  );\n\n  assert.strictEqual(\n    printNode(declaration),\n    `export interface Test<T extends string> {\n    readonly thing?: T;\n}`\n  );\n});\n\ntest('printNodeList: prints statements', () => {\n  const interfaceDeclaration = f.createInterfaceDeclaration(\n    /* modifiers */ undefined,\n    'Interface',\n    /* typeParams */ undefined,\n    /* heritageClauses */ undefined,\n    []\n  );\n  const classDeclaration = f.createClassDeclaration(\n    /* modifiers */ undefined,\n    'Class',\n    /* typeParams */ undefined,\n    /* heritageClauses */ undefined,\n    []\n  );\n  const printed = printNodeList([interfaceDeclaration, classDeclaration]);\n  assert.strictEqual(\n    printed,\n    `interface Interface {\n}\nclass Class {\n}\n`\n  );\n});\n"
  },
  {
    "path": "types/test/transforms/class-to-interface.spec.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from \"assert\";\nimport { test } from \"node:test\";\nimport path from \"path\";\nimport ts from \"typescript\";\nimport { printer } from \"../../src/print\";\nimport { createMemoryProgram } from \"../../src/program\";\nimport { createClassToInterfaceTransformer } from \"../../src/transforms/class-to-interface\";\n\ntest(\"createClassToInterfaceTransformer: transforms class to interface\", () => {\n  const source = `\n    class MyClass<T = void, U = void> {\n      constructor(str: string): MyClass;\n      prop: T;\n      method(): U {}\n      get accessor(): number { return 42; }\n      static staticMethod(str?: string): void {}\n      private privateMethod() {}\n    }\n  `;\n\n  const expectedOutput = `\n    declare var MyClass: {\n      prototype: MyClass;\n      new <T = void, U = void>(str: string): MyClass<T, U>;\n      staticMethod(str?: string): void;\n    };\n    interface MyClass<T = void, U = void> {\n      prop: T;\n      method(): U;\n      accessor: number;\n    }\n  `;\n\n  const sourcePath = path.resolve(__dirname, \"source.ts\");\n  const sources = new Map([[sourcePath, source]]);\n  const program = createMemoryProgram(sources);\n  const sourceFile = program.getSourceFile(sourcePath);\n  assert(sourceFile !== undefined);\n\n  const result = ts.transform(sourceFile, [\n    createClassToInterfaceTransformer([\"MyClass\"]),\n  ]);\n  assert.strictEqual(result.transformed.length, 1);\n\n  const output = printer.printFile(result.transformed[0]);\n  assert.strictEqual(\n    normalizeWhitespace(output.trim()),\n    normalizeWhitespace(expectedOutput.trim()),\n    \"The transformed output did not match the expected output\"\n  );\n});\n\nfunction normalizeWhitespace(str: string) {\n  return str.replace(/\\s+/g, \" \").trim();\n}\n"
  },
  {
    "path": "types/test/transforms/globals.spec.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from \"assert\";\nimport { test } from \"node:test\";\nimport path from \"path\";\nimport ts from \"typescript\";\nimport { printer } from \"../../src/print\";\nimport { createMemoryProgram } from \"../../src/program\";\nimport { createGlobalScopeTransformer } from \"../../src/transforms\";\n\ntest(\"createGlobalScopeTransformer: extracts global scope\", () => {\n  const source = `type WorkerGlobalScopeEventMap = {\n    fetch: Event;\n    scheduled: Event;\n};\ndeclare class EventTarget<EventMap extends Record<string, Event> = Record<string, Event>> {\n    constructor();\n    addEventListener<Type extends keyof EventMap>(type: Type, handler: (event: EventMap[Type]) => void): void; // MethodDeclaration\n    removeEventListener<Type extends keyof EventMap>(type: Type, handler: (event: EventMap[Type]) => void): void; // MethodDeclaration\n    dispatchEvent(event: EventMap[keyof EventMap]): void; // MethodDeclaration\n}\ndeclare class WorkerGlobalScope extends EventTarget<WorkerGlobalScopeEventMap> {\n    thing: string; // PropertyDeclaration\n    static readonly CONSTANT: 42; // PropertyDeclaration\n    get property(): number; // GetAccessorDeclaration\n    set property(value: number); // GetAccessorDeclaration\n}\ndeclare class DOMException {\n}\ndeclare abstract class Crypto {\n}\ndeclare abstract class Console {\n}\ninterface ServiceWorkerGlobalScope extends WorkerGlobalScope {\n    DOMException: typeof DOMException; // PropertySignature\n    btoa(value: string): string; // MethodSignature\n    crypto: Crypto; // PropertySignature\n    get console(): Console; // GetAccessorDeclaration\n}\n`;\n\n  const sourcePath = path.resolve(__dirname, \"source.ts\");\n  const sources = new Map([[sourcePath, source]]);\n  const program = createMemoryProgram(sources);\n  const checker = program.getTypeChecker();\n  const sourceFile = program.getSourceFile(sourcePath);\n  assert(sourceFile !== undefined);\n\n  const result = ts.transform(sourceFile, [\n    createGlobalScopeTransformer(checker),\n  ]);\n  assert.strictEqual(result.transformed.length, 1);\n\n  const output = printer.printFile(result.transformed[0]);\n  assert.strictEqual(\n    output,\n    // Extracted global nodes inserted after ServiceWorkerGlobalScope\n    source +\n      `declare function addEventListener<Type extends keyof WorkerGlobalScopeEventMap>(type: Type, handler: (event: WorkerGlobalScopeEventMap[Type]) => void): void;\ndeclare function removeEventListener<Type extends keyof WorkerGlobalScopeEventMap>(type: Type, handler: (event: WorkerGlobalScopeEventMap[Type]) => void): void;\ndeclare function dispatchEvent(event: WorkerGlobalScopeEventMap[keyof WorkerGlobalScopeEventMap]): void;\ndeclare const thing: string;\ndeclare const CONSTANT: 42;\ndeclare const property: number;\ndeclare function btoa(value: string): string;\ndeclare const crypto: Crypto;\ndeclare const console: Console;\n`\n  );\n});\n\ntest(\"createGlobalScopeTransformer: inlining type parameters in heritage\", () => {\n  const source = `declare class A<T> {\n    thing: T;\n}\ndeclare class B<T> extends A<T> {\n}\ndeclare class ServiceWorkerGlobalScope extends B<string> {\n}\n`;\n\n  const sourcePath = path.resolve(__dirname, \"source.ts\");\n  const sources = new Map([[sourcePath, source]]);\n  const program = createMemoryProgram(sources);\n  const checker = program.getTypeChecker();\n  const sourceFile = program.getSourceFile(sourcePath);\n  assert(sourceFile !== undefined);\n\n  const result = ts.transform(sourceFile, [\n    createGlobalScopeTransformer(checker),\n  ]);\n  assert.strictEqual(result.transformed.length, 1);\n\n  const output = printer.printFile(result.transformed[0]);\n  assert.strictEqual(\n    output,\n    source +\n      `declare const thing: string;\n`\n  );\n});\n"
  },
  {
    "path": "types/test/transforms/iterators.spec.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from \"assert\";\nimport { test } from \"node:test\";\nimport path from \"path\";\nimport ts from \"typescript\";\nimport { printer } from \"../../src/print\";\nimport { createMemoryProgram } from \"../../src/program\";\nimport { createIteratorTransformer } from \"../../src/transforms\";\n\ntest(\"createIteratorTransformer: replaces Iterator-like interfaces with built-in Iterators\", () => {\n  const source = `export class Thing {\n    readonly thingsProperty: ThingIterator;\n    readonly asyncThingsProperty: AsyncThingIterator;\n    things(): ThingIterator;\n    asyncThings(): AsyncThingIterator;\n    [Symbol.iterator](): ThingIterator;\n    [Symbol.asyncIterator](): AsyncThingIterator;\n}\nexport interface ThingIterator extends Iterator {\n    next(): ThingIteratorNext;\n    [Symbol.iterator](): any;\n}\nexport interface ThingIteratorNext {\n    done: boolean;\n    value?: string;\n}\nexport interface AsyncThingIterator extends AsyncIterator {\n    next(): Promise<AsyncThingIteratorNext>;\n    return(value?: any): Promise<AsyncThingIteratorNext>;\n    [Symbol.asyncIterator](): any;\n}\nexport interface AsyncThingIteratorNext {\n  done: boolean;\n  value?: number;\n}\n`;\n  const sourcePath = path.resolve(__dirname, \"source.ts\");\n  const sources = new Map([[sourcePath, source]]);\n  const program = createMemoryProgram(sources);\n  const checker = program.getTypeChecker();\n  const sourceFile = program.getSourceFile(sourcePath);\n  assert(sourceFile !== undefined);\n\n  const result = ts.transform(sourceFile, [createIteratorTransformer(checker)]);\n  assert.strictEqual(result.transformed.length, 1);\n\n  const output = printer.printFile(result.transformed[0]);\n  assert.strictEqual(\n    output,\n    `export class Thing {\n    readonly thingsProperty: IterableIterator<string>;\n    readonly asyncThingsProperty: AsyncIterableIterator<number>;\n    things(): IterableIterator<string>;\n    asyncThings(): AsyncIterableIterator<number>;\n    [Symbol.iterator](): IterableIterator<string>;\n    [Symbol.asyncIterator](): AsyncIterableIterator<number>;\n}\n`\n  );\n});\n"
  },
  {
    "path": "types/test/transforms/overrides/index.spec.ts",
    "content": "// Copyright (c) 2022-2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport assert from 'assert';\nimport { test } from 'node:test';\nimport path from 'path';\nimport { Member_Nested, StructureGroups, Type } from '@workerd/jsg/rtti';\nimport { Message } from 'capnp-es';\nimport ts from 'typescript';\nimport { generateDefinitions } from '../../../src/generator';\nimport { printNodeList, printer } from '../../../src/print';\nimport { createMemoryProgram } from '../../../src/program';\nimport {\n  compileOverridesDefines,\n  createOverrideDefineTransformer,\n} from '../../../src/transforms';\n\nfunction printDefinitionsWithOverrides(root: StructureGroups): string {\n  const { nodes } = generateDefinitions(root);\n\n  const [sources, replacements] = compileOverridesDefines(root);\n  const sourcePath = path.resolve(__dirname, 'source.ts');\n  const source = printNodeList(nodes);\n  sources.set(sourcePath, source);\n\n  const program = createMemoryProgram(sources);\n  const sourceFile = program.getSourceFile(sourcePath);\n  assert(sourceFile !== undefined);\n\n  const result = ts.transform(sourceFile, [\n    createOverrideDefineTransformer(program, replacements),\n  ]);\n  assert.strictEqual(result.transformed.length, 1);\n\n  return printer.printFile(result.transformed[0]);\n}\n\ntest('createOverrideDefineTransformer: applies type renames', () => {\n  const root = new Message().initRoot(StructureGroups);\n  const group = root._initGroups(1).get(0);\n  const structures = group._initStructures(2);\n\n  const thing = structures.get(0);\n  thing.name = 'Thing';\n  thing.fullyQualifiedName = 'workerd::api::Thing';\n  thing.tsOverride = 'RenamedThing';\n  function referenceThing(type: Type | Member_Nested) {\n    const structureType = type._initStructure();\n    structureType.name = 'Thing';\n    structureType.fullyQualifiedName = 'workerd::api::Thing';\n  }\n\n  // Create type root that references Thing in different ways to test renaming\n  const root1 = structures.get(1);\n  root1.name = 'Root1';\n  root1.fullyQualifiedName = 'workerd::api::Root1';\n  root1.tsRoot = true;\n  // Make sure references to original names in overrides get renamed too\n  root1.tsOverride = '{ newProp: Thing; }';\n  {\n    const members = root1._initMembers(3);\n\n    const prop = members.get(0)._initProperty();\n    prop.name = 'prop';\n    referenceThing(prop._initType());\n\n    const method = members.get(1)._initMethod();\n    method.name = 'method';\n    referenceThing(method._initArgs(1).get(0));\n    referenceThing(method._initReturnType());\n\n    const nested = members.get(2)._initNested();\n    nested.name = 'Thing'; // Should keep original name\n    referenceThing(nested);\n  }\n\n  assert.strictEqual(\n    printDefinitionsWithOverrides(root),\n    `declare abstract class RenamedThing {\n}\ninterface Root1 {\n    prop: RenamedThing;\n    method(param0: RenamedThing): RenamedThing;\n    Thing: typeof RenamedThing;\n    newProp: RenamedThing;\n}\n`\n  );\n});\n\ntest('createOverrideDefineTransformer: applies property overrides', () => {\n  const root = new Message().initRoot(StructureGroups);\n  const group = root._initGroups(1).get(0);\n  const structures = group._initStructures(1);\n\n  const root1 = structures.get(0);\n  root1.name = 'Root1';\n  root1.fullyQualifiedName = 'workerd::api::Root1';\n  root1.tsRoot = true;\n  {\n    const members = root1._initMembers(6);\n\n    // Readonly instance property, overridden to be mutable and optional\n    const prop1 = members.get(0)._initProperty();\n    prop1.name = 'prop1';\n    prop1._initType()._initString().name = 'kj::String';\n    prop1.readonly = true;\n\n    // Mutable instance property, overridden to be readonly and required\n    const prop2 = members.get(1)._initProperty();\n    prop2.name = 'prop2';\n    prop2._initType()._initMaybe()._initValue().boolt = true;\n\n    // Readonly prototype property, overridden to be mutable\n    const prop3 = members.get(2)._initProperty();\n    prop3.name = 'prop3';\n    prop3._initType()._initArray()._initElement().boolt = true;\n    prop3.readonly = true;\n    prop3.prototype = true;\n\n    // Mutable prototype property, overridden to be readonly\n    const prop4 = members.get(3)._initProperty();\n    prop4.name = 'prop4';\n    prop4._initType()._initNumber().name = 'int';\n    prop4.prototype = true;\n\n    // Deleted property\n    const prop5 = members.get(4)._initProperty();\n    prop5.name = 'prop5';\n    prop5._initType().boolt = true;\n\n    // Untouched property\n    const prop6 = members.get(5)._initProperty();\n    prop6.name = 'prop6';\n    prop6._initType()._initPromise()._initValue().voidt = true;\n  }\n  root1.tsOverride = `{\n    prop1?: \"thing\";\n    readonly prop2: true;\n    get prop3(): false;\n    set prop3(value: false);\n    get prop4(): 1 | 2 | 3;\n    prop5: never;\n  }`;\n\n  assert.strictEqual(\n    printDefinitionsWithOverrides(root),\n    `interface Root1 {\n    prop1?: \"thing\";\n    readonly prop2: true;\n    get prop3(): false;\n    set prop3(value: false);\n    get prop4(): 1 | 2 | 3;\n    prop6: Promise<void>;\n}\n`\n  );\n});\n\ntest('createOverrideDefineTransformer: applies method overrides', () => {\n  const root = new Message().initRoot(StructureGroups);\n  const group = root._initGroups(1).get(0);\n  const structures = group._initStructures(1);\n\n  const root1 = structures.get(0);\n  root1.name = 'Root1';\n  root1.fullyQualifiedName = 'workerd::api::Root1';\n  root1.tsRoot = true;\n  {\n    const members = root1._initMembers(7);\n\n    // Static and instance methods with the same names\n    const method1 = members.get(0)._initMethod();\n    method1.name = 'one';\n    method1._initReturnType()._initNumber().name = 'int';\n    const staticMethod1 = members.get(1)._initMethod();\n    staticMethod1.name = 'one';\n    staticMethod1._initReturnType()._initNumber().name = 'int';\n    staticMethod1.static = true;\n    const method2 = members.get(2)._initMethod();\n    method2.name = 'two';\n    method2._initReturnType()._initNumber().name = 'int';\n    const staticMethod2 = members.get(3)._initMethod();\n    staticMethod2.name = 'two';\n    staticMethod2._initReturnType()._initNumber().name = 'int';\n    staticMethod2.static = true;\n\n    // Method with multiple overloads\n    const methodGet = members.get(4)._initMethod();\n    methodGet.name = 'get';\n    {\n      const args = methodGet._initArgs(2);\n      args.get(0)._initString().name = 'kj::String';\n      const variants = args.get(1)._initOneOf()._initVariants(2);\n      variants.get(0)._initString().name = 'kj::String';\n      variants.get(1).unknown = true;\n    }\n    const methodGetReturn = methodGet._initReturnType()._initMaybe();\n    methodGetReturn.name = 'kj::Maybe';\n    methodGetReturn._initValue().unknown = true;\n\n    // Deleted method\n    const methodDeleteAll = members.get(5)._initMethod();\n    methodDeleteAll.name = 'deleteAll';\n    methodDeleteAll._initReturnType().voidt = true;\n\n    // Untouched method\n    const methodThing = members.get(6)._initMethod();\n    methodThing.name = 'thing';\n    methodThing._initArgs(1).get(0).boolt = true;\n    methodThing._initReturnType().boolt = true;\n  }\n  // These overrides test:\n  // - Overriding a static method with an instance method of the same name\n  // - Overriding an instance method with a static method of the same name\n  // - Split overloads, these should be grouped\n  // - Deleted method\n  root1.tsOverride = `{\n    static one(): 1;\n    two(): 2;\n\n    get(key: string, type: \"text\"): Promise<string | null>;\n    get(key: string, type: \"arrayBuffer\"): Promise<ArrayBuffer | null>;\n\n    deleteAll: never;\n\n    get<T>(key: string, type: \"json\"): Promise<T | null>;\n  }`;\n\n  assert.strictEqual(\n    printDefinitionsWithOverrides(root),\n    `declare abstract class Root1 {\n    one(): number;\n    static one(): 1;\n    two(): 2;\n    static two(): number;\n    get(key: string, type: \"text\"): Promise<string | null>;\n    get(key: string, type: \"arrayBuffer\"): Promise<ArrayBuffer | null>;\n    get<T>(key: string, type: \"json\"): Promise<T | null>;\n    thing(param0: boolean): boolean;\n}\n`\n  );\n});\n\ntest('createOverrideDefineTransformer: applies type parameter overrides', () => {\n  const root = new Message().initRoot(StructureGroups);\n  const group = root._initGroups(1).get(0);\n  const structures = group._initStructures(2);\n\n  const struct = structures.get(0);\n  struct.name = 'Struct';\n  struct.fullyQualifiedName = 'workerd::api::Struct';\n  {\n    const members = struct._initMembers(1);\n    const prop = members.get(0)._initProperty();\n    prop.name = 'type';\n    prop._initType().unknown = true;\n  }\n  struct.tsOverride = `RenamedStruct<Type extends string = string> {\n    type: Type;\n  }`;\n\n  const root1 = structures.get(1);\n  root1.name = 'Root1';\n  root1.fullyQualifiedName = 'workerd::api::Root1';\n  root1.tsRoot = true;\n  {\n    const members = root1._initMembers(2);\n\n    const methodGet = members.get(0)._initMethod();\n    methodGet.name = 'get';\n    const returnStruct = methodGet._initReturnType()._initStructure();\n    returnStruct.name = 'Struct';\n    returnStruct.fullyQualifiedName = 'workerd::api::Struct';\n\n    const methodRead = members.get(1)._initMethod();\n    methodRead.name = 'read';\n    methodRead._initReturnType()._initPromise()._initValue().unknown = true;\n  }\n  root1.tsOverride = `<R> {\n    read(): Promise<R>;\n  }`;\n\n  assert.strictEqual(\n    printDefinitionsWithOverrides(root),\n    `interface RenamedStruct<Type extends string = string> {\n    type: Type;\n}\ninterface Root1<R> {\n    get(): RenamedStruct;\n    read(): Promise<R>;\n}\n`\n  );\n});\n\ntest('createOverrideDefineTransformer: applies heritage overrides', () => {\n  const root = new Message().initRoot(StructureGroups);\n  const group = root._initGroups(1).get(0);\n  const structures = group._initStructures(4);\n\n  const superclass = structures.get(0);\n  superclass.name = `Superclass`;\n  superclass.fullyQualifiedName = `workerd::api::Superclass`;\n  superclass.tsOverride = '<T, U = unknown>';\n\n  const root1 = structures.get(1);\n  root1.name = 'Root1';\n  root1.fullyQualifiedName = 'workerd::api::Root1';\n  const root1Extends = root1._initExtends()._initStructure();\n  root1Extends.name = 'Superclass';\n  root1Extends.fullyQualifiedName = 'workerd::api::Superclass';\n  root1.tsRoot = true;\n  root1.tsOverride = `extends Superclass<ArrayBuffer | ArrayBufferView, Uint8Array>`;\n\n  const root2 = structures.get(2);\n  root2.name = 'Root2';\n  root2.fullyQualifiedName = 'workerd::api::Root2';\n  const root2Extends = root1._initExtends()._initStructure();\n  root2Extends.name = 'Superclass';\n  root2Extends.fullyQualifiedName = 'workerd::api::Superclass';\n  root2.tsRoot = true;\n  root2.tsOverride = 'Root2<T> implements Superclass<T>';\n\n  const root3 = structures.get(3);\n  root3.name = 'Root3';\n  root3.fullyQualifiedName = 'workerd::api::Root3';\n  const root3Extends = root1._initExtends()._initStructure();\n  root3Extends.name = 'Superclass';\n  root3Extends.fullyQualifiedName = 'workerd::api::Superclass';\n  root3.tsRoot = true;\n  {\n    const members = root3._initMembers(1);\n    const prop = members.get(0)._initProperty();\n    prop.name = 'prop';\n    prop._initType()._initNumber().name = 'int';\n  }\n  root3.tsOverride = `extends Superclass<boolean> {\n    prop: 1 | 2 | 3;\n  }`;\n\n  assert.strictEqual(\n    printDefinitionsWithOverrides(root),\n    `declare abstract class Superclass<T, U = unknown> {\n}\ninterface Root1 extends Superclass<ArrayBuffer | ArrayBufferView, Uint8Array> {\n}\ninterface Root2<T> implements Superclass<T> {\n}\ninterface Root3 extends Superclass<boolean> {\n    prop: 1 | 2 | 3;\n}\n`\n  );\n});\n\ntest('createOverrideDefineTransformer: applies full type replacements', () => {\n  const root = new Message().initRoot(StructureGroups);\n  const group = root._initGroups(1).get(0);\n  const structures = group._initStructures(4);\n\n  const root1 = structures.get(0);\n  root1.name = 'Root1';\n  root1.fullyQualifiedName = 'workerd::api::Root1';\n  root1.tsRoot = true;\n  root1.tsOverride = `const Root1 = {\n    new (): { 0: Root2; 1: Root3; };\n  }`;\n\n  const root2 = structures.get(1);\n  root2.name = 'Root2';\n  root2.fullyQualifiedName = 'workerd::api::Root2';\n  root2.tsRoot = true;\n  root2.tsOverride = `enum Root2 { ONE, TWO, THREE; }`;\n\n  const root3 = structures.get(2);\n  root3.name = 'Root3';\n  root3.fullyQualifiedName = 'workerd::api::Root3';\n  root3.tsRoot = true;\n  // Check renames still applied with full-type replacements\n  root3.tsOverride = `type RenamedRoot3<T = any> = { done: false; value: T; } | { done: true; value: undefined; }`;\n\n  const root4 = structures.get(3);\n  root4.name = 'Root4';\n  root4.fullyQualifiedName = 'workerd::api::Root4';\n  root4.tsRoot = true;\n  root4.tsOverride = `type Root4 = never`;\n\n  assert.strictEqual(\n    printDefinitionsWithOverrides(root),\n    `declare const Root1 = {\n    new(): {\n        0: Root2;\n        1: RenamedRoot3;\n    };\n};\ndeclare enum Root2 {\n    ONE,\n    TWO,\n    THREE\n}\ntype RenamedRoot3<T = any> = {\n    done: false;\n    value: T;\n} | {\n    done: true;\n    value: undefined;\n};\n`\n  );\n});\n\ntest('createOverrideDefineTransformer: applies overrides with literals', () => {\n  const root = new Message().initRoot(StructureGroups);\n  const group = root._initGroups(1).get(0);\n  const structures = group._initStructures(1);\n\n  const root1 = structures.get(0);\n  root1.name = 'Root1';\n  root1.fullyQualifiedName = 'workerd::api::Root1';\n  root1.tsRoot = true;\n  root1.tsOverride = `{\n    literalString: \"hello\";\n    literalNumber: 42;\n    literalArray: [a: \"a\", b: 2];\n    literalObject: { a: \"a\"; b: 2; };\n    literalTemplate: \\`\\${string}-\\${number}\\`;\n  }`;\n\n  assert.strictEqual(\n    printDefinitionsWithOverrides(root),\n    `interface Root1 {\n    literalString: \"hello\";\n    literalNumber: 42;\n    literalArray: [\n        a: \"a\",\n        b: 2\n    ];\n    literalObject: {\n        a: \"a\";\n        b: 2;\n    };\n    literalTemplate: \\`\\${string}-\\${number}\\`;\n}\n`\n  );\n});\n\ntest('createOverrideDefineTransformer: inserts extra defines', () => {\n  const root = new Message().initRoot(StructureGroups);\n  const group = root._initGroups(1).get(0);\n  const structures = group._initStructures(2);\n\n  const root1 = structures.get(0);\n  root1.name = 'Root1';\n  root1.fullyQualifiedName = 'workerd::api::Root1';\n  root1.tsRoot = true;\n\n  const root2 = structures.get(1);\n  root2.name = 'Root2';\n  root2.fullyQualifiedName = 'workerd::api::Root2';\n  root2.tsRoot = true;\n  root2.tsDefine = 'interface Root2Extra<Type> { prop: Type }';\n  root2.tsOverride = 'RenamedRoot2';\n\n  // Check defines inserted before structure\n  assert.strictEqual(\n    printDefinitionsWithOverrides(root),\n    `interface Root1 {\n}\ninterface Root2Extra<Type> {\n    prop: Type;\n}\ninterface RenamedRoot2 {\n}\n`\n  );\n});\n"
  },
  {
    "path": "types/test/types/cf.ts",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nfunction expectType<T>(_value: T) {}\n\nexport const handler: ExportedHandler<{ SERVICE: Fetcher }> = {\n  async fetch(request, env) {\n    // Can access incoming cf properties without narrowing\n    expectType<string | undefined>(request.cf?.colo);\n    // ...but cannot access request init cf properties\n    expectType<unknown>(request.cf?.cacheEverything);\n\n    // Can forward request as is (unknown cf properties ignored)\n    await fetch(request);\n\n    // Can forward request to different URL  (unknown cf properties ignored)\n    await fetch(\"https://example.com\", request);\n\n    // Can fetch with request init cf properties\n    await fetch(\"https://example.com\", {\n      cf: { cacheEverything: true },\n    });\n\n    // Can fetch to service binding with incoming properties to simulate incoming request\n    await env.SERVICE.fetch(\"https://example.com\", {\n      cf: { colo: \"LHR\" },\n    });\n\n    // Can fetch to service binding with custom properties\n    await env.SERVICE.fetch(\"https://example.com\", {\n      cf: { token: \"thing\" },\n    });\n    // ...and can safely access that on the incoming request\n    expectType<unknown>(request.cf?.token);\n    if (typeof request.cf?.token === \"string\") {\n      expectType<string>(request.cf.token);\n    }\n\n    return new Response();\n  },\n};\n"
  },
  {
    "path": "types/test/types/d1.ts",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nfunction expectType<T>(_value: T) {}\n\ntype Row = { col1: string; col2: string };\n\nexport const handler: ExportedHandler<{ DB: D1Database }> = {\n  async fetch(request, env) {\n    const stmt = env.DB.prepare(`SELECT * FROM tbl WHERE id = ?`);\n\n    // RUN\n    {\n      const response = await stmt.bind(1).run();\n      expectType<{\n        meta: Record<string, unknown>;\n        success: boolean;\n        results?: any;\n      }>(response);\n    }\n\n    // ALL\n    {\n      const { results } = await stmt.bind(1).all();\n      expectType<Record<string, unknown>[]>(results);\n    }\n    {\n      const { results } = await stmt.bind(1).all<Row>();\n      expectType<Row[]>(results);\n    }\n\n    // RAW\n    {\n      const results = await stmt.bind(1).raw();\n      expectType<unknown[][]>(results);\n    }\n    {\n      const results = await stmt.bind(1).raw<[string, number, boolean]>();\n      expectType<[string, number, boolean][]>(results);\n    }\n    {\n      const results = await stmt.bind(1).raw<[string, number, boolean]>({});\n      expectType<[string, number, boolean][]>(results);\n    }\n    {\n      const results = await stmt\n        .bind(1)\n        .raw<[string, number, boolean]>({ columnNames: false });\n      expectType<[string, number, boolean][]>(results);\n    }\n    {\n      const results = await stmt.bind(1).raw({ columnNames: true });\n      expectType<[string[], ...unknown[]]>(results);\n    }\n    {\n      const [columns, ...rows] = await stmt\n        .bind(1)\n        .raw<[string, number, boolean]>({ columnNames: true });\n      expectType<string[]>(columns);\n      expectType<[string, number, boolean][]>(rows);\n    }\n\n    // FIRST\n    {\n      const result = await stmt.bind(1).first();\n      expectType<Record<string, unknown> | null>(result);\n    }\n    {\n      const result = await stmt.bind(1).first<Row>();\n      expectType<Row | null>(result);\n    }\n\n    // FIRST (col)\n    {\n      const result = await stmt.bind(1).first(\"col1\");\n      expectType<unknown | null>(result);\n    }\n    {\n      const result = await stmt.bind(1).first<string>(\"col1\");\n      expectType<string | null>(result);\n    }\n\n    // BATCH\n    {\n      const results = await env.DB.batch([stmt.bind(1), stmt.bind(2)]);\n      expectType<D1Result[]>(results);\n      expectType<unknown[]>(results[0].results);\n    }\n    {\n      const results = await env.DB.batch<Row>([stmt.bind(1), stmt.bind(2)]);\n      expectType<D1Result<Row>[]>(results);\n      expectType<Row[]>(results[0].results);\n    }\n\n    // EXEC\n    {\n      const response = await env.DB.exec(`\n       select 1;\n       select * from tbl;\n     `);\n      expectType<{ count: number; duration: number }>(response);\n    }\n\n    // WITHSESSION\n    {\n      expectType<D1DatabaseSession>(env.DB.withSession());\n      const session = env.DB.withSession(\"first-primary\");\n      expectType<D1DatabaseSession>(session);\n\n      const bookmark = session.getBookmark();\n      expectType<D1SessionBookmark | null>(bookmark);\n\n      // SESSION PREPARE\n      const stmt = session.prepare(`SELECT * FROM tbl WHERE id = ?`);\n\n      // SESSION BATCH\n      {\n        const results = await env.DB.batch([stmt.bind(1), stmt.bind(2)]);\n        expectType<D1Result[]>(results);\n        expectType<unknown[]>(results[0].results);\n      }\n      {\n        const results = await env.DB.batch<Row>([stmt.bind(1), stmt.bind(2)]);\n        expectType<D1Result<Row>[]>(results);\n        expectType<Row[]>(results[0].results);\n      }\n    }\n\n    return new Response();\n  },\n};\n"
  },
  {
    "path": "types/test/types/do.ts",
    "content": "import { DurableObject } from \"cloudflare:workers\";\nimport { expectTypeOf } from \"expect-type\";\n\n// Aliased as SqlStorageValue, but let's assert it includes the raw types we expect\ntype Value = ArrayBuffer | string | number | null;\n\nclass TestDOSql extends DurableObject {\n  test() {\n    const db = this.ctx.storage.sql;\n\n    expectTypeOf<SqlStorage>(db);\n\n    expectTypeOf<number>(db.databaseSize);\n\n    // Verify default row type of exec\n    for (const row of db.exec(\"...\")) {\n      expectTypeOf<Record<string, Value>>(row);\n    }\n\n    // Verify scoped row type of exec\n    for (const row of db.exec<{ name: string; phone: number }>(\"...\")) {\n      expectTypeOf<{ name: string; phone: number }>(row);\n\n      // @ts-expect-error double-checking our assertions are strict\n      expectTypeOf<{ name: string; phone: string }>(row);\n      // @ts-expect-error double-checking our assertions are strict\n      expectTypeOf<Record<string, number>>(row);\n    }\n\n    const cursor = db.exec(\"...\", 1, \"two\")\n    expectTypeOf<SqlStorageCursor<Record<string, Value>>>(cursor);\n\n    expectTypeOf<number>(cursor.rowsRead);\n    expectTypeOf<number>(cursor.rowsWritten);\n    expectTypeOf<string[]>(cursor.columnNames);\n\n    expectTypeOf<Record<string, Value>[]>(cursor.toArray());\n    expectTypeOf<Record<string, Value>>(cursor.one());\n\n    const next = cursor.next();\n    // Can narrow the type by checking .done\n    if (!next.done) {\n      expectTypeOf<Record<string, Value>>(next.value);\n    }\n\n    const another = cursor.next()\n    // Or check .value to do the same thing\n    if (another.value) {\n      expectTypeOf<Record<string, Value>>(another.value);\n      expectTypeOf<false | undefined>(another.done);\n    } else {\n      expectTypeOf<undefined>(another.value);\n      expectTypeOf<true>(another.done);\n    }\n\n    // Common shorthand usage\n    const { value: thirdRow } = cursor.next()\n    if (!thirdRow) throw new Error('No value!')\n    expectTypeOf<Record<string, Value>>(thirdRow);\n  }\n}\n"
  },
  {
    "path": "types/test/types/rpc.ts",
    "content": "// Copyright (c) 2023 Cloudflare, Inc.\n// Licensed under the Apache 2.0 license found in the LICENSE file or at:\n//     https://opensource.org/licenses/Apache-2.0\n\nimport {\n  DurableObject,\n  RpcStub,\n  RpcTarget,\n  WorkerEntrypoint,\n} from 'cloudflare:workers';\nimport { expectTypeOf } from 'expect-type';\n\ntype TestType = {\n  fieldString: string;\n  fieldCallback: (p: string) => number;\n  fieldBasicMap: Map<string, number>;\n  fieldComplexMap: Map<\n    string,\n    {\n      fieldString: string;\n      fieldCallback: (p: string) => number;\n    }\n  >;\n  fieldSet: Set<string>;\n  fieldSubLevel: {\n    fieldString: string;\n    fieldCallback: (p: string) => number;\n  };\n};\n\ninterface ABasicInterface {\n  fieldString: string;\n  fieldCallback: (p: string) => number;\n}\n\ninterface TestInterface extends ABasicInterface {\n  fieldBasicMap: Map<string, number>;\n  fieldComplexMap: Map<string, ABasicInterface>;\n  fieldSet: Set<string>;\n  fieldSubLevelInline: {\n    fieldString: string;\n    fieldCallback: (p: string) => number;\n  };\n  fieldSubLevelInterface: ABasicInterface;\n}\n\ninterface NonSerializableInterface {\n  field: ReadableStream<string>;\n}\n\nclass TestCounter extends RpcTarget {\n  constructor(private val = 0) {\n    super();\n  }\n  get value() {\n    return this.val;\n  }\n  increment(by = 1) {\n    return (this.val += by);\n  }\n  copy() {\n    return new TestCounter(this.val);\n  }\n\n  [Symbol.dispose]() {\n    console.log('Disposing');\n  }\n\n  // Check can't use custom `dup()` method\n  dup(x: number) {\n    return x;\n  }\n}\n\nconst symbolMethod = Symbol('symbolMethod');\n\ntype Props = {myProp: number};\n\nclass TestEntrypoint extends WorkerEntrypoint<Env, Props> {\n  constructor(ctx: ExecutionContext<Props>, env: Env) {\n    super(ctx, env);\n  }\n\n  fetch(request: Request) {\n    return new Response(request.url);\n  }\n  async tail(_events: TraceItem[]) {}\n  trace(_events: TraceItem[]) {}\n  async scheduled(_controller: ScheduledController) {}\n  queue(_batch: MessageBatch<number>) {}\n  async test(_controller: TestController) {}\n\n  private privateInstanceProperty = 0;\n  private get privateProperty() {\n    expectTypeOf(this.ctx).toEqualTypeOf<ExecutionContext<Props>>();\n    expectTypeOf(this.env).toEqualTypeOf<Env>();\n\n    return 1;\n  }\n\n  instanceProperty = '2';\n  get property(): number {\n    return 3;\n  }\n  get asyncProperty() {\n    return Promise.resolve(true as const);\n  }\n\n  private privateMethod() {\n    return 4;\n  }\n  [symbolMethod]() {\n    return 5;\n  }\n  method() {\n    return null;\n  }\n  async asyncMethod() {\n    return true;\n  }\n  voidMethod(_x?: number) {}\n  async asyncVoidMethod() {}\n\n  functionMethod() {\n    return (x: number) => x;\n  }\n  async asyncFunctionMethod() {\n    return (x: number) => x;\n  }\n  async asyncAsyncFunctionMethod() {\n    return async (x: number) => x;\n  }\n\n  functionWithExtrasMethod() {\n    const fn = (x: number) => x;\n    fn.y = 'z';\n    return fn;\n  }\n\n  async stubMethod(\n    callback: RpcStub<(x: number) => number>,\n    counter: RpcStub<TestCounter>,\n    _map: Map<RpcStub<TestCounter>, RpcStub<TestCounter>>,\n    _set: Set<RpcStub<TestCounter>>,\n    _array: Array<RpcStub<TestCounter>>,\n    _readonlyArray: ReadonlyArray<RpcStub<TestCounter>>,\n    _object: { a: { b: RpcStub<TestCounter> } }\n  ) {\n    expectTypeOf(callback(1)).toEqualTypeOf<Promise<number>>(); // (always async)\n    expectTypeOf(counter.value).toEqualTypeOf<Promise<number>>(); // (always async)\n    const result = await callback(2);\n    return result * 3;\n  }\n\n  get objectProperty() {\n    return {\n      w: 1,\n      get x() {\n        return 2;\n      },\n      y() {\n        return 3;\n      },\n      async z(_a: boolean) {\n        return 4;\n      },\n    };\n  }\n\n  get everySerializable() {\n    return {\n      undefined: undefined,\n      null: null,\n      boolean: false,\n      number: 42,\n      bigint: 1_000_000n,\n      string: 'hello',\n      Int8Array: new Int8Array(),\n      Uint8Array: new Uint8Array(),\n      Uint8ClampedArray: new Uint8ClampedArray(),\n      Int16Array: new Int16Array(),\n      Uint16Array: new Uint16Array(),\n      Int32Array: new Int32Array(),\n      Uint32Array: new Uint32Array(),\n      Float32Array: new Float32Array(),\n      Float64Array: new Float64Array(),\n      BigInt64Array: new BigInt64Array(),\n      BigUint64Array: new BigUint64Array(),\n      ArrayBuffer: new ArrayBuffer(4),\n      DataView: new DataView(new ArrayBuffer(4)),\n      Date: new Date(),\n      Error: new Error(),\n      EvalError: new EvalError(),\n      RangeError: new RangeError(),\n      ReferenceError: new ReferenceError(),\n      SyntaxError: new SyntaxError(),\n      TypeError: new TypeError(),\n      URIError: new URIError(),\n      RegExp: /abc/,\n      Map: new Map([['a', 1]]),\n      Set: new Set(['a']),\n      Array: [1, 2, 3],\n      ReadonlyArray: [4, 5, 6] as const,\n      Object: { a: { b: 1 } },\n      ReadableStream: new ReadableStream<Uint8Array>(),\n      WritableStream: new WritableStream<Uint8Array>(),\n      Request: new Request('https://example.com'),\n      Response: new Response(),\n      Headers: new Headers(),\n      Stub: new RpcStub(() => {}),\n    };\n  }\n  get everyCompositeSerializable() {\n    return {\n      Map: new Map([[new TestCounter(), new TestCounter()]]),\n      Set: new Set([new TestCounter()]),\n      Array: [new TestCounter()],\n      ReadonlyArray: [new TestCounter()] as const,\n      Object: { a: { b: new TestCounter() } },\n    };\n  }\n\n  methodReturnsTypeObject(): TestType {\n    return {\n      fieldString: 'a',\n      fieldCallback: (p: string) => 1,\n      fieldBasicMap: new Map([['b', 2]]),\n      fieldComplexMap: new Map([\n        [\n          'c',\n          {\n            fieldString: 'd',\n            fieldCallback: (p: string) => 3,\n          },\n        ],\n      ]),\n      fieldSet: new Set(['e']),\n      fieldSubLevel: {\n        fieldString: 'f',\n        fieldCallback: (p: string) => 4,\n      },\n    };\n  }\n  methodReturnsInterfaceObject(): TestInterface {\n    return {\n      fieldString: 'a',\n      fieldCallback: (p: string) => 1,\n      fieldBasicMap: new Map([['b', 2]]),\n      fieldComplexMap: new Map([\n        [\n          'c',\n          {\n            fieldString: 'd',\n            fieldCallback: (p: string) => 3,\n          },\n        ],\n      ]),\n      fieldSet: new Set(['e']),\n      fieldSubLevelInline: {\n        fieldString: 'f',\n        fieldCallback: (p: string) => 4,\n      },\n      fieldSubLevelInterface: {\n        fieldString: 'e',\n        fieldCallback: (p: string) => 5,\n      },\n    };\n  }\n\n  nonSerializable1() {\n    return new ReadableStream<string>();\n  }\n  nonSerializable2() {\n    return { field: new ReadableStream<string>() };\n  }\n  nonSerializable3(): NonSerializableInterface {\n    return { field: new ReadableStream<string>() };\n  }\n\n  [Symbol.dispose]() {\n    console.log('Disposing');\n  }\n}\n\nclass TestObject extends DurableObject {\n  async fetch(request: Request) {\n    return new Response(request.url);\n  }\n  async alarm() {}\n\n  complexTypes() {\n    return {\n      undefined: undefined,\n      void: void 0,\n      null: null,\n      boolean: true,\n      number: 1,\n      bigint: BigInt(4),\n      string: 'string',\n      ArrayBuffer: new ArrayBuffer(0),\n      DataView: new DataView(new ArrayBuffer(0)),\n      Date: new Date(),\n      Error: new Error(),\n      RegExp: new RegExp(''),\n      ReadableStream: new ReadableStream(),\n      WritableStream: new WritableStream(),\n      Request: new Request('https://example.com'),\n      Response: new Response(),\n      Headers: new Headers(),\n      nested: {\n        undefined: undefined,\n        void: void 0,\n        null: null,\n        boolean: true,\n        number: 1,\n        bigint: BigInt(4),\n        string: 'string',\n        ArrayBuffer: new ArrayBuffer(0),\n        DataView: new DataView(new ArrayBuffer(0)),\n        Date: new Date(),\n        Error: new Error(),\n        RegExp: new RegExp(''),\n        ReadableStream: new ReadableStream(),\n        WritableStream: new WritableStream(),\n        Request: new Request('https://example.com'),\n        Response: new Response(),\n        Headers: new Headers(),\n      },\n    };\n  }\n\n  webSocketMessage(_ws: WebSocket, _message: string | ArrayBuffer) {}\n  async webSocketClose(\n    _ws: WebSocket,\n    _code: number,\n    _reason: string,\n    _wasClean: boolean\n  ) {}\n  webSocketError(_ws: WebSocket, _error: unknown) {}\n\n  targetMethod() {\n    return new TestCounter();\n  }\n  async asyncTargetMethod() {\n    return new TestCounter();\n  }\n\n  [Symbol.dispose]() {\n    console.log('Disposing');\n  }\n}\n\nclass TestAlarmObject extends DurableObject {\n  // Can declare alarm method consuming optional alarmInfo parameter\n  async alarm(alarmInfo?: AlarmInvocationInfo) {\n    if (alarmInfo !== undefined) {\n      const _isRetry: boolean = alarmInfo.isRetry;\n      const _retryCount: number = alarmInfo.retryCount;\n    }\n  }\n\n  // User code can invoke alarm() directly, if desired.\n  async runAlarmVoid(): Promise<void> {\n    return await this.alarm();\n  }\n  async runAlarmInfo(): Promise<void> {\n    return await this.alarm({\n      isRetry: true,\n      retryCount: 1,\n    });\n  }\n}\n\nclass TestNaughtyEntrypoint extends WorkerEntrypoint {\n  // Check incorrectly typed methods\n  // @ts-expect-error\n  fetch(_request: Request) {\n    return 'body';\n  }\n  // @ts-expect-error\n  async tail(_animal: '🐶') {}\n  // @ts-expect-error\n  trace(_draw: boolean) {}\n  // @ts-expect-error\n  async scheduled(_at: Date) {}\n  // @ts-expect-error\n  queue(_message: string) {}\n  // @ts-expect-error\n  async test(_x: number, _y: number) {}\n}\n\nclass TestNaughtyObject extends DurableObject {\n  // Check incorrectly typed methods\n  // @ts-expect-error\n  async fetch(url: string) {\n    return new Response(url);\n  }\n  // @ts-expect-error\n  async alarm(_x: boolean) {}\n  // @ts-expect-error\n  webSocketMessage(_x: boolean) {}\n  // @ts-expect-error\n  async webSocketClose(_x: boolean) {}\n  // @ts-expect-error\n  webSocketError(_x: boolean) {}\n}\n\ninterface Env {\n  REGULAR_SERVICE: Service;\n  RPC_SERVICE: Service<TestEntrypoint>;\n  TYPEOF_RPC_SERVICE: Service<typeof TestEntrypoint>;\n  NAUGHTY_SERVICE: Service<TestNaughtyEntrypoint>;\n  // @ts-expect-error `BoringClass` isn't an RPC capable type\n  __INVALID_RPC_SERVICE_1: Service<BoringClass>;\n\n  REGULAR_OBJECT: DurableObjectNamespace;\n  RPC_OBJECT: DurableObjectNamespace<TestObject>;\n  ALARM_OBJECT: DurableObjectNamespace<TestAlarmObject>;\n  NAUGHTY_OBJECT: DurableObjectNamespace<TestNaughtyObject>;\n  // @ts-expect-error `BoringClass` isn't an RPC capable type\n  __INVALID_OBJECT_1: DurableObjectNamespace<BoringClass>;\n  // @ts-expect-error `TestEntrypoint` is a `WorkerEntrypoint`, not a `DurableObject`\n  __INVALID_OBJECT_2: DurableObjectNamespace<TestEntrypoint>;\n}\n\nexport default <ExportedHandler<Env>>{\n  async fetch(_request, env, _ctx) {\n    // Check non-RPC services and namespaces work as usual\n    {\n      const response = await env.REGULAR_SERVICE.fetch('https://example.com', {\n        method: 'POST',\n      });\n      expectTypeOf(response).toEqualTypeOf<Response>();\n\n      const uniqueId = env.REGULAR_OBJECT.newUniqueId();\n      expectTypeOf(uniqueId).toEqualTypeOf<DurableObjectId>();\n      const nameId = env.REGULAR_OBJECT.newUniqueId();\n      expectTypeOf(nameId).toEqualTypeOf<DurableObjectId>();\n      const stringId = env.REGULAR_OBJECT.newUniqueId();\n      expectTypeOf(stringId).toEqualTypeOf<DurableObjectId>();\n\n      const stub = env.REGULAR_OBJECT.get(uniqueId);\n      const objectResponse = await stub.fetch('https://example.com', {\n        method: 'POST',\n      });\n      expectTypeOf(objectResponse).toEqualTypeOf<Response>();\n      expectTypeOf(stub.id).toEqualTypeOf<DurableObjectId>();\n      expectTypeOf(stub.name).toEqualTypeOf<string | undefined>();\n    }\n\n    // Check RPC services and namespaces support standard methods (without overloads,\n    // `toEqualTypeOf(...)` will fail if the function signature doesn't match *exactly*)\n    {\n      expectTypeOf(env.RPC_SERVICE.fetch).toEqualTypeOf<\n        (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>\n      >();\n      expectTypeOf(env.RPC_SERVICE.connect).toEqualTypeOf<\n        (address: SocketAddress | string, options?: SocketOptions) => Socket\n      >();\n      expectTypeOf(env.RPC_SERVICE.queue).toEqualTypeOf<\n        (\n          queueName: string,\n          messages: ServiceBindingQueueMessage[]\n        ) => Promise<FetcherQueueResult>\n      >();\n      expectTypeOf(env.RPC_SERVICE.scheduled).toEqualTypeOf<\n        (options?: FetcherScheduledOptions) => Promise<FetcherScheduledResult>\n      >();\n\n      const stub = env.RPC_OBJECT.get(env.RPC_OBJECT.newUniqueId());\n      expectTypeOf(stub.fetch).toEqualTypeOf<\n        (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>\n      >();\n      expectTypeOf(stub.connect).toEqualTypeOf<\n        (address: SocketAddress | string, options?: SocketOptions) => Socket\n      >();\n    }\n\n    // Check cannot access `env` and `ctx` over RPC\n    {\n      // @ts-expect-error protected properties are not accessible\n      env.RPC_SERVICE.env;\n      // @ts-expect-error protected properties are not accessible\n      env.RPC_SERVICE.ctx;\n\n      const stub = env.RPC_OBJECT.get(env.RPC_OBJECT.newUniqueId());\n      // @ts-expect-error protected properties are not accessible\n      stub.env;\n      // @ts-expect-error protected properties are not accessible\n      stub.ctx;\n    }\n\n    // Check accessing properties (including using)\n    {\n      const s = env.RPC_SERVICE;\n\n      // @ts-expect-error private properties are not accessible\n      s.privateInstanceProperty;\n      // @ts-expect-error private properties are not accessible\n      s.privateProperty;\n\n      // Unfortunately, TypeScript's structural typing makes it tricky to\n      // differentiate between instance and prototype properties. This line\n      // would fail at runtime. We should encourage people to use `private`\n      // access modifiers when using TypeScript.\n      // TODO(someday): try make this fail type checking\n      expectTypeOf(s.instanceProperty).toEqualTypeOf<Promise<string>>();\n\n      expectTypeOf(s.property).toEqualTypeOf<Promise<number>>(); // (always async)\n      expectTypeOf(s.asyncProperty).toEqualTypeOf<Promise<true>>();\n    }\n\n    // Check calling methods (including using)\n    {\n      const s = env.RPC_SERVICE;\n\n      // @ts-expect-error private methods are not accessible\n      s.privateMethod;\n      // @ts-expect-error symbol methods are not accessible\n      s[symbolMethod];\n\n      expectTypeOf(s.method()).toEqualTypeOf<Promise<null>>(); // (always async)\n      expectTypeOf(await s.asyncMethod()).toEqualTypeOf<boolean>();\n\n      expectTypeOf(s.voidMethod).toEqualTypeOf<(x?: number) => Promise<void>>(); // (always async)\n      expectTypeOf(s.asyncVoidMethod).toEqualTypeOf<() => Promise<void>>();\n    }\n\n    // Check methods returning functions (including pipelining/ERM)\n    {\n      const s = env.RPC_SERVICE;\n\n      expectTypeOf(s.functionMethod()).toMatchTypeOf<Promise<unknown>>();\n      using f1 = await s.functionMethod();\n      expectTypeOf(f1).toEqualTypeOf<RpcStub<(x: number) => number>>();\n      expectTypeOf(f1.dup()).toEqualTypeOf(f1);\n      expectTypeOf(f1(1)).toEqualTypeOf<Promise<number>>(); // (always async)\n      expectTypeOf(s.functionMethod()(1)).toEqualTypeOf<Promise<number>>(); // (pipelining)\n\n      expectTypeOf(s.asyncFunctionMethod()).toMatchTypeOf<Promise<unknown>>();\n      using f2 = await s.asyncFunctionMethod();\n      expectTypeOf(f2).toEqualTypeOf<RpcStub<(x: number) => number>>();\n      expectTypeOf(s.asyncFunctionMethod()(1)).toEqualTypeOf<Promise<number>>(); // (pipelining)\n\n      expectTypeOf(s.asyncAsyncFunctionMethod()).toMatchTypeOf<\n        Promise<unknown>\n      >();\n      using f3 = await s.asyncAsyncFunctionMethod();\n      expectTypeOf(f3).toEqualTypeOf<RpcStub<(x: number) => Promise<number>>>();\n      expectTypeOf(f3(1)).toEqualTypeOf<Promise<number>>();\n      expectTypeOf(s.asyncAsyncFunctionMethod()(1)).toEqualTypeOf<\n        Promise<number>\n      >(); // (pipelining)\n\n      expectTypeOf(s.functionWithExtrasMethod()).toMatchTypeOf<\n        Promise<unknown>\n      >();\n      using f4 = await s.functionWithExtrasMethod();\n      type F4 = { (x: number): number; y: string };\n      expectTypeOf(f4).toEqualTypeOf<RpcStub<F4>>();\n      expectTypeOf(f4(1)).toEqualTypeOf<Promise<number>>(); // (always async)\n      expectTypeOf(f4.y).toEqualTypeOf<Promise<string>>(); // (always async)\n      expectTypeOf(s.functionWithExtrasMethod()(1)).toEqualTypeOf<\n        Promise<number>\n      >(); // (pipelining)\n      expectTypeOf(s.functionWithExtrasMethod().y).toEqualTypeOf<\n        Promise<string>\n      >(); // (pipelining)\n    }\n\n    // Check methods returning objects (including pipelining/ERM)\n    {\n      const s = env.RPC_SERVICE;\n\n      expectTypeOf(s.objectProperty).toMatchTypeOf<Promise<unknown>>();\n      using o = await s.objectProperty;\n      expectTypeOf(o.w).toEqualTypeOf<number>();\n      expectTypeOf(o.x).toEqualTypeOf<number>();\n      expectTypeOf(o.y).toEqualTypeOf<RpcStub<() => number>>();\n      expectTypeOf(o.y()).toEqualTypeOf<Promise<number>>(); // (always async)\n      expectTypeOf(o.z).toEqualTypeOf<\n        RpcStub<(a: boolean) => Promise<number>>\n      >();\n      expectTypeOf(o.z(true)).toEqualTypeOf<Promise<number>>();\n\n      expectTypeOf(s.objectProperty.w).toEqualTypeOf<Promise<number>>(); // (pipelining)\n      expectTypeOf(s.objectProperty.x).toEqualTypeOf<Promise<number>>(); // (pipelining)\n      expectTypeOf(s.objectProperty.y()).toEqualTypeOf<Promise<number>>(); // (pipelining)\n      expectTypeOf(s.objectProperty.z(false)).toEqualTypeOf<Promise<number>>(); // (pipelining)\n\n      expectTypeOf(s.everySerializable).not.toBeNever();\n\n      // Verify serializable composite objects defined with \"type\" keyword\n      const oType = await s.methodReturnsTypeObject();\n      expectTypeOf(oType).not.toBeNever();\n      expectTypeOf(oType.fieldString).toEqualTypeOf<string>();\n      expectTypeOf(oType.fieldCallback).toEqualTypeOf<\n        RpcStub<(p: string) => number>\n      >(); // stubified\n      expectTypeOf(oType.fieldBasicMap).toEqualTypeOf<Map<string, number>>();\n      expectTypeOf(oType.fieldComplexMap).toEqualTypeOf<\n        Map<\n          string,\n          {\n            fieldString: string;\n            fieldCallback: RpcStub<(p: string) => number>; // stubified\n          }\n        >\n      >();\n      expectTypeOf(oType.fieldSet).toEqualTypeOf<Set<string>>();\n      expectTypeOf(oType.fieldSubLevel.fieldString).toEqualTypeOf<string>();\n      expectTypeOf(oType.fieldSubLevel.fieldCallback).toEqualTypeOf<\n        RpcStub<(p: string) => number>\n      >(); // stubified\n\n      // Verify serializable composite objects defined with \"interface\" keyword\n      const oInterface = await s.methodReturnsInterfaceObject();\n      expectTypeOf(oInterface).not.toBeNever();\n      expectTypeOf(oInterface.fieldString).toEqualTypeOf<string>();\n      expectTypeOf(oInterface.fieldCallback).toEqualTypeOf<\n        RpcStub<(p: string) => number>\n      >(); // stubified\n      expectTypeOf(oInterface.fieldBasicMap).toEqualTypeOf<\n        Map<string, number>\n      >();\n      expectTypeOf(oInterface.fieldComplexMap).toEqualTypeOf<\n        Map<\n          string,\n          {\n            fieldString: string;\n            fieldCallback: RpcStub<(p: string) => number>; // stubified\n          }\n        >\n      >();\n      expectTypeOf(oInterface.fieldSet).toEqualTypeOf<Set<string>>();\n      expectTypeOf(\n        oInterface.fieldSubLevelInline.fieldString\n      ).toEqualTypeOf<string>();\n      expectTypeOf(oInterface.fieldSubLevelInline.fieldCallback).toEqualTypeOf<\n        RpcStub<(p: string) => number>\n      >(); // stubified\n      expectTypeOf(\n        oInterface.fieldSubLevelInterface.fieldString\n      ).toEqualTypeOf<string>();\n      expectTypeOf(\n        oInterface.fieldSubLevelInterface.fieldCallback\n      ).toEqualTypeOf<RpcStub<(p: string) => number>>(); // stubified\n\n      expectTypeOf(s.nonSerializable1).returns.toBeNever();\n      // Note: Since one of the object's members is non-serializable,\n      //   the entire object is resolved as 'never'.\n      expectTypeOf(s.nonSerializable2).returns.toBeNever();\n      expectTypeOf(s.nonSerializable3).returns.toBeNever();\n\n      // Verify serializable objects without any stubs are still disposable\n      (await s.everySerializable)[Symbol.dispose]();\n\n      // Verify types passed by value can still be pipelined\n      expectTypeOf(s.everySerializable.Object.a.b).toEqualTypeOf<\n        Promise<number>\n      >;\n      // TODO(now): these next two don't actually work, should they?\n      expectTypeOf(s.everySerializable.Array[0]).toEqualTypeOf<Promise<number>>;\n      expectTypeOf(await s.everySerializable.Map.get('a')).toEqualTypeOf<\n        number | undefined\n      >;\n\n      // Verify stubable types replaced with stubs\n      using ecs = await s.everyCompositeSerializable;\n      expectTypeOf(ecs.Map).toEqualTypeOf<\n        Map<RpcStub<TestCounter>, RpcStub<TestCounter>>\n      >();\n      expectTypeOf(ecs.Set).toEqualTypeOf<Set<RpcStub<TestCounter>>>();\n      expectTypeOf(ecs.Array).toEqualTypeOf<Array<RpcStub<TestCounter>>>();\n      expectTypeOf(ecs.ReadonlyArray).toEqualTypeOf<\n        ReadonlyArray<RpcStub<TestCounter>>\n      >();\n      expectTypeOf(ecs.Object).toEqualTypeOf<{\n        a: { b: RpcStub<TestCounter> };\n      }>();\n    }\n\n    // Check methods returning targets (including pipelining/ERM)\n    {\n      const s = env.RPC_OBJECT.get(env.RPC_OBJECT.newUniqueId());\n\n      expectTypeOf(s.targetMethod()).toMatchTypeOf<Promise<unknown>>();\n      using t1 = await s.targetMethod();\n      expectTypeOf(t1).toEqualTypeOf<RpcStub<TestCounter>>();\n      expectTypeOf(t1.dup()).toEqualTypeOf(t1);\n      expectTypeOf(t1.value).toEqualTypeOf<Promise<number>>(); // (always async)\n      expectTypeOf(t1.increment()).toEqualTypeOf<Promise<number>>(); // (always async)\n      expectTypeOf(t1.increment(1)).toEqualTypeOf<Promise<number>>(); // (always async)\n      expectTypeOf(t1.copy()).toMatchTypeOf<Promise<RpcStub<TestCounter>>>();\n\n      expectTypeOf(s.targetMethod().copy().increment(1)).toEqualTypeOf<\n        Promise<number>\n      >(); // (pipelining)\n\n      expectTypeOf(s.asyncTargetMethod()).toMatchTypeOf<Promise<unknown>>();\n      using t2 = await s.asyncTargetMethod();\n      expectTypeOf(t2).toEqualTypeOf<RpcStub<TestCounter>>();\n    }\n\n    // Check methods accepting stubs\n    {\n      expectTypeOf(env.RPC_SERVICE.stubMethod).toEqualTypeOf<\n        (\n          callback: (x: number) => number,\n          counter: TestCounter,\n          map: Map<TestCounter, TestCounter>,\n          set: Set<TestCounter>,\n          array: Array<TestCounter>,\n          readonlyArray: ReadonlyArray<TestCounter>,\n          object: { a: { b: TestCounter } }\n        ) => Promise<number>\n      >();\n    }\n\n    // Check loopback stubs support same operations as remote stubs\n    {\n      const stub = new RpcStub(new TestCounter(42));\n      expectTypeOf(stub).toEqualTypeOf<RpcStub<TestCounter>>();\n      expectTypeOf(stub.value).toEqualTypeOf<Promise<number>>(); // (always async)\n      expectTypeOf(stub.copy().increment(1)).toEqualTypeOf<Promise<number>>(); // (pipelining)\n\n      // @ts-expect-error requires stubable type\n      new RpcStub(1);\n    }\n\n    // Check can't override `dup()`\n    {\n      const stub = new RpcStub(new TestCounter(42));\n      expectTypeOf(stub.dup).toEqualTypeOf<() => RpcStub<TestCounter>>();\n    }\n\n    // Check methods returning base types are not stubified\n    {\n      const s = env.RPC_OBJECT.get(env.RPC_OBJECT.newUniqueId());\n\n      expectTypeOf(s.fetch(_request)).toMatchTypeOf<Promise<Response>>();\n      expectTypeOf(s.complexTypes()).toMatchTypeOf<\n        Promise<{\n          undefined: undefined;\n          void: void;\n          null: null;\n          boolean: boolean;\n          number: number;\n          bigint: bigint;\n          string: string;\n          ArrayBuffer: ArrayBuffer;\n          DataView: DataView;\n          Date: Date;\n          Error: Error;\n          RegExp: RegExp;\n          ReadableStream: ReadableStream;\n          WritableStream: WritableStream;\n          Request: Request;\n          Response: Response;\n          Headers: Headers;\n          nested: {\n            undefined: undefined;\n            void: void;\n            null: null;\n            boolean: boolean;\n            number: number;\n            bigint: bigint;\n            string: string;\n            ArrayBuffer: ArrayBuffer;\n            DataView: DataView;\n            Date: Date;\n            Error: Error;\n            RegExp: RegExp;\n            ReadableStream: ReadableStream;\n            WritableStream: WritableStream;\n            Request: Request;\n            Response: Response;\n            Headers: Headers;\n          };\n        }>\n      >;\n    }\n\n    return new Response();\n  },\n};\n"
  },
  {
    "path": "types/test/types/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"lib\": [\"esnext\"],\n    \"strict\": true,\n    \"types\": [\"../../../bazel-bin/types/definitions/experimental\"],\n    \"noEmit\": true\n  },\n  \"include\": [\"**/*.ts\"]\n}\n"
  },
  {
    "path": "types/tsconfig.json",
    "content": "{\n  \"extends\": \"../tools/base.tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"lib\": [\"ESNext\", \"dom\", \"dom.iterable\"],\n    \"types\": [\"@types/node\"],\n    \"sourceMap\": true,\n    \"baseUrl\": \".\",\n    \"rootDir\": \".\",\n    \"paths\": {\n      \"@workerd/*\": [\"../bazel-bin/src/workerd/*\"]\n    },\n    \"checkJs\": true,\n    \"composite\": true,\n    \"skipLibCheck\": true,\n\n    \"exactOptionalPropertyTypes\": false,\n    \"strictNullChecks\": false,\n    \"noImplicitReturns\": false,\n    \"noImplicitOverride\": false,\n    \"verbatimModuleSyntax\": false\n  },\n  \"include\": [\"src/**/*.ts\", \"test/**/*.ts\", \"scripts/**/*.ts\", \"eslint.config.mjs\"],\n  \"exclude\": [\"test/types/**/*.ts\", \"src/worker/**\", \"node_modules\", \"**/*.mjs\"]\n}\n"
  }
]